Initial commit of respository.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..cfe6248
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,48 @@
+# Contributing to the Checker Framework
+
+Thank you for contributing to the Checker Framework!  This project is a
+community effort of [more than 110
+developers](https://checkerframework.org/manual/#credits), plus countless
+more people who have contributed bug reports and feature suggestions.  We
+couldn't do it without your help.
+
+
+## Reporting bugs
+
+Please see the [bug
+reporting](https://checkerframework.org/manual/#reporting-bugs) section of
+the Checker Framework manual.
+
+If the documentation is incorrect, incomplete, or confusing, that is a
+bug, and we want to fix it.  Please report it.
+
+
+## Submitting changes
+
+Please see the [pull
+requests](https://rawgit.com/typetools/checker-framework/master/docs/developer/developer-manual.html#pull-requests)
+section of the Developer Manual.
+
+Submit changes to the annotated JDK at https://github.com/typetools/jdk/pulls .
+Annotations for other libraries can be contributed as stub files in this
+repository, in a fork of the library in https://github.com/typetools/, or
+in the library's own repository.
+
+Do you want to contribute to the project, but you are not sure what issue
+to fix or what feature to add?  Use the tool in your daily work, and when
+you encounter a limitation that bothers you, fix that one.  The ["help
+wanted"](https://github.com/typetools/checker-framework/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)
+label marks issues that require less deep knowledge and may be appropriate
+for a newcomer to the codebase.
+
+
+## License
+
+By contributing, you agree that your contributions will be licensed under the
+existing [license](LICENSE.txt), usually GPL2 or MIT License.
+
+
+## Code of conduct
+
+When interacting with other people, please abide by the [Contributor
+Covenant](https://www.contributor-covenant.org/version/2/0/code_of_conduct).
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..827585f
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,413 @@
+The Checker Framework
+Copyright 2004-present by the Checker Framework developers
+
+
+Most of the Checker Framework is licensed under the GNU General Public
+License, version 2 (GPL2), with the classpath exception.  The text of this
+license appears below.  This is the same license used for OpenJDK.
+
+A few parts of the Checker Framework have more permissive licenses, notably
+the parts that you might want to include with your own program.
+
+ * The annotations and utility files are licensed under the MIT License.
+   (The text of this license also appears below.)  This applies to
+   checker-qual*.jar and checker-util.jar and all the files that appear in
+   them, which is all files in checker-qual and checker-util directories.
+   It also applies to the cleanroom implementations of
+   third-party annotations (in checker/src/testannotations/,
+   framework/src/main/java/org/jmlspecs/, and
+   framework/src/main/java/com/google/).
+
+The Checker Framework includes annotations for some libraries.  Those in
+.astub files use the MIT License.  Those in https://github.com/typetools/jdk
+(which appears in the annotated-jdk directory of file checker.jar) use the
+GPL2 license.
+
+Some external libraries that are included with the Checker Framework
+distribution have different licenses.  Here are some examples.
+
+ * JavaParser is dual licensed under the LGPL or the Apache license -- you
+   may use it under whichever one you want.  (The JavaParser source code
+   contains a file with the text of the GPL, but it is not clear why, since
+   JavaParser does not use the GPL.)  See
+   https://github.com/typetools/stubparser .
+
+ * Annotation Tools (https://github.com/typetools/annotation-tools) uses
+   the MIT license.
+
+ * Libraries in plume-lib (https://github.com/plume-lib/) are licensed
+   under the MIT License.
+
+===========================================================================
+
+The GNU General Public License (GPL)
+
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it.  By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users.  This General Public License applies to
+most of the Free Software Foundation's software and to any other program whose
+authors commit to using it.  (Some other Free Software Foundation software is
+covered by the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price.  Our
+General Public Licenses are designed to make sure that you have the freedom to
+distribute copies of free software (and charge for this service if you wish),
+that you receive source code or can get it if you want it, that you can change
+the software or use pieces of it in new free programs; and that you know you
+can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny
+you these rights or to ask you to surrender the rights.  These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for
+a fee, you must give the recipients all the rights that you have.  You must
+make sure that they, too, receive or can get the source code.  And you must
+show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2)
+offer you this license which gives you legal permission to copy, distribute
+and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software.  If the
+software is modified by someone else and passed on, we want its recipients to
+know that what they have is not the original, so that any problems introduced
+by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents.  We
+wish to avoid the danger that redistributors of a free program will
+individually obtain patent licenses, in effect making the program proprietary.
+To prevent this, we have made it clear that any patent must be licensed for
+everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms of
+this General Public License.  The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or
+translated into another language.  (Hereinafter, translation is included
+without limitation in the term "modification".) Each licensee is addressed as
+"you".
+
+Activities other than copying, distribution and modification are not covered by
+this License; they are outside its scope.  The act of running the Program is
+not restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made by
+running the Program).  Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as
+you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and
+disclaimer of warranty; keep intact all the notices that refer to this License
+and to the absence of any warranty; and give any other recipients of the
+Program a copy of this License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may
+at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus
+forming a work based on the Program, and copy and distribute such modifications
+or work under the terms of Section 1 above, provided that you also meet all of
+these conditions:
+
+    a) You must cause the modified files to carry prominent notices stating
+    that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in whole or
+    in part contains or is derived from the Program or any part thereof, to be
+    licensed as a whole at no charge to all third parties under the terms of
+    this License.
+
+    c) If the modified program normally reads commands interactively when run,
+    you must cause it, when started running for such interactive use in the
+    most ordinary way, to print or display an announcement including an
+    appropriate copyright notice and a notice that there is no warranty (or
+    else, saying that you provide a warranty) and that users may redistribute
+    the program under these conditions, and telling the user how to view a copy
+    of this License.  (Exception: if the Program itself is interactive but does
+    not normally print such an announcement, your work based on the Program is
+    not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License, and
+its terms, do not apply to those sections when you distribute them as separate
+works.  But when you distribute the same sections as part of a whole which is a
+work based on the Program, the distribution of the whole must be on the terms
+of this License, whose permissions for other licensees extend to the entire
+whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise the
+right to control the distribution of derivative or collective works based on
+the Program.
+
+In addition, mere aggregation of another work not based on the Program with the
+Program (or with a work based on the Program) on a volume of a storage or
+distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1 and
+2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable source
+    code, which must be distributed under the terms of Sections 1 and 2 above
+    on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three years, to
+    give any third party, for a charge no more than your cost of physically
+    performing source distribution, a complete machine-readable copy of the
+    corresponding source code, to be distributed under the terms of Sections 1
+    and 2 above on a medium customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer to
+    distribute corresponding source code.  (This alternative is allowed only
+    for noncommercial distribution and only if you received the program in
+    object code or executable form with such an offer, in accord with
+    Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it.  For an executable work, complete source code means all
+the source code for all modules it contains, plus any associated interface
+definition files, plus the scripts used to control compilation and installation
+of the executable.  However, as a special exception, the source code
+distributed need not include anything that is normally distributed (in either
+source or binary form) with the major components (compiler, kernel, and so on)
+of the operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the source
+code from the same place counts as distribution of the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License.  Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License.  However, parties who have received copies, or
+rights, from you under this License will not have their licenses terminated so
+long as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the Program
+or its derivative works.  These actions are prohibited by law if you do not
+accept this License.  Therefore, by modifying or distributing the Program (or
+any work based on the Program), you indicate your acceptance of this License to
+do so, and all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor to
+copy, distribute or modify the Program subject to these terms and conditions.
+You may not impose any further restrictions on the recipients' exercise of the
+rights granted herein.  You are not responsible for enforcing compliance by
+third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues), conditions
+are imposed on you (whether by court order, agreement or otherwise) that
+contradict the conditions of this License, they do not excuse you from the
+conditions of this License.  If you cannot distribute so as to satisfy
+simultaneously your obligations under this License and any other pertinent
+obligations, then as a consequence you may not distribute the Program at all.
+For example, if a patent license would not permit royalty-free redistribution
+of the Program by all those who receive copies directly or indirectly through
+you, then the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply and
+the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or
+other property right claims or to contest validity of any such claims; this
+section has the sole purpose of protecting the integrity of the free software
+distribution system, which is implemented by public license practices.  Many
+people have made generous contributions to the wide range of software
+distributed through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing to
+distribute software through any other system and a licensee cannot impose that
+choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain
+countries either by patents or by copyrighted interfaces, the original
+copyright holder who places the Program under this License may add an explicit
+geographical distribution limitation excluding those countries, so that
+distribution is permitted only in or among countries not thus excluded.  In
+such case, this License incorporates the limitation as if written in the body
+of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the
+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 Program
+specifies a version number of this License which applies to it and "any later
+version", you have the option of following the terms and conditions either of
+that version or of any later version published by the Free Software Foundation.
+If the Program does not specify a version number of this License, you may
+choose any version ever published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission.  For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status of
+all derivatives of our free software and of promoting the sharing and reuse of
+software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN OTHERWISE
+STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE
+PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND
+PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE,
+YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
+ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE
+PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR
+INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA
+BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER
+OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible
+use to the public, the best way to achieve this is to make it free software
+which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program.  It is safest to attach
+them to the start of each source file to most effectively convey the exclusion
+of warranty; and each file should have at least the "copyright" line and a
+pointer to where the full notice is found.
+
+    One line to give the program's name and a brief idea of what it does.
+
+    Copyright (C) <year> <name of author>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by the Free
+    Software Foundation; either version 2 of the License, or (at your option)
+    any later version.
+
+    This program 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 General Public License for
+    more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc., 59
+    Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this when it
+starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
+    with ABSOLUTELY NO WARRANTY; for details type 'show w'.  This is free
+    software, and you are welcome to redistribute it under certain conditions;
+    type 'show c' for details.
+
+The hypothetical commands 'show w' and 'show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may be
+called something other than 'show w' and 'show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.  Here
+is a sample; alter the names:
+
+    Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+    'Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+    signature of Ty Coon, 1 April 1989
+
+    Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General Public
+License instead of this License.
+
+
+"CLASSPATH" EXCEPTION TO THE GPL
+
+Certain source files distributed by Oracle America and/or its affiliates are
+subject to the following clarification and special exception to the GPL, but
+only where Oracle has expressly included in the particular source file's header
+the words "Oracle designates this particular file as subject to the "Classpath"
+exception as provided by Oracle in the LICENSE file that accompanied this code."
+
+    Linking this library statically or dynamically with other modules is making
+    a combined work based on this library.  Thus, the terms and conditions of
+    the GNU General Public License cover the whole combination.
+
+    As a special exception, the copyright holders of this library give you
+    permission to link this library with independent modules to produce an
+    executable, regardless of the license terms of these independent modules,
+    and to copy and distribute the resulting executable under terms of your
+    choice, provided that you also meet, for each linked independent module,
+    the terms and conditions of the license of that module.  An independent
+    module is a module which is not derived from or based on this library.  If
+    you modify this library, you may extend this exception to your version of
+    the library, but you are not obligated to do so.  If you do not wish to do
+    so, delete this exception statement from your version.
+
+===========================================================================
+
+MIT License:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+===========================================================================
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a2197d7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
+Please see the Checker Framework manual
+([HTML](https://checkerframework.org/manual/),
+[PDF](https://checkerframework.org/manual/checker-framework-manual.pdf)).
+
+The history of releases and changes is in file
+[docs/CHANGELOG.md](docs/CHANGELOG.md).
+
+Documentation for Checker Framework developers
+is in directory `docs/developer/`.
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 0000000..371b4ed
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,228 @@
+# Workaround for https://status.dev.azure.com/_event/179641421
+trigger:
+  branches:
+    include:
+    - '*'
+pr:
+  branches:
+    include:
+    - '*'
+
+
+jobs:
+- job: junit_tests_jdk8
+  dependsOn:
+   - junit_tests_jdk11
+   - nonjunit_tests_jdk11
+   - misc_jdk11
+   - typecheck_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk8:latest
+  timeoutInMinutes: 70
+  steps:
+  - checkout: self
+    fetchDepth: 25
+  - bash: ./checker/bin-devel/test-cftests-junit.sh
+    displayName: test-cftests-junit.sh
+- job: junit_tests_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk11:latest
+  steps:
+  - checkout: self
+    fetchDepth: 25
+  - bash: ./checker/bin-devel/test-cftests-junit.sh
+    displayName: test-cftests-junit.sh
+- job: nonjunit_tests_jdk8
+  dependsOn:
+   - junit_tests_jdk11
+   - nonjunit_tests_jdk11
+   - misc_jdk11
+   - typecheck_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk8:latest
+  steps:
+  - checkout: self
+    fetchDepth: 25
+  - bash: ./checker/bin-devel/test-cftests-nonjunit.sh
+    displayName: test-cftests-nonjunit.sh
+- job: nonjunit_tests_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk11:latest
+  steps:
+  - checkout: self
+    fetchDepth: 25
+  - bash: ./checker/bin-devel/test-cftests-nonjunit.sh
+    displayName: test-cftests-nonjunit.sh
+- job: inference_tests_jdk8
+  dependsOn:
+   - junit_tests_jdk11
+   - inference_tests_jdk11
+   - misc_jdk11
+   - typecheck_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk8:latest
+  steps:
+  - checkout: self
+    fetchDepth: 25
+  - bash: ./checker/bin-devel/test-cftests-inference.sh
+    displayName: test-cftests-inference.sh
+- job: inference_tests_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk11:latest
+  steps:
+  - checkout: self
+    fetchDepth: 25
+  - bash: ./checker/bin-devel/test-cftests-inference.sh
+    displayName: test-cftests-inference.sh
+- job: misc_jdk8
+  ## The dependsOn is commented out because misc_jdk8 sometimes fails when misc_jdk11 does not.
+  # dependsOn:
+  #  - junit_tests_jdk11
+  #  - nonjunit_tests_jdk11
+  #  - misc_jdk11
+  #  - typecheck_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk8-plus:latest
+  steps:
+  - checkout: self
+    fetchDepth: 1000
+  - bash: ./checker/bin-devel/test-misc.sh
+    displayName: test-misc.sh
+- job: misc_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk11-plus:latest
+  steps:
+  - checkout: self
+    fetchDepth: 1000
+  - bash: ./checker/bin-devel/test-misc.sh
+    displayName: test-misc.sh
+- job: typecheck_jdk8
+  dependsOn:
+   - junit_tests_jdk11
+   - nonjunit_tests_jdk11
+   - misc_jdk11
+   - typecheck_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk8-plus:latest
+  steps:
+  - checkout: self
+    fetchDepth: 1000
+  - bash: ./checker/bin-devel/test-typecheck.sh
+    displayName: test-typecheck.sh
+- job: typecheck_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk11-plus:latest
+  steps:
+  - checkout: self
+    fetchDepth: 1000
+  - bash: ./checker/bin-devel/test-typecheck.sh
+    displayName: test-typecheck.sh
+- job: daikon_jdk8
+  dependsOn:
+   - junit_tests_jdk11
+   - nonjunit_tests_jdk11
+   - misc_jdk11
+   - typecheck_jdk11
+   ## Commented to reduce latency and eliminate the "daikon_jdk11 -> daikon_jdk8" critical path.
+   # - daikon_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk8:latest
+  timeoutInMinutes: 70
+  steps:
+  - checkout: self
+    fetchDepth: 25
+  - bash: ./checker/bin-devel/test-daikon.sh
+    displayName: test-daikon.sh
+- job: daikon_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk11:latest
+  timeoutInMinutes: 80
+  steps:
+  - checkout: self
+    fetchDepth: 25
+  - bash: ./checker/bin-devel/test-daikon.sh
+    displayName: test-daikon.sh
+- job: guava_jdk8
+  dependsOn:
+   - junit_tests_jdk11
+   - nonjunit_tests_jdk11
+   - misc_jdk11
+   - typecheck_jdk11
+   - guava_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk8:latest
+  steps:
+  - checkout: self
+    fetchDepth: 25
+  - bash: ./checker/bin-devel/test-guava.sh
+    displayName: test-guava.sh
+- job: guava_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk11:latest
+  steps:
+  - checkout: self
+    fetchDepth: 25
+  - bash: ./checker/bin-devel/test-guava.sh
+    displayName: test-guava.sh
+- job: plume_lib_jdk8
+  dependsOn:
+   - junit_tests_jdk11
+   - nonjunit_tests_jdk11
+   - misc_jdk11
+   - typecheck_jdk11
+   - plume_lib_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk8:latest
+  steps:
+  - checkout: self
+    fetchDepth: 25
+  - bash: ./checker/bin-devel/test-plume-lib.sh
+    displayName: test-plume-lib.sh
+- job: plume_lib_jdk11
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: mdernst/cf-ubuntu-jdk11:latest
+  steps:
+  - checkout: self
+    fetchDepth: 25
+  - bash: ./checker/bin-devel/test-plume-lib.sh
+    displayName: test-plume-lib.sh
+# - job: downstream_jdk8
+#   dependsOn:
+#    - junit_tests_jdk11
+#    - nonjunit_tests_jdk11
+#    - misc_jdk11
+#    - typecheck_jdk11
+#    - downstream_jdk11
+#   pool:
+#     vmImage: 'ubuntu-latest'
+#   container: mdernst/cf-ubuntu-jdk8:latest
+#   steps:
+#   - checkout: self
+#     fetchDepth: 25
+#   - bash: ./checker/bin-devel/test-downstream.sh
+#     displayName: test-downstream.sh
+# - job: downstream_jdk11
+#   pool:
+#     vmImage: 'ubuntu-latest'
+#   container: mdernst/cf-ubuntu-jdk11:latest
+#   steps:
+#   - checkout: self
+#     fetchDepth: 25
+#   - bash: ./checker/bin-devel/test-downstream.sh
+#     displayName: test-downstream.sh
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..844686d
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,1131 @@
+import de.undercouch.gradle.tasks.download.Download
+
+plugins {
+    // https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow (v5 requires Gradle 5)
+    id 'com.github.johnrengelman.shadow' version '6.1.0'
+    // https://plugins.gradle.org/plugin/de.undercouch.download
+    id "de.undercouch.download" version "4.1.1"
+    id 'java'
+    // https://github.com/tbroyer/gradle-errorprone-plugin
+    id "net.ltgt.errorprone" version "2.0.1"
+    // https://plugins.gradle.org/plugin/org.ajoberstar.grgit
+    id 'org.ajoberstar.grgit' version '4.1.0' apply false
+    // https://github.com/n0mer/gradle-git-properties ; target is: generateGitProperties
+    id "com.gorylenko.gradle-git-properties" version "2.3.1"
+}
+apply plugin: "de.undercouch.download"
+
+import org.ajoberstar.grgit.Grgit
+
+repositories {
+    jcenter()
+    mavenCentral()
+}
+
+gitProperties {
+    // logically belongs in framework, but only checker resources are copied to .jar file
+    gitPropertiesResourceDir = file("${project.rootDir}/checker/src/main/resources")
+}
+
+ext {
+    release = false
+
+    // On a Java 8 JVM, use error-prone javac and source/target 8.
+    // On a Java 9+ JVM, use the host javac, default source/target, and required module flags.
+    isJava8 = JavaVersion.current() == JavaVersion.VERSION_1_8
+
+    errorproneJavacVersion = '9+181-r4173-1'
+
+    parentDir = file("${rootDir}/../").absolutePath
+
+    annotationTools = "${parentDir}/annotation-tools"
+    afu = "${annotationTools}/annotation-file-utilities"
+
+    stubparser = "${parentDir}/stubparser"
+    stubparserJar = "${stubparser}/javaparser-core/target/stubparser-3.20.2.1.jar"
+
+    jtregHome = "${parentDir}/jtreg"
+    formatScriptsHome = "${project(':checker').projectDir}/bin-devel/.run-google-java-format"
+    plumeScriptsHome = "${project(':checker').projectDir}/bin-devel/.plume-scripts"
+    htmlToolsHome = "${project(':checker').projectDir}/bin-devel/.html-tools"
+
+    javadocMemberLevel = JavadocMemberLevel.PROTECTED
+
+    // The local git repository, typically in the .git directory, but not for worktrees.
+    // This value is always overwritten, but Gradle needs the variable to be initialized.
+    localRepo = ".git"
+}
+// Keep in sync with check in org.checkerframework.framework.source.SourceChecker.init
+// and with text in #installation
+switch (JavaVersion.current()) {
+    case JavaVersion.VERSION_1_9:
+    case JavaVersion.VERSION_1_10:
+    case JavaVersion.VERSION_12:
+        logger.warn("The Checker Framework has only been tested with JDK 8 and 11." +
+                " Found version " + JavaVersion.current().majorVersion);
+        break;
+    case JavaVersion.VERSION_1_8:
+    case JavaVersion.VERSION_11:
+        break; // Supported versions
+    default:
+        throw new GradleException("Build the Checker Framework with JDK 8 or JDK 11." +
+                " Found version " + JavaVersion.current().majorVersion);
+}
+
+task setLocalRepo(type:Exec) {
+    commandLine 'git', 'worktree', 'list'
+    standardOutput = new ByteArrayOutputStream()
+    doLast {
+       String worktreeList = standardOutput.toString()
+       localRepo = worktreeList.substring(0, worktreeList.indexOf(" ")) + "/.git"
+    }
+}
+
+// No group so it does not show up in the output of `gradlew tasks`
+task installGitHooks(type: Copy, dependsOn: 'setLocalRepo') {
+    description 'Copies git hooks to .git directory'
+    from files("checker/bin-devel/git.post-merge", "checker/bin-devel/git.pre-commit")
+    rename('git\\.(.*)', '$1')
+    into localRepo + "/hooks"
+}
+
+allprojects {
+    apply plugin: 'java'
+    apply plugin: 'com.github.johnrengelman.shadow'
+    apply plugin: "de.undercouch.download"
+    apply plugin: 'net.ltgt.errorprone'
+
+    group 'org.checkerframework'
+    // Increment the minor version (second number) rather than just the patch
+    // level (third number) if:
+    //   * any new checkers have been added, or
+    //   * backward-incompatible changes have been made to APIs or elsewhere.
+    version '3.13.1-SNAPSHOT'
+
+    repositories {
+        mavenCentral()
+    }
+
+    configurations {
+        javacJar
+
+        // Holds the combined classpath of all subprojects including the subprojects themselves.
+        allProjects
+
+        // Exclude checker-qual dependency added by Error Prone to avoid a circular dependency.
+        annotationProcessor.exclude group:'org.checkerframework', module:'checker-qual'
+    }
+
+    configurations {
+        checkerFatJar
+    }
+    dependencies {
+        if (isJava8) {
+            javacJar group: 'com.google.errorprone', name: 'javac', version: "$errorproneJavacVersion"
+        }
+
+        errorproneJavac("com.google.errorprone:javac:$errorproneJavacVersion")
+
+        allProjects subprojects
+        checkerFatJar project(path: ':checker', configuration: 'shadow')
+    }
+
+
+    // After all the tasks have been created, modify some of them.
+    afterEvaluate {
+        // Add the fat checker.jar to the classpath of every Javadoc task. This allows Javadoc in
+        // any module to reference classes in any other module.
+        // Also, build and use ManualTaglet as a taglet.
+        tasks.withType(Javadoc) {
+            def tagletVersion = isJava8 ? 'taglet' : 'tagletJdk11'
+
+            dependsOn(':checker:shadowJar')
+            dependsOn(":framework-test:${tagletVersion}Classes")
+            doFirst {
+                options.encoding = 'UTF-8'
+                if (!name.equals("javadocDoclintAll")) {
+                    options.memberLevel = javadocMemberLevel
+                }
+                classpath += rootProject.configurations.getByName('checkerFatJar').asFileTree
+                if (isJava8) {
+                    classpath += configurations.javacJar
+                }
+                options.taglets 'org.checkerframework.taglet.ManualTaglet'
+                options.tagletPath(project(':framework-test').sourceSets."${tagletVersion}".output.classesDirs.getFiles() as File[])
+
+                // We want to link to Java 9 documentation of the compiler classes since we use Java 9
+                // versions of those classes and Java 8 for everything else.  Because the compiler classes are not
+                // a part of the main documentation of Java 8, javadoc links to the Java 9 versions.
+                // TODO, this creates broken links to the com.sun.tools.javac package.
+                options.links = ['https://docs.oracle.com/javase/8/docs/api/', 'https://docs.oracle.com/javase/9/docs/api/']
+                // This file is looked for by Javadoc.
+                file("${destinationDir}/resources/fonts/").mkdirs()
+                ant.touch(file: "${destinationDir}/resources/fonts/dejavu.css")
+                options.addStringOption('source', '8')
+                // "-Xwerror" requires Javadoc everywhere.  Currently, CI jobs require Javadoc only
+                // on changed lines.  Enable -Xwerror in the future when all Javadoc exists.
+                // options.addBooleanOption('Xwerror', true)
+                options.addStringOption('Xmaxwarns', '99999')
+            }
+        }
+
+        // Add standard javac options
+        tasks.withType(JavaCompile) { compilationTask ->
+            dependsOn(':installGitHooks')
+            // Put source files in deterministic order, for debugging.
+            compilationTask.source = compilationTask.source.sort()
+            sourceCompatibility = 8
+            targetCompatibility = 8
+            // Because the target is 8, all of the public compiler classes are accessible, so
+            // --add-exports are not required, (nor are they allowed with target 8). See
+            // https://openjdk.java.net/jeps/247 for details on compiling for older versions.
+
+            // When sourceCompatibilty is changed to 11, then the following will be required.
+            // options.compilerArgs += [
+            // "--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
+            // "--add-exports", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+            // "--add-exports", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
+            // "--add-exports", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
+            // "--add-exports", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
+            // "--add-exports", "jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+            // "--add-exports", "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
+            // "--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+            // "--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+            // ]
+            // This is equivalent to writing "exports jdk.compiler/... to ALL-UNNAMED" in the
+            // module-info.java of jdk.compiler, so corresponding --add-opens are only required for
+            // reflective access to private members.
+            //
+            // From https://openjdk.java.net/jeps/261, Section titled: "Breaking encapsulation"
+            // "The effect of each instance [of --add-exports] is to add a qualified export of the
+            // named package from the source module to the target module. This is, essentially, a
+            // command-line form of an exports clause in a module declaration[...].
+            // [...]
+            // The --add-exports option enables access to the public types of a specified package.
+            // It is sometimes necessary to go further and enable access to all non-public elements
+            // via the setAccessible method of the core reflection API. The --add-opens option can
+            // be used, at run time, to do this."
+
+            options.failOnError = true
+            options.deprecation = true
+            options.compilerArgs += [
+                    '-g',
+                    '-Werror',
+                    // -options: To not get a warning about missing bootstrap classpath for Java 8 (once we use Java 9).
+                    // -fallthrough: Don't check fallthroughs.  Instead, use Error Prone.  Its
+                    // warnings are suppressible with a "// fall through" comment.
+                    "-Xlint:-options,-fallthrough",
+                    "-Xlint",
+            ]
+
+            options.encoding = 'UTF-8'
+            options.fork = true
+            if (isJava8) {
+                options.forkOptions.jvmArgs += ["-Xbootclasspath/p:${configurations.javacJar.asPath}".toString()]
+            }
+
+            // Error prone depends on checker-qual.jar, so don't run it on that project to avoid a circular dependency.
+            // TODO: enable Error Prone on test classes.
+            if (compilationTask.name.equals('compileJava') && !project.name.startsWith('checker-qual')) {
+                // Error Prone must be available in the annotation processor path
+                options.annotationProcessorPath = configurations.errorprone
+                // Enable Error Prone
+                options.errorprone.enabled = true
+                options.errorprone.disableWarningsInGeneratedCode = true
+                options.errorprone.errorproneArgs = [
+                        // Many compiler classes are interned.
+                        '-Xep:ReferenceEquality:OFF',
+                        // These might be worth fixing.
+                        '-Xep:DefaultCharset:OFF',
+                        // Not useful to suggest Splitter; maybe clean up.
+                        '-Xep:StringSplitter:OFF',
+                        // Too broad, rejects seemingly-correct code.
+                        '-Xep:EqualsGetClass:OFF',
+                        // Not a real problem
+                        '-Xep:MixedMutabilityReturnType:OFF',
+                        // Don't want to add a dependency to ErrorProne.
+                        '-Xep:AnnotateFormatMethod:OFF',
+                        // Warns for every use of "@checker_framework.manual"
+                        '-Xep:InvalidBlockTag:OFF',
+                        // Recommends writing @InlineMe which is an Error-Prone-specific annotation
+                        '-Xep:InlineMeSuggester:OFF',
+                        // -Werror halts the build if Error Prone issues a warning, which ensures that
+                        // the errors get fixed.  On the downside, Error Prone (or maybe the compiler?)
+                        // stops as soon as it issues one warning, rather than outputting them all.
+                        // https://github.com/google/error-prone/issues/436
+                        '-Werror',
+                ]
+            } else {
+                options.errorprone.enabled = false
+            }
+        }
+    }
+}
+
+task cloneAndBuildDependencies(type: Exec, group: 'Build') {
+    description 'Clones (or updates) and builds all dependencies'
+    executable 'checker/bin-devel/build.sh'
+}
+
+task maybeCloneAndBuildDependencies() {
+    // No group so it does not show up in the output of `gradlew tasks`
+    description 'Clones (or updates) and builds all dependencies if they are not present.'
+    onlyIf {
+        !file(stubparserJar).exists()
+        // The jdk repository is cloned via the copyAndMinimizeAnnotatedJdkFiles task that is run if
+        // the repository does not exist when building checker.jar.
+    }
+
+    doFirst {
+        if (file(stubparser).exists()) {
+            exec {
+                workingDir stubparser
+                executable 'git'
+                args = ['pull', '-q']
+                ignoreExitValue = true
+            }
+            exec {
+                workingDir stubparser
+                executable "${stubparser}/.travis-build-without-test.sh"
+            }
+        } else {
+            executable 'checker/bin-devel/build.sh'
+        }
+    }
+    doLast {
+        if (!file(stubparserJar).exists()) {
+            exec {
+                workingDir ${stubparser}/javaparser-core/target
+                executable 'ls'
+                ignoreExitValue = true
+            }
+            throw new RuntimeException("Can't find stubparser jar: " + stubparserJar)
+        }
+    }
+}
+
+task version(group: 'Documentation') {
+    description 'Print Checker Framework version'
+    doLast {
+        println version
+    }
+}
+
+/**
+ * Creates a task that runs the checker on the main source set of each subproject. The task is named
+ * "check${taskName}", for example "checkPurity" or "checkNullness".
+ *
+ * @param projectName name of the project
+ * @param taskName short name (often the checker name) to use as part of the task name
+ * @param checker fully qualified name of the checker to run
+ * @param args list of arguments to pass to the checker
+ */
+def createCheckTypeTask(projectName, taskName, checker, args = []) {
+    project("${projectName}").tasks.create(name: "check${taskName}", type: JavaCompile, dependsOn: ':checker:shadowJar') {
+        description "Run the ${taskName} Checker on the main sources."
+        group 'Verification'
+        // Always run the task.
+        outputs.upToDateWhen { false }
+        source = project("${projectName}").sourceSets.main.java
+        classpath = files(project("${projectName}").compileJava.classpath,project(':checker-qual').sourceSets.main.output)
+        destinationDir = file("${buildDir}")
+
+        options.annotationProcessorPath = files(project(':checker').tasks.shadowJar.archivePath)
+        options.compilerArgs += [
+                '-processor', "${checker}",
+                '-proc:only',
+                '-Xlint:-processing',
+                '-Xmaxerrs', '10000',
+                '-Xmaxwarns', '10000',
+                '-ArequirePrefixInWarningSuppressions',
+                '-AwarnUnneededSuppressions',
+        ]
+        options.compilerArgs += args
+        options.forkOptions.jvmArgs += ["-Xmx2g"]
+
+        if (isJava8) {
+            options.compilerArgs += [
+                "-source",
+                "8",
+                "-target",
+                "8"
+                ]
+        } else {
+            options.fork = true
+            options.forkOptions.jvmArgs += [
+                "--add-opens", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
+                ]
+       }
+    }
+}
+
+/**
+ * Returns a list of all the Java files that should be formatted for the given project. These are:
+ *
+ * All java files in the main sourceSet.
+ * All java files in the tests directory that compile.
+ *
+ * @param projectName name of the project to format
+ * @return a list of all Java files that should be formatted for projectName
+ */
+List<String> getJavaFilesToFormat(projectName) {
+    List<File> javaFiles = new ArrayList<>();
+    project(':' + projectName).sourceSets.forEach { set ->
+        javaFiles.addAll(set.java.files)
+    }
+
+    // Collect all java files in tests directory
+    fileTree("${project(projectName).projectDir}/tests").visit { details ->
+        // If you change this, also change checker/bin-devel/git.pre-commit
+        if (!details.path.contains("nullness-javac-errors")
+                && !details.path.contains("returnsreceiverdelomboked")
+                && !details.path.contains("build")
+                && details.name.endsWith('.java')) {
+            javaFiles.add(details.file)
+        }
+    }
+
+    // Collect all java files in jtreg directory
+    fileTree("${project(projectName).projectDir}/jtreg").visit { details ->
+        if (!details.path.contains("nullness-javac-errors") && details.name.endsWith('.java')) {
+            javaFiles.add(details.file)
+        }
+    }
+
+    // Collect all java files in jtregJdk11 directory
+    fileTree("${project(projectName).projectDir}/jtregJdk11").visit { details ->
+        if (!details.path.contains("nullness-javac-errors") && details.name.endsWith('.java')) {
+            javaFiles.add(details.file)
+        }
+    }
+
+    List<String> args = new ArrayList<>(javaFiles.size());
+    for (File f : javaFiles) {
+        args += project(projectName).relativePath(f)
+    }
+    return args
+}
+
+task htmlValidate(type: Exec, group: 'Format') {
+    description 'Validate that HTML files are well-formed'
+    executable 'html5validator'
+    args = [
+            "--ignore",
+            "/api/",
+            "/build/",
+            "/docs/manual/manual.html",
+            "/checker/jdk/nullness/src/java/lang/ref/package.html"
+    ]
+}
+
+
+// `gradle allJavadoc` builds the Javadoc for all modules in `docs/api`.
+//   This is what is published to checkerframework.org.
+// `gradle javadoc` builds the Javadoc for each sub-project in <subproject>/build/docs/javadoc/ .
+//   It's needed to create the Javadoc jars that we release in Maven Central.
+// To make javadoc for only one subproject, run `./gradlew javadoc`
+//   in the subproject or `./gradlew :checker:javadoc` at the top level.
+task allJavadoc(type: Javadoc, group: 'Documentation') {
+    description = 'Generates API documentation that includes all the modules.'
+    dependsOn(':checker:shadowJar', 'getPlumeScripts', 'getHtmlTools')
+    destinationDir = file("${rootDir}/docs/api")
+    source(project(':checker-util').sourceSets.main.allJava, project(':checker-qual').sourceSets.main.allJava, project(':checker').sourceSets.main.allJava, project(':framework').sourceSets.main.allJava,
+            project(':dataflow').sourceSets.main.allJava, project(':javacutil').sourceSets.main.allJava)
+
+    classpath = configurations.allProjects
+    if (isJava8) {
+        classpath += configurations.javacJar
+    }
+    doLast {
+        exec {
+            // Javadoc for to com.sun.tools.java.* is not publicly available, so these links are broken.
+            // This command removes those links.
+            workingDir "${rootDir}/docs/api"
+            executable "${plumeScriptsHome}/preplace"
+            args += ['<a href="https://docs.oracle.com/javase/9/docs/api/com/sun/tools/javac/.*?>(.*?)</a>', '\\1']
+        }
+        copy {
+            from 'docs/logo/Checkmark/CFCheckmark_favicon.png'
+            rename('CFCheckmark_favicon.png', 'favicon-checkerframework.png')
+            into "${rootDir}/docs/api"
+        }
+        exec {
+            workingDir "${rootDir}/docs/api"
+            executable "${htmlToolsHome}/html-add-favicon"
+            args += ['.', 'favicon-checkerframework.png']
+        }
+    }
+}
+
+// See documentation for allJavadoc task.
+javadoc.dependsOn(allJavadoc)
+
+configurations {
+    requireJavadoc
+}
+dependencies {
+    requireJavadoc "org.plumelib:require-javadoc:1.0.2"
+}
+task requireJavadoc(type: JavaExec, group: 'Documentation') {
+    description = 'Ensures that Javadoc documentation exists in source code.'
+    main = "org.plumelib.javadoc.RequireJavadoc"
+    classpath = configurations.requireJavadoc
+    args "checker/src/main/java", "dataflow/src/main/java", "framework-test/src/main/java", "framework/src/main/java", "javacutil/src/main/java"
+}
+
+
+/**
+ * Creates a task named taskName that runs javadoc with the -Xdoclint:all option.
+ *
+ * @param taskName the name of the task to create
+ * @param taskDescription description of the task
+ * @param memberLevel the JavadocMemberLevel to use
+ * @return the new task
+ */
+def createJavadocTask(taskName, taskDescription, memberLevel) {
+    tasks.create(name: taskName, type: Javadoc) {
+        description = taskDescription
+        destinationDir = file("${rootDir}/docs/tmpapi")
+        destinationDir.mkdirs()
+        subprojects.forEach {
+            if (!it.name.startsWith("checker-qual-android")) {
+                source += it.sourceSets.main.allJava
+            }
+        }
+
+        classpath = configurations.allProjects
+
+        destinationDir.deleteDir()
+        options.memberLevel = memberLevel
+        options.addBooleanOption('Xdoclint:all', true)
+        options.addStringOption('Xmaxwarns', '99999')
+
+        // options.addStringOption('skip', 'ClassNotToCheck|OtherClass')
+    }
+}
+
+createJavadocTask('javadocDoclintAll', 'Runs javadoc with -Xdoclint:all option.', JavadocMemberLevel.PRIVATE)
+
+task manual(group: 'Documentation') {
+    description 'Build the manual'
+    doLast {
+        exec {
+            commandLine "make", "-C", "docs/manual", "all"
+        }
+    }
+}
+
+// No group so it does not show up in the output of `gradlew tasks`
+task downloadJtreg(type: Download) {
+    description "Downloads and unpacks jtreg."
+    onlyIf { !(new File("${jtregHome}/lib/jtreg.jar").exists()) }
+    // src 'https://ci.adoptopenjdk.net/view/Dependencies/job/jtreg/lastSuccessfulBuild/artifact/jtreg-4.2.0-tip.tar.gz'
+    // If ci.adoptopenjdk.net is down, use this copy.
+    src 'https://checkerframework.org/jtreg-4.2.0-tip.tar.gz'
+    dest new File(buildDir, 'jtreg-4.2.0-tip.tar.gz')
+    overwrite true
+    retries 3
+    doLast {
+        copy {
+            from tarTree(dest)
+            into "${jtregHome}/.."
+        }
+        exec {
+            commandLine('chmod',  '+x', "${jtregHome}/bin/jtdiff", "${jtregHome}/bin/jtreg")
+        }
+    }
+}
+
+// See alternate implementation getCodeFormatScriptsInGradle below.
+// No group so it does not show up in the output of `gradlew tasks`
+task getCodeFormatScripts() {
+    description 'Obtain or update the run-google-java-format scripts'
+    if (file(formatScriptsHome).exists()) {
+        exec {
+            workingDir formatScriptsHome
+            executable 'git'
+            args = ['pull', '-q']
+            ignoreExitValue = true
+        }
+    } else {
+        exec {
+            workingDir "${formatScriptsHome}/../"
+            executable 'git'
+            args = ['clone', '-q', '--depth', '1', 'https://github.com/plume-lib/run-google-java-format.git', '.run-google-java-format']
+        }
+    }
+}
+
+// This implementation is preferable to the above because it does work in Gradle rather than in bash.
+// However, it fails in the presence of worktrees: https://github.com/ajoberstar/grgit/issues/97 .
+// No group so it does not show up in the output of `gradlew tasks`
+task getCodeFormatScriptsInGradle {
+  description "Obtain the run-google-java-format scripts"
+  doLast {
+    if (! new File(formatScriptsHome).exists()) {
+      // There is no support for the --depth argument:
+      // https://github.com/ajoberstar/grgit/issues/155   https://bugs.eclipse.org/bugs/show_bug.cgi?id=475615
+      def rgjfGit = Grgit.clone(dir: formatScriptsHome, uri: 'https://github.com/plume-lib/run-google-java-format.git')
+    } else {
+      def rgjfGit = Grgit.open(dir: formatScriptsHome)
+      rgjfGit.pull()
+    }
+  }
+}
+
+// No group so it does not show up in the output of `gradlew tasks`
+task getPlumeScripts() {
+    description 'Obtain or update plume-scripts'
+    if (file(plumeScriptsHome).exists()) {
+        exec {
+            workingDir plumeScriptsHome
+            executable 'git'
+            args = ['pull', '-q']
+            ignoreExitValue = true
+        }
+    } else {
+        exec {
+            workingDir "${plumeScriptsHome}/../"
+            executable 'git'
+            args = ['clone', '-q', '--depth', '1', 'https://github.com/plume-lib/plume-scripts.git', '.plume-scripts']
+        }
+    }
+}
+
+// No group so it does not show up in the output of `gradlew tasks`
+task getHtmlTools() {
+    description 'Obtain or update html-tools'
+    if (file(htmlToolsHome).exists()) {
+        exec {
+            workingDir htmlToolsHome
+            executable 'git'
+            args = ['pull', '-q']
+            ignoreExitValue = true
+        }
+    } else {
+        exec {
+            workingDir "${htmlToolsHome}/../"
+            executable 'git'
+            args = ['clone', '-q', '--depth', '1', 'https://github.com/plume-lib/html-tools.git', '.html-tools']
+        }
+    }
+}
+
+// No group so it does not show up in the output of `gradlew tasks`
+task pythonIsInstalled(type: Exec) {
+  description "Check that the python3 executable is installed."
+  executable = "python3"
+  args "--version"
+}
+
+task tags {
+    group 'Emacs'
+    description 'Create Emacs TAGS table'
+    doLast {
+        exec {
+            commandLine "etags", "-i", "checker/TAGS", "-i", "checker-qual/TAGS", "-i", "checker-util/TAGS", "-i", "dataflow/TAGS", "-i", "framework/TAGS", "-i", "framework-test/TAGS", "-i", "javacutil/TAGS", "-i", "docs/manual/TAGS"
+        }
+        exec {
+            commandLine "make", "-C", "docs/manual", "tags"
+        }
+    }
+}
+
+subprojects {
+    configurations {
+        errorprone
+    }
+
+    dependencies {
+        // https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core
+        // If you update this:
+        //  * Temporarily comment out "-Werror" elsewhere in this file
+        //  * Repeatedly run `./gradlew clean compileJava` and fix all errors
+        //  * Uncomment "-Werror"
+        //  * Don't edit framework/build.gradle to use the same version number
+        //    (it is updated when the annotated version of Guava is updated).
+        errorprone group: 'com.google.errorprone', name: 'error_prone_core', version: '2.7.1'
+    }
+
+    task checkFormat(type: Exec, dependsOn: [getCodeFormatScripts, pythonIsInstalled], group: 'Format') {
+        description 'Check whether the source code is properly formatted'
+        // checker-qual-android project has no source, so skip
+        onlyIf {!project.name.startsWith('checker-qual-android') }
+        executable 'python3'
+
+        doFirst {
+            args += "${formatScriptsHome}/check-google-java-format.py"
+            args += getJavaFilesToFormat(project.name)
+        }
+        ignoreExitValue = true
+        doLast {
+            if (execResult.exitValue != 0) {
+                throw new RuntimeException('Found improper formatting, try running:  ./gradlew reformat"')
+            }
+        }
+    }
+
+    task reformat(type: Exec, dependsOn: [getCodeFormatScripts, pythonIsInstalled], group: 'Format') {
+        description 'Format the Java source code'
+        // checker-qual-android project has no source, so skip
+        onlyIf {!project.name.startsWith('checker-qual-android') }
+        executable 'python3'
+        doFirst {
+            args += "${formatScriptsHome}/run-google-java-format.py"
+            args += getJavaFilesToFormat(project.name)
+        }
+    }
+
+    shadowJar {
+        // If you add an external dependency, then do the following:
+        // 1. Before adding the dependency, run ./gradlew copyJarsToDist.
+        // 2. Copy checker/dist/checker.jar elsewhere.
+        // 3. Add the dependency, then run ./gradlew clean copyJarsToDist.
+        // 4. Unzip both jars and compare the contents.
+        // 5. Add relocate lines below for the packages.
+        // 6. Do steps 3-5 until all new classes are in org/checkerframework/.
+
+        // Relocate packages that might conflict with user's classpath.
+        relocate 'org.apache', 'org.checkerframework.org.apache'
+        relocate 'org.relaxng', 'org.checkerframework.org.relaxng'
+        relocate 'org.plumelib', 'org.checkerframework.org.plumelib'
+        relocate 'org.codehaus', 'org.checkerframework.org.codehaus'
+        relocate 'org.objectweb.asm', 'org.checkerframework.org.objectweb.asm'
+        relocate 'io.github.classgraph', 'org.checkerframework.io.github.classgraph'
+        relocate 'nonapi.io.github.classgraph', 'org.checkerframework.nonapi.io.github.classgraph'
+        // relocate 'sun', 'org.checkerframework.sun'
+        relocate 'com.google', 'org.checkerframework.com.google'
+        relocate 'plume', 'org.checkerframework.plume'
+        exclude '**/module-info.class'
+
+        doFirst {
+            if (release) {
+                // Only relocate JavaParser during a release:
+                relocate 'com.github.javaparser', 'org.checkerframework.com.github.javaparser'
+            }
+        }
+    }
+
+    if (!project.name.startsWith('checker-qual-android')) {
+        task tags(type: Exec) {
+            description 'Create Emacs TAGS table'
+            commandLine "bash", "-c", "find . \\( -name build \\) -prune -o -name '*.java' -print | sort-directory-order | xargs ctags -e -f TAGS"
+        }
+    }
+
+    java {
+        withJavadocJar()
+        withSourcesJar()
+    }
+
+    // Things in this block reference definitions in the subproject that do not exist,
+    // until the project is evaluated.
+    afterEvaluate {
+        // Adds manifest to all Jar files
+        tasks.withType(Jar) {
+            includeEmptyDirs = false
+            if (archiveFileName.get().startsWith("checker-qual") || archiveFileName.get().startsWith("checker-util")) {
+                metaInf {
+                    from './LICENSE.txt'
+                }
+            } else {
+                metaInf {
+                    from "${rootDir}/LICENSE.txt"
+                }
+            }
+            manifest {
+                attributes("Implementation-Version": "${project.version}")
+                attributes("Implementation-URL": "https://checkerframework.org")
+                if (! archiveFileName.get().endsWith("source.jar")) {
+                    attributes('Automatic-Module-Name': "org.checkerframework." + project.name.replaceAll('-', '.'))
+                }
+                if (archiveFileName.get().startsWith("checker-qual") || archiveFileName.get().startsWith("checker-util")) {
+                    attributes("Bundle-License": "MIT")
+                } else {
+                    attributes("Bundle-License": "(GPL-2.0-only WITH Classpath-exception-2.0)")
+                }
+            }
+        }
+
+        // Add tasks to run various checkers on all the main source sets.
+        // These pass and are run by typecheckTests.
+        createCheckTypeTask(project.name, 'Formatter',
+            'org.checkerframework.checker.formatter.FormatterChecker')
+        createCheckTypeTask(project.name, 'Interning',
+            'org.checkerframework.checker.interning.InterningChecker',
+            ['-Astubs=javax-lang-model-element-name.astub'])
+        createCheckTypeTask(project.name, 'NullnessOnlyAnnotatedFor',
+            'org.checkerframework.checker.nullness.NullnessChecker',
+            ['-AskipUses=com.sun.*', '-AuseConservativeDefaultsForUncheckedCode=source'])
+        createCheckTypeTask(project.name, 'Purity',
+            'org.checkerframework.framework.util.PurityChecker')
+        createCheckTypeTask(project.name, 'Signature',
+            'org.checkerframework.checker.signature.SignatureChecker')
+        // These pass on some subprojects, which the `typecheck` task runs.
+        // TODO: Incrementally add @AnnotatedFor on more classes.
+        createCheckTypeTask(project.name, 'Nullness',
+            'org.checkerframework.checker.nullness.NullnessChecker',
+            ['-AskipUses=com.sun.*'])
+
+
+        // Add jtregTests to framework and checker modules
+        if (project.name.is('framework') || project.name.is('checker')) {
+            tasks.create(name: 'jtregTests', dependsOn: ':downloadJtreg', group: 'Verification') {
+                description 'Run the jtreg tests.'
+                dependsOn('compileJava')
+                dependsOn('compileTestJava')
+                dependsOn('shadowJar')
+
+                String jtregOutput = "${buildDir}/jtreg"
+                String name = 'all'
+                String tests = '.'
+                doLast {
+                    exec {
+                        executable "${jtregHome}/bin/jtreg"
+                        args = [
+                                "-dir:${projectDir}/jtreg",
+                                "-workDir:${jtregOutput}/${name}/work",
+                                "-reportDir:${jtregOutput}/${name}/report",
+                                "-verbose:error,fail",
+                                // Don't add debugging information
+                                //  "-javacoptions:-g",
+                                "-keywords:!ignore",
+                                "-samevm",
+                                "-javacoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}",
+                                // Required for checker/jtreg/nullness/PersistUtil.java and other tests
+                                "-vmoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}",
+                        ]
+                        if (isJava8) {
+                            // Use Error Prone javac and source/target 8
+                            args += [
+                                "-vmoptions:-Xbootclasspath/p:${configurations.javacJar.asPath}",
+                                "-javacoptions:-Xbootclasspath/p:${configurations.javacJar.asPath}",
+                                "-javacoptions:-source 8",
+                                "-javacoptions:-target 8"
+                                ]
+                        } else {
+                            args += [
+                                    // checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java
+                                    // uses the jdk.jdeps module.
+                                    "-javacoptions:--add-modules jdk.jdeps",
+                                    "-javacoptions:--add-exports=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED",
+                                    "-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
+                            ]
+                        }
+                        if (project.name.is('framework')) {
+                            // Do not check for the annotated JDK
+                            args += [
+                                    "-javacoptions:-ApermitMissingJdk"
+                            ]
+                        } else if (project.name.is('checker')) {
+                            args += [
+                                    "-javacoptions:-classpath ${sourceSets.testannotations.output.asPath}",
+                            ]
+                        }
+
+                        // Location of jtreg tests
+                        args += "${tests}"
+                    }
+                }
+            }
+        }
+
+        // Create a task for each JUnit test class whose name is the same as the JUnit class name.
+        sourceSets.test.allJava.filter { it.path.contains('test/junit') }.forEach { file ->
+            String junitClassName = file.name.replaceAll(".java", "")
+            tasks.create(name: "${junitClassName}", type: Test) {
+                description "Run ${junitClassName} tests."
+                include "**/${name}.class"
+            }
+        }
+
+        // Configure JUnit tests
+        tasks.withType(Test) {
+            if (isJava8) {
+                jvmArgs "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString()
+            } else {
+                jvmArgs += [
+                        "--illegal-access=warn",
+                        "--add-opens", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
+                ]
+            }
+
+            maxParallelForks = Integer.MAX_VALUE
+
+            if (project.name.is('checker')) {
+                dependsOn('copyJarsToDist')
+            }
+
+            if (project.hasProperty('emit.test.debug')) {
+                systemProperties += ["emit.test.debug": 'true']
+            }
+
+            testLogging {
+                showStandardStreams = true
+                // Always run the tests
+                outputs.upToDateWhen { false }
+
+                // Show the found unexpected diagnostics and expected diagnostics not found.
+                exceptionFormat "full"
+                events "failed"
+            }
+
+            // After each test, print a summary.
+            afterSuite { desc, result ->
+                if (desc.getClassName() != null) {
+                    long mils = result.getEndTime() - result.getStartTime()
+                    double seconds = mils / 1000.0
+
+                    println "Testsuite: ${desc.getClassName()}\n" +
+                            "Tests run: ${result.testCount}, " +
+                            "Failures: ${result.failedTestCount}, " +
+                            "Skipped: ${result.skippedTestCount}, " +
+                            "Time elapsed: ${seconds} sec\n"
+                }
+
+            }
+        }
+
+        // Create a nonJunitTests task per project
+        tasks.create(name: 'nonJunitTests', group: 'Verification') {
+            description 'Run all Checker Framework tests except for the Junit tests and inference tests.'
+            if (project.name.is('framework') || project.name.is('checker')) {
+                dependsOn('jtregTests')
+            }
+            if (project.name.is('framework')) {
+                dependsOn('loaderTests')
+            }
+
+            if (project.name.is('checker')) {
+                if (!isJava8) {
+                    dependsOn('jtregJdk11Tests')
+                }
+                dependsOn('nullnessExtraTests', 'commandLineTests', 'tutorialTests')
+            }
+
+            if (project.name.is('dataflow')) {
+                dependsOn('liveVariableTest')
+                dependsOn('issue3447Test')
+            }
+        }
+
+        // Create an inferenceTests task per project
+        tasks.create(name: 'inferenceTests', group: 'Verification') {
+            description 'Run inference tests.'
+            if (project.name.is('checker')) {
+                dependsOn('wholeProgramInferenceTests', 'wpiManyTests', 'wpiPlumeLibTests')
+            }
+        }
+
+        // Create a typecheck task per project (dogfooding the Checker Framework on itself).
+        // This isn't a test of the Checker Framework as the test and nonJunitTests tasks are.
+        // Tasks such as 'checkInterning' are constructed by createCheckTypeTask.
+        tasks.create(name: 'typecheck', group: 'Verification') {
+            description 'Run the Checker Framework on itself'
+            dependsOn('checkFormatter', 'checkInterning', 'checkPurity', 'checkSignature')
+            if (project.name.is('framework') || project.name.is('checker')) {
+                dependsOn('checkNullnessOnlyAnnotatedFor', 'checkCompilerMessages')
+            } else {
+                dependsOn('checkNullness')
+            }
+        }
+
+        // Create an allTests task per project.
+        // allTests = test + nonJunitTests + inferenceTests + typecheck
+        tasks.create(name: 'allTests', group: 'Verification') {
+            description 'Run all Checker Framework tests'
+            // The 'test' target is just the JUnit tests.
+            dependsOn('test', 'nonJunitTests', 'inferenceTests', 'typecheck')
+        }
+
+        task javadocPrivate(dependsOn: javadoc) {
+            doFirst {
+                javadocMemberLevel = JavadocMemberLevel.PRIVATE
+            }
+            doLast {
+                javadocMemberLevel = JavadocMemberLevel.PROTECTED
+            }
+        }
+    }
+}
+
+assemble.dependsOn(':checker:copyJarsToDist')
+
+task checkBasicStyle(group: 'Format') {
+    description 'Check basic style guidelines, mostly whitespace.  Not related to Checkstyle tool.'
+    String[] ignoreDirectories = ['.git',
+                                  '.gradle',
+                                  '.html-tools',
+                                  '.idea',
+                                  '.plume-scripts',
+                                  '.run-google-java-format',
+                                  'annotated',
+                                  'api',
+                                  'plume-bib',
+                                  'bootstrap',
+                                  'build',
+                                  'jdk']
+
+    String[] ignoreFilePatterns = [
+            '*.aux',
+            '*.bib',
+            '*.class',
+            '*.dvi',
+            '*.expected',
+            '*.gif',
+            '*.jar',
+            '*.jtr',
+            '*.log',
+            '*.out',
+            '*.patch',
+            '*.pdf',
+            '*.png',
+            '*.sty',
+            '*.toc',
+            '*.xcf',
+            '*~',
+            '#*#',
+            'CFLogo.ai',
+            'logfile.log.rec.index',
+            'manual.html',
+            'manual.html-e',
+            'junit.*.properties',
+            'securerandom.*',
+            'checker/dist/META-INF/maven/org.apache.bcel/bcel/pom.xml',
+            'checker/dist/META-INF/maven/org.apache.commons/commons-text/pom.xml',
+            'framework/src/main/resources/git.properties']
+    doLast {
+        FileTree tree = fileTree(dir: projectDir)
+        for (String dir : ignoreDirectories) {
+            tree.exclude "**/${dir}/**"
+        }
+        for (String file : ignoreFilePatterns) {
+            tree.exclude "**/${file}"
+        }
+        boolean failed = false
+        tree.visit {
+            if (!it.file.isDirectory()) {
+                boolean blankLineAtEnd = false
+                String fileName = it.file.getName()
+                boolean checkTabs = !fileName.equals("Makefile")
+                it.file.eachLine { line ->
+                    if (line.endsWith(' ')) {
+                        println("Trailing whitespace: ${it.file.absolutePath}")
+                        failed = true
+                    }
+                    if (checkTabs && line.contains('\t')) {
+                        println("Contains tab (use spaces): ${it.file.absolutePath}")
+                        failed = true
+                        checkTabs = false
+                    }
+                    if (!line.startsWith('\\') &&
+                            (line.matches('^.* (else|finally|try)\\{}.*$')
+                                    || line.matches('^.*}(catch|else|finally) .*$')
+                                    || line.matches('^.* (catch|for|if|while)\\('))) {
+                        // This runs on non-java files, too.
+                        println("Missing space: ${it.file.absolutePath}")
+                        failed = true
+                    }
+                    if (line.isEmpty()) {
+                        blankLineAtEnd = true;
+                    } else {
+                        blankLineAtEnd = false;
+                    }
+                }
+
+                if (blankLineAtEnd) {
+                    println("Blank line at end of file: ${it.file.absolutePath}")
+                    failed = true
+                }
+
+                RandomAccessFile file
+                try {
+                    file = new RandomAccessFile(it.file, 'r')
+                    int end = file.length() - 1;
+                    if (end > 0) {
+                        file.seek(end)
+                        byte last = file.readByte()
+                        if (last != '\n') {
+                            println("Missing newline at end of file: ${it.file.absolutePath}")
+                            failed = true
+                        }
+                    }
+                } finally {
+                    if (file != null) {
+                        file.close()
+                    }
+                }
+            }
+        }
+        if (failed) {
+            throw new GradleException("Files do not meet basic style guidelines.")
+        }
+    }
+}
+assemble.mustRunAfter(clean)
+task buildAll(group: 'Build') {
+    description 'Build all jar files, including source and javadoc jars'
+    dependsOn(allJavadoc)
+    subprojects { Project subproject ->
+        dependsOn("${subproject.name}:assemble")
+        dependsOn("${subproject.name}:javadocJar")
+        dependsOn("${subproject.name}:sourcesJar")
+    }
+    dependsOn('framework:allJavadocJar', 'framework:allSourcesJar', 'checker:allJavadocJar', 'checker:allSourcesJar')
+}
+
+task releaseBuild(group: 'Build') {
+    description 'Build everything required for a release'
+    dependsOn(clean)
+    doFirst {
+        release = true
+    }
+    // Use finalizedBy rather than dependsOn so that release is set to true before any of the tasks are run.
+    finalizedBy(buildAll)
+}
+
+// No group so it does not show up in the output of `gradlew tasks`
+task releaseAndTest {
+    description 'Build everything required for a release and run allTests'
+    dependsOn(releaseBuild)
+    subprojects { Project subproject ->
+        dependsOn("${subproject.name}:allTests")
+    }
+}
+
+// Don't create an empty checker-framework-VERSION.jar
+jar.onlyIf {false}
+
+/**
+ * Adds the shared pom information to the given publication.
+ * @param publication MavenPublication
+ */
+final sharedPublicationConfiguration(publication) {
+    publication.pom {
+        url = 'https://checkerframework.org'
+        developers {
+            // These are the lead developers/maintainers, not all the developers or contributors.
+            developer {
+                id = 'mernst'
+                name = 'Michael Ernst'
+                email = 'mernst@cs.washington.edu'
+                url = 'https://homes.cs.washington.edu/~mernst/'
+                organization = 'University of Washington'
+                organizationUrl = 'https://www.cs.washington.edu/'
+            }
+            developer {
+                id = 'smillst'
+                name = 'Suzanne Millstein'
+                email = 'smillst@cs.washington.edu'
+                organization = 'University of Washington'
+                organizationUrl = 'https://www.cs.washington.edu/'
+            }
+        }
+
+        scm {
+            url = 'https://github.com/typetools/checker-framework.git'
+            connection = 'scm:git:git://github.com/typetools/checker-framework.git'
+            developerConnection = 'scm:git:ssh://git@github.com/typetools/checker-framework.git'
+        }
+    }
+}
diff --git a/checker-qual-android/build.gradle b/checker-qual-android/build.gradle
new file mode 100644
index 0000000..9ce17da
--- /dev/null
+++ b/checker-qual-android/build.gradle
@@ -0,0 +1,76 @@
+evaluationDependsOn(":checker-qual")
+
+task copySources(type: Copy) {
+    description 'Copy checker-qual source to checker-qual-android'
+
+    includeEmptyDirs = false
+    doFirst {
+        // Delete the directory in case a previously copied file should no longer be in checker-qual
+        delete file('src')
+    }
+    from files('../checker-qual/src/main')
+    include "**/*.java"
+    exclude "**/SignednessUtilExtra.java"
+    into file('src/main')
+
+    // Not read only because "replaceAnnotations" tasks writes to the files.
+    fileMode(0666)
+    dirMode(0777)
+}
+
+/**
+* Types annotated with runtime annotations are always kept in the main dex by the default Android Gradle plugin.
+* Using the standard Checker Framework annotations can lead to main dex overflows;
+* users of the Checker framework may find themselves unable to build their Android apps.
+* By contrast, class-retention annotations are stripped out before packaging by all build systems as a convention.
+*/
+task replaceAnnotations {
+    doLast {
+        fileTree(dir: 'src', include: "**/*.java").each {
+            it.text = it.text.replaceAll("RetentionPolicy.RUNTIME", "RetentionPolicy.CLASS")
+        }
+    }
+}
+replaceAnnotations.dependsOn copySources
+
+compileJava.dependsOn replaceAnnotations
+
+clean {
+    delete file('src')
+}
+
+apply from: rootProject.file("gradle-mvn-push.gradle")
+
+/** Adds information to the publication for uploading to Maven repositories. */
+final checkerQualAndroidPom(publication) {
+    sharedPublicationConfiguration(publication)
+    publication.from components.java
+    publication.pom {
+        name = 'Checker Qual Android'
+        description = 'checker-qual-android contains annotations (type qualifiers) that a programmer\n' +
+                        'writes to specify Java code for type-checking by the Checker Framework.\n' +
+                        '\n' +
+                        'The checker-qual-android artifact is identical to the checker-qual\n' +
+                        'artifact, except that in checker-qual-android annotations have classfile\n' +
+                        'retention.  The default Android Gradle plugin retains types annotated with\n' +
+                        'runtime annotations in the main dex, but strips out class-retention\n' +
+                        'annotations.\n'
+        licenses {
+            license {
+                name = 'The MIT License'
+                url = 'http://opensource.org/licenses/MIT'
+                distribution = 'repo'
+            }
+        }
+    }
+}
+publishing {
+    publications {
+        checkerQualAndroid(MavenPublication) {
+            checkerQualAndroidPom it
+        }
+    }
+}
+signing {
+    sign publishing.publications.checkerQualAndroid
+}
diff --git a/checker-qual/LICENSE.txt b/checker-qual/LICENSE.txt
new file mode 100644
index 0000000..9837c6b
--- /dev/null
+++ b/checker-qual/LICENSE.txt
@@ -0,0 +1,22 @@
+Checker Framework qualifiers
+Copyright 2004-present by the Checker Framework developers
+
+MIT License:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/checker-qual/build.gradle b/checker-qual/build.gradle
new file mode 100644
index 0000000..f5cd3e3
--- /dev/null
+++ b/checker-qual/build.gradle
@@ -0,0 +1,51 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        // Create OSGI bundles
+        classpath "biz.aQute.bnd:biz.aQute.bnd.gradle:5.3.0"
+        // Don't add implementation dependencies; checker-qual.jar should have no dependencies.
+    }
+}
+plugins {
+    id 'java-library'
+}
+
+apply plugin: 'biz.aQute.bnd.builder'
+
+jar {
+    manifest {
+        attributes('Export-Package': '*')
+    }
+}
+
+apply from: rootProject.file("gradle-mvn-push.gradle")
+
+/** Adds information to the publication for uploading to Maven repositories. */
+final checkerQualPom(publication) {
+    sharedPublicationConfiguration(publication)
+    publication.from components.java
+    publication.pom {
+        name = 'Checker Qual'
+        description = 'checker-qual contains annotations (type qualifiers) that a programmer\n' +
+                        'writes to specify Java code for type-checking by the Checker Framework.\n'
+        licenses {
+            license {
+                name = 'The MIT License'
+                url = 'http://opensource.org/licenses/MIT'
+                distribution = 'repo'
+            }
+        }
+    }
+}
+publishing {
+    publications {
+        checkerQual(MavenPublication) {
+            checkerQualPom it
+        }
+    }
+}
+signing {
+    sign publishing.publications.checkerQual
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/CalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/CalledMethods.java
new file mode 100644
index 0000000..a6083b5
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/CalledMethods.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.builder.qual;
+
+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;
+
+/**
+ * A deprecated variant of {@link org.checkerframework.checker.calledmethods.qual.CalledMethods}.
+ *
+ * <p>Lombok outputs this annotation. This annotation could be marked as deprecated, but that causes
+ * extra warnings when processing delombok'd code.
+ *
+ * @checker_framework.manual #called-methods-checker Called Methods Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface CalledMethods {
+  /**
+   * The names of methods that have definetely been called.
+   *
+   * @return the names of methods that have definetely been called
+   */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/NotCalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/NotCalledMethods.java
new file mode 100644
index 0000000..8d831ce
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/NotCalledMethods.java
@@ -0,0 +1,31 @@
+package org.checkerframework.checker.builder.qual;
+
+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;
+
+/**
+ * An annotation speculatively used by Lombok's lombok.config checkerframework = true option. It has
+ * no meaning to the Called Methods Checker, which treats it as {@code @}{@link
+ * org.checkerframework.checker.calledmethods.qual.CalledMethods}{@code ()}.
+ *
+ * <p>A similar annotation might be supported in the future.
+ *
+ * <p>This annotation could be marked as deprecated, but that causes extra warnings when processing
+ * delombok'd code.
+ *
+ * @checker_framework.manual #called-methods-checker Called Methods Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface NotCalledMethods {
+  /**
+   * The names of the methods that have NOT been called.
+   *
+   * @return the names of the methods that have NOT been called
+   */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/ReturnsReceiver.java b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/ReturnsReceiver.java
new file mode 100644
index 0000000..05eb7c2
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/ReturnsReceiver.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.builder.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A deprecated variant of {@code org.checkerframework.common.returnsreceiver.qual.This}.
+ *
+ * <p>Lombok outputs this annotation. It is retained only for backwards-compatibility with Lombok's
+ * {@code checkerframework = true} lombok.config flag. It should not be used in new code, because it
+ * is TRUSTED, NOT CHECKED.
+ *
+ * <p>This annotation could be marked as deprecated, but that causes extra warnings when processing
+ * delombok'd code.
+ *
+ * @checker_framework.manual #called-methods-checker Called Methods Checker
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Inherited
+public @interface ReturnsReceiver {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethods.java
new file mode 100644
index 0000000..1a861ec
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethods.java
@@ -0,0 +1,32 @@
+package org.checkerframework.checker.calledmethods.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * If an expression has type {@code @CalledMethods({"m1", "m2"})}, then methods {@code m1} and
+ * {@code m2} have definitely been called on its value. Other methods might or might not have been
+ * called.
+ *
+ * <p>The subtyping relationship is:
+ *
+ * <pre>{@code @CalledMethods({"m1", "m2", "m3"}) <: @CalledMethods({"m1", "m2"})}</pre>
+ *
+ * @checker_framework.manual #called-methods-checker Called Methods Checker
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface CalledMethods {
+  /**
+   * Methods that have definitely been called on the expression whose type is annotated.
+   *
+   * @return methods that have definitely been called
+   */
+  public String[] value() default {};
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsBottom.java
new file mode 100644
index 0000000..eb0b8a2
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsBottom.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.calledmethods.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type for the Called Methods type system.
+ *
+ * <p>It should rarely be written by a programmer.
+ *
+ * @checker_framework.manual #called-methods-checker Called Methods Checker
+ */
+@SubtypeOf({CalledMethods.class, CalledMethodsPredicate.class})
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+public @interface CalledMethodsBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsPredicate.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsPredicate.java
new file mode 100644
index 0000000..38aebe0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsPredicate.java
@@ -0,0 +1,31 @@
+package org.checkerframework.checker.calledmethods.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This annotation represents a predicate on {@code @}{@link CalledMethods} annotations. If method
+ * {@code c()}'s receiver type is annotated with {@code @CalledMethodsPredicate("a || b")}, then it
+ * is acceptable to call either method {@code a()} or method {@code b()} before calling method
+ * {@code c()}.
+ *
+ * @checker_framework.manual #called-methods-checker Called Methods Checker
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({CalledMethods.class})
+public @interface CalledMethodsPredicate {
+  /**
+   * A boolean expression constructed from the following grammar:
+   *
+   * <p>S &rarr; method name | S &amp;&amp; S | S || S | !S | (S)
+   *
+   * <p>The expression uses standard Java operator precedence: "!" then "&amp;&amp;" then "||".
+   *
+   * @return the boolean expression
+   */
+  String value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethods.java
new file mode 100644
index 0000000..2b89814
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethods.java
@@ -0,0 +1,42 @@
+package org.checkerframework.checker.calledmethods.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.PostconditionAnnotation;
+import org.checkerframework.framework.qual.QualifierArgument;
+
+/**
+ * Indicates that the method, if it terminates successfully, always invokes the given methods on the
+ * given expressions.
+ *
+ * <p>Consider the following method:
+ *
+ * <pre>
+ * &#64;EnsuresCalledMethods(value = "#1", methods = "m")
+ * public void callM(T t) { ... }
+ * </pre>
+ *
+ * <p>This method guarantees that {@code t.m()} is always called before the method returns.
+ *
+ * @checker_framework.manual #called-methods-checker Called Methods Checker
+ */
+@PostconditionAnnotation(qualifier = CalledMethods.class)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+public @interface EnsuresCalledMethods {
+  /**
+   * The Java expressions to which the qualifier applies.
+   *
+   * @return the Java expressions to which the qualifier applies
+   * @see org.checkerframework.framework.qual.EnsuresQualifier
+   */
+  // Postconditions must use "value" as the name (conditional postconditions use "expression").
+  String[] value();
+
+  /**
+   * The methods guaranteed to be invoked on the expressions.
+   *
+   * @return the methods guaranteed to be invoked on the expressions
+   */
+  @QualifierArgument("value")
+  String[] methods();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsIf.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsIf.java
new file mode 100644
index 0000000..edf5df0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsIf.java
@@ -0,0 +1,73 @@
+package org.checkerframework.checker.calledmethods.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+import org.checkerframework.framework.qual.QualifierArgument;
+
+/**
+ * Indicates that the method, if it terminates with the given result, invokes the given methods on
+ * the given expressions.
+ *
+ * @see EnsuresCalledMethods
+ * @see CalledMethods
+ * @checker_framework.manual #called-methods-checker Called Methods Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@ConditionalPostconditionAnnotation(qualifier = CalledMethods.class)
+@InheritedAnnotation
+@Repeatable(EnsuresCalledMethodsIf.List.class)
+public @interface EnsuresCalledMethodsIf {
+  /**
+   * Returns Java expressions that have had the given methods called on them after the method
+   * returns {@link #result}.
+   *
+   * @return an array of Java expressions
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  String[] expression();
+
+  /**
+   * Returns the return value of the method under which the postcondition holds.
+   *
+   * @return the return value of the method under which the postcondition holds
+   */
+  boolean result();
+
+  /**
+   * The methods guaranteed to be invoked on the expressions if the result of the method is {@link
+   * #result}.
+   *
+   * @return the methods guaranteed to be invoked on the expressions if the result of the method is
+   *     {@link #result}
+   */
+  @QualifierArgument("value")
+  String[] methods();
+
+  /**
+   * A wrapper annotation that makes the {@link EnsuresCalledMethodsIf} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresCalledMethodsIf} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @ConditionalPostconditionAnnotation(qualifier = CalledMethods.class)
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Return the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresCalledMethodsIf[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKey.java b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKey.java
new file mode 100644
index 0000000..cd9f177
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKey.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.compilermsgs.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * A string that is definitely a compiler message key.
+ *
+ * @checker_framework.manual #compilermsgs-checker Compiler Message Key Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownCompilerMessageKey.class)
+public @interface CompilerMessageKey {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKeyBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKeyBottom.java
new file mode 100644
index 0000000..911db2c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKeyBottom.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.compilermsgs.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Compiler Message Key type system. Programmers should rarely write this
+ * type.
+ *
+ * @checker_framework.manual #compilermsgs-checker Compiler Message Key Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(CompilerMessageKey.class)
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@DefaultFor(TypeUseLocation.LOWER_BOUND)
+public @interface CompilerMessageKeyBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/UnknownCompilerMessageKey.java b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/UnknownCompilerMessageKey.java
new file mode 100644
index 0000000..1e8916e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/UnknownCompilerMessageKey.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.compilermsgs.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * A {@code String} that might or might not be a compiler message key.
+ *
+ * @checker_framework.manual #compilermsgs-checker Compiler Message Key Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@InvisibleQualifier
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface UnknownCompilerMessageKey {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/package-info.java b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/package-info.java
new file mode 100644
index 0000000..f3c3136
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Qualifiers for the Compiler Message Key Checker.
+ *
+ * @checker_framework.manual #compilermsgs-checker Compiler Message Key Checker
+ */
+package org.checkerframework.checker.compilermsgs.qual;
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtAlphaCompositingRule.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtAlphaCompositingRule.java
new file mode 100644
index 0000000..c6f2849
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtAlphaCompositingRule.java
@@ -0,0 +1,33 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Basic alpha compositing rules for combining source and destination colors to achieve blending and
+ * transparency effects with graphics and images (see {@link java.awt.AlphaComposite} for more
+ * details).
+ *
+ * @see java.awt.AlphaComposite#CLEAR
+ * @see java.awt.AlphaComposite#SRC
+ * @see java.awt.AlphaComposite#DST
+ * @see java.awt.AlphaComposite#SRC_OVER
+ * @see java.awt.AlphaComposite#DST_OVER
+ * @see java.awt.AlphaComposite#SRC_IN
+ * @see java.awt.AlphaComposite#DST_IN
+ * @see java.awt.AlphaComposite#SRC_OUT
+ * @see java.awt.AlphaComposite#DST_OUT
+ * @see java.awt.AlphaComposite#SRC_ATOP
+ * @see java.awt.AlphaComposite#DST_ATOP
+ * @see java.awt.AlphaComposite#XOR
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface AwtAlphaCompositingRule {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtColorSpace.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtColorSpace.java
new file mode 100644
index 0000000..0dbe271
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtColorSpace.java
@@ -0,0 +1,51 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Color space tags to identify the specific color space of a Color object or, via a ColorModel
+ * object, of an Image, a BufferedImage, or a GraphicsDevice (see {@link java.awt.color.ColorSpace}
+ * for more details).
+ *
+ * @see java.awt.color.ColorSpace#TYPE_XYZ
+ * @see java.awt.color.ColorSpace#TYPE_Lab
+ * @see java.awt.color.ColorSpace#TYPE_Luv
+ * @see java.awt.color.ColorSpace#TYPE_YCbCr
+ * @see java.awt.color.ColorSpace#TYPE_Yxy
+ * @see java.awt.color.ColorSpace#TYPE_RGB
+ * @see java.awt.color.ColorSpace#TYPE_GRAY
+ * @see java.awt.color.ColorSpace#TYPE_HSV
+ * @see java.awt.color.ColorSpace#TYPE_HLS
+ * @see java.awt.color.ColorSpace#TYPE_CMYK
+ * @see java.awt.color.ColorSpace#TYPE_CMY
+ * @see java.awt.color.ColorSpace#TYPE_2CLR
+ * @see java.awt.color.ColorSpace#TYPE_3CLR
+ * @see java.awt.color.ColorSpace#TYPE_4CLR
+ * @see java.awt.color.ColorSpace#TYPE_5CLR
+ * @see java.awt.color.ColorSpace#TYPE_6CLR
+ * @see java.awt.color.ColorSpace#TYPE_7CLR
+ * @see java.awt.color.ColorSpace#TYPE_8CLR
+ * @see java.awt.color.ColorSpace#TYPE_9CLR
+ * @see java.awt.color.ColorSpace#TYPE_ACLR
+ * @see java.awt.color.ColorSpace#TYPE_BCLR
+ * @see java.awt.color.ColorSpace#TYPE_CCLR
+ * @see java.awt.color.ColorSpace#TYPE_DCLR
+ * @see java.awt.color.ColorSpace#TYPE_ECLR
+ * @see java.awt.color.ColorSpace#TYPE_FCLR
+ * @see java.awt.color.ColorSpace#CS_sRGB
+ * @see java.awt.color.ColorSpace#CS_LINEAR_RGB
+ * @see java.awt.color.ColorSpace#CS_CIEXYZ
+ * @see java.awt.color.ColorSpace#CS_PYCC
+ * @see java.awt.color.ColorSpace#CS_GRAY
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface AwtColorSpace {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtCursorType.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtCursorType.java
new file mode 100644
index 0000000..7e78009
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtCursorType.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * AwtCursorType.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface AwtCursorType {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtFlowLayout.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtFlowLayout.java
new file mode 100644
index 0000000..a5bc81f
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtFlowLayout.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Line alignments in a flow layout (see {@link java.awt.FlowLayout} for more details).
+ *
+ * @see java.awt.FlowLayout#LEFT
+ * @see java.awt.FlowLayout#CENTER
+ * @see java.awt.FlowLayout#RIGHT
+ * @see java.awt.FlowLayout#LEADING
+ * @see java.awt.FlowLayout#TRAILING
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface AwtFlowLayout {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/Fenum.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/Fenum.java
new file mode 100644
index 0000000..f17e9d4
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/Fenum.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * A generic fake enumeration qualifier that is parameterized by a name. It is written in source
+ * code as, for example, {@code @Fenum("cardSuit")} and {@code @Fenum("faceValue")}, which would be
+ * distinct fake enumerations.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface Fenum {
+  String value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumBottom.java
new file mode 100644
index 0000000..5607bd0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumBottom.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Fenum type system. Programmers should rarely write this type.
+ *
+ * <p>Its relationships are set up via the FenumAnnotatedTypeFactory.
+ *
+ * @checker_framework.manual #propkey-checker Property File Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+// Subtype relationships are set up by passing this class as a bottom
+// to the multigraph hierarchy constructor.
+@SubtypeOf({})
+@DefaultFor(TypeUseLocation.LOWER_BOUND)
+public @interface FenumBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumTop.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumTop.java
new file mode 100644
index 0000000..764b7f3
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumTop.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The top of the fake enumeration type hierarchy.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf({})
+@DefaultFor({TypeUseLocation.LOCAL_VARIABLE, TypeUseLocation.RESOURCE_VARIABLE})
+public @interface FenumTop {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumUnqualified.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumUnqualified.java
new file mode 100644
index 0000000..4eeaf96
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumUnqualified.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.fenum.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * An unqualified type. Such a type is incomparable to (that is, neither a subtype nor a supertype
+ * of) any fake enum type.
+ *
+ * <p>This annotation may not be written in source code; it is an implementation detail of the
+ * checker.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({}) // empty target prevents programmers from writing this in a program
+@SubtypeOf({FenumTop.class})
+@DefaultQualifierInHierarchy
+@DefaultFor(TypeUseLocation.EXCEPTION_PARAMETER)
+public @interface FenumUnqualified {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/PolyFenum.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/PolyFenum.java
new file mode 100644
index 0000000..d203d9a
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/PolyFenum.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the fake enum type system.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(FenumTop.class)
+public @interface PolyFenum {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingBoxOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingBoxOrientation.java
new file mode 100644
index 0000000..1c079d5
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingBoxOrientation.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * SwingBoxOrientation.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface SwingBoxOrientation {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingCompassDirection.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingCompassDirection.java
new file mode 100644
index 0000000..f89cd2d
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingCompassDirection.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * SwingCompassDirection.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface SwingCompassDirection {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingElementOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingElementOrientation.java
new file mode 100644
index 0000000..8935db1
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingElementOrientation.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * SwingElementOrientation.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface SwingElementOrientation {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingHorizontalOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingHorizontalOrientation.java
new file mode 100644
index 0000000..569f860
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingHorizontalOrientation.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * SwingHorizontalOrientation.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SwingBoxOrientation.class)
+public @interface SwingHorizontalOrientation {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingSplitPaneOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingSplitPaneOrientation.java
new file mode 100644
index 0000000..d555bf1
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingSplitPaneOrientation.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * SwingSplitPaneOrientation.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface SwingSplitPaneOrientation {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTextOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTextOrientation.java
new file mode 100644
index 0000000..84833e6
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTextOrientation.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * SwingTextOrientation.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface SwingTextOrientation {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitleJustification.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitleJustification.java
new file mode 100644
index 0000000..ac51e16
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitleJustification.java
@@ -0,0 +1,25 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Vertical orientations for the title text of a {@link javax.swing.border.TitledBorder}.
+ *
+ * @see javax.swing.border.TitledBorder#DEFAULT_JUSTIFICATION
+ * @see javax.swing.border.TitledBorder#LEFT
+ * @see javax.swing.border.TitledBorder#CENTER
+ * @see javax.swing.border.TitledBorder#RIGHT
+ * @see javax.swing.border.TitledBorder#LEADING
+ * @see javax.swing.border.TitledBorder#TRAILING
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface SwingTitleJustification {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitlePosition.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitlePosition.java
new file mode 100644
index 0000000..e3ca36f
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitlePosition.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Justifications for the title text of a {@link javax.swing.border.TitledBorder}.
+ *
+ * @see javax.swing.border.TitledBorder#DEFAULT_POSITION
+ * @see javax.swing.border.TitledBorder#ABOVE_TOP
+ * @see javax.swing.border.TitledBorder#TOP
+ * @see javax.swing.border.TitledBorder#BELOW_TOP
+ * @see javax.swing.border.TitledBorder#ABOVE_BOTTOM
+ * @see javax.swing.border.TitledBorder#BOTTOM
+ * @see javax.swing.border.TitledBorder#BELOW_BOTTOM
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface SwingTitlePosition {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingVerticalOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingVerticalOrientation.java
new file mode 100644
index 0000000..7a70857
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingVerticalOrientation.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.fenum.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * SwingVerticalOrientation.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SwingBoxOrientation.class)
+public @interface SwingVerticalOrientation {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java
new file mode 100644
index 0000000..3d1728c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java
@@ -0,0 +1,353 @@
+package org.checkerframework.checker.formatter.qual;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+/**
+ * Elements of this enumeration are used in a {@link Format Format} annotation to indicate the valid
+ * types that may be passed as a format parameter. For example:
+ *
+ * <blockquote>
+ *
+ * <pre>{@literal @}Format({GENERAL, INT}) String f = "String '%s' has length %d";
+ *
+ * String.format(f, "Example", 7);</pre>
+ *
+ * </blockquote>
+ *
+ * The annotation indicates that the format string requires any Object as the first parameter
+ * ({@link ConversionCategory#GENERAL}) and an integer as the second parameter ({@link
+ * ConversionCategory#INT}).
+ *
+ * @see Format
+ * @checker_framework.manual #formatter-checker Format String Checker
+ */
+@SuppressWarnings("unchecked") // ".class" expressions in varargs position
+@AnnotatedFor("nullness")
+public enum ConversionCategory {
+  /** Use if the parameter can be of any type. Applicable for conversions b, B, h, H, s, S. */
+  GENERAL("bBhHsS", (Class<?>[]) null /* everything */),
+
+  /**
+   * Use if the parameter is of a basic types which represent Unicode characters: char, Character,
+   * byte, Byte, short, and Short. This conversion may also be applied to the types int and Integer
+   * when Character.isValidCodePoint(int) returns true. Applicable for conversions c, C.
+   */
+  CHAR("cC", Character.class, Byte.class, Short.class, Integer.class),
+
+  /**
+   * Use if the parameter is an integral type: byte, Byte, short, Short, int and Integer, long,
+   * Long, and BigInteger. Applicable for conversions d, o, x, X.
+   */
+  INT("doxX", Byte.class, Short.class, Integer.class, Long.class, BigInteger.class),
+
+  /**
+   * Use if the parameter is a floating-point type: float, Float, double, Double, and BigDecimal.
+   * Applicable for conversions e, E, f, g, G, a, A.
+   */
+  FLOAT("eEfgGaA", Float.class, Double.class, BigDecimal.class),
+
+  /**
+   * Use if the parameter is a type which is capable of encoding a date or time: long, Long,
+   * Calendar, and Date. Applicable for conversions t, T.
+   */
+  @SuppressWarnings("JdkObsolete")
+  TIME("tT", Long.class, Calendar.class, Date.class),
+
+  /**
+   * Use if the parameter is both a char and an int.
+   *
+   * <p>In a format string, multiple conversions may be applied to the same parameter. This is
+   * seldom needed, but the following is an example of such use:
+   *
+   * <pre>
+   *   format("Test %1$c %1$d", (int)42);
+   * </pre>
+   *
+   * In this example, the first parameter is interpreted as both a character and an int, therefore
+   * the parameter must be compatible with both conversion, and can therefore neither be char nor
+   * long. This intersection of conversions is called CHAR_AND_INT.
+   *
+   * <p>One other conversion intersection is interesting, namely the intersection of INT and TIME,
+   * resulting in INT_AND_TIME.
+   *
+   * <p>All other intersection either lead to an already existing type, or NULL, in which case it is
+   * illegal to pass object's of any type as parameter.
+   */
+  CHAR_AND_INT(null, Byte.class, Short.class, Integer.class),
+
+  /**
+   * Use if the parameter is both an int and a time.
+   *
+   * @see #CHAR_AND_INT
+   */
+  INT_AND_TIME(null, Long.class),
+
+  /**
+   * Use if no object of any type can be passed as parameter. In this case, the only legal value is
+   * null. This is seldomly needed, and indicates an error in most cases. For example:
+   *
+   * <pre>
+   *   format("Test %1$f %1$d", null);
+   * </pre>
+   *
+   * Only null can be legally passed, passing a value such as 4 or 4.2 would lead to an exception.
+   */
+  NULL(null),
+
+  /**
+   * Use if a parameter is not used by the formatter. This is seldomly needed, and indicates an
+   * error in most cases. For example:
+   *
+   * <pre>
+   *   format("Test %1$s %3$s", "a","unused","b");
+   * </pre>
+   *
+   * Only the first "a" and third "b" parameters are used, the second "unused" parameter is ignored.
+   */
+  UNUSED(null, (Class<?>[]) null /* everything */);
+
+  /** The argument types. Null means every type. */
+  @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up!
+  public final Class<?> @Nullable [] types;
+
+  /** The format specifier characters. Null means users cannot specify it directly. */
+  public final @Nullable String chars;
+
+  /**
+   * Create a new conversion category.
+   *
+   * @param chars the format specifier characters. Null means users cannot specify it directly.
+   * @param types the argument types. Null means every type.
+   */
+  ConversionCategory(@Nullable String chars, Class<?> @Nullable ... types) {
+    this.chars = chars;
+    if (types == null) {
+      this.types = types;
+    } else {
+      List<Class<?>> typesWithPrimitives = new ArrayList<>(types.length);
+      for (Class<?> type : types) {
+        typesWithPrimitives.add(type);
+        Class<?> unwrapped = unwrapPrimitive(type);
+        if (unwrapped != null) {
+          typesWithPrimitives.add(unwrapped);
+        }
+      }
+      this.types = typesWithPrimitives.toArray(new Class<?>[typesWithPrimitives.size()]);
+    }
+  }
+
+  /**
+   * If the given class is a primitive wrapper, return the corresponding primitive class. Otherwise
+   * return null.
+   *
+   * @param c a class
+   * @return the unwrapped primitive, or null
+   */
+  private static @Nullable Class<? extends Object> unwrapPrimitive(Class<?> c) {
+    if (c == Byte.class) {
+      return byte.class;
+    }
+    if (c == Character.class) {
+      return char.class;
+    }
+    if (c == Short.class) {
+      return short.class;
+    }
+    if (c == Integer.class) {
+      return int.class;
+    }
+    if (c == Long.class) {
+      return long.class;
+    }
+    if (c == Float.class) {
+      return float.class;
+    }
+    if (c == Double.class) {
+      return double.class;
+    }
+    if (c == Boolean.class) {
+      return boolean.class;
+    }
+    return null;
+  }
+
+  /**
+   * Converts a conversion character to a category. For example:
+   *
+   * <pre>{@code
+   * ConversionCategory.fromConversionChar('d') == ConversionCategory.INT
+   * }</pre>
+   *
+   * @param c a conversion character
+   * @return the category for the given conversion character
+   */
+  @SuppressWarnings("nullness:dereference.of.nullable") // `chars` field is non-null for these
+  public static ConversionCategory fromConversionChar(char c) {
+    for (ConversionCategory v : new ConversionCategory[] {GENERAL, CHAR, INT, FLOAT, TIME}) {
+      if (v.chars.contains(String.valueOf(c))) {
+        return v;
+      }
+    }
+    throw new IllegalArgumentException("Bad conversion character " + c);
+  }
+
+  private static <E> Set<E> arrayToSet(E[] a) {
+    return new HashSet<>(Arrays.asList(a));
+  }
+
+  public static boolean isSubsetOf(ConversionCategory a, ConversionCategory b) {
+    return intersect(a, b) == a;
+  }
+
+  /**
+   * Returns the intersection of two categories. This is seldomly needed.
+   *
+   * <blockquote>
+   *
+   * <pre>
+   * ConversionCategory.intersect(INT, TIME) == INT_AND_TIME;
+   * </pre>
+   *
+   * </blockquote>
+   *
+   * @param a a category
+   * @param b a category
+   * @return the intersection of the two categories (their greatest lower bound)
+   */
+  public static ConversionCategory intersect(ConversionCategory a, ConversionCategory b) {
+    if (a == UNUSED) {
+      return b;
+    }
+    if (b == UNUSED) {
+      return a;
+    }
+    if (a == GENERAL) {
+      return b;
+    }
+    if (b == GENERAL) {
+      return a;
+    }
+
+    @SuppressWarnings("nullness:argument" // `types` field is null only for UNUSED and GENERAL
+    )
+    Set<Class<?>> as = arrayToSet(a.types);
+    @SuppressWarnings("nullness:argument" // `types` field is null only for UNUSED and GENERAL
+    )
+    Set<Class<?>> bs = arrayToSet(b.types);
+    as.retainAll(bs); // intersection
+    for (ConversionCategory v :
+        new ConversionCategory[] {CHAR, INT, FLOAT, TIME, CHAR_AND_INT, INT_AND_TIME, NULL}) {
+      @SuppressWarnings("nullness:argument" // `types` field is null only for UNUSED and GENERAL
+      )
+      Set<Class<?>> vs = arrayToSet(v.types);
+      if (vs.equals(as)) {
+        return v;
+      }
+    }
+    throw new RuntimeException();
+  }
+
+  /**
+   * Returns the union of two categories. This is seldomly needed.
+   *
+   * <blockquote>
+   *
+   * <pre>
+   * ConversionCategory.union(INT, TIME) == GENERAL;
+   * </pre>
+   *
+   * </blockquote>
+   *
+   * @param a a category
+   * @param b a category
+   * @return the union of the two categories (their least upper bound)
+   */
+  public static ConversionCategory union(ConversionCategory a, ConversionCategory b) {
+    if (a == UNUSED || b == UNUSED) {
+      return UNUSED;
+    }
+    if (a == GENERAL || b == GENERAL) {
+      return GENERAL;
+    }
+    if ((a == CHAR_AND_INT && b == INT_AND_TIME) || (a == INT_AND_TIME && b == CHAR_AND_INT)) {
+      // This is special-cased because the union of a.types and b.types
+      // does not include BigInteger.class, whereas the types for INT does.
+      // Returning INT here to prevent returning GENERAL below.
+      return INT;
+    }
+
+    @SuppressWarnings("nullness:argument" // `types` field is null only for UNUSED and GENERAL
+    )
+    Set<Class<?>> as = arrayToSet(a.types);
+    @SuppressWarnings("nullness:argument" // `types` field is null only for UNUSED and GENERAL
+    )
+    Set<Class<?>> bs = arrayToSet(b.types);
+    as.addAll(bs); // union
+    for (ConversionCategory v :
+        new ConversionCategory[] {NULL, CHAR_AND_INT, INT_AND_TIME, CHAR, INT, FLOAT, TIME}) {
+      @SuppressWarnings("nullness:argument" // `types` field is null only for UNUSED and GENERAL
+      )
+      Set<Class<?>> vs = arrayToSet(v.types);
+      if (vs.equals(as)) {
+        return v;
+      }
+    }
+
+    return GENERAL;
+  }
+
+  /**
+   * Returns true if {@code argType} can be an argument used by this format specifier.
+   *
+   * @param argType an argument type
+   * @return true if {@code argType} can be an argument used by this format specifier
+   */
+  public boolean isAssignableFrom(Class<?> argType) {
+    if (types == null) {
+      return true;
+    }
+    if (argType == void.class) {
+      return true;
+    }
+    for (Class<?> c : types) {
+      if (c.isAssignableFrom(argType)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /** Returns a pretty printed {@link ConversionCategory}. */
+  @Pure
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append(name());
+    sb.append(" conversion category");
+
+    if (types == null || types.length == 0) {
+      return sb.toString();
+    }
+
+    StringJoiner sj = new StringJoiner(", ", "(one of: ", ")");
+    for (Class<?> cls : types) {
+      sj.add(cls.getSimpleName());
+    }
+    sb.append(" ");
+    sb.append(sj);
+
+    return sb.toString();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/Format.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/Format.java
new file mode 100644
index 0000000..878fce1
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/Format.java
@@ -0,0 +1,48 @@
+package org.checkerframework.checker.formatter.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This annotation, attached to a String type, indicates that the String may be passed to {@link
+ * java.util.Formatter#format(String, Object...) Formatter.format} and similar methods.
+ *
+ * <p>The annotation's value represents the valid arguments that may be passed to the format method.
+ * For example:
+ *
+ * <blockquote>
+ *
+ * <pre>
+ * {@literal @}Format({GENERAL, INT}) String f = "String '%s' has length %d";
+ *
+ *  String.format(f, "Example", 7);
+ * </pre>
+ *
+ * </blockquote>
+ *
+ * The annotation indicates that the format string requires any Object as the first parameter
+ * ({@link ConversionCategory#GENERAL}) and an integer as the second parameter ({@link
+ * ConversionCategory#INT}). The format string accepts any values as additional parameters (because
+ * it ignores them).
+ *
+ * @see ConversionCategory
+ * @checker_framework.manual #formatter-checker Format String Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownFormat.class)
+public @interface Format {
+  /**
+   * An array of {@link ConversionCategory}, indicating the types of legal remaining arguments when
+   * a value of the annotated type is used as the first argument to {@link
+   * java.util.Formatter#format(String, Object...) Formatter.format} and similar methods.
+   *
+   * @return types that can be used as values when a value of this type is the format string
+   */
+  ConversionCategory[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatBottom.java
new file mode 100644
index 0000000..9ce0ec0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatBottom.java
@@ -0,0 +1,25 @@
+package org.checkerframework.checker.formatter.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Format String type system. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #formatter-checker Format String Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf({Format.class, InvalidFormat.class})
+@DefaultFor(value = {TypeUseLocation.LOWER_BOUND})
+public @interface FormatBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatMethod.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatMethod.java
new file mode 100644
index 0000000..1a851fb
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatMethod.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.formatter.qual;
+
+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;
+
+/**
+ * If this annotation is attached to a {@link java.util.Formatter#format(String, Object...)
+ * Formatter.format}-like method, then the first parameter of type String is treated as a format
+ * string for the following arguments. The Format String Checker ensures that the arguments passed
+ * as varargs are compatible with the format string argument, and also permits them to be passed to
+ * {@link java.util.Formatter#format(String, Object...) Formatter.format}-like methods within the
+ * body.
+ *
+ * @checker_framework.manual #formatter-checker Format String Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+public @interface FormatMethod {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/InvalidFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/InvalidFormat.java
new file mode 100644
index 0000000..d6597ee
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/InvalidFormat.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.formatter.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This annotation, attached to a {@link java.lang.String String} type, indicates that the string is
+ * not a legal format string. Passing the string to {@link java.util.Formatter#format(String,
+ * Object...) Formatter.format} or similar methods will lead to the exception message indicated in
+ * the annotation's value.
+ *
+ * @checker_framework.manual #formatter-checker Format String Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownFormat.class)
+public @interface InvalidFormat {
+  /**
+   * Using a value of the annotated type as the first argument to {@link
+   * java.util.Formatter#format(String, Object...) Formatter.format} or similar methods will lead to
+   * this exception message.
+   */
+  String value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ReturnsFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ReturnsFormat.java
new file mode 100644
index 0000000..ef34fd3
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ReturnsFormat.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.formatter.qual;
+
+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;
+
+/**
+ * Attach this annotation to a method with the following properties:
+ *
+ * <ul>
+ *   <li>The first parameter is a format string.
+ *   <li>The second parameter is a vararg that takes conversion categories.
+ *   <li>The method throws an exception if the format string's format specifiers do not match the
+ *       passed conversion categories.
+ *   <li>On success, the method returns the passed format string unmodified.
+ * </ul>
+ *
+ * An example is {@link org.checkerframework.checker.formatter.util.FormatUtil#asFormat}.
+ *
+ * @checker_framework.manual #formatter-checker Format String Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface ReturnsFormat {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/UnknownFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/UnknownFormat.java
new file mode 100644
index 0000000..e4da196
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/UnknownFormat.java
@@ -0,0 +1,32 @@
+package org.checkerframework.checker.formatter.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The top qualifier.
+ *
+ * <p>A type annotation indicating that the run-time value might or might not be a valid format
+ * string.
+ *
+ * <p>This annotation may not be written in source code; it is an implementation detail of the
+ * checker.
+ *
+ * @checker_framework.manual #formatter-checker Format String Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@InvisibleQualifier
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+public @interface UnknownFormat {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/AlwaysSafe.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/AlwaysSafe.java
new file mode 100644
index 0000000..0730811
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/AlwaysSafe.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.guieffect.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Annotation to override the UI effect on a class, and make a field or method safe for non-UI code
+ * to use.
+ *
+ * @checker_framework.manual #guieffect-checker GUI Effect Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({UI.class})
+@DefaultQualifierInHierarchy
+public @interface AlwaysSafe {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUI.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUI.java
new file mode 100644
index 0000000..7586919
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUI.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.guieffect.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * Annotation for the polymorphic-UI effect.
+ *
+ * @checker_framework.manual #guieffect-checker GUI Effect Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(UI.class)
+public @interface PolyUI {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUIEffect.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUIEffect.java
new file mode 100644
index 0000000..cf8cc04
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUIEffect.java
@@ -0,0 +1,18 @@
+package org.checkerframework.checker.guieffect.qual;
+
+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;
+
+/**
+ * Annotation for the polymorphic effect on methods, or on field accesses.
+ *
+ * @checker_framework.manual #guieffect-checker GUI Effect Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD})
+public @interface PolyUIEffect {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUIType.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUIType.java
new file mode 100644
index 0000000..2026273
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUIType.java
@@ -0,0 +1,18 @@
+package org.checkerframework.checker.guieffect.qual;
+
+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;
+
+/**
+ * Annotation for the polymorphic type declaration.
+ *
+ * @checker_framework.manual #guieffect-checker GUI Effect Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface PolyUIType {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/SafeEffect.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/SafeEffect.java
new file mode 100644
index 0000000..1d529d9
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/SafeEffect.java
@@ -0,0 +1,17 @@
+package org.checkerframework.checker.guieffect.qual;
+
+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;
+
+/**
+ * Annotation for the concrete safe effect on methods, or on field accesses.
+ *
+ * @checker_framework.manual #guieffect-checker GUI Effect Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD})
+public @interface SafeEffect {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/SafeType.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/SafeType.java
new file mode 100644
index 0000000..a1ced02
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/SafeType.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.guieffect.qual;
+
+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;
+
+/**
+ * Class declaration annotation to make methods default to {@code @AlwaysSafe}. While the normal
+ * default is already {@code @AlwaysSafe} methods, this is useful for a type inside a package marked
+ * {@code @UIPackage}.
+ *
+ * @checker_framework.manual #guieffect-checker GUI Effect Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface SafeType {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UI.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UI.java
new file mode 100644
index 0000000..615b4da
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UI.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.guieffect.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Annotation for the UI effect.
+ *
+ * @checker_framework.manual #guieffect-checker GUI Effect Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+public @interface UI {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UIEffect.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UIEffect.java
new file mode 100644
index 0000000..67a6d43
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UIEffect.java
@@ -0,0 +1,17 @@
+package org.checkerframework.checker.guieffect.qual;
+
+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;
+
+/**
+ * Annotation for the concrete UI effect on methods, or on field accesses.
+ *
+ * @checker_framework.manual #guieffect-checker GUI Effect Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD})
+public @interface UIEffect {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UIPackage.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UIPackage.java
new file mode 100644
index 0000000..6f0a5f5
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UIPackage.java
@@ -0,0 +1,17 @@
+package org.checkerframework.checker.guieffect.qual;
+
+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;
+
+/**
+ * Package annotation to make all classes within a package {@code @UIType}.
+ *
+ * @checker_framework.manual #guieffect-checker GUI Effect Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PACKAGE})
+public @interface UIPackage {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UIType.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UIType.java
new file mode 100644
index 0000000..b3cb665
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UIType.java
@@ -0,0 +1,17 @@
+package org.checkerframework.checker.guieffect.qual;
+
+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;
+
+/**
+ * Class declaration annotation to make methods default to {@code @UI}.
+ *
+ * @checker_framework.manual #guieffect-checker GUI Effect Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface UIType {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKey.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKey.java
new file mode 100644
index 0000000..ce74615
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKey.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.i18n.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Indicates that the {@code String} is a key into a property file or resource bundle containing
+ * Localized Strings.
+ *
+ * @checker_framework.manual #i18n-checker Internationalization Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownLocalizableKey.class)
+public @interface LocalizableKey {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKeyBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKeyBottom.java
new file mode 100644
index 0000000..712244a
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKeyBottom.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.i18n.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Internationalization type system. Programmers should rarely write this
+ * type.
+ *
+ * @checker_framework.manual #i18n-checker Internationalization Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf(LocalizableKey.class)
+@DefaultFor(TypeUseLocation.LOWER_BOUND)
+public @interface LocalizableKeyBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/Localized.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/Localized.java
new file mode 100644
index 0000000..663cb07
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/Localized.java
@@ -0,0 +1,31 @@
+package org.checkerframework.checker.i18n.qual;
+
+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;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Indicates that the {@code String} type has been localized and formatted for the target output
+ * locale.
+ *
+ * @checker_framework.manual #i18n-checker Internationalization Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownLocalized.class)
+@QualifierForLiterals({
+  // All literals except chars and strings, which may need to be localized.
+  // (null is bottom by default.)
+  LiteralKind.INT,
+  LiteralKind.LONG,
+  LiteralKind.FLOAT,
+  LiteralKind.DOUBLE,
+  LiteralKind.BOOLEAN
+})
+public @interface Localized {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalizableKey.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalizableKey.java
new file mode 100644
index 0000000..71a8448
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalizableKey.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.i18n.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Indicates that the {@code String} type has an unknown localizable key property.
+ *
+ * @checker_framework.manual #i18n-checker Internationalization Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@InvisibleQualifier
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface UnknownLocalizableKey {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalized.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalized.java
new file mode 100644
index 0000000..2bb0980
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalized.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.i18n.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Indicates that the {@code String} type has unknown localization properties.
+ *
+ * @checker_framework.manual #i18n-checker Internationalization Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@InvisibleQualifier
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface UnknownLocalized {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nChecksFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nChecksFormat.java
new file mode 100644
index 0000000..b37f899
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nChecksFormat.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.i18nformatter.qual;
+
+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;
+
+/**
+ * This annotation is used internally to annotate {@link
+ * org.checkerframework.checker.i18nformatter.util.I18nFormatUtil#hasFormat} (and will potentially
+ * be used to annotate more such functions in the future).
+ *
+ * <p>Attach this annotation to a method with the following properties:
+ *
+ * <ul>
+ *   <li>The first parameter is a format string.
+ *   <li>The second parameter is a vararg that takes conversion categories.
+ *   <li>The method returns true if the format string is compatible with the conversion categories.
+ * </ul>
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface I18nChecksFormat {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java
new file mode 100644
index 0000000..e8ee3d3
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java
@@ -0,0 +1,204 @@
+package org.checkerframework.checker.i18nformatter.qual;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+/**
+ * Elements of this enumeration are used in a {@link I18nFormat} annotation to indicate the valid
+ * types that may be passed as a format parameter. For example:
+ *
+ * <pre>{@literal @}I18nFormat({GENERAL, NUMBER}) String f = "{0}{1, number}";
+ * MessageFormat.format(f, "Example", 0) // valid</pre>
+ *
+ * The annotation indicates that the format string requires any object as the first parameter
+ * ({@link I18nConversionCategory#GENERAL}) and a number as the second parameter ({@link
+ * I18nConversionCategory#NUMBER}).
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+@AnnotatedFor("nullness")
+public enum I18nConversionCategory {
+
+  /**
+   * Use if a parameter is not used by the formatter. For example, in
+   *
+   * <pre>
+   * MessageFormat.format(&quot;{1}&quot;, a, b);
+   * </pre>
+   *
+   * only the second argument ("b") is used. The first argument ("a") is ignored.
+   */
+  UNUSED(null /* everything */, null),
+
+  /** Use if the parameter can be of any type. */
+  GENERAL(null /* everything */, null),
+
+  /** Use if the parameter can be of date, time, or number types. */
+  DATE(new Class<?>[] {Date.class, Number.class}, new String[] {"date", "time"}),
+
+  /**
+   * Use if the parameter can be of number or choice types. An example of choice:
+   *
+   * <pre>{@code
+   * format("{0, choice, 0#zero|1#one|1<{0, number} is more than 1}", 2)
+   * }</pre>
+   *
+   * This will print "2 is more than 1".
+   */
+  NUMBER(new Class<?>[] {Number.class}, new String[] {"number", "choice"});
+
+  @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up!
+  public final Class<?> @Nullable [] types;
+
+  @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up!
+  public final String @Nullable [] strings;
+
+  I18nConversionCategory(Class<?> @Nullable [] types, String @Nullable [] strings) {
+    this.types = types;
+    this.strings = strings;
+  }
+
+  /** Used by {@link #stringToI18nConversionCategory}. */
+  static I18nConversionCategory[] namedCategories = new I18nConversionCategory[] {DATE, NUMBER};
+
+  /**
+   * Creates a conversion cagetogry from a string name.
+   *
+   * <pre>
+   * I18nConversionCategory.stringToI18nConversionCategory("number") == I18nConversionCategory.NUMBER;
+   * </pre>
+   *
+   * @return the I18nConversionCategory associated with the given string
+   */
+  @SuppressWarnings(
+      "nullness:iterating.over.nullable") // in namedCategories, `strings` field is non-null
+  public static I18nConversionCategory stringToI18nConversionCategory(String string) {
+    string = string.toLowerCase();
+    for (I18nConversionCategory v : namedCategories) {
+      for (String s : v.strings) {
+        if (s.equals(string)) {
+          return v;
+        }
+      }
+    }
+    throw new IllegalArgumentException("Invalid format type " + string);
+  }
+
+  private static <E> Set<E> arrayToSet(E[] a) {
+    return new HashSet<>(Arrays.asList(a));
+  }
+
+  /**
+   * Return true if a is a subset of b.
+   *
+   * @return true if a is a subset of b
+   */
+  public static boolean isSubsetOf(I18nConversionCategory a, I18nConversionCategory b) {
+    return intersect(a, b) == a;
+  }
+
+  /**
+   * Returns the intersection of the two given I18nConversionCategories.
+   *
+   * <blockquote>
+   *
+   * <pre>
+   * I18nConversionCategory.intersect(DATE, NUMBER) == NUMBER;
+   * </pre>
+   *
+   * </blockquote>
+   */
+  public static I18nConversionCategory intersect(
+      I18nConversionCategory a, I18nConversionCategory b) {
+    if (a == UNUSED) {
+      return b;
+    }
+    if (b == UNUSED) {
+      return a;
+    }
+    if (a == GENERAL) {
+      return b;
+    }
+    if (b == GENERAL) {
+      return a;
+    }
+
+    @SuppressWarnings("nullness:argument" // types field  is only null in UNUSED and GENERAL
+    )
+    Set<Class<?>> as = arrayToSet(a.types);
+    @SuppressWarnings("nullness:argument" // types field  is only null in UNUSED and GENERAL
+    )
+    Set<Class<?>> bs = arrayToSet(b.types);
+    as.retainAll(bs); // intersection
+    for (I18nConversionCategory v : new I18nConversionCategory[] {DATE, NUMBER}) {
+      @SuppressWarnings("nullness:argument") // in those values, `types` field is non-null
+      Set<Class<?>> vs = arrayToSet(v.types);
+      if (vs.equals(as)) {
+        return v;
+      }
+    }
+    throw new RuntimeException();
+  }
+
+  /**
+   * Returns the union of the two given I18nConversionCategories.
+   *
+   * <pre>
+   * I18nConversionCategory.intersect(DATE, NUMBER) == DATE;
+   * </pre>
+   */
+  public static I18nConversionCategory union(I18nConversionCategory a, I18nConversionCategory b) {
+    if (a == UNUSED || b == UNUSED) {
+      return UNUSED;
+    }
+    if (a == GENERAL || b == GENERAL) {
+      return GENERAL;
+    }
+    if (a == DATE || b == DATE) {
+      return DATE;
+    }
+    return NUMBER;
+  }
+
+  /**
+   * Returns true if {@code argType} can be an argument used by this format specifier.
+   *
+   * @param argType an argument type
+   * @return true if {@code argType} can be an argument used by this format specifier
+   */
+  public boolean isAssignableFrom(Class<?> argType) {
+    if (types == null) {
+      return true;
+    }
+    if (argType == void.class) {
+      return true;
+    }
+    for (Class<?> c : types) {
+      if (c.isAssignableFrom(argType)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /** Returns a pretty printed {@link I18nConversionCategory}. */
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder(this.name());
+    if (this.types == null) {
+      sb.append(" conversion category (all types)");
+    } else {
+      StringJoiner sj = new StringJoiner(", ", " conversion category (one of: ", ")");
+      for (Class<?> cls : this.types) {
+        sj.add(cls.getCanonicalName());
+      }
+      sb.append(sj);
+    }
+    return sb.toString();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormat.java
new file mode 100644
index 0000000..5e29edc
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormat.java
@@ -0,0 +1,42 @@
+package org.checkerframework.checker.i18nformatter.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This annotation, attached to a String type, indicates that the String may be passed to {@link
+ * java.text.MessageFormat#format(String, Object...) MessageFormat.format}.
+ *
+ * <p>The annotation's value represents the valid arguments that may be passed to the format method.
+ * For example:
+ *
+ * <pre>{@literal @}I18nFormat({GENERAL, NUMBER}) String f;
+ *
+ * f = "{0}{1, number}"; // valid
+ * f = "{0} {1} {2}"; // error, the format string is stronger (more restrictive) than the specifiers.
+ * f = "{0, number} {1, number}"; // error, the format string is stronger (NUMBER is a subtype of GENERAL).
+ * </pre>
+ *
+ * The annotation indicates that the format string requires any object as the first parameter
+ * ({@link I18nConversionCategory#GENERAL}) and a number as the second parameter ({@link
+ * I18nConversionCategory#NUMBER}).
+ *
+ * @see I18nConversionCategory
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(I18nUnknownFormat.class)
+public @interface I18nFormat {
+  /**
+   * An array of {@link I18nConversionCategory}, indicating the types of legal remaining arguments
+   * when a value of the annotated type is used as the first argument to {@link
+   * java.text.MessageFormat#format(String, Object...) Message.format}.
+   */
+  I18nConversionCategory[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatBottom.java
new file mode 100644
index 0000000..44637a3
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatBottom.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.i18nformatter.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Internationalization Format String type system. Programmers should rarely
+ * write this type.
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf({I18nFormat.class, I18nInvalidFormat.class, I18nFormatFor.class})
+@DefaultFor(value = {TypeUseLocation.LOWER_BOUND})
+public @interface I18nFormatBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatFor.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatFor.java
new file mode 100644
index 0000000..a0e30d6
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatFor.java
@@ -0,0 +1,43 @@
+package org.checkerframework.checker.i18nformatter.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This annotation indicates that when a string of the annotated type is passed as the first
+ * argument to {@link java.text.MessageFormat#format(String, Object...)}, then the expression that
+ * is an argument to the annotation can be passed as the remaining arguments, in varargs style.
+ *
+ * <p>The annotation is used to annotate a method to ensure that an argument is of a particular type
+ * indicated by a format string.
+ *
+ * <p>Example:
+ *
+ * <pre> static void method(@I18nFormatFor("#2") String format, Object... arg2) {...}
+ *
+ * method("{0, number}", 2);</pre>
+ *
+ * This ensures that the second parameter ("#2") can be passed as the remaining arguments of {@link
+ * java.text.MessageFormat#format(String, Object...)}, when the first argument is {@code "format"}.
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(I18nUnknownFormat.class)
+public @interface I18nFormatFor {
+  /**
+   * Indicates which formal parameter is the arguments to the format method. The value should be
+   * {@code #} followed by the 1-based index of the formal parameter that is the arguments to the
+   * format method, e.g., {@code "#2"}.
+   *
+   * @return {@code #} followed by the 1-based index of the formal parameter that is the arguments
+   *     to the format method
+   */
+  String value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nInvalidFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nInvalidFormat.java
new file mode 100644
index 0000000..379082d
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nInvalidFormat.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.i18nformatter.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This annotation, attached to a {@link java.lang.String String} type, indicates that if the String
+ * is passed to {@link java.text.MessageFormat#format(String, Object...)}, an exception will result.
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(I18nUnknownFormat.class)
+public @interface I18nInvalidFormat {
+  /**
+   * Using a value of the annotated type as the first argument to {@link
+   * java.text.MessageFormat#format(String, Object...)} will lead to this exception message.
+   */
+  String value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nMakeFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nMakeFormat.java
new file mode 100644
index 0000000..a9d6641
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nMakeFormat.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.i18nformatter.qual;
+
+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;
+
+/**
+ * This annotation is used internally to annotate {@link java.util.ResourceBundle#getString}
+ * indicating the checker to check if the given key exist in the translation file and annotate the
+ * result string with the correct format annotation according to the corresponding key's value. This
+ * is done in {@link org.checkerframework.checker.i18nformatter.I18nFormatterTransfer}
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface I18nMakeFormat {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nUnknownFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nUnknownFormat.java
new file mode 100644
index 0000000..f8dbc7d
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nUnknownFormat.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.i18nformatter.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The top qualifier.
+ *
+ * <p>A type annotation indicating that the run-time value might or might not be a valid i18n format
+ * string.
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@InvisibleQualifier
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface I18nUnknownFormat {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nValidFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nValidFormat.java
new file mode 100644
index 0000000..3285964
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nValidFormat.java
@@ -0,0 +1,18 @@
+package org.checkerframework.checker.i18nformatter.qual;
+
+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;
+
+/**
+ * This annotation is used internally to annotate {@link
+ * org.checkerframework.checker.i18nformatter.util.I18nFormatUtil#isFormat}.
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface I18nValidFormat {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOf.java
new file mode 100644
index 0000000..e799cd4
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOf.java
@@ -0,0 +1,105 @@
+package org.checkerframework.checker.index.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.PostconditionAnnotation;
+import org.checkerframework.framework.qual.QualifierArgument;
+
+/**
+ * Indicates that the value expressions evaluate to an integer whose value is less than the lengths
+ * of all the given sequences, if the method terminates successfully.
+ *
+ * <p>Consider the following example, from the Index Checker's regression tests:
+ *
+ * <pre>
+ * {@code @EnsuresLTLengthOf(value = "end", targetValue = "array", offset = "#1 - 1")
+ *  public void shiftIndex(@NonNegative int x) {
+ *      int newEnd = end - x;
+ *      if (newEnd < 0) throw new RuntimeException();
+ *      end = newEnd;
+ *  }
+ * }
+ * </pre>
+ *
+ * where {@code end} is annotated as {@code @NonNegative @LTEqLengthOf("array") int end;}
+ *
+ * <p>This method guarantees that {@code end} has type {@code @LTLengthOf(value="array", offset="x -
+ * 1")} after the method returns. This is useful in cases like this one:
+ *
+ * <pre>{@code
+ * public void useShiftIndex(@NonNegative int x) {
+ *    // :: error: (argument)
+ *    Arrays.fill(array, end, end + x, null);
+ *    shiftIndex(x);
+ *    Arrays.fill(array, end, end + x, null);
+ * }
+ * }</pre>
+ *
+ * The first call to {@code Arrays.fill} is rejected (hence the comment about an error). But, after
+ * calling {@code shiftIndex(x)}, {@code end} has an annotation that allows the {@code end + x} to
+ * be accepted as {@code @LTLengthOf("array")}.
+ *
+ * @see LTLengthOf
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@PostconditionAnnotation(qualifier = LTLengthOf.class)
+@InheritedAnnotation
+@Repeatable(EnsuresLTLengthOf.List.class)
+public @interface EnsuresLTLengthOf {
+  /**
+   * The Java expressions that are less than the length of the given sequences on successful method
+   * termination.
+   *
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  @JavaExpression
+  String[] value();
+
+  /**
+   * Sequences, each of which is longer than the each of the expressions' value on successful method
+   * termination.
+   */
+  @JavaExpression
+  @QualifierArgument("value")
+  String[] targetValue();
+
+  /**
+   * This expression plus each of the value expressions is less than the length of the sequence on
+   * successful method termination. The {@code offset} element must ether be empty or the same
+   * length as {@code targetValue}.
+   *
+   * @return the offset expressions
+   */
+  @JavaExpression
+  @QualifierArgument("offset")
+  String[] offset() default {};
+
+  /**
+   * A wrapper annotation that makes the {@link EnsuresLTLengthOf} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresLTLengthOf} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @PostconditionAnnotation(qualifier = LTLengthOf.class)
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Return the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresLTLengthOf[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOfIf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOfIf.java
new file mode 100644
index 0000000..926d01b
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOfIf.java
@@ -0,0 +1,110 @@
+package org.checkerframework.checker.index.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.QualifierArgument;
+
+/**
+ * Indicates that the given expressions evaluate to an integer whose value is less than the lengths
+ * of all the given sequences, if the method returns the given result (either true or false).
+ *
+ * <p>As an example, consider the following method:
+ *
+ * <pre>
+ *      &#64;EnsuresLTLengthOfIf(
+ *          expression = "end",
+ *          result = true,
+ *          targetValue = "array",
+ *          offset = "#1 - 1"
+ *      )
+ *      public boolean tryShiftIndex(&#64;NonNegative int x) {
+ *          int newEnd = end - x;
+ *          if (newEnd &#60; 0) {
+ *             return false;
+ *          }
+ *          end = newEnd;
+ *          return true;
+ *      }
+ * </pre>
+ *
+ * Calling this function ensures that the field {@code end} of the {@code this} object is of type
+ * {@code @LTLengthOf(value = "array", offset = "x - 1")}, for the value {@code x} that is passed as
+ * the argument. This allows the Index Checker to verify that {@code end + x} is an index into
+ * {@code array} in the following code:
+ *
+ * <pre>
+ *      public void useTryShiftIndex(&#64;NonNegative int x) {
+ *          if (tryShiftIndex(x)) {
+ *              Arrays.fill(array, end, end + x, null);
+ *          }
+ *      }
+ * </pre>
+ *
+ * @see LTLengthOf
+ * @see EnsuresLTLengthOf
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@ConditionalPostconditionAnnotation(qualifier = LTLengthOf.class)
+@InheritedAnnotation
+@Repeatable(EnsuresLTLengthOfIf.List.class)
+public @interface EnsuresLTLengthOfIf {
+  /**
+   * Java expression(s) that are less than the length of the given sequences after the method
+   * returns the given result.
+   *
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  String[] expression();
+
+  /** The return value of the method that needs to hold for the postcondition to hold. */
+  boolean result();
+
+  /**
+   * Sequences, each of which is longer than each of the expressions' value after the method returns
+   * the given result.
+   */
+  @JavaExpression
+  @QualifierArgument("value")
+  String[] targetValue();
+
+  /**
+   * This expression plus each of the expressions is less than the length of the sequence after the
+   * method returns the given result. The {@code offset} element must ether be empty or the same
+   * length as {@code targetValue}.
+   *
+   * @return the offset expressions
+   */
+  @JavaExpression
+  @QualifierArgument("offset")
+  String[] offset() default {};
+
+  /**
+   * A wrapper annotation that makes the {@link EnsuresLTLengthOfIf} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresLTLengthOfIf} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @ConditionalPostconditionAnnotation(qualifier = LTLengthOf.class)
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Return the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresLTLengthOfIf[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/GTENegativeOne.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/GTENegativeOne.java
new file mode 100644
index 0000000..034dc32
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/GTENegativeOne.java
@@ -0,0 +1,35 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The annotated expression evaluates to an integer greater than or equal to -1. ("GTE" stands for
+ * ``Greater Than or Equal to''.)
+ *
+ * <p>As an example use case, consider the definition of the read() method in java.io.InputStream:
+ *
+ * <pre>
+ *
+ *      Reads the next byte of data from the input stream. The value byte is returned as an int in the range 0 to 255.
+ *      If no byte is available because the end of the stream has been reached, the value -1 is returned.
+ *      This method blocks until input data is available, the end of the stream is detected, or an exception is thrown.
+ *      A subclass must provide an implementation of this method.
+ *
+ *      Returns: the next byte of data, or -1 if the end of the stream is reached.
+ *      Throws: IOException - if an I/O error occurs.
+ *
+ *     {@code public abstract @GTENegativeOne int read() throws IOException;}
+ * </pre>
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({LowerBoundUnknown.class})
+public @interface GTENegativeOne {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/HasSubsequence.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/HasSubsequence.java
new file mode 100644
index 0000000..4283e2f
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/HasSubsequence.java
@@ -0,0 +1,74 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.JavaExpression;
+
+/**
+ * The annotated sequence contains a subsequence that is equal to the value of some other
+ * expression. This annotation permits the Upper Bound Checker to translate indices for one sequence
+ * into indices for the other sequence.
+ *
+ * <p>Consider the following example:
+ *
+ * <pre><code>
+ *  class IntSubArray {
+ *    {@literal @}HasSubsequence(subsequence = "this", from = "this.start", to = "this.end")
+ *    int [] array;
+ *    {@literal @}IndexFor("array") int start;
+ *    {@literal @}IndexOrHigh("array") int end;
+ *  }
+ * </code></pre>
+ *
+ * The above annotations mean that the value of an {@code IntSubArray} object is equal to a
+ * subsequence of its {@code array} field.
+ *
+ * <p>These annotations imply the following relationships among {@code @}{@link IndexFor}
+ * annotations:
+ *
+ * <ul>
+ *   <li>If {@code i} is {@code @IndexFor("this")}, then {@code start + i} is
+ *       {@code @IndexFor("array")}.
+ *   <li>If {@code j} is {@code @IndexFor("array")}, then {@code j - start } is
+ *       {@code @IndexFor("this")}.
+ * </ul>
+ *
+ * When assigning an array {@code a} to {@code array}, 4 facts need to be true:
+ *
+ * <ul>
+ *   <li>{@code start} is {@code @NonNegative}.
+ *   <li>{@code end} is {@code @LTEqLengthOf("a")}.
+ *   <li>{@code start} is {@code @LessThan("end + 1")}.
+ *   <li>the value of {@code this} equals {@code array[start..end-1]}
+ * </ul>
+ *
+ * The Index Checker verifies the first 3 facts, but always issues a warning because it cannot prove
+ * the 4th fact. The programmer should should manually verify that the {@code subsequence} field is
+ * equal to the given subsequence and then suppress the warning.
+ *
+ * <p>For an example of how this annotation is used in practice, see the test GuavaPrimitives.java
+ * in /checker/tests/index/.
+ *
+ * <p>This annotation may only be written on fields.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface HasSubsequence {
+  /** An expression that evaluates to the subsequence. */
+  @JavaExpression
+  String subsequence();
+
+  /** The index into this where the subsequence starts. */
+  @JavaExpression
+  String from();
+
+  /** The index into this, immediately past where the subsequence ends. */
+  @JavaExpression
+  String to();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexFor.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexFor.java
new file mode 100644
index 0000000..5393a55
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexFor.java
@@ -0,0 +1,41 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+
+/**
+ * An integer that can be used to index any of the given sequences.
+ *
+ * <p>For example, an expression with type {@code @IndexFor({"a", "b"})} is non-negative and is less
+ * than both {@code a.length} and {@code b.length}. The sequences {@code a} and {@code b} might have
+ * different lengths.
+ *
+ * <p>The <a
+ * href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#charAt(int)">
+ * {@code String.charAt(int)}</a> method is declared as
+ *
+ * <pre>{@code
+ * class String {
+ *   char charAt(@IndexFor("this") index) { ... }
+ * }
+ * }</pre>
+ *
+ * <p>Writing {@code @IndexFor("arr")} is equivalent to writing {@link NonNegative @NonNegative}
+ * {@link LTLengthOf @LTLengthOf("arr")}, and that is how it is treated internally by the checker.
+ * Thus, if you write an {@code @IndexFor("arr")} annotation, you might see warnings about
+ * {@code @NonNegative} or {@code @LTLengthOf}.
+ *
+ * @see NonNegative
+ * @see LTLengthOf
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface IndexFor {
+  /** Sequences that the annotated expression is a valid index for. */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrHigh.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrHigh.java
new file mode 100644
index 0000000..b2bbd6a
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrHigh.java
@@ -0,0 +1,38 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+
+/**
+ * An integer that, for each of the given sequences, is either a valid index or is equal to the
+ * sequence's length.
+ *
+ * <p>The <a
+ * href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Arrays.html#binarySearch(java.lang.Object%5B%5D,int,int,java.lang.Object)">
+ * {@code Arrays.binarySearch}</a> method is declared as
+ *
+ * <pre>{@code
+ * class Arrays {
+ *   int binarySearch(Object[] a, @IndexFor("#1") int fromIndex, @IndexOrHigh("#1") int toIndex, Object key)
+ * }
+ * }</pre>
+ *
+ * <p>Writing {@code @IndexOrHigh("arr")} is equivalent to writing {@link NonNegative @NonNegative}
+ * {@link LTEqLengthOf @LTEqLengthOf("arr")}, and that is how it is treated internally by the
+ * checker. Thus, if you write an {@code @IndexFor("arr")} annotation, you might see warnings about
+ * {@code @NonNegative} or {@code @LTEqLengthOf}.
+ *
+ * @see NonNegative
+ * @see LTLengthOf
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface IndexOrHigh {
+  /** Sequences that the annotated expression is a valid index for or is equal to the lengeth of. */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrLow.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrLow.java
new file mode 100644
index 0000000..1965d76
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrLow.java
@@ -0,0 +1,37 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+
+/**
+ * An integer that is either -1 or is a valid index for each of the given sequences.
+ *
+ * <p>The <a
+ * href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#indexOf(java.lang.String)">
+ * {@code String.indexOf(String)}</a> method is declared as
+ *
+ * <pre><code>
+ *   class String {
+ *    {@literal @}IndexOrLow("this") int indexOf(String str) { ... }
+ *   }
+ * </code></pre>
+ *
+ * <p>Writing {@code @IndexOrLow("arr")} is equivalent to writing {@link
+ * GTENegativeOne @GTENegativeOne} {@link LTLengthOf @LTLengthOf("arr")}, and that is how it is
+ * treated internally by the checker. Thus, if you write an {@code @IndexOrLow("arr")} annotation,
+ * you might see warnings about {@code @GTENegativeOne} or {@code @LTLengthOf}.
+ *
+ * @see GTENegativeOne
+ * @see LTLengthOf
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface IndexOrLow {
+  /** Sequences that the annotated expression is a valid index for (or it's -1). */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTEqLengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTEqLengthOf.java
new file mode 100644
index 0000000..1035cee
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTEqLengthOf.java
@@ -0,0 +1,33 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The annotated expression evaluates to an integer whose value is less than or equal to the lengths
+ * of all the given sequences. ("LTEq" stands for "Less than or equal to".)
+ *
+ * <p>For example, an expression with type {@code @LTLengthOf({"a", "b"})} is less than or equal to
+ * both {@code a.length} and {@code b.length}. The sequences {@code a} and {@code b} might have
+ * different lengths.
+ *
+ * <p>{@code @LTEqLengthOf({"a"})} = {@code @LTLengthOf(value={"a"}, offset=-1)}, and<br>
+ * {@code @LTEqLengthOf(value={"a"}, offset=x)} = {@code @LTLengthOf(value={"a"}, offset=x-1)} for
+ * any x.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UpperBoundUnknown.class)
+public @interface LTEqLengthOf {
+  /** Sequences, each of which is at least as long as the annotated expression's value. */
+  @JavaExpression
+  public String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTLengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTLengthOf.java
new file mode 100644
index 0000000..770e19a
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTLengthOf.java
@@ -0,0 +1,52 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The annotated expression evaluates to an integer whose value is less than the lengths of all the
+ * given sequences. This annotation is rarely used; it is more common to use {@code @}{@link
+ * IndexFor}.
+ *
+ * <p>For example, an expression with type {@code @LTLengthOf({"a", "b"})} is less than both {@code
+ * a.length} and {@code b.length}. The sequences {@code a} and {@code b} might have different
+ * lengths.
+ *
+ * <p>The {@code @LTLengthOf} annotation takes an optional {@code offset} element. If it is
+ * nonempty, then the annotated expression plus the expression in {@code offset[i]} is less than the
+ * length of the sequence specified by {@code value[i]}.
+ *
+ * <p>For example, suppose expression {@code e} has type {@code @LTLengthOf(value = {"a", "b"},
+ * offset = {"-1", "x"})}. Then {@code e - 1} is less than {@code a.length}, and {@code e + x} is
+ * less than {@code b.length}.
+ *
+ * <p>It is an error to write a {@code LTLengthOf} annotation with a different number of sequences
+ * and offsets, if an offset is included.
+ *
+ * @see IndexFor
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(LTEqLengthOf.class)
+public @interface LTLengthOf {
+  /** Sequences, each of which is longer than the annotated expression's value. */
+  @JavaExpression
+  public String[] value();
+
+  /**
+   * This expression plus the annotated expression is less than the length of the sequence. The
+   * {@code offset} element must ether be empty or the same length as {@code value}.
+   *
+   * <p>The expressions in {@code offset} may be addition/subtraction of any number of Java
+   * expressions. For example, {@code @LessThanLengthOf(value = "a", offset = "x + y + 2"}}.
+   */
+  @JavaExpression
+  String[] offset() default {};
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTOMLengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTOMLengthOf.java
new file mode 100644
index 0000000..43a545d
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTOMLengthOf.java
@@ -0,0 +1,38 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The annotated expression evaluates to an integer whose value is at least 2 less than the lengths
+ * of all the given sequences.
+ *
+ * <p>For example, an expression with type {@code @LTLengthOf({"a", "b"})} is less than or equal to
+ * both {@code a.length-2} and {@code b.length-2}. Equivalently, it is less than both {@code
+ * a.length-1} and {@code b.length-1}. The sequences {@code a} and {@code b} might have different
+ * lengths.
+ *
+ * <p>In the annotation's name, "LTOM" stands for "less than one minus".
+ *
+ * <p>{@code @LTOMLengthOf({"a"})} = {@code @LTLengthOf(value={"a"}, offset=1)}, and<br>
+ * {@code @LTOMLengthOf(value={"a"}, offset=x)} = {@code @LTLengthOf(value={"a"}, offset=x+1)} for
+ * any x.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(LTLengthOf.class)
+public @interface LTOMLengthOf {
+  /**
+   * Sequences, each of whose lengths is at least 1 larger than the annotated expression's value.
+   */
+  @JavaExpression
+  public String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LengthOf.java
new file mode 100644
index 0000000..53adb1a
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LengthOf.java
@@ -0,0 +1,34 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+
+/**
+ * An integer that, for each of the given sequences, is equal to the sequence's length.
+ *
+ * <p>This is treated as an {@link IndexOrHigh} annotation internally. This is an implementation
+ * detail that may change in the future, when this type may be used to implement more precise
+ * refinements.
+ *
+ * <p>The usual use case for the {@code LengthOf} annotation is in the defintions of custom
+ * collections. Consider the signature of java.lang.String#length():
+ *
+ * <pre>
+ *
+ *     {@code public @LengthOf("this") int length()}
+ * </pre>
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+// Has target of METHOD so that it is stored as a declaration annotation and the SameLen Checker can
+// read it.
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER, ElementType.METHOD})
+public @interface LengthOf {
+  /** Sequences that the annotated expression is equal to the length of. */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThan.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThan.java
new file mode 100644
index 0000000..8bcc1ba
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThan.java
@@ -0,0 +1,48 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An annotation indicating the relationship between values with a byte, short, char, int, or long
+ * type.
+ *
+ * <p>If an expression's type has this annotation, then at run time, the expression evaluates to a
+ * value that is less than the value of the expression in the annotation.
+ *
+ * <p>{@code @LessThan("end + 1")} is equivalent to {@code @LessThanOrEqual("end")}.
+ *
+ * <p>Subtyping:
+ *
+ * <ul>
+ *   <li>{@code @LessThan({"a", "b"}) <: @LessThan({"a"})}
+ *   <li>{@code @LessThan({"a", "b"})} is not related to {@code @LessThan({"a", "c"})}.
+ * </ul>
+ *
+ * @checker_framework.manual #index-inequalities Index Chceker Inequalities
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf({LessThanUnknown.class})
+// TODO: I chose to implement less than rather than greater than because in most of the case studies
+// false positives, the bigger value is final or effectively final, so it can appear in a dependent
+// annotation without causing soundness issues.
+public @interface LessThan {
+  /**
+   * The annotated expression's value is less than this expression.
+   *
+   * <p>The expressions in {@code value} may be addition/subtraction of any number of Java
+   * expressions. For example, {@code @LessThan(value = "x + y + 2"}}.
+   *
+   * <p>The expression in {@code value} must be final or constant or the addition/subtract of final
+   * or constant expressions.
+   */
+  @JavaExpression
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanBottom.java
new file mode 100644
index 0000000..422f87e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanBottom.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The bottom type in the LessThan type system. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #index-inequalities Index Chceker Inequalities
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf({LessThan.class})
+public @interface LessThanBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanUnknown.java
new file mode 100644
index 0000000..3b456c3
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanUnknown.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The top qualifier for the LessThan type hierarchy. It indicates that no other expression is known
+ * to be larger than the annotated one.
+ *
+ * @checker_framework.manual #index-inequalities Index Chceker Inequalities
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface LessThanUnknown {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundBottom.java
new file mode 100644
index 0000000..3cfe92b
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundBottom.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type of the lower bound type system. A variable annotated with this value cannot take
+ * on any integer values.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf({Positive.class})
+public @interface LowerBoundBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundUnknown.java
new file mode 100644
index 0000000..f9c9031
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundUnknown.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The annotated expression evaluates to value that might be -2 or lower. This is the top type for
+ * the Lower Bound type system. It should not have to be written by a programmer.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface LowerBoundUnknown {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NegativeIndexFor.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NegativeIndexFor.java
new file mode 100644
index 0000000..2fd92db
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NegativeIndexFor.java
@@ -0,0 +1,43 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The annotated expression is between {@code -1} and {@code -a.length - 1}, inclusive, for each
+ * sequence {@code a} listed in the annotation.
+ *
+ * <p>This type should rarely (if ever) be written by programmers. It is inferred by the
+ * SearchIndexChecker when the result of a call to one of the JDK's binary search methods (like
+ * {@code Arrays.binarySearch}) is known to be less than zero. For example, consider the following
+ * code:
+ *
+ * <pre>
+ *
+ *     int index = Arrays.binarySearch(array, target);
+ *     if (index &#60; 0) {
+ *          // index's type here is &#64;NegativeIndexFor("array")
+ *          index = index * -1;
+ *          // now index's type is &#64;IndexFor("array")
+ *     }
+ * </pre>
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SearchIndexFor.class)
+public @interface NegativeIndexFor {
+  /**
+   * Sequences for which this value is a "negative index"; that is, the expression is in the range
+   * {@code -1} to {@code -a.length - 1}, inclusive, for each sequence {@code a} given here.
+   */
+  @JavaExpression
+  public String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NonNegative.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NonNegative.java
new file mode 100644
index 0000000..40806f1
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NonNegative.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The annotated expression evaluates to an integer greater than or equal to 0.
+ *
+ * <p>Consider the following example, from a collection that wraps an array. This constructor
+ * creates the {@code delegate} array, which must have a non-negative size.
+ *
+ * <pre>{@code
+ * ArrayWrapper(@NonNegative int size) { delegate = new Object[size]; }
+ * }</pre>
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({GTENegativeOne.class})
+public @interface NonNegative {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyIndex.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyIndex.java
new file mode 100644
index 0000000..fa5c811
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyIndex.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the Lower Bound and Upper Bound type systems.
+ *
+ * <p>Writing {@code @PolyIndex} is equivalent to writing {@link PolyUpperBound @PolyUpperBound}
+ * {@link PolyLowerBound @PolyLowerBound}, and that is how it is treated internally by the checker.
+ * Thus, if you write an {@code @PolyIndex} annotation, you might see warnings about
+ * {@code @PolyUpperBound} or {@code @PolyLowerBound}.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ * @see PolyUpperBound
+ * @see PolyLowerBound
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(UpperBoundUnknown.class)
+public @interface PolyIndex {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLength.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLength.java
new file mode 100644
index 0000000..4d51237
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLength.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * Syntactic sugar for both @PolyValue and @PolySameLen.
+ *
+ * @see org.checkerframework.common.value.qual.PolyValue
+ * @see PolySameLen
+ * @checker_framework.manual #index-checker Index Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(UpperBoundUnknown.class)
+public @interface PolyLength {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLowerBound.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLowerBound.java
new file mode 100644
index 0000000..824609a
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLowerBound.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the Lower Bound type system.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(LowerBoundUnknown.class)
+public @interface PolyLowerBound {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolySameLen.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolySameLen.java
new file mode 100644
index 0000000..89e7fc4
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolySameLen.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the SameLen type system.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(SameLenUnknown.class)
+public @interface PolySameLen {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyUpperBound.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyUpperBound.java
new file mode 100644
index 0000000..c8eaa92
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyUpperBound.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the Upper Bound type system.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(UpperBoundUnknown.class)
+public @interface PolyUpperBound {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/Positive.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/Positive.java
new file mode 100644
index 0000000..7321a02
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/Positive.java
@@ -0,0 +1,30 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The annotated expression evaluates to an integer greater than or equal to 1.
+ *
+ * <p>As an example of a use-case for this type, consider the following code:
+ *
+ * <pre>{@code
+ * if (arr.length > 0) {
+ *    int j = arr[arr.length - 1];
+ * }
+ * }</pre>
+ *
+ * Without the knowing that {@code arr.length} is positive, the Index Checker cannot verify that
+ * accessing the last element of the array is safe - there might not be a last element!
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({NonNegative.class})
+public @interface Positive {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLen.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLen.java
new file mode 100644
index 0000000..db65d59
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLen.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An expression whose type has this annotation evaluates to a value that is a sequence, and that
+ * sequence has the same length as the given sequences. For example, if {@code b}'s type is
+ * annotated with {@code @SameLen("a")}, then {@code a} and {@code b} have the same length.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SameLenUnknown.class)
+public @interface SameLen {
+  /** A list of other sequences with the same length. */
+  @JavaExpression
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenBottom.java
new file mode 100644
index 0000000..df9b82b
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenBottom.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the SameLen type system. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf(SameLen.class)
+public @interface SameLenBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenUnknown.java
new file mode 100644
index 0000000..f090c95
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenUnknown.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This type represents any variable that isn't known to have the same length as another sequence.
+ * This is the top type of the Same Length type system. Programmers should not need to write this
+ * type.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface SameLenUnknown {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexBottom.java
new file mode 100644
index 0000000..54ac9eb
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexBottom.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Search Index type system. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf(NegativeIndexFor.class)
+public @interface SearchIndexBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexFor.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexFor.java
new file mode 100644
index 0000000..e53499a
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexFor.java
@@ -0,0 +1,31 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The annotated expression evaluates to an integer whose length is between {@code -a.length - 1}
+ * and {@code a.length - 1}, inclusive, for all sequences {@code a} listed in the annotation.
+ *
+ * <p>This is the return type of {@link java.util.Arrays#binarySearch(Object[],Object) {@code
+ * Arrays.binarySearch}} in the JDK.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SearchIndexUnknown.class)
+public @interface SearchIndexFor {
+  /**
+   * Sequences for which the annotated expression has the type of the result of a call to {@link
+   * java.util.Arrays#binarySearch(Object[],Object) {@code Arrays.binarySearch}}.
+   */
+  @JavaExpression
+  public String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexUnknown.java
new file mode 100644
index 0000000..75bc343
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexUnknown.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The top type for the SearchIndex type system. This indicates that the Index checker does not know
+ * any arrays that this integer is a {@link SearchIndexFor search index} for.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface SearchIndexUnknown {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexBottom.java
new file mode 100644
index 0000000..797aea4
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexBottom.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Substring Index type system. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #index-substringindex Index Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf(SubstringIndexFor.class)
+public @interface SubstringIndexBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexFor.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexFor.java
new file mode 100644
index 0000000..235d0fa
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexFor.java
@@ -0,0 +1,58 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The annotated expression evaluates to either -1 or a non-negative integer less than the lengths
+ * of all the given sequences. The annotation {@code @SubstringIndexFor(args)} is like <code>
+ * {@literal @}{@link NonNegative} {@literal @}{@link LTLengthOf}(args)</code>, except that
+ * {@code @SubstringIndexFor(args)} additionally permits the value -1.
+ *
+ * <p>When multiple values or offsets are given, they are considered pairwise. For example,
+ * {@code @SubstringIndexFor(value={"a", "b"}, offset={"c", "d"})} is equivalent to writing both
+ * {@code @SubstringIndexFor(value="a", offset="c")} and {@code @SubstringIndexFor(value="b",
+ * offset="d")}.
+ *
+ * <p>The return types of JDK methods {@link java.lang.String#indexOf(String) String.indexOf} and
+ * {@link java.lang.String#lastIndexOf(String) String.lastIndexOf} are annotated
+ * {@code @SubstringIndexFor(value="this",offset="#1.length()-1")}. This means that the returned
+ * value is either -1 or it is less than or equal to the length of the receiver sequence minus the
+ * length of the sequence passed as the first argument.
+ *
+ * <p>The name of this annotation, "substring index for", is intended to mean that the annotated
+ * expression is a index of a substring (returned by {@code indexOf} or similar methods) for the
+ * specified sequence.
+ *
+ * @checker_framework.manual #index-substringindex Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SubstringIndexUnknown.class)
+public @interface SubstringIndexFor {
+  /**
+   * Sequences, each of which is longer than the annotated expression plus the corresponding {@code
+   * offset}. (Exception: the annotated expression is also allowed to have the value -1.)
+   */
+  @JavaExpression
+  public String[] value();
+
+  /**
+   * This expression plus the annotated expression is less than the length of the corresponding
+   * sequence in the {@code value} array. (Exception: the annotated expression is also allowed to
+   * have the value -1.)
+   *
+   * <p>The {@code offset} array must either be empty or the same length as {@code value}.
+   *
+   * <p>The expressions in {@code offset} may be addition/subtraction of any number of Java
+   * expressions. For example, {@code @SubstringIndexFor(value = "a", offset = "b.length() - 1")}.
+   */
+  @JavaExpression
+  public String[] offset();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexUnknown.java
new file mode 100644
index 0000000..94c4480
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexUnknown.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The top type for the Substring Index type system. This indicates that the Index Checker does not
+ * know any sequences that this integer is a {@link SubstringIndexFor substring index} for.
+ *
+ * @checker_framework.manual #index-substringindex Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface SubstringIndexUnknown {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundBottom.java
new file mode 100644
index 0000000..ed231a1
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundBottom.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Upper Bound type system. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf({LTOMLengthOf.class, UpperBoundLiteral.class})
+public @interface UpperBoundBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundLiteral.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundLiteral.java
new file mode 100644
index 0000000..9a0ef13
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundLiteral.java
@@ -0,0 +1,30 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * A literal value. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf(LTEqLengthOf.class)
+public @interface UpperBoundLiteral {
+
+  /**
+   * Returns the value of the literal.
+   *
+   * @return the value of the literal
+   */
+  int value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundUnknown.java
new file mode 100644
index 0000000..8548028
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundUnknown.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.index.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * A variable not known to have a relation to any sequence length. This is the top type of the Upper
+ * Bound type system. Programmers should not need to write this type.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface UpperBoundUnknown {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/FBCBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/FBCBottom.java
new file mode 100644
index 0000000..5be6737
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/FBCBottom.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.initialization.qual;
+
+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;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the initialization type system. Programmers should rarely write this type.
+ *
+ * <p>The "FBC" in the name stands for "Freedom Before Commitment", an approach that the
+ * Initialization Checker builds upon.
+ *
+ * @checker_framework.manual #initialization-checker Initialization Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf({UnderInitialization.class, Initialized.class})
+@QualifierForLiterals(LiteralKind.NULL)
+public @interface FBCBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/Initialized.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/Initialized.java
new file mode 100644
index 0000000..cac3fc8
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/Initialized.java
@@ -0,0 +1,36 @@
+package org.checkerframework.checker.initialization.qual;
+
+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;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * This type qualifier belongs to the freedom-before-commitment initialization tracking type-system.
+ * This type-system is not used on its own, but in conjunction with some other type-system that
+ * wants to ensure safe initialization. For instance, {@link
+ * org.checkerframework.checker.nullness.NullnessChecker} uses freedom-before-commitment to track
+ * initialization of {@link NonNull} fields.
+ *
+ * <p>This type qualifier indicates that the object has been fully initialized; reading fields from
+ * such objects is fully safe and yields objects of the correct type.
+ *
+ * @checker_framework.manual #initialization-checker Initialization Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownInitialization.class)
+@DefaultQualifierInHierarchy
+@DefaultFor({
+  TypeUseLocation.IMPLICIT_UPPER_BOUND,
+  TypeUseLocation.IMPLICIT_LOWER_BOUND,
+  TypeUseLocation.EXCEPTION_PARAMETER
+})
+public @interface Initialized {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/NotOnlyInitialized.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/NotOnlyInitialized.java
new file mode 100644
index 0000000..89dc52e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/NotOnlyInitialized.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.initialization.qual;
+
+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;
+
+/**
+ * A declaration annotation for fields that indicates that a client might observe the field storing
+ * values that are {@link org.checkerframework.checker.initialization.qual.Initialized}, {@link
+ * org.checkerframework.checker.initialization.qual.UnderInitialization}, or {@link
+ * org.checkerframework.checker.initialization.qual.UnknownInitialization}, regardless of the
+ * initialization type annotation on the field's type. This is necessary to allow circular
+ * initialization as supported by freedom-before-commitment.
+ *
+ * @checker_framework.manual #initialization-checker Initialization Checker
+ */
+@Documented
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface NotOnlyInitialized {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnderInitialization.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnderInitialization.java
new file mode 100644
index 0000000..5f3c43e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnderInitialization.java
@@ -0,0 +1,52 @@
+package org.checkerframework.checker.initialization.qual;
+
+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;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This type qualifier indicates that an object is (definitely) in the process of being
+ * constructed/initialized. The type qualifier also indicates how much of the object has already
+ * been initialized. Please see the manual for examples of how to use the annotation (the link
+ * appears below).
+ *
+ * <p>Consider a class {@code B} that is a subtype of {@code A}. At the beginning of the constructor
+ * of {@code B}, {@code this} has the type {@code @UnderInitialization(A.class)}, since all fields
+ * of {@code A} have been initialized by the super-constructor. Inside the constructor body, as soon
+ * as all fields of {@code B} are initialized, then the type of {@code this} changes to
+ * {@code @UnderInitialization(B.class)}.
+ *
+ * <p>Code is allowed to store potentially not-fully-initialized objects in the fields of a
+ * partially-initialized object, as long as all initialization is complete by the end of the
+ * constructor.
+ *
+ * <p>What type qualifiers on the field are considered depends on the checker; for instance, the
+ * {@link org.checkerframework.checker.nullness.NullnessChecker} considers {@link NonNull}. The
+ * initialization type system is not used on its own, but in conjunction with some other type-system
+ * that wants to ensure safe initialization.
+ *
+ * <p>When an expression has type {@code @UnderInitialization}, then no aliases that are typed
+ * differently may exist.
+ *
+ * @checker_framework.manual #initialization-checker Initialization Checker
+ * @checker_framework.manual #underinitialization-examples Examples of the @UnderInitialization
+ *     annotation
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownInitialization.class)
+public @interface UnderInitialization {
+  /**
+   * The type-frame down to which the expression (of this type) has been initialized at least
+   * (inclusive). That is, an expression of type {@code @UnderInitialization(T.class)} has all
+   * type-frames initialized starting at {@code Object} down to (and including) {@code T}.
+   *
+   * @return the type whose fields are fully initialized
+   */
+  Class<?> value() default Object.class;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnknownInitialization.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnknownInitialization.java
new file mode 100644
index 0000000..42015af
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnknownInitialization.java
@@ -0,0 +1,53 @@
+package org.checkerframework.checker.initialization.qual;
+
+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;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * This type qualifier indicates how much of an object has been fully initialized. An object is
+ * fully initialized when each of its fields contains a value that satisfies the field's
+ * declaration.
+ *
+ * <p>An expression of type {@code @UnknownInitialization(T.class)} refers to an object that has all
+ * fields of {@code T} (and any super-classes) initialized. Just {@code @UnknownInitialization} is
+ * equivalent to {@code @UnknownInitialization(Object.class)}. Please see the manual for examples of
+ * how to use the annotation (the link appears below).
+ *
+ * <p>Reading a field of an object of type {@code @UnknownInitialization} might yield a value that
+ * does not correspond to the declared type qualifier for that field. For instance, consider a
+ * non-null field:
+ *
+ * <pre>@NonNull Object f;</pre>
+ *
+ * In a partially-initialized object, field {@code f} might be {@code null} despite its
+ * {@literal @}{@link NonNull} type annotation.
+ *
+ * <p>What type qualifiers on the field are considered depends on the checker; for instance, the
+ * {@link org.checkerframework.checker.nullness.NullnessChecker} considers {@link NonNull}. The
+ * initialization type system is not used on its own, but in conjunction with some other type-system
+ * that wants to ensure safe initialization.
+ *
+ * @checker_framework.manual #initialization-checker Initialization Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultFor({TypeUseLocation.LOCAL_VARIABLE, TypeUseLocation.RESOURCE_VARIABLE})
+public @interface UnknownInitialization {
+  /**
+   * The type-frame down to which the expression (of this type) has been initialized at least
+   * (inclusive). That is, an expression of type {@code @UnknownInitialization(T.class)} has all
+   * type-frames initialized starting at {@code Object} down to (and including) {@code T}.
+   *
+   * @return the type whose fields are fully initialized
+   */
+  Class<?> value() default Object.class;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java
new file mode 100644
index 0000000..8fc5212
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.interning.qual;
+
+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;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+
+/**
+ * Method declaration annotation that indicates a method has a specification like {@code
+ * compareTo()} or {@code compare()}. The Interning Checker permits use of {@code if (this == arg) {
+ * return 0; }} or {@code if (arg1 == arg2) { return 0; }} within the body.
+ *
+ * @checker_framework.manual #interning-checker Interning Checker
+ */
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@InheritedAnnotation
+public @interface CompareToMethod {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java
new file mode 100644
index 0000000..727024e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.interning.qual;
+
+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;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+
+/**
+ * Method declaration annotation that indicates a method has a specification like {@code equals()}.
+ * The Interning Checker permits use of {@code this == arg} within the body. Can also be applied to
+ * a static two-argument method, in which case {@code arg1 == arg2} is permitted within the body.
+ *
+ * @checker_framework.manual #interning-checker Interning Checker
+ */
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@InheritedAnnotation
+public @interface EqualsMethod {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/FindDistinct.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/FindDistinct.java
new file mode 100644
index 0000000..adda1fa
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/FindDistinct.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.interning.qual;
+
+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;
+
+/**
+ * This formal parameter annotation indicates that the method searches for the given value, using
+ * reference equality ({@code ==}).
+ *
+ * <p>Within the method, the formal parameter should be compared with {@code ==} rather than with
+ * {@code equals()}. However, any value may be passed to the method, and the Interning Checker does
+ * not verify that use of {@code ==} within the method is logically correct.
+ *
+ * @see org.checkerframework.checker.interning.InterningChecker
+ * @checker_framework.manual #interning-checker Interning Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface FindDistinct {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java
new file mode 100644
index 0000000..9157a72
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.interning.qual;
+
+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;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+
+/**
+ * Method declaration annotation used to indicate that this method may be invoked on an uninterned
+ * object and that it returns an interned object.
+ *
+ * @checker_framework.manual #interning-checker Interning Checker
+ */
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@InheritedAnnotation
+public @interface InternMethod {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/Interned.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/Interned.java
new file mode 100644
index 0000000..876bd79
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/Interned.java
@@ -0,0 +1,46 @@
+package org.checkerframework.checker.interning.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeKind;
+
+/**
+ * Indicates that a variable has been interned, i.e., that the variable refers to the canonical
+ * representation of an object.
+ *
+ * <p>To specify that all objects of a given type are interned, annotate the class declaration:
+ *
+ * <pre>
+ *   public @Interned class MyInternedClass { ... }
+ * </pre>
+ *
+ * This is equivalent to annotating every use of MyInternedClass, in a declaration or elsewhere. For
+ * example, enum classes are implicitly so annotated.
+ *
+ * @see org.checkerframework.checker.interning.InterningChecker
+ * @checker_framework.manual #interning-checker Interning Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownInterned.class)
+@QualifierForLiterals({LiteralKind.PRIMITIVE, LiteralKind.STRING}) // everything but NULL
+@DefaultFor(
+    typeKinds = {
+      TypeKind.BOOLEAN,
+      TypeKind.BYTE,
+      TypeKind.CHAR,
+      TypeKind.DOUBLE,
+      TypeKind.FLOAT,
+      TypeKind.INT,
+      TypeKind.LONG,
+      TypeKind.SHORT
+    })
+public @interface Interned {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternedDistinct.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternedDistinct.java
new file mode 100644
index 0000000..c1e7fb5
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternedDistinct.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.interning.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * Indicates that no other value is {@code equals()} to the given value. Therefore, it is correct to
+ * use == to test an InternedDistinct value for equality against any other value.
+ *
+ * <p>This is a stronger property than {@link Interned}, but a weaker property than every value of a
+ * Java type being interned.
+ *
+ * <p>This annotation is trusted, not verified.
+ *
+ * @see org.checkerframework.checker.interning.InterningChecker
+ * @checker_framework.manual #interning-checker Interning Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Interned.class)
+@DefaultFor(value = {TypeUseLocation.LOWER_BOUND})
+public @interface InternedDistinct {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/PolyInterned.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/PolyInterned.java
new file mode 100644
index 0000000..fb657e4
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/PolyInterned.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.interning.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the Interning type system.
+ *
+ * <p>Any method written using @PolyInterned conceptually has two versions: one in which every
+ * instance of @PolyInterned has been replaced by @Interned, and one in which every instance
+ * of @PolyInterned has been erased.
+ *
+ * @checker_framework.manual #interning-checker Interning Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(UnknownInterned.class)
+public @interface PolyInterned {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UnknownInterned.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UnknownInterned.java
new file mode 100644
index 0000000..c1a8b79
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UnknownInterned.java
@@ -0,0 +1,25 @@
+package org.checkerframework.checker.interning.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The top qualifier for the Interning Checker. It indicates lack of knowledge about whether values
+ * are interned or not. It is not written by programmers, but is used internally by the type system.
+ *
+ * @see org.checkerframework.checker.interning.InterningChecker
+ * @checker_framework.manual #interning-checker Interning Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@InvisibleQualifier
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface UnknownInterned {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UsesObjectEquals.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UsesObjectEquals.java
new file mode 100644
index 0000000..003e3cd
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UsesObjectEquals.java
@@ -0,0 +1,25 @@
+package org.checkerframework.checker.interning.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Class declaration to indicate the class does not override {@code equals(Object)}, and therefore
+ * {@code a.equals(b)} and {@code a == b} behave identically.
+ *
+ * <p>A class may be annotated @UsesObjectEquals if neither it, nor any of its supertypes or
+ * subtypes, overrides {@code equals}. Therefore, it cannot be written on {@code Object} itself. It
+ * is most commonly written on a direct subclass of {@code Object}.
+ *
+ * @see org.checkerframework.checker.interning.InterningChecker
+ * @checker_framework.manual #interning-checker Interning Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface UsesObjectEquals {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeld.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeld.java
new file mode 100644
index 0000000..262a9ee
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeld.java
@@ -0,0 +1,56 @@
+package org.checkerframework.checker.lock.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+import org.checkerframework.framework.qual.PostconditionAnnotation;
+
+/**
+ * Indicates that the given expressions are held if the method terminates successfully.
+ *
+ * @see EnsuresLockHeldIf
+ * @checker_framework.manual #lock-checker Lock Checker
+ * @checker_framework.manual #ensureslockheld-examples Example use of @EnsuresLockHeld
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@PostconditionAnnotation(qualifier = LockHeld.class)
+@InheritedAnnotation
+@Repeatable(EnsuresLockHeld.List.class)
+public @interface EnsuresLockHeld {
+  /**
+   * Returns Java expressions whose values are locks that are held after successful method
+   * termination.
+   *
+   * @return Java expressions whose values are locks that are held after successful method
+   *     termination
+   * @see <a href="https://checkerframework.org/manual/#java-expressions-as-arguments">Syntax of
+   *     Java expressions</a>
+   */
+  String[] value();
+
+  /**
+   * A wrapper annotation that makes the {@link EnsuresLockHeld} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresLockHeld} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @PostconditionAnnotation(qualifier = LockHeld.class)
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Return the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresLockHeld[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeldIf.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeldIf.java
new file mode 100644
index 0000000..b9f9c20
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeldIf.java
@@ -0,0 +1,67 @@
+package org.checkerframework.checker.lock.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+
+/**
+ * Indicates that the given expressions are held if the method terminates successfully and returns
+ * the given result (either true or false).
+ *
+ * @see EnsuresLockHeld
+ * @checker_framework.manual #lock-checker Lock Checker
+ * @checker_framework.manual #ensureslockheld-examples Example use of @EnsuresLockHeldIf
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@ConditionalPostconditionAnnotation(qualifier = LockHeld.class)
+@InheritedAnnotation
+@Repeatable(EnsuresLockHeldIf.List.class)
+public @interface EnsuresLockHeldIf {
+  /**
+   * Returns Java expressions whose values are locks that are held after the method returns the
+   * given result.
+   *
+   * @return Java expressions whose values are locks that are held after the method returns the
+   *     given result
+   * @see <a href="https://checkerframework.org/manual/#java-expressions-as-arguments">Syntax of
+   *     Java expressions</a>
+   */
+  // It would be clearer for users if this field were named "lock".
+  // However, method ContractsFromMethod.getConditionalPostconditions in the CF implementation
+  // assumes that conditional postconditions have a field named "expression".
+  String[] expression();
+
+  /**
+   * Returns the return value of the method under which the postconditions hold.
+   *
+   * @return the return value of the method under which the postconditions hold
+   */
+  boolean result();
+
+  /**
+   * A wrapper annotation that makes the {@link EnsuresLockHeldIf} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresLockHeldIf} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @ConditionalPostconditionAnnotation(qualifier = LockHeld.class)
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Return the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresLockHeldIf[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardSatisfied.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardSatisfied.java
new file mode 100644
index 0000000..711ae16
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardSatisfied.java
@@ -0,0 +1,43 @@
+package org.checkerframework.checker.lock.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * If a variable {@code x} has type {@code @GuardSatisfied}, then all lock expressions for {@code
+ * x}'s value are held.
+ *
+ * <p>Written on a formal parameter (including the receiver), this annotation indicates that the
+ * {@literal @}{@link GuardedBy} type for the corresponding actual argument at the method call site
+ * is unknown at the method definition site, but any lock expressions that guard it are known to be
+ * held prior to the method call.
+ *
+ * <p>For example, the formal parameter of the String copy constructor, {@link String#String(String
+ * s)}, is annotated with {@code @GuardSatisfied}. This requires that all locks guarding the actual
+ * argument are held when the constructor is called. However, the definition of the constructor does
+ * not need to know what those locks are (and it cannot know, because the constructor can be called
+ * by arbitrary code).
+ *
+ * @see GuardedBy
+ * @see Holding
+ * @checker_framework.manual #lock-checker Lock Checker
+ * @checker_framework.manual #lock-checker-polymorphism-example Lock Checker polymorphism example
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE_USE)
+@TargetLocations({TypeUseLocation.RECEIVER, TypeUseLocation.PARAMETER, TypeUseLocation.RETURN})
+@SubtypeOf(GuardedByUnknown.class) // TODO: Should @GuardSatisfied be in its own hierarchy?
+public @interface GuardSatisfied {
+  /**
+   * The index on the GuardSatisfied polymorphic qualifier. Defaults to -1 so that the user can
+   * write any index starting from 0.
+   */
+  int value() default -1;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java
new file mode 100644
index 0000000..e5bc9c9
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java
@@ -0,0 +1,78 @@
+package org.checkerframework.checker.lock.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeKind;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.qual.UpperBoundFor;
+
+/**
+ * Indicates that a thread may dereference the value referred to by the annotated variable only if
+ * the thread holds all the given lock expressions.
+ *
+ * <p>{@code @GuardedBy({})} is the default type qualifier.
+ *
+ * <p>The argument is a string or set of strings that indicates the expression(s) that must be held,
+ * using the <a href="https://checkerframework.org/manual/#java-expressions-as-arguments">syntax of
+ * Java expressions</a> described in the manual. The expressions evaluate to an intrinsic (built-in,
+ * synchronization) monitor or an explicit {@link java.util.concurrent.locks.Lock}. The expression
+ * {@code "<self>"} is also permitted; the type {@code @GuardedBy("<self>") Object o} indicates that
+ * the value referenced by {@code o} is guarded by the intrinsic (monitor) lock of the value
+ * referenced by {@code o}.
+ *
+ * <p>Two {@code @GuardedBy} annotations with different argument expressions are unrelated by
+ * subtyping.
+ *
+ * @see Holding
+ * @checker_framework.manual #lock-checker Lock Checker
+ * @checker_framework.manual #lock-examples-guardedby Example use of @GuardedBy
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(GuardedByUnknown.class)
+@DefaultQualifierInHierarchy
+// These are required because the default for local variables is @GuardedByUnknown, but if the local
+// variable is one of these type kinds, the default should be @GuardedByUnknown.
+@DefaultFor(
+    value = {TypeUseLocation.EXCEPTION_PARAMETER, TypeUseLocation.UPPER_BOUND},
+    typeKinds = {
+      TypeKind.BOOLEAN,
+      TypeKind.BYTE,
+      TypeKind.CHAR,
+      TypeKind.DOUBLE,
+      TypeKind.FLOAT,
+      TypeKind.INT,
+      TypeKind.LONG,
+      TypeKind.SHORT
+    },
+    types = {String.class, Void.class})
+@UpperBoundFor(
+    typeKinds = {
+      TypeKind.BOOLEAN,
+      TypeKind.BYTE,
+      TypeKind.CHAR,
+      TypeKind.DOUBLE,
+      TypeKind.FLOAT,
+      TypeKind.INT,
+      TypeKind.LONG,
+      TypeKind.SHORT
+    },
+    types = String.class)
+public @interface GuardedBy {
+  /**
+   * The Java value expressions that need to be held.
+   *
+   * @see <a href="https://checkerframework.org/manual/#java-expressions-as-arguments">Syntax of
+   *     Java expressions</a>
+   */
+  @JavaExpression
+  String[] value() default {};
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByBottom.java
new file mode 100644
index 0000000..91a66e3
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByBottom.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.lock.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the GuardedBy type system. Programmers should rarely write this type.
+ *
+ * <p>If a variable {@code x} has type {@code @GuardedByBottom}, then the value referred to by
+ * {@code x} is {@code null} (or dead code) and can never be dereferenced.
+ *
+ * @checker_framework.manual #lock-checker Lock Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf({GuardedBy.class, GuardSatisfied.class})
+public @interface GuardedByBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByUnknown.java
new file mode 100644
index 0000000..08def36
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByUnknown.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.lock.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * It is unknown what locks guard the value referred to by the annotated variable. Therefore, the
+ * value can never be dereferenced. The locks that guard it might not even be in scope (might be
+ * inaccessible) at the location where the {@code @GuardedByUnknown} annotation is written.
+ *
+ * <p>{@code @GuardedByUnknown} is the top of the GuardedBy qualifier hierarchy. Any value can be
+ * assigned into a variable of type {@code @GuardedByUnknown}.
+ *
+ * @checker_framework.manual #lock-checker Lock Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+public @interface GuardedByUnknown {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/Holding.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/Holding.java
new file mode 100644
index 0000000..3a862ae
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/Holding.java
@@ -0,0 +1,35 @@
+package org.checkerframework.checker.lock.qual;
+
+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;
+import org.checkerframework.framework.qual.PreconditionAnnotation;
+
+/**
+ * Indicates a method precondition: the specified expressions must be held when the annotated method
+ * is invoked.
+ *
+ * <p>The argument is a string or set of strings that indicates the expression(s) that must be held,
+ * using the <a href="https://checkerframework.org/manual/#java-expressions-as-arguments">syntax of
+ * Java expressions</a> described in the manual. The expressions evaluate to an intrinsic (built-in,
+ * synchronization) monitor, or an explicit {@link java.util.concurrent.locks.Lock}.
+ *
+ * @see GuardedBy
+ * @checker_framework.manual #lock-checker Lock Checker
+ * @checker_framework.manual #lock-examples-holding Example use of @Holding
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@PreconditionAnnotation(qualifier = LockHeld.class)
+public @interface Holding {
+  /**
+   * The Java expressions that need to be held.
+   *
+   * @see <a href="https://checkerframework.org/manual/#java-expressions-as-arguments">Syntax of
+   *     Java expressions</a>
+   */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockHeld.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockHeld.java
new file mode 100644
index 0000000..d77d8c0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockHeld.java
@@ -0,0 +1,25 @@
+package org.checkerframework.checker.lock.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Indicates that an expression is used as a lock and the lock is known to be held on the current
+ * thread.
+ *
+ * <p>This annotation may not be written in source code; it is an implementation detail of the
+ * checker.
+ *
+ * @see LockPossiblyHeld
+ * @checker_framework.manual #lock-checker Lock Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({})
+@SubtypeOf(LockPossiblyHeld.class) // This is the bottom type in this hierarchy
+@InvisibleQualifier
+public @interface LockHeld {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockPossiblyHeld.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockPossiblyHeld.java
new file mode 100644
index 0000000..f9216d7
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockPossiblyHeld.java
@@ -0,0 +1,32 @@
+package org.checkerframework.checker.lock.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * Indicates that an expression is not known to be {@link LockHeld}.
+ *
+ * <p>This annotation may not be written in source code; it is an implementation detail of the
+ * checker.
+ *
+ * @see LockHeld
+ * @checker_framework.manual #lock-checker Lock Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({})
+@InvisibleQualifier
+@SubtypeOf({}) // The top type in the hierarchy
+@DefaultQualifierInHierarchy
+@DefaultFor(value = TypeUseLocation.LOWER_BOUND, types = Void.class)
+@QualifierForLiterals(LiteralKind.NULL)
+public @interface LockPossiblyHeld {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockingFree.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockingFree.java
new file mode 100644
index 0000000..e9737ea
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockingFree.java
@@ -0,0 +1,32 @@
+package org.checkerframework.checker.lock.qual;
+
+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;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+
+/**
+ * The method neither acquires nor releases locks, nor do any of the methods that it calls. More
+ * specifically, the method is not {@code synchronized}, it contains no {@code synchronized} blocks,
+ * it contains no calls to {@code lock} or {@code unlock}, and it contains no calls to other
+ * non-{@code @LockingFree} methods.
+ *
+ * <p>{@code @LockingFree} provides a stronger guarantee than {@code @}{@link ReleasesNoLocks} and a
+ * weaker guarantee than {@code @}{@link SideEffectFree}.
+ *
+ * @see MayReleaseLocks
+ * @see ReleasesNoLocks
+ * @see SideEffectFree
+ * @see Pure
+ * @checker_framework.manual #lock-checker Lock Checker
+ * @checker_framework.manual #lock-lockingfree-example Example use of @LockingFree
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@InheritedAnnotation
+public @interface LockingFree {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/MayReleaseLocks.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/MayReleaseLocks.java
new file mode 100644
index 0000000..80742fa
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/MayReleaseLocks.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.lock.qual;
+
+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;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+
+/**
+ * The method, or one of the methods it calls, might release locks that were held prior to the
+ * method being called. You can write this when you are certain the method releases locks, or when
+ * you don't know whether the method releases locks.
+ *
+ * @see ReleasesNoLocks
+ * @see LockingFree
+ * @see org.checkerframework.dataflow.qual.SideEffectFree
+ * @see org.checkerframework.dataflow.qual.Pure
+ * @checker_framework.manual #lock-checker Lock Checker
+ * @checker_framework.manual #lock-lockingfree-example Example use of @MayReleaseLocks
+ * @checker_framework.manual #annotating-libraries Annotating libraries
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@InheritedAnnotation
+public @interface MayReleaseLocks {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/PolyGuardedBy.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/PolyGuardedBy.java
new file mode 100644
index 0000000..a0ff940
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/PolyGuardedBy.java
@@ -0,0 +1,37 @@
+/*
+TODO: Implement the functionality for @PolyGuardedBy and uncomment this.
+
+package org.checkerframework.checker.lock.qual;
+
+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;
+
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the GuardedBy type system.
+ * Indicates that it is unknown what the guards are or whether they are held.
+ * An expression whose type is {@code @PolyGuardedBy} cannot be dereferenced.
+ * Hence, unlike for {@code @GuardSatisfied}, when an expression of type {@code @PolyGuardedBy}
+ * is the LHS of an assignment, the locks guarding the RHS do not need to be held.
+ *
+ * <p>Any method written using {@code @PolyGuardedBy} conceptually has an
+ * arbitrary number of versions:  one in which every instance of
+ * {@code @PolyGuardedBy} has been replaced by {@code @}{@link GuardedByUnknown},
+ * one in which every instance of {@code @PolyGuardedBy} has been
+ * replaced by {@code @}{@link GuardedByBottom}, and ones in which every
+ * instance of {@code @PolyGuardedBy} has been replaced by {@code @}{@link GuardedBy},
+ * for every possible combination of map arguments.
+ *
+ * @see GuardedBy
+ * @checker_framework.manual #lock-checker Lock Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+// @PolymorphicQualifier(GuardedByUnknown.class)
+// @Documented
+// @Retention(RetentionPolicy.RUNTIME)
+// @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+// public @interface PolyGuardedBy {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/ReleasesNoLocks.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/ReleasesNoLocks.java
new file mode 100644
index 0000000..dc43239
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/ReleasesNoLocks.java
@@ -0,0 +1,41 @@
+package org.checkerframework.checker.lock.qual;
+
+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;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+
+/**
+ * The method maintains a strictly nondecreasing lock held count on the current thread for any locks
+ * that were held prior to the method call. The same property must in general be true of all the
+ * methods it calls, which should themselves be annotated as {@code @ReleasesNoLocks} or a stronger
+ * annotation such as {@code @}{@link SideEffectFree}.
+ *
+ * <p>The method might acquire locks but then release them, or might acquire locks but not release
+ * them (in which case it should also be annotated with {@literal @}{@link EnsuresLockHeld} or
+ * {@literal @}{@link EnsuresLockHeldIf}).
+ *
+ * <p>This is the default for methods being type-checked that have no {@code @}{@link LockingFree},
+ * {@code @}{@link MayReleaseLocks}, {@code @}{@link SideEffectFree}, or {@code @}{@link Pure}
+ * annotation.
+ *
+ * <p>{@code @ReleasesNoLocks} provides a guarantee unlike {@code @}{@link MayReleaseLocks}, which
+ * provides no guarantees. However, {@code @ReleasesNoLocks} provides a weaker guarantee than
+ * {@code @}{@link LockingFree}.
+ *
+ * @see MayReleaseLocks
+ * @see LockingFree
+ * @see SideEffectFree
+ * @see Pure
+ * @checker_framework.manual #lock-checker Lock Checker
+ * @checker_framework.manual #lock-lockingfree-example Example use of @ReleasesNoLocks
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@InheritedAnnotation
+public @interface ReleasesNoLocks {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/CreatesObligation.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/CreatesObligation.java
new file mode 100644
index 0000000..e933c20
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/CreatesObligation.java
@@ -0,0 +1,104 @@
+package org.checkerframework.checker.mustcall.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.checker.calledmethods.qual.CalledMethods;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+import org.checkerframework.framework.qual.JavaExpression;
+
+/**
+ * Indicates that the method resets the target's must-call type to its declared type. This
+ * effectively undoes flow-sensitive type refinement. The target is the {@code value}
+ * argument/element. More precisely, the method resets the target's must-call type to the least
+ * upper bound of its current must-call type and its declared must-call type.
+ *
+ * <p>When calling a method annotated as {@code @CreatesObligation("}<em>target</em>{@code ")}, the
+ * expression {@code target} must not have type {@code @}{@link CalledMethods}{@code ({})}. That is,
+ * {@code target}'s CalledMethods type must be non-empty.
+ *
+ * <p>{@code @CreatesObligation("this")} must be written on any method that assigns a non-final,
+ * owning field whose declared type has a must-call obligation.
+ *
+ * <p>This annotation is trusted, not checked. (Because this annotation can only add obligations,
+ * the analysis remains sound.)
+ *
+ * <p>For example, consider the following code, which uses a {@code @CreatesObligation} annotation
+ * to indicate that the {@code reset()} method re-assigns the {@code socket} field:
+ *
+ * <pre>
+ * &#64;MustCall("stop")
+ * class SocketContainer {
+ *     // Note that @MustCall("close") is the default type for java.net.Socket, but it
+ *     // is included on the next line for illustrative purposes. This example would function
+ *     // identically if that qualifier were omitted.
+ *     private @Owning @MustCall("close") Socket socket = ...;
+ *
+ *     &#64;EnsuresCalledMethods(value="this.socket", methods="close")
+ *     public void stop() throws IOException {
+ *       socket.close();
+ *     }
+ *
+ *    &#64;CreatesObligation("this")
+ *    public void reset() {
+ *      if (socket.isClosed()) {
+ *        socket = new Socket(...);
+ *      }
+ *    }
+ * }
+ * </pre>
+ *
+ * A client of {@code SocketContainer} is permitted to call {@code reset()} arbitrarily many times.
+ * Each time it does so, a new {@code Socket} might be created. A {@code SocketContainer}'s
+ * must-call obligation of "stop" is fulfilled only if {@code stop()} is called after the last call
+ * to {@code reset()}. The {@code @CreatesObligation} annotation on {@code reset()}'s declaration
+ * enforces this requirement: at any call to {@code reset()}, all called-methods information about
+ * the receiver is removed from the store of the Must Call Checker and the store of the Called
+ * Methods Checker, so the client has to "start over" as if a fresh {@code SocketContainer} object
+ * had been created.
+ *
+ * <p>When the -AnoAccumulationFrames command-line argument is passed to the checker, this
+ * annotation is ignored and all fields are treated as non-owning.
+ *
+ * @checker_framework.manual #must-call-checker Must Call Checker
+ */
+@Target({ElementType.METHOD})
+@InheritedAnnotation
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(CreatesObligation.List.class)
+public @interface CreatesObligation {
+
+  /**
+   * The target of this annotation is stored in this field. The target must be an expression which
+   * can be refined in the store, such as a local variable or field.
+   *
+   * @return the expression to which must-call obligations are added when the annotated method is
+   *     invoked
+   */
+  @JavaExpression
+  String value() default "this";
+
+  /**
+   * A wrapper annotation that makes the {@link CreatesObligation} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link CreatesObligation} annotation at the same location.
+   *
+   * @checker_framework.manual #must-call-checker Must Call Checker
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD})
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Return the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    CreatesObligation[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/InheritableMustCall.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/InheritableMustCall.java
new file mode 100644
index 0000000..0883979
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/InheritableMustCall.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.mustcall.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is an alias for {@link MustCall} that applies to the type on which it is written
+ * <b>and</b> all of its subtypes. It prevents the need to annotate each subtype with an {@link
+ * MustCall} annotation. This annotation may only be written on a class declaration.
+ *
+ * @checker_framework.manual #must-call-checker Must Call Checker
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface InheritableMustCall {
+  /**
+   * Methods that might need to be called on the expression whose type is annotated.
+   *
+   * @return methods that might need to be called
+   */
+  public String[] value() default {};
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCall.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCall.java
new file mode 100644
index 0000000..e8bf584
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCall.java
@@ -0,0 +1,38 @@
+package org.checkerframework.checker.mustcall.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * An expression of type {@code @MustCall({"m1", "m2"})} may be obligated to call {@code m1()}
+ * and/or {@code m2()} before it is deallocated, but it is not obligated to call any other methods.
+ *
+ * <p>This annotation is enforced by the Object Construction Checker's {@code -AcheckMustCall} mode.
+ * It enforces that the methods {@code m1()} and {@code m2()} are called on the annotated expression
+ * before it is deallocated.
+ *
+ * <p>The subtyping relationship is:
+ *
+ * <pre>{@code @MustCall({"m1"}) <: @MustCall({"m1", "m2"})}</pre>
+ *
+ * @checker_framework.manual #must-call-checker Must Call Checker
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({MustCallUnknown.class})
+@DefaultQualifierInHierarchy
+@DefaultFor({TypeUseLocation.EXCEPTION_PARAMETER})
+public @interface MustCall {
+  /**
+   * Methods that might need to be called on the expression whose type is annotated.
+   *
+   * @return methods that might need to be called
+   */
+  public String[] value() default {};
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallAlias.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallAlias.java
new file mode 100644
index 0000000..ce79ba4
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallAlias.java
@@ -0,0 +1,60 @@
+package org.checkerframework.checker.mustcall.qual;
+
+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;
+
+/**
+ * This polymorphic annotation represents an either-or must-call obligation. This annotation should
+ * always be used in pairs. On a method, it is written on some formal parameter type and on the
+ * method return type. On a constructor, it is written on some formal parameter type and on the
+ * result type. Fulfilling the must-call obligation of one is equivalent to fulfilling the must-call
+ * obligation of the other.
+ *
+ * <p>This annotation is useful for wrapper objects. For example, consider the declaration of {@code
+ * java.net.Socket#getOutputStream}:
+ *
+ * <pre>
+ * &#64;MustCall("close")
+ * class Socket {
+ *   &#64;MustCallAlias OutputStream getOutputStream(&#64;MustCallAlias Socket this) { ... }
+ * }</pre>
+ *
+ * Calling {@code close()} on the returned {@code OutputStream} will close the underlying socket,
+ * but the Socket may also be closed directly, which has the same effect.
+ *
+ * <h3>Verifying {@code @MustCallAlias} annotations</h3>
+ *
+ * Suppose that {@code @MustCallAlias} is written on the type of formal parameter {@code p}.
+ *
+ * <p>For a constructor:
+ *
+ * <ul>
+ *   <li>The constructor must always write p into exactly one field {@code f} of the new object.
+ *   <li>Field {@code f} must be annotated {@code @}{@link Owning}.
+ * </ul>
+ *
+ * For a method:
+ *
+ * <ul>
+ *   <li>All return sites must be calls to other methods or constructors with {@code @MustCallAlias}
+ *       return types, and this method's {@code @MustCallAlias} parameter must be passed in the
+ *       {@code MustCallAlias} position to that method or constructor (i.e., the calls must pass
+ *       {@code @MustCallAlias} parameter through a chain of {@code @MustCallAlias}-annotated
+ *       parameters and returns).
+ * </ul>
+ *
+ * When the -AnoResourceAliases command-line argument is passed to the checker, this annotation is
+ * treated identically to {@link PolyMustCall}.
+ *
+ * @checker_framework.manual #must-call-checker Must Call Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+// In Java 11, this can be:
+// @Target({ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.METHOD})
+@Target({ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE_USE})
+public @interface MustCallAlias {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallUnknown.java
new file mode 100644
index 0000000..727a839
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallUnknown.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.mustcall.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The top qualifier in the Must Call type hierarchy. It represents a type that might have an
+ * obligation to call any set (even an infinite set!) of methods. This type contains every object.
+ * This type should rarely be written by a programmer.
+ *
+ * <p>The Object Construction Checker cannot verify that the property represented by this annotation
+ * is enforced; that is, the Object Construction Checker will always issue a warning when the value
+ * of an expression with this type might be de-allocated.
+ *
+ * @checker_framework.manual #must-call-checker Must Call Checker
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+public @interface MustCallUnknown {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/NotOwning.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/NotOwning.java
new file mode 100644
index 0000000..4090ac8
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/NotOwning.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.mustcall.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation indicating that ownership should not be transferred to the annotated parameter, field,
+ * or (when this is written on a method) return type, for the purposes of Must Call checking.
+ *
+ * <p>Parameters and fields are treated as if they have this annotation by default unless they have
+ * {@link Owning}.
+ *
+ * <p>When the -AnoLightweightOwnership command-line argument is passed to the checker, this
+ * annotation and {@link Owning} are ignored.
+ *
+ * @checker_framework.manual #must-call-checker Must Call Checker
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+public @interface NotOwning {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/Owning.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/Owning.java
new file mode 100644
index 0000000..429a76e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/Owning.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.mustcall.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation indicating that ownership should be transferred to the annotated parameter, field, or
+ * (when written on a method) return type, for the purposes of Must Call checking.
+ *
+ * <p>Method return types are treated as if they have this annotation by default unless their method
+ * is annotated as {@link NotOwning}.
+ *
+ * <p>When the -AnoLightweightOwnership command-line argument is passed to the checker, this
+ * annotation and {@link NotOwning} are ignored.
+ *
+ * @checker_framework.manual #must-call-checker Must Call Checker
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+public @interface Owning {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/PolyMustCall.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/PolyMustCall.java
new file mode 100644
index 0000000..8347477
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/PolyMustCall.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.mustcall.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * The polymorphic qualifier for the Must Call type system.
+ *
+ * @checker_framework.manual #must-call-checker Must Call Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(MustCallUnknown.class)
+public @interface PolyMustCall {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/AssertNonNullIfNonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/AssertNonNullIfNonNull.java
new file mode 100644
index 0000000..68408f1
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/AssertNonNullIfNonNull.java
@@ -0,0 +1,49 @@
+package org.checkerframework.checker.nullness.qual;
+
+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;
+
+/**
+ * Indicates that if the method returns a non-null value, then the value expressions are also
+ * non-null.
+ *
+ * <p><b>WARNING:</b> Type-checking for this annotation is <em>not implemented</em> at present.
+ *
+ * <p>Here is an example use:
+ *
+ * <pre><code>
+ *    {@literal @}AssertNonNullIfNonNull("id")
+ *    {@literal @}Pure
+ *     public @Nullable Long getId() {
+ *         return id;
+ *     }
+ * </code></pre>
+ *
+ * Note the direction of the implication. This annotation says that if the result is non-null, then
+ * the variable {@code id} is also non-null. The annotation does not say that if {@code id} is
+ * non-null, then the result is non-null. (There is not currently a way to say the latter, though it
+ * would also be useful.)
+ *
+ * <p>You should <em>not</em> write a formal parameter name or {@code this} as the argument of this
+ * annotation. In those cases, use the {@link PolyNull} annotation instead.
+ *
+ * @see NonNull
+ * @see PolyNull
+ * @see org.checkerframework.checker.nullness.NullnessChecker
+ * @checker_framework.manual #nullness-checker Nullness Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface AssertNonNullIfNonNull {
+
+  /**
+   * Java expression(s) that are non-null after the method returns a non-null value.
+   *
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyFor.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyFor.java
new file mode 100644
index 0000000..de2c629
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyFor.java
@@ -0,0 +1,77 @@
+package org.checkerframework.checker.nullness.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.PostconditionAnnotation;
+import org.checkerframework.framework.qual.QualifierArgument;
+
+/**
+ * Indicates that the value expressions evaluate to a value that is a key in all the given maps, if
+ * the method terminates successfully.
+ *
+ * <p>Consider the following method from {@code java.util.Map}:
+ *
+ * <pre>
+ * &#64;EnsuresKeyFor(value="key", map="this")
+ * public @Nullable V put(K key, V value) { ... }
+ * </pre>
+ *
+ * <p>This method guarantees that {@code key} has type {@code @KeyFor("this")} after the method
+ * returns.
+ *
+ * @see KeyFor
+ * @see EnsuresKeyForIf
+ * @checker_framework.manual #map-key-checker Map Key Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@PostconditionAnnotation(qualifier = KeyFor.class)
+@InheritedAnnotation
+@Repeatable(EnsuresKeyFor.List.class)
+public @interface EnsuresKeyFor {
+  /**
+   * Java expressions that are keys in the given maps on successful method termination.
+   *
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  String[] value();
+
+  /**
+   * Returns Java expressions whose values are maps, each of which contains each expression value as
+   * a key (after successful method termination).
+   *
+   * @return Java expressions whose values are maps, each of which contains each expression value as
+   *     a key (after successful method termination)
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  @JavaExpression
+  @QualifierArgument("value")
+  String[] map();
+
+  /**
+   * A wrapper annotation that makes the {@link EnsuresKeyFor} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresKeyFor} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @PostconditionAnnotation(qualifier = KeyFor.class)
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Returns the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresKeyFor[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyForIf.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyForIf.java
new file mode 100644
index 0000000..cc60484
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyForIf.java
@@ -0,0 +1,80 @@
+package org.checkerframework.checker.nullness.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.QualifierArgument;
+
+/**
+ * Indicates that the given expressions evaluate to a value that is a key in all the given maps, if
+ * the method returns the given result (either true or false).
+ *
+ * <p>As an example, consider the following method in {@code java.util.Map}:
+ *
+ * <pre>
+ *   &#64;EnsuresKeyForIf(result=true, expression="key", map="this")
+ *   public boolean containsKey(String key) { ... }
+ * </pre>
+ *
+ * If an invocation {@code m.containsKey(k)} returns true, then the type of {@code k} can be
+ * inferred to be {@code @KeyFor("m")}.
+ *
+ * @see KeyFor
+ * @see EnsuresKeyFor
+ * @checker_framework.manual #map-key-checker Map Key Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@ConditionalPostconditionAnnotation(qualifier = KeyFor.class)
+@InheritedAnnotation
+@Repeatable(EnsuresKeyForIf.List.class)
+public @interface EnsuresKeyForIf {
+  /** The value the method must return, in order for the postcondition to hold. */
+  boolean result();
+
+  /**
+   * Java expressions that are keys in the given maps after the method returns the given result.
+   *
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  String[] expression();
+
+  /**
+   * Returns Java expressions whose values are maps, each of which contains each expression value as
+   * a key (after the method returns the given result).
+   *
+   * @return Java expressions whose values are maps, each of which contains each expression value as
+   *     a key (after the method returns the given result)
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  @JavaExpression
+  @QualifierArgument("value")
+  String[] map();
+
+  /**
+   * A wrapper annotation that makes the {@link EnsuresKeyForIf} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresKeyForIf} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @ConditionalPostconditionAnnotation(qualifier = KeyFor.class)
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Returns the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresKeyForIf[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNull.java
new file mode 100644
index 0000000..011a9ff
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNull.java
@@ -0,0 +1,74 @@
+package org.checkerframework.checker.nullness.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+import org.checkerframework.framework.qual.PostconditionAnnotation;
+
+// TODO: In a fix for https://tinyurl.com/cfissue/1917, add the text:  Every prefix expression is
+// also non-null; for example, {@code @EnsuresNonNull(expression="a.b.c")} implies that both {@code
+// a.b} and {@code a.b.c} are non-null.
+/**
+ * Indicates that the value expressions are non-null just after a method call, if the method
+ * terminates successfully.
+ *
+ * <p>This postcondition annotation is useful for methods that initialize a field:
+ *
+ * <pre><code>
+ * {@literal @}EnsuresNonNull("theMap")
+ *  void initialize() {
+ *    theMap = new HashMap&lt;&gt;();
+ *  }
+ * </code></pre>
+ *
+ * It can also be used for a method that fails if a given expression is null:
+ *
+ * <pre><code>
+ *  /** Throws an exception if the argument is null. *&#47;
+ * {@literal @}EnsuresNonNull("#1")
+ *  void assertNonNull(Object arg) { ... }
+ * </code></pre>
+ *
+ * @see NonNull
+ * @see org.checkerframework.checker.nullness.NullnessChecker
+ * @checker_framework.manual #nullness-checker Nullness Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@PostconditionAnnotation(qualifier = NonNull.class)
+@InheritedAnnotation
+@Repeatable(EnsuresNonNull.List.class)
+public @interface EnsuresNonNull {
+  /**
+   * Returns Java expressions that are {@link NonNull} after successful method termination.
+   *
+   * @return Java expressions that are {@link NonNull} after successful method termination
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  String[] value();
+
+  /**
+   * A wrapper annotation that makes the {@link EnsuresNonNull} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresNonNull} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @PostconditionAnnotation(qualifier = NonNull.class)
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Returns the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresNonNull[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNullIf.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNullIf.java
new file mode 100644
index 0000000..2d216cc
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNullIf.java
@@ -0,0 +1,112 @@
+package org.checkerframework.checker.nullness.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+
+// TODO: In a fix for https://tinyurl.com/cfissue/1917, add the text:  Every prefix expression is
+// also non-null; for example, {@code @EnsuresNonNullIf(expression="a.b.c", results=true)} implies
+// that both {@code a.b} and {@code a.b.c} are non-null if the method returns {@code true}.
+/**
+ * Indicates that the given expressions are non-null, if the method returns the given result (either
+ * true or false).
+ *
+ * <p>Here are ways this conditional postcondition annotation can be used:
+ *
+ * <p><b>Method parameters:</b> A common example is that the {@code equals} method is annotated as
+ * follows:
+ *
+ * <pre>{@code   @EnsuresNonNullIf(expression="#1", result=true)
+ *   public boolean equals(@Nullable Object obj) { ... }}</pre>
+ *
+ * because, if {@code equals} returns true, then the first (#1) argument to {@code equals} was not
+ * null.
+ *
+ * <p><b>Fields:</b> The value expressions can refer to fields, even private ones. For example:
+ *
+ * <pre>{@code   @EnsuresNonNullIf(expression="this.derived", result=true)
+ *   public boolean isDerived() {
+ *     return (this.derived != null);
+ *   }}</pre>
+ *
+ * As another example, an {@code Iterator} may cache the next value that will be returned, in which
+ * case its {@code hasNext} method could be annotated as:
+ *
+ * <pre>{@code   @EnsuresNonNullIf(expression="next_cache", result=true)
+ *   public boolean hasNext() {
+ *     if (next_cache == null) {
+ *       return false;
+ *     }
+ *     ...
+ *   }}</pre>
+ *
+ * An {@code EnsuresNonNullIf} annotation that refers to a private field is useful for verifying
+ * that client code performs needed checks in the right order, even if the client code cannot
+ * directly affect the field.
+ *
+ * <p><b>Method calls:</b> If {@link Class#isArray()} returns true, then {@link
+ * Class#getComponentType()} returns non-null. You can express this relationship as:
+ *
+ * <pre>{@code   @EnsuresNonNullIf(expression="getComponentType()", result=true)
+ *   public native @Pure boolean isArray();}</pre>
+ *
+ * You can write two {@code @EnsuresNonNullIf} annotations on a single method:
+ *
+ * <pre><code>
+ * &nbsp;   @EnsuresNonNullIf(expression="outputFile", result=true)
+ * &nbsp;   @EnsuresNonNullIf(expression="memoryOutputStream", result=false)
+ *     public boolean isThresholdExceeded() { ... }
+ * </code></pre>
+ *
+ * @see NonNull
+ * @see EnsuresNonNull
+ * @see org.checkerframework.checker.nullness.NullnessChecker
+ * @checker_framework.manual #nullness-checker Nullness Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@ConditionalPostconditionAnnotation(qualifier = NonNull.class)
+@InheritedAnnotation
+@Repeatable(EnsuresNonNullIf.List.class)
+public @interface EnsuresNonNullIf {
+  /**
+   * Returns Java expression(s) that are non-null after the method returns the given result.
+   *
+   * @return Java expression(s) that are non-null after the method returns the given result
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  String[] expression();
+
+  /**
+   * Returns the return value of the method under which the postcondition holds.
+   *
+   * @return the return value of the method under which the postcondition holds
+   */
+  boolean result();
+
+  /**
+   * * A wrapper annotation that makes the {@link EnsuresNonNullIf} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresNonNullIf} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @ConditionalPostconditionAnnotation(qualifier = NonNull.class)
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Returns the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresNonNullIf[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyFor.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyFor.java
new file mode 100644
index 0000000..396f716
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyFor.java
@@ -0,0 +1,51 @@
+package org.checkerframework.checker.nullness.qual;
+
+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;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Indicates that the value assigned to the annotated variable is a key for at least the given
+ * map(s).
+ *
+ * <p>The value of the annotation is the reference name of the map. Suppose that {@code config} is a
+ * {@code Map<String, String>}. Then the declaration
+ *
+ * <pre>{@code   @KeyFor("config") String key = "HOSTNAME"; }</pre>
+ *
+ * indicates that "HOSTNAME" is a key in {@code config}.
+ *
+ * <p>The value of the annotation can also be a set of reference names of the maps. If {@code
+ * defaultConfig} is also a {@code Map<String, String>}, then
+ *
+ * <pre>{@code   @KeyFor({"config","defaultConfig"}) String key = "HOSTNAME"; }</pre>
+ *
+ * indicates that "HOSTNAME" is a key in {@code config} and in {@code defaultConfig}.
+ *
+ * <p>You do not usually need to write {@code @KeyFor} on the key type in a map. That is, you can
+ * declare variable {@code Map<String, Integer> myMap;} and the Nullness Checker will apply
+ * {@code @KeyFor} as appropriate. If you redundantly write {@code @KeyFor}, as in {@code
+ * Map<@KeyFor("myMap") String, Integer> myMap;}, then your code is more verbose, and more seriously
+ * the Nullness Checker will issue errors when calling methods such as {@code Map.put}.
+ *
+ * @see EnsuresKeyFor
+ * @see EnsuresKeyForIf
+ * @checker_framework.manual #map-key-checker Map Key Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownKeyFor.class)
+public @interface KeyFor {
+  /**
+   * Java expression(s) that evaluate to a map for which the annotated type is a key.
+   *
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  @JavaExpression
+  public String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyForBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyForBottom.java
new file mode 100644
index 0000000..c879956
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyForBottom.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.nullness.qual;
+
+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;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Map Key type system. Programmers should rarely write this type.
+ *
+ * <p>There are no values of this type (not even {@code null}).
+ *
+ * @checker_framework.manual #map-key-checker Map Key Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@InvisibleQualifier
+@SubtypeOf(KeyFor.class)
+public @interface KeyForBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/MonotonicNonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/MonotonicNonNull.java
new file mode 100644
index 0000000..8608c15
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/MonotonicNonNull.java
@@ -0,0 +1,51 @@
+package org.checkerframework.checker.nullness.qual;
+
+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;
+import org.checkerframework.framework.qual.MonotonicQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Indicates that once the field (or variable) becomes non-null, it never becomes null again. There
+ * is no guarantee that the field ever becomes non-null, but if it does, it will stay non-null.
+ *
+ * <p>Example use cases include lazy initialization and framework-based initialization in a
+ * lifecycle method other than the constructor.
+ *
+ * <p>A monotonically non-null field has these two properties:
+ *
+ * <ol>
+ *   <li>The field may be assigned only non-null values.
+ *   <li>The field may be re-assigned as often as desired.
+ * </ol>
+ *
+ * <p>When the field is first read within a method, the field cannot be assumed to be non-null.
+ * After a check that a {@code @MonotonicNonNull} field holds a non-null value, all subsequent
+ * accesses <em>within that method</em> can be assumed to be non-null, even after arbitrary external
+ * method calls that might access the field.
+ *
+ * <p>{@code @MonotonicNonNull} gives stronger guarantees than {@link Nullable}. After a check that
+ * a {@link Nullable} field holds a non-null value, only accesses until the next non-{@link
+ * org.checkerframework.dataflow.qual.SideEffectFree} method is called can be assumed to be
+ * non-null.
+ *
+ * <p>To indicate that a {@code @MonotonicNonNull} or {@code @Nullable} field is non-null whenever a
+ * particular method is called, use {@code @}{@link RequiresNonNull}.
+ *
+ * <p>Final fields are treated as MonotonicNonNull by default.
+ *
+ * @see EnsuresNonNull
+ * @see RequiresNonNull
+ * @see MonotonicQualifier
+ * @see org.checkerframework.checker.nullness.NullnessChecker
+ * @checker_framework.manual #nullness-checker Nullness Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE_USE)
+@SubtypeOf(Nullable.class)
+@MonotonicQualifier(NonNull.class)
+public @interface MonotonicNonNull {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNull.java
new file mode 100644
index 0000000..430d711
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNull.java
@@ -0,0 +1,56 @@
+package org.checkerframework.checker.nullness.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeKind;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.qual.UpperBoundFor;
+
+/**
+ * If an expression's type is qualified by {@code @NonNull}, then the expression never evaluates to
+ * {@code null}. (Unless the program has a bug; annotations specify intended behavior.)
+ *
+ * <p>For fields of a class, the {@link NonNull} annotation indicates that this field is never
+ * {@code null} <em>after the class has been fully initialized</em>. For static fields, the {@link
+ * NonNull} annotation indicates that this field is never {@code null} <em>after the containing
+ * class is initialized</em>. "Fully initialized" essentially means that the Java constructor has
+ * completed. See the <a
+ * href="https://checkerframework.org/manual/#initialization-checker">Initialization Checker
+ * documentation</a> for more details.
+ *
+ * <p>This annotation is rarely written in source code, because it is the default.
+ *
+ * @see Nullable
+ * @see MonotonicNonNull
+ * @checker_framework.manual #nullness-checker Nullness Checker
+ * @checker_framework.manual #initialization-checker Initialization Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(MonotonicNonNull.class)
+@QualifierForLiterals(LiteralKind.STRING)
+@DefaultQualifierInHierarchy
+@DefaultFor(TypeUseLocation.EXCEPTION_PARAMETER)
+@UpperBoundFor(
+    typeKinds = {
+      TypeKind.PACKAGE,
+      TypeKind.INT,
+      TypeKind.BOOLEAN,
+      TypeKind.CHAR,
+      TypeKind.DOUBLE,
+      TypeKind.FLOAT,
+      TypeKind.LONG,
+      TypeKind.SHORT,
+      TypeKind.BYTE
+    })
+public @interface NonNull {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/Nullable.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/Nullable.java
new file mode 100644
index 0000000..3dd6cf5
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/Nullable.java
@@ -0,0 +1,35 @@
+package org.checkerframework.checker.nullness.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * {@link Nullable} is a type annotation that makes no commitments about whether the value is {@code
+ * null}. Equivalently, the type includes the {@code null} value.
+ *
+ * <p>The Nullness Checker issues an error if {@code null} is assigned an an expression of {@link
+ * NonNull} type.
+ *
+ * <p>Programmers typically write {@code @Nullable} to indicate that the value is not known to be
+ * {@link NonNull}. However, since {@code @Nullable} is a supertype of {@code @NonNull}, an
+ * expression that never evaluates to {@code null} can have a declared type of {@code @Nullable}.
+ *
+ * @see NonNull
+ * @see MonotonicNonNull
+ * @see org.checkerframework.checker.nullness.NullnessChecker
+ * @checker_framework.manual #nullness-checker Nullness Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@QualifierForLiterals(LiteralKind.NULL)
+@DefaultFor(types = Void.class)
+public @interface Nullable {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyKeyFor.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyKeyFor.java
new file mode 100644
index 0000000..6a89836
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyKeyFor.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.nullness.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the Map Key (@KeyFor) type system.
+ *
+ * <p>Any method written using {@code @PolyKeyFor} conceptually has an arbitrary number of versions:
+ * one in which every instance of {@code @PolyKeyFor} has been replaced by {@code @}{@link
+ * UnknownKeyFor}, one in which every instance of {@code @}{@link PolyKeyFor} has been replaced by
+ * {@code @}{@link KeyForBottom}, and ones in which every instance of {@code @PolyKeyFor} has been
+ * replaced by {@code @}{@code KeyFor}, for every possible combination of map arguments.
+ *
+ * @checker_framework.manual #nullness-checker Nullness Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(UnknownKeyFor.class)
+public @interface PolyKeyFor {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyNull.java
new file mode 100644
index 0000000..35b10a0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyNull.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.nullness.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the non-null type system.
+ *
+ * <p>Any method written using {@link PolyNull} conceptually has two versions: one in which every
+ * instance of {@link PolyNull} has been replaced by {@link NonNull}, and one in which every
+ * instance of {@link PolyNull} has been replaced by {@link Nullable}.
+ *
+ * @checker_framework.manual #nullness-checker Nullness Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(Nullable.class)
+public @interface PolyNull {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/RequiresNonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/RequiresNonNull.java
new file mode 100644
index 0000000..e1a73e5
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/RequiresNonNull.java
@@ -0,0 +1,69 @@
+package org.checkerframework.checker.nullness.qual;
+
+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;
+import org.checkerframework.framework.qual.PreconditionAnnotation;
+
+// TODO: In a fix for https://tinyurl.com/cfissue/1917, add the text:  Every prefix expression must
+// also be non-null; for example, {@code @RequiresNonNull(expression="a.b.c")} implies that both
+// {@code a.b} and {@code a.b.c} must be non-null.
+/**
+ * Indicates a method precondition: the method expects the specified expressions to be non-null when
+ * the annotated method is invoked.
+ *
+ * <p>For example:
+ * <!-- The "&nbsp;" is to hide the at-signs from Javadoc. -->
+ *
+ * <pre>
+ * class MyClass {
+ * &nbsp; @Nullable Object field1;
+ * &nbsp; @Nullable Object field2;
+ *
+ * &nbsp; @RequiresNonNull({"field1", "#1.field1"})
+ *   void method1(@NonNull MyClass other) {
+ *     field1.toString();           // OK, this.field1 is known to be non-null
+ *     field2.toString();           // error, might throw NullPointerException
+ *     other.field1.toString();     // OK, other.field1 is known to be non-null
+ *     other.field2.toString();     // error, might throw NullPointerException
+ *   }
+ *
+ *   void method2() {
+ *     MyClass other = new MyClass();
+ *
+ *     field1 = new Object();
+ *     other.field1 = new Object();
+ *     method1(other);                   // OK, satisfies method precondition
+ *
+ *     field1 = null;
+ *     other.field1 = new Object();
+ *     method1(other);                   // error, does not satisfy this.field1 method precondition
+ *
+ *     field1 = new Object();
+ *     other.field1 = null;
+ *     method1(other);                   // error, does not satisfy other.field1 method precondition
+ *   }
+ * }
+ * </pre>
+ *
+ * Do not use this annotation for formal parameters (instead, give them a {@code @NonNull} type,
+ * which is the default and need not be written). The {@code @RequiresNonNull} annotation is
+ * intended for other expressions, such as field accesses or method calls.
+ *
+ * @checker_framework.manual #nullness-checker Nullness Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@PreconditionAnnotation(qualifier = NonNull.class)
+public @interface RequiresNonNull {
+  /**
+   * The Java expressions that need to be {@link
+   * org.checkerframework.checker.nullness.qual.NonNull}.
+   *
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java
new file mode 100644
index 0000000..19e423c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java
@@ -0,0 +1,33 @@
+package org.checkerframework.checker.nullness.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * Used internally by the type system; should never be written by a programmer.
+ *
+ * <p>Indicates that the value assigned to the annotated variable is not known to be a key for any
+ * map. It is the top type qualifier in the {@link KeyFor} hierarchy. It is also the default type
+ * qualifier.
+ *
+ * @checker_framework.manual #map-key-checker Map Key Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@InvisibleQualifier
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+@DefaultFor(value = TypeUseLocation.LOWER_BOUND, types = Void.class)
+@QualifierForLiterals(LiteralKind.NULL)
+public @interface UnknownKeyFor {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/MaybePresent.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/MaybePresent.java
new file mode 100644
index 0000000..b37ac12
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/MaybePresent.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.optional.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The {@link java.util.Optional Optional} container may or may not contain a value.
+ *
+ * @checker_framework.manual #optional-checker Optional Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface MaybePresent {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalBottom.java
new file mode 100644
index 0000000..755f071
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalBottom.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.optional.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The bottom type qualifier for the Optional Checker. The only value of this type is {@code null}.
+ * Programmers rarely write this annotation.
+ *
+ * @checker_framework.manual #optional-checker Optional Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({Present.class})
+public @interface OptionalBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/PolyPresent.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/PolyPresent.java
new file mode 100644
index 0000000..e462ed7
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/PolyPresent.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.optional.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the Optional type system.
+ *
+ * @checker_framework.manual #optional-checker Optional Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(MaybePresent.class)
+public @interface PolyPresent {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/Present.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/Present.java
new file mode 100644
index 0000000..90c59e2
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/Present.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.optional.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The {@link java.util.Optional Optional} container definitely contains a (non-null) value.
+ *
+ * @checker_framework.manual #optional-checker Optional Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({MaybePresent.class})
+public @interface Present {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKey.java b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKey.java
new file mode 100644
index 0000000..712fc8f
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKey.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.propkey.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Indicates that the {@code String} type can be used as key in a property file or resource bundle.
+ *
+ * @checker_framework.manual #propkey-checker Property File Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownPropertyKey.class)
+public @interface PropertyKey {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKeyBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKeyBottom.java
new file mode 100644
index 0000000..e3b40a9
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKeyBottom.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.propkey.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the PropertyKeyChecker (and associated checkers) qualifier hierarchy.
+ * Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #propkey-checker Property File Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf(PropertyKey.class)
+@DefaultFor(TypeUseLocation.LOWER_BOUND)
+public @interface PropertyKeyBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/UnknownPropertyKey.java b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/UnknownPropertyKey.java
new file mode 100644
index 0000000..89a55a0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/UnknownPropertyKey.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.propkey.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Indicates that the {@code String} type has an unknown property key property.
+ *
+ * @checker_framework.manual #propkey-checker Property File Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface UnknownPropertyKey {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PartialRegex.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PartialRegex.java
new file mode 100644
index 0000000..2519b67
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PartialRegex.java
@@ -0,0 +1,32 @@
+package org.checkerframework.checker.regex.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Indicates a String that is not a syntactically valid regular expression. The String itself can be
+ * stored as a parameter to the annotation, allowing the Regex Checker to verify some concatenations
+ * of partial regular expression Strings.
+ *
+ * <p>This annotation may not be written in source code; it is an implementation detail of the Regex
+ * Checker.
+ *
+ * @checker_framework.manual #regex-checker Regex Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({}) // empty target prevents programmers from writing this in a program
+@InvisibleQualifier
+@SubtypeOf(org.checkerframework.checker.regex.qual.UnknownRegex.class)
+public @interface PartialRegex {
+
+  /**
+   * The String qualified by this annotation. Used to verify concatenation of partial regular
+   * expressions. Defaults to the empty String.
+   */
+  String value() default "";
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PolyRegex.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PolyRegex.java
new file mode 100644
index 0000000..ea81c50
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PolyRegex.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.regex.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the Regex type system.
+ *
+ * <p>Any method written using {@link PolyRegex} conceptually has two versions: one in which every
+ * instance of {@link PolyRegex} has been replaced by {@link Regex}, and one in which every instance
+ * of {@link PolyRegex} has been replaced by {@link UnknownRegex}.
+ *
+ * @checker_framework.manual #regex-checker Regex Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(UnknownRegex.class)
+public @interface PolyRegex {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/Regex.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/Regex.java
new file mode 100644
index 0000000..9f7e674
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/Regex.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.regex.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * If a type is annotated as {@code @Regex(n)}, then the run-time value is a regular expression with
+ * <em>n</em> capturing groups.
+ *
+ * <p>For example, if an expression's type is <em>@Regex(2) String</em>, then at run time its value
+ * will be a legal regular expression with at least two capturing groups. The type states that
+ * possible run-time values include {@code "(a*)(b*)"}, {@code "a(b?)c(d?)e"}, and {@code
+ * "(.)(.)(.)"}, but not {@code "hello"} nor {@code "(good)bye"} nor {@code "(a*)(b*)("}.
+ *
+ * @checker_framework.manual #regex-checker Regex Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownRegex.class)
+public @interface Regex {
+  /** The number of groups in the regular expression. Defaults to 0. */
+  int value() default 0;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/RegexBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/RegexBottom.java
new file mode 100644
index 0000000..e0f226b
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/RegexBottom.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.regex.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Regex type system. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #regex-checker Regex Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@InvisibleQualifier
+@SubtypeOf({Regex.class, org.checkerframework.checker.regex.qual.PartialRegex.class})
+@DefaultFor(value = {TypeUseLocation.LOWER_BOUND})
+public @interface RegexBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/UnknownRegex.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/UnknownRegex.java
new file mode 100644
index 0000000..ea1948b
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/UnknownRegex.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.regex.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * Represents the top of the Regex qualifier hierarchy.
+ *
+ * @checker_framework.manual #regex-checker Regex Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@InvisibleQualifier
+@DefaultQualifierInHierarchy
+@SubtypeOf({})
+public @interface UnknownRegex {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ArrayWithoutPackage.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ArrayWithoutPackage.java
new file mode 100644
index 0000000..880dcc4
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ArrayWithoutPackage.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An identifier or primitive type, followed by any number of array square brackets.
+ *
+ * <p>Example: Foobar[][] Example: Baz22
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({FullyQualifiedName.class, ClassGetSimpleName.class})
+public @interface ArrayWithoutPackage {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryName.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryName.java
new file mode 100644
index 0000000..c1a8acb
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryName.java
@@ -0,0 +1,39 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Represents a binary name as defined in the <a
+ * href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1">Java Language
+ * Specification, section 13.1</a>.
+ *
+ * <p>For example, in
+ *
+ * <pre>
+ *  package org.checkerframework.checker.signature;
+ *  public class SignatureChecker {
+ *    private class Inner {}
+ *  }
+ * </pre>
+ *
+ * the binary names for the two types are org.checkerframework.checker.signature.SignatureChecker
+ * and org.checkerframework.checker.signature.SignatureChecker$Inner.
+ *
+ * <p>Binary names and {@linkplain InternalForm internal form} only differ by the use of '.' vs. '/'
+ * as package separator.
+ *
+ * <p>The binary name should not be confused with the {@linkplain InternalForm internal form}, which
+ * is a variant of the binary name that actually appears in the class file.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({BinaryNameOrPrimitiveType.class})
+public @interface BinaryName {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryNameOrPrimitiveType.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryNameOrPrimitiveType.java
new file mode 100644
index 0000000..c2d36f7
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryNameOrPrimitiveType.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Represents a primitive type name or a {@link BinaryName binary name}.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({ClassGetName.class, FqBinaryName.class})
+public @interface BinaryNameOrPrimitiveType {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryNameWithoutPackage.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryNameWithoutPackage.java
new file mode 100644
index 0000000..7a28a75
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryNameWithoutPackage.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Represents a string that is a {@link BinaryName}, an {@link InternalForm}, and a {@link
+ * ClassGetName}. The string represents a class that is in the unnamed package (also known as the
+ * default package).
+ *
+ * <p>Examples:
+ *
+ * <pre>{@code
+ * MyClass
+ * MyClass$22
+ * }</pre>
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({BinaryName.class, InternalForm.class})
+public @interface BinaryNameWithoutPackage {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/CanonicalName.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/CanonicalName.java
new file mode 100644
index 0000000..4ea4b01
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/CanonicalName.java
@@ -0,0 +1,49 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Canonical names have the same syntactic form as {@link FullyQualifiedName fully-qualified name}s.
+ * Every canonical name is a fully-qualified name, but not every fully-qualified name is a canonical
+ * name.
+ *
+ * <p><a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.7">JLS section
+ * 6.7</a> gives the following example:
+ *
+ * <blockquote>
+ *
+ * The difference between a fully qualified name and a canonical name can be seen in code such as:
+ *
+ * <pre>{@code
+ * package p;
+ * class O1 { class I {} }
+ * class O2 extends O1 {}
+ * }</pre>
+ *
+ * Both {@code p.O1.I} and {@code p.O2.I} are fully qualified names that denote the member class
+ * {@code I}, but only {@code p.O1.I} is its canonical name.
+ *
+ * </blockquote>
+ *
+ * Given a character sequence that is a fully-qualified name, there is no way to know whether or not
+ * it is a canonical name, without examining the program it refers to. Type-checking determines that
+ * a string is a {@code CanonicalName} based on provenance (what method produced the string), rather
+ * than the contents of the string.
+ *
+ * @see FullyQualifiedName
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({
+  FullyQualifiedName.class,
+  CanonicalNameOrEmpty.class,
+  CanonicalNameOrPrimitiveType.class
+})
+public @interface CanonicalName {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/CanonicalNameAndBinaryName.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/CanonicalNameAndBinaryName.java
new file mode 100644
index 0000000..c096a3f
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/CanonicalNameAndBinaryName.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This is a string that is a valid {@linkplain
+ * org.checkerframework.checker.signature.qual.CanonicalName canonical name} and a valid {@linkplain
+ * org.checkerframework.checker.signature.qual.BinaryName binary name}. It represents a non-array,
+ * non-inner, non-primitive class.
+ *
+ * <p>Examples: int, MyClass, java.lang, java.lang.Integer
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({CanonicalName.class, DotSeparatedIdentifiers.class})
+public @interface CanonicalNameAndBinaryName {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/CanonicalNameOrEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/CanonicalNameOrEmpty.java
new file mode 100644
index 0000000..bf347b1
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/CanonicalNameOrEmpty.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Either a {@link CanonicalName} or the empty string.
+ *
+ * @see CanonicalName
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SignatureUnknown.class)
+public @interface CanonicalNameOrEmpty {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/CanonicalNameOrPrimitiveType.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/CanonicalNameOrPrimitiveType.java
new file mode 100644
index 0000000..d56e5bd
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/CanonicalNameOrPrimitiveType.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This is a string that is a valid {@linkplain CanonicalName canonical name} and a valid
+ * {@linkplain BinaryNameOrPrimitiveType binary name or primitive type}. It represents a primitive
+ * type or a non-array, non-inner class, where the latter is represented by dot-separated
+ * identifiers.
+ *
+ * <p>Examples: int, MyClass, java.lang.Integer
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({DotSeparatedIdentifiersOrPrimitiveType.class})
+public @interface CanonicalNameOrPrimitiveType {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ClassGetName.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ClassGetName.java
new file mode 100644
index 0000000..c5bed39
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ClassGetName.java
@@ -0,0 +1,35 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The type representation used by the {@link Class#getName()}, {@link Class#forName(String)}, and
+ * {@link Class#forName(String, boolean, ClassLoader)} methods. This format is:
+ *
+ * <ul>
+ *   <li>for any non-array type, the {@link BinaryName binary name}
+ *   <li>for any array type, a format like the {@link FieldDescriptor field descriptor}, but using
+ *       '.' where the field descriptor uses '/'
+ * </ul>
+ *
+ * <p>Examples include
+ *
+ * <pre>
+ *   java.lang.String
+ *   [Ljava.lang.Object;
+ *   int
+ *   [[[I
+ * </pre>
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SignatureUnknown.class)
+public @interface ClassGetName {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ClassGetSimpleName.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ClassGetSimpleName.java
new file mode 100644
index 0000000..ca75e87
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ClassGetSimpleName.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The format produced by the {@link Class#getSimpleName()} method. It is an identifier or the empty
+ * string (for an anonymous class), followed by an number of array square brackets.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SignatureUnknown.class)
+public @interface ClassGetSimpleName {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/DotSeparatedIdentifiers.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/DotSeparatedIdentifiers.java
new file mode 100644
index 0000000..bbc2227
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/DotSeparatedIdentifiers.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This is a string that is a valid {@linkplain
+ * org.checkerframework.checker.signature.qual.FullyQualifiedName fully qualified name} and a valid
+ * {@linkplain org.checkerframework.checker.signature.qual.BinaryName binary name}. It represents a
+ * non-array, non-inner, non-primitive class: dot-separated identifiers.
+ *
+ * <p>Examples: int, MyClass, java.lang, java.lang.Integer
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({DotSeparatedIdentifiersOrPrimitiveType.class, BinaryName.class})
+public @interface DotSeparatedIdentifiers {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/DotSeparatedIdentifiersOrPrimitiveType.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/DotSeparatedIdentifiersOrPrimitiveType.java
new file mode 100644
index 0000000..f50ce6c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/DotSeparatedIdentifiersOrPrimitiveType.java
@@ -0,0 +1,25 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This is a string that is a valid {@linkplain
+ * org.checkerframework.checker.signature.qual.FullyQualifiedName fully qualified name} and a valid
+ * {@linkplain org.checkerframework.checker.signature.qual.BinaryNameOrPrimitiveType binary name or
+ * primitive type}. It represents a primitive type or a non-array, non-inner class, where the latter
+ * is represented by dot-separated identifiers.
+ *
+ * <p>Examples: int, MyClass, java.lang.Integer
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({FullyQualifiedName.class, BinaryNameOrPrimitiveType.class})
+public @interface DotSeparatedIdentifiersOrPrimitiveType {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FieldDescriptor.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FieldDescriptor.java
new file mode 100644
index 0000000..a069898
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FieldDescriptor.java
@@ -0,0 +1,34 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Represents a field descriptor (JVM type format) as defined in the <a
+ * href="https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.3.2">Java Virtual
+ * Machine Specification, section 4.3.2</a>.
+ *
+ * <p>For example, in
+ *
+ * <pre>
+ *  package org.checkerframework.checker.signature;
+ *  public class SignatureChecker {
+ *    private class Inner {}
+ *  }
+ * </pre>
+ *
+ * the field descriptors for the two types are
+ * Lorg/checkerframework/checker/signature/SignatureChecker; and
+ * Lorg/checkerframework/checker/signature/SignatureChecker$Inner; .
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SignatureUnknown.class)
+public @interface FieldDescriptor {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FieldDescriptorForPrimitive.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FieldDescriptorForPrimitive.java
new file mode 100644
index 0000000..3b4bb26
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FieldDescriptorForPrimitive.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Represents a field descriptor (JVM type format) for a primitive as defined in the <a
+ * href="https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.3.2">Java Virtual
+ * Machine Specification, section 4.3.2</a>.
+ *
+ * <p>Must be one of B, C, D, F, I, J, S, Z.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({FieldDescriptorWithoutPackage.class, Identifier.class})
+public @interface FieldDescriptorForPrimitive {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FieldDescriptorWithoutPackage.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FieldDescriptorWithoutPackage.java
new file mode 100644
index 0000000..ac2817e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FieldDescriptorWithoutPackage.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Represents a {@link FieldDescriptor field descriptor} for a primitive or for an array whose base
+ * type is primitive or in the unnamed package.
+ *
+ * <p>Examples: I [[J MyClass MyClass$22 [LMyClass; [LMyClass$22
+ *
+ * <p>Field descriptor (JVM type format) is defined in the <a
+ * href="https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.3.2">Java Virtual
+ * Machine Specification, section 4.3.2</a>.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({ClassGetName.class, FieldDescriptor.class})
+public @interface FieldDescriptorWithoutPackage {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FqBinaryName.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FqBinaryName.java
new file mode 100644
index 0000000..32af0d6
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FqBinaryName.java
@@ -0,0 +1,34 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An extension of binary name format to represent primitives and arrays. It is just like
+ * fully-qualified name format, but uses "$" rather than "." to indicate a nested class.
+ *
+ * <p>Examples include
+ *
+ * <pre>
+ *   int
+ *   int[][]
+ *   java.lang.String
+ *   java.lang.String[]
+ *   pkg.Outer$Inner
+ *   pkg.Outer$Inner[]
+ * </pre>
+ *
+ * <p>This is not a format defined by the Java language or platform, but is a convenient format for
+ * users to unambiguously specify a type.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SignatureUnknown.class)
+public @interface FqBinaryName {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FullyQualifiedName.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FullyQualifiedName.java
new file mode 100644
index 0000000..4ddb441
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/FullyQualifiedName.java
@@ -0,0 +1,49 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * A sequence of dot-separated identifiers, followed by any number of array square brackets.
+ * Represents a fully-qualified name as defined in the <a
+ * href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.7">Java Language
+ * Specification, section 6.7</a>.
+ *
+ * <p>Examples:
+ *
+ * <pre>{@code
+ * int
+ * MyClass
+ * java.lang.Integer
+ * int[][]
+ * MyClass[]
+ * java.lang.Integer[][][]
+ * }</pre>
+ *
+ * <p>in
+ *
+ * <pre>
+ *  package org.checkerframework.checker.signature;
+ *  public class SignatureChecker {
+ *    private class Inner {}
+ *  }
+ * </pre>
+ *
+ * the fully-qualified names for the two types are
+ * org.checkerframework.checker.signature.SignatureChecker and
+ * org.checkerframework.checker.signature.SignatureChecker.Inner.
+ *
+ * <p>Fully-qualified names and {@linkplain BinaryName binary names} are the same for top-level
+ * classes and only differ by a '.' vs. '$' for inner classes.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FqBinaryName.class)
+public @interface FullyQualifiedName {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/Identifier.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/Identifier.java
new file mode 100644
index 0000000..c08108a
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/Identifier.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An identifier.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@SubtypeOf({
+  DotSeparatedIdentifiers.class,
+  BinaryNameWithoutPackage.class,
+  IdentifierOrPrimitiveType.class
+})
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Identifier {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/IdentifierOrPrimitiveType.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/IdentifierOrPrimitiveType.java
new file mode 100644
index 0000000..64313c8
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/IdentifierOrPrimitiveType.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An identifier or a primitive type.
+ *
+ * <p>Example: Foobar, Baz22, int, float
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({ArrayWithoutPackage.class, DotSeparatedIdentifiersOrPrimitiveType.class})
+public @interface IdentifierOrPrimitiveType {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/InternalForm.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/InternalForm.java
new file mode 100644
index 0000000..cec44ee
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/InternalForm.java
@@ -0,0 +1,30 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The syntax for binary names that appears in a class file, as defined in the <a
+ * href="https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.2">JVM
+ * Specification, section 4.2</a>. A {@linkplain BinaryName binary name} is conceptually the name
+ * for the class or interface in a compiled binary, but the actual representation of that name in
+ * its class file is slightly different.
+ *
+ * <p>Internal form is the same as the binary name, but with periods ({@code .}) replaced by forward
+ * slashes ({@code /}).
+ *
+ * <p>Programmers more often use the binary name, leaving the internal form as a JVM implementation
+ * detail.
+ *
+ * @see BinaryName
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SignatureUnknown.class)
+public @interface InternalForm {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/MethodDescriptor.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/MethodDescriptor.java
new file mode 100644
index 0000000..61f4e70
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/MethodDescriptor.java
@@ -0,0 +1,34 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Represents a method descriptor (JVM representation of method signature) as defined in the <a
+ * href="https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.3.3">Java Virtual
+ * Machine Specification, section 4.3.3</a>.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ *  package edu.cs.washington;
+ *  public class BinaryName {
+ *    private class Inner {
+ *      public void method(Object obj, int i) {}
+ *    }
+ *  }
+ * </pre>
+ *
+ * In this example method descriptor for method 'method': (Ljava/lang/Object;I)Z
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(SignatureUnknown.class)
+public @interface MethodDescriptor {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/PolySignature.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/PolySignature.java
new file mode 100644
index 0000000..89c05ef
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/PolySignature.java
@@ -0,0 +1,25 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the Signature type system.
+ *
+ * <p>Any method written using {@code @PolySignature} conceptually has two versions: one in which
+ * every instance of {@code @PolySignature String} has been replaced by {@code @Signature String},
+ * and one in which every instance of {@code @PolySignature String} has been replaced by {@code
+ * String}.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(SignatureUnknown.class)
+public @interface PolySignature {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/PrimitiveType.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/PrimitiveType.java
new file mode 100644
index 0000000..9d1e72e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/PrimitiveType.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * A primitive type. One of: boolean, byte, char, double, float, int, long, short.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@SubtypeOf({IdentifierOrPrimitiveType.class, CanonicalName.class})
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface PrimitiveType {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/SignatureBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/SignatureBottom.java
new file mode 100644
index 0000000..67adb0e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/SignatureBottom.java
@@ -0,0 +1,30 @@
+package org.checkerframework.checker.signature.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Signature String type system. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf({
+  FieldDescriptorForPrimitive.class,
+  PrimitiveType.class,
+  CanonicalNameAndBinaryName.class,
+  MethodDescriptor.class
+})
+@DefaultFor({TypeUseLocation.LOWER_BOUND})
+public @interface SignatureBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/SignatureUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/SignatureUnknown.java
new file mode 100644
index 0000000..ddac82f
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/SignatureUnknown.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.signature.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Top qualifier in the type hierarchy.
+ *
+ * <p>This annotation may not be written in source code; it is an implementation detail of the
+ * checker.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({}) // empty target prevents programmers from writing this in a program
+@DefaultQualifierInHierarchy
+@SubtypeOf({})
+public @interface SignatureUnknown {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/PolySigned.java b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/PolySigned.java
new file mode 100644
index 0000000..d07dd51
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/PolySigned.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.signedness.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the signedness type system.
+ *
+ * @checker_framework.manual #signedness-checker Signedness Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(UnknownSignedness.class)
+public @interface PolySigned {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java
new file mode 100644
index 0000000..e316b1c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java
@@ -0,0 +1,43 @@
+package org.checkerframework.checker.signedness.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeKind;
+import org.checkerframework.framework.qual.UpperBoundFor;
+
+/**
+ * The value is to be interpreted as signed. That is, if the most significant bit in the bitwise
+ * representation is set, then the bits should be interpreted as a negative number.
+ *
+ * @checker_framework.manual #signedness-checker Signedness Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({UnknownSignedness.class})
+@DefaultFor(
+    typeKinds = {
+      TypeKind.BYTE,
+      TypeKind.INT,
+      TypeKind.LONG,
+      TypeKind.SHORT,
+      TypeKind.FLOAT,
+      TypeKind.DOUBLE
+    },
+    types = {
+      java.lang.Byte.class,
+      java.lang.Integer.class,
+      java.lang.Long.class,
+      java.lang.Short.class,
+      java.lang.Float.class,
+      java.lang.Double.class
+    })
+@UpperBoundFor(
+    typeKinds = {TypeKind.FLOAT, TypeKind.DOUBLE},
+    types = {java.lang.Float.class, java.lang.Double.class})
+public @interface Signed {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/SignedPositive.java b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/SignedPositive.java
new file mode 100644
index 0000000..ef4ef02
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/SignedPositive.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.signedness.qual;
+
+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;
+
+/**
+ * The expression's value is in the signed positive range; that is, its most significant bit is not
+ * set. The value has the same interpretation as {@link Signed} and {@link Unsigned} &mdash; both
+ * interpretations are equivalent.
+ *
+ * <p>Programmers should rarely write {@code @SignedPositive}. Instead, the programmer should write
+ * {@link Signed} or {@link Unsigned} to indicate how the programmer intends the value to be
+ * interpreted.
+ *
+ * <p>Internally, this is translated to the {@code @}{@link SignednessGlb} annotation. This means
+ * that programmers do not see this annotation in error messages.
+ *
+ * @see SignednessGlb
+ * @checker_framework.manual #signedness-checker Signedness Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface SignedPositive {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/SignedPositiveFromUnsigned.java b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/SignedPositiveFromUnsigned.java
new file mode 100644
index 0000000..ca17613
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/SignedPositiveFromUnsigned.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.signedness.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The expression is {@code @}{@link SignedPositive}, and its value came from widening a value that
+ * is allowed to be interpreted as unsigned.
+ *
+ * <p>Programmers should rarely write this annotation.
+ *
+ * @see SignednessGlb
+ * @checker_framework.manual #signedness-checker Signedness Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({SignednessGlb.class})
+public @interface SignedPositiveFromUnsigned {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/SignednessBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/SignednessBottom.java
new file mode 100644
index 0000000..31b8a7c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/SignednessBottom.java
@@ -0,0 +1,25 @@
+package org.checkerframework.checker.signedness.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Signedness type system. Programmers should rarely write this type.
+ *
+ * <p>This is the type of the {@code null} literal.
+ *
+ * @checker_framework.manual #signedness-checker Signedness Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf({SignedPositiveFromUnsigned.class})
+public @interface SignednessBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/SignednessGlb.java b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/SignednessGlb.java
new file mode 100644
index 0000000..1a7150d
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/SignednessGlb.java
@@ -0,0 +1,43 @@
+package org.checkerframework.checker.signedness.qual;
+
+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;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Client code may interpret the value either as {@link Signed} or as {@link Unsigned}. This
+ * primarily applies to values whose most significant bit is not set {@link SignedPositive}, and
+ * thus the value has the same interpretation as signed or unsigned.
+ *
+ * <p>As a special case, the Signedness Checker also applies this annotation to manifest literals.
+ * This permits a value like {@code -1} or {@code 255} or {@code 0xFF} to be used in both signed and
+ * unsigned contexts. The Signedness Checker has no way of knowing how a programmer intended a
+ * literal to be used, so it does not issue warnings for any uses of a literal. (An alternate design
+ * would require the programmer to explicitly annotate every manifest literal whose most significant
+ * bit is set. That might detect more errors, at the cost of much greater programmer annotation
+ * effort.)
+ *
+ * <p>The programmer should not write this annotation. Instead, the programmer should write {@link
+ * Signed} or {@link Unsigned} to indicate how the programmer intends the value to be interpreted.
+ * For a value whose most significant bit is not set and different clients may treat it differently
+ * (say, the return value of certain library routines, or certain constant fields), the programmer
+ * should write {@code @}{@link SignedPositive} instead of {@code @SignednessGlb}.
+ *
+ * <p>The "Glb" in the name stands for "greatest lower bound", because this type is the greatest
+ * lower bound of the types {@link Signed} and {@link Unsigned}; that is, this type is a subtype of
+ * both of those types.
+ *
+ * @see SignedPositive
+ * @checker_framework.manual #signedness-checker Signedness Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({Unsigned.class, Signed.class})
+@QualifierForLiterals({LiteralKind.INT, LiteralKind.LONG, LiteralKind.CHAR})
+public @interface SignednessGlb {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/UnknownSignedness.java b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/UnknownSignedness.java
new file mode 100644
index 0000000..0049b7c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/UnknownSignedness.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.signedness.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The value's signedness is not known to the Signedness Checker. This is also used for non-numeric
+ * values, which cannot have a signedness.
+ *
+ * @checker_framework.manual #signedness-checker Signedness Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface UnknownSignedness {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/Unsigned.java b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/Unsigned.java
new file mode 100644
index 0000000..534c5ac
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/signedness/qual/Unsigned.java
@@ -0,0 +1,30 @@
+package org.checkerframework.checker.signedness.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeKind;
+import org.checkerframework.framework.qual.UpperBoundFor;
+
+/**
+ * The value is to be interpreted as unsigned. That is, if the most significant bit in the bitwise
+ * representation is set, then the bits should be interpreted as a large positive number rather than
+ * as a negative number.
+ *
+ * @checker_framework.manual #signedness-checker Signedness Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({UnknownSignedness.class})
+@DefaultFor(
+    typeKinds = {TypeKind.CHAR},
+    types = {java.lang.Character.class})
+@UpperBoundFor(
+    typeKinds = {TypeKind.CHAR},
+    types = {java.lang.Character.class})
+public @interface Unsigned {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/tainting/qual/PolyTainted.java b/checker-qual/src/main/java/org/checkerframework/checker/tainting/qual/PolyTainted.java
new file mode 100644
index 0000000..0f481e6
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/tainting/qual/PolyTainted.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.tainting.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the Tainting type system.
+ *
+ * @checker_framework.manual #tainting-checker Tainting Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(Tainted.class)
+public @interface PolyTainted {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/tainting/qual/Tainted.java b/checker-qual/src/main/java/org/checkerframework/checker/tainting/qual/Tainted.java
new file mode 100644
index 0000000..bc523dc
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/tainting/qual/Tainted.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.tainting.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Denotes a possibly-tainted value: at run time, the value might be tainted or might be untainted.
+ *
+ * @see Untainted
+ * @see org.checkerframework.checker.tainting.TaintingChecker
+ * @checker_framework.manual #tainting-checker Tainting Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@DefaultQualifierInHierarchy
+@SubtypeOf({})
+public @interface Tainted {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/tainting/qual/Untainted.java b/checker-qual/src/main/java/org/checkerframework/checker/tainting/qual/Untainted.java
new file mode 100644
index 0000000..83d48f5
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/tainting/qual/Untainted.java
@@ -0,0 +1,25 @@
+package org.checkerframework.checker.tainting.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * Denotes a reference that is untainted, i.e. can be trusted.
+ *
+ * @checker_framework.manual #tainting-checker Tainting Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Tainted.class)
+@QualifierForLiterals(LiteralKind.STRING)
+@DefaultFor(TypeUseLocation.LOWER_BOUND)
+public @interface Untainted {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/A.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/A.java
new file mode 100644
index 0000000..ce46d19
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/A.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Ampere.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Current.class)
+public @interface A {
+  Prefix value() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Acceleration.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Acceleration.java
new file mode 100644
index 0000000..fcd64b6
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Acceleration.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of acceleration.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Acceleration {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Angle.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Angle.java
new file mode 100644
index 0000000..bc764db
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Angle.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of measure for angles.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Angle {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Area.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Area.java
new file mode 100644
index 0000000..14dcf93
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Area.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of areas.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Area {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/C.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/C.java
new file mode 100644
index 0000000..bdda8f0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/C.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Degree Centigrade (Celsius).
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Temperature.class)
+public @interface C {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Current.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Current.java
new file mode 100644
index 0000000..fc49e8c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Current.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Electric current.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Current {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Force.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Force.java
new file mode 100644
index 0000000..0152b30
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Force.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of force.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Force {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/K.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/K.java
new file mode 100644
index 0000000..a00b979
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/K.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Kelvin (unit of temperature).
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Temperature.class)
+public @interface K {
+  Prefix value() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Length.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Length.java
new file mode 100644
index 0000000..994acf2
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Length.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of length.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Length {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Luminance.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Luminance.java
new file mode 100644
index 0000000..0d7dd66
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Luminance.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of luminance.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Luminance {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Mass.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Mass.java
new file mode 100644
index 0000000..b0337b2
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Mass.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of mass.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Mass {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/MixedUnits.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/MixedUnits.java
new file mode 100644
index 0000000..08dcc37
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/MixedUnits.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * MixedUnits is the result of multiplying or dividing units, where no more specific unit is known
+ * from a UnitsRelations implementation.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({}) // forbids a programmer from writing it in a program
+@SubtypeOf(UnknownUnits.class)
+public @interface MixedUnits {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/N.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/N.java
new file mode 100644
index 0000000..1e91a14
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/N.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Newton.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Force.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface N {
+  Prefix value() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/PolyUnit.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/PolyUnit.java
new file mode 100644
index 0000000..0435cb4
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/PolyUnit.java
@@ -0,0 +1,44 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the units-of-measure type system implemented by the Units Checker.
+ *
+ * <p>Any method written using @PolyUnit conceptually has many versions: in each one, every instance
+ * of @PolyUnit has been replaced by a different unit qualifier such as @kg (kilograms) or @h
+ * (hours).
+ *
+ * <p>The following example shows how method {@code triplePolyUnit} can be used to process either
+ * meters or seconds:
+ *
+ * <pre><code>
+ * {@literal @}PolyUnit int triplePolyUnit(@PolyUnit int amount) {
+ *    return 3*amount;
+ *  }
+ *
+ *  void testPolyUnit() {
+ *   {@literal @}m int m1 = 7 * UnitsTools.m;
+ *   {@literal @}m int m2 = triplePolyUnit(m1);
+ *
+ *   {@literal @}s int sec1 = 7 * UnitsTools.s;
+ *   {@literal @}s int sec2 = triplePolyUnit(sec1);
+ *
+ *    // :: error: (assignment)
+ *   {@literal @}s int sec3 = triplePolyUnit(m1);
+ *  }
+ * </code></pre>
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(UnknownUnits.class)
+public @interface PolyUnit {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Prefix.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Prefix.java
new file mode 100644
index 0000000..0f1a7b8
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Prefix.java
@@ -0,0 +1,78 @@
+package org.checkerframework.checker.units.qual;
+
+/**
+ * SI prefixes.
+ *
+ * <p>From <a
+ * href="https://en.wikipedia.org/wiki/SI_prefix">http://en.wikipedia.org/wiki/SI_prefix</a>:
+ *
+ * <pre>
+ * yotta   Y   1000^8     10^24    1000000000000000000000000   Septillion      Quadrillion     1991
+ * zetta   Z   1000^7     10^21    1000000000000000000000      Sextillion      Trilliard       1991
+ * exa     E   1000^6     10^18    1000000000000000000         Quintillion     Trillion        1975
+ * peta    P   1000^5     10^15    1000000000000000            Quadrillion     Billiard        1975
+ * tera    T   1000^4     10^12    1000000000000               Trillion        Billion         1960
+ * giga    G   1000^3     10^9     1000000000                  Billion         Milliard        1960
+ * mega    M   1000^2     10^6     1000000                     Million                         1960
+ * kilo    k   1000^1     10^3     1000                        Thousand                        1795
+ * hecto   h   1000^2/3   10^2     100                         Hundred                         1795
+ * deca    da  1000^1/3   10^1     10                          Ten                             1795
+ * 1000^0     10^0     1                           One
+ * deci    d   1000^-1/3  10^-1    0.1                         Tenth                           1795
+ * centi   c   1000^-2/3  10^-2    0.01                        Hundredth                       1795
+ * milli   m   1000^-1    10^-3    0.001                       Thousandth                      1795
+ * micro   my  1000^-2    10^-6    0.000001                    Millionth                       1960
+ * nano    n   1000^-3    10^-9    0.000000001                 Billionth       Milliardth      1960
+ * pico    p   1000^-4    10^-12   0.000000000001              Trillionth      Billionth       1960
+ * femto   f   1000^-5    10^-15   0.000000000000001           Quadrillionth   Billiardth      1964
+ * atto    a   1000^-6    10^-18   0.000000000000000001        Quintillionth   Trillionth      1964
+ * zepto   z   1000^-7    10^-21   0.000000000000000000001     Sextillionth    Trilliardth     1991
+ * yocto   y   1000^-8    10^-24   0.000000000000000000000001  Septillionth    Quadrillionth   1991
+ * </pre>
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+public enum Prefix {
+  /** SI prefix for 10^24. */
+  yotta,
+  /** SI prefix for 10^21. */
+  zetta,
+  /** SI prefix for 10^18. */
+  exa,
+  /** SI prefix for 10^15. */
+  peta,
+  /** SI prefix for 10^12. */
+  tera,
+  /** SI prefix for 10^9. */
+  giga,
+  /** SI prefix for 10^6. */
+  mega,
+  /** SI prefix for 10^3. */
+  kilo,
+  /** SI prefix for 10^2. */
+  hecto,
+  /** SI prefix for 10^1. */
+  deca,
+  /** SI prefix for 10^0, or 1. */
+  one,
+  /** SI prefix for 10^-1. */
+  deci,
+  /** SI prefix for 10^-2. */
+  centi,
+  /** SI prefix for 10^-3. */
+  milli,
+  /** SI prefix for 10^-6. */
+  micro,
+  /** SI prefix for 10^-9. */
+  nano,
+  /** SI prefix for 10^-12. */
+  pico,
+  /** SI prefix for 10^-15. */
+  femto,
+  /** SI prefix for 10^-18. */
+  atto,
+  /** SI prefix for 10^-21. */
+  zepto,
+  /** SI prefix for 10^-24. */
+  yocto
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Speed.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Speed.java
new file mode 100644
index 0000000..94fe348
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Speed.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of speed.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Speed {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Substance.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Substance.java
new file mode 100644
index 0000000..214f981
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Substance.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of substance, such as mole (@{@link mol}).
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Substance {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Temperature.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Temperature.java
new file mode 100644
index 0000000..e251f8c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Temperature.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of temperature.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Temperature {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Time.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Time.java
new file mode 100644
index 0000000..ecf7b91
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Time.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of time.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Time {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsBottom.java
new file mode 100644
index 0000000..8e1f454
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsBottom.java
@@ -0,0 +1,25 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Units type system. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf({}) // needs to be done programmatically
+@DefaultFor(TypeUseLocation.LOWER_BOUND)
+public @interface UnitsBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsMultiple.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsMultiple.java
new file mode 100644
index 0000000..60942d4
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsMultiple.java
@@ -0,0 +1,32 @@
+package org.checkerframework.checker.units.qual;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Define the relation between a base unit and the current unit.
+ *
+ * <p>TODO: add support for factors and more general formulas? E.g. it would be cool if the relation
+ * hour &rarr; minute and Fahrenheit &rarr; Celsius could be expressed.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UnitsMultiple {
+  /**
+   * Returns the base unit to use.
+   *
+   * @return the base unit to use
+   */
+  Class<? extends Annotation> quantity();
+
+  /**
+   * Returns the scaling prefix.
+   *
+   * @return the scaling prefix
+   */
+  Prefix prefix() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsRelations.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsRelations.java
new file mode 100644
index 0000000..c8c0756
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsRelations.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.units.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Specify the class that knows how to handle the meta-annotated unit when put in relation (plus,
+ * multiply, ...) with another unit. That class is a subtype of interface {@link
+ * org.checkerframework.checker.units.UnitsRelations}.
+ *
+ * @see org.checkerframework.checker.units.UnitsRelations
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UnitsRelations {
+  /**
+   * Returns the subclass of {@link org.checkerframework.checker.units.UnitsRelations} to use.
+   *
+   * @return the subclass of {@link org.checkerframework.checker.units.UnitsRelations} to use
+   */
+  // The more precise type is Class<? extends org.checkerframework.checker.units.UnitsRelations>,
+  // but org.checkerframework.checker.units.UnitsRelations is not in checker-qual.jar, nor can
+  // it be since it uses AnnotatedTypeMirrors.  So this declaration uses a less precise type, and
+  // UnitsAnnotatedTypeFactory checks that the argument implements
+  // org.checkerframework.checker.units.UnitsRelations.
+  Class<?> value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnknownUnits.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnknownUnits.java
new file mode 100644
index 0000000..70d98c0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnknownUnits.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * UnknownUnits is the top type of the type hierarchy.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@InvisibleQualifier
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface UnknownUnits {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Volume.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Volume.java
new file mode 100644
index 0000000..a387cc2
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Volume.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of volume.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(UnknownUnits.class)
+public @interface Volume {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/cd.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/cd.java
new file mode 100644
index 0000000..3952193
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/cd.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Candela (unit of luminance).
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Luminance.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface cd {
+  Prefix value() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/degrees.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/degrees.java
new file mode 100644
index 0000000..a4d7ae6
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/degrees.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Degrees.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Angle.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface degrees {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/g.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/g.java
new file mode 100644
index 0000000..54c0da1
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/g.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Gram.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Mass.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface g {
+  Prefix value() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/h.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/h.java
new file mode 100644
index 0000000..6a568a9
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/h.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Hour.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Time.class)
+// TODO: support arbitrary factors?
+// @UnitsMultiple(quantity=s.class, factor=3600)
+@SuppressWarnings("checkstyle:typename")
+public @interface h {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kN.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kN.java
new file mode 100644
index 0000000..fc8e99b
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kN.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Kilonewton.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Force.class)
+@UnitsMultiple(quantity = N.class, prefix = Prefix.kilo)
+@SuppressWarnings("checkstyle:typename")
+public @interface kN {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kg.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kg.java
new file mode 100644
index 0000000..714cdfb
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kg.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Kilogram.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Mass.class)
+@UnitsMultiple(quantity = g.class, prefix = Prefix.kilo)
+@SuppressWarnings("checkstyle:typename")
+public @interface kg {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km.java
new file mode 100644
index 0000000..c2cb32d
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Kilometer.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Length.class)
+@UnitsMultiple(quantity = m.class, prefix = Prefix.kilo)
+@SuppressWarnings("checkstyle:typename")
+public @interface km {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km2.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km2.java
new file mode 100644
index 0000000..590d0fe
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km2.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Square kilometer.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Area.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface km2 {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km3.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km3.java
new file mode 100644
index 0000000..8dc4f86
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km3.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Cubic kilometer.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Volume.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface km3 {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kmPERh.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kmPERh.java
new file mode 100644
index 0000000..28abf4b
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kmPERh.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Kilometer per hour.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Speed.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface kmPERh {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m.java
new file mode 100644
index 0000000..0b9da5c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Meter.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Length.class)
+// This is the default:
+// @UnitsRelations(org.checkerframework.checker.units.UnitsRelationsDefault.class)
+// If you want an alias for "m", e.g. "Meter", simply create that
+// annotation and add this meta-annotation:
+// @UnitsMultiple(quantity=m.class, prefix=Prefix.one)
+@SuppressWarnings("checkstyle:typename")
+public @interface m {
+  Prefix value() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m2.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m2.java
new file mode 100644
index 0000000..dc4473d
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m2.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Square meter.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Area.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface m2 {
+  // does this make sense? Is it multiple of (m^2)? Or (multiple of m)^2?
+  Prefix value() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m3.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m3.java
new file mode 100644
index 0000000..48de6bb
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m3.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Cubic meter.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Volume.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface m3 {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs.java
new file mode 100644
index 0000000..b626b34
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Meter per second.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Speed.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface mPERs {
+  Prefix value() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs2.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs2.java
new file mode 100644
index 0000000..71f2440
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs2.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Meter per second squared.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Acceleration.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface mPERs2 {
+  Prefix value() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/min.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/min.java
new file mode 100644
index 0000000..b729aa0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/min.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Minute.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Time.class)
+@SuppressWarnings("checkstyle:typename")
+// TODO: @UnitsMultiple(quantity=s.class, factor=60)
+public @interface min {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm.java
new file mode 100644
index 0000000..dff9961
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm.java
@@ -0,0 +1,21 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Millimeter.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Length.class)
+@UnitsMultiple(quantity = m.class, prefix = Prefix.milli)
+@SuppressWarnings("checkstyle:typename")
+public @interface mm {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm2.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm2.java
new file mode 100644
index 0000000..2e55698
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm2.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Square millimeter.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Area.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface mm2 {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm3.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm3.java
new file mode 100644
index 0000000..dc84f01
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm3.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Cubic millimeter.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Volume.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface mm3 {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mol.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mol.java
new file mode 100644
index 0000000..a1ea94e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mol.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Mole (unit of {@link Substance}).
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Substance.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface mol {
+  Prefix value() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/radians.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/radians.java
new file mode 100644
index 0000000..1493949
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/radians.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Radians.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Angle.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface radians {
+  Prefix value() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/s.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/s.java
new file mode 100644
index 0000000..89b93fa
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/s.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * A second (1/60 of a minute).
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Time.class)
+@SuppressWarnings("checkstyle:typename")
+public @interface s {
+  Prefix value() default Prefix.one;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/t.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/t.java
new file mode 100644
index 0000000..ca254a0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/t.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.units.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Metric ton.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Mass.class)
+// TODO: support arbitrary factors?
+// @UnitsMultiple(quantity=kg.class, factor=1000)
+@SuppressWarnings("checkstyle:typename")
+public @interface t {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/LeakedToResult.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/LeakedToResult.java
new file mode 100644
index 0000000..1e692e3
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/LeakedToResult.java
@@ -0,0 +1,32 @@
+package org.checkerframework.common.aliasing.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This annotation is used on a formal parameter to indicate that the parameter may be returned, but
+ * it is not otherwise leaked. (A parameter is leaked if it is stored in a field where it could be
+ * accessed later, and in that case this annotation would not apply.)
+ *
+ * <p>For example, the receiver parameter of {@link StringBuffer#append(String s)} is annotated as
+ * {@code @LeakedToResult}, because the method returns the updated receiver.
+ *
+ * <p>This annotation is currently trusted, not checked.
+ *
+ * @see NonLeaked
+ * @checker_framework.manual #aliasing-checker Aliasing Checker
+ */
+
+// This is a type qualifier because of a checker framework limitation (Issue 383), but its hierarchy
+// is ignored. Once the stub parser gets updated to read non-type-qualifiers annotations on stub
+// files, this annotation won't be a type qualifier anymore.
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE})
+@SubtypeOf({NonLeaked.class})
+public @interface LeakedToResult {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeAliased.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeAliased.java
new file mode 100644
index 0000000..241fdcb
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeAliased.java
@@ -0,0 +1,28 @@
+package org.checkerframework.common.aliasing.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * An expression with this type might have an alias. In other words, some other expression,
+ * evaluated at the same program point, might evaluate to the exact same object value.
+ *
+ * @see Unique
+ * @checker_framework.manual #aliasing-checker Aliasing Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+@DefaultFor(
+    value = {TypeUseLocation.UPPER_BOUND, TypeUseLocation.LOWER_BOUND},
+    types = Void.class)
+public @interface MaybeAliased {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeLeaked.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeLeaked.java
new file mode 100644
index 0000000..9038b6b
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeLeaked.java
@@ -0,0 +1,28 @@
+package org.checkerframework.common.aliasing.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Temporary type qualifier:
+ *
+ * <p>This is the default type qualifier for the Leaked hierarchy.
+ *
+ * <p>Once the stub parser gets updated to read non-type-qualifier annotations on stub files (Issue
+ * 383), this annotation can be removed, and {@link NonLeaked} and {@link LeakedToResult} can be
+ * made to be type annotations but not type qualifiers and not in a type hierarchy.
+ *
+ * @checker_framework.manual #aliasing-checker Aliasing Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({})
+@DefaultQualifierInHierarchy
+@SubtypeOf({LeakedToResult.class})
+@InvisibleQualifier
+public @interface MaybeLeaked {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/NonLeaked.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/NonLeaked.java
new file mode 100644
index 0000000..3fca72c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/NonLeaked.java
@@ -0,0 +1,31 @@
+package org.checkerframework.common.aliasing.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This annotation is used on a formal parameter to indicate that the parameter is not leaked
+ * (stored in a location that could be accessed later) nor returned by the method body.
+ *
+ * <p>For example, the parameter of {@link String#String(String s)} is {@code @NonLeaked}, because
+ * the method only uses the parameter to make a copy of it.
+ *
+ * <p>This annotation is currently trusted, not checked.
+ *
+ * @see LeakedToResult
+ * @checker_framework.manual #aliasing-checker Aliasing Checker
+ */
+
+// This is a type qualifier because of a checker framework limitation (Issue 383), but its hierarchy
+// is ignored. Once the stub parser gets updated to read non-type-qualifiers annotations on stub
+// files, this annotation won't be a type qualifier anymore.
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE})
+@SubtypeOf({})
+public @interface NonLeaked {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/Unique.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/Unique.java
new file mode 100644
index 0000000..6194511
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/Unique.java
@@ -0,0 +1,25 @@
+package org.checkerframework.common.aliasing.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An expression with this type has no aliases. In other words, no other expression, evaluated at
+ * the same program point, would evaluate to the exact same object value.
+ *
+ * <p>A constructor's return type should be annotated with {@code @Unique} if the constructor does
+ * not leak references to the constructed object. For example, the {@code String()} constructor
+ * return type is annotated as {@code @Unique}.
+ *
+ * @see MaybeAliased
+ * @checker_framework.manual #aliasing-checker Aliasing Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({MaybeAliased.class})
+public @interface Unique {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/EnsuresInitializedFields.java b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/EnsuresInitializedFields.java
new file mode 100644
index 0000000..6c3c5ed
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/EnsuresInitializedFields.java
@@ -0,0 +1,59 @@
+package org.checkerframework.common.initializedfields.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+import org.checkerframework.framework.qual.PostconditionAnnotation;
+import org.checkerframework.framework.qual.QualifierArgument;
+
+/**
+ * A method postcondition annotation indicates which fields the method definitely initializes.
+ *
+ * @checker_framework.manual #initialized-fields-checker Initialized Fields Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@PostconditionAnnotation(qualifier = InitializedFields.class)
+@InheritedAnnotation
+@Repeatable(EnsuresInitializedFields.List.class)
+public @interface EnsuresInitializedFields {
+  /**
+   * The object whose fields this method initializes.
+   *
+   * @return object whose fields are initialized
+   */
+  public String[] value() default {"this"};
+
+  /**
+   * Fields that this method initializes.
+   *
+   * @return fields that this method initializes
+   */
+  @QualifierArgument("value")
+  public String[] fields();
+
+  /**
+   * A wrapper annotation that makes the {@link EnsuresInitializedFields} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresInitializedFields} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @PostconditionAnnotation(qualifier = InitializedFields.class)
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Return the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresInitializedFields[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFields.java b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFields.java
new file mode 100644
index 0000000..d83042c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFields.java
@@ -0,0 +1,26 @@
+package org.checkerframework.common.initializedfields.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Indicates which fields have definitely been initialized.
+ *
+ * @checker_framework.manual #initialized-fields-checker Initialized Fields Checker
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface InitializedFields {
+  /**
+   * Fields that have been initialized.
+   *
+   * @return the initialized fields
+   */
+  public String[] value() default {};
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFieldsBottom.java b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFieldsBottom.java
new file mode 100644
index 0000000..2f16939
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFieldsBottom.java
@@ -0,0 +1,21 @@
+package org.checkerframework.common.initializedfields.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type qualifier for the Initialized Fields type system. It is the type of {@code null}.
+ * Programmers should rarely write this qualifier.
+ *
+ * @checker_framework.manual #initialized-fields-checker Initialized Fields Checker
+ */
+@SubtypeOf({InitializedFields.class})
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+public @interface InitializedFieldsBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/PolyInitializedFields.java b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/PolyInitializedFields.java
new file mode 100644
index 0000000..1f25542
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/PolyInitializedFields.java
@@ -0,0 +1,14 @@
+package org.checkerframework.common.initializedfields.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * Polymorphic qualifier for the Initialized Fields type system.
+ *
+ * @checker_framework.manual #initialized-fields-checker Initialized Fields Checker
+ */
+@PolymorphicQualifier(InitializedFields.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface PolyInitializedFields {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassBound.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassBound.java
new file mode 100644
index 0000000..6f0d8cf
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassBound.java
@@ -0,0 +1,26 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This represents a {@code Class<T>} object whose run-time value is equal to or a subtype of one of
+ * the arguments.
+ *
+ * @checker_framework.manual #methodval-and-classval-checkers ClassVal Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({UnknownClass.class})
+public @interface ClassBound {
+  /**
+   * The <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1">binary
+   * name</a> of the class or classes that upper-bound the values of this Class object.
+   */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassVal.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassVal.java
new file mode 100644
index 0000000..5d3ea55
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassVal.java
@@ -0,0 +1,31 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This represents a {@link java.lang.Class Class&lt;T&gt;} object where the set of possible values
+ * of T is known at compile time. If only one argument is given, then the exact value of T is known.
+ * If more than one argument is given, then the value of T is one of those classes.
+ *
+ * @checker_framework.manual #methodval-and-classval-checkers ClassVal Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({UnknownClass.class})
+public @interface ClassVal {
+  /**
+   * The name of the type that this Class object represents. The name is a "fully-qualified binary
+   * name" ({@link org.checkerframework.checker.signature.qual.FqBinaryName}): a primitive or <a
+   * href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1">binary name</a>,
+   * possibly followed by some number of array brackets.
+   *
+   * @return the name of the type that this Class object represents
+   */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassValBottom.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassValBottom.java
new file mode 100644
index 0000000..757845f
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassValBottom.java
@@ -0,0 +1,25 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the ClassVal type system. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #methodval-and-classval-checkers ClassVal Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@InvisibleQualifier
+@SubtypeOf({ClassVal.class, ClassBound.class})
+public @interface ClassValBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ForName.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ForName.java
new file mode 100644
index 0000000..0a568e9
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ForName.java
@@ -0,0 +1,21 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+
+/**
+ * Annotation for methods like {@code Class.forName}. Their signature is
+ *
+ * <pre><code>
+ * &nbsp;@ClassVal("name") Class method(@BinaryName String name) {...}
+ * </code></pre>
+ *
+ * @checker_framework.manual #reflection-resolution Reflection resolution
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface ForName {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/GetClass.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/GetClass.java
new file mode 100644
index 0000000..bd97c1a
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/GetClass.java
@@ -0,0 +1,18 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+
+/**
+ * Annotation for methods like {@code Object.getClassName}. Their signature is:<br>
+ * {@code @}{@link ClassBound}{@code ("ReceiverType") Class method(ReceiverType this) {...}}
+ *
+ * @checker_framework.manual #reflection-resolution Reflection resolution
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface GetClass {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/GetConstructor.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/GetConstructor.java
new file mode 100644
index 0000000..db4b9bc
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/GetConstructor.java
@@ -0,0 +1,19 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+
+/**
+ * Annotation for methods like {@code Class.getConstructor}, whose signature is: <br>
+ * {@code @}{@link MethodVal}{@code (classname=c, methodname="<init>", params=p) Constructor<T>
+ * method(Class<c> this, Object... params)}
+ *
+ * @checker_framework.manual #reflection-resolution Reflection resolution
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface GetConstructor {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/GetMethod.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/GetMethod.java
new file mode 100644
index 0000000..f1aba33
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/GetMethod.java
@@ -0,0 +1,20 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+
+/**
+ * Annotation for methods like {@code Class.getMethod} and {@code Class.getDeclaredMethod}, whose
+ * signature is: <br>
+ * {@code {@link MethodVal}(classname=c, methodname=m, params=p) Method getMyMethod(Class<c> this,
+ * String m, Object... params)}
+ *
+ * @checker_framework.manual #reflection-resolution Reflection resolution
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface GetMethod {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/Invoke.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/Invoke.java
new file mode 100644
index 0000000..431631c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/Invoke.java
@@ -0,0 +1,19 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+
+/**
+ * Annotation for methods like {@code Method.invoke}, whose signature is: <br>
+ * {@code Object method({@link MethodVal}(classname=c, methodname=m, params=p) Method this, Object
+ * obj, Object... args)}
+ *
+ * @checker_framework.manual #reflection-resolution Reflection resolution
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface Invoke {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodVal.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodVal.java
new file mode 100644
index 0000000..451109c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodVal.java
@@ -0,0 +1,39 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * This represents a set of {@link java.lang.reflect.Method Method} or {@link
+ * java.lang.reflect.Constructor Constructor} values. If an expression's type has
+ * {@code @MethodVal}, then the expression's run-time value is one of those values.
+ *
+ * <p>Each of {@code @MethodVal}'s argument lists must be of equal length, and { className[i],
+ * methodName[i], params[i] } represents one of the {@code Method} or {@code Constructor} values in
+ * the set.
+ *
+ * @checker_framework.manual #methodval-and-classval-checkers MethodVal Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({UnknownMethod.class})
+public @interface MethodVal {
+  /**
+   * The <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1">binary
+   * name</a> of the class that declares this method.
+   */
+  String[] className();
+
+  /**
+   * The name of the method that this Method object represents. Use {@code <init>} for constructors.
+   */
+  String[] methodName();
+
+  /** The number of parameters to the method. */
+  int[] params();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodValBottom.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodValBottom.java
new file mode 100644
index 0000000..c99f874
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodValBottom.java
@@ -0,0 +1,25 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the MethodVal type system. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #methodval-and-classval-checkers MethodVal Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@InvisibleQualifier
+@SubtypeOf({MethodVal.class})
+public @interface MethodValBottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/NewInstance.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/NewInstance.java
new file mode 100644
index 0000000..5efda6c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/NewInstance.java
@@ -0,0 +1,19 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+
+/**
+ * Annotation for methods like {@code Constructor.newInstance}, whose signature is: <br>
+ * {@code T method(}{@link MethodVal}{@code (classname=c, methodname="<init>", params=p) Constructor
+ * this, Object... args)}
+ *
+ * @checker_framework.manual #reflection-resolution Reflection resolution
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface NewInstance {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownClass.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownClass.java
new file mode 100644
index 0000000..f89c5e9
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownClass.java
@@ -0,0 +1,29 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * Represents a Class object whose run-time value is not known at compile time. Also represents
+ * non-Class values.
+ *
+ * <p>This annotation is the default in the hierarchy.
+ *
+ * @checker_framework.manual #methodval-and-classval-checkers ClassVal Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@InvisibleQualifier
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface UnknownClass {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownMethod.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownMethod.java
new file mode 100644
index 0000000..3454775
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownMethod.java
@@ -0,0 +1,30 @@
+package org.checkerframework.common.reflection.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * Represents a {@link java.lang.reflect.Method Method} or {@link java.lang.reflect.Constructor
+ * Constructor} expression whose run-time value is not known at compile time. Also represents
+ * non-Method, non-Constructor values.
+ *
+ * <p>This annotation is the default in the hierarchy.
+ *
+ * @checker_framework.manual #methodval-and-classval-checkers MethodVal Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@InvisibleQualifier
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface UnknownMethod {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/BottomThis.java b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/BottomThis.java
new file mode 100644
index 0000000..99dd4fc
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/BottomThis.java
@@ -0,0 +1,22 @@
+package org.checkerframework.common.returnsreceiver.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type for the Returns Receiver Checker's type system. Programmers should rarely write
+ * this type.
+ *
+ * @checker_framework.manual #returns-receiver-checker Returns Receiver Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@SubtypeOf({UnknownThis.class})
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+public @interface BottomThis {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/This.java b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/This.java
new file mode 100644
index 0000000..4d30a59
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/This.java
@@ -0,0 +1,47 @@
+package org.checkerframework.common.returnsreceiver.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * Write {@code @This} on the return type of a method that always returns its receiver ({@code
+ * this}). For example:
+ *
+ * <pre><code>
+ * class MyBuilder {
+ *   &#064;This MyBuilder setName(String name) {
+ *     this.name = name;
+ *     return this;
+ *   }
+ * }
+ * </code></pre>
+ *
+ * <p>Strictly speaking, this is a polymorphic annotation, but when you write it on a return type,
+ * the Returns Receiver Checker automatically adds it to the receiver, so the above method is
+ * equivalent to:
+ *
+ * <pre><code>
+ * &#064;This MyBuilder setName(@This MyBuilder this, String name) {
+ *   this.name = name;
+ *   return this;
+ * }
+ * </code></pre>
+ *
+ * <p>While it would be natural to make {@code @This} the default annotation for receivers, it leads
+ * to false positives warnings due to <a
+ * href="https://github.com/typetools/checker-framework/issues/2931">https://github.com/typetools/checker-framework/issues/2931</a>,
+ * so this defaulting is currently elided.
+ *
+ * @checker_framework.manual #returns-receiver-checker Returns Receiver Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier
+@TargetLocations({TypeUseLocation.RECEIVER, TypeUseLocation.RETURN})
+public @interface This {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/UnknownThis.java b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/UnknownThis.java
new file mode 100644
index 0000000..6d1d282
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/UnknownThis.java
@@ -0,0 +1,26 @@
+package org.checkerframework.common.returnsreceiver.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The top type for the Returns Receiver Checker's type system. Values of the annotated type might
+ * be the receiver ({@code this}) or might not. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #returns-receiver-checker Returns Receiver Checker
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@DefaultQualifierInHierarchy
+@SubtypeOf({})
+@QualifierForLiterals(LiteralKind.NULL)
+@DefaultFor(value = TypeUseLocation.LOWER_BOUND)
+public @interface UnknownThis {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Bottom.java b/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Bottom.java
new file mode 100644
index 0000000..b60bf2f
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Bottom.java
@@ -0,0 +1,32 @@
+package org.checkerframework.common.subtyping.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * A special annotation intended solely for representing the bottom type in the qualifier hierarchy.
+ * It should not be used! Instead, each type system should define its own dedicated bottom type.
+ *
+ * <p>This qualifier is used automatically if the existing qualifiers do not have a bottom type.
+ * This only works the user never runs two type systems together. Furthermore, because it has no
+ * {@code @RetentionPolicy} meta-annotation, this qualifier will not be stored in bytecode. So, only
+ * use this qualifier during prototyping of very simple type systems. For realistic systems,
+ * introduce a top and bottom qualifier that gets stored in bytecode.
+ *
+ * <p>To use this qualifier, the type system designer needs to use methods like {@code
+ * org.checkerframework.framework.type.treeannotator.ImplicitsTreeAnnotator#addTreeKind(com.sun.source.tree.Tree.Kind,
+ * javax.lang.model.element.AnnotationMirror)} to add default annotations and needs to manually add
+ * the bottom qualifier to the qualifier hierarchy.
+ *
+ * @see org.checkerframework.framework.type.QualifierHierarchy#getBottomAnnotations()
+ * @checker_framework.manual #subtyping-checker Subtyping Checker
+ */
+@Documented
+@SubtypeOf({})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+public @interface Bottom {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Unqualified.java b/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Unqualified.java
new file mode 100644
index 0000000..df08edb
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Unqualified.java
@@ -0,0 +1,27 @@
+package org.checkerframework.common.subtyping.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * A special annotation intended solely for representing an unqualified type in the qualifier
+ * hierarchy, as an argument to {@link SubtypeOf#value()}, in a type qualifier declaration.
+ *
+ * <p>This annotation may not be written in source code; it is an implementation detail of the
+ * checker.
+ *
+ * <p>Use this qualifier only when experimenting with very simple type systems. For any more
+ * realistic type systems, introduce a top and bottom qualifier that gets stored in bytecode.
+ *
+ * @checker_framework.manual #subtyping-checker Subtyping Checker
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE) // don't store in class file
+@Target({}) // empty target prevents programmers from writing this in a program.
+@InvisibleQualifier
+@SubtypeOf({})
+public @interface Unqualified {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportCall.java b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportCall.java
new file mode 100644
index 0000000..e680208
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportCall.java
@@ -0,0 +1,22 @@
+package org.checkerframework.common.util.report.qual;
+
+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;
+
+/**
+ * Report all calls of a method that has this annotation, including calls of methods that override
+ * this method. Note that calls through a supertype, where the method is not annotated, cannot be
+ * reported.
+ *
+ * <p>For example, assume three classes A, B, and C, that each implement/override a method m and A
+ * &lt;: B &lt;: C. Assume that B.m is annotated as ReportCall. Calls of A.m and B.m will then be
+ * reported, but calls of C.m will not be reported, even though the C reference might point to a B
+ * object. Therefore, add the ReportCall annotation high enough in the subtype hierarchy.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface ReportCall {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportCreation.java b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportCreation.java
new file mode 100644
index 0000000..689691c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportCreation.java
@@ -0,0 +1,17 @@
+package org.checkerframework.common.util.report.qual;
+
+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;
+
+/**
+ * Report all instantiations of a class/interface that has this annotation, including any subclass.
+ * Report all invocations of a particular constructor. (There is no overriding of constructors, so
+ * use on a constructor reports only that particular constructor.)
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.CONSTRUCTOR})
+public @interface ReportCreation {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportInherit.java b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportInherit.java
new file mode 100644
index 0000000..f3582f0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportInherit.java
@@ -0,0 +1,13 @@
+package org.checkerframework.common.util.report.qual;
+
+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;
+
+/** Report all types that extend/implement a type that has this annotation. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ReportInherit {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportOverride.java b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportOverride.java
new file mode 100644
index 0000000..2dfebef
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportOverride.java
@@ -0,0 +1,13 @@
+package org.checkerframework.common.util.report.qual;
+
+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;
+
+/** Report all methods that override a method with this annotation. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface ReportOverride {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportReadWrite.java b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportReadWrite.java
new file mode 100644
index 0000000..2fe0532
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportReadWrite.java
@@ -0,0 +1,13 @@
+package org.checkerframework.common.util.report.qual;
+
+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;
+
+/** Report all read or write access to a field with this annotation. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface ReportReadWrite {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUnqualified.java b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUnqualified.java
new file mode 100644
index 0000000..2354cef
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUnqualified.java
@@ -0,0 +1,22 @@
+package org.checkerframework.common.util.report.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An annotation intended solely for representing an unqualified type in the qualifier hierarchy for
+ * the Report Checker.
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE) // do not store in class file
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+@InvisibleQualifier
+public @interface ReportUnqualified {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUse.java b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUse.java
new file mode 100644
index 0000000..91b7f64
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUse.java
@@ -0,0 +1,13 @@
+package org.checkerframework.common.util.report.qual;
+
+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;
+
+/** Report all uses of a type that has this annotation. Can also be used on a package. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PACKAGE, ElementType.TYPE})
+public @interface ReportUse {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportWrite.java b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportWrite.java
new file mode 100644
index 0000000..f7bf265
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportWrite.java
@@ -0,0 +1,13 @@
+package org.checkerframework.common.util.report.qual;
+
+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;
+
+/** Report all write accesses to a field with this annotation. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface ReportWrite {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLen.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLen.java
new file mode 100644
index 0000000..20b19a1
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLen.java
@@ -0,0 +1,28 @@
+package org.checkerframework.common.value.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An annotation indicating the length of an array or a string. If an expression's type has this
+ * annotation, then at run time, the expression evaluates to an array or a string whose length is
+ * one of the annotation's arguments.
+ *
+ * <p>For example, {@code String @ArrayLen(2) []} is the type for an array of two strings, and
+ * {@code String @ArrayLen({1, 2, 4, 8}) []} is the type for an array that contains 1, 2, 4, or 8
+ * strings.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf({UnknownVal.class})
+public @interface ArrayLen {
+  /** The possible lengths of the array. */
+  int[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLenRange.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLenRange.java
new file mode 100644
index 0000000..4597c44
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLenRange.java
@@ -0,0 +1,26 @@
+package org.checkerframework.common.value.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An expression with this type evaluates to an array or a string whose length is in the given
+ * range. The bounds are inclusive; for example, {@code @ArrayLenRange(from=6, to=9)} represents an
+ * array or a string with four possible values for its length: 6, 7, 8, and 9.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf(UnknownVal.class)
+public @interface ArrayLenRange {
+  /** Smallest value in the range, inclusive. */
+  int from() default 0;
+  /** Largest value in the range, inclusive. */
+  int to() default Integer.MAX_VALUE;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/BoolVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/BoolVal.java
new file mode 100644
index 0000000..66f5a7f
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/BoolVal.java
@@ -0,0 +1,23 @@
+package org.checkerframework.common.value.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An annotation indicating the possible values for a bool type. If an expression's type has this
+ * annotation, then at run time, the expression evaluates to one of the annotation's arguments.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf({UnknownVal.class})
+public @interface BoolVal {
+  /** The values that the expression might evaluate to. */
+  boolean[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/BottomVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/BottomVal.java
new file mode 100644
index 0000000..82a1243
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/BottomVal.java
@@ -0,0 +1,37 @@
+package org.checkerframework.common.value.qual;
+
+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;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * The bottom type in the Constant Value type system. Programmers should rarely write this type.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ * @checker_framework.manual #bottom-type the bottom type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf({
+  ArrayLen.class,
+  BoolVal.class,
+  DoubleVal.class,
+  IntVal.class,
+  StringVal.class,
+  MatchesRegex.class,
+  ArrayLenRange.class,
+  IntRange.class,
+  IntRangeFromPositive.class,
+  IntRangeFromGTENegativeOne.class,
+  IntRangeFromNonNegative.class
+})
+@InvisibleQualifier
+public @interface BottomVal {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoubleVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoubleVal.java
new file mode 100644
index 0000000..1bfab7a
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoubleVal.java
@@ -0,0 +1,26 @@
+package org.checkerframework.common.value.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An annotation indicating the possible values for a double or float type. If an expression's type
+ * has this annotation, then at run time, the expression evaluates to one of the annotation's
+ * arguments.
+ *
+ * <p>Annotation for values
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf({UnknownVal.class})
+public @interface DoubleVal {
+  /** The values that the expression might evaluate to. */
+  double[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnsuresMinLenIf.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnsuresMinLenIf.java
new file mode 100644
index 0000000..9fe5089
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnsuresMinLenIf.java
@@ -0,0 +1,74 @@
+package org.checkerframework.common.value.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+import org.checkerframework.framework.qual.QualifierArgument;
+
+/**
+ * Indicates that the value of the given expression is a sequence containing at least the given
+ * number of elements, if the method returns the given result (either true or false).
+ *
+ * <p>When the annotated method returns {@code result}, then all the expressions in {@code
+ * expression} are considered to be {@code MinLen(targetValue)}.
+ *
+ * @see MinLen
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@ConditionalPostconditionAnnotation(qualifier = MinLen.class)
+@InheritedAnnotation
+@Repeatable(EnsuresMinLenIf.List.class)
+public @interface EnsuresMinLenIf {
+  /**
+   * Returns Java expression(s) that are a sequence with the given minimum length after the method
+   * returns {@link #result}.
+   *
+   * @return an array of Java expression(s), each of which is a sequence with the given minimum
+   *     length after the method returns {@link #result}
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  String[] expression();
+
+  /**
+   * Returns the return value of the method under which the postcondition to hold.
+   *
+   * @return the return value of the method under which the postcondition to hold
+   */
+  boolean result();
+
+  /**
+   * Returns the minimum number of elements in the sequence.
+   *
+   * @return the minimum number of elements in the sequence
+   */
+  @QualifierArgument("value")
+  int targetValue() default 0;
+
+  /**
+   * A wrapper annotation that makes the {@link EnsuresMinLenIf} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresMinLenIf} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @ConditionalPostconditionAnnotation(qualifier = MinLen.class)
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Return the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresMinLenIf[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnumVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnumVal.java
new file mode 100644
index 0000000..c07b669
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnumVal.java
@@ -0,0 +1,29 @@
+package org.checkerframework.common.value.qual;
+
+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;
+
+/**
+ * An annotation indicating the possible values for an enum type. If an expression's type has this
+ * annotation, then at run time, the expression evaluates to one of the enum values named by the
+ * arguments. EnumVal uses the simple name of the enum value: the EnumVal type corresponding to
+ * {@code MyEnum.MY_VALUE} is {@code @EnumVal("MY_VALUE")}.
+ *
+ * <p>This annotation is treated as {@link StringVal} internally by the Constant Value Checker.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+public @interface EnumVal {
+  /**
+   * The simple names of the possible enum values for an expression with the annotated type.
+   *
+   * @return the simple names of the possible enum values for an expression with the annotated type
+   */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRange.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRange.java
new file mode 100644
index 0000000..be8de9d
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRange.java
@@ -0,0 +1,33 @@
+package org.checkerframework.common.value.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An expression with this type evaluates to an integral value (byte, short, char, int, or long) in
+ * the given range. The bounds are inclusive. For example, the following declaration allows the 12
+ * values 0, 1, ..., 11:
+ *
+ * <pre>{@code @IntRange(from=0, to=11) int month;}</pre>
+ *
+ * <p>If only one of the {@code to} and {@code from} fields is set, then the other will default to
+ * the max/min value of the type of the variable that is annotated. (In other words, the defaults
+ * {@code Long.MIN_VALUE} and {@code Long.MAX_VALUE} are used only for \code{long}; appropriate
+ * values are used for other types.)
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf(UnknownVal.class)
+public @interface IntRange {
+  /** Smallest value in the range, inclusive. */
+  long from() default Long.MIN_VALUE;
+  /** Largest value in the range, inclusive. */
+  long to() default Long.MAX_VALUE;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromGTENegativeOne.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromGTENegativeOne.java
new file mode 100644
index 0000000..fc65c4a
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromGTENegativeOne.java
@@ -0,0 +1,33 @@
+package org.checkerframework.common.value.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An expression with this type is exactly the same as an {@link IntRange} annotation whose {@code
+ * from} field is {@code -1} and whose {@code to} field is {@code Integer.MAX_VALUE}. However, this
+ * annotation is derived from an {@code org.checkerframework.checker.index.qual.GTENegativeOne}
+ * annotation.
+ *
+ * <p>IntRangeFromGTENegativeOne annotations derived from GTENegativeOne annotations are used to
+ * create IntRange annotations, but IntRangeFromGTENegativeOne annotations are not checked when they
+ * appear on the left hand side of expressions. Therefore, the Index Checker MUST be run on any code
+ * with @GTENegativeOne annotations on the left-hand side of expressions, since the Value Checker
+ * will derive information from them but not check them.
+ *
+ * <p>It is an error to write this annotation directly. {@code @GTENegativeOne} or {@code
+ * IntRange(from = -1, to = Integer.MAX_VALUE)} should always be written instead. This annotation is
+ * not retained in bytecode, but is replaced with {@code @UnknownVal}, so that it is not enforced on
+ * method boundaries. The {@code @GTENegativeOne} annotation it replaced is retained in bytecode by
+ * the Lower Bound Checker instead.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({})
+@SubtypeOf(UnknownVal.class)
+public @interface IntRangeFromGTENegativeOne {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromNonNegative.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromNonNegative.java
new file mode 100644
index 0000000..434ff25
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromNonNegative.java
@@ -0,0 +1,33 @@
+package org.checkerframework.common.value.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An expression with this type is exactly the same as an {@link IntRange} annotation whose {@code
+ * from} field is {@code 0} and whose {@code to} field is {@code Integer.MAX_VALUE}. However, this
+ * annotation is derived from an {@code org.checkerframework.checker.index.qual.NonNegative}
+ * annotation.
+ *
+ * <p>IntRangeFromNonNegative annotations derived from NonNegative annotations are used to create
+ * IntRange annotations, but IntRangeFromNonNegative annotations are not checked when they appear on
+ * the left hand side of expressions. Therefore, the Index Checker MUST be run on any code
+ * with @NonNegative annotations on the left-hand side of expressions, since the Value Checker will
+ * derive information from them but not check them.
+ *
+ * <p>It is an error to write this annotation directly. {@code @NonNegative} or {@code IntRange(from
+ * = 0, to = Integer.MAX_VALUE)} should always be written instead. This annotation is not retained
+ * in bytecode, but is replaced with {@code @UnknownVal}, so that it is not enforced on method
+ * boundaries. The {@code @NonNegative} annotation it replaced is retained in bytecode by the Lower
+ * Bound Checker instead.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({})
+@SubtypeOf(UnknownVal.class)
+public @interface IntRangeFromNonNegative {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromPositive.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromPositive.java
new file mode 100644
index 0000000..4941505
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromPositive.java
@@ -0,0 +1,33 @@
+package org.checkerframework.common.value.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An expression with this type is exactly the same as an {@link IntRange} annotation whose {@code
+ * from} field is {@code 1} and whose {@code to} field is {@code Integer.MAX_VALUE}. However, this
+ * annotation is derived from an {@code org.checkerframework.checker.index.qual.Positive}
+ * annotation.
+ *
+ * <p>IntRangeFromPositive annotations derived from Positive annotations are used to create IntRange
+ * annotations, but IntRangeFromPositive annotations are not checked when they appear on the left
+ * hand side of expressions. Therefore, the Index Checker MUST be run on any code with @Positive
+ * annotations on the left-hand side of expressions, since the Value Checker will derive information
+ * from them but not check them.
+ *
+ * <p>It is an error to write this annotation directly. {@code @Positive} or {@code IntRange(from =
+ * 1, to = Integer.MAX_VALUE)} should always be written instead. This annotation is not retained in
+ * bytecode, but is replaced with {@code @UnknownVal}, so that it is not enforced on method
+ * boundaries. The {@code @Positive} annotation it replaced is retained in bytecode by the Lower
+ * Bound Checker instead.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({})
+@SubtypeOf(UnknownVal.class)
+public @interface IntRangeFromPositive {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntVal.java
new file mode 100644
index 0000000..3ab290f
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntVal.java
@@ -0,0 +1,24 @@
+package org.checkerframework.common.value.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An annotation indicating the possible values for a byte, short, char, int, or long type. If an
+ * expression's type has this annotation, then at run time, the expression evaluates to one of the
+ * annotation's arguments.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf({UnknownVal.class})
+public @interface IntVal {
+  /** The values that the expression might evaluate to. */
+  long[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MatchesRegex.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MatchesRegex.java
new file mode 100644
index 0000000..76eee09
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MatchesRegex.java
@@ -0,0 +1,32 @@
+package org.checkerframework.common.value.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An annotation indicating the possible values for a String type. The annotation's arguments are
+ * Java regular expressions. If an expression's type has this annotation, then at run time, the
+ * expression evaluates to a string that matches at least one of the regular expressions. Matching
+ * is via the <a
+ * href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#matches(java.lang.String)">java.lang.String#matches</a>
+ * method, which matches against the entire string (it does not look for a match against a
+ * substring).
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf({UnknownVal.class})
+public @interface MatchesRegex {
+  /**
+   * A set of Java regular expressions.
+   *
+   * @return the regular expressions
+   */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLen.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLen.java
new file mode 100644
index 0000000..7384c2f
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLen.java
@@ -0,0 +1,24 @@
+package org.checkerframework.common.value.qual;
+
+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;
+
+/**
+ * The value of the annotated expression is a sequence containing at least the given number of
+ * elements. An alias for an {@link ArrayLenRange} annotation with a {@code from} field and the
+ * maximum possible value for an array length ({@code Integer.MAX_VALUE}) as its {@code to} field.
+ *
+ * <p>This annotation is used extensively by the Index Chcker.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface MinLen {
+  /** The minimum number of elements in this sequence. */
+  int value() default 0;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLenFieldInvariant.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLenFieldInvariant.java
new file mode 100644
index 0000000..bf12001
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLenFieldInvariant.java
@@ -0,0 +1,34 @@
+package org.checkerframework.common.value.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.FieldInvariant;
+
+/**
+ * A specialization of {@link FieldInvariant} for specifying the minimum length of an array. A class
+ * can be annotated with both this annotation and a {@link FieldInvariant} annotation.
+ *
+ * @checker_framework.manual #field-invariants Field invariants
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface MinLenFieldInvariant {
+
+  /**
+   * Min length of the array. Must be greater than the min length of the array as declared in the
+   * superclass.
+   */
+  int[] minLen();
+
+  /**
+   * The field that has an array length qualifier in the class on which the field invariant is
+   * written. The field must be final and declared in a superclass.
+   */
+  String[] field();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/PolyValue.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/PolyValue.java
new file mode 100644
index 0000000..8ae998b
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/PolyValue.java
@@ -0,0 +1,20 @@
+package org.checkerframework.common.value.qual;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the Constant Value Checker.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(UnknownVal.class)
+public @interface PolyValue {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/StaticallyExecutable.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/StaticallyExecutable.java
new file mode 100644
index 0000000..7dc91cb
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/StaticallyExecutable.java
@@ -0,0 +1,19 @@
+package org.checkerframework.common.value.qual;
+
+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;
+
+/**
+ * StaticallyExecutable is a method annotation that indicates that the compiler is allowed to run
+ * the method at compile time, if all of the method's arguments are compile-time constants. It is
+ * used by the Constant Value Checker.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+public @interface StaticallyExecutable {}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/StringVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/StringVal.java
new file mode 100644
index 0000000..6f0ccb4
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/StringVal.java
@@ -0,0 +1,23 @@
+package org.checkerframework.common.value.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * An annotation indicating the possible values for a String type. If an expression's type has this
+ * annotation, then at run time, the expression evaluates to one of the annotation's arguments.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf({UnknownVal.class})
+public @interface StringVal {
+  /** The values that the expression might evaluate to. */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/UnknownVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/UnknownVal.java
new file mode 100644
index 0000000..bcc6b9d
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/UnknownVal.java
@@ -0,0 +1,22 @@
+package org.checkerframework.common.value.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * UnknownVal is a type annotation indicating that the expression's value is not known at compile
+ * type.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface UnknownVal {}
diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Deterministic.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Deterministic.java
new file mode 100644
index 0000000..693898c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Deterministic.java
@@ -0,0 +1,92 @@
+package org.checkerframework.dataflow.qual;
+
+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;
+
+/**
+ * A method is called <em>deterministic</em> if it returns the same value (according to {@code ==})
+ * every time it is called with the same parameters and in the same environment. The parameters
+ * include the receiver, and the environment includes all of the Java heap (that is, all fields of
+ * all objects and all static variables).
+ *
+ * <p>Determinism refers to the return value during a non-exceptional execution. If a method throws
+ * an exception, the Throwable does not have to be exactly the same object on each invocation (and
+ * generally should not be, to capture the correct stack trace).
+ *
+ * <p>This annotation is important to pluggable type-checking because, after a call to a
+ * {@code @Deterministic} method, flow-sensitive type refinement can assume that anything learned
+ * about the first invocation is true about subsequent invocations (so long as no
+ * non-{@code @}{@link SideEffectFree} method call intervenes). For example, the following code
+ * never suffers a null pointer exception, so the Nullness Checker need not issue a warning:
+ *
+ * <pre>{@code
+ * if (x.myDeterministicMethod() != null) {
+ *   x.myDeterministicMethod().hashCode();
+ * }
+ * }</pre>
+ *
+ * <p>Note that {@code @Deterministic} guarantees that the result is identical according to {@code
+ * ==}, <b>not</b> just equal according to {@code equals()}. This means that writing
+ * {@code @Deterministic} on a method that returns a reference (including a String) is often
+ * erroneous unless the returned value is cached or interned.
+ *
+ * <p>Also see {@link Pure}, which means both deterministic and {@link SideEffectFree}.
+ *
+ * <p><b>Analysis:</b> The Checker Framework performs a conservative analysis to verify a
+ * {@code @Deterministic} annotation. The Checker Framework issues a warning if the method uses any
+ * of the following Java constructs:
+ *
+ * <ol>
+ *   <li>Assignment to any expression, except for local variables and method parameters.<br>
+ *       (Note that storing into an array element, such a {@code a[i] = x}, is not an assignment to
+ *       a variable and is therefore forbidden.)
+ *   <li>A method invocation of a method that is not {@link Deterministic}.
+ *   <li>Construction of a new object.
+ *   <li>Catching any exceptions. This restriction prevents a method from obtaining a reference to a
+ *       newly-created exception object and using these objects (or some property thereof) to change
+ *       the method's return value. For instance, the following method must be forbidden.
+ *       <!-- "<code>" instead of "{@code ...}" because of at-sign at beginning of line -->
+ *       <pre><code>@Deterministic
+ * int f() {
+ *   try {
+ *     int b = 0;
+ *     int a = 1/b;
+ *   } catch (Throwable t) {
+ *     return t.hashCode();
+ *   }
+ *   return 0;
+ * }
+ * </code></pre>
+ * </ol>
+ *
+ * When a constructor is annotated as {@code Deterministic} (or {@code @Pure}), that means that all
+ * the fields are deterministic (the same values, if the arguments are the same). The constructed
+ * object itself is different. That is, a constructor <em>invocation</em> is never deterministic
+ * since it returns a different new object each time.
+ *
+ * <p>Note that the rules for checking currently imply that every {@code Deterministic} method is
+ * also {@link SideEffectFree}. This might change in the future; in general, a deterministic method
+ * does not need to be side-effect-free.
+ *
+ * <p>These rules are conservative: any code that passes the checks is deterministic, but the
+ * Checker Framework may issue false positive warnings, for code that uses one of the forbidden
+ * constructs but is deterministic nonetheless.
+ *
+ * <p>In fact, the rules are so conservative that checking is currently disabled by default, but can
+ * be enabled via the {@code -AcheckPurityAnnotations} command-line option.
+ *
+ * <p>This annotation is inherited by subtypes, just as if it were meta-annotated with
+ * {@code @InheritedAnnotation}.
+ *
+ * @checker_framework.manual #type-refinement-purity Side effects, determinism, purity, and
+ *     flow-sensitive analysis
+ */
+// @InheritedAnnotation cannot be written here, because "dataflow" project cannot depend on
+// "framework" project.
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+public @interface Deterministic {}
diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Pure.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Pure.java
new file mode 100644
index 0000000..82cdb59
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Pure.java
@@ -0,0 +1,37 @@
+package org.checkerframework.dataflow.qual;
+
+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;
+
+/**
+ * {@code Pure} is a method annotation that means both {@link SideEffectFree} and {@link
+ * Deterministic}. The more important of these, when performing pluggable type-checking, is usually
+ * {@link SideEffectFree}.
+ *
+ * <p>For a discussion of the meaning of {@code Pure} on a constructor, see the documentation of
+ * {@link Deterministic}.
+ *
+ * <p>This annotation is inherited by subtypes, just as if it were meta-annotated with
+ * {@code @InheritedAnnotation}.
+ *
+ * @checker_framework.manual #type-refinement-purity Side effects, determinism, purity, and
+ *     flow-sensitive analysis
+ */
+// @InheritedAnnotation cannot be written here, because "dataflow" project cannot depend on
+// "framework" project.
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+public @interface Pure {
+  /** The type of purity. */
+  public static enum Kind {
+    /** The method has no visible side effects. */
+    SIDE_EFFECT_FREE,
+
+    /** The method returns exactly the same value when called in the same environment. */
+    DETERMINISTIC
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectFree.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectFree.java
new file mode 100644
index 0000000..da33d80
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectFree.java
@@ -0,0 +1,58 @@
+package org.checkerframework.dataflow.qual;
+
+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;
+
+/**
+ * A method is called <em>side-effect-free</em> if it has no visible side-effects, such as setting a
+ * field of an object that existed before the method was called.
+ *
+ * <p>Only the visible side effects are important. The method is allowed to cache the answer to a
+ * computationally expensive query, for instance. It is also allowed to modify newly-created
+ * objects, and a constructor is side-effect-free if it does not modify any objects that existed
+ * before it was called.
+ *
+ * <p>This annotation is important to pluggable type-checking because if some fact about an object
+ * is known before a call to such a method, then the fact is still known afterwards, even if the
+ * fact is about some non-final field. When any non-{@code @SideEffectFree} method is called, then a
+ * pluggable type-checker must assume that any field of any accessible object might have been
+ * modified, which annuls the effect of flow-sensitive type refinement and prevents the pluggable
+ * type-checker from making conclusions that are obvious to a programmer.
+ *
+ * <p>Also see {@link Pure}, which means both side-effect-free and {@link Deterministic}.
+ *
+ * <p><b>Analysis:</b> The Checker Framework performs a conservative analysis to verify a
+ * {@code @SideEffectFree} annotation. The Checker Framework issues a warning if the method uses any
+ * of the following Java constructs:
+ *
+ * <ol>
+ *   <li>Assignment to any expression, except for local variables and method parameters.<br>
+ *       (Note that storing into an array element, such a {@code a[i] = x}, is not an assignment to
+ *       a variable and is therefore forbidden.)
+ *   <li>A method invocation of a method that is not {@code @SideEffectFree}.
+ *   <li>Construction of a new object where the constructor is not {@code @SideEffectFree}.
+ * </ol>
+ *
+ * These rules are conservative: any code that passes the checks is side-effect-free, but the
+ * Checker Framework may issue false positive warnings, for code that uses one of the forbidden
+ * constructs but is side-effect-free nonetheless. In particular, a method that caches its result
+ * will be rejected.
+ *
+ * <p>In fact, the rules are so conservative that checking is currently disabled by default, but can
+ * be enabled via the {@code -AcheckPurityAnnotations} command-line option.
+ *
+ * <p>This annotation is inherited by subtypes, just as if it were meta-annotated with
+ * {@code @InheritedAnnotation}.
+ *
+ * @checker_framework.manual #type-refinement-purity Side effects, determinism, purity, and
+ *     flow-sensitive analysis
+ */
+// @InheritedAnnotation cannot be written here, because "dataflow" project cannot depend on
+// "framework" project.
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+public @interface SideEffectFree {}
diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/TerminatesExecution.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/TerminatesExecution.java
new file mode 100644
index 0000000..412c4be
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/TerminatesExecution.java
@@ -0,0 +1,39 @@
+package org.checkerframework.dataflow.qual;
+
+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;
+
+/**
+ * {@code TerminatesExecution} is a method annotation that indicates that a method terminates the
+ * execution of the program. This can be used to annotate methods such as {@code System.exit()}, or
+ * methods that unconditionally throw an exception.
+ *
+ * <p>The annotation enables flow-sensitive type refinement to be more precise. For example, after
+ *
+ * <pre>
+ * if (x == null) {
+ *   System.err.println("Bad value supplied");
+ *   System.exit(1);
+ * }
+ * </pre>
+ *
+ * the Nullness Checker can determine that {@code x} is non-null.
+ *
+ * <p>The annotation is a <em>trusted</em> annotation, meaning that it is not checked whether the
+ * annotated method really does terminate the program.
+ *
+ * <p>This annotation is inherited by subtypes, just as if it were meta-annotated with
+ * {@code @InheritedAnnotation}.
+ *
+ * @checker_framework.manual #type-refinement Automatic type refinement (flow-sensitive type
+ *     qualifier inference)
+ */
+// @InheritedAnnotation cannot be written here, because "dataflow" project cannot depend on
+// "framework" project.
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+public @interface TerminatesExecution {}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/AnnotatedFor.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/AnnotatedFor.java
new file mode 100644
index 0000000..df857d2
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/AnnotatedFor.java
@@ -0,0 +1,41 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * Indicates that this class has been annotated for the given type system. For example,
+ * {@code @AnnotatedFor({"nullness", "regex"})} indicates that the class has been annotated with
+ * annotations such as {@code @Nullable} and {@code @Regex}.
+ *
+ * <p>You should only use this annotation in a partially-annotated library. There is no point to
+ * using it in a fully-annotated library nor in an application that does not export APIs for
+ * clients.
+ *
+ * <p>This annotation has no effect unless the {@code
+ * -AuseConservativeDefaultsForUncheckedCode=source} command-line argument is supplied. Ordinarily,
+ * the {@code -AuseConservativeDefaultsForUncheckedCode=source} command-line argument causes
+ * unannotated locations to be defaulted using conservative defaults, and it suppresses all
+ * warnings. However, a class with a relevant {@code @AnnotatedFor} annotation is always defaulted
+ * normally (typically using the CLIMB-to-top rule), and typechecking warnings are issued.
+ *
+ * @checker_framework.manual #compiling-libraries Compiling partially-annotated libraries
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PACKAGE})
+public @interface AnnotatedFor {
+  /**
+   * Returns the type systems for which the class has been annotated. Legal arguments are any string
+   * that may be passed to the {@code -processor} command-line argument: the fully-qualified class
+   * name for the checker, or a shorthand for built-in checkers. Using the annotation with no
+   * arguments, as in {@code @AnnotatedFor({})}, has no effect.
+   *
+   * @return the type systems for which the class has been annotated
+   * @checker_framework.manual #shorthand-for-checkers Short names for built-in checkers
+   */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/CFComment.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/CFComment.java
new file mode 100644
index 0000000..f9ef370
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/CFComment.java
@@ -0,0 +1,41 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This annotation is for comments related to the Checker Framework.
+ *
+ * <p>Using {@code @CFComment} makes it easy to find Checker-Framework-related comments, and to copy
+ * those comments from one version of a codebase to another (using the Annotation File Utilities).
+ *
+ * <p>Here is an example:
+ *
+ * <pre><code>
+ * {@literal @}CFComment("interning: factory methods guarantee that all elements are interned")
+ *  public class MyClass {
+ *   {@literal @}CFComment({"nullness: non-null return type is more specific than in superclass",
+ *                "signedness: comment related to Signedness type system"})
+ *    public String myMethod() { ... }
+ * }
+ * </code></pre>
+ *
+ * <p>As a matter of style, programmers should use this annotation on the most deeply nested element
+ * to which the comment applies (e.g., local variable rather than method, and method rather than
+ * class).
+ *
+ * @checker_framework.manual #library-tips-dont-change-the-code Don't change the code
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+public @interface CFComment {
+  /**
+   * Comments about Checker Framework annotations. The text is not interpreted by the Checker
+   * Framework.
+   *
+   * <p>If you prefix each comment by the name of the type system, the comments are easier to
+   * understand and search for.
+   */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/ConditionalPostconditionAnnotation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/ConditionalPostconditionAnnotation.java
new file mode 100644
index 0000000..9b7feb6
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/ConditionalPostconditionAnnotation.java
@@ -0,0 +1,74 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+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;
+
+/**
+ * A meta-annotation that indicates that an annotation E is a conditional postcondition annotation,
+ * i.e., E is a type-specialized version of {@link EnsuresQualifierIf} or {@link
+ * EnsuresQualifierIf.List}.
+ *
+ * <ul>
+ *   <li>If E is a type-specialized version of {@link EnsuresQualifierIf}, it must have
+ *       <ul>
+ *         <li>an element {@code expression} that is an array of {@code String}s, analogous to
+ *             {@link EnsuresQualifierIf#expression()}, and
+ *         <li>an element {@code result} with the same meaning as {@link
+ *             EnsuresQualifierIf#result()}.
+ *       </ul>
+ *   <li>If E is a type-specialized version of {@link EnsuresQualifierIf.List}, it must have an
+ *       element {@code value} that is an array of conditional postcondition annotations, analogous
+ *       to {@link EnsuresQualifierIf.List#value()}.
+ * </ul>
+ *
+ * <p>The established postcondition P has type specified by the {@code qualifier} field of this
+ * annotation. If the annotation E has elements annotated by {@link QualifierArgument}, their values
+ * are copied to the arguments (elements) of annotation P with the same names. Different element
+ * names may be used in E and P, if a {@link QualifierArgument} in E gives the name of the
+ * corresponding element in P.
+ *
+ * <p>For example, the following code declares a postcondition annotation for the {@link
+ * org.checkerframework.common.value.qual.MinLen} qualifier:
+ *
+ * <pre><code>
+ * {@literal @}ConditionalPostconditionAnnotation(qualifier = MinLen.class)
+ * {@literal @}Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+ * public {@literal @}interface EnsuresMinLen {
+ *   String[] expression();
+ *   boolean result();
+ *   {@literal @}QualifierArgument("value")
+ *   int targetValue() default 0;
+ * </code></pre>
+ *
+ * The {@code expression} element holds the expressions to which the qualifier applies and {@code
+ * targetValue} holds the value for the {@code value} argument of {@link
+ * org.checkerframework.common.value.qual.MinLen}.
+ *
+ * <p>The following code then uses the annotation on a method that ensures {@code field} to be
+ * {@code @MinLen(4)} upon returning {@code true}.
+ *
+ * <pre><code>
+ * {@literal @}EnsuresMinLenIf(expression = "field", result = true, targetValue = 4")
+ * public boolean isFieldBool() {
+ *   return field == "true" || field == "false";
+ * }
+ * </code></pre>
+ *
+ * @see EnsuresQualifier
+ * @see QualifierArgument
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.ANNOTATION_TYPE})
+public @interface ConditionalPostconditionAnnotation {
+  /**
+   * The qualifier that will be established as a postcondition.
+   *
+   * <p>This element is analogous to {@link EnsuresQualifierIf#qualifier()}.
+   */
+  Class<? extends Annotation> qualifier();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/Covariant.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/Covariant.java
new file mode 100644
index 0000000..1599464
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/Covariant.java
@@ -0,0 +1,37 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * A marker annotation, written on a class declaration, that signifies that one or more of the
+ * class's type parameters can be treated covariantly. For example, if {@code MyClass} has a single
+ * type parameter that is treated covariantly, and if {@code B} is a subtype of {@code A}, then
+ * {@code SomeClass<B>} is a subtype of {@code SomeClass<A>}.
+ *
+ * <p>Ordinarily, Java treats type parameters invariantly: {@code SomeClass<B>} is unrelated to
+ * (neither a subtype nor a supertype of) {@code SomeClass<A>}.
+ *
+ * <p>It is only safe to mark a type parameter as covariant if clients use the type parameter in a
+ * read-only way: clients read values of that type but never modify them.
+ *
+ * <p>This property is not checked; the {@code @Covariant} is simply trusted.
+ *
+ * <p>Here is an example use:
+ *
+ * <pre>{@code @Covariant(0)
+ * public interface Iterator<E extends @Nullable Object> { ... }
+ * }</pre>
+ *
+ * @checker_framework.manual #covariant-type-parameters Covariant type parameters
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface Covariant {
+  /** The zero-based indices of the type parameters that should be treated covariantly. */
+  int[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultFor.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultFor.java
new file mode 100644
index 0000000..a9a8c6c
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultFor.java
@@ -0,0 +1,83 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * A meta-annotation applied to the declaration of a type qualifier. It specifies that the given
+ * annotation should be the default for:
+ *
+ * <ul>
+ *   <li>all uses at a particular location,
+ *   <li>all uses of a particular type, and
+ *   <li>all uses of a particular kind of type.
+ * </ul>
+ *
+ * <p>The default applies to every match for any of this annotation's conditions.
+ *
+ * @see TypeUseLocation
+ * @see DefaultQualifier
+ * @see DefaultQualifierInHierarchy
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface DefaultFor {
+  /**
+   * Returns the locations to which the annotation should be applied.
+   *
+   * @return the locations to which the annotation should be applied
+   */
+  TypeUseLocation[] value() default {};
+
+  /**
+   * Returns {@link TypeKind}s of types for which an annotation should be implicitly added.
+   *
+   * @return {@link TypeKind}s of types for which an annotation should be implicitly added
+   */
+  TypeKind[] typeKinds() default {};
+
+  /**
+   * Returns {@link Class}es for which an annotation should be applied. For example, if
+   * {@code @MyAnno} is meta-annotated with {@code @DefaultFor(classes=String.class)}, then every
+   * occurrence of {@code String} is actually {@code @MyAnno String}.
+   *
+   * @return {@link Class}es for which an annotation should be applied
+   */
+  Class<?>[] types() default {};
+
+  /**
+   * Returns regular expressions matching names of variables, to whose types the annotation should
+   * be applied as a default. If a regular expression matches the name of a method, the annotation
+   * is applied as a default to the return type.
+   *
+   * <p>The regular expression must match the entire variable or method name. For example, to match
+   * any name that contains "foo", use ".*foo.*".
+   *
+   * <p>The default does not apply if the name matches any of the regexes in {@link
+   * #namesExceptions}.
+   *
+   * <p>This affects formal parameter types only if one of the following is true:
+   *
+   * <ul>
+   *   <li>The method's source code is available; that is, the method is type-checked along with
+   *       client calls.
+   *   <li>When the method was compiled in a previous run of javac, either the processor was run or
+   *       the {@code -g} command-line option was provided.
+   * </ul>
+   *
+   * @return regular expressions matching variables to whose type an annotation should be applied
+   */
+  String[] names() default {};
+
+  /**
+   * Returns exceptions to regular expression rules.
+   *
+   * @return exceptions to regular expression rules
+   * @see #names
+   */
+  String[] namesExceptions() default {};
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifier.java
new file mode 100644
index 0000000..19eb9a1
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifier.java
@@ -0,0 +1,91 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Applied to a declaration of a package, type, method, variable, etc., specifies that the given
+ * annotation should be the default. The default is applied to type uses within the declaration for
+ * which no other annotation is explicitly written. (The default is not applied to the "parametric
+ * locations": class declarations, type parameter declarations, and type parameter uses.) If
+ * multiple {@code DefaultQualifier} annotations are in scope, the innermost one takes precedence.
+ * DefaultQualifier takes precedence over {@link DefaultQualifierInHierarchy}.
+ *
+ * <p>You may write multiple {@code @DefaultQualifier} annotations (for unrelated type systems, or
+ * with different {@code locations} fields) at the same location. For example:
+ *
+ * <pre>
+ * &nbsp; @DefaultQualifier(NonNull.class)
+ * &nbsp; @DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.IMPLICIT_UPPER_BOUND)
+ * &nbsp; @DefaultQualifier(Tainted.class)
+ * &nbsp; class MyClass { ... }
+ * </pre>
+ *
+ * <p>This annotation currently has no effect in stub files.
+ *
+ * @see org.checkerframework.framework.qual.TypeUseLocation
+ * @see DefaultQualifierInHierarchy
+ * @see DefaultFor
+ * @checker_framework.manual #defaults Default qualifier for unannotated types
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({
+  ElementType.PACKAGE,
+  ElementType.TYPE,
+  ElementType.CONSTRUCTOR,
+  ElementType.METHOD,
+  ElementType.FIELD,
+  ElementType.LOCAL_VARIABLE,
+  ElementType.PARAMETER
+})
+@Repeatable(DefaultQualifier.List.class)
+public @interface DefaultQualifier {
+
+  /**
+   * The Class for the default annotation.
+   *
+   * <p>To prevent affecting other type systems, always specify an annotation in your own type
+   * hierarchy. (For example, do not set {@link
+   * org.checkerframework.common.subtyping.qual.Unqualified} as the default.)
+   */
+  Class<? extends Annotation> value();
+
+  /**
+   * Returns the locations to which the annotation should be applied.
+   *
+   * @return the locations to which the annotation should be applied
+   */
+  TypeUseLocation[] locations() default {TypeUseLocation.ALL};
+
+  /**
+   * A wrapper annotation that makes the {@link DefaultQualifier} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link DefaultQualifier} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({
+    ElementType.PACKAGE,
+    ElementType.TYPE,
+    ElementType.CONSTRUCTOR,
+    ElementType.METHOD,
+    ElementType.FIELD,
+    ElementType.LOCAL_VARIABLE,
+    ElementType.PARAMETER
+  })
+  @interface List {
+    /**
+     * Return the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    DefaultQualifier[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierForUse.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierForUse.java
new file mode 100644
index 0000000..c88065e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierForUse.java
@@ -0,0 +1,20 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+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;
+
+/**
+ * Declaration annotation applied to type declarations to specify the qualifier to be added to
+ * unannotated uses of the type.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface DefaultQualifierForUse {
+  /** Qualifier to add to all unannotated uses of the type with this declaration annotation. */
+  Class<? extends Annotation>[] value() default {};
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierInHierarchy.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierInHierarchy.java
new file mode 100644
index 0000000..69515f7
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierInHierarchy.java
@@ -0,0 +1,31 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * Indicates that the annotated qualifier is the default qualifier in the qualifier hierarchy: it
+ * applies if the programmer writes no explicit qualifier and no other default has been specified
+ * for the location.
+ *
+ * <p>Other defaults can be specified for a checker via the {@link DefaultFor} meta-annotation,
+ * which takes precedence over {@code DefaultQualifierInHierarchy}, or via {@link
+ * org.checkerframework.framework.type.GenericAnnotatedTypeFactory#addCheckedCodeDefaults(org.checkerframework.framework.util.defaults.QualifierDefaults)}.
+ *
+ * <p>The {@link DefaultQualifier} annotation, which targets Java code elements, takes precedence
+ * over {@code DefaultQualifierInHierarchy}.
+ *
+ * <p>Each type qualifier hierarchy may have at most one qualifier marked as {@code
+ * DefaultQualifierInHierarchy}.
+ *
+ * @checker_framework.manual #effective-qualifier The effective qualifier on a type (defaults and
+ *     inference)
+ * @see org.checkerframework.framework.qual.DefaultQualifier
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface DefaultQualifierInHierarchy {}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifier.java
new file mode 100644
index 0000000..8af324d
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifier.java
@@ -0,0 +1,72 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A postcondition annotation to indicate that a method ensures that certain expressions have a
+ * certain type qualifier once the method has successfully terminated. The expressions for which the
+ * qualifier holds after the method's execution are indicated by {@code expression} and are
+ * specified using a string. The qualifier is specified by the {@code qualifier} annotation element.
+ *
+ * <p>Here is an example use:
+ *
+ * <pre><code>
+ *  {@literal @}EnsuresQualifier(expression = "p.f1", qualifier = Odd.class)
+ *   void oddF1_1() {
+ *       p.f1 = null;
+ *   }
+ * </code></pre>
+ *
+ * Some type systems have specialized versions of this annotation, such as {@code
+ * org.checkerframework.checker.nullness.qual.EnsuresNonNull} and {@code
+ * org.checkerframework.checker.lock.qual.EnsuresLockHeld}.
+ *
+ * @see EnsuresQualifierIf
+ * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@InheritedAnnotation
+@Repeatable(EnsuresQualifier.List.class)
+public @interface EnsuresQualifier {
+  /**
+   * Returns the Java expressions for which the qualifier holds after successful method termination.
+   *
+   * @return the Java expressions for which the qualifier holds after successful method termination
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  String[] expression();
+
+  /**
+   * Returns the qualifier that is guaranteed to hold on successful termination of the method.
+   *
+   * @return the qualifier that is guaranteed to hold on successful termination of the method
+   */
+  Class<? extends Annotation> qualifier();
+
+  /**
+   * A wrapper annotation that makes the {@link EnsuresQualifier} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresQualifier} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Return the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresQualifier[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifierIf.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifierIf.java
new file mode 100644
index 0000000..2596dbe
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifierIf.java
@@ -0,0 +1,86 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A conditional postcondition annotation to indicate that a method ensures that certain expressions
+ * have a certain qualifier once the method has terminated, and if the result is as indicated by
+ * {@code result}. The expressions for which the qualifier holds after the method's execution are
+ * indicated by {@code expression} and are specified using a string. The qualifier is specified by
+ * the {@code qualifier} annotation element.
+ *
+ * <p>Here is an example use:
+ *
+ * <pre><code>
+ *   {@literal @}EnsuresQualifierIf(result = true, expression = "#1", qualifier = Odd.class)
+ *    boolean isOdd(final int p1, int p2) {
+ *        return p1 % 2 == 1;
+ *    }
+ * </code></pre>
+ *
+ * <p>This annotation is only applicable to methods with a boolean return type.
+ *
+ * <p>Some type systems have specialized versions of this annotation, such as {@code
+ * org.checkerframework.checker.nullness.qual.EnsuresNonNullIf} and {@code
+ * org.checkerframework.checker.lock.qual.EnsuresLockHeldIf}.
+ *
+ * @see EnsuresQualifier
+ * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+@InheritedAnnotation
+@Repeatable(EnsuresQualifierIf.List.class)
+public @interface EnsuresQualifierIf {
+  /**
+   * Returns the Java expressions for which the qualifier holds if the method terminates with return
+   * value {@link #result()}.
+   *
+   * @return the Java expressions for which the qualifier holds if the method terminates with return
+   *     value {@link #result()}
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  String[] expression();
+
+  /**
+   * Returns the qualifier that is guaranteed to hold if the method terminates with return value
+   * {@link #result()}.
+   *
+   * @return the qualifier that is guaranteed to hold if the method terminates with return value
+   *     {@link #result()}
+   */
+  Class<? extends Annotation> qualifier();
+
+  /**
+   * Returns the return value of the method that needs to hold for the postcondition to hold.
+   *
+   * @return the return value of the method that needs to hold for the postcondition to hold
+   */
+  boolean result();
+
+  /**
+   * A wrapper annotation that makes the {@link EnsuresQualifierIf} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link EnsuresQualifierIf} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD})
+  @InheritedAnnotation
+  @interface List {
+    /**
+     * Return the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    EnsuresQualifierIf[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/FieldInvariant.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/FieldInvariant.java
new file mode 100644
index 0000000..2a5b604
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/FieldInvariant.java
@@ -0,0 +1,41 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies that a field's type, in the class on which this annotation is written, is a subtype of
+ * its declared type. The field must be declared in a superclass and must be final.
+ *
+ * <p>The {@code @FieldInvariant} annotation does not currently accommodate type qualifiers with
+ * attributes, such as {@code @MinLen(1)}. In this case, the type system should implement its own
+ * field invariant annotation and override {@link
+ * org.checkerframework.framework.type.AnnotatedTypeFactory#getFieldInvariantDeclarationAnnotations()}
+ * and {@link
+ * org.checkerframework.framework.type.AnnotatedTypeFactory#getFieldInvariants(javax.lang.model.element.TypeElement)}.
+ * See {@link org.checkerframework.common.value.qual.MinLenFieldInvariant} for example.
+ *
+ * @checker_framework.manual #field-invariants Field invariants
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Inherited
+public @interface FieldInvariant {
+
+  /**
+   * The qualifier on the field. Must be a subtype of the qualifier on the declaration of the field.
+   */
+  Class<? extends Annotation>[] qualifier();
+
+  /**
+   * The field that has a more precise type, in the class on which the {@code FieldInvariant}
+   * annotation is written. The field must be declared in a superclass and must be {@code final}.
+   */
+  String[] field();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/FromByteCode.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/FromByteCode.java
new file mode 100644
index 0000000..11522ff
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/FromByteCode.java
@@ -0,0 +1,19 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * If a method is annotated with this declaration annotation, then its signature is not written in a
+ * stub file and the method is not declared in source. This annotation is added in
+ * AnnotatedTypeFactory. (If a method is already annotated with @FromStubFile, this annotation is
+ * not added.)
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE, ElementType.PACKAGE})
+@SubtypeOf({})
+public @interface FromByteCode {}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/FromStubFile.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/FromStubFile.java
new file mode 100644
index 0000000..de528ee
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/FromStubFile.java
@@ -0,0 +1,16 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * If a method is annotated with this declaration annotation, then its signature was read from a
+ * stub file. It is added by the AnnotationFileParser and is not visible to the end user.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE, ElementType.PACKAGE})
+public @interface FromStubFile {}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/HasQualifierParameter.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/HasQualifierParameter.java
new file mode 100644
index 0000000..9cc7501
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/HasQualifierParameter.java
@@ -0,0 +1,79 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This is a declaration annotation that applies to type declarations and packages. On a type, it
+ * means that the class conceptually takes a type qualifier parameter, though there is nowhere to
+ * write it because the class hard-codes a Java basetype rather than taking a type parameter.
+ * Writing {@code HasQualifierParameter} on a package is the same as writing it on each class in
+ * that package.
+ *
+ * <p>Writing {@code @HasQualifierParameter} on a type declaration has two effects.
+ *
+ * <ol>
+ *   <li>Invariant subtyping is used for occurrences of the type: no two occurrences of the type
+ *       with different qualifiers have a subtyping relationship.
+ *   <li>The polymorphic qualifier is the default for all occurrences of that type in its own
+ *       compilation unit, including as the receiver, as another formal parameter, or as a return
+ *       type.
+ * </ol>
+ *
+ * Here is an example of the effect of invariant subtyping. Suppose we have the following
+ * declaration:
+ *
+ * <pre>
+ *  {@code @HasQualifierParameter}
+ *   class StringBuffer { ... }
+ * </pre>
+ *
+ * Then {@code @Tainted StringBuffer} is unrelated to {@code @Untainted StringBuffer}.
+ *
+ * <p>The type hierarchy looks like this:
+ *
+ * <pre>
+ *
+ *                       {@code @Tainted} Object
+ *                      /       |       \
+ *                     /        |       {@code @Tainted} Date
+ *                   /          |               |
+ *                  /           |               |
+ *                 /   {@code @Untainted} Object       |
+ *                /             |       \       |
+ *  {@code @Tainted} StringBuffer      |      {@code @Untainted} Date
+ *             |                |
+ *             |      {@code @Untainted} StringBuffer
+ *             |                |
+ *  {@code @Tainted} MyStringBuffer    |
+ *                              |
+ *                    {@code @Untainted} MyStringBuffer
+ * </pre>
+ *
+ * <p>When a class is {@code @HasQualifierParameter}, all its subclasses are as well.
+ *
+ * <p>When {@code @HasQualifierParameter} is written on a package, it is equivalent to writing that
+ * annotation on each class in the package or in a sub-package. It can be disabled on a specific
+ * class and its subclasses by writing {@code @NoQualifierParameter} on that class. This annotation
+ * may not be written on the same class as {@code NoQualifierParameter} for the same hierarchy.
+ *
+ * @see NoQualifierParameter
+ */
+@Inherited
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.PACKAGE})
+public @interface HasQualifierParameter {
+
+  /**
+   * Class of the top qualifier for the hierarchy for which this class has a qualifier parameter.
+   *
+   * @return the value
+   */
+  Class<? extends Annotation>[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/IgnoreInWholeProgramInference.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/IgnoreInWholeProgramInference.java
new file mode 100644
index 0000000..588bf67
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/IgnoreInWholeProgramInference.java
@@ -0,0 +1,39 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * This annotation can be used two ways:
+ *
+ * <p>1. As a meta-annotation indicating that an annotation type prevents whole-program inference.
+ * For example, if the definition of {@code @Inject} is meta-annotated with
+ * {@code @IgnoreInWholeProgramInference}:<br>
+ * {@code @IgnoreInWholeProgramInference}<br>
+ * {@code @interface Inject {}}<br>
+ * then no type qualifier will be inferred for any field annotated by {@code @Inject}.
+ *
+ * <p>This is appropriate for fields that are set reflectively, so there are no calls in client code
+ * that type inference can learn from. Examples of qualifiers that should be meta-annotated with
+ * {@code @IgnoreInWholeProgramInference} include <a
+ * href="https://docs.oracle.com/javaee/7/api/javax/inject/Inject.html">{@code @Inject}</a>, <a
+ * href="https://docs.oracle.com/javaee/7/api/javax/inject/Singleton.html">{@code @Singleton}</a>,
+ * and <a
+ * href="https://types.cs.washington.edu/plume-lib/api/plume/Option.html">{@code @Option}</a>.
+ *
+ * <p>2. As a field annotation indicating that no type qualifier will be inferred for the field it
+ * annotates.
+ *
+ * <p>See
+ * org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenes#updateFromFieldAssignment
+ *
+ * @checker_framework.manual #whole-program-inference-ignores-some-code Whole-program inference
+ *     ignores some code
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
+public @interface IgnoreInWholeProgramInference {}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/InheritedAnnotation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/InheritedAnnotation.java
new file mode 100644
index 0000000..cd1db57
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/InheritedAnnotation.java
@@ -0,0 +1,16 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * A meta-annotation that specifies if a declaration annotation should be inherited. This should not
+ * be written on type annotations.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface InheritedAnnotation {}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/InvisibleQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/InvisibleQualifier.java
new file mode 100644
index 0000000..1925dc0
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/InvisibleQualifier.java
@@ -0,0 +1,16 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * A meta-annotation indicating that an annotation is a type qualifier that should not be visible in
+ * output.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface InvisibleQualifier {}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/JavaExpression.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/JavaExpression.java
new file mode 100644
index 0000000..18e01ca
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/JavaExpression.java
@@ -0,0 +1,19 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * An annotation to use on an element of a dependent type qualifier to specify which elements of the
+ * annotation should be interpreted as Java expressions. The type of the element must be an array of
+ * Strings.
+ *
+ * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface JavaExpression {}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/LiteralKind.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/LiteralKind.java
new file mode 100644
index 0000000..7b3b291
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/LiteralKind.java
@@ -0,0 +1,60 @@
+package org.checkerframework.framework.qual;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Specifies kinds of literal trees.
+ *
+ * <p>These correspond to the *_LITERAL constants in {@link com.sun.source.tree.Tree.Kind}. However,
+ * that enum is in the tools.jar which is not on the user's classpath by default. This enum is used
+ * by meta-annotations, such as {@link QualifierForLiterals}, instead.
+ */
+// https://docs.oracle.com/javase/8/docs/technotes/tools/findingclasses.html#bootclass
+public enum LiteralKind {
+  /** Corresponds to {@link com.sun.source.tree.Tree.Kind#NULL_LITERAL} trees. */
+  NULL,
+  /** Corresponds to {@link com.sun.source.tree.Tree.Kind#INT_LITERAL} trees. */
+  INT,
+  /** Corresponds to {@link com.sun.source.tree.Tree.Kind#LONG_LITERAL} trees. */
+  LONG,
+  /** Corresponds to {@link com.sun.source.tree.Tree.Kind#FLOAT_LITERAL} trees. */
+  FLOAT,
+  /** Corresponds to {@link com.sun.source.tree.Tree.Kind#DOUBLE_LITERAL} trees. */
+  DOUBLE,
+  /** Corresponds to {@link com.sun.source.tree.Tree.Kind#BOOLEAN_LITERAL} trees. */
+  BOOLEAN,
+  /** Corresponds to {@link com.sun.source.tree.Tree.Kind#CHAR_LITERAL} trees. */
+  CHAR,
+  /** Corresponds to {@link com.sun.source.tree.Tree.Kind#STRING_LITERAL} trees. */
+  STRING,
+  /** Shorthand for all other LiteralKind constants, other than PRIMITIVE. */
+  ALL,
+  /** Shorthand for all primitive LiteralKind constants: INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR. */
+  PRIMITIVE;
+
+  /**
+   * Returns all LiteralKinds except for ALL and PRIMITIVE (which are shorthands for groups of other
+   * LiteralKinds).
+   *
+   * @return list of LiteralKinds except for ALL and PRIMITIVE
+   */
+  public static List<LiteralKind> allLiteralKinds() {
+    List<LiteralKind> list = new ArrayList<>(Arrays.asList(values()));
+    list.remove(ALL);
+    list.remove(PRIMITIVE);
+    return list;
+  }
+
+  /**
+   * Returns the primitive {@code LiteralKind}s: INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR. This is
+   * all LiteralKinds except for NULL, STRING, and ones that are shorthands for groups of other
+   * LiteralKinds.
+   *
+   * @return list of LiteralKinds except for NULL and STRING
+   */
+  public static List<LiteralKind> primitiveLiteralKinds() {
+    return Arrays.asList(INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR);
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/MonotonicQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/MonotonicQualifier.java
new file mode 100644
index 0000000..73b2fde
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/MonotonicQualifier.java
@@ -0,0 +1,43 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+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;
+
+/**
+ * A meta-annotation that indicates that a qualifier indicates that an expression goes monotonically
+ * from a type qualifier {@code T} to another qualifier {@code S}. The annotation {@code S} is
+ * called the <em>target</em> of the monotonic qualifier, and has to be indicated by {@link
+ * MonotonicQualifier#value()}.
+ *
+ * <p>This meta-annotation can be used on the declaration of the monotonic qualifier used for the
+ * type-system at hand, and is often called {@code MonoT} if the target is {@code T}. The subtyping
+ * hierarchy has to be defined as follows:
+ *
+ * <pre>{@code
+ * T <: MonoT <: S
+ * }</pre>
+ *
+ * where {@code <:} indicates the subtyping relation.
+ *
+ * <p>An expression of a monotonic type can only be assigned expressions of the target type {@code
+ * T}. This means that an expression of the monotonic type {@code MonoT} cannot be assigned to a
+ * variable of the same type.
+ *
+ * <p>Reading an expression of a monotonic type {@code MonoT} might always yield an expression of
+ * type {@code S}. However, once it has been observed that a variable has the target type {@code T},
+ * the monotonic property ensures that it will stay of type {@code T} for the rest of the program
+ * execution. This is even true if arbitrary other code is executed.
+ *
+ * <p>Note that variables of a monotonic type can be re-assigned arbitrarily often, but only with
+ * expressions of the target type.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.ANNOTATION_TYPE})
+public @interface MonotonicQualifier {
+  Class<? extends Annotation> value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/NoDefaultQualifierForUse.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/NoDefaultQualifierForUse.java
new file mode 100644
index 0000000..d1d1c76
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/NoDefaultQualifierForUse.java
@@ -0,0 +1,23 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+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;
+
+/**
+ * Declaration annotation applied to type declarations to specify that the annotation on the type
+ * declaration should not be applied to unannotated uses of the type. Instead, another default
+ * should be applied based on the location of the type or some other defaulting rule.
+ *
+ * @checker_framework.manual #creating-debugging-options Debugging Options
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface NoDefaultQualifierForUse {
+  /** Top qualifier in hierarchies for which no default annotation for use should be applied. */
+  Class<? extends Annotation>[] value() default {};
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/NoQualifierParameter.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/NoQualifierParameter.java
new file mode 100644
index 0000000..4dd970b
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/NoQualifierParameter.java
@@ -0,0 +1,41 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This is a declaration annotation that applies to type declarations. Some classes conceptually
+ * take a type qualifier parameter. This annotation indicates that this class and its subclasses
+ * explicitly do not do so. The only reason to write this annotation is when {@code
+ * HasQualifierParameter} is enabled by default, by writing {@code HasQualifierParameter} on a
+ * package.
+ *
+ * <p>When a class is {@code @NoQualifierParameter}, all its subclasses are as well.
+ *
+ * <p>One or more top qualifiers must be given for the hierarchies for which there are no qualifier
+ * parameters. This annotation may not be written on the same class as {@code HasQualifierParameter}
+ * for the same hierarchy.
+ *
+ * <p>It is an error for a superclass to be {@code @HasQualifierParameter} but a subclass to be
+ * {@code @NoQualifierParameter} for the same hierarchy.
+ *
+ * @see HasQualifierParameter
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface NoQualifierParameter {
+
+  /**
+   * Class of the top qualifier for the hierarchy for which this class has no qualifier parameter.
+   *
+   * @return the value
+   */
+  Class<? extends Annotation>[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java
new file mode 100644
index 0000000..adcc865
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java
@@ -0,0 +1,35 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+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;
+
+/**
+ * A meta-annotation that indicates that an annotation is a polymorphic type qualifier.
+ *
+ * <p>Any method written using a polymorphic type qualifier conceptually has two or more versions
+ * &mdash; one version for each qualifier in the qualifier hierarchy. In each version of the method,
+ * all instances of the polymorphic type qualifier are replaced by one of the other type qualifiers.
+ *
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.ANNOTATION_TYPE})
+@AnnotatedFor("nullness")
+public @interface PolymorphicQualifier {
+  /**
+   * Indicates which type system this annotation refers to (optional, and usually unnecessary). When
+   * multiple type hierarchies are supported by a single type system, then each polymorphic
+   * qualifier needs to indicate which sub-hierarchy it belongs to. Do so by passing the top
+   * qualifier from the given hierarchy.
+   *
+   * @return the top qualifier in the hierarchy of this qualifier
+   */
+  // We use the meaningless Annotation.class as default value and
+  // then ensure there is a single top qualifier to use.
+  Class<? extends Annotation> value() default Annotation.class;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/PostconditionAnnotation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/PostconditionAnnotation.java
new file mode 100644
index 0000000..67bb434
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/PostconditionAnnotation.java
@@ -0,0 +1,69 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+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;
+
+/**
+ * A meta-annotation that indicates that an annotation E is a postcondition annotation, i.e., E is a
+ * type-specialized version of {@link EnsuresQualifier} or of {@link EnsuresQualifier.List}.
+ *
+ * <ul>
+ *   <li>If E is a type-specialized version of {@link EnsuresQualifier}, its {@code value} element
+ *       must be an array of {@code String}s, analogous to {@link EnsuresQualifier#expression()}.
+ *   <li>If E is a type-specialized version of {@link EnsuresQualifier.List}, its {@code value}
+ *       element must be an array of postcondition annotations, analogous to {@link
+ *       EnsuresQualifier.List#value()}.
+ * </ul>
+ *
+ * <p>The established postcondition P has type specified by the {@code qualifier} field of this
+ * annotation.
+ *
+ * <p>If the annotation E has elements annotated by {@link QualifierArgument}, their values are
+ * copied to the arguments (elements) of annotation P with the same names. Different element names
+ * may be used in E and P, if a {@link QualifierArgument} in E gives the name of the corresponding
+ * element in P.
+ *
+ * <p>For example, the following code declares a postcondition annotation for the {@link
+ * org.checkerframework.common.value.qual.MinLen} qualifier:
+ *
+ * <pre><code>
+ * {@literal @}PostconditionAnnotation(qualifier = MinLen.class)
+ * {@literal @}Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+ *  public {@literal @}interface EnsuresMinLen {
+ *    String[] value();
+ *    {@literal @}QualifierArgument("value")
+ *    int targetValue() default 0;
+ * </code></pre>
+ *
+ * The {@code value} element holds the expressions to which the qualifier applies and {@code
+ * targetValue} holds the value for the {@code value} argument of {@link
+ * org.checkerframework.common.value.qual.MinLen}.
+ *
+ * <p>The following code then uses the annotation on a method that ensures {@code field} to be
+ * {@code @MinLen(2)} upon return.
+ *
+ * <pre><code>
+ * {@literal @}EnsuresMinLen(value = "field", targetValue = 2")
+ *  public void setField(String argument) {
+ *    field = "(" + argument + ")";
+ *  }
+ * </code></pre>
+ *
+ * @see EnsuresQualifier
+ * @see QualifierArgument
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.ANNOTATION_TYPE})
+public @interface PostconditionAnnotation {
+  /**
+   * The qualifier that will be established as a postcondition.
+   *
+   * <p>This element is analogous to {@link EnsuresQualifier#qualifier()}.
+   */
+  Class<? extends Annotation> qualifier();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/PreconditionAnnotation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/PreconditionAnnotation.java
new file mode 100644
index 0000000..95af23e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/PreconditionAnnotation.java
@@ -0,0 +1,61 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+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;
+
+/**
+ * A meta-annotation that indicates that an annotation R is a precondition annotation, i.e., R is a
+ * type-specialized version of {@link RequiresQualifier}. The value {@code qualifier} that is
+ * necessary for a precondition specified with {@link RequiresQualifier} is specified here with the
+ * value {@code qualifier}.
+ *
+ * <p>The annotation R that is meta-annotated as {@link PreconditionAnnotation} must have an element
+ * called {@code value} that is an array of {@code String}s of the same format and with the same
+ * meaning as the value {@code expression} in {@link RequiresQualifier}.
+ *
+ * <p>The established precondition P has type specified by the {@code qualifier} field of this
+ * annotation. If the annotation R has elements annotated by {@link QualifierArgument}, their values
+ * are copied to the arguments (elements) of annotation P with the same names. Different element
+ * names may be used in R and P, if a {@link QualifierArgument} in R gives the name of the
+ * corresponding element in P.
+ *
+ * <p>For example, the following code declares a precondition annotation for the {@link
+ * org.checkerframework.common.value.qual.MinLen} qualifier:
+ *
+ * <pre><code>
+ * {@literal @}PreconditionAnnotation(qualifier = MinLen.class)
+ * {@literal @}Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+ * public {@literal @}interface RequiresMinLen {
+ *   String[] value();
+ *   {@literal @}QualifierArgument("value")
+ *   int targetValue() default 0;
+ * </code></pre>
+ *
+ * The {@code value} element holds the expressions to which the qualifier applies and {@code
+ * targetValue} holds the value for the {@code value} argument of {@link
+ * org.checkerframework.common.value.qual.MinLen}.
+ *
+ * <p>The following code then uses the annotation on a method that requires {@code field} to be
+ * {@code @MinLen(2)} upon entry.
+ *
+ * <pre><code>
+ * {@literal @}RequiresMinLen(value = "field", targetValue = 2")
+ * public char getThirdCharacter() {
+ *   return field.charAt(2);
+ * }
+ * </code></pre>
+ *
+ * @see RequiresQualifier
+ * @see QualifierArgument
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.ANNOTATION_TYPE})
+public @interface PreconditionAnnotation {
+  /** The qualifier that must be established as a precondition. */
+  Class<? extends Annotation> qualifier();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/PurityUnqualified.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/PurityUnqualified.java
new file mode 100644
index 0000000..231f999
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/PurityUnqualified.java
@@ -0,0 +1,21 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * An annotation intended solely for representing an unqualified type in the qualifier hierarchy for
+ * the Purity Checker.
+ *
+ * @checker_framework.manual #purity-checker Purity Checker
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE) // do not store in .class file
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+@InvisibleQualifier
+public @interface PurityUnqualified {}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierArgument.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierArgument.java
new file mode 100644
index 0000000..e56a280
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierArgument.java
@@ -0,0 +1,46 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * An annotation to use on an element of a contract annotation to indicate that the element
+ * specifies the value of an argument of the qualifier. A contract annotation is an annotation
+ * declared with a {@link PreconditionAnnotation}, {@link PostconditionAnnotation}, or {@link
+ * ConditionalPostconditionAnnotation} meta-annotation. The meta-annotation specifies the qualifier
+ * taking the arguments.
+ *
+ * <p>For example, the following code declares a postcondition annotation for the {@link
+ * org.checkerframework.common.value.qual.MinLen} qualifier, allowing to specify its value:
+ *
+ * <pre><code>
+ * {@literal @}PostconditionAnnotation(qualifier = MinLen.class)
+ * {@literal @}Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+ * public {@literal @}interface EnsuresMinLen {
+ *   String[] value();
+ *   {@literal @}QualifierArgument("value")
+ *   int targetValue() default 0;
+ * </code></pre>
+ *
+ * The {@code value} element holds the expressions to which the qualifier applies and {@code
+ * targetValue} holds the value for the {@code value} argument of {@link
+ * org.checkerframework.common.value.qual.MinLen}.
+ *
+ * @see PostconditionAnnotation
+ * @see ConditionalPostconditionAnnotation
+ * @see PreconditionAnnotation
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface QualifierArgument {
+  /**
+   * Specifies the name of the argument of the qualifier, that is passed the values held in the
+   * annotated element. If the value is omitted or is empty, then the name of the annotated element
+   * is used as the argument name.
+   */
+  String value() default "";
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierForLiterals.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierForLiterals.java
new file mode 100644
index 0000000..95eb068
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierForLiterals.java
@@ -0,0 +1,34 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * A meta-annotation that indicates what qualifier should be given to literals.
+ * {@code @QualifierForLiterals} is equivalent to {@code @QualfierForLiterals(LiteralKind.ALL)}
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface QualifierForLiterals {
+  /**
+   * The kinds of literals whose types have this qualifier. For example, if {@code @MyAnno} is
+   * meta-annotated with {@code @QualifierForLiterals(LiteralKind.STRING)}, then a literal {@code
+   * String} constant such as {@code "hello world"} has type {@code @MyAnno String}, but occurrences
+   * of {@code String} in the source code are not affected.
+   *
+   * <p>For String literals, also see the {@link #stringPatterns} annotation element/field.
+   */
+  LiteralKind[] value() default {};
+
+  /**
+   * A string literal that matches any of these patterns has this qualifier.
+   *
+   * <p>If patterns for multiple qualifers match, then the string literal is given the greatest
+   * lower bound of all the matches.
+   */
+  String[] stringPatterns() default {};
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/RelevantJavaTypes.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/RelevantJavaTypes.java
new file mode 100644
index 0000000..667f408
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/RelevantJavaTypes.java
@@ -0,0 +1,43 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation on a SourceChecker subclass to specify which Java types are processed by the
+ * checker. In source code, the checker's type qualifiers may only appear on the given types and
+ * their subtypes. If a checker is not annotated with this annotation, then the checker's qualifiers
+ * may appear on any type.
+ *
+ * <p>This restriction is coarse-grained in that it applies to all type annotations for a given
+ * checker. To have different restrictions for different Java types, override {@code
+ * org.checkerframework.common.basetype.BaseTypeVisitor#visitAnnotatedType(List, Tree)}.
+ *
+ * <p>This is orthogonal to Java's {@code @Target} annotation; each enforces a different type of
+ * restriction on what can be written in source code.
+ *
+ * @checker_framework.manual #creating-relevant-java-types Relevant Java types
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface RelevantJavaTypes {
+  /**
+   * Classes where a type annotation supported by this checker may be written.
+   *
+   * <p>{@code Object[].class} means that the checker processes all array types. No distinction
+   * among array types is currently made, and no other array class should be supplied to
+   * {@code @RelevantJavaTypes}.
+   *
+   * <p>If a checker processes both primitive and boxed types, both must be specified separately,
+   * for example as {@code int.class} and {@code Integer.class}.
+   *
+   * @return classes where a type annotation supported by this checker may be written
+   */
+  Class<?>[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/RequiresQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/RequiresQualifier.java
new file mode 100644
index 0000000..f8d57e4
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/RequiresQualifier.java
@@ -0,0 +1,56 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A precondition annotation to indicate that a method requires certain expressions to have a
+ * certain qualifier at the time of the call to the method. The expressions for which the annotation
+ * must hold after the method's execution are indicated by {@code expression} and are specified
+ * using a string. The qualifier is specified by {@code qualifier}.
+ *
+ * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@Repeatable(RequiresQualifier.List.class)
+public @interface RequiresQualifier {
+  /**
+   * Returns the Java expressions for which the annotation need to be present.
+   *
+   * @return the Java expressions for which the annotation need to be present
+   * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
+   */
+  String[] expression();
+
+  /**
+   * Returns the qualifier that is required.
+   *
+   * @return the qualifier that is required
+   */
+  Class<? extends Annotation> qualifier();
+
+  /**
+   * A wrapper annotation that makes the {@link RequiresQualifier} annotation repeatable.
+   *
+   * <p>Programmers generally do not need to write this. It is created by Java when a programmer
+   * writes more than one {@link RequiresQualifier} annotation at the same location.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+  @interface List {
+    /**
+     * Returns the repeatable annotations.
+     *
+     * @return the repeatable annotations
+     */
+    RequiresQualifier[] value();
+  }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/StubFiles.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/StubFiles.java
new file mode 100644
index 0000000..25d74cb
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/StubFiles.java
@@ -0,0 +1,29 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * An annotation on a SourceChecker subclass to provide additional stub files that should be used in
+ * addition to {@code jdk.astub}. This allows larger compound checkers to separate the annotations
+ * into multiple files, or to provide annotations for non-JDK classes.
+ *
+ * <p>This annotation is not inherited. That means that if a checker with this annotation is
+ * subclassed, then this annotation must be copied to the subclass and the stub file must also be
+ * copied to the directory that contains the subclass.
+ *
+ * @checker_framework.manual #creating-a-checker-annotated-jdk Annotated JDK
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface StubFiles {
+  /**
+   * Stub file names. These are basenames: they include the extension (usually ".astub"), but no
+   * directory component.
+   */
+  String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java
new file mode 100644
index 0000000..66a5194
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java
@@ -0,0 +1,46 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+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;
+
+/**
+ * A meta-annotation to specify all the qualifiers that the given qualifier is an immediate subtype
+ * of. This provides a declarative way to specify the type qualifier hierarchy. (Alternatively, the
+ * hierarchy can be defined procedurally by subclassing {@link
+ * org.checkerframework.framework.type.QualifierHierarchy} or {@link
+ * org.checkerframework.framework.type.TypeHierarchy}.)
+ *
+ * <p>Example:
+ *
+ * <pre> @SubtypeOf( { Nullable.class } )
+ * public @interface NonNull {}
+ * </pre>
+ *
+ * <p>For the top qualifier in the qualifier hierarchy (i.e., the qualifier that is a supertype of
+ * all other qualifiers in the given hierarchy), use an empty set of values:
+ *
+ * <pre><code> @SubtypeOf( {} )
+ * public @interface Nullable {}
+ *
+ * &#64;SubtypeOf( {} )
+ * public @interface MaybeAliased {}
+ * </code></pre>
+ *
+ * <p>Together, all the {@code @SubtypeOf} meta-annotations fully describe the type qualifier
+ * hierarchy.
+ *
+ * @checker_framework.manual #creating-declarative-hierarchy Declaratively defining the qualifier
+ *     hierarchy
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+@AnnotatedFor("nullness")
+public @interface SubtypeOf {
+  /** An array of the supertype qualifiers of the annotated qualifier. */
+  Class<? extends Annotation>[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/TargetLocations.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/TargetLocations.java
new file mode 100644
index 0000000..704566a
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/TargetLocations.java
@@ -0,0 +1,37 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * NOTE: This meta-annotation is <a
+ * href="https://github.com/typetools/checker-framework/issues/1919"><b>not currently
+ * enforced</b></a>.
+ *
+ * <p>A meta-annotation that restricts the type-use locations where a type qualifier may be written.
+ * When written together with {@code @Target({ElementType.TYPE_USE})}, the given type qualifier may
+ * be written only at locations listed in the {@code @TargetLocations(...)} meta-annotation.
+ * {@code @Target({ElementType.TYPE_USE})} together with no {@code @TargetLocations(...)} means that
+ * the qualifier can be written on any type use.
+ *
+ * <p>This enables a type system designer to permit a qualifier to be written only in certain
+ * locations. For example, some type systems' top and bottom qualifier (such as {@link
+ * org.checkerframework.checker.nullness.qual.KeyForBottom}) should only be written on an explicit
+ * wildcard upper or lower bound. This meta-annotation is a declarative, coarse-grained approach to
+ * enable that. For finer-grained control, override {@code visit*} methods that visit trees in
+ * BaseTypeVisitor.
+ *
+ * <p>This annotation does not prevent the type system from defaulting, inferring, or computing the
+ * given type annotation at the given location. It only prevents users from writing an explicit
+ * annotation at the given location.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface TargetLocations {
+  /** Type uses at which the qualifier is permitted to be written in source code. */
+  TypeUseLocation[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeKind.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeKind.java
new file mode 100644
index 0000000..9adef28
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeKind.java
@@ -0,0 +1,75 @@
+package org.checkerframework.framework.qual;
+
+/**
+ * Specifies kinds of types.
+ *
+ * <p>These correspond to the constants in {@link javax.lang.model.type.TypeKind}. However, that
+ * enum is not available on Android and a warning is produced. So this enum is used instead.
+ *
+ * @checker_framework.manual #creating-type-introduction Declaratively specifying default
+ *     annotations
+ */
+public enum TypeKind {
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#BOOLEAN} types. */
+  BOOLEAN,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#BYTE} types. */
+  BYTE,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#SHORT} types. */
+  SHORT,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#INT} types. */
+  INT,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#LONG} types. */
+  LONG,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#CHAR} types. */
+  CHAR,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#FLOAT} types. */
+  FLOAT,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#DOUBLE} types. */
+  DOUBLE,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#VOID} types. */
+  VOID,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#NONE} types. */
+  NONE,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#NULL} types. */
+  NULL,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#ARRAY} types. */
+  ARRAY,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#DECLARED} types. */
+  DECLARED,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#ERROR} types. */
+  ERROR,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#TYPEVAR} types. */
+  TYPEVAR,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#WILDCARD} types. */
+  WILDCARD,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#PACKAGE} types. */
+  PACKAGE,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#EXECUTABLE} types. */
+  EXECUTABLE,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#OTHER} types. */
+  OTHER,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#UNION} types. */
+  UNION,
+
+  /** Corresponds to {@link javax.lang.model.type.TypeKind#INTERSECTION} types. */
+  INTERSECTION;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java
new file mode 100644
index 0000000..5ae57a2
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java
@@ -0,0 +1,90 @@
+package org.checkerframework.framework.qual;
+
+/**
+ * Specifies the locations to which a {@link DefaultQualifier} annotation applies.
+ *
+ * <p>The order of enums is important. Defaults are applied in this order. In particular, this means
+ * that OTHERWISE and ALL should be last.
+ *
+ * @see DefaultQualifier
+ * @see javax.lang.model.element.ElementKind
+ */
+public enum TypeUseLocation {
+
+  /** Apply default annotations to all unannotated raw types of fields. */
+  FIELD,
+
+  /**
+   * Apply default annotations to all unannotated raw types of local variables, casts, and
+   * instanceof.
+   *
+   * <p>TODO: should cast/instanceof be separated?
+   */
+  LOCAL_VARIABLE,
+
+  /** Apply default annotations to all unannotated raw types of resource variables. */
+  RESOURCE_VARIABLE,
+
+  /** Apply default annotations to all unannotated raw types of exception parameters. */
+  EXCEPTION_PARAMETER,
+
+  /** Apply default annotations to all unannotated raw types of receiver types. */
+  RECEIVER,
+
+  /**
+   * Apply default annotations to all unannotated raw types of formal parameter types, excluding the
+   * receiver.
+   */
+  PARAMETER,
+
+  /** Apply default annotations to all unannotated raw types of return types. */
+  RETURN,
+
+  /** Apply default annotations to all unannotated raw types of constructor result types. */
+  CONSTRUCTOR_RESULT,
+
+  /**
+   * Apply default annotations to unannotated lower bounds for type variables and wildcards both
+   * explicit ones in {@code extends} clauses, and implicit upper bounds when no explicit {@code
+   * extends} or {@code super} clause is present.
+   */
+  LOWER_BOUND,
+
+  /**
+   * Apply default annotations to unannotated, but explicit lower bounds: {@code <? super Object>}
+   */
+  EXPLICIT_LOWER_BOUND,
+
+  /**
+   * Apply default annotations to unannotated, but implicit lower bounds: {@code <T>} {@code <?>}.
+   */
+  IMPLICIT_LOWER_BOUND,
+
+  /**
+   * Apply default annotations to unannotated upper bounds: both explicit ones in {@code extends}
+   * clauses, and implicit upper bounds when no explicit {@code extends} or {@code super} clause is
+   * present.
+   *
+   * <p>Especially useful for parametrized classes that provide a lot of static methods with the
+   * same generic parameters as the class.
+   */
+  UPPER_BOUND,
+
+  /**
+   * Apply default annotations to unannotated, but explicit upper bounds: {@code <T extends
+   * Object>}.
+   */
+  EXPLICIT_UPPER_BOUND,
+
+  /** Apply default annotations to unannotated type variables: {@code <T>}. */
+  IMPLICIT_UPPER_BOUND,
+
+  /** Apply if nothing more concrete is provided. TODO: clarify relation to ALL. */
+  OTHERWISE,
+
+  /**
+   * Apply default annotations to all type uses other than uses of type parameters. Does not allow
+   * any of the other constants. Usually you want OTHERWISE.
+   */
+  ALL;
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/Unused.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/Unused.java
new file mode 100644
index 0000000..e431e76
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/Unused.java
@@ -0,0 +1,47 @@
+package org.checkerframework.framework.qual;
+
+import java.lang.annotation.Annotation;
+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;
+
+/**
+ * Declares that the field may not be accessed if the receiver is of the specified qualifier type
+ * (or any supertype).
+ *
+ * <p>This property is verified by the checker that type-checks the {@code when} element value
+ * qualifier.
+ *
+ * <p><b>Example</b> Consider a class, {@code Table}, with a locking field, {@code lock}. The lock
+ * is used when a {@code Table} instance is shared across threads. When running in a local thread,
+ * the {@code lock} field ought not to be used.
+ *
+ * <p>You can declare this behavior in the following way:
+ *
+ * <pre>{@code
+ * class Table {
+ *   private @Unused(when=LocalToThread.class) final Lock lock;
+ *   ...
+ * }
+ * }</pre>
+ *
+ * The checker for {@code @LocalToThread} would issue an error for the following code:
+ *
+ * <pre>  @LocalToThread Table table = ...;
+ *   ... table.lock ...;
+ * </pre>
+ *
+ * @checker_framework.manual #unused-fields Unused fields
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface Unused {
+  /**
+   * The field that is annotated with @Unused may not be accessed via a receiver that is annotated
+   * with the "when" annotation.
+   */
+  Class<? extends Annotation> when();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java
new file mode 100644
index 0000000..f1f3342
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java
@@ -0,0 +1,49 @@
+package org.checkerframework.framework.qual;
+
+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;
+
+/**
+ * A meta-annotation applied to the declaration of a type qualifier. It specifies that the
+ * annotation should be the upper bound for
+ *
+ * <ul>
+ *   <li>all uses of a particular type, and
+ *   <li>all uses of a particular kind of type.
+ * </ul>
+ *
+ * An example is the declaration
+ *
+ * <pre><code>
+ * {@literal @}DefaultFor(classes=String.class)
+ * {@literal @}interface MyAnno {}
+ * </code></pre>
+ *
+ * <p>The upper bound applies to every occurrence of the given classes and also to every occurrence
+ * of the given type kinds.
+ *
+ * @checker_framework.manual #upper-bound-for-use Upper bound of qualifiers on uses of a given type
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface UpperBoundFor {
+  /**
+   * Returns {@link TypeKind}s of types that get an upper bound. The meta-annotated annotation is
+   * the upper bound.
+   *
+   * @return {@link TypeKind}s of types that get an upper bound
+   */
+  TypeKind[] typeKinds() default {};
+
+  /**
+   * Returns {@link Class}es that should get an upper bound. The meta-annotated annotation is the
+   * upper bound.
+   *
+   * @return {@link Class}es that get an upper bound
+   */
+  Class<?>[] types() default {};
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/package-info.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/package-info.java
new file mode 100644
index 0000000..8fc168e
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/package-info.java
@@ -0,0 +1,10 @@
+/**
+ * Contains the basic annotations to be used by all type systems and meta-annotations to qualify
+ * annotations (qualifiers).
+ *
+ * <p>They may serve as documentation for the type qualifiers, and aid the Checker Framework to
+ * infer the relations between the type qualifiers.
+ *
+ * @checker_framework.manual #creating-a-checker Writing a checker
+ */
+package org.checkerframework.framework.qual;
diff --git a/checker-util/LICENSE.txt b/checker-util/LICENSE.txt
new file mode 100644
index 0000000..47fa719
--- /dev/null
+++ b/checker-util/LICENSE.txt
@@ -0,0 +1,22 @@
+Checker Framework utilities
+Copyright 2004-present by the Checker Framework developers
+
+MIT License:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/checker-util/build.gradle b/checker-util/build.gradle
new file mode 100644
index 0000000..37230f7
--- /dev/null
+++ b/checker-util/build.gradle
@@ -0,0 +1,38 @@
+plugins {
+    id 'java-library'
+}
+
+dependencies {
+    api project(':checker-qual')
+    // Don't add implementation dependencies; checker-util.jar should have no dependencies.
+
+    testImplementation group: 'junit', name: 'junit', version: '4.13.2'
+}
+apply from: rootProject.file("gradle-mvn-push.gradle")
+
+/** Adds information to the publication for uploading to Maven repositories. */
+final checkerUtilPom(publication) {
+    sharedPublicationConfiguration(publication)
+    publication.from components.java
+    publication.pom {
+        name = 'Checker Util'
+        description = 'checker-util contains utility classes for programmers to use at run time.'
+        licenses {
+            license {
+                name = 'The MIT License'
+                url = 'http://opensource.org/licenses/MIT'
+                distribution = 'repo'
+            }
+        }
+    }
+}
+publishing {
+    publications {
+        checkerUtil(MavenPublication) {
+            checkerUtilPom it
+        }
+    }
+}
+signing {
+    sign publishing.publications.checkerUtil
+}
diff --git a/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java b/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java
new file mode 100644
index 0000000..c456124
--- /dev/null
+++ b/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java
@@ -0,0 +1,320 @@
+package org.checkerframework.checker.formatter.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.IllegalFormatConversionException;
+import java.util.IllegalFormatException;
+import java.util.Map;
+import java.util.MissingFormatArgumentException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.checkerframework.checker.formatter.qual.ConversionCategory;
+import org.checkerframework.checker.formatter.qual.ReturnsFormat;
+import org.checkerframework.checker.regex.qual.Regex;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+/** This class provides a collection of utilities to ease working with format strings. */
+@AnnotatedFor("nullness")
+public class FormatUtil {
+
+  /**
+   * A representation of a format specifier, which is represented by "%..." in the format string.
+   * Indicates how to convert a value into a string.
+   */
+  private static class Conversion {
+    /** The index in the argument list. */
+    private final int index;
+    /** The conversion category. */
+    private final ConversionCategory cath;
+
+    /**
+     * Construct a new Conversion.
+     *
+     * @param index the index in the argument list
+     * @param c the conversion character
+     */
+    public Conversion(char c, int index) {
+      this.index = index;
+      this.cath = ConversionCategory.fromConversionChar(c);
+    }
+
+    /**
+     * Returns the index in the argument list.
+     *
+     * @return the index in the argument list
+     */
+    int index() {
+      return index;
+    }
+
+    /**
+     * Returns the conversion category.
+     *
+     * @return the conversion category
+     */
+    ConversionCategory category() {
+      return cath;
+    }
+  }
+
+  /**
+   * Returns the first argument if the format string is satisfiable, and if the format's parameters
+   * match the passed {@link ConversionCategory}s. Otherwise throws an exception.
+   *
+   * @param format a format string
+   * @param cc an array of conversion categories
+   * @return the {@code format} argument
+   * @throws IllegalFormatException if the format string is incompatible with the conversion
+   *     categories
+   */
+  // TODO introduce more such functions, see RegexUtil for examples
+  @ReturnsFormat
+  public static String asFormat(String format, ConversionCategory... cc)
+      throws IllegalFormatException {
+    ConversionCategory[] fcc = formatParameterCategories(format);
+    if (fcc.length != cc.length) {
+      throw new ExcessiveOrMissingFormatArgumentException(cc.length, fcc.length);
+    }
+
+    for (int i = 0; i < cc.length; i++) {
+      if (cc[i] != fcc[i]) {
+        throw new IllegalFormatConversionCategoryException(cc[i], fcc[i]);
+      }
+    }
+
+    return format;
+  }
+
+  /**
+   * Throws an exception if the format is not syntactically valid.
+   *
+   * @param format a format string
+   * @throws IllegalFormatException if the format string is invalid
+   */
+  public static void tryFormatSatisfiability(String format) throws IllegalFormatException {
+    @SuppressWarnings({
+      "unused", // called for side effect, to see if it throws an exception
+      "nullness:argument", // it's not documented, but String.format permits
+      // a null array, which it treats as matching any format string (null is supplied to each
+      // format specifier).
+      "formatter:format.string", // this is a test of format string validity
+    })
+    String unused = String.format(format, (Object[]) null);
+  }
+
+  /**
+   * Returns a {@link ConversionCategory} for every conversion found in the format string.
+   *
+   * <p>Throws an exception if the format is not syntactically valid.
+   */
+  public static ConversionCategory[] formatParameterCategories(String format)
+      throws IllegalFormatException {
+    tryFormatSatisfiability(format);
+
+    int last = -1; // index of last argument referenced
+    int lasto = -1; // last ordinary index
+    int maxindex = -1;
+
+    Conversion[] cs = parse(format);
+    Map<Integer, ConversionCategory> conv = new HashMap<>(cs.length);
+
+    for (Conversion c : cs) {
+      int index = c.index();
+      switch (index) {
+        case -1: // relative index
+          break;
+        case 0: // ordinary index
+          lasto++;
+          last = lasto;
+          break;
+        default: // explicit index
+          last = index - 1;
+          break;
+      }
+      maxindex = Math.max(maxindex, last);
+      Integer lastKey = last;
+      conv.put(
+          last,
+          ConversionCategory.intersect(
+              conv.containsKey(lastKey) ? conv.get(lastKey) : ConversionCategory.UNUSED,
+              c.category()));
+    }
+
+    ConversionCategory[] res = new ConversionCategory[maxindex + 1];
+    for (int i = 0; i <= maxindex; ++i) {
+      Integer key = i; // autoboxing prevents recognizing that containsKey => get() != null
+      res[i] = conv.containsKey(key) ? conv.get(key) : ConversionCategory.UNUSED;
+    }
+    return res;
+  }
+
+  /**
+   * A regex that matches a format specifier. Its syntax is specified in the See <a
+   * href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html#syntax">{@code
+   * Formatter} documentation</a>.
+   *
+   * <pre>
+   * %[argument_index$][flags][width][.precision][t]conversion
+   * group 1            2      3      4           5 6
+   * </pre>
+   *
+   * For dates and times, the [t] is required and precision must not be provided. For types other
+   * than dates and times, the [t] must not be provided.
+   */
+  private static final @Regex(6) String formatSpecifier =
+      "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
+  /** The capturing group for the optional {@code t} character. */
+  private static final int formatSpecifierT = 5;
+  /**
+   * The capturing group for the last character in a format specifier, which is the conversion
+   * character unless the {@code t} character was given.
+   */
+  private static final int formatSpecifierConversion = 6;
+
+  /**
+   * A Pattern that matches a format specifier.
+   *
+   * @see #formatSpecifier
+   */
+  private static @Regex(6) Pattern fsPattern = Pattern.compile(formatSpecifier);
+
+  /**
+   * Return the index, in the argument list, of the value that will be formatted by the matched
+   * format specifier.
+   *
+   * @param m a matcher that matches a format specifier
+   * @return the index of the argument to format
+   */
+  private static int indexFromFormat(Matcher m) {
+    int index;
+    String s = m.group(1);
+    if (s != null) { // explicit index
+      index = Integer.parseInt(s.substring(0, s.length() - 1));
+    } else {
+      String group2 = m.group(2); // not @Deterministic, so extract into local var
+      if (group2 != null && group2.contains(String.valueOf('<'))) {
+        index = -1; // relative index
+      } else {
+        index = 0; // ordinary index
+      }
+    }
+    return index;
+  }
+
+  /**
+   * Returns the conversion character from a format specifier..
+   *
+   * @param m a matcher that matches a format specifier
+   * @return the conversion character from the format specifier
+   */
+  @SuppressWarnings(
+      "nullness:dereference.of.nullable") // group formatSpecifierConversion always exists
+  private static char conversionCharFromFormat(@Regex(6) Matcher m) {
+    String tGroup = m.group(formatSpecifierT);
+    if (tGroup != null) {
+      return tGroup.charAt(0); // This is the letter "t" or "T".
+    } else {
+      return m.group(formatSpecifierConversion).charAt(0);
+    }
+  }
+
+  /**
+   * Return the conversion character that is in the given format specifier.
+   *
+   * @param formatSpecifier a <a
+   *     href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html#syntax">format
+   *     specifier</a>
+   * @return the conversion character that is in the given format specifier
+   * @deprecated This method is public only for testing. Use private method {@code
+   *     #conversionCharFromFormat(Matcher)}.
+   */
+  @Deprecated // used only for testing.  Use conversionCharFromFormat(Matcher).
+  public static char conversionCharFromFormat(String formatSpecifier) {
+    Matcher m = fsPattern.matcher(formatSpecifier);
+    assert m.find();
+    return conversionCharFromFormat(m);
+  }
+
+  /**
+   * Parse the given format string, return information about its format specifiers.
+   *
+   * @param format a format string
+   * @return the list of Conversions from the format specifiers in the format string
+   */
+  private static Conversion[] parse(String format) {
+    ArrayList<Conversion> cs = new ArrayList<>();
+    @Regex(7) Matcher m = fsPattern.matcher(format);
+    while (m.find()) {
+      char c = conversionCharFromFormat(m);
+      switch (c) {
+        case '%':
+        case 'n':
+          break;
+        default:
+          cs.add(new Conversion(c, indexFromFormat(m)));
+      }
+    }
+    return cs.toArray(new Conversion[cs.size()]);
+  }
+
+  public static class ExcessiveOrMissingFormatArgumentException
+      extends MissingFormatArgumentException {
+    private static final long serialVersionUID = 17000126L;
+
+    private final int expected;
+    private final int found;
+
+    /**
+     * Constructs an instance of this class with the actual argument length and the expected one.
+     */
+    public ExcessiveOrMissingFormatArgumentException(int expected, int found) {
+      super("-");
+      this.expected = expected;
+      this.found = found;
+    }
+
+    public int getExpected() {
+      return expected;
+    }
+
+    public int getFound() {
+      return found;
+    }
+
+    @Override
+    public String getMessage() {
+      return String.format("Expected %d arguments but found %d.", expected, found);
+    }
+  }
+
+  public static class IllegalFormatConversionCategoryException
+      extends IllegalFormatConversionException {
+    private static final long serialVersionUID = 17000126L;
+
+    private final ConversionCategory expected;
+    private final ConversionCategory found;
+
+    /** Constructs an instance of this class with the mismatched conversion and the expected one. */
+    public IllegalFormatConversionCategoryException(
+        ConversionCategory expected, ConversionCategory found) {
+      super(
+          expected.chars == null || expected.chars.length() == 0 ? '-' : expected.chars.charAt(0),
+          found.types == null ? Object.class : found.types[0]);
+      this.expected = expected;
+      this.found = found;
+    }
+
+    public ConversionCategory getExpected() {
+      return expected;
+    }
+
+    public ConversionCategory getFound() {
+      return found;
+    }
+
+    @Override
+    public String getMessage() {
+      return String.format("Expected category %s but found %s.", expected, found);
+    }
+  }
+}
diff --git a/checker-util/src/main/java/org/checkerframework/checker/i18nformatter/util/I18nFormatUtil.java b/checker-util/src/main/java/org/checkerframework/checker/i18nformatter/util/I18nFormatUtil.java
new file mode 100644
index 0000000..c47f42c
--- /dev/null
+++ b/checker-util/src/main/java/org/checkerframework/checker/i18nformatter/util/I18nFormatUtil.java
@@ -0,0 +1,397 @@
+package org.checkerframework.checker.i18nformatter.util;
+
+import java.text.ChoiceFormat;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.IllegalFormatException;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import org.checkerframework.checker.i18nformatter.qual.I18nChecksFormat;
+import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
+import org.checkerframework.checker.i18nformatter.qual.I18nValidFormat;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+/**
+ * This class provides a collection of utilities to ease working with i18n format strings.
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+@AnnotatedFor("nullness")
+public class I18nFormatUtil {
+
+  /**
+   * Throws an exception if the format is not syntactically valid.
+   *
+   * @param format the format string to parse
+   */
+  @SuppressWarnings("nullness:argument") // It's not documented, but passing null as the
+  // argument array is supported.
+  public static void tryFormatSatisfiability(String format) throws IllegalFormatException {
+    MessageFormat.format(format, (Object[]) null);
+  }
+
+  /**
+   * Returns a {@link I18nConversionCategory} for every conversion found in the format string.
+   *
+   * @param format the format string to parse
+   * @throws IllegalFormatException if the format is not syntactically valid
+   */
+  public static I18nConversionCategory[] formatParameterCategories(String format)
+      throws IllegalFormatException {
+    tryFormatSatisfiability(format);
+    I18nConversion[] cs = MessageFormatParser.parse(format);
+
+    int maxIndex = -1;
+    Map<Integer, I18nConversionCategory> conv = new HashMap<>(cs.length);
+
+    for (I18nConversion c : cs) {
+      int index = c.index;
+      Integer indexKey = index;
+      conv.put(
+          indexKey,
+          I18nConversionCategory.intersect(
+              c.category,
+              conv.containsKey(indexKey) ? conv.get(indexKey) : I18nConversionCategory.UNUSED));
+      maxIndex = Math.max(maxIndex, index);
+    }
+
+    I18nConversionCategory[] res = new I18nConversionCategory[maxIndex + 1];
+    for (int i = 0; i <= maxIndex; i++) {
+      Integer indexKey = i;
+      res[i] = conv.containsKey(indexKey) ? conv.get(indexKey) : I18nConversionCategory.UNUSED;
+    }
+    return res;
+  }
+
+  /**
+   * Returns true if the format string is satisfiable, and if the format's parameters match the
+   * passed {@link I18nConversionCategory}s. Otherwise an error is thrown.
+   *
+   * @param format a format string
+   * @param cc a list of expected categories for the string's format specifiers
+   * @return true if the format string's specifiers are the given categories, in order
+   */
+  // TODO introduce more such functions, see RegexUtil for examples
+  @I18nChecksFormat
+  public static boolean hasFormat(String format, I18nConversionCategory... cc) {
+    I18nConversionCategory[] fcc = formatParameterCategories(format);
+    if (fcc.length != cc.length) {
+      return false;
+    }
+
+    for (int i = 0; i < cc.length; i++) {
+      if (!I18nConversionCategory.isSubsetOf(cc[i], fcc[i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @I18nValidFormat
+  public static boolean isFormat(String format) {
+    try {
+      formatParameterCategories(format);
+    } catch (Exception e) {
+      return false;
+    }
+    return true;
+  }
+
+  private static class I18nConversion {
+    public int index;
+    public I18nConversionCategory category;
+
+    public I18nConversion(int index, I18nConversionCategory category) {
+      this.index = index;
+      this.category = category;
+    }
+
+    @Override
+    public String toString() {
+      return category.toString() + "(index: " + index + ")";
+    }
+  }
+
+  private static class MessageFormatParser {
+
+    public static int maxOffset;
+
+    /** The locale to use for formatting numbers and dates. Is set in {@link #parse}. */
+    private static @MonotonicNonNull Locale locale;
+
+    /** An array of formatters, which are used to format the arguments. Is set in {@link #parse}. */
+    private static @MonotonicNonNull List<I18nConversionCategory> categories;
+
+    /**
+     * The argument numbers corresponding to each formatter. (The formatters are stored in the order
+     * they occur in the pattern, not in the order in which the arguments are specified.) Is set in
+     * {@link #parse}.
+     */
+    private static @MonotonicNonNull List<Integer> argumentIndices;
+
+    // I think this means the number of format specifiers in the format string.
+    /** The number of subformats. */
+    private static int numFormat;
+
+    // Indices for segments
+    private static final int SEG_RAW = 0;
+    private static final int SEG_INDEX = 1;
+    private static final int SEG_TYPE = 2;
+    private static final int SEG_MODIFIER = 3; // modifier or subformat
+
+    // Indices for type keywords
+    private static final int TYPE_NULL = 0;
+    private static final int TYPE_NUMBER = 1;
+    private static final int TYPE_DATE = 2;
+    private static final int TYPE_TIME = 3;
+    private static final int TYPE_CHOICE = 4;
+
+    private static final String[] TYPE_KEYWORDS = {"", "number", "date", "time", "choice"};
+
+    // Indices for number modifiers
+    private static final int MODIFIER_DEFAULT = 0; // common in number and date-time
+    private static final int MODIFIER_CURRENCY = 1;
+    private static final int MODIFIER_PERCENT = 2;
+    private static final int MODIFIER_INTEGER = 3;
+
+    private static final String[] NUMBER_MODIFIER_KEYWORDS = {"", "currency", "percent", "integer"};
+
+    private static final String[] DATE_TIME_MODIFIER_KEYWORDS = {
+      "", "short", "medium", "long", "full"
+    };
+
+    @EnsuresNonNull({"categories", "argumentIndices", "locale"})
+    public static I18nConversion[] parse(String pattern) {
+      MessageFormatParser.categories = new ArrayList<>();
+      MessageFormatParser.argumentIndices = new ArrayList<>();
+      MessageFormatParser.locale = Locale.getDefault(Locale.Category.FORMAT);
+      applyPattern(pattern);
+
+      I18nConversion[] ret = new I18nConversion[MessageFormatParser.numFormat];
+      for (int i = 0; i < MessageFormatParser.numFormat; i++) {
+        ret[i] = new I18nConversion(argumentIndices.get(i), categories.get(i));
+      }
+      return ret;
+    }
+
+    @SuppressWarnings("nullness:dereference.of.nullable") // complex rules for segments[i]
+    @RequiresNonNull({"argumentIndices", "categories", "locale"})
+    private static void applyPattern(String pattern) {
+      @Nullable StringBuilder[] segments = new StringBuilder[4];
+      // Allocate only segments[SEG_RAW] here. The rest are
+      // allocated on demand.
+      segments[SEG_RAW] = new StringBuilder();
+
+      int part = SEG_RAW;
+      MessageFormatParser.numFormat = 0;
+      boolean inQuote = false;
+      int braceStack = 0;
+      maxOffset = -1;
+      for (int i = 0; i < pattern.length(); ++i) {
+        char ch = pattern.charAt(i);
+        if (part == SEG_RAW) {
+          if (ch == '\'') {
+            if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'') {
+              segments[part].append(ch); // handle doubles
+              ++i;
+            } else {
+              inQuote = !inQuote;
+            }
+          } else if (ch == '{' && !inQuote) {
+            part = SEG_INDEX;
+            if (segments[SEG_INDEX] == null) {
+              segments[SEG_INDEX] = new StringBuilder();
+            }
+          } else {
+            segments[part].append(ch);
+          }
+        } else {
+          if (inQuote) { // just copy quotes in parts
+            segments[part].append(ch);
+            if (ch == '\'') {
+              inQuote = false;
+            }
+          } else {
+            switch (ch) {
+              case ',':
+                if (part < SEG_MODIFIER) {
+                  if (segments[++part] == null) {
+                    segments[part] = new StringBuilder();
+                  }
+                } else {
+                  segments[part].append(ch);
+                }
+                break;
+              case '{':
+                ++braceStack;
+                segments[part].append(ch);
+                break;
+              case '}':
+                if (braceStack == 0) {
+                  part = SEG_RAW;
+                  makeFormat(numFormat, segments);
+                  numFormat++;
+                  // throw away other segments
+                  segments[SEG_INDEX] = null;
+                  segments[SEG_TYPE] = null;
+                  segments[SEG_MODIFIER] = null;
+                } else {
+                  --braceStack;
+                  segments[part].append(ch);
+                }
+                break;
+              case ' ':
+                // Skip any leading space chars for SEG_TYPE.
+                if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) {
+                  segments[part].append(ch);
+                }
+                break;
+              case '\'':
+                inQuote = true;
+                segments[part].append(ch);
+                break;
+              default:
+                segments[part].append(ch);
+                break;
+            }
+          }
+        }
+      }
+      if (braceStack == 0 && part != 0) {
+        maxOffset = -1;
+        throw new IllegalArgumentException("Unmatched braces in the pattern");
+      }
+    }
+
+    /** Side-effects {@code categories} field, adding to it an I18nConversionCategory. */
+    @RequiresNonNull({"argumentIndices", "categories", "locale"})
+    private static void makeFormat(int offsetNumber, @Nullable StringBuilder[] textSegments) {
+      String[] segments = new String[textSegments.length];
+      for (int i = 0; i < textSegments.length; i++) {
+        StringBuilder oneseg = textSegments[i];
+        segments[i] = (oneseg != null) ? oneseg.toString() : "";
+      }
+
+      // get the argument number
+      int argumentNumber;
+      try {
+        argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always
+        // unlocalized!
+      } catch (NumberFormatException e) {
+        throw new IllegalArgumentException(
+            "can't parse argument number: " + segments[SEG_INDEX], e);
+      }
+      if (argumentNumber < 0) {
+        throw new IllegalArgumentException("negative argument number: " + argumentNumber);
+      }
+
+      int oldMaxOffset = maxOffset;
+      maxOffset = offsetNumber;
+      argumentIndices.add(argumentNumber);
+
+      // now get the format
+      I18nConversionCategory category = null;
+      if (segments[SEG_TYPE].length() != 0) {
+        int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS);
+        switch (type) {
+          case TYPE_NULL:
+            category = I18nConversionCategory.GENERAL;
+            break;
+          case TYPE_NUMBER:
+            switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) {
+              case MODIFIER_DEFAULT:
+              case MODIFIER_CURRENCY:
+              case MODIFIER_PERCENT:
+              case MODIFIER_INTEGER:
+                break;
+              default: // DecimalFormat pattern
+                try {
+                  new DecimalFormat(
+                      segments[SEG_MODIFIER], DecimalFormatSymbols.getInstance(locale));
+                } catch (IllegalArgumentException e) {
+                  maxOffset = oldMaxOffset;
+                  // invalid decimal subformat pattern
+                  throw e;
+                }
+                break;
+            }
+            category = I18nConversionCategory.NUMBER;
+            break;
+          case TYPE_DATE:
+          case TYPE_TIME:
+            int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS);
+            if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) {
+              // nothing to do
+            } else {
+              // SimpleDateFormat pattern
+              try {
+                new SimpleDateFormat(segments[SEG_MODIFIER], locale);
+              } catch (IllegalArgumentException e) {
+                maxOffset = oldMaxOffset;
+                // invalid date subformat pattern
+                throw e;
+              }
+            }
+            category = I18nConversionCategory.DATE;
+            break;
+          case TYPE_CHOICE:
+            if (segments[SEG_MODIFIER].length() == 0) {
+              throw new IllegalArgumentException(
+                  "Choice Pattern requires Subformat Pattern: " + segments[SEG_MODIFIER]);
+            }
+            try {
+              // ChoiceFormat pattern
+              new ChoiceFormat(segments[SEG_MODIFIER]);
+            } catch (Exception e) {
+              maxOffset = oldMaxOffset;
+              // invalid choice subformat pattern
+              throw new IllegalArgumentException(
+                  "Choice Pattern incorrect: " + segments[SEG_MODIFIER], e);
+            }
+            category = I18nConversionCategory.NUMBER;
+            break;
+          default:
+            maxOffset = oldMaxOffset;
+            throw new IllegalArgumentException("unknown format type: " + segments[SEG_TYPE]);
+        }
+      } else {
+        category = I18nConversionCategory.GENERAL;
+      }
+      categories.add(category);
+    }
+
+    /**
+     * Return the index of s in list. If not found, return the index of
+     * s.trim().toLowerCase(Locale.ROOT) in list. If still not found, return -1.
+     */
+    private static final int findKeyword(String s, String[] list) {
+      for (int i = 0; i < list.length; ++i) {
+        if (s.equals(list[i])) {
+          return i;
+        }
+      }
+
+      // Try trimmed lowercase.
+      @SuppressWarnings("interning:assignment") // test if value changed
+      @InternedDistinct String ls = s.trim().toLowerCase(Locale.ROOT);
+      if (ls != s) { // Don't loop if the string trim().toLowerCase returned the same object.
+        for (int i = 0; i < list.length; ++i) {
+          if (ls.equals(list[i])) {
+            return i;
+          }
+        }
+      }
+      return -1;
+    }
+  }
+}
diff --git a/checker-util/src/main/java/org/checkerframework/checker/nullness/util/NullnessUtil.java b/checker-util/src/main/java/org/checkerframework/checker/nullness/util/NullnessUtil.java
new file mode 100644
index 0000000..b52a4b5
--- /dev/null
+++ b/checker-util/src/main/java/org/checkerframework/checker/nullness/util/NullnessUtil.java
@@ -0,0 +1,313 @@
+package org.checkerframework.checker.nullness.util;
+
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+/**
+ * Utility class for the Nullness Checker.
+ *
+ * <p>To avoid the need to write the NullnessUtil class name, do:
+ *
+ * <pre>import static org.checkerframework.checker.nullness.util.NullnessUtil.castNonNull;</pre>
+ *
+ * or
+ *
+ * <pre>import static org.checkerframework.checker.nullness.util.NullnessUtil.*;</pre>
+ *
+ * <p><b>Runtime Dependency</b>: If you use this class, you must distribute (or link to) {@code
+ * checker-qual.jar}, along with your binaries. Or, you can can copy this class into your own
+ * project.
+ */
+@SuppressWarnings({
+  "nullness", // Nullness utilities are trusted regarding nullness.
+  "cast" // Casts look redundant if Nullness Checker is not run.
+})
+@AnnotatedFor("nullness")
+public final class NullnessUtil {
+
+  private NullnessUtil() {
+    throw new AssertionError("shouldn't be instantiated");
+  }
+
+  /**
+   * A method that suppresses warnings from the Nullness Checker.
+   *
+   * <p>The method takes a possibly-null reference, unsafely casts it to have the @NonNull type
+   * qualifier, and returns it. The Nullness Checker considers both the return value, and also the
+   * argument, to be non-null after the method call. Therefore, the {@code castNonNull} method can
+   * be used either as a cast expression or as a statement. The Nullness Checker issues no warnings
+   * in any of the following code:
+   *
+   * <pre><code>
+   *   // one way to use as a cast:
+   *  {@literal @}NonNull String s = castNonNull(possiblyNull1);
+   *
+   *   // another way to use as a cast:
+   *   castNonNull(possiblyNull2).toString();
+   *
+   *   // one way to use as a statement:
+   *   castNonNull(possiblyNull3);
+   *   possiblyNull3.toString();`
+   * </code></pre>
+   *
+   * The {@code castNonNull} method is intended to be used in situations where the programmer
+   * definitively knows that a given reference is not null, but the type system is unable to make
+   * this deduction. It is not intended for defensive programming, in which a programmer cannot
+   * prove that the value is not null but wishes to have an earlier indication if it is. See the
+   * Checker Framework Manual for further discussion.
+   *
+   * <p>The method throws {@link AssertionError} if Java assertions are enabled and the argument is
+   * {@code null}. If the exception is ever thrown, then that indicates that the programmer misused
+   * the method by using it in a circumstance where its argument can be null.
+   *
+   * @param <T> the type of the reference
+   * @param ref a reference of @Nullable type, that is non-null at run time
+   * @return the argument, casted to have the type qualifier @NonNull
+   */
+  @EnsuresNonNull("#1")
+  public static <T extends @Nullable Object> @NonNull T castNonNull(@Nullable T ref) {
+    assert ref != null : "Misuse of castNonNull: called with a null argument";
+    return (@NonNull T) ref;
+  }
+
+  /**
+   * Suppress warnings from the Nullness Checker, with a custom error message. See {@link
+   * #castNonNull(Object)} for documentation.
+   *
+   * @see #castNonNull(Object)
+   * @param <T> the type of the reference
+   * @param ref a reference of @Nullable type, that is non-null at run time
+   * @param message text to include if this method is misused
+   * @return the argument, casted to have the type qualifier @NonNull
+   */
+  public static @EnsuresNonNull("#1") <T extends @Nullable Object> @NonNull T castNonNull(
+      @Nullable T ref, String message) {
+    assert ref != null : "Misuse of castNonNull: called with a null argument: " + message;
+    return (@NonNull T) ref;
+  }
+
+  /**
+   * Like castNonNull, but whereas that method only checks and casts the reference itself, this
+   * traverses all levels of the argument array. The array is recursively checked to ensure that all
+   * elements at every array level are non-null.
+   *
+   * @param <T> the component type of the array
+   * @param arr an array all of whose elements, and their elements recursively, are non-null at run
+   *     time
+   * @return the argument, casted to have the type qualifier @NonNull at all levels
+   * @see #castNonNull(Object)
+   */
+  @EnsuresNonNull("#1")
+  public static <T extends @Nullable Object> @NonNull T @NonNull [] castNonNullDeep(
+      T @Nullable [] arr) {
+    return (@NonNull T[]) castNonNullArray(arr, null);
+  }
+
+  /**
+   * Like castNonNull, but whereas that method only checks and casts the reference itself, this
+   * traverses all levels of the argument array. The array is recursively checked to ensure that all
+   * elements at every array level are non-null.
+   *
+   * @param <T> the component type of the array
+   * @param arr an array all of whose elements, and their elements recursively, are non-null at run
+   *     time
+   * @param message text to include if this method is misused
+   * @return the argument, casted to have the type qualifier @NonNull at all levels
+   * @see #castNonNull(Object)
+   */
+  @EnsuresNonNull("#1")
+  public static <T extends @Nullable Object> @NonNull T @NonNull [] castNonNullDeep(
+      T @Nullable [] arr, String message) {
+    return (@NonNull T[]) castNonNullArray(arr, message);
+  }
+
+  /**
+   * Like castNonNull, but whereas that method only checks and casts the reference itself, this
+   * traverses all levels of the argument array. The array is recursively checked to ensure that all
+   * elements at every array level are non-null.
+   *
+   * @param <T> the component type of the component type of the array
+   * @param arr an array all of whose elements, and their elements recursively, are non-null at run
+   *     time
+   * @return the argument, casted to have the type qualifier @NonNull at all levels
+   * @see #castNonNull(Object)
+   */
+  @EnsuresNonNull("#1")
+  public static <T extends @Nullable Object> @NonNull T @NonNull [][] castNonNullDeep(
+      T @Nullable [] @Nullable [] arr) {
+    return (@NonNull T[][]) castNonNullArray(arr, null);
+  }
+
+  /**
+   * Like castNonNull, but whereas that method only checks and casts the reference itself, this
+   * traverses all levels of the argument array. The array is recursively checked to ensure that all
+   * elements at every array level are non-null.
+   *
+   * @param <T> the component type of the component type of the array
+   * @param arr an array all of whose elements, and their elements recursively, are non-null at run
+   *     time
+   * @param message text to include if this method is misused
+   * @return the argument, casted to have the type qualifier @NonNull at all levels
+   * @see #castNonNull(Object)
+   */
+  @EnsuresNonNull("#1")
+  public static <T extends @Nullable Object> @NonNull T @NonNull [][] castNonNullDeep(
+      T @Nullable [] @Nullable [] arr, String message) {
+    return (@NonNull T[][]) castNonNullArray(arr, message);
+  }
+
+  /**
+   * Like castNonNull, but whereas that method only checks and casts the reference itself, this
+   * traverses all levels of the argument array. The array is recursively checked to ensure that all
+   * elements at every array level are non-null.
+   *
+   * @param <T> the component type (three levels in) of the array
+   * @param arr an array all of whose elements, and their elements recursively, are non-null at run
+   *     time
+   * @return the argument, casted to have the type qualifier @NonNull at all levels
+   * @see #castNonNull(Object)
+   */
+  @EnsuresNonNull("#1")
+  public static <T extends @Nullable Object> @NonNull T @NonNull [][][] castNonNullDeep(
+      T @Nullable [] @Nullable [] @Nullable [] arr) {
+    return (@NonNull T[][][]) castNonNullArray(arr, null);
+  }
+
+  /**
+   * Like castNonNull, but whereas that method only checks and casts the reference itself, this
+   * traverses all levels of the argument array. The array is recursively checked to ensure that all
+   * elements at every array level are non-null.
+   *
+   * @param <T> the component type (three levels in) of the array
+   * @param arr an array all of whose elements, and their elements recursively, are non-null at run
+   *     time
+   * @param message text to include if this method is misused
+   * @return the argument, casted to have the type qualifier @NonNull at all levels
+   * @see #castNonNull(Object)
+   */
+  @EnsuresNonNull("#1")
+  public static <T extends @Nullable Object> @NonNull T @NonNull [][][] castNonNullDeep(
+      T @Nullable [] @Nullable [] @Nullable [] arr, String message) {
+    return (@NonNull T[][][]) castNonNullArray(arr, message);
+  }
+
+  /**
+   * Like castNonNull, but whereas that method only checks and casts the reference itself, this
+   * traverses all levels of the argument array. The array is recursively checked to ensure that all
+   * elements at every array level are non-null.
+   *
+   * @param <T> the component type of the array
+   * @param arr an array all of whose elements, and their elements recursively, are non-null at run
+   *     time
+   * @return the argument, casted to have the type qualifier @NonNull at all levels
+   * @see #castNonNull(Object)
+   */
+  @EnsuresNonNull("#1")
+  public static <T extends @Nullable Object> @NonNull T @NonNull [][][][] castNonNullDeep(
+      T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) {
+    return (@NonNull T[][][][]) castNonNullArray(arr, null);
+  }
+
+  /**
+   * Like castNonNull, but whereas that method only checks and casts the reference itself, this
+   * traverses all levels of the argument array. The array is recursively checked to ensure that all
+   * elements at every array level are non-null.
+   *
+   * @param <T> the component type (four levels in) of the array
+   * @param arr an array all of whose elements, and their elements recursively, are non-null at run
+   *     time
+   * @param message text to include if this method is misused
+   * @return the argument, casted to have the type qualifier @NonNull at all levels
+   * @see #castNonNull(Object)
+   */
+  @EnsuresNonNull("#1")
+  public static <T extends @Nullable Object> @NonNull T @NonNull [][][][] castNonNullDeep(
+      T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, String message) {
+    return (@NonNull T[][][][]) castNonNullArray(arr, message);
+  }
+
+  /**
+   * Like castNonNull, but whereas that method only checks and casts the reference itself, this
+   * traverses all levels of the argument array. The array is recursively checked to ensure that all
+   * elements at every array level are non-null.
+   *
+   * @param <T> the component type (four levels in) of the array
+   * @param arr an array all of whose elements, and their elements recursively, are non-null at run
+   *     time
+   * @return the argument, casted to have the type qualifier @NonNull at all levels
+   * @see #castNonNull(Object)
+   */
+  @EnsuresNonNull("#1")
+  public static <T extends @Nullable Object> @NonNull T @NonNull [][][][][] castNonNullDeep(
+      T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) {
+    return (@NonNull T[][][][][]) castNonNullArray(arr, null);
+  }
+
+  /**
+   * Like castNonNull, but whereas that method only checks and casts the reference itself, this
+   * traverses all levels of the argument array. The array is recursively checked to ensure that all
+   * elements at every array level are non-null.
+   *
+   * @param <T> the component type (five levels in) of the array
+   * @param arr an array all of whose elements, and their elements recursively, are non-null at run
+   *     time
+   * @param message text to include if this method is misused
+   * @return the argument, casted to have the type qualifier @NonNull at all levels
+   * @see #castNonNull(Object)
+   */
+  @EnsuresNonNull("#1")
+  public static <T extends @Nullable Object> @NonNull T @NonNull [][][][][] castNonNullDeep(
+      T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, String message) {
+    return (@NonNull T[][][][][]) castNonNullArray(arr, message);
+  }
+
+  /**
+   * The implementation of castNonNullDeep.
+   *
+   * @param <T> the component type (five levels in) of the array
+   * @param arr an array all of whose elements, and their elements recursively, are non-null at run
+   *     time
+   * @param message text to include if there is a non-null value, or null to use uncustomized
+   *     message
+   * @return the argument, casted to have the type qualifier @NonNull at all levels
+   */
+  private static <T extends @Nullable Object> @NonNull T @NonNull [] castNonNullArray(
+      T @Nullable [] arr, @Nullable String message) {
+    assert arr != null
+        : "Misuse of castNonNullArray: called with a null array argument"
+            + ((message == null) ? "" : (": " + message));
+    for (int i = 0; i < arr.length; ++i) {
+      assert arr[i] != null
+          : "Misuse of castNonNull: called with a null array element"
+              + ((message == null) ? "" : (": " + message));
+      checkIfArray(arr[i], message);
+    }
+    return (@NonNull T[]) arr;
+  }
+
+  /**
+   * If the argument is an array, requires it to be non-null at all levels.
+   *
+   * @param ref a value; if an array, all of its elements, and their elements recursively, are
+   *     non-null at run time
+   * @param message text to include if there is a non-null value, or null to use uncustomized
+   *     message
+   */
+  private static void checkIfArray(@NonNull Object ref, @Nullable String message) {
+    assert ref != null
+        : "Misuse of checkIfArray: called with a null argument"
+            + ((message == null) ? "" : (": " + message));
+    Class<?> comp = ref.getClass().getComponentType();
+    if (comp != null) {
+      // comp is non-null for arrays, otherwise null.
+      if (comp.isPrimitive()) {
+        // Nothing to do for arrays of primitive type: primitives are
+        // never null.
+      } else {
+        castNonNullArray((Object[]) ref, message);
+      }
+    }
+  }
+}
diff --git a/checker-util/src/main/java/org/checkerframework/checker/nullness/util/Opt.java b/checker-util/src/main/java/org/checkerframework/checker/nullness/util/Opt.java
new file mode 100644
index 0000000..b6c7b02
--- /dev/null
+++ b/checker-util/src/main/java/org/checkerframework/checker/nullness/util/Opt.java
@@ -0,0 +1,143 @@
+package org.checkerframework.checker.nullness.util;
+
+import java.util.NoSuchElementException;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+/**
+ * Utility class for the Nullness Checker, providing every method in {@link java.util.Optional}, but
+ * written for possibly-null references rather than for the {@code Optional} type.
+ *
+ * <p>To avoid the need to write the {@code Opt} class name at invocation sites, do:
+ *
+ * <pre>import static org.checkerframework.checker.nullness.util.Opt.orElse;</pre>
+ *
+ * or
+ *
+ * <pre>import static org.checkerframework.checker.nullness.util.Opt.*;</pre>
+ *
+ * <p><b>Runtime Dependency</b>: If you use this class, you must distribute (or link to) {@code
+ * checker-qual.jar}, along with your binaries. Or, you can can copy this class into your own
+ * project.
+ *
+ * @see java.util.Optional
+ */
+@AnnotatedFor("nullness")
+public final class Opt {
+
+  /** The Opt class cannot be instantiated. */
+  private Opt() {
+    throw new AssertionError("shouldn't be instantiated");
+  }
+
+  /**
+   * If primary is non-null, returns it, otherwise throws NoSuchElementException.
+   *
+   * @param <T> the type of the argument
+   * @param primary a non-null value to return
+   * @return {@code primary} if it is non-null
+   * @throws NoSuchElementException if primary is null
+   * @see java.util.Optional#get()
+   */
+  // `primary` is @NonNull; otherwise, the method could throw an exception.
+  public static <T extends @NonNull Object> T get(T primary) {
+    if (primary == null) {
+      throw new NoSuchElementException("No value present");
+    }
+    return primary;
+  }
+
+  /**
+   * Returns true if primary is non-null, false if primary is null.
+   *
+   * @see java.util.Optional#isPresent()
+   */
+  @EnsuresNonNullIf(expression = "#1", result = true)
+  public static boolean isPresent(@Nullable Object primary) {
+    return primary != null;
+  }
+
+  /**
+   * If primary is non-null, invoke the specified consumer with the value, otherwise do nothing.
+   *
+   * @see java.util.Optional#ifPresent(Consumer)
+   */
+  public static <T> void ifPresent(T primary, Consumer<@NonNull ? super @NonNull T> consumer) {
+    if (primary != null) {
+      consumer.accept(primary);
+    }
+  }
+
+  /**
+   * If primary is non-null, and its value matches the given predicate, return the value. If primary
+   * is null or its non-null value does not match the predicate, return null.
+   *
+   * @see java.util.Optional#filter(Predicate)
+   */
+  public static <T> @Nullable T filter(
+      T primary, Predicate<@NonNull ? super @NonNull T> predicate) {
+    if (primary == null) {
+      return null;
+    } else {
+      return predicate.test(primary) ? primary : null;
+    }
+  }
+
+  /**
+   * If primary is non-null, apply the provided mapping function to it and return the result. If
+   * primary is null, return null.
+   *
+   * @see java.util.Optional#map(Function)
+   */
+  public static <T, U> @Nullable U map(
+      T primary, Function<@NonNull ? super @NonNull T, ? extends U> mapper) {
+    if (primary == null) {
+      return null;
+    } else {
+      return mapper.apply(primary);
+    }
+  }
+
+  // flatMap would have the same signature and implementation as map
+
+  /**
+   * Return primary if it is non-null. If primary is null, return other.
+   *
+   * @see java.util.Optional#orElse(Object)
+   */
+  public static <T> @NonNull T orElse(T primary, @NonNull T other) {
+    return primary != null ? primary : other;
+  }
+
+  /**
+   * Return primary if it is non-null. If primary is null, invoke {@code other} and return the
+   * result of that invocation.
+   *
+   * @see java.util.Optional#orElseGet(Supplier)
+   */
+  public static <T> @NonNull T orElseGet(T primary, Supplier<? extends @NonNull T> other) {
+    return primary != null ? primary : other.get();
+  }
+
+  /**
+   * Return primary if it is non-null. If primary is null, throw an exception to be created by the
+   * provided supplier.
+   *
+   * @see java.util.Optional#orElseThrow(Supplier)
+   */
+  // `primary` is @NonNull; otherwise, the method could throw an exception.
+  public static <T extends @NonNull Object, X extends @NonNull Throwable> T orElseThrow(
+      T primary, Supplier<? extends X> exceptionSupplier) throws X {
+    if (primary != null) {
+      return primary;
+    } else {
+      throw exceptionSupplier.get();
+    }
+  }
+}
diff --git a/checker-util/src/main/java/org/checkerframework/checker/regex/util/RegexUtil.java b/checker-util/src/main/java/org/checkerframework/checker/regex/util/RegexUtil.java
new file mode 100644
index 0000000..4746a02
--- /dev/null
+++ b/checker-util/src/main/java/org/checkerframework/checker/regex/util/RegexUtil.java
@@ -0,0 +1,335 @@
+// This class should be kept in sync with org.plumelib.util.RegexUtil in the plume-util project.
+// (Warning suppressions may differ.)
+
+package org.checkerframework.checker.regex.util;
+
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.lock.qual.GuardSatisfied;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.regex.qual.Regex;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.qual.EnsuresQualifierIf;
+
+/**
+ * Utility methods for regular expressions, most notably for testing whether a string is a regular
+ * expression.
+ *
+ * <p>For an example of intended use, see section <a
+ * href="https://checkerframework.org/manual/#regexutil-methods">Testing whether a string is a
+ * regular expression</a> in the Checker Framework manual.
+ *
+ * <p><b>Runtime Dependency</b>: If you use this class, you must distribute (or link to) {@code
+ * checker-qual.jar}, along with your binaries. Or, you can can copy this class into your own
+ * project.
+ */
+@AnnotatedFor("nullness")
+public final class RegexUtil {
+
+  /** This class is a collection of methods; it does not represent anything. */
+  private RegexUtil() {
+    throw new Error("do not instantiate");
+  }
+
+  /**
+   * A checked version of {@link PatternSyntaxException}.
+   *
+   * <p>This exception is useful when an illegal regex is detected but the contextual information to
+   * report a helpful error message is not available at the current depth in the call stack. By
+   * using a checked PatternSyntaxException the error must be handled up the call stack where a
+   * better error message can be reported.
+   *
+   * <p>Typical usage is:
+   *
+   * <pre>
+   * void myMethod(...) throws CheckedPatternSyntaxException {
+   *   ...
+   *   if (! isRegex(myString)) {
+   *     throw new CheckedPatternSyntaxException(...);
+   *   }
+   *   ... Pattern.compile(myString) ...
+   * </pre>
+   *
+   * Simply calling {@code Pattern.compile} would have a similar effect, in that {@code
+   * PatternSyntaxException} would be thrown at run time if {@code myString} is not a regular
+   * expression. There are two problems with such an approach. First, a client of {@code myMethod}
+   * might forget to handle the exception, since {@code PatternSyntaxException} is not checked.
+   * Also, the Regex Checker would issue a warning about the call to {@code Pattern.compile} that
+   * might throw an exception. The above usage pattern avoids both problems.
+   *
+   * @see PatternSyntaxException
+   */
+  public static class CheckedPatternSyntaxException extends Exception {
+
+    private static final long serialVersionUID = 6266881831979001480L;
+
+    /** The PatternSyntaxException that this is a wrapper around. */
+    private final PatternSyntaxException pse;
+
+    /**
+     * Constructs a new CheckedPatternSyntaxException equivalent to the given {@link
+     * PatternSyntaxException}.
+     *
+     * <p>Consider calling this constructor with the result of {@link RegexUtil#regexError}.
+     *
+     * @param pse the PatternSyntaxException to be wrapped
+     */
+    public CheckedPatternSyntaxException(PatternSyntaxException pse) {
+      this.pse = pse;
+    }
+
+    /**
+     * Constructs a new CheckedPatternSyntaxException.
+     *
+     * @param desc a description of the error
+     * @param regex the erroneous pattern
+     * @param index the approximate index in the pattern of the error, or {@code -1} if the index is
+     *     not known
+     */
+    public CheckedPatternSyntaxException(String desc, String regex, @GTENegativeOne int index) {
+      this(new PatternSyntaxException(desc, regex, index));
+    }
+
+    /**
+     * Retrieves the description of the error.
+     *
+     * @return the description of the error
+     */
+    public String getDescription() {
+      return pse.getDescription();
+    }
+
+    /**
+     * Retrieves the error index.
+     *
+     * @return the approximate index in the pattern of the error, or {@code -1} if the index is not
+     *     known
+     */
+    public int getIndex() {
+      return pse.getIndex();
+    }
+
+    /**
+     * Returns a multi-line string containing the description of the syntax error and its index, the
+     * erroneous regular-expression pattern, and a visual indication of the error index within the
+     * pattern.
+     *
+     * @return the full detail message
+     */
+    @Override
+    @Pure
+    public String getMessage(@GuardSatisfied CheckedPatternSyntaxException this) {
+      return pse.getMessage();
+    }
+
+    /**
+     * Retrieves the erroneous regular-expression pattern.
+     *
+     * @return the erroneous pattern
+     */
+    public String getPattern() {
+      return pse.getPattern();
+    }
+  }
+
+  /**
+   * Returns true if the argument is a syntactically valid regular expression.
+   *
+   * @param s string to check for being a regular expression
+   * @return true iff s is a regular expression
+   */
+  @Pure
+  @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class)
+  public static boolean isRegex(String s) {
+    return isRegex(s, 0);
+  }
+
+  /**
+   * Returns true if the argument is a syntactically valid regular expression with at least the
+   * given number of groups.
+   *
+   * @param s string to check for being a regular expression
+   * @param groups number of groups expected
+   * @return true iff s is a regular expression with {@code groups} groups
+   */
+  @SuppressWarnings("regex") // RegexUtil; for purity, catches an exception
+  @Pure
+  // @EnsuresQualifierIf annotation is extraneous because this method is special-cased
+  // in RegexTransfer.
+  @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class)
+  public static boolean isRegex(String s, int groups) {
+    Pattern p;
+    try {
+      p = Pattern.compile(s);
+    } catch (PatternSyntaxException e) {
+      return false;
+    }
+    return getGroupCount(p) >= groups;
+  }
+
+  /**
+   * Returns true if the argument is a syntactically valid regular expression.
+   *
+   * @param c char to check for being a regular expression
+   * @return true iff c is a regular expression
+   */
+  @SuppressWarnings({
+    "regex", "lock"
+  }) // RegexUtil; temp value used in pure method is equal up to equals but not up to ==
+  @Pure
+  @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class)
+  public static boolean isRegex(final char c) {
+    return isRegex(Character.toString(c));
+  }
+
+  /**
+   * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a
+   * string describing why the argument is not a regex.
+   *
+   * @param s string to check for being a regular expression
+   * @return null, or a string describing why the argument is not a regex
+   */
+  @SideEffectFree
+  public static @Nullable String regexError(String s) {
+    return regexError(s, 0);
+  }
+
+  /**
+   * Returns null if the argument is a syntactically valid regular expression with at least the
+   * given number of groups. Otherwise returns a string describing why the argument is not a regex.
+   *
+   * @param s string to check for being a regular expression
+   * @param groups number of groups expected
+   * @return null, or a string describing why the argument is not a regex
+   */
+  @SuppressWarnings({"regex", "not.sef"}) // RegexUtil;
+  @SideEffectFree
+  public static @Nullable String regexError(String s, int groups) {
+    try {
+      Pattern p = Pattern.compile(s);
+      int actualGroups = getGroupCount(p);
+      if (actualGroups < groups) {
+        return regexErrorMessage(s, groups, actualGroups);
+      }
+    } catch (PatternSyntaxException e) {
+      return e.getMessage();
+    }
+    return null;
+  }
+
+  /**
+   * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a
+   * PatternSyntaxException describing why the argument is not a regex.
+   *
+   * @param s string to check for being a regular expression
+   * @return null, or a PatternSyntaxException describing why the argument is not a regex
+   */
+  @SideEffectFree
+  public static @Nullable PatternSyntaxException regexException(String s) {
+    return regexException(s, 0);
+  }
+
+  /**
+   * Returns null if the argument is a syntactically valid regular expression with at least the
+   * given number of groups. Otherwise returns a PatternSyntaxException describing why the argument
+   * is not a regex.
+   *
+   * @param s string to check for being a regular expression
+   * @param groups number of groups expected
+   * @return null, or a PatternSyntaxException describing why the argument is not a regex
+   */
+  @SuppressWarnings("regex") // RegexUtil
+  @SideEffectFree
+  public static @Nullable PatternSyntaxException regexException(String s, int groups) {
+    try {
+      Pattern p = Pattern.compile(s);
+      int actualGroups = getGroupCount(p);
+      if (actualGroups < groups) {
+        return new PatternSyntaxException(regexErrorMessage(s, groups, actualGroups), s, -1);
+      }
+    } catch (PatternSyntaxException pse) {
+      return pse;
+    }
+    return null;
+  }
+
+  /**
+   * Returns the argument as a {@code @Regex String} if it is a regex, otherwise throws an error.
+   *
+   * <p>The purpose of this method is to suppress Regex Checker warnings. It should be very rarely
+   * needed.
+   *
+   * @param s string to check for being a regular expression
+   * @return its argument
+   * @throws Error if argument is not a regex
+   */
+  @SideEffectFree
+  // The return type annotation is a conservative bound.
+  public static @Regex String asRegex(String s) {
+    return asRegex(s, 0);
+  }
+
+  /**
+   * Returns the argument as a {@code @Regex(groups) String} if it is a regex with at least the
+   * given number of groups, otherwise throws an error.
+   *
+   * <p>The purpose of this method is to suppress Regex Checker warnings. It should be very rarely
+   * needed.
+   *
+   * @param s string to check for being a regular expression
+   * @param groups number of groups expected
+   * @return its argument
+   * @throws Error if argument is not a regex
+   */
+  @SuppressWarnings("regex") // RegexUtil
+  @SideEffectFree
+  // The return type annotation is irrelevant; this method is special-cased by
+  // RegexAnnotatedTypeFactory.
+  public static @Regex String asRegex(String s, int groups) {
+    try {
+      Pattern p = Pattern.compile(s);
+      int actualGroups = getGroupCount(p);
+      if (actualGroups < groups) {
+        throw new Error(regexErrorMessage(s, groups, actualGroups));
+      }
+      return s;
+    } catch (PatternSyntaxException e) {
+      throw new Error(e);
+    }
+  }
+
+  /**
+   * Generates an error message for s when expectedGroups are needed, but s only has actualGroups.
+   *
+   * @param s string to check for being a regular expression
+   * @param expectedGroups the number of needed capturing groups
+   * @param actualGroups the number of groups that {@code s} has
+   * @return an error message for s when expectedGroups groups are needed, but s only has
+   *     actualGroups groups
+   */
+  @SideEffectFree
+  private static String regexErrorMessage(String s, int expectedGroups, int actualGroups) {
+    return "regex \""
+        + s
+        + "\" has "
+        + actualGroups
+        + " groups, but "
+        + expectedGroups
+        + " groups are needed.";
+  }
+
+  /**
+   * Return the count of groups in the argument.
+   *
+   * @param p pattern whose groups to count
+   * @return the count of groups in the argument
+   */
+  @SuppressWarnings("lock") // does not depend on object identity
+  @Pure
+  private static int getGroupCount(Pattern p) {
+    return p.matcher("").groupCount();
+  }
+}
diff --git a/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtil.java b/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtil.java
new file mode 100644
index 0000000..21c9e75
--- /dev/null
+++ b/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtil.java
@@ -0,0 +1,539 @@
+package org.checkerframework.checker.signedness.util;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+/**
+ * Provides static utility methods for unsigned values, beyond what is available in the JDK's
+ * Unsigned Integer API. The JDK's Unsigned Integer API is methods in primitive wrapper classes and
+ * in classes {@code Arrays}, {@code RandomAccessFile}, {@code ObjectInputStream}, and {@code
+ * DataInputStream}.
+ *
+ * <p>{@link SignednessUtilExtra} has more methods that reference packages that Android does not
+ * provide. That is, {@code SignednessUtil} can be used anywhere, and {@link SignednessUtilExtra}
+ * can be used anywhere except on Android.
+ *
+ * @checker_framework.manual #signedness-utilities Utility routines for manipulating unsigned values
+ */
+@AnnotatedFor("nullness")
+public final class SignednessUtil {
+
+  private SignednessUtil() {
+    throw new Error("Do not instantiate");
+  }
+
+  /**
+   * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#wrap(byte[]) wrap(byte[])}, but assumes that the input should be
+   * interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static ByteBuffer wrapUnsigned(@Unsigned byte[] array) {
+    return ByteBuffer.wrap(array);
+  }
+
+  /**
+   * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#wrap(byte[], int, int) wrap(byte[], int, int)}, but assumes that the input
+   * should be interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static ByteBuffer wrapUnsigned(@Unsigned byte[] array, int offset, int length) {
+    return ByteBuffer.wrap(array, offset, length);
+  }
+
+  /**
+   * Gets an unsigned int from the ByteBuffer b. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#getInt() getInt()}, but assumes that the result should be interpreted as
+   * unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static @Unsigned int getUnsignedInt(ByteBuffer b) {
+    return b.getInt();
+  }
+
+  /**
+   * Gets an unsigned short from the ByteBuffer b. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#getShort() getShort()}, but assumes that the result should be interpreted
+   * as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static @Unsigned short getUnsignedShort(ByteBuffer b) {
+    return b.getShort();
+  }
+
+  /**
+   * Gets an unsigned byte from the ByteBuffer b. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#get() get()}, but assumes that the result should be interpreted as
+   * unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static @Unsigned byte getUnsigned(ByteBuffer b) {
+    return b.get();
+  }
+
+  /**
+   * Gets an unsigned byte from the ByteBuffer b at i. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#get(int) get(int)}, but assumes that the result should be interpreted as
+   * unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static @Unsigned byte getUnsigned(ByteBuffer b, int i) {
+    return b.get(i);
+  }
+
+  /**
+   * Populates an unsigned byte array from the ByteBuffer b at i with l bytes. This method is a
+   * wrapper around {@link java.nio.ByteBuffer#get(byte[] bs, int, int) get(byte[], int, int)}, but
+   * assumes that the bytes should be interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static ByteBuffer getUnsigned(ByteBuffer b, byte[] bs, int i, int l) {
+    return b.get(bs, i, l);
+  }
+
+  /**
+   * Places an unsigned byte into the ByteBuffer b. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#put(byte) put(byte)}, but assumes that the input should be interpreted as
+   * unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static ByteBuffer putUnsigned(ByteBuffer b, @Unsigned byte ubyte) {
+    return b.put(ubyte);
+  }
+
+  /**
+   * Places an unsigned byte into the ByteBuffer b at i. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#put(int, byte) put(int, byte)}, but assumes that the input should be
+   * interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static ByteBuffer putUnsigned(ByteBuffer b, int i, @Unsigned byte ubyte) {
+    return b.put(i, ubyte);
+  }
+
+  /**
+   * Places an unsigned int into the IntBuffer b. This method is a wrapper around {@link
+   * java.nio.IntBuffer#put(int) put(int)}, but assumes that the input should be interpreted as
+   * unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int uint) {
+    return b.put(uint);
+  }
+
+  /**
+   * Places an unsigned int into the IntBuffer b at i. This method is a wrapper around {@link
+   * java.nio.IntBuffer#put(int, int) put(int, int)}, but assumes that the input should be
+   * interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static IntBuffer putUnsigned(IntBuffer b, int i, @Unsigned int uint) {
+    return b.put(i, uint);
+  }
+
+  /**
+   * Places an unsigned int array into the IntBuffer b. This method is a wrapper around {@link
+   * java.nio.IntBuffer#put(int[]) put(int[])}, but assumes that the input should be interpreted as
+   * unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints) {
+    return b.put(uints);
+  }
+
+  /**
+   * Places an unsigned int array into the IntBuffer b at i with length l. This method is a wrapper
+   * around {@link java.nio.IntBuffer#put(int[], int, int) put(int[], int, int)}, but assumes that
+   * the input should be interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints, int i, int l) {
+    return b.put(uints, i, l);
+  }
+
+  /**
+   * Gets an unsigned int from the IntBuffer b at i. This method is a wrapper around {@link
+   * java.nio.IntBuffer#get(int) get(int)}, but assumes that the output should be interpreted as
+   * unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static @Unsigned int getUnsigned(IntBuffer b, int i) {
+    return b.get(i);
+  }
+
+  /**
+   * Places an unsigned short into the ByteBuffer b. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#putShort(short) putShort(short)}, but assumes that the input should be
+   * interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static ByteBuffer putUnsignedShort(ByteBuffer b, @Unsigned short ushort) {
+    return b.putShort(ushort);
+  }
+
+  /**
+   * Places an unsigned short into the ByteBuffer b at i. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#putShort(int, short) putShort(int, short)}, but assumes that the input
+   * should be interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static ByteBuffer putUnsignedShort(ByteBuffer b, int i, @Unsigned short ushort) {
+    return b.putShort(i, ushort);
+  }
+
+  /**
+   * Places an unsigned int into the ByteBuffer b. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#putInt(int) putInt(int)}, but assumes that the input should be interpreted
+   * as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static ByteBuffer putUnsignedInt(ByteBuffer b, @Unsigned int uint) {
+    return b.putInt(uint);
+  }
+
+  /**
+   * Places an unsigned int into the ByteBuffer b at i. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#putInt(int, int) putInt(int, int)}, but assumes that the input should be
+   * interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static ByteBuffer putUnsignedInt(ByteBuffer b, int i, @Unsigned int uint) {
+    return b.putInt(i, uint);
+  }
+
+  /**
+   * Places an unsigned long into the ByteBuffer b at i. This method is a wrapper around {@link
+   * java.nio.ByteBuffer#putLong(int, long) putLong(int, long)}, but assumes that the input should
+   * be interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static ByteBuffer putUnsignedLong(ByteBuffer b, int i, @Unsigned long ulong) {
+    return b.putLong(i, ulong);
+  }
+
+  /**
+   * Reads an unsigned char from the RandomAccessFile f. This method is a wrapper around {@link
+   * java.io.RandomAccessFile#readChar() readChar()}, but assumes the output should be interpreted
+   * as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static @Unsigned char readUnsignedChar(RandomAccessFile f) throws IOException {
+    return f.readChar();
+  }
+
+  /**
+   * Reads an unsigned int from the RandomAccessFile f. This method is a wrapper around {@link
+   * java.io.RandomAccessFile#readInt() readInt()}, but assumes the output should be interpreted as
+   * unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static @Unsigned int readUnsignedInt(RandomAccessFile f) throws IOException {
+    return f.readInt();
+  }
+
+  /**
+   * Reads an unsigned long from the RandomAccessFile f. This method is a wrapper around {@link
+   * java.io.RandomAccessFile#readLong() readLong()}, but assumes the output should be interpreted
+   * as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static @Unsigned long readUnsignedLong(RandomAccessFile f) throws IOException {
+    return f.readLong();
+  }
+
+  /**
+   * Reads up to {@code len} bytes of data from this file into an unsigned array of bytes. This
+   * method is a wrapper around {@link java.io.RandomAccessFile#read(byte[], int, int) read(byte[],
+   * int, int)}, but assumes the output should be interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static int readUnsigned(RandomAccessFile f, @Unsigned byte b[], int off, int len)
+      throws IOException {
+    return f.read(b, off, len);
+  }
+
+  /**
+   * Reads a file fully into an unsigned byte array. This method is a wrapper around {@link
+   * java.io.RandomAccessFile#readFully(byte[]) readFully(byte[])}, but assumes the output should be
+   * interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static void readFullyUnsigned(RandomAccessFile f, @Unsigned byte b[]) throws IOException {
+    f.readFully(b);
+  }
+
+  /**
+   * Writes len unsigned bytes to the RandomAccessFile f at offset off. This method is a wrapper
+   * around {@link java.io.RandomAccessFile#write(byte[], int, int) write(byte[], int, int)}, but
+   * assumes the input should be interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static void writeUnsigned(RandomAccessFile f, @Unsigned byte[] bs, int off, int len)
+      throws IOException {
+    f.write(bs, off, len);
+  }
+
+  /**
+   * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link
+   * java.io.RandomAccessFile#writeByte(int) writeByte(int)}, but assumes the input should be
+   * interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static void writeUnsignedByte(RandomAccessFile f, @Unsigned byte b) throws IOException {
+    f.writeByte(Byte.toUnsignedInt(b));
+  }
+
+  /**
+   * Writes an unsigned char to the RandomAccessFile f. This method is a wrapper around {@link
+   * java.io.RandomAccessFile#writeChar(int) writeChar(int)}, but assumes the input should be
+   * interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static void writeUnsignedChar(RandomAccessFile f, @Unsigned char c) throws IOException {
+    f.writeChar(toUnsignedInt(c));
+  }
+
+  /**
+   * Writes an unsigned short to the RandomAccessFile f. This method is a wrapper around {@link
+   * java.io.RandomAccessFile#writeShort(int) writeShort(int)}, but assumes the input should be
+   * interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static void writeUnsignedShort(RandomAccessFile f, @Unsigned short s) throws IOException {
+    f.writeShort(Short.toUnsignedInt(s));
+  }
+
+  /**
+   * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link
+   * java.io.RandomAccessFile#writeInt(int) writeInt(int)}, but assumes the input should be
+   * interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static void writeUnsignedInt(RandomAccessFile f, @Unsigned int i) throws IOException {
+    f.writeInt(i);
+  }
+
+  /**
+   * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link
+   * java.io.RandomAccessFile#writeLong(long) writeLong(long)}, but assumes the input should be
+   * interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static void writeUnsignedLong(RandomAccessFile f, @Unsigned long l) throws IOException {
+    f.writeLong(l);
+  }
+
+  /**
+   * Gets an array of unsigned bytes from the ByteBuffer b and stores them in the array bs. This
+   * method is a wrapper around {@link java.nio.ByteBuffer#get(byte[]) get(byte[])}, but assumes
+   * that the array of bytes should be interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static void getUnsigned(ByteBuffer b, @Unsigned byte[] bs) {
+    b.get(bs);
+  }
+
+  /**
+   * Compares two unsigned shorts x and y.
+   *
+   * <p>In Java 11 or later, use Short.compareUnsigned.
+   *
+   * @param x the first value to compare
+   * @param y the second value to compare
+   * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and
+   *     zero iff x == y.
+   */
+  // TODO: deprecate when we require Java 9, which defines Short.compareUnsigned()
+  // * @deprecated use Short.compareUnsigned
+  // @Deprecated // use Short.compareUnsigned
+  @SuppressWarnings("signedness")
+  public static int compareUnsigned(@Unsigned short x, @Unsigned short y) {
+    return Integer.compareUnsigned(Short.toUnsignedInt(x), Short.toUnsignedInt(y));
+  }
+
+  /**
+   * Compares two unsigned bytes x and y.
+   *
+   * <p>In Java 11 or later, use Byte.compareUnsigned.
+   *
+   * @param x the first value to compare
+   * @param y the second value to compare
+   * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and
+   *     zero iff x == y.
+   */
+  // TODO: deprecate when we require Java 9, which defines Byte.compareUnsigned()
+  // * @deprecated use Byte.compareUnsigned
+  // @Deprecated // use Byte.compareUnsigned
+  @SuppressWarnings("signedness")
+  public static int compareUnsigned(@Unsigned byte x, @Unsigned byte y) {
+    return Integer.compareUnsigned(Byte.toUnsignedInt(x), Byte.toUnsignedInt(y));
+  }
+
+  /** Produces a string representation of the unsigned short s. */
+  @SuppressWarnings("signedness")
+  public static String toUnsignedString(@Unsigned short s) {
+    return Long.toString(Short.toUnsignedLong(s));
+  }
+
+  /** Produces a string representation of the unsigned short s in base radix. */
+  @SuppressWarnings("signedness")
+  public static String toUnsignedString(@Unsigned short s, int radix) {
+    return Integer.toUnsignedString(Short.toUnsignedInt(s), radix);
+  }
+
+  /** Produces a string representation of the unsigned byte b. */
+  @SuppressWarnings("signedness")
+  public static String toUnsignedString(@Unsigned byte b) {
+    return Integer.toUnsignedString(Byte.toUnsignedInt(b));
+  }
+
+  /** Produces a string representation of the unsigned byte b in base radix. */
+  @SuppressWarnings("signedness")
+  public static String toUnsignedString(@Unsigned byte b, int radix) {
+    return Integer.toUnsignedString(Byte.toUnsignedInt(b), radix);
+  }
+
+  /*
+   * Creates a BigInteger representing the same value as unsigned long.
+   *
+   * This is a reimplementation of Java 8's
+   * {@link Long.toUnsignedBigInteger(long)}.
+   */
+  @SuppressWarnings("signedness")
+  private static @Unsigned BigInteger toUnsignedBigInteger(@Unsigned long l) {
+    // Java 8 version: return Long.toUnsignedBigInteger(l);
+    if (l >= 0L) {
+      return BigInteger.valueOf(l);
+    } else {
+      int upper = (int) (l >>> 32);
+      int lower = (int) l;
+
+      // return (upper << 32) + lower
+      return BigInteger.valueOf(Integer.toUnsignedLong(upper))
+          .shiftLeft(32)
+          .add(BigInteger.valueOf(Integer.toUnsignedLong(lower)));
+    }
+  }
+
+  /** Returns an unsigned short representing the same value as an unsigned byte. */
+  public static @Unsigned short toUnsignedShort(@Unsigned byte b) {
+    return (short) (((int) b) & 0xff);
+  }
+
+  /** Returns an unsigned long representing the same value as an unsigned char. */
+  public static @Unsigned long toUnsignedLong(@Unsigned char c) {
+    return ((long) c) & 0xffL;
+  }
+
+  /** Returns an unsigned int representing the same value as an unsigned char. */
+  public static @Unsigned int toUnsignedInt(@Unsigned char c) {
+    return ((int) c) & 0xff;
+  }
+
+  /** Returns an unsigned short representing the same value as an unsigned char. */
+  public static @Unsigned short toUnsignedShort(@Unsigned char c) {
+    return (short) (((int) c) & 0xff);
+  }
+
+  /** Returns a float representing the same value as the unsigned byte. */
+  public static float toFloat(@Unsigned byte b) {
+    return toUnsignedBigInteger(Byte.toUnsignedLong(b)).floatValue();
+  }
+
+  /** Returns a float representing the same value as the unsigned short. */
+  public static float toFloat(@Unsigned short s) {
+    return toUnsignedBigInteger(Short.toUnsignedLong(s)).floatValue();
+  }
+
+  /** Returns a float representing the same value as the unsigned int. */
+  public static float toFloat(@Unsigned int i) {
+    return toUnsignedBigInteger(Integer.toUnsignedLong(i)).floatValue();
+  }
+
+  /** Returns a float representing the same value as the unsigned long. */
+  public static float toFloat(@Unsigned long l) {
+    return toUnsignedBigInteger(l).floatValue();
+  }
+
+  /** Returns a double representing the same value as the unsigned byte. */
+  public static double toDouble(@Unsigned byte b) {
+    return toUnsignedBigInteger(Byte.toUnsignedLong(b)).doubleValue();
+  }
+
+  /** Returns a double representing the same value as the unsigned short. */
+  public static double toDouble(@Unsigned short s) {
+    return toUnsignedBigInteger(Short.toUnsignedLong(s)).doubleValue();
+  }
+
+  /** Returns a double representing the same value as the unsigned int. */
+  public static double toDouble(@Unsigned int i) {
+    return toUnsignedBigInteger(Integer.toUnsignedLong(i)).doubleValue();
+  }
+
+  /** Returns a double representing the same value as the unsigned long. */
+  public static double toDouble(@Unsigned long l) {
+    return toUnsignedBigInteger(l).doubleValue();
+  }
+
+  /** Returns an unsigned byte representing the same value as the float. */
+  @SuppressWarnings("signedness")
+  public static @Unsigned byte byteFromFloat(float f) {
+    assert f >= 0;
+    return (byte) f;
+  }
+
+  /** Returns an unsigned short representing the same value as the float. */
+  @SuppressWarnings("signedness")
+  public static @Unsigned short shortFromFloat(float f) {
+    assert f >= 0;
+    return (short) f;
+  }
+
+  /** Returns an unsigned int representing the same value as the float. */
+  @SuppressWarnings("signedness")
+  public static @Unsigned int intFromFloat(float f) {
+    assert f >= 0;
+    return (int) f;
+  }
+
+  /** Returns an unsigned long representing the same value as the float. */
+  @SuppressWarnings("signedness")
+  public static @Unsigned long longFromFloat(float f) {
+    assert f >= 0;
+    return (long) f;
+  }
+
+  /** Returns an unsigned byte representing the same value as the double. */
+  @SuppressWarnings("signedness")
+  public static @Unsigned byte byteFromDouble(double d) {
+    assert d >= 0;
+    return (byte) d;
+  }
+
+  /** Returns an unsigned short representing the same value as the double. */
+  @SuppressWarnings("signedness")
+  public static @Unsigned short shortFromDouble(double d) {
+    assert d >= 0;
+    return (short) d;
+  }
+
+  /** Returns an unsigned int representing the same value as the double. */
+  @SuppressWarnings("signedness")
+  public static @Unsigned int intFromDouble(double d) {
+    assert d >= 0;
+    return (int) d;
+  }
+
+  /** Returns an unsigned long representing the same value as the double. */
+  @SuppressWarnings("signedness")
+  public static @Unsigned long longFromDouble(double d) {
+    assert d >= 0;
+    return (long) d;
+  }
+}
diff --git a/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtilExtra.java b/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtilExtra.java
new file mode 100644
index 0000000..719f5d0
--- /dev/null
+++ b/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtilExtra.java
@@ -0,0 +1,71 @@
+package org.checkerframework.checker.signedness.util;
+
+import java.awt.Dimension;
+import java.awt.image.BufferedImage;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+/**
+ * Provides more static utility methods for unsigned values. These methods use Java packages not
+ * included in Android.
+ *
+ * <p>{@link SignednessUtil} has more methods that can be used anywhere, whereas {@code
+ * SignednessUtilExtra} can be used anywhere except on Android.
+ *
+ * @checker_framework.manual #signedness-utilities Utility routines for manipulating unsigned values
+ */
+@AnnotatedFor("nullness")
+public class SignednessUtilExtra {
+  /** Do not instantiate this class. */
+  private SignednessUtilExtra() {
+    throw new Error("Do not instantiate");
+  }
+
+  /** Gets the unsigned width of a {@code Dimension}. */
+  @SuppressWarnings("signedness")
+  public static @Unsigned int dimensionUnsignedWidth(Dimension dim) {
+    return dim.width;
+  }
+
+  /** Gets the unsigned height of a {@code Dimension}. */
+  @SuppressWarnings("signedness")
+  public static @Unsigned int dimensionUnsignedHeight(Dimension dim) {
+    return dim.height;
+  }
+
+  /**
+   * Sets rgb of BufferedImage b given unsigned ints. This method is a wrapper around {@link
+   * java.awt.image.BufferedImage#setRGB(int, int, int, int, int[], int, int) setRGB(int, int, int,
+   * int, int[], int, int)}, but assumes that the input should be interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static void setUnsignedRGB(
+      BufferedImage b,
+      int startX,
+      int startY,
+      int w,
+      int h,
+      @Unsigned int[] rgbArray,
+      int offset,
+      int scansize) {
+    b.setRGB(startX, startY, w, h, rgbArray, offset, scansize);
+  }
+
+  /**
+   * Gets rgb of BufferedImage b as unsigned ints. This method is a wrapper around {@link
+   * java.awt.image.BufferedImage#getRGB(int, int, int, int, int[], int, int) getRGB(int, int, int,
+   * int, int[], int, int)}, but assumes that the output should be interpreted as unsigned.
+   */
+  @SuppressWarnings("signedness")
+  public static @Unsigned int[] getUnsignedRGB(
+      BufferedImage b,
+      int startX,
+      int startY,
+      int w,
+      int h,
+      @Unsigned int[] rgbArray,
+      int offset,
+      int scansize) {
+    return b.getRGB(startX, startY, w, h, rgbArray, offset, scansize);
+  }
+}
diff --git a/checker-util/src/main/java/org/checkerframework/checker/units/util/UnitsTools.java b/checker-util/src/main/java/org/checkerframework/checker/units/util/UnitsTools.java
new file mode 100644
index 0000000..7b92e25
--- /dev/null
+++ b/checker-util/src/main/java/org/checkerframework/checker/units/util/UnitsTools.java
@@ -0,0 +1,170 @@
+package org.checkerframework.checker.units.util;
+
+import org.checkerframework.checker.units.qual.A;
+import org.checkerframework.checker.units.qual.C;
+import org.checkerframework.checker.units.qual.K;
+import org.checkerframework.checker.units.qual.N;
+import org.checkerframework.checker.units.qual.cd;
+import org.checkerframework.checker.units.qual.degrees;
+import org.checkerframework.checker.units.qual.g;
+import org.checkerframework.checker.units.qual.h;
+import org.checkerframework.checker.units.qual.kN;
+import org.checkerframework.checker.units.qual.kg;
+import org.checkerframework.checker.units.qual.km;
+import org.checkerframework.checker.units.qual.km2;
+import org.checkerframework.checker.units.qual.km3;
+import org.checkerframework.checker.units.qual.kmPERh;
+import org.checkerframework.checker.units.qual.m;
+import org.checkerframework.checker.units.qual.m2;
+import org.checkerframework.checker.units.qual.m3;
+import org.checkerframework.checker.units.qual.mPERs;
+import org.checkerframework.checker.units.qual.mPERs2;
+import org.checkerframework.checker.units.qual.min;
+import org.checkerframework.checker.units.qual.mm;
+import org.checkerframework.checker.units.qual.mm2;
+import org.checkerframework.checker.units.qual.mm3;
+import org.checkerframework.checker.units.qual.mol;
+import org.checkerframework.checker.units.qual.radians;
+import org.checkerframework.checker.units.qual.s;
+import org.checkerframework.checker.units.qual.t;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+// TODO: add fromTo methods for all useful unit combinations.
+
+/** Utility methods to generate annotated types and to convert between them. */
+@SuppressWarnings({"units", "checkstyle:constantname"})
+@AnnotatedFor("nullness")
+public class UnitsTools {
+  // Acceleration
+  public static final @mPERs2 int mPERs2 = 1;
+
+  // Angle
+  public static final @radians double rad = 1;
+  public static final @degrees double deg = 1;
+
+  public static @radians double toRadians(@degrees double angdeg) {
+    return Math.toRadians(angdeg);
+  }
+
+  public static @degrees double toDegrees(@radians double angrad) {
+    return Math.toDegrees(angrad);
+  }
+
+  // Area
+  public static final @mm2 int mm2 = 1;
+  public static final @m2 int m2 = 1;
+  public static final @km2 int km2 = 1;
+
+  // Volume
+  public static final @mm3 int mm3 = 1;
+  public static final @m3 int m3 = 1;
+  public static final @km3 int km3 = 1;
+
+  // Current
+  public static final @A int A = 1;
+
+  // Luminance
+  public static final @cd int cd = 1;
+
+  // Lengths
+  public static final @mm int mm = 1;
+  public static final @m int m = 1;
+  public static final @km int km = 1;
+
+  public static @m int fromMilliMeterToMeter(@mm int mm) {
+    return mm / 1000;
+  }
+
+  public static @mm int fromMeterToMilliMeter(@m int m) {
+    return m * 1000;
+  }
+
+  public static @km int fromMeterToKiloMeter(@m int m) {
+    return m / 1000;
+  }
+
+  public static @m int fromKiloMeterToMeter(@km int km) {
+    return km * 1000;
+  }
+
+  // Mass
+  public static final @g int g = 1;
+  public static final @kg int kg = 1;
+  public static final @t int t = 1;
+
+  public static @kg int fromGramToKiloGram(@g int g) {
+    return g / 1000;
+  }
+
+  public static @g int fromKiloGramToGram(@kg int kg) {
+    return kg * 1000;
+  }
+
+  public static @t int fromKiloGramToMetricTon(@kg int kg) {
+    return kg / 1000;
+  }
+
+  public static @kg int fromMetricTonToKiloGram(@t int t) {
+    return t * 1000;
+  }
+
+  // Force
+  public static final @N int N = 1;
+  public static final @kN int kN = 1;
+
+  public static @kN int fromNewtonToKiloNewton(@N int N) {
+    return N / 1000;
+  }
+
+  public static @N int fromKiloNewtonToNewton(@kN int kN) {
+    return kN * 1000;
+  }
+
+  // Speed
+  public static final @mPERs int mPERs = 1;
+  public static final @kmPERh int kmPERh = 1;
+
+  public static @kmPERh double fromMeterPerSecondToKiloMeterPerHour(@mPERs double mps) {
+    return mps * 3.6d;
+  }
+
+  public static @mPERs double fromKiloMeterPerHourToMeterPerSecond(@kmPERh double kmph) {
+    return kmph / 3.6d;
+  }
+
+  // Substance
+  public static final @mol int mol = 1;
+
+  // Temperature
+  public static final @K int K = 1;
+  public static final @C int C = 1;
+
+  public static @C int fromKelvinToCelsius(@K int k) {
+    return k - (int) 273.15;
+  }
+
+  public static @K int fromCelsiusToKelvin(@C int c) {
+    return c + (int) 273.15;
+  }
+
+  // Time
+  public static final @s int s = 1;
+  public static final @min int min = 1;
+  public static final @h int h = 1;
+
+  public static @min int fromSecondToMinute(@s int s) {
+    return s / 60;
+  }
+
+  public static @s int fromMinuteToSecond(@min int min) {
+    return min * 60;
+  }
+
+  public static @h int fromMinuteToHour(@min int min) {
+    return min / 60;
+  }
+
+  public static @min int fromHourToMinute(@h int h) {
+    return h * 60;
+  }
+}
diff --git a/checker-util/src/test/java/org/checkerframework/checker/regex/util/RegexUtilTest.java b/checker-util/src/test/java/org/checkerframework/checker/regex/util/RegexUtilTest.java
new file mode 100644
index 0000000..6abf75c
--- /dev/null
+++ b/checker-util/src/test/java/org/checkerframework/checker/regex/util/RegexUtilTest.java
@@ -0,0 +1,54 @@
+// This class should be kept in sync with org.plumelib.util.RegexUtilTest in the plume-util project.
+
+package org.checkerframework.checker.regex.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public final class RegexUtilTest {
+
+  @Test
+  public void test_isRegex_and_asRegex() {
+
+    String s1 = "colo(u?)r";
+    String s2 = "(brown|beige)";
+    String s3 = "colou?r";
+    String s4 = "1) first point";
+
+    Assert.assertTrue(RegexUtil.isRegex(s1));
+    RegexUtil.asRegex(s1);
+    Assert.assertTrue(RegexUtil.isRegex(s1, 0));
+    RegexUtil.asRegex(s1, 0);
+    Assert.assertTrue(RegexUtil.isRegex(s1, 1));
+    RegexUtil.asRegex(s1, 1);
+    Assert.assertFalse(RegexUtil.isRegex(s1, 2));
+    Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s1, 2));
+
+    Assert.assertTrue(RegexUtil.isRegex(s2));
+    RegexUtil.asRegex(s2);
+    Assert.assertTrue(RegexUtil.isRegex(s2, 0));
+    RegexUtil.asRegex(s2, 0);
+    Assert.assertTrue(RegexUtil.isRegex(s2, 1));
+    RegexUtil.asRegex(s2, 1);
+    Assert.assertFalse(RegexUtil.isRegex(s2, 2));
+    Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s2, 2));
+
+    Assert.assertTrue(RegexUtil.isRegex(s3));
+    RegexUtil.asRegex(s3);
+    Assert.assertTrue(RegexUtil.isRegex(s3, 0));
+    RegexUtil.asRegex(s3, 0);
+    Assert.assertFalse(RegexUtil.isRegex(s3, 1));
+    Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s3, 1));
+    Assert.assertFalse(RegexUtil.isRegex(s3, 2));
+    Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s3, 2));
+
+    Assert.assertFalse(RegexUtil.isRegex(s4));
+    Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4));
+    Assert.assertFalse(RegexUtil.isRegex(s4, 0));
+    Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 0));
+    Assert.assertFalse(RegexUtil.isRegex(s4, 1));
+    Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 1));
+    Assert.assertFalse(RegexUtil.isRegex(s4, 2));
+    Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 2));
+  }
+}
diff --git a/checker/bin-devel/Dockerfile-README b/checker/bin-devel/Dockerfile-README
new file mode 100644
index 0000000..ddc5551
--- /dev/null
+++ b/checker/bin-devel/Dockerfile-README
@@ -0,0 +1,59 @@
+This directory contains Dockerfiles to create new Docker images for
+running Checker Framework tests reproducibly.
+
+The rest of this file explains how to build new Docker images:
+
+
+Preliminaries:
+
+  # Finish docker setup if necessary.
+  sudo usermod -aG docker $(whoami)
+  # Then log out and back in.
+
+  # Obtain Docker credentials.
+  # (This is only necessary once per machine; credentials are cached.)
+  docker login
+
+
+Create the Docker image:
+
+# Alias to create the Docker image, in an empty directory, and upload to Docker Hub.
+# Takes about 12 minutes for jdk8 or jdk11, about 1 hour for jdk8-plus or jdk11-plus.
+alias create_upload_docker_image=' \
+  rm -rf dockerdir && \
+  mkdir -p dockerdir && \
+  (cd dockerdir && \
+  \cp -pf ../Dockerfile-$OS-$JDKVER Dockerfile && \
+  docker build -t mdernst/$PROJECT-$OS-$JDKVER . && \
+  docker push mdernst/$PROJECT-$OS-$JDKVER) && \
+  rm -rf dockerdir'
+
+export OS=ubuntu
+export JDKVER=jdk8
+export PROJECT=cf
+create_upload_docker_image
+
+export OS=ubuntu
+export JDKVER=jdk8-plus
+export PROJECT=cf
+create_upload_docker_image
+
+export OS=ubuntu
+export JDKVER=jdk11
+export PROJECT=cf
+create_upload_docker_image
+
+export OS=ubuntu
+export JDKVER=jdk11-plus
+export PROJECT=cf
+create_upload_docker_image
+
+
+Cleanup:
+
+After creating docker images, consider deleting the docker containers,
+which can take up a lot of disk space.
+To stop and remove/delete all docker containers:
+  docker stop $(docker ps -a -q)
+  docker rm $(docker ps -a -q)
+or you can just remove some of them.
diff --git a/checker/bin-devel/Dockerfile-ubuntu-jdk11 b/checker/bin-devel/Dockerfile-ubuntu-jdk11
new file mode 100644
index 0000000..891a6e8
--- /dev/null
+++ b/checker/bin-devel/Dockerfile-ubuntu-jdk11
@@ -0,0 +1,36 @@
+# Create a Docker image that is ready to run the main Checker Framework tests,
+# using JDK 11.
+
+FROM ubuntu
+MAINTAINER Michael Ernst <mernst@cs.washington.edu>
+
+# According to
+# https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/:
+#  * Put "apt-get update" and "apt-get install" and apt cleanup in the same RUN command.
+#  * Do not run "apt-get upgrade"; instead get upstream to update.
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get -qqy update \
+&& apt-get -qqy install \
+  openjdk-11-jdk
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get -qqy update \
+&& apt-get -qqy install \
+  ant \
+  cpp \
+  git \
+  gradle \
+  jq \
+  libcurl3-gnutls \
+  make \
+  maven \
+  mercurial \
+  python3-pip \
+  python3-requests \
+  unzip \
+  wget
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get clean \
+&& rm -rf /var/lib/apt/lists/*
diff --git a/checker/bin-devel/Dockerfile-ubuntu-jdk11-plus b/checker/bin-devel/Dockerfile-ubuntu-jdk11-plus
new file mode 100644
index 0000000..2cdee53
--- /dev/null
+++ b/checker/bin-devel/Dockerfile-ubuntu-jdk11-plus
@@ -0,0 +1,58 @@
+# Create a Docker image that is ready to run the full Checker Framework tests,
+# including building the manual and Javadoc, using JDK 11.
+
+FROM ubuntu
+MAINTAINER Michael Ernst <mernst@cs.washington.edu>
+
+## Keep this file in sync with ../../docs/manual/troubleshooting.tex
+
+# According to
+# https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/:
+#  * Put "apt-get update" and "apt-get install" and apt cleanup in the same RUN command.
+#  * Do not run "apt-get upgrade"; instead get upstream to update.
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get -qqy update \
+&& apt-get -qqy install \
+  openjdk-11-jdk
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get -qqy update \
+&& apt-get -qqy install \
+  ant \
+  cpp \
+  git \
+  gradle \
+  jq \
+  libcurl3-gnutls \
+  make \
+  maven \
+  mercurial \
+  pdf2svg \
+  python3-pip \
+  python3-requests \
+  unzip \
+  wget
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get -qqy update \
+&& apt-get -qqy install \
+  devscripts \
+  dia \
+  hevea \
+  imagemagick \
+  latexmk \
+  librsvg2-bin \
+  rsync \
+  shellcheck \
+  texlive-font-utils \
+  texlive-fonts-recommended \
+  texlive-latex-base \
+  texlive-latex-extra \
+  texlive-latex-recommended
+
+RUN pip3 install black flake8 html5validator
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get clean \
+&& rm -rf /var/lib/apt/lists/*
diff --git a/checker/bin-devel/Dockerfile-ubuntu-jdk8 b/checker/bin-devel/Dockerfile-ubuntu-jdk8
new file mode 100644
index 0000000..31192e4
--- /dev/null
+++ b/checker/bin-devel/Dockerfile-ubuntu-jdk8
@@ -0,0 +1,37 @@
+# Create a Docker image that is ready to run the main Checker Framework tests,
+# using JDK 8.
+
+FROM ubuntu
+MAINTAINER Michael Ernst <mernst@cs.washington.edu>
+
+# According to
+# https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/:
+#  * Put "apt-get update" and "apt-get install" and apt cleanup in the same RUN command.
+#  * Do not run "apt-get upgrade"; instead get upstream to update.
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get -qqy update \
+&& apt-get -qqy install \
+  openjdk-8-jdk \
+&& update-java-alternatives --set java-1.8.0-openjdk-amd64
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get -qqy update \
+&& apt-get -qqy install \
+  ant \
+  cpp \
+  git \
+  gradle \
+  jq \
+  libcurl3-gnutls \
+  make \
+  maven \
+  mercurial \
+  python3-pip \
+  python3-requests \
+  unzip \
+  wget
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get clean \
+&& rm -rf /var/lib/apt/lists/*
diff --git a/checker/bin-devel/Dockerfile-ubuntu-jdk8-plus b/checker/bin-devel/Dockerfile-ubuntu-jdk8-plus
new file mode 100644
index 0000000..f80d04a
--- /dev/null
+++ b/checker/bin-devel/Dockerfile-ubuntu-jdk8-plus
@@ -0,0 +1,57 @@
+# Create a Docker image that is ready to run the full Checker Framework tests,
+# including building the manual and Javadoc, using JDK 8.
+
+FROM ubuntu
+MAINTAINER Michael Ernst <mernst@cs.washington.edu>
+
+# According to
+# https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/:
+#  * Put "apt-get update" and "apt-get install" and apt cleanup in the same RUN command.
+#  * Do not run "apt-get upgrade"; instead get upstream to update.
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get -qqy update \
+&& apt-get -qqy install \
+  openjdk-8-jdk \
+&& update-java-alternatives --set java-1.8.0-openjdk-amd64
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get -qqy update \
+&& apt-get -qqy install \
+  ant \
+  cpp \
+  git \
+  gradle \
+  jq \
+  libcurl3-gnutls \
+  make \
+  maven \
+  mercurial \
+  python3-pip \
+  python3-requests \
+  unzip \
+  wget
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get -qqy update \
+&& apt-get -qqy install \
+  devscripts \
+  dia \
+  hevea \
+  imagemagick \
+  latexmk \
+  librsvg2-bin \
+  pdf2svg \
+  rsync \
+  shellcheck \
+  texlive-font-utils \
+  texlive-fonts-recommended \
+  texlive-latex-base \
+  texlive-latex-extra \
+  texlive-latex-recommended
+
+RUN pip3 install black flake8 html5validator
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+&& apt-get clean \
+&& rm -rf /var/lib/apt/lists/*
diff --git a/checker/bin-devel/Makefile b/checker/bin-devel/Makefile
new file mode 100644
index 0000000..40e3ece
--- /dev/null
+++ b/checker/bin-devel/Makefile
@@ -0,0 +1,10 @@
+SH_SCRIPTS = $(shell grep -r -l '^\#! \?\(/bin/\|/usr/bin/env \)sh' * | grep -v .git | grep -v "~")
+BASH_SCRIPTS = $(shell grep -r -l '^\#! \?\(/bin/\|/usr/bin/env \)bash' * | grep -v .git | grep -v "~")
+
+shell-script-style:
+	shellcheck --format=gcc ${SH_SCRIPTS} ${BASH_SCRIPTS}
+	checkbashisms ${SH_SCRIPTS}
+
+showvars:
+	@echo "SH_SCRIPTS=${SH_SCRIPTS}"
+	@echo "BASH_SCRIPTS=${BASH_SCRIPTS}"
diff --git a/checker/bin-devel/README b/checker/bin-devel/README
new file mode 100644
index 0000000..cf96a65
--- /dev/null
+++ b/checker/bin-devel/README
@@ -0,0 +1,4 @@
+This directory contains a version of the scripts suitable for Checker
+Framework developers.  The scripts use locally-compiled .class files in
+preference to .jar files, because just creating .class files leads to a
+faster edit-compile-test cycle than generating .jar files.
diff --git a/checker/bin-devel/build.sh b/checker/bin-devel/build.sh
new file mode 100755
index 0000000..69c0e03
--- /dev/null
+++ b/checker/bin-devel/build.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+
+echo Entering checker/bin-devel/build.sh in "$(pwd)"
+
+# Fail the whole script if any command fails
+set -e
+
+echo "initial CHECKERFRAMEWORK=$CHECKERFRAMEWORK"
+export CHECKERFRAMEWORK="${CHECKERFRAMEWORK:-$(pwd -P)}"
+echo "CHECKERFRAMEWORK=$CHECKERFRAMEWORK"
+
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+if [ "$(uname)" == "Darwin" ] ; then
+  export JAVA_HOME=${JAVA_HOME:-$(/usr/libexec/java_home)}
+else
+  # shellcheck disable=SC2230
+  export JAVA_HOME=${JAVA_HOME:-$(dirname "$(dirname "$(readlink -f "$(which javac)")")")}
+fi
+echo "JAVA_HOME=${JAVA_HOME}"
+
+# Using `(cd "$CHECKERFRAMEWORK" && ./gradlew getPlumeScripts -q)` leads to infinite regress.
+PLUME_SCRIPTS="$CHECKERFRAMEWORK/checker/bin-devel/.plume-scripts"
+if [ -d "$PLUME_SCRIPTS" ] ; then
+  (cd "$PLUME_SCRIPTS" && git pull -q)
+else
+  (cd "$CHECKERFRAMEWORK/checker/bin-devel" && \
+      (git clone --depth 1 -q https://github.com/plume-lib/plume-scripts.git .plume-scripts || \
+       git clone --depth 1 -q https://github.com/plume-lib/plume-scripts.git .plume-scripts))
+fi
+
+# Clone the annotated JDK into ../jdk .
+"$PLUME_SCRIPTS/git-clone-related" typetools jdk
+
+AFU="${AFU:-../annotation-tools/annotation-file-utilities}"
+# Don't use `AT=${AFU}/..` which causes a git failure.
+AT=$(dirname "${AFU}")
+
+## Build annotation-tools (Annotation File Utilities)
+"$PLUME_SCRIPTS/git-clone-related" typetools annotation-tools "${AT}"
+if [ ! -d ../annotation-tools ] ; then
+  ln -s "${AT}" ../annotation-tools
+fi
+
+echo "Running:  (cd ${AT} && ./.travis-build-without-test.sh)"
+(cd "${AT}" && ./.travis-build-without-test.sh)
+echo "... done: (cd ${AT} && ./.travis-build-without-test.sh)"
+
+
+## Build stubparser
+"$PLUME_SCRIPTS/git-clone-related" typetools stubparser
+echo "Running:  (cd ../stubparser/ && ./.travis-build-without-test.sh)"
+(cd ../stubparser/ && ./.travis-build-without-test.sh)
+echo "... done: (cd ../stubparser/ && ./.travis-build-without-test.sh)"
+
+
+## Build JSpecify, only for the purpose of using its tests.
+"$PLUME_SCRIPTS/git-clone-related" jspecify jspecify
+if type -p java; then
+  _java=java
+elif [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]];  then
+  _java="$JAVA_HOME/bin/java"
+else
+  echo "Can't find java"
+  exit 1
+fi
+version=$("$_java" -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1)
+if [[ "$version" -ge 9 ]]; then
+  echo "Running:  (cd ../jspecify/ && ./gradlew build)"
+  ## Try twice in case of network lossage.
+  (cd ../jspecify/ && ./gradlew build) || (sleep 60 && cd ../jspecify/ && ./gradlew build)
+  echo "... done: (cd ../jspecify/ && ./gradlew build)"
+fi
+
+
+## Compile
+
+# Downloading the gradle wrapper sometimes fails.
+# If so, the next command gets another chance to try the download.
+(./gradlew help || sleep 10) > /dev/null 2>&1
+
+echo "running \"./gradlew assemble\" for checker-framework"
+./gradlew assemble --console=plain --warning-mode=all -s --no-daemon -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.http.connectionTimeout=60000
+
+echo Exiting checker/bin-devel/build.sh in "$(pwd)"
diff --git a/checker/bin-devel/count-suppressions b/checker/bin-devel/count-suppressions
new file mode 100755
index 0000000..aa06308
--- /dev/null
+++ b/checker/bin-devel/count-suppressions
@@ -0,0 +1,94 @@
+#!/bin/bash
+
+# This command counts the approximate frequency of each distinct reason for
+# warning suppressions, in all files under the current directory.
+# To invoke it, pass a type system name; for example:
+#   count-suppressions nullness
+
+# If warning suppression text contains a colon, this script prints only
+# the text before the colon, under the assumption that the initial text
+# is a category name.
+
+# This script is useful to determine the most frequent reasons for warning
+# suppressions, to help checker developers decide what featuers to add to
+# their type systems.  However, use common.util.count.AnnotationStatistics
+# to count the total number of warning suppressions (for example, to report
+# in a paper), because this script gives only an approximate count.
+
+# To debug, pass --debug as the first command-line argument.
+debug=0
+
+if [[ "$OSTYPE" == "darwin"* ]]; then
+    SED="gsed"
+    GREP="ggrep"
+else
+    SED="sed"
+    GREP="grep"
+fi
+
+if [ "$1" == "--debug" ] ; then
+    debug=1
+    shift
+fi
+
+if [ "$#" -ne 1 ]; then
+  echo "Usage: $0 TYPESYSTEM" >&2
+  exit 1
+fi
+
+regex="$1"
+
+# If argument is a compound checker, make the regex match them all.
+if [ "$regex" = "nullness" ]; then
+    regex="\(nullness\|initialization\|keyfor\)"
+fi
+if [ "$regex" = "index" ]; then
+    regex="\(index\|lessthan\|lowerbound\|samelen\|searchindex\|substringindex\|upperbound\)"
+fi
+
+# Diagnostics
+# echo "regex=${regex}"
+
+greplines=$(mktemp /tmp/count-suppressions."$(date +%Y%m%d-%H%M%S)"-XXX)
+countedreasons=$(mktemp /tmp/count-suppressions."$(date +%Y%m%d-%H%M%S)"-XXX)
+
+# These are the two types of matching lines:
+#  * "checkername" or "chekername:..."
+#    This matches occurrences within @SuppressWarnings.  The regex does not
+#    include "@SuppressWarnings" because it might appear on the previous line.
+#  * @AssumeAssertion(checkername)
+# This grep command captures a few stray lines; users should ignore them.
+# This grep command assumes that tests are not annotated, and it hard-codes ignoring "annotated-jdk", "jdk", and "true positive".
+${GREP} -n --recursive --include='*.java' "\"${regex}[:\"]\(.*[^;]\)\?\(\$\|//\)\|@AssumeAssertion(${regex})" \
+    | grep -v "@AnnotatedFor" | grep -v "/tests/" \
+    | grep -v "/annotated-jdk/" | grep -v "/jdk/" | grep -v "^jdk/" | grep -v "true positive" > "${greplines}"
+
+total=$(wc -l < "${greplines}")
+## Don't output a total, to avoid people using this approximate count.
+# echo "Total: $total"
+# shellcheck disable=SC2002
+cat "${greplines}" \
+    | ${SED} 's/.*\/\/ //g' \
+    | ${SED} "s/.*@AssumeAssertion([^)])[ :]*\([^\"]\+\)\";/\1/g" \
+    | ${SED} 's/\([^0-9]\): [^:].*/\1/' \
+    | ${SED} 's/ \+$//' \
+    | sort | uniq -c | sort -rg > "${countedreasons}"
+
+# Add leading percentages to `uniq -c` output.  Note that it rounds down to the nearest integer.
+# (Digits afert the decimal don't make a practical difference.)
+while read -r line; do
+    count=$(echo "$line" | cut -f1 -d " ");
+    content=$(echo "$line" | cut -f2- -d " ");
+    percent=$(echo "scale=0; (100*$count/$total);" | bc);
+    if [ "$debug" -eq "0" ]; then
+        printf "%d%%\t%s\n" "$percent" "$content";
+    else
+        printf "%d%%\t%s\t%s\n" "$percent" "$count" "$content";
+    fi;
+done < "${countedreasons}";
+
+if [ "$debug" -eq "0" ]; then
+  rm -f "${greplines}" "${countedreasons}"
+else
+  echo "Total is $total, Intermediate output is in: ${greplines} ${countedreasons}"
+fi
diff --git a/checker/bin-devel/git.post-merge b/checker/bin-devel/git.post-merge
new file mode 100755
index 0000000..1f477c3
--- /dev/null
+++ b/checker/bin-devel/git.post-merge
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# This file will be used as .git/hooks/post-merge.
+# However, it should be edited as checker/bin-devel/git.post-merge.
+
+# Fail if any command fails
+set -e
+
+# This doesn't seem't necessary.
+# ant -e check-style
diff --git a/checker/bin-devel/git.pre-commit b/checker/bin-devel/git.pre-commit
new file mode 100755
index 0000000..f714056
--- /dev/null
+++ b/checker/bin-devel/git.pre-commit
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+# This file will be used as ../../.git/hooks/pre-commit.
+# However, it should be edited as checker/bin-devel/git.pre-commit.
+# You can install it by running: (cd .. && ./gradlew installGitHooks)
+
+# Fail if any command fails
+set -e
+
+# On commit we only need to check files that changed.
+# Need to keep checked files in sync with getJavaFilesToFormat in build.gradle.
+# Otherwise `./gradlew reformat` might not reformat a file that this
+# hook complains about.
+CHANGED_JAVA_FILES=$(git diff --staged --name-only --diff-filter=ACM | grep '\.java$' | grep -v '/jdk/' | grep -v 'stubparser/' | grep -v '/nullness-javac-errors/' | grep -v 'dataflow/manual/examples/' ) || true
+# echo "CHANGED_JAVA_FILES=${CHANGED_JAVA_FILES}"
+if [ -n "$CHANGED_JAVA_FILES" ]; then
+    ./gradlew getCodeFormatScripts -q
+    ## For debugging:
+    # echo "CHANGED_JAVA_FILES: ${CHANGED_JAVA_FILES}"
+
+    # shellcheck disable=SC2086
+    python3 checker/bin-devel/.run-google-java-format/check-google-java-format.py ${CHANGED_JAVA_FILES} || (echo "Problem in pre-commit.  Try running: ./gradlew reformat" && /bin/false)
+
+    BRANCH=$(git rev-parse --abbrev-ref HEAD)
+    if [ "$BRANCH" = "master" ]; then
+        git diff --staged > /tmp/diff.txt
+        ./gradlew getPlumeScripts -q
+        (./gradlew requireJavadoc > /tmp/warnings-rjp.txt 2>&1) || true
+        checker/bin-devel/.plume-scripts/lint-diff.py --guess-strip /tmp/diff.txt /tmp/warnings-rjp.txt
+        (./gradlew javadocDoclintAll > /tmp/warnings-jda.txt 2>&1) || true
+        checker/bin-devel/.plume-scripts/lint-diff.py --guess-strip /tmp/diff.txt /tmp/warnings-jda.txt
+    fi
+fi
+
+# This is to handle non-.java files, since the above already handled .java files.
+# May need to remove files that are allowed to have trailing whitespace or are
+# not text files.
+CHANGED_FILES=$(git diff --staged --name-only --diff-filter=ACM | grep -v '\.class$' | grep -v '\.gz$' | grep -v '\.jar$' | grep -v '\.pdf$' | grep -v '\.png$' | grep -v '\.xcf$') || true
+if [ -n "$CHANGED_FILES" ]; then
+    ## For debugging:
+    # echo "CHANGED_FILES: ${CHANGED_FILES}"
+
+    # shellcheck disable=SC2086
+    FILES_WITH_TRAILING_SPACES=$(grep -l -s '[[:blank:]]$' ${CHANGED_FILES} 2>&1) || true
+    if [ -n "$FILES_WITH_TRAILING_SPACES" ]; then
+        echo "Some files have trailing whitespace: ${FILES_WITH_TRAILING_SPACES}" && exit 1
+    fi
+fi
diff --git a/checker/bin-devel/test-cftests-all.sh b/checker/bin-devel/test-cftests-all.sh
new file mode 100755
index 0000000..e5f2af2
--- /dev/null
+++ b/checker/bin-devel/test-cftests-all.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+# This script test-cftests-all.sh = tests-cftests-junit.sh + tests-cftests-nonjunit.sh + tests-cftests-inference.sh .
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+
+./gradlew allTests --console=plain --warning-mode=all --no-daemon
+# Moved example-tests out of all tests because it fails in
+# the release script because the newest maven artifacts are not published yet.
+./gradlew :checker:exampleTests --console=plain --warning-mode=all --no-daemon
diff --git a/checker/bin-devel/test-cftests-inference.sh b/checker/bin-devel/test-cftests-inference.sh
new file mode 100755
index 0000000..0ead9a8
--- /dev/null
+++ b/checker/bin-devel/test-cftests-inference.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+
+./gradlew inferenceTests --console=plain --warning-mode=all --no-daemon
diff --git a/checker/bin-devel/test-cftests-junit.sh b/checker/bin-devel/test-cftests-junit.sh
new file mode 100755
index 0000000..3ac347f
--- /dev/null
+++ b/checker/bin-devel/test-cftests-junit.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+
+./gradlew test --console=plain --warning-mode=all --no-daemon
diff --git a/checker/bin-devel/test-cftests-nonjunit.sh b/checker/bin-devel/test-cftests-nonjunit.sh
new file mode 100755
index 0000000..fb203ad
--- /dev/null
+++ b/checker/bin-devel/test-cftests-nonjunit.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+
+./gradlew nonJunitTests --console=plain --warning-mode=all --no-daemon
+# Moved example-tests out of all tests because it fails in
+# the release script because the newest maven artifacts are not published yet.
+./gradlew :checker:exampleTests --console=plain --warning-mode=all --no-daemon
diff --git a/checker/bin-devel/test-daikon-part1.sh b/checker/bin-devel/test-daikon-part1.sh
new file mode 100755
index 0000000..de6cbc9
--- /dev/null
+++ b/checker/bin-devel/test-daikon-part1.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+# daikon-typecheck: 15 minutes
+"$SCRIPTDIR/.plume-scripts/git-clone-related" codespecs daikon
+cd ../daikon
+git log | head -n 5
+make compile
+if [ "$TRAVIS" = "true" ] ; then
+  # Travis kills a job if it runs 10 minutes without output
+  time make JAVACHECK_EXTRA_ARGS=-Afilenames -C java typecheck-part1
+else
+  time make -C java typecheck-part1
+fi
diff --git a/checker/bin-devel/test-daikon-part2.sh b/checker/bin-devel/test-daikon-part2.sh
new file mode 100755
index 0000000..59afa2a
--- /dev/null
+++ b/checker/bin-devel/test-daikon-part2.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+# daikon-typecheck: 15 minutes
+"$SCRIPTDIR/.plume-scripts/git-clone-related" codespecs daikon
+cd ../daikon
+git log | head -n 5
+make compile
+if [ "$TRAVIS" = "true" ] ; then
+  # Travis kills a job if it runs 10 minutes without output
+  time make JAVACHECK_EXTRA_ARGS=-Afilenames -C java typecheck-part2
+else
+  time make -C java typecheck-part2
+fi
diff --git a/checker/bin-devel/test-daikon.sh b/checker/bin-devel/test-daikon.sh
new file mode 100755
index 0000000..7e660c7
--- /dev/null
+++ b/checker/bin-devel/test-daikon.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+# daikon-typecheck: 15 minutes
+"$SCRIPTDIR/.plume-scripts/git-clone-related" codespecs daikon
+cd ../daikon
+git log | head -n 5
+make compile
+if [ "$TRAVIS" = "true" ] ; then
+  # Travis kills a job if it runs 10 minutes without output
+  time make JAVACHECK_EXTRA_ARGS=-Afilenames -C java typecheck
+else
+  time make -C java typecheck
+fi
diff --git a/checker/bin-devel/test-downstream.sh b/checker/bin-devel/test-downstream.sh
new file mode 100755
index 0000000..d1a9a51
--- /dev/null
+++ b/checker/bin-devel/test-downstream.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+## downstream tests:  projects that depend on the Checker Framework.
+## These are here so they can be run by pull requests.  (Pull requests
+## currently don't trigger downstream jobs.)
+## Exceptions:
+##  * plume-lib is run by test-plume-lib.sh
+##  * daikon-typecheck is run as a separate CI project
+##  * guava is run as a separate CI project
+
+## This is moved to misc, because otherwise it would be the only work done by this script.
+# # Checker Framework demos
+# "$SCRIPTDIR/.plume-scripts/git-clone-related" typetools checker-framework.demos
+# ./gradlew :checker:demosTests --console=plain --warning-mode=all --no-daemon
diff --git a/checker/bin-devel/test-guava-formatter.sh b/checker/bin-devel/test-guava-formatter.sh
new file mode 100755
index 0000000..fd09305
--- /dev/null
+++ b/checker/bin-devel/test-guava-formatter.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+"$SCRIPTDIR/.plume-scripts/git-clone-related" typetools guava
+cd ../guava
+
+./typecheck.sh formatter
diff --git a/checker/bin-devel/test-guava-index.sh b/checker/bin-devel/test-guava-index.sh
new file mode 100755
index 0000000..0b8f8f9
--- /dev/null
+++ b/checker/bin-devel/test-guava-index.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+"$SCRIPTDIR/.plume-scripts/git-clone-related" typetools guava
+cd ../guava
+
+if [ "$TRAVIS" = "true" ] ; then
+  # Travis which kills jobs that have not produced output for 10 minutes.
+  echo "Setting up sleep-and-output jobs for Travis"
+  (sleep 1s && echo "1 second has elapsed") &
+  (sleep 5m && echo "5 minutes have elapsed") &
+  (sleep 14m && echo "14 minutes have elapsed") &
+  (sleep 23m && echo "23 minutes have elapsed") &
+fi
+
+./typecheck.sh index
diff --git a/checker/bin-devel/test-guava-interning.sh b/checker/bin-devel/test-guava-interning.sh
new file mode 100755
index 0000000..d1670b1
--- /dev/null
+++ b/checker/bin-devel/test-guava-interning.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+"$SCRIPTDIR/.plume-scripts/git-clone-related" typetools guava
+cd ../guava
+
+./typecheck.sh interning
diff --git a/checker/bin-devel/test-guava-lock.sh b/checker/bin-devel/test-guava-lock.sh
new file mode 100755
index 0000000..79528d9
--- /dev/null
+++ b/checker/bin-devel/test-guava-lock.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+"$SCRIPTDIR/.plume-scripts/git-clone-related" typetools guava
+cd ../guava
+
+./typecheck.sh lock
diff --git a/checker/bin-devel/test-guava-nullness.sh b/checker/bin-devel/test-guava-nullness.sh
new file mode 100755
index 0000000..d14f17f
--- /dev/null
+++ b/checker/bin-devel/test-guava-nullness.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+"$SCRIPTDIR/.plume-scripts/git-clone-related" typetools guava
+cd ../guava
+
+./typecheck.sh nullness
diff --git a/checker/bin-devel/test-guava-regex.sh b/checker/bin-devel/test-guava-regex.sh
new file mode 100755
index 0000000..0b7f662
--- /dev/null
+++ b/checker/bin-devel/test-guava-regex.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+"$SCRIPTDIR/.plume-scripts/git-clone-related" typetools guava
+cd ../guava
+
+./typecheck.sh regex
diff --git a/checker/bin-devel/test-guava-signature.sh b/checker/bin-devel/test-guava-signature.sh
new file mode 100755
index 0000000..3f6e5f7
--- /dev/null
+++ b/checker/bin-devel/test-guava-signature.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+"$SCRIPTDIR/.plume-scripts/git-clone-related" typetools guava
+cd ../guava
+
+./typecheck.sh signature
diff --git a/checker/bin-devel/test-guava.sh b/checker/bin-devel/test-guava.sh
new file mode 100755
index 0000000..558ce13
--- /dev/null
+++ b/checker/bin-devel/test-guava.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+"$SCRIPTDIR/.plume-scripts/git-clone-related" typetools guava
+cd ../guava
+
+if [ "$TRAVIS" = "true" ] ; then
+  # There are two reasons that this script does not work on Travis.
+  # 1. Travis kills jobs that do not produce output for 10 minutes.  (This can be worked around.)
+  # 2. Travis kills jobs that produce too much output.  (This cannot be worked around.)
+  echo "On Travis, use scripts that run just one type-checker."
+  exit 1
+fi
+
+## This command works locally, but on Azure it fails with timouts while downloading Maven dependencies.
+# cd guava && time mvn --debug -B package -P checkerframework-local -Dmaven.test.skip=true -Danimal.sniffer.skip=true
+
+cd guava && time mvn --debug -B compile -P checkerframework-local
diff --git a/checker/bin-devel/test-misc.sh b/checker/bin-devel/test-misc.sh
new file mode 100755
index 0000000..85f711e
--- /dev/null
+++ b/checker/bin-devel/test-misc.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+PLUME_SCRIPTS="$SCRIPTDIR/.plume-scripts"
+
+## Checker Framework demos
+"$PLUME_SCRIPTS/git-clone-related" typetools checker-framework.demos
+./gradlew :checker:demosTests --console=plain --warning-mode=all --no-daemon
+
+status=0
+
+## Code style and formatting
+./gradlew checkBasicStyle checkFormat --console=plain --warning-mode=all --no-daemon
+if grep -n -r --exclude-dir=build --exclude-dir=examples --exclude-dir=jtreg --exclude-dir=tests --exclude="*.astub" --exclude="*.tex" '^\(import static \|import .*\*;$\)'; then
+  echo "Don't use static import or wildcard import"
+  exit 1
+fi
+make -C checker/bin
+make -C checker/bin-devel
+make -C docs/developer/release
+
+## HTML legality
+./gradlew htmlValidate --console=plain --warning-mode=all --no-daemon
+
+## Javadoc documentation
+# Try twice in case of network lossage.
+(./gradlew javadoc --console=plain --warning-mode=all --no-daemon || (sleep 60 && ./gradlew javadoc --console=plain --warning-mode=all --no-daemon)) || status=1
+./gradlew javadocPrivate --console=plain --warning-mode=all --no-daemon || status=1
+# For refactorings that touch a lot of code that you don't understand, create
+# top-level file SKIP-REQUIRE-JAVADOC.  Delete it after the pull request is merged.
+if [ ! -f SKIP-REQUIRE-JAVADOC ]; then
+  (./gradlew requireJavadoc --console=plain --warning-mode=all --no-daemon > /tmp/warnings-rjp.txt 2>&1) || true
+  "$PLUME_SCRIPTS"/ci-lint-diff /tmp/warnings-rjp.txt || status=1
+  (./gradlew javadocDoclintAll --console=plain --warning-mode=all --no-daemon > /tmp/warnings-jda.txt 2>&1) || true
+  "$PLUME_SCRIPTS"/ci-lint-diff /tmp/warnings-jda.txt || status=1
+fi
+if [ $status -ne 0 ]; then exit $status; fi
+
+
+## User documentation
+./gradlew manual
+git diff  --exit-code docs/manual/contributors.tex || \
+    (set +x && set +v &&
+     echo "docs/manual/contributors.tex is not up to date." &&
+     echo "If the above suggestion is appropriate, run: make -C docs/manual contributors.tex" &&
+     echo "If the suggestion contains a username rather than a human name, then do all the following:" &&
+     echo "  * Update your git configuration by running:  git config --global user.name \"YOURFULLNAME\"" &&
+     echo "  * Add your name to your GitHub account profile at https://github.com/settings/profile" &&
+     echo "  * Make a pull request to add your GitHub ID to" &&
+     echo "    https://github.com/plume-lib/plume-scripts/blob/master/git-authors.sed" &&
+     echo "    and remake contributors.tex after that pull request is merged." &&
+     false)
diff --git a/checker/bin-devel/test-plume-lib.sh b/checker/bin-devel/test-plume-lib.sh
new file mode 100755
index 0000000..d1c2e7a
--- /dev/null
+++ b/checker/bin-devel/test-plume-lib.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+# Optional argument $1 is the group.
+GROUPARG=$1
+echo "GROUPARG=$GROUPARG"
+# These are all the Java projects at https://github.com/plume-lib
+if [[ "${GROUPARG}" == "bcel-util" ]]; then PACKAGES=("${GROUPARG}"); fi
+if [[ "${GROUPARG}" == "bibtex-clean" ]]; then PACKAGES=("${GROUPARG}"); fi
+if [[ "${GROUPARG}" == "html-pretty-print" ]]; then PACKAGES=("${GROUPARG}"); fi
+if [[ "${GROUPARG}" == "icalavailable" ]]; then PACKAGES=("${GROUPARG}"); fi
+if [[ "${GROUPARG}" == "lookup" ]]; then PACKAGES=("${GROUPARG}"); fi
+if [[ "${GROUPARG}" == "multi-version-control" ]]; then PACKAGES=("${GROUPARG}"); fi
+if [[ "${GROUPARG}" == "options" ]]; then PACKAGES=("${GROUPARG}"); fi
+if [[ "${GROUPARG}" == "plume-util" ]]; then PACKAGES=("${GROUPARG}"); fi
+if [[ "${GROUPARG}" == "require-javadoc" ]]; then PACKAGES=("${GROUPARG}"); fi
+if [[ "${GROUPARG}" == "signature-util" ]]; then PACKAGES=("${GROUPARG}"); fi
+if [[ "${GROUPARG}" == "all" ]] || [[ "${GROUPARG}" == "" ]]; then
+    if java -version 2>&1 | grep version | grep 1.8 ; then
+        PACKAGES=(bcel-util bibtex-clean html-pretty-print icalavailable lookup multi-version-control options plume-util require-javadoc)
+    else
+        PACKAGES=(bcel-util bibtex-clean html-pretty-print icalavailable lookup multi-version-control options plume-util)
+    fi
+fi
+if [ -z ${PACKAGES+x} ]; then
+  echo "Bad group argument '${GROUPARG}'"
+  exit 1
+fi
+echo "PACKAGES=" "${PACKAGES[@]}"
+
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+echo "PACKAGES=" "${PACKAGES[@]}"
+for PACKAGE in "${PACKAGES[@]}"; do
+  echo "PACKAGE=${PACKAGE}"
+  PACKAGEDIR="/tmp/${PACKAGE}"
+  rm -rf "${PACKAGEDIR}"
+  "$SCRIPTDIR/.plume-scripts/git-clone-related" plume-lib "${PACKAGE}" "${PACKAGEDIR}"
+  # Uses "compileJava" target instead of "assemble" to avoid the javadoc error "Error fetching URL:
+  # https://docs.oracle.com/en/java/javase/11/docs/api/" due to network problems.
+  echo "About to call ./gradlew --console=plain -PcfLocal compileJava"
+  # Try twice in case of network lossage while downloading packages (e.g., from Maven Central).
+  # A disadvantage is that if there is a real error in pluggable type-checking, this runs it twice
+  # and puts a delays in between.
+  (cd "${PACKAGEDIR}" && (./gradlew --console=plain -PcfLocal compileJava || (sleep 60 && ./gradlew --console=plain -PcfLocal compileJava)))
+done
diff --git a/checker/bin-devel/test-typecheck.sh b/checker/bin-devel/test-typecheck.sh
new file mode 100755
index 0000000..b612d95
--- /dev/null
+++ b/checker/bin-devel/test-typecheck.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+set -e
+set -o verbose
+set -o xtrace
+export SHELLOPTS
+echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally)
+# shellcheck disable=SC1090
+source "$SCRIPTDIR"/build.sh
+
+
+# Pluggable type-checking:  run the Checker Framework on itself
+./gradlew typecheck --console=plain --warning-mode=all --no-daemon
diff --git a/checker/bin-devel/wpi-plumelib/README b/checker/bin-devel/wpi-plumelib/README
new file mode 100644
index 0000000..fd289da
--- /dev/null
+++ b/checker/bin-devel/wpi-plumelib/README
@@ -0,0 +1,31 @@
+This directory contains the expected result of type-checking projects from
+https://github.com/plume-lib/, after whole-program inference.
+The script `test-wpi-plumelib.sh` runs the tests.
+
+If the tests fail, you may need to fix a problem with whole-program
+inference, or you might need to edit the `*.expected` files in this
+directory.  Those files are just like the `typecheck.out` file created by
+running wpi.sh, with two permitted additions:
+ * comments (a line starting with "#") to explain type-checking errors
+ * blank lines to improve readability
+
+The script `test-wpi-plumelib.sh` complements the Gradle `wpiManyTests`
+task.  Here are differences:
+ * different projects use a different list of type-checkers.
+   (wpi-many.sh uses a fixed set of type-checkers for all projects)
+ * this uses the HEAD commit
+   (wpi-many.sh uses a fixed commit)
+ * this checks for expected type-checking errors
+   (the Gradle wpiManyTests target requires that there are no errors, so it
+   skips type systems for which inference does not currently work)
+
+The use of the HEAD commit makes these tests brittle.  They might fail if:
+ * The Checker Framework is changed in a way that makes whole-program
+   inference fail to infer all needed annotations.
+ * A plume-lib project is changed in a way that exposes a limitation of
+   whole-program inference.
+
+A test failure always indicates a limitation of whole-program inference, or
+the removal of a limitation.  It is unfortunate that commits to a different
+repository can make the Checker Framework tests fail, but that is the price
+of staying at the head commit.
diff --git a/checker/bin-devel/wpi-plumelib/bcel-util.expected b/checker/bin-devel/wpi-plumelib/bcel-util.expected
new file mode 100644
index 0000000..9c3d113
--- /dev/null
+++ b/checker/bin-devel/wpi-plumelib/bcel-util.expected
@@ -0,0 +1,13 @@
+warning: No processor claimed any of these annotations: org.checkerframework.checker.nullness.qual.EnsuresNonNull,org.checkerframework.checker.nullness.qual.RequiresNonNull
+
+#
+# This is in method fqBinaryNameToType, but there are no calls to it, so WPI didn't infer a type for its formal parameter.
+#
+src/main/java/org/plumelib/bcelutil/BcelUtil.java:777: error: [argument] incompatible argument for parameter typename of parseFqBinaryName.
+        Signatures.ClassnameAndDimensions.parseFqBinaryName(classname);
+                                                            ^
+  found   : @SignatureUnknown String
+  required: @FqBinaryName String
+
+1 error
+1 warning
diff --git a/checker/bin-devel/wpi-plumelib/bibtex-clean.expected b/checker/bin-devel/wpi-plumelib/bibtex-clean.expected
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/checker/bin-devel/wpi-plumelib/bibtex-clean.expected
diff --git a/checker/bin-devel/wpi-plumelib/html-pretty-print.expected b/checker/bin-devel/wpi-plumelib/html-pretty-print.expected
new file mode 100644
index 0000000..79e90eb
--- /dev/null
+++ b/checker/bin-devel/wpi-plumelib/html-pretty-print.expected
@@ -0,0 +1 @@
+4 warnings
diff --git a/checker/bin-devel/wpi-plumelib/icalavailable.expected b/checker/bin-devel/wpi-plumelib/icalavailable.expected
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/checker/bin-devel/wpi-plumelib/icalavailable.expected
diff --git a/checker/bin-devel/wpi-plumelib/lookup.expected b/checker/bin-devel/wpi-plumelib/lookup.expected
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/checker/bin-devel/wpi-plumelib/lookup.expected
diff --git a/checker/bin-devel/wpi-plumelib/options.expected b/checker/bin-devel/wpi-plumelib/options.expected
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/checker/bin-devel/wpi-plumelib/options.expected
diff --git a/checker/bin-devel/wpi-plumelib/test-wpi-plumelib.sh b/checker/bin-devel/wpi-plumelib/test-wpi-plumelib.sh
new file mode 100755
index 0000000..373d9f8
--- /dev/null
+++ b/checker/bin-devel/wpi-plumelib/test-wpi-plumelib.sh
@@ -0,0 +1,107 @@
+#!/bin/sh
+
+# Run wpi.sh on plume-lib projects.
+# For each project:
+#  * clone it
+#  * remove its annotations
+#  * run WPI to infer annotations
+#  * type-check the annotated version
+#  * check that the output of type-checking is the same as the *.expected file in this directory
+# Afterward, the inferred annotations can be found in a directory named /tmp/wpi-stubs-XXXXXX .
+# The exact directory name is the last directory in the -Astubs= argument in file
+# checker-framework/checker/build/wpi-plumelib-tests/PROJECTNAME/dljc-out/typecheck.out .
+
+# This script is run by `./gradlew wpiPlumeLibTests` at the top level.
+
+# wpi.sh may exit with non-zero status.
+set +e
+
+# set -o verbose
+# set -o xtrace
+export SHELLOPTS
+# echo "SHELLOPTS=${SHELLOPTS}"
+
+SCRIPTDIR="$(cd "$(dirname "$0")" && pwd)"
+CHECKERFRAMEWORK="$(cd "$(dirname "$0")"/../../.. && pwd)"
+
+# Do not use a subdirectory of $CHECKERFRAMEWORK because if a project has no
+# `settings.gradle` file, Gradle will find one in $CHECKERFRAMEWORK.
+TESTDIR=$(mktemp -d "${TMPDIR:-/tmp}"/wpi-plumelib-tests-"$(date +%Y%m%d-%H%M%S)"-XXX)
+
+# Takes two arguments, an input file (produced by compilation) and an output file.
+# Copies the input to the output, removing parts that might differ from run to run.
+clean_compile_output() {
+    in="$1"
+    out="$2"
+
+    cp -f "$in" "$out" || exit 1
+
+    # Remove "Running ..." line
+    sed -i '/^Running /d' "$out"
+
+    # Remove comments starting with "#" and blank lines
+    sed -i '/^#/d' "$out"
+    sed -i '/^$/d' "$out"
+
+    # Remove uninteresting output
+    sed -i '/^warning: \[path\] bad path element /d' "$out"
+
+    # Remove directory names and line numbers
+    sed -i 's/^[^ ]*\///' "$out"
+    sed -i 's/:[0-9]+: /: /' "$out"
+}
+
+# Takes two arguments, the project name and the comma-separated list of checkers to run.
+test_wpi_plume_lib() {
+    project="$1"
+    checkers="$2"
+
+    rm -rf "$project"
+    # Try twice in case of network lossage
+    git clone -q --depth 1 "https://github.com/plume-lib/$project.git" || (sleep 60 && git clone -q --depth 1 "https://github.com/plume-lib/$project.git")
+
+    cd "$project" || (echo "can't run: cd $project" && exit 1)
+
+    java -cp "$CHECKERFRAMEWORK/checker/dist/checker.jar" org.checkerframework.framework.stub.RemoveAnnotationsForInference . || exit 1
+    # The project may not build after running RemoveAnnotationsForInference, because some casts
+    # may become redundant and javac -Xlint:all yields "warning: [cast] redundant cast to ...".
+    "$CHECKERFRAMEWORK"/checker/bin-devel/.plume-scripts/preplace -- "-Xlint:" "-Xlint:-cast," build.gradle
+
+    "$CHECKERFRAMEWORK/checker/bin/wpi.sh" -b "-PskipCheckerFramework" -- --checker "$checkers" --extraJavacArgs='-AsuppressWarnings=type.checking.not.run'
+
+    EXPECTED_FILE="$SCRIPTDIR/$project.expected"
+    DLJC_OUT_DIR="$TESTDIR/$project/dljc-out"
+    ACTUAL_FILE="$DLJC_OUT_DIR"/typecheck.out
+    touch "${ACTUAL_FILE}"
+    clean_compile_output "$EXPECTED_FILE" "expected.txt"
+    clean_compile_output "$ACTUAL_FILE" "actual.txt"
+    if ! cmp --quiet expected.txt actual.txt ; then
+      echo "Comparing $EXPECTED_FILE $ACTUAL_FILE in $(pwd)"
+      diff -u expected.txt actual.txt
+      if [ -n "$AZURE_HTTP_USER_AGENT" ] || [ -n "$CIRCLE_PR_USERNAME" ] || [ -n "$GITHUB_HEAD_REF" ] || [ "$TRAVIS" = "true" ] ; then
+        # Running under continuous integration.  Output files that may be useful for debugging.
+        more "$TESTDIR/$project"/dljc-out/*
+        STUBDIR="$(sed -n 's/Directory for generated stub files: \(.*\)$/\1/p' "$DLJC_OUT_DIR"/dljc-stdout-*)"
+        echo "STUBDIR=$STUBDIR"
+        find "$STUBDIR" -type f -print0 | xargs -0 more
+      fi
+      exit 1
+    fi
+
+    cd ..
+}
+
+
+mkdir -p "$TESTDIR"
+cd "$TESTDIR" || (echo "can't do: cd $TESTDIR" && exit 1)
+
+# Get the list of checkers from the project's build.gradle file
+test_wpi_plume_lib bcel-util         "formatter,interning,lock,nullness,regex,signature"
+test_wpi_plume_lib bibtex-clean      "formatter,index,interning,lock,nullness,regex,signature"
+test_wpi_plume_lib html-pretty-print "formatter,index,interning,lock,nullness,regex,signature"
+test_wpi_plume_lib icalavailable     "formatter,index,interning,lock,nullness,regex,signature,initializedfields"
+test_wpi_plume_lib lookup            "formatter,index,interning,lock,nullness,regex,signature"
+## Commented out temporarily
+# test_wpi_plume_lib options           "formatter,index,interning,lock,nullness,regex,signature,initializedfields"
+
+echo "exiting test-wpi-plumelib.sh"
diff --git a/checker/bin/Makefile b/checker/bin/Makefile
new file mode 100644
index 0000000..40e3ece
--- /dev/null
+++ b/checker/bin/Makefile
@@ -0,0 +1,10 @@
+SH_SCRIPTS = $(shell grep -r -l '^\#! \?\(/bin/\|/usr/bin/env \)sh' * | grep -v .git | grep -v "~")
+BASH_SCRIPTS = $(shell grep -r -l '^\#! \?\(/bin/\|/usr/bin/env \)bash' * | grep -v .git | grep -v "~")
+
+shell-script-style:
+	shellcheck --format=gcc ${SH_SCRIPTS} ${BASH_SCRIPTS}
+	checkbashisms ${SH_SCRIPTS}
+
+showvars:
+	@echo "SH_SCRIPTS=${SH_SCRIPTS}"
+	@echo "BASH_SCRIPTS=${BASH_SCRIPTS}"
diff --git a/checker/bin/README b/checker/bin/README
new file mode 100644
index 0000000..fc6ab16
--- /dev/null
+++ b/checker/bin/README
@@ -0,0 +1,15 @@
+This directory, "checker/bin", contains scripts to run the Checker Framework.
+Before using them, you must run `./gradlew assemble` in the parent directory.
+
+javac - Is a shell script that runs the Checker Framework in Unix systems
+including Mac OS X.  This script is a drop-in replacement for the script
+javac provided by the OpenJDK.
+
+javac.bat - Is the equivalent of the javac script for Windows systems.
+
+The other scripts are used for whole-program inference:
+infer-and-annotate.sh
+query-github.sh
+wpi.sh
+wpi-many.sh
+wpi-summary.sh
diff --git a/checker/bin/infer-and-annotate.sh b/checker/bin/infer-and-annotate.sh
new file mode 100755
index 0000000..6d3f737
--- /dev/null
+++ b/checker/bin/infer-and-annotate.sh
@@ -0,0 +1,186 @@
+#!/bin/bash
+
+# This script runs the Checker Framework's whole-program inference
+# iteratively on a program, adding type annotations to the program, until the
+# .jaif files from one iteration are the same as the .jaif files from the
+# previous iteration (which means there is nothing new to infer anymore).
+
+# To use this script, the $CHECKERFRAMEWORK variable must be set to the
+# Checker Framework's directory. Also, the AFU's insert-annotations-to-source
+# program must be on the search path (that is, in the PATH environment variable).
+
+# This script receives as arguments:
+# 0. Any number of cmd-line arguments to insert-annotations-to-source (optional).
+# 1. Processor's name (in any form recognized by CF's javac -processor argument).
+# 2. Classpath (target project's classpath).
+# 3. Any number of extra processor arguments to be passed to the checker.
+# 4. Any number of paths to .jaif files -- used as input (optional).
+# 5. Any number of paths to .java files in a program.
+
+# Example of usage:
+# ./infer-and-annotate.sh "LockChecker,NullnessChecker" \
+#     plume-util/build/libs/plume-util-all.jar \
+#     `find plume-util/src/main/java/ -name "*.java"`
+
+# In case of using this script for Android projects, the classpath must include
+# paths to: android.jar, gen folder, all libs used, source code folder.
+# The Android project must be built with "ant debug" before running this script.
+
+# TODO: This script deletes all .unannotated files, including ones that could
+# have been generated previously by another means other than using this script.
+# We must decide if we want to make a backup of previously generated
+# .unannotated files, or if we want to keep the first set of generated
+# .unannotated files.
+
+# Halts the script when a nonzero value is returned from a command.
+set -e
+
+# Path to directory that will contain .jaif files after running the CF
+# with -Ainfer
+WHOLE_PROGRAM_INFERENCE_DIR=build/whole-program-inference
+
+# Path that will contain .class files after running CF's javac. This dir will
+# be deleted after executing this script.
+TEMP_DIR=build/temp-whole-program-inference-output
+
+# Path to directory that will contain .jaif files from the previous iteration.
+PREV_ITERATION_DIR=build/prev-whole-program-inference
+
+debug=
+interactive=
+# For debugging
+# debug=1
+# Require user confirmation before running each command
+# interactive=1
+
+CHECKERBIN=$(dirname "$0")
+
+insert_to_source_args=()
+extra_args=()
+java_files=()
+jaif_files=()
+
+# This function separates extra arguments passed to the checker from Java files
+# received as arguments.
+# TODO: Handle the following limitation: This function makes the assumption
+# that every argument starts with a hyphen. It means one cannot pass arguments
+# such as -processorpath and -source, which are followed by a value.
+read_input() {
+
+    # Collect command-line arguments that come before the preprocessor.
+    # Assumes that every command line argument starts with a hyphen.
+    for i in "$@"
+    do
+        case "$1" in
+            -*)
+                insert_to_source_args+=( "$1" )
+                shift
+            ;;
+            *)
+                break
+            ;;
+        esac
+    done
+
+    # First two arguments are processor and cp.
+    processor=$1
+    cp=$2
+    shift
+    shift
+
+    # shellcheck disable=SC2034
+    for i in "$@"
+    do
+        # This function makes the assumption that every extra argument
+        # starts with a hyphen. The rest are .java/.jaif files.
+        case "$1" in
+            -*)
+                extra_args+=( "$1" )
+            ;;
+            *.jaif)
+                jaif_files+=( "$1" )
+            ;;
+            *.java)
+                java_files+=( "$1" )
+            ;;
+        esac
+        shift
+    done
+}
+
+# Iteratively runs the Checker
+infer_and_annotate() {
+    mkdir -p $TEMP_DIR
+    DIFF_JAIF=firstdiff
+    # Create/clean whole-program-inference directory.
+    rm -rf $WHOLE_PROGRAM_INFERENCE_DIR
+    mkdir -p $WHOLE_PROGRAM_INFERENCE_DIR
+    # If there are .jaif files as input, copy them.
+    for file in "${jaif_files[@]}";
+    do
+        cp "$file" "$WHOLE_PROGRAM_INFERENCE_DIR/"
+    done
+
+    # Perform inference and add annotations from .jaif to .java files until
+    # $PREV_ITERATION_DIR has the same contents as $WHOLE_PROGRAM_INFERENCE_DIR.
+    while [ "$DIFF_JAIF" != "" ]
+    do
+        # Updates $PREV_ITERATION_DIR folder
+        rm -rf $PREV_ITERATION_DIR
+        mv $WHOLE_PROGRAM_INFERENCE_DIR $PREV_ITERATION_DIR
+        mkdir -p $WHOLE_PROGRAM_INFERENCE_DIR
+
+        # Runs CF's javac
+        command="$CHECKERBIN/javac -d $TEMP_DIR/ -cp $cp -processor $processor -Ainfer=jaifs -Awarns -Xmaxwarns 10000 ${extra_args[*]} ${java_files[*]}"
+        echo "About to run: ${command}"
+        if [ "$interactive" ]; then
+            echo "Press any key to run command... "
+            IFS="" read -r _
+        fi
+        "$CHECKERBIN"/javac -d "$TEMP_DIR/" -cp "$cp" -processor "$processor" -Ainfer -Awarns -Xmaxwarns 10000 "${extra_args[@]}" "${java_files[@]}" || true
+        # Deletes .unannotated backup files. This is necessary otherwise the
+        # insert-annotations-to-source tool will use this file instead of the
+        # updated .java one.
+        # See TODO about .unannotated file at the top of this file.
+        for file in "${java_files[@]}";
+        do
+            rm -f "${file}.unannotated"
+        done
+        if [ ! "$(find $WHOLE_PROGRAM_INFERENCE_DIR -prune -empty)" ]
+        then
+            # Only insert annotations if there is at least one .jaif file.
+            # shellcheck disable=SC2046
+            insert-annotations-to-source "${insert_to_source_args[@]}" -i $(find $WHOLE_PROGRAM_INFERENCE_DIR -name "*.jaif") "${java_files[@]}"
+        fi
+        # Updates DIFF_JAIF variable.
+        # diff returns exit-value 1 when there are differences between files.
+        # When this happens, this script halts due to the "set -e"
+        # in its header. To avoid this problem, we add the "|| true" below.
+        DIFF_JAIF="$(diff -qr $PREV_ITERATION_DIR $WHOLE_PROGRAM_INFERENCE_DIR || true)"
+    done
+    if [ ! "$debug" ]; then
+        clean
+    fi
+}
+
+clean() {
+    # It might be good to keep the final .jaif files.
+    # rm -rf $WHOLE_PROGRAM_INFERENCE_DIR
+    rm -rf $PREV_ITERATION_DIR
+    rm -rf $TEMP_DIR
+    # See TODO about .unannotated file at the top of this file.
+    for file in "${java_files[@]}";
+        do
+            rm -f "${file}.unannotated"
+    done
+}
+
+# Main
+if [ "$#" -lt 3 ]; then
+    echo "Aborting infer-and-annotate.sh: Expected at least 3 arguments, received $#."
+    echo "Received the following arguments: $*"
+    exit 1
+fi
+
+read_input "$@"
+infer_and_annotate
diff --git a/checker/bin/javac b/checker/bin/javac
new file mode 100755
index 0000000..551b159
--- /dev/null
+++ b/checker/bin/javac
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+#
+# This file simply redirects all passed arguments
+# to org.checkerframework.framework.util.CheckerMain
+#
+
+mydir="$(dirname "$0")"
+case $(uname -s) in
+    CYGWIN*)
+      mydir=$(cygpath -m "$mydir")
+      ;;
+esac
+
+## Preserve quoting and spaces in arguments, which would otherwise be lost
+## due to being passed through the shell twice.
+# Unset IFS and use newline as arg separator to preserve spaces in args.
+# shellcheck disable=SC2034
+DUALCASE=1  # for MKS: make case statement case-sensitive (6709498)
+saveIFS="$IFS"
+nl='
+'
+for i in "$@" ; do
+   IFS=
+   # shellcheck disable=SC2027
+   case $i in
+     "-Xmn"*) jvmargs=$jvmargs$nl"'"$i"'" ;;
+     "-Xms"*) jvmargs=$jvmargs$nl"'"$i"'" ;;
+     "-Xmx"*) jvmargs=$jvmargs$nl"'"$i"'" ;;
+     *)       args=$args$nl"'"$i"'" ;;
+   esac
+   IFS="$saveIFS"
+done
+
+# shellcheck disable=SC2086
+eval "java" \
+     ${jvmargs} \
+     "-jar" "${mydir}"/../dist/checker.jar \
+     ${args}
diff --git a/checker/bin/javac.bat b/checker/bin/javac.bat
new file mode 100644
index 0000000..d06d4c2
--- /dev/null
+++ b/checker/bin/javac.bat
@@ -0,0 +1,3 @@
+@echo off
+
+java -jar "%~dp0\..\dist\checker.jar" %*
diff --git a/checker/bin/query-github.sh b/checker/bin/query-github.sh
new file mode 100755
index 0000000..e21bb98
--- /dev/null
+++ b/checker/bin/query-github.sh
@@ -0,0 +1,116 @@
+#!/bin/sh
+
+# This script collects a list of projects that match a query from GitHub.
+
+# inputs:
+#
+# The file git-personal-access-token must exist in the directory from which
+# this script is run, and must be a valid github OAuth token.  The token only
+# needs the "Access public repositories" permission.
+#
+# $1 is the query file, which should contain the literal string to use
+# as the github search. REQUIRED, no default
+#
+# $2 is the number of pages to search. default 1
+
+# Set to 1 to enable debug output from this script.
+DEBUG=0
+
+query_file=$1
+# Number of times to retry a GitHub search query.
+query_tries=5
+
+if [ -z "${query_file}" ]; then
+    echo "you must provide a query file as the first argument"
+    exit 2
+fi
+
+if [ -z "$2" ]; then
+    page_count=1
+else
+    page_count=$2
+fi
+
+query=$(tr ' ' '+' < "${query_file}")
+
+mkdir -p "/tmp/$USER"
+
+## for storing the results before sorting and uniqing them
+rm -f "/tmp/$USER/github-query-results-*.txt"
+tempfile=$(mktemp "/tmp/$USER/github-query-results-$(date +%Y%m%d-%H%M%S)-XXX.txt")
+#trap "rm -f ${tempfile}" 0 2 3 15
+
+rm -f "/tmp/$USER/github-hash-results-*.txt"
+hashfile=$(mktemp "/tmp/$USER/github-hash-results-$(date +%Y%m%d-%H%M%S)-XXX.txt")
+#trap "rm -f ${hashfile}" 0 2 3 15
+
+rm -rf "/tmp/$USER/curl-output-*.txt"
+curl_output_file=$(mktemp "/tmp/$USER/curl-output-$(date +%Y%m%d-%H%M%S)-XXX.txt")
+
+# find the repos
+for i in $(seq "${page_count}"); do
+    # GitHub only allows 30 searches per minute, so add a delay to each request.
+    if [ "${i}" -gt 1 ]; then
+        sleep 5
+    fi
+
+    full_query='https://api.github.com/search/code?q='${query}'&page='${i}
+    if [ $DEBUG -ne 0 ] ; then
+        echo "full_query=$full_query"
+    fi
+    for tries in $(seq ${query_tries}); do
+        status_code=$(curl -s \
+            -H "Authorization: token $(cat git-personal-access-token)" \
+            -H "Accept: application/vnd.github.v3+json" \
+            -w "%{http_code}" \
+            -o "${curl_output_file}" \
+            "${full_query}")
+
+        if [ "${status_code}" -eq 200 ] || [ "${status_code}" -eq 422 ]; then
+            # Don't retry.
+            # 200 is success.  422 means too many GitHub requests.
+            break
+        elif [ "${tries}" -lt $((query_tries - 1)) ]; then
+            # Retry.
+            # Other status codes are failures. Failures are usually due to
+            # triggering the abuse detection mechanism for sending too many
+            # requests, so we add a delay when this happens.
+            sleep 20
+        fi
+    done
+
+    # GitHub only returns the first 1000 results. Requests past this limit
+    # return 422, so stop making requests.
+    if [ "${status_code}" -eq 422 ]; then
+        break;
+    elif [ "${status_code}" -ne 200 ]; then
+        echo "GitHub query failed, last response:"
+        cat "${curl_output_file}"
+        rm -f "${curl_output_file}"
+        exit 1
+    fi
+
+    grep "        \"html_url" < "${curl_output_file}" \
+        | grep -v "          " \
+        | sort -u \
+        | cut -d \" -f 4 >> "${tempfile}"
+done
+
+rm -f "${curl_output_file}"
+
+# Each loop iteration was sorted and unique; this does it for the full result.
+sort -u -o "${tempfile}" "${tempfile}"
+
+while IFS= read -r line
+do
+    repo=$(echo "${line}" | cut -d / -f 5)
+    owner=$(echo "${line}" | cut -d / -f 4)
+    hash_query='https://api.github.com/repos/'${owner}'/'${repo}'/commits?per_page=1'
+    curl -sH "Authorization: token $(cat git-personal-access-token)" \
+             "Accept: application/vnd.github.v3+json" \
+             "${hash_query}" \
+        | grep '^    "sha":' \
+        | cut -d \" -f 4 >> "${hashfile}"
+done < "${tempfile}"
+
+paste "${tempfile}" "${hashfile}"
diff --git a/checker/bin/wpi-many.sh b/checker/bin/wpi-many.sh
new file mode 100755
index 0000000..567984f
--- /dev/null
+++ b/checker/bin/wpi-many.sh
@@ -0,0 +1,297 @@
+#!/bin/bash
+
+# This script runs the Checker Framework's whole-program inference on each of a list of projects.
+
+# For usage and requirements, see the "Whole-program inference"
+# section of the Checker Framework manual:
+# https://checkerframework.org/manual/#whole-program-inference
+
+set -eo pipefail
+# not set -u, because this script checks variables directly
+
+DEBUG=0
+# To enable debugging, uncomment the following line.
+# DEBUG=1
+
+while getopts "o:i:t:g:s" opt; do
+  case $opt in
+    o) OUTDIR="$OPTARG"
+       ;;
+    i) INLIST="$OPTARG"
+       ;;
+    t) TIMEOUT="$OPTARG"
+       ;;
+    g) GRADLECACHEDIR="$OPTARG"
+       ;;
+    s) SKIP_OR_DELETE_UNUSABLE="skip"
+       ;;
+    \?) # the remainder of the arguments will be passed to DLJC directly
+       ;;
+  esac
+done
+
+# Make $@ be the arguments that should be passed to dljc.
+shift $(( OPTIND - 1 ))
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+SCRIPTPATH="${SCRIPTDIR}/wpi-many.sh"
+
+# Report line numbers when the script fails, from
+# https://unix.stackexchange.com/a/522815 .
+trap 'echo >&2 "Error - exited with status $? at line $LINENO of wpi-many.sh:";
+         pr -tn ${SCRIPTPATH} | tail -n+$((LINENO - 3)) | head -n7' ERR
+
+echo "Starting wpi-many.sh."
+
+# check required arguments and environment variables:
+
+if [ "x${JAVA_HOME}" = "x" ]; then
+  has_java_home="no"
+else
+  has_java_home="yes"
+fi
+
+# testing for JAVA8_HOME, not an unintentional reference to JAVA_HOME
+# shellcheck disable=SC2153
+if [ "x${JAVA8_HOME}" = "x" ]; then
+  has_java8="no"
+else
+  has_java8="yes"
+fi
+
+# testing for JAVA11_HOME, not an unintentional reference to JAVA_HOME
+# shellcheck disable=SC2153
+if [ "x${JAVA11_HOME}" = "x" ]; then
+  has_java11="no"
+else
+  has_java11="yes"
+fi
+
+if [ "${has_java_home}" = "yes" ]; then
+    java_version=$("${JAVA_HOME}"/bin/java -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1)
+    if [ "${has_java8}" = "no" ] && [ "${java_version}" = 8 ]; then
+      export JAVA8_HOME="${JAVA_HOME}"
+      has_java8="yes"
+    fi
+    if [ "${has_java11}" = "no" ] && [ "${java_version}" = 11 ]; then
+      export JAVA11_HOME="${JAVA_HOME}"
+      has_java11="yes"
+    fi
+fi
+
+if [ "${has_java8}" = "yes" ] && [ ! -d "${JAVA8_HOME}" ]; then
+    echo "JAVA8_HOME is set to a non-existent directory ${JAVA8_HOME}"
+    exit 1
+fi
+
+if [ "${has_java11}" = "yes" ] && [ ! -d "${JAVA11_HOME}" ]; then
+    echo "JAVA11_HOME is set to a non-existent directory ${JAVA11_HOME}"
+    exit 1
+fi
+
+if [ "${has_java8}" = "no" ] && [ "${has_java11}" = "no" ]; then
+    echo "No Java 8 or 11 JDKs found. At least one of JAVA_HOME, JAVA8_HOME, or JAVA11_HOME must be set."
+    exit 1
+fi
+
+if [ "x${CHECKERFRAMEWORK}" = "x" ]; then
+    echo "CHECKERFRAMEWORK is not set; it must be set to a locally-built Checker Framework. Please clone and build github.com/typetools/checker-framework"
+    exit 2
+fi
+
+if [ ! -d "${CHECKERFRAMEWORK}" ]; then
+    echo "CHECKERFRAMEWORK is set to a non-existent directory ${CHECKERFRAMEWORK}"
+    exit 2
+fi
+
+if [ "x${OUTDIR}" = "x" ]; then
+    echo "You must specify an output directory using the -o argument."
+    exit 3
+fi
+
+if [ "x${INLIST}" = "x" ]; then
+    echo "You must specify an input file using the -i argument."
+    exit 4
+fi
+
+if [ "x${GRADLECACHEDIR}" = "x" ]; then
+  GRADLECACHEDIR=".gradle"
+fi
+
+if [ "x${SKIP_OR_DELETE_UNUSABLE}" = "x" ]; then
+  SKIP_OR_DELETE_UNUSABLE="delete"
+fi
+
+JAVA_HOME_BACKUP="${JAVA_HOME}"
+export JAVA_HOME="${JAVA11_HOME}"
+
+### Script
+
+echo "Finished configuring wpi-many.sh. Results will be placed in ${OUTDIR}-results/."
+
+export PATH="${JAVA_HOME}/bin:${PATH}"
+
+mkdir -p "${OUTDIR}"
+mkdir -p "${OUTDIR}-results"
+
+cd "${OUTDIR}" || exit 5
+
+while IFS='' read -r line || [ "$line" ]
+do
+    REPOHASH=${line}
+
+    REPO=$(echo "${REPOHASH}" | awk '{print $1}')
+    HASH=$(echo "${REPOHASH}" | awk '{print $2}')
+
+    REPO_NAME=$(echo "${REPO}" | cut -d / -f 5)
+    REPO_NAME_HASH="${REPO_NAME}-${HASH}"
+
+    if [ "$DEBUG" -eq "1" ]; then
+        echo "REPOHASH=$REPOHASH"
+        echo "REPO=$REPO"
+        echo "HASH=$HASH"
+        echo "REPO_NAME=$REPO_NAME"
+        echo "REPO_NAME_HASH=$REPO_NAME_HASH"
+    fi
+
+    # Use repo name and hash, but not owner.  We want
+    # repos that are different but have the same name to be treated
+    # as different repos, but forks with the same content to be skipped.
+    # TODO: consider just using hash, to skip hard forks?
+    mkdir -p "./${REPO_NAME_HASH}" || (echo "command failed in $(pwd): mkdir -p ./${REPO_NAME_HASH}" && exit 5)
+
+    cd "./${REPO_NAME_HASH}" || (echo "command failed in $(pwd): cd ./${REPO_NAME_HASH}" && exit 5)
+
+    if [ ! -d "${REPO_NAME}" ]; then
+        # see https://stackoverflow.com/questions/3489173/how-to-clone-git-repository-with-specific-revision-changeset
+        # for the inspiration for this code
+        mkdir "./${REPO_NAME}" || (echo "command failed in $(pwd): mkdir ./${REPO_NAME}" && exit 5)
+        cd "./${REPO_NAME}" || (echo "command failed in $(pwd): cd ./${REPO_NAME}" && exit 5)
+        git init
+        git remote add origin "${REPO}"
+
+        # The "GIT_TERMINAL_PROMPT=0" setting prevents git from prompting for
+        # username/password if the repository no longer exists.
+        GIT_TERMINAL_PROMPT=0 git fetch origin "${HASH}"
+
+        git reset --hard FETCH_HEAD
+
+        cd .. || exit 5
+        # Skip the rest of the loop and move on to the next project
+        # if the checkout isn't successful.
+        if [ ! -d "${REPO_NAME}" ]; then
+           continue
+        fi
+    else
+        rm -rf -- "${REPO_NAME}/dljc-out"
+    fi
+
+    cd "./${REPO_NAME}" || (echo "command failed in $(pwd): cd ./${REPO_NAME}" && exit 5)
+
+    git checkout "${HASH}"
+
+    REPO_FULLPATH=$(pwd)
+
+    cd "${OUTDIR}/${REPO_NAME_HASH}" || exit 5
+
+    RESULT_LOG="${OUTDIR}-results/${REPO_NAME_HASH}-wpi.log"
+    touch "${RESULT_LOG}"
+
+    if [ -f "${REPO_FULLPATH}/.cannot-run-wpi" ]; then
+      if [ "${SKIP_OR_DELETE_UNUSABLE}" = "skip" ]; then
+        echo "Skipping ${REPO_NAME_HASH} because it has a .cannot-run-wpi file present, indicating that an earlier run of WPI failed. To try again, delete the .cannot-run-wpi file and re-run the script."
+      fi
+      # the repo will be deleted later if SKIP_OR_DELETE_UNUSABLE is "delete"
+    else
+      /bin/bash -x "${SCRIPTDIR}/wpi.sh" -d "${REPO_FULLPATH}" -t "${TIMEOUT}" -g "${GRADLECACHEDIR}" -- "$@" &> "${OUTDIR}-results/wpi-out"
+    fi
+
+    cd "${OUTDIR}" || exit 5
+
+    if [ -f "${REPO_FULLPATH}/.cannot-run-wpi" ]; then
+        # If the result is unusable (i.e. wpi cannot run),
+        # we don't need it for data analysis and we can
+        # delete it right away.
+        if [ "${SKIP_OR_DELETE_UNUSABLE}" = "delete" ]; then
+          echo "Deleting ${REPO_NAME_HASH} because WPI could not be run."
+          rm -rf -- "./${REPO_NAME_HASH}"
+        fi
+    else
+        cat "${REPO_FULLPATH}/dljc-out/wpi.log" >> "${RESULT_LOG}"
+        TYPECHECK_FILE=${REPO_FULLPATH}/dljc-out/typecheck.out
+        if [ -f "$TYPECHECK_FILE" ]; then
+            cp -p "$TYPECHECK_FILE" "${OUTDIR}-results/${REPO_NAME_HASH}-typecheck.out"
+        else
+            echo "Could not find file $TYPECHECK_FILE"
+            ls -l "${REPO_FULLPATH}/dljc-out"
+            cat "${REPO_FULLPATH}"/dljc-out/*.log
+            echo "Start of toplevel.log:"
+            cat "${REPO_FULLPATH}"/dljc-out/toplevel.log
+            echo "End of toplevel.log."
+            echo "Start of wpi.log:"
+            cat "${REPO_FULLPATH}"/dljc-out/wpi.log
+            echo "End of wpi.log."
+        fi
+    fi
+
+    cd "${OUTDIR}" || exit 5
+
+done < "${INLIST}"
+
+## This section is here rather than in wpi-summary.sh because counting lines can be moderately expensive.
+## wpi-summary.sh is intended to be run while a human waits (unlike this script), so this script
+## precomputes as much as it can, to make wpi-summary.sh faster.
+
+# this command is allowed to fail, because if no projects returned results then none
+# of these expressions will match, and we want to enter the special handling for that
+# case that appears below
+results_available=$(grep -vl -e "no build file found for" \
+    -e "dljc could not run the Checker Framework" \
+    -e "dljc could not run the build successfully" \
+    -e "dljc timed out for" \
+    "${OUTDIR}-results/"*.log || true)
+
+echo "${results_available}" > "${OUTDIR}-results/results_available.txt"
+
+if [ -z "${results_available}" ]; then
+  echo "No results are available."
+  echo "Log files:"
+  ls "${OUTDIR}-results"/*.log
+  echo "End of log files."
+else
+  if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+    listpath=$(mktemp "/tmp/cloc-file-list-$(date +%Y%m%d-%H%M%S)-XXX.txt")
+    # Compute lines of non-comment, non-blank Java code in the projects whose
+    # results can be inspected by hand (that is, those that WPI succeeded on).
+    # Don't match arguments like "-J--add-opens=jdk.compiler/com.sun.tools.java".
+    # shellcheck disable=SC2046
+    grep -oh "\S*\.java" $(cat "${OUTDIR}-results/results_available.txt") | sed "s/'//g" | grep -v '^\-J' | sort | uniq > "${listpath}"
+
+    if [ ! -s "${listpath}" ] ; then
+        echo "${listpath} has size zero"
+        ls -l "${listpath}"
+        echo "results_available = ${results_available}"
+        echo "---------------- start of ${OUTDIR}-results/results_available.txt ----------------"
+        cat "${OUTDIR}-results/results_available.txt"
+        echo "---------------- end of ${OUTDIR}-results/results_available.txt ----------------"
+        exit 1
+    fi
+
+    mkdir -p "${SCRIPTDIR}/.scc"
+    cd "${SCRIPTDIR}/.scc" || exit 5
+    wget -nc "https://github.com/boyter/scc/releases/download/v2.13.0/scc-2.13.0-i386-unknown-linux.zip"
+    unzip -o "scc-2.13.0-i386-unknown-linux.zip"
+
+    # shellcheck disable=SC2046
+    "${SCRIPTDIR}/.scc/scc" --output "${OUTDIR}-results/loc.txt" \
+        $(< "${listpath}")
+
+    rm -f "${listpath}"
+  else
+    echo "skipping computation of lines of code because the operating system is not linux: ${OSTYPE}}"
+  fi
+fi
+
+export JAVA_HOME="${JAVA_HOME_BACKUP}"
+
+echo "Exiting wpi-many.sh. Results were placed in ${OUTDIR}-results/."
diff --git a/checker/bin/wpi-summary.sh b/checker/bin/wpi-summary.sh
new file mode 100755
index 0000000..1e32904
--- /dev/null
+++ b/checker/bin/wpi-summary.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+# This script takes a directory of .log files as input, and produces a summary of the results.
+# Use its output to guide your analysis of the results of running ./wpi-many.sh.
+#
+# This script categorizes projects as:
+#  * does not have a maven or gradle build file
+#  * failed to build
+#  * WPI timed out
+#  * WPI produced results (reported as "results available"). This
+#    script makes no attempt to categorize these projects: a human
+#    should inspect these log files/projects to see the results.
+
+targetdir=$1
+
+number_of_projects=$(find "${targetdir}" -name "*.log" | wc -l)
+
+no_build_file=$(grep -cl "no build file found for" "${targetdir}/"*.log)
+no_build_file_percent=$(((no_build_file*100)/number_of_projects))
+
+# "old" and "new" in the below refer to the two different messages that
+# dljc's wpi tool can emit for this kind of failure. At some point while
+# running an early set of these experiments, I realized that the original
+# message wasn't correct, and fixed it. But, for backwards compatibility,
+# this script looks for both messages and combines the counts.
+build_failed_old=$(grep -cl "dljc could not run the Checker Framework" "${targetdir}/"*.log)
+build_failed_new=$(grep -cl "dljc could not run the build successfully" "${targetdir}/"*.log)
+build_failed=$((build_failed_old+build_failed_new))
+build_failed_percent=$(((build_failed*100)/number_of_projects))
+
+timed_out=$(grep -cl "dljc timed out for" "${targetdir}/"*.log)
+timed_out_percent=$(((timed_out*100)/number_of_projects))
+
+echo "number of projects: ${number_of_projects} (100%)"
+echo "no maven or gradle build file: ${no_build_file} (~${no_build_file_percent}%)"
+echo "build failed: ${build_failed} (~${build_failed_percent}%)"
+echo "timed out: ${timed_out} (~${timed_out_percent}%)"
+echo ""
+echo "timeouts:"
+echo ""
+grep -l "dljc timed out for" "${targetdir}/"*.log
+echo ""
+
+results_available=$(cat "${targetdir}/results_available.txt")
+
+echo "results are available for these projects: "
+echo ""
+echo "${results_available}" | tr ' ' '\n'
+echo ""
+
+if [ -f "${targetdir}/loc.txt" ]; then
+    echo "LoC of projects with available results:"
+
+    cat "${targetdir}/loc.txt"
+else
+    echo "No LoC count found for projects with available results"
+fi
diff --git a/checker/bin/wpi.sh b/checker/bin/wpi.sh
new file mode 100755
index 0000000..0194db9
--- /dev/null
+++ b/checker/bin/wpi.sh
@@ -0,0 +1,283 @@
+#!/bin/bash
+
+# This script performs whole-program inference on a project directory.
+
+# For usage and requirements, see the "Whole-program inference"
+# section of the Checker Framework manual:
+# https://checkerframework.org/manual/#whole-program-inference
+
+set -eo pipefail
+# not set -u, because this script checks variables directly
+
+while getopts "d:t:b:g:" opt; do
+  case $opt in
+    d) DIR="$OPTARG"
+       ;;
+    t) TIMEOUT="$OPTARG"
+       ;;
+    b) EXTRA_BUILD_ARGS="$OPTARG"
+       ;;
+    g) GRADLECACHEDIR="$OPTARG"
+       ;;
+    \?) # echo "Invalid option -$OPTARG" >&2
+       ;;
+  esac
+done
+
+# Make $@ be the arguments that should be passed to dljc.
+shift $(( OPTIND - 1 ))
+
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+SCRIPTPATH="${SCRIPTDIR}/wpi.sh"
+
+# Report line numbers when the script fails, from
+# https://unix.stackexchange.com/a/522815
+trap 'echo >&2 "Error - exited with status $? at line $LINENO of wpi.sh:";
+         pr -tn $SCRIPTPATH | tail -n+$((LINENO - 3)) | head -n7' ERR
+
+echo "Starting wpi.sh."
+
+# check required arguments and environment variables:
+
+if [ "x${JAVA_HOME}" = "x" ]; then
+  has_java_home="no"
+else
+  has_java_home="yes"
+fi
+
+# testing for JAVA8_HOME, not an unintentional reference to JAVA_HOME
+# shellcheck disable=SC2153
+if [ "x${JAVA8_HOME}" = "x" ]; then
+  has_java8="no"
+else
+  has_java8="yes"
+fi
+
+# testing for JAVA11_HOME, not an unintentional reference to JAVA_HOME
+# shellcheck disable=SC2153
+if [ "x${JAVA11_HOME}" = "x" ]; then
+  has_java11="no"
+else
+  has_java11="yes"
+fi
+
+if [ "${has_java_home}" = "yes" ]; then
+    java_version=$("${JAVA_HOME}"/bin/java -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1)
+    if [ "${has_java8}" = "no" ] && [ "${java_version}" = 8 ]; then
+      export JAVA8_HOME="${JAVA_HOME}"
+      has_java8="yes"
+    fi
+    if [ "${has_java11}" = "no" ] && [ "${java_version}" = 11 ]; then
+      export JAVA11_HOME="${JAVA_HOME}"
+      has_java11="yes"
+    fi
+fi
+
+if [ "${has_java8}" = "yes" ] && [ ! -d "${JAVA8_HOME}" ]; then
+    echo "JAVA8_HOME is set to a non-existent directory ${JAVA8_HOME}"
+    exit 6
+fi
+
+if [ "${has_java11}" = "yes" ] && [ ! -d "${JAVA11_HOME}" ]; then
+    echo "JAVA11_HOME is set to a non-existent directory ${JAVA11_HOME}"
+    exit 7
+fi
+
+if [ "${has_java8}" = "no" ] && [ "${has_java11}" = "no" ]; then
+    echo "No Java 8 or 11 JDKs found. At least one of JAVA_HOME, JAVA8_HOME, or JAVA11_HOME must be set."
+    exit 8
+fi
+
+if [ "x${CHECKERFRAMEWORK}" = "x" ]; then
+    echo "CHECKERFRAMEWORK is not set; it must be set to a locally-built Checker Framework. Please clone and build github.com/typetools/checker-framework"
+    exit 2
+fi
+
+if [ ! -d "${CHECKERFRAMEWORK}" ]; then
+    echo "CHECKERFRAMEWORK is set to a non-existent directory ${CHECKERFRAMEWORK}"
+    exit 9
+fi
+
+if [ "x${DIR}" = "x" ]; then
+    # echo "wpi.sh: no -d argument supplied, using the current directory."
+    DIR=$(pwd)
+fi
+
+if [ ! -d "${DIR}" ]; then
+    echo "wpi.sh's -d argument was not a directory: ${DIR}"
+    exit 4
+fi
+
+if [ "x${EXTRA_BUILD_ARGS}" = "x" ]; then
+  EXTRA_BUILD_ARGS=""
+fi
+
+if [ "x${GRADLECACHEDIR}" = "x" ]; then
+  # Assume that each project should use its own gradle cache. This is more expensive,
+  # but prevents crashes on distributed file systems, such as the UW CSE machines.
+  GRADLECACHEDIR=".gradle"
+fi
+
+function configure_and_exec_dljc {
+
+  if [ -f build.gradle ]; then
+      if [ -f gradlew ]; then
+        chmod +x gradlew
+        GRADLE_EXEC="./gradlew"
+      else
+        GRADLE_EXEC="gradle"
+      fi
+      if [ ! -d "${GRADLECACHEDIR}" ]; then
+        mkdir "${GRADLECACHEDIR}"
+      fi
+      CLEAN_CMD="${GRADLE_EXEC} clean -g ${GRADLECACHEDIR} -Dorg.gradle.java.home=${JAVA_HOME} ${EXTRA_BUILD_ARGS}"
+      BUILD_CMD="${GRADLE_EXEC} clean compileJava -g ${GRADLECACHEDIR} -Dorg.gradle.java.home=${JAVA_HOME} ${EXTRA_BUILD_ARGS}"
+  elif [ -f pom.xml ]; then
+      if [ -f mvnw ]; then
+        chmod +x mvnw
+        MVN_EXEC="./mvnw"
+      else
+        MVN_EXEC="mvn"
+      fi
+      # if running on java 8, need /jre at the end of this Maven command
+      if [ "${JAVA_HOME}" = "${JAVA8_HOME}" ]; then
+          CLEAN_CMD="${MVN_EXEC} clean -Djava.home=${JAVA_HOME}/jre ${EXTRA_BUILD_ARGS}"
+          BUILD_CMD="${MVN_EXEC} clean compile -Djava.home=${JAVA_HOME}/jre ${EXTRA_BUILD_ARGS}"
+      else
+          CLEAN_CMD="${MVN_EXEC} clean -Djava.home=${JAVA_HOME} ${EXTRA_BUILD_ARGS}"
+          BUILD_CMD="${MVN_EXEC} clean compile -Djava.home=${JAVA_HOME} ${EXTRA_BUILD_ARGS}"
+      fi
+  elif [ -f build.xml ]; then
+    # TODO: test these more thoroughly
+    CLEAN_CMD="ant clean ${EXTRA_BUILD_ARGS}"
+    BUILD_CMD="ant clean compile ${EXTRA_BUILD_ARGS}"
+  else
+      echo "no build file found for ${REPO_NAME}; not calling DLJC"
+      WPI_RESULTS_AVAILABLE="no build file found for ${REPO_NAME}"
+      return
+  fi
+
+  if [ "${JAVA_HOME}" = "${JAVA8_HOME}" ]; then
+    JDK_VERSION_ARG="--jdkVersion 8"
+  else
+    JDK_VERSION_ARG="--jdkVersion 11"
+  fi
+
+  # In bash 4.4, ${QUOTED_ARGS} below can be replaced by ${*@Q} .
+  # (But, this script does not assume that bash is at least version 4.4.)
+  QUOTED_ARGS=$(printf '%q ' "$@")
+
+  # This command also includes "clean"; I'm not sure why it is necessary.
+  DLJC_CMD="${DLJC} -t wpi ${JDK_VERSION_ARG} ${QUOTED_ARGS} -- ${BUILD_CMD}"
+
+  if [ ! "x${TIMEOUT}" = "x" ]; then
+      TMP="${DLJC_CMD}"
+      DLJC_CMD="timeout ${TIMEOUT} ${TMP}"
+  fi
+
+  # Remove old DLJC output.
+  rm -rf dljc-out
+
+  # ensure the project is clean before invoking DLJC
+  eval "${CLEAN_CMD}" < /dev/null > /dev/null 2>&1
+
+  mkdir -p "${DIR}/dljc-out/"
+  dljc_stdout=$(mktemp "${DIR}/dljc-out/dljc-stdout-$(date +%Y%m%d-%H%M%S)-XXX")
+
+  PATH_BACKUP="${PATH}"
+  export PATH="${JAVA_HOME}/bin:${PATH}"
+
+  # use simpler syntax because this line was crashing mysteriously in CI, to get better debugging output
+  # shellcheck disable=SC2129
+  echo "WORKING DIR: $(pwd)" >> "$dljc_stdout"
+  echo "JAVA_HOME: ${JAVA_HOME}" >> "$dljc_stdout"
+  echo "PATH: ${PATH}" >> "$dljc_stdout"
+  echo "DLJC_CMD: ${DLJC_CMD}" >> "$dljc_stdout"
+  DLJC_STATUS=0
+  eval "${DLJC_CMD}" < /dev/null >> "$dljc_stdout" 2>&1 || DLJC_STATUS=$?
+
+  export PATH="${PATH_BACKUP}"
+
+  echo "=== DLJC standard out/err (${dljc_stdout}) follows: ==="
+  cat "${dljc_stdout}"
+  echo "=== End of DLJC standard out/err.  ==="
+
+  if [[ $DLJC_STATUS -eq 124 ]]; then
+      echo "dljc timed out for ${DIR}"
+      WPI_RESULTS_AVAILABLE="dljc timed out for ${DIR}"
+      return
+  fi
+
+  if [ -f dljc-out/wpi.log ]; then
+      # Put, in file `typecheck.out`, everything from the last "Running ..." onwards.
+      sed -n '/^Running/h;//!H;$!d;x;//p' dljc-out/wpi.log > dljc-out/typecheck.out
+      WPI_RESULTS_AVAILABLE="yes"
+      echo "dljc output is in ${DIR}/dljc-out/"
+      echo "typecheck output is in ${DIR}/dljc-out/typecheck.out"
+      echo "stdout is in $dljc_stdout"
+  else
+      WPI_RESULTS_AVAILABLE="file ${DIR}/dljc-out/wpi.log does not exist"
+      echo "dljc failed: ${WPI_RESULTS_AVAILABLE}"
+      echo "dljc output is in ${DIR}/dljc-out/"
+      echo "stdout is in $dljc_stdout"
+  fi
+}
+
+#### Check and setup dependencies
+
+# Clone or update DLJC
+if [ "${DLJC}x" = "x" ]; then
+  # The user did not set the DLJC environment variable.
+  (cd "${SCRIPTDIR}"/../.. && ./gradlew getPlumeScripts -q)
+  "${SCRIPTDIR}"/../bin-devel/.plume-scripts/git-clone-related kelloggm do-like-javac "${SCRIPTDIR}"/.do-like-javac
+  if [ ! -d "${SCRIPTDIR}/.do-like-javac" ]; then
+      echo "Failed to clone do-like-javac"
+      exit 1
+  fi
+  DLJC="${SCRIPTDIR}/.do-like-javac/dljc"
+else
+  # The user did set the DLJC environment variable.
+  if [ ! -f "${DLJC}" ]; then
+    echo "Failure: DLJC is set to ${DLJC} which is not a file or does not exist."
+    exit 1
+  fi
+fi
+
+#### Main script
+
+echo "Finished configuring wpi.sh."
+
+rm -f -- "${DIR}/.cannot-run-wpi"
+
+cd "${DIR}" || exit 5
+
+JAVA_HOME_BACKUP="${JAVA_HOME}"
+if [ "${has_java11}" = "yes" ]; then
+  export JAVA_HOME="${JAVA11_HOME}"
+  configure_and_exec_dljc "$@"
+elif [ "${has_java8}" = "yes" ]; then
+  export JAVA_HOME="${JAVA8_HOME}"
+  configure_and_exec_dljc "$@"
+fi
+
+if [ "${has_java11}" = "yes" ] && [ "${WPI_RESULTS_AVAILABLE}" != "yes" ]; then
+    # if running under Java 11 fails, try to run
+    # under Java 8 instead
+    if [ "${has_java8}" = "yes" ]; then
+      export JAVA_HOME="${JAVA8_HOME}"
+      echo "couldn't build using Java 11; trying Java 8"
+      configure_and_exec_dljc "$@"
+    fi
+fi
+
+# support wpi-many.sh's ability to delete projects without usable results
+# automatically
+if [ "${WPI_RESULTS_AVAILABLE}" != "yes" ]; then
+    echo "dljc could not run the build successfully: ${WPI_RESULTS_AVAILABLE}"
+    echo "Check the log files in ${DIR}/dljc-out/ for diagnostics."
+    echo "${WPI_RESULTS_AVAILABLE}" > "${DIR}/.cannot-run-wpi"
+fi
+
+export JAVA_HOME="${JAVA_HOME_BACKUP}"
+
+echo "Exiting wpi.sh."
diff --git a/checker/build.gradle b/checker/build.gradle
new file mode 100644
index 0000000..be8ab3e
--- /dev/null
+++ b/checker/build.gradle
@@ -0,0 +1,777 @@
+sourceSets {
+    main {
+        resources {
+            // Stub files, message.properties, etc.
+            srcDirs += ['src/main/java']
+        }
+    }
+    testannotations
+}
+
+
+sourcesJar {
+    // The resources duplicate content from the src directory.
+    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+}
+
+dependencies {
+    implementation project(':javacutil')
+    implementation project(':dataflow')
+    implementation project(':framework')
+    // AFU is an "includedBuild" imported in checker-framework/settings.gradle, so the version number doesn't matter.
+    // https://docs.gradle.org/current/userguide/composite_builds.html#settings_defined_composite
+    implementation('org.checkerframework:annotation-file-utilities:*') {
+        exclude group: 'com.google.errorprone', module: 'javac'
+    }
+    implementation project(':checker-qual')
+    implementation project(':checker-util')
+
+    // External dependencies:
+    // If you add an external dependency, you must shadow its packages.
+    // See the comment in ../build.gradle in the shadowJar block.
+
+    // As of 2019-12-16, the version of reflection-util in the Annotation
+    // File Utilities takes priority over this version, in the fat jar
+    // file. :-( So update it and re-build it locally when updating this.
+    implementation 'org.plumelib:reflection-util:1.0.3'
+    implementation 'org.plumelib:plume-util:1.5.3'
+
+    // Dependencies added to "shadow" appear as dependencies in Maven Central.
+    shadow project(':checker-qual')
+    shadow project(':checker-util')
+
+    // Called Methods Checker AutoValue + Lombok support
+    testImplementation "com.google.auto.value:auto-value-annotations:1.7.4"
+    testImplementation "com.google.auto.value:auto-value:1.7.4"
+    testImplementation "com.ryanharter.auto.value:auto-value-parcel:0.2.8"
+    testImplementation "org.projectlombok:lombok:1.18.20"
+    // Called Methods Checker support for detecting misuses of AWS APIs
+    testImplementation "com.amazonaws:aws-java-sdk-ec2"
+    testImplementation "com.amazonaws:aws-java-sdk-kms"
+    // The AWS SDK is used for testing the Called Methods Checker.
+    testImplementation platform("com.amazonaws:aws-java-sdk-bom:1.11.964")
+
+    testImplementation group: 'junit', name: 'junit', version: '4.13.2'
+    testImplementation project(':framework-test')
+    testImplementation sourceSets.testannotations.output
+
+    testannotationsImplementation project(':checker-qual')
+}
+
+jar {
+    manifest {
+        attributes("Main-Class": "org.checkerframework.framework.util.CheckerMain")
+    }
+}
+
+task copyJarsToDist(dependsOn: shadowJar, group: 'Build') {
+    description 'Builds or downloads jars required by CheckerMain and puts them in checker/dist.'
+    dependsOn project(':checker-qual').tasks.jar
+    doLast {
+        copy {
+            from file(project(':checker-qual').tasks.getByName("jar").archivePath)
+            into "${projectDir}/dist"
+            rename { String fileName ->
+                // remove version number on checker-qual.jar
+                fileName.replace(fileName, "checker-qual.jar")
+            }
+        }
+
+        copy {
+            from file(project(':checker-util').tasks.getByName("jar").archivePath)
+            into "${projectDir}/dist"
+            rename { String fileName ->
+                // remove version number on checker-util.jar
+                fileName.replace(fileName, "checker-util.jar")
+            }
+        }
+
+        copy {
+            from configurations.javacJar
+            into "${projectDir}/dist"
+            rename { String fileName ->
+                fileName.replace(fileName, "javac.jar")
+            }
+        }
+    }
+}
+
+assemble.dependsOn copyJarsToDist
+
+task allSourcesJar(type: Jar, group: 'Build') {
+    description 'Creates a sources jar that includes sources for all Checker Framework classes in checker.jar'
+    destinationDirectory = file("${projectDir}/dist")
+    archiveFileName = "checker-source.jar"
+    archiveClassifier = "sources"
+    from (sourceSets.main.java, project(':framework').sourceSets.main.allJava,
+            project(':dataflow').sourceSets.main.allJava, project(':javacutil').sourceSets.main.allJava,
+            project(':checker-qual').sourceSets.main.allJava, project(':checker-util').sourceSets.main.allJava)
+}
+
+task allJavadocJar(type: Jar, group: 'Build') {
+    description 'Creates javadoc jar including Javadoc for all of the Checker Framework'
+    dependsOn rootProject.tasks.allJavadoc
+    destinationDirectory = file("${projectDir}/dist")
+    archiveFileName = "checker-javadoc.jar"
+    archiveClassifier = "javadoc"
+    from rootProject.tasks.allJavadoc.destinationDir
+}
+
+// Shadowing Test Sources and Dependencies
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+
+task checkerJar(type: ShadowJar, dependsOn: compileJava, group: 'Build') {
+    description "Builds checker-${project.version}.jar with all dependencies except checker-qual and checker-util."
+    includeEmptyDirs = false
+    archivesBaseName = 'checker'
+    archiveClassifier = ''
+
+    from shadowJar.source
+    configurations = shadowJar.configurations
+    // To see what files are incorporated into the shadow jar file:
+    // doLast { println sourceSets.main.runtimeClasspath.asPath }
+    manifest {
+        attributes("Main-Class": "org.checkerframework.framework.util.CheckerMain")
+    }
+    exclude 'org/checkerframework/**/qual/*'
+    exclude 'org/checkerframework/checker/*/util/*'
+    relocators = shadowJar.getRelocators()
+}
+
+jar {
+    dependsOn(checkerJar)
+    // Never build the skinny jar.
+    onlyIf {false}
+    archiveClassifier = 'skinny'
+}
+
+shadowJar {
+    description 'Creates checker-VERSION-all.jar and copies it to dist/checker.jar.'
+    // To see what files are incorporated into the shadow jar file:
+    // doFirst { println sourceSets.main.runtimeClasspath.asPath }
+    doLast{
+        copy {
+            from archiveFile.get()
+            into file("${projectDir}/dist")
+            rename 'checker.*', 'checker.jar'
+        }
+    }
+}
+
+artifacts {
+    // Don't add this here or else the Javadoc and the sources jar is built during the assemble task.
+    // archives allJavadocJar
+    // archives allSourcesJar
+    archives shadowJar
+    archives checkerJar
+}
+
+clean {
+    delete "${projectDir}/dist"
+    delete "tests/calledmethods-delomboked"
+    delete("tests/wpi-testchecker/annotated")
+    delete("tests/wpi-testchecker/inference-output")
+    delete("tests/wpi-nullness/annotated")
+    delete("tests/wpi-nullness/inference-output")
+}
+
+// Add non-junit tests
+createCheckTypeTask(project.name,, "CompilerMessages",
+    'org.checkerframework.checker.compilermsgs.CompilerMessagesChecker')
+checkCompilerMessages {
+    doFirst {
+        options.compilerArgs += [
+                '-Apropfiles=' + sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath + ":"
+                        + project(':framework').sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath
+        ]
+    }
+}
+
+task nullnessExtraTests(type: Exec, dependsOn: copyJarsToDist, group: 'Verification') {
+    description 'Run extra tests for the Nullness Checker.'
+    executable 'make'
+    environment JAVAC: "${projectDir}/bin/javac", JAVAP: 'javap'
+    args = ['-C', 'tests/nullness-extra/']
+}
+
+task commandLineTests(type: Exec, dependsOn: copyJarsToDist, group: 'Verification') {
+    description 'Run tests that need a special command line.'
+    executable 'make'
+    environment JAVAC: "${projectDir}/bin/javac"
+    args = ['-C', 'tests/command-line/']
+}
+
+task tutorialTests(dependsOn: copyJarsToDist, group: 'Verification') {
+    description 'Test that the tutorial is working as expected.'
+    doLast {
+        ant.ant(dir: "${rootDir}/docs/tutorial/tests", useNativeBasedir: 'true', inheritAll: 'false') {
+            target(name: 'check-tutorial')
+        }
+    }
+}
+
+task exampleTests(type: Exec, dependsOn: copyJarsToDist, group: 'Verification') {
+    description 'Run tests for the example programs.'
+    executable 'make'
+    environment JAVAC: "${projectDir}/bin/javac"
+    args = ['-C', '../docs/examples']
+}
+
+task demosTests(dependsOn: copyJarsToDist, group: 'Verification') {
+    description 'Test that the demos are working as expected.'
+    doLast {
+        if (JavaVersion.current() == JavaVersion.VERSION_1_8) {
+            File demosDir = new File(projectDir, '../../checker-framework.demos');
+            if (!demosDir.exists()) {
+                exec {
+                    workingDir file(demosDir.toString() + '/../')
+                    executable 'git'
+                    args = ['clone', '--depth', '1', 'https://github.com/typetools/checker-framework.demos.git']
+                }
+            } else {
+                exec {
+                    workingDir demosDir
+                    executable 'git'
+                    args = ['pull', 'https://github.com/typetools/checker-framework.demos.git']
+                    ignoreExitValue = true
+                }
+            }
+            ant.properties.put('checker.lib', file("${projectDir}/dist/checker.jar").absolutePath)
+            ant.ant(dir: demosDir.toString())
+        } else {
+            println("Skipping demosTests because they only work with Java 8.")
+        }
+    }
+}
+
+task allNullnessTests(type: Test, group: 'Verification') {
+    description 'Run all Junit tests for the Nullness Checker.'
+    include '**/Nullness*.class'
+}
+
+task allCalledMethodsTests(type: Test, group: 'Verification') {
+    description 'Run all Junit tests for the Called Methods Checker.'
+    include '**/CalledMethods*.class'
+    dependsOn 'delombok'
+}
+
+// These are tests that should only be run with JDK 11.
+task jtregJdk11Tests(dependsOn: ':downloadJtreg', group: 'Verification') {
+    description 'Run the jtreg tests made for JDK 11.'
+    dependsOn('compileJava')
+    dependsOn('compileTestJava')
+    dependsOn('shadowJar')
+
+    String jtregOutput = "${buildDir}/jtregJdk11"
+    String name = 'all'
+    doLast {
+        if (isJava8) {
+            println "This test is only run with JDK 11."
+            return;
+        }
+        exec {
+            executable "${jtregHome}/bin/jtreg"
+            args = [
+                    "-dir:${projectDir}/jtregJdk11",
+                    "-workDir:${jtregOutput}/${name}/work",
+                    "-reportDir:${jtregOutput}/${name}/report",
+                    "-verbose:summary",
+                    "-javacoptions:-g",
+                    "-keywords:!ignore",
+                    "-samevm",
+                    "-javacoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}",
+                    "-vmoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}",
+                    "-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
+                    "-javacoptions:-classpath ${sourceSets.testannotations.output.asPath}",
+                    // Location of jtreg tests
+                    '.'
+            ]
+        }
+
+
+    }
+}
+
+// JSpecify tests are excluded by default.  To run them:
+// ./gradlew NullnessJSpecifySamplesTest
+test {
+    exclude '**/org/checkerframework/checker/test/junit/NullnessJSpecifySamplesTest.class'
+}
+
+task delombok {
+    description 'Delomboks the source code tree in tests/calledmethods-lombok'
+
+    def srcDelomboked = 'tests/calledmethods-delomboked'
+    def srcJava = 'tests/calledmethods-lombok'
+
+    inputs.files file(srcJava)
+    outputs.dir file(srcDelomboked)
+
+    // Because there are Checker Framework annotations in the test source.
+    dependsOn project(':checker-qual').tasks.jar
+
+    doLast {
+        def collection = files(configurations.testCompileClasspath)
+        ant.taskdef(name: 'delombok', classname: 'lombok.delombok.ant.Tasks$Delombok',
+                classpath: collection.asPath)
+        ant.delombok(from: srcJava, to: srcDelomboked, classpath: collection.asPath)
+    }
+}
+
+tasks.test.dependsOn("delombok")
+
+///
+/// Whole-program inference tests
+///
+
+test {
+    useJUnit {
+        // These are run in task wholeProgramInferenceTests.
+        excludeCategories 'org.checkerframework.checker.test.junit.wpirunners.WholeProgramInferenceTestCheckerJaifsTest'
+        excludeCategories 'org.checkerframework.checker.test.junit.wpirunners.WholeProgramInferenceTestCheckerStubsTest'
+        excludeCategories 'org.checkerframework.checker.test.junit.wpirunners.WholeProgramInferenceTestCheckerAjavaTest'
+        excludeCategories 'org.checkerframework.checker.test.junit.wpirunners.WholeProgramInferenceNullnessJaifsTest'
+    }
+}
+
+task testWpiTestCheckerStubs(type: Test) {
+    description 'Internal task.  Users should run wholeProgramInferenceTestCheckerStubTests instead.  This runs the wpi-testchecker tests with -Ainfer=stubs to generate stub files'
+
+    dependsOn(compileTestJava)
+    doFirst {
+        delete("tests/wpi-testchecker/annotated")
+        delete("${buildDir}/wpi-testchecker/")
+    }
+    outputs.upToDateWhen { false }
+    include '**/WholeProgramInferenceTestCheckerStubsTest.class'
+    testLogging {
+        // Always run the tests
+        outputs.upToDateWhen { false }
+
+        // Show the found unexpected diagnostics and the expected diagnostics not found.
+        exceptionFormat "full"
+        events "passed", "skipped", "failed"
+    }
+
+    doLast {
+        copyNonannotatedToAnnotatedDirectory("wpi-testchecker")
+        // The stub file format doesn't support annotations on anonymous inner classes, so
+        // this test also expects errors on UsesAnonymous.java.
+        delete('tests/wpi-testchecker/annotated/UsesAnonymous.java')
+        copy {
+            from file('tests/wpi-testchecker/non-annotated/UsesAnonymous.java')
+            into file('tests/wpi-testchecker/annotated')
+        }
+    }
+}
+
+task testWpiTestCheckerStubsValidate(type: Test) {
+    description 'Internal task.  Users should run wholeProgramInferenceTestCheckerStubTests instead.  This re-runs the wpi-testchecker tests using the stub files generated by testWpiTestCheckerStubs'
+
+    dependsOn(testWpiTestCheckerStubs)
+    outputs.upToDateWhen { false }
+    include '**/WholeProgramInferenceTestCheckerStubsValidationTest.class'
+    testLogging {
+        // Always run the tests
+        outputs.upToDateWhen { false }
+
+        // Show the found unexpected diagnostics and the expected diagnostics not found.
+        exceptionFormat "full"
+        events "passed", "skipped", "failed"
+    }
+}
+
+task testWpiTestCheckerAjava(type: Test) {
+    description 'Internal task.  Users sholud run wholeProgramInferenceTestCheckerAjavaTests instead.  This runs the wpi-testchecker tests with -Ainfer=ajava to generate stub files'
+
+    dependsOn(compileTestJava)
+    doFirst {
+        delete("tests/wpi-testchecker/annotated")
+        delete("${buildDir}/wpi-testchecker/")
+    }
+    outputs.upToDateWhen { false }
+    include '**/WholeProgramInferenceTestCheckerAjavaTest.class'
+    testLogging {
+        // Always run the tests
+        outputs.upToDateWhen { false }
+
+        // Show the found unexpected diagnostics and the expected diagnostics not found.
+        exceptionFormat "full"
+        events "passed", "skipped", "failed"
+    }
+
+    doLast {
+        copyNonannotatedToAnnotatedDirectory("wpi-testchecker")
+    }
+}
+
+task testWpiTestCheckerAjavaValidate(type: Test) {
+    description 'Internal task.  Users should run wholeProgramInferenceTestCheckerAjavaTests instead.  This re-runs the wpi-testchecker tests using the ajava files generated by testWpiTestCheckerAjava'
+
+    dependsOn(testWpiTestCheckerAjava)
+    outputs.upToDateWhen { false }
+    include '**/WholeProgramInferenceTestCheckerAjavaValidationTest.class'
+    testLogging {
+        // Always run the tests
+        outputs.upToDateWhen { false }
+
+        // Show the found unexpected diagnostics and the expected diagnostics not found.
+        exceptionFormat "full"
+        events "passed", "skipped", "failed"
+    }
+}
+
+// Copies directories as needed by WPI tests.
+// Formal parameter testdir is, for example, "wpi-testchecker".
+// Does work in directory "tests/${testdir}/".
+// 1. Copies whole-program inference test source code from the non-annotated/ to the annotated/ directory.
+// 2. Copies WPI output, such as .jaif or .stub files, to the inferference-output/ directory.
+void copyNonannotatedToAnnotatedDirectory(String testdir) {
+    // Copying all test files to another directory, removing all expected errors that should not
+    // occur after inserting inferred annotations from .jaif files.
+    copy {
+        from files("tests/${testdir}/non-annotated")
+        into file("tests/${testdir}/annotated")
+        filter { String line ->
+            line.contains('// :: error:') || line.contains('// :: warning:') ? null : line
+        }
+    }
+    // The only file for which expected errors are maintained is ExpectedErrors.java, so we copy it over.
+    delete("tests/${testdir}/annotated/ExpectedErrors.java")
+    copy {
+        from file("tests/${testdir}/non-annotated/ExpectedErrors.java")
+        into file("tests/${testdir}/annotated")
+    }
+
+    delete("tests/${testdir}/inference-output")
+    file("build/whole-program-inference").renameTo(file("tests/${testdir}/inference-output"))
+}
+
+// This task is similar to the wholeProgramInferenceTestCheckerJaifTests task below, but it doesn't
+// run the insert-annotations-to-source tool. Instead, it tests the -Ainfer=stubs feature
+// and the -AmergeStubsWithSource feature to do WPI using stub files.
+task wholeProgramInferenceTestCheckerStubTests(dependsOn: 'shadowJar', group: 'Verification') {
+    description 'Run tests for whole-program inference using stub files'
+    dependsOn(testWpiTestCheckerStubsValidate)
+    outputs.upToDateWhen { false }
+}
+
+// Like wholeProgramInferenceTestCheckerStubTests, but with ajava files instead
+task wholeProgramInferenceTestCheckerAjavaTests(dependsOn: 'shadowJar', group: 'Verification') {
+    description 'Run tests for whole-program inference using ajava files'
+    dependsOn(testWpiTestCheckerAjavaValidate)
+    outputs.upToDateWhen { false }
+}
+
+task testWpiTestCheckerJaifs(type: Test) {
+    description 'Internal task.  Users should run wholeProgramInferenceNullnessJaifTests instead.  This runs the wpi-testchecker tests with -Ainfer=jaifs to generate .jaif files'
+
+    dependsOn(compileTestJava)
+    dependsOn(':checker-qual:jar')  // For the Value Checker annotations.
+    doFirst {
+        delete("tests/wpi-testchecker/annotated")
+    }
+    outputs.upToDateWhen { false }
+    include '**/WholeProgramInferenceTestCheckerJaifsTest.class'
+    testLogging {
+        // Always run the tests
+        outputs.upToDateWhen { false }
+
+        // Show the found unexpected diagnostics and expected diagnostics not found.
+        exceptionFormat "full"
+        events "passed", "skipped", "failed"
+    }
+
+    doLast {
+        copyNonannotatedToAnnotatedDirectory("wpi-testchecker")
+
+        // JAIF-based WPI fails these tests, which was added for stub-based WPI.
+        // See issue here: https://github.com/typetools/checker-framework/issues/3009
+        delete('tests/wpi-testchecker/annotated/ConflictingAnnotationsTest.java')
+        delete('tests/wpi-testchecker/annotated/MultiDimensionalArrays.java')
+
+        // Inserting annotations from .jaif files in-place.
+        String jaifsDir = "tests/wpi-testchecker/inference-output";
+        List<File> jaifs = fileTree(jaifsDir).matching {
+            include '*.jaif'
+        }.asList()
+        if (jaifs.isEmpty()) {
+            throw new GradleException("no .jaif files found in ${jaifsDir}")
+        }
+        String javasDir = "tests/wpi-testchecker/annotated/";
+        List<File> javas = fileTree(javasDir).matching {
+            include '*.java'
+        }.asList()
+        if (javas.isEmpty()) {
+            throw new GradleException("no .java files found in ${javasDir}")
+        }
+        exec {
+            executable "${afu}/scripts/insert-annotations-to-source"
+            // Script argument -cp must precede Java program argument -i.
+            // checker-qual is needed for Constant Value Checker annotations.
+            args = ['-cp', "${sourceSets.test.output.asPath}:${project(':checker-qual').tasks.jar.archivePath}"]
+            args += ['-i']
+            for (File jaif : jaifs) {
+                args += [jaif.toString()]
+            }
+            for (File javaFile : javas) {
+                args += [javaFile.toString()]
+            }
+        }
+    }
+}
+
+task testWpiTestCheckerJaifsValidate(type: Test) {
+    description 'Internal task.  Users should run wholeProgramInferenceNullnessJaifTests instead.  This re-runs the wpi-testchecker tests using the .jaif files generated by testWpiTestCheckerJaifs'
+
+    dependsOn(testWpiTestCheckerJaifs)
+    outputs.upToDateWhen { false }
+    include '**/WholeProgramInferenceTestCheckerJaifsValidationTest.class'
+    testLogging {
+        // Always run the tests
+        outputs.upToDateWhen { false }
+
+        // Show the found unexpected diagnostics and expected diagnostics not found.
+        exceptionFormat "full"
+        events "passed", "skipped", "failed"
+    }
+}
+
+task wholeProgramInferenceTestCheckerJaifTests(dependsOn: 'shadowJar', group: 'Verification') {
+    description 'Run tests for whole-program inference using .jaif files'
+    dependsOn(testWpiTestCheckerJaifsValidate)
+    outputs.upToDateWhen { false }
+}
+
+
+task testWpiNullnessJaifs(type: Test) {
+    description 'Internal task.  Users should run wholeProgramInferenceNullnessJaifTests instead.  This runs the wpi-nullness tests with -Ainfer=jaifs to generate .jaif files'
+
+    dependsOn(compileTestJava)
+    doFirst {
+        delete("tests/wpi-nullness/annotated")
+    }
+    outputs.upToDateWhen { false }
+    include '**/WholeProgramInferenceNullnessJaifsTest.class'
+    testLogging {
+        // Always run the tests
+        outputs.upToDateWhen { false }
+
+        // Show the found unexpected diagnostics and expected diagnostics not found.
+        exceptionFormat "full"
+        events "passed", "skipped", "failed"
+    }
+
+    doLast {
+        copyNonannotatedToAnnotatedDirectory("wpi-nullness")
+
+        // JAIF-based WPI fails these tests, which was added for stub-based WPI.
+        // See issue here: https://github.com/typetools/checker-framework/issues/3009
+        delete('tests/wpi-nullness/annotated/ConflictingAnnotationsTest.java')
+        delete('tests/wpi-nullness/annotated/MultiDimensionalArrays.java')
+
+        // Inserting annotations from .jaif files in-place.
+        String jaifsDir = "tests/wpi-nullness/inference-output";
+        List<File> jaifs = fileTree(jaifsDir).matching {
+            include '*.jaif'
+        }.asList()
+        if (jaifs.isEmpty()) {
+            throw new GradleException("no .jaif files found in ${jaifsDir}")
+        }
+        String javasDir = "tests/wpi-nullness/annotated/";
+        List<File> javas = fileTree(javasDir).matching {
+            include '*.java'
+        }.asList()
+        if (javas.isEmpty()) {
+            throw new GradleException("no .java files found in ${javasDir}")
+        }
+        exec {
+            executable "${afu}/scripts/insert-annotations-to-source"
+            // Script argument -cp must precede Java program argument -i.
+            args = ['-cp', "${sourceSets.test.output.asPath}"]
+            args += ['-i']
+            for (File jaif : jaifs) {
+                args += [jaif.toString()]
+            }
+            for (File javaFile : javas) {
+                args += [javaFile.toString()]
+            }
+        }
+    }
+}
+
+task testWpiNullnessJaifsValidate(type: Test) {
+    description 'Internal task.  Users should run wholeProgramInferenceNullnessJaifTests instead.  This re-runs the wpi-nullness tests using the .jaif files generated by testWpiNullnessJaifs'
+
+    dependsOn(testWpiNullnessJaifs)
+    outputs.upToDateWhen { false }
+    include '**/WholeProgramInferenceNullnessJaifsValidationTest.class'
+    testLogging {
+        // Always run the tests
+        outputs.upToDateWhen { false }
+
+        // Show the found unexpected diagnostics and expected diagnostics not found.
+        exceptionFormat "full"
+        events "passed", "skipped", "failed"
+    }
+}
+
+task wholeProgramInferenceNullnessJaifTests(dependsOn: 'shadowJar', group: 'Verification') {
+    description 'Run tests for whole-program inference using .jaif files'
+    dependsOn(testWpiNullnessJaifsValidate)
+    outputs.upToDateWhen { false }
+}
+
+
+// Empty task that just runs both the jaif and stub WPI tests.
+// It is run as part of the inferenceTests task.
+task wholeProgramInferenceTests(group: 'Verification') {
+    description "Run tests for all whole program inference modes."
+    dependsOn('wholeProgramInferenceTestCheckerJaifTests')
+    dependsOn('wholeProgramInferenceTestCheckerStubTests')
+    dependsOn('wholeProgramInferenceTestCheckerAjavaTests')
+    dependsOn('wholeProgramInferenceNullnessJaifTests')
+}
+
+// This is run as part of the inferenceTests task.
+task wpiManyTests(group: "Verification") {
+    description 'Tests the wpi-many.sh script (and indirectly the wpi.sh script). Requires an Internet connection.'
+    dependsOn(copyJarsToDist)
+    // This test must always be re-run when requested.
+    outputs.upToDateWhen { false }
+
+    doFirst {
+        delete("${project.projectDir}/build/wpi-many-tests-results/")
+        // wpi-many.sh is run in skip mode so that logs are preserved, but
+        // we don't actually want to skip previously-failing tests when we
+        // re-run the tests locally.
+        delete fileTree("${project.projectDir}/build/wpi-many-tests") {
+            include '**/.cannot-run-wpi'
+        }
+    }
+
+    doLast {
+        // Run wpi-many.sh
+        def typecheckFilesDir = "${project.projectDir}/build/wpi-many-tests-results/"
+        try {
+            exec {
+                commandLine 'bin/wpi-many.sh',
+                        '-i', "${project.projectDir}/tests/wpi-many/testin.txt",
+                        '-o', "${project.projectDir}/build/wpi-many-tests",
+                        '-s',
+                        '--', '--checker', 'nullness,interning,lock,regex,signature'
+            }
+        } catch (Exception e) {
+            println("Failure: Running wpi-many.sh failed with a non-zero exit code.")
+            File wpiOut = new File("${typecheckFilesDir}/wpi-out")
+            if (wpiOut.exists()) {
+                println("========= Output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========")
+                exec {
+                    commandLine 'cat', "${typecheckFilesDir}/wpi-out"
+                }
+                println("========= End of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========")
+                throw e
+            }
+        }
+        // collect the logs from running WPI
+        def typecheckFiles = fileTree(typecheckFilesDir).matching {
+            include "**/*-typecheck.out"
+        }
+        if (typecheckFiles.size() == 0) {
+            println("Failure: No *-typecheck.out files in ${typecheckFilesDir}")
+            println("========= Output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========")
+            exec {
+                commandLine 'cat', "${typecheckFilesDir}/wpi-out"
+            }
+            println("========= End of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========")
+            def logFiles = fileTree(typecheckFilesDir).matching {
+                include "**/*.log"
+            }
+            logFiles.visit { FileVisitDetails details ->
+                def filename = "${typecheckFilesDir}" + details.getName()
+                println("======== printing contents of ${filename} ========")
+                details.getFile().eachLine { line -> println(line) }
+                println("======== end of contents of ${filename} ========")
+            }
+            throw new GradleException("Failure: No *-typecheck.out files in ${project.projectDir}/build/wpi-many-tests-results/")
+        }
+
+        // check that WPI causes the expected builds to succeed
+        typecheckFiles.visit { FileVisitDetails details ->
+            def filename = "${project.projectDir}/build/wpi-many-tests-results/" + details.getName()
+            def file = details.getFile()
+            if (file.length() == 0) {
+                throw new GradleException("Failure: WPI produced empty typecheck file " + filename)
+            }
+            file.eachLine { line ->
+                if (
+                        // Ignore the line that WPI echoes with the javac command being run.
+                        line.startsWith("Running ")
+                        // Warnings about bad path elements aren't related to WPI and are ignored.
+                        || line.startsWith("warning: [path]")
+                        // Ignore the summary line that reports the total number of warnings.
+                        || line.endsWith("warnings")) {
+                  return;
+                }
+                if (!line.trim().equals("")) {
+                    throw new GradleException("Failure: WPI scripts produced an unexpected output in " + filename + ". " +
+                            "Failing line is the following: " + line)
+                }
+            }
+        }
+    }
+}
+
+// This is run as part of the inferenceTests task.
+task wpiPlumeLibTests(group: "Verification") {
+    description 'Tests whole-program inference on the plume-lib projects. Requires an Internet connection.'
+    dependsOn(copyJarsToDist)
+
+    // This test must always be re-run when requested.
+    outputs.upToDateWhen { false }
+
+    doLast {
+        exec {
+            commandLine 'bin-devel/wpi-plumelib/test-wpi-plumelib.sh'
+            ignoreExitValue = false
+        }
+    }
+}
+
+apply from: rootProject.file("gradle-mvn-push.gradle")
+
+/** Adds information to the publication for uploading to Maven repositories. */
+final checkerPom(publication) {
+    sharedPublicationConfiguration(publication)
+    // Don't use publication.from components.java which would publish the skinny jar as checker.jar.
+    publication.pom {
+        name = 'Checker Framework'
+        description = 'The Checker Framework enhances Java\'s type system to\n' +
+                'make it more powerful and useful. This lets software developers\n' +
+                'detect and prevent errors in their Java programs.\n' +
+                'The Checker Framework includes compiler plug-ins ("checkers")\n' +
+                'that find bugs or verify their absence. It also permits you to\n' +
+                'write your own compiler plug-ins.'
+        licenses {
+            license {
+                name = 'GNU General Public License, version 2 (GPL2), with the classpath exception'
+                url = 'http://www.gnu.org/software/classpath/license.html'
+                distribution = 'repo'
+            }
+        }
+    }
+}
+publishing {
+    publications {
+        checker(MavenPublication) {
+            project.shadow.component it
+            checkerPom it
+            artifact checkerJar
+            artifact allSourcesJar
+            artifact allJavadocJar
+        }
+    }
+}
+signing {
+    sign publishing.publications.checker
+}
diff --git a/checker/jtreg/README b/checker/jtreg/README
new file mode 100644
index 0000000..68475f3
--- /dev/null
+++ b/checker/jtreg/README
@@ -0,0 +1,10 @@
+This directory contains tests that use the jtreg testing framework.
+
+In general it is preferred to add test cases to
+checker-framework/checker/tests
+
+There are a few cases when jtreg is appropriate:
+ * The jtreg approach makes it easier to pass options to the compiler
+   in the test file itself.
+ * The jtreg approach makes it possible to check that an error message
+   was output two times.
diff --git a/checker/jtreg/SymbolNotFoundErrors.java b/checker/jtreg/SymbolNotFoundErrors.java
new file mode 100644
index 0000000..6a93e7e
--- /dev/null
+++ b/checker/jtreg/SymbolNotFoundErrors.java
@@ -0,0 +1,9 @@
+/*
+ * @test
+ * @summary Test for expected number of error messages.
+ * @compile/fail/ref=SymbolNotFoundErrors.out -XDrawDiagnostics SymbolNotFoundErrors.java
+ * @compile/fail/ref=SymbolNotFoundErrors2.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker SymbolNotFoundErrors.java
+ */
+public class SymbolNotFoundErrors {
+  CCC f;
+}
diff --git a/checker/jtreg/SymbolNotFoundErrors.out b/checker/jtreg/SymbolNotFoundErrors.out
new file mode 100644
index 0000000..df81d2f
--- /dev/null
+++ b/checker/jtreg/SymbolNotFoundErrors.out
@@ -0,0 +1,2 @@
+SymbolNotFoundErrors.java:8:3: compiler.err.cant.resolve.location: kindname.class, CCC, , , (compiler.misc.location: kindname.class, SymbolNotFoundErrors, null)
+1 error
diff --git a/checker/jtreg/SymbolNotFoundErrors2.out b/checker/jtreg/SymbolNotFoundErrors2.out
new file mode 100644
index 0000000..fd1f802
--- /dev/null
+++ b/checker/jtreg/SymbolNotFoundErrors2.out
@@ -0,0 +1,3 @@
+SymbolNotFoundErrors.java:8:3: compiler.err.cant.resolve.location: kindname.class, CCC, , , (compiler.misc.location: kindname.class, SymbolNotFoundErrors, null)
+SymbolNotFoundErrors.java:7:8: compiler.err.proc.messager: [type.checking.not.run] NullnessChecker did not run because of a previous error issued by javac
+2 errors
diff --git a/checker/jtreg/TEST.ROOT b/checker/jtreg/TEST.ROOT
new file mode 100644
index 0000000..f72eb4a
--- /dev/null
+++ b/checker/jtreg/TEST.ROOT
@@ -0,0 +1,5 @@
+# This file identifies the root of the test-suite hierarchy.
+# It also contains test-suite configuration information.
+
+# The list of keywords supported in the entire test suite
+keys=nullness framework stubs
diff --git a/checker/jtreg/index/UnneededSuppressionsTest.java b/checker/jtreg/index/UnneededSuppressionsTest.java
new file mode 100644
index 0000000..d73f271
--- /dev/null
+++ b/checker/jtreg/index/UnneededSuppressionsTest.java
@@ -0,0 +1,45 @@
+/*
+ * @test
+ * @summary Test -AwarnUnneededSuppressions
+ *
+ * @compile/ref=UnneededSuppressionsTest.out -XDrawDiagnostics -processor org.checkerframework.checker.index.IndexChecker -AwarnUnneededSuppressions UnneededSuppressionsTest.java
+ */
+
+import org.checkerframework.checker.index.qual.NonNegative;
+
+@SuppressWarnings("index")
+public class UnneededSuppressionsTest {
+
+  void method(@NonNegative int i) {
+    @SuppressWarnings("index")
+    @NonNegative int x = i - 1;
+  }
+
+  void method2() {
+    @SuppressWarnings("fallthrough")
+    int x = 0;
+  }
+
+  @SuppressWarnings({"tainting", "lowerbound"})
+  void method3() {
+    @SuppressWarnings("upperbound:assignment")
+    int z = 0;
+  }
+
+  void method4() {
+    @SuppressWarnings("lowerbound:assignment")
+    @NonNegative int x = -1;
+  }
+
+  @SuppressWarnings("purity.not.deterministic.call")
+  void method5() {}
+
+  @SuppressWarnings("purity")
+  void method6() {}
+
+  @SuppressWarnings("index:foo.bar.baz")
+  void method7() {}
+
+  @SuppressWarnings("allcheckers:purity.not.deterministic.call")
+  void method8() {}
+}
diff --git a/checker/jtreg/index/UnneededSuppressionsTest.out b/checker/jtreg/index/UnneededSuppressionsTest.out
new file mode 100644
index 0000000..3a45717
--- /dev/null
+++ b/checker/jtreg/index/UnneededSuppressionsTest.out
@@ -0,0 +1,6 @@
+UnneededSuppressionsTest.java:10:1: compiler.warn.proc.messager: [unneeded.suppression] warning suppression "index" is not used by IndexChecker
+UnneededSuppressionsTest.java:23:3: compiler.warn.proc.messager: [unneeded.suppression] warning suppression "lowerbound" is not used by IndexChecker
+UnneededSuppressionsTest.java:25:5: compiler.warn.proc.messager: [unneeded.suppression] warning suppression "upperbound:assignment" is not used by IndexChecker
+UnneededSuppressionsTest.java:40:3: compiler.warn.proc.messager: [unneeded.suppression] warning suppression "index:foo.bar.baz" is not used by IndexChecker
+UnneededSuppressionsTest.java:43:3: compiler.warn.proc.messager: [unneeded.suppression] warning suppression "allcheckers:purity.not.deterministic.call" is not used by IndexChecker
+5 warnings
diff --git a/checker/jtreg/index/ValueStubDriver.java b/checker/jtreg/index/ValueStubDriver.java
new file mode 100644
index 0000000..2af0c02
--- /dev/null
+++ b/checker/jtreg/index/ValueStubDriver.java
@@ -0,0 +1,9 @@
+/*
+ * @test
+ * @summary Test problem with aliasing LengthOf -> NonNegative and NonNegative -> IntRangeFromNonNegative
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.index.IndexChecker valuestub/Test.java -Astubs=valuestub/Test.astub
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.index.IndexChecker valuestub/UseTest.java
+ */
+
+public class ValueStubDriver {}
diff --git a/checker/jtreg/index/valuestub/Test.astub b/checker/jtreg/index/valuestub/Test.astub
new file mode 100644
index 0000000..a2a85cc
--- /dev/null
+++ b/checker/jtreg/index/valuestub/Test.astub
@@ -0,0 +1,4 @@
+package valuestub;
+public class Test {
+    public int length();
+}
diff --git a/checker/jtreg/index/valuestub/Test.java b/checker/jtreg/index/valuestub/Test.java
new file mode 100644
index 0000000..6c35a2d
--- /dev/null
+++ b/checker/jtreg/index/valuestub/Test.java
@@ -0,0 +1,10 @@
+package valuestub;
+
+import org.checkerframework.checker.index.qual.LengthOf;
+
+@SuppressWarnings("index")
+public class Test {
+  public @LengthOf("this") int length() {
+    return 1;
+  }
+}
diff --git a/checker/jtreg/index/valuestub/UseTest.java b/checker/jtreg/index/valuestub/UseTest.java
new file mode 100644
index 0000000..4be77b3
--- /dev/null
+++ b/checker/jtreg/index/valuestub/UseTest.java
@@ -0,0 +1,9 @@
+package valuestub;
+
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+
+public class UseTest {
+  void test(Test t) {
+    @IndexOrHigh("t") int x = t.length();
+  }
+}
diff --git a/checker/jtreg/issue1133/ClassA.java b/checker/jtreg/issue1133/ClassA.java
new file mode 100644
index 0000000..1c3e27f
--- /dev/null
+++ b/checker/jtreg/issue1133/ClassA.java
@@ -0,0 +1,5 @@
+public class ClassA {
+  void test(ClassB classB) {
+    classB.test();
+  }
+}
diff --git a/checker/jtreg/issue1133/ClassB.java b/checker/jtreg/issue1133/ClassB.java
new file mode 100644
index 0000000..e8a1d10
--- /dev/null
+++ b/checker/jtreg/issue1133/ClassB.java
@@ -0,0 +1,5 @@
+public class ClassB {
+  public void test() {
+    Object @Nullable [] o;
+  }
+}
diff --git a/checker/jtreg/issue1133/Main.java b/checker/jtreg/issue1133/Main.java
new file mode 100644
index 0000000..0e59563
--- /dev/null
+++ b/checker/jtreg/issue1133/Main.java
@@ -0,0 +1,9 @@
+/*
+ * @test
+ * @summary Ensure that an invalid annotation doesn't crash the
+ * Checker Framework.
+ *
+ * @compile/fail/ref=errorAB.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker ClassA.java ClassB.java
+ * @compile/fail/ref=errorBA.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker ClassB.java ClassA.java
+ */
+public class Main {}
diff --git a/checker/jtreg/issue1133/errorAB.out b/checker/jtreg/issue1133/errorAB.out
new file mode 100644
index 0000000..6f5b98a
--- /dev/null
+++ b/checker/jtreg/issue1133/errorAB.out
@@ -0,0 +1,3 @@
+ClassB.java:3:13: compiler.err.cant.resolve.location: kindname.class, Nullable, , , (compiler.misc.location: kindname.class, ClassB, null)
+ClassB.java:1:8: compiler.err.proc.messager: [type.checking.not.run] NullnessChecker did not run because of a previous error issued by javac
+2 errors
diff --git a/checker/jtreg/issue1133/errorBA.out b/checker/jtreg/issue1133/errorBA.out
new file mode 100644
index 0000000..a3f28f9
--- /dev/null
+++ b/checker/jtreg/issue1133/errorBA.out
@@ -0,0 +1,4 @@
+ClassB.java:3:13: compiler.err.cant.resolve.location: kindname.class, Nullable, , , (compiler.misc.location: kindname.class, ClassB, null)
+ClassB.java:1:8: compiler.err.proc.messager: [type.checking.not.run] NullnessChecker did not run because of a previous error issued by javac
+ClassA.java:1:8: compiler.err.proc.messager: [type.checking.not.run] NullnessChecker did not run because of a previous error issued by javac
+3 errors
diff --git a/checker/jtreg/issue469/Main.java b/checker/jtreg/issue469/Main.java
new file mode 100644
index 0000000..763d08f
--- /dev/null
+++ b/checker/jtreg/issue469/Main.java
@@ -0,0 +1,15 @@
+/*
+ * @test
+ * @summary Test that Issue 469 is fixed. Thanks to user pSub for the test case.
+ * @library .
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.regex.RegexChecker simplecrash/CrashyInterface.java simplecrash/LetItCrash.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.regex.RegexChecker simplecrash/LetItCrash.java simplecrash/CrashyInterface.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.regex.RegexChecker simplecrash/CrashyInterface.java simplecrash/LetItCrash.java simplecrash/SomeRandomClass.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.regex.RegexChecker simplecrash/LetItCrash.java simplecrash/CrashyInterface.java simplecrash/SomeRandomClass.java
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker advancedcrash/CrashyInterface.java advancedcrash/LetItCrash.java advancedcrash/SomeInterface.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker advancedcrash/LetItCrash.java advancedcrash/CrashyInterface.java advancedcrash/SomeInterface.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker advancedcrash/LetItCrash.java advancedcrash/SomeInterface.java  advancedcrash/CrashyInterface.java
+ */
+public class Main {}
diff --git a/checker/jtreg/issue469/advancedcrash/CrashyInterface.java b/checker/jtreg/issue469/advancedcrash/CrashyInterface.java
new file mode 100644
index 0000000..e687821
--- /dev/null
+++ b/checker/jtreg/issue469/advancedcrash/CrashyInterface.java
@@ -0,0 +1,5 @@
+package advancedcrash;
+
+public interface CrashyInterface {
+  void makeItLongerAndCrash();
+}
diff --git a/checker/jtreg/issue469/advancedcrash/LetItCrash.java b/checker/jtreg/issue469/advancedcrash/LetItCrash.java
new file mode 100644
index 0000000..f95fdfb
--- /dev/null
+++ b/checker/jtreg/issue469/advancedcrash/LetItCrash.java
@@ -0,0 +1,16 @@
+package advancedcrash;
+
+public class LetItCrash implements SomeInterface {
+
+  Integer longer = 0;
+
+  @Override
+  public void doSomethingFancy() {
+    System.out.print("Yay");
+  }
+
+  @Override
+  public void makeItLongerAndCrash() {
+    this.longer += 0;
+  }
+}
diff --git a/checker/jtreg/issue469/advancedcrash/SomeInterface.java b/checker/jtreg/issue469/advancedcrash/SomeInterface.java
new file mode 100644
index 0000000..e83a4dc
--- /dev/null
+++ b/checker/jtreg/issue469/advancedcrash/SomeInterface.java
@@ -0,0 +1,5 @@
+package advancedcrash;
+
+public interface SomeInterface extends CrashyInterface {
+  void doSomethingFancy();
+}
diff --git a/checker/jtreg/issue469/simplecrash/CrashyInterface.java b/checker/jtreg/issue469/simplecrash/CrashyInterface.java
new file mode 100644
index 0000000..7016050
--- /dev/null
+++ b/checker/jtreg/issue469/simplecrash/CrashyInterface.java
@@ -0,0 +1,5 @@
+package simplecrash;
+
+public interface CrashyInterface {
+  void makeItLongerAndCrash();
+}
diff --git a/checker/jtreg/issue469/simplecrash/LetItCrash.java b/checker/jtreg/issue469/simplecrash/LetItCrash.java
new file mode 100644
index 0000000..519742f
--- /dev/null
+++ b/checker/jtreg/issue469/simplecrash/LetItCrash.java
@@ -0,0 +1,10 @@
+package simplecrash;
+
+public class LetItCrash implements CrashyInterface {
+  private Long longer = 0L;
+
+  @Override
+  public void makeItLongerAndCrash() {
+    this.longer += 0;
+  }
+}
diff --git a/checker/jtreg/issue469/simplecrash/SomeRandomClass.java b/checker/jtreg/issue469/simplecrash/SomeRandomClass.java
new file mode 100644
index 0000000..f4e89aa
--- /dev/null
+++ b/checker/jtreg/issue469/simplecrash/SomeRandomClass.java
@@ -0,0 +1,7 @@
+package simplecrash;
+
+public class SomeRandomClass {
+  void randomStuff(LetItCrash letItCrash) {
+    letItCrash.makeItLongerAndCrash();
+  }
+}
diff --git a/checker/jtreg/multiplecheckers/NullnessInterning.java b/checker/jtreg/multiplecheckers/NullnessInterning.java
new file mode 100644
index 0000000..89b3f95
--- /dev/null
+++ b/checker/jtreg/multiplecheckers/NullnessInterning.java
@@ -0,0 +1,24 @@
+/*
+ * @test
+ * @summary Test that command-line options can be provided to specific org.checkerframework.checker
+ *
+ * @compile/ref=NullnessInterning1.out -XDrawDiagnostics -Anomsgtext -processor org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.interning.InterningChecker NullnessInterning.java -Awarns
+ *
+ * @compile/ref=NullnessInterning2.out -XDrawDiagnostics -Anomsgtext -processor org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.interning.InterningChecker NullnessInterning.java -Awarns -ANullnessChecker_skipDefs=NullnessInterning
+ *
+ * @compile/ref=NullnessInterning2.out -XDrawDiagnostics -Anomsgtext -processor org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.interning.InterningChecker NullnessInterning.java -Awarns -Aorg.checkerframework.checker.nullness.NullnessChecker_skipDefs=NullnessInterning
+ *
+ * @compile/ref=NullnessInterning3.out -XDrawDiagnostics -Anomsgtext -processor org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.interning.InterningChecker NullnessInterning.java -Awarns -AInterningChecker_skipDefs=NullnessInterning
+ *
+ * @compile/ref=NullnessInterning3.out -XDrawDiagnostics -Anomsgtext -processor org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.interning.InterningChecker NullnessInterning.java -Awarns -Aorg.checkerframework.checker.interning.InterningChecker_skipDefs=NullnessInterning
+ *
+ * @compile/ref=NullnessInterning4.out -XDrawDiagnostics -Anomsgtext -processor org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.interning.InterningChecker NullnessInterning.java -Awarns -Aorg.checkerframework.common.basetype.BaseTypeChecker_skipDefs=NullnessInterning
+ */
+
+public class NullnessInterning {
+  Object f = null;
+
+  void m(Object p) {
+    if (f == p) {}
+  }
+}
diff --git a/checker/jtreg/multiplecheckers/NullnessInterning1.out b/checker/jtreg/multiplecheckers/NullnessInterning1.out
new file mode 100644
index 0000000..ec6620e
--- /dev/null
+++ b/checker/jtreg/multiplecheckers/NullnessInterning1.out
@@ -0,0 +1,4 @@
+NullnessInterning.java:19:14: compiler.warn.proc.messager: (assignment)
+NullnessInterning.java:22:9: compiler.warn.proc.messager: (not.interned)
+NullnessInterning.java:22:14: compiler.warn.proc.messager: (not.interned)
+3 warnings
diff --git a/checker/jtreg/multiplecheckers/NullnessInterning2.out b/checker/jtreg/multiplecheckers/NullnessInterning2.out
new file mode 100644
index 0000000..1100bac
--- /dev/null
+++ b/checker/jtreg/multiplecheckers/NullnessInterning2.out
@@ -0,0 +1,3 @@
+NullnessInterning.java:22:9: compiler.warn.proc.messager: (not.interned)
+NullnessInterning.java:22:14: compiler.warn.proc.messager: (not.interned)
+2 warnings
diff --git a/checker/jtreg/multiplecheckers/NullnessInterning3.out b/checker/jtreg/multiplecheckers/NullnessInterning3.out
new file mode 100644
index 0000000..9913090
--- /dev/null
+++ b/checker/jtreg/multiplecheckers/NullnessInterning3.out
@@ -0,0 +1,2 @@
+NullnessInterning.java:19:14: compiler.warn.proc.messager: (assignment)
+1 warning
diff --git a/checker/jtreg/multiplecheckers/NullnessInterning4.out b/checker/jtreg/multiplecheckers/NullnessInterning4.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/checker/jtreg/multiplecheckers/NullnessInterning4.out
diff --git a/checker/jtreg/multipleexecutions/Main.java b/checker/jtreg/multipleexecutions/Main.java
new file mode 100644
index 0000000..ecf00de
--- /dev/null
+++ b/checker/jtreg/multipleexecutions/Main.java
@@ -0,0 +1,60 @@
+/*
+ * @test
+ * @summary Ensure that the Java Compiler API can be used multiple times
+ *   to execute the Checker Framework.
+ *
+ * @compile Main.java
+ * @run main Main
+ */
+/*
+ * Test based on message by Daniil Ovchinnikov:
+ * https://groups.google.com/d/msg/checker-framework-dev/FvWmCxB8OpE/Cgp1DsPwnWwJ
+ */
+
+import java.io.File;
+import java.util.Arrays;
+import javax.tools.JavaCompiler;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+import org.checkerframework.checker.regex.RegexChecker;
+
+public class Main {
+
+  public static void main(String[] args) {
+    final JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
+    final StandardJavaFileManager fileManager = javac.getStandardFileManager(null, null, null);
+    if (!doStuff(javac, fileManager)) {
+      return;
+    }
+    if (!doStuff(javac, fileManager)) {
+      return;
+    }
+    if (!doStuff(javac, fileManager)) {
+      return;
+    }
+  }
+
+  public static boolean doStuff(JavaCompiler javac, StandardJavaFileManager fileManager) {
+    File testfile = new File(System.getProperty("test.src", "."), "Test.java");
+
+    JavaCompiler.CompilationTask task =
+        javac.getTask(
+            null,
+            null,
+            null,
+            Arrays.asList(
+                "-classpath",
+                "../../dist/checker.jar",
+                "-proc:only",
+                "-AprintAllQualifiers",
+                "-source",
+                "8",
+                "-target",
+                "8",
+                "-Xlint:-options"),
+            null,
+            fileManager.getJavaFileObjects(testfile));
+    task.setProcessors(Arrays.asList(new RegexChecker()));
+    return task.call();
+  }
+}
diff --git a/checker/jtreg/multipleexecutions/Test.java b/checker/jtreg/multipleexecutions/Test.java
new file mode 100644
index 0000000..4813553
--- /dev/null
+++ b/checker/jtreg/multipleexecutions/Test.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.regex.qual.Regex;
+import org.checkerframework.checker.regex.util.RegexUtil;
+
+public class Test {
+  void foo(String simple) {
+    if (RegexUtil.isRegex(simple)) {
+      @Regex String in = simple;
+    }
+  }
+}
diff --git a/checker/jtreg/nullness/AssignmentPerformanceTest.java b/checker/jtreg/nullness/AssignmentPerformanceTest.java
new file mode 100644
index 0000000..e71e69d
--- /dev/null
+++ b/checker/jtreg/nullness/AssignmentPerformanceTest.java
@@ -0,0 +1,461 @@
+/*
+ * @test
+ * @summary Test case for issue #536: https://github.com/typetools/checker-framework/issues/536
+ *
+ * @compile/timeout=60 -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Alint AssignmentPerformanceTest.java
+ */
+public class AssignmentPerformanceTest {
+  private String s1;
+  private String s2;
+  private String s3;
+  private String s4;
+  private String s5;
+  private String s6;
+  private String s7;
+  private String s8;
+  private String s9;
+  private String s10;
+  private String s11;
+  private String s12;
+  private String s13;
+  private String s14;
+  private String s15;
+  private String s16;
+  private String s17;
+  private String s18;
+  private String s19;
+  private String s20;
+  private String s21;
+  private String s22;
+  private String s23;
+  private String s24;
+  private String s25;
+  private String s26;
+  private String s27;
+  private String s28;
+  private String s29;
+  private String s30;
+  private String s31;
+  private String s32;
+  private String s33;
+  private String s34;
+  private String s35;
+  private String s36;
+  private String s37;
+  private String s38;
+  private String s39;
+  private String s40;
+  private String s41;
+  private String s42;
+  private String s43;
+  private String s44;
+  private String s45;
+  private String s46;
+  private String s47;
+  private String s48;
+  private String s49;
+  private String s50;
+  private String s51;
+  private String s52;
+  private String s53;
+  private String s54;
+  private String s55;
+  private String s56;
+  private String s57;
+  private String s58;
+  private String s59;
+  private String s60;
+  private String s61;
+  private String s62;
+  private String s63;
+  private String s64;
+  private String s65;
+  private String s66;
+  private String s67;
+  private String s68;
+  private String s69;
+  private String s70;
+  private String s71;
+  private String s72;
+  private String s73;
+  private String s74;
+  private String s75;
+  private String s76;
+  private String s77;
+  private String s78;
+  private String s79;
+  private String s80;
+  private String s81;
+  private String s82;
+  private String s83;
+  private String s84;
+  private String s85;
+  private String s86;
+  private String s87;
+  private String s88;
+  private String s89;
+  private String s90;
+  private String s91;
+  private String s92;
+  private String s93;
+  private String s94;
+  private String s95;
+  private String s96;
+  private String s97;
+  private String s98;
+  private String s99;
+  private String s100;
+  private String s101;
+  private String s102;
+  private String s103;
+  private String s104;
+  private String s105;
+  private String s106;
+  private String s107;
+  private String s108;
+  private String s109;
+  private String s110;
+  private String s111;
+  private String s112;
+  private String s113;
+  private String s114;
+  private String s115;
+  private String s116;
+  private String s117;
+  private String s118;
+  private String s119;
+  private String s120;
+  private String s121;
+  private String s122;
+  private String s123;
+  private String s124;
+  private String s125;
+  private String s126;
+  private String s127;
+  private String s128;
+  private String s129;
+  private String s130;
+  private String s131;
+  private String s132;
+  private String s133;
+  private String s134;
+  private String s135;
+  private String s136;
+  private String s137;
+  private String s138;
+  private String s139;
+  private String s140;
+  private String s141;
+  private String s142;
+  private String s143;
+  private String s144;
+  private String s145;
+  private String s146;
+  private String s147;
+  private String s148;
+  private String s149;
+  private String s150;
+
+  public AssignmentPerformanceTest(
+      String s1,
+      String s2,
+      String s3,
+      String s4,
+      String s5,
+      String s6,
+      String s7,
+      String s8,
+      String s9,
+      String s10,
+      String s11,
+      String s12,
+      String s13,
+      String s14,
+      String s15,
+      String s16,
+      String s17,
+      String s18,
+      String s19,
+      String s20,
+      String s21,
+      String s22,
+      String s23,
+      String s24,
+      String s25,
+      String s26,
+      String s27,
+      String s28,
+      String s29,
+      String s30,
+      String s31,
+      String s32,
+      String s33,
+      String s34,
+      String s35,
+      String s36,
+      String s37,
+      String s38,
+      String s39,
+      String s40,
+      String s41,
+      String s42,
+      String s43,
+      String s44,
+      String s45,
+      String s46,
+      String s47,
+      String s48,
+      String s49,
+      String s50,
+      String s51,
+      String s52,
+      String s53,
+      String s54,
+      String s55,
+      String s56,
+      String s57,
+      String s58,
+      String s59,
+      String s60,
+      String s61,
+      String s62,
+      String s63,
+      String s64,
+      String s65,
+      String s66,
+      String s67,
+      String s68,
+      String s69,
+      String s70,
+      String s71,
+      String s72,
+      String s73,
+      String s74,
+      String s75,
+      String s76,
+      String s77,
+      String s78,
+      String s79,
+      String s80,
+      String s81,
+      String s82,
+      String s83,
+      String s84,
+      String s85,
+      String s86,
+      String s87,
+      String s88,
+      String s89,
+      String s90,
+      String s91,
+      String s92,
+      String s93,
+      String s94,
+      String s95,
+      String s96,
+      String s97,
+      String s98,
+      String s99,
+      String s100,
+      String s101,
+      String s102,
+      String s103,
+      String s104,
+      String s105,
+      String s106,
+      String s107,
+      String s108,
+      String s109,
+      String s110,
+      String s111,
+      String s112,
+      String s113,
+      String s114,
+      String s115,
+      String s116,
+      String s117,
+      String s118,
+      String s119,
+      String s120,
+      String s121,
+      String s122,
+      String s123,
+      String s124,
+      String s125,
+      String s126,
+      String s127,
+      String s128,
+      String s129,
+      String s130,
+      String s131,
+      String s132,
+      String s133,
+      String s134,
+      String s135,
+      String s136,
+      String s137,
+      String s138,
+      String s139,
+      String s140,
+      String s141,
+      String s142,
+      String s143,
+      String s144,
+      String s145,
+      String s146,
+      String s147,
+      String s148,
+      String s149,
+      String s150) {
+    this.s1 = s1;
+    this.s2 = s2;
+    this.s3 = s3;
+    this.s4 = s4;
+    this.s5 = s5;
+    this.s6 = s6;
+    this.s7 = s7;
+    this.s8 = s8;
+    this.s9 = s9;
+    this.s10 = s10;
+    this.s11 = s11;
+    this.s12 = s12;
+    this.s13 = s13;
+    this.s14 = s14;
+    this.s15 = s15;
+    this.s16 = s16;
+    this.s17 = s17;
+    this.s18 = s18;
+    this.s19 = s19;
+    this.s20 = s20;
+    this.s21 = s21;
+    this.s22 = s22;
+    this.s23 = s23;
+    this.s24 = s24;
+    this.s25 = s25;
+    this.s26 = s26;
+    this.s27 = s27;
+    this.s28 = s28;
+    this.s29 = s29;
+    this.s30 = s30;
+    this.s31 = s31;
+    this.s32 = s32;
+    this.s33 = s33;
+    this.s34 = s34;
+    this.s35 = s35;
+    this.s36 = s36;
+    this.s37 = s37;
+    this.s38 = s38;
+    this.s39 = s39;
+    this.s40 = s40;
+    this.s41 = s41;
+    this.s42 = s42;
+    this.s43 = s43;
+    this.s44 = s44;
+    this.s45 = s45;
+    this.s46 = s46;
+    this.s47 = s47;
+    this.s48 = s48;
+    this.s49 = s49;
+    this.s50 = s50;
+    this.s51 = s51;
+    this.s52 = s52;
+    this.s53 = s53;
+    this.s54 = s54;
+    this.s55 = s55;
+    this.s56 = s56;
+    this.s57 = s57;
+    this.s58 = s58;
+    this.s59 = s59;
+    this.s60 = s60;
+    this.s61 = s61;
+    this.s62 = s62;
+    this.s63 = s63;
+    this.s64 = s64;
+    this.s65 = s65;
+    this.s66 = s66;
+    this.s67 = s67;
+    this.s68 = s68;
+    this.s69 = s69;
+    this.s70 = s70;
+    this.s71 = s71;
+    this.s72 = s72;
+    this.s73 = s73;
+    this.s74 = s74;
+    this.s75 = s75;
+    this.s76 = s76;
+    this.s77 = s77;
+    this.s78 = s78;
+    this.s79 = s79;
+    this.s80 = s80;
+    this.s81 = s81;
+    this.s82 = s82;
+    this.s83 = s83;
+    this.s84 = s84;
+    this.s85 = s85;
+    this.s86 = s86;
+    this.s87 = s87;
+    this.s88 = s88;
+    this.s89 = s89;
+    this.s90 = s90;
+    this.s91 = s91;
+    this.s92 = s92;
+    this.s93 = s93;
+    this.s94 = s94;
+    this.s95 = s95;
+    this.s96 = s96;
+    this.s97 = s97;
+    this.s98 = s98;
+    this.s99 = s99;
+    this.s100 = s100;
+    this.s101 = s101;
+    this.s102 = s102;
+    this.s103 = s103;
+    this.s104 = s104;
+    this.s105 = s105;
+    this.s106 = s106;
+    this.s107 = s107;
+    this.s108 = s108;
+    this.s109 = s109;
+    this.s110 = s110;
+    this.s111 = s111;
+    this.s112 = s112;
+    this.s113 = s113;
+    this.s114 = s114;
+    this.s115 = s115;
+    this.s116 = s116;
+    this.s117 = s117;
+    this.s118 = s118;
+    this.s119 = s119;
+    this.s120 = s120;
+    this.s121 = s121;
+    this.s122 = s122;
+    this.s123 = s123;
+    this.s124 = s124;
+    this.s125 = s125;
+    this.s126 = s126;
+    this.s127 = s127;
+    this.s128 = s128;
+    this.s129 = s129;
+    this.s130 = s130;
+    this.s131 = s131;
+    this.s132 = s132;
+    this.s133 = s133;
+    this.s134 = s134;
+    this.s135 = s135;
+    this.s136 = s136;
+    this.s137 = s137;
+    this.s138 = s138;
+    this.s139 = s139;
+    this.s140 = s140;
+    this.s141 = s141;
+    this.s142 = s142;
+    this.s143 = s143;
+    this.s144 = s144;
+    this.s145 = s145;
+    this.s146 = s146;
+    this.s147 = s147;
+    this.s148 = s148;
+    this.s149 = s149;
+    this.s150 = s150;
+  }
+}
diff --git a/checker/jtreg/nullness/DefaultNonPublicClass.java b/checker/jtreg/nullness/DefaultNonPublicClass.java
new file mode 100644
index 0000000..6908779
--- /dev/null
+++ b/checker/jtreg/nullness/DefaultNonPublicClass.java
@@ -0,0 +1,23 @@
+/*
+ * @test
+ * @summary Test that verifies defaults in a non-public class in the same
+ * file.
+ *
+ * @compile -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Alint DefaultNonPublicClass.java
+ * @compile -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Alint DefaultNonPublicClass.java
+ */
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+class Test {
+  Integer foo() {
+    return 123;
+  }
+}
+
+public class DefaultNonPublicClass {
+  public static void main(String[] args) {
+    Test ti = new Test();
+    @NonNull Integer ls = ti.foo();
+  }
+}
diff --git a/checker/jtreg/nullness/Issue1438.java b/checker/jtreg/nullness/Issue1438.java
new file mode 100644
index 0000000..7f0039f
--- /dev/null
+++ b/checker/jtreg/nullness/Issue1438.java
@@ -0,0 +1,2019 @@
+/*
+ * @test
+ * @summary Test case for issue #1438: https://github.com/typetools/checker-framework/issues/1438
+ *
+ * @compile/fail/timeout=90 -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Alint Issue1438.java
+ */
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Issue1438 {
+  // Do not initialize these variables.  The Nullness Checker is supposed to issue an error about
+  // uninitialized fields.
+  static Integer v0;
+  static Integer v1;
+  static Integer v2;
+  static Integer v3;
+  static Integer v4;
+  static Integer v5;
+  static Integer v6;
+  static Integer v7;
+  static Integer v8;
+  static Integer v9;
+  static Integer v10;
+  static Integer v11;
+  static Integer v12;
+  static Integer v13;
+  static Integer v14;
+  static Integer v15;
+  static Integer v16;
+  static Integer v17;
+  static Integer v18;
+  static Integer v19;
+  static Integer v20;
+  static Integer v21;
+  static Integer v22;
+  static Integer v23;
+  static Integer v24;
+  static Integer v25;
+  static Integer v26;
+  static Integer v27;
+  static Integer v28;
+  static Integer v29;
+  static Integer v30;
+  static Integer v31;
+  static Integer v32;
+  static Integer v33;
+  static Integer v34;
+  static Integer v35;
+  static Integer v36;
+  static Integer v37;
+  static Integer v38;
+  static Integer v39;
+  static Integer v40;
+  static Integer v41;
+  static Integer v42;
+  static Integer v43;
+  static Integer v44;
+  static Integer v45;
+  static Integer v46;
+  static Integer v47;
+  static Integer v48;
+  static Integer v49;
+  static Integer v50;
+  static Integer v51;
+  static Integer v52;
+  static Integer v53;
+  static Integer v54;
+  static Integer v55;
+  static Integer v56;
+  static Integer v57;
+  static Integer v58;
+  static Integer v59;
+  static Integer v60;
+  static Integer v61;
+  static Integer v62;
+  static Integer v63;
+  static Integer v64;
+  static Integer v65;
+  static Integer v66;
+  static Integer v67;
+  static Integer v68;
+  static Integer v69;
+  static Integer v70;
+  static Integer v71;
+  static Integer v72;
+  static Integer v73;
+  static Integer v74;
+  static Integer v75;
+  static Integer v76;
+  static Integer v77;
+  static Integer v78;
+  static Integer v79;
+  static Integer v80;
+  static Integer v81;
+  static Integer v82;
+  static Integer v83;
+  static Integer v84;
+  static Integer v85;
+  static Integer v86;
+  static Integer v87;
+  static Integer v88;
+  static Integer v89;
+  static Integer v90;
+  static Integer v91;
+  static Integer v92;
+  static Integer v93;
+  static Integer v94;
+  static Integer v95;
+  static Integer v96;
+  static Integer v97;
+  static Integer v98;
+  static Integer v99;
+  static Integer v100;
+  static Integer v101;
+  static Integer v102;
+  static Integer v103;
+  static Integer v104;
+  static Integer v105;
+  static Integer v106;
+  static Integer v107;
+  static Integer v108;
+  static Integer v109;
+  static Integer v110;
+  static Integer v111;
+  static Integer v112;
+  static Integer v113;
+  static Integer v114;
+  static Integer v115;
+  static Integer v116;
+  static Integer v117;
+  static Integer v118;
+  static Integer v119;
+  static Integer v120;
+  static Integer v121;
+  static Integer v122;
+  static Integer v123;
+  static Integer v124;
+  static Integer v125;
+  static Integer v126;
+  static Integer v127;
+  static Integer v128;
+  static Integer v129;
+  static Integer v130;
+  static Integer v131;
+  static Integer v132;
+  static Integer v133;
+  static Integer v134;
+  static Integer v135;
+  static Integer v136;
+  static Integer v137;
+  static Integer v138;
+  static Integer v139;
+  static Integer v140;
+  static Integer v141;
+  static Integer v142;
+  static Integer v143;
+  static Integer v144;
+  static Integer v145;
+  static Integer v146;
+  static Integer v147;
+  static Integer v148;
+  static Integer v149;
+  static Integer v150;
+  static Integer v151;
+  static Integer v152;
+  static Integer v153;
+  static Integer v154;
+  static Integer v155;
+  static Integer v156;
+  static Integer v157;
+  static Integer v158;
+  static Integer v159;
+  static Integer v160;
+  static Integer v161;
+  static Integer v162;
+  static Integer v163;
+  static Integer v164;
+  static Integer v165;
+  static Integer v166;
+  static Integer v167;
+  static Integer v168;
+  static Integer v169;
+  static Integer v170;
+  static Integer v171;
+  static Integer v172;
+  static Integer v173;
+  static Integer v174;
+  static Integer v175;
+  static Integer v176;
+  static Integer v177;
+  static Integer v178;
+  static Integer v179;
+  static Integer v180;
+  static Integer v181;
+  static Integer v182;
+  static Integer v183;
+  static Integer v184;
+  static Integer v185;
+  static Integer v186;
+  static Integer v187;
+  static Integer v188;
+  static Integer v189;
+  static Integer v190;
+  static Integer v191;
+  static Integer v192;
+  static Integer v193;
+  static Integer v194;
+  static Integer v195;
+  static Integer v196;
+  static Integer v197;
+  static Integer v198;
+  static Integer v199;
+  static Integer v200;
+  static Integer v201;
+  static Integer v202;
+  static Integer v203;
+  static Integer v204;
+  static Integer v205;
+  static Integer v206;
+  static Integer v207;
+  static Integer v208;
+  static Integer v209;
+  static Integer v210;
+  static Integer v211;
+  static Integer v212;
+  static Integer v213;
+  static Integer v214;
+  static Integer v215;
+  static Integer v216;
+  static Integer v217;
+  static Integer v218;
+  static Integer v219;
+  static Integer v220;
+  static Integer v221;
+  static Integer v222;
+  static Integer v223;
+  static Integer v224;
+  static Integer v225;
+  static Integer v226;
+  static Integer v227;
+  static Integer v228;
+  static Integer v229;
+  static Integer v230;
+  static Integer v231;
+  static Integer v232;
+  static Integer v233;
+  static Integer v234;
+  static Integer v235;
+  static Integer v236;
+  static Integer v237;
+  static Integer v238;
+  static Integer v239;
+  static Integer v240;
+  static Integer v241;
+  static Integer v242;
+  static Integer v243;
+  static Integer v244;
+  static Integer v245;
+  static Integer v246;
+  static Integer v247;
+  static Integer v248;
+  static Integer v249;
+  static Integer v250;
+  static Integer v251;
+  static Integer v252;
+  static Integer v253;
+  static Integer v254;
+  static Integer v255;
+  static Integer v256;
+  static Integer v257;
+  static Integer v258;
+  static Integer v259;
+  static Integer v260;
+  static Integer v261;
+  static Integer v262;
+  static Integer v263;
+  static Integer v264;
+  static Integer v265;
+  static Integer v266;
+  static Integer v267;
+  static Integer v268;
+  static Integer v269;
+  static Integer v270;
+  static Integer v271;
+  static Integer v272;
+  static Integer v273;
+  static Integer v274;
+  static Integer v275;
+  static Integer v276;
+  static Integer v277;
+  static Integer v278;
+  static Integer v279;
+  static Integer v280;
+  static Integer v281;
+  static Integer v282;
+  static Integer v283;
+  static Integer v284;
+  static Integer v285;
+  static Integer v286;
+  static Integer v287;
+  static Integer v288;
+  static Integer v289;
+  static Integer v290;
+  static Integer v291;
+  static Integer v292;
+  static Integer v293;
+  static Integer v294;
+  static Integer v295;
+  static Integer v296;
+  static Integer v297;
+  static Integer v298;
+  static Integer v299;
+  static Integer v300;
+  static Integer v301;
+  static Integer v302;
+  static Integer v303;
+  static Integer v304;
+  static Integer v305;
+  static Integer v306;
+  static Integer v307;
+  static Integer v308;
+  static Integer v309;
+  static Integer v310;
+  static Integer v311;
+  static Integer v312;
+  static Integer v313;
+  static Integer v314;
+  static Integer v315;
+  static Integer v316;
+  static Integer v317;
+  static Integer v318;
+  static Integer v319;
+  static Integer v320;
+  static Integer v321;
+  static Integer v322;
+  static Integer v323;
+  static Integer v324;
+  static Integer v325;
+  static Integer v326;
+  static Integer v327;
+  static Integer v328;
+  static Integer v329;
+  static Integer v330;
+  static Integer v331;
+  static Integer v332;
+  static Integer v333;
+  static Integer v334;
+  static Integer v335;
+  static Integer v336;
+  static Integer v337;
+  static Integer v338;
+  static Integer v339;
+  static Integer v340;
+  static Integer v341;
+  static Integer v342;
+  static Integer v343;
+  static Integer v344;
+  static Integer v345;
+  static Integer v346;
+  static Integer v347;
+  static Integer v348;
+  static Integer v349;
+  static Integer v350;
+  static Integer v351;
+  static Integer v352;
+  static Integer v353;
+  static Integer v354;
+  static Integer v355;
+  static Integer v356;
+  static Integer v357;
+  static Integer v358;
+  static Integer v359;
+  static Integer v360;
+  static Integer v361;
+  static Integer v362;
+  static Integer v363;
+  static Integer v364;
+  static Integer v365;
+  static Integer v366;
+  static Integer v367;
+  static Integer v368;
+  static Integer v369;
+  static Integer v370;
+  static Integer v371;
+  static Integer v372;
+  static Integer v373;
+  static Integer v374;
+  static Integer v375;
+  static Integer v376;
+  static Integer v377;
+  static Integer v378;
+  static Integer v379;
+  static Integer v380;
+  static Integer v381;
+  static Integer v382;
+  static Integer v383;
+  static Integer v384;
+  static Integer v385;
+  static Integer v386;
+  static Integer v387;
+  static Integer v388;
+  static Integer v389;
+  static Integer v390;
+  static Integer v391;
+  static Integer v392;
+  static Integer v393;
+  static Integer v394;
+  static Integer v395;
+  static Integer v396;
+  static Integer v397;
+  static Integer v398;
+  static Integer v399;
+  static Integer v400;
+  static Integer v401;
+  static Integer v402;
+  static Integer v403;
+  static Integer v404;
+  static Integer v405;
+  static Integer v406;
+  static Integer v407;
+  static Integer v408;
+  static Integer v409;
+  static Integer v410;
+  static Integer v411;
+  static Integer v412;
+  static Integer v413;
+  static Integer v414;
+  static Integer v415;
+  static Integer v416;
+  static Integer v417;
+  static Integer v418;
+  static Integer v419;
+  static Integer v420;
+  static Integer v421;
+  static Integer v422;
+  static Integer v423;
+  static Integer v424;
+  static Integer v425;
+  static Integer v426;
+  static Integer v427;
+  static Integer v428;
+  static Integer v429;
+  static Integer v430;
+  static Integer v431;
+  static Integer v432;
+  static Integer v433;
+  static Integer v434;
+  static Integer v435;
+  static Integer v436;
+  static Integer v437;
+  static Integer v438;
+  static Integer v439;
+  static Integer v440;
+  static Integer v441;
+  static Integer v442;
+  static Integer v443;
+  static Integer v444;
+  static Integer v445;
+  static Integer v446;
+  static Integer v447;
+  static Integer v448;
+  static Integer v449;
+  static Integer v450;
+  static Integer v451;
+  static Integer v452;
+  static Integer v453;
+  static Integer v454;
+  static Integer v455;
+  static Integer v456;
+  static Integer v457;
+  static Integer v458;
+  static Integer v459;
+  static Integer v460;
+  static Integer v461;
+  static Integer v462;
+  static Integer v463;
+  static Integer v464;
+  static Integer v465;
+  static Integer v466;
+  static Integer v467;
+  static Integer v468;
+  static Integer v469;
+  static Integer v470;
+  static Integer v471;
+  static Integer v472;
+  static Integer v473;
+  static Integer v474;
+  static Integer v475;
+  static Integer v476;
+  static Integer v477;
+  static Integer v478;
+  static Integer v479;
+  static Integer v480;
+  static Integer v481;
+  static Integer v482;
+  static Integer v483;
+  static Integer v484;
+  static Integer v485;
+  static Integer v486;
+  static Integer v487;
+  static Integer v488;
+  static Integer v489;
+  static Integer v490;
+  static Integer v491;
+  static Integer v492;
+  static Integer v493;
+  static Integer v494;
+  static Integer v495;
+  static Integer v496;
+  static Integer v497;
+  static Integer v498;
+  static Integer v499;
+  static Integer v500;
+  static Integer v501;
+  static Integer v502;
+  static Integer v503;
+  static Integer v504;
+  static Integer v505;
+  static Integer v506;
+  static Integer v507;
+  static Integer v508;
+  static Integer v509;
+  static Integer v510;
+  static Integer v511;
+  static Integer v512;
+  static Integer v513;
+  static Integer v514;
+  static Integer v515;
+  static Integer v516;
+  static Integer v517;
+  static Integer v518;
+  static Integer v519;
+  static Integer v520;
+  static Integer v521;
+  static Integer v522;
+  static Integer v523;
+  static Integer v524;
+  static Integer v525;
+  static Integer v526;
+  static Integer v527;
+  static Integer v528;
+  static Integer v529;
+  static Integer v530;
+  static Integer v531;
+  static Integer v532;
+  static Integer v533;
+  static Integer v534;
+  static Integer v535;
+  static Integer v536;
+  static Integer v537;
+  static Integer v538;
+  static Integer v539;
+  static Integer v540;
+  static Integer v541;
+  static Integer v542;
+  static Integer v543;
+  static Integer v544;
+  static Integer v545;
+  static Integer v546;
+  static Integer v547;
+  static Integer v548;
+  static Integer v549;
+  static Integer v550;
+  static Integer v551;
+  static Integer v552;
+  static Integer v553;
+  static Integer v554;
+  static Integer v555;
+  static Integer v556;
+  static Integer v557;
+  static Integer v558;
+  static Integer v559;
+  static Integer v560;
+  static Integer v561;
+  static Integer v562;
+  static Integer v563;
+  static Integer v564;
+  static Integer v565;
+  static Integer v566;
+  static Integer v567;
+  static Integer v568;
+  static Integer v569;
+  static Integer v570;
+  static Integer v571;
+  static Integer v572;
+  static Integer v573;
+  static Integer v574;
+  static Integer v575;
+  static Integer v576;
+  static Integer v577;
+  static Integer v578;
+  static Integer v579;
+  static Integer v580;
+  static Integer v581;
+  static Integer v582;
+  static Integer v583;
+  static Integer v584;
+  static Integer v585;
+  static Integer v586;
+  static Integer v587;
+  static Integer v588;
+  static Integer v589;
+  static Integer v590;
+  static Integer v591;
+  static Integer v592;
+  static Integer v593;
+  static Integer v594;
+  static Integer v595;
+  static Integer v596;
+  static Integer v597;
+  static Integer v598;
+  static Integer v599;
+  static Integer v600;
+  static Integer v601;
+  static Integer v602;
+  static Integer v603;
+  static Integer v604;
+  static Integer v605;
+  static Integer v606;
+  static Integer v607;
+  static Integer v608;
+  static Integer v609;
+  static Integer v610;
+  static Integer v611;
+  static Integer v612;
+  static Integer v613;
+  static Integer v614;
+  static Integer v615;
+  static Integer v616;
+  static Integer v617;
+  static Integer v618;
+  static Integer v619;
+  static Integer v620;
+  static Integer v621;
+  static Integer v622;
+  static Integer v623;
+  static Integer v624;
+  static Integer v625;
+  static Integer v626;
+  static Integer v627;
+  static Integer v628;
+  static Integer v629;
+  static Integer v630;
+  static Integer v631;
+  static Integer v632;
+  static Integer v633;
+  static Integer v634;
+  static Integer v635;
+  static Integer v636;
+  static Integer v637;
+  static Integer v638;
+  static Integer v639;
+  static Integer v640;
+  static Integer v641;
+  static Integer v642;
+  static Integer v643;
+  static Integer v644;
+  static Integer v645;
+  static Integer v646;
+  static Integer v647;
+  static Integer v648;
+  static Integer v649;
+  static Integer v650;
+  static Integer v651;
+  static Integer v652;
+  static Integer v653;
+  static Integer v654;
+  static Integer v655;
+  static Integer v656;
+  static Integer v657;
+  static Integer v658;
+  static Integer v659;
+  static Integer v660;
+  static Integer v661;
+  static Integer v662;
+  static Integer v663;
+  static Integer v664;
+  static Integer v665;
+  static Integer v666;
+  static Integer v667;
+  static Integer v668;
+  static Integer v669;
+  static Integer v670;
+  static Integer v671;
+  static Integer v672;
+  static Integer v673;
+  static Integer v674;
+  static Integer v675;
+  static Integer v676;
+  static Integer v677;
+  static Integer v678;
+  static Integer v679;
+  static Integer v680;
+  static Integer v681;
+  static Integer v682;
+  static Integer v683;
+  static Integer v684;
+  static Integer v685;
+  static Integer v686;
+  static Integer v687;
+  static Integer v688;
+  static Integer v689;
+  static Integer v690;
+  static Integer v691;
+  static Integer v692;
+  static Integer v693;
+  static Integer v694;
+  static Integer v695;
+  static Integer v696;
+  static Integer v697;
+  static Integer v698;
+  static Integer v699;
+  static Integer v700;
+  static Integer v701;
+  static Integer v702;
+  static Integer v703;
+  static Integer v704;
+  static Integer v705;
+  static Integer v706;
+  static Integer v707;
+  static Integer v708;
+  static Integer v709;
+  static Integer v710;
+  static Integer v711;
+  static Integer v712;
+  static Integer v713;
+  static Integer v714;
+  static Integer v715;
+  static Integer v716;
+  static Integer v717;
+  static Integer v718;
+  static Integer v719;
+  static Integer v720;
+  static Integer v721;
+  static Integer v722;
+  static Integer v723;
+  static Integer v724;
+  static Integer v725;
+  static Integer v726;
+  static Integer v727;
+  static Integer v728;
+  static Integer v729;
+  static Integer v730;
+  static Integer v731;
+  static Integer v732;
+  static Integer v733;
+  static Integer v734;
+  static Integer v735;
+  static Integer v736;
+  static Integer v737;
+  static Integer v738;
+  static Integer v739;
+  static Integer v740;
+  static Integer v741;
+  static Integer v742;
+  static Integer v743;
+  static Integer v744;
+  static Integer v745;
+  static Integer v746;
+  static Integer v747;
+  static Integer v748;
+  static Integer v749;
+  static Integer v750;
+  static Integer v751;
+  static Integer v752;
+  static Integer v753;
+  static Integer v754;
+  static Integer v755;
+  static Integer v756;
+  static Integer v757;
+  static Integer v758;
+  static Integer v759;
+  static Integer v760;
+  static Integer v761;
+  static Integer v762;
+  static Integer v763;
+  static Integer v764;
+  static Integer v765;
+  static Integer v766;
+  static Integer v767;
+  static Integer v768;
+  static Integer v769;
+  static Integer v770;
+  static Integer v771;
+  static Integer v772;
+  static Integer v773;
+  static Integer v774;
+  static Integer v775;
+  static Integer v776;
+  static Integer v777;
+  static Integer v778;
+  static Integer v779;
+  static Integer v780;
+  static Integer v781;
+  static Integer v782;
+  static Integer v783;
+  static Integer v784;
+  static Integer v785;
+  static Integer v786;
+  static Integer v787;
+  static Integer v788;
+  static Integer v789;
+  static Integer v790;
+  static Integer v791;
+  static Integer v792;
+  static Integer v793;
+  static Integer v794;
+  static Integer v795;
+  static Integer v796;
+  static Integer v797;
+  static Integer v798;
+  static Integer v799;
+  static Integer v800;
+  static Integer v801;
+  static Integer v802;
+  static Integer v803;
+  static Integer v804;
+  static Integer v805;
+  static Integer v806;
+  static Integer v807;
+  static Integer v808;
+  static Integer v809;
+  static Integer v810;
+  static Integer v811;
+  static Integer v812;
+  static Integer v813;
+  static Integer v814;
+  static Integer v815;
+  static Integer v816;
+  static Integer v817;
+  static Integer v818;
+  static Integer v819;
+  static Integer v820;
+  static Integer v821;
+  static Integer v822;
+  static Integer v823;
+  static Integer v824;
+  static Integer v825;
+  static Integer v826;
+  static Integer v827;
+  static Integer v828;
+  static Integer v829;
+  static Integer v830;
+  static Integer v831;
+  static Integer v832;
+  static Integer v833;
+  static Integer v834;
+  static Integer v835;
+  static Integer v836;
+  static Integer v837;
+  static Integer v838;
+  static Integer v839;
+  static Integer v840;
+  static Integer v841;
+  static Integer v842;
+  static Integer v843;
+  static Integer v844;
+  static Integer v845;
+  static Integer v846;
+  static Integer v847;
+  static Integer v848;
+  static Integer v849;
+  static Integer v850;
+  static Integer v851;
+  static Integer v852;
+  static Integer v853;
+  static Integer v854;
+  static Integer v855;
+  static Integer v856;
+  static Integer v857;
+  static Integer v858;
+  static Integer v859;
+  static Integer v860;
+  static Integer v861;
+  static Integer v862;
+  static Integer v863;
+  static Integer v864;
+  static Integer v865;
+  static Integer v866;
+  static Integer v867;
+  static Integer v868;
+  static Integer v869;
+  static Integer v870;
+  static Integer v871;
+  static Integer v872;
+  static Integer v873;
+  static Integer v874;
+  static Integer v875;
+  static Integer v876;
+  static Integer v877;
+  static Integer v878;
+  static Integer v879;
+  static Integer v880;
+  static Integer v881;
+  static Integer v882;
+  static Integer v883;
+  static Integer v884;
+  static Integer v885;
+  static Integer v886;
+  static Integer v887;
+  static Integer v888;
+  static Integer v889;
+  static Integer v890;
+  static Integer v891;
+  static Integer v892;
+  static Integer v893;
+  static Integer v894;
+  static Integer v895;
+  static Integer v896;
+  static Integer v897;
+  static Integer v898;
+  static Integer v899;
+  static Integer v900;
+  static Integer v901;
+  static Integer v902;
+  static Integer v903;
+  static Integer v904;
+  static Integer v905;
+  static Integer v906;
+  static Integer v907;
+  static Integer v908;
+  static Integer v909;
+  static Integer v910;
+  static Integer v911;
+  static Integer v912;
+  static Integer v913;
+  static Integer v914;
+  static Integer v915;
+  static Integer v916;
+  static Integer v917;
+  static Integer v918;
+  static Integer v919;
+  static Integer v920;
+  static Integer v921;
+  static Integer v922;
+  static Integer v923;
+  static Integer v924;
+  static Integer v925;
+  static Integer v926;
+  static Integer v927;
+  static Integer v928;
+  static Integer v929;
+  static Integer v930;
+  static Integer v931;
+  static Integer v932;
+  static Integer v933;
+  static Integer v934;
+  static Integer v935;
+  static Integer v936;
+  static Integer v937;
+  static Integer v938;
+  static Integer v939;
+  static Integer v940;
+  static Integer v941;
+  static Integer v942;
+  static Integer v943;
+  static Integer v944;
+  static Integer v945;
+  static Integer v946;
+  static Integer v947;
+  static Integer v948;
+  static Integer v949;
+  static Integer v950;
+  static Integer v951;
+  static Integer v952;
+  static Integer v953;
+  static Integer v954;
+  static Integer v955;
+  static Integer v956;
+  static Integer v957;
+  static Integer v958;
+  static Integer v959;
+  static Integer v960;
+  static Integer v961;
+  static Integer v962;
+  static Integer v963;
+  static Integer v964;
+  static Integer v965;
+  static Integer v966;
+  static Integer v967;
+  static Integer v968;
+  static Integer v969;
+  static Integer v970;
+  static Integer v971;
+  static Integer v972;
+  static Integer v973;
+  static Integer v974;
+  static Integer v975;
+  static Integer v976;
+  static Integer v977;
+  static Integer v978;
+  static Integer v979;
+  static Integer v980;
+  static Integer v981;
+  static Integer v982;
+  static Integer v983;
+  static Integer v984;
+  static Integer v985;
+  static Integer v986;
+  static Integer v987;
+  static Integer v988;
+  static Integer v989;
+  static Integer v990;
+  static Integer v991;
+  static Integer v992;
+  static Integer v993;
+  static Integer v994;
+  static Integer v995;
+  static Integer v996;
+  static Integer v997;
+  static Integer v998;
+  static Integer v999;
+
+  static Map<String, Integer> f() {
+    Map<String, Integer> map = new HashMap<>();
+    map.put("k0", v0);
+    map.put("k1", v1);
+    map.put("k2", v2);
+    map.put("k3", v3);
+    map.put("k4", v4);
+    map.put("k5", v5);
+    map.put("k6", v6);
+    map.put("k7", v7);
+    map.put("k8", v8);
+    map.put("k9", v9);
+    map.put("k10", v10);
+    map.put("k11", v11);
+    map.put("k12", v12);
+    map.put("k13", v13);
+    map.put("k14", v14);
+    map.put("k15", v15);
+    map.put("k16", v16);
+    map.put("k17", v17);
+    map.put("k18", v18);
+    map.put("k19", v19);
+    map.put("k20", v20);
+    map.put("k21", v21);
+    map.put("k22", v22);
+    map.put("k23", v23);
+    map.put("k24", v24);
+    map.put("k25", v25);
+    map.put("k26", v26);
+    map.put("k27", v27);
+    map.put("k28", v28);
+    map.put("k29", v29);
+    map.put("k30", v30);
+    map.put("k31", v31);
+    map.put("k32", v32);
+    map.put("k33", v33);
+    map.put("k34", v34);
+    map.put("k35", v35);
+    map.put("k36", v36);
+    map.put("k37", v37);
+    map.put("k38", v38);
+    map.put("k39", v39);
+    map.put("k40", v40);
+    map.put("k41", v41);
+    map.put("k42", v42);
+    map.put("k43", v43);
+    map.put("k44", v44);
+    map.put("k45", v45);
+    map.put("k46", v46);
+    map.put("k47", v47);
+    map.put("k48", v48);
+    map.put("k49", v49);
+    map.put("k50", v50);
+    map.put("k51", v51);
+    map.put("k52", v52);
+    map.put("k53", v53);
+    map.put("k54", v54);
+    map.put("k55", v55);
+    map.put("k56", v56);
+    map.put("k57", v57);
+    map.put("k58", v58);
+    map.put("k59", v59);
+    map.put("k60", v60);
+    map.put("k61", v61);
+    map.put("k62", v62);
+    map.put("k63", v63);
+    map.put("k64", v64);
+    map.put("k65", v65);
+    map.put("k66", v66);
+    map.put("k67", v67);
+    map.put("k68", v68);
+    map.put("k69", v69);
+    map.put("k70", v70);
+    map.put("k71", v71);
+    map.put("k72", v72);
+    map.put("k73", v73);
+    map.put("k74", v74);
+    map.put("k75", v75);
+    map.put("k76", v76);
+    map.put("k77", v77);
+    map.put("k78", v78);
+    map.put("k79", v79);
+    map.put("k80", v80);
+    map.put("k81", v81);
+    map.put("k82", v82);
+    map.put("k83", v83);
+    map.put("k84", v84);
+    map.put("k85", v85);
+    map.put("k86", v86);
+    map.put("k87", v87);
+    map.put("k88", v88);
+    map.put("k89", v89);
+    map.put("k90", v90);
+    map.put("k91", v91);
+    map.put("k92", v92);
+    map.put("k93", v93);
+    map.put("k94", v94);
+    map.put("k95", v95);
+    map.put("k96", v96);
+    map.put("k97", v97);
+    map.put("k98", v98);
+    map.put("k99", v99);
+    map.put("k100", v100);
+    map.put("k101", v101);
+    map.put("k102", v102);
+    map.put("k103", v103);
+    map.put("k104", v104);
+    map.put("k105", v105);
+    map.put("k106", v106);
+    map.put("k107", v107);
+    map.put("k108", v108);
+    map.put("k109", v109);
+    map.put("k110", v110);
+    map.put("k111", v111);
+    map.put("k112", v112);
+    map.put("k113", v113);
+    map.put("k114", v114);
+    map.put("k115", v115);
+    map.put("k116", v116);
+    map.put("k117", v117);
+    map.put("k118", v118);
+    map.put("k119", v119);
+    map.put("k120", v120);
+    map.put("k121", v121);
+    map.put("k122", v122);
+    map.put("k123", v123);
+    map.put("k124", v124);
+    map.put("k125", v125);
+    map.put("k126", v126);
+    map.put("k127", v127);
+    map.put("k128", v128);
+    map.put("k129", v129);
+    map.put("k130", v130);
+    map.put("k131", v131);
+    map.put("k132", v132);
+    map.put("k133", v133);
+    map.put("k134", v134);
+    map.put("k135", v135);
+    map.put("k136", v136);
+    map.put("k137", v137);
+    map.put("k138", v138);
+    map.put("k139", v139);
+    map.put("k140", v140);
+    map.put("k141", v141);
+    map.put("k142", v142);
+    map.put("k143", v143);
+    map.put("k144", v144);
+    map.put("k145", v145);
+    map.put("k146", v146);
+    map.put("k147", v147);
+    map.put("k148", v148);
+    map.put("k149", v149);
+    map.put("k150", v150);
+    map.put("k151", v151);
+    map.put("k152", v152);
+    map.put("k153", v153);
+    map.put("k154", v154);
+    map.put("k155", v155);
+    map.put("k156", v156);
+    map.put("k157", v157);
+    map.put("k158", v158);
+    map.put("k159", v159);
+    map.put("k160", v160);
+    map.put("k161", v161);
+    map.put("k162", v162);
+    map.put("k163", v163);
+    map.put("k164", v164);
+    map.put("k165", v165);
+    map.put("k166", v166);
+    map.put("k167", v167);
+    map.put("k168", v168);
+    map.put("k169", v169);
+    map.put("k170", v170);
+    map.put("k171", v171);
+    map.put("k172", v172);
+    map.put("k173", v173);
+    map.put("k174", v174);
+    map.put("k175", v175);
+    map.put("k176", v176);
+    map.put("k177", v177);
+    map.put("k178", v178);
+    map.put("k179", v179);
+    map.put("k180", v180);
+    map.put("k181", v181);
+    map.put("k182", v182);
+    map.put("k183", v183);
+    map.put("k184", v184);
+    map.put("k185", v185);
+    map.put("k186", v186);
+    map.put("k187", v187);
+    map.put("k188", v188);
+    map.put("k189", v189);
+    map.put("k190", v190);
+    map.put("k191", v191);
+    map.put("k192", v192);
+    map.put("k193", v193);
+    map.put("k194", v194);
+    map.put("k195", v195);
+    map.put("k196", v196);
+    map.put("k197", v197);
+    map.put("k198", v198);
+    map.put("k199", v199);
+    map.put("k200", v200);
+    map.put("k201", v201);
+    map.put("k202", v202);
+    map.put("k203", v203);
+    map.put("k204", v204);
+    map.put("k205", v205);
+    map.put("k206", v206);
+    map.put("k207", v207);
+    map.put("k208", v208);
+    map.put("k209", v209);
+    map.put("k210", v210);
+    map.put("k211", v211);
+    map.put("k212", v212);
+    map.put("k213", v213);
+    map.put("k214", v214);
+    map.put("k215", v215);
+    map.put("k216", v216);
+    map.put("k217", v217);
+    map.put("k218", v218);
+    map.put("k219", v219);
+    map.put("k220", v220);
+    map.put("k221", v221);
+    map.put("k222", v222);
+    map.put("k223", v223);
+    map.put("k224", v224);
+    map.put("k225", v225);
+    map.put("k226", v226);
+    map.put("k227", v227);
+    map.put("k228", v228);
+    map.put("k229", v229);
+    map.put("k230", v230);
+    map.put("k231", v231);
+    map.put("k232", v232);
+    map.put("k233", v233);
+    map.put("k234", v234);
+    map.put("k235", v235);
+    map.put("k236", v236);
+    map.put("k237", v237);
+    map.put("k238", v238);
+    map.put("k239", v239);
+    map.put("k240", v240);
+    map.put("k241", v241);
+    map.put("k242", v242);
+    map.put("k243", v243);
+    map.put("k244", v244);
+    map.put("k245", v245);
+    map.put("k246", v246);
+    map.put("k247", v247);
+    map.put("k248", v248);
+    map.put("k249", v249);
+    map.put("k250", v250);
+    map.put("k251", v251);
+    map.put("k252", v252);
+    map.put("k253", v253);
+    map.put("k254", v254);
+    map.put("k255", v255);
+    map.put("k256", v256);
+    map.put("k257", v257);
+    map.put("k258", v258);
+    map.put("k259", v259);
+    map.put("k260", v260);
+    map.put("k261", v261);
+    map.put("k262", v262);
+    map.put("k263", v263);
+    map.put("k264", v264);
+    map.put("k265", v265);
+    map.put("k266", v266);
+    map.put("k267", v267);
+    map.put("k268", v268);
+    map.put("k269", v269);
+    map.put("k270", v270);
+    map.put("k271", v271);
+    map.put("k272", v272);
+    map.put("k273", v273);
+    map.put("k274", v274);
+    map.put("k275", v275);
+    map.put("k276", v276);
+    map.put("k277", v277);
+    map.put("k278", v278);
+    map.put("k279", v279);
+    map.put("k280", v280);
+    map.put("k281", v281);
+    map.put("k282", v282);
+    map.put("k283", v283);
+    map.put("k284", v284);
+    map.put("k285", v285);
+    map.put("k286", v286);
+    map.put("k287", v287);
+    map.put("k288", v288);
+    map.put("k289", v289);
+    map.put("k290", v290);
+    map.put("k291", v291);
+    map.put("k292", v292);
+    map.put("k293", v293);
+    map.put("k294", v294);
+    map.put("k295", v295);
+    map.put("k296", v296);
+    map.put("k297", v297);
+    map.put("k298", v298);
+    map.put("k299", v299);
+    map.put("k300", v300);
+    map.put("k301", v301);
+    map.put("k302", v302);
+    map.put("k303", v303);
+    map.put("k304", v304);
+    map.put("k305", v305);
+    map.put("k306", v306);
+    map.put("k307", v307);
+    map.put("k308", v308);
+    map.put("k309", v309);
+    map.put("k310", v310);
+    map.put("k311", v311);
+    map.put("k312", v312);
+    map.put("k313", v313);
+    map.put("k314", v314);
+    map.put("k315", v315);
+    map.put("k316", v316);
+    map.put("k317", v317);
+    map.put("k318", v318);
+    map.put("k319", v319);
+    map.put("k320", v320);
+    map.put("k321", v321);
+    map.put("k322", v322);
+    map.put("k323", v323);
+    map.put("k324", v324);
+    map.put("k325", v325);
+    map.put("k326", v326);
+    map.put("k327", v327);
+    map.put("k328", v328);
+    map.put("k329", v329);
+    map.put("k330", v330);
+    map.put("k331", v331);
+    map.put("k332", v332);
+    map.put("k333", v333);
+    map.put("k334", v334);
+    map.put("k335", v335);
+    map.put("k336", v336);
+    map.put("k337", v337);
+    map.put("k338", v338);
+    map.put("k339", v339);
+    map.put("k340", v340);
+    map.put("k341", v341);
+    map.put("k342", v342);
+    map.put("k343", v343);
+    map.put("k344", v344);
+    map.put("k345", v345);
+    map.put("k346", v346);
+    map.put("k347", v347);
+    map.put("k348", v348);
+    map.put("k349", v349);
+    map.put("k350", v350);
+    map.put("k351", v351);
+    map.put("k352", v352);
+    map.put("k353", v353);
+    map.put("k354", v354);
+    map.put("k355", v355);
+    map.put("k356", v356);
+    map.put("k357", v357);
+    map.put("k358", v358);
+    map.put("k359", v359);
+    map.put("k360", v360);
+    map.put("k361", v361);
+    map.put("k362", v362);
+    map.put("k363", v363);
+    map.put("k364", v364);
+    map.put("k365", v365);
+    map.put("k366", v366);
+    map.put("k367", v367);
+    map.put("k368", v368);
+    map.put("k369", v369);
+    map.put("k370", v370);
+    map.put("k371", v371);
+    map.put("k372", v372);
+    map.put("k373", v373);
+    map.put("k374", v374);
+    map.put("k375", v375);
+    map.put("k376", v376);
+    map.put("k377", v377);
+    map.put("k378", v378);
+    map.put("k379", v379);
+    map.put("k380", v380);
+    map.put("k381", v381);
+    map.put("k382", v382);
+    map.put("k383", v383);
+    map.put("k384", v384);
+    map.put("k385", v385);
+    map.put("k386", v386);
+    map.put("k387", v387);
+    map.put("k388", v388);
+    map.put("k389", v389);
+    map.put("k390", v390);
+    map.put("k391", v391);
+    map.put("k392", v392);
+    map.put("k393", v393);
+    map.put("k394", v394);
+    map.put("k395", v395);
+    map.put("k396", v396);
+    map.put("k397", v397);
+    map.put("k398", v398);
+    map.put("k399", v399);
+    map.put("k400", v400);
+    map.put("k401", v401);
+    map.put("k402", v402);
+    map.put("k403", v403);
+    map.put("k404", v404);
+    map.put("k405", v405);
+    map.put("k406", v406);
+    map.put("k407", v407);
+    map.put("k408", v408);
+    map.put("k409", v409);
+    map.put("k410", v410);
+    map.put("k411", v411);
+    map.put("k412", v412);
+    map.put("k413", v413);
+    map.put("k414", v414);
+    map.put("k415", v415);
+    map.put("k416", v416);
+    map.put("k417", v417);
+    map.put("k418", v418);
+    map.put("k419", v419);
+    map.put("k420", v420);
+    map.put("k421", v421);
+    map.put("k422", v422);
+    map.put("k423", v423);
+    map.put("k424", v424);
+    map.put("k425", v425);
+    map.put("k426", v426);
+    map.put("k427", v427);
+    map.put("k428", v428);
+    map.put("k429", v429);
+    map.put("k430", v430);
+    map.put("k431", v431);
+    map.put("k432", v432);
+    map.put("k433", v433);
+    map.put("k434", v434);
+    map.put("k435", v435);
+    map.put("k436", v436);
+    map.put("k437", v437);
+    map.put("k438", v438);
+    map.put("k439", v439);
+    map.put("k440", v440);
+    map.put("k441", v441);
+    map.put("k442", v442);
+    map.put("k443", v443);
+    map.put("k444", v444);
+    map.put("k445", v445);
+    map.put("k446", v446);
+    map.put("k447", v447);
+    map.put("k448", v448);
+    map.put("k449", v449);
+    map.put("k450", v450);
+    map.put("k451", v451);
+    map.put("k452", v452);
+    map.put("k453", v453);
+    map.put("k454", v454);
+    map.put("k455", v455);
+    map.put("k456", v456);
+    map.put("k457", v457);
+    map.put("k458", v458);
+    map.put("k459", v459);
+    map.put("k460", v460);
+    map.put("k461", v461);
+    map.put("k462", v462);
+    map.put("k463", v463);
+    map.put("k464", v464);
+    map.put("k465", v465);
+    map.put("k466", v466);
+    map.put("k467", v467);
+    map.put("k468", v468);
+    map.put("k469", v469);
+    map.put("k470", v470);
+    map.put("k471", v471);
+    map.put("k472", v472);
+    map.put("k473", v473);
+    map.put("k474", v474);
+    map.put("k475", v475);
+    map.put("k476", v476);
+    map.put("k477", v477);
+    map.put("k478", v478);
+    map.put("k479", v479);
+    map.put("k480", v480);
+    map.put("k481", v481);
+    map.put("k482", v482);
+    map.put("k483", v483);
+    map.put("k484", v484);
+    map.put("k485", v485);
+    map.put("k486", v486);
+    map.put("k487", v487);
+    map.put("k488", v488);
+    map.put("k489", v489);
+    map.put("k490", v490);
+    map.put("k491", v491);
+    map.put("k492", v492);
+    map.put("k493", v493);
+    map.put("k494", v494);
+    map.put("k495", v495);
+    map.put("k496", v496);
+    map.put("k497", v497);
+    map.put("k498", v498);
+    map.put("k499", v499);
+    map.put("k500", v500);
+    map.put("k501", v501);
+    map.put("k502", v502);
+    map.put("k503", v503);
+    map.put("k504", v504);
+    map.put("k505", v505);
+    map.put("k506", v506);
+    map.put("k507", v507);
+    map.put("k508", v508);
+    map.put("k509", v509);
+    map.put("k510", v510);
+    map.put("k511", v511);
+    map.put("k512", v512);
+    map.put("k513", v513);
+    map.put("k514", v514);
+    map.put("k515", v515);
+    map.put("k516", v516);
+    map.put("k517", v517);
+    map.put("k518", v518);
+    map.put("k519", v519);
+    map.put("k520", v520);
+    map.put("k521", v521);
+    map.put("k522", v522);
+    map.put("k523", v523);
+    map.put("k524", v524);
+    map.put("k525", v525);
+    map.put("k526", v526);
+    map.put("k527", v527);
+    map.put("k528", v528);
+    map.put("k529", v529);
+    map.put("k530", v530);
+    map.put("k531", v531);
+    map.put("k532", v532);
+    map.put("k533", v533);
+    map.put("k534", v534);
+    map.put("k535", v535);
+    map.put("k536", v536);
+    map.put("k537", v537);
+    map.put("k538", v538);
+    map.put("k539", v539);
+    map.put("k540", v540);
+    map.put("k541", v541);
+    map.put("k542", v542);
+    map.put("k543", v543);
+    map.put("k544", v544);
+    map.put("k545", v545);
+    map.put("k546", v546);
+    map.put("k547", v547);
+    map.put("k548", v548);
+    map.put("k549", v549);
+    map.put("k550", v550);
+    map.put("k551", v551);
+    map.put("k552", v552);
+    map.put("k553", v553);
+    map.put("k554", v554);
+    map.put("k555", v555);
+    map.put("k556", v556);
+    map.put("k557", v557);
+    map.put("k558", v558);
+    map.put("k559", v559);
+    map.put("k560", v560);
+    map.put("k561", v561);
+    map.put("k562", v562);
+    map.put("k563", v563);
+    map.put("k564", v564);
+    map.put("k565", v565);
+    map.put("k566", v566);
+    map.put("k567", v567);
+    map.put("k568", v568);
+    map.put("k569", v569);
+    map.put("k570", v570);
+    map.put("k571", v571);
+    map.put("k572", v572);
+    map.put("k573", v573);
+    map.put("k574", v574);
+    map.put("k575", v575);
+    map.put("k576", v576);
+    map.put("k577", v577);
+    map.put("k578", v578);
+    map.put("k579", v579);
+    map.put("k580", v580);
+    map.put("k581", v581);
+    map.put("k582", v582);
+    map.put("k583", v583);
+    map.put("k584", v584);
+    map.put("k585", v585);
+    map.put("k586", v586);
+    map.put("k587", v587);
+    map.put("k588", v588);
+    map.put("k589", v589);
+    map.put("k590", v590);
+    map.put("k591", v591);
+    map.put("k592", v592);
+    map.put("k593", v593);
+    map.put("k594", v594);
+    map.put("k595", v595);
+    map.put("k596", v596);
+    map.put("k597", v597);
+    map.put("k598", v598);
+    map.put("k599", v599);
+    map.put("k600", v600);
+    map.put("k601", v601);
+    map.put("k602", v602);
+    map.put("k603", v603);
+    map.put("k604", v604);
+    map.put("k605", v605);
+    map.put("k606", v606);
+    map.put("k607", v607);
+    map.put("k608", v608);
+    map.put("k609", v609);
+    map.put("k610", v610);
+    map.put("k611", v611);
+    map.put("k612", v612);
+    map.put("k613", v613);
+    map.put("k614", v614);
+    map.put("k615", v615);
+    map.put("k616", v616);
+    map.put("k617", v617);
+    map.put("k618", v618);
+    map.put("k619", v619);
+    map.put("k620", v620);
+    map.put("k621", v621);
+    map.put("k622", v622);
+    map.put("k623", v623);
+    map.put("k624", v624);
+    map.put("k625", v625);
+    map.put("k626", v626);
+    map.put("k627", v627);
+    map.put("k628", v628);
+    map.put("k629", v629);
+    map.put("k630", v630);
+    map.put("k631", v631);
+    map.put("k632", v632);
+    map.put("k633", v633);
+    map.put("k634", v634);
+    map.put("k635", v635);
+    map.put("k636", v636);
+    map.put("k637", v637);
+    map.put("k638", v638);
+    map.put("k639", v639);
+    map.put("k640", v640);
+    map.put("k641", v641);
+    map.put("k642", v642);
+    map.put("k643", v643);
+    map.put("k644", v644);
+    map.put("k645", v645);
+    map.put("k646", v646);
+    map.put("k647", v647);
+    map.put("k648", v648);
+    map.put("k649", v649);
+    map.put("k650", v650);
+    map.put("k651", v651);
+    map.put("k652", v652);
+    map.put("k653", v653);
+    map.put("k654", v654);
+    map.put("k655", v655);
+    map.put("k656", v656);
+    map.put("k657", v657);
+    map.put("k658", v658);
+    map.put("k659", v659);
+    map.put("k660", v660);
+    map.put("k661", v661);
+    map.put("k662", v662);
+    map.put("k663", v663);
+    map.put("k664", v664);
+    map.put("k665", v665);
+    map.put("k666", v666);
+    map.put("k667", v667);
+    map.put("k668", v668);
+    map.put("k669", v669);
+    map.put("k670", v670);
+    map.put("k671", v671);
+    map.put("k672", v672);
+    map.put("k673", v673);
+    map.put("k674", v674);
+    map.put("k675", v675);
+    map.put("k676", v676);
+    map.put("k677", v677);
+    map.put("k678", v678);
+    map.put("k679", v679);
+    map.put("k680", v680);
+    map.put("k681", v681);
+    map.put("k682", v682);
+    map.put("k683", v683);
+    map.put("k684", v684);
+    map.put("k685", v685);
+    map.put("k686", v686);
+    map.put("k687", v687);
+    map.put("k688", v688);
+    map.put("k689", v689);
+    map.put("k690", v690);
+    map.put("k691", v691);
+    map.put("k692", v692);
+    map.put("k693", v693);
+    map.put("k694", v694);
+    map.put("k695", v695);
+    map.put("k696", v696);
+    map.put("k697", v697);
+    map.put("k698", v698);
+    map.put("k699", v699);
+    map.put("k700", v700);
+    map.put("k701", v701);
+    map.put("k702", v702);
+    map.put("k703", v703);
+    map.put("k704", v704);
+    map.put("k705", v705);
+    map.put("k706", v706);
+    map.put("k707", v707);
+    map.put("k708", v708);
+    map.put("k709", v709);
+    map.put("k710", v710);
+    map.put("k711", v711);
+    map.put("k712", v712);
+    map.put("k713", v713);
+    map.put("k714", v714);
+    map.put("k715", v715);
+    map.put("k716", v716);
+    map.put("k717", v717);
+    map.put("k718", v718);
+    map.put("k719", v719);
+    map.put("k720", v720);
+    map.put("k721", v721);
+    map.put("k722", v722);
+    map.put("k723", v723);
+    map.put("k724", v724);
+    map.put("k725", v725);
+    map.put("k726", v726);
+    map.put("k727", v727);
+    map.put("k728", v728);
+    map.put("k729", v729);
+    map.put("k730", v730);
+    map.put("k731", v731);
+    map.put("k732", v732);
+    map.put("k733", v733);
+    map.put("k734", v734);
+    map.put("k735", v735);
+    map.put("k736", v736);
+    map.put("k737", v737);
+    map.put("k738", v738);
+    map.put("k739", v739);
+    map.put("k740", v740);
+    map.put("k741", v741);
+    map.put("k742", v742);
+    map.put("k743", v743);
+    map.put("k744", v744);
+    map.put("k745", v745);
+    map.put("k746", v746);
+    map.put("k747", v747);
+    map.put("k748", v748);
+    map.put("k749", v749);
+    map.put("k750", v750);
+    map.put("k751", v751);
+    map.put("k752", v752);
+    map.put("k753", v753);
+    map.put("k754", v754);
+    map.put("k755", v755);
+    map.put("k756", v756);
+    map.put("k757", v757);
+    map.put("k758", v758);
+    map.put("k759", v759);
+    map.put("k760", v760);
+    map.put("k761", v761);
+    map.put("k762", v762);
+    map.put("k763", v763);
+    map.put("k764", v764);
+    map.put("k765", v765);
+    map.put("k766", v766);
+    map.put("k767", v767);
+    map.put("k768", v768);
+    map.put("k769", v769);
+    map.put("k770", v770);
+    map.put("k771", v771);
+    map.put("k772", v772);
+    map.put("k773", v773);
+    map.put("k774", v774);
+    map.put("k775", v775);
+    map.put("k776", v776);
+    map.put("k777", v777);
+    map.put("k778", v778);
+    map.put("k779", v779);
+    map.put("k780", v780);
+    map.put("k781", v781);
+    map.put("k782", v782);
+    map.put("k783", v783);
+    map.put("k784", v784);
+    map.put("k785", v785);
+    map.put("k786", v786);
+    map.put("k787", v787);
+    map.put("k788", v788);
+    map.put("k789", v789);
+    map.put("k790", v790);
+    map.put("k791", v791);
+    map.put("k792", v792);
+    map.put("k793", v793);
+    map.put("k794", v794);
+    map.put("k795", v795);
+    map.put("k796", v796);
+    map.put("k797", v797);
+    map.put("k798", v798);
+    map.put("k799", v799);
+    map.put("k800", v800);
+    map.put("k801", v801);
+    map.put("k802", v802);
+    map.put("k803", v803);
+    map.put("k804", v804);
+    map.put("k805", v805);
+    map.put("k806", v806);
+    map.put("k807", v807);
+    map.put("k808", v808);
+    map.put("k809", v809);
+    map.put("k810", v810);
+    map.put("k811", v811);
+    map.put("k812", v812);
+    map.put("k813", v813);
+    map.put("k814", v814);
+    map.put("k815", v815);
+    map.put("k816", v816);
+    map.put("k817", v817);
+    map.put("k818", v818);
+    map.put("k819", v819);
+    map.put("k820", v820);
+    map.put("k821", v821);
+    map.put("k822", v822);
+    map.put("k823", v823);
+    map.put("k824", v824);
+    map.put("k825", v825);
+    map.put("k826", v826);
+    map.put("k827", v827);
+    map.put("k828", v828);
+    map.put("k829", v829);
+    map.put("k830", v830);
+    map.put("k831", v831);
+    map.put("k832", v832);
+    map.put("k833", v833);
+    map.put("k834", v834);
+    map.put("k835", v835);
+    map.put("k836", v836);
+    map.put("k837", v837);
+    map.put("k838", v838);
+    map.put("k839", v839);
+    map.put("k840", v840);
+    map.put("k841", v841);
+    map.put("k842", v842);
+    map.put("k843", v843);
+    map.put("k844", v844);
+    map.put("k845", v845);
+    map.put("k846", v846);
+    map.put("k847", v847);
+    map.put("k848", v848);
+    map.put("k849", v849);
+    map.put("k850", v850);
+    map.put("k851", v851);
+    map.put("k852", v852);
+    map.put("k853", v853);
+    map.put("k854", v854);
+    map.put("k855", v855);
+    map.put("k856", v856);
+    map.put("k857", v857);
+    map.put("k858", v858);
+    map.put("k859", v859);
+    map.put("k860", v860);
+    map.put("k861", v861);
+    map.put("k862", v862);
+    map.put("k863", v863);
+    map.put("k864", v864);
+    map.put("k865", v865);
+    map.put("k866", v866);
+    map.put("k867", v867);
+    map.put("k868", v868);
+    map.put("k869", v869);
+    map.put("k870", v870);
+    map.put("k871", v871);
+    map.put("k872", v872);
+    map.put("k873", v873);
+    map.put("k874", v874);
+    map.put("k875", v875);
+    map.put("k876", v876);
+    map.put("k877", v877);
+    map.put("k878", v878);
+    map.put("k879", v879);
+    map.put("k880", v880);
+    map.put("k881", v881);
+    map.put("k882", v882);
+    map.put("k883", v883);
+    map.put("k884", v884);
+    map.put("k885", v885);
+    map.put("k886", v886);
+    map.put("k887", v887);
+    map.put("k888", v888);
+    map.put("k889", v889);
+    map.put("k890", v890);
+    map.put("k891", v891);
+    map.put("k892", v892);
+    map.put("k893", v893);
+    map.put("k894", v894);
+    map.put("k895", v895);
+    map.put("k896", v896);
+    map.put("k897", v897);
+    map.put("k898", v898);
+    map.put("k899", v899);
+    map.put("k900", v900);
+    map.put("k901", v901);
+    map.put("k902", v902);
+    map.put("k903", v903);
+    map.put("k904", v904);
+    map.put("k905", v905);
+    map.put("k906", v906);
+    map.put("k907", v907);
+    map.put("k908", v908);
+    map.put("k909", v909);
+    map.put("k910", v910);
+    map.put("k911", v911);
+    map.put("k912", v912);
+    map.put("k913", v913);
+    map.put("k914", v914);
+    map.put("k915", v915);
+    map.put("k916", v916);
+    map.put("k917", v917);
+    map.put("k918", v918);
+    map.put("k919", v919);
+    map.put("k920", v920);
+    map.put("k921", v921);
+    map.put("k922", v922);
+    map.put("k923", v923);
+    map.put("k924", v924);
+    map.put("k925", v925);
+    map.put("k926", v926);
+    map.put("k927", v927);
+    map.put("k928", v928);
+    map.put("k929", v929);
+    map.put("k930", v930);
+    map.put("k931", v931);
+    map.put("k932", v932);
+    map.put("k933", v933);
+    map.put("k934", v934);
+    map.put("k935", v935);
+    map.put("k936", v936);
+    map.put("k937", v937);
+    map.put("k938", v938);
+    map.put("k939", v939);
+    map.put("k940", v940);
+    map.put("k941", v941);
+    map.put("k942", v942);
+    map.put("k943", v943);
+    map.put("k944", v944);
+    map.put("k945", v945);
+    map.put("k946", v946);
+    map.put("k947", v947);
+    map.put("k948", v948);
+    map.put("k949", v949);
+    map.put("k950", v950);
+    map.put("k951", v951);
+    map.put("k952", v952);
+    map.put("k953", v953);
+    map.put("k954", v954);
+    map.put("k955", v955);
+    map.put("k956", v956);
+    map.put("k957", v957);
+    map.put("k958", v958);
+    map.put("k959", v959);
+    map.put("k960", v960);
+    map.put("k961", v961);
+    map.put("k962", v962);
+    map.put("k963", v963);
+    map.put("k964", v964);
+    map.put("k965", v965);
+    map.put("k966", v966);
+    map.put("k967", v967);
+    map.put("k968", v968);
+    map.put("k969", v969);
+    map.put("k970", v970);
+    map.put("k971", v971);
+    map.put("k972", v972);
+    map.put("k973", v973);
+    map.put("k974", v974);
+    map.put("k975", v975);
+    map.put("k976", v976);
+    map.put("k977", v977);
+    map.put("k978", v978);
+    map.put("k979", v979);
+    map.put("k980", v980);
+    map.put("k981", v981);
+    map.put("k982", v982);
+    map.put("k983", v983);
+    map.put("k984", v984);
+    map.put("k985", v985);
+    map.put("k986", v986);
+    map.put("k987", v987);
+    map.put("k988", v988);
+    map.put("k989", v989);
+    map.put("k990", v990);
+    map.put("k991", v991);
+    map.put("k992", v992);
+    map.put("k993", v993);
+    map.put("k994", v994);
+    map.put("k995", v995);
+    map.put("k996", v996);
+    map.put("k997", v997);
+    map.put("k998", v998);
+    map.put("k999", v999);
+    return map;
+  }
+}
diff --git a/checker/jtreg/nullness/Issue1809.java b/checker/jtreg/nullness/Issue1809.java
new file mode 100644
index 0000000..94d03d6
--- /dev/null
+++ b/checker/jtreg/nullness/Issue1809.java
@@ -0,0 +1,40 @@
+/*
+ * @test
+ * @summary Test for Issue 1809: caching issue.
+ *     https://github.com/typetools/checker-framework/issues/1809
+ *     Also see framework/tests/all-systems/Issue1809.java
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -AatfCacheSize=4 Issue1809.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -AatfDoNotCache Issue1809.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker Issue1809.java
+ */
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+@SuppressWarnings("unchecked")
+abstract class Issue1809 {
+
+  abstract <T> Stream<T> concat(Stream<? extends T>... streams);
+
+  abstract Optional<A> f();
+
+  private static class A {}
+
+  interface B {
+    List<C> g();
+  }
+
+  interface C {
+    List<S> h();
+  }
+
+  interface S {}
+
+  private Stream<A> xrefsFor(B b) {
+    return concat(b.g().stream().flatMap(a -> a.h().stream().map(c -> f())))
+        .filter(Optional::isPresent)
+        .map(Optional::get);
+  }
+}
diff --git a/checker/jtreg/nullness/Issue2853.java b/checker/jtreg/nullness/Issue2853.java
new file mode 100644
index 0000000..ba037ec
--- /dev/null
+++ b/checker/jtreg/nullness/Issue2853.java
@@ -0,0 +1,119 @@
+/*
+ * @test
+ * @summary Test case for issue #2853: https://github.com/typetools/checker-framework/issues/2853
+ *
+ * @compile/timeout=30 -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker Issue2853.java
+ */
+public class Issue2853 {
+
+  abstract static class A {
+
+    abstract B getB();
+
+    public abstract C getC();
+
+    public abstract Object getD();
+
+    public abstract Object getE();
+
+    public abstract Object getF();
+
+    public abstract Object getG();
+
+    public abstract H getH();
+  }
+
+  abstract static class B {}
+
+  abstract static class C {
+
+    abstract Object getI();
+  }
+
+  static class I {
+
+    static class J {}
+  }
+
+  abstract static class H {
+
+    abstract Object getK();
+
+    abstract Object getL();
+
+    abstract Object getM();
+
+    abstract Object getN();
+  }
+
+  abstract static class O {}
+
+  abstract static class J {
+
+    static O f(B b) {
+      throw new AssertionError();
+    }
+  }
+
+  abstract static class K {
+
+    abstract Object getL();
+  }
+
+  abstract static class M {
+
+    abstract N getN();
+  }
+
+  abstract static class N {
+
+    abstract Object f();
+  }
+
+  static class Test {
+
+    static final ImmutableSet<P> C =
+        new ImmutableSet.Builder<P>()
+            .add(R.c((A x) -> J.f(x.getB())))
+            .add(R.c((A x) -> x.getC().getI()))
+            .add(R.c((M x) -> x.getN().f()))
+            .add(R.c((A x) -> x.getH().getK()))
+            .add(R.c((A x) -> x.getH().getM()))
+            .add(R.c((A x) -> x.getH().getL()))
+            .add(R.c((A x) -> x.getH().getN()))
+            .add(R.c((A x) -> x.getD()))
+            .add(R.c((A x) -> x.getE()))
+            .add(R.c((A x) -> x.getE()))
+            .add(R.c((A x) -> x.getG()))
+            .add(R.c((K x) -> x.getL()))
+            .build();
+
+    interface P {}
+
+    interface Q<U, V> {
+
+      V get(U message);
+    }
+
+    private static class R<T, U> implements P {
+
+      static <T, U> R<T, U> c(Q<T, U> x) {
+        throw new AssertionError();
+      }
+    }
+  }
+
+  abstract static class ImmutableSet<E> {
+
+    static class Builder<E> {
+
+      public Builder<E> add(E... elements) {
+        throw new AssertionError();
+      }
+
+      public ImmutableSet<E> build() {
+        throw new AssertionError();
+      }
+    }
+  }
+}
diff --git a/checker/jtreg/nullness/Issue347-con.out b/checker/jtreg/nullness/Issue347-con.out
new file mode 100644
index 0000000..471c499
--- /dev/null
+++ b/checker/jtreg/nullness/Issue347-con.out
@@ -0,0 +1,2 @@
+Issue347.java:32:5: compiler.err.proc.messager: [dereference.of.nullable] dereference of possibly-null reference nble
+1 error
diff --git a/checker/jtreg/nullness/Issue347.java b/checker/jtreg/nullness/Issue347.java
new file mode 100644
index 0000000..0ebc27d
--- /dev/null
+++ b/checker/jtreg/nullness/Issue347.java
@@ -0,0 +1,34 @@
+/*
+ * @test
+ * @summary Test for Issue 347: concurrent semantics has desired behavior
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Alint Issue347.java
+ * @compile/fail/ref=Issue347-con.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Alint Issue347.java -AconcurrentSemantics
+ */
+
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue347 {
+
+  @MonotonicNonNull String mono;
+
+  @Nullable String nble;
+
+  void testMono() {
+    if (mono == null) {
+      return;
+    }
+    // The object referenced by mono might change, but it can't become null again, even in
+    // concurrent semantics.
+    mono.toString();
+  }
+
+  void testNble() {
+    if (nble == null) {
+      return;
+    }
+    // error expected in concurrent semantics only
+    nble.toString();
+  }
+}
diff --git a/checker/jtreg/nullness/Issue373-err.out b/checker/jtreg/nullness/Issue373-err.out
new file mode 100644
index 0000000..f04a090
--- /dev/null
+++ b/checker/jtreg/nullness/Issue373-err.out
@@ -0,0 +1,8 @@
+Issue373.java:16:13: compiler.err.proc.messager: [override.return] Incompatible return type.
+found   : Set<Entry<String, String>>
+required: Set<Entry<@KeyFor("this") String, String>>
+Consequence: method in Issue373
+  Set<Entry<String, String>> entrySet(Issue373 this)
+cannot override method in AbstractMap<String, String>
+  Set<Entry<@KeyFor("this") String, String>> entrySet(AbstractMap<String, String> this)
+1 error
diff --git a/checker/jtreg/nullness/Issue373-warn.out b/checker/jtreg/nullness/Issue373-warn.out
new file mode 100644
index 0000000..bf78466
--- /dev/null
+++ b/checker/jtreg/nullness/Issue373-warn.out
@@ -0,0 +1,8 @@
+Issue373.java:16:13: compiler.warn.proc.messager: [override.return] Incompatible return type.
+found   : Set<Entry<String, String>>
+required: Set<Entry<@KeyFor("this") String, String>>
+Consequence: method in Issue373
+  Set<Entry<String, String>> entrySet(Issue373 this)
+cannot override method in AbstractMap<String, String>
+  Set<Entry<@KeyFor("this") String, String>> entrySet(AbstractMap<String, String> this)
+1 warning
diff --git a/checker/jtreg/nullness/Issue373.java b/checker/jtreg/nullness/Issue373.java
new file mode 100644
index 0000000..b0e19bf
--- /dev/null
+++ b/checker/jtreg/nullness/Issue373.java
@@ -0,0 +1,19 @@
+/*
+ * @test
+ * @summary Test for Issue 373: message duplicated when using -Awarns
+ *
+ * @compile/fail/ref=Issue373-err.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Alint Issue373.java
+ * @compile/ref=Issue373-warn.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Alint Issue373.java -Awarns
+ */
+
+import java.util.AbstractMap;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+public class Issue373 extends AbstractMap<String, String> {
+  @Override
+  public Set<Map.Entry<String, String>> entrySet() {
+    return Collections.emptySet();
+  }
+}
diff --git a/checker/jtreg/nullness/PersistUtil.java b/checker/jtreg/nullness/PersistUtil.java
new file mode 100644
index 0000000..51f92de
--- /dev/null
+++ b/checker/jtreg/nullness/PersistUtil.java
@@ -0,0 +1,113 @@
+// Note that "-processor org/checkerframework/checker.nullness.NullnessChecker"
+// is added to the invocation of the compiler!
+// TODO: add a @Processor method-annotation to parameterize
+
+/**
+ * This class has auxiliary methods to compile a class and return its classfile. It is used by
+ * defaultPersists/Driver and inheritDeclAnnoPersist/Driver.
+ */
+import com.sun.tools.classfile.ClassFile;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import java.util.StringJoiner;
+
+public class PersistUtil {
+
+  public static String testClassOf(Method m) {
+    TestClass tc = m.getAnnotation(TestClass.class);
+    if (tc != null) {
+      return tc.value();
+    } else {
+      return "Test";
+    }
+  }
+
+  public static ClassFile compileAndReturn(String fullFile, String testClass) throws Exception {
+    File source = writeTestFile(fullFile);
+    File clazzFile = compileTestFile(source, testClass);
+    return ClassFile.read(clazzFile);
+  }
+
+  public static File writeTestFile(String fullFile) throws IOException {
+    File f = new File("Test.java");
+    PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(f)));
+    out.println(fullFile);
+    out.close();
+    return f;
+  }
+
+  public static File compileTestFile(File f, String testClass) {
+    int rc =
+        com.sun.tools.javac.Main.compile(
+            new String[] {
+              "-source",
+              "1.8",
+              "-g",
+              "-processor",
+              "org.checkerframework.checker.nullness.NullnessChecker",
+              f.getPath()
+            });
+    if (rc != 0) {
+      throw new Error("compilation failed. rc=" + rc);
+    }
+    String path;
+    if (f.getParent() != null) {
+      path = f.getParent();
+    } else {
+      path = "";
+    }
+
+    return new File(path + testClass + ".class");
+  }
+
+  public static String wrap(String compact) {
+    StringJoiner sj = new StringJoiner(System.lineSeparator());
+
+    // Automatically import java.util
+    sj.add("");
+    sj.add("import java.util.*;");
+    sj.add("import java.lang.annotation.*;");
+
+    // And the Nullness qualifiers
+    sj.add("import org.checkerframework.framework.qual.DefaultQualifier;");
+    sj.add("import org.checkerframework.checker.nullness.qual.*;");
+    sj.add("import org.checkerframework.dataflow.qual.*;");
+
+    sj.add("");
+    boolean isSnippet =
+        !(compact.startsWith("class") || compact.contains(" class"))
+            && !compact.contains("interface")
+            && !compact.contains("enum");
+
+    if (isSnippet) {
+      sj.add("class Test {");
+    }
+
+    sj.add(compact);
+
+    if (isSnippet) {
+      sj.add("}");
+      sj.add("");
+    }
+
+    return sj.toString();
+  }
+}
+
+/**
+ * The name of the class that should be analyzed. Should only need to be provided when analyzing
+ * inner classes.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@interface TestClass {
+  String value() default "Test";
+}
diff --git a/checker/jtreg/nullness/UncheckedWarning.java b/checker/jtreg/nullness/UncheckedWarning.java
new file mode 100644
index 0000000..92d5c98
--- /dev/null
+++ b/checker/jtreg/nullness/UncheckedWarning.java
@@ -0,0 +1,26 @@
+/*
+ * @test
+ * @summary Test that unchecked warnings are issued, without and with processor
+ *
+ * @compile/ref=UncheckedWarning.out -XDrawDiagnostics -Xlint:unchecked UncheckedWarning.java
+ * @compile/ref=UncheckedWarning.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Alint UncheckedWarning.java
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+class Test<T> {
+  List<T> foo() {
+    List<String> ret = new ArrayList<>();
+    ret.add("Hi there!");
+    return (List) ret;
+  }
+}
+
+public class UncheckedWarning {
+  public static void main(String[] args) {
+    Test<Integer> ti = new Test<>();
+    List<Integer> ls = ti.foo();
+    Integer i = ls.get(0);
+  }
+}
diff --git a/checker/jtreg/nullness/UncheckedWarning.out b/checker/jtreg/nullness/UncheckedWarning.out
new file mode 100644
index 0000000..84ed086
--- /dev/null
+++ b/checker/jtreg/nullness/UncheckedWarning.out
@@ -0,0 +1,2 @@
+UncheckedWarning.java:16:12: compiler.warn.prob.found.req: (compiler.misc.unchecked.assign), java.util.List, java.util.List<T>
+1 warning
diff --git a/checker/jtreg/nullness/annotationsOnExtends/Other.java b/checker/jtreg/nullness/annotationsOnExtends/Other.java
new file mode 100644
index 0000000..67a8736
--- /dev/null
+++ b/checker/jtreg/nullness/annotationsOnExtends/Other.java
@@ -0,0 +1,14 @@
+/*
+ * @test
+ * @summary Test for bug when storing annotations on extends or implements in class declarations in elements.
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker Test.java Test2.java Other.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker Other.java Test.java Test2.java
+ */
+
+public class Other {
+  void foo() {
+    Test other = null;
+    Test2 other2 = null;
+  }
+}
diff --git a/checker/jtreg/nullness/annotationsOnExtends/Test.java b/checker/jtreg/nullness/annotationsOnExtends/Test.java
new file mode 100644
index 0000000..6fd8099
--- /dev/null
+++ b/checker/jtreg/nullness/annotationsOnExtends/Test.java
@@ -0,0 +1,3 @@
+import java.io.Serializable;
+
+public class Test implements Serializable {}
diff --git a/checker/jtreg/nullness/annotationsOnExtends/Test2.java b/checker/jtreg/nullness/annotationsOnExtends/Test2.java
new file mode 100644
index 0000000..68e0782
--- /dev/null
+++ b/checker/jtreg/nullness/annotationsOnExtends/Test2.java
@@ -0,0 +1 @@
+public class Test2 extends Object {}
diff --git a/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.java b/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.java
new file mode 100644
index 0000000..ffb8393
--- /dev/null
+++ b/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.java
@@ -0,0 +1,16 @@
+/*
+ * @test
+ * @summary Test that the stub files get invoked
+ * @compile/fail/ref=DefaultConstructor.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Alint DefaultConstructor.java
+ */
+
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+
+public class DefaultConstructor {
+  Object nullObject;
+  @MonotonicNonNull Object lazyField;
+
+  public Object getNull() {
+    return nullObject;
+  }
+}
diff --git a/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.out b/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.out
new file mode 100644
index 0000000..443379e
--- /dev/null
+++ b/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.out
@@ -0,0 +1,2 @@
+DefaultConstructor.java:10:10: compiler.err.proc.messager: [initialization.field.uninitialized] the default constructor does not initialize field nullObject
+1 error
diff --git a/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.java b/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.java
new file mode 100644
index 0000000..01addfe
--- /dev/null
+++ b/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.java
@@ -0,0 +1,35 @@
+/*
+ * @test
+ * @summary Test that the stub files get invoked
+ * @compile/fail/ref=NonDefaultConstructor.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Alint NonDefaultConstructor.java
+ */
+
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+
+public class NonDefaultConstructor {
+  Object nonNull = 4;
+  Object nullObject;
+  @MonotonicNonNull Object lazyField;
+
+  // error doesn't initialize nullObject
+  public NonDefaultConstructor() {}
+
+  // error doesn't initialize nullObject
+  public NonDefaultConstructor(int i) {
+    lazyField = "m";
+  }
+
+  // OK, lazyField is lazy!
+  public NonDefaultConstructor(double a) {
+    nullObject = "n";
+  }
+
+  public NonDefaultConstructor(String s) {
+    nullObject = "a";
+    lazyField = "m";
+  }
+
+  public Object getNull() {
+    return nullObject;
+  }
+}
diff --git a/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.out b/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.out
new file mode 100644
index 0000000..a4c8478
--- /dev/null
+++ b/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.out
@@ -0,0 +1,3 @@
+NonDefaultConstructor.java:15:10: compiler.err.proc.messager: [initialization.fields.uninitialized] the constructor does not initialize fields: nullObject
+NonDefaultConstructor.java:18:10: compiler.err.proc.messager: [initialization.fields.uninitialized] the constructor does not initialize fields: nullObject
+2 errors
diff --git a/checker/jtreg/nullness/defaultsPersist/Classes.java b/checker/jtreg/nullness/defaultsPersist/Classes.java
new file mode 100644
index 0000000..582ce06
--- /dev/null
+++ b/checker/jtreg/nullness/defaultsPersist/Classes.java
@@ -0,0 +1,204 @@
+/*
+ * @test
+ * @summary Test that defaulted types are stored in bytecode.
+ *
+ * @compile  ../PersistUtil.java Driver.java ReferenceInfoUtil.java Classes.java
+ * @run main Driver Classes
+ */
+
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.CLASS_TYPE_PARAMETER;
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.CLASS_TYPE_PARAMETER_BOUND;
+
+public class Classes {
+
+  /* TODO: store extends/implements in TypesIntoElements.
+  @TADescriptions({
+      @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1),
+      @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1),
+      @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1),
+  })
+  public String extendsDefault1() {
+      return "class Test {}";
+  }
+
+  @TADescriptions({
+      @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1),
+      @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1),
+      @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1),
+  })
+  public String extendsDefault2() {
+      return "class Test extends Object {}";
+  }
+
+  @TADescriptions({
+      @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1),
+      @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1),
+      @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1),
+      @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=0),
+      @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=0),
+      @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=0),
+  })
+  public String extendsDefault3() {
+      return "class Test implements java.io.Serializable {}";
+  }
+  */
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/Nullable",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+  })
+  public String typeParams1() {
+    return "class Test <T1> {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+  })
+  public String typeParams2() {
+    return "class Test<T1 extends Object> {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 1),
+  })
+  public String typeParams3() {
+    return "class Test<T2 extends Comparable<T2>> {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/Nullable",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = CLASS_TYPE_PARAMETER,
+        paramIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 1,
+        boundIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 1,
+        boundIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = CLASS_TYPE_PARAMETER_BOUND,
+        paramIndex = 1,
+        boundIndex = 1),
+  })
+  public String typeParams4() {
+    return "class Test<T1, T2 extends Comparable<T2>> {}";
+  }
+}
diff --git a/checker/jtreg/nullness/defaultsPersist/Constructors.java b/checker/jtreg/nullness/defaultsPersist/Constructors.java
new file mode 100644
index 0000000..a5389af
--- /dev/null
+++ b/checker/jtreg/nullness/defaultsPersist/Constructors.java
@@ -0,0 +1,210 @@
+/*
+ * @test
+ * @summary Test that defaulted types are stored in bytecode.
+ *
+ * @compile ../PersistUtil.java Driver.java ReferenceInfoUtil.java Constructors.java
+ * @run main Driver Constructors
+ */
+
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.METHOD_FORMAL_PARAMETER;
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.METHOD_RECEIVER;
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.METHOD_TYPE_PARAMETER;
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.METHOD_TYPE_PARAMETER_BOUND;
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.THROWS;
+
+public class Constructors {
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_FORMAL_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_FORMAL_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_FORMAL_PARAMETER,
+        paramIndex = 0),
+  })
+  public String paramDefault1() {
+    return "Test(Object o) {}";
+  }
+
+  @TADescriptions({
+    // Should there be defaults?
+    // @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+    //     type = METHOD_RETURN),
+    // @TADescription(annotation =
+    //    "org/checkerframework/checker/initialization/qual/Initialized", type = METHOD_RETURN),
+    // @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+    //     type = METHOD_RETURN),
+  })
+  public String retDefault1() {
+    return "Test() {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = THROWS,
+        typeIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = THROWS,
+        typeIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = THROWS,
+        typeIndex = 0),
+  })
+  public String throwsDefault1() {
+    return "Test() throws Throwable {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = THROWS,
+        typeIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = THROWS,
+        typeIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = THROWS,
+        typeIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = THROWS,
+        typeIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = THROWS,
+        typeIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = THROWS,
+        typeIndex = 1),
+  })
+  public String throwsDefault2() {
+    return "Test() throws ArrayIndexOutOfBoundsException, NullPointerException {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_RECEIVER),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_RECEIVER),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_RECEIVER),
+  })
+  @TestClass("Outer$Inner")
+  public String recvDefault1() {
+    return "class Outer {" + "  class Inner {" + "    Inner(Outer Outer.this) {}" + "  }" + "}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/Nullable",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+  })
+  public String typeParams1() {
+    return "<M1> Test(M1 p) {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+  })
+  public String typeParams2() {
+    return "<M1 extends Object> Test(M1 p) {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 1),
+  })
+  public String typeParams3() {
+    return "<M2 extends Comparable<M2>> Test(M2 p) {}";
+  }
+}
diff --git a/checker/jtreg/nullness/defaultsPersist/Driver.java b/checker/jtreg/nullness/defaultsPersist/Driver.java
new file mode 100644
index 0000000..e97b20d
--- /dev/null
+++ b/checker/jtreg/nullness/defaultsPersist/Driver.java
@@ -0,0 +1,176 @@
+// Keep somewhat in sync with
+// langtools/test/tools/javac/annotations/typeAnnotations/referenceinfos/Driver.java
+
+// I removed some unnecessary code, e.g. declarations of @TA.
+// I changed expected logic to handle multiple appearances
+// of the same qualifier in different positions.
+
+import com.sun.tools.classfile.ClassFile;
+import com.sun.tools.classfile.TypeAnnotation;
+import com.sun.tools.classfile.TypeAnnotation.TargetType;
+import java.io.PrintStream;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.javacutil.Pair;
+
+public class Driver {
+
+  private static final PrintStream out = System.out;
+
+  public static void main(String[] args) throws Exception {
+    if (args.length == 0 || args.length > 1) {
+      throw new IllegalArgumentException("Usage: java Driver <test-name>");
+    }
+    String name = args[0];
+    Class<?> clazz = Class.forName(name);
+    new Driver().runDriver(clazz.newInstance());
+  }
+
+  protected void runDriver(Object object) throws Exception {
+    int passed = 0, failed = 0;
+    Class<?> clazz = object.getClass();
+    out.println("Tests for " + clazz.getName());
+
+    // Find methods
+    for (Method method : clazz.getMethods()) {
+      List<Pair<String, TypeAnnotation.Position>> expected = expectedOf(method);
+      if (expected == null) {
+        continue;
+      }
+      if (method.getReturnType() != String.class) {
+        throw new IllegalArgumentException("Test method needs to return a string: " + method);
+      }
+      String testClass = PersistUtil.testClassOf(method);
+
+      try {
+        String compact = (String) method.invoke(object);
+        String fullFile = PersistUtil.wrap(compact);
+        ClassFile cf = PersistUtil.compileAndReturn(fullFile, testClass);
+        List<TypeAnnotation> actual = ReferenceInfoUtil.extendedAnnotationsOf(cf);
+        ReferenceInfoUtil.compare(expected, actual, cf);
+        out.println("PASSED:  " + method.getName());
+        ++passed;
+      } catch (Throwable e) {
+        out.println("FAILED:  " + method.getName());
+        out.println("    " + e);
+        ++failed;
+      }
+    }
+
+    out.println();
+    int total = passed + failed;
+    out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED");
+
+    out.flush();
+
+    if (failed != 0) {
+      throw new RuntimeException(failed + " tests failed");
+    }
+  }
+
+  private List<Pair<String, TypeAnnotation.Position>> expectedOf(Method m) {
+    TADescription ta = m.getAnnotation(TADescription.class);
+    TADescriptions tas = m.getAnnotation(TADescriptions.class);
+
+    if (ta == null && tas == null) {
+      return null;
+    }
+
+    List<Pair<String, TypeAnnotation.Position>> result = new ArrayList<>();
+
+    if (ta != null) {
+      result.add(expectedOf(ta));
+    }
+
+    if (tas != null) {
+      for (TADescription a : tas.value()) {
+        result.add(expectedOf(a));
+      }
+    }
+
+    return result;
+  }
+
+  private Pair<String, TypeAnnotation.Position> expectedOf(TADescription d) {
+    String annoName = d.annotation();
+
+    TypeAnnotation.Position p = new TypeAnnotation.Position();
+    p.type = d.type();
+    if (d.offset() != NOT_SET) {
+      p.offset = d.offset();
+    }
+    if (d.lvarOffset().length != 0) {
+      p.lvarOffset = d.lvarOffset();
+    }
+    if (d.lvarLength().length != 0) {
+      p.lvarLength = d.lvarLength();
+    }
+    if (d.lvarIndex().length != 0) {
+      p.lvarIndex = d.lvarIndex();
+    }
+    if (d.boundIndex() != NOT_SET) {
+      p.bound_index = d.boundIndex();
+    }
+    if (d.paramIndex() != NOT_SET) {
+      p.parameter_index = d.paramIndex();
+    }
+    if (d.typeIndex() != NOT_SET) {
+      p.type_index = d.typeIndex();
+    }
+    if (d.exceptionIndex() != NOT_SET) {
+      p.exception_index = d.exceptionIndex();
+    }
+    if (d.genericLocation().length != 0) {
+      p.location = TypeAnnotation.Position.getTypePathFromBinary(wrapIntArray(d.genericLocation()));
+    }
+
+    return Pair.of(annoName, p);
+  }
+
+  private List<Integer> wrapIntArray(int[] ints) {
+    List<Integer> list = new ArrayList<>(ints.length);
+    for (int i : ints) {
+      list.add(i);
+    }
+    return list;
+  }
+
+  public static final int NOT_SET = -888;
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@interface TADescription {
+  String annotation();
+
+  TargetType type();
+
+  int offset() default Driver.NOT_SET;
+
+  int[] lvarOffset() default {};
+
+  int[] lvarLength() default {};
+
+  int[] lvarIndex() default {};
+
+  int boundIndex() default Driver.NOT_SET;
+
+  int paramIndex() default Driver.NOT_SET;
+
+  int typeIndex() default Driver.NOT_SET;
+
+  int exceptionIndex() default Driver.NOT_SET;
+
+  int[] genericLocation() default {};
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@interface TADescriptions {
+  TADescription[] value() default {};
+}
diff --git a/checker/jtreg/nullness/defaultsPersist/Fields.java b/checker/jtreg/nullness/defaultsPersist/Fields.java
new file mode 100644
index 0000000..f8f4224
--- /dev/null
+++ b/checker/jtreg/nullness/defaultsPersist/Fields.java
@@ -0,0 +1,236 @@
+/*
+ * @test
+ * @summary Test that defaulted types are stored in bytecode.
+ *
+ * @compile ../PersistUtil.java Driver.java ReferenceInfoUtil.java Fields.java
+ * @run main Driver Fields
+ * @ignore This fails for Java 11. See Issue 2816.
+ */
+
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.FIELD;
+
+public class Fields {
+
+  @TADescriptions({
+    @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD),
+  })
+  public String fieldDefault() {
+    return "Object f = new Object();";
+  }
+
+  @TADescriptions({
+    @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD),
+  })
+  public String fieldDefaultOneExplicit() {
+    return "@NonNull Object f = new Object();";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/Nullable",
+        type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD),
+  })
+  public String fieldWithDefaultQualifier() {
+    return "@DefaultQualifier(Nullable.class)" + System.lineSeparator() + " Object f;";
+  }
+
+  @TADescriptions({
+    @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = FIELD,
+        genericLocation = {0, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD,
+        genericLocation = {0, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD,
+        genericLocation = {0, 0}),
+  })
+  public String fieldArray1() {
+    return "String[] sa = new String[1];";
+  }
+
+  @TADescriptions({
+    @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/Nullable",
+        type = FIELD,
+        genericLocation = {0, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD,
+        genericLocation = {0, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD,
+        genericLocation = {0, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = FIELD,
+        genericLocation = {0, 0, 0, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD,
+        genericLocation = {0, 0, 0, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD,
+        genericLocation = {0, 0, 0, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = FIELD,
+        genericLocation = {0, 0, 0, 0, 0, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD,
+        genericLocation = {0, 0, 0, 0, 0, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD,
+        genericLocation = {0, 0, 0, 0, 0, 0}),
+  })
+  public String fieldArray2() {
+    return "String[] @Nullable [][] saaa = new String[1][][];";
+  }
+
+  @TADescriptions({
+    // in front of the java.util.List
+    @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD),
+
+    // in front of Object //TODO: NEXT ANNO CHANGE TO NULLABLE WHEN WE GET JDK WORKING WITH THIS
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = FIELD,
+        genericLocation = {3, 0, 2, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD,
+        genericLocation = {3, 0, 2, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD,
+        genericLocation = {3, 0, 2, 0}),
+
+    // in front of the wildcard (?)
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = FIELD,
+        genericLocation = {3, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD,
+        genericLocation = {3, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD,
+        genericLocation = {3, 0}),
+  })
+  public String wildcards1() {
+    return "java.util.List<? extends Object> f = new java.util.ArrayList<>();";
+  }
+
+  @TADescriptions({
+    // in front of the first java.util.List
+    @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD),
+
+    // in front of the wildcard (?)
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = FIELD,
+        genericLocation = {3, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD,
+        genericLocation = {3, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD,
+        genericLocation = {3, 0}),
+
+    // in front of the second java.util.List
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = FIELD,
+        genericLocation = {3, 0, 2, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD,
+        genericLocation = {3, 0, 2, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD,
+        genericLocation = {3, 0, 2, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = FIELD,
+        genericLocation = {3, 0, 2, 0, 3, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD,
+        genericLocation = {3, 0, 2, 0, 3, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD,
+        genericLocation = {3, 0, 2, 0, 3, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = FIELD,
+        genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = FIELD,
+        genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = FIELD,
+        genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}),
+  })
+  public String wildcards2() {
+    return "java.util.List<? extends java.util.List<String[]>> f = new java.util.ArrayList<>();";
+  }
+}
diff --git a/checker/jtreg/nullness/defaultsPersist/Methods.java b/checker/jtreg/nullness/defaultsPersist/Methods.java
new file mode 100644
index 0000000..8b2bf2f
--- /dev/null
+++ b/checker/jtreg/nullness/defaultsPersist/Methods.java
@@ -0,0 +1,212 @@
+/*
+ * @test
+ * @summary Test that defaulted types are stored in bytecode.
+ *
+ * @compile ../PersistUtil.java Driver.java ReferenceInfoUtil.java Methods.java
+ * @run main Driver Methods
+ */
+
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.METHOD_FORMAL_PARAMETER;
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.METHOD_RECEIVER;
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.METHOD_RETURN;
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.METHOD_TYPE_PARAMETER;
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.METHOD_TYPE_PARAMETER_BOUND;
+import static com.sun.tools.classfile.TypeAnnotation.TargetType.THROWS;
+
+public class Methods {
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_FORMAL_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_FORMAL_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_FORMAL_PARAMETER,
+        paramIndex = 0),
+  })
+  public String paramDefault1() {
+    return "void pm1(Object o) {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_RETURN),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_RETURN),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_RETURN),
+  })
+  public String retDefault1() {
+    return "Object rm1() { return new Object(); }";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = THROWS,
+        typeIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = THROWS,
+        typeIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = THROWS,
+        typeIndex = 0),
+  })
+  public String throwsDefault1() {
+    return "void tm1() throws Throwable {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = THROWS,
+        typeIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = THROWS,
+        typeIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = THROWS,
+        typeIndex = 0), // from KeyFor
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = THROWS,
+        typeIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = THROWS,
+        typeIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = THROWS,
+        typeIndex = 1),
+  })
+  public String throwsDefault2() {
+    return "void tm2() throws ArrayIndexOutOfBoundsException, NullPointerException {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_RECEIVER),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_RECEIVER),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_RECEIVER),
+  })
+  public String recvDefault1() {
+    return "void rd1(Test this) {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/Nullable",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+  })
+  public String typeParams1() {
+    return "<M1> void foo(M1 p) {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 0),
+  })
+  public String typeParams2() {
+    return "<M1 extends Object> void foo(M1 p) {}";
+  }
+
+  @TADescriptions({
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_TYPE_PARAMETER,
+        paramIndex = 0),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/NonNull",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/initialization/qual/Initialized",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 1),
+    @TADescription(
+        annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor",
+        type = METHOD_TYPE_PARAMETER_BOUND,
+        paramIndex = 0,
+        boundIndex = 1),
+  })
+  public String typeParams3() {
+    return "<M2 extends Comparable<M2>> void bar(M2 p) {}";
+  }
+}
diff --git a/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java b/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java
new file mode 100644
index 0000000..97deb42
--- /dev/null
+++ b/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java
@@ -0,0 +1,240 @@
+// Keep somewhat in sync with
+// langtools/test/tools/javac/annotations/typeAnnotations/referenceinfos/ReferenceInfoUtil.java
+// Adapted to handled the same type qualifier appearing multiple times.
+
+import com.sun.tools.classfile.Attribute;
+import com.sun.tools.classfile.ClassFile;
+import com.sun.tools.classfile.Code_attribute;
+import com.sun.tools.classfile.ConstantPool.InvalidIndex;
+import com.sun.tools.classfile.ConstantPool.UnexpectedEntry;
+import com.sun.tools.classfile.Field;
+import com.sun.tools.classfile.Method;
+import com.sun.tools.classfile.RuntimeTypeAnnotations_attribute;
+import com.sun.tools.classfile.TypeAnnotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.checkerframework.javacutil.Pair;
+
+public class ReferenceInfoUtil {
+
+  public static final int IGNORE_VALUE = -321;
+
+  public static List<TypeAnnotation> extendedAnnotationsOf(ClassFile cf) {
+    List<TypeAnnotation> annos = new ArrayList<>();
+    findAnnotations(cf, annos);
+    return annos;
+  }
+
+  /////////////////// Extract type annotations //////////////////
+  private static void findAnnotations(ClassFile cf, List<TypeAnnotation> annos) {
+    findAnnotations(cf, Attribute.RuntimeVisibleTypeAnnotations, annos);
+    findAnnotations(cf, Attribute.RuntimeInvisibleTypeAnnotations, annos);
+
+    for (Field f : cf.fields) {
+      findAnnotations(cf, f, annos);
+    }
+    for (Method m : cf.methods) {
+      findAnnotations(cf, m, annos);
+    }
+  }
+
+  private static void findAnnotations(ClassFile cf, Method m, List<TypeAnnotation> annos) {
+    findAnnotations(cf, m, Attribute.RuntimeVisibleTypeAnnotations, annos);
+    findAnnotations(cf, m, Attribute.RuntimeInvisibleTypeAnnotations, annos);
+  }
+
+  private static void findAnnotations(ClassFile cf, Field m, List<TypeAnnotation> annos) {
+    findAnnotations(cf, m, Attribute.RuntimeVisibleTypeAnnotations, annos);
+    findAnnotations(cf, m, Attribute.RuntimeInvisibleTypeAnnotations, annos);
+  }
+
+  /**
+   * Test the result of Attributes.getIndex according to expectations encoded in the method's name.
+   */
+  private static void findAnnotations(ClassFile cf, String name, List<TypeAnnotation> annos) {
+    int index = cf.attributes.getIndex(cf.constant_pool, name);
+    if (index != -1) {
+      Attribute attr = cf.attributes.get(index);
+      assert attr instanceof RuntimeTypeAnnotations_attribute;
+      RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr;
+      annos.addAll(Arrays.asList(tAttr.annotations));
+    }
+  }
+
+  /**
+   * Test the result of Attributes.getIndex according to expectations encoded in the method's name.
+   */
+  private static void findAnnotations(
+      ClassFile cf, Method m, String name, List<TypeAnnotation> annos) {
+    int index = m.attributes.getIndex(cf.constant_pool, name);
+    if (index != -1) {
+      Attribute attr = m.attributes.get(index);
+      assert attr instanceof RuntimeTypeAnnotations_attribute;
+      RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr;
+      annos.addAll(Arrays.asList(tAttr.annotations));
+    }
+
+    int cindex = m.attributes.getIndex(cf.constant_pool, Attribute.Code);
+    if (cindex != -1) {
+      Attribute cattr = m.attributes.get(cindex);
+      assert cattr instanceof Code_attribute;
+      Code_attribute cAttr = (Code_attribute) cattr;
+      index = cAttr.attributes.getIndex(cf.constant_pool, name);
+      if (index != -1) {
+        Attribute attr = cAttr.attributes.get(index);
+        assert attr instanceof RuntimeTypeAnnotations_attribute;
+        RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr;
+        annos.addAll(Arrays.asList(tAttr.annotations));
+      }
+    }
+  }
+
+  /**
+   * Test the result of Attributes.getIndex according to expectations encoded in the method's name.
+   */
+  private static void findAnnotations(
+      ClassFile cf, Field m, String name, List<TypeAnnotation> annos) {
+    int index = m.attributes.getIndex(cf.constant_pool, name);
+    if (index != -1) {
+      Attribute attr = m.attributes.get(index);
+      assert attr instanceof RuntimeTypeAnnotations_attribute;
+      RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr;
+      annos.addAll(Arrays.asList(tAttr.annotations));
+    }
+  }
+
+  /////////////////////// Equality testing /////////////////////
+  private static boolean areEquals(int a, int b) {
+    return a == b || a == IGNORE_VALUE || b == IGNORE_VALUE;
+  }
+
+  private static boolean areEquals(int[] a, int[] a2) {
+    if (a == a2) {
+      return true;
+    }
+    if (a == null || a2 == null) {
+      return false;
+    }
+
+    int length = a.length;
+    if (a2.length != length) {
+      return false;
+    }
+
+    for (int i = 0; i < length; i++) {
+      if (areEquals(a[i], a2[i])) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  public static boolean areEquals(TypeAnnotation.Position p1, TypeAnnotation.Position p2) {
+    if (p1 == p2) {
+      return true;
+    }
+    if (p1 == null || p2 == null) {
+      return false;
+    }
+
+    boolean result =
+        ((p1.type == p2.type)
+            && (p1.location.equals(p2.location))
+            && areEquals(p1.offset, p2.offset)
+            && areEquals(p1.lvarOffset, p2.lvarOffset)
+            && areEquals(p1.lvarLength, p2.lvarLength)
+            && areEquals(p1.lvarIndex, p2.lvarIndex)
+            && areEquals(p1.bound_index, p2.bound_index)
+            && areEquals(p1.parameter_index, p2.parameter_index)
+            && areEquals(p1.type_index, p2.type_index)
+            && areEquals(p1.exception_index, p2.exception_index));
+    return result;
+  }
+
+  public static String positionCompareStr(TypeAnnotation.Position p1, TypeAnnotation.Position p2) {
+    return String.join(
+        System.lineSeparator(),
+        "type = " + p1.type + ", " + p2.type,
+        "offset = " + p1.offset + ", " + p2.offset,
+        "lvarOffset = " + p1.lvarOffset + ", " + p2.lvarOffset,
+        "lvarLength = " + p1.lvarLength + ", " + p2.lvarLength,
+        "lvarIndex = " + p1.lvarIndex + ", " + p2.lvarIndex,
+        "bound_index = " + p1.bound_index + ", " + p2.bound_index,
+        "parameter_index = " + p1.parameter_index + ", " + p2.parameter_index,
+        "type_index = " + p1.type_index + ", " + p2.type_index,
+        "exception_index = " + p1.exception_index + ", " + p2.exception_index,
+        "");
+  }
+
+  private static TypeAnnotation findAnnotation(
+      String name, TypeAnnotation.Position expected, List<TypeAnnotation> annotations, ClassFile cf)
+      throws InvalidIndex, UnexpectedEntry {
+    String properName = "L" + name + ";";
+    for (TypeAnnotation anno : annotations) {
+      String actualName = cf.constant_pool.getUTF8Value(anno.annotation.type_index);
+
+      if (properName.equals(actualName)) {
+        System.out.println("For Anno: " + actualName);
+      }
+
+      if (properName.equals(actualName) && areEquals(expected, anno.position)) {
+        return anno;
+      }
+    }
+    return null;
+  }
+
+  public static boolean compare(
+      List<Pair<String, TypeAnnotation.Position>> expectedAnnos,
+      List<TypeAnnotation> actualAnnos,
+      ClassFile cf)
+      throws InvalidIndex, UnexpectedEntry {
+    if (actualAnnos.size() != expectedAnnos.size()) {
+      throw new ComparisonException("Wrong number of annotations", expectedAnnos, actualAnnos);
+    }
+
+    for (Pair<String, TypeAnnotation.Position> e : expectedAnnos) {
+      String aName = e.first;
+      TypeAnnotation.Position expected = e.second;
+      TypeAnnotation actual = findAnnotation(aName, expected, actualAnnos, cf);
+      if (actual == null) {
+        throw new ComparisonException(
+            "Expected annotation not found: " + aName + " position: " + expected,
+            expectedAnnos,
+            actualAnnos);
+      }
+    }
+    return true;
+  }
+}
+
+class ComparisonException extends RuntimeException {
+  private static final long serialVersionUID = -3930499712333815821L;
+
+  public final List<Pair<String, TypeAnnotation.Position>> expected;
+  public final List<TypeAnnotation> found;
+
+  public ComparisonException(
+      String message,
+      List<Pair<String, TypeAnnotation.Position>> expected,
+      List<TypeAnnotation> found) {
+    super(message);
+    this.expected = expected;
+    this.found = found;
+  }
+
+  public String toString() {
+    return String.join(
+        System.lineSeparator(),
+        super.toString(),
+        "\tExpected: "
+            + expected.size()
+            + " annotations; but found: "
+            + found.size()
+            + " annotations",
+        "  Expected: " + expected,
+        "  Found: " + found);
+  }
+}
diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/AbstractClass.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/AbstractClass.java
new file mode 100644
index 0000000..a9d0388
--- /dev/null
+++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/AbstractClass.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public abstract class AbstractClass {
+  @Nullable Object f;
+
+  @EnsuresNonNull("f")
+  public abstract void setf();
+
+  public abstract void setg();
+}
diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Driver.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Driver.java
new file mode 100644
index 0000000..1651a10
--- /dev/null
+++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Driver.java
@@ -0,0 +1,108 @@
+// Keep somewhat in sync with
+// ../defaultsPersist/Driver.java and ../PersistUtil.
+
+import com.sun.tools.classfile.Annotation;
+import com.sun.tools.classfile.ClassFile;
+import java.io.PrintStream;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Driver {
+
+  private static final PrintStream out = System.out;
+
+  public static void main(String[] args) throws Exception {
+    if (args.length == 0 || args.length > 1) {
+      throw new IllegalArgumentException("Usage: java Driver <test-name>");
+    }
+    String name = args[0];
+    Class<?> clazz = Class.forName(name);
+    new Driver().runDriver(clazz.newInstance());
+  }
+
+  protected void runDriver(Object object) throws Exception {
+    int passed = 0, failed = 0;
+    Class<?> clazz = object.getClass();
+    out.println("Tests for " + clazz.getName());
+
+    // Find methods
+    for (Method method : clazz.getMethods()) {
+      List<String> expected = expectedOf(method);
+      if (expected == null) {
+        continue;
+      }
+      if (method.getReturnType() != String.class) {
+        throw new IllegalArgumentException("Test method needs to return a string: " + method);
+      }
+      String testClass = PersistUtil.testClassOf(method);
+
+      try {
+        String compact = (String) method.invoke(object);
+        String fullFile = PersistUtil.wrap(compact);
+        ClassFile cf = PersistUtil.compileAndReturn(fullFile, testClass);
+        List<Annotation> actual = ReferenceInfoUtil.extendedAnnotationsOf(cf);
+        ReferenceInfoUtil.compare(expected, actual, cf);
+        out.println("PASSED:  " + method.getName());
+        ++passed;
+      } catch (Throwable e) {
+        out.println("FAILED:  " + method.getName());
+        out.println("    " + e);
+        ++failed;
+      }
+    }
+
+    out.println();
+    int total = passed + failed;
+    out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED");
+
+    out.flush();
+
+    if (failed != 0) {
+      throw new RuntimeException(failed + " tests failed");
+    }
+  }
+
+  private List<String> expectedOf(Method m) {
+    ADescription ta = m.getAnnotation(ADescription.class);
+    ADescriptions tas = m.getAnnotation(ADescriptions.class);
+
+    if (ta == null && tas == null) {
+      return null;
+    }
+
+    List<String> result = new ArrayList<>();
+
+    if (ta != null) {
+      result.add(expectedOf(ta));
+    }
+
+    if (tas != null) {
+      for (ADescription a : tas.value()) {
+        result.add(expectedOf(a));
+      }
+    }
+
+    return result;
+  }
+
+  private String expectedOf(ADescription d) {
+    return d.annotation();
+  }
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@interface ADescription {
+  String annotation();
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@interface ADescriptions {
+  ADescription[] value() default {};
+}
diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java
new file mode 100644
index 0000000..1cf830d
--- /dev/null
+++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java
@@ -0,0 +1,43 @@
+/*
+ * @test
+ * @summary Test that inherited declaration annotations are stored in bytecode.
+ *
+ * @compile ../PersistUtil.java Driver.java ReferenceInfoUtil.java Extends.java Super.java
+ * @run main Driver Extends
+ */
+
+public class Extends {
+
+  @ADescriptions({
+    @ADescription(annotation = "org/checkerframework/checker/nullness/qual/EnsuresNonNull")
+  })
+  public String m1() {
+    StringBuilder sb = new StringBuilder();
+    return TestWrapper.wrap("@Override void setf() { f = new Object(); }");
+  }
+
+  @ADescriptions({})
+  public String m2() {
+    return TestWrapper.wrap("@Override void setg() {}");
+  }
+
+  // Issue 342
+  // We do not want that behavior with related annotations. @Pure should override @SideEffectFree.
+  @ADescriptions({
+    @ADescription(annotation = "org/checkerframework/dataflow/qual/Pure"),
+    @ADescription(annotation = "org/checkerframework/dataflow/qual/SideEffectFree")
+  })
+  public String m3() {
+    return TestWrapper.wrap("@Pure @Override void seth() {}");
+  }
+}
+
+class TestWrapper {
+  public static String wrap(String... method) {
+    return "class Test extends Super {"
+        + System.lineSeparator()
+        + String.join(System.lineSeparator(), method)
+        + System.lineSeparator()
+        + "}";
+  }
+}
diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java
new file mode 100644
index 0000000..31f43b7
--- /dev/null
+++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java
@@ -0,0 +1,30 @@
+/*
+ * @test
+ * @summary Test that inherited declaration annotations are stored in bytecode.
+ *
+ * @compile ../PersistUtil.java Driver.java ReferenceInfoUtil.java Implements.java AbstractClass.java
+ * @run main Driver Implements
+ */
+
+public class Implements {
+
+  @ADescriptions({
+    @ADescription(annotation = "org/checkerframework/checker/nullness/qual/EnsuresNonNull")
+  })
+  public String m1() {
+    return TestWrapper.wrap(
+        "public Test() { f = new Object(); }",
+        "@Override public void setf() { f = new Object(); }",
+        "@Override public void setg() {}");
+  }
+}
+
+class TestWrapper {
+  public static String wrap(String... method) {
+    return String.join(
+        System.lineSeparator(),
+        "class Test extends AbstractClass {",
+        String.join(System.lineSeparator(), method),
+        "}");
+  }
+}
diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java
new file mode 100644
index 0000000..032fc57
--- /dev/null
+++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java
@@ -0,0 +1,129 @@
+// Keep somewhat in sync with
+// langtools/test/tools/javac/annotations/typeAnnotations/referenceinfos/ReferenceInfoUtil.java
+// Adapted to handled the same type qualifier appearing multiple times.
+
+import com.sun.tools.classfile.Annotation;
+import com.sun.tools.classfile.Attribute;
+import com.sun.tools.classfile.ClassFile;
+import com.sun.tools.classfile.ConstantPool.InvalidIndex;
+import com.sun.tools.classfile.ConstantPool.UnexpectedEntry;
+import com.sun.tools.classfile.Method;
+import com.sun.tools.classfile.RuntimeAnnotations_attribute;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+public class ReferenceInfoUtil {
+
+  public static final int IGNORE_VALUE = -321;
+
+  public static List<Annotation> extendedAnnotationsOf(ClassFile cf) {
+    List<Annotation> annos = new ArrayList<>();
+    findAnnotations(cf, annos);
+    return annos;
+  }
+
+  /////////////////// Extract annotations //////////////////
+  private static void findAnnotations(ClassFile cf, List<Annotation> annos) {
+    for (Method m : cf.methods) {
+      findAnnotations(cf, m, Attribute.RuntimeVisibleAnnotations, annos);
+    }
+  }
+
+  /**
+   * Test the result of Attributes.getIndex according to expectations encoded in the method's name.
+   */
+  private static void findAnnotations(ClassFile cf, Method m, String name, List<Annotation> annos) {
+    int index = m.attributes.getIndex(cf.constant_pool, name);
+    if (index != -1) {
+      Attribute attr = m.attributes.get(index);
+      assert attr instanceof RuntimeAnnotations_attribute;
+      RuntimeAnnotations_attribute tAttr = (RuntimeAnnotations_attribute) attr;
+      for (Annotation an : tAttr.annotations) {
+        if (!containsName(annos, an, cf)) {
+          annos.add(an);
+        }
+      }
+    }
+  }
+
+  private static Annotation findAnnotation(String name, List<Annotation> annotations, ClassFile cf)
+      throws InvalidIndex, UnexpectedEntry {
+    String properName = "L" + name + ";";
+    for (Annotation anno : annotations) {
+      String actualName = cf.constant_pool.getUTF8Value(anno.type_index);
+      if (properName.equals(actualName)) {
+        return anno;
+      }
+    }
+    return null;
+  }
+
+  public static boolean compare(
+      List<String> expectedAnnos, List<Annotation> actualAnnos, ClassFile cf)
+      throws InvalidIndex, UnexpectedEntry {
+    if (actualAnnos.size() != expectedAnnos.size()) {
+      throw new ComparisonException("Wrong number of annotations", expectedAnnos, actualAnnos, cf);
+    }
+    for (String annoName : expectedAnnos) {
+      Annotation anno = findAnnotation(annoName, actualAnnos, cf);
+      if (anno == null) {
+        throw new ComparisonException(
+            "Expected annotation not found: " + annoName, expectedAnnos, actualAnnos, cf);
+      }
+    }
+    return true;
+  }
+
+  private static boolean containsName(List<Annotation> annos, Annotation anno, ClassFile cf) {
+    try {
+      for (Annotation an : annos) {
+        if (cf.constant_pool
+            .getUTF8Value(an.type_index)
+            .equals(cf.constant_pool.getUTF8Value(anno.type_index))) {
+          return true;
+        }
+      }
+    } catch (Exception e) {
+      throw new RuntimeException();
+    }
+    return false;
+  }
+}
+
+class ComparisonException extends RuntimeException {
+  private static final long serialVersionUID = -3930499712333815821L;
+
+  public final List<String> expected;
+  public final List<Annotation> found;
+  public final ClassFile cf;
+
+  public ComparisonException(
+      String message, List<String> expected, List<Annotation> found, ClassFile cf) {
+    super(message);
+    this.expected = expected;
+    this.found = found;
+    this.cf = cf;
+  }
+
+  public String toString() {
+    StringJoiner foundString = new StringJoiner(",");
+    for (Annotation anno : found) {
+      try {
+        foundString.add(cf.constant_pool.getUTF8Value(anno.type_index));
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+    return String.join(
+        System.lineSeparator(),
+        super.toString(),
+        "\tExpected: "
+            + expected.size()
+            + " annotations; but found: "
+            + found.size()
+            + " annotations",
+        "  Expected: " + expected,
+        "  Found: " + foundString);
+  }
+}
diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Super.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Super.java
new file mode 100644
index 0000000..085cf32
--- /dev/null
+++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Super.java
@@ -0,0 +1,22 @@
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+
+public class Super {
+  Object f;
+  Object g;
+  Object h;
+
+  @EnsuresNonNull("f")
+  void setf() {
+    f = new Object();
+  }
+
+  void setg() {
+    g = null;
+  }
+
+  @SideEffectFree
+  void seth() {
+    h = null;
+  }
+}
diff --git a/checker/jtreg/nullness/issue12/BinaryDefaultTest.java b/checker/jtreg/nullness/issue12/BinaryDefaultTest.java
new file mode 100644
index 0000000..8323dc2
--- /dev/null
+++ b/checker/jtreg/nullness/issue12/BinaryDefaultTest.java
@@ -0,0 +1,19 @@
+/*
+ * @test
+ * @summary Test the defaulting mechanism for nullness in binary files.
+ *
+ * @ignore Temporarily, until safe defaults for unannotated libraries are the default
+ * @compile -XDrawDiagnostics -Xlint:unchecked BinaryDefaultTestBinary.java
+ * @compile/fail/ref=BinaryDefaultTest.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker BinaryDefaultTest.java
+ */
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class BinaryDefaultTest {
+  void test1(@NonNull BinaryDefaultTestInterface bar, @Nullable BinaryDefaultTestInterface bar2) {
+    @Nullable BinaryDefaultTestBinary foo = BinaryDefaultTestBinary.foo(bar);
+    @Nullable BinaryDefaultTestBinary baz = BinaryDefaultTestBinary.foo(bar2);
+    @NonNull BinaryDefaultTestBinary biz = BinaryDefaultTestBinary.foo(bar);
+  }
+}
diff --git a/checker/jtreg/nullness/issue12/BinaryDefaultTest.out b/checker/jtreg/nullness/issue12/BinaryDefaultTest.out
new file mode 100644
index 0000000..02fbf41
--- /dev/null
+++ b/checker/jtreg/nullness/issue12/BinaryDefaultTest.out
@@ -0,0 +1,7 @@
+BinaryDefaultTest.java:15:73: compiler.err.proc.messager: [argument] incompatible types in argument.
+found   : @Initialized @Nullable BinaryDefaultTestInterface
+required: @Initialized @NonNull BinaryDefaultTestInterface
+BinaryDefaultTest.java:16:71: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @Initialized @Nullable BinaryDefaultTestBinary
+required: @UnknownInitialization @NonNull BinaryDefaultTestBinary
+2 errors
diff --git a/checker/jtreg/nullness/issue12/BinaryDefaultTestBinary.java b/checker/jtreg/nullness/issue12/BinaryDefaultTestBinary.java
new file mode 100644
index 0000000..35b333a
--- /dev/null
+++ b/checker/jtreg/nullness/issue12/BinaryDefaultTestBinary.java
@@ -0,0 +1,5 @@
+public class BinaryDefaultTestBinary {
+  public static BinaryDefaultTestBinary foo(BinaryDefaultTestInterface foo) {
+    return new BinaryDefaultTestBinary();
+  }
+}
diff --git a/checker/jtreg/nullness/issue12/BinaryDefaultTestInterface.java b/checker/jtreg/nullness/issue12/BinaryDefaultTestInterface.java
new file mode 100644
index 0000000..18827eb
--- /dev/null
+++ b/checker/jtreg/nullness/issue12/BinaryDefaultTestInterface.java
@@ -0,0 +1 @@
+public interface BinaryDefaultTestInterface {}
diff --git a/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.java b/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.java
new file mode 100644
index 0000000..17e85a3
--- /dev/null
+++ b/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.java
@@ -0,0 +1,21 @@
+/*
+ * @test
+ * @summary Test the defaulting mechanism for nullness in binary files, but with stub files.
+ * Because source defaults are used in stub files, only the error for the invalid
+ * argument is expected.
+ *
+ * @ignore Temporarily, until safe defaults fon unannotated libraries are the default
+ * @compile -XDrawDiagnostics -Xlint:unchecked BinaryDefaultTestBinary.java
+ * @compile/fail/ref=BinaryDefaultTestWithStub.out -XDrawDiagnostics -Xlint:unchecked -Astubs=binary.astub -processor org.checkerframework.checker.nullness.NullnessChecker BinaryDefaultTest.java
+ */
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class BinaryDefaultTestWithStub {
+  void test1(@NonNull BinaryDefaultTestInterface bar, @Nullable BinaryDefaultTestInterface bar2) {
+    @Nullable BinaryDefaultTestBinary foo = BinaryDefaultTestBinary.foo(bar);
+    @Nullable BinaryDefaultTestBinary baz = BinaryDefaultTestBinary.foo(bar2);
+    @NonNull BinaryDefaultTestBinary biz = BinaryDefaultTestBinary.foo(bar);
+  }
+}
diff --git a/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.out b/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.out
new file mode 100644
index 0000000..8ee871f
--- /dev/null
+++ b/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.out
@@ -0,0 +1,4 @@
+BinaryDefaultTest.java:16:73: compiler.err.proc.messager: [argument] incompatible types in argument.
+found   : @Initialized @Nullable BinaryDefaultTestInterface
+required: @Initialized @NonNull BinaryDefaultTestInterface
+1 error
diff --git a/checker/jtreg/nullness/issue12/binary.astub b/checker/jtreg/nullness/issue12/binary.astub
new file mode 100644
index 0000000..3399426
--- /dev/null
+++ b/checker/jtreg/nullness/issue12/binary.astub
@@ -0,0 +1,3 @@
+public class BinaryDefaultTestBinary {
+  public static BinaryDefaultTestBinary foo(BinaryDefaultTestInterface foo);
+}
diff --git a/checker/jtreg/nullness/issue141/Appendable.java b/checker/jtreg/nullness/issue141/Appendable.java
new file mode 100644
index 0000000..0a84134
--- /dev/null
+++ b/checker/jtreg/nullness/issue141/Appendable.java
@@ -0,0 +1 @@
+interface Appendable {}
diff --git a/checker/jtreg/nullness/issue141/CharStreams.java b/checker/jtreg/nullness/issue141/CharStreams.java
new file mode 100644
index 0000000..b40120e
--- /dev/null
+++ b/checker/jtreg/nullness/issue141/CharStreams.java
@@ -0,0 +1,4 @@
+public class CharStreams {
+  static <R extends Readable & Closeable, W extends Appendable & Closeable> void copy(
+      InputSupplier<R> from, OutputSupplier<W> to) {}
+}
diff --git a/checker/jtreg/nullness/issue141/Closeable.java b/checker/jtreg/nullness/issue141/Closeable.java
new file mode 100644
index 0000000..ec469bd
--- /dev/null
+++ b/checker/jtreg/nullness/issue141/Closeable.java
@@ -0,0 +1 @@
+interface Closeable {}
diff --git a/checker/jtreg/nullness/issue141/Driver.java b/checker/jtreg/nullness/issue141/Driver.java
new file mode 100644
index 0000000..e1bf7ec
--- /dev/null
+++ b/checker/jtreg/nullness/issue141/Driver.java
@@ -0,0 +1,12 @@
+/*
+ * @test
+ * @summary Test for Issue 141
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker Appendable.java Driver.java InputSupplier.java Readable.java CharStreams.java Files.java OutputStreamWriter.java Closeable.java InputStreamReader.java OutputSupplier.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker Files.java
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.regex.RegexChecker Appendable.java Driver.java InputSupplier.java Readable.java CharStreams.java Files.java OutputStreamWriter.java Closeable.java InputStreamReader.java OutputSupplier.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.regex.RegexChecker Files.java
+ *
+ */
+public class Driver {}
diff --git a/checker/jtreg/nullness/issue141/Files.java b/checker/jtreg/nullness/issue141/Files.java
new file mode 100644
index 0000000..e3f6c9e
--- /dev/null
+++ b/checker/jtreg/nullness/issue141/Files.java
@@ -0,0 +1,13 @@
+abstract class Files {
+  public <R extends Readable & Closeable> void copy(InputSupplier<R> from) {
+    CharStreams.copy(from, newWriterSupplier());
+  }
+
+  public <W extends Appendable & Closeable> void copy(OutputSupplier<W> to) {
+    CharStreams.copy(newReaderSupplier(), to);
+  }
+
+  abstract OutputSupplier<OutputStreamWriter> newWriterSupplier();
+
+  abstract InputSupplier<InputStreamReader> newReaderSupplier();
+}
diff --git a/checker/jtreg/nullness/issue141/InputStreamReader.java b/checker/jtreg/nullness/issue141/InputStreamReader.java
new file mode 100644
index 0000000..14d86d7
--- /dev/null
+++ b/checker/jtreg/nullness/issue141/InputStreamReader.java
@@ -0,0 +1 @@
+interface InputStreamReader extends Closeable, Readable {}
diff --git a/checker/jtreg/nullness/issue141/InputSupplier.java b/checker/jtreg/nullness/issue141/InputSupplier.java
new file mode 100644
index 0000000..6bccbb7
--- /dev/null
+++ b/checker/jtreg/nullness/issue141/InputSupplier.java
@@ -0,0 +1 @@
+interface InputSupplier<R> {}
diff --git a/checker/jtreg/nullness/issue141/OutputStreamWriter.java b/checker/jtreg/nullness/issue141/OutputStreamWriter.java
new file mode 100644
index 0000000..c0baf4d
--- /dev/null
+++ b/checker/jtreg/nullness/issue141/OutputStreamWriter.java
@@ -0,0 +1 @@
+interface OutputStreamWriter extends Closeable, Appendable {}
diff --git a/checker/jtreg/nullness/issue141/OutputSupplier.java b/checker/jtreg/nullness/issue141/OutputSupplier.java
new file mode 100644
index 0000000..cde5454
--- /dev/null
+++ b/checker/jtreg/nullness/issue141/OutputSupplier.java
@@ -0,0 +1 @@
+interface OutputSupplier<W> {}
diff --git a/checker/jtreg/nullness/issue141/Readable.java b/checker/jtreg/nullness/issue141/Readable.java
new file mode 100644
index 0000000..a25e8b3
--- /dev/null
+++ b/checker/jtreg/nullness/issue141/Readable.java
@@ -0,0 +1 @@
+interface Readable {}
diff --git a/checker/jtreg/nullness/issue1582/Foo.out b/checker/jtreg/nullness/issue1582/Foo.out
new file mode 100644
index 0000000..a28aa9c
--- /dev/null
+++ b/checker/jtreg/nullness/issue1582/Foo.out
@@ -0,0 +1,2 @@
+Foo.java:15:18: compiler.err.proc.messager: (flowexpr.parse.error)
+1 error
diff --git a/checker/jtreg/nullness/issue1582/JavaExpressionParseError.out b/checker/jtreg/nullness/issue1582/JavaExpressionParseError.out
new file mode 100644
index 0000000..32abfa8
--- /dev/null
+++ b/checker/jtreg/nullness/issue1582/JavaExpressionParseError.out
@@ -0,0 +1,2 @@
+JavaExpressionParseError.java:8:25: compiler.err.proc.messager: (flowexpr.parse.error.postcondition)
+1 error
diff --git a/checker/jtreg/nullness/issue1582/Main.java b/checker/jtreg/nullness/issue1582/Main.java
new file mode 100644
index 0000000..65e38f6
--- /dev/null
+++ b/checker/jtreg/nullness/issue1582/Main.java
@@ -0,0 +1,14 @@
+/*
+ * @test
+ * @summary
+ * Test case for Issue 1582
+ * https://github.com/typetools/checker-framework/issues/1582
+ *
+ * @compile/fail/ref=Foo.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext foo/Foo.java
+ * @compile -XDrawDiagnostics foo/Foo.java
+ * @compile/fail/ref=JavaExpressionParseError.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext mainrepropkg/JavaExpressionParseError.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext mainrepropkg/JavaExpressionParseError.java -AsuppressWarnings=flowexpr.parse.error.postcondition
+ *
+ */
+
+public class Main {}
diff --git a/checker/jtreg/nullness/issue1582/foo/Foo.java b/checker/jtreg/nullness/issue1582/foo/Foo.java
new file mode 100644
index 0000000..ef57f45
--- /dev/null
+++ b/checker/jtreg/nullness/issue1582/foo/Foo.java
@@ -0,0 +1,23 @@
+package foo;
+
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class Foo {
+
+  Foo(@Nullable Object theObject) {}
+
+  @SuppressWarnings("contracts.conditional.postcondition")
+  @EnsuresNonNullIf(
+      expression = {"theObject", "getTheObject()"},
+      result = true)
+  public boolean hasTheObject() {
+    return false;
+  }
+
+  @Pure
+  public @Nullable Object getTheObject() {
+    return null;
+  }
+}
diff --git a/checker/jtreg/nullness/issue1582/mainrepropkg/JavaExpressionParseError.java b/checker/jtreg/nullness/issue1582/mainrepropkg/JavaExpressionParseError.java
new file mode 100644
index 0000000..f8641bb
--- /dev/null
+++ b/checker/jtreg/nullness/issue1582/mainrepropkg/JavaExpressionParseError.java
@@ -0,0 +1,12 @@
+package mainrepropkg;
+
+import foo.Foo;
+
+public class JavaExpressionParseError {
+
+  public void printAThing(Foo foo) {
+    if (foo.hasTheObject()) {
+      System.out.println("Print false: " + foo.getTheObject().equals(new Object()));
+    }
+  }
+}
diff --git a/checker/jtreg/nullness/issue1929/Issue1929-notrust.out b/checker/jtreg/nullness/issue1929/Issue1929-notrust.out
new file mode 100644
index 0000000..11a6913
--- /dev/null
+++ b/checker/jtreg/nullness/issue1929/Issue1929-notrust.out
@@ -0,0 +1,10 @@
+Issue1929.java:21:21: compiler.err.proc.messager: [return] incompatible types in return.
+type of expression: @Initialized @Nullable String @Initialized @NonNull []
+method return type: @Initialized @NonNull String @Initialized @NonNull []
+Issue1929.java:21:21: compiler.warn.proc.messager: [toarray.nullable.elements.not.newarray] call of toArray on collection of non-null elements yields an array of possibly-null elements; omit the argument to toArray or make it an explicit array constructor
+Issue1929.java:29:21: compiler.err.proc.messager: [return] incompatible types in return.
+type of expression: @Initialized @Nullable String @Initialized @NonNull []
+method return type: @Initialized @NonNull String @Initialized @NonNull []
+Issue1929.java:29:21: compiler.warn.proc.messager: [toarray.nullable.elements.not.newarray] call of toArray on collection of non-null elements yields an array of possibly-null elements; omit the argument to toArray or make it an explicit array constructor
+2 errors
+2 warnings
diff --git a/checker/jtreg/nullness/issue1929/Issue1929-trust.out b/checker/jtreg/nullness/issue1929/Issue1929-trust.out
new file mode 100644
index 0000000..43d1436
--- /dev/null
+++ b/checker/jtreg/nullness/issue1929/Issue1929-trust.out
@@ -0,0 +1,6 @@
+Issue1929.java:29:21: compiler.err.proc.messager: [return] incompatible types in return.
+type of expression: @Initialized @Nullable String @Initialized @NonNull []
+method return type: @Initialized @NonNull String @Initialized @NonNull []
+Issue1929.java:29:21: compiler.warn.proc.messager: [toarray.nullable.elements.not.newarray] call of toArray on collection of non-null elements yields an array of possibly-null elements; omit the argument to toArray or make it an explicit array constructor
+1 error
+1 warning
diff --git a/checker/jtreg/nullness/issue1929/Issue1929.java b/checker/jtreg/nullness/issue1929/Issue1929.java
new file mode 100644
index 0000000..db65d89
--- /dev/null
+++ b/checker/jtreg/nullness/issue1929/Issue1929.java
@@ -0,0 +1,31 @@
+/*
+ * @test
+ * @summary Test case for Issue 1929: test -Alint=trustArrayLenZero
+ *
+ * @compile/fail/ref=Issue1929-notrust.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker Issue1929.java
+ * @compile/fail/ref=Issue1929-trust.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Alint=trustArrayLenZero Issue1929.java
+ */
+
+import java.util.Collection;
+import org.checkerframework.common.value.qual.ArrayLen;
+
+public class Issue1929 {
+
+  String[] works1(Collection<String> c) {
+    return c.toArray(new String[0]);
+  }
+
+  private static final String @ArrayLen(0) [] EMPTY_STRING_ARRAY_2 = new String[0];
+
+  String[] fails2(Collection<String> c) {
+    return c.toArray(EMPTY_STRING_ARRAY_2);
+  }
+
+  private static final String[] EMPTY_STRING_ARRAY_3 = new String[0];
+
+  String[] fails3(Collection<String> c) {
+    // We don't determine field types from initialization expressions.
+    // :: error: (return)
+    return c.toArray(EMPTY_STRING_ARRAY_3);
+  }
+}
diff --git a/checker/jtreg/nullness/issue1958/NPE2Test.java b/checker/jtreg/nullness/issue1958/NPE2Test.java
new file mode 100644
index 0000000..8d29279
--- /dev/null
+++ b/checker/jtreg/nullness/issue1958/NPE2Test.java
@@ -0,0 +1,19 @@
+public class NPE2Test {
+  public void testNPE() {
+    SupplierDefs.Supplier<String> s = new SupplierDefs.NullSupplier();
+    boolean b = s.get().equals("");
+  }
+
+  public void testNPE2() {
+    SupplierDefs.MyInterface<String> s = new SupplierDefs.NullInterface();
+    boolean b = s.getT().equals("");
+  }
+
+  public void testNPE3() {
+    SupplierDefs.MyInterface<String> s = new SupplierDefs.NullSupplierMyInterface();
+    boolean b = s.getT().equals("");
+
+    SupplierDefs.Supplier<String> s2 = new SupplierDefs.NullSupplierMyInterface();
+    boolean b2 = s2.get().equals("");
+  }
+}
diff --git a/checker/jtreg/nullness/issue1958/NPE2Test.out b/checker/jtreg/nullness/issue1958/NPE2Test.out
new file mode 100644
index 0000000..d047b65
--- /dev/null
+++ b/checker/jtreg/nullness/issue1958/NPE2Test.out
@@ -0,0 +1,5 @@
+NPE2Test.java:3:39: compiler.err.proc.messager: (assignment)
+NPE2Test.java:8:42: compiler.err.proc.messager: (assignment)
+NPE2Test.java:13:42: compiler.err.proc.messager: (assignment)
+NPE2Test.java:16:40: compiler.err.proc.messager: (assignment)
+4 errors
diff --git a/checker/jtreg/nullness/issue1958/SupplierDefs.java b/checker/jtreg/nullness/issue1958/SupplierDefs.java
new file mode 100644
index 0000000..9a8f054
--- /dev/null
+++ b/checker/jtreg/nullness/issue1958/SupplierDefs.java
@@ -0,0 +1,47 @@
+/*
+ * @test
+ * @summary Test annotations on type parameters in extends.
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker SupplierDefs.java
+ * @compile/fail/ref=NPE2Test.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker NPE2Test.java -Anomsgtext
+ */
+
+import java.util.function.Supplier;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class SupplierDefs {
+  public abstract static class Supplier<R> {
+    public abstract R get();
+  }
+
+  public static class NullSupplier extends Supplier<@Nullable String> {
+    @Override
+    public @Nullable String get() {
+      return null;
+    }
+  }
+
+  public static class NullInterface implements MyInterface<@Nullable String> {
+    @Override
+    public @Nullable String getT() {
+      return null;
+    }
+  }
+
+  public static class NullSupplierMyInterface extends Supplier<@Nullable String>
+      implements MyInterface<@Nullable String> {
+    @Override
+    public @Nullable String get() {
+      return null;
+    }
+
+    @Override
+    public @Nullable String getT() {
+      return null;
+    }
+  }
+
+  public interface MyInterface<T> {
+    T getT();
+  }
+}
diff --git a/checker/jtreg/nullness/issue2173/Importer.class b/checker/jtreg/nullness/issue2173/Importer.class
new file mode 100644
index 0000000..3582413
--- /dev/null
+++ b/checker/jtreg/nullness/issue2173/Importer.class
Binary files differ
diff --git a/checker/jtreg/nullness/issue2173/ImporterManager.class b/checker/jtreg/nullness/issue2173/ImporterManager.class
new file mode 100644
index 0000000..62d3f27
--- /dev/null
+++ b/checker/jtreg/nullness/issue2173/ImporterManager.class
Binary files differ
diff --git a/checker/jtreg/nullness/issue2173/ImporterManager.java.tmp b/checker/jtreg/nullness/issue2173/ImporterManager.java.tmp
new file mode 100644
index 0000000..280831b
--- /dev/null
+++ b/checker/jtreg/nullness/issue2173/ImporterManager.java.tmp
@@ -0,0 +1,27 @@
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import org.checkerframework.checker.i18n.qual.Localized;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.common.subtyping.qual.Bottom;
+
+interface Importer {
+    List<Pair<@Localized String, List<String>>> getSupportedFileTypes();
+}
+
+interface Pair<A, B> {
+    B getSecond();
+}
+
+public class ImporterManager {
+    private static final List<Importer> registeredImporters = new ArrayList<>();
+
+    public static void chooseAndImportFile(Object parent) {
+        Function<@NonNull ?, @NonNull ?> x =
+                (@NonNull Importer imp) ->
+                        imp.getSupportedFileTypes().stream()
+                                .flatMap(
+                                        (@Bottom Pair<@Localized String, List<String>> p) ->
+                                                p.getSecond().stream());
+    }
+}
diff --git a/checker/jtreg/nullness/issue2173/Pair.class b/checker/jtreg/nullness/issue2173/Pair.class
new file mode 100644
index 0000000..83dfc0c
--- /dev/null
+++ b/checker/jtreg/nullness/issue2173/Pair.class
Binary files differ
diff --git a/checker/jtreg/nullness/issue2173/README b/checker/jtreg/nullness/issue2173/README
new file mode 100644
index 0000000..82e80a5
--- /dev/null
+++ b/checker/jtreg/nullness/issue2173/README
@@ -0,0 +1,7 @@
+Test case for issue #2173: https://github.com/typetools/checker-framework/issues/2173
+
+ImporterManager.class was compiled using a Java 8 compiler.  The compiler copied
+the annotation from a lambda to a method parameter type argument, but the
+parameter does not have a type argument.  This bug has been fixed in Java 9,
+but bytecode generated by Java 8 can still be read by a checker, so this tests
+that it won't crash a checker.
diff --git a/checker/jtreg/nullness/issue2173/View.java b/checker/jtreg/nullness/issue2173/View.java
new file mode 100644
index 0000000..f46ea99
--- /dev/null
+++ b/checker/jtreg/nullness/issue2173/View.java
@@ -0,0 +1,14 @@
+/*
+ * @test
+ * @summary Test case for issue #2173: https://github.com/typetools/checker-framework/issues/2173
+ * See README in this directory.
+ *
+ * @compile/ref=View.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker View.java -Anomsgtext
+ * @compile -processor org.checkerframework.checker.nullness.NullnessChecker View.java -AignoreInvalidAnnotationLocations -Werror
+ */
+
+public class View {
+  private static void createTable() {
+    ImporterManager.chooseAndImportFile("");
+  }
+}
diff --git a/checker/jtreg/nullness/issue2173/View.out b/checker/jtreg/nullness/issue2173/View.out
new file mode 100644
index 0000000..7edd091
--- /dev/null
+++ b/checker/jtreg/nullness/issue2173/View.out
@@ -0,0 +1,5 @@
+- compiler.warn.proc.messager: (invalid.annotation.location.bytecode)
+- compiler.warn.proc.messager: (invalid.annotation.location.bytecode)
+- compiler.warn.proc.messager: (invalid.annotation.location.bytecode)
+- compiler.warn.proc.messager: (invalid.annotation.location.bytecode)
+4 warnings
diff --git a/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.1.out b/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.1.out
new file mode 100644
index 0000000..03a0595
--- /dev/null
+++ b/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.1.out
@@ -0,0 +1,10 @@
+RequireCheckerPrefix.java:19:25: compiler.err.proc.messager: [nullness:assignment] incompatible types in assignment.
+found   : @Initialized @Nullable Object
+required: @UnknownInitialization @NonNull Object
+RequireCheckerPrefix.java:24:25: compiler.err.proc.messager: [nullness:assignment] incompatible types in assignment.
+found   : @Initialized @Nullable Object
+required: @UnknownInitialization @NonNull Object
+RequireCheckerPrefix.java:27:25: compiler.err.proc.messager: [nullness:assignment] incompatible types in assignment.
+found   : @Initialized @Nullable Object
+required: @UnknownInitialization @NonNull Object
+3 errors
diff --git a/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.2.out b/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.2.out
new file mode 100644
index 0000000..f72ddfe
--- /dev/null
+++ b/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.2.out
@@ -0,0 +1,4 @@
+RequireCheckerPrefix.java:19:25: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @Initialized @Nullable Object
+required: @UnknownInitialization @NonNull Object
+1 error
diff --git a/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.java b/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.java
new file mode 100644
index 0000000..3ba9260
--- /dev/null
+++ b/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.java
@@ -0,0 +1,31 @@
+/*
+ * @test
+ * @summary Test -ArequirePrefixInWarningSuppressions
+ *
+ * @compile/fail/ref=RequireCheckerPrefix.1.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -ArequirePrefixInWarningSuppressions RequireCheckerPrefix.java
+ * @compile/fail/ref=RequireCheckerPrefix.2.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker RequireCheckerPrefix.java
+ */
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class RequireCheckerPrefix {
+
+  void method(@Nullable Object o) {
+    @SuppressWarnings("nullness:assignment")
+    @NonNull Object s = o;
+    // "all" is not a valid prefix, so the warning is never suppressed.
+    @SuppressWarnings("all:assignment")
+    @NonNull Object t = o;
+    @SuppressWarnings("allcheckers:assignment")
+    @NonNull Object u = o;
+
+    @SuppressWarnings("assignment")
+    @NonNull Object p = o;
+    // Suppresses the warning if -ArequirePrefixInWarningSuppressions isn't used.
+    @SuppressWarnings("all")
+    @NonNull Object q = o;
+    @SuppressWarnings("allcheckers")
+    @NonNull Object w = o;
+  }
+}
diff --git a/checker/jtreg/nullness/issue257/ClientBuilder.java b/checker/jtreg/nullness/issue257/ClientBuilder.java
new file mode 100644
index 0000000..f42da3f
--- /dev/null
+++ b/checker/jtreg/nullness/issue257/ClientBuilder.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class ClientBuilder<T extends @NonNull ClientBuilder<T>> {
+
+  static @NonNull ClientBuilder<?> newBuilder() {
+    return new BuilderImpl();
+  }
+
+  // Dummy class to get the recursive Builder typing right.
+  static class BuilderImpl extends ClientBuilder<BuilderImpl> {}
+
+  T setThing() {
+    return (T) this;
+  }
+}
diff --git a/checker/jtreg/nullness/issue257/Module.java b/checker/jtreg/nullness/issue257/Module.java
new file mode 100644
index 0000000..785dacc
--- /dev/null
+++ b/checker/jtreg/nullness/issue257/Module.java
@@ -0,0 +1,20 @@
+/*
+ * @test
+ * @summary Test for Issue 257
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker Small.java
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker ClientBuilder.java Module.java
+ *
+ * @compile -XDrawDiagnostics ClientBuilder.java
+ */
+public class Module {
+  void buildClient() {
+    ClientBuilder<?> builder = ClientBuilder.newBuilder().setThing().setThing();
+  }
+
+  void smaller() {
+    ClientBuilder<? extends ClientBuilder<? extends ClientBuilder<? extends ClientBuilder<?>>>>
+        builder = ClientBuilder.newBuilder();
+  }
+}
diff --git a/checker/jtreg/nullness/issue257/Small.java b/checker/jtreg/nullness/issue257/Small.java
new file mode 100644
index 0000000..6209a3e
--- /dev/null
+++ b/checker/jtreg/nullness/issue257/Small.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class Gen<T extends Gen<T>> {
+
+  static @Nullable Gen<?> newBuilder() {
+    return null;
+  }
+}
+
+public class Small {
+  void buildGen() {
+    Gen<? extends Gen<?>> builder = Gen.newBuilder();
+  }
+}
diff --git a/checker/jtreg/nullness/issue3700/Client.class b/checker/jtreg/nullness/issue3700/Client.class
new file mode 100644
index 0000000..365b7a2
--- /dev/null
+++ b/checker/jtreg/nullness/issue3700/Client.class
Binary files differ
diff --git a/checker/jtreg/nullness/issue3700/Client.java b/checker/jtreg/nullness/issue3700/Client.java
new file mode 100644
index 0000000..e9657c7
--- /dev/null
+++ b/checker/jtreg/nullness/issue3700/Client.java
@@ -0,0 +1,4 @@
+public class Client {
+
+  TimeUnitRange t = TimeUnitRange.YEAR;
+}
diff --git a/checker/jtreg/nullness/issue3700/Client.out b/checker/jtreg/nullness/issue3700/Client.out
new file mode 100644
index 0000000..f0d8acb
--- /dev/null
+++ b/checker/jtreg/nullness/issue3700/Client.out
@@ -0,0 +1,2 @@
+warning: TimeUnitRange.astub:(line 3,col 1): TimeUnitRange is an enum, but stub file declared it as class TimeUnitRange {...
+1 warning
diff --git a/checker/jtreg/nullness/issue3700/TimeUnitRange.astub b/checker/jtreg/nullness/issue3700/TimeUnitRange.astub
new file mode 100644
index 0000000..293b488
--- /dev/null
+++ b/checker/jtreg/nullness/issue3700/TimeUnitRange.astub
@@ -0,0 +1,5 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+class TimeUnitRange {
+   public TimeUnitRange of(@Nullable String endUnit);
+}
diff --git a/checker/jtreg/nullness/issue3700/TimeUnitRange.class b/checker/jtreg/nullness/issue3700/TimeUnitRange.class
new file mode 100644
index 0000000..82c3a39
--- /dev/null
+++ b/checker/jtreg/nullness/issue3700/TimeUnitRange.class
Binary files differ
diff --git a/checker/jtreg/nullness/issue3700/TimeUnitRange.java b/checker/jtreg/nullness/issue3700/TimeUnitRange.java
new file mode 100644
index 0000000..c11ced4
--- /dev/null
+++ b/checker/jtreg/nullness/issue3700/TimeUnitRange.java
@@ -0,0 +1,16 @@
+/*
+ * @test
+ * @summary Test case for Issue #3700 https://github.com/typetools/checker-framework/issues/3700
+ * @compile -XDrawDiagnostics -Xlint:unchecked TimeUnitRange.java
+ * @compile/ref=Client.out -processor org.checkerframework.checker.nullness.NullnessChecker Client.java -Astubs=TimeUnitRange.astub -implicit:none -Anomsgtext
+ */
+
+public enum TimeUnitRange {
+  YEAR,
+  YEAR_TO_MONTH,
+  MONTH;
+
+  public static TimeUnitRange of(Object endUnit) {
+    throw new Error("body is irrelevant");
+  }
+}
diff --git a/checker/jtreg/nullness/issue380/DA.java b/checker/jtreg/nullness/issue380/DA.java
new file mode 100644
index 0000000..553eb49
--- /dev/null
+++ b/checker/jtreg/nullness/issue380/DA.java
@@ -0,0 +1,4 @@
+public class DA {
+  @Decl(flag = true)
+  void foo() {}
+}
diff --git a/checker/jtreg/nullness/issue380/DB.java b/checker/jtreg/nullness/issue380/DB.java
new file mode 100644
index 0000000..b998112
--- /dev/null
+++ b/checker/jtreg/nullness/issue380/DB.java
@@ -0,0 +1,3 @@
+public class DB extends DA {
+  void foo() {}
+}
diff --git a/checker/jtreg/nullness/issue380/Decl.java b/checker/jtreg/nullness/issue380/Decl.java
new file mode 100644
index 0000000..d262028
--- /dev/null
+++ b/checker/jtreg/nullness/issue380/Decl.java
@@ -0,0 +1,6 @@
+import org.checkerframework.framework.qual.InheritedAnnotation;
+
+@InheritedAnnotation
+public @interface Decl {
+  boolean flag();
+}
diff --git a/checker/jtreg/nullness/issue380/Driver.java b/checker/jtreg/nullness/issue380/Driver.java
new file mode 100644
index 0000000..7cb884d
--- /dev/null
+++ b/checker/jtreg/nullness/issue380/Driver.java
@@ -0,0 +1,10 @@
+/*
+ * @test
+ * @summary Test for Issue 141
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker Decl.java DA.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker DB.java
+ *
+ *
+ */
+public class Driver {}
diff --git a/checker/jtreg/nullness/issue767/Class1.java b/checker/jtreg/nullness/issue767/Class1.java
new file mode 100644
index 0000000..fc2c20b
--- /dev/null
+++ b/checker/jtreg/nullness/issue767/Class1.java
@@ -0,0 +1,36 @@
+/*
+ * @test
+ * @summary Test that compliation order doesn't effect typechecking (#767)
+ *
+ * @compile -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Class1.java Class2.java
+ * @compile -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Class2.java Class1.java
+ *
+ */
+
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Class1 {
+  public static @Nullable Object field = null;
+  public @Nullable Object instanceField = null;
+
+  @EnsuresNonNull("instanceField")
+  public void instanceMethod() {
+    instanceField = new Object();
+  }
+
+  @EnsuresNonNull("Class1.field")
+  public static void method() {
+    field = new Object();
+  }
+
+  @EnsuresNonNull("Class2.field")
+  public static void method2() {
+    Class2.field = new Object();
+  }
+
+  @EnsuresNonNull("#1.instanceField")
+  public static void method3(Class2 class2) {
+    class2.instanceField = new Object();
+  }
+}
diff --git a/checker/jtreg/nullness/issue767/Class2.java b/checker/jtreg/nullness/issue767/Class2.java
new file mode 100644
index 0000000..5d508a9
--- /dev/null
+++ b/checker/jtreg/nullness/issue767/Class2.java
@@ -0,0 +1,22 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Class2 {
+  public static @Nullable Object field;
+  public @Nullable Object instanceField;
+
+  public static void method(Class1 class1) {
+    Class1.method();
+    @NonNull Object o = Class1.field;
+    Class1.method2();
+    @NonNull Object o2 = field;
+
+    class1.instanceMethod();
+    @NonNull Object o3 = class1.instanceField;
+  }
+
+  void test() {
+    Class1.method3(this);
+    @NonNull Object o = instanceField;
+  }
+}
diff --git a/checker/jtreg/nullness/issue790/Class1.java b/checker/jtreg/nullness/issue790/Class1.java
new file mode 100644
index 0000000..9ff0193
--- /dev/null
+++ b/checker/jtreg/nullness/issue790/Class1.java
@@ -0,0 +1,14 @@
+/*
+ * @test
+ * @summary Test for Issue #790
+ *
+ * @compile/fail/ref=expected12.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Class1.java Class2.java
+ *
+ * @compile/fail/ref=expected21.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Class2.java Class1.java
+ *
+ */
+public class Class1 {
+  Class1() {
+    super(this);
+  }
+}
diff --git a/checker/jtreg/nullness/issue790/Class2.java b/checker/jtreg/nullness/issue790/Class2.java
new file mode 100644
index 0000000..a96bbe9
--- /dev/null
+++ b/checker/jtreg/nullness/issue790/Class2.java
@@ -0,0 +1 @@
+public class Class2 extends Class1 {}
diff --git a/checker/jtreg/nullness/issue790/expected12.out b/checker/jtreg/nullness/issue790/expected12.out
new file mode 100644
index 0000000..0e4d086
--- /dev/null
+++ b/checker/jtreg/nullness/issue790/expected12.out
@@ -0,0 +1,5 @@
+Class1.java:12:11: compiler.err.cant.ref.before.ctor.called: this
+Class1.java:12:5: compiler.err.cant.apply.symbol: kindname.constructor, Object, compiler.misc.no.args, Class1, kindname.class, java.lang.Object, (compiler.misc.arg.length.mismatch)
+Class1.java:10:8: compiler.err.proc.messager: (type.checking.not.run)
+Class2.java:1:8: compiler.err.proc.messager: (type.checking.not.run)
+4 errors
diff --git a/checker/jtreg/nullness/issue790/expected21.out b/checker/jtreg/nullness/issue790/expected21.out
new file mode 100644
index 0000000..359013d
--- /dev/null
+++ b/checker/jtreg/nullness/issue790/expected21.out
@@ -0,0 +1,5 @@
+Class1.java:12:11: compiler.err.cant.ref.before.ctor.called: this
+Class1.java:12:5: compiler.err.cant.apply.symbol: kindname.constructor, Object, compiler.misc.no.args, Class1, kindname.class, java.lang.Object, (compiler.misc.arg.length.mismatch)
+Class2.java:1:8: compiler.err.proc.messager: (type.checking.not.run)
+Class1.java:10:8: compiler.err.proc.messager: (type.checking.not.run)
+4 errors
diff --git a/checker/jtreg/nullness/issue820/AnonymousClass.java b/checker/jtreg/nullness/issue820/AnonymousClass.java
new file mode 100644
index 0000000..b20c622
--- /dev/null
+++ b/checker/jtreg/nullness/issue820/AnonymousClass.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class AnonymousClass {
+  @NonNull Object error = null;
+
+  void method() {
+    Object effectivelyFinalLocal = new Object();
+    Object a =
+        new Object() {
+          void foo() {
+            @NonNull Object nonNull = effectivelyFinalLocal;
+          }
+        };
+  }
+}
diff --git a/checker/jtreg/nullness/issue820/AnonymousClass.out b/checker/jtreg/nullness/issue820/AnonymousClass.out
new file mode 100644
index 0000000..e329aef
--- /dev/null
+++ b/checker/jtreg/nullness/issue820/AnonymousClass.out
@@ -0,0 +1,2 @@
+AnonymousClass.java:4:27: compiler.err.proc.messager: (assignment)
+1 error
diff --git a/checker/jtreg/nullness/issue820/Class1.java b/checker/jtreg/nullness/issue820/Class1.java
new file mode 100644
index 0000000..4634344
--- /dev/null
+++ b/checker/jtreg/nullness/issue820/Class1.java
@@ -0,0 +1,38 @@
+/*
+ * @test
+ * @summary Test case for Issue 820 https://github.com/typetools/checker-framework/issues/820
+ *
+ * @compile/fail/ref=Class1Class2-err.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Class1.java Class2.java
+ * @compile/fail/ref=Class2Class1-err.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Class2.java Class1.java
+ *
+ */
+
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public class Class1 {
+  public static @Nullable Object field = null;
+  public @Nullable Object instanceField = null;
+
+  @EnsuresNonNull("#1.instanceField")
+  public static void method3(Class2 class2) {
+    class2.instanceField = new Object();
+  }
+
+  @EnsuresNonNull("#1.instanceField")
+  public static void method4(Class2 class2) {
+    class2.instanceField = new Object();
+  }
+
+  @EnsuresNonNull("#1.instanceField")
+  public static void method5(Class2 class2) {}
+
+  @EnsuresNonNull("#1")
+  public static void method6(Class2 class2) {}
+
+  @RequiresNonNull("#1.instanceField")
+  public static void method3R(Class2 class2) {
+    class2.instanceField.toString();
+  }
+}
diff --git a/checker/jtreg/nullness/issue820/Class1Class2-err.out b/checker/jtreg/nullness/issue820/Class1Class2-err.out
new file mode 100644
index 0000000..6c88780
--- /dev/null
+++ b/checker/jtreg/nullness/issue820/Class1Class2-err.out
@@ -0,0 +1,3 @@
+Class1.java:29:22: compiler.err.proc.messager: (contracts.postcondition)
+Class2.java:15:20: compiler.err.proc.messager: (contracts.precondition)
+2 errors
diff --git a/checker/jtreg/nullness/issue820/Class1Min.java b/checker/jtreg/nullness/issue820/Class1Min.java
new file mode 100644
index 0000000..4e89973
--- /dev/null
+++ b/checker/jtreg/nullness/issue820/Class1Min.java
@@ -0,0 +1,15 @@
+/*
+ * @test
+ * @summary Test case for Issue 820 https://github.com/typetools/checker-framework/issues/820
+ *
+ * @compile/fail/ref=Class1MinClass2Min-err.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Class1Min.java Class2Min.java
+ * @compile/fail/ref=Class1MinClass2Min-err.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Class2Min.java Class1Min.java
+ *
+ */
+
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+
+public class Class1Min {
+  @EnsuresNonNull("#1")
+  public void methodInstance(Class2Min class2) {}
+}
diff --git a/checker/jtreg/nullness/issue820/Class1MinClass2Min-err.out b/checker/jtreg/nullness/issue820/Class1MinClass2Min-err.out
new file mode 100644
index 0000000..bd23cfc
--- /dev/null
+++ b/checker/jtreg/nullness/issue820/Class1MinClass2Min-err.out
@@ -0,0 +1,2 @@
+Class2Min.java:6:25: compiler.err.proc.messager: (assignment)
+1 error
diff --git a/checker/jtreg/nullness/issue820/Class2.java b/checker/jtreg/nullness/issue820/Class2.java
new file mode 100644
index 0000000..a7b28dc
--- /dev/null
+++ b/checker/jtreg/nullness/issue820/Class2.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Class2 {
+  public static @Nullable Object field;
+  public @Nullable Object instanceField;
+
+  void test() {
+    Class1.method3(this);
+    @NonNull Object o = instanceField;
+  }
+
+  void test2() {
+    //        instanceField = new Object(); Can't reproduce with assignment
+    Class1.method3R(this);
+  }
+}
diff --git a/checker/jtreg/nullness/issue820/Class2Class1-err.out b/checker/jtreg/nullness/issue820/Class2Class1-err.out
new file mode 100644
index 0000000..7721249
--- /dev/null
+++ b/checker/jtreg/nullness/issue820/Class2Class1-err.out
@@ -0,0 +1,3 @@
+Class2.java:15:20: compiler.err.proc.messager: (contracts.precondition)
+Class1.java:29:22: compiler.err.proc.messager: (contracts.postcondition)
+2 errors
diff --git a/checker/jtreg/nullness/issue820/Class2Min.java b/checker/jtreg/nullness/issue820/Class2Min.java
new file mode 100644
index 0000000..086fc7c
--- /dev/null
+++ b/checker/jtreg/nullness/issue820/Class2Min.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class Class2Min {
+  void test(Class1Min class1) {
+    // Any error must be issued, not suppressed, for this to reproduce
+    @NonNull Object o = null;
+    class1.methodInstance(this);
+  }
+}
diff --git a/checker/jtreg/nullness/issue820/Error.java b/checker/jtreg/nullness/issue820/Error.java
new file mode 100644
index 0000000..c3c97aa
--- /dev/null
+++ b/checker/jtreg/nullness/issue820/Error.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+/*
+ * @test
+ * @summary Test case for Issue 820 https://github.com/typetools/checker-framework/issues/820
+ *
+ * @compile/fail/ref=ErrorAnonymousClass.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Error.java AnonymousClass.java
+ * @compile/fail/ref=AnonymousClass.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext AnonymousClass.java
+ *
+ */
+
+public class Error {
+  @NonNull Object o = null;
+}
diff --git a/checker/jtreg/nullness/issue820/ErrorAnonymousClass.out b/checker/jtreg/nullness/issue820/ErrorAnonymousClass.out
new file mode 100644
index 0000000..d38d36f
--- /dev/null
+++ b/checker/jtreg/nullness/issue820/ErrorAnonymousClass.out
@@ -0,0 +1,3 @@
+Error.java:13:23: compiler.err.proc.messager: (assignment)
+AnonymousClass.java:4:27: compiler.err.proc.messager: (assignment)
+2 errors
diff --git a/checker/jtreg/nullness/issue824/Class1.astub b/checker/jtreg/nullness/issue824/Class1.astub
new file mode 100644
index 0000000..58b8989
--- /dev/null
+++ b/checker/jtreg/nullness/issue824/Class1.astub
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+ class Class1<Q extends @NonNull Object> {
+    public <T extends @NonNull Object> T methodTypeParam(T t);
+    public void classTypeParam(Q e);
+
+    public <F extends @NonNull Object> void wildcardExtends(Gen<? extends F> class1);
+    public <F extends @NonNull Object> void wildcardSuper(Gen<@NonNull ? super F> class1);
+
+    class Gen<F extends @NonNull Object> {}
+}
diff --git a/checker/jtreg/nullness/issue824/Class2.java b/checker/jtreg/nullness/issue824/Class2.java
new file mode 100644
index 0000000..7f18124
--- /dev/null
+++ b/checker/jtreg/nullness/issue824/Class2.java
@@ -0,0 +1,27 @@
+/*
+ * @test
+ * @summary Test case for Issue 824 https://github.com/typetools/checker-framework/issues/824
+ * The defaults for type variable upper bounds with type Object changed since
+ * the issue was filed.  So, this test case has been changed so that
+ * annotations on type variable bounds in stub files is still tested.
+ * @compile -XDrawDiagnostics -Xlint:unchecked ../issue824lib/Class1.java
+ * @compile/fail/ref=Class2.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Class2.java -Astubs=Class1.astub -AstubWarnIfNotFound
+ * @compile -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Class2.java
+ */
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Class2<X> extends Class1<X> {
+  void call(Class1<@Nullable X> class1, Gen<@Nullable X> gen) {
+    class1.methodTypeParam(null);
+    class1.classTypeParam(null);
+
+    class1.wildcardExtends(gen);
+    class1.wildcardSuper(gen);
+  }
+
+  @Override
+  public <T> T methodTypeParam(T t) {
+    return super.methodTypeParam(t);
+  }
+}
diff --git a/checker/jtreg/nullness/issue824/Class2.out b/checker/jtreg/nullness/issue824/Class2.out
new file mode 100644
index 0000000..36024f9
--- /dev/null
+++ b/checker/jtreg/nullness/issue824/Class2.out
@@ -0,0 +1,10 @@
+Class2.java:14:39: compiler.err.proc.messager: (type.argument)
+Class2.java:15:20: compiler.err.proc.messager: (type.argument)
+Class2.java:15:45: compiler.err.proc.messager: (type.argument)
+Class2.java:16:27: compiler.err.proc.messager: (type.argument)
+Class2.java:19:27: compiler.err.proc.messager: (type.argument)
+Class2.java:20:25: compiler.err.proc.messager: (type.argument)
+Class2.java:20:26: compiler.err.proc.messager: (argument)
+Class2.java:24:14: compiler.err.proc.messager: (override.return)
+Class2.java:25:33: compiler.err.proc.messager: (type.argument)
+9 errors
diff --git a/checker/jtreg/nullness/issue824/First.astub b/checker/jtreg/nullness/issue824/First.astub
new file mode 100644
index 0000000..ef00c8e
--- /dev/null
+++ b/checker/jtreg/nullness/issue824/First.astub
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class First {
+    public static <T extends @Nullable Object> void method(Supplier<T> supplier, Callable<@Nullable ? super T> callable) {}
+
+    public interface Supplier<T extends @Nullable Object> {}
+
+    public interface Callable<T extends @Nullable Object> {}
+}
diff --git a/checker/jtreg/nullness/issue824/NoStubFirst.java b/checker/jtreg/nullness/issue824/NoStubFirst.java
new file mode 100644
index 0000000..27e8a02
--- /dev/null
+++ b/checker/jtreg/nullness/issue824/NoStubFirst.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class NoStubFirst {
+  public static <T extends @Nullable Object> void method(
+      Supplier<T> supplier, Callable<? super T> callable) {}
+
+  public interface Supplier<T extends @Nullable Object> {}
+
+  public interface Callable<T extends @Nullable Object> {}
+}
diff --git a/checker/jtreg/nullness/issue824/NoStubSecond.java b/checker/jtreg/nullness/issue824/NoStubSecond.java
new file mode 100644
index 0000000..ab32bcd
--- /dev/null
+++ b/checker/jtreg/nullness/issue824/NoStubSecond.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class NoStubSecond {
+  public static void one(
+      NoStubFirst.Supplier<Integer> supplier, NoStubFirst.Callable<@Nullable Object> callable) {
+    NoStubFirst.method(supplier, callable);
+  }
+
+  public static void two(
+      NoStubFirst.Supplier<Integer> supplier, NoStubFirst.Callable<Object> callable) {
+    NoStubFirst.method(supplier, callable);
+  }
+}
diff --git a/checker/jtreg/nullness/issue824/Second.java b/checker/jtreg/nullness/issue824/Second.java
new file mode 100644
index 0000000..495e943
--- /dev/null
+++ b/checker/jtreg/nullness/issue824/Second.java
@@ -0,0 +1,20 @@
+/*
+ * @test
+ * @summary Test case for Issue 824 https://github.com/typetools/checker-framework/issues/824
+ * @compile -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext NoStubFirst.java NoStubSecond.java
+ * @compile -XDrawDiagnostics -Xlint:unchecked ../issue824lib/First.java
+ * @compile -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Second.java -Astubs=First.astub
+ */
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Second {
+  public static void one(
+      First.Supplier<Integer> supplier, First.Callable<@Nullable Object> callable) {
+    First.method(supplier, callable);
+  }
+
+  public static void two(First.Supplier<Integer> supplier, First.Callable<Object> callable) {
+    First.method(supplier, callable);
+  }
+}
diff --git a/checker/jtreg/nullness/issue824lib/Class1.java b/checker/jtreg/nullness/issue824lib/Class1.java
new file mode 100644
index 0000000..a358e82
--- /dev/null
+++ b/checker/jtreg/nullness/issue824lib/Class1.java
@@ -0,0 +1,13 @@
+public class Class1<Q> {
+  class Gen<S> {}
+
+  public <T> T methodTypeParam(T t) {
+    return t;
+  }
+
+  public void classTypeParam(Q e) {}
+
+  public <F> void wildcardExtends(Gen<? extends F> class1) {}
+
+  public <F> void wildcardSuper(Gen<? super F> class1) {}
+}
diff --git a/checker/jtreg/nullness/issue824lib/First.java b/checker/jtreg/nullness/issue824lib/First.java
new file mode 100644
index 0000000..f8cda22
--- /dev/null
+++ b/checker/jtreg/nullness/issue824lib/First.java
@@ -0,0 +1,7 @@
+public class First {
+  public interface Supplier<T> {}
+
+  public interface Callable<T> {}
+
+  public static <T> void method(Supplier<T> supplier, Callable<? super T> callable) {}
+}
diff --git a/checker/jtreg/nullness/preciseErrorMsg/Class1.java b/checker/jtreg/nullness/preciseErrorMsg/Class1.java
new file mode 100644
index 0000000..cbfd5cc
--- /dev/null
+++ b/checker/jtreg/nullness/preciseErrorMsg/Class1.java
@@ -0,0 +1,16 @@
+/*
+ * @test
+ * @summary
+ * Test case for Issue 1051
+ * https://github.com/typetools/checker-framework/issues/1051
+ *
+ * @compile/fail/ref=Class1.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Class1.java
+ *
+ */
+
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public class Class1 {
+  @RequiresNonNull("instanceField")
+  public static void foo() {}
+}
diff --git a/checker/jtreg/nullness/preciseErrorMsg/Class1.out b/checker/jtreg/nullness/preciseErrorMsg/Class1.out
new file mode 100644
index 0000000..24cf78a
--- /dev/null
+++ b/checker/jtreg/nullness/preciseErrorMsg/Class1.out
@@ -0,0 +1,2 @@
+Class1.java:15:22: compiler.err.proc.messager: (flowexpr.parse.error)
+1 error
diff --git a/checker/jtreg/nullness/stub-warnings/Binary.java b/checker/jtreg/nullness/stub-warnings/Binary.java
new file mode 100644
index 0000000..3b98cd3
--- /dev/null
+++ b/checker/jtreg/nullness/stub-warnings/Binary.java
@@ -0,0 +1,23 @@
+// This class is not compiled with the Nullness Checker,
+// so that only explicit annotations are stored in bytecode.
+
+import javax.annotation.Nullable;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class Binary {
+  @Nullable Object foo() {
+    return null;
+  }
+
+  Object bar(Object p) {
+    return null;
+  }
+
+  int baz(Object @NonNull [] p) {
+    return 1;
+  }
+
+  int baz2(Object[] p) {
+    return 1;
+  }
+}
diff --git a/checker/jtreg/nullness/stub-warnings/StubWarnings.java b/checker/jtreg/nullness/stub-warnings/StubWarnings.java
new file mode 100644
index 0000000..7055fe3
--- /dev/null
+++ b/checker/jtreg/nullness/stub-warnings/StubWarnings.java
@@ -0,0 +1,9 @@
+/*
+ * @test
+ * @summary Test warnings for redundant stub specifications.
+ *
+ * @compile -XDrawDiagnostics -Xlint:unchecked Binary.java
+ * @compile/fail/ref=StubWarnings.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext -Astubs=binary.astub -AstubWarnIfRedundantWithBytecode -AstubWarnIfOverwritesBytecode -Werror StubWarnings.java
+ * @compile -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext -Astubs=binary.astub StubWarnings.java
+ */
+public class StubWarnings {}
diff --git a/checker/jtreg/nullness/stub-warnings/StubWarnings.out b/checker/jtreg/nullness/stub-warnings/StubWarnings.out
new file mode 100644
index 0000000..72d0f47
--- /dev/null
+++ b/checker/jtreg/nullness/stub-warnings/StubWarnings.out
@@ -0,0 +1,5 @@
+- compiler.warn.proc.messager: binary.astub:(line 6,col 5): redundant stub file specification for Binary.foo()
+- compiler.warn.proc.messager: binary.astub:(line 8,col 5): redundant stub file specification for Binary.bar(java.lang.Object)
+- compiler.err.warnings.and.werror
+1 error
+2 warnings
diff --git a/checker/jtreg/nullness/stub-warnings/binary.astub b/checker/jtreg/nullness/stub-warnings/binary.astub
new file mode 100644
index 0000000..62f0bdf
--- /dev/null
+++ b/checker/jtreg/nullness/stub-warnings/binary.astub
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class Binary {
+    // warn: redundant
+    @Nullable Object foo() { return null; }
+    // warn: redundant
+    Object bar(Object p) { return null; }
+    // TODO: https://github.com/typetools/checker-framework/issues/2759
+    int baz(Object @Nullable [] p) { return 1; }
+    // don't warn: changes unannotated parameter type
+    int baz2(Object @Nullable [] p) { return 1; }
+}
diff --git a/checker/jtreg/rawtypes/RawTypeFail.java b/checker/jtreg/rawtypes/RawTypeFail.java
new file mode 100644
index 0000000..402bf5f
--- /dev/null
+++ b/checker/jtreg/rawtypes/RawTypeFail.java
@@ -0,0 +1,17 @@
+/*
+ * @test
+ * @summary Test that raw types sometimes produces unwanted errors
+ * @ignore Renable once Issue #635 is fixed (https://github.com/typetools/checker-framework/issues/635)
+ *
+ * @compile/fail/ref=RawTypeFail.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Alint -Anomsgtext RawTypeFail.java
+ * @compile/ref=RawTypeFailIgnored.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Alint -Anomsgtext -AignoreRawTypeArguments RawTypeFail.java
+ */
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class RawTypeFail {
+  Map mr = new HashMap();
+  Map<String, Object> mc = mr;
+  Map<String, Object> mc2 = new HashMap();
+}
diff --git a/checker/jtreg/rawtypes/RawTypeFail.out b/checker/jtreg/rawtypes/RawTypeFail.out
new file mode 100644
index 0000000..474a239
--- /dev/null
+++ b/checker/jtreg/rawtypes/RawTypeFail.out
@@ -0,0 +1,6 @@
+RawTypeFail.java:14:30: compiler.warn.prob.found.req: (compiler.misc.unchecked.assign), java.util.Map, java.util.Map<java.lang.String,java.lang.Object>
+RawTypeFail.java:15:31: compiler.warn.prob.found.req: (compiler.misc.unchecked.assign), java.util.HashMap, java.util.Map<java.lang.String,java.lang.Object>
+RawTypeFail.java:14:30: compiler.err.proc.messager: (assignment)
+RawTypeFail.java:15:31: compiler.err.proc.messager: (assignment)
+2 errors
+2 warnings
\ No newline at end of file
diff --git a/checker/jtreg/rawtypes/RawTypeFailIgnored.out b/checker/jtreg/rawtypes/RawTypeFailIgnored.out
new file mode 100644
index 0000000..e6bec8e
--- /dev/null
+++ b/checker/jtreg/rawtypes/RawTypeFailIgnored.out
@@ -0,0 +1,3 @@
+RawTypeFail.java:14:30: compiler.warn.prob.found.req: (compiler.misc.unchecked.assign), java.util.Map, java.util.Map<java.lang.String,java.lang.Object>
+RawTypeFail.java:15:31: compiler.warn.prob.found.req: (compiler.misc.unchecked.assign), java.util.HashMap, java.util.Map<java.lang.String,java.lang.Object>
+2 warnings
\ No newline at end of file
diff --git a/checker/jtreg/sortwarnings/ErrorOrders.java b/checker/jtreg/sortwarnings/ErrorOrders.java
new file mode 100644
index 0000000..fde72d1
--- /dev/null
+++ b/checker/jtreg/sortwarnings/ErrorOrders.java
@@ -0,0 +1,63 @@
+package index;
+
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.SameLenBottom;
+import org.checkerframework.checker.index.qual.UpperBoundBottom;
+import org.checkerframework.common.value.qual.BottomVal;
+
+/** This class tests that errors are issued in order of postion. */
+public class ErrorOrders {
+
+  void test2(int i, int[] a) {
+    a[i] = 2;
+  }
+
+  int test4(
+      @GTENegativeOne @UpperBoundBottom int p1,
+      @UpperBoundBottom @GTENegativeOne int p2,
+      int @BottomVal [] p3,
+      int @SameLenBottom [] p4,
+      @BottomVal int p5) {
+
+    @IndexFor("p2") int z = 0;
+    @IndexFor("This isn't an expression") int x = z;
+    return x;
+  }
+
+  void useTest4(int p1, int p2, int[] p3, int[] p4, int p5) {
+    test4(p1, test4(p1, p2, p3, p4, p5), p3, p4, p5);
+  }
+
+  class InnerClass {
+    @IndexFor("This isn't an expression") int x = 0;
+
+    void test2(int i, int[] a) {
+      a[i] = 2;
+    }
+  }
+}
+
+class InSameCompilationUnit {
+  @IndexFor("This isn't an expression") int x = 0;
+
+  void test2(int i, int[] a) {
+    a[i] = 2;
+  }
+
+  int test4(
+      @GTENegativeOne @UpperBoundBottom int p1,
+      @UpperBoundBottom @GTENegativeOne int p2,
+      int @BottomVal [] p3,
+      int @SameLenBottom [] p4,
+      @BottomVal int p5) {
+
+    @IndexFor("p2") int z = 0;
+    @IndexFor("This isn't an expression") int x = z;
+    return x;
+  }
+
+  void useTest4(int p1, int p2, int[] p3, int[] p4, int p5) {
+    test4(p1, test4(p1, p2, p3, p4, p5), p3, p4, p5);
+  }
+}
diff --git a/checker/jtreg/sortwarnings/ErrorOrders.out b/checker/jtreg/sortwarnings/ErrorOrders.out
new file mode 100644
index 0000000..143ba4a
--- /dev/null
+++ b/checker/jtreg/sortwarnings/ErrorOrders.out
@@ -0,0 +1,140 @@
+OrderOfCheckers.java:12:95: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @UnknownVal int @UnknownVal []
+required: @UnknownVal int []
+OrderOfCheckers.java:12:95: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @SearchIndexUnknown int @SearchIndexUnknown []
+required: @SearchIndexBottom int @SearchIndexUnknown []
+OrderOfCheckers.java:12:95: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @SameLenUnknown int @SameLen("y") []
+required: @SameLenUnknown int @SameLenBottom []
+OrderOfCheckers.java:12:95: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @LowerBoundUnknown int @LowerBoundUnknown []
+required: @GTENegativeOne int @LowerBoundUnknown []
+OrderOfCheckers.java:12:95: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @UpperBoundUnknown int @UpperBoundUnknown []
+required: @UpperBoundBottom int @UpperBoundUnknown []
+ErrorOrders.java:13:7: compiler.err.proc.messager: [array.access.unsafe.low] Potentially unsafe array access: the index could be negative.
+found   : @LowerBoundUnknown int
+required: an integer >= 0 (@NonNegative or @Positive)
+ErrorOrders.java:13:7: compiler.err.proc.messager: [array.access.unsafe.high] Potentially unsafe array access: the index could be larger than the array's bound
+found   : @UpperBoundUnknown int
+required: @IndexFor("a") or @LTLengthOf("a") -- an integer less than a's length
+ErrorOrders.java:23:29: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @UpperBoundLiteral(0) int
+required: @LTLengthOf("p2") int
+ErrorOrders.java:24:43: compiler.err.proc.messager: [expression.unparsable] Expression invalid in dependent type annotation: [error for expression: This isn't an expression; error: Invalid 'This isn't an expression' because the expression did not parse. Error message: Encountered unexpected token: "isn" <IDENTIFIER>]
+ErrorOrders.java:24:51: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @LTLengthOf("p2") int
+required: @LTLengthOf("[error for expression: This isn't an expression; error: Invalid 'This isn't an expression' because the expression did not parse. Error message: Encountered unexpected token: "isn" <IDENTIFIER>]") int
+ErrorOrders.java:29:11: compiler.err.proc.messager: [argument] incompatible argument for parameter p1 of test4.
+found   : @LowerBoundUnknown int
+required: @GTENegativeOne int
+ErrorOrders.java:29:11: compiler.err.proc.messager: [argument] incompatible argument for parameter p1 of test4.
+found   : @UpperBoundUnknown int
+required: @UpperBoundBottom int
+ErrorOrders.java:29:20: compiler.err.proc.messager: [argument] incompatible argument for parameter p2 of test4.
+found   : @LowerBoundUnknown int
+required: @GTENegativeOne int
+ErrorOrders.java:29:20: compiler.err.proc.messager: [argument] incompatible argument for parameter p2 of test4.
+found   : @UpperBoundUnknown int
+required: @UpperBoundBottom int
+ErrorOrders.java:29:21: compiler.err.proc.messager: [argument] incompatible argument for parameter p1 of test4.
+found   : @LowerBoundUnknown int
+required: @GTENegativeOne int
+ErrorOrders.java:29:21: compiler.err.proc.messager: [argument] incompatible argument for parameter p1 of test4.
+found   : @UpperBoundUnknown int
+required: @UpperBoundBottom int
+ErrorOrders.java:29:25: compiler.err.proc.messager: [argument] incompatible argument for parameter p2 of test4.
+found   : @LowerBoundUnknown int
+required: @GTENegativeOne int
+ErrorOrders.java:29:25: compiler.err.proc.messager: [argument] incompatible argument for parameter p2 of test4.
+found   : @UpperBoundUnknown int
+required: @UpperBoundBottom int
+ErrorOrders.java:29:29: compiler.err.proc.messager: [argument] incompatible argument for parameter p3 of test4.
+found   : @UnknownVal int @UnknownVal []
+required: @UnknownVal int []
+ErrorOrders.java:29:33: compiler.err.proc.messager: [argument] incompatible argument for parameter p4 of test4.
+found   : @SameLenUnknown int @SameLen("p4") []
+required: @SameLenUnknown int @SameLenBottom []
+ErrorOrders.java:29:37: compiler.err.proc.messager: [argument] incompatible argument for parameter p5 of test4.
+found   : @UnknownVal int
+required: int
+ErrorOrders.java:29:42: compiler.err.proc.messager: [argument] incompatible argument for parameter p3 of test4.
+found   : @UnknownVal int @UnknownVal []
+required: @UnknownVal int []
+ErrorOrders.java:29:46: compiler.err.proc.messager: [argument] incompatible argument for parameter p4 of test4.
+found   : @SameLenUnknown int @SameLen("p4") []
+required: @SameLenUnknown int @SameLenBottom []
+ErrorOrders.java:29:50: compiler.err.proc.messager: [argument] incompatible argument for parameter p5 of test4.
+found   : @UnknownVal int
+required: int
+ErrorOrders.java:33:43: compiler.err.proc.messager: [expression.unparsable] Expression invalid in dependent type annotation: [error for expression: This isn't an expression; error: Invalid 'This isn't an expression' because the expression did not parse. Error message: Encountered unexpected token: "isn" <IDENTIFIER>]
+ErrorOrders.java:33:51: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @UpperBoundLiteral(0) int
+required: @LTLengthOf("[error for expression: This isn't an expression; error: Invalid 'This isn't an expression' because the expression did not parse. Error message: Encountered unexpected token: "isn" <IDENTIFIER>]") int
+ErrorOrders.java:36:9: compiler.err.proc.messager: [array.access.unsafe.low] Potentially unsafe array access: the index could be negative.
+found   : @LowerBoundUnknown int
+required: an integer >= 0 (@NonNegative or @Positive)
+ErrorOrders.java:36:9: compiler.err.proc.messager: [array.access.unsafe.high] Potentially unsafe array access: the index could be larger than the array's bound
+found   : @UpperBoundUnknown int
+required: @IndexFor("a") or @LTLengthOf("a") -- an integer less than a's length
+ErrorOrders.java:42:41: compiler.err.proc.messager: [expression.unparsable] Expression invalid in dependent type annotation: [error for expression: This isn't an expression; error: Invalid 'This isn't an expression' because the expression did not parse. Error message: Encountered unexpected token: "isn" <IDENTIFIER>]
+ErrorOrders.java:42:49: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @UpperBoundLiteral(0) int
+required: @LTLengthOf("[error for expression: This isn't an expression; error: Invalid 'This isn't an expression' because the expression did not parse. Error message: Encountered unexpected token: "isn" <IDENTIFIER>]") int
+ErrorOrders.java:45:7: compiler.err.proc.messager: [array.access.unsafe.low] Potentially unsafe array access: the index could be negative.
+found   : @LowerBoundUnknown int
+required: an integer >= 0 (@NonNegative or @Positive)
+ErrorOrders.java:45:7: compiler.err.proc.messager: [array.access.unsafe.high] Potentially unsafe array access: the index could be larger than the array's bound
+found   : @UpperBoundUnknown int
+required: @IndexFor("a") or @LTLengthOf("a") -- an integer less than a's length
+ErrorOrders.java:55:29: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @UpperBoundLiteral(0) int
+required: @LTLengthOf("p2") int
+ErrorOrders.java:56:43: compiler.err.proc.messager: [expression.unparsable] Expression invalid in dependent type annotation: [error for expression: This isn't an expression; error: Invalid 'This isn't an expression' because the expression did not parse. Error message: Encountered unexpected token: "isn" <IDENTIFIER>]
+ErrorOrders.java:56:51: compiler.err.proc.messager: [assignment] incompatible types in assignment.
+found   : @LTLengthOf("p2") int
+required: @LTLengthOf("[error for expression: This isn't an expression; error: Invalid 'This isn't an expression' because the expression did not parse. Error message: Encountered unexpected token: "isn" <IDENTIFIER>]") int
+ErrorOrders.java:61:11: compiler.err.proc.messager: [argument] incompatible argument for parameter p1 of test4.
+found   : @LowerBoundUnknown int
+required: @GTENegativeOne int
+ErrorOrders.java:61:11: compiler.err.proc.messager: [argument] incompatible argument for parameter p1 of test4.
+found   : @UpperBoundUnknown int
+required: @UpperBoundBottom int
+ErrorOrders.java:61:20: compiler.err.proc.messager: [argument] incompatible argument for parameter p2 of test4.
+found   : @LowerBoundUnknown int
+required: @GTENegativeOne int
+ErrorOrders.java:61:20: compiler.err.proc.messager: [argument] incompatible argument for parameter p2 of test4.
+found   : @UpperBoundUnknown int
+required: @UpperBoundBottom int
+ErrorOrders.java:61:21: compiler.err.proc.messager: [argument] incompatible argument for parameter p1 of test4.
+found   : @LowerBoundUnknown int
+required: @GTENegativeOne int
+ErrorOrders.java:61:21: compiler.err.proc.messager: [argument] incompatible argument for parameter p1 of test4.
+found   : @UpperBoundUnknown int
+required: @UpperBoundBottom int
+ErrorOrders.java:61:25: compiler.err.proc.messager: [argument] incompatible argument for parameter p2 of test4.
+found   : @LowerBoundUnknown int
+required: @GTENegativeOne int
+ErrorOrders.java:61:25: compiler.err.proc.messager: [argument] incompatible argument for parameter p2 of test4.
+found   : @UpperBoundUnknown int
+required: @UpperBoundBottom int
+ErrorOrders.java:61:29: compiler.err.proc.messager: [argument] incompatible argument for parameter p3 of test4.
+found   : @UnknownVal int @UnknownVal []
+required: @UnknownVal int []
+ErrorOrders.java:61:33: compiler.err.proc.messager: [argument] incompatible argument for parameter p4 of test4.
+found   : @SameLenUnknown int @SameLen("p4") []
+required: @SameLenUnknown int @SameLenBottom []
+ErrorOrders.java:61:37: compiler.err.proc.messager: [argument] incompatible argument for parameter p5 of test4.
+found   : @UnknownVal int
+required: int
+ErrorOrders.java:61:42: compiler.err.proc.messager: [argument] incompatible argument for parameter p3 of test4.
+found   : @UnknownVal int @UnknownVal []
+required: @UnknownVal int []
+ErrorOrders.java:61:46: compiler.err.proc.messager: [argument] incompatible argument for parameter p4 of test4.
+found   : @SameLenUnknown int @SameLen("p4") []
+required: @SameLenUnknown int @SameLenBottom []
+ErrorOrders.java:61:50: compiler.err.proc.messager: [argument] incompatible argument for parameter p5 of test4.
+found   : @UnknownVal int
+required: int
+49 errors
diff --git a/checker/jtreg/sortwarnings/Main.java b/checker/jtreg/sortwarnings/Main.java
new file mode 100644
index 0000000..42f9a81
--- /dev/null
+++ b/checker/jtreg/sortwarnings/Main.java
@@ -0,0 +1,13 @@
+/*
+ * @test
+ * @summary Test that errors from compound checkers are sorted correctly.
+ * The first compilation below tests that the errors are ordered by line/column
+ * number.  The second line tests that the errors at the same line/column are
+ * sorted in order of checker; hence, -Anomsgtext is not passed so that
+ * -AshowSuppressWarningsStrings has an effect.
+ *
+ * @compile/fail/ref=ErrorOrders.out -XDrawDiagnostics -processor org.checkerframework.checker.index.IndexChecker OrderOfCheckers.java ErrorOrders.java
+ * @compile/fail/ref=OrderOfCheckers.out -XDrawDiagnostics -processor org.checkerframework.checker.index.IndexChecker OrderOfCheckers.java -AshowSuppressWarningsStrings
+ */
+
+public class Main {}
diff --git a/checker/jtreg/sortwarnings/OrderOfCheckers.java b/checker/jtreg/sortwarnings/OrderOfCheckers.java
new file mode 100644
index 0000000..7fb83ca
--- /dev/null
+++ b/checker/jtreg/sortwarnings/OrderOfCheckers.java
@@ -0,0 +1,14 @@
+package index;
+
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.SameLenBottom;
+import org.checkerframework.checker.index.qual.SearchIndexBottom;
+import org.checkerframework.checker.index.qual.UpperBoundBottom;
+import org.checkerframework.common.value.qual.BottomVal;
+
+/** This class tests that errors issued on the same tree are sorted by checker. */
+public class OrderOfCheckers {
+  void test(int[] y) {
+    @GTENegativeOne @UpperBoundBottom @SearchIndexBottom int @BottomVal @SameLenBottom [] x = y;
+  }
+}
diff --git a/checker/jtreg/sortwarnings/OrderOfCheckers.out b/checker/jtreg/sortwarnings/OrderOfCheckers.out
new file mode 100644
index 0000000..186ba72
--- /dev/null
+++ b/checker/jtreg/sortwarnings/OrderOfCheckers.out
@@ -0,0 +1,16 @@
+OrderOfCheckers.java:12:95: compiler.err.proc.messager: [[value, allcheckers]:assignment] incompatible types in assignment.
+found   : @UnknownVal int @UnknownVal []
+required: @UnknownVal int []
+OrderOfCheckers.java:12:95: compiler.err.proc.messager: [[index, searchindex, allcheckers]:assignment] incompatible types in assignment.
+found   : @SearchIndexUnknown int @SearchIndexUnknown []
+required: @SearchIndexBottom int @SearchIndexUnknown []
+OrderOfCheckers.java:12:95: compiler.err.proc.messager: [[index, samelen, allcheckers]:assignment] incompatible types in assignment.
+found   : @SameLenUnknown int @SameLen("y") []
+required: @SameLenUnknown int @SameLenBottom []
+OrderOfCheckers.java:12:95: compiler.err.proc.messager: [[index, lowerbound, allcheckers]:assignment] incompatible types in assignment.
+found   : @LowerBoundUnknown int @LowerBoundUnknown []
+required: @GTENegativeOne int @LowerBoundUnknown []
+OrderOfCheckers.java:12:95: compiler.err.proc.messager: [[index, upperbound, allcheckers]:assignment] incompatible types in assignment.
+found   : @UpperBoundUnknown int @UpperBoundUnknown []
+required: @UpperBoundBottom int @UpperBoundUnknown []
+5 errors
diff --git a/checker/jtreg/stubs/Issue1542Driver.java b/checker/jtreg/stubs/Issue1542Driver.java
new file mode 100644
index 0000000..7c0495b
--- /dev/null
+++ b/checker/jtreg/stubs/Issue1542Driver.java
@@ -0,0 +1,9 @@
+/*
+ * @test
+ * @summary Test case for Issue 1542 https://github.com/typetools/checker-framework/issues/1542
+ *
+ * @compile -XDrawDiagnostics issue1542/NeedsIntRange.java issue1542/Stub.java issue1542/ExampleAnno.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.common.value.ValueChecker issue1542/UsesIntRange.java -Astubs=issue1542/ -AstubWarnIfNotFound -Werror
+ */
+
+public class Issue1542Driver {}
diff --git a/checker/jtreg/stubs/annotatedFor/Test.astub b/checker/jtreg/stubs/annotatedFor/Test.astub
new file mode 100644
index 0000000..f0fd607
--- /dev/null
+++ b/checker/jtreg/stubs/annotatedFor/Test.astub
@@ -0,0 +1,11 @@
+package annotatedforlib;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+public class Test<T> {
+    @AnnotatedFor("tainting")
+    public void method1(@Nullable T t){}
+    @AnnotatedFor("nullness")
+    public void method2(T t){}
+}
diff --git a/checker/jtreg/stubs/annotatedFor/UseTest.java b/checker/jtreg/stubs/annotatedFor/UseTest.java
new file mode 100644
index 0000000..63b6d46
--- /dev/null
+++ b/checker/jtreg/stubs/annotatedFor/UseTest.java
@@ -0,0 +1,18 @@
+/*
+ * @test
+ * @summary Test AnnotatedFor in stub files
+ * @compile -XDrawDiagnostics -Xlint:unchecked ../annotatedForLib/Test.java
+ * @compile/fail/ref=WithStub.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext UseTest.java -Astubs=Test.astub -AstubWarnIfNotFound -Werror
+ * @compile/fail/ref=WithoutStub.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext UseTest.java -AstubWarnIfNotFound -Werror
+ */
+
+package annotatedfor;
+
+import annotatedforlib.Test;
+
+public class UseTest {
+  void test(Test<String> test) {
+    test.method1(null);
+    test.method2(null);
+  }
+}
diff --git a/checker/jtreg/stubs/annotatedFor/WithStub.out b/checker/jtreg/stubs/annotatedFor/WithStub.out
new file mode 100644
index 0000000..3ed19a9
--- /dev/null
+++ b/checker/jtreg/stubs/annotatedFor/WithStub.out
@@ -0,0 +1,3 @@
+UseTest.java:15:18: compiler.err.proc.messager: (argument)
+UseTest.java:16:18: compiler.err.proc.messager: (argument)
+2 errors
diff --git a/checker/jtreg/stubs/annotatedFor/WithoutStub.out b/checker/jtreg/stubs/annotatedFor/WithoutStub.out
new file mode 100644
index 0000000..3882bb2
--- /dev/null
+++ b/checker/jtreg/stubs/annotatedFor/WithoutStub.out
@@ -0,0 +1,2 @@
+UseTest.java:15:18: compiler.err.proc.messager: (argument)
+1 error
diff --git a/checker/jtreg/stubs/annotatedForLib/Test.java b/checker/jtreg/stubs/annotatedForLib/Test.java
new file mode 100644
index 0000000..0076b95
--- /dev/null
+++ b/checker/jtreg/stubs/annotatedForLib/Test.java
@@ -0,0 +1,9 @@
+package annotatedforlib;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Test<T> {
+  public void method1(T t) {}
+
+  public void method2(@Nullable T t) {}
+}
diff --git a/checker/jtreg/stubs/defaultqualinstub/Main.java b/checker/jtreg/stubs/defaultqualinstub/Main.java
new file mode 100644
index 0000000..7720f4f
--- /dev/null
+++ b/checker/jtreg/stubs/defaultqualinstub/Main.java
@@ -0,0 +1,8 @@
+/*
+ * @test
+ * @summary Test that the DefaultQualifier in an astub works.
+ * @library .
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=defaults.astub pck/Defaults.java -AstubWarnIfNotFound -Werror
+ */
+
+public class Main {}
diff --git a/checker/jtreg/stubs/defaultqualinstub/defaults.astub b/checker/jtreg/stubs/defaultqualinstub/defaults.astub
new file mode 100644
index 0000000..80ce049
--- /dev/null
+++ b/checker/jtreg/stubs/defaultqualinstub/defaults.astub
@@ -0,0 +1,8 @@
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+package pck;
+
+@DefaultQualifier(Nullable.class)
+class Defaults {
+}
diff --git a/checker/jtreg/stubs/defaultqualinstub/pck/Defaults.java b/checker/jtreg/stubs/defaultqualinstub/pck/Defaults.java
new file mode 100644
index 0000000..ff9be02
--- /dev/null
+++ b/checker/jtreg/stubs/defaultqualinstub/pck/Defaults.java
@@ -0,0 +1,11 @@
+package pck;
+
+public class Defaults {
+  Object o;
+
+  void test() {
+    // The astub file changes o to @Nullable
+    // and therefore the assignment is allowed.
+    o = null;
+  }
+}
diff --git a/checker/jtreg/stubs/fakeoverrides/DefineClasses.astub b/checker/jtreg/stubs/fakeoverrides/DefineClasses.astub
new file mode 100644
index 0000000..a01a704
--- /dev/null
+++ b/checker/jtreg/stubs/fakeoverrides/DefineClasses.astub
@@ -0,0 +1,12 @@
+package fakeoverrides;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+class SuperClass implements SuperInterface {
+    // fake override:
+    @Untainted int m();
+}
+
+interface SubInterface extends SuperInterface {
+    // fake override:
+    int m();
+}
diff --git a/checker/jtreg/stubs/fakeoverrides/DefineClasses.java b/checker/jtreg/stubs/fakeoverrides/DefineClasses.java
new file mode 100644
index 0000000..a77f876
--- /dev/null
+++ b/checker/jtreg/stubs/fakeoverrides/DefineClasses.java
@@ -0,0 +1,19 @@
+package fakeoverrides;
+
+public class DefineClasses {}
+
+interface SuperInterface {
+  default int m() {
+    return 0;
+  }
+}
+
+class SuperClass implements SuperInterface {
+  // fake override:
+  // @Untainted int m();
+}
+
+interface SubInterface extends SuperInterface {
+  // fake override:
+  // int m();
+}
diff --git a/checker/jtreg/stubs/fakeoverrides/Use.java b/checker/jtreg/stubs/fakeoverrides/Use.java
new file mode 100644
index 0000000..bb0e746
--- /dev/null
+++ b/checker/jtreg/stubs/fakeoverrides/Use.java
@@ -0,0 +1,19 @@
+package fakeoverrides;
+
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+/*
+ * @test
+ * @summary Test case for multiple fake overrides applying to a callsite.
+ *
+ * @compile -XDrawDiagnostics DefineClasses.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.tainting.TaintingChecker -Astubs=DefineClasses.astub -AstubWarnIfNotFound -Werror Use.java
+ */
+// TODO: Issue error SuperClass and SubInterface have conflicting fake overrides
+// See https://github.com/typetools/checker-framework/issues/2724
+public class Use extends SuperClass implements SubInterface {
+  void use(Use d) {
+    // Ok, because the fake override in SuperClasses is taken over the one in SubInterface.
+    @Untainted int i = d.m();
+  }
+}
diff --git a/checker/jtreg/stubs/general/Driver.java b/checker/jtreg/stubs/general/Driver.java
new file mode 100644
index 0000000..c06c8b2
--- /dev/null
+++ b/checker/jtreg/stubs/general/Driver.java
@@ -0,0 +1,13 @@
+/*
+ * @test
+ * @summary Test that java.lang annotations can be used.
+ * @library .
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=MyStub.astub Driver.java  -AstubWarnIfNotFound -Werror
+ */
+
+public class Driver {
+  void test() {
+    Object o = null;
+    String v = String.valueOf(o);
+  }
+}
diff --git a/checker/jtreg/stubs/general/Driver2.java b/checker/jtreg/stubs/general/Driver2.java
new file mode 100644
index 0000000..f4876dd
--- /dev/null
+++ b/checker/jtreg/stubs/general/Driver2.java
@@ -0,0 +1,9 @@
+/*
+ * @test
+ * @summary Test that a stub file can have no package, but have an annotation on the class.
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=NoPackage.astub Driver2.java  -Werror
+ */
+
+public class Driver2 {
+  void test() {}
+}
diff --git a/checker/jtreg/stubs/general/MyStub.astub b/checker/jtreg/stubs/general/MyStub.astub
new file mode 100644
index 0000000..b030a87
--- /dev/null
+++ b/checker/jtreg/stubs/general/MyStub.astub
@@ -0,0 +1,6 @@
+package java.lang;
+import org.checkerframework.framework.qual.*;
+@SuppressWarnings("something")
+class String {
+
+}
diff --git a/checker/jtreg/stubs/general/NoPackage.astub b/checker/jtreg/stubs/general/NoPackage.astub
new file mode 100644
index 0000000..5e70f59
--- /dev/null
+++ b/checker/jtreg/stubs/general/NoPackage.astub
@@ -0,0 +1,7 @@
+// No package statement.
+
+// Prevent a warning about a stub file having no import statements.
+import java.lang.SuppressWarnings;
+
+@SuppressWarnings("") // Any annotation.
+class Driver {}
diff --git a/checker/jtreg/stubs/issue1356/Main.java b/checker/jtreg/stubs/issue1356/Main.java
new file mode 100644
index 0000000..551a004
--- /dev/null
+++ b/checker/jtreg/stubs/issue1356/Main.java
@@ -0,0 +1,10 @@
+/*
+ * @test
+ * @ignore
+ * @summary Test case for Issue 1356.
+ * https://github.com/typetools/checker-framework/issues/1356
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.regex.RegexChecker -Astubs=MyClass.astub mypackage/MyClass.java -Werror -AstubWarnIfNotFound
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.regex.RegexChecker mypackage/UseMyClass.java
+ */
+
+public class Main {}
diff --git a/checker/jtreg/stubs/issue1356/MyClass.astub b/checker/jtreg/stubs/issue1356/MyClass.astub
new file mode 100644
index 0000000..f455c01
--- /dev/null
+++ b/checker/jtreg/stubs/issue1356/MyClass.astub
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.regex.qual.*;
+import org.checkerframework.common.value.qual.*;
+import org.checkerframework.dataflow.qual.Pure;
+
+package mypackage;
+
+class MyClass {
+   static @ArrayLen(1) @Regex(1) String toString(char c);
+   void method1(Object[] param);
+   void method2(Object[][] param);
+   void method3(Object[][][] param);
+   void method4(Object[][][][] param);
+}
diff --git a/checker/jtreg/stubs/issue1356/mypackage/MyClass.java b/checker/jtreg/stubs/issue1356/mypackage/MyClass.java
new file mode 100644
index 0000000..9cdb2e4
--- /dev/null
+++ b/checker/jtreg/stubs/issue1356/mypackage/MyClass.java
@@ -0,0 +1,15 @@
+package mypackage;
+
+public class MyClass {
+  static String toString(char c) {
+    return c + "";
+  }
+
+  void method1(Object[] param) {}
+
+  void method2(Object[][] param) {}
+
+  void method3(Object[][][] param) {}
+
+  void method4(Object[][][][] param) {}
+}
diff --git a/checker/jtreg/stubs/issue1356/mypackage/UseMyClass.java b/checker/jtreg/stubs/issue1356/mypackage/UseMyClass.java
new file mode 100644
index 0000000..ddf224e
--- /dev/null
+++ b/checker/jtreg/stubs/issue1356/mypackage/UseMyClass.java
@@ -0,0 +1,7 @@
+package mypackage;
+
+public class UseMyClass {
+  void test() {
+    String s = MyClass.toString('a');
+  }
+}
diff --git a/checker/jtreg/stubs/issue1456/Lib.astub b/checker/jtreg/stubs/issue1456/Lib.astub
new file mode 100644
index 0000000..0e214d8
--- /dev/null
+++ b/checker/jtreg/stubs/issue1456/Lib.astub
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+
+package issue1456lib;
+
+public class Lib {
+    public <T extends @Untainted Object> Lib(T p);
+    public @Untainted Object object1, object2;
+    public @Untainted Object object;
+    public @Untainted byte @Untainted [] byteArray;
+    public @Untainted byte byte1, byteArray2 @Untainted [];
+    public @Tainted byte @Untainted [] @Untainted [] byteArray3;
+}
diff --git a/checker/jtreg/stubs/issue1456/Main.java b/checker/jtreg/stubs/issue1456/Main.java
new file mode 100644
index 0000000..7cac3b2
--- /dev/null
+++ b/checker/jtreg/stubs/issue1456/Main.java
@@ -0,0 +1,28 @@
+/*
+ * @test
+ * @summary Test case for Issue 1456 https://github.com/typetools/checker-framework/issues/1456
+ * @compile -XDrawDiagnostics -Xlint:unchecked ../issue1456lib/Lib.java
+ * @compile/fail/ref=WithStub.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.tainting.TaintingChecker -Anomsgtext Main.java -Astubs=Lib.astub -AstubWarnIfNotFound -Werror
+ * @compile/fail/ref=WithoutStub.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.tainting.TaintingChecker -Anomsgtext Main.java -AstubWarnIfNotFound -Werror
+ */
+package issue1456;
+
+import issue1456lib.Lib;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class Main {
+
+  void test(Lib lib) {
+    @Untainted Object o = lib.object;
+    @Untainted byte @Untainted [] b = lib.byteArray;
+    @Untainted Object o1 = lib.object1;
+    @Untainted Object o2 = lib.object2;
+    @Untainted byte b2 = lib.byte1;
+    @Untainted byte @Untainted [] b3 = lib.byteArray2;
+    byte @Untainted [] @Untainted [] b4 = lib.byteArray3;
+  }
+
+  void test2(Lib l) {
+    Lib f = new Lib(l);
+  }
+}
diff --git a/checker/jtreg/stubs/issue1456/WithStub.out b/checker/jtreg/stubs/issue1456/WithStub.out
new file mode 100644
index 0000000..35a8157
--- /dev/null
+++ b/checker/jtreg/stubs/issue1456/WithStub.out
@@ -0,0 +1,2 @@
+Main.java:26:13: compiler.err.proc.messager: (type.argument)
+1 error
diff --git a/checker/jtreg/stubs/issue1456/WithoutStub.out b/checker/jtreg/stubs/issue1456/WithoutStub.out
new file mode 100644
index 0000000..e40a24b
--- /dev/null
+++ b/checker/jtreg/stubs/issue1456/WithoutStub.out
@@ -0,0 +1,8 @@
+Main.java:16:30: compiler.err.proc.messager: (assignment)
+Main.java:17:42: compiler.err.proc.messager: (assignment)
+Main.java:18:31: compiler.err.proc.messager: (assignment)
+Main.java:19:31: compiler.err.proc.messager: (assignment)
+Main.java:20:29: compiler.err.proc.messager: (assignment)
+Main.java:21:43: compiler.err.proc.messager: (assignment)
+Main.java:22:46: compiler.err.proc.messager: (assignment)
+7 errors
diff --git a/checker/jtreg/stubs/issue1456lib/Lib.java b/checker/jtreg/stubs/issue1456lib/Lib.java
new file mode 100644
index 0000000..12817ff
--- /dev/null
+++ b/checker/jtreg/stubs/issue1456lib/Lib.java
@@ -0,0 +1,11 @@
+package issue1456lib;
+
+public class Lib {
+  public <T> Lib(T p) {}
+
+  public Object object1, object2;
+  public Object object;
+  public byte[] byteArray;
+  public byte byte1, byteArray2[];
+  public byte[][] byteArray3;
+}
diff --git a/checker/jtreg/stubs/issue1496/ClassAnnotation.astub b/checker/jtreg/stubs/issue1496/ClassAnnotation.astub
new file mode 100644
index 0000000..65bb657
--- /dev/null
+++ b/checker/jtreg/stubs/issue1496/ClassAnnotation.astub
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+// This checks that fully-qualified class literals in annotations are properly handled by the AnnotationFileParser.
+@DefaultQualifier(org.checkerframework.checker.nullness.qual.NonNull.class)
+package java.lang;
+
+// This checks that simply-named class literals in annotations are properly handled by the AnnotationFileParser.
+@DefaultQualifier(Nullable.class)
+package java.util;
diff --git a/checker/jtreg/stubs/issue1496/Main.java b/checker/jtreg/stubs/issue1496/Main.java
new file mode 100644
index 0000000..df9affd
--- /dev/null
+++ b/checker/jtreg/stubs/issue1496/Main.java
@@ -0,0 +1,9 @@
+/*
+ * @test
+ * @summary Test case for Issue 1496 https://github.com/typetools/checker-framework/issues/1496
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=ClassAnnotation.astub Main.java -Werror -AstubWarnIfNotFound
+ */
+
+package issue1496;
+
+public class Main {}
diff --git a/checker/jtreg/stubs/issue1542/ExampleAnno.java b/checker/jtreg/stubs/issue1542/ExampleAnno.java
new file mode 100644
index 0000000..ffa3740
--- /dev/null
+++ b/checker/jtreg/stubs/issue1542/ExampleAnno.java
@@ -0,0 +1,81 @@
+package issue1542;
+
+public class ExampleAnno {
+  public enum MyEnum {
+    A,
+    B,
+    C;
+  }
+
+  @interface DoubleExample {
+    double value();
+  }
+
+  @interface FloatExample {
+    float value();
+  }
+
+  @interface ShortExample {
+    short value();
+  }
+
+  @interface IntExample {
+    int value();
+  }
+
+  @interface LongExample {
+    long value();
+  }
+
+  @interface CharExample {
+    char value();
+  }
+
+  @interface StringExample {
+    String value();
+  }
+
+  @interface ClassExample {
+    Class<?> value();
+  }
+
+  @interface MyEnumExample {
+    MyEnum value();
+  }
+
+  @interface DoubleArrayExample {
+    double[] value();
+  }
+
+  @interface FloatArrayExample {
+    float[] value();
+  }
+
+  @interface ShortArrayExample {
+    short[] value();
+  }
+
+  @interface IntArrayExample {
+    int[] value();
+  }
+
+  @interface LongArrayExample {
+    long[] value();
+  }
+
+  @interface CharArrayExample {
+    char[] value();
+  }
+
+  @interface StringArrayExample {
+    String[] value();
+  }
+
+  @interface ClassArrayExample {
+    Class<?>[] value();
+  }
+
+  @interface MyEnumArrayExample {
+    MyEnum[] value();
+  }
+}
diff --git a/checker/jtreg/stubs/issue1542/NeedsIntRange.astub b/checker/jtreg/stubs/issue1542/NeedsIntRange.astub
new file mode 100644
index 0000000..dbb05f5
--- /dev/null
+++ b/checker/jtreg/stubs/issue1542/NeedsIntRange.astub
@@ -0,0 +1,5 @@
+import org.checkerframework.common.value.qual.IntRange;
+package issue1542;
+class NeedsIntRange {
+    @IntRange(from = 3, to = 20000L) int range(boolean big);
+}
diff --git a/checker/jtreg/stubs/issue1542/NeedsIntRange.java b/checker/jtreg/stubs/issue1542/NeedsIntRange.java
new file mode 100644
index 0000000..9473085
--- /dev/null
+++ b/checker/jtreg/stubs/issue1542/NeedsIntRange.java
@@ -0,0 +1,11 @@
+package issue1542;
+
+public class NeedsIntRange {
+  public static int range(boolean big) {
+    if (big) {
+      return 20000;
+    } else {
+      return 3;
+    }
+  }
+}
diff --git a/checker/jtreg/stubs/issue1542/Stub.astub b/checker/jtreg/stubs/issue1542/Stub.astub
new file mode 100644
index 0000000..c41565a
--- /dev/null
+++ b/checker/jtreg/stubs/issue1542/Stub.astub
@@ -0,0 +1,46 @@
+package issue1542;
+
+import issue1542.ExampleAnno.CharArrayExample;
+import issue1542.ExampleAnno.DoubleArrayExample;
+import issue1542.ExampleAnno.DoubleExample;
+import issue1542.ExampleAnno.LongArrayExample;
+import issue1542.ExampleAnno.LongExample;
+import issue1542.ExampleAnno.MyEnum;
+import static issue1542.ExampleAnno.MyEnum.*;
+import issue1542.ExampleAnno.MyEnumArrayExample;
+import issue1542.ExampleAnno.MyEnumExample;
+import issue1542.ExampleAnno.ClassArrayExample;
+import issue1542.ExampleAnno.ClassExample;
+
+public class Stub {
+    @CharArrayExample({1, 'a'}) int x1;
+    @CharArrayExample('a') int x2;
+    @CharArrayExample(1) int x3;
+    @LongExample(1) int x4;
+    @LongExample(1L) int x5;
+    @LongArrayExample(1L) int x6;
+    @LongArrayExample({1L, 1}) int x7;
+
+    @MyEnumArrayExample(MyEnum.A) int x8;
+    @MyEnumArrayExample(A) int x9;
+    @MyEnumArrayExample({MyEnum.A, B}) int x10;
+    @MyEnumExample(A) int x11;
+
+    @DoubleExample(0) int x12;
+    @DoubleExample(0L) int x13;
+    @DoubleExample(0.0) int x14;
+    @DoubleExample(0.0f) int x15;
+    @DoubleArrayExample({0, 0L, 0.0, 0.0F}) int x16;
+
+    @ClassExample(ClassExample.class) int x17;
+    @ClassArrayExample(ClassExample.class) int x18;
+    @ClassArrayExample({ClassExample.class, java.lang.String.class, String.class}) int x19;
+    @ClassArrayExample({InnerStub.class, Stub.class, Stub.InnerStub.class}) int x20;
+
+    class InnerStub {
+        @ClassArrayExample({InnerStub.class, Stub.class, InnerStub2.class}) int x20;
+        class InnerInnerStub {
+            @ClassArrayExample({InnerStub.class, Stub.class, InnerStub2.class}) int x20;
+        }
+    }
+}
diff --git a/checker/jtreg/stubs/issue1542/Stub.java b/checker/jtreg/stubs/issue1542/Stub.java
new file mode 100644
index 0000000..f1da1ae
--- /dev/null
+++ b/checker/jtreg/stubs/issue1542/Stub.java
@@ -0,0 +1,34 @@
+package issue1542;
+
+public class Stub {
+  int x1;
+  int x2;
+  int x3;
+  int x4;
+  int x5;
+  int x6;
+  int x7;
+  int x8;
+  int x9;
+  int x10;
+  int x11;
+  int x12;
+  int x13;
+  int x14;
+  int x15;
+  int x16;
+  int x17;
+  int x18;
+  int x19;
+  int x20;
+
+  public class InnerStub {
+    int x20;
+
+    class InnerInnerStub {
+      int x20;
+    }
+  }
+
+  class InnerStub2 {}
+}
diff --git a/checker/jtreg/stubs/issue1542/UsesIntRange.java b/checker/jtreg/stubs/issue1542/UsesIntRange.java
new file mode 100644
index 0000000..0a05718
--- /dev/null
+++ b/checker/jtreg/stubs/issue1542/UsesIntRange.java
@@ -0,0 +1,10 @@
+package issue1542;
+
+import org.checkerframework.common.value.qual.IntRange;
+
+public class UsesIntRange {
+  void do_things() {
+    @IntRange(from = 3, to = 20000) int x = NeedsIntRange.range(true);
+    @IntRange(from = 3L, to = 20000) int y = NeedsIntRange.range(true);
+  }
+}
diff --git a/checker/jtreg/stubs/issue1565/MyListGeneric.java b/checker/jtreg/stubs/issue1565/MyListGeneric.java
new file mode 100644
index 0000000..719f64c
--- /dev/null
+++ b/checker/jtreg/stubs/issue1565/MyListGeneric.java
@@ -0,0 +1,14 @@
+/*
+ * @test
+ * @summary Test case for Issue 1565 https://github.com/typetools/checker-framework/issues/1565
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=MyStub.astub -AstubWarnIfNotFound MyListGeneric.java
+ */
+
+import java.util.AbstractList;
+
+public abstract class MyListGeneric<T> extends AbstractList<T> {
+  public MyListGeneric() {
+    clear();
+  }
+}
diff --git a/checker/jtreg/stubs/issue1565/MyStub.astub b/checker/jtreg/stubs/issue1565/MyStub.astub
new file mode 100644
index 0000000..83af7c4
--- /dev/null
+++ b/checker/jtreg/stubs/issue1565/MyStub.astub
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import java.util.*;
+
+package java.util;
+class AbstractList<T>
+{
+    void clear(@UnknownInitialization(AbstractList.class) AbstractList<T> this);
+}
+class BitSet
+{
+    void clear(@UnknownInitialization(BitSet.class) BitSet this);
+}
diff --git a/checker/jtreg/stubs/issue1585/Main.java b/checker/jtreg/stubs/issue1585/Main.java
new file mode 100644
index 0000000..3e4c708
--- /dev/null
+++ b/checker/jtreg/stubs/issue1585/Main.java
@@ -0,0 +1,14 @@
+/*
+ * @test
+ * @summary Test case for Issue 1585 https://github.com/typetools/checker-framework/issues/1585
+ * and for more general Java parse errors in stub files.
+ *
+ * ParseError.out and UnknownField.out contain expected warnings.
+ *
+ * @compile/ref=UnknownField.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=UnknownField.astub -Anomsgtext Main.java
+ * @compile/ref=ParseError.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=ParseError.astub -Anomsgtext Main.java
+ */
+
+package issue1585;
+
+public class Main {}
diff --git a/checker/jtreg/stubs/issue1585/ParseError.astub b/checker/jtreg/stubs/issue1585/ParseError.astub
new file mode 100644
index 0000000..a4b88a7
--- /dev/null
+++ b/checker/jtreg/stubs/issue1585/ParseError.astub
@@ -0,0 +1,5 @@
+package issue1585;
+
+class Demo<dkfa {
+  Demo x;
+}
diff --git a/checker/jtreg/stubs/issue1585/ParseError.out b/checker/jtreg/stubs/issue1585/ParseError.out
new file mode 100644
index 0000000..fd6157b
--- /dev/null
+++ b/checker/jtreg/stubs/issue1585/ParseError.out
@@ -0,0 +1,2 @@
+- compiler.warn.proc.messager: ParseError.astub: (line 3,col 12) Parse error. Found "{", expected one of  "," ">" "extends"
+1 warning
diff --git a/checker/jtreg/stubs/issue1585/UnknownField.astub b/checker/jtreg/stubs/issue1585/UnknownField.astub
new file mode 100644
index 0000000..0481a5c
--- /dev/null
+++ b/checker/jtreg/stubs/issue1585/UnknownField.astub
@@ -0,0 +1,6 @@
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+package issue1585;
+
+@DefaultQualifier(NonNull)
+class Main {}
diff --git a/checker/jtreg/stubs/issue1585/UnknownField.out b/checker/jtreg/stubs/issue1585/UnknownField.out
new file mode 100644
index 0000000..68cf927
--- /dev/null
+++ b/checker/jtreg/stubs/issue1585/UnknownField.out
@@ -0,0 +1,4 @@
+- compiler.warn.proc.messager: UnknownField.astub:(line 5,col 19): Static field NonNull is not imported
+- compiler.warn.proc.messager: UnknownField.astub:(line 5,col 19): For annotation @DefaultQualifier(NonNull), could not add  value=NonNull  because variable NonNull not found
+- compiler.warn.proc.messager: UnknownField.astub:(line 5,col 1): Unknown annotation @DefaultQualifier(NonNull)
+3 warnings
diff --git a/checker/jtreg/stubs/issue2059/AnnoNotFound.astub b/checker/jtreg/stubs/issue2059/AnnoNotFound.astub
new file mode 100644
index 0000000..2d16d98
--- /dev/null
+++ b/checker/jtreg/stubs/issue2059/AnnoNotFound.astub
@@ -0,0 +1,9 @@
+// Import at least one supported annotation.
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+package java.lang;
+
+class Object {
+  // Annotation not imported.
+  @Nullable String toString() { throw new RuntimeException("skeleton method"); }
+}
diff --git a/checker/jtreg/stubs/issue2059/AnnoNotFound.out b/checker/jtreg/stubs/issue2059/AnnoNotFound.out
new file mode 100644
index 0000000..51cc8d8
--- /dev/null
+++ b/checker/jtreg/stubs/issue2059/AnnoNotFound.out
@@ -0,0 +1,2 @@
+- compiler.warn.proc.messager: AnnoNotFound.astub:(line 8,col 3): Unknown annotation @Nullable
+1 warning
diff --git a/checker/jtreg/stubs/issue2059/Main.java b/checker/jtreg/stubs/issue2059/Main.java
new file mode 100644
index 0000000..acda5b2
--- /dev/null
+++ b/checker/jtreg/stubs/issue2059/Main.java
@@ -0,0 +1,10 @@
+/*
+ * @test
+ * @summary Test case for Issue 2059 https://github.com/typetools/checker-framework/issues/2059
+ *
+ * @compile/ref=AnnoNotFound.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -AstubWarnIfNotFound -Astubs=AnnoNotFound.astub -Anomsgtext Main.java
+ */
+
+package issue2059;
+
+public class Main {}
diff --git a/checker/jtreg/stubs/issue2147/EnumStubTest.java b/checker/jtreg/stubs/issue2147/EnumStubTest.java
new file mode 100644
index 0000000..05de3b5
--- /dev/null
+++ b/checker/jtreg/stubs/issue2147/EnumStubTest.java
@@ -0,0 +1,20 @@
+/*
+ * @test
+ * @summary Test case for Issue 2147 https://github.com/typetools/checker-framework/issues/2147
+ *
+ * @compile -XDrawDiagnostics SampleEnum.java
+ *
+ * @compile/fail/ref=WithoutStub.out -XDrawDiagnostics -processor org.checkerframework.checker.tainting.TaintingChecker -AstubWarnIfNotFound EnumStubTest.java
+ * @compile/fail/ref=WithStub.out -XDrawDiagnostics -processor org.checkerframework.checker.tainting.TaintingChecker -AstubWarnIfNotFound -Astubs=SampleEnum.astub EnumStubTest.java
+ */
+
+import org.checkerframework.checker.tainting.qual.*;
+
+public class EnumStubTest {
+  void test() {
+    requireEnum(SampleEnum.FIRST);
+    requireEnum(SampleEnum.SECOND);
+  }
+
+  void requireEnum(@Untainted SampleEnum sEnum) {}
+}
diff --git a/checker/jtreg/stubs/issue2147/SampleEnum.astub b/checker/jtreg/stubs/issue2147/SampleEnum.astub
new file mode 100644
index 0000000..80209a7
--- /dev/null
+++ b/checker/jtreg/stubs/issue2147/SampleEnum.astub
@@ -0,0 +1,6 @@
+import org.checkerframework.checker.tainting.qual.*;
+
+enum SampleEnum {
+    @Untainted FIRST,
+    @Tainted SECOND;
+}
diff --git a/checker/jtreg/stubs/issue2147/SampleEnum.java b/checker/jtreg/stubs/issue2147/SampleEnum.java
new file mode 100644
index 0000000..ef1bb6a
--- /dev/null
+++ b/checker/jtreg/stubs/issue2147/SampleEnum.java
@@ -0,0 +1,6 @@
+import org.checkerframework.checker.tainting.qual.*;
+
+public enum SampleEnum {
+  FIRST,
+  SECOND;
+}
diff --git a/checker/jtreg/stubs/issue2147/WithStub.out b/checker/jtreg/stubs/issue2147/WithStub.out
new file mode 100644
index 0000000..a1a85bf
--- /dev/null
+++ b/checker/jtreg/stubs/issue2147/WithStub.out
@@ -0,0 +1,4 @@
+EnumStubTest.java:16:27: compiler.err.proc.messager: [argument] incompatible argument for parameter sEnum of requireEnum.
+found   : @Tainted SampleEnum
+required: @Untainted SampleEnum
+1 error
diff --git a/checker/jtreg/stubs/issue2147/WithoutStub.out b/checker/jtreg/stubs/issue2147/WithoutStub.out
new file mode 100644
index 0000000..13ede75
--- /dev/null
+++ b/checker/jtreg/stubs/issue2147/WithoutStub.out
@@ -0,0 +1,7 @@
+EnumStubTest.java:15:27: compiler.err.proc.messager: [argument] incompatible argument for parameter sEnum of requireEnum.
+found   : @Tainted SampleEnum
+required: @Untainted SampleEnum
+EnumStubTest.java:16:27: compiler.err.proc.messager: [argument] incompatible argument for parameter sEnum of requireEnum.
+found   : @Tainted SampleEnum
+required: @Untainted SampleEnum
+2 errors
diff --git a/checker/jtreg/stubs/sample/Sample.java b/checker/jtreg/stubs/sample/Sample.java
new file mode 100644
index 0000000..2b83564
--- /dev/null
+++ b/checker/jtreg/stubs/sample/Sample.java
@@ -0,0 +1,13 @@
+/*
+ * @test
+ * @summary Test that the stub files get invoked
+ * @library .
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=sample.astub Sample.java  -AstubWarnIfNotFound -Werror
+ */
+
+public class Sample {
+  void test() {
+    Object o = null;
+    String v = String.valueOf(o);
+  }
+}
diff --git a/checker/jtreg/stubs/sample/sample.astub b/checker/jtreg/stubs/sample/sample.astub
new file mode 100644
index 0000000..e76bcb2
--- /dev/null
+++ b/checker/jtreg/stubs/sample/sample.astub
@@ -0,0 +1,7 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+package java.lang;
+
+class String {
+    String valueOf(@Nullable Object s);
+}
diff --git a/checker/jtreg/stubs/wildcards/NonN.astub b/checker/jtreg/stubs/wildcards/NonN.astub
new file mode 100644
index 0000000..4daf4b3
--- /dev/null
+++ b/checker/jtreg/stubs/wildcards/NonN.astub
@@ -0,0 +1,4 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+class NonN<T extends @NonNull Object> {
+}
diff --git a/checker/jtreg/stubs/wildcards/NonN.java b/checker/jtreg/stubs/wildcards/NonN.java
new file mode 100644
index 0000000..3da7420
--- /dev/null
+++ b/checker/jtreg/stubs/wildcards/NonN.java
@@ -0,0 +1 @@
+public class NonN<T> {}
diff --git a/checker/jtreg/stubs/wildcards/Wildcards.java b/checker/jtreg/stubs/wildcards/Wildcards.java
new file mode 100644
index 0000000..dea78d1
--- /dev/null
+++ b/checker/jtreg/stubs/wildcards/Wildcards.java
@@ -0,0 +1,15 @@
+/*
+ * @test
+ * @summary Test for Issue #1055
+ * @library .
+ * @compile NonN.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=NonN.astub Wildcards.java  -AstubWarnIfNotFound -Werror
+ */
+
+public class Wildcards {
+  NonN<?> f = new NonN<Object>();
+
+  class LocalNonN<T extends Object> {}
+
+  LocalNonN<?> g = new LocalNonN<Object>();
+}
diff --git a/checker/jtreg/tainting/NewClass.java b/checker/jtreg/tainting/NewClass.java
new file mode 100644
index 0000000..67c2662
--- /dev/null
+++ b/checker/jtreg/tainting/NewClass.java
@@ -0,0 +1,20 @@
+/*
+ * @test
+ * @summary Test for bug where arguments to constructors were visited twice.
+ *
+ * @compile/fail/ref=NewClass.out -XDrawDiagnostics -processor org.checkerframework.checker.tainting.TaintingChecker -Alint NewClass.java
+ */
+
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class NewClass {
+  public NewClass(Object param) {}
+
+  Object get(@Untainted Object o) {
+    return o;
+  }
+
+  void test() {
+    NewClass newClass = new NewClass(get(get("")));
+  }
+}
diff --git a/checker/jtreg/tainting/NewClass.out b/checker/jtreg/tainting/NewClass.out
new file mode 100644
index 0000000..d2fb527
--- /dev/null
+++ b/checker/jtreg/tainting/NewClass.out
@@ -0,0 +1,4 @@
+NewClass.java:18:45: compiler.err.proc.messager: [argument] incompatible argument for parameter o of get.
+found   : @Tainted Object
+required: @Untainted Object
+1 error
diff --git a/checker/jtreg/tainting/classes/Issue919.java b/checker/jtreg/tainting/classes/Issue919.java
new file mode 100644
index 0000000..9001784
--- /dev/null
+++ b/checker/jtreg/tainting/classes/Issue919.java
@@ -0,0 +1,10 @@
+package classes;
+
+import classes.Issue919B.InnerClass;
+import java.util.Set;
+
+public class Issue919 {
+  private static void method(Set<InnerClass> innerClassSet2) throws Exception {
+    Issue919B.otherMethod(innerClassSet2);
+  }
+}
diff --git a/checker/jtreg/tainting/classes/Issue919B.java b/checker/jtreg/tainting/classes/Issue919B.java
new file mode 100644
index 0000000..2267682
--- /dev/null
+++ b/checker/jtreg/tainting/classes/Issue919B.java
@@ -0,0 +1,24 @@
+package classes;
+
+import java.util.Map;
+import java.util.Set;
+
+public class Issue919B {
+
+  @SuppressWarnings("nullness:return")
+  public static Map<String, InnerClass> otherMethod(Set<InnerClass> innerClassSet) {
+    return null;
+  }
+
+  public static class InnerClass {
+
+    InnerClass() {}
+
+    InnerClass method(String a) {
+      return new InnerClass();
+    }
+  }
+
+  // This class is required in order to reproduce the error.
+  public static class AnotherInnerClass {}
+}
diff --git a/checker/jtreg/tainting/test/Issue919.java b/checker/jtreg/tainting/test/Issue919.java
new file mode 100644
index 0000000..b131fbe
--- /dev/null
+++ b/checker/jtreg/tainting/test/Issue919.java
@@ -0,0 +1,7 @@
+/*
+ * @test
+ * @summary Test case for Issue 919 https://github.com/typetools/checker-framework/issues/919
+ * @compile -processor org.checkerframework.checker.tainting.TaintingChecker ../classes/Issue919.java ../classes/Issue919B.java -AatfCacheSize=9
+ */
+
+public class Issue919 {}
diff --git a/checker/jtreg/unboundedWildcards/issue1275/Crash.java b/checker/jtreg/unboundedWildcards/issue1275/Crash.java
new file mode 100644
index 0000000..46ecb23
--- /dev/null
+++ b/checker/jtreg/unboundedWildcards/issue1275/Crash.java
@@ -0,0 +1,14 @@
+/*
+ * @test
+ * @summary Test case for Issue 1275.
+ * https://github.com/typetools/checker-framework/issues/1275
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Lib.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Crash.java
+ */
+public class Crash {
+  void crash(Sub o) {
+    Sub.SubInner<?> x = o.a().b().b();
+    o.a().b().b().c();
+  }
+}
diff --git a/checker/jtreg/unboundedWildcards/issue1275/Lib.java b/checker/jtreg/unboundedWildcards/issue1275/Lib.java
new file mode 100644
index 0000000..769ae60
--- /dev/null
+++ b/checker/jtreg/unboundedWildcards/issue1275/Lib.java
@@ -0,0 +1,22 @@
+abstract class Super {
+  abstract SuperInner<?> a();
+
+  abstract static class SuperInner<T extends SuperInner<T>> {
+    abstract T b();
+
+    abstract Super c();
+  }
+}
+
+abstract class Sub extends Super {
+  // It is significant that this method specializes the
+  // return type. If this returns SuperInner, no crash happens.
+  @Override
+  abstract SubInner<?> a();
+
+  abstract static class SubInner<S extends SubInner<S>> extends Super.SuperInner<S> {
+    // Crashes with this overridden method and passes without it.
+    @Override
+    abstract Super c();
+  }
+}
diff --git a/checker/jtreg/unboundedWildcards/issue1420/Crash8.java b/checker/jtreg/unboundedWildcards/issue1420/Crash8.java
new file mode 100644
index 0000000..554a114
--- /dev/null
+++ b/checker/jtreg/unboundedWildcards/issue1420/Crash8.java
@@ -0,0 +1,14 @@
+/*
+ * @test
+ * @summary Test for Issue 1420.
+ * https://github.com/typetools/checker-framework/issues/1420
+ *
+ * @compile Crash8Lib.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.tainting.TaintingChecker  Crash8.java
+ */
+abstract class Crash8<X extends Crash8Lib.Box<X>> {
+  void test(Crash8Lib.Main main, boolean b, Class<X> cls) {
+    Crash8Lib.MyIterable<X> x = main.foo(cls).get1().getIterable2();
+    x = b ? x : main.foo(cls).get1().getIterable2();
+  }
+}
diff --git a/checker/jtreg/unboundedWildcards/issue1420/Crash8Lib.java b/checker/jtreg/unboundedWildcards/issue1420/Crash8Lib.java
new file mode 100644
index 0000000..386ea3b
--- /dev/null
+++ b/checker/jtreg/unboundedWildcards/issue1420/Crash8Lib.java
@@ -0,0 +1,19 @@
+interface Crash8Lib {
+  interface MyIterable<A> extends Iterable<A> {}
+
+  interface Box<B extends Box<B>> {}
+
+  interface Root<C extends Root<C, D>, D> {
+    C get1();
+
+    MyIterable<D> getIterable2();
+  }
+
+  interface Sub<F extends Root<F, G>, G, H extends Box<H>> extends Root<F, G> {}
+
+  interface Leaf<I extends Root<I, J>, J extends Box<J>> extends Sub<I, J, J> {}
+
+  interface Main {
+    <K extends Box<K>> Leaf<?, K> foo(Class<K> b);
+  }
+}
diff --git a/checker/jtreg/unboundedWildcards/issue1427/B.java b/checker/jtreg/unboundedWildcards/issue1427/B.java
new file mode 100644
index 0000000..ec411f8
--- /dev/null
+++ b/checker/jtreg/unboundedWildcards/issue1427/B.java
@@ -0,0 +1,28 @@
+class One {
+  abstract static class B<A extends B<A, C>, C extends One> {
+    abstract C build();
+
+    A f() {
+      throw new AssertionError();
+    }
+  }
+}
+
+class Two extends One {
+  static class B<D extends B<D>> extends One.B<D, Two> {
+    @Override
+    Two build() {
+      throw new AssertionError();
+    }
+  }
+}
+
+class Three<E extends Three<E>> extends Two.B<E> {
+  static Three<?> c() {
+    throw new AssertionError();
+  }
+
+  E g() {
+    throw new AssertionError();
+  }
+}
diff --git a/checker/jtreg/unboundedWildcards/issue1427/T.java b/checker/jtreg/unboundedWildcards/issue1427/T.java
new file mode 100644
index 0000000..632bf45
--- /dev/null
+++ b/checker/jtreg/unboundedWildcards/issue1427/T.java
@@ -0,0 +1,13 @@
+/*
+ * @test
+ * @summary Test for Issue 1427.
+ * https://github.com/typetools/checker-framework/issues/1427
+ *
+ * @compile B.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.tainting.TaintingChecker T.java
+ */
+public class T {
+  {
+    Three.c().g().f().build();
+  }
+}
diff --git a/checker/jtreg/unboundedWildcards/issue1428/B.java b/checker/jtreg/unboundedWildcards/issue1428/B.java
new file mode 100644
index 0000000..181d6da
--- /dev/null
+++ b/checker/jtreg/unboundedWildcards/issue1428/B.java
@@ -0,0 +1,9 @@
+import java.util.List;
+
+interface B {
+  List<L<?>> getItems();
+}
+
+interface V {}
+
+interface L<T extends V> {}
diff --git a/checker/jtreg/unboundedWildcards/issue1428/T.java b/checker/jtreg/unboundedWildcards/issue1428/T.java
new file mode 100644
index 0000000..fe27125
--- /dev/null
+++ b/checker/jtreg/unboundedWildcards/issue1428/T.java
@@ -0,0 +1,17 @@
+/*
+ * @test
+ * @summary Test for Issue 1428.
+ * https://github.com/typetools/checker-framework/issues/1428
+ *
+ * @compile B.java
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.tainting.TaintingChecker T.java
+ */
+
+import java.util.Collections;
+import java.util.List;
+
+public class T {
+  public List<L<?>> f(B b) {
+    return true ? Collections.<L<?>>emptyList() : b.getItems();
+  }
+}
diff --git a/checker/jtregJdk11/Jdk11Release8.java b/checker/jtregJdk11/Jdk11Release8.java
new file mode 100644
index 0000000..ec5d530
--- /dev/null
+++ b/checker/jtregJdk11/Jdk11Release8.java
@@ -0,0 +1,8 @@
+/*
+ * @test
+ * @summary Test that --release 8 does not cause a crash.
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.interning.InterningChecker Jdk11Release8.java --release 8
+ */
+
+public class Jdk11Release8 {}
diff --git a/checker/jtregJdk11/TEST.ROOT b/checker/jtregJdk11/TEST.ROOT
new file mode 100644
index 0000000..aa43051
--- /dev/null
+++ b/checker/jtregJdk11/TEST.ROOT
@@ -0,0 +1,5 @@
+# This file identifies the root of the test-suite hierarchy.
+# It also contains test-suite configuration information.
+
+# The list of keywords supported in the entire test suite
+keys=
diff --git a/checker/jtregJdk11/issue244/MyDialog.java b/checker/jtregJdk11/issue244/MyDialog.java
new file mode 100644
index 0000000..266ce7e
--- /dev/null
+++ b/checker/jtregJdk11/issue244/MyDialog.java
@@ -0,0 +1,7 @@
+/*
+ * @test
+ * @summary Test for crash
+ *
+ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker MyDialog.java -target 1.8 -source 1.8 -nowarn
+ */
+public class MyDialog extends javax.swing.JDialog {}
diff --git a/checker/resources b/checker/resources
new file mode 120000
index 0000000..02aeeb1
--- /dev/null
+++ b/checker/resources
@@ -0,0 +1 @@
+src/main/resources
\ No newline at end of file
diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java
new file mode 100644
index 0000000..45c8a1e
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java
@@ -0,0 +1,362 @@
+package org.checkerframework.checker.calledmethods;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import java.util.ArrayList;
+import java.util.Collection;
+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.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.calledmethods.builder.AutoValueSupport;
+import org.checkerframework.checker.calledmethods.builder.BuilderFrameworkSupport;
+import org.checkerframework.checker.calledmethods.builder.LombokSupport;
+import org.checkerframework.checker.calledmethods.qual.CalledMethods;
+import org.checkerframework.checker.calledmethods.qual.CalledMethodsBottom;
+import org.checkerframework.checker.calledmethods.qual.CalledMethodsPredicate;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.accumulation.AccumulationAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.common.value.ValueCheckerUtils;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.UserError;
+
+/** The annotated type factory for the Called Methods Checker. */
+public class CalledMethodsAnnotatedTypeFactory extends AccumulationAnnotatedTypeFactory {
+
+  /**
+   * The builder frameworks (such as Lombok and AutoValue) supported by this instance of the Called
+   * Methods Checker.
+   */
+  private Collection<BuilderFrameworkSupport> builderFrameworkSupports;
+
+  /**
+   * Whether to use the Value Checker as a subchecker to reduce false positives when analyzing calls
+   * to the AWS SDK. Defaults to false. Controlled by the command-line option {@code
+   * -AuseValueChecker}.
+   */
+  private final boolean useValueChecker;
+
+  /**
+   * The {@link java.util.Collections#singletonList} method. It is treated specially by {@link
+   * #adjustMethodNameUsingValueChecker}.
+   */
+  private final ExecutableElement collectionsSingletonList =
+      TreeUtils.getMethod("java.util.Collections", "singletonList", 1, getProcessingEnv());
+
+  /**
+   * Create a new CalledMethodsAnnotatedTypeFactory.
+   *
+   * @param checker the checker
+   */
+  public CalledMethodsAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker, CalledMethods.class, CalledMethodsBottom.class, CalledMethodsPredicate.class);
+    this.builderFrameworkSupports = new ArrayList<>(2);
+    String[] disabledFrameworks;
+    if (checker.hasOption(CalledMethodsChecker.DISABLE_BUILDER_FRAMEWORK_SUPPORTS)) {
+      disabledFrameworks =
+          checker.getOption(CalledMethodsChecker.DISABLE_BUILDER_FRAMEWORK_SUPPORTS).split(",");
+    } else {
+      disabledFrameworks = new String[0];
+    }
+    enableFrameworks(disabledFrameworks);
+    this.useValueChecker = checker.hasOption(CalledMethodsChecker.USE_VALUE_CHECKER);
+    // Lombok generates @CalledMethods annotations using an old package name,
+    // so we maintain it as an alias.
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.builder.qual.CalledMethods", CalledMethods.class, true);
+    // Lombok also generates an @NotCalledMethods annotation, which we have no support for. We
+    // therefore treat it as top.
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.builder.qual.NotCalledMethods", this.top);
+    this.postInit();
+  }
+
+  /**
+   * Enables support for the default builder-generation frameworks, except those listed in the
+   * disabled builder frameworks parsed from the -AdisableBuilderFrameworkSupport option's
+   * arguments. Throws a UserError if the user included an unsupported framework in the list of
+   * frameworks to be disabled.
+   *
+   * @param disabledFrameworks the disabled builder frameworks
+   */
+  private void enableFrameworks(String[] disabledFrameworks) {
+    boolean enableAutoValueSupport = true;
+    boolean enableLombokSupport = true;
+    for (String framework : disabledFrameworks) {
+      switch (framework) {
+        case "autovalue":
+          enableAutoValueSupport = false;
+          break;
+        case "lombok":
+          enableLombokSupport = false;
+          break;
+        default:
+          throw new UserError(
+              "Unsupported builder framework in -AdisableBuilderFrameworkSupports: " + framework);
+      }
+    }
+    if (enableAutoValueSupport) {
+      builderFrameworkSupports.add(new AutoValueSupport(this));
+    }
+    if (enableLombokSupport) {
+      builderFrameworkSupports.add(new LombokSupport(this));
+    }
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(super.createTreeAnnotator(), new CalledMethodsTreeAnnotator(this));
+  }
+
+  @Override
+  protected TypeAnnotator createTypeAnnotator() {
+    return new ListTypeAnnotator(super.createTypeAnnotator(), new CalledMethodsTypeAnnotator(this));
+  }
+
+  @Override
+  public boolean returnsThis(MethodInvocationTree tree) {
+    return super.returnsThis(tree)
+        // Continue to trust but not check the old {@link
+        // org.checkerframework.checker.builder.qual.ReturnsReceiver} annotation, for
+        // backwards compatibility.
+        || this.getDeclAnnotation(
+                TreeUtils.elementFromUse(tree),
+                org.checkerframework.checker.builder.qual.ReturnsReceiver.class)
+            != null;
+  }
+
+  /**
+   * Given a tree, returns the name of the method that the tree should be considered as calling.
+   * Returns "withOwners" if the call sets an "owner", "owner-alias", or "owner-id" filter. Returns
+   * "withImageIds" if the call sets an "image-ids" filter.
+   *
+   * <p>Package-private to permit calls from {@link CalledMethodsTransfer}.
+   *
+   * @param methodName the name of the method being explicitly called
+   * @param tree the invocation of the method
+   * @return "withOwners" or "withImageIds" if the tree is an equivalent filter addition. Otherwise,
+   *     return the first argument.
+   */
+  // This cannot return a Name because filterTreeToMethodName cannot.
+  public String adjustMethodNameUsingValueChecker(
+      final String methodName, final MethodInvocationTree tree) {
+    if (!useValueChecker) {
+      return methodName;
+    }
+
+    ExecutableElement invokedMethod = TreeUtils.elementFromUse(tree);
+    if (!ElementUtils.enclosingTypeElement(invokedMethod)
+        .getQualifiedName()
+        .contentEquals("com.amazonaws.services.ec2.model.DescribeImagesRequest")) {
+      return methodName;
+    }
+
+    if (methodName.equals("withFilters") || methodName.equals("setFilters")) {
+      ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class);
+      for (Tree filterTree : tree.getArguments()) {
+        if (TreeUtils.isMethodInvocation(
+            filterTree, collectionsSingletonList, getProcessingEnv())) {
+          // Descend into a call to Collections.singletonList()
+          filterTree = ((MethodInvocationTree) filterTree).getArguments().get(0);
+        }
+        String adjustedMethodName = filterTreeToMethodName(filterTree, valueATF);
+        if (adjustedMethodName != null) {
+          return adjustedMethodName;
+        }
+      }
+    }
+    return methodName;
+  }
+
+  /**
+   * Determine the name of the method in DescribeImagesRequest that is equivalent to the Filter in
+   * the given tree.
+   *
+   * <p>Returns null unless the argument is one of the following:
+   *
+   * <ul>
+   *   <li>a constructor invocation of the Filter constructor whose first argument is the name, such
+   *       as {@code new Filter("owner").*}, or
+   *   <li>a call to the withName method, such as {@code new Filter().*.withName("owner").*}.
+   * </ul>
+   *
+   * In those cases, it returns either the argument to the constructor or the argument to the last
+   * invocation of withName ("owner" in both of the above examples).
+   *
+   * @param filterTree the tree that represents the filter (an argument to the withFilters or
+   *     setFilters method)
+   * @param valueATF the type factory from the Value Checker
+   * @return the adjusted method name, or null if the method name should not be adjusted
+   */
+  // This cannot return a Name because filterKindToMethodName cannot.
+  private @Nullable String filterTreeToMethodName(
+      Tree filterTree, ValueAnnotatedTypeFactory valueATF) {
+    while (filterTree != null && filterTree.getKind() == Tree.Kind.METHOD_INVOCATION) {
+
+      MethodInvocationTree filterTreeAsMethodInvocation = (MethodInvocationTree) filterTree;
+      String filterMethodName = TreeUtils.methodName(filterTreeAsMethodInvocation).toString();
+      if (filterMethodName.contentEquals("withName")
+          && filterTreeAsMethodInvocation.getArguments().size() >= 1) {
+        Tree withNameArgTree = filterTreeAsMethodInvocation.getArguments().get(0);
+        String withNameArg = ValueCheckerUtils.getExactStringValue(withNameArgTree, valueATF);
+        return filterKindToMethodName(withNameArg);
+      }
+      // Proceed leftward (toward the receiver) in a fluent call sequence.
+      filterTree = TreeUtils.getReceiverTree(filterTreeAsMethodInvocation.getMethodSelect());
+    }
+    // The loop has reached the beginning of a fluent sequence of method calls.  If the ultimate
+    // receiver at the beginning of that fluent sequence is a call to the Filter() constructor, then
+    // use the first argument to the Filter constructor, which is the name of the filter.
+    if (filterTree == null) {
+      return null;
+    }
+    if (filterTree.getKind() == Tree.Kind.NEW_CLASS) {
+      ExpressionTree constructorArg = ((NewClassTree) filterTree).getArguments().get(0);
+      String filterKindName = ValueCheckerUtils.getExactStringValue(constructorArg, valueATF);
+      if (filterKindName != null) {
+        return filterKindToMethodName(filterKindName);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Converts from a kind of filter to the name of the corresponding method on a
+   * DescribeImagesRequest object.
+   *
+   * @param filterKind the kind of filter
+   * @return "withOwners" if filterKind is "owner", "owner-alias", or "owner-id"; "withImageIds" if
+   *     filterKind is "image-id"; null otherwise
+   */
+  private static @Nullable String filterKindToMethodName(String filterKind) {
+    switch (filterKind) {
+      case "owner":
+      case "owner-alias":
+      case "owner-id":
+        return "withOwners";
+      case "image-id":
+        return "withImageIds";
+      default:
+        return null;
+    }
+  }
+
+  /**
+   * At a fluent method call (which returns {@code this}), add the method to the type of the return
+   * value.
+   */
+  private class CalledMethodsTreeAnnotator extends AccumulationTreeAnnotator {
+    /**
+     * Creates an instance of this tree annotator for the given type factory.
+     *
+     * @param factory the type factory
+     */
+    public CalledMethodsTreeAnnotator(AccumulationAnnotatedTypeFactory factory) {
+      super(factory);
+    }
+
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
+      // Accumulate a method call, by adding the method being invoked to the return type.
+      if (returnsThis(tree)) {
+        String methodName = TreeUtils.getMethodName(tree.getMethodSelect());
+        methodName = adjustMethodNameUsingValueChecker(methodName, tree);
+        AnnotationMirror oldAnno = type.getAnnotationInHierarchy(top);
+        AnnotationMirror newAnno =
+            qualHierarchy.greatestLowerBound(oldAnno, createAccumulatorAnnotation(methodName));
+        type.replaceAnnotation(newAnno);
+      }
+
+      // Also do the standard accumulation analysis behavior: copy any accumulation
+      // annotations from the receiver to the return type.
+      return super.visitMethodInvocation(tree, type);
+    }
+
+    @Override
+    public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) {
+      for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) {
+        builderFrameworkSupport.handleConstructor(tree, type);
+      }
+      return super.visitNewClass(tree, type);
+    }
+  }
+
+  /**
+   * Adds @CalledMethod annotations for build() methods of AutoValue and Lombok Builders to ensure
+   * required properties have been set.
+   */
+  private class CalledMethodsTypeAnnotator extends TypeAnnotator {
+
+    /**
+     * Constructor matching super.
+     *
+     * @param atypeFactory the type factory
+     */
+    public CalledMethodsTypeAnnotator(AnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitExecutable(AnnotatedTypeMirror.AnnotatedExecutableType t, Void p) {
+      ExecutableElement element = t.getElement();
+
+      TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
+
+      for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) {
+        if (builderFrameworkSupport.isToBuilderMethod(element)) {
+          builderFrameworkSupport.handleToBuilderMethod(t);
+        }
+      }
+
+      Element nextEnclosingElement = enclosingElement.getEnclosingElement();
+      if (nextEnclosingElement.getKind().isClass()) {
+        for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) {
+          if (builderFrameworkSupport.isBuilderBuildMethod(element)) {
+            builderFrameworkSupport.handleBuilderBuildMethod(t);
+          }
+        }
+      }
+
+      return super.visitExecutable(t, p);
+    }
+  }
+
+  /**
+   * Returns the annotation type mirror for the type of {@code expressionTree} with default
+   * annotations applied. As types relevant to Called Methods checking are rarely used inside
+   * generics, this is typically the best choice for type inference.
+   */
+  @Override
+  public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) {
+    TypeMirror type = TreeUtils.typeOf(expressionTree);
+    if (type.getKind() != TypeKind.VOID) {
+      AnnotatedTypeMirror atm = type(expressionTree);
+      addDefaultAnnotations(atm);
+      return atm;
+    }
+    return null;
+  }
+
+  /**
+   * Fetch the supported builder frameworks that are enabled.
+   *
+   * @return a collection of builder frameworks that are enabled in this run of the checker
+   */
+  /* package-private */ Collection<BuilderFrameworkSupport> getBuilderFrameworkSupports() {
+    return builderFrameworkSupports;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsChecker.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsChecker.java
new file mode 100644
index 0000000..dfba90c
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsChecker.java
@@ -0,0 +1,121 @@
+package org.checkerframework.checker.calledmethods;
+
+import java.util.LinkedHashSet;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.common.accumulation.AccumulationChecker;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.qual.StubFiles;
+import org.checkerframework.framework.source.SupportedOptions;
+import org.checkerframework.framework.source.SuppressWarningsPrefix;
+
+/**
+ * The Called Methods Checker tracks the methods that have definitely been called on an object. One
+ * common use case for the Called Methods Checker is to specify safe combinations of options to
+ * builder or builder-like interfaces, preventing objects from being instantiated incompletely.
+ */
+@SuppressWarningsPrefix({
+  // Preferred checkername.
+  "calledmethods",
+  // Deprecated checkernames, supported for backward compatibility.
+  "builder",
+  "object.construction",
+  "objectconstruction"
+})
+@SupportedOptions({
+  CalledMethodsChecker.USE_VALUE_CHECKER,
+  CalledMethodsChecker.COUNT_FRAMEWORK_BUILD_CALLS,
+  CalledMethodsChecker.DISABLE_BUILDER_FRAMEWORK_SUPPORTS,
+  CalledMethodsChecker.DISABLE_RETURNS_RECEIVER
+})
+@StubFiles({"DescribeImages.astub", "GenerateDataKey.astub"})
+public class CalledMethodsChecker extends AccumulationChecker {
+
+  /**
+   * If this option is supplied, count the number of analyzed calls to build() in supported builder
+   * frameworks and print it when analysis is complete. Useful for collecting metrics.
+   */
+  public static final String COUNT_FRAMEWORK_BUILD_CALLS = "countFrameworkBuildCalls";
+
+  /**
+   * This option disables the support for (and therefore the automated checking of) code that uses
+   * the given builder frameworks. Useful when a user **only** wants to enforce specifications on
+   * custom builder objects (such as the AWS SDK examples).
+   */
+  public static final String DISABLE_BUILDER_FRAMEWORK_SUPPORTS = "disableBuilderFrameworkSupports";
+
+  /**
+   * If this option is supplied, use the Value Checker to reduce false positives when analyzing
+   * calls to the AWS SDK.
+   */
+  public static final String USE_VALUE_CHECKER = "useValueChecker";
+
+  /**
+   * Some use cases for the Called Methods Checker do not involve checking fluent APIs, and in those
+   * cases disabling the Returns Receiver Checker using this flag will make the Called Methods
+   * Checker run much faster.
+   */
+  public static final String DISABLE_RETURNS_RECEIVER = "disableReturnsReceiver";
+
+  /**
+   * The number of calls to build frameworks supported by this invocation. Incremented only if the
+   * {@link #COUNT_FRAMEWORK_BUILD_CALLS} option was supplied.
+   */
+  int numBuildCalls = 0;
+
+  /** Never access this boolean directly. Call {@link #isReturnsReceiverDisabled()} instead. */
+  private @MonotonicNonNull Boolean returnsReceiverDisabled = null;
+
+  /**
+   * Was the Returns Receiver Checker disabled on the command line?
+   *
+   * @return whether the -AdisableReturnsReceiver option was specified on the command line
+   */
+  private boolean isReturnsReceiverDisabled() {
+    if (returnsReceiverDisabled == null) {
+      returnsReceiverDisabled = hasOptionNoSubcheckers(DISABLE_RETURNS_RECEIVER);
+    }
+    return returnsReceiverDisabled;
+  }
+
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    LinkedHashSet<Class<? extends BaseTypeChecker>> checkers =
+        super.getImmediateSubcheckerClasses();
+    if (!isReturnsReceiverDisabled()) {
+      checkers.add(ReturnsReceiverChecker.class);
+    }
+    // BaseTypeChecker#hasOption calls this method (so that all subcheckers' options are
+    // considered), so the processingEnvironment must be checked for options directly.
+    if (this.processingEnv.getOptions().containsKey(USE_VALUE_CHECKER)
+        || this.processingEnv
+            .getOptions()
+            .containsKey(this.getClass().getSimpleName() + "_" + USE_VALUE_CHECKER)) {
+      checkers.add(ValueChecker.class);
+    }
+    return checkers;
+  }
+
+  /**
+   * Check whether the given alias analysis is enabled by this particular accumulation checker.
+   *
+   * @param aliasAnalysis the analysis to check
+   * @return true iff the analysis is enabled
+   */
+  @Override
+  public boolean isEnabled(AliasAnalysis aliasAnalysis) {
+    if (aliasAnalysis == AliasAnalysis.RETURNS_RECEIVER) {
+      return !isReturnsReceiverDisabled();
+    }
+    return super.isEnabled(aliasAnalysis);
+  }
+
+  @Override
+  public void typeProcessingOver() {
+    if (getBooleanOption(COUNT_FRAMEWORK_BUILD_CALLS)) {
+      System.out.printf("Found %d build() method calls.%n", numBuildCalls);
+    }
+    super.typeProcessingOver();
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java
new file mode 100644
index 0000000..97f878a
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java
@@ -0,0 +1,38 @@
+package org.checkerframework.checker.calledmethods;
+
+import org.checkerframework.common.accumulation.AccumulationTransfer;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFValue;
+
+/** A transfer function that accumulates the names of methods called. */
+public class CalledMethodsTransfer extends AccumulationTransfer {
+
+  /**
+   * Create a new CalledMethodsTransfer.
+   *
+   * @param analysis the analysis
+   */
+  public CalledMethodsTransfer(final CFAnalysis analysis) {
+    super(analysis);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitMethodInvocation(
+      final MethodInvocationNode node, final TransferInput<CFValue, CFStore> input) {
+    TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(node, input);
+    Node receiver = node.getTarget().getReceiver();
+    if (receiver != null) {
+      String methodName = node.getTarget().getMethod().getSimpleName().toString();
+      methodName =
+          ((CalledMethodsAnnotatedTypeFactory) atypeFactory)
+              .adjustMethodNameUsingValueChecker(methodName, node.getTree());
+      accumulate(receiver, result, methodName);
+    }
+    return result;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsVisitor.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsVisitor.java
new file mode 100644
index 0000000..ea8c3af
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsVisitor.java
@@ -0,0 +1,74 @@
+package org.checkerframework.checker.calledmethods;
+
+import com.sun.source.tree.MethodInvocationTree;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.calledmethods.builder.BuilderFrameworkSupport;
+import org.checkerframework.checker.calledmethods.qual.CalledMethods;
+import org.checkerframework.common.accumulation.AccumulationVisitor;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * This visitor implements the custom error message finalizer.invocation. It also supports counting
+ * the number of framework build calls.
+ */
+public class CalledMethodsVisitor extends AccumulationVisitor {
+
+  /**
+   * Creates a new CalledMethodsVisitor.
+   *
+   * @param checker the type-checker associated with this visitor
+   */
+  public CalledMethodsVisitor(final BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+
+    if (checker.getBooleanOption(CalledMethodsChecker.COUNT_FRAMEWORK_BUILD_CALLS)) {
+      ExecutableElement element = TreeUtils.elementFromUse(node);
+      for (BuilderFrameworkSupport builderFrameworkSupport :
+          ((CalledMethodsAnnotatedTypeFactory) getTypeFactory()).getBuilderFrameworkSupports()) {
+        if (builderFrameworkSupport.isBuilderBuildMethod(element)) {
+          ((CalledMethodsChecker) checker).numBuildCalls++;
+          break;
+        }
+      }
+    }
+    return super.visitMethodInvocation(node, p);
+  }
+
+  /** Turns some method.invocation errors into finalizer.invocation errors. */
+  @Override
+  protected void reportMethodInvocabilityError(
+      MethodInvocationTree node, AnnotatedTypeMirror found, AnnotatedTypeMirror expected) {
+
+    AnnotationMirror expectedCM = expected.getAnnotation(CalledMethods.class);
+    if (expectedCM != null) {
+      AnnotationMirror foundCM = found.getAnnotation(CalledMethods.class);
+      Set<String> foundMethods =
+          foundCM == null
+              ? Collections.emptySet()
+              : new HashSet<>(atypeFactory.getAccumulatedValues(foundCM));
+      List<String> expectedMethods = atypeFactory.getAccumulatedValues(expectedCM);
+      StringJoiner missingMethods = new StringJoiner(" ");
+      for (String expectedMethod : expectedMethods) {
+        if (!foundMethods.contains(expectedMethod)) {
+          missingMethods.add(expectedMethod + "()");
+        }
+      }
+
+      checker.reportError(node, "finalizer.invocation", missingMethods.toString());
+    } else {
+      super.reportMethodInvocabilityError(node, found, expected);
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/DescribeImages.astub b/checker/src/main/java/org/checkerframework/checker/calledmethods/DescribeImages.astub
new file mode 100644
index 0000000..43f299f
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/DescribeImages.astub
@@ -0,0 +1,35 @@
+package com.amazonaws.services.ec2;
+
+import org.checkerframework.checker.calledmethods.qual.*;
+
+// Interface
+class AmazonEC2 {
+    DescribeImagesResult describeImages(
+    // The receiver must have its owner, imageId, or executable user set, either via the "wither" or "setter" methods.
+    @CalledMethodsPredicate("(withOwners || setOwners) || (withImageIds || setImageIds) || (withExecutableUsers || setExecutableUsers)")
+    DescribeImagesRequest request);
+}
+
+// The main implementation class
+class AmazonEC2Client {
+    DescribeImagesResult describeImages(
+    // any combination of withX/setX has to be permitted if an owner has been set or an imageId has been set
+    @CalledMethodsPredicate("(withOwners || setOwners) || (withImageIds || setImageIds) || (withExecutableUsers || setExecutableUsers)")
+    DescribeImagesRequest request);
+}
+
+// Async interface
+class AmazonEC2Async {
+    Future<DescribeImagesResult> describeImagesAsync(
+    // The receiver must have its owner, imageId, or executable user set, either via the "wither" or "setter" methods.
+    @CalledMethodsPredicate("(withOwners || setOwners) || (withImageIds || setImageIds) || (withExecutableUsers || setExecutableUsers)")
+    DescribeImagesRequest request);
+}
+
+// The main async implementation class
+class AmazonEC2AsyncClient {
+    Future<DescribeImagesResult> describeImagesAsync(
+    // any combination of withX/setX has to be permitted if an owner has been set or an imageId has been set
+    @CalledMethodsPredicate("(withOwners || setOwners) || (withImageIds || setImageIds) || (withExecutableUsers || setExecutableUsers)")
+    DescribeImagesRequest request);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/GenerateDataKey.astub b/checker/src/main/java/org/checkerframework/checker/calledmethods/GenerateDataKey.astub
new file mode 100644
index 0000000..d51e0b6
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/GenerateDataKey.astub
@@ -0,0 +1,11 @@
+package com.amazonaws.services.kms;
+
+import org.checkerframework.checker.calledmethods.qual.*;
+
+interface AWSKMS {
+    // This predicate enforces two properties:
+    // 1) the number of bytes or the keyspec has been set. This property is enforced soundly.
+    // 2) both the number of bytes and the keyspec have not been set. This property uses the ! operator,
+    //    so it is best regarded as a bug-finder: what it really proves is that both are not set on all paths.
+    GenerateDataKeyResult generateDataKey(@CalledMethodsPredicate("(setNumberOfBytes || withNumberOfBytes || setKeySpec || withKeySpec) && !(setNumberOfBytes && setKeySpec) && !(setNumberOfBytes && withKeySpec) && !(withNumberOfBytes && setKeySpec) && !(withNumberOfBytes && withKeySpec)") GenerateDataKeyRequest request);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/AutoValueSupport.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/AutoValueSupport.java
new file mode 100644
index 0000000..4e03dcf
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/AutoValueSupport.java
@@ -0,0 +1,432 @@
+package org.checkerframework.checker.calledmethods.builder;
+
+import com.sun.source.tree.NewClassTree;
+import java.beans.Introspector;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory;
+import org.checkerframework.checker.calledmethods.qual.CalledMethods;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.ArraysPlume;
+
+/**
+ * AutoValue support for the Called Methods Checker. This class adds {@code @}{@link CalledMethods}
+ * annotations to the code generated by AutoValue.
+ */
+public class AutoValueSupport implements BuilderFrameworkSupport {
+
+  /** The type factory. */
+  private CalledMethodsAnnotatedTypeFactory atypeFactory;
+
+  /**
+   * Create a new AutoValueSupport.
+   *
+   * @param atypeFactory the typechecker's type factory
+   */
+  public AutoValueSupport(CalledMethodsAnnotatedTypeFactory atypeFactory) {
+    this.atypeFactory = atypeFactory;
+  }
+
+  /**
+   * This method modifies the type of a copy constructor generated by AutoValue to match the type of
+   * the AutoValue toBuilder method, and has no effect if {@code tree} is a call to any other
+   * constructor.
+   *
+   * @param tree AST for a constructor call
+   * @param type type of the call expression
+   */
+  @Override
+  public void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type) {
+    ExecutableElement element = TreeUtils.elementFromUse(tree);
+    TypeMirror superclass = ((TypeElement) element.getEnclosingElement()).getSuperclass();
+
+    if (superclass.getKind() != TypeKind.NONE
+        && ElementUtils.hasAnnotation(
+            TypesUtils.getTypeElement(superclass), getAutoValuePackageName() + ".AutoValue.Builder")
+        && element.getParameters().size() > 0) {
+      handleToBuilderType(
+          type,
+          superclass,
+          (TypeElement) TypesUtils.getTypeElement(superclass).getEnclosingElement());
+    }
+  }
+
+  @Override
+  public boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement) {
+    TypeElement builderElement = (TypeElement) candidateBuildElement.getEnclosingElement();
+    if (ElementUtils.hasAnnotation(
+        builderElement, getAutoValuePackageName() + ".AutoValue.Builder")) {
+      Element classContainingBuilderElement = builderElement.getEnclosingElement();
+      if (!ElementUtils.hasAnnotation(
+          classContainingBuilderElement, getAutoValuePackageName() + ".AutoValue")) {
+        throw new BugInCF(
+            "class "
+                + classContainingBuilderElement.getSimpleName()
+                + " is missing @AutoValue annotation");
+      }
+      // it is a build method if it returns the type with the @AutoValue annotation
+      if (TypesUtils.getTypeElement(candidateBuildElement.getReturnType())
+          .equals(classContainingBuilderElement)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType) {
+
+    ExecutableElement element = builderBuildType.getElement();
+    TypeElement builderElement = (TypeElement) element.getEnclosingElement();
+    TypeElement autoValueClassElement = (TypeElement) builderElement.getEnclosingElement();
+    AnnotationMirror newCalledMethodsAnno =
+        createCalledMethodsForAutoValueClass(builderElement, autoValueClassElement);
+    // Only add the new @CalledMethods annotation if there is not already a @CalledMethods
+    // annotation present.
+    AnnotationMirror explicitCalledMethodsAnno =
+        builderBuildType
+            .getReceiverType()
+            .getAnnotationInHierarchy(
+                atypeFactory.getQualifierHierarchy().getTopAnnotation(newCalledMethodsAnno));
+    if (explicitCalledMethodsAnno == null) {
+      builderBuildType.getReceiverType().addAnnotation(newCalledMethodsAnno);
+    }
+  }
+
+  @Override
+  public boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement) {
+    if (!"toBuilder".equals(candidateToBuilderElement.getSimpleName().toString())) {
+      return false;
+    }
+
+    TypeElement candidateClassContainingToBuilder =
+        (TypeElement) candidateToBuilderElement.getEnclosingElement();
+    boolean isAbstractAV =
+        isAutoValueGenerated(candidateClassContainingToBuilder)
+            && candidateToBuilderElement.getModifiers().contains(Modifier.ABSTRACT);
+    TypeMirror superclassOfClassContainingToBuilder =
+        candidateClassContainingToBuilder.getSuperclass();
+    boolean superIsAV = false;
+    if (superclassOfClassContainingToBuilder.getKind() != TypeKind.NONE) {
+      superIsAV =
+          isAutoValueGenerated(TypesUtils.getTypeElement(superclassOfClassContainingToBuilder));
+    }
+    return superIsAV || isAbstractAV;
+  }
+
+  @Override
+  public void handleToBuilderMethod(AnnotatedExecutableType toBuilderType) {
+    AnnotatedTypeMirror returnType = toBuilderType.getReturnType();
+    ExecutableElement toBuilderElement = toBuilderType.getElement();
+    TypeElement classContainingToBuilder = (TypeElement) toBuilderElement.getEnclosingElement();
+    // Because of the way that the check in #isToBuilderMethod works, if the code reaches this
+    // point and this condition is false, the other condition MUST be true (otherwise,
+    // isToBuilderMethod would have returned false).
+    if (isAutoValueGenerated(classContainingToBuilder)
+        && toBuilderElement.getModifiers().contains(Modifier.ABSTRACT)) {
+      handleToBuilderType(returnType, returnType.getUnderlyingType(), classContainingToBuilder);
+    } else {
+      TypeElement superElement =
+          TypesUtils.getTypeElement(classContainingToBuilder.getSuperclass());
+      handleToBuilderType(returnType, returnType.getUnderlyingType(), superElement);
+    }
+  }
+
+  /**
+   * Was the given element generated by AutoValue?
+   *
+   * @param element the element to check
+   * @return true if the element was generated by AutoValue
+   */
+  private boolean isAutoValueGenerated(Element element) {
+    return ElementUtils.hasAnnotation(element, getAutoValuePackageName() + ".AutoValue");
+  }
+
+  /**
+   * Add, to {@code type}, a CalledMethods annotation with all required methods called. The type can
+   * be the return type of toBuilder or of the corresponding generated "copy" constructor.
+   *
+   * @param type type to update
+   * @param builderType type of abstract @AutoValue.Builder class
+   * @param classElement AutoValue class corresponding to {@code type}
+   */
+  private void handleToBuilderType(
+      AnnotatedTypeMirror type, TypeMirror builderType, TypeElement classElement) {
+    TypeElement builderElement = TypesUtils.getTypeElement(builderType);
+    AnnotationMirror calledMethodsAnno =
+        createCalledMethodsForAutoValueClass(builderElement, classElement);
+    type.replaceAnnotation(calledMethodsAnno);
+  }
+
+  /**
+   * Create an @CalledMethods annotation for the given AutoValue class and builder. The returned
+   * annotation contains all the required setters.
+   *
+   * @param builderElement the element for the Builder class
+   * @param classElement the element for the AutoValue class (i.e. the class that is built by the
+   *     builder)
+   * @return an @CalledMethods annotation representing that all the required setters have been
+   *     called
+   */
+  private AnnotationMirror createCalledMethodsForAutoValueClass(
+      TypeElement builderElement, TypeElement classElement) {
+    Set<String> avBuilderSetterNames = getAutoValueBuilderSetterMethodNames(builderElement);
+    List<String> requiredProperties =
+        getAutoValueRequiredProperties(classElement, avBuilderSetterNames);
+    return createCalledMethodsForAutoValueProperties(requiredProperties, avBuilderSetterNames);
+  }
+
+  /**
+   * Creates a @CalledMethods annotation for the given property names, converting the names to the
+   * corresponding setter method name in the Builder.
+   *
+   * @param propertyNames the property names
+   * @param avBuilderSetterNames names of all setters in the builder class
+   * @return a @CalledMethods annotation that indicates all the given properties have been set
+   */
+  private AnnotationMirror createCalledMethodsForAutoValueProperties(
+      final List<String> propertyNames, Set<String> avBuilderSetterNames) {
+    List<String> calledMethodNames =
+        propertyNames.stream()
+            .map(prop -> autoValuePropToBuilderSetterName(prop, avBuilderSetterNames))
+            .filter(Objects::nonNull)
+            .collect(Collectors.toList());
+    return atypeFactory.createAccumulatorAnnotation(calledMethodNames);
+  }
+
+  /**
+   * Converts the name of a property (i.e., a field) into the name of its setter.
+   *
+   * @param prop the property (i.e., field) name
+   * @param builderSetterNames names of all methods in the builder class
+   * @return the name of the setter for prop
+   */
+  private static String autoValuePropToBuilderSetterName(
+      String prop, Set<String> builderSetterNames) {
+    String[] possiblePropNames;
+    if (prop.startsWith("get") && prop.length() > 3 && Character.isUpperCase(prop.charAt(3))) {
+      possiblePropNames = new String[] {prop, Introspector.decapitalize(prop.substring(3))};
+    } else if (prop.startsWith("is")
+        && prop.length() > 2
+        && Character.isUpperCase(prop.charAt(2))) {
+      possiblePropNames = new String[] {prop, Introspector.decapitalize(prop.substring(2))};
+    } else {
+      possiblePropNames = new String[] {prop};
+    }
+
+    for (String propName : possiblePropNames) {
+      // The setter may be the property name itself, or prefixed by 'set'.
+      if (builderSetterNames.contains(propName)) {
+        return propName;
+      }
+      String setterName = "set" + BuilderFrameworkSupportUtils.capitalize(propName);
+      if (builderSetterNames.contains(setterName)) {
+        return setterName;
+      }
+    }
+
+    // Could not find a corresponding setter.  This is likely because an AutoValue Extension is in
+    // use.  See https://github.com/kelloggm/object-construction-checker/issues/110 .  For now we
+    // return null, but once that bug is fixed, this should be changed to an assertion failure.
+    return null;
+  }
+
+  /**
+   * Computes the required properties of an @AutoValue class.
+   *
+   * @param autoValueClassElement the @AutoValue class
+   * @param avBuilderSetterNames names of all setters in the corresponding AutoValue builder class
+   * @return a list of required property names
+   */
+  private List<String> getAutoValueRequiredProperties(
+      final TypeElement autoValueClassElement, Set<String> avBuilderSetterNames) {
+    return getAllAbstractMethods(autoValueClassElement).stream()
+        .filter(member -> isAutoValueRequiredProperty(member, avBuilderSetterNames))
+        .map(e -> e.getSimpleName().toString())
+        .collect(Collectors.toList());
+  }
+
+  /**
+   * Does member represent a required property of an AutoValue class?
+   *
+   * @param member a member of an AutoValue class or superclass
+   * @param avBuilderSetterNames names of all setters in corresponding AutoValue builder class
+   * @return true if {@code member} is required
+   */
+  private boolean isAutoValueRequiredProperty(Element member, Set<String> avBuilderSetterNames) {
+    String name = member.getSimpleName().toString();
+    // Ignore java.lang.Object overrides, constructors, and toBuilder methods in AutoValue classes.
+    // Strictly speaking, this code should check return types, etc. to handle strange
+    // overloads and other corner cases. They seem unlikely enough that we are skipping for now.
+    if (ArraysPlume.indexOf(
+            new String[] {"equals", "hashCode", "toString", "<init>", "toBuilder"}, name)
+        != -1) {
+      return false;
+    }
+    TypeMirror returnType = ((ExecutableElement) member).getReturnType();
+    if (returnType.getKind() == TypeKind.VOID) {
+      return false;
+    }
+    // shouldn't have a nullable return
+    boolean hasNullable =
+        Stream.concat(
+                atypeFactory.getElementUtils().getAllAnnotationMirrors(member).stream(),
+                returnType.getAnnotationMirrors().stream())
+            .anyMatch(anm -> AnnotationUtils.annotationName(anm).endsWith(".Nullable"));
+    if (hasNullable) {
+      return false;
+    }
+    // if return type of foo() is a Guava Immutable type, not required if there is a
+    // builder method fooBuilder()
+    if (BuilderFrameworkSupportUtils.isGuavaImmutableType(returnType)
+        && avBuilderSetterNames.contains(name + "Builder")) {
+      return false;
+    }
+    // if it's an Optional, the Builder will automatically initialize it
+    if (isOptional(returnType)) {
+      return false;
+    }
+    // it's required!
+    return true;
+  }
+
+  /**
+   * Returns whether AutoValue considers a type to be "optional". Optional types do not need to be
+   * set before build is called on a builder. Adapted from AutoValue source code.
+   *
+   * @param type some type
+   * @return true if type is an Optional type
+   */
+  static boolean isOptional(TypeMirror type) {
+    if (type.getKind() != TypeKind.DECLARED) {
+      return false;
+    }
+    DeclaredType declaredType = (DeclaredType) type;
+    TypeElement typeElement = (TypeElement) declaredType.asElement();
+    // This list of classes that AutoValue considers "optional" comes from AutoValue's source code.
+    String[] optionalClassNames =
+        new String[] {
+          "com.google.common.base.Optional",
+          "java.util.Optional",
+          "java.util.OptionalDouble",
+          "java.util.OptionalInt",
+          "java.util.OptionalLong"
+        };
+    return typeElement.getTypeParameters().size() == declaredType.getTypeArguments().size()
+        && ArraysPlume.indexOf(optionalClassNames, typeElement.getQualifiedName().toString()) != -1;
+  }
+
+  /**
+   * Returns names of all setter methods.
+   *
+   * @see #isAutoValueBuilderSetter
+   * @param builderElement the element representing an AutoValue builder
+   * @return the names of setter methods for the AutoValue builder
+   */
+  private Set<String> getAutoValueBuilderSetterMethodNames(TypeElement builderElement) {
+    return getAllAbstractMethods(builderElement).stream()
+        .filter(e -> isAutoValueBuilderSetter(e, builderElement))
+        .map(e -> e.getSimpleName().toString())
+        .collect(Collectors.toSet());
+  }
+
+  /**
+   * Return true if the given method is a setter for an AutoValue builder; that is, its return type
+   * is the builder itself or a Guava Immutable type.
+   *
+   * @param method a method of a builder or one of its supertypes
+   * @param builderElement element for the AutoValue builder
+   * @return true if {@code method} is a setter for the builder
+   */
+  private boolean isAutoValueBuilderSetter(ExecutableElement method, TypeElement builderElement) {
+    TypeMirror retType = method.getReturnType();
+    if (retType.getKind() == TypeKind.TYPEVAR) {
+      // instantiate the type variable for the Builder class
+      retType =
+          AnnotatedTypes.asMemberOf(
+                  atypeFactory.getChecker().getTypeUtils(),
+                  atypeFactory,
+                  atypeFactory.getAnnotatedType(builderElement),
+                  method)
+              .getReturnType()
+              .getUnderlyingType();
+    }
+    // either the return type should be the builder itself, or it should be a Guava immutable type
+    return BuilderFrameworkSupportUtils.isGuavaImmutableType(retType)
+        || builderElement.equals(TypesUtils.getTypeElement(retType));
+  }
+
+  /**
+   * Get all the abstract methods for a class. This includes inherited abstract methods that are not
+   * overridden by the class or a superclass. There is no guarantee that this method will work as
+   * intended on code that implements an interface (which AutoValue classes are not supposed to do:
+   * https://github.com/google/auto/blob/master/value/userguide/howto.md#inherit).
+   *
+   * @param classElement the class
+   * @return list of all abstract methods
+   */
+  public List<ExecutableElement> getAllAbstractMethods(TypeElement classElement) {
+    List<TypeElement> supertypes =
+        ElementUtils.getAllSupertypes(classElement, atypeFactory.getProcessingEnv());
+    List<ExecutableElement> abstractMethods = new ArrayList<>();
+    Set<ExecutableElement> overriddenMethods = new HashSet<>();
+    for (Element t : supertypes) {
+      for (Element member : t.getEnclosedElements()) {
+        if (member.getKind() != ElementKind.METHOD) {
+          continue;
+        }
+        Set<Modifier> modifiers = member.getModifiers();
+        if (modifiers.contains(Modifier.STATIC)) {
+          continue;
+        }
+        if (modifiers.contains(Modifier.ABSTRACT)) {
+          // Make sure it's not overridden. This only works because ElementUtils#closure
+          // returns results in a particular order.
+          if (!overriddenMethods.contains(member)) {
+            abstractMethods.add((ExecutableElement) member);
+          }
+        } else {
+          // Exclude any methods that this overrides.
+          overriddenMethods.addAll(
+              AnnotatedTypes.overriddenMethods(
+                      atypeFactory.getElementUtils(), atypeFactory, (ExecutableElement) member)
+                  .values());
+        }
+      }
+    }
+    return abstractMethods;
+  }
+
+  /**
+   * Get the qualified name of the package containing AutoValue annotations. This method constructs
+   * the String dynamically, to ensure it does not get rewritten due to relocation of the {@code
+   * "com.google"} package during the build process.
+   *
+   * @return {@code "com.google.auto.value"}
+   */
+  private String getAutoValuePackageName() {
+    String com = "com";
+    return com + "." + "google.auto.value";
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupport.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupport.java
new file mode 100644
index 0000000..a00a76f
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupport.java
@@ -0,0 +1,73 @@
+package org.checkerframework.checker.calledmethods.builder;
+
+import com.sun.source.tree.NewClassTree;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+
+/**
+ * Provides hooks to add CalledMethods annotations to code generated by a builder framework like
+ * Lombok or AutoValue. If you are adding support to the Called Methods Checker for a new builder
+ * framework, you should create a subclass of this class and modify the private method {@code
+ * enableFramework} in {@link
+ * org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory}.
+ *
+ * <p>Every method in this class is permitted to do nothing (or always return false). The work that
+ * each method must do is particular to the builder framework being supported.
+ */
+public interface BuilderFrameworkSupport {
+
+  /**
+   * Determines if a method is a {@code toBuilder} method on a type generated by the builder
+   * framework.
+   *
+   * @param candidateToBuilderElement a method
+   * @return {@code true} if {@code candidateToBuilderElement} is a {@code toBuilder} method on a
+   *     type generated by the builder framework
+   */
+  boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement);
+
+  /**
+   * Hook for supporting a builder framework's {@code toBuilder} routine. Typically, the returned
+   * Builder has had all of its required setters invoked. So, implementations of this method should
+   * add a {@link org.checkerframework.checker.calledmethods.qual.CalledMethods} annotation
+   * capturing this fact.
+   *
+   * @param toBuilderType the type of a method that is the {@code toBuilder} method (as determined
+   *     by {@link #isToBuilderMethod(ExecutableElement)}) for a type that has an associated builder
+   */
+  void handleToBuilderMethod(AnnotatedExecutableType toBuilderType);
+
+  /**
+   * Determines if a method is a {@code build} method on a {@code Builder} type for the builder
+   * framework.
+   *
+   * @param candidateBuildElement a method
+   * @return {@code true} if {@code candidateBuildElement} is a {@code build} method on a {@code
+   *     Builder} type for the builder framework
+   */
+  boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement);
+
+  /**
+   * Hook for adding annotations to a build() method (i.e. a finalizer) generated by a builder
+   * framework.
+   *
+   * <p>For {@code build} methods on {@code Builder} types, implementations of this method should
+   * determine the required properties and add a corresponding {@link
+   * org.checkerframework.checker.calledmethods.qual.CalledMethods} annotation to the type of the
+   * receiver parameter.
+   *
+   * @param builderBuildType the type of a method that is the {@code build} method (as determined by
+   *     {@link #isBuilderBuildMethod(ExecutableElement)}) for a builder
+   */
+  void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType);
+
+  /**
+   * Hook for adding annotations (e.g., {@code @}{@link
+   * org.checkerframework.checker.calledmethods.qual.CalledMethods}) to a constructor call.
+   *
+   * @param tree a constructor call
+   * @param type type of the call expression
+   */
+  void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupportUtils.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupportUtils.java
new file mode 100644
index 0000000..923ce3f
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupportUtils.java
@@ -0,0 +1,32 @@
+package org.checkerframework.checker.calledmethods.builder;
+
+import javax.lang.model.type.TypeMirror;
+
+/** A utility class of static methods used in supporting builder-generation frameworks. */
+public class BuilderFrameworkSupportUtils {
+  /** This class is non-instantiable. */
+  private BuilderFrameworkSupportUtils() {
+    throw new Error("Do not instantiate");
+  }
+
+  /**
+   * Returns true if the given type is one of the immutable collections defined in
+   * com.google.common.collect.
+   *
+   * @param type a type
+   * @return true if the type is a Guava immutable collection
+   */
+  public static boolean isGuavaImmutableType(TypeMirror type) {
+    return type.toString().startsWith("com.google.common.collect.Immutable");
+  }
+
+  /**
+   * Capitalizes the first letter of the given string.
+   *
+   * @param prop a String
+   * @return the same String, but with the first character capitalized
+   */
+  public static String capitalize(String prop) {
+    return prop.substring(0, 1).toUpperCase() + prop.substring(1);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/LombokSupport.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/LombokSupport.java
new file mode 100644
index 0000000..17db34f
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/LombokSupport.java
@@ -0,0 +1,204 @@
+package org.checkerframework.checker.calledmethods.builder;
+
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.VariableTree;
+import java.beans.Introspector;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+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.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.ElementUtils;
+
+/**
+ * Lombok support for the Called Methods Checker. This class adds CalledMethods annotations to the
+ * code generated by Lombok.
+ */
+public class LombokSupport implements BuilderFrameworkSupport {
+
+  /** The type factory. */
+  private CalledMethodsAnnotatedTypeFactory atypeFactory;
+
+  /**
+   * Create a new LombokSupport.
+   *
+   * @param atypeFactory the typechecker's type factory
+   */
+  public LombokSupport(CalledMethodsAnnotatedTypeFactory atypeFactory) {
+    this.atypeFactory = atypeFactory;
+  }
+
+  // The list is copied from lombok.core.handlers.HandlerUtil. The list cannot be used from that
+  // class directly because Lombok does not provide class files for its own implementation, to
+  // prevent itself from being accidentally added to clients' compile classpaths. This design
+  // decision means that it is impossible to depend directly on Lombok internals.
+  /** The list of annotations that Lombok treats as non-null. */
+  public static final List<String> NONNULL_ANNOTATIONS =
+      Collections.unmodifiableList(
+          Arrays.asList(
+              "android.annotation.NonNull",
+              "android.support.annotation.NonNull",
+              "com.sun.istack.internal.NotNull",
+              "edu.umd.cs.findbugs.annotations.NonNull",
+              "javax.annotation.Nonnull",
+              // "javax.validation.constraints.NotNull", // The field might contain a
+              // null value until it is persisted.
+              "lombok.NonNull",
+              "org.checkerframework.checker.nullness.qual.NonNull",
+              "org.eclipse.jdt.annotation.NonNull",
+              "org.eclipse.jgit.annotations.NonNull",
+              "org.jetbrains.annotations.NotNull",
+              "org.jmlspecs.annotation.NonNull",
+              "org.netbeans.api.annotations.common.NonNull",
+              "org.springframework.lang.NonNull"));
+
+  /**
+   * A map from elements that have a lombok.Builder.Default annotation to the simple property name
+   * that should be treated as defaulted.
+   *
+   * <p>This cache is kept because the usual method for checking that an element has been defaulted
+   * (calling declarationFromElement and examining the resulting VariableTree) only works if a
+   * corresponding Tree is available (for code that is only available as bytecode, no such Tree is
+   * available and that method returns null). See the code in {@link
+   * #getLombokRequiredProperties(Element)} that handles fields.
+   */
+  private final Map<Element, Name> defaultedElements = new HashMap<>(2);
+
+  @Override
+  public boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement) {
+    TypeElement candidateGeneratedBuilderElement =
+        (TypeElement) candidateBuildElement.getEnclosingElement();
+
+    if ((ElementUtils.hasAnnotation(candidateGeneratedBuilderElement, "lombok.Generated")
+            || ElementUtils.hasAnnotation(candidateBuildElement, "lombok.Generated"))
+        && candidateGeneratedBuilderElement.getSimpleName().toString().endsWith("Builder")) {
+      return candidateBuildElement.getSimpleName().contentEquals("build");
+    }
+    return false;
+  }
+
+  @Override
+  public void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType) {
+    ExecutableElement buildElement = builderBuildType.getElement();
+
+    TypeElement generatedBuilderElement = (TypeElement) buildElement.getEnclosingElement();
+    // The class with the @lombok.Builder annotation...
+    Element annotatedWithBuilderElement = generatedBuilderElement.getEnclosingElement();
+
+    List<String> requiredProperties = getLombokRequiredProperties(annotatedWithBuilderElement);
+    AnnotationMirror newCalledMethodsAnno =
+        atypeFactory.createAccumulatorAnnotation(requiredProperties);
+    builderBuildType.getReceiverType().addAnnotation(newCalledMethodsAnno);
+  }
+
+  @Override
+  public boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement) {
+    return candidateToBuilderElement.getSimpleName().contentEquals("toBuilder")
+        && (ElementUtils.hasAnnotation(candidateToBuilderElement, "lombok.Generated")
+            || ElementUtils.hasAnnotation(
+                candidateToBuilderElement.getEnclosingElement(), "lombok.Generated"));
+  }
+
+  @Override
+  public void handleToBuilderMethod(AnnotatedExecutableType toBuilderType) {
+    AnnotatedTypeMirror returnType = toBuilderType.getReturnType();
+    ExecutableElement buildElement = toBuilderType.getElement();
+    TypeElement generatedBuilderElement = (TypeElement) buildElement.getEnclosingElement();
+    handleToBuilderType(returnType, generatedBuilderElement);
+  }
+
+  /**
+   * Add, to a type, a CalledMethods annotation that states that all required setters have been
+   * called. The type can be the return type of toBuilder or of the corresponding generated "copy"
+   * constructor.
+   *
+   * @param type type to update
+   * @param classElement corresponding AutoValue class
+   */
+  private void handleToBuilderType(AnnotatedTypeMirror type, Element classElement) {
+    List<String> requiredProperties = getLombokRequiredProperties(classElement);
+    AnnotationMirror calledMethodsAnno =
+        atypeFactory.createAccumulatorAnnotation(requiredProperties);
+    type.replaceAnnotation(calledMethodsAnno);
+  }
+
+  /**
+   * Computes the required properties of a @lombok.Builder class, i.e., the names of the fields
+   * with @lombok.NonNull annotations.
+   *
+   * @param lombokClassElement the class with the @lombok.Builder annotation
+   * @return a list of required property names
+   */
+  private List<String> getLombokRequiredProperties(final Element lombokClassElement) {
+    List<String> requiredPropertyNames = new ArrayList<>();
+    List<String> defaultedPropertyNames = new ArrayList<>();
+    for (Element member : lombokClassElement.getEnclosedElements()) {
+      if (member.getKind() == ElementKind.FIELD) {
+        // Lombok never generates non-null fields with initializers in builders, unless the field is
+        // annotated with @Default or @Singular, which are handled elsewhere.  So, this code doesn't
+        // need to consider whether the field has or does not have initializers.
+        for (AnnotationMirror anm :
+            atypeFactory.getElementUtils().getAllAnnotationMirrors(member)) {
+          if (NONNULL_ANNOTATIONS.contains(AnnotationUtils.annotationName(anm))) {
+            requiredPropertyNames.add(member.getSimpleName().toString());
+          }
+        }
+      } else if (member.getKind() == ElementKind.METHOD
+          && ElementUtils.hasAnnotation(member, "lombok.Generated")) {
+        String methodName = member.getSimpleName().toString();
+        // If a field foo has an @Builder.Default annotation, Lombok always generates a
+        // method called $default$foo.
+        if (methodName.startsWith("$default$")) {
+          String propName = methodName.substring(9); // $default$ has 9 characters
+          defaultedPropertyNames.add(propName);
+        }
+      } else if (member.getKind().isClass() && member.toString().endsWith("Builder")) {
+        // Note that the test above will fail to catch builders generated by Lombok that have custom
+        // names using the builderClassName attribute. TODO: find a way to handle such builders too.
+
+        // If a field bar has an @Singular annotation, Lombok always generates a method called
+        // clearBar in the builder class itself. Therefore, search the builder for such a method,
+        // and extract the appropriate property name to treat as defaulted.
+        for (Element builderMember : member.getEnclosedElements()) {
+          if (builderMember.getKind() == ElementKind.METHOD
+              && ElementUtils.hasAnnotation(builderMember, "lombok.Generated")) {
+            String methodName = builderMember.getSimpleName().toString();
+            if (methodName.startsWith("clear")) {
+              String propName =
+                  Introspector.decapitalize(methodName.substring(5)); // clear has 5 characters
+              defaultedPropertyNames.add(propName);
+            }
+          } else if (builderMember.getKind() == ElementKind.FIELD) {
+            VariableTree variableTree =
+                (VariableTree) atypeFactory.declarationFromElement(builderMember);
+            if (variableTree != null && variableTree.getInitializer() != null) {
+              Name propName = variableTree.getName();
+              defaultedPropertyNames.add(propName.toString());
+              defaultedElements.put(builderMember, propName);
+            } else if (defaultedElements.containsKey(builderMember)) {
+              defaultedPropertyNames.add(defaultedElements.get(builderMember).toString());
+            }
+          }
+        }
+      }
+    }
+    requiredPropertyNames.removeAll(defaultedPropertyNames);
+    return requiredPropertyNames;
+  }
+
+  @Override
+  public void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type) {
+    return;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/messages.properties b/checker/src/main/java/org/checkerframework/checker/calledmethods/messages.properties
new file mode 100644
index 0000000..7284bb8
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/messages.properties
@@ -0,0 +1 @@
+finalizer.invocation=This finalizer cannot be invoked, because the following methods have not been called: %s
diff --git a/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesAnnotatedTypeFactory.java
new file mode 100644
index 0000000..c324d3c
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesAnnotatedTypeFactory.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.compilermsgs;
+
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.propkey.PropertyKeyAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+
+/** A PropertyKeyATF that uses CompilerMessageKey to annotate the keys. */
+public class CompilerMessagesAnnotatedTypeFactory extends PropertyKeyAnnotatedTypeFactory {
+
+  public CompilerMessagesAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    // Does not call postInit() because its superclass does.
+    // If we ever add code to this constructor, it needs to:
+    //   * call a superclass constructor that does not call postInit(), and
+    //   * call postInit() itself.
+  }
+
+  @Override
+  public TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(
+        super.createBasicTreeAnnotator(),
+        new KeyLookupTreeAnnotator(this, CompilerMessageKey.class));
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesChecker.java b/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesChecker.java
new file mode 100644
index 0000000..063b410
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesChecker.java
@@ -0,0 +1,6 @@
+package org.checkerframework.checker.compilermsgs;
+
+import org.checkerframework.checker.propkey.PropertyKeyChecker;
+
+/** A PropertyKeyChecker for the compiler message keys that are used in the Checker Framework. */
+public class CompilerMessagesChecker extends PropertyKeyChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/compilermsgs/messages.properties b/checker/src/main/java/org/checkerframework/checker/compilermsgs/messages.properties
new file mode 100644
index 0000000..9e7a166
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/compilermsgs/messages.properties
@@ -0,0 +1 @@
+type.incompatible=Could not identify a correct compiler message key!%n%nFound a %s, but required a %s.%nEnsure that the String is contained in the message property file or add a @CompilerMessageKey annotation.
diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/FenumAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/fenum/FenumAnnotatedTypeFactory.java
new file mode 100644
index 0000000..1c0c68d
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/fenum/FenumAnnotatedTypeFactory.java
@@ -0,0 +1,172 @@
+package org.checkerframework.checker.fenum;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.fenum.qual.Fenum;
+import org.checkerframework.checker.fenum.qual.FenumBottom;
+import org.checkerframework.checker.fenum.qual.FenumTop;
+import org.checkerframework.checker.fenum.qual.FenumUnqualified;
+import org.checkerframework.checker.fenum.qual.PolyFenum;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.util.DefaultQualifierKindHierarchy;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.framework.util.QualifierKindHierarchy;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.UserError;
+import org.plumelib.reflection.Signatures;
+
+/** The type factory for the Fenum Checker. */
+public class FenumAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** AnnotationMirror for {@link FenumUnqualified}. */
+  protected AnnotationMirror FENUM_UNQUALIFIED;
+  /** AnnotationMirror for {@link FenumBottom}. */
+  protected AnnotationMirror FENUM_BOTTOM;
+  /** AnnotationMirror for {@link FenumTop}. */
+  protected AnnotationMirror FENUM_TOP;
+
+  /**
+   * Create a FenumAnnotatedTypeFactory.
+   *
+   * @param checker checker
+   */
+  public FenumAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    FENUM_BOTTOM = AnnotationBuilder.fromClass(elements, FenumBottom.class);
+    FENUM_UNQUALIFIED = AnnotationBuilder.fromClass(elements, FenumUnqualified.class);
+    FENUM_TOP = AnnotationBuilder.fromClass(elements, FenumTop.class);
+
+    this.postInit();
+  }
+
+  /**
+   * Copied from SubtypingChecker. Instead of returning an empty set if no "quals" option is given,
+   * we return Fenum as the only qualifier.
+   *
+   * @return the supported type qualifiers
+   */
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    // Load everything in qual directory, and top, bottom, unqualified, and fake enum
+    Set<Class<? extends Annotation>> qualSet =
+        getBundledTypeQualifiers(
+            FenumTop.class,
+            Fenum.class,
+            FenumUnqualified.class,
+            FenumBottom.class,
+            PolyFenum.class);
+
+    // Load externally defined quals given in the -Aquals and/or -AqualDirs options
+    String qualNames = checker.getOption("quals");
+    String qualDirectories = checker.getOption("qualDirs");
+
+    // load individually named qualifiers
+    if (qualNames != null) {
+      for (String qualName : qualNames.split(",")) {
+        if (!Signatures.isBinaryName(qualName)) {
+          throw new UserError("Malformed qualifier \"%s\" in -Aquals=%s", qualName, qualNames);
+        }
+        qualSet.add(loader.loadExternalAnnotationClass(qualName));
+      }
+    }
+
+    // load directories of qualifiers
+    if (qualDirectories != null) {
+      for (String dirName : qualDirectories.split(":")) {
+        qualSet.addAll(loader.loadExternalAnnotationClassesFromDirectory(dirName));
+      }
+    }
+
+    // TODO: warn if no qualifiers given?
+    // Just Fenum("..") is still valid, though...
+    return qualSet;
+  }
+
+  @Override
+  public QualifierHierarchy createQualifierHierarchy() {
+    return new FenumQualifierHierarchy(getSupportedTypeQualifiers(), elements);
+  }
+
+  /** Fenum qualifier hierarchy. */
+  protected class FenumQualifierHierarchy extends MostlyNoElementQualifierHierarchy {
+
+    /** QualifierKind for {@link Fenum} qualifier. */
+    private final QualifierKind FENUM_KIND;
+
+    /**
+     * Creates FenumQualifierHierarchy.
+     *
+     * @param qualifierClasses qualifier classes
+     * @param elements element utils
+     */
+    public FenumQualifierHierarchy(
+        Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+      this.FENUM_KIND =
+          this.qualifierKindHierarchy.getQualifierKind(Fenum.class.getCanonicalName());
+    }
+
+    @Override
+    protected QualifierKindHierarchy createQualifierKindHierarchy(
+        @UnderInitialization FenumQualifierHierarchy this,
+        Collection<Class<? extends Annotation>> qualifierClasses) {
+      return new DefaultQualifierKindHierarchy(qualifierClasses, FenumBottom.class);
+    }
+
+    @Override
+    protected boolean isSubtypeWithElements(
+        AnnotationMirror subAnno,
+        QualifierKind subKind,
+        AnnotationMirror superAnno,
+        QualifierKind superKind) {
+      return AnnotationUtils.areSame(subAnno, superAnno);
+    }
+
+    @Override
+    protected AnnotationMirror leastUpperBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind lubKind) {
+      if (qualifierKind1 == FENUM_KIND && qualifierKind2 == FENUM_KIND) {
+        if (AnnotationUtils.areSame(a1, a2)) {
+          return a1;
+        }
+        return FENUM_TOP;
+      } else if (qualifierKind1 == FENUM_KIND) {
+        return a1;
+      } else if (qualifierKind2 == FENUM_KIND) {
+        return a2;
+      }
+      throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2);
+    }
+
+    @Override
+    protected AnnotationMirror greatestLowerBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind glbKind) {
+      if (qualifierKind1 == FENUM_KIND && qualifierKind2 == FENUM_KIND) {
+        return FENUM_BOTTOM;
+      } else if (qualifierKind1 == FENUM_KIND) {
+        return a2;
+      } else if (qualifierKind2 == FENUM_KIND) {
+        return a1;
+      }
+      throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2);
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/FenumChecker.java b/checker/src/main/java/org/checkerframework/checker/fenum/FenumChecker.java
new file mode 100644
index 0000000..b00ed9a
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/fenum/FenumChecker.java
@@ -0,0 +1,33 @@
+package org.checkerframework.checker.fenum;
+
+import java.util.SortedSet;
+import javax.annotation.processing.SupportedOptions;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.subtyping.SubtypingChecker;
+import org.checkerframework.framework.qual.StubFiles;
+
+/**
+ * The main checker class for the Fake Enum Checker.
+ *
+ * <p>There are two options to distinguish different enumerators:
+ *
+ * <ol>
+ *   <li>{@code @Fenum("Name")}: introduces a fake enumerator with the name "Name". Enumerators with
+ *       different names are distinct. The default name is empty, but you are encouraged to use a
+ *       unique name for your purpose.
+ *   <li>Alternatively, you can specify the annotation to use with the {@code -Aqual} command line
+ *       argument.
+ * </ol>
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+@StubFiles("jdnc.astub")
+@SupportedOptions({"quals", "qualDirs"})
+public class FenumChecker extends BaseTypeChecker {
+
+  @Override
+  public SortedSet<String> getSuppressWarningsPrefixes() {
+    return SubtypingChecker.getSuppressWarningsPrefixes(
+        this.visitor, super.getSuppressWarningsPrefixes());
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/FenumVisitor.java b/checker/src/main/java/org/checkerframework/checker/fenum/FenumVisitor.java
new file mode 100644
index 0000000..d986cce
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/fenum/FenumVisitor.java
@@ -0,0 +1,91 @@
+package org.checkerframework.checker.fenum;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CaseTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.SwitchTree;
+import com.sun.source.tree.Tree;
+import java.util.Collections;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.TreeUtils;
+
+public class FenumVisitor extends BaseTypeVisitor<FenumAnnotatedTypeFactory> {
+  public FenumVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  public Void visitBinary(BinaryTree node, Void p) {
+    if (!TreeUtils.isStringConcatenation(node)) {
+      // TODO: ignore string concatenations
+
+      // The Fenum Checker is only concerned with primitive types, so just check that
+      // the primary annotations are equivalent.
+      AnnotatedTypeMirror lhsAtm = atypeFactory.getAnnotatedType(node.getLeftOperand());
+      AnnotatedTypeMirror rhsAtm = atypeFactory.getAnnotatedType(node.getRightOperand());
+
+      Set<AnnotationMirror> lhs = lhsAtm.getEffectiveAnnotations();
+      Set<AnnotationMirror> rhs = rhsAtm.getEffectiveAnnotations();
+      QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy();
+      if (!(qualHierarchy.isSubtype(lhs, rhs) || qualHierarchy.isSubtype(rhs, lhs))) {
+        checker.reportError(node, "binary", lhsAtm, rhsAtm);
+      }
+    }
+    return super.visitBinary(node, p);
+  }
+
+  @Override
+  public Void visitSwitch(SwitchTree node, Void p) {
+    ExpressionTree expr = node.getExpression();
+    AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(expr);
+
+    for (CaseTree caseExpr : node.getCases()) {
+      ExpressionTree realCaseExpr = caseExpr.getExpression();
+      if (realCaseExpr != null) {
+        AnnotatedTypeMirror caseType = atypeFactory.getAnnotatedType(realCaseExpr);
+
+        // There is currently no "switch" message key, so it is treated
+        // identically to "type.incompatible".
+        this.commonAssignmentCheck(exprType, caseType, caseExpr, "type.incompatible");
+      }
+    }
+    return super.visitSwitch(node, p);
+  }
+
+  @Override
+  protected void checkConstructorInvocation(
+      AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, NewClassTree src) {
+    // Ignore the default annotation on the constructor
+  }
+
+  @Override
+  protected void checkConstructorResult(
+      AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {
+    // Skip this check
+  }
+
+  @Override
+  protected Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotations() {
+    return Collections.singleton(atypeFactory.FENUM_UNQUALIFIED);
+  }
+
+  // TODO: should we require a match between switch expression and cases?
+
+  @Override
+  public boolean isValidUse(
+      AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) {
+    // The checker calls this method to compare the annotation used in a type to the modifier it
+    // adds to the class declaration. As our default modifier is FenumBottom, this results in an
+    // error when a non-subtype is used. Can we use FenumTop as default instead?
+    return true;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/jdk8.astub b/checker/src/main/java/org/checkerframework/checker/fenum/jdk8.astub
new file mode 100644
index 0000000..5175fba
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/fenum/jdk8.astub
@@ -0,0 +1,72 @@
+// This file is for classes that appear in JDK 8 but not in JDK 11.
+
+import org.checkerframework.checker.fenum.qual.*;
+
+package javax.swing;
+
+interface SwingConstants {
+  //
+  // WMD adds this new constant to use instead of literally using -1.
+  //
+  @SwingCompassDirection
+  @SwingHorizontalOrientation
+  @SwingVerticalOrientation
+  public static final int NOTSET = -1;
+
+  //
+  // The central position in an area. Used for
+  // both compass-direction constants (NORTH, etc.)
+  // and box-orientation constants (TOP, etc.).
+  //
+  @SwingCompassDirection
+  @SwingHorizontalOrientation
+  @SwingVerticalOrientation
+  int CENTER  = 0;
+
+  //
+  // Box-orientation constant used to specify locations in a box.
+  //
+  @SwingVerticalOrientation int TOP     = 1;
+  @SwingHorizontalOrientation int LEFT    = 2;
+  @SwingVerticalOrientation int BOTTOM  = 3;
+  @SwingHorizontalOrientation int RIGHT   = 4;
+
+  //
+  // Compass-direction constants used to specify a position.
+  //
+  @SwingCompassDirection int NORTH      = 1;
+  @SwingCompassDirection int NORTH_EAST = 2;
+  @SwingCompassDirection int EAST       = 3;
+  @SwingCompassDirection int SOUTH_EAST = 4;
+  @SwingCompassDirection int SOUTH      = 5;
+  @SwingCompassDirection int SOUTH_WEST = 6;
+  @SwingCompassDirection int WEST       = 7;
+  @SwingCompassDirection int NORTH_WEST = 8;
+
+  //
+  // These constants specify a horizontal or
+  // vertical orientation. For example, they are
+  // used by scrollbars and sliders.
+  //
+  @SwingElementOrientation int HORIZONTAL = 0;
+  @SwingElementOrientation int VERTICAL   = 1;
+
+  //
+  // Constants for orientation support, since some languages are
+  // left-to-right oriented and some are right-to-left oriented.
+  // This orientation is currently used by buttons and labels.
+  //
+  @SwingTextOrientation @SwingHorizontalOrientation int LEADING  = 10;
+  @SwingTextOrientation @SwingHorizontalOrientation int TRAILING = 11;
+
+  @SwingTextOrientation int NEXT = 12;
+  @SwingTextOrientation int PREVIOUS = 13;
+}
+
+
+package java.beans;
+
+class PropertyChangeSupport {
+    public void firePropertyChange(String propertyName, @FenumTop int oldValue, @FenumTop int newValue);
+    public void firePropertyChange(String propertyName, @FenumTop Object oldValue, @FenumTop Object newValue);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/jdnc.astub b/checker/src/main/java/org/checkerframework/checker/fenum/jdnc.astub
new file mode 100644
index 0000000..ada6f20
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/fenum/jdnc.astub
@@ -0,0 +1,7 @@
+import org.checkerframework.checker.fenum.qual.*;
+
+package org.jdesktop.jdnc;
+
+class JNTable {
+  void setColumnHorizontalAlignment(String columnName, @SwingHorizontalOrientation int alignment);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/messages.properties b/checker/src/main/java/org/checkerframework/checker/fenum/messages.properties
new file mode 100644
index 0000000..ba7a5e7
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/fenum/messages.properties
@@ -0,0 +1,2 @@
+binary=binary operation between incompatible fenums: %s and %s
+compound.assignment=compound assignment between incompatible fenums: %s and %s
diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/package-info.java b/checker/src/main/java/org/checkerframework/checker/fenum/package-info.java
new file mode 100644
index 0000000..93814a9
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/fenum/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * The implementation of the Fake Enum Checker.
+ *
+ * @checker_framework.manual #fenum-checker Fake Enum Checker
+ */
+package org.checkerframework.checker.fenum;
diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java
new file mode 100644
index 0000000..d33c2ff
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java
@@ -0,0 +1,327 @@
+package org.checkerframework.checker.formatter;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.util.IllegalFormatException;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.formatter.qual.ConversionCategory;
+import org.checkerframework.checker.formatter.qual.Format;
+import org.checkerframework.checker.formatter.qual.FormatBottom;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.checker.formatter.qual.InvalidFormat;
+import org.checkerframework.checker.formatter.qual.UnknownFormat;
+import org.checkerframework.checker.formatter.util.FormatUtil;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.wholeprograminference.WholeProgramInferenceJavaParserStorage;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import scenelib.annotations.Annotation;
+import scenelib.annotations.el.AField;
+import scenelib.annotations.el.AMethod;
+
+/**
+ * Adds {@link Format} to the type of tree, if it is a {@code String} or {@code char} literal that
+ * represents a satisfiable format. The annotation's value is set to be a list of appropriate {@link
+ * ConversionCategory} values for every parameter of the format.
+ *
+ * @see ConversionCategory
+ */
+public class FormatterAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** The @{@link UnknownFormat} annotation. */
+  protected final AnnotationMirror UNKNOWNFORMAT =
+      AnnotationBuilder.fromClass(elements, UnknownFormat.class);
+  /** The @{@link FormatBottom} annotation. */
+  protected final AnnotationMirror FORMATBOTTOM =
+      AnnotationBuilder.fromClass(elements, FormatBottom.class);
+  /** The @{@link FormatMethod} annotation. */
+  protected final AnnotationMirror FORMATMETHOD =
+      AnnotationBuilder.fromClass(elements, FormatMethod.class);
+
+  /** The fully-qualified name of the {@link Format} qualifier. */
+  protected static final @CanonicalName String FORMAT_NAME = Format.class.getCanonicalName();
+  /** The fully-qualified name of the {@link InvalidFormat} qualifier. */
+  protected static final @CanonicalName String INVALIDFORMAT_NAME =
+      InvalidFormat.class.getCanonicalName();
+
+  /** Syntax tree utilities. */
+  protected final FormatterTreeUtil treeUtil = new FormatterTreeUtil(checker);
+
+  /** Creates a FormatterAnnotatedTypeFactory. */
+  public FormatterAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    addAliasedDeclAnnotation(
+        com.google.errorprone.annotations.FormatMethod.class, FormatMethod.class, FORMATMETHOD);
+
+    this.postInit();
+  }
+
+  @Override
+  public QualifierHierarchy createQualifierHierarchy() {
+    return new FormatterQualifierHierarchy();
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(super.createTreeAnnotator(), new FormatterTreeAnnotator(this));
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>If a method is annotated with {@code @FormatMethod}, remove any {@code @Format} annotation
+   * from its first argument.
+   */
+  @Override
+  public void prepareMethodForWriting(AMethod method) {
+    if (hasFormatMethodAnno(method)) {
+      AField param = method.parameters.get(0);
+      if (param != null) {
+        Set<Annotation> paramTypeAnnos = param.type.tlAnnotationsHere;
+        paramTypeAnnos.removeIf(
+            a -> a.def.name.equals("org.checkerframework.checker.formatter.qual.Format"));
+      }
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>If a method is annotated with {@code @FormatMethod}, remove any {@code @Format} annotation
+   * from its first argument.
+   */
+  @Override
+  public void prepareMethodForWriting(
+      WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) {
+    if (hasFormatMethodAnno(methodAnnos)) {
+      AnnotatedTypeMirror atm = methodAnnos.getParameterType(0);
+      atm.removeAnnotationByClass(org.checkerframework.checker.formatter.qual.Format.class);
+    }
+  }
+
+  /**
+   * Returns true if the method has a {@code @FormatMethod} annotation.
+   *
+   * @param methodAnnos method annotations
+   * @return true if the method has a {@code @FormatMethod} annotation
+   */
+  private boolean hasFormatMethodAnno(AMethod methodAnnos) {
+    for (Annotation anno : methodAnnos.tlAnnotationsHere) {
+      String annoName = anno.def.name;
+      if (annoName.equals("org.checkerframework.checker.formatter.qual.FormatMethod")
+          || anno.def.name.equals("com.google.errorprone.annotations.FormatMethod")) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if the method has a {@code @FormatMethod} annotation.
+   *
+   * @param methodAnnos method annotations
+   * @return true if the method has a {@code @FormatMethod} annotation
+   */
+  private boolean hasFormatMethodAnno(
+      WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) {
+    Set<AnnotationMirror> declarationAnnos = methodAnnos.getDeclarationAnnotations();
+    return AnnotationUtils.containsSameByClass(
+            declarationAnnos, org.checkerframework.checker.formatter.qual.FormatMethod.class)
+        || AnnotationUtils.containsSameByName(
+            declarationAnnos, "com.google.errorprone.annotations.FormatMethod");
+  }
+
+  /** The tree annotator for the Format String Checker. */
+  private class FormatterTreeAnnotator extends TreeAnnotator {
+    /**
+     * Create the tree annotator for the Format String Checker.
+     *
+     * @param atypeFactory the Format String Checker type factory
+     */
+    public FormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
+      if (!type.isAnnotatedInHierarchy(UNKNOWNFORMAT)) {
+        String format = null;
+        if (tree.getKind() == Tree.Kind.STRING_LITERAL) {
+          format = (String) tree.getValue();
+        } else if (tree.getKind() == Tree.Kind.CHAR_LITERAL) {
+          format = Character.toString((Character) tree.getValue());
+        }
+        if (format != null) {
+          AnnotationMirror anno;
+          try {
+            ConversionCategory[] cs = FormatUtil.formatParameterCategories(format);
+            anno = FormatterAnnotatedTypeFactory.this.treeUtil.categoriesToFormatAnnotation(cs);
+          } catch (IllegalFormatException e) {
+            anno =
+                FormatterAnnotatedTypeFactory.this.treeUtil.exceptionToInvalidFormatAnnotation(e);
+          }
+          type.addAnnotation(anno);
+        }
+      }
+      return super.visitLiteral(tree, type);
+    }
+  }
+
+  /** Qualifier hierarchy for the Formatter Checker. */
+  class FormatterQualifierHierarchy extends MostlyNoElementQualifierHierarchy {
+
+    /** Qualifier kind for the @{@link Format} annotation. */
+    private final QualifierKind FORMAT_KIND;
+
+    /** Qualifier kind for the @{@link InvalidFormat} annotation. */
+    private final QualifierKind INVALIDFORMAT_KIND;
+
+    /** Creates a {@link FormatterQualifierHierarchy}. */
+    public FormatterQualifierHierarchy() {
+      super(FormatterAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), elements);
+      FORMAT_KIND = getQualifierKind(FORMAT_NAME);
+      INVALIDFORMAT_KIND = getQualifierKind(INVALIDFORMAT_NAME);
+    }
+
+    @Override
+    protected boolean isSubtypeWithElements(
+        AnnotationMirror subAnno,
+        QualifierKind subKind,
+        AnnotationMirror superAnno,
+        QualifierKind superKind) {
+      if (subKind == FORMAT_KIND && superKind == FORMAT_KIND) {
+        ConversionCategory[] rhsArgTypes = treeUtil.formatAnnotationToCategories(subAnno);
+        ConversionCategory[] lhsArgTypes = treeUtil.formatAnnotationToCategories(superAnno);
+
+        if (rhsArgTypes.length > lhsArgTypes.length) {
+          return false;
+        }
+
+        for (int i = 0; i < rhsArgTypes.length; ++i) {
+          if (!ConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) {
+            return false;
+          }
+        }
+        return true;
+      } else if (subKind == INVALIDFORMAT_KIND && superKind == INVALIDFORMAT_KIND) {
+        return true;
+      }
+      throw new BugInCF("Unexpected kinds: %s %s", subKind, superKind);
+    }
+
+    @Override
+    protected AnnotationMirror leastUpperBoundWithElements(
+        AnnotationMirror anno1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror anno2,
+        QualifierKind qualifierKind2,
+        QualifierKind lubKind) {
+      if (qualifierKind1.isBottom()) {
+        return anno2;
+      } else if (qualifierKind2.isBottom()) {
+        return anno1;
+      } else if (qualifierKind1 == FORMAT_KIND && qualifierKind2 == FORMAT_KIND) {
+        ConversionCategory[] shorterArgTypesList = treeUtil.formatAnnotationToCategories(anno1);
+        ConversionCategory[] longerArgTypesList = treeUtil.formatAnnotationToCategories(anno2);
+        if (shorterArgTypesList.length > longerArgTypesList.length) {
+          ConversionCategory[] temp = longerArgTypesList;
+          longerArgTypesList = shorterArgTypesList;
+          shorterArgTypesList = temp;
+        }
+
+        // From the manual:
+        // It is legal to use a format string with fewer format specifiers
+        // than required, but a warning is issued.
+
+        ConversionCategory[] resultArgTypes = new ConversionCategory[longerArgTypesList.length];
+
+        for (int i = 0; i < shorterArgTypesList.length; ++i) {
+          resultArgTypes[i] =
+              ConversionCategory.intersect(shorterArgTypesList[i], longerArgTypesList[i]);
+        }
+        for (int i = shorterArgTypesList.length; i < longerArgTypesList.length; ++i) {
+          resultArgTypes[i] = longerArgTypesList[i];
+        }
+        return treeUtil.categoriesToFormatAnnotation(resultArgTypes);
+      } else if (qualifierKind1 == INVALIDFORMAT_KIND && qualifierKind2 == INVALIDFORMAT_KIND) {
+
+        assert !anno1.getElementValues().isEmpty();
+        assert !anno2.getElementValues().isEmpty();
+
+        if (AnnotationUtils.areSame(anno1, anno2)) {
+          return anno1;
+        }
+
+        return treeUtil.stringToInvalidFormatAnnotation(
+            "("
+                + treeUtil.invalidFormatAnnotationToErrorMessage(anno1)
+                + " or "
+                + treeUtil.invalidFormatAnnotationToErrorMessage(anno2)
+                + ")");
+      }
+
+      return UNKNOWNFORMAT;
+    }
+
+    @Override
+    protected AnnotationMirror greatestLowerBoundWithElements(
+        AnnotationMirror anno1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror anno2,
+        QualifierKind qualifierKind2,
+        QualifierKind glbKind) {
+      if (qualifierKind1.isTop()) {
+        return anno2;
+      } else if (qualifierKind2.isTop()) {
+        return anno1;
+      } else if (qualifierKind1 == FORMAT_KIND && qualifierKind2 == FORMAT_KIND) {
+        ConversionCategory[] anno1ArgTypes = treeUtil.formatAnnotationToCategories(anno1);
+        ConversionCategory[] anno2ArgTypes = treeUtil.formatAnnotationToCategories(anno2);
+
+        // From the manual:
+        // It is legal to use a format string with fewer format specifiers
+        // than required, but a warning is issued.
+        int length = anno1ArgTypes.length;
+        if (anno2ArgTypes.length < length) {
+          length = anno2ArgTypes.length;
+        }
+
+        ConversionCategory[] anno3ArgTypes = new ConversionCategory[length];
+
+        for (int i = 0; i < length; ++i) {
+          anno3ArgTypes[i] = ConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]);
+        }
+        return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes);
+      } else if (qualifierKind1 == INVALIDFORMAT_KIND && qualifierKind2 == INVALIDFORMAT_KIND) {
+
+        assert !anno1.getElementValues().isEmpty();
+        assert !anno2.getElementValues().isEmpty();
+
+        if (AnnotationUtils.areSame(anno1, anno2)) {
+          return anno1;
+        }
+
+        return treeUtil.stringToInvalidFormatAnnotation(
+            "("
+                + treeUtil.invalidFormatAnnotationToErrorMessage(anno1)
+                + " and "
+                + treeUtil.invalidFormatAnnotationToErrorMessage(anno2)
+                + ")");
+      }
+
+      return FORMATBOTTOM;
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterChecker.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterChecker.java
new file mode 100644
index 0000000..c6e109e
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterChecker.java
@@ -0,0 +1,14 @@
+package org.checkerframework.checker.formatter;
+
+import org.checkerframework.checker.formatter.qual.Format;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+
+/**
+ * A type-checker plug-in for the {@link Format} qualifier that finds syntactically invalid
+ * formatter calls.
+ *
+ * @checker_framework.manual #formatter-checker Format String Checker
+ */
+@RelevantJavaTypes(CharSequence.class)
+public class FormatterChecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTransfer.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTransfer.java
new file mode 100644
index 0000000..3b235f9
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTransfer.java
@@ -0,0 +1,47 @@
+package org.checkerframework.checker.formatter;
+
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.formatter.FormatterTreeUtil.Result;
+import org.checkerframework.checker.formatter.qual.ConversionCategory;
+import org.checkerframework.checker.formatter.util.FormatUtil;
+import org.checkerframework.dataflow.analysis.RegularTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+
+public class FormatterTransfer extends CFTransfer {
+
+  public FormatterTransfer(CFAnalysis analysis) {
+    super(analysis);
+  }
+
+  /**
+   * Makes it so that the {@link FormatUtil#asFormat} method returns a correctly annotated String.
+   */
+  @Override
+  public TransferResult<CFValue, CFStore> visitMethodInvocation(
+      MethodInvocationNode node, TransferInput<CFValue, CFStore> in) {
+    FormatterAnnotatedTypeFactory atypeFactory =
+        (FormatterAnnotatedTypeFactory) analysis.getTypeFactory();
+    TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(node, in);
+    FormatterTreeUtil tu = atypeFactory.treeUtil;
+
+    if (tu.isAsFormatCall(node, atypeFactory)) {
+      Result<ConversionCategory[]> cats = tu.asFormatCallCategories(node);
+      if (cats.value() == null) {
+        tu.failure(cats, "format.asformat.indirect.arguments");
+      } else {
+        AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value());
+        CFValue newResultValue =
+            analysis.createSingleAnnotationValue(anno, result.getResultValue().getUnderlyingType());
+        return new RegularTransferResult<>(newResultValue, result.getRegularStore());
+      }
+    }
+
+    return result;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java
new file mode 100644
index 0000000..c8816ec
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java
@@ -0,0 +1,494 @@
+package org.checkerframework.checker.formatter;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.util.SimpleTreeVisitor;
+import java.util.IllegalFormatException;
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.NullType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.SimpleTypeVisitor7;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.formatter.qual.ConversionCategory;
+import org.checkerframework.checker.formatter.qual.Format;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.checker.formatter.qual.InvalidFormat;
+import org.checkerframework.checker.formatter.qual.ReturnsFormat;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.cfg.node.ArrayCreationNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * This class provides a collection of utilities to ease working with syntax trees that have
+ * something to do with Formatters.
+ */
+public class FormatterTreeUtil {
+  /** The checker. */
+  public final BaseTypeChecker checker;
+  /** The processing environment. */
+  public final ProcessingEnvironment processingEnv;
+
+  /** The value() element/field of an @Format annotation. */
+  protected final ExecutableElement formatValueElement;
+  /** The value() element/field of an @InvalidFormat annotation. */
+  protected final ExecutableElement invalidFormatValueElement;
+  // private final ExecutableElement formatArgTypesElement;
+
+  public FormatterTreeUtil(BaseTypeChecker checker) {
+    this.checker = checker;
+    this.processingEnv = checker.getProcessingEnvironment();
+    formatValueElement = TreeUtils.getMethod(Format.class, "value", 0, processingEnv);
+    invalidFormatValueElement = TreeUtils.getMethod(InvalidFormat.class, "value", 0, processingEnv);
+    /*
+    this.formatArgTypesElement =
+            TreeUtils.getMethod(
+                    Format.class,
+                    "value",
+                    0,
+                    processingEnv);
+     */
+  }
+
+  /** Describes the ways a format method may be invoked. */
+  public static enum InvocationType {
+    /**
+     * The parameters are passed as varargs. For example:
+     *
+     * <blockquote>
+     *
+     * <pre>
+     * String.format("%s %d", "Example", 7);
+     * </pre>
+     *
+     * </blockquote>
+     */
+    VARARG,
+
+    /**
+     * The parameters are passed as array. For example:
+     *
+     * <blockquote>
+     *
+     * <pre>
+     * Object[] a = new Object[]{"Example",7};
+     * String.format("%s %d", a);
+     * </pre>
+     *
+     * </blockquote>
+     */
+    ARRAY,
+
+    /**
+     * A null array is passed to the format method. This happens seldomly.
+     *
+     * <blockquote>
+     *
+     * <pre>
+     * String.format("%s %d", (Object[])null);
+     * </pre>
+     *
+     * </blockquote>
+     */
+    NULLARRAY;
+  }
+
+  /** A wrapper around a value of type E, plus an ExpressionTree location. */
+  public static class Result<E> {
+    private final E value;
+    public final ExpressionTree location;
+
+    public Result(E value, ExpressionTree location) {
+      this.value = value;
+      this.location = location;
+    }
+
+    public E value() {
+      return value;
+    }
+  }
+
+  /**
+   * Returns true if the call is to a method with the @ReturnsFormat annotation. An example of such
+   * a method is FormatUtil.asFormat.
+   */
+  public boolean isAsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
+    ExecutableElement method = node.getTarget().getMethod();
+    AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, ReturnsFormat.class);
+    return anno != null;
+  }
+
+  private ConversionCategory[] asFormatCallCategoriesLowLevel(MethodInvocationNode node) {
+    Node vararg = node.getArgument(1);
+    if (!(vararg instanceof ArrayCreationNode)) {
+      return null;
+    }
+    List<Node> convs = ((ArrayCreationNode) vararg).getInitializers();
+    ConversionCategory[] res = new ConversionCategory[convs.size()];
+    for (int i = 0; i < convs.size(); ++i) {
+      Node conv = convs.get(i);
+      if (conv instanceof FieldAccessNode) {
+        Class<? extends Object> clazz =
+            TypesUtils.getClassFromType(((FieldAccessNode) conv).getType());
+        if (clazz == ConversionCategory.class) {
+          res[i] = ConversionCategory.valueOf(((FieldAccessNode) conv).getFieldName());
+          continue; /* avoid returning null */
+        }
+      }
+      return null;
+    }
+    return res;
+  }
+
+  public Result<ConversionCategory[]> asFormatCallCategories(MethodInvocationNode node) {
+    // TODO make sure the method signature looks good
+    return new Result<>(asFormatCallCategoriesLowLevel(node), node.getTree());
+  }
+
+  /**
+   * Returns true if {@code node} is a call to a method annotated with {@code @FormatMethod}.
+   *
+   * @param node a method call
+   * @param atypeFactory a type factory
+   * @return true if {@code node} is a call to a method annotated with {@code @FormatMethod}
+   */
+  public boolean isFormatMethodCall(MethodInvocationTree node, AnnotatedTypeFactory atypeFactory) {
+    ExecutableElement method = TreeUtils.elementFromUse(node);
+    AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, FormatMethod.class);
+    return anno != null;
+  }
+
+  /**
+   * Creates a new FormatCall, or returns null.
+   *
+   * @param invocationTree a method invocation, where the method is annotated @FormatMethod
+   * @param atypeFactory the type factory
+   * @return a new FormatCall, or null if the invocation is of a method that is improperly
+   *     annotated @FormatMethod
+   */
+  public @Nullable FormatCall create(
+      MethodInvocationTree invocationTree, AnnotatedTypeFactory atypeFactory) {
+    FormatterTreeUtil ftu = ((FormatterAnnotatedTypeFactory) atypeFactory).treeUtil;
+    if (!ftu.isFormatMethodCall(invocationTree, atypeFactory)) {
+      return null;
+    }
+
+    ExecutableElement methodElement = TreeUtils.elementFromUse(invocationTree);
+    int formatStringIndex = FormatterVisitor.formatStringIndex(methodElement);
+    if (formatStringIndex == -1) {
+      // Reporting the error is redundant if the method was declared in source code, because the
+      // visitor will have reported it; but it is necessary if the method was declared in byte code.
+      atypeFactory
+          .getChecker()
+          .reportError(invocationTree, "format.method", methodElement.getSimpleName());
+      return null;
+    }
+    ExpressionTree formatStringTree = invocationTree.getArguments().get(formatStringIndex);
+    AnnotatedTypeMirror formatStringType = atypeFactory.getAnnotatedType(formatStringTree);
+    List<? extends ExpressionTree> allArgs = invocationTree.getArguments();
+    List<? extends ExpressionTree> args = allArgs.subList(formatStringIndex + 1, allArgs.size());
+
+    return new FormatCall(invocationTree, formatStringTree, formatStringType, args, atypeFactory);
+  }
+
+  /** Represents a format method invocation in the syntax tree. */
+  public class FormatCall {
+    /** The call itself. */
+    final MethodInvocationTree invocationTree;
+    /** The format string argument. */
+    private final ExpressionTree formatStringTree;
+    /** The type of the format string argument. */
+    private final AnnotatedTypeMirror formatStringType;
+    /** The arguments that follow the format string argument. */
+    private final List<? extends ExpressionTree> args;
+    /** The type factory. */
+    private final AnnotatedTypeFactory atypeFactory;
+
+    /**
+     * Create a new FormatCall object.
+     *
+     * @param invocationTree the call itself
+     * @param formatStringTree the format string argument
+     * @param formatStringType the type of the format string argument
+     * @param args the arguments that follow the format string argument
+     * @param atypeFactory the type factory
+     */
+    private FormatCall(
+        MethodInvocationTree invocationTree,
+        ExpressionTree formatStringTree,
+        AnnotatedTypeMirror formatStringType,
+        List<? extends ExpressionTree> args,
+        AnnotatedTypeFactory atypeFactory) {
+      this.invocationTree = invocationTree;
+      this.formatStringTree = formatStringTree;
+      this.formatStringType = formatStringType;
+      this.args = args;
+      this.atypeFactory = atypeFactory;
+    }
+
+    /**
+     * Returns an error description if the format-string argument's type is <em>not</em> annotated
+     * as {@code @Format}. Returns null if it is annotated.
+     *
+     * @return an error description if the format string is not annotated as {@code @Format}, or
+     *     null if it is
+     */
+    public final Result<String> errMissingFormatAnnotation() {
+      if (!formatStringType.hasAnnotation(Format.class)) {
+        String msg = "(is a @Format annotation missing?)";
+        AnnotationMirror inv = formatStringType.getAnnotation(InvalidFormat.class);
+        if (inv != null) {
+          msg = invalidFormatAnnotationToErrorMessage(inv);
+        }
+        return new Result<>(msg, formatStringTree);
+      }
+      return null;
+    }
+
+    /**
+     * Returns the type of method invocation.
+     *
+     * @see InvocationType
+     */
+    public final Result<InvocationType> getInvocationType() {
+      InvocationType type = InvocationType.VARARG;
+
+      if (args.size() == 1) {
+        final ExpressionTree first = args.get(0);
+        TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType();
+        // figure out if argType is an array
+        type =
+            argType.accept(
+                new SimpleTypeVisitor7<InvocationType, Class<Void>>() {
+                  @Override
+                  protected InvocationType defaultAction(TypeMirror e, Class<Void> p) {
+                    // not an array
+                    return InvocationType.VARARG;
+                  }
+
+                  @Override
+                  public InvocationType visitArray(ArrayType t, Class<Void> p) {
+                    // it's an array, now figure out if it's a (Object[])null
+                    // array
+                    return first.accept(
+                        new SimpleTreeVisitor<InvocationType, Class<Void>>() {
+                          @Override
+                          protected InvocationType defaultAction(Tree node, Class<Void> p) {
+                            // just a normal array
+                            return InvocationType.ARRAY;
+                          }
+
+                          @Override
+                          public InvocationType visitTypeCast(TypeCastTree node, Class<Void> p) {
+                            // it's a (Object[])null
+                            return atypeFactory
+                                        .getAnnotatedType(node.getExpression())
+                                        .getUnderlyingType()
+                                        .getKind()
+                                    == TypeKind.NULL
+                                ? InvocationType.NULLARRAY
+                                : InvocationType.ARRAY;
+                          }
+                        },
+                        p);
+                  }
+
+                  @Override
+                  public InvocationType visitNull(NullType t, Class<Void> p) {
+                    return InvocationType.NULLARRAY;
+                  }
+                },
+                Void.TYPE);
+      }
+
+      ExpressionTree loc = invocationTree.getMethodSelect();
+      if (type != InvocationType.VARARG && !args.isEmpty()) {
+        loc = args.get(0);
+      }
+      return new Result<>(type, loc);
+    }
+
+    /**
+     * Returns the conversion category for every parameter.
+     *
+     * @return the conversion categories of all the parameters
+     * @see ConversionCategory
+     */
+    public final ConversionCategory[] getFormatCategories() {
+      AnnotationMirror anno = formatStringType.getAnnotation(Format.class);
+      return formatAnnotationToCategories(anno);
+    }
+
+    /**
+     * Returns the types of the arguments to the call. Use {@link #isValidArgument} and {@link
+     * #isArgumentNull} to work with the result.
+     *
+     * @return the types of the arguments to the call
+     */
+    public final Result<TypeMirror>[] getArgTypes() {
+      // One to suppress warning in javac, the other to suppress warning in Eclipse...
+      @SuppressWarnings({"rawtypes", "unchecked"})
+      Result<TypeMirror>[] res = new Result[args.size()];
+      for (int i = 0; i < res.length; ++i) {
+        ExpressionTree arg = args.get(i);
+        TypeMirror argType;
+        if (TreeUtils.isNullExpression(arg)) {
+          argType = atypeFactory.getProcessingEnv().getTypeUtils().getNullType();
+        } else {
+          argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType();
+        }
+        res[i] = new Result<>(argType, arg);
+      }
+      return res;
+    }
+
+    /**
+     * Checks if the type of an argument returned from {@link #getArgTypes()} is valid for the
+     * passed ConversionCategory.
+     *
+     * @param formatCat a format specifier
+     * @param argType an argument type
+     * @return true if the argument can be passed to the format specifier
+     */
+    public final boolean isValidArgument(ConversionCategory formatCat, TypeMirror argType) {
+      if (argType.getKind() == TypeKind.NULL || isArgumentNull(argType)) {
+        return true;
+      }
+      Class<? extends Object> type = TypesUtils.getClassFromType(argType);
+      return formatCat.isAssignableFrom(type);
+    }
+
+    /**
+     * Checks if the argument returned from {@link #getArgTypes()} is a {@code null} expression.
+     *
+     * @param type a type
+     * @return true if the argument is a {@code null} expression
+     */
+    public final boolean isArgumentNull(TypeMirror type) {
+      // TODO: Just check whether it is the VOID TypeMirror.
+
+      // is it the null literal
+      return type.accept(
+          new SimpleTypeVisitor7<Boolean, Class<Void>>() {
+            @Override
+            protected Boolean defaultAction(TypeMirror e, Class<Void> p) {
+              // it's not the null literal
+              return false;
+            }
+
+            @Override
+            public Boolean visitNull(NullType t, Class<Void> p) {
+              // it's the null literal
+              return true;
+            }
+          },
+          Void.TYPE);
+    }
+  }
+
+  // The failure() method is required so that FormatterTransfer, which has no access to the
+  // FormatterChecker, can report errors.
+  /**
+   * Reports an error.
+   *
+   * @param res used for source location information
+   * @param msgKey the diagnostic message key
+   * @param args arguments to the diagnostic message
+   */
+  public final void failure(Result<?> res, @CompilerMessageKey String msgKey, Object... args) {
+    checker.reportError(res.location, msgKey, args);
+  }
+
+  /**
+   * Reports a warning.
+   *
+   * @param res used for source location information
+   * @param msgKey the diagnostic message key
+   * @param args arguments to the diagnostic message
+   */
+  public final void warning(Result<?> res, @CompilerMessageKey String msgKey, Object... args) {
+    checker.reportWarning(res.location, msgKey, args);
+  }
+
+  /**
+   * Takes an exception that describes an invalid formatter string and, returns a syntax trees
+   * element that represents a {@link InvalidFormat} annotation with the exception's error message
+   * as value.
+   */
+  public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalFormatException ex) {
+    return stringToInvalidFormatAnnotation(ex.getMessage());
+  }
+
+  /**
+   * Creates an {@link InvalidFormat} annotation with the given string as its value.
+   *
+   * @param invalidFormatString an invalid formatter string
+   * @return an {@link InvalidFormat} annotation with the given string as its value
+   */
+  // package-private
+  AnnotationMirror stringToInvalidFormatAnnotation(String invalidFormatString) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, InvalidFormat.class);
+    builder.setValue("value", invalidFormatString);
+    return builder.build();
+  }
+
+  /**
+   * Gets the value() element/field out of an InvalidFormat annotation.
+   *
+   * @param anno an InvalidFormat annotation
+   * @return its value() element/field
+   */
+  private String getInvalidFormatValue(AnnotationMirror anno) {
+    return (String) anno.getElementValues().get(invalidFormatValueElement).getValue();
+  }
+
+  /**
+   * Takes a syntax tree element that represents a {@link InvalidFormat} annotation, and returns its
+   * value.
+   *
+   * @param anno an InvalidFormat annotation
+   * @return its value() element/field
+   */
+  public String invalidFormatAnnotationToErrorMessage(AnnotationMirror anno) {
+    return "\"" + getInvalidFormatValue(anno) + "\"";
+  }
+
+  /**
+   * Creates a {@code @}{@link Format} annotation with the given list as its value.
+   *
+   * @param args conversion categories for the {@code @Format} annotation
+   * @return a {@code @}{@link Format} annotation with the given list as its value
+   */
+  public AnnotationMirror categoriesToFormatAnnotation(ConversionCategory[] args) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, Format.class);
+    builder.setValue("value", args);
+    return builder.build();
+  }
+
+  /**
+   * Returns the value of a {@code @}{@link Format} annotation.
+   *
+   * @param anno a {@code @}{@link Format} annotation
+   * @return the annotation's {@code value} element
+   */
+  @SuppressWarnings("GetClassOnEnum")
+  public ConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) {
+    return AnnotationUtils.getElementValueEnumArray(
+        anno, formatValueElement, ConversionCategory.class);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java
new file mode 100644
index 0000000..c2d8425
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java
@@ -0,0 +1,287 @@
+package org.checkerframework.checker.formatter;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.formatter.FormatterTreeUtil.FormatCall;
+import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType;
+import org.checkerframework.checker.formatter.FormatterTreeUtil.Result;
+import org.checkerframework.checker.formatter.qual.ConversionCategory;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.wholeprograminference.WholeProgramInference;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Whenever a format method invocation is found in the syntax tree, checks are performed as
+ * specified in the Format String Checker manual.
+ *
+ * @checker_framework.manual #formatter-guarantees Format String Checker
+ */
+public class FormatterVisitor extends BaseTypeVisitor<FormatterAnnotatedTypeFactory> {
+  public FormatterVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  public Void visitMethod(MethodTree node, Void p) {
+    ExecutableElement methodElement = TreeUtils.elementFromDeclaration(node);
+    if (atypeFactory.getDeclAnnotation(methodElement, FormatMethod.class) != null) {
+      int formatStringIndex = FormatterVisitor.formatStringIndex(methodElement);
+      if (formatStringIndex == -1) {
+        checker.reportError(node, "format.method", methodElement.getSimpleName());
+      }
+    }
+    return super.visitMethod(node, p);
+  }
+
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+    FormatterTreeUtil ftu = atypeFactory.treeUtil;
+    FormatCall fc = ftu.create(node, atypeFactory);
+    if (fc != null) {
+      MethodTree enclosingMethod =
+          TreePathUtil.enclosingMethod(atypeFactory.getPath(fc.invocationTree));
+
+      Result<String> errMissingFormat = fc.errMissingFormatAnnotation();
+      if (errMissingFormat != null) {
+        // The string's type has no @Format annotation.
+        if (isWrappedFormatCall(fc, enclosingMethod)) {
+          // Nothing to do, because call is legal.
+        } else {
+          // I.1
+          ftu.failure(errMissingFormat, "format.string", errMissingFormat.value());
+        }
+      } else {
+        // The string has a @Format annotation.
+        Result<InvocationType> invc = fc.getInvocationType();
+        ConversionCategory[] formatCats = fc.getFormatCategories();
+        switch (invc.value()) {
+          case VARARG:
+            Result<TypeMirror>[] argTypes = fc.getArgTypes();
+            int argl = argTypes.length;
+            int formatl = formatCats.length;
+            if (argl < formatl) {
+              // For assignments, format.missing.arguments is issued from commonAssignmentCheck.
+              // II.1
+              ftu.failure(invc, "format.missing.arguments", formatl, argl);
+            } else {
+              if (argl > formatl) {
+                // II.2
+                ftu.warning(invc, "format.excess.arguments", formatl, argl);
+              }
+              for (int i = 0; i < formatl; ++i) {
+                ConversionCategory formatCat = formatCats[i];
+                Result<TypeMirror> arg = argTypes[i];
+                TypeMirror argType = arg.value();
+
+                switch (formatCat) {
+                  case UNUSED:
+                    // I.2
+                    ftu.warning(arg, "format.argument.unused", " " + (1 + i));
+                    break;
+                  case NULL:
+                    // I.3
+                    if (argType.getKind() == TypeKind.NULL) {
+                      ftu.warning(arg, "format.specifier.null", " " + (1 + i));
+                    } else {
+                      ftu.failure(arg, "format.specifier.null", " " + (1 + i));
+                    }
+                    break;
+                  case GENERAL:
+                    break;
+                  default:
+                    if (!fc.isValidArgument(formatCat, argType)) {
+                      // II.3
+                      ExecutableElement method = TreeUtils.elementFromUse(node);
+                      CharSequence methodName = ElementUtils.getSimpleNameOrDescription(method);
+                      ftu.failure(
+                          arg, "argument", "in varargs position", methodName, argType, formatCat);
+                    }
+                    break;
+                }
+              }
+            }
+            break;
+          case ARRAY:
+            // III
+            if (!isWrappedFormatCall(fc, enclosingMethod)) {
+              ftu.warning(invc, "format.indirect.arguments");
+            }
+            // TODO:  If it is explict array construction, such as "new Object[] {
+            // ... }", then we could treat it like the VARARGS case, analyzing each
+            // argument.  "new array" is probably rare, in the varargs position.
+            // fall through
+          case NULLARRAY:
+            for (ConversionCategory cat : formatCats) {
+              if (cat == ConversionCategory.NULL) {
+                // I.3
+                if (invc.value() == FormatterTreeUtil.InvocationType.NULLARRAY) {
+                  ftu.warning(invc, "format.specifier.null", "");
+                } else {
+                  ftu.failure(invc, "format.specifier.null", "");
+                }
+              }
+              if (cat == ConversionCategory.UNUSED) {
+                // I.2
+                ftu.warning(invc, "format.argument.unused", "");
+              }
+            }
+            break;
+        }
+      }
+
+      // Support -Ainfer command-line argument.
+      WholeProgramInference wpi = atypeFactory.getWholeProgramInference();
+      if (wpi != null && forwardsArguments(node, enclosingMethod)) {
+        wpi.addMethodDeclarationAnnotation(
+            TreeUtils.elementFromDeclaration(enclosingMethod), atypeFactory.FORMATMETHOD);
+      }
+    }
+    return super.visitMethodInvocation(node, p);
+  }
+
+  /**
+   * Returns true if {@code fc} is within a method m annotated as {@code @FormatMethod}, and fc's
+   * arguments are m's formal parameters. In other words, fc forwards m's arguments to another
+   * format method.
+   *
+   * @param fc an invocation of a format method
+   * @param enclosingMethod the method that contains the call
+   * @return true if {@code fc} is a call to a format method that forwards its containing method's
+   *     arguments
+   */
+  private boolean isWrappedFormatCall(FormatCall fc, @Nullable MethodTree enclosingMethod) {
+    if (enclosingMethod == null) {
+      return false;
+    }
+    ExecutableElement enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod);
+    boolean withinFormatMethod =
+        (atypeFactory.getDeclAnnotation(enclosingMethodElement, FormatMethod.class) != null);
+    return withinFormatMethod && forwardsArguments(fc.invocationTree, enclosingMethod);
+  }
+
+  /**
+   * Returns true if {@code invocationTree}'s arguments are {@code enclosingMethod}'s formal
+   * parameters. In other words, {@code invocationTree} forwards {@code enclosingMethod}'s
+   * arguments.
+   *
+   * <p>Only arguments from the first String formal parameter onward count. Returns false if there
+   * is no String formal parameter.
+   *
+   * @param invocationTree an invocation of a method
+   * @param enclosingMethod the method that contains the call
+   * @return true if {@code invocationTree} is a call to a method that forwards its containing
+   *     method's arguments
+   */
+  private boolean forwardsArguments(
+      MethodInvocationTree invocationTree, @Nullable MethodTree enclosingMethod) {
+
+    if (enclosingMethod == null) {
+      return false;
+    }
+
+    ExecutableElement enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod);
+    int paramIndex = formatStringIndex(enclosingMethodElement);
+    if (paramIndex == -1) {
+      return false;
+    }
+
+    ExecutableElement calledMethodElement = TreeUtils.elementFromUse(invocationTree);
+    int callIndex = formatStringIndex(calledMethodElement);
+    if (callIndex == -1) {
+      throw new BugInCF(
+          "Method "
+              + calledMethodElement
+              + " is annotated @FormatMethod but has no String formal parameter");
+    }
+
+    List<? extends ExpressionTree> args = invocationTree.getArguments();
+    List<? extends VariableTree> params = enclosingMethod.getParameters();
+
+    if (params.size() - paramIndex != args.size() - callIndex) {
+      return false;
+    }
+    while (paramIndex < params.size()) {
+      ExpressionTree argTree = args.get(callIndex);
+      if (argTree.getKind() != Tree.Kind.IDENTIFIER) {
+        return false;
+      }
+      VariableTree param = params.get(paramIndex);
+      if (param.getName() != ((IdentifierTree) argTree).getName()) {
+        return false;
+      }
+      paramIndex++;
+      callIndex++;
+    }
+
+    return true;
+  }
+
+  // TODO: Should this be the last String argument?  That would require that every method
+  // annotated with @FormatMethod uses varargs syntax.
+  /**
+   * Returns the index of the format string of a method: the first formal parameter with declared
+   * type String.
+   *
+   * @param m a method
+   * @return the index of the last String formal parameter, or -1 if none
+   */
+  public static int formatStringIndex(ExecutableElement m) {
+    List<? extends VariableElement> params = m.getParameters();
+    for (int i = 0; i < params.size(); i++) {
+      if (TypesUtils.isString(params.get(i).asType())) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  @Override
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      AnnotatedTypeMirror valueType,
+      Tree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
+
+    AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT);
+    AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT);
+
+    // From the manual: "It is legal to use a format string with fewer format specifiers
+    // than required, but a warning is issued."
+    // The format.missing.arguments warning is issued here for assignments.
+    // For method calls, it is issued in visitMethodInvocation.
+    if (rhs != null
+        && lhs != null
+        && AnnotationUtils.areSameByName(rhs, FormatterAnnotatedTypeFactory.FORMAT_NAME)
+        && AnnotationUtils.areSameByName(lhs, FormatterAnnotatedTypeFactory.FORMAT_NAME)) {
+      ConversionCategory[] rhsArgTypes = atypeFactory.treeUtil.formatAnnotationToCategories(rhs);
+      ConversionCategory[] lhsArgTypes = atypeFactory.treeUtil.formatAnnotationToCategories(lhs);
+
+      if (rhsArgTypes.length < lhsArgTypes.length) {
+        checker.reportWarning(
+            valueTree, "format.missing.arguments", varType.toString(), valueType.toString());
+      }
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/messages.properties b/checker/src/main/java/org/checkerframework/checker/formatter/messages.properties
new file mode 100644
index 0000000..3692985
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/formatter/messages.properties
@@ -0,0 +1,8 @@
+format.method=method %s is annotated @FormatMethod but has no String formal parameter
+format.string=invalid format string %s
+format.argument.unused=unused argument%s
+format.specifier.null=format specifier%s accepts only null as its argument
+format.excess.arguments=too many arguments; expected %s but %s given
+format.missing.arguments=missing arguments; expected %s but %s given
+format.asformat.indirect.arguments=categories to the asFormat() function must be passed directly as varargs
+format.indirect.arguments=parameters to a format method can only be checked if passed as varargs
diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/Effect.java b/checker/src/main/java/org/checkerframework/checker/guieffect/Effect.java
new file mode 100644
index 0000000..986f497
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/guieffect/Effect.java
@@ -0,0 +1,123 @@
+package org.checkerframework.checker.guieffect;
+
+import java.lang.annotation.Annotation;
+import org.checkerframework.checker.guieffect.qual.PolyUIEffect;
+import org.checkerframework.checker.guieffect.qual.SafeEffect;
+import org.checkerframework.checker.guieffect.qual.UIEffect;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+
+/** An effect -- either UIEffect, PolyUIEffect, or SafeEffect. */
+public final class Effect {
+  // Colin hates Java's comparable interface, so he's not using it
+
+  private final Class<? extends Annotation> annotClass;
+
+  /**
+   * Create a new Effect object.
+   *
+   * @param cls one of UIEffect.class, PolyUIEffect.class, or SafeEffect.class
+   */
+  public Effect(Class<? extends Annotation> cls) {
+    assert cls == UIEffect.class || cls == PolyUIEffect.class || cls == SafeEffect.class;
+    annotClass = cls;
+  }
+
+  /**
+   * Return true iff {@code left} is less than or equal to {@code right}.
+   *
+   * @param left the first effect to compare
+   * @param right the first effect to compare
+   * @return true iff {@code left} is less than or equal to {@code right}
+   */
+  public static boolean lessThanOrEqualTo(Effect left, Effect right) {
+    assert (left != null && right != null);
+    boolean leftBottom = left.annotClass == SafeEffect.class;
+    boolean rightTop = right.annotClass == UIEffect.class;
+    return leftBottom || rightTop || left.annotClass == right.annotClass;
+  }
+
+  public static Effect min(Effect l, Effect r) {
+    if (lessThanOrEqualTo(l, r)) {
+      return l;
+    } else {
+      return r;
+    }
+  }
+
+  public static final class EffectRange {
+    public final Effect min, max;
+
+    public EffectRange(Effect min, Effect max) {
+      assert (min != null || max != null);
+      // If one is null, fill in with the other
+      this.min = (min != null ? min : max);
+      this.max = (max != null ? max : min);
+    }
+  }
+
+  /**
+   * Return true if this is SafeEffect.
+   *
+   * @return true if this is SafeEffect
+   */
+  public boolean isSafe() {
+    return annotClass == SafeEffect.class;
+  }
+
+  /**
+   * Return true if this is UIEffect.
+   *
+   * @return true if this is UIEffect
+   */
+  public boolean isUI() {
+    return annotClass == UIEffect.class;
+  }
+
+  /**
+   * Return true if this is PolyUIEffect.
+   *
+   * @return true if this is PolyUIEffect
+   */
+  @Pure
+  public boolean isPoly() {
+    return annotClass == PolyUIEffect.class;
+  }
+
+  public Class<? extends Annotation> getAnnot() {
+    return annotClass;
+  }
+
+  @SideEffectFree
+  @Override
+  public String toString() {
+    return annotClass.getSimpleName();
+  }
+
+  /**
+   * Return true if this equals the given effect.
+   *
+   * @param e the effect to compare this to
+   * @return true if this equals the given effect
+   */
+  @SuppressWarnings("NonOverridingEquals") // TODO: clean this up!
+  public boolean equals(Effect e) {
+    return annotClass == e.annotClass;
+  }
+
+  @Override
+  public boolean equals(@Nullable Object o) {
+    if (o instanceof Effect) {
+      return this.equals((Effect) o);
+    } else {
+      return super.equals(o);
+    }
+  }
+
+  @Pure
+  @Override
+  public int hashCode() {
+    return 31 + annotClass.hashCode();
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectChecker.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectChecker.java
new file mode 100644
index 0000000..2484db1
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectChecker.java
@@ -0,0 +1,14 @@
+package org.checkerframework.checker.guieffect;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.StubFiles;
+import org.checkerframework.framework.source.SupportedLintOptions;
+
+/**
+ * The GUI Effect Checker.
+ *
+ * @checker_framework.manual #guieffect-checker GUI Effect Checker
+ */
+@StubFiles({"org-eclipse.astub", "org-osgi.astub", "org-swtchart.astub"})
+@SupportedLintOptions({"debugSpew"})
+public class GuiEffectChecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java
new file mode 100644
index 0000000..2770d6a
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java
@@ -0,0 +1,648 @@
+package org.checkerframework.checker.guieffect;
+
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.Tree;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.guieffect.qual.AlwaysSafe;
+import org.checkerframework.checker.guieffect.qual.PolyUI;
+import org.checkerframework.checker.guieffect.qual.PolyUIEffect;
+import org.checkerframework.checker.guieffect.qual.PolyUIType;
+import org.checkerframework.checker.guieffect.qual.SafeEffect;
+import org.checkerframework.checker.guieffect.qual.SafeType;
+import org.checkerframework.checker.guieffect.qual.UI;
+import org.checkerframework.checker.guieffect.qual.UIEffect;
+import org.checkerframework.checker.guieffect.qual.UIPackage;
+import org.checkerframework.checker.guieffect.qual.UIType;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** Annotated type factory for the GUI Effect Checker. */
+public class GuiEffectTypeFactory extends BaseAnnotatedTypeFactory {
+
+  protected final boolean debugSpew;
+
+  /**
+   * Keeps track of all lambda expressions with inferred UIEffect.
+   *
+   * <p>{@link #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI} adds lambda
+   * expressions to this set, and is called from GuiEffectVisitor whenever a lambda expression calls
+   * a @UIEffect method. Afterwards {@link
+   * #getInferedEffectForLambdaExpression(LambdaExpressionTree) getInferedEffectForLambdaExpression}
+   * uses this set and the type annotations of the functional interface of the lambda to figure out
+   * if it can affect the UI or not.
+   */
+  protected final Set<LambdaExpressionTree> uiLambdas = new HashSet<>();
+
+  /**
+   * Keeps track of all anonymous inner classes with inferred UIEffect.
+   *
+   * <p>{@link #constrainAnonymousClassToUI(TypeElement) constrainAnonymousClassToUI} adds anonymous
+   * inner classes to this set, and is called from GuiEffectVisitor whenever an anonymous inner
+   * class calls a @UIEffect method. Afterwards {@link #isUIType(TypeElement) isUIType} and {@link
+   * #getAnnotatedType(Tree) getAnnotatedType} will treat this inner class as if it had been
+   * annotated with @UI.
+   */
+  protected final Set<TypeElement> uiAnonClasses = new HashSet<>();
+
+  public GuiEffectTypeFactory(BaseTypeChecker checker, boolean spew) {
+    // use true to enable flow inference, false to disable it
+    super(checker, false);
+
+    debugSpew = spew;
+    this.postInit();
+  }
+
+  // Could move this to a public method on the checker class
+  public ExecutableElement findJavaOverride(ExecutableElement overrider, TypeMirror parentType) {
+    if (parentType.getKind() != TypeKind.NONE) {
+      if (debugSpew) {
+        System.err.println("Searching for overridden methods from " + parentType);
+      }
+
+      TypeElement overriderClass = (TypeElement) overrider.getEnclosingElement();
+      TypeElement elem = (TypeElement) ((DeclaredType) parentType).asElement();
+      if (debugSpew) {
+        System.err.println("necessary TypeElements acquired: " + elem);
+      }
+
+      for (Element e : elem.getEnclosedElements()) {
+        if (debugSpew) {
+          System.err.println("Considering element " + e);
+        }
+        if (e.getKind() == ElementKind.METHOD || e.getKind() == ElementKind.CONSTRUCTOR) {
+          ExecutableElement ex = (ExecutableElement) e;
+          boolean overrides = elements.overrides(overrider, ex, overriderClass);
+          if (overrides) {
+            return ex;
+          }
+        }
+      }
+      if (debugSpew) {
+        System.err.println("Done considering elements of " + parentType);
+      }
+    }
+    return null;
+  }
+
+  public boolean isPolymorphicType(TypeElement cls) {
+    assert (cls != null);
+    return getDeclAnnotation(cls, PolyUIType.class) != null
+        || fromElement(cls).hasAnnotation(PolyUI.class);
+  }
+
+  public boolean isUIType(TypeElement cls) {
+    if (debugSpew) {
+      System.err.println(" isUIType(" + cls + ")");
+    }
+    boolean targetClassUIP = fromElement(cls).hasAnnotation(UI.class);
+    AnnotationMirror targetClassUITypeP = getDeclAnnotation(cls, UIType.class);
+    AnnotationMirror targetClassSafeTypeP = getDeclAnnotation(cls, SafeType.class);
+
+    if (targetClassSafeTypeP != null) {
+      return false; // explicitly marked not a UI type
+    }
+
+    boolean hasUITypeDirectly = (targetClassUIP || targetClassUITypeP != null);
+
+    if (hasUITypeDirectly) {
+      return true;
+    }
+
+    // Anon inner classes should not inherit the package annotation, since
+    // they're so often used for closures to run async on background threads.
+    if (isAnonymousType(cls)) {
+      // However, we need to look into Anonymous class effect inference
+      if (uiAnonClasses.contains(cls)) {
+        return true;
+      }
+      return false;
+    }
+
+    // We don't check polymorphic annos so we can make a couple methods of
+    // an @UIType polymorphic explicitly
+    // AnnotationMirror targetClassPolyP = getDeclAnnotation(cls, PolyUI.class);
+    // AnnotationMirror targetClassPolyTypeP = getDeclAnnotation(cls, PolyUIType.class);
+    boolean targetClassSafeP = fromElement(cls).hasAnnotation(AlwaysSafe.class);
+    if (targetClassSafeP) {
+      return false; // explicitly annotated otherwise
+    }
+
+    // Look for the package
+    Element packageP = ElementUtils.enclosingPackage(cls);
+
+    if (packageP != null) {
+      if (debugSpew) {
+        System.err.println("Found package " + packageP);
+      }
+      if (getDeclAnnotation(packageP, UIPackage.class) != null) {
+        if (debugSpew) {
+          System.err.println("Package " + packageP + " is annotated @UIPackage");
+        }
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  // TODO: is there a framework method for this?
+  private static boolean isAnonymousType(TypeElement elem) {
+    return elem.getSimpleName().length() == 0;
+  }
+
+  /**
+   * Calling context annotations.
+   *
+   * <p>To make anon-inner-classes work, I need to climb the inheritance DAG, until I:
+   *
+   * <ul>
+   *   <li>find the class/interface that declares this calling method (an anon inner class is a
+   *       separate class that implements an interface)
+   *   <li>check whether *that* declaration specifies @UI on either the type or method
+   * </ul>
+   *
+   * A method has the UI effect when:
+   *
+   * <ol>
+   *   <li>A method is UI if annotated @UIEffect
+   *   <li>A method is UI if the enclosing class is annotated @UI or @UIType and the method is not
+   *       annotated @AlwaysSafe
+   *   <li>A method is UI if the corresponding method in the super-class/interface is UI, and this
+   *       method is not annotated @AlwaysSafe, and this method resides in an anonymous inner class
+   *       (named classes still require a package/class/method annotation to make it UI, only anon
+   *       inner classes have this inheritance-by-default)
+   *       <ul>
+   *         <li>A method must be *annotated* UI if the method it overrides is *annotated* UI
+   *         <li>A method must be *annotated* UI if it overrides a UI method and the enclosing class
+   *             is not UI
+   *       </ul>
+   *   <li>It is an error if a method is UI but the same method in a super-type is not UI
+   *   <li>It is an error if two super-types specify the same method, where one type says it's UI
+   *       and one says it's not (it's possible to simply enforce the weaker (safe) effect, but this
+   *       seems more principled, it's easier --- backwards-compatible --- to change our minds about
+   *       this later)
+   * </ol>
+   */
+  public Effect getDeclaredEffect(ExecutableElement methodElt) {
+    if (debugSpew) {
+      System.err.println("begin mayHaveUIEffect(" + methodElt + ")");
+    }
+    AnnotationMirror targetUIP = getDeclAnnotation(methodElt, UIEffect.class);
+    AnnotationMirror targetSafeP = getDeclAnnotation(methodElt, SafeEffect.class);
+    AnnotationMirror targetPolyP = getDeclAnnotation(methodElt, PolyUIEffect.class);
+    TypeElement targetClassElt = (TypeElement) methodElt.getEnclosingElement();
+    boolean hasMainThreadAnnot =
+        getDeclAnnotations(methodElt).toString().contains("@android.support.annotation.MainThread");
+
+    if (debugSpew) {
+      System.err.println("targetClassElt found");
+    }
+
+    // Short-circuit if the method is explicitly annotated
+    if (targetSafeP != null) {
+      if (debugSpew) {
+        System.err.println("Method marked @SafeEffect");
+      }
+      return new Effect(SafeEffect.class);
+    } else if (targetUIP != null || hasMainThreadAnnot) {
+      if (debugSpew) {
+        System.err.println("Method marked @UIEffect");
+      }
+      return new Effect(UIEffect.class);
+    } else if (targetPolyP != null) {
+      if (debugSpew) {
+        System.err.println("Method marked @PolyUIEffect");
+      }
+      return new Effect(PolyUIEffect.class);
+    }
+
+    // The method is not explicitly annotated, so check class and package annotations,
+    // and supertype effects if in an anonymous inner class
+
+    if (isUIType(targetClassElt)) {
+      // Already checked, no explicit @SafeEffect annotation
+      return new Effect(UIEffect.class);
+    }
+
+    // Anonymous inner types should just get the effect of the parent by default, rather than
+    // annotating every instance. Unless it's implementing a polymorphic supertype, in which case we
+    // still want the developer to be explicit.
+    if (isAnonymousType(targetClassElt)) {
+      boolean canInheritParentEffects = true; // Refine this for polymorphic parents
+      DeclaredType directSuper = (DeclaredType) targetClassElt.getSuperclass();
+      TypeElement superElt = (TypeElement) directSuper.asElement();
+      // Anonymous subtypes of polymorphic classes other than object can't inherit
+      if (getDeclAnnotation(superElt, PolyUIType.class) != null
+          && !TypesUtils.isObject(directSuper)) {
+        canInheritParentEffects = false;
+      } else {
+        for (TypeMirror ifaceM : targetClassElt.getInterfaces()) {
+          DeclaredType iface = (DeclaredType) ifaceM;
+          TypeElement ifaceElt = (TypeElement) iface.asElement();
+          if (getDeclAnnotation(ifaceElt, PolyUIType.class) != null) {
+            canInheritParentEffects = false;
+          }
+        }
+      }
+
+      if (canInheritParentEffects) {
+        Effect.EffectRange r = findInheritedEffectRange(targetClassElt, methodElt);
+        return (r != null ? Effect.min(r.min, r.max) : new Effect(SafeEffect.class));
+      }
+    }
+
+    return new Effect(SafeEffect.class);
+  }
+
+  /**
+   * Get the effect of a method call at its callsite, acknowledging polymorphic instantiation using
+   * type use annotations.
+   *
+   * @param node the method invocation as an AST node
+   * @param callerReceiver the type of the receiver object if available. Used to resolve direct
+   *     calls like "super()"
+   * @param methodElt the element of the callee method
+   * @return the computed effect (SafeEffect or UIEffect) for the method call
+   */
+  public Effect getComputedEffectAtCallsite(
+      MethodInvocationTree node,
+      AnnotatedTypeMirror.AnnotatedDeclaredType callerReceiver,
+      ExecutableElement methodElt) {
+    Effect targetEffect = getDeclaredEffect(methodElt);
+    if (targetEffect.isPoly()) {
+      AnnotatedTypeMirror srcType = null;
+      if (node.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) {
+        ExpressionTree src = ((MemberSelectTree) node.getMethodSelect()).getExpression();
+        srcType = getAnnotatedType(src);
+      } else if (node.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER) {
+        // Tree.Kind.IDENTIFIER, e.g. a direct call like "super()"
+        if (callerReceiver == null) {
+          // Not enought information provided to instantiate this type-polymorphic effects
+          return targetEffect;
+        }
+        srcType = callerReceiver;
+      } else {
+        throw new BugInCF("Unexpected getMethodSelect() kind at callsite " + node);
+      }
+
+      // Instantiate type-polymorphic effects
+      if (srcType.hasAnnotation(AlwaysSafe.class)) {
+        targetEffect = new Effect(SafeEffect.class);
+      } else if (srcType.hasAnnotation(UI.class)) {
+        targetEffect = new Effect(UIEffect.class);
+      }
+      // Poly substitution would be a noop.
+    }
+    return targetEffect;
+  }
+
+  /**
+   * Get the inferred effect of a lambda expression based on the type annotations of its functional
+   * interface and the effects of the calls in its body.
+   *
+   * <p>This relies on GuiEffectVisitor to perform the actual inference step and mark lambdas
+   * with @PolyUIEffect functional interfaces as being explicitly UI-affecting using the {@link
+   * #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI} method.
+   *
+   * @param lambdaTree a lambda expression's AST node
+   * @return the inferred effect of the lambda
+   */
+  public Effect getInferedEffectForLambdaExpression(LambdaExpressionTree lambdaTree) {
+    // @UI type if annotated on the lambda expression explicitly
+    if (uiLambdas.contains(lambdaTree)) {
+      return new Effect(UIEffect.class);
+    }
+    ExecutableElement functionalInterfaceMethodElt =
+        (ExecutableElement) TreeUtils.findFunction(lambdaTree, checker.getProcessingEnvironment());
+    if (debugSpew) {
+      System.err.println("functionalInterfaceMethodElt found for lambda");
+    }
+    return getDeclaredEffect(functionalInterfaceMethodElt);
+  }
+
+  /**
+   * Test if this tree corresponds to a lambda expression or new class marked as UI affecting by
+   * either {@link #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI}} or {@link
+   * #constrainAnonymousClassToUI(TypeElement)}. Only explicit markings due to inference are
+   * considered here, for the properly computed type of the expression, use {@link
+   * #getAnnotatedType(Tree)} instead.
+   *
+   * @param tree the tree to check
+   * @return whether it is a lambda expression or new class marked as UI by inference
+   */
+  public boolean isDirectlyMarkedUIThroughInference(Tree tree) {
+    if (tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) {
+      return uiLambdas.contains((LambdaExpressionTree) tree);
+    } else if (tree.getKind() == Tree.Kind.NEW_CLASS) {
+      AnnotatedTypeMirror typeMirror = super.getAnnotatedType(tree);
+      if (typeMirror.getKind() == TypeKind.DECLARED) {
+        return uiAnonClasses.contains(((DeclaredType) typeMirror.getUnderlyingType()).asElement());
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public AnnotatedTypeMirror getAnnotatedType(Tree tree) {
+    AnnotatedTypeMirror typeMirror = super.getAnnotatedType(tree);
+    if (typeMirror.hasAnnotation(UI.class)) {
+      return typeMirror;
+    }
+    // Check if this an @UI anonymous class or lambda due to inference, or an expression
+    // containing such class/lambda
+    if (isDirectlyMarkedUIThroughInference(tree)) {
+      typeMirror.replaceAnnotation(AnnotationBuilder.fromClass(elements, UI.class));
+    } else if (tree.getKind() == Tree.Kind.PARENTHESIZED) {
+      ParenthesizedTree parenthesizedTree = (ParenthesizedTree) tree;
+      return this.getAnnotatedType(parenthesizedTree.getExpression());
+    } else if (tree.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) {
+      ConditionalExpressionTree cet = (ConditionalExpressionTree) tree;
+      boolean isTrueOperandUI =
+          (cet.getTrueExpression() != null
+              && this.getAnnotatedType(cet.getTrueExpression()).hasAnnotation(UI.class));
+      boolean isFalseOperandUI =
+          (cet.getFalseExpression() != null
+              && this.getAnnotatedType(cet.getFalseExpression()).hasAnnotation(UI.class));
+      if (isTrueOperandUI || isFalseOperandUI) {
+        typeMirror.replaceAnnotation(AnnotationBuilder.fromClass(elements, UI.class));
+      }
+    }
+    // TODO: Do we need to support other expression here?
+    // (i.e. are there any other operators that take new or lambda expressions as operands)
+    return typeMirror;
+  }
+
+  // Only the visitMethod call should pass true for warnings
+  public Effect.EffectRange findInheritedEffectRange(
+      TypeElement declaringType, ExecutableElement overridingMethod) {
+    return findInheritedEffectRange(declaringType, overridingMethod, false, null);
+  }
+
+  /**
+   * Find the greatest and least effects of methods the specified definition overrides. This method
+   * is used for two reasons:
+   *
+   * <p>1. {@link GuiEffectVisitor#visitMethod(MethodTree,Void) GuiEffectVisitor.visitMethod} calls
+   * this to perform an effect override check (that a method's effect is less than or equal to the
+   * effect of any method it overrides). This use passes {@code true} for the {@code
+   * issueConflictWarning} in order to trigger warning messages.
+   *
+   * <p>2. {@link #getDeclaredEffect(ExecutableElement) getDeclaredEffect} in this class uses this
+   * to infer the default effect of methods in anonymous inner classes. This use passes {@code
+   * false} for {@code issueConflictWarning}, because it only needs the return value.
+   *
+   * @param declaringType the type declaring the override
+   * @param overridingMethod the method override itself
+   * @param issueConflictWarning whether or not to issue warnings
+   * @param errorNode the method declaration node; used for reporting errors
+   * @return the min and max inherited effects
+   */
+  public Effect.EffectRange findInheritedEffectRange(
+      TypeElement declaringType,
+      ExecutableElement overridingMethod,
+      boolean issueConflictWarning,
+      Tree errorNode) {
+    assert (declaringType != null);
+    ExecutableElement uiOverriden = null;
+    ExecutableElement safeOverriden = null;
+    ExecutableElement polyOverriden = null;
+
+    // We must account for explicit annotation, type declaration annotations, and package
+    // annotations.
+    boolean isUI =
+        (getDeclAnnotation(overridingMethod, UIEffect.class) != null || isUIType(declaringType))
+            && getDeclAnnotation(overridingMethod, SafeEffect.class) == null;
+    boolean isPolyUI = getDeclAnnotation(overridingMethod, PolyUIEffect.class) != null;
+
+    // Check for invalid overrides.
+    // AnnotatedTypes.overriddenMethods retrieves all transitive definitions overridden by this
+    // declaration.
+    Map<AnnotatedTypeMirror.AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
+        AnnotatedTypes.overriddenMethods(elements, this, overridingMethod);
+
+    for (Map.Entry<AnnotatedTypeMirror.AnnotatedDeclaredType, ExecutableElement> pair :
+        overriddenMethods.entrySet()) {
+      AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType = pair.getKey();
+      AnnotatedTypeMirror.AnnotatedExecutableType overriddenMethod =
+          AnnotatedTypes.asMemberOf(types, this, overriddenType, pair.getValue());
+      ExecutableElement overriddenMethodElt = pair.getValue();
+      if (debugSpew)
+        System.err.println(
+            "Found "
+                + declaringType
+                + "::"
+                + overridingMethod
+                + " overrides "
+                + overriddenType
+                + "::"
+                + overriddenMethod);
+      Effect eff = getDeclaredEffect(overriddenMethodElt);
+      if (eff.isSafe()) {
+        safeOverriden = overriddenMethodElt;
+        if (isUI) {
+          checker.reportError(
+              errorNode,
+              "override.effect",
+              declaringType,
+              overridingMethod,
+              overriddenType,
+              safeOverriden);
+        } else if (isPolyUI) {
+          checker.reportError(
+              errorNode,
+              "override.effect.polymorphic",
+              declaringType,
+              overridingMethod,
+              overriddenType,
+              safeOverriden);
+        }
+      } else if (eff.isUI()) {
+        uiOverriden = overriddenMethodElt;
+      } else {
+        assert eff.isPoly();
+        polyOverriden = overriddenMethodElt;
+        if (isUI) {
+          // Need to special case an anonymous class with @UI on the decl, because "new @UI Runnable
+          // {...}" parses as @UI on an anon class decl extending Runnable
+          boolean isAnonInstantiation =
+              isAnonymousType(declaringType)
+                  && (fromElement(declaringType).hasAnnotation(UI.class)
+                      || uiAnonClasses.contains(declaringType));
+          if (!isAnonInstantiation && !overriddenType.hasAnnotation(UI.class)) {
+            checker.reportError(
+                errorNode,
+                "override.effect.nonui",
+                declaringType,
+                overridingMethod,
+                overriddenType,
+                polyOverriden);
+          }
+        }
+      }
+    }
+
+    // We don't need to issue warnings for overriding both poly and a concrete effect.
+    if (uiOverriden != null && safeOverriden != null && issueConflictWarning) {
+      // There may be more than two parent methods, but for now it's
+      // enough to know there are at least 2 in conflict.
+      checker.reportWarning(
+          errorNode,
+          "override.effect.warning.inheritance",
+          declaringType,
+          overridingMethod,
+          uiOverriden.getEnclosingElement().asType(),
+          uiOverriden,
+          safeOverriden.getEnclosingElement().asType(),
+          safeOverriden);
+    }
+
+    Effect min =
+        (safeOverriden != null
+            ? new Effect(SafeEffect.class)
+            : (polyOverriden != null
+                ? new Effect(PolyUIEffect.class)
+                : (uiOverriden != null ? new Effect(UIEffect.class) : null)));
+    Effect max =
+        (uiOverriden != null
+            ? new Effect(UIEffect.class)
+            : (polyOverriden != null
+                ? new Effect(PolyUIEffect.class)
+                : (safeOverriden != null ? new Effect(SafeEffect.class) : null)));
+    if (debugSpew) {
+      System.err.println(
+          "Found "
+              + declaringType
+              + "."
+              + overridingMethod
+              + " to have inheritance pair ("
+              + min
+              + ","
+              + max
+              + ")");
+    }
+
+    if (min == null && max == null) {
+      return null;
+    } else {
+      return new Effect.EffectRange(min, max);
+    }
+  }
+
+  @Override
+  protected Set<? extends AnnotationMirror> getDefaultTypeDeclarationBounds() {
+    return qualHierarchy.getBottomAnnotations();
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(super.createTreeAnnotator(), new GuiEffectTreeAnnotator());
+  }
+
+  /**
+   * Force the given lambda expression to have UIEffect.
+   *
+   * <p>Used by GuiEffectVisitor to mark as UIEffect all lambdas that perform UIEffect calls inside
+   * their bodies.
+   *
+   * @param lambdaExpressionTree a lambda expression's AST node
+   */
+  public void constrainLambdaToUI(LambdaExpressionTree lambdaExpressionTree) {
+    uiLambdas.add(lambdaExpressionTree);
+  }
+
+  /**
+   * Force the given anonymous inner class to be an @UI instantiation of its base class.
+   *
+   * <p>Used by GuiEffectVisitor to mark as @UI all anonymous inner classes which: inherit from a
+   * PolyUIType annotated superclass, override a PolyUIEffect method from said superclass, and
+   * perform UIEffect calls inside the body of this method.
+   *
+   * @param classElt the TypeElement corresponding to the anonymous inner class to mark as an @UI
+   *     instantiation of an UI-polymorphic superclass.
+   */
+  public void constrainAnonymousClassToUI(TypeElement classElt) {
+    assert TypesUtils.isAnonymous(classElt.asType());
+    uiAnonClasses.add(classElt);
+  }
+
+  /** A class for adding annotations based on tree. */
+  private class GuiEffectTreeAnnotator extends TreeAnnotator {
+
+    GuiEffectTreeAnnotator() {
+      super(GuiEffectTypeFactory.this);
+    }
+
+    /*
+    public boolean hasExplicitUIEffect(ExecutableElement methElt) {
+        return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, UIEffect.class) != null;
+    }
+
+    public boolean hasExplicitSafeEffect(ExecutableElement methElt) {
+        return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, SafeEffect.class) != null;
+    }
+
+    public boolean hasExplicitPolyUIEffect(ExecutableElement methElt) {
+        return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, PolyUIEffect.class) != null;
+    }
+
+    public boolean hasExplicitEffect(ExecutableElement methElt) {
+        return hasExplicitUIEffect(methElt)
+                || hasExplicitSafeEffect(methElt)
+                || hasExplicitPolyUIEffect(methElt);
+    }
+    */
+
+    @Override
+    public Void visitMethod(MethodTree node, AnnotatedTypeMirror type) {
+      AnnotatedTypeMirror.AnnotatedExecutableType methType =
+          (AnnotatedTypeMirror.AnnotatedExecutableType) type;
+      // Effect e = getDeclaredEffect(methType.getElement());
+      TypeElement cls = (TypeElement) methType.getElement().getEnclosingElement();
+
+      // STEP 1: Get the method effect annotation
+      // if (!hasExplicitEffect(methType.getElement())) {
+      // TODO: This line does nothing!
+      // AnnotatedTypeMirror.addAnnotation silently ignores non-qualifier annotations!
+      // We should be digging up the /declaration/ of the method, and annotating that.
+      // methType.addAnnotation(e.getAnnot());
+      // }
+
+      // STEP 2: Fix up the method receiver annotation
+      AnnotatedTypeMirror.AnnotatedDeclaredType receiverType = methType.getReceiverType();
+      if (receiverType != null
+          && !receiverType.isAnnotatedInHierarchy(
+              AnnotationBuilder.fromClass(elements, UI.class))) {
+        receiverType.addAnnotation(
+            isPolymorphicType(cls)
+                ? PolyUI.class
+                : fromElement(cls).hasAnnotation(UI.class) ? UI.class : AlwaysSafe.class);
+      }
+      return super.visitMethod(node, type);
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java
new file mode 100644
index 0000000..f596b4d
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java
@@ -0,0 +1,586 @@
+package org.checkerframework.checker.guieffect;
+
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import org.checkerframework.checker.guieffect.qual.AlwaysSafe;
+import org.checkerframework.checker.guieffect.qual.PolyUI;
+import org.checkerframework.checker.guieffect.qual.PolyUIEffect;
+import org.checkerframework.checker.guieffect.qual.PolyUIType;
+import org.checkerframework.checker.guieffect.qual.SafeEffect;
+import org.checkerframework.checker.guieffect.qual.UI;
+import org.checkerframework.checker.guieffect.qual.UIEffect;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** Require that only UI code invokes code with the UI effect. */
+public class GuiEffectVisitor extends BaseTypeVisitor<GuiEffectTypeFactory> {
+
+  protected final boolean debugSpew;
+
+  // effStack and currentMethods should always be the same size.
+  protected final ArrayDeque<Effect> effStack;
+  protected final ArrayDeque<MethodTree> currentMethods;
+
+  public GuiEffectVisitor(BaseTypeChecker checker) {
+    super(checker);
+    debugSpew = checker.getLintOption("debugSpew", false);
+    if (debugSpew) {
+      System.err.println("Running GuiEffectVisitor");
+    }
+    effStack = new ArrayDeque<>();
+    currentMethods = new ArrayDeque<>();
+  }
+
+  @Override
+  protected GuiEffectTypeFactory createTypeFactory() {
+    return new GuiEffectTypeFactory(checker, debugSpew);
+  }
+
+  // The issue is that the receiver implicitly receives an @AlwaysSafe anno, so calls on @UI
+  // references fail because the framework doesn't implicitly upcast the receiver (which in
+  // general wouldn't be sound).
+  // TODO: Fix method receiver defaults: method-polymorphic for any polymorphic method, UI
+  //       for any UI instantiations, safe otherwise
+  @Override
+  protected void checkMethodInvocability(
+      AnnotatedExecutableType method, MethodInvocationTree node) {
+    // The inherited version of this complains about invoking methods of @UI instantiations of
+    // classes, which by default are annotated @AlwaysSafe, which for data type qualifiers is
+    // reasonable, but it not what we want, since we want .
+    // TODO: Undo this hack!
+  }
+
+  protected class GuiEffectOverrideChecker extends OverrideChecker {
+    /**
+     * Extend the receiver part of the method override check. We extend the standard check, to
+     * additionally permit narrowing the receiver's permission to {@code @AlwaysSafe} in a safe
+     * instantiation of a {@code @PolyUIType}. Returns true if the override is permitted.
+     */
+    @Override
+    protected boolean checkReceiverOverride() {
+      // We cannot reuse the inherited method because it directly issues the failure, but we
+      // want a more permissive check.  So this is copied down and modified from
+      // BaseTypeVisitor.OverrideChecker.checkReceiverOverride.
+      // isSubtype() requires its arguments to be actual subtypes with
+      // respect to JLS, but the overrider receiver is not a subtype of the
+      // overridden receiver.  Hence copying the annotations.
+      // TODO: Does this need to be improved for generic receivers?  I.e., do we need to
+      // add extra checking to reject the case of also changing qualifiers in type parameters?
+      // Such as overriding a {@code @PolyUI C<@UI T>} by {@code @AlwaysSafe C<@AlwaysSafe
+      // T>}?  The change to the receiver permission is acceptable, while the change to the
+      // parameter should be rejected.
+      AnnotatedTypeMirror overriddenReceiver =
+          overrider.getReceiverType().getErased().shallowCopy(false);
+      overriddenReceiver.addAnnotations(overridden.getReceiverType().getAnnotations());
+      if (!atypeFactory
+          .getTypeHierarchy()
+          .isSubtype(overriddenReceiver, overrider.getReceiverType().getErased())) {
+        // This is the point at which the default check would issue an error.
+        // We additionally permit overrides to move from @PolyUI receivers to @AlwaysSafe
+        // receivers, if it's in a @AlwaysSafe specialization of a @PolyUIType
+        boolean safeParent = overriddenType.getAnnotation(AlwaysSafe.class) != null;
+        boolean polyParentDecl =
+            atypeFactory.getDeclAnnotation(
+                    overriddenType.getUnderlyingType().asElement(), PolyUIType.class)
+                != null;
+        // TODO: How much validation do I need here?  Do I need to check that the overridden
+        // receiver was really @PolyUI and the method is really an @PolyUIEffect?  I don't think so
+        // - we know it's a polymorphic parent type, so all receivers would be @PolyUI.
+        // Java would already reject before running type annotation processors if the Java
+        // types were wrong.
+        // The *only* extra leeway we want to permit is overriding @PolyUI receiver to
+        // @AlwaysSafe.  But with generics, the tentative check below is inadequate.
+        boolean safeReceiverOverride =
+            overrider.getReceiverType().getAnnotation(AlwaysSafe.class) != null;
+        if (safeParent && polyParentDecl && safeReceiverOverride) {
+          return true;
+        }
+        checker.reportError(
+            overriderTree,
+            "override.receiver",
+            overrider.getReceiverType(),
+            overridden.getReceiverType(),
+            overriderType,
+            overrider,
+            overriddenType,
+            overridden);
+        return false;
+      }
+      return true;
+    }
+
+    /**
+     * Create a GuiEffectOverrideChecker.
+     *
+     * @param overriderTree the AST node of the overriding method or method reference
+     * @param overrider the type of the overriding method
+     * @param overridingType the type enclosing the overrider method, usually an
+     *     AnnotatedDeclaredType; for Method References may be something else
+     * @param overridingReturnType the return type of the overriding method
+     * @param overridden the type of the overridden method
+     * @param overriddenType the declared type enclosing the overridden method
+     * @param overriddenReturnType the return type of the overridden method
+     */
+    public GuiEffectOverrideChecker(
+        Tree overriderTree,
+        AnnotatedExecutableType overrider,
+        AnnotatedTypeMirror overridingType,
+        AnnotatedTypeMirror overridingReturnType,
+        AnnotatedExecutableType overridden,
+        AnnotatedDeclaredType overriddenType,
+        AnnotatedTypeMirror overriddenReturnType) {
+      super(
+          overriderTree,
+          overrider,
+          overridingType,
+          overridingReturnType,
+          overridden,
+          overriddenType,
+          overriddenReturnType);
+    }
+  }
+
+  @Override
+  protected OverrideChecker createOverrideChecker(
+      Tree overriderTree,
+      AnnotatedExecutableType overrider,
+      AnnotatedTypeMirror overridingType,
+      AnnotatedTypeMirror overridingReturnType,
+      AnnotatedExecutableType overridden,
+      AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType,
+      AnnotatedTypeMirror overriddenReturnType) {
+    return new GuiEffectOverrideChecker(
+        overriderTree,
+        overrider,
+        overridingType,
+        overridingReturnType,
+        overridden,
+        overriddenType,
+        overriddenReturnType);
+  }
+
+  @Override
+  protected Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotations() {
+    return Collections.singleton(AnnotationBuilder.fromClass(elements, AlwaysSafe.class));
+  }
+
+  @Override
+  public boolean isValidUse(
+      AnnotatedTypeMirror.AnnotatedDeclaredType declarationType,
+      AnnotatedTypeMirror.AnnotatedDeclaredType useType,
+      Tree tree) {
+    boolean ret =
+        useType.hasAnnotation(AlwaysSafe.class)
+            || useType.hasAnnotation(PolyUI.class)
+            || atypeFactory.isPolymorphicType(
+                (TypeElement) declarationType.getUnderlyingType().asElement())
+            || (useType.hasAnnotation(UI.class) && declarationType.hasAnnotation(UI.class));
+    if (debugSpew && !ret) {
+      System.err.println("use: " + useType);
+      System.err.println("use safe: " + useType.hasAnnotation(AlwaysSafe.class));
+      System.err.println("use poly: " + useType.hasAnnotation(PolyUI.class));
+      System.err.println("use ui: " + useType.hasAnnotation(UI.class));
+      System.err.println("declaration safe: " + declarationType.hasAnnotation(AlwaysSafe.class));
+      System.err.println(
+          "declaration poly: "
+              + atypeFactory.isPolymorphicType(
+                  (TypeElement) declarationType.getUnderlyingType().asElement()));
+      System.err.println("declaration ui: " + declarationType.hasAnnotation(UI.class));
+      System.err.println("declaration: " + declarationType);
+    }
+    return ret;
+  }
+
+  @Override
+  @SuppressWarnings("interning:not.interned") // comparing AST nodes
+  public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
+    Void v = super.visitLambdaExpression(node, p);
+    // If this is a lambda inferred to be @UI, scan up the path and re-check any assignments
+    // involving it.
+    if (atypeFactory.isDirectlyMarkedUIThroughInference(node)) {
+      // Backtrack path to the lambda expression itself
+      TreePath path = visitorState.getPath();
+      while (path.getLeaf() != node) {
+        assert path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT;
+        path = path.getParentPath();
+      }
+      scanUp(path);
+    }
+    return v;
+  }
+
+  @Override
+  protected void checkExtendsImplements(ClassTree classTree) {
+    // Skip this check
+  }
+
+  @Override
+  protected void checkConstructorResult(
+      AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {
+    // Skip this check.
+  }
+
+  @Override
+  protected void checkForPolymorphicQualifiers(ClassTree classTree) {
+    // Polymorphic qualifiers are legal on classes, so skip this check.
+  }
+
+  // Check that the invoked effect is <= permitted effect (effStack.peek())
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+    if (debugSpew) {
+      System.err.println("For invocation " + node + " in " + currentMethods.peek().getName());
+    }
+
+    // Target method annotations
+    ExecutableElement methodElt = TreeUtils.elementFromUse(node);
+    if (debugSpew) {
+      System.err.println("methodElt found");
+    }
+
+    Tree callerTree = TreePathUtil.enclosingMethodOrLambda(getCurrentPath());
+    if (callerTree == null) {
+      // Static initializer; let's assume this is safe to have the UI effect
+      if (debugSpew) {
+        System.err.println("No enclosing method: likely static initializer");
+      }
+      return super.visitMethodInvocation(node, p);
+    }
+    if (debugSpew) {
+      System.err.println("callerTree found: " + callerTree.getKind());
+    }
+
+    Effect targetEffect =
+        atypeFactory.getComputedEffectAtCallsite(node, visitorState.getMethodReceiver(), methodElt);
+
+    Effect callerEffect = null;
+    if (callerTree.getKind() == Tree.Kind.METHOD) {
+      ExecutableElement callerElt = TreeUtils.elementFromDeclaration((MethodTree) callerTree);
+      if (debugSpew) {
+        System.err.println("callerElt found");
+      }
+
+      callerEffect = atypeFactory.getDeclaredEffect(callerElt);
+      final DeclaredType callerReceiverType = this.visitorState.getClassType().getUnderlyingType();
+      assert callerReceiverType != null;
+      final TypeElement callerReceiverElt = (TypeElement) callerReceiverType.asElement();
+      // Note: All these checks should be fast in the common case, but happen for every method call
+      // inside the anonymous class. Consider a cache here if profiling surfaces this as taking too
+      // long.
+      if (TypesUtils.isAnonymous(callerReceiverType)
+          // Skip if already inferred @UI
+          && !effStack.peek().isUI()
+          // Ignore if explicitly annotated
+          && !atypeFactory.fromElement(callerReceiverElt).hasAnnotation(AlwaysSafe.class)
+          && !atypeFactory.fromElement(callerReceiverElt).hasAnnotation(UI.class)) {
+        boolean overridesPolymorphic = false;
+        Map<AnnotatedTypeMirror.AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
+            AnnotatedTypes.overriddenMethods(elements, atypeFactory, callerElt);
+        for (Map.Entry<AnnotatedTypeMirror.AnnotatedDeclaredType, ExecutableElement> pair :
+            overriddenMethods.entrySet()) {
+          AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType = pair.getKey();
+          AnnotatedExecutableType overriddenMethod =
+              AnnotatedTypes.asMemberOf(types, atypeFactory, overriddenType, pair.getValue());
+          if (atypeFactory.getDeclAnnotation(overriddenMethod.getElement(), PolyUIEffect.class)
+                  != null
+              && atypeFactory.getDeclAnnotation(
+                      overriddenType.getUnderlyingType().asElement(), PolyUIType.class)
+                  != null) {
+            overridesPolymorphic = true;
+            break;
+          }
+        }
+        // Perform anonymous class polymorphic effect inference:
+        // method overrides @PolyUIEffect method of @PolyUIClass class, calls @UIEffect =>
+        // @UI anon class
+        if (overridesPolymorphic && targetEffect.isUI()) {
+          // Mark the anonymous class as @UI
+          atypeFactory.constrainAnonymousClassToUI(callerReceiverElt);
+          // Then re-calculate this method's effect (it might still not be an
+          // @PolyUIEffect method).
+          callerEffect = atypeFactory.getDeclaredEffect(callerElt);
+          effStack.pop();
+          effStack.push(callerEffect);
+        }
+      }
+
+      // Field initializers inside anonymous inner classes show up with a null current-method
+      // --- the traversal goes straight from the class to the initializer.
+      assert (currentMethods.peek() == null || callerEffect.equals(effStack.peek()));
+    } else if (callerTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) {
+      callerEffect =
+          atypeFactory.getInferedEffectForLambdaExpression((LambdaExpressionTree) callerTree);
+      // Perform lambda polymorphic effect inference: @PolyUI lambda, calling @UIEffect => @UI
+      // lambda
+      if (targetEffect.isUI() && callerEffect.isPoly()) {
+        atypeFactory.constrainLambdaToUI((LambdaExpressionTree) callerTree);
+        callerEffect = new Effect(UIEffect.class);
+      }
+    }
+    assert callerEffect != null;
+
+    if (!Effect.lessThanOrEqualTo(targetEffect, callerEffect)) {
+      checker.reportError(node, "call.ui", targetEffect, callerEffect);
+      if (debugSpew) {
+        System.err.println("Issuing error for node: " + node);
+      }
+    }
+    if (debugSpew) {
+      System.err.println("Successfully finished main non-recursive checkinv of invocation " + node);
+    }
+    return super.visitMethodInvocation(node, p);
+  }
+
+  @Override
+  public Void visitMethod(MethodTree node, Void p) {
+    // TODO: If the type we're in is a polymorphic (over effect qualifiers) type, the receiver must
+    // be @PolyUI.  Otherwise a "non-polymorphic" method of a polymorphic type could be called on a
+    // UI instance, which then gets a Safe reference to itself (unsound!) that it can then pass off
+    // elsewhere (dangerous!).  So all receivers in methods of a @PolyUIType must be @PolyUI.
+
+    // TODO: What do we do then about classes that inherit from a concrete instantiation?  If it
+    // subclasses a Safe instantiation, all is well.  If it subclasses a UI instantiation, then the
+    // receivers should probably be @UI in both new and override methods, so calls to polymorphic
+    // methods of the parent class will work correctly.  In which case for proving anything, the
+    // qualifier on sublasses of UI instantiations would always have to be @UI... Need to write down
+    // |- t for this system!  And the judgments for method overrides and inheritance!  Those are
+    // actually the hardest part of the system.
+
+    ExecutableElement methElt = TreeUtils.elementFromDeclaration(node);
+    if (debugSpew) {
+      System.err.println("Visiting method " + methElt + " of " + methElt.getEnclosingElement());
+    }
+
+    // Check for conflicting (multiple) annotations
+    assert (methElt != null);
+    // TypeMirror scratch = methElt.getReturnType();
+    AnnotationMirror targetUIP = atypeFactory.getDeclAnnotation(methElt, UIEffect.class);
+    AnnotationMirror targetSafeP = atypeFactory.getDeclAnnotation(methElt, SafeEffect.class);
+    AnnotationMirror targetPolyP = atypeFactory.getDeclAnnotation(methElt, PolyUIEffect.class);
+    TypeElement targetClassElt = (TypeElement) methElt.getEnclosingElement();
+
+    if ((targetUIP != null && (targetSafeP != null || targetPolyP != null))
+        || (targetSafeP != null && targetPolyP != null)) {
+      checker.reportError(node, "annotations.conflicts");
+    }
+    if (targetPolyP != null && !atypeFactory.isPolymorphicType(targetClassElt)) {
+      checker.reportError(node, "polymorphism");
+    }
+    if (targetUIP != null && atypeFactory.isUIType(targetClassElt)) {
+      checker.reportWarning(node, "effects.redundant.uitype");
+    }
+
+    // TODO: Report an error for polymorphic method bodies??? Until we fix the receiver
+    // defaults, it won't really be correct
+    @SuppressWarnings("unused") // call has side effects
+    Effect.EffectRange range =
+        atypeFactory.findInheritedEffectRange(
+            ((TypeElement) methElt.getEnclosingElement()), methElt, true, node);
+    // if (targetUIP == null && targetSafeP == null && targetPolyP == null) {
+    // implicitly annotate this method with the LUB of the effects of the methods it overrides
+    // atypeFactory.fromElement(methElt).addAnnotation(range != null ? range.min.getAnnot()
+    // : (isUIType(((TypeElement)methElt.getEnclosingElement())) ? UI.class :
+    // AlwaysSafe.class));
+    // TODO: This line does nothing! AnnotatedTypeMirror.addAnnotation
+    // silently ignores non-qualifier annotations!
+    // System.err.println("ERROR: TREE ANNOTATOR SHOULD HAVE ADDED EXPLICIT ANNOTATION! ("
+    //     +node.getName()+")");
+    // atypeFactory
+    //         .fromElement(methElt)
+    //         .addAnnotation(atypeFactory.getDeclaredEffect(methElt).getAnnot());
+    // }
+
+    // We hang onto the current method here for ease.  We back up the old
+    // current method because this code is reentrant when we traverse methods of an inner class
+    currentMethods.addFirst(node);
+    // effStack.push(targetSafeP != null ? new Effect(AlwaysSafe.class) :
+    //                (targetPolyP != null ? new Effect(PolyUI.class) :
+    //                   (targetUIP != null ? new Effect(UI.class) :
+    //                      (range != null ? range.min :
+    // (isUIType(((TypeElement)methElt.getEnclosingElement())) ? new Effect(UI.class) : new
+    // Effect(AlwaysSafe.class))))));
+    effStack.addFirst(atypeFactory.getDeclaredEffect(methElt));
+    if (debugSpew) {
+      System.err.println("Pushing " + effStack.peek() + " onto the stack when checking " + methElt);
+    }
+
+    Void ret = super.visitMethod(node, p);
+    currentMethods.removeFirst();
+    effStack.removeFirst();
+    return ret;
+  }
+
+  @Override
+  @SuppressWarnings("interning:not.interned") // comparing AST nodes
+  public Void visitNewClass(NewClassTree node, Void p) {
+    Void v = super.visitNewClass(node, p);
+    // If this is an anonymous inner class inferred to be @UI, scan up the path and re-check any
+    // assignments involving it.
+    if (atypeFactory.isDirectlyMarkedUIThroughInference(node)) {
+      // Backtrack path to the new class expression itself
+      TreePath path = visitorState.getPath();
+      while (path.getLeaf() != node) {
+        assert path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT;
+        path = path.getParentPath();
+      }
+      scanUp(visitorState.getPath().getParentPath());
+    }
+    return v;
+  }
+
+  /**
+   * This method is called to traverse the path back up from any anonymous inner class or lambda
+   * which has been inferred to be UI affecting and re-run {@code commonAssignmentCheck} as needed
+   * on places where the class declaration or lambda expression are being assigned to a variable,
+   * passed as a parameter or returned from a method. This is necessary because the normal visitor
+   * traversal only checks assignments on the way down the AST, before inference has had a chance to
+   * run.
+   *
+   * @param path the path to traverse up from a UI-affecting class
+   */
+  private void scanUp(TreePath path) {
+    Tree tree = path.getLeaf();
+    switch (tree.getKind()) {
+      case ASSIGNMENT:
+        AssignmentTree assignmentTree = (AssignmentTree) tree;
+        commonAssignmentCheck(
+            atypeFactory.getAnnotatedType(assignmentTree.getVariable()),
+            atypeFactory.getAnnotatedType(assignmentTree.getExpression()),
+            assignmentTree.getExpression(),
+            "assignment");
+        break;
+      case VARIABLE:
+        VariableTree variableTree = (VariableTree) tree;
+        commonAssignmentCheck(
+            atypeFactory.getAnnotatedType(variableTree),
+            atypeFactory.getAnnotatedType(variableTree.getInitializer()),
+            variableTree.getInitializer(),
+            "assignment");
+        break;
+      case METHOD_INVOCATION:
+        MethodInvocationTree invocationTree = (MethodInvocationTree) tree;
+        List<? extends ExpressionTree> args = invocationTree.getArguments();
+        ParameterizedExecutableType mType = atypeFactory.methodFromUse(invocationTree);
+        AnnotatedExecutableType invokedMethod = mType.executableType;
+        ExecutableElement method = invokedMethod.getElement();
+        CharSequence methodName = ElementUtils.getSimpleNameOrDescription(method);
+        List<? extends VariableElement> methodParams = method.getParameters();
+        List<AnnotatedTypeMirror> argsTypes =
+            AnnotatedTypes.expandVarArgs(
+                atypeFactory, invokedMethod, invocationTree.getArguments());
+        for (int i = 0; i < args.size(); ++i) {
+          if (args.get(i).getKind() == Tree.Kind.NEW_CLASS
+              || args.get(i).getKind() == Tree.Kind.LAMBDA_EXPRESSION) {
+            commonAssignmentCheck(
+                argsTypes.get(i),
+                atypeFactory.getAnnotatedType(args.get(i)),
+                args.get(i),
+                "argument",
+                methodParams.get(i),
+                methodName);
+          }
+        }
+        break;
+      case RETURN:
+        ReturnTree returnTree = (ReturnTree) tree;
+        if (returnTree.getExpression().getKind() == Tree.Kind.NEW_CLASS
+            || returnTree.getExpression().getKind() == Tree.Kind.LAMBDA_EXPRESSION) {
+          Tree enclosing = TreePathUtil.enclosingMethodOrLambda(path);
+          AnnotatedTypeMirror ret = null;
+          if (enclosing.getKind() == Tree.Kind.METHOD) {
+            MethodTree enclosingMethod = (MethodTree) enclosing;
+            boolean valid = validateTypeOf(enclosing);
+            if (valid) {
+              ret = atypeFactory.getMethodReturnType(enclosingMethod, returnTree);
+            }
+          } else {
+            ret =
+                atypeFactory
+                    .getFunctionTypeFromTree((LambdaExpressionTree) enclosing)
+                    .getReturnType();
+          }
+
+          if (ret != null) {
+            Pair<Tree, AnnotatedTypeMirror> preAssignmentContext =
+                visitorState.getAssignmentContext();
+            try {
+              visitorState.setAssignmentContext(Pair.of((Tree) returnTree, ret));
+              commonAssignmentCheck(
+                  ret,
+                  atypeFactory.getAnnotatedType(returnTree.getExpression()),
+                  returnTree.getExpression(),
+                  "return");
+            } finally {
+              visitorState.setAssignmentContext(preAssignmentContext);
+            }
+          }
+        }
+        break;
+      case METHOD:
+        // Stop scanning at method boundaries, since the expression can't escape the method
+        // without either being assigned to a field or returned.
+        return;
+      case CLASS:
+        // Can't ever happen, because we stop scanning at either method or field initializer
+        // boundaries
+        assert false;
+        return;
+      default:
+        scanUp(path.getParentPath());
+    }
+  }
+
+  // @Override
+  // public Void visitMemberSelect(MemberSelectTree node, Void p) {
+  // TODO: Same effect checks as for methods
+  // return super.visitMemberSelect(node, p);
+  // }
+
+  // @Override
+  // public void processClassTree(ClassTree node) {
+  // TODO: Check constraints on this class decl vs. parent class decl., and interfaces
+  // TODO: This has to wait for now: maybe this will be easier with the isValidUse on the
+  // TypeFactory.
+  // AnnotatedTypeMirror.AnnotatedDeclaredType atype = atypeFactory.fromClass(node);
+
+  // Push a null method and UI effect onto the stack for static field initialization
+  // TODO: Figure out if this is safe! For static data, almost certainly,
+  // but for statically initialized instance fields, I'm assuming those
+  // are implicitly moved into each constructor, which must then be @UI.
+  // currentMethods.addFirst(null);
+  // effStack.addFirst(new Effect(UIEffect.class));
+  // super.processClassTree(node);
+  // currentMethods.removeFirst();
+  // effStack.removeFirst();
+  // }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/jdk8.astub b/checker/src/main/java/org/checkerframework/checker/guieffect/jdk8.astub
new file mode 100644
index 0000000..ae5ba18
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/guieffect/jdk8.astub
@@ -0,0 +1,27 @@
+// This file is for classes that appear in JDK 8 but not in JDK 11.
+
+@UIPackage
+package javax.swing;
+
+import org.checkerframework.checker.guieffect.qual.*;
+@UIType class JComponent extends Container implements Serializable, HasGetTransferHandler {
+    @SafeEffect void revalidate();
+}
+
+@UIType class SwingUtilities implements SwingConstants {
+    @SafeEffect static void invokeLater(@UI Runnable doRun);
+    @SafeEffect static void invokeAndWait(@UI Runnable doRun);
+}
+
+@UIType class UIManager implements Serializable {
+    @SafeEffect static void setLookAndFeel(LookAndFeel newLookAndFeel);
+    @SafeEffect static void setLookAndFeel(String classname);
+}
+
+@UIPackage package javax.swing.text;
+public abstract class JTextComponent extends JComponent implements Scrollable, Accessible {
+    @SafeEffect public void setText(String t);
+}
+
+@UIPackage
+package javax.swing.event;
diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/messages.properties b/checker/src/main/java/org/checkerframework/checker/guieffect/messages.properties
new file mode 100644
index 0000000..30c6b87
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/guieffect/messages.properties
@@ -0,0 +1,9 @@
+call.ui=Calling a method with %s effect from a context limited to %s effects.
+annotations.conflicts=A method may only have one effect annotation from @UI, @AlwaysSafe, and @PolyUI.
+override.effect=A method override may only be @UI if it overrides an @UI method.%nmethod in %s%n  %s%n  cannot override method in %s%n  %s
+override.effect.polymorphic=A method override may only be @PolyUIEffect if it overrides a @PolyUIEffect method.%nmethod in %s%n  %s%n  cannot override method in %s%n  %s
+override.effect.nonui=A method override may only be @UI if it overrides an @UI method (overriding non-UI instantiation of supertype).%nmethod in %s%n  %s%n  cannot override method in %s%n  %s
+override.effect.warning.inheritance=method in %s%n  %s%noverrides a method with @UI effect in %s%n  %s%nand another method with an @AlwaysSafe effect in %s%n  %s%nThis is discouraged.
+polymorphism=Only @PolyUIType types may have @PolyUIEffect methods.
+inheritance.polymorphic=An effect-polymorphic type may only inherit from another effect-polymorphic type (%s extends/implements %s).
+effects.redundant.uitype=This method is annotated @UIEffect, which is redundant because the enclosing type is @UIType.
diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/org-eclipse.astub b/checker/src/main/java/org/checkerframework/checker/guieffect/org-eclipse.astub
new file mode 100644
index 0000000..fa7e182
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/guieffect/org-eclipse.astub
@@ -0,0 +1,569 @@
+import org.checkerframework.checker.guieffect.qual.*;
+
+package org.eclipse.compare;
+@UIType public abstract class CompareEditorInput extends PlatformObject implements IEditorInput, IPropertyChangeNotifier, IRunnableWithProgress, ICompareContainer {
+    @SafeEffect public CompareConfiguration getCompareConfiguration();
+    @SafeEffect public void setTitle(String title);
+    @SafeEffect public boolean isSaveNeeded();
+    @SafeEffect public void save(IProgressMonitor pm);
+    @SafeEffect public void saveChanges(IProgressMonitor monitor) throws CoreException;
+    @SafeEffect protected void setMessage(String message);
+    @SafeEFfect public String getMessage();
+}
+
+package org.eclipse.core.commands;
+@PolyUIType public interface IHandler {
+    @SafeEffect void addHandlerListener(IHandlerListener handlerListener);
+    @SafeEffect void dispose();
+    @PolyUIEffect Object execute(@PolyUI IHandler this, ExecutionEvent event);
+    @SafeEffect boolean isEnabled();
+    @SafeEffect boolean isHandled();
+    @SafeEffect void removeHandlerListener(IHandlerListener handlerListener);
+
+}
+
+package org.eclipse.core.expression;
+public interface IPropertyTester {
+    // XXX Not sure if this should be UIEffect or poly
+    @UIEffect boolean test(Object receiver, String property, Object[] args, Object expectedValue);
+}
+public abstract class PropertyTester implements IPropertyTester {
+    // XXX Not sure if this should be UIEffect or poly
+    @UIEffect boolean test(Object receiver, String property, Object[] args, Object expectedValue);
+}
+
+package org.eclipse.core.runtime;
+@PolyUIType public interface IProgressMonitor {
+    @PolyUIEffect void beginTask(@PolyUI IProgressMonitor this, String name, int totalWork);
+    @PolyUIEffect void done(@PolyUI IProgressMonitor this);
+    @PolyUIEffect void internalWorked(@PolyUI IProgressMonitor this, double work);
+    @PolyUIEffect boolean isCanceled(@PolyUI IProgressMonitor this);
+    @PolyUIEffect void setCanceled(@PolyUI IProgressMonitor this, boolean value);
+    @PolyUIEffect void setTaskName(@PolyUI IProgressMonitor this, String name);
+    @PolyUIEffect void subTask(@PolyUI IProgressMonitor this, String name);
+    @PolyUIEffect void worked(@PolyUI IProgressMonitor this, int work);
+}
+@PolyUIType
+public abstract class Plugin implements BundleActivator {
+    // These methods are present on all plugins, UI or not
+    @PolyUIEffect void start(@PolyUI Plugin this, BundleContext context);
+    @PolyUIEffect void stop(@PolyUI Plugin this, BundleContext context);
+    @SafeEffect public final Preferences getPluginPreferences();
+}
+public class Preferences extends Object {
+    public static interface IPropertyChangeListener extends EventListener {
+        @PolyUIEffect public void propertyChange(@PolyUI Preferences$IPropertyChangeListener this, Preferences.PropertyChangeEvent event);
+    }
+}
+package org.eclipse.core.runtime.jobs;
+@PolyUIType
+public abstract class Job extends org.eclipse.core.internal.jobs.InternalJob implements IAdaptable {
+    @PolyUIEffect protected abstract IStatus run(@PolyUI Job this, IProgressMonitor monitor);
+}
+
+@UIPackage package org.eclipse.debug.ui;
+@UIPackage package org.eclipse.debug.ui.actions;
+@UIPackage package org.eclipse.debug.ui.console;
+@UIPackage package org.eclipse.debug.ui.contexts;
+@UIPackage package org.eclipse.debug.ui.memory;
+@UIPackage package org.eclipse.debug.ui.sourcelookup;
+@UIPackage package org.eclipse.draw2d;
+@UIPackage package org.eclipse.draw2d.geometry;
+@UIPackage package org.eclipse.draw2d.parts;
+@UIPackage package org.eclipse.gef;
+@UIPackage package org.eclipse.gef.editparts;
+@UIPackage package org.eclipse.gef.editpolicies;
+@UIPackage package org.eclipse.gef.tools;
+@UIPackage package org.eclipse.gef.ui;
+@UIPackage package org.eclipse.gef.ui.actions;
+@UIPackage package org.eclipse.gef.ui.parts;
+@UIPackage package org.eclipse.help.ui;
+@UIPackage package org.eclipse.help.ui.browser;
+@UIPackage package org.eclipse.jface.action;
+//@PolyUIType
+//public interface IAction {
+//    // TODO: IAction and Action are all-safe interfaces!
+//    @PolyUIEffect public void run();
+//    @PolyUIEffect public void runWithEvent(Event event);
+//}
+//@PolyUIType
+//public abstract class Action extends AbstractAction implements IAction {
+//    // TODO: IAction and Action are all-safe interfaces!
+//    @PolyUIEffect public void run();
+//    @PolyUIEffect public void runWithEvent(Event event);
+//}
+//public interface IContributionManager {
+//    @UIEffect public void add(@UI IAction action);
+//}
+@SafeType interface IAction {
+    @UIEffect public void run();
+    @UIEffect public void runWithEvent(Event event);
+}
+@SafeType class Action extends AbstractAction implements IAction {
+    @UIEffect public void run();
+    @UIEffect public void runWithEvent(Event event);
+}
+public interface IContributionManager {
+    @SafeEffect public void add(IAction action);
+    @SafeEffect public void add(IContributionItem item);
+}
+public interface IMenuManager extends IContributionManager, IContributionItem {
+    @SafeEffect public void addMenuListener(IMenuListener listener);
+    @SafeEffect public void setRemoveAllWhenShown(boolean removeAll);
+}
+
+@UIPackage package org.eclipse.jface.bindings;
+@UIPackage package org.eclipse.jface.bindings.keys;
+@UIPackage package org.eclipse.jface.bindings.keys.formatting;
+@UIPackage package org.eclipse.jface.commands;
+@UIPackage package org.eclipse.jface.contentassist;
+@UIPackage package org.eclipse.jface.contexts;
+@UIPackage package org.eclipse.jface.databinding.dialog;
+@UIPackage package org.eclipse.jface.databinding.preference;
+@UIPackage package org.eclipse.jface.databinding.swt;
+@UIPackage package org.eclipse.jface.databinding.util;
+@UIPackage package org.eclipse.jface.databinding.viewers;
+@UIPackage package org.eclipse.jface.databinding.wizard;
+@UIPackage package org.eclipse.jface.dialogs;
+public abstract class Dialog extends Window {
+    // XXX: Not 100% sure about this
+    // XXX: Reproduced here because if Window is annotated @UIType, it doesn't inherit the weaker override!
+    @SafeEffect void getShell();
+}
+// XXX: Not 100% sure about this
+@SafeType public interface IDialogSettings {}
+
+@UIPackage package org.eclipse.jface.fieldassist;
+@UIPackage package org.eclipse.jface.layout;
+@UIPackage package org.eclipse.jface.menus;
+@UIPackage package org.eclipse.jface.operation;
+@UIPackage package org.eclipse.jface.preference;
+public interface IPreferenceStore {
+    @SafeEffect void addPropertyChangeListener(IPropertyChangeListener listener);
+    // TODO: ALL methods of this type should be safe!
+    @SafeEffect boolean contains(String name);
+    @SafeEffect void firePropertyChangeEvent(String name, Object oldValue, Object newValue);
+    @SafeEffect boolean getBoolean(String name);
+    @SafeEffect boolean getDefaultBoolean(String name);
+    @SafeEffect double getDefaultDouble(String name);
+    @SafeEffect float getDefaultFloat(String name);
+    @SafeEffect int getDefaultInt(String name);
+    @SafeEffect long getDefaultLong(String name);
+    @SafeEffect String getDefaultString(String name);
+    @SafeEffect double getDouble(String name);
+    @SafeEffect float getFloat(String name);
+    @SafeEffect int getInt(String name);
+    @SafeEffect long getLong(String name);
+    @SafeEffect String getString(String name);
+    @SafeEffect boolean isDefault(String name);
+    @SafeEffect boolean needsSaving();
+    @SafeEffect void putValue(String name, String value);
+    @SafeEffect void removePropertyChangeListener(IPropertyChangeListener listener);
+    @SafeEffect void setDefault(String name, boolean value);
+    @SafeEffect void setDefault(String name, double value);
+    @SafeEffect void setDefault(String name, float value);
+    @SafeEffect void setDefault(String name, int value);
+    @SafeEffect void setDefault(String name, long value);
+    @SafeEffect void setDefault(String name, String defaultObject);
+    @SafeEffect void setToDefault(String name);
+    @SafeEffect void setValue(String name, boolean value);
+    @SafeEffect void setValue(String name, double value);
+    @SafeEffect void setValue(String name, float value);
+    @SafeEffect void setValue(String name, int value);
+    @SafeEffect void setValue(String name, long value);
+    @SafeEffect void setValue(String name, String value);
+}
+public abstract class PreferencePage extends DialogPage implements IPreferencePage {
+    @SafeEffect protected IPreferenceStore doGetPreferenceStore();
+}
+public class ColorSelector extends EventManager {
+    void addListener(@UI IPropertyChangeListener listener);
+    void removeListener(@UI IPropertyChangeListener listener);
+}
+public abstract class FieldEditor {
+    public void setPropertyChangeListener(@UI IPropertyChangeListener listener);
+}
+// XXX: Not 100% on this
+@SafeType public class PreferenceConverter {}
+@UIPackage package org.eclipse.jface.resource;
+// XXX: Not 100% on this
+@SafeType public class JFaceResources {
+    // This method is explicitly documented as UI-only
+    @UIEffect public static ResourceManager getResources();
+    // This method is NOT documented as ui-only, but the above does something safe and then calls this... might just be a nullness issue, though (Display.getCurrent())
+    @UIEffect public static ResourceManager getResources(Display toQuery);
+}
+@SafeType public class StringConverter {}
+public class ImageRegistry {
+    @SafeEffect ImageDescriptor getDescriptor(String key);
+    @SafeEffect public Image get(String key);
+}
+public abstract class ImageDescriptor extends DeviceResourceDescriptor {
+    @SafeEffect public static ImageDescriptor createFromURL(URL url);
+}
+public class FontRegistry extends ResourceRegistry {
+    @SafeEffect public Font get(String symbolicName);
+}
+public abstract class ResourceRegistry extends EventManager {
+    @SafeEffect public void addListener(IPropertyChangeListener listener);
+    @SafeEffect public void removeListener(IPropertyChangeListener listener);
+}
+
+@UIPackage package org.eclipse.jface.text;
+@SafeType public class DocumentEvent{}
+
+// XXX: Not 100% on this
+@SafeType public interface IDocument{}
+@SafeType public abstract class AbstractDocument implements IDocument, IDocumentExtension, IDocumentExtension2, IDocumentExtension3, IDocumentExtension4, IRepairableDocument, IRepairableDocumentExtension {}
+@SafeType public interface ITextStore{}
+@SafeType public class GapTextStore implements ITextStore {}
+@SafeType public class CopyOnWriteTextStore implements ITextStore {}
+@SafeType public interface ILineTracker {}
+@SafeType public class DefaultLineTracker extends AbstractLineTracker {}
+@SafeType public class ConfigurableLineTracker extends AbstractLineTracker {}
+@SafeType public abstract class AbstractLineTracker implements ILineTracker, ILineTrackerExtension {}
+public interface ITextViewer {
+    @SafeEffect public ITextOperationTarget getTextOperationTarget();
+    @SafeEffect public IDocument getDocument();
+}
+
+
+@UIPackage package org.eclipse.jface.text.contentassist;
+@UIPackage package org.eclipse.jface.text.formatter;
+@UIPackage package org.eclipse.jface.text.hyperlink;
+@UIPackage package org.eclipse.jface.text.information;
+@UIPackage package org.eclipse.jface.text.link;
+@UIPackage package org.eclipse.jface.text.presentation;
+@UIPackage package org.eclipse.jface.text.projection;
+@UIPackage package org.eclipse.jface.text.quickassist;
+@UIPackage package org.eclipse.jface.text.reconciler;
+@UIPackage package org.eclipse.jface.text.revisions;
+// XXX: Not 100%, but everything in jface.text.rules seems to be safe
+//@UIPackage package org.eclipse.jface.text.rules;
+@UIPackage package org.eclipse.jface.text.source;
+@UIPackage package org.eclipse.jface.text.source.projection;
+@UIPackage package org.eclipse.jface.text.templates;
+@UIPackage package org.eclipse.jface.text.templates.persistence;
+@UIPackage package org.eclipse.jface.util;
+public class PropertyChangeEvent extends EventObject {
+    @SafeEffect PropertyChangeEvent(Object source, String property, Object oldValue, Object newValue);
+    @SafeEffect Object getNewValue();
+    @SafeEffect Object getOldValue();
+    @SafeEffect String getProperty();
+}
+@PolyUIType public interface IPropertyChangeListener extends EventListener {
+    @PolyUIEffect void propertyChange(@PolyUI IPropertyChangeListener this, PropertyChangeEvent event);
+}
+@UIPackage package org.eclipse.jface.viewers;
+public abstract class ContentViewer extends Viewer {
+    @SafeEffect public Object getInput();
+}
+public abstract class StructuredViewer extends ContentViewer implements IPostSelectionProvider {
+    // @SafeEffect void refresh(); -- Why the hell did I ever mark this safe???
+}
+public class TreeViewer extends AbstractTreeViewer {
+    @SafeEffect Tree getTree();
+    @SafeEffect Control getControl();
+}
+@SafeType public interface IStructuredSelection extends ISelection {
+}
+@UIPackage package org.eclipse.jface.viewers.deferred;
+@UIPackage package org.eclipse.jface.window;
+public interface IShellProvider {
+    // XXX: Not 100% sure about this
+    @SafeEffect void getShell();
+}
+public abstract class Window implements IShellProvider {
+    // XXX: Not 100% sure about this
+    // XXX: Reproduced here because if Window is annotated @UIType, it doesn't inherit the weaker override!
+    @SafeEffect void getShell();
+}
+@UIPackage package org.eclipse.jface.wizard;
+@UIPackage package org.eclipse.jsch.ui;
+@UIPackage package org.eclipse.ltk.ui.refactoring;
+@UIPackage package org.eclipse.ltk.ui.refactoring.actions;
+@UIPackage package org.eclipse.ltk.ui.refactoring.history;
+@UIPackage package org.eclipse.ltk.ui.refactoring.model;
+@UIPackage package org.eclipse.ltk.ui.refactoring.resource;
+@UIPackage package org.eclipse.search.ui;
+@UIPackage package org.eclipse.search.ui.actions;
+@UIPackage package org.eclipse.search.ui.text;
+
+/*
+ * SWT
+ */
+@UIPackage package org.eclipse.swt;
+@UIPackage package org.eclipse.swt.accessibility;
+@UIPackage package org.eclipse.swt.awt;
+@UIPackage package org.eclipse.swt.browser;
+@UIPackage package org.eclipse.swt.custom;
+
+public class BusyIndicator {
+    @UIEffect public static void showWhile(Display display, @UI Runnable runnable);
+}
+
+@UIPackage package org.eclipse.swt.dnd;
+@SafeType public abstract class Transfer {}
+@SafeType public abstract class ByteArrayTransfer extends Transfer {}
+
+@UIPackage package org.eclipse.swt.events;
+@UIPackage package org.eclipse.swt.graphics;
+// XXX: Not 100%, but this seems safe.  At least the .equals(), which is what my case study needs
+public final class RGB extends Object implements org.eclipse.swt.internal.SerializableCompatibility {
+    @SafeEffect public boolean equals(Object object);
+}
+public final class Font extends Resource {
+    // Another SWT class that happens to work on other threads... (https://bugs.eclipse.org/bugs/show_bug.cgi?id=241062)
+    @SafeEffect FontData[] getFontData();
+}
+public abstract class Resource {
+    // XXX not 100% - the impl appears safe, but this is probably not the intended model
+    @SafeEffect public void dispose();
+}
+public final class Image extends Resource implements Drawable {
+    @SafeEffect public boolean equals(Object object);
+    @SafeEffect public int hashCode();
+}
+
+@UIPackage package org.eclipse.swt.layout;
+@UIPackage package org.eclipse.swt.ole.win32;
+@UIPackage package org.eclipse.swt.opengl;
+@UIPackage package org.eclipse.swt.printing;
+@UIPackage package org.eclipse.swt.program;
+@UIPackage package org.eclipse.swt.widgets;
+// These are the only two safe methods in all of SWT according to the docs
+@UIType class Display extends Device {
+    @SafeEffect public void syncExec(@UI Runnable runnable);
+    @SafeEffect public void asyncExec(@UI Runnable runnable);
+    // Not 100% sure about this one
+    @SafeEffect public static Display getDefault();
+    @SafeEffect public static Display getCurrent();
+}
+public class Shell extends Decorations {
+    // XXX: Actually inherited, as @SafeEffect, from Widget
+    @SafeEffect public Display getDisplay();
+}
+public abstract class Widget {
+    // XXX: Not 100% on this
+    @SafeEffect public Display getDisplay();
+    @SafeEffect public boolean isDisposed();
+}
+
+// XXX Not 100% on these internal packages
+@UIPackage package org.eclipse.team.internal.ui;
+@UIPackage package org.eclipse.team.internal.ui.synchronize;
+public abstract class SyncInfoSetChangeSetCollector extends ChangeSetManager {
+    @SafeEffect protected final void performUpdate(IWorkspaceRunnable runnable, boolean preserveExpansion, IProgressMonitor monitor);
+}
+
+
+@UIPackage package org.eclipse.team.ui;
+// XXX This TeamOperation class is messy for us: canRunAsJob() determines whether the class has UI
+// effects or safe effects.  Some of these methods need to be marked safe because they're to be used
+// by both variants.  Because there are issues with valid types, I'm making this type safe for now,
+// and I'll suffer the extra warnings until I decide whether or not I want to go polymorphic.
+@SafeType public abstract class TeamOperation extends JobChangeAdapter implements IRunnableWithProgress {
+}
+@SafeType public class TeamImages {}
+
+@UIPackage package org.eclipse.team.ui.history;
+public abstract class HistoryPage extends Page implements IHistoryPage, IAdaptable {
+    @SafeEffect public Object getInput();
+}
+
+@UIPackage package org.eclipse.team.ui.synchronize;
+public interface ISynchronizeModelElement extends IDiffContainer, ITypedElement, ICompareInput {
+    @SafeEffect IResource getResource();
+}
+public abstract class SubscriberParticipant extends AbstractSynchronizeParticipant implements IPropertyChangeListener {
+    @SafeEffect public final IStatus refreshNow(IResource[] resources, String taskName, IProgressMonitor monitor);
+    @SafeEffect public final void refresh(IResource[] resources, String shortTaskName, String longTaskName, IWorkbenchSite site);
+    @SafeEffect protected String getLongTaskName(IResource[] resources);
+    @SafeEffect public IResource[] getResources();
+    @SafeEffect public void reset();
+}
+public abstract class SynchronizeModelOperation extends TeamOperation {
+    @SafeEffect protected SyncInfoSet getSyncInfoSet();
+}
+
+@UIPackage package org.eclipse.ui;
+public final class PlatformUI {
+    // XXX Not 100%
+    @SafeEffect public static IWorkbench getWorkbench();
+    @SafeEffect public static boolean isWorkbenchRunning();
+}
+// XXX Not 100% about these "safe" types
+@SafeType public interface IWorkbench extends IAdaptable, IServiceLocator {}
+public interface IWorkbenchPart extends IAdaptable {
+    @SafeEffect IWorkbenchPartSite getSite();
+}
+public interface IWorkbenchPartSite extends IWorkbenchSite {
+    @SafeEffect IWorkbenchPart getPart();
+}
+@SafeType public interface IWorkbenchWindow extends IPageService, IRunnableContext, IServiceLocator, IShellProvider {}
+public interface IWorkbenchSite extends IAdaptable, IShellProvider, IServiceLocator {
+    @SafeEffect public Shell getShell(); // XXX: This is specified by IShellProvider, but the docs for IWorkbenchSite say this is ui safe only for compat, and the result may be wrong
+    @SafeEffect IWorkbenchPage getPage();
+}
+public interface IFileEditorInput extends IStorageEditorInput {
+    // XXX Not 100%
+    @SafeEffect IFile getFile();
+}
+public interface IPathEditorInput extends IEditorInput {
+    @SafeEffect IPath getPath();
+}
+@SafeType public interface IEditorRegistry {}
+@SafeType public interface IEditorDescriptor extends IWorkbenchPartDescriptor {}
+public interface IEditorPart extends IWorkbenchPart, ISaveablePart {
+    // XXX not 100% sure
+    @SafeEffect public IEditorInput getEditorInput();
+}
+public interface IPartService {
+    @SafeEffect IWorkbenchPart getActivePart();
+}
+
+
+@UIPackage package org.eclipse.ui.about;
+@UIPackage package org.eclipse.ui.actions;
+public abstract class BaseSelectionListenerAction extends Action implements ISelectionChangedListener {
+    // XXX every impl of this simply returns a member, but I can't tell if this is actually (race) safe
+    @SafeEffect public IStructuredSelection getStructuredSelection();
+}
+
+@UIPackage package org.eclipse.ui.activities;
+@UIPackage package org.eclipse.ui.application;
+@UIPackage package org.eclipse.ui.branding;
+@UIPackage package org.eclipse.ui.browser;
+@UIPackage package org.eclipse.ui.cheatsheets;
+@UIPackage package org.eclipse.ui.commands;
+@UIPackage package org.eclipse.ui.console;
+// XXX: Not 100% on these console types
+@SafeType public interface IConsole {}
+@SafeType public interface IConsoleManager {}
+@SafeType public abstract class TextConsole extends AbstractConsole {
+    @UIEffect public Font getFont();
+}
+@SafeType public class ConsolePlugin extends AbstractUIPlugin {}
+@SafeType public class IOConsole extends TextConsole{}
+@SafeType public class IOConsoleInputStream extends InputStream{}
+@SafeType public class IOConsoleOutputStream extends OutputStream{}
+
+@UIPackage package org.eclipse.ui.console.actions;
+@UIPackage package org.eclipse.ui.contentassist;
+@UIPackage package org.eclipse.ui.contexts;
+@UIPackage package org.eclipse.ui.databinding;
+@UIPackage package org.eclipse.ui.dialogs;
+public class FilteredTree extends Composite {
+    // XXX Not 100%
+    @SafeEffect TreeViewer getViewer();
+}
+@UIPackage package org.eclipse.ui.editors.text;
+// XXX not 100%
+@SafeType public interface IStorageDocumentProvider {}
+
+@UIPackage package org.eclipse.ui.editors.text.templates;
+@UIPackage package org.eclipse.ui.fieldassist;
+@UIPackage package org.eclipse.ui.forms;
+@UIPackage package org.eclipse.ui.forms.editor;
+@UIPackage package org.eclipse.ui.forms.events;
+@UIPackage package org.eclipse.ui.forms.widgets;
+@UIPackage package org.eclipse.ui.handlers;
+@UIPackage package org.eclipse.ui.help;
+public interface IWorkbenchHelpSystem {
+    // XXX not 100%
+    @SafeEffect void setHelp(IAction action, String contextId);
+}
+
+@UIPackage package org.eclipse.ui.ide;
+@UIPackage package org.eclipse.ui.ide.dialogs;
+@UIPackage package org.eclipse.ui.ide.fileSystem;
+@UIPackage package org.eclipse.ui.ide.undo;
+@UIPackage package org.eclipse.ui.intro;
+@UIPackage package org.eclipse.ui.intro.config;
+@UIPackage package org.eclipse.ui.intro.contentproviders;
+@UIPackage package org.eclipse.ui.intro.universal;
+@UIPackage package org.eclipse.ui.keys;
+@UIPackage package org.eclipse.ui.menus;
+@UIPackage package org.eclipse.ui.model;
+public interface IWorkbenchAdapter {
+    @SafeEffect ImageDescriptor getImageDescriptor(Object object);
+}
+
+@UIPackage package org.eclipse.ui.navigator;
+@UIPackage package org.eclipse.ui.navigator.resources;
+@UIPackage package org.eclipse.ui.operations;
+@UIPackage package org.eclipse.ui.part;
+public abstract class EditorPart extends WorkbenchPart implements IEditorPart {
+    // XXX Again, not 100%
+    @SafeEffect public IEditorInput getEditorInput();
+}
+public abstract class Page implements IPageBookViewPage {
+    @SafeEffect public IPageSite getSite();
+}
+@SafeType public class PluginTransfer extends ByteArrayTransfer {}
+
+@UIPackage package org.eclipse.ui.plugin;
+@UI public abstract class AbstractUIPlugin extends @UI Plugin {
+    // XXX: These methods are present on all plugins, UI or not, and I'm reproducing them explicitly here because inheritance from a *particular* polymorphic instantiation doesn't work yet
+    @SafeEffect public AbstractUIPlugin();
+    @SafeEffect public AbstractUIPlugin(IPluginDescriptor descriptor);
+    @SafeEffect void start(BundleContext context);
+    @SafeEffect void stop(BundleContext context);
+    @SafeEffect public static ImageDescriptor imageDescriptorFromPlugin(String pluginId, String imageFilePath);
+    @SafeEffect public IDialogSettings getDialogSettings();
+    // XXX Not 100%
+    @SafeEffect public IWorkbench getWorkbench();
+    @SafeEffect public Preferences getPluginPreferences();
+    @SafeEffect public IPreferenceStore getPreferenceStore();
+    @SafeEffect public ImageRegistry getImageRegistry(); // Technically only safe after the workbench is running, but no plugins will ever call this before then...
+}
+public final class PlatformUI {
+    // XXX Not 100%
+    @SafeEffect public static IWorkbench getWorkbench();
+}
+public abstract class Page implements IPageBookViewPage {
+    @SafeEffect public IPageSite getSite();
+}
+
+@UIPackage package org.eclipse.ui.preferences;
+@UIPackage package org.eclipse.ui.presentations;
+@UIPackage package org.eclipse.ui.progress;
+@SafeType public interface IWorkbenchSiteProgressService extends IProgressService {}
+
+@UIPackage package org.eclipse.ui.services;
+@UIPackage package org.eclipse.ui.splash;
+@UIPackage package org.eclipse.ui.statushandlers;
+@UIPackage package org.eclipse.ui.swt;
+@UIPackage package org.eclipse.ui.testing;
+@UIPackage package org.eclipse.ui.texteditor;
+@UIType public class StatusLineContributionItem extends ContributionItem implements IStatusField, IStatusFieldExtension {
+    @UIEffect public void setActionHandler(@UI IAction actionHandler);
+}
+public interface ITextEditor extends IEditorPart {
+    // XXX not 100% sure
+    @SafeEffect public IEditorInput getEditorInput();
+    @SafeEffect public IDocumentProvider getDocumentProvider();
+}
+@SafeType public interface IDocumentProvider {}
+
+
+@UIPackage package org.eclipse.ui.texteditor.link;
+@UIPackage package org.eclipse.ui.texteditor.quickdiff;
+@UIPackage package org.eclipse.ui.texteditor.rulers;
+@UIPackage package org.eclipse.ui.texteditor.spelling;
+@UIPackage package org.eclipse.ui.texteditor.templates;
+@UIPackage package org.eclipse.ui.themes;
+@UIPackage package org.eclipse.ui.views;
+@UIPackage package org.eclipse.ui.views.bookmarkexplorer;
+@UIPackage package org.eclipse.ui.views.contentoutline;
+@UIPackage package org.eclipse.ui.views.framelist;
+@UIPackage package org.eclipse.ui.views.markers;
+@UIPackage package org.eclipse.ui.views.navigator;
+@UIPackage package org.eclipse.ui.views.properties;
+@SafeType public class PropertySheet extends PageBookView implements ISelectionListener, IShowInTarget, IShowInSource, IRegistryEventListener {
+}
+@UIPackage package org.eclipse.ui.views.properties.tabbed;
+@UIPackage package org.eclipse.ui.views.tasklist;
+@UIPackage package org.eclipse.ui.wizards;
+@UIPackage package org.eclipse.ui.wizards.datatransfer;
+@UIPackage package org.eclipse.ui.wizards.newresource;
diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/org-osgi.astub b/checker/src/main/java/org/checkerframework/checker/guieffect/org-osgi.astub
new file mode 100644
index 0000000..64871ac
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/guieffect/org-osgi.astub
@@ -0,0 +1,8 @@
+import org.checkerframework.checker.guieffect.qual.*;
+
+package org.osgi.framework;
+public interface BundleActivator {
+    // These methods are present on all plugins, UI or not
+    @PolyUIEffect void start(BundleContext context);
+    @PolyUIEffect void stop(BundleContext context);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/org-swtchart.astub b/checker/src/main/java/org/checkerframework/checker/guieffect/org-swtchart.astub
new file mode 100644
index 0000000..3f22a49
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/guieffect/org-swtchart.astub
@@ -0,0 +1,4 @@
+import org.checkerframework.checker.guieffect.qual.*;
+
+package org.swtchart;
+@UIType public class Chart extends org.eclipse.swt.widgets.Composite implements org.eclipse.swt.widgets.Listener {}
diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/I18nAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/i18n/I18nAnnotatedTypeFactory.java
new file mode 100644
index 0000000..c47c8b8
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/i18n/I18nAnnotatedTypeFactory.java
@@ -0,0 +1,71 @@
+package org.checkerframework.checker.i18n;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.i18n.qual.Localized;
+import org.checkerframework.checker.i18n.qual.UnknownLocalized;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+
+public class I18nAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  public I18nAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new LinkedHashSet<>(Arrays.asList(Localized.class, UnknownLocalized.class));
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(super.createTreeAnnotator(), new I18nTreeAnnotator(this));
+  }
+
+  /** Do not propagate types through binary/compound operations. */
+  private class I18nTreeAnnotator extends TreeAnnotator {
+    /** The @{@link Localized} annotation. */
+    private final AnnotationMirror LOCALIZED =
+        AnnotationBuilder.fromClass(elements, Localized.class);
+
+    public I18nTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) {
+      type.removeAnnotation(LOCALIZED);
+      return null;
+    }
+
+    @Override
+    public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
+      type.removeAnnotation(LOCALIZED);
+      return null;
+    }
+
+    @Override
+    public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
+      if (!type.isAnnotatedInHierarchy(LOCALIZED)) {
+        if (tree.getKind() == Tree.Kind.STRING_LITERAL && tree.getValue().equals("")) {
+          type.addAnnotation(LOCALIZED);
+        }
+      }
+      return super.visitLiteral(tree, type);
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/I18nChecker.java b/checker/src/main/java/org/checkerframework/checker/i18n/I18nChecker.java
new file mode 100644
index 0000000..d08f2b1
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/i18n/I18nChecker.java
@@ -0,0 +1,30 @@
+package org.checkerframework.checker.i18n;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import org.checkerframework.framework.source.AggregateChecker;
+import org.checkerframework.framework.source.SourceChecker;
+
+/**
+ * A type-checker that enforces (and finds the violations of) two properties:
+ *
+ * <ol>
+ *   <li value="1">Only localized output gets emitted to the user
+ *   <li value="2">Only localizable keys (i.e. keys found in localizing resource bundles) get used
+ *       as such.
+ * </ol>
+ *
+ * @see I18nSubchecker
+ * @see LocalizableKeyChecker
+ * @checker_framework.manual #i18n-checker Internationalization Checker
+ */
+public class I18nChecker extends AggregateChecker {
+
+  @Override
+  protected Collection<Class<? extends SourceChecker>> getSupportedCheckers() {
+    Collection<Class<? extends SourceChecker>> checkers = new ArrayList<>(2);
+    checkers.add(I18nSubchecker.class);
+    checkers.add(LocalizableKeyChecker.class);
+    return checkers;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/I18nSubchecker.java b/checker/src/main/java/org/checkerframework/checker/i18n/I18nSubchecker.java
new file mode 100644
index 0000000..5d897b0
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/i18n/I18nSubchecker.java
@@ -0,0 +1,12 @@
+package org.checkerframework.checker.i18n;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+
+/**
+ * A type-checker that checks that only localized {@code String}s are visible to the user.
+ *
+ * @checker_framework.manual #i18n-checker Internationalization Checker
+ */
+@RelevantJavaTypes(CharSequence.class)
+public class I18nSubchecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyAnnotatedTypeFactory.java
new file mode 100644
index 0000000..5141db2
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyAnnotatedTypeFactory.java
@@ -0,0 +1,38 @@
+package org.checkerframework.checker.i18n;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import org.checkerframework.checker.i18n.qual.LocalizableKey;
+import org.checkerframework.checker.i18n.qual.LocalizableKeyBottom;
+import org.checkerframework.checker.i18n.qual.UnknownLocalizableKey;
+import org.checkerframework.checker.propkey.PropertyKeyAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+
+/** A PropertyKeyATF that uses LocalizableKey to annotate the keys. */
+public class LocalizableKeyAnnotatedTypeFactory extends PropertyKeyAnnotatedTypeFactory {
+
+  public LocalizableKeyAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    // Does not call postInit() because its superclass does.
+    // If we ever add code to this constructor, it needs to:
+    //   * call a superclass constructor that does not call postInit(), and
+    //   * call postInit() itself.
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new LinkedHashSet<>(
+        Arrays.asList(
+            LocalizableKey.class, LocalizableKeyBottom.class, UnknownLocalizableKey.class));
+  }
+
+  @Override
+  public TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(
+        super.createBasicTreeAnnotator(), new KeyLookupTreeAnnotator(this, LocalizableKey.class));
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyChecker.java b/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyChecker.java
new file mode 100644
index 0000000..7e932e5
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyChecker.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.i18n;
+
+import java.util.Locale;
+import java.util.ResourceBundle;
+import javax.annotation.processing.SupportedOptions;
+import org.checkerframework.checker.propkey.PropertyKeyChecker;
+
+/**
+ * A type-checker that checks that only valid localizable keys are used when using localizing
+ * methods (e.g. {@link ResourceBundle#getString(String)}).
+ *
+ * <p>Currently, the checker supports two methods for localization checks:
+ *
+ * <ol>
+ *   <li value="1">Properties files: A common method for localization using a properties file,
+ *       mapping the localization keys to localized messages. Programmers pass the property file
+ *       location via {@code propfiles} option (e.g. {@code
+ *       -Apropfiles=/path/to/messages.properties}), separating multiple files by a colon ":".
+ *   <li value="2">{@link ResourceBundle}: The proper recommended mechanism for localization.
+ *       Programmers pass the {@code baseName} name of the bundle via {@code bundlename} (e.g.
+ *       {@code -Abundlename=MyResource}. The checker uses the resource associated with the default
+ *       {@link Locale} in the compilation system.
+ * </ol>
+ */
+@SupportedOptions({"propfiles", "bundlenames"})
+public class LocalizableKeyChecker extends PropertyKeyChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java
new file mode 100644
index 0000000..50f9285
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java
@@ -0,0 +1,373 @@
+package org.checkerframework.checker.i18nformatter;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
+import org.checkerframework.checker.i18nformatter.qual.I18nFormat;
+import org.checkerframework.checker.i18nformatter.qual.I18nFormatBottom;
+import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor;
+import org.checkerframework.checker.i18nformatter.qual.I18nInvalidFormat;
+import org.checkerframework.checker.i18nformatter.qual.I18nUnknownFormat;
+import org.checkerframework.checker.i18nformatter.util.I18nFormatUtil;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.plumelib.reflection.Signatures;
+
+/**
+ * Adds {@link I18nFormat} to the type of tree, if it is a {@code String} or {@code char} literal
+ * that represents a satisfiable format. The annotation's value is set to be a list of appropriate
+ * {@link I18nConversionCategory} values for every parameter of the format.
+ *
+ * <p>It also creates a map from the provided translation file if exists. This map will be used to
+ * get the corresponding value of a key when {@link java.util.ResourceBundle#getString} method is
+ * invoked.
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+public class I18nFormatterAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** The @{@link I18nUnknownFormat} annotation. */
+  protected final AnnotationMirror I18NUNKNOWNFORMAT =
+      AnnotationBuilder.fromClass(elements, I18nUnknownFormat.class);
+  /** The @{@link I18nFormatBottom} annotation. */
+  protected final AnnotationMirror I18NFORMATBOTTOM =
+      AnnotationBuilder.fromClass(elements, I18nFormatBottom.class);
+
+  /** The fully-qualified name of {@link I18nFormat}. */
+  protected static final @CanonicalName String I18NFORMAT_NAME =
+      I18nFormat.class.getCanonicalName();
+  /** The fully-qualified name of {@link I18nInvalidFormat}. */
+  protected static final @CanonicalName String I18NINVALIDFORMAT_NAME =
+      I18nInvalidFormat.class.getCanonicalName();
+  /** The fully-qualified name of {@link I18nFormatFor}. */
+  protected static final @CanonicalName String I18NFORMATFOR_NAME =
+      I18nFormatFor.class.getCanonicalName();
+
+  /** Map from a translation file key to its value in the file. */
+  public final Map<String, String> translations = Collections.unmodifiableMap(buildLookup());
+
+  /** Syntax tree utilities. */
+  protected final I18nFormatterTreeUtil treeUtil = new I18nFormatterTreeUtil(checker);
+
+  /** Create a new I18nFormatterAnnotatedTypeFactory. */
+  public I18nFormatterAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    this.postInit();
+  }
+
+  /**
+   * Builds a map from a translation file key to its value in the file.
+   *
+   * @return a map from a translation file key to its value in the file
+   */
+  private Map<String, String> buildLookup() {
+    Map<String, String> result = new HashMap<>();
+
+    if (checker.hasOption("propfiles")) {
+      String names = checker.getOption("propfiles");
+      String[] namesArr = names.split(":");
+
+      if (namesArr == null) {
+        System.err.println("Couldn't parse the properties files: <" + names + ">");
+      } else {
+        for (String name : namesArr) {
+          try {
+            Properties prop = new Properties();
+
+            ClassLoader cl = this.getClass().getClassLoader();
+            if (cl == null) {
+              // The class loader is null if the system class loader was used.
+              cl = ClassLoader.getSystemClassLoader();
+            }
+            InputStream in = cl.getResourceAsStream(name);
+
+            if (in == null) {
+              // If the classloader didn't manage to load the file, try whether a
+              // FileInputStream works. For absolute paths this might help.
+              try {
+                in = new FileInputStream(name);
+              } catch (FileNotFoundException e) {
+                // ignore
+              }
+            }
+
+            if (in == null) {
+              System.err.println("Couldn't find the properties file: " + name);
+              // report(null, "propertykeychecker.filenotfound", name);
+              // return Collections.emptySet();
+              continue;
+            }
+
+            prop.load(in);
+
+            for (String key : prop.stringPropertyNames()) {
+              result.put(key, prop.getProperty(key));
+            }
+          } catch (Exception e) {
+            // TODO: is there a nicer way to report messages, that are not connected to
+            // an AST node?  One cannot use report, because it needs a node.
+            System.err.println("Exception in PropertyKeyChecker.keysOfPropertyFile: " + e);
+            e.printStackTrace();
+          }
+        }
+      }
+    }
+
+    if (checker.hasOption("bundlenames")) {
+      String bundleNames = checker.getOption("bundlenames");
+      String[] namesArr = bundleNames.split(":");
+
+      if (namesArr == null) {
+        System.err.println("Couldn't parse the resource bundles: <" + bundleNames + ">");
+      } else {
+        for (String bundleName : namesArr) {
+          if (!Signatures.isBinaryName(bundleName)) {
+            System.err.println(
+                "Malformed resource bundle: <" + bundleName + "> should be a binary name.");
+            continue;
+          }
+          ResourceBundle bundle = ResourceBundle.getBundle(bundleName);
+          if (bundle == null) {
+            System.err.println(
+                "Couldn't find the resource bundle: <"
+                    + bundleName
+                    + "> for locale <"
+                    + Locale.getDefault()
+                    + ">.");
+            continue;
+          }
+
+          for (String key : bundle.keySet()) {
+            result.put(key, bundle.getString(key));
+          }
+        }
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new I18nFormatterQualifierHierarchy();
+  }
+
+  @Override
+  public TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(super.createTreeAnnotator(), new I18nFormatterTreeAnnotator(this));
+  }
+
+  private class I18nFormatterTreeAnnotator extends TreeAnnotator {
+    public I18nFormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
+      if (!type.isAnnotatedInHierarchy(I18NUNKNOWNFORMAT)) {
+        String format = null;
+        if (tree.getKind() == Tree.Kind.STRING_LITERAL) {
+          format = (String) tree.getValue();
+        } else if (tree.getKind() == Tree.Kind.CHAR_LITERAL) {
+          format = Character.toString((Character) tree.getValue());
+        }
+        if (format != null) {
+          AnnotationMirror anno;
+          try {
+            I18nConversionCategory[] cs = I18nFormatUtil.formatParameterCategories(format);
+            anno = I18nFormatterAnnotatedTypeFactory.this.treeUtil.categoriesToFormatAnnotation(cs);
+          } catch (IllegalArgumentException e) {
+            anno =
+                I18nFormatterAnnotatedTypeFactory.this.treeUtil.exceptionToInvalidFormatAnnotation(
+                    e);
+          }
+          type.addAnnotation(anno);
+        }
+      }
+
+      return super.visitLiteral(tree, type);
+    }
+  }
+
+  /** I18nFormatterQualifierHierarchy. */
+  class I18nFormatterQualifierHierarchy extends MostlyNoElementQualifierHierarchy {
+
+    /** Qualifier kind for the @{@link I18nFormat} annotation. */
+    private final QualifierKind I18NFORMAT_KIND;
+    /** Qualifier kind for the @{@link I18nFormatFor} annotation. */
+    private final QualifierKind I18NFORMATFOR_KIND;
+    /** Qualifier kind for the @{@link I18nInvalidFormat} annotation. */
+    private final QualifierKind I18NINVALIDFORMAT_KIND;
+
+    /** Creates I18nFormatterQualifierHierarchy. */
+    public I18nFormatterQualifierHierarchy() {
+      super(I18nFormatterAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), elements);
+      this.I18NFORMAT_KIND = this.getQualifierKind(I18NFORMAT_NAME);
+      this.I18NFORMATFOR_KIND = this.getQualifierKind(I18NFORMATFOR_NAME);
+      this.I18NINVALIDFORMAT_KIND = this.getQualifierKind(I18NINVALIDFORMAT_NAME);
+    }
+
+    @Override
+    protected boolean isSubtypeWithElements(
+        AnnotationMirror subAnno,
+        QualifierKind subKind,
+        AnnotationMirror superAnno,
+        QualifierKind superKind) {
+      if (subKind == I18NFORMAT_KIND && superKind == I18NFORMAT_KIND) {
+
+        I18nConversionCategory[] rhsArgTypes = treeUtil.formatAnnotationToCategories(subAnno);
+        I18nConversionCategory[] lhsArgTypes = treeUtil.formatAnnotationToCategories(superAnno);
+
+        if (rhsArgTypes.length > lhsArgTypes.length) {
+          return false;
+        }
+
+        for (int i = 0; i < rhsArgTypes.length; ++i) {
+          if (!I18nConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) {
+            return false;
+          }
+        }
+        return true;
+      } else if ((subKind == I18NINVALIDFORMAT_KIND && superKind == I18NINVALIDFORMAT_KIND)
+          || (subKind == I18NFORMATFOR_KIND && superKind == I18NFORMATFOR_KIND)) {
+        return Objects.equals(
+            treeUtil.getI18nInvalidFormatValue(subAnno),
+            treeUtil.getI18nInvalidFormatValue(superAnno));
+      }
+      throw new BugInCF("Unexpected QualifierKinds: %s %s", subKind, superKind);
+    }
+
+    @Override
+    protected AnnotationMirror leastUpperBoundWithElements(
+        AnnotationMirror anno1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror anno2,
+        QualifierKind qualifierKind2,
+        QualifierKind lubKind) {
+      if (qualifierKind1.isBottom()) {
+        return anno2;
+      } else if (qualifierKind2.isBottom()) {
+        return anno1;
+      } else if (qualifierKind1 == I18NFORMAT_KIND && qualifierKind2 == I18NFORMAT_KIND) {
+        I18nConversionCategory[] shorterArgTypesList = treeUtil.formatAnnotationToCategories(anno1);
+        I18nConversionCategory[] longerArgTypesList = treeUtil.formatAnnotationToCategories(anno2);
+        if (shorterArgTypesList.length > longerArgTypesList.length) {
+          I18nConversionCategory[] temp = longerArgTypesList;
+          longerArgTypesList = shorterArgTypesList;
+          shorterArgTypesList = temp;
+        }
+
+        // From the manual:
+        // It is legal to use a format string with fewer format specifiers
+        // than required, but a warning is issued.
+
+        I18nConversionCategory[] resultArgTypes =
+            new I18nConversionCategory[longerArgTypesList.length];
+
+        for (int i = 0; i < shorterArgTypesList.length; ++i) {
+          resultArgTypes[i] =
+              I18nConversionCategory.intersect(shorterArgTypesList[i], longerArgTypesList[i]);
+        }
+        for (int i = shorterArgTypesList.length; i < longerArgTypesList.length; ++i) {
+          resultArgTypes[i] = longerArgTypesList[i];
+        }
+        return treeUtil.categoriesToFormatAnnotation(resultArgTypes);
+      } else if (qualifierKind1 == I18NINVALIDFORMAT_KIND
+          && qualifierKind2 == I18NINVALIDFORMAT_KIND) {
+        assert !anno1.getElementValues().isEmpty();
+        assert !anno1.getElementValues().isEmpty();
+
+        if (AnnotationUtils.areSame(anno1, anno2)) {
+          return anno1;
+        }
+
+        return treeUtil.stringToInvalidFormatAnnotation(
+            "("
+                + treeUtil.invalidFormatAnnotationToErrorMessage(anno1)
+                + " or "
+                + treeUtil.invalidFormatAnnotationToErrorMessage(anno2)
+                + ")");
+      } else if (qualifierKind1 == I18NFORMATFOR_KIND && AnnotationUtils.areSame(anno1, anno2)) {
+        // @I18nFormatFor annotations are unrelated by subtyping, unless they are identical.
+        return anno1;
+      }
+
+      return I18NUNKNOWNFORMAT;
+    }
+
+    @Override
+    protected AnnotationMirror greatestLowerBoundWithElements(
+        AnnotationMirror anno1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror anno2,
+        QualifierKind qualifierKind2,
+        QualifierKind glbKind) {
+      if (qualifierKind1.isTop()) {
+        return anno2;
+      } else if (qualifierKind2.isTop()) {
+        return anno1;
+      } else if (qualifierKind1 == I18NFORMAT_KIND && qualifierKind2 == I18NFORMAT_KIND) {
+        I18nConversionCategory[] anno1ArgTypes = treeUtil.formatAnnotationToCategories(anno1);
+        I18nConversionCategory[] anno2ArgTypes = treeUtil.formatAnnotationToCategories(anno2);
+
+        // From the manual:
+        // It is legal to use a format string with fewer format specifiers
+        // than required, but a warning is issued.
+        int length = anno1ArgTypes.length;
+        if (anno2ArgTypes.length < length) {
+          length = anno2ArgTypes.length;
+        }
+
+        I18nConversionCategory[] anno3ArgTypes = new I18nConversionCategory[length];
+
+        for (int i = 0; i < length; ++i) {
+          anno3ArgTypes[i] = I18nConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]);
+        }
+        return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes);
+      } else if (qualifierKind1 == I18NINVALIDFORMAT_KIND
+          && qualifierKind2 == I18NINVALIDFORMAT_KIND) {
+
+        assert !anno2.getElementValues().isEmpty();
+
+        if (AnnotationUtils.areSame(anno1, anno2)) {
+          return anno1;
+        }
+
+        return treeUtil.stringToInvalidFormatAnnotation(
+            "("
+                + treeUtil.invalidFormatAnnotationToErrorMessage(anno1)
+                + " and "
+                + treeUtil.invalidFormatAnnotationToErrorMessage(anno2)
+                + ")");
+      } else if (qualifierKind1 == I18NFORMATFOR_KIND && AnnotationUtils.areSame(anno1, anno2)) {
+        // @I18nFormatFor annotations are unrelated by subtyping, unless they are identical.
+        return anno1;
+      }
+
+      return I18NFORMATBOTTOM;
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterChecker.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterChecker.java
new file mode 100644
index 0000000..af89106
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterChecker.java
@@ -0,0 +1,15 @@
+package org.checkerframework.checker.i18nformatter;
+
+import javax.annotation.processing.SupportedOptions;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+
+/**
+ * A type-checker plug-in for the qualifier that finds syntactically invalid i18n-formatter calls
+ * (MessageFormat.format()).
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+@SupportedOptions({"bundlenames", "propfiles"})
+@RelevantJavaTypes(CharSequence.class)
+public class I18nFormatterChecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTransfer.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTransfer.java
new file mode 100644
index 0000000..a366029
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTransfer.java
@@ -0,0 +1,87 @@
+package org.checkerframework.checker.i18nformatter;
+
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.formatter.FormatterTreeUtil.Result;
+import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
+import org.checkerframework.checker.i18nformatter.qual.I18nInvalidFormat;
+import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
+import org.checkerframework.dataflow.analysis.RegularTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.javacutil.AnnotationBuilder;
+
+/**
+ * The transfer function for the Internationalization Format String Checker.
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+public class I18nFormatterTransfer extends CFTransfer {
+
+  public I18nFormatterTransfer(CFAnalysis analysis) {
+    super(analysis);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitMethodInvocation(
+      MethodInvocationNode node, TransferInput<CFValue, CFStore> in) {
+    I18nFormatterAnnotatedTypeFactory atypeFactory =
+        (I18nFormatterAnnotatedTypeFactory) analysis.getTypeFactory();
+    TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(node, in);
+    I18nFormatterTreeUtil tu = atypeFactory.treeUtil;
+
+    // If hasFormat is called, make sure that the format string is annotated correctly
+    if (tu.isHasFormatCall(node, atypeFactory)) {
+      CFStore thenStore = result.getRegularStore();
+      CFStore elseStore = thenStore.copy();
+      ConditionalTransferResult<CFValue, CFStore> newResult =
+          new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore);
+      Result<I18nConversionCategory[]> cats = tu.getHasFormatCallCategories(node);
+      if (cats.value() == null) {
+        tu.failure(cats, "i18nformat.indirect.arguments");
+      } else {
+        JavaExpression firstParam = JavaExpression.fromNode(node.getArgument(0));
+        AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value());
+        thenStore.insertValue(firstParam, anno);
+      }
+      return newResult;
+    }
+
+    // If isFormat is called, annotate the format string with I18nInvalidFormat
+    if (tu.isIsFormatCall(node, atypeFactory)) {
+      CFStore thenStore = result.getRegularStore();
+      CFStore elseStore = thenStore.copy();
+      ConditionalTransferResult<CFValue, CFStore> newResult =
+          new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore);
+      JavaExpression firstParam = JavaExpression.fromNode(node.getArgument(0));
+      AnnotationBuilder builder = new AnnotationBuilder(tu.processingEnv, I18nInvalidFormat.class);
+      // No need to set a value of @I18nInvalidFormat
+      builder.setValue("value", "");
+      elseStore.insertValue(firstParam, builder.build());
+      return newResult;
+    }
+
+    // @I18nMakeFormat that will be used to annotate ResourceBundle.getString() so that when the
+    // getString() method is called, this will check if the given key exist in the translation
+    // file and annotate the result string with the correct format annotation according to the
+    // corresponding key's value.
+    if (tu.isMakeFormatCall(node, atypeFactory)) {
+      Result<I18nConversionCategory[]> cats = tu.makeFormatCallCategories(node, atypeFactory);
+      if (cats.value() == null) {
+        tu.failure(cats, "i18nformat.key.not.found");
+      } else {
+        AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value());
+        CFValue newResultValue =
+            analysis.createSingleAnnotationValue(anno, result.getResultValue().getUnderlyingType());
+        return new RegularTransferResult<>(newResultValue, result.getRegularStore());
+      }
+    }
+
+    return result;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java
new file mode 100644
index 0000000..fd91640
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java
@@ -0,0 +1,575 @@
+package org.checkerframework.checker.i18nformatter;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.util.SimpleTreeVisitor;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.NullType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.SimpleElementVisitor7;
+import javax.lang.model.util.SimpleTypeVisitor7;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType;
+import org.checkerframework.checker.formatter.FormatterTreeUtil.Result;
+import org.checkerframework.checker.i18nformatter.qual.I18nChecksFormat;
+import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
+import org.checkerframework.checker.i18nformatter.qual.I18nFormat;
+import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor;
+import org.checkerframework.checker.i18nformatter.qual.I18nInvalidFormat;
+import org.checkerframework.checker.i18nformatter.qual.I18nMakeFormat;
+import org.checkerframework.checker.i18nformatter.qual.I18nValidFormat;
+import org.checkerframework.checker.i18nformatter.util.I18nFormatUtil;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.cfg.node.ArrayCreationNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.StringLiteralNode;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.util.JavaExpressionParseUtil;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * This class provides a collection of utilities to ease working with syntax trees that have
+ * something to do with I18nFormatters.
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+public class I18nFormatterTreeUtil {
+  /** The checker. */
+  public final BaseTypeChecker checker;
+  /** The processing environment. */
+  public final ProcessingEnvironment processingEnv;
+
+  /** The value() element/field of an @I18nFormat annotation. */
+  protected final ExecutableElement i18nFormatValueElement;
+  /** The value() element/field of an @I18nFormatFor annotation. */
+  protected final ExecutableElement i18nFormatForValueElement;
+  /** The value() element/field of an @I18nInvalidFormat annotation. */
+  protected final ExecutableElement i18nInvalidFormatValueElement;
+
+  /**
+   * Creates a new I18nFormatterTreeUtil.
+   *
+   * @param checker the checker
+   */
+  public I18nFormatterTreeUtil(BaseTypeChecker checker) {
+    this.checker = checker;
+    this.processingEnv = checker.getProcessingEnvironment();
+    i18nFormatValueElement = TreeUtils.getMethod(I18nFormat.class, "value", 0, processingEnv);
+    i18nFormatForValueElement = TreeUtils.getMethod(I18nFormatFor.class, "value", 0, processingEnv);
+    i18nInvalidFormatValueElement =
+        TreeUtils.getMethod(I18nInvalidFormat.class, "value", 0, processingEnv);
+  }
+
+  /** Describe the format annotation type. */
+  public enum FormatType {
+    I18NINVALID,
+    I18NFORMAT,
+    I18NFORMATFOR
+  }
+
+  /**
+   * Takes an exception that describes an invalid formatter string and returns a syntax trees
+   * element that represents a {@link I18nInvalidFormat} annotation with the exception's error
+   * message as value.
+   */
+  public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalArgumentException ex) {
+    return stringToInvalidFormatAnnotation(ex.getMessage());
+  }
+
+  /**
+   * Creates an {@link I18nInvalidFormat} annotation with the given string as its value.
+   *
+   * @param invalidFormatString an invalid formatter string
+   * @return an {@link I18nInvalidFormat} annotation with the given string as its value
+   */
+  // package-private
+  AnnotationMirror stringToInvalidFormatAnnotation(String invalidFormatString) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class);
+    builder.setValue("value", invalidFormatString);
+    return builder.build();
+  }
+
+  /**
+   * Gets the value() element/field out of an I18nInvalidFormat annotation.
+   *
+   * @param anno an I18nInvalidFormat annotation
+   * @return its value() element/field, or null if it does not have one
+   */
+  /*package-visible*/
+  @Nullable String getI18nInvalidFormatValue(AnnotationMirror anno) {
+    return AnnotationUtils.getElementValue(anno, i18nInvalidFormatValueElement, String.class, null);
+  }
+
+  /**
+   * Gets the value() element/field out of an I18NFormatFor annotation.
+   *
+   * @param anno an I18NFormatFor annotation
+   * @return its value() element/field
+   */
+  /*package-visible*/ String getI18nFormatForValue(AnnotationMirror anno) {
+    return AnnotationUtils.getElementValue(anno, i18nFormatForValueElement, String.class);
+  }
+
+  /**
+   * Takes a syntax tree element that represents a {@link I18nInvalidFormat} annotation, and returns
+   * its value.
+   *
+   * @param anno an I18nInvalidFormat annotation
+   * @return its value() element/field, within double-quotes
+   */
+  public String invalidFormatAnnotationToErrorMessage(AnnotationMirror anno) {
+    return "\"" + getI18nInvalidFormatValue(anno) + "\"";
+  }
+
+  /**
+   * Creates a {@code @}{@link I18nFormat} annotation with the given list as its value.
+   *
+   * @param args conversion categories for the {@code @Format} annotation
+   * @return a {@code @}{@link I18nFormat} annotation with the given list as its value
+   */
+  public AnnotationMirror categoriesToFormatAnnotation(I18nConversionCategory[] args) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nFormat.class);
+    builder.setValue("value", args);
+    return builder.build();
+  }
+
+  /**
+   * Takes an {@code @}{@link I18nFormat} annotation, and returns its {@code value} element
+   *
+   * @param anno an {@code @}{@link I18nFormat} annotation
+   * @return the {@code @}{@link I18nFormat} annotation's {@code value} element
+   */
+  public I18nConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) {
+    return AnnotationUtils.getElementValueEnumArray(
+        anno, i18nFormatValueElement, I18nConversionCategory.class);
+  }
+
+  /**
+   * Returns true if the call is to a method with the @I18nChecksFormat annotation. An example of
+   * such a method is I18nFormatUtil.hasFormat.
+   */
+  public boolean isHasFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
+    ExecutableElement method = node.getTarget().getMethod();
+    AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nChecksFormat.class);
+    return anno != null;
+  }
+
+  /**
+   * Returns true if the call is to a method with the @I18nValidFormat annotation. An example of
+   * such a method is I18nFormatUtil.isFormat.
+   */
+  public boolean isIsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
+    ExecutableElement method = node.getTarget().getMethod();
+    AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nValidFormat.class);
+    return anno != null;
+  }
+
+  /**
+   * Returns true if the call is to a method with the @I18nMakeFormat annotation. An example of such
+   * a method is ResourceBundle.getString.
+   */
+  public boolean isMakeFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
+    ExecutableElement method = node.getTarget().getMethod();
+    AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nMakeFormat.class);
+    return anno != null;
+  }
+
+  /**
+   * Reports an error.
+   *
+   * @param res used for source location information
+   * @param msgKey the diagnostic message key
+   * @param args arguments to the diagnostic message
+   */
+  public final void failure(Result<?> res, @CompilerMessageKey String msgKey, Object... args) {
+    checker.reportError(res.location, msgKey, args);
+  }
+
+  /**
+   * Reports a warning.
+   *
+   * @param res used for source location information
+   * @param msgKey the diagnostic message key
+   * @param args arguments to the diagnostic message
+   */
+  public final void warning(Result<?> res, @CompilerMessageKey String msgKey, Object... args) {
+    checker.reportWarning(res.location, msgKey, args);
+  }
+
+  private I18nConversionCategory[] asFormatCallCategoriesLowLevel(MethodInvocationNode node) {
+    Node vararg = node.getArgument(1);
+    if (vararg instanceof ArrayCreationNode) {
+      List<Node> convs = ((ArrayCreationNode) vararg).getInitializers();
+      I18nConversionCategory[] res = new I18nConversionCategory[convs.size()];
+      for (int i = 0; i < convs.size(); i++) {
+        Node conv = convs.get(i);
+        if (conv instanceof FieldAccessNode) {
+          if (typeMirrorToClass(((FieldAccessNode) conv).getType())
+              == I18nConversionCategory.class) {
+            res[i] = I18nConversionCategory.valueOf(((FieldAccessNode) conv).getFieldName());
+            continue; /* avoid returning null */
+          }
+        }
+        return null;
+      }
+      return res;
+    }
+    return null;
+  }
+
+  public Result<I18nConversionCategory[]> getHasFormatCallCategories(MethodInvocationNode node) {
+    return new Result<>(asFormatCallCategoriesLowLevel(node), node.getTree());
+  }
+
+  public Result<I18nConversionCategory[]> makeFormatCallCategories(
+      MethodInvocationNode node, I18nFormatterAnnotatedTypeFactory atypeFactory) {
+    Map<String, String> translations = atypeFactory.translations;
+    Node firstParam = node.getArgument(0);
+    Result<I18nConversionCategory[]> ret = new Result<>(null, node.getTree());
+
+    // Now only work with a literal string
+    if (firstParam instanceof StringLiteralNode) {
+      String s = ((StringLiteralNode) firstParam).getValue();
+      if (translations.containsKey(s)) {
+        String value = translations.get(s);
+        ret = new Result<>(I18nFormatUtil.formatParameterCategories(value), node.getTree());
+      }
+    }
+    return ret;
+  }
+
+  /**
+   * Returns an I18nFormatCall instance, only if there is an {@code @I18nFormatFor} annotation.
+   * Otherwise, returns null.
+   *
+   * @param tree method invocation tree
+   * @param atypeFactory type factory
+   * @return an I18nFormatCall instance, only if there is an {@code @I18nFormatFor} annotation.
+   *     Otherwise, returns null.
+   */
+  public @Nullable I18nFormatCall createFormatForCall(
+      MethodInvocationTree tree, I18nFormatterAnnotatedTypeFactory atypeFactory) {
+    ExecutableElement method = TreeUtils.elementFromUse(tree);
+    AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method);
+    for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) {
+      // find @FormatFor
+      if (paramType.getAnnotation(I18nFormatFor.class) != null) {
+        return atypeFactory.treeUtil.new I18nFormatCall(tree, atypeFactory);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Represents a format method invocation in the syntax tree.
+   *
+   * <p>An I18nFormatCall instance can only be instantiated by the createFormatForCall method.
+   */
+  public class I18nFormatCall {
+
+    /** The AST node for the call. */
+    private final MethodInvocationTree tree;
+    /** The format string argument. */
+    private ExpressionTree formatArg;
+    /** The type factory. */
+    private final AnnotatedTypeFactory atypeFactory;
+    /** The arguments to the format string. */
+    private List<? extends ExpressionTree> args;
+    /** Extra description for error messages. */
+    private String invalidMessage;
+
+    /** The type of the format string formal parameter. */
+    private AnnotatedTypeMirror formatAnno;
+
+    /**
+     * Creates an {@code I18nFormatCall} for the given method invocation tree.
+     *
+     * @param tree method invocation tree
+     * @param atypeFactory type factory
+     */
+    public I18nFormatCall(MethodInvocationTree tree, AnnotatedTypeFactory atypeFactory) {
+      this.tree = tree;
+      this.atypeFactory = atypeFactory;
+      List<? extends ExpressionTree> theargs = tree.getArguments();
+      this.args = null;
+      ExecutableElement method = TreeUtils.elementFromUse(tree);
+      AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method);
+      initialCheck(theargs, method, methodAnno);
+    }
+
+    /**
+     * Returns the AST node for the call.
+     *
+     * @return the AST node for the call
+     */
+    public MethodInvocationTree getTree() {
+      return tree;
+    }
+
+    @Override
+    public String toString() {
+      return this.tree.toString();
+    }
+
+    /**
+     * This method checks the validity of the FormatFor. If it is valid, this.args will be set to
+     * the correct parameter arguments. Otherwise, it will be still null.
+     *
+     * @param theargs arguments to the format method call
+     * @param method the ExecutableElement of the format method
+     * @param methodAnno annotated type of {@code method}
+     */
+    private void initialCheck(
+        List<? extends ExpressionTree> theargs,
+        ExecutableElement method,
+        AnnotatedExecutableType methodAnno) {
+      // paramIndex is a 0-based index
+      int paramIndex = -1;
+      int i = 0;
+      for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) {
+        if (paramType.getAnnotation(I18nFormatFor.class) != null) {
+          this.formatArg = theargs.get(i);
+          this.formatAnno = atypeFactory.getAnnotatedType(formatArg);
+
+          if (typeMirrorToClass(paramType.getUnderlyingType()) != String.class) {
+            // Invalid FormatFor invocation
+            return;
+          }
+
+          String formatforArg = getI18nFormatForValue(paramType.getAnnotation(I18nFormatFor.class));
+
+          paramIndex = JavaExpressionParseUtil.parameterIndex(formatforArg);
+          if (paramIndex == -1) {
+            // report errors here
+            checker.reportError(tree, "i18nformat.formatfor");
+          } else {
+            paramIndex--;
+          }
+          break;
+        }
+        i++;
+      }
+
+      if (paramIndex != -1) {
+        VariableElement param = method.getParameters().get(paramIndex);
+        if (param.asType().getKind() == TypeKind.ARRAY) {
+          this.args = theargs.subList(paramIndex, theargs.size());
+        } else {
+          this.args = theargs.subList(paramIndex, paramIndex + 1);
+        }
+      }
+    }
+
+    public Result<FormatType> getFormatType() {
+      FormatType type;
+      if (isValidFormatForInvocation()) {
+        if (formatAnno.hasAnnotation(I18nFormat.class)) {
+          type = FormatType.I18NFORMAT;
+        } else if (formatAnno.hasAnnotation(I18nFormatFor.class)) {
+          type = FormatType.I18NFORMATFOR;
+        } else {
+          type = FormatType.I18NINVALID;
+          invalidMessage = "(is a @I18nFormat annotation missing?)";
+          AnnotationMirror inv = formatAnno.getAnnotation(I18nInvalidFormat.class);
+          if (inv != null) {
+            invalidMessage = getI18nInvalidFormatValue(inv);
+          }
+        }
+      } else {
+        // If the FormatFor is invalid, it's still I18nFormatFor type but invalid,
+        // and we can't do anything else
+        type = FormatType.I18NFORMATFOR;
+      }
+      return new Result<>(type, formatArg);
+    }
+
+    public final String getInvalidError() {
+      return invalidMessage;
+    }
+
+    public boolean isValidFormatForInvocation() {
+      return this.args != null;
+    }
+
+    /**
+     * Returns the type of method invocation.
+     *
+     * @see InvocationType
+     */
+    public final Result<InvocationType> getInvocationType() {
+      InvocationType type = InvocationType.VARARG;
+
+      if (args.size() == 1) {
+        final ExpressionTree first = args.get(0);
+        TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType();
+        // figure out if argType is an array
+        type =
+            argType.accept(
+                new SimpleTypeVisitor7<InvocationType, Class<Void>>() {
+                  @Override
+                  protected InvocationType defaultAction(TypeMirror e, Class<Void> p) {
+                    // not an array
+                    return InvocationType.VARARG;
+                  }
+
+                  @Override
+                  public InvocationType visitArray(ArrayType t, Class<Void> p) {
+                    // it's an array, now figure out if it's a
+                    // (Object[])null array
+                    return first.accept(
+                        new SimpleTreeVisitor<InvocationType, Class<Void>>() {
+                          @Override
+                          protected InvocationType defaultAction(Tree node, Class<Void> p) {
+                            // just a normal array
+                            return InvocationType.ARRAY;
+                          }
+
+                          @Override
+                          public InvocationType visitTypeCast(TypeCastTree node, Class<Void> p) {
+                            // it's a (Object[])null
+                            return atypeFactory
+                                        .getAnnotatedType(node.getExpression())
+                                        .getUnderlyingType()
+                                        .getKind()
+                                    == TypeKind.NULL
+                                ? InvocationType.NULLARRAY
+                                : InvocationType.ARRAY;
+                          }
+                        },
+                        p);
+                  }
+
+                  @Override
+                  public InvocationType visitNull(NullType t, Class<Void> p) {
+                    return InvocationType.NULLARRAY;
+                  }
+                },
+                Void.TYPE);
+      }
+
+      ExpressionTree loc;
+      loc = tree.getMethodSelect();
+      if (type != InvocationType.VARARG && !args.isEmpty()) {
+        loc = args.get(0);
+      }
+      return new Result<>(type, loc);
+    }
+
+    public Result<FormatType> getInvalidInvocationType() {
+      return new Result<>(FormatType.I18NFORMATFOR, formatArg);
+    }
+
+    /**
+     * Returns the conversion category for every parameter.
+     *
+     * @see I18nConversionCategory
+     */
+    public final I18nConversionCategory[] getFormatCategories() {
+      AnnotationMirror anno = formatAnno.getAnnotation(I18nFormat.class);
+      return formatAnnotationToCategories(anno);
+    }
+
+    public final Result<TypeMirror>[] getParamTypes() {
+      // One to suppress warning in javac, the other to suppress warning in Eclipse...
+      @SuppressWarnings({"rawtypes", "unchecked"})
+      Result<TypeMirror>[] res = new Result[args.size()];
+      for (int i = 0; i < res.length; ++i) {
+        ExpressionTree arg = args.get(i);
+        TypeMirror argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType();
+        res[i] = new Result<>(argType, arg);
+      }
+      return res;
+    }
+
+    public boolean isValidParameter(I18nConversionCategory formatCat, TypeMirror paramType) {
+      Class<? extends Object> type = typeMirrorToClass(paramType);
+      if (type == null) {
+        // we did not recognize the parameter type
+        return false;
+      }
+      return formatCat.isAssignableFrom(type);
+    }
+  }
+
+  /** Converts a TypeMirror to a Class. */
+  private static class TypeMirrorToClassVisitor
+      extends SimpleTypeVisitor7<Class<? extends Object>, Class<Void>> {
+    @Override
+    public Class<? extends Object> visitPrimitive(PrimitiveType t, Class<Void> v) {
+      switch (t.getKind()) {
+        case BOOLEAN:
+          return Boolean.class;
+        case BYTE:
+          return Byte.class;
+        case CHAR:
+          return Character.class;
+        case SHORT:
+          return Short.class;
+        case INT:
+          return Integer.class;
+        case LONG:
+          return Long.class;
+        case FLOAT:
+          return Float.class;
+        case DOUBLE:
+          return Double.class;
+        default:
+          return null;
+      }
+    }
+
+    @Override
+    public Class<? extends Object> visitDeclared(DeclaredType dt, Class<Void> v) {
+      return dt.asElement()
+          .accept(
+              new SimpleElementVisitor7<Class<? extends Object>, Class<Void>>() {
+                @Override
+                public Class<? extends Object> visitType(TypeElement e, Class<Void> v) {
+                  try {
+                    @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658:
+                    // Name.toString should be @PolySignature
+                    @BinaryName String cname = e.getQualifiedName().toString();
+                    return Class.forName(cname);
+                  } catch (ClassNotFoundException e1) {
+                    return null; // the lookup should work for all
+                    // the classes we care about
+                  }
+                }
+              },
+              Void.TYPE);
+    }
+  }
+
+  /** The singleton instance of TypeMirrorToClassVisitor. */
+  private static TypeMirrorToClassVisitor typeMirrorToClassVisitor = new TypeMirrorToClassVisitor();
+
+  /**
+   * Converts a TypeMirror to a Class.
+   *
+   * @param type a TypeMirror
+   * @return the class corresponding to the argument
+   */
+  private static final Class<? extends Object> typeMirrorToClass(final TypeMirror type) {
+    return type.accept(typeMirrorToClassVisitor, Void.TYPE);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java
new file mode 100644
index 0000000..ecfae15
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java
@@ -0,0 +1,163 @@
+package org.checkerframework.checker.i18nformatter;
+
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType;
+import org.checkerframework.checker.formatter.FormatterTreeUtil.Result;
+import org.checkerframework.checker.i18nformatter.I18nFormatterTreeUtil.FormatType;
+import org.checkerframework.checker.i18nformatter.I18nFormatterTreeUtil.I18nFormatCall;
+import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
+import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Whenever a method with {@link I18nFormatFor} annotation is invoked, it will perform the format
+ * string verification.
+ *
+ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker
+ */
+public class I18nFormatterVisitor extends BaseTypeVisitor<I18nFormatterAnnotatedTypeFactory> {
+
+  public I18nFormatterVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree tree, Void p) {
+    I18nFormatterTreeUtil tu = atypeFactory.treeUtil;
+    I18nFormatCall fc = tu.createFormatForCall(tree, atypeFactory);
+    if (fc != null) {
+      checkInvocationFormatFor(fc);
+      return p;
+    } else {
+      return super.visitMethodInvocation(tree, p);
+    }
+  }
+
+  private void checkInvocationFormatFor(I18nFormatCall fc) {
+    I18nFormatterTreeUtil tu = atypeFactory.treeUtil;
+    Result<FormatType> type = fc.getFormatType();
+
+    switch (type.value()) {
+      case I18NINVALID:
+        tu.failure(type, "i18nformat.string", fc.getInvalidError());
+        break;
+      case I18NFORMATFOR:
+        if (!fc.isValidFormatForInvocation()) {
+          Result<FormatType> failureType = fc.getInvalidInvocationType();
+          tu.failure(failureType, "i18nformat.formatfor");
+        }
+        break;
+      case I18NFORMAT:
+        Result<InvocationType> invc = fc.getInvocationType();
+        I18nConversionCategory[] formatCats = fc.getFormatCategories();
+        switch (invc.value()) {
+          case VARARG:
+            Result<TypeMirror>[] paramTypes = fc.getParamTypes();
+            int paraml = paramTypes.length;
+            int formatl = formatCats.length;
+
+            // For assignments, i18nformat.missing.arguments and i18nformat.excess.arguments are
+            // issued from commonAssignmentCheck.
+            if (paraml < formatl) {
+              tu.warning(invc, "i18nformat.missing.arguments", formatl, paraml);
+            }
+            if (paraml > formatl) {
+              tu.warning(invc, "i18nformat.excess.arguments", formatl, paraml);
+            }
+            for (int i = 0; i < formatl && i < paraml; ++i) {
+              I18nConversionCategory formatCat = formatCats[i];
+              Result<TypeMirror> param = paramTypes[i];
+              TypeMirror paramType = param.value();
+              switch (formatCat) {
+                case UNUSED:
+                  tu.warning(param, "i18nformat.argument.unused", " " + (1 + i));
+                  break;
+                case GENERAL:
+                  break;
+                default:
+                  if (!fc.isValidParameter(formatCat, paramType)) {
+                    ExecutableElement method = TreeUtils.elementFromUse(fc.getTree());
+                    CharSequence methodName = ElementUtils.getSimpleNameOrDescription(method);
+                    tu.failure(
+                        param,
+                        "argument",
+                        "", // parameter name is not useful
+                        methodName,
+                        paramType,
+                        formatCat);
+                  }
+              }
+            }
+            break;
+          case NULLARRAY:
+            // fall-through
+          case ARRAY:
+            for (I18nConversionCategory cat : formatCats) {
+              if (cat == I18nConversionCategory.UNUSED) {
+                tu.warning(invc, "i18nformat.argument.unused", "");
+              }
+            }
+            tu.warning(invc, "i18nformat.indirect.arguments");
+            break;
+          default:
+            break;
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  @Override
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      AnnotatedTypeMirror valueType,
+      Tree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT);
+    AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT);
+
+    // i18nformat.missing.arguments and i18nformat.excess.arguments are issued here for
+    // assignments.
+    // For method calls, they are issued in checkInvocationFormatFor.
+    if (rhs != null
+        && lhs != null
+        && AnnotationUtils.areSameByName(rhs, I18nFormatterAnnotatedTypeFactory.I18NFORMAT_NAME)
+        && AnnotationUtils.areSameByName(lhs, I18nFormatterAnnotatedTypeFactory.I18NFORMAT_NAME)) {
+      I18nConversionCategory[] rhsArgTypes =
+          atypeFactory.treeUtil.formatAnnotationToCategories(rhs);
+      I18nConversionCategory[] lhsArgTypes =
+          atypeFactory.treeUtil.formatAnnotationToCategories(lhs);
+
+      if (rhsArgTypes.length < lhsArgTypes.length) {
+        // From the manual:
+        // It is legal to use a format string with fewer format specifiers
+        // than required, but a warning is issued.
+        checker.reportWarning(
+            valueTree, "i18nformat.missing.arguments", varType.toString(), valueType.toString());
+      } else if (rhsArgTypes.length > lhsArgTypes.length) {
+        // Since it is known that too many conversion categories were provided, issue a more
+        // specific error message to that effect than assignment.
+        checker.reportError(
+            valueTree, "i18nformat.excess.arguments", varType.toString(), valueType.toString());
+      }
+    }
+
+    // By calling super.commonAssignmentCheck last, any i18nformat.excess.arguments message
+    // issued for a given line of code will take precedence over the
+    //   assignment
+    // issued by super.commonAssignmentCheck.
+    super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/messages.properties b/checker/src/main/java/org/checkerframework/checker/i18nformatter/messages.properties
new file mode 100644
index 0000000..9c3047b
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/messages.properties
@@ -0,0 +1,7 @@
+i18nformat.formatfor=invalid I18nFormatFor or its parameter index is out of range
+i18nformat.string=invalid format string %s
+i18nformat.argument.unused=unused argument %s
+i18nformat.excess.arguments=too many arguments; expected %s but %s given
+i18nformat.missing.arguments=missing arguments; expected %s but %s given
+i18nformat.indirect.arguments=parameters to a format method can only be checked if passed as varargs
+i18nformat.key.not.found=a key doesn't exist in the provided translation file
diff --git a/checker/src/main/java/org/checkerframework/checker/index/BaseAnnotatedTypeFactoryForIndexChecker.java b/checker/src/main/java/org/checkerframework/checker/index/BaseAnnotatedTypeFactoryForIndexChecker.java
new file mode 100644
index 0000000..1c5b428
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/BaseAnnotatedTypeFactoryForIndexChecker.java
@@ -0,0 +1,64 @@
+package org.checkerframework.checker.index;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.index.qual.HasSubsequence;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A class for functionality common to multiple type-checkers that are used by the Index Checker.
+ */
+public abstract class BaseAnnotatedTypeFactoryForIndexChecker extends BaseAnnotatedTypeFactory {
+
+  /** The from() element/field of a @HasSubsequence annotation. */
+  protected final ExecutableElement hasSubsequenceFromElement =
+      TreeUtils.getMethod(HasSubsequence.class, "from", 0, processingEnv);
+  /** The to() element/field of a @HasSubsequence annotation. */
+  protected final ExecutableElement hasSubsequenceToElement =
+      TreeUtils.getMethod(HasSubsequence.class, "to", 0, processingEnv);
+  /** The subsequence() element/field of a @HasSubsequence annotation. */
+  protected final ExecutableElement hasSubsequenceSubsequenceElement =
+      TreeUtils.getMethod(HasSubsequence.class, "subsequence", 0, processingEnv);
+
+  /**
+   * Creates a new BaseAnnotatedTypeFactoryForIndexChecker.
+   *
+   * @param checker the checker
+   */
+  public BaseAnnotatedTypeFactoryForIndexChecker(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  /**
+   * Gets the from() element/field out of a HasSubsequence annotation.
+   *
+   * @param anno a HasSubsequence annotation
+   * @return its from() element/field
+   */
+  public String hasSubsequenceFromValue(AnnotationMirror anno) {
+    return AnnotationUtils.getElementValue(anno, hasSubsequenceFromElement, String.class);
+  }
+
+  /**
+   * Gets the to() element/field out of a HasSubsequence annotation.
+   *
+   * @param anno a HasSubsequence annotation
+   * @return its to() element/field
+   */
+  public String hasSubsequenceToValue(AnnotationMirror anno) {
+    return AnnotationUtils.getElementValue(anno, hasSubsequenceToElement, String.class);
+  }
+
+  /**
+   * Gets the subsequence() element/field out of a HasSubsequence annotation.
+   *
+   * @param anno a HasSubsequence annotation
+   * @return its subsequence() element/field
+   */
+  public String hasSubsequenceSubsequenceValue(AnnotationMirror anno) {
+    return AnnotationUtils.getElementValue(anno, hasSubsequenceSubsequenceElement, String.class);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexAbstractTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/IndexAbstractTransfer.java
new file mode 100644
index 0000000..5e1eda1
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/IndexAbstractTransfer.java
@@ -0,0 +1,115 @@
+package org.checkerframework.checker.index;
+
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.GreaterThanNode;
+import org.checkerframework.dataflow.cfg.node.GreaterThanOrEqualNode;
+import org.checkerframework.dataflow.cfg.node.LessThanNode;
+import org.checkerframework.dataflow.cfg.node.LessThanOrEqualNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+
+/**
+ * This class provides methods shared by the Index Checker's internal checkers in their transfer
+ * functions. In particular, it provides a common framework for visiting comparison operators.
+ */
+@SuppressWarnings("ArgumentSelectionDefectChecker") // TODO: apply suggested error-prone fixes
+public abstract class IndexAbstractTransfer extends CFTransfer {
+
+  protected IndexAbstractTransfer(CFAnalysis analysis) {
+    super(analysis);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitGreaterThan(
+      GreaterThanNode node, TransferInput<CFValue, CFStore> in) {
+    TransferResult<CFValue, CFStore> result = super.visitGreaterThan(node, in);
+
+    IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node);
+    if (rfi.leftAnno == null || rfi.rightAnno == null) {
+      return result;
+    }
+    // Refine the then branch.
+    refineGT(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.thenStore, in);
+
+    // Refine the else branch, which is the inverse of the then branch.
+    refineGTE(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.elseStore, in);
+
+    return rfi.newResult;
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitGreaterThanOrEqual(
+      GreaterThanOrEqualNode node, TransferInput<CFValue, CFStore> in) {
+    TransferResult<CFValue, CFStore> result = super.visitGreaterThanOrEqual(node, in);
+
+    IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node);
+    if (rfi.leftAnno == null || rfi.rightAnno == null) {
+      return result;
+    }
+
+    // Refine the then branch.
+    refineGTE(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.thenStore, in);
+
+    // Refine the else branch.
+    refineGT(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.elseStore, in);
+
+    return rfi.newResult;
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitLessThanOrEqual(
+      LessThanOrEqualNode node, TransferInput<CFValue, CFStore> in) {
+    TransferResult<CFValue, CFStore> result = super.visitLessThanOrEqual(node, in);
+
+    IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node);
+    if (rfi.leftAnno == null || rfi.rightAnno == null) {
+      return result;
+    }
+
+    // Refine the then branch. A <= is just a flipped >=.
+    refineGTE(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.thenStore, in);
+
+    // Refine the else branch.
+    refineGT(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.elseStore, in);
+    return rfi.newResult;
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitLessThan(
+      LessThanNode node, TransferInput<CFValue, CFStore> in) {
+    TransferResult<CFValue, CFStore> result = super.visitLessThan(node, in);
+
+    IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node);
+    if (rfi.leftAnno == null || rfi.rightAnno == null) {
+      return result;
+    }
+
+    // Refine the then branch. A < is just a flipped >.
+    refineGT(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.thenStore, in);
+
+    // Refine the else branch.
+    refineGTE(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.elseStore, in);
+    return rfi.newResult;
+  }
+
+  protected abstract void refineGT(
+      Node left,
+      AnnotationMirror leftAnno,
+      Node right,
+      AnnotationMirror rightAnno,
+      CFStore store,
+      TransferInput<CFValue, CFStore> in);
+
+  protected abstract void refineGTE(
+      Node left,
+      AnnotationMirror leftAnno,
+      Node right,
+      AnnotationMirror rightAnno,
+      CFStore store,
+      TransferInput<CFValue, CFStore> in);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexChecker.java b/checker/src/main/java/org/checkerframework/checker/index/IndexChecker.java
new file mode 100644
index 0000000..c115246
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/IndexChecker.java
@@ -0,0 +1,84 @@
+package org.checkerframework.checker.index;
+
+import org.checkerframework.checker.index.upperbound.UpperBoundChecker;
+
+/**
+ * A type checker for preventing out-of-bounds accesses on fixed-length sequences, such as arrays
+ * and strings. Contains five subcheckers that do all of the actual work, which are described here.
+ * First, the order the checkers are run in is described, and then what each checker requests from
+ * the checkers that run before it is described. The Index Checker itself is just an alias for the
+ * Upper Bound Checker, which runs last.
+ *
+ * <p>The checkers run in this order:
+ *
+ * <p>Constant Value Checker, SameLen Checker, SearchIndex Checker, Lower Bound Checker, Upper Bound
+ * Checker
+ *
+ * <p>The Constant Value Checker has no dependencies, but it does trust Positive annotations from
+ * the Lower Bound Checker. This means that if the Value Checker is run on code containing Positive
+ * annotations, then the Lower Bound Checker also needs to be run to guarantee soundness.
+ *
+ * <p>The SameLen Checker has no dependencies.
+ *
+ * <p>The SearchIndex Checker depends only on the Value Checker, which it uses to refine
+ * SearchIndexFor types to NegativeIndexFor types by comparing to compile-time constants of zero or
+ * negative one.
+ *
+ * <p>The Lower Bound Checker depends only on the Value Checker. It uses the Value Checker to:
+ *
+ * <ul>
+ *   <li>give appropriate types to compile time constants. For example, the type of 7 is Positive, 0
+ *       is NonNegative, etc.
+ *   <li>in a subtraction expression of the form {@code a.length - x}, if x is a compile-time
+ *       constant, and if the minimum length of a &gt; x, the resulting expression is non-negative.
+ *   <li>when typing an array length (i.e. {@code a.length}), if the minimum length of the array is
+ *       &ge; 1, then the type is @Positive; if its MinLen is zero, then the type is @NonNegative.
+ * </ul>
+ *
+ * <p>The Upper Bound Checker depends on all three other checkers.
+ *
+ * <p>Value dependencies in the UBC:
+ *
+ * <ul>
+ *   <li>When computing offsets, the UBC replaces compile-time constants with their known values
+ *       (though it also keeps an offset with the variable's name, if applicable).
+ *   <li>The UBC has relaxed assignment rules: it allows assignments where the right hand side is a
+ *       value known at compile time and the type of the left hand side is annotated with
+ *       LT*LengthOf("a"). If the minimum length of a is in the correct relationship with the value
+ *       on the right hand side, then the assignment is legal.
+ *   <li>When checking whether an array access is legal, the UBC first checks the upper bound type
+ *       of the index. If that fails, it checks if the index is a compile-time constant. If it is,
+ *       then it queries the Value Checker to determine if the array is guaranteed to be longer than
+ *       the value of the constant. If it is, the access is safe.
+ *   <li>When compile time constants would improve the precision of reasoning about arithmetic, the
+ *       UBC queries the Value Checker for their values. For instance, dividing a value with type
+ *       LTLengthOf by a compile-time constant of 1 is guaranteed to result in another LTLengthOf
+ *       for the same arrays.
+ * </ul>
+ *
+ * <p>SameLen dependencies in the UBC:
+ *
+ * <ul>
+ *   <li>When checking whether an array access is legal, the UBC first checks the upper bound type.
+ *       If it's an LTL (or LTOM/LTEL), then it collects, from the SameLen Checker, the list of
+ *       arrays that are known to be the same length as the array being accessed. Then the
+ *       annotation is checked to see if it is valid for any of the arrays in question.
+ * </ul>
+ *
+ * <p>Lower Bound dependencies in UBC:
+ *
+ * <ul>
+ *   <li>When an array is created with length equal to the sum of two quantities, if one of the
+ *       quantities is non-negative, the other becomes LTEL of the new array. If one is positive,
+ *       the other becomes LTL.
+ *   <li>When a non-negative is subtracted from an LTL, it stays LTL.
+ * </ul>
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+// @RelevantJavaTypes annotations appear on other checkers.
+public class IndexChecker extends UpperBoundChecker {
+
+  /** Creates the Index Checker. */
+  public IndexChecker() {}
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexMethodIdentifier.java b/checker/src/main/java/org/checkerframework/checker/index/IndexMethodIdentifier.java
new file mode 100644
index 0000000..b00e094
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/IndexMethodIdentifier.java
@@ -0,0 +1,139 @@
+package org.checkerframework.checker.index;
+
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.index.qual.LengthOf;
+import org.checkerframework.dataflow.cfg.node.MethodAccessNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Given a Tree or other construct, this class has methods to query whether it is a particular
+ * method call.
+ */
+public class IndexMethodIdentifier {
+
+  /** The {@code java.lang.Math#random()} method. */
+  private final ExecutableElement mathRandom;
+  /** The {@code java.util.Random#nextDouble()} method. */
+  private final ExecutableElement randomNextDouble;
+  /** The {@code java.util.Random#nextInt()} method. */
+  private final ExecutableElement randomNextInt;
+  /** The {@code java.lang.String#length()} method. */
+  private final ExecutableElement stringLength;
+  /** The {@code java.lang.Math#min()} methods. */
+  private final List<ExecutableElement> mathMinMethods;
+  /** The {@code java.lang.Math#max()} methods. */
+  private final List<ExecutableElement> mathMaxMethods;
+
+  /** The LengthOf.value argument/element. */
+  private final ExecutableElement lengthOfValueElement;
+
+  /** The type factory. */
+  private final AnnotatedTypeFactory factory;
+
+  public IndexMethodIdentifier(AnnotatedTypeFactory factory) {
+    this.factory = factory;
+    ProcessingEnvironment processingEnv = factory.getProcessingEnv();
+    mathRandom = TreeUtils.getMethod("java.lang.Math", "random", 0, processingEnv);
+    randomNextDouble = TreeUtils.getMethod("java.util.Random", "nextDouble", 0, processingEnv);
+    randomNextInt = TreeUtils.getMethod("java.util.Random", "nextInt", 1, processingEnv);
+
+    stringLength = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv);
+
+    mathMinMethods = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv);
+    mathMaxMethods = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv);
+
+    lengthOfValueElement = TreeUtils.getMethod(LengthOf.class, "value", 0, processingEnv);
+  }
+
+  /** Returns true iff the argument is an invocation of Math.min. */
+  public boolean isMathMin(Tree methodTree) {
+    ProcessingEnvironment processingEnv = factory.getProcessingEnv();
+    return TreeUtils.isMethodInvocation(methodTree, mathMinMethods, processingEnv);
+  }
+
+  /** Returns true iff the argument is an invocation of Math.max. */
+  public boolean isMathMax(Tree methodTree) {
+    ProcessingEnvironment processingEnv = factory.getProcessingEnv();
+    return TreeUtils.isMethodInvocation(methodTree, mathMaxMethods, processingEnv);
+  }
+
+  /** Returns true iff the argument is an invocation of Math.random(). */
+  public boolean isMathRandom(Tree tree, ProcessingEnvironment processingEnv) {
+    return TreeUtils.isMethodInvocation(tree, mathRandom, processingEnv);
+  }
+
+  /** Returns true iff the argument is an invocation of Random.nextDouble(). */
+  public boolean isRandomNextDouble(Tree tree, ProcessingEnvironment processingEnv) {
+    return TreeUtils.isMethodInvocation(tree, randomNextDouble, processingEnv);
+  }
+
+  /** Returns true iff the argument is an invocation of Random.nextInt(). */
+  public boolean isRandomNextInt(Tree tree, ProcessingEnvironment processingEnv) {
+    return TreeUtils.isMethodInvocation(tree, randomNextInt, processingEnv);
+  }
+
+  /**
+   * Returns true if {@code tree} is an invocation of a method that returns the length of "this"
+   *
+   * @param tree a tree
+   * @return true if {@code tree} is an invocation of a method that returns the length of {@code
+   *     this}
+   */
+  public boolean isLengthOfMethodInvocation(Tree tree) {
+    if (tree.getKind() != Kind.METHOD_INVOCATION) {
+      return false;
+    }
+    return isLengthOfMethodInvocation(TreeUtils.elementFromUse((MethodInvocationTree) tree));
+  }
+
+  /**
+   * Returns true if {@code tree} evaluates to the length of "this". This might be a call to
+   * String,length, or a method annotated with @LengthOf.
+   *
+   * @return true if {@code tree} evaluates to the length of "this"
+   */
+  public boolean isLengthOfMethodInvocation(ExecutableElement ele) {
+    if (stringLength.equals(ele)) {
+      // TODO: Why not just annotate String.length with @LengthOf and thus eliminate the
+      // special case in this method's implementation?
+      return true;
+    }
+
+    AnnotationMirror lengthOfAnno = factory.getDeclAnnotation(ele, LengthOf.class);
+    if (lengthOfAnno == null) {
+      return false;
+    }
+    AnnotationValue lengthOfValue = lengthOfAnno.getElementValues().get(lengthOfValueElement);
+    return AnnotationUtils.annotationValueContains(lengthOfValue, "this");
+  }
+
+  /**
+   * Returns true if {@code node} is an invocation of a method that returns the length of {@code
+   * this}
+   *
+   * @param node a node
+   * @return true if {@code node} is an invocation of a method that returns the length of {@code
+   *     this}
+   */
+  public boolean isLengthOfMethodInvocation(Node node) {
+    if (node instanceof MethodInvocationNode) {
+      MethodInvocationNode methodInvocationNode = (MethodInvocationNode) node;
+      MethodAccessNode methodAccessNode = methodInvocationNode.getTarget();
+      ExecutableElement ele = methodAccessNode.getMethod();
+
+      return isLengthOfMethodInvocation(ele);
+    }
+    return false;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexRefinementInfo.java b/checker/src/main/java/org/checkerframework/checker/index/IndexRefinementInfo.java
new file mode 100644
index 0000000..bb7a076
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/IndexRefinementInfo.java
@@ -0,0 +1,74 @@
+package org.checkerframework.checker.index;
+
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.BinaryOperationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * This struct contains all of the information that the refinement functions need. It's called by
+ * each node function (i.e. greater than node, less than node, etc.) and then the results are passed
+ * to the refinement function in whatever order is appropriate for that node. Its constructor
+ * contains all of its logic.
+ */
+public class IndexRefinementInfo {
+
+  public Node left, right;
+
+  /**
+   * Annotation for left and right expressions. Might be null if dataflow doesn't have a value for
+   * the expression.
+   */
+  public AnnotationMirror leftAnno, rightAnno;
+
+  public CFStore thenStore, elseStore;
+  public ConditionalTransferResult<CFValue, CFStore> newResult;
+
+  public IndexRefinementInfo(
+      TransferResult<CFValue, CFStore> result,
+      CFAbstractAnalysis<?, ?, ?> analysis,
+      Node r,
+      Node l) {
+    right = r;
+    left = l;
+
+    if (analysis.getValue(right) == null || analysis.getValue(left) == null) {
+      leftAnno = null;
+      rightAnno = null;
+      newResult = new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore);
+    } else {
+      QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy();
+      rightAnno = getAnno(analysis.getValue(right).getAnnotations(), hierarchy);
+      leftAnno = getAnno(analysis.getValue(left).getAnnotations(), hierarchy);
+
+      thenStore = result.getThenStore();
+      elseStore = result.getElseStore();
+
+      newResult = new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore);
+    }
+  }
+
+  public IndexRefinementInfo(
+      TransferResult<CFValue, CFStore> result,
+      CFAbstractAnalysis<?, ?, ?> analysis,
+      BinaryOperationNode node) {
+    this(result, analysis, node.getRightOperand(), node.getLeftOperand());
+  }
+
+  private static AnnotationMirror getAnno(Set<AnnotationMirror> set, QualifierHierarchy hierarchy) {
+    Set<? extends AnnotationMirror> tops = hierarchy.getTopAnnotations();
+    if (tops.size() != 1) {
+      throw new BugInCF(
+          "%s: Found %d tops, but expected one.%nFound: %s",
+          IndexRefinementInfo.class, tops.size(), tops);
+    }
+    return hierarchy.findAnnotationInHierarchy(set, tops.iterator().next());
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexUtil.java b/checker/src/main/java/org/checkerframework/checker/index/IndexUtil.java
new file mode 100644
index 0000000..a9b934a
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/IndexUtil.java
@@ -0,0 +1,31 @@
+package org.checkerframework.checker.index;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** A collection of utility functions used by several Index Checker subcheckers. */
+public class IndexUtil {
+  /** Determines whether the type is a sequence supported by this checker. */
+  public static boolean isSequenceType(TypeMirror type) {
+    return type.getKind() == TypeKind.ARRAY || TypesUtils.isString(type);
+  }
+
+  /** Gets a sequence tree for a length access tree, or null if it is not a length access. */
+  public static ExpressionTree getLengthSequenceTree(
+      Tree lengthTree, IndexMethodIdentifier imf, ProcessingEnvironment processingEnv) {
+    if (TreeUtils.isArrayLengthAccess(lengthTree)) {
+      return ((MemberSelectTree) lengthTree).getExpression();
+    } else if (imf.isLengthOfMethodInvocation(lengthTree)) {
+      return TreeUtils.getReceiverTree((MethodInvocationTree) lengthTree);
+    }
+
+    return null;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/OffsetDependentTypesHelper.java b/checker/src/main/java/org/checkerframework/checker/index/OffsetDependentTypesHelper.java
new file mode 100644
index 0000000..7ebea40
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/OffsetDependentTypesHelper.java
@@ -0,0 +1,43 @@
+package org.checkerframework.checker.index;
+
+import com.sun.source.tree.MemberSelectTree;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.value.ValueCheckerUtils;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesTreeAnnotator;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Dependent type helper for array offset expressions. Each array offset expression may be the
+ * addition or subtraction of several Java expressions. For example, {@code array.length - 1}.
+ */
+public class OffsetDependentTypesHelper extends DependentTypesHelper {
+  public OffsetDependentTypesHelper(AnnotatedTypeFactory factory) {
+    super(factory);
+  }
+
+  @Override
+  protected @Nullable JavaExpression transform(JavaExpression javaExpr) {
+    return ValueCheckerUtils.optimize(javaExpr, factory);
+  }
+
+  @Override
+  public TreeAnnotator createDependentTypesTreeAnnotator() {
+    return new DependentTypesTreeAnnotator(factory, this) {
+      @Override
+      public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) {
+        // UpperBoundTreeAnnotator changes the type of array.length to @LTEL("array").
+        // If the DependentTypesTreeAnnotator tries to viewpoint-adapt it based on the
+        // declaration of length, it will fail.
+        if (TreeUtils.isArrayLengthAccess(tree)) {
+          return null;
+        }
+        return super.visitMemberSelect(tree, type);
+      }
+    };
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/Subsequence.java b/checker/src/main/java/org/checkerframework/checker/index/Subsequence.java
new file mode 100644
index 0000000..2ef1fd6
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/Subsequence.java
@@ -0,0 +1,164 @@
+package org.checkerframework.checker.index;
+
+import com.sun.source.tree.Tree;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.checker.index.qual.HasSubsequence;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.util.JavaExpressionParseUtil;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.framework.util.StringToJavaExpression;
+import org.checkerframework.javacutil.TreeUtils;
+
+/** Holds information from {@link HasSubsequence} annotations. */
+public class Subsequence {
+
+  /** Name of the Subsequence. */
+  public final String array;
+  /** First index of the subsequence in the backing sequence. */
+  public final String from;
+  /** Last index of the subsequence in the backing sequence. */
+  public final String to;
+
+  private Subsequence(String array, String from, String to) {
+    this.array = array;
+    this.from = from;
+    this.to = to;
+  }
+
+  /**
+   * Returns a Subsequence representing the {@link HasSubsequence} annotation on the declaration of
+   * {@code varTree} or null if there is not such annotation.
+   *
+   * <p>Note that this method does not standardize or viewpoint adapt the arguments to the
+   * annotation, unlike getSubsequenceFromReceiver.
+   *
+   * @param varTree some tree
+   * @param factory an AnnotatedTypeFactory
+   * @return null or a new Subsequence from the declaration of {@code varTree}
+   */
+  public static Subsequence getSubsequenceFromTree(
+      Tree varTree, BaseAnnotatedTypeFactoryForIndexChecker factory) {
+
+    if (!(varTree.getKind() == Tree.Kind.IDENTIFIER
+        || varTree.getKind() == Tree.Kind.MEMBER_SELECT
+        || varTree.getKind() == Tree.Kind.VARIABLE)) {
+      return null;
+    }
+
+    Element element = TreeUtils.elementFromTree(varTree);
+    AnnotationMirror hasSub = factory.getDeclAnnotation(element, HasSubsequence.class);
+    return createSubsequence(hasSub, factory);
+  }
+
+  /**
+   * Factory method to create a representation of a subsequence.
+   *
+   * @param hasSub {@link HasSubsequence} annotation or null
+   * @param factory the type factory
+   * @return a new Subsequence object representing {@code hasSub} or null
+   */
+  private static Subsequence createSubsequence(
+      AnnotationMirror hasSub, BaseAnnotatedTypeFactoryForIndexChecker factory) {
+    if (hasSub == null) {
+      return null;
+    }
+    String from = factory.hasSubsequenceFromValue(hasSub);
+    String to = factory.hasSubsequenceToValue(hasSub);
+    String array = factory.hasSubsequenceSubsequenceValue(hasSub);
+
+    return new Subsequence(array, from, to);
+  }
+
+  /**
+   * Returns a Subsequence representing the {@link HasSubsequence} annotation on the declaration of
+   * {@code rec} or null if there is not such annotation.
+   *
+   * @param expr some tree
+   * @param factory an AnnotatedTypeFactory
+   * @return null or a new Subsequence from the declaration of {@code varTree}
+   */
+  public static Subsequence getSubsequenceFromReceiver(
+      JavaExpression expr, BaseAnnotatedTypeFactoryForIndexChecker factory) {
+    if (!(expr instanceof FieldAccess)) {
+      return null;
+    }
+
+    FieldAccess fa = (FieldAccess) expr;
+    VariableElement element = fa.getField();
+    AnnotationMirror hasSub = factory.getDeclAnnotation(element, HasSubsequence.class);
+    if (hasSub == null) {
+      return null;
+    }
+
+    String array =
+        standardizeAndViewpointAdapt(
+            factory.hasSubsequenceSubsequenceValue(hasSub), fa, factory.getChecker());
+    String from =
+        standardizeAndViewpointAdapt(
+            factory.hasSubsequenceFromValue(hasSub), fa, factory.getChecker());
+    String to =
+        standardizeAndViewpointAdapt(
+            factory.hasSubsequenceToValue(hasSub), fa, factory.getChecker());
+
+    return new Subsequence(array, from, to);
+  }
+
+  /**
+   * Helper function to standardize and viewpoint-adapt a string at a given field access. Wraps
+   * {@link JavaExpressionParseUtil#parse}. If a parse exception is encountered, this returns its
+   * argument.
+   *
+   * @param s a Java expression string
+   * @param fieldAccess the field access
+   * @param checker used to parse the expression
+   * @return the argument, standardized and viewpoint-adapted
+   */
+  private static String standardizeAndViewpointAdapt(
+      String s, FieldAccess fieldAccess, SourceChecker checker) {
+    JavaExpression parseResult;
+    try {
+      parseResult = StringToJavaExpression.atFieldDecl(s, fieldAccess.getField(), checker);
+    } catch (JavaExpressionParseException e) {
+      return s;
+    }
+
+    return parseResult.atFieldAccess(fieldAccess.getReceiver()).toString();
+  }
+
+  /**
+   * Returns the additive inverse of the given String. That is, if the result of this method is some
+   * String s', then s + s' == 0 will evaluate to true. Note that this relies on the fact that the
+   * JavaExpression parser cannot parse multiplication, so it naively just changes '-' to '+' and
+   * vice-versa.
+   *
+   * @param s a Java expression string
+   * @return the negated string
+   */
+  public static String negateString(String s) {
+    String original = s;
+    String result = "";
+    if (!original.startsWith("-")) {
+      result += '-';
+    }
+    for (int i = 0; i < original.length(); i++) {
+      char c = original.charAt(i);
+      if (c == '-') {
+        result += '+';
+      } else if (c == '+') {
+        result += '-';
+      } else {
+        result += c;
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "Subsequence(array=" + this.array + ", from=" + this.from + ", to=" + this.to + ")";
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanAnnotatedTypeFactory.java
new file mode 100644
index 0000000..7694cde
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanAnnotatedTypeFactory.java
@@ -0,0 +1,353 @@
+package org.checkerframework.checker.index.inequality;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.index.BaseAnnotatedTypeFactoryForIndexChecker;
+import org.checkerframework.checker.index.OffsetDependentTypesHelper;
+import org.checkerframework.checker.index.qual.LessThan;
+import org.checkerframework.checker.index.qual.LessThanBottom;
+import org.checkerframework.checker.index.qual.LessThanUnknown;
+import org.checkerframework.checker.index.upperbound.OffsetEquation;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.common.value.ValueCheckerUtils;
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.ArrayLenRange;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.ElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.SystemUtil;
+import org.checkerframework.javacutil.TreeUtils;
+
+/** The type factory for the Less Than Checker. */
+public class LessThanAnnotatedTypeFactory extends BaseAnnotatedTypeFactoryForIndexChecker {
+  /** The @LessThanBottom annotation. */
+  private final AnnotationMirror LESS_THAN_BOTTOM =
+      AnnotationBuilder.fromClass(elements, LessThanBottom.class);
+  /** The @LessThanUnknown annotation. */
+  public final AnnotationMirror LESS_THAN_UNKNOWN =
+      AnnotationBuilder.fromClass(elements, LessThanUnknown.class);
+
+  /** The LessThan.value argument/element. */
+  private final ExecutableElement lessThanValueElement =
+      TreeUtils.getMethod(LessThan.class, "value", 0, processingEnv);
+
+  /**
+   * Creates a new LessThanAnnotatedTypeFactory.
+   *
+   * @param checker the type-checker associated with this type factory
+   */
+  public LessThanAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    postInit();
+  }
+
+  /**
+   * Returns the Value Checker's annotated type factory.
+   *
+   * @return the Value Checker's annotated type factory
+   */
+  public ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() {
+    return getTypeFactoryOfSubchecker(ValueChecker.class);
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new LinkedHashSet<>(
+        Arrays.asList(LessThan.class, LessThanUnknown.class, LessThanBottom.class));
+  }
+
+  @Override
+  protected DependentTypesHelper createDependentTypesHelper() {
+    // Allows + or - in a @LessThan.
+    return new OffsetDependentTypesHelper(this);
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new LessThanQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
+  }
+
+  /** LessThanQualifierHierarchy. */
+  class LessThanQualifierHierarchy extends ElementQualifierHierarchy {
+
+    /**
+     * Creates a LessThanQualifierHierarchy from the given classes.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers
+     * @param elements element utils
+     */
+    public LessThanQualifierHierarchy(
+        Set<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+    }
+
+    @Override
+    public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+      List<String> subList = getLessThanExpressions(subAnno);
+      if (subList == null) {
+        return true;
+      }
+      List<String> superList = getLessThanExpressions(superAnno);
+      if (superList == null) {
+        return false;
+      }
+
+      return subList.containsAll(superList);
+    }
+
+    @Override
+    public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
+      if (isSubtype(a1, a2)) {
+        return a2;
+      } else if (isSubtype(a2, a1)) {
+        return a1;
+      }
+
+      List<String> a1List = getLessThanExpressions(a1);
+      List<String> a2List = getLessThanExpressions(a2);
+      a1List.retainAll(a2List); // intersection
+      return createLessThanQualifier(a1List);
+    }
+
+    @Override
+    public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
+      if (isSubtype(a1, a2)) {
+        return a1;
+      } else if (isSubtype(a2, a1)) {
+        return a2;
+      }
+
+      List<String> a1List = getLessThanExpressions(a1);
+      List<String> a2List = getLessThanExpressions(a2);
+      SystemUtil.addWithoutDuplicates(a1List, a2List); // union
+      return createLessThanQualifier(a1List);
+    }
+  }
+
+  /**
+   * Returns true if {@code left} is less than {@code right}.
+   *
+   * @param left the first tree to compare
+   * @param right the second tree to compare
+   * @return is left less than right?
+   */
+  public boolean isLessThan(Tree left, String right) {
+    AnnotatedTypeMirror leftATM = getAnnotatedType(left);
+    return isLessThan(leftATM.getAnnotationInHierarchy(LESS_THAN_UNKNOWN), right);
+  }
+
+  /**
+   * Returns true if {@code left} is less than {@code right}.
+   *
+   * @param left the first value to compare (an annotation)
+   * @param right the second value to compare (an expression)
+   * @return is left less than right?
+   */
+  public boolean isLessThan(AnnotationMirror left, String right) {
+    List<String> expressions = getLessThanExpressions(left);
+    if (expressions == null) {
+      // `left` is @LessThanBottom
+      return true;
+    }
+    return expressions.contains(right);
+  }
+
+  /**
+   * Returns true if {@code smaller < bigger}.
+   *
+   * @param smaller the first value to compare
+   * @param bigger the second value to compare
+   * @return {@code smaller < bigger}, using information from the Value Checker
+   */
+  public boolean isLessThanByValue(Tree smaller, String bigger, TreePath path) {
+    Long smallerValue = ValueCheckerUtils.getMinValue(smaller, getValueAnnotatedTypeFactory());
+    if (smallerValue == null) {
+      return false;
+    }
+
+    OffsetEquation offsetEquation = OffsetEquation.createOffsetFromJavaExpression(bigger);
+    if (offsetEquation.isInt()) {
+      // bigger is an int literal
+      return smallerValue < offsetEquation.getInt();
+    }
+    // If bigger is "expression + literal", then smaller < expression + literal
+    // can be reduced to smaller - literal < expression + literal - literal
+    smallerValue = smallerValue - offsetEquation.getInt();
+    offsetEquation =
+        offsetEquation.copyAdd('-', OffsetEquation.createOffsetForInt(offsetEquation.getInt()));
+
+    long minValueOfBigger = getMinValueFromString(offsetEquation.toString(), smaller, path);
+    return smallerValue < minValueOfBigger;
+  }
+
+  /**
+   * Returns the minimum value of {@code expression} at {@code tree}.
+   *
+   * @param expression the expression whose minimum value to retrieve
+   * @param tree where to determine the value
+   * @param path the path to {@code tree}
+   * @return the minimum value of {@code expression} at {@code tree}
+   */
+  private long getMinValueFromString(String expression, Tree tree, TreePath path) {
+    ValueAnnotatedTypeFactory valueAtypeFactory = getValueAnnotatedTypeFactory();
+    JavaExpression expressionJe;
+    try {
+      expressionJe = valueAtypeFactory.parseJavaExpressionString(expression, path);
+    } catch (JavaExpressionParseException e) {
+      return Long.MIN_VALUE;
+    }
+
+    AnnotationMirror intRange =
+        valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, IntRange.class);
+    if (intRange != null) {
+      return valueAtypeFactory.getRange(intRange).from;
+    }
+    AnnotationMirror intValue =
+        valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, IntVal.class);
+    if (intValue != null) {
+      List<Long> possibleValues = valueAtypeFactory.getIntValues(intValue);
+      return Collections.min(possibleValues);
+    }
+
+    if (expressionJe instanceof FieldAccess) {
+      FieldAccess fieldAccess = ((FieldAccess) expressionJe);
+      if (fieldAccess.getReceiver().getType().getKind() == TypeKind.ARRAY) {
+        // array.length might not be in the store, so check for the length of the array.
+        AnnotationMirror arrayRange =
+            valueAtypeFactory.getAnnotationFromJavaExpression(
+                fieldAccess.getReceiver(), tree, ArrayLenRange.class);
+        if (arrayRange != null) {
+          return valueAtypeFactory.getRange(arrayRange).from;
+        }
+        AnnotationMirror arrayLen =
+            valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, ArrayLen.class);
+        if (arrayLen != null) {
+          List<Integer> possibleValues = valueAtypeFactory.getArrayLength(arrayLen);
+          return Collections.min(possibleValues);
+        }
+        // Even arrays that we know nothing about must have at least zero length.
+        return 0;
+      }
+    }
+
+    return Long.MIN_VALUE;
+  }
+
+  /**
+   * Returns true if left is less than or equal to right.
+   *
+   * @param left the first value to compare
+   * @param right the second value to compare
+   * @return is left less than or equal to right?
+   */
+  public boolean isLessThanOrEqual(Tree left, String right) {
+    AnnotatedTypeMirror leftATM = getAnnotatedType(left);
+    return isLessThanOrEqual(leftATM.getAnnotationInHierarchy(LESS_THAN_UNKNOWN), right);
+  }
+
+  /**
+   * Returns true if left is less than or equal to right.
+   *
+   * @param left the first value to compare
+   * @param right the second value to compare
+   * @return is left less than or equal to right?
+   */
+  public boolean isLessThanOrEqual(AnnotationMirror left, String right) {
+    List<String> expressions = getLessThanExpressions(left);
+    if (expressions == null) {
+      // left is bottom so it is always less than right.
+      return true;
+    }
+    if (expressions.contains(right)) {
+      return true;
+    }
+    // {@code @LessThan("end + 1")} is equivalent to {@code @LessThanOrEqual("end")}.
+    for (String expression : expressions) {
+      if (expression.endsWith(" + 1")
+          && expression.substring(0, expression.length() - 4).equals(right)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns a sorted, modifiable list of expressions that {@code expression} is less than. If the
+   * {@code expression} is annotated with {@link LessThanBottom}, null is returned.
+   *
+   * @param expression an expression
+   * @return expressions that {@code expression} is less than
+   */
+  public List<String> getLessThanExpressions(ExpressionTree expression) {
+    AnnotatedTypeMirror annotatedTypeMirror = getAnnotatedType(expression);
+    return getLessThanExpressions(annotatedTypeMirror.getAnnotationInHierarchy(LESS_THAN_UNKNOWN));
+  }
+
+  /**
+   * Creates a less than qualifier given the expressions.
+   *
+   * <p>If expressions is null, {@link LessThanBottom} is returned. If expressions is empty, {@link
+   * LessThanUnknown} is returned. Otherwise, {@code @LessThan(expressions)} is returned.
+   *
+   * @param expressions a list of expressions
+   * @return a @LessThan qualifier with the given arguments
+   */
+  public AnnotationMirror createLessThanQualifier(List<String> expressions) {
+    if (expressions == null) {
+      return LESS_THAN_BOTTOM;
+    } else if (expressions.isEmpty()) {
+      return LESS_THAN_UNKNOWN;
+    } else {
+      AnnotationBuilder builder = new AnnotationBuilder(processingEnv, LessThan.class);
+      builder.setValue("value", expressions);
+      return builder.build();
+    }
+  }
+
+  /** Returns {@code @LessThan(expression)}. */
+  public AnnotationMirror createLessThanQualifier(String expression) {
+    return createLessThanQualifier(Collections.singletonList(expression));
+  }
+
+  /**
+   * If the annotation is LessThan, returns a list of expressions in the annotation. If the
+   * annotation is {@link LessThanBottom}, returns null. If the annotation is {@link
+   * LessThanUnknown}, returns the empty list.
+   *
+   * @param annotation an annotation from the same hierarchy as LessThan
+   * @return the list of expressions in the annotation
+   */
+  public List<String> getLessThanExpressions(AnnotationMirror annotation) {
+    if (AnnotationUtils.areSameByName(
+        annotation, "org.checkerframework.checker.index.qual.LessThanBottom")) {
+      return null;
+    } else if (AnnotationUtils.areSameByName(
+        annotation, "org.checkerframework.checker.index.qual.LessThanUnknown")) {
+      return Collections.emptyList();
+    } else {
+      // The annotation is @LessThan.
+      return AnnotationUtils.getElementValueArray(annotation, lessThanValueElement, String.class);
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanChecker.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanChecker.java
new file mode 100644
index 0000000..ada8078
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanChecker.java
@@ -0,0 +1,36 @@
+package org.checkerframework.checker.index.inequality;
+
+import java.util.LinkedHashSet;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+import org.checkerframework.framework.source.SuppressWarningsPrefix;
+
+/**
+ * An internal checker that estimates which expression's values are less than other expressions'
+ * values.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@SuppressWarningsPrefix({"index", "lessthan"})
+@RelevantJavaTypes({
+  Byte.class,
+  Short.class,
+  Integer.class,
+  Long.class,
+  Character.class,
+  byte.class,
+  short.class,
+  int.class,
+  long.class,
+  char.class,
+})
+public class LessThanChecker extends BaseTypeChecker {
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    LinkedHashSet<Class<? extends BaseTypeChecker>> checkers =
+        super.getImmediateSubcheckerClasses();
+    checkers.add(ValueChecker.class);
+    return checkers;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanTransfer.java
new file mode 100644
index 0000000..b15179c
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanTransfer.java
@@ -0,0 +1,188 @@
+package org.checkerframework.checker.index.inequality;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.checker.index.IndexAbstractTransfer;
+import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
+import org.checkerframework.common.value.ValueCheckerUtils;
+import org.checkerframework.dataflow.analysis.RegularTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.ValueLiteral;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.util.JavaExpressionParseUtil;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * Implements 3 refinement rules:
+ *
+ * <ul>
+ *   <li>1. if left &gt; right, right has type {@code @LessThan("left")}
+ *   <li>2. if left &ge; right, right has type {@code @LessThan("left + 1")}
+ *   <li>3. if {@code 0 < right}, {@code left - right} has type {@code @LessThan("left")}
+ * </ul>
+ *
+ * These are implemented generally, so they also apply to e.g. &lt; and &le; comparisons.
+ */
+public class LessThanTransfer extends IndexAbstractTransfer {
+
+  public LessThanTransfer(CFAnalysis analysis) {
+    super(analysis);
+  }
+
+  /** Case 1. */
+  @Override
+  protected void refineGT(
+      Node left,
+      AnnotationMirror leftAnno,
+      Node right,
+      AnnotationMirror rightAnno,
+      CFStore store,
+      TransferInput<CFValue, CFStore> in) {
+    // left > right so right < left
+    // Refine right to @LessThan("left")
+    JavaExpression leftJe = JavaExpression.fromNode(left);
+    if (leftJe != null && leftJe.isUnassignableByOtherCode()) {
+      if (isDoubleOrFloatLiteral(leftJe)) {
+        return;
+      }
+      LessThanAnnotatedTypeFactory factory =
+          (LessThanAnnotatedTypeFactory) analysis.getTypeFactory();
+      List<String> lessThanExpressions = factory.getLessThanExpressions(rightAnno);
+      if (lessThanExpressions == null) {
+        // right is already bottom, nothing to refine.
+        return;
+      }
+      String leftString = leftJe.toString();
+      if (!lessThanExpressions.contains(leftString)) {
+        lessThanExpressions = CollectionsPlume.append(lessThanExpressions, leftString);
+        JavaExpression rightJe = JavaExpression.fromNode(right);
+        store.insertValue(rightJe, factory.createLessThanQualifier(lessThanExpressions));
+      }
+    }
+  }
+
+  /** Case 2. */
+  @Override
+  protected void refineGTE(
+      Node left,
+      AnnotationMirror leftAnno,
+      Node right,
+      AnnotationMirror rightAnno,
+      CFStore store,
+      TransferInput<CFValue, CFStore> in) {
+    // left >= right so right is less than left
+    // Refine right to @LessThan("left + 1")
+
+    // left > right so right is less than left
+    // Refine right to @LessThan("left")
+    JavaExpression leftJe = JavaExpression.fromNode(left);
+    if (leftJe != null && leftJe.isUnassignableByOtherCode()) {
+      if (isDoubleOrFloatLiteral(leftJe)) {
+        return;
+      }
+      LessThanAnnotatedTypeFactory factory =
+          (LessThanAnnotatedTypeFactory) analysis.getTypeFactory();
+      List<String> lessThanExpressions = factory.getLessThanExpressions(rightAnno);
+      if (lessThanExpressions == null) {
+        // right is already bottom, nothing to refine.
+        return;
+      }
+      String leftIncremented = incrementedExpression(leftJe);
+      if (!lessThanExpressions.contains(leftIncremented)) {
+        lessThanExpressions = CollectionsPlume.append(lessThanExpressions, leftIncremented);
+        JavaExpression rightJe = JavaExpression.fromNode(right);
+        store.insertValue(rightJe, factory.createLessThanQualifier(lessThanExpressions));
+      }
+    }
+  }
+
+  /** Case 3. */
+  @Override
+  public TransferResult<CFValue, CFStore> visitNumericalSubtraction(
+      NumericalSubtractionNode n, TransferInput<CFValue, CFStore> in) {
+    LessThanAnnotatedTypeFactory factory = (LessThanAnnotatedTypeFactory) analysis.getTypeFactory();
+    JavaExpression leftJe = JavaExpression.fromNode(n.getLeftOperand());
+    if (leftJe != null && leftJe.isUnassignableByOtherCode()) {
+      ValueAnnotatedTypeFactory valueFactory = factory.getValueAnnotatedTypeFactory();
+      Long right = ValueCheckerUtils.getMinValue(n.getRightOperand().getTree(), valueFactory);
+      if (right != null && 0 < right) {
+        // left - right < left iff 0 < right
+        List<String> expressions = getLessThanExpressions(n.getLeftOperand());
+        if (!isDoubleOrFloatLiteral(leftJe)) {
+          if (expressions == null) {
+            expressions = Collections.singletonList(leftJe.toString());
+          } else {
+            expressions = CollectionsPlume.append(expressions, leftJe.toString());
+          }
+        }
+        AnnotationMirror refine = factory.createLessThanQualifier(expressions);
+        CFValue value = analysis.createSingleAnnotationValue(refine, n.getType());
+        CFStore info = in.getRegularStore();
+        return new RegularTransferResult<>(finishValue(value, info), info);
+      }
+    }
+    return super.visitNumericalSubtraction(n, in);
+  }
+
+  /**
+   * Return the expressions that {@code node} is less than.
+   *
+   * @param node a CFG node
+   * @return the expressions that {@code node} is less than
+   */
+  private List<String> getLessThanExpressions(Node node) {
+    Set<AnnotationMirror> s = analysis.getValue(node).getAnnotations();
+    if (s != null && !s.isEmpty()) {
+      LessThanAnnotatedTypeFactory factory =
+          (LessThanAnnotatedTypeFactory) analysis.getTypeFactory();
+      return factory.getLessThanExpressions(
+          factory.getQualifierHierarchy().findAnnotationInHierarchy(s, factory.LESS_THAN_UNKNOWN));
+    } else {
+      return Collections.emptyList();
+    }
+  }
+
+  /**
+   * Return true if {@code expr} is a double or float literal, which can't be parsed by {@link
+   * JavaExpressionParseUtil}.
+   */
+  private boolean isDoubleOrFloatLiteral(JavaExpression expr) {
+    if (expr instanceof ValueLiteral) {
+      return expr.getType().getKind() == TypeKind.DOUBLE
+          || expr.getType().getKind() == TypeKind.FLOAT;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Return the string representation of {@code expr + 1}.
+   *
+   * @param expr a JavaExpression
+   * @return the string representation of {@code expr + 1}
+   */
+  private String incrementedExpression(JavaExpression expr) {
+    expr = ValueCheckerUtils.optimize(expr, analysis.getTypeFactory());
+    if (expr instanceof ValueLiteral) {
+      ValueLiteral literal = (ValueLiteral) expr;
+      if (literal.getValue() instanceof Number) {
+        long longLiteral = ((Number) literal.getValue()).longValue();
+        if (longLiteral != Long.MAX_VALUE) {
+          return (longLiteral + 1) + "L";
+        }
+      }
+    }
+
+    // Could do more optimization to merge with a literal at end of `exprString`.  Is that needed?
+    return expr + " + 1";
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java
new file mode 100644
index 0000000..e64cc77
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java
@@ -0,0 +1,132 @@
+package org.checkerframework.checker.index.inequality;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.Tree;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.index.Subsequence;
+import org.checkerframework.checker.index.upperbound.OffsetEquation;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.util.JavaExpressionParseUtil;
+import org.plumelib.util.CollectionsPlume;
+
+/** The visitor for the Less Than Checker. */
+public class LessThanVisitor extends BaseTypeVisitor<LessThanAnnotatedTypeFactory> {
+
+  private static final @CompilerMessageKey String FROM_GT_TO = "from.gt.to";
+
+  public LessThanVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  protected void commonAssignmentCheck(
+      Tree varTree,
+      ExpressionTree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+
+    // check that when an assignment to a variable declared as @HasSubsequence(a, from, to)
+    // occurs, from <= to.
+
+    Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory);
+    if (subSeq != null) {
+      AnnotationMirror anm;
+      try {
+        anm =
+            atypeFactory.getAnnotationMirrorFromJavaExpressionString(
+                subSeq.from, varTree, getCurrentPath());
+      } catch (JavaExpressionParseUtil.JavaExpressionParseException e) {
+        anm = null;
+      }
+
+      LessThanAnnotatedTypeFactory factory = getTypeFactory();
+
+      if (anm == null || !factory.isLessThanOrEqual(anm, subSeq.to)) {
+        // issue an error
+        checker.reportError(
+            valueTree,
+            FROM_GT_TO,
+            subSeq.from,
+            subSeq.to,
+            anm == null ? "@LessThanUnknown" : anm,
+            subSeq.to,
+            subSeq.to);
+      }
+    }
+
+    super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs);
+  }
+
+  @Override
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      AnnotatedTypeMirror valueType,
+      Tree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    // If value is less than all expressions in the annotation in varType,
+    // using the Value Checker, then skip the common assignment check.
+    // Also skip the check if the only expression is "a + 1" and the valueTree is "a".
+    List<String> expressions =
+        getTypeFactory()
+            .getLessThanExpressions(
+                varType.getEffectiveAnnotationInHierarchy(atypeFactory.LESS_THAN_UNKNOWN));
+    if (expressions != null) {
+      boolean isLessThan = true;
+      for (String expression : expressions) {
+        if (!atypeFactory.isLessThanByValue(valueTree, expression, getCurrentPath())) {
+          isLessThan = false;
+        }
+      }
+      if (!isLessThan && expressions.size() == 1) {
+        String expression = expressions.get(0);
+        if (expression.endsWith(" + 1")) {
+          String value = expression.substring(0, expression.length() - 4);
+          if (valueTree.getKind() == Tree.Kind.IDENTIFIER) {
+            String id = ((IdentifierTree) valueTree).getName().toString();
+            if (id.equals(value)) {
+              isLessThan = true;
+            }
+          }
+        }
+      }
+
+      if (isLessThan) {
+        // Print the messages because super isn't called.
+        commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree);
+        commonAssignmentCheckEndDiagnostic(true, "isLessThan", varType, valueType, valueTree);
+        // skip call to super, everything is OK.
+        return;
+      }
+    }
+    super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
+  }
+
+  @Override
+  protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) {
+
+    AnnotationMirror exprLTAnno =
+        exprType.getEffectiveAnnotationInHierarchy(atypeFactory.LESS_THAN_UNKNOWN);
+
+    if (exprLTAnno != null) {
+      LessThanAnnotatedTypeFactory factory = getTypeFactory();
+      List<String> initialAnnotations = factory.getLessThanExpressions(exprLTAnno);
+
+      if (initialAnnotations != null) {
+        List<String> updatedAnnotations =
+            CollectionsPlume.mapList(
+                annotation -> OffsetEquation.createOffsetFromJavaExpression(annotation).toString(),
+                initialAnnotations);
+
+        exprType.replaceAnnotation(atypeFactory.createLessThanQualifier(updatedAnnotations));
+      }
+    }
+
+    return super.isTypeCastSafe(castType, exprType);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/messages.properties b/checker/src/main/java/org/checkerframework/checker/index/inequality/messages.properties
new file mode 100644
index 0000000..128edd5
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/messages.properties
@@ -0,0 +1,2 @@
+# Error messages for the LessThan Checker
+from.gt.to=While attempting to validate a subsequence type, the LessThan Checker could not prove that %s is less than or equal to %s.%nfound   : %s%nrequired: an integer <= %s (@LessThan("%s + 1"))
diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java
new file mode 100644
index 0000000..c0d7b69
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java
@@ -0,0 +1,446 @@
+package org.checkerframework.checker.index.lowerbound;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.UnaryTree;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import org.checkerframework.checker.index.BaseAnnotatedTypeFactoryForIndexChecker;
+import org.checkerframework.checker.index.IndexMethodIdentifier;
+import org.checkerframework.checker.index.inequality.LessThanAnnotatedTypeFactory;
+import org.checkerframework.checker.index.inequality.LessThanChecker;
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.IndexOrLow;
+import org.checkerframework.checker.index.qual.LengthOf;
+import org.checkerframework.checker.index.qual.LowerBoundBottom;
+import org.checkerframework.checker.index.qual.LowerBoundUnknown;
+import org.checkerframework.checker.index.qual.NegativeIndexFor;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.PolyIndex;
+import org.checkerframework.checker.index.qual.PolyLowerBound;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.checker.index.qual.SubstringIndexFor;
+import org.checkerframework.checker.index.searchindex.SearchIndexAnnotatedTypeFactory;
+import org.checkerframework.checker.index.searchindex.SearchIndexChecker;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.common.value.ValueCheckerUtils;
+import org.checkerframework.common.value.qual.BottomVal;
+import org.checkerframework.common.value.util.Range;
+import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Implements the introduction rules for the Lower Bound Checker.
+ *
+ * <pre>
+ *  The type hierarchy is:
+ *
+ *  Top = lbu ("Lower Bound Unknown")
+ *   |
+ *  gte-1 ("Greater than or equal to -1")
+ *   |
+ *  nn  ("NonNegative")
+ *   |
+ *  pos ("Positive")
+ *  </pre>
+ *
+ * In general, check whether the constant Value Checker can determine the value of a variable; if it
+ * can, use that; if not, use more specific rules based on expression type. This class implements
+ * the following type rules:
+ *
+ * <ul>
+ *   <li>1. If the value checker type for any expression is &ge; 1, refine that expression's type to
+ *       positive.
+ *   <li>2. If the value checker type for any expression is &ge; 0 and case 1 did not apply, then
+ *       refine that expression's type to non-negative.
+ *   <li>3. If the value checker type for any expression is &ge; -1 and cases 1 and 2 did not apply,
+ *       then refine that expression's type to GTEN1.
+ *   <li>4. A unary prefix decrement shifts the type "down" in the hierarchy (i.e. {@code --i} when
+ *       {@code i} is non-negative implies that {@code i} will be GTEN1 afterwards). Should this be
+ *       3 rules?
+ *   <li>5. A unary prefix increment shifts the type "up" in the hierarchy (i.e. {@code ++i} when
+ *       {@code i} is non-negative implies that {@code i} will be positive afterwards). Should this
+ *       be 3 rules?
+ *   <li>6. Unary negation on a NegativeIndexFor from the SearchIndex type system results in a
+ *       non-negative.
+ *   <li>7. The result of a call to Math.max is the GLB of its arguments.
+ *   <li>8. If an array has a MinLen type &ge; 1 and its length is accessed, the length expression
+ *       is positive.
+ * </ul>
+ */
+public class LowerBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactoryForIndexChecker {
+
+  /** The canonical @{@link GTENegativeOne} annotation. */
+  public final AnnotationMirror GTEN1 = AnnotationBuilder.fromClass(elements, GTENegativeOne.class);
+  /** The canonical @{@link NonNegative} annotation. */
+  public final AnnotationMirror NN = AnnotationBuilder.fromClass(elements, NonNegative.class);
+  /** The canonical @{@link Positive} annotation. */
+  public final AnnotationMirror POS = AnnotationBuilder.fromClass(elements, Positive.class);
+  /** The bottom annotation. */
+  public final AnnotationMirror BOTTOM =
+      AnnotationBuilder.fromClass(elements, LowerBoundBottom.class);
+  /** The canonical @{@link LowerBoundUnknown} annotation. */
+  public final AnnotationMirror UNKNOWN =
+      AnnotationBuilder.fromClass(elements, LowerBoundUnknown.class);
+  /** The canonical @{@link PolyLowerBound} annotation. */
+  public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyLowerBound.class);
+
+  /** Predicates about method calls. */
+  private final IndexMethodIdentifier imf;
+
+  /**
+   * Create a new LowerBoundAnnotatedTypeFactory.
+   *
+   * @param checker the type-checker
+   */
+  public LowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    // Any annotations that are aliased to @NonNegative, @Positive, or @GTENegativeOne must also be
+    // aliased in the constructor of ValueAnnotatedTypeFactory to the appropriate @IntRangeFrom*
+    // annotation.
+    addAliasedTypeAnnotation(IndexFor.class, NN);
+    addAliasedTypeAnnotation(IndexOrLow.class, GTEN1);
+    addAliasedTypeAnnotation(IndexOrHigh.class, NN);
+    addAliasedTypeAnnotation(LengthOf.class, NN);
+    addAliasedTypeAnnotation(PolyIndex.class, POLY);
+    addAliasedTypeAnnotation(SubstringIndexFor.class, GTEN1);
+
+    imf = new IndexMethodIdentifier(this);
+
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined.
+    return new LinkedHashSet<>(
+        Arrays.asList(
+            Positive.class,
+            NonNegative.class,
+            GTENegativeOne.class,
+            LowerBoundUnknown.class,
+            PolyLowerBound.class,
+            LowerBoundBottom.class));
+  }
+
+  /**
+   * Takes a value type (only interesting if it's an IntVal), and converts it to a lower bound type.
+   * If the new lower bound type is more specific than type, convert type to that type.
+   *
+   * @param valueType the Value Checker type
+   * @param type the current lower bound type of the expression being evaluated
+   */
+  private void addLowerBoundTypeFromValueType(
+      AnnotatedTypeMirror valueType, AnnotatedTypeMirror type) {
+    AnnotationMirror anm = getLowerBoundAnnotationFromValueType(valueType);
+    if (!type.isAnnotatedInHierarchy(UNKNOWN)) {
+      if (!areSameByClass(anm, LowerBoundUnknown.class)) {
+        type.addAnnotation(anm);
+      }
+      return;
+    }
+    if (qualHierarchy.isSubtype(anm, type.getAnnotationInHierarchy(UNKNOWN))) {
+      type.replaceAnnotation(anm);
+    }
+  }
+
+  /** Handles cases 1, 2, and 3. */
+  @Override
+  public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) {
+    super.addComputedTypeAnnotations(element, type);
+    if (element != null) {
+      AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(element);
+      addLowerBoundTypeFromValueType(valueType, type);
+    }
+  }
+
+  /** Handles cases 1, 2, and 3. */
+  @Override
+  public void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) {
+    super.addComputedTypeAnnotations(tree, type, iUseFlow);
+    // If dataflow shouldn't be used to compute this type, then do not use the result from
+    // the Value Checker, because dataflow is used to compute that type.  (Without this,
+    // "int i = 1; --i;" fails.)
+    if (tree != null
+        && TreeUtils.isExpressionTree(tree)
+        && (iUseFlow || tree instanceof LiteralTree)) {
+      AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(tree);
+      addLowerBoundTypeFromValueType(valueType, type);
+    }
+  }
+
+  /** Returns the Value Checker's annotated type factory. */
+  public ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() {
+    return getTypeFactoryOfSubchecker(ValueChecker.class);
+  }
+
+  /** Returns the SearchIndexFor Checker's annotated type factory. */
+  public SearchIndexAnnotatedTypeFactory getSearchIndexAnnotatedTypeFactory() {
+    return getTypeFactoryOfSubchecker(SearchIndexChecker.class);
+  }
+
+  /** Returns the LessThan Checker's annotated type factory. */
+  public LessThanAnnotatedTypeFactory getLessThanAnnotatedTypeFactory() {
+    return getTypeFactoryOfSubchecker(LessThanChecker.class);
+  }
+
+  /** Returns the type in the lower bound hierarchy that a Value Checker type corresponds to. */
+  private AnnotationMirror getLowerBoundAnnotationFromValueType(AnnotatedTypeMirror valueType) {
+    Range possibleValues =
+        ValueCheckerUtils.getPossibleValues(valueType, getValueAnnotatedTypeFactory());
+    // possibleValues is null if the Value Checker does not have any estimate.
+    if (possibleValues == null) {
+      // possibleValues is null if there is no IntVal annotation on the type - such as
+      // when there is a BottomVal annotation. In that case, give this the LBC's bottom type.
+      if (containsSameByClass(valueType.getAnnotations(), BottomVal.class)) {
+        return BOTTOM;
+      }
+      return UNKNOWN;
+    }
+    // The annotation of the whole list is the min of the list.
+    long lvalMin = possibleValues.from;
+    // Turn it into an integer.
+    int valMin = (int) Math.max(Math.min(Integer.MAX_VALUE, lvalMin), Integer.MIN_VALUE);
+    return anmFromVal(valMin);
+  }
+
+  /** Determine the annotation that should be associated with a literal. */
+  AnnotationMirror anmFromVal(long val) {
+    if (val >= 1) {
+      return POS;
+    } else if (val >= 0) {
+      return NN;
+    } else if (val >= -1) {
+      return GTEN1;
+    } else {
+      return UNKNOWN;
+    }
+  }
+
+  @Override
+  public TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(new LowerBoundTreeAnnotator(this), super.createTreeAnnotator());
+  }
+
+  private class LowerBoundTreeAnnotator extends TreeAnnotator {
+    public LowerBoundTreeAnnotator(AnnotatedTypeFactory annotatedTypeFactory) {
+      super(annotatedTypeFactory);
+    }
+
+    /**
+     * Sets typeDst to the immediate supertype of typeSrc, unless typeSrc is already Positive.
+     * Implements the following transitions:
+     *
+     * <pre>
+     *      pos &rarr; pos
+     *      nn &rarr; pos
+     *      gte-1 &rarr; nn
+     *      lbu &rarr; lbu
+     *  </pre>
+     */
+    private void promoteType(AnnotatedTypeMirror typeSrc, AnnotatedTypeMirror typeDst) {
+      if (typeSrc.hasAnnotation(POS)) {
+        typeDst.replaceAnnotation(POS);
+      } else if (typeSrc.hasAnnotation(NN)) {
+        typeDst.replaceAnnotation(POS);
+      } else if (typeSrc.hasAnnotation(GTEN1)) {
+        typeDst.replaceAnnotation(NN);
+      } else { // Only unknown is left.
+        typeDst.replaceAnnotation(UNKNOWN);
+      }
+    }
+
+    /**
+     * Sets typeDst to the immediate subtype of typeSrc, unless typeSrc is already
+     * LowerBoundUnknown. Implements the following transitions:
+     *
+     * <pre>
+     *       pos &rarr; nn
+     *       nn &rarr; gte-1
+     *       gte-1, lbu &rarr; lbu
+     *  </pre>
+     */
+    private void demoteType(AnnotatedTypeMirror typeSrc, AnnotatedTypeMirror typeDst) {
+      if (typeSrc.hasAnnotation(POS)) {
+        typeDst.replaceAnnotation(NN);
+      } else if (typeSrc.hasAnnotation(NN)) {
+        typeDst.replaceAnnotation(GTEN1);
+      } else { // GTEN1 and UNKNOWN both become UNKNOWN.
+        typeDst.replaceAnnotation(UNKNOWN);
+      }
+    }
+
+    /** Call increment and decrement helper functions. Handles cases 4, 5 and 6. */
+    @Override
+    public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror typeDst) {
+      AnnotatedTypeMirror typeSrc = getAnnotatedType(tree.getExpression());
+      switch (tree.getKind()) {
+        case PREFIX_INCREMENT:
+          promoteType(typeSrc, typeDst);
+          break;
+        case PREFIX_DECREMENT:
+          demoteType(typeSrc, typeDst);
+          break;
+        case POSTFIX_INCREMENT:
+        case POSTFIX_DECREMENT:
+          // Do nothing. The CF should take care of these itself.
+          break;
+        case BITWISE_COMPLEMENT:
+          handleBitWiseComplement(
+              getSearchIndexAnnotatedTypeFactory().getAnnotatedType(tree.getExpression()), typeDst);
+          break;
+        default:
+          break;
+      }
+      return super.visitUnary(tree, typeDst);
+    }
+
+    /**
+     * Bitwise complement converts between {@code @NegativeIndexFor} and {@code @IndexOrHigh}. This
+     * handles the lowerbound part of that type, so the result is converted to {@code @NonNegative}.
+     *
+     * @param searchIndexType the type of an expression in a bitwise complement. For instance, in
+     *     {@code ~x}, this is the type of {@code x}.
+     * @param typeDst the type of the entire bitwise complement expression. It is modified by this
+     *     method.
+     */
+    private void handleBitWiseComplement(
+        AnnotatedTypeMirror searchIndexType, AnnotatedTypeMirror typeDst) {
+      if (containsSameByClass(searchIndexType.getAnnotations(), NegativeIndexFor.class)) {
+        typeDst.addAnnotation(NN);
+      }
+    }
+
+    /** Special handling for Math.max. The return is the GLB of the arguments. Case 7. */
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
+      if (imf.isMathMax(tree)) {
+        ExpressionTree left = tree.getArguments().get(0);
+        ExpressionTree right = tree.getArguments().get(1);
+        type.replaceAnnotation(
+            qualHierarchy.greatestLowerBound(
+                getAnnotatedType(left).getAnnotationInHierarchy(POS),
+                getAnnotatedType(right).getAnnotationInHierarchy(POS)));
+      }
+      return super.visitMethodInvocation(tree, type);
+    }
+
+    /**
+     * For dealing with array length expressions. Looks for array length accesses specifically, then
+     * dispatches to the MinLen checker to determine the length of the relevant array. If it's
+     * found, use it to give the expression a type. Case 8.
+     */
+    @Override
+    public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) {
+      Integer minLen = getMinLenFromMemberSelectTree(tree);
+      if (minLen != null) {
+        type.replaceAnnotation(anmFromVal(minLen));
+      }
+      return super.visitMemberSelect(tree, type);
+    }
+
+    /**
+     * Does not dispatch to binary operator helper methods. The Lower Bound Checker handles binary
+     * operations via its transfer function.
+     */
+    @Override
+    public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) {
+      type.addAnnotation(UNKNOWN);
+      return super.visitBinary(tree, type);
+    }
+  }
+
+  /**
+   * Looks up the minlen of a member select tree. Returns null if the tree doesn't represent an
+   * array's length field.
+   */
+  Integer getMinLenFromMemberSelectTree(MemberSelectTree tree) {
+    if (TreeUtils.isArrayLengthAccess(tree)) {
+      return ValueCheckerUtils.getMinLenFromTree(tree, getValueAnnotatedTypeFactory());
+    }
+    return null;
+  }
+
+  /**
+   * Looks up the minlen of a method invocation tree. Returns null if the tree doesn't represent an
+   * string length method.
+   */
+  Integer getMinLenFromMethodInvocationTree(MethodInvocationTree tree) {
+    if (imf.isLengthOfMethodInvocation(tree)) {
+      return ValueCheckerUtils.getMinLenFromTree(tree, getValueAnnotatedTypeFactory());
+    }
+    return null;
+  }
+
+  /**
+   * Given a multiplication, return its type if the LBC special-cases it, or null otherwise.
+   *
+   * <p>The LBC special-cases {@code Math.random() * array.length} and {@code Random.nextDouble() *
+   * array.length}.
+   *
+   * @param node a multiplication node that may need special casing
+   * @return an AnnotationMirror representing the result if the special case is valid, or null if
+   *     not
+   */
+  AnnotationMirror checkForMathRandomSpecialCase(NumericalMultiplicationNode node) {
+    AnnotationMirror forwardRes =
+        checkForMathRandomSpecialCase(
+            node.getLeftOperand().getTree(), node.getRightOperand().getTree());
+    if (forwardRes != null) {
+      return forwardRes;
+    }
+    AnnotationMirror backwardsRes =
+        checkForMathRandomSpecialCase(
+            node.getRightOperand().getTree(), node.getLeftOperand().getTree());
+    if (backwardsRes != null) {
+      return backwardsRes;
+    }
+    return null;
+  }
+
+  /**
+   * Return true if randTree is a call to Math.random() or Random.nextDouble(), and arrLenTree is
+   * someArray.length.
+   */
+  private AnnotationMirror checkForMathRandomSpecialCase(Tree randTree, Tree arrLenTree) {
+    if (randTree.getKind() == Tree.Kind.METHOD_INVOCATION
+        && TreeUtils.isArrayLengthAccess(arrLenTree)) {
+      MethodInvocationTree miTree = (MethodInvocationTree) randTree;
+
+      if (imf.isMathRandom(miTree, processingEnv)) {
+        // This is Math.random() * array.length, which must be NonNegative
+        return NN;
+      }
+
+      if (imf.isRandomNextDouble(miTree, processingEnv)) {
+        // This is Random.nextDouble() * array.length, which must be NonNegative
+        return NN;
+      }
+    }
+    return null;
+  }
+
+  /** Checks if the expression is non-negative, i.e. it has Positive on NonNegative annotation. */
+  public boolean isNonNegative(Tree tree) {
+    // TODO: consolidate with the isNonNegative method in LowerBoundTransfer
+    AnnotatedTypeMirror treeType = getAnnotatedType(tree);
+    return treeType.hasAnnotation(NonNegative.class) || treeType.hasAnnotation(Positive.class);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundChecker.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundChecker.java
new file mode 100644
index 0000000..1617099
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundChecker.java
@@ -0,0 +1,69 @@
+package org.checkerframework.checker.index.lowerbound;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import org.checkerframework.checker.index.inequality.LessThanChecker;
+import org.checkerframework.checker.index.searchindex.SearchIndexChecker;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+import org.checkerframework.framework.source.SuppressWarningsPrefix;
+
+/**
+ * A type-checker for preventing fixed-length sequences such as arrays or strings from being
+ * accessed with values that are too low. Normally bundled as part of the Index Checker.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@SuppressWarningsPrefix({"index", "lowerbound"})
+@RelevantJavaTypes({
+  Byte.class,
+  Short.class,
+  Integer.class,
+  Long.class,
+  Character.class,
+  byte.class,
+  short.class,
+  int.class,
+  long.class,
+  char.class,
+})
+public class LowerBoundChecker extends BaseTypeChecker {
+
+  /**
+   * These collection classes have some subtypes whose length can change and some subtypes whose
+   * length cannot change. Lower bound checker warnings are skipped at uses of them.
+   */
+  private HashSet<String> collectionBaseTypeNames;
+
+  /**
+   * A type-checker for preventing fixed-length sequences such as arrays or strings from being
+   * accessed with values that are too low. Normally bundled as part of the Index Checker.
+   */
+  public LowerBoundChecker() {
+    Class<?>[] collectionBaseClasses = {java.util.List.class, java.util.AbstractList.class};
+    collectionBaseTypeNames = new HashSet<>(collectionBaseClasses.length);
+    for (Class<?> collectionBaseClass : collectionBaseClasses) {
+      collectionBaseTypeNames.add(collectionBaseClass.getName());
+    }
+  }
+
+  @Override
+  public boolean shouldSkipUses(@FullyQualifiedName String typeName) {
+    if (collectionBaseTypeNames.contains(typeName)) {
+      return true;
+    }
+    return super.shouldSkipUses(typeName);
+  }
+
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    LinkedHashSet<Class<? extends BaseTypeChecker>> checkers =
+        super.getImmediateSubcheckerClasses();
+    checkers.add(ValueChecker.class);
+    checkers.add(LessThanChecker.class);
+    checkers.add(SearchIndexChecker.class);
+    return checkers;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundTransfer.java
new file mode 100644
index 0000000..574199e
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundTransfer.java
@@ -0,0 +1,904 @@
+package org.checkerframework.checker.index.lowerbound;
+
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.checker.index.IndexAbstractTransfer;
+import org.checkerframework.checker.index.IndexRefinementInfo;
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.LowerBoundUnknown;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.checker.index.upperbound.OffsetEquation;
+import org.checkerframework.common.value.ValueCheckerUtils;
+import org.checkerframework.dataflow.analysis.RegularTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.node.BinaryOperationNode;
+import org.checkerframework.dataflow.cfg.node.BitwiseAndNode;
+import org.checkerframework.dataflow.cfg.node.IntegerDivisionNode;
+import org.checkerframework.dataflow.cfg.node.IntegerRemainderNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode;
+import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode;
+import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
+import org.checkerframework.dataflow.cfg.node.SignedRightShiftNode;
+import org.checkerframework.dataflow.cfg.node.UnsignedRightShiftNode;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Implements dataflow refinement rules based on tests: &lt;, &gt;, ==, and their derivatives.
+ *
+ * <p>Also implements the logic for binary operations: +, -, *, /, and %.
+ *
+ * <p>&gt;, &lt;, &ge;, &le;, ==, and != nodes are represented as combinations of &gt; and &ge;
+ * (e.g. == is &ge; in both directions in the then branch), and implement refinements based on these
+ * decompositions.
+ *
+ * <pre>
+ * Refinement/transfer rules for conditionals:
+ *
+ * There are two "primitives":
+ *
+ * x &gt; y, which implies things about x based on y's type:
+ *
+ * y has type:    implies x has type:
+ *  gte-1                nn
+ *  nn                   pos
+ *  pos                  pos
+ *
+ * and x &ge; y:
+ *
+ * y has type:    implies x has type:
+ *  gte-1                gte-1
+ *  nn                   nn
+ *  pos                  pos
+ *
+ * These two "building blocks" can be combined to make all
+ * other conditional expressions:
+ *
+ * EXPR             THEN          ELSE
+ * x &gt; y            x &gt; y         y &ge; x
+ * x &ge; y           x &ge; y        y &gt; x
+ * x &lt; y            y &gt; x         x &ge; y
+ * x &le; y           y &ge; x        x &gt; y
+ *
+ * Or, more formally:
+ *
+ * EXPR        THEN                                        ELSE
+ * x &gt; y       x_refined = GLB(x_orig, promote(y))         y_refined = GLB(y_orig, x)
+ * x &ge; y      x_refined = GLB(x_orig, y)                  y_refined = GLB(y_orig, promote(x))
+ * x &lt; y       y_refined = GLB(y_orig, promote(x))         x_refined = GLB(x_orig, y)
+ * x &le; y      y_refined = GLB(y_orig, x)                  x_refined = GLB(x_orig, promote(y))
+ *
+ * where GLB is the greatest lower bound and promote is the increment
+ * function on types (or, equivalently, the function specified by the "x
+ * &gt; y" information above).
+ *
+ * There's also ==, which is a special case. Only the THEN
+ * branch is refined:
+ *
+ * EXPR             THEN                   ELSE
+ * x == y           x &ge; y &amp;&amp; y &ge; x       nothing known
+ *
+ * or, more formally:
+ *
+ * EXPR            THEN                                    ELSE
+ * x == y          x_refined = GLB(x_orig, y_orig)         nothing known
+ *                y_refined = GLB(x_orig, y_orig)
+ *
+ * finally, not equal:
+ *
+ * EXPR             THEN                   ELSE
+ * x != y           nothing known          x &ge; y &amp;&amp; y &ge; x
+ *
+ * more formally:
+ *
+ * EXPR            THEN               ELSE
+ * x != y          nothing known      x_refined = GLB(x_orig, y_orig)
+ *                                   y_refined = GLB(x_orig, y_orig)
+ *
+ * </pre>
+ *
+ * Dividing these rules up by cases, this class implements:
+ *
+ * <ul>
+ *   <li>1. The rule described above for &gt;
+ *   <li>2. The rule described above for &ge;
+ *   <li>3. The rule described above for &lt;
+ *   <li>4. The rule described above for &le;
+ *   <li>5. The rule described above for ==
+ *   <li>6. The rule described above for !=
+ *   <li>7. A special refinement for != when the value being compared to is a compile-time constant
+ *       with a value exactly equal to -1 or 0 (i.e. {@code x != -1} and x is GTEN1 implies x is
+ *       non-negative). Maybe two rules?
+ *   <li>8. When a compile-time constant -2 is added to a positive, the result is GTEN1
+ *   <li>9. When a compile-time constant 2 is added to a GTEN1, the result is positive
+ *   <li>10. When a positive is added to a positive, the result is positive
+ *   <li>11. When a non-negative is added to any other type, the result is that other type
+ *   <li>12. When a GTEN1 is added to a positive, the result is non-negative
+ *   <li>13. When the left side of a subtraction expression is &gt; the right side according to the
+ *       LessThanChecker, the result of the subtraction expression is positive
+ *   <li>14. When the left side of a subtraction expression is &ge; the right side according to the
+ *       LessThanChecker, the result of the subtraction expression is non-negative
+ *   <li>15. special handling for when the left side is the length of an array or String that's
+ *       stored as a field, and the right side is a compile time constant. Do we need this?
+ *   <li>16. Multiplying any value by a compile time constant of 1 preserves its type
+ *   <li>17. Multiplying two positives produces a positive
+ *   <li>18. Multiplying a positive and a non-negative produces a non-negative
+ *   <li>19. Multiplying two non-negatives produces a non-negative
+ *   <li>20. When the result of Math.random is multiplied by an array length, the result is
+ *       NonNegative.
+ *   <li>21. literal 0 divided by anything is non-negative
+ *   <li>22. anything divided by literal zero is bottom
+ *   <li>23. literal 1 divided by a positive or non-negative is non-negative
+ *   <li>24. literal 1 divided by anything else is GTEN1
+ *   <li>25. anything divided by literal 1 is itself
+ *   <li>26. a positive or non-negative divided by a positive or non-negative is non-negative
+ *   <li>27. anything modded by literal 1 or -1 is non-negative
+ *   <li>28. a positive or non-negative modded by anything is non-negative
+ *   <li>29. a GTEN1 modded by anything is GTEN1
+ *   <li>30. anything right-shifted by a non-negative is non-negative
+ *   <li>31. anything bitwise-anded by a non-negative is non-negative
+ *   <li>32. If a and b are non-negative and {@code a <= b} and {@code a != b}, then b is pos.
+ *   <li>33. A char is always non-negative
+ * </ul>
+ */
+public class LowerBoundTransfer extends IndexAbstractTransfer {
+
+  /** The canonical {@link GTENegativeOne} annotation. */
+  public final AnnotationMirror GTEN1;
+  /** The canonical {@link NonNegative} annotation. */
+  public final AnnotationMirror NN;
+  /** The canonical {@link Positive} annotation. */
+  public final AnnotationMirror POS;
+  /** The canonical {@link LowerBoundUnknown} annotation. */
+  public final AnnotationMirror UNKNOWN;
+
+  // The ATF (Annotated Type Factory).
+  private LowerBoundAnnotatedTypeFactory aTypeFactory;
+
+  public LowerBoundTransfer(CFAnalysis analysis) {
+    super(analysis);
+    aTypeFactory = (LowerBoundAnnotatedTypeFactory) analysis.getTypeFactory();
+    // Initialize qualifiers.
+    GTEN1 = aTypeFactory.GTEN1;
+    NN = aTypeFactory.NN;
+    POS = aTypeFactory.POS;
+    UNKNOWN = aTypeFactory.UNKNOWN;
+  }
+
+  /**
+   * Refines GTEN1 to NN if it is not equal to -1, and NN to Pos if it is not equal to 0. Implements
+   * case 7.
+   *
+   * @param mLiteral a potential literal
+   * @param otherNode the node on the other side of the ==/!=
+   * @param otherAnno the annotation of the other side of the ==/!=
+   */
+  private void notEqualToValue(
+      Node mLiteral, Node otherNode, AnnotationMirror otherAnno, CFStore store) {
+
+    Long integerLiteral =
+        ValueCheckerUtils.getExactValue(
+            mLiteral.getTree(), aTypeFactory.getValueAnnotatedTypeFactory());
+
+    if (integerLiteral == null) {
+      return;
+    }
+    long intLiteral = integerLiteral.longValue();
+
+    if (intLiteral == 0) {
+      if (aTypeFactory.areSameByClass(otherAnno, NonNegative.class)) {
+        List<Node> internals = splitAssignments(otherNode);
+        for (Node internal : internals) {
+          JavaExpression je = JavaExpression.fromNode(internal);
+          store.insertValue(je, POS);
+        }
+      }
+    } else if (intLiteral == -1) {
+      if (aTypeFactory.areSameByClass(otherAnno, GTENegativeOne.class)) {
+        List<Node> internals = splitAssignments(otherNode);
+        for (Node internal : internals) {
+          JavaExpression je = JavaExpression.fromNode(internal);
+          store.insertValue(je, NN);
+        }
+      }
+    }
+  }
+
+  /**
+   * Implements the transfer rules for both equal nodes and not-equals nodes (i.e. cases 5, 6, 32).
+   */
+  @Override
+  protected TransferResult<CFValue, CFStore> strengthenAnnotationOfEqualTo(
+      TransferResult<CFValue, CFStore> result,
+      Node firstNode,
+      Node secondNode,
+      CFValue firstValue,
+      CFValue secondValue,
+      boolean notEqualTo) {
+    result =
+        super.strengthenAnnotationOfEqualTo(
+            result, firstNode, secondNode, firstValue, secondValue, notEqualTo);
+
+    IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, secondNode, firstNode);
+    if (rfi.leftAnno == null || rfi.rightAnno == null) {
+      return result;
+    }
+
+    // There is also special processing to look for literals on one side of the equals and a GTEN1
+    // or NN on the other, so that those types can be promoted in the branch where their values are
+    // not equal to certain literals.
+    CFStore notEqualsStore = notEqualTo ? rfi.thenStore : rfi.elseStore;
+    notEqualToValue(rfi.left, rfi.right, rfi.rightAnno, notEqualsStore);
+    notEqualToValue(rfi.right, rfi.left, rfi.leftAnno, notEqualsStore);
+
+    notEqualsLessThan(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, notEqualsStore);
+    notEqualsLessThan(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, notEqualsStore);
+
+    return rfi.newResult;
+  }
+
+  /** Implements case 32. */
+  private void notEqualsLessThan(
+      Node leftNode,
+      AnnotationMirror leftAnno,
+      Node otherNode,
+      AnnotationMirror otherAnno,
+      CFStore store) {
+    if (!isNonNegative(leftAnno) || !isNonNegative(otherAnno)) {
+      return;
+    }
+    JavaExpression otherJe = JavaExpression.fromNode(otherNode);
+    if (aTypeFactory
+        .getLessThanAnnotatedTypeFactory()
+        .isLessThanOrEqual(leftNode.getTree(), otherJe.toString())) {
+      store.insertValue(otherJe, POS);
+    }
+  }
+
+  /**
+   * The implementation of the algorithm for refining a &gt; test. Changes the type of left (the
+   * greater one) to one closer to bottom than the type of right. Can't call the promote function
+   * from the ATF directly because a new expression isn't introduced here - the modifications have
+   * to be made to an existing one.
+   *
+   * <p>This implements parts of cases 1, 2, 3, and 4 using the decomposition strategy described in
+   * the Javadoc of this class.
+   */
+  @Override
+  protected void refineGT(
+      Node left,
+      AnnotationMirror leftAnno,
+      Node right,
+      AnnotationMirror rightAnno,
+      CFStore store,
+      TransferInput<CFValue, CFStore> in) {
+
+    if (rightAnno == null || leftAnno == null) {
+      return;
+    }
+
+    JavaExpression leftJe = JavaExpression.fromNode(left);
+
+    if (AnnotationUtils.areSame(rightAnno, GTEN1)) {
+      store.insertValue(leftJe, NN);
+      return;
+    }
+    if (AnnotationUtils.areSame(rightAnno, NN)) {
+      store.insertValue(leftJe, POS);
+      return;
+    }
+    if (AnnotationUtils.areSame(rightAnno, POS)) {
+      store.insertValue(leftJe, POS);
+      return;
+    }
+  }
+
+  /**
+   * Refines left to exactly the level of right, since in the worst case they're equal. Modifies an
+   * existing type in the store, but has to be careful not to overwrite a more precise existing
+   * type.
+   *
+   * <p>This implements parts of cases 1, 2, 3, and 4 using the decomposition strategy described in
+   * this class's Javadoc.
+   */
+  @Override
+  protected void refineGTE(
+      Node left,
+      AnnotationMirror leftAnno,
+      Node right,
+      AnnotationMirror rightAnno,
+      CFStore store,
+      TransferInput<CFValue, CFStore> in) {
+
+    if (rightAnno == null || leftAnno == null) {
+      return;
+    }
+
+    JavaExpression leftJe = JavaExpression.fromNode(left);
+
+    AnnotationMirror newLBType =
+        aTypeFactory.getQualifierHierarchy().greatestLowerBound(rightAnno, leftAnno);
+
+    store.insertValue(leftJe, newLBType);
+  }
+
+  /**
+   * Returns an annotation mirror representing the result of subtracting one from {@code oldAnm}.
+   */
+  private AnnotationMirror anmAfterSubtractingOne(AnnotationMirror oldAnm) {
+    if (isPositive(oldAnm)) {
+      return NN;
+    } else if (isNonNegative(oldAnm)) {
+      return GTEN1;
+    } else {
+      return UNKNOWN;
+    }
+  }
+
+  /** Returns an annotation mirror representing the result of adding one to {@code oldAnm}. */
+  private AnnotationMirror anmAfterAddingOne(AnnotationMirror oldAnm) {
+    if (isNonNegative(oldAnm)) {
+      return POS;
+    } else if (isGTEN1(oldAnm)) {
+      return NN;
+    } else {
+      return UNKNOWN;
+    }
+  }
+
+  /**
+   * Helper method for getAnnotationForPlus. Handles addition of constants (cases 8 and 9).
+   *
+   * @param val the integer value of the constant
+   * @param nonLiteralType the type of the side of the expression that isn't a constant
+   */
+  private AnnotationMirror getAnnotationForLiteralPlus(int val, AnnotationMirror nonLiteralType) {
+    if (val == -2) {
+      if (isPositive(nonLiteralType)) {
+        return GTEN1;
+      }
+    } else if (val == -1) {
+      return anmAfterSubtractingOne(nonLiteralType);
+    } else if (val == 0) {
+      return nonLiteralType;
+    } else if (val == 1) {
+      return anmAfterAddingOne(nonLiteralType);
+    } else if (val >= 2) {
+      if (isGTEN1(nonLiteralType)) {
+        // 2 + a positive, or a non-negative, or a non-negative-1 is a positive
+        return POS;
+      }
+    }
+    return UNKNOWN;
+  }
+
+  /**
+   * getAnnotationForPlus handles the following cases (cases 10-12 above):
+   *
+   * <pre>
+   *      8. lit -2 + pos &rarr; gte-1
+   *      lit -1 + * &rarr; call demote
+   *      lit 0 + * &rarr; *
+   *      lit 1 + * &rarr; call promote
+   *      9. lit &ge; 2 + {gte-1, nn, or pos} &rarr; pos
+   *      let all other lits, including sets, fall through:
+   *      10. pos + pos &rarr; pos
+   *      11. nn + * &rarr; *
+   *      12. pos + gte-1 &rarr; nn
+   *      * + * &rarr; lbu
+   *  </pre>
+   */
+  private AnnotationMirror getAnnotationForPlus(
+      BinaryOperationNode binaryOpNode, TransferInput<CFValue, CFStore> p) {
+
+    Node leftExprNode = binaryOpNode.getLeftOperand();
+    Node rightExprNode = binaryOpNode.getRightOperand();
+
+    AnnotationMirror leftAnno = getLowerBoundAnnotation(leftExprNode, p);
+
+    // Check if the right side's value is known at compile time.
+    Long valRight =
+        ValueCheckerUtils.getExactValue(
+            rightExprNode.getTree(), aTypeFactory.getValueAnnotatedTypeFactory());
+    if (valRight != null) {
+      return getAnnotationForLiteralPlus(valRight.intValue(), leftAnno);
+    }
+
+    AnnotationMirror rightAnno = getLowerBoundAnnotation(rightExprNode, p);
+
+    // Check if the left side's value is known at compile time.
+    Long valLeft =
+        ValueCheckerUtils.getExactValue(
+            leftExprNode.getTree(), aTypeFactory.getValueAnnotatedTypeFactory());
+    if (valLeft != null) {
+      return getAnnotationForLiteralPlus(valLeft.intValue(), rightAnno);
+    }
+
+    /* This section is handling the generic cases:
+     *      pos + pos -> pos
+     *      nn + * -> *
+     *      pos + gte-1 -> nn
+     */
+    if (aTypeFactory.areSameByClass(leftAnno, Positive.class)
+        && aTypeFactory.areSameByClass(rightAnno, Positive.class)) {
+      return POS;
+    }
+
+    if (aTypeFactory.areSameByClass(leftAnno, NonNegative.class)) {
+      return rightAnno;
+    }
+
+    if (aTypeFactory.areSameByClass(rightAnno, NonNegative.class)) {
+      return leftAnno;
+    }
+
+    if ((isPositive(leftAnno) && isGTEN1(rightAnno))
+        || (isGTEN1(leftAnno) && isPositive(rightAnno))) {
+      return NN;
+    }
+    return UNKNOWN;
+  }
+
+  /**
+   * getAnnotationForMinus handles the following cases:
+   *
+   * <pre>
+   *      * - lit &rarr; call plus(*, -1 * the value of the lit)
+   *      * - * &rarr; lbu
+   *      13. if the LessThan type checker can establish that the left side of the expression is &gt; the right side,
+   *      returns POS.
+   *      14. if the LessThan type checker can establish that the left side of the expression is &ge; the right side,
+   *      returns NN.
+   *      15. special handling for when the left side is the length of an array or String that's stored as a field,
+   *      and the right side is a compile time constant. Do we need this?
+   *  </pre>
+   */
+  private AnnotationMirror getAnnotationForMinus(
+      BinaryOperationNode minusNode, TransferInput<CFValue, CFStore> p) {
+
+    // Check if the right side's value is known at compile time.
+    Long valRight =
+        ValueCheckerUtils.getExactValue(
+            minusNode.getRightOperand().getTree(), aTypeFactory.getValueAnnotatedTypeFactory());
+    if (valRight != null) {
+      AnnotationMirror leftAnno = getLowerBoundAnnotation(minusNode.getLeftOperand(), p);
+      // Instead of a separate method for subtraction, add the negative of a constant.
+      AnnotationMirror result = getAnnotationForLiteralPlus(-1 * valRight.intValue(), leftAnno);
+
+      Tree leftExpr = minusNode.getLeftOperand().getTree();
+      Integer minLen = null;
+      // Check if the left side is a field access of an array's length, or invocation of
+      // String.length. If so, try to look up the MinLen of the array, and potentially keep this
+      // either NN or POS instead of GTEN1 or LBU.
+      if (leftExpr.getKind() == Tree.Kind.MEMBER_SELECT) {
+        MemberSelectTree mstree = (MemberSelectTree) leftExpr;
+        minLen = aTypeFactory.getMinLenFromMemberSelectTree(mstree);
+      } else if (leftExpr.getKind() == Tree.Kind.METHOD_INVOCATION) {
+        MethodInvocationTree mitree = (MethodInvocationTree) leftExpr;
+        minLen = aTypeFactory.getMinLenFromMethodInvocationTree(mitree);
+      }
+
+      if (minLen != null) {
+        result = aTypeFactory.anmFromVal(minLen - valRight);
+      }
+      return result;
+    }
+
+    OffsetEquation leftExpression =
+        OffsetEquation.createOffsetFromNode(minusNode.getLeftOperand(), aTypeFactory, '+');
+    if (leftExpression != null) {
+      if (aTypeFactory
+          .getLessThanAnnotatedTypeFactory()
+          .isLessThan(minusNode.getRightOperand().getTree(), leftExpression.toString())) {
+        return POS;
+      }
+
+      if (aTypeFactory
+          .getLessThanAnnotatedTypeFactory()
+          .isLessThanOrEqual(minusNode.getRightOperand().getTree(), leftExpression.toString())) {
+        return NN;
+      }
+    }
+
+    // The checker can't reason about arbitrary (i.e. non-literal)
+    // things that are being subtracted, so it gives up.
+    return UNKNOWN;
+  }
+
+  /**
+   * Helper function for getAnnotationForMultiply. Handles compile-time known constants.
+   *
+   * @param val the integer value of the constant
+   * @param nonLiteralType the type of the side of the expression that isn't a constant
+   */
+  private AnnotationMirror getAnnotationForLiteralMultiply(
+      int val, AnnotationMirror nonLiteralType) {
+    if (val == 0) {
+      return NN;
+    } else if (val == 1) {
+      return nonLiteralType;
+    } else if (val > 1) {
+      if (isNonNegative(nonLiteralType)) {
+        return nonLiteralType;
+      }
+    }
+    return UNKNOWN;
+  }
+
+  /**
+   * getAnnotationForMultiply handles the following cases:
+   *
+   * <pre>
+   *        * * lit 0 &rarr; nn (=0)
+   *        16. * * lit 1 &rarr; *
+   *        17. pos * pos &rarr; pos
+   *        18. pos * nn &rarr; nn
+   *        19. nn * nn &rarr; nn
+   *        * * * &rarr; lbu
+   *  </pre>
+   *
+   * Also handles a special case involving Math.random (case 20).
+   */
+  private AnnotationMirror getAnnotationForMultiply(
+      NumericalMultiplicationNode node, TransferInput<CFValue, CFStore> p) {
+
+    // Special handling for multiplying an array length by a Math.random().
+    AnnotationMirror randomSpecialCaseResult = aTypeFactory.checkForMathRandomSpecialCase(node);
+    if (randomSpecialCaseResult != null) {
+      return randomSpecialCaseResult;
+    }
+
+    AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p);
+
+    // Check if the right side's value is known at compile time.
+    Long valRight =
+        ValueCheckerUtils.getExactValue(
+            node.getRightOperand().getTree(), aTypeFactory.getValueAnnotatedTypeFactory());
+    if (valRight != null) {
+      return getAnnotationForLiteralMultiply(valRight.intValue(), leftAnno);
+    }
+
+    AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p);
+    // Check if the left side's value is known at compile time.
+    Long valLeft =
+        ValueCheckerUtils.getExactValue(
+            node.getLeftOperand().getTree(), aTypeFactory.getValueAnnotatedTypeFactory());
+    if (valLeft != null) {
+      return getAnnotationForLiteralMultiply(valLeft.intValue(), rightAnno);
+    }
+
+    /* This section handles generic annotations:
+     *   pos * pos -> pos
+     *   nn * pos -> nn (elided, since positives are also non-negative)
+     *   nn * nn -> nn
+     */
+    if (isPositive(leftAnno) && isPositive(rightAnno)) {
+      return POS;
+    }
+    if (isNonNegative(leftAnno) && isNonNegative(rightAnno)) {
+      return NN;
+    }
+    return UNKNOWN;
+  }
+
+  /** When the value on the left is known at compile time. */
+  private AnnotationMirror addAnnotationForLiteralDivideLeft(int val, AnnotationMirror rightAnno) {
+    if (val == 0) {
+      return NN;
+    } else if (val == 1) {
+      if (isNonNegative(rightAnno)) {
+        return NN;
+      } else {
+        // (1 / x) can't be outside the range [-1, 1] when x is an integer.
+        return GTEN1;
+      }
+    }
+    return UNKNOWN;
+  }
+
+  /** When the value on the right is known at compile time. */
+  private AnnotationMirror addAnnotationForLiteralDivideRight(int val, AnnotationMirror leftAnno) {
+    if (val == 0) {
+      // Reaching this indicates a divide by zero error. If the value is zero, then this is
+      // division by zero. Division by zero is treated as bottom so that users aren't warned
+      // about dead code that's dividing by zero. This code assumes that non-dead code won't
+      // include literal divide by zeros...
+      return aTypeFactory.BOTTOM;
+    } else if (val == 1) {
+      return leftAnno;
+    } else if (val >= 2) {
+      if (isNonNegative(leftAnno)) {
+        return NN;
+      }
+    }
+    return UNKNOWN;
+  }
+
+  /**
+   * getAnnotationForDivide handles the following cases (21-26).
+   *
+   * <pre>
+   *      lit 0 / * &rarr; nn (=0)
+   *      * / lit 0 &rarr; pos
+   *      lit 1 / {pos, nn} &rarr; nn
+   *      lit 1 / * &rarr; gten1
+   *      * / lit 1 &rarr; *
+   *      {pos, nn} / lit &gt;1 &rarr; nn
+   *      pos / {pos, nn} &rarr; nn (can round to zero)
+   *      * / {pos, nn} &rarr; *
+   *      * / * &rarr; lbu
+   *  </pre>
+   */
+  private AnnotationMirror getAnnotationForDivide(
+      IntegerDivisionNode node, TransferInput<CFValue, CFStore> p) {
+
+    AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p);
+
+    // Check if the right side's value is known at compile time.
+    Long valRight =
+        ValueCheckerUtils.getExactValue(
+            node.getRightOperand().getTree(), aTypeFactory.getValueAnnotatedTypeFactory());
+    if (valRight != null) {
+      return addAnnotationForLiteralDivideRight(valRight.intValue(), leftAnno);
+    }
+
+    AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p);
+
+    // Check if the left side's value is known at compile time.
+    Long valLeft =
+        ValueCheckerUtils.getExactValue(
+            node.getLeftOperand().getTree(), aTypeFactory.getValueAnnotatedTypeFactory());
+    if (valLeft != null) {
+      return addAnnotationForLiteralDivideLeft(valLeft.intValue(), leftAnno);
+    }
+
+    /* This section handles generic annotations:
+     *    pos / {pos, nn} -> nn (can round to zero)
+     *    * / {pos, nn} -> *
+     */
+    if (isPositive(leftAnno) && isNonNegative(rightAnno)) {
+      return NN;
+    }
+    if (isNonNegative(rightAnno)) {
+      return leftAnno;
+    }
+    // Everything else is unknown.
+    return UNKNOWN;
+  }
+
+  /** A remainder with 1 or -1 as the divisor always results in zero. */
+  private AnnotationMirror addAnnotationForLiteralRemainder(int val) {
+    if (val == 1 || val == -1) {
+      return NN;
+    }
+    return UNKNOWN;
+  }
+
+  /** Adds a default NonNegative annotation to every character. Implements case 33. */
+  @Override
+  protected void addInformationFromPreconditions(
+      CFStore info,
+      AnnotatedTypeFactory factory,
+      UnderlyingAST.CFGMethod method,
+      MethodTree methodTree,
+      ExecutableElement methodElement) {
+    super.addInformationFromPreconditions(info, factory, method, methodTree, methodElement);
+
+    List<? extends VariableTree> paramTrees = methodTree.getParameters();
+
+    for (VariableTree variableTree : paramTrees) {
+      if (TreeUtils.typeOf(variableTree).getKind() == TypeKind.CHAR) {
+        JavaExpression je = JavaExpression.fromVariableTree(variableTree);
+        info.insertValuePermitNondeterministic(je, aTypeFactory.NN);
+      }
+    }
+  }
+
+  /**
+   * getAnnotationForRemainder handles these cases:
+   *
+   * <pre>
+   *      27. * % 1/-1 &rarr; nn
+   *      28. pos/nn % * &rarr; nn
+   *      29. gten1 % * &rarr; gten1
+   *      * % * &rarr; lbu
+   * </pre>
+   */
+  public AnnotationMirror getAnnotationForRemainder(
+      IntegerRemainderNode node, TransferInput<CFValue, CFStore> p) {
+
+    AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p);
+
+    // Check if the right side's value is known at compile time.
+    Long valRight =
+        ValueCheckerUtils.getExactValue(
+            node.getRightOperand().getTree(), aTypeFactory.getValueAnnotatedTypeFactory());
+    if (valRight != null) {
+      return addAnnotationForLiteralRemainder(valRight.intValue());
+    }
+
+    /* This section handles generic annotations:
+          pos/nn % * -> nn
+          gten1 % * -> gten1
+    */
+    if (isNonNegative(leftAnno)) {
+      return NN;
+    }
+    if (isGTEN1(leftAnno)) {
+      return GTEN1;
+    }
+
+    // Everything else is unknown.
+    return UNKNOWN;
+  }
+
+  /** Handles shifts (case 30). * &gt;&gt; NonNegative &rarr; NonNegative */
+  private AnnotationMirror getAnnotationForRightShift(
+      BinaryOperationNode node, TransferInput<CFValue, CFStore> p) {
+    AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p);
+    AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p);
+
+    if (isNonNegative(leftAnno)) {
+      if (isNonNegative(rightAnno)) {
+        return NN;
+      }
+    }
+    return UNKNOWN;
+  }
+
+  /**
+   * Handles masking (case 31). Particularly, handles the following cases: * &amp; NonNegative
+   * &rarr; NonNegative
+   */
+  private AnnotationMirror getAnnotationForAnd(
+      BitwiseAndNode node, TransferInput<CFValue, CFStore> p) {
+
+    AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p);
+    if (isNonNegative(rightAnno)) {
+      return NN;
+    }
+
+    AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p);
+    if (isNonNegative(leftAnno)) {
+      return NN;
+    }
+    return UNKNOWN;
+  }
+
+  /**
+   * Returns true if the argument is the @Positive type annotation.
+   *
+   * @param anm the annotation to test
+   * @return true if the the argument is the @Positive type annotation
+   */
+  private boolean isPositive(AnnotationMirror anm) {
+    return aTypeFactory.areSameByClass(anm, Positive.class);
+  }
+
+  /**
+   * Returns true if the argument is the @NonNegative type annotation (or a stronger one).
+   *
+   * @param anm the annotation to test
+   * @return true if the the argument is the @NonNegative type annotation
+   */
+  private boolean isNonNegative(AnnotationMirror anm) {
+    return aTypeFactory.areSameByClass(anm, NonNegative.class) || isPositive(anm);
+  }
+
+  /**
+   * Returns true if the argument is the @GTENegativeOne type annotation (or a stronger one).
+   *
+   * @param anm the annotation to test
+   * @return true if the the argument is the @GTENegativeOne type annotation
+   */
+  private boolean isGTEN1(AnnotationMirror anm) {
+    return aTypeFactory.areSameByClass(anm, GTENegativeOne.class) || isNonNegative(anm);
+  }
+
+  private AnnotationMirror getLowerBoundAnnotation(
+      Node subNode, TransferInput<CFValue, CFStore> p) {
+    CFValue value = p.getValueOfSubNode(subNode);
+    return getLowerBoundAnnotation(value);
+  }
+
+  private AnnotationMirror getLowerBoundAnnotation(CFValue cfValue) {
+    return aTypeFactory
+        .getQualifierHierarchy()
+        .findAnnotationInHierarchy(cfValue.getAnnotations(), aTypeFactory.UNKNOWN);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitNumericalAddition(
+      NumericalAdditionNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> result = super.visitNumericalAddition(n, p);
+    AnnotationMirror newAnno = getAnnotationForPlus(n, p);
+    return createNewResult(result, newAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitNumericalSubtraction(
+      NumericalSubtractionNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> result = super.visitNumericalSubtraction(n, p);
+    AnnotationMirror newAnno = getAnnotationForMinus(n, p);
+    return createNewResult(result, newAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitNumericalMultiplication(
+      NumericalMultiplicationNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> result = super.visitNumericalMultiplication(n, p);
+    AnnotationMirror newAnno = getAnnotationForMultiply(n, p);
+    return createNewResult(result, newAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitIntegerDivision(
+      IntegerDivisionNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> result = super.visitIntegerDivision(n, p);
+    AnnotationMirror newAnno = getAnnotationForDivide(n, p);
+    return createNewResult(result, newAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitIntegerRemainder(
+      IntegerRemainderNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitIntegerRemainder(n, p);
+    AnnotationMirror resultAnno = getAnnotationForRemainder(n, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitSignedRightShift(
+      SignedRightShiftNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitSignedRightShift(n, p);
+    AnnotationMirror resultAnno = getAnnotationForRightShift(n, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitUnsignedRightShift(
+      UnsignedRightShiftNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitUnsignedRightShift(n, p);
+    AnnotationMirror resultAnno = getAnnotationForRightShift(n, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitBitwiseAnd(
+      BitwiseAndNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitBitwiseAnd(n, p);
+    AnnotationMirror resultAnno = getAnnotationForAnd(n, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  /**
+   * Create a new transfer result based on the original result and the new annotation.
+   *
+   * @param result the original result
+   * @param resultAnno the new annotation
+   * @return the new transfer result
+   */
+  private TransferResult<CFValue, CFStore> createNewResult(
+      TransferResult<CFValue, CFStore> result, AnnotationMirror resultAnno) {
+    CFValue newResultValue =
+        analysis.createSingleAnnotationValue(
+            resultAnno, result.getResultValue().getUnderlyingType());
+    return new RegularTransferResult<>(newResultValue, result.getRegularStore());
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java
new file mode 100644
index 0000000..e0d1e77
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java
@@ -0,0 +1,91 @@
+package org.checkerframework.checker.index.lowerbound;
+
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.Tree;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.index.Subsequence;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.util.JavaExpressionParseUtil;
+
+/**
+ * Implements the actual checks to make sure that array accesses aren't too low. Will issue a
+ * warning if a variable that can't be proved to be either "NonNegative" (i.e. &ge; 0) or "Positive"
+ * (i.e. &ge; 1) is used as an array index.
+ */
+public class LowerBoundVisitor extends BaseTypeVisitor<LowerBoundAnnotatedTypeFactory> {
+
+  /* This is a key into the messages.properties file in the same
+   * directory, which includes the actual text of the warning.
+   */
+  private static final @CompilerMessageKey String LOWER_BOUND = "array.access.unsafe.low";
+  private static final @CompilerMessageKey String NEGATIVE_ARRAY = "array.length.negative";
+  private static final @CompilerMessageKey String FROM_NOT_NN = "from.not.nonnegative";
+
+  public LowerBoundVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  public Void visitArrayAccess(ArrayAccessTree tree, Void type) {
+    ExpressionTree index = tree.getIndex();
+    String arrName = tree.getExpression().toString();
+    AnnotatedTypeMirror indexType = atypeFactory.getAnnotatedType(index);
+    if (!(indexType.hasAnnotation(NonNegative.class) || indexType.hasAnnotation(Positive.class))) {
+      checker.reportError(index, LOWER_BOUND, indexType.toString(), arrName);
+    }
+
+    return super.visitArrayAccess(tree, type);
+  }
+
+  @Override
+  public Void visitNewArray(NewArrayTree tree, Void type) {
+    if (!tree.getDimensions().isEmpty()) {
+      for (ExpressionTree dim : tree.getDimensions()) {
+        AnnotatedTypeMirror dimType = atypeFactory.getAnnotatedType(dim);
+        if (!(dimType.hasAnnotation(NonNegative.class) || dimType.hasAnnotation(Positive.class))) {
+          checker.reportError(dim, NEGATIVE_ARRAY, dimType.toString());
+        }
+      }
+    }
+
+    return super.visitNewArray(tree, type);
+  }
+
+  @Override
+  protected void commonAssignmentCheck(
+      Tree varTree,
+      ExpressionTree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+
+    // check that when an assignment to a variable declared as @HasSubsequence(a, from, to)
+    // occurs, from is non-negative.
+
+    Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory);
+    if (subSeq != null) {
+      AnnotationMirror anm;
+      try {
+        anm =
+            atypeFactory.getAnnotationMirrorFromJavaExpressionString(
+                subSeq.from, varTree, getCurrentPath());
+      } catch (JavaExpressionParseUtil.JavaExpressionParseException e) {
+        anm = null;
+      }
+      if (anm == null
+          || !(atypeFactory.areSameByClass(anm, NonNegative.class)
+              || atypeFactory.areSameByClass(anm, Positive.class))) {
+        checker.reportError(
+            valueTree, FROM_NOT_NN, subSeq.from, anm == null ? "@LowerBoundUnknown" : anm);
+      }
+    }
+
+    super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/messages.properties b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/messages.properties
new file mode 100644
index 0000000..200383c
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/messages.properties
@@ -0,0 +1,4 @@
+### Error messages for the Lower Bound Checker
+array.access.unsafe.low=Potentially unsafe array access: the index could be negative.%nfound   : %s%nrequired: an integer >= 0 (@NonNegative or @Positive)
+array.length.negative=Variable used in array creation could be negative.%nfound   : %s%nrequired: an integer >= 0 (@NonNegative or @Positive)
+from.not.nonnegative=While attempting to validate a subsequence type, the Lower Bound Checker could not prove that %s is non-negative.%nfound   : %s%nrequired: an integer >= 0 (@NonNegative or @Positive)
diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java
new file mode 100644
index 0000000..9a88d93
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java
@@ -0,0 +1,381 @@
+package org.checkerframework.checker.index.samelen;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.index.IndexMethodIdentifier;
+import org.checkerframework.checker.index.IndexUtil;
+import org.checkerframework.checker.index.qual.PolyLength;
+import org.checkerframework.checker.index.qual.PolySameLen;
+import org.checkerframework.checker.index.qual.SameLen;
+import org.checkerframework.checker.index.qual.SameLenBottom;
+import org.checkerframework.checker.index.qual.SameLenUnknown;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.expression.ArrayCreation;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.ElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.util.JavaExpressionParseUtil;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * The SameLen Checker is used to determine whether there are multiple fixed-length sequences (such
+ * as arrays or strings) in a program that share the same length. It is part of the Index Checker,
+ * and is used as a subchecker by the Index Checker's components.
+ *
+ * <p>This type factory adds extra expressions to @SameLen annotations when necessary. For example,
+ * if the full version of the type {@code @SameLen({"a","b"})} should include "a", "b", and whatever
+ * is in the @SameLen types for "a" and for "b".
+ *
+ * <p>Also, every sequence s should have type @SameLen("s"). However, sometimes the sequence has
+ * no @SameLen annotation, and users may write the annotation without the variable itself, as in
+ *
+ * <pre>{@code @SameLen("b") String a;}</pre>
+ *
+ * rather than the more pedantic
+ *
+ * <pre>{@code @SameLen({"a", "b"}) String a;}</pre>
+ *
+ * <p>Here are two specific examples where this annotated type factory refines types:
+ *
+ * <ul>
+ *   <li>User-written SameLen: If a variable is declared with a user-written {@code @SameLen}
+ *       annotation, then the type of the new variable is the union of the user-written arrays in
+ *       the annotation and the arrays listed in the SameLen types of each of those arrays.
+ *   <li>New array: The type of an expression of the form {@code new T[a.length]} is the union of
+ *       the SameLen type of {@code a} and the arrays listed in {@code a}'s SameLen type.
+ * </ul>
+ */
+public class SameLenAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** The @{@link SameLenUnknown} annotation. */
+  public final AnnotationMirror UNKNOWN =
+      AnnotationBuilder.fromClass(elements, SameLenUnknown.class);
+  /** The @{@link SameLenBottom} annotation. */
+  private final AnnotationMirror BOTTOM =
+      AnnotationBuilder.fromClass(elements, SameLenBottom.class);
+  /** The @{@link PolySameLen} annotation. */
+  private final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolySameLen.class);
+
+  /** The SameLen.value field/element. */
+  final ExecutableElement sameLenValueElement =
+      TreeUtils.getMethod(SameLen.class, "value", 0, processingEnv);
+
+  /** Predicates about method calls. */
+  private final IndexMethodIdentifier imf = new IndexMethodIdentifier(this);
+
+  /** Create a new SameLenAnnotatedTypeFactory. */
+  public SameLenAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    addAliasedTypeAnnotation(PolyLength.class, POLY);
+
+    this.postInit();
+  }
+
+  /** Gets a helper object that holds references to methods with special handling. */
+  IndexMethodIdentifier getMethodIdentifier() {
+    return imf;
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined.
+    return new LinkedHashSet<>(
+        Arrays.asList(SameLen.class, SameLenBottom.class, SameLenUnknown.class, PolySameLen.class));
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new SameLenQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
+  }
+
+  // Handles case "user-written SameLen"
+  @Override
+  public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree tree) {
+    AnnotatedTypeMirror atm = super.getAnnotatedTypeLhs(tree);
+
+    if (tree.getKind() == Tree.Kind.VARIABLE) {
+      AnnotationMirror sameLenAnno = atm.getAnnotation(SameLen.class);
+      if (sameLenAnno != null) {
+        JavaExpression je = JavaExpression.fromVariableTree((VariableTree) tree);
+        String varName = je.toString();
+
+        List<String> exprs =
+            AnnotationUtils.getElementValueArray(sameLenAnno, sameLenValueElement, String.class);
+        exprs.remove(varName);
+        if (exprs.isEmpty()) {
+          atm.replaceAnnotation(UNKNOWN);
+        } else {
+          atm.replaceAnnotation(createSameLen(exprs));
+        }
+      }
+    }
+
+    return atm;
+  }
+
+  /** Returns true if the given expression may appear in a @SameLen annotation. */
+  public static boolean mayAppearInSameLen(JavaExpression expr) {
+    return !expr.containsUnknown()
+        && !(expr instanceof ArrayCreation)
+        && !(expr instanceof ClassName)
+        // Big expressions cause a stack overflow in JavaExpressionParseUtil.
+        // So limit them to an arbitrary length of 999.
+        && expr.toString().length() < 1000;
+  }
+
+  /**
+   * The qualifier hierarchy for the SameLen type system. Most types are distinct and at the same
+   * level: for instance @SameLen("a") and @SameLen("b) have nothing in common. However, if one type
+   * includes even one overlapping name, then the types have to be the same:
+   * so @SameLen({"a","b","c"} and @SameLen({"c","f","g"} are actually the same type -- both should
+   * usually be replaced by a SameLen with the union of the lists of names.
+   */
+  private final class SameLenQualifierHierarchy extends ElementQualifierHierarchy {
+
+    /**
+     * Creates a SameLenQualifierHierarchy from the given classes.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers
+     * @param elements element utils
+     */
+    public SameLenQualifierHierarchy(
+        Set<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+    }
+
+    @Override
+    public AnnotationMirror getTopAnnotation(AnnotationMirror start) {
+      return UNKNOWN;
+    }
+
+    /**
+     * If the collections are disjoint, returns null. Otherwise, returns their union. The
+     * collections must not contain duplicates.
+     */
+    private Set<String> unionIfNotDisjoint(Collection<String> c1, Collection<String> c2) {
+      Set<String> result = new TreeSet<>(c1);
+      for (String s : c2) {
+        if (!result.add(s)) {
+          return null;
+        }
+      }
+      return result;
+    }
+
+    // The GLB of two SameLen annotations is the union of the two sets of arrays, or is bottom
+    // if the sets do not intersect.
+    @Override
+    public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
+      if (areSameByClass(a1, SameLen.class) && areSameByClass(a2, SameLen.class)) {
+        List<String> a1Val =
+            AnnotationUtils.getElementValueArray(a1, sameLenValueElement, String.class);
+        List<String> a2Val =
+            AnnotationUtils.getElementValueArray(a2, sameLenValueElement, String.class);
+
+        Set<String> exprs = unionIfNotDisjoint(a1Val, a2Val);
+        if (exprs == null) {
+          return BOTTOM;
+        } else {
+          return createSameLen(exprs);
+        }
+      } else {
+        // the glb is either one of the annotations (if the other is top), or bottom.
+        if (areSameByClass(a1, SameLenUnknown.class)) {
+          return a2;
+        } else if (areSameByClass(a2, SameLenUnknown.class)) {
+          return a1;
+        } else {
+          return BOTTOM;
+        }
+      }
+    }
+
+    // The LUB of two SameLen annotations is the intersection of the two sets of arrays, or is
+    // top if they do not intersect.
+    @Override
+    public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
+      if (areSameByClass(a1, SameLen.class) && areSameByClass(a2, SameLen.class)) {
+        List<String> a1Val =
+            AnnotationUtils.getElementValueArray(a1, sameLenValueElement, String.class);
+        List<String> a2Val =
+            AnnotationUtils.getElementValueArray(a2, sameLenValueElement, String.class);
+
+        if (!Collections.disjoint(a1Val, a2Val)) {
+          a1Val.retainAll(a2Val);
+          return createSameLen(a1Val);
+        } else {
+          return UNKNOWN;
+        }
+
+      } else {
+        // the lub is either one of the annotations (if the other is bottom), or top.
+        if (areSameByClass(a1, SameLenBottom.class)) {
+          return a2;
+        } else if (areSameByClass(a2, SameLenBottom.class)) {
+          return a1;
+        } else if (areSameByClass(a1, PolySameLen.class) && areSameByClass(a2, PolySameLen.class)) {
+          return a1;
+        } else {
+          return UNKNOWN;
+        }
+      }
+    }
+
+    @Override
+    public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+      if (areSameByClass(subAnno, SameLenBottom.class)) {
+        return true;
+      } else if (areSameByClass(superAnno, SameLenUnknown.class)) {
+        return true;
+      } else if (areSameByClass(subAnno, PolySameLen.class)) {
+        return areSameByClass(superAnno, PolySameLen.class);
+      } else if (areSameByClass(subAnno, SameLen.class)
+          && areSameByClass(superAnno, SameLen.class)) {
+        List<String> subArrays =
+            AnnotationUtils.getElementValueArray(subAnno, sameLenValueElement, String.class);
+        List<String> superArrays =
+            AnnotationUtils.getElementValueArray(superAnno, sameLenValueElement, String.class);
+
+        if (subArrays.containsAll(superArrays)) {
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+
+  @Override
+  public TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(super.createTreeAnnotator(), new SameLenTreeAnnotator(this));
+  }
+
+  /**
+   * SameLen needs a tree annotator in order to properly type the right side of assignments of new
+   * arrays that are initialized with the length of another array.
+   */
+  protected class SameLenTreeAnnotator extends TreeAnnotator {
+
+    public SameLenTreeAnnotator(SameLenAnnotatedTypeFactory factory) {
+      super(factory);
+    }
+
+    // Case "new array" for "new T[a.length]"
+    @Override
+    public Void visitNewArray(NewArrayTree node, AnnotatedTypeMirror type) {
+      if (node.getDimensions().size() == 1) {
+        Tree dimensionTree = node.getDimensions().get(0);
+        ExpressionTree sequenceTree =
+            IndexUtil.getLengthSequenceTree(dimensionTree, imf, processingEnv);
+        if (sequenceTree != null) {
+          AnnotationMirror sequenceAnno =
+              getAnnotatedType(sequenceTree).getAnnotationInHierarchy(UNKNOWN);
+
+          JavaExpression sequenceExpr = JavaExpression.fromTree(sequenceTree);
+          if (mayAppearInSameLen(sequenceExpr)) {
+            String recString = sequenceExpr.toString();
+            if (areSameByClass(sequenceAnno, SameLenUnknown.class)) {
+              sequenceAnno = createSameLen(Collections.singletonList(recString));
+            } else if (areSameByClass(sequenceAnno, SameLen.class)) {
+              // Add the sequence whose length is being used to the annotation.
+              List<String> exprs =
+                  AnnotationUtils.getElementValueArray(
+                      sequenceAnno, sameLenValueElement, String.class);
+              int index = Collections.binarySearch(exprs, recString);
+              if (index < 0) {
+                exprs.add(-index - 1, recString);
+                sequenceAnno = createSameLen(exprs);
+              }
+            }
+          }
+          type.addAnnotation(sequenceAnno);
+        }
+      }
+      return null;
+    }
+  }
+
+  /**
+   * Find all the sequences that are members of the SameLen annotation associated with the sequence
+   * named in sequenceExpression along the current path.
+   */
+  public List<String> getSameLensFromString(
+      String sequenceExpression, Tree tree, TreePath currentPath) {
+    AnnotationMirror sameLenAnno;
+    try {
+      sameLenAnno =
+          getAnnotationFromJavaExpressionString(
+              sequenceExpression, tree, currentPath, SameLen.class);
+    } catch (JavaExpressionParseUtil.JavaExpressionParseException e) {
+      // ignore parse errors
+      return Collections.emptyList();
+    }
+    if (sameLenAnno == null) {
+      return Collections.emptyList();
+    }
+    return AnnotationUtils.getElementValueArray(sameLenAnno, sameLenValueElement, String.class);
+  }
+
+  ///
+  /// Creating @SameLen annotations
+  ///
+
+  /**
+   * Creates a @SameLen annotation whose values are the given strings, from an <em>ordered</em>
+   * collection such as a list or TreeSet in which the strings are in alphabetical order.
+   */
+  public AnnotationMirror createSameLen(Collection<String> exprs) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SameLen.class);
+    String[] exprArray = exprs.toArray(new String[0]);
+    builder.setValue("value", exprArray);
+    return builder.build();
+  }
+
+  /**
+   * Generates a SameLen that includes each expression, as well as everything in the annotations2,
+   * if they are SameLen annotations.
+   *
+   * @param exprs a list of expressions representing arrays to be included in the combined
+   *     annotation
+   * @param annos a list of annotations
+   * @return a combined SameLen annotation
+   */
+  public AnnotationMirror createCombinedSameLen(
+      List<JavaExpression> exprs, List<AnnotationMirror> annos) {
+
+    Set<String> strings = new TreeSet<>();
+    for (JavaExpression expr : exprs) {
+      if (mayAppearInSameLen(expr)) {
+        strings.add(expr.toString());
+      }
+    }
+    for (AnnotationMirror anno : annos) {
+      if (areSameByClass(anno, SameLen.class)) {
+        strings.addAll(
+            AnnotationUtils.getElementValueArray(anno, sameLenValueElement, String.class));
+      }
+    }
+    return createSameLen(strings);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenChecker.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenChecker.java
new file mode 100644
index 0000000..f8a2105
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenChecker.java
@@ -0,0 +1,15 @@
+package org.checkerframework.checker.index.samelen;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+import org.checkerframework.framework.source.SuppressWarningsPrefix;
+
+/**
+ * An internal checker that collects information about arrays that have the same length. It is used
+ * by the Upper Bound Checker.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@RelevantJavaTypes({CharSequence.class, Object[].class})
+@SuppressWarningsPrefix({"index", "samelen"})
+public class SameLenChecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenTransfer.java
new file mode 100644
index 0000000..a39c35a
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenTransfer.java
@@ -0,0 +1,301 @@
+package org.checkerframework.checker.index.samelen;
+
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.checker.index.IndexUtil;
+import org.checkerframework.checker.index.qual.SameLen;
+import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.node.ArrayCreationNode;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.util.JavaExpressionParseUtil;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * The transfer function for the SameLen checker. Contains three cases:
+ *
+ * <ul>
+ *   <li>new array: "b = new T[a.length]" implies that b is the same length as a.
+ *   <li>length equality: after "if (a.length == b.length)", a and b have the same length.
+ *   <li>object equality: after "if (a == b)", a and b have the same length, if they are arrays or
+ *       strings.
+ * </ul>
+ */
+public class SameLenTransfer extends CFTransfer {
+
+  private SameLenAnnotatedTypeFactory aTypeFactory;
+
+  /** Shorthand for aTypeFactory.UNKNOWN. */
+  private AnnotationMirror UNKNOWN;
+
+  public SameLenTransfer(CFAnalysis analysis) {
+    super(analysis);
+    this.aTypeFactory = (SameLenAnnotatedTypeFactory) analysis.getTypeFactory();
+    this.UNKNOWN = aTypeFactory.UNKNOWN;
+  }
+
+  /**
+   * Gets the receiver sequence of a length access node, or null if {@code lengthNode} is not a
+   * length access.
+   */
+  private Node getLengthReceiver(Node lengthNode) {
+    if (isArrayLengthAccess(lengthNode)) {
+      // lengthNode is a.length
+      FieldAccessNode lengthFieldAccessNode = (FieldAccessNode) lengthNode;
+      return lengthFieldAccessNode.getReceiver();
+    } else if (aTypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(lengthNode)) {
+      // lengthNode is s.length()
+      MethodInvocationNode lengthMethodInvocationNode = (MethodInvocationNode) lengthNode;
+      return lengthMethodInvocationNode.getTarget().getReceiver();
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitAssignment(
+      AssignmentNode node, TransferInput<CFValue, CFStore> in) {
+    TransferResult<CFValue, CFStore> result = super.visitAssignment(node, in);
+
+    // Handle b = new T[a.length]
+    if (node.getExpression() instanceof ArrayCreationNode) {
+      ArrayCreationNode acNode = (ArrayCreationNode) node.getExpression();
+      if (acNode.getDimensions().size() == 1) {
+
+        Node lengthNode = acNode.getDimension(0);
+        Node lengthNodeReceiver = getLengthReceiver(lengthNode);
+
+        if (lengthNodeReceiver != null) {
+          // "new T[a.length]" or "new T[s.length()]" is the right hand side of the assignment.
+          // lengthNode is known to be "lengthNodeReceiver.length" or "lengthNodeReceiver.length()"
+
+          // targetRec is the receiver for the left hand side of the assignment.
+          JavaExpression targetRec = JavaExpression.fromNode(node.getTarget());
+          JavaExpression otherRec = JavaExpression.fromNode(lengthNodeReceiver);
+
+          AnnotationMirror lengthNodeAnnotation =
+              aTypeFactory
+                  .getAnnotatedType(lengthNodeReceiver.getTree())
+                  .getAnnotationInHierarchy(UNKNOWN);
+
+          AnnotationMirror combinedSameLen =
+              aTypeFactory.createCombinedSameLen(
+                  Arrays.asList(targetRec, otherRec), Arrays.asList(UNKNOWN, lengthNodeAnnotation));
+
+          propagateCombinedSameLen(combinedSameLen, node, result.getRegularStore());
+          return result;
+        }
+      }
+    }
+
+    AnnotationMirror rightAnno =
+        aTypeFactory
+            .getAnnotatedType(node.getExpression().getTree())
+            .getAnnotationInHierarchy(UNKNOWN);
+
+    // If the left side of the assignment is an array or a string, then have both the right and
+    // left side be SameLen of each other.
+
+    JavaExpression targetRec = JavaExpression.fromNode(node.getTarget());
+
+    JavaExpression exprRec = JavaExpression.fromNode(node.getExpression());
+
+    if (IndexUtil.isSequenceType(node.getTarget().getType())
+        || (rightAnno != null && aTypeFactory.areSameByClass(rightAnno, SameLen.class))) {
+
+      AnnotationMirror rightAnnoOrUnknown = rightAnno == null ? UNKNOWN : rightAnno;
+
+      AnnotationMirror combinedSameLen =
+          aTypeFactory.createCombinedSameLen(
+              Arrays.asList(targetRec, exprRec), Arrays.asList(UNKNOWN, rightAnnoOrUnknown));
+
+      propagateCombinedSameLen(combinedSameLen, node, result.getRegularStore());
+    }
+
+    return result;
+  }
+
+  /**
+   * Insert a @SameLen annotation into the store as the SameLen type of each array listed in it.
+   *
+   * @param sameLenAnno a {@code @SameLen} annotation. Not just an annotation in the SameLen
+   *     hierarchy; this annotation must be {@code @SameLen(...)}.
+   * @param node the node in the tree where the combination is happening. Used for context.
+   * @param store the store to modify
+   */
+  private void propagateCombinedSameLen(AnnotationMirror sameLenAnno, Node node, CFStore store) {
+    TreePath currentPath = aTypeFactory.getPath(node.getTree());
+    if (currentPath == null) {
+      return;
+    }
+    for (String exprString :
+        AnnotationUtils.getElementValueArray(
+            sameLenAnno, aTypeFactory.sameLenValueElement, String.class)) {
+      JavaExpression je;
+      try {
+        je = aTypeFactory.parseJavaExpressionString(exprString, currentPath);
+      } catch (JavaExpressionParseUtil.JavaExpressionParseException e) {
+        continue;
+      }
+      store.clearValue(je);
+      store.insertValue(je, sameLenAnno);
+    }
+  }
+
+  /** Returns true if node is of the form "someArray.length". */
+  private boolean isArrayLengthAccess(Node node) {
+    return (node instanceof FieldAccessNode
+        && ((FieldAccessNode) node).getFieldName().equals("length")
+        && ((FieldAccessNode) node).getReceiver().getType().getKind() == TypeKind.ARRAY);
+  }
+
+  /**
+   * Handles refinement of equality comparisons. Assumes "a == b" or "a.length == b.length"
+   * evaluates to true. The method gives a and b SameLen of each other in the store.
+   *
+   * @param left the first argument to the equality operator
+   * @param right the second argument to the equality operator
+   * @param store the store in which to perform refinement
+   */
+  private void refineEq(Node left, Node right, CFStore store) {
+    List<JavaExpression> exprs = new ArrayList<>(2);
+    List<AnnotationMirror> annos = new ArrayList<>(2);
+    for (Node internal : splitAssignments(left)) {
+      exprs.add(JavaExpression.fromNode(internal));
+      annos.add(getAnno(internal));
+    }
+    for (Node internal : splitAssignments(right)) {
+      exprs.add(JavaExpression.fromNode(internal));
+      annos.add(getAnno(internal));
+    }
+
+    AnnotationMirror combinedSameLen = aTypeFactory.createCombinedSameLen(exprs, annos);
+
+    propagateCombinedSameLen(combinedSameLen, left, store);
+  }
+
+  /**
+   * Return n's annotation from the SameLen hierarchy.
+   *
+   * <p>analysis.getValue fails if called on an lvalue. However, this method needs to always
+   * succeed, even when n is an lvalue. Consider this code:
+   *
+   * <pre>{@code if ((a = b) == c) {...}}</pre>
+   *
+   * where a, b, and c are all arrays, and a has type {@code @SameLen("d")}. Afterwards, all three
+   * should have the type {@code @SameLen({"a", "b", "c", "d"})}, but in order to accomplish this,
+   * this method must return the type of a, which is an lvalue.
+   */
+  AnnotationMirror getAnno(Node n) {
+    if (n.isLValue()) {
+      return aTypeFactory.getAnnotatedType(n.getTree()).getAnnotationInHierarchy(UNKNOWN);
+    }
+    CFValue cfValue = analysis.getValue(n);
+    if (cfValue == null) {
+      return UNKNOWN;
+    }
+    return aTypeFactory
+        .getQualifierHierarchy()
+        .findAnnotationInHierarchy(cfValue.getAnnotations(), UNKNOWN);
+  }
+
+  /** Implements the transfer rules for both equal nodes and not-equals nodes. */
+  @Override
+  protected TransferResult<CFValue, CFStore> strengthenAnnotationOfEqualTo(
+      TransferResult<CFValue, CFStore> result,
+      Node firstNode,
+      Node secondNode,
+      CFValue firstValue,
+      CFValue secondValue,
+      boolean notEqualTo) {
+    // If result is a Regular transfer, then the elseStore is a copy of the then store, that is
+    // created when getElseStore is called.  So do that before refining any values.
+    CFStore elseStore = result.getElseStore();
+    CFStore thenStore = result.getThenStore();
+    CFStore equalStore = notEqualTo ? elseStore : thenStore;
+
+    Node firstLengthReceiver = getLengthReceiver(firstNode);
+    Node secondLengthReceiver = getLengthReceiver(secondNode);
+
+    if (firstLengthReceiver != null && secondLengthReceiver != null) {
+      // Refinement in the else store if this is a.length == b.length (or length() in case of
+      // strings).
+      refineEq(firstLengthReceiver, secondLengthReceiver, equalStore);
+    } else if (IndexUtil.isSequenceType(firstNode.getType())
+        || IndexUtil.isSequenceType(secondNode.getType())) {
+      refineEq(firstNode, secondNode, equalStore);
+    }
+
+    return new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore);
+  }
+
+  /** Overridden to ensure that SameLen annotations on method parameters are symmetric. */
+  @Override
+  protected void addInformationFromPreconditions(
+      CFStore info,
+      AnnotatedTypeFactory factory,
+      UnderlyingAST.CFGMethod method,
+      MethodTree methodTree,
+      ExecutableElement methodElement) {
+    super.addInformationFromPreconditions(info, factory, method, methodTree, methodElement);
+
+    List<? extends VariableTree> paramTrees = methodTree.getParameters();
+    int numParams = paramTrees.size();
+    List<String> paramNames = new ArrayList<>(numParams);
+    List<AnnotatedTypeMirror> params = new ArrayList<>(numParams);
+
+    for (VariableTree tree : paramTrees) {
+      paramNames.add(tree.getName().toString());
+      params.add(aTypeFactory.getAnnotatedType(tree));
+    }
+
+    for (int index = 0; index < numParams; index++) {
+
+      // If the parameter has a samelen annotation, then look for other parameters in that
+      // annotation and propagate default the other annotation so that it is symmetric.
+      AnnotatedTypeMirror atm = params.get(index);
+      AnnotationMirror sameLenAnno = atm.getAnnotation(SameLen.class);
+      if (sameLenAnno == null) {
+        continue;
+      }
+
+      List<String> values =
+          AnnotationUtils.getElementValueArray(
+              sameLenAnno, aTypeFactory.sameLenValueElement, String.class);
+      for (String value : values) {
+        int otherParamIndex = paramNames.indexOf(value);
+        if (otherParamIndex == -1) {
+          continue;
+        }
+
+        // the SameLen value is in the list of params, so modify the type of
+        // that param in the store
+        AnnotationMirror newSameLen =
+            aTypeFactory.createSameLen(Collections.singletonList(paramNames.get(index)));
+        JavaExpression otherParamRec =
+            JavaExpression.fromVariableTree(paramTrees.get(otherParamIndex));
+        info.insertValuePermitNondeterministic(otherParamRec, newSameLen);
+      }
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java
new file mode 100644
index 0000000..c3ab077
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java
@@ -0,0 +1,63 @@
+package org.checkerframework.checker.index.samelen;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.TreeSet;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.index.IndexUtil;
+import org.checkerframework.checker.index.qual.PolySameLen;
+import org.checkerframework.checker.index.qual.SameLen;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+public class SameLenVisitor extends BaseTypeVisitor<SameLenAnnotatedTypeFactory> {
+  public SameLenVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  /**
+   * Merges SameLen annotations, then calls super.
+   *
+   * <p>{@inheritDoc}
+   */
+  @Override
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      AnnotatedTypeMirror valueType,
+      Tree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    if (IndexUtil.isSequenceType(valueType.getUnderlyingType())
+        && TreeUtils.isExpressionTree(valueTree)
+        // if both annotations are @PolySameLen, there is nothing to do
+        && !(valueType.hasAnnotation(PolySameLen.class)
+            && varType.hasAnnotation(PolySameLen.class))) {
+
+      JavaExpression rhs = JavaExpression.fromTree((ExpressionTree) valueTree);
+      if (rhs != null && SameLenAnnotatedTypeFactory.mayAppearInSameLen(rhs)) {
+        String rhsExpr = rhs.toString();
+        AnnotationMirror sameLenAnno = valueType.getAnnotation(SameLen.class);
+        Collection<String> exprs;
+        if (sameLenAnno == null) {
+          exprs = Collections.singletonList(rhsExpr);
+        } else {
+          exprs =
+              new TreeSet<>(
+                  AnnotationUtils.getElementValueArray(
+                      sameLenAnno, atypeFactory.sameLenValueElement, String.class));
+          exprs.add(rhsExpr);
+        }
+        AnnotationMirror newSameLen = atypeFactory.createSameLen(exprs);
+        valueType.replaceAnnotation(newSameLen);
+      }
+    }
+    super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java
new file mode 100644
index 0000000..cc4983c
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java
@@ -0,0 +1,240 @@
+package org.checkerframework.checker.index.searchindex;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.index.qual.NegativeIndexFor;
+import org.checkerframework.checker.index.qual.SearchIndexBottom;
+import org.checkerframework.checker.index.qual.SearchIndexFor;
+import org.checkerframework.checker.index.qual.SearchIndexUnknown;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.type.ElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * The Search Index Checker is used to help type the results of calls to the JDK's binary search
+ * methods. It is part of the Index Checker.
+ */
+public class SearchIndexAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** The @{@link SearchIndexUnknown} annotation. */
+  public final AnnotationMirror UNKNOWN =
+      AnnotationBuilder.fromClass(elements, SearchIndexUnknown.class);
+  /** The @{@link SearchIndexBottom} annotation. */
+  public final AnnotationMirror BOTTOM =
+      AnnotationBuilder.fromClass(elements, SearchIndexBottom.class);
+
+  /** The NegativeIndexFor.value field/element. */
+  protected final ExecutableElement negativeIndexForValueElement =
+      TreeUtils.getMethod(NegativeIndexFor.class, "value", 0, processingEnv);
+  /** The SearchIndexFor.value field/element. */
+  protected final ExecutableElement searchIndexForValueElement =
+      TreeUtils.getMethod(SearchIndexFor.class, "value", 0, processingEnv);
+
+  /**
+   * Create a new SearchIndexAnnotatedTypeFactory.
+   *
+   * @param checker the type-checker associated with this
+   */
+  public SearchIndexAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    this.postInit();
+  }
+
+  /**
+   * Provides a way to query the Constant Value Checker, which computes the values of expressions
+   * known at compile time (constant propagation and folding).
+   */
+  ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() {
+    return getTypeFactoryOfSubchecker(ValueChecker.class);
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new LinkedHashSet<>(
+        Arrays.asList(
+            SearchIndexFor.class,
+            SearchIndexBottom.class,
+            SearchIndexUnknown.class,
+            NegativeIndexFor.class));
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new SearchIndexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
+  }
+
+  /**
+   * Returns the {@code value} field/element of the given annotation.
+   *
+   * @param am a @NegativeIndexFor or @SearchIndexFor annotation
+   * @return the {@code value} field/element of the given annotation
+   */
+  private List<String> getValueElement(AnnotationMirror am) {
+    if (areSameByClass(am, NegativeIndexFor.class)) {
+      return AnnotationUtils.getElementValueArray(am, negativeIndexForValueElement, String.class);
+    } else if (areSameByClass(am, SearchIndexFor.class)) {
+      return AnnotationUtils.getElementValueArray(am, searchIndexForValueElement, String.class);
+    } else {
+      throw new BugInCF("indexForValue(%s)", am);
+    }
+  }
+
+  /** SearchIndexQualifierHierarchy. */
+  private final class SearchIndexQualifierHierarchy extends ElementQualifierHierarchy {
+
+    /**
+     * Creates a SearchIndexQualifierHierarchy from the given classes.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers
+     * @param elements element utils
+     */
+    public SearchIndexQualifierHierarchy(
+        Set<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+    }
+
+    @Override
+    public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
+      if (AnnotationUtils.areSame(a1, UNKNOWN)) {
+        return a2;
+      }
+      if (AnnotationUtils.areSame(a2, UNKNOWN)) {
+        return a1;
+      }
+      if (AnnotationUtils.areSame(a1, BOTTOM)) {
+        return a1;
+      }
+      if (AnnotationUtils.areSame(a2, BOTTOM)) {
+        return a2;
+      }
+      if (isSubtype(a1, a2)) {
+        return a1;
+      }
+      if (isSubtype(a2, a1)) {
+        return a2;
+      }
+      // If neither is a subtype of the other, then create an
+      // annotation that combines their values.
+
+      // Each annotation is either NegativeIndexFor or SearchIndexFor.
+      Set<String> combinedArrays = new HashSet<>(getValueElement(a1));
+      combinedArrays.addAll(getValueElement(a2));
+
+      // NegativeIndexFor <: SearchIndexFor.
+      if (areSameByClass(a1, NegativeIndexFor.class)
+          || areSameByClass(a2, NegativeIndexFor.class)) {
+        return createNegativeIndexFor(Arrays.asList(combinedArrays.toArray(new String[0])));
+      } else {
+        return createSearchIndexFor(Arrays.asList(combinedArrays.toArray(new String[0])));
+      }
+    }
+
+    @Override
+    public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
+      if (AnnotationUtils.areSame(a1, UNKNOWN)) {
+        return a1;
+      }
+      if (AnnotationUtils.areSame(a2, UNKNOWN)) {
+        return a2;
+      }
+      if (AnnotationUtils.areSame(a1, BOTTOM)) {
+        return a2;
+      }
+      if (AnnotationUtils.areSame(a2, BOTTOM)) {
+        return a1;
+      }
+      if (isSubtype(a1, a2)) {
+        return a2;
+      }
+      if (isSubtype(a2, a1)) {
+        return a1;
+      }
+      // If neither is a subtype of the other, then create an
+      // annotation that includes only their overlapping values.
+
+      // Each annotation is either NegativeIndexFor or SearchIndexFor.
+      List<String> arrayIntersection = getValueElement(a1);
+      arrayIntersection.retainAll(getValueElement(a2)); // intersection
+
+      if (arrayIntersection.isEmpty()) {
+        return UNKNOWN;
+      }
+
+      if (areSameByClass(a1, SearchIndexFor.class) || areSameByClass(a2, SearchIndexFor.class)) {
+        return createSearchIndexFor(arrayIntersection);
+      } else {
+        return createNegativeIndexFor(arrayIntersection);
+      }
+    }
+
+    @Override
+    public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+      if (areSameByClass(superAnno, SearchIndexUnknown.class)) {
+        return true;
+      }
+      if (areSameByClass(subAnno, SearchIndexBottom.class)) {
+        return true;
+      }
+      if (areSameByClass(subAnno, SearchIndexUnknown.class)) {
+        return false;
+      }
+      if (areSameByClass(superAnno, SearchIndexBottom.class)) {
+        return false;
+      }
+
+      // Each annotation is either NegativeIndexFor or SearchIndexFor.
+      List<String> superArrays = getValueElement(superAnno);
+      List<String> subArrays = getValueElement(subAnno);
+
+      // Subtyping requires:
+      //  * subtype is NegativeIndexFor or supertype is SearchIndexFor
+      //  * subtype's arrays are a superset of supertype's arrays
+      return ((areSameByClass(subAnno, NegativeIndexFor.class)
+              || areSameByClass(superAnno, SearchIndexFor.class))
+          && subArrays.containsAll(superArrays));
+    }
+  }
+
+  /** Create a new {@code @NegativeIndexFor} annotation with the given arrays as its arguments. */
+  AnnotationMirror createNegativeIndexFor(List<String> arrays) {
+    if (arrays.isEmpty()) {
+      return UNKNOWN;
+    }
+
+    arrays = new ArrayList<>(new TreeSet<>(arrays)); // remove duplicates and sort
+
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, NegativeIndexFor.class);
+    builder.setValue("value", arrays);
+    return builder.build();
+  }
+
+  /** Create a new {@code @SearchIndexFor} annotation with the given arrays as its arguments. */
+  AnnotationMirror createSearchIndexFor(List<String> arrays) {
+    if (arrays.isEmpty()) {
+      return UNKNOWN;
+    }
+
+    arrays = new ArrayList<>(new TreeSet<>(arrays)); // remove duplicates and sort
+
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SearchIndexFor.class);
+    builder.setValue("value", arrays);
+    return builder.build();
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexChecker.java b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexChecker.java
new file mode 100644
index 0000000..1a5fb48
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexChecker.java
@@ -0,0 +1,37 @@
+package org.checkerframework.checker.index.searchindex;
+
+import java.util.LinkedHashSet;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+import org.checkerframework.framework.source.SuppressWarningsPrefix;
+
+/**
+ * An internal checker that assists the Index Checker in typing the results of calls to the JDK's
+ * {@link java.util.Arrays#binarySearch(Object[],Object) Arrays.binarySearch} routine.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@SuppressWarningsPrefix({"index", "searchindex"})
+@RelevantJavaTypes({
+  Byte.class,
+  Short.class,
+  Integer.class,
+  Long.class,
+  Character.class,
+  byte.class,
+  short.class,
+  int.class,
+  long.class,
+  char.class,
+})
+public class SearchIndexChecker extends BaseTypeChecker {
+
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    LinkedHashSet<Class<? extends BaseTypeChecker>> checkers =
+        super.getImmediateSubcheckerClasses();
+    checkers.add(ValueChecker.class);
+    return checkers;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexTransfer.java
new file mode 100644
index 0000000..740e4cf
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexTransfer.java
@@ -0,0 +1,95 @@
+package org.checkerframework.checker.index.searchindex;
+
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.index.IndexAbstractTransfer;
+import org.checkerframework.checker.index.qual.NegativeIndexFor;
+import org.checkerframework.checker.index.qual.SearchIndexFor;
+import org.checkerframework.common.value.ValueCheckerUtils;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * The transfer function for the SearchIndexFor checker. Allows {@link SearchIndexFor} to be refined
+ * to {@link NegativeIndexFor}.
+ *
+ * <p>Contains 1 refinement rule: SearchIndexFor &rarr; NegativeIndexFor when compared against zero.
+ */
+public class SearchIndexTransfer extends IndexAbstractTransfer {
+
+  // The ATF (Annotated Type Factory).
+  private SearchIndexAnnotatedTypeFactory aTypeFactory;
+
+  public SearchIndexTransfer(CFAnalysis analysis) {
+    super(analysis);
+    aTypeFactory = (SearchIndexAnnotatedTypeFactory) analysis.getTypeFactory();
+  }
+
+  /**
+   * If a {@link SearchIndexFor} value is negative, then refine it to {@link NegativeIndexFor}. This
+   * method is called by refinement rules for binary comparison operators.
+   *
+   * <p>If the left value (in a greater-than or greater-than-or-equal binary comparison) is exactly
+   * the value of {@code valueToCompareTo}, and the right side has type {@link SearchIndexFor}, then
+   * the right side's new value in the store should become {@link NegativeIndexFor}.
+   *
+   * <p>For example, this allows the following code to typecheck:
+   *
+   * <pre>
+   * <code>
+   * {@literal @}SearchIndexFor("a") int index = Arrays.binarySearch(a, y);
+   *  if (index &lt; 0) {
+   *    {@literal @}NegativeIndexFor("a") int negInsertionPoint = index;
+   *  }
+   * </code>
+   * </pre>
+   *
+   * @param valueToCompareTo this value must be 0 (for greater than or less than) or -1 (for greater
+   *     than or equal or less than or equal)
+   */
+  private void refineSearchIndexToNegativeIndexFor(
+      Node left, Node right, CFStore store, int valueToCompareTo) {
+    assert valueToCompareTo == 0 || valueToCompareTo == -1;
+    Long leftValue =
+        ValueCheckerUtils.getExactValue(
+            left.getTree(), aTypeFactory.getValueAnnotatedTypeFactory());
+    if (leftValue != null && leftValue == valueToCompareTo) {
+      AnnotationMirror rightSIF =
+          aTypeFactory.getAnnotationMirror(right.getTree(), SearchIndexFor.class);
+      if (rightSIF != null) {
+        List<String> arrays =
+            AnnotationUtils.getElementValueArray(
+                rightSIF, aTypeFactory.searchIndexForValueElement, String.class);
+        AnnotationMirror nif = aTypeFactory.createNegativeIndexFor(arrays);
+        store.insertValue(JavaExpression.fromNode(right), nif);
+      }
+    }
+  }
+
+  @Override
+  protected void refineGT(
+      Node left,
+      AnnotationMirror leftAnno,
+      Node right,
+      AnnotationMirror rightAnno,
+      CFStore store,
+      TransferInput<CFValue, CFStore> in) {
+    refineSearchIndexToNegativeIndexFor(left, right, store, 0);
+  }
+
+  @Override
+  protected void refineGTE(
+      Node left,
+      AnnotationMirror leftAnno,
+      Node right,
+      AnnotationMirror rightAnno,
+      CFStore store,
+      TransferInput<CFValue, CFStore> in) {
+    refineSearchIndexToNegativeIndexFor(left, right, store, -1);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexAnnotatedTypeFactory.java
new file mode 100644
index 0000000..12ced2a
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexAnnotatedTypeFactory.java
@@ -0,0 +1,178 @@
+package org.checkerframework.checker.index.substringindex;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.index.IndexChecker;
+import org.checkerframework.checker.index.OffsetDependentTypesHelper;
+import org.checkerframework.checker.index.qual.SubstringIndexBottom;
+import org.checkerframework.checker.index.qual.SubstringIndexFor;
+import org.checkerframework.checker.index.qual.SubstringIndexUnknown;
+import org.checkerframework.checker.index.upperbound.UBQualifier;
+import org.checkerframework.checker.index.upperbound.UBQualifier.LessThanLengthOf;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.ElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * Builds types with annotations from the Substring Index checker hierarchy, which contains
+ * the @{@link SubstringIndexFor} annotation.
+ */
+public class SubstringIndexAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** The top qualifier of the Substring Index hierarchy. */
+  public final AnnotationMirror UNKNOWN =
+      AnnotationBuilder.fromClass(elements, SubstringIndexUnknown.class);
+  /** The bottom qualifier of the Substring Index hierarchy. */
+  public final AnnotationMirror BOTTOM =
+      AnnotationBuilder.fromClass(elements, SubstringIndexBottom.class);
+
+  /**
+   * Create a new SubstringIndexAnnotatedTypeFactory.
+   *
+   * @param checker the associated checker
+   */
+  public SubstringIndexAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    this.postInit();
+  }
+
+  /**
+   * Returns a mutable set of annotation classes that are supported by the Substring Index Checker.
+   *
+   * @return mutable set containing annotation classes from the Substring Index qualifier hierarchy
+   */
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new LinkedHashSet<>(
+        Arrays.asList(
+            SubstringIndexUnknown.class, SubstringIndexFor.class, SubstringIndexBottom.class));
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new SubstringIndexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
+  }
+
+  /**
+   * Creates an {@link DependentTypesHelper} that allows use of addition and subtraction in the
+   * Substring Index Checker annotations.
+   */
+  @Override
+  protected DependentTypesHelper createDependentTypesHelper() {
+    return new OffsetDependentTypesHelper(this);
+  }
+
+  /**
+   * The Substring Index qualifier hierarchy. The hierarchy consists of a top element {@link
+   * UNKNOWN} of type {@link SubstringIndexUnknown}, bottom element {@link BOTTOM} of type {@link
+   * SubstringIndexBottom}, and elements of type {@link SubstringIndexFor} that follow the subtyping
+   * relation of {@link UBQualifier}.
+   */
+  private final class SubstringIndexQualifierHierarchy extends ElementQualifierHierarchy {
+
+    /**
+     * Creates a SubstringIndexQualifierHierarchy from the given classes.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers
+     * @param elements element utils
+     */
+    public SubstringIndexQualifierHierarchy(
+        Set<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+    }
+
+    @Override
+    public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
+      if (AnnotationUtils.areSame(a1, UNKNOWN)) {
+        return a2;
+      }
+      if (AnnotationUtils.areSame(a2, UNKNOWN)) {
+        return a1;
+      }
+      if (AnnotationUtils.areSame(a1, BOTTOM)) {
+        return a1;
+      }
+      if (AnnotationUtils.areSame(a2, BOTTOM)) {
+        return a2;
+      }
+      UBQualifier ubq1 =
+          UBQualifier.createUBQualifier(a1, (IndexChecker) checker.getUltimateParentChecker());
+      UBQualifier ubq2 =
+          UBQualifier.createUBQualifier(a2, (IndexChecker) checker.getUltimateParentChecker());
+      UBQualifier glb = ubq1.glb(ubq2);
+      return convertUBQualifierToAnnotation(glb);
+    }
+
+    @Override
+    public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
+      if (AnnotationUtils.areSame(a1, UNKNOWN)) {
+        return a1;
+      }
+      if (AnnotationUtils.areSame(a2, UNKNOWN)) {
+        return a2;
+      }
+      if (AnnotationUtils.areSame(a1, BOTTOM)) {
+        return a2;
+      }
+      if (AnnotationUtils.areSame(a2, BOTTOM)) {
+        return a1;
+      }
+      UBQualifier ubq1 =
+          UBQualifier.createUBQualifier(a1, (IndexChecker) checker.getUltimateParentChecker());
+      UBQualifier ubq2 =
+          UBQualifier.createUBQualifier(a2, (IndexChecker) checker.getUltimateParentChecker());
+      UBQualifier lub = ubq1.lub(ubq2);
+      return convertUBQualifierToAnnotation(lub);
+    }
+
+    @Override
+    public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+      if (areSameByClass(superAnno, SubstringIndexUnknown.class)) {
+        return true;
+      }
+      if (areSameByClass(subAnno, SubstringIndexBottom.class)) {
+        return true;
+      }
+      if (areSameByClass(subAnno, SubstringIndexUnknown.class)) {
+        return false;
+      }
+      if (areSameByClass(superAnno, SubstringIndexBottom.class)) {
+        return false;
+      }
+
+      UBQualifier subtype =
+          UBQualifier.createUBQualifier(subAnno, (IndexChecker) checker.getUltimateParentChecker());
+      UBQualifier supertype =
+          UBQualifier.createUBQualifier(
+              superAnno, (IndexChecker) checker.getUltimateParentChecker());
+      return subtype.isSubtype(supertype);
+    }
+  }
+
+  /**
+   * Converts an instance of {@link UBQualifier} to an annotation from the Substring Index
+   * hierarchy.
+   *
+   * @param qualifier the {@link UBQualifier} to be converted
+   * @return an annotation from the Substring Index hierarchy, representing {@code qualifier}
+   */
+  public AnnotationMirror convertUBQualifierToAnnotation(UBQualifier qualifier) {
+    if (qualifier.isUnknown()) {
+      return UNKNOWN;
+    } else if (qualifier.isBottom()) {
+      return BOTTOM;
+    }
+
+    LessThanLengthOf ltlQualifier = (LessThanLengthOf) qualifier;
+    return ltlQualifier.convertToSubstringIndexAnnotation(processingEnv);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexChecker.java b/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexChecker.java
new file mode 100644
index 0000000..e282522
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexChecker.java
@@ -0,0 +1,16 @@
+package org.checkerframework.checker.index.substringindex;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+import org.checkerframework.framework.source.SuppressWarningsPrefix;
+
+/**
+ * The Substring Index Checker is an internal checker that assists the Index Checker in typing the
+ * results of calls to the JDK's {@link java.lang.String#indexOf(String) String.indexOf} and {@link
+ * java.lang.String#lastIndexOf(String) String.lastIndexOf} routines.
+ *
+ * @checker_framework.manual #index-substringindex Index Checker
+ */
+@SuppressWarningsPrefix({"index", "substringindex"})
+@RelevantJavaTypes({CharSequence.class, Object[].class})
+public class SubstringIndexChecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/OffsetEquation.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/OffsetEquation.java
new file mode 100644
index 0000000..b2a49df
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/OffsetEquation.java
@@ -0,0 +1,477 @@
+package org.checkerframework.checker.index.upperbound;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Pattern;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
+import org.checkerframework.common.value.ValueCheckerUtils;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode;
+import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.Unknown;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesError;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * An offset equation is 2 sets of Java expression strings, one set of added terms and one set of
+ * subtracted terms, and a single integer constant. The Java expression strings have been
+ * standardized and viewpoint-adapted.
+ *
+ * <p>An OffsetEquation is mutable.
+ */
+public class OffsetEquation {
+  public static final OffsetEquation ZERO = createOffsetForInt(0);
+  public static final OffsetEquation NEG_1 = createOffsetForInt(-1);
+  public static final OffsetEquation ONE = createOffsetForInt(1);
+
+  private final List<String> addedTerms;
+  private final List<String> subtractedTerms;
+  private int intValue = 0;
+  private String error = null;
+
+  private OffsetEquation() {
+    addedTerms = new ArrayList<>(1);
+    subtractedTerms = new ArrayList<>(1);
+  }
+
+  /**
+   * Create a new OffsetEquation that is a copy of the given one.
+   *
+   * @param other the OffsetEquation to copy
+   */
+  protected OffsetEquation(OffsetEquation other) {
+    this.addedTerms = new ArrayList<>(other.addedTerms);
+    this.subtractedTerms = new ArrayList<>(other.subtractedTerms);
+    this.error = other.error;
+    this.intValue = other.intValue;
+  }
+
+  public boolean hasError() {
+    return error != null;
+  }
+
+  public String getError() {
+    return error;
+  }
+
+  @Override
+  public boolean equals(@Nullable Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    OffsetEquation that = (OffsetEquation) o;
+
+    if (intValue != that.intValue) {
+      return false;
+    }
+    if (addedTerms.size() != that.addedTerms.size()
+        || !addedTerms.containsAll(that.addedTerms)
+        || !that.addedTerms.containsAll(addedTerms)) {
+      return false;
+    }
+    if (subtractedTerms.size() != that.subtractedTerms.size()
+        || !subtractedTerms.containsAll(that.subtractedTerms)
+        || !that.subtractedTerms.containsAll(subtractedTerms)) {
+      return false;
+    }
+    return error != null ? error.equals(that.error) : that.error == null;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(addedTerms, subtractedTerms, intValue, error);
+  }
+
+  @Override
+  public String toString() {
+    if (addedTerms.isEmpty() && subtractedTerms.isEmpty()) {
+      return String.valueOf(intValue);
+    }
+    List<String> sortedAdds = new ArrayList<>(addedTerms);
+    Collections.sort(sortedAdds);
+
+    List<String> sortedSubs = new ArrayList<>(subtractedTerms);
+    Collections.sort(sortedSubs);
+
+    String adds = String.join(" + ", sortedAdds);
+    String minus = String.join(" - ", sortedSubs);
+    if (sortedSubs.size() == 1 && sortedAdds.isEmpty()) {
+      minus = "-" + minus;
+    } else if (!sortedSubs.isEmpty()) {
+      minus = " - " + minus;
+    }
+    String terms = (adds + minus).trim();
+    if (intValue != 0) {
+      char op = intValue > 0 ? '+' : '-';
+      if (terms.isEmpty()) {
+        terms += intValue;
+      } else {
+        terms += " " + op + " " + Math.abs(intValue);
+      }
+    }
+    return terms;
+  }
+
+  /**
+   * Makes a copy of this offset and removes any added terms that are accesses to the length of the
+   * listed sequences. If any terms were removed, then the copy is returned. Otherwise, null is
+   * returned.
+   *
+   * @param sequences list of sequences (arrays or strings)
+   * @return a copy of this equation with array.length and string.length() removed or null if no
+   *     array.lengths or string.length() could be removed
+   */
+  public OffsetEquation removeSequenceLengths(List<String> sequences) {
+    OffsetEquation copy = new OffsetEquation(this);
+    boolean simplified = false;
+    for (String sequence : sequences) {
+      String arrayLen = sequence + ".length";
+      if (addedTerms.contains(arrayLen)) {
+        copy.addedTerms.remove(arrayLen);
+        simplified = true;
+      }
+      String stringLen = sequence + ".length()";
+      if (addedTerms.contains(stringLen)) {
+        copy.addedTerms.remove(stringLen);
+        simplified = true;
+      }
+    }
+    return simplified ? copy : null;
+  }
+  /**
+   * Adds or subtracts the other equation to a copy of this one.
+   *
+   * <p>If subtraction is specified, then every term in other is subtracted.
+   *
+   * @param op '-' for subtraction or '+' for addition
+   * @param other equation to add or subtract
+   * @return a copy of this equation +/- other
+   */
+  public OffsetEquation copyAdd(char op, OffsetEquation other) {
+    assert op == '-' || op == '+';
+    OffsetEquation copy = new OffsetEquation(this);
+    if (op == '+') {
+      copy.plus(other);
+    } else {
+      copy.minus(other);
+    }
+    return copy;
+  }
+
+  private void plus(OffsetEquation eq) {
+    addInt(eq.intValue);
+    for (String term : eq.addedTerms) {
+      addTerm('+', term);
+    }
+    for (String term : eq.subtractedTerms) {
+      addTerm('-', term);
+    }
+  }
+
+  private void minus(OffsetEquation eq) {
+    addInt(-1 * eq.intValue);
+    for (String term : eq.addedTerms) {
+      addTerm('-', term);
+    }
+    for (String term : eq.subtractedTerms) {
+      addTerm('+', term);
+    }
+  }
+
+  /**
+   * Returns whether or not this equation is known to be less than or equal to the other equation.
+   *
+   * @param other equation
+   * @return whether or not this equation is known to be less than or equal to the other equation
+   */
+  public boolean lessThanOrEqual(OffsetEquation other) {
+    return (isInt() && other.isInt() && intValue <= other.getInt()) || this.equals(other);
+  }
+
+  /**
+   * Returns true if this equation is a single int value.
+   *
+   * @return true if this equation is a single int value
+   */
+  public boolean isInt() {
+    return addedTerms.isEmpty() && subtractedTerms.isEmpty();
+  }
+
+  /**
+   * Returns the int value associated with this equation.
+   *
+   * <p>The equation may or may not have other terms. Use {@link #isInt()} to determine if the
+   * equation is only this int value.
+   *
+   * @return the int value associated with this equation
+   */
+  public int getInt() {
+    return intValue;
+  }
+
+  /**
+   * Returns true if this equation is exactly -1.
+   *
+   * @return true if this equation is exactly -1
+   */
+  public boolean isNegOne() {
+    return isInt() && getInt() == -1;
+  }
+
+  /**
+   * Returns true if this equation non-negative.
+   *
+   * @return true if this equation non-negative
+   */
+  public boolean isNonNegative() {
+    return isInt() && getInt() >= 0;
+  }
+
+  /**
+   * Returns true if this equation is negative or zero.
+   *
+   * @return true if this equation is negative or zero
+   */
+  public boolean isNegativeOrZero() {
+    return isInt() && getInt() <= 0;
+  }
+
+  /**
+   * Adds the term to this equation. If string is an integer, then it is added or subtracted,
+   * depending on operator, from the int value of this equation. Otherwise, the term is placed in
+   * the added or subtracted terms set, depending on operator.
+   *
+   * @param operator '+' or '-'
+   * @param term an int value or Java expression to add to this equation
+   */
+  private void addTerm(char operator, String term) {
+    term = term.trim();
+    if (operator == '-' && term.equals("2147483648")) {
+      addInt(-2147483648);
+      return;
+    }
+    if (isInt(term)) {
+      int literal = parseInt(term);
+      addInt(operator == '-' ? -1 * literal : literal);
+      return;
+    }
+    if (operator == '-') {
+      if (addedTerms.contains(term)) {
+        addedTerms.remove(term);
+      } else {
+        subtractedTerms.add(term);
+      }
+    } else if (operator == '+') {
+      if (subtractedTerms.contains(term)) {
+        subtractedTerms.remove(term);
+      } else {
+        addedTerms.add(term);
+      }
+    } else {
+      assert false;
+    }
+  }
+
+  private void addInt(int value) {
+    intValue += value;
+  }
+
+  /**
+   * Returns the offset equation that is an int value or null if there isn't one.
+   *
+   * @param equationSet a set of offset equations
+   * @return the offset equation that is an int value or null if there isn't one
+   */
+  public static OffsetEquation getIntOffsetEquation(Set<OffsetEquation> equationSet) {
+    for (OffsetEquation eq : equationSet) {
+      if (eq.isInt()) {
+        return eq;
+      }
+    }
+    return null;
+  }
+  /**
+   * Creates an offset equation that is only the int value specified.
+   *
+   * @param value int value of the equation
+   * @return an offset equation that is only the int value specified
+   */
+  public static OffsetEquation createOffsetForInt(int value) {
+    OffsetEquation equation = new OffsetEquation();
+    equation.intValue = value;
+    return equation;
+  }
+
+  /**
+   * Creates an offset equation from the expressionEquation. The expressionEquation may be several
+   * Java expressions added or subtracted from each other. The expressionEquation may also start
+   * with + or -. If the expressionEquation is the empty string, then the offset equation returned
+   * is zero.
+   *
+   * @param expressionEquation a Java expression made up of sums and differences
+   * @return an offset equation created from expressionEquation
+   */
+  public static OffsetEquation createOffsetFromJavaExpression(String expressionEquation) {
+    expressionEquation = expressionEquation.trim();
+    OffsetEquation equation = new OffsetEquation();
+    if (expressionEquation.isEmpty()) {
+      equation.addTerm('+', "0");
+      return equation;
+    }
+
+    if (DependentTypesError.isExpressionError(expressionEquation)) {
+      equation.error = expressionEquation;
+      return equation;
+    }
+    if (indexOf(expressionEquation, '-', '+', 0) == -1) {
+      equation.addTerm('+', expressionEquation);
+      return equation;
+    }
+
+    int index = 0;
+    while (index < expressionEquation.length()) {
+      char operator = expressionEquation.charAt(index);
+      if (operator == '-' || operator == '+') {
+        index++;
+      } else {
+        operator = '+';
+      }
+
+      int endIndex = indexOf(expressionEquation, '-', '+', index);
+      String subexpression;
+      if (endIndex == -1) {
+        endIndex = expressionEquation.length();
+        subexpression = expressionEquation.substring(index);
+      } else {
+        subexpression = expressionEquation.substring(index, endIndex);
+      }
+
+      equation.addTerm(operator, subexpression);
+      index = endIndex;
+    }
+    return equation;
+  }
+
+  /** A regular expression that matches an integer literal. */
+  private static Pattern intPattern = Pattern.compile("[-+]?[0-9]+");
+
+  /**
+   * Returns true if the given string is an integer literal
+   *
+   * @param string a string
+   * @return true if the given string is an integer literal
+   */
+  private static boolean isInt(String string) {
+    return intPattern.matcher(string).matches();
+  }
+
+  private static int parseInt(String intLiteral) {
+    if (intLiteral.isEmpty()) {
+      return 0;
+    }
+    return Integer.valueOf(intLiteral);
+  }
+
+  /** Returns the first index of a or b in string, or -1 if neither char is in string. */
+  private static int indexOf(String string, char a, char b, int index) {
+    int aIndex = string.indexOf(a, index);
+    int bIndex = string.indexOf(b, index);
+    if (aIndex == -1) {
+      return bIndex;
+    } else if (bIndex == -1) {
+      return aIndex;
+    } else {
+      return Math.min(aIndex, bIndex);
+    }
+  }
+
+  /**
+   * If node is an int value known at compile time, then the returned equation is just the int value
+   * or if op is '-', the return equation is the negation of the int value.
+   *
+   * <p>Otherwise, null is returned.
+   *
+   * @param node the Node from which to create an offset equation
+   * @param factory an AnnotationTypeFactory
+   * @param op '+' or '-'
+   * @return an offset equation from value of known or null if the value isn't known
+   */
+  public static OffsetEquation createOffsetFromNodesValue(
+      Node node, ValueAnnotatedTypeFactory factory, char op) {
+    assert op == '+' || op == '-';
+    if (node.getTree() != null && TreeUtils.isExpressionTree(node.getTree())) {
+      Long i = ValueCheckerUtils.getExactValue(node.getTree(), factory);
+      if (i != null) {
+        if (op == '-') {
+          i = -i;
+        }
+        OffsetEquation eq = new OffsetEquation();
+        eq.addInt(i.intValue());
+        return eq;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Creates an offset equation from the Node.
+   *
+   * <p>If node is an addition or subtracted node, then this method is called recursively on the
+   * left and right hand nodes and the two equations are added/subtracted to each other depending on
+   * the value of op.
+   *
+   * <p>Otherwise the return equation is created by converting the node to a {@link
+   * org.checkerframework.dataflow.expression.JavaExpression} and then added as a term to the
+   * returned equation. If op is '-' then it is a subtracted term.
+   *
+   * @param node the Node from which to create an offset equation
+   * @param factory an AnnotationTypeFactory
+   * @param op '+' or '-'
+   * @return an offset equation from the Node
+   */
+  public static OffsetEquation createOffsetFromNode(
+      Node node, AnnotationProvider factory, char op) {
+    assert op == '+' || op == '-';
+    OffsetEquation eq = new OffsetEquation();
+    createOffsetFromNode(node, factory, eq, op);
+    return eq;
+  }
+
+  /**
+   * Updates an offset equation from a Node.
+   *
+   * @param node the Node from which to create an offset equation
+   * @param factory an AnnotationTypeFactory
+   * @param eq an OffsetEquation to update
+   * @param op '+' or '-'
+   */
+  private static void createOffsetFromNode(
+      Node node, AnnotationProvider factory, OffsetEquation eq, char op) {
+    JavaExpression je = JavaExpression.fromNode(node);
+    if (je instanceof Unknown || je == null) {
+      if (node instanceof NumericalAdditionNode) {
+        createOffsetFromNode(((NumericalAdditionNode) node).getLeftOperand(), factory, eq, op);
+        createOffsetFromNode(((NumericalAdditionNode) node).getRightOperand(), factory, eq, op);
+      } else if (node instanceof NumericalSubtractionNode) {
+        createOffsetFromNode(((NumericalSubtractionNode) node).getLeftOperand(), factory, eq, op);
+        char other = op == '+' ? '-' : '+';
+        createOffsetFromNode(
+            ((NumericalSubtractionNode) node).getRightOperand(), factory, eq, other);
+      } else {
+        eq.error = node.toString();
+      }
+    } else {
+      eq.addTerm(op, je.toString());
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UBQualifier.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UBQualifier.java
new file mode 100644
index 0000000..f9e40e3
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UBQualifier.java
@@ -0,0 +1,1479 @@
+package org.checkerframework.checker.index.upperbound;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.LTOMLengthOf;
+import org.checkerframework.checker.index.qual.SubstringIndexFor;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.Pair;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * Abstraction for Upper Bound annotations. This abstract class has 4 subclasses, each of which is a
+ * nested class: {@link LessThanLengthOf}, {@link UpperBoundUnknownQualifier}, {@code
+ * UpperBoundBottomQualifier}, and {@code PolyQualifier}.
+ *
+ * <p>{@link LTLengthOf} is modeled by {@link LessThanLengthOf}. {@link LTEqLengthOf} is equivalent
+ * to @{@link LessThanLengthOf} with an offset of -1. {@link LTOMLengthOf} is equivalent to @{@link
+ * LessThanLengthOf} with an offset of 1.
+ */
+public abstract class UBQualifier {
+
+  /**
+   * Create a UBQualifier from the given annotation.
+   *
+   * @param am the annotation to turn into a UBQualifier
+   * @param ubChecker used to obtain the fields of {@code am}
+   * @return a UBQualifier that represents the same information as the given annotation
+   */
+  public static UBQualifier createUBQualifier(AnnotationMirror am, UpperBoundChecker ubChecker) {
+    return createUBQualifier(am, null, ubChecker);
+  }
+
+  /**
+   * Create a UBQualifier from the given annotation, with an extra offset.
+   *
+   * @param am the annotation to turn into a UBQualifier
+   * @param offset the extra offset; may be null
+   * @param ubChecker used to obtain the fields of {@code am}
+   * @return a UBQualifier that represents the same information as the given annotation (plus an
+   *     optional offset)
+   */
+  public static UBQualifier createUBQualifier(
+      AnnotationMirror am, String offset, UpperBoundChecker ubChecker) {
+    switch (AnnotationUtils.annotationName(am)) {
+      case "org.checkerframework.checker.index.qual.UpperBoundUnknown":
+        return UpperBoundUnknownQualifier.UNKNOWN;
+      case "org.checkerframework.checker.index.qual.UpperBoundBottom":
+        return UpperBoundBottomQualifier.BOTTOM;
+      case "org.checkerframework.checker.index.qual.UpperBoundLiteral":
+        int intValue =
+            AnnotationUtils.getElementValueInt(am, ubChecker.upperBoundLiteralValueElement);
+        return UpperBoundLiteralQualifier.create(intValue);
+      case "org.checkerframework.checker.index.qual.LTLengthOf":
+        return parseLTLengthOf(am, offset, ubChecker);
+      case "org.checkerframework.checker.index.qual.SubstringIndexFor":
+        return parseSubstringIndexFor(am, offset, ubChecker);
+      case "org.checkerframework.checker.index.qual.LTEqLengthOf":
+        return parseLTEqLengthOf(am, offset, ubChecker);
+      case "org.checkerframework.checker.index.qual.LTOMLengthOf":
+        return parseLTOMLengthOf(am, offset, ubChecker);
+      case "org.checkerframework.checker.index.qual.PolyUpperBound":
+        // TODO:  Ignores offset.  Should we check that offset is not set?
+        return PolyQualifier.POLY;
+      default:
+        throw new BugInCF("createUBQualifier(%s, %s, ...)", am, offset);
+    }
+  }
+
+  /** A cache for the {@link #nCopiesEmptyStringCache} method. */
+  private static List<List<String>> nCopiesEmptyStringCache = new ArrayList<>(10);
+
+  static {
+    nCopiesEmptyStringCache.add(Collections.emptyList());
+    nCopiesEmptyStringCache.add(Collections.singletonList(""));
+    nCopiesEmptyStringCache.add(Collections.nCopies(2, ""));
+    nCopiesEmptyStringCache.add(Collections.nCopies(3, ""));
+    nCopiesEmptyStringCache.add(Collections.nCopies(4, ""));
+    nCopiesEmptyStringCache.add(Collections.nCopies(5, ""));
+    nCopiesEmptyStringCache.add(Collections.nCopies(6, ""));
+    nCopiesEmptyStringCache.add(Collections.nCopies(7, ""));
+    nCopiesEmptyStringCache.add(Collections.nCopies(8, ""));
+    nCopiesEmptyStringCache.add(Collections.nCopies(9, ""));
+  }
+
+  /**
+   * Equivalent to {@code Collections.nCopies(n, "")}.
+   *
+   * @param n the length of the list
+   * @return an immutable list of {@code n} copies of {@code ""}
+   */
+  private static List<String> nCopiesEmptyString(int n) {
+    if (n < 10) {
+      return nCopiesEmptyStringCache.get(n);
+    } else {
+      return Collections.nCopies(n, "");
+    }
+  }
+
+  /**
+   * Create a UBQualifier from a @LTLengthOf annotation.
+   *
+   * @param ltLengthOfAnno a @LTLengthOf annotation
+   * @param extraOffset the extra offset
+   * @param ubChecker used to obtain the fields of {@code am}
+   * @return a UBQualifier created from the @LTLengthOf annotation
+   */
+  private static UBQualifier parseLTLengthOf(
+      AnnotationMirror ltLengthOfAnno, String extraOffset, UpperBoundChecker ubChecker) {
+    List<String> sequences =
+        AnnotationUtils.getElementValueArray(
+            ltLengthOfAnno, ubChecker.ltLengthOfValueElement, String.class);
+    List<String> offsets =
+        AnnotationUtils.getElementValueArray(
+            ltLengthOfAnno,
+            ubChecker.ltLengthOfOffsetElement,
+            String.class,
+            nCopiesEmptyString(sequences.size()));
+    return createUBQualifier(sequences, offsets, extraOffset);
+  }
+
+  /**
+   * Create a UBQualifier from a @SubstringIndexFor annotation.
+   *
+   * @param substringIndexForAnno a @SubstringIndexFor annotation
+   * @param extraOffset the extra offset
+   * @param ubChecker used for obtaining arguments/elements from {@code substringIndexForAnno}
+   * @return a UBQualifier created from the @SubstringIndexFor annotation
+   */
+  private static UBQualifier parseSubstringIndexFor(
+      AnnotationMirror substringIndexForAnno, String extraOffset, UpperBoundChecker ubChecker) {
+    List<String> sequences =
+        AnnotationUtils.getElementValueArray(
+            substringIndexForAnno, ubChecker.substringIndexForValueElement, String.class);
+    List<String> offsets =
+        AnnotationUtils.getElementValueArray(
+            substringIndexForAnno, ubChecker.substringIndexForOffsetElement, String.class);
+    if (offsets.isEmpty()) {
+      offsets = nCopiesEmptyString(sequences.size());
+    }
+    return createUBQualifier(sequences, offsets, extraOffset);
+  }
+
+  /**
+   * Create a UBQualifier from a @LTEqLengthOf annotation.
+   *
+   * @param am a @LTEqLengthOf annotation
+   * @param extraOffset the extra offset
+   * @param ubChecker used for obtaining fields from {@code am}
+   * @return a UBQualifier created from the @LTEqLengthOf annotation
+   */
+  private static UBQualifier parseLTEqLengthOf(
+      AnnotationMirror am, String extraOffset, UpperBoundChecker ubChecker) {
+    List<String> sequences =
+        AnnotationUtils.getElementValueArray(am, ubChecker.ltEqLengthOfValueElement, String.class);
+    if (sequences.isEmpty()) {
+      // How did this AnnotationMirror even get made?  It seems invalid.
+      return UpperBoundUnknownQualifier.UNKNOWN;
+    }
+    List<String> offset = Collections.nCopies(sequences.size(), "-1");
+    return createUBQualifier(sequences, offset, extraOffset);
+  }
+
+  /**
+   * Create a UBQualifier from a @LTOMLengthOf annotation.
+   *
+   * @param am a @LTOMLengthOf annotation
+   * @param extraOffset offset to add to each element of offsets; may be null
+   * @param ubChecker used for obtaining fields from {@code am}
+   * @return a UBQualifier created from the @LTOMLengthOf annotation
+   */
+  private static UBQualifier parseLTOMLengthOf(
+      AnnotationMirror am, String extraOffset, UpperBoundChecker ubChecker) {
+    List<String> sequences =
+        AnnotationUtils.getElementValueArray(am, ubChecker.ltOMLengthOfValueElement, String.class);
+    List<String> offset = Collections.nCopies(sequences.size(), "1");
+    return createUBQualifier(sequences, offset, extraOffset);
+  }
+
+  public static UBQualifier createUBQualifier(String sequence, String offset) {
+    return createUBQualifier(
+        Collections.singletonList(sequence), Collections.singletonList(offset));
+  }
+
+  /**
+   * Create an upper bound qualifier.
+   *
+   * @param type the type from which to obtain an annotation
+   * @param top the top annotation in a hierarchy; the annotation in this hierarchy will be used
+   * @param ubChecker used to obtain the fields of {@code am}
+   * @return a new upper bound qualifier
+   */
+  public static UBQualifier createUBQualifier(
+      AnnotatedTypeMirror type, AnnotationMirror top, UpperBoundChecker ubChecker) {
+    return createUBQualifier(type.getEffectiveAnnotationInHierarchy(top), ubChecker);
+  }
+
+  /**
+   * Creates an {@link UBQualifier} from the given sequences and offsets. The list of sequences may
+   * not be empty. If the offsets list is empty, then an offset of 0 is used for each sequence. If
+   * the offsets list is not empty, then it must be the same size as sequence.
+   *
+   * @param sequences non-empty list of sequences
+   * @param offsets list of offset, if empty, an offset of 0 is used
+   * @return an {@link UBQualifier} for the sequences with the given offsets
+   */
+  public static UBQualifier createUBQualifier(List<String> sequences, List<String> offsets) {
+    return createUBQualifier(sequences, offsets, null);
+  }
+
+  /**
+   * Creates an {@link UBQualifier} from the given sequences and offsets, with the given additional
+   * offset. The list of sequences may not be empty. If the offsets list is empty, then an offset of
+   * 0 is used for each sequence. If the offsets list is not empty, then it must be the same size as
+   * sequence.
+   *
+   * @param sequences non-empty list of sequences
+   * @param offsets list of offset, if empty, an offset of 0 is used
+   * @param extraOffset offset to add to each element of offsets; may be null
+   * @return an {@link UBQualifier} for the sequences with the given offsets
+   */
+  public static UBQualifier createUBQualifier(
+      List<String> sequences, List<String> offsets, String extraOffset) {
+    assert !sequences.isEmpty();
+
+    OffsetEquation extraEq;
+    if (extraOffset == null) {
+      extraEq = OffsetEquation.ZERO;
+    } else {
+      extraEq = OffsetEquation.createOffsetFromJavaExpression(extraOffset);
+      if (extraEq.hasError()) {
+        return UpperBoundUnknownQualifier.UNKNOWN;
+      }
+    }
+
+    return new LessThanLengthOf(sequences, offsets, extraEq);
+  }
+
+  /**
+   * Add the node as an offset to a copy of this qualifier. If this qualifier is UNKNOWN or BOTTOM,
+   * then UNKNOWN is returned. Otherwise, see {@link LessThanLengthOf#plusOffset(int)} for an
+   * explanation of how node is added as an offset.
+   *
+   * @param node a Node
+   * @param factory an AnnotatedTypeFactory
+   * @return a copy of this qualifier with node added as an offset
+   */
+  public UBQualifier plusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) {
+    return UpperBoundUnknownQualifier.UNKNOWN;
+  }
+
+  public UBQualifier plusOffset(int value) {
+    return UpperBoundUnknownQualifier.UNKNOWN;
+  }
+
+  public UBQualifier minusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) {
+    return UpperBoundUnknownQualifier.UNKNOWN;
+  }
+
+  public UBQualifier minusOffset(int value) {
+    return UpperBoundUnknownQualifier.UNKNOWN;
+  }
+
+  public boolean isLessThanLengthQualifier() {
+    return false;
+  }
+
+  /**
+   * Returns true if this UBQualifier represents a literal integer.
+   *
+   * @return true if this UBQualifier represents a literal integer
+   */
+  public boolean isLiteral() {
+    return false;
+  }
+
+  /**
+   * Returns true if this UBQualifier is the top type.
+   *
+   * @return true if this UBQualifier is the top type
+   */
+  public boolean isUnknown() {
+    return false;
+  }
+
+  /**
+   * Returns true if this UBQualifier is the bottom type.
+   *
+   * @return true if this UBQualifier is the bottom type
+   */
+  public boolean isBottom() {
+    return false;
+  }
+
+  /**
+   * Return true if this is UBQualifier.PolyQualifier.
+   *
+   * @return true if this is UBQualifier.PolyQualifier
+   */
+  @Pure
+  public boolean isPoly() {
+    return false;
+  }
+
+  public abstract boolean isSubtype(UBQualifier superType);
+
+  public abstract UBQualifier lub(UBQualifier other);
+
+  public UBQualifier widenUpperBound(UBQualifier obj) {
+    return lub(obj);
+  }
+
+  public abstract UBQualifier glb(UBQualifier other);
+
+  /**
+   * Is the value with this qualifier less than the length of sequence?
+   *
+   * @param sequence a String sequence
+   * @return whether or not the value with this qualifier is less than the length of sequence
+   */
+  public boolean isLessThanLengthOf(String sequence) {
+    return false;
+  }
+
+  /**
+   * Is the value with this qualifier less than the length of any of the sequences?
+   *
+   * @param sequences list of sequences
+   * @return whether or not the value with this qualifier is less than the length of any of the
+   *     sequences
+   */
+  public boolean isLessThanLengthOfAny(List<String> sequences) {
+    return false;
+  }
+
+  /**
+   * Returns whether or not this qualifier has sequence with the specified offset.
+   *
+   * @param sequence sequence expression
+   * @param offset the offset being looked for
+   * @return whether or not this qualifier has sequence with the specified offset
+   */
+  public boolean hasSequenceWithOffset(String sequence, int offset) {
+    return false;
+  }
+
+  /**
+   * Returns whether or not this qualifier has sequence with the specified offset.
+   *
+   * @param sequence sequence expression
+   * @param offset the offset being looked for
+   * @return whether or not this qualifier has sequence with the specified offset
+   */
+  public boolean hasSequenceWithOffset(String sequence, String offset) {
+    return false;
+  }
+
+  /**
+   * Is the value with this qualifier less than or equal to the length of sequence?
+   *
+   * @param sequence a String sequence
+   * @return whether or not the value with this qualifier is less than or equal to the length of
+   *     sequence
+   */
+  public boolean isLessThanOrEqualTo(String sequence) {
+    return false;
+  }
+
+  /** The less-than-length-of qualifier (@LTLengthOf). */
+  public static class LessThanLengthOf extends UBQualifier {
+
+    // There are two representations for sequences and offsets.
+    // In source code, they are represented by two parallel arrays, as in
+    //   @LTLengthOf(value = {"a", "b", "a", "c"}, offset = {"-1", "x", "y", "0"}).
+    // In this implementation, they are represented by a single map; the above would be
+    //   { "a" : {"-1", "y"}, "b" : {"x"}, "c" : {"0"} }
+    // Code in this class transforms from one representation to the other.
+
+    /** Maps from sequence name to offset. */
+    private final Map<String, Set<OffsetEquation>> map;
+
+    /**
+     * Returns a copy of the map.
+     *
+     * @return a copy of the map
+     */
+    private Map<String, Set<OffsetEquation>> copyMap() {
+      Map<String, Set<OffsetEquation>> result = new HashMap<>(CollectionsPlume.mapCapacity(map));
+      for (String sequenceName : map.keySet()) {
+        Set<OffsetEquation> oldEquations = map.get(sequenceName);
+        Set<OffsetEquation> newEquations =
+            new HashSet<>(CollectionsPlume.mapCapacity(oldEquations));
+        for (OffsetEquation offsetEquation : oldEquations) {
+          newEquations.add(new OffsetEquation(offsetEquation));
+        }
+        result.put(sequenceName, newEquations);
+      }
+      return result;
+    }
+
+    /**
+     * Returns true if the given integer literal is a subtype of this. The literal is a subtype of
+     * this if, for every offset expression, {@code literal + offset <= -1}.
+     *
+     * @param i an integer
+     * @return true if the given integer literal is a subtype of this
+     */
+    /*package-protected*/ boolean literalIsSubtype(int i) {
+      for (Map.Entry<String, Set<OffsetEquation>> entry : map.entrySet()) {
+        for (OffsetEquation equation : entry.getValue()) {
+          if (!equation.isInt()) {
+            return false;
+          }
+          int offset = equation.getInt();
+          if (i + offset > -1) {
+            return false;
+          }
+        }
+      }
+      return true;
+    }
+
+    /**
+     * Convert the parallel array representation to the map representation.
+     *
+     * @param sequences non-empty list of sequences
+     * @param offsets list of offset, if empty, an offset of 0 is used
+     * @param extraEq offset to add to each element of offsets; may be null
+     * @return the map representation of a {@link UBQualifier}, or null if there is an error
+     */
+    private static @Nullable Map<String, Set<OffsetEquation>> sequencesAndOffsetsToMap(
+        List<String> sequences, List<String> offsets, OffsetEquation extraEq) {
+
+      Map<String, Set<OffsetEquation>> map = new HashMap<>(CollectionsPlume.mapCapacity(sequences));
+      if (offsets.isEmpty()) {
+        for (String sequence : sequences) {
+          // Not `Collections.singleton(extraEq)` because the values get modified
+          Set<OffsetEquation> thisSet = new HashSet<>(1);
+          thisSet.add(extraEq);
+          map.put(sequence, thisSet);
+        }
+      } else {
+        assert sequences.size() == offsets.size();
+        for (int i = 0; i < sequences.size(); i++) {
+          String sequence = sequences.get(i);
+          String offset = offsets.get(i);
+          Set<OffsetEquation> set = map.computeIfAbsent(sequence, __ -> new HashSet<>());
+          OffsetEquation eq = OffsetEquation.createOffsetFromJavaExpression(offset);
+          if (eq.hasError()) {
+            return null;
+          }
+          eq = eq.copyAdd('+', extraEq);
+          set.add(eq);
+        }
+      }
+      return map;
+    }
+
+    /** A triple that is the return type of {@link #mapToSequencesAndOffsets}. */
+    private static class SequencesOffsetsAndClass {
+      /** List of sequences. */
+      public final List<String> sequences;
+      /** List of offsets. */
+      public final List<String> offsets;
+      /** The class of the annotation to be built. */
+      public final Class<? extends Annotation> annoClass;
+
+      /**
+       * Creates a new SequencesOffsetsAndClass.
+       *
+       * @param sequences list of sequences
+       * @param offsets list of offsets
+       * @param annoClass the class of the annotation to be built
+       */
+      public SequencesOffsetsAndClass(
+          List<String> sequences, List<String> offsets, Class<? extends Annotation> annoClass) {
+
+        this.sequences = sequences;
+        this.offsets = offsets;
+        this.annoClass = annoClass;
+      }
+    }
+
+    /**
+     * Given the map representation, returns parallel-arrays representation.
+     *
+     * @param map the internal representation of LessThanLengthOf
+     * @param buildSubstringIndexAnnotation if true, the annoClass in the result is
+     *     ubstringIndexFor.class
+     * @return the external representation
+     */
+    private static SequencesOffsetsAndClass mapToSequencesAndOffsets(
+        Map<String, Set<OffsetEquation>> map, boolean buildSubstringIndexAnnotation) {
+      List<String> sortedSequences = new ArrayList<>(map.keySet());
+      Collections.sort(sortedSequences);
+      List<String> sequences = new ArrayList<>();
+      List<String> offsets = new ArrayList<>();
+      boolean isLTEq = true;
+      boolean isLTOM = true;
+      for (String sequence : sortedSequences) {
+        // The offsets for this sequence.
+        List<String> thisOffsets = new ArrayList<>();
+        for (OffsetEquation eq : map.get(sequence)) {
+          isLTEq = isLTEq && eq.equals(OffsetEquation.NEG_1);
+          isLTOM = isLTOM && eq.equals(OffsetEquation.ONE);
+          thisOffsets.add(eq.toString());
+        }
+        Collections.sort(thisOffsets);
+        for (String offset : thisOffsets) {
+          sequences.add(sequence);
+          offsets.add(offset);
+        }
+      }
+      Class<? extends Annotation> annoClass;
+      if (buildSubstringIndexAnnotation) {
+        annoClass = SubstringIndexFor.class;
+      } else if (isLTEq) {
+        annoClass = LTEqLengthOf.class;
+      } else if (isLTOM) {
+        annoClass = LTOMLengthOf.class;
+      } else {
+        annoClass = LTLengthOf.class;
+      }
+      return new SequencesOffsetsAndClass(sequences, offsets, annoClass);
+    }
+
+    // End of code for manipulating the representation
+
+    /**
+     * Create a new LessThanLengthOf, from the internal representation.
+     *
+     * @param map a map from sequence name to offse
+     */
+    private LessThanLengthOf(Map<String, Set<OffsetEquation>> map) {
+      assert !map.isEmpty();
+      this.map = map;
+    }
+
+    /**
+     * Create a new LessThanLengthOf from the parallel array representation.
+     *
+     * @param sequences non-empty list of sequences
+     * @param offsets list of offset, if empty, an offset of 0 is used
+     * @param extraEq offset to add to each element of offsets; may be null
+     */
+    private LessThanLengthOf(List<String> sequences, List<String> offsets, OffsetEquation extraEq) {
+      this(sequencesAndOffsetsToMap(sequences, offsets, extraEq));
+    }
+
+    @Override
+    public boolean hasSequenceWithOffset(String sequence, int offset) {
+      Set<OffsetEquation> offsets = map.get(sequence);
+      if (offsets == null) {
+        return false;
+      }
+      return offsets.contains(OffsetEquation.createOffsetForInt(offset));
+    }
+
+    @Override
+    public boolean hasSequenceWithOffset(String sequence, String offset) {
+      Set<OffsetEquation> offsets = map.get(sequence);
+      if (offsets == null) {
+        return false;
+      }
+      OffsetEquation target = OffsetEquation.createOffsetFromJavaExpression(offset);
+      return offsets.contains(target);
+    }
+
+    /**
+     * Is a value with this type less than or equal to the length of sequence?
+     *
+     * @param sequence a String sequence
+     * @return true if a value with this type is less than or equal to the length of sequence
+     */
+    @Override
+    public boolean isLessThanOrEqualTo(String sequence) {
+      return isLessThanLengthOf(sequence) || hasSequenceWithOffset(sequence, -1);
+    }
+
+    /**
+     * Is a value with this type less than the length of any of the sequences?
+     *
+     * @param sequences list of sequences
+     * @return true if a value with this type is less than the length of any of the sequences
+     */
+    @Override
+    public boolean isLessThanLengthOfAny(List<String> sequences) {
+      for (String sequence : sequences) {
+        if (isLessThanLengthOf(sequence)) {
+          return true;
+        }
+      }
+      return false;
+    }
+    /**
+     * Is a value with this type less than the length of the sequence?
+     *
+     * @param sequence a String sequence
+     * @return true if a value with this type is less than the length of the sequence
+     */
+    @Override
+    public boolean isLessThanLengthOf(String sequence) {
+      Set<OffsetEquation> offsets = map.get(sequence);
+      if (offsets == null) {
+        return false;
+      }
+      if (offsets.isEmpty()) {
+        return true;
+      }
+      for (OffsetEquation offset : offsets) {
+        if (offset.isNonNegative()) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    /**
+     * Returns the AnnotationMirror that represents this qualifier. If possible, AnnotationMirrors
+     * using @{@link LTEqLengthOf} or @{@link LTOMLengthOf} are returned. Otherwise, @{@link
+     * LTLengthOf} is used.
+     *
+     * <p>The returned annotation is canonicalized by sorting its arguments by sequence and then
+     * offset. This is so that {@link AnnotationUtils#areSame(AnnotationMirror, AnnotationMirror)}
+     * returns true for equivalent annotations.
+     *
+     * @param env a processing environment used to build the returned annotation
+     * @return the AnnotationMirror that represents this qualifier
+     */
+    public AnnotationMirror convertToAnnotation(ProcessingEnvironment env) {
+      return convertToAnnotation(env, false);
+    }
+
+    /**
+     * Returns the @{@link SubstringIndexFor} AnnotationMirror from the Substring Index hierarchy
+     * that imposes the same upper bounds on the annotated expression as this qualifier. However,
+     * the upper bounds represented by this qualifier do not apply to the value -1 which is always
+     * allowed by the returned annotation.
+     *
+     * @param env a processing environment used to build the returned annotation
+     * @return the AnnotationMirror from the Substring Index hierarchy that represents the same
+     *     upper bounds as this qualifier
+     */
+    public AnnotationMirror convertToSubstringIndexAnnotation(ProcessingEnvironment env) {
+      return convertToAnnotation(env, true);
+    }
+
+    /**
+     * Helper method called by {@link #convertToAnnotation} and {@link
+     * convertToSubstringIndexAnnotation} that does the real work.
+     *
+     * @param env a processing environment used to build the returned annotation
+     * @param buildSubstringIndexAnnotation if true, act like {@link
+     *     #convertToSubstringIndexAnnotation} and return a @{@link SubstringIndexFor} annotation;
+     *     if false, act like {@link #convertToAnnotation}
+     * @return the AnnotationMirror that represents the same upper bounds as this qualifier
+     */
+    private AnnotationMirror convertToAnnotation(
+        ProcessingEnvironment env, boolean buildSubstringIndexAnnotation) {
+      SequencesOffsetsAndClass soc = mapToSequencesAndOffsets(map, buildSubstringIndexAnnotation);
+      List<String> sequences = soc.sequences;
+      List<String> offsets = soc.offsets;
+      Class<? extends Annotation> annoClass = soc.annoClass;
+
+      AnnotationBuilder builder = new AnnotationBuilder(env, annoClass);
+      if (annoClass == SubstringIndexFor.class) {
+        builder.setValue("value", sequences);
+        builder.setValue("offset", offsets);
+      } else if (annoClass == LTEqLengthOf.class) {
+        builder.setValue("value", sequences);
+      } else if (annoClass == LTOMLengthOf.class) {
+        builder.setValue("value", sequences);
+      } else if (annoClass == LTLengthOf.class) {
+        builder.setValue("value", sequences);
+        builder.setValue("offset", offsets);
+      } else {
+        throw new BugInCF("What annoClass? " + annoClass);
+      }
+      return builder.build();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+
+      LessThanLengthOf qualifier = (LessThanLengthOf) o;
+      if (containsSame(map.keySet(), qualifier.map.keySet())) {
+        for (Map.Entry<String, Set<OffsetEquation>> entry : map.entrySet()) {
+          Set<OffsetEquation> otherOffset = qualifier.map.get(entry.getKey());
+          Set<OffsetEquation> thisOffset = entry.getValue();
+          if (!containsSame(otherOffset, thisOffset)) {
+            return false;
+          }
+        }
+        return true;
+      }
+      return false;
+    }
+
+    private static <T> boolean containsSame(Set<T> set1, Set<T> set2) {
+      return set1.containsAll(set2) && set2.containsAll(set1);
+    }
+
+    @Override
+    public int hashCode() {
+      return map.hashCode();
+    }
+
+    @Override
+    public boolean isLessThanLengthQualifier() {
+      return true;
+    }
+
+    /**
+     * If superType is Unknown, return true. If superType is Bottom, return false.
+     *
+     * <p>Otherwise, return true if this qualifier contains all the sequences in superType, AND for
+     * each of the offsets for each sequence in superType, there is an offset in this qualifier for
+     * the sequence that is greater than or equal to the super offset.
+     *
+     * @param superType other qualifier
+     * @return whether this qualifier is a subtype of superType
+     */
+    @Override
+    public boolean isSubtype(UBQualifier superType) {
+      if (superType.isUnknown()) {
+        return true;
+      } else if (superType.isBottom()) {
+        return false;
+      } else if (superType.isLiteral()) {
+        return false;
+      }
+
+      LessThanLengthOf superTypeLTL = (LessThanLengthOf) superType;
+
+      if (!map.keySet().containsAll(superTypeLTL.map.keySet())) {
+        return false;
+      }
+      for (Map.Entry<String, Set<OffsetEquation>> entry : superTypeLTL.map.entrySet()) {
+        String sequence = entry.getKey();
+        Set<OffsetEquation> superOffsets = entry.getValue();
+        Set<OffsetEquation> subOffsets = map.get(sequence);
+
+        if (!isSubtypeOffset(subOffsets, superOffsets)) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+    /**
+     * One set of offsets is a subtype of another if for every superOffsets, at least one suboffset
+     * is greater than or equal to the superOffset.
+     */
+    private boolean isSubtypeOffset(
+        Set<OffsetEquation> subOffsets, Set<OffsetEquation> superOffsets) {
+      for (OffsetEquation superOffset : superOffsets) {
+        boolean oneIsSubtype = false;
+        for (OffsetEquation subOffset : subOffsets) {
+          if (superOffset.lessThanOrEqual(subOffset)) {
+            oneIsSubtype = true;
+            break;
+          }
+        }
+        if (!oneIsSubtype) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /**
+     * If other is Unknown, return Unknown. If other is Bottom, return this.
+     *
+     * <p>Otherwise lub is computed as follows:
+     *
+     * <p>1. Create the intersection of the sets of arrays for this and other.
+     *
+     * <p>2. For each sequence in the intersection, get the offsets for this and other. If any
+     * offset in this is a less than or equal to an offset in other, then that offset is an offset
+     * for the sequence in lub. If any offset in other is a less than or equal to an offset in this,
+     * then that offset is an offset for the sequence in lub.
+     *
+     * @param other to lub with this
+     * @return the lub
+     */
+    @Override
+    public UBQualifier lub(UBQualifier other) {
+      if (other.isUnknown()) {
+        return other;
+      } else if (other.isBottom()) {
+        return this;
+      } else if (other.isLiteral()) {
+        return other.lub(this);
+      }
+      LessThanLengthOf otherLtl = (LessThanLengthOf) other;
+
+      Set<String> sequences = new HashSet<>(map.keySet());
+      sequences.retainAll(otherLtl.map.keySet());
+
+      Map<String, Set<OffsetEquation>> lubMap =
+          new HashMap<>(CollectionsPlume.mapCapacity(sequences));
+      for (String sequence : sequences) {
+        Set<OffsetEquation> offsets1 = map.get(sequence);
+        Set<OffsetEquation> offsets2 = otherLtl.map.get(sequence);
+        Set<OffsetEquation> lub = new HashSet<>(offsets1.size() + offsets2.size());
+        for (OffsetEquation offset1 : offsets1) {
+          for (OffsetEquation offset2 : offsets2) {
+            if (offset2.lessThanOrEqual(offset1)) {
+              lub.add(offset2);
+            } else if (offset1.lessThanOrEqual(offset2)) {
+              lub.add(offset1);
+            }
+          }
+        }
+        if (!lub.isEmpty()) {
+          lubMap.put(sequence, lub);
+        }
+      }
+      if (lubMap.isEmpty()) {
+        return UpperBoundUnknownQualifier.UNKNOWN;
+      }
+      return new LessThanLengthOf(lubMap);
+    }
+
+    @Override
+    public UBQualifier widenUpperBound(UBQualifier obj) {
+      UBQualifier lub = lub(obj);
+      if (!lub.isLessThanLengthQualifier() || !obj.isLessThanLengthQualifier()) {
+        return lub;
+      }
+      Map<String, Set<OffsetEquation>> lubMap = ((LessThanLengthOf) lub).map;
+      widenLub((LessThanLengthOf) obj, lubMap);
+      if (lubMap.isEmpty()) {
+        return UpperBoundUnknownQualifier.UNKNOWN;
+      }
+      return new LessThanLengthOf(lubMap);
+    }
+
+    /**
+     *
+     *
+     * <pre>@LTLengthOf("a") int i = ...;
+     * while (expr) {
+     *   i++;
+     * }</pre>
+     *
+     * <p>Dataflow never stops analyzing the above loop, because the type of i always changes after
+     * each analysis of the loop:
+     *
+     * <p>1. @LTLengthOf(value="a', offset="-1")
+     *
+     * <p>2. @LTLengthOf(value="a', offset="-2")
+     *
+     * <p>3. @LTLengthOf(value="a', offset="-3")
+     *
+     * <p>In order to prevent this, if both types passed to lub include all the same sequences with
+     * the same non-constant value offsets and if the constant value offsets are different then
+     * remove that sequence-offset pair from lub.
+     *
+     * <p>For example:
+     *
+     * <p>LUB @LTLengthOf(value={"a", "b"}, offset={"0", "0") and @LTLengthOf(value={"a", "b"},
+     * offset={"-20", "0") is @LTLengthOf("b")
+     *
+     * <p>This widened lub should only be used in order to break dataflow analysis loops.
+     */
+    private void widenLub(LessThanLengthOf other, Map<String, Set<OffsetEquation>> lubMap) {
+      if (!containsSame(this.map.keySet(), lubMap.keySet())
+          || !containsSame(other.map.keySet(), lubMap.keySet())) {
+        return;
+      }
+      List<Pair<String, OffsetEquation>> remove = new ArrayList<>();
+      for (Map.Entry<String, Set<OffsetEquation>> entry : lubMap.entrySet()) {
+        String sequence = entry.getKey();
+        Set<OffsetEquation> lubOffsets = entry.getValue();
+        Set<OffsetEquation> thisOffsets = this.map.get(sequence);
+        Set<OffsetEquation> otherOffsets = other.map.get(sequence);
+        if (lubOffsets.size() != thisOffsets.size() || lubOffsets.size() != otherOffsets.size()) {
+          return;
+        }
+        for (OffsetEquation lubEq : lubOffsets) {
+          if (lubEq.isInt()) {
+            int thisInt = OffsetEquation.getIntOffsetEquation(thisOffsets).getInt();
+            int otherInt = OffsetEquation.getIntOffsetEquation(otherOffsets).getInt();
+            if (thisInt != otherInt) {
+              remove.add(Pair.of(sequence, lubEq));
+            }
+          } else if (thisOffsets.contains(lubEq) && otherOffsets.contains(lubEq)) {
+            //  continue;
+          } else {
+            return;
+          }
+        }
+      }
+      for (Pair<String, OffsetEquation> pair : remove) {
+        Set<OffsetEquation> offsets = lubMap.get(pair.first);
+        offsets.remove(pair.second);
+        if (offsets.isEmpty()) {
+          lubMap.remove(pair.first);
+        }
+      }
+    }
+
+    @Override
+    public UBQualifier glb(UBQualifier other) {
+      if (other.isUnknown()) {
+        return this;
+      } else if (other.isBottom()) {
+        return other;
+      } else if (other.isLiteral()) {
+        return other.glb(this);
+      }
+      LessThanLengthOf otherLtl = (LessThanLengthOf) other;
+
+      Set<String> sequences = new HashSet<>(map.keySet());
+      sequences.addAll(otherLtl.map.keySet());
+
+      Map<String, Set<OffsetEquation>> glbMap = new HashMap<>(sequences.size());
+      for (String sequence : sequences) {
+        Set<OffsetEquation> glb = map.get(sequence);
+        Set<OffsetEquation> otherglb = otherLtl.map.get(sequence);
+        if (glb == null) {
+          glb = otherglb;
+        } else if (otherglb != null) {
+          glb.addAll(otherglb);
+        }
+        glbMap.put(sequence, removeSmallerInts(glb));
+      }
+      return new LessThanLengthOf(glbMap);
+    }
+
+    /**
+     * Returns a copy of the argument, but it contains just one offset equation that is an int value
+     * -- the largest one in the argument. Any non-int offset equations appear in the result. Does
+     * not side effect its argument.
+     *
+     * @param offsets a set of offset equations
+     * @return a copy of the argument with just one int value (the largest in the input) and
+     *     arbitrarily many non-ints
+     */
+    private Set<OffsetEquation> removeSmallerInts(Set<OffsetEquation> offsets) {
+      Set<OffsetEquation> newOff = new HashSet<>(offsets.size());
+      OffsetEquation literal = null;
+      for (OffsetEquation eq : offsets) {
+        if (eq.isInt()) {
+          if (literal == null) {
+            literal = eq;
+          } else {
+            literal = literal.lessThanOrEqual(eq) ? eq : literal;
+          }
+        } else {
+          newOff.add(eq);
+        }
+      }
+      if (literal != null) {
+        newOff.add(literal);
+      }
+      return newOff;
+    }
+
+    /**
+     * Adds node as an offset to a copy of this qualifier. This is done by creating an offset
+     * equation for node and then adding that equation to every offset equation in a copy of this
+     * object.
+     *
+     * @param node a Node
+     * @param factory an AnnotatedTypeFactory
+     * @return a copy of this qualifier with node add as an offset
+     */
+    @Override
+    public UBQualifier plusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) {
+      return plusOrMinusOffset(node, factory, '+');
+    }
+
+    /**
+     * Adds node as a negative offset to a copy of this qualifier. This is done by creating a
+     * negative offset equation for node and then adding that equation to every offset equation in a
+     * copy of this object.
+     *
+     * @param node a Node
+     * @param factory an AnnotatedTypeFactory
+     * @return a copy of this qualifier with node add as an offset
+     */
+    @Override
+    public UBQualifier minusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) {
+      return plusOrMinusOffset(node, factory, '-');
+    }
+
+    /**
+     * Adds node as a positive or negative offset to a copy of this qualifier. This is done by
+     * creating an offset equation for node and then adding or subtracting that equation to every
+     * offset equation in a copy of this object.
+     *
+     * @param node a Node
+     * @param factory an AnnotatedTypeFactory
+     * @param op either '-' or '+'
+     * @return a copy of this qualifier with node add as an offset
+     */
+    private UBQualifier plusOrMinusOffset(
+        Node node, UpperBoundAnnotatedTypeFactory factory, char op) {
+      assert op == '-' || op == '+';
+
+      // Try treating the offset as both an OffsetEquation and as a value.
+      // Use whichever is not null, or glb the two.
+
+      OffsetEquation newOffset = OffsetEquation.createOffsetFromNode(node, factory, op);
+      LessThanLengthOf nodeOffsetQualifier = null;
+      if (!newOffset.hasError()) {
+        UBQualifier nodeOffsetQualifierMaybe = addOffset(newOffset);
+        if (!(nodeOffsetQualifierMaybe instanceof UpperBoundUnknownQualifier)) {
+          nodeOffsetQualifier = (LessThanLengthOf) nodeOffsetQualifierMaybe;
+        }
+      }
+
+      OffsetEquation valueOffset =
+          OffsetEquation.createOffsetFromNodesValue(
+              node, factory.getValueAnnotatedTypeFactory(), op);
+      LessThanLengthOf valueOffsetQualifier = null;
+      if (valueOffset != null && !valueOffset.hasError()) {
+        UBQualifier valueOffsetQualifierMaybe = addOffset(valueOffset);
+        if (!(valueOffsetQualifierMaybe instanceof UpperBoundUnknownQualifier)) {
+          valueOffsetQualifier = (LessThanLengthOf) valueOffsetQualifierMaybe;
+        }
+      }
+
+      if (valueOffsetQualifier == null) {
+        if (nodeOffsetQualifier == null) {
+          return UpperBoundUnknownQualifier.UNKNOWN;
+        } else {
+          return nodeOffsetQualifier;
+        }
+      } else {
+        if (nodeOffsetQualifier == null) {
+          return valueOffsetQualifier;
+        } else {
+          return nodeOffsetQualifier.glb(valueOffsetQualifier);
+        }
+      }
+    }
+
+    /**
+     * Adds value as an offset to a copy of this qualifier. This is done by adding value to every
+     * offset equation in a copy of this object.
+     *
+     * @param value int value to add
+     * @return a copy of this qualifier with value add as an offset
+     */
+    @Override
+    public UBQualifier plusOffset(int value) {
+      OffsetEquation newOffset = OffsetEquation.createOffsetForInt(value);
+      return addOffset(newOffset);
+    }
+
+    /**
+     * Adds the negation of value as an offset to a copy of this qualifier. This is done by adding
+     * the negation of {@code value} to every offset equation in a copy of this object.
+     *
+     * @param value int value to add
+     * @return a copy of this qualifier with value add as an offset
+     */
+    @Override
+    public UBQualifier minusOffset(int value) {
+      OffsetEquation newOffset = OffsetEquation.createOffsetForInt(-value);
+      return addOffset(newOffset);
+    }
+
+    /**
+     * Returns a copy of this qualifier with sequence-offset pairs where in the original the offset
+     * contains an access of an sequence length in {@code sequences}. The sequence length access has
+     * been removed from the offset. If the original qualifier has no sequence length offsets, then
+     * UNKNOWN is returned.
+     *
+     * @param sequences access of the length of these sequences are removed
+     * @return a copy of this qualifier with some offsets removed
+     */
+    public UBQualifier removeSequenceLengthAccess(final List<String> sequences) {
+      if (sequences.isEmpty()) {
+        return UpperBoundUnknownQualifier.UNKNOWN;
+      }
+      OffsetEquationFunction removeSequenceLengthsFunc =
+          new OffsetEquationFunction() {
+            @Override
+            public OffsetEquation compute(OffsetEquation eq) {
+              return eq.removeSequenceLengths(sequences);
+            }
+          };
+      return computeNewOffsets(removeSequenceLengthsFunc);
+    }
+    /**
+     * Returns a copy of this qualifier with sequence-offset pairs where in the original the offset
+     * contains an access of an sequence length in {@code sequences}. The sequence length access has
+     * been removed from the offset. If the offset also has -1 then -1 is also removed.
+     *
+     * @param sequences access of the length of these sequences are removed
+     * @return a copy of this qualifier with some offsets removed
+     */
+    public UBQualifier removeSequenceLengthAccessAndNeg1(final List<String> sequences) {
+      if (sequences.isEmpty()) {
+        return UpperBoundUnknownQualifier.UNKNOWN;
+      }
+      OffsetEquationFunction removeSequenceLenFunc =
+          new OffsetEquationFunction() {
+            @Override
+            public OffsetEquation compute(OffsetEquation eq) {
+              OffsetEquation newEq = eq.removeSequenceLengths(sequences);
+              if (newEq == null) {
+                return null;
+              }
+              if (newEq.getInt() == -1) {
+                return newEq.copyAdd('+', OffsetEquation.ONE);
+              }
+              return newEq;
+            }
+          };
+      return computeNewOffsets(removeSequenceLenFunc);
+    }
+
+    private UBQualifier addOffset(final OffsetEquation newOffset) {
+      OffsetEquationFunction addOffsetFunc =
+          new OffsetEquationFunction() {
+            @Override
+            public OffsetEquation compute(OffsetEquation eq) {
+              return eq.copyAdd('+', newOffset);
+            }
+          };
+      return computeNewOffsets(addOffsetFunc);
+    }
+
+    /**
+     * If divisor == 1, return this object.
+     *
+     * <p>If divisor greater than 1, then return a copy of this object keeping only sequences and
+     * offsets where the offset is less than or equal to zero.
+     *
+     * <p>Otherwise, return UNKNOWN.
+     *
+     * @param divisor number to divide by
+     * @return the result of dividing a value with this qualifier by divisor
+     */
+    public UBQualifier divide(int divisor) {
+      if (divisor == 1) {
+        return this;
+      } else if (divisor > 1) {
+        OffsetEquationFunction divideFunc =
+            new OffsetEquationFunction() {
+              @Override
+              public OffsetEquation compute(OffsetEquation eq) {
+                if (eq.isNegativeOrZero()) {
+                  return eq;
+                }
+                return null;
+              }
+            };
+        return computeNewOffsets(divideFunc);
+      }
+      return UpperBoundUnknownQualifier.UNKNOWN;
+    }
+
+    public boolean isValuePlusOffsetLessThanMinLen(String sequence, long value, int minlen) {
+      Set<OffsetEquation> offsets = map.get(sequence);
+      if (offsets == null) {
+        return false;
+      }
+      for (OffsetEquation offset : offsets) {
+        if (offset.isInt()) {
+          // This expression must not overflow
+          return (long) minlen - offset.getInt() > value;
+        }
+      }
+      return false;
+    }
+
+    /**
+     * Checks whether replacing sequence with replacementSequence in this qualifier creates
+     * replacementSequence entry in other.
+     */
+    public boolean isValidReplacement(
+        String sequence, String replacementSequence, LessThanLengthOf other) {
+      Set<OffsetEquation> offsets = map.get(sequence);
+      if (offsets == null) {
+        return false;
+      }
+      Set<OffsetEquation> otherOffsets = other.map.get(replacementSequence);
+      if (otherOffsets == null) {
+        return false;
+      }
+      return containsSame(offsets, otherOffsets);
+    }
+
+    @Override
+    public String toString() {
+      return "LessThanLengthOf{" + "map=" + map + '}';
+    }
+
+    public Iterable<? extends String> getSequences() {
+      return map.keySet();
+    }
+
+    /**
+     * Generates a new UBQualifer without the given (sequence, offset) pair. Other occurrences of
+     * the sequence and the offset may remain in the result, but not together.
+     *
+     * @param sequence a Java expression representing a string
+     * @param offset an integral offset
+     * @return a new UBQualifer without the given sequence and offset
+     */
+    public UBQualifier removeOffset(String sequence, int offset) {
+      OffsetEquation offsetEq = OffsetEquation.createOffsetForInt(offset);
+      Map<String, Set<OffsetEquation>> newMap = copyMap();
+      Set<OffsetEquation> equations = newMap.get(sequence);
+      if (equations != null) {
+        equations.remove(offsetEq);
+        if (equations.isEmpty()) {
+          newMap.remove(sequence);
+        }
+      }
+
+      if (newMap.isEmpty()) {
+        return UpperBoundUnknownQualifier.UNKNOWN;
+      } else {
+        return new LessThanLengthOf(newMap);
+      }
+    }
+
+    /** Functional interface that operates on {@link OffsetEquation}s. */
+    interface OffsetEquationFunction {
+      /**
+       * Returns the result of the computation or null if the passed equation should be removed.
+       *
+       * @param eq current offset equation
+       * @return the result of the computation or null if the passed equation should be removed
+       */
+      OffsetEquation compute(OffsetEquation eq);
+    }
+
+    /**
+     * Returns a new qualifier that is a copy of this qualifier with the OffsetEquationFunction
+     * applied to each offset.
+     *
+     * <p>If the {@link OffsetEquationFunction} returns null, it's not added as an offset. If after
+     * all functions have been applied, an sequence has no offsets, then that sequence is not added
+     * to the returned qualifier. If no sequences are added to the returned qualifier, then UNKNOWN
+     * is returned.
+     *
+     * @param f function to apply
+     * @return a new qualifier that is a copy of this qualifier with the OffsetEquationFunction
+     *     applied to each offset
+     */
+    private UBQualifier computeNewOffsets(OffsetEquationFunction f) {
+      Map<String, Set<OffsetEquation>> newMap = new HashMap<>(map.size());
+      for (Map.Entry<String, Set<OffsetEquation>> entry : map.entrySet()) {
+        Set<OffsetEquation> offsets = new HashSet<>(entry.getValue().size());
+        for (OffsetEquation eq : entry.getValue()) {
+          OffsetEquation newEq = f.compute(eq);
+          if (newEq != null) {
+            offsets.add(newEq);
+          }
+        }
+        if (!offsets.isEmpty()) {
+          newMap.put(entry.getKey(), offsets);
+        }
+      }
+      if (newMap.isEmpty()) {
+        return UpperBoundUnknownQualifier.UNKNOWN;
+      }
+      return new LessThanLengthOf(newMap);
+    }
+  }
+
+  /** Represents an integer value that is known at compile time. */
+  public static class UpperBoundLiteralQualifier extends UBQualifier {
+
+    /** Represents the value -1. */
+    public static UpperBoundLiteralQualifier NEGATIVEONE = new UpperBoundLiteralQualifier(-1);
+    /** Represents the value 0. */
+    public static UpperBoundLiteralQualifier ZERO = new UpperBoundLiteralQualifier(0);
+    /** Represents the value 1. */
+    public static UpperBoundLiteralQualifier ONE = new UpperBoundLiteralQualifier(1);
+
+    /**
+     * Creates a new UpperBoundLiteralQualifier, without using cached values.
+     *
+     * @param value the integer value
+     */
+    private UpperBoundLiteralQualifier(int value) {
+      this.value = value;
+    }
+
+    /**
+     * Creates an UpperBoundLiteralQualifier.
+     *
+     * @param value the integer value
+     * @return an UpperBoundLiteralQualifier
+     */
+    public static UpperBoundLiteralQualifier create(int value) {
+      switch (value) {
+        case -1:
+          return NEGATIVEONE;
+        case 0:
+          return ZERO;
+        case 1:
+          return ONE;
+        default:
+          return new UpperBoundLiteralQualifier(value);
+      }
+    }
+
+    /** The integer value. */
+    int value;
+
+    /**
+     * Returns the integer value.
+     *
+     * @return the integer value
+     */
+    int getValue() {
+      return value;
+    }
+
+    @Override
+    public boolean isLiteral() {
+      return true;
+    }
+
+    @Override
+    public boolean isSubtype(UBQualifier superType) {
+      if (superType.isUnknown()) {
+        return true;
+      } else if (superType.isBottom()) {
+        return false;
+      } else if (superType.isLiteral()) {
+        int otherValue = ((UpperBoundLiteralQualifier) superType).value;
+        return value == otherValue;
+      }
+
+      LessThanLengthOf superTypeLTL = (LessThanLengthOf) superType;
+      return superTypeLTL.literalIsSubtype(value);
+    }
+
+    @Override
+    public UBQualifier lub(UBQualifier other) {
+      if (isSubtype(other)) {
+        return other;
+      } else {
+        return UpperBoundUnknownQualifier.UNKNOWN;
+      }
+    }
+
+    @Override
+    public UBQualifier glb(UBQualifier other) {
+      if (isSubtype(other)) {
+        return this;
+      } else {
+        return UpperBoundBottomQualifier.BOTTOM;
+      }
+    }
+
+    @Override
+    public String toString() {
+      return "Literal(" + value + ")";
+    }
+  }
+
+  /** The top type qualifier. */
+  public static class UpperBoundUnknownQualifier extends UBQualifier {
+    /** The canonical representative. */
+    static final UBQualifier UNKNOWN = new UpperBoundUnknownQualifier();
+
+    /** This class is a singleton. */
+    private UpperBoundUnknownQualifier() {}
+
+    @Override
+    public boolean isSubtype(UBQualifier superType) {
+      return superType.isUnknown();
+    }
+
+    @Override
+    public boolean isUnknown() {
+      return true;
+    }
+
+    @Override
+    public UBQualifier lub(UBQualifier other) {
+      return this;
+    }
+
+    @Override
+    public UBQualifier glb(UBQualifier other) {
+      return other;
+    }
+
+    @Override
+    public String toString() {
+      return "UNKNOWN";
+    }
+  }
+
+  private static class UpperBoundBottomQualifier extends UBQualifier {
+    static final UBQualifier BOTTOM = new UpperBoundBottomQualifier();
+
+    @Override
+    public boolean isBottom() {
+      return true;
+    }
+
+    @Override
+    public boolean isSubtype(UBQualifier superType) {
+      return true;
+    }
+
+    @Override
+    public UBQualifier lub(UBQualifier other) {
+      return other;
+    }
+
+    @Override
+    public UBQualifier glb(UBQualifier other) {
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return "BOTTOM";
+    }
+  }
+
+  private static class PolyQualifier extends UBQualifier {
+    static final UBQualifier POLY = new PolyQualifier();
+
+    @Override
+    @Pure
+    public boolean isPoly() {
+      return true;
+    }
+
+    @Override
+    public boolean isSubtype(UBQualifier superType) {
+      return superType.isUnknown() || superType.isPoly();
+    }
+
+    @Override
+    public UBQualifier lub(UBQualifier other) {
+      if (other.isPoly() || other.isBottom()) {
+        return this;
+      }
+      return UpperBoundUnknownQualifier.UNKNOWN;
+    }
+
+    @Override
+    public UBQualifier glb(UBQualifier other) {
+      if (other.isPoly() || other.isUnknown()) {
+        return this;
+      }
+      return UpperBoundBottomQualifier.BOTTOM;
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java
new file mode 100644
index 0000000..beae7c2
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java
@@ -0,0 +1,896 @@
+package org.checkerframework.checker.index.upperbound;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.util.TreePath;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+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.util.Elements;
+import org.checkerframework.checker.index.BaseAnnotatedTypeFactoryForIndexChecker;
+import org.checkerframework.checker.index.IndexChecker;
+import org.checkerframework.checker.index.IndexMethodIdentifier;
+import org.checkerframework.checker.index.IndexUtil;
+import org.checkerframework.checker.index.OffsetDependentTypesHelper;
+import org.checkerframework.checker.index.inequality.LessThanAnnotatedTypeFactory;
+import org.checkerframework.checker.index.inequality.LessThanChecker;
+import org.checkerframework.checker.index.lowerbound.LowerBoundAnnotatedTypeFactory;
+import org.checkerframework.checker.index.lowerbound.LowerBoundChecker;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.IndexOrLow;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.LTOMLengthOf;
+import org.checkerframework.checker.index.qual.LengthOf;
+import org.checkerframework.checker.index.qual.NegativeIndexFor;
+import org.checkerframework.checker.index.qual.PolyIndex;
+import org.checkerframework.checker.index.qual.PolyUpperBound;
+import org.checkerframework.checker.index.qual.SameLen;
+import org.checkerframework.checker.index.qual.SearchIndexFor;
+import org.checkerframework.checker.index.qual.UpperBoundBottom;
+import org.checkerframework.checker.index.qual.UpperBoundLiteral;
+import org.checkerframework.checker.index.qual.UpperBoundUnknown;
+import org.checkerframework.checker.index.samelen.SameLenAnnotatedTypeFactory;
+import org.checkerframework.checker.index.samelen.SameLenChecker;
+import org.checkerframework.checker.index.searchindex.SearchIndexAnnotatedTypeFactory;
+import org.checkerframework.checker.index.searchindex.SearchIndexChecker;
+import org.checkerframework.checker.index.substringindex.SubstringIndexAnnotatedTypeFactory;
+import org.checkerframework.checker.index.substringindex.SubstringIndexChecker;
+import org.checkerframework.checker.index.upperbound.UBQualifier.LessThanLengthOf;
+import org.checkerframework.checker.index.upperbound.UBQualifier.UpperBoundLiteralQualifier;
+import org.checkerframework.checker.index.upperbound.UBQualifier.UpperBoundUnknownQualifier;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.common.value.ValueCheckerUtils;
+import org.checkerframework.common.value.qual.BottomVal;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.ElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Implements the introduction rules for the Upper Bound Checker.
+ *
+ * <p>Rules implemented by this class:
+ *
+ * <ul>
+ *   <li>1. Math.min has unusual semantics that combines annotations for the UBC.
+ *   <li>2. The return type of Random.nextInt depends on the argument, but is not equal to it, so a
+ *       polymorhpic qualifier is insufficient.
+ *   <li>3. Unary negation on a NegativeIndexFor (from the SearchIndex Checker) results in a
+ *       LTLengthOf for the same arrays.
+ *   <li>4. Right shifting by a constant between 0 and 30 preserves the type of the left side
+ *       expression.
+ *   <li>5. If either argument to a bitwise and expression is non-negative, the and expression
+ *       retains that argument's upperbound type. If both are non-negative, the result of the
+ *       expression is the GLB of the two.
+ *   <li>6. if numerator &ge; 0, then numerator % divisor &le; numerator
+ *   <li>7. if divisor &ge; 0, then numerator % divisor &lt; divisor
+ *   <li>8. If the numerator is an array length access of an array with non-zero length, and the
+ *       divisor is greater than one, glb the result with an LTL of the array.
+ *   <li>9. if numeratorTree is a + b and divisor greater than 1, and a and b are less than the
+ *       length of some sequence, then (a + b) / divisor is less than the length of that sequence.
+ *   <li>10. Special handling for Math.random: Math.random() * array.length is LTL array.
+ * </ul>
+ */
+public class UpperBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactoryForIndexChecker {
+
+  /** The @{@link UpperBoundUnknown} annotation. */
+  public final AnnotationMirror UNKNOWN =
+      AnnotationBuilder.fromClass(elements, UpperBoundUnknown.class);
+  /** The @{@link UpperBoundBottom} annotation. */
+  public final AnnotationMirror BOTTOM =
+      AnnotationBuilder.fromClass(elements, UpperBoundBottom.class);
+  /** The @{@link PolyUpperBound} annotation. */
+  public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyUpperBound.class);
+  /** The @{@link UpperBoundLiteral}(-1) annotation. */
+  public final AnnotationMirror NEGATIVEONE =
+      new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class)
+          .setValue("value", -1)
+          .build();
+  /** The @{@link UpperBoundLiteral}(0) annotation. */
+  public final AnnotationMirror ZERO =
+      new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class)
+          .setValue("value", 0)
+          .build();
+  /** The @{@link UpperBoundLiteral}(1) annotation. */
+  public final AnnotationMirror ONE =
+      new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class)
+          .setValue("value", 1)
+          .build();
+
+  /** The NegativeIndexFor.value element/field. */
+  public final ExecutableElement negativeIndexForValueElement =
+      TreeUtils.getMethod(NegativeIndexFor.class, "value", 0, processingEnv);
+  /** The SameLen.value element/field. */
+  public final ExecutableElement sameLenValueElement =
+      TreeUtils.getMethod(SameLen.class, "value", 0, processingEnv);
+  /** The LTLengthOf.value element/field. */
+  public final ExecutableElement ltLengthOfValueElement =
+      TreeUtils.getMethod(LTLengthOf.class, "value", 0, processingEnv);
+  /** The LTLengthOf.offset element/field. */
+  public final ExecutableElement ltLengthOfOffsetElement =
+      TreeUtils.getMethod(LTLengthOf.class, "offset", 0, processingEnv);
+
+  /** Predicates about what method an invocation is calling. */
+  private final IndexMethodIdentifier imf;
+
+  /** Create a new UpperBoundAnnotatedTypeFactory. */
+  public UpperBoundAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    addAliasedTypeAnnotation(IndexFor.class, LTLengthOf.class, true);
+    addAliasedTypeAnnotation(IndexOrLow.class, LTLengthOf.class, true);
+    addAliasedTypeAnnotation(IndexOrHigh.class, LTEqLengthOf.class, true);
+    addAliasedTypeAnnotation(SearchIndexFor.class, LTLengthOf.class, true);
+    addAliasedTypeAnnotation(NegativeIndexFor.class, LTLengthOf.class, true);
+    addAliasedTypeAnnotation(LengthOf.class, LTEqLengthOf.class, true);
+    addAliasedTypeAnnotation(PolyIndex.class, POLY);
+
+    imf = new IndexMethodIdentifier(this);
+
+    this.postInit();
+  }
+
+  /** Gets a helper object that holds references to methods with special handling. */
+  IndexMethodIdentifier getMethodIdentifier() {
+    return imf;
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined.
+    return new LinkedHashSet<>(
+        Arrays.asList(
+            UpperBoundUnknown.class,
+            LTEqLengthOf.class,
+            LTLengthOf.class,
+            LTOMLengthOf.class,
+            UpperBoundLiteral.class,
+            UpperBoundBottom.class,
+            PolyUpperBound.class));
+  }
+
+  /**
+   * Provides a way to query the Constant Value Checker, which computes the values of expressions
+   * known at compile time (constant propagation and folding).
+   */
+  ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() {
+    return getTypeFactoryOfSubchecker(ValueChecker.class);
+  }
+
+  /**
+   * Provides a way to query the Search Index Checker, which helps the Index Checker type the
+   * results of calling the JDK's binary search methods correctly.
+   */
+  private SearchIndexAnnotatedTypeFactory getSearchIndexAnnotatedTypeFactory() {
+    return getTypeFactoryOfSubchecker(SearchIndexChecker.class);
+  }
+
+  /**
+   * Gets the annotated type factory of the Substring Index Checker running along with the Upper
+   * Bound checker, allowing it to refine the upper bounds of expressions annotated by Substring
+   * Index Checker annotations.
+   */
+  SubstringIndexAnnotatedTypeFactory getSubstringIndexAnnotatedTypeFactory() {
+    return getTypeFactoryOfSubchecker(SubstringIndexChecker.class);
+  }
+
+  /**
+   * Provides a way to query the SameLen (same length) Checker, which determines the relationships
+   * among the lengths of arrays.
+   */
+  SameLenAnnotatedTypeFactory getSameLenAnnotatedTypeFactory() {
+    return getTypeFactoryOfSubchecker(SameLenChecker.class);
+  }
+
+  /**
+   * Provides a way to query the Lower Bound Checker, which determines whether each integer in the
+   * program is non-negative or not, and checks that no possibly negative integers are used to
+   * access arrays.
+   */
+  LowerBoundAnnotatedTypeFactory getLowerBoundAnnotatedTypeFactory() {
+    return getTypeFactoryOfSubchecker(LowerBoundChecker.class);
+  }
+
+  /** Returns the LessThan Checker's annotated type factory. */
+  public LessThanAnnotatedTypeFactory getLessThanAnnotatedTypeFactory() {
+    return getTypeFactoryOfSubchecker(LessThanChecker.class);
+  }
+
+  @Override
+  public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) {
+    super.addComputedTypeAnnotations(element, type);
+    if (element != null) {
+      AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(element);
+      addUpperBoundTypeFromValueType(valueType, type);
+    }
+  }
+
+  @Override
+  public void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) {
+    super.addComputedTypeAnnotations(tree, type, iUseFlow);
+    // If dataflow shouldn't be used to compute this type, then do not use the result from
+    // the Value Checker, because dataflow is used to compute that type.  (Without this,
+    // "int i = 1; --i;" fails.)
+    if (iUseFlow && tree != null && TreeUtils.isExpressionTree(tree)) {
+      AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(tree);
+      addUpperBoundTypeFromValueType(valueType, type);
+    }
+  }
+
+  /**
+   * Checks if valueType contains a {@link org.checkerframework.common.value.qual.BottomVal}
+   * annotation. If so, adds an {@link UpperBoundBottom} annotation to type.
+   *
+   * @param valueType annotated type from the {@link ValueAnnotatedTypeFactory}
+   * @param type annotated type from this factory that is side effected
+   */
+  private void addUpperBoundTypeFromValueType(
+      AnnotatedTypeMirror valueType, AnnotatedTypeMirror type) {
+    if (containsSameByClass(valueType.getAnnotations(), BottomVal.class)) {
+      type.replaceAnnotation(BOTTOM);
+    }
+  }
+
+  @Override
+  protected TypeAnnotator createTypeAnnotator() {
+    return new ListTypeAnnotator(new UpperBoundTypeAnnotator(this), super.createTypeAnnotator());
+  }
+
+  /**
+   * Performs pre-processing on annotations written by users, replacing illegal annotations by legal
+   * ones.
+   */
+  private class UpperBoundTypeAnnotator extends TypeAnnotator {
+
+    private UpperBoundTypeAnnotator(AnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    protected Void scan(AnnotatedTypeMirror type, Void aVoid) {
+      // If there is an LTLengthOf annotation whose argument lengths don't match, replace it with
+      // bottom.
+      AnnotationMirror anm = type.getAnnotation(LTLengthOf.class);
+      if (anm != null) {
+        List<String> sequences =
+            AnnotationUtils.getElementValueArray(anm, ltLengthOfValueElement, String.class);
+        List<String> offsets =
+            AnnotationUtils.getElementValueArray(
+                anm, ltLengthOfOffsetElement, String.class, Collections.emptyList());
+        if (sequences != null
+            && offsets != null
+            && sequences.size() != offsets.size()
+            && !offsets.isEmpty()) {
+          // Cannot use type.replaceAnnotation because it will call isSubtype, which will
+          // try to process the annotation and throw an error.
+          type.clearAnnotations();
+          type.addAnnotation(BOTTOM);
+        }
+      }
+      return super.scan(type, aVoid);
+    }
+  }
+
+  @Override
+  protected DependentTypesHelper createDependentTypesHelper() {
+    return new OffsetDependentTypesHelper(this);
+  }
+
+  /**
+   * Queries the SameLen Checker to return the type that the SameLen Checker associates with the
+   * given tree.
+   */
+  public AnnotationMirror sameLenAnnotationFromTree(Tree tree) {
+    AnnotatedTypeMirror sameLenType = getSameLenAnnotatedTypeFactory().getAnnotatedType(tree);
+    return sameLenType.getAnnotation(SameLen.class);
+  }
+
+  // Wrapper methods for accessing the IndexMethodIdentifier.
+
+  public boolean isMathMin(Tree methodTree) {
+    return imf.isMathMin(methodTree);
+  }
+
+  /**
+   * Returns true if the tree is for {@code Random.nextInt(int)}.
+   *
+   * @param methodTree a tree to check
+   * @return true iff the tree is for {@code Random.nextInt(int)}
+   */
+  public boolean isRandomNextInt(Tree methodTree) {
+    return imf.isRandomNextInt(methodTree, processingEnv);
+  }
+
+  AnnotationMirror createLTLengthOfAnnotation(String... names) {
+    if (names == null || names.length == 0) {
+      throw new BugInCF("createLTLengthOfAnnotation: bad argument %s", Arrays.toString(names));
+    }
+    AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), LTLengthOf.class);
+    builder.setValue("value", names);
+    return builder.build();
+  }
+
+  AnnotationMirror createLTEqLengthOfAnnotation(String... names) {
+    if (names == null || names.length == 0) {
+      throw new BugInCF("createLTEqLengthOfAnnotation: bad argument %s", Arrays.toString(names));
+    }
+    AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), LTEqLengthOf.class);
+    builder.setValue("value", names);
+    return builder.build();
+  }
+
+  /**
+   * Returns true iff the given node has the passed Lower Bound qualifier according to the LBC. The
+   * last argument should be Positive.class, NonNegative.class, or GTENegativeOne.class.
+   *
+   * @param node the given node
+   * @param classOfType one of Positive.class, NonNegative.class, or GTENegativeOne.class
+   * @return true iff the given node has the passed Lower Bound qualifier according to the LBC
+   */
+  public boolean hasLowerBoundTypeByClass(Node node, Class<? extends Annotation> classOfType) {
+    return areSameByClass(
+        getLowerBoundAnnotatedTypeFactory()
+            .getAnnotatedType(node.getTree())
+            .getAnnotationInHierarchy(getLowerBoundAnnotatedTypeFactory().UNKNOWN),
+        classOfType);
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new UpperBoundQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
+  }
+
+  /** The qualifier hierarchy for the upperbound type system. */
+  protected final class UpperBoundQualifierHierarchy extends ElementQualifierHierarchy {
+    /**
+     * Creates an UpperBoundQualifierHierarchy from the given classes.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers
+     * @param elements element utils
+     */
+    UpperBoundQualifierHierarchy(
+        Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+    }
+
+    @Override
+    public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
+      UBQualifier a1Obj = UBQualifier.createUBQualifier(a1, (IndexChecker) checker);
+      UBQualifier a2Obj = UBQualifier.createUBQualifier(a2, (IndexChecker) checker);
+      UBQualifier glb = a1Obj.glb(a2Obj);
+      return convertUBQualifierToAnnotation(glb);
+    }
+
+    /**
+     * Determines the least upper bound of a1 and a2. If a1 and a2 are both the same type of Value
+     * annotation, then the LUB is the result of taking the intersection of values from both a1 and
+     * a2.
+     *
+     * @return the least upper bound of a1 and a2
+     */
+    @Override
+    public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
+      UBQualifier a1Obj = UBQualifier.createUBQualifier(a1, (IndexChecker) checker);
+      UBQualifier a2Obj = UBQualifier.createUBQualifier(a2, (IndexChecker) checker);
+      UBQualifier lub = a1Obj.lub(a2Obj);
+      return convertUBQualifierToAnnotation(lub);
+    }
+
+    @Override
+    public AnnotationMirror widenedUpperBound(
+        AnnotationMirror newQualifier, AnnotationMirror previousQualifier) {
+      UBQualifier a1Obj = UBQualifier.createUBQualifier(newQualifier, (IndexChecker) checker);
+      UBQualifier a2Obj = UBQualifier.createUBQualifier(previousQualifier, (IndexChecker) checker);
+      UBQualifier lub = a1Obj.widenUpperBound(a2Obj);
+      return convertUBQualifierToAnnotation(lub);
+    }
+
+    @Override
+    public int numberOfIterationsBeforeWidening() {
+      return 10;
+    }
+
+    /**
+     * Computes subtyping as per the subtyping in the qualifier hierarchy structure unless both
+     * annotations have the same class. In this case, rhs is a subtype of lhs iff rhs contains every
+     * element of lhs.
+     *
+     * @return true if rhs is a subtype of lhs, false otherwise
+     */
+    @Override
+    public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+      UBQualifier subtype = UBQualifier.createUBQualifier(subAnno, (IndexChecker) checker);
+      UBQualifier supertype = UBQualifier.createUBQualifier(superAnno, (IndexChecker) checker);
+      return subtype.isSubtype(supertype);
+    }
+  }
+
+  @Override
+  public TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(new UpperBoundTreeAnnotator(this), super.createTreeAnnotator());
+  }
+
+  protected class UpperBoundTreeAnnotator extends TreeAnnotator {
+
+    public UpperBoundTreeAnnotator(UpperBoundAnnotatedTypeFactory factory) {
+      super(factory);
+    }
+
+    /**
+     * This exists for Math.min and Random.nextInt, which must be special-cased.
+     *
+     * <ul>
+     *   <li>Math.min has unusual semantics that combines annotations for the UBC.
+     *   <li>The return type of Random.nextInt depends on the argument, but is not equal to it, so a
+     *       polymorhpic qualifier is insufficient.
+     * </ul>
+     *
+     * Other methods should not be special-cased here unless there is a compelling reason to do so.
+     */
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
+      if (isMathMin(tree)) {
+        AnnotatedTypeMirror leftType = getAnnotatedType(tree.getArguments().get(0));
+        AnnotatedTypeMirror rightType = getAnnotatedType(tree.getArguments().get(1));
+
+        type.replaceAnnotation(
+            qualHierarchy.greatestLowerBound(
+                leftType.getAnnotationInHierarchy(UNKNOWN),
+                rightType.getAnnotationInHierarchy(UNKNOWN)));
+      }
+      if (isRandomNextInt(tree)) {
+        AnnotatedTypeMirror argType = getAnnotatedType(tree.getArguments().get(0));
+        AnnotationMirror anno = argType.getAnnotationInHierarchy(UNKNOWN);
+        UBQualifier qualifier = UBQualifier.createUBQualifier(anno, (IndexChecker) checker);
+        qualifier = qualifier.plusOffset(1);
+        type.replaceAnnotation(convertUBQualifierToAnnotation(qualifier));
+      }
+      return super.visitMethodInvocation(tree, type);
+    }
+
+    @Override
+    public Void visitLiteral(LiteralTree node, AnnotatedTypeMirror type) {
+      // Could also handle long literals, but array indexes are always ints.
+      if (node.getKind() == Tree.Kind.INT_LITERAL) {
+        type.addAnnotation(createLiteral(((Integer) node.getValue()).intValue()));
+      }
+      return super.visitLiteral(node, type);
+    }
+
+    /* Handles case 3. */
+    @Override
+    public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) {
+      // Dataflow refines this type if possible
+      if (node.getKind() == Kind.BITWISE_COMPLEMENT) {
+        addAnnotationForBitwiseComplement(
+            getSearchIndexAnnotatedTypeFactory().getAnnotatedType(node.getExpression()), type);
+      } else {
+        type.addAnnotation(UNKNOWN);
+      }
+      return super.visitUnary(node, type);
+    }
+
+    /**
+     * If a type returned by an {@link SearchIndexAnnotatedTypeFactory} has a {@link
+     * NegativeIndexFor} annotation, then refine the result to be {@link LTEqLengthOf}. This handles
+     * this case:
+     *
+     * <pre>{@code
+     * int i = Arrays.binarySearch(a, x);
+     * if (i >= 0) {
+     *     // do something
+     * } else {
+     *     i = ~i;
+     *     // i is now @LTEqLengthOf("a"), because the bitwise complement of a NegativeIndexFor is an LTL.
+     *     for (int j = 0; j < i; j++) {
+     *          // j is now a valid index for "a"
+     *     }
+     * }
+     * }</pre>
+     *
+     * @param searchIndexType the type of an expression in a bitwise complement. For instance, in
+     *     {@code ~x}, this is the type of {@code x}.
+     * @param typeDst the type of the entire bitwise complement expression. It is modified by this
+     *     method.
+     */
+    private void addAnnotationForBitwiseComplement(
+        AnnotatedTypeMirror searchIndexType, AnnotatedTypeMirror typeDst) {
+      AnnotationMirror nif = searchIndexType.getAnnotation(NegativeIndexFor.class);
+      if (nif != null) {
+        List<String> arrays =
+            AnnotationUtils.getElementValueArray(nif, negativeIndexForValueElement, String.class);
+        List<String> negativeOnes = Collections.nCopies(arrays.size(), "-1");
+        UBQualifier qual = UBQualifier.createUBQualifier(arrays, negativeOnes);
+        typeDst.addAnnotation(convertUBQualifierToAnnotation(qual));
+      } else {
+        typeDst.addAnnotation(UNKNOWN);
+      }
+    }
+
+    @Override
+    public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
+      // Dataflow refines this type if possible
+      type.addAnnotation(UNKNOWN);
+      return super.visitCompoundAssignment(node, type);
+    }
+
+    @Override
+    public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) {
+      // A few small rules for addition/subtraction by 0/1, etc.
+      if (TreeUtils.isStringConcatenation(tree)) {
+        type.addAnnotation(UNKNOWN);
+        return super.visitBinary(tree, type);
+      }
+
+      ExpressionTree left = tree.getLeftOperand();
+      ExpressionTree right = tree.getRightOperand();
+      switch (tree.getKind()) {
+        case PLUS:
+        case MINUS:
+          // Dataflow refines this type if possible
+          type.addAnnotation(UNKNOWN);
+          break;
+        case MULTIPLY:
+          addAnnotationForMultiply(left, right, type);
+          break;
+        case DIVIDE:
+          addAnnotationForDivide(left, right, type);
+          break;
+        case REMAINDER:
+          addAnnotationForRemainder(left, right, type);
+          break;
+        case AND:
+          addAnnotationForAnd(left, right, type);
+          break;
+        case RIGHT_SHIFT:
+        case UNSIGNED_RIGHT_SHIFT:
+          addAnnotationForRightShift(left, right, type);
+          break;
+        default:
+          break;
+      }
+      return super.visitBinary(tree, type);
+    }
+
+    /**
+     * Infers upper-bound annotation for {@code left >> right} and {@code left >>> right} (case 4).
+     */
+    private void addAnnotationForRightShift(
+        ExpressionTree left, ExpressionTree right, AnnotatedTypeMirror type) {
+      LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory();
+      if (lowerBoundATF.isNonNegative(left)) {
+        AnnotationMirror annotation = getAnnotatedType(left).getAnnotationInHierarchy(UNKNOWN);
+        // For non-negative numbers, right shift is equivalent to division by a power of two.
+        // The range of the shift amount is limited to 0..30 to avoid overflows and int/long
+        // differences.
+        Long shiftAmount = ValueCheckerUtils.getExactValue(right, getValueAnnotatedTypeFactory());
+        if (shiftAmount != null && shiftAmount >= 0 && shiftAmount < Integer.SIZE - 1) {
+          int divisor = 1 << shiftAmount;
+          // Support average by shift just like for division
+          UBQualifier plusDivQualifier = plusTreeDivideByVal(divisor, left);
+          if (!plusDivQualifier.isUnknown()) {
+            UBQualifier qualifier =
+                UBQualifier.createUBQualifier(annotation, (IndexChecker) checker);
+            qualifier = qualifier.glb(plusDivQualifier);
+            annotation = convertUBQualifierToAnnotation(qualifier);
+          }
+        }
+        type.addAnnotation(annotation);
+      }
+    }
+
+    /**
+     * If either argument is non-negative, the and expression retains that argument's upperbound
+     * type. If both are non-negative, the result of the expression is the GLB of the two (case 5).
+     */
+    private void addAnnotationForAnd(
+        ExpressionTree left, ExpressionTree right, AnnotatedTypeMirror type) {
+      LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory();
+      AnnotatedTypeMirror leftType = getAnnotatedType(left);
+      AnnotationMirror leftResultType = UNKNOWN;
+      if (lowerBoundATF.isNonNegative(left)) {
+        leftResultType = leftType.getAnnotationInHierarchy(UNKNOWN);
+      }
+
+      AnnotatedTypeMirror rightType = getAnnotatedType(right);
+      AnnotationMirror rightResultType = UNKNOWN;
+      if (lowerBoundATF.isNonNegative(right)) {
+        rightResultType = rightType.getAnnotationInHierarchy(UNKNOWN);
+      }
+
+      type.addAnnotation(qualHierarchy.greatestLowerBound(leftResultType, rightResultType));
+    }
+
+    /** Gets a sequence tree for a length access tree, or null if it is not a length access. */
+    private ExpressionTree getLengthSequenceTree(ExpressionTree lengthTree) {
+      return IndexUtil.getLengthSequenceTree(lengthTree, imf, processingEnv);
+    }
+
+    /**
+     * Infers upper-bound annotation for {@code numerator % divisor}. There are two cases where an
+     * upperbound type is inferred:
+     *
+     * <ul>
+     *   <li>6. if numerator &ge; 0, then numerator % divisor &le; numerator
+     *   <li>7. if divisor &ge; 0, then numerator % divisor &lt; divisor
+     * </ul>
+     */
+    private void addAnnotationForRemainder(
+        ExpressionTree numeratorTree, ExpressionTree divisorTree, AnnotatedTypeMirror resultType) {
+      LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory();
+      UBQualifier result = UpperBoundUnknownQualifier.UNKNOWN;
+      // if numerator >= 0, then numerator%divisor <= numerator
+      if (lowerBoundATF.isNonNegative(numeratorTree)) {
+        result =
+            UBQualifier.createUBQualifier(
+                getAnnotatedType(numeratorTree), UNKNOWN, (IndexChecker) checker);
+      }
+      // if divisor >= 0, then numerator%divisor < divisor
+      if (lowerBoundATF.isNonNegative(divisorTree)) {
+        UBQualifier divisor =
+            UBQualifier.createUBQualifier(
+                getAnnotatedType(divisorTree), UNKNOWN, (IndexChecker) checker);
+        result = result.glb(divisor.plusOffset(1));
+      }
+      resultType.addAnnotation(convertUBQualifierToAnnotation(result));
+    }
+
+    /**
+     * Implements two cases (8 and 9):
+     *
+     * <ul>
+     *   <li>8. If the numerator is an array length access of an array with non-zero length, and the
+     *       divisor is greater than one, glb the result with an LTL of the array.
+     *   <li>9. if numeratorTree is a + b and divisor greater than 1, and a and b are less than the
+     *       length of some sequence, then (a + b) / divisor is less than the length of that
+     *       sequence.
+     * </ul>
+     */
+    private void addAnnotationForDivide(
+        ExpressionTree numeratorTree, ExpressionTree divisorTree, AnnotatedTypeMirror resultType) {
+
+      Long divisor = ValueCheckerUtils.getExactValue(divisorTree, getValueAnnotatedTypeFactory());
+      if (divisor == null) {
+        resultType.addAnnotation(UNKNOWN);
+        return;
+      }
+
+      UBQualifier result = UpperBoundUnknownQualifier.UNKNOWN;
+      UBQualifier numerator =
+          UBQualifier.createUBQualifier(
+              getAnnotatedType(numeratorTree), UNKNOWN, (IndexChecker) checker);
+      if (numerator.isLessThanLengthQualifier()) {
+        result = ((LessThanLengthOf) numerator).divide(divisor.intValue());
+      }
+      result = result.glb(plusTreeDivideByVal(divisor.intValue(), numeratorTree));
+
+      ExpressionTree numeratorSequenceTree = getLengthSequenceTree(numeratorTree);
+      // If the numerator is an array length access of an array with non-zero length, and the
+      // divisor is greater than one, glb the result with an LTL of the array.
+      if (numeratorSequenceTree != null && divisor > 1) {
+        String arrayName = numeratorSequenceTree.toString();
+        int minlen =
+            getValueAnnotatedTypeFactory()
+                .getMinLenFromString(arrayName, numeratorTree, getPath(numeratorTree));
+        if (minlen > 0) {
+          result = result.glb(UBQualifier.createUBQualifier(arrayName, "0"));
+        }
+      }
+
+      resultType.addAnnotation(convertUBQualifierToAnnotation(result));
+    }
+
+    /**
+     * If {@code numeratorTree} is "a + b" and {@code divisor} is greater than 1, and a and b are
+     * less than the length of some sequence, then "(a + b) / divisor" is less than the length of
+     * that sequence.
+     *
+     * @param divisor the divisor
+     * @param numeratorTree an addition tree that is divided by {@code divisor}
+     * @return a qualifier for the division
+     */
+    private UBQualifier plusTreeDivideByVal(int divisor, ExpressionTree numeratorTree) {
+      numeratorTree = TreeUtils.withoutParens(numeratorTree);
+      if (divisor < 2 || numeratorTree.getKind() != Kind.PLUS) {
+        return UpperBoundUnknownQualifier.UNKNOWN;
+      }
+      BinaryTree plusTree = (BinaryTree) numeratorTree;
+      UBQualifier left =
+          UBQualifier.createUBQualifier(
+              getAnnotatedType(plusTree.getLeftOperand()), UNKNOWN, (IndexChecker) checker);
+      UBQualifier right =
+          UBQualifier.createUBQualifier(
+              getAnnotatedType(plusTree.getRightOperand()), UNKNOWN, (IndexChecker) checker);
+      if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) {
+        LessThanLengthOf leftLTL = (LessThanLengthOf) left;
+        LessThanLengthOf rightLTL = (LessThanLengthOf) right;
+        List<String> sequences = new ArrayList<>();
+        for (String sequence : leftLTL.getSequences()) {
+          if (rightLTL.isLessThanLengthOf(sequence) && leftLTL.isLessThanLengthOf(sequence)) {
+            sequences.add(sequence);
+          }
+        }
+        if (!sequences.isEmpty()) {
+          return UBQualifier.createUBQualifier(sequences, Collections.emptyList());
+        }
+      }
+
+      return UpperBoundUnknownQualifier.UNKNOWN;
+    }
+
+    /**
+     * Special handling for Math.random: Math.random() * array.length is LTL array. Same for
+     * Random.nextDouble. Case 10.
+     */
+    private boolean checkForMathRandomSpecialCase(
+        ExpressionTree randTree, ExpressionTree seqLenTree, AnnotatedTypeMirror type) {
+
+      ExpressionTree seqTree = getLengthSequenceTree(seqLenTree);
+
+      if (randTree.getKind() == Tree.Kind.METHOD_INVOCATION && seqTree != null) {
+
+        MethodInvocationTree mitree = (MethodInvocationTree) randTree;
+
+        if (imf.isMathRandom(mitree, processingEnv)) {
+          // Okay, so this is Math.random() * array.length, which must be NonNegative
+          type.addAnnotation(createLTLengthOfAnnotation(seqTree.toString()));
+          return true;
+        }
+
+        if (imf.isRandomNextDouble(mitree, processingEnv)) {
+          // Okay, so this is Random.nextDouble() * array.length, which must be NonNegative
+          type.addAnnotation(createLTLengthOfAnnotation(seqTree.toString()));
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+    private void addAnnotationForMultiply(
+        ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) {
+      // Special handling for multiplying an array length by a random variable.
+      if (checkForMathRandomSpecialCase(rightExpr, leftExpr, type)
+          || checkForMathRandomSpecialCase(leftExpr, rightExpr, type)) {
+        return;
+      }
+      type.addAnnotation(UNKNOWN);
+    }
+  }
+
+  /**
+   * Creates a @{@link UpperBoundLiteral} annotation.
+   *
+   * @param i the integer
+   * @return a @{@link UpperBoundLiteral} annotation
+   */
+  public AnnotationMirror createLiteral(int i) {
+    switch (i) {
+      case -1:
+        return NEGATIVEONE;
+      case 0:
+        return ZERO;
+      case 1:
+        return ONE;
+      default:
+        return new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class)
+            .setValue("value", i)
+            .build();
+    }
+  }
+
+  /**
+   * Convert the internal representation to an annotation.
+   *
+   * @param qualifier a UBQualifier
+   * @return an annotation corresponding to the given qualifier
+   */
+  public AnnotationMirror convertUBQualifierToAnnotation(UBQualifier qualifier) {
+    if (qualifier.isUnknown()) {
+      return UNKNOWN;
+    } else if (qualifier.isBottom()) {
+      return BOTTOM;
+    } else if (qualifier.isPoly()) {
+      return POLY;
+    } else if (qualifier.isLiteral()) {
+      return createLiteral(((UpperBoundLiteralQualifier) qualifier).getValue());
+    }
+
+    LessThanLengthOf ltlQualifier = (LessThanLengthOf) qualifier;
+    return ltlQualifier.convertToAnnotation(processingEnv);
+  }
+
+  UBQualifier fromLessThan(ExpressionTree tree, TreePath treePath) {
+    List<String> lessThanExpressions =
+        getLessThanAnnotatedTypeFactory().getLessThanExpressions(tree);
+    if (lessThanExpressions == null) {
+      return null;
+    }
+    UBQualifier ubQualifier = fromLessThanOrEqual(tree, treePath, lessThanExpressions);
+    if (ubQualifier != null) {
+      return ubQualifier.plusOffset(1);
+    }
+    return null;
+  }
+
+  UBQualifier fromLessThanOrEqual(ExpressionTree tree, TreePath treePath) {
+    List<String> lessThanExpressions =
+        getLessThanAnnotatedTypeFactory().getLessThanExpressions(tree);
+    if (lessThanExpressions == null) {
+      return null;
+    }
+    UBQualifier ubQualifier = fromLessThanOrEqual(tree, treePath, lessThanExpressions);
+    return ubQualifier;
+  }
+
+  private UBQualifier fromLessThanOrEqual(
+      Tree tree, TreePath treePath, List<String> lessThanExpressions) {
+    UBQualifier ubQualifier = null;
+    for (String expression : lessThanExpressions) {
+      Pair<JavaExpression, String> exprAndOffset;
+      try {
+        exprAndOffset = getExpressionAndOffsetFromJavaExpressionString(expression, treePath);
+      } catch (JavaExpressionParseException e) {
+        exprAndOffset = null;
+      }
+      if (exprAndOffset == null) {
+        continue;
+      }
+      JavaExpression je = exprAndOffset.first;
+      String offset = exprAndOffset.second;
+
+      if (!CFAbstractStore.canInsertJavaExpression(je)) {
+        continue;
+      }
+      CFStore store = getStoreBefore(tree);
+      CFValue value = store.getValue(je);
+      if (value != null && value.getAnnotations().size() == 1) {
+        UBQualifier newUBQ =
+            UBQualifier.createUBQualifier(
+                qualHierarchy.findAnnotationInHierarchy(value.getAnnotations(), UNKNOWN),
+                AnnotatedTypeFactory.negateConstant(offset),
+                (IndexChecker) checker);
+        if (ubQualifier == null) {
+          ubQualifier = newUBQ;
+        } else {
+          ubQualifier = ubQualifier.glb(newUBQ);
+        }
+      }
+    }
+    return ubQualifier;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundChecker.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundChecker.java
new file mode 100644
index 0000000..4ccaf64
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundChecker.java
@@ -0,0 +1,112 @@
+package org.checkerframework.checker.index.upperbound;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.index.inequality.LessThanChecker;
+import org.checkerframework.checker.index.lowerbound.LowerBoundChecker;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.LTOMLengthOf;
+import org.checkerframework.checker.index.qual.SubstringIndexFor;
+import org.checkerframework.checker.index.qual.UpperBoundLiteral;
+import org.checkerframework.checker.index.samelen.SameLenChecker;
+import org.checkerframework.checker.index.searchindex.SearchIndexChecker;
+import org.checkerframework.checker.index.substringindex.SubstringIndexChecker;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+import org.checkerframework.framework.source.SuppressWarningsPrefix;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A type-checker for preventing arrays from being accessed with values that are too high.
+ *
+ * @checker_framework.manual #index-checker Index Checker
+ */
+@RelevantJavaTypes({
+  Byte.class,
+  Short.class,
+  Integer.class,
+  Long.class,
+  Character.class,
+  byte.class,
+  short.class,
+  int.class,
+  long.class,
+  char.class,
+})
+@SuppressWarningsPrefix({"index", "upperbound"})
+public class UpperBoundChecker extends BaseTypeChecker {
+  /** The SubstringIndexFor.value argument/element. */
+  public @MonotonicNonNull ExecutableElement substringIndexForValueElement;
+  /** The SubstringIndexFor.offset argument/element. */
+  public @MonotonicNonNull ExecutableElement substringIndexForOffsetElement;
+
+  /** The LTLengthOf.value argument/element. */
+  public @MonotonicNonNull ExecutableElement ltLengthOfValueElement;
+  /** The LTLengthOf.offset argument/element. */
+  public @MonotonicNonNull ExecutableElement ltLengthOfOffsetElement;
+  /** The LTEqLengthOf.value argument/element. */
+  public @MonotonicNonNull ExecutableElement ltEqLengthOfValueElement;
+  /** The LTOMLengthOf.value argument/element. */
+  public @MonotonicNonNull ExecutableElement ltOMLengthOfValueElement;
+  /** The UpperBoundLiteral.value element/field. */
+  public @MonotonicNonNull ExecutableElement upperBoundLiteralValueElement;
+
+  /**
+   * These collection classes have some subtypes whose length can change and some subtypes whose
+   * length cannot change. Warnings are skipped at uses of them.
+   */
+  private HashSet<String> collectionBaseTypeNames;
+
+  /** Create a new UpperBoundChecker. */
+  public UpperBoundChecker() {
+    // These classes are bases for both mutable and immutable sequence collections, which
+    // contain methods that change the length.
+    // Upper bound checker warnings are skipped at uses of them.
+    Class<?>[] collectionBaseClasses = {java.util.List.class, java.util.AbstractList.class};
+    collectionBaseTypeNames = new HashSet<>(collectionBaseClasses.length);
+    for (Class<?> collectionBaseClass : collectionBaseClasses) {
+      collectionBaseTypeNames.add(collectionBaseClass.getName());
+    }
+  }
+
+  @Override
+  public void initChecker() {
+    super.initChecker();
+    substringIndexForValueElement =
+        TreeUtils.getMethod(SubstringIndexFor.class, "value", 0, processingEnv);
+    substringIndexForOffsetElement =
+        TreeUtils.getMethod(SubstringIndexFor.class, "offset", 0, processingEnv);
+    ltLengthOfValueElement = TreeUtils.getMethod(LTLengthOf.class, "value", 0, processingEnv);
+    ltLengthOfOffsetElement = TreeUtils.getMethod(LTLengthOf.class, "offset", 0, processingEnv);
+    ltEqLengthOfValueElement = TreeUtils.getMethod(LTEqLengthOf.class, "value", 0, processingEnv);
+    ltOMLengthOfValueElement = TreeUtils.getMethod(LTOMLengthOf.class, "value", 0, processingEnv);
+    upperBoundLiteralValueElement =
+        TreeUtils.getMethod(UpperBoundLiteral.class, "value", 0, processingEnv);
+  }
+
+  @Override
+  public boolean shouldSkipUses(@FullyQualifiedName String typeName) {
+    if (collectionBaseTypeNames.contains(typeName)) {
+      return true;
+    }
+    return super.shouldSkipUses(typeName);
+  }
+
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    LinkedHashSet<Class<? extends BaseTypeChecker>> checkers =
+        super.getImmediateSubcheckerClasses();
+    checkers.add(SubstringIndexChecker.class);
+    checkers.add(SearchIndexChecker.class);
+    checkers.add(SameLenChecker.class);
+    checkers.add(LowerBoundChecker.class);
+    checkers.add(ValueChecker.class);
+    checkers.add(LessThanChecker.class);
+    return checkers;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundTransfer.java
new file mode 100644
index 0000000..1be086e
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundTransfer.java
@@ -0,0 +1,854 @@
+package org.checkerframework.checker.index.upperbound;
+
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.index.IndexAbstractTransfer;
+import org.checkerframework.checker.index.IndexRefinementInfo;
+import org.checkerframework.checker.index.Subsequence;
+import org.checkerframework.checker.index.inequality.LessThanAnnotatedTypeFactory;
+import org.checkerframework.checker.index.qual.LessThan;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.checker.index.qual.SubstringIndexFor;
+import org.checkerframework.checker.index.upperbound.UBQualifier.LessThanLengthOf;
+import org.checkerframework.checker.index.upperbound.UBQualifier.UpperBoundUnknownQualifier;
+import org.checkerframework.common.value.ValueCheckerUtils;
+import org.checkerframework.dataflow.analysis.RegularTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.ArrayCreationNode;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.CaseNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode;
+import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode;
+import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
+import org.checkerframework.dataflow.cfg.node.TypeCastNode;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.MethodCall;
+import org.checkerframework.dataflow.util.NodeUtils;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * Contains the transfer functions for the upper bound type system, a part of the Index Checker.
+ * This class implements the following refinement rules:
+ *
+ * <ul>
+ *   <li>1. Refine the type of expressions used as an array dimension to be less than length of the
+ *       array to which the new array is assigned. For example, in {@code int[] array = new
+ *       int[expr];}, the type of expr is {@code @LTEqLength("array")}.
+ *   <li>2. If {@code other * node} has type {@code typeOfMultiplication}, then if {@code other} is
+ *       positive, then {@code node} is {@code typeOfMultiplication}.
+ *   <li>3. If {@code other * node} has type {@code typeOfMultiplication}, if {@code other} is
+ *       greater than 1, then {@code node} is {@code typeOfMultiplication} plus 1.
+ *   <li>4. Given a subtraction node, {@code node}, that is known to have type {@code
+ *       typeOfSubtraction}. An offset can be applied to the left node (i.e. the left node has the
+ *       same type, but with an offset based on the right node).
+ *   <li>5. In an addition expression, refine the two operands based on the type of the whole
+ *       expression with appropriate offsets.
+ *   <li>6. If an addition expression has a type that is less than length of an array, and one of
+ *       the operands is non-negative, then the other is less than or equal to the length of the
+ *       array.
+ *   <li>7. If an addition expression has a type that is less than length of an array, and one of
+ *       the operands is positive, then the other is also less than the length of the array.
+ *   <li>8. if x &lt; y, and y has a type that is related to the length of an array, then x has the
+ *       same type, with an offset that is one less.
+ *   <li>9. if x &le; y, and y has a type that is related to the length of an array, then x has the
+ *       same type.
+ *   <li>10. refine the subtrahend in a subtraction which is greater than or equal to a certain
+ *       offset. The type of the subtrahend is refined to the type of the minuend with the offset
+ *       added.
+ *   <li>11. if two variables are equal, they have the same type
+ *   <li>12. If one node in a != expression is an sequence length field or method access (optionally
+ *       with a constant offset subtracted) and the other node is less than or equal to that
+ *       sequence length (minus the offset), then refine the other node's type to less than the
+ *       sequence length (minus the offset).
+ *   <li>13. If some Node a is known to be less than the length of some array, x, then, the type of
+ *       a + b, is {@code @LTLengthOf(value="x", offset="-b")}. If b is known to be less than the
+ *       length of some other array, y, then the type of a + b is {@code @LTLengthOf(value={"x",
+ *       "y"}, offset={"-b", "-a"})}.
+ *   <li>14. If a is known to be less than the length of x when some offset, o, is added to a (the
+ *       type of a is {@code @LTLengthOf(value="x", offset="o"))}, then the type of a + b is
+ *       {@code @LTLengthOf(value="x",offset="o - b")}. (Note, if "o - b" can be computed, then it
+ *       is and the result is used in the annotation.)
+ *   <li>15. If expression i has type {@code @LTLengthOf(value = "f2", offset = "f1.length")} int
+ *       and expression j is less than or equal to the length of f1, then the type of i + j is
+ *       {@code @LTLengthOf("f2")}.
+ *   <li>16. If some Node a is known to be less than the length of some sequence x, then the type of
+ *       a - b is {@code @LTLengthOf(value="x", offset="b")}.
+ *   <li>17. If some Node a is known to be less than the length of some sequence x, and if b is
+ *       non-negative or positive, then a - b should keep the types of a.
+ *   <li>18. The type of a sequence length access (i.e. array.length) is
+ *       {@code @LTLength(value={"array"...}, offset="-1")} where "array"... is the set of all
+ *       sequences that are the same length (via the SameLen checker) as "array"
+ *   <li>19. If n is an array length field access, then the type of a.length is the glb of
+ *       {@code @LTEqLengthOf("a")} and the value of a.length in the store.
+ *   <li>20. If n is a String.length() method invocation, then the type of s.length() is the glb of
+ *       {@code @LTEqLengthOf("s")} and the value of s.length() in the store.
+ * </ul>
+ */
+public class UpperBoundTransfer extends IndexAbstractTransfer {
+
+  /** The type factory associated with this transfer function. */
+  private UpperBoundAnnotatedTypeFactory atypeFactory;
+
+  /** The int TypeMirror. */
+  TypeMirror intTM;
+
+  /**
+   * Creates a new UpperBoundTransfer.
+   *
+   * @param analysis the analysis for this transfer function
+   */
+  public UpperBoundTransfer(CFAnalysis analysis) {
+    super(analysis);
+    atypeFactory = (UpperBoundAnnotatedTypeFactory) analysis.getTypeFactory();
+    intTM = atypeFactory.types.getPrimitiveType(TypeKind.INT);
+  }
+
+  /**
+   * Case 1: Refine the type of expressions used as an array dimension to be less than length of the
+   * array to which the new array is assigned. For example, in "int[] array = new int[expr];", the
+   * type of expr is @LTEqLength("array").
+   */
+  @Override
+  public TransferResult<CFValue, CFStore> visitAssignment(
+      AssignmentNode node, TransferInput<CFValue, CFStore> in) {
+    TransferResult<CFValue, CFStore> result = super.visitAssignment(node, in);
+
+    Node expNode = node.getExpression();
+
+    // strip off typecast if any
+    Node expNodeSansCast =
+        (expNode instanceof TypeCastNode) ? ((TypeCastNode) expNode).getOperand() : expNode;
+    // null if right-hand-side is not an array creation expression
+    ArrayCreationNode acNode =
+        (expNodeSansCast instanceof ArrayCreationNode)
+            ? acNode = (ArrayCreationNode) expNodeSansCast
+            : null;
+
+    if (acNode != null) {
+      // Right-hand side of assignment is an array creation expression
+      List<Node> nodeList = acNode.getDimensions();
+      if (nodeList.size() < 1) {
+        return result;
+      }
+      Node dim = acNode.getDimension(0);
+
+      UBQualifier previousQualifier = getUBQualifier(dim, in);
+      JavaExpression arrayExpr = JavaExpression.fromNode(node.getTarget());
+      String arrayString = arrayExpr.toString();
+      LessThanLengthOf newInfo =
+          (LessThanLengthOf) UBQualifier.createUBQualifier(arrayString, "-1");
+      UBQualifier combined = previousQualifier.glb(newInfo);
+      AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(combined);
+
+      JavaExpression dimExpr = JavaExpression.fromNode(dim);
+      result.getRegularStore().insertValue(dimExpr, newAnno);
+      propagateToOperands(newInfo, dim, in, result.getRegularStore());
+    }
+    return result;
+  }
+
+  /**
+   * {@code node} is known to be {@code typeOfNode}. If the node is a plus or a minus then the types
+   * of the left and right operands can be refined to include offsets. If the node is a
+   * multiplication, its operands can also be refined. See {@link
+   * #propagateToAdditionOperand(LessThanLengthOf, Node, Node, TransferInput, CFStore)}, {@link
+   * #propagateToSubtractionOperands(LessThanLengthOf, NumericalSubtractionNode, TransferInput,
+   * CFStore)}, and {@link #propagateToMultiplicationOperand(LessThanLengthOf, Node, Node,
+   * TransferInput, CFStore)} for details.
+   */
+  private void propagateToOperands(
+      LessThanLengthOf typeOfNode, Node node, TransferInput<CFValue, CFStore> in, CFStore store) {
+    if (node instanceof NumericalAdditionNode) {
+      Node right = ((NumericalAdditionNode) node).getRightOperand();
+      Node left = ((NumericalAdditionNode) node).getLeftOperand();
+      propagateToAdditionOperand(typeOfNode, left, right, in, store);
+      propagateToAdditionOperand(typeOfNode, right, left, in, store);
+    } else if (node instanceof NumericalSubtractionNode) {
+      propagateToSubtractionOperands(typeOfNode, (NumericalSubtractionNode) node, in, store);
+    } else if (node instanceof NumericalMultiplicationNode) {
+      if (atypeFactory.hasLowerBoundTypeByClass(node, Positive.class)) {
+        Node right = ((NumericalMultiplicationNode) node).getRightOperand();
+        Node left = ((NumericalMultiplicationNode) node).getLeftOperand();
+        propagateToMultiplicationOperand(typeOfNode, left, right, in, store);
+        propagateToMultiplicationOperand(typeOfNode, right, left, in, store);
+      }
+    }
+  }
+
+  /**
+   * {@code other} times {@code node} is known to be {@code typeOfMultiplication}.
+   *
+   * <p>This implies that if {@code other} is positive, then {@code node} is {@code
+   * typeOfMultiplication}. If {@code other} is greater than 1, then {@code node} is {@code
+   * typeOfMultiplication} plus 1. These are cases 2 and 3, respectively.
+   */
+  private void propagateToMultiplicationOperand(
+      LessThanLengthOf typeOfMultiplication,
+      Node node,
+      Node other,
+      TransferInput<CFValue, CFStore> in,
+      CFStore store) {
+    if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) {
+      Long minValue =
+          ValueCheckerUtils.getMinValue(
+              other.getTree(), atypeFactory.getValueAnnotatedTypeFactory());
+      if (minValue != null && minValue > 1) {
+        typeOfMultiplication = (LessThanLengthOf) typeOfMultiplication.plusOffset(1);
+      }
+      UBQualifier qual = getUBQualifier(node, in);
+      UBQualifier newQual = qual.glb(typeOfMultiplication);
+      JavaExpression je = JavaExpression.fromNode(node);
+      store.insertValue(je, atypeFactory.convertUBQualifierToAnnotation(newQual));
+    }
+  }
+
+  /**
+   * The subtraction node, {@code node}, is known to be {@code typeOfSubtraction}.
+   *
+   * <p>This means that the left node is less than or equal to the length of the array when the
+   * right node is subtracted from the left node. Note that unlike {@link
+   * #propagateToAdditionOperand(LessThanLengthOf, Node, Node, TransferInput, CFStore)} and {@link
+   * #propagateToMultiplicationOperand(LessThanLengthOf, Node, Node, TransferInput, CFStore)}, this
+   * method takes the NumericalSubtractionNode instead of the two operand nodes. This implements
+   * case 4.
+   *
+   * @param typeOfSubtraction type of node
+   * @param node subtraction node that has typeOfSubtraction
+   * @param in a TransferInput
+   * @param store location to store the refined type
+   */
+  private void propagateToSubtractionOperands(
+      LessThanLengthOf typeOfSubtraction,
+      NumericalSubtractionNode node,
+      TransferInput<CFValue, CFStore> in,
+      CFStore store) {
+    UBQualifier left = getUBQualifier(node.getLeftOperand(), in);
+    UBQualifier newInfo = typeOfSubtraction.minusOffset(node.getRightOperand(), atypeFactory);
+
+    UBQualifier newLeft = left.glb(newInfo);
+    JavaExpression leftJe = JavaExpression.fromNode(node.getLeftOperand());
+    store.insertValue(leftJe, atypeFactory.convertUBQualifierToAnnotation(newLeft));
+  }
+
+  /**
+   * Refines the type of {@code operand} to {@code typeOfAddition} plus {@code other}. If {@code
+   * other} is non-negative, then {@code operand} also less than the length of the arrays in {@code
+   * typeOfAddition}. If {@code other} is positive, then {@code operand} also less than the length
+   * of the arrays in {@code typeOfAddition} plus 1. These are cases 5, 6, and 7.
+   *
+   * @param typeOfAddition type of {@code operand + other}
+   * @param operand the Node to refine
+   * @param other the Node added to {@code operand}
+   * @param in a TransferInput
+   * @param store location to store the refined types
+   */
+  private void propagateToAdditionOperand(
+      LessThanLengthOf typeOfAddition,
+      Node operand,
+      Node other,
+      TransferInput<CFValue, CFStore> in,
+      CFStore store) {
+    UBQualifier operandQual = getUBQualifier(operand, in);
+    UBQualifier newQual = operandQual.glb(typeOfAddition.plusOffset(other, atypeFactory));
+
+    // If the node is NonNegative, add an LTEL to the qual. If Positive, add an LTL.
+    if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) {
+      newQual = newQual.glb(typeOfAddition.plusOffset(1));
+    } else if (atypeFactory.hasLowerBoundTypeByClass(other, NonNegative.class)) {
+      newQual = newQual.glb(typeOfAddition);
+    }
+    JavaExpression operandJe = JavaExpression.fromNode(operand);
+    store.insertValue(operandJe, atypeFactory.convertUBQualifierToAnnotation(newQual));
+  }
+
+  /**
+   * Case 8: if x &lt; y, and y has a type that is related to the length of an array, then x has the
+   * same type, with an offset that is one less.
+   */
+  @Override
+  protected void refineGT(
+      Node larger,
+      AnnotationMirror largerAnno,
+      Node smaller,
+      AnnotationMirror smallerAnno,
+      CFStore store,
+      TransferInput<CFValue, CFStore> in) {
+    // larger > smaller
+    UBQualifier largerQual =
+        UBQualifier.createUBQualifier(largerAnno, (UpperBoundChecker) atypeFactory.getChecker());
+    // larger + 1 >= smaller
+    UBQualifier largerQualPlus1 = largerQual.plusOffset(1);
+    UBQualifier rightQualifier =
+        UBQualifier.createUBQualifier(smallerAnno, (UpperBoundChecker) atypeFactory.getChecker());
+    UBQualifier refinedRight = rightQualifier.glb(largerQualPlus1);
+
+    if (largerQualPlus1.isLessThanLengthQualifier()) {
+      propagateToOperands((LessThanLengthOf) largerQualPlus1, smaller, in, store);
+    }
+
+    refineSubtrahendWithOffset(larger, smaller, true, in, store);
+
+    JavaExpression rightJe = JavaExpression.fromNode(smaller);
+    store.insertValue(rightJe, atypeFactory.convertUBQualifierToAnnotation(refinedRight));
+  }
+
+  /**
+   * Case 9: if x &le; y, and y has a type that is related to the length of an array, then x has the
+   * same type.
+   */
+  @Override
+  protected void refineGTE(
+      Node left,
+      AnnotationMirror leftAnno,
+      Node right,
+      AnnotationMirror rightAnno,
+      CFStore store,
+      TransferInput<CFValue, CFStore> in) {
+    UBQualifier leftQualifier =
+        UBQualifier.createUBQualifier(leftAnno, (UpperBoundChecker) atypeFactory.getChecker());
+    UBQualifier rightQualifier =
+        UBQualifier.createUBQualifier(rightAnno, (UpperBoundChecker) atypeFactory.getChecker());
+    UBQualifier refinedRight = rightQualifier.glb(leftQualifier);
+
+    if (leftQualifier.isLessThanLengthQualifier()) {
+      propagateToOperands((LessThanLengthOf) leftQualifier, right, in, store);
+    }
+
+    refineSubtrahendWithOffset(left, right, false, in, store);
+
+    JavaExpression rightJe = JavaExpression.fromNode(right);
+    store.insertValue(rightJe, atypeFactory.convertUBQualifierToAnnotation(refinedRight));
+  }
+
+  /**
+   * Refines the subtrahend in a subtraction which is greater than or equal to a certain offset. The
+   * type of the subtrahend is refined to the type of the minuend with the offset added. This is
+   * case 10.
+   *
+   * <p>This is based on the fact that if {@code (minuend - subtrahend) >= offset}, and {@code
+   * minuend + o < l}, then {@code subtrahend + o + offset < l}.
+   *
+   * <p>If {@code gtNode} is not a {@link NumericalSubtractionNode}, the method does nothing.
+   *
+   * @param gtNode the node that is greater or equal to the offset
+   * @param offsetNode a node part of the offset
+   * @param offsetAddOne whether to add one to the offset
+   * @param in input of the transfer function
+   * @param store location to store the refined types
+   */
+  private void refineSubtrahendWithOffset(
+      Node gtNode,
+      Node offsetNode,
+      boolean offsetAddOne,
+      TransferInput<CFValue, CFStore> in,
+      CFStore store) {
+    if (gtNode instanceof NumericalSubtractionNode) {
+      NumericalSubtractionNode subtractionNode = (NumericalSubtractionNode) gtNode;
+
+      Node minuend = subtractionNode.getLeftOperand();
+      UBQualifier minuendQual = getUBQualifier(minuend, in);
+      Node subtrahend = subtractionNode.getRightOperand();
+      UBQualifier subtrahendQual = getUBQualifier(subtrahend, in);
+
+      UBQualifier newQual =
+          subtrahendQual.glb(
+              minuendQual.plusOffset(offsetNode, atypeFactory).plusOffset(offsetAddOne ? 1 : 0));
+      JavaExpression subtrahendJe = JavaExpression.fromNode(subtrahend);
+      store.insertValue(subtrahendJe, atypeFactory.convertUBQualifierToAnnotation(newQual));
+    }
+  }
+
+  /** Implements case 11. */
+  @Override
+  protected TransferResult<CFValue, CFStore> strengthenAnnotationOfEqualTo(
+      TransferResult<CFValue, CFStore> res,
+      Node firstNode,
+      Node secondNode,
+      CFValue firstValue,
+      CFValue secondValue,
+      boolean notEqualTo) {
+    TransferResult<CFValue, CFStore> result =
+        super.strengthenAnnotationOfEqualTo(
+            res, firstNode, secondNode, firstValue, secondValue, notEqualTo);
+    IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, firstNode, secondNode);
+    if (rfi.leftAnno == null || rfi.rightAnno == null) {
+      return result;
+    }
+
+    CFStore equalsStore = notEqualTo ? rfi.elseStore : rfi.thenStore;
+    CFStore notEqualStore = notEqualTo ? rfi.thenStore : rfi.elseStore;
+
+    refineEq(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, equalsStore);
+
+    refineNeqSequenceLength(rfi.left, rfi.right, rfi.rightAnno, notEqualStore);
+    refineNeqSequenceLength(rfi.right, rfi.left, rfi.leftAnno, notEqualStore);
+    return rfi.newResult;
+  }
+
+  /** Refines the type of the left and right node to glb of the left and right annotation. */
+  private void refineEq(
+      Node left, AnnotationMirror leftAnno, Node right, AnnotationMirror rightAnno, CFStore store) {
+    UBQualifier leftQualifier =
+        UBQualifier.createUBQualifier(leftAnno, (UpperBoundChecker) atypeFactory.getChecker());
+    UBQualifier rightQualifier =
+        UBQualifier.createUBQualifier(rightAnno, (UpperBoundChecker) atypeFactory.getChecker());
+    UBQualifier glb = rightQualifier.glb(leftQualifier);
+    AnnotationMirror glbAnno = atypeFactory.convertUBQualifierToAnnotation(glb);
+
+    List<Node> internalsRight = splitAssignments(right);
+    for (Node internal : internalsRight) {
+      JavaExpression rightJe = JavaExpression.fromNode(internal);
+      store.insertValue(rightJe, glbAnno);
+    }
+
+    List<Node> internalsLeft = splitAssignments(left);
+    for (Node internal : internalsLeft) {
+      JavaExpression leftJe = JavaExpression.fromNode(internal);
+      store.insertValue(leftJe, glbAnno);
+    }
+  }
+
+  /**
+   * If lengthAccess node is an sequence length field or method access (optionally with a constant
+   * offset subtracted) and the other node is less than or equal to that sequence length (minus the
+   * offset), then refine the other node's type to less than the sequence length (minus the offset).
+   * This is case 12.
+   */
+  private void refineNeqSequenceLength(
+      Node lengthAccess, Node otherNode, AnnotationMirror otherNodeAnno, CFStore store) {
+
+    // If lengthAccess is "receiver.length - c" where c is an integer constant,
+    // then lengthOffset is "c".
+    int lengthOffset = 0;
+    if (lengthAccess instanceof NumericalSubtractionNode) {
+      NumericalSubtractionNode subtraction = (NumericalSubtractionNode) lengthAccess;
+      Node offsetNode = subtraction.getRightOperand();
+      Long offsetValue =
+          ValueCheckerUtils.getExactValue(
+              offsetNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory());
+      if (offsetValue != null
+          && offsetValue > Integer.MIN_VALUE
+          && offsetValue <= Integer.MAX_VALUE) {
+        lengthOffset = offsetValue.intValue();
+        lengthAccess = subtraction.getLeftOperand();
+      } else {
+        // Subtraction of non-constant expressions is not supported
+        return;
+      }
+    }
+
+    JavaExpression receiver = null;
+    if (NodeUtils.isArrayLengthFieldAccess(lengthAccess)) {
+      FieldAccess fa =
+          (FieldAccess) JavaExpression.fromNodeFieldAccess((FieldAccessNode) lengthAccess);
+      receiver = fa.getReceiver();
+
+    } else if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(lengthAccess)) {
+      JavaExpression ma = JavaExpression.fromNode(lengthAccess);
+      if (ma instanceof MethodCall) {
+        receiver = ((MethodCall) ma).getReceiver();
+      }
+    }
+
+    if (receiver != null && !receiver.containsUnknown()) {
+      UBQualifier otherQualifier =
+          UBQualifier.createUBQualifier(
+              otherNodeAnno, (UpperBoundChecker) atypeFactory.getChecker());
+      String sequence = receiver.toString();
+      // Check if otherNode + c - 1 < receiver.length
+      if (otherQualifier.hasSequenceWithOffset(sequence, lengthOffset - 1)) {
+        // Add otherNode + c < receiver.length
+        UBQualifier newQualifier =
+            UBQualifier.createUBQualifier(sequence, Integer.toString(lengthOffset));
+        otherQualifier = otherQualifier.glb(newQualifier);
+        for (Node internal : splitAssignments(otherNode)) {
+          JavaExpression leftJe = JavaExpression.fromNode(internal);
+          store.insertValue(leftJe, atypeFactory.convertUBQualifierToAnnotation(otherQualifier));
+        }
+      }
+    }
+  }
+
+  /**
+   * If some Node a is known to be less than the length of some array, x, then, the type of a + b,
+   * is @LTLengthOf(value="x", offset="-b"). If b is known to be less than the length of some other
+   * array, y, then the type of a + b is @LTLengthOf(value={"x", "y"}, offset={"-b", "-a"}).
+   *
+   * <p>Alternatively, if a is known to be less than the length of x when some offset, o, is added
+   * to a (the type of a is @LTLengthOf(value="x", offset="o")), then the type of a + b
+   * is @LTLengthOf(value="x",offset="o - b"). (Note, if "o - b" can be computed, then it is and the
+   * result is used in the annotation.)
+   *
+   * <p>In addition, If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int
+   * and expression j is less than or equal to the length of f1, then the type of i + j is
+   * .@LTLengthOf("f2").
+   *
+   * <p>These three cases correspond to cases 13-15.
+   */
+  @Override
+  public TransferResult<CFValue, CFStore> visitNumericalAddition(
+      NumericalAdditionNode n, TransferInput<CFValue, CFStore> in) {
+    // type of leftNode + rightNode  is  glb(t, s) where
+    // t = minusOffset(type(leftNode), rightNode) and
+    // s = minusOffset(type(rightNode), leftNode)
+
+    UBQualifier left = getUBQualifierForAddition(n.getLeftOperand(), in);
+    UBQualifier t = left.minusOffset(n.getRightOperand(), atypeFactory);
+
+    UBQualifier right = getUBQualifierForAddition(n.getRightOperand(), in);
+    UBQualifier s = right.minusOffset(n.getLeftOperand(), atypeFactory);
+
+    UBQualifier glb = t.glb(s);
+    if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) {
+      // If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int and expression
+      // j is less than or equal to the length of f1, then the type of i + j is @LTLengthOf("f2").
+      UBQualifier r = removeSequenceLengths((LessThanLengthOf) left, (LessThanLengthOf) right);
+      glb = glb.glb(r);
+      UBQualifier l = removeSequenceLengths((LessThanLengthOf) right, (LessThanLengthOf) left);
+      glb = glb.glb(l);
+    }
+
+    return createTransferResult(n, in, glb);
+  }
+
+  /**
+   * Return the result of adding i to j.
+   *
+   * <p>When expression i has type {@code @LTLengthOf(value = "f2", offset = "f1.length") int} and
+   * expression j is less than or equal to the length of f1, then the type of i + j
+   * is @LTLengthOf("f2").
+   *
+   * <p>When expression i has type {@code @LTLengthOf (value = "f2", offset = "f1.length - 1") int}
+   * and expression j is less than the length of f1, then the type of i + j is @LTLengthOf("f2").
+   *
+   * @param i the type of the expression added to j
+   * @param j the type of the expression added to i
+   * @return the type of i + j
+   */
+  private UBQualifier removeSequenceLengths(LessThanLengthOf i, LessThanLengthOf j) {
+    List<String> lessThan = new ArrayList<>();
+    List<String> lessThanOrEqual = new ArrayList<>();
+    for (String sequence : i.getSequences()) {
+      if (i.isLessThanLengthOf(sequence)) {
+        lessThan.add(sequence);
+      } else if (i.hasSequenceWithOffset(sequence, -1)) {
+        lessThanOrEqual.add(sequence);
+      }
+    }
+    // Creates a qualifier that is the same a j with the array.length offsets removed. If
+    // an offset doesn't have an array.length, then the offset/array pair is removed. If
+    // there are no such pairs, Unknown is returned.
+    UBQualifier lessThanEqQ = j.removeSequenceLengthAccess(lessThanOrEqual);
+    // Creates a qualifier that is the same a j with the array.length - 1 offsets removed. If
+    // an offset doesn't have an array.length, then the offset/array pair is removed. If
+    // there are no such pairs, Unknown is returned.
+    UBQualifier lessThanQ = j.removeSequenceLengthAccessAndNeg1(lessThan);
+
+    return lessThanEqQ.glb(lessThanQ);
+  }
+
+  /**
+   * If some Node a is known to be less than the length of some sequence x, then the type of a - b
+   * is @LTLengthOf(value="x", offset="b"). If b is known to be less than the length of some other
+   * sequence, this doesn't add any information about the type of a - b. But, if b is non-negative
+   * or positive, then a - b should keep the types of a. This corresponds to cases 16 and 17.
+   */
+  @Override
+  public TransferResult<CFValue, CFStore> visitNumericalSubtraction(
+      NumericalSubtractionNode n, TransferInput<CFValue, CFStore> in) {
+    UBQualifier left = getUBQualifier(n.getLeftOperand(), in);
+    UBQualifier leftWithOffset = left.plusOffset(n.getRightOperand(), atypeFactory);
+    if (atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), NonNegative.class)
+        || atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), Positive.class)) {
+      // If the right side of the expression is NN or POS, then all the left side's
+      // annotations should be kept.
+      if (left.isLessThanLengthQualifier()) {
+        leftWithOffset = left.glb(leftWithOffset);
+      }
+    }
+
+    // If the result of a numerical subtraction would be LTEL(b) or LTL(b), and b is HSS(a,
+    // from, to), and the subtraction node itself is i - from where i is LTEL(b), then the
+    // result is LTEL(a).  If i is LTL(b) instead, the result is LTL(a).
+
+    if (leftWithOffset.isLessThanLengthQualifier()) {
+
+      LessThanLengthOf subtractionResult = (LessThanLengthOf) leftWithOffset;
+
+      for (String b : subtractionResult.getSequences()) {
+        if (subtractionResult.hasSequenceWithOffset(b, -1)
+            || subtractionResult.hasSequenceWithOffset(b, 0)) {
+
+          TreePath currentPath = this.atypeFactory.getPath(n.getTree());
+          JavaExpression je;
+          try {
+            je = UpperBoundVisitor.parseJavaExpressionString(b, atypeFactory, currentPath);
+          } catch (NullPointerException npe) {
+            // I have no idea why this seems to happen only on a few JDK classes.  It appears to
+            // only happen during the preprocessing step - the NPE is thrown while trying to find
+            // the enclosing class of a class tree, which is null. I can't find a reproducible test
+            // case that's smaller than the size of DualPivotQuicksort.  Since this refinement is
+            // optional, but useful elsewhere, catching this NPE here and returning is always safe.
+            return createTransferResult(n, in, leftWithOffset);
+          }
+
+          Subsequence subsequence = Subsequence.getSubsequenceFromReceiver(je, atypeFactory);
+
+          if (subsequence != null) {
+            String from = subsequence.from;
+            String to = subsequence.to;
+            String a = subsequence.array;
+
+            JavaExpression leftOp = JavaExpression.fromNode(n.getLeftOperand());
+            JavaExpression rightOp = JavaExpression.fromNode(n.getRightOperand());
+
+            if (rightOp.toString().equals(from)) {
+              LessThanAnnotatedTypeFactory lessThanAtypeFactory =
+                  atypeFactory.getLessThanAnnotatedTypeFactory();
+              AnnotationMirror lessThanType =
+                  lessThanAtypeFactory
+                      .getAnnotatedType(n.getLeftOperand().getTree())
+                      .getAnnotation(LessThan.class);
+
+              if (lessThanType != null && lessThanAtypeFactory.isLessThan(lessThanType, to)) {
+                UBQualifier ltlA = UBQualifier.createUBQualifier(a, "0");
+                leftWithOffset = leftWithOffset.glb(ltlA);
+              } else if (leftOp.toString().equals(to)
+                  || (lessThanType != null
+                      && lessThanAtypeFactory.isLessThanOrEqual(lessThanType, to))) {
+                // It's necessary to check if leftOp == to because LessThan doesn't
+                // infer that things are less than or equal to themselves.
+                UBQualifier ltelA = UBQualifier.createUBQualifier(a, "-1");
+                leftWithOffset = leftWithOffset.glb(ltelA);
+              }
+            }
+          }
+        }
+      }
+    }
+    return createTransferResult(n, in, leftWithOffset);
+  }
+
+  /**
+   * Computes a type of a sequence length access. This is case 18.
+   *
+   * @param n sequence length access node
+   */
+  private TransferResult<CFValue, CFStore> visitLengthAccess(
+      Node n, TransferInput<CFValue, CFStore> in, JavaExpression sequenceJe, Tree sequenceTree) {
+    if (sequenceTree == null) {
+      return null;
+    }
+    // Look up the SameLen type of the sequence.
+    AnnotationMirror sameLenAnno = atypeFactory.sameLenAnnotationFromTree(sequenceTree);
+    List<String> sameLenSequences;
+    if (sameLenAnno == null) {
+      sameLenSequences = Collections.singletonList(sequenceJe.toString());
+    } else {
+      sameLenSequences =
+          AnnotationUtils.getElementValueArray(
+              sameLenAnno, atypeFactory.sameLenValueElement, String.class);
+      String sequenceString = sequenceJe.toString();
+      if (!sameLenSequences.contains(sequenceString)) {
+        sameLenSequences.add(sequenceString);
+      }
+    }
+
+    List<String> offsets = Collections.nCopies(sameLenSequences.size(), "-1");
+
+    if (CFAbstractStore.canInsertJavaExpression(sequenceJe)) {
+      UBQualifier qualifier = UBQualifier.createUBQualifier(sameLenSequences, offsets);
+      UBQualifier previous = getUBQualifier(n, in);
+      return createTransferResult(n, in, qualifier.glb(previous));
+    }
+
+    return null;
+  }
+
+  /**
+   * If n is an array length field access, then the type of a.length is the glb
+   * of @LTEqLengthOf("a") and the value of a.length in the store. This is case 19.
+   */
+  @Override
+  public TransferResult<CFValue, CFStore> visitFieldAccess(
+      FieldAccessNode n, TransferInput<CFValue, CFStore> in) {
+    if (NodeUtils.isArrayLengthFieldAccess(n)) {
+      FieldAccess arrayLength = (FieldAccess) JavaExpression.fromNodeFieldAccess(n);
+      JavaExpression arrayJe = arrayLength.getReceiver();
+      Tree arrayTree = n.getReceiver().getTree();
+      TransferResult<CFValue, CFStore> result = visitLengthAccess(n, in, arrayJe, arrayTree);
+      if (result != null) {
+        return result;
+      }
+    }
+    return super.visitFieldAccess(n, in);
+  }
+
+  /**
+   * If n is a String.length() method invocation, then the type of s.length() is the glb
+   * of @LTEqLengthOf("s") and the value of s.length() in the store. This is case 20.
+   */
+  @Override
+  public TransferResult<CFValue, CFStore> visitMethodInvocation(
+      MethodInvocationNode n, TransferInput<CFValue, CFStore> in) {
+
+    if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(n)) {
+      JavaExpression stringLength = JavaExpression.fromNode(n);
+      if (stringLength instanceof MethodCall) {
+        JavaExpression receiverJe = ((MethodCall) stringLength).getReceiver();
+        Tree receiverTree = n.getTarget().getReceiver().getTree();
+        // receiverTree is null when the receiver is implicit "this".
+        if (receiverTree != null) {
+          TransferResult<CFValue, CFStore> result =
+              visitLengthAccess(n, in, receiverJe, receiverTree);
+          if (result != null) {
+            return result;
+          }
+        }
+      }
+    }
+    return super.visitMethodInvocation(n, in);
+  }
+
+  /**
+   * Returns the UBQualifier for a node, with additional refinement useful specifically for integer
+   * addition, based on the information from subcheckers of the Index Checker.
+   *
+   * @param n the node
+   * @param in dataflow analysis transfer input
+   * @return the UBQualifier for {@code node}
+   */
+  private UBQualifier getUBQualifierForAddition(Node n, TransferInput<CFValue, CFStore> in) {
+
+    // The method takes the greatest lower bound of the qualifier returned by
+    // getUBQualifier and a qualifier created from a SubstringIndexFor annotation, if such
+    // annotation is present and the index is known to be non-negative.
+
+    UBQualifier ubQualifier = getUBQualifier(n, in);
+    Tree nodeTree = n.getTree();
+    // Annotation from the Substring Index hierarchy
+    AnnotatedTypeMirror substringIndexType =
+        atypeFactory.getSubstringIndexAnnotatedTypeFactory().getAnnotatedType(nodeTree);
+    AnnotationMirror substringIndexAnno = substringIndexType.getAnnotation(SubstringIndexFor.class);
+    // Annotation from the Lower bound hierarchy
+    AnnotatedTypeMirror lowerBoundType =
+        atypeFactory.getLowerBoundAnnotatedTypeFactory().getAnnotatedType(nodeTree);
+    // If the index has an SubstringIndexFor annotation and at the same time is non-negative,
+    // convert the SubstringIndexFor annotation to a upper bound qualifier.
+    if (substringIndexAnno != null
+        && (lowerBoundType.hasAnnotation(NonNegative.class)
+            || lowerBoundType.hasAnnotation(Positive.class))) {
+      UBQualifier substringIndexQualifier =
+          UBQualifier.createUBQualifier(
+              substringIndexAnno, (UpperBoundChecker) atypeFactory.getChecker());
+      ubQualifier = ubQualifier.glb(substringIndexQualifier);
+    }
+    return ubQualifier;
+  }
+
+  /**
+   * Returns the UBQualifier for node. It does this by finding a {@link CFValue} for node. First it
+   * checks the store in the transfer input. If one isn't there, the analysis is checked. If the
+   * UNKNOWN qualifier is returned, then the AnnotatedTypeMirror from the type factory is used.
+   *
+   * @param n node
+   * @param in transfer input
+   * @return the UBQualifier for node
+   */
+  private UBQualifier getUBQualifier(Node n, TransferInput<CFValue, CFStore> in) {
+    QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy();
+    JavaExpression je = JavaExpression.fromNode(n);
+    CFValue value = null;
+    if (CFAbstractStore.canInsertJavaExpression(je)) {
+      value = in.getRegularStore().getValue(je);
+    }
+    if (value == null) {
+      value = analysis.getValue(n);
+    }
+    UBQualifier qualifier = getUBQualifier(hierarchy, value);
+    if (qualifier.isUnknown()) {
+      // The qualifier from the store or analysis might be UNKNOWN if there was some error.
+      // For example,
+      //   @LTLength("a") int i = 4;  // error
+      // The type of i in the store is @UpperBoundUnknown, but the type of i as computed by
+      // the type factory is @LTLength("a"), so use that type.
+      CFValue valueFromFactory = getValueFromFactory(n.getTree(), n);
+      return getUBQualifier(hierarchy, valueFromFactory);
+    }
+    return qualifier;
+  }
+
+  private UBQualifier getUBQualifier(QualifierHierarchy hierarchy, CFValue value) {
+    if (value == null) {
+      return UpperBoundUnknownQualifier.UNKNOWN;
+    }
+    Set<AnnotationMirror> set = value.getAnnotations();
+    AnnotationMirror anno = hierarchy.findAnnotationInHierarchy(set, atypeFactory.UNKNOWN);
+    if (anno == null) {
+      return UpperBoundUnknownQualifier.UNKNOWN;
+    }
+    return UBQualifier.createUBQualifier(anno, (UpperBoundChecker) atypeFactory.getChecker());
+  }
+
+  private TransferResult<CFValue, CFStore> createTransferResult(
+      Node n, TransferInput<CFValue, CFStore> in, UBQualifier qualifier) {
+    AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(qualifier);
+    CFValue value = analysis.createSingleAnnotationValue(newAnno, n.getType());
+    return createTransferResult(value, in);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitCase(
+      CaseNode n, TransferInput<CFValue, CFStore> in) {
+    TransferResult<CFValue, CFStore> result = super.visitCase(n, in);
+    // Refines subtrahend in the switch expression
+    // TODO: This cannot be done in strengthenAnnotationOfEqualTo, because that does not provide
+    // transfer input.
+    Node caseNode = n.getCaseOperand();
+    AssignmentNode assign = (AssignmentNode) n.getSwitchOperand();
+    Node switchNode = assign.getExpression();
+    refineSubtrahendWithOffset(switchNode, caseNode, false, in, result.getThenStore());
+    return result;
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitIntegerLiteral(
+      IntegerLiteralNode n, TransferInput<CFValue, CFStore> pi) {
+    TransferResult<CFValue, CFStore> result = super.visitIntegerLiteral(n, pi);
+
+    int intValue = n.getValue();
+    AnnotationMirror newAnno;
+    switch (intValue) {
+      case 0:
+        newAnno = atypeFactory.ZERO;
+        break;
+      case -1:
+        newAnno = atypeFactory.NEGATIVEONE;
+        break;
+      default:
+        return result;
+    }
+    CFValue c = new CFValue(analysis, Collections.singleton(newAnno), intTM);
+    return new RegularTransferResult<>(c, result.getRegularStore());
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java
new file mode 100644
index 0000000..18b960e
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java
@@ -0,0 +1,514 @@
+package org.checkerframework.checker.index.upperbound;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.util.TreePath;
+import java.util.Collections;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.index.Subsequence;
+import org.checkerframework.checker.index.qual.HasSubsequence;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.samelen.SameLenAnnotatedTypeFactory;
+import org.checkerframework.checker.index.upperbound.UBQualifier.LessThanLengthOf;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
+import org.checkerframework.common.value.ValueCheckerUtils;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.ThisReference;
+import org.checkerframework.dataflow.expression.ValueLiteral;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.framework.util.StringToJavaExpression;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+
+/** Warns about array accesses that could be too high. */
+public class UpperBoundVisitor extends BaseTypeVisitor<UpperBoundAnnotatedTypeFactory> {
+
+  private static final @CompilerMessageKey String UPPER_BOUND = "array.access.unsafe.high";
+  private static final @CompilerMessageKey String UPPER_BOUND_CONST =
+      "array.access.unsafe.high.constant";
+  private static final @CompilerMessageKey String UPPER_BOUND_RANGE =
+      "array.access.unsafe.high.range";
+  private static final @CompilerMessageKey String TO_NOT_LTEL = "to.not.ltel";
+  private static final @CompilerMessageKey String NOT_FINAL = "not.final";
+  private static final @CompilerMessageKey String HSS = "which.subsequence";
+
+  public UpperBoundVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  /**
+   * When the visitor reaches an array access, it needs to check a couple of things. First, it
+   * checks if the index has been assigned a reasonable UpperBound type: only an index with type
+   * LTLengthOf(arr) is safe to access arr. If that fails, it checks if the access is still safe. To
+   * do so, it checks if the Value Checker knows the minimum length of arr by querying the Value
+   * Annotated Type Factory. If the minimum length of the array is known, the visitor can check if
+   * the index is less than that minimum length. If so, then the access is still safe. Otherwise,
+   * report a potential unsafe access.
+   */
+  @Override
+  public Void visitArrayAccess(ArrayAccessTree tree, Void type) {
+    ExpressionTree indexTree = tree.getIndex();
+    ExpressionTree arrTree = tree.getExpression();
+    visitAccess(indexTree, arrTree);
+    return super.visitArrayAccess(tree, type);
+  }
+
+  /** Warns about LTLengthOf annotations with arguments whose lengths do not match. */
+  @Override
+  public Void visitAnnotation(AnnotationTree node, Void p) {
+    AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(node);
+    if (atypeFactory.areSameByClass(anno, LTLengthOf.class)) {
+      List<? extends ExpressionTree> args = node.getArguments();
+      if (args.size() == 2) {
+        // If offsets are provided, there must be the same number of them as there are arrays.
+        List<String> sequences =
+            AnnotationUtils.getElementValueArray(
+                anno, atypeFactory.ltLengthOfValueElement, String.class);
+        List<String> offsets =
+            AnnotationUtils.getElementValueArray(
+                anno, atypeFactory.ltLengthOfOffsetElement, String.class, Collections.emptyList());
+        if (sequences.size() != offsets.size() && !offsets.isEmpty()) {
+          checker.reportError(
+              node, "different.length.sequences.offsets", sequences.size(), offsets.size());
+          return null;
+        }
+      }
+    } else if (atypeFactory.areSameByClass(anno, HasSubsequence.class)) {
+      // Check that the arguments to a HasSubsequence annotation are valid JavaExpressions,
+      // and issue an error if one of them is not.
+
+      String seq = atypeFactory.hasSubsequenceSubsequenceValue(anno);
+      String from = atypeFactory.hasSubsequenceFromValue(anno);
+      String to = atypeFactory.hasSubsequenceToValue(anno);
+
+      // check that each expression is parsable at the declaration of this class
+      ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath());
+      checkEffectivelyFinalAndParsable(seq, enclosingClass, node);
+      checkEffectivelyFinalAndParsable(from, enclosingClass, node);
+      checkEffectivelyFinalAndParsable(to, enclosingClass, node);
+    }
+    return super.visitAnnotation(node, p);
+  }
+
+  /**
+   * Reports an error if the Java expression named by s is not effectively final when parsed at the
+   * declaration of the given class.
+   *
+   * @param s a Java expression
+   * @param classTree the expression is parsed with respect to this class
+   * @param whereToReportError the tree at which to possibly report an error
+   */
+  private void checkEffectivelyFinalAndParsable(
+      String s, ClassTree classTree, Tree whereToReportError) {
+    JavaExpression je;
+    try {
+      je =
+          StringToJavaExpression.atTypeDecl(
+              s, TreeUtils.elementFromDeclaration(classTree), checker);
+    } catch (JavaExpressionParseException e) {
+      checker.report(whereToReportError, e.getDiagMessage());
+      return;
+    }
+    Element element = null;
+    if (je instanceof LocalVariable) {
+      element = ((LocalVariable) je).getElement();
+    } else if (je instanceof FieldAccess) {
+      element = ((FieldAccess) je).getField();
+    } else if (je instanceof ThisReference || je instanceof ValueLiteral) {
+      return;
+    }
+    if (element == null || !ElementUtils.isEffectivelyFinal(element)) {
+      checker.reportError(whereToReportError, NOT_FINAL, je);
+    }
+  }
+
+  /**
+   * Checks if this array access is legal. Uses the common assignment check and a simple MinLen
+   * check of its own. The MinLen check is needed because the common assignment check always returns
+   * false when the upper bound qualifier is @UpperBoundUnknown.
+   *
+   * @param indexTree the array index
+   * @param arrTree the array
+   */
+  private void visitAccess(ExpressionTree indexTree, ExpressionTree arrTree) {
+
+    String arrName = JavaExpression.fromTree(arrTree).toString();
+    LessThanLengthOf lhsQual = (LessThanLengthOf) UBQualifier.createUBQualifier(arrName, "0");
+    if (relaxedCommonAssignmentCheck(lhsQual, indexTree) || checkMinLen(indexTree, arrTree)) {
+      return;
+    } // else issue errors.
+
+    // We can issue three different errors:
+    // 1. If the index is a compile-time constant, issue an error that describes the array type.
+    // 2. If the index is a compile-time range and has no upperbound qualifier,
+    //    issue an error that names the upperbound of the range and the array's type.
+    // 3. If neither of the above, issue an error that names the upper bound type.
+
+    AnnotatedTypeMirror indexType = atypeFactory.getAnnotatedType(indexTree);
+    UBQualifier qualifier =
+        UBQualifier.createUBQualifier(indexType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker);
+    ValueAnnotatedTypeFactory valueFactory = atypeFactory.getValueAnnotatedTypeFactory();
+    Long valMax = ValueCheckerUtils.getMaxValue(indexTree, valueFactory);
+
+    if (ValueCheckerUtils.getExactValue(indexTree, valueFactory) != null) {
+      // Note that valMax is equal to the exact value in this case.
+      checker.reportError(
+          indexTree,
+          UPPER_BOUND_CONST,
+          valMax,
+          valueFactory.getAnnotatedType(arrTree).toString(),
+          valMax + 1,
+          valMax + 1);
+    } else if (valMax != null && qualifier.isUnknown() && valMax != Integer.MAX_VALUE) {
+
+      checker.reportError(
+          indexTree,
+          UPPER_BOUND_RANGE,
+          valueFactory.getAnnotatedType(indexTree).toString(),
+          valueFactory.getAnnotatedType(arrTree).toString(),
+          arrName,
+          arrName,
+          valMax + 1);
+    } else {
+      checker.reportError(indexTree, UPPER_BOUND, indexType.toString(), arrName, arrName, arrName);
+    }
+  }
+
+  @Override
+  protected void commonAssignmentCheck(
+      Tree varTree,
+      ExpressionTree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+
+    // check that when an assignment to a variable b declared as @HasSubsequence(a, from, to)
+    // occurs, to <= a.length, i.e. to is @LTEqLengthOf(a).
+
+    Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory);
+    if (subSeq != null) {
+      AnnotationMirror anm;
+      try {
+        anm =
+            atypeFactory.getAnnotationMirrorFromJavaExpressionString(
+                subSeq.to, varTree, getCurrentPath());
+      } catch (JavaExpressionParseException e) {
+        anm = null;
+      }
+
+      boolean ltelCheckFailed = true;
+      if (anm != null) {
+        UBQualifier qual = UBQualifier.createUBQualifier(anm, (UpperBoundChecker) checker);
+        ltelCheckFailed = !qual.isLessThanOrEqualTo(subSeq.array);
+      }
+
+      if (ltelCheckFailed) {
+        // issue an error
+        checker.reportError(
+            valueTree,
+            TO_NOT_LTEL,
+            subSeq.to,
+            subSeq.array,
+            anm == null ? "@UpperBoundUnknown" : anm,
+            subSeq.array,
+            subSeq.array,
+            subSeq.array);
+      } else {
+        checker.reportWarning(
+            valueTree,
+            HSS,
+            subSeq.array,
+            subSeq.from,
+            subSeq.from,
+            subSeq.to,
+            subSeq.to,
+            subSeq.array,
+            subSeq.array);
+      }
+    }
+
+    super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs);
+  }
+
+  @Override
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      ExpressionTree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueTree);
+    commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree);
+    if (!relaxedCommonAssignment(varType, valueTree)) {
+      commonAssignmentCheckEndDiagnostic(
+          "relaxedCommonAssignment did not succeed, now must call super",
+          varType,
+          valueType,
+          valueTree);
+      super.commonAssignmentCheck(varType, valueTree, errorKey, extraArgs);
+    } else if (checker.hasOption("showchecks")) {
+      commonAssignmentCheckEndDiagnostic(
+          true, "relaxedCommonAssignment", varType, valueType, valueTree);
+    }
+  }
+
+  /**
+   * Returns whether the assignment is legal based on the relaxed assignment rules.
+   *
+   * <p>The relaxed assignment rules are the following: Assuming the varType (left-hand side) is
+   * less than the length of some array given some offset
+   *
+   * <p>1. If both the offset and the value expression (rhs) are ints known at compile time, and if
+   * the min length of the array is greater than offset + value, then the assignment is legal. (This
+   * method returns true.)
+   *
+   * <p>2. If the value expression (rhs) is less than the length of an array that is the same length
+   * as the array in the varType, and if the offsets are equal, then the assignment is legal. (This
+   * method returns true.)
+   *
+   * <p>3. Otherwise the assignment is only legal if the usual assignment rules are true, so this
+   * method returns false.
+   *
+   * <p>If the varType is less than the length of multiple arrays, then this method only returns
+   * true if the relaxed rules above apply for each array.
+   *
+   * <p>If the varType is an array type and the value expression is an array initializer, then the
+   * above rules are applied for expression in the initializer where the varType is the component
+   * type of the array.
+   *
+   * @param varType the type of the left-hand side (the variable in the assignment)
+   * @param valueExp the right-hand side (the expression in the assignment)
+   * @return true if the assignment is legal based on special Upper Bound rules
+   */
+  private boolean relaxedCommonAssignment(AnnotatedTypeMirror varType, ExpressionTree valueExp) {
+    if (valueExp.getKind() == Kind.NEW_ARRAY && varType.getKind() == TypeKind.ARRAY) {
+      List<? extends ExpressionTree> expressions = ((NewArrayTree) valueExp).getInitializers();
+      if (expressions == null || expressions.isEmpty()) {
+        return false;
+      }
+      // The qualifier we need for an array is in the component type, not varType.
+      AnnotatedTypeMirror componentType = ((AnnotatedArrayType) varType).getComponentType();
+      UBQualifier qualifier =
+          UBQualifier.createUBQualifier(
+              componentType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker);
+      if (!qualifier.isLessThanLengthQualifier()) {
+        return false;
+      }
+      for (ExpressionTree expressionTree : expressions) {
+        if (!relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, expressionTree)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    UBQualifier qualifier =
+        UBQualifier.createUBQualifier(varType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker);
+    return qualifier.isLessThanLengthQualifier()
+        && relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, valueExp);
+  }
+
+  /**
+   * Fetches a receiver and an offset from a String using the passed type factory. Returns null if
+   * there is a parse exception. This wraps GenericAnnotatedTypeFactory#parseJavaExpressionString.
+   *
+   * <p>This is useful for expressions like "n+1", for which {@link #parseJavaExpressionString}
+   * returns null because the whole expression is not a receiver.
+   */
+  static Pair<JavaExpression, String> getExpressionAndOffsetFromJavaExpressionString(
+      String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) {
+
+    Pair<String, String> p = AnnotatedTypeFactory.getExpressionAndOffset(s);
+
+    JavaExpression je = parseJavaExpressionString(p.first, atypeFactory, currentPath);
+    if (je == null) {
+      return null;
+    }
+    return Pair.of(je, p.second);
+  }
+
+  /**
+   * Fetches a receiver from a String using the passed type factory. Returns null if there is a
+   * parse exception -- that is, if the string does not represent an expression for a
+   * JavaExpression. For example, the expression "n+1" does not represent a JavaExpression.
+   *
+   * <p>This wraps GenericAnnotatedTypeFactory#parseJavaExpressionString.
+   */
+  static JavaExpression parseJavaExpressionString(
+      String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) {
+    JavaExpression result;
+    try {
+      result = atypeFactory.parseJavaExpressionString(s, currentPath);
+    } catch (JavaExpressionParseException e) {
+      result = null;
+    }
+    return result;
+  }
+
+  /*
+   *  Queries the Value Checker to determine if the maximum possible value of indexTree
+   *  is less than the minimum possible length of arrTree, and returns true if so.
+   */
+  private boolean checkMinLen(ExpressionTree indexTree, ExpressionTree arrTree) {
+    int minLen = ValueCheckerUtils.getMinLen(arrTree, atypeFactory.getValueAnnotatedTypeFactory());
+    Long valMax =
+        ValueCheckerUtils.getMaxValue(indexTree, atypeFactory.getValueAnnotatedTypeFactory());
+    return valMax != null && valMax < minLen;
+  }
+
+  /**
+   * Implements the actual check for the relaxed common assignment check. For what is permitted, see
+   * {@link #relaxedCommonAssignment}.
+   *
+   * @param varLtlQual the variable qualifier (the left-hand side of the assignment)
+   * @param valueExp the expression (the right-hand side of the assignment)
+   * @return true if the assignment is legal: varLtlQual is a supertype of the type of valueExp
+   */
+  private boolean relaxedCommonAssignmentCheck(
+      LessThanLengthOf varLtlQual, ExpressionTree valueExp) {
+
+    AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(valueExp);
+    UBQualifier expQual =
+        UBQualifier.createUBQualifier(expType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker);
+
+    UBQualifier lessThanQual = atypeFactory.fromLessThan(valueExp, getCurrentPath());
+    if (lessThanQual != null) {
+      expQual = expQual.glb(lessThanQual);
+    }
+
+    UBQualifier lessThanOrEqualQual = atypeFactory.fromLessThanOrEqual(valueExp, getCurrentPath());
+    if (lessThanOrEqualQual != null) {
+      expQual = expQual.glb(lessThanOrEqualQual);
+    }
+    if (expQual.isSubtype(varLtlQual)) {
+      return true;
+    }
+
+    // Take advantage of information available on a HasSubsequence(a, from, to) annotation
+    // on the lhs qualifier (varLtlQual):
+    // this allows us to show that iff varLtlQual includes LTL(b), b has HSS, and expQual includes
+    // LTL(a, -from), then the LTL(b) can be removed from varLtlQual.
+
+    UBQualifier newLHS = processSubsequenceForLHS(varLtlQual, expQual);
+    if (newLHS.isUnknown()) {
+      return true;
+    } else {
+      varLtlQual = (LessThanLengthOf) newLHS;
+    }
+
+    Long value =
+        ValueCheckerUtils.getMaxValue(valueExp, atypeFactory.getValueAnnotatedTypeFactory());
+
+    if (value == null && !expQual.isLessThanLengthQualifier()) {
+      return false;
+    }
+
+    SameLenAnnotatedTypeFactory sameLenFactory = atypeFactory.getSameLenAnnotatedTypeFactory();
+    ValueAnnotatedTypeFactory valueAnnotatedTypeFactory =
+        atypeFactory.getValueAnnotatedTypeFactory();
+    checkloop:
+    for (String sequenceName : varLtlQual.getSequences()) {
+
+      List<String> sameLenSequences =
+          sameLenFactory.getSameLensFromString(sequenceName, valueExp, getCurrentPath());
+      if (testSameLen(expQual, varLtlQual, sameLenSequences, sequenceName)) {
+        continue;
+      }
+
+      int minlen =
+          valueAnnotatedTypeFactory.getMinLenFromString(sequenceName, valueExp, getCurrentPath());
+      if (testMinLen(value, minlen, sequenceName, varLtlQual)) {
+        continue;
+      }
+      for (String sequence : sameLenSequences) {
+        int minlenSL =
+            valueAnnotatedTypeFactory.getMinLenFromString(sequence, valueExp, getCurrentPath());
+        if (testMinLen(value, minlenSL, sequenceName, varLtlQual)) {
+          continue checkloop;
+        }
+      }
+
+      return false;
+    }
+
+    return true;
+  }
+
+  /* Returns the new value of the left hand side after processing the arrays named in the lhs.
+   * Iff varLtlQual includes LTL(lhsSeq),
+   * lhsSeq has HSS, and expQual includes LTL(a, -from), then the LTL(lhsSeq) will be removed from varLtlQual
+   */
+  private UBQualifier processSubsequenceForLHS(LessThanLengthOf varLtlQual, UBQualifier expQual) {
+    UBQualifier newLHS = varLtlQual;
+    for (String lhsSeq : varLtlQual.getSequences()) {
+      // check is lhsSeq is an actual LTL
+      if (varLtlQual.hasSequenceWithOffset(lhsSeq, 0)) {
+
+        JavaExpression lhsSeqExpr =
+            parseJavaExpressionString(lhsSeq, atypeFactory, getCurrentPath());
+        Subsequence subSeq = Subsequence.getSubsequenceFromReceiver(lhsSeqExpr, atypeFactory);
+
+        if (subSeq != null) {
+          String from = subSeq.from;
+          String a = subSeq.array;
+          if (expQual.hasSequenceWithOffset(a, Subsequence.negateString(from))) {
+            // This cast is safe because LTLs cannot contain duplicates.
+            // Note that this updates newLHS on each iteration from its old value,
+            // so even if there are multiple HSS arrays the result will be correct.
+            newLHS = ((LessThanLengthOf) newLHS).removeOffset(lhsSeq, 0);
+          }
+        }
+      }
+    }
+    return newLHS;
+  }
+
+  /**
+   * Tests whether replacing any of the arrays in sameLenArrays with arrayName makes expQual
+   * equivalent to varQual.
+   */
+  private boolean testSameLen(
+      UBQualifier expQual, LessThanLengthOf varQual, List<String> sameLenArrays, String arrayName) {
+
+    if (!expQual.isLessThanLengthQualifier()) {
+      return false;
+    }
+
+    for (String sameLenArrayName : sameLenArrays) {
+      // Check whether replacing the value for any of the current type's offset results
+      // in the type we're trying to match.
+      if (varQual.isValidReplacement(arrayName, sameLenArrayName, (LessThanLengthOf) expQual)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Tests a constant value (value) against the minlen (minlens) of an array (arrayName) with a
+   * qualifier (varQual).
+   */
+  private boolean testMinLen(Long value, int minLen, String arrayName, LessThanLengthOf varQual) {
+    if (value == null) {
+      return false;
+    }
+    return varQual.isValuePlusOffsetLessThanMinLen(arrayName, value, minLen);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/messages.properties b/checker/src/main/java/org/checkerframework/checker/index/upperbound/messages.properties
new file mode 100644
index 0000000..5a3d60c
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/messages.properties
@@ -0,0 +1,8 @@
+### Error messages for the Upper Bound Checker
+array.access.unsafe.high=Potentially unsafe array access: the index could be larger than the array's bound%nfound   : %s%nrequired: @IndexFor("%s") or @LTLengthOf("%s") -- an integer less than %s's length
+array.access.unsafe.high.constant=Potentially unsafe array access: the constant index %s could be larger than the array's bound%nfound   : %s%nrequired: @MinLen(%s) -- an array guaranteed to have at least %s elements
+array.access.unsafe.high.range=Potentially unsafe array access: the index could be larger than the array's bound%nindex type found: %s%narray type found: %s%nrequired        : index of type @IndexFor("%s") or @LTLengthOf("%s"), or array of type @MinLen(%s)
+different.length.sequences.offsets=If offsets are provided, the annotation must contain the same number of sequences and offsets, but this annotation has %s sequence(s) and %s offset(s).
+to.not.ltel=While attempting to validate a subsequence type, the Upper Bound Checker could not prove that %s is less than or equal to the length of %s.%nfound   : %s%nrequired: @IndexOrHigh("%s") or @LTEqLengthOf("%s") -- an integer less than or equal to %s's length
+which.subsequence=The Index Checker cannot prove that %s is a subsequence. The Index Checker did prove that %s is non-negative, that %s is less than or equal to %s, and that %s is less than or equal to the length of %s. You should manually verify that %s is actually the name of the subsequence that the Index Checker verified, and then suppress this warning.
+not.final=The Index Checker cannot prove that the expression %s is effectively final, but it is used in a HasSubsequence expression.
diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java
new file mode 100644
index 0000000..a37ad4f
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java
@@ -0,0 +1,994 @@
+package org.checkerframework.checker.initialization;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.tree.JCTree;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+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.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.initialization.qual.FBCBottom;
+import org.checkerframework.checker.initialization.qual.Initialized;
+import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.NullnessAnnotatedTypeFactory;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAbstractValue;
+import org.checkerframework.framework.qual.Unused;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * The annotated type factory for the freedom-before-commitment type-system. The
+ * freedom-before-commitment type-system and this class are abstract and need to be combined with
+ * another type-system whose safe initialization should be tracked. For an example, see the {@link
+ * NullnessChecker}.
+ */
+public abstract class InitializationAnnotatedTypeFactory<
+        Value extends CFAbstractValue<Value>,
+        Store extends InitializationStore<Value, Store>,
+        Transfer extends InitializationTransfer<Value, Transfer, Store>,
+        Flow extends CFAbstractAnalysis<Value, Store, Transfer>>
+    extends GenericAnnotatedTypeFactory<Value, Store, Transfer, Flow> {
+
+  /** {@link UnknownInitialization}. */
+  protected final AnnotationMirror UNKNOWN_INITIALIZATION;
+
+  /** {@link Initialized}. */
+  protected final AnnotationMirror INITIALIZED;
+
+  /** {@link UnderInitialization} or null. */
+  protected final AnnotationMirror UNDER_INITALIZATION;
+
+  /** {@link NotOnlyInitialized} or null. */
+  protected final AnnotationMirror NOT_ONLY_INITIALIZED;
+
+  /** {@link FBCBottom}. */
+  protected final AnnotationMirror FBCBOTTOM;
+
+  /** The java.lang.Object type. */
+  protected final TypeMirror objectTypeMirror;
+
+  /** The Unused.when field/element. */
+  protected final ExecutableElement unusedWhenElement;
+  /** The UnderInitialization.value field/element. */
+  protected final ExecutableElement underInitializationValueElement;
+  /** The UnknownInitialization.value field/element. */
+  protected final ExecutableElement unknownInitializationValueElement;
+
+  /** Cache for the initialization annotations. */
+  protected final Set<Class<? extends Annotation>> initAnnos;
+
+  /**
+   * String representation of all initialization annotations.
+   *
+   * <p>{@link UnknownInitialization} {@link UnderInitialization} {@link Initialized} {@link
+   * FBCBottom}
+   *
+   * <p>This is used to quickly check of an AnnotationMirror is one of the initialization
+   * annotations without having to repeatedly convert them to strings.
+   */
+  protected final Set<String> initAnnoNames;
+
+  /**
+   * Create a new InitializationAnnotatedTypeFactory.
+   *
+   * @param checker the checker to which the new type factory belongs
+   */
+  protected InitializationAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker, true);
+
+    UNKNOWN_INITIALIZATION = AnnotationBuilder.fromClass(elements, UnknownInitialization.class);
+    INITIALIZED = AnnotationBuilder.fromClass(elements, Initialized.class);
+    UNDER_INITALIZATION = AnnotationBuilder.fromClass(elements, UnderInitialization.class);
+    NOT_ONLY_INITIALIZED = AnnotationBuilder.fromClass(elements, NotOnlyInitialized.class);
+    FBCBOTTOM = AnnotationBuilder.fromClass(elements, FBCBottom.class);
+
+    objectTypeMirror = processingEnv.getElementUtils().getTypeElement("java.lang.Object").asType();
+    unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, processingEnv);
+    underInitializationValueElement =
+        TreeUtils.getMethod(UnderInitialization.class, "value", 0, processingEnv);
+    unknownInitializationValueElement =
+        TreeUtils.getMethod(UnknownInitialization.class, "value", 0, processingEnv);
+
+    Set<Class<? extends Annotation>> tempInitAnnos = new LinkedHashSet<>(4);
+    tempInitAnnos.add(UnderInitialization.class);
+    tempInitAnnos.add(Initialized.class);
+    tempInitAnnos.add(UnknownInitialization.class);
+    tempInitAnnos.add(FBCBottom.class);
+
+    initAnnos = Collections.unmodifiableSet(tempInitAnnos);
+
+    Set<String> tempInitAnnoNames = new HashSet<>(4);
+    tempInitAnnoNames.add(AnnotationUtils.annotationName(UNKNOWN_INITIALIZATION));
+    tempInitAnnoNames.add(AnnotationUtils.annotationName(UNDER_INITALIZATION));
+    tempInitAnnoNames.add(AnnotationUtils.annotationName(INITIALIZED));
+    tempInitAnnoNames.add(AnnotationUtils.annotationName(FBCBOTTOM));
+
+    initAnnoNames = Collections.unmodifiableSet(tempInitAnnoNames);
+
+    // No call to postInit() because this class is abstract.
+    // Its subclasses must call postInit().
+  }
+
+  public Set<Class<? extends Annotation>> getInitializationAnnotations() {
+    return initAnnos;
+  }
+
+  /**
+   * Is the annotation {@code anno} an initialization qualifier?
+   *
+   * @param anno the annotation to check
+   * @return true if the argument is an initialization qualifier
+   */
+  protected boolean isInitializationAnnotation(AnnotationMirror anno) {
+    assert anno != null;
+    return initAnnoNames.contains(AnnotationUtils.annotationName(anno));
+  }
+
+  /*
+   * The following method can be used to appropriately configure the
+   * commitment type-system.
+   */
+
+  /**
+   * Returns the list of annotations that is forbidden for the constructor return type.
+   *
+   * @return the list of annotations that is forbidden for the constructor return type
+   */
+  public Set<Class<? extends Annotation>> getInvalidConstructorReturnTypeAnnotations() {
+    return getInitializationAnnotations();
+  }
+
+  /**
+   * Returns the annotation that makes up the invariant of this commitment type system, such as
+   * {@code @NonNull}.
+   *
+   * @return the invariant annotation for this type system
+   */
+  public abstract AnnotationMirror getFieldInvariantAnnotation();
+
+  /**
+   * Returns whether or not {@code field} has the invariant annotation.
+   *
+   * <p>This method is a convenience method for {@link
+   * #hasFieldInvariantAnnotation(AnnotatedTypeMirror, VariableElement)}.
+   *
+   * <p>If the {@code field} is a type variable, this method returns true if any possible
+   * instantiation of the type parameter could have the invariant annotation. See {@link
+   * NullnessAnnotatedTypeFactory#hasFieldInvariantAnnotation(VariableTree)} for an example.
+   *
+   * @param field field that might have invariant annotation
+   * @return whether or not field has the invariant annotation
+   */
+  protected final boolean hasFieldInvariantAnnotation(VariableTree field) {
+    AnnotatedTypeMirror type = getAnnotatedType(field);
+    VariableElement fieldElement = TreeUtils.elementFromDeclaration(field);
+    return hasFieldInvariantAnnotation(type, fieldElement);
+  }
+
+  /**
+   * Returns whether or not {@code type} has the invariant annotation.
+   *
+   * <p>If the {@code type} is a type variable, this method returns true if any possible
+   * instantiation of the type parameter could have the invariant annotation. See {@link
+   * NullnessAnnotatedTypeFactory#hasFieldInvariantAnnotation(VariableTree)} for an example.
+   *
+   * @param type of field that might have invariant annotation
+   * @param fieldElement the field element, which can be used to check annotations on the
+   *     declaration
+   * @return whether or not the type has the invariant annotation
+   */
+  protected abstract boolean hasFieldInvariantAnnotation(
+      AnnotatedTypeMirror type, VariableElement fieldElement);
+
+  /**
+   * Creates a {@link UnderInitialization} annotation with the given type as its type frame
+   * argument.
+   *
+   * @param typeFrame the type down to which some value has been initialized
+   * @return an {@link UnderInitialization} annotation with the given argument
+   */
+  public AnnotationMirror createUnderInitializationAnnotation(TypeMirror typeFrame) {
+    assert typeFrame != null;
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class);
+    builder.setValue("value", typeFrame);
+    return builder.build();
+  }
+
+  /**
+   * Creates a {@link UnderInitialization} annotation with the given type frame.
+   *
+   * @param typeFrame the type down to which some value has been initialized
+   * @return an {@link UnderInitialization} annotation with the given argument
+   */
+  public AnnotationMirror createUnderInitializationAnnotation(Class<?> typeFrame) {
+    assert typeFrame != null;
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class);
+    builder.setValue("value", typeFrame);
+    return builder.build();
+  }
+
+  /**
+   * Creates a {@link UnknownInitialization} annotation with a given type frame.
+   *
+   * @param typeFrame the type down to which some value has been initialized
+   * @return an {@link UnknownInitialization} annotation with the given argument
+   */
+  public AnnotationMirror createUnknownInitializationAnnotation(Class<?> typeFrame) {
+    assert typeFrame != null;
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnknownInitialization.class);
+    builder.setValue("value", typeFrame);
+    return builder.build();
+  }
+
+  /**
+   * Creates an {@link UnknownInitialization} annotation with a given type frame.
+   *
+   * @param typeFrame the type down to which some value has been initialized
+   * @return an {@link UnknownInitialization} annotation with the given argument
+   */
+  public AnnotationMirror createUnknownInitializationAnnotation(TypeMirror typeFrame) {
+    assert typeFrame != null;
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnknownInitialization.class);
+    builder.setValue("value", typeFrame);
+    return builder.build();
+  }
+
+  /**
+   * Returns the type frame (that is, the argument) of a given initialization annotation.
+   *
+   * @param annotation a {@link UnderInitialization} or {@link UnknownInitialization} annotation
+   * @return the annotation's argument
+   */
+  public TypeMirror getTypeFrameFromAnnotation(AnnotationMirror annotation) {
+    if (AnnotationUtils.areSameByName(
+        annotation, "org.checkerframework.checker.initialization.qual.UnderInitialization")) {
+      return AnnotationUtils.getElementValue(
+          annotation, underInitializationValueElement, TypeMirror.class, objectTypeMirror);
+    } else {
+      return AnnotationUtils.getElementValue(
+          annotation, unknownInitializationValueElement, TypeMirror.class, objectTypeMirror);
+    }
+  }
+
+  /**
+   * Is {@code anno} the {@link UnderInitialization} annotation (with any type frame)?
+   *
+   * @param anno the annotation to check
+   * @return true if {@code anno} is {@link UnderInitialization}
+   */
+  public boolean isUnderInitialization(AnnotationMirror anno) {
+    return areSameByClass(anno, UnderInitialization.class);
+  }
+
+  /**
+   * Is {@code anno} the {@link UnknownInitialization} annotation (with any type frame)?
+   *
+   * @param anno the annotation to check
+   * @return true if {@code anno} is {@link UnknownInitialization}
+   */
+  public boolean isUnknownInitialization(AnnotationMirror anno) {
+    return areSameByClass(anno, UnknownInitialization.class);
+  }
+
+  /**
+   * Is {@code anno} the bottom annotation?
+   *
+   * @param anno the annotation to check
+   * @return true if {@code anno} is {@link FBCBottom}
+   */
+  public boolean isFbcBottom(AnnotationMirror anno) {
+    return AnnotationUtils.areSame(anno, FBCBOTTOM);
+  }
+
+  /**
+   * Is {@code anno} the {@link Initialized} annotation?
+   *
+   * @param anno the annotation to check
+   * @return true if {@code anno} is {@link Initialized}
+   */
+  public boolean isInitialized(AnnotationMirror anno) {
+    return AnnotationUtils.areSame(anno, INITIALIZED);
+  }
+
+  /**
+   * Does {@code anno} have the annotation {@link UnderInitialization} (with any type frame)?
+   *
+   * @param anno the annotation to check
+   * @return true if {@code anno} has {@link UnderInitialization}
+   */
+  public boolean isUnderInitialization(AnnotatedTypeMirror anno) {
+    return anno.hasEffectiveAnnotation(UnderInitialization.class);
+  }
+
+  /**
+   * Does {@code anno} have the annotation {@link UnknownInitialization} (with any type frame)?
+   *
+   * @param anno the annotation to check
+   * @return true if {@code anno} has {@link UnknownInitialization}
+   */
+  public boolean isUnknownInitialization(AnnotatedTypeMirror anno) {
+    return anno.hasEffectiveAnnotation(UnknownInitialization.class);
+  }
+
+  /**
+   * Does {@code anno} have the bottom annotation?
+   *
+   * @param anno the annotation to check
+   * @return true if {@code anno} has {@link FBCBottom}
+   */
+  public boolean isFbcBottom(AnnotatedTypeMirror anno) {
+    return anno.hasEffectiveAnnotation(FBCBottom.class);
+  }
+
+  /**
+   * Does {@code anno} have the annotation {@link Initialized}?
+   *
+   * @param anno the annotation to check
+   * @return true if {@code anno} has {@link Initialized}
+   */
+  public boolean isInitialized(AnnotatedTypeMirror anno) {
+    return anno.hasEffectiveAnnotation(Initialized.class);
+  }
+
+  /**
+   * Are all fields initialized-only?
+   *
+   * @param classTree the class to query
+   * @return true if all fields are initialized-only
+   */
+  protected boolean areAllFieldsInitializedOnly(ClassTree classTree) {
+    for (Tree member : classTree.getMembers()) {
+      if (member.getKind() != Tree.Kind.VARIABLE) {
+        continue;
+      }
+      VariableTree var = (VariableTree) member;
+      VariableElement varElt = TreeUtils.elementFromDeclaration(var);
+      // var is not initialized-only
+      if (getDeclAnnotation(varElt, NotOnlyInitialized.class) != null) {
+        // var is not static -- need a check of initializer blocks,
+        // not of constructor which is where this is used
+        if (!varElt.getModifiers().contains(Modifier.STATIC)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>In most cases, subclasses want to call this method first because it may clear all
+   * annotations and use the hierarchy's root annotations.
+   */
+  @Override
+  public void postAsMemberOf(AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) {
+    super.postAsMemberOf(type, owner, element);
+
+    if (element.getKind().isField()) {
+      Collection<? extends AnnotationMirror> declaredFieldAnnotations = getDeclAnnotations(element);
+      AnnotatedTypeMirror fieldAnnotations = getAnnotatedType(element);
+      computeFieldAccessType(type, declaredFieldAnnotations, owner, fieldAnnotations, element);
+    }
+  }
+
+  /**
+   * Controls which hierarchies' qualifiers are changed based on the receiver type and the declared
+   * annotations for a field.
+   *
+   * @see #computeFieldAccessType
+   * @see #getAnnotatedTypeLhs(Tree)
+   */
+  private boolean computingAnnotatedTypeMirrorOfLHS = false;
+
+  @Override
+  public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) {
+    boolean oldComputingAnnotatedTypeMirrorOfLHS = computingAnnotatedTypeMirrorOfLHS;
+    computingAnnotatedTypeMirrorOfLHS = true;
+    AnnotatedTypeMirror result = super.getAnnotatedTypeLhs(lhsTree);
+    computingAnnotatedTypeMirrorOfLHS = oldComputingAnnotatedTypeMirrorOfLHS;
+    return result;
+  }
+
+  @Override
+  public AnnotatedDeclaredType getSelfType(Tree tree) {
+    AnnotatedDeclaredType selfType = super.getSelfType(tree);
+
+    TreePath path = getPath(tree);
+    AnnotatedDeclaredType enclosing = selfType;
+    while (path != null && enclosing != null) {
+      TreePath topLevelMemberPath = findTopLevelClassMemberForTree(path);
+      if (topLevelMemberPath != null && topLevelMemberPath.getLeaf() != null) {
+        Tree topLevelMember = topLevelMemberPath.getLeaf();
+        if (topLevelMember.getKind() != Kind.METHOD
+            || TreeUtils.isConstructor((MethodTree) topLevelMember)) {
+          setSelfTypeInInitializationCode(tree, enclosing, topLevelMemberPath);
+        }
+        path = topLevelMemberPath.getParentPath();
+        enclosing = enclosing.getEnclosingType();
+      } else {
+        break;
+      }
+    }
+
+    return selfType;
+  }
+
+  /**
+   * In the first enclosing class, find the path to the top-level member that contains {@code path}.
+   *
+   * @param path the path whose leaf is the target
+   * @return path to a top-level member containing the leaf of {@code path}
+   */
+  @SuppressWarnings("interning:not.interned") // AST node comparison
+  private TreePath findTopLevelClassMemberForTree(TreePath path) {
+    if (TreeUtils.isClassTree(path.getLeaf())) {
+      path = path.getParentPath();
+      if (path == null) {
+        return null;
+      }
+    }
+    ClassTree enclosingClass = TreePathUtil.enclosingClass(path);
+    if (enclosingClass != null) {
+      List<? extends Tree> classMembers = enclosingClass.getMembers();
+      TreePath searchPath = path;
+      while (searchPath.getParentPath() != null
+          && searchPath.getParentPath().getLeaf() != enclosingClass) {
+        searchPath = searchPath.getParentPath();
+        if (classMembers.contains(searchPath.getLeaf())) {
+          return searchPath;
+        }
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Side-effects argument {@code selfType} to make it @Initialized or @UnderInitialization,
+   * depending on whether all fields have been set.
+   *
+   * @param tree a tree
+   * @param selfType the type to side-effect
+   * @param path a path
+   */
+  protected void setSelfTypeInInitializationCode(
+      Tree tree, AnnotatedDeclaredType selfType, TreePath path) {
+    ClassTree enclosingClass = TreePathUtil.enclosingClass(path);
+    Type classType = ((JCTree) enclosingClass).type;
+    AnnotationMirror annotation = null;
+
+    // If all fields are initialized-only, and they are all initialized,
+    // then:
+    //  - if the class is final, this is @Initialized
+    //  - otherwise, this is @UnderInitialization(CurrentClass) as
+    //    there might still be subclasses that need initialization.
+    if (areAllFieldsInitializedOnly(enclosingClass)) {
+      Store store = getStoreBefore(tree);
+      if (store != null
+          && getUninitializedInvariantFields(store, path, false, Collections.emptyList())
+              .isEmpty()) {
+        if (classType.isFinal()) {
+          annotation = INITIALIZED;
+        } else {
+          annotation = createUnderInitializationAnnotation(classType);
+        }
+      }
+    }
+
+    if (annotation == null) {
+      annotation = getUnderInitializationAnnotationOfSuperType(classType);
+    }
+    selfType.replaceAnnotation(annotation);
+  }
+
+  /**
+   * Returns an {@link UnderInitialization} annotation that has the superclass of {@code type} as
+   * type frame.
+   *
+   * @param type a type
+   * @return true an {@link UnderInitialization} for the supertype of {@code type}
+   */
+  protected AnnotationMirror getUnderInitializationAnnotationOfSuperType(TypeMirror type) {
+    // Find supertype if possible.
+    AnnotationMirror annotation;
+    List<? extends TypeMirror> superTypes = types.directSupertypes(type);
+    TypeMirror superClass = null;
+    for (TypeMirror superType : superTypes) {
+      ElementKind kind = types.asElement(superType).getKind();
+      if (kind == ElementKind.CLASS) {
+        superClass = superType;
+        break;
+      }
+    }
+    // Create annotation.
+    if (superClass != null) {
+      annotation = createUnderInitializationAnnotation(superClass);
+    } else {
+      // Use Object as a valid super-class.
+      annotation = createUnderInitializationAnnotation(Object.class);
+    }
+    return annotation;
+  }
+
+  /**
+   * Returns the fields that are not yet initialized in a given store. The result is a pair of
+   * lists:
+   *
+   * <ul>
+   *   <li>fields that are not yet initialized and have the invariant annotation
+   *   <li>fields that are not yet initialized and do not have the invariant annotation
+   * </ul>
+   *
+   * @param store a store
+   * @param path the current path, used to determine the current class
+   * @param isStatic whether to report static fields or instance fields
+   * @param receiverAnnotations the annotations on the receiver
+   * @return the fields that are not yet initialized in a given store (a pair of lists)
+   */
+  public Pair<List<VariableTree>, List<VariableTree>> getUninitializedFields(
+      Store store,
+      TreePath path,
+      boolean isStatic,
+      Collection<? extends AnnotationMirror> receiverAnnotations) {
+    ClassTree currentClass = TreePathUtil.enclosingClass(path);
+    List<VariableTree> fields = InitializationChecker.getAllFields(currentClass);
+    List<VariableTree> uninitWithInvariantAnno = new ArrayList<>();
+    List<VariableTree> uninitWithoutInvariantAnno = new ArrayList<>();
+    for (VariableTree field : fields) {
+      if (isUnused(field, receiverAnnotations)) {
+        continue; // don't consider unused fields
+      }
+      VariableElement fieldElem = TreeUtils.elementFromDeclaration(field);
+      if (ElementUtils.isStatic(fieldElem) == isStatic) {
+        // Has the field been initialized?
+        if (!store.isFieldInitialized(fieldElem)) {
+          // Does this field need to satisfy the invariant?
+          if (hasFieldInvariantAnnotation(field)) {
+            uninitWithInvariantAnno.add(field);
+          } else {
+            uninitWithoutInvariantAnno.add(field);
+          }
+        }
+      }
+    }
+    return Pair.of(uninitWithInvariantAnno, uninitWithoutInvariantAnno);
+  }
+
+  /**
+   * Returns the fields that have the invariant annotation and are not yet initialized in a given
+   * store.
+   *
+   * @param store a store
+   * @param path the current path, used to determine the current class
+   * @param isStatic whether to report static fields or instance fields
+   * @param receiverAnnotations the annotations on the receiver
+   * @return the fields that have the invariant annotation and are not yet initialized in a given
+   *     store (a pair of lists)
+   */
+  public final List<VariableTree> getUninitializedInvariantFields(
+      Store store,
+      TreePath path,
+      boolean isStatic,
+      List<? extends AnnotationMirror> receiverAnnotations) {
+    return getUninitializedFields(store, path, isStatic, receiverAnnotations).first;
+  }
+
+  /**
+   * Returns the fields that have the invariant annotation and are initialized in a given store.
+   *
+   * @param store a store
+   * @param path the current path; used to compute the current class
+   * @return the fields that have the invariant annotation and are initialized in a given store
+   */
+  public List<VariableTree> getInitializedInvariantFields(Store store, TreePath path) {
+    // TODO: Instead of passing the TreePath around, can we use
+    // getCurrentClassTree?
+    ClassTree currentClass = TreePathUtil.enclosingClass(path);
+    List<VariableTree> fields = InitializationChecker.getAllFields(currentClass);
+    List<VariableTree> initializedFields = new ArrayList<>();
+    for (VariableTree field : fields) {
+      VariableElement fieldElem = TreeUtils.elementFromDeclaration(field);
+      if (!ElementUtils.isStatic(fieldElem)) {
+        // Does this field need to satisfy the invariant?
+        if (hasFieldInvariantAnnotation(field)) {
+          // Has the field been initialized?
+          if (store.isFieldInitialized(fieldElem)) {
+            initializedFields.add(field);
+          }
+        }
+      }
+    }
+    return initializedFields;
+  }
+
+  /** Returns whether the field {@code f} is unused, given the annotations on the receiver. */
+  private boolean isUnused(
+      VariableTree field, Collection<? extends AnnotationMirror> receiverAnnos) {
+    if (receiverAnnos.isEmpty()) {
+      return false;
+    }
+
+    AnnotationMirror unused =
+        getDeclAnnotation(TreeUtils.elementFromDeclaration(field), Unused.class);
+    if (unused == null) {
+      return false;
+    }
+
+    Name when = AnnotationUtils.getElementValueClassName(unused, unusedWhenElement);
+    for (AnnotationMirror anno : receiverAnnos) {
+      Name annoName = ((TypeElement) anno.getAnnotationType().asElement()).getQualifiedName();
+      if (annoName.contentEquals(when)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Return true if the type is initialized with respect to the given frame -- that is, all of the
+   * fields of the frame are initialized.
+   *
+   * @param type the type whose initialization type qualifiers to check
+   * @param frame a class in {@code type}'s class hierarchy
+   * @return true if the type is initialized for the given frame
+   */
+  public boolean isInitializedForFrame(AnnotatedTypeMirror type, TypeMirror frame) {
+    AnnotationMirror initializationAnno =
+        type.getEffectiveAnnotationInHierarchy(UNKNOWN_INITIALIZATION);
+    TypeMirror typeFrame = getTypeFrameFromAnnotation(initializationAnno);
+    Types types = processingEnv.getTypeUtils();
+    return types.isSubtype(typeFrame, types.erasure(frame));
+  }
+
+  /**
+   * Determine the type of a field access (implicit or explicit) based on the receiver type and the
+   * declared annotations for the field.
+   *
+   * @param type type of the field access expression
+   * @param declaredFieldAnnotations annotations on the element
+   * @param receiverType inferred annotations of the receiver
+   */
+  private void computeFieldAccessType(
+      AnnotatedTypeMirror type,
+      Collection<? extends AnnotationMirror> declaredFieldAnnotations,
+      AnnotatedTypeMirror receiverType,
+      AnnotatedTypeMirror fieldAnnotations,
+      Element element) {
+    // not necessary for primitive fields
+    if (TypesUtils.isPrimitive(type.getUnderlyingType())) {
+      return;
+    }
+    // not necessary if there is an explicit UnknownInitialization
+    // annotation on the field
+    if (AnnotationUtils.containsSameByName(
+        fieldAnnotations.getAnnotations(), UNKNOWN_INITIALIZATION)) {
+      return;
+    }
+    if (isUnknownInitialization(receiverType) || isUnderInitialization(receiverType)) {
+
+      TypeMirror fieldDeclarationType = element.getEnclosingElement().asType();
+      boolean isInitializedForFrame = isInitializedForFrame(receiverType, fieldDeclarationType);
+      if (isInitializedForFrame) {
+        // The receiver is initialized for this frame.
+        // Change the type of the field to @UnknownInitialization so that
+        // anything can be assigned to this field.
+        type.replaceAnnotation(UNKNOWN_INITIALIZATION);
+      } else if (computingAnnotatedTypeMirrorOfLHS) {
+        // The receiver is not initialized for this frame, but the type of a lhs is being computed.
+        // Change the type of the field to @UnknownInitialization so that
+        // anything can be assigned to this field.
+        type.replaceAnnotation(UNKNOWN_INITIALIZATION);
+      } else {
+        // The receiver is not initialized for this frame and the type being computed is not a LHS.
+        // Replace all annotations with the top annotation for that hierarchy.
+        type.clearAnnotations();
+        type.addAnnotations(qualHierarchy.getTopAnnotations());
+      }
+
+      if (!AnnotationUtils.containsSame(declaredFieldAnnotations, NOT_ONLY_INITIALIZED)) {
+        // add root annotation for all other hierarchies, and
+        // Initialized for the initialization hierarchy
+        type.replaceAnnotation(INITIALIZED);
+      }
+    }
+  }
+
+  @Override
+  protected TypeAnnotator createTypeAnnotator() {
+    return new ListTypeAnnotator(super.createTypeAnnotator(), new CommitmentTypeAnnotator(this));
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(super.createTreeAnnotator(), new CommitmentTreeAnnotator(this));
+  }
+
+  protected class CommitmentTypeAnnotator extends TypeAnnotator {
+    public CommitmentTypeAnnotator(InitializationAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitExecutable(AnnotatedExecutableType t, Void p) {
+      Void result = super.visitExecutable(t, p);
+      Element elem = t.getElement();
+      if (elem.getKind() == ElementKind.CONSTRUCTOR) {
+        AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) t.getReturnType();
+        DeclaredType underlyingType = returnType.getUnderlyingType();
+        returnType.replaceAnnotation(getUnderInitializationAnnotationOfSuperType(underlyingType));
+      }
+      return result;
+    }
+  }
+
+  protected class CommitmentTreeAnnotator extends TreeAnnotator {
+
+    public CommitmentTreeAnnotator(InitializationAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitMethod(MethodTree node, AnnotatedTypeMirror p) {
+      Void result = super.visitMethod(node, p);
+      if (TreeUtils.isConstructor(node)) {
+        assert p instanceof AnnotatedExecutableType;
+        AnnotatedExecutableType exeType = (AnnotatedExecutableType) p;
+        DeclaredType underlyingType = (DeclaredType) exeType.getReturnType().getUnderlyingType();
+        AnnotationMirror a = getUnderInitializationAnnotationOfSuperType(underlyingType);
+        exeType.getReturnType().replaceAnnotation(a);
+      }
+      return result;
+    }
+
+    @Override
+    public Void visitNewClass(NewClassTree node, AnnotatedTypeMirror p) {
+      super.visitNewClass(node, p);
+      boolean allInitialized = true;
+      Type type = ((JCTree) node).type;
+      for (ExpressionTree a : node.getArguments()) {
+        final AnnotatedTypeMirror t = getAnnotatedType(a);
+        allInitialized &= (isInitialized(t) || isFbcBottom(t));
+      }
+      if (!allInitialized) {
+        p.replaceAnnotation(createUnderInitializationAnnotation(type));
+        return null;
+      }
+      p.replaceAnnotation(INITIALIZED);
+      return null;
+    }
+
+    @Override
+    public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
+      if (tree.getKind() != Tree.Kind.NULL_LITERAL) {
+        type.addAnnotation(INITIALIZED);
+      }
+      return super.visitLiteral(tree, type);
+    }
+
+    @Override
+    public Void visitMemberSelect(MemberSelectTree node, AnnotatedTypeMirror annotatedTypeMirror) {
+      if (TreeUtils.isArrayLengthAccess(node)) {
+        annotatedTypeMirror.replaceAnnotation(INITIALIZED);
+      }
+      return super.visitMemberSelect(node, annotatedTypeMirror);
+    }
+  }
+
+  /**
+   * The {@link QualifierHierarchy} for the initialization type system.
+   *
+   * <p>Type systems extending the Initialization Checker should call methods {@link
+   * InitializationQualifierHierarchy#isSubtypeInitialization} and {@link
+   * InitializationQualifierHierarchy#leastUpperBoundInitialization} for appropriate qualifiers. See
+   * protected subclass NullnessQualifierHierarchy within class {@link NullnessChecker} for an
+   * example.
+   */
+  protected abstract class InitializationQualifierHierarchy
+      extends MostlyNoElementQualifierHierarchy {
+
+    /** Qualifier kind for the @{@link UnknownInitialization} annotation. */
+    private final QualifierKind UNKNOWN_INIT;
+    /** Qualifier kind for the @{@link UnderInitialization} annotation. */
+    private final QualifierKind UNDER_INIT;
+
+    /** Create an InitializationQualifierHierarchy. */
+    protected InitializationQualifierHierarchy() {
+      super(InitializationAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), elements);
+      UNKNOWN_INIT = getQualifierKind(UNKNOWN_INITIALIZATION);
+      UNDER_INIT = getQualifierKind(UNDER_INITALIZATION);
+    }
+
+    /**
+     * Subtype testing for initialization annotations. Will return false if either qualifier is not
+     * an initialization annotation. Subclasses should override isSubtype and call this method for
+     * initialization qualifiers.
+     *
+     * @param subAnno subtype annotation
+     * @param subKind subtype kind
+     * @param superAnno supertype annotation
+     * @param superKind supertype kind
+     * @return true if subAnno is a subtype of superAnno in the initialization hierarchy
+     */
+    public boolean isSubtypeInitialization(
+        AnnotationMirror subAnno,
+        QualifierKind subKind,
+        AnnotationMirror superAnno,
+        QualifierKind superKind) {
+      if (!subKind.isSubtypeOf(superKind)) {
+        return false;
+      } else if ((subKind == UNDER_INIT && superKind == UNDER_INIT)
+          || (subKind == UNDER_INIT && superKind == UNKNOWN_INIT)
+          || (subKind == UNKNOWN_INIT && superKind == UNKNOWN_INIT)) {
+        // Thus, we only need to look at the type frame.
+        TypeMirror frame1 = getTypeFrameFromAnnotation(subAnno);
+        TypeMirror frame2 = getTypeFrameFromAnnotation(superAnno);
+        return types.isSubtype(frame1, frame2);
+      } else {
+        return true;
+      }
+    }
+
+    /**
+     * Compute the least upper bound of two initialization qualifiers. Returns null if one of the
+     * qualifiers is not in the initialization hierarachy. Subclasses should override
+     * leastUpperBound and call this method for initialization qualifiers.
+     *
+     * @param anno1 an initialization qualifier
+     * @param qual1 a qualifier kind
+     * @param anno2 an initialization qualifier
+     * @param qual2 a qualifier kind
+     * @return the lub of anno1 and anno2
+     */
+    protected AnnotationMirror leastUpperBoundInitialization(
+        AnnotationMirror anno1, QualifierKind qual1, AnnotationMirror anno2, QualifierKind qual2) {
+      if (!isInitializationAnnotation(anno1) || !isInitializationAnnotation(anno2)) {
+        return null;
+      }
+
+      // Handle the case where one is a subtype of the other.
+      if (isSubtypeInitialization(anno1, qual1, anno2, qual2)) {
+        return anno2;
+      } else if (isSubtypeInitialization(anno2, qual2, anno1, qual1)) {
+        return anno1;
+      }
+      boolean unknowninit1 = isUnknownInitialization(anno1);
+      boolean unknowninit2 = isUnknownInitialization(anno2);
+      boolean underinit1 = isUnderInitialization(anno1);
+      boolean underinit2 = isUnderInitialization(anno2);
+
+      // Handle @Initialized.
+      if (isInitialized(anno1)) {
+        assert underinit2;
+        return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno2));
+      } else if (isInitialized(anno2)) {
+        assert underinit1;
+        return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno1));
+      }
+
+      if (underinit1 && underinit2) {
+        return createUnderInitializationAnnotation(
+            lubTypeFrame(getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2)));
+      }
+
+      assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2);
+      return createUnknownInitializationAnnotation(
+          lubTypeFrame(getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2)));
+    }
+
+    /**
+     * Returns the least upper bound of two types.
+     *
+     * @param a the first argument
+     * @param b the second argument
+     * @return the lub of the two arguments
+     */
+    protected TypeMirror lubTypeFrame(TypeMirror a, TypeMirror b) {
+      if (types.isSubtype(a, b)) {
+        return b;
+      } else if (types.isSubtype(b, a)) {
+        return a;
+      }
+
+      return TypesUtils.leastUpperBound(a, b, processingEnv);
+    }
+
+    /**
+     * Compute the greatest lower bound of two initialization qualifiers. Returns null if one of the
+     * qualifiers is not in the initialization hierarachy. Subclasses should override
+     * greatestLowerBound and call this method for initialization qualifiers.
+     *
+     * @param anno1 an initialization qualifier
+     * @param qual1 a qualifier kind
+     * @param anno2 an initialization qualifier
+     * @param qual2 a qualifier kind
+     * @return the glb of anno1 and anno2
+     */
+    protected AnnotationMirror greatestLowerBoundInitialization(
+        AnnotationMirror anno1, QualifierKind qual1, AnnotationMirror anno2, QualifierKind qual2) {
+      if (!isInitializationAnnotation(anno1) || !isInitializationAnnotation(anno2)) {
+        return null;
+      }
+
+      // Handle the case where one is a subtype of the other.
+      if (isSubtypeInitialization(anno1, qual1, anno2, qual2)) {
+        return anno1;
+      } else if (isSubtypeInitialization(anno2, qual2, anno1, qual1)) {
+        return anno2;
+      }
+      boolean unknowninit1 = isUnknownInitialization(anno1);
+      boolean unknowninit2 = isUnknownInitialization(anno2);
+      boolean underinit1 = isUnderInitialization(anno1);
+      boolean underinit2 = isUnderInitialization(anno2);
+
+      // Handle @Initialized.
+      if (isInitialized(anno1)) {
+        assert underinit2;
+        return FBCBOTTOM;
+      } else if (isInitialized(anno2)) {
+        assert underinit1;
+        return FBCBOTTOM;
+      }
+
+      TypeMirror typeFrame =
+          TypesUtils.greatestLowerBound(
+              getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2), processingEnv);
+      if (typeFrame.getKind() == TypeKind.ERROR || typeFrame.getKind() == TypeKind.INTERSECTION) {
+        return FBCBOTTOM;
+      }
+
+      if (underinit1 && underinit2) {
+        return createUnderInitializationAnnotation(typeFrame);
+      }
+
+      assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2);
+      return createUnderInitializationAnnotation(typeFrame);
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationChecker.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationChecker.java
new file mode 100644
index 0000000..d35fcdc
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationChecker.java
@@ -0,0 +1,46 @@
+package org.checkerframework.checker.initialization;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedSet;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+/**
+ * Tracks whether a value is initialized (all its fields are set), and checks that values are
+ * initialized before being used. Implements the freedom-before-commitment scheme for
+ * initialization, augmented by type frames.
+ *
+ * @checker_framework.manual #initialization-checker Initialization Checker
+ */
+public abstract class InitializationChecker extends BaseTypeChecker {
+
+  /** Create a new InitializationChecker. */
+  protected InitializationChecker() {}
+
+  @Override
+  public SortedSet<String> getSuppressWarningsPrefixes() {
+    SortedSet<String> result = super.getSuppressWarningsPrefixes();
+    // "fbc" is for backward compatibility only.
+    // Notes:
+    //   * "fbc" suppresses *all* warnings, not just those related to initialization.  See
+    //     https://checkerframework.org/manual/#initialization-checking-suppressing-warnings .
+    //   * "initialization" is not a checkername/prefix.
+    result.add("fbc");
+    return result;
+  }
+
+  /** Returns a list of all fields of the given class. */
+  public static List<VariableTree> getAllFields(ClassTree clazz) {
+    List<VariableTree> fields = new ArrayList<>();
+    for (Tree t : clazz.getMembers()) {
+      if (t.getKind() == Tree.Kind.VARIABLE) {
+        VariableTree vt = (VariableTree) t;
+        fields.add(vt);
+      }
+    }
+    return fields;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationStore.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationStore.java
new file mode 100644
index 0000000..591b2b9
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationStore.java
@@ -0,0 +1,264 @@
+package org.checkerframework.checker.initialization;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.ThisReference;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.checkerframework.framework.flow.CFAbstractValue;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.ToStringComparator;
+
+/**
+ * A store that extends {@code CFAbstractStore} and additionally tracks which fields of the 'self'
+ * reference have been initialized.
+ *
+ * @see InitializationTransfer
+ */
+public class InitializationStore<V extends CFAbstractValue<V>, S extends InitializationStore<V, S>>
+    extends CFAbstractStore<V, S> {
+
+  /** The set of fields that are initialized. */
+  protected final Set<VariableElement> initializedFields;
+  /** The set of fields that have the 'invariant' annotation, and their value. */
+  protected final Map<FieldAccess, V> invariantFields;
+
+  /**
+   * Creates a new InitializationStore.
+   *
+   * @param analysis the analysis class this store belongs to
+   * @param sequentialSemantics should the analysis use sequential Java semantics?
+   */
+  public InitializationStore(CFAbstractAnalysis<V, S, ?> analysis, boolean sequentialSemantics) {
+    super(analysis, sequentialSemantics);
+    initializedFields = new HashSet<>(4);
+    invariantFields = new HashMap<>(4);
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>If the receiver is a field, and has an invariant annotation, then it can be considered
+   * initialized.
+   */
+  @Override
+  public void insertValue(JavaExpression je, V value, boolean permitNondeterministic) {
+    if (!shouldInsert(je, value, permitNondeterministic)) {
+      return;
+    }
+
+    InitializationAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory =
+        (InitializationAnnotatedTypeFactory<?, ?, ?, ?>) analysis.getTypeFactory();
+    QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
+    AnnotationMirror invariantAnno = atypeFactory.getFieldInvariantAnnotation();
+
+    // Remember fields that have the 'invariant' annotation in the store.
+    if (je instanceof FieldAccess) {
+      FieldAccess fieldAccess = (FieldAccess) je;
+      if (!fieldValues.containsKey(je)) {
+        Set<AnnotationMirror> declaredAnnos =
+            atypeFactory.getAnnotatedType(fieldAccess.getField()).getAnnotations();
+        if (AnnotationUtils.containsSame(declaredAnnos, invariantAnno)) {
+          if (!invariantFields.containsKey(fieldAccess)) {
+            invariantFields.put(
+                fieldAccess, analysis.createSingleAnnotationValue(invariantAnno, je.getType()));
+          }
+        }
+      }
+    }
+
+    super.insertValue(je, value, permitNondeterministic);
+
+    for (AnnotationMirror a : value.getAnnotations()) {
+      if (qualifierHierarchy.isSubtype(a, invariantAnno)) {
+        if (je instanceof FieldAccess) {
+          FieldAccess fa = (FieldAccess) je;
+          if (fa.getReceiver() instanceof ThisReference || fa.getReceiver() instanceof ClassName) {
+            addInitializedField(fa.getField());
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>Additionally, the {@link InitializationStore} keeps all field values for fields that have
+   * the 'invariant' annotation.
+   */
+  @Override
+  public void updateForMethodCall(
+      MethodInvocationNode n, AnnotatedTypeFactory atypeFactory, V val) {
+    // Remove invariant annotated fields to avoid performance issue reported in #1438.
+    for (FieldAccess invariantField : invariantFields.keySet()) {
+      fieldValues.remove(invariantField);
+    }
+
+    super.updateForMethodCall(n, atypeFactory, val);
+
+    // Add invariant annotation again.
+    fieldValues.putAll(invariantFields);
+  }
+
+  /** A copy constructor. */
+  public InitializationStore(S other) {
+    super(other);
+    initializedFields = new HashSet<>(other.initializedFields);
+    invariantFields = new HashMap<>(other.invariantFields);
+  }
+
+  /**
+   * Mark the field identified by the element {@code field} as initialized if it belongs to the
+   * current class, or is static (in which case there is no aliasing issue and we can just add all
+   * static fields).
+   *
+   * @param field a field that is initialized
+   */
+  public void addInitializedField(FieldAccess field) {
+    boolean fieldOnThisReference = field.getReceiver() instanceof ThisReference;
+    boolean staticField = field.isStatic();
+    if (fieldOnThisReference || staticField) {
+      initializedFields.add(field.getField());
+    }
+  }
+
+  /**
+   * Mark the field identified by the element {@code f} as initialized (the caller needs to ensure
+   * that the field belongs to the current class, or is a static field).
+   *
+   * @param f a field that is initialized
+   */
+  public void addInitializedField(VariableElement f) {
+    initializedFields.add(f);
+  }
+
+  /** Is the field identified by the element {@code f} initialized? */
+  public boolean isFieldInitialized(Element f) {
+    return initializedFields.contains(f);
+  }
+
+  @Override
+  protected boolean supersetOf(CFAbstractStore<V, S> o) {
+    if (!(o instanceof InitializationStore)) {
+      return false;
+    }
+    @SuppressWarnings("unchecked")
+    S other = (S) o;
+
+    for (Element field : other.initializedFields) {
+      if (!initializedFields.contains(field)) {
+        return false;
+      }
+    }
+
+    for (FieldAccess invariantField : other.invariantFields.keySet()) {
+      if (!invariantFields.containsKey(invariantField)) {
+        return false;
+      }
+    }
+
+    Map<FieldAccess, V> removedFieldValues = new HashMap<>(invariantFields.size());
+    Map<FieldAccess, V> removedOtherFieldValues = new HashMap<>(other.invariantFields.size());
+    try {
+      // Remove invariant annotated fields to avoid performance issue reported in #1438.
+      for (FieldAccess invariantField : invariantFields.keySet()) {
+        V v = fieldValues.remove(invariantField);
+        removedFieldValues.put(invariantField, v);
+      }
+      for (FieldAccess invariantField : other.invariantFields.keySet()) {
+        V v = other.fieldValues.remove(invariantField);
+        removedOtherFieldValues.put(invariantField, v);
+      }
+
+      return super.supersetOf(other);
+    } finally {
+      // Restore removed values.
+      fieldValues.putAll(removedFieldValues);
+      other.fieldValues.putAll(removedOtherFieldValues);
+    }
+  }
+
+  @Override
+  public S leastUpperBound(S other) {
+    // Remove invariant annotated fields to avoid performance issue reported in #1438.
+    Map<FieldAccess, V> removedFieldValues = new HashMap<>(invariantFields.size());
+    Map<FieldAccess, V> removedOtherFieldValues = new HashMap<>(other.invariantFields.size());
+    for (FieldAccess invariantField : invariantFields.keySet()) {
+      V v = fieldValues.remove(invariantField);
+      removedFieldValues.put(invariantField, v);
+    }
+    for (FieldAccess invariantField : other.invariantFields.keySet()) {
+      V v = other.fieldValues.remove(invariantField);
+      removedOtherFieldValues.put(invariantField, v);
+    }
+
+    S result = super.leastUpperBound(other);
+
+    // Restore removed values.
+    fieldValues.putAll(removedFieldValues);
+    other.fieldValues.putAll(removedOtherFieldValues);
+
+    // Set intersection for initializedFields.
+    result.initializedFields.addAll(other.initializedFields);
+    result.initializedFields.retainAll(initializedFields);
+
+    // Set intersection for invariantFields.
+    for (Map.Entry<FieldAccess, V> e : invariantFields.entrySet()) {
+      FieldAccess key = e.getKey();
+      if (other.invariantFields.containsKey(key)) {
+        // TODO: Is the value other.invariantFields.get(key) the same as e.getValue()?  Should the
+        // two values be lubbed?
+        result.invariantFields.put(key, e.getValue());
+      }
+    }
+    // Add invariant annotation.
+    result.fieldValues.putAll(result.invariantFields);
+
+    return result;
+  }
+
+  @Override
+  protected String internalVisualize(CFGVisualizer<V, S, ?> viz) {
+    String superVisualize = super.internalVisualize(viz);
+
+    String initializedVisualize =
+        viz.visualizeStoreKeyVal(
+            "initialized fields", ToStringComparator.sorted(initializedFields));
+
+    List<VariableElement> invariantVars =
+        CollectionsPlume.mapList(FieldAccess::getField, invariantFields.keySet());
+    String invariantVisualize =
+        viz.visualizeStoreKeyVal("invariant fields", ToStringComparator.sorted(invariantVars));
+
+    if (superVisualize.isEmpty()) {
+      return String.join(viz.getSeparator(), initializedVisualize, invariantVisualize);
+    } else {
+      return String.join(
+          viz.getSeparator(), superVisualize, initializedVisualize, invariantVisualize);
+    }
+  }
+
+  /**
+   * Returns the analysis associated with this store.
+   *
+   * @return the analysis associated with this store
+   */
+  public CFAbstractAnalysis<V, S, ?> getAnalysis() {
+    return analysis;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java
new file mode 100644
index 0000000..f823c97
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java
@@ -0,0 +1,202 @@
+package org.checkerframework.checker.initialization;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.tools.javac.code.Symbol;
+import java.util.ArrayList;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import org.checkerframework.dataflow.analysis.RegularTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ThisNode;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAbstractTransfer;
+import org.checkerframework.framework.flow.CFAbstractValue;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A transfer function that extends {@link CFAbstractTransfer} and tracks {@link
+ * InitializationStore}s. In addition to the features of {@link CFAbstractTransfer}, this transfer
+ * function also tracks which fields of the current class ('self' receiver) have been initialized.
+ *
+ * <p>More precisely, the following refinements are performed:
+ *
+ * <ol>
+ *   <li>After the call to a constructor ("this()" call), all non-null fields of the current class
+ *       can safely be considered initialized.
+ *   <li>After a method call with a postcondition that ensures a field to be non-null, that field
+ *       can safely be considered initialized (this is done in {@link
+ *       InitializationStore#insertValue(JavaExpression, CFAbstractValue)}).
+ *   <li>All non-null fields with an initializer can be considered initialized (this is done in
+ *       {@link InitializationStore#insertValue(JavaExpression, CFAbstractValue)}).
+ *   <li>After the call to a super constructor ("super()" call), all non-null fields of the super
+ *       class can safely be considered initialized.
+ * </ol>
+ *
+ * @see InitializationStore
+ * @param <T> the type of the transfer function
+ */
+public class InitializationTransfer<
+        V extends CFAbstractValue<V>,
+        T extends InitializationTransfer<V, T, S>,
+        S extends InitializationStore<V, S>>
+    extends CFAbstractTransfer<V, S, T> {
+
+  protected final InitializationAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory;
+
+  public InitializationTransfer(CFAbstractAnalysis<V, S, T> analysis) {
+    super(analysis);
+    this.atypeFactory = (InitializationAnnotatedTypeFactory<?, ?, ?, ?>) analysis.getTypeFactory();
+  }
+
+  @Override
+  protected boolean isNotFullyInitializedReceiver(MethodTree methodTree) {
+    if (super.isNotFullyInitializedReceiver(methodTree)) {
+      return true;
+    }
+    final AnnotatedDeclaredType receiverType =
+        analysis.getTypeFactory().getAnnotatedType(methodTree).getReceiverType();
+    if (receiverType != null) {
+      return atypeFactory.isUnknownInitialization(receiverType)
+          || atypeFactory.isUnderInitialization(receiverType);
+    } else {
+      // There is no receiver e.g. in static methods.
+      return false;
+    }
+  }
+
+  /**
+   * Returns the fields that can safely be considered initialized after the method call {@code
+   * node}.
+   *
+   * @param node a method call
+   * @return the fields that are initialized after the method call
+   */
+  protected List<VariableElement> initializedFieldsAfterCall(MethodInvocationNode node) {
+    List<VariableElement> result = new ArrayList<>();
+    MethodInvocationTree tree = node.getTree();
+    ExecutableElement method = TreeUtils.elementFromUse(tree);
+    boolean isConstructor = method.getSimpleName().contentEquals("<init>");
+    Node receiver = node.getTarget().getReceiver();
+    String methodString = tree.getMethodSelect().toString();
+
+    // Case 1: After a call to the constructor of the same class, all
+    // invariant fields are guaranteed to be initialized.
+    if (isConstructor && receiver instanceof ThisNode && methodString.equals("this")) {
+      ClassTree clazz = TreePathUtil.enclosingClass(analysis.getTypeFactory().getPath(tree));
+      TypeElement clazzElem = TreeUtils.elementFromDeclaration(clazz);
+      markInvariantFieldsAsInitialized(result, clazzElem);
+    }
+
+    // Case 4: After a call to the constructor of the super class, all
+    // invariant fields of any super class are guaranteed to be initialized.
+    if (isConstructor && receiver instanceof ThisNode && methodString.equals("super")) {
+      ClassTree clazz = TreePathUtil.enclosingClass(analysis.getTypeFactory().getPath(tree));
+      TypeElement clazzElem = TreeUtils.elementFromDeclaration(clazz);
+      TypeMirror superClass = clazzElem.getSuperclass();
+
+      while (superClass != null && superClass.getKind() != TypeKind.NONE) {
+        clazzElem = (TypeElement) analysis.getTypes().asElement(superClass);
+        superClass = clazzElem.getSuperclass();
+        markInvariantFieldsAsInitialized(result, clazzElem);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Adds all the fields of the class {@code clazzElem} that have the 'invariant annotation' to the
+   * set of initialized fields {@code result}.
+   */
+  protected void markInvariantFieldsAsInitialized(
+      List<VariableElement> result, TypeElement clazzElem) {
+    List<VariableElement> fields = ElementFilter.fieldsIn(clazzElem.getEnclosedElements());
+    for (VariableElement field : fields) {
+      if (((Symbol) field).type.tsym.completer != Symbol.Completer.NULL_COMPLETER
+          || ((Symbol) field).type.getKind() == TypeKind.ERROR) {
+        // If the type is not completed yet, we might run into trouble. Skip the field.
+        // TODO: is there a nicer solution?
+        // This was raised by Issue #244.
+        continue;
+      }
+      AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(field);
+      if (atypeFactory.hasFieldInvariantAnnotation(fieldType, field)) {
+        result.add(field);
+      }
+    }
+  }
+
+  @Override
+  public TransferResult<V, S> visitAssignment(AssignmentNode n, TransferInput<V, S> in) {
+    TransferResult<V, S> result = super.visitAssignment(n, in);
+    assert result instanceof RegularTransferResult;
+    JavaExpression lhs = JavaExpression.fromNode(n.getTarget());
+
+    // If this is an assignment to a field of 'this', then mark the field as initialized.
+    if (!lhs.containsUnknown()) {
+      if (lhs instanceof FieldAccess) {
+        FieldAccess fa = (FieldAccess) lhs;
+        result.getRegularStore().addInitializedField(fa);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * If an invariant field is initialized and has the invariant annotation, than it has at least the
+   * invariant annotation. Note that only fields of the 'this' receiver are tracked for
+   * initialization.
+   */
+  @Override
+  public TransferResult<V, S> visitFieldAccess(FieldAccessNode n, TransferInput<V, S> p) {
+    TransferResult<V, S> result = super.visitFieldAccess(n, p);
+    assert !result.containsTwoStores();
+    S store = result.getRegularStore();
+    if (store.isFieldInitialized(n.getElement()) && n.getReceiver() instanceof ThisNode) {
+      AnnotatedTypeMirror fieldAnno = analysis.getTypeFactory().getAnnotatedType(n.getElement());
+      // Only if the field has the type system's invariant annotation,
+      // such as @NonNull.
+      if (fieldAnno.hasAnnotation(atypeFactory.getFieldInvariantAnnotation())) {
+        AnnotationMirror inv = atypeFactory.getFieldInvariantAnnotation();
+        V oldResultValue = result.getResultValue();
+        V refinedResultValue =
+            analysis.createSingleAnnotationValue(inv, oldResultValue.getUnderlyingType());
+        V newResultValue = refinedResultValue.mostSpecific(oldResultValue, null);
+        result.setResultValue(newResultValue);
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public TransferResult<V, S> visitMethodInvocation(
+      MethodInvocationNode n, TransferInput<V, S> in) {
+    TransferResult<V, S> result = super.visitMethodInvocation(n, in);
+    List<VariableElement> newlyInitializedFields = initializedFieldsAfterCall(n);
+    if (!newlyInitializedFields.isEmpty()) {
+      for (VariableElement f : newlyInitializedFields) {
+        result.getThenStore().addInitializedField(f);
+        result.getElseStore().addInitializedField(f);
+      }
+    }
+    return result;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java
new file mode 100644
index 0000000..9db9cb6
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java
@@ -0,0 +1,443 @@
+package org.checkerframework.checker.initialization;
+
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.VariableTree;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.wholeprograminference.WholeProgramInference;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.ThisReference;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.checkerframework.framework.flow.CFAbstractValue;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.util.AnnotationFormatter;
+import org.checkerframework.framework.util.DefaultAnnotationFormatter;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * The visitor for the freedom-before-commitment type-system. The freedom-before-commitment
+ * type-system and this class are abstract and need to be combined with another type-system whose
+ * safe initialization should be tracked. For an example, see the {@link NullnessChecker}.
+ */
+public class InitializationVisitor<
+        Factory extends InitializationAnnotatedTypeFactory<Value, Store, ?, ?>,
+        Value extends CFAbstractValue<Value>,
+        Store extends InitializationStore<Value, Store>>
+    extends BaseTypeVisitor<Factory> {
+
+  /** The annotation formatter. */
+  protected final AnnotationFormatter annoFormatter;
+
+  /**
+   * Creates a new InitializationVisitor.
+   *
+   * @param checker the associated type-checker
+   */
+  public InitializationVisitor(BaseTypeChecker checker) {
+    super(checker);
+    annoFormatter = new DefaultAnnotationFormatter();
+    initializedFields = new ArrayList<>();
+  }
+
+  @Override
+  public void setRoot(CompilationUnitTree root) {
+    // Clean up the cache of initialized fields once per compilation unit.
+    // Alternatively, but harder to determine, this could be done once per top-level class.
+    initializedFields.clear();
+    super.setRoot(root);
+  }
+
+  @Override
+  protected void checkConstructorInvocation(
+      AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, NewClassTree src) {
+    // Receiver annotations for constructors are forbidden, therefore no check is necessary.
+    // TODO: nested constructors can have receivers!
+  }
+
+  @Override
+  protected void checkConstructorResult(
+      AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {
+    // Nothing to check
+  }
+
+  @Override
+  protected void checkThisOrSuperConstructorCall(
+      MethodInvocationTree superCall, @CompilerMessageKey String errorKey) {
+    // Nothing to check
+  }
+
+  @Override
+  protected void commonAssignmentCheck(
+      Tree varTree,
+      ExpressionTree valueExp,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    // field write of the form x.f = y
+    if (TreeUtils.isFieldAccess(varTree)) {
+      // cast is safe: a field access can only be an IdentifierTree or MemberSelectTree
+      ExpressionTree lhs = (ExpressionTree) varTree;
+      ExpressionTree y = valueExp;
+      Element el = TreeUtils.elementFromUse(lhs);
+      AnnotatedTypeMirror xType = atypeFactory.getReceiverType(lhs);
+      AnnotatedTypeMirror yType = atypeFactory.getAnnotatedType(y);
+      // the special FBC rules do not apply if there is an explicit
+      // UnknownInitialization annotation
+      Set<AnnotationMirror> fieldAnnotations =
+          atypeFactory.getAnnotatedType(TreeUtils.elementFromUse(lhs)).getAnnotations();
+      if (!AnnotationUtils.containsSameByName(
+          fieldAnnotations, atypeFactory.UNKNOWN_INITIALIZATION)) {
+        if (!ElementUtils.isStatic(el)
+            && !(atypeFactory.isInitialized(yType)
+                || atypeFactory.isUnderInitialization(xType)
+                || atypeFactory.isFbcBottom(yType))) {
+          @CompilerMessageKey String err;
+          if (atypeFactory.isInitialized(xType)) {
+            err = "initialization.field.write.initialized";
+          } else {
+            err = "initialization.field.write.unknown";
+          }
+          checker.reportError(varTree, err, varTree);
+          return; // prevent issuing another errow about subtyping
+        }
+      }
+    }
+    super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs);
+  }
+
+  @Override
+  public Void visitVariable(VariableTree node, Void p) {
+    // is this a field (and not a local variable)?
+    if (TreeUtils.elementFromDeclaration(node).getKind().isField()) {
+      Set<AnnotationMirror> annotationMirrors =
+          atypeFactory.getAnnotatedType(node).getExplicitAnnotations();
+      // Fields cannot have commitment annotations.
+      for (Class<? extends Annotation> c : atypeFactory.getInitializationAnnotations()) {
+        for (AnnotationMirror a : annotationMirrors) {
+          if (atypeFactory.isUnknownInitialization(a)) {
+            continue; // unknown initialization is allowed
+          }
+          if (atypeFactory.areSameByClass(a, c)) {
+            checker.reportError(node, "initialization.field.type", node);
+            break;
+          }
+        }
+      }
+    }
+    return super.visitVariable(node, p);
+  }
+
+  @Override
+  protected boolean checkContract(
+      JavaExpression expr,
+      AnnotationMirror necessaryAnnotation,
+      AnnotationMirror inferredAnnotation,
+      CFAbstractStore<?, ?> store) {
+    // also use the information about initialized fields to check contracts
+    final AnnotationMirror invariantAnno = atypeFactory.getFieldInvariantAnnotation();
+
+    if (!atypeFactory.getQualifierHierarchy().isSubtype(invariantAnno, necessaryAnnotation)
+        || !(expr instanceof FieldAccess)) {
+      return super.checkContract(expr, necessaryAnnotation, inferredAnnotation, store);
+    }
+    if (expr.containsUnknown()) {
+      return false;
+    }
+
+    FieldAccess fa = (FieldAccess) expr;
+    if (fa.getReceiver() instanceof ThisReference || fa.getReceiver() instanceof ClassName) {
+      @SuppressWarnings("unchecked")
+      Store s = (Store) store;
+      if (s.isFieldInitialized(fa.getField())) {
+        AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(fa.getField());
+        // is this an invariant-field?
+        if (AnnotationUtils.containsSame(fieldType.getAnnotations(), invariantAnno)) {
+          return true;
+        }
+      }
+    } else {
+      @SuppressWarnings("unchecked")
+      Value value = (Value) store.getValue(fa.getReceiver());
+
+      Set<AnnotationMirror> receiverAnnoSet;
+      if (value != null) {
+        receiverAnnoSet = value.getAnnotations();
+      } else if (fa.getReceiver() instanceof LocalVariable) {
+        Element elem = ((LocalVariable) fa.getReceiver()).getElement();
+        AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(elem);
+        receiverAnnoSet = receiverType.getAnnotations();
+      } else {
+        // Is there anything better we could do?
+        return false;
+      }
+
+      boolean isReceiverInitialized = false;
+      for (AnnotationMirror anno : receiverAnnoSet) {
+        if (atypeFactory.isInitialized(anno)) {
+          isReceiverInitialized = true;
+        }
+      }
+
+      AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(fa.getField());
+      // The receiver is fully initialized and the field type
+      // has the invariant type.
+      if (isReceiverInitialized
+          && AnnotationUtils.containsSame(fieldType.getAnnotations(), invariantAnno)) {
+        return true;
+      }
+    }
+    return super.checkContract(expr, necessaryAnnotation, inferredAnnotation, store);
+  }
+
+  @Override
+  public Void visitTypeCast(TypeCastTree node, Void p) {
+    AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(node.getExpression());
+    AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(node);
+    AnnotationMirror exprAnno = null, castAnno = null;
+
+    // find commitment annotation
+    for (Class<? extends Annotation> a : atypeFactory.getInitializationAnnotations()) {
+      if (castType.hasAnnotation(a)) {
+        assert castAnno == null;
+        castAnno = castType.getAnnotation(a);
+      }
+      if (exprType.hasAnnotation(a)) {
+        assert exprAnno == null;
+        exprAnno = exprType.getAnnotation(a);
+      }
+    }
+
+    // TODO: this is most certainly unsafe!! (and may be hiding some problems)
+    // If we don't find a commitment annotation, then we just assume that
+    // the subtyping is alright.
+    // The case that has come up is with wildcards not getting a type for
+    // some reason, even though the default is @Initialized.
+    boolean isSubtype;
+    if (exprAnno == null || castAnno == null) {
+      isSubtype = true;
+    } else {
+      assert exprAnno != null && castAnno != null;
+      isSubtype = atypeFactory.getQualifierHierarchy().isSubtype(exprAnno, castAnno);
+    }
+
+    if (!isSubtype) {
+      checker.reportError(
+          node,
+          "initialization.cast",
+          annoFormatter.formatAnnotationMirror(exprAnno),
+          annoFormatter.formatAnnotationMirror(castAnno));
+      return p; // suppress cast.unsafe warning
+    }
+
+    return super.visitTypeCast(node, p);
+  }
+
+  protected final List<VariableTree> initializedFields;
+
+  @Override
+  public void processClassTree(ClassTree node) {
+    // go through all members and look for initializers.
+    // save all fields that are initialized and do not report errors about
+    // them later when checking constructors.
+    for (Tree member : node.getMembers()) {
+      if (member.getKind() == Tree.Kind.BLOCK && !((BlockTree) member).isStatic()) {
+        BlockTree block = (BlockTree) member;
+        Store store = atypeFactory.getRegularExitStore(block);
+
+        // Add field values for fields with an initializer.
+        for (Pair<VariableElement, Value> t : store.getAnalysis().getFieldValues()) {
+          store.addInitializedField(t.first);
+        }
+        final List<VariableTree> init =
+            atypeFactory.getInitializedInvariantFields(store, getCurrentPath());
+        initializedFields.addAll(init);
+      }
+    }
+
+    super.processClassTree(node);
+
+    // Warn about uninitialized static fields.
+    if (node.getKind() == Kind.CLASS) {
+      boolean isStatic = true;
+      // See GenericAnnotatedTypeFactory.performFlowAnalysis for why we use
+      // the regular exit store of the class here.
+      Store store = atypeFactory.getRegularExitStore(node);
+      // Add field values for fields with an initializer.
+      for (Pair<VariableElement, Value> t : store.getAnalysis().getFieldValues()) {
+        store.addInitializedField(t.first);
+      }
+      List<AnnotationMirror> receiverAnnotations = Collections.emptyList();
+      checkFieldsInitialized(node, isStatic, store, receiverAnnotations);
+    }
+  }
+
+  @Override
+  public Void visitMethod(MethodTree node, Void p) {
+    if (TreeUtils.isConstructor(node)) {
+      Collection<? extends AnnotationMirror> returnTypeAnnotations =
+          AnnotationUtils.getExplicitAnnotationsOnConstructorResult(node);
+      // check for invalid constructor return type
+      for (Class<? extends Annotation> c :
+          atypeFactory.getInvalidConstructorReturnTypeAnnotations()) {
+        for (AnnotationMirror a : returnTypeAnnotations) {
+          if (atypeFactory.areSameByClass(a, c)) {
+            checker.reportError(node, "initialization.constructor.return.type", node);
+            break;
+          }
+        }
+      }
+
+      // Check that all fields have been initialized at the end of the constructor.
+      boolean isStatic = false;
+      Store store = atypeFactory.getRegularExitStore(node);
+      List<? extends AnnotationMirror> receiverAnnotations = getAllReceiverAnnotations(node);
+      checkFieldsInitialized(node, isStatic, store, receiverAnnotations);
+    }
+    return super.visitMethod(node, p);
+  }
+
+  /** Returns the full list of annotations on the receiver. */
+  private List<? extends AnnotationMirror> getAllReceiverAnnotations(MethodTree node) {
+    // TODO: get access to a Types instance and use it to get receiver type
+    // Or, extend ExecutableElement with such a method.
+    // Note that we cannot use the receiver type from AnnotatedExecutableType, because that would
+    // only have the nullness annotations; here we want to see all annotations on the receiver.
+    List<? extends AnnotationMirror> rcvannos = null;
+    if (TreeUtils.isConstructor(node)) {
+      com.sun.tools.javac.code.Symbol meth =
+          (com.sun.tools.javac.code.Symbol) TreeUtils.elementFromDeclaration(node);
+      rcvannos = meth.getRawTypeAttributes();
+      if (rcvannos == null) {
+        rcvannos = Collections.emptyList();
+      }
+    }
+    return rcvannos;
+  }
+
+  /**
+   * Checks that all fields (all static fields if {@code staticFields} is true) are initialized in
+   * the given store.
+   *
+   * @param node a {@link ClassTree} if {@code staticFields} is true; a {@link MethodTree} for a
+   *     constructor if {@code staticFields} is false. This is where errors are reported, if they
+   *     are not reported at the fields themselves
+   * @param staticFields whether to check static fields or instance fields
+   * @param store the store
+   * @param receiverAnnotations the annotations on the receiver
+   */
+  // TODO: the code for checking if fields are initialized should be re-written,
+  // as the current version contains quite a few ugly parts, is hard to understand,
+  // and it is likely that it does not take full advantage of the information
+  // about initialization we compute in
+  // GenericAnnotatedTypeFactory.initializationStaticStore and
+  // GenericAnnotatedTypeFactory.initializationStore.
+  protected void checkFieldsInitialized(
+      Tree node,
+      boolean staticFields,
+      Store store,
+      List<? extends AnnotationMirror> receiverAnnotations) {
+    // If the store is null, then the constructor cannot terminate successfully
+    if (store == null) {
+      return;
+    }
+
+    Pair<List<VariableTree>, List<VariableTree>> uninitializedFields =
+        atypeFactory.getUninitializedFields(
+            store, getCurrentPath(), staticFields, receiverAnnotations);
+    List<VariableTree> violatingFields = uninitializedFields.first;
+    List<VariableTree> nonviolatingFields = uninitializedFields.second;
+
+    if (staticFields) {
+      // TODO: Why is nothing done for static fields?
+      // Do we need the following?
+      // violatingFields.removeAll(store.initializedFields);
+    } else {
+      // remove fields that have already been initialized by an
+      // initializer block
+      violatingFields.removeAll(initializedFields);
+      nonviolatingFields.removeAll(initializedFields);
+    }
+
+    // Errors are issued at the field declaration if the field is static or if the constructor
+    // is the default constructor.
+    // Errors are issued at the constructor declaration if the field is non-static and the
+    // constructor is non-default.
+    boolean errorAtField = staticFields || TreeUtils.isSynthetic((MethodTree) node);
+
+    String FIELDS_UNINITIALIZED_KEY =
+        (staticFields
+            ? "initialization.static.field.uninitialized"
+            : errorAtField
+                ? "initialization.field.uninitialized"
+                : "initialization.fields.uninitialized");
+
+    // Remove fields with a relevant @SuppressWarnings annotation.
+    violatingFields.removeIf(
+        f ->
+            checker.shouldSuppressWarnings(TreeUtils.elementFromTree(f), FIELDS_UNINITIALIZED_KEY));
+    nonviolatingFields.removeIf(
+        f ->
+            checker.shouldSuppressWarnings(TreeUtils.elementFromTree(f), FIELDS_UNINITIALIZED_KEY));
+
+    if (!violatingFields.isEmpty()) {
+      if (errorAtField) {
+        // Issue each error at the relevant field
+        for (VariableTree f : violatingFields) {
+          checker.reportError(f, FIELDS_UNINITIALIZED_KEY, f.getName());
+        }
+      } else {
+        // Issue all the errors at the relevant constructor
+        StringJoiner fieldsString = new StringJoiner(", ");
+        for (VariableTree f : violatingFields) {
+          fieldsString.add(f.getName());
+        }
+        checker.reportError(node, FIELDS_UNINITIALIZED_KEY, fieldsString);
+      }
+    }
+
+    // Support -Ainfer command-line argument.
+    WholeProgramInference wpi = atypeFactory.getWholeProgramInference();
+    if (wpi != null) {
+      // For each uninitialized field, treat it as if the default value is assigned to it.
+      List<VariableTree> uninitFields = new ArrayList<>(violatingFields);
+      uninitFields.addAll(nonviolatingFields);
+      for (VariableTree fieldTree : uninitFields) {
+        Element elt = TreeUtils.elementFromTree(fieldTree);
+        wpi.updateFieldFromType(
+            fieldTree,
+            elt,
+            fieldTree.getName().toString(),
+            atypeFactory.getDefaultValueAnnotatedType(elt.asType()));
+      }
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/messages.properties b/checker/src/main/java/org/checkerframework/checker/initialization/messages.properties
new file mode 100644
index 0000000..18115b9
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/initialization/messages.properties
@@ -0,0 +1,9 @@
+initialization.field.write.initialized=storing values that are possibly under initialization in the field of initialized objects is not permitted
+initialization.field.write.unknown=storing values that are possibly under initialization in the field of objects whose initialization state is not known is not permitted
+initialization.field.write.in.constructor=storing possibly-uninitialized values that come from outside the constructor is not permitted
+initialization.constructor.return.type=the newly created object in a constructor must be @UnderInitialization
+initialization.field.type=initialization annotations are not allowed on fields, except for @UnknownInitialization
+initialization.cast=cast between initialization types %s and %s is not allowed
+initialization.field.uninitialized=the default constructor does not initialize field %s
+initialization.fields.uninitialized=the constructor does not initialize fields: %s
+initialization.static.field.uninitialized=static field %s not initialized
diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java
new file mode 100644
index 0000000..e769aa7
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java
@@ -0,0 +1,242 @@
+package org.checkerframework.checker.interning;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.checker.interning.qual.InternMethod;
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+import org.checkerframework.checker.interning.qual.PolyInterned;
+import org.checkerframework.checker.interning.qual.UnknownInterned;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.type.typeannotator.DefaultQualifierForUseTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * An {@link AnnotatedTypeFactory} that accounts for the properties of the Interned type system.
+ * This type factory will add the {@link Interned} annotation to a type if the input:
+ *
+ * <ol>
+ *   <li value="1">is a String literal
+ *   <li value="2">is a class literal
+ *   <li value="3">has an enum type
+ *   <li value="4">has a primitive type
+ *   <li value="5">has the type java.lang.Class
+ *   <li value="6">is a use of a class declared to be @Interned
+ * </ol>
+ *
+ * This type factory adds {@link InternedDistinct} to formal parameters that have a {@code @}{@link
+ * FindDistinct} declaration annotation. (TODO: That isn't a good implementation, because it is not
+ * accurate: the value might be equals() to some other Java value. More seriously, it permits too
+ * much. Writing {@code @FindDistinct} should permit equality tests on the given formal parameter,
+ * but should not (for example) permit the formal parameter to be assigned into an
+ * {@code @InternedDistinct} location.)
+ *
+ * <p>This factory extends {@link BaseAnnotatedTypeFactory} and inherits its functionality,
+ * including: flow-sensitive qualifier inference, qualifier polymorphism (of {@link PolyInterned}),
+ * implicit annotations via {@link org.checkerframework.framework.qual.DefaultFor} on {@link
+ * Interned} (to handle cases 1, 2, 4), and user-specified defaults via {@link DefaultQualifier}.
+ * Case 5 is handled by the stub library.
+ */
+public class InterningAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** The {@link UnknownInterned} annotation. */
+  final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, UnknownInterned.class);
+  /** The {@link Interned} annotation. */
+  final AnnotationMirror INTERNED = AnnotationBuilder.fromClass(elements, Interned.class);
+  /** The {@link InternedDistinct} annotation. */
+  final AnnotationMirror INTERNED_DISTINCT =
+      AnnotationBuilder.fromClass(elements, InternedDistinct.class);
+
+  /**
+   * Creates a new {@link InterningAnnotatedTypeFactory} that operates on a particular AST.
+   *
+   * @param checker the checker to use
+   */
+  public InterningAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    // If you update the following, also update ../../../../../docs/manual/interning-checker.tex
+    addAliasedTypeAnnotation("com.sun.istack.internal.Interned", INTERNED);
+
+    this.postInit();
+  }
+
+  @Override
+  protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() {
+    return new InterningDefaultQualifierForUseTypeAnnotator(this);
+  }
+
+  /**
+   * Does not add defaults for type uses on constructor results. Constructor results should be
+   * {@code @UnknownInterned} by default.
+   */
+  static class InterningDefaultQualifierForUseTypeAnnotator
+      extends DefaultQualifierForUseTypeAnnotator {
+
+    public InterningDefaultQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) {
+      super(typeFactory);
+    }
+
+    @Override
+    public Void visitExecutable(AnnotatedExecutableType type, Void p) {
+      MethodSymbol methodElt = (MethodSymbol) type.getElement();
+
+      if (methodElt == null || methodElt.getKind() != ElementKind.CONSTRUCTOR) {
+        // Annotate method returns, not constructors.
+        scan(type.getReturnType(), p);
+      }
+      if (type.getReceiverType() != null
+          // Intern method may be called on UnknownInterned object, so its receiver should
+          // not be annotated as @Interned.
+          && typeFactory.getDeclAnnotation(methodElt, InternMethod.class) == null) {
+        scanAndReduce(type.getReceiverType(), p, null);
+      }
+      scanAndReduce(type.getParameterTypes(), p, null);
+      scanAndReduce(type.getThrownTypes(), p, null);
+      scanAndReduce(type.getTypeVariables(), p, null);
+      return null;
+    }
+  }
+
+  @Override
+  public Set<AnnotationMirror> getTypeDeclarationBounds(TypeMirror typeMirror) {
+    if (typeMirror.getKind() == TypeKind.DECLARED
+        && ((DeclaredType) typeMirror).asElement().getKind() == ElementKind.ENUM) {
+      return AnnotationMirrorSet.singleElementSet(INTERNED);
+    }
+    return super.getTypeDeclarationBounds(typeMirror);
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(super.createTreeAnnotator(), new InterningTreeAnnotator(this));
+  }
+
+  @Override
+  protected TypeAnnotator createTypeAnnotator() {
+    return new ListTypeAnnotator(new InterningTypeAnnotator(this), super.createTypeAnnotator());
+  }
+
+  @Override
+  public void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean useFlow) {
+    Element element = TreeUtils.elementFromTree(tree);
+    if (!type.isAnnotatedInHierarchy(INTERNED) && ElementUtils.isCompileTimeConstant(element)) {
+      type.addAnnotation(INTERNED);
+    }
+    super.addComputedTypeAnnotations(tree, type, useFlow);
+  }
+
+  @Override
+  public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) {
+    if (!type.isAnnotatedInHierarchy(INTERNED) && ElementUtils.isCompileTimeConstant(element)) {
+      type.addAnnotation(INTERNED);
+    }
+    super.addComputedTypeAnnotations(element, type);
+  }
+
+  /** A class for adding annotations based on tree. */
+  private class InterningTreeAnnotator extends TreeAnnotator {
+
+    InterningTreeAnnotator(InterningAnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
+      if (TreeUtils.isCompileTimeString(node)) {
+        type.replaceAnnotation(INTERNED);
+      } else if (TreeUtils.isStringConcatenation(node)) {
+        type.replaceAnnotation(TOP);
+      } else if (type.getKind().isPrimitive()
+          || node.getKind() == Tree.Kind.EQUAL_TO
+          || node.getKind() == Tree.Kind.NOT_EQUAL_TO) {
+        type.replaceAnnotation(INTERNED);
+      } else {
+        type.replaceAnnotation(TOP);
+      }
+      return super.visitBinary(node, type);
+    }
+
+    /* Compound assignments never result in an interned result.
+     */
+    @Override
+    public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
+      type.replaceAnnotation(TOP);
+      return super.visitCompoundAssignment(node, type);
+    }
+
+    @Override
+    public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) {
+      if (TreeUtils.typeOf(node.getType()).getKind().isPrimitive()) {
+        type.replaceAnnotation(INTERNED);
+      }
+      return super.visitTypeCast(node, type);
+    }
+
+    @Override
+    public Void visitIdentifier(IdentifierTree node, AnnotatedTypeMirror type) {
+      Element e = TreeUtils.elementFromTree(node);
+      if (atypeFactory.getDeclAnnotation(e, FindDistinct.class) != null) {
+        // TODO: See note above about this being a poor implementation.
+        type.replaceAnnotation(INTERNED_DISTINCT);
+      }
+      return super.visitIdentifier(node, type);
+    }
+  }
+
+  /** Adds @Interned to enum types. */
+  private class InterningTypeAnnotator extends TypeAnnotator {
+
+    InterningTypeAnnotator(InterningAnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitDeclared(AnnotatedDeclaredType t, Void p) {
+      // case 3: Enum types, and the Enum class itself, are interned
+      Element elt = t.getUnderlyingType().asElement();
+      assert elt != null;
+      if (elt.getKind() == ElementKind.ENUM) {
+        t.replaceAnnotation(INTERNED);
+      }
+      return super.visitDeclared(t, p);
+    }
+  }
+
+  /**
+   * Unbox type and replace any interning type annotations with @Interned since all primitives can
+   * safely use ==. See case 4 in the class comments.
+   */
+  @Override
+  public AnnotatedPrimitiveType getUnboxedType(AnnotatedDeclaredType type) {
+    AnnotatedPrimitiveType primitive = super.getUnboxedType(type);
+    primitive.replaceAnnotation(INTERNED);
+    return primitive;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningChecker.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningChecker.java
new file mode 100644
index 0000000..063200c
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningChecker.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.interning;
+
+import javax.annotation.processing.SupportedOptions;
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.StubFiles;
+import org.checkerframework.framework.source.SupportedLintOptions;
+
+/**
+ * A type-checker plug-in for the {@link Interned} qualifier that finds (and verifies the absence
+ * of) equality-testing and interning errors.
+ *
+ * <p>The {@link Interned} annotation indicates that a variable refers to the canonical instance of
+ * an object, meaning that it is safe to compare that object using the "==" operator. This plugin
+ * warns whenever "==" is used in cases where one or both operands are not {@link Interned}.
+ * Optionally, it suggests using "==" instead of ".equals" where possible.
+ *
+ * @checker_framework.manual #interning-checker Interning Checker
+ */
+@StubFiles({"com-sun.astub", "org-jcp.astub", "org-xml.astub", "sun.astub"})
+@SupportedLintOptions({"dotequals"})
+@SupportedOptions({"checkclass"})
+public final class InterningChecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java
new file mode 100644
index 0000000..5a48866
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java
@@ -0,0 +1,954 @@
+package org.checkerframework.checker.interning;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.Scope;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import java.util.Comparator;
+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.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.Diagnostic.Kind;
+import org.checkerframework.checker.interning.qual.CompareToMethod;
+import org.checkerframework.checker.interning.qual.EqualsMethod;
+import org.checkerframework.checker.interning.qual.InternMethod;
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+import org.checkerframework.checker.interning.qual.UsesObjectEquals;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.util.Heuristics;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Typechecks source code for interning violations. A type is considered interned if its primary
+ * annotation is {@link Interned} or {@link InternedDistinct}. This visitor reports errors or
+ * warnings for violations for the following cases:
+ *
+ * <ol>
+ *   <li value="1">either argument to a "==" or "!=" comparison is not Interned (error
+ *       "not.interned"). As a special case, the comparison is permitted if either arugment is
+ *       InternedDistinct.
+ *   <li value="2">the receiver and argument for a call to an equals method are both Interned
+ *       (optional warning "unnecessary.equals")
+ * </ol>
+ *
+ * @see BaseTypeVisitor
+ */
+public final class InterningVisitor extends BaseTypeVisitor<InterningAnnotatedTypeFactory> {
+
+  /** The @Interned annotation. */
+  private final AnnotationMirror INTERNED = AnnotationBuilder.fromClass(elements, Interned.class);
+  /** The @InternedDistinct annotation. */
+  private final AnnotationMirror INTERNED_DISTINCT =
+      AnnotationBuilder.fromClass(elements, InternedDistinct.class);
+  /**
+   * The declared type of which the equality tests should be tested, if the user explicitly passed
+   * one. The user can pass the class name via the {@code -Acheckclass=...} option. Null if no class
+   * is specified, or the class specified isn't in the classpath.
+   */
+  private final DeclaredType typeToCheck = typeToCheck();
+
+  /** The Comparable.compareTo method. */
+  private final ExecutableElement comparableCompareTo =
+      TreeUtils.getMethod(
+          "java.lang.Comparable", "compareTo", 1, checker.getProcessingEnvironment());
+
+  /** Create an InterningVisitor. */
+  public InterningVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  /**
+   * @return true if interning should be verified for the input expression. By default, all classes
+   *     are checked for interning unless {@code -Acheckclass} is specified.
+   * @see <a href="https://checkerframework.org/manual/#interning-checks">What the Interning Checker
+   *     checks</a>
+   */
+  private boolean shouldCheckExpression(ExpressionTree tree) {
+    if (typeToCheck == null) {
+      return true;
+    }
+
+    TypeMirror type = TreeUtils.typeOf(tree);
+    return types.isSubtype(type, typeToCheck) || types.isSubtype(typeToCheck, type);
+  }
+
+  /** Checks comparison operators, == and !=, for INTERNING violations. */
+  @Override
+  public Void visitBinary(BinaryTree node, Void p) {
+
+    // No checking unless the operator is "==" or "!=".
+    if (!(node.getKind() == Tree.Kind.EQUAL_TO || node.getKind() == Tree.Kind.NOT_EQUAL_TO)) {
+      return super.visitBinary(node, p);
+    }
+
+    ExpressionTree leftOp = node.getLeftOperand();
+    ExpressionTree rightOp = node.getRightOperand();
+
+    // Check passes if either arg is null.
+    if (leftOp.getKind() == Tree.Kind.NULL_LITERAL || rightOp.getKind() == Tree.Kind.NULL_LITERAL) {
+      return super.visitBinary(node, p);
+    }
+
+    AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp);
+    AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp);
+
+    // If either argument is a primitive, check passes due to auto-unboxing
+    if (left.getKind().isPrimitive() || right.getKind().isPrimitive()) {
+      return super.visitBinary(node, p);
+    }
+
+    if (left.hasEffectiveAnnotation(INTERNED_DISTINCT)
+        || right.hasEffectiveAnnotation(INTERNED_DISTINCT)) {
+      return super.visitBinary(node, p);
+    }
+
+    // If shouldCheckExpression returns true for either the LHS or RHS,
+    // this method proceeds with the interning check.
+
+    // Justification: Consider the following scenario:
+
+    // interface I { ... }
+    // class A { ... }
+    // class B extends A implements I { ... }
+    // ...
+    // I i;
+    // A a;
+    // ...
+    // if (a == i) { ... }
+
+    // The Java compiler does not issue a compilation error for the (a == i) comparison because,
+    // even though A does not implement I, 'a' could be assigned an instance of B, and B does
+    // implement I (note that the compiler does not need to know about the existence of B
+    // in order to assume this).
+
+    // Now suppose the user passes -AcheckClass=A on the command-line.
+    // I is not a subtype or supertype of A, so shouldCheckExpression will not return true for I.
+    // But the interning check must be performed, given the argument above.  Therefore if
+    // shouldCheckExpression returns true for either the LHS or the RHS, this method proceeds
+    // with the interning check.
+
+    if (!shouldCheckExpression(leftOp) && !shouldCheckExpression(rightOp)) {
+      return super.visitBinary(node, p);
+    }
+
+    // Syntactic checks for legal uses of ==
+    if (suppressInsideComparison(node)) {
+      return super.visitBinary(node, p);
+    }
+    if (suppressEarlyEquals(node)) {
+      return super.visitBinary(node, p);
+    }
+    if (suppressEarlyCompareTo(node)) {
+      return super.visitBinary(node, p);
+    }
+
+    if (suppressEqualsIfClassIsAnnotated(left, right)) {
+      return super.visitBinary(node, p);
+    }
+
+    Element leftElt = TypesUtils.getTypeElement(left.getUnderlyingType());
+    // If neither @Interned or @UsesObjectEquals, report error.
+    if (!(left.hasEffectiveAnnotation(INTERNED)
+        || (leftElt != null
+            && atypeFactory.getDeclAnnotation(leftElt, UsesObjectEquals.class) != null))) {
+      checker.reportError(leftOp, "not.interned", left);
+    }
+
+    Element rightElt = TypesUtils.getTypeElement(right.getUnderlyingType());
+    if (!(right.hasEffectiveAnnotation(INTERNED)
+        || (rightElt != null
+            && atypeFactory.getDeclAnnotation(rightElt, UsesObjectEquals.class) != null))) {
+      checker.reportError(rightOp, "not.interned", right);
+    }
+    return super.visitBinary(node, p);
+  }
+
+  /**
+   * If lint option "dotequals" is specified, warn if the .equals method is used where reference
+   * equality is safe.
+   */
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+    if (isInvocationOfEquals(node)) {
+      AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(node);
+      AnnotatedTypeMirror comp = atypeFactory.getAnnotatedType(node.getArguments().get(0));
+
+      if (this.checker.getLintOption("dotequals", true)
+          && receiverType.hasEffectiveAnnotation(INTERNED)
+          && comp.hasEffectiveAnnotation(INTERNED)) {
+        checker.reportWarning(node, "unnecessary.equals");
+      }
+    }
+
+    return super.visitMethodInvocation(node, p);
+  }
+
+  // Ensure that method annotations are not written on methods they don't apply to.
+  @Override
+  public Void visitMethod(MethodTree node, Void p) {
+    ExecutableElement methElt = TreeUtils.elementFromDeclaration(node);
+    boolean hasCompareToMethodAnno =
+        atypeFactory.getDeclAnnotation(methElt, CompareToMethod.class) != null;
+    boolean hasEqualsMethodAnno =
+        atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null;
+    boolean hasInternMethodAnno =
+        atypeFactory.getDeclAnnotation(methElt, InternMethod.class) != null;
+    int params = methElt.getParameters().size();
+    if (hasCompareToMethodAnno && !(params == 1 || params == 2)) {
+      checker.reportError(
+          node, "invalid.method.annotation", "@CompareToMethod", "1 or 2", methElt, params);
+    } else if (hasEqualsMethodAnno && !(params == 1 || params == 2)) {
+      checker.reportError(
+          node, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params);
+    } else if (hasInternMethodAnno && !(params == 0)) {
+      checker.reportError(node, "invalid.method.annotation", "@InternMethod", "0", methElt, params);
+    }
+
+    return super.visitMethod(node, p);
+  }
+
+  /**
+   * Method to implement the @UsesObjectEquals functionality. If a class is annotated
+   * with @UsesObjectEquals, it must:
+   *
+   * <ul>
+   *   <li>not override .equals(Object) and be a subclass of a class annotated
+   *       with @UsesObjectEquals, or
+   *   <li>override equals(Object) with body "this == arg"
+   * </ul>
+   *
+   * If a class is not annotated with @UsesObjectEquals, it must:
+   *
+   * <ul>
+   *   <li>not have a superclass annotated with @UsesObjectEquals
+   * </ul>
+   *
+   * @see
+   *     org.checkerframework.common.basetype.BaseTypeVisitor#visitClass(com.sun.source.tree.ClassTree,
+   *     java.lang.Object)
+   */
+  @Override
+  public void processClassTree(ClassTree classTree) {
+    TypeElement elt = TreeUtils.elementFromDeclaration(classTree);
+    AnnotationMirror annotation = atypeFactory.getDeclAnnotation(elt, UsesObjectEquals.class);
+
+    // If @UsesObjectEquals is present, check to make sure the class does not override equals
+    // and its supertype is Object or is annotated with @UsesObjectEquals.
+    if (annotation != null) {
+      MethodTree equalsMethod = equalsImplementation(classTree);
+      if (equalsMethod != null) {
+        if (!isReferenceEqualityImplementation(equalsMethod)) {
+          checker.reportError(classTree, "overrides.equals");
+        }
+      } else {
+        // Does not override equals()
+        TypeMirror superClass = elt.getSuperclass();
+        if (superClass != null
+            // The super class of an interface is "none" rather than null.
+            && superClass.getKind() == TypeKind.DECLARED) {
+          TypeElement superClassElement = TypesUtils.getTypeElement(superClass);
+          if (superClassElement != null
+              && !ElementUtils.isObject(superClassElement)
+              && atypeFactory.getDeclAnnotation(superClassElement, UsesObjectEquals.class)
+                  == null) {
+            checker.reportError(classTree, "superclass.notannotated");
+          }
+        }
+      }
+    }
+
+    super.processClassTree(classTree);
+  }
+
+  /**
+   * Returns true if the given equals() method implements reference equality.
+   *
+   * @param equalsMethod an overriding implementation of Object.equals()
+   * @return true if the given equals() method implements reference equality
+   */
+  private boolean isReferenceEqualityImplementation(MethodTree equalsMethod) {
+    BlockTree body = equalsMethod.getBody();
+    List<? extends StatementTree> bodyStatements = body.getStatements();
+    if (bodyStatements.size() == 1) {
+      StatementTree bodyStatement = bodyStatements.get(0);
+      if (bodyStatement.getKind() == Tree.Kind.RETURN) {
+        ExpressionTree returnExpr =
+            TreeUtils.withoutParens(((ReturnTree) bodyStatement).getExpression());
+        if (returnExpr.getKind() == Tree.Kind.EQUAL_TO) {
+          BinaryTree bt = (BinaryTree) returnExpr;
+          ExpressionTree lhsTree = bt.getLeftOperand();
+          ExpressionTree rhsTree = bt.getRightOperand();
+          if (lhsTree.getKind() == Tree.Kind.IDENTIFIER
+              && rhsTree.getKind() == Tree.Kind.IDENTIFIER) {
+            Name leftName = ((IdentifierTree) lhsTree).getName();
+            Name rightName = ((IdentifierTree) rhsTree).getName();
+            Name paramName = equalsMethod.getParameters().get(0).getName();
+            if ((leftName.contentEquals("this") && rightName == paramName)
+                || (leftName == paramName && rightName.contentEquals("this"))) {
+              return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  @Override
+  protected void checkConstructorResult(
+      AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {
+    if (constructorElement.getEnclosingElement().getKind() == ElementKind.ENUM) {
+      // Enums constructor are only called once per enum constant.
+      return;
+    }
+    super.checkConstructorResult(constructorType, constructorElement);
+  }
+
+  @Override
+  public boolean validateTypeOf(Tree tree) {
+    // Don't check the result type of a constructor, because it must be @UnknownInterned, even
+    // if the type on the class declaration is @Interned.
+    if (tree.getKind() == Tree.Kind.METHOD && TreeUtils.isConstructor((MethodTree) tree)) {
+      return true;
+    } else if (tree.getKind() == Tree.Kind.NEW_CLASS) {
+      NewClassTree newClassTree = (NewClassTree) tree;
+      TypeMirror typeMirror = TreeUtils.typeOf(newClassTree);
+      Set<AnnotationMirror> bounds = atypeFactory.getTypeDeclarationBounds(typeMirror);
+      // Don't issue an invalid type warning for creations of objects of interned classes;
+      // instead, issue an interned.object.creation if required.
+      if (atypeFactory.containsSameByClass(bounds, Interned.class)) {
+        ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(newClassTree);
+        AnnotatedExecutableType constructor = fromUse.executableType;
+        if (!checkCreationOfInternedObject(newClassTree, constructor)) {
+          return false;
+        }
+      }
+    }
+    return super.validateTypeOf(tree);
+  }
+
+  /**
+   * Issue an error if {@code newInternedObject} is not immediately interned.
+   *
+   * @param newInternedObject call to a constructor of an interned class
+   * @param constructor declared type of the constructor
+   * @return false unless {@code newInternedObject} is immediately interned
+   */
+  private boolean checkCreationOfInternedObject(
+      NewClassTree newInternedObject, AnnotatedExecutableType constructor) {
+    if (constructor.getReturnType().hasAnnotation(Interned.class)) {
+      return true;
+    }
+    TreePath path = getCurrentPath();
+    if (path != null) {
+      TreePath parentPath = path.getParentPath();
+      while (parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.PARENTHESIZED) {
+        parentPath = parentPath.getParentPath();
+      }
+      if (parentPath != null && parentPath.getParentPath() != null) {
+        Tree parent = parentPath.getParentPath().getLeaf();
+        if (parent.getKind() == Tree.Kind.METHOD_INVOCATION) {
+          // Allow new MyInternType().intern(), where "intern" is any method marked @InternMethod.
+          ExecutableElement elt = TreeUtils.elementFromUse((MethodInvocationTree) parent);
+          if (atypeFactory.getDeclAnnotation(elt, InternMethod.class) != null) {
+            return true;
+          }
+        }
+      }
+    }
+
+    checker.reportError(newInternedObject, "interned.object.creation");
+    return false;
+  }
+
+  // **********************************************************************
+  // Helper methods
+  // **********************************************************************
+
+  /**
+   * Returns the method that overrides Object.equals, or null.
+   *
+   * @param node a class
+   * @return the class's implementation of equals, or null
+   */
+  private MethodTree equalsImplementation(ClassTree node) {
+    List<? extends Tree> members = node.getMembers();
+    for (Tree member : members) {
+      if (member instanceof MethodTree) {
+        MethodTree mTree = (MethodTree) member;
+        ExecutableElement enclosing = TreeUtils.elementFromDeclaration(mTree);
+        if (overrides(enclosing, Object.class, "equals")) {
+          return mTree;
+        }
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Tests whether a method invocation is an invocation of {@link #equals} with one argument.
+   *
+   * <p>Returns true even if a method overloads {@link Object#equals(Object)}, because of the common
+   * idiom of writing an equals method with a non-Object parameter, in addition to the equals method
+   * that overrides {@link Object#equals(Object)}.
+   *
+   * @param node a method invocation node
+   * @return true iff {@code node} is a invocation of {@code equals()}
+   */
+  private boolean isInvocationOfEquals(MethodInvocationTree node) {
+    ExecutableElement method = TreeUtils.elementFromUse(node);
+    return (method.getParameters().size() == 1
+        && method.getReturnType().getKind() == TypeKind.BOOLEAN
+        // method symbols only have simple names
+        && method.getSimpleName().contentEquals("equals"));
+  }
+
+  /**
+   * Pattern matches particular comparisons to avoid common false positives in the {@link
+   * Comparable#compareTo(Object)} and {@link Object#equals(Object)}.
+   *
+   * <p>Specifically, this method tests if: the comparison is a == comparison, it is the test of an
+   * if statement that's the first statement in the method, and one of the following is true:
+   *
+   * <ol>
+   *   <li>the method overrides {@link Comparator#compare}, the "then" branch of the if statement
+   *       returns zero, and the comparison tests equality of the method's two parameters
+   *   <li>the method overrides {@link Object#equals(Object)} and the comparison tests "this"
+   *       against the method's parameter
+   *   <li>the method overrides {@link Comparable#compareTo(Object)}, the "then" branch of the if
+   *       statement returns zero, and the comparison tests "this" against the method's parameter
+   * </ol>
+   *
+   * @param node the comparison to check
+   * @return true if one of the supported heuristics is matched, false otherwise
+   */
+  // TODO: handle != comparisons too!
+  // TODO: handle more methods, such as early return from addAll when this == arg
+  private boolean suppressInsideComparison(final BinaryTree node) {
+    // Only handle == binary trees
+    if (node.getKind() != Tree.Kind.EQUAL_TO) {
+      return false;
+    }
+
+    Tree left = node.getLeftOperand();
+    Tree right = node.getRightOperand();
+
+    // Only valid if we're comparing identifiers.
+    if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) {
+      return false;
+    }
+
+    TreePath path = getCurrentPath();
+    TreePath parentPath = path.getParentPath();
+    Tree parent = parentPath.getLeaf();
+
+    // Ensure the == is in a return or in an if, and that enclosing statement is the first
+    // statement in the method.
+    if (parent.getKind() == Tree.Kind.RETURN) {
+      // ensure the return statement is the first statement in the method
+      if (parentPath.getParentPath().getParentPath().getLeaf().getKind() != Tree.Kind.METHOD) {
+        return false;
+      }
+
+      // maybe set some variables??
+    } else if (Heuristics.matchParents(getCurrentPath(), Tree.Kind.IF, Tree.Kind.METHOD)) {
+      // Ensure the if statement is the first statement in the method
+
+      // Retrieve the enclosing if statement tree and method tree
+      Tree ifStatementTree = null;
+      MethodTree methodTree = null;
+      // Set ifStatementTree and methodTree
+      {
+        TreePath ppath = parentPath;
+        Tree tree;
+        while ((tree = ppath.getLeaf()) != null) {
+          if (tree.getKind() == Tree.Kind.IF) {
+            ifStatementTree = tree;
+          } else if (tree.getKind() == Tree.Kind.METHOD) {
+            methodTree = (MethodTree) tree;
+            break;
+          }
+          ppath = ppath.getParentPath();
+        }
+      }
+      assert ifStatementTree != null;
+      assert methodTree != null;
+      StatementTree firstStmnt = methodTree.getBody().getStatements().get(0);
+      assert firstStmnt != null;
+      @SuppressWarnings("interning:not.interned") // comparing AST nodes
+      boolean notSameNode = firstStmnt != ifStatementTree;
+      if (notSameNode) {
+        return false; // The if statement is not the first statement in the method.
+      }
+    } else {
+      return false;
+    }
+
+    ExecutableElement enclosingMethod =
+        TreeUtils.elementFromDeclaration(visitorState.getMethodTree());
+    assert enclosingMethod != null;
+
+    final Element lhs = TreeUtils.elementFromUse((IdentifierTree) left);
+    final Element rhs = TreeUtils.elementFromUse((IdentifierTree) right);
+
+    // Matcher to check for if statement that returns zero
+    Heuristics.Matcher matcherIfReturnsZero =
+        new Heuristics.Matcher() {
+
+          @Override
+          public Boolean visitIf(IfTree tree, Void p) {
+            return visit(tree.getThenStatement(), p);
+          }
+
+          @Override
+          public Boolean visitBlock(BlockTree tree, Void p) {
+            if (tree.getStatements().isEmpty()) {
+              return false;
+            }
+            return visit(tree.getStatements().get(0), p);
+          }
+
+          @Override
+          public Boolean visitReturn(ReturnTree tree, Void p) {
+            ExpressionTree expr = tree.getExpression();
+            return (expr != null
+                && expr.getKind() == Tree.Kind.INT_LITERAL
+                && ((LiteralTree) expr).getValue().equals(0));
+          }
+        };
+
+    boolean hasCompareToMethodAnno =
+        atypeFactory.getDeclAnnotation(enclosingMethod, CompareToMethod.class) != null;
+    boolean hasEqualsMethodAnno =
+        atypeFactory.getDeclAnnotation(enclosingMethod, EqualsMethod.class) != null;
+    int params = enclosingMethod.getParameters().size();
+
+    // Determine whether or not the "then" statement of the if has a single
+    // "return 0" statement (for the Comparator.compare heuristic).
+    if (overrides(enclosingMethod, Comparator.class, "compare")
+        || (hasCompareToMethodAnno && params == 2)) {
+      final boolean returnsZero =
+          new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero))
+              .match(getCurrentPath());
+
+      if (!returnsZero) {
+        return false;
+      }
+
+      assert params == 2;
+      Element p1 = enclosingMethod.getParameters().get(0);
+      Element p2 = enclosingMethod.getParameters().get(1);
+      return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs));
+
+    } else if (overrides(enclosingMethod, Object.class, "equals")
+        || (hasEqualsMethodAnno && params == 1)) {
+      assert params == 1;
+      Element param = enclosingMethod.getParameters().get(0);
+      Element thisElt = getThis(trees.getScope(getCurrentPath()));
+      assert thisElt != null;
+      return (thisElt.equals(lhs) && param.equals(rhs))
+          || (thisElt.equals(rhs) && param.equals(lhs));
+
+    } else if (hasEqualsMethodAnno && params == 2) {
+      Element p1 = enclosingMethod.getParameters().get(0);
+      Element p2 = enclosingMethod.getParameters().get(1);
+      return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs));
+
+    } else if (overrides(enclosingMethod, Comparable.class, "compareTo")
+        || (hasCompareToMethodAnno && params == 1)) {
+
+      final boolean returnsZero =
+          new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero))
+              .match(getCurrentPath());
+
+      if (!returnsZero) {
+        return false;
+      }
+
+      assert params == 1;
+      Element param = enclosingMethod.getParameters().get(0);
+      Element thisElt = getThis(trees.getScope(getCurrentPath()));
+      assert thisElt != null;
+      return (thisElt.equals(lhs) && param.equals(rhs))
+          || (thisElt.equals(rhs) && param.equals(lhs));
+    }
+
+    return false;
+  }
+
+  /**
+   * Pattern matches to prevent false positives of the forms:
+   *
+   * <pre>{@code
+   * (a == b) || a.equals(b)
+   * (a == b) || (a != null ? a.equals(b) : false)
+   * (a == b) || (a != null && a.equals(b))
+   * }</pre>
+   *
+   * Returns true iff the given node fits this pattern.
+   *
+   * @return true iff the node fits a pattern such as (a == b || a.equals(b))
+   */
+  private boolean suppressEarlyEquals(final BinaryTree node) {
+    // Only handle == binary trees
+    if (node.getKind() != Tree.Kind.EQUAL_TO) {
+      return false;
+    }
+
+    // should strip parens
+    final ExpressionTree left = TreeUtils.withoutParens(node.getLeftOperand());
+    final ExpressionTree right = TreeUtils.withoutParens(node.getRightOperand());
+
+    // looking for ((a == b || a.equals(b))
+    Heuristics.Matcher matcherEqOrEquals =
+        new Heuristics.Matcher() {
+
+          /** Returns true if e is either "e1 != null" or "e2 != null". */
+          private boolean isNeqNull(ExpressionTree e, ExpressionTree e1, ExpressionTree e2) {
+            e = TreeUtils.withoutParens(e);
+            if (e.getKind() != Tree.Kind.NOT_EQUAL_TO) {
+              return false;
+            }
+            ExpressionTree neqLeft = ((BinaryTree) e).getLeftOperand();
+            ExpressionTree neqRight = ((BinaryTree) e).getRightOperand();
+            return (((TreeUtils.sameTree(neqLeft, e1) || TreeUtils.sameTree(neqLeft, e2))
+                    && neqRight.getKind() == Tree.Kind.NULL_LITERAL)
+                // also check for "null != e1" and "null != e2"
+                || ((TreeUtils.sameTree(neqRight, e1) || TreeUtils.sameTree(neqRight, e2))
+                    && neqLeft.getKind() == Tree.Kind.NULL_LITERAL));
+          }
+
+          @Override
+          public Boolean visitBinary(BinaryTree tree, Void p) {
+            ExpressionTree leftTree = tree.getLeftOperand();
+            ExpressionTree rightTree = tree.getRightOperand();
+
+            if (tree.getKind() == Tree.Kind.CONDITIONAL_OR) {
+              if (TreeUtils.sameTree(leftTree, node)) {
+                // left is "a==b"
+                // check right, which should be a.equals(b) or b.equals(a) or similar
+                return visit(rightTree, p);
+              } else {
+                return false;
+              }
+            }
+
+            if (tree.getKind() == Tree.Kind.CONDITIONAL_AND) {
+              // looking for: (a != null && a.equals(b)))
+              if (isNeqNull(leftTree, left, right)) {
+                return visit(rightTree, p);
+              }
+              return false;
+            }
+
+            return false;
+          }
+
+          @Override
+          public Boolean visitConditionalExpression(ConditionalExpressionTree tree, Void p) {
+            // looking for: (a != null ? a.equals(b) : false)
+            ExpressionTree cond = tree.getCondition();
+            ExpressionTree trueExp = tree.getTrueExpression();
+            ExpressionTree falseExp = tree.getFalseExpression();
+            if (isNeqNull(cond, left, right)
+                && (falseExp.getKind() == Tree.Kind.BOOLEAN_LITERAL)
+                && ((LiteralTree) falseExp).getValue().equals(false)) {
+              return visit(trueExp, p);
+            }
+            return false;
+          }
+
+          @Override
+          public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) {
+            if (!isInvocationOfEquals(tree)) {
+              return false;
+            }
+
+            List<? extends ExpressionTree> args = tree.getArguments();
+            if (args.size() != 1) {
+              return false;
+            }
+            ExpressionTree arg = args.get(0);
+            // if (arg.getKind() != Tree.Kind.IDENTIFIER) {
+            //     return false;
+            // }
+            // Element argElt = TreeUtils.elementFromUse((IdentifierTree) arg);
+
+            ExpressionTree exp = tree.getMethodSelect();
+            if (exp.getKind() != Tree.Kind.MEMBER_SELECT) {
+              return false;
+            }
+            MemberSelectTree member = (MemberSelectTree) exp;
+            ExpressionTree receiver = member.getExpression();
+            // Element refElt = TreeUtils.elementFromUse(receiver);
+
+            // if (!((refElt.equals(lhs) && argElt.equals(rhs)) ||
+            //       ((refElt.equals(rhs) && argElt.equals(lhs))))) {
+            //     return false;
+            // }
+
+            if (TreeUtils.sameTree(receiver, left) && TreeUtils.sameTree(arg, right)) {
+              return true;
+            }
+            if (TreeUtils.sameTree(receiver, right) && TreeUtils.sameTree(arg, left)) {
+              return true;
+            }
+
+            return false;
+          }
+        };
+
+    boolean okay =
+        new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.CONDITIONAL_OR, matcherEqOrEquals))
+            .match(getCurrentPath());
+    return okay;
+  }
+
+  /**
+   * Pattern matches to prevent false positives of the form {@code (a == b || a.compareTo(b) == 0)}.
+   * Returns true iff the given node fits this pattern.
+   *
+   * @return true iff the node fits the pattern (a == b || a.compareTo(b) == 0)
+   */
+  private boolean suppressEarlyCompareTo(final BinaryTree node) {
+    // Only handle == binary trees
+    if (node.getKind() != Tree.Kind.EQUAL_TO) {
+      return false;
+    }
+
+    Tree left = TreeUtils.withoutParens(node.getLeftOperand());
+    Tree right = TreeUtils.withoutParens(node.getRightOperand());
+
+    // Only valid if we're comparing identifiers.
+    if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) {
+      return false;
+    }
+
+    final Element lhs = TreeUtils.elementFromUse((IdentifierTree) left);
+    final Element rhs = TreeUtils.elementFromUse((IdentifierTree) right);
+
+    // looking for ((a == b || a.compareTo(b) == 0)
+    Heuristics.Matcher matcherEqOrCompareTo =
+        new Heuristics.Matcher() {
+
+          @Override
+          public Boolean visitBinary(BinaryTree tree, Void p) {
+            if (tree.getKind() == Tree.Kind.EQUAL_TO) { // a.compareTo(b) == 0
+              ExpressionTree leftTree = tree.getLeftOperand(); // looking for a.compareTo(b) or
+              // b.compareTo(a)
+              ExpressionTree rightTree = tree.getRightOperand(); // looking for 0
+
+              if (rightTree.getKind() != Tree.Kind.INT_LITERAL) {
+                return false;
+              }
+              LiteralTree rightLiteral = (LiteralTree) rightTree;
+              if (!rightLiteral.getValue().equals(0)) {
+                return false;
+              }
+
+              return visit(leftTree, p);
+            } else {
+              // a == b || a.compareTo(b) == 0
+              @SuppressWarnings("interning:assignment" // AST node comparisons
+              )
+              @InternedDistinct ExpressionTree leftTree = tree.getLeftOperand(); // looking for a==b
+              ExpressionTree rightTree = tree.getRightOperand(); // looking for a.compareTo(b) == 0
+              // or b.compareTo(a) == 0
+              if (leftTree != node) {
+                return false;
+              }
+              if (rightTree.getKind() != Tree.Kind.EQUAL_TO) {
+                return false;
+              }
+              return visit(rightTree, p);
+            }
+          }
+
+          @Override
+          public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) {
+            if (!TreeUtils.isMethodInvocation(
+                tree, comparableCompareTo, checker.getProcessingEnvironment())) {
+              return false;
+            }
+
+            List<? extends ExpressionTree> args = tree.getArguments();
+            if (args.size() != 1) {
+              return false;
+            }
+            ExpressionTree arg = args.get(0);
+            if (arg.getKind() != Tree.Kind.IDENTIFIER) {
+              return false;
+            }
+            Element argElt = TreeUtils.elementFromUse(arg);
+
+            ExpressionTree exp = tree.getMethodSelect();
+            if (exp.getKind() != Tree.Kind.MEMBER_SELECT) {
+              return false;
+            }
+            MemberSelectTree member = (MemberSelectTree) exp;
+            if (member.getExpression().getKind() != Tree.Kind.IDENTIFIER) {
+              return false;
+            }
+
+            Element refElt = TreeUtils.elementFromUse(member.getExpression());
+
+            if (!((refElt.equals(lhs) && argElt.equals(rhs))
+                || (refElt.equals(rhs) && argElt.equals(lhs)))) {
+              return false;
+            }
+            return true;
+          }
+        };
+
+    boolean okay =
+        new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.CONDITIONAL_OR, matcherEqOrCompareTo))
+            .match(getCurrentPath());
+    return okay;
+  }
+
+  /**
+   * Given {@code a == b}, where a has type A and b has type B, don't issue a warning when either
+   * the declaration of A or that of B is annotated with @Interned because {@code a == b} will be
+   * true only if a's run-time type is B (or lower), in which case a is actually interned.
+   */
+  private boolean suppressEqualsIfClassIsAnnotated(
+      AnnotatedTypeMirror left, AnnotatedTypeMirror right) {
+    // It would be better to just test their greatest lower bound.
+    // That could permit some comparisons that this forbids.
+    return classIsAnnotated(left) || classIsAnnotated(right);
+  }
+
+  /** Returns true if the type's declaration has an @Interned annotation. */
+  private boolean classIsAnnotated(AnnotatedTypeMirror type) {
+
+    TypeMirror tm = type.getUnderlyingType();
+    if (tm == null) {
+      // Maybe a type variable or wildcard had no upper bound
+      return false;
+    }
+
+    tm = TypesUtils.findConcreteUpperBound(tm);
+    if (tm == null || tm.getKind() == TypeKind.ARRAY) {
+      // Bound of a wildcard might be null
+      return false;
+    }
+
+    if (tm.getKind() != TypeKind.DECLARED) {
+      checker.message(
+          Kind.WARNING, "InterningVisitor.classIsAnnotated: tm = %s (%s)", tm, tm.getClass());
+    }
+    Element classElt = ((DeclaredType) tm).asElement();
+    if (classElt == null) {
+      checker.message(
+          Kind.WARNING,
+          "InterningVisitor.classIsAnnotated: classElt = null for tm = %s (%s)",
+          tm,
+          tm.getClass());
+    }
+    if (classElt != null) {
+      Set<AnnotationMirror> bound = atypeFactory.getTypeDeclarationBounds(tm);
+      return atypeFactory.containsSameByClass(bound, Interned.class);
+    }
+    return false;
+  }
+
+  /**
+   * Determines the element corresponding to "this" inside a scope. Returns null within static
+   * methods.
+   *
+   * @param scope the scope to search for the element corresponding to "this" in
+   * @return the element corresponding to "this" in the given scope, or null if not found
+   */
+  private Element getThis(Scope scope) {
+    for (Element e : scope.getLocalElements()) {
+      if (e.getSimpleName().contentEquals("this")) {
+        return e;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Determines whether or not the given element overrides the named method in the named class.
+   *
+   * @param e an element for a method
+   * @param clazz the class
+   * @param method the name of a method
+   * @return true if the method given by {@code e} overrides the named method in the named class;
+   *     false otherwise
+   */
+  private boolean overrides(ExecutableElement e, Class<?> clazz, String method) {
+
+    // Get the element named by "clazz".
+    TypeElement clazzElt = elements.getTypeElement(clazz.getCanonicalName());
+    assert clazzElt != null;
+
+    // Check all of the methods in the class for name matches and overriding.
+    for (ExecutableElement elt : ElementFilter.methodsIn(clazzElt.getEnclosedElements())) {
+      if (elt.getSimpleName().contentEquals(method) && elements.overrides(e, elt, clazzElt)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Returns the type to check.
+   *
+   * @return the type to check
+   */
+  DeclaredType typeToCheck() {
+    @SuppressWarnings("signature:assignment") // user input
+    @CanonicalName String className = checker.getOption("checkclass");
+    if (className == null) {
+      return null;
+    }
+
+    TypeElement classElt = elements.getTypeElement(className);
+    if (classElt == null) {
+      return null;
+    }
+
+    return types.getDeclaredType(classElt);
+  }
+
+  @Override
+  protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) {
+    if (castType.getKind().isPrimitive()) {
+      return true;
+    }
+    return super.isTypeCastSafe(castType, exprType);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/interning/com-sun.astub b/checker/src/main/java/org/checkerframework/checker/interning/com-sun.astub
new file mode 100644
index 0000000..d23ee82
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/interning/com-sun.astub
@@ -0,0 +1,349 @@
+import org.checkerframework.checker.interning.qual.*;
+
+package com.sun.org.apache.xml.internal.utils;
+
+public class NamespaceSupport2 extends org.xml.sax.helpers.NamespaceSupport
+{
+  public @Interned String [] processName (String qName, String[] parts,
+                                          boolean isAttribute);
+}
+
+
+package com.sun.org.apache.xml.internal.security.utils;
+
+public abstract class ElementProxy {
+  public static @Interned String getDefaultPrefix(String namespace);
+  public static @Interned String getDefaultPrefixBindings(String namespace);
+}
+
+// TODO:
+// package com.sun.org.apache.xerces.*;
+// (Rather than having Xerces half-finished, leave it completely un-done.)
+
+package com.sun.org.apache.bcel.internal.util;
+
+public class ClassPath implements Serializable {
+  public static final @Interned String getClassPath();
+}
+
+package com.sun.jmx.mbeanserver;
+
+public class Repository {
+  public @Interned String getDefaultDomain();
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+/// public static final @Interned String (initialized to constant)
+///
+
+package com.sun.imageio.plugins.gif;
+public class GIFStreamMetadata {
+     public static final @Interned String[] versionStrings = { "87a", "89a" };
+}
+package com.sun.imageio.plugins.jpeg;
+public class JPEG {
+     public static final @Interned String vendor = "Sun Microsystems, Inc.";
+     public static final @Interned String version = "0.5";
+     public static final @Interned String [] names = {"JPEG", "jpeg", "JPG", "jpg"};
+     public static final @Interned String [] suffixes = {"jpg", "jpeg"};
+     public static final @Interned String [] MIMETypes = {"image/jpeg"};
+}
+package com.sun.java.swing.plaf.gtk;
+public class PangoFonts {
+     public static final @Interned String CHARS_DIGITS = "0123456789";
+}
+package com.sun.java.util.jar.pack;
+public class DriverResource {
+         public static final @Interned String VERSION ="VERSION";
+         public static final @Interned String BAD_ARGUMENT ="BAD_ARGUMENT";
+         public static final @Interned String BAD_OPTION ="BAD_OPTION";
+         public static final @Interned String BAD_REPACK_OUTPUT="BAD_REPACK_OUTPUT";
+         public static final @Interned String DETECTED_ZIP_COMMENT="DETECTED_ZIP_COMMENT";
+         public static final @Interned String SKIP_FOR_REPACKED ="SKIP_FOR_REPACKED";
+         public static final @Interned String WRITE_PACK_FILE ="WRITE_PACK_FILE";
+         public static final @Interned String WRITE_PACKGZ_FILE="WRITE_PACKGZ_FILE";
+         public static final @Interned String SKIP_FOR_MOVE_FAILED="SKIP_FOR_MOVE_FAILED";
+         public static final @Interned String PACK_HELP="PACK_HELP";
+         public static final @Interned String UNPACK_HELP ="UNPACK_HELP";
+         public static final @Interned String MORE_INFO = "MORE_INFO";
+         public static final @Interned String DUPLICATE_OPTION = "DUPLICATE_OPTION";
+         public static final @Interned String BAD_SPEC = "BAD_SPEC";
+}
+package com.sun.jmx.defaults;
+public class JmxProperties {
+     public static final @Interned String MLET_LIB_DIR = "jmx.mlet.library.dir";
+     public static final @Interned String JMX_SPEC_NAME = "jmx.specification.name";
+     public static final @Interned String JMX_SPEC_VERSION = "jmx.specification.version";
+     public static final @Interned String JMX_SPEC_VENDOR = "jmx.specification.vendor";
+     public static final @Interned String JMX_IMPL_NAME = "jmx.implementation.name";
+     public static final @Interned String JMX_IMPL_VENDOR = "jmx.implementation.vendor";
+     public static final @Interned String JMX_IMPL_VERSION = "jmx.implementation.version";
+}
+package com.sun.jmx.defaults;
+public class ServiceName {
+     public static final @Interned String MLET = "type=MLet";
+     public static final @Interned String DOMAIN = "DefaultDomain";
+     public static final @Interned String JMX_SPEC_NAME = "Java Management Extensions";
+     public static final @Interned String JMX_SPEC_VERSION = "1.4";
+     public static final @Interned String JMX_SPEC_VENDOR = "Sun Microsystems";
+     public static final @Interned String JMX_IMPL_NAME = "JMX";
+     public static final @Interned String JMX_IMPL_VENDOR = "Sun Microsystems";
+}
+package com.sun.jmx.remote.util;
+public class EnvHelp {
+     public static final @Interned String DEFAULT_ORB="java.naming.corba.orb";
+     public static final @Interned String JMX_SERVER_DAEMON = "jmx.remote.x.daemon";
+}
+package com.sun.jmx.snmp.defaults;
+public class SnmpProperties {
+     public static final @Interned String MLET_LIB_DIR = "jmx.mlet.library.dir";
+     public static final @Interned String ACL_FILE = "jdmk.acl.file";
+     public static final @Interned String SECURITY_FILE = "jdmk.security.file";
+     public static final @Interned String UACL_FILE = "jdmk.uacl.file";
+     public static final @Interned String MIB_CORE_FILE = "mibcore.file";
+      public static final @Interned String JMX_SPEC_NAME = "jmx.specification.name";
+      public static final @Interned String JMX_SPEC_VERSION = "jmx.specification.version";
+      public static final @Interned String JMX_SPEC_VENDOR = "jmx.specification.vendor";
+     public static final @Interned String JMX_IMPL_NAME = "jmx.implementation.name";
+     public static final @Interned String JMX_IMPL_VENDOR = "jmx.implementation.vendor";
+     public static final @Interned String JMX_IMPL_VERSION = "jmx.implementation.version";
+     public static final @Interned String SSL_CIPHER_SUITE = "jdmk.ssl.cipher.suite.";
+}
+package com.sun.jmx.snmp;
+public class ServiceName {
+     public static final @Interned String DELEGATE = "JMImplementation:type=MBeanServerDelegate" ;
+     public static final @Interned String MLET = "type=MLet";
+     public static final @Interned String DOMAIN = "DefaultDomain";
+     public static final @Interned String RMI_CONNECTOR_SERVER = "name=RmiConnectorServer" ;
+     public static final @Interned String SNMP_ADAPTOR_SERVER = "name=SnmpAdaptorServer" ;
+     public static final @Interned String HTTP_CONNECTOR_SERVER = "name=HttpConnectorServer" ;
+     public static final @Interned String HTTPS_CONNECTOR_SERVER = "name=HttpsConnectorServer" ;
+     public static final @Interned String HTML_ADAPTOR_SERVER = "name=HtmlAdaptorServer" ;
+     public static final @Interned String JMX_SPEC_NAME = "Java Management Extensions";
+     public static final @Interned String JMX_SPEC_VERSION = "1.2 Maintenance Release";
+     public static final @Interned String JMX_SPEC_VENDOR = "Sun Microsystems";
+     public static final @Interned String JMX_IMPL_VENDOR = "Sun Microsystems";
+     public static final @Interned String BUILD_NUMBER = "r01";
+     public static final @Interned String JMX_IMPL_VERSION = "5.1_" + BUILD_NUMBER;
+}
+package com.sun.jndi.ldap;
+public class EntryChangeResponseControl {
+     public static final @Interned String OID = "2.16.840.1.113730.3.4.7";
+}
+package com.sun.jndi.ldap;
+public class LdapCtx {
+     public static final @Interned String DEFAULT_HOST = "localhost";
+}
+package com.sun.jndi.ldap;
+public class ManageReferralControl {
+     public static final @Interned String OID = "2.16.840.1.113730.3.4.2";
+}
+package com.sun.jndi.ldap;
+public class PersistentSearchControl {
+     public static final @Interned String OID = "2.16.840.1.113730.3.4.3";
+}
+package com.sun.org.apache.xml.internal.security.algorithms.implementations;
+public class SignatureDSA {
+     public static final @Interned String _URI = Constants.SignatureSpecNS + "dsa-sha1";
+}
+package com.sun.org.apache.xml.internal.security.algorithms;
+public class MessageDigestAlgorithm {
+    public static final @Interned String ALGO_ID_DIGEST_NOT_RECOMMENDED_MD5 = Constants.MoreAlgorithmsSpecNS + "md5";
+    public static final @Interned String ALGO_ID_DIGEST_SHA1 = Constants.SignatureSpecNS + "sha1";
+    public static final @Interned String ALGO_ID_DIGEST_SHA256 = EncryptionConstants.EncryptionSpecNS + "sha256";
+    public static final @Interned String ALGO_ID_DIGEST_SHA384 = Constants.MoreAlgorithmsSpecNS + "sha384";
+    public static final @Interned String ALGO_ID_DIGEST_SHA512 = EncryptionConstants.EncryptionSpecNS + "sha512";
+    public static final @Interned String ALGO_ID_DIGEST_RIPEMD160 = EncryptionConstants.EncryptionSpecNS + "ripemd160";
+}
+package com.sun.org.apache.xml.internal.security.c14n;
+public class Canonicalizer {
+     public static final @Interned String ENCODING = "UTF8";
+}
+package com.sun.org.apache.xml.internal.security.keys.content.x509;
+public class XMLX509Certificate {
+    public static final @Interned String JCA_CERT_ID = "X.509";
+}
+package com.sun.org.apache.xml.internal.security.keys.content.x509;
+public class XMLX509SKI {
+     public static final @Interned String SKI_OID = "2.5.29.14";
+}
+package com.sun.org.apache.xml.internal.security.keys.content;
+public class RetrievalMethod {
+    public static final @Interned String TYPE_DSA     = Constants.SignatureSpecNS + "DSAKeyValue";
+    public static final @Interned String TYPE_RSA     = Constants.SignatureSpecNS + "RSAKeyValue";
+    public static final @Interned String TYPE_PGP     = Constants.SignatureSpecNS + "PGPData";
+    public static final @Interned String TYPE_SPKI    = Constants.SignatureSpecNS + "SPKIData";
+    public static final @Interned String TYPE_MGMT    = Constants.SignatureSpecNS + "MgmtData";
+    public static final @Interned String TYPE_X509    = Constants.SignatureSpecNS + "X509Data";
+    public static final @Interned String TYPE_RAWX509 = Constants.SignatureSpecNS + "rawX509Certificate";
+}
+package com.sun.org.apache.xml.internal.security.signature;
+public class XMLSignature {
+    public static final @Interned String ALGO_ID_MAC_HMAC_SHA1 = Constants.SignatureSpecNS + "hmac-sha1";
+    public static final @Interned String ALGO_ID_SIGNATURE_DSA = Constants.SignatureSpecNS + "dsa-sha1";
+    public static final @Interned String ALGO_ID_SIGNATURE_RSA = Constants.SignatureSpecNS + "rsa-sha1";
+    public static final @Interned String ALGO_ID_SIGNATURE_RSA_SHA1 = Constants.SignatureSpecNS + "rsa-sha1";
+    public static final @Interned String ALGO_ID_SIGNATURE_NOT_RECOMMENDED_RSA_MD5 = Constants.MoreAlgorithmsSpecNS + "rsa-md5";
+    public static final @Interned String ALGO_ID_SIGNATURE_RSA_RIPEMD160 = Constants.MoreAlgorithmsSpecNS + "rsa-ripemd160";
+    public static final @Interned String ALGO_ID_SIGNATURE_RSA_SHA256 = Constants.MoreAlgorithmsSpecNS + "rsa-sha256";
+    public static final @Interned String ALGO_ID_SIGNATURE_RSA_SHA384 = Constants.MoreAlgorithmsSpecNS + "rsa-sha384";
+    public static final @Interned String ALGO_ID_SIGNATURE_RSA_SHA512 = Constants.MoreAlgorithmsSpecNS + "rsa-sha512";
+    public static final @Interned String ALGO_ID_MAC_HMAC_NOT_RECOMMENDED_MD5 = Constants.MoreAlgorithmsSpecNS + "hmac-md5";
+    public static final @Interned String ALGO_ID_MAC_HMAC_RIPEMD160 = Constants.MoreAlgorithmsSpecNS + "hmac-ripemd160";
+    public static final @Interned String ALGO_ID_MAC_HMAC_SHA256 = Constants.MoreAlgorithmsSpecNS + "hmac-sha256";
+    public static final @Interned String ALGO_ID_MAC_HMAC_SHA384 = Constants.MoreAlgorithmsSpecNS + "hmac-sha384";
+    public static final @Interned String ALGO_ID_MAC_HMAC_SHA512 = Constants.MoreAlgorithmsSpecNS + "hmac-sha512";
+    public static final @Interned String ALGO_ID_SIGNATURE_ECDSA_SHA1 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1";
+}
+package com.sun.org.apache.xml.internal.security.signature;
+public class XMLSignatureInputDebugger {
+         // public static final @Interned String HTMLPrefix = "<!DOCTYPE HTML PUBLIC
+}
+package com.sun.org.apache.xml.internal.security.transforms.params;
+public class InclusiveNamespaces {
+    public static final @Interned String _ATT_EC_PREFIXLIST = "PrefixList";
+}
+package com.sun.org.apache.xml.internal.security.transforms.params;
+public class XPath2FilterContainer {
+    public static final @Interned String _TAG_XPATH2 = "XPath";
+}
+package com.sun.org.apache.xml.internal.security.transforms.params;
+public class XPath2FilterContainer04 {
+    public static final @Interned String _TAG_XPATH2 = "XPath";
+}
+package com.sun.org.apache.xml.internal.security.transforms.params;
+public class XPathFilterCHGPContainer {
+    public static final @Interned String _TAG_XPATHCHGP = "XPathAlternative";
+    public static final @Interned String _ATT_INCLUDESLASH = "IncludeSlashPolicy";
+}
+package com.sun.org.apache.xml.internal.security.utils;
+public class Constants {
+    public static final @Interned String configurationFile = "data/websig.conf";
+    public static final @Interned String configurationFileNew = ".xmlsecurityconfig";
+    public static final @Interned String SIGNATURESPECIFICATION_URL = "http://www.w3.org/TR/2001/CR-xmldsig-core-20010419/";
+    public static final @Interned String SignatureSpecNS   = "http://www.w3.org/2000/09/xmldsig#";
+    public static final @Interned String MoreAlgorithmsSpecNS   = "http://www.w3.org/2001/04/xmldsig-more#";
+    public static final @Interned String XML_LANG_SPACE_SpecNS = "http://www.w3.org/XML/1998/namespace";
+    public static final @Interned String NamespaceSpecNS = "http://www.w3.org/2000/xmlns/";
+    public static final @Interned String _ATT_ALGORITHM              = "Algorithm";
+    public static final @Interned String _ATT_URI                    = "URI";
+    public static final @Interned String _ATT_TYPE                   = "Type";
+    public static final @Interned String _ATT_ID                     = "Id";
+    public static final @Interned String _ATT_MIMETYPE               = "MimeType";
+    public static final @Interned String _ATT_ENCODING               = "Encoding";
+    public static final @Interned String _ATT_TARGET                 = "Target";
+    public static final @Interned String _TAG_CANONICALIZATIONMETHOD = "CanonicalizationMethod";
+    public static final @Interned String _TAG_DIGESTMETHOD           = "DigestMethod";
+    public static final @Interned String _TAG_DIGESTVALUE            = "DigestValue";
+    public static final @Interned String _TAG_MANIFEST               = "Manifest";
+    public static final @Interned String _TAG_METHODS                = "Methods";
+    public static final @Interned String _TAG_OBJECT                 = "Object";
+    public static final @Interned String _TAG_REFERENCE              = "Reference";
+    public static final @Interned String _TAG_SIGNATURE              = "Signature";
+    public static final @Interned String _TAG_SIGNATUREMETHOD        = "SignatureMethod";
+    public static final @Interned String _TAG_HMACOUTPUTLENGTH       = "HMACOutputLength";
+    public static final @Interned String _TAG_SIGNATUREPROPERTIES    = "SignatureProperties";
+    public static final @Interned String _TAG_SIGNATUREPROPERTY      = "SignatureProperty";
+    public static final @Interned String _TAG_SIGNATUREVALUE         = "SignatureValue";
+    public static final @Interned String _TAG_SIGNEDINFO             = "SignedInfo";
+    public static final @Interned String _TAG_TRANSFORM              = "Transform";
+    public static final @Interned String _TAG_TRANSFORMS             = "Transforms";
+    public static final @Interned String _TAG_XPATH                  = "XPath";
+    public static final @Interned String _TAG_KEYINFO                = "KeyInfo";
+    public static final @Interned String _TAG_KEYNAME                = "KeyName";
+    public static final @Interned String _TAG_KEYVALUE               = "KeyValue";
+    public static final @Interned String _TAG_RETRIEVALMETHOD        = "RetrievalMethod";
+    public static final @Interned String _TAG_X509DATA               = "X509Data";
+    public static final @Interned String _TAG_PGPDATA                = "PGPData";
+    public static final @Interned String _TAG_SPKIDATA               = "SPKIData";
+    public static final @Interned String _TAG_MGMTDATA               = "MgmtData";
+    public static final @Interned String _TAG_RSAKEYVALUE            = "RSAKeyValue";
+    public static final @Interned String _TAG_EXPONENT               = "Exponent";
+    public static final @Interned String _TAG_MODULUS                = "Modulus";
+    public static final @Interned String _TAG_DSAKEYVALUE            = "DSAKeyValue";
+    public static final @Interned String _TAG_P                      = "P";
+    public static final @Interned String _TAG_Q                      = "Q";
+    public static final @Interned String _TAG_G                      = "G";
+    public static final @Interned String _TAG_Y                      = "Y";
+    public static final @Interned String _TAG_J                      = "J";
+    public static final @Interned String _TAG_SEED                   = "Seed";
+    public static final @Interned String _TAG_PGENCOUNTER            = "PgenCounter";
+    public static final @Interned String _TAG_RAWX509CERTIFICATE     = "rawX509Certificate";
+    public static final @Interned String _TAG_X509ISSUERSERIAL       = "X509IssuerSerial";
+    public static final @Interned String _TAG_X509SKI                = "X509SKI";
+    public static final @Interned String _TAG_X509SUBJECTNAME        = "X509SubjectName";
+    public static final @Interned String _TAG_X509CERTIFICATE        = "X509Certificate";
+    public static final @Interned String _TAG_X509CRL                = "X509CRL";
+    public static final @Interned String _TAG_X509ISSUERNAME         = "X509IssuerName";
+    public static final @Interned String _TAG_X509SERIALNUMBER       = "X509SerialNumber";
+    public static final @Interned String _TAG_PGPKEYID               = "PGPKeyID";
+    public static final @Interned String _TAG_PGPKEYPACKET           = "PGPKeyPacket";
+    public static final @Interned String _TAG_SPKISEXP               = "SPKISexp";
+    public static final @Interned String ALGO_ID_DIGEST_SHA1        = SignatureSpecNS + "sha1";
+    public static final @Interned String ALGO_ID_SIGNATURE_ECDSA_CERTICOM = "http://www.certicom.com/2000/11/xmlecdsig#ecdsa-sha1";
+}
+package com.sun.org.apache.xml.internal.security.utils;
+public class EncryptionConstants {
+    public static final @Interned String _ATT_ENCODING               = "Encoding";
+    public static final @Interned String _ATT_RECIPIENT              = "Recipient";
+    public static final @Interned String _ATT_MIMETYPE               = "MimeType";
+    public static final @Interned String _TAG_CARRIEDKEYNAME         = "CarriedKeyName";
+    public static final @Interned String _TAG_CIPHERDATA             = "CipherData";
+    public static final @Interned String _TAG_CIPHERREFERENCE        = "CipherReference";
+    public static final @Interned String _TAG_CIPHERVALUE            = "CipherValue";
+    public static final @Interned String _TAG_DATAREFERENCE          = "DataReference";
+    public static final @Interned String _TAG_ENCRYPTEDDATA          = "EncryptedData";
+    public static final @Interned String _TAG_ENCRYPTEDKEY           = "EncryptedKey";
+    public static final @Interned String _TAG_ENCRYPTIONMETHOD       = "EncryptionMethod";
+    public static final @Interned String _TAG_ENCRYPTIONPROPERTIES   = "EncryptionProperties";
+    public static final @Interned String _TAG_ENCRYPTIONPROPERTY     = "EncryptionProperty";
+    public static final @Interned String _TAG_KEYREFERENCE           = "KeyReference";
+    public static final @Interned String _TAG_KEYSIZE                = "KeySize";
+    public static final @Interned String _TAG_OAEPPARAMS             = "OAEPparams";
+    public static final @Interned String _TAG_REFERENCELIST          = "ReferenceList";
+    public static final @Interned String _TAG_TRANSFORMS             = "Transforms";
+    public static final @Interned String _TAG_AGREEMENTMETHOD        = "AgreementMethod";
+    public static final @Interned String _TAG_KA_NONCE               = "KA-Nonce";
+    public static final @Interned String _TAG_ORIGINATORKEYINFO      = "OriginatorKeyInfo";
+    public static final @Interned String _TAG_RECIPIENTKEYINFO       = "RecipientKeyInfo";
+    public static final @Interned String ENCRYPTIONSPECIFICATION_URL = "http://www.w3.org/TR/2001/WD-xmlenc-core-20010626/";
+    public static final @Interned String EncryptionSpecNS = "http://www.w3.org/2001/04/xmlenc#";
+    public static final @Interned String TYPE_CONTENT                = EncryptionSpecNS + "Content";
+    public static final @Interned String TYPE_ELEMENT                = EncryptionSpecNS + "Element";
+    public static final @Interned String TYPE_MEDIATYPE              = "http://www.isi.edu/in-notes/iana/assignments/media-types/"; // + "*/*";
+    public static final @Interned String ALGO_ID_BLOCKCIPHER_TRIPLEDES = EncryptionConstants.EncryptionSpecNS + "tripledes-cbc";
+    public static final @Interned String ALGO_ID_BLOCKCIPHER_AES128 = EncryptionConstants.EncryptionSpecNS + "aes128-cbc";
+    public static final @Interned String ALGO_ID_BLOCKCIPHER_AES256 = EncryptionConstants.EncryptionSpecNS + "aes256-cbc";
+    public static final @Interned String ALGO_ID_BLOCKCIPHER_AES192 = EncryptionConstants.EncryptionSpecNS + "aes192-cbc";
+    public static final @Interned String ALGO_ID_KEYTRANSPORT_RSA15 = EncryptionConstants.EncryptionSpecNS + "rsa-1_5";
+    public static final @Interned String ALGO_ID_KEYTRANSPORT_RSAOAEP = EncryptionConstants.EncryptionSpecNS + "rsa-oaep-mgf1p";
+    public static final @Interned String ALGO_ID_KEYAGREEMENT_DH = EncryptionConstants.EncryptionSpecNS + "dh";
+    public static final @Interned String ALGO_ID_KEYWRAP_TRIPLEDES = EncryptionConstants.EncryptionSpecNS + "kw-tripledes";
+    public static final @Interned String ALGO_ID_KEYWRAP_AES128 = EncryptionConstants.EncryptionSpecNS + "kw-aes128";
+    public static final @Interned String ALGO_ID_KEYWRAP_AES256 = EncryptionConstants.EncryptionSpecNS + "kw-aes256";
+    public static final @Interned String ALGO_ID_KEYWRAP_AES192 = EncryptionConstants.EncryptionSpecNS + "kw-aes192";
+    public static final @Interned String ALGO_ID_DIGEST_SHA256 = EncryptionConstants.EncryptionSpecNS + "sha256";
+    public static final @Interned String ALGO_ID_DIGEST_SHA512 = EncryptionConstants.EncryptionSpecNS + "sha512";
+    public static final @Interned String ALGO_ID_DIGEST_RIPEMD160 = EncryptionConstants.EncryptionSpecNS + "ripemd160";
+    public static final @Interned String ALGO_ID_AUTHENTICATION_XMLSIGNATURE = "http://www.w3.org/TR/2001/CR-xmldsig-core-20010419/";
+    public static final @Interned String ALGO_ID_C14N_WITHCOMMENTS = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
+    public static final @Interned String ALGO_ID_C14N_OMITCOMMENTS = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
+    public static final @Interned String ALGO_ID_ENCODING_BASE64 = "http://www.w3.org/2000/09/xmldsig#base64";
+}
+package com.sun.org.apache.xml.internal.security;
+public class Init {
+    public static final @Interned String CONF_NS="http://www.xmlsecurity.org/NS/#configuration";
+}
+package com.sun.tools.example.debug.gui;
+public class GUI {
+     public static final @Interned String progname = "javadt";
+     public static final @Interned String version = "1.0Beta";  //### FIX ME. :     public static final @Interned String windowBanner = "Java(tm) platform Debug Tool";
+}
+package com.sun.tools.extcheck;
+public class Main {
+     public static final @Interned String INSUFFICIENT = "Insufficient number of arguments";
+     public static final @Interned String MISSING = "Missing <jar file> argument";
+     public static final @Interned String DOES_NOT_EXIST = "Jarfile does not exist: ";
+     public static final @Interned String EXTRA = "Extra command line argument: ";
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/interning/javax-lang-model-element-name.astub b/checker/src/main/java/org/checkerframework/checker/interning/javax-lang-model-element-name.astub
new file mode 100644
index 0000000..6829231
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/interning/javax-lang-model-element-name.astub
@@ -0,0 +1,13 @@
+// Name is interned within one instance of the Java compiler, but may differ between different
+// instances.  Use this stub file if your program interacts with only one instance of the compiler
+// and doesn't make its own Name objects.
+
+package javax.lang.model.element;
+
+import org.checkerframework.checker.interning.qual.Interned;
+
+public @Interned interface Name extends java.lang.CharSequence {}
+
+package com.sun.tools.javac.util;
+
+public @Interned class Name implements javax.lang.model.element.Name {}
diff --git a/checker/src/main/java/org/checkerframework/checker/interning/jdk8.astub b/checker/src/main/java/org/checkerframework/checker/interning/jdk8.astub
new file mode 100644
index 0000000..b401124
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/interning/jdk8.astub
@@ -0,0 +1,308 @@
+// This file is for classes that appear in JDK 8 but not in JDK 11.
+
+package java.util;
+
+import org.checkerframework.checker.interning.qual.UsesObjectEquals;
+import org.checkerframework.checker.interning.qual.Interned;
+
+@UsesObjectEquals class XMLUtils {}
+
+/*
+TODO: Attributes does not declare a toString method.
+This declaration then pollutes java.lang.Object.toString, making
+any override illegal.
+
+package java.util.jar;
+
+public class Attributes implements Map<Object,Object>, Cloneable {
+  public @Interned String toString();
+}
+*/
+
+package javax.management;
+
+public class MBeanServerPermission extends BasicPermission {
+  static @Interned String getCanonicalName(int mask);
+}
+
+package javax.swing;
+
+public class DefaultListCellRenderer extends JLabel
+    implements ListCellRenderer<Object>, Serializable
+{
+    protected void firePropertyChange(@Interned String propertyName, Object oldValue, Object newValue);
+}
+
+package javax.swing.tree;
+
+public class DefaultTreeCellRenderer extends JLabel implements TreeCellRenderer
+{
+  protected void firePropertyChange(@Interned String propertyName, Object oldValue, Object newValue);
+}
+
+package javax.swing.table;
+
+public class DefaultTableCellRenderer extends JLabel
+    implements TableCellRenderer, Serializable
+{
+  protected void firePropertyChange(@Interned String propertyName, Object oldValue, Object newValue);
+}
+
+package javax.swing.plaf.basic;
+public class BasicHTML {
+     public static final @Interned String propertyKey = "html";
+     public static final @Interned String documentBaseKey = "html.base";
+}
+package javax.swing.plaf.basic;
+class BasicRootPaneUI extends RootPaneUI implements PropertyChangeListener {
+    public class Actions {
+             public static final @Interned String PRESS = "press";
+             public static final @Interned String RELEASE = "release";
+             public static final @Interned String POST_POPUP = "postPopup";
+    }
+}
+package javax.swing.plaf.basic;
+class BasicSliderUI extends SliderUI {
+    public class Actions {
+             public static final @Interned String MIN_SCROLL_INCREMENT = "minScroll";
+             public static final @Interned String MAX_SCROLL_INCREMENT = "maxScroll";
+    }
+}
+package javax.swing.plaf.metal;
+public class MetalScrollBarUI {
+     public static final @Interned String FREE_STANDING_PROP = "JScrollBar.isFreeStanding";
+}
+package javax.swing.plaf.nimbus;
+public class NimbusStyle {
+     public static final @Interned String LARGE_KEY = "large";
+     public static final @Interned String SMALL_KEY = "small";
+     public static final @Interned String MINI_KEY = "mini";
+}
+package javax.swing.text.html;
+public class FormView {
+     public static final @Interned String SUBMIT = new String("Submit Query");
+     public static final @Interned String RESET = new String("Reset");
+}
+package javax.swing.text.html;
+public class HTML {
+     public static final @Interned String NULL_ATTRIBUTE_VALUE = "#DEFAULT";
+}
+package javax.swing.text.html;
+public class HTMLDocument {
+     public static final @Interned String AdditionalComments = "AdditionalComments";
+}
+package javax.swing.text.html;
+public class HTMLEditorKit {
+     public static final @Interned String DEFAULT_CSS = "default.css";
+     public static final @Interned String  BOLD_ACTION = "html-bold-action";
+     public static final @Interned String  ITALIC_ACTION = "html-italic-action";
+     public static final @Interned String  PARA_INDENT_LEFT = "html-para-indent-left";
+     public static final @Interned String  PARA_INDENT_RIGHT = "html-para-indent-right";
+     public static final @Interned String  FONT_CHANGE_BIGGER = "html-font-bigger";
+     public static final @Interned String  FONT_CHANGE_SMALLER = "html-font-smaller";
+     public static final @Interned String  COLOR_ACTION = "html-color-action";
+     public static final @Interned String  LOGICAL_STYLE_ACTION = "html-logical-style-action";
+     public static final @Interned String  IMG_ALIGN_TOP = "html-image-align-top";
+     public static final @Interned String  IMG_ALIGN_MIDDLE = "html-image-align-middle";
+     public static final @Interned String  IMG_ALIGN_BOTTOM = "html-image-align-bottom";
+     public static final @Interned String  IMG_BORDER = "html-image-border";
+}
+package javax.swing.text;
+public class AbstractDocument {
+     public static final @Interned String ParagraphElementName = "paragraph";
+     public static final @Interned String ContentElementName = "content";
+     public static final @Interned String SectionElementName = "section";
+     public static final @Interned String BidiElementName = "bidi level";
+     public static final @Interned String ElementNameAttribute = "$ename";
+}
+package javax.swing.text;
+public class DefaultEditorKit {
+     public static final @Interned String EndOfLineStringProperty = "__EndOfLine__";
+     public static final @Interned String insertContentAction = "insert-content";
+     public static final @Interned String insertBreakAction = "insert-break";
+     public static final @Interned String insertTabAction = "insert-tab";
+     public static final @Interned String deletePrevCharAction = "delete-previous";
+     public static final @Interned String deleteNextCharAction = "delete-next";
+     public static final @Interned String deleteNextWordAction = "delete-next-word";
+     public static final @Interned String deletePrevWordAction = "delete-previous-word";
+     public static final @Interned String readOnlyAction = "set-read-only";
+     public static final @Interned String writableAction = "set-writable";
+     public static final @Interned String cutAction = "cut-to-clipboard";
+     public static final @Interned String copyAction = "copy-to-clipboard";
+     public static final @Interned String pasteAction = "paste-from-clipboard";
+     public static final @Interned String beepAction = "beep";
+     public static final @Interned String pageUpAction = "page-up";
+     public static final @Interned String pageDownAction = "page-down";
+     public static final @Interned String forwardAction = "caret-forward";
+     public static final @Interned String backwardAction = "caret-backward";
+     public static final @Interned String selectionForwardAction = "selection-forward";
+     public static final @Interned String selectionBackwardAction = "selection-backward";
+     public static final @Interned String upAction = "caret-up";
+     public static final @Interned String downAction = "caret-down";
+     public static final @Interned String selectionUpAction = "selection-up";
+     public static final @Interned String selectionDownAction = "selection-down";
+     public static final @Interned String beginWordAction = "caret-begin-word";
+     public static final @Interned String endWordAction = "caret-end-word";
+     public static final @Interned String selectionBeginWordAction = "selection-begin-word";
+     public static final @Interned String selectionEndWordAction = "selection-end-word";
+     public static final @Interned String previousWordAction = "caret-previous-word";
+     public static final @Interned String nextWordAction = "caret-next-word";
+     public static final @Interned String selectionPreviousWordAction = "selection-previous-word";
+     public static final @Interned String selectionNextWordAction = "selection-next-word";
+     public static final @Interned String beginLineAction = "caret-begin-line";
+     public static final @Interned String endLineAction = "caret-end-line";
+     public static final @Interned String selectionBeginLineAction = "selection-begin-line";
+     public static final @Interned String selectionEndLineAction = "selection-end-line";
+     public static final @Interned String beginParagraphAction = "caret-begin-paragraph";
+     public static final @Interned String endParagraphAction = "caret-end-paragraph";
+     public static final @Interned String selectionBeginParagraphAction = "selection-begin-paragraph";
+     public static final @Interned String selectionEndParagraphAction = "selection-end-paragraph";
+     public static final @Interned String beginAction = "caret-begin";
+     public static final @Interned String endAction = "caret-end";
+     public static final @Interned String selectionBeginAction = "selection-begin";
+     public static final @Interned String selectionEndAction = "selection-end";
+     public static final @Interned String selectWordAction = "select-word";
+     public static final @Interned String selectLineAction = "select-line";
+     public static final @Interned String selectParagraphAction = "select-paragraph";
+     public static final @Interned String selectAllAction = "select-all";
+     public static final @Interned String defaultKeyTypedAction = "default-typed";
+}
+package javax.swing.text;
+public class Document {
+     public static final @Interned String StreamDescriptionProperty = "stream";
+     public static final @Interned String TitleProperty = "title";
+}
+package javax.swing.text;
+public class JTextComponent {
+     public static final @Interned String FOCUS_ACCELERATOR_KEY = "focusAcceleratorKey";
+     public static final @Interned String DEFAULT_KEYMAP = "default";
+}
+package javax.swing.text;
+public class PlainDocument {
+     public static final @Interned String tabSizeAttribute = "tabSize";
+     public static final @Interned String lineLimitAttribute = "lineLimit";
+}
+package javax.swing.text;
+public class StyleConstants {
+     public static final @Interned String ComponentElementName = "component";
+     public static final @Interned String IconElementName = "icon";
+}
+package javax.swing.text;
+public class StyleContext {
+     public static final @Interned String DEFAULT_STYLE = "default";
+}
+package javax.swing.tree;
+public class DefaultTreeSelectionModel {
+     public static final @Interned String          SELECTION_MODE_PROPERTY = "selectionMode";
+}
+package javax.swing.undo;
+public class StateEditable {
+     public static final @Interned String RCSID = "$Id: StateEditable.java,v 1.2 1997/09/08 19:39:08 marklin Exp $";
+}
+package javax.swing;
+public class AbstractButton {
+     public static final @Interned String MODEL_CHANGED_PROPERTY = "model";
+     public static final @Interned String TEXT_CHANGED_PROPERTY = "text";
+     public static final @Interned String MNEMONIC_CHANGED_PROPERTY = "mnemonic";
+     public static final @Interned String MARGIN_CHANGED_PROPERTY = "margin";
+     public static final @Interned String VERTICAL_ALIGNMENT_CHANGED_PROPERTY = "verticalAlignment";
+     public static final @Interned String HORIZONTAL_ALIGNMENT_CHANGED_PROPERTY = "horizontalAlignment";
+     public static final @Interned String VERTICAL_TEXT_POSITION_CHANGED_PROPERTY = "verticalTextPosition";
+     public static final @Interned String HORIZONTAL_TEXT_POSITION_CHANGED_PROPERTY = "horizontalTextPosition";
+     public static final @Interned String BORDER_PAINTED_CHANGED_PROPERTY = "borderPainted";
+     public static final @Interned String FOCUS_PAINTED_CHANGED_PROPERTY = "focusPainted";
+     public static final @Interned String ROLLOVER_ENABLED_CHANGED_PROPERTY = "rolloverEnabled";
+     public static final @Interned String CONTENT_AREA_FILLED_CHANGED_PROPERTY = "contentAreaFilled";
+     public static final @Interned String ICON_CHANGED_PROPERTY = "icon";
+     public static final @Interned String PRESSED_ICON_CHANGED_PROPERTY = "pressedIcon";
+     public static final @Interned String SELECTED_ICON_CHANGED_PROPERTY = "selectedIcon";
+     public static final @Interned String ROLLOVER_ICON_CHANGED_PROPERTY = "rolloverIcon";
+     public static final @Interned String ROLLOVER_SELECTED_ICON_CHANGED_PROPERTY = "rolloverSelectedIcon";
+     public static final @Interned String DISABLED_ICON_CHANGED_PROPERTY = "disabledIcon";
+     public static final @Interned String DISABLED_SELECTED_ICON_CHANGED_PROPERTY = "disabledSelectedIcon";
+}
+package javax.swing;
+public class Action {
+     public static final @Interned String DEFAULT = "Default";
+     public static final @Interned String NAME = "Name";
+     public static final @Interned String SHORT_DESCRIPTION = "ShortDescription";
+     public static final @Interned String LONG_DESCRIPTION = "LongDescription";
+     public static final @Interned String SMALL_ICON = "SmallIcon";
+     public static final @Interned String ACTION_COMMAND_KEY = "ActionCommandKey";
+     public static final @Interned String ACCELERATOR_KEY="AcceleratorKey";
+     public static final @Interned String MNEMONIC_KEY="MnemonicKey";
+     public static final @Interned String SELECTED_KEY = "SwingSelectedKey";
+     public static final @Interned String LARGE_ICON_KEY = "SwingLargeIconKey";
+}
+package javax.swing;
+public class JCheckBox {
+     public static final @Interned String BORDER_PAINTED_FLAT_CHANGED_PROPERTY = "borderPaintedFlat";
+}
+package javax.swing;
+public class JColorChooser {
+     public static final @Interned String      SELECTION_MODEL_PROPERTY = "selectionModel";
+     public static final @Interned String      PREVIEW_PANEL_PROPERTY = "previewPanel";
+     public static final @Interned String      CHOOSER_PANELS_PROPERTY = "chooserPanels";
+}
+package javax.swing;
+public class JComponent {
+     public static final @Interned String TOOL_TIP_TEXT_KEY = "ToolTipText";
+}
+package javax.swing;
+public class JEditorPane {
+     public static final @Interned String W3C_LENGTH_UNITS = "JEditorPane.w3cLengthUnits";
+     public static final @Interned String HONOR_DISPLAY_PROPERTIES = "JEditorPane.honorDisplayProperties";
+}
+package javax.swing;
+public class JFileChooser {
+     public static final @Interned String CANCEL_SELECTION = "CancelSelection";
+     public static final @Interned String APPROVE_SELECTION = "ApproveSelection";
+     public static final @Interned String APPROVE_BUTTON_TEXT_CHANGED_PROPERTY = "ApproveButtonTextChangedProperty";
+     public static final @Interned String APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY = "ApproveButtonToolTipTextChangedProperty";
+     public static final @Interned String APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY = "ApproveButtonMnemonicChangedProperty";
+     public static final @Interned String CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY = "ControlButtonsAreShownChangedProperty";
+     public static final @Interned String DIRECTORY_CHANGED_PROPERTY = "directoryChanged";
+     public static final @Interned String SELECTED_FILE_CHANGED_PROPERTY = "SelectedFileChangedProperty";
+     public static final @Interned String SELECTED_FILES_CHANGED_PROPERTY = "SelectedFilesChangedProperty";
+     public static final @Interned String MULTI_SELECTION_ENABLED_CHANGED_PROPERTY = "MultiSelectionEnabledChangedProperty";
+     public static final @Interned String FILE_SYSTEM_VIEW_CHANGED_PROPERTY = "FileSystemViewChanged";
+     public static final @Interned String FILE_VIEW_CHANGED_PROPERTY = "fileViewChanged";
+     public static final @Interned String FILE_HIDING_CHANGED_PROPERTY = "FileHidingChanged";
+     public static final @Interned String FILE_FILTER_CHANGED_PROPERTY = "fileFilterChanged";
+     public static final @Interned String FILE_SELECTION_MODE_CHANGED_PROPERTY = "fileSelectionChanged";
+     public static final @Interned String ACCESSORY_CHANGED_PROPERTY = "AccessoryChangedProperty";
+     public static final @Interned String ACCEPT_ALL_FILE_FILTER_USED_CHANGED_PROPERTY = "acceptAllFileFilterUsedChanged";
+     public static final @Interned String DIALOG_TITLE_CHANGED_PROPERTY = "DialogTitleChangedProperty";
+     public static final @Interned String DIALOG_TYPE_CHANGED_PROPERTY = "DialogTypeChangedProperty";
+     public static final @Interned String CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY = "ChoosableFileFilterChangedProperty";
+}
+package javax.swing;
+public class JOptionPane {
+     public static final @Interned String      ICON_PROPERTY = "icon";
+     public static final @Interned String      MESSAGE_PROPERTY = "message";
+     public static final @Interned String      VALUE_PROPERTY = "value";
+     public static final @Interned String      OPTIONS_PROPERTY = "options";
+     public static final @Interned String      INITIAL_VALUE_PROPERTY = "initialValue";
+     public static final @Interned String      MESSAGE_TYPE_PROPERTY = "messageType";
+     public static final @Interned String      OPTION_TYPE_PROPERTY = "optionType";
+     public static final @Interned String      SELECTION_VALUES_PROPERTY = "selectionValues";
+     public static final @Interned String      INITIAL_SELECTION_VALUE_PROPERTY = "initialSelectionValue";
+     public static final @Interned String      INPUT_VALUE_PROPERTY = "inputValue";
+     public static final @Interned String      WANTS_INPUT_PROPERTY = "wantsInput";
+}
+package javax.swing;
+public class JTextField {
+     public static final @Interned String notifyAction = "notify-field-accept";
+}
+package javax.swing;
+public class SpringLayout {
+     public static final @Interned String NORTH  = "North";
+     public static final @Interned String SOUTH  = "South";
+     public static final @Interned String EAST   = "East";
+     public static final @Interned String WEST   = "West";
+     public static final @Interned String HORIZONTAL_CENTER   = "HorizontalCenter";
+     public static final @Interned String VERTICAL_CENTER   = "VerticalCenter";
+     public static final @Interned String BASELINE   = "Baseline";
+     public static final @Interned String WIDTH = "Width";
+     public static final @Interned String HEIGHT = "Height";
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/interning/messages.properties b/checker/src/main/java/org/checkerframework/checker/interning/messages.properties
new file mode 100644
index 0000000..435cb65
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/interning/messages.properties
@@ -0,0 +1,8 @@
+### Error/warning messages for the Interning Checker
+not.interned=attempting to use a non-@Interned comparison operand%nfound: %s
+unnecessary.equals=use of .equals can be safely replaced by ==/!=
+overrides.equals=annotated with @UsesObjectEquals but overrides .equals(Object)
+superclass.notannotated=superclass must be annotated with @UsesObjectEquals
+superclass.annotated=subclasses must also be annotated with @UsesObjectEquals
+interned.object.creation=Cannot statically verify that a new object of an @Interned class is @Interned
+invalid.method.annotation=%s applies to a method with %s formal parameters; %s has %d
diff --git a/checker/src/main/java/org/checkerframework/checker/interning/org-jcp.astub b/checker/src/main/java/org/checkerframework/checker/interning/org-jcp.astub
new file mode 100644
index 0000000..28b6502
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/interning/org-jcp.astub
@@ -0,0 +1,6 @@
+import org.checkerframework.checker.interning.qual.*;
+
+package org.jcp.xml.dsig.internal.dom;
+public class DOMCanonicalXMLC14N11Method {
+     public static final @Interned String C14N_11 = "http://www.w3.org/2006/12/xml-c14n11";
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/interning/org-xml.astub b/checker/src/main/java/org/checkerframework/checker/interning/org-xml.astub
new file mode 100644
index 0000000..5f43a72
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/interning/org-xml.astub
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.interning.qual.*;
+
+package org.xml.sax.helpers;
+
+public class NamespaceSupport
+{
+  public @Interned String [] processName (String qName, String[] parts,
+                                          boolean isAttribute);
+  // These are not necessary, it seems.
+  // final class Context {
+  //     @Interned String [] processName (String qName, boolean isAttribute);
+  // }
+  // final class AttributeListAdapter implements Attributes
+  // {
+  //     public @Interned String getQName (int i);
+  //     public @Interned String getType (int i);
+  //     public @Interned String getType (String uri, String localName);
+  //     public @Interned String getType (String qName);
+  // }
+
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/interning/package-info.java b/checker/src/main/java/org/checkerframework/checker/interning/package-info.java
new file mode 100644
index 0000000..f0bdddb
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/interning/package-info.java
@@ -0,0 +1,9 @@
+/**
+ * Provides a type-checker plug-in for the {@link
+ * org.checkerframework.checker.interning.qual.Interned} qualifier that finds (and verifies the
+ * absence of) equality-testing and interning errors.
+ *
+ * @see org.checkerframework.checker.interning.InterningChecker
+ * @checker_framework.manual #interning-checker Interning Checker
+ */
+package org.checkerframework.checker.interning;
diff --git a/checker/src/main/java/org/checkerframework/checker/interning/sun.astub b/checker/src/main/java/org/checkerframework/checker/interning/sun.astub
new file mode 100644
index 0000000..eb944f8
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/interning/sun.astub
@@ -0,0 +1,436 @@
+import org.checkerframework.checker.interning.qual.*;
+
+package sun.awt;
+public class AppContext {
+     public static final @Interned String DISPOSED_PROPERTY_NAME = "disposed";
+     public static final @Interned String GUI_DISPOSED = "guidisposed";
+}
+package sun.awt;
+public class SunToolkit {
+     public static final @Interned String DESKTOPFONTHINTS = "awt.font.desktophints";
+}
+package sun.font;
+public class SunFontManager {
+     public static final @Interned String lucidaFontName = "Lucida Sans Regular";
+}
+package sun.java2d.loops;
+public class Blit {
+     public static final @Interned String methodSignature = "Blit(...)".toString();
+}
+package sun.java2d.loops;
+public class BlitBg {
+     public static final @Interned String methodSignature = "BlitBg(...)".toString();
+}
+package sun.java2d.loops;
+public class CompositeType {
+     public static final @Interned String DESC_ANY      = "Any CompositeContext";
+     public static final @Interned String DESC_XOR      = "XOR mode";
+     public static final @Interned String DESC_CLEAR     = "Porter-Duff Clear";
+     public static final @Interned String DESC_SRC       = "Porter-Duff Src";
+     public static final @Interned String DESC_DST       = "Porter-Duff Dst";
+     public static final @Interned String DESC_SRC_OVER  = "Porter-Duff Src Over Dst";
+     public static final @Interned String DESC_DST_OVER  = "Porter-Duff Dst Over Src";
+     public static final @Interned String DESC_SRC_IN    = "Porter-Duff Src In Dst";
+     public static final @Interned String DESC_DST_IN    = "Porter-Duff Dst In Src";
+     public static final @Interned String DESC_SRC_OUT   = "Porter-Duff Src HeldOutBy Dst";
+     public static final @Interned String DESC_DST_OUT   = "Porter-Duff Dst HeldOutBy Src";
+     public static final @Interned String DESC_SRC_ATOP  = "Porter-Duff Src Atop Dst";
+     public static final @Interned String DESC_DST_ATOP  = "Porter-Duff Dst Atop Src";
+     public static final @Interned String DESC_ALPHA_XOR = "Porter-Duff Xor";
+     public static final @Interned String DESC_ANY_ALPHA = "Any AlphaComposite Rule";
+}
+package sun.java2d.loops;
+public class MaskBlit {
+     public static final @Interned String methodSignature = "MaskBlit(...)".toString();
+}
+package sun.java2d.loops;
+public class MaskFill {
+     public static final @Interned String methodSignature = "MaskFill(...)".toString();
+}
+package sun.java2d.loops;
+public class ScaledBlit {
+     public static final @Interned String methodSignature = "ScaledBlit(...)".toString();
+}
+package sun.java2d.loops;
+public class SurfaceType {
+     public static final @Interned String DESC_ANY_INT = "Any Discrete Integer";
+     public static final @Interned String DESC_ANY_SHORT = "Any Discrete Short";
+     public static final @Interned String DESC_ANY_BYTE = "Any Discrete Byte";
+     public static final @Interned String DESC_ANY_3BYTE = "Any 3 Byte Component";
+     public static final @Interned String DESC_ANY_4BYTE = "Any 4 Byte Component";
+     public static final @Interned String DESC_ANY_INT_DCM = "Any Integer DCM";
+     public static final @Interned String DESC_INT_RGBx = "Integer RGBx";
+     public static final @Interned String DESC_INT_BGRx = "Integer BGRx";
+     public static final @Interned String DESC_3BYTE_RGB = "3 Byte RGB";
+     public static final @Interned String DESC_INT_ARGB_BM     = "Int ARGB (Bitmask)";
+     public static final @Interned String DESC_BYTE_INDEXED_BM = "8-bit Indexed (Bitmask)";
+     public static final @Interned String DESC_INDEX8_GRAY  = "8-bit Palettized Gray";
+     public static final @Interned String DESC_INDEX12_GRAY = "12-bit Palettized Gray";
+     public static final @Interned String DESC_ANY_PAINT      = "Paint Object";
+     public static final @Interned String DESC_ANY_COLOR      = "Single Color";
+     public static final @Interned String DESC_OPAQUE_COLOR   = "Opaque Color";
+}
+package sun.jkernel;
+public class BackgroundDownloader {
+     public static final @Interned String BACKGROUND_DOWNLOAD_PROPERTY = "kernel.background.download";
+     public static final @Interned String PID_PATH = "tmp" + File.separator + "background.pid";
+}
+package sun.jkernel;
+public class DownloadManager {
+     public static final @Interned String KERNEL_DEBUG_PROPERTY = "kernel.debug";
+     public static final @Interned String KERNEL_NOMERGE_PROPERTY = "kernel.nomerge";
+     public static final @Interned String RESOURCE_URL = "internal-resource/";
+     public static final @Interned String REQUESTED_BUNDLES_PATH;
+     public static final @Interned String JAR_PATH_PROPERTY = "jarpath";
+     public static final @Interned String SIZE_PROPERTY = "size";
+     public static final @Interned String DEPENDENCIES_PROPERTY = "dependencies";
+     public static final @Interned String INSTALL_PROPERTY = "install";
+}
+package sun.jvmstat.perfdata.monitor.protocol.local;
+public class PerfDataFile {
+     public static final @Interned String dirNamePrefix = "hsperfdata_";
+     public static final @Interned String userDirNamePattern = "hsperfdata_\\S*";
+     public static final @Interned String fileNamePattern = "^[0-9]+$";
+}
+package sun.management.jmxremote;
+public class ConnectorBootstrap {
+         public static final @Interned String PORT = "0";
+         public static final @Interned String CONFIG_FILE_NAME = "management.properties";
+         public static final @Interned String USE_SSL = "true";
+         public static final @Interned String USE_LOCAL_ONLY = "true";
+         public static final @Interned String USE_REGISTRY_SSL = "false";
+         public static final @Interned String USE_AUTHENTICATION = "true";
+         public static final @Interned String PASSWORD_FILE_NAME = "jmxremote.password";
+         public static final @Interned String ACCESS_FILE_NAME = "jmxremote.access";
+         public static final @Interned String SSL_NEED_CLIENT_AUTH = "false";
+}
+package sun.management.snmp;
+public class AdaptorBootstrap {
+         public static final @Interned String PORT="161";
+         public static final @Interned String CONFIG_FILE_NAME="management.properties";
+         public static final @Interned String TRAP_PORT="162";
+         public static final @Interned String USE_ACL="true";
+         public static final @Interned String ACL_FILE_NAME="snmp.acl";
+         public static final @Interned String BIND_ADDRESS="localhost";
+         public static final @Interned String PORT="com.sun.management.snmp.port";
+}
+package sun.misc;
+public class JarIndex {
+     public static final @Interned String INDEX_NAME = "META-INF/INDEX.LIST";
+}
+package sun.rmi.rmic.newrmic;
+public class Constants {
+     public static final @Interned String REMOTE = "java.rmi.Remote";
+     public static final @Interned String EXCEPTION = "java.lang.Exception";
+     public static final @Interned String REMOTE_EXCEPTION = "java.rmi.RemoteException";
+     public static final @Interned String RUNTIME_EXCEPTION = "java.lang.RuntimeException";
+}
+package sun.security.krb5;
+public class PrincipalName {
+     public static final @Interned String TGS_DEFAULT_SRV_NAME = "krbtgt";
+     public static final @Interned String NAME_COMPONENT_SEPARATOR_STR = "/";
+     public static final @Interned String NAME_REALM_SEPARATOR_STR = "@";
+     public static final @Interned String REALM_COMPONENT_SEPARATOR_STR = ".";
+}
+package sun.security.pkcs11.wrapper;
+public class Constants {
+     public static final @Interned String INDENT = "  ";
+}
+package sun.security.pkcs;
+public class PKCS9Attribute {
+     public static final @Interned String EMAIL_ADDRESS_STR = "EmailAddress";
+     public static final @Interned String UNSTRUCTURED_NAME_STR = "UnstructuredName";
+     public static final @Interned String CONTENT_TYPE_STR = "ContentType";
+     public static final @Interned String MESSAGE_DIGEST_STR = "MessageDigest";
+     public static final @Interned String SIGNING_TIME_STR = "SigningTime";
+     public static final @Interned String COUNTERSIGNATURE_STR = "Countersignature";
+     public static final @Interned String CHALLENGE_PASSWORD_STR = "ChallengePassword";
+     public static final @Interned String UNSTRUCTURED_ADDRESS_STR = "UnstructuredAddress";
+     public static final @Interned String ISSUER_SERIALNUMBER_STR = "IssuerAndSerialNumber";
+     public static final @Interned String EXTENSION_REQUEST_STR = "ExtensionRequest";
+     public static final @Interned String SMIME_CAPABILITY_STR = "SMIMECapability";
+     public static final @Interned String SIGNING_CERTIFICATE_STR = "SigningCertificate";
+}
+package sun.security.provider;
+public class PolicyParser {
+     public static final @Interned String REPLACE_NAME = "PolicyParser.REPLACE_NAME";
+         public static final @Interned String WILDCARD_CLASS = "WILDCARD_PRINCIPAL_CLASS";
+         public static final @Interned String WILDCARD_NAME = "WILDCARD_PRINCIPAL_NAME";
+}
+package sun.security.provider;
+public class X509Factory {
+     public static final @Interned String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
+     public static final @Interned String END_CERT = "-----END CERTIFICATE-----";
+}
+package sun.security.util;
+public class ManifestDigester {
+     public static final @Interned String MF_MAIN_ATTRS = "Manifest-Main-Attributes";
+}
+package sun.security.util;
+public class SecurityConstants {
+     public static final @Interned String FILE_DELETE_ACTION = "delete";
+     public static final @Interned String FILE_EXECUTE_ACTION = "execute";
+     public static final @Interned String FILE_READ_ACTION = "read";
+     public static final @Interned String FILE_WRITE_ACTION = "write";
+     public static final @Interned String FILE_READLINK_ACTION = "readlink";
+     public static final @Interned String SOCKET_RESOLVE_ACTION = "resolve";
+     public static final @Interned String SOCKET_CONNECT_ACTION = "connect";
+     public static final @Interned String SOCKET_LISTEN_ACTION = "listen";
+     public static final @Interned String SOCKET_ACCEPT_ACTION = "accept";
+     public static final @Interned String SOCKET_CONNECT_ACCEPT_ACTION = "connect,accept";
+     public static final @Interned String PROPERTY_RW_ACTION = "read,write";
+     public static final @Interned String PROPERTY_READ_ACTION = "read";
+     public static final @Interned String PROPERTY_WRITE_ACTION = "write";
+}
+package sun.security.x509;
+public class AuthorityInfoAccessExtension {
+     public static final @Interned String NAME = "AuthorityInfoAccess";
+     public static final @Interned String DESCRIPTIONS = "descriptions";
+}
+package sun.security.x509;
+public class AuthorityKeyIdentifierExtension {
+     public static final @Interned String NAME = "AuthorityKeyIdentifier";
+     public static final @Interned String KEY_ID = "key_id";
+     public static final @Interned String AUTH_NAME = "auth_name";
+     public static final @Interned String SERIAL_NUMBER = "serial_number";
+}
+package sun.security.x509;
+public class BasicConstraintsExtension {
+     public static final @Interned String IDENT = "x509.info.extensions.BasicConstraints";
+     public static final @Interned String NAME = "BasicConstraints";
+     public static final @Interned String IS_CA = "is_ca";
+     public static final @Interned String PATH_LEN = "path_len";
+}
+package sun.security.x509;
+public class CRLDistributionPointsExtension {
+     public static final @Interned String NAME = "CRLDistributionPoints";
+     public static final @Interned String POINTS = "points";
+}
+package sun.security.x509;
+public class CRLNumberExtension {
+     public static final @Interned String NAME = "CRLNumber";
+     public static final @Interned String NUMBER = "value";
+}
+package sun.security.x509;
+public class CRLReasonCodeExtension {
+     public static final @Interned String NAME = "CRLReasonCode";
+     public static final @Interned String REASON = "reason";
+}
+package sun.security.x509;
+public class CertificateAlgorithmId {
+     public static final @Interned String IDENT = "x509.info.algorithmID";
+     public static final @Interned String NAME = "algorithmID";
+     public static final @Interned String ALGORITHM = "algorithm";
+}
+package sun.security.x509;
+public class CertificateExtensions {
+     public static final @Interned String IDENT = "x509.info.extensions";
+     public static final @Interned String NAME = "extensions";
+}
+package sun.security.x509;
+public class CertificateIssuerExtension {
+     public static final @Interned String NAME = "CertificateIssuer";
+     public static final @Interned String ISSUER = "issuer";
+}
+package sun.security.x509;
+public class CertificateIssuerName {
+     public static final @Interned String IDENT = "x509.info.issuer";
+     public static final @Interned String NAME = "issuer";
+     public static final @Interned String DN_NAME = "dname";
+     public static final @Interned String DN_PRINCIPAL = "x500principal";
+}
+package sun.security.x509;
+public class CertificateIssuerUniqueIdentity {
+     public static final @Interned String IDENT = "x509.info.issuerID";
+     public static final @Interned String NAME = "issuerID";
+     public static final @Interned String ID = "id";
+}
+package sun.security.x509;
+public class CertificatePoliciesExtension {
+     public static final @Interned String IDENT = "x509.info.extensions.CertificatePolicies";
+     public static final @Interned String NAME = "CertificatePolicies";
+     public static final @Interned String POLICIES = "policies";
+}
+package sun.security.x509;
+public class CertificateSerialNumber {
+     public static final @Interned String IDENT = "x509.info.serialNumber";
+     public static final @Interned String NAME = "serialNumber";
+     public static final @Interned String NUMBER = "number";
+}
+package sun.security.x509;
+public class CertificateSubjectName {
+     public static final @Interned String IDENT = "x509.info.subject";
+     public static final @Interned String NAME = "subject";
+     public static final @Interned String DN_NAME = "dname";
+     public static final @Interned String DN_PRINCIPAL = "x500principal";
+}
+package sun.security.x509;
+public class CertificateSubjectUniqueIdentity {
+     public static final @Interned String IDENT = "x509.info.subjectID";
+     public static final @Interned String NAME = "subjectID";
+     public static final @Interned String ID = "id";
+}
+package sun.security.x509;
+public class CertificateValidity {
+     public static final @Interned String IDENT = "x509.info.validity";
+     public static final @Interned String NAME = "validity";
+     public static final @Interned String NOT_BEFORE = "notBefore";
+     public static final @Interned String NOT_AFTER = "notAfter";
+}
+package sun.security.x509;
+public class CertificateVersion {
+     public static final @Interned String IDENT = "x509.info.version";
+     public static final @Interned String NAME = "version";
+     public static final @Interned String VERSION = "number";
+}
+package sun.security.x509;
+public class CertificateX509Key {
+     public static final @Interned String IDENT = "x509.info.key";
+     public static final @Interned String NAME = "key";
+     public static final @Interned String KEY = "value";
+}
+package sun.security.x509;
+public class DeltaCRLIndicatorExtension {
+     public static final @Interned String NAME = "DeltaCRLIndicator";
+}
+package sun.security.x509;
+public class ExtendedKeyUsageExtension {
+     public static final @Interned String IDENT = "x509.info.extensions.ExtendedKeyUsage";
+     public static final @Interned String NAME = "ExtendedKeyUsage";
+     public static final @Interned String USAGES = "usages";
+}
+package sun.security.x509;
+public class FreshestCRLExtension {
+     public static final @Interned String NAME = "FreshestCRL";
+}
+package sun.security.x509;
+public class InhibitAnyPolicyExtension {
+     public static final @Interned String IDENT = "x509.info.extensions.InhibitAnyPolicy";
+     public static final @Interned String NAME = "InhibitAnyPolicy";
+     public static final @Interned String SKIP_CERTS = "skip_certs";
+}
+package sun.security.x509;
+public class InvalidityDateExtension {
+     public static final @Interned String NAME = "InvalidityDate";
+     public static final @Interned String DATE = "date";
+}
+package sun.security.x509;
+public class IssuerAlternativeNameExtension {
+     public static final @Interned String NAME = "IssuerAlternativeName";
+     public static final @Interned String ISSUER_NAME = "issuer_name";
+}
+package sun.security.x509;
+public class IssuingDistributionPointExtension {
+     public static final @Interned String NAME = "IssuingDistributionPoint";
+     public static final @Interned String POINT = "point";
+     public static final @Interned String REASONS = "reasons";
+     public static final @Interned String ONLY_USER_CERTS = "only_user_certs";
+     public static final @Interned String ONLY_CA_CERTS = "only_ca_certs";
+     public static final @Interned String ONLY_ATTRIBUTE_CERTS = "only_attribute_certs";
+     public static final @Interned String INDIRECT_CRL = "indirect_crl";
+}
+package sun.security.x509;
+public class KeyUsageExtension {
+     public static final @Interned String IDENT = "x509.info.extensions.KeyUsage";
+     public static final @Interned String NAME = "KeyUsage";
+     public static final @Interned String DIGITAL_SIGNATURE = "digital_signature";
+     public static final @Interned String NON_REPUDIATION = "non_repudiation";
+     public static final @Interned String KEY_ENCIPHERMENT = "key_encipherment";
+     public static final @Interned String DATA_ENCIPHERMENT = "data_encipherment";
+     public static final @Interned String KEY_AGREEMENT = "key_agreement";
+     public static final @Interned String KEY_CERTSIGN = "key_certsign";
+     public static final @Interned String CRL_SIGN = "crl_sign";
+     public static final @Interned String ENCIPHER_ONLY = "encipher_only";
+     public static final @Interned String DECIPHER_ONLY = "decipher_only";
+}
+package sun.security.x509;
+public class NameConstraintsExtension {
+     public static final @Interned String IDENT = "x509.info.extensions.NameConstraints";
+     public static final @Interned String NAME = "NameConstraints";
+     public static final @Interned String PERMITTED_SUBTREES = "permitted_subtrees";
+     public static final @Interned String EXCLUDED_SUBTREES = "excluded_subtrees";
+}
+package sun.security.x509;
+public class NetscapeCertTypeExtension {
+     public static final @Interned String IDENT = "x509.info.extensions.NetscapeCertType";
+     public static final @Interned String NAME = "NetscapeCertType";
+     public static final @Interned String SSL_CLIENT = "ssl_client";
+     public static final @Interned String SSL_SERVER = "ssl_server";
+     public static final @Interned String S_MIME = "s_mime";
+     public static final @Interned String OBJECT_SIGNING = "object_signing";
+     public static final @Interned String SSL_CA = "ssl_ca";
+     public static final @Interned String S_MIME_CA = "s_mime_ca";
+     public static final @Interned String OBJECT_SIGNING_CA = "object_signing_ca";
+}
+package sun.security.x509;
+public class OCSPNoCheckExtension {
+     public static final @Interned String NAME = "OCSPNoCheck";
+}
+package sun.security.x509;
+public class PolicyConstraintsExtension {
+     public static final @Interned String IDENT = "x509.info.extensions.PolicyConstraints";
+     public static final @Interned String NAME = "PolicyConstraints";
+     public static final @Interned String REQUIRE = "require";
+     public static final @Interned String INHIBIT = "inhibit";
+}
+package sun.security.x509;
+public class PolicyInformation {
+     public static final @Interned String NAME       = "PolicyInformation";
+     public static final @Interned String ID         = "id";
+     public static final @Interned String QUALIFIERS = "qualifiers";
+}
+package sun.security.x509;
+public class PolicyMappingsExtension {
+     public static final @Interned String IDENT = "x509.info.extensions.PolicyMappings";
+     public static final @Interned String NAME = "PolicyMappings";
+     public static final @Interned String MAP = "map";
+}
+package sun.security.x509;
+public class PrivateKeyUsageExtension {
+     public static final @Interned String IDENT = "x509.info.extensions.PrivateKeyUsage";
+     public static final @Interned String NAME = "PrivateKeyUsage";
+     public static final @Interned String NOT_BEFORE = "not_before";
+     public static final @Interned String NOT_AFTER = "not_after";
+}
+package sun.security.x509;
+public class ReasonFlags {
+     public static final @Interned String UNUSED = "unused";
+     public static final @Interned String KEY_COMPROMISE = "key_compromise";
+     public static final @Interned String CA_COMPROMISE = "ca_compromise";
+     public static final @Interned String AFFILIATION_CHANGED = "affiliation_changed";
+     public static final @Interned String SUPERSEDED = "superseded";
+     public static final @Interned String CERTIFICATE_HOLD = "certificate_hold";
+     public static final @Interned String PRIVILEGE_WITHDRAWN = "privilege_withdrawn";
+     public static final @Interned String AA_COMPROMISE = "aa_compromise";
+}
+package sun.security.x509;
+public class SubjectAlternativeNameExtension {
+     public static final @Interned String NAME = "SubjectAlternativeName";
+     public static final @Interned String SUBJECT_NAME = "subject_name";
+}
+package sun.security.x509;
+public class SubjectInfoAccessExtension {
+     public static final @Interned String NAME = "SubjectInfoAccess";
+     public static final @Interned String DESCRIPTIONS = "descriptions";
+}
+package sun.security.x509;
+public class SubjectKeyIdentifierExtension {
+     public static final @Interned String NAME = "SubjectKeyIdentifier";
+     public static final @Interned String KEY_ID = "key_id";
+}
+package sun.security.x509;
+public class X509CertImpl {
+     public static final @Interned String NAME = "x509";
+     public static final @Interned String ALG_ID = "algorithm";
+     public static final @Interned String SIGNATURE = "signature";
+     public static final @Interned String SIGNED_CERT = "signed_cert";
+}
+package sun.security.x509;
+public class X509CertInfo {
+     public static final @Interned String IDENT = "x509.info";
+     public static final @Interned String NAME = "info";
+}
+package sun.util.calendar;
+public class ZoneInfoFile {
+     public static final @Interned String  JAVAZM_FILE_NAME = "ZoneInfoMappings";
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockAnalysis.java b/checker/src/main/java/org/checkerframework/checker/lock/LockAnalysis.java
new file mode 100644
index 0000000..b1ee377
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/lock/LockAnalysis.java
@@ -0,0 +1,48 @@
+package org.checkerframework.checker.lock;
+
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * The analysis class for the lock type system.
+ *
+ * <p>This class extends {@link CFAbstractAnalysis} so that {@link LockStore} is used rather than
+ * {@link CFStore}.
+ */
+public class LockAnalysis extends CFAbstractAnalysis<CFValue, LockStore, LockTransfer> {
+
+  public LockAnalysis(
+      BaseTypeChecker checker,
+      LockAnnotatedTypeFactory factory,
+      List<Pair<VariableElement, CFValue>> fieldValues) {
+    super(checker, factory, fieldValues);
+  }
+
+  @Override
+  public LockTransfer createTransferFunction() {
+    return new LockTransfer(this, (LockChecker) checker);
+  }
+
+  @Override
+  public LockStore createEmptyStore(boolean sequentialSemantics) {
+    return new LockStore(this, sequentialSemantics);
+  }
+
+  @Override
+  public LockStore createCopiedStore(LockStore s) {
+    return new LockStore(this, s);
+  }
+
+  @Override
+  public CFValue createAbstractValue(Set<AnnotationMirror> annotations, TypeMirror underlyingType) {
+    return defaultCreateAbstractValue(this, annotations, underlyingType);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java
new file mode 100644
index 0000000..fe5014b
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java
@@ -0,0 +1,751 @@
+package org.checkerframework.checker.lock;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.lock.qual.EnsuresLockHeld;
+import org.checkerframework.checker.lock.qual.EnsuresLockHeldIf;
+import org.checkerframework.checker.lock.qual.GuardSatisfied;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.GuardedByBottom;
+import org.checkerframework.checker.lock.qual.GuardedByUnknown;
+import org.checkerframework.checker.lock.qual.LockHeld;
+import org.checkerframework.checker.lock.qual.LockPossiblyHeld;
+import org.checkerframework.checker.lock.qual.LockingFree;
+import org.checkerframework.checker.lock.qual.MayReleaseLocks;
+import org.checkerframework.checker.lock.qual.ReleasesNoLocks;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.ClassGetName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.MethodCall;
+import org.checkerframework.dataflow.expression.ThisReference;
+import org.checkerframework.dataflow.expression.Unknown;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.dataflow.util.PurityUtils;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesError;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreeUtils;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * LockAnnotatedTypeFactory builds types with @LockHeld and @LockPossiblyHeld annotations. LockHeld
+ * identifies that an object is being used as a lock and is being held when a given tree is
+ * executed. LockPossiblyHeld is the default type qualifier for this hierarchy and applies to all
+ * fields, local variables and parameters -- hence it does not convey any information other than
+ * that it is not LockHeld.
+ *
+ * <p>However, there are a number of other annotations used in conjunction with these annotations to
+ * enforce proper locking.
+ *
+ * @checker_framework.manual #lock-checker Lock Checker
+ */
+public class LockAnnotatedTypeFactory
+    extends GenericAnnotatedTypeFactory<CFValue, LockStore, LockTransfer, LockAnalysis> {
+
+  /** dependent type annotation error message for when the expression is not effectively final. */
+  public static final String NOT_EFFECTIVELY_FINAL = "lock expression is not effectively final";
+
+  /** The @{@link LockHeld} annotation. */
+  protected final AnnotationMirror LOCKHELD = AnnotationBuilder.fromClass(elements, LockHeld.class);
+  /** The @{@link LockPossiblyHeld} annotation. */
+  protected final AnnotationMirror LOCKPOSSIBLYHELD =
+      AnnotationBuilder.fromClass(elements, LockPossiblyHeld.class);
+  /** The @{@link SideEffectFree} annotation. */
+  protected final AnnotationMirror SIDEEFFECTFREE =
+      AnnotationBuilder.fromClass(elements, SideEffectFree.class);
+  /** The @{@link GuardedByUnknown} annotation. */
+  protected final AnnotationMirror GUARDEDBYUNKNOWN =
+      AnnotationBuilder.fromClass(elements, GuardedByUnknown.class);
+  /** The @{@link GuardedByBottom} annotation. */
+  protected final AnnotationMirror GUARDEDBY =
+      createGuardedByAnnotationMirror(new ArrayList<String>());
+  /** The @{@link GuardedByBottom} annotation. */
+  protected final AnnotationMirror GUARDEDBYBOTTOM =
+      AnnotationBuilder.fromClass(elements, GuardedByBottom.class);
+  /** The @{@link GuardSatisfied} annotation. */
+  protected final AnnotationMirror GUARDSATISFIED =
+      AnnotationBuilder.fromClass(elements, GuardSatisfied.class);
+
+  /** The value() element/field of a @GuardedBy annotation. */
+  protected final ExecutableElement guardedByValueElement =
+      TreeUtils.getMethod(GuardedBy.class, "value", 0, processingEnv);
+  /** The value() element/field of a @GuardSatisfied annotation. */
+  protected final ExecutableElement guardSatisfiedValueElement =
+      TreeUtils.getMethod(GuardSatisfied.class, "value", 0, processingEnv);
+  /** The EnsuresLockHeld.value element/field. */
+  protected final ExecutableElement ensuresLockHeldValueElement =
+      TreeUtils.getMethod(EnsuresLockHeld.class, "value", 0, processingEnv);
+  /** The EnsuresLockHeldIf.expression element/field. */
+  protected final ExecutableElement ensuresLockHeldIfExpressionElement =
+      TreeUtils.getMethod(EnsuresLockHeldIf.class, "expression", 0, processingEnv);
+
+  /** The net.jcip.annotations.GuardedBy annotation, or null if not on the classpath. */
+  protected final Class<? extends Annotation> jcipGuardedBy;
+
+  /** The javax.annotation.concurrent.GuardedBy annotation, or null if not on the classpath. */
+  protected final Class<? extends Annotation> javaxGuardedBy;
+
+  /** Create a new LockAnnotatedTypeFactory. */
+  public LockAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker, true);
+
+    // This alias is only true for the Lock Checker. All other checkers must
+    // ignore the @LockingFree annotation.
+    addAliasedDeclAnnotation(LockingFree.class, SideEffectFree.class, SIDEEFFECTFREE);
+
+    // This alias is only true for the Lock Checker. All other checkers must
+    // ignore the @ReleasesNoLocks annotation.  Note that ReleasesNoLocks is
+    // not truly side-effect-free even as far as the Lock Checker is concerned,
+    // so there is additional handling of this annotation in the Lock Checker.
+    addAliasedDeclAnnotation(ReleasesNoLocks.class, SideEffectFree.class, SIDEEFFECTFREE);
+
+    jcipGuardedBy = classForNameOrNull("net.jcip.annotations.GuardedBy");
+
+    javaxGuardedBy = classForNameOrNull("javax.annotation.concurrent.GuardedBy");
+
+    postInit();
+  }
+
+  /**
+   * Returns the value of Class.forName, or null if Class.forName would throw an exception.
+   *
+   * @param annotationClassName an annotation's name, in ClassGetName format
+   * @return an annotation class or null
+   */
+  @SuppressWarnings("unchecked") // cast to generic type
+  private Class<? extends Annotation> classForNameOrNull(@ClassGetName String annotationClassName) {
+    try {
+      return (Class<? extends Annotation>) Class.forName(annotationClassName);
+    } catch (Exception e) {
+      return null;
+    }
+  }
+
+  @Override
+  protected DependentTypesHelper createDependentTypesHelper() {
+    return new DependentTypesHelper(this) {
+      @Override
+      protected void reportErrors(Tree errorTree, List<DependentTypesError> errors) {
+        // If the error message is NOT_EFFECTIVELY_FINAL, then report
+        // lock.expression.not.final instead of expression.unparsable .
+        List<DependentTypesError> superErrors = new ArrayList<>(errors.size());
+        for (DependentTypesError error : errors) {
+          if (error.error.equals(NOT_EFFECTIVELY_FINAL)) {
+            checker.reportError(errorTree, "lock.expression.not.final", error.expression);
+          } else {
+            superErrors.add(error);
+          }
+        }
+        super.reportErrors(errorTree, superErrors);
+      }
+
+      @Override
+      protected boolean shouldPassThroughExpression(String expression) {
+        // There is no expression to use to replace <self> here, so just pass the expression along.
+        return super.shouldPassThroughExpression(expression)
+            || LockVisitor.SELF_RECEIVER_PATTERN.matcher(expression).matches();
+      }
+
+      @Override
+      protected @Nullable JavaExpression transform(JavaExpression javaExpr) {
+        if (javaExpr instanceof Unknown || isExpressionEffectivelyFinal(javaExpr)) {
+          return javaExpr;
+        }
+
+        // If the expression isn't effectively final, then return the NOT_EFFECTIVELY_FINAL error
+        // string.
+        return createError(javaExpr.toString(), NOT_EFFECTIVELY_FINAL);
+      }
+    };
+  }
+
+  /**
+   * Returns whether or not the expression is effectively final.
+   *
+   * <p>This method returns true in the following cases when expr is:
+   *
+   * <p>1. a field access and the field is final and the field access expression is effectively
+   * final as specified by this method.
+   *
+   * <p>2. an effectively final local variable.
+   *
+   * <p>3. a deterministic method call whose arguments and receiver expression are effectively final
+   * as specified by this method.
+   *
+   * <p>4. a this reference or a class literal
+   *
+   * @param expr expression
+   * @return whether or not the expression is effectively final
+   */
+  boolean isExpressionEffectivelyFinal(JavaExpression expr) {
+    if (expr instanceof FieldAccess) {
+      FieldAccess fieldAccess = (FieldAccess) expr;
+      JavaExpression receiver = fieldAccess.getReceiver();
+      // Don't call fieldAccess
+      return fieldAccess.isFinal() && isExpressionEffectivelyFinal(receiver);
+    } else if (expr instanceof LocalVariable) {
+      return ElementUtils.isEffectivelyFinal(((LocalVariable) expr).getElement());
+    } else if (expr instanceof MethodCall) {
+      MethodCall methodCall = (MethodCall) expr;
+      for (JavaExpression arg : methodCall.getArguments()) {
+        if (!isExpressionEffectivelyFinal(arg)) {
+          return false;
+        }
+      }
+      return PurityUtils.isDeterministic(this, methodCall.getElement())
+          && isExpressionEffectivelyFinal(methodCall.getReceiver());
+    } else if (expr instanceof ThisReference || expr instanceof ClassName) {
+      // this is always final. "ClassName" is actually a class literal (String.class), it's final
+      // too.
+      return true;
+    } else { // type of 'expr' is not supported in @GuardedBy(...) lock expressions
+      return false;
+    }
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new LinkedHashSet<>(
+        Arrays.asList(
+            LockHeld.class,
+            LockPossiblyHeld.class,
+            GuardedBy.class,
+            GuardedByUnknown.class,
+            GuardSatisfied.class,
+            GuardedByBottom.class));
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new LockQualifierHierarchy(getSupportedTypeQualifiers(), elements);
+  }
+
+  @Override
+  protected LockAnalysis createFlowAnalysis(List<Pair<VariableElement, CFValue>> fieldValues) {
+    return new LockAnalysis(checker, this, fieldValues);
+  }
+
+  @Override
+  public LockTransfer createFlowTransferFunction(
+      CFAbstractAnalysis<CFValue, LockStore, LockTransfer> analysis) {
+    return new LockTransfer((LockAnalysis) analysis, (LockChecker) this.checker);
+  }
+
+  /** LockQualifierHierarchy. */
+  class LockQualifierHierarchy extends MostlyNoElementQualifierHierarchy {
+
+    /** Qualifier kind for the @{@link GuardedBy} annotation. */
+    private final QualifierKind GUARDEDBY_KIND;
+    /** Qualifier kind for the @{@link GuardSatisfied} annotation. */
+    private final QualifierKind GUARDSATISFIED_KIND;
+    /** Qualifier kind for the @{@link GuardedByBottom} annotation. */
+    private final QualifierKind GUARDEDBYBOTTOM_KIND;
+    /** Qualifier kind for the @{@link GuardedByUnknown} annotation. */
+    private final QualifierKind GUARDEDBYUNKNOWN_KIND;
+
+    /**
+     * Creates a LockQualifierHierarchy.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+     * @param elements element utils
+     */
+    public LockQualifierHierarchy(
+        Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+      GUARDEDBY_KIND = getQualifierKind(GUARDEDBY);
+      GUARDSATISFIED_KIND = getQualifierKind(GUARDSATISFIED);
+      GUARDEDBYBOTTOM_KIND = getQualifierKind(GUARDEDBYBOTTOM);
+      GUARDEDBYUNKNOWN_KIND = getQualifierKind(GUARDEDBYUNKNOWN);
+    }
+
+    @Override
+    protected boolean isSubtypeWithElements(
+        AnnotationMirror subAnno,
+        QualifierKind subKind,
+        AnnotationMirror superAnno,
+        QualifierKind superKind) {
+      if (subKind == GUARDEDBY_KIND && superKind == GUARDEDBY_KIND) {
+        List<String> subLocks =
+            AnnotationUtils.getElementValueArray(
+                superAnno, guardedByValueElement, String.class, Collections.emptyList());
+        List<String> superLocks =
+            AnnotationUtils.getElementValueArray(
+                subAnno, guardedByValueElement, String.class, Collections.emptyList());
+        return subLocks.containsAll(superLocks) && superLocks.containsAll(subLocks);
+      } else if (subKind == GUARDSATISFIED_KIND && superKind == GUARDSATISFIED_KIND) {
+        return AnnotationUtils.areSame(superAnno, subAnno);
+      }
+      throw new RuntimeException("Unexpected");
+    }
+
+    @Override
+    protected AnnotationMirror leastUpperBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind lubKind) {
+      if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) {
+        List<String> locks1 =
+            AnnotationUtils.getElementValueArray(
+                a1, guardedByValueElement, String.class, Collections.emptyList());
+        List<String> locks2 =
+            AnnotationUtils.getElementValueArray(
+                a2, guardedByValueElement, String.class, Collections.emptyList());
+        if (locks1.containsAll(locks2) && locks2.containsAll(locks1)) {
+          return a1;
+        } else {
+          return GUARDEDBYUNKNOWN;
+        }
+      } else if (qualifierKind1 == GUARDSATISFIED_KIND && qualifierKind2 == GUARDSATISFIED_KIND) {
+        if (AnnotationUtils.areSame(a1, a2)) {
+          return a1;
+        } else {
+          return GUARDEDBYUNKNOWN;
+        }
+      } else if (qualifierKind1 == GUARDEDBYBOTTOM_KIND) {
+        return a2;
+      } else if (qualifierKind2 == GUARDEDBYBOTTOM_KIND) {
+        return a1;
+      }
+      throw new RuntimeException("Unexpected");
+    }
+
+    @Override
+    protected AnnotationMirror greatestLowerBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind glbKind) {
+      if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) {
+        List<String> locks1 =
+            AnnotationUtils.getElementValueArray(
+                a1, guardedByValueElement, String.class, Collections.emptyList());
+        List<String> locks2 =
+            AnnotationUtils.getElementValueArray(
+                a2, guardedByValueElement, String.class, Collections.emptyList());
+        if (locks1.containsAll(locks2) && locks2.containsAll(locks1)) {
+          return a1;
+        } else {
+          return GUARDEDBYBOTTOM;
+        }
+      } else if (qualifierKind1 == GUARDSATISFIED_KIND && qualifierKind2 == GUARDSATISFIED_KIND) {
+        if (AnnotationUtils.areSame(a1, a2)) {
+          return a1;
+        } else {
+          return GUARDEDBYBOTTOM;
+        }
+      } else if (qualifierKind1 == GUARDEDBYUNKNOWN_KIND) {
+        return a2;
+      } else if (qualifierKind2 == GUARDEDBYUNKNOWN_KIND) {
+        return a1;
+      }
+      throw new RuntimeException("Unexpected");
+    }
+  }
+
+  // The side effect annotations processed by the Lock Checker.
+  enum SideEffectAnnotation {
+    MAYRELEASELOCKS("@MayReleaseLocks", MayReleaseLocks.class),
+    RELEASESNOLOCKS("@ReleasesNoLocks", ReleasesNoLocks.class),
+    LOCKINGFREE("@LockingFree", LockingFree.class),
+    SIDEEFFECTFREE("@SideEffectFree", SideEffectFree.class),
+    PURE("@Pure", Pure.class);
+    final String annotation;
+    final Class<? extends Annotation> annotationClass;
+
+    SideEffectAnnotation(String annotation, Class<? extends Annotation> annotationClass) {
+      this.annotation = annotation;
+      this.annotationClass = annotationClass;
+    }
+
+    public String getNameOfSideEffectAnnotation() {
+      return annotation;
+    }
+
+    public Class<? extends Annotation> getAnnotationClass() {
+      return annotationClass;
+    }
+
+    /**
+     * Returns true if the receiver side effect annotation is weaker than side effect annotation
+     * 'other'.
+     */
+    boolean isWeakerThan(SideEffectAnnotation other) {
+      boolean weaker = false;
+
+      switch (other) {
+        case MAYRELEASELOCKS:
+          break;
+        case RELEASESNOLOCKS:
+          if (this == SideEffectAnnotation.MAYRELEASELOCKS) {
+            weaker = true;
+          }
+          break;
+        case LOCKINGFREE:
+          switch (this) {
+            case MAYRELEASELOCKS:
+            case RELEASESNOLOCKS:
+              weaker = true;
+              break;
+            default:
+          }
+          break;
+        case SIDEEFFECTFREE:
+          switch (this) {
+            case MAYRELEASELOCKS:
+            case RELEASESNOLOCKS:
+            case LOCKINGFREE:
+              weaker = true;
+              break;
+            default:
+          }
+          break;
+        case PURE:
+          switch (this) {
+            case MAYRELEASELOCKS:
+            case RELEASESNOLOCKS:
+            case LOCKINGFREE:
+            case SIDEEFFECTFREE:
+              weaker = true;
+              break;
+            default:
+          }
+          break;
+      }
+
+      return weaker;
+    }
+
+    static SideEffectAnnotation weakest = null;
+
+    public static SideEffectAnnotation weakest() {
+      if (weakest == null) {
+        for (SideEffectAnnotation sea : SideEffectAnnotation.values()) {
+          if (weakest == null) {
+            weakest = sea;
+          }
+          if (sea.isWeakerThan(weakest)) {
+            weakest = sea;
+          }
+        }
+      }
+      return weakest;
+    }
+  }
+
+  /**
+   * Indicates which side effect annotation is present on the given method. If more than one
+   * annotation is present, this method issues an error (if issueErrorIfMoreThanOnePresent is true)
+   * and returns the annotation providing the weakest guarantee. Only call with
+   * issueErrorIfMoreThanOnePresent == true when visiting a method definition. This prevents
+   * multiple errors being issued for the same method (as would occur if
+   * issueErrorIfMoreThanOnePresent were set to true when visiting method invocations). If no
+   * annotation is present, return RELEASESNOLOCKS as the default, and MAYRELEASELOCKS as the
+   * conservative default.
+   *
+   * @param element the method element
+   * @param issueErrorIfMoreThanOnePresent whether to issue an error if more than one side effect
+   *     annotation is present on the method
+   */
+  // package-private
+  SideEffectAnnotation methodSideEffectAnnotation(
+      Element element, boolean issueErrorIfMoreThanOnePresent) {
+    if (element != null) {
+      Set<SideEffectAnnotation> sideEffectAnnotationPresent =
+          EnumSet.noneOf(SideEffectAnnotation.class);
+      for (SideEffectAnnotation sea : SideEffectAnnotation.values()) {
+        if (getDeclAnnotationNoAliases(element, sea.getAnnotationClass()) != null) {
+          sideEffectAnnotationPresent.add(sea);
+        }
+      }
+
+      int count = sideEffectAnnotationPresent.size();
+
+      if (count == 0) {
+        return defaults.applyConservativeDefaults(element)
+            ? SideEffectAnnotation.MAYRELEASELOCKS
+            : SideEffectAnnotation.RELEASESNOLOCKS;
+      }
+
+      if (count > 1 && issueErrorIfMoreThanOnePresent) {
+        // TODO: Turn on after figuring out how this interacts with inherited annotations.
+        // checker.reportError(element, "multiple.sideeffect.annotations");
+      }
+
+      SideEffectAnnotation weakest = null;
+      // At least one side effect annotation was found. Return the weakest.
+      for (SideEffectAnnotation sea : sideEffectAnnotationPresent) {
+        if (weakest == null || sea.isWeakerThan(weakest)) {
+          weakest = sea;
+        }
+      }
+      return weakest;
+    }
+
+    // When there is not enough information to determine the correct side effect annotation,
+    // return the weakest one.
+    return SideEffectAnnotation.weakest();
+  }
+
+  /**
+   * Returns the index (that is, the {@code value} element) on the {@code @GuardSatisfied}
+   * annotation in the given AnnotatedTypeMirror. Assumes atm is non-null and contains a
+   * {@code @GuardSatisfied} annotation.
+   *
+   * @param atm an AnnotatedTypeMirror containing a GuardSatisfied annotation
+   * @return the index on the GuardSatisfied annotation
+   */
+  // package-private
+  int getGuardSatisfiedIndex(AnnotatedTypeMirror atm) {
+    return getGuardSatisfiedIndex(atm.getAnnotation(GuardSatisfied.class));
+  }
+
+  /**
+   * Returns the index (that is, the {@code value} element) on the given {@code @GuardSatisfied}
+   * annotation. Assumes am is non-null and is a GuardSatisfied annotation.
+   *
+   * @param am an AnnotationMirror for a GuardSatisfied annotation
+   * @return the index on the GuardSatisfied annotation
+   */
+  // package-private
+  int getGuardSatisfiedIndex(AnnotationMirror am) {
+    return AnnotationUtils.getElementValueInt(am, guardSatisfiedValueElement, -1);
+  }
+
+  @Override
+  public ParameterizedExecutableType methodFromUse(
+      ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) {
+    ParameterizedExecutableType mType = super.methodFromUse(tree, methodElt, receiverType);
+
+    if (tree.getKind() != Kind.METHOD_INVOCATION) {
+      return mType;
+    }
+
+    // If a method's formal return type is annotated with @GuardSatisfied(index), look for the
+    // first instance of @GuardSatisfied(index) in the method definition's receiver type or
+    // formal parameters, retrieve the corresponding type of the actual parameter / receiver at
+    // the call site (e.g. @GuardedBy("someLock") and replace the return type at the call site
+    // with this type.
+
+    AnnotatedExecutableType invokedMethod = mType.executableType;
+
+    if (invokedMethod.getElement().getKind() == ElementKind.CONSTRUCTOR) {
+      return mType;
+    }
+
+    AnnotatedTypeMirror methodDefinitionReturn = invokedMethod.getReturnType();
+
+    if (methodDefinitionReturn == null
+        || !methodDefinitionReturn.hasAnnotation(GuardSatisfied.class)) {
+      return mType;
+    }
+
+    int returnGuardSatisfiedIndex = getGuardSatisfiedIndex(methodDefinitionReturn);
+
+    // @GuardSatisfied with no index defaults to index -1. Ignore instances of @GuardSatisfied
+    // with no index.  If a method is defined with a return type of @GuardSatisfied with no
+    // index, an error is reported by LockVisitor.visitMethod.
+
+    if (returnGuardSatisfiedIndex == -1) {
+      return mType;
+    }
+
+    // Find the receiver or first parameter whose @GS index matches that of the return type.
+    // Ensuring that the type annotations on distinct @GS parameters with the same index
+    // match at the call site is handled in LockVisitor.visitMethodInvocation
+
+    if (!ElementUtils.isStatic(invokedMethod.getElement())
+        && replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches(
+            methodDefinitionReturn,
+            invokedMethod.getReceiverType() /* the method definition receiver*/,
+            returnGuardSatisfiedIndex,
+            receiverType.getAnnotationInHierarchy(GUARDEDBYUNKNOWN))) {
+      return mType;
+    }
+
+    List<? extends ExpressionTree> methodInvocationTreeArguments =
+        ((MethodInvocationTree) tree).getArguments();
+    List<AnnotatedTypeMirror> requiredArgs =
+        AnnotatedTypes.expandVarArgs(this, invokedMethod, methodInvocationTreeArguments);
+
+    for (int i = 0; i < requiredArgs.size(); i++) {
+      if (replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches(
+          methodDefinitionReturn,
+          requiredArgs.get(i),
+          returnGuardSatisfiedIndex,
+          getAnnotatedType(methodInvocationTreeArguments.get(i))
+              .getEffectiveAnnotationInHierarchy(GUARDEDBYUNKNOWN))) {
+        return mType;
+      }
+    }
+
+    return mType;
+  }
+
+  /**
+   * If {@code atm} is not null and contains a {@code @GuardSatisfied} annotation, and if the index
+   * of this {@code @GuardSatisfied} annotation matches {@code matchingGuardSatisfiedIndex}, then
+   * {@code methodReturnAtm} will have its annotation in the {@code @GuardedBy} hierarchy replaced
+   * with that in {@code annotationInGuardedByHierarchy}.
+   *
+   * @param methodReturnAtm the AnnotatedTypeMirror for the return type of a method that will
+   *     potentially have its annotation in the {@code @GuardedBy} hierarchy replaced.
+   * @param atm an AnnotatedTypeMirror that may contain a {@code @GuardSatisfied} annotation. May be
+   *     null.
+   * @param matchingGuardSatisfiedIndex the {code @GuardSatisfied} index that the
+   *     {@code @GuardSatisfied} annotation in {@code atm} must have in order for the replacement to
+   *     occur.
+   * @param annotationInGuardedByHierarchy if the replacement occurs, the annotation in the
+   *     {@code @GuardedBy} hierarchy in this parameter will be used for the replacement.
+   * @return true if the replacement occurred, false otherwise
+   */
+  private boolean replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches(
+      AnnotatedTypeMirror methodReturnAtm,
+      AnnotatedTypeMirror atm,
+      int matchingGuardSatisfiedIndex,
+      AnnotationMirror annotationInGuardedByHierarchy) {
+    if (atm == null
+        || !atm.hasAnnotation(GuardSatisfied.class)
+        || getGuardSatisfiedIndex(atm) != matchingGuardSatisfiedIndex) {
+      return false;
+    }
+
+    methodReturnAtm.replaceAnnotation(annotationInGuardedByHierarchy);
+
+    return true;
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(new LockTreeAnnotator(this), super.createTreeAnnotator());
+  }
+
+  @Override
+  public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) {
+    translateJcipAndJavaxAnnotations(elt, type);
+
+    super.addComputedTypeAnnotations(elt, type);
+  }
+
+  @Override
+  public void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean useFlow) {
+    if (tree.getKind() == Tree.Kind.VARIABLE) {
+      translateJcipAndJavaxAnnotations(TreeUtils.elementFromTree((VariableTree) tree), type);
+    }
+
+    super.addComputedTypeAnnotations(tree, type, useFlow);
+  }
+
+  /**
+   * Given a field declaration with a {@code @net.jcip.annotations.GuardedBy} or {@code
+   * javax.annotation.concurrent.GuardedBy} annotation and an AnnotatedTypeMirror for that field,
+   * inserts the corresponding {@code @org.checkerframework.checker.lock.qual.GuardedBy} type
+   * qualifier into that AnnotatedTypeMirror.
+   *
+   * @param element any Element (this method does nothing if the Element is not for a field
+   *     declaration)
+   * @param atm the AnnotatedTypeMirror for element - the {@code @GuardedBy} type qualifier will be
+   *     inserted here
+   */
+  private void translateJcipAndJavaxAnnotations(Element element, AnnotatedTypeMirror atm) {
+    if (!element.getKind().isField()) {
+      return;
+    }
+
+    AnnotationMirror anno = null;
+
+    if (jcipGuardedBy != null) {
+      anno = getDeclAnnotation(element, jcipGuardedBy);
+    }
+
+    if (anno == null && javaxGuardedBy != null) {
+      anno = getDeclAnnotation(element, javaxGuardedBy);
+    }
+
+    if (anno == null) {
+      return;
+    }
+
+    // The version of javax.annotation.concurrent.GuardedBy included with the Checker Framework
+    // declares the type of value as an array of Strings, whereas the one defined in JCIP and
+    // included with FindBugs declares it as a String. So, the code below figures out which type
+    // should be used.
+    Map<? extends ExecutableElement, ? extends AnnotationValue> valmap = anno.getElementValues();
+    Object value = null;
+    for (ExecutableElement elem : valmap.keySet()) {
+      if (elem.getSimpleName().contentEquals("value")) {
+        value = valmap.get(elem).getValue();
+        break;
+      }
+    }
+    List<String> lockExpressions;
+    if (value instanceof List) {
+      @SuppressWarnings("unchecked")
+      List<AnnotationValue> la = (List<AnnotationValue>) value;
+      lockExpressions = CollectionsPlume.mapList((AnnotationValue a) -> (String) a.getValue(), la);
+    } else if (value instanceof String) {
+      lockExpressions = Collections.singletonList((String) value);
+    } else {
+      return;
+    }
+
+    if (lockExpressions.isEmpty()) {
+      atm.addAnnotation(GUARDEDBY);
+    } else {
+      atm.addAnnotation(createGuardedByAnnotationMirror(lockExpressions));
+    }
+  }
+
+  /**
+   * @param values a list of lock expressions
+   * @return an AnnotationMirror corresponding to @GuardedBy(values)
+   */
+  private AnnotationMirror createGuardedByAnnotationMirror(List<String> values) {
+    AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), GuardedBy.class);
+    builder.setValue("value", values.toArray());
+
+    // Return the resulting AnnotationMirror
+    return builder.build();
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockChecker.java b/checker/src/main/java/org/checkerframework/checker/lock/LockChecker.java
new file mode 100644
index 0000000..ec5291e
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/lock/LockChecker.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.lock;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+// TODO: Here is a list of nice-to-have features/tests but not critical to release the Lock Checker:
+
+// Add a warning if a user annotates a static field with @GuardedBy("this") instead of
+// @GuardedBy("<class name>.class")
+
+// Add a test that @GuardedBy("<class name>.class") is never ambiguous given two classes with the
+// same name in two different packages.
+
+// In LockStore.updateForMethodCall, calling a method annotated with @MayReleaseLocks should not
+// always cause local variables' refinement to be reset to @GuardedByUnknown.
+// The current workaround is to explicitly annotate the local variable with the appropriate
+// annotation in the @GuardedBy hierarchy.
+
+// Would it be a useful feature for the Lock Checker to warn about missing unlock calls when there
+// is a call to .lock() in a method?
+// Or is this a common pattern to lock in one method and unlock in a different one?
+
+/**
+ * The Lock Checker.
+ *
+ * @checker_framework.manual #lock-checker Lock Checker
+ */
+public class LockChecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java b/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java
new file mode 100644
index 0000000..9f9ac01
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java
@@ -0,0 +1,258 @@
+package org.checkerframework.checker.lock;
+
+import java.util.ArrayList;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
+import org.checkerframework.dataflow.expression.ArrayAccess;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.MethodCall;
+import org.checkerframework.dataflow.expression.ThisReference;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * The Lock Store behaves like CFAbstractStore but requires the ability to insert exact annotations.
+ * This is because we want to be able to insert @LockPossiblyHeld to replace @LockHeld, which
+ * normally is not possible in CFAbstractStore since @LockHeld is more specific.
+ */
+public class LockStore extends CFAbstractStore<CFValue, LockStore> {
+
+  /**
+   * If true, indicates that the store refers to a point in the code inside a constructor or
+   * initializer. This is useful because constructors and initializers are special with regard to
+   * the set of locks that is considered to be held. For example, 'this' is considered to be held
+   * inside a constructor.
+   */
+  protected boolean inConstructorOrInitializer = false;
+
+  private final LockAnnotatedTypeFactory atypeFactory;
+
+  public LockStore(LockAnalysis analysis, boolean sequentialSemantics) {
+    super(analysis, sequentialSemantics);
+    this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory();
+  }
+
+  /** Copy constructor. */
+  public LockStore(LockAnalysis analysis, CFAbstractStore<CFValue, LockStore> other) {
+    super(other);
+    this.inConstructorOrInitializer = ((LockStore) other).inConstructorOrInitializer;
+    this.atypeFactory = ((LockStore) other).atypeFactory;
+  }
+
+  @Override
+  public LockStore leastUpperBound(LockStore other) {
+    LockStore newStore = super.leastUpperBound(other);
+
+    // Least upper bound of a boolean
+    newStore.inConstructorOrInitializer =
+        this.inConstructorOrInitializer && other.inConstructorOrInitializer;
+
+    return newStore;
+  }
+
+  /*
+   * Insert an annotation exactly, without regard to whether an annotation was already present.
+   * This is only done for @LockPossiblyHeld. This is not sound for other type qualifiers.
+   */
+  public void insertLockPossiblyHeld(JavaExpression je) {
+    if (je.containsUnknown()) {
+      // Expressions containing unknown expressions are not stored.
+      return;
+    }
+    if (je instanceof LocalVariable) {
+      LocalVariable localVar = (LocalVariable) je;
+      CFValue current = localVariableValues.get(localVar);
+      CFValue value = changeLockAnnoToTop(je, current);
+      if (value != null) {
+        localVariableValues.put(localVar, value);
+      }
+    } else if (je instanceof FieldAccess) {
+      FieldAccess fieldAcc = (FieldAccess) je;
+      CFValue current = fieldValues.get(fieldAcc);
+      CFValue value = changeLockAnnoToTop(je, current);
+      if (value != null) {
+        fieldValues.put(fieldAcc, value);
+      }
+    } else if (je instanceof MethodCall) {
+      MethodCall method = (MethodCall) je;
+      CFValue current = methodValues.get(method);
+      CFValue value = changeLockAnnoToTop(je, current);
+      if (value != null) {
+        methodValues.put(method, value);
+      }
+    } else if (je instanceof ArrayAccess) {
+      ArrayAccess arrayAccess = (ArrayAccess) je;
+      CFValue current = arrayValues.get(arrayAccess);
+      CFValue value = changeLockAnnoToTop(je, current);
+      if (value != null) {
+        arrayValues.put(arrayAccess, value);
+      }
+    } else if (je instanceof ThisReference) {
+      thisValue = changeLockAnnoToTop(je, thisValue);
+    } else if (je instanceof ClassName) {
+      ClassName className = (ClassName) je;
+      CFValue current = classValues.get(className);
+      CFValue value = changeLockAnnoToTop(je, current);
+      if (value != null) {
+        classValues.put(className, value);
+      }
+    } else {
+      // No other types of expressions need to be stored.
+    }
+  }
+
+  /**
+   * Makes a new CFValue with the same annotations as currentValue except that the annotation in the
+   * LockPossiblyHeld hierarchy is set to LockPossiblyHeld. If currentValue is null, then a new
+   * value is created where the annotation set is LockPossiblyHeld and GuardedByUnknown
+   */
+  private CFValue changeLockAnnoToTop(JavaExpression je, CFValue currentValue) {
+    if (currentValue == null) {
+      Set<AnnotationMirror> set = AnnotationUtils.createAnnotationSet();
+      set.add(atypeFactory.GUARDEDBYUNKNOWN);
+      set.add(atypeFactory.LOCKPOSSIBLYHELD);
+      return analysis.createAbstractValue(set, je.getType());
+    }
+
+    QualifierHierarchy hierarchy = atypeFactory.getQualifierHierarchy();
+    Set<AnnotationMirror> currentSet = currentValue.getAnnotations();
+    AnnotationMirror gb =
+        hierarchy.findAnnotationInHierarchy(currentSet, atypeFactory.GUARDEDBYUNKNOWN);
+    Set<AnnotationMirror> newSet = AnnotationUtils.createAnnotationSet();
+    newSet.add(atypeFactory.LOCKPOSSIBLYHELD);
+    if (gb != null) {
+      newSet.add(gb);
+    }
+    return analysis.createAbstractValue(newSet, currentValue.getUnderlyingType());
+  }
+
+  public void setInConstructorOrInitializer() {
+    inConstructorOrInitializer = true;
+  }
+
+  @Override
+  public @Nullable CFValue getValue(JavaExpression expr) {
+
+    if (inConstructorOrInitializer) {
+      // 'this' is automatically considered as being held in a constructor or initializer.
+      // The class name, however, is not.
+      if (expr instanceof ThisReference) {
+        initializeThisValue(atypeFactory.LOCKHELD, expr.getType());
+      } else if (expr instanceof FieldAccess) {
+        FieldAccess fieldAcc = (FieldAccess) expr;
+        if (!fieldAcc.isStatic() && fieldAcc.getReceiver() instanceof ThisReference) {
+          insertValue(fieldAcc.getReceiver(), atypeFactory.LOCKHELD);
+        }
+      }
+    }
+
+    return super.getValue(expr);
+  }
+
+  @Override
+  protected String internalVisualize(CFGVisualizer<CFValue, LockStore, ?> viz) {
+    return viz.visualizeStoreKeyVal("inConstructorOrInitializer", inConstructorOrInitializer)
+        + viz.getSeparator()
+        + super.internalVisualize(viz);
+  }
+
+  @Override
+  protected boolean isSideEffectFree(AnnotatedTypeFactory atypeFactory, ExecutableElement method) {
+    LockAnnotatedTypeFactory lockAnnotatedTypeFactory = (LockAnnotatedTypeFactory) atypeFactory;
+    SourceChecker checker = lockAnnotatedTypeFactory.getChecker();
+    return checker.hasOption("assumeSideEffectFree")
+        || checker.hasOption("assumePure")
+        || lockAnnotatedTypeFactory.methodSideEffectAnnotation(method, false)
+            == SideEffectAnnotation.RELEASESNOLOCKS
+        || super.isSideEffectFree(atypeFactory, method);
+  }
+
+  @Override
+  public void updateForMethodCall(
+      MethodInvocationNode n, AnnotatedTypeFactory atypeFactory, CFValue val) {
+    super.updateForMethodCall(n, atypeFactory, val);
+    ExecutableElement method = n.getTarget().getMethod();
+    // The following behavior is similar to setting the sideEffectsUnrefineAliases field of
+    // Lockannotatedtypefactory, but it affects only one of the two type hierarchies, so it
+    // cannot use that logic.
+    if (!isSideEffectFree(atypeFactory, method)) {
+      // After the call to super.updateForMethodCall, only final fields are left in fieldValues (if
+      // the method called is side-effecting). For the LockPossiblyHeld hierarchy, even a final
+      // field might be locked or unlocked by a side-effecting method.  So, final fields must be set
+      // to @LockPossiblyHeld, but the annotation in the GuardedBy hierarchy should not be changed.
+      for (FieldAccess field : new ArrayList<>(fieldValues.keySet())) {
+        CFValue newValue = changeLockAnnoToTop(field, fieldValues.get(field));
+        if (newValue != null) {
+          fieldValues.put(field, newValue);
+        } else {
+          fieldValues.remove(field);
+        }
+      }
+
+      // Local variables could also be unlocked via an alias
+      for (LocalVariable var : new ArrayList<>(localVariableValues.keySet())) {
+        CFValue newValue = changeLockAnnoToTop(var, localVariableValues.get(var));
+        if (newValue != null) {
+          localVariableValues.put(var, newValue);
+        }
+      }
+
+      if (thisValue != null) {
+        thisValue = changeLockAnnoToTop(null, thisValue);
+      }
+    }
+  }
+
+  boolean hasLockHeld(CFValue value) {
+    return AnnotationUtils.containsSame(value.getAnnotations(), atypeFactory.LOCKHELD);
+  }
+
+  boolean hasLockPossiblyHeld(CFValue value) {
+    return AnnotationUtils.containsSame(value.getAnnotations(), atypeFactory.LOCKPOSSIBLYHELD);
+  }
+
+  @Override
+  public void insertValue(
+      JavaExpression je, @Nullable CFValue value, boolean permitNondeterministic) {
+    if (!shouldInsert(je, value, permitNondeterministic)) {
+      return;
+    }
+
+    // Even with concurrent semantics enabled, a @LockHeld value must always be
+    // stored for fields and @Pure method calls. This is sound because:
+    //  * Another thread can never release the lock on the current thread, and
+    //  * Locks are assumed to be effectively final, hence another thread will not
+    //    side effect the lock expression that has value @LockHeld.
+    if (hasLockHeld(value)) {
+      if (je instanceof FieldAccess) {
+        FieldAccess fieldAcc = (FieldAccess) je;
+        CFValue oldValue = fieldValues.get(fieldAcc);
+        CFValue newValue = value.mostSpecific(oldValue, null);
+        if (newValue != null) {
+          fieldValues.put(fieldAcc, newValue);
+        }
+      } else if (je instanceof MethodCall) {
+        MethodCall method = (MethodCall) je;
+        CFValue oldValue = methodValues.get(method);
+        CFValue newValue = value.mostSpecific(oldValue, null);
+        if (newValue != null) {
+          methodValues.put(method, newValue);
+        }
+      }
+    }
+
+    super.insertValue(je, value, permitNondeterministic);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockTransfer.java b/checker/src/main/java/org/checkerframework/checker/lock/LockTransfer.java
new file mode 100644
index 0000000..7bfd556
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/lock/LockTransfer.java
@@ -0,0 +1,153 @@
+package org.checkerframework.checker.lock;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.MethodTree;
+import java.util.List;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.Kind;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.SynchronizedNode;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAbstractTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * LockTransfer handles constructors, initializers, synchronized methods, and synchronized blocks.
+ */
+public class LockTransfer extends CFAbstractTransfer<CFValue, LockStore, LockTransfer> {
+  /** The type factory associated with this transfer function. */
+  private final LockAnnotatedTypeFactory atypeFactory;
+
+  /**
+   * Create a transfer function for the Lock Checker.
+   *
+   * @param analysis the analysis this transfer function belongs to
+   * @param checker the type-checker this transfer function belongs to
+   */
+  public LockTransfer(LockAnalysis analysis, LockChecker checker) {
+    // Always run the Lock Checker with -AconcurrentSemantics turned on.
+    super(analysis, /*useConcurrentSemantics=*/ true);
+    this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory();
+  }
+
+  /**
+   * Sets a given {@link Node} to @LockHeld in the given {@code store}.
+   *
+   * @param store the store to update
+   * @param node the node that should be @LockHeld
+   */
+  protected void makeLockHeld(LockStore store, Node node) {
+    JavaExpression internalRepr = JavaExpression.fromNode(node);
+    store.insertValue(internalRepr, atypeFactory.LOCKHELD);
+  }
+
+  /**
+   * Sets a given {@link Node} to @LockPossiblyHeld in the given {@code store}.
+   *
+   * @param store the store to update
+   * @param node the node that should be @LockPossiblyHeld
+   */
+  protected void makeLockPossiblyHeld(LockStore store, Node node) {
+    JavaExpression internalRepr = JavaExpression.fromNode(node);
+
+    // insertValue cannot change an annotation to a less specific type (e.g. LockHeld to
+    // LockPossiblyHeld), so insertLockPossiblyHeld is called.
+    store.insertLockPossiblyHeld(internalRepr);
+  }
+
+  /** Sets a given {@link Node} {@code node} to LockHeld in the given {@link TransferResult}. */
+  protected void makeLockHeld(TransferResult<CFValue, LockStore> result, Node node) {
+    if (result.containsTwoStores()) {
+      makeLockHeld(result.getThenStore(), node);
+      makeLockHeld(result.getElseStore(), node);
+    } else {
+      makeLockHeld(result.getRegularStore(), node);
+    }
+  }
+
+  /**
+   * Sets a given {@link Node} {@code node} to LockPossiblyHeld in the given {@link TransferResult}.
+   */
+  protected void makeLockPossiblyHeld(TransferResult<CFValue, LockStore> result, Node node) {
+    if (result.containsTwoStores()) {
+      makeLockPossiblyHeld(result.getThenStore(), node);
+      makeLockPossiblyHeld(result.getElseStore(), node);
+    } else {
+      makeLockPossiblyHeld(result.getRegularStore(), node);
+    }
+  }
+
+  @Override
+  public LockStore initialStore(UnderlyingAST underlyingAST, List<LocalVariableNode> parameters) {
+
+    LockStore store = super.initialStore(underlyingAST, parameters);
+
+    Kind astKind = underlyingAST.getKind();
+
+    // Methods with the 'synchronized' modifier are holding the 'this' lock.
+
+    // There is a subtle difference between synchronized methods and constructors/initializers. A
+    // synchronized method is only taking the intrinsic lock of the current object. It says nothing
+    // about any fields of the current object.
+
+    // Furthermore, since the current object already exists, other objects may be guarded by the
+    // current object. So a synchronized method can affect the locking behavior of other objects.
+
+    // A constructor/initializer behaves as if the current object and all its non-static fields were
+    // held as locks. But in reality no locks are held.
+
+    // Furthermore, since the current object is being constructed, no other object can be guarded by
+    // it or any of its non-static fields.
+
+    // Handle synchronized methods and constructors.
+    if (astKind == Kind.METHOD) {
+      CFGMethod method = (CFGMethod) underlyingAST;
+      MethodTree methodTree = method.getMethod();
+
+      ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree);
+
+      if (methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) {
+        final ClassTree classTree = method.getClassTree();
+        TypeMirror classType = TreeUtils.typeOf(classTree);
+
+        if (methodElement.getModifiers().contains(Modifier.STATIC)) {
+          store.insertValue(new ClassName(classType), atypeFactory.LOCKHELD);
+        } else {
+          store.insertThisValue(atypeFactory.LOCKHELD, classType);
+        }
+      } else if (methodElement.getKind() == ElementKind.CONSTRUCTOR) {
+        store.setInConstructorOrInitializer();
+      }
+    } else if (astKind == Kind.ARBITRARY_CODE) { // Handle initializers
+      store.setInConstructorOrInitializer();
+    }
+
+    return store;
+  }
+
+  @Override
+  public TransferResult<CFValue, LockStore> visitSynchronized(
+      SynchronizedNode n, TransferInput<CFValue, LockStore> p) {
+
+    TransferResult<CFValue, LockStore> result = super.visitSynchronized(n, p);
+
+    // Handle the entering and leaving of the synchronized block
+    if (n.getIsStartOfBlock()) {
+      makeLockHeld(result, n.getExpression());
+    } else {
+      makeLockPossiblyHeld(result, n.getExpression());
+    }
+
+    return result;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockTreeAnnotator.java b/checker/src/main/java/org/checkerframework/checker/lock/LockTreeAnnotator.java
new file mode 100644
index 0000000..8e6bf8a
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/lock/LockTreeAnnotator.java
@@ -0,0 +1,66 @@
+package org.checkerframework.checker.lock;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.Tree.Kind;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.TypesUtils;
+
+public class LockTreeAnnotator extends TreeAnnotator {
+
+  public LockTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+    super(atypeFactory);
+  }
+
+  @Override
+  public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
+    // For any binary operation whose LHS or RHS can be a non-boolean type, and whose resulting type
+    // is necessarily boolean, the resulting annotation on the boolean type must be @GuardedBy({}).
+
+    // There is no need to enforce that the annotation on the result of &&, ||, etc.  is
+    // @GuardedBy({}) since for such operators, both operands are of type @GuardedBy({}) boolean to
+    // begin with.
+
+    if (isBinaryComparisonOrInstanceOfOperator(node.getKind())
+        || TypesUtils.isString(type.getUnderlyingType())) {
+      // A boolean or String is always @GuardedBy({}). LockVisitor determines whether
+      // the LHS and RHS of this operation can be legally dereferenced.
+      type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).GUARDEDBY);
+
+      return null;
+    }
+
+    return super.visitBinary(node, type);
+  }
+
+  /** Indicates that the result of the operation is a boolean value. */
+  private static boolean isBinaryComparisonOrInstanceOfOperator(Kind opKind) {
+    switch (opKind) {
+      case EQUAL_TO:
+      case NOT_EQUAL_TO:
+        // Technically, <=, <, > and >= are irrelevant for visitBinary, since currently
+        // boxed primitives cannot be annotated with @GuardedBy(...), but they are left here
+        // in case that rule changes.
+      case LESS_THAN:
+      case LESS_THAN_EQUAL:
+      case GREATER_THAN:
+      case GREATER_THAN_EQUAL:
+      case INSTANCE_OF:
+        return true;
+      default:
+    }
+
+    return false;
+  }
+
+  @Override
+  public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
+    if (TypesUtils.isString(type.getUnderlyingType())) {
+      type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).GUARDEDBY);
+    }
+
+    return super.visitCompoundAssignment(node, type);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java b/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java
new file mode 100644
index 0000000..f9c1514
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java
@@ -0,0 +1,1251 @@
+package org.checkerframework.checker.lock;
+
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.SynchronizedTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation;
+import org.checkerframework.checker.lock.qual.EnsuresLockHeld;
+import org.checkerframework.checker.lock.qual.EnsuresLockHeldIf;
+import org.checkerframework.checker.lock.qual.GuardSatisfied;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.GuardedByBottom;
+import org.checkerframework.checker.lock.qual.GuardedByUnknown;
+import org.checkerframework.checker.lock.qual.Holding;
+import org.checkerframework.checker.lock.qual.LockHeld;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.Unknown;
+import org.checkerframework.dataflow.qual.Deterministic;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.flow.CFAbstractValue;
+import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.framework.util.StringToJavaExpression;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesError;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * The LockVisitor enforces the special type-checking rules described in the Lock Checker manual
+ * chapter.
+ *
+ * @checker_framework.manual #lock-checker Lock Checker
+ */
+public class LockVisitor extends BaseTypeVisitor<LockAnnotatedTypeFactory> {
+  private final Class<? extends Annotation> checkerGuardedByClass = GuardedBy.class;
+  private final Class<? extends Annotation> checkerGuardSatisfiedClass = GuardSatisfied.class;
+
+  protected static final Pattern SELF_RECEIVER_PATTERN = Pattern.compile("^<self>(\\.(.*))?$");
+
+  public LockVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  public Void visitVariable(VariableTree node, Void p) { // visit a variable declaration
+    // A user may not annotate a primitive type, a boxed primitive type or a String
+    // with any qualifier from the @GuardedBy hierarchy.
+    // They are immutable, so there is no need to guard them.
+
+    TypeMirror tm = TreeUtils.typeOf(node);
+
+    if (TypesUtils.isBoxedPrimitive(tm) || TypesUtils.isPrimitive(tm) || TypesUtils.isString(tm)) {
+      AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(node);
+      if (atm.hasExplicitAnnotationRelaxed(atypeFactory.GUARDSATISFIED)
+          || atm.hasExplicitAnnotationRelaxed(atypeFactory.GUARDEDBY)
+          || atm.hasExplicitAnnotation(atypeFactory.GUARDEDBYUNKNOWN)
+          || atm.hasExplicitAnnotation(atypeFactory.GUARDEDBYBOTTOM)) {
+        checker.reportError(node, "immutable.type.guardedby");
+      }
+    }
+
+    issueErrorIfMoreThanOneGuardedByAnnotationPresent(node);
+
+    return super.visitVariable(node, p);
+  }
+
+  /**
+   * Issues an error if two or more of the following annotations are present on a variable
+   * declaration.
+   *
+   * <ul>
+   *   <li>{@code @org.checkerframework.checker.lock.qual.GuardedBy}
+   *   <li>{@code @net.jcip.annotations.GuardedBy}
+   *   <li>{@code @javax.annotation.concurrent.GuardedBy}
+   * </ul>
+   *
+   * @param variableTree the VariableTree for the variable declaration used to determine if
+   *     multiple @GuardedBy annotations are present and to report the error
+   */
+  private void issueErrorIfMoreThanOneGuardedByAnnotationPresent(VariableTree variableTree) {
+    int guardedByAnnotationCount = 0;
+
+    List<AnnotationMirror> annos =
+        TreeUtils.annotationsFromTypeAnnotationTrees(variableTree.getModifiers().getAnnotations());
+    for (AnnotationMirror anno : annos) {
+      if (atypeFactory.areSameByClass(anno, GuardedBy.class)
+          || AnnotationUtils.areSameByName(anno, "net.jcip.annotations.GuardedBy")
+          || AnnotationUtils.areSameByName(anno, "javax.annotation.concurrent.GuardedBy")) {
+        guardedByAnnotationCount++;
+        if (guardedByAnnotationCount > 1) {
+          checker.reportError(variableTree, "multiple.guardedby.annotations");
+          return;
+        }
+      }
+    }
+  }
+
+  @Override
+  public LockAnnotatedTypeFactory createTypeFactory() {
+    return new LockAnnotatedTypeFactory(checker);
+  }
+
+  /**
+   * Issues an error if a method (explicitly or implicitly) annotated with @MayReleaseLocks has a
+   * formal parameter or receiver (explicitly or implicitly) annotated with @GuardSatisfied. Also
+   * issues an error if a synchronized method has a @LockingFree, @SideEffectFree, or @Pure
+   * annotation.
+   *
+   * @param node the MethodTree of the method definition to visit
+   */
+  @Override
+  public Void visitMethod(MethodTree node, Void p) {
+    ExecutableElement methodElement = TreeUtils.elementFromDeclaration(node);
+
+    issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent(methodElement, node);
+
+    SideEffectAnnotation sea = atypeFactory.methodSideEffectAnnotation(methodElement, true);
+
+    if (sea == SideEffectAnnotation.MAYRELEASELOCKS) {
+      boolean issueGSwithMRLWarning = false;
+
+      VariableTree receiver = node.getReceiverParameter();
+      if (receiver != null) {
+        if (atypeFactory.getAnnotatedType(receiver).hasAnnotation(checkerGuardSatisfiedClass)) {
+          issueGSwithMRLWarning = true;
+        }
+      }
+
+      if (!issueGSwithMRLWarning) { // Skip loop if we already decided to issue the warning.
+        for (VariableTree vt : node.getParameters()) {
+          if (atypeFactory.getAnnotatedType(vt).hasAnnotation(checkerGuardSatisfiedClass)) {
+            issueGSwithMRLWarning = true;
+            break;
+          }
+        }
+      }
+
+      if (issueGSwithMRLWarning) {
+        checker.reportError(node, "guardsatisfied.with.mayreleaselocks");
+      }
+    }
+
+    // Issue an error if a non-constructor method definition has a return type of
+    // @GuardSatisfied without an index.
+    if (methodElement != null && methodElement.getKind() != ElementKind.CONSTRUCTOR) {
+      AnnotatedTypeMirror returnTypeATM = atypeFactory.getAnnotatedType(node).getReturnType();
+
+      if (returnTypeATM != null && returnTypeATM.hasAnnotation(GuardSatisfied.class)) {
+        int returnGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(returnTypeATM);
+
+        if (returnGuardSatisfiedIndex == -1) {
+          checker.reportError(node, "guardsatisfied.return.must.have.index");
+        }
+      }
+    }
+
+    if (!sea.isWeakerThan(SideEffectAnnotation.LOCKINGFREE)
+        && methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) {
+      checker.reportError(node, "lockingfree.synchronized.method", sea);
+    }
+
+    return super.visitMethod(node, p);
+  }
+
+  /**
+   * Issues an error if two or more of the following annotations are present on a method.
+   *
+   * <ul>
+   *   <li>{@code @Holding}
+   *   <li>{@code @net.jcip.annotations.GuardedBy}
+   *   <li>{@code @javax.annotation.concurrent.GuardedBy}
+   * </ul>
+   *
+   * @param methodElement the ExecutableElement for the method call referred to by {@code node}
+   * @param treeForErrorReporting the MethodTree used to report the error
+   */
+  private void issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent(
+      ExecutableElement methodElement, MethodTree treeForErrorReporting) {
+    int lockPreconditionAnnotationCount = 0;
+
+    if (atypeFactory.getDeclAnnotation(methodElement, Holding.class) != null) {
+      lockPreconditionAnnotationCount++;
+    }
+
+    try {
+      if (atypeFactory.jcipGuardedBy != null
+          && atypeFactory.getDeclAnnotation(methodElement, atypeFactory.jcipGuardedBy) != null) {
+        lockPreconditionAnnotationCount++;
+      }
+
+      if (lockPreconditionAnnotationCount < 2
+          && atypeFactory.javaxGuardedBy != null
+          && atypeFactory.getDeclAnnotation(methodElement, atypeFactory.javaxGuardedBy) != null) {
+        lockPreconditionAnnotationCount++;
+      }
+    } catch (Exception e) {
+      // Ignore exceptions from Class.forName
+    }
+
+    if (lockPreconditionAnnotationCount > 1) {
+      checker.reportError(treeForErrorReporting, "multiple.lock.precondition.annotations");
+    }
+  }
+
+  /**
+   * When visiting a method call, if the receiver formal parameter has type @GuardSatisfied and the
+   * receiver actual parameter has type @GuardedBy(...), this method verifies that the guard is
+   * satisfied, and it returns true, indicating that the receiver subtype check should be skipped.
+   * If the receiver actual parameter has type @GuardSatisfied, this method simply returns true
+   * without performing any other actions. The method returns false otherwise.
+   *
+   * @param methodInvocationTree the MethodInvocationTree of the method being called
+   * @param methodDefinitionReceiver the ATM of the formal receiver parameter of the method being
+   *     called
+   * @param methodCallReceiver the ATM of the receiver argument of the method call
+   * @return whether the caller can skip the receiver subtype check
+   */
+  @Override
+  protected boolean skipReceiverSubtypeCheck(
+      MethodInvocationTree methodInvocationTree,
+      AnnotatedTypeMirror methodDefinitionReceiver,
+      AnnotatedTypeMirror methodCallReceiver) {
+
+    AnnotationMirror primaryGb =
+        methodCallReceiver.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN);
+    AnnotationMirror effectiveGb =
+        methodCallReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN);
+
+    // If the receiver actual parameter has type @GuardSatisfied, skip the subtype check.  Consider
+    // only a @GuardSatisfied primary annotation - hence use primaryGb instead of effectiveGb.
+    if (primaryGb != null && atypeFactory.areSameByClass(primaryGb, checkerGuardSatisfiedClass)) {
+      AnnotationMirror primaryGbOnMethodDefinition =
+          methodDefinitionReceiver.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN);
+      if (primaryGbOnMethodDefinition != null
+          && atypeFactory.areSameByClass(primaryGbOnMethodDefinition, checkerGuardSatisfiedClass)) {
+        return true;
+      }
+    }
+
+    if (atypeFactory.areSameByClass(effectiveGb, checkerGuardedByClass)) {
+      Set<AnnotationMirror> annos = methodDefinitionReceiver.getAnnotations();
+      AnnotationMirror guardSatisfied =
+          atypeFactory.getAnnotationByClass(annos, checkerGuardSatisfiedClass);
+      if (guardSatisfied != null) {
+        ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvocationTree);
+        if (receiverTree == null) {
+          checkLockOfImplicitThis(methodInvocationTree, effectiveGb);
+        } else {
+          checkLock(receiverTree, effectiveGb);
+        }
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  @Override
+  protected Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotations() {
+    Set<? extends AnnotationMirror> tops = atypeFactory.getQualifierHierarchy().getTopAnnotations();
+    Set<AnnotationMirror> annotationSet = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror anno : tops) {
+      if (AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYUNKNOWN)) {
+        annotationSet.add(atypeFactory.GUARDEDBY);
+      } else {
+        annotationSet.add(anno);
+      }
+    }
+    return annotationSet;
+  }
+
+  @Override
+  protected void checkConstructorResult(
+      AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {
+    // Newly created objects are guarded by nothing, so allow @GuardBy({}) on constructor results.
+    AnnotationMirror anno =
+        constructorType.getReturnType().getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN);
+    if (!AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBY)) {
+      super.checkConstructorResult(constructorType, constructorElement);
+    }
+  }
+
+  @Override
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      AnnotatedTypeMirror valueType,
+      Tree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+
+    Kind valueTreeKind = valueTree.getKind();
+
+    switch (valueTreeKind) {
+      case NEW_CLASS:
+      case NEW_ARRAY:
+        // Avoid issuing warnings for: @GuardedBy(<something>) Object o = new Object();
+        // Do NOT do this if the LHS is @GuardedByBottom.
+        if (!varType.hasAnnotation(GuardedByBottom.class)) {
+          return;
+        }
+        break;
+      case INT_LITERAL:
+      case LONG_LITERAL:
+      case FLOAT_LITERAL:
+      case DOUBLE_LITERAL:
+      case BOOLEAN_LITERAL:
+      case CHAR_LITERAL:
+      case STRING_LITERAL:
+        // Avoid issuing warnings for: @GuardedBy(<something>) Object o; o = <some literal>;
+        // Do NOT do this if the LHS is @GuardedByBottom.
+        if (!varType.hasAnnotation(GuardedByBottom.class)) {
+          return;
+        }
+        break;
+      default:
+    }
+
+    // In cases where assigning a value with a @GuardedBy annotation to a variable with a
+    // @GuardSatisfied annotation is legal, this is our last chance to check that the appropriate
+    // locks are held before the information in the @GuardedBy annotation is lost in the assignment
+    // to the variable annotated with @GuardSatisfied. See the discussion of @GuardSatisfied in the
+    // "Type-checking rules" section of the Lock Checker manual chapter for more details.
+
+    if (varType.hasAnnotation(GuardSatisfied.class)) {
+      if (valueType.hasAnnotation(GuardedBy.class)) {
+        checkLock(valueTree, valueType.getAnnotation(GuardedBy.class));
+        return;
+      } else if (valueType.hasAnnotation(GuardSatisfied.class)) {
+        // TODO: Find a cleaner, non-abstraction-breaking way to know whether method actual
+        // parameters are being assigned to formal parameters.
+
+        if (!errorKey.equals("argument")) {
+          // If both @GuardSatisfied have no index, the assignment is not allowed because
+          // the LHS and RHS expressions may be guarded by different lock expressions.
+          // The assignment is allowed when matching a formal parameter to an actual
+          // parameter (see the if block above).
+
+          int varTypeGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(varType);
+          int valueTypeGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(valueType);
+
+          if (varTypeGuardSatisfiedIndex == -1 && valueTypeGuardSatisfiedIndex == -1) {
+            checker.reportError(
+                valueTree, "guardsatisfied.assignment.disallowed", varType, valueType);
+          }
+        } else {
+          // The RHS can be @GuardSatisfied with a different index when matching method
+          // formal parameters to actual parameters.
+          // The actual matching is done in LockVisitor.visitMethodInvocation and a
+          // guardsatisfied.parameters.must.match error
+          // is issued if the parameters do not match exactly.
+          // Do nothing here, since there is no precondition to be checked on a
+          // @GuardSatisfied parameter.
+          // Note: this matching of a @GS(index) to a @GS(differentIndex) is *only*
+          // allowed when matching method formal parameters to actual parameters.
+
+          return;
+        }
+      } else if (!atypeFactory.getTypeHierarchy().isSubtype(valueType, varType)) {
+        // Special case: replace the @GuardSatisfied primary annotation on the LHS with
+        // @GuardedBy({}) and see if it type checks.
+
+        AnnotatedTypeMirror varType2 = varType.deepCopy(); // TODO: Would shallowCopy be sufficient?
+        varType2.replaceAnnotation(atypeFactory.GUARDEDBY);
+        if (atypeFactory.getTypeHierarchy().isSubtype(valueType, varType2)) {
+          return;
+        }
+      }
+    }
+
+    super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
+  }
+
+  @Override
+  public Void visitMemberSelect(MemberSelectTree tree, Void p) {
+    if (TreeUtils.isFieldAccess(tree)) {
+      AnnotatedTypeMirror atmOfReceiver = atypeFactory.getAnnotatedType(tree.getExpression());
+      // The atmOfReceiver for "void.class" is TypeKind.VOID, which isn't annotated so avoid it.
+      if (atmOfReceiver.getKind() != TypeKind.VOID) {
+        AnnotationMirror gb =
+            atmOfReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN);
+        checkLock(tree.getExpression(), gb);
+      }
+    }
+
+    return super.visitMemberSelect(tree, p);
+  }
+
+  private void reportFailure(
+      @CompilerMessageKey String messageKey,
+      MethodTree overriderTree,
+      AnnotatedDeclaredType enclosingType,
+      AnnotatedExecutableType overridden,
+      AnnotatedDeclaredType overriddenType,
+      List<String> overriderLocks,
+      List<String> overriddenLocks) {
+    // Get the type of the overriding method.
+    AnnotatedExecutableType overrider = atypeFactory.getAnnotatedType(overriderTree);
+
+    if (overrider.getTypeVariables().isEmpty() && !overridden.getTypeVariables().isEmpty()) {
+      overridden = overridden.getErased();
+    }
+    String overriderMeth = overrider.toString();
+    String overriderTyp = enclosingType.getUnderlyingType().asElement().toString();
+    String overriddenMeth = overridden.toString();
+    String overriddenTyp = overriddenType.getUnderlyingType().asElement().toString();
+
+    if (overriderLocks == null || overriddenLocks == null) {
+      checker.reportError(
+          overriderTree, messageKey, overriderTyp, overriderMeth, overriddenTyp, overriddenMeth);
+    } else {
+      checker.reportError(
+          overriderTree,
+          messageKey,
+          overriderTyp,
+          overriderMeth,
+          overriddenTyp,
+          overriddenMeth,
+          overriderLocks,
+          overriddenLocks);
+    }
+  }
+
+  /**
+   * Ensures that subclass methods are annotated with a stronger or equally strong side effect
+   * annotation than the parent class method.
+   */
+  @Override
+  protected boolean checkOverride(
+      MethodTree overriderTree,
+      AnnotatedDeclaredType enclosingType,
+      AnnotatedExecutableType overriddenMethodType,
+      AnnotatedDeclaredType overriddenType) {
+
+    boolean isValid = true;
+
+    SideEffectAnnotation seaOfOverriderMethod =
+        atypeFactory.methodSideEffectAnnotation(
+            TreeUtils.elementFromDeclaration(overriderTree), false);
+    SideEffectAnnotation seaOfOverridenMethod =
+        atypeFactory.methodSideEffectAnnotation(overriddenMethodType.getElement(), false);
+
+    if (seaOfOverriderMethod.isWeakerThan(seaOfOverridenMethod)) {
+      isValid = false;
+      reportFailure(
+          "override.sideeffect",
+          overriderTree,
+          enclosingType,
+          overriddenMethodType,
+          overriddenType,
+          null,
+          null);
+    }
+
+    return super.checkOverride(overriderTree, enclosingType, overriddenMethodType, overriddenType)
+        && isValid;
+  }
+
+  @Override
+  public Void visitArrayAccess(ArrayAccessTree tree, Void p) {
+    AnnotatedTypeMirror atmOfReceiver = atypeFactory.getAnnotatedType(tree.getExpression());
+    AnnotationMirror gb =
+        atmOfReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN);
+    checkLock(tree.getExpression(), gb);
+    return super.visitArrayAccess(tree, p);
+  }
+
+  /**
+   * Skips the call to super and returns true.
+   *
+   * <p>{@code GuardedBy({})} is the default type on class declarations, which is a subtype of the
+   * top annotation {@code @GuardedByUnknown}. However, it is valid to declare an instance of a
+   * class with any annotation from the {@code @GuardedBy} hierarchy. Hence, this method returns
+   * true for annotations in the {@code @GuardedBy} hierarchy.
+   *
+   * <p>Also returns true for annotations in the {@code @LockPossiblyHeld} hierarchy since the
+   * default for that hierarchy is the top type and annotations from that hierarchy cannot be
+   * explicitly written in code.
+   */
+  @Override
+  public boolean isValidUse(
+      AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) {
+    return true;
+  }
+
+  /**
+   * When visiting a method invocation, issue an error if the side effect annotation on the called
+   * method causes the side effect guarantee of the enclosing method to be violated. For example, a
+   * method annotated with @ReleasesNoLocks may not call a method annotated with @MayReleaseLocks.
+   * Also check that matching @GuardSatisfied(index) on a method's formal receiver/parameters
+   * matches those in corresponding locations on the method call site.
+   *
+   * @param node the MethodInvocationTree of the method call being visited
+   */
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+    ExecutableElement methodElement = TreeUtils.elementFromUse(node);
+
+    SideEffectAnnotation seaOfInvokedMethod =
+        atypeFactory.methodSideEffectAnnotation(methodElement, false);
+
+    MethodTree enclosingMethod = TreePathUtil.enclosingMethod(atypeFactory.getPath(node));
+
+    ExecutableElement enclosingMethodElement = null;
+    if (enclosingMethod != null) {
+      enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod);
+    }
+
+    if (enclosingMethodElement != null) {
+      SideEffectAnnotation seaOfContainingMethod =
+          atypeFactory.methodSideEffectAnnotation(enclosingMethodElement, false);
+
+      if (seaOfInvokedMethod.isWeakerThan(seaOfContainingMethod)) {
+        checker.reportError(
+            node,
+            "method.guarantee.violated",
+            seaOfContainingMethod.getNameOfSideEffectAnnotation(),
+            enclosingMethodElement.getSimpleName(),
+            methodElement.getSimpleName(),
+            seaOfInvokedMethod.getNameOfSideEffectAnnotation());
+      }
+    }
+
+    if (methodElement != null) {
+      // Handle releasing of explicit locks. Verify that the lock expression is effectively final.
+      ExpressionTree receiverTree = TreeUtils.getReceiverTree(node);
+
+      ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal(methodElement, receiverTree);
+
+      // Handle acquiring of explicit locks. Verify that the lock expression is effectively final.
+
+      // If the method causes expression "this" or "#1" to be locked, verify that those expressions
+      // are effectively final.  TODO: generalize to any expression. This is currently designed only
+      // to support methods in ReentrantLock and ReentrantReadWriteLock (which use the "this"
+      // expression), as well as Thread.holdsLock (which uses the "#1" expression).
+
+      AnnotationMirror ensuresLockHeldAnno =
+          atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeld.class);
+
+      List<String> expressions = new ArrayList<>();
+      if (ensuresLockHeldAnno != null) {
+        expressions.addAll(
+            AnnotationUtils.getElementValueArray(
+                ensuresLockHeldAnno, atypeFactory.ensuresLockHeldValueElement, String.class));
+      }
+
+      AnnotationMirror ensuresLockHeldIfAnno =
+          atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeldIf.class);
+
+      if (ensuresLockHeldIfAnno != null) {
+        expressions.addAll(
+            AnnotationUtils.getElementValueArray(
+                ensuresLockHeldIfAnno,
+                atypeFactory.ensuresLockHeldIfExpressionElement,
+                String.class));
+      }
+
+      for (String expr : expressions) {
+        if (expr.equals("this")) {
+          // receiverTree will be null for implicit this, or class name receivers. But they are also
+          // final. So nothing to be checked for them.
+          if (receiverTree != null) {
+            ensureExpressionIsEffectivelyFinal(receiverTree);
+          }
+        } else if (expr.equals("#1")) {
+          ExpressionTree firstParameter = node.getArguments().get(0);
+          if (firstParameter != null) {
+            ensureExpressionIsEffectivelyFinal(firstParameter);
+          }
+        }
+      }
+    }
+
+    // Check that matching @GuardSatisfied(index) on a method's formal receiver/parameters
+    // matches those in corresponding locations on the method call site.
+
+    ParameterizedExecutableType mType = atypeFactory.methodFromUse(node);
+    AnnotatedExecutableType invokedMethod = mType.executableType;
+
+    List<AnnotatedTypeMirror> requiredArgs =
+        AnnotatedTypes.expandVarArgs(atypeFactory, invokedMethod, node.getArguments());
+
+    // Index on @GuardSatisfied at each location. -1 when no @GuardSatisfied annotation was present.
+    // Note that @GuardSatisfied with no index is normally represented as having index -1.
+    // We would like to ignore a @GuardSatisfied with no index for these purposes, so if it is
+    // encountered we leave its index as -1.
+    // The first element of the array is reserved for the receiver.
+    int guardSatisfiedIndex[] =
+        new int[requiredArgs.size() + 1]; // + 1 for the receiver parameter type
+
+    // Retrieve receiver types from method definition and method call
+
+    guardSatisfiedIndex[0] = -1;
+
+    AnnotatedTypeMirror methodDefinitionReceiver = null;
+    AnnotatedTypeMirror methodCallReceiver = null;
+
+    ExecutableElement invokedMethodElement = invokedMethod.getElement();
+    if (!ElementUtils.isStatic(invokedMethodElement)
+        && invokedMethod.getElement().getKind() != ElementKind.CONSTRUCTOR) {
+      methodDefinitionReceiver = invokedMethod.getReceiverType();
+      if (methodDefinitionReceiver != null
+          && methodDefinitionReceiver.hasAnnotation(checkerGuardSatisfiedClass)) {
+        guardSatisfiedIndex[0] = atypeFactory.getGuardSatisfiedIndex(methodDefinitionReceiver);
+        methodCallReceiver = atypeFactory.getReceiverType(node);
+      }
+    }
+
+    // Retrieve formal parameter types from the method definition
+
+    for (int i = 0; i < requiredArgs.size(); i++) {
+      guardSatisfiedIndex[i + 1] = -1;
+
+      AnnotatedTypeMirror arg = requiredArgs.get(i);
+
+      if (arg.hasAnnotation(checkerGuardSatisfiedClass)) {
+        guardSatisfiedIndex[i + 1] = atypeFactory.getGuardSatisfiedIndex(arg);
+      }
+    }
+
+    // Combine all of the actual parameters into one list of AnnotationMirrors
+
+    ArrayList<AnnotationMirror> passedArgAnnotations = new ArrayList<>(guardSatisfiedIndex.length);
+    passedArgAnnotations.add(
+        methodCallReceiver == null
+            ? null
+            : methodCallReceiver.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN));
+    for (ExpressionTree tree : node.getArguments()) {
+      passedArgAnnotations.add(
+          atypeFactory
+              .getAnnotatedType(tree)
+              .getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN));
+    }
+
+    // Perform the validity check and issue an error if not valid.
+
+    for (int i = 0; i < guardSatisfiedIndex.length; i++) {
+      if (guardSatisfiedIndex[i] != -1) {
+        for (int j = i + 1; j < guardSatisfiedIndex.length; j++) {
+          if (guardSatisfiedIndex[i] == guardSatisfiedIndex[j]) {
+            // The @GuardedBy/@GuardSatisfied/@GuardedByUnknown/@GuardedByBottom
+            // annotations must be identical on the corresponding actual parameters.
+            AnnotationMirror arg1Anno = passedArgAnnotations.get(i);
+            AnnotationMirror arg2Anno = passedArgAnnotations.get(j);
+            if (arg1Anno != null && arg2Anno != null) {
+              boolean bothAreGSwithNoIndex = false;
+
+              if (atypeFactory.areSameByClass(arg1Anno, checkerGuardSatisfiedClass)
+                  && atypeFactory.areSameByClass(arg2Anno, checkerGuardSatisfiedClass)) {
+                if (atypeFactory.getGuardSatisfiedIndex(arg1Anno) == -1
+                    && atypeFactory.getGuardSatisfiedIndex(arg2Anno) == -1) {
+                  // Generally speaking, two @GuardSatisfied annotations with no
+                  // index are incomparable.
+                  // TODO: If they come from the same variable, they are
+                  // comparable.  Fix and add a test case.
+                  bothAreGSwithNoIndex = true;
+                }
+              }
+
+              if (bothAreGSwithNoIndex
+                  || !(atypeFactory.getQualifierHierarchy().isSubtype(arg1Anno, arg2Anno)
+                      || atypeFactory.getQualifierHierarchy().isSubtype(arg2Anno, arg1Anno))) {
+                // TODO: allow these strings to be localized
+
+                String formalParam1 = null;
+
+                if (i == 0) {
+                  formalParam1 = "The receiver type";
+                } else {
+                  formalParam1 = "Parameter #" + i; // i, not i-1, so the index is 1-based
+                }
+
+                String formalParam2 = "parameter #" + j; // j, not j-1, so the index is 1-based
+
+                checker.reportError(
+                    node,
+                    "guardsatisfied.parameters.must.match",
+                    formalParam1,
+                    formalParam2,
+                    invokedMethod.toString(),
+                    guardSatisfiedIndex[i],
+                    arg1Anno,
+                    arg2Anno);
+              }
+            }
+          }
+        }
+      }
+    }
+
+    return super.visitMethodInvocation(node, p);
+  }
+
+  /**
+   * Issues an error if the receiver of an unlock() call is not effectively final.
+   *
+   * @param methodElement the ExecutableElement for a method call to unlock()
+   * @param lockExpression the receiver tree for the method call to unlock(). Can be null.
+   */
+  private void ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal(
+      ExecutableElement methodElement, ExpressionTree lockExpression) {
+    if (lockExpression == null) {
+      // Implicit this, or class name receivers, are null. But they are also final. So nothing
+      // to be checked for them.
+      return;
+    }
+
+    if (!methodElement.getSimpleName().contentEquals("unlock")) {
+      return;
+    }
+
+    TypeMirror lockExpressionType = TreeUtils.typeOf(lockExpression);
+
+    ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment();
+
+    javax.lang.model.util.Types types = processingEnvironment.getTypeUtils();
+
+    // TODO: make a type declaration annotation for this rather than looking for the
+    // Lock.unlock() method explicitly.
+    TypeMirror lockInterfaceTypeMirror =
+        TypesUtils.typeFromClass(Lock.class, types, processingEnvironment.getElementUtils());
+
+    if (types.isSubtype(types.erasure(lockExpressionType), lockInterfaceTypeMirror)) {
+      ensureExpressionIsEffectivelyFinal(lockExpression);
+    }
+  }
+
+  /**
+   * When visiting a synchronized block, issue an error if the expression has a type that implements
+   * the java.util.concurrent.locks.Lock interface. This prevents explicit locks from being
+   * accidentally used as built-in (monitor) locks. This is important because the Lock Checker does
+   * not have a mechanism to separately keep track of the explicit lock and the monitor lock of an
+   * expression that implements the Lock interface (i.e. there is a @LockHeld annotation used in
+   * dataflow, but there are not distinct @MonitorLockHeld and @ExplicitLockHeld annotations). It is
+   * assumed that both kinds of locks will never be held for any expression that implements Lock.
+   *
+   * <p>Additionally, a synchronized block may not be present in a method that has a @LockingFree
+   * guarantee or stronger. An error is issued in this case.
+   *
+   * @param node the SynchronizedTree for the synchronized block being visited
+   */
+  @Override
+  public Void visitSynchronized(SynchronizedTree node, Void p) {
+    ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment();
+
+    javax.lang.model.util.Types types = processingEnvironment.getTypeUtils();
+
+    // TODO: make a type declaration annotation for this rather than looking for Lock.class
+    // explicitly.
+    TypeMirror lockInterfaceTypeMirror =
+        TypesUtils.typeFromClass(Lock.class, types, processingEnvironment.getElementUtils());
+
+    ExpressionTree synchronizedExpression = node.getExpression();
+
+    ensureExpressionIsEffectivelyFinal(synchronizedExpression);
+
+    TypeMirror expressionType =
+        types.erasure(atypeFactory.getAnnotatedType(synchronizedExpression).getUnderlyingType());
+
+    if (types.isSubtype(expressionType, lockInterfaceTypeMirror)) {
+      checker.reportError(node, "explicit.lock.synchronized");
+    }
+
+    MethodTree enclosingMethod = TreePathUtil.enclosingMethod(atypeFactory.getPath(node));
+
+    ExecutableElement methodElement = null;
+    if (enclosingMethod != null) {
+      methodElement = TreeUtils.elementFromDeclaration(enclosingMethod);
+
+      SideEffectAnnotation seaOfContainingMethod =
+          atypeFactory.methodSideEffectAnnotation(methodElement, false);
+
+      if (!seaOfContainingMethod.isWeakerThan(SideEffectAnnotation.LOCKINGFREE)) {
+        checker.reportError(
+            node, "synchronized.block.in.lockingfree.method", seaOfContainingMethod);
+      }
+    }
+
+    return super.visitSynchronized(node, p);
+  }
+
+  /**
+   * Ensures that each variable accessed in an expression is final or effectively final and that
+   * each called method in the expression is @Deterministic. Issues an error otherwise. Recursively
+   * performs this check on method arguments. Only intended to be used on the expression of a
+   * synchronized block.
+   *
+   * <p>Example: given the expression var1.field1.method1(var2.method2()).field2, var1, var2, field1
+   * and field2 are enforced to be final or effectively final, and method1 and method2 are enforced
+   * to be @Deterministic.
+   *
+   * @param lockExpressionTree the expression tree of a synchronized block
+   */
+  private void ensureExpressionIsEffectivelyFinal(final ExpressionTree lockExpressionTree) {
+    // This functionality could be implemented using a visitor instead, however with this design, it
+    // is easier to be certain that an error will always be issued if a tree kind is not recognized.
+    // Only the most common tree kinds for synchronized expressions are supported.
+
+    // Traverse the expression using 'tree', as 'lockExpressionTree' is used for error reporting.
+    ExpressionTree tree = lockExpressionTree;
+
+    while (true) {
+      tree = TreeUtils.withoutParens(tree);
+
+      switch (tree.getKind()) {
+        case MEMBER_SELECT:
+          if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) {
+            checker.reportError(tree, "lock.expression.not.final", lockExpressionTree);
+            return;
+          }
+          tree = ((MemberSelectTree) tree).getExpression();
+          break;
+        case IDENTIFIER:
+          if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) {
+            checker.reportError(tree, "lock.expression.not.final", lockExpressionTree);
+          }
+          return;
+        case METHOD_INVOCATION:
+          Element elem = TreeUtils.elementFromUse(tree);
+          if (atypeFactory.getDeclAnnotationNoAliases(elem, Deterministic.class) == null
+              && atypeFactory.getDeclAnnotationNoAliases(elem, Pure.class) == null) {
+            checker.reportError(tree, "lock.expression.not.final", lockExpressionTree);
+            return;
+          }
+
+          MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree;
+
+          for (ExpressionTree argTree : methodInvocationTree.getArguments()) {
+            ensureExpressionIsEffectivelyFinal(argTree);
+          }
+
+          tree = methodInvocationTree.getMethodSelect();
+          break;
+        default:
+          checker.reportError(tree, "lock.expression.possibly.not.final", lockExpressionTree);
+          return;
+      }
+    }
+  }
+
+  private void ensureExpressionIsEffectivelyFinal(
+      final JavaExpression lockExpr,
+      String expressionForErrorReporting,
+      Tree treeForErrorReporting) {
+    if (!atypeFactory.isExpressionEffectivelyFinal(lockExpr)) {
+      checker.reportError(
+          treeForErrorReporting, "lock.expression.not.final", expressionForErrorReporting);
+    }
+  }
+
+  @Override
+  public Void visitAnnotation(AnnotationTree tree, Void p) {
+    ArrayList<AnnotationTree> annotationTreeList = new ArrayList<>(1);
+    annotationTreeList.add(tree);
+    List<AnnotationMirror> amList =
+        TreeUtils.annotationsFromTypeAnnotationTrees(annotationTreeList);
+
+    if (amList != null) {
+      for (AnnotationMirror annotationMirror : amList) {
+        if (atypeFactory.areSameByClass(annotationMirror, checkerGuardSatisfiedClass)) {
+          issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation(tree);
+        }
+      }
+    }
+
+    return super.visitAnnotation(tree, p);
+  }
+
+  /**
+   * Issues an error if a GuardSatisfied annotation is found in a location other than a method
+   * return type or parameter (including the receiver).
+   *
+   * @param annotationTree AnnotationTree used for error reporting and to help determine that an
+   *     array parameter has no GuardSatisfied annotations except on the array type
+   */
+  // TODO: Remove this method once @TargetLocations are enforced (i.e. once
+  // issue https://github.com/typetools/checker-framework/issues/1919 is closed).
+  private void issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation(
+      AnnotationTree annotationTree) {
+    TreePath currentPath = getCurrentPath();
+    TreePath path = getPathForLocalVariableRetrieval(currentPath);
+    if (path != null) {
+      Tree tree = path.getLeaf();
+      Tree.Kind kind = tree.getKind();
+
+      if (kind == Tree.Kind.METHOD) {
+        // The @GuardSatisfied annotation is on the return type.
+        return;
+      } else if (kind == Tree.Kind.VARIABLE) {
+        VariableTree varTree = (VariableTree) tree;
+        Tree varTypeTree = varTree.getType();
+        if (varTypeTree != null) {
+          TreePath parentPath = path.getParentPath();
+          if (parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.METHOD) {
+            Tree.Kind varTypeTreeKind = varTypeTree.getKind();
+            if (varTypeTreeKind == Tree.Kind.ANNOTATED_TYPE) {
+              AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) varTypeTree;
+
+              if (annotatedTypeTree.getUnderlyingType().getKind() != Tree.Kind.ARRAY_TYPE
+                  || annotatedTypeTree.getAnnotations().contains(annotationTree)) {
+                // Method parameter
+                return;
+              }
+            } else if (varTypeTreeKind != Tree.Kind.ARRAY_TYPE) {
+              // Method parameter or receiver
+              return;
+            }
+          }
+        }
+      }
+    }
+
+    checker.reportError(annotationTree, "guardsatisfied.location.disallowed");
+  }
+
+  /**
+   * The JavaExpression parser requires a path for retrieving the scope that will be used to resolve
+   * local variables. One would expect that simply providing the path to an AnnotationTree would
+   * work, since the compiler (as called by the org.checkerframework.javacutil.Resolver class) could
+   * walk up the path from the AnnotationTree to determine the scope. Unfortunately this is not how
+   * the compiler works. One must provide the path at the right level (not so deep that it results
+   * in a symbol not being found, but not so high up that it is out of the scope at hand). This is a
+   * problem when trying to retrieve local variables, since one could silently miss a local variable
+   * in scope and accidentally retrieve a field with the same name. This method returns the correct
+   * path for this purpose, given a path to an AnnotationTree.
+   *
+   * <p>Note: this is definitely necessary for local variable retrieval. It has not been tested
+   * whether this is strictly necessary for fields or other identifiers.
+   *
+   * <p>Only call this method from visitAnnotation.
+   *
+   * @param path the TreePath whose leaf is an AnnotationTree
+   * @return a TreePath that can be passed to methods in the Resolver class to locate local
+   *     variables
+   */
+  private TreePath getPathForLocalVariableRetrieval(TreePath path) {
+    assert path.getLeaf() instanceof AnnotationTree;
+
+    // TODO: handle annotations in trees of kind NEW_CLASS (and add test coverage for this
+    // scenario).
+    // Currently an annotation in such a tree, such as "new @GuardedBy("foo") Object()",
+    // results in a constructor.invocation error. This must be fixed first.
+
+    path = path.getParentPath();
+
+    if (path == null) {
+      return null;
+    }
+
+    // A MODIFIERS tree for a VARIABLE or METHOD parent tree would be available at this level,
+    // but it is not directly handled. Instead, its parent tree (one level higher) is handled.
+    // Other tree kinds are also handled one level higher.
+
+    path = path.getParentPath();
+
+    if (path == null) {
+      return null;
+    }
+
+    Tree tree = path.getLeaf();
+    Tree.Kind kind = tree.getKind();
+
+    switch (kind) {
+      case ARRAY_TYPE:
+      case VARIABLE:
+      case TYPE_CAST:
+      case INSTANCE_OF:
+      case METHOD:
+      case NEW_ARRAY:
+      case TYPE_PARAMETER:
+        // TODO: visitAnnotation does not currently visit annotations on wildcard bounds.
+        // Address this for the Lock Checker somehow and enable these, as well as the
+        // corresponding test cases in ChapterExamples.java
+        // case EXTENDS_WILDCARD:
+        // case SUPER_WILDCARD:
+        return path;
+      default:
+        return null;
+    }
+  }
+
+  /**
+   * Returns true if the symbol for the given tree is final or effectively final. Package, class and
+   * method symbols are unmodifiable and therefore considered final.
+   */
+  private boolean isTreeSymbolEffectivelyFinalOrUnmodifiable(Tree tree) {
+    Element elem = TreeUtils.elementFromTree(tree);
+    ElementKind ek = elem.getKind();
+    return ek == ElementKind.PACKAGE
+        || ek == ElementKind.CLASS
+        || ek == ElementKind.METHOD
+        || ElementUtils.isEffectivelyFinal(elem);
+  }
+
+  @Override
+  @SuppressWarnings("interning:not.interned") // AST node comparison
+  public Void visitIdentifier(IdentifierTree tree, Void p) {
+    // If the identifier is a field accessed via an implicit this, then check the lock of this.
+    // (All other field accesses are checked in visitMemberSelect.)
+    if (TreeUtils.isFieldAccess(tree)) {
+      Tree parent = getCurrentPath().getParentPath().getLeaf();
+      // If the parent is not a member select, or if it is and the field is the expression,
+      // then the field is accessed via an implicit this.
+      if ((parent.getKind() != Kind.MEMBER_SELECT
+              || ((MemberSelectTree) parent).getExpression() == tree)
+          && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) {
+        AnnotationMirror guardedBy =
+            atypeFactory.getSelfType(tree).getAnnotationInHierarchy(atypeFactory.GUARDEDBY);
+        checkLockOfImplicitThis(tree, guardedBy);
+      }
+    }
+    return super.visitIdentifier(tree, p);
+  }
+
+  @Override
+  public Void visitBinary(BinaryTree binaryTree, Void p) {
+    if (TreeUtils.isStringConcatenation(binaryTree)) {
+      ExpressionTree leftTree = binaryTree.getLeftOperand();
+      ExpressionTree rightTree = binaryTree.getRightOperand();
+
+      boolean lhsIsString = TypesUtils.isString(TreeUtils.typeOf(leftTree));
+      boolean rhsIsString = TypesUtils.isString(TreeUtils.typeOf(rightTree));
+      if (!lhsIsString) {
+        checkPreconditionsForImplicitToStringCall(leftTree);
+      } else if (!rhsIsString) {
+        checkPreconditionsForImplicitToStringCall(rightTree);
+      }
+    }
+
+    return super.visitBinary(binaryTree, p);
+  }
+
+  @Override
+  public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
+    if (TreeUtils.isStringCompoundConcatenation(node)) {
+      ExpressionTree rightTree = node.getExpression();
+      if (!TypesUtils.isString(TreeUtils.typeOf(rightTree))) {
+        checkPreconditionsForImplicitToStringCall(rightTree);
+      }
+    }
+
+    return super.visitCompoundAssignment(node, p);
+  }
+
+  /**
+   * Checks precondition for {@code tree} that is known to be the receiver of an implicit toString()
+   * call. The receiver of toString() is defined in the annotated JDK to be @GuardSatisfied.
+   * Therefore if the expression is guarded by a set of locks, the locks must be held prior to this
+   * implicit call to toString().
+   *
+   * <p>Only call this method from visitBinary and visitCompoundAssignment.
+   *
+   * @param tree the Tree corresponding to the expression that is known to be the receiver of an
+   *     implicit toString() call
+   */
+  // TODO: If and when the de-sugared .toString() tree is accessible from BaseTypeVisitor,
+  // the toString() method call should be visited instead of doing this. This would result
+  // in contracts.precondition errors being issued instead of
+  // contracts.precondition.field, so it would be clear that
+  // the error refers to an implicit method call, not a dereference (field access).
+  private void checkPreconditionsForImplicitToStringCall(ExpressionTree tree) {
+    AnnotationMirror gbAnno =
+        atypeFactory
+            .getAnnotatedType(tree)
+            .getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBY);
+    checkLock(tree, gbAnno);
+  }
+
+  private void checkLockOfImplicitThis(Tree tree, AnnotationMirror gbAnno) {
+    checkLockOfThisOrTree(tree, true, gbAnno);
+  }
+
+  private void checkLock(Tree tree, AnnotationMirror gbAnno) {
+    checkLockOfThisOrTree(tree, false, gbAnno);
+  }
+
+  private void checkLockOfThisOrTree(Tree tree, boolean implicitThis, AnnotationMirror gbAnno) {
+    if (gbAnno == null) {
+      throw new BugInCF("LockVisitor.checkLock: gbAnno cannot be null");
+    }
+    if (atypeFactory.areSameByClass(gbAnno, GuardedByUnknown.class)
+        || atypeFactory.areSameByClass(gbAnno, GuardedByBottom.class)) {
+      checker.reportError(tree, "lock.not.held", "unknown lock " + gbAnno);
+      return;
+    } else if (atypeFactory.areSameByClass(gbAnno, GuardSatisfied.class)) {
+      return;
+    }
+
+    List<LockExpression> expressions = getLockExpressions(implicitThis, gbAnno, tree);
+    if (expressions.isEmpty()) {
+      return;
+    }
+
+    LockStore store = atypeFactory.getStoreBefore(tree);
+    for (LockExpression expression : expressions) {
+      if (expression.error != null) {
+        checker.reportError(tree, "expression.unparsable", expression.error.toString());
+      } else if (expression.lockExpression == null) {
+        checker.reportError(tree, "expression.unparsable", expression.expressionString);
+      } else if (!isLockHeld(expression.lockExpression, store)) {
+        checker.reportError(tree, "lock.not.held", expression.lockExpression.toString());
+      }
+
+      if (expression.error != null && expression.lockExpression != null) {
+        ensureExpressionIsEffectivelyFinal(
+            expression.lockExpression, expression.expressionString, tree);
+      }
+    }
+  }
+
+  private boolean isLockHeld(JavaExpression lockExpr, LockStore store) {
+    if (store == null) {
+      return false;
+    }
+    CFAbstractValue<?> value = store.getValue(lockExpr);
+    if (value == null) {
+      return false;
+    }
+    Set<AnnotationMirror> annos = value.getAnnotations();
+    QualifierHierarchy hierarchy = atypeFactory.getQualifierHierarchy();
+    AnnotationMirror lockAnno =
+        hierarchy.findAnnotationInSameHierarchy(annos, atypeFactory.LOCKHELD);
+    return lockAnno != null && atypeFactory.areSameByClass(lockAnno, LockHeld.class);
+  }
+
+  private List<LockExpression> getLockExpressions(
+      boolean implicitThis, AnnotationMirror gbAnno, Tree tree) {
+
+    List<String> expressions =
+        AnnotationUtils.getElementValueArray(
+            gbAnno, atypeFactory.guardedByValueElement, String.class, Collections.emptyList());
+
+    if (expressions.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    TreePath currentPath = getCurrentPath();
+
+    TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(currentPath));
+    JavaExpression pseudoReceiver = JavaExpression.getPseudoReceiver(currentPath, enclosingType);
+
+    JavaExpression self;
+    if (implicitThis) {
+      self = pseudoReceiver;
+    } else if (TreeUtils.isExpressionTree(tree)) {
+      self = JavaExpression.fromTree((ExpressionTree) tree);
+    } else {
+      self = new Unknown(tree);
+    }
+
+    return CollectionsPlume.mapList(
+        expression -> parseExpressionString(expression, currentPath, self), expressions);
+  }
+
+  /**
+   * Parse a Java expression.
+   *
+   * @param expression the Java expression
+   * @param path the path to the expression
+   * @param itself the self expression
+   * @return the parsed expression
+   */
+  private LockExpression parseExpressionString(
+      String expression, TreePath path, JavaExpression itself) {
+
+    LockExpression lockExpression = new LockExpression(expression);
+    if (DependentTypesError.isExpressionError(expression)) {
+      lockExpression.error = DependentTypesError.unparse(expression);
+      return lockExpression;
+    }
+
+    Matcher selfReceiverMatcher = SELF_RECEIVER_PATTERN.matcher(expression);
+    try {
+      if (selfReceiverMatcher.matches()) {
+        String remainingExpression = selfReceiverMatcher.group(2);
+        if (remainingExpression == null || remainingExpression.isEmpty()) {
+          lockExpression.lockExpression = itself;
+          if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) {
+            checker.reportError(
+                path.getLeaf(), "lock.expression.not.final", lockExpression.lockExpression);
+          }
+          return lockExpression;
+        } else {
+          lockExpression.lockExpression =
+              StringToJavaExpression.atPath(
+                  itself.toString() + "." + remainingExpression, path, checker);
+          if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) {
+            checker.reportError(
+                path.getLeaf(), "lock.expression.not.final", lockExpression.lockExpression);
+          }
+          return lockExpression;
+        }
+      } else {
+        lockExpression.lockExpression = StringToJavaExpression.atPath(expression, path, checker);
+        return lockExpression;
+      }
+    } catch (JavaExpressionParseException ex) {
+      lockExpression.error = new DependentTypesError(expression, ex);
+      return lockExpression;
+    }
+  }
+
+  private static class LockExpression {
+    final String expressionString;
+    JavaExpression lockExpression = null;
+    DependentTypesError error = null;
+
+    LockExpression(String expression) {
+      this.expressionString = expression;
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/lock/messages.properties b/checker/src/main/java/org/checkerframework/checker/lock/messages.properties
new file mode 100644
index 0000000..52ad5f6
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/lock/messages.properties
@@ -0,0 +1,20 @@
+### Error messages for the Lock Checker
+contracts.precondition=call to '%s' requires '%s' to be held
+override.sideeffect=the side-effect annotation on an overrider method must be at least as strong as the one the overridden method.%nmethod in %s%n  %s%n  cannot override method in %s%n  %s
+multiple.sideeffect.annotations=method is annotated with multiple side effect annotations
+multiple.lock.precondition.annotations=only one @Holding, @net.jcip.annotations.GuardedBy or @javax.annotation.concurrent.GuardedBy annotation is allowed on a method
+multiple.guardedby.annotations=only one @org.checkerframework.checker.lock.qual.GuardedBy, @net.jcip.annotations.GuardedBy or @javax.annotation.concurrent.GuardedBy annotation is allowed on a variable declaration
+method.guarantee.violated=%s method %s calls method %s with a weaker %s side effect guarantee
+cannot.dereference=cannot dereference expression %s with refined type annotation @%s
+immutable.type.guardedby=immutable types need not be annotated with any qualifier from the @GuardedBy hierarchy
+explicit.lock.synchronized=expression of synchronized block is an explicit lock
+guardsatisfied.with.mayreleaselocks=a method annotated with @MayReleaseLocks may not have a formal parameter or receiver annotated with @GuardSatisfied
+guardsatisfied.parameters.must.match=%s and %s on the declaration of method %s are both annotated with @GuardSatisfied(%s). However the corresponding annotations at the method call site are %s and %s, and neither is a subtype of the other.
+guardsatisfied.return.must.have.index=@GuardSatisfied on a return type must use an index.
+guardsatisfied.assignment.disallowed=Expressions %s and %s are both annotated with @GuardSatisfied. The guards for both expressions may be different, so the assignment is disallowed.
+guardsatisfied.location.disallowed=@GuardSatisfied annotations are only allowed on method return types and parameters (including the receiver).
+lockingfree.synchronized.method=A synchronized method cannot be %s. It may only be annotated with @ReleasesNoLocks or @MayReleaseLocks.
+synchronized.block.in.lockingfree.method=A synchronized block cannot be written inside a %s method. It may only be written in a method annotated with @ReleasesNoLocks or @MayReleaseLocks.
+lock.expression.not.final=lock expression includes a non-final field or a call to a method that is not pure or deterministic.%n lock expression: %s
+lock.expression.possibly.not.final=could not determine that the lock expression(s) include only non-final fields or calls to methods that are pure or deterministic.%n lock expression: %s
+lock.not.held=required lock not held.%nrequired: %s
diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesObligationElementSupplier.java b/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesObligationElementSupplier.java
new file mode 100644
index 0000000..314a59d
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesObligationElementSupplier.java
@@ -0,0 +1,153 @@
+package org.checkerframework.checker.mustcall;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.mustcall.qual.CreatesObligation;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.Unknown;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.framework.util.StringToJavaExpression;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * This interface should be implemented by all type factories that can provide the ExecutableElement
+ * needed to call {@link AnnotationUtils#getElementValueArray} when {@link
+ * #getCreatesObligationExpressions(MethodInvocationNode, GenericAnnotatedTypeFactory,
+ * CreatesObligationElementSupplier)} is called. This interface is needed so any type factory with
+ * these elements can be used to call that method, not just the MustCallAnnotatedTypeFactory (in
+ * particular, the consistency checker needs to be able to call that method with both the
+ * ObjectConstruction/CalledMethods type factory and the MustCall type factory).
+ */
+public interface CreatesObligationElementSupplier {
+
+  /**
+   * Returns the CreatesObligation.value field/element.
+   *
+   * @return the CreatesObligation.value field/element
+   */
+  ExecutableElement getCreatesObligationValueElement();
+
+  /**
+   * Returns the CreatesObligation.List.value field/element.
+   *
+   * @return the CreatesObligation.List.value field/element
+   */
+  ExecutableElement getCreatesObligationListValueElement();
+
+  /**
+   * Returns the arguments of the @CreatesObligation annotation on the invoked method, as
+   * JavaExpressions. Returns the empty set if the given method has no @CreatesObligation
+   * annotation.
+   *
+   * <p>If any expression is unparseable, this method reports an error and returns the empty set.
+   *
+   * @param n a method invocation
+   * @param atypeFactory the type factory to report errors and parse the expression string
+   * @param supplier a type factory that can supply the executable elements for CreatesObligation
+   *     and CreatesObligation.List's value elements. Usually, you should just pass atypeFactory
+   *     again. The arguments are different so that the given type factory's adherence to both
+   *     protocols are checked by the type system.
+   * @return the arguments of the method's @CreatesObligation annotation, or an empty list
+   */
+  static List<JavaExpression> getCreatesObligationExpressions(
+      MethodInvocationNode n,
+      GenericAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory,
+      CreatesObligationElementSupplier supplier) {
+    AnnotationMirror createsObligationList =
+        atypeFactory.getDeclAnnotation(n.getTarget().getMethod(), CreatesObligation.List.class);
+    List<JavaExpression> results = new ArrayList<>(1);
+    if (createsObligationList != null) {
+      // Handle a set of CreatesObligation annotations.
+      List<AnnotationMirror> createsObligations =
+          AnnotationUtils.getElementValueArray(
+              createsObligationList,
+              supplier.getCreatesObligationListValueElement(),
+              AnnotationMirror.class);
+      for (AnnotationMirror co : createsObligations) {
+        JavaExpression expr = getCreatesObligationExpression(co, n, atypeFactory, supplier);
+        if (expr != null && !results.contains(expr)) {
+          results.add(expr);
+        }
+      }
+    }
+    AnnotationMirror createsObligation =
+        atypeFactory.getDeclAnnotation(n.getTarget().getMethod(), CreatesObligation.class);
+    if (createsObligation != null) {
+      JavaExpression expr =
+          getCreatesObligationExpression(createsObligation, n, atypeFactory, supplier);
+      if (expr != null && !results.contains(expr)) {
+        results.add(expr);
+      }
+    }
+    return results;
+  }
+
+  /**
+   * Parses a single CreatesObligation annotation. Clients should use {@link
+   * #getCreatesObligationExpressions(MethodInvocationNode, GenericAnnotatedTypeFactory,
+   * CreatesObligationElementSupplier)}, which handles the possibility of multiple such annotations,
+   * instead.
+   *
+   * @param createsObligation a @CreatesObligation annotation
+   * @param n the invocation of a reset method
+   * @param atypeFactory the type factory
+   * @param supplier a type factory that can supply the executable elements for CreatesObligation
+   *     and CreatesObligation.List's value elements. Usually, you should just pass atypeFactory
+   *     again. The arguments are different so that the given type factory's adherence to both
+   *     protocols are checked by the type system.
+   * @return the Java expression representing the target, or null if the target is unparseable
+   */
+  static @Nullable JavaExpression getCreatesObligationExpression(
+      AnnotationMirror createsObligation,
+      MethodInvocationNode n,
+      GenericAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory,
+      CreatesObligationElementSupplier supplier) {
+    // Unfortunately, there is no way to avoid passing the default string "this" here. The default
+    // must be hard-coded into the client, such as here. That is the price for the efficiency of not
+    // having to query the annotation definition (such queries are expensive).
+    String targetStrWithoutAdaptation =
+        AnnotationUtils.getElementValue(
+            createsObligation, supplier.getCreatesObligationValueElement(), String.class, "this");
+    // TODO: find a way to also check if the target is a known tempvar, and if so return that. That
+    // should improve the quality of the error messages we give.
+    JavaExpression targetExpr;
+    try {
+      targetExpr =
+          StringToJavaExpression.atMethodInvocation(
+              targetStrWithoutAdaptation, n, atypeFactory.getChecker());
+      if (targetExpr instanceof Unknown) {
+        issueUnparseableError(n, atypeFactory, targetStrWithoutAdaptation);
+        return null;
+      }
+    } catch (JavaExpressionParseException e) {
+      issueUnparseableError(n, atypeFactory, targetStrWithoutAdaptation);
+      return null;
+    }
+    return targetExpr;
+  }
+
+  /**
+   * Issues a createsobligation.target.unparseable error.
+   *
+   * @param n the node
+   * @param atypeFactory the type factory to use to issue the error
+   * @param unparseable the unparseable string
+   */
+  static void issueUnparseableError(
+      MethodInvocationNode n,
+      GenericAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory,
+      String unparseable) {
+    atypeFactory
+        .getChecker()
+        .reportError(
+            n.getTree(),
+            "createsobligation.target.unparseable",
+            n.getTarget().getMethod().getSimpleName(),
+            unparseable);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/JavaEE.astub b/checker/src/main/java/org/checkerframework/checker/mustcall/JavaEE.astub
new file mode 100644
index 0000000..3549597
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/mustcall/JavaEE.astub
@@ -0,0 +1,19 @@
+// DO NOT INCLUDE ANNOTATIONS THAT RELY ON ACCUMULATION FRAMES IN THIS FILE.
+// This file is loaded regardless of the -AnoAccumulationFrames option. All
+// assumptions that rely on the presence of accumulation frames (such as @MustCall({})
+// on unconnected sockets) MUST go in SocketAccumulationFrames.astub. CreatesObligation
+// okay, because the OCC turns that off anyway.
+
+import org.checkerframework.checker.mustcall.qual.*;
+
+package javax.servlet;
+
+// This interface doesn't appear in the annotated JDK, because it is part
+// Java EE not Java SE. Therefore, this annotated version appears here in a
+// stub file rather than in typetools/jdk.
+interface ServletResponse {
+    // The link below justifies why these annotations are correct
+    // https://stackoverflow.com/questions/1159168/should-one-call-close-on-httpservletresponse-getoutputstream-getwriter
+    @NotOwning ServletOutputStream getOutputStream() throws IOException;
+    @NotOwning PrintWriter getWriter() throws IOException;
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java
new file mode 100644
index 0000000..4355f39
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java
@@ -0,0 +1,430 @@
+package org.checkerframework.checker.mustcall;
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.mustcall.qual.CreatesObligation;
+import org.checkerframework.checker.mustcall.qual.InheritableMustCall;
+import org.checkerframework.checker.mustcall.qual.MustCall;
+import org.checkerframework.checker.mustcall.qual.MustCallAlias;
+import org.checkerframework.checker.mustcall.qual.MustCallUnknown;
+import org.checkerframework.checker.mustcall.qual.Owning;
+import org.checkerframework.checker.mustcall.qual.PolyMustCall;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.QualifierUpperBounds;
+import org.checkerframework.framework.type.SubtypeIsSubsetQualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.type.typeannotator.DefaultQualifierForUseTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * The annotated type factory for the Must Call Checker. Primarily responsible for the subtyping
+ * rules between @MustCall annotations.
+ */
+public class MustCallAnnotatedTypeFactory extends BaseAnnotatedTypeFactory
+    implements CreatesObligationElementSupplier {
+
+  /** The {@code @}{@link MustCallUnknown} annotation. */
+  public final AnnotationMirror TOP;
+
+  /** The {@code @}{@link MustCall}{@code ()} annotation. It is the default in unannotated code. */
+  public final AnnotationMirror BOTTOM;
+
+  /** The {@code @}{@link PolyMustCall} annotation. */
+  final AnnotationMirror POLY;
+
+  /**
+   * Map from trees representing expressions to the temporary variables that represent them in the
+   * store.
+   *
+   * <p>Consider the following code, adapted from Apache Zookeeper:
+   *
+   * <pre>
+   *   sock = SocketChannel.open();
+   *   sock.socket().setSoLinger(false, -1);
+   * </pre>
+   *
+   * This code is safe from resource leaks: sock is an unconnected socket and therefore has no
+   * must-call obligation. The expression sock.socket() similarly has no must-call obligation
+   * because it is a resource alias, but without a temporary variable that represents that
+   * expression in the store, the resource leak checker wouldn't be able to determine that.
+   *
+   * <p>These temporary variables are only created once---here---but are used by all three parts of
+   * the resource leak checker by calling {@link #getTempVar(Node)}. The temporary variables are
+   * shared in the same way that subcheckers share CFG structure; see {@link
+   * #getSharedCFGForTree(Tree)}.
+   */
+  /* package-private */ final HashMap<Tree, LocalVariableNode> tempVars = new HashMap<>();
+
+  /** The MustCall.value field/element. */
+  final ExecutableElement mustCallValueElement =
+      TreeUtils.getMethod(MustCall.class, "value", 0, processingEnv);
+
+  /** The InheritableMustCall.value field/element. */
+  final ExecutableElement inheritableMustCallValueElement =
+      TreeUtils.getMethod(InheritableMustCall.class, "value", 0, processingEnv);
+
+  /** The CreatesObligation.List.value field/element. */
+  private final ExecutableElement createsObligationListValueElement =
+      TreeUtils.getMethod(CreatesObligation.List.class, "value", 0, processingEnv);
+
+  /** The CreatesObligation.value field/element. */
+  private final ExecutableElement createsObligationValueElement =
+      TreeUtils.getMethod(CreatesObligation.class, "value", 0, processingEnv);
+
+  /**
+   * Creates a MustCallAnnotatedTypeFactory.
+   *
+   * @param checker the checker associated with this type factory
+   */
+  public MustCallAnnotatedTypeFactory(final BaseTypeChecker checker) {
+    super(checker);
+    TOP = AnnotationBuilder.fromClass(elements, MustCallUnknown.class);
+    BOTTOM = createMustCall(Collections.emptyList());
+    POLY = AnnotationBuilder.fromClass(elements, PolyMustCall.class);
+    addAliasedTypeAnnotation(InheritableMustCall.class, MustCall.class, true);
+    if (!checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES)) {
+      // In NO_RESOURCE_ALIASES mode, all @MustCallAlias annotations are ignored.
+      addAliasedTypeAnnotation(MustCallAlias.class, POLY);
+    }
+    this.postInit();
+  }
+
+  @Override
+  public void setRoot(@Nullable CompilationUnitTree root) {
+    super.setRoot(root);
+    // TODO: This should probably be guarded by isSafeToClearSharedCFG from
+    // GenericAnnotatedTypeFactory, but this works here because we know the Must Call Checker is
+    // always the first subchecker that's sharing tempvars.
+    tempVars.clear();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    // Explicitly name the qualifiers, in order to exclude @MustCallAlias.
+    return new LinkedHashSet<>(
+        Arrays.asList(MustCall.class, MustCallUnknown.class, PolyMustCall.class));
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(super.createTreeAnnotator(), new MustCallTreeAnnotator(this));
+  }
+
+  @Override
+  protected TypeAnnotator createTypeAnnotator() {
+    return new ListTypeAnnotator(super.createTypeAnnotator(), new MustCallTypeAnnotator(this));
+  }
+
+  /**
+   * Returns a {@literal @}MustCall annotation that is like the input, but it does not have "close".
+   * Returns the argument annotation mirror (not a new one) if the argument doesn't have "close" as
+   * one of its elements.
+   *
+   * <p>If the argument is null, returns bottom.
+   *
+   * @param anno a MustCall annotation
+   * @return a MustCall annotation that does not have "close" as one of its values, but is otherwise
+   *     identical to anno
+   */
+  // Package private to permit usage from the visitor in the common assignment check.
+  /* package-private */ AnnotationMirror withoutClose(@Nullable AnnotationMirror anno) {
+    if (anno == null || AnnotationUtils.areSame(anno, BOTTOM)) {
+      return BOTTOM;
+    } else if (!AnnotationUtils.areSameByName(
+        anno, "org.checkerframework.checker.mustcall.qual.MustCall")) {
+      return anno;
+    }
+    List<String> values =
+        AnnotationUtils.getElementValueArray(anno, mustCallValueElement, String.class);
+    // Use `removeAll` because `remove` only removes the first occurrence.
+    if (values.removeAll(Collections.singletonList("close"))) {
+      return createMustCall(values);
+    } else {
+      return anno;
+    }
+  }
+
+  /**
+   * Returns true iff the given element is a resource variable.
+   *
+   * @param elt an element; may be null, in which case this method always returns false
+   * @return true iff the given element represents a resource variable
+   */
+  private boolean isResourceVariable(@Nullable Element elt) {
+    return elt != null && elt.getKind() == ElementKind.RESOURCE_VARIABLE;
+  }
+
+  /** Treat non-owning method parameters as @MustCallUnknown (top) when the method is called. */
+  @Override
+  public void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) {
+    ExecutableElement declaration;
+    if (tree instanceof MethodInvocationTree) {
+      declaration = TreeUtils.elementFromUse((MethodInvocationTree) tree);
+    } else if (tree instanceof MemberReferenceTree) {
+      declaration = (ExecutableElement) TreeUtils.elementFromTree(tree);
+    } else {
+      throw new BugInCF("unexpected type of method tree: " + tree.getKind());
+    }
+    changeNonOwningParameterTypesToTop(declaration, type);
+    super.methodFromUsePreSubstitution(tree, type);
+  }
+
+  @Override
+  protected void constructorFromUsePreSubstitution(
+      NewClassTree tree, AnnotatedExecutableType type) {
+    ExecutableElement declaration = TreeUtils.elementFromUse(tree);
+    changeNonOwningParameterTypesToTop(declaration, type);
+    super.constructorFromUsePreSubstitution(tree, type);
+  }
+
+  /**
+   * Changes the type of each parameter not annotated as @Owning to @MustCallUnknown (top). Also
+   * replaces the component type of the varargs array, if applicable.
+   *
+   * <p>This method is not responsible for handling receivers, which can never be owning.
+   *
+   * @param declaration a method or constructor declaration
+   * @param type the method or constructor's type
+   */
+  private void changeNonOwningParameterTypesToTop(
+      ExecutableElement declaration, AnnotatedExecutableType type) {
+    List<AnnotatedTypeMirror> parameterTypes = type.getParameterTypes();
+    for (int i = 0; i < parameterTypes.size(); i++) {
+      Element paramDecl = declaration.getParameters().get(i);
+      if (checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP)
+          || getDeclAnnotation(paramDecl, Owning.class) == null) {
+        AnnotatedTypeMirror paramType = parameterTypes.get(i);
+        if (!paramType.hasAnnotation(POLY)) {
+          paramType.replaceAnnotation(TOP);
+        }
+      }
+    }
+    if (declaration.isVarArgs()) {
+      // also modify the component type of a varargs array
+      AnnotatedTypeMirror varargsType =
+          ((AnnotatedArrayType) parameterTypes.get(parameterTypes.size() - 1)).getComponentType();
+      if (!varargsType.hasAnnotation(POLY)) {
+        varargsType.replaceAnnotation(TOP);
+      }
+    }
+  }
+
+  @Override
+  protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() {
+    return new MustCallDefaultQualifierForUseTypeAnnotator();
+  }
+
+  /** Support @InheritableMustCall meaning @MustCall on all subtype elements. */
+  class MustCallDefaultQualifierForUseTypeAnnotator extends DefaultQualifierForUseTypeAnnotator {
+
+    /** Creates a {@code MustCallDefaultQualifierForUseTypeAnnotator}. */
+    public MustCallDefaultQualifierForUseTypeAnnotator() {
+      super(MustCallAnnotatedTypeFactory.this);
+    }
+
+    @Override
+    protected Set<AnnotationMirror> getExplicitAnnos(Element element) {
+      Set<AnnotationMirror> explict = super.getExplicitAnnos(element);
+      if (explict.isEmpty() && ElementUtils.isTypeElement(element)) {
+        AnnotationMirror inheritableMustCall =
+            getDeclAnnotation(element, InheritableMustCall.class);
+        if (inheritableMustCall != null) {
+          List<String> mustCallVal =
+              AnnotationUtils.getElementValueArray(
+                  inheritableMustCall, inheritableMustCallValueElement, String.class);
+          return Collections.singleton(createMustCall(mustCallVal));
+        }
+      }
+      return explict;
+    }
+  }
+
+  @Override
+  protected QualifierUpperBounds createQualifierUpperBounds() {
+    return new MustCallQualifierUpperBounds();
+  }
+
+  /** Support @InheritableMustCall meaning @MustCall on all subtypes. */
+  class MustCallQualifierUpperBounds extends QualifierUpperBounds {
+
+    /**
+     * Creates a {@link QualifierUpperBounds} from the MustCall Checker the annotations that are in
+     * the type hierarchy.
+     */
+    public MustCallQualifierUpperBounds() {
+      super(MustCallAnnotatedTypeFactory.this);
+    }
+
+    @Override
+    protected Set<AnnotationMirror> getAnnotationFromElement(Element element) {
+      Set<AnnotationMirror> explict = super.getAnnotationFromElement(element);
+      if (!explict.isEmpty()) {
+        return explict;
+      }
+      AnnotationMirror inheritableMustCall = getDeclAnnotation(element, InheritableMustCall.class);
+      if (inheritableMustCall != null) {
+        List<String> mustCallVal =
+            AnnotationUtils.getElementValueArray(
+                inheritableMustCall, inheritableMustCallValueElement, String.class);
+        return Collections.singleton(createMustCall(mustCallVal));
+      }
+      return Collections.emptySet();
+    }
+  }
+
+  /**
+   * Cache of the MustCall annotations that have actually been created. Most programs require few
+   * distinct MustCall annotations (e.g. MustCall() and MustCall("close")).
+   */
+  private Map<List<String>, AnnotationMirror> mustCallAnnotations = new HashMap<>(10);
+
+  /**
+   * Creates a {@link MustCall} annotation whose values are the given strings.
+   *
+   * @param val the methods that should be called
+   * @return an annotation indicating that the given methods should be called
+   */
+  public AnnotationMirror createMustCall(final List<String> val) {
+    return mustCallAnnotations.computeIfAbsent(val, this::createMustCallImpl);
+  }
+
+  /**
+   * Creates a {@link MustCall} annotation whose values are the given strings.
+   *
+   * <p>This internal version bypasses the cache, and is only used for new annotations.
+   *
+   * @param methodList the methods that should be called
+   * @return an annotation indicating that the given methods should be called
+   */
+  private AnnotationMirror createMustCallImpl(List<String> methodList) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MustCall.class);
+    String[] methodArray = methodList.toArray(new String[methodList.size()]);
+    Arrays.sort(methodArray);
+    builder.setValue("value", methodArray);
+    return builder.build();
+  }
+
+  @Override
+  public QualifierHierarchy createQualifierHierarchy() {
+    return new SubtypeIsSubsetQualifierHierarchy(
+        this.getSupportedTypeQualifiers(), this.getProcessingEnv());
+  }
+
+  /**
+   * Fetches the store from the results of dataflow for {@code first}. If {@code afterFirstStore} is
+   * true, then the store after {@code first} is returned; if {@code afterFirstStore} is false, the
+   * store before {@code succ} is returned.
+   *
+   * @param afterFirstStore whether to use the store after the first block or the store before its
+   *     successor, succ
+   * @param first a block
+   * @param succ first's successor
+   * @return the appropriate CFStore, populated with MustCall annotations, from the results of
+   *     running dataflow
+   */
+  public CFStore getStoreForBlock(boolean afterFirstStore, Block first, Block succ) {
+    return afterFirstStore ? flowResult.getStoreAfter(first) : flowResult.getStoreBefore(succ);
+  }
+
+  /**
+   * Returns the CreatesObligation.value field/element.
+   *
+   * @return the CreatesObligation.value field/element
+   */
+  @Override
+  public ExecutableElement getCreatesObligationValueElement() {
+    return createsObligationValueElement;
+  }
+
+  /**
+   * Returns the CreatesObligation.List.value field/element.
+   *
+   * @return the CreatesObligation.List.value field/element
+   */
+  @Override
+  public ExecutableElement getCreatesObligationListValueElement() {
+    return createsObligationListValueElement;
+  }
+
+  /**
+   * The TreeAnnotator for the MustCall type system.
+   *
+   * <p>This tree annotator treats non-owning method parameters as bottom, regardless of their
+   * declared type, when they appear in the body of the method. Doing so is safe because being
+   * non-owning means, by definition, that their must-call obligations are only relevant in the
+   * callee. (This behavior is disabled if the -AnoLightweightOwnership option is passed to the
+   * checker.)
+   *
+   * <p>The tree annotator also changes the type of resource variables to remove "close" from their
+   * must-call types, because the try-with-resources statement guarantees that close() is called on
+   * all such variables.
+   */
+  private class MustCallTreeAnnotator extends TreeAnnotator {
+    /**
+     * Create a MustCallTreeAnnotator.
+     *
+     * @param mustCallAnnotatedTypeFactory the type factory
+     */
+    public MustCallTreeAnnotator(MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory) {
+      super(mustCallAnnotatedTypeFactory);
+    }
+
+    @Override
+    public Void visitIdentifier(IdentifierTree node, AnnotatedTypeMirror type) {
+      Element elt = TreeUtils.elementFromTree(node);
+      if (elt.getKind() == ElementKind.PARAMETER
+          && (checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP)
+              || getDeclAnnotation(elt, Owning.class) == null)) {
+        type.replaceAnnotation(BOTTOM);
+      }
+      if (isResourceVariable(TreeUtils.elementFromTree(node))) {
+        type.replaceAnnotation(withoutClose(type.getAnnotationInHierarchy(TOP)));
+      }
+      return super.visitIdentifier(node, type);
+    }
+  }
+
+  /**
+   * Return the temporary variable for node, if it exists. See {@link #tempVars}.
+   *
+   * @param node a CFG node
+   * @return the corresponding temporary variable, or null if there is not one
+   */
+  public @Nullable LocalVariableNode getTempVar(Node node) {
+    return tempVars.get(node.getTree());
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallChecker.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallChecker.java
new file mode 100644
index 0000000..9f5ace7
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallChecker.java
@@ -0,0 +1,32 @@
+package org.checkerframework.checker.mustcall;
+
+import org.checkerframework.checker.mustcall.qual.MustCall;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.StubFiles;
+import org.checkerframework.framework.source.SupportedOptions;
+
+/**
+ * This typechecker ensures that {@code @}{@link MustCall} annotations are consistent with one
+ * another. The Object Construction Checker verifies that the given methods are actually called.
+ */
+@StubFiles({
+  "JavaEE.astub",
+  "Reflection.astub",
+  "SocketAccumulationFrames.astub",
+})
+@SupportedOptions({
+  MustCallChecker.NO_ACCUMULATION_FRAMES,
+  MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP,
+  MustCallChecker.NO_RESOURCE_ALIASES
+})
+public class MustCallChecker extends BaseTypeChecker {
+
+  /** Disables @CreatesObligation support. */
+  public static final String NO_ACCUMULATION_FRAMES = "noAccumulationFrames";
+
+  /** Disables @Owning/@NotOwning support. */
+  public static final String NO_LIGHTWEIGHT_OWNERSHIP = "noLightweightOwnership";
+
+  /** Disables @MustCallAlias support. */
+  public static final String NO_RESOURCE_ALIASES = "noResourceAliases";
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallNoAccumulationFramesChecker.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallNoAccumulationFramesChecker.java
new file mode 100644
index 0000000..eb2a681
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallNoAccumulationFramesChecker.java
@@ -0,0 +1,17 @@
+package org.checkerframework.checker.mustcall;
+
+import org.checkerframework.framework.qual.StubFiles;
+import org.checkerframework.framework.source.SupportedOptions;
+
+/**
+ * This copy of the Must Call Checker is identical, except that it does not load the stub files that
+ * treat unconnected sockets as {@code @MustCall({})}. See SocketAccumulationFrames.astub.
+ *
+ * <p>The only difference is the contents of the @StubFiles annotation.
+ */
+@StubFiles({
+  "JavaEE.astub",
+  "Reflection.astub",
+})
+@SupportedOptions({MustCallChecker.NO_ACCUMULATION_FRAMES})
+public class MustCallNoAccumulationFramesChecker extends MustCallChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java
new file mode 100644
index 0000000..75523e8
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java
@@ -0,0 +1,244 @@
+package org.checkerframework.checker.mustcall;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+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.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
+import org.checkerframework.dataflow.cfg.node.StringConversionNode;
+import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.checkerframework.javacutil.trees.TreeBuilder;
+
+/**
+ * Transfer function for the must-call type system. Its primary purposes are (1) to create temporary
+ * variables for expressions (which allow those expressions to have refined information in the
+ * store, which the consistency checker can use), and (2) to reset refined information when a method
+ * annotated with @CreatesObligation is called.
+ */
+public class MustCallTransfer extends CFTransfer {
+
+  /** For building new AST nodes. */
+  private final TreeBuilder treeBuilder;
+
+  /** The type factory. */
+  private MustCallAnnotatedTypeFactory atypeFactory;
+
+  /**
+   * A cache for the default type for java.lang.String, to avoid needing to look it up for every
+   * implicit string conversion. See {@link #getDefaultStringType(StringConversionNode)}.
+   */
+  private @MonotonicNonNull AnnotationMirror defaultStringType;
+
+  /**
+   * Create a MustCallTransfer.
+   *
+   * @param analysis the analysis
+   */
+  public MustCallTransfer(CFAnalysis analysis) {
+    super(analysis);
+    atypeFactory = (MustCallAnnotatedTypeFactory) analysis.getTypeFactory();
+    ProcessingEnvironment env = atypeFactory.getChecker().getProcessingEnvironment();
+    treeBuilder = new TreeBuilder(env);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitStringConversion(
+      StringConversionNode n, TransferInput<CFValue, CFStore> p) {
+    // Implicit String conversions should assume that the String's type is
+    // whatever the default for String is, not that the conversion is polymorphic.
+    TransferResult<CFValue, CFStore> result = super.visitStringConversion(n, p);
+    LocalVariableNode temp = getOrCreateTempVar(n);
+    if (temp != null) {
+      AnnotationMirror defaultStringType = getDefaultStringType(n);
+      JavaExpression localExp = JavaExpression.fromNode(temp);
+      insertIntoStores(result, localExp, defaultStringType);
+    }
+    return result;
+  }
+
+  /**
+   * Returns the default type for java.lang.String, which is cached in this class to avoid
+   * recomputing it. If the cache is currently unset, this method sets it.
+   *
+   * @param n a string conversion node, from which the type is computed if it is required
+   * @return the type of java.lang.String
+   */
+  private AnnotationMirror getDefaultStringType(StringConversionNode n) {
+    if (this.defaultStringType == null) {
+      this.defaultStringType =
+          atypeFactory
+              .getAnnotatedType(TypesUtils.getTypeElement(n.getType()))
+              .getAnnotationInHierarchy(atypeFactory.TOP);
+    }
+    return this.defaultStringType;
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitMethodInvocation(
+      MethodInvocationNode n, TransferInput<CFValue, CFStore> in) {
+    TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(n, in);
+
+    updateStoreWithTempVar(result, n);
+    if (!atypeFactory.getChecker().hasOption(MustCallChecker.NO_ACCUMULATION_FRAMES)) {
+      List<JavaExpression> targetExprs =
+          CreatesObligationElementSupplier.getCreatesObligationExpressions(
+              n, atypeFactory, atypeFactory);
+      for (JavaExpression targetExpr : targetExprs) {
+        AnnotationMirror defaultType =
+            atypeFactory
+                .getAnnotatedType(TypesUtils.getTypeElement(targetExpr.getType()))
+                .getAnnotationInHierarchy(atypeFactory.TOP);
+
+        if (result.containsTwoStores()) {
+          CFStore thenStore = result.getThenStore();
+          lubWithStoreValue(thenStore, targetExpr, defaultType);
+
+          CFStore elseStore = result.getElseStore();
+          lubWithStoreValue(elseStore, targetExpr, defaultType);
+        } else {
+          CFStore store = result.getRegularStore();
+          lubWithStoreValue(store, targetExpr, defaultType);
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Computes the LUB of the current value in the store for expr, if it exists, and defaultType.
+   * Inserts that LUB into the store as the new value for expr.
+   *
+   * @param store a CFStore
+   * @param expr an expression that might be in the store
+   * @param defaultType the default type of the expression's static type
+   */
+  private void lubWithStoreValue(CFStore store, JavaExpression expr, AnnotationMirror defaultType) {
+    CFValue value = store.getValue(expr);
+    CFValue defaultTypeAsCFValue =
+        analysis.createSingleAnnotationValue(defaultType, expr.getType());
+    CFValue newValue = defaultTypeAsCFValue.leastUpperBound(value);
+    store.clearValue(expr);
+    store.insertValue(expr, newValue);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitObjectCreation(
+      ObjectCreationNode node, TransferInput<CFValue, CFStore> input) {
+    TransferResult<CFValue, CFStore> result = super.visitObjectCreation(node, input);
+    updateStoreWithTempVar(result, node);
+    return result;
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitTernaryExpression(
+      TernaryExpressionNode node, TransferInput<CFValue, CFStore> input) {
+    TransferResult<CFValue, CFStore> result = super.visitTernaryExpression(node, input);
+    updateStoreWithTempVar(result, node);
+    return result;
+  }
+
+  /**
+   * This method either creates or looks up the temp var t for node, and then updates the store to
+   * give t the same type as node.
+   *
+   * @param node the node to be assigned to a temporary variable
+   * @param result the transfer result containing the store to be modified
+   */
+  public void updateStoreWithTempVar(TransferResult<CFValue, CFStore> result, Node node) {
+    // Must-call obligations on primitives are not supported.
+    if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) {
+      LocalVariableNode temp = getOrCreateTempVar(node);
+      if (temp != null) {
+        JavaExpression localExp = JavaExpression.fromNode(temp);
+        AnnotationMirror anm =
+            atypeFactory
+                .getAnnotatedType(node.getTree())
+                .getAnnotationInHierarchy(atypeFactory.TOP);
+        insertIntoStores(result, localExp, anm == null ? atypeFactory.TOP : anm);
+      }
+    }
+  }
+
+  /**
+   * Either returns the temporary variable associated with node, or creates one if one does not
+   * exist.
+   *
+   * @param node a node, which must be an expression (not a statement)
+   * @return a temporary variable node representing {@code node} that can be placed into a store
+   */
+  private @Nullable LocalVariableNode getOrCreateTempVar(Node node) {
+    LocalVariableNode localVariableNode = atypeFactory.tempVars.get(node.getTree());
+    if (localVariableNode == null) {
+      VariableTree temp = createTemporaryVar(node);
+      if (temp != null) {
+        IdentifierTree identifierTree = treeBuilder.buildVariableUse(temp);
+        localVariableNode = new LocalVariableNode(identifierTree);
+        localVariableNode.setInSource(true);
+        atypeFactory.tempVars.put(node.getTree(), localVariableNode);
+      }
+    }
+    return localVariableNode;
+  }
+
+  /**
+   * Creates a variable declaration for the given expression node, if possible.
+   *
+   * @param node an expression node
+   * @return a variable tree for the node, or null if an appropriate containing element cannot be
+   *     located
+   */
+  protected @Nullable VariableTree createTemporaryVar(Node node) {
+    ExpressionTree tree = (ExpressionTree) node.getTree();
+    TypeMirror treeType = TreeUtils.typeOf(tree);
+    Element enclosingElement;
+    TreePath path = atypeFactory.getPath(tree);
+    if (path == null) {
+      enclosingElement = TreeUtils.elementFromTree(tree).getEnclosingElement();
+    } else {
+      ClassTree classTree = TreePathUtil.enclosingClass(path);
+      enclosingElement = TreeUtils.elementFromTree(classTree);
+    }
+    if (enclosingElement == null) {
+      return null;
+    }
+    // Declare and initialize a new, unique variable
+    VariableTree tmpVarTree =
+        treeBuilder.buildVariableDecl(treeType, uniqueName("temp-var"), enclosingElement, tree);
+    return tmpVarTree;
+  }
+
+  /** A unique identifier counter for node names. */
+  protected long uid = 0;
+
+  /**
+   * Creates a unique, abitrary string that can be used as a name for a temporary variable, using
+   * the given prefix. Can be used up to Long.MAX_VALUE times.
+   *
+   * @param prefix the prefix for the name
+   * @return a unique name that starts with the prefix
+   */
+  protected String uniqueName(String prefix) {
+    return prefix + "-" + uid++;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTypeAnnotator.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTypeAnnotator.java
new file mode 100644
index 0000000..fdd4227
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTypeAnnotator.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.mustcall;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
+
+/** Primitive types always have no must-call obligations. */
+public class MustCallTypeAnnotator extends TypeAnnotator {
+
+  /**
+   * Create a MustCallTypeAnnotator.
+   *
+   * @param typeFactory the type factory
+   */
+  protected MustCallTypeAnnotator(MustCallAnnotatedTypeFactory typeFactory) {
+    super(typeFactory);
+  }
+
+  @Override
+  public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) {
+    type.replaceAnnotation(((MustCallAnnotatedTypeFactory) typeFactory).BOTTOM);
+    return super.visitPrimitive(type, aVoid);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java
new file mode 100644
index 0000000..94bae96
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java
@@ -0,0 +1,242 @@
+package org.checkerframework.checker.mustcall;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.Tree;
+import java.util.Collections;
+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.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.mustcall.qual.InheritableMustCall;
+import org.checkerframework.checker.mustcall.qual.MustCallAlias;
+import org.checkerframework.checker.mustcall.qual.NotOwning;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * The visitor for the Must Call Checker. This visitor is similar to BaseTypeVisitor, but overrides
+ * methods that don't work well with the MustCall type hierarchy because it doesn't use the top type
+ * as the default type.
+ */
+public class MustCallVisitor extends BaseTypeVisitor<MustCallAnnotatedTypeFactory> {
+
+  /**
+   * Creates a new MustCallVisitor.
+   *
+   * @param checker the type-checker associated with this visitor
+   */
+  public MustCallVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  public Void visitReturn(ReturnTree node, Void p) {
+    // Only check return types if ownership is being transferred.
+    if (!checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP)) {
+      MethodTree enclosingMethod = TreePathUtil.enclosingMethod(this.getCurrentPath());
+      // enclosingMethod is null if this return site is inside a lambda. TODO: handle lambdas more
+      // precisely?
+      if (enclosingMethod != null) {
+        ExecutableElement methodElt = TreeUtils.elementFromDeclaration(enclosingMethod);
+        AnnotationMirror notOwningAnno = atypeFactory.getDeclAnnotation(methodElt, NotOwning.class);
+        if (notOwningAnno != null) {
+          // Skip return type subtyping check, because not-owning pointer means Object Construction
+          // Checker won't check anyway.
+          return null;
+        }
+      }
+    }
+    return super.visitReturn(node, p);
+  }
+
+  @Override
+  protected boolean validateType(Tree tree, AnnotatedTypeMirror type) {
+    if (TreeUtils.isClassTree(tree)) {
+      Element classEle = TreeUtils.elementFromDeclaration((ClassTree) tree);
+      AnnotationMirror inheritableMustCall =
+          atypeFactory.getDeclAnnotation(classEle, InheritableMustCall.class);
+      if (inheritableMustCall != null) {
+        AnnotationMirror explict = atypeFactory.fromElement(classEle).getAnnotation();
+        if (explict != null) {
+          List<String> mustCallVal =
+              AnnotationUtils.getElementValueArray(
+                  inheritableMustCall, atypeFactory.inheritableMustCallValueElement, String.class);
+          AnnotationMirror inheritedMCAnno = atypeFactory.createMustCall(mustCallVal);
+
+          // Issue an error if there is an inconsistent, user-written @MustCall annotation.
+          AnnotationMirror writtenMCAnno = type.getAnnotation();
+          if (writtenMCAnno != null
+              && !atypeFactory.getQualifierHierarchy().isSubtype(inheritedMCAnno, writtenMCAnno)) {
+
+            checker.reportError(
+                tree,
+                "inconsistent.mustcall.subtype",
+                classEle.getSimpleName(),
+                writtenMCAnno,
+                inheritableMustCall);
+            return false;
+          }
+        }
+      }
+    }
+    return super.validateType(tree, type);
+  }
+
+  @Override
+  public boolean isValidUse(
+      AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) {
+    // MustCallAlias annotations are always permitted on type uses, because these will be validated
+    // by the Object Construction Checker's -AcheckMustCall algorithm.
+    AnnotatedDeclaredType useTypeCopy = useType.deepCopy();
+    if (!checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES)) {
+      useTypeCopy.removeAnnotationByClass(MustCallAlias.class);
+    }
+    return super.isValidUse(declarationType, useTypeCopy, tree);
+  }
+
+  @Override
+  protected boolean skipReceiverSubtypeCheck(
+      MethodInvocationTree node,
+      AnnotatedTypeMirror methodDefinitionReceiver,
+      AnnotatedTypeMirror methodCallReceiver) {
+    // It does not make sense for receivers to have must-call obligations. If the receiver of a
+    // method were to have a non-empty must-call obligation, then actually this method should
+    // be part of the must-call annotation on the class declaration! So skipping this check is
+    // always sound.
+    return true;
+  }
+
+  /**
+   * This boolean is used to communicate between different levels of the common assignment check
+   * whether a given check is being carried out on a (pseudo-)assignment to a resource variable. In
+   * those cases, close doesn't need to be considered when doing the check, since close will always
+   * be called by Java.
+   *
+   * <p>The check for whether the LHS is a resource variable can only be carried out on the element,
+   * but the effect needs to happen at the stage where the type is available (i.e. close needs to be
+   * removed from the type). Thus, this variable is used to communicate that a resource variable was
+   * detected on the LHS.
+   */
+  private boolean commonAssignmentCheckOnResourceVariable = false;
+
+  /**
+   * Mark (using the {@link #commonAssignmentCheckOnResourceVariable} field of this class) any
+   * assignments where the LHS is a resource variable, so that close doesn't need to be considered.
+   * See {@link #commonAssignmentCheck(AnnotatedTypeMirror, AnnotatedTypeMirror, Tree, String,
+   * Object...)} for the code that uses and removes the mark.
+   */
+  @Override
+  protected void commonAssignmentCheck(
+      Tree varTree,
+      ExpressionTree valueExp,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    if (TreeUtils.elementFromTree(varTree).getKind() == ElementKind.RESOURCE_VARIABLE) {
+      commonAssignmentCheckOnResourceVariable = true;
+    }
+    super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs);
+  }
+
+  /**
+   * Iff the LHS is a resource variable, then {@link #commonAssignmentCheckOnResourceVariable} will
+   * be true. This method guarantees that {@link #commonAssignmentCheckOnResourceVariable} will be
+   * false when it returns.
+   */
+  @Override
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      AnnotatedTypeMirror valueType,
+      Tree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    if (commonAssignmentCheckOnResourceVariable) {
+      commonAssignmentCheckOnResourceVariable = false;
+      // The LHS has been marked as a resource variable.  Skip the standard common assignment check;
+      // instead do a check that does not include "close".
+      AnnotationMirror varAnno = varType.getAnnotationInHierarchy(atypeFactory.TOP);
+      AnnotationMirror valAnno = valueType.getAnnotationInHierarchy(atypeFactory.TOP);
+      if (atypeFactory
+          .getQualifierHierarchy()
+          .isSubtype(atypeFactory.withoutClose(valAnno), atypeFactory.withoutClose(varAnno))) {
+        return;
+      }
+      // Note that in this case, the rest of the common assignment check should fail (barring an
+      // exception).  Control falls through here to avoid duplicating error-issuing code.
+    }
+    // commonAssignmentCheckOnResourceVariable is already false, so no need to set it.
+    super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
+  }
+
+  /**
+   * This method typically issues a warning if the result type of the constructor is not top,
+   * because in top-default type systems that indicates a potential problem. The Must Call Checker
+   * does not need this warning, because it expects the type of all constructors to be {@code
+   * MustCall({})} (by default) or some other {@code MustCall} type, not the top type.
+   *
+   * <p>Instead, this method checks that the result type of a constructor is a supertype of the
+   * declared type on the class, if one exists.
+   *
+   * @param constructorType AnnotatedExecutableType for the constructor
+   * @param constructorElement element that declares the constructor
+   */
+  @Override
+  protected void checkConstructorResult(
+      AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {
+    AnnotatedTypeMirror defaultType =
+        atypeFactory.getAnnotatedType(ElementUtils.enclosingTypeElement(constructorElement));
+    AnnotationMirror defaultAnno = defaultType.getAnnotationInHierarchy(atypeFactory.TOP);
+    AnnotationMirror resultAnno =
+        constructorType.getReturnType().getAnnotationInHierarchy(atypeFactory.TOP);
+    if (!atypeFactory.getQualifierHierarchy().isSubtype(defaultAnno, resultAnno)) {
+      checker.reportError(
+          constructorElement, "inconsistent.constructor.type", resultAnno, defaultAnno);
+    }
+  }
+
+  /**
+   * Change the default for exception parameter lower bounds to bottom (the default), to prevent
+   * false positives. This is unsound; see the discussion on
+   * https://github.com/typetools/checker-framework/issues/3839.
+   *
+   * <p>TODO: change checking of throws clauses to require that the thrown exception
+   * is @MustCall({}). This would probably eliminate most of the same false positives, without
+   * adding undue false positives.
+   *
+   * @return a set containing only the @MustCall({}) annotation
+   */
+  @Override
+  protected Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotations() {
+    return Collections.singleton(atypeFactory.BOTTOM);
+  }
+
+  /**
+   * Does not issue any warnings.
+   *
+   * <p>This implementation prevents recursing into annotation arguments. Annotation arguments are
+   * literals, which don't have must-call obligations.
+   *
+   * <p>Annotation arguments are treated as return locations for the purposes of defaulting, rather
+   * than parameter locations. This causes them to default incorrectly when the annotation is
+   * defined in bytecode. See https://github.com/typetools/checker-framework/issues/3178 for an
+   * explanation of why this is necessary to avoid false positives.
+   */
+  @Override
+  public Void visitAnnotation(AnnotationTree node, Void p) {
+    return null;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/Reflection.astub b/checker/src/main/java/org/checkerframework/checker/mustcall/Reflection.astub
new file mode 100644
index 0000000..ed22694
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/mustcall/Reflection.astub
@@ -0,0 +1,21 @@
+// Contains assumptions about reflection. In particular, this
+// makes the results of reflection the default (i.e. bottom) type
+// in the Must Call type system. This choice is technically unsound:
+// if e.g. the java.net.Socket constructor is invoked via reflection,
+// then our checker will fail to track it. For usability, we found it
+// much more useful to ignore these warnings than to issue them.
+// If you wish to see warnings related to reflection, remove this stub file
+// when running the checker.
+
+// TODO: make this configurable so that users can easily turn this assumption off,
+// e.g. via a command-line option. For now, keep this as a stub file rather than
+// moving it to the annotated JDK so that re-compiling the checker without this
+// assumption is easy if we ever need to do it.
+
+package java.lang.reflect;
+
+import org.checkerframework.checker.mustcall.qual.MustCall;
+
+class Constructor<T> {
+    @MustCall({}) T newInstance(Object... initArgs);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/SocketAccumulationFrames.astub b/checker/src/main/java/org/checkerframework/checker/mustcall/SocketAccumulationFrames.astub
new file mode 100644
index 0000000..7370958
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/mustcall/SocketAccumulationFrames.astub
@@ -0,0 +1,41 @@
+// This version of the standard assumptions for sockets is intended for use with accumulation frame support (i.e.
+// without the -AnoAccumulationFrames argument to the checker). It includes @MustCall({}) annotations for no-argument
+// socket constructors, which would be unsound if accumulation frame support was disabled. The checker chooses
+// whether to use it automatically, when invoked via the Object Construction Checker.
+
+package java.net;
+
+
+import org.checkerframework.checker.mustcall.qual.*;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+
+class Socket implements Closeable {
+    @MustCall({}) Socket();
+    @MustCall({}) Socket(Proxy arg0);
+}
+
+class ServerSocket implements Closeable {
+    @MustCall({}) ServerSocket() throws IOException;
+}
+
+package javax.net;
+
+class SocketFactory {
+    @Owning @MustCall({})  Socket createSocket() throws IOException;
+}
+
+class ServerSocketFactory {
+    @Owning @MustCall({})  ServerSocket createServerSocket() throws IOException;
+}
+
+package java.nio.channels;
+
+class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel {
+    static @MustCall({})  SocketChannel open() throws IOException;
+}
+
+class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel {
+    static @MustCall({})  ServerSocketChannel open() throws IOException;
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/messages.properties b/checker/src/main/java/org/checkerframework/checker/mustcall/messages.properties
new file mode 100644
index 0000000..3d787c4
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/mustcall/messages.properties
@@ -0,0 +1,2 @@
+inconsistent.mustcall.subtype=%s is annotated as %s, but one of its supertypes is annotated %s
+createsobligation.target.unparseable=The method %s is annotated as @CreatesObligation, but the target (%s) was unparseable in the current context. Rewrite your code so that the relevant expression is a local variable.
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/CollectionToArrayHeuristics.java b/checker/src/main/java/org/checkerframework/checker/nullness/CollectionToArrayHeuristics.java
new file mode 100644
index 0000000..1f2fcab
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/CollectionToArrayHeuristics.java
@@ -0,0 +1,222 @@
+package org.checkerframework.checker.nullness;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+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.ExecutableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Determines the nullness type of calls to {@link java.util.Collection#toArray()}.
+ *
+ * @checker_framework.manual #nullness-collection-toarray Nullness and conversions from collections
+ *     to arrays
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+public class CollectionToArrayHeuristics {
+
+  /** The processing environment. */
+  private final ProcessingEnvironment processingEnv;
+  /** The checker, used for issuing diagnostic messages. */
+  private final BaseTypeChecker checker;
+  /** The type factory. */
+  private final NullnessAnnotatedTypeFactory atypeFactory;
+
+  /** Whether to trust {@code @ArrayLen(0)} annotations. */
+  private final boolean trustArrayLenZero;
+
+  /** The Collection type. */
+  private final AnnotatedDeclaredType collectionType;
+  /** The Collection.toArray(T[]) method. */
+  private final ExecutableElement collectionToArrayE;
+  /** The Collection.size() method. */
+  private final ExecutableElement size;
+  /** The ArrayLen.value field/element. */
+  private final ExecutableElement arrayLenValueElement;
+
+  /**
+   * Create a CollectionToArrayHeuristics.
+   *
+   * @param checker the checker, used for issuing diagnostic messages
+   * @param factory the type factory
+   */
+  public CollectionToArrayHeuristics(
+      BaseTypeChecker checker, NullnessAnnotatedTypeFactory factory) {
+    this.processingEnv = checker.getProcessingEnvironment();
+    this.checker = checker;
+    this.atypeFactory = factory;
+
+    this.collectionType =
+        factory.fromElement(ElementUtils.getTypeElement(processingEnv, Collection.class));
+    this.collectionToArrayE =
+        TreeUtils.getMethod("java.util.Collection", "toArray", processingEnv, "T[]");
+    this.size = TreeUtils.getMethod("java.util.Collection", "size", 0, processingEnv);
+    this.arrayLenValueElement = TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv);
+
+    this.trustArrayLenZero =
+        checker.getLintOption(
+            NullnessChecker.LINT_TRUSTARRAYLENZERO, NullnessChecker.LINT_DEFAULT_TRUSTARRAYLENZERO);
+  }
+
+  /**
+   * If the method invocation is a call to {@code toArray}, then it manipulates the returned type of
+   * {@code method} arg to contain the appropriate nullness. Otherwise, it does nothing.
+   *
+   * @param tree method invocation tree
+   * @param method invoked method type
+   */
+  public void handle(MethodInvocationTree tree, AnnotatedExecutableType method) {
+    if (TreeUtils.isMethodInvocation(tree, collectionToArrayE, processingEnv)) {
+      assert !tree.getArguments().isEmpty() : tree;
+      ExpressionTree argument = tree.getArguments().get(0);
+      boolean receiverIsNonNull = receiverIsCollectionOfNonNullElements(tree);
+      boolean argIsHandled =
+          isHandledArrayCreation(argument, receiverName(tree.getMethodSelect()))
+              || (trustArrayLenZero && isArrayLenZeroFieldAccess(argument));
+      setComponentNullness(receiverIsNonNull && argIsHandled, method.getReturnType());
+
+      // TODO: We need a mechanism to prevent nullable collections
+      // from inserting null elements into a nonnull arrays.
+      if (!receiverIsNonNull) {
+        setComponentNullness(false, method.getParameterTypes().get(0));
+      }
+
+      if (receiverIsNonNull && !argIsHandled) {
+        if (argument.getKind() != Tree.Kind.NEW_ARRAY) {
+          checker.reportWarning(tree, "toarray.nullable.elements.not.newarray");
+        } else {
+          checker.reportWarning(tree, "toarray.nullable.elements.mismatched.size");
+        }
+      }
+    }
+  }
+
+  /**
+   * Sets the nullness of the component of the array type.
+   *
+   * @param isNonNull indicates which annotation ({@code NonNull} or {@code Nullable}) should be
+   *     inserted
+   * @param type the array type
+   */
+  private void setComponentNullness(boolean isNonNull, AnnotatedTypeMirror type) {
+    assert type.getKind() == TypeKind.ARRAY;
+    AnnotatedTypeMirror compType = ((AnnotatedArrayType) type).getComponentType();
+    compType.replaceAnnotation(isNonNull ? atypeFactory.NONNULL : atypeFactory.NULLABLE);
+  }
+
+  /**
+   * Returns true if {@code argument} is one of the array creation trees that the heuristic handles.
+   *
+   * @param argument the tree passed to {@link Collection#toArray(Object[]) Collection.toArray(T[])}
+   * @param receiver the expression for the receiver collection
+   * @return true if the argument is handled and assume to return nonnull elements
+   */
+  private boolean isHandledArrayCreation(Tree argument, String receiver) {
+    if (argument.getKind() != Tree.Kind.NEW_ARRAY) {
+      return false;
+    }
+    NewArrayTree newArr = (NewArrayTree) argument;
+
+    // empty array initializer
+    if (newArr.getInitializers() != null) {
+      return newArr.getInitializers().isEmpty();
+    }
+
+    assert !newArr.getDimensions().isEmpty();
+    Tree dimension = newArr.getDimensions().get(newArr.getDimensions().size() - 1);
+
+    // 0-length array creation
+    if (dimension.toString().equals("0")) {
+      return true;
+    }
+
+    // size()-length array creation
+    if (TreeUtils.isMethodInvocation(dimension, size, processingEnv)) {
+      MethodInvocationTree invok = (MethodInvocationTree) dimension;
+      String invokReceiver = receiverName(invok.getMethodSelect());
+      return invokReceiver.equals(receiver);
+    }
+
+    return false;
+  }
+
+  /**
+   * Returns true if the argument is a field access expression, where the field has declared type
+   * {@code @ArrayLen(0)}.
+   *
+   * @param argument an expression tree
+   * @return true if the argument is a field access expression, where the field has declared type
+   *     {@code @ArrayLen(0)}
+   */
+  private boolean isArrayLenZeroFieldAccess(ExpressionTree argument) {
+    Element el = TreeUtils.elementFromUse(argument);
+    if (el != null && el.getKind().isField()) {
+      TypeMirror t = ElementUtils.getType(el);
+      if (t.getKind() == TypeKind.ARRAY) {
+        List<? extends AnnotationMirror> ams = t.getAnnotationMirrors();
+        for (AnnotationMirror am : ams) {
+          if (atypeFactory.areSameByClass(am, ArrayLen.class)) {
+            List<Integer> lens =
+                AnnotationUtils.getElementValueArray(am, arrayLenValueElement, Integer.class);
+            if (lens.size() == 1 && lens.get(0) == 0) {
+              return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns {@code true} if the method invocation tree receiver is collection that contains
+   * non-null elements (i.e. its type argument is {@code @NonNull}.
+   *
+   * @param tree a method invocation
+   * @return true if the receiver is a collection of non-null elements
+   */
+  private boolean receiverIsCollectionOfNonNullElements(MethodInvocationTree tree) {
+    // check receiver
+    AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree);
+    AnnotatedDeclaredType collection =
+        AnnotatedTypes.asSuper(atypeFactory, receiver, collectionType);
+
+    if (collection.getTypeArguments().isEmpty() // raw type
+        || !collection.getTypeArguments().get(0).hasEffectiveAnnotation(atypeFactory.NONNULL)) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * The name of the receiver object of the tree.
+   *
+   * @param tree either an identifier tree or a member select tree
+   */
+  // This method is quite sloppy, but works most of the time
+  private String receiverName(Tree tree) {
+    if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
+      return ((MemberSelectTree) tree).getExpression().toString();
+    } else {
+      return "this";
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnalysis.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnalysis.java
new file mode 100644
index 0000000..980d7d4
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnalysis.java
@@ -0,0 +1,50 @@
+package org.checkerframework.checker.nullness;
+
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAbstractValue;
+import org.checkerframework.javacutil.Pair;
+
+/** Boilerplate code to glue together all the parts the KeyFor dataflow classes. */
+public class KeyForAnalysis extends CFAbstractAnalysis<KeyForValue, KeyForStore, KeyForTransfer> {
+
+  public KeyForAnalysis(
+      BaseTypeChecker checker,
+      KeyForAnnotatedTypeFactory factory,
+      List<Pair<VariableElement, KeyForValue>> fieldValues,
+      int maxCountBeforeWidening) {
+    super(checker, factory, fieldValues, maxCountBeforeWidening);
+  }
+
+  public KeyForAnalysis(
+      BaseTypeChecker checker,
+      KeyForAnnotatedTypeFactory factory,
+      List<Pair<VariableElement, KeyForValue>> fieldValues) {
+    super(checker, factory, fieldValues);
+  }
+
+  @Override
+  public KeyForStore createEmptyStore(boolean sequentialSemantics) {
+    return new KeyForStore(this, sequentialSemantics);
+  }
+
+  @Override
+  public KeyForStore createCopiedStore(KeyForStore store) {
+    return new KeyForStore(store);
+  }
+
+  @Override
+  public KeyForValue createAbstractValue(
+      Set<AnnotationMirror> annotations, TypeMirror underlyingType) {
+
+    if (!CFAbstractValue.validateSet(annotations, underlyingType, qualifierHierarchy)) {
+      return null;
+    }
+    return new KeyForValue(this, annotations, underlyingType);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java
new file mode 100644
index 0000000..c9b0cbe
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java
@@ -0,0 +1,266 @@
+package org.checkerframework.checker.nullness;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.KeyForBottom;
+import org.checkerframework.checker.nullness.qual.PolyKeyFor;
+import org.checkerframework.checker.nullness.qual.UnknownKeyFor;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.util.NodeUtils;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.DefaultTypeHierarchy;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.SubtypeIsSupersetQualifierHierarchy;
+import org.checkerframework.framework.type.TypeHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreeUtils;
+
+public class KeyForAnnotatedTypeFactory
+    extends GenericAnnotatedTypeFactory<KeyForValue, KeyForStore, KeyForTransfer, KeyForAnalysis> {
+
+  /** The @{@link UnknownKeyFor} annotation. */
+  protected final AnnotationMirror UNKNOWNKEYFOR =
+      AnnotationBuilder.fromClass(elements, UnknownKeyFor.class);
+  /** The @{@link KeyForBottom} annotation. */
+  protected final AnnotationMirror KEYFORBOTTOM =
+      AnnotationBuilder.fromClass(elements, KeyForBottom.class);
+
+  /** The canonical name of the KeyFor class. */
+  protected final @CanonicalName String KEYFOR_NAME = KeyFor.class.getCanonicalName();
+
+  /** The Map.containsKey method. */
+  private final ExecutableElement mapContainsKey =
+      TreeUtils.getMethod("java.util.Map", "containsKey", 1, processingEnv);
+  /** The Map.get method. */
+  private final ExecutableElement mapGet =
+      TreeUtils.getMethod("java.util.Map", "get", 1, processingEnv);
+  /** The Map.put method. */
+  private final ExecutableElement mapPut =
+      TreeUtils.getMethod("java.util.Map", "put", 2, processingEnv);
+  /** The KeyFor.value field/element. */
+  protected final ExecutableElement keyForValueElement =
+      TreeUtils.getMethod(KeyFor.class, "value", 0, processingEnv);
+
+  /** Moves annotations from one side of a pseudo-assignment to the other. */
+  private final KeyForPropagator keyForPropagator = new KeyForPropagator(UNKNOWNKEYFOR);
+
+  /**
+   * If true, assume the argument to Map.get is always a key for the receiver map. This is set by
+   * the `-AassumeKeyFor` command-line argument. However, if the Nullness Checker is being run, then
+   * `-AassumeKeyFor` disables the Map Key Checker.
+   */
+  private final boolean assumeKeyFor;
+
+  /**
+   * Creates a new KeyForAnnotatedTypeFactory.
+   *
+   * @param checker the associated checker
+   */
+  public KeyForAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker, true);
+
+    assumeKeyFor = checker.hasOption("assumeKeyFor");
+
+    // Add compatibility annotations:
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.nullness.compatqual.KeyForDecl", KeyFor.class, true);
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.nullness.compatqual.KeyForType", KeyFor.class, true);
+
+    // While strictly required for soundness, this leads to too many false positives.  Printing
+    // a key or putting it in a map erases all knowledge of what maps it was a key for.
+    // TODO: Revisit when side effect annotations are more precise.
+    // sideEffectsUnrefineAliases = true;
+
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new LinkedHashSet<>(
+        Arrays.asList(KeyFor.class, UnknownKeyFor.class, KeyForBottom.class, PolyKeyFor.class));
+  }
+
+  @Override
+  public ParameterizedExecutableType constructorFromUse(NewClassTree tree) {
+    ParameterizedExecutableType result = super.constructorFromUse(tree);
+    keyForPropagator.propagateNewClassTree(tree, result.executableType.getReturnType(), this);
+    return result;
+  }
+
+  @Override
+  protected TypeHierarchy createTypeHierarchy() {
+    return new KeyForTypeHierarchy(
+        checker,
+        getQualifierHierarchy(),
+        checker.getBooleanOption("ignoreRawTypeArguments", true),
+        checker.hasOption("invariantArrays"));
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(
+        super.createTreeAnnotator(), new KeyForPropagationTreeAnnotator(this, keyForPropagator));
+  }
+
+  // TODO: work on removing this class
+  protected static class KeyForTypeHierarchy extends DefaultTypeHierarchy {
+
+    public KeyForTypeHierarchy(
+        BaseTypeChecker checker,
+        QualifierHierarchy qualifierHierarchy,
+        boolean ignoreRawTypes,
+        boolean invariantArrayComponents) {
+      super(checker, qualifierHierarchy, ignoreRawTypes, invariantArrayComponents);
+    }
+
+    @Override
+    protected boolean isSubtype(
+        AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, AnnotationMirror top) {
+      // TODO: THIS IS FROM THE OLD TYPE HIERARCHY.  WE SHOULD FIX DATA-FLOW/PROPAGATION TO DO
+      // THE RIGHT THING
+      if (supertype.getKind() == TypeKind.TYPEVAR && subtype.getKind() == TypeKind.TYPEVAR) {
+        // TODO: Investigate whether there is a nicer and more proper way to
+        // get assignments between two type variables working.
+        if (supertype.getAnnotations().isEmpty()) {
+          return true;
+        }
+      }
+
+      // Otherwise Covariant would cause trouble.
+      if (subtype.hasAnnotation(KeyForBottom.class)) {
+        return true;
+      }
+      return super.isSubtype(subtype, supertype, top);
+    }
+  }
+
+  @Override
+  protected KeyForAnalysis createFlowAnalysis(
+      List<Pair<VariableElement, KeyForValue>> fieldValues) {
+    // Explicitly call the constructor instead of using reflection.
+    return new KeyForAnalysis(checker, this, fieldValues);
+  }
+
+  @Override
+  public KeyForTransfer createFlowTransferFunction(
+      CFAbstractAnalysis<KeyForValue, KeyForStore, KeyForTransfer> analysis) {
+    // Explicitly call the constructor instead of using reflection.
+    return new KeyForTransfer((KeyForAnalysis) analysis);
+  }
+
+  /**
+   * Given a string array 'values', returns an AnnotationMirror corresponding to @KeyFor(values)
+   *
+   * @param values the values for the {@code @KeyFor} annotation
+   * @return a {@code @KeyFor} annotation with the given values
+   */
+  public AnnotationMirror createKeyForAnnotationMirrorWithValue(Set<String> values) {
+    // Create an AnnotationBuilder with the ArrayList
+    AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), KeyFor.class);
+    builder.setValue("value", values.toArray());
+
+    // Return the resulting AnnotationMirror
+    return builder.build();
+  }
+
+  /**
+   * Given a string 'value', returns an AnnotationMirror corresponding to @KeyFor(value)
+   *
+   * @param value the argument to {@code @KeyFor}
+   * @return a {@code @KeyFor} annotation with the given value
+   */
+  public AnnotationMirror createKeyForAnnotationMirrorWithValue(String value) {
+    return createKeyForAnnotationMirrorWithValue(Collections.singleton(value));
+  }
+
+  /**
+   * Returns true if the expression tree is a key for the map.
+   *
+   * @param mapExpression expression that has type Map
+   * @param tree expression that might be a key for the map
+   * @return whether or not the expression is a key for the map
+   */
+  public boolean isKeyForMap(String mapExpression, ExpressionTree tree) {
+    // This test only has an effect if the Map Key Checker is being run on its own.  If the Nullness
+    // Checker is being run, then -AassumeKeyFor disables the Map Key Checker.
+    if (assumeKeyFor) {
+      return true;
+    }
+    Collection<String> maps = null;
+    AnnotatedTypeMirror type = getAnnotatedType(tree);
+    AnnotationMirror keyForAnno = type.getAnnotation(KeyFor.class);
+    if (keyForAnno != null) {
+      maps = AnnotationUtils.getElementValueArray(keyForAnno, keyForValueElement, String.class);
+    } else {
+      KeyForValue value = getInferredValueFor(tree);
+      if (value != null) {
+        maps = value.getKeyForMaps();
+      }
+    }
+
+    return maps != null && maps.contains(mapExpression);
+  }
+
+  @Override
+  public QualifierHierarchy createQualifierHierarchy() {
+    return new SubtypeIsSupersetQualifierHierarchy(getSupportedTypeQualifiers(), processingEnv);
+  }
+
+  /** Returns true if the node is an invocation of Map.containsKey. */
+  boolean isMapContainsKey(Tree tree) {
+    return TreeUtils.isMethodInvocation(tree, mapContainsKey, getProcessingEnv());
+  }
+
+  /** Returns true if the node is an invocation of Map.get. */
+  boolean isMapGet(Tree tree) {
+    return TreeUtils.isMethodInvocation(tree, mapGet, getProcessingEnv());
+  }
+
+  /** Returns true if the node is an invocation of Map.put. */
+  boolean isMapPut(Tree tree) {
+    return TreeUtils.isMethodInvocation(tree, mapPut, getProcessingEnv());
+  }
+
+  /** Returns true if the node is an invocation of Map.containsKey. */
+  boolean isMapContainsKey(Node node) {
+    return NodeUtils.isMethodInvocation(node, mapContainsKey, getProcessingEnv());
+  }
+
+  /** Returns true if the node is an invocation of Map.get. */
+  boolean isMapGet(Node node) {
+    return NodeUtils.isMethodInvocation(node, mapGet, getProcessingEnv());
+  }
+
+  /** Returns true if the node is an invocation of Map.put. */
+  boolean isMapPut(Node node) {
+    return NodeUtils.isMethodInvocation(node, mapPut, getProcessingEnv());
+  }
+
+  /** Returns false. Redundancy in the KeyFor hierarchy is not worth warning about. */
+  @Override
+  public boolean shouldWarnIfStubRedundantWithBytecode() {
+    return false;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagationTreeAnnotator.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagationTreeAnnotator.java
new file mode 100644
index 0000000..67616f4
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagationTreeAnnotator.java
@@ -0,0 +1,104 @@
+package org.checkerframework.checker.nullness;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.VariableTree;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.checker.nullness.KeyForPropagator.PropagationDirection;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * For the following initializations we wish to propagate the annotations from the left-hand side to
+ * the right-hand side or vice versa:
+ *
+ * <p>1. If a keySet is being saved to a newly declared set, we transfer the annotations from the
+ * keySet to the lhs. e.g.,
+ *
+ * <pre>{@code
+ * // The user is not required to explicitly annotate the LHS's type argument with @KeyFor("m")
+ * Set<String> keySet = m.keySet();
+ * }</pre>
+ *
+ * 2. If a variable declaration contains type arguments with an @KeyFor annotation and its
+ * initializer is a new class tree with corresponding type arguments that have an @UknownKeyFor
+ * primary annotation, we transfer from the LHS to RHS. e.g.,
+ *
+ * <pre>{@code
+ * // The user does not have to write @KeyFor("m") on both sides
+ * List<@KeyFor("m") String> keys = new ArrayList<String>();
+ * }</pre>
+ *
+ * 3. IMPORTANT NOTE: The following case must be (and is) handled in KeyForAnnotatedTypeFactory. In
+ * BaseTypeVisitor we check to make sure that the constructor called in a NewClassTree is actually
+ * compatible with the annotations placed on the NewClassTree. This requires that, prior to this
+ * check we also propagate the annotations to this constructor in constructorFromUse so that the
+ * constructor call matches the type given to the NewClassTree.
+ *
+ * @see
+ *     org.checkerframework.checker.nullness.KeyForAnnotatedTypeFactory#constructorFromUse(com.sun.source.tree.NewClassTree)
+ *     <p>Note propagation only occurs between two AnnotatedDeclaredTypes. If either side is not an
+ *     AnnotatedDeclaredType then this class does nothing.
+ */
+public class KeyForPropagationTreeAnnotator extends TreeAnnotator {
+  private final KeyForPropagator keyForPropagator;
+  private final ExecutableElement keySetMethod;
+
+  public KeyForPropagationTreeAnnotator(
+      AnnotatedTypeFactory atypeFactory, KeyForPropagator propagationTreeAnnotator) {
+    super(atypeFactory);
+    this.keyForPropagator = propagationTreeAnnotator;
+    keySetMethod =
+        TreeUtils.getMethod("java.util.Map", "keySet", 0, atypeFactory.getProcessingEnv());
+  }
+
+  /**
+   * Returns true iff expression is a call to java.util.Map.KeySet.
+   *
+   * @return true iff expression is a call to java.util.Map.KeySet
+   */
+  public boolean isCallToKeyset(ExpressionTree expression) {
+    return TreeUtils.isMethodInvocation(expression, keySetMethod, atypeFactory.getProcessingEnv());
+  }
+
+  /**
+   * Transfers annotations on type arguments from the initializer to the variableTree, if the
+   * initializer is a call to java.util.Map.keySet.
+   */
+  @Override
+  public Void visitVariable(VariableTree variableTree, AnnotatedTypeMirror type) {
+    super.visitVariable(variableTree, type);
+
+    // This should only happen on Map.keySet();
+    if (type.getKind() == TypeKind.DECLARED) {
+      final ExpressionTree initializer = variableTree.getInitializer();
+
+      if (isCallToKeyset(initializer)) {
+        final AnnotatedDeclaredType variableType = (AnnotatedDeclaredType) type;
+        final AnnotatedTypeMirror initializerType = atypeFactory.getAnnotatedType(initializer);
+
+        // Propagate just for declared (class) types, not for array types, boxed primitives, etc.
+        if (variableType.getKind() == TypeKind.DECLARED) {
+          keyForPropagator.propagate(
+              (AnnotatedDeclaredType) initializerType,
+              variableType,
+              PropagationDirection.TO_SUPERTYPE,
+              atypeFactory);
+        }
+      }
+    }
+
+    return null;
+  }
+
+  /** Transfers annotations to type if the left hand side is a variable declaration. */
+  @Override
+  public Void visitNewClass(NewClassTree node, AnnotatedTypeMirror type) {
+    keyForPropagator.propagateNewClassTree(node, type, (KeyForAnnotatedTypeFactory) atypeFactory);
+    return super.visitNewClass(node, type);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java
new file mode 100644
index 0000000..00e1f4f
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java
@@ -0,0 +1,199 @@
+package org.checkerframework.checker.nullness;
+
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.UnknownKeyFor;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeReplacer;
+import org.checkerframework.framework.util.TypeArgumentMapper;
+import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * KeyForPropagator is used to move nested KeyFor annotations in type arguments from one side of a
+ * pseudo-assignment to the other. The KeyForPropagationTreeAnnotator details the locations in which
+ * this occurs.
+ *
+ * @see org.checkerframework.checker.nullness.KeyForPropagationTreeAnnotator
+ */
+public class KeyForPropagator {
+  public static enum PropagationDirection {
+    // transfer FROM the super type to the subtype
+    TO_SUBTYPE,
+
+    // transfer FROM the subtype to the supertype
+    TO_SUPERTYPE,
+
+    // first execute TO_SUBTYPE then TO_SUPERTYPE, if TO_SUBTYPE actually transfers
+    // an annotation for a particular type T then T will not be affected by the
+    // TO_SUPERTYPE transfer because it will already have a KeyFor annotation
+    BOTH
+  }
+
+  /**
+   * The top type of the KeyFor hierarchy.
+   *
+   * <p>This class will replace @UnknownKeyFor annotations. It will also add annotations when they
+   * are missing for types that require primary annotation (i.e. not TypeVars, Wildcards,
+   * Intersections, or Unions).
+   */
+  private final AnnotationMirror UNKNOWN_KEYFOR;
+
+  /** Instance of {@link KeyForPropagationReplacer}. */
+  private final KeyForPropagationReplacer replacer = new KeyForPropagationReplacer();
+
+  /**
+   * Creates a KeyForPropagator
+   *
+   * @param unknownKeyfor an {@link UnknownKeyFor} annotation
+   */
+  public KeyForPropagator(AnnotationMirror unknownKeyfor) {
+    this.UNKNOWN_KEYFOR = unknownKeyfor;
+  }
+
+  /**
+   * Propagate annotations from the type arguments of one type to another. Which type is the source
+   * and destination of the annotations depends on the direction parameter. Only @KeyFor annotations
+   * are propagated and only if the type to which it would be propagated contains an @UnknownKeyFor
+   * or contains no key for annotations of any kind. If any of the type arguments are wildcards than
+   * they are ignored.
+   *
+   * <p>Note the primary annotations of subtype/supertype are not used.
+   *
+   * <p>Simple Example:
+   *
+   * <pre>{@code
+   * typeOf(subtype) = ArrayList<@KeyFor("a") String>
+   * typeOf(supertype) = List<@UnknownKeyFor String>
+   * direction = TO_SUPERTYPE
+   * }</pre>
+   *
+   * The type of supertype after propagate would be: {@code List<@KeyFor("a") String>}
+   *
+   * <p>A more complex example would be:
+   *
+   * <pre>{@code
+   * typeOf(subtype) = HashMap<@UnknownKeyFor String, @KeyFor("b") List<@KeyFor("c") String>>
+   * typeOf(supertype) = Map<@KeyFor("a") String, @KeyFor("b") List<@KeyFor("c") String>>
+   * direction = TO_SUBTYPE
+   * }</pre>
+   *
+   * The type of subtype after propagate would be: {@code HashMap<@KeyFor("a") String, @KeyFor("b")
+   * List<@KeyFor("c") String>>}
+   */
+  public void propagate(
+      final AnnotatedDeclaredType subtype,
+      final AnnotatedDeclaredType supertype,
+      PropagationDirection direction,
+      final AnnotatedTypeFactory typeFactory) {
+    final TypeElement subtypeElement = (TypeElement) subtype.getUnderlyingType().asElement();
+    final TypeElement supertypeElement = (TypeElement) supertype.getUnderlyingType().asElement();
+    final Types types = typeFactory.getProcessingEnv().getTypeUtils();
+
+    // Note: The right hand side of this or expression will cover raw types
+    if (subtype.getTypeArguments().isEmpty()) {
+      return;
+    } // else
+
+    // this can happen for two reasons:
+    //  1) the subclass introduced NEW type arguments when the superclass had none
+    //  2) the supertype was RAW.
+    // In either case, there is no reason to propagate
+    if (supertype.getTypeArguments().isEmpty()) {
+      return;
+    }
+
+    Set<Pair<Integer, Integer>> typeParamMappings =
+        TypeArgumentMapper.mapTypeArgumentIndices(subtypeElement, supertypeElement, types);
+
+    final List<AnnotatedTypeMirror> subtypeArgs = subtype.getTypeArguments();
+    final List<AnnotatedTypeMirror> supertypeArgs = supertype.getTypeArguments();
+
+    for (final Pair<Integer, Integer> path : typeParamMappings) {
+      final AnnotatedTypeMirror subtypeArg = subtypeArgs.get(path.first);
+      final AnnotatedTypeMirror supertypeArg = supertypeArgs.get(path.second);
+
+      if (subtypeArg.getKind() == TypeKind.WILDCARD
+          || supertypeArg.getKind() == TypeKind.WILDCARD) {
+        continue;
+      }
+
+      switch (direction) {
+        case TO_SUBTYPE:
+          replacer.visit(supertypeArg, subtypeArg);
+          break;
+
+        case TO_SUPERTYPE:
+          replacer.visit(subtypeArg, supertypeArg);
+          break;
+
+        case BOTH:
+          // note if they both have an annotation nothing will happen
+          replacer.visit(subtypeArg, supertypeArg);
+          replacer.visit(supertypeArg, subtypeArg);
+          break;
+      }
+    }
+  }
+
+  /**
+   * Propagate annotations from the type arguments of {@code type} to the assignment context of
+   * {@code newClassTree} if one exists.
+   *
+   * @param newClassTree new class tree
+   * @param type annotated type of {@code newClassTree}
+   * @param atypeFactory factory
+   */
+  public void propagateNewClassTree(
+      NewClassTree newClassTree,
+      AnnotatedTypeMirror type,
+      KeyForAnnotatedTypeFactory atypeFactory) {
+    Pair<Tree, AnnotatedTypeMirror> context = atypeFactory.getVisitorState().getAssignmentContext();
+    if (type.getKind() != TypeKind.DECLARED || context == null || context.first == null) {
+      return;
+    }
+    TreePath path = atypeFactory.getPath(newClassTree);
+    if (path == null) {
+      return;
+    }
+    AnnotatedTypeMirror assignedTo = TypeArgInferenceUtil.assignedTo(atypeFactory, path);
+    if (assignedTo == null) {
+      return;
+    }
+    // array types and boxed primitives etc don't require propagation
+    if (assignedTo.getKind() == TypeKind.DECLARED) {
+      propagate(
+          (AnnotatedDeclaredType) type,
+          (AnnotatedDeclaredType) assignedTo,
+          PropagationDirection.TO_SUBTYPE,
+          atypeFactory);
+    }
+  }
+
+  /**
+   * An {@link AnnotatedTypeReplacer} that copies the annotation in KeyFor hierarchy from the first
+   * types to the second type, if the second type is annotated with @UnknownKeyFor or has no
+   * annotation in the KeyFor hierarchy.
+   */
+  private class KeyForPropagationReplacer extends AnnotatedTypeReplacer {
+    @Override
+    protected void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) {
+      AnnotationMirror fromKeyFor = from.getAnnotationInHierarchy(UNKNOWN_KEYFOR);
+      if (fromKeyFor != null) {
+        if (to.hasAnnotation(UNKNOWN_KEYFOR)
+            || to.getAnnotationInHierarchy(UNKNOWN_KEYFOR) == null) {
+          to.replaceAnnotation(fromKeyFor);
+        }
+      }
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForStore.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForStore.java
new file mode 100644
index 0000000..f59c9ef
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForStore.java
@@ -0,0 +1,15 @@
+package org.checkerframework.checker.nullness;
+
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAbstractStore;
+
+public class KeyForStore extends CFAbstractStore<KeyForValue, KeyForStore> {
+  public KeyForStore(
+      CFAbstractAnalysis<KeyForValue, KeyForStore, ?> analysis, boolean sequentialSemantics) {
+    super(analysis, sequentialSemantics);
+  }
+
+  protected KeyForStore(CFAbstractStore<KeyForValue, KeyForStore> other) {
+    super(other);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForSubchecker.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForSubchecker.java
new file mode 100644
index 0000000..bce1270
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForSubchecker.java
@@ -0,0 +1,14 @@
+package org.checkerframework.checker.nullness;
+
+import javax.annotation.processing.SupportedOptions;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+/**
+ * A type-checker for determining which values are keys for which maps. Typically used as part of
+ * the compound checker for the nullness type system.
+ *
+ * @checker_framework.manual #map-key-checker Map Key Checker
+ * @checker_framework.manual #nullness-checker Nullness Checker
+ */
+@SupportedOptions({"assumeKeyFor"})
+public class KeyForSubchecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForTransfer.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForTransfer.java
new file mode 100644
index 0000000..0252f13
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForTransfer.java
@@ -0,0 +1,102 @@
+package org.checkerframework.checker.nullness;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAbstractTransfer;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * KeyForTransfer ensures that java.util.Map.put and containsKey cause the appropriate @KeyFor
+ * annotation to be added to the key.
+ */
+public class KeyForTransfer extends CFAbstractTransfer<KeyForValue, KeyForStore, KeyForTransfer> {
+
+  /** The KeyFor.value element/field. */
+  ExecutableElement keyForValueElement;
+
+  /**
+   * Creates a new KeyForTransfer.
+   *
+   * @param analysis the analysis
+   */
+  public KeyForTransfer(KeyForAnalysis analysis) {
+    super(analysis);
+
+    ProcessingEnvironment processingEnv =
+        ((KeyForAnnotatedTypeFactory) analysis.getTypeFactory()).getProcessingEnv();
+    keyForValueElement = TreeUtils.getMethod(KeyFor.class, "value", 0, processingEnv);
+  }
+
+  /*
+   * Provided that m is of a type that implements interface java.util.Map:
+   * <ul>
+   * <li>Given a call m.containsKey(k), ensures that k is @KeyFor("m") in the thenStore of the transfer result.
+   * <li>Given a call m.put(k, ...), ensures that k is @KeyFor("m") in the thenStore and elseStore of the transfer result.
+   * </ul>
+   */
+  @Override
+  public TransferResult<KeyForValue, KeyForStore> visitMethodInvocation(
+      MethodInvocationNode node, TransferInput<KeyForValue, KeyForStore> in) {
+
+    TransferResult<KeyForValue, KeyForStore> result = super.visitMethodInvocation(node, in);
+    KeyForAnnotatedTypeFactory factory = (KeyForAnnotatedTypeFactory) analysis.getTypeFactory();
+    if (factory.isMapContainsKey(node) || factory.isMapPut(node)) {
+
+      Node receiver = node.getTarget().getReceiver();
+      JavaExpression receiverJe = JavaExpression.fromNode(receiver);
+      String mapName = receiverJe.toString();
+      JavaExpression keyExpr = JavaExpression.fromNode(node.getArgument(0));
+
+      LinkedHashSet<String> keyForMaps = new LinkedHashSet<>();
+      keyForMaps.add(mapName);
+
+      final KeyForValue previousKeyValue = in.getValueOfSubNode(node.getArgument(0));
+      if (previousKeyValue != null) {
+        for (AnnotationMirror prevAm : previousKeyValue.getAnnotations()) {
+          if (prevAm != null && factory.areSameByClass(prevAm, KeyFor.class)) {
+            keyForMaps.addAll(getKeys(prevAm));
+          }
+        }
+      }
+
+      AnnotationMirror am = factory.createKeyForAnnotationMirrorWithValue(keyForMaps);
+
+      if (factory.isMapContainsKey(node)) {
+        // method is Map.containsKey
+        result.getThenStore().insertValue(keyExpr, am);
+      } else {
+        // method is Map.put
+        result.getThenStore().insertValue(keyExpr, am);
+        result.getElseStore().insertValue(keyExpr, am);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns the elements/arguments of a {@code @KeyFor} annotation.
+   *
+   * @param keyFor a {@code @KeyFor} annotation
+   * @return the elements/arguments of a {@code @KeyFor} annotation
+   */
+  private Set<String> getKeys(final AnnotationMirror keyFor) {
+    if (keyFor.getElementValues().isEmpty()) {
+      return Collections.emptySet();
+    }
+
+    return new LinkedHashSet<>(
+        AnnotationUtils.getElementValueArray(keyFor, keyForValueElement, String.class));
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForValue.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForValue.java
new file mode 100644
index 0000000..b886f20
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForValue.java
@@ -0,0 +1,127 @@
+package org.checkerframework.checker.nullness;
+
+import com.sun.source.tree.ExpressionTree;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAbstractValue;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * KeyForValue holds additional information about which maps this value is a key for. This extra
+ * information is required when adding the @KeyFor qualifier to the type is not a refinement of the
+ * type. For example,
+ *
+ * <pre>
+ * {@code Map<T, Object> map = ...;}
+ * {@code <T> T method(T param) { }
+ *   if (map.contains(param) {
+ *     {@code @NonNull Object o = map.get(param);}
+ *     return param;
+ *   }
+ * }
+ * }</pre>
+ *
+ * Inside the if statement, {@code param} is a key for "map". This would normally be represented as
+ * {@code @KeyFor("map") T}, but this is not a subtype of {@code T}, so the type cannot be refined.
+ * Instead, the value for {@code param} includes "map" in the list of keyForMaps. This information
+ * is used in {@link KeyForAnnotatedTypeFactory#isKeyForMap(String, ExpressionTree)}.
+ */
+public class KeyForValue extends CFAbstractValue<KeyForValue> {
+  /**
+   * If the underlying type is a type variable or a wildcard, then this is a set of maps for which
+   * this value is a key. Otherwise, it's null.
+   */
+  // Cannot be final because lub re-assigns; add a new constructor to do this cleanly?
+  private @Nullable Set<String> keyForMaps;
+
+  /** Create an instance. */
+  public KeyForValue(
+      CFAbstractAnalysis<KeyForValue, ?, ?> analysis,
+      Set<AnnotationMirror> annotations,
+      TypeMirror underlyingType) {
+    super(analysis, annotations, underlyingType);
+    KeyForAnnotatedTypeFactory atypeFactory =
+        (KeyForAnnotatedTypeFactory) analysis.getTypeFactory();
+    AnnotationMirror keyfor = atypeFactory.getAnnotationByClass(annotations, KeyFor.class);
+    if (keyfor != null
+        && (underlyingType.getKind() == TypeKind.TYPEVAR
+            || underlyingType.getKind() == TypeKind.WILDCARD)) {
+      List<String> list =
+          AnnotationUtils.getElementValueArray(
+              keyfor, atypeFactory.keyForValueElement, String.class);
+      keyForMaps = new LinkedHashSet<>(list.size());
+      keyForMaps.addAll(list);
+    } else {
+      keyForMaps = null;
+    }
+  }
+
+  /**
+   * If the underlying type is a type variable or a wildcard, then this is a set of maps for which
+   * this value is a key. Otherwise, it's null.
+   */
+  public Set<String> getKeyForMaps() {
+    return keyForMaps;
+  }
+
+  @Override
+  public KeyForValue leastUpperBound(KeyForValue other) {
+    KeyForValue lub = super.leastUpperBound(other);
+    if (other == null || other.keyForMaps == null || this.keyForMaps == null) {
+      return lub;
+    }
+    // Lub the keyForMaps by intersecting the sets.
+    lub.keyForMaps = new LinkedHashSet<>(this.keyForMaps.size());
+    lub.keyForMaps.addAll(this.keyForMaps);
+    lub.keyForMaps.retainAll(other.keyForMaps);
+    if (lub.keyForMaps.isEmpty()) {
+      lub.keyForMaps = null;
+    }
+    return lub;
+  }
+
+  @Override
+  public KeyForValue mostSpecific(KeyForValue other, KeyForValue backup) {
+    KeyForValue mostSpecific = super.mostSpecific(other, backup);
+    if (mostSpecific == null) {
+      if (other == null) {
+        return this;
+      }
+      // mostSpecific is null if the two types are not comparable.  This is normally
+      // because one of this or other is a type variable and annotations is empty, but the
+      // other annotations are not empty.  In this case, copy the keyForMaps and to the
+      // value with the no annotations and return it as most specific.
+      if (other.getAnnotations().isEmpty()) {
+        other.addKeyFor(this.keyForMaps);
+        return other;
+      } else if (this.getAnnotations().isEmpty()) {
+        this.addKeyFor(other.keyForMaps);
+        return this;
+      }
+      return null;
+    }
+
+    mostSpecific.addKeyFor(this.keyForMaps);
+    if (other != null) {
+      mostSpecific.addKeyFor(other.keyForMaps);
+    }
+    return mostSpecific;
+  }
+
+  private void addKeyFor(Set<String> newKeyForMaps) {
+    if (newKeyForMaps == null || newKeyForMaps.isEmpty()) {
+      return;
+    }
+    if (keyForMaps == null) {
+      keyForMaps = new LinkedHashSet<>();
+    }
+    keyForMaps.addAll(newKeyForMaps);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnalysis.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnalysis.java
new file mode 100644
index 0000000..cec0475
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnalysis.java
@@ -0,0 +1,45 @@
+package org.checkerframework.checker.nullness;
+
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAbstractValue;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * The analysis class for the non-null type system (serves as factory for the transfer function,
+ * stores and abstract values.
+ */
+public class NullnessAnalysis
+    extends CFAbstractAnalysis<NullnessValue, NullnessStore, NullnessTransfer> {
+
+  public NullnessAnalysis(
+      BaseTypeChecker checker,
+      NullnessAnnotatedTypeFactory factory,
+      List<Pair<VariableElement, NullnessValue>> fieldValues) {
+    super(checker, factory, fieldValues);
+  }
+
+  @Override
+  public NullnessStore createEmptyStore(boolean sequentialSemantics) {
+    return new NullnessStore(this, sequentialSemantics);
+  }
+
+  @Override
+  public NullnessStore createCopiedStore(NullnessStore s) {
+    return new NullnessStore(s);
+  }
+
+  @Override
+  public NullnessValue createAbstractValue(
+      Set<AnnotationMirror> annotations, TypeMirror underlyingType) {
+    if (!CFAbstractValue.validateSet(annotations, underlyingType, qualifierHierarchy)) {
+      return null;
+    }
+    return new NullnessValue(this, annotations, underlyingType);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnnotatedTypeFactory.java
new file mode 100644
index 0000000..5b06648
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnnotatedTypeFactory.java
@@ -0,0 +1,866 @@
+package org.checkerframework.checker.nullness;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+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.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.initialization.InitializationAnnotatedTypeFactory;
+import org.checkerframework.checker.initialization.qual.FBCBottom;
+import org.checkerframework.checker.initialization.qual.Initialized;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.PolyNull;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeFormatter;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.type.typeannotator.DefaultForTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** The annotated type factory for the nullness type-system. */
+public class NullnessAnnotatedTypeFactory
+    extends InitializationAnnotatedTypeFactory<
+        NullnessValue, NullnessStore, NullnessTransfer, NullnessAnalysis> {
+
+  /** The @{@link NonNull} annotation. */
+  protected final AnnotationMirror NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class);
+  /** The @{@link Nullable} annotation. */
+  protected final AnnotationMirror NULLABLE = AnnotationBuilder.fromClass(elements, Nullable.class);
+  /** The @{@link PolyNull} annotation. */
+  protected final AnnotationMirror POLYNULL = AnnotationBuilder.fromClass(elements, PolyNull.class);
+  /** The @{@link MonotonicNonNull} annotation. */
+  protected final AnnotationMirror MONOTONIC_NONNULL =
+      AnnotationBuilder.fromClass(elements, MonotonicNonNull.class);
+
+  /** Handles invocations of {@link java.lang.System#getProperty(String)}. */
+  protected final SystemGetPropertyHandler systemGetPropertyHandler;
+
+  /** Determines the nullness type of calls to {@link java.util.Collection#toArray()}. */
+  protected final CollectionToArrayHeuristics collectionToArrayHeuristics;
+
+  /** The Class.getCanonicalName() method. */
+  protected final ExecutableElement classGetCanonicalName;
+  /** The Arrays.copyOf() methods that operate on arrays of references. */
+  private final List<ExecutableElement> copyOfMethods;
+
+  /** Cache for the nullness annotations. */
+  protected final Set<Class<? extends Annotation>> nullnessAnnos;
+
+  // List is in alphabetical order.  If you update it, also update
+  // ../../../../../../../../docs/manual/nullness-checker.tex .
+  /** Aliases for {@code @Nonnull}. */
+  private static final List<@FullyQualifiedName String> NONNULL_ALIASES =
+      Arrays.asList(
+          // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/NonNull.java
+          "android.annotation.NonNull",
+          // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/NonNull.java
+          "android.support.annotation.NonNull",
+          // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/androidx/annotation/NonNull.java
+          "androidx.annotation.NonNull",
+          // https://android.googlesource.com/platform/tools/metalava/+/master/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java
+          "androidx.annotation.RecentlyNonNull",
+          "com.sun.istack.internal.NotNull",
+          // http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/NonNull.html
+          "edu.umd.cs.findbugs.annotations.NonNull",
+          // https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/NonNull.java
+          "io.reactivex.annotations.NonNull",
+          // https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/annotations/NonNull.java
+          "io.reactivex.rxjava3.annotations.NonNull",
+          // https://jcp.org/en/jsr/detail?id=305
+          "javax.annotation.Nonnull",
+          // https://javaee.github.io/javaee-spec/javadocs/javax/validation/constraints/NotNull.html
+          "javax.validation.constraints.NotNull",
+          // https://github.com/rzwitserloot/lombok/blob/master/src/core/lombok/NonNull.java
+          "lombok.NonNull",
+          // https://search.maven.org/search?q=a:checker-compat-qual
+          "org.checkerframework.checker.nullness.compatqual.NonNullDecl",
+          "org.checkerframework.checker.nullness.compatqual.NonNullType",
+          // https://janino-compiler.github.io/janino/apidocs/org/codehaus/commons/nullanalysis/NotNull.html
+          "org.codehaus.commons.nullanalysis.NotNull",
+          // https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/NonNull.html
+          "org.eclipse.jdt.annotation.NonNull",
+          // https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java
+          "org.eclipse.jgit.annotations.NonNull",
+          // https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/NotNull.java
+          "org.jetbrains.annotations.NotNull",
+          // http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/NonNull.java
+          "org.jmlspecs.annotation.NonNull",
+          // http://bits.netbeans.org/8.2/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NonNull.html
+          "org.netbeans.api.annotations.common.NonNull",
+          // https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/NonNull.java
+          "org.springframework.lang.NonNull");
+
+  // List is in alphabetical order.  If you update it, also update
+  // ../../../../../../../../docs/manual/nullness-checker.tex .
+  /** Aliases for {@code @Nullable}. */
+  private static final List<@FullyQualifiedName String> NULLABLE_ALIASES =
+      Arrays.asList(
+          // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/Nullable.java
+          "android.annotation.Nullable",
+          // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/Nullable.java
+          "android.support.annotation.Nullable",
+          // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/androidx/annotation/Nullable.java
+          "androidx.annotation.Nullable",
+          // https://android.googlesource.com/platform/tools/metalava/+/master/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java
+          "androidx.annotation.RecentlyNullable",
+          "com.sun.istack.internal.Nullable",
+          // http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/CheckForNull.html
+          "edu.umd.cs.findbugs.annotations.CheckForNull",
+          // http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/Nullable.html
+          "edu.umd.cs.findbugs.annotations.Nullable",
+          // http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/PossiblyNull.html
+          "edu.umd.cs.findbugs.annotations.PossiblyNull",
+          // http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/UnknownNullness.html
+          "edu.umd.cs.findbugs.annotations.UnknownNullness",
+          // https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/Nullable.java
+          "io.reactivex.annotations.Nullable",
+          // https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/annotations/Nullable.java
+          "io.reactivex.rxjava3.annotations.Nullable",
+          // https://jcp.org/en/jsr/detail?id=305
+          "javax.annotation.CheckForNull",
+          "javax.annotation.Nullable",
+          // https://search.maven.org/search?q=a:checker-compat-qual
+          "org.checkerframework.checker.nullness.compatqual.NullableDecl",
+          "org.checkerframework.checker.nullness.compatqual.NullableType",
+          // https://janino-compiler.github.io/janino/apidocs/org/codehaus/commons/nullanalysis/Nullable.html
+          "org.codehaus.commons.nullanalysis.Nullable",
+          // https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/Nullable.html
+          "org.eclipse.jdt.annotation.Nullable",
+          // https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java
+          "org.eclipse.jgit.annotations.Nullable",
+          // https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/Nullable.java
+          "org.jetbrains.annotations.Nullable",
+          // http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/Nullable.java
+          "org.jmlspecs.annotation.Nullable",
+          // https://github.com/jspecify/jspecify/tree/main/src/main/java/org/jspecify/nullness
+          "org.jspecify.nullness.Nullable",
+          "org.jspecify.nullness.NullnessUnspecified",
+          // http://bits.netbeans.org/8.2/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/CheckForNull.html
+          "org.netbeans.api.annotations.common.CheckForNull",
+          // http://bits.netbeans.org/8.2/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullAllowed.html
+          "org.netbeans.api.annotations.common.NullAllowed",
+          // http://bits.netbeans.org/8.2/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullUnknown.html
+          "org.netbeans.api.annotations.common.NullUnknown",
+          // https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/Nullable.java
+          "org.springframework.lang.Nullable");
+
+  /**
+   * Creates a NullnessAnnotatedTypeFactory.
+   *
+   * @param checker the associated {@link NullnessChecker}
+   */
+  public NullnessAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    Set<Class<? extends Annotation>> tempNullnessAnnos = new LinkedHashSet<>(4);
+    tempNullnessAnnos.add(NonNull.class);
+    tempNullnessAnnos.add(MonotonicNonNull.class);
+    tempNullnessAnnos.add(Nullable.class);
+    tempNullnessAnnos.add(PolyNull.class);
+    nullnessAnnos = Collections.unmodifiableSet(tempNullnessAnnos);
+
+    NONNULL_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, NONNULL));
+    NULLABLE_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, NULLABLE));
+
+    // Add compatibility annotations:
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.nullness.compatqual.PolyNullDecl", POLYNULL);
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.nullness.compatqual.MonotonicNonNullDecl", MONOTONIC_NONNULL);
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.nullness.compatqual.PolyNullType", POLYNULL);
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.nullness.compatqual.MonotonicNonNullType", MONOTONIC_NONNULL);
+
+    boolean permitClearProperty =
+        checker.getLintOption(
+            NullnessChecker.LINT_PERMITCLEARPROPERTY,
+            NullnessChecker.LINT_DEFAULT_PERMITCLEARPROPERTY);
+    systemGetPropertyHandler =
+        new SystemGetPropertyHandler(processingEnv, this, permitClearProperty);
+
+    classGetCanonicalName =
+        TreeUtils.getMethod("java.lang.Class", "getCanonicalName", 0, processingEnv);
+    copyOfMethods =
+        Arrays.asList(
+            TreeUtils.getMethod("java.util.Arrays", "copyOf", processingEnv, "T[]", "int"),
+            TreeUtils.getMethod("java.util.Arrays", "copyOf", 3, processingEnv));
+
+    postInit();
+
+    // do this last, as it might use the factory again.
+    this.collectionToArrayHeuristics = new CollectionToArrayHeuristics(checker, this);
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new LinkedHashSet<>(
+        Arrays.asList(
+            Nullable.class,
+            MonotonicNonNull.class,
+            NonNull.class,
+            UnderInitialization.class,
+            Initialized.class,
+            UnknownInitialization.class,
+            FBCBottom.class,
+            PolyNull.class));
+  }
+
+  /**
+   * For types of left-hand side of an assignment, this method replaces {@link PolyNull} with {@link
+   * Nullable} (or with {@link NonNull} if the org.checkerframework.dataflow analysis has determined
+   * that this is allowed soundly. For example:
+   *
+   * <pre> @PolyNull String foo(@PolyNull String param) {
+   *    if (param == null) {
+   *        //  @PolyNull is really @Nullable, so change
+   *        // the type of param to @Nullable.
+   *        param = null;
+   *    }
+   *    return param;
+   * }
+   * </pre>
+   *
+   * @param lhsType type to replace whose polymorphic qualifier will be replaced
+   * @param context tree used to get dataflow value
+   */
+  protected void replacePolyQualifier(AnnotatedTypeMirror lhsType, Tree context) {
+    if (lhsType.hasAnnotation(PolyNull.class)) {
+      NullnessValue inferred = getInferredValueFor(context);
+      if (inferred != null) {
+        if (inferred.isPolyNullNonNull) {
+          lhsType.replaceAnnotation(NONNULL);
+        } else if (inferred.isPolyNullNull) {
+          lhsType.replaceAnnotation(NULLABLE);
+        }
+      }
+    }
+  }
+
+  @Override
+  public Pair<List<VariableTree>, List<VariableTree>> getUninitializedFields(
+      NullnessStore store,
+      TreePath path,
+      boolean isStatic,
+      Collection<? extends AnnotationMirror> receiverAnnotations) {
+    Pair<List<VariableTree>, List<VariableTree>> result =
+        super.getUninitializedFields(store, path, isStatic, receiverAnnotations);
+    // Filter out primitives.  They have the @NonNull annotation, but this checker issues no
+    // warning when they are not initialized.
+    result.first.removeIf(vt -> TypesUtils.isPrimitive(getAnnotatedType(vt).getUnderlyingType()));
+    result.second.removeIf(vt -> TypesUtils.isPrimitive(getAnnotatedType(vt).getUnderlyingType()));
+    return result;
+  }
+
+  @Override
+  protected NullnessAnalysis createFlowAnalysis(
+      List<Pair<VariableElement, NullnessValue>> fieldValues) {
+    return new NullnessAnalysis(checker, this, fieldValues);
+  }
+
+  @Override
+  public NullnessTransfer createFlowTransferFunction(
+      CFAbstractAnalysis<NullnessValue, NullnessStore, NullnessTransfer> analysis) {
+    return new NullnessTransfer((NullnessAnalysis) analysis);
+  }
+
+  /** @return an AnnotatedTypeFormatter that does not print the qualifiers on null literals */
+  @Override
+  protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() {
+    boolean printVerboseGenerics = checker.hasOption("printVerboseGenerics");
+    return new NullnessAnnotatedTypeFormatter(
+        printVerboseGenerics,
+        // -AprintVerboseGenerics implies -AprintAllQualifiers
+        printVerboseGenerics || checker.hasOption("printAllQualifiers"));
+  }
+
+  @Override
+  public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) {
+    ParameterizedExecutableType mType = super.methodFromUse(tree);
+    AnnotatedExecutableType method = mType.executableType;
+
+    // Special cases for method invocations with specific arguments.
+    systemGetPropertyHandler.handle(tree, method);
+    collectionToArrayHeuristics.handle(tree, method);
+    // `MyClass.class.getCanonicalName()` is non-null.
+    if (TreeUtils.isMethodInvocation(tree, classGetCanonicalName, processingEnv)) {
+      ExpressionTree receiver = ((MemberSelectTree) tree.getMethodSelect()).getExpression();
+      if (TreeUtils.isClassLiteral(receiver)) {
+        AnnotatedTypeMirror type = method.getReturnType();
+        type.replaceAnnotation(NONNULL);
+      }
+    }
+
+    return mType;
+  }
+
+  @Override
+  public void adaptGetClassReturnTypeToReceiver(
+      final AnnotatedExecutableType getClassType, final AnnotatedTypeMirror receiverType) {
+
+    super.adaptGetClassReturnTypeToReceiver(getClassType, receiverType);
+
+    // Make the wildcard always @NonNull, regardless of the declared type.
+
+    final AnnotatedDeclaredType returnAdt = (AnnotatedDeclaredType) getClassType.getReturnType();
+    final List<AnnotatedTypeMirror> typeArgs = returnAdt.getTypeArguments();
+    final AnnotatedWildcardType classWildcardArg = (AnnotatedWildcardType) typeArgs.get(0);
+    classWildcardArg.getExtendsBoundField().replaceAnnotation(NONNULL);
+  }
+
+  @Override
+  public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) {
+    AnnotatedTypeMirror result = super.getMethodReturnType(m, r);
+    replacePolyQualifier(result, r);
+    return result;
+  }
+
+  @Override
+  protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() {
+    DefaultForTypeAnnotator defaultForTypeAnnotator = new DefaultForTypeAnnotator(this);
+    defaultForTypeAnnotator.addAtmClass(AnnotatedNoType.class, NONNULL);
+    defaultForTypeAnnotator.addAtmClass(AnnotatedPrimitiveType.class, NONNULL);
+    return defaultForTypeAnnotator;
+  }
+
+  @Override
+  protected void addAnnotationsFromDefaultForType(
+      @Nullable Element element, AnnotatedTypeMirror type) {
+    if (element != null
+        && element.getKind() == ElementKind.LOCAL_VARIABLE
+        && type.getKind().isPrimitive()) {
+      // Always apply the DefaultQualifierForUse for primitives.
+      super.addAnnotationsFromDefaultForType(null, type);
+    } else {
+      super.addAnnotationsFromDefaultForType(element, type);
+    }
+  }
+
+  @Override
+  protected TypeAnnotator createTypeAnnotator() {
+    return new ListTypeAnnotator(
+        new PropagationTypeAnnotator(this),
+        new NullnessTypeAnnotator(this),
+        new CommitmentTypeAnnotator(this));
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    // Don't call super.createTreeAnnotator because the default tree annotators are incorrect
+    // for the Nullness Checker.
+    return new ListTreeAnnotator(
+        // DebugListTreeAnnotator(new Tree.Kind[] {Tree.Kind.CONDITIONAL_EXPRESSION},
+        new NullnessPropagationTreeAnnotator(this),
+        new LiteralTreeAnnotator(this),
+        new NullnessTreeAnnotator(this),
+        new CommitmentTreeAnnotator(this));
+  }
+
+  /**
+   * Nullness doesn't call propagation on binary and unary because the result is always @Initialized
+   * (the default qualifier).
+   *
+   * <p>Would this be valid to move into CommitmentTreeAnnotator.
+   */
+  protected static class NullnessPropagationTreeAnnotator extends PropagationTreeAnnotator {
+
+    /** Create the NullnessPropagationTreeAnnotator. */
+    public NullnessPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
+      return null;
+    }
+
+    @Override
+    public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) {
+      return null;
+    }
+
+    @Override
+    public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) {
+      if (type.getKind().isPrimitive()) {
+        AnnotationMirror NONNULL = ((NullnessAnnotatedTypeFactory) atypeFactory).NONNULL;
+        // If a @Nullable expression is cast to a primitive, then an unboxing.of.nullable
+        // error is issued.  Treat the cast as if it were annotated as @NonNull to avoid an
+        // annotations.on.use error.
+        if (!type.isAnnotatedInHierarchy(NONNULL)) {
+          type.addAnnotation(NONNULL);
+        }
+      }
+      return super.visitTypeCast(node, type);
+    }
+  }
+
+  protected class NullnessTreeAnnotator extends TreeAnnotator
+  /*extends InitializationAnnotatedTypeFactory<NullnessValue, NullnessStore, NullnessTransfer, NullnessAnalysis>.CommitmentTreeAnnotator*/ {
+
+    public NullnessTreeAnnotator(NullnessAnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitMemberSelect(MemberSelectTree node, AnnotatedTypeMirror type) {
+
+      Element elt = TreeUtils.elementFromUse(node);
+      assert elt != null;
+      return null;
+    }
+
+    @Override
+    public Void visitVariable(VariableTree node, AnnotatedTypeMirror type) {
+      Element elt = TreeUtils.elementFromTree(node);
+      if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) {
+        if (!type.isAnnotatedInHierarchy(NONNULL)) {
+          // case 9. exception parameter
+          type.addAnnotation(NONNULL);
+        }
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitIdentifier(IdentifierTree node, AnnotatedTypeMirror type) {
+
+      Element elt = TreeUtils.elementFromUse(node);
+      assert elt != null;
+
+      if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) {
+        // TODO: It's surprising that we have to do this in both visitVariable and
+        // visitIdentifier. This should already be handled by applying the defaults anyway.
+        // case 9. exception parameter
+        type.replaceAnnotation(NONNULL);
+      }
+
+      return null;
+    }
+
+    // The result of a binary operation is always non-null.
+    @Override
+    public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
+      type.replaceAnnotation(NONNULL);
+      return null;
+    }
+
+    // The result of a compound operation is always non-null.
+    @Override
+    public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
+      type.replaceAnnotation(NONNULL);
+      // Commitment will run after for initialization defaults
+      return null;
+    }
+
+    // The result of a unary operation is always non-null.
+    @Override
+    public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) {
+      type.replaceAnnotation(NONNULL);
+      return null;
+    }
+
+    // The result of newly allocated structures is always non-null.
+    @Override
+    public Void visitNewClass(NewClassTree node, AnnotatedTypeMirror type) {
+      type.replaceAnnotation(NONNULL);
+      return null;
+    }
+
+    @Override
+    public Void visitNewArray(NewArrayTree node, AnnotatedTypeMirror type) {
+      // The result of newly allocated structures is always non-null.
+      if (!type.isAnnotatedInHierarchy(NONNULL)) {
+        type.replaceAnnotation(NONNULL);
+      }
+
+      // The most precise element type for `new Object[] {null}` is @FBCBottom, but
+      // the most useful element type is @Initialized (which is also accurate).
+      AnnotatedArrayType arrayType = (AnnotatedArrayType) type;
+      AnnotatedTypeMirror componentType = arrayType.getComponentType();
+      if (componentType.hasEffectiveAnnotation(FBCBOTTOM)) {
+        componentType.replaceAnnotation(INITIALIZED);
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
+      if (TreeUtils.isMethodInvocation(tree, copyOfMethods, processingEnv)) {
+        List<? extends ExpressionTree> args = tree.getArguments();
+        ExpressionTree lengthArg = args.get(1);
+        if (TreeUtils.isArrayLengthAccess(lengthArg)) {
+          // TODO: This syntactic test may not be not correct if the array expression has a side
+          // effect that affects the array length.  This code could require that the expression has
+          // no method calls, assignments, etc.
+          ExpressionTree arrayArg = args.get(0);
+          if (TreeUtils.sameTree(arrayArg, ((MemberSelectTree) lengthArg).getExpression())) {
+            AnnotatedArrayType arrayArgType = (AnnotatedArrayType) getAnnotatedType(arrayArg);
+            AnnotatedTypeMirror arrayArgComponentType = arrayArgType.getComponentType();
+            // Maybe this call is only necessary if argNullness is @NonNull.
+            ((AnnotatedArrayType) type)
+                .getComponentType()
+                .replaceAnnotations(arrayArgComponentType.getAnnotations());
+          }
+        }
+      }
+      return super.visitMethodInvocation(tree, type);
+    }
+  }
+
+  protected class NullnessTypeAnnotator
+      extends InitializationAnnotatedTypeFactory<
+              NullnessValue, NullnessStore, NullnessTransfer, NullnessAnalysis>
+          .CommitmentTypeAnnotator {
+
+    public NullnessTypeAnnotator(InitializationAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory) {
+      super(atypeFactory);
+    }
+  }
+
+  /**
+   * Returns the list of annotations of the non-null type system.
+   *
+   * @return the list of annotations of the non-null type system
+   */
+  public Set<Class<? extends Annotation>> getNullnessAnnotations() {
+    return nullnessAnnos;
+  }
+
+  @Override
+  public AnnotationMirror getFieldInvariantAnnotation() {
+    return NONNULL;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>In other words, is the lower bound @NonNull?
+   *
+   * @param type of field that might have invariant annotation
+   * @return whether or not type has the invariant annotation
+   */
+  @Override
+  protected boolean hasFieldInvariantAnnotation(
+      AnnotatedTypeMirror type, VariableElement fieldElement) {
+    AnnotationMirror invariant = getFieldInvariantAnnotation();
+    Set<AnnotationMirror> lowerBounds =
+        AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type);
+    return AnnotationUtils.containsSame(lowerBounds, invariant);
+  }
+
+  @Override
+  public QualifierHierarchy createQualifierHierarchy() {
+    return new NullnessQualifierHierarchy();
+  }
+
+  /** NullnessQualifierHierarchy. */
+  protected class NullnessQualifierHierarchy extends InitializationQualifierHierarchy {
+
+    /** Qualifier kind for the @{@link Nullable} annotation. */
+    private final QualifierKind NULLABLE;
+
+    /** Creates NullnessQualifierHierarchy. */
+    public NullnessQualifierHierarchy() {
+      super();
+      NULLABLE = getQualifierKind(NullnessAnnotatedTypeFactory.this.NULLABLE);
+    }
+
+    @Override
+    protected boolean isSubtypeWithElements(
+        AnnotationMirror subAnno,
+        QualifierKind subKind,
+        AnnotationMirror superAnno,
+        QualifierKind superKind) {
+      if (!subKind.isInSameHierarchyAs(NULLABLE) || !superKind.isInSameHierarchyAs(NULLABLE)) {
+        return this.isSubtypeInitialization(subAnno, subKind, superAnno, superKind);
+      }
+      throw new BugInCF("Unexpected annotations isSubtypeWithElements(%s, %s)", subAnno, superAnno);
+    }
+
+    @Override
+    protected AnnotationMirror leastUpperBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind lubKind) {
+      if (!qualifierKind1.isInSameHierarchyAs(NULLABLE)
+          || !qualifierKind2.isInSameHierarchyAs(NULLABLE)) {
+        return this.leastUpperBoundInitialization(a1, qualifierKind1, a2, qualifierKind2);
+      }
+      throw new BugInCF("Unexpected annotations leastUpperBoundWithElements(%s, %s)", a1, a2);
+    }
+
+    @Override
+    protected AnnotationMirror greatestLowerBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind glbKind) {
+      if (!qualifierKind1.isInSameHierarchyAs(NULLABLE)
+          || !qualifierKind2.isInSameHierarchyAs(NULLABLE)) {
+        return this.greatestLowerBoundInitialization(a1, qualifierKind1, a2, qualifierKind2);
+      }
+      throw new BugInCF("Unexpected annotations greatestLowerBoundWithElements(%s, %s)", a1, a2);
+    }
+  }
+
+  /**
+   * Returns true if some annotation on the given type, or in the given list, is a nullness
+   * annotation such as @NonNull, @Nullable, @MonotonicNonNull, etc.
+   *
+   * <p>This method ignores aliases of nullness annotations that are declaration annotations,
+   * because they may apply to inner types.
+   *
+   * @param annoTrees a list of annotations that the the Java parser attached to the variable/method
+   *     declaration; null if this type is not from such a location. This is a list of extra
+   *     annotations to check, in addition to those on the type.
+   * @param typeTree the type whose annotations to test
+   * @return true if some annotation is a nullness annotation
+   */
+  protected boolean containsNullnessAnnotation(
+      List<? extends AnnotationTree> annoTrees, Tree typeTree) {
+    List<? extends AnnotationTree> annos =
+        TreeUtils.getExplicitAnnotationTrees(annoTrees, typeTree);
+    return containsNullnessAnnotation(annos);
+  }
+
+  /**
+   * Returns true if some annotation in the given list is a nullness annotation such
+   * as @NonNull, @Nullable, @MonotonicNonNull, etc.
+   *
+   * <p>This method ignores aliases of nullness annotations that are declaration annotations,
+   * because they may apply to inner types.
+   *
+   * <p>Clients that are processing a field or variable definition, or a method return type, should
+   * call {@link #containsNullnessAnnotation(List, Tree)} instead.
+   *
+   * @param annoTrees a list of annotations to check
+   * @return true if some annotation is a nullness annotation
+   * @see #containsNullnessAnnotation(List, Tree)
+   */
+  protected boolean containsNullnessAnnotation(List<? extends AnnotationTree> annoTrees) {
+    for (AnnotationTree annoTree : annoTrees) {
+      AnnotationMirror am = TreeUtils.annotationFromAnnotationTree(annoTree);
+      if (isNullnessAnnotation(am) && !AnnotationUtils.isDeclarationAnnotation(am)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if the given annotation is a nullness annotation such
+   * as @NonNull, @Nullable, @MonotonicNonNull, etc.
+   *
+   * @param am an annotation
+   * @return true if the given annotation is a nullness annotation
+   */
+  protected boolean isNullnessAnnotation(AnnotationMirror am) {
+    return isNonNullOrAlias(am)
+        || isNullableOrAlias(am)
+        || AnnotationUtils.areSameByName(am, MONOTONIC_NONNULL)
+        || AnnotationUtils.areSameByName(am, POLYNULL);
+  }
+
+  /**
+   * Returns true if the given annotation is @NonNull or an alias for it.
+   *
+   * @param am an annotation
+   * @return true if the given annotation is @NonNull or an alias for it
+   */
+  protected boolean isNonNullOrAlias(AnnotationMirror am) {
+    AnnotationMirror canonical = canonicalAnnotation(am);
+    if (canonical != null) {
+      am = canonical;
+    }
+    return AnnotationUtils.areSameByName(am, NONNULL);
+  }
+
+  /**
+   * Returns true if the given annotation is @Nullable or an alias for it.
+   *
+   * @param am an annotation
+   * @return true if the given annotation is @Nullable or an alias for it
+   */
+  protected boolean isNullableOrAlias(AnnotationMirror am) {
+    AnnotationMirror canonical = canonicalAnnotation(am);
+    if (canonical != null) {
+      am = canonical;
+    }
+    return AnnotationUtils.areSameByName(am, NULLABLE);
+  }
+
+  // If a reference field has no initializer, then its default value is null.  Treat that as
+  // @MonotonicNonNull rather than as @Nullable.
+  @Override
+  public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) {
+    AnnotatedTypeMirror result = super.getDefaultValueAnnotatedType(typeMirror);
+    if (getAnnotationByClass(result.getAnnotations(), Nullable.class) != null) {
+      result.replaceAnnotation(MONOTONIC_NONNULL);
+    }
+    return result;
+  }
+
+  // If
+  //  1. rhs is @Nullable
+  //  2. lhs is a field of this
+  //  3. in a constructor, initializer block, or field initializer
+  // then change rhs to @MonotonicNonNull.
+  @Override
+  public void wpiAdjustForUpdateField(
+      Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) {
+    if (!rhsATM.hasAnnotation(Nullable.class)) {
+      return;
+    }
+    TreePath lhsPath = getPath(lhsTree);
+    TypeElement enclosingClassOfLhs =
+        TreeUtils.elementFromDeclaration(TreePathUtil.enclosingClass(lhsPath));
+    ClassSymbol enclosingClassOfField = ((VarSymbol) element).enclClass();
+    if (enclosingClassOfLhs.equals(enclosingClassOfField) && TreePathUtil.inConstructor(lhsPath)) {
+      rhsATM.replaceAnnotation(MONOTONIC_NONNULL);
+    }
+  }
+
+  // If
+  //  1. rhs is @MonotonicNonNull
+  // then change rhs to @Nullable
+  @Override
+  public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) {
+    if (rhsATM.hasAnnotation(MonotonicNonNull.class)) {
+      rhsATM.replaceAnnotation(NULLABLE);
+    }
+  }
+
+  // This implementation overrides the superclass implementation to:
+  //  * check for @MonotonicNonNull
+  //  * output @RequiresNonNull rather than @RequiresQualifier.
+  @Override
+  public List<AnnotationMirror> getPreconditionAnnotation(
+      VariableElement elt, AnnotatedTypeMirror fieldType) {
+    AnnotatedTypeMirror declaredType = fromElement(elt);
+    // TODO: This does not handle the possibility that the user set a different default annotation.
+    if (!(declaredType.hasAnnotation(NULLABLE)
+        || declaredType.hasAnnotation(POLYNULL)
+        || declaredType.hasAnnotation(MONOTONIC_NONNULL))) {
+      return Collections.emptyList();
+    }
+
+    if (AnnotationUtils.containsSameByName(
+        fieldType.getAnnotations(), "org.checkerframework.checker.nullness.qual.NonNull")) {
+      return requiresNonNullAnno(elt);
+    }
+    return Collections.emptyList();
+  }
+
+  /**
+   * Returns a {@code RequiresNonNull("...")} annotation for the given field.
+   *
+   * @param fieldElement a field
+   * @return a {@code RequiresNonNull("...")} annotation for the given field
+   */
+  private List<AnnotationMirror> requiresNonNullAnno(VariableElement fieldElement) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, RequiresNonNull.class);
+    String receiver = JavaExpression.getImplicitReceiver(fieldElement).toString();
+    String expression = receiver + "." + fieldElement.getSimpleName();
+    builder.setValue("value", new String[] {expression});
+    AnnotationMirror am = builder.build();
+    return Collections.singletonList(am);
+  }
+
+  @Override
+  public List<AnnotationMirror> getPostconditionAnnotation(
+      VariableElement elt, AnnotatedTypeMirror fieldAnnos, List<AnnotationMirror> preconds) {
+    AnnotatedTypeMirror declaredType = fromElement(elt);
+    // TODO: This does not handle the possibility that the user set a different default annotation.
+    if (!(declaredType.hasAnnotation(NULLABLE)
+        || declaredType.hasAnnotation(POLYNULL)
+        || declaredType.hasAnnotation(MONOTONIC_NONNULL))) {
+      return Collections.emptyList();
+    }
+    if (declaredType.hasAnnotation(MONOTONIC_NONNULL)
+        && preconds.contains(requiresNonNullAnno(elt))) {
+      // The postcondition is implied by the precondition and the field being @MonotonicNonNull.
+      return Collections.emptyList();
+    }
+    if (AnnotationUtils.containsSameByName(
+        fieldAnnos.getAnnotations(), "org.checkerframework.checker.nullness.qual.NonNull")) {
+      return ensuresNonNullAnno(elt);
+    }
+    return Collections.emptyList();
+  }
+
+  /**
+   * Returns a {@code EnsuresNonNull("...")} annotation for the given field.
+   *
+   * @param fieldElement a field
+   * @return a {@code EnsuresNonNull("...")} annotation for the given field
+   */
+  private List<AnnotationMirror> ensuresNonNullAnno(VariableElement fieldElement) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, EnsuresNonNull.class);
+    String receiver = JavaExpression.getImplicitReceiver(fieldElement).toString();
+    String expression = receiver + "." + fieldElement.getSimpleName();
+    builder.setValue("value", new String[] {expression});
+    AnnotationMirror am = builder.build();
+    return Collections.singletonList(am);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnnotatedTypeFormatter.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnnotatedTypeFormatter.java
new file mode 100644
index 0000000..3dd8738
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnnotatedTypeFormatter.java
@@ -0,0 +1,43 @@
+package org.checkerframework.checker.nullness;
+
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.DefaultAnnotatedTypeFormatter;
+import org.checkerframework.framework.util.AnnotationFormatter;
+import org.checkerframework.framework.util.DefaultAnnotationFormatter;
+
+/** A DefaultAnnotatedTypeFormatter that prints null literals without their annotations. */
+public class NullnessAnnotatedTypeFormatter extends DefaultAnnotatedTypeFormatter {
+  public NullnessAnnotatedTypeFormatter(
+      boolean printVerboseGenerics, boolean printInvisibleQualifiers) {
+    super(
+        new NullnessFormattingVisitor(
+            new DefaultAnnotationFormatter(), printVerboseGenerics, printInvisibleQualifiers));
+  }
+
+  protected static class NullnessFormattingVisitor extends FormattingVisitor {
+
+    public NullnessFormattingVisitor(
+        AnnotationFormatter annoFormatter,
+        boolean printVerboseGenerics,
+        boolean defaultInvisiblesSetting) {
+      super(annoFormatter, printVerboseGenerics, defaultInvisiblesSetting);
+    }
+
+    @Override
+    public String visitNull(AnnotatedNullType type, Set<AnnotatedTypeMirror> visiting) {
+      if (type.getAnnotation(Nullable.class) != null) {
+        // The null type will be understood as nullable by readers (I hope), therefore omit the
+        // annotations if they are @Nullable.
+        // Note: The visitTypeVariable will still print lower bounds with Null kind as "Void"
+        if (!currentPrintInvisibleSetting) {
+          return "null (NullType)";
+        }
+      }
+
+      return super.visitNull(type, visiting);
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java
new file mode 100644
index 0000000..020c82e
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java
@@ -0,0 +1,102 @@
+package org.checkerframework.checker.nullness;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.SortedSet;
+import javax.annotation.processing.SupportedOptions;
+import org.checkerframework.checker.initialization.InitializationChecker;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.source.SupportedLintOptions;
+
+/**
+ * An implementation of the nullness type-system, parameterized by an initialization type-system for
+ * safe initialization. It uses freedom-before-commitment, augmented by type frames (which are
+ * crucial to obtain acceptable precision), as its initialization type system.
+ *
+ * @checker_framework.manual #nullness-checker Nullness Checker
+ */
+@SupportedLintOptions({
+  NullnessChecker.LINT_NOINITFORMONOTONICNONNULL,
+  NullnessChecker.LINT_REDUNDANTNULLCOMPARISON,
+  // Temporary option to forbid non-null array component types, which is allowed by default.
+  // Forbidding is sound and will eventually be the default.
+  // Allowing is unsound, as described in Section 3.3.4, "Nullness and arrays":
+  //     https://checkerframework.org/manual/#nullness-arrays
+  // It is the default temporarily, until we improve the analysis to reduce false positives or we
+  // learn what advice to give programmers about avoid false positive warnings.
+  // See issue #986: https://github.com/typetools/checker-framework/issues/986
+  "soundArrayCreationNullness",
+  // Old name for soundArrayCreationNullness, for backward compatibility; remove in January 2021.
+  "forbidnonnullarraycomponents",
+  NullnessChecker.LINT_TRUSTARRAYLENZERO,
+  NullnessChecker.LINT_PERMITCLEARPROPERTY,
+})
+@SupportedOptions({"assumeKeyFor"})
+public class NullnessChecker extends InitializationChecker {
+
+  /** Should we be strict about initialization of {@link MonotonicNonNull} variables? */
+  public static final String LINT_NOINITFORMONOTONICNONNULL = "noInitForMonotonicNonNull";
+
+  /** Default for {@link #LINT_NOINITFORMONOTONICNONNULL}. */
+  public static final boolean LINT_DEFAULT_NOINITFORMONOTONICNONNULL = false;
+
+  /**
+   * Warn about redundant comparisons of an expression with {@code null}, if the expression is known
+   * to be non-null.
+   */
+  public static final String LINT_REDUNDANTNULLCOMPARISON = "redundantNullComparison";
+
+  /** Default for {@link #LINT_REDUNDANTNULLCOMPARISON}. */
+  public static final boolean LINT_DEFAULT_REDUNDANTNULLCOMPARISON = false;
+
+  /**
+   * Should the Nullness Checker unsoundly trust {@code @ArrayLen(0)} annotations to improve
+   * handling of {@link java.util.Collection#toArray()} by {@link CollectionToArrayHeuristics}?
+   */
+  public static final String LINT_TRUSTARRAYLENZERO = "trustArrayLenZero";
+
+  /** Default for {@link #LINT_TRUSTARRAYLENZERO}. */
+  public static final boolean LINT_DEFAULT_TRUSTARRAYLENZERO = false;
+
+  /**
+   * If true, client code may clear system properties. If false (the default), some calls to {@code
+   * System.getProperty} are refined to return @NonNull.
+   */
+  public static final String LINT_PERMITCLEARPROPERTY = "permitClearProperty";
+
+  /** Default for {@link #LINT_PERMITCLEARPROPERTY}. */
+  public static final boolean LINT_DEFAULT_PERMITCLEARPROPERTY = false;
+
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    LinkedHashSet<Class<? extends BaseTypeChecker>> checkers =
+        super.getImmediateSubcheckerClasses();
+    if (!hasOptionNoSubcheckers("assumeKeyFor")) {
+      checkers.add(KeyForSubchecker.class);
+    }
+    return checkers;
+  }
+
+  @Override
+  public SortedSet<String> getSuppressWarningsPrefixes() {
+    SortedSet<String> result = super.getSuppressWarningsPrefixes();
+    result.add("nullness");
+    return result;
+  }
+
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new NullnessVisitor(this);
+  }
+
+  @Override
+  public List<String> getExtraStubFiles() {
+    List<String> result = super.getExtraStubFiles();
+    if (hasOption("assumeKeyFor")) {
+      result.add("map-assumeKeyFor.astub");
+    }
+    return result;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessStore.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessStore.java
new file mode 100644
index 0000000..dd176c7
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessStore.java
@@ -0,0 +1,131 @@
+package org.checkerframework.checker.nullness;
+
+import java.util.concurrent.atomic.AtomicLong;
+import org.checkerframework.checker.initialization.InitializationStore;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.PolyNull;
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.plumelib.util.UniqueId;
+
+/**
+ * Behaves like {@link InitializationStore}, but additionally tracks whether {@link PolyNull} is
+ * known to be {@link NonNull} or {@link Nullable} (or not known to be either).
+ */
+public class NullnessStore extends InitializationStore<NullnessValue, NullnessStore>
+    implements UniqueId {
+
+  /** True if, at this point, {@link PolyNull} is known to be {@link NonNull}. */
+  protected boolean isPolyNullNonNull;
+
+  /** True if, at this point, {@link PolyNull} is known to be {@link Nullable}. */
+  protected boolean isPolyNullNull;
+
+  /** The unique ID for the next-created object. */
+  static final AtomicLong nextUid = new AtomicLong(0);
+  /** The unique ID of this object. */
+  final long uid = nextUid.getAndIncrement();
+  /**
+   * Returns the unique ID of this object.
+   *
+   * @return the unique ID of this object
+   */
+  @Override
+  public long getUid() {
+    return uid;
+  }
+
+  /**
+   * Create a NullnessStore.
+   *
+   * @param analysis the analysis class this store belongs to
+   * @param sequentialSemantics should the analysis use sequential Java semantics (i.e., assume that
+   *     only one thread is running at all times)?
+   */
+  public NullnessStore(
+      CFAbstractAnalysis<NullnessValue, NullnessStore, ?> analysis, boolean sequentialSemantics) {
+    super(analysis, sequentialSemantics);
+    isPolyNullNonNull = false;
+    isPolyNullNull = false;
+  }
+
+  /**
+   * Create a NullnessStore (copy constructor).
+   *
+   * @param s a store to copy
+   */
+  public NullnessStore(NullnessStore s) {
+    super(s);
+    isPolyNullNonNull = s.isPolyNullNonNull;
+    isPolyNullNull = s.isPolyNullNull;
+  }
+
+  @Override
+  public NullnessStore leastUpperBound(NullnessStore other) {
+    NullnessStore lub = super.leastUpperBound(other);
+    lub.isPolyNullNonNull = isPolyNullNonNull && other.isPolyNullNonNull;
+    lub.isPolyNullNull = isPolyNullNull && other.isPolyNullNull;
+    return lub;
+  }
+
+  @Override
+  protected boolean supersetOf(CFAbstractStore<NullnessValue, NullnessStore> o) {
+    if (!(o instanceof InitializationStore)) {
+      return false;
+    }
+    NullnessStore other = (NullnessStore) o;
+    if ((other.isPolyNullNonNull != isPolyNullNonNull)
+        || (other.isPolyNullNull != isPolyNullNull)) {
+      return false;
+    }
+    return super.supersetOf(other);
+  }
+
+  @Override
+  protected String internalVisualize(CFGVisualizer<NullnessValue, NullnessStore, ?> viz) {
+    return super.internalVisualize(viz)
+        + viz.getSeparator()
+        + viz.visualizeStoreKeyVal("isPolyNullNonNull", isPolyNullNonNull)
+        + viz.getSeparator()
+        + viz.visualizeStoreKeyVal("isPolyNullNull", isPolyNullNull);
+  }
+
+  /**
+   * Returns true if, at this point, {@link PolyNull} is known to be {@link NonNull}.
+   *
+   * @return true if, at this point, {@link PolyNull} is known to be {@link NonNull}
+   */
+  public boolean isPolyNullNonNull() {
+    return isPolyNullNonNull;
+  }
+
+  /**
+   * Set the value of whether, at this point, {@link PolyNull} is known to be {@link NonNull}.
+   *
+   * @param isPolyNullNonNull whether, at this point, {@link PolyNull} is known to be {@link
+   *     NonNull}
+   */
+  public void setPolyNullNonNull(boolean isPolyNullNonNull) {
+    this.isPolyNullNonNull = isPolyNullNonNull;
+  }
+
+  /**
+   * Returns true if, at this point, {@link PolyNull} is known to be {@link Nullable}.
+   *
+   * @return true if, at this point, {@link PolyNull} is known to be {@link Nullable}
+   */
+  public boolean isPolyNullNull() {
+    return isPolyNullNull;
+  }
+
+  /**
+   * Set the value of whether, at this point, {@link PolyNull} is known to be {@link Nullable}.
+   *
+   * @param isPolyNullNull whether, at this point, {@link PolyNull} is known to be {@link Nullable}
+   */
+  public void setPolyNullNull(boolean isPolyNullNull) {
+    this.isPolyNullNull = isPolyNullNull;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessTransfer.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessTransfer.java
new file mode 100644
index 0000000..f0d4c21
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessTransfer.java
@@ -0,0 +1,430 @@
+package org.checkerframework.checker.nullness;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.initialization.InitializationTransfer;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.PolyNull;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.InstanceOfNode;
+import org.checkerframework.dataflow.cfg.node.MethodAccessNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.NullLiteralNode;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+import org.checkerframework.dataflow.cfg.node.ThrowNode;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Transfer function for the non-null type system. Performs the following refinements:
+ *
+ * <ol>
+ *   <li>After an expression is compared with the {@code null} literal, then that expression can
+ *       safely be considered {@link NonNull} if the result of the comparison is false or {@link
+ *       Nullable} if the result is true.
+ *   <li>If an expression is dereferenced, then it can safely be assumed to non-null in the future.
+ *       If it would not be, then the dereference would have raised a {@link NullPointerException}.
+ *   <li>Tracks whether {@link PolyNull} is known to be {@link NonNull} or {@link Nullable} (or not
+ *       known to be either).
+ * </ol>
+ */
+public class NullnessTransfer
+    extends InitializationTransfer<NullnessValue, NullnessTransfer, NullnessStore> {
+
+  /** The @{@link NonNull} annotation. */
+  protected final AnnotationMirror NONNULL;
+  /** The @{@link Nullable} annotation. */
+  protected final AnnotationMirror NULLABLE;
+  /** The @{@link PolyNull} annotation. */
+  protected final AnnotationMirror POLYNULL;
+
+  /**
+   * Java's Map interface.
+   *
+   * <p>The qualifiers in this type don't matter -- it is not used as a fully-annotated
+   * AnnotatedDeclaredType, but just passed to asSuper().
+   */
+  protected final AnnotatedDeclaredType MAP_TYPE;
+
+  /** The type factory for the nullness analysis that was passed to the constructor. */
+  protected final GenericAnnotatedTypeFactory<
+          NullnessValue,
+          NullnessStore,
+          NullnessTransfer,
+          ? extends CFAbstractAnalysis<NullnessValue, NullnessStore, NullnessTransfer>>
+      nullnessTypeFactory;
+
+  /**
+   * The type factory for the map key analysis, or null if the Map Key Checker should not be run.
+   */
+  protected final @Nullable KeyForAnnotatedTypeFactory keyForTypeFactory;
+
+  /** Create a new NullnessTransfer for the given analysis. */
+  public NullnessTransfer(NullnessAnalysis analysis) {
+    super(analysis);
+    this.nullnessTypeFactory = analysis.getTypeFactory();
+    Elements elements = nullnessTypeFactory.getElementUtils();
+    BaseTypeChecker checker = nullnessTypeFactory.getChecker();
+    if (checker.hasOption("assumeKeyFor")) {
+      this.keyForTypeFactory = null;
+    } else {
+      // It is error-prone to put a type factory in a field.  It is OK here because
+      // keyForTypeFactory is used only to call methods isMapGet() and isKeyForMap().
+      this.keyForTypeFactory = checker.getTypeFactoryOfSubchecker(KeyForSubchecker.class);
+    }
+
+    NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class);
+    NULLABLE = AnnotationBuilder.fromClass(elements, Nullable.class);
+    POLYNULL = AnnotationBuilder.fromClass(elements, PolyNull.class);
+
+    MAP_TYPE =
+        (AnnotatedDeclaredType)
+            AnnotatedTypeMirror.createType(
+                TypesUtils.typeFromClass(Map.class, analysis.getTypes(), elements),
+                nullnessTypeFactory,
+                false);
+  }
+
+  /**
+   * Sets a given {@link Node} to non-null in the given {@code store}. Calls to this method
+   * implement case 2.
+   *
+   * @param store the store to update
+   * @param node the node that should be non-null
+   */
+  protected void makeNonNull(NullnessStore store, Node node) {
+    JavaExpression internalRepr = JavaExpression.fromNode(node);
+    store.insertValue(internalRepr, NONNULL);
+  }
+
+  /** Sets a given {@link Node} {@code node} to non-null in the given {@link TransferResult}. */
+  protected void makeNonNull(TransferResult<NullnessValue, NullnessStore> result, Node node) {
+    if (result.containsTwoStores()) {
+      makeNonNull(result.getThenStore(), node);
+      makeNonNull(result.getElseStore(), node);
+    } else {
+      makeNonNull(result.getRegularStore(), node);
+    }
+  }
+
+  /** Refine the given result to @NonNull. */
+  protected void refineToNonNull(TransferResult<NullnessValue, NullnessStore> result) {
+    NullnessValue oldResultValue = result.getResultValue();
+    NullnessValue refinedResultValue =
+        analysis.createSingleAnnotationValue(NONNULL, oldResultValue.getUnderlyingType());
+    NullnessValue newResultValue = refinedResultValue.mostSpecific(oldResultValue, null);
+    result.setResultValue(newResultValue);
+  }
+
+  @Override
+  protected @Nullable NullnessValue finishValue(
+      @Nullable NullnessValue value, NullnessStore store) {
+    value = super.finishValue(value, store);
+    if (value != null) {
+      value.isPolyNullNonNull = store.isPolyNullNonNull();
+      value.isPolyNullNull = store.isPolyNullNull();
+    }
+    return value;
+  }
+
+  @Override
+  protected @Nullable NullnessValue finishValue(
+      @Nullable NullnessValue value, NullnessStore thenStore, NullnessStore elseStore) {
+    value = super.finishValue(value, thenStore, elseStore);
+    if (value != null) {
+      value.isPolyNullNonNull = thenStore.isPolyNullNonNull() && elseStore.isPolyNullNonNull();
+      value.isPolyNullNull = thenStore.isPolyNullNull() && elseStore.isPolyNullNull();
+    }
+    return value;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>Furthermore, this method refines the type to {@code NonNull} for the appropriate branch if
+   * an expression is compared to the {@code null} literal (listed as case 1 in the class
+   * description).
+   */
+  @Override
+  protected TransferResult<NullnessValue, NullnessStore> strengthenAnnotationOfEqualTo(
+      TransferResult<NullnessValue, NullnessStore> res,
+      Node firstNode,
+      Node secondNode,
+      NullnessValue firstValue,
+      NullnessValue secondValue,
+      boolean notEqualTo) {
+    res =
+        super.strengthenAnnotationOfEqualTo(
+            res, firstNode, secondNode, firstValue, secondValue, notEqualTo);
+    if (firstNode instanceof NullLiteralNode) {
+      NullnessStore thenStore = res.getThenStore();
+      NullnessStore elseStore = res.getElseStore();
+
+      List<Node> secondParts = splitAssignments(secondNode);
+      for (Node secondPart : secondParts) {
+        JavaExpression secondInternal = JavaExpression.fromNode(secondPart);
+        if (CFAbstractStore.canInsertJavaExpression(secondInternal)) {
+          thenStore = thenStore == null ? res.getThenStore() : thenStore;
+          elseStore = elseStore == null ? res.getElseStore() : elseStore;
+          if (notEqualTo) {
+            thenStore.insertValue(secondInternal, NONNULL);
+          } else {
+            elseStore.insertValue(secondInternal, NONNULL);
+          }
+        }
+      }
+
+      Set<AnnotationMirror> secondAnnos =
+          secondValue != null
+              ? secondValue.getAnnotations()
+              : AnnotationUtils.createAnnotationSet();
+      if (nullnessTypeFactory.containsSameByClass(secondAnnos, PolyNull.class)) {
+        thenStore = thenStore == null ? res.getThenStore() : thenStore;
+        elseStore = elseStore == null ? res.getElseStore() : elseStore;
+        // TODO: methodTree is null for lambdas.  Handle that case.  See Issue3850.java.
+        MethodTree methodTree = analysis.getContainingMethod(secondNode.getTree());
+        ExecutableElement methodElem =
+            methodTree == null ? null : TreeUtils.elementFromDeclaration(methodTree);
+        if (notEqualTo) {
+          elseStore.setPolyNullNull(true);
+          if (methodElem != null && polyNullIsNonNull(methodElem, thenStore)) {
+            thenStore.setPolyNullNonNull(true);
+          }
+        } else {
+          thenStore.setPolyNullNull(true);
+          if (methodElem != null && polyNullIsNonNull(methodElem, elseStore)) {
+            elseStore.setPolyNullNonNull(true);
+          }
+        }
+      }
+
+      if (thenStore != null) {
+        return new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore);
+      }
+    }
+    return res;
+  }
+
+  /**
+   * Returns true if every formal parameter that is declared as @PolyNull is currently known to be
+   * non-null.
+   *
+   * @param method a method
+   * @param s a store
+   * @return true if every formal parameter declared as @PolyNull is non-null
+   */
+  private boolean polyNullIsNonNull(ExecutableElement method, NullnessStore s) {
+    // No need to check the receiver, which is always non-null.
+    for (VariableElement var : method.getParameters()) {
+      AnnotatedTypeMirror varType = atypeFactory.fromElement(var);
+
+      if (containsPolyNullNotAtTopLevel(varType)) {
+        return false;
+      }
+
+      if (varType.hasAnnotation(POLYNULL)) {
+        NullnessValue v = s.getValue(new LocalVariable(var));
+        if (!AnnotationUtils.containsSameByName(v.getAnnotations(), NONNULL)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
+   * A scanner that returns true if there is an occurrence of @PolyNull that is not at the top
+   * level.
+   */
+  // Not static so it can access field POLYNULL.
+  private class ContainsPolyNullNotAtTopLevelScanner
+      extends SimpleAnnotatedTypeScanner<Boolean, Void> {
+    /**
+     * True if the top-level type has not yet been processed (by the first call to defaultAction).
+     */
+    private boolean isTopLevel = true;
+
+    /** Create a ContainsPolyNullNotAtTopLevelScanner. */
+    ContainsPolyNullNotAtTopLevelScanner() {
+      super(Boolean::logicalOr, false);
+    }
+
+    @Override
+    protected Boolean defaultAction(AnnotatedTypeMirror type, Void p) {
+      if (isTopLevel) {
+        isTopLevel = false;
+        return false;
+      } else {
+        return type.hasAnnotation(POLYNULL);
+      }
+    }
+  }
+
+  /**
+   * Returns true if there is an occurrence of @PolyNull that is not at the top level.
+   *
+   * @param t a type
+   * @return true if there is an occurrence of @PolyNull that is not at the top level
+   */
+  private boolean containsPolyNullNotAtTopLevel(AnnotatedTypeMirror t) {
+    return new ContainsPolyNullNotAtTopLevelScanner().visit(t);
+  }
+
+  @Override
+  public TransferResult<NullnessValue, NullnessStore> visitArrayAccess(
+      ArrayAccessNode n, TransferInput<NullnessValue, NullnessStore> p) {
+    TransferResult<NullnessValue, NullnessStore> result = super.visitArrayAccess(n, p);
+    makeNonNull(result, n.getArray());
+    return result;
+  }
+
+  @Override
+  public TransferResult<NullnessValue, NullnessStore> visitInstanceOf(
+      InstanceOfNode n, TransferInput<NullnessValue, NullnessStore> p) {
+    TransferResult<NullnessValue, NullnessStore> result = super.visitInstanceOf(n, p);
+    NullnessStore thenStore = result.getThenStore();
+    NullnessStore elseStore = result.getElseStore();
+    makeNonNull(thenStore, n.getOperand());
+    return new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore);
+  }
+
+  @Override
+  public TransferResult<NullnessValue, NullnessStore> visitMethodAccess(
+      MethodAccessNode n, TransferInput<NullnessValue, NullnessStore> p) {
+    TransferResult<NullnessValue, NullnessStore> result = super.visitMethodAccess(n, p);
+    makeNonNull(result, n.getReceiver());
+    return result;
+  }
+
+  @Override
+  public TransferResult<NullnessValue, NullnessStore> visitFieldAccess(
+      FieldAccessNode n, TransferInput<NullnessValue, NullnessStore> p) {
+    TransferResult<NullnessValue, NullnessStore> result = super.visitFieldAccess(n, p);
+    makeNonNull(result, n.getReceiver());
+    return result;
+  }
+
+  @Override
+  public TransferResult<NullnessValue, NullnessStore> visitThrow(
+      ThrowNode n, TransferInput<NullnessValue, NullnessStore> p) {
+    TransferResult<NullnessValue, NullnessStore> result = super.visitThrow(n, p);
+    makeNonNull(result, n.getExpression());
+    return result;
+  }
+
+  /*
+   * Provided that m is of a type that implements interface java.util.Map:
+   * <ul>
+   * <li>Given a call m.get(k), if k is @KeyFor("m") and m's value type is @NonNull,
+   *     then the result is @NonNull in the thenStore and elseStore of the transfer result.
+   * </ul>
+   */
+  @Override
+  public TransferResult<NullnessValue, NullnessStore> visitMethodInvocation(
+      MethodInvocationNode n, TransferInput<NullnessValue, NullnessStore> in) {
+    TransferResult<NullnessValue, NullnessStore> result = super.visitMethodInvocation(n, in);
+
+    // Make receiver non-null.
+    Node receiver = n.getTarget().getReceiver();
+    makeNonNull(result, receiver);
+
+    // For all formal parameters with a non-null annotation, make the actual argument non-null.
+    // The point of this is to prevent cascaded errors -- the Nullness Checker will issue a
+    // warning for the method invocation, but not for subsequent uses of the argument.  See test
+    // case FlowNullness.java.
+    MethodInvocationTree tree = n.getTree();
+    ExecutableElement method = TreeUtils.elementFromUse(tree);
+    AnnotatedExecutableType methodType = nullnessTypeFactory.getAnnotatedType(method);
+    List<AnnotatedTypeMirror> methodParams = methodType.getParameterTypes();
+    List<? extends ExpressionTree> methodArgs = tree.getArguments();
+    for (int i = 0; i < methodParams.size() && i < methodArgs.size(); ++i) {
+      if (methodParams.get(i).hasAnnotation(NONNULL)) {
+        makeNonNull(result, n.getArgument(i));
+      }
+    }
+
+    // Refine result to @NonNull if n is an invocation of Map.get, the argument is a key for
+    // the map, and the map's value type is not @Nullable.
+    if (keyForTypeFactory != null && keyForTypeFactory.isMapGet(n)) {
+      String mapName = JavaExpression.fromNode(receiver).toString();
+      AnnotatedTypeMirror receiverType = nullnessTypeFactory.getReceiverType(n.getTree());
+
+      if (keyForTypeFactory.isKeyForMap(mapName, methodArgs.get(0))
+          && !hasNullableValueType(receiverType)) {
+        makeNonNull(result, n);
+        refineToNonNull(result);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns true if mapType's value type (the V type argument to Map) is @Nullable.
+   *
+   * @param mapOrSubtype the Map type, or a subtype
+   * @return true if mapType's value type is @Nullable
+   */
+  private boolean hasNullableValueType(AnnotatedTypeMirror mapOrSubtype) {
+    AnnotatedDeclaredType mapType =
+        AnnotatedTypes.asSuper(nullnessTypeFactory, mapOrSubtype, MAP_TYPE);
+    int numTypeArguments = mapType.getTypeArguments().size();
+    if (numTypeArguments != 2) {
+      throw new BugInCF("Wrong number %d of type arguments: %s", numTypeArguments, mapType);
+    }
+    AnnotatedTypeMirror valueType = mapType.getTypeArguments().get(1);
+    return valueType.hasAnnotation(NULLABLE);
+  }
+
+  @Override
+  public TransferResult<NullnessValue, NullnessStore> visitReturn(
+      ReturnNode n, TransferInput<NullnessValue, NullnessStore> in) {
+    TransferResult<NullnessValue, NullnessStore> result = super.visitReturn(n, in);
+
+    if (result.getResultValue() == null) {
+      // Make sure there is a value for return statements, to record (at this return
+      // statement) the values of isPolyNullNotNull and isPolyNullNull.
+      return recreateTransferResult(createDummyValue(), result);
+    } else {
+      return result;
+    }
+  }
+
+  /** Creates a dummy abstract value (whose type is not supposed to be looked at). */
+  private NullnessValue createDummyValue() {
+    TypeMirror dummy = analysis.getEnv().getTypeUtils().getPrimitiveType(TypeKind.BOOLEAN);
+    Set<AnnotationMirror> annos = AnnotationUtils.createAnnotationSet();
+    annos.addAll(nullnessTypeFactory.getQualifierHierarchy().getBottomAnnotations());
+    return new NullnessValue(analysis, annos, dummy);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessValue.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessValue.java
new file mode 100644
index 0000000..4714a63
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessValue.java
@@ -0,0 +1,94 @@
+package org.checkerframework.checker.nullness;
+
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.PolyNull;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAbstractValue;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Behaves just like {@link CFValue}, but additionally tracks whether at this point {@link PolyNull}
+ * is known to be {@link NonNull} or {@link Nullable} (or not known to be either)
+ */
+public class NullnessValue extends CFAbstractValue<NullnessValue> {
+
+  /** True if, at this point, {@link PolyNull} is known to be {@link NonNull}. */
+  protected boolean isPolyNullNonNull;
+
+  /** True if, at this point, {@link PolyNull} is known to be {@link Nullable}. */
+  protected boolean isPolyNullNull;
+
+  public NullnessValue(
+      CFAbstractAnalysis<NullnessValue, ?, ?> analysis,
+      Set<AnnotationMirror> annotations,
+      TypeMirror underlyingType) {
+    super(analysis, annotations, underlyingType);
+  }
+
+  @Override
+  public NullnessValue leastUpperBound(NullnessValue other) {
+    NullnessValue result = super.leastUpperBound(other);
+
+    AnnotationMirror resultNullableAnno =
+        analysis.getTypeFactory().getAnnotationByClass(result.annotations, Nullable.class);
+
+    if (resultNullableAnno != null && other != null) {
+      if ((this.isPolyNullNonNull
+              && this.containsNonNullOrPolyNull()
+              && other.isPolyNullNull
+              && other.containsNullableOrPolyNull())
+          || (other.isPolyNullNonNull
+              && other.containsNonNullOrPolyNull()
+              && this.isPolyNullNull
+              && this.containsNullableOrPolyNull())) {
+        result.annotations.remove(resultNullableAnno);
+        result.annotations.add(((NullnessAnnotatedTypeFactory) analysis.getTypeFactory()).POLYNULL);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns true if this value contans {@code @NonNull} or {@code @PolyNull}.
+   *
+   * @return true if this value contans {@code @NonNull} or {@code @PolyNull}
+   */
+  @Pure
+  private boolean containsNonNullOrPolyNull() {
+    return analysis.getTypeFactory().containsSameByClass(annotations, NonNull.class)
+        || analysis.getTypeFactory().containsSameByClass(annotations, PolyNull.class);
+  }
+
+  /**
+   * Returns true if this value contans {@code @Nullable} or {@code @PolyNull}.
+   *
+   * @return true if this value contans {@code @Nullable} or {@code @PolyNull}
+   */
+  @Pure
+  private boolean containsNullableOrPolyNull() {
+    return analysis.getTypeFactory().containsSameByClass(annotations, Nullable.class)
+        || analysis.getTypeFactory().containsSameByClass(annotations, PolyNull.class);
+  }
+
+  @SideEffectFree
+  @Override
+  public String toStringSimple() {
+    return "NV{"
+        + AnnotationUtils.toStringSimple(annotations)
+        + ", "
+        + TypesUtils.simpleTypeName(underlyingType)
+        + ", "
+        + (isPolyNullNonNull ? 't' : 'f')
+        + ' '
+        + (isPolyNullNull ? 't' : 'f')
+        + '}';
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java
new file mode 100644
index 0000000..8c0f9e3
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java
@@ -0,0 +1,831 @@
+package org.checkerframework.checker.nullness;
+
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.AssertTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CatchTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.DoWhileLoopTree;
+import com.sun.source.tree.EnhancedForLoopTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.ForLoopTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.SwitchTree;
+import com.sun.source.tree.SynchronizedTree;
+import com.sun.source.tree.ThrowTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.tree.WhileLoopTree;
+import com.sun.source.util.TreePath;
+import java.lang.annotation.Annotation;
+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;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.checker.initialization.InitializationVisitor;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeValidator;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.basetype.TypeValidator;
+import org.checkerframework.framework.flow.CFCFGBuilder;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** The visitor for the nullness type-system. */
+public class NullnessVisitor
+    extends InitializationVisitor<NullnessAnnotatedTypeFactory, NullnessValue, NullnessStore> {
+  // Error message keys
+  // private static final @CompilerMessageKey String ASSIGNMENT_TYPE_INCOMPATIBLE =
+  // "assignment.type.incompatible";
+  private static final @CompilerMessageKey String UNBOXING_OF_NULLABLE = "unboxing.of.nullable";
+  private static final @CompilerMessageKey String LOCKING_NULLABLE = "locking.nullable";
+  private static final @CompilerMessageKey String THROWING_NULLABLE = "throwing.nullable";
+  private static final @CompilerMessageKey String ACCESSING_NULLABLE = "accessing.nullable";
+  private static final @CompilerMessageKey String CONDITION_NULLABLE = "condition.nullable";
+  private static final @CompilerMessageKey String ITERATING_NULLABLE = "iterating.over.nullable";
+  private static final @CompilerMessageKey String SWITCHING_NULLABLE = "switching.nullable";
+  private static final @CompilerMessageKey String DEREFERENCE_OF_NULLABLE =
+      "dereference.of.nullable";
+
+  // Annotation and type constants
+  private final AnnotationMirror NONNULL, NULLABLE, MONOTONIC_NONNULL;
+  private final TypeMirror stringType;
+
+  /** The element for java.util.Collection.size(). */
+  private final ExecutableElement collectionSize;
+
+  /** The element for java.util.Collection.toArray(T). */
+  private final ExecutableElement collectionToArray;
+
+  /** The System.clearProperty(String) method. */
+  private final ExecutableElement systemClearProperty;
+
+  /** The System.setProperties(String) method. */
+  private final ExecutableElement systemSetProperties;
+
+  /** True if checked code may clear system properties. */
+  private final boolean permitClearProperty;
+
+  /**
+   * Create a new NullnessVisitor.
+   *
+   * @param checker the checker to which this visitor belongs
+   */
+  public NullnessVisitor(BaseTypeChecker checker) {
+    super(checker);
+
+    NONNULL = atypeFactory.NONNULL;
+    NULLABLE = atypeFactory.NULLABLE;
+    MONOTONIC_NONNULL = atypeFactory.MONOTONIC_NONNULL;
+    stringType = elements.getTypeElement(String.class.getCanonicalName()).asType();
+
+    ProcessingEnvironment env = checker.getProcessingEnvironment();
+    this.collectionSize = TreeUtils.getMethod("java.util.Collection", "size", 0, env);
+    this.collectionToArray = TreeUtils.getMethod("java.util.Collection", "toArray", env, "T[]");
+    systemClearProperty = TreeUtils.getMethod("java.lang.System", "clearProperty", 1, env);
+    systemSetProperties = TreeUtils.getMethod("java.lang.System", "setProperties", 1, env);
+
+    this.permitClearProperty =
+        checker.getLintOption(
+            NullnessChecker.LINT_PERMITCLEARPROPERTY,
+            NullnessChecker.LINT_DEFAULT_PERMITCLEARPROPERTY);
+  }
+
+  @Override
+  public NullnessAnnotatedTypeFactory createTypeFactory() {
+    return new NullnessAnnotatedTypeFactory(checker);
+  }
+
+  @Override
+  public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) {
+    // The Nullness Checker issues a more comprehensible "nullness.on.primitive" error rather
+    // than the "annotations.on.use" error this method would issue.
+    return true;
+  }
+
+  private boolean containsSameByName(
+      Set<Class<? extends Annotation>> quals, AnnotationMirror anno) {
+    for (Class<? extends Annotation> q : quals) {
+      if (atypeFactory.areSameByClass(anno, q)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  protected void commonAssignmentCheck(
+      Tree varTree,
+      ExpressionTree valueExp,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+
+    // Allow a MonotonicNonNull field to be initialized to null at its declaration, in a
+    // constructor, or in an initializer block.  (The latter two are, strictly speaking, unsound
+    // because the constructor or initializer block might have previously set the field to a
+    // non-null value.  Maybe add an option to disable that behavior.)
+    Element elem = initializedElement(varTree);
+    if (elem != null
+        && atypeFactory.fromElement(elem).hasEffectiveAnnotation(MONOTONIC_NONNULL)
+        && !checker.getLintOption(
+            NullnessChecker.LINT_NOINITFORMONOTONICNONNULL,
+            NullnessChecker.LINT_DEFAULT_NOINITFORMONOTONICNONNULL)) {
+      return;
+    }
+    super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs);
+  }
+
+  /**
+   * Returns the variable element, if the argument is an initialization; otherwise returns null.
+   *
+   * @param varTree an assignment LHS
+   * @return the initialized element, or null
+   */
+  @SuppressWarnings("UnusedMethod")
+  private Element initializedElement(Tree varTree) {
+    switch (varTree.getKind()) {
+      case VARIABLE:
+        // It's a variable declaration.
+        return TreeUtils.elementFromDeclaration((VariableTree) varTree);
+
+      case MEMBER_SELECT:
+        MemberSelectTree mst = (MemberSelectTree) varTree;
+        ExpressionTree receiver = mst.getExpression();
+        // This recognizes "this.fieldname = ..." but not "MyClass.fieldname = ..." or
+        // "MyClass.this.fieldname = ...".  The latter forms are probably rare in a constructor.
+        // Note that this method should return non-null only for fields of this class, not fields of
+        // any other class, including outer classes.
+        if (receiver.getKind() != Tree.Kind.IDENTIFIER
+            || !((IdentifierTree) receiver).getName().contentEquals("this")) {
+          return null;
+        }
+        // fallthrough
+      case IDENTIFIER:
+        TreePath path = getCurrentPath();
+        if (TreePathUtil.inConstructor(path)) {
+          return TreeUtils.elementFromUse((ExpressionTree) varTree);
+        } else {
+          return null;
+        }
+
+      default:
+        return null;
+    }
+  }
+
+  @Override
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      ExpressionTree valueExp,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    // Use the valueExp as the context because data flow will have a value for that tree.  It might
+    // not have a value for the var tree.  This is sound because if data flow has determined
+    // @PolyNull is @Nullable at the RHS, then it is also @Nullable for the LHS.
+    atypeFactory.replacePolyQualifier(varType, valueExp);
+    super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs);
+  }
+
+  @Override
+  @FormatMethod
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      AnnotatedTypeMirror valueType,
+      Tree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    if (TypesUtils.isPrimitive(varType.getUnderlyingType())
+        && !TypesUtils.isPrimitive(valueType.getUnderlyingType())) {
+      boolean succeed = checkForNullability(valueType, valueTree, UNBOXING_OF_NULLABLE);
+      if (!succeed) {
+        // Only issue the unboxing of nullable error.
+        return;
+      }
+    }
+    super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
+  }
+
+  /** Case 1: Check for null dereferencing. */
+  @Override
+  public Void visitMemberSelect(MemberSelectTree node, Void p) {
+    Element e = TreeUtils.elementFromTree(node);
+    if (e.getKind() == ElementKind.CLASS) {
+      if (atypeFactory.containsNullnessAnnotation(null, node.getExpression())) {
+        checker.reportError(node, "nullness.on.outer");
+      }
+    } else if (!(TreeUtils.isSelfAccess(node)
+        || node.getExpression().getKind() == Kind.PARAMETERIZED_TYPE
+        // case 8. static member access
+        || ElementUtils.isStatic(e))) {
+      checkForNullability(node.getExpression(), DEREFERENCE_OF_NULLABLE);
+    }
+
+    return super.visitMemberSelect(node, p);
+  }
+
+  /** Case 2: Check for implicit {@code .iterator} call. */
+  @Override
+  public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void p) {
+    checkForNullability(node.getExpression(), ITERATING_NULLABLE);
+    return super.visitEnhancedForLoop(node, p);
+  }
+
+  /** Case 3: Check for array dereferencing. */
+  @Override
+  public Void visitArrayAccess(ArrayAccessTree node, Void p) {
+    checkForNullability(node.getExpression(), ACCESSING_NULLABLE);
+    return super.visitArrayAccess(node, p);
+  }
+
+  @Override
+  public Void visitNewArray(NewArrayTree node, Void p) {
+    AnnotatedArrayType type = atypeFactory.getAnnotatedType(node);
+    AnnotatedTypeMirror componentType = type.getComponentType();
+    if (componentType.hasEffectiveAnnotation(NONNULL)
+        && !isNewArrayAllZeroDims(node)
+        && !isNewArrayInToArray(node)
+        && !TypesUtils.isPrimitive(componentType.getUnderlyingType())
+        && (checker.getLintOption("soundArrayCreationNullness", false)
+            // temporary, for backward compatibility
+            || checker.getLintOption("forbidnonnullarraycomponents", false))) {
+      checker.reportError(node, "new.array", componentType.getAnnotations(), type.toString());
+    }
+
+    return super.visitNewArray(node, p);
+  }
+
+  /**
+   * Determine whether all dimensions given in a new array expression have zero as length. For
+   * example "new Object[0][0];". Also true for empty dimensions, as in "new Object[] {...}".
+   */
+  private static boolean isNewArrayAllZeroDims(NewArrayTree node) {
+    boolean isAllZeros = true;
+    for (ExpressionTree dim : node.getDimensions()) {
+      if (dim instanceof LiteralTree) {
+        Object val = ((LiteralTree) dim).getValue();
+        if (!(val instanceof Number) || !Integer.valueOf(0).equals(val)) {
+          isAllZeros = false;
+          break;
+        }
+      } else {
+        isAllZeros = false;
+        break;
+      }
+    }
+    return isAllZeros;
+  }
+
+  /**
+   * Return true if the given node is "new X[]", in the context "toArray(new X[])".
+   *
+   * @param node a node to test
+   * @return true if the node is a new array within acall to toArray()
+   */
+  private boolean isNewArrayInToArray(NewArrayTree node) {
+    if (node.getDimensions().size() != 1) {
+      return false;
+    }
+
+    ExpressionTree dim = node.getDimensions().get(0);
+    ProcessingEnvironment env = checker.getProcessingEnvironment();
+
+    if (!TreeUtils.isMethodInvocation(dim, collectionSize, env)) {
+      return false;
+    }
+
+    ExpressionTree rcvsize = ((MethodInvocationTree) dim).getMethodSelect();
+    if (!(rcvsize instanceof MemberSelectTree)) {
+      return false;
+    }
+    rcvsize = ((MemberSelectTree) rcvsize).getExpression();
+    if (!(rcvsize instanceof IdentifierTree)) {
+      return false;
+    }
+
+    Tree encl = getCurrentPath().getParentPath().getLeaf();
+
+    if (!TreeUtils.isMethodInvocation(encl, collectionToArray, env)) {
+      return false;
+    }
+
+    ExpressionTree rcvtoarray = ((MethodInvocationTree) encl).getMethodSelect();
+    if (!(rcvtoarray instanceof MemberSelectTree)) {
+      return false;
+    }
+    rcvtoarray = ((MemberSelectTree) rcvtoarray).getExpression();
+    if (!(rcvtoarray instanceof IdentifierTree)) {
+      return false;
+    }
+
+    return ((IdentifierTree) rcvsize).getName() == ((IdentifierTree) rcvtoarray).getName();
+  }
+
+  /** Case 4: Check for thrown exception nullness. */
+  @Override
+  protected void checkThrownExpression(ThrowTree node) {
+    checkForNullability(node.getExpression(), THROWING_NULLABLE);
+  }
+
+  /** Case 5: Check for synchronizing locks. */
+  @Override
+  public Void visitSynchronized(SynchronizedTree node, Void p) {
+    checkForNullability(node.getExpression(), LOCKING_NULLABLE);
+    return super.visitSynchronized(node, p);
+  }
+
+  @Override
+  public Void visitAssert(AssertTree node, Void p) {
+    // See also
+    // org.checkerframework.dataflow.cfg.builder.CFGBuilder.CFGTranslationPhaseOne.visitAssert
+
+    // In cases where neither assumeAssertionsAreEnabled nor assumeAssertionsAreDisabled are turned
+    // on and @AssumeAssertions is not used, checkForNullability is still called since the
+    // CFGBuilder will have generated one branch for which asserts are assumed to be enabled.
+
+    boolean doVisitAssert = true;
+
+    if (checker.hasOption("assumeAssertionsAreEnabled")
+        || CFCFGBuilder.assumeAssertionsActivatedForAssertTree(checker, node)) {
+      doVisitAssert = true;
+    } else if (checker.hasOption("assumeAssertionsAreDisabled")) {
+      doVisitAssert = false;
+    }
+
+    if (doVisitAssert) {
+      checkForNullability(node.getCondition(), CONDITION_NULLABLE);
+      return super.visitAssert(node, p);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitIf(IfTree node, Void p) {
+    checkForNullability(node.getCondition(), CONDITION_NULLABLE);
+    return super.visitIf(node, p);
+  }
+
+  @Override
+  public Void visitInstanceOf(InstanceOfTree node, Void p) {
+    // The "reference type" is the type after "instanceof".
+    Tree refTypeTree = node.getType();
+    if (refTypeTree.getKind() == Kind.ANNOTATED_TYPE) {
+      List<? extends AnnotationMirror> annotations =
+          TreeUtils.annotationsFromTree((AnnotatedTypeTree) refTypeTree);
+      if (AnnotationUtils.containsSame(annotations, NULLABLE)) {
+        checker.reportError(node, "instanceof.nullable");
+      }
+      if (AnnotationUtils.containsSame(annotations, NONNULL)) {
+        checker.reportWarning(node, "instanceof.nonnull.redundant");
+      }
+    }
+    return super.visitInstanceOf(node, p);
+  }
+
+  /**
+   * Reports an error if a comparison of a @NonNull expression with the null literal is performed.
+   */
+  protected void checkForRedundantTests(BinaryTree node) {
+
+    final ExpressionTree leftOp = node.getLeftOperand();
+    final ExpressionTree rightOp = node.getRightOperand();
+
+    // respect command-line option
+    if (!checker.getLintOption(
+        NullnessChecker.LINT_REDUNDANTNULLCOMPARISON,
+        NullnessChecker.LINT_DEFAULT_REDUNDANTNULLCOMPARISON)) {
+      return;
+    }
+
+    // equality tests
+    if ((node.getKind() == Tree.Kind.EQUAL_TO || node.getKind() == Tree.Kind.NOT_EQUAL_TO)) {
+      AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp);
+      AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp);
+      if (leftOp.getKind() == Tree.Kind.NULL_LITERAL && right.hasEffectiveAnnotation(NONNULL)) {
+        checker.reportWarning(node, "nulltest.redundant", rightOp.toString());
+      } else if (rightOp.getKind() == Tree.Kind.NULL_LITERAL
+          && left.hasEffectiveAnnotation(NONNULL)) {
+        checker.reportWarning(node, "nulltest.redundant", leftOp.toString());
+      }
+    }
+  }
+
+  /** Case 6: Check for redundant nullness tests Case 7: unboxing case: primitive operations. */
+  @Override
+  public Void visitBinary(BinaryTree node, Void p) {
+    final ExpressionTree leftOp = node.getLeftOperand();
+    final ExpressionTree rightOp = node.getRightOperand();
+
+    if (isUnboxingOperation(node)) {
+      checkForNullability(leftOp, UNBOXING_OF_NULLABLE);
+      checkForNullability(rightOp, UNBOXING_OF_NULLABLE);
+    }
+
+    checkForRedundantTests(node);
+
+    return super.visitBinary(node, p);
+  }
+
+  /** Case 7: unboxing case: primitive operation. */
+  @Override
+  public Void visitUnary(UnaryTree node, Void p) {
+    checkForNullability(node.getExpression(), UNBOXING_OF_NULLABLE);
+    return super.visitUnary(node, p);
+  }
+
+  /** Case 7: unboxing case: primitive operation. */
+  @Override
+  public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
+    // ignore String concatenation
+    if (!isString(node)) {
+      checkForNullability(node.getVariable(), UNBOXING_OF_NULLABLE);
+      checkForNullability(node.getExpression(), UNBOXING_OF_NULLABLE);
+    }
+    return super.visitCompoundAssignment(node, p);
+  }
+
+  /** Case 7: unboxing case: casting to a primitive. */
+  @Override
+  public Void visitTypeCast(TypeCastTree node, Void p) {
+    if (isPrimitive(node) && !isPrimitive(node.getExpression())) {
+      if (!checkForNullability(node.getExpression(), UNBOXING_OF_NULLABLE)) {
+        // If unboxing of nullable is issued, don't issue any other errors.
+        return null;
+      }
+    }
+    return super.visitTypeCast(node, p);
+  }
+
+  @Override
+  public Void visitMethod(MethodTree node, Void p) {
+    if (TreeUtils.isConstructor(node)) {
+      List<? extends AnnotationTree> annoTrees = node.getModifiers().getAnnotations();
+      if (atypeFactory.containsNullnessAnnotation(annoTrees)) {
+        checker.reportError(node, "nullness.on.constructor");
+      }
+    }
+
+    VariableTree receiver = node.getReceiverParameter();
+    if (receiver != null) {
+      List<? extends AnnotationTree> annoTrees = receiver.getModifiers().getAnnotations();
+      Tree type = receiver.getType();
+      if (atypeFactory.containsNullnessAnnotation(annoTrees, type)) {
+        checker.reportError(node, "nullness.on.receiver");
+      }
+    }
+
+    return super.visitMethod(node, p);
+  }
+
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+    if (!permitClearProperty) {
+      ProcessingEnvironment env = checker.getProcessingEnvironment();
+      if (TreeUtils.isMethodInvocation(node, systemClearProperty, env)) {
+        String literal = literalFirstArgument(node);
+        if (literal == null
+            || SystemGetPropertyHandler.predefinedSystemProperties.contains(literal)) {
+          checker.reportError(node, "clear.system.property");
+        }
+      }
+      if (TreeUtils.isMethodInvocation(node, systemSetProperties, env)) {
+        checker.reportError(node, "clear.system.property");
+      }
+    }
+    return super.visitMethodInvocation(node, p);
+  }
+
+  /**
+   * If the first argument of a method call is a literal, return it; otherwise return null.
+   *
+   * @param tree a method invocation whose first formal parameter is of String type
+   * @return the first argument if it is a literal, otherwise null
+   */
+  /*package-private*/ static @Nullable String literalFirstArgument(MethodInvocationTree tree) {
+    List<? extends ExpressionTree> args = tree.getArguments();
+    assert args.size() > 0;
+    ExpressionTree arg = args.get(0);
+    if (arg.getKind() == Tree.Kind.STRING_LITERAL) {
+      String literal = (String) ((LiteralTree) arg).getValue();
+      return literal;
+    }
+    return null;
+  }
+
+  @Override
+  public void processClassTree(ClassTree classTree) {
+
+    Tree extendsClause = classTree.getExtendsClause();
+    if (extendsClause != null) {
+      reportErrorIfSupertypeContainsNullnessAnnotation(extendsClause);
+    }
+    for (Tree implementsClause : classTree.getImplementsClause()) {
+      reportErrorIfSupertypeContainsNullnessAnnotation(implementsClause);
+    }
+
+    if (classTree.getKind() == Tree.Kind.ENUM) {
+      for (Tree member : classTree.getMembers()) {
+        if (member.getKind() == Tree.Kind.VARIABLE
+            && TreeUtils.elementFromDeclaration((VariableTree) member).getKind()
+                == ElementKind.ENUM_CONSTANT) {
+          VariableTree varDecl = (VariableTree) member;
+          List<? extends AnnotationTree> annoTrees = varDecl.getModifiers().getAnnotations();
+          Tree type = varDecl.getType();
+          if (atypeFactory.containsNullnessAnnotation(annoTrees, type)) {
+            checker.reportError(member, "nullness.on.enum");
+          }
+        }
+      }
+    }
+
+    super.processClassTree(classTree);
+  }
+
+  /**
+   * Report "nullness.on.supertype" error if a supertype has a nullness annotation.
+   *
+   * @param typeTree a supertype tree, from an {@code extends} or {@code implements} clause
+   */
+  private void reportErrorIfSupertypeContainsNullnessAnnotation(Tree typeTree) {
+    if (typeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) {
+      List<? extends AnnotationTree> annoTrees = ((AnnotatedTypeTree) typeTree).getAnnotations();
+      if (atypeFactory.containsNullnessAnnotation(annoTrees)) {
+        checker.reportError(typeTree, "nullness.on.supertype");
+      }
+    }
+  }
+
+  // ///////////// Utility methods //////////////////////////////
+
+  /**
+   * Issues the error message if the type of the tree is not of a {@link NonNull} type.
+   *
+   * @param tree the tree where the error is to reported
+   * @param errMsg the error message (must be {@link CompilerMessageKey})
+   * @return whether or not the check succeeded
+   */
+  private boolean checkForNullability(ExpressionTree tree, @CompilerMessageKey String errMsg) {
+    AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree);
+    return checkForNullability(type, tree, errMsg);
+  }
+
+  /**
+   * Issues the error message if an expression with this type may be null.
+   *
+   * @param type annotated type
+   * @param tree the tree where the error is to reported
+   * @param errMsg the error message (must be {@link CompilerMessageKey})
+   * @return whether or not the check succeeded
+   */
+  private boolean checkForNullability(
+      AnnotatedTypeMirror type, Tree tree, @CompilerMessageKey String errMsg) {
+    if (!type.hasEffectiveAnnotation(NONNULL)) {
+      checker.reportError(tree, errMsg, tree);
+      return false;
+    }
+    return true;
+  }
+
+  @Override
+  protected void checkMethodInvocability(
+      AnnotatedExecutableType method, MethodInvocationTree node) {
+    if (!TreeUtils.isSelfAccess(node)
+        &&
+        // Static methods don't have a receiver
+        method.getReceiverType() != null) {
+      // TODO: should all or some constructors be excluded?
+      // method.getElement().getKind() != ElementKind.CONSTRUCTOR) {
+      Set<AnnotationMirror> receiverAnnos = atypeFactory.getReceiverType(node).getAnnotations();
+      AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased();
+      AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false);
+      AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(node);
+      treeReceiver.addAnnotations(rcv.getEffectiveAnnotations());
+      // If receiver is Nullable, then we don't want to issue a warning about method invocability
+      // (we'd rather have only the "dereference.of.nullable" message).
+      if (treeReceiver.hasAnnotation(NULLABLE) || receiverAnnos.contains(MONOTONIC_NONNULL)) {
+        return;
+      }
+    }
+    super.checkMethodInvocability(method, node);
+  }
+
+  /** @return true if binary operation could cause an unboxing operation */
+  private final boolean isUnboxingOperation(BinaryTree tree) {
+    if (tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO) {
+      // it is valid to check equality between two reference types, even
+      // if one (or both) of them is null
+      return isPrimitive(tree.getLeftOperand()) != isPrimitive(tree.getRightOperand());
+    } else {
+      // All BinaryTree's are of type String, a primitive type or the reference type equivalent of a
+      // primitive type. Furthermore, Strings don't have a primitive type, and therefore only
+      // BinaryTrees that aren't String can cause unboxing.
+      return !isString(tree);
+    }
+  }
+
+  /** @return true if the type of the tree is a super of String */
+  private final boolean isString(ExpressionTree tree) {
+    TypeMirror type = TreeUtils.typeOf(tree);
+    return types.isAssignable(stringType, type);
+  }
+
+  /** @return true if the type of the tree is a primitive */
+  private static final boolean isPrimitive(ExpressionTree tree) {
+    return TreeUtils.typeOf(tree).getKind().isPrimitive();
+  }
+
+  @Override
+  public Void visitSwitch(SwitchTree node, Void p) {
+    checkForNullability(node.getExpression(), SWITCHING_NULLABLE);
+    return super.visitSwitch(node, p);
+  }
+
+  @Override
+  public Void visitForLoop(ForLoopTree node, Void p) {
+    if (node.getCondition() != null) {
+      // Condition is null e.g. in "for (;;) {...}"
+      checkForNullability(node.getCondition(), CONDITION_NULLABLE);
+    }
+    return super.visitForLoop(node, p);
+  }
+
+  @Override
+  public Void visitNewClass(NewClassTree node, Void p) {
+    AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(node);
+    ExpressionTree identifier = node.getIdentifier();
+    if (identifier instanceof AnnotatedTypeTree) {
+      AnnotatedTypeTree t = (AnnotatedTypeTree) identifier;
+      for (AnnotationMirror a : atypeFactory.getAnnotatedType(t).getAnnotations()) {
+        // is this an annotation of the nullness checker?
+        boolean nullnessCheckerAnno = containsSameByName(atypeFactory.getNullnessAnnotations(), a);
+        if (nullnessCheckerAnno && !AnnotationUtils.areSame(NONNULL, a)) {
+          // The type is not non-null => warning
+          checker.reportWarning(node, "new.class", type.getAnnotations());
+          // Note that other consistency checks are made by isValid.
+        }
+      }
+      if (t.toString().contains("@PolyNull")) {
+        // TODO: this is a hack, but PolyNull gets substituted
+        // afterwards
+        checker.reportWarning(node, "new.class", type.getAnnotations());
+      }
+    }
+    // TODO: It might be nicer to introduce a framework-level
+    // isValidNewClassType or some such.
+    return super.visitNewClass(node, p);
+  }
+
+  @Override
+  public Void visitWhileLoop(WhileLoopTree node, Void p) {
+    checkForNullability(node.getCondition(), CONDITION_NULLABLE);
+    return super.visitWhileLoop(node, p);
+  }
+
+  @Override
+  public Void visitDoWhileLoop(DoWhileLoopTree node, Void p) {
+    checkForNullability(node.getCondition(), CONDITION_NULLABLE);
+    return super.visitDoWhileLoop(node, p);
+  }
+
+  @Override
+  public Void visitConditionalExpression(ConditionalExpressionTree node, Void p) {
+    checkForNullability(node.getCondition(), CONDITION_NULLABLE);
+    return super.visitConditionalExpression(node, p);
+  }
+
+  @Override
+  protected void checkExceptionParameter(CatchTree node) {
+    VariableTree param = node.getParameter();
+    List<? extends AnnotationTree> annoTrees = param.getModifiers().getAnnotations();
+    Tree paramType = param.getType();
+    if (atypeFactory.containsNullnessAnnotation(annoTrees, paramType)) {
+      // This is a warning rather than an error because writing `@Nullable` could make sense
+      // if the catch block re-assigns the variable to null.  (That would be bad style.)
+      checker.reportWarning(param, "nullness.on.exception.parameter");
+    }
+
+    // Don't call super.
+    // BasetypeVisitor forces annotations on exception parameters to be top, but because exceptions
+    // can never be null, the Nullness Checker does not require this check.
+  }
+
+  @Override
+  public Void visitAnnotation(AnnotationTree node, Void p) {
+    // All annotation arguments are non-null and initialized, so no need to check them.
+    return null;
+  }
+
+  @Override
+  public void visitAnnotatedType(
+      @Nullable List<? extends AnnotationTree> annoTrees, Tree typeTree) {
+    // Look for a MEMBER_SELECT or PRIMITIVE within the type.
+    Tree t = typeTree;
+    while (t != null) {
+      switch (t.getKind()) {
+        case MEMBER_SELECT:
+          Tree expr = ((MemberSelectTree) t).getExpression();
+          if (atypeFactory.containsNullnessAnnotation(annoTrees, expr)) {
+            checker.reportError(expr, "nullness.on.outer");
+          }
+          t = null;
+          break;
+        case PRIMITIVE_TYPE:
+          if (atypeFactory.containsNullnessAnnotation(annoTrees, t)) {
+            checker.reportError(t, "nullness.on.primitive");
+          }
+          t = null;
+          break;
+        case ANNOTATED_TYPE:
+          AnnotatedTypeTree at = ((AnnotatedTypeTree) t);
+          Tree underlying = at.getUnderlyingType();
+          if (underlying.getKind() == Tree.Kind.PRIMITIVE_TYPE) {
+            if (atypeFactory.containsNullnessAnnotation(null, at)) {
+              checker.reportError(t, "nullness.on.primitive");
+            }
+            t = null;
+          } else {
+            t = underlying;
+          }
+          break;
+        case ARRAY_TYPE:
+          t = ((ArrayTypeTree) t).getType();
+          break;
+        case PARAMETERIZED_TYPE:
+          t = ((ParameterizedTypeTree) t).getType();
+          break;
+        default:
+          t = null;
+          break;
+      }
+    }
+
+    super.visitAnnotatedType(annoTrees, typeTree);
+  }
+
+  @Override
+  protected TypeValidator createTypeValidator() {
+    return new NullnessValidator(checker, this, atypeFactory);
+  }
+
+  /**
+   * Check that primitive types are annotated with {@code @NonNull} even if they are the type of a
+   * local variable.
+   */
+  private static class NullnessValidator extends BaseTypeValidator {
+
+    /**
+     * Create NullnessValidator.
+     *
+     * @param checker checker
+     * @param visitor visitor
+     * @param atypeFactory factory
+     */
+    public NullnessValidator(
+        BaseTypeChecker checker, BaseTypeVisitor<?> visitor, AnnotatedTypeFactory atypeFactory) {
+      super(checker, visitor, atypeFactory);
+    }
+
+    @Override
+    protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType(
+        AnnotatedTypeMirror type, Tree tree) {
+      if (type.getKind().isPrimitive()) {
+        return true;
+      }
+      return super.shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree);
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/SystemGetPropertyHandler.java b/checker/src/main/java/org/checkerframework/checker/nullness/SystemGetPropertyHandler.java
new file mode 100644
index 0000000..577ea90
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/SystemGetPropertyHandler.java
@@ -0,0 +1,118 @@
+package org.checkerframework.checker.nullness;
+
+import com.sun.source.tree.MethodInvocationTree;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Utility class for handling {@link java.lang.System#getProperty(String)} and related invocations.
+ *
+ * <p>The result of the method call is is assumed to be non-null if the argument is a literal key
+ * that is guaranteed to be present in the system properties (according to the documentation of
+ * {@link java.lang.System#getProperty(String)}), as in {@code
+ * System.getProperties("line.separator")}.
+ */
+public class SystemGetPropertyHandler {
+
+  /**
+   * If true, client code may clear system properties, and this class (SystemGetPropertyHandler) has
+   * no effect.
+   */
+  private final boolean permitClearProperty;
+
+  /** The processing environment. */
+  private final ProcessingEnvironment env;
+
+  /** The factory for constructing and looking up types. */
+  private final NullnessAnnotatedTypeFactory factory;
+
+  /** The System.getProperty(String) method. */
+  private final ExecutableElement systemGetProperty;
+
+  /** The System.setProperty(String) method. */
+  private final ExecutableElement systemSetProperty;
+
+  /**
+   * System properties that are defined at startup on every JVM.
+   *
+   * <p>This list is from the Javadoc of System.getProperties, for Java 11.
+   */
+  public static final Collection<String> predefinedSystemProperties =
+      new HashSet<>(
+          Arrays.asList(
+              "java.version",
+              "java.version.date",
+              "java.vendor",
+              "java.vendor.url",
+              "java.vendor.version",
+              "java.home",
+              "java.vm.specification.version",
+              "java.vm.specification.vendor",
+              "java.vm.specification.name",
+              "java.vm.version",
+              "java.vm.vendor",
+              "java.vm.name",
+              "java.specification.version",
+              "java.specification.vendor",
+              "java.specification.name",
+              "java.class.version",
+              "java.class.path",
+              "java.library.path",
+              "java.io.tmpdir",
+              "java.compiler",
+              "os.name",
+              "os.arch",
+              "os.version",
+              "file.separator",
+              "path.separator",
+              "line.separator",
+              "user.name",
+              "user.home",
+              "user.dir"));
+
+  /**
+   * Creates a SystemGetPropertyHandler.
+   *
+   * @param env the processing environment
+   * @param factory the factory for constructing and looking up types
+   * @param permitClearProperty if true, client code may clear system properties, and this object
+   *     does nothing
+   */
+  public SystemGetPropertyHandler(
+      ProcessingEnvironment env,
+      NullnessAnnotatedTypeFactory factory,
+      boolean permitClearProperty) {
+    this.env = env;
+    this.factory = factory;
+    this.permitClearProperty = permitClearProperty;
+
+    systemGetProperty = TreeUtils.getMethod("java.lang.System", "getProperty", 1, env);
+    systemSetProperty = TreeUtils.getMethod("java.lang.System", "setProperty", 2, env);
+  }
+
+  /**
+   * Apply rules regarding System.getProperty and related methods.
+   *
+   * @param tree a method invocation
+   * @param method the method being invoked
+   */
+  public void handle(MethodInvocationTree tree, AnnotatedExecutableType method) {
+    if (permitClearProperty) {
+      return;
+    }
+    if (TreeUtils.isMethodInvocation(tree, systemGetProperty, env)
+        || TreeUtils.isMethodInvocation(tree, systemSetProperty, env)) {
+      String literal = NullnessVisitor.literalFirstArgument(tree);
+      if (literal != null && predefinedSystemProperties.contains(literal)) {
+        AnnotatedTypeMirror type = method.getReturnType();
+        type.replaceAnnotation(factory.NONNULL);
+      }
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/collection-object-parameters-may-be-null.astub b/checker/src/main/java/org/checkerframework/checker/nullness/collection-object-parameters-may-be-null.astub
new file mode 100644
index 0000000..b2cb372
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/collection-object-parameters-may-be-null.astub
@@ -0,0 +1,255 @@
+// This file is a workaround for https://tinyurl.com/cfissue/1326 .
+// For documentation, see
+// https://checkerframework.org/manual/#collection-object-parameters-may-be-null .
+
+// This file relaxes the specifications for the following methods when they are documented to
+// possibly throw NPE.  It does not change the specifications when they are documented to definitely
+// throw NPE.
+// containsAll(Collection)
+// containsKey(Object)
+// contains(Object)
+// containsValue(Object)
+// get(Object)
+// getOrDefault(Object, V)
+// indexOf(Object)
+// lastIndexOf(Object)
+// removeAll(Collection)
+// removeFirstOccurrence(Object)
+// removeLastOccurrence(Object)
+// remove(Object)
+// remove(Object, Object)
+// retainAll(Collection)
+
+// The following exceptions don't need to appear in this file, because their specifications in the
+// annotated JDK already permit null.
+//
+// Here are all the null-friendly classes (every instance permits null elements).
+//
+// Lists:
+//   ArrayList
+//   LinkedList
+//   Vector
+//     Stack
+// Maps:
+//   HashMap
+//     LinkedHashMap
+//     PrinterStateReasons
+//   IdentityHashMap
+//   WeakHashMap
+// Sets:
+//   HashSet
+//     JobStateReasons
+//     LinkedHashSet
+//
+// Here are some methods whose specification is ambiguous: says "such that o.equals(e)" or "such
+// that Objects.equals(o, e)".  The annotated JDK interprets this to say that null is a permitted
+// value.
+//
+//   ArrayBlockingQueue
+//   ArrayDeque
+//   BlockingDeque
+//   BlockingQueue
+//   ConcurrentLinkedDeque
+//   ConcurrentLinkedQueue
+//   ConcurrentSkipListSet
+//   CopyOnWriteArrayList
+//   CopyOnWriteArraySet
+//   LinkedBlockingDeque
+//   LinkedBlockingQueue
+//   LinkedTransferQueue
+//   PriorityBlockingQueue
+//   PriorityQueue
+//     contains
+//     remove
+//
+//   ArrayDeque
+//   BlockingDeque
+//     removeFirstOccurrence
+//     removeLastOccurrence
+//
+//   Collections
+//     frequency
+//
+// Special cases:
+//   EnumMap:
+//     "Attempts to insert a null key will throw NullPointerException. Attempts to test for the
+//     presence of a null key or to remove one will, however, function properly. Null values are
+//     permitted."
+//   EnumSet:
+//     "Null elements are not permitted.  Attempts to insert a null element will throw
+//     NullPointerException.  Attempts to test for the presence of a null element or to remove one
+//     will, however, function properly."
+
+//
+// Many additional method implementations are null-safe, but the specification does not guarantee
+// that all overriding implementations in subclasses will be.
+
+
+package java.util;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+
+class AbstractCollection<E> {
+    @Pure public boolean contains(@Nullable Object o);
+    public boolean remove(@Nullable Object o);
+    @Pure public boolean containsAll(Collection<?> c);
+    public boolean removeAll(Collection<?> c);
+    public boolean retainAll(Collection<?> c);
+}
+
+public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
+    public int indexOf(@Nullable Object o);
+    public int lastIndexOf(@Nullable Object o);
+}
+
+class AbstractMap<K, V> {
+    public boolean containsValue(@Nullable Object value);
+    public boolean containsKey(@Nullable Object key);
+    public @Nullable V get(@Nullable Object key);
+    public @Nullable V remove(@Nullable Object key);
+}
+
+class AbstractSet<E> {
+  public boolean removeAll(Collection<?> c);
+}
+
+public interface Collection<E> extends Iterable<E> {
+    boolean contains(@Nullable Object o);
+    boolean remove(@Nullable Object o);
+    boolean containsAll(Collection<?> c);
+    boolean removeAll(Collection<?> c);
+    boolean retainAll(Collection<?> c);
+}
+
+public interface Deque<E> extends Queue<E> {
+    boolean removeFirstOccurrence(@Nullable Object o);
+    boolean removeLastOccurrence(@Nullable Object o);
+    boolean remove(@Nullable Object o);
+    boolean contains(@Nullable Object o);
+}
+
+public interface List<E> extends Collection<E> {
+    boolean contains(@Nullable Object o);
+    boolean remove(@Nullable Object o);
+    boolean containsAll(Collection<?> c);
+    boolean removeAll(Collection<?> c);
+    boolean retainAll(Collection<?> c);
+    int indexOf(@Nullable Object o);
+    int lastIndexOf(@Nullable Object o);
+}
+
+public interface Map<K, V> {
+    boolean containsKey(@Nullable Object key);
+    boolean containsValue(@Nullable Object value);
+    @Nullable V get(@Nullable Object key);
+    @Nullable V remove(@Nullable Object key);
+    default V getOrDefault(@Nullable Object key, V defaultValue);
+    default boolean remove(@Nullable Object key, @Nullable Object value);
+    }
+
+public interface Set<E> extends Collection<E> {
+    @Pure boolean contains(@Nullable Object o);
+    boolean remove(@Nullable Object o);
+    @Pure boolean containsAll(Collection<?> c);
+    boolean retainAll(Collection<?> c);
+    boolean removeAll(Collection<?> c);
+}
+
+public class TreeMap<K,V>
+    extends AbstractMap<K,V>
+    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
+{
+    public boolean containsKey(@Nullable Object key);
+    public boolean containsValue(@Nullable Object value);
+    public @Nullable V get(@Nullable Object key);
+    public @Nullable V remove(@Nullable Object key);
+}
+
+public class TreeSet<E> extends AbstractSet<E>
+    implements NavigableSet<E>, Cloneable, java.io.Serializable
+{
+    public boolean contains(@Nullable Object o);
+    public boolean remove(@Nullable Object o);
+}
+
+class Vector<E> {
+    public synchronized boolean containsAll(Collection<?> c);
+    public synchronized boolean removeAll(Collection<?> c);
+    public synchronized boolean retainAll(Collection<?> c);
+}
+
+package java.util.concurrent;
+
+public class ArrayBlockingQueue<E> {
+    public boolean removeAll(Collection<?> c);
+    public boolean retainAll(Collection<?> c);
+}
+
+public class ConcurrentLinkedDeque<E> {
+    public boolean removeAll(Collection<?> c);
+    public boolean retainAll(Collection<?> c);
+}
+
+public class ConcurrentLinkedQueue<E> {
+    public boolean removeAll(Collection<?> c);
+    public boolean retainAll(Collection<?> c);
+}
+
+public interface ConcurrentMap<K,V> extends Map<K,V> {
+    boolean remove(@Nullable Object key, @Nullable Object value);
+}
+
+public class ConcurrentSkipListSet<E>
+    extends AbstractSet<E>
+    implements NavigableSet<E>, Cloneable, java.io.Serializable {
+    public boolean contains(@Nullable Object o);
+    public boolean remove(@Nullable Object o);
+    public boolean removeAll(Collection<?> c);
+}
+
+public class CopyOnWriteArrayList<E>
+    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
+    public int indexOf(@Nullable Object o);
+    public int lastIndexOf(@Nullable Object o);
+    public boolean removeAll(Collection<?> c);
+    public boolean retainAll(Collection<?> c);
+}
+
+public class CopyOnWriteArraySet<E>
+    extends AbstractSet<E>
+    implements java.io.Serializable {
+    public boolean removeAll(Collection<?> c);
+    public boolean retainAll(Collection<?> c);
+}
+
+public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
+    implements BlockingQueue<E> {
+    public boolean remove(@Nullable Object o);
+}
+
+class LinkedBlockingDeque<E> {
+    public boolean removeAll(Collection<?> c);
+    public boolean retainAll(Collection<?> c);
+}
+
+class LinkedBlockingQueue<E> {
+    public boolean removeAll(Collection<?> c);
+    public boolean retainAll(Collection<?> c);
+}
+
+class LinkedTransferQueue<E> {
+    public boolean removeAll(Collection<?> c);
+    public boolean retainAll(Collection<?> c);
+}
+
+class PriorityBlockingQueue<E> {
+    public boolean removeAll(Collection<?> c);
+    public boolean retainAll(Collection<?> c);
+}
+
+public class SynchronousQueue<E> extends AbstractQueue<E>
+    implements BlockingQueue<E>, java.io.Serializable {
+    public boolean contains(@Nullable Object o);
+    public boolean remove(@Nullable Object o);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/collection-object-parameters-may-be-null.readme b/checker/src/main/java/org/checkerframework/checker/nullness/collection-object-parameters-may-be-null.readme
new file mode 100644
index 0000000..6a5413f
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/collection-object-parameters-may-be-null.readme
@@ -0,0 +1,36 @@
+# Run the following in both copies of the annotated JDK.  For example, in:
+cd $t/libraries/jdk-fork-mernst-branch-collection-object-parameters-may-be-null/src/java.base/share/classes
+cd $t/checker-framework-fork-mernst-branch-collection-object-parameters-may-be-null/checker/jdk/nullness/src
+
+
+rm -f *.txt
+rg -H --no-heading --line-buffered -n 'public.* (containsKey|contains|containsValue|get|getOrDefault|indexOf|lastIndexOf|removeFirstOccurrence|removeLastOccurrence|remove|remove)\(.*\bObject\b' \
+  > rg-output.txt
+
+grep -E '/(ArrayList|LinkedList|Vector|Stack|HashMap|LinkedHashMap|PrinterStateReasons|IdentityHashMap|WeakHashMap|HashSet|JobStateReasons|LinkedHashSet).java' rg-output.txt \
+  > null-friendly.txt
+
+cat null-friendly.txt \
+  | sed 's/@Nullable Object/Obj/g' \
+  | grep Object \
+  > null-friendly-errors.txt
+
+grep -E 'public.* (contains|containsKey|containsValue|remove|removeFirstOccurrence|removeLastOccurrence)\(' rg-output.txt \
+  | grep -E '/(ArrayBlockingQueue|ArrayDeque|ConcurrentLinkedQueue|CopyOnWriteArrayList|CopyOnWriteArraySet|LinkedBlockingDeque|LinkedBlockingQueue|LinkedTransferQueue|PriorityBlockingQueue|PriorityQueue|EnumMap).java' \
+  > contains-remove-friendly.txt
+
+cat contains-remove-friendly.txt \
+  | sed 's/@Nullable Object/Obj/g' \
+  | grep Object \
+  > contains-remove-errors.txt
+
+diff --new-line-format="" --unchanged-line-format="" <(diff --new-line-format="" --unchanged-line-format=""  rg-output.txt null-friendly.txt) contains-remove-friendly.txt > everything-else.txt
+
+grep -E '\(.*@Nullable' everything-else.txt \
+  > everything-else-errors.txt
+
+
+# Files *-errors.txt should be empty.
+# This line will appear because it's an exception:
+java/util/concurrent/ConcurrentLinkedDeque.java:1089:    public boolean contains(@Nullable Object o) {
+java/util/EnumMap.java:258:    public @Nullable V get(@Nullable Object key) {
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/map-assumeKeyFor.astub b/checker/src/main/java/org/checkerframework/checker/nullness/map-assumeKeyFor.astub
new file mode 100644
index 0000000..cbbcc07
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/map-assumeKeyFor.astub
@@ -0,0 +1,9 @@
+// This stub file is used by the Nullness Checker when
+// the user supplies the `-AassumeKeyFor` command-line option.
+
+package java.util;
+
+public interface Map<K, V> {
+    // No `@Nullable` on the return type.
+    V get(Map<K, V> this, Object key);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/messages.properties b/checker/src/main/java/org/checkerframework/checker/nullness/messages.properties
new file mode 100644
index 0000000..c48e167
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nullness/messages.properties
@@ -0,0 +1,32 @@
+### Error messages for the Nullness Checker
+
+# Dereferencing a possibly-null reference
+dereference.of.nullable=dereference of possibly-null reference %s
+iterating.over.nullable=iterating over possibly-null reference %s
+unboxing.of.nullable=unboxing a possibly-null reference %s
+throwing.nullable=throwing a possibly-null throwable %s
+locking.nullable=synchronizing over a possibly-null lock %s
+accessing.nullable=accessing a possibly-null array %s
+condition.nullable=condition on a possibly-null value %s
+switching.nullable=switching on a possibly-null value %s
+
+# Messages for special-cased methods
+toarray.nullable.elements.not.newarray=call of toArray on collection of non-null elements yields an array of possibly-null elements; omit the argument to toArray or make it an explicit array constructor
+toarray.nullable.elements.mismatched.size=call of toArray on collection of non-null elements yields an array of possibly-null elements; cannot determine that the argument array has the same size as the receiver collection
+clear.system.property=call might clear a predefined system property; pass -Alint=permitClearProperty to permit it
+
+# Unnecessary operations
+nulltest.redundant=redundant test against null; "%s" is non-null
+
+# Annotations that are invalid or unnecessary
+instanceof.nullable=instanceof is only true for a non-null expression
+instanceof.nonnull.redundant=redundant @NonNull annotation on instanceof
+new.array=annotations %s may not be applied as component type for array "%s"
+new.class=the annotations %s do not need be applied in object creations
+nullness.on.constructor=do not write nullness annotations on a constructor, whose result is always non-null
+nullness.on.enum=do not write nullness annotations on an enum constant, which is always non-null
+nullness.on.exception.parameter=do not write nullness annotations on an exception parameter, which is always non-null
+nullness.on.outer=nullness annotations are not applicable to outer types
+nullness.on.primitive=nullness annotations are not applicable to primitive types
+nullness.on.receiver=do not write nullness annotations on the receiver formal parameter `this`, which is always non-null
+nullness.on.supertype=do not write nullness annotations on supertypes in a class declaration
diff --git a/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java b/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java
new file mode 100644
index 0000000..da622fd
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java
@@ -0,0 +1,15 @@
+package org.checkerframework.checker.optional;
+
+import java.util.Optional;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+
+/**
+ * A type-checker that prevents misuse of the {@link java.util.Optional} class.
+ *
+ * @checker_framework.manual #optional-checker Optional Checker
+ */
+// TODO: For a call to ofNullable, if the argument has type @NonNull, make the return type have type
+// @Present.  Make Optional Checker a subchecker of the Nullness Checker.
+@RelevantJavaTypes(Optional.class)
+public class OptionalChecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java b/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java
new file mode 100644
index 0000000..65d66e3
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java
@@ -0,0 +1,414 @@
+package org.checkerframework.checker.optional;
+
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ExpressionStatementTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.VariableTree;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeValidator;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * The OptionalVisitor enforces the Optional Checker rules. These rules are described in the Checker
+ * Framework Manual.
+ *
+ * @checker_framework.manual #optional-checker Optional Checker
+ */
+public class OptionalVisitor
+    extends BaseTypeVisitor</* OptionalAnnotatedTypeFactory*/ BaseAnnotatedTypeFactory> {
+
+  private final TypeMirror collectionType;
+
+  /** The element for java.util.Optional.get(). */
+  private final ExecutableElement optionalGet;
+  /** The element for java.util.Optional.isPresent(). */
+  private final ExecutableElement optionalIsPresent;
+  /** The element for java.util.Optional.isEmpty(), or null if running under JDK 8. */
+  private final @Nullable ExecutableElement optionalIsEmpty;
+  /** The element for java.util.Optional.of(). */
+  private final ExecutableElement optionalOf;
+  /** The element for java.util.Optional.ofNullable(). */
+  private final ExecutableElement optionalOfNullable;
+  /** The element for java.util.Optional.orElse(). */
+  private final ExecutableElement optionalOrElse;
+  /** The element for java.util.Optional.orElseGet(). */
+  private final ExecutableElement optionalOrElseGet;
+  /** The element for java.util.Optional.orElseThrow(). */
+  private final @Nullable ExecutableElement optionalOrElseThrow;
+  /** The element for java.util.Optional.orElseThrow(Supplier), or null if running under JDK 8. */
+  private final ExecutableElement optionalOrElseThrowSupplier;
+
+  /** Create an OptionalVisitor. */
+  public OptionalVisitor(BaseTypeChecker checker) {
+    super(checker);
+    collectionType = types.erasure(TypesUtils.typeFromClass(Collection.class, types, elements));
+
+    ProcessingEnvironment env = checker.getProcessingEnvironment();
+    optionalGet = TreeUtils.getMethod("java.util.Optional", "get", 0, env);
+    optionalIsPresent = TreeUtils.getMethod("java.util.Optional", "isPresent", 0, env);
+    optionalIsEmpty = TreeUtils.getMethodOrNull("java.util.Optional", "isEmpty", 0, env);
+    optionalOf = TreeUtils.getMethod("java.util.Optional", "of", 1, env);
+    optionalOfNullable = TreeUtils.getMethod("java.util.Optional", "ofNullable", 1, env);
+    optionalOrElse = TreeUtils.getMethod("java.util.Optional", "orElse", 1, env);
+    optionalOrElseGet = TreeUtils.getMethod("java.util.Optional", "orElseGet", 1, env);
+    optionalOrElseThrow = TreeUtils.getMethodOrNull("java.util.Optional", "orElseThrow", 0, env);
+    optionalOrElseThrowSupplier = TreeUtils.getMethod("java.util.Optional", "orElseThrow", 1, env);
+  }
+
+  @Override
+  protected BaseTypeValidator createTypeValidator() {
+    return new OptionalTypeValidator(checker, this, atypeFactory);
+  }
+
+  /** @return true iff expression is a call to java.util.Optional.get */
+  private boolean isCallToGet(ExpressionTree expression) {
+    ProcessingEnvironment env = checker.getProcessingEnvironment();
+    return TreeUtils.isMethodInvocation(expression, optionalGet, env);
+  }
+
+  /**
+   * Is the expression a call to {@code isPresent} or {@code isEmpty}? If not, returns null. If so,
+   * returns a pair of (boolean, receiver expression). The boolean is true if the given expression
+   * is a call to {@code isPresent} and is false if the given expression is a call to {@code
+   * isEmpty}.
+   *
+   * @param expression an expression
+   * @return a pair of a boolean (indicating whether the expression is a call to {@code
+   *     Optional.isPresent} or to {@code Optional.isEmpty}) and its receiver; or null if not a call
+   *     to either of the methods
+   */
+  private @Nullable Pair<Boolean, ExpressionTree> isCallToIsPresent(ExpressionTree expression) {
+    ProcessingEnvironment env = checker.getProcessingEnvironment();
+    boolean negate = false;
+    while (true) {
+      switch (expression.getKind()) {
+        case PARENTHESIZED:
+          expression = ((ParenthesizedTree) expression).getExpression();
+          break;
+        case LOGICAL_COMPLEMENT:
+          expression = ((UnaryTree) expression).getExpression();
+          negate = !negate;
+          break;
+        case METHOD_INVOCATION:
+          if (TreeUtils.isMethodInvocation(expression, optionalIsPresent, env)) {
+            return Pair.of(!negate, TreeUtils.getReceiverTree(expression));
+          } else if (optionalIsEmpty != null
+              && TreeUtils.isMethodInvocation(expression, optionalIsEmpty, env)) {
+            return Pair.of(negate, TreeUtils.getReceiverTree(expression));
+          } else {
+            return null;
+          }
+        default:
+          return null;
+      }
+    }
+  }
+
+  /** @return true iff expression is a call to Optional creation: of, ofNullable. */
+  private boolean isOptionalCreation(MethodInvocationTree methInvok) {
+    ProcessingEnvironment env = checker.getProcessingEnvironment();
+    return TreeUtils.isMethodInvocation(methInvok, optionalOf, env)
+        || TreeUtils.isMethodInvocation(methInvok, optionalOfNullable, env);
+  }
+
+  /**
+   * @return true iff expression is a call to Optional elimination: get, orElse, orElseGet,
+   *     orElseThrow
+   */
+  private boolean isOptionalElimation(MethodInvocationTree methInvok) {
+    ProcessingEnvironment env = checker.getProcessingEnvironment();
+    return TreeUtils.isMethodInvocation(methInvok, optionalGet, env)
+        || TreeUtils.isMethodInvocation(methInvok, optionalOrElse, env)
+        || TreeUtils.isMethodInvocation(methInvok, optionalOrElseGet, env)
+        || (optionalIsEmpty != null
+            && TreeUtils.isMethodInvocation(methInvok, optionalOrElseThrow, env))
+        || TreeUtils.isMethodInvocation(methInvok, optionalOrElseThrowSupplier, env);
+  }
+
+  @Override
+  public Void visitConditionalExpression(ConditionalExpressionTree node, Void p) {
+    handleTernaryIsPresentGet(node);
+    return super.visitConditionalExpression(node, p);
+  }
+
+  /**
+   * Part of rule #3.
+   *
+   * <p>Pattern match for: {@code VAR.isPresent() ? VAR.get().METHOD() : VALUE}
+   *
+   * <p>Prefer: {@code VAR.map(METHOD).orElse(VALUE);}
+   */
+  // TODO: Should handle this via a transfer function, instead of pattern-matching.
+  public void handleTernaryIsPresentGet(ConditionalExpressionTree node) {
+
+    ExpressionTree condExpr = TreeUtils.withoutParens(node.getCondition());
+    Pair<Boolean, ExpressionTree> isPresentCall = isCallToIsPresent(condExpr);
+    if (isPresentCall == null) {
+      return;
+    }
+    ExpressionTree trueExpr = TreeUtils.withoutParens(node.getTrueExpression());
+    ExpressionTree falseExpr = TreeUtils.withoutParens(node.getFalseExpression());
+    if (!isPresentCall.first) {
+      ExpressionTree tmp = trueExpr;
+      trueExpr = falseExpr;
+      falseExpr = tmp;
+    }
+
+    if (trueExpr.getKind() != Kind.METHOD_INVOCATION) {
+      return;
+    }
+    ExpressionTree trueReceiver = TreeUtils.getReceiverTree(trueExpr);
+    if (!isCallToGet(trueReceiver)) {
+      return;
+    }
+    ExpressionTree getReceiver = TreeUtils.getReceiverTree(trueReceiver);
+
+    // What is a better way to do this than string comparison?
+    // Use transfer functions and Store entries.
+    ExpressionTree receiver = isPresentCall.second;
+    if (sameExpression(receiver, getReceiver)) {
+      ExecutableElement ele = TreeUtils.elementFromUse((MethodInvocationTree) trueExpr);
+
+      checker.reportWarning(
+          node,
+          "prefer.map.and.orelse",
+          receiver,
+          // The literal "CONTAININGCLASS::" is gross.
+          // TODO: add this to the error message.
+          // ElementUtils.getQualifiedClassName(ele);
+          ele.getSimpleName(),
+          falseExpr);
+    }
+  }
+
+  /**
+   * Returns true if the two trees represent the same expression.
+   *
+   * @param tree1 the first tree
+   * @param tree2 the second tree
+   * @return true if the two trees represent the same expression
+   */
+  private boolean sameExpression(ExpressionTree tree1, ExpressionTree tree2) {
+    JavaExpression r1 = JavaExpression.fromTree(tree1);
+    JavaExpression r2 = JavaExpression.fromTree(tree1);
+    if (r1 != null && !r1.containsUnknown() && r2 != null && !r2.containsUnknown()) {
+      return r1.equals(r2);
+    } else {
+      return tree1.toString().equals(tree2.toString());
+    }
+  }
+
+  @Override
+  public Void visitIf(IfTree node, Void p) {
+    handleConditionalStatementIsPresentGet(node);
+    return super.visitIf(node, p);
+  }
+
+  /**
+   * Part of rule #3.
+   *
+   * <p>Pattern match for: {@code if (VAR.isPresent()) { METHOD(VAR.get()); }}
+   *
+   * <p>Prefer: {@code VAR.ifPresent(METHOD);}
+   */
+  public void handleConditionalStatementIsPresentGet(IfTree node) {
+
+    ExpressionTree condExpr = TreeUtils.withoutParens(node.getCondition());
+    Pair<Boolean, ExpressionTree> isPresentCall = isCallToIsPresent(condExpr);
+    if (isPresentCall == null) {
+      return;
+    }
+
+    StatementTree thenStmt = skipBlocks(node.getThenStatement());
+    StatementTree elseStmt = skipBlocks(node.getElseStatement());
+    if (!isPresentCall.first) {
+      StatementTree tmp = thenStmt;
+      thenStmt = elseStmt;
+      elseStmt = tmp;
+    }
+
+    if (!(elseStmt == null
+        || (elseStmt.getKind() == Tree.Kind.BLOCK
+            && ((BlockTree) elseStmt).getStatements().isEmpty()))) {
+      // else block is missing or is an empty block: "{}"
+      return;
+    }
+
+    if (thenStmt.getKind() != Kind.EXPRESSION_STATEMENT) {
+      return;
+    }
+    ExpressionTree thenExpr = ((ExpressionStatementTree) thenStmt).getExpression();
+    if (thenExpr.getKind() != Kind.METHOD_INVOCATION) {
+      return;
+    }
+    MethodInvocationTree invok = (MethodInvocationTree) thenExpr;
+    List<? extends ExpressionTree> args = invok.getArguments();
+    if (args.size() != 1) {
+      return;
+    }
+    ExpressionTree arg = TreeUtils.withoutParens(args.get(0));
+    if (!isCallToGet(arg)) {
+      return;
+    }
+    ExpressionTree receiver = isPresentCall.second;
+    ExpressionTree getReceiver = TreeUtils.getReceiverTree(arg);
+    if (!receiver.toString().equals(getReceiver.toString())) {
+      return;
+    }
+    ExpressionTree method = invok.getMethodSelect();
+
+    String methodString = method.toString();
+    int dotPos = methodString.lastIndexOf(".");
+    if (dotPos != -1) {
+      methodString = methodString.substring(0, dotPos) + "::" + methodString.substring(dotPos + 1);
+    }
+
+    checker.reportWarning(node, "prefer.ifpresent", receiver, methodString);
+  }
+
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+    handleCreationElimination(node);
+    return super.visitMethodInvocation(node, p);
+  }
+
+  /**
+   * Rule #4.
+   *
+   * <p>Pattern match for: {@code CREATION().ELIMINATION()}
+   *
+   * <p>Prefer: {@code VAR.ifPresent(METHOD);}
+   */
+  public void handleCreationElimination(MethodInvocationTree node) {
+    if (!isOptionalElimation(node)) {
+      return;
+    }
+    ExpressionTree receiver = TreeUtils.getReceiverTree(node);
+    if (!(receiver.getKind() == Kind.METHOD_INVOCATION
+        && isOptionalCreation((MethodInvocationTree) receiver))) {
+      return;
+    }
+
+    checker.reportWarning(node, "introduce.eliminate");
+  }
+
+  /**
+   * Rule #6 (partial).
+   *
+   * <p>Don't use Optional in fields and method parameters.
+   */
+  @Override
+  public Void visitVariable(VariableTree node, Void p) {
+    VariableElement ve = TreeUtils.elementFromDeclaration(node);
+    TypeMirror tm = ve.asType();
+    if (isOptionalType(tm)) {
+      ElementKind ekind = TreeUtils.elementFromDeclaration(node).getKind();
+      if (ekind.isField()) {
+        checker.reportWarning(node, "optional.field");
+      } else if (ekind == ElementKind.PARAMETER) {
+        checker.reportWarning(node, "optional.parameter");
+      }
+    }
+    return super.visitVariable(node, p);
+  }
+
+  /**
+   * Handles part of Rule #6, and also Rule #7: Don't permit {@code Collection<Optional<...>>} or
+   * {@code Optional<Collection<...>>}.
+   */
+  private final class OptionalTypeValidator extends BaseTypeValidator {
+
+    public OptionalTypeValidator(
+        BaseTypeChecker checker, BaseTypeVisitor<?> visitor, AnnotatedTypeFactory atypeFactory) {
+      super(checker, visitor, atypeFactory);
+    }
+
+    /**
+     * Rules 6 (partial) and 7: Don't permit {@code Collection<Optional<...>>} or {@code
+     * Optional<Collection<...>>}.
+     */
+    @Override
+    public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) {
+      TypeMirror tm = type.getUnderlyingType();
+      if (isCollectionType(tm)) {
+        List<? extends TypeMirror> typeArgs = ((DeclaredType) tm).getTypeArguments();
+        if (typeArgs.size() == 1) {
+          // TODO: handle collections that have more than one type parameter
+          TypeMirror typeArg = typeArgs.get(0);
+          if (isOptionalType(typeArg)) {
+            checker.reportWarning(tree, "optional.as.element.type");
+          }
+        }
+      } else if (isOptionalType(tm)) {
+        List<? extends TypeMirror> typeArgs = ((DeclaredType) tm).getTypeArguments();
+        // If typeArgs.size()==0, then the user wrote a raw type `Optional`.
+        if (typeArgs.size() == 1) {
+          TypeMirror typeArg = typeArgs.get(0);
+          if (isCollectionType(typeArg)) {
+            checker.reportError(tree, "optional.collection");
+          }
+        }
+      }
+      return super.visitDeclared(type, tree);
+    }
+  }
+
+  /** Return true if tm represents a subtype of Collection (other than the Null type). */
+  private boolean isCollectionType(TypeMirror tm) {
+    return tm.getKind() == TypeKind.DECLARED && types.isSubtype(tm, collectionType);
+  }
+
+  /** Return true if tm represents java.util.Optional. */
+  private boolean isOptionalType(TypeMirror tm) {
+    return TypesUtils.isDeclaredOfName(tm, "java.util.Optional");
+  }
+
+  /**
+   * If the given tree is a block tree with a single element, return the enclosed non-block
+   * statement. Otherwise, return the same tree.
+   *
+   * @param tree a statement tree
+   * @return the single enclosed statement, if it exists; otherwise, the same tree
+   */
+  // TODO: The Optional Checker should work over the CFG, then it would not need this any longer.
+  public static StatementTree skipBlocks(final StatementTree tree) {
+    if (tree == null) {
+      return tree;
+    }
+    StatementTree s = tree;
+    while (s.getKind() == Tree.Kind.BLOCK) {
+      List<? extends StatementTree> stmts = ((BlockTree) s).getStatements();
+      if (stmts.size() == 1) {
+        s = stmts.get(0);
+      } else {
+        return s;
+      }
+    }
+    return s;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/optional/messages.properties b/checker/src/main/java/org/checkerframework/checker/optional/messages.properties
new file mode 100644
index 0000000..c9c1c51
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/optional/messages.properties
@@ -0,0 +1,8 @@
+### Error messages for the Optional Checker
+prefer.map.and.orelse=It is better style to use map and orElse.%nConsider changing to:%n%s.map(CONTAININGCLASS::%s).orElse(%s)
+prefer.ifpresent=It is better style to use ifPresent.%nConsider changing to:%n%s.ifPresent(%s)
+introduce.eliminate=It is bad style to create an Optional just to chain methods to get a value.
+optional.as.element.type=Don't use Optional as the element type in a collection.
+optional.collection=Don't use Optional to wrap a collection type.%nUse an empty collection to represent the absence of values.
+optional.field=Don't use Optional as the type of a field.
+optional.parameter=Don't use Optional as the type of a formal parameter.
diff --git a/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyAnnotatedTypeFactory.java
new file mode 100644
index 0000000..46d809e
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyAnnotatedTypeFactory.java
@@ -0,0 +1,227 @@
+package org.checkerframework.checker.propkey;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.tools.Diagnostic.Kind;
+import org.checkerframework.checker.propkey.qual.PropertyKey;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.plumelib.reflection.Signatures;
+
+/**
+ * This AnnotatedTypeFactory adds PropertyKey annotations to String literals that contain values
+ * from lookupKeys.
+ */
+public class PropertyKeyAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  private final Set<String> lookupKeys;
+
+  public PropertyKeyAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    this.lookupKeys = Collections.unmodifiableSet(buildLookupKeys());
+
+    this.postInit();
+  }
+
+  @Override
+  public TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(
+        super.createTreeAnnotator(), new KeyLookupTreeAnnotator(this, PropertyKey.class));
+  }
+
+  // To allow subclasses access to createTreeAnnotator from the BATF.
+  protected TreeAnnotator createBasicTreeAnnotator() {
+    return super.createTreeAnnotator();
+  }
+
+  /**
+   * This TreeAnnotator checks for every String literal whether it is included in the lookup keys.
+   * If it is, the given annotation is added to the literal; otherwise, nothing happens. Subclasses
+   * of this AnnotatedTypeFactory can directly reuse this class and use a different annotation as
+   * parameter.
+   */
+  protected class KeyLookupTreeAnnotator extends TreeAnnotator {
+    AnnotationMirror theAnnot;
+
+    public KeyLookupTreeAnnotator(BaseAnnotatedTypeFactory atf, Class<? extends Annotation> annot) {
+      super(atf);
+      theAnnot = AnnotationBuilder.fromClass(elements, annot);
+    }
+
+    @Override
+    public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
+      if (!type.isAnnotatedInHierarchy(theAnnot)
+          && tree.getKind() == Tree.Kind.STRING_LITERAL
+          && strContains(lookupKeys, tree.getValue().toString())) {
+        type.addAnnotation(theAnnot);
+      }
+      // A possible extension is to record all the keys that have been used and
+      // in the end output a list of keys that were not used in the program,
+      // possibly pointing to the opposite problem, keys that were supposed to
+      // be used somewhere, but have not been, maybe because of copy-and-paste errors.
+      return super.visitLiteral(tree, type);
+    }
+
+    // Result of binary op might not be a property key.
+    @Override
+    public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
+      type.removeAnnotation(theAnnot);
+      return null; // super.visitBinary(node, type);
+    }
+
+    // Result of unary op might not be a property key.
+    @Override
+    public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
+      type.removeAnnotation(theAnnot);
+      return null; // super.visitCompoundAssignment(node, type);
+    }
+  }
+
+  /**
+   * Instead of a precise comparison, we incrementally remove leading dot-separated strings until we
+   * find a match. For example if messages contains "y.z" and we look for "x.y.z" we find a match
+   * after removing the first "x.".
+   *
+   * <p>Compare to SourceChecker.fullMessageOf.
+   */
+  private static boolean strContains(Set<String> messages, String messageKey) {
+    String key = messageKey;
+
+    do {
+      if (messages.contains(key)) {
+        return true;
+      }
+
+      int dot = key.indexOf('.');
+      if (dot < 0) {
+        return false;
+      }
+      key = key.substring(dot + 1);
+    } while (true);
+  }
+
+  /**
+   * Returns a set of the valid keys that can be used.
+   *
+   * @return the valid keys that can be used
+   */
+  public Set<String> getLookupKeys() {
+    return this.lookupKeys;
+  }
+
+  private Set<String> buildLookupKeys() {
+    Set<String> result = new HashSet<>();
+
+    if (checker.hasOption("propfiles")) {
+      result.addAll(keysOfPropertyFiles(checker.getOption("propfiles")));
+    }
+    if (checker.hasOption("bundlenames")) {
+      result.addAll(keysOfResourceBundle(checker.getOption("bundlenames")));
+    }
+
+    return result;
+  }
+
+  private Set<String> keysOfPropertyFiles(String names) {
+    String[] namesArr = names.split(":");
+
+    if (namesArr == null) {
+      checker.message(Kind.WARNING, "Couldn't parse the properties files: <" + names + ">");
+      return Collections.emptySet();
+    }
+
+    Set<String> result = new HashSet<>(namesArr.length);
+
+    for (String name : namesArr) {
+      try {
+        Properties prop = new Properties();
+
+        ClassLoader cl = this.getClass().getClassLoader();
+        if (cl == null) {
+          // the class loader is null if the system class loader was
+          // used
+          cl = ClassLoader.getSystemClassLoader();
+        }
+        InputStream in = cl.getResourceAsStream(name);
+
+        if (in == null) {
+          // if the classloader didn't manage to load the file, try whether a FileInputStream
+          // works. For absolute paths this might help.
+          try {
+            in = new FileInputStream(name);
+          } catch (FileNotFoundException e) {
+            // ignore
+          }
+        }
+
+        if (in == null) {
+          checker.message(Kind.WARNING, "Couldn't find the properties file: " + name);
+          // report(null, "propertykeychecker.filenotfound", name);
+          // return Collections.emptySet();
+          continue;
+        }
+
+        prop.load(in);
+        result.addAll(prop.stringPropertyNames());
+      } catch (Exception e) {
+        // TODO: is there a nicer way to report messages, that are not
+        // connected to an AST node?
+        // One cannot use report, because it needs a node.
+        checker.message(Kind.WARNING, "Exception in PropertyKeyChecker.keysOfPropertyFile: " + e);
+        e.printStackTrace();
+      }
+    }
+
+    return result;
+  }
+
+  private Set<String> keysOfResourceBundle(String bundleNames) {
+    String[] namesArr = bundleNames.split(":");
+
+    if (namesArr == null) {
+      checker.message(Kind.WARNING, "Couldn't parse the resource bundles: <" + bundleNames + ">");
+      return Collections.emptySet();
+    }
+
+    Set<String> result = new HashSet<>(namesArr.length);
+
+    for (String bundleName : namesArr) {
+      if (!Signatures.isBinaryName(bundleName)) {
+        System.err.println(
+            "Malformed resource bundle: <" + bundleName + "> should be a binary name.");
+        continue;
+      }
+      ResourceBundle bundle = ResourceBundle.getBundle(bundleName);
+      if (bundle == null) {
+        checker.message(
+            Kind.WARNING,
+            "Couldn't find the resource bundle: <"
+                + bundleName
+                + "> for locale <"
+                + Locale.getDefault()
+                + ">");
+        continue;
+      }
+
+      result.addAll(bundle.keySet());
+    }
+    return result;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyChecker.java b/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyChecker.java
new file mode 100644
index 0000000..f0a893c
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyChecker.java
@@ -0,0 +1,31 @@
+package org.checkerframework.checker.propkey;
+
+import java.util.Locale;
+import java.util.ResourceBundle;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+import org.checkerframework.framework.source.SupportedOptions;
+
+/**
+ * A type-checker that checks that only valid keys are used to access property files and resource
+ * bundles. Subclasses can specialize this class for the different uses of property files, for
+ * examples see the I18n Checker and Compiler Messages Checker.
+ *
+ * <p>Currently, the checker supports two methods to check:
+ *
+ * <ol>
+ *   <li value="1">Property files: A common method for localization using a property file, mapping
+ *       the keys to values. Programmers pass the property file locations via {@code propfiles}
+ *       option (e.g. {@code -Apropfiles=/path/to/messages.properties}), separating multiple files
+ *       by a colon ":".
+ *   <li value="2">{@link ResourceBundle}: Programmers pass the {@code baseName} name of the bundle
+ *       via {@code bundlename} (e.g. {@code -Abundlename=MyResource}. The checker uses the resource
+ *       associated with the default {@link Locale} in the compilation system.
+ * </ol>
+ *
+ * @checker_framework.manual #propkey-checker Property File Checker
+ */
+// Subclasses need something similar to this:
+@SupportedOptions({"propfiles", "bundlenames"})
+@RelevantJavaTypes(CharSequence.class)
+public class PropertyKeyChecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexAnnotatedTypeFactory.java
new file mode 100644
index 0000000..c11ff8a
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexAnnotatedTypeFactory.java
@@ -0,0 +1,540 @@
+package org.checkerframework.checker.regex;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.regex.Pattern;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.regex.qual.PartialRegex;
+import org.checkerframework.checker.regex.qual.PolyRegex;
+import org.checkerframework.checker.regex.qual.Regex;
+import org.checkerframework.checker.regex.qual.RegexBottom;
+import org.checkerframework.checker.regex.qual.UnknownRegex;
+import org.checkerframework.checker.regex.util.RegexUtil;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Adds {@link Regex} to the type of tree, in the following cases:
+ *
+ * <ol>
+ *   <li value="1">a {@code String} or {@code char} literal that is a valid regular expression
+ *   <li value="2">concatenation of two valid regular expression values (either {@code String} or
+ *       {@code char}) or two partial regular expression values that make a valid regular expression
+ *       when concatenated.
+ *   <li value="3">for calls to Pattern.compile, change the group count value of the return type to
+ *       be the same as the parameter. For calls to the asRegex methods of the classes in
+ *       asRegexClasses, the returned {@code @Regex String} gets the same group count as the second
+ *       argument to the call to asRegex.
+ *       <!--<li value="4">initialization of a char array that when converted to a String
+ * is a valid regular expression.</li>-->
+ * </ol>
+ *
+ * Provides a basic analysis of concatenation of partial regular expressions to determine if a valid
+ * regular expression is produced by concatenating non-regular expression Strings. Do do this,
+ * {@link PartialRegex} is added to the type of tree in the following cases:
+ *
+ * <ol>
+ *   <li value="1">a String literal that is not a valid regular expression.
+ *   <li value="2">concatenation of two partial regex Strings that doesn't result in a regex String
+ *       or a partial regex and regex String.
+ * </ol>
+ *
+ * Also, adds {@link PolyRegex} to the type of String/char concatenation of a Regex and a PolyRegex
+ * or two PolyRegexs.
+ */
+public class RegexAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** The @{@link Regex} annotation. */
+  protected final AnnotationMirror REGEX = AnnotationBuilder.fromClass(elements, Regex.class);
+  /** The @{@link RegexBottom} annotation. */
+  protected final AnnotationMirror REGEXBOTTOM =
+      AnnotationBuilder.fromClass(elements, RegexBottom.class);
+  /** The @{@link PartialRegex} annotation. */
+  protected final AnnotationMirror PARTIALREGEX =
+      AnnotationBuilder.fromClass(elements, PartialRegex.class);
+  /** The @{@link PolyRegex} annotation. */
+  protected final AnnotationMirror POLYREGEX =
+      AnnotationBuilder.fromClass(elements, PolyRegex.class);
+  /** The @{@link UnknownRegex} annotation. */
+  protected final AnnotationMirror UNKNOWNREGEX =
+      AnnotationBuilder.fromClass(elements, UnknownRegex.class);
+
+  /** The method that returns the value element of a {@code @Regex} annotation. */
+  protected final ExecutableElement regexValueElement =
+      TreeUtils.getMethod(
+          "org.checkerframework.checker.regex.qual.Regex", "value", 0, processingEnv);
+
+  /**
+   * The value method of the PartialRegex qualifier.
+   *
+   * @see org.checkerframework.checker.regex.qual.PartialRegex
+   */
+  private final ExecutableElement partialRegexValueElement =
+      TreeUtils.getMethod(PartialRegex.class, "value", 0, processingEnv);
+
+  /**
+   * The Pattern.compile method.
+   *
+   * @see java.util.regex.Pattern#compile(String)
+   */
+  private final ExecutableElement patternCompile =
+      TreeUtils.getMethod("java.util.regex.Pattern", "compile", 1, processingEnv);
+
+  /**
+   * Create a new RegexAnnotatedTypeFactory.
+   *
+   * @param checker the checker
+   */
+  public RegexAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return getBundledTypeQualifiers(
+        Regex.class, PartialRegex.class,
+        RegexBottom.class, UnknownRegex.class);
+  }
+
+  @Override
+  public CFTransfer createFlowTransferFunction(
+      CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
+    return new RegexTransfer((CFAnalysis) analysis);
+  }
+
+  /** Returns a new Regex annotation with the given group count. */
+  /*package-scope*/ AnnotationMirror createRegexAnnotation(int groupCount) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, Regex.class);
+    if (groupCount > 0) {
+      builder.setValue("value", groupCount);
+    }
+    return builder.build();
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new RegexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
+  }
+
+  /**
+   * A custom qualifier hierarchy for the Regex Checker. This makes a regex annotation a subtype of
+   * all regex annotations with lower group count values. For example, {@code @Regex(3)} is a
+   * subtype of {@code @Regex(1)}. All regex annotations are subtypes of {@code @Regex}, which has a
+   * default value of 0.
+   */
+  private final class RegexQualifierHierarchy extends MostlyNoElementQualifierHierarchy {
+
+    /** Qualifier kind for the @{@link Regex} annotation. */
+    private final QualifierKind REGEX_KIND;
+    /** Qualifier kind for the @{@link PartialRegex} annotation. */
+    private final QualifierKind PARTIALREGEX_KIND;
+    /**
+     * Creates a RegexQualifierHierarchy from the given classes.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+     * @param elements element utils
+     */
+    private RegexQualifierHierarchy(
+        Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+      REGEX_KIND = getQualifierKind(REGEX);
+      PARTIALREGEX_KIND = getQualifierKind(PARTIALREGEX);
+    }
+
+    @Override
+    protected boolean isSubtypeWithElements(
+        AnnotationMirror subAnno,
+        QualifierKind subKind,
+        AnnotationMirror superAnno,
+        QualifierKind superKind) {
+      if (subKind == REGEX_KIND && superKind == REGEX_KIND) {
+        int rhsValue = getRegexValue(subAnno);
+        int lhsValue = getRegexValue(superAnno);
+        return lhsValue <= rhsValue;
+      } else if (subKind == PARTIALREGEX_KIND && superKind == PARTIALREGEX_KIND) {
+        return AnnotationUtils.areSame(subAnno, superAnno);
+      }
+      throw new BugInCF("Unexpected qualifiers: %s %s", subAnno, superAnno);
+    }
+
+    @Override
+    protected AnnotationMirror leastUpperBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind lubKind) {
+      if (qualifierKind1 == REGEX_KIND && qualifierKind2 == REGEX_KIND) {
+        int value1 = getRegexValue(a1);
+        int value2 = getRegexValue(a2);
+        if (value1 < value2) {
+          return a1;
+        } else {
+          return a2;
+        }
+      } else if (qualifierKind1 == PARTIALREGEX_KIND && qualifierKind2 == PARTIALREGEX_KIND) {
+        if (AnnotationUtils.areSame(a1, a2)) {
+          return a1;
+        } else {
+          return UNKNOWNREGEX;
+        }
+      } else if (qualifierKind1 == PARTIALREGEX_KIND || qualifierKind1 == REGEX_KIND) {
+        return a1;
+      } else if (qualifierKind2 == PARTIALREGEX_KIND || qualifierKind2 == REGEX_KIND) {
+        return a2;
+      }
+      throw new BugInCF("Unexpected qualifiers: %s %s", a1, a2);
+    }
+
+    @Override
+    protected AnnotationMirror greatestLowerBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind glbKind) {
+      if (qualifierKind1 == REGEX_KIND && qualifierKind2 == REGEX_KIND) {
+        int value1 = getRegexValue(a1);
+        int value2 = getRegexValue(a2);
+        if (value1 > value2) {
+          return a1;
+        } else {
+          return a2;
+        }
+      } else if (qualifierKind1 == PARTIALREGEX_KIND && qualifierKind2 == PARTIALREGEX_KIND) {
+        if (AnnotationUtils.areSame(a1, a2)) {
+          return a1;
+        } else {
+          return REGEXBOTTOM;
+        }
+      } else if (qualifierKind1 == PARTIALREGEX_KIND || qualifierKind1 == REGEX_KIND) {
+        return a1;
+      } else if (qualifierKind2 == PARTIALREGEX_KIND || qualifierKind2 == REGEX_KIND) {
+        return a2;
+      }
+      throw new BugInCF("Unexpected qualifiers: %s %s", a1, a2);
+    }
+
+    /**
+     * Gets the value out of a regex annotation.
+     *
+     * @param anno a @Regex annotation
+     * @return the {@code value} element of the annotation
+     */
+    private int getRegexValue(AnnotationMirror anno) {
+      return AnnotationUtils.getElementValue(anno, regexValueElement, Integer.class, 0);
+    }
+  }
+
+  /**
+   * Returns the group count value of the given annotation or 0 if there's a problem getting the
+   * group count value.
+   *
+   * @param anno a @Regex annotation
+   * @return the {@code value} element of the annotation
+   */
+  public int getGroupCount(AnnotationMirror anno) {
+    if (anno == null) {
+      return 0;
+    }
+    return AnnotationUtils.getElementValue(anno, regexValueElement, Integer.class, 0);
+  }
+
+  /** Returns the number of groups in the given regex String. */
+  public static int getGroupCount(@Regex String regexp) {
+    return Pattern.compile(regexp).matcher("").groupCount();
+  }
+
+  @Override
+  public Set<AnnotationMirror> getWidenedAnnotations(
+      Set<AnnotationMirror> annos, TypeKind typeKind, TypeKind widenedTypeKind) {
+    return Collections.singleton(UNKNOWNREGEX);
+  }
+
+  @Override
+  public TreeAnnotator createTreeAnnotator() {
+    // Don't call super.createTreeAnnotator because the PropagationTreeAnnotator types binary
+    // expressions as lub.
+    return new ListTreeAnnotator(
+        new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(),
+        new RegexTreeAnnotator(this),
+        new RegexPropagationTreeAnnotator(this));
+  }
+
+  private static class RegexPropagationTreeAnnotator extends PropagationTreeAnnotator {
+
+    public RegexPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
+      // Don't call super method which will try to create a LUB
+      // Even when it is not yet valid: i.e. between a @PolyRegex and a @Regex
+      return null;
+    }
+  }
+
+  private class RegexTreeAnnotator extends TreeAnnotator {
+
+    public RegexTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    /**
+     * Case 1: valid regular expression String or char literal. Adds PartialRegex annotation to
+     * String literals that are not valid regular expressions.
+     */
+    @Override
+    public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
+      if (!type.isAnnotatedInHierarchy(REGEX)) {
+        String regex = null;
+        if (tree.getKind() == Tree.Kind.STRING_LITERAL) {
+          regex = (String) tree.getValue();
+        } else if (tree.getKind() == Tree.Kind.CHAR_LITERAL) {
+          regex = Character.toString((Character) tree.getValue());
+        }
+        if (regex != null) {
+          if (RegexUtil.isRegex(regex)) {
+            int groupCount = getGroupCount(regex);
+            type.addAnnotation(createRegexAnnotation(groupCount));
+          } else {
+            type.addAnnotation(createPartialRegexAnnotation(regex));
+          }
+        }
+      }
+      return super.visitLiteral(tree, type);
+    }
+
+    /**
+     * Case 2: concatenation of Regex or PolyRegex String/char literals. Also handles concatenation
+     * of partial regular expressions.
+     */
+    @Override
+    public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) {
+      if (!type.isAnnotatedInHierarchy(REGEX) && TreeUtils.isStringConcatenation(tree)) {
+        AnnotatedTypeMirror lExpr = getAnnotatedType(tree.getLeftOperand());
+        AnnotatedTypeMirror rExpr = getAnnotatedType(tree.getRightOperand());
+
+        Integer lGroupCount = getMinimumRegexCount(lExpr);
+        Integer rGroupCount = getMinimumRegexCount(rExpr);
+        boolean lExprRE = lGroupCount != null;
+        boolean rExprRE = rGroupCount != null;
+        boolean lExprPart = lExpr.hasAnnotation(PartialRegex.class);
+        boolean rExprPart = rExpr.hasAnnotation(PartialRegex.class);
+        boolean lExprPoly = lExpr.hasAnnotation(PolyRegex.class);
+        boolean rExprPoly = rExpr.hasAnnotation(PolyRegex.class);
+
+        if (lExprRE && rExprRE) {
+          // Remove current @Regex annotation...
+          type.removeAnnotationInHierarchy(REGEX);
+          // ...and add a new one with the correct group count value.
+          type.addAnnotation(createRegexAnnotation(lGroupCount + rGroupCount));
+        } else if ((lExprPoly && rExprPoly) || (lExprPoly && rExprRE) || (lExprRE && rExprPoly)) {
+          type.addAnnotation(PolyRegex.class);
+        } else if (lExprPart && rExprPart) {
+          String lRegex = getPartialRegexValue(lExpr);
+          String rRegex = getPartialRegexValue(rExpr);
+          String concat = lRegex + rRegex;
+          if (RegexUtil.isRegex(concat)) {
+            int groupCount = getGroupCount(concat);
+            type.addAnnotation(createRegexAnnotation(groupCount));
+          } else {
+            type.addAnnotation(createPartialRegexAnnotation(concat));
+          }
+        } else if (lExprRE && rExprPart) {
+          String rRegex = getPartialRegexValue(rExpr);
+          String concat = "e" + rRegex;
+          type.addAnnotation(createPartialRegexAnnotation(concat));
+        } else if (lExprPart && rExprRE) {
+          String lRegex = getPartialRegexValue(lExpr);
+          String concat = lRegex + "e";
+          type.addAnnotation(createPartialRegexAnnotation(concat));
+        }
+      }
+      return null; // super.visitBinary(tree, type);
+    }
+
+    /** Case 2: Also handle compound String concatenation. */
+    @Override
+    public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
+      if (TreeUtils.isStringCompoundConcatenation(node)) {
+        AnnotatedTypeMirror rhs = getAnnotatedType(node.getExpression());
+        AnnotatedTypeMirror lhs = getAnnotatedType(node.getVariable());
+
+        final Integer lhsRegexCount = getMinimumRegexCount(lhs);
+        final Integer rhsRegexCount = getMinimumRegexCount(rhs);
+
+        if (lhsRegexCount != null && rhsRegexCount != null) {
+          int lCount = getGroupCount(lhs.getAnnotation(Regex.class));
+          int rCount = getGroupCount(rhs.getAnnotation(Regex.class));
+          type.removeAnnotationInHierarchy(REGEX);
+          type.addAnnotation(createRegexAnnotation(lCount + rCount));
+        }
+      }
+      return null; // super.visitCompoundAssignment(node, type);
+    }
+
+    /**
+     * Case 3: For a call to Pattern.compile, add an annotation to the return type that has the same
+     * group count value as the parameter. For calls to {@code asRegex(String, int)} change the
+     * return type to have the same group count as the value of the second argument.
+     */
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
+      // TODO: Also get this to work with 2 argument Pattern.compile.
+      if (TreeUtils.isMethodInvocation(tree, patternCompile, processingEnv)) {
+        ExpressionTree arg0 = tree.getArguments().get(0);
+
+        final AnnotatedTypeMirror argType = getAnnotatedType(arg0);
+        Integer regexCount = getMinimumRegexCount(argType);
+        AnnotationMirror bottomAnno = getAnnotatedType(arg0).getAnnotation(RegexBottom.class);
+
+        if (regexCount != null) {
+          // Remove current @Regex annotation...
+          // ...and add a new one with the correct group count value.
+          type.replaceAnnotation(createRegexAnnotation(regexCount));
+        } else if (bottomAnno != null) {
+          type.replaceAnnotation(AnnotationBuilder.fromClass(elements, RegexBottom.class));
+        }
+      }
+      return super.visitMethodInvocation(tree, type);
+    }
+
+    /** Returns a new PartialRegex annotation with the given partial regular expression. */
+    private AnnotationMirror createPartialRegexAnnotation(String partial) {
+      AnnotationBuilder builder = new AnnotationBuilder(processingEnv, PartialRegex.class);
+      builder.setValue("value", partial);
+      return builder.build();
+    }
+
+    /**
+     * Returns the {@code value} element of a {@code @PartialRegex} annotation, if there is one in
+     * {@code type}.
+     *
+     * @param type a type
+     * @return the {@code value} element of a {@code @PartialRegex} annotation, or "" if none
+     */
+    private String getPartialRegexValue(AnnotatedTypeMirror type) {
+      AnnotationMirror partialRegexAnno = type.getAnnotation(PartialRegex.class);
+      if (partialRegexAnno == null) {
+        return "";
+      }
+      return AnnotationUtils.getElementValue(
+          partialRegexAnno, partialRegexValueElement, String.class, "");
+    }
+
+    /**
+     * Returns the value of the Regex annotation on the given type or NULL if there is no Regex
+     * annotation. If type is a TYPEVAR, WILDCARD, or INTERSECTION type, visit first their primary
+     * annotation then visit their upper bounds to get the Regex annotation. The method gets
+     * "minimum" regex count because, depending on the bounds of a typevar or wildcard, the actual
+     * type may have more than the upper bound's count.
+     *
+     * @param type type that may carry a Regex annotation
+     * @return the Integer value of the Regex annotation (0 if no value exists)
+     */
+    private Integer getMinimumRegexCount(final AnnotatedTypeMirror type) {
+      final AnnotationMirror primaryRegexAnno = type.getAnnotation(Regex.class);
+      if (primaryRegexAnno == null) {
+        switch (type.getKind()) {
+          case TYPEVAR:
+            return getMinimumRegexCount(((AnnotatedTypeVariable) type).getUpperBound());
+
+          case WILDCARD:
+            return getMinimumRegexCount(((AnnotatedWildcardType) type).getExtendsBound());
+
+          case INTERSECTION:
+            Integer maxBound = null;
+            for (final AnnotatedTypeMirror bound : ((AnnotatedIntersectionType) type).getBounds()) {
+              Integer boundRegexNum = getMinimumRegexCount(bound);
+              if (boundRegexNum != null) {
+                if (maxBound == null || boundRegexNum > maxBound) {
+                  maxBound = boundRegexNum;
+                }
+              }
+            }
+            return maxBound;
+          default:
+            // Nothing to do for other cases.
+        }
+
+        return null;
+      }
+
+      return getGroupCount(primaryRegexAnno);
+    }
+
+    // This won't work correctly until flow sensitivity is supported by the
+    // the Regex Checker. For example:
+    //
+    //  char @Regex [] arr = {'r', 'e'};
+    //  arr[0] = '('; // type is still "char @Regex []", but this is no longer correct
+    //
+    // There are associated tests in tests/regex/Simple.java:testCharArrays
+    // that can be uncommented when this is uncommented.
+    // /**
+    //  * Case 4: a char array that as a String is a valid regular expression.
+    //  */
+    // @Override
+    // public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) {
+    //     boolean isCharArray = ((ArrayType) type.getUnderlyingType())
+    //             .getComponentType().getKind() == TypeKind.CHAR;
+    //     if (isCharArray && tree.getInitializers() != null) {
+    //         List<? extends ExpressionTree> initializers = tree.getInitializers();
+    //         StringBuilder charArray = new StringBuilder();
+    //         boolean allLiterals = true;
+    //         for (int i = 0; allLiterals && i < initializers.size(); i++) {
+    //             ExpressionTree e = initializers.get(i);
+    //             if (e.getKind() == Tree.Kind.CHAR_LITERAL) {
+    //                 charArray.append(((LiteralTree) e).getValue());
+    //             } else if (getAnnotatedType(e).hasAnnotation(Regex.class)) {
+    //                 // if there's an @Regex char in the array then substitute
+    //                 // it with a .
+    //                 charArray.append('.');
+    //             } else {
+    //                 allLiterals = false;
+    //             }
+    //         }
+    //         if (allLiterals && RegexUtil.isRegex(charArray.toString())) {
+    //             type.addAnnotation(Regex.class);
+    //         }
+    //     }
+    //     return super.visitNewArray(tree, type);
+    // }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexChecker.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexChecker.java
new file mode 100644
index 0000000..2b1f725
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexChecker.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.regex;
+
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.checkerframework.checker.regex.qual.Regex;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+import org.checkerframework.framework.qual.StubFiles;
+
+/**
+ * A type-checker plug-in for the {@link Regex} qualifier that finds syntactically invalid regular
+ * expressions.
+ *
+ * @checker_framework.manual #regex-checker Regex Checker
+ */
+@StubFiles("apache-xerces.astub")
+@RelevantJavaTypes({
+  CharSequence.class,
+  // javax.swing.text.Segment.class
+  char.class,
+  Character.class,
+  Pattern.class,
+  Matcher.class,
+  MatchResult.class
+})
+public class RegexChecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexTransfer.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexTransfer.java
new file mode 100644
index 0000000..8fe58b6
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexTransfer.java
@@ -0,0 +1,224 @@
+package org.checkerframework.checker.regex;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
+import org.checkerframework.dataflow.analysis.RegularTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.ClassNameNode;
+import org.checkerframework.dataflow.cfg.node.GreaterThanNode;
+import org.checkerframework.dataflow.cfg.node.GreaterThanOrEqualNode;
+import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode;
+import org.checkerframework.dataflow.cfg.node.LessThanNode;
+import org.checkerframework.dataflow.cfg.node.LessThanOrEqualNode;
+import org.checkerframework.dataflow.cfg.node.MethodAccessNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.util.NodeUtils;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/** The transfer function for the Regex Checker. */
+public class RegexTransfer extends CFTransfer {
+
+  // isRegex and asRegex are tested as signatures (string name plus formal parameters), not
+  // ExecutableElement, because they exist in two packages:
+  // org.checkerframework.checker.regex.util.RegexUtil.isRegex(String,int)
+  // org.plumelib.util.RegexUtil.isRegex(String,int)
+  // and org.plumelib.util might not be on the classpath.
+  private static final String IS_REGEX_METHOD_NAME = "isRegex";
+  private static final String AS_REGEX_METHOD_NAME = "asRegex";
+
+  /** The MatchResult.groupCount() method. */
+  private final ExecutableElement matchResultgroupCount;
+
+  /** Create the transfer function for the Regex Checker. */
+  public RegexTransfer(CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
+    super(analysis);
+    this.matchResultgroupCount =
+        TreeUtils.getMethod(
+            "java.util.regex.MatchResult",
+            "groupCount",
+            0,
+            analysis.getTypeFactory().getProcessingEnv());
+  }
+
+  // TODO: These are special cases for isRegex(String, int) and asRegex(String, int).  They should
+  // be replaced by adding an @EnsuresQualifierIf annotation that supports specifying attributes.
+  @Override
+  public TransferResult<CFValue, CFStore> visitMethodInvocation(
+      MethodInvocationNode n, TransferInput<CFValue, CFStore> in) {
+    TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(n, in);
+
+    // refine result for some helper methods
+    MethodAccessNode target = n.getTarget();
+    ExecutableElement method = target.getMethod();
+    Node receiver = target.getReceiver();
+    if (receiver instanceof ClassNameNode) {
+      ClassNameNode cnn = (ClassNameNode) receiver;
+      String receiverName = cnn.getElement().toString();
+      if (isRegexUtil(receiverName)) {
+        result = handleRegexUtil(n, method, result);
+      }
+    }
+    return result;
+  }
+
+  private TransferResult<CFValue, CFStore> handleRegexUtil(
+      MethodInvocationNode n, ExecutableElement method, TransferResult<CFValue, CFStore> result) {
+    RegexAnnotatedTypeFactory factory = (RegexAnnotatedTypeFactory) analysis.getTypeFactory();
+    if (ElementUtils.matchesElement(method, IS_REGEX_METHOD_NAME, String.class, int.class)) {
+      // RegexUtil.isRegex(s, groups) method
+      // (No special case is needed for isRegex(String) because of
+      // the annotation on that method's definition.)
+
+      CFStore thenStore = result.getRegularStore();
+      CFStore elseStore = thenStore.copy();
+      ConditionalTransferResult<CFValue, CFStore> newResult =
+          new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore);
+      JavaExpression firstParam = JavaExpression.fromNode(n.getArgument(0));
+
+      // add annotation with correct group count (if possible,
+      // regex annotation without count otherwise)
+      Node count = n.getArgument(1);
+      int groupCount;
+      if (count instanceof IntegerLiteralNode) {
+        IntegerLiteralNode iln = (IntegerLiteralNode) count;
+        groupCount = iln.getValue();
+      } else {
+        groupCount = 0;
+      }
+      AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount);
+      thenStore.insertValue(firstParam, regexAnnotation);
+      return newResult;
+    } else if (ElementUtils.matchesElement(method, AS_REGEX_METHOD_NAME, String.class, int.class)) {
+      // RegexUtil.asRegex(s, groups) method
+      // (No special case is needed for asRegex(String) because of
+      // the annotation on that method's definition.)
+
+      // add annotation with correct group count (if possible,
+      // regex annotation without count otherwise)
+      AnnotationMirror regexAnnotation;
+      Node count = n.getArgument(1);
+      int groupCount;
+      if (count instanceof IntegerLiteralNode) {
+        IntegerLiteralNode iln = (IntegerLiteralNode) count;
+        groupCount = iln.getValue();
+      } else {
+        groupCount = 0;
+      }
+      regexAnnotation = factory.createRegexAnnotation(groupCount);
+
+      CFValue newResultValue =
+          analysis.createSingleAnnotationValue(
+              regexAnnotation, result.getResultValue().getUnderlyingType());
+      return new RegularTransferResult<>(newResultValue, result.getRegularStore());
+    }
+    return result;
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitLessThan(
+      LessThanNode n, TransferInput<CFValue, CFStore> in) {
+    // Look for: constant < mat.groupCount()
+    // Make mat be @Regex(constant + 1)
+    TransferResult<CFValue, CFStore> res = super.visitLessThan(n, in);
+    return handleMatcherGroupCount(n.getRightOperand(), n.getLeftOperand(), false, res);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitLessThanOrEqual(
+      LessThanOrEqualNode n, TransferInput<CFValue, CFStore> in) {
+    // Look for: constant <= mat.groupCount()
+    // Make mat be @Regex(constant)
+    TransferResult<CFValue, CFStore> res = super.visitLessThanOrEqual(n, in);
+    return handleMatcherGroupCount(n.getRightOperand(), n.getLeftOperand(), true, res);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitGreaterThan(
+      GreaterThanNode n, TransferInput<CFValue, CFStore> in) {
+
+    TransferResult<CFValue, CFStore> res = super.visitGreaterThan(n, in);
+    return handleMatcherGroupCount(n.getLeftOperand(), n.getRightOperand(), false, res);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitGreaterThanOrEqual(
+      GreaterThanOrEqualNode n, TransferInput<CFValue, CFStore> in) {
+    // Look for: mat.groupCount() >= constant
+    // Make mat be @Regex(constant)
+    TransferResult<CFValue, CFStore> res = super.visitGreaterThanOrEqual(n, in);
+    return handleMatcherGroupCount(n.getLeftOperand(), n.getRightOperand(), true, res);
+  }
+
+  /**
+   * See whether possibleMatcher is a call of groupCount on a Matcher and possibleConstant is a
+   * constant. If so, annotate the matcher as constant + 1 if !isAlsoEqual constant if isAlsoEqual
+   *
+   * @param possibleMatcher the Node that might be a call of Matcher.groupCount()
+   * @param possibleConstant the Node that might be a constant
+   * @param isAlsoEqual whether the comparison operation is strict or reflexive
+   * @param resultIn TransferResult
+   * @return the possibly refined output TransferResult
+   */
+  private TransferResult<CFValue, CFStore> handleMatcherGroupCount(
+      Node possibleMatcher,
+      Node possibleConstant,
+      boolean isAlsoEqual,
+      TransferResult<CFValue, CFStore> resultIn) {
+    if (!(possibleMatcher instanceof MethodInvocationNode)) {
+      return resultIn;
+    }
+    if (!(possibleConstant instanceof IntegerLiteralNode)) {
+      return resultIn;
+    }
+
+    if (!NodeUtils.isMethodInvocation(
+        possibleMatcher, matchResultgroupCount, analysis.getTypeFactory().getProcessingEnv())) {
+      return resultIn;
+    }
+
+    MethodAccessNode methodAccessNode = ((MethodInvocationNode) possibleMatcher).getTarget();
+    Node receiver = methodAccessNode.getReceiver();
+
+    JavaExpression matcherReceiver = JavaExpression.fromNode(receiver);
+
+    IntegerLiteralNode iln = (IntegerLiteralNode) possibleConstant;
+    int groupCount;
+    if (isAlsoEqual) {
+      groupCount = iln.getValue();
+    } else {
+      groupCount = iln.getValue() + 1;
+    }
+
+    CFStore thenStore = resultIn.getRegularStore();
+    CFStore elseStore = thenStore.copy();
+    ConditionalTransferResult<CFValue, CFStore> newResult =
+        new ConditionalTransferResult<>(resultIn.getResultValue(), thenStore, elseStore);
+    RegexAnnotatedTypeFactory factory = (RegexAnnotatedTypeFactory) analysis.getTypeFactory();
+
+    AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount);
+    thenStore.insertValue(matcherReceiver, regexAnnotation);
+
+    return newResult;
+  }
+
+  /**
+   * Returns true if the given receiver is a class named "RegexUtil". Examples of such classes are
+   * org.checkerframework.checker.regex.util.RegexUtil and org.plumelib.util.RegexUtil, and the user
+   * might copy one into their own project.
+   *
+   * @param receiver some string
+   * @return true if the given receiver is a class named "RegexUtil"
+   */
+  private boolean isRegexUtil(String receiver) {
+    return receiver.equals("RegexUtil") || receiver.endsWith(".RegexUtil");
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexVisitor.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexVisitor.java
new file mode 100644
index 0000000..4533e54
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexVisitor.java
@@ -0,0 +1,124 @@
+package org.checkerframework.checker.regex;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree.Kind;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.regex.qual.Regex;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A type-checking visitor for the Regex type system.
+ *
+ * <p>This visitor does the following:
+ *
+ * <ol>
+ *   <li value="1">Allows any String to be passed to Pattern.compile if the Pattern.LITERAL flag is
+ *       passed.
+ *   <li value="2">Checks compound String concatenation to ensure correct usage of Regex Strings.
+ *   <li value="3">Checks calls to {@code MatchResult.start}, {@code MatchResult.end} and {@code
+ *       MatchResult.group} to ensure that a valid group number is passed.
+ * </ol>
+ *
+ * @see RegexChecker
+ */
+public class RegexVisitor extends BaseTypeVisitor<RegexAnnotatedTypeFactory> {
+
+  private final ExecutableElement matchResultEnd;
+  private final ExecutableElement matchResultGroup;
+  private final ExecutableElement matchResultStart;
+  private final ExecutableElement patternCompile;
+  private final VariableElement patternLiteral;
+
+  /** Reference types that may be annotated with @Regex. */
+  protected TypeMirror[] legalReferenceTypes;
+
+  /**
+   * Create a RegexVisitor.
+   *
+   * @param checker the associated RegexChecker
+   */
+  public RegexVisitor(BaseTypeChecker checker) {
+    super(checker);
+    ProcessingEnvironment env = checker.getProcessingEnvironment();
+    this.matchResultEnd = TreeUtils.getMethod("java.util.regex.MatchResult", "end", 1, env);
+    this.matchResultGroup = TreeUtils.getMethod("java.util.regex.MatchResult", "group", 1, env);
+    this.matchResultStart = TreeUtils.getMethod("java.util.regex.MatchResult", "start", 1, env);
+    this.patternCompile = TreeUtils.getMethod("java.util.regex.Pattern", "compile", 2, env);
+    this.patternLiteral = TreeUtils.getField("java.util.regex.Pattern", "LITERAL", env);
+  }
+
+  /**
+   * Case 1: Don't require a Regex annotation on the String argument to Pattern.compile if the
+   * Pattern.LITERAL flag is passed.
+   */
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+    ProcessingEnvironment env = checker.getProcessingEnvironment();
+    if (TreeUtils.isMethodInvocation(node, patternCompile, env)) {
+      ExpressionTree flagParam = node.getArguments().get(1);
+      if (flagParam.getKind() == Kind.MEMBER_SELECT) {
+        MemberSelectTree memSelect = (MemberSelectTree) flagParam;
+        if (TreeUtils.isSpecificFieldAccess(memSelect, patternLiteral)) {
+          // This is a call to Pattern.compile with the Pattern.LITERAL flag so the first parameter
+          // doesn't need to be a @Regex String. Don't call the super method to skip checking if the
+          // first parameter is a @Regex String, but make sure to still recurse on all of the
+          // different parts of the method call.
+          Void r = scan(node.getTypeArguments(), p);
+          r = reduce(scan(node.getMethodSelect(), p), r);
+          r = reduce(scan(node.getArguments(), p), r);
+          return r;
+        }
+      }
+    } else if (TreeUtils.isMethodInvocation(node, matchResultEnd, env)
+        || TreeUtils.isMethodInvocation(node, matchResultGroup, env)
+        || TreeUtils.isMethodInvocation(node, matchResultStart, env)) {
+      /**
+       * Case 3: Checks calls to {@code MatchResult.start}, {@code MatchResult.end} and {@code
+       * MatchResult.group} to ensure that a valid group number is passed.
+       */
+      ExpressionTree group = node.getArguments().get(0);
+      if (group.getKind() == Kind.INT_LITERAL) {
+        LiteralTree literal = (LiteralTree) group;
+        int paramGroups = (Integer) literal.getValue();
+        ExpressionTree receiver = TreeUtils.getReceiverTree(node);
+        if (receiver == null) {
+          // When checking implementations of java.util.regex.MatcherResult, calls to group (and
+          // other methods) don't have a receiver tree.  So, just do the regular checking. Verifying
+          // an implemenation of a subclass of MatcherResult is out of the scope of this checker.
+          return super.visitMethodInvocation(node, p);
+        }
+        int annoGroups = 0;
+        AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(receiver);
+
+        if (receiverType != null && receiverType.hasAnnotation(Regex.class)) {
+          annoGroups = atypeFactory.getGroupCount(receiverType.getAnnotation(Regex.class));
+        }
+        if (paramGroups > annoGroups) {
+          checker.reportError(group, "group.count", paramGroups, annoGroups, receiver);
+        }
+      } else {
+        checker.reportWarning(group, "group.count.unknown");
+      }
+    }
+    return super.visitMethodInvocation(node, p);
+  }
+
+  /** Case 2: Check String compound concatenation for valid Regex use. */
+  // TODO: Remove this. This should be handled by flow.
+  /*
+  @Override
+  public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
+      // Default behavior from superclass
+  }
+  */
+
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/regex/apache-xerces.astub b/checker/src/main/java/org/checkerframework/checker/regex/apache-xerces.astub
new file mode 100644
index 0000000..743231b
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/regex/apache-xerces.astub
@@ -0,0 +1,20 @@
+import org.checkerframework.checker.regex.qual.*;
+
+package com.sun.org.apache.xerces.internal.impl.xpath.regex;
+
+public class RegularExpression implements java.io.Serializable {
+    public RegularExpression(@Regex String regex) throws ParseException;
+    public RegularExpression(@Regex String regex, String options) throws ParseException;
+    RegularExpression(@Regex String regex, Token tok, int parens, boolean hasBackReferences, int options);
+}
+
+public final class REUtil {
+    static String stripExtendedComment(String regex);  // no @Regex annotation needed
+    public static boolean matches(@Regex String regex, String target) throws ParseException;
+    public static boolean matches(@Regex String regex, String options, String target) throws ParseException;
+}
+
+class RegexParser {
+    synchronized Token parse(@Regex String regex, int options) throws ParseException;
+    public RegularExpression createRegex(@Regex String regex, int options) throws ParseException;
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/regex/jdk8.astub b/checker/src/main/java/org/checkerframework/checker/regex/jdk8.astub
new file mode 100644
index 0000000..0570eba
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/regex/jdk8.astub
@@ -0,0 +1,17 @@
+// This file is for classes that appear in JDK 8 but not in JDK 11.
+
+import org.checkerframework.checker.regex.qual.*;
+
+package javax.swing.plaf.synth;
+
+class DefaultSynthStyleFactory extends SynthStyleFactory {
+    public synchronized void addStyle(DefaultSynthStyle style,
+                         @Regex String path, int type) throws PatternSyntaxException;
+}
+
+package javax.swing;
+
+public abstract class RowFilter<M,I> {
+    public static <M,I> RowFilter<M,I> regexFilter(@Regex String regex,
+                                                       int... indices);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/regex/messages.properties b/checker/src/main/java/org/checkerframework/checker/regex/messages.properties
new file mode 100644
index 0000000..238d68d
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/regex/messages.properties
@@ -0,0 +1,2 @@
+group.count=invalid groups parameter %s. Only %s groups are guaranteed to exist for %s.
+group.count.unknown=unable to verify non-literal groups parameter.
diff --git a/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java
new file mode 100644
index 0000000..3d81287
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java
@@ -0,0 +1,308 @@
+package org.checkerframework.checker.signature;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.PrimitiveTypeTree;
+import com.sun.source.tree.Tree;
+import java.lang.annotation.Annotation;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.signature.qual.ArrayWithoutPackage;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.checker.signature.qual.BinaryNameOrPrimitiveType;
+import org.checkerframework.checker.signature.qual.BinaryNameWithoutPackage;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.checker.signature.qual.CanonicalNameAndBinaryName;
+import org.checkerframework.checker.signature.qual.ClassGetName;
+import org.checkerframework.checker.signature.qual.ClassGetSimpleName;
+import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
+import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiersOrPrimitiveType;
+import org.checkerframework.checker.signature.qual.FieldDescriptor;
+import org.checkerframework.checker.signature.qual.FieldDescriptorForPrimitive;
+import org.checkerframework.checker.signature.qual.FieldDescriptorWithoutPackage;
+import org.checkerframework.checker.signature.qual.FqBinaryName;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.checker.signature.qual.Identifier;
+import org.checkerframework.checker.signature.qual.IdentifierOrPrimitiveType;
+import org.checkerframework.checker.signature.qual.InternalForm;
+import org.checkerframework.checker.signature.qual.PrimitiveType;
+import org.checkerframework.checker.signature.qual.SignatureBottom;
+import org.checkerframework.checker.signature.qual.SignatureUnknown;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.reflection.SignatureRegexes;
+
+// TODO: Does not yet handle method signature annotations, such as
+// @MethodDescriptor.
+
+/** Accounts for the effects of certain calls to String.replace. */
+public class SignatureAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** The {@literal @}{@link SignatureUnknown} annotation. */
+  protected final AnnotationMirror SIGNATURE_UNKNOWN =
+      AnnotationBuilder.fromClass(elements, SignatureUnknown.class);
+  /** The {@literal @}{@link BinaryName} annotation. */
+  protected final AnnotationMirror BINARY_NAME =
+      AnnotationBuilder.fromClass(elements, BinaryName.class);
+  /** The {@literal @}{@link InternalForm} annotation. */
+  protected final AnnotationMirror INTERNAL_FORM =
+      AnnotationBuilder.fromClass(elements, InternalForm.class);
+  /** The {@literal @}{@link DotSeparatedIdentifiers} annotation. */
+  protected final AnnotationMirror DOT_SEPARATED_IDENTIFIERS =
+      AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiers.class);
+  /** The {@literal @}{@link CanonicalName} annotation. */
+  protected final AnnotationMirror CANONICAL_NAME =
+      AnnotationBuilder.fromClass(elements, CanonicalName.class);
+  /** The {@literal @}{@link CanonicalNameAndBinaryName} annotation. */
+  protected final AnnotationMirror CANONICAL_NAME_AND_BINARY_NAME =
+      AnnotationBuilder.fromClass(elements, CanonicalNameAndBinaryName.class);
+  /** The {@literal @}{@link PrimitiveType} annotation. */
+  protected final AnnotationMirror PRIMITIVE_TYPE =
+      AnnotationBuilder.fromClass(elements, PrimitiveType.class);
+
+  /** The {@link String#replace(char, char)} method. */
+  private final ExecutableElement replaceCharChar =
+      TreeUtils.getMethod("java.lang.String", "replace", processingEnv, "char", "char");
+
+  /** The {@link String#replace(CharSequence, CharSequence)} method. */
+  private final ExecutableElement replaceCharSequenceCharSequence =
+      TreeUtils.getMethod(
+          "java.lang.String",
+          "replace",
+          processingEnv,
+          "java.lang.CharSequence",
+          "java.lang.CharSequence");
+
+  /** The {@link Class#getName()} method. */
+  private final ExecutableElement classGetName =
+      TreeUtils.getMethod("java.lang.Class", "getName", processingEnv);
+
+  /** The {@link Class#getCanonicalName()} method. */
+  private final ExecutableElement classGetCanonicalName =
+      TreeUtils.getMethod(java.lang.Class.class, "getCanonicalName", processingEnv);
+
+  /**
+   * Creates a SignatureAnnotatedTypeFactory.
+   *
+   * @param checker the type-checker assocated with this type factory
+   */
+  public SignatureAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return getBundledTypeQualifiers(SignatureUnknown.class, SignatureBottom.class);
+  }
+
+  @Override
+  public TreeAnnotator createTreeAnnotator() {
+    // It is slightly inefficient that super also adds a LiteralTreeAnnotator, but it seems
+    // better than hard-coding the behavior of super here.
+    return new ListTreeAnnotator(
+        signatureLiteralTreeAnnotator(this),
+        new SignatureTreeAnnotator(this),
+        super.createTreeAnnotator());
+  }
+
+  /**
+   * Create a LiteralTreeAnnotator for the Signature Checker.
+   *
+   * @param atypeFactory the type factory
+   * @return a LiteralTreeAnnotator for the Signature Checker
+   */
+  private LiteralTreeAnnotator signatureLiteralTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+    LiteralTreeAnnotator result = new LiteralTreeAnnotator(atypeFactory);
+    result.addStandardLiteralQualifiers();
+
+    // The below code achieves the same effect as writing a meta-annotation
+    //     @QualifierForLiterals(stringPatterns = "...")
+    // on each type qualifier definition.  Annotation elements cannot be computations (not even
+    // string concatenations of literal strings) and cannot be not references to compile-time
+    // constants such as effectively-final fields.  So every `stringPatterns = "..."` would have
+    // to be a literal string, which would be verbose ard hard to maintain.
+    result.addStringPattern(
+        SignatureRegexes.ArrayWithoutPackageRegex,
+        AnnotationBuilder.fromClass(elements, ArrayWithoutPackage.class));
+    result.addStringPattern(
+        SignatureRegexes.BinaryNameRegex, AnnotationBuilder.fromClass(elements, BinaryName.class));
+    result.addStringPattern(
+        SignatureRegexes.BinaryNameOrPrimitiveTypeRegex,
+        AnnotationBuilder.fromClass(elements, BinaryNameOrPrimitiveType.class));
+    result.addStringPattern(
+        SignatureRegexes.BinaryNameWithoutPackageRegex,
+        AnnotationBuilder.fromClass(elements, BinaryNameWithoutPackage.class));
+    result.addStringPattern(
+        SignatureRegexes.ClassGetNameRegex,
+        AnnotationBuilder.fromClass(elements, ClassGetName.class));
+    result.addStringPattern(
+        SignatureRegexes.ClassGetSimpleNameRegex,
+        AnnotationBuilder.fromClass(elements, ClassGetSimpleName.class));
+    result.addStringPattern(
+        SignatureRegexes.DotSeparatedIdentifiersRegex,
+        AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiers.class));
+    result.addStringPattern(
+        SignatureRegexes.DotSeparatedIdentifiersOrPrimitiveTypeRegex,
+        AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiersOrPrimitiveType.class));
+    result.addStringPattern(
+        SignatureRegexes.FieldDescriptorRegex,
+        AnnotationBuilder.fromClass(elements, FieldDescriptor.class));
+    result.addStringPattern(
+        SignatureRegexes.FieldDescriptorForPrimitiveRegex,
+        AnnotationBuilder.fromClass(elements, FieldDescriptorForPrimitive.class));
+    result.addStringPattern(
+        SignatureRegexes.FieldDescriptorWithoutPackageRegex,
+        AnnotationBuilder.fromClass(elements, FieldDescriptorWithoutPackage.class));
+    result.addStringPattern(
+        SignatureRegexes.FqBinaryNameRegex,
+        AnnotationBuilder.fromClass(elements, FqBinaryName.class));
+    result.addStringPattern(
+        SignatureRegexes.FullyQualifiedNameRegex,
+        AnnotationBuilder.fromClass(elements, FullyQualifiedName.class));
+    result.addStringPattern(
+        SignatureRegexes.IdentifierRegex, AnnotationBuilder.fromClass(elements, Identifier.class));
+    result.addStringPattern(
+        SignatureRegexes.IdentifierOrPrimitiveTypeRegex,
+        AnnotationBuilder.fromClass(elements, IdentifierOrPrimitiveType.class));
+    result.addStringPattern(
+        SignatureRegexes.InternalFormRegex,
+        AnnotationBuilder.fromClass(elements, InternalForm.class));
+    result.addStringPattern(
+        SignatureRegexes.PrimitiveTypeRegex,
+        AnnotationBuilder.fromClass(elements, PrimitiveType.class));
+    return result;
+  }
+
+  private class SignatureTreeAnnotator extends TreeAnnotator {
+
+    public SignatureTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) {
+      if (TreeUtils.isStringConcatenation(tree)) {
+        type.removeAnnotationInHierarchy(SIGNATURE_UNKNOWN);
+        // This could be made more precise.
+        type.addAnnotation(SignatureUnknown.class);
+      }
+      return null; // super.visitBinary(tree, type);
+    }
+
+    @Override
+    public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
+      if (TreeUtils.isStringCompoundConcatenation(node)) {
+        type.removeAnnotationInHierarchy(SIGNATURE_UNKNOWN);
+        // This could be made more precise.
+        type.addAnnotation(SignatureUnknown.class);
+      }
+      return null; // super.visitCompoundAssignment(node, type);
+    }
+
+    /**
+     * String.replace, when called with specific constant arguments, converts between internal form
+     * and binary name:
+     *
+     * <pre><code>
+     * {@literal @}InternalForm String internalForm = binaryName.replace('.', '/');
+     * {@literal @}BinaryName String binaryName = internalForm.replace('/', '.');
+     * </code></pre>
+     *
+     * Class.getName and Class.getCanonicalName(): Cwhen called on a primitive type ,the return a
+     * {@link PrimitiveType}. When called on a non-array, non-nested, non-primitive type, they
+     * return a {@link BinaryName}:
+     *
+     * <pre><code>
+     * {@literal @}BinaryName String binaryName = MyClass.class.getName();
+     * </code></pre>
+     */
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
+      if (TreeUtils.isMethodInvocation(tree, replaceCharChar, processingEnv)
+          || TreeUtils.isMethodInvocation(tree, replaceCharSequenceCharSequence, processingEnv)) {
+        char oldChar = ' '; // initial dummy value
+        char newChar = ' '; // initial dummy value
+        if (TreeUtils.isMethodInvocation(tree, replaceCharChar, processingEnv)) {
+          ExpressionTree arg0 = tree.getArguments().get(0);
+          ExpressionTree arg1 = tree.getArguments().get(1);
+          if (arg0.getKind() == Tree.Kind.CHAR_LITERAL
+              && arg1.getKind() == Tree.Kind.CHAR_LITERAL) {
+            oldChar = (char) ((LiteralTree) arg0).getValue();
+            newChar = (char) ((LiteralTree) arg1).getValue();
+          }
+        } else {
+          ExpressionTree arg0 = tree.getArguments().get(0);
+          ExpressionTree arg1 = tree.getArguments().get(1);
+          if (arg0.getKind() == Tree.Kind.STRING_LITERAL
+              && arg1.getKind() == Tree.Kind.STRING_LITERAL) {
+            String const0 = (String) ((LiteralTree) arg0).getValue();
+            String const1 = (String) ((LiteralTree) arg1).getValue();
+            if (const0.length() == 1 && const1.length() == 1) {
+              oldChar = const0.charAt(0);
+              newChar = const1.charAt(0);
+            }
+          }
+        }
+        ExpressionTree receiver = TreeUtils.getReceiverTree(tree);
+        final AnnotatedTypeMirror receiverType = getAnnotatedType(receiver);
+        if ((oldChar == '.' && newChar == '/')
+            && receiverType.getAnnotation(BinaryName.class) != null) {
+          type.replaceAnnotation(INTERNAL_FORM);
+        } else if ((oldChar == '/' && newChar == '.')
+            && receiverType.getAnnotation(InternalForm.class) != null) {
+          type.replaceAnnotation(BINARY_NAME);
+        }
+      } else {
+        boolean isClassGetName = TreeUtils.isMethodInvocation(tree, classGetName, processingEnv);
+        boolean isClassGetCanonicalName =
+            TreeUtils.isMethodInvocation(tree, classGetCanonicalName, processingEnv);
+        if (isClassGetName || isClassGetCanonicalName) {
+          ExpressionTree receiver = TreeUtils.getReceiverTree(tree);
+          if (TreeUtils.isClassLiteral(receiver)) {
+            ExpressionTree classExpr = ((MemberSelectTree) receiver).getExpression();
+            if (classExpr.getKind() == Tree.Kind.PRIMITIVE_TYPE) {
+              if (((PrimitiveTypeTree) classExpr).getPrimitiveTypeKind() == TypeKind.VOID) {
+                // do nothing
+              } else {
+                type.replaceAnnotation(PRIMITIVE_TYPE);
+              }
+            } else {
+              // Binary name if non-array, non-primitive, non-nested.
+              TypeMirror literalType = TreeUtils.typeOf(classExpr);
+              if (literalType.getKind() == TypeKind.DECLARED) {
+                TypeElement typeElt = TypesUtils.getTypeElement(literalType);
+                Element enclosing = typeElt.getEnclosingElement();
+                if (enclosing == null || enclosing.getKind() == ElementKind.PACKAGE) {
+                  type.replaceAnnotation(
+                      isClassGetName ? DOT_SEPARATED_IDENTIFIERS : CANONICAL_NAME_AND_BINARY_NAME);
+                }
+              }
+            }
+          }
+        }
+      }
+
+      return super.visitMethodInvocation(tree, type);
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/signature/SignatureChecker.java b/checker/src/main/java/org/checkerframework/checker/signature/SignatureChecker.java
new file mode 100644
index 0000000..770db52
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/signature/SignatureChecker.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.signature;
+
+import com.sun.source.tree.CompilationUnitTree;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.StubFiles;
+
+/**
+ * The Signature Checker.
+ *
+ * @checker_framework.manual #signature-checker Signature Checker
+ */
+// Don't use @RelevantJavaTypes.  Any object can be annotated, which should propagate through its
+// toString().
+// @RelevantJavaTypes(CharSequence.class)
+@StubFiles({"javac.astub", "javaparser.astub"})
+public final class SignatureChecker extends BaseTypeChecker {
+
+  // This method is needed only under MacOS, perhaps as a result of the
+  // broken Apple Java distribution.
+  public SignatureAnnotatedTypeFactory createFactory(CompilationUnitTree root) {
+    return new SignatureAnnotatedTypeFactory(this);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/signature/SignatureTransfer.java b/checker/src/main/java/org/checkerframework/checker/signature/SignatureTransfer.java
new file mode 100644
index 0000000..3b9e8e5
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/signature/SignatureTransfer.java
@@ -0,0 +1,62 @@
+package org.checkerframework.checker.signature;
+
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty;
+import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.MethodAccessNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** The transfer function for the Signature Checker. */
+public class SignatureTransfer extends CFTransfer {
+
+  /** The annotated type factory for this transfer function. */
+  private SignatureAnnotatedTypeFactory aTypeFactory;
+
+  /**
+   * Create a new SignatureTransfer.
+   *
+   * @param analysis the analysis
+   */
+  public SignatureTransfer(CFAnalysis analysis) {
+    super(analysis);
+    aTypeFactory = (SignatureAnnotatedTypeFactory) analysis.getTypeFactory();
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitMethodInvocation(
+      MethodInvocationNode n, TransferInput<CFValue, CFStore> in) {
+    TransferResult<CFValue, CFStore> superResult = super.visitMethodInvocation(n, in);
+
+    MethodAccessNode target = n.getTarget();
+    ExecutableElement method = target.getMethod();
+    Node receiver = target.getReceiver();
+    if (TypesUtils.isString(receiver.getType()) && ElementUtils.matchesElement(method, "isEmpty")) {
+
+      AnnotatedTypeMirror receiverAtm = aTypeFactory.getAnnotatedType(receiver.getTree());
+      if (receiverAtm.hasAnnotation(CanonicalNameOrEmpty.class)) {
+
+        CFStore thenStore = superResult.getRegularStore();
+        CFStore elseStore = thenStore.copy();
+        ConditionalTransferResult<CFValue, CFStore> result =
+            new ConditionalTransferResult<>(superResult.getResultValue(), thenStore, elseStore);
+        // The refined expression is the receiver of the method call.
+        JavaExpression refinedExpr = JavaExpression.fromNode(receiver);
+
+        elseStore.insertValue(refinedExpr, aTypeFactory.CANONICAL_NAME);
+        return result;
+      }
+    }
+    return superResult;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/signature/javac.astub b/checker/src/main/java/org/checkerframework/checker/signature/javac.astub
new file mode 100644
index 0000000..07b263a
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/signature/javac.astub
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.signature.qual.*;
+
+package javax.lang.model.element;
+
+// This fake override does not work yet because it depends on an annotation on a formal parameter,
+// and currently fake overrides affect only return types.
+// interface Name {
+//     @PolySignature String toString(@PolySignature Name this);
+// }
diff --git a/checker/src/main/java/org/checkerframework/checker/signature/javaparser.astub b/checker/src/main/java/org/checkerframework/checker/signature/javaparser.astub
new file mode 100644
index 0000000..85c3aa1
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/signature/javaparser.astub
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.signature.qual.*;
+
+package com.github.javaparser.ast;
+
+class PackageDeclaration {
+  @DotSeparatedIdentifiers Name getName();
+}
+
+package com.github.javaparser.ast;
+
+class ImportDeclaration {
+  @DotSeparatedIdentifiers Name getName();
+}
+
+// Class is documented as "A node with a (qualified) name."
+interface NodeWithName {
+  @FullyQualifiedName Name getName();
+}
+
+package com.github.javaparser.ast.expr;
+
+class AnnotationExpr {
+  @FullyQualifiedName Name getName();
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/signature/jdk8.astub b/checker/src/main/java/org/checkerframework/checker/signature/jdk8.astub
new file mode 100644
index 0000000..c903cf4
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/signature/jdk8.astub
@@ -0,0 +1,159 @@
+// This file is for classes that appear in JDK 8 but not in JDK 11.
+
+import org.checkerframework.checker.signature.qual.*;
+
+package java.lang.management;
+
+enum PlatformComponent {
+    @SuppressWarnings("signature")
+    CLASS_LOADING(
+        "java.lang.management.ClassLoadingMXBean",
+        "java.lang", "ClassLoading", defaultKeyProperties(),
+        new MXBeanFetcher<ClassLoadingMXBean>() {
+            public List<ClassLoadingMXBean> getMXBeans() {
+                return Collections.singletonList(ManagementFactoryHelper.getClassLoadingMXBean());
+            }
+        }),
+    @SuppressWarnings("signature")
+    COMPILATION(
+        "java.lang.management.CompilationMXBean",
+        "java.lang", "Compilation", defaultKeyProperties(),
+        new MXBeanFetcher<CompilationMXBean>() {
+            public List<CompilationMXBean> getMXBeans() {
+                CompilationMXBean m = ManagementFactoryHelper.getCompilationMXBean();
+                if (m == null) {
+                   return Collections.emptyList();
+                } else {
+                   return Collections.singletonList(m);
+                }
+            }
+        }),
+    @SuppressWarnings("signature")
+    MEMORY(
+        "java.lang.management.MemoryMXBean",
+        "java.lang", "Memory", defaultKeyProperties(),
+        new MXBeanFetcher<MemoryMXBean>() {
+            public List<MemoryMXBean> getMXBeans() {
+                return Collections.singletonList(ManagementFactoryHelper.getMemoryMXBean());
+            }
+        }),
+    @SuppressWarnings("signature")
+    GARBAGE_COLLECTOR(
+        "java.lang.management.GarbageCollectorMXBean",
+        "java.lang", "GarbageCollector", keyProperties("name"),
+        new MXBeanFetcher<GarbageCollectorMXBean>() {
+            public List<GarbageCollectorMXBean> getMXBeans() {
+                return ManagementFactoryHelper.
+                           getGarbageCollectorMXBeans();
+            }
+        }),
+    @SuppressWarnings("signature")
+    MEMORY_MANAGER(
+        "java.lang.management.MemoryManagerMXBean",
+        "java.lang", "MemoryManager", keyProperties("name"),
+        new MXBeanFetcher<MemoryManagerMXBean>() {
+            public List<MemoryManagerMXBean> getMXBeans() {
+                return ManagementFactoryHelper.getMemoryManagerMXBeans();
+            }
+        },
+        GARBAGE_COLLECTOR),
+    @SuppressWarnings("signature")
+    MEMORY_POOL(
+        "java.lang.management.MemoryPoolMXBean",
+        "java.lang", "MemoryPool", keyProperties("name"),
+        new MXBeanFetcher<MemoryPoolMXBean>() {
+            public List<MemoryPoolMXBean> getMXBeans() {
+                return ManagementFactoryHelper.getMemoryPoolMXBeans();
+            }
+        }),
+    @SuppressWarnings("signature")
+    OPERATING_SYSTEM(
+        "java.lang.management.OperatingSystemMXBean",
+        "java.lang", "OperatingSystem", defaultKeyProperties(),
+        new MXBeanFetcher<OperatingSystemMXBean>() {
+            public List<OperatingSystemMXBean> getMXBeans() {
+                return Collections.singletonList(ManagementFactoryHelper.getOperatingSystemMXBean());
+            }
+        }),
+    @SuppressWarnings("signature")
+    RUNTIME(
+        "java.lang.management.RuntimeMXBean",
+        "java.lang", "Runtime", defaultKeyProperties(),
+        new MXBeanFetcher<RuntimeMXBean>() {
+            public List<RuntimeMXBean> getMXBeans() {
+                return Collections.singletonList(ManagementFactoryHelper.getRuntimeMXBean());
+            }
+        }),
+    @SuppressWarnings("signature")
+    THREADING(
+        "java.lang.management.ThreadMXBean",
+        "java.lang", "Threading", defaultKeyProperties(),
+        new MXBeanFetcher<ThreadMXBean>() {
+            public List<ThreadMXBean> getMXBeans() {
+                return Collections.singletonList(ManagementFactoryHelper.getThreadMXBean());
+            }
+        }),
+    @SuppressWarnings("signature")
+    LOGGING(
+        "java.util.logging.PlatformLoggingMXBean",
+        "java.util.logging", "Logging", defaultKeyProperties(),
+        new MXBeanFetcher<PlatformLoggingMXBean>() {
+            public List<PlatformLoggingMXBean> getMXBeans() {
+                return ManagementFactoryHelper.getLoggingMXBean();
+            }
+        }),
+    @SuppressWarnings("signature")
+    BUFFER_POOL(
+        "java.nio.BufferPoolMXBean",
+        "java.nio", "BufferPool", keyProperties("name"),
+        new MXBeanFetcher<BufferPoolMXBean>() {
+            public List<BufferPoolMXBean> getMXBeans() {
+                return ManagementFactoryHelper.getBufferPoolMXBeans();
+            }
+        }),
+    @SuppressWarnings("signature")
+    SUN_GARBAGE_COLLECTOR(
+        "com.sun.management.GarbageCollectorMXBean",
+        "java.lang", "GarbageCollector", keyProperties("name"),
+        new MXBeanFetcher<com.sun.management.GarbageCollectorMXBean>() {
+            public List<com.sun.management.GarbageCollectorMXBean> getMXBeans() {
+                return getGcMXBeanList(com.sun.management.GarbageCollectorMXBean.class);
+            }
+        }),
+    @SuppressWarnings("signature")
+    SUN_OPERATING_SYSTEM(
+        "com.sun.management.OperatingSystemMXBean",
+        "java.lang", "OperatingSystem", defaultKeyProperties(),
+        new MXBeanFetcher<com.sun.management.OperatingSystemMXBean>() {
+            public List<com.sun.management.OperatingSystemMXBean> getMXBeans() {
+                return getOSMXBeanList(com.sun.management.OperatingSystemMXBean.class);
+            }
+        }),
+    @SuppressWarnings("signature")
+    SUN_UNIX_OPERATING_SYSTEM(
+        "com.sun.management.UnixOperatingSystemMXBean",
+        "java.lang", "OperatingSystem", defaultKeyProperties(),
+        new MXBeanFetcher<UnixOperatingSystemMXBean>() {
+            public List<UnixOperatingSystemMXBean> getMXBeans() {
+                return getOSMXBeanList(com.sun.management.UnixOperatingSystemMXBean.class);
+            }
+        }),
+    @SuppressWarnings("signature")
+    HOTSPOT_DIAGNOSTIC(
+        "com.sun.management.HotSpotDiagnosticMXBean",
+        "com.sun.management", "HotSpotDiagnostic", defaultKeyProperties(),
+        new MXBeanFetcher<HotSpotDiagnosticMXBean>() {
+            public List<HotSpotDiagnosticMXBean> getMXBeans() {
+                return Collections.singletonList(ManagementFactoryHelper.getDiagnosticMXBean());
+            }
+        });
+
+    @BinaryName String getMXBeanInterfaceName();
+}
+
+package java.util;
+
+class SecurityManager {
+    protected int classDepth(@FullyQualifiedName String name);
+    protected boolean inClass(@FullyQualifiedName String name);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java
new file mode 100644
index 0000000..5467eca
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java
@@ -0,0 +1,585 @@
+package org.checkerframework.checker.signedness;
+
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.PrimitiveTypeTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.util.TreePath;
+import java.lang.annotation.Annotation;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signedness.qual.PolySigned;
+import org.checkerframework.checker.signedness.qual.Signed;
+import org.checkerframework.checker.signedness.qual.SignedPositive;
+import org.checkerframework.checker.signedness.qual.SignedPositiveFromUnsigned;
+import org.checkerframework.checker.signedness.qual.SignednessGlb;
+import org.checkerframework.checker.signedness.qual.UnknownSignedness;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.common.value.ValueCheckerUtils;
+import org.checkerframework.common.value.qual.IntRangeFromNonNegative;
+import org.checkerframework.common.value.qual.IntRangeFromPositive;
+import org.checkerframework.common.value.util.Range;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypeKindUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * The type factory for the Signedness Checker.
+ *
+ * @checker_framework.manual #signedness-checker Signedness Checker
+ */
+public class SignednessAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** The @UnknownSignedness annotation. */
+  private final AnnotationMirror UNKNOWN_SIGNEDNESS =
+      AnnotationBuilder.fromClass(elements, UnknownSignedness.class);
+  /** The @Signed annotation. */
+  private final AnnotationMirror SIGNED = AnnotationBuilder.fromClass(elements, Signed.class);
+  /** The @Unsigned annotation. */
+  private final AnnotationMirror UNSIGNED = AnnotationBuilder.fromClass(elements, Unsigned.class);
+  /** The @SignednessGlb annotation. Do not use @SignedPositive; use this instead. */
+  private final AnnotationMirror SIGNEDNESS_GLB =
+      AnnotationBuilder.fromClass(elements, SignednessGlb.class);
+  /** The @SignedPositiveFromUnsigned annotation. */
+  protected final AnnotationMirror SIGNED_POSITIVE_FROM_UNSIGNED =
+      AnnotationBuilder.fromClass(elements, SignedPositiveFromUnsigned.class);
+  /** The @PolySigned annotation. */
+  protected final AnnotationMirror POLY_SIGNED =
+      AnnotationBuilder.fromClass(elements, PolySigned.class);
+
+  /** The @NonNegative annotation of the Index Checker, as represented by the Value Checker. */
+  private final AnnotationMirror INT_RANGE_FROM_NON_NEGATIVE =
+      AnnotationBuilder.fromClass(elements, IntRangeFromNonNegative.class);
+  /** The @Positive annotation of the Index Checker, as represented by the Value Checker. */
+  private final AnnotationMirror INT_RANGE_FROM_POSITIVE =
+      AnnotationBuilder.fromClass(elements, IntRangeFromPositive.class);
+
+  /**
+   * Create a SignednessAnnotatedTypeFactory.
+   *
+   * @param checker the type-checker associated with this type factory
+   */
+  public SignednessAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    addAliasedTypeAnnotation(SignedPositive.class, SIGNEDNESS_GLB);
+
+    addAliasedTypeAnnotation("jdk.jfr.Unsigned", UNSIGNED);
+
+    postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    Set<Class<? extends Annotation>> result = getBundledTypeQualifiers();
+    result.remove(SignedPositive.class); // this method should not return aliases
+    return result;
+  }
+
+  @Override
+  protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) {
+    if (!computingAnnotatedTypeMirrorOfLHS) {
+      addSignednessGlbAnnotation(tree, type);
+    }
+
+    super.addComputedTypeAnnotations(tree, type, iUseFlow);
+  }
+
+  /**
+   * True when the AnnotatedTypeMirror currently being computed is the left hand side of an
+   * assignment or pseudo-assignment.
+   *
+   * @see #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror, boolean)
+   * @see #getAnnotatedTypeLhs(Tree)
+   */
+  private boolean computingAnnotatedTypeMirrorOfLHS = false;
+
+  @Override
+  public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) {
+    boolean oldComputingAnnotatedTypeMirrorOfLHS = computingAnnotatedTypeMirrorOfLHS;
+    computingAnnotatedTypeMirrorOfLHS = true;
+    AnnotatedTypeMirror result = super.getAnnotatedTypeLhs(lhsTree);
+    computingAnnotatedTypeMirrorOfLHS = oldComputingAnnotatedTypeMirrorOfLHS;
+    return result;
+  }
+
+  /**
+   * Refines an integer expression to @SignednessGlb if its value is within the signed positive
+   * range (i.e. its MSB is zero).
+   *
+   * @param tree an AST node, whose type may be refined
+   * @param type the type of the tree
+   */
+  private void addSignednessGlbAnnotation(Tree tree, AnnotatedTypeMirror type) {
+    TypeMirror javaType = type.getUnderlyingType();
+    TypeKind javaTypeKind = javaType.getKind();
+    if (tree.getKind() != Tree.Kind.VARIABLE) {
+      if (javaTypeKind == TypeKind.BYTE
+          || javaTypeKind == TypeKind.CHAR
+          || javaTypeKind == TypeKind.SHORT
+          || javaTypeKind == TypeKind.INT
+          || javaTypeKind == TypeKind.LONG) {
+        ValueAnnotatedTypeFactory valueFactory = getTypeFactoryOfSubchecker(ValueChecker.class);
+        AnnotatedTypeMirror valueATM = valueFactory.getAnnotatedType(tree);
+        // These annotations are trusted rather than checked.  Maybe have an option to
+        // disable using them?
+        if ((valueATM.hasAnnotation(INT_RANGE_FROM_NON_NEGATIVE)
+                || valueATM.hasAnnotation(INT_RANGE_FROM_POSITIVE))
+            && type.hasAnnotation(SIGNED)) {
+          type.replaceAnnotation(SIGNEDNESS_GLB);
+        } else {
+          Range treeRange = ValueCheckerUtils.getPossibleValues(valueATM, valueFactory);
+
+          if (treeRange != null) {
+            switch (javaType.getKind()) {
+              case BYTE:
+              case CHAR:
+                if (treeRange.isWithin(0, Byte.MAX_VALUE)) {
+                  type.replaceAnnotation(SIGNEDNESS_GLB);
+                }
+                break;
+              case SHORT:
+                if (treeRange.isWithin(0, Short.MAX_VALUE)) {
+                  type.replaceAnnotation(SIGNEDNESS_GLB);
+                }
+                break;
+              case INT:
+                if (treeRange.isWithin(0, Integer.MAX_VALUE)) {
+                  type.replaceAnnotation(SIGNEDNESS_GLB);
+                }
+                break;
+              case LONG:
+                if (treeRange.isWithin(0, Long.MAX_VALUE)) {
+                  type.replaceAnnotation(SIGNEDNESS_GLB);
+                }
+                break;
+              default:
+                // Nothing
+            }
+          }
+        }
+      }
+    }
+  }
+
+  @Override
+  public Set<AnnotationMirror> getWidenedAnnotations(
+      Set<AnnotationMirror> annos, TypeKind typeKind, TypeKind widenedTypeKind) {
+    assert annos.size() == 1;
+
+    Set<AnnotationMirror> result = AnnotationUtils.createAnnotationSet();
+    if (TypeKindUtils.isFloatingPoint(widenedTypeKind)) {
+      result.add(SIGNED);
+      return result;
+    }
+    if (widenedTypeKind == TypeKind.CHAR) {
+      result.add(UNSIGNED);
+      return result;
+    }
+    AnnotationMirror anno = annos.iterator().next();
+    if (AnnotationUtils.areSame(anno, POLY_SIGNED)) {
+      return annos;
+    } else if (getQualifierHierarchy().isSubtype(anno, UNSIGNED)) {
+      // TODO: A future enhancement will make the widened type indicate the unsigned basetype
+      // from which it was widened.
+      result.add(SIGNED_POSITIVE_FROM_UNSIGNED);
+      return result;
+    } else {
+      // TODO: A future enhancement will make the widened type indicate the signed basetype
+      // from which it was widened.
+      result.add(SIGNEDNESS_GLB);
+      return result;
+    }
+  }
+
+  @Override
+  public Set<AnnotationMirror> getNarrowedAnnotations(
+      Set<AnnotationMirror> annos, TypeKind typeKind, TypeKind narrowedTypeKind) {
+    assert annos.size() == 1;
+
+    Set<AnnotationMirror> result = AnnotationUtils.createAnnotationSet();
+
+    if (narrowedTypeKind == TypeKind.CHAR) {
+      result.add(SIGNED);
+      return result;
+    }
+
+    return annos;
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(new SignednessTreeAnnotator(this), super.createTreeAnnotator());
+  }
+
+  /**
+   * This TreeAnnotator ensures that:
+   *
+   * <ul>
+   *   <li>boolean expressions are not given Unsigned or Signed annotations by {@link
+   *       PropagationTreeAnnotator},
+   *   <li>shift results take on the type of their left operand,
+   *   <li>the types of identifiers are refined based on the results of the Value Checker.
+   *   <li>casts take types related to widening
+   * </ul>
+   */
+  private class SignednessTreeAnnotator extends TreeAnnotator {
+
+    public SignednessTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    /**
+     * Change the type of booleans to {@code @UnknownSignedness} so that the {@link
+     * PropagationTreeAnnotator} does not change the type of them.
+     *
+     * @param type a type to change the annotation of, if it is boolean
+     */
+    private void annotateBooleanAsUnknownSignedness(AnnotatedTypeMirror type) {
+      switch (type.getKind()) {
+        case BOOLEAN:
+          type.addAnnotation(UNKNOWN_SIGNEDNESS);
+          break;
+        default:
+          // Nothing for other cases.
+      }
+    }
+
+    @Override
+    public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) {
+      switch (tree.getKind()) {
+        case LEFT_SHIFT:
+        case RIGHT_SHIFT:
+        case UNSIGNED_RIGHT_SHIFT:
+          TreePath path = getPath(tree);
+          if (path != null
+              && (isMaskedShiftEitherSignedness(tree, path)
+                  || isCastedShiftEitherSignedness(tree, path))) {
+            type.replaceAnnotation(SIGNEDNESS_GLB);
+          } else {
+            AnnotatedTypeMirror lht = getAnnotatedType(tree.getLeftOperand());
+            type.replaceAnnotations(lht.getAnnotations());
+          }
+          break;
+        default:
+          // Do nothing
+      }
+      annotateBooleanAsUnknownSignedness(type);
+      return null;
+    }
+
+    @Override
+    public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) {
+      annotateBooleanAsUnknownSignedness(type);
+      return null;
+    }
+  }
+
+  @Override
+  protected void addAnnotationsFromDefaultForType(
+      @Nullable Element element, AnnotatedTypeMirror type) {
+    if (TypesUtils.isFloatingPrimitive(type.getUnderlyingType())
+        || TypesUtils.isBoxedFloating(type.getUnderlyingType())
+        || type.getKind() == TypeKind.CHAR
+        || TypesUtils.isDeclaredOfName(type.getUnderlyingType(), "java.lang.Character")) {
+      // Floats are always signed and chars are always unsigned.
+      super.addAnnotationsFromDefaultForType(null, type);
+    } else {
+      super.addAnnotationsFromDefaultForType(element, type);
+    }
+  }
+
+  // The remainder of this file contains code to special-case shifts whose result does not depend
+  // on the MSB of the first argument, due to subsequent masking or casts.
+
+  /**
+   * Returns true iff the given tree node is a mask operation (&amp; or |).
+   *
+   * @param node a tree to test
+   * @return true iff node is a mask operation (&amp; or |)
+   */
+  private boolean isMask(Tree node) {
+    Kind kind = node.getKind();
+
+    return kind == Kind.AND || kind == Kind.OR;
+  }
+
+  // TODO: Return a TypeKind rather than a PrimitiveTypeTree?
+  /**
+   * Returns the type of a primitive cast, or null the argument is not a cast to a primitive.
+   *
+   * @param node a tree that might be a cast to a primitive
+   * @return type of a primitive cast, or null if not a cast to a primitive
+   */
+  private PrimitiveTypeTree primitiveTypeCast(Tree node) {
+    if (node.getKind() != Kind.TYPE_CAST) {
+      return null;
+    }
+
+    TypeCastTree cast = (TypeCastTree) node;
+    Tree castType = cast.getType();
+
+    Tree underlyingType;
+    if (castType.getKind() == Kind.ANNOTATED_TYPE) {
+      underlyingType = ((AnnotatedTypeTree) castType).getUnderlyingType();
+    } else {
+      underlyingType = castType;
+    }
+
+    if (underlyingType.getKind() != Kind.PRIMITIVE_TYPE) {
+      return null;
+    }
+
+    return (PrimitiveTypeTree) underlyingType;
+  }
+
+  /**
+   * Returns true iff the given tree is a literal.
+   *
+   * @param expr a tree to test
+   * @return true iff expr is a literal
+   */
+  private boolean isLiteral(ExpressionTree expr) {
+    return expr instanceof LiteralTree;
+  }
+
+  /**
+   * @param obj either an Integer or a Long
+   * @return the long value of obj
+   */
+  private long getLong(Object obj) {
+    return ((Number) obj).longValue();
+  }
+
+  /**
+   * Given a masking operation of the form {@code expr & maskLit} or {@code expr | maskLit}, return
+   * true iff the masking operation results in the same output regardless of the value of the
+   * shiftAmount most significant bits of expr. This is if the shiftAmount most significant bits of
+   * mask are 0 for AND, and 1 for OR. For example, assuming that shiftAmount is 4, the following is
+   * true about AND and OR masks:
+   *
+   * <p>{@code expr & 0x0[anything] == 0x0[something] ;}
+   *
+   * <p>{@code expr | 0xF[anything] == 0xF[something] ;}
+   *
+   * @param maskKind the kind of mask (AND or OR)
+   * @param shiftAmountLit the LiteralTree whose value is shiftAmount
+   * @param maskLit the LiteralTree whose value is mask
+   * @param shiftedTypeKind the type of shift operation; int or long
+   * @return true iff the shiftAmount most significant bits of mask are 0 for AND, and 1 for OR
+   */
+  private boolean maskIgnoresMSB(
+      Kind maskKind, LiteralTree shiftAmountLit, LiteralTree maskLit, TypeKind shiftedTypeKind) {
+    long shiftAmount = getLong(shiftAmountLit.getValue());
+
+    // Shift of zero is a nop
+    if (shiftAmount == 0) {
+      return true;
+    }
+
+    long mask = getLong(maskLit.getValue());
+    // Shift the shiftAmount most significant bits to become the shiftAmount least significant
+    // bits, zeroing out the rest.
+    if (shiftedTypeKind == TypeKind.INT) {
+      mask <<= 32;
+    }
+    mask >>>= (64 - shiftAmount);
+
+    if (maskKind == Kind.AND) {
+      // Check that the shiftAmount most significant bits of the mask were 0.
+      return mask == 0;
+    } else if (maskKind == Kind.OR) {
+      // Check that the shiftAmount most significant bits of the mask were 1.
+      return mask == (1 << shiftAmount) - 1;
+    } else {
+      throw new BugInCF("Invalid Masking Operation");
+    }
+  }
+
+  /**
+   * Given a casted right shift of the form {@code (type) (baseExpr >> shiftAmount)} or {@code
+   * (type) (baseExpr >>> shiftAmount)}, return true iff the expression's value is the same
+   * regardless of the type of right shift (signed or unsigned). This is true if the cast ignores
+   * the shiftAmount most significant bits of the shift result -- that is, if the cast ignores all
+   * the new bits that the right shift introduced on the left.
+   *
+   * <p>For example, the function returns true for
+   *
+   * <pre>{@code (short) (myInt >> 16)}</pre>
+   *
+   * and for
+   *
+   * <pre>{@code (short) (myInt >>> 16)}</pre>
+   *
+   * because these two expressions are guaranteed to have the same result.
+   *
+   * @param shiftTypeKind the kind of the type of the shift literal (BYTE, CHAR, SHORT, INT, or
+   *     LONG)
+   * @param castTypeKind the kind of the cast target type (BYTE, CHAR, SHORT, INT, or LONG)
+   * @param shiftAmountLit the LiteralTree whose value is shiftAmount
+   * @return true iff introduced bits are discarded
+   */
+  private boolean castIgnoresMSB(
+      TypeKind shiftTypeKind, TypeKind castTypeKind, LiteralTree shiftAmountLit) {
+
+    // Determine number of bits in the shift type, note shifts upcast to int.
+    // Also determine the shift amount as it is dependent on the shift type.
+    long shiftBits;
+    long shiftAmount;
+    switch (shiftTypeKind) {
+      case INT:
+        shiftBits = 32;
+        // When the LHS of the shift is an int, the 5 lower order bits of the RHS are used.
+        shiftAmount = 0x1F & getLong(shiftAmountLit.getValue());
+        break;
+      case LONG:
+        shiftBits = 64;
+        // When the LHS of the shift is a long, the 6 lower order bits of the RHS are used.
+        shiftAmount = 0x3F & getLong(shiftAmountLit.getValue());
+        break;
+      default:
+        throw new BugInCF("Invalid shift type");
+    }
+
+    // Determine number of bits in the cast type
+    long castBits;
+    switch (castTypeKind) {
+      case BYTE:
+        castBits = 8;
+        break;
+      case CHAR:
+        castBits = 8;
+        break;
+      case SHORT:
+        castBits = 16;
+        break;
+      case INT:
+        castBits = 32;
+        break;
+      case LONG:
+        castBits = 64;
+        break;
+      default:
+        throw new BugInCF("Invalid cast target");
+    }
+
+    long bitsDiscarded = shiftBits - castBits;
+
+    return shiftAmount <= bitsDiscarded || shiftAmount == 0;
+  }
+
+  /**
+   * Determines if a right shift operation, {@code >>} or {@code >>>}, is masked with a masking
+   * operation of the form {@code shiftExpr & maskLit} or {@code shiftExpr | maskLit} such that the
+   * mask renders the shift signedness ({@code >>} vs {@code >>>}) irrelevent by destroying the bits
+   * duplicated into the shift result. For example, the following pairs of right shifts on {@code
+   * byte b} both produce the same results under any input, because of their masks:
+   *
+   * <p>{@code (b >> 4) & 0x0F == (b >>> 4) & 0x0F;}
+   *
+   * <p>{@code (b >> 4) | 0xF0 == (b >>> 4) | 0xF0;}
+   *
+   * @param shiftExpr a right shift expression: {@code expr1 >> expr2} or {@code expr1 >>> expr2}
+   * @param path the path to {@code shiftExpr}
+   * @return true iff the right shift is masked such that a signed or unsigned right shift has the
+   *     same effect
+   */
+  /*package-private*/ boolean isMaskedShiftEitherSignedness(BinaryTree shiftExpr, TreePath path) {
+    Pair<Tree, Tree> enclosingPair = TreePathUtil.enclosingNonParen(path);
+    // enclosing immediately contains shiftExpr or a parenthesized version of shiftExpr
+    Tree enclosing = enclosingPair.first;
+    // enclosingChild is a child of enclosing:  shiftExpr or a parenthesized version of it.
+    @SuppressWarnings("interning:assignment") // comparing AST nodes
+    @InternedDistinct Tree enclosingChild = enclosingPair.second;
+
+    if (!isMask(enclosing)) {
+      return false;
+    }
+
+    BinaryTree maskExpr = (BinaryTree) enclosing;
+    ExpressionTree shiftAmountExpr = shiftExpr.getRightOperand();
+
+    // Determine which child of maskExpr leads to shiftExpr. The other one is the mask.
+    ExpressionTree mask =
+        maskExpr.getRightOperand() == enclosingChild
+            ? maskExpr.getLeftOperand()
+            : maskExpr.getRightOperand();
+
+    // Strip away the parentheses from the mask if any exist
+    mask = TreeUtils.withoutParens(mask);
+
+    if (!isLiteral(shiftAmountExpr) || !isLiteral(mask)) {
+      return false;
+    }
+
+    LiteralTree shiftLit = (LiteralTree) shiftAmountExpr;
+    LiteralTree maskLit = (LiteralTree) mask;
+
+    return maskIgnoresMSB(
+        maskExpr.getKind(), shiftLit, maskLit, TreeUtils.typeOf(shiftExpr).getKind());
+  }
+
+  /**
+   * Determines if a right shift operation, {@code >>} or {@code >>>}, is type casted such that the
+   * cast renders the shift signedness ({@code >>} vs {@code >>>}) irrelevent by discarding the bits
+   * duplicated into the shift result. For example, the following pair of right shifts on {@code
+   * short s} both produce the same results under any input, because of type casting:
+   *
+   * <p>{@code (byte)(s >> 8) == (byte)(b >>> 8);}
+   *
+   * @param shiftExpr a right shift expression: {@code expr1 >> expr2} or {@code expr1 >>> expr2}
+   * @param path the path to {@code shiftExpr}
+   * @return true iff the right shift is type casted such that a signed or unsigned right shift has
+   *     the same effect
+   */
+  /*package-private*/ boolean isCastedShiftEitherSignedness(BinaryTree shiftExpr, TreePath path) {
+    // enclosing immediately contains shiftExpr or a parenthesized version of shiftExpr
+    Tree enclosing = TreePathUtil.enclosingNonParen(path).first;
+
+    PrimitiveTypeTree castPrimitiveType = primitiveTypeCast(enclosing);
+    if (castPrimitiveType == null) {
+      return false;
+    }
+    TypeKind castTypeKind = castPrimitiveType.getPrimitiveTypeKind();
+
+    // Determine the type of the shift result
+    TypeKind shiftTypeKind = TreeUtils.typeOf(shiftExpr).getKind();
+
+    // Determine shift literal
+    ExpressionTree shiftAmountExpr = shiftExpr.getRightOperand();
+    if (!isLiteral(shiftAmountExpr)) {
+      return false;
+    }
+    LiteralTree shiftLit = (LiteralTree) shiftAmountExpr;
+
+    boolean result = castIgnoresMSB(shiftTypeKind, castTypeKind, shiftLit);
+    return result;
+  }
+
+  // End of special-case code for shifts that do not depend on the MSB of the first argument.
+
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessChecker.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessChecker.java
new file mode 100644
index 0000000..6747df7
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessChecker.java
@@ -0,0 +1,35 @@
+package org.checkerframework.checker.signedness;
+
+import java.util.LinkedHashSet;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+
+/**
+ * A type-checker that prevents mixing of unsigned and signed values, and prevents meaningless
+ * operations on unsigned values.
+ *
+ * @checker_framework.manual #signedness-checker Signedness Checker
+ */
+@RelevantJavaTypes({
+  Byte.class,
+  Short.class,
+  Integer.class,
+  Long.class,
+  Character.class,
+  byte.class,
+  short.class,
+  int.class,
+  long.class,
+  char.class
+})
+public class SignednessChecker extends BaseTypeChecker {
+
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    LinkedHashSet<Class<? extends BaseTypeChecker>> checkers =
+        super.getImmediateSubcheckerClasses();
+    checkers.add(ValueChecker.class);
+    return checkers;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java
new file mode 100644
index 0000000..c44f3a2
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java
@@ -0,0 +1,235 @@
+package org.checkerframework.checker.signedness;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.Tree.Kind;
+import org.checkerframework.checker.signedness.qual.PolySigned;
+import org.checkerframework.checker.signedness.qual.Signed;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * The SignednessVisitor enforces the Signedness Checker rules. These rules are described in the
+ * Checker Framework Manual.
+ *
+ * @checker_framework.manual #signedness-checker Signedness Checker
+ */
+public class SignednessVisitor extends BaseTypeVisitor<SignednessAnnotatedTypeFactory> {
+
+  public SignednessVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  /**
+   * Determines if an annotated type is annotated as {@link Unsigned} or {@link PolySigned}
+   *
+   * @param type the annotated type to be checked
+   * @return true if the annotated type is annotated as {@link Unsigned} or {@link PolySigned}
+   */
+  private boolean hasUnsignedAnnotation(AnnotatedTypeMirror type) {
+    return type.hasAnnotation(Unsigned.class) || type.hasAnnotation(PolySigned.class);
+  }
+
+  /**
+   * Determines if an annotated type is annotated as {@link Signed} or {@link PolySigned}
+   *
+   * @param type the annotated type to be checked
+   * @return true if the annotated type is annotated as {@link Signed} or {@link PolySigned}
+   */
+  private boolean hasSignedAnnotation(AnnotatedTypeMirror type) {
+    return type.hasAnnotation(Signed.class) || type.hasAnnotation(PolySigned.class);
+  }
+
+  /**
+   * Enforces the following rules on binary operations involving Unsigned and Signed types:
+   *
+   * <ul>
+   *   <li>Do not allow any Unsigned types or PolySigned types in {@literal {/, %}} operations.
+   *   <li>Do not allow signed right shift {@literal {>>}} on an Unsigned type or a PolySigned type.
+   *   <li>Do not allow unsigned right shift {@literal {>>>}} on a Signed type or a PolySigned type.
+   *   <li>Allow any left shift {@literal {<<}}.
+   *   <li>Do not allow non-equality comparisons {@literal {<, <=, >, >=}} on Unsigned types or
+   *       PolySigned types.
+   *   <li>Do not allow the mixing of Signed and Unsigned types.
+   * </ul>
+   */
+  @Override
+  public Void visitBinary(BinaryTree node, Void p) {
+    // Used in diagnostic messages.
+    ExpressionTree leftOp = node.getLeftOperand();
+    ExpressionTree rightOp = node.getRightOperand();
+
+    Pair<AnnotatedTypeMirror, AnnotatedTypeMirror> argTypes = atypeFactory.binaryTreeArgTypes(node);
+    AnnotatedTypeMirror leftOpType = argTypes.first;
+    AnnotatedTypeMirror rightOpType = argTypes.second;
+
+    Kind kind = node.getKind();
+
+    switch (kind) {
+      case DIVIDE:
+      case REMAINDER:
+        if (hasUnsignedAnnotation(leftOpType)) {
+          checker.reportError(leftOp, "operation.unsignedlhs", kind, leftOpType, rightOpType);
+        } else if (hasUnsignedAnnotation(rightOpType)) {
+          checker.reportError(rightOp, "operation.unsignedrhs", kind, leftOpType, rightOpType);
+        }
+        break;
+
+      case RIGHT_SHIFT:
+        if (hasUnsignedAnnotation(leftOpType)
+            && !atypeFactory.isMaskedShiftEitherSignedness(node, getCurrentPath())
+            && !atypeFactory.isCastedShiftEitherSignedness(node, getCurrentPath())) {
+          checker.reportError(leftOp, "shift.signed", kind, leftOpType, rightOpType);
+        }
+        break;
+
+      case UNSIGNED_RIGHT_SHIFT:
+        if (hasSignedAnnotation(leftOpType)
+            && !atypeFactory.isMaskedShiftEitherSignedness(node, getCurrentPath())
+            && !atypeFactory.isCastedShiftEitherSignedness(node, getCurrentPath())) {
+          checker.reportError(leftOp, "shift.unsigned", kind, leftOpType, rightOpType);
+        }
+        break;
+
+      case LEFT_SHIFT:
+        break;
+
+      case GREATER_THAN:
+      case GREATER_THAN_EQUAL:
+      case LESS_THAN:
+      case LESS_THAN_EQUAL:
+        if (hasUnsignedAnnotation(leftOpType)) {
+          checker.reportError(leftOp, "comparison.unsignedlhs", leftOpType, rightOpType);
+        } else if (hasUnsignedAnnotation(rightOpType)) {
+          checker.reportError(rightOp, "comparison.unsignedrhs", leftOpType, rightOpType);
+        }
+        break;
+
+      case EQUAL_TO:
+      case NOT_EQUAL_TO:
+        if (leftOpType.hasAnnotation(Unsigned.class) && rightOpType.hasAnnotation(Signed.class)) {
+          checker.reportError(node, "comparison.mixed.unsignedlhs", leftOpType, rightOpType);
+        } else if (leftOpType.hasAnnotation(Signed.class)
+            && rightOpType.hasAnnotation(Unsigned.class)) {
+          checker.reportError(node, "comparison.mixed.unsignedrhs", leftOpType, rightOpType);
+        }
+        break;
+
+      default:
+        if (leftOpType.hasAnnotation(Unsigned.class) && rightOpType.hasAnnotation(Signed.class)) {
+          checker.reportError(node, "operation.mixed.unsignedlhs", kind, leftOpType, rightOpType);
+        } else if (leftOpType.hasAnnotation(Signed.class)
+            && rightOpType.hasAnnotation(Unsigned.class)) {
+          checker.reportError(node, "operation.mixed.unsignedrhs", kind, leftOpType, rightOpType);
+        }
+        break;
+    }
+    return super.visitBinary(node, p);
+  }
+
+  /** @return a string representation of kind, with trailing _ASSIGNMENT stripped off if any */
+  private String kindWithoutAssignment(Kind kind) {
+    String result = kind.toString();
+    if (result.endsWith("_ASSIGNMENT")) {
+      return result.substring(0, result.length() - "_ASSIGNMENT".length());
+    } else {
+      return result;
+    }
+  }
+
+  /**
+   * Enforces the following rules on compound assignments involving Unsigned and Signed types:
+   *
+   * <ul>
+   *   <li>Do not allow any Unsigned types or PolySigned types in {@literal {/=, %=}} assignments.
+   *   <li>Do not allow signed right shift {@literal {>>=}} to assign to an Unsigned type or a
+   *       PolySigned type.
+   *   <li>Do not allow unsigned right shift {@literal {>>>=}} to assign to a Signed type or a
+   *       PolySigned type.
+   *   <li>Allow any left shift {@literal {<<=}} assignment.
+   *   <li>Do not allow mixing of Signed and Unsigned types.
+   * </ul>
+   */
+  @Override
+  public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
+
+    ExpressionTree var = node.getVariable();
+    ExpressionTree expr = node.getExpression();
+
+    Pair<AnnotatedTypeMirror, AnnotatedTypeMirror> argTypes =
+        atypeFactory.compoundAssignmentTreeArgTypes(node);
+    AnnotatedTypeMirror varType = argTypes.first;
+    AnnotatedTypeMirror exprType = argTypes.second;
+
+    Kind kind = node.getKind();
+
+    switch (kind) {
+      case DIVIDE_ASSIGNMENT:
+      case REMAINDER_ASSIGNMENT:
+        if (hasUnsignedAnnotation(varType)) {
+          checker.reportError(
+              var,
+              "compound.assignment.unsigned.variable",
+              kindWithoutAssignment(kind),
+              varType,
+              exprType);
+        } else if (hasUnsignedAnnotation(exprType)) {
+          checker.reportError(
+              expr,
+              "compound.assignment.unsigned.expression",
+              kindWithoutAssignment(kind),
+              varType,
+              exprType);
+        }
+        break;
+
+      case RIGHT_SHIFT_ASSIGNMENT:
+        if (hasUnsignedAnnotation(varType)) {
+          checker.reportError(
+              var,
+              "compound.assignment.shift.signed",
+              kindWithoutAssignment(kind),
+              varType,
+              exprType);
+        }
+        break;
+
+      case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
+        if (hasSignedAnnotation(varType)) {
+          checker.reportError(
+              var,
+              "compound.assignment.shift.unsigned",
+              kindWithoutAssignment(kind),
+              varType,
+              exprType);
+        }
+        break;
+
+      case LEFT_SHIFT_ASSIGNMENT:
+        break;
+
+      default:
+        if (varType.hasAnnotation(Unsigned.class) && exprType.hasAnnotation(Signed.class)) {
+          checker.reportError(
+              expr,
+              "compound.assignment.mixed.unsigned.variable",
+              kindWithoutAssignment(kind),
+              varType,
+              exprType);
+        } else if (varType.hasAnnotation(Signed.class) && exprType.hasAnnotation(Unsigned.class)) {
+          checker.reportError(
+              expr,
+              "compound.assignment.mixed.unsigned.expression",
+              kindWithoutAssignment(kind),
+              varType,
+              exprType);
+        }
+        break;
+    }
+    return super.visitCompoundAssignment(node, p);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/messages.properties b/checker/src/main/java/org/checkerframework/checker/signedness/messages.properties
new file mode 100644
index 0000000..40e7b15
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/signedness/messages.properties
@@ -0,0 +1,17 @@
+## Error messages for the Signedness Checker
+operation.unsignedrhs=%s operation has an unsigned RHS%nlhs: %s%nrhs: %s
+operation.unsignedlhs=%s operation has an unsigned LHS%nlhs: %s%nrhs: %s
+operation.mixed.unsignedlhs=%s operation on unsigned LHS and signed RHS values%nlhs: %s%nrhs: %s
+operation.mixed.unsignedrhs=%s operation on signed LHS and unsigned RHS values%nlhs: %s%nrhs: %s
+shift.signed=%s operation on an unsigned value%nlhs: %s%nrhs: %s
+shift.unsigned=%s operation on a signed value%nlhs: %s%nrhs: %s
+comparison.unsignedlhs=comparison has an unsigned LHS%nlhs: %s%nrhs: %s
+comparison.unsignedrhs=comparison has an unsigned RHS%nlhs: %s%nrhs: %s
+comparison.mixed.unsignedlhs=comparison between unsigned LHS and signed RHS values%nlhs: %s%nrhs: %s
+comparison.mixed.unsignedrhs=comparison between signed LHS and unsigned RHS values%nlhs: %s%nrhs: %s
+compound.assignment.unsigned.variable=%s has unsigned LHS%nlhs: %s%nrhs: %s
+compound.assignment.unsigned.expression=%s has unsigned RHS%nlhs: %s%nrhs: %s
+compound.assignment.mixed.unsigned.variable=%s operation has unsigned LHS and signed RHS%nlhs: %s%nrhs: %s
+compound.assignment.mixed.unsigned.expression=%s operation has signed LHS and unsigned RHS%nlhs: %s%nrhs: %s
+compound.assignment.shift.signed=%s has unsigned LHS%nlhs: %s%nrhs: %s
+compound.assignment.shift.unsigned=%s has signed LHS%nlhs: %s%nrhs: %s
diff --git a/checker/src/main/java/org/checkerframework/checker/tainting/TaintingChecker.java b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingChecker.java
new file mode 100644
index 0000000..91fb23f
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingChecker.java
@@ -0,0 +1,15 @@
+package org.checkerframework.checker.tainting;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.source.SuppressWarningsPrefix;
+
+/**
+ * A type-checker plug-in for the Tainting type system qualifier that finds (and verifies the
+ * absence of) trust bugs.
+ *
+ * <p>It verifies that only verified values are trusted and that user-input is sanitized before use.
+ *
+ * @checker_framework.manual #tainting-checker Tainting Checker
+ */
+@SuppressWarningsPrefix({"untainted", "tainting"})
+public class TaintingChecker extends BaseTypeChecker {}
diff --git a/checker/src/main/java/org/checkerframework/checker/tainting/TaintingVisitor.java b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingVisitor.java
new file mode 100644
index 0000000..40f78e1
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingVisitor.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.tainting;
+
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+
+/** Visitor for the {@link TaintingChecker}. */
+public class TaintingVisitor extends BaseTypeVisitor<BaseAnnotatedTypeFactory> {
+
+  /**
+   * Creates a {@link TaintingVisitor}.
+   *
+   * @param checker the checker that uses this visitor
+   */
+  public TaintingVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  /**
+   * Don't check that the constructor result is top. Checking that the super() or this() call is a
+   * subtype of the constructor result is sufficient.
+   */
+  @Override
+  protected void checkConstructorResult(
+      AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {}
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFactory.java
new file mode 100644
index 0000000..64cf8eb
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFactory.java
@@ -0,0 +1,695 @@
+package org.checkerframework.checker.units;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.Tree;
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.util.Elements;
+import javax.tools.Diagnostic.Kind;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
+import org.checkerframework.checker.units.qual.MixedUnits;
+import org.checkerframework.checker.units.qual.Prefix;
+import org.checkerframework.checker.units.qual.UnitsBottom;
+import org.checkerframework.checker.units.qual.UnitsMultiple;
+import org.checkerframework.checker.units.qual.UnknownUnits;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeFormatter;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotationClassLoader;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.util.DefaultQualifierKindHierarchy;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.framework.util.QualifierKindHierarchy;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.InternalUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypeSystemError;
+import org.checkerframework.javacutil.UserError;
+import org.plumelib.reflection.Signatures;
+
+/**
+ * Annotated type factory for the Units Checker.
+ *
+ * <p>Handles multiple names for the same unit, with different prefixes, e.g. @kg is the same
+ * as @g(Prefix.kilo).
+ *
+ * <p>Supports relations between units, e.g. if "m" is a variable of type "@m" and "s" is a variable
+ * of type "@s", the division "m/s" is automatically annotated as "mPERs", the correct unit for the
+ * result.
+ */
+public class UnitsAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+  private static final Class<org.checkerframework.checker.units.qual.UnitsRelations>
+      unitsRelationsAnnoClass = org.checkerframework.checker.units.qual.UnitsRelations.class;
+
+  protected final AnnotationMirror mixedUnits =
+      AnnotationBuilder.fromClass(elements, MixedUnits.class);
+  protected final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, UnknownUnits.class);
+  protected final AnnotationMirror BOTTOM =
+      AnnotationBuilder.fromClass(elements, UnitsBottom.class);
+
+  /** The UnitsMultiple.prefix argument/element. */
+  private final ExecutableElement unitsMultiplePrefixElement =
+      TreeUtils.getMethod(UnitsMultiple.class, "prefix", 0, processingEnv);
+  /** The UnitsMultiple.quantity argument/element. */
+  private final ExecutableElement unitsMultipleQuantityElement =
+      TreeUtils.getMethod(UnitsMultiple.class, "quantity", 0, processingEnv);
+  /** The UnitsRelations.value argument/element. */
+  private final ExecutableElement unitsRelationsValueElement =
+      TreeUtils.getMethod(
+          org.checkerframework.checker.units.qual.UnitsRelations.class, "value", 0, processingEnv);
+
+  /**
+   * Map from canonical class name to the corresponding UnitsRelations instance. We use the string
+   * to prevent instantiating the UnitsRelations multiple times.
+   */
+  private Map<@CanonicalName String, UnitsRelations> unitsRel;
+
+  /** Map from canonical name of external qualifiers, to their Class. */
+  private static final Map<@CanonicalName String, Class<? extends Annotation>> externalQualsMap =
+      new HashMap<>();
+
+  private static final Map<String, AnnotationMirror> aliasMap = new HashMap<>();
+
+  public UnitsAnnotatedTypeFactory(BaseTypeChecker checker) {
+    // use true to enable flow inference, false to disable it
+    super(checker, false);
+
+    this.postInit();
+  }
+
+  // In Units Checker, we always want to print out the Invisible Qualifiers (UnknownUnits), and to
+  // format the print out of qualifiers by removing Prefix.one
+  @Override
+  protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() {
+    return new UnitsAnnotatedTypeFormatter(checker);
+  }
+
+  // Converts all metric-prefixed units' alias annotations (eg @kg) into base unit annotations
+  // with prefix values (eg @g(Prefix.kilo))
+  @Override
+  public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) {
+    // Get the name of the aliased annotation
+    String aname = AnnotationUtils.annotationName(anno);
+
+    // See if we already have a map from this aliased annotation to its corresponding base unit
+    // annotation
+    if (aliasMap.containsKey(aname)) {
+      // if so return it
+      return aliasMap.get(aname);
+    }
+
+    boolean built = false;
+    AnnotationMirror result = null;
+    // if not, look for the UnitsMultiple meta annotations of this aliased annotation
+    for (AnnotationMirror metaAnno : anno.getAnnotationType().asElement().getAnnotationMirrors()) {
+      // see if the meta annotation is UnitsMultiple
+      if (isUnitsMultiple(metaAnno)) {
+        // retrieve the Class of the base unit annotation
+        Name baseUnitAnnoClass =
+            AnnotationUtils.getElementValueClassName(metaAnno, unitsMultipleQuantityElement);
+
+        // retrieve the SI Prefix of the aliased annotation
+        Prefix prefix =
+            AnnotationUtils.getElementValueEnum(
+                metaAnno, unitsMultiplePrefixElement, Prefix.class, Prefix.one);
+
+        // Build a base unit annotation with the prefix applied
+        result =
+            UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(
+                processingEnv, baseUnitAnnoClass, prefix);
+
+        // TODO: assert that this annotation is a prefix multiple of a Unit that's in the supported
+        // type qualifiers list currently this breaks for externally loaded annotations if the order
+        // was an alias before a base annotation.
+        // assert isSupportedQualifier(result);
+
+        built = true;
+        break;
+      }
+    }
+
+    if (built) {
+      // aliases shouldn't have Prefix.one, but if it does then clean it up here
+      if (UnitsRelationsTools.getPrefix(result) == Prefix.one) {
+        result = removePrefix(result);
+      }
+
+      // add this to the alias map
+      aliasMap.put(aname, result);
+      return result;
+    }
+
+    return super.canonicalAnnotation(anno);
+  }
+
+  /**
+   * Returns a map from canonical class name to the corresponding UnitsRelations instance.
+   *
+   * @return a map from canonical class name to the corresponding UnitsRelations instance
+   */
+  protected Map<@CanonicalName String, UnitsRelations> getUnitsRel() {
+    if (unitsRel == null) {
+      unitsRel = new HashMap<>();
+      // Always add the default units relations, for the standard units.
+      // Other code adds more relations.
+      unitsRel.put(
+          UnitsRelationsDefault.class.getCanonicalName(),
+          new UnitsRelationsDefault().init(processingEnv));
+    }
+    return unitsRel;
+  }
+
+  @Override
+  protected AnnotationClassLoader createAnnotationClassLoader() {
+    // Use the UnitsAnnotationClassLoader instead of the default one
+    return new UnitsAnnotationClassLoader(checker);
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    // get all the loaded annotations
+    Set<Class<? extends Annotation>> qualSet = getBundledTypeQualifiers();
+
+    // load all the external units
+    loadAllExternalUnits();
+
+    // copy all loaded external Units to qual set
+    qualSet.addAll(externalQualsMap.values());
+
+    return qualSet;
+  }
+
+  private void loadAllExternalUnits() {
+    // load external individually named units
+    String qualNames = checker.getOption("units");
+    if (qualNames != null) {
+      for (String qualName : qualNames.split(",")) {
+        if (!Signatures.isBinaryName(qualName)) {
+          throw new UserError("Malformed qualifier name \"%s\" in -Aunits=%s", qualName, qualNames);
+        }
+        loadExternalUnit(qualName);
+      }
+    }
+
+    // load external directories of units
+    String qualDirectories = checker.getOption("unitsDirs");
+    if (qualDirectories != null) {
+      for (String directoryName : qualDirectories.split(":")) {
+        loadExternalDirectory(directoryName);
+      }
+    }
+  }
+
+  /**
+   * Loads and processes a single external units qualifier.
+   *
+   * @param annoName the name of a units qualifier
+   */
+  private void loadExternalUnit(@BinaryName String annoName) {
+    // loadExternalAnnotationClass() returns null for alias units
+    Class<? extends Annotation> loadedClass = loader.loadExternalAnnotationClass(annoName);
+    if (loadedClass != null) {
+      addUnitToExternalQualMap(loadedClass);
+    }
+  }
+
+  /** Loads and processes the units qualifiers from a single external directory. */
+  private void loadExternalDirectory(String directoryName) {
+    Set<Class<? extends Annotation>> annoClassSet =
+        loader.loadExternalAnnotationClassesFromDirectory(directoryName);
+
+    for (Class<? extends Annotation> annoClass : annoClassSet) {
+      addUnitToExternalQualMap(annoClass);
+    }
+  }
+
+  /** Adds the annotation class to the external qualifier map if it is not an alias annotation. */
+  private void addUnitToExternalQualMap(final Class<? extends Annotation> annoClass) {
+    AnnotationMirror mirror =
+        UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(
+            processingEnv, annoClass.getCanonicalName());
+
+    // if it is not an aliased annotation, add to external quals map if it isn't already in map
+    if (!isAliasedAnnotation(mirror)) {
+      String unitClassName = annoClass.getCanonicalName();
+      if (!externalQualsMap.containsKey(unitClassName)) {
+        externalQualsMap.put(unitClassName, annoClass);
+      }
+    }
+    // if it is an aliased annotation
+    else {
+      // ensure it has a base unit
+      @CanonicalName Name baseUnitClass = getBaseUnitAnno(mirror);
+      if (baseUnitClass != null) {
+        // if the base unit isn't already added, add that first
+        @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658
+        @DotSeparatedIdentifiers String baseUnitClassName = baseUnitClass.toString();
+        if (!externalQualsMap.containsKey(baseUnitClassName)) {
+          loadExternalUnit(baseUnitClassName);
+        }
+
+        // then add the aliased annotation to the alias map
+        // TODO: refactor so we can directly add to alias map, skipping the assert check in
+        // canonicalAnnotation.
+        canonicalAnnotation(mirror);
+      } else {
+        // error: somehow the aliased annotation has @UnitsMultiple meta annotation, but no
+        // base class defined in that meta annotation
+        // TODO: error abort
+      }
+    }
+
+    // process the units annotation and add its corresponding units relations class
+    addUnitsRelations(annoClass);
+  }
+
+  private boolean isAliasedAnnotation(AnnotationMirror anno) {
+    // loop through the meta annotations of the annotation, look for UnitsMultiple
+    for (AnnotationMirror metaAnno : anno.getAnnotationType().asElement().getAnnotationMirrors()) {
+      // see if the meta annotation is UnitsMultiple
+      if (isUnitsMultiple(metaAnno)) {
+        // TODO: does every alias have to have Prefix?
+        return true;
+      }
+    }
+
+    // if we are unable to find UnitsMultiple meta annotation, then this is not an Aliased
+    // Annotation
+    return false;
+  }
+
+  /**
+   * Return the name of the given annotation, if it is meta-annotated with UnitsMultiple; otherwise
+   * return null.
+   *
+   * @param anno the annotation to examine
+   * @return the annotation's name, if it is meta-annotated with UnitsMultiple; otherwise null
+   */
+  private @Nullable @CanonicalName Name getBaseUnitAnno(AnnotationMirror anno) {
+    // loop through the meta annotations of the annotation, look for UnitsMultiple
+    for (AnnotationMirror metaAnno : anno.getAnnotationType().asElement().getAnnotationMirrors()) {
+      // see if the meta annotation is UnitsMultiple
+      if (isUnitsMultiple(metaAnno)) {
+        // TODO: does every alias have to have Prefix?
+        // Retrieve the base unit annotation.
+        Name baseUnitAnnoClass =
+            AnnotationUtils.getElementValueClassName(metaAnno, unitsMultipleQuantityElement);
+        return baseUnitAnnoClass;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns true if {@code metaAnno} is {@link UnitsMultiple}.
+   *
+   * @param metaAnno an annotation mirror
+   * @return true if {@code metaAnno} is {@link UnitsMultiple}
+   */
+  private boolean isUnitsMultiple(AnnotationMirror metaAnno) {
+    return areSameByClass(metaAnno, UnitsMultiple.class);
+  }
+
+  /**
+   * Look for an @UnitsRelations annotation on the qualifier and add it to the list of
+   * UnitsRelations.
+   *
+   * @param qual the qualifier to investigate
+   */
+  private void addUnitsRelations(Class<? extends Annotation> qual) {
+    AnnotationMirror am = AnnotationBuilder.fromClass(elements, qual);
+
+    for (AnnotationMirror ama : am.getAnnotationType().asElement().getAnnotationMirrors()) {
+      if (areSameByClass(ama, unitsRelationsAnnoClass)) {
+        String theclassname =
+            AnnotationUtils.getElementValueClassName(ama, unitsRelationsValueElement).toString();
+        if (!Signatures.isClassGetName(theclassname)) {
+          throw new UserError(
+              "Malformed class name \"%s\" should be in ClassGetName format in annotation %s",
+              theclassname, ama);
+        }
+        Class<?> valueElement;
+        try {
+          ClassLoader classLoader = InternalUtils.getClassLoaderForClass(AnnotationUtils.class);
+          valueElement = Class.forName(theclassname, true, classLoader);
+        } catch (ClassNotFoundException e) {
+          String msg =
+              String.format(
+                  "Could not load class '%s' for field 'value' in annotation %s",
+                  theclassname, ama);
+          throw new UserError(msg, e);
+        }
+        Class<? extends UnitsRelations> unitsRelationsClass;
+        try {
+          unitsRelationsClass = valueElement.asSubclass(UnitsRelations.class);
+        } catch (ClassCastException ex) {
+          throw new UserError(
+              "Invalid @UnitsRelations meta-annotation found in %s. "
+                  + "@UnitsRelations value %s is not a subclass of "
+                  + "org.checkerframework.checker.units.UnitsRelations.",
+              qual, ama);
+        }
+        String classname = unitsRelationsClass.getCanonicalName();
+
+        if (!getUnitsRel().containsKey(classname)) {
+          try {
+            unitsRel.put(
+                classname,
+                unitsRelationsClass.getDeclaredConstructor().newInstance().init(processingEnv));
+          } catch (Throwable e) {
+            throw new BugInCF("Throwable when instantiating UnitsRelations", e);
+          }
+        }
+      }
+    }
+  }
+
+  @Override
+  public TreeAnnotator createTreeAnnotator() {
+    // Don't call super.createTreeAnnotator because it includes PropagationTreeAnnotator which
+    // is incorrect.
+    return new ListTreeAnnotator(
+        new UnitsPropagationTreeAnnotator(this),
+        new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(),
+        new UnitsTreeAnnotator(this));
+  }
+
+  private static class UnitsPropagationTreeAnnotator extends PropagationTreeAnnotator {
+
+    public UnitsPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    // Handled completely by UnitsTreeAnnotator
+    @Override
+    public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
+      return null;
+    }
+
+    // Handled completely by UnitsTreeAnnotator
+    @Override
+    public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
+      return null;
+    }
+  }
+
+  /** A class for adding annotations based on tree. */
+  private class UnitsTreeAnnotator extends TreeAnnotator {
+
+    UnitsTreeAnnotator(UnitsAnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
+      AnnotatedTypeMirror lht = getAnnotatedType(node.getLeftOperand());
+      AnnotatedTypeMirror rht = getAnnotatedType(node.getRightOperand());
+      Tree.Kind kind = node.getKind();
+
+      // Remove Prefix.one
+      if (UnitsRelationsTools.getPrefix(lht) == Prefix.one) {
+        lht = UnitsRelationsTools.removePrefix(elements, lht);
+      }
+      if (UnitsRelationsTools.getPrefix(rht) == Prefix.one) {
+        rht = UnitsRelationsTools.removePrefix(elements, rht);
+      }
+
+      AnnotationMirror bestres = null;
+      for (UnitsRelations ur : getUnitsRel().values()) {
+        AnnotationMirror res = useUnitsRelation(kind, ur, lht, rht);
+
+        if (bestres != null && res != null && !bestres.equals(res)) {
+          checker.message(
+              Kind.WARNING,
+              "UnitsRelation mismatch, taking neither! Previous: "
+                  + bestres
+                  + " and current: "
+                  + res);
+          return null; // super.visitBinary(node, type);
+        }
+
+        if (res != null) {
+          bestres = res;
+        }
+      }
+
+      if (bestres != null) {
+        type.replaceAnnotation(bestres);
+      } else {
+        // If none of the units relations classes could resolve the units, then apply default rules
+
+        switch (kind) {
+          case MINUS:
+          case PLUS:
+            if (lht.getAnnotations().equals(rht.getAnnotations())) {
+              // The sum or difference has the same units as both operands.
+              type.replaceAnnotations(lht.getAnnotations());
+            } else {
+              // otherwise it results in mixed
+              type.replaceAnnotation(mixedUnits);
+            }
+            break;
+          case DIVIDE:
+            if (lht.getAnnotations().equals(rht.getAnnotations())) {
+              // If the units of the division match, return TOP
+              type.replaceAnnotation(TOP);
+            } else if (UnitsRelationsTools.hasNoUnits(rht)) {
+              // any unit divided by a scalar keeps that unit
+              type.replaceAnnotations(lht.getAnnotations());
+            } else if (UnitsRelationsTools.hasNoUnits(lht)) {
+              // scalar divided by any unit returns mixed
+              type.replaceAnnotation(mixedUnits);
+            } else {
+              // else it is a division of two units that have no defined relations
+              // from a relations class
+              // return mixed
+              type.replaceAnnotation(mixedUnits);
+            }
+            break;
+          case MULTIPLY:
+            if (UnitsRelationsTools.hasNoUnits(lht)) {
+              // any unit multiplied by a scalar keeps the unit
+              type.replaceAnnotations(rht.getAnnotations());
+            } else if (UnitsRelationsTools.hasNoUnits(rht)) {
+              // any scalar multiplied by a unit becomes the unit
+              type.replaceAnnotations(lht.getAnnotations());
+            } else {
+              // else it is a multiplication of two units that have no defined
+              // relations from a relations class
+              // return mixed
+              type.replaceAnnotation(mixedUnits);
+            }
+            break;
+          case REMAINDER:
+            // in modulo operation, it always returns the left unit regardless of what
+            // it is (unknown, or some unit)
+            type.replaceAnnotations(lht.getAnnotations());
+            break;
+          default:
+            // Placeholders for unhandled binary operations
+            // Do nothing
+        }
+      }
+
+      return null;
+    }
+
+    @Override
+    public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
+      ExpressionTree var = node.getVariable();
+      AnnotatedTypeMirror varType = getAnnotatedType(var);
+
+      type.replaceAnnotations(varType.getAnnotations());
+      return null;
+    }
+
+    private AnnotationMirror useUnitsRelation(
+        Tree.Kind kind, UnitsRelations ur, AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) {
+
+      AnnotationMirror res = null;
+      if (ur != null) {
+        switch (kind) {
+          case DIVIDE:
+            res = ur.division(lht, rht);
+            break;
+          case MULTIPLY:
+            res = ur.multiplication(lht, rht);
+            break;
+          default:
+            // Do nothing
+        }
+      }
+      return res;
+    }
+  }
+
+  /** Set the Bottom qualifier as the bottom of the hierarchy. */
+  @Override
+  public QualifierHierarchy createQualifierHierarchy() {
+    return new UnitsQualifierHierarchy();
+  }
+
+  /** Qualifier Hierarchy for the Units Checker. */
+  @AnnotatedFor("nullness")
+  protected class UnitsQualifierHierarchy extends MostlyNoElementQualifierHierarchy {
+    /** Constructor. */
+    public UnitsQualifierHierarchy() {
+      super(UnitsAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), elements);
+    }
+
+    @Override
+    protected QualifierKindHierarchy createQualifierKindHierarchy(
+        @UnderInitialization UnitsQualifierHierarchy this,
+        Collection<Class<? extends Annotation>> qualifierClasses) {
+      return new UnitsQualifierKindHierarchy(qualifierClasses, elements);
+    }
+
+    @Override
+    protected boolean isSubtypeWithElements(
+        AnnotationMirror subAnno,
+        QualifierKind subKind,
+        AnnotationMirror superAnno,
+        QualifierKind superKind) {
+      return AnnotationUtils.areSame(subAnno, superAnno);
+    }
+
+    @Override
+    protected AnnotationMirror leastUpperBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind lubKind) {
+      if (qualifierKind1.isBottom()) {
+        return a2;
+      } else if (qualifierKind2.isBottom()) {
+        return a1;
+      } else if (qualifierKind1 == qualifierKind2) {
+        if (AnnotationUtils.areSame(a1, a2)) {
+          return a1;
+        } else {
+          @SuppressWarnings({
+            "nullness:assignment" // Every qualifier kind is a
+            // key in directSuperQualifierMap.
+          })
+          @NonNull AnnotationMirror lub =
+              ((UnitsQualifierKindHierarchy) qualifierKindHierarchy)
+                  .directSuperQualifierMap.get(qualifierKind1);
+          return lub;
+        }
+      }
+      throw new BugInCF("Unexpected QualifierKinds: %s %s", qualifierKind1, qualifierKind2);
+    }
+
+    @Override
+    protected AnnotationMirror greatestLowerBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind glbKind) {
+      return UnitsAnnotatedTypeFactory.this.BOTTOM;
+    }
+  }
+
+  /** UnitsQualifierKindHierarchy. */
+  @AnnotatedFor("nullness")
+  protected static class UnitsQualifierKindHierarchy extends DefaultQualifierKindHierarchy {
+
+    /**
+     * Mapping from QualifierKind to an AnnotationMirror that represents its direct super qualifier.
+     * Every qualifier kind maps to a nonnull AnnotationMirror.
+     */
+    private final Map<QualifierKind, AnnotationMirror> directSuperQualifierMap;
+
+    /**
+     * Creates a UnitsQualifierKindHierarchy.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+     * @param elements element utils
+     */
+    public UnitsQualifierKindHierarchy(
+        Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, UnitsBottom.class);
+      directSuperQualifierMap = createDirectSuperQualifierMap(elements);
+    }
+
+    /**
+     * Creates the direct super qualifier map.
+     *
+     * @param elements element utils
+     * @return the map
+     */
+    @RequiresNonNull("this.qualifierKinds")
+    private Map<QualifierKind, AnnotationMirror> createDirectSuperQualifierMap(
+        @UnderInitialization UnitsQualifierKindHierarchy this, Elements elements) {
+      Map<QualifierKind, AnnotationMirror> directSuperType = new TreeMap<>();
+      for (QualifierKind qualifierKind : qualifierKinds) {
+        QualifierKind directSuperTypeKind = getDirectSuperQualifierKind(qualifierKind);
+        AnnotationMirror directSuperTypeAnno;
+        try {
+          directSuperTypeAnno = AnnotationBuilder.fromName(elements, directSuperTypeKind.getName());
+        } catch (BugInCF ex) {
+          throw new TypeSystemError("Unit annotations must have a default for all elements.");
+        }
+        if (directSuperTypeAnno == null) {
+          throw new TypeSystemError("Could not create AnnotationMirror: %s", directSuperTypeAnno);
+        }
+        directSuperType.put(qualifierKind, directSuperTypeAnno);
+      }
+      return directSuperType;
+    }
+
+    /**
+     * Get the direct super qualifier for the given qualifier kind.
+     *
+     * @param qualifierKind qualifier kind
+     * @return direct super qualifier kind
+     */
+    private QualifierKind getDirectSuperQualifierKind(
+        @UnderInitialization UnitsQualifierKindHierarchy this, QualifierKind qualifierKind) {
+      if (qualifierKind.isTop()) {
+        return qualifierKind;
+      }
+      Set<QualifierKind> superQuals = new TreeSet<>(qualifierKind.getStrictSuperTypes());
+      while (superQuals.size() > 0) {
+        Set<QualifierKind> lowest = findLowestQualifiers(superQuals);
+        if (lowest.size() == 1) {
+          return lowest.iterator().next();
+        }
+        superQuals.removeAll(lowest);
+      }
+      throw new BugInCF("No direct super qualifier found for %s", qualifierKind);
+    }
+  }
+
+  private AnnotationMirror removePrefix(AnnotationMirror anno) {
+    return UnitsRelationsTools.removePrefix(elements, anno);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFormatter.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFormatter.java
new file mode 100644
index 0000000..7f65a4d
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFormatter.java
@@ -0,0 +1,81 @@
+package org.checkerframework.checker.units;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.units.qual.Prefix;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.DefaultAnnotatedTypeFormatter;
+import org.checkerframework.framework.util.AnnotationFormatter;
+import org.checkerframework.framework.util.DefaultAnnotationFormatter;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+public class UnitsAnnotatedTypeFormatter extends DefaultAnnotatedTypeFormatter {
+  protected final BaseTypeChecker checker;
+  protected final Elements elements;
+
+  public UnitsAnnotatedTypeFormatter(BaseTypeChecker checker) {
+    // Utilize the Default Type Formatter, but force it to print out Invisible Qualifiers
+    // keep super call in sync with implementation in DefaultAnnotatedTypeFormatter
+    // keep checker options in sync with implementation in AnnotatedTypeFactory
+    super(
+        new UnitsFormattingVisitor(
+            checker,
+            new UnitsAnnotationFormatter(checker),
+            checker.hasOption("printVerboseGenerics"),
+            true));
+
+    this.checker = checker;
+    this.elements = checker.getElementUtils();
+  }
+
+  protected static class UnitsFormattingVisitor
+      extends DefaultAnnotatedTypeFormatter.FormattingVisitor {
+    protected final BaseTypeChecker checker;
+    protected final Elements elements;
+
+    public UnitsFormattingVisitor(
+        BaseTypeChecker checker,
+        AnnotationFormatter annoFormatter,
+        boolean printVerboseGenerics,
+        boolean defaultInvisiblesSetting) {
+
+      super(annoFormatter, printVerboseGenerics, defaultInvisiblesSetting);
+      this.checker = checker;
+      this.elements = checker.getElementUtils();
+    }
+  }
+
+  /** Format the error printout of any units qualifier that uses Prefix.one. */
+  protected static class UnitsAnnotationFormatter extends DefaultAnnotationFormatter {
+    protected final BaseTypeChecker checker;
+    protected final Elements elements;
+
+    public UnitsAnnotationFormatter(BaseTypeChecker checker) {
+      this.checker = checker;
+      this.elements = checker.getElementUtils();
+    }
+
+    @Override
+    public String formatAnnotationString(
+        Collection<? extends AnnotationMirror> annos, boolean printInvisible) {
+      // create an empty annotation set
+      Set<AnnotationMirror> trimmedAnnoSet = AnnotationUtils.createAnnotationSet();
+
+      // loop through all the annotation mirrors to see if they use Prefix.one, remove
+      // Prefix.one if it does
+      for (AnnotationMirror anno : annos) {
+        if (UnitsRelationsTools.getPrefix(anno) == Prefix.one) {
+          anno = UnitsRelationsTools.removePrefix(elements, anno);
+        }
+        // add to set
+        trimmedAnnoSet.add(anno);
+      }
+
+      return super.formatAnnotationString(
+          Collections.unmodifiableSet(trimmedAnnoSet), printInvisible);
+    }
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotationClassLoader.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotationClassLoader.java
new file mode 100644
index 0000000..b7c7df3
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotationClassLoader.java
@@ -0,0 +1,49 @@
+package org.checkerframework.checker.units;
+
+import java.lang.annotation.Annotation;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.units.qual.UnitsMultiple;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.AnnotationClassLoader;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+public class UnitsAnnotationClassLoader extends AnnotationClassLoader {
+
+  public UnitsAnnotationClassLoader(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  /**
+   * Custom filter for units annotations:
+   *
+   * <p>This filter will ignore (by returning false) any units annotation which is an alias of
+   * another base unit annotation (identified via {@link UnitsMultiple} meta-annotation). Alias
+   * annotations can still be used in source code; they are converted into a base annotation by
+   * {@link UnitsAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}. This filter simply
+   * makes sure that the alias annotations themselves don't become part of the type hierarchy as
+   * their base annotations already are in the hierarchy.
+   */
+  @Override
+  protected boolean isSupportedAnnotationClass(Class<? extends Annotation> annoClass) {
+    // build the initial annotation mirror (missing prefix)
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoClass);
+    AnnotationMirror initialResult = builder.build();
+
+    // further refine to see if the annotation is an alias of some other SI Unit annotation
+    for (AnnotationMirror metaAnno :
+        initialResult.getAnnotationType().asElement().getAnnotationMirrors()) {
+      // TODO : special treatment of invisible qualifiers?
+
+      // If the annotation is a SI prefix multiple of some base unit, then return false classic
+      // Units checker does not need to load the annotations of SI prefix multiples of base units.
+      if (AnnotationUtils.areSameByName(
+          metaAnno, "org.checkerframework.checker.units.qual.UnitsMultiple")) {
+        return false;
+      }
+    }
+
+    // Not an alias unit
+    return true;
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsChecker.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsChecker.java
new file mode 100644
index 0000000..0d44776
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsChecker.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.units;
+
+import java.util.SortedSet;
+import javax.annotation.processing.SupportedOptions;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.subtyping.SubtypingChecker;
+
+/**
+ * Units Checker main class.
+ *
+ * <p>Supports "units" option to add support for additional individually named and externally
+ * defined units, and "unitsDirs" option to add support for directories of externally defined units.
+ * Directories must be well-formed paths from file system root, separated by colon (:) between each
+ * directory.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@SupportedOptions({"units", "unitsDirs"})
+public class UnitsChecker extends BaseTypeChecker {
+
+  @Override
+  public SortedSet<String> getSuppressWarningsPrefixes() {
+    return SubtypingChecker.getSuppressWarningsPrefixes(
+        this.visitor, super.getSuppressWarningsPrefixes());
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelations.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelations.java
new file mode 100644
index 0000000..10b6bde
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelations.java
@@ -0,0 +1,41 @@
+package org.checkerframework.checker.units;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/**
+ * Interface that is used to specify the relation between units. A class that implements this
+ * interface is the argument to the {@link org.checkerframework.checker.units.qual.UnitsRelations}
+ * annotation.
+ */
+public interface UnitsRelations {
+  /**
+   * Initialize the object. Needs to be called before any other method.
+   *
+   * @param env the ProcessingEnvironment to use
+   * @return a reference to "this"
+   */
+  UnitsRelations init(ProcessingEnvironment env);
+
+  /**
+   * Called for the multiplication of type lht and rht.
+   *
+   * @param lht left hand side in multiplication
+   * @param rht right hand side in multiplication
+   * @return the annotation to use for the result of the multiplication or null if no special
+   *     relation is known
+   */
+  @Nullable AnnotationMirror multiplication(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht);
+
+  /**
+   * Called for the division of type lht and rht.
+   *
+   * @param lht left hand side in division
+   * @param rht right hand side in division
+   * @return the annotation to use for the result of the division or null if no special relation is
+   *     known
+   */
+  @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht);
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java
new file mode 100644
index 0000000..6bb9297
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java
@@ -0,0 +1,259 @@
+package org.checkerframework.checker.units;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.units.qual.N;
+import org.checkerframework.checker.units.qual.Prefix;
+import org.checkerframework.checker.units.qual.g;
+import org.checkerframework.checker.units.qual.h;
+import org.checkerframework.checker.units.qual.kg;
+import org.checkerframework.checker.units.qual.km2;
+import org.checkerframework.checker.units.qual.km3;
+import org.checkerframework.checker.units.qual.kmPERh;
+import org.checkerframework.checker.units.qual.m;
+import org.checkerframework.checker.units.qual.m2;
+import org.checkerframework.checker.units.qual.m3;
+import org.checkerframework.checker.units.qual.mPERs;
+import org.checkerframework.checker.units.qual.mPERs2;
+import org.checkerframework.checker.units.qual.mm2;
+import org.checkerframework.checker.units.qual.mm3;
+import org.checkerframework.checker.units.qual.s;
+import org.checkerframework.checker.units.qual.t;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/** Default relations between SI units. */
+public class UnitsRelationsDefault implements UnitsRelations {
+  /** SI base units. */
+  protected AnnotationMirror m, km, mm, s, g, kg;
+
+  /** Derived SI units without special names */
+  protected AnnotationMirror m2, km2, mm2, m3, km3, mm3, mPERs, mPERs2;
+
+  /** Derived SI units with special names */
+  protected AnnotationMirror N, kN;
+
+  /** Non-SI units */
+  protected AnnotationMirror h, kmPERh, t;
+
+  /** The Element Utilities from the Units Checker's processing environment. */
+  protected Elements elements;
+
+  /**
+   * Constructs various AnnotationMirrors representing specific checker-framework provided Units
+   * involved in the rules resolved in this UnitsRelations implementation.
+   */
+  @Override
+  public UnitsRelations init(ProcessingEnvironment env) {
+    elements = env.getElementUtils();
+
+    m = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, m.class);
+    km = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, m.class, Prefix.kilo);
+    mm = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, m.class, Prefix.milli);
+
+    m2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, m2.class);
+    km2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, km2.class);
+    mm2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mm2.class);
+
+    m3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, m3.class);
+    km3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, km3.class);
+    mm3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mm3.class);
+
+    s = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, s.class);
+    h = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, h.class);
+
+    mPERs = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mPERs.class);
+    kmPERh = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, kmPERh.class);
+
+    mPERs2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mPERs2.class);
+
+    g = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, g.class);
+    kg = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, g.class, Prefix.kilo);
+    t = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, t.class);
+    N = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, N.class);
+    kN = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, N.class, Prefix.kilo);
+
+    return this;
+  }
+
+  /**
+   * Provides rules for resolving the result Unit of the multiplication of checker-framework
+   * provided Units.
+   */
+  @Override
+  public @Nullable AnnotationMirror multiplication(
+      AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) {
+    // TODO: does this handle scaling correctly?
+
+    // length * length => area
+    // checking SI units only
+    if (UnitsRelationsTools.hasSpecificUnitIgnoringPrefix(lht, m)
+        && UnitsRelationsTools.hasSpecificUnitIgnoringPrefix(rht, m)) {
+      if (UnitsRelationsTools.hasNoPrefix(lht) && UnitsRelationsTools.hasNoPrefix(rht)) {
+        // m * m
+        return m2;
+      }
+
+      Prefix lhtPrefix = UnitsRelationsTools.getPrefix(lht);
+      Prefix rhtPrefix = UnitsRelationsTools.getPrefix(rht);
+
+      if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.kilo)) {
+        // km * km
+        return km2;
+      } else if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.one)) {
+        // m(Prefix.one) * m(Prefix.one)
+        return m2;
+      } else if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.milli)) {
+        // mm * mm
+        return mm2;
+      } else {
+        return null;
+      }
+    } else if (havePairOfUnitsIgnoringOrder(lht, m, rht, m2)) {
+      return m3;
+    } else if (havePairOfUnitsIgnoringOrder(lht, km, rht, km2)) {
+      return km3;
+    } else if (havePairOfUnitsIgnoringOrder(lht, mm, rht, mm2)) {
+      return mm3;
+    } else if (havePairOfUnitsIgnoringOrder(lht, s, rht, mPERs)) {
+      // s * mPERs or mPERs * s => m
+      return m;
+    } else if (havePairOfUnitsIgnoringOrder(lht, s, rht, mPERs2)) {
+      // s * mPERs2 or mPERs2 * s => mPERs
+      return mPERs;
+    } else if (havePairOfUnitsIgnoringOrder(lht, h, rht, kmPERh)) {
+      // h * kmPERh or kmPERh * h => km
+      return km;
+    } else if (havePairOfUnitsIgnoringOrder(lht, kg, rht, mPERs2)) {
+      // kg * mPERs2 or mPERs2 * kg = N
+      return N;
+    } else if (havePairOfUnitsIgnoringOrder(lht, t, rht, mPERs2)) {
+      // t * mPERs2 or mPERs2 * t = kN
+      return kN;
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Provides rules for resolving the result Unit of the division of checker-framework provided
+   * Units.
+   */
+  @Override
+  public @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) {
+    if (havePairOfUnits(lht, m, rht, s)) {
+      // m / s => mPERs
+      return mPERs;
+    } else if (havePairOfUnits(lht, km, rht, h)) {
+      // km / h => kmPERh
+      return kmPERh;
+    } else if (havePairOfUnits(lht, m2, rht, m)) {
+      // m2 / m => m
+      return m;
+    } else if (havePairOfUnits(lht, km2, rht, km)) {
+      // km2 / km => km
+      return km;
+    } else if (havePairOfUnits(lht, mm2, rht, mm)) {
+      // mm2 / mm => mm
+      return mm;
+    } else if (havePairOfUnits(lht, m3, rht, m)) {
+      // m3 / m => m2
+      return m2;
+    } else if (havePairOfUnits(lht, km3, rht, km)) {
+      // km3 / km => km2
+      return km2;
+    } else if (havePairOfUnits(lht, mm3, rht, mm)) {
+      // mm3 / mm => mm2
+      return mm2;
+    } else if (havePairOfUnits(lht, m3, rht, m2)) {
+      // m3 / m2 => m
+      return m;
+    } else if (havePairOfUnits(lht, km3, rht, km2)) {
+      // km3 / km2 => km
+      return km;
+    } else if (havePairOfUnits(lht, mm3, rht, mm2)) {
+      // mm3 / mm2 => mm
+      return mm;
+    } else if (havePairOfUnits(lht, m, rht, mPERs)) {
+      // m / mPERs => s
+      return s;
+    } else if (havePairOfUnits(lht, km, rht, kmPERh)) {
+      // km / kmPERh => h
+      return h;
+    } else if (havePairOfUnits(lht, mPERs, rht, s)) {
+      // mPERs / s = mPERs2
+      return mPERs2;
+    } else if (havePairOfUnits(lht, mPERs, rht, mPERs2)) {
+      // mPERs / mPERs2 => s  (velocity / acceleration == time)
+      return s;
+    } else if (UnitsRelationsTools.hasSpecificUnit(lht, N)) {
+      if (UnitsRelationsTools.hasSpecificUnit(rht, kg)) {
+        // N / kg => mPERs2
+        return mPERs2;
+      } else if (UnitsRelationsTools.hasSpecificUnit(rht, mPERs2)) {
+        // N / mPERs2 => kg
+        return kg;
+      }
+      return null;
+    } else if (UnitsRelationsTools.hasSpecificUnit(lht, kN)) {
+      if (UnitsRelationsTools.hasSpecificUnit(rht, t)) {
+        // kN / t => mPERs2
+        return mPERs2;
+      } else if (UnitsRelationsTools.hasSpecificUnit(rht, mPERs2)) {
+        // kN / mPERs2 => t
+        return t;
+      }
+      return null;
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Checks to see if both lhtPrefix and rhtPrefix have the same prefix as specificPrefix.
+   *
+   * @param lhtPrefix left hand side prefix
+   * @param rhtPrefix right hand side prefix
+   * @param specificPrefix specific desired prefix to match
+   * @return true if all 3 Prefix are the same, false otherwise
+   */
+  protected boolean bothHaveSpecificPrefix(
+      Prefix lhtPrefix, Prefix rhtPrefix, Prefix specificPrefix) {
+    if (lhtPrefix == null || rhtPrefix == null || specificPrefix == null) {
+      return false;
+    }
+
+    return lhtPrefix == rhtPrefix && rhtPrefix == specificPrefix;
+  }
+
+  /**
+   * Checks to see if lht has the unit ul and if rht has the unit ur all at the same time.
+   *
+   * @param lht left hand annotated type
+   * @param ul left hand unit
+   * @param rht right hand annotated type
+   * @param ur right hand unit
+   * @return true if lht has lu and rht has ru, false otherwise
+   */
+  protected boolean havePairOfUnits(
+      AnnotatedTypeMirror lht, AnnotationMirror ul, AnnotatedTypeMirror rht, AnnotationMirror ur) {
+    return UnitsRelationsTools.hasSpecificUnit(lht, ul)
+        && UnitsRelationsTools.hasSpecificUnit(rht, ur);
+  }
+
+  /**
+   * Checks to see if lht and rht have the pair of units u1 and u2 regardless of order.
+   *
+   * @param lht left hand annotated type
+   * @param u1 unit 1
+   * @param rht right hand annotated type
+   * @param u2 unit 2
+   * @return true if lht and rht have the pair of units u1 and u2 regardless of order, false
+   *     otherwise
+   */
+  protected boolean havePairOfUnitsIgnoringOrder(
+      AnnotatedTypeMirror lht, AnnotationMirror u1, AnnotatedTypeMirror rht, AnnotationMirror u2) {
+    return havePairOfUnits(lht, u1, rht, u2) || havePairOfUnits(lht, u2, rht, u1);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsTools.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsTools.java
new file mode 100644
index 0000000..a4238ef
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsTools.java
@@ -0,0 +1,317 @@
+package org.checkerframework.checker.units;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+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.ExecutableElement;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.checker.units.qual.Prefix;
+import org.checkerframework.checker.units.qual.UnknownUnits;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * A helper class for UnitsRelations, providing numerous methods which help process Annotations and
+ * Annotated Types representing various units.
+ */
+public class UnitsRelationsTools {
+
+  /**
+   * Creates an AnnotationMirror representing a unit defined by annoClass, with the specific Prefix
+   * p.
+   *
+   * @param env the Checker Processing Environment, provided as a parameter in init() of a
+   *     UnitsRelations implementation
+   * @param annoClass the fully-qualified name of an Annotation representing a Unit (eg m.class for
+   *     meters)
+   * @param p a Prefix value
+   * @return an AnnotationMirror of the Unit with the Prefix p, or null if it cannot be constructed
+   */
+  public static @Nullable AnnotationMirror buildAnnoMirrorWithSpecificPrefix(
+      final ProcessingEnvironment env,
+      final @FullyQualifiedName CharSequence annoClass,
+      final Prefix p) {
+    AnnotationBuilder builder = new AnnotationBuilder(env, annoClass);
+    builder.setValue("value", p);
+    return builder.build();
+  }
+
+  /**
+   * Creates an AnnotationMirror representing a unit defined by annoClass, with no prefix.
+   *
+   * @param env checker Processing Environment, provided as a parameter in init() of a
+   *     UnitsRelations implementation
+   * @param annoClass the getElementValueClassname of an Annotation representing a Unit (eg m.class
+   *     for meters)
+   * @return an AnnotationMirror of the Unit with no prefix, or null if it cannot be constructed
+   */
+  public static @Nullable AnnotationMirror buildAnnoMirrorWithNoPrefix(
+      final ProcessingEnvironment env, final @FullyQualifiedName CharSequence annoClass) {
+    return AnnotationBuilder.fromName(env.getElementUtils(), annoClass);
+  }
+
+  /**
+   * Retrieves the SI Prefix of an Annotated Type.
+   *
+   * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type
+   * @return a Prefix value (including Prefix.one), or null if it has none
+   */
+  public static @Nullable Prefix getPrefix(final AnnotatedTypeMirror annoType) {
+    Prefix result = null;
+
+    // go through each Annotation of an Annotated Type, find the prefix and return it
+    for (AnnotationMirror mirror : annoType.getAnnotations()) {
+      // try to get a prefix
+      result = getPrefix(mirror);
+      // if it is not null, then return the retrieved prefix immediately
+      if (result != null) {
+        return result;
+      }
+    }
+
+    // if it can't find any prefix at all, then return null
+    return result;
+  }
+
+  /**
+   * Retrieves the SI Prefix of an Annotation.
+   *
+   * @param unitsAnnotation an AnnotationMirror representing a Units Annotation
+   * @return a Prefix value (including Prefix.one), or null if it has none
+   */
+  public static @Nullable Prefix getPrefix(final AnnotationMirror unitsAnnotation) {
+    AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation);
+
+    // if this Annotation has no prefix, return null
+    if (hasNoPrefix(annotationValue)) {
+      return null;
+    }
+
+    // if the Annotation has a value, then detect and match the string name of the prefix, and
+    // return the matching Prefix
+    String prefixString = annotationValue.getValue().toString();
+    for (Prefix prefix : Prefix.values()) {
+      if (prefixString.equals(prefix.toString())) {
+        return prefix;
+      }
+    }
+
+    // if none of the strings match, then return null
+    return null;
+  }
+
+  /**
+   * Checks to see if an Annotated Type has no prefix.
+   *
+   * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type
+   * @return true if it has no prefix, false otherwise
+   */
+  public static boolean hasNoPrefix(final AnnotatedTypeMirror annoType) {
+    for (AnnotationMirror mirror : annoType.getAnnotations()) {
+      // if any Annotation has a prefix, return false
+      if (!hasNoPrefix(mirror)) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Checks to see if an Annotation has no prefix (ie, no value element).
+   *
+   * @param unitsAnnotation an AnnotationMirror representing a Units Annotation
+   * @return true if it has no prefix, false otherwise
+   */
+  public static boolean hasNoPrefix(final AnnotationMirror unitsAnnotation) {
+    AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation);
+    return hasNoPrefix(annotationValue);
+  }
+
+  private static boolean hasNoPrefix(final AnnotationValue annotationValue) {
+    // Annotation has no element value (ie no SI prefix)
+    if (annotationValue == null) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Given an Annotation, returns the prefix (eg kilo) as an AnnotationValue if there is any,
+   * otherwise returns null.
+   */
+  private static @Nullable AnnotationValue getAnnotationMirrorPrefix(
+      final AnnotationMirror unitsAnnotation) {
+    Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues =
+        unitsAnnotation.getElementValues();
+
+    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
+        elementValues.entrySet()) {
+      if (entry.getKey().getSimpleName().contentEquals("value")) {
+        return entry.getValue();
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Removes the prefix value from an Annotation, by constructing and returning a copy of its base
+   * SI unit's Annotation.
+   *
+   * @param elements the Element Utilities from a checker's processing environment, typically
+   *     obtained by calling env.getElementUtils() in init() of a Units Relations implementation
+   * @param unitsAnnotation an AnnotationMirror representing a Units Annotation
+   * @return the base SI Unit's AnnotationMirror, or null if the base SI Unit cannot be constructed
+   */
+  public static @Nullable AnnotationMirror removePrefix(
+      final Elements elements, final AnnotationMirror unitsAnnotation) {
+    if (hasNoPrefix(unitsAnnotation)) {
+      // Optimization, though the else case would also work.
+      return unitsAnnotation;
+    } else {
+      String unitsAnnoName = AnnotationUtils.annotationName(unitsAnnotation);
+      // In the Units Checker, the only annotation value is the prefix value.  Therefore,
+      // fromName (which creates an annotation with no values) is acceptable.
+      // TODO: refine sensitivity of removal for extension units, in case extension
+      // Annotations have more than just Prefix in its values.
+      return AnnotationBuilder.fromName(elements, unitsAnnoName);
+    }
+  }
+
+  /**
+   * Removes the Prefix value from an Annotated Type, by constructing and returning a copy of the
+   * Annotated Type without the prefix.
+   *
+   * @param elements the Element Utilities from a checker's processing environment, typically
+   *     obtained by calling env.getElementUtils() in init() of a Units Relations implementation
+   * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type
+   * @return a copy of the Annotated Type without the prefix
+   */
+  public static AnnotatedTypeMirror removePrefix(
+      final Elements elements, final AnnotatedTypeMirror annoType) {
+    // deep copy the Annotated Type Mirror without any of the Annotations
+    AnnotatedTypeMirror result = annoType.deepCopy(false);
+
+    // get all of the original Annotations in the Annotated Type
+    Set<AnnotationMirror> annos = annoType.getAnnotations();
+
+    // loop through all the Annotations to see if they use Prefix.one, remove Prefix.one if it does
+    for (AnnotationMirror anno : annos) {
+      // try to clean the Annotation Mirror of the Prefix
+      AnnotationMirror cleanedMirror = removePrefix(elements, anno);
+      // if successful, add the cleaned annotation to the deep copy
+      if (cleanedMirror != null) {
+        result.addAnnotation(cleanedMirror);
+      }
+      // if unsuccessful, add the original annotation
+      else {
+        result.addAnnotation(anno);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks to see if a particular Annotated Type has no units, such as scalar constants in
+   * calculations.
+   *
+   * <p>Any number that isn't assigned a unit will automatically get the Annotation UnknownUnits.
+   * eg: int x = 5; // x has @UnknownUnits
+   *
+   * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type
+   * @return true if the Type has no units, false otherwise
+   */
+  public static boolean hasNoUnits(final AnnotatedTypeMirror annoType) {
+    return (annoType.getAnnotation(UnknownUnits.class) != null);
+  }
+
+  /**
+   * Checks to see if a particular Annotated Type has a specific unit (represented by its
+   * Annotation).
+   *
+   * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type
+   * @param unitsAnnotation an AnnotationMirror representing a Units Annotation of a specific unit
+   * @return true if the Type has the specific unit, false otherwise
+   */
+  public static boolean hasSpecificUnit(
+      final AnnotatedTypeMirror annoType, final AnnotationMirror unitsAnnotation) {
+    return AnnotationUtils.containsSame(annoType.getAnnotations(), unitsAnnotation);
+  }
+
+  /**
+   * Checks to see if a particular Annotated Type has a particular base unit (represented by its
+   * Annotation).
+   *
+   * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type
+   * @param unitsAnnotation an AnnotationMirror representing a Units Annotation of the base unit
+   * @return true if the Type has the specific unit, false otherwise
+   */
+  public static boolean hasSpecificUnitIgnoringPrefix(
+      final AnnotatedTypeMirror annoType, final AnnotationMirror unitsAnnotation) {
+    return AnnotationUtils.containsSameByName(annoType.getAnnotations(), unitsAnnotation);
+  }
+
+  /**
+   * Creates an AnnotationMirror representing a unit defined by annoClass, with the specific Prefix
+   * p.
+   *
+   * <p>This interface is intended only for subclasses of UnitsRelations; other clients should use
+   * {@link #buildAnnoMirrorWithSpecificPrefix(ProcessingEnvironment, CharSequence, Prefix)}
+   *
+   * @param env the Checker Processing Environment, provided as a parameter in init() of a
+   *     UnitsRelations implementation
+   * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters)
+   * @param p a Prefix value
+   * @return an AnnotationMirror of the Unit with the Prefix p, or null if it cannot be constructed
+   */
+  public static @Nullable AnnotationMirror buildAnnoMirrorWithSpecificPrefix(
+      final ProcessingEnvironment env,
+      final Class<? extends Annotation> annoClass,
+      final Prefix p) {
+    AnnotationBuilder builder = new AnnotationBuilder(env, annoClass);
+    builder.setValue("value", p);
+    return builder.build();
+  }
+
+  /**
+   * Creates an AnnotationMirror representing a unit defined by annoClass, with the default Prefix
+   * of {@code Prefix.one}.
+   *
+   * <p>This interface is intended only for subclasses of UnitsRelations; other clients should not
+   * use it.
+   *
+   * @param env the Checker Processing Environment, provided as a parameter in init() of a
+   *     UnitsRelations implementation
+   * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters)
+   * @return an AnnotationMirror of the Unit with Prefix.one, or null if it cannot be constructed
+   */
+  public static @Nullable AnnotationMirror buildAnnoMirrorWithDefaultPrefix(
+      final ProcessingEnvironment env, final Class<? extends Annotation> annoClass) {
+    return buildAnnoMirrorWithSpecificPrefix(env, annoClass, Prefix.one);
+  }
+
+  /**
+   * Creates an AnnotationMirror representing a unit defined by annoClass, with no prefix.
+   *
+   * <p>This interface is intended only for subclasses of UnitsRelations; other clients should use
+   * {@link #buildAnnoMirrorWithNoPrefix(ProcessingEnvironment, CharSequence)}.
+   *
+   * @param env checker Processing Environment, provided as a parameter in init() of a
+   *     UnitsRelations implementation
+   * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters)
+   * @return an AnnotationMirror of the Unit with no prefix, or null if it cannot be constructed
+   */
+  static @Nullable AnnotationMirror buildAnnoMirrorWithNoPrefix(
+      final ProcessingEnvironment env, final Class<? extends Annotation> annoClass) {
+    return AnnotationBuilder.fromClass(env.getElementUtils(), annoClass);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsVisitor.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsVisitor.java
new file mode 100644
index 0000000..eae5199
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsVisitor.java
@@ -0,0 +1,41 @@
+package org.checkerframework.checker.units;
+
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.Tree.Kind;
+import org.checkerframework.checker.units.qual.UnknownUnits;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/**
+ * Units visitor.
+ *
+ * <p>Ensure consistent use of compound assignments.
+ */
+public class UnitsVisitor extends BaseTypeVisitor<UnitsAnnotatedTypeFactory> {
+  public UnitsVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
+    ExpressionTree var = node.getVariable();
+    ExpressionTree expr = node.getExpression();
+    AnnotatedTypeMirror varType = atypeFactory.getAnnotatedType(var);
+    AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(expr);
+
+    Kind kind = node.getKind();
+
+    if ((kind == Kind.PLUS_ASSIGNMENT || kind == Kind.MINUS_ASSIGNMENT)) {
+      if (!atypeFactory.getTypeHierarchy().isSubtype(exprType, varType)) {
+        checker.reportError(node, "compound.assignment", varType, exprType);
+      }
+    } else if (exprType.getAnnotation(UnknownUnits.class) == null) {
+      // Only allow mul/div with unqualified units
+      checker.reportError(node, "compound.assignment", varType, exprType);
+    }
+
+    return null; // super.visitCompoundAssignment(node, p);
+  }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/units/qual/README b/checker/src/main/java/org/checkerframework/checker/units/qual/README
new file mode 100644
index 0000000..2efa3e9
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/units/qual/README
@@ -0,0 +1,2 @@
+If you add a new unit qualifier to this package, make sure
+to also modify the UnitsAnnotatedTypeFactory accordingly.
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsAutoValueTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsAutoValueTest.java
new file mode 100644
index 0000000..8b5623e
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsAutoValueTest.java
@@ -0,0 +1,34 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.checker.calledmethods.CalledMethodsChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Test case for Called Methods Checker's AutoValue support. */
+public class CalledMethodsAutoValueTest extends CheckerFrameworkPerDirectoryTest {
+
+  public CalledMethodsAutoValueTest(List<File> testFiles) {
+    super(
+        testFiles,
+        Arrays.asList(
+            "com.google.auto.value.extension.memoized.processor.MemoizedValidator",
+            "com.google.auto.value.processor.AutoAnnotationProcessor",
+            "com.google.auto.value.processor.AutoOneOfProcessor",
+            "com.google.auto.value.processor.AutoValueBuilderProcessor",
+            "com.google.auto.value.processor.AutoValueProcessor",
+            CalledMethodsChecker.class.getName()),
+        "calledmethods-autovalue",
+        Collections.emptyList(),
+        "-Anomsgtext",
+        "-nowarn");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"calledmethods-autovalue"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableReturnsReceiverTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableReturnsReceiverTest.java
new file mode 100644
index 0000000..0293c70
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableReturnsReceiverTest.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.calledmethods.CalledMethodsChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Basic tests for the Called Methods Checker. */
+public class CalledMethodsDisableReturnsReceiverTest extends CheckerFrameworkPerDirectoryTest {
+  public CalledMethodsDisableReturnsReceiverTest(List<File> testFiles) {
+    super(
+        testFiles,
+        CalledMethodsChecker.class,
+        "calledmethods-disablereturnsreceiver",
+        "-Anomsgtext",
+        "-AdisableReturnsReceiver",
+        "-encoding",
+        "UTF-8");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"calledmethods-disablereturnsreceiver"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableframeworksTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableframeworksTest.java
new file mode 100644
index 0000000..29db674
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableframeworksTest.java
@@ -0,0 +1,37 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.checker.calledmethods.CalledMethodsChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class CalledMethodsDisableframeworksTest extends CheckerFrameworkPerDirectoryTest {
+
+  public CalledMethodsDisableframeworksTest(List<File> testFiles) {
+    super(
+        testFiles,
+        Arrays.asList(
+            "com.google.auto.value.extension.memoized.processor.MemoizedValidator",
+            "com.google.auto.value.processor.AutoAnnotationProcessor",
+            "com.google.auto.value.processor.AutoOneOfProcessor",
+            "com.google.auto.value.processor.AutoValueBuilderProcessor",
+            "com.google.auto.value.processor.AutoValueProcessor",
+            CalledMethodsChecker.class.getName()),
+        "calledmethods-disableframeworks",
+        Collections.emptyList(),
+        "-Anomsgtext",
+        "-AdisableBuilderFrameworkSupports=autovalue,lombok",
+        // The next option is so that we can run the usevaluechecker tests under this
+        // configuration.
+        "-ACalledMethodsChecker_useValueChecker",
+        "-nowarn");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"calledmethods-disableframeworks", "calledmethods-usevaluechecker"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsLombokTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsLombokTest.java
new file mode 100644
index 0000000..f0e2bc9
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsLombokTest.java
@@ -0,0 +1,25 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.calledmethods.CalledMethodsChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Test that the Called Methods Checker's support for Lombok works correctly. */
+public class CalledMethodsLombokTest extends CheckerFrameworkPerDirectoryTest {
+  public CalledMethodsLombokTest(List<File> testFiles) {
+    super(
+        testFiles,
+        CalledMethodsChecker.class,
+        "calledmethods-delomboked",
+        "-Anomsgtext",
+        "-nowarn",
+        "-AsuppressWarnings=type.anno.before.modifier");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"calledmethods-delomboked"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsTest.java
new file mode 100644
index 0000000..fb0e679
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsTest.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.calledmethods.CalledMethodsChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Basic tests for the Called Methods Checker. */
+public class CalledMethodsTest extends CheckerFrameworkPerDirectoryTest {
+  public CalledMethodsTest(List<File> testFiles) {
+    super(
+        testFiles,
+        CalledMethodsChecker.class,
+        "calledmethods",
+        "-Anomsgtext",
+        "-nowarn",
+        "-encoding",
+        "UTF-8");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"calledmethods"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsUseValueCheckerTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsUseValueCheckerTest.java
new file mode 100644
index 0000000..9a7eac5
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsUseValueCheckerTest.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.calledmethods.CalledMethodsChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized;
+
+public class CalledMethodsUseValueCheckerTest extends CheckerFrameworkPerDirectoryTest {
+  public CalledMethodsUseValueCheckerTest(List<File> testFiles) {
+    super(
+        testFiles,
+        CalledMethodsChecker.class,
+        "calledmethods-usevaluechecker",
+        "-Anomsgtext",
+        "-AuseValueChecker",
+        "-nowarn");
+  }
+
+  @Parameterized.Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"calledmethods-usevaluechecker"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CompilerMessagesTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CompilerMessagesTest.java
new file mode 100644
index 0000000..d450ba5
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CompilerMessagesTest.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Compiler Messages Checker. Depends on the compiler.properties file. */
+public class CompilerMessagesTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a CompilerMessagesTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public CompilerMessagesTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.compilermsgs.CompilerMessagesChecker.class,
+        "compilermsg",
+        "-Anomsgtext",
+        "-Apropfiles=tests/compilermsg/compiler.properties");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"compilermsg", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FenumSwingTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FenumSwingTest.java
new file mode 100644
index 0000000..3dbd0d8
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FenumSwingTest.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class FenumSwingTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a FenumSwingTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public FenumSwingTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.fenum.FenumChecker.class,
+        "fenum",
+        "-Anomsgtext",
+        "-Aquals=org.checkerframework.checker.fenum.qual.SwingVerticalOrientation,org.checkerframework.checker.fenum.qual.SwingHorizontalOrientation,org.checkerframework.checker.fenum.qual.SwingBoxOrientation,org.checkerframework.checker.fenum.qual.SwingCompassDirection,org.checkerframework.checker.fenum.qual.SwingElementOrientation,org.checkerframework.checker.fenum.qual.SwingTextOrientation");
+    // TODO: check all qualifiers
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"fenumswing", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FenumTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FenumTest.java
new file mode 100644
index 0000000..0c3d427
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FenumTest.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class FenumTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a FenumTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public FenumTest(List<File> testFiles) {
+    super(testFiles, org.checkerframework.checker.fenum.FenumChecker.class, "fenum", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"fenum", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterLubGlbCheckerTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterLubGlbCheckerTest.java
new file mode 100644
index 0000000..b6af2eb
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterLubGlbCheckerTest.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.test.junit;
+
+// Test case for issue 691.
+// https://github.com/typetools/checker-framework/issues/691
+// This exists to just run the FormatterLubGlbChecker.
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.testchecker.lubglb.FormatterLubGlbChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class FormatterLubGlbCheckerTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a FormatterLubGlbCheckerTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public FormatterLubGlbCheckerTest(List<File> testFiles) {
+    super(testFiles, FormatterLubGlbChecker.class, "", "-Anomsgtext", "-AcheckPurityAnnotations");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"formatter-lubglb"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterTest.java
new file mode 100644
index 0000000..eeda972
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterTest.java
@@ -0,0 +1,26 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class FormatterTest extends CheckerFrameworkPerDirectoryTest {
+  /**
+   * Create a FormatterTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public FormatterTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.formatter.FormatterChecker.class,
+        "formatter",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"formatter", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUncheckedDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUncheckedDefaultsTest.java
new file mode 100644
index 0000000..d09f9a0
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUncheckedDefaultsTest.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class FormatterUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest {
+  /**
+   * Create a FormatterUncheckedDefaultsTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public FormatterUncheckedDefaultsTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.formatter.FormatterChecker.class,
+        "formatter",
+        "-Anomsgtext",
+        "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"formatter-unchecked-defaults"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUnitTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUnitTest.java
new file mode 100644
index 0000000..fd179ea
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUnitTest.java
@@ -0,0 +1,45 @@
+package org.checkerframework.checker.test.junit;
+
+import org.checkerframework.checker.formatter.util.FormatUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class FormatterUnitTest {
+
+  @SuppressWarnings("deprecation") // calls methods that are used only for testing
+  @Test
+  public void testConversionCharFromFormat() {
+    Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%1$2s"));
+    Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%1$s"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$tb"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$te"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$tm"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$tY"));
+    Assert.assertEquals('f', FormatUtil.conversionCharFromFormat("%+10.4f"));
+    Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%2$2s"));
+    Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%2$s"));
+    Assert.assertEquals('f', FormatUtil.conversionCharFromFormat("%(,.2f"));
+    Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%3$2s"));
+    Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%3$s"));
+    Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%4$2s"));
+    Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%4$s"));
+    Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%<s"));
+    Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%s"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%ta"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%tb"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%tc"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%td"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%<te"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%tH"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%tI"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%tm"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%tM"));
+    Assert.assertEquals('T', FormatUtil.conversionCharFromFormat("%Tp"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%tS"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%tT"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%ty"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%<tY"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%tY"));
+    Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%tZ"));
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/GuiEffectTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/GuiEffectTest.java
new file mode 100644
index 0000000..73b59ec
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/GuiEffectTest.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class GuiEffectTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a GuiEffectTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public GuiEffectTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.guieffect.GuiEffectChecker.class,
+        "guieffect",
+        "-Anomsgtext");
+    // , "-Alint=debugSpew");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"guieffect", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterLubGlbCheckerTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterLubGlbCheckerTest.java
new file mode 100644
index 0000000..807c9b1
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterLubGlbCheckerTest.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.test.junit;
+
+// Test case for issue 723.
+// https://github.com/typetools/checker-framework/issues/723
+// This exists to just run the I18nFormatterLubGlbChecker.
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.testchecker.lubglb.I18nFormatterLubGlbChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class I18nFormatterLubGlbCheckerTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create an I18nFormatterLubGlbCheckerTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public I18nFormatterLubGlbCheckerTest(List<File> testFiles) {
+    super(
+        testFiles, I18nFormatterLubGlbChecker.class, "", "-Anomsgtext", "-AcheckPurityAnnotations");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"i18n-formatter-lubglb"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterTest.java
new file mode 100644
index 0000000..f4c1f6e
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterTest.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class I18nFormatterTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create an I18nFormatterTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public I18nFormatterTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.i18nformatter.I18nFormatterChecker.class,
+        "i18n-formatter",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"i18n-formatter", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUncheckedDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUncheckedDefaultsTest.java
new file mode 100644
index 0000000..2cc553a
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUncheckedDefaultsTest.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class I18nFormatterUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create an I18nFormatterUncheckedDefaultsTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public I18nFormatterUncheckedDefaultsTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.i18nformatter.I18nFormatterChecker.class,
+        "i18n-formatter",
+        "-Anomsgtext",
+        "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"i18n-formatter-unchecked-defaults"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUnitTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUnitTest.java
new file mode 100644
index 0000000..8f59efb
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUnitTest.java
@@ -0,0 +1,141 @@
+package org.checkerframework.checker.test.junit;
+
+import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
+import org.checkerframework.checker.i18nformatter.util.I18nFormatUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class I18nFormatterUnitTest {
+
+  @Test
+  public void stringToI18nConversionCategoryTest() {
+    Assert.assertEquals(
+        I18nConversionCategory.NUMBER,
+        I18nConversionCategory.stringToI18nConversionCategory("number"));
+    Assert.assertEquals(
+        I18nConversionCategory.NUMBER,
+        I18nConversionCategory.stringToI18nConversionCategory("nuMber"));
+    Assert.assertEquals(
+        I18nConversionCategory.NUMBER,
+        I18nConversionCategory.stringToI18nConversionCategory("choice"));
+    Assert.assertEquals(
+        I18nConversionCategory.DATE, I18nConversionCategory.stringToI18nConversionCategory("TIME"));
+    Assert.assertEquals(
+        I18nConversionCategory.DATE, I18nConversionCategory.stringToI18nConversionCategory("DatE"));
+    Assert.assertEquals(
+        I18nConversionCategory.DATE, I18nConversionCategory.stringToI18nConversionCategory("date"));
+  }
+
+  @Test
+  public void isSubsetTest() {
+
+    Assert.assertTrue(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.UNUSED, I18nConversionCategory.UNUSED));
+    Assert.assertFalse(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.UNUSED, I18nConversionCategory.GENERAL));
+    Assert.assertFalse(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.UNUSED, I18nConversionCategory.DATE));
+    Assert.assertFalse(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER));
+
+    Assert.assertTrue(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.GENERAL, I18nConversionCategory.UNUSED));
+    Assert.assertTrue(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.GENERAL, I18nConversionCategory.GENERAL));
+    Assert.assertFalse(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.GENERAL, I18nConversionCategory.DATE));
+    Assert.assertFalse(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER));
+
+    Assert.assertTrue(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.DATE, I18nConversionCategory.UNUSED));
+    Assert.assertTrue(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.DATE, I18nConversionCategory.GENERAL));
+    Assert.assertTrue(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.DATE, I18nConversionCategory.DATE));
+    Assert.assertFalse(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.DATE, I18nConversionCategory.NUMBER));
+
+    Assert.assertTrue(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.NUMBER, I18nConversionCategory.UNUSED));
+    Assert.assertTrue(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.NUMBER, I18nConversionCategory.GENERAL));
+    Assert.assertTrue(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.NUMBER, I18nConversionCategory.DATE));
+    Assert.assertTrue(
+        I18nConversionCategory.isSubsetOf(
+            I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER));
+  }
+
+  @Test
+  public void hasFormatTest() {
+    Assert.assertTrue(I18nFormatUtil.hasFormat("{0}", I18nConversionCategory.GENERAL));
+    Assert.assertTrue(I18nFormatUtil.hasFormat("{0, date}", I18nConversionCategory.DATE));
+    Assert.assertTrue(
+        I18nFormatUtil.hasFormat(
+            "{1} {0, date}", I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER));
+    Assert.assertTrue(
+        I18nFormatUtil.hasFormat(
+            "{0} and {1,number}", I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER));
+    Assert.assertTrue(
+        I18nFormatUtil.hasFormat(
+            "{1, number}", I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER));
+    Assert.assertTrue(
+        I18nFormatUtil.hasFormat(
+            "{1, date}", I18nConversionCategory.UNUSED, I18nConversionCategory.DATE));
+    Assert.assertTrue(
+        I18nFormatUtil.hasFormat(
+            "{2}",
+            I18nConversionCategory.UNUSED,
+            I18nConversionCategory.UNUSED,
+            I18nConversionCategory.NUMBER));
+    Assert.assertTrue(
+        I18nFormatUtil.hasFormat(
+            "{3, number} {0} {1, time}",
+            I18nConversionCategory.GENERAL,
+            I18nConversionCategory.DATE,
+            I18nConversionCategory.UNUSED,
+            I18nConversionCategory.NUMBER));
+    Assert.assertTrue(
+        I18nFormatUtil.hasFormat(
+            "{0} {1, date} {2, time} {3, number} {5}",
+            I18nConversionCategory.GENERAL,
+            I18nConversionCategory.DATE,
+            I18nConversionCategory.DATE,
+            I18nConversionCategory.NUMBER,
+            I18nConversionCategory.UNUSED,
+            I18nConversionCategory.GENERAL));
+    Assert.assertTrue(
+        I18nFormatUtil.hasFormat(
+            "{1} {1, date}", I18nConversionCategory.UNUSED, I18nConversionCategory.DATE));
+    Assert.assertTrue(
+        I18nFormatUtil.hasFormat(
+            "{1, number} {1, date}", I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER));
+    Assert.assertTrue(I18nFormatUtil.hasFormat("{0, date} {0, date}", I18nConversionCategory.DATE));
+
+    Assert.assertFalse(I18nFormatUtil.hasFormat("{1}", I18nConversionCategory.GENERAL));
+    Assert.assertFalse(I18nFormatUtil.hasFormat("{0, number}", I18nConversionCategory.DATE));
+    Assert.assertFalse(I18nFormatUtil.hasFormat("{0, number}", I18nConversionCategory.GENERAL));
+    Assert.assertFalse(I18nFormatUtil.hasFormat("{0, date}", I18nConversionCategory.GENERAL));
+    Assert.assertFalse(
+        I18nFormatUtil.hasFormat(
+            "{0, date}", I18nConversionCategory.DATE, I18nConversionCategory.DATE));
+    Assert.assertFalse(
+        I18nFormatUtil.hasFormat("{0, date} {1, date}", I18nConversionCategory.DATE));
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nTest.java
new file mode 100644
index 0000000..4514bb0
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nTest.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class I18nTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create an I18nTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public I18nTest(List<File> testFiles) {
+    super(testFiles, org.checkerframework.checker.i18n.I18nChecker.class, "i18n", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"i18n", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nUncheckedDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nUncheckedDefaultsTest.java
new file mode 100644
index 0000000..fca1179
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nUncheckedDefaultsTest.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class I18nUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create an I18nUncheckedDefaultsTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public I18nUncheckedDefaultsTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.i18n.I18nChecker.class,
+        "i18n",
+        "-Anomsgtext",
+        "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"i18n-unchecked-defaults"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/IndexTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/IndexTest.java
new file mode 100644
index 0000000..e93f545
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/IndexTest.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Index Checker. */
+public class IndexTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create an IndexTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public IndexTest(List<File> testFiles) {
+    super(testFiles, org.checkerframework.checker.index.IndexChecker.class, "index", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"index", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/InterningTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/InterningTest.java
new file mode 100644
index 0000000..44dd687
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/InterningTest.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Interning Checker, which tests the Interned annotation. */
+public class InterningTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create an InterningTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public InterningTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.interning.InterningChecker.class,
+        "interning",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"interning", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/LockSafeDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/LockSafeDefaultsTest.java
new file mode 100644
index 0000000..9f1b0cb
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/LockSafeDefaultsTest.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Lock checker when using safe defaults for unchecked source code. */
+public class LockSafeDefaultsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a LockSafeDefaultsTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public LockSafeDefaultsTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.lock.LockChecker.class,
+        "lock",
+        "-AuseConservativeDefaultsForUncheckedCode=source",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"lock-safedefaults"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/LockTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/LockTest.java
new file mode 100644
index 0000000..251c013
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/LockTest.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class LockTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a LockTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public LockTest(List<File> testFiles) {
+    super(testFiles, org.checkerframework.checker.lock.LockChecker.class, "lock", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"lock", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallTest.java
new file mode 100644
index 0000000..f3f875f
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallTest.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class MustCallTest extends CheckerFrameworkPerDirectoryTest {
+  public MustCallTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.mustcall.MustCallChecker.class,
+        "mustcall",
+        "-Anomsgtext",
+        // "-AstubDebug");
+        "-nowarn");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"mustcall"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NestedAggregateCheckerTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NestedAggregateCheckerTest.java
new file mode 100644
index 0000000..0155fa3
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NestedAggregateCheckerTest.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.test.junit;
+
+// Test case for issue 343.
+// https://github.com/typetools/checker-framework/issues/343
+// This exists to just run the NestedAggregateChecker.
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.testchecker.NestedAggregateChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class NestedAggregateCheckerTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NestedAggregateCheckerTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NestedAggregateCheckerTest(List<File> testFiles) {
+    super(testFiles, NestedAggregateChecker.class, "", "-Anomsgtext", "-AcheckPurityAnnotations");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"aggregate", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NoLightweightOwnershipTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NoLightweightOwnershipTest.java
new file mode 100644
index 0000000..a1190ed
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NoLightweightOwnershipTest.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class NoLightweightOwnershipTest extends CheckerFrameworkPerDirectoryTest {
+  public NoLightweightOwnershipTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.mustcall.MustCallChecker.class,
+        "nolightweightownership",
+        "-Anomsgtext",
+        "-AnoLightweightOwnership",
+        // "-AstubDebug");
+        "-nowarn");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nolightweightownership"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssertsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssertsTest.java
new file mode 100644
index 0000000..c762074
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssertsTest.java
@@ -0,0 +1,36 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness checker. */
+public class NullnessAssertsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessAssertsTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessAssertsTest(List<File> testFiles) {
+    // TODO: remove soundArrayCreationNullness option once it's no
+    // longer needed.  See issue #986:
+    // https://github.com/typetools/checker-framework/issues/986
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-AcheckPurityAnnotations",
+        "-AassumeAssertionsAreEnabled",
+        "-Anomsgtext",
+        "-Xlint:deprecation",
+        "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON);
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-asserts"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeAssertionsAreDisabledTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeAssertionsAreDisabledTest.java
new file mode 100644
index 0000000..5cf302c
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeAssertionsAreDisabledTest.java
@@ -0,0 +1,30 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness Checker. */
+public class NullnessAssumeAssertionsAreDisabledTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessAssumeAssertionsAreDisabledTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessAssumeAssertionsAreDisabledTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-AassumeAssertionsAreDisabled",
+        "-Anomsgtext",
+        "-Xlint:deprecation");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-assumeassertions"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeKeyForTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeKeyForTest.java
new file mode 100644
index 0000000..f6502c5
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeKeyForTest.java
@@ -0,0 +1,36 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness checker. */
+public class NullnessAssumeKeyForTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessAssumeKeyForTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessAssumeKeyForTest(List<File> testFiles) {
+    // TODO: remove soundArrayCreationNullness option once it's no
+    // longer needed.  See issue #986:
+    // https://github.com/typetools/checker-framework/issues/986
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-AcheckPurityAnnotations",
+        "-AassumeKeyFor",
+        "-Anomsgtext",
+        "-Xlint:deprecation",
+        "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON);
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-assumekeyfor"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessCheckCastElementTypeTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessCheckCastElementTypeTest.java
new file mode 100644
index 0000000..bab6b7f
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessCheckCastElementTypeTest.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness checker when checkCastElementType is used. */
+public class NullnessCheckCastElementTypeTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessCheckCastElementTypeTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessCheckCastElementTypeTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-AcheckCastElementType",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-checkcastelementtype"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessConcurrentTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessConcurrentTest.java
new file mode 100644
index 0000000..a391156
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessConcurrentTest.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness checker when running with concurrent semantics. */
+public class NullnessConcurrentTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessConcurrentTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessConcurrentTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-AconcurrentSemantics",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-concurrent-semantics"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java
new file mode 100644
index 0000000..b9afc86
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java
@@ -0,0 +1,66 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.test.TestConfiguration;
+import org.checkerframework.framework.test.TestConfigurationBuilder;
+import org.checkerframework.framework.test.TestUtilities;
+import org.checkerframework.framework.test.TypecheckExecutor;
+import org.checkerframework.framework.test.TypecheckResult;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness checker for issue #511. */
+public class NullnessGenericWildcardTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessGenericWildcardTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessGenericWildcardTest(List<File> testFiles) {
+    super(
+        testFiles,
+        NullnessChecker.class,
+        "nullness",
+        // This test reads bytecode .class files created by NullnessGenericWildcardLibTest
+        "-cp",
+        "dist/checker.jar:tests/build/testclasses/",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-genericwildcard"};
+  }
+
+  @Override
+  public void run() {
+    boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo();
+    List<String> customizedOptions1 = customizeOptions(Arrays.asList("-Anomsgtext"));
+    TestConfiguration config1 =
+        TestConfigurationBuilder.buildDefaultConfiguration(
+            "tests/nullness-genericwildcardlib",
+            new File("tests/nullness-genericwildcardlib", "GwiParent.java"),
+            NullnessChecker.class,
+            customizedOptions1,
+            shouldEmitDebugInfo);
+    TypecheckResult testResult1 = new TypecheckExecutor().runTest(config1);
+    TestUtilities.assertTestDidNotFail(testResult1);
+
+    List<String> customizedOptions2 =
+        customizeOptions(Collections.unmodifiableList(checkerOptions));
+    TestConfiguration config2 =
+        TestConfigurationBuilder.buildDefaultConfiguration(
+            testDir,
+            testFiles,
+            Collections.singleton(NullnessChecker.class.getName()),
+            customizedOptions2,
+            shouldEmitDebugInfo);
+    TypecheckResult testResult2 = new TypecheckExecutor().runTest(config2);
+    TestUtilities.assertTestDidNotFail(testResult2);
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessInvariantArraysTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessInvariantArraysTest.java
new file mode 100644
index 0000000..bd7d027
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessInvariantArraysTest.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness checker when array subtyping is invariant. */
+public class NullnessInvariantArraysTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessInvariantArraysTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessInvariantArraysTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-AinvariantArrays",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-invariantarrays"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJSpecifySamplesTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJSpecifySamplesTest.java
new file mode 100644
index 0000000..6f370c6
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJSpecifySamplesTest.java
@@ -0,0 +1,145 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.test.TypecheckResult;
+import org.checkerframework.framework.test.diagnostics.DiagnosticKind;
+import org.checkerframework.framework.test.diagnostics.TestDiagnostic;
+import org.checkerframework.javacutil.BugInCF;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * JUnit tests for the Nullness Checker -- test the JSpecify samples.
+ *
+ * <p>Requirements:
+ *
+ * <ul>
+ *   <li>Java 9 or later
+ *   <li>Clone and build https://github.com/jspecify/jspecify in a sibling directory of the Checker
+ *       Framework.
+ * </ul>
+ *
+ * To run this test:
+ *
+ * <pre>{@code
+ * ./gradlew :checker:NullnessJSpecifySamples
+ * }</pre>
+ */
+public class NullnessJSpecifySamplesTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessJSpecifySamplesTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessJSpecifySamplesTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "../../../jspecify/samples",
+        Collections.singletonList("../../jspecify/build/libs/jspecify-0.1.0-SNAPSHOT.jar"),
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"../../../jspecify/samples"};
+  }
+
+  @Override
+  public TypecheckResult adjustTypecheckResult(TypecheckResult testResult) {
+    // The "all*" variables are a copy that contains everything.
+    // This method removes from the non-all* variables.
+    // These are JSpecify diagnostics.
+    List<TestDiagnostic> missingDiagnostics = testResult.getMissingDiagnostics();
+    List<TestDiagnostic> allMissingDiagnostics =
+        Collections.unmodifiableList(new ArrayList<>(missingDiagnostics));
+    // These are Checker Framework diagnostics.
+    List<TestDiagnostic> unexpectedDiagnostics = testResult.getUnexpectedDiagnostics();
+    List<TestDiagnostic> allUnexpectedDiagnostics =
+        Collections.unmodifiableList(new ArrayList<>(unexpectedDiagnostics));
+
+    for (TestDiagnostic missing : allMissingDiagnostics) {
+      unexpectedDiagnostics.removeIf(unexpected -> matches(missing, unexpected));
+    }
+    for (TestDiagnostic unexpected : allUnexpectedDiagnostics) {
+      missingDiagnostics.removeIf(missing -> matches(missing, unexpected));
+    }
+    missingDiagnostics.removeIf(
+        missing -> missing.getMessage().equals("jspecify_unrecognized_location"));
+    missingDiagnostics.removeIf(
+        missing -> missing.getMessage().equals("jspecify_nullness_not_enough_information"));
+    missingDiagnostics.removeIf(
+        // Currently, the only conflict involves @NullnessUnspecified.
+        missing -> missing.getMessage().equals("jspecify_conflicting_annotations"));
+
+    return testResult;
+  }
+
+  /**
+   * Returns true if {@code cfDiagnostic} being issued fulfils the expectation that {@code
+   * jspecifyDiagnostic} should be issued.
+   *
+   * @param jspecifyDiagnostic an expected JSpecify diagnostic
+   * @param cfDiagnostic an actual javacdiagnostic
+   * @return true if {@code actual} fulfills an expectation to see {@code expected}
+   */
+  private static boolean matches(TestDiagnostic jspecifyDiagnostic, TestDiagnostic cfDiagnostic) {
+    assert jspecifyDiagnostic.getKind() == DiagnosticKind.JSpecify
+        : "bad JSpecify diagnostic " + jspecifyDiagnostic;
+    assert cfDiagnostic.getKind() != DiagnosticKind.JSpecify : "bad CF diagnostic " + cfDiagnostic;
+
+    if (!(jspecifyDiagnostic.getFilename().equals(cfDiagnostic.getFilename())
+        && jspecifyDiagnostic.getLineNumber() == cfDiagnostic.getLineNumber())) {
+      return false;
+    }
+
+    // The JSpecify diagnostics are documented at
+    // https://github.com/jspecify/jspecify/blob/main/samples/README.md#syntax .
+    switch (jspecifyDiagnostic.getMessage()) {
+      case "jspecify_conflicting_annotations":
+        throw new BugInCF("jspecifyMatches(%s, %s)", jspecifyDiagnostic, cfDiagnostic);
+
+      case "jspecify_unrecognized_location":
+        return true;
+
+      case "jspecify_nullness_intrinsically_not_nullable":
+        switch (cfDiagnostic.getMessage()) {
+          case "nullness.on.constructor":
+          case "nullness.on.enum":
+          case "nullness.on.outer":
+          case "nullness.on.primitive":
+          case "nullness.on.receiver":
+          case "nullness.on.supertype":
+            return true;
+          default:
+            return false;
+        }
+
+      case "jspecify_nullness_mismatch":
+      case "jspecify_nullness_not_enough_information":
+        switch (cfDiagnostic.getMessage()) {
+          case "argument":
+          case "assignment":
+          case "condition.nullable":
+          case "dereference.of.nullable":
+          case "initialization.field.uninitialized":
+          case "locking.nullable":
+          case "override.param":
+          case "override.return":
+          case "return":
+          case "type.argument":
+          case "unboxing.of.nullable":
+            return true;
+          default:
+            return false;
+        }
+
+      default:
+        throw new BugInCF("Unexpected JSpecify diagnostic: " + jspecifyDiagnostic.getMessage());
+    }
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavacErrorsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavacErrorsTest.java
new file mode 100644
index 0000000..1c0bca4
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavacErrorsTest.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerFileTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness checker that issue javac errors. */
+public class NullnessJavacErrorsTest extends CheckerFrameworkPerFileTest {
+
+  public NullnessJavacErrorsTest(File testFile) {
+    // TODO: remove soundArrayCreationNullness option once it's no
+    // longer needed.  See issue #986:
+    // https://github.com/typetools/checker-framework/issues/986
+    super(
+        testFile,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-AcheckPurityAnnotations",
+        "-Anomsgtext",
+        "-Xlint:deprecation",
+        "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON);
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-javac-errors"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavadocTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavadocTest.java
new file mode 100644
index 0000000..e17cba2
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavadocTest.java
@@ -0,0 +1,43 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.javacutil.SystemUtil;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * JUnit tests for the Nullness Checker -- testing type-checking of code that uses Javadoc classes.
+ */
+public class NullnessJavadocTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public NullnessJavadocTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        toolsJarList(),
+        "-Anomsgtext");
+  }
+
+  /**
+   * Return a list that contains the pathname to the tools.jar file, if it exists.
+   *
+   * @return a list that contains the pathname to the tools.jar file, if it exists
+   */
+  private static List<String> toolsJarList() {
+    String toolsJar = SystemUtil.getToolsJar();
+    if (toolsJar == null) {
+      return Collections.emptyList();
+    } else {
+      return Collections.singletonList(toolsJar);
+    }
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-javadoc"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessPermitClearPropertyTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessPermitClearPropertyTest.java
new file mode 100644
index 0000000..b42ff2f
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessPermitClearPropertyTest.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * JUnit tests for the Nullness Checker -- testing {@code -Alint=permitClearProperty} command-line
+ * argument.
+ */
+public class NullnessPermitClearPropertyTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public NullnessPermitClearPropertyTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-Anomsgtext",
+        "-Alint=permitClearProperty");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-permitClearProperty"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessReflectionTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessReflectionTest.java
new file mode 100644
index 0000000..dca6d57
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessReflectionTest.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness checker when reflection resolution is enabled. */
+public class NullnessReflectionTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessReflectionTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessReflectionTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-AresolveReflection",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-reflection"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsBytecodeTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsBytecodeTest.java
new file mode 100644
index 0000000..c1b2feb
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsBytecodeTest.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness checker when using safe defaults for unannotated bytecode. */
+public class NullnessSafeDefaultsBytecodeTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessSafeDefaultsBytecodeTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessSafeDefaultsBytecodeTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-AuseConservativeDefaultsForUncheckedCode=bytecode",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-safedefaultsbytecode"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java
new file mode 100644
index 0000000..13acb76
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java
@@ -0,0 +1,69 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.test.TestConfiguration;
+import org.checkerframework.framework.test.TestConfigurationBuilder;
+import org.checkerframework.framework.test.TestUtilities;
+import org.checkerframework.framework.test.TypecheckExecutor;
+import org.checkerframework.framework.test.TypecheckResult;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness checker when using safe defaults for unannotated source code. */
+public class NullnessSafeDefaultsSourceCodeTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessSafeDefaultsSourceCodeTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessSafeDefaultsSourceCodeTest(List<File> testFiles) {
+    super(
+        testFiles,
+        NullnessChecker.class,
+        "nullness",
+        "-AuseConservativeDefaultsForUncheckedCode=source",
+        "-cp",
+        "dist/checker.jar:tests/build/testclasses/",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-safedefaultssourcecode"};
+  }
+
+  @Override
+  public void run() {
+    boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo();
+    List<String> customizedOptions1 =
+        customizeOptions(
+            Arrays.asList(
+                "-AuseConservativeDefaultsForUncheckedCode=source,bytecode", "-Anomsgtext"));
+    TestConfiguration config1 =
+        TestConfigurationBuilder.buildDefaultConfiguration(
+            "tests/nullness-safedefaultssourcecodelib",
+            new File("tests/nullness-safedefaultssourcecodelib", "Lib.java"),
+            NullnessChecker.class,
+            customizedOptions1,
+            shouldEmitDebugInfo);
+    TypecheckResult testResult1 = new TypecheckExecutor().runTest(config1);
+    TestUtilities.assertTestDidNotFail(testResult1);
+
+    List<String> customizedOptions2 =
+        customizeOptions(Collections.unmodifiableList(checkerOptions));
+    TestConfiguration config2 =
+        TestConfigurationBuilder.buildDefaultConfiguration(
+            testDir,
+            testFiles,
+            Collections.singleton(NullnessChecker.class.getName()),
+            customizedOptions2,
+            shouldEmitDebugInfo);
+    TypecheckResult testResult2 = new TypecheckExecutor().runTest(config2);
+    TestUtilities.assertTestDidNotFail(testResult2);
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDefsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDefsTest.java
new file mode 100644
index 0000000..8cd74bf
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDefsTest.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness Checker -- testing {@code -AskipDefs} command-line argument. */
+public class NullnessSkipDefsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessSkipDefsTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessSkipDefsTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-Anomsgtext",
+        "-AskipDefs=SkipMe");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-skipdefs"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipUsesTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipUsesTest.java
new file mode 100644
index 0000000..f168ca1
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipUsesTest.java
@@ -0,0 +1,29 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness Checker -- testing {@code -AskipUses} command-line argument. */
+public class NullnessSkipUsesTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessSkipUsesTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessSkipUsesTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-Anomsgtext",
+        "-AskipUses=SkipMe");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-skipuses"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java
new file mode 100644
index 0000000..025cd72
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java
@@ -0,0 +1,34 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class NullnessStubfileTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessStubfileTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessStubfileTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-Anomsgtext",
+        "-AstubWarnIfNotFound",
+        "-Astubs="
+            + String.join(
+                ":",
+                "tests/nullness-stubfile/stubfile1.astub",
+                "tests/nullness-stubfile/stubfile2.astub",
+                "tests/nullness-stubfile/requireNonNull.astub"));
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-stubfile"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTempTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTempTest.java
new file mode 100644
index 0000000..1a31beb
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTempTest.java
@@ -0,0 +1,33 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness Checker. */
+public class NullnessTempTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessTempTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessTempTest(List<File> testFiles) {
+    // TODO: remove soundArrayCreationNullness option once it's no
+    // longer needed.  See issue #986:
+    // https://github.com/typetools/checker-framework/issues/986
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-Anomsgtext",
+        "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON);
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness-temp"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTest.java
new file mode 100644
index 0000000..4eb9e0d
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTest.java
@@ -0,0 +1,35 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Nullness checker. */
+public class NullnessTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a NullnessTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public NullnessTest(List<File> testFiles) {
+    // TODO: remove soundArrayCreationNullness option once it's no
+    // longer needed.  See issue #986:
+    // https://github.com/typetools/checker-framework/issues/986
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "nullness",
+        "-AcheckPurityAnnotations",
+        "-Anomsgtext",
+        "-Xlint:deprecation",
+        "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON);
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nullness", "initialization", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalTest.java
new file mode 100644
index 0000000..6129315
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalTest.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Optional Checker, which has the {@code @Present} annotation. */
+public class OptionalTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create an OptionalTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public OptionalTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.optional.OptionalChecker.class,
+        "optional",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"optional", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ParseAllJdkTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ParseAllJdkTest.java
new file mode 100644
index 0000000..948ec4d
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ParseAllJdkTest.java
@@ -0,0 +1,25 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Tests -AparseAllJdk option. */
+public class ParseAllJdkTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a ParseAllJdkTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public ParseAllJdkTest(List<File> testFiles) {
+    super(testFiles, NullnessChecker.class, "parse-all-jdk", "-AparseAllJdk");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"parse-all-jdk"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/README b/checker/src/test/java/org/checkerframework/checker/test/junit/README
new file mode 100644
index 0000000..5557557
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/README
@@ -0,0 +1 @@
+See the README file in the parent directory.
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/RegexTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/RegexTest.java
new file mode 100644
index 0000000..485c8ce
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/RegexTest.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class RegexTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a RegexTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public RegexTest(List<File> testFiles) {
+    super(testFiles, org.checkerframework.checker.regex.RegexChecker.class, "regex", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"regex", "regex_poly", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/SignatureTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/SignatureTest.java
new file mode 100644
index 0000000..d3e8506
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/SignatureTest.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class SignatureTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a SignatureTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public SignatureTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.signature.SignatureChecker.class,
+        "signature",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"signature", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessTest.java
new file mode 100644
index 0000000..b0d9892
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessTest.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class SignednessTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a SignednessTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public SignednessTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.signedness.SignednessChecker.class,
+        "signedness",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"signedness", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessUncheckedDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessUncheckedDefaultsTest.java
new file mode 100644
index 0000000..7d5b66d
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessUncheckedDefaultsTest.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class SignednessUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a SignednessUncheckedDefaultsTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public SignednessUncheckedDefaultsTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.signedness.SignednessChecker.class,
+        "signedness",
+        "-Anomsgtext",
+        "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"signedness-unchecked-defaults"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserNullnessTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserNullnessTest.java
new file mode 100644
index 0000000..1b41e37
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserNullnessTest.java
@@ -0,0 +1,30 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized;
+
+/** Tests for stub parsing. */
+public class StubparserNullnessTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a StubparserNullnessTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public StubparserNullnessTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.nullness.NullnessChecker.class,
+        "stubparser-nullness",
+        "-Anomsgtext",
+        "-Astubs=tests/stubparser-nullness",
+        "-AstubWarnIfNotFound");
+  }
+
+  @Parameterized.Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"stubparser-nullness"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserTaintingTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserTaintingTest.java
new file mode 100644
index 0000000..2e2130b
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserTaintingTest.java
@@ -0,0 +1,31 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized;
+
+/** Tests for stub parsing. */
+public class StubparserTaintingTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a StubparserTaintingTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public StubparserTaintingTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.checker.tainting.TaintingChecker.class,
+        "stubparser-tainting",
+        "-Anomsgtext",
+        "-AmergeStubsWithSource",
+        "-Astubs=tests/stubparser-tainting",
+        "-AstubWarnIfNotFound");
+  }
+
+  @Parameterized.Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"stubparser-tainting", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/TaintingTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/TaintingTest.java
new file mode 100644
index 0000000..e544d81
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/TaintingTest.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.tainting.TaintingChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class TaintingTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a TaintingTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public TaintingTest(List<File> testFiles) {
+    super(testFiles, TaintingChecker.class, "tainting", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"tainting", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/UnitsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/UnitsTest.java
new file mode 100644
index 0000000..2c570e6
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/UnitsTest.java
@@ -0,0 +1,23 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class UnitsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a UnitsTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public UnitsTest(List<File> testFiles) {
+    super(testFiles, org.checkerframework.checker.units.UnitsChecker.class, "units", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"units", "all-systems"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ValueIndexInteractionTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ValueIndexInteractionTest.java
new file mode 100644
index 0000000..6f56cbd
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ValueIndexInteractionTest.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Value Checker's interactions with the Index Checker. */
+public class ValueIndexInteractionTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a ValueIndexInteractionTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public ValueIndexInteractionTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.value.ValueChecker.class,
+        "value-index-interaction",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"value-index-interaction"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/README.md b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/README.md
new file mode 100644
index 0000000..6ad7a76
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/README.md
@@ -0,0 +1,4 @@
+This package contains the test runners for testing whole-program inference.
+They are in a separate package so that they don't run by default; they should
+only run when they're invoked directly by their corresponding build rules, which
+are in checker/build.gradle (wholeProgramInferenceTest and wholeProgramInferenceStubsTest).
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceNullnessJaifsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceNullnessJaifsTest.java
new file mode 100644
index 0000000..912df56
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceNullnessJaifsTest.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.test.junit.wpirunners;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Runs whole-program inference and inserts annotations into source code.
+ *
+ * <p>IMPORTANT: The errors captured in the tests located in tests/wpi-nullness/ are not relevant.
+ * The meaning of this test class is to test if the generated .jaif files are similar to the
+ * expected ones. The errors on .java files must be ignored.
+ */
+@Category(WholeProgramInferenceNullnessJaifsTest.class)
+public class WholeProgramInferenceNullnessJaifsTest extends CheckerFrameworkPerDirectoryTest {
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public WholeProgramInferenceNullnessJaifsTest(List<File> testFiles) {
+    super(testFiles, NullnessChecker.class, "nullness", "-Anomsgtext", "-Ainfer=jaifs", "-Awarns");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"wpi-nullness/non-annotated"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceNullnessJaifsValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceNullnessJaifsValidationTest.java
new file mode 100644
index 0000000..dd04142
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceNullnessJaifsValidationTest.java
@@ -0,0 +1,37 @@
+package org.checkerframework.checker.test.junit.wpirunners;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests whole-program type inference with the aid of .jaif files. This test is the second pass,
+ * which ensures that with the annotations inserted, the errors are no longer issued.
+ */
+@Category(WholeProgramInferenceNullnessJaifsTest.class)
+public class WholeProgramInferenceNullnessJaifsValidationTest
+    extends CheckerFrameworkPerDirectoryTest {
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public WholeProgramInferenceNullnessJaifsValidationTest(List<File> testFiles) {
+    super(testFiles, NullnessChecker.class, "nullness", "-Anomsgtext");
+  }
+
+  @Override
+  public void run() {
+    // Only run if annotated files have been created.
+    // See wholeProgramInferenceTests task.
+    if (!new File("tests/wpi-nullness/annotated/").exists()) {
+      throw new RuntimeException(
+          WholeProgramInferenceNullnessJaifsTest.class + " must be run before this test.");
+    }
+    super.run();
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"wpi-nullness/annotated/"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerAjavaTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerAjavaTest.java
new file mode 100644
index 0000000..684c866
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerAjavaTest.java
@@ -0,0 +1,36 @@
+package org.checkerframework.checker.test.junit.wpirunners;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.testchecker.wholeprograminference.WholeProgramInferenceTestChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests whole-program inference with the aid of ajava files. This test is the first pass on the
+ * test data, which generates the ajava files.
+ *
+ * <p>IMPORTANT: The errors captured in the tests located in tests/wpi-testchecker/ are not
+ * relevant. The meaning of this test class is to test if the generated ajava files are similar to
+ * the expected ones. The errors on .java files must be ignored.
+ */
+@Category(WholeProgramInferenceTestCheckerAjavaTest.class)
+public class WholeProgramInferenceTestCheckerAjavaTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public WholeProgramInferenceTestCheckerAjavaTest(List<File> testFiles) {
+    super(
+        testFiles,
+        WholeProgramInferenceTestChecker.class,
+        "wpi-testchecker/non-annotated",
+        "-Anomsgtext",
+        "-Ainfer=ajava",
+        "-Awarns");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"wpi-testchecker/non-annotated"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerAjavaValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerAjavaValidationTest.java
new file mode 100644
index 0000000..253033a
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerAjavaValidationTest.java
@@ -0,0 +1,45 @@
+package org.checkerframework.checker.test.junit.wpirunners;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.testchecker.wholeprograminference.WholeProgramInferenceTestChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests whole-program type inference with ajava files. This test is the second pass, which ensures
+ * that with the ajava files in place, the errors that those annotations remove are no longer
+ * issued.
+ */
+@Category(WholeProgramInferenceTestCheckerAjavaTest.class)
+public class WholeProgramInferenceTestCheckerAjavaValidationTest
+    extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public WholeProgramInferenceTestCheckerAjavaValidationTest(List<File> testFiles) {
+    super(
+        testFiles,
+        WholeProgramInferenceTestChecker.class,
+        "wpi-testchecker/annotated",
+        "-Anomsgtext",
+        "-Aajava=tests/wpi-testchecker/inference-output",
+        "-Awarns");
+  }
+
+  @Override
+  public void run() {
+    // Only run if annotated files have been created.
+    // See wholeProgramInferenceTests task.
+    if (!new File("tests/wpi-testchecker/annotated/").exists()) {
+      throw new RuntimeException(
+          WholeProgramInferenceTestCheckerAjavaTest.class + " must be run before this test.");
+    }
+    super.run();
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"wpi-testchecker/annotated/"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerJaifsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerJaifsTest.java
new file mode 100644
index 0000000..5c75e12
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerJaifsTest.java
@@ -0,0 +1,34 @@
+package org.checkerframework.checker.test.junit.wpirunners;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.testchecker.wholeprograminference.WholeProgramInferenceTestChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Runs whole-program inference and inserts annotations into source code.
+ *
+ * <p>IMPORTANT: The errors captured in the tests located in tests/wpi-testchecker/ are not
+ * relevant. The meaning of this test class is to test if the generated .jaif files are similar to
+ * the expected ones. The errors on .java files must be ignored.
+ */
+@Category(WholeProgramInferenceTestCheckerJaifsTest.class)
+public class WholeProgramInferenceTestCheckerJaifsTest extends CheckerFrameworkPerDirectoryTest {
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public WholeProgramInferenceTestCheckerJaifsTest(List<File> testFiles) {
+    super(
+        testFiles,
+        WholeProgramInferenceTestChecker.class,
+        "wpi-testchecker/non-annotated",
+        "-Anomsgtext",
+        "-Ainfer=jaifs",
+        "-Awarns");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"wpi-testchecker/non-annotated"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerJaifsValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerJaifsValidationTest.java
new file mode 100644
index 0000000..e09e266
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerJaifsValidationTest.java
@@ -0,0 +1,42 @@
+package org.checkerframework.checker.test.junit.wpirunners;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.testchecker.wholeprograminference.WholeProgramInferenceTestChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests whole-program type inference with the aid of .jaif files. This test is the second pass,
+ * which ensures that with the annotations inserted, the errors are no longer issued.
+ */
+@Category(WholeProgramInferenceTestCheckerJaifsTest.class)
+public class WholeProgramInferenceTestCheckerJaifsValidationTest
+    extends CheckerFrameworkPerDirectoryTest {
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public WholeProgramInferenceTestCheckerJaifsValidationTest(List<File> testFiles) {
+    super(
+        testFiles,
+        WholeProgramInferenceTestChecker.class,
+        "wpi-testchecker/non-annotated",
+        "-Anomsgtext",
+        "-Awarns");
+  }
+
+  @Override
+  public void run() {
+    // Only run if annotated files have been created.
+    // See wholeProgramInferenceTests task.
+    if (!new File("tests/wpi-testchecker/annotated/").exists()) {
+      throw new RuntimeException(
+          WholeProgramInferenceTestCheckerJaifsTest.class + " must be run before this test.");
+    }
+    super.run();
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"wpi-testchecker/annotated/"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerStubsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerStubsTest.java
new file mode 100644
index 0000000..2c39078
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerStubsTest.java
@@ -0,0 +1,36 @@
+package org.checkerframework.checker.test.junit.wpirunners;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.testchecker.wholeprograminference.WholeProgramInferenceTestChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests whole-program inference with the aid of stub files. This test is the first pass on the test
+ * data, which generates the stubs.
+ *
+ * <p>IMPORTANT: The errors captured in the tests located in tests/wpi-testchecker/ are not
+ * relevant. The meaning of this test class is to test if the generated stub files are similar to
+ * the expected ones. The errors on .java files must be ignored.
+ */
+@Category(WholeProgramInferenceTestCheckerStubsTest.class)
+public class WholeProgramInferenceTestCheckerStubsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public WholeProgramInferenceTestCheckerStubsTest(List<File> testFiles) {
+    super(
+        testFiles,
+        WholeProgramInferenceTestChecker.class,
+        "wpi-testchecker/non-annotated",
+        "-Anomsgtext",
+        "-Ainfer=stubs",
+        "-Awarns");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"wpi-testchecker/non-annotated"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerStubsValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerStubsValidationTest.java
new file mode 100644
index 0000000..497a4fb
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/wpirunners/WholeProgramInferenceTestCheckerStubsValidationTest.java
@@ -0,0 +1,46 @@
+package org.checkerframework.checker.test.junit.wpirunners;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.checker.testchecker.wholeprograminference.WholeProgramInferenceTestChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests whole-program type inference with stub files. This test is the second pass, which ensures
+ * that with the stubs in place, the errors that those annotations remove are no longer issued.
+ */
+@Category(WholeProgramInferenceTestCheckerStubsTest.class)
+public class WholeProgramInferenceTestCheckerStubsValidationTest
+    extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public WholeProgramInferenceTestCheckerStubsValidationTest(List<File> testFiles) {
+    super(
+        testFiles,
+        WholeProgramInferenceTestChecker.class,
+        "wpi-testchecker/annotated",
+        "-Anomsgtext",
+        "-Astubs=tests/wpi-testchecker/inference-output",
+        // "-AstubDebug",
+        "-AmergeStubsWithSource",
+        "-Awarns");
+  }
+
+  @Override
+  public void run() {
+    // Only run if annotated files have been created.
+    // See wholeProgramInferenceTests task.
+    if (!new File("tests/wpi-testchecker/annotated/").exists()) {
+      throw new RuntimeException(
+          WholeProgramInferenceTestCheckerStubsTest.class + " must be run before this test.");
+    }
+    super.run();
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"wpi-testchecker/annotated/"};
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/NestedAggregateChecker.java b/checker/src/test/java/org/checkerframework/checker/testchecker/NestedAggregateChecker.java
new file mode 100644
index 0000000..87c7887
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/NestedAggregateChecker.java
@@ -0,0 +1,28 @@
+package org.checkerframework.checker.testchecker;
+
+// Test case for Issue 343
+// https://github.com/typetools/checker-framework/issues/343
+
+import java.util.ArrayList;
+import java.util.Collection;
+import org.checkerframework.checker.fenum.FenumChecker;
+import org.checkerframework.checker.i18n.I18nChecker;
+import org.checkerframework.checker.nullness.NullnessChecker;
+import org.checkerframework.checker.regex.RegexChecker;
+import org.checkerframework.framework.source.AggregateChecker;
+import org.checkerframework.framework.source.SourceChecker;
+
+public class NestedAggregateChecker extends AggregateChecker {
+  @Override
+  protected Collection<Class<? extends SourceChecker>> getSupportedCheckers() {
+    ArrayList<Class<? extends SourceChecker>> list =
+        new ArrayList<Class<? extends SourceChecker>>();
+
+    list.add(FenumChecker.class);
+    list.add(I18nChecker.class); // The I18nChecker is an aggregate checker
+    list.add(NullnessChecker.class);
+    list.add(RegexChecker.class);
+
+    return list;
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/lib b/checker/src/test/java/org/checkerframework/checker/testchecker/lib
new file mode 120000
index 0000000..4bd3bf4
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/lib
@@ -0,0 +1 @@
+../../../../../../../../framework/src/test/java/org/checkerframework/framework/testchecker/lib
\ No newline at end of file
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/FormatterLubGlbChecker.java b/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/FormatterLubGlbChecker.java
new file mode 100644
index 0000000..7c69d69
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/FormatterLubGlbChecker.java
@@ -0,0 +1,948 @@
+package org.checkerframework.checker.testchecker.lubglb;
+
+// Test case for issues 691 and 756.
+// https://github.com/typetools/checker-framework/issues/691
+// https://github.com/typetools/checker-framework/issues/756
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.formatter.FormatterAnnotatedTypeFactory;
+import org.checkerframework.checker.formatter.FormatterChecker;
+import org.checkerframework.checker.formatter.FormatterTreeUtil;
+import org.checkerframework.checker.formatter.FormatterVisitor;
+import org.checkerframework.checker.formatter.qual.ConversionCategory;
+import org.checkerframework.checker.formatter.qual.Format;
+import org.checkerframework.checker.formatter.qual.FormatBottom;
+import org.checkerframework.checker.formatter.qual.InvalidFormat;
+import org.checkerframework.checker.formatter.qual.UnknownFormat;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * This class tests the implementation of GLB computation in the Formatter Checker, but it does not
+ * test for the crash described in issue 691. That is done by tests/all-systems/Issue691.java. It
+ * also tests the implementation of LUB computation in the Formatter Checker.
+ */
+public class FormatterLubGlbChecker extends FormatterChecker {
+
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new FormatterVisitor(this) {
+      @Override
+      protected FormatterLubGlbAnnotatedTypeFactory createTypeFactory() {
+        return new FormatterLubGlbAnnotatedTypeFactory(checker);
+      }
+    };
+  }
+
+  /** FormatterLubGlbAnnotatedTypeFactory. */
+  private static class FormatterLubGlbAnnotatedTypeFactory extends FormatterAnnotatedTypeFactory {
+
+    /**
+     * Constructor.
+     *
+     * @param checker checker
+     */
+    public FormatterLubGlbAnnotatedTypeFactory(BaseTypeChecker checker) {
+      super(checker);
+      postInit();
+    }
+
+    @Override
+    protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+      return new HashSet<>(
+          Arrays.asList(
+              FormatBottom.class, Format.class, InvalidFormat.class, UnknownFormat.class));
+    }
+  }
+
+  @SuppressWarnings("checkstyle:localvariablename")
+  @Override
+  public void initChecker() {
+    super.initChecker();
+    FormatterTreeUtil treeUtil = new FormatterTreeUtil(this);
+
+    Elements elements = getElementUtils();
+    AnnotationMirror UNKNOWNFORMAT = AnnotationBuilder.fromClass(elements, UnknownFormat.class);
+    AnnotationMirror FORMAT =
+        AnnotationBuilder.fromClass(
+            elements,
+            Format.class,
+            AnnotationBuilder.elementNamesValues("value", new ConversionCategory[0]));
+    AnnotationMirror INVALIDFORMAT =
+        AnnotationBuilder.fromClass(
+            elements, InvalidFormat.class, AnnotationBuilder.elementNamesValues("value", "dummy"));
+    AnnotationMirror FORMATBOTTOM = AnnotationBuilder.fromClass(elements, FormatBottom.class);
+
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, InvalidFormat.class);
+    builder.setValue("value", "Message");
+    AnnotationMirror invalidFormatWithMessage = builder.build();
+
+    builder = new AnnotationBuilder(processingEnv, InvalidFormat.class);
+    builder.setValue("value", "Message2");
+    AnnotationMirror invalidFormatWithMessage2 = builder.build();
+
+    builder = new AnnotationBuilder(processingEnv, InvalidFormat.class);
+    builder.setValue("value", "(\"Message\" or \"Message2\")");
+    AnnotationMirror invalidFormatWithMessagesOred = builder.build();
+
+    builder = new AnnotationBuilder(processingEnv, InvalidFormat.class);
+    builder.setValue("value", "(\"Message\" and \"Message2\")");
+    AnnotationMirror invalidFormatWithMessagesAnded = builder.build();
+
+    ConversionCategory[] cc = new ConversionCategory[1];
+
+    cc[0] = ConversionCategory.UNUSED;
+    AnnotationMirror formatUnusedAnno = treeUtil.categoriesToFormatAnnotation(cc);
+    cc[0] = ConversionCategory.GENERAL;
+    AnnotationMirror formatGeneralAnno = treeUtil.categoriesToFormatAnnotation(cc);
+    cc[0] = ConversionCategory.CHAR;
+    AnnotationMirror formatCharAnno = treeUtil.categoriesToFormatAnnotation(cc);
+    cc[0] = ConversionCategory.INT;
+    AnnotationMirror formatIntAnno = treeUtil.categoriesToFormatAnnotation(cc);
+    cc[0] = ConversionCategory.TIME;
+    AnnotationMirror formatTimeAnno = treeUtil.categoriesToFormatAnnotation(cc);
+    cc[0] = ConversionCategory.FLOAT;
+    AnnotationMirror formatFloatAnno = treeUtil.categoriesToFormatAnnotation(cc);
+    cc[0] = ConversionCategory.CHAR_AND_INT;
+    AnnotationMirror formatCharAndIntAnno = treeUtil.categoriesToFormatAnnotation(cc);
+    cc[0] = ConversionCategory.INT_AND_TIME;
+    AnnotationMirror formatIntAndTimeAnno = treeUtil.categoriesToFormatAnnotation(cc);
+    cc[0] = ConversionCategory.NULL;
+    AnnotationMirror formatNullAnno = treeUtil.categoriesToFormatAnnotation(cc);
+
+    QualifierHierarchy qh = ((BaseTypeVisitor<?>) visitor).getTypeFactory().getQualifierHierarchy();
+
+    // ** GLB tests **
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAndIntAnno, formatIntAndTimeAnno), formatIntAnno)
+        : "GLB of @Format(CHAR_AND_INT) and @Format(INT_AND_TIME) is not @Format(INT)!";
+
+    // GLB of UNUSED and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, formatUnusedAnno), formatUnusedAnno)
+        : "GLB of @Format(UNUSED) and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, formatGeneralAnno), formatUnusedAnno)
+        : "GLB of @Format(UNUSED) and @Format(GENERAL) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, formatCharAnno), formatUnusedAnno)
+        : "GLB of @Format(UNUSED) and @Format(CHAR) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, formatIntAnno), formatUnusedAnno)
+        : "GLB of @Format(UNUSED) and @Format(INT) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, formatTimeAnno), formatUnusedAnno)
+        : "GLB of @Format(UNUSED) and @Format(TIME) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, formatFloatAnno), formatUnusedAnno)
+        : "GLB of @Format(UNUSED) and @Format(FLOAT) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, formatCharAndIntAnno), formatUnusedAnno)
+        : "GLB of @Format(UNUSED) and @Format(CHAR_AND_INT) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, formatIntAndTimeAnno), formatUnusedAnno)
+        : "GLB of @Format(UNUSED) and @Format(INT_AND_TIME) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, formatNullAnno), formatUnusedAnno)
+        : "GLB of @Format(UNUSED) and @Format(NULL) is not @Format(UNUSED)!";
+
+    // GLB of GENERAL and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatGeneralAnno, formatUnusedAnno), formatUnusedAnno)
+        : "GLB of @Format(GENERAL) and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatGeneralAnno, formatGeneralAnno), formatGeneralAnno)
+        : "GLB of @Format(GENERAL) and @Format(GENERAL) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatGeneralAnno, formatCharAnno), formatGeneralAnno)
+        : "GLB of @Format(GENERAL) and @Format(CHAR) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatGeneralAnno, formatIntAnno), formatGeneralAnno)
+        : "GLB of @Format(GENERAL) and @Format(INT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatGeneralAnno, formatTimeAnno), formatGeneralAnno)
+        : "GLB of @Format(GENERAL) and @Format(TIME) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatGeneralAnno, formatFloatAnno), formatGeneralAnno)
+        : "GLB of @Format(GENERAL) and @Format(FLOAT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatGeneralAnno, formatCharAndIntAnno), formatGeneralAnno)
+        : "GLB of @Format(GENERAL) and @Format(CHAR_AND_INT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatGeneralAnno, formatIntAndTimeAnno), formatGeneralAnno)
+        : "GLB of @Format(GENERAL) and @Format(INT_AND_TIME) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatGeneralAnno, formatNullAnno), formatGeneralAnno)
+        : "GLB of @Format(GENERAL) and @Format(NULL) is not @Format(GENERAL)!";
+
+    // GLB of CHAR and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAnno, formatUnusedAnno), formatUnusedAnno)
+        : "GLB of @Format(CHAR) and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAnno, formatGeneralAnno), formatGeneralAnno)
+        : "GLB of @Format(CHAR) and @Format(GENERAL) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAnno, formatCharAnno), formatCharAnno)
+        : "GLB of @Format(CHAR) and @Format(CHAR) is not @Format(CHAR)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAnno, formatIntAnno), formatGeneralAnno)
+        : "GLB of @Format(CHAR) and @Format(INT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAnno, formatTimeAnno), formatGeneralAnno)
+        : "GLB of @Format(CHAR) and @Format(TIME) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAnno, formatFloatAnno), formatGeneralAnno)
+        : "GLB of @Format(CHAR) and @Format(FLOAT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAnno, formatCharAndIntAnno), formatCharAnno)
+        : "GLB of @Format(CHAR) and @Format(CHAR_AND_INT) is not @Format(CHAR)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAnno, formatIntAndTimeAnno), formatGeneralAnno)
+        : "GLB of @Format(CHAR) and @Format(INT_AND_TIME) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAnno, formatNullAnno), formatCharAnno)
+        : "GLB of @Format(CHAR) and @Format(NULL) is not @Format(CHAR)!";
+
+    // GLB of INT and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAnno, formatUnusedAnno), formatUnusedAnno)
+        : "GLB of @Format(INT) and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAnno, formatGeneralAnno), formatGeneralAnno)
+        : "GLB of @Format(INT) and @Format(GENERAL) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAnno, formatCharAnno), formatGeneralAnno)
+        : "GLB of @Format(INT) and @Format(CHAR) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAnno, formatIntAnno), formatIntAnno)
+        : "GLB of @Format(INT) and @Format(INT) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAnno, formatTimeAnno), formatGeneralAnno)
+        : "GLB of @Format(INT) and @Format(TIME) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAnno, formatFloatAnno), formatGeneralAnno)
+        : "GLB of @Format(INT) and @Format(FLOAT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAnno, formatCharAndIntAnno), formatIntAnno)
+        : "GLB of @Format(INT) and @Format(CHAR_AND_INT) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAnno, formatIntAndTimeAnno), formatIntAnno)
+        : "GLB of @Format(INT) and @Format(INT_AND_TIME) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAnno, formatNullAnno), formatIntAnno)
+        : "GLB of @Format(INT) and @Format(NULL) is not @Format(INT)!";
+
+    // GLB of TIME and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTimeAnno, formatUnusedAnno), formatUnusedAnno)
+        : "GLB of @Format(TIME) and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTimeAnno, formatGeneralAnno), formatGeneralAnno)
+        : "GLB of @Format(TIME) and @Format(GENERAL) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTimeAnno, formatCharAnno), formatGeneralAnno)
+        : "GLB of @Format(TIME) and @Format(CHAR) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTimeAnno, formatIntAnno), formatGeneralAnno)
+        : "GLB of @Format(TIME) and @Format(INT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTimeAnno, formatTimeAnno), formatTimeAnno)
+        : "GLB of @Format(TIME) and @Format(TIME) is not @Format(TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTimeAnno, formatFloatAnno), formatGeneralAnno)
+        : "GLB of @Format(TIME) and @Format(FLOAT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTimeAnno, formatCharAndIntAnno), formatGeneralAnno)
+        : "GLB of @Format(TIME) and @Format(CHAR_AND_INT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTimeAnno, formatIntAndTimeAnno), formatTimeAnno)
+        : "GLB of @Format(TIME) and @Format(INT_AND_TIME) is not @Format(TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTimeAnno, formatNullAnno), formatTimeAnno)
+        : "GLB of @Format(TIME) and @Format(NULL) is not @Format(TIME)!";
+
+    // GLB of FLOAT and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatFloatAnno, formatUnusedAnno), formatUnusedAnno)
+        : "GLB of @Format(FLOAT) and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatFloatAnno, formatGeneralAnno), formatGeneralAnno)
+        : "GLB of @Format(FLOAT) and @Format(GENERAL) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatFloatAnno, formatCharAnno), formatGeneralAnno)
+        : "GLB of @Format(FLOAT) and @Format(CHAR) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatFloatAnno, formatIntAnno), formatGeneralAnno)
+        : "GLB of @Format(FLOAT) and @Format(INT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatFloatAnno, formatTimeAnno), formatGeneralAnno)
+        : "GLB of @Format(FLOAT) and @Format(TIME) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatFloatAnno, formatFloatAnno), formatFloatAnno)
+        : "GLB of @Format(FLOAT) and @Format(FLOAT) is not @Format(FLOAT)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatFloatAnno, formatCharAndIntAnno), formatGeneralAnno)
+        : "GLB of @Format(FLOAT) and @Format(CHAR_AND_INT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatFloatAnno, formatIntAndTimeAnno), formatGeneralAnno)
+        : "GLB of @Format(FLOAT) and @Format(INT_AND_TIME) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatFloatAnno, formatNullAnno), formatFloatAnno)
+        : "GLB of @Format(FLOAT) and @Format(NULL) is not @Format(FLOAT)!";
+
+    // GLB of CHAR_AND_INT and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAndIntAnno, formatUnusedAnno), formatUnusedAnno)
+        : "GLB of @Format(CHAR_AND_INT) and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAndIntAnno, formatGeneralAnno), formatGeneralAnno)
+        : "GLB of @Format(CHAR_AND_INT) and @Format(GENERAL) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAndIntAnno, formatCharAnno), formatCharAnno)
+        : "GLB of @Format(CHAR_AND_INT) and @Format(CHAR) is not @Format(CHAR)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAndIntAnno, formatIntAnno), formatIntAnno)
+        : "GLB of @Format(CHAR_AND_INT) and @Format(INT) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAndIntAnno, formatTimeAnno), formatGeneralAnno)
+        : "GLB of @Format(CHAR_AND_INT) and @Format(TIME) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAndIntAnno, formatFloatAnno), formatGeneralAnno)
+        : "GLB of @Format(CHAR_AND_INT) and @Format(FLOAT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAndIntAnno, formatCharAndIntAnno), formatCharAndIntAnno)
+        : "GLB of @Format(CHAR_AND_INT) and @Format(CHAR_AND_INT) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAndIntAnno, formatIntAndTimeAnno), formatIntAnno)
+        : "GLB of @Format(CHAR_AND_INT) and @Format(INT_AND_TIME) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatCharAndIntAnno, formatNullAnno), formatCharAndIntAnno)
+        : "GLB of @Format(CHAR_AND_INT) and @Format(NULL) is not @Format(CHAR_AND_INT)!";
+
+    // GLB of INT_AND_TIME and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAndTimeAnno, formatUnusedAnno), formatUnusedAnno)
+        : "GLB of @Format(INT_AND_TIME) and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAndTimeAnno, formatGeneralAnno), formatGeneralAnno)
+        : "GLB of @Format(INT_AND_TIME) and @Format(GENERAL) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAndTimeAnno, formatCharAnno), formatGeneralAnno)
+        : "GLB of @Format(INT_AND_TIME) and @Format(CHAR) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAndTimeAnno, formatIntAnno), formatIntAnno)
+        : "GLB of @Format(INT_AND_TIME) and @Format(INT) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAndTimeAnno, formatTimeAnno), formatTimeAnno)
+        : "GLB of @Format(INT_AND_TIME) and @Format(TIME) is not @Format(TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAndTimeAnno, formatFloatAnno), formatGeneralAnno)
+        : "GLB of @Format(INT_AND_TIME) and @Format(FLOAT) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAndTimeAnno, formatCharAndIntAnno), formatIntAnno)
+        : "GLB of @Format(INT_AND_TIME) and @Format(CHAR_AND_INT) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAndTimeAnno, formatIntAndTimeAnno), formatIntAndTimeAnno)
+        : "GLB of @Format(INT_AND_TIME) and @Format(INT_AND_TIME) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatIntAndTimeAnno, formatNullAnno), formatIntAndTimeAnno)
+        : "GLB of @Format(INT_AND_TIME) and @Format(NULL) is not @Format(INT_AND_TIME)!";
+
+    // GLB of NULL and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatNullAnno, formatUnusedAnno), formatUnusedAnno)
+        : "GLB of @Format(NULL) and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatNullAnno, formatGeneralAnno), formatGeneralAnno)
+        : "GLB of @Format(NULL) and @Format(GENERAL) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatNullAnno, formatCharAnno), formatCharAnno)
+        : "GLB of @Format(NULL) and @Format(CHAR) is not @Format(CHAR)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatNullAnno, formatIntAnno), formatIntAnno)
+        : "GLB of @Format(NULL) and @Format(INT) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatNullAnno, formatTimeAnno), formatTimeAnno)
+        : "GLB of @Format(NULL) and @Format(TIME) is not @Format(TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatNullAnno, formatFloatAnno), formatFloatAnno)
+        : "GLB of @Format(NULL) and @Format(FLOAT) is not @Format(FLOAT)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatNullAnno, formatCharAndIntAnno), formatCharAndIntAnno)
+        : "GLB of @Format(NULL) and @Format(CHAR_AND_INT) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatNullAnno, formatIntAndTimeAnno), formatIntAndTimeAnno)
+        : "GLB of @Format(NULL) and @Format(INT_AND_TIME) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatNullAnno, formatNullAnno), formatNullAnno)
+        : "GLB of @Format(NULL) and @Format(NULL) is not @Format(NULL)!";
+
+    // Now test with two ConversionCategory at a time:
+
+    ConversionCategory[] cc2 = new ConversionCategory[2];
+
+    cc2[0] = ConversionCategory.CHAR_AND_INT;
+    cc2[1] = ConversionCategory.FLOAT;
+    AnnotationMirror formatTwoConvCat1 = treeUtil.categoriesToFormatAnnotation(cc2);
+    cc2[0] = ConversionCategory.INT;
+    cc2[1] = ConversionCategory.CHAR;
+    AnnotationMirror formatTwoConvCat2 = treeUtil.categoriesToFormatAnnotation(cc2);
+    cc2[0] = ConversionCategory.INT;
+    cc2[1] = ConversionCategory.GENERAL;
+    AnnotationMirror formatTwoConvCat3 = treeUtil.categoriesToFormatAnnotation(cc2);
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTwoConvCat1, formatTwoConvCat2), formatTwoConvCat3)
+        : "GLB of @Format([CHAR_AND_INT,FLOAT]) and @Format([INT,CHAR]) is not"
+            + " @Format([INT,GENERAL])!";
+
+    // Test that the GLB of two ConversionCategory arrays of different sizes is an array of the
+    // smallest size of the two:
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatGeneralAnno, formatTwoConvCat1), formatGeneralAnno)
+        : "GLB of @I18nFormat(GENERAL) and @I18nFormat([CHAR_AND_INT,FLOAT]) is not"
+            + " @I18nFormat(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTwoConvCat2, formatNullAnno), formatIntAnno)
+        : "GLB of @I18nFormat([INT,CHAR]) and @I18nFormat(NULL) is not @I18nFormat(INT)!";
+
+    // GLB of @UnknownFormat and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(UNKNOWNFORMAT, UNKNOWNFORMAT), UNKNOWNFORMAT)
+        : "GLB of @UnknownFormat and @UnknownFormat is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(UNKNOWNFORMAT, FORMAT), FORMAT)
+        : "GLB of @UnknownFormat and @Format(null) is not @Format(null)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(UNKNOWNFORMAT, formatUnusedAnno), formatUnusedAnno)
+        : "GLB of @UnknownFormat and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(UNKNOWNFORMAT, INVALIDFORMAT), INVALIDFORMAT)
+        : "GLB of @UnknownFormat and @InvalidFormat(null) is not @InvalidFormat(null)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(UNKNOWNFORMAT, invalidFormatWithMessage),
+            invalidFormatWithMessage)
+        : "GLB of @UnknownFormat and @InvalidFormat(\"Message\") is not"
+            + " @InvalidFormat(\"Message\")!";
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(UNKNOWNFORMAT, FORMATBOTTOM), FORMATBOTTOM)
+        : "GLB of @UnknownFormat and @FormatBottom is not @FormatBottom!";
+
+    // GLB of @Format(null) and others
+
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(FORMAT, UNKNOWNFORMAT), FORMAT)
+        : "GLB of @Format(null) and @UnknownFormat is not @Format(null)!";
+    // Computing the GLB of @Format(null) and @Format(null) should never occur in practice.
+    // Skipping this case as it causes an expected crash.
+    // Computing the GLB of @Format(null) and @Format with a value should never occur in
+    // practice. Skipping this case as it causes an expected crash.
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(FORMAT, INVALIDFORMAT), FORMATBOTTOM)
+        : "GLB of @Format(null) and @InvalidFormat(null) is not @FormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(FORMAT, invalidFormatWithMessage), FORMATBOTTOM)
+        : "GLB of @Format(null) and @InvalidFormat(\"Message\") is not @FormatBottom!";
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(FORMAT, FORMATBOTTOM), FORMATBOTTOM)
+        : "GLB of @Format(null) and @FormatBottom is not @FormatBottom!";
+
+    // GLB of @Format(UNUSED) and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, UNKNOWNFORMAT), formatUnusedAnno)
+        : "GLB of @Format(UNUSED) and @UnknownFormat is not @Format(UNUSED)!";
+    // Computing the GLB of @Format with a value and @Format(null) should never occur in
+    // practice. Skipping this case as it causes an expected crash.
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, formatUnusedAnno), formatUnusedAnno)
+        : "GLB of @Format(UNUSED) and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, INVALIDFORMAT), FORMATBOTTOM)
+        : "GLB of @Format(UNUSED) and @InvalidFormat(null) is not @FormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, invalidFormatWithMessage), FORMATBOTTOM)
+        : "GLB of @Format(UNUSED) and @InvalidFormat(\"Message\") is not @FormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatUnusedAnno, FORMATBOTTOM), FORMATBOTTOM)
+        : "GLB of @Format(UNUSED) and @FormatBottom is not @FormatBottom!";
+
+    // GLB of @InvalidFormat(null) and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(INVALIDFORMAT, UNKNOWNFORMAT), INVALIDFORMAT)
+        : "GLB of @InvalidFormat(null) and @UnknownFormat is not @InvalidFormat(null)!";
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(INVALIDFORMAT, FORMAT), FORMATBOTTOM)
+        : "GLB of @InvalidFormat(null) and @Format(null) is not @FormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(INVALIDFORMAT, formatUnusedAnno), FORMATBOTTOM)
+        : "GLB of @InvalidFormat(null) and @Format(UNUSED) is not @FormatBottom!";
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(INVALIDFORMAT, FORMATBOTTOM), FORMATBOTTOM)
+        : "GLB of @InvalidFormat(null) and @FormatBottom is not @FormatBottom!";
+
+    // GLB of @InvalidFormat("Message") and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(invalidFormatWithMessage, UNKNOWNFORMAT),
+            invalidFormatWithMessage)
+        : "GLB of @InvalidFormat(\"Message\") and @UnknownFormat is not"
+            + " @InvalidFormat(\"Message\")!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(invalidFormatWithMessage, FORMAT), FORMATBOTTOM)
+        : "GLB of @InvalidFormat(\"Message\") and @Format(null) is not @FormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(invalidFormatWithMessage, formatUnusedAnno), FORMATBOTTOM)
+        : "GLB of @InvalidFormat(\"Message\") and @Format(UNUSED) is not @FormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(invalidFormatWithMessage, invalidFormatWithMessage),
+            invalidFormatWithMessage)
+        : "GLB of @InvalidFormat(\"Message\") and @InvalidFormat(\"Message\") is not"
+            + " @InvalidFormat(\"Message\")!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(invalidFormatWithMessage, invalidFormatWithMessage2),
+            invalidFormatWithMessagesAnded)
+        : "GLB of @InvalidFormat(\"Message\") and @InvalidFormat(\"Message2\") is not"
+            + " @InvalidFormat(\"(\"Message\" and \"Message2\")\")!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(invalidFormatWithMessage, FORMATBOTTOM), FORMATBOTTOM)
+        : "GLB of @InvalidFormat(\"Message\") and @FormatBottom is not @FormatBottom!";
+
+    // GLB of @FormatBottom and others
+
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(FORMATBOTTOM, UNKNOWNFORMAT), FORMATBOTTOM)
+        : "GLB of @FormatBottom and @UnknownFormat is not @FormatBottom!";
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(FORMATBOTTOM, FORMAT), FORMATBOTTOM)
+        : "GLB of @FormatBottom and @Format(null) is not @FormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(FORMATBOTTOM, formatUnusedAnno), FORMATBOTTOM)
+        : "GLB of @FormatBottom and @Format(UNUSED) is not @FormatBottom!";
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(FORMATBOTTOM, INVALIDFORMAT), FORMATBOTTOM)
+        : "GLB of @FormatBottom and @InvalidFormat(null) is not @FormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(FORMATBOTTOM, invalidFormatWithMessage), FORMATBOTTOM)
+        : "GLB of @FormatBottom and @InvalidFormat(\"Message\") is not @FormatBottom!";
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(FORMATBOTTOM, FORMATBOTTOM), FORMATBOTTOM)
+        : "GLB of @FormatBottom and @FormatBottom is not @FormatBottom!";
+
+    // ** LUB tests **
+
+    // LUB of UNUSED and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, formatUnusedAnno), formatUnusedAnno)
+        : "LUB of @Format(UNUSED) and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, formatGeneralAnno), formatGeneralAnno)
+        : "LUB of @Format(UNUSED) and @Format(GENERAL) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, formatCharAnno), formatCharAnno)
+        : "LUB of @Format(UNUSED) and @Format(CHAR) is not @Format(CHAR)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, formatIntAnno), formatIntAnno)
+        : "LUB of @Format(UNUSED) and @Format(INT) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, formatTimeAnno), formatTimeAnno)
+        : "LUB of @Format(UNUSED) and @Format(TIME) is not @Format(TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, formatFloatAnno), formatFloatAnno)
+        : "LUB of @Format(UNUSED) and @Format(FLOAT) is not @Format(FLOAT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, formatCharAndIntAnno), formatCharAndIntAnno)
+        : "LUB of @Format(UNUSED) and @Format(CHAR_AND_INT) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, formatIntAndTimeAnno), formatIntAndTimeAnno)
+        : "LUB of @Format(UNUSED) and @Format(INT_AND_TIME) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, formatNullAnno), formatNullAnno)
+        : "LUB of @Format(UNUSED) and @Format(NULL) is not @Format(NULL)!";
+
+    // LUB of GENERAL and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatGeneralAnno, formatUnusedAnno), formatGeneralAnno)
+        : "LUB of @Format(GENERAL) and @Format(UNUSED) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatGeneralAnno, formatGeneralAnno), formatGeneralAnno)
+        : "LUB of @Format(GENERAL) and @Format(GENERAL) is not @Format(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatGeneralAnno, formatCharAnno), formatCharAnno)
+        : "LUB of @Format(GENERAL) and @Format(CHAR) is not @Format(CHAR)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatGeneralAnno, formatIntAnno), formatIntAnno)
+        : "LUB of @Format(GENERAL) and @Format(INT) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatGeneralAnno, formatTimeAnno), formatTimeAnno)
+        : "LUB of @Format(GENERAL) and @Format(TIME) is not @Format(TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatGeneralAnno, formatFloatAnno), formatFloatAnno)
+        : "LUB of @Format(GENERAL) and @Format(FLOAT) is not @Format(FLOAT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatGeneralAnno, formatCharAndIntAnno), formatCharAndIntAnno)
+        : "LUB of @Format(GENERAL) and @Format(CHAR_AND_INT) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatGeneralAnno, formatIntAndTimeAnno), formatIntAndTimeAnno)
+        : "LUB of @Format(GENERAL) and @Format(INT_AND_TIME) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatGeneralAnno, formatNullAnno), formatNullAnno)
+        : "LUB of @Format(GENERAL) and @Format(NULL) is not @Format(NULL)!";
+
+    // LUB of CHAR and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAnno, formatUnusedAnno), formatCharAnno)
+        : "LUB of @Format(CHAR) and @Format(UNUSED) is not @Format(CHAR)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAnno, formatGeneralAnno), formatCharAnno)
+        : "LUB of @Format(CHAR) and @Format(GENERAL) is not @Format(CHAR)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAnno, formatCharAnno), formatCharAnno)
+        : "LUB of @Format(CHAR) and @Format(CHAR) is not @Format(CHAR)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAnno, formatIntAnno), formatCharAndIntAnno)
+        : "LUB of @Format(CHAR) and @Format(INT) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAnno, formatTimeAnno), formatNullAnno)
+        : "LUB of @Format(CHAR) and @Format(TIME) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAnno, formatFloatAnno), formatNullAnno)
+        : "LUB of @Format(CHAR) and @Format(FLOAT) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAnno, formatCharAndIntAnno), formatCharAndIntAnno)
+        : "LUB of @Format(CHAR) and @Format(CHAR_AND_INT) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAnno, formatIntAndTimeAnno), formatNullAnno)
+        : "LUB of @Format(CHAR) and @Format(INT_AND_TIME) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAnno, formatNullAnno), formatNullAnno)
+        : "LUB of @Format(CHAR) and @Format(NULL) is not @Format(NULL)!";
+
+    // LUB of INT and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAnno, formatUnusedAnno), formatIntAnno)
+        : "LUB of @Format(INT) and @Format(UNUSED) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAnno, formatGeneralAnno), formatIntAnno)
+        : "LUB of @Format(INT) and @Format(GENERAL) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAnno, formatCharAnno), formatCharAndIntAnno)
+        : "LUB of @Format(INT) and @Format(CHAR) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(formatIntAnno, formatIntAnno), formatIntAnno)
+        : "LUB of @Format(INT) and @Format(INT) is not @Format(INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAnno, formatTimeAnno), formatIntAndTimeAnno)
+        : "LUB of @Format(INT) and @Format(TIME) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAnno, formatFloatAnno), formatNullAnno)
+        : "LUB of @Format(INT) and @Format(FLOAT) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAnno, formatCharAndIntAnno), formatCharAndIntAnno)
+        : "LUB of @Format(INT) and @Format(CHAR_AND_INT) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAnno, formatIntAndTimeAnno), formatIntAndTimeAnno)
+        : "LUB of @Format(INT) and @Format(INT_AND_TIME) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAnno, formatNullAnno), formatNullAnno)
+        : "LUB of @Format(INT) and @Format(NULL) is not @Format(NULL)!";
+
+    // LUB of TIME and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTimeAnno, formatUnusedAnno), formatTimeAnno)
+        : "LUB of @Format(TIME) and @Format(UNUSED) is not @Format(TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTimeAnno, formatGeneralAnno), formatTimeAnno)
+        : "LUB of @Format(TIME) and @Format(GENERAL) is not @Format(TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTimeAnno, formatCharAnno), formatNullAnno)
+        : "LUB of @Format(TIME) and @Format(CHAR) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTimeAnno, formatIntAnno), formatIntAndTimeAnno)
+        : "LUB of @Format(TIME) and @Format(INT) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTimeAnno, formatTimeAnno), formatTimeAnno)
+        : "LUB of @Format(TIME) and @Format(TIME) is not @Format(TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTimeAnno, formatFloatAnno), formatNullAnno)
+        : "LUB of @Format(TIME) and @Format(FLOAT) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTimeAnno, formatCharAndIntAnno), formatNullAnno)
+        : "LUB of @Format(TIME) and @Format(CHAR_AND_INT) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTimeAnno, formatIntAndTimeAnno), formatIntAndTimeAnno)
+        : "LUB of @Format(TIME) and @Format(INT_AND_TIME) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTimeAnno, formatNullAnno), formatNullAnno)
+        : "LUB of @Format(TIME) and @Format(NULL) is not @Format(NULL)!";
+
+    // LUB of FLOAT and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatFloatAnno, formatUnusedAnno), formatFloatAnno)
+        : "LUB of @Format(FLOAT) and @Format(UNUSED) is not @Format(FLOAT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatFloatAnno, formatGeneralAnno), formatFloatAnno)
+        : "LUB of @Format(FLOAT) and @Format(GENERAL) is not @Format(FLOAT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatFloatAnno, formatCharAnno), formatNullAnno)
+        : "LUB of @Format(FLOAT) and @Format(CHAR) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatFloatAnno, formatIntAnno), formatNullAnno)
+        : "LUB of @Format(FLOAT) and @Format(INT) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatFloatAnno, formatTimeAnno), formatNullAnno)
+        : "LUB of @Format(FLOAT) and @Format(TIME) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatFloatAnno, formatFloatAnno), formatFloatAnno)
+        : "LUB of @Format(FLOAT) and @Format(FLOAT) is not @Format(FLOAT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatFloatAnno, formatCharAndIntAnno), formatNullAnno)
+        : "LUB of @Format(FLOAT) and @Format(CHAR_AND_INT) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatFloatAnno, formatIntAndTimeAnno), formatNullAnno)
+        : "LUB of @Format(FLOAT) and @Format(INT_AND_TIME) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatFloatAnno, formatNullAnno), formatNullAnno)
+        : "LUB of @Format(FLOAT) and @Format(NULL) is not @Format(NULL)!";
+
+    // LUB of CHAR_AND_INT and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAndIntAnno, formatUnusedAnno), formatCharAndIntAnno)
+        : "LUB of @Format(CHAR_AND_INT) and @Format(UNUSED) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAndIntAnno, formatGeneralAnno), formatCharAndIntAnno)
+        : "LUB of @Format(CHAR_AND_INT) and @Format(GENERAL) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAndIntAnno, formatCharAnno), formatCharAndIntAnno)
+        : "LUB of @Format(CHAR_AND_INT) and @Format(CHAR) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAndIntAnno, formatIntAnno), formatCharAndIntAnno)
+        : "LUB of @Format(CHAR_AND_INT) and @Format(INT) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAndIntAnno, formatTimeAnno), formatNullAnno)
+        : "LUB of @Format(CHAR_AND_INT) and @Format(TIME) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAndIntAnno, formatFloatAnno), formatNullAnno)
+        : "LUB of @Format(CHAR_AND_INT) and @Format(FLOAT) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAndIntAnno, formatCharAndIntAnno), formatCharAndIntAnno)
+        : "LUB of @Format(CHAR_AND_INT) and @Format(CHAR_AND_INT) is not @Format(CHAR_AND_INT)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAndIntAnno, formatIntAndTimeAnno), formatNullAnno)
+        : "LUB of @Format(CHAR_AND_INT) and @Format(INT_AND_TIME) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatCharAndIntAnno, formatNullAnno), formatNullAnno)
+        : "LUB of @Format(CHAR_AND_INT) and @Format(NULL) is not @Format(NULL)!";
+
+    // LUB of INT_AND_TIME and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAndTimeAnno, formatUnusedAnno), formatIntAndTimeAnno)
+        : "LUB of @Format(INT_AND_TIME) and @Format(UNUSED) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAndTimeAnno, formatGeneralAnno), formatIntAndTimeAnno)
+        : "LUB of @Format(INT_AND_TIME) and @Format(GENERAL) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAndTimeAnno, formatCharAnno), formatNullAnno)
+        : "LUB of @Format(INT_AND_TIME) and @Format(CHAR) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAndTimeAnno, formatIntAnno), formatIntAndTimeAnno)
+        : "LUB of @Format(INT_AND_TIME) and @Format(INT) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAndTimeAnno, formatTimeAnno), formatIntAndTimeAnno)
+        : "LUB of @Format(INT_AND_TIME) and @Format(TIME) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAndTimeAnno, formatFloatAnno), formatNullAnno)
+        : "LUB of @Format(INT_AND_TIME) and @Format(FLOAT) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAndTimeAnno, formatCharAndIntAnno), formatNullAnno)
+        : "LUB of @Format(INT_AND_TIME) and @Format(CHAR_AND_INT) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAndTimeAnno, formatIntAndTimeAnno), formatIntAndTimeAnno)
+        : "LUB of @Format(INT_AND_TIME) and @Format(INT_AND_TIME) is not @Format(INT_AND_TIME)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatIntAndTimeAnno, formatNullAnno), formatNullAnno)
+        : "LUB of @Format(INT_AND_TIME) and @Format(NULL) is not @Format(NULL)!";
+
+    // LUB of NULL and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatNullAnno, formatUnusedAnno), formatNullAnno)
+        : "LUB of @Format(NULL) and @Format(UNUSED) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatNullAnno, formatGeneralAnno), formatNullAnno)
+        : "LUB of @Format(NULL) and @Format(GENERAL) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatNullAnno, formatCharAnno), formatNullAnno)
+        : "LUB of @Format(NULL) and @Format(CHAR) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatNullAnno, formatIntAnno), formatNullAnno)
+        : "LUB of @Format(NULL) and @Format(INT) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatNullAnno, formatTimeAnno), formatNullAnno)
+        : "LUB of @Format(NULL) and @Format(TIME) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatNullAnno, formatFloatAnno), formatNullAnno)
+        : "LUB of @Format(NULL) and @Format(FLOAT) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatNullAnno, formatCharAndIntAnno), formatNullAnno)
+        : "LUB of @Format(NULL) and @Format(CHAR_AND_INT) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatNullAnno, formatIntAndTimeAnno), formatNullAnno)
+        : "LUB of @Format(NULL) and @Format(INT_AND_TIME) is not @Format(NULL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatNullAnno, formatNullAnno), formatNullAnno)
+        : "LUB of @Format(NULL) and @Format(NULL) is not @Format(NULL)!";
+
+    // Now test with two ConversionCategory at a time:
+
+    cc2[0] = ConversionCategory.CHAR_AND_INT;
+    cc2[1] = ConversionCategory.NULL;
+    AnnotationMirror formatTwoConvCat4 = treeUtil.categoriesToFormatAnnotation(cc2);
+    cc2[0] = ConversionCategory.NULL;
+    cc2[1] = ConversionCategory.CHAR;
+    AnnotationMirror formatTwoConvCat5 = treeUtil.categoriesToFormatAnnotation(cc2);
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTwoConvCat1, formatTwoConvCat2), formatTwoConvCat4)
+        : "LUB of @Format([CHAR_AND_INT,FLOAT]) and @Format([INT,CHAR]) is not"
+            + " @Format([CHAR_AND_INT,NULL])!";
+
+    // Test that the LUB of two ConversionCategory arrays of different sizes is an array of the
+    // largest size of the two:
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatGeneralAnno, formatTwoConvCat1), formatTwoConvCat1)
+        : "LUB of @I18nFormat(GENERAL) and @I18nFormat([CHAR_AND_INT,FLOAT]) is not"
+            + " @I18nFormat([CHAR_AND_INT,FLOAT])!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTwoConvCat2, formatNullAnno), formatTwoConvCat5)
+        : "LUB of @I18nFormat([INT,CHAR]) and @I18nFormat(NULL) is not @I18nFormat([NULL,CHAR])!";
+
+    // LUB of @UnknownFormat and others
+
+    assert AnnotationUtils.areSame(qh.leastUpperBound(UNKNOWNFORMAT, UNKNOWNFORMAT), UNKNOWNFORMAT)
+        : "LUB of @UnknownFormat and @UnknownFormat is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(UNKNOWNFORMAT, FORMAT), UNKNOWNFORMAT)
+        : "LUB of @UnknownFormat and @Format(null) is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(UNKNOWNFORMAT, formatUnusedAnno), UNKNOWNFORMAT)
+        : "LUB of @UnknownFormat and @Format(UNUSED) is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(UNKNOWNFORMAT, INVALIDFORMAT), UNKNOWNFORMAT)
+        : "LUB of @UnknownFormat and @InvalidFormat(null) is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(UNKNOWNFORMAT, invalidFormatWithMessage), UNKNOWNFORMAT)
+        : "LUB of @UnknownFormat and @InvalidFormat(\"Message\") is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(UNKNOWNFORMAT, FORMATBOTTOM), UNKNOWNFORMAT)
+        : "LUB of @UnknownFormat and @FormatBottom is not @UnknownFormat!";
+
+    // LUB of @Format(null) and others
+
+    assert AnnotationUtils.areSame(qh.leastUpperBound(FORMAT, UNKNOWNFORMAT), UNKNOWNFORMAT)
+        : "LUB of @Format(null) and @UnknownFormat is not @UnknownFormat!";
+    // Computing the LUB of @Format(null) and @Format(null) should never occur in practice.
+    // Skipping this case as it causes an expected crash.
+    // Computing the LUB of @Format(null) and @Format with a value should never occur in
+    // practice. Skipping this case as it causes an expected crash.
+    assert AnnotationUtils.areSame(qh.leastUpperBound(FORMAT, INVALIDFORMAT), UNKNOWNFORMAT)
+        : "LUB of @Format(null) and @InvalidFormat(null) is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(FORMAT, invalidFormatWithMessage), UNKNOWNFORMAT)
+        : "LUB of @Format(null) and @InvalidFormat(\"Message\") is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(FORMAT, FORMATBOTTOM), FORMAT)
+        : "LUB of @Format(null) and @FormatBottom is not @Format(null)!";
+
+    // LUB of @Format(UNUSED) and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, UNKNOWNFORMAT), UNKNOWNFORMAT)
+        : "LUB of @Format(UNUSED) and @UnknownFormat is not @UnknownFormat!";
+    // Computing the LUB of @Format with a value and @Format(null) should never occur in
+    // practice. Skipping this case as it causes an expected crash.
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, formatUnusedAnno), formatUnusedAnno)
+        : "LUB of @Format(UNUSED) and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, INVALIDFORMAT), UNKNOWNFORMAT)
+        : "LUB of @Format(UNUSED) and @InvalidFormat(null) is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, invalidFormatWithMessage), UNKNOWNFORMAT)
+        : "LUB of @Format(UNUSED) and @InvalidFormat(\"Message\") is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatUnusedAnno, FORMATBOTTOM), formatUnusedAnno)
+        : "LUB of @Format(UNUSED) and @FormatBottom is not @Format(UNUSED)!";
+
+    // LUB of @InvalidFormat(null) and others
+
+    assert AnnotationUtils.areSame(qh.leastUpperBound(INVALIDFORMAT, UNKNOWNFORMAT), UNKNOWNFORMAT)
+        : "LUB of @InvalidFormat(null) and @UnknownFormat is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(INVALIDFORMAT, FORMAT), UNKNOWNFORMAT)
+        : "LUB of @InvalidFormat(null) and @Format(null) is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(INVALIDFORMAT, formatUnusedAnno), UNKNOWNFORMAT)
+        : "LUB of @InvalidFormat(null) and @Format(UNUSED) is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(INVALIDFORMAT, FORMATBOTTOM), INVALIDFORMAT)
+        : "LUB of @InvalidFormat(null) and @FormatBottom is not @InvalidFormat(null)!";
+
+    // LUB of @InvalidFormat("Message") and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(invalidFormatWithMessage, UNKNOWNFORMAT), UNKNOWNFORMAT)
+        : "LUB of @InvalidFormat(\"Message\") and @UnknownFormat is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(invalidFormatWithMessage, FORMAT), UNKNOWNFORMAT)
+        : "LUB of @InvalidFormat(\"Message\") and @Format(null) is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(invalidFormatWithMessage, formatUnusedAnno), UNKNOWNFORMAT)
+        : "LUB of @InvalidFormat(\"Message\") and @Format(UNUSED) is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(invalidFormatWithMessage, invalidFormatWithMessage),
+            invalidFormatWithMessage)
+        : "LUB of @InvalidFormat(\"Message\") and @InvalidFormat(\"Message\") is not"
+            + " @InvalidFormat(\"Message\")!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(invalidFormatWithMessage, invalidFormatWithMessage2),
+            invalidFormatWithMessagesOred)
+        : "LUB of @InvalidFormat(\"Message\") and @InvalidFormat(\"Message2\") is not"
+            + " @InvalidFormat(\"(\"Message\" or \"Message2\")\")!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(invalidFormatWithMessage, FORMATBOTTOM), invalidFormatWithMessage)
+        : "LUB of @InvalidFormat(\"Message\") and @FormatBottom is not"
+            + " @InvalidFormat(\"Message\")!";
+
+    // LUB of @FormatBottom and others
+
+    assert AnnotationUtils.areSame(qh.leastUpperBound(FORMATBOTTOM, UNKNOWNFORMAT), UNKNOWNFORMAT)
+        : "LUB of @FormatBottom and @UnknownFormat is not @UnknownFormat!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(FORMATBOTTOM, FORMAT), FORMAT)
+        : "LUB of @FormatBottom and @Format(null) is not @Format(null)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(FORMATBOTTOM, formatUnusedAnno), formatUnusedAnno)
+        : "LUB of @FormatBottom and @Format(UNUSED) is not @Format(UNUSED)!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(FORMATBOTTOM, INVALIDFORMAT), INVALIDFORMAT)
+        : "LUB of @FormatBottom and @InvalidFormat(null) is not @InvalidFormat(null)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(FORMATBOTTOM, invalidFormatWithMessage), invalidFormatWithMessage)
+        : "LUB of @FormatBottom and @InvalidFormat(\"Message\") is not"
+            + " @InvalidFormat(\"Message\")!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(FORMATBOTTOM, FORMATBOTTOM), FORMATBOTTOM)
+        : "LUB of @FormatBottom and @FormatBottom is not @FormatBottom!";
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/I18nFormatterLubGlbChecker.java b/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/I18nFormatterLubGlbChecker.java
new file mode 100644
index 0000000..fdbd294
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/I18nFormatterLubGlbChecker.java
@@ -0,0 +1,777 @@
+package org.checkerframework.checker.testchecker.lubglb;
+
+// Test case for issues 723 and 756.
+// https://github.com/typetools/checker-framework/issues/723
+// https://github.com/typetools/checker-framework/issues/756
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.i18nformatter.I18nFormatterAnnotatedTypeFactory;
+import org.checkerframework.checker.i18nformatter.I18nFormatterChecker;
+import org.checkerframework.checker.i18nformatter.I18nFormatterTreeUtil;
+import org.checkerframework.checker.i18nformatter.I18nFormatterVisitor;
+import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
+import org.checkerframework.checker.i18nformatter.qual.I18nFormat;
+import org.checkerframework.checker.i18nformatter.qual.I18nFormatBottom;
+import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor;
+import org.checkerframework.checker.i18nformatter.qual.I18nInvalidFormat;
+import org.checkerframework.checker.i18nformatter.qual.I18nUnknownFormat;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * This class tests the implementation of GLB computation in the I18n Format String Checker (see
+ * issue 723), but it does not test for the crash that occurs if I18nFormatterAnnotatedTypeFactory
+ * does not override greatestLowerBound. That is done by tests/all-systems/Issue691.java. It also
+ * tests the implementation of LUB computation in the I18n Format String Checker.
+ */
+public class I18nFormatterLubGlbChecker extends I18nFormatterChecker {
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new I18nFormatterVisitor(this) {
+      @Override
+      protected I18nFormatterAnnotatedTypeFactory createTypeFactory() {
+        return new I18nFormatterLubGlbAnnotatedTypeFactory(checker);
+      }
+    };
+  }
+
+  /** I18nFormatterLubGlbAnnotatedTypeFactory. */
+  private static class I18nFormatterLubGlbAnnotatedTypeFactory
+      extends I18nFormatterAnnotatedTypeFactory {
+
+    /**
+     * Constructor.
+     *
+     * @param checker checker
+     */
+    public I18nFormatterLubGlbAnnotatedTypeFactory(BaseTypeChecker checker) {
+      super(checker);
+      postInit();
+    }
+
+    @Override
+    protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+      return new HashSet<>(
+          Arrays.asList(
+              I18nUnknownFormat.class,
+              I18nFormatBottom.class,
+              I18nFormat.class,
+              I18nInvalidFormat.class,
+              I18nFormatFor.class));
+    }
+  }
+
+  @SuppressWarnings("checkstyle:localvariablename")
+  @Override
+  public void initChecker() {
+    super.initChecker();
+    I18nFormatterTreeUtil treeUtil = new I18nFormatterTreeUtil(this);
+
+    Elements elements = getElementUtils();
+    AnnotationMirror I18NUNKNOWNFORMAT =
+        AnnotationBuilder.fromClass(elements, I18nUnknownFormat.class);
+    AnnotationMirror I18NFORMAT =
+        AnnotationBuilder.fromClass(
+            elements,
+            I18nFormat.class,
+            AnnotationBuilder.elementNamesValues("value", new I18nConversionCategory[0]));
+    AnnotationMirror I18NINVALIDFORMAT =
+        AnnotationBuilder.fromClass(
+            elements,
+            I18nInvalidFormat.class,
+            AnnotationBuilder.elementNamesValues("value", "dummy"));
+    AnnotationMirror I18NFORMATFOR =
+        AnnotationBuilder.fromClass(
+            elements, I18nFormatFor.class, AnnotationBuilder.elementNamesValues("value", "dummy"));
+    AnnotationMirror I18NFORMATBOTTOM =
+        AnnotationBuilder.fromClass(elements, I18nFormatBottom.class);
+
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class);
+    builder.setValue("value", "Message");
+    AnnotationMirror i18nInvalidFormatWithMessage = builder.build();
+
+    builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class);
+    builder.setValue("value", "Message2");
+    AnnotationMirror i18nInvalidFormatWithMessage2 = builder.build();
+
+    builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class);
+    builder.setValue("value", "(\"Message\" or \"Message2\")");
+    AnnotationMirror i18nInvalidFormatWithMessagesOred = builder.build();
+
+    builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class);
+    builder.setValue("value", "(\"Message\" and \"Message2\")");
+    AnnotationMirror i18nInvalidFormatWithMessagesAnded = builder.build();
+
+    builder = new AnnotationBuilder(processingEnv, I18nFormatFor.class);
+    builder.setValue("value", "#1");
+    AnnotationMirror i18nFormatForWithValue1 = builder.build();
+
+    builder = new AnnotationBuilder(processingEnv, I18nFormatFor.class);
+    builder.setValue("value", "#2");
+    AnnotationMirror i18nFormatForWithValue2 = builder.build();
+
+    I18nConversionCategory[] cc = new I18nConversionCategory[1];
+
+    cc[0] = I18nConversionCategory.UNUSED;
+    AnnotationMirror i18nFormatUnusedAnno = treeUtil.categoriesToFormatAnnotation(cc);
+    cc[0] = I18nConversionCategory.GENERAL;
+    AnnotationMirror i18nFormatGeneralAnno = treeUtil.categoriesToFormatAnnotation(cc);
+    cc[0] = I18nConversionCategory.DATE;
+    AnnotationMirror i18nFormatDateAnno = treeUtil.categoriesToFormatAnnotation(cc);
+    cc[0] = I18nConversionCategory.NUMBER;
+    AnnotationMirror i18nFormatNumberAnno = treeUtil.categoriesToFormatAnnotation(cc);
+
+    QualifierHierarchy qh = ((BaseTypeVisitor<?>) visitor).getTypeFactory().getQualifierHierarchy();
+
+    // ** GLB tests **
+
+    // GLB of UNUSED and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatUnusedAnno, i18nFormatUnusedAnno), i18nFormatUnusedAnno)
+        : "GLB of @I18nFormat(UNUSED) and @I18nFormat(UNUSED) is not @I18nFormat(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatUnusedAnno, i18nFormatGeneralAnno),
+            i18nFormatUnusedAnno)
+        : "GLB of @I18nFormat(UNUSED) and @I18nFormat(GENERAL) is not @I18nFormat(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatUnusedAnno, i18nFormatDateAnno), i18nFormatUnusedAnno)
+        : "GLB of @I18nFormat(UNUSED) and @I18nFormat(DATE) is not @I18nFormat(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatUnusedAnno, i18nFormatNumberAnno), i18nFormatUnusedAnno)
+        : "GLB of @I18nFormat(UNUSED) and @I18nFormat(NUMBER) is not @I18nFormat(UNUSED)!";
+
+    // GLB of GENERAL and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatGeneralAnno, i18nFormatUnusedAnno),
+            i18nFormatUnusedAnno)
+        : "GLB of @I18nFormat(GENERAL) and @I18nFormat(UNUSED) is not @I18nFormat(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatGeneralAnno, i18nFormatGeneralAnno),
+            i18nFormatGeneralAnno)
+        : "GLB of @I18nFormat(GENERAL) and @I18nFormat(GENERAL) is not @I18nFormat(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatGeneralAnno, i18nFormatDateAnno), i18nFormatGeneralAnno)
+        : "GLB of @I18nFormat(GENERAL) and @I18nFormat(DATE) is not @I18nFormat(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatGeneralAnno, i18nFormatNumberAnno),
+            i18nFormatGeneralAnno)
+        : "GLB of @I18nFormat(GENERAL) and @I18nFormat(NUMBER) is not @I18nFormat(GENERAL)!";
+
+    // GLB of DATE and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatDateAnno, i18nFormatUnusedAnno), i18nFormatUnusedAnno)
+        : "GLB of @I18nFormat(DATE) and @I18nFormat(UNUSED) is not @I18nFormat(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatDateAnno, i18nFormatGeneralAnno), i18nFormatGeneralAnno)
+        : "GLB of @I18nFormat(DATE) and @I18nFormat(GENERAL) is not @I18nFormat(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatDateAnno, i18nFormatDateAnno), i18nFormatDateAnno)
+        : "GLB of @I18nFormat(DATE) and @I18nFormat(DATE) is not @I18nFormat(DATE)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatDateAnno, i18nFormatNumberAnno), i18nFormatDateAnno)
+        : "GLB of @I18nFormat(DATE) and @I18nFormat(NUMBER) is not @I18nFormat(DATE)!";
+
+    // GLB of NUMBER and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatNumberAnno, i18nFormatUnusedAnno), i18nFormatUnusedAnno)
+        : "GLB of @I18nFormat(NUMBER) and @I18nFormat(UNUSED) is not @I18nFormat(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatNumberAnno, i18nFormatGeneralAnno),
+            i18nFormatGeneralAnno)
+        : "GLB of @I18nFormat(NUMBER) and @I18nFormat(GENERAL) is not @I18nFormat(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatNumberAnno, i18nFormatDateAnno), i18nFormatDateAnno)
+        : "GLB of @I18nFormat(NUMBER) and @I18nFormat(DATE) is not @I18nFormat(DATE)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatNumberAnno, i18nFormatNumberAnno), i18nFormatNumberAnno)
+        : "GLB of @I18nFormat(NUMBER) and @I18nFormat(NUMBER) is not @I18nFormat(NUMBER)!";
+
+    // Now test with two I18nConversionCategory at a time:
+
+    I18nConversionCategory[] cc2 = new I18nConversionCategory[2];
+
+    cc2[0] = I18nConversionCategory.DATE;
+    cc2[1] = I18nConversionCategory.DATE;
+    AnnotationMirror formatTwoConvCat1 = treeUtil.categoriesToFormatAnnotation(cc2);
+    cc2[0] = I18nConversionCategory.UNUSED;
+    cc2[1] = I18nConversionCategory.NUMBER;
+    AnnotationMirror formatTwoConvCat2 = treeUtil.categoriesToFormatAnnotation(cc2);
+    cc2[0] = I18nConversionCategory.UNUSED;
+    cc2[1] = I18nConversionCategory.DATE;
+    AnnotationMirror formatTwoConvCat3 = treeUtil.categoriesToFormatAnnotation(cc2);
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTwoConvCat1, formatTwoConvCat2), formatTwoConvCat3)
+        : "GLB of @I18nFormat([DATE,DATE]) and @I18nFormat([UNUSED,NUMBER]) is not"
+            + " @I18nFormat([UNUSED,DATE])!";
+
+    // Test that the GLB of two I18nConversionCategory arrays of different sizes is an array of
+    // the smallest size of the two:
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatGeneralAnno, formatTwoConvCat1), i18nFormatGeneralAnno)
+        : "GLB of @I18nFormat(GENERAL) and @I18nFormat([DATE,DATE]) is not @I18nFormat(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(formatTwoConvCat2, i18nFormatDateAnno), i18nFormatUnusedAnno)
+        : "GLB of @I18nFormat([UNUSED,NUMBER]) and @I18nFormat(DATE) is not @I18nFormat(UNUSED)!";
+
+    // GLB of two distinct @I18nFormatFor(...) annotations is @I18nFormatBottom
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatForWithValue1, i18nFormatForWithValue2),
+            I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(\"#1\") and @I18nFormatFor(\"#2\") is not @I18nFormatBottom!";
+
+    // GLB of @I18nUnknownFormat and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT), I18NUNKNOWNFORMAT)
+        : "GLB of @I18nUnknownFormat and @I18nUnknownFormat is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(I18NUNKNOWNFORMAT, I18NFORMAT), I18NFORMAT)
+        : "GLB of @I18nUnknownFormat and @I18nFormat(null) is not @I18nFormat(null)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NUNKNOWNFORMAT, i18nFormatUnusedAnno), i18nFormatUnusedAnno)
+        : "GLB of @I18nUnknownFormat and @I18nFormat(UNUSED) is not @I18nFormat(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NUNKNOWNFORMAT, I18NINVALIDFORMAT), I18NINVALIDFORMAT)
+        : "GLB of @I18nUnknownFormat and @I18nInvalidFormat(null) is not @I18nInvalidFormat(null)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage),
+            i18nInvalidFormatWithMessage)
+        : "GLB of @I18nUnknownFormat and @I18nInvalidFormat(\"Message\") is not"
+            + " @I18nInvalidFormat(\"Message\")!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NUNKNOWNFORMAT, I18NFORMATFOR), I18NFORMATFOR)
+        : "GLB of @I18nUnknownFormat and @I18nFormatFor(null) is not @I18nFormatFor(null)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NUNKNOWNFORMAT, i18nFormatForWithValue1),
+            i18nFormatForWithValue1)
+        : "GLB of @I18nUnknownFormat and @I18nFormatFor(\"#1\") is not @I18nFormatFor(\"#1\")!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NUNKNOWNFORMAT, I18NFORMATBOTTOM), I18NFORMATBOTTOM)
+        : "GLB of @I18nUnknownFormat and @I18nFormatBottom is not @I18nFormatBottom!";
+
+    // GLB of @I18nFormat(null) and others
+
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(I18NFORMAT, I18NUNKNOWNFORMAT), I18NFORMAT)
+        : "GLB of @I18nFormat(null) and @I18nUnknownFormat is not @I18nFormat(null)!";
+    // Computing the GLB of @I18nFormat(null) and @I18nFormat(null) should never occur in
+    // practice. Skipping this case as it causes an expected crash.
+    // Computing the GLB of @I18nFormat(null) and @I18nFormat with a value should never occur in
+    // practice. Skipping this case as it causes an expected crash.
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMAT, I18NINVALIDFORMAT), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormat(null) and @I18nInvalidFormat(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMAT, i18nInvalidFormatWithMessage), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormat(null) and @I18nInvalidFormat(\"Message\") is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMAT, I18NFORMATFOR), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormat(null) and @I18nFormatFor(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMAT, i18nFormatForWithValue1), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormat(null) and @I18nFormatFor(\"#1\") is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMAT, I18NFORMATBOTTOM), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormat(null) and @I18nFormatBottom is not @I18nFormatBottom!";
+
+    // GLB of @I18nFormat(UNUSED) and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatUnusedAnno, I18NUNKNOWNFORMAT), i18nFormatUnusedAnno)
+        : "GLB of @I18nFormat(UNUSED) and @I18nUnknownFormat is not @I18nFormat(UNUSED)!";
+    // Computing the GLB of @I18nFormat with a value and @I18nFormat(null) should never occur in
+    // practice. Skipping this case as it causes an expected crash.
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatUnusedAnno, i18nFormatUnusedAnno), i18nFormatUnusedAnno)
+        : "GLB of @I18nFormat(UNUSED) and @I18nFormat(UNUSED) is not @I18nFormat(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatUnusedAnno, I18NINVALIDFORMAT), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormat(UNUSED) and @I18nInvalidFormat(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatUnusedAnno, i18nInvalidFormatWithMessage),
+            I18NFORMATBOTTOM)
+        : "GLB of @I18nFormat(UNUSED) and @I18nInvalidFormat(\"Message\") is not"
+            + " @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatUnusedAnno, I18NFORMATFOR), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormat(UNUSED) and @I18nFormatFor(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatUnusedAnno, i18nFormatForWithValue1), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormat(UNUSED) and @I18nFormatFor(\"#1\") is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatUnusedAnno, I18NFORMATBOTTOM), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormat(UNUSED) and @I18nFormatBottom is not @I18nFormatBottom!";
+
+    // GLB of @I18nInvalidFormat(null) and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NINVALIDFORMAT, I18NUNKNOWNFORMAT), I18NINVALIDFORMAT)
+        : "GLB of @I18nInvalidFormat(null) and @I18nUnknownFormat is not @I18nInvalidFormat(null)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NINVALIDFORMAT, I18NFORMAT), I18NFORMATBOTTOM)
+        : "GLB of @I18nInvalidFormat(null) and @I18nFormat(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NINVALIDFORMAT, i18nFormatUnusedAnno), I18NFORMATBOTTOM)
+        : "GLB of @I18nInvalidFormat(null) and @I18nFormat(UNUSED) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NINVALIDFORMAT, I18NFORMATFOR), I18NFORMATBOTTOM)
+        : "GLB of @I18nInvalidFormat(null) and @I18nFormatFor(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NINVALIDFORMAT, i18nFormatForWithValue1), I18NFORMATBOTTOM)
+        : "GLB of @I18nInvalidFormat(null) and @I18nFormatFor(\"#1\") is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NINVALIDFORMAT, I18NFORMATBOTTOM), I18NFORMATBOTTOM)
+        : "GLB of @I18nInvalidFormat(null) and @I18nFormatBottom is not @I18nFormatBottom!";
+
+    // GLB of @I18nInvalidFormat("Message") and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT),
+            i18nInvalidFormatWithMessage)
+        : "GLB of @I18nInvalidFormat(\"Message\") and @I18nUnknownFormat is not"
+            + " @I18nInvalidFormat(\"Message\")!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nInvalidFormatWithMessage, I18NFORMAT), I18NFORMATBOTTOM)
+        : "GLB of @I18nInvalidFormat(\"Message\") and @I18nFormat(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nInvalidFormatWithMessage, i18nFormatUnusedAnno),
+            I18NFORMATBOTTOM)
+        : "GLB of @I18nInvalidFormat(\"Message\") and @I18nFormat(UNUSED) is not"
+            + " @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage),
+            i18nInvalidFormatWithMessage)
+        : "GLB of @I18nInvalidFormat(\"Message\") and @I18nInvalidFormat(\"Message\") is not"
+            + " @I18nInvalidFormat(\"Message\")!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage2),
+            i18nInvalidFormatWithMessagesAnded)
+        : "GLB of @I18nInvalidFormat(\"Message\") and @I18nInvalidFormat(\"Message2\") is not"
+            + " @I18nInvalidFormat(\"(\"Message\" and \"Message2\")\")!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nInvalidFormatWithMessage, I18NFORMATFOR), I18NFORMATBOTTOM)
+        : "GLB of @I18nInvalidFormat(\"Message\") and @I18nFormatFor(null) is not"
+            + " @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nInvalidFormatWithMessage, i18nFormatForWithValue1),
+            I18NFORMATBOTTOM)
+        : "GLB of @I18nInvalidFormat(\"Message\") and @I18nFormatFor(\"#1\") is not"
+            + " @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nInvalidFormatWithMessage, I18NFORMATBOTTOM), I18NFORMATBOTTOM)
+        : "GLB of @I18nInvalidFormat(\"Message\") and @I18nFormatBottom is not @I18nFormatBottom!";
+
+    // GLB of @I18nFormatFor(null) and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATFOR, I18NUNKNOWNFORMAT), I18NFORMATFOR)
+        : "GLB of @I18nFormatFor(null) and @I18nUnknownFormat is not @I18nFormatFor(null)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATFOR, I18NFORMAT), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(null) and @I18nFormat(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATFOR, i18nFormatUnusedAnno), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(null) and @I18nFormat(UNUSED) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATFOR, I18NINVALIDFORMAT), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(null) and @I18nInvalidFormat(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATFOR, i18nInvalidFormatWithMessage), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(null) and @I18nInvalidFormat(\"Message\") is not"
+            + " @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATFOR, I18NFORMATFOR), I18NFORMATFOR)
+        : "GLB of @I18nFormatFor(null) and @I18nFormatFor(null) is not @I18nFormatFor(null)!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATFOR, i18nFormatForWithValue1), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(null) and @I18nFormatFor(\"#1\") is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATFOR, I18NFORMATBOTTOM), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(null) and @I18nFormatBottom is not @I18nFormatBottom!";
+
+    // GLB of @I18nFormatFor("#1") and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatForWithValue1, I18NUNKNOWNFORMAT),
+            i18nFormatForWithValue1)
+        : "GLB of @I18nFormatFor(\"#1\") and @I18nUnknownFormat is not @I18nFormatFor(\"#1\")!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatForWithValue1, I18NFORMAT), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(\"#1\") and @I18nFormat(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatForWithValue1, i18nFormatUnusedAnno), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(\"#1\") and @I18nFormat(UNUSED) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatForWithValue1, I18NINVALIDFORMAT), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(\"#1\") and @I18nInvalidFormat(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatForWithValue1, i18nInvalidFormatWithMessage),
+            I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(\"#1\") and @I18nInvalidFormat(\"Message\") is not"
+            + " @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatForWithValue1, I18NFORMATFOR), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(\"#1\") and @I18nFormatFor(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatForWithValue1, i18nFormatForWithValue1),
+            i18nFormatForWithValue1)
+        : "GLB of @I18nFormatFor(\"#1\") and @I18nFormatFor(\"#1\") is not @I18nFormatFor(\"#1\")!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(i18nFormatForWithValue1, I18NFORMATBOTTOM), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatFor(\"#1\") and @I18nFormatBottom is not @I18nFormatBottom!";
+
+    // GLB of @I18nFormatBottom and others
+
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATBOTTOM, I18NUNKNOWNFORMAT), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatBottom and @I18nUnknownFormat is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATBOTTOM, I18NFORMAT), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatBottom and @I18nFormat(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATBOTTOM, i18nFormatUnusedAnno), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatBottom and @I18nFormat(UNUSED) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATBOTTOM, I18NINVALIDFORMAT), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatBottom and @I18nInvalidFormat(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATBOTTOM, i18nInvalidFormatWithMessage), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatBottom and @I18nInvalidFormat(\"Message\") is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATBOTTOM, I18NFORMATFOR), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatBottom and @I18nFormatFor(null) is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATBOTTOM, i18nFormatForWithValue1), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatBottom and @I18nFormatFor(\"#1\") is not @I18nFormatBottom!";
+    assert AnnotationUtils.areSame(
+            qh.greatestLowerBound(I18NFORMATBOTTOM, I18NFORMATBOTTOM), I18NFORMATBOTTOM)
+        : "GLB of @I18nFormatBottom and @I18nFormatBottom is not @I18nFormatBottom!";
+
+    // ** LUB tests **
+
+    // LUB of UNUSED and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatUnusedAnno, i18nFormatUnusedAnno), i18nFormatUnusedAnno)
+        : "LUB of @I18nFormat(UNUSED) and @I18nFormat(UNUSED) is not @I18nFormat(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatUnusedAnno, i18nFormatGeneralAnno), i18nFormatGeneralAnno)
+        : "LUB of @I18nFormat(UNUSED) and @I18nFormat(GENERAL) is not @I18nFormat(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatUnusedAnno, i18nFormatDateAnno), i18nFormatDateAnno)
+        : "LUB of @I18nFormat(UNUSED) and @I18nFormat(DATE) is not @I18nFormat(DATE)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatUnusedAnno, i18nFormatNumberAnno), i18nFormatNumberAnno)
+        : "LUB of @I18nFormat(UNUSED) and @I18nFormat(NUMBER) is not @I18nFormat(NUMBER)!";
+
+    // LUB of GENERAL and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatGeneralAnno, i18nFormatUnusedAnno), i18nFormatGeneralAnno)
+        : "LUB of @I18nFormat(GENERAL) and @I18nFormat(UNUSED) is not @I18nFormat(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatGeneralAnno, i18nFormatGeneralAnno), i18nFormatGeneralAnno)
+        : "LUB of @I18nFormat(GENERAL) and @I18nFormat(GENERAL) is not @I18nFormat(GENERAL)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatGeneralAnno, i18nFormatDateAnno), i18nFormatDateAnno)
+        : "LUB of @I18nFormat(GENERAL) and @I18nFormat(DATE) is not @I18nFormat(DATE)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatGeneralAnno, i18nFormatNumberAnno), i18nFormatNumberAnno)
+        : "LUB of @I18nFormat(GENERAL) and @I18nFormat(NUMBER) is not @I18nFormat(NUMBER)!";
+
+    // LUB of DATE and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatDateAnno, i18nFormatUnusedAnno), i18nFormatDateAnno)
+        : "LUB of @I18nFormat(DATE) and @I18nFormat(UNUSED) is not @I18nFormat(DATE)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatDateAnno, i18nFormatGeneralAnno), i18nFormatDateAnno)
+        : "LUB of @I18nFormat(DATE) and @I18nFormat(GENERAL) is not @I18nFormat(DATE)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatDateAnno, i18nFormatDateAnno), i18nFormatDateAnno)
+        : "LUB of @I18nFormat(DATE) and @I18nFormat(DATE) is not @I18nFormat(DATE)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatDateAnno, i18nFormatNumberAnno), i18nFormatNumberAnno)
+        : "LUB of @I18nFormat(DATE) and @I18nFormat(NUMBER) is not @I18nFormat(NUMBER)!";
+
+    // LUB of NUMBER and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatNumberAnno, i18nFormatUnusedAnno), i18nFormatNumberAnno)
+        : "LUB of @I18nFormat(NUMBER) and @I18nFormat(UNUSED) is not @I18nFormat(NUMBER)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatNumberAnno, i18nFormatGeneralAnno), i18nFormatNumberAnno)
+        : "LUB of @I18nFormat(NUMBER) and @I18nFormat(GENERAL) is not @I18nFormat(NUMBER)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatNumberAnno, i18nFormatDateAnno), i18nFormatNumberAnno)
+        : "LUB of @I18nFormat(NUMBER) and @I18nFormat(DATE) is not @I18nFormat(NUMBER)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatNumberAnno, i18nFormatNumberAnno), i18nFormatNumberAnno)
+        : "LUB of @I18nFormat(NUMBER) and @I18nFormat(NUMBER) is not @I18nFormat(NUMBER)!";
+
+    // Now test with two I18nConversionCategory at a time:
+
+    cc2[0] = I18nConversionCategory.DATE;
+    cc2[1] = I18nConversionCategory.NUMBER;
+    AnnotationMirror formatTwoConvCat4 = treeUtil.categoriesToFormatAnnotation(cc2);
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTwoConvCat1, formatTwoConvCat2), formatTwoConvCat4)
+        : "LUB of @I18nFormat([DATE,DATE]) and @I18nFormat([UNUSED,NUMBER]) is not"
+            + " @I18nFormat([DATE,NUMBER])!";
+
+    // Test that the LUB of two I18nConversionCategory arrays of different sizes is an array of
+    // the largest size of the two:
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatGeneralAnno, formatTwoConvCat1), formatTwoConvCat1)
+        : "LUB of @I18nFormat(GENERAL) and @I18nFormat([DATE,DATE]) is not"
+            + " @I18nFormat([DATE,DATE])!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(formatTwoConvCat2, i18nFormatDateAnno), formatTwoConvCat4)
+        : "LUB of @I18nFormat([UNUSED,NUMBER]) and @I18nFormat(DATE) is not"
+            + " @I18nFormat([DATE,NUMBER])!";
+
+    // LUB of two distinct @I18nFormatFor(...) annotations is @I18nUnknownFormat
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatForWithValue1, i18nFormatForWithValue2), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(\"#1\") and @I18nFormatFor(\"#2\") is not @I18nUnknownFormat!";
+
+    // LUB of @I18nUnknownFormat and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nUnknownFormat and @I18nUnknownFormat is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NUNKNOWNFORMAT, I18NFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nUnknownFormat and @I18nFormat(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NUNKNOWNFORMAT, i18nFormatUnusedAnno), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nUnknownFormat and @I18nFormat(UNUSED) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NUNKNOWNFORMAT, I18NINVALIDFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nUnknownFormat and @I18nInvalidFormat(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nUnknownFormat and @I18nInvalidFormat(\"Message\") is not"
+            + " @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NUNKNOWNFORMAT, I18NFORMATFOR), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nUnknownFormat and @I18nFormatFor(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NUNKNOWNFORMAT, i18nFormatForWithValue1), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nUnknownFormat and @I18nFormatFor(\"#1\") is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NUNKNOWNFORMAT, I18NFORMATBOTTOM), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nUnknownFormat and @I18nFormatBottom is not @I18nUnknownFormat!";
+
+    // LUB of @I18nFormat(null) and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMAT, I18NUNKNOWNFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormat(null) and @I18nUnknownFormat is not @I18nUnknownFormat!";
+    // Computing the LUB of @I18nFormat(null) and @I18nFormat(null) should never occur in
+    // practice. Skipping this case as it causes an expected crash.
+    // Computing the LUB of @I18nFormat(null) and @I18nFormat with a value should never occur in
+    // practice. Skipping this case as it causes an expected crash.
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMAT, I18NINVALIDFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormat(null) and @I18nInvalidFormat(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMAT, i18nInvalidFormatWithMessage), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormat(null) and @I18nInvalidFormat(\"Message\") is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(I18NFORMAT, I18NFORMATFOR), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormat(null) and @I18nFormatFor(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMAT, i18nFormatForWithValue1), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormat(null) and @I18nFormatFor(\"#1\") is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(I18NFORMAT, I18NFORMATBOTTOM), I18NFORMAT)
+        : "LUB of @I18nFormat(null) and @I18nFormatBottom is not @I18nFormat(null)!";
+
+    // LUB of @I18nFormat(UNUSED) and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatUnusedAnno, I18NUNKNOWNFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormat(UNUSED) and @I18nUnknownFormat is not @I18nUnknownFormat!";
+    // Computing the LUB of @I18nFormat with a value and @I18nFormat(null) should never occur in
+    // practice. Skipping this case as it causes an expected crash.
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatUnusedAnno, i18nFormatUnusedAnno), i18nFormatUnusedAnno)
+        : "LUB of @I18nFormat(UNUSED) and @I18nFormat(UNUSED) is not @I18nFormat(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatUnusedAnno, I18NINVALIDFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormat(UNUSED) and @I18nInvalidFormat(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatUnusedAnno, i18nInvalidFormatWithMessage),
+            I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormat(UNUSED) and @I18nInvalidFormat(\"Message\") is not"
+            + " @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatUnusedAnno, I18NFORMATFOR), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormat(UNUSED) and @I18nFormatFor(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatUnusedAnno, i18nFormatForWithValue1), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormat(UNUSED) and @I18nFormatFor(\"#1\") is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatUnusedAnno, I18NFORMATBOTTOM), i18nFormatUnusedAnno)
+        : "LUB of @I18nFormat(UNUSED) and @I18nFormatBottom is not @I18nFormat(UNUSED)!";
+
+    // LUB of @I18nInvalidFormat(null) and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NINVALIDFORMAT, I18NUNKNOWNFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nInvalidFormat(null) and @I18nUnknownFormat is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NINVALIDFORMAT, I18NFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nInvalidFormat(null) and @I18nFormat(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NINVALIDFORMAT, i18nFormatUnusedAnno), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nInvalidFormat(null) and @I18nFormat(UNUSED) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NINVALIDFORMAT, I18NFORMATFOR), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nInvalidFormat(null) and @I18nFormatFor(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NINVALIDFORMAT, i18nFormatForWithValue1), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nInvalidFormat(null) and @I18nFormatFor(\"#1\") is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NINVALIDFORMAT, I18NFORMATBOTTOM), I18NINVALIDFORMAT)
+        : "LUB of @I18nInvalidFormat(null) and @I18nFormatBottom is not @I18nInvalidFormat(null)!";
+
+    // LUB of @I18nInvalidFormat("Message") and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nInvalidFormat(\"Message\") and @I18nUnknownFormat is not"
+            + " @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nInvalidFormatWithMessage, I18NFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nInvalidFormat(\"Message\") and @I18nFormat(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nInvalidFormatWithMessage, i18nFormatUnusedAnno),
+            I18NUNKNOWNFORMAT)
+        : "LUB of @I18nInvalidFormat(\"Message\") and @I18nFormat(UNUSED) is not"
+            + " @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage),
+            i18nInvalidFormatWithMessage)
+        : "LUB of @I18nInvalidFormat(\"Message\") and @I18nInvalidFormat(\"Message\") is not"
+            + " @I18nInvalidFormat(\"Message\")!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage2),
+            i18nInvalidFormatWithMessagesOred)
+        : "LUB of @I18nInvalidFormat(\"Message\") and @I18nInvalidFormat(\"Message2\") is not"
+            + " @I18nInvalidFormat(\"(\"Message\" or \"Message2\")\")!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nInvalidFormatWithMessage, I18NFORMATFOR), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nInvalidFormat(\"Message\") and @I18nFormatFor(null) is not"
+            + " @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nInvalidFormatWithMessage, i18nFormatForWithValue1),
+            I18NUNKNOWNFORMAT)
+        : "LUB of @I18nInvalidFormat(\"Message\") and @I18nFormatFor(\"#1\") is not"
+            + " @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nInvalidFormatWithMessage, I18NFORMATBOTTOM),
+            i18nInvalidFormatWithMessage)
+        : "LUB of @I18nInvalidFormat(\"Message\") and @I18nFormatBottom is not"
+            + " @I18nInvalidFormat(\"Message\")!";
+
+    // LUB of @I18nFormatFor(null) and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATFOR, I18NUNKNOWNFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(null) and @I18nUnknownFormat is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(I18NFORMATFOR, I18NFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(null) and @I18nFormat(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATFOR, i18nFormatUnusedAnno), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(null) and @I18nFormat(UNUSED) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATFOR, I18NINVALIDFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(null) and @I18nInvalidFormat(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATFOR, i18nInvalidFormatWithMessage), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(null) and @I18nInvalidFormat(\"Message\") is not"
+            + " @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(I18NFORMATFOR, I18NFORMATFOR), I18NFORMATFOR)
+        : "LUB of @I18nFormatFor(null) and @I18nFormatFor(null) is not @I18nFormatFor(null)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATFOR, i18nFormatForWithValue1), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(null) and @I18nFormatFor(\"#1\") is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATFOR, I18NFORMATBOTTOM), I18NFORMATFOR)
+        : "LUB of @I18nFormatFor(null) and @I18nFormatBottom is not @I18nFormatFor(null)!";
+
+    // LUB of @I18nFormatFor("#1") and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatForWithValue1, I18NUNKNOWNFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(\"#1\") and @I18nUnknownFormat is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatForWithValue1, I18NFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(\"#1\") and @I18nFormat(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatForWithValue1, i18nFormatUnusedAnno), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(\"#1\") and @I18nFormat(UNUSED) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatForWithValue1, I18NINVALIDFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(\"#1\") and @I18nInvalidFormat(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatForWithValue1, i18nInvalidFormatWithMessage),
+            I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(\"#1\") and @I18nInvalidFormat(\"Message\") is not"
+            + " @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatForWithValue1, I18NFORMATFOR), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatFor(\"#1\") and @I18nFormatFor(null) is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatForWithValue1, i18nFormatForWithValue1),
+            i18nFormatForWithValue1)
+        : "LUB of @I18nFormatFor(\"#1\") and @I18nFormatFor(\"#1\") is not @I18nFormatFor(\"#1\")!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(i18nFormatForWithValue1, I18NFORMATBOTTOM), i18nFormatForWithValue1)
+        : "LUB of @I18nFormatFor(\"#1\") and @I18nFormatBottom is not @I18nFormatFor(\"#1\")!";
+
+    // LUB of @I18nFormatBottom and others
+
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATBOTTOM, I18NUNKNOWNFORMAT), I18NUNKNOWNFORMAT)
+        : "LUB of @I18nFormatBottom and @I18nUnknownFormat is not @I18nUnknownFormat!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(I18NFORMATBOTTOM, I18NFORMAT), I18NFORMAT)
+        : "LUB of @I18nFormatBottom and @I18nFormat(null) is not @I18nFormat(null)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATBOTTOM, i18nFormatUnusedAnno), i18nFormatUnusedAnno)
+        : "LUB of @I18nFormatBottom and @I18nFormat(UNUSED) is not @I18nFormat(UNUSED)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATBOTTOM, I18NINVALIDFORMAT), I18NINVALIDFORMAT)
+        : "LUB of @I18nFormatBottom and @I18nInvalidFormat(null) is not @I18nInvalidFormat(null)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATBOTTOM, i18nInvalidFormatWithMessage),
+            i18nInvalidFormatWithMessage)
+        : "LUB of @I18nFormatBottom and @I18nInvalidFormat(\"Message\") is not"
+            + " @I18nInvalidFormat(\"Message\")!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATBOTTOM, I18NFORMATFOR), I18NFORMATFOR)
+        : "LUB of @I18nFormatBottom and @I18nFormatFor(null) is not @I18nFormatFor(null)!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATBOTTOM, i18nFormatForWithValue1), i18nFormatForWithValue1)
+        : "LUB of @I18nFormatBottom and @I18nFormatFor(\"#1\") is not @I18nFormatFor(\"#1\")!";
+    assert AnnotationUtils.areSame(
+            qh.leastUpperBound(I18NFORMATBOTTOM, I18NFORMATBOTTOM), I18NFORMATBOTTOM)
+        : "LUB of @I18nFormatBottom and @I18nFormatBottom is not @I18nFormatBottom!";
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/WholeProgramInferenceTestAnnotatedTypeFactory.java b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/WholeProgramInferenceTestAnnotatedTypeFactory.java
new file mode 100644
index 0000000..bb5973a
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/WholeProgramInferenceTestAnnotatedTypeFactory.java
@@ -0,0 +1,188 @@
+package org.checkerframework.checker.testchecker.wholeprograminference;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.DefaultType;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.ImplicitAnno;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Parent;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.SiblingWithFields;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Top;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.WholeProgramInferenceBottom;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * AnnotatedTypeFactory to test whole-program inference using .jaif files.
+ *
+ * <p>The used qualifier hierarchy is straightforward and only intended for test purposes.
+ */
+public class WholeProgramInferenceTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  private final AnnotationMirror PARENT =
+      new AnnotationBuilder(processingEnv, Parent.class).build();
+  private final AnnotationMirror BOTTOM =
+      new AnnotationBuilder(processingEnv, WholeProgramInferenceBottom.class).build();
+  private final AnnotationMirror IMPLICIT_ANNO =
+      new AnnotationBuilder(processingEnv, ImplicitAnno.class).build();
+
+  /** The SiblingWithFields.value field/element. */
+  private final ExecutableElement siblingWithFieldsValueElement =
+      TreeUtils.getMethod(SiblingWithFields.class, "value", 0, processingEnv);
+  /** The SiblingWithFields.value2 field/element. */
+  private final ExecutableElement siblingWithFieldsValue2Element =
+      TreeUtils.getMethod(SiblingWithFields.class, "value2", 0, processingEnv);
+
+  public WholeProgramInferenceTestAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<Class<? extends Annotation>>(
+        Arrays.asList(
+            Parent.class,
+            DefaultType.class,
+            Top.class,
+            Sibling1.class,
+            Sibling2.class,
+            WholeProgramInferenceBottom.class,
+            SiblingWithFields.class,
+            ImplicitAnno.class));
+  }
+
+  @Override
+  public TreeAnnotator createTreeAnnotator() {
+    LiteralTreeAnnotator literalTreeAnnotator = new LiteralTreeAnnotator(this);
+    literalTreeAnnotator.addLiteralKind(LiteralKind.INT, BOTTOM);
+    literalTreeAnnotator.addStandardLiteralQualifiers();
+
+    return new ListTreeAnnotator(new PropagationTreeAnnotator(this), literalTreeAnnotator);
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new WholeProgramInferenceTestQualifierHierarchy(
+        this.getSupportedTypeQualifiers(), elements);
+  }
+
+  /**
+   * Using a MultiGraphQualifierHierarchy to enable tests with Annotations that contain fields. @see
+   * SiblingWithFields.
+   */
+  protected class WholeProgramInferenceTestQualifierHierarchy
+      extends MostlyNoElementQualifierHierarchy {
+
+    private final QualifierKind SIBLING_WITH_FIELDS_KIND;
+
+    /**
+     * Creates a WholeProgramInferenceTestQualifierHierarchy from the given classes.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+     * @param elements element utils
+     */
+    protected WholeProgramInferenceTestQualifierHierarchy(
+        Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+      SIBLING_WITH_FIELDS_KIND = getQualifierKind(SiblingWithFields.class.getCanonicalName());
+    }
+
+    @Override
+    public AnnotationMirror getBottomAnnotation(AnnotationMirror start) {
+      return BOTTOM;
+    }
+
+    @Override
+    public Set<? extends AnnotationMirror> getBottomAnnotations() {
+      return Collections.singleton(BOTTOM);
+    }
+
+    @Override
+    protected AnnotationMirror greatestLowerBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind glbKind) {
+      if (qualifierKind1 == qualifierKind2 && qualifierKind1 == SIBLING_WITH_FIELDS_KIND) {
+        if (isSubtypeWithElements(a1, qualifierKind1, a2, qualifierKind2)) {
+          return a1;
+        } else {
+          return IMPLICIT_ANNO;
+        }
+      } else if (qualifierKind1 == SIBLING_WITH_FIELDS_KIND) {
+        return a1;
+      } else if (qualifierKind2 == SIBLING_WITH_FIELDS_KIND) {
+        return a2;
+      }
+      throw new BugInCF("Unexpected qualifiers: %s %s", a1, a2);
+    }
+
+    @Override
+    protected AnnotationMirror leastUpperBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind lubKind) {
+      if (qualifierKind1 == qualifierKind2 && qualifierKind1 == SIBLING_WITH_FIELDS_KIND) {
+        if (isSubtypeWithElements(a1, qualifierKind1, a2, qualifierKind2)) {
+          return a1;
+        } else {
+          return PARENT;
+        }
+      } else if (qualifierKind1 == SIBLING_WITH_FIELDS_KIND) {
+        return a1;
+      } else if (qualifierKind2 == SIBLING_WITH_FIELDS_KIND) {
+        return a2;
+      }
+      throw new BugInCF("Unexpected qualifiers: %s %s", a1, a2);
+    }
+
+    @Override
+    protected boolean isSubtypeWithElements(
+        AnnotationMirror subAnno,
+        QualifierKind subKind,
+        AnnotationMirror superAnno,
+        QualifierKind superKind) {
+      if (subKind == SIBLING_WITH_FIELDS_KIND && superKind == SIBLING_WITH_FIELDS_KIND) {
+        List<String> subVal1 =
+            AnnotationUtils.getElementValueArray(
+                subAnno, siblingWithFieldsValueElement, String.class, Collections.emptyList());
+        List<String> supVal1 =
+            AnnotationUtils.getElementValueArray(
+                superAnno, siblingWithFieldsValueElement, String.class, Collections.emptyList());
+        String subVal2 =
+            AnnotationUtils.getElementValue(
+                subAnno, siblingWithFieldsValue2Element, String.class, "");
+        String supVal2 =
+            AnnotationUtils.getElementValue(
+                superAnno, siblingWithFieldsValue2Element, String.class, "");
+        return subVal1.equals(supVal1) && subVal2.equals(supVal2);
+      }
+      throw new BugInCF("Unexpected qualifiers: %s %s", subAnno, superAnno);
+    }
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/WholeProgramInferenceTestChecker.java b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/WholeProgramInferenceTestChecker.java
new file mode 100644
index 0000000..69fff27
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/WholeProgramInferenceTestChecker.java
@@ -0,0 +1,27 @@
+package org.checkerframework.checker.testchecker.wholeprograminference;
+
+import java.util.LinkedHashSet;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.value.ValueChecker;
+
+/**
+ * Checker for a simple type system to test whole-program inference. Uses the Value Checker as a
+ * subchecker to ensure that generated files contain annotations both from this checker and from the
+ * Value Checker, to make certain that subchecker outputs aren't overwritten.
+ */
+public class WholeProgramInferenceTestChecker extends BaseTypeChecker {
+
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new WholeProgramInferenceTestVisitor(this);
+  }
+
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    LinkedHashSet<Class<? extends BaseTypeChecker>> checkers =
+        super.getImmediateSubcheckerClasses();
+    checkers.add(ValueChecker.class);
+    return checkers;
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/WholeProgramInferenceTestVisitor.java b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/WholeProgramInferenceTestVisitor.java
new file mode 100644
index 0000000..eb85a6b
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/WholeProgramInferenceTestVisitor.java
@@ -0,0 +1,40 @@
+package org.checkerframework.checker.testchecker.wholeprograminference;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.TreeInfo;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.DefaultType;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+
+/** Visitor for a simple type system to test whole-program inference using .jaif files. */
+public class WholeProgramInferenceTestVisitor
+    extends BaseTypeVisitor<WholeProgramInferenceTestAnnotatedTypeFactory> {
+
+  public WholeProgramInferenceTestVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  protected WholeProgramInferenceTestAnnotatedTypeFactory createTypeFactory() {
+    return new WholeProgramInferenceTestAnnotatedTypeFactory(checker);
+  }
+
+  @Override
+  public Void visitAnnotation(AnnotationTree node, Void p) {
+    Element anno = TreeInfo.symbol((JCTree) node.getAnnotationType());
+    if (anno.toString().equals(DefaultType.class.getName())) {
+      checker.reportError(node, "annotation.not.allowed.in.src", anno.toString());
+    }
+    return super.visitAnnotation(node, p);
+  }
+
+  @Override
+  protected void checkConstructorResult(
+      AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {
+    // Skip this check.
+  }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/DefaultType.java b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/DefaultType.java
new file mode 100644
index 0000000..4afcb91
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/DefaultType.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.testchecker.wholeprograminference.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * DefaultType is used to test the relaxInference option. Toy type system for testing field
+ * inference. This annotation cannot be used in source code.
+ *
+ * @see Sibling1
+ * @see Sibling2
+ * @see Parent
+ * @see Top
+ */
+@SubtypeOf({Top.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@DefaultQualifierInHierarchy
+public @interface DefaultType {}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/ImplicitAnno.java b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/ImplicitAnno.java
new file mode 100644
index 0000000..2d9281e
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/ImplicitAnno.java
@@ -0,0 +1,18 @@
+package org.checkerframework.checker.testchecker.wholeprograminference.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.IgnoreInWholeProgramInference;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Toy type system for testing field inference.
+ *
+ * @see Sibling1, Sibling2, Parent
+ */
+@SubtypeOf({Sibling1.class, Sibling2.class, SiblingWithFields.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@IgnoreInWholeProgramInference
+@DefaultFor(types = java.lang.StringBuffer.class)
+public @interface ImplicitAnno {}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/Parent.java b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/Parent.java
new file mode 100644
index 0000000..092deef
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/Parent.java
@@ -0,0 +1,14 @@
+package org.checkerframework.checker.testchecker.wholeprograminference.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Toy type system for testing field inference.
+ *
+ * @see Sibling1, Sibling2, Parent
+ */
+@SubtypeOf({DefaultType.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Parent {}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/Sibling1.java b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/Sibling1.java
new file mode 100644
index 0000000..72c3136
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/Sibling1.java
@@ -0,0 +1,14 @@
+package org.checkerframework.checker.testchecker.wholeprograminference.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Toy type system for testing field inference.
+ *
+ * @see Sibling1, Sibling2, Parent
+ */
+@SubtypeOf(Parent.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Sibling1 {}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/Sibling2.java b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/Sibling2.java
new file mode 100644
index 0000000..b1168fe
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/Sibling2.java
@@ -0,0 +1,14 @@
+package org.checkerframework.checker.testchecker.wholeprograminference.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Toy type system for testing field inference.
+ *
+ * @see Sibling1, Sibling2, Parent
+ */
+@SubtypeOf(Parent.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Sibling2 {}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/SiblingWithFields.java b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/SiblingWithFields.java
new file mode 100644
index 0000000..ba7f7aa
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/SiblingWithFields.java
@@ -0,0 +1,18 @@
+package org.checkerframework.checker.testchecker.wholeprograminference.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Toy type system for testing field inference.
+ *
+ * @see Sibling1, Sibling2, Parent
+ */
+@SubtypeOf(Parent.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface SiblingWithFields {
+  String[] value() default {};
+
+  String value2() default "";
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/ToIgnore.java b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/ToIgnore.java
new file mode 100644
index 0000000..945075f
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/ToIgnore.java
@@ -0,0 +1,11 @@
+package org.checkerframework.checker.testchecker.wholeprograminference.qual;
+
+import org.checkerframework.framework.qual.IgnoreInWholeProgramInference;
+
+/**
+ * Toy type system for testing field inference.
+ *
+ * @see Sibling1, Sibling2, Parent
+ */
+@IgnoreInWholeProgramInference
+public @interface ToIgnore {}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/Top.java b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/Top.java
new file mode 100644
index 0000000..0ca9c89
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/Top.java
@@ -0,0 +1,14 @@
+package org.checkerframework.checker.testchecker.wholeprograminference.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Toy type system for testing field inference.
+ *
+ * @see Sibling1, Sibling2, Parent
+ */
+@SubtypeOf({})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Top {}
diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/WholeProgramInferenceBottom.java b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/WholeProgramInferenceBottom.java
new file mode 100644
index 0000000..9d6ad12
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/testchecker/wholeprograminference/qual/WholeProgramInferenceBottom.java
@@ -0,0 +1,19 @@
+package org.checkerframework.checker.testchecker.wholeprograminference.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * Toy type system for testing field inference.
+ *
+ * @see Sibling1, Sibling2, Parent
+ */
+@SubtypeOf({ImplicitAnno.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@DefaultFor(TypeUseLocation.LOWER_BOUND)
+public @interface WholeProgramInferenceBottom {}
diff --git a/checker/src/testannotations/java/android/annotation/NonNull.java b/checker/src/testannotations/java/android/annotation/NonNull.java
new file mode 100644
index 0000000..e3c9901
--- /dev/null
+++ b/checker/src/testannotations/java/android/annotation/NonNull.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/NonNull.java
+
+package android.annotation;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+public @interface NonNull {}
diff --git a/checker/src/testannotations/java/android/annotation/Nullable.java b/checker/src/testannotations/java/android/annotation/Nullable.java
new file mode 100644
index 0000000..fb1d353
--- /dev/null
+++ b/checker/src/testannotations/java/android/annotation/Nullable.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/Nullable.java
+
+package android.annotation;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+public @interface Nullable {}
diff --git a/checker/src/testannotations/java/android/support/annotation/NonNull.java b/checker/src/testannotations/java/android/support/annotation/NonNull.java
new file mode 100644
index 0000000..05a67f4
--- /dev/null
+++ b/checker/src/testannotations/java/android/support/annotation/NonNull.java
@@ -0,0 +1,22 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/NonNull.java
+
+package android.support.annotation;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({
+  ElementType.METHOD,
+  ElementType.PARAMETER,
+  ElementType.FIELD,
+  ElementType.LOCAL_VARIABLE,
+  ElementType.ANNOTATION_TYPE,
+  ElementType.PACKAGE
+})
+public @interface NonNull {}
diff --git a/checker/src/testannotations/java/android/support/annotation/Nullable.java b/checker/src/testannotations/java/android/support/annotation/Nullable.java
new file mode 100644
index 0000000..0bd40d3
--- /dev/null
+++ b/checker/src/testannotations/java/android/support/annotation/Nullable.java
@@ -0,0 +1,22 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/Nullable.java
+
+package android.support.annotation;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({
+  ElementType.METHOD,
+  ElementType.PARAMETER,
+  ElementType.FIELD,
+  ElementType.LOCAL_VARIABLE,
+  ElementType.ANNOTATION_TYPE,
+  ElementType.PACKAGE
+})
+public @interface Nullable {}
diff --git a/checker/src/testannotations/java/com/sun/istack/internal/Interned.java b/checker/src/testannotations/java/com/sun/istack/internal/Interned.java
new file mode 100644
index 0000000..f8a8f61
--- /dev/null
+++ b/checker/src/testannotations/java/com/sun/istack/internal/Interned.java
@@ -0,0 +1,16 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// in the OpenJDK repository, file
+// jaxws/src/share/jaxws_classes/com/sun/istack/internal/Interned.java
+
+package com.sun.istack.internal;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface Interned {}
diff --git a/checker/src/testannotations/java/com/sun/istack/internal/NotNull.java b/checker/src/testannotations/java/com/sun/istack/internal/NotNull.java
new file mode 100644
index 0000000..e87ccb6
--- /dev/null
+++ b/checker/src/testannotations/java/com/sun/istack/internal/NotNull.java
@@ -0,0 +1,16 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// in the OpenJDK repository, file
+// jaxws/src/share/jaxws_classes/com/sun/istack/internal/NotNull.java
+
+package com.sun.istack.internal;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface NotNull {}
diff --git a/checker/src/testannotations/java/com/sun/istack/internal/Nullable.java b/checker/src/testannotations/java/com/sun/istack/internal/Nullable.java
new file mode 100644
index 0000000..958d417
--- /dev/null
+++ b/checker/src/testannotations/java/com/sun/istack/internal/Nullable.java
@@ -0,0 +1,16 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// in the OpenJDK repository, file
+// jaxws/src/share/jaxws_classes/com/sun/istack/internal/Nullable.java
+
+package com.sun.istack.internal;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface Nullable {}
diff --git a/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/CheckForNull.java b/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/CheckForNull.java
new file mode 100644
index 0000000..bfede2a
--- /dev/null
+++ b/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/CheckForNull.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/CheckForNull.html
+
+package edu.umd.cs.findbugs.annotations;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface CheckForNull {}
diff --git a/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/NonNull.java b/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/NonNull.java
new file mode 100644
index 0000000..b36ac92
--- /dev/null
+++ b/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/NonNull.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/NonNull.html
+
+package edu.umd.cs.findbugs.annotations;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface NonNull {}
diff --git a/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/Nullable.java b/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/Nullable.java
new file mode 100644
index 0000000..2ca1c2a
--- /dev/null
+++ b/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/Nullable.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/Nullable.html
+
+package edu.umd.cs.findbugs.annotations;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface Nullable {}
diff --git a/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/PossiblyNull.java b/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/PossiblyNull.java
new file mode 100644
index 0000000..056af92
--- /dev/null
+++ b/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/PossiblyNull.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/PossiblyNull.html
+
+package edu.umd.cs.findbugs.annotations;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface PossiblyNull {}
diff --git a/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/UnknownNullness.java b/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/UnknownNullness.java
new file mode 100644
index 0000000..6c4d811
--- /dev/null
+++ b/checker/src/testannotations/java/edu/umd/cs/findbugs/annotations/UnknownNullness.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/UnknownNullness.html
+
+package edu.umd.cs.findbugs.annotations;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface UnknownNullness {}
diff --git a/checker/src/testannotations/java/javax/annotation/CheckForNull.java b/checker/src/testannotations/java/javax/annotation/CheckForNull.java
new file mode 100644
index 0000000..a0599cf
--- /dev/null
+++ b/checker/src/testannotations/java/javax/annotation/CheckForNull.java
@@ -0,0 +1,9 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CheckForNull {}
diff --git a/checker/src/testannotations/java/javax/annotation/Nonnull.java b/checker/src/testannotations/java/javax/annotation/Nonnull.java
new file mode 100644
index 0000000..5d4b646
--- /dev/null
+++ b/checker/src/testannotations/java/javax/annotation/Nonnull.java
@@ -0,0 +1,12 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import javax.annotation.meta.When;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Nonnull {
+  When when() default When.ALWAYS;
+}
diff --git a/checker/src/testannotations/java/javax/annotation/Nullable.java b/checker/src/testannotations/java/javax/annotation/Nullable.java
new file mode 100644
index 0000000..d9024b4
--- /dev/null
+++ b/checker/src/testannotations/java/javax/annotation/Nullable.java
@@ -0,0 +1,9 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Nullable {}
diff --git a/checker/src/testannotations/java/javax/annotation/concurrent/GuardedBy.java b/checker/src/testannotations/java/javax/annotation/concurrent/GuardedBy.java
new file mode 100644
index 0000000..57f9c19
--- /dev/null
+++ b/checker/src/testannotations/java/javax/annotation/concurrent/GuardedBy.java
@@ -0,0 +1,30 @@
+package javax.annotation.concurrent;
+
+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;
+import org.checkerframework.checker.lock.qual.LockHeld;
+import org.checkerframework.framework.qual.PreconditionAnnotation;
+
+// The JCIP annotation can be used on a field (in which case it corresponds to the Lock Checker's
+// @GuardedBy annotation) or on a method (in which case it is a declaration annotation corresponding
+// to the Lock Checker's @Holding annotation).
+// It is preferred to use these Checker Framework annotations instead:
+//  org.checkerframework.checker.lock.qual.GuardedBy
+//  org.checkerframework.checker.lock.qual.Holding
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.FIELD})
+@PreconditionAnnotation(qualifier = LockHeld.class)
+public @interface GuardedBy {
+  /**
+   * The Java expressions that need to be held.
+   *
+   * @see <a href="https://checkerframework.org/manual/#java-expressions-as-arguments">Syntax of
+   *     Java expressions</a>
+   */
+  String[] value() default {};
+}
diff --git a/checker/src/testannotations/java/javax/annotation/meta/When.java b/checker/src/testannotations/java/javax/annotation/meta/When.java
new file mode 100644
index 0000000..a108207
--- /dev/null
+++ b/checker/src/testannotations/java/javax/annotation/meta/When.java
@@ -0,0 +1,16 @@
+package javax.annotation.meta;
+
+/**
+ * Used to describe the relationship between a qualifier T and the set of values S possible on an
+ * annotated element.
+ */
+public enum When {
+  /** S is a subset of T */
+  ALWAYS,
+  /** nothing definitive is known about the relation between S and T */
+  UNKNOWN,
+  /** S intersection T is non empty and S - T is nonempty. */
+  MAYBE,
+  /** S intersection T is empty. */
+  NEVER;
+}
diff --git a/checker/src/testannotations/java/lombok/NonNull.java b/checker/src/testannotations/java/lombok/NonNull.java
new file mode 100644
index 0000000..995145a
--- /dev/null
+++ b/checker/src/testannotations/java/lombok/NonNull.java
@@ -0,0 +1,21 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://github.com/rzwitserloot/lombok/blob/master/src/core/lombok/NonNull.java
+
+package lombok;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({
+  ElementType.FIELD,
+  ElementType.METHOD,
+  ElementType.PARAMETER,
+  ElementType.LOCAL_VARIABLE,
+  ElementType.TYPE_USE
+})
+public @interface NonNull {}
diff --git a/checker/src/testannotations/java/net/jcip/annotations/GuardedBy.java b/checker/src/testannotations/java/net/jcip/annotations/GuardedBy.java
new file mode 100644
index 0000000..0b3dda4
--- /dev/null
+++ b/checker/src/testannotations/java/net/jcip/annotations/GuardedBy.java
@@ -0,0 +1,33 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://jcip.net/annotations/doc/net/jcip/annotations/GuardedBy.html
+
+package net.jcip.annotations;
+
+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;
+import org.checkerframework.checker.lock.qual.LockHeld;
+import org.checkerframework.framework.qual.PreconditionAnnotation;
+
+// The JCIP annotation can be used on a field (in which case it corresponds to the Lock Checker's
+// @GuardedBy annotation) or on a method (in which case it is a declaration annotation corresponding
+// to the Lock Checker's @Holding annotation).
+// It is preferred to use these Checker Framework annotations instead:
+//  org.checkerframework.checker.lock.qual.GuardedBy
+//  org.checkerframework.checker.lock.qual.Holding
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+@PreconditionAnnotation(qualifier = LockHeld.class)
+public @interface GuardedBy {
+  /**
+   * The Java expressions that need to be held.
+   *
+   * @see <a href="https://checkerframework.org/manual/#java-expressions-as-arguments">Syntax of
+   *     Java expressions</a>
+   */
+  String[] value() default {};
+}
diff --git a/checker/src/testannotations/java/org/eclipse/jdt/annotation/NonNull.java b/checker/src/testannotations/java/org/eclipse/jdt/annotation/NonNull.java
new file mode 100644
index 0000000..26b317b
--- /dev/null
+++ b/checker/src/testannotations/java/org/eclipse/jdt/annotation/NonNull.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/NonNull.html
+
+package org.eclipse.jdt.annotation;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.TYPE_USE})
+public @interface NonNull {}
diff --git a/checker/src/testannotations/java/org/eclipse/jdt/annotation/Nullable.java b/checker/src/testannotations/java/org/eclipse/jdt/annotation/Nullable.java
new file mode 100644
index 0000000..78651ad
--- /dev/null
+++ b/checker/src/testannotations/java/org/eclipse/jdt/annotation/Nullable.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/Nullable.html
+
+package org.eclipse.jdt.annotation;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.TYPE_USE})
+public @interface Nullable {}
diff --git a/checker/src/testannotations/java/org/eclipse/jgit/annotations/NonNull.java b/checker/src/testannotations/java/org/eclipse/jgit/annotations/NonNull.java
new file mode 100644
index 0000000..7258192
--- /dev/null
+++ b/checker/src/testannotations/java/org/eclipse/jgit/annotations/NonNull.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java
+
+package org.eclipse.jgit.annotations;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface NonNull {}
diff --git a/checker/src/testannotations/java/org/eclipse/jgit/annotations/Nullable.java b/checker/src/testannotations/java/org/eclipse/jgit/annotations/Nullable.java
new file mode 100644
index 0000000..b134f5c
--- /dev/null
+++ b/checker/src/testannotations/java/org/eclipse/jgit/annotations/Nullable.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java
+
+package org.eclipse.jgit.annotations;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface Nullable {}
diff --git a/checker/src/testannotations/java/org/jetbrains/annotations/NotNull.java b/checker/src/testannotations/java/org/jetbrains/annotations/NotNull.java
new file mode 100644
index 0000000..9b78ffc
--- /dev/null
+++ b/checker/src/testannotations/java/org/jetbrains/annotations/NotNull.java
@@ -0,0 +1,23 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/NotNull.java
+
+package org.jetbrains.annotations;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({
+  ElementType.FIELD,
+  ElementType.METHOD,
+  ElementType.PARAMETER,
+  ElementType.LOCAL_VARIABLE,
+  ElementType.TYPE_USE
+})
+public @interface NotNull {
+  String value() default "";
+}
diff --git a/checker/src/testannotations/java/org/jetbrains/annotations/Nullable.java b/checker/src/testannotations/java/org/jetbrains/annotations/Nullable.java
new file mode 100644
index 0000000..1c6fc45
--- /dev/null
+++ b/checker/src/testannotations/java/org/jetbrains/annotations/Nullable.java
@@ -0,0 +1,23 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/Nullable.java
+
+package org.jetbrains.annotations;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({
+  ElementType.FIELD,
+  ElementType.METHOD,
+  ElementType.PARAMETER,
+  ElementType.LOCAL_VARIABLE,
+  ElementType.TYPE_USE
+})
+public @interface Nullable {
+  String value() default "";
+}
diff --git a/checker/src/testannotations/java/org/jmlspecs/annotation/NonNull.java b/checker/src/testannotations/java/org/jmlspecs/annotation/NonNull.java
new file mode 100644
index 0000000..ee8eb70
--- /dev/null
+++ b/checker/src/testannotations/java/org/jmlspecs/annotation/NonNull.java
@@ -0,0 +1,12 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/NonNull.java
+
+package org.jmlspecs.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface NonNull {}
diff --git a/checker/src/testannotations/java/org/jmlspecs/annotation/Nullable.java b/checker/src/testannotations/java/org/jmlspecs/annotation/Nullable.java
new file mode 100644
index 0000000..a4089fa
--- /dev/null
+++ b/checker/src/testannotations/java/org/jmlspecs/annotation/Nullable.java
@@ -0,0 +1,12 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/Nullable.java
+
+package org.jmlspecs.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Nullable {}
diff --git a/checker/src/testannotations/java/org/netbeans/api/annotations/common/CheckForNull.java b/checker/src/testannotations/java/org/netbeans/api/annotations/common/CheckForNull.java
new file mode 100644
index 0000000..e71b4f7
--- /dev/null
+++ b/checker/src/testannotations/java/org/netbeans/api/annotations/common/CheckForNull.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// http://bits.netbeans.org/8.2/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/CheckForNull.html
+
+package org.netbeans.api.annotations.common;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.METHOD})
+public @interface CheckForNull {}
diff --git a/checker/src/testannotations/java/org/netbeans/api/annotations/common/NonNull.java b/checker/src/testannotations/java/org/netbeans/api/annotations/common/NonNull.java
new file mode 100644
index 0000000..96f262d
--- /dev/null
+++ b/checker/src/testannotations/java/org/netbeans/api/annotations/common/NonNull.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// http://bits.netbeans.org/8.2/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NonNull.html
+
+package org.netbeans.api.annotations.common;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface NonNull {}
diff --git a/checker/src/testannotations/java/org/netbeans/api/annotations/common/NullAllowed.java b/checker/src/testannotations/java/org/netbeans/api/annotations/common/NullAllowed.java
new file mode 100644
index 0000000..453d951
--- /dev/null
+++ b/checker/src/testannotations/java/org/netbeans/api/annotations/common/NullAllowed.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// http://bits.netbeans.org/8.2/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullAllowed.html
+
+package org.netbeans.api.annotations.common;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface NullAllowed {}
diff --git a/checker/src/testannotations/java/org/netbeans/api/annotations/common/NullUnknown.java b/checker/src/testannotations/java/org/netbeans/api/annotations/common/NullUnknown.java
new file mode 100644
index 0000000..dd52452
--- /dev/null
+++ b/checker/src/testannotations/java/org/netbeans/api/annotations/common/NullUnknown.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// http://bits.netbeans.org/8.2/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullUnknown.html
+
+package org.netbeans.api.annotations.common;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+public @interface NullUnknown {}
diff --git a/checker/src/testannotations/java/org/springframework/lang/NonNull.java b/checker/src/testannotations/java/org/springframework/lang/NonNull.java
new file mode 100644
index 0000000..72df0da
--- /dev/null
+++ b/checker/src/testannotations/java/org/springframework/lang/NonNull.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/NonNull.java
+
+package org.springframework.lang;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value = {ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+public @interface NonNull {}
diff --git a/checker/src/testannotations/java/org/springframework/lang/Nullable.java b/checker/src/testannotations/java/org/springframework/lang/Nullable.java
new file mode 100644
index 0000000..393f201
--- /dev/null
+++ b/checker/src/testannotations/java/org/springframework/lang/Nullable.java
@@ -0,0 +1,15 @@
+// Upstream version (this is a clean-room reimplementation of its interface):
+// https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/Nullable.java
+
+package org.springframework.lang;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value = {ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+public @interface Nullable {}
diff --git a/checker/tests/README b/checker/tests/README
new file mode 100644
index 0000000..f7d34a9
--- /dev/null
+++ b/checker/tests/README
@@ -0,0 +1,217 @@
+This document describes how to write and run tests for the Checker
+Framework.  Writing and running tests is useful for bug submitters,
+checker writers, and Checker Framework maintainers.  Users of the Checker
+Framework and of the checkers packaged with it should read the manual
+instead; see file ../../docs/manual/manual.html .
+
+
+How to run all the tests for the Checker Framework
+==================================================
+
+  # To run from the checker-framework directory:
+  ./gradlew allTests
+
+Other Gradle tasks also exist to run a subset of the tests; for example,
+  # Run from the checker-framework directory:
+  ./gradlew :checker:NullnessTest
+  # Run from the checker directory:
+  ../gradlew NullnessTest
+  # To see all tasks
+  ./gradlew tasks
+
+
+How to run just one test for the Checker Framework
+==================================================
+
+To run a subset of the JUnit tests, use Gradle's --tests command-line
+argument, from a project directory such as checker/, dataflow/, or
+framework/:
+
+  cd $CHECKERFRAMEWORK/checker
+  ../gradlew test --tests '*ParseAllJdkTest'
+
+To check one source code file,
+do something like the following, assuming that the source code
+file to be checked is AssertNonNullTest.java in directory
+$CHECKERFRAMEWORK/checker/tests/nullness/ and the checker is
+org.checkerframework.checker.nullness.NullnessChecker.
+
+  cd $CHECKERFRAMEWORK
+  ./gradlew copyJarsToDist && checker/bin/javac -processor org.checkerframework.checker.nullness.NullnessChecker -implicit:class checker/tests/nullness/AssertNonNullTest.java
+
+where the specific checker and command-line arguments are often clear from
+the directory name but can also be determined from a file such as
+  checker-framework/checker/tests/src/tests/MyTypeSystemTest.java
+which is the source code for the test itself.
+
+
+Writing new tests for an existing checker
+=========================================
+
+To create a new test case, just place a Java file in the test directory,
+whose name usually corresponds to the checker name, such as
+checker-framework/checker/tests/nullness/ .  Unless the README file in
+the test directory specifies otherwise, then the Java file must
+1. Not issue any javac errors.
+2. Not declare a class with the same (fully qualified) name as any other class in
+   the test directory.
+3. Not declare a class with the same (simple) name as any commonly used Java
+   library class such as List.
+
+The testing framework for the Checker Framework is built on top of JUnit.
+However, its tests are more like end-to-end system tests than unit tests.
+
+A checker test case has two parts:
+  1. the Java class to be compiled, and
+  2. a set of expected errors.
+Both parts can be expressed in one Java file (see below).
+
+Classes in checker-framework/framework/tests/src/tests/lib can be referenced by
+tests to check behavior related to unchecked bytecode.
+
+By convention, when a test is about an issue in the issue tracker, we write
+a comment at the top of the file, in this format:
+
+  // Test case for issue 266:
+  // https://github.com/typetools/checker-framework/issues/266
+
+  // @skip-test until the issue is fixed
+
+
+Specifying expected warnings and errors
+=======================================
+
+A test case is a Java file that uses stylized comments to indicate expected
+error messages.
+
+Suppose that you want to test the Nullness Checker's behavior when
+type-checking the following Java class:
+
+public class MyNullnessTest {
+  void method() {
+    Object nullable = null;
+    nullable.toString();   // should emit error
+  }
+}
+
+The Nullness Checker should report an error for the dereference in line 4.
+The non-localized message key for such an error is
+'dereference.of.nullable'.  You could learn that by reading the Javadoc (or
+the source code) for org.checkerframework.checker.nullness.NullnessVisitor,
+or by creating the test and observing the failure.
+
+To indicate the expected failure, insert the line
+  // :: error: (<error-message-key>)
+directly preceding the expected error line.
+If a warning rather than an error is expected, insert the line
+  // :: warning: (<warning-message-key>)
+If a stub parser warning is expected, insert the line
+  //warning: AnnotationFileParser: <stub parser warning>
+If multiple errors are expected on a single line, duplicate everthing
+except the "//" comment characters, as in
+  // :: error: (<error-message-key1>) :: error: (<error-message-key2>)
+If the expected failures line would be very long, you may break it across
+multiple comment lines.
+It is permitted to write a "// ::" comment after "{" that was at the end of
+a line, to indicate a warning on the line immediately after the "{".
+
+So the final test case would be:
+
+public class MyNullnessTest {
+  void method() {
+    Object nullable = null;
+    // :: error: (dereference.of.nullable)
+    nullable.toString();
+  }
+}
+
+The file may appear anywhere in or under
+checker-framework/checker/tests/nullness/.  (You may find it useful to use
+separate subfolders, such as nullness/tests/nullness/dereference/.)  Each
+checker should have its own folder under checker-framework/checker/tests/,
+such as checker-framework/checker/tests/interning/,
+checker-framework/checker/tests/regex/, etc.
+
+You can indicate an expected warning (as opposed to error) by using
+"warning:" instead of "error:", as in
+
+  // :: warning: (nulltest.redundant)
+  assert val != null;
+
+Multiple expected messages can be given on the same line using the
+"// :: A :: B :: C" syntax.  This example expects both an error and
+a warning from the same line of code:
+
+  @Regex String s1 = null;
+  // :: error: (assignment) :: warning: (cast.unsafe)
+  @Regex(3) String s2 = (@Regex(2) String) s;
+
+
+As an alternative to writing expected errors in the source file using "// ::"
+syntax, expected errors can be specified in a separate file using the .out
+file extension.  These files contain lines of the following format:
+
+:19: error: (dereference.of.nullable)
+
+The number between the colons is the line number of the expected error
+message.  This format is harder to maintain, and we suggest using the
+in-line comment format.
+
+
+Writing tests for a new checker or with different command-line arguments
+========================================================================
+
+To create tests for a new checker, mimic some existing checker's tests:
+ * create a top-level test directory, such as
+   checker-framework/checker/tests/regex for the test cases
+ * create a top-level JUnit test in checker-framework/checker/src/test/java/tests,
+   such as: RegexTest.java
+   It is a subclass of CheckerFrameworkPerDirectoryTest, and its list of checker
+   options must include "-Anomsgtext".  (See the API documentation for
+   CheckerFrameworkPerDirectoryTest for more detailed information.)
+ * include "all-systems" as a test directly by adding it to the array created
+   in getTestDirs in the new test class.  See
+   checker-framework/framework/tests/all-systems/README for more details about
+   the all-systems tests.
+
+Different test cases may need to pass different command-line arguments
+(flags) to the checker -- for instance, to check an optional command-line
+argument that should not be enabled for every test.  Follow the same
+instructions as for writing tests for a new checker.
+
+A Gradle task is created for each Junit test class.  The task is named the same
+as the Junit test classname, for example, ./gradlew RegexTest runs the Regex
+Checker tests.
+
+
+Disabling a test case
+=====================
+
+Write @skip-test anywhere in a test file to disable that test.
+
+Write @below-java9-jdk-skip-test anywhere in a test file to disable that
+test if the executing JDK version is lower than 9.
+This is useful when the test depends on Java 9 language features that
+need runtime support or depends on Java 9 APIs.
+Tests that only exercise the static type annotation API do not need
+this marker.
+
+To disable all tests for a given type system, annotate the JUnit test class
+with @Ignore.
+
+
+Annotated JDK
+=============
+
+The tests run with the annotated JDK.  Keep this in mind when writing tests.
+
+
+Seeing the javac commands that are run
+======================================
+
+To see the exact javac commands that the Checker Framework test framework
+runs, use
+  -Pemit.test.debug=true
+For example:
+  ./gradlew NullnessStubfileTest -Pemit.test.debug=true
+This may be helpful during debugging.
diff --git a/checker/tests/aggregate/NullnessAndRegex.java b/checker/tests/aggregate/NullnessAndRegex.java
new file mode 100644
index 0000000..92394e7
--- /dev/null
+++ b/checker/tests/aggregate/NullnessAndRegex.java
@@ -0,0 +1,20 @@
+// Make sure that we actually receive errors from sub-checkers.
+
+import org.checkerframework.checker.i18n.qual.Localized;
+import org.checkerframework.checker.regex.qual.Regex;
+
+public class NullnessAndRegex {
+  // :: error: (assignment)
+  @Regex String s1 = "De(mo";
+  // :: error: (assignment)
+  Object f = null;
+  // :: error: (assignment)
+  @Regex String s2 = "De(mo";
+
+  void localized(@Localized String s) {}
+
+  void method() {
+    // :: error: (argument)
+    localized("ldskjfldj"); // error
+  }
+}
diff --git a/checker/tests/aggregate/Placeholder.java b/checker/tests/aggregate/Placeholder.java
new file mode 100644
index 0000000..33a99cf
--- /dev/null
+++ b/checker/tests/aggregate/Placeholder.java
@@ -0,0 +1,4 @@
+// We need a file to start the checker.
+// We should add more files to test aggregate checker functionality.
+
+public class Placeholder {}
diff --git a/checker/tests/all-systems b/checker/tests/all-systems
new file mode 120000
index 0000000..db47990
--- /dev/null
+++ b/checker/tests/all-systems
@@ -0,0 +1 @@
+../../framework/tests/all-systems
\ No newline at end of file
diff --git a/checker/tests/calledmethods-autovalue/Animal.java b/checker/tests/calledmethods-autovalue/Animal.java
new file mode 100644
index 0000000..80ef0a8
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/Animal.java
@@ -0,0 +1,86 @@
+import com.google.auto.value.AutoValue;
+import java.util.Optional;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+/**
+ * Adapted from the standard AutoValue example code:
+ * https://github.com/google/auto/blob/master/value/userguide/builders.md
+ */
+@AutoValue
+abstract class Animal {
+  abstract String name();
+
+  abstract @Nullable String habitat();
+
+  abstract int numberOfLegs();
+
+  // does not need to be explicitly set
+  abstract Optional<String> extra();
+
+  public String getStr() {
+    return "str";
+  }
+
+  public abstract Builder toBuilder();
+
+  static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract Builder setName(String value);
+
+    abstract Builder setNumberOfLegs(int value);
+
+    abstract Builder setHabitat(String value);
+
+    abstract Builder setExtra(String value);
+
+    abstract Animal build();
+  }
+
+  public static void buildSomethingWrong() {
+    Builder b = builder();
+    b.setName("Frank");
+    // :: error: finalizer.invocation
+    b.build();
+  }
+
+  public static void buildSomethingRight() {
+    Builder b = builder();
+    b.setName("Frank");
+    b.setNumberOfLegs(4);
+    b.build();
+  }
+
+  public static void buildSomethingRightIncludeOptional() {
+    Builder b = builder();
+    b.setName("Frank");
+    b.setNumberOfLegs(4);
+    b.setHabitat("jungle");
+    b.build();
+  }
+
+  public static void buildSomethingWrongFluent() {
+    // :: error: finalizer.invocation
+    builder().setName("Frank").build();
+  }
+
+  public static void buildSomethingRightFluent() {
+    builder().setName("Jim").setNumberOfLegs(7).build();
+  }
+
+  public static void buildWithToBuilder() {
+    Animal a1 = builder().setName("Jim").setNumberOfLegs(7).build();
+    a1.toBuilder().build();
+  }
+
+  public static void buildSomethingRightFluentWithLocal() {
+    Builder b = builder();
+    b.setName("Jim").setNumberOfLegs(7);
+    b.build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/AnimalNoSet.java b/checker/tests/calledmethods-autovalue/AnimalNoSet.java
new file mode 100644
index 0000000..1c72710
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/AnimalNoSet.java
@@ -0,0 +1,67 @@
+import com.google.auto.value.AutoValue;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+/**
+ * Adapted from the standard AutoValue example code:
+ * https://github.com/google/auto/blob/master/value/userguide/builders.md
+ */
+@AutoValue
+abstract class AnimalNoSet {
+  abstract String name();
+
+  abstract @Nullable String habitat();
+
+  abstract int numberOfLegs();
+
+  public String getStr() {
+    return "str";
+  }
+
+  static Builder builder() {
+    return new AutoValue_AnimalNoSet.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract Builder name(String value);
+
+    abstract Builder numberOfLegs(int value);
+
+    abstract Builder habitat(String value);
+
+    abstract AnimalNoSet build();
+  }
+
+  public static void buildSomethingWrong() {
+    Builder b = builder();
+    b.name("Frank");
+    // :: error: finalizer.invocation
+    b.build();
+  }
+
+  public static void buildSomethingRight() {
+    Builder b = builder();
+    b.name("Frank");
+    b.numberOfLegs(4);
+    b.build();
+  }
+
+  public static void buildSomethingRightIncludeOptional() {
+    Builder b = builder();
+    b.name("Frank");
+    b.numberOfLegs(4);
+    b.habitat("jungle");
+    b.build();
+  }
+
+  public static void buildSomethingWrongFluent() {
+    // :: error: finalizer.invocation
+    builder().name("Frank").build();
+  }
+
+  public static void buildSomethingRightFluent() {
+    builder().name("Jim").numberOfLegs(7).build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/BuilderGetter.java b/checker/tests/calledmethods-autovalue/BuilderGetter.java
new file mode 100644
index 0000000..c2a991f
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/BuilderGetter.java
@@ -0,0 +1,36 @@
+import com.google.auto.value.AutoValue;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+@AutoValue
+abstract class BuilderGetter {
+
+  public abstract String name();
+
+  static Builder builder() {
+    return new AutoValue_BuilderGetter.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract Builder setName(String name);
+
+    abstract String name();
+
+    abstract BuilderGetter build();
+  }
+
+  static void correct() {
+    Builder b = builder();
+    b.setName("Phil");
+    b.build();
+  }
+
+  static void wrong() {
+    Builder b = builder();
+    b.name();
+    // :: error: finalizer.invocation
+    b.build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/CallWithinBuilder.java b/checker/tests/calledmethods-autovalue/CallWithinBuilder.java
new file mode 100644
index 0000000..6cc3ca4
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/CallWithinBuilder.java
@@ -0,0 +1,35 @@
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+@AutoValue
+abstract class CallWithinBuilder {
+
+  public abstract ImmutableList<String> names();
+
+  static Builder builder() {
+    return new AutoValue_CallWithinBuilder.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract ImmutableList.Builder<String> namesBuilder();
+
+    public Builder addName(String name) {
+      namesBuilder().add(name);
+      return this;
+    }
+
+    public Builder addNames(Collection<String> names) {
+      for (String n : names) {
+        addName(n);
+      }
+      return this;
+    }
+
+    abstract CallWithinBuilder build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/FooParcelable.java b/checker/tests/calledmethods-autovalue/FooParcelable.java
new file mode 100644
index 0000000..2392f53
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/FooParcelable.java
@@ -0,0 +1,36 @@
+import android.os.Parcelable;
+import com.google.auto.value.AutoValue;
+
+/**
+ * Test for support of AutoValue Parcel extension. This test currently passes, but only because we
+ * ignore cases where we cannot find a matching setter for a method we think corresponds to an
+ * AutoValue property. See https://github.com/kelloggm/object-construction-checker/issues/110
+ */
+@AutoValue
+abstract class FooParcelable implements Parcelable {
+  abstract String name();
+
+  static Builder builder() {
+    return new AutoValue_FooParcelable.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract Builder setName(String value);
+
+    abstract FooParcelable build();
+  }
+
+  public static void buildSomethingWrong() {
+    Builder b = builder();
+    // :: error: finalizer.invocation
+    b.build();
+  }
+
+  public static void buildSomethingRight() {
+    Builder b = builder();
+    b.setName("Frank");
+    b.build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/GetAndIs.java b/checker/tests/calledmethods-autovalue/GetAndIs.java
new file mode 100644
index 0000000..23f7ef2
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/GetAndIs.java
@@ -0,0 +1,47 @@
+import com.google.auto.value.AutoValue;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+@AutoValue
+abstract class GetAndIs {
+  abstract String get();
+
+  abstract boolean is();
+
+  static Builder builder() {
+    return new AutoValue_GetAndIs.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract Builder setGet(String value);
+
+    abstract Builder setIs(boolean value);
+
+    abstract GetAndIs build();
+  }
+
+  public static void buildSomethingWrong() {
+    Builder b = builder();
+    b.setGet("Frank");
+    // :: error: finalizer.invocation
+    b.build();
+  }
+
+  public static void buildSomethingRight() {
+    Builder b = builder();
+    b.setGet("Frank");
+    b.setIs(false);
+    b.build();
+  }
+
+  public static void buildSomethingWrongFluent() {
+    // :: error: finalizer.invocation
+    builder().setGet("Frank").build();
+  }
+
+  public static void buildSomethingRightFluent() {
+    builder().setGet("Jim").setIs(true).build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/GetAnimal.java b/checker/tests/calledmethods-autovalue/GetAnimal.java
new file mode 100644
index 0000000..afddfbd
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/GetAnimal.java
@@ -0,0 +1,69 @@
+import com.google.auto.value.AutoValue;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+/**
+ * Adapted from the standard AutoValue example code:
+ * https://github.com/google/auto/blob/master/value/userguide/builders.md
+ */
+@AutoValue
+abstract class GetAnimal {
+  abstract String getName();
+
+  abstract @Nullable String getHabitat();
+
+  abstract int getNumberOfLegs();
+
+  abstract boolean isHasArms();
+
+  static Builder builder() {
+    return new AutoValue_GetAnimal.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract Builder setName(String value);
+
+    abstract Builder setNumberOfLegs(int value);
+
+    abstract Builder setHabitat(String value);
+
+    abstract Builder setHasArms(boolean b);
+
+    abstract GetAnimal build();
+  }
+
+  public static void buildSomethingWrong() {
+    Builder b = builder();
+    b.setName("Frank");
+    // :: error: finalizer.invocation
+    b.build();
+  }
+
+  public static void buildSomethingRight() {
+    Builder b = builder();
+    b.setName("Frank");
+    b.setNumberOfLegs(4);
+    b.setHasArms(true);
+    b.build();
+  }
+
+  public static void buildSomethingRightIncludeOptional() {
+    Builder b = builder();
+    b.setName("Frank");
+    b.setNumberOfLegs(4);
+    b.setHabitat("jungle");
+    b.setHasArms(true);
+    b.build();
+  }
+
+  public static void buildSomethingWrongFluent() {
+    // :: error: finalizer.invocation
+    builder().setName("Frank").build();
+  }
+
+  public static void buildSomethingRightFluent() {
+    builder().setName("Jim").setNumberOfLegs(7).setHasArms(false).build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/GuavaImmutable.java b/checker/tests/calledmethods-autovalue/GuavaImmutable.java
new file mode 100644
index 0000000..1c98cfa
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/GuavaImmutable.java
@@ -0,0 +1,31 @@
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+@AutoValue
+abstract class GuavaImmutable {
+
+  public abstract ImmutableList<String> names();
+
+  static Builder builder() {
+    return new AutoValue_GuavaImmutable.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract Builder names(ImmutableList<String> value);
+
+    abstract GuavaImmutable build();
+  }
+
+  public static void buildSomethingWrong() {
+    // :: error: finalizer.invocation
+    builder().build();
+  }
+
+  public static void buildSomethingRight() {
+    builder().names(ImmutableList.of()).build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/GuavaImmutablePropBuilder.java b/checker/tests/calledmethods-autovalue/GuavaImmutablePropBuilder.java
new file mode 100644
index 0000000..dbf258a
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/GuavaImmutablePropBuilder.java
@@ -0,0 +1,27 @@
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+@AutoValue
+abstract class GuavaImmutablePropBuilder {
+
+  public abstract ImmutableList<String> names();
+
+  static Builder builder() {
+    return new AutoValue_GuavaImmutablePropBuilder.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract ImmutableList.Builder<String> namesBuilder();
+
+    abstract GuavaImmutablePropBuilder build();
+  }
+
+  public static void buildSomething() {
+    // don't need to explicitly set the names
+    builder().build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/Inheritance.java b/checker/tests/calledmethods-autovalue/Inheritance.java
new file mode 100644
index 0000000..b87ee1c
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/Inheritance.java
@@ -0,0 +1,47 @@
+import com.google.auto.value.AutoValue;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.common.returnsreceiver.qual.This;
+
+public class Inheritance {
+  static interface Props {
+    String name();
+
+    String somethingElse();
+
+    abstract class Builder<B extends Builder<B>> {
+      public abstract @This B name(String value);
+    }
+  }
+
+  @AutoValue
+  abstract static class PropHolder implements Props {
+    abstract int size();
+
+    @Override
+    public String somethingElse() {
+      return "hi";
+    }
+
+    static Builder builder() {
+      return new AutoValue_Inheritance_PropHolder.Builder();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder extends Props.Builder<Builder> {
+      public abstract Builder size(int value);
+
+      public abstract PropHolder build();
+    }
+  }
+
+  static void correct() {
+    PropHolder.Builder b = PropHolder.builder();
+    b.name("Manu").size(1).build();
+  }
+
+  static void wrong() {
+    PropHolder.Builder b = PropHolder.builder();
+    // :: error: finalizer.invocation
+    b.size(1).build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/IsPreserved.java b/checker/tests/calledmethods-autovalue/IsPreserved.java
new file mode 100644
index 0000000..f8fa269
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/IsPreserved.java
@@ -0,0 +1,41 @@
+import com.google.auto.value.AutoValue;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+@AutoValue
+abstract class IsPreserved {
+
+  abstract String name();
+
+  abstract String getAddress();
+
+  abstract boolean isPresent();
+
+  static Builder builder() {
+    return new AutoValue_IsPreserved.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract Builder name(String val);
+
+    abstract Builder getAddress(String val);
+
+    abstract Builder isPresent(boolean value);
+
+    abstract IsPreserved build();
+  }
+
+  public static void buildSomethingRight() {
+    Builder b = builder();
+    b.name("Frank");
+    b.getAddress("something");
+    b.isPresent(true);
+    b.build();
+  }
+
+  public static void buildSomethingRightFluent() {
+    builder().name("Bill").getAddress("something").isPresent(false).build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/NonAVBuilder.java b/checker/tests/calledmethods-autovalue/NonAVBuilder.java
new file mode 100644
index 0000000..7f9dc03
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/NonAVBuilder.java
@@ -0,0 +1,18 @@
+import com.google.auto.value.AutoValue;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+@AutoValue
+abstract class NonAVBuilder {
+  abstract String name();
+
+  public Builder toBuilder() {
+    return new Builder(this);
+  }
+
+  // NOT an AutoValue builder
+  static final class Builder {
+
+    Builder(NonAVBuilder b) {}
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/NonBuildName.java b/checker/tests/calledmethods-autovalue/NonBuildName.java
new file mode 100644
index 0000000..ab25f5c
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/NonBuildName.java
@@ -0,0 +1,33 @@
+import com.google.auto.value.AutoValue;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+@AutoValue
+abstract class NonBuildName {
+
+  public abstract String name();
+
+  static Builder builder() {
+    return new AutoValue_NonBuildName.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract Builder setName(String name);
+
+    abstract NonBuildName makeIt();
+  }
+
+  static void correct() {
+    Builder b = builder();
+    b.setName("Phil");
+    b.makeIt();
+  }
+
+  static void wrong() {
+    Builder b = builder();
+    // :: error: finalizer.invocation
+    b.makeIt();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/Parcel.java b/checker/tests/calledmethods-autovalue/Parcel.java
new file mode 100644
index 0000000..8120360
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/Parcel.java
@@ -0,0 +1,11 @@
+package android.os;
+
+/** stub to avoid bringing in Android dependence */
+public final class Parcel {
+
+  public String readString() {
+    return "";
+  }
+
+  public void writeString(String val) {}
+}
diff --git a/checker/tests/calledmethods-autovalue/Parcelable.java b/checker/tests/calledmethods-autovalue/Parcelable.java
new file mode 100644
index 0000000..7b9fb4f
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/Parcelable.java
@@ -0,0 +1,15 @@
+package android.os;
+
+/** stub to avoid bringing in Android dependence */
+public interface Parcelable {
+  public interface Creator<T> {
+
+    public T createFromParcel(Parcel source);
+
+    public T[] newArray(int size);
+  }
+
+  public int describeContents();
+
+  public void writeToParcel(Parcel dest, int flags);
+}
diff --git a/checker/tests/calledmethods-autovalue/SetInsideBuild.java b/checker/tests/calledmethods-autovalue/SetInsideBuild.java
new file mode 100644
index 0000000..840a265
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/SetInsideBuild.java
@@ -0,0 +1,40 @@
+import com.google.auto.value.AutoValue;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+@AutoValue
+abstract class SetInsideBuild {
+  public abstract String name();
+
+  public abstract int size();
+
+  static Builder builder() {
+    return new AutoValue_SetInsideBuild.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+    abstract Builder setName(String name);
+
+    abstract Builder setSize(int value);
+
+    abstract SetInsideBuild autoBuild();
+
+    public SetInsideBuild build(@CalledMethods({"setName"}) Builder this) {
+
+      return this.setSize(4).autoBuild();
+    }
+  }
+
+  public static void buildSomethingWrong() {
+    Builder b = builder();
+    // :: error: finalizer.invocation
+    b.build();
+  }
+
+  public static void buildSomethingCorrect() {
+    Builder b = builder();
+    b.setName("Frank");
+    b.build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/SetInsideBuildWithCM.java b/checker/tests/calledmethods-autovalue/SetInsideBuildWithCM.java
new file mode 100644
index 0000000..2612da9
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/SetInsideBuildWithCM.java
@@ -0,0 +1,40 @@
+import com.google.auto.value.AutoValue;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+@AutoValue
+abstract class SetInsideBuildWithCM {
+  public abstract String name();
+
+  public abstract int size();
+
+  static Builder builder() {
+    return new AutoValue_SetInsideBuildWithCM.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+    abstract Builder setName(String name);
+
+    abstract Builder setSize(int value);
+
+    abstract SetInsideBuildWithCM autoBuild();
+
+    public SetInsideBuildWithCM build() {
+      return this.autoBuild();
+    }
+  }
+
+  public static void buildSomethingCorrect() {
+    Builder b = builder();
+    b.setName("Frank");
+    b.setSize(2);
+    b.build();
+  }
+
+  public static void buildSomethingWrong() {
+    Builder b = builder();
+    // :: error: finalizer.invocation
+    b.build();
+  }
+}
diff --git a/checker/tests/calledmethods-autovalue/Validation.java b/checker/tests/calledmethods-autovalue/Validation.java
new file mode 100644
index 0000000..0f6f81f
--- /dev/null
+++ b/checker/tests/calledmethods-autovalue/Validation.java
@@ -0,0 +1,41 @@
+import com.google.auto.value.AutoValue;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+@AutoValue
+abstract class Validation {
+
+  public abstract String name();
+
+  static Builder builder() {
+    return new AutoValue_Validation.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract Builder setName(String name);
+
+    abstract Validation autoBuild();
+
+    public Validation build(@CalledMethods("setName") Builder this) {
+      Validation v = autoBuild();
+      if (v.name().length() < 5) {
+        throw new RuntimeException("name too short!");
+      }
+      return v;
+    }
+  }
+
+  static void correct() {
+    Builder b = builder();
+    b.setName("Phil");
+    b.build();
+  }
+
+  static void wrong() {
+    Builder b = builder();
+    // :: error: finalizer.invocation
+    b.build();
+  }
+}
diff --git a/checker/tests/calledmethods-disableframeworks/AnimalSimple.java b/checker/tests/calledmethods-disableframeworks/AnimalSimple.java
new file mode 100644
index 0000000..538b4bf
--- /dev/null
+++ b/checker/tests/calledmethods-disableframeworks/AnimalSimple.java
@@ -0,0 +1,53 @@
+import com.google.auto.value.AutoValue;
+
+/**
+ * Adapted from the standard AutoValue example code:
+ * https://github.com/google/auto/blob/master/value/userguide/builders.md
+ */
+@AutoValue
+abstract class AnimalSimple {
+  abstract String name();
+
+  abstract int numberOfLegs();
+
+  static Builder builder() {
+    return new AutoValue_AnimalSimple.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract Builder setName(String value);
+
+    abstract Builder setNumberOfLegs(int value);
+
+    abstract AnimalSimple build();
+  }
+
+  public static void buildSomethingWrong() {
+    Builder b = builder();
+    b.setName("Frank");
+    b.build();
+  }
+
+  public static void buildSomethingRight() {
+    Builder b = builder();
+    b.setName("Frank");
+    b.setNumberOfLegs(4);
+    b.build();
+  }
+
+  public static void buildSomethingWrongFluent() {
+    builder().setName("Frank").build();
+  }
+
+  public static void buildSomethingRightFluent() {
+    builder().setName("Jim").setNumberOfLegs(7).build();
+  }
+
+  public static void buildSomethingRightFluentWithLocal() {
+    Builder b = builder();
+    b.setName("Jim").setNumberOfLegs(7);
+    b.build();
+  }
+}
diff --git a/checker/tests/calledmethods-disableframeworks/LombokBuilderExample.java b/checker/tests/calledmethods-disableframeworks/LombokBuilderExample.java
new file mode 100644
index 0000000..0e6ce11
--- /dev/null
+++ b/checker/tests/calledmethods-disableframeworks/LombokBuilderExample.java
@@ -0,0 +1,82 @@
+// Generated by delombok at Wed Aug 14 15:57:15 PDT 2019
+// A test for support for the builder() method in Lombok builders.
+public class LombokBuilderExample {
+  @lombok.NonNull Object foo;
+  @lombok.NonNull Object bar;
+
+  static void test() {
+    builder().build();
+  }
+
+  @java.lang.SuppressWarnings("all")
+  @lombok.Generated
+  LombokBuilderExample(@lombok.NonNull final Object foo, @lombok.NonNull final Object bar) {
+    if (foo == null) {
+      throw new java.lang.NullPointerException("foo is marked non-null but is null");
+    }
+    if (bar == null) {
+      throw new java.lang.NullPointerException("bar is marked non-null but is null");
+    }
+    this.foo = foo;
+    this.bar = bar;
+  }
+
+  @java.lang.SuppressWarnings("all")
+  @lombok.Generated
+  public static class LombokBuilderExampleBuilder {
+    @java.lang.SuppressWarnings("all")
+    @lombok.Generated
+    private Object foo;
+
+    @java.lang.SuppressWarnings("all")
+    @lombok.Generated
+    private Object bar;
+
+    @java.lang.SuppressWarnings("all")
+    @lombok.Generated
+    LombokBuilderExampleBuilder() {}
+
+    @java.lang.SuppressWarnings("all")
+    @lombok.Generated
+    public LombokBuilderExampleBuilder foo(@lombok.NonNull final Object foo) {
+      if (foo == null) {
+        throw new java.lang.NullPointerException("foo is marked non-null but is null");
+      }
+      this.foo = foo;
+      return this;
+    }
+
+    @java.lang.SuppressWarnings("all")
+    @lombok.Generated
+    public LombokBuilderExampleBuilder bar(@lombok.NonNull final Object bar) {
+      if (bar == null) {
+        throw new java.lang.NullPointerException("bar is marked non-null but is null");
+      }
+      this.bar = bar;
+      return this;
+    }
+
+    @java.lang.SuppressWarnings("all")
+    @lombok.Generated
+    public LombokBuilderExample build() {
+      return new LombokBuilderExample(foo, bar);
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("all")
+    @lombok.Generated
+    public java.lang.String toString() {
+      return "LombokBuilderExample.LombokBuilderExampleBuilder(foo="
+          + this.foo
+          + ", bar="
+          + this.bar
+          + ")";
+    }
+  }
+
+  @java.lang.SuppressWarnings("all")
+  @lombok.Generated
+  public static LombokBuilderExampleBuilder builder() {
+    return new LombokBuilderExampleBuilder();
+  }
+}
diff --git a/checker/tests/calledmethods-disableframeworks/Parcel.java b/checker/tests/calledmethods-disableframeworks/Parcel.java
new file mode 100644
index 0000000..8120360
--- /dev/null
+++ b/checker/tests/calledmethods-disableframeworks/Parcel.java
@@ -0,0 +1,11 @@
+package android.os;
+
+/** stub to avoid bringing in Android dependence */
+public final class Parcel {
+
+  public String readString() {
+    return "";
+  }
+
+  public void writeString(String val) {}
+}
diff --git a/checker/tests/calledmethods-disableframeworks/Parcelable.java b/checker/tests/calledmethods-disableframeworks/Parcelable.java
new file mode 100644
index 0000000..7b9fb4f
--- /dev/null
+++ b/checker/tests/calledmethods-disableframeworks/Parcelable.java
@@ -0,0 +1,15 @@
+package android.os;
+
+/** stub to avoid bringing in Android dependence */
+public interface Parcelable {
+  public interface Creator<T> {
+
+    public T createFromParcel(Parcel source);
+
+    public T[] newArray(int size);
+  }
+
+  public int describeContents();
+
+  public void writeToParcel(Parcel dest, int flags);
+}
diff --git a/checker/tests/calledmethods-disableframeworks/README.md b/checker/tests/calledmethods-disableframeworks/README.md
new file mode 100644
index 0000000..2e53a0d
--- /dev/null
+++ b/checker/tests/calledmethods-disableframeworks/README.md
@@ -0,0 +1,6 @@
+These are test cases for when we disable the framework supports for AutoValue and Lombok, therefore
+the checker annotation won't be automatically inserted into the code.
+These two test cases are adapted from _autovalue/Animal.java_ and _lombok/LombokBuilderExample.java_
+but removed all `// :: error:` comments.
+
+Parcel and Parcelable have to be included here to avoid a crash in the auto-value-parcel plugin.
diff --git a/checker/tests/calledmethods-disablereturnsreceiver/SimpleFluentInference.java b/checker/tests/calledmethods-disablereturnsreceiver/SimpleFluentInference.java
new file mode 100644
index 0000000..eafb11a
--- /dev/null
+++ b/checker/tests/calledmethods-disablereturnsreceiver/SimpleFluentInference.java
@@ -0,0 +1,76 @@
+// This is a copy of SimpleFluentInference.java in the main calledmethods
+// test directory. The only difference is that the expected errors have been
+// modified to account for the Returns Receiver checker having been disabled.
+
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+/* Simple inference of a fluent builder */
+public class SimpleFluentInference {
+  SimpleFluentInference build(@CalledMethods({"a", "b"}) SimpleFluentInference this) {
+    return this;
+  }
+
+  SimpleFluentInference weakbuild(@CalledMethods({"a"}) SimpleFluentInference this) {
+    return this;
+  }
+
+  @This SimpleFluentInference a() {
+    return this;
+  }
+
+  @This SimpleFluentInference b() {
+    return this;
+  }
+
+  // intentionally does not have an @This annotation
+  SimpleFluentInference c() {
+    return new SimpleFluentInference();
+  }
+
+  static void doStuffCorrect() {
+    SimpleFluentInference s =
+        new SimpleFluentInference()
+            .a()
+            .b()
+            // :: error: finalizer.invocation
+            .build();
+  }
+
+  static void doStuffWrong() {
+    SimpleFluentInference s =
+        new SimpleFluentInference()
+            .a()
+            // :: error: finalizer.invocation
+            .build();
+  }
+
+  static void doStuffRightWeak() {
+    SimpleFluentInference s =
+        new SimpleFluentInference()
+            .a()
+            // :: error: finalizer.invocation
+            .weakbuild();
+  }
+
+  static void noReturnsReceiverAnno() {
+    SimpleFluentInference s =
+        new SimpleFluentInference()
+            .a()
+            .b()
+            .c()
+            // :: error: finalizer.invocation
+            .build();
+  }
+
+  static void fluentLoop() {
+    SimpleFluentInference s = new SimpleFluentInference().a();
+    int i = 10;
+    while (i > 0) {
+      // :: error: finalizer.invocation
+      s.b().build();
+      i--;
+      s = new SimpleFluentInference();
+    }
+  }
+}
diff --git a/checker/tests/calledmethods-lombok/BuilderTest.java b/checker/tests/calledmethods-lombok/BuilderTest.java
new file mode 100644
index 0000000..c166b05
--- /dev/null
+++ b/checker/tests/calledmethods-lombok/BuilderTest.java
@@ -0,0 +1,27 @@
+import lombok.Builder;
+import lombok.NonNull;
+
+@Builder
+public class BuilderTest {
+  private Integer x;
+  @NonNull private Integer y;
+  @Builder.Default @NonNull private Integer z = 5;
+
+  public static void test_simplePattern() {
+    BuilderTest.builder().x(0).y(0).build(); // good builder
+    BuilderTest.builder().y(0).build(); // good builder
+    BuilderTest.builder().y(0).z(5).build(); // good builder
+    // :: error: (finalizer.invocation)
+    BuilderTest.builder().x(0).build(); // bad builder
+  }
+
+  public static void test_builderVar() {
+    final BuilderTest.BuilderTestBuilder goodBuilder = new BuilderTestBuilder();
+    goodBuilder.x(0);
+    goodBuilder.y(0);
+    goodBuilder.build();
+    final BuilderTest.BuilderTestBuilder badBuilder = new BuilderTestBuilder();
+    // :: error: (finalizer.invocation)
+    badBuilder.build();
+  }
+}
diff --git a/checker/tests/calledmethods-lombok/CheckerFrameworkBuilder.java b/checker/tests/calledmethods-lombok/CheckerFrameworkBuilder.java
new file mode 100644
index 0000000..5987979
--- /dev/null
+++ b/checker/tests/calledmethods-lombok/CheckerFrameworkBuilder.java
@@ -0,0 +1,190 @@
+// skip-idempotent
+import java.util.List;
+
+public class CheckerFrameworkBuilder {
+
+  /**
+   * Most of this test was copied from
+   * https://raw.githubusercontent.com/rzwitserloot/lombok/master/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java
+   * with the exception of the following lines until the next long comment. I have made one change
+   * outside the scope of these comments: - I fixed the placement of the type annotations, which
+   * were originally on scoping constructs. I think this is a bug in the delombok pretty-printer
+   * used to generate this code, but I wasn't able to find the configuration options used to
+   * reproduce it in the public release.
+   *
+   * <p>This test represents exactly the code that Lombok generates with the checkerframework = True
+   * option in a lombok.config file, including the weird package names they use for the CF and the
+   * {@code @NotCalledMethods} annotation that they use even though we don't (and never have)
+   * supported such a thing.
+   *
+   * <p>The new code added in this block ensures that the Called Methods checker handles clients of
+   * the copied code correctly.
+   */
+  public static void testOldCalledMethodsGood(
+          @org.checkerframework.checker.calledmethods.qual.CalledMethods({"y", "z"}) CheckerFrameworkBuilderBuilder pb) {
+    pb.build();
+  }
+
+  public static void testOldCalledMethodsBad(
+          @org.checkerframework.checker.calledmethods.qual.CalledMethods({"y"}) CheckerFrameworkBuilderBuilder pb) {
+    // :: error: finalizer.invocation
+    pb.build(); // pb requires y, z
+  }
+
+  public static void testOldRRGood() {
+    CheckerFrameworkBuilder b = CheckerFrameworkBuilder.builder().y(5).z(6).build();
+  }
+
+  public static void testOldRRBad() {
+    CheckerFrameworkBuilder b =
+        // :: error: finalizer.invocation
+        CheckerFrameworkBuilder.builder().z(6).build(); // also needs to call y
+  }
+
+  /** End new, non-copied code. */
+  int x;
+
+  int y;
+  int z;
+  List<String> names;
+
+  @java.lang.SuppressWarnings("all")
+  private static int $default$x() {
+    return 5;
+  }
+
+  @org.checkerframework.common.aliasing.qual.Unique @java.lang.SuppressWarnings("all")
+  CheckerFrameworkBuilder(final int x, final int y, final int z, final List<String> names) {
+    this.x = x;
+    this.y = y;
+    this.z = z;
+    this.names = names;
+  }
+
+  @java.lang.SuppressWarnings("all")
+  public static class CheckerFrameworkBuilderBuilder {
+    @java.lang.SuppressWarnings("all")
+    private boolean x$set;
+
+    @java.lang.SuppressWarnings("all")
+    private int x$value;
+
+    @java.lang.SuppressWarnings("all")
+    private int y;
+
+    @java.lang.SuppressWarnings("all")
+    private int z;
+
+    @java.lang.SuppressWarnings("all")
+    private java.util.ArrayList<String> names;
+
+    @org.checkerframework.common.aliasing.qual.Unique @java.lang.SuppressWarnings("all")
+    CheckerFrameworkBuilderBuilder() {}
+
+    @org.checkerframework.checker.builder.qual.ReturnsReceiver
+    @java.lang.SuppressWarnings("all")
+    public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder x(
+        CheckerFrameworkBuilder.
+            @org.checkerframework.checker.builder.qual.NotCalledMethods("x") CheckerFrameworkBuilderBuilder
+            this,
+        final int x) {
+      this.x$value = x;
+      x$set = true;
+      return this;
+    }
+
+    @org.checkerframework.checker.builder.qual.ReturnsReceiver
+    @java.lang.SuppressWarnings("all")
+    public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder y(
+        CheckerFrameworkBuilder.
+            @org.checkerframework.checker.builder.qual.NotCalledMethods("y") CheckerFrameworkBuilderBuilder
+            this,
+        final int y) {
+      this.y = y;
+      return this;
+    }
+
+    @org.checkerframework.checker.builder.qual.ReturnsReceiver
+    @java.lang.SuppressWarnings("all")
+    public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder z(
+        CheckerFrameworkBuilder.
+            @org.checkerframework.checker.builder.qual.NotCalledMethods("z") CheckerFrameworkBuilderBuilder
+            this,
+        final int z) {
+      this.z = z;
+      return this;
+    }
+
+    @org.checkerframework.checker.builder.qual.ReturnsReceiver
+    @java.lang.SuppressWarnings("all")
+    public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder name(final String name) {
+      if (this.names == null) this.names = new java.util.ArrayList<String>();
+      this.names.add(name);
+      return this;
+    }
+
+    @org.checkerframework.checker.builder.qual.ReturnsReceiver
+    @java.lang.SuppressWarnings("all")
+    public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder names(
+        final java.util.Collection<? extends String> names) {
+      if (names == null) {
+        throw new java.lang.NullPointerException("names cannot be null");
+      }
+      if (this.names == null) this.names = new java.util.ArrayList<String>();
+      this.names.addAll(names);
+      return this;
+    }
+
+    @org.checkerframework.checker.builder.qual.ReturnsReceiver
+    @java.lang.SuppressWarnings("all")
+    public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder clearNames() {
+      if (this.names != null) this.names.clear();
+      return this;
+    }
+
+    @org.checkerframework.dataflow.qual.SideEffectFree
+    @java.lang.SuppressWarnings("all")
+    public CheckerFrameworkBuilder build(
+        CheckerFrameworkBuilder.
+            @org.checkerframework.checker.builder.qual.CalledMethods({"y", "z"}) CheckerFrameworkBuilderBuilder
+            this) {
+      java.util.List<String> names;
+      switch (this.names == null ? 0 : this.names.size()) {
+        case 0:
+          names = java.util.Collections.emptyList();
+          break;
+        case 1:
+          names = java.util.Collections.singletonList(this.names.get(0));
+          break;
+        default:
+          names =
+              java.util.Collections.unmodifiableList(new java.util.ArrayList<String>(this.names));
+      }
+      int x$value = this.x$value;
+      if (!this.x$set) x$value = CheckerFrameworkBuilder.$default$x();
+      return new CheckerFrameworkBuilder(x$value, this.y, this.z, names);
+    }
+
+    @org.checkerframework.dataflow.qual.SideEffectFree
+    @java.lang.Override
+    @java.lang.SuppressWarnings("all")
+    public java.lang.String toString() {
+      return "CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder(x$value="
+          + this.x$value
+          + ", y="
+          + this.y
+          + ", z="
+          + this.z
+          + ", names="
+          + this.names
+          + ")";
+    }
+  }
+
+  @org.checkerframework.dataflow.qual.SideEffectFree
+  @java.lang.SuppressWarnings("all")
+  public static CheckerFrameworkBuilder.
+      @org.checkerframework.common.aliasing.qual.Unique CheckerFrameworkBuilderBuilder builder() {
+    return new CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder();
+  }
+}
diff --git a/checker/tests/calledmethods-lombok/DefaultedName.java b/checker/tests/calledmethods-lombok/DefaultedName.java
new file mode 100644
index 0000000..02cb09c
--- /dev/null
+++ b/checker/tests/calledmethods-lombok/DefaultedName.java
@@ -0,0 +1,18 @@
+import lombok.Builder;
+
+@Builder
+public class DefaultedName {
+  @Builder.Default @lombok.NonNull String name = "Martin";
+
+  static void test1() {
+    builder().build();
+  }
+
+  static void test2(Object foo) {
+    DefaultedNameBuilder b = builder();
+    if (foo != null) {
+      b.name(foo.toString());
+    }
+    b.build();
+  }
+}
diff --git a/checker/tests/calledmethods-lombok/LombokBuilderExample.java b/checker/tests/calledmethods-lombok/LombokBuilderExample.java
new file mode 100644
index 0000000..16fbe6b
--- /dev/null
+++ b/checker/tests/calledmethods-lombok/LombokBuilderExample.java
@@ -0,0 +1,14 @@
+// A test for support for the builder() method in Lombok builders.
+
+import lombok.Builder;
+
+@Builder
+public class LombokBuilderExample {
+  @lombok.NonNull Object foo;
+  @lombok.NonNull Object bar;
+
+  static void test() {
+    // :: error: (finalizer.invocation)
+    builder().build();
+  }
+}
diff --git a/checker/tests/calledmethods-lombok/LombokDefaultAssignments.java b/checker/tests/calledmethods-lombok/LombokDefaultAssignments.java
new file mode 100644
index 0000000..a2c164e
--- /dev/null
+++ b/checker/tests/calledmethods-lombok/LombokDefaultAssignments.java
@@ -0,0 +1,15 @@
+import java.util.Optional;
+import lombok.Builder;
+
+@Builder
+public class LombokDefaultAssignments {
+  @lombok.NonNull Optional<String> bar;
+
+  public static class LombokDefaultAssignmentsBuilder {
+    private Optional<String> bar = Optional.empty();
+  }
+
+  static void test() {
+    LombokDefaultAssignments.builder().build();
+  }
+}
diff --git a/checker/tests/calledmethods-lombok/LombokNoSingularButClearMethodExample.java b/checker/tests/calledmethods-lombok/LombokNoSingularButClearMethodExample.java
new file mode 100644
index 0000000..b65eff3
--- /dev/null
+++ b/checker/tests/calledmethods-lombok/LombokNoSingularButClearMethodExample.java
@@ -0,0 +1,22 @@
+import java.util.List;
+import lombok.Builder;
+
+@Builder
+public class LombokNoSingularButClearMethodExample {
+  @lombok.NonNull List<Object> items;
+
+  // This one should throw an error, because the field isn't
+  // automatically initialized.
+  public static void testNoItems() {
+    // :: error: finalizer.invocation
+    LombokNoSingularButClearMethodExample.builder().build();
+  }
+
+  public static void testWithList(List<Object> l) {
+    LombokNoSingularButClearMethodExample.builder().items(l).build();
+  }
+
+  public static class LombokNoSingularButClearMethodExampleBuilder {
+    public void clearItems() {}
+  }
+}
diff --git a/checker/tests/calledmethods-lombok/LombokSingularExample.java b/checker/tests/calledmethods-lombok/LombokSingularExample.java
new file mode 100644
index 0000000..82b171e
--- /dev/null
+++ b/checker/tests/calledmethods-lombok/LombokSingularExample.java
@@ -0,0 +1,24 @@
+import java.util.List;
+import lombok.Builder;
+import lombok.Singular;
+
+@Builder
+public class LombokSingularExample {
+  @Singular @lombok.NonNull List<Object> items;
+
+  // This one should be permitted, because @Singular will
+  // produce an empty list even if one is not specified directly.
+  public static void testNoItems() {
+    LombokSingularExample.builder().build();
+  }
+
+  // This call should also be permitted, even though items() is
+  // not called, because the list will be automatically initialized.
+  public static void testOneItem() {
+    LombokSingularExample.builder().item(new Object()).build();
+  }
+
+  public static void testWithList(List<Object> l) {
+    LombokSingularExample.builder().items(l).build();
+  }
+}
diff --git a/checker/tests/calledmethods-lombok/LombokToBuilderExample.java b/checker/tests/calledmethods-lombok/LombokToBuilderExample.java
new file mode 100644
index 0000000..0295c40
--- /dev/null
+++ b/checker/tests/calledmethods-lombok/LombokToBuilderExample.java
@@ -0,0 +1,17 @@
+// A test for support for the toBuilder() method in Lombok builders.
+
+import lombok.Builder;
+
+@Builder(toBuilder = true)
+public class LombokToBuilderExample {
+  @lombok.NonNull String req;
+
+  static void test(LombokToBuilderExample foo) {
+    foo.toBuilder().build();
+  }
+
+  static void ensureThatErrorIssued() {
+    // :: error: finalizer.invocation
+    LombokToBuilderExample.builder().build();
+  }
+}
diff --git a/checker/tests/calledmethods-lombok/OldInherited.java b/checker/tests/calledmethods-lombok/OldInherited.java
new file mode 100644
index 0000000..d0b549d
--- /dev/null
+++ b/checker/tests/calledmethods-lombok/OldInherited.java
@@ -0,0 +1,45 @@
+// This is a test for the old @ReturnsReceiver annotation, which is inherited.
+// No one should ever write that annotation, but Lombok generates it as of version 1.18.10.
+
+import org.checkerframework.checker.builder.qual.ReturnsReceiver;
+import org.checkerframework.checker.calledmethods.qual.*;
+
+public class OldInherited {
+  @ReturnsReceiver
+  OldInherited getThis() {
+    return this;
+  }
+
+  static class OldInheritedChild extends OldInherited {
+    @java.lang.Override
+    OldInherited getThis() {
+      return this;
+    }
+  }
+
+  void requiresGetThis(@CalledMethods("getThis") OldInherited this) {}
+
+  public static void testGoodParent() {
+    OldInherited o = new OldInherited();
+    o.getThis();
+    o.requiresGetThis();
+  }
+
+  public static void testGoodChild() {
+    OldInheritedChild o = new OldInheritedChild();
+    o.getThis();
+    o.requiresGetThis();
+  }
+
+  public static void testBadParent() {
+    OldInherited o = new OldInherited();
+    // :: error: finalizer.invocation
+    o.requiresGetThis();
+  }
+
+  public static void testBadChild() {
+    OldInheritedChild o = new OldInheritedChild();
+    // :: error: finalizer.invocation
+    o.requiresGetThis();
+  }
+}
diff --git a/checker/tests/calledmethods-lombok/lombok.config b/checker/tests/calledmethods-lombok/lombok.config
new file mode 100644
index 0000000..7a21e88
--- /dev/null
+++ b/checker/tests/calledmethods-lombok/lombok.config
@@ -0,0 +1 @@
+lombok.addLombokGeneratedAnnotation = true
diff --git a/checker/tests/calledmethods-usevaluechecker/Cve.java b/checker/tests/calledmethods-usevaluechecker/Cve.java
new file mode 100644
index 0000000..f0a440d
--- /dev/null
+++ b/checker/tests/calledmethods-usevaluechecker/Cve.java
@@ -0,0 +1,107 @@
+import com.amazonaws.services.ec2.AmazonEC2;
+import com.amazonaws.services.ec2.AmazonEC2AsyncClient;
+import com.amazonaws.services.ec2.AmazonEC2Client;
+import com.amazonaws.services.ec2.model.DescribeImagesRequest;
+import com.amazonaws.services.ec2.model.DescribeImagesResult;
+import com.amazonaws.services.ec2.model.Filter;
+
+// https://nvd.nist.gov/vuln/detail/CVE-2018-15869
+public class Cve {
+  private static final String IMG_NAME = "some_linux_img";
+
+  public static void onlyNames(AmazonEC2 client) {
+    // Should not be allowed unless .withOwner is also used
+    DescribeImagesResult result =
+        client.describeImages(
+            new DescribeImagesRequest()
+                // :: error: argument
+                .withFilters(new Filter("name").withValues(IMG_NAME)));
+  }
+
+  public static void correct1(AmazonEC2 client) {
+    DescribeImagesResult result =
+        client.describeImages(
+            new DescribeImagesRequest()
+                .withFilters(new Filter("name").withValues(IMG_NAME))
+                .withOwners("martin"));
+  }
+
+  public static void correct2(AmazonEC2 client) {
+    DescribeImagesResult result =
+        client.describeImages(new DescribeImagesRequest().withImageIds("myImageId"));
+  }
+
+  public static void correct3(AmazonEC2 client) {
+    DescribeImagesResult result =
+        client.describeImages(new DescribeImagesRequest().withExecutableUsers("myUsers"));
+  }
+
+  // Using impl class instead of interface
+  public static void onlyNamesImpl(AmazonEC2Client client) {
+    // Should not be allowed unless .withOwner is also used
+    DescribeImagesResult result =
+        client.describeImages(
+            new DescribeImagesRequest()
+                // :: error: argument
+                .withFilters(new Filter("name").withValues(IMG_NAME)));
+  }
+
+  public static void correct1Impl(AmazonEC2Client client) {
+    DescribeImagesResult result =
+        client.describeImages(
+            new DescribeImagesRequest()
+                .withFilters(new Filter("name").withValues(IMG_NAME))
+                .withOwners("martin"));
+  }
+
+  public static void correct2Impl(AmazonEC2Client client) {
+    DescribeImagesResult result =
+        client.describeImages(new DescribeImagesRequest().withImageIds("myImageId"));
+  }
+
+  // Using async impl class
+  public static void onlyNamesAsync(AmazonEC2AsyncClient client) {
+    // Should not be allowed unless .withOwner is also used
+    DescribeImagesResult result =
+        client.describeImages(
+            new DescribeImagesRequest()
+                // :: error: argument
+                .withFilters(new Filter("name").withValues(IMG_NAME)));
+  }
+
+  public static void correct1Async(AmazonEC2AsyncClient client) {
+    DescribeImagesResult result =
+        client.describeImages(
+            new DescribeImagesRequest()
+                .withFilters(new Filter("name").withValues(IMG_NAME))
+                .withOwners("martin"));
+  }
+
+  public static void correct2Async(AmazonEC2AsyncClient client) {
+    DescribeImagesResult result =
+        client.describeImages(new DescribeImagesRequest().withImageIds("myImageId"));
+  }
+
+  // Using async methods
+  public static void onlyNamesAsync2(AmazonEC2AsyncClient client) {
+    // Should not be allowed unless .withOwner is also used
+    Object result =
+        client.describeImagesAsync(
+            new DescribeImagesRequest()
+                // :: error: argument
+                .withFilters(new Filter("name").withValues(IMG_NAME)));
+  }
+
+  public static void correct1Async2(AmazonEC2AsyncClient client) {
+    Object result =
+        client.describeImagesAsync(
+            new DescribeImagesRequest()
+                .withFilters(new Filter("name").withValues(IMG_NAME))
+                .withOwners("martin"));
+  }
+
+  public static void correct2Async2(AmazonEC2AsyncClient client) {
+    Object result =
+        client.describeImagesAsync(new DescribeImagesRequest().withImageIds("myImageId"));
+  }
+}
diff --git a/checker/tests/calledmethods-usevaluechecker/Cve2.java b/checker/tests/calledmethods-usevaluechecker/Cve2.java
new file mode 100644
index 0000000..d1e0d69
--- /dev/null
+++ b/checker/tests/calledmethods-usevaluechecker/Cve2.java
@@ -0,0 +1,41 @@
+import com.amazonaws.services.ec2.AmazonEC2;
+import com.amazonaws.services.ec2.model.DescribeImagesRequest;
+import com.amazonaws.services.ec2.model.DescribeImagesResult;
+import com.amazonaws.services.ec2.model.Filter;
+import java.util.Collections;
+
+// https://nvd.nist.gov/vuln/detail/CVE-2018-15869
+public class Cve2 {
+  private static final String IMG_NAME = "some_linux_img";
+
+  public static void onlyNames(AmazonEC2 client) {
+    // Should not be allowed unless .withOwner is also used
+    DescribeImagesRequest request = new DescribeImagesRequest();
+    request.withFilters(new Filter("name").withValues(IMG_NAME));
+
+    // :: error: argument
+    DescribeImagesResult result = client.describeImages(request);
+  }
+
+  public static void correct1(AmazonEC2 client) {
+    DescribeImagesRequest request = new DescribeImagesRequest();
+    request.withFilters(new Filter("name").withValues(IMG_NAME));
+    request.withOwners("martin");
+
+    DescribeImagesResult result = client.describeImages(request);
+  }
+
+  public static void correct2(AmazonEC2 client) {
+    DescribeImagesRequest request = new DescribeImagesRequest();
+    request.withImageIds("myImageId");
+
+    DescribeImagesResult result = client.describeImages(request);
+  }
+
+  public static void correct3(AmazonEC2 client) {
+    DescribeImagesRequest request = new DescribeImagesRequest();
+    request.setExecutableUsers(Collections.singletonList("myUser1"));
+
+    DescribeImagesResult result = client.describeImages(request);
+  }
+}
diff --git a/checker/tests/calledmethods-usevaluechecker/GenerateDataKeyRequestExamples.java b/checker/tests/calledmethods-usevaluechecker/GenerateDataKeyRequestExamples.java
new file mode 100644
index 0000000..030cb89
--- /dev/null
+++ b/checker/tests/calledmethods-usevaluechecker/GenerateDataKeyRequestExamples.java
@@ -0,0 +1,112 @@
+// Examples of trying to prove the key size was set correctly on a AWS GenerateDataKeyRequest object
+
+import com.amazonaws.services.kms.AWSKMS;
+import com.amazonaws.services.kms.model.DataKeySpec;
+import com.amazonaws.services.kms.model.GenerateDataKeyRequest;
+import org.checkerframework.checker.calledmethods.qual.*;
+
+public class GenerateDataKeyRequestExamples {
+
+  void correctWithKeySpec(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    request.withKeySpec(DataKeySpec.AES_256);
+    client.generateDataKey(request);
+  }
+
+  void correctWithNumberOfBytes(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    request.withNumberOfBytes(32);
+    client.generateDataKey(request);
+  }
+
+  void correctSetKeySpec(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    request.setKeySpec(DataKeySpec.AES_256);
+    client.generateDataKey(request);
+  }
+
+  void correctSetNumberOfBytes(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    request.setNumberOfBytes(32);
+    client.generateDataKey(request);
+  }
+
+  // The next four examples are "both"
+  void incorrect1(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    request.setKeySpec(DataKeySpec.AES_256);
+    request.setNumberOfBytes(32);
+    // :: error: argument
+    client.generateDataKey(request);
+  }
+
+  void incorrect2(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    request.withKeySpec(DataKeySpec.AES_256);
+    request.setNumberOfBytes(32);
+    // :: error: argument
+    client.generateDataKey(request);
+  }
+
+  void incorrect3(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    request.setKeySpec(DataKeySpec.AES_256);
+    request.withNumberOfBytes(32);
+    // :: error: argument
+    client.generateDataKey(request);
+  }
+
+  void incorrect4(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    request.withKeySpec(DataKeySpec.AES_256);
+    request.withNumberOfBytes(32);
+    // :: error: argument
+    client.generateDataKey(request);
+  }
+
+  // This example is "neither"
+  void incorrect5(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    // :: error: argument
+    client.generateDataKey(request);
+  }
+
+  // Calling these methods are idempotent, including between with/set versions of the same.
+  // TODO: Verify that these calls should be permitted.
+  void setTwice1(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    request.withKeySpec(DataKeySpec.AES_256);
+    request.withKeySpec(DataKeySpec.AES_256);
+    client.generateDataKey(request);
+  }
+
+  void setTwice2(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    request.withKeySpec(DataKeySpec.AES_256);
+    request.setKeySpec(DataKeySpec.AES_256);
+    client.generateDataKey(request);
+  }
+
+  void setTwice3(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    request.withNumberOfBytes(32);
+    request.setNumberOfBytes(32);
+    client.generateDataKey(request);
+  }
+
+  void setTwice4(AWSKMS client) {
+    GenerateDataKeyRequest request = new GenerateDataKeyRequest();
+    request.setNumberOfBytes(32);
+    request.setNumberOfBytes(32);
+    client.generateDataKey(request);
+  }
+
+  /// Interprocedural
+
+  void callee2(
+      AWSKMS client,
+          @CalledMethodsPredicate("(!withNumberOfBytes) && (!setNumberOfBytes)") GenerateDataKeyRequest request) {
+    request.withKeySpec(DataKeySpec.AES_256);
+    client.generateDataKey(request);
+  }
+}
diff --git a/checker/tests/calledmethods-usevaluechecker/MorePreciseFilters.java b/checker/tests/calledmethods-usevaluechecker/MorePreciseFilters.java
new file mode 100644
index 0000000..e315df0
--- /dev/null
+++ b/checker/tests/calledmethods-usevaluechecker/MorePreciseFilters.java
@@ -0,0 +1,69 @@
+import com.amazonaws.services.ec2.AmazonEC2;
+import com.amazonaws.services.ec2.model.DescribeImagesRequest;
+import com.amazonaws.services.ec2.model.DescribeImagesResult;
+import com.amazonaws.services.ec2.model.Filter;
+import java.util.Arrays;
+import java.util.Collections;
+
+public class MorePreciseFilters {
+
+  /* TODO: handle lists
+  void ownerAliasList(AmazonEC2 ec2Client) {
+      DescribeImagesRequest imagesRequest = new DescribeImagesRequest();
+      List<Filter> imageFilters = new ArrayList<Filter>();
+      imageFilters.add(new Filter().withName("owner-alias").withValues("microsoft"));
+      ec2Client.describeImages(imagesRequest.withFilters(imageFilters)).getImages();
+  }
+  */
+
+  void withFilterNameInList(AmazonEC2 ec2Client) {
+    DescribeImagesRequest request = new DescribeImagesRequest();
+    request.setFilters(
+        Collections.singletonList(new Filter().withName("image-id").withValues("12345")));
+
+    DescribeImagesResult result = ec2Client.describeImages(request);
+  }
+
+  void withOwnerId(AmazonEC2 ec2) {
+    DescribeImagesRequest request =
+        new DescribeImagesRequest()
+            .withFilters(
+                new Filter("name", Arrays.asList("my_image_name")),
+                new Filter("owner-id", Arrays.asList("12345")));
+    DescribeImagesResult result = ec2.describeImages(request);
+  }
+
+  void withName(AmazonEC2 ec2Client) {
+    DescribeImagesRequest request = new DescribeImagesRequest();
+    request.withFilters(new Filter().withName("image-id").withValues("12345"));
+    DescribeImagesResult result = ec2Client.describeImages(request);
+  }
+
+  void withName2(AmazonEC2 ec2Client) {
+    DescribeImagesRequest request = new DescribeImagesRequest();
+    request.withFilters(new Filter().withName("image-id").withName("foo").withValues("12345"));
+    // :: error: (argument)
+    DescribeImagesResult result = ec2Client.describeImages(request);
+  }
+
+  void withName3(AmazonEC2 ec2Client) {
+    DescribeImagesRequest request = new DescribeImagesRequest();
+    request.withFilters(new Filter().withName("foo").withName("image-id").withValues("12345"));
+    DescribeImagesResult result = ec2Client.describeImages(request);
+  }
+
+  void withName4(AmazonEC2 ec2Client) {
+    DescribeImagesRequest request = new DescribeImagesRequest();
+    request.withFilters(
+        new Filter().withName("owner-id").withName("foo").withValues("12345"),
+        new Filter("owner-id", Arrays.asList("12345")));
+    DescribeImagesResult result = ec2Client.describeImages(request);
+  }
+
+  void withName5(AmazonEC2 ec2Client) {
+    DescribeImagesRequest request = new DescribeImagesRequest();
+    Filter f = new Filter();
+    request.withFilters(f.withName("owner-id").withValues("12345"));
+    DescribeImagesResult result = ec2Client.describeImages(request);
+  }
+}
diff --git a/checker/tests/calledmethods-usevaluechecker/OnlyOwnersFalsePositive.java b/checker/tests/calledmethods-usevaluechecker/OnlyOwnersFalsePositive.java
new file mode 100644
index 0000000..0d3b205
--- /dev/null
+++ b/checker/tests/calledmethods-usevaluechecker/OnlyOwnersFalsePositive.java
@@ -0,0 +1,15 @@
+import com.amazonaws.services.ec2.AmazonEC2;
+import com.amazonaws.services.ec2.model.DescribeImagesRequest;
+import com.amazonaws.services.ec2.model.DescribeImagesResult;
+import java.util.Collections;
+
+// Tests that just setting with/setOwners is permitted, since there are legitimate reasons to do
+// that.
+// Originally, we required with/setFilters && with/setOwners.
+public class OnlyOwnersFalsePositive {
+  void test(AmazonEC2 ec2Client) {
+    DescribeImagesRequest describeImagesRequest = new DescribeImagesRequest();
+    describeImagesRequest.setOwners(Collections.singleton("self"));
+    DescribeImagesResult describeImagesResult = ec2Client.describeImages(describeImagesRequest);
+  }
+}
diff --git a/checker/tests/calledmethods-usevaluechecker/RequestCreatedInCall.java b/checker/tests/calledmethods-usevaluechecker/RequestCreatedInCall.java
new file mode 100644
index 0000000..21ab8d8
--- /dev/null
+++ b/checker/tests/calledmethods-usevaluechecker/RequestCreatedInCall.java
@@ -0,0 +1,15 @@
+import com.amazonaws.services.ec2.AmazonEC2;
+import com.amazonaws.services.ec2.model.DescribeImagesRequest;
+import com.amazonaws.services.ec2.model.DescribeImagesResult;
+import com.amazonaws.services.ec2.model.Filter;
+import java.util.*;
+
+// A test to ensure that requests that are created in the call to describeImages work correctly.
+public class RequestCreatedInCall {
+  void test(AmazonEC2 ec2) {
+    List<Filter> filters = new ArrayList<>();
+    filters.add(new Filter().withName("foo").withValues("bar"));
+    DescribeImagesResult describeImagesResult =
+        ec2.describeImages(new DescribeImagesRequest().withOwners("martin").withFilters(filters));
+  }
+}
diff --git a/checker/tests/calledmethods-usevaluechecker/SimpleFalsePositive.java b/checker/tests/calledmethods-usevaluechecker/SimpleFalsePositive.java
new file mode 100644
index 0000000..bd3655f
--- /dev/null
+++ b/checker/tests/calledmethods-usevaluechecker/SimpleFalsePositive.java
@@ -0,0 +1,19 @@
+import com.amazonaws.services.ec2.AmazonEC2;
+import com.amazonaws.services.ec2.model.DescribeImagesRequest;
+import com.amazonaws.services.ec2.model.DescribeImagesResult;
+import com.amazonaws.services.ec2.model.Filter;
+import java.util.*;
+
+// A simple (potential) false positive case with mutliple filters.
+public class SimpleFalsePositive {
+  void test(AmazonEC2 ec2Client, String namePrefix) {
+    DescribeImagesRequest request =
+        new DescribeImagesRequest()
+            .withOwners("martin")
+            .withFilters(
+                Arrays.asList(
+                    new Filter("platform", Arrays.asList("windows")),
+                    new Filter("name", Arrays.asList(String.format("%s*", namePrefix)))));
+    DescribeImagesResult result = ec2Client.describeImages(request);
+  }
+}
diff --git a/checker/tests/calledmethods-usevaluechecker/SpecialNames.java b/checker/tests/calledmethods-usevaluechecker/SpecialNames.java
new file mode 100644
index 0000000..07cc1eb
--- /dev/null
+++ b/checker/tests/calledmethods-usevaluechecker/SpecialNames.java
@@ -0,0 +1,60 @@
+// This test ensures that special handling for method names in DescribeImagesRequest isn't used for
+// other classes with the same names.
+
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+public class SpecialNames {
+  @This SpecialNames withFilters() {
+    return this;
+  }
+
+  void setFilters() {}
+
+  @This SpecialNames withFilters(SpecialNames f) {
+    return this;
+  }
+
+  void setFilters(SpecialNames f) {}
+
+  @This SpecialNames withName() {
+    return this;
+  }
+
+  @This SpecialNames withName(String f) {
+    return this;
+  }
+
+  SpecialNames() {}
+
+  SpecialNames(String x) {}
+
+  static void test(SpecialNames s) {
+    // :: error: assignment
+    @CalledMethods("withOwners") SpecialNames x = s.withFilters(new SpecialNames().withName("owner"));
+  }
+
+  static void test2(SpecialNames s) {
+    s.setFilters(new SpecialNames("owner"));
+    // :: error: assignment
+    @CalledMethods("withOwners") SpecialNames x = s;
+  }
+
+  static void test3(SpecialNames s) {
+    // :: error: assignment
+    @CalledMethods("withOwners") SpecialNames x = s.withFilters(new SpecialNames().withName("owner"));
+  }
+
+  static void test4(SpecialNames s) {
+    s.setFilters(new SpecialNames("owner"));
+    // :: error: assignment
+    @CalledMethods("withOwners") SpecialNames x = s;
+  }
+
+  static void testForCrashes(SpecialNames s) {
+    s.setFilters();
+    s.withFilters();
+
+    s.setFilters(new SpecialNames().withName());
+  }
+}
diff --git a/checker/tests/calledmethods-usevaluechecker/WithOwnersFilter.java b/checker/tests/calledmethods-usevaluechecker/WithOwnersFilter.java
new file mode 100644
index 0000000..0e1b274
--- /dev/null
+++ b/checker/tests/calledmethods-usevaluechecker/WithOwnersFilter.java
@@ -0,0 +1,30 @@
+import com.amazonaws.services.ec2.AmazonEC2;
+import com.amazonaws.services.ec2.model.DescribeImagesRequest;
+import com.amazonaws.services.ec2.model.DescribeImagesResult;
+import com.amazonaws.services.ec2.model.Filter;
+
+public class WithOwnersFilter {
+  private static final String IMG_NAME = "some_linux_img";
+
+  public static void correct1(AmazonEC2 client) {
+    DescribeImagesResult result =
+        client.describeImages(
+            new DescribeImagesRequest()
+                .withFilters(new Filter("name").withValues(IMG_NAME))
+                .withFilters(new Filter("owner").withValues("my_aws_acct")));
+  }
+
+  public static void correct2(AmazonEC2 client) {
+    DescribeImagesRequest request = new DescribeImagesRequest();
+    request.withFilters(new Filter("name").withValues(IMG_NAME));
+    request.withFilters(new Filter("owner").withValues("my_aws_acct"));
+    client.describeImages(request);
+  }
+
+  public static void correct3(AmazonEC2 client) {
+    DescribeImagesRequest request = new DescribeImagesRequest();
+    request.withFilters(
+        new Filter("name").withValues(IMG_NAME), new Filter("owner").withValues("my_aws_acct"));
+    client.describeImages(request);
+  }
+}
diff --git a/checker/tests/calledmethods/CmPredicate.java b/checker/tests/calledmethods/CmPredicate.java
new file mode 100644
index 0000000..789b69b
--- /dev/null
+++ b/checker/tests/calledmethods/CmPredicate.java
@@ -0,0 +1,607 @@
+import org.checkerframework.checker.calledmethods.qual.*;
+
+public class CmPredicate {
+
+  void testOr1() {
+    MyClass m1 = new MyClass();
+
+    // :: error: method.invocation
+    m1.c();
+  }
+
+  void testOr2() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.c();
+  }
+
+  void testOr3() {
+    MyClass m1 = new MyClass();
+
+    m1.b();
+    m1.c();
+  }
+
+  void testAnd1() {
+    MyClass m1 = new MyClass();
+
+    // :: error: method.invocation
+    m1.d();
+  }
+
+  void testAnd2() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    // :: error: method.invocation
+    m1.d();
+  }
+
+  void testAnd3() {
+    MyClass m1 = new MyClass();
+
+    m1.b();
+    // :: error: method.invocation
+    m1.d();
+  }
+
+  void testAnd4() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.c();
+    // :: error: method.invocation
+    m1.d();
+  }
+
+  void testAnd5() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.b();
+    m1.d();
+  }
+
+  void testAnd6() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.b();
+    m1.c();
+    m1.d();
+  }
+
+  void testAndOr1() {
+    MyClass m1 = new MyClass();
+
+    // :: error: method.invocation
+    m1.e();
+  }
+
+  void testAndOr2() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.e();
+  }
+
+  void testAndOr3() {
+    MyClass m1 = new MyClass();
+
+    m1.b();
+    // :: error: method.invocation
+    m1.e();
+  }
+
+  void testAndOr4() {
+    MyClass m1 = new MyClass();
+
+    m1.b();
+    m1.c();
+    m1.e();
+  }
+
+  void testAndOr5() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.b();
+    m1.c();
+    m1.d();
+    m1.e();
+  }
+
+  void testPrecedence1() {
+    MyClass m1 = new MyClass();
+
+    // :: error: method.invocation
+    m1.f();
+  }
+
+  void testPrecedence2() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    // :: error: method.invocation
+    m1.f();
+  }
+
+  void testPrecedence3() {
+    MyClass m1 = new MyClass();
+
+    m1.b();
+    // :: error: method.invocation
+    m1.f();
+  }
+
+  void testPrecedence4() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.b();
+    m1.f();
+  }
+
+  void testPrecedence5() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.c();
+    m1.f();
+  }
+
+  void testPrecedence6() {
+    MyClass m1 = new MyClass();
+
+    m1.b();
+    m1.c();
+    m1.f();
+  }
+
+  private static class MyClass {
+
+    @CalledMethods("a") MyClass cmA;
+
+    @CalledMethodsPredicate("a") MyClass cmpA;
+
+    @CalledMethods({"a", "b"}) MyClass aB;
+
+    @CalledMethodsPredicate("a || b") MyClass aOrB;
+
+    @CalledMethodsPredicate("a && b") MyClass aAndB;
+
+    @CalledMethodsPredicate("a || b && c") MyClass bAndCOrA;
+
+    @CalledMethodsPredicate("a || (b && c)") MyClass bAndCOrAParens;
+
+    @CalledMethodsPredicate("a && b || c") MyClass aAndBOrC;
+
+    @CalledMethodsPredicate("(a && b) || c") MyClass aAndBOrCParens;
+
+    @CalledMethodsPredicate("(a || b) && c") MyClass aOrBAndC;
+
+    @CalledMethodsPredicate("a && (b || c)") MyClass bOrCAndA;
+
+    @CalledMethodsPredicate("b && c") MyClass bAndC;
+
+    @CalledMethodsPredicate("(b && c)") MyClass bAndCParens;
+
+    void a() {}
+
+    void b() {}
+
+    void c(@CalledMethodsPredicate("a || b") MyClass this) {}
+
+    void d(@CalledMethodsPredicate("a && b") MyClass this) {}
+
+    void e(@CalledMethodsPredicate("a || (b && c)") MyClass this) {}
+
+    void f(@CalledMethodsPredicate("a && b || c") MyClass this) {}
+
+    static void testAssignability1(@CalledMethodsPredicate("a || b") MyClass cAble) {
+      cAble.c();
+      // :: error: method.invocation
+      cAble.d();
+      // :: error: method.invocation
+      cAble.e();
+      // :: error: method.invocation
+      cAble.f();
+    }
+
+    static void testAssignability2(@CalledMethodsPredicate("a && b") MyClass dAble) {
+      // These calls would work if subtyping between predicates was by implication. They issue
+      // errors, because it is not.
+      // :: error: method.invocation
+      dAble.c();
+      dAble.d();
+      // :: error: method.invocation
+      dAble.e();
+      // :: error: method.invocation
+      dAble.f();
+    }
+
+    void testAllAssignability() {
+
+      @CalledMethods("a") MyClass cmALocal;
+      @CalledMethodsPredicate("a") MyClass cmpALocal;
+      @CalledMethodsPredicate("a || b") MyClass aOrBLocal;
+      @CalledMethods({"a", "b"}) MyClass aBLocal;
+      @CalledMethodsPredicate("a && b") MyClass aAndBLocal;
+      @CalledMethodsPredicate("a || b && c") MyClass bAndCOrALocal;
+      @CalledMethodsPredicate("a || (b && c)") MyClass bAndCOrAParensLocal;
+      @CalledMethodsPredicate("a && b || c") MyClass aAndBOrCLocal;
+      @CalledMethodsPredicate("(a && b) || c") MyClass aAndBOrCParensLocal;
+      @CalledMethodsPredicate("(a || b) && c") MyClass aOrBAndCLocal;
+      @CalledMethodsPredicate("a && (b || c)") MyClass bOrCAndALocal;
+      @CalledMethodsPredicate("b && c") MyClass bAndCLocal;
+      @CalledMethodsPredicate("(b && c)") MyClass bAndCParensLocal;
+
+      cmALocal = cmA;
+      cmALocal = cmpA;
+      // :: error: assignment
+      cmALocal = aOrB;
+      cmALocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      cmALocal = aAndB;
+      // :: error: assignment
+      cmALocal = bAndCOrA;
+      // :: error: assignment
+      cmALocal = bAndCOrAParens;
+      // :: error: assignment
+      cmALocal = aAndBOrC;
+      // :: error: assignment
+      cmALocal = aAndBOrCParens;
+      // :: error: assignment
+      cmALocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      cmALocal = bOrCAndA;
+      // :: error: assignment
+      cmALocal = bAndC;
+      // :: error: assignment
+      cmALocal = bAndCParens;
+
+      cmpALocal = cmA;
+      cmpALocal = cmpA;
+      // :: error: assignment
+      cmpALocal = aOrB;
+      cmpALocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      cmpALocal = aAndB;
+      // :: error: assignment
+      cmpALocal = bAndCOrA;
+      // :: error: assignment
+      cmpALocal = bAndCOrAParens;
+      // :: error: assignment
+      cmpALocal = aAndBOrC;
+      // :: error: assignment
+      cmpALocal = aAndBOrCParens;
+      // :: error: assignment
+      cmpALocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      cmpALocal = bOrCAndA;
+      // :: error: assignment
+      cmpALocal = bAndC;
+      // :: error: assignment
+      cmpALocal = bAndCParens;
+
+      aOrBLocal = cmA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = cmpA;
+      aOrBLocal = aOrB;
+      aOrBLocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = aAndB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = bAndCOrA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = bAndCOrAParens;
+      // :: error: assignment
+      aOrBLocal = aAndBOrC;
+      // :: error: assignment
+      aOrBLocal = aAndBOrCParens;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = bAndCParens;
+
+      // :: error: (assignment)
+      aBLocal = cmA;
+      // :: error: (assignment)
+      aBLocal = cmpA;
+      // :: error: (assignment)
+      aBLocal = aOrB;
+      aBLocal = aB;
+      aBLocal = aAndB;
+      // :: error: (assignment)
+      aBLocal = bAndCOrA;
+      // :: error: (assignment)
+      aBLocal = bAndCOrAParens;
+      // :: error: (assignment)
+      aBLocal = aAndBOrC;
+      // :: error: (assignment)
+      aBLocal = aAndBOrCParens;
+      // :: error: (assignment)
+      aBLocal = aOrBAndC;
+      // :: error: (assignment)
+      aBLocal = bOrCAndA;
+      // :: error: (assignment)
+      aBLocal = bAndC;
+      // :: error: (assignment)
+      aBLocal = bAndCParens;
+
+      // :: error: (assignment)
+      aAndBLocal = cmA;
+      // :: error: (assignment)
+      aAndBLocal = cmpA;
+      // :: error: (assignment)
+      aAndBLocal = aOrB;
+      aAndBLocal = aB;
+      aAndBLocal = aAndB;
+      // :: error: (assignment)
+      aAndBLocal = bAndCOrA;
+      // :: error: (assignment)
+      aAndBLocal = bAndCOrAParens;
+      // :: error: (assignment)
+      aAndBLocal = aAndBOrC;
+      // :: error: (assignment)
+      aAndBLocal = aAndBOrCParens;
+      // :: error: (assignment)
+      aAndBLocal = aOrBAndC;
+      // :: error: (assignment)
+      aAndBLocal = bOrCAndA;
+      // :: error: (assignment)
+      aAndBLocal = bAndC;
+      // :: error: (assignment)
+      aAndBLocal = bAndCParens;
+
+      bAndCOrALocal = cmA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = cmpA;
+      // :: error: (assignment)
+      bAndCOrALocal = aOrB;
+      bAndCOrALocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = aAndB;
+      bAndCOrALocal = bAndCOrA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = bAndCOrAParens;
+      // :: error: (assignment)
+      bAndCOrALocal = aAndBOrC;
+      // :: error: (assignment)
+      bAndCOrALocal = aAndBOrCParens;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = bAndCParens;
+
+      bAndCOrAParensLocal = cmA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = cmpA;
+      // :: error: (assignment)
+      bAndCOrAParensLocal = aOrB;
+      bAndCOrAParensLocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = aAndB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = bAndCOrA;
+      bAndCOrAParensLocal = bAndCOrAParens;
+      // :: error: (assignment)
+      bAndCOrAParensLocal = aAndBOrC;
+      // :: error: (assignment)
+      bAndCOrAParensLocal = aAndBOrCParens;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = bAndCParens;
+
+      // :: error: (assignment)
+      aAndBOrCLocal = cmA;
+      // :: error: (assignment)
+      aAndBOrCLocal = cmpA;
+      // :: error: (assignment)
+      aAndBOrCLocal = aOrB;
+      aAndBOrCLocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCLocal = aAndB;
+      // :: error: (assignment)
+      aAndBOrCLocal = bAndCOrA;
+      // :: error: (assignment)
+      aAndBOrCLocal = bAndCOrAParens;
+      aAndBOrCLocal = aAndBOrC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCLocal = aAndBOrCParens;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCLocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCLocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCLocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCLocal = bAndCParens;
+
+      // :: error: (assignment)
+      aAndBOrCParensLocal = cmA;
+      // :: error: (assignment)
+      aAndBOrCParensLocal = cmpA;
+      // :: error: (assignment)
+      aAndBOrCParensLocal = aOrB;
+      aAndBOrCParensLocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCParensLocal = aAndB;
+      // :: error: (assignment)
+      aAndBOrCParensLocal = bAndCOrA;
+      // :: error: (assignment)
+      aAndBOrCParensLocal = bAndCOrAParens;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCParensLocal = aAndBOrC;
+      aAndBOrCParensLocal = aAndBOrCParens;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCParensLocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCParensLocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCParensLocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCParensLocal = bAndCParens;
+
+      // :: error: (assignment)
+      aOrBAndCLocal = cmA;
+      // :: error: (assignment)
+      aOrBAndCLocal = cmpA;
+      // :: error: (assignment)
+      aOrBAndCLocal = aOrB;
+      // :: error: (assignment)
+      aOrBAndCLocal = aB;
+      // :: error: (assignment)
+      aOrBAndCLocal = aAndB;
+      // :: error: (assignment)
+      aOrBAndCLocal = bAndCOrA;
+      // :: error: (assignment)
+      aOrBAndCLocal = bAndCOrAParens;
+      // :: error: (assignment)
+      aOrBAndCLocal = aAndBOrC;
+      // :: error: (assignment)
+      aOrBAndCLocal = aAndBOrCParens;
+      aOrBAndCLocal = aOrBAndC;
+      // :: error: (assignment)
+      aOrBAndCLocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBAndCLocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBAndCLocal = bAndCParens;
+
+      // :: error: (assignment)
+      bOrCAndALocal = cmA;
+      // :: error: (assignment)
+      bOrCAndALocal = cmpA;
+      // :: error: (assignment)
+      bOrCAndALocal = aOrB;
+      bOrCAndALocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bOrCAndALocal = aAndB;
+      // :: error: (assignment)
+      bOrCAndALocal = bAndCOrA;
+      // :: error: (assignment)
+      bOrCAndALocal = bAndCOrAParens;
+      // :: error: (assignment)
+      bOrCAndALocal = aAndBOrC;
+      // :: error: (assignment)
+      bOrCAndALocal = aAndBOrCParens;
+      // :: error: (assignment)
+      bOrCAndALocal = aOrBAndC;
+      bOrCAndALocal = bOrCAndA;
+      // :: error: (assignment)
+      bOrCAndALocal = bAndC;
+      // :: error: (assignment)
+      bOrCAndALocal = bAndCParens;
+
+      // :: error: (assignment)
+      bAndCLocal = cmA;
+      // :: error: (assignment)
+      bAndCLocal = cmpA;
+      // :: error: (assignment)
+      bAndCLocal = aOrB;
+      // :: error: (assignment)
+      bAndCLocal = aB;
+      // :: error: (assignment)
+      bAndCLocal = aAndB;
+      // :: error: (assignment)
+      bAndCLocal = bAndCOrA;
+      // :: error: (assignment)
+      bAndCLocal = bAndCOrAParens;
+      // :: error: (assignment)
+      bAndCLocal = aAndBOrC;
+      // :: error: (assignment)
+      bAndCLocal = aAndBOrCParens;
+      // :: error: (assignment)
+      bAndCLocal = aOrBAndC;
+      // :: error: (assignment)
+      bAndCLocal = bOrCAndA;
+      bAndCLocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCLocal = bAndCParens;
+
+      // :: error: (assignment)
+      bAndCParensLocal = cmA;
+      // :: error: (assignment)
+      bAndCParensLocal = cmpA;
+      // :: error: (assignment)
+      bAndCParensLocal = aOrB;
+      // :: error: (assignment)
+      bAndCParensLocal = aB;
+      // :: error: (assignment)
+      bAndCParensLocal = aAndB;
+      // :: error: (assignment)
+      bAndCParensLocal = bAndCOrA;
+      // :: error: (assignment)
+      bAndCParensLocal = bAndCOrAParens;
+      // :: error: (assignment)
+      bAndCParensLocal = aAndBOrC;
+      // :: error: (assignment)
+      bAndCParensLocal = aAndBOrCParens;
+      // :: error: (assignment)
+      bAndCParensLocal = aOrBAndC;
+      // :: error: (assignment)
+      bAndCParensLocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCParensLocal = bAndC;
+      bAndCParensLocal = bAndCParens;
+    }
+  }
+}
diff --git a/checker/tests/calledmethods/Generics.java b/checker/tests/calledmethods/Generics.java
new file mode 100644
index 0000000..bfc75d6
--- /dev/null
+++ b/checker/tests/calledmethods/Generics.java
@@ -0,0 +1,54 @@
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+public class Generics {
+
+  static interface Symbol {
+
+    boolean isStatic();
+
+    void finalize(@CalledMethods("isStatic") Symbol this);
+  }
+
+  static List<@CalledMethods("isStatic") Symbol> makeList(Symbol s) {
+    s.isStatic();
+    ArrayList<@CalledMethods("isStatic") Symbol> l = new ArrayList<>();
+    l.add(s);
+    return l;
+  }
+
+  static void useList() {
+    Symbol s = null;
+    for (Symbol t : makeList(s)) {
+      t.finalize();
+    }
+  }
+
+  // reduced from real-world code
+  private <@CalledMethods() T extends Symbol> T getMember(Class<T> type, boolean b) {
+    if (b) {
+      T sym = getMember(type, !b);
+      if (sym != null && sym.isStatic()) {
+        return sym;
+      }
+    } else {
+      T sym = getMember(type, b);
+      if (sym != null) {
+        return sym;
+      }
+    }
+    return null;
+  }
+
+  static Stream<String> stringList() {
+    String s = "hi";
+    // dummy method call
+    s.contains("h");
+    // should infer type Stream<@CalledMethods() String>
+    return Arrays.asList(s).stream();
+  }
+}
diff --git a/checker/tests/calledmethods/Issue20.java b/checker/tests/calledmethods/Issue20.java
new file mode 100644
index 0000000..c4b0ac2
--- /dev/null
+++ b/checker/tests/calledmethods/Issue20.java
@@ -0,0 +1,36 @@
+// test case for issue 20: https://github.com/kelloggm/object-construction-checker/issues/20
+
+import java.util.Map;
+
+public class Issue20<E> {
+
+  private boolean enableProtoAnnotations;
+
+  @SuppressWarnings({"unchecked"})
+  private <T, O extends Message, E extends ProtoElement> T getProtoExtension(
+      E element, GeneratedExtension<O, T> extension) {
+    // Use this method as the chokepoint for all field annotations processing, so we can
+    // toggle on/off annotations processing in one place.
+    if (!enableProtoAnnotations) {
+      return null;
+    }
+    return (T) element.getOptionFields().get(extension.getDescriptor());
+  }
+
+  // stubs of relevant classes
+  private class Message {}
+
+  private class ProtoElement {
+    public Map<FieldDescriptor, Object> getOptionFields() {
+      return null;
+    }
+  }
+
+  private class FieldDescriptor {}
+
+  private class GeneratedExtension<O, T> {
+    public FieldDescriptor getDescriptor() {
+      return null;
+    }
+  }
+}
diff --git a/checker/tests/calledmethods/Not.java b/checker/tests/calledmethods/Not.java
new file mode 100644
index 0000000..c363bad
--- /dev/null
+++ b/checker/tests/calledmethods/Not.java
@@ -0,0 +1,75 @@
+import org.checkerframework.checker.calledmethods.qual.*;
+
+public class Not {
+
+  class Foo {
+    void a() {}
+
+    void b() {}
+
+    void c() {}
+
+    void notA(@CalledMethodsPredicate("!a") Foo this) {}
+
+    void notB(@CalledMethodsPredicate("!b") Foo this) {}
+  }
+
+  void test1(Foo f) {
+    f.notA();
+    f.notB();
+  }
+
+  void test2(Foo f) {
+    f.c();
+    f.notA();
+    f.notB();
+  }
+
+  void test3(Foo f) {
+    f.a();
+    // :: error: method.invocation
+    f.notA();
+    f.notB();
+  }
+
+  void test4(Foo f) {
+    f.b();
+    f.notA();
+    // :: error: method.invocation
+    f.notB();
+  }
+
+  void test5(Foo f) {
+    f.a();
+    f.b();
+    // :: error: method.invocation
+    f.notA();
+    // :: error: method.invocation
+    f.notB();
+  }
+
+  void callA(Foo f) {
+    f.a();
+  }
+
+  void test6(Foo f) {
+    callA(f);
+    // DEMONSTRATION OF UNSOUNDNESS
+    f.notA();
+  }
+
+  void test7(@CalledMethods("a") Foo f) {
+    // :: error: method.invocation
+    f.notA();
+  }
+
+  void test8(Foo f, boolean test) {
+    if (test) {
+      f.a();
+    } else {
+      f.b();
+    }
+    // DEMONSTRATION OF UNSOUNDNESS
+    f.notA();
+  }
+}
diff --git a/checker/tests/calledmethods/Parens.java b/checker/tests/calledmethods/Parens.java
new file mode 100644
index 0000000..c8f4329
--- /dev/null
+++ b/checker/tests/calledmethods/Parens.java
@@ -0,0 +1,5 @@
+public class Parens {
+  public synchronized void incrementPushed(long[] pushed, int operationType) {
+    // ++(pushed[operationType]);
+  }
+}
diff --git a/checker/tests/calledmethods/Postconditions.java b/checker/tests/calledmethods/Postconditions.java
new file mode 100644
index 0000000..41a185e
--- /dev/null
+++ b/checker/tests/calledmethods/Postconditions.java
@@ -0,0 +1,99 @@
+import org.checkerframework.checker.calledmethods.qual.*;
+
+/** Test for postcondition support via @EnsureCalledMethods. */
+public class Postconditions {
+  void build(@CalledMethods({"a", "b", "c"}) Postconditions this) {}
+
+  void a() {}
+
+  void b() {}
+
+  void c() {}
+
+  @EnsuresCalledMethods(value = "#1", methods = "b")
+  static void callB(Postconditions x) {
+    x.b();
+  }
+
+  @EnsuresCalledMethods(value = "#1", methods = "b")
+  // :: error: contracts.postcondition
+  static void doesNotCallB(Postconditions x) {}
+
+  @EnsuresCalledMethods(
+      value = "#1",
+      methods = {"b", "c"})
+  static void callBAndC(Postconditions x) {
+    x.b();
+    x.c();
+  }
+
+  static void allInOneMethod() {
+    Postconditions y = new Postconditions();
+    y.a();
+    y.b();
+    y.c();
+    y.build();
+  }
+
+  static void invokeCallB() {
+    Postconditions y = new Postconditions();
+    y.a();
+    callB(y);
+    y.c();
+    y.build();
+  }
+
+  static void invokeCallBLast() {
+    Postconditions y = new Postconditions();
+    y.a();
+    y.c();
+    callB(y);
+    y.build();
+  }
+
+  static void invokeCallBAndC() {
+    Postconditions y = new Postconditions();
+    y.a();
+    callBAndC(y);
+    y.build();
+  }
+
+  static void invokeCallBAndCWrong() {
+    Postconditions y = new Postconditions();
+    callBAndC(y);
+    // :: error: finalizer.invocation
+    y.build();
+  }
+
+  @EnsuresCalledMethodsIf(
+      expression = "#1",
+      methods = {"a", "b", "c"},
+      result = true)
+  static boolean ensuresABCIfTrue(Postconditions p, boolean b) {
+    if (b) {
+      p.a();
+      p.b();
+      p.c();
+      return true;
+    }
+    return false;
+  }
+
+  static void testEnsuresCalledMethodsIf(Postconditions p, boolean b) {
+    if (ensuresABCIfTrue(p, b)) {
+      p.build();
+    } else {
+      // :: error: finalizer.invocation
+      p.build();
+    }
+  }
+
+  @EnsuresCalledMethods(value = "#1", methods = "a")
+  static void callWithException(Postconditions p) {
+    try {
+      p.a();
+      throw new java.io.IOException();
+    } catch (java.io.IOException e) {
+    }
+  }
+}
diff --git a/checker/tests/calledmethods/SimpleFluentInference.java b/checker/tests/calledmethods/SimpleFluentInference.java
new file mode 100644
index 0000000..8d42ed2
--- /dev/null
+++ b/checker/tests/calledmethods/SimpleFluentInference.java
@@ -0,0 +1,63 @@
+import org.checkerframework.checker.calledmethods.qual.*;
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+/* Simple inference of a fluent builder */
+public class SimpleFluentInference {
+  SimpleFluentInference build(@CalledMethods({"a", "b"}) SimpleFluentInference this) {
+    return this;
+  }
+
+  SimpleFluentInference weakbuild(@CalledMethods({"a"}) SimpleFluentInference this) {
+    return this;
+  }
+
+  @This SimpleFluentInference a() {
+    return this;
+  }
+
+  @This SimpleFluentInference b() {
+    return this;
+  }
+
+  // intentionally does not have an @This annotation
+  SimpleFluentInference c() {
+    return new SimpleFluentInference();
+  }
+
+  static void doStuffCorrect() {
+    SimpleFluentInference s = new SimpleFluentInference().a().b().build();
+  }
+
+  static void doStuffWrong() {
+    SimpleFluentInference s =
+        new SimpleFluentInference()
+            .a()
+            // :: error: finalizer.invocation
+            .build();
+  }
+
+  static void doStuffRightWeak() {
+    SimpleFluentInference s = new SimpleFluentInference().a().weakbuild();
+  }
+
+  static void noReturnsReceiverAnno() {
+    SimpleFluentInference s =
+        new SimpleFluentInference()
+            .a()
+            .b()
+            .c()
+            // :: error: finalizer.invocation
+            .build();
+  }
+
+  static void fluentLoop() {
+    SimpleFluentInference s = new SimpleFluentInference().a();
+    int i = 10;
+    while (i > 0) {
+      // :: error: finalizer.invocation
+      s.b().build();
+      i--;
+      s = new SimpleFluentInference();
+    }
+  }
+}
diff --git a/checker/tests/calledmethods/SimpleInference.java b/checker/tests/calledmethods/SimpleInference.java
new file mode 100644
index 0000000..70fa1c8
--- /dev/null
+++ b/checker/tests/calledmethods/SimpleInference.java
@@ -0,0 +1,20 @@
+import org.checkerframework.checker.calledmethods.qual.*;
+
+/* The simplest inference test case Martin could think of */
+public class SimpleInference {
+  void build(@CalledMethods({"a"}) SimpleInference this) {}
+
+  void a() {}
+
+  static void doStuffCorrect() {
+    SimpleInference s = new SimpleInference();
+    s.a();
+    s.build();
+  }
+
+  static void doStuffWrong() {
+    SimpleInference s = new SimpleInference();
+    // :: error: finalizer.invocation
+    s.build();
+  }
+}
diff --git a/checker/tests/calledmethods/SimpleInferenceMerge.java b/checker/tests/calledmethods/SimpleInferenceMerge.java
new file mode 100644
index 0000000..d0974d8
--- /dev/null
+++ b/checker/tests/calledmethods/SimpleInferenceMerge.java
@@ -0,0 +1,38 @@
+import org.checkerframework.checker.calledmethods.qual.*;
+
+/* The simplest inference test case Martin could think of */
+public class SimpleInferenceMerge {
+  void build(@CalledMethods({"a", "b"}) SimpleInferenceMerge this) {}
+
+  void a() {}
+
+  void b() {}
+
+  void c() {}
+
+  static void doStuffCorrectMerge(boolean b) {
+    SimpleInferenceMerge s = new SimpleInferenceMerge();
+    if (b) {
+      s.a();
+      s.b();
+    } else {
+      s.b();
+      s.a();
+      s.c();
+    }
+    s.build();
+  }
+
+  static void doStuffWrongMerge(boolean b) {
+    SimpleInferenceMerge s = new SimpleInferenceMerge();
+    if (b) {
+      s.a();
+      s.b();
+    } else {
+      s.b();
+      s.c();
+    }
+    // :: error: finalizer.invocation
+    s.build();
+  }
+}
diff --git a/checker/tests/calledmethods/Subtyping.java b/checker/tests/calledmethods/Subtyping.java
new file mode 100644
index 0000000..3934fe6
--- /dev/null
+++ b/checker/tests/calledmethods/Subtyping.java
@@ -0,0 +1,74 @@
+import org.checkerframework.checker.calledmethods.qual.*;
+
+// basic subtyping checks
+public class Subtyping {
+  @CalledMethods({}) Object top_top(@CalledMethods({}) Object o) {
+    return o;
+  }
+
+  @CalledMethods({}) Object top_a(@CalledMethods({"a"}) Object o) {
+    return o;
+  }
+
+  @CalledMethods({}) Object top_ab(@CalledMethods({"a", "b"}) Object o) {
+    return o;
+  }
+
+  @CalledMethods({}) Object top_bot(@CalledMethodsBottom Object o) {
+    return o;
+  }
+
+  @CalledMethods({"a"}) Object a_top(@CalledMethods({}) Object o) {
+    // :: error: return
+    return o;
+  }
+
+  @CalledMethods({"a"}) Object a_a(@CalledMethods({"a"}) Object o) {
+    return o;
+  }
+
+  @CalledMethods({"a"}) Object a_ab(@CalledMethods({"a", "b"}) Object o) {
+    return o;
+  }
+
+  @CalledMethods({"a"}) Object a_bot(@CalledMethodsBottom Object o) {
+    return o;
+  }
+
+  @CalledMethods({"a", "b"}) Object ab_top(@CalledMethods({}) Object o) {
+    // :: error: return
+    return o;
+  }
+
+  @CalledMethods({"a", "b"}) Object ab_a(@CalledMethods({"a"}) Object o) {
+    // :: error: return
+    return o;
+  }
+
+  @CalledMethods({"a", "b"}) Object ab_ab(@CalledMethods({"a", "b"}) Object o) {
+    return o;
+  }
+
+  @CalledMethods({"a", "b"}) Object ab_bot(@CalledMethodsBottom Object o) {
+    return o;
+  }
+
+  @CalledMethodsBottom Object bot_top(@CalledMethods({}) Object o) {
+    // :: error: return
+    return o;
+  }
+
+  @CalledMethodsBottom Object bot_a(@CalledMethods({"a"}) Object o) {
+    // :: error: return
+    return o;
+  }
+
+  @CalledMethodsBottom Object bot_ab(@CalledMethods({"a", "b"}) Object o) {
+    // :: error: return
+    return o;
+  }
+
+  @CalledMethodsBottom Object bot_bot(@CalledMethodsBottom Object o) {
+    return o;
+  }
+}
diff --git a/checker/tests/calledmethods/UnparsablePredicate.java b/checker/tests/calledmethods/UnparsablePredicate.java
new file mode 100644
index 0000000..dcfa3fb
--- /dev/null
+++ b/checker/tests/calledmethods/UnparsablePredicate.java
@@ -0,0 +1,50 @@
+import org.checkerframework.checker.calledmethods.qual.*;
+
+public class UnparsablePredicate {
+
+  // :: error: predicate
+  void unclosedOpen(@CalledMethodsPredicate("(foo && bar") Object x) {}
+
+  // :: error: predicate
+  void unopenedClose(@CalledMethodsPredicate("foo || bar)") Object x) {}
+
+  // :: error: predicate
+  void badKeywords1(@CalledMethodsPredicate("foo OR bar") Object x) {}
+
+  // :: error: predicate
+  void badKeywords2(@CalledMethodsPredicate("foo AND bar") Object x) {}
+
+  // These tests check that valid java identifiers don't cause problems
+  // when evaluating predicates. Examples of identifiers from
+  // https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8
+
+  void jls0Example(@CalledMethodsPredicate("String") Object x) {}
+
+  void callJls0Example(@CalledMethods("String") Object y) {
+    jls0Example(y);
+  }
+
+  void jls1Example(@CalledMethodsPredicate("i3") Object x) {}
+
+  void callJls1Example(@CalledMethods("i3") Object y) {
+    jls1Example(y);
+  }
+
+  // TODO: support Unicode. SPEL, which we use to parse expressions, doesn't.
+  /*   void jls2Example(@CalledMethodsPredicate("αρετη") Object x) { }
+  void callJls2Example(@CalledMethods("αρετη") Object y) {
+      jls2Example(y);
+  }*/
+
+  void jls3Example(@CalledMethodsPredicate("MAX_VALUE") Object x) {}
+
+  void callJls3Example(@CalledMethods("MAX_VALUE") Object y) {
+    jls3Example(y);
+  }
+
+  void jls4Example(@CalledMethodsPredicate("isLetterOrDigit") Object x) {}
+
+  void callJls4Example(@CalledMethods("isLetterOrDigit") Object y) {
+    jls4Example(y);
+  }
+}
diff --git a/checker/tests/calledmethods/Xor.java b/checker/tests/calledmethods/Xor.java
new file mode 100644
index 0000000..8a52976
--- /dev/null
+++ b/checker/tests/calledmethods/Xor.java
@@ -0,0 +1,65 @@
+import org.checkerframework.checker.calledmethods.qual.*;
+
+public class Xor {
+
+  class Foo {
+    void a() {}
+
+    void b() {}
+
+    void c() {}
+    // SPEL doesn't support XOR directly (it represents exponentiation as ^ instead),
+    // so use a standard gate encoding
+    void aXorB(@CalledMethodsPredicate("(a || b) && !(a && b)") Foo this) {}
+  }
+
+  void test1(Foo f) {
+    // :: error: method.invocation
+    f.aXorB();
+  }
+
+  void test2(Foo f) {
+    f.c();
+    // :: error: method.invocation
+    f.aXorB();
+  }
+
+  void test3(Foo f) {
+    f.a();
+    f.aXorB();
+  }
+
+  void test4(Foo f) {
+    f.b();
+    f.aXorB();
+  }
+
+  void test5(Foo f) {
+    f.a();
+    f.b();
+    // :: error: method.invocation
+    f.aXorB();
+  }
+
+  void callA(Foo f) {
+    f.a();
+  }
+
+  void test6(Foo f) {
+    callA(f);
+    f.b();
+    // DEMONSTRATION OF UNSOUNDNESS
+    f.aXorB();
+  }
+
+  void test7(@CalledMethods("a") Foo f) {
+    f.aXorB();
+  }
+
+  void test8(Foo f) {
+    callA(f);
+    // THIS IS AN UNAVOIDABLE FALSE POSITIVE
+    // :: error: method.invocation
+    f.aXorB();
+  }
+}
diff --git a/checker/tests/command-line/Makefile b/checker/tests/command-line/Makefile
new file mode 100644
index 0000000..46228eb
--- /dev/null
+++ b/checker/tests/command-line/Makefile
@@ -0,0 +1,11 @@
+# All targets
+.PHONY: all skipped issue618
+
+# Tests that are currently passing
+all: issue618
+
+# Tests that are currently not passing
+skipped:
+
+issue618:
+	make -C issue618
diff --git a/checker/tests/command-line/README b/checker/tests/command-line/README
new file mode 100644
index 0000000..3fbb3d7
--- /dev/null
+++ b/checker/tests/command-line/README
@@ -0,0 +1,3 @@
+This directory contains tests that do not fit into our standard testing
+framework and must be run from the command line rather than by extending
+CheckerFrameworkTest.
diff --git a/checker/tests/command-line/issue618/Makefile b/checker/tests/command-line/issue618/Makefile
new file mode 100644
index 0000000..ff7f6c1
--- /dev/null
+++ b/checker/tests/command-line/issue618/Makefile
@@ -0,0 +1,5 @@
+.PHONY: all
+
+all:
+	$(JAVAC) -processor regex,org.checkerframework.checker.tainting.TaintingChecker TwoCheckers.java > out.txt 2>&1 || true
+	diff -u expected.txt out.txt
diff --git a/checker/tests/command-line/issue618/TwoCheckers.java b/checker/tests/command-line/issue618/TwoCheckers.java
new file mode 100644
index 0000000..79f4b08
--- /dev/null
+++ b/checker/tests/command-line/issue618/TwoCheckers.java
@@ -0,0 +1,32 @@
+// Expected error appears:
+// $ch/bin/javac -processor org.checkerframework.checker.tainting.TaintingChecker
+// TwoCheckers.java
+// $ch/bin/javac -processor org.checkerframework.checker.tainting.TaintingChecker,regex
+// TwoCheckers.java
+
+// Expected error is suppressed:
+// $ch/bin/javac -processor regex,org.checkerframework.checker.tainting.TaintingChecker
+// TwoCheckers.java
+
+// Compare these two executions:
+// $ch/bin/javac -processor org.checkerframework.checker.tainting.TaintingChecker,regex
+// TwoCheckers.java -AprintAllQualifiers -Ashowchecks > out-good.txt
+// $ch/bin/javac -processor regex,org.checkerframework.checker.tainting.TaintingChecker
+// TwoCheckers.java -AprintAllQualifiers -Ashowchecks > out-bad.txt
+
+// Turning off caches has no effect:
+// $ch/bin/javac -processor regex,org.checkerframework.checker.tainting.TaintingChecker
+// TwoCheckers.java -AprintAllQualifiers -Ashowchecks -AatfDoNotCache AAatfDoNotReadCache >
+// out-bad-nocache.txt
+
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class TwoCheckers {
+
+  void client(String a) {
+    // :: error: (argument)
+    requiresUntainted(a);
+  }
+
+  void requiresUntainted(@Untainted String b) {}
+}
diff --git a/checker/tests/command-line/issue618/expected.txt b/checker/tests/command-line/issue618/expected.txt
new file mode 100644
index 0000000..76ee06a
--- /dev/null
+++ b/checker/tests/command-line/issue618/expected.txt
@@ -0,0 +1,6 @@
+TwoCheckers.java:28: error: [argument] incompatible argument for parameter b of requiresUntainted.
+    requiresUntainted(a);
+                      ^
+  found   : @Tainted String
+  required: @Untainted String
+1 error
diff --git a/checker/tests/compilermsg/Basic.java b/checker/tests/compilermsg/Basic.java
new file mode 100644
index 0000000..e7362b9
--- /dev/null
+++ b/checker/tests/compilermsg/Basic.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+
+public class Basic {
+
+  void required(@CompilerMessageKey String in) {}
+
+  void test() {
+    required("test.property");
+  }
+}
diff --git a/checker/tests/compilermsg/Diamonds.java b/checker/tests/compilermsg/Diamonds.java
new file mode 100644
index 0000000..9da14e4
--- /dev/null
+++ b/checker/tests/compilermsg/Diamonds.java
@@ -0,0 +1,18 @@
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import org.checkerframework.checker.compilermsgs.qual.UnknownCompilerMessageKey;
+
+public class Diamonds {
+
+  void method(List<String> arrays) {
+    arrays = new ArrayList<>(new HashSet<>(arrays));
+    arrays = newArrayList(new HashSet<>(arrays));
+    arrays = new ArrayList<>(new HashSet<@UnknownCompilerMessageKey String>(arrays));
+  }
+
+  <F> ArrayList<F> newArrayList(Collection<? extends F> param) {
+    return new ArrayList<>(param);
+  }
+}
diff --git a/checker/tests/compilermsg/compiler.properties b/checker/tests/compilermsg/compiler.properties
new file mode 100644
index 0000000..4fde3f8
--- /dev/null
+++ b/checker/tests/compilermsg/compiler.properties
@@ -0,0 +1 @@
+test.property=value
diff --git a/checker/tests/fenum/CastsFenum.java b/checker/tests/fenum/CastsFenum.java
new file mode 100644
index 0000000..f76adff
--- /dev/null
+++ b/checker/tests/fenum/CastsFenum.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.fenum.qual.Fenum;
+
+public class CastsFenum {
+  @Fenum("A") Object fa;
+
+  void m(Object p, @Fenum("A") Object a) {
+    fa = (Object) a;
+    // :: error: (assignment)
+    fa = (Object) p;
+
+    // TODO: How can we test the behavior for
+    // instanceof? It should be the same as for casts.
+    // if (p instanceof Object) {
+  }
+}
diff --git a/checker/tests/fenum/CatchFenumUnqualified.java b/checker/tests/fenum/CatchFenumUnqualified.java
new file mode 100644
index 0000000..d447aba
--- /dev/null
+++ b/checker/tests/fenum/CatchFenumUnqualified.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.fenum.qual.Fenum;
+
+public class CatchFenumUnqualified {
+  void method() {
+    try {
+    } catch (
+        // :: error: (exception.parameter)
+        @Fenum("A") RuntimeException e) {
+
+    }
+    try {
+      // :: error: (exception.parameter)
+    } catch (@Fenum("A") NullPointerException | @Fenum("A") ArrayIndexOutOfBoundsException e) {
+
+    }
+  }
+}
diff --git a/checker/tests/fenum/TestFlow.java b/checker/tests/fenum/TestFlow.java
new file mode 100644
index 0000000..317e60f
--- /dev/null
+++ b/checker/tests/fenum/TestFlow.java
@@ -0,0 +1,40 @@
+import org.checkerframework.checker.fenum.qual.Fenum;
+
+@SuppressWarnings("fenum:assignment")
+public class TestFlow {
+  public final @Fenum("A") Object ACONST1 = new Object();
+
+  public final @Fenum("B") Object BCONST1 = new Object();
+}
+
+class FenumUser {
+
+  @Fenum("A") Object state1 = new TestFlow().ACONST1;
+
+  void foo(TestFlow t) {
+    // :: error: (method.invocation)
+    state1.hashCode();
+    state1 = null;
+    state1.hashCode();
+    m();
+    // :: error: (method.invocation)
+    state1.hashCode();
+
+    Object o = new Object();
+    o = t.ACONST1;
+    methodA(o);
+    // :: error: (argument)
+    methodB(o);
+
+    o = t.BCONST1;
+    // :: error: (argument)
+    methodA(o);
+    methodB(o);
+  }
+
+  void m() {}
+
+  void methodA(@Fenum("A") Object a) {}
+
+  void methodB(@Fenum("B") Object a) {}
+}
diff --git a/checker/tests/fenum/TestInstance.java b/checker/tests/fenum/TestInstance.java
new file mode 100644
index 0000000..e820767
--- /dev/null
+++ b/checker/tests/fenum/TestInstance.java
@@ -0,0 +1,44 @@
+import org.checkerframework.checker.fenum.qual.Fenum;
+
+@SuppressWarnings("fenum:assignment")
+public class TestInstance {
+  public final @Fenum("A") Object ACONST1 = new Object();
+  public final @Fenum("A") Object ACONST2 = new Object();
+  public final @Fenum("A") Object ACONST3 = new Object();
+
+  public final @Fenum("B") Object BCONST1 = new Object();
+  public final @Fenum("B") Object BCONST2 = new Object();
+  public final @Fenum("B") Object BCONST3 = new Object();
+}
+
+class FenumUserTestInstance {
+  @Fenum("A") Object state1 = new TestInstance().ACONST1;
+
+  // :: error: (assignment)
+  @Fenum("B") Object state2 = new TestInstance().ACONST1;
+
+  void foo(TestInstance t) {
+    // :: error: (assignment)
+    state1 = new Object();
+
+    state1 = t.ACONST2;
+    state1 = t.ACONST3;
+
+    // :: error: (assignment)
+    state1 = t.BCONST1;
+
+    // :: error: (method.invocation)
+    state1.hashCode();
+    // :: error: (method.invocation)
+    t.ACONST1.hashCode();
+
+    // sanity check: unqualified instantiation and call work.
+    Object o = new Object();
+    o.hashCode();
+
+    if (t.ACONST1 == t.ACONST2) {}
+
+    // :: error: (binary)
+    if (t.ACONST1 == t.BCONST2) {}
+  }
+}
diff --git a/checker/tests/fenum/TestPrimitive.java b/checker/tests/fenum/TestPrimitive.java
new file mode 100644
index 0000000..2667d40
--- /dev/null
+++ b/checker/tests/fenum/TestPrimitive.java
@@ -0,0 +1,46 @@
+import org.checkerframework.checker.fenum.qual.Fenum;
+
+@SuppressWarnings("fenum:assignment")
+public class TestPrimitive {
+  public final @Fenum("A") int ACONST1 = 1;
+  public final @Fenum("A") int ACONST2 = 2;
+  public final @Fenum("A") int ACONST3 = 3;
+
+  public final @Fenum("B") int BCONST1 = 4;
+  public final @Fenum("B") int BCONST2 = 5;
+  public final @Fenum("B") int BCONST3 = 6;
+}
+
+class FenumUserTestPrimitive {
+  @Fenum("A") int state1 = new TestPrimitive().ACONST1;
+
+  @Fenum("A") int state3 = this.state1;
+
+  // :: error: (assignment)
+  @Fenum("B") int state2 = new TestPrimitive().ACONST1;
+
+  void foo(TestPrimitive t) {
+    // :: error: (assignment)
+    state1 = 4;
+
+    state1 = t.ACONST2;
+    state1 = t.ACONST3;
+
+    // :: error: (assignment)
+    state1 = t.BCONST1;
+
+    if (t.ACONST1 < t.ACONST2) {
+      // ok
+    }
+
+    // :: error: (binary)
+    if (t.ACONST1 < t.BCONST2) {}
+    // :: error: (binary)
+    if (t.ACONST1 == t.BCONST2) {}
+
+    // :: error: (binary)
+    if (t.ACONST1 < 5) {}
+    // :: error: (binary)
+    if (t.ACONST1 == 5) {}
+  }
+}
diff --git a/checker/tests/fenum/TestStatic.java b/checker/tests/fenum/TestStatic.java
new file mode 100644
index 0000000..5e21ccf
--- /dev/null
+++ b/checker/tests/fenum/TestStatic.java
@@ -0,0 +1,53 @@
+import org.checkerframework.checker.fenum.qual.Fenum;
+
+@SuppressWarnings("fenum:assignment")
+public class TestStatic {
+  public static final @Fenum("A") int ACONST1 = 1;
+  public static final @Fenum("A") int ACONST2 = 2;
+  public static final @Fenum("A") int ACONST3 = 3;
+
+  public static final @Fenum("B") int BCONST1 = 4;
+  public static final @Fenum("B") int BCONST2 = 5;
+  public static final @Fenum("B") int BCONST3 = 6;
+}
+
+class FenumUserTestStatic {
+  @Fenum("A") int state1 = TestStatic.ACONST1;
+
+  // :: error: (assignment)
+  @Fenum("B") int state2 = TestStatic.ACONST1;
+
+  void bar(@Fenum("A") int p) {}
+
+  void foo() {
+    // :: error: (assignment)
+    state1 = 4;
+
+    state1 = TestStatic.ACONST2;
+    state1 = TestStatic.ACONST3;
+
+    state2 = TestStatic.BCONST3;
+
+    // :: error: (assignment)
+    state1 = TestStatic.BCONST1;
+
+    // :: error: (argument)
+    bar(5);
+    bar(TestStatic.ACONST1);
+    // :: error: (argument)
+    bar(TestStatic.BCONST1);
+  }
+
+  @SuppressWarnings("fenum")
+  void ignoreAll() {
+    state1 = 4;
+    bar(5);
+  }
+
+  @SuppressWarnings("fenum:assignment")
+  void ignoreOne() {
+    state1 = 4;
+    // :: error: (argument)
+    bar(5);
+  }
+}
diff --git a/checker/tests/fenum/TestSwitch.java b/checker/tests/fenum/TestSwitch.java
new file mode 100644
index 0000000..0431bba
--- /dev/null
+++ b/checker/tests/fenum/TestSwitch.java
@@ -0,0 +1,38 @@
+import org.checkerframework.checker.fenum.qual.Fenum;
+
+public class TestSwitch {
+  void m() {
+    @SuppressWarnings("fenum:assignment")
+    @Fenum("TEST") final int annotated = 3;
+
+    @SuppressWarnings("fenum:assignment")
+    @Fenum("TEST") final int annotated2 = 6;
+
+    int plain = 9; // FenumUnqualified
+
+    switch (plain) {
+        // :: error: (type.incompatible)
+      case annotated:
+      default:
+    }
+
+    // un-annotated still working
+    switch (plain) {
+      case 1:
+      case 2:
+      default:
+    }
+
+    switch (annotated) {
+        // :: error: (type.incompatible)
+      case 45:
+      default:
+    }
+
+    // annotated working
+    switch (annotated) {
+      case annotated2:
+      default:
+    }
+  }
+}
diff --git a/checker/tests/fenum/TypeVariable.java b/checker/tests/fenum/TypeVariable.java
new file mode 100644
index 0000000..f112283
--- /dev/null
+++ b/checker/tests/fenum/TypeVariable.java
@@ -0,0 +1,12 @@
+/*
+ * Make sure that unqualified type variables still work.
+ */
+public class TypeVariable<X> {
+  X m() {
+    return null;
+  }
+
+  <Y extends Object> Y bar() {
+    return null;
+  }
+}
diff --git a/checker/tests/fenum/UpperBoundsInByteCode.java b/checker/tests/fenum/UpperBoundsInByteCode.java
new file mode 100644
index 0000000..a053bba
--- /dev/null
+++ b/checker/tests/fenum/UpperBoundsInByteCode.java
@@ -0,0 +1,25 @@
+import org.checkerframework.checker.fenum.qual.Fenum;
+import org.checkerframework.framework.testchecker.lib.UncheckedByteCode;
+
+public class UpperBoundsInByteCode {
+  UncheckedByteCode<@Fenum("Foo") String> foo;
+  UncheckedByteCode<@Fenum("Bar") Object> bar;
+
+  void typeVarWithNonObjectUpperBound(@Fenum("A") int a) {
+    // :: error: (type.argument)
+    UncheckedByteCode.methodWithTypeVarBoundedByNumber(a);
+    UncheckedByteCode.methodWithTypeVarBoundedByNumber(1);
+  }
+
+  void wildcardsInByteCode() {
+    UncheckedByteCode.unboundedWildcardParam(foo);
+    UncheckedByteCode.lowerboundedWildcardParam(bar);
+    // :: error: (argument)
+    UncheckedByteCode.upperboundedWildcardParam(foo);
+  }
+
+  // :: error: (type.argument)
+  SourceCode<@Fenum("Foo") String> foo2;
+
+  class SourceCode<T extends Object> {}
+}
diff --git a/checker/tests/fenumswing/FlowBreak.java b/checker/tests/fenumswing/FlowBreak.java
new file mode 100644
index 0000000..3c6e67b
--- /dev/null
+++ b/checker/tests/fenumswing/FlowBreak.java
@@ -0,0 +1,38 @@
+import org.checkerframework.checker.fenum.qual.SwingHorizontalOrientation;
+import org.checkerframework.checker.fenum.qual.SwingVerticalOrientation;
+
+public class FlowBreak {
+  static @SwingHorizontalOrientation Object CENTER;
+  static @SwingHorizontalOrientation Object LEFT;
+
+  boolean flag;
+
+  @SwingHorizontalOrientation Object testInference() {
+    Object o;
+    // initially o is @FenumTop
+    o = null;
+    // o is @Bottom
+    while (flag) {
+      if (flag) {
+        o = CENTER;
+        // o is @SwingHorizontalOrientation
+      } else {
+        o = new @SwingVerticalOrientation Object();
+        // o is @SwingVerticalOrientation
+        break;
+      }
+      // We can only come here from the then-branch, the else-branch is dead.
+      // Therefore, we only take the annotations at the end of the then-branch and ignore the
+      // results of the else-branch.
+      // Therefore, o is @SwingHorizontalOrientation and the following is valid:
+      @SwingHorizontalOrientation Object pla = o;
+    }
+    // Here we have to merge three paths:
+    // 1. The entry to the loop, if the condition is false [@Bottom]
+    // 2. The normal end of the loop body [@SwingHorizontalOrientation]
+    // 3. The path from the break to here [@SwingVerticalOrientation]
+    // Currently, the third path is ignored and we do not get this error message.
+    // :: error: (return)
+    return o;
+  }
+}
diff --git a/checker/tests/fenumswing/IdentityArrayListTest.java b/checker/tests/fenumswing/IdentityArrayListTest.java
new file mode 100644
index 0000000..226bb09
--- /dev/null
+++ b/checker/tests/fenumswing/IdentityArrayListTest.java
@@ -0,0 +1,36 @@
+import java.util.Arrays;
+import org.checkerframework.checker.fenum.qual.FenumTop;
+
+/*
+ * This test case violates an assertion in the compiler.
+ * It does not depend on the Fenum Checker, it breaks for any checker.
+ */
+public class IdentityArrayListTest {
+  // The type of the third argument to Arrays.copyOf should be:
+  // Class<? extends T @FenumTop []>
+  // But the annotated JDK does not have annotations for the Fenum Checker.
+  @SuppressWarnings("argument")
+  public <T> T[] toArray(T[] a) {
+    // Warnings only with -Alint=cast:strict.
+    // TODO:: warning: (cast.unsafe)
+    // :: warning: [unchecked] unchecked cast
+    return (T[]) Arrays.copyOf(null, 0, a.getClass());
+  }
+
+  public <T> T[] toArray2(T[] a) {
+    wc(null, 0, new java.util.LinkedList<T[]>());
+    // TODO:: warning: (cast.unsafe)
+    // :: warning: [unchecked] unchecked cast
+    return (T[]) myCopyOf(null, 0, a.getClass());
+  }
+
+  public static <T, U> T[] myCopyOf(
+      U[] original, int newLength, Class<? extends T @FenumTop []> newType) {
+    return null;
+  }
+
+  public static <T, U> T[] wc(
+      U[] original, int newLength, java.util.List<? extends T @FenumTop []> arr) {
+    return null;
+  }
+}
diff --git a/checker/tests/fenumswing/PolyTest.java b/checker/tests/fenumswing/PolyTest.java
new file mode 100644
index 0000000..e0b1b32
--- /dev/null
+++ b/checker/tests/fenumswing/PolyTest.java
@@ -0,0 +1,25 @@
+import org.checkerframework.checker.fenum.qual.FenumBottom;
+import org.checkerframework.checker.fenum.qual.PolyFenum;
+import org.checkerframework.checker.fenum.qual.SwingCompassDirection;
+
+public class PolyTest {
+  public static boolean flag = false;
+
+  @PolyFenum String merge(
+      @PolyFenum String a,
+      @PolyFenum String b,
+      @SwingCompassDirection String x,
+      @FenumBottom String bot) {
+    // Test lub with poly and a qualifier that isn't top or bottom.
+    String y = flag ? a : x;
+    // :: error: (assignment)
+    @PolyFenum String y2 = flag ? a : x;
+
+    // Test lub with poly and bottom.
+    // Test lub with poly and bottom.
+    @PolyFenum String z = flag ? a : bot;
+
+    // Test lub with two polys
+    return flag ? a : b;
+  }
+}
diff --git a/checker/tests/fenumswing/SwingTest.java b/checker/tests/fenumswing/SwingTest.java
new file mode 100644
index 0000000..94fb270
--- /dev/null
+++ b/checker/tests/fenumswing/SwingTest.java
@@ -0,0 +1,267 @@
+import org.checkerframework.checker.fenum.qual.SwingBoxOrientation;
+import org.checkerframework.checker.fenum.qual.SwingCompassDirection;
+import org.checkerframework.checker.fenum.qual.SwingHorizontalOrientation;
+import org.checkerframework.checker.fenum.qual.SwingVerticalOrientation;
+
+public class SwingTest {
+
+  static @SwingVerticalOrientation int BOTTOM;
+  static @SwingCompassDirection int NORTH;
+
+  static @SwingHorizontalOrientation int CENTER;
+  static @SwingHorizontalOrientation int LEFT;
+
+  static void m(@SwingVerticalOrientation int box) {}
+
+  public static void main(String[] args) {
+    // ok
+    m(BOTTOM);
+
+    // :: error: (argument)
+    m(5);
+
+    // :: error: (argument)
+    m(NORTH);
+  }
+
+  @SuppressWarnings("swingverticalorientation")
+  static void ignoreAll() {
+    m(NORTH);
+
+    @SwingVerticalOrientation int b = 5;
+  }
+
+  @SuppressWarnings("fenum:argument")
+  static void ignoreOne() {
+    m(NORTH);
+
+    // :: error: (assignment)
+    @SwingVerticalOrientation int b = 5;
+  }
+
+  void testNull() {
+    // This enum should only be used on ints, but I wanted to
+    // test how an Object enum and null interact.
+    @SwingVerticalOrientation Object box = null;
+  }
+
+  @SwingVerticalOrientation Object testNullb() {
+    return null;
+  }
+
+  @SwingVerticalOrientation Object testNullc() {
+    Object o = null;
+    return o;
+  }
+
+  @SwingVerticalOrientation int testInference0() {
+    // :: error: (assignment)
+    @SwingVerticalOrientation int boxint = 5;
+    int box = boxint;
+    return box;
+  }
+
+  Object testInference1() {
+    Object o = new String();
+    return o;
+  }
+
+  @SwingVerticalOrientation int testInference2() {
+    int i = BOTTOM;
+    return i;
+  }
+
+  @SwingVerticalOrientation Object testInference3() {
+    // :: error: (assignment)
+    @SwingVerticalOrientation Object boxobj = new Object();
+    Object obox = boxobj;
+    return obox;
+  }
+
+  int testInference4() {
+    int aint = 5;
+    return aint;
+  }
+
+  Object testInference5() {
+    Object o = null;
+    if (5 == 4) {
+      o = new Object();
+    }
+    return o;
+  }
+
+  Object testInference5b() {
+    Object o = null;
+    if (5 == 4) {
+      o = new Object();
+    } else {
+    }
+    // the empty else branch actually covers a different code path!
+    return o;
+  }
+
+  @SwingHorizontalOrientation int testInference5c() {
+    int o;
+    if (5 == 4) {
+      o = CENTER;
+    } else {
+      o = LEFT;
+    }
+    return o;
+  }
+
+  int testInference6() {
+    int last = 0;
+    last += 1;
+    return last;
+  }
+
+  @SwingBoxOrientation Object testInference7() {
+    Object o = new @SwingVerticalOrientation Object();
+    if (5 == 4) {
+      o = new @SwingHorizontalOrientation Object();
+    } else {
+      //   o = new @SwingVerticalOrientation Object();
+    }
+    return o;
+  }
+
+  @SwingBoxOrientation Object testInference7b() {
+    Object o;
+    if (5 == 4) {
+      o = new @SwingHorizontalOrientation Object();
+    } else {
+      o = new @SwingVerticalOrientation Object();
+    }
+    return o;
+  }
+
+  @SwingBoxOrientation Object testInference7c() {
+    Object o = null;
+    if (5 == 4) {
+      o = new @SwingHorizontalOrientation Object();
+    } else {
+      o = new @SwingVerticalOrientation Object();
+    }
+    return o;
+  }
+
+  int s1 = 0;
+  int c = 3;
+
+  void testInference8() {
+    int s2 = s1;
+    s1 = (s2 &= c);
+  }
+
+  void testInference8b() {
+    // :: error: (assignment)
+    @SwingHorizontalOrientation int s2 = 5;
+    // :: error: (compound.assignment)
+    s2 += 1;
+
+    // :: error: (assignment)
+    s1 = (s2 += s2);
+
+    // :: error: (assignment)
+    @SwingHorizontalOrientation String str = "abc";
+    // yes, somebody in the Swing API really wrote this.
+    str += null;
+  }
+
+  boolean flag;
+
+  Object testInference9() {
+    Object o = null;
+    while (flag) {
+      if (5 == 4) {
+        o = new @SwingHorizontalOrientation Object();
+      } else {
+        o = new @SwingVerticalOrientation Object();
+        // note that this break makes a difference!
+        break;
+      }
+    }
+    // :: error: (return)
+    return o;
+  }
+
+  @SwingBoxOrientation Object testInference9b() {
+    Object o = null;
+    while (flag) {
+      if (5 == 4) {
+        o = new @SwingHorizontalOrientation Object();
+      } else {
+        o = new @SwingVerticalOrientation Object();
+        // note that this break makes a difference!
+        break;
+      }
+    }
+    return o;
+  }
+
+  @SwingHorizontalOrientation Object testInference9c() {
+    Object o = null;
+    while (flag) {
+      if (5 == 4) {
+        o = new @SwingHorizontalOrientation Object();
+      } else {
+        o = new @SwingVerticalOrientation Object();
+        // note that this break makes a difference!
+        break;
+      }
+    }
+    // :: error: (return)
+    return o;
+  }
+
+  @SwingVerticalOrientation Object testInference9d() {
+    Object o = null;
+    while (flag) {
+      if (5 == 4) {
+        o = new @SwingHorizontalOrientation Object();
+      } else {
+        o = new @SwingVerticalOrientation Object();
+        // note that this break makes a difference!
+        break;
+      }
+    }
+    // :: error: (return)
+    return o;
+  }
+
+  @SwingBoxOrientation Object testInference9e() {
+    Object o = null;
+    while (flag) {
+      if (5 == 4) {
+        o = new @SwingHorizontalOrientation Object();
+      } else {
+        o = new @SwingVerticalOrientation Object();
+      }
+    }
+    return o;
+  }
+
+  /* TODO: Flow inference does not handle dead branches correctly.
+   * The return statement is only reachable with i being unqualified.
+   * However, the else-branch does not initialize the variable, leaving it
+   * at the @FenumTop initial value.
+  int testInferenceThrow() {
+      int i;
+      if ( 5==4 ) {
+        i = 5;
+      } else {
+        throw new RuntimeException("bla");
+      }
+      return i;
+  }
+  */
+
+  @SwingVerticalOrientation Object testDefaulting0() {
+    @org.checkerframework.framework.qual.DefaultQualifier(SwingVerticalOrientation.class)
+    // :: error: (assignment)
+    Object o = new String();
+    return o;
+  }
+}
diff --git a/checker/tests/fenumswing/TypeVariable.java b/checker/tests/fenumswing/TypeVariable.java
new file mode 100644
index 0000000..f112283
--- /dev/null
+++ b/checker/tests/fenumswing/TypeVariable.java
@@ -0,0 +1,12 @@
+/*
+ * Make sure that unqualified type variables still work.
+ */
+public class TypeVariable<X> {
+  X m() {
+    return null;
+  }
+
+  <Y extends Object> Y bar() {
+    return null;
+  }
+}
diff --git a/checker/tests/formatter-lubglb/Placeholder.java b/checker/tests/formatter-lubglb/Placeholder.java
new file mode 100644
index 0000000..5ee2ef0
--- /dev/null
+++ b/checker/tests/formatter-lubglb/Placeholder.java
@@ -0,0 +1,3 @@
+// We need a file to start the checker.
+
+public class Placeholder {}
diff --git a/checker/tests/formatter-unchecked-defaults/TestUncheckedByteCode.java b/checker/tests/formatter-unchecked-defaults/TestUncheckedByteCode.java
new file mode 100644
index 0000000..1b84161
--- /dev/null
+++ b/checker/tests/formatter-unchecked-defaults/TestUncheckedByteCode.java
@@ -0,0 +1,18 @@
+import org.checkerframework.framework.testchecker.lib.UncheckedByteCode;
+
+public class TestUncheckedByteCode {
+  Object field;
+
+  void test(UncheckedByteCode<Object> param, Integer i) {
+    field = param.getCT();
+    field = param.getInt(1);
+    field = param.getInteger(i);
+    field = param.identity("hello");
+
+    // String and Object are relevant types and must be annotated in bytecode
+    // :: error: (argument)
+    field = param.getObject(new Object());
+    // :: error: (argument)
+    field = param.getString("hello");
+  }
+}
diff --git a/checker/tests/formatter/ConversionBasic.java b/checker/tests/formatter/ConversionBasic.java
new file mode 100644
index 0000000..a84b297
--- /dev/null
+++ b/checker/tests/formatter/ConversionBasic.java
@@ -0,0 +1,76 @@
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Formatter;
+import org.checkerframework.checker.formatter.qual.ConversionCategory;
+import org.checkerframework.checker.formatter.qual.Format;
+
+public class ConversionBasic {
+  public static void main(String... p) {
+    Formatter f = new Formatter();
+
+    // test GENERAL, there is nothing we can do wrong
+    @Format({ConversionCategory.GENERAL}) String s = "%s";
+    f.format("Suc-%s-ful", "cess");
+    f.format("%b", 4);
+    f.format("%B", 7.5);
+    f.format("%h", new Date());
+    f.format("%H", Integer.valueOf(4));
+    f.format("%s", new ArrayList<Integer>());
+
+    // test CHAR
+    @Format({ConversionCategory.CHAR}) String c = "%c";
+    f.format("%c", 'c');
+    f.format("%c", (byte) 67);
+    f.format("%c", (int) 67);
+    f.format("%c", Character.valueOf('c'));
+    f.format("%c", Byte.valueOf((byte) 67));
+    f.format("%c", Short.valueOf((short) 67));
+    f.format("%C", Integer.valueOf(67));
+    // :: error: (argument)
+    f.format("%c", 7.5);
+    // :: error: (argument)
+    f.format("%C", "Value");
+
+    // test INT
+    @Format({ConversionCategory.INT}) String i = "%d";
+    f.format("%d", (byte) 67);
+    f.format("%o", (short) 67);
+    f.format("%x", (int) 67);
+    f.format("%X", (long) 67);
+    f.format("%d", Long.valueOf(67));
+    f.format("%d", BigInteger.ONE);
+    // :: error: (argument)
+    f.format("%d", 'c');
+    // :: error: (argument)
+    f.format("%d", BigDecimal.ONE);
+
+    // test FLOAT
+    @Format({ConversionCategory.FLOAT}) String d = "%f";
+    f.format("%e", (float) 67.1);
+    f.format("%E", (double) 67.3);
+    f.format("%f", Float.valueOf(42.5f));
+    f.format("%g", Double.valueOf(42.5));
+    f.format("%G", 67.87);
+    f.format("%a", BigDecimal.ONE);
+    // :: error: (argument)
+    f.format("%A", 1325);
+    // :: error: (argument)
+    f.format("%a", BigInteger.ONE);
+
+    // test TIME
+    @Format({ConversionCategory.TIME}) String t = "%tT";
+    f.format("%tD", new Date());
+    f.format("%TM", (long) 32165456);
+    f.format("%TD", Calendar.getInstance());
+    // :: error: (argument)
+    f.format("%tD", 1321543512);
+    // :: error: (argument)
+    f.format("%tD", new Object());
+
+    System.out.println(f.toString());
+    f.close();
+  }
+}
diff --git a/checker/tests/formatter/ConversionNull.java b/checker/tests/formatter/ConversionNull.java
new file mode 100644
index 0000000..2fe0bdc
--- /dev/null
+++ b/checker/tests/formatter/ConversionNull.java
@@ -0,0 +1,30 @@
+import java.util.Date;
+import java.util.Formatter;
+
+public class ConversionNull {
+  public static void main(String... p) {
+    Formatter f = new Formatter();
+
+    f.format("%d %s", 0, null);
+    f.format("%s", (Object) null);
+
+    f.format("%d %c", 0, null);
+    f.format("%c", (Character) null);
+    f.format("%c", (Object) null);
+
+    f.format("%d %d", 0, null);
+    f.format("%d", (Integer) null);
+    f.format("%d", (Object) null);
+
+    f.format("%d %f", 0, null);
+    f.format("%f", (Float) null);
+    f.format("%f", (Object) null);
+
+    f.format("%d %tD", 0, null);
+    f.format("%tD", (Date) null);
+    f.format("%tD", (Object) null);
+
+    System.out.println(f.toString());
+    f.close();
+  }
+}
diff --git a/checker/tests/formatter/ConversionNull2.java b/checker/tests/formatter/ConversionNull2.java
new file mode 100644
index 0000000..99d4ebf
--- /dev/null
+++ b/checker/tests/formatter/ConversionNull2.java
@@ -0,0 +1,16 @@
+import java.util.Formatter;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+
+public class ConversionNull2 {
+  void foo(Formatter f1, MyFormatter f2) {
+    f1.format("%d %c", 0, null);
+    f2.format("%d %c", 0, null);
+  }
+}
+
+class MyFormatter {
+  @FormatMethod
+  public MyFormatter format(String format, Object... args) {
+    return null;
+  }
+}
diff --git a/checker/tests/formatter/FlowFormatter.java b/checker/tests/formatter/FlowFormatter.java
new file mode 100644
index 0000000..479b0b1
--- /dev/null
+++ b/checker/tests/formatter/FlowFormatter.java
@@ -0,0 +1,83 @@
+import java.util.Date;
+import java.util.Formatter;
+import org.checkerframework.checker.formatter.qual.ConversionCategory;
+import org.checkerframework.checker.formatter.qual.Format;
+import org.checkerframework.checker.formatter.util.FormatUtil;
+import org.junit.Assert;
+
+public class FlowFormatter {
+  public static String callUnqual(String u) {
+    return u;
+  }
+
+  public static void main(String... p) {
+    Formatter f = new Formatter();
+
+    String unqual = System.lineSeparator();
+    String qual = "%s %d %f";
+    String wrong = "%$s";
+    callUnqual("%s");
+    callUnqual(qual);
+    callUnqual(wrong);
+    callUnqual(null);
+    // :: error: (format.string)
+    f.format(null);
+    @Format({ConversionCategory.GENERAL}) String nullAssign = null;
+    // :: error: (format.string)
+    f.format(nullAssign, "string");
+    if (false) {
+      nullAssign = "%s";
+    }
+    f.format(nullAssign, "string");
+    // :: error: (assignment)
+    @Format({ConversionCategory.GENERAL}) String err0 = unqual;
+    // :: error: (assignment)
+    @Format({ConversionCategory.GENERAL}) String err2 = "%$s";
+    @Format({ConversionCategory.GENERAL}) String ok = "%s";
+
+    String u = "%s" + " %" + "d";
+    String v = FormatUtil.asFormat(u, ConversionCategory.GENERAL, ConversionCategory.INT);
+    f.format(u, "String", 1337);
+    // :: error: (argument)
+    f.format(u, "String", 7.4);
+
+    try {
+      String l = FormatUtil.asFormat(u, ConversionCategory.FLOAT, ConversionCategory.INT);
+      Assert.fail("Expected Exception");
+    } catch (Error e) {
+    }
+
+    String a = "Success: %s %d %f";
+    f.format(a, "String", 1337, 7.5);
+
+    String b = "Fail: %d";
+    // :: error: (argument)
+    f.format(b, "Wrong");
+
+    @Format({
+      ConversionCategory.GENERAL,
+      ConversionCategory.INT,
+      ConversionCategory.FLOAT,
+      ConversionCategory.CHAR
+    })
+    String s = "Success: %s %d %f %c";
+    f.format(s, "OK", 42, 3.14, 'c');
+
+    @Format({ConversionCategory.GENERAL, ConversionCategory.INT, ConversionCategory.FLOAT}) String t = "Fail: %s %d %f";
+    // :: error: (argument)
+    f.format(t, "OK", "Wrong", 3.14);
+
+    call(f, "Success: %tM");
+    // :: error: (argument)
+    call(f, "Fail: %d");
+
+    System.out.println(f.toString());
+    f.close();
+  }
+
+  public static void call(Formatter f, @Format({ConversionCategory.TIME}) String s) {
+    f.format(s, new Date());
+    // :: error: (argument)
+    f.format(s, "Wrong");
+  }
+}
diff --git a/checker/tests/formatter/FormatBasic.java b/checker/tests/formatter/FormatBasic.java
new file mode 100644
index 0000000..ccc961d
--- /dev/null
+++ b/checker/tests/formatter/FormatBasic.java
@@ -0,0 +1,36 @@
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Formatter;
+import java.util.GregorianCalendar;
+
+public class FormatBasic {
+  public static void main(String... p) {
+    Formatter f = new Formatter();
+
+    f.format("String");
+    f.format("String %20% %n");
+    f.format("%% %s", "str");
+    f.format("%4$2s %3$2s %2$2s %1$2s", "a", "b", "c", "d");
+    f.format("e = %+10.4f", Math.E);
+    f.format("Amount gained or lost since last statement: $ %(,.2f", -6217.58);
+    f.format("Local time: %tT", Calendar.getInstance());
+    f.format("Unable to open file '%1$s': %2$s", "food", "No such file or directory");
+    f.format("Duke's Birthday: %1$tm %1$te,%1$tY", new GregorianCalendar(1995, Calendar.MAY, 23));
+    f.format("Duke's Birthday: %tm %<te,%<tY", new Date());
+    f.format("Duke's Birthday: %2$tm %<te,%<tY (it's the %dth)", 123, new Date());
+
+    String s = "%+s%";
+    // :: error: (format.string)
+    f.format(s, "illegal");
+    // :: error: (format.string)
+    f.format("%+s%", "illegal");
+    // :: error: (format.string)
+    f.format("Wrong < indexing: %1$tm %<te,%<$tY", new Date());
+    // :: error: (format.string)
+    f.format("%t", new Date());
+    // :: error: (argument)
+    f.format("%Td", (int) 231);
+
+    f.close();
+  }
+}
diff --git a/checker/tests/formatter/FormatIndexing.java b/checker/tests/formatter/FormatIndexing.java
new file mode 100644
index 0000000..96c8553
--- /dev/null
+++ b/checker/tests/formatter/FormatIndexing.java
@@ -0,0 +1,72 @@
+import java.util.Date;
+import java.util.Formatter;
+import org.checkerframework.checker.formatter.qual.ConversionCategory;
+import org.checkerframework.checker.formatter.qual.Format;
+
+public class FormatIndexing {
+  public static void main(String... p) {
+    Formatter f = new Formatter();
+
+    // GENERAL and self intersections
+    @Format({ConversionCategory.CHAR}) String ccc = "%1$c %<c %c";
+    @Format({ConversionCategory.CHAR}) String c = "%c %<s";
+    @Format({ConversionCategory.INT}) String i = "%d %<s";
+    @Format({ConversionCategory.FLOAT}) String d = "%f %<s";
+    @Format({ConversionCategory.TIME}) String t = "%TT %<s";
+
+    // test CHAR_AND_INT
+    @Format({ConversionCategory.CHAR_AND_INT}) String ici = "%1$d %1$c";
+    f.format("%1$c %1$d", (int) 42);
+    f.format("%1$c %1$d %1$d", (int) 42);
+    // :: error: (argument)
+    f.format("%1$c %1$d", (long) 42);
+    // :: error: (argument)
+    f.format("%1$c %1$d", 'c');
+
+    // test INT_AND_TIME
+    @Format({ConversionCategory.INT_AND_TIME}) String iit = "%1$tT %1$d";
+    f.format("%1$tT %1$d", (long) 42);
+    f.format("%1$d %1$tT %1$d", (long) 42);
+    // :: error: (argument)
+    f.format("%1$tT %1$d", (int) 42);
+    // :: error: (argument)
+    f.format("%1$tT %1$d", new Date());
+
+    // test NULL
+    @Format({ConversionCategory.NULL}) String cf = "%c %<f";
+    @Format({ConversionCategory.NULL}) String ct = "%c %<TT";
+    @Format({ConversionCategory.NULL}) String _if = "%d %<f";
+    @Format({ConversionCategory.NULL}) String fc = "%f %<c";
+    @Format({ConversionCategory.NULL}) String fi = "%f %<d";
+    @Format({ConversionCategory.NULL}) String ft = "%f %<TT";
+    @Format({ConversionCategory.NULL}) String tc = "%TT %<c";
+    @Format({ConversionCategory.NULL, ConversionCategory.INT}) String tf = "%TT %<f %2$d";
+    @Format({ConversionCategory.NULL}) String icf = "%d %<c %<f";
+    @Format({ConversionCategory.NULL}) String itc = "%d %<TT %<c";
+    // :: warning: (format.specifier.null)
+    f.format(tf, null, 0);
+    // :: warning: (format.specifier.null)
+    f.format(tf, (Object[]) null);
+    // :: error: (format.specifier.null)
+    f.format(tf, 'c', 0);
+    // :: warning: (format.specifier.null)
+    f.format(tf, (Object) null, 0);
+
+    // test UNUSED
+    // :: warning: (format.argument.unused)
+    f.format("%1$s %3$s", "Hello", "Missing", "World");
+    // :: warning: (format.argument.unused) :: warning: (format.indirect.arguments)
+    f.format("%1$s %3$s", new Object[5]);
+    // :: warning: (format.argument.unused)
+    f.format("%5$s", (Object[]) null);
+    // :: warning: (format.argument.unused)
+    f.format("%3$s", null, null, null);
+    // :: warning: (format.argument.unused)
+    f.format("%7$s %2$s %4$s %<s %7$s", 0, 0, 0, 0, 0, 0, 0);
+    f.close();
+
+    // test UNUSED and NULL
+    // :: warning: (format.argument.unused) :: error: (format.specifier.null)
+    f.format("%1$s %3$d %3$f", "Hello", "Missing", "World");
+  }
+}
diff --git a/checker/tests/formatter/FormatMethodAndFormat.java b/checker/tests/formatter/FormatMethodAndFormat.java
new file mode 100644
index 0000000..21b8668
--- /dev/null
+++ b/checker/tests/formatter/FormatMethodAndFormat.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.formatter.qual.ConversionCategory;
+import org.checkerframework.checker.formatter.qual.Format;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+
+public class FormatMethodAndFormat {
+
+  // This method contains both @FormatMethod and @Format.  Both constrain the arguments.
+  @FormatMethod
+  void log(@Format(ConversionCategory.INT) String format, Object... args) {
+    System.out.printf(format, args);
+  }
+
+  void client() {
+    // Without the @Format annotation (with just the @FormatMethod anno), this would be legal.
+    // :: error: (argument)
+    log("%s %s", "hello", "goodbye");
+  }
+}
diff --git a/checker/tests/formatter/FormatMethodAnnotation.java b/checker/tests/formatter/FormatMethodAnnotation.java
new file mode 100644
index 0000000..b8fcbe8
--- /dev/null
+++ b/checker/tests/formatter/FormatMethodAnnotation.java
@@ -0,0 +1,48 @@
+// Test case for Issue 1507:
+// https://github.com/typetools/checker-framework/issues/1507
+
+import java.io.PrintStream;
+import java.util.Locale;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+
+public class FormatMethodAnnotation {
+
+  public void example() {
+    String ex1 = String.format(Locale.ENGLISH, "%s %d", "cost", 12);
+    log("%d", 0);
+    log2(Locale.ENGLISH, "%d", 0);
+  }
+
+  @FormatMethod
+  static void log(String format, Object... args) {
+    String ex1 = String.format(format, args);
+    String ex2 = String.format(Locale.ENGLISH, format, args);
+  }
+
+  @FormatMethod
+  static void log2(Locale locale, String format, Object... args) {
+    String ex1 = String.format(format, args);
+    String ex2 = String.format(locale, format, args);
+    String ex3 = String.format(Locale.FRENCH, format, args);
+  }
+
+  PrintStream logfile;
+  boolean enabled;
+  String indent_str;
+
+  @FormatMethod
+  void log3(String format, Object... args) {
+    if (enabled) {
+      logfile.print(indent_str);
+      logfile.printf(format, args);
+    }
+  }
+
+  @com.google.errorprone.annotations.FormatMethod
+  void log4(String format, Object... args) {
+    if (enabled) {
+      logfile.print(indent_str);
+      logfile.printf(format, args);
+    }
+  }
+}
diff --git a/checker/tests/formatter/FormatMethodInvocation.java b/checker/tests/formatter/FormatMethodInvocation.java
new file mode 100644
index 0000000..0ed0095
--- /dev/null
+++ b/checker/tests/formatter/FormatMethodInvocation.java
@@ -0,0 +1,73 @@
+import java.io.ByteArrayOutputStream;
+import java.io.Console;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.Formatter;
+import java.util.Locale;
+
+public class FormatMethodInvocation {
+  public static void main() {
+    Formatter f = new Formatter();
+    f.format("%d", 1337);
+    f.format(Locale.GERMAN, "%d", 1337);
+    // :: error: (argument)
+    f.format("%f", 1337);
+    // :: error: (argument)
+    f.format(Locale.GERMAN, "%f", 1337);
+    f.close();
+
+    String.format("%d", 1337);
+    String.format(Locale.GERMAN, "%d", 1337);
+    // :: error: (argument)
+    String.format("%f", 1337);
+    // :: error: (argument)
+    String.format(Locale.GERMAN, "%f", 1337);
+
+    PrintWriter pw = new PrintWriter(new ByteArrayOutputStream());
+    pw.format("%d", 1337);
+    pw.format(Locale.GERMAN, "%d", 1337);
+    pw.printf("%d", 1337);
+    pw.printf(Locale.GERMAN, "%d", 1337);
+    // :: error: (argument)
+    pw.format("%f", 1337);
+    // :: error: (argument)
+    pw.format(Locale.GERMAN, "%f", 1337);
+    // :: error: (argument)
+    pw.printf("%f", 1337);
+    // :: error: (argument)
+    pw.printf(Locale.GERMAN, "%f", 1337);
+    pw.close();
+
+    PrintStream ps = System.out;
+    ps.format("%d", 1337);
+    ps.format(Locale.GERMAN, "%d", 1337);
+    ps.printf("%d", 1337);
+    ps.printf(Locale.GERMAN, "%d", 1337);
+    // :: error: (argument)
+    ps.format("%f", 1337);
+    // :: error: (argument)
+    ps.format(Locale.GERMAN, "%f", 1337);
+    // :: error: (argument)
+    ps.printf("%f", 1337);
+    // :: error: (argument)
+    ps.printf(Locale.GERMAN, "%f", 1337);
+    ps.close();
+
+    Console c = System.console();
+    c.format("%d", 1337);
+    c.printf("%d", 1337);
+    // :: error: (argument)
+    c.format("%f", 1337);
+    // :: error: (argument)
+    c.printf("%f", 1337);
+
+    myFormatMethod("%d", 1337);
+    // :: error: (argument)
+    myFormatMethod("%f", 1337);
+  }
+
+  @com.google.errorprone.annotations.FormatMethod
+  static void myFormatMethod(String fmt, Object... args) {
+    System.out.printf(fmt, args);
+  }
+}
diff --git a/checker/tests/formatter/FormatNullArray.java b/checker/tests/formatter/FormatNullArray.java
new file mode 100644
index 0000000..ea535f3
--- /dev/null
+++ b/checker/tests/formatter/FormatNullArray.java
@@ -0,0 +1,11 @@
+public class FormatNullArray {
+
+  public static void main(String[] args) {
+
+    // All 4 lines are legal and produce the same output: "null null".
+    System.out.printf("%s %s%n", null, null);
+    System.out.printf("%d %d%n", null, null);
+    System.out.printf("%s %s%n", (Object[]) null);
+    System.out.printf("%d %d%n", (Object[]) null);
+  }
+}
diff --git a/checker/tests/formatter/FormatNullCategory.java b/checker/tests/formatter/FormatNullCategory.java
new file mode 100644
index 0000000..d6c35a1
--- /dev/null
+++ b/checker/tests/formatter/FormatNullCategory.java
@@ -0,0 +1,26 @@
+import static org.checkerframework.checker.formatter.qual.ConversionCategory.NULL;
+
+import org.checkerframework.checker.formatter.qual.ConversionCategory;
+import org.checkerframework.checker.formatter.qual.Format;
+
+public class FormatNullCategory {
+
+  static @Format(NULL) String n1 = "%1$f %1$c %n";
+
+  static @Format(ConversionCategory.NULL) String n2 = "%1$f %1$c %n";
+
+  public static void main(String[] args) {
+
+    System.out.printf("%d %n", (Object) null);
+
+    // Warning if call is legal, error if it is not.
+    // :: warning: (format.specifier.null)
+    System.out.printf(n1, (Object) null);
+    // :: error: (format.specifier.null)
+    System.out.printf(n1, 3);
+    // :: warning: (format.specifier.null)
+    System.out.printf(n2, (Object) null);
+    // :: error: (format.specifier.null)
+    System.out.printf(n2, 3);
+  }
+}
diff --git a/checker/tests/formatter/InvalidFormatMethod.java b/checker/tests/formatter/InvalidFormatMethod.java
new file mode 100644
index 0000000..6d7e2f6
--- /dev/null
+++ b/checker/tests/formatter/InvalidFormatMethod.java
@@ -0,0 +1,23 @@
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+
+public class InvalidFormatMethod {
+
+  @FormatMethod
+  void m1(String s, Object... args) {}
+
+  @FormatMethod
+  void m2(int x, double y, boolean z, String s, Object... args) {}
+
+  @FormatMethod
+  // :: error: (format.method)
+  void m3(int x, Object... args) {}
+
+  void client(Object... args) {
+    m1("hello");
+    m1("hello %s", "goodbye");
+    m2(22, 3.14, true, "hello");
+    m2(22, 3.14, true, "hello %s", "goodbye");
+    // :: error: (format.method)
+    m3(22, "goodbye");
+  }
+}
diff --git a/checker/tests/formatter/Issue285.java b/checker/tests/formatter/Issue285.java
new file mode 100644
index 0000000..74faad6
--- /dev/null
+++ b/checker/tests/formatter/Issue285.java
@@ -0,0 +1,7 @@
+// Test case for Issue 285:
+// https://github.com/typetools/checker-framework/issues/285
+public class Issue285 {
+  void f() {
+    for (String s : new String[] {"s"}) {}
+  }
+}
diff --git a/checker/tests/formatter/ManualExampleFormatter.java b/checker/tests/formatter/ManualExampleFormatter.java
new file mode 100644
index 0000000..b723e67
--- /dev/null
+++ b/checker/tests/formatter/ManualExampleFormatter.java
@@ -0,0 +1,36 @@
+// Example from the manual
+
+import static org.checkerframework.checker.formatter.qual.ConversionCategory.FLOAT;
+import static org.checkerframework.checker.formatter.qual.ConversionCategory.INT;
+
+import org.checkerframework.checker.formatter.qual.Format;
+
+public class ManualExampleFormatter {
+
+  void m(boolean flag) {
+
+    @Format({FLOAT, INT}) String f;
+
+    f = "%f %d"; // OK
+    f = "%s %d"; // OK, %s is weaker than %f
+    // :: warning: (format.missing.arguments)
+    f = "%f"; // warning: last argument is ignored
+    // :: warning: (format.missing.arguments)
+    f = flag ? "%f %d" : "%f";
+
+    if (flag) {
+      f = "%f %d";
+    } else {
+      // :: warning: (format.missing.arguments)
+      f = "%f";
+    }
+    @Format({FLOAT, INT}) String f2 = f;
+
+    // :: error: (assignment)
+    f = "%f %d %s"; // error: too many arguments
+    // :: error: (assignment)
+    f = "%d %d"; // error: %d is not weaker than %f
+
+    String.format(f, 0.8, 42);
+  }
+}
diff --git a/checker/tests/formatter/VarargsFormatter.java b/checker/tests/formatter/VarargsFormatter.java
new file mode 100644
index 0000000..f451aea
--- /dev/null
+++ b/checker/tests/formatter/VarargsFormatter.java
@@ -0,0 +1,59 @@
+import java.util.ArrayList;
+import java.util.Formatter;
+
+public class VarargsFormatter {
+  public static void main(String... p) {
+    Formatter f = new Formatter();
+
+    // vararg as parameter
+    // :: warning: non-varargs call of varargs method with inexact argument type for last parameter;
+    f.format("Nothing", null); // equivalent to (Object[])null
+    f.format("Nothing", (Object[]) null);
+    f.format("%s", (Object[]) null);
+    f.format("%s %d %x", (Object[]) null);
+    // :: warning: non-varargs call of varargs method with inexact argument type for last
+    // parameter;
+    f.format("%s %d %x", null); // equivalent to (Object[])null
+    // :: warning: (format.indirect.arguments)
+    f.format("%d", new Object[1]);
+    // :: warning: (format.indirect.arguments)
+    f.format("%s", new Object[2]);
+    // :: warning: (format.indirect.arguments)
+    f.format("%s %s", new Object[0]);
+    // :: warning: (format.indirect.arguments)
+    f.format("Empty", new Object[0]);
+    // :: warning: (format.indirect.arguments)
+    f.format("Empty", new Object[5]);
+    f.format("%s", new ArrayList<Object>());
+    f.format("%d %s", 132, new Object[2]);
+    f.format("%s %d", new Object[2], 123);
+    // :: error: (format.missing.arguments)
+    f.format("%d %s %s", 132, new Object[2]);
+    // :: error: (argument)
+    f.format("%d %d", new Object[2], 123);
+    // "error: (format.specifier.null)" could be a warning rather than an error, but that would
+    // require reasoning about the values in an array construction expression.
+    // :: error: (format.specifier.null) :: warning: (format.indirect.arguments)
+    f.format("%d %<f", new Object[1]);
+
+    // too many arguments
+    // :: warning: (format.excess.arguments)
+    f.format("", 213);
+    // :: warning: (format.excess.arguments)
+    f.format("%d", 232, 132);
+    // :: warning: (format.excess.arguments)
+    f.format("%s", "a", "b");
+    // :: warning: (format.excess.arguments)
+    f.format("%d %s", 123, "a", 123);
+
+    // too few arguments
+    // :: error: (format.missing.arguments)
+    f.format("%s");
+    // :: error: (format.missing.arguments)
+    f.format("%d %s", 545);
+    // :: error: (format.missing.arguments)
+    f.format("%s %c %c", 'c', 'c');
+
+    f.close();
+  }
+}
diff --git a/checker/tests/guieffect/AnonInnerDefaults.java b/checker/tests/guieffect/AnonInnerDefaults.java
new file mode 100644
index 0000000..1322112
--- /dev/null
+++ b/checker/tests/guieffect/AnonInnerDefaults.java
@@ -0,0 +1,231 @@
+import java.util.Random;
+import org.checkerframework.checker.guieffect.qual.PolyUI;
+import org.checkerframework.checker.guieffect.qual.PolyUIEffect;
+import org.checkerframework.checker.guieffect.qual.PolyUIType;
+import org.checkerframework.checker.guieffect.qual.SafeEffect;
+import org.checkerframework.checker.guieffect.qual.UI;
+import org.checkerframework.checker.guieffect.qual.UIEffect;
+import org.checkerframework.checker.guieffect.qual.UIType;
+
+public class AnonInnerDefaults {
+
+  private static Random random;
+
+  private static boolean maybe() {
+    return random.nextBoolean();
+  }
+
+  public static interface SafeIface {
+    public void doStuff();
+  }
+
+  public static interface ExplicitUIIface {
+    @UIEffect
+    public void doStuff();
+  }
+
+  @UIType
+  public static interface UITypeIface {
+    public void doStuff();
+  }
+
+  @PolyUIType
+  public static interface PolyIface {
+    @PolyUIEffect
+    public void doStuff();
+  }
+
+  @PolyUIType
+  public static interface ParlyPolyIface {
+    @PolyUIEffect
+    public void doPolyUIStuff();
+
+    public void doSafeStuff();
+  }
+
+  public static interface IndirectPolyIface extends PolyIface {}
+
+  @PolyUIType
+  public static interface IPolyIfaceCaller {
+
+    @PolyUIEffect
+    public void call(final @PolyUI PolyIface p);
+  }
+
+  public PolyIface getSafePolyIface(final UIElement e) {
+    // :: error: (return)
+    return new PolyIface() { // Anonymous inner class inference for @UI
+      @Override
+      public void doStuff() {
+        // Safe due to anonymous inner class effect inference
+        e.dangerous(); // should be okay
+      }
+    };
+  }
+
+  public @UI PolyIface getUIPolyIface(final UIElement e) {
+    return new PolyIface() { // Anonymous inner class inference for @UI
+      @Override
+      public void doStuff() {
+        // Safe due to anonymous inner class effect inference
+        e.dangerous(); // should be okay
+      }
+    };
+  }
+
+  public void callSafePolyIface(final PolyIface p) {
+    p.doStuff();
+  }
+
+  @UIEffect
+  public void callUIPolyIface(final @UI PolyIface p) {
+    p.doStuff();
+  }
+
+  @UIEffect
+  public void tryStuff(final UIElement e) {
+    SafeIface s =
+        new SafeIface() {
+          @Override
+          public void doStuff() {
+            // :: error: (call.ui)
+            e.dangerous();
+          }
+        };
+    ExplicitUIIface ex =
+        new ExplicitUIIface() {
+          @Override
+          public void doStuff() {
+            e.dangerous(); // should be okay
+          }
+        };
+    UITypeIface u =
+        new UITypeIface() {
+          @Override
+          public void doStuff() {
+            e.dangerous(); // should be okay
+          }
+        };
+    @UI PolyIface p =
+        new @UI PolyIface() {
+          @Override
+          public void doStuff() {
+            e.dangerous(); // should be okay
+          }
+        };
+    @UI PolyIface p2 =
+        new PolyIface() {
+          @Override
+          public void doStuff() {
+            // Safe due to anonymous inner class effect inference
+            e.dangerous(); // should be okay
+          }
+        };
+    PolyIface p3 =
+        // :: error: (assignment)
+        new PolyIface() { // Anonymous inner class inference for @UI
+          @Override
+          public void doStuff() {
+            // Safe due to anonymous inner class effect inference
+            e.dangerous(); // should be okay
+          }
+        };
+    @UI PolyIface p4 =
+        new IndirectPolyIface() {
+          @Override
+          public void doStuff() {
+            // Safe due to anonymous inner class effect inference
+            e.dangerous(); // should be okay
+          }
+        };
+    @UI ParlyPolyIface p5 =
+        new ParlyPolyIface() {
+          @Override
+          public void doPolyUIStuff() {
+            // Safe due to anonymous inner class effect inference
+            e.dangerous(); // should be okay
+          }
+
+          @Override
+          @SafeEffect
+          public void doSafeStuff() {
+            e.repaint();
+          }
+        };
+    ParlyPolyIface p6 =
+        new ParlyPolyIface() {
+          @Override
+          public void doPolyUIStuff() {
+            e.repaint(); // Safe
+          }
+
+          @Override
+          public void doSafeStuff() {
+            // :: error: (call.ui)
+            e.dangerous(); // No inference here, just as an invalid call
+          }
+        };
+    @UI ParlyPolyIface p7 =
+        new ParlyPolyIface() {
+          @Override
+          public void doPolyUIStuff() {
+            // Safe due to anonymous inner class effect inference
+            e.dangerous(); // should be okay
+          }
+
+          @Override
+          @SafeEffect
+          public void doSafeStuff() {
+            // :: error: (call.ui)
+            e.dangerous(); // No inference here, just as an invalid call
+          }
+        };
+    callSafePolyIface(
+        // :: error: (argument)
+        new PolyIface() { // Anonymous inner class inference for @UI
+          @Override
+          public void doStuff() {
+            // Safe due to anonymous inner class effect inference
+            e.dangerous(); // should be okay
+          }
+        });
+    callUIPolyIface(
+        new PolyIface() { // Anonymous inner class inference for @UI
+          @Override
+          public void doStuff() {
+            // Safe due to anonymous inner class effect inference
+            e.dangerous(); // should be okay
+          }
+        });
+    callSafePolyIface(getSafePolyIface(e));
+    callUIPolyIface(getUIPolyIface(e));
+    (new IPolyIfaceCaller() { // Anonymous inner class inference for @UI
+          @Override
+          public void call(final @UI PolyIface p) { // No global inference
+            p.doStuff();
+          }
+        })
+        .call(
+            new PolyIface() { // Anonymous inner class inference for @UI
+              @Override
+              public void doStuff() {
+                // Safe due to anonymous inner class effect inference
+                e.dangerous(); // should be okay
+              }
+            });
+    PolyIface maybeUIInstance =
+        // :: error: (assignment)
+        (maybe()
+            ? new PolyIface() { // Anonymous inner class inference for @UI
+              @Override
+              public void doStuff() {
+                // Safe due to anonymous inner class effect inference
+                e.dangerous(); // should be okay
+              }
+            }
+            : new PolyIface() {
+              @Override
+              public void doStuff() {}
+            });
+  }
+}
diff --git a/checker/tests/guieffect/AssignmentTests.java b/checker/tests/guieffect/AssignmentTests.java
new file mode 100644
index 0000000..a20954c
--- /dev/null
+++ b/checker/tests/guieffect/AssignmentTests.java
@@ -0,0 +1,48 @@
+import org.checkerframework.checker.guieffect.qual.AlwaysSafe;
+import org.checkerframework.checker.guieffect.qual.PolyUI;
+import org.checkerframework.checker.guieffect.qual.PolyUIType;
+import org.checkerframework.checker.guieffect.qual.UI;
+
+public class AssignmentTests {
+  public static @PolyUIType class P {}
+  // We must separate these tests, otherwise the flow sensitivity kicks in and confounds the test
+  // results
+  public void testBody1(@UI P ui, @AlwaysSafe P safe, @PolyUI P poly) {
+    ui = safe;
+  }
+
+  public void testBody2(@UI P ui, @AlwaysSafe P safe, @PolyUI P poly) {
+    ui = ui;
+  }
+
+  public void testBody3(@UI P ui, @AlwaysSafe P safe, @PolyUI P poly) {
+    ui = poly;
+  }
+
+  public void testBody4(@UI P ui, @AlwaysSafe P safe, @PolyUI P poly) {
+    safe = safe;
+  }
+
+  public void testBody5(@UI P ui, @AlwaysSafe P safe, @PolyUI P poly) {
+    // :: error: (assignment)
+    safe = ui;
+  }
+
+  public void testBody6(@UI P ui, @AlwaysSafe P safe, @PolyUI P poly) {
+    // :: error: (assignment)
+    safe = poly;
+  }
+
+  public void testBody7(@UI P ui, @AlwaysSafe P safe, @PolyUI P poly) {
+    poly = safe;
+  }
+
+  public void testBody8(@UI P ui, @AlwaysSafe P safe, @PolyUI P poly) {
+    poly = poly;
+  }
+
+  public void testBody9(@UI P ui, @AlwaysSafe P safe, @PolyUI P poly) {
+    // :: error: (assignment)
+    poly = ui;
+  }
+}
diff --git a/checker/tests/guieffect/BadUIOverrideChild.java b/checker/tests/guieffect/BadUIOverrideChild.java
new file mode 100644
index 0000000..b6e2642
--- /dev/null
+++ b/checker/tests/guieffect/BadUIOverrideChild.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.guieffect.qual.UIType;
+
+@UIType
+public class BadUIOverrideChild extends SafeParent {
+  // Should be an error because we marked this @UIType.
+  @Override
+  // :: error: (override.effect)
+  void m() {}
+}
diff --git a/checker/tests/guieffect/FooConflict.java b/checker/tests/guieffect/FooConflict.java
new file mode 100644
index 0000000..b9ba20e
--- /dev/null
+++ b/checker/tests/guieffect/FooConflict.java
@@ -0,0 +1,5 @@
+public class FooConflict implements IFooSafe, IFooUI {
+  @Override
+  // :: warning: (override.effect.warning.inheritance)
+  public void foo() {}
+}
diff --git a/checker/tests/guieffect/GenericSubTask.java b/checker/tests/guieffect/GenericSubTask.java
new file mode 100644
index 0000000..918367c
--- /dev/null
+++ b/checker/tests/guieffect/GenericSubTask.java
@@ -0,0 +1,16 @@
+import org.checkerframework.checker.guieffect.qual.PolyUI;
+import org.checkerframework.checker.guieffect.qual.PolyUIEffect;
+
+@PolyUI public class GenericSubTask implements @PolyUI IGenericTask {
+  public GenericTaskUIConsumer uicons;
+  public GenericTaskSafeConsumer safecons;
+
+  @Override
+  @PolyUIEffect
+  public void doGenericStuff() {
+    // In here, it should be that this:@PolyUI
+    uicons.runAsync(this); // should be okay
+    // :: error: (argument)
+    safecons.runAsync(this); // should be error!
+  }
+}
diff --git a/checker/tests/guieffect/GenericTaskSafeConsumer.java b/checker/tests/guieffect/GenericTaskSafeConsumer.java
new file mode 100644
index 0000000..2c83777
--- /dev/null
+++ b/checker/tests/guieffect/GenericTaskSafeConsumer.java
@@ -0,0 +1,7 @@
+import org.checkerframework.checker.guieffect.qual.AlwaysSafe;
+import org.checkerframework.checker.guieffect.qual.SafeEffect;
+
+public interface GenericTaskSafeConsumer {
+  @SafeEffect
+  public void runAsync(@AlwaysSafe IGenericTask t);
+}
diff --git a/checker/tests/guieffect/GenericTaskUIConsumer.java b/checker/tests/guieffect/GenericTaskUIConsumer.java
new file mode 100644
index 0000000..bb8bd1e
--- /dev/null
+++ b/checker/tests/guieffect/GenericTaskUIConsumer.java
@@ -0,0 +1,7 @@
+import org.checkerframework.checker.guieffect.qual.SafeEffect;
+import org.checkerframework.checker.guieffect.qual.UI;
+
+public interface GenericTaskUIConsumer {
+  @SafeEffect
+  public void runAsync(@UI IGenericTask t);
+}
diff --git a/checker/tests/guieffect/IAsyncUITask.java b/checker/tests/guieffect/IAsyncUITask.java
new file mode 100644
index 0000000..73540d8
--- /dev/null
+++ b/checker/tests/guieffect/IAsyncUITask.java
@@ -0,0 +1,6 @@
+import org.checkerframework.checker.guieffect.qual.UIType;
+
+@UIType
+public interface IAsyncUITask {
+  public void doStuff();
+}
diff --git a/checker/tests/guieffect/IFooSafe.java b/checker/tests/guieffect/IFooSafe.java
new file mode 100644
index 0000000..87369bd
--- /dev/null
+++ b/checker/tests/guieffect/IFooSafe.java
@@ -0,0 +1,6 @@
+import org.checkerframework.checker.guieffect.qual.SafeEffect;
+
+public interface IFooSafe {
+  @SafeEffect
+  public void foo();
+}
diff --git a/checker/tests/guieffect/IFooUI.java b/checker/tests/guieffect/IFooUI.java
new file mode 100644
index 0000000..3caeb8c
--- /dev/null
+++ b/checker/tests/guieffect/IFooUI.java
@@ -0,0 +1,6 @@
+import org.checkerframework.checker.guieffect.qual.UIEffect;
+
+public interface IFooUI {
+  @UIEffect
+  public void foo();
+}
diff --git a/checker/tests/guieffect/IGenericTask.java b/checker/tests/guieffect/IGenericTask.java
new file mode 100644
index 0000000..bec5eea
--- /dev/null
+++ b/checker/tests/guieffect/IGenericTask.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.guieffect.qual.PolyUI;
+import org.checkerframework.checker.guieffect.qual.PolyUIEffect;
+import org.checkerframework.checker.guieffect.qual.PolyUIType;
+
+@PolyUIType
+@PolyUI public interface IGenericTask {
+  @PolyUIEffect
+  public void doGenericStuff();
+}
diff --git a/checker/tests/guieffect/Java8Lambdas.java b/checker/tests/guieffect/Java8Lambdas.java
new file mode 100644
index 0000000..fa1c0e8
--- /dev/null
+++ b/checker/tests/guieffect/Java8Lambdas.java
@@ -0,0 +1,177 @@
+import org.checkerframework.checker.guieffect.qual.AlwaysSafe;
+import org.checkerframework.checker.guieffect.qual.PolyUI;
+import org.checkerframework.checker.guieffect.qual.PolyUIEffect;
+import org.checkerframework.checker.guieffect.qual.PolyUIType;
+import org.checkerframework.checker.guieffect.qual.UI;
+import org.checkerframework.checker.guieffect.qual.UIEffect;
+
+public class Java8Lambdas {
+
+  private interface SafeFunctionalInterface<T> {
+    public void executeAlwaysSafe(T arg);
+  }
+
+  private interface UIFunctionalInterface<T> {
+    @UIEffect
+    public void executeUI(T arg);
+  }
+
+  @PolyUIType
+  private interface PolymorphicFunctionalInterface<T> {
+    @PolyUIEffect
+    public void executePolymorphic(T arg);
+  }
+
+  private static class LambdaRunner {
+    private final UIElement arg;
+
+    public LambdaRunner(UIElement arg) {
+      this.arg = arg;
+    }
+
+    public void doSafe(SafeFunctionalInterface<UIElement> func) {
+      func.executeAlwaysSafe(this.arg);
+    }
+
+    @UIEffect
+    public void doUI(UIFunctionalInterface<UIElement> func) {
+      func.executeUI(this.arg);
+    }
+
+    // Needs to be @UIEffect, because the functional interface method is @UIEffect
+    public void unsafeDoUI(UIFunctionalInterface<UIElement> func) {
+      // :: error: (call.ui)
+      func.executeUI(this.arg);
+    }
+
+    public void doEither(@PolyUI PolymorphicFunctionalInterface<UIElement> func) {
+      // In a real program some intelligent dispatch could be done here to avoid running on UI
+      // thread unless needed.
+      arg.runOnUIThread(
+          new IAsyncUITask() {
+            final UIElement e2 = arg;
+
+            @Override
+            public void doStuff() { // should inherit UI effect
+              func.executePolymorphic(e2); // should be okay
+            }
+          });
+    }
+
+    public void doUISafely(@UI PolymorphicFunctionalInterface<UIElement> func) {
+      // In a real program some intelligent dispatch could be done here to avoid running on UI
+      // thread unless needed.
+      arg.runOnUIThread(
+          new IAsyncUITask() {
+            final UIElement e2 = arg;
+
+            @Override
+            public void doStuff() { // should inherit UI effect
+              func.executePolymorphic(e2); // should be okay
+            }
+          });
+    }
+  }
+
+  @PolyUIType
+  private static class PolymorphicLambdaRunner {
+    private final UIElement arg;
+
+    public PolymorphicLambdaRunner(UIElement arg) {
+      this.arg = arg;
+    }
+
+    @PolyUIEffect
+    public void doEither(@PolyUI PolymorphicFunctionalInterface<UIElement> func) {
+      func.executePolymorphic(this.arg);
+    }
+  }
+
+  public static void safeContextTestCases(UIElement elem) {
+    LambdaRunner runner = new LambdaRunner(elem);
+    runner.doSafe(e -> e.repaint());
+    // :: error: (call.ui)
+    runner.doSafe(e -> e.dangerous()); // Not allowed in doSafe
+    // :: error: (call.ui)
+    runner.doUI(e -> e.repaint()); // Not allowed in safe context
+    // :: error: (call.ui)
+    runner.doUI(e -> e.dangerous()); // Not allowed in safe context
+    runner.doEither(e -> e.repaint());
+    runner.doEither(e -> e.dangerous());
+    runner.doUISafely(e -> e.dangerous());
+    @AlwaysSafe PolymorphicLambdaRunner safePolymorphicLambdaRunner = new PolymorphicLambdaRunner(elem);
+    safePolymorphicLambdaRunner.doEither(e -> e.repaint());
+    // This next two are ok for this patch since the behavior is the same (no report) for
+    // lambdas as for annon classes. However, shouldn't this be (argument)
+    // just because safePolymorphicLambdaRunner is not an @UI PolymorphicLambdaRunner ? Or,
+    // failing that (call.ui) since doEither is @PolyUIEffect ?
+    safePolymorphicLambdaRunner.doEither(e -> e.dangerous());
+    safePolymorphicLambdaRunner.doEither(
+        new @UI PolymorphicFunctionalInterface<UIElement>() {
+          public void executePolymorphic(UIElement arg) {
+            arg.dangerous();
+          }
+        });
+    @UI PolymorphicLambdaRunner uiPolymorphicLambdaRunner = new @UI PolymorphicLambdaRunner(elem);
+    // :: error: (call.ui)
+    uiPolymorphicLambdaRunner.doEither(
+        e -> e.repaint()); // Safe at runtime, but not by the type system!
+    // :: error: (call.ui)
+    uiPolymorphicLambdaRunner.doEither(e -> e.dangerous());
+    PolymorphicFunctionalInterface<UIElement> func1 = e -> e.repaint();
+    // :: error: (assignment)
+    PolymorphicFunctionalInterface<UIElement> func2 = e -> e.dangerous(); // Incompatible types!
+    PolymorphicFunctionalInterface<UIElement> func2p =
+        // :: error: (assignment)
+        (new @UI PolymorphicFunctionalInterface<UIElement>() {
+          public void executePolymorphic(UIElement arg) {
+            arg.dangerous();
+          }
+        });
+    @UI PolymorphicFunctionalInterface<UIElement> func3 = e -> e.dangerous();
+    safePolymorphicLambdaRunner.doEither(func1);
+    safePolymorphicLambdaRunner.doEither(func2);
+    // :: error: (call.ui)
+    uiPolymorphicLambdaRunner.doEither(func1);
+    // :: error: (call.ui)
+    uiPolymorphicLambdaRunner.doEither(func2);
+    // :: error: (call.ui)
+    uiPolymorphicLambdaRunner.doEither(func3);
+  }
+
+  @UIEffect
+  public static void uiContextTestCases(UIElement elem) {
+    LambdaRunner runner = new LambdaRunner(elem);
+    // :: error: (call.ui)
+    runner.doSafe(e -> e.dangerous());
+    runner.doUI(e -> e.repaint());
+    runner.doUI(e -> e.dangerous());
+    PolymorphicLambdaRunner safePolymorphicLambdaRunner = new PolymorphicLambdaRunner(elem);
+    // No error, why? :: error: (argument)
+    safePolymorphicLambdaRunner.doEither(e -> e.dangerous());
+    @UI PolymorphicLambdaRunner uiPolymorphicLambdaRunner = new @UI PolymorphicLambdaRunner(elem);
+    uiPolymorphicLambdaRunner.doEither(e -> e.dangerous());
+  }
+
+  public @UI PolymorphicFunctionalInterface<UIElement> returnLambdasTest1() {
+    return e -> e.dangerous();
+  }
+
+  // This should be an error without an @UI annotation on the return type. No?
+  public PolymorphicFunctionalInterface<UIElement> returnLambdasTest2() {
+    // :: error: (return)
+    return e -> {
+      e.dangerous();
+    };
+  }
+
+  // Just to check
+  public PolymorphicFunctionalInterface<UIElement> returnLambdasTest3() {
+    // :: error: (return)
+    return (new @UI PolymorphicFunctionalInterface<UIElement>() {
+      public void executePolymorphic(UIElement arg) {
+        arg.dangerous();
+      }
+    });
+  }
+}
diff --git a/checker/tests/guieffect/MouseTest.java b/checker/tests/guieffect/MouseTest.java
new file mode 100644
index 0000000..9ab979c
--- /dev/null
+++ b/checker/tests/guieffect/MouseTest.java
@@ -0,0 +1,13 @@
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import org.checkerframework.checker.guieffect.qual.UIType;
+
+// Test the stub file handling
+@UIType
+public class MouseTest extends MouseAdapter {
+  @Override
+  public void mouseEntered(MouseEvent arg0) {
+    IAsyncUITask t = null;
+    t.doStuff();
+  }
+}
diff --git a/checker/tests/guieffect/NoAnnotationsTest.java b/checker/tests/guieffect/NoAnnotationsTest.java
new file mode 100644
index 0000000..1a3c8a1
--- /dev/null
+++ b/checker/tests/guieffect/NoAnnotationsTest.java
@@ -0,0 +1,3 @@
+public class NoAnnotationsTest {
+  public boolean b;
+}
diff --git a/checker/tests/guieffect/SafeParent.java b/checker/tests/guieffect/SafeParent.java
new file mode 100644
index 0000000..06015b2
--- /dev/null
+++ b/checker/tests/guieffect/SafeParent.java
@@ -0,0 +1,6 @@
+import org.checkerframework.checker.guieffect.qual.SafeEffect;
+
+public class SafeParent {
+  @SafeEffect
+  void m() {}
+}
diff --git a/checker/tests/guieffect/Specialization.java b/checker/tests/guieffect/Specialization.java
new file mode 100644
index 0000000..f0db7dc
--- /dev/null
+++ b/checker/tests/guieffect/Specialization.java
@@ -0,0 +1,50 @@
+import org.checkerframework.checker.guieffect.qual.*;
+
+public class Specialization {
+
+  @PolyUIType
+  public static interface I {
+    @PolyUIEffect
+    public void m();
+  }
+
+  public static void reqSafe(@AlwaysSafe I i) {}
+
+  @UIEffect
+  public static void reqUI(@UI I i) {}
+
+  @PolyUIType
+  public static interface Doer {
+
+    public void doStuff(@PolyUI Doer this, @PolyUI I i);
+  }
+
+  @AlwaysSafe public static class SafeDoer implements @AlwaysSafe Doer {
+    // :: error: (override.param)
+    public void doStuff(@AlwaysSafe SafeDoer this, @AlwaysSafe I i) {}
+  }
+
+  public void q(@AlwaysSafe Doer doer, @UI I i) {
+    doer.doStuff(i);
+  }
+
+  public static void main(String[] args) {
+
+    @AlwaysSafe Doer d =
+        new @AlwaysSafe Doer() {
+          @SafeEffect
+          // :: error: (override.param)
+          public void doStuff(@AlwaysSafe I i) {
+            reqSafe(i);
+          }
+        };
+    @UI I ui =
+        new @UI I() {
+          public void m() {
+            reqUI(null);
+          }
+        };
+    Specialization q = new Specialization();
+    q.q(d, ui);
+  }
+}
diff --git a/checker/tests/guieffect/TestProgram.java b/checker/tests/guieffect/TestProgram.java
new file mode 100644
index 0000000..2f50fe3
--- /dev/null
+++ b/checker/tests/guieffect/TestProgram.java
@@ -0,0 +1,80 @@
+import org.checkerframework.checker.guieffect.qual.AlwaysSafe;
+import org.checkerframework.checker.guieffect.qual.UI;
+import org.checkerframework.checker.guieffect.qual.UIEffect;
+import packagetests.SafeByDecl;
+import packagetests.UIByPackageDecl;
+
+public class TestProgram {
+  public void nonUIStuff(
+      final UIElement e,
+      final GenericTaskUIConsumer uicons,
+      final GenericTaskSafeConsumer safecons) {
+    // :: error: (call.ui)
+    e.dangerous(); // should be bad
+    e.runOnUIThread(
+        new IAsyncUITask() {
+          final UIElement e2 = e;
+
+          @Override
+          public void doStuff() { // should inherit UI effect
+            e2.dangerous(); // should be okay
+          }
+        });
+    uicons.runAsync(
+        new @UI IGenericTask() {
+          final UIElement e2 = e;
+
+          @Override
+          public void doGenericStuff() { // Should be inst. w/ @UI eff.
+            e2.dangerous(); // should be okay
+          }
+        });
+    safecons.runAsync(
+        new @AlwaysSafe IGenericTask() {
+          final UIElement e2 = e;
+
+          @Override
+          public void doGenericStuff() { // Should be inst. w/ @AlwaysSafe
+            // :: error: (call.ui)
+            e2.dangerous(); // should be an error
+            safecons.runAsync(this); // Should be okay, this:@AlwaysSafe
+          }
+        });
+    safecons.runAsync(
+        // :: error: (argument)
+        new @UI IGenericTask() {
+          final UIElement e2 = e;
+
+          @Override
+          public void doGenericStuff() { // Should be inst. w/ @UI
+            e2.dangerous(); // should be ok
+            // :: error: (argument)
+            safecons.runAsync(this); // Should be error, this:@UI
+          }
+        });
+    // Test that the package annotation works
+    // :: error: (call.ui)
+    UIByPackageDecl.implicitlyUI();
+    // Test that @SafeType works: SafeByDecl is inside a @UIPackage
+    SafeByDecl.safeByTypeDespiteUIPackage();
+    safecons.runAsync(
+        // :: error: (argument)
+        new IGenericTask() {
+          @Override
+          public void doGenericStuff() {
+            // Safe here due to anonymous inner class effect inference, but will trigger
+            // an error above due to safecons.runAsync not taking an @UI IGenericTask.
+            UIByPackageDecl.implicitlyUI();
+          }
+        });
+    safecons.runAsync(
+        new IGenericTask() {
+          @Override
+          @UIEffect
+          // :: error: (override.effect.nonui)
+          public void doGenericStuff() {
+            UIByPackageDecl.implicitlyUI();
+          }
+        });
+  }
+}
diff --git a/checker/tests/guieffect/ThrowCatchTest.java b/checker/tests/guieffect/ThrowCatchTest.java
new file mode 100644
index 0000000..92befa9
--- /dev/null
+++ b/checker/tests/guieffect/ThrowCatchTest.java
@@ -0,0 +1,127 @@
+import java.util.List;
+import org.checkerframework.checker.guieffect.qual.AlwaysSafe;
+import org.checkerframework.checker.guieffect.qual.PolyUIType;
+import org.checkerframework.checker.guieffect.qual.UI;
+
+public class ThrowCatchTest {
+  // Default type of List's type parameter is below @UI so these
+  // fields are type.argument.incompatible
+  // :: error: (type.argument)
+  List<? extends @UI Object> ooo;
+
+  // :: error: (type.argument) :: error: (annotations.on.use)
+  List<? extends @UI Inner> iii;
+
+  class Inner {}
+
+  boolean flag = true;
+  // Type var test
+  <E extends @UI PolyUIException> void throwTypeVarUI1(E ex1, @UI E ex2) throws PolyUIException {
+    if (flag) {
+      // :: error: (throw)
+      throw ex1;
+    }
+    // :: error: (throw)
+    throw ex2;
+  }
+
+  <@UI E extends @UI PolyUIException> void throwTypeVarUI2(E ex1) throws PolyUIException {
+    // :: error: (throw)
+    throw ex1;
+  }
+
+  <E extends @AlwaysSafe PolyUIException> void throwTypeVarAlwaysSafe1(E ex1, @AlwaysSafe E ex2)
+      throws PolyUIException {
+    if (flag) {
+      throw ex1;
+    }
+    throw ex2;
+  }
+
+  <@AlwaysSafe E extends PolyUIException> void throwTypeVarAlwaysSafe2(E ex1, @AlwaysSafe E ex2)
+      throws PolyUIException {
+    if (flag) {
+      throw ex1;
+    }
+    throw ex2;
+  }
+
+  <@AlwaysSafe E extends @UI PolyUIException> void throwTypeVarMixed(E ex1, @AlwaysSafe E ex2)
+      throws PolyUIException {
+    if (flag) {
+      // :: error: (throw)
+      throw ex1;
+    }
+    throw ex2;
+  }
+
+  // Wildcards
+  void throwWildcard(
+      // :: error: (type.argument)
+      List<? extends @UI PolyUIException>
+          ui, // Default type of List's type parameter is below @UI so this is
+      // type.argument.incompatible
+      List<? extends @AlwaysSafe PolyUIException> alwaysSafe)
+      throws PolyUIException {
+    if (flag) {
+      // :: error: (throw)
+      throw ui.get(0);
+    }
+    throw alwaysSafe.get(0);
+  }
+
+  void throwNull() {
+    throw null;
+  }
+
+  // Declared
+  @UI PolyUIException ui = new PolyUIException();
+  @AlwaysSafe PolyUIException alwaysSafe = new PolyUIException();
+
+  void throwDeclared() {
+    try {
+      // :: error: (throw)
+      throw ui;
+    } catch (@UI PolyUIException UIParam) {
+
+    }
+
+    try {
+      throw alwaysSafe;
+    } catch (@AlwaysSafe PolyUIException alwaysSafeParam) {
+
+    }
+  }
+
+  // Test Exception parameters
+  void unionTypes() {
+    try {
+    } catch (
+        @AlwaysSafe NullPointerPolyUIException
+        | @AlwaysSafe ArrayStorePolyUIException unionParam) {
+
+    }
+
+    try {
+    } catch (@UI NullPointerPolyUIException | @UI ArrayStorePolyUIException unionParam) {
+
+    }
+  }
+
+  void defaults() {
+    try {
+      throw new PolyUIException();
+    } catch (PolyUIException e) {
+
+    }
+  }
+
+  @PolyUIType
+  class PolyUIException extends Exception {}
+
+  @PolyUIType
+  class NullPointerPolyUIException extends NullPointerException {}
+
+  @PolyUIType
+  class ArrayStorePolyUIException extends ArrayStoreException {}
+}
diff --git a/checker/tests/guieffect/TransitiveInheritance.java b/checker/tests/guieffect/TransitiveInheritance.java
new file mode 100644
index 0000000..0185567
--- /dev/null
+++ b/checker/tests/guieffect/TransitiveInheritance.java
@@ -0,0 +1,58 @@
+import org.checkerframework.checker.guieffect.qual.UIEffect;
+
+public class TransitiveInheritance {
+
+  public static class TopLevel {
+    // Implicitly safe
+    public void foo() {}
+  }
+
+  public static interface ITop {
+    public void bar();
+  }
+
+  public static interface IIndirect {
+    public void baz();
+  }
+
+  // Mid-level class and interface that do not redeclare or override the default safe methods
+  public abstract static class MidLevel extends TopLevel implements IIndirect {}
+
+  public static interface IMid extends ITop {}
+
+  // Issue #3287 is that if foo or bar is overridden with a @UIEffect implementation here, the
+  // "skip" in declarations causes the override error to not be issued
+  // We check both classes and interfaces; the reported issue is related only to methods whose
+  // nearest explicit definition lives in an interface
+  public static class Base extends MidLevel implements IMid {
+
+    // Should catch when the override is for a method originating in a class two levels up (here
+    // TopLevel)
+    @Override
+    @UIEffect
+    // :: error: (override.effect)
+    public void foo() {}
+
+    // Should catch when the override is for a method originating in an interface two levels up
+    // (here ITop)
+    @Override
+    @UIEffect
+    // :: error: (override.effect)
+    public void bar() {}
+
+    // Should catch when the override is for a method originating in an interface two levels up, but
+    // which is implemented via class inheritance (here IIndirect, which is implemented by
+    // MidLevel).
+    @Override
+    @UIEffect
+    // :: error: (override.effect)
+    public void baz() {}
+  }
+
+  public static interface IBase extends IMid {
+    @Override
+    @UIEffect
+    // :: error: (override.effect)
+    public void bar();
+  }
+}
diff --git a/checker/tests/guieffect/UIChild.java b/checker/tests/guieffect/UIChild.java
new file mode 100644
index 0000000..f6f94c6
--- /dev/null
+++ b/checker/tests/guieffect/UIChild.java
@@ -0,0 +1,38 @@
+import org.checkerframework.checker.guieffect.qual.PolyUIEffect;
+import org.checkerframework.checker.guieffect.qual.SafeEffect;
+import org.checkerframework.checker.guieffect.qual.UIEffect;
+
+// Should not inherit @UI!
+public class UIChild extends UIParent {
+  @Override
+  public void doingUIStuff() {
+    // :: error: (call.ui)
+    thingy.dangerous();
+  }
+
+  // Should be an error to make this @UI
+  @Override
+  @UIEffect
+  // :: error: (override.effect)
+  public void doingSafeStuff() {}
+
+  public void shouldNotBeUI() {
+    // :: error: (call.ui)
+    thingy.dangerous();
+  }
+
+  @UIEffect
+  @SafeEffect
+  // :: error: (annotations.conflicts)
+  public void doubleAnnot1() {}
+
+  @UIEffect
+  @PolyUIEffect
+  // :: error: (annotations.conflicts) :: error: (polymorphism)
+  public void doubleAnnot2() {}
+
+  @PolyUIEffect
+  @SafeEffect
+  // :: error: (annotations.conflicts) :: error: (polymorphism)
+  public void doubleAnnot3() {}
+}
diff --git a/checker/tests/guieffect/UIElement.java b/checker/tests/guieffect/UIElement.java
new file mode 100644
index 0000000..ee4517e
--- /dev/null
+++ b/checker/tests/guieffect/UIElement.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.guieffect.qual.SafeEffect;
+import org.checkerframework.checker.guieffect.qual.UIType;
+
+@UIType
+public interface UIElement {
+  public void dangerous();
+
+  @SafeEffect
+  public void repaint();
+
+  @SafeEffect
+  public void runOnUIThread(IAsyncUITask task);
+}
diff --git a/checker/tests/guieffect/UIParent.java b/checker/tests/guieffect/UIParent.java
new file mode 100644
index 0000000..6727443
--- /dev/null
+++ b/checker/tests/guieffect/UIParent.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.guieffect.qual.SafeEffect;
+import org.checkerframework.checker.guieffect.qual.UIType;
+
+@UIType
+public class UIParent {
+  protected UIElement thingy;
+
+  @SafeEffect // Making this ctor safe to allow easy safe subclasses
+  public UIParent() {}
+
+  public void doingUIStuff() {
+    thingy.dangerous();
+  } // should have UI effect
+
+  @SafeEffect
+  public void doingSafeStuff() {} // non-UI
+}
diff --git a/checker/tests/guieffect/WeakeningChild.java b/checker/tests/guieffect/WeakeningChild.java
new file mode 100644
index 0000000..a4f815c
--- /dev/null
+++ b/checker/tests/guieffect/WeakeningChild.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.guieffect.qual.SafeEffect;
+
+// Should not inherit @UI!
+public class WeakeningChild extends UIParent {
+  // Should be valid to override @UI methods with @AlwaysSafe methods
+  @Override
+  @SafeEffect
+  public void doingUIStuff() {}
+
+  @Override
+  public void doingSafeStuff() {}
+}
diff --git a/checker/tests/guieffect/packagetests/SafeByDecl.java b/checker/tests/guieffect/packagetests/SafeByDecl.java
new file mode 100644
index 0000000..20fe6cd
--- /dev/null
+++ b/checker/tests/guieffect/packagetests/SafeByDecl.java
@@ -0,0 +1,8 @@
+package packagetests;
+
+import org.checkerframework.checker.guieffect.qual.SafeType;
+
+@SafeType
+public class SafeByDecl {
+  public static void safeByTypeDespiteUIPackage() {}
+}
diff --git a/checker/tests/guieffect/packagetests/UIByPackageDecl.java b/checker/tests/guieffect/packagetests/UIByPackageDecl.java
new file mode 100644
index 0000000..2a519f6
--- /dev/null
+++ b/checker/tests/guieffect/packagetests/UIByPackageDecl.java
@@ -0,0 +1,7 @@
+package packagetests;
+
+public class UIByPackageDecl {
+  public static void implicitlyUI() {
+    // don't need to do anything here
+  }
+}
diff --git a/checker/tests/guieffect/packagetests/package-info.java b/checker/tests/guieffect/packagetests/package-info.java
new file mode 100644
index 0000000..15b0f51
--- /dev/null
+++ b/checker/tests/guieffect/packagetests/package-info.java
@@ -0,0 +1,6 @@
+@UIPackage
+package packagetests;
+
+// This must come *AFTER* the package name, oddly enough.
+
+import org.checkerframework.checker.guieffect.qual.UIPackage;
diff --git a/checker/tests/i18n-formatter-lubglb/Placeholder.java b/checker/tests/i18n-formatter-lubglb/Placeholder.java
new file mode 100644
index 0000000..5ee2ef0
--- /dev/null
+++ b/checker/tests/i18n-formatter-lubglb/Placeholder.java
@@ -0,0 +1,3 @@
+// We need a file to start the checker.
+
+public class Placeholder {}
diff --git a/checker/tests/i18n-formatter-unchecked-defaults/TestUncheckedByteCode.java b/checker/tests/i18n-formatter-unchecked-defaults/TestUncheckedByteCode.java
new file mode 100644
index 0000000..1b84161
--- /dev/null
+++ b/checker/tests/i18n-formatter-unchecked-defaults/TestUncheckedByteCode.java
@@ -0,0 +1,18 @@
+import org.checkerframework.framework.testchecker.lib.UncheckedByteCode;
+
+public class TestUncheckedByteCode {
+  Object field;
+
+  void test(UncheckedByteCode<Object> param, Integer i) {
+    field = param.getCT();
+    field = param.getInt(1);
+    field = param.getInteger(i);
+    field = param.identity("hello");
+
+    // String and Object are relevant types and must be annotated in bytecode
+    // :: error: (argument)
+    field = param.getObject(new Object());
+    // :: error: (argument)
+    field = param.getString("hello");
+  }
+}
diff --git a/checker/tests/i18n-formatter/ConversionCategoryTest.java b/checker/tests/i18n-formatter/ConversionCategoryTest.java
new file mode 100644
index 0000000..becba97
--- /dev/null
+++ b/checker/tests/i18n-formatter/ConversionCategoryTest.java
@@ -0,0 +1,69 @@
+import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
+import org.checkerframework.checker.i18nformatter.qual.I18nFormat;
+
+public class ConversionCategoryTest {
+
+  public static void main(String[] args) {
+    @I18nFormat({I18nConversionCategory.GENERAL}) String s1 = "{0}";
+
+    @I18nFormat({I18nConversionCategory.DATE}) String s2 = "{0, date}";
+    @I18nFormat({I18nConversionCategory.NUMBER}) String s3 = "{0, number}";
+
+    @I18nFormat({I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER}) String s4 = "{1} {0, date}";
+    // :: warning: (i18nformat.missing.arguments)
+    s4 = "{0}";
+
+    @I18nFormat({I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER}) String s5 = "{0} and {1, number}";
+    @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER}) String s6 = "{1, number}";
+    @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.DATE}) String s7 = "{1, date}";
+
+    @I18nFormat({
+      I18nConversionCategory.UNUSED,
+      I18nConversionCategory.UNUSED,
+      I18nConversionCategory.NUMBER
+    })
+    String s8 = "{2}";
+
+    @I18nFormat({
+      I18nConversionCategory.GENERAL,
+      I18nConversionCategory.DATE,
+      I18nConversionCategory.UNUSED,
+      I18nConversionCategory.NUMBER
+    })
+    String s9 = "{3, number} {0} {1, time}";
+
+    @I18nFormat({
+      I18nConversionCategory.GENERAL,
+      I18nConversionCategory.DATE,
+      I18nConversionCategory.DATE,
+      I18nConversionCategory.NUMBER,
+      I18nConversionCategory.UNUSED,
+      I18nConversionCategory.GENERAL
+    })
+    String s10 = "{0} {1, date} {2, time} {3, number} {5}";
+
+    @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.DATE}) String s11 = "{1} {1, date}";
+
+    @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER}) String s12 = "{1, number} {1, date}";
+
+    @I18nFormat({I18nConversionCategory.DATE}) String s13 = "{0, date} {0, date}";
+
+    // :: error: (i18nformat.excess.arguments) :: error: (assignment)
+    @I18nFormat({I18nConversionCategory.GENERAL}) String b1 = "{1}";
+
+    // :: error: (assignment)
+    @I18nFormat({I18nConversionCategory.DATE}) String b2 = "{0, number}";
+
+    // :: error: (assignment)
+    @I18nFormat({I18nConversionCategory.GENERAL}) String b3 = "{0, number}";
+
+    // :: error: (assignment)
+    @I18nFormat({I18nConversionCategory.GENERAL}) String b4 = "{0, date}";
+
+    // :: error: (i18nformat.excess.arguments) :: error: (assignment)
+    @I18nFormat({I18nConversionCategory.DATE}) String b5 = "{0, date} {1, date}";
+
+    // :: warning: (i18nformat.missing.arguments)
+    @I18nFormat({I18nConversionCategory.DATE, I18nConversionCategory.DATE}) String b6 = "{0, date}";
+  }
+}
diff --git a/checker/tests/i18n-formatter/HasFormat.java b/checker/tests/i18n-formatter/HasFormat.java
new file mode 100644
index 0000000..ce7172c
--- /dev/null
+++ b/checker/tests/i18n-formatter/HasFormat.java
@@ -0,0 +1,62 @@
+import java.text.MessageFormat;
+import java.util.Date;
+import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
+import org.checkerframework.checker.i18nformatter.util.I18nFormatUtil;
+
+public class HasFormat {
+
+  void test1(String format) {
+    if (I18nFormatUtil.hasFormat(
+        format, I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)) {
+      MessageFormat.format(format, "S", 1);
+      // :: warning: (i18nformat.missing.arguments)
+      MessageFormat.format(format, "S");
+      // :: error: (argument)
+      MessageFormat.format(format, "S", "S");
+      // :: warning: (i18nformat.excess.arguments)
+      MessageFormat.format(format, "S", 1, 2);
+    }
+  }
+
+  void test2(String format) {
+    if (!I18nFormatUtil.hasFormat(
+        format, I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)) {
+      // :: error: (i18nformat.string)
+      MessageFormat.format(format, "S", 1);
+    }
+  }
+
+  void test3(String format) {
+    if (I18nFormatUtil.hasFormat(
+        format,
+        I18nConversionCategory.GENERAL,
+        I18nConversionCategory.UNUSED,
+        I18nConversionCategory.GENERAL)) {
+      // :: warning: (i18nformat.argument.unused)
+      MessageFormat.format(format, "S", 1, "S");
+    }
+  }
+
+  void test4(String format) throws Exception {
+    // :: error: (i18nformat.string)
+    MessageFormat.format(format, "S");
+    if (I18nFormatUtil.hasFormat(format, I18nConversionCategory.GENERAL)) {
+      MessageFormat.format(format, "S");
+      MessageFormat.format(format, new Date());
+      MessageFormat.format(format, 1);
+    } else {
+      throw new Exception();
+    }
+  }
+
+  void tes5(String format) {
+    if (I18nFormatUtil.hasFormat(format, I18nConversionCategory.NUMBER)) {
+      // :: error: (argument)
+      MessageFormat.format(format, "S");
+      MessageFormat.format(format, 1);
+    } else {
+      // :: error: (i18nformat.string)
+      MessageFormat.format(format, 1);
+    }
+  }
+}
diff --git a/checker/tests/i18n-formatter/I18nFormat.java b/checker/tests/i18n-formatter/I18nFormat.java
new file mode 100644
index 0000000..c5b1c9e
--- /dev/null
+++ b/checker/tests/i18n-formatter/I18nFormat.java
@@ -0,0 +1,52 @@
+import java.text.MessageFormat;
+import java.util.Date;
+
+public class I18nFormat {
+
+  void test() {
+
+    MessageFormat.format(
+        "{0} {1, number} {2, time} {3, date} {4, choice, 0#zero}",
+        "S", 1, new Date(), new Date(), 0);
+    MessageFormat.format("{0, number}{1, number}", 1, 2);
+    MessageFormat.format("{0, number}{0}", 1);
+
+    // :: warning: (i18nformat.excess.arguments)
+    MessageFormat.format("'{0, number}'", new Date(12));
+
+    // :: warning: (i18nformat.missing.arguments)
+    MessageFormat.format("''{0, time, short}''{1}{2, time} {33, number}{44444}'{''''", 0);
+
+    // :: warning: (i18nformat.missing.arguments)
+    MessageFormat.format("{0, number}{1, number}", 1);
+
+    // :: warning: (i18nformat.argument.unused)
+    MessageFormat.format("{1, number}", 1, 1);
+
+    // :: warning: (i18nformat.excess.arguments)
+    MessageFormat.format("{0, number}", 1, new Date());
+
+    // :: warning: (i18nformat.indirect.arguments)
+    MessageFormat.format("{0, number}", new Object[2]);
+
+    MessageFormat.format("{0}", "S");
+    MessageFormat.format("{0}", 1);
+    MessageFormat.format("{0}", new Date());
+
+    // :: error: (argument)
+    MessageFormat.format("{0, number}", "S");
+    MessageFormat.format("{0, number}", 1);
+    // :: error: (argument)
+    MessageFormat.format("{0, number}", new Date());
+
+    // :: error: (argument)
+    MessageFormat.format("{0, time}", "S");
+    MessageFormat.format("{0, time}", 1);
+    MessageFormat.format("{0, time}", new Date());
+
+    // :: error: (argument)
+    MessageFormat.format("{0, date}", "S");
+    MessageFormat.format("{0, date}", 1);
+    MessageFormat.format("{0, date}", new Date());
+  }
+}
diff --git a/checker/tests/i18n-formatter/I18nFormatForTest.java b/checker/tests/i18n-formatter/I18nFormatForTest.java
new file mode 100644
index 0000000..6402bce
--- /dev/null
+++ b/checker/tests/i18n-formatter/I18nFormatForTest.java
@@ -0,0 +1,96 @@
+import java.text.MessageFormat;
+import java.util.Date;
+import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor;
+
+public class I18nFormatForTest {
+
+  static class A {
+    public void methodA(@I18nFormatFor("#2") String format, Object... args) {}
+  }
+
+  public static void main(String[] args) {
+
+    A a1 = new A();
+
+    // :: error: (i18nformat.string)
+    a1.methodA("{0, number", new Date(12));
+
+    // :: warning: (i18nformat.excess.arguments)
+    a1.methodA("'{0{}", 1);
+    a1.methodA("{0}", "A");
+
+    // :: error: (i18nformat.string)
+    a(1, 1.2, "{0, number", 1.2, new Date(12));
+    a(1, 1.2, "{0, number}{1}", 1.2, 1, "A");
+    // :: warning: (i18nformat.missing.arguments)
+    a(1, 1.2, "{0, number}{1}", 1.2, 1);
+    // :: warning: (i18nformat.excess.arguments)
+    a(1, 1.2, "{0, number}{1}", 1.2, 1, "A", 2);
+    b("{0, number}{1}", 1, "A");
+
+    // :: error: (i18nformat.string)
+    b("{0, number", new Date(12));
+    b("{0, number}{1}", 1, "A");
+    b("{0}", "a string");
+    // :: error: (argument)
+    b("{0, number}", "a string");
+
+    // :: error: (i18nformat.formatfor)
+    c("{0, number}{1}", 1, "A");
+
+    // :: error: (i18nformat.formatfor)
+    e(1, 2);
+
+    f("{0}", 2);
+
+    // :: error: (i18nformat.formatfor)
+    h("{0}", "a string");
+
+    // :: error: (i18nformat.formatfor)
+    i("{0}", "a string");
+
+    j("{0}");
+    // :: error: (argument)
+    j("{0, number}");
+  }
+
+  // Normal use
+  static void b(@I18nFormatFor("#2") String f, Object... args) {
+    MessageFormat.format(f, args);
+  }
+
+  // @II18nFormatFor can be annotated anywhere
+  static void a(
+      int dummy1, double dummy2, @I18nFormatFor("#5") String f, Object dummy3, Object... args2) {
+    MessageFormat.format(f, args2);
+  }
+
+  // Invalid index
+  static void c(@I18nFormatFor("#-1") String f, Object... args) {
+    MessageFormat.format("{0}", "A");
+  }
+
+  // @I18nFormatFor needs to be annotated to a string.
+  // :: error: (anno.on.irrelevant)
+  static void e(@I18nFormatFor("#2") int f, Object... args) {}
+
+  // The parameter type is not necessary to an array of objects
+  static void f(@I18nFormatFor("#2") String f, int args) {
+    MessageFormat.format(f, args);
+  }
+
+  // Invalid formatfor argument
+  static void h(@I18nFormatFor("2") String f, String args) {
+    MessageFormat.format(f, args);
+  }
+
+  // We don't support this form of argument. You need to specify the parameter index.
+  static void i(@I18nFormatFor("arg") String f, Object... arg) {
+    MessageFormat.format(f, arg);
+  }
+
+  // This is also a valid thing to do.
+  static void j(@I18nFormatFor("#1") String f) {
+    MessageFormat.format(f, f);
+  }
+}
diff --git a/checker/tests/i18n-formatter/IsFormat.java b/checker/tests/i18n-formatter/IsFormat.java
new file mode 100644
index 0000000..1295753
--- /dev/null
+++ b/checker/tests/i18n-formatter/IsFormat.java
@@ -0,0 +1,37 @@
+import java.text.MessageFormat;
+import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
+import org.checkerframework.checker.i18nformatter.util.I18nFormatUtil;
+
+public class IsFormat {
+  public static void test1(String cc) {
+    if (!I18nFormatUtil.isFormat(cc)) {
+      // :: error: (i18nformat.string)
+      MessageFormat.format(cc, "A");
+    } else {
+      // :: error: (i18nformat.string)
+      MessageFormat.format(cc, "A");
+      if (I18nFormatUtil.hasFormat(cc, I18nConversionCategory.GENERAL)) {
+        MessageFormat.format(cc, "A");
+      } else {
+        // :: error: (i18nformat.string)
+        MessageFormat.format(cc, "A");
+      }
+    }
+  }
+
+  public static void test2(String cc) {
+    if (!I18nFormatUtil.isFormat(cc)) {
+      // :: error: (i18nformat.string)
+      MessageFormat.format(cc, "A");
+    } else {
+      // :: error: (i18nformat.string)
+      MessageFormat.format(cc, "A");
+      if (I18nFormatUtil.hasFormat(cc, I18nConversionCategory.NUMBER)) {
+        MessageFormat.format(cc, 1);
+      } else {
+        // :: error: (i18nformat.string)
+        MessageFormat.format(cc, "A");
+      }
+    }
+  }
+}
diff --git a/checker/tests/i18n-formatter/ManualExampleI18nFormatter.java b/checker/tests/i18n-formatter/ManualExampleI18nFormatter.java
new file mode 100644
index 0000000..d0bfa4c
--- /dev/null
+++ b/checker/tests/i18n-formatter/ManualExampleI18nFormatter.java
@@ -0,0 +1,37 @@
+// Example from the manual
+
+import static org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory.DATE;
+import static org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory.NUMBER;
+
+import org.checkerframework.checker.i18nformatter.qual.I18nFormat;
+
+public class ManualExampleI18nFormatter {
+
+  void m(boolean flag) {
+
+    @I18nFormat({NUMBER, DATE}) String f;
+
+    f = "{0, number, #.#} {1, date}"; // OK
+    f = "{0, number} {1}"; // OK, GENERAL is weaker (less restrictive) than DATE
+    f = "{0} {1, date}"; // OK, GENERAL is weaker (less restrictive) than NUMBER
+    // :: warning: (i18nformat.missing.arguments)
+    f = "{0, number}"; // warning: last argument is ignored
+    // :: warning: (i18nformat.missing.arguments)
+    f = "{0}"; // warning: last argument is ignored
+    // :: warning: (i18nformat.missing.arguments)
+    f = flag ? "{0, number} {1}" : "{0, number}";
+
+    if (flag) {
+      f = "{0, number} {1}";
+    } else {
+      // :: warning: (i18nformat.missing.arguments)
+      f = "{0, number}";
+    }
+    @I18nFormat({NUMBER, DATE}) String f2 = f;
+
+    // :: error: (assignment)
+    f = "{0, number} {1, number}"; // error: NUMBER is stronger (more restrictive) than DATE
+    // :: error: (i18nformat.excess.arguments) :: error: (assignment)
+    f = "{0} {1} {2}"; // error: too many arguments
+  }
+}
diff --git a/checker/tests/i18n-formatter/Syntax.java b/checker/tests/i18n-formatter/Syntax.java
new file mode 100644
index 0000000..8665aef
--- /dev/null
+++ b/checker/tests/i18n-formatter/Syntax.java
@@ -0,0 +1,94 @@
+import java.text.MessageFormat;
+import java.util.Date;
+
+public class Syntax {
+
+  // Test 2.1.1: Missing '}' at end of message format (Unmatched braces in the pattern)
+  public static void unmatchedBraces() {
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{0, number", new Date(12));
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{0}{", 1);
+
+    // good
+    // :: warning: (i18nformat.excess.arguments)
+    MessageFormat.format("'{0{}", 1);
+    // :: warning: (i18nformat.excess.arguments)
+    MessageFormat.format("'{0{}'", 1);
+  }
+
+  // Test 2.1.2.1: The argument number needs to be an integer
+  public static void integerRequired() {
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{{0}}", 1);
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{0.2}", 1);
+
+    // good
+    // :: warning: (i18nformat.excess.arguments)
+    MessageFormat.format("'{{0}}'", 1);
+  }
+
+  // Test 2.1.2.2: The argument number can't be negative
+  public static void nonNegativeRequired() {
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{-1, number}", 1);
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{-123}", 1);
+
+    // good
+    MessageFormat.format("{0}", 1);
+  }
+
+  // Test 2.1.3: Format Style required for choice format
+  public static void formatStyleRequired() {
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{0, choice}", 1);
+
+    // good
+    MessageFormat.format("{0, choice, 0#zero}", 1);
+  }
+
+  // Test 2.1.4: Wrong format Style
+  public static void wrongFormatStyle() {
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{0, time, number}", 1);
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{0, number, y.m.d}", 1);
+
+    // good
+    MessageFormat.format("{0, time, short}", 1);
+    MessageFormat.format("{0, number, currency}", 1);
+  }
+
+  // Test 2.1.5: Unknown format type
+  public static void unknownFormatType() {
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{0, general}", 1);
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{0, fool}", 1);
+
+    // good
+    MessageFormat.format("{0}", 1);
+    MessageFormat.format("{0, time}", 1);
+    MessageFormat.format("{0, date}", 1);
+    MessageFormat.format("{0, number}", 1);
+    MessageFormat.format("{0, daTe}", 1);
+    MessageFormat.format("{0, NUMBER}", 1);
+  }
+
+  // Test 2.1.6: Invalid Subformat Pattern
+  public static void invalidSubformatPattern() {
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{0, number, #.#.#}", 1);
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{0, date, y.m.d.x}", new Date());
+    // :: error: (i18nformat.string)
+    MessageFormat.format("{0, choice, 0##zero}", 0);
+
+    // good
+    MessageFormat.format("{0, number, #.#}", 1);
+    MessageFormat.format("{0, date, y.m.d}", new Date());
+    MessageFormat.format("{0, choice, 0>zero}", 0);
+  }
+}
diff --git a/checker/tests/i18n-unchecked-defaults/TestUncheckedByteCode.java b/checker/tests/i18n-unchecked-defaults/TestUncheckedByteCode.java
new file mode 100644
index 0000000..1b84161
--- /dev/null
+++ b/checker/tests/i18n-unchecked-defaults/TestUncheckedByteCode.java
@@ -0,0 +1,18 @@
+import org.checkerframework.framework.testchecker.lib.UncheckedByteCode;
+
+public class TestUncheckedByteCode {
+  Object field;
+
+  void test(UncheckedByteCode<Object> param, Integer i) {
+    field = param.getCT();
+    field = param.getInt(1);
+    field = param.getInteger(i);
+    field = param.identity("hello");
+
+    // String and Object are relevant types and must be annotated in bytecode
+    // :: error: (argument)
+    field = param.getObject(new Object());
+    // :: error: (argument)
+    field = param.getString("hello");
+  }
+}
diff --git a/checker/tests/i18n/Issue2163FinalI18n.java b/checker/tests/i18n/Issue2163FinalI18n.java
new file mode 100644
index 0000000..991f17e
--- /dev/null
+++ b/checker/tests/i18n/Issue2163FinalI18n.java
@@ -0,0 +1,67 @@
+import org.checkerframework.checker.i18n.qual.*;
+
+// @C <: @B <: @A
+
+// Testing Rule 1 (constructor declaration type <: class type)
+@UnknownLocalizableKey class Issue2163FinalAA {
+  @UnknownLocalizableKey Issue2163FinalAA() {}
+}
+
+@UnknownLocalizableKey class Issue2163FinalAB {
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  @LocalizableKey Issue2163FinalAB() {}
+}
+
+@UnknownLocalizableKey class Issue2163FinalAC {
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  @LocalizableKeyBottom Issue2163FinalAC() {}
+}
+
+@LocalizableKey class Issue2163FinalBA {
+  // :: error: (annotations.on.use)
+  @UnknownLocalizableKey Issue2163FinalBA() {}
+}
+
+@LocalizableKey class Issue2163FinalBB {
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  @LocalizableKey Issue2163FinalBB() {}
+}
+
+@LocalizableKey class Issue2163FinalBC {
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  @LocalizableKeyBottom Issue2163FinalBC() {}
+}
+
+@LocalizableKeyBottom class Issue2163FinalCA {
+  // :: error: (annotations.on.use)
+  @UnknownLocalizableKey Issue2163FinalCA() {}
+}
+
+@LocalizableKeyBottom class Issue2163FinalCB {
+  // :: error: (annotations.on.use) :: warning: (inconsistent.constructor.type) ::
+  // error: (super.invocation)
+  @LocalizableKey Issue2163FinalCB() {}
+}
+
+@LocalizableKeyBottom class Issue2163FinalCC {
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  @LocalizableKeyBottom Issue2163FinalCC() {}
+}
+
+// Testing Rule 2 (Issue type cast warning if constuctor declaration type <: invocation type)
+@SuppressWarnings("anno.on.irrelevant")
+class Issue2163FinalAAClient {
+  void test() {
+    new @UnknownLocalizableKey Issue2163FinalAA();
+    // :: warning: (cast.unsafe.constructor.invocation)
+    new @LocalizableKey Issue2163FinalAA();
+    // :: warning: (cast.unsafe.constructor.invocation)
+    new @LocalizableKeyBottom Issue2163FinalAA();
+  }
+}
+
+// Testing Default
+@SuppressWarnings("anno.on.irrelevant")
+class Issue2163FinalBCClient {
+  @LocalizableKeyBottom Issue2163FinalBC obj = new Issue2163FinalBC();
+}
diff --git a/checker/tests/i18n/Issue2186.java b/checker/tests/i18n/Issue2186.java
new file mode 100644
index 0000000..844a02a
--- /dev/null
+++ b/checker/tests/i18n/Issue2186.java
@@ -0,0 +1,25 @@
+// Test case for issue #2186
+// https://github.com/typetools/checker-framework/issues/2186
+
+import java.util.ArrayList;
+import org.checkerframework.checker.i18n.qual.*;
+
+@SuppressWarnings("anno.on.irrelevant")
+@LocalizableKey class Issue2186 {
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  Issue2186() {}
+
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  @LocalizableKeyBottom Issue2186(int x) {}
+
+  void test() {
+    @LocalizableKey Issue2186 obj = new Issue2186();
+    @LocalizableKeyBottom Issue2186 obj1 = new Issue2186(9);
+  }
+
+  void testDiamond() {
+    @LocalizableKeyBottom ArrayList<@LocalizableKeyBottom String> list =
+        // :: warning: (cast.unsafe.constructor.invocation)
+        new @LocalizableKeyBottom ArrayList<@LocalizableKeyBottom String>();
+  }
+}
diff --git a/checker/tests/i18n/Issue2264.java b/checker/tests/i18n/Issue2264.java
new file mode 100644
index 0000000..81a82ef
--- /dev/null
+++ b/checker/tests/i18n/Issue2264.java
@@ -0,0 +1,27 @@
+// Test case for issue 2264
+// https://github.com/typetools/checker-framework/issues/2264
+
+import org.checkerframework.checker.i18n.qual.LocalizableKey;
+import org.checkerframework.checker.i18n.qual.UnknownLocalizableKey;
+
+public class Issue2264 extends SuperClass {
+  // :: warning: (inconsistent.constructor.type)
+  @LocalizableKey Issue2264() {
+    // :: error: (super.invocation)
+    super(9);
+  }
+}
+
+class ImplicitSuperCall {
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  @LocalizableKey ImplicitSuperCall() {}
+}
+
+class SuperClass {
+  @UnknownLocalizableKey SuperClass(int x) {}
+}
+
+@LocalizableKey class TestClass {
+  // :: error: (annotations.on.use)
+  @UnknownLocalizableKey TestClass() {}
+}
diff --git a/checker/tests/i18n/LocalizedMessage.java b/checker/tests/i18n/LocalizedMessage.java
new file mode 100644
index 0000000..737d22c
--- /dev/null
+++ b/checker/tests/i18n/LocalizedMessage.java
@@ -0,0 +1,59 @@
+import org.checkerframework.checker.i18n.qual.Localized;
+
+public class LocalizedMessage {
+  @Localized String localize(String s) {
+    throw new RuntimeException();
+  }
+
+  void localized(@Localized String s) {}
+
+  void any(String s) {}
+
+  void stringLiteral() {
+    // :: error: (argument)
+    localized("ldskjfldj"); // error
+    any("lksjdflkjdf");
+  }
+
+  void stringRef(String ref) {
+    // :: error: (argument)
+    localized(ref); // error
+    any(ref);
+  }
+
+  void localizedRef(@Localized String ref) {
+    localized(ref);
+    any(ref);
+  }
+
+  void methodRet(String ref) {
+    localized(localize(ref));
+    localized(localize(ref));
+  }
+
+  void concatenation(@Localized String s1, String s2) {
+    // :: error: (argument)
+    localized(s1 + s1); // error
+    // :: error: (argument) :: error: (compound.assignment)
+    localized(s1 += s1); // error
+    // :: error: (argument)
+    localized(s1 + "m"); // error
+    // :: error: (argument)
+    localized(s1 + s2); // error
+
+    // :: error: (argument)
+    localized(s2 + s1); // error
+    // :: error: (argument)
+    localized(s2 + "m"); // error
+    // :: error: (argument)
+    localized(s2 + s2); // error
+
+    any(s1 + s1);
+    any(s1 + "m");
+    any(s1 + s2);
+
+    any(s2 + s1);
+    any(s2 + "m");
+    any(s2 + s2);
+  }
+}
diff --git a/checker/tests/index/AndExample.java b/checker/tests/index/AndExample.java
new file mode 100644
index 0000000..580b3c8
--- /dev/null
+++ b/checker/tests/index/AndExample.java
@@ -0,0 +1,16 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+
+public class AndExample {
+
+  @SuppressWarnings("index") // forward reference to field iYearInfoCache
+  private static final @IndexOrHigh("iYearInfoCache") int CACHE_SIZE = 1 << 10;
+
+  private static final @IndexFor("iYearInfoCache") int CACHE_MASK = CACHE_SIZE - 1;
+
+  private static final String[] iYearInfoCache = new String[CACHE_SIZE];
+
+  private String getYearInfo(int year) {
+    return iYearInfoCache[year & CACHE_MASK];
+  }
+}
diff --git a/checker/tests/index/AnnotatedJDKTest.java b/checker/tests/index/AnnotatedJDKTest.java
new file mode 100644
index 0000000..c8008a1
--- /dev/null
+++ b/checker/tests/index/AnnotatedJDKTest.java
@@ -0,0 +1,10 @@
+import java.io.PrintWriter;
+
+public class AnnotatedJDKTest {
+
+  public void printWriterWrite(PrintWriter writer) {
+    writer.write(-1);
+
+    writer.write(8);
+  }
+}
diff --git a/checker/tests/index/ArrayAsList.java b/checker/tests/index/ArrayAsList.java
new file mode 100644
index 0000000..5b00764
--- /dev/null
+++ b/checker/tests/index/ArrayAsList.java
@@ -0,0 +1,26 @@
+import java.util.Arrays;
+import java.util.List;
+import org.checkerframework.common.value.qual.MinLen;
+
+// @skip-test until we bring list support back
+
+public class ArrayAsList {
+
+  public static void toList(Integer @MinLen(10) [] arg) {
+    @MinLen(10) List<Integer> list = Arrays.asList(arg);
+    System.out.println("Integer: " + list.size());
+  }
+
+  public static void toList2(int @MinLen(10) [] arg2) {
+    // :: error: (assignment)
+    @MinLen(10) List list = Arrays.asList(arg2);
+    System.out.println("int: " + list.size());
+
+    @MinLen(1) List list2 = Arrays.asList(arg2);
+  }
+
+  public static void toList3() {
+    @MinLen(4) List<Integer> list = Arrays.asList(1, 2, 3, 6);
+    System.out.println("args: " + list.size());
+  }
+}
diff --git a/checker/tests/index/ArrayAssignmentSameLen.java b/checker/tests/index/ArrayAssignmentSameLen.java
new file mode 100644
index 0000000..854ceb3
--- /dev/null
+++ b/checker/tests/index/ArrayAssignmentSameLen.java
@@ -0,0 +1,57 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class ArrayAssignmentSameLen {
+
+  private final int[] i_array;
+  private final @IndexFor("i_array") int i_index;
+
+  ArrayAssignmentSameLen(int[] array, @IndexFor("#1") int index) {
+    i_array = array;
+    i_index = index;
+  }
+
+  void test1(int[] a, int[] b, @LTEqLengthOf("#1") int index) {
+    int[] array = a;
+    @LTLengthOf(
+        value = {"array", "b"},
+        offset = {"0", "-3"})
+    // :: error: (assignment)
+    int i = index;
+  }
+
+  void test2(int[] a, int[] b, @LTLengthOf("#1") int i) {
+    int[] c = a;
+    // :: error: (assignment)
+    @LTLengthOf(value = {"c", "b"}) int x = i;
+    @LTLengthOf("c") int y = i;
+  }
+
+  void test3(int[] a, @LTLengthOf("#1") int i, @NonNegative int x) {
+    int[] c1 = a;
+    // See useTest3 for an example of why this assignment should fail.
+    @LTLengthOf(
+        value = {"c1", "c1"},
+        offset = {"0", "x"})
+    // :: error: (assignment)
+    int z = i;
+  }
+
+  void test4(
+      int[] a,
+      @LTLengthOf(
+              value = {"#1", "#1"},
+              offset = {"0", "#3"})
+          int i,
+      @NonNegative int x) {
+    int[] c1 = a;
+    @LTLengthOf(
+        value = {"c1", "c1"},
+        offset = {"0", "x"})
+    int z = i;
+  }
+
+  void useTest3() {
+    int[] a = {1, 3};
+    test3(a, 0, 10);
+  }
+}
diff --git a/checker/tests/index/ArrayAssignmentSameLenComplex.java b/checker/tests/index/ArrayAssignmentSameLenComplex.java
new file mode 100644
index 0000000..8f95666
--- /dev/null
+++ b/checker/tests/index/ArrayAssignmentSameLenComplex.java
@@ -0,0 +1,23 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.NonNegative;
+
+// @skip-test until #127 is resolved.
+
+public class ArrayAssignmentSameLenComplex {
+
+  static class Partial {
+    private final int[] iValues;
+
+    Partial(@NonNegative int n) {
+      iValues = new int[n];
+    }
+  }
+
+  private final Partial iBase;
+  private final @IndexFor("iBase.iValues") int iFieldIndex;
+
+  ArrayAssignmentSameLenComplex(Partial partial, @IndexFor("#1.iValues") int fieldIndex) {
+    iBase = partial;
+    iFieldIndex = fieldIndex;
+  }
+}
diff --git a/checker/tests/index/ArrayConstructionPositiveLength.java b/checker/tests/index/ArrayConstructionPositiveLength.java
new file mode 100644
index 0000000..c6ee6dc
--- /dev/null
+++ b/checker/tests/index/ArrayConstructionPositiveLength.java
@@ -0,0 +1,12 @@
+// Test case for issue #66:
+// https://github.com/kelloggm/checker-framework/issues/66
+
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.common.value.qual.*;
+
+public class ArrayConstructionPositiveLength {
+
+  public void makeArray(@Positive int max_values) {
+    String @MinLen(1) [] a = new String[max_values];
+  }
+}
diff --git a/checker/tests/index/ArrayCopy.java b/checker/tests/index/ArrayCopy.java
new file mode 100644
index 0000000..b73f95c
--- /dev/null
+++ b/checker/tests/index/ArrayCopy.java
@@ -0,0 +1,10 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+public class ArrayCopy {
+
+  void copy(int @MinLen(1) [] nums) {
+    int[] nums_copy = new int[nums.length];
+    System.arraycopy(nums, 0, nums_copy, 0, nums.length);
+    nums = nums_copy;
+  }
+}
diff --git a/checker/tests/index/ArrayCreationChecks.java b/checker/tests/index/ArrayCreationChecks.java
new file mode 100644
index 0000000..636c723
--- /dev/null
+++ b/checker/tests/index/ArrayCreationChecks.java
@@ -0,0 +1,50 @@
+// This test case is for issue 44: https://github.com/kelloggm/checker-framework/issues/44
+
+import org.checkerframework.checker.index.qual.*;
+
+public class ArrayCreationChecks {
+
+  void test1(@Positive int x, @Positive int y) {
+    int[] newArray = new int[x + y];
+    @IndexFor("newArray") int i = x;
+    @IndexFor("newArray") int j = y;
+  }
+
+  void test2(@NonNegative int x, @Positive int y) {
+    int[] newArray = new int[x + y];
+    @IndexFor("newArray") int i = x;
+    @IndexOrHigh("newArray") int j = y;
+  }
+
+  void test3(@NonNegative int x, @NonNegative int y) {
+    int[] newArray = new int[x + y];
+    @IndexOrHigh("newArray") int i = x;
+    @IndexOrHigh("newArray") int j = y;
+  }
+
+  void test4(@GTENegativeOne int x, @NonNegative int y) {
+    // :: error: (array.length.negative)
+    int[] newArray = new int[x + y];
+    @LTEqLengthOf("newArray") int i = x;
+    // :: error: (assignment)
+    @IndexOrHigh("newArray") int j = y;
+  }
+
+  void test5(@GTENegativeOne int x, @GTENegativeOne int y) {
+    // :: error: (array.length.negative)
+    int[] newArray = new int[x + y];
+    // :: error: (assignment)
+    @IndexOrHigh("newArray") int i = x;
+    // :: error: (assignment)
+    @IndexOrHigh("newArray") int j = y;
+  }
+
+  void test6(int x, int y) {
+    // :: error: (array.length.negative)
+    int[] newArray = new int[x + y];
+    // :: error: (assignment)
+    @IndexFor("newArray") int i = x;
+    // :: error: (assignment)
+    @IndexOrHigh("newArray") int j = y;
+  }
+}
diff --git a/checker/tests/index/ArrayCreationParam.java b/checker/tests/index/ArrayCreationParam.java
new file mode 100644
index 0000000..188cc21
--- /dev/null
+++ b/checker/tests/index/ArrayCreationParam.java
@@ -0,0 +1,17 @@
+// Test case for issue 93: https://github.com/kelloggm/checker-framework/issues/93
+
+import org.checkerframework.checker.index.qual.*;
+
+public class ArrayCreationParam {
+
+  public static void m1() {
+    int n = 5;
+    int[] a = new int[n + 1];
+    // Index Checker correctly issues no warning on the lines below
+    @LTLengthOf("a") int j = n;
+    @IndexFor("a") int k = n;
+    for (int i = 1; i <= n; i++) {
+      int x = a[i];
+    }
+  }
+}
diff --git a/checker/tests/index/ArrayCreationTest.java b/checker/tests/index/ArrayCreationTest.java
new file mode 100644
index 0000000..f526c0f
--- /dev/null
+++ b/checker/tests/index/ArrayCreationTest.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.index.qual.SameLen;
+
+// Check that creating an array with the length of another
+// makes both @SameLen of each other.
+
+public class ArrayCreationTest {
+  void test(int[] a, int[] d) {
+    int[] b = new int[a.length];
+    int @SameLen({"a", "b"}) [] c = b;
+  }
+}
diff --git a/checker/tests/index/ArrayIntro.java b/checker/tests/index/ArrayIntro.java
new file mode 100644
index 0000000..4611c4f
--- /dev/null
+++ b/checker/tests/index/ArrayIntro.java
@@ -0,0 +1,19 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+@SuppressWarnings("lowerbound")
+public class ArrayIntro {
+  void test() {
+    int @MinLen(5) [] arr = new int[5];
+    int a = 9;
+    a += 5;
+    a -= 2;
+    int @MinLen(12) [] arr1 = new int[a];
+    int @MinLen(3) [] arr2 = {1, 2, 3};
+    // :: error: (assignment)
+    int @MinLen(4) [] arr3 = {4, 5, 6};
+    // :: error: (assignment)
+    int @MinLen(7) [] arr4 = new int[4];
+    // :: error: (assignment)
+    int @MinLen(16) [] arr5 = new int[a];
+  }
+}
diff --git a/checker/tests/index/ArrayIntroWithCast.java b/checker/tests/index/ArrayIntroWithCast.java
new file mode 100644
index 0000000..b55a4bc
--- /dev/null
+++ b/checker/tests/index/ArrayIntroWithCast.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class ArrayIntroWithCast<T> {
+
+  void test(String[] a, String[] b) {
+    Object result = new Object[a.length + b.length];
+    System.arraycopy(a, 0, result, 0, a.length);
+  }
+
+  void test2(String[] a, String[] b) {
+    @SuppressWarnings("unchecked")
+    T[] result = (T[]) new Object[a.length + b.length];
+    System.arraycopy(a, 0, result, 0, a.length);
+  }
+}
diff --git a/checker/tests/index/ArrayLenTest.java b/checker/tests/index/ArrayLenTest.java
new file mode 100644
index 0000000..932ed33
--- /dev/null
+++ b/checker/tests/index/ArrayLenTest.java
@@ -0,0 +1,13 @@
+import org.checkerframework.common.value.qual.*;
+
+public class ArrayLenTest {
+  public static String esc_quantify(String @ArrayLen({1, 2}) ... vars) {
+    if (vars.length == 1) {
+      return vars[0];
+    } else {
+      @IntVal({2}) int i = vars.length;
+      String @ArrayLen({2}) [] a = vars;
+      return vars[0] + vars[1];
+    }
+  }
+}
diff --git a/checker/tests/index/ArrayLength.java b/checker/tests/index/ArrayLength.java
new file mode 100644
index 0000000..7dece22
--- /dev/null
+++ b/checker/tests/index/ArrayLength.java
@@ -0,0 +1,8 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+
+public class ArrayLength {
+  void test() {
+    int[] arr = {1, 2, 3};
+    @LTEqLengthOf({"arr"}) int a = arr.length;
+  }
+}
diff --git a/checker/tests/index/ArrayLength2.java b/checker/tests/index/ArrayLength2.java
new file mode 100644
index 0000000..5fece54
--- /dev/null
+++ b/checker/tests/index/ArrayLength2.java
@@ -0,0 +1,13 @@
+// Test case for issue #14:
+// https://github.com/kelloggm/checker-framework/issues/14
+
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class ArrayLength2 {
+  public static void main(String[] args) {
+    int N = 8;
+    int @MinLen(8) [] Grid = new int[N];
+    @LTLengthOf("Grid") int i = 0;
+  }
+}
diff --git a/checker/tests/index/ArrayLength3.java b/checker/tests/index/ArrayLength3.java
new file mode 100644
index 0000000..4299e5b
--- /dev/null
+++ b/checker/tests/index/ArrayLength3.java
@@ -0,0 +1,16 @@
+// Test case for issue #14:
+// https://github.com/kelloggm/checker-framework/issues/14
+
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.common.value.qual.ArrayLen;
+
+public class ArrayLength3 {
+  String getFirst(String @ArrayLen(2) [] sa) {
+    return sa[0];
+  }
+
+  void m() {
+    Integer[] a = new Integer[10];
+    @LTLengthOf("a") int i = 5;
+  }
+}
diff --git a/checker/tests/index/ArrayLengthEquality.java b/checker/tests/index/ArrayLengthEquality.java
new file mode 100644
index 0000000..0d20938
--- /dev/null
+++ b/checker/tests/index/ArrayLengthEquality.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.index.qual.SameLen;
+
+public class ArrayLengthEquality {
+  void test(int[] a, int[] b) {
+    if (a.length == b.length) {
+      int @SameLen({"a", "b"}) [] c = a;
+      int @SameLen({"a", "b"}) [] d = b;
+    }
+    if (a.length != b.length) {
+      // Do nothing.
+      int x = 0;
+    } else {
+      int @SameLen({"a", "b"}) [] e = a;
+      int @SameLen({"a", "b"}) [] f = b;
+    }
+  }
+}
diff --git a/checker/tests/index/ArrayLengthLBC.java b/checker/tests/index/ArrayLengthLBC.java
new file mode 100644
index 0000000..84caa60
--- /dev/null
+++ b/checker/tests/index/ArrayLengthLBC.java
@@ -0,0 +1,13 @@
+import java.util.Date;
+
+public class ArrayLengthLBC {
+
+  public static Date[] add_date(Date[] dates, Date new_date) {
+    Date[] new_dates = new Date[dates.length + 1];
+    System.arraycopy(dates, 0, new_dates, 0, dates.length);
+    new_dates[dates.length] = new_date;
+    Date[] new_dates_cast = new_dates;
+    return (new_dates_cast);
+  }
+}
+// a comment
diff --git a/checker/tests/index/ArrayNull.java b/checker/tests/index/ArrayNull.java
new file mode 100644
index 0000000..deb5739
--- /dev/null
+++ b/checker/tests/index/ArrayNull.java
@@ -0,0 +1,4 @@
+public class ArrayNull {
+
+  Object[][] a = new Object[][] {new Object[] {null}, null};
+}
diff --git a/checker/tests/index/ArrayWrapper.java b/checker/tests/index/ArrayWrapper.java
new file mode 100644
index 0000000..ccc3187
--- /dev/null
+++ b/checker/tests/index/ArrayWrapper.java
@@ -0,0 +1,50 @@
+// Test case for https://github.com/kelloggm/checker-framework/issues/154
+// This class wraps an array, but doesn't expose the array in its public interface. This test
+// ensures that indexes for this new collection can be annotated as if the collection were an array.
+
+// Note that there is a copy of this code in the manual in index-checker.tex. If this code is
+// updated, you MUST update that copy, as well.
+
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LengthOf;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.SameLen;
+
+/** ArrayWrapper is a fixed-size generic collection. */
+public class ArrayWrapper<T> {
+  private final Object @SameLen("this") [] delegate;
+
+  @SuppressWarnings("index") // constructor creates object of size @SameLen(this) by definition
+  ArrayWrapper(@NonNegative int size) {
+    delegate = new Object[size];
+  }
+
+  public @LengthOf("this") int size() {
+    return delegate.length;
+  }
+
+  public void set(@IndexFor("this") int index, T obj) {
+    delegate[index] = obj;
+  }
+
+  @SuppressWarnings("unchecked") // required for normal Java compilation due to unchecked cast
+  public T get(@IndexFor("this") int index) {
+    return (T) delegate[index];
+  }
+
+  public static void clearIndex1(ArrayWrapper<? extends Object> a, @IndexFor("#1") int i) {
+    a.set(i, null);
+  }
+
+  public static void clearIndex2(ArrayWrapper<? extends Object> a, int i) {
+    if (0 <= i && i < a.size()) {
+      a.set(i, null);
+    }
+  }
+
+  public static void clearIndex3(ArrayWrapper<? extends Object> a, @NonNegative int i) {
+    if (i < a.size()) {
+      a.set(i, null);
+    }
+  }
+}
diff --git a/checker/tests/index/ArraysSort.java b/checker/tests/index/ArraysSort.java
new file mode 100644
index 0000000..a4220bc
--- /dev/null
+++ b/checker/tests/index/ArraysSort.java
@@ -0,0 +1,12 @@
+import java.util.Arrays;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class ArraysSort {
+
+  void sortInt(int @MinLen(10) [] nums) {
+    // Checks the correct handling of the toIndex parameter
+    Arrays.sort(nums, 0, 10);
+    // :: error: (argument)
+    Arrays.sort(nums, 0, 11);
+  }
+}
diff --git a/checker/tests/index/BasicSubsequence.java b/checker/tests/index/BasicSubsequence.java
new file mode 100644
index 0000000..2c34408
--- /dev/null
+++ b/checker/tests/index/BasicSubsequence.java
@@ -0,0 +1,47 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class BasicSubsequence {
+  // :: error: not.final
+  @HasSubsequence(subsequence = "this", from = "this.x", to = "this.y")
+  int[] b;
+
+  int x;
+  int y;
+
+  void test2(@NonNegative @LessThan("y + 1") int x1, int[] a) {
+    x = x1;
+    // :: error: to.not.ltel
+    b = a;
+  }
+
+  void test3(@NonNegative @LessThan("y") int x1, int[] a) {
+    x = x1;
+    // :: error: to.not.ltel
+    b = a;
+  }
+
+  void test4(@NonNegative int x1, int[] a) {
+    x = x1;
+    // :: error: from.gt.to :: error: to.not.ltel
+    b = a;
+  }
+
+  void test5(@GTENegativeOne @LessThan("y + 1") int x1, int[] a) {
+    x = x1;
+    // :: error: from.not.nonnegative :: error: to.not.ltel
+    b = a;
+  }
+
+  void test6(@GTENegativeOne int x1, int[] a) {
+    x = x1;
+    // :: error: from.not.nonnegative :: error: to.not.ltel :: error: from.gt.to
+    b = a;
+  }
+
+  void test7(@IndexFor("this") @LessThan("y") int x1, @IndexOrHigh("this") int y1, int[] a) {
+    x = x1;
+    y = y1;
+    // :: warning: which.subsequence
+    b = a;
+  }
+}
diff --git a/checker/tests/index/BasicSubsequence2.java b/checker/tests/index/BasicSubsequence2.java
new file mode 100644
index 0000000..6ac9ccc
--- /dev/null
+++ b/checker/tests/index/BasicSubsequence2.java
@@ -0,0 +1,36 @@
+import org.checkerframework.checker.index.qual.HasSubsequence;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+
+public class BasicSubsequence2 {
+  @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end")
+  int[] array;
+
+  @HasSubsequence(subsequence = "this", from = "start", to = "end")
+  int[] array2;
+
+  final @IndexFor("array") int start;
+
+  final @IndexOrHigh("array") int end;
+
+  private BasicSubsequence2(@IndexFor("array") int s, @IndexOrHigh("array") int e) {
+    start = s;
+    end = e;
+  }
+
+  void testStartIndex(@IndexFor("this") int x) {
+    @IndexFor("array") int y = x + start;
+  }
+
+  void testViewpointAdaption(@IndexFor("this") int x) {
+    @IndexFor("array2") int y = x + start;
+  }
+
+  void testArrayAccess(@IndexFor("this") int x) {
+    int y = array[x + start];
+  }
+
+  void testCommutativity(@IndexFor("this") int x) {
+    @IndexFor("array") int y = start + x;
+  }
+}
diff --git a/checker/tests/index/BasicSubsequence3.java b/checker/tests/index/BasicSubsequence3.java
new file mode 100644
index 0000000..b992570
--- /dev/null
+++ b/checker/tests/index/BasicSubsequence3.java
@@ -0,0 +1,30 @@
+import org.checkerframework.checker.index.qual.HasSubsequence;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.LessThan;
+
+@SuppressWarnings("lowerbound")
+public class BasicSubsequence3 {
+  @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end")
+  int[] array;
+
+  @HasSubsequence(subsequence = "this", from = "start", to = "end")
+  int[] array2;
+
+  final @IndexFor("array") int start;
+
+  final @IndexOrHigh("array") int end;
+
+  private BasicSubsequence3(@IndexFor("array") int s, @IndexOrHigh("array") int e) {
+    start = s;
+    end = e;
+  }
+
+  void testStartIndex(@IndexFor("array") @LessThan("this.end") int x) {
+    @IndexFor("this") int y = x - start;
+  }
+
+  void testViewpointAdaption(@IndexFor("array2") @LessThan("end") int x) {
+    @IndexFor("this") int y = x - start;
+  }
+}
diff --git a/checker/tests/index/BigBinaryExpr.java b/checker/tests/index/BigBinaryExpr.java
new file mode 100644
index 0000000..039f7f2
--- /dev/null
+++ b/checker/tests/index/BigBinaryExpr.java
@@ -0,0 +1,111 @@
+public class BigBinaryExpr {
+  void test() {
+    int i0 = 163;
+    int i1 = 153;
+    int i2 = 75;
+    int i3 = -72;
+    int i4 = 61;
+    int i5 = 7;
+    int i6 = 83;
+    int i7 = -36;
+    int i8 = -90;
+    int i9 = -93;
+    int i10 = 187;
+    int i11 = -76;
+    int i12 = -16;
+    int i13 = -99;
+    int i14 = 113;
+    int i15 = 72;
+    int i16 = 58;
+    int i17 = -97;
+    int i18 = 115;
+    int i19 = -85;
+    int i20 = 156;
+    int i21 = -10;
+    int i22 = -85;
+    int i23 = 81;
+    int i24 = 63;
+    int i25 = -49;
+    int i26 = 158;
+    int i27 = 158;
+    int i28 = 25;
+    int i29 = 136;
+    int i30 = -90;
+    int i31 = 115;
+    int i32 = 179;
+    int i33 = 11;
+    int i34 = -100;
+    int i35 = 70;
+    int i36 = -46;
+    int i37 = -56;
+    int i38 = 108;
+    int i39 = -41;
+    int i40 = 124;
+    int i41 = -88;
+    int i42 = 54;
+    int i43 = 117;
+    int i44 = -92;
+    int i45 = 7;
+    int i46 = -94;
+    int i47 = 162;
+    int i48 = -34;
+    int i49 = 104;
+    int i50 = 111;
+    int i51 = -16;
+    int i52 = 197;
+    int i53 = -8;
+    int i54 = 101;
+    int i55 = 96;
+    int i56 = 132;
+    int i57 = -36;
+    int i58 = 148;
+    int i59 = 43;
+    int i60 = -59;
+    int i61 = 150;
+    int i62 = 48;
+    int i63 = 130;
+    int i64 = 74;
+    int i65 = -1;
+    int i66 = 79;
+    int i67 = 109;
+    int i68 = -70;
+    int i69 = 111;
+    int i70 = 78;
+    int i71 = 155;
+    int i72 = 176;
+    int i73 = 80;
+    int i74 = 181;
+    int i75 = 41;
+    int i76 = -85;
+    int i77 = 189;
+    int i78 = 97;
+    int i79 = 139;
+    int i80 = 9;
+    int i81 = 42;
+    int i82 = -50;
+    int i83 = 82;
+    int i84 = -70;
+    int i85 = 162;
+    int i86 = -20;
+    int i87 = 52;
+    int i88 = -94;
+    int i89 = 133;
+    int i90 = 136;
+    int i91 = 129;
+    int i92 = -55;
+    int i93 = 153;
+    int i94 = 6;
+    int i95 = -18;
+    int i96 = 132;
+    int i97 = 45;
+    int i98 = 120;
+    int i99 = 60;
+    // int result = i0 +i1 +i2 +i3 +i4 +i5 +i6 +i7 +i8 +i9 +i10 +i11 +i12 +i13 +i14 +i15 +i16
+    // +i17 +i18 +i19 +i20 +i21 +i22 +i23 +i24 +i25 +i26 +i27 +i28 +i29 +i30 +i31 +i32 +i33 +i34
+    // +i35 +i36 +i37 +i38 +i39 +i40 +i41 +i42 +i43 +i44 +i45 +i46 +i47 +i48 +i49 +i50 +i51 +i52
+    // +i53 +i54 +i55 +i56 +i57 +i58 +i59 +i60 +i61 +i62 +i63 +i64 +i65 +i66 +i67 +i68 +i69 +i70
+    // +i71 +i72 +i73 +i74 +i75 +i76 +i77 +i78 +i79 +i80 +i81 +i82 +i83 +i84 +i85 +i86 +i87 +i88
+    // +i89 +i90 +i91 +i92 +i93 +i94 +i95 +i96 +i97 +i98 +i99;
+  }
+}
+// a comment
diff --git a/checker/tests/index/BinarySearchTest.java b/checker/tests/index/BinarySearchTest.java
new file mode 100644
index 0000000..df6ea8b
--- /dev/null
+++ b/checker/tests/index/BinarySearchTest.java
@@ -0,0 +1,40 @@
+import java.util.Arrays;
+import org.checkerframework.checker.index.qual.*;
+
+public class BinarySearchTest {
+
+  private final long @SameLen("iNameKeys") [] iTransitions;
+  private final String @SameLen("iTransitions") [] iNameKeys;
+
+  private BinarySearchTest(
+      long @SameLen("iNameKeys") [] transitions, String @SameLen("iTransitions") [] nameKeys) {
+    iTransitions = transitions;
+    iNameKeys = nameKeys;
+  }
+
+  public String getNameKey(long instant) {
+    long[] transitions = iTransitions;
+    int i = Arrays.binarySearch(transitions, instant);
+    if (i >= 0) {
+      return iNameKeys[i];
+    }
+    i = ~i;
+    if (i > 0) {
+      return iNameKeys[i - 1];
+    }
+    return "";
+  }
+
+  public String getNameKey2(long instant) {
+    long[] transitions = iTransitions;
+    int i = Arrays.binarySearch(transitions, instant);
+    if (i >= 0) {
+      return iNameKeys[i];
+    }
+    i = ~i;
+    if (i < iNameKeys.length) {
+      return iNameKeys[i];
+    }
+    return "";
+  }
+}
diff --git a/checker/tests/index/BinomialTest.java b/checker/tests/index/BinomialTest.java
new file mode 100644
index 0000000..ac264fb
--- /dev/null
+++ b/checker/tests/index/BinomialTest.java
@@ -0,0 +1,73 @@
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.common.value.qual.*;
+
+public class BinomialTest {
+
+  static final long @MinLen(1) [] factorials = {1L, 1L, 1L * 2};
+
+  public static long binomial(
+      @NonNegative @LTLengthOf("BinomialTest.factorials") int n,
+      @NonNegative @LessThan("#1 + 1") int k) {
+    return factorials[k];
+  }
+
+  public static void binomial0(
+      @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) {
+    @LTLengthOf(value = "factorials", offset = "1") int i = k;
+  }
+
+  public static void binomial0Error(
+      @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) {
+    // :: error: (assignment)
+    @LTLengthOf(value = "factorials", offset = "2") int i = k;
+  }
+
+  public static void binomial0Weak(
+      @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) {
+    @LTLengthOf("factorials") int i = k;
+  }
+
+  public static void binomial1(
+      @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 1") int k) {
+    @LTLengthOf("factorials") int i = k;
+  }
+
+  public static void binomial1Error(
+      @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 1") int k) {
+    // :: error: (assignment)
+    @LTLengthOf(value = "factorials", offset = "1") int i = k;
+  }
+
+  public static void binomial2(
+      @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 2") int k) {
+    @LTLengthOf(value = "factorials", offset = "-1") int i = k;
+  }
+
+  public static void binomial2Error(
+      @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 2") int k) {
+    // :: error: (assignment)
+    @LTLengthOf(value = "factorials", offset = "0") int i = k;
+  }
+
+  public static void binomial_1(
+      @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 1") int k) {
+    @LTLengthOf(value = "factorials", offset = "2") int i = k;
+  }
+
+  public static void binomial_1Error(
+      @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 1") int k) {
+    // :: error: (assignment)
+    @LTLengthOf(value = "factorials", offset = "3") int i = k;
+  }
+
+  public static void binomial_2(
+      @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 2") int k) {
+    @LTLengthOf(value = "factorials", offset = "3") int i = k;
+  }
+
+  public static void binomial_2Error(
+      @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 2") int k) {
+    // :: error: (assignment)
+    @LTLengthOf(value = "factorials", offset = "4") int i = k;
+  }
+}
diff --git a/checker/tests/index/BitSetLowerBound.java b/checker/tests/index/BitSetLowerBound.java
new file mode 100644
index 0000000..a47ef7b
--- /dev/null
+++ b/checker/tests/index/BitSetLowerBound.java
@@ -0,0 +1,19 @@
+// Test case for Issue 185:
+// https://github.com/typetools/kelloggm/issues/185
+
+import java.util.BitSet;
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+
+public class BitSetLowerBound {
+
+  private void m(BitSet b) {
+    b.set(b.nextClearBit(0));
+    // next set bit does not have to exist
+    // :: error: (argument)
+    b.clear(b.nextSetBit(0));
+    @GTENegativeOne int i = b.nextSetBit(0);
+
+    @GTENegativeOne int j = b.previousClearBit(-1);
+    @GTENegativeOne int k = b.previousSetBit(-1);
+  }
+}
diff --git a/checker/tests/index/Boilerplate.java b/checker/tests/index/Boilerplate.java
new file mode 100644
index 0000000..9835b69
--- /dev/null
+++ b/checker/tests/index/Boilerplate.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.index.qual.Positive;
+
+public class Boilerplate {
+
+  void test() {
+    // :: error: (assignment)
+    @Positive int a = -1;
+  }
+}
+// a comment
diff --git a/checker/tests/index/BottomValTest.java b/checker/tests/index/BottomValTest.java
new file mode 100644
index 0000000..213ddce
--- /dev/null
+++ b/checker/tests/index/BottomValTest.java
@@ -0,0 +1,16 @@
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.common.value.qual.*;
+
+public class BottomValTest {
+  @NonNegative int foo(@BottomVal int bottom) {
+    return bottom;
+  }
+
+  @Positive int bar(@BottomVal int bottom) {
+    return bottom;
+  }
+
+  @LTLengthOf("#1") int baz(int[] a, @BottomVal int bottom) {
+    return bottom;
+  }
+}
diff --git a/checker/tests/index/CastArray.java b/checker/tests/index/CastArray.java
new file mode 100644
index 0000000..c6c7797
--- /dev/null
+++ b/checker/tests/index/CastArray.java
@@ -0,0 +1,5 @@
+public class CastArray {
+  void test(Object a) {
+    int[] b = (int[]) a;
+  }
+}
diff --git a/checker/tests/index/CharPrintedAsVariable.java b/checker/tests/index/CharPrintedAsVariable.java
new file mode 100644
index 0000000..a143eae
--- /dev/null
+++ b/checker/tests/index/CharPrintedAsVariable.java
@@ -0,0 +1,15 @@
+// Test case for https://github.com/typetools/checker-framework/issues/3167 .
+
+public class CharPrintedAsVariable {
+  void m1(char c) {
+    if (c <= 'A') {
+      int x = (int) c;
+    }
+  }
+
+  void m2(char c) {
+    if (c <= '\377') {
+      int x = (int) c;
+    }
+  }
+}
diff --git a/checker/tests/index/CharSequenceTest.java b/checker/tests/index/CharSequenceTest.java
new file mode 100644
index 0000000..c5164b5
--- /dev/null
+++ b/checker/tests/index/CharSequenceTest.java
@@ -0,0 +1,79 @@
+// Tests suport for index annotations applied to CharSequence and related indices.
+
+import java.io.IOException;
+import java.io.StringWriter;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.common.value.qual.MinLen;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class CharSequenceTest {
+  // Tests that minlen is correctly applied to CharSequence assigned from String, but not
+  // StringBuilder
+  void minLenCharSequence() {
+    @MinLen(10) CharSequence str = "0123456789";
+    // :: error: (assignment)
+    @MinLen(10) CharSequence sb = new StringBuilder("0123456789");
+  }
+  // Tests the subSequence method
+  void testSubSequence() {
+    // Local variable used because of https://github.com/kelloggm/checker-framework/issues/165
+    String str = "0123456789";
+    str.subSequence(5, 8);
+    // :: error: (argument)
+    str.subSequence(5, 13);
+  }
+
+  // Dummy method that takes a CharSequence and its index
+  void sink(CharSequence cs, @IndexOrHigh("#1") int i) {}
+
+  // Tests passing sequences as CharSequence
+  void argumentPassing() {
+    String s = "0123456789";
+    sink(s, 8);
+    StringBuilder sb = new StringBuilder("0123456789");
+    // :: error: (argument)
+    sink(sb, 8);
+  }
+  // Tests forwardning sequences as CharSequence
+  void agumentForwarding(String s, @IndexOrHigh("#1") int i) {
+    sink(s, i);
+  }
+
+  // Tests concatenation of CharSequence and String
+  void concat() {
+    CharSequence a = "a";
+    @StringVal({"nullb", "ab"}) CharSequence ab = a + "b";
+    sink(ab, 2);
+  }
+
+  // Tests that length retrieved from CharSequence can be used as an index
+  void getLength(CharSequence cs, int i) {
+    if (i >= 0 && i < cs.length()) {
+      cs.charAt(i);
+    }
+
+    @IndexOrHigh("cs") int l = cs.length();
+  }
+
+  void testCharAt(CharSequence cs, int i, @IndexFor("#1") int j) {
+    cs.charAt(j);
+    cs.subSequence(j, j);
+    // :: error: (argument)
+    cs.charAt(i);
+    // :: error: (argument)
+    cs.subSequence(i, j);
+  }
+
+  void testAppend(Appendable app, CharSequence cs, @IndexFor("#2") int i) throws IOException {
+    app.append(cs, i, i);
+    // :: error: (argument)
+    app.append(cs, 1, 2);
+  }
+
+  void testAppend(StringWriter app, CharSequence cs, @IndexFor("#2") int i) throws IOException {
+    app.append(cs, i, i);
+    // :: error: (argument)
+    app.append(cs, 1, 2);
+  }
+}
diff --git a/checker/tests/index/CharToIntCast.java b/checker/tests/index/CharToIntCast.java
new file mode 100644
index 0000000..1344b43
--- /dev/null
+++ b/checker/tests/index/CharToIntCast.java
@@ -0,0 +1,18 @@
+// Test case for issue #2540: https://github.com/typetools/checker-framework/issues/2540
+
+import org.checkerframework.common.value.qual.IntRange;
+
+public class CharToIntCast {
+
+  public static void charRange(char c) {
+    @IntRange(from = 0, to = Character.MAX_VALUE) int i = c;
+  }
+
+  public static void charShift(char c) {
+    char c2 = (char) (c >> 4);
+  }
+
+  public static void rangeShiftOk(@IntRange(from = 0, to = Character.MAX_VALUE) int i) {
+    char c2 = (char) (i >> 4);
+  }
+}
diff --git a/checker/tests/index/CheckAgainstNegativeOne.java b/checker/tests/index/CheckAgainstNegativeOne.java
new file mode 100644
index 0000000..211e922
--- /dev/null
+++ b/checker/tests/index/CheckAgainstNegativeOne.java
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+
+public class CheckAgainstNegativeOne {
+
+  public static String replaceString(String target, String oldStr, String newStr) {
+    if (oldStr.equals("")) {
+      throw new IllegalArgumentException();
+    }
+
+    StringBuffer result = new StringBuffer();
+    @IndexOrHigh("target") int lastend = 0;
+    int pos;
+    while ((pos = target.indexOf(oldStr, lastend)) != -1) {
+      result.append(target.substring(lastend, pos));
+      result.append(newStr);
+      lastend = pos + oldStr.length();
+    }
+    result.append(target.substring(lastend));
+    return result.toString();
+  }
+}
diff --git a/checker/tests/index/CheckNotNull1.java b/checker/tests/index/CheckNotNull1.java
new file mode 100644
index 0000000..dcc1854
--- /dev/null
+++ b/checker/tests/index/CheckNotNull1.java
@@ -0,0 +1,9 @@
+public class CheckNotNull1 {
+  <T extends Object> T checkNotNull(T ref) {
+    return ref;
+  }
+
+  <S extends Object> void test(S ref) {
+    checkNotNull(ref);
+  }
+}
diff --git a/checker/tests/index/CheckNotNull2.java b/checker/tests/index/CheckNotNull2.java
new file mode 100644
index 0000000..03047e6
--- /dev/null
+++ b/checker/tests/index/CheckNotNull2.java
@@ -0,0 +1,9 @@
+public class CheckNotNull2<T extends Object> {
+  T checkNotNull(T ref) {
+    return ref;
+  }
+
+  void test(T ref) {
+    checkNotNull(ref);
+  }
+}
diff --git a/checker/tests/index/CombineFacts.java b/checker/tests/index/CombineFacts.java
new file mode 100644
index 0000000..817eecb
--- /dev/null
+++ b/checker/tests/index/CombineFacts.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+@SuppressWarnings("lowerbound")
+public class CombineFacts {
+  void test(int[] a1) {
+    @LTLengthOf("a1") int len = a1.length - 1;
+    int[] a2 = new int[len];
+    a2[len - 1] = 1;
+    a1[len] = 1;
+
+    // This access should issue an error.
+    // :: error: (array.access.unsafe.high)
+    a2[len] = 1;
+  }
+}
diff --git a/checker/tests/index/CompareBySubtraction.java b/checker/tests/index/CompareBySubtraction.java
new file mode 100644
index 0000000..8ac834c
--- /dev/null
+++ b/checker/tests/index/CompareBySubtraction.java
@@ -0,0 +1,20 @@
+// @skip-test until fixed.
+
+public class CompareBySubtraction {
+  public int compare(int[] a1, int[] a2) {
+    if (a1 == a2) {
+      return 0;
+    }
+    int tmp;
+    tmp = a1.length - a2.length;
+    if (tmp != 0) {
+      return tmp;
+    }
+    for (int i = 0; i < a1.length; i++) {
+      if (a1[i] != a2[i]) {
+        return ((a1[i] > a2[i]) ? 1 : -1);
+      }
+    }
+    return 0;
+  }
+}
diff --git a/checker/tests/index/CompoundAssignmentCheck.java b/checker/tests/index/CompoundAssignmentCheck.java
new file mode 100644
index 0000000..65f577b
--- /dev/null
+++ b/checker/tests/index/CompoundAssignmentCheck.java
@@ -0,0 +1,8 @@
+public class CompoundAssignmentCheck {
+  void test() {
+    int a = 9;
+    a += 5;
+    a -= 2;
+    int[] arr5 = new int[a]; // LBC shouldn't warn here.
+  }
+}
diff --git a/checker/tests/index/ComputeConst.java b/checker/tests/index/ComputeConst.java
new file mode 100644
index 0000000..2616ddb
--- /dev/null
+++ b/checker/tests/index/ComputeConst.java
@@ -0,0 +1,11 @@
+public class ComputeConst {
+
+  public static int hash(long l) {
+    // If possible, use the value itself.
+    if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) {
+      return (int) l;
+    }
+
+    return Long.hashCode(l);
+  }
+}
diff --git a/checker/tests/index/ConditionalIndex.java b/checker/tests/index/ConditionalIndex.java
new file mode 100644
index 0000000..1ee3ec1
--- /dev/null
+++ b/checker/tests/index/ConditionalIndex.java
@@ -0,0 +1,15 @@
+// test case for issue 162: https://github.com/kelloggm/checker-framework/issues/162
+
+public class ConditionalIndex {
+  public void f(boolean cond) {
+    int[] a = new int[10];
+    int[] b = new int[1];
+    if (cond) {
+      int[] c = a;
+    } else {
+      int[] c = b;
+    }
+
+    int[] d = (cond ? a : b);
+  }
+}
diff --git a/checker/tests/index/ConstantArrays.java b/checker/tests/index/ConstantArrays.java
new file mode 100644
index 0000000..5f606cb
--- /dev/null
+++ b/checker/tests/index/ConstantArrays.java
@@ -0,0 +1,33 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class ConstantArrays {
+  void basic_test() {
+    int[] b = new int[4];
+    @LTLengthOf("b") int[] a = {0, 1, 2, 3};
+
+    // :: error: (array.initializer)::error: (assignment)
+    @LTLengthOf("b") int[] a1 = {0, 1, 2, 4};
+
+    @LTEqLengthOf("b") int[] c = {-1, 4, 3, 1};
+
+    // :: error: (array.initializer)::error: (assignment)
+    @LTEqLengthOf("b") int[] c2 = {-1, 4, 5, 1};
+  }
+
+  void offset_test() {
+    int[] b = new int[4];
+    int[] b2 = new int[10];
+    @LTLengthOf(
+        value = {"b", "b2"},
+        offset = {"-2", "5"})
+    int[] a = {2, 3, 0};
+
+    @LTLengthOf(
+        value = {"b", "b2"},
+        offset = {"-2", "5"})
+    // :: error: (array.initializer)::error: (assignment)
+    int[] a2 = {2, 3, 5};
+
+    // Non-constant offsets don't work correctly. See kelloggm#120.
+  }
+}
diff --git a/checker/tests/index/ConstantOffsets.java b/checker/tests/index/ConstantOffsets.java
new file mode 100644
index 0000000..bd5cccf
--- /dev/null
+++ b/checker/tests/index/ConstantOffsets.java
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class ConstantOffsets {
+  void method1(int[] a, int offset, @LTLengthOf(value = "#1", offset = "-#2 - 1") int x) {}
+
+  void test1() {
+    int offset = -4;
+    int x = 4;
+    int[] f1 = new int[x - offset];
+    method1(f1, offset, x);
+  }
+
+  void method2(int[] a, int offset, @LTLengthOf(value = "#1", offset = "#2 - 1") int x) {}
+
+  void test2() {
+    int offset = 4;
+    int x = 4;
+    int[] f1 = new int[x + offset];
+    method2(f1, offset, x);
+  }
+}
diff --git a/checker/tests/index/ConstantsIndex.java b/checker/tests/index/ConstantsIndex.java
new file mode 100644
index 0000000..2287160
--- /dev/null
+++ b/checker/tests/index/ConstantsIndex.java
@@ -0,0 +1,11 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+public class ConstantsIndex {
+
+  void test() {
+    int @MinLen(3) [] arr = {1, 2, 3};
+    int i = arr[1];
+    // :: error: (array.access.unsafe.high.constant)
+    int j = arr[3];
+  }
+}
diff --git a/checker/tests/index/CustomContractWithArgs.java b/checker/tests/index/CustomContractWithArgs.java
new file mode 100644
index 0000000..8ace86d
--- /dev/null
+++ b/checker/tests/index/CustomContractWithArgs.java
@@ -0,0 +1,161 @@
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.common.value.qual.MinLen;
+import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.PostconditionAnnotation;
+import org.checkerframework.framework.qual.PreconditionAnnotation;
+import org.checkerframework.framework.qual.QualifierArgument;
+
+public class CustomContractWithArgs {
+  // Postcondition for MinLen
+  @PostconditionAnnotation(qualifier = MinLen.class)
+  @interface EnsuresMinLen {
+    public String[] value();
+
+    @QualifierArgument("value")
+    public int targetValue();
+  }
+
+  // Conditional postcondition for LTLengthOf
+  @ConditionalPostconditionAnnotation(qualifier = LTLengthOf.class)
+  @interface EnsuresLTLIf {
+    public boolean result();
+
+    public String[] expression();
+
+    @JavaExpression
+    @QualifierArgument("value")
+    public String[] targetValue();
+
+    @JavaExpression
+    @QualifierArgument("offset")
+    public String[] targetOffset();
+  }
+
+  // Precondition for LTLengthOf
+  @PreconditionAnnotation(qualifier = LTLengthOf.class)
+  @interface RequiresLTL {
+    public String[] value();
+
+    @JavaExpression
+    @QualifierArgument("value")
+    public String[] targetValue();
+
+    @JavaExpression
+    @QualifierArgument("offset")
+    public String[] targetOffset();
+  }
+
+  class Base {
+    @EnsuresMinLen(value = "#1", targetValue = 10)
+    void minLenContract(int[] a) {
+      if (a.length < 10) throw new RuntimeException();
+    }
+
+    @EnsuresMinLen(value = "#1", targetValue = 10)
+    // :: error: (contracts.postcondition)
+    void minLenWrong(int[] a) {
+      if (a.length < 9) throw new RuntimeException();
+    }
+
+    void minLenUse(int[] b) {
+      minLenContract(b);
+      int @MinLen(10) [] c = b;
+    }
+
+    public int b, y;
+
+    @EnsuresLTLIf(
+        expression = "b",
+        targetValue = {"#1", "#1"},
+        targetOffset = {"#2 + 1", "10"},
+        result = true)
+    boolean ltlPost(int[] a, int c) {
+      if (b < a.length - c - 1 && b < a.length - 10) {
+        return true;
+      } else {
+        return false;
+      }
+    }
+
+    @EnsuresLTLIf(expression = "b", targetValue = "#1", targetOffset = "#3", result = true)
+    // :: error: (flowexpr.parse.error)
+    boolean ltlPostInvalid(int[] a, int c) {
+      return false;
+    }
+
+    @RequiresLTL(
+        value = "b",
+        targetValue = {"#1", "#1"},
+        targetOffset = {"#2 + 1", "-10"})
+    void ltlPre(int[] a, int c) {
+      @LTLengthOf(value = "a", offset = "c+1") int i = b;
+    }
+
+    void ltlUse(int[] a, int c) {
+      if (ltlPost(a, c)) {
+        @LTLengthOf(value = "a", offset = "c+1") int i = b;
+
+        ltlPre(a, c);
+      }
+      // :: error: (assignment)
+      @LTLengthOf(value = "a", offset = "c+1") int j = b;
+    }
+  }
+
+  class Derived extends Base {
+    public int x;
+
+    @Override
+    @EnsuresLTLIf(
+        expression = "b ",
+        targetValue = {"#1", "#1"},
+        targetOffset = {"#2 + 1", "11"},
+        result = true)
+    boolean ltlPost(int[] a, int d) {
+      return false;
+    }
+
+    @Override
+    @RequiresLTL(
+        value = "b ",
+        targetValue = {"#1", "#1"},
+        targetOffset = {"#2 + 1", "-11"})
+    void ltlPre(int[] a, int d) {
+      @LTLengthOf(
+          value = {"a", "a"},
+          offset = {"d+1", "-10"})
+      // :: error: (assignment)
+      int i = b;
+    }
+  }
+
+  class DerivedInvalid extends Base {
+    public int x;
+
+    @Override
+    @EnsuresLTLIf(
+        expression = "b ",
+        targetValue = {"#1", "#1"},
+        targetOffset = {"#2 + 1", "9"},
+        result = true)
+    // :: error: (contracts.conditional.postcondition.true.override)
+    boolean ltlPost(int[] a, int c) {
+      // :: error: (contracts.conditional.postcondition)
+      return true;
+    }
+
+    @Override
+    @RequiresLTL(
+        value = "b ",
+        targetValue = {"#1", "#1"},
+        targetOffset = {"#2 + 1", "-9"})
+    // :: error: (contracts.precondition.override)
+    void ltlPre(int[] a, int d) {
+      @LTLengthOf(
+          value = {"a", "a"},
+          offset = {"d+1", "-10"})
+      int i = b;
+    }
+  }
+}
diff --git a/checker/tests/index/DaikonCrash.java b/checker/tests/index/DaikonCrash.java
new file mode 100644
index 0000000..4a4beb8
--- /dev/null
+++ b/checker/tests/index/DaikonCrash.java
@@ -0,0 +1,14 @@
+import java.util.Arrays;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class DaikonCrash {
+  void method(Object[] a1) {
+    int[] u = union(new int[] {}, new int[] {});
+    Arrays.sort(u);
+  }
+
+  @Pure
+  private int[] union(int[] ints, int[] ints1) {
+    throw new RuntimeException();
+  }
+}
diff --git a/checker/tests/index/DefaultingForEach.java b/checker/tests/index/DefaultingForEach.java
new file mode 100644
index 0000000..bf4ae6a
--- /dev/null
+++ b/checker/tests/index/DefaultingForEach.java
@@ -0,0 +1,22 @@
+// Test case for issue #4248: https://github.com/typetools/checker-framework/issues/4248.
+// This test exposed a crash in the original version of the fix.
+
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+class DefaultForEach {
+
+  @DefaultQualifier(NonNegative.class)
+  static int[] foo() {
+    throw new RuntimeException();
+  }
+
+  void bar() {
+    for (Integer p : foo()) {
+      // :: error: assignment
+      @Positive int x = p;
+      @NonNegative int y = p;
+    }
+  }
+}
diff --git a/checker/tests/index/Dimension.java b/checker/tests/index/Dimension.java
new file mode 100644
index 0000000..b2ab086
--- /dev/null
+++ b/checker/tests/index/Dimension.java
@@ -0,0 +1,18 @@
+@SuppressWarnings("lowerbound")
+public class Dimension {
+  void test(int expr) {
+    int[] array = new int[expr];
+    // :: error: (array.access.unsafe.high)
+    array[expr] = 0;
+    array[expr - 1] = 0;
+  }
+
+  String[] arrayField = new String[1];
+
+  void test2(int expr) {
+    arrayField = new String[expr];
+    // :: error: (array.access.unsafe.high)
+    this.arrayField[expr] = "";
+    this.arrayField[expr - 1] = "";
+  }
+}
diff --git a/checker/tests/index/DivisionTest.java b/checker/tests/index/DivisionTest.java
new file mode 100644
index 0000000..26003be
--- /dev/null
+++ b/checker/tests/index/DivisionTest.java
@@ -0,0 +1,8 @@
+import java.util.*;
+
+public class DivisionTest {
+
+  public static void division() {
+    System.out.println(1 / (2.0));
+  }
+}
diff --git a/checker/tests/index/EndsWith.java b/checker/tests/index/EndsWith.java
new file mode 100644
index 0000000..a41ff8b
--- /dev/null
+++ b/checker/tests/index/EndsWith.java
@@ -0,0 +1,13 @@
+// Test case for issue #56:
+// https://github.com/kelloggm/checker-framework/issues/56
+
+import org.checkerframework.common.value.qual.MinLen;
+
+public class EndsWith {
+
+  void testEndsWith(String arg) {
+    if (arg.endsWith("[]")) {
+      @MinLen(2) String arg2 = arg;
+    }
+  }
+}
diff --git a/checker/tests/index/EndsWith2.java b/checker/tests/index/EndsWith2.java
new file mode 100644
index 0000000..5034b59
--- /dev/null
+++ b/checker/tests/index/EndsWith2.java
@@ -0,0 +1,17 @@
+// Test case for issue #168: https://github.com/kelloggm/checker-framework/issues/168
+
+public class EndsWith2 {
+
+  public static String invertBrackets(String classname) {
+
+    // Get the array depth (if any)
+    int array_depth = 0;
+    String brackets = "";
+    while (classname.endsWith("[]")) {
+      brackets = brackets + classname.substring(classname.length() - 2);
+      classname = classname.substring(0, classname.length() - 2);
+      array_depth++;
+    }
+    return brackets + classname;
+  }
+}
diff --git a/checker/tests/index/EnumValues.java b/checker/tests/index/EnumValues.java
new file mode 100644
index 0000000..7a23d1f
--- /dev/null
+++ b/checker/tests/index/EnumValues.java
@@ -0,0 +1,22 @@
+import org.checkerframework.common.value.qual.*;
+
+public class EnumValues {
+
+  public static enum Direction {
+    NORTH,
+    SOUTH,
+    EAST,
+    WEST
+  };
+
+  public static void enumValues() {
+    Direction @ArrayLen(4) [] arr4 = Direction.values();
+    Direction[] arr = Direction.values();
+    Direction a = arr[0];
+    Direction b = arr[1];
+    Direction c = arr[2];
+    Direction d = arr[3];
+    // :: error: (array.access.unsafe.high.constant)
+    Direction e = arr[4];
+  }
+}
diff --git a/checker/tests/index/EqualToIndex.java b/checker/tests/index/EqualToIndex.java
new file mode 100644
index 0000000..b9cd7f5
--- /dev/null
+++ b/checker/tests/index/EqualToIndex.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class EqualToIndex {
+  static int[] a = {0};
+
+  public static void equalToUpper(@LTLengthOf("a") int m, @LTEqLengthOf("a") int r) {
+    if (r == m) {
+      @LTLengthOf("a") int j = r;
+    }
+  }
+}
diff --git a/checker/tests/index/EqualToTransfer.java b/checker/tests/index/EqualToTransfer.java
new file mode 100644
index 0000000..b8bfac3
--- /dev/null
+++ b/checker/tests/index/EqualToTransfer.java
@@ -0,0 +1,19 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+public class EqualToTransfer {
+  void eq_check(int[] a) {
+    if (1 == a.length) {
+      int @MinLen(1) [] b = a;
+    }
+    if (a.length == 1) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+
+  void eq_bad_check(int[] a) {
+    if (1 == a.length) {
+      // :: error: (assignment)
+      int @MinLen(2) [] b = a;
+    }
+  }
+}
diff --git a/checker/tests/index/ErrorMessageCheck.java b/checker/tests/index/ErrorMessageCheck.java
new file mode 100644
index 0000000..9c22594
--- /dev/null
+++ b/checker/tests/index/ErrorMessageCheck.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.index.qual.NonNegative;
+
+public class ErrorMessageCheck {
+  @NonNegative int size;
+  int[] vDown = new int[size];
+
+  void method3(@NonNegative int size, @NonNegative int value) {
+    this.size = size;
+    this.vDown = new int[this.size];
+    // :: error: (array.access.unsafe.high)
+    vDown[1 + value] = 10;
+  }
+}
diff --git a/checker/tests/index/Errors.java b/checker/tests/index/Errors.java
new file mode 100644
index 0000000..d68201c
--- /dev/null
+++ b/checker/tests/index/Errors.java
@@ -0,0 +1,29 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.LowerBoundUnknown;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class Errors {
+
+  void test() {
+    int[] arr = new int[5];
+
+    // unsafe
+    @GTENegativeOne int n1p = -1;
+    @LowerBoundUnknown int u = -10;
+
+    // safe
+    @NonNegative int nn = 0;
+    @Positive int p = 1;
+
+    // :: error: (array.access.unsafe.low)
+    int a = arr[n1p];
+
+    // :: error: (array.access.unsafe.low)
+    int b = arr[u];
+
+    int c = arr[nn];
+    int d = arr[p];
+  }
+}
+// a comment
diff --git a/checker/tests/index/ExampleUsage.java b/checker/tests/index/ExampleUsage.java
new file mode 100644
index 0000000..005c147
--- /dev/null
+++ b/checker/tests/index/ExampleUsage.java
@@ -0,0 +1,33 @@
+public class ExampleUsage {
+  /**
+   * this class contains a set of test methods that are supposed to show how the lowerbound checker
+   * should work in practice. They contain no annotations - the only test is whether or not it
+   * alarms on particular code constructs that are or are not safe
+   */
+  void safe_loop_const() {
+    int[] arr = new int[5];
+    int k;
+    for (int i = 0; i < 5; i++) {
+      k = arr[i];
+    }
+  }
+
+  void safe_loop_spooky() {
+    int[] arr = new int[5];
+    int k;
+    for (int i = -1; i < 4; ) {
+      i++;
+      k = arr[i];
+    }
+  }
+
+  void obviously_unsafe_loop() {
+    int[] arr = new int[5];
+    int k;
+    for (int i = -1; i < 5; i++) {
+      // :: error: (array.access.unsafe.low)
+      k = arr[i];
+    }
+  }
+}
+// a comment
diff --git a/checker/tests/index/GenericAssignment.java b/checker/tests/index/GenericAssignment.java
new file mode 100644
index 0000000..7d4f361
--- /dev/null
+++ b/checker/tests/index/GenericAssignment.java
@@ -0,0 +1,28 @@
+import java.util.List;
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.common.value.qual.IntRange;
+
+public class GenericAssignment {
+  public void assignNonNegativeList(List<@NonNegative Integer> l) {
+    List<@NonNegative Integer> i = l; // line 10
+  }
+
+  public void assignPositiveList(List<@Positive Integer> l) {
+    List<@Positive Integer> i = l; // line 13
+  }
+
+  public void assignGTENOList(List<@GTENegativeOne Integer> l) {
+    List<@GTENegativeOne Integer> i = l; // line 16
+  }
+
+  // Similar examples that work
+  public void assignNonNegativeArrayOK(@NonNegative Integer[] l) {
+    @NonNegative Integer[] i = l;
+  }
+
+  public void assignIntRangeListOK(List<@IntRange(from = 0) Integer> l) {
+    List<@IntRange(from = 0) Integer> i = l;
+  }
+}
diff --git a/checker/tests/index/GreaterThanOrEqualTransfer.java b/checker/tests/index/GreaterThanOrEqualTransfer.java
new file mode 100644
index 0000000..eb650a2
--- /dev/null
+++ b/checker/tests/index/GreaterThanOrEqualTransfer.java
@@ -0,0 +1,16 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+public class GreaterThanOrEqualTransfer {
+  void gte_check(int[] a) {
+    if (a.length >= 1) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+
+  void gte_bad_check(int[] a) {
+    if (a.length >= 1) {
+      // :: error: (assignment)
+      int @MinLen(2) [] b = a;
+    }
+  }
+}
diff --git a/checker/tests/index/GreaterThanTransfer.java b/checker/tests/index/GreaterThanTransfer.java
new file mode 100644
index 0000000..bd7394e
--- /dev/null
+++ b/checker/tests/index/GreaterThanTransfer.java
@@ -0,0 +1,16 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+public class GreaterThanTransfer {
+  void gt_check(int[] a) {
+    if (a.length > 0) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+
+  void gt_bad_check(int[] a) {
+    if (a.length > 0) {
+      // :: error: (assignment)
+      int @MinLen(2) [] b = a;
+    }
+  }
+}
diff --git a/checker/tests/index/GuavaPrimitives.java b/checker/tests/index/GuavaPrimitives.java
new file mode 100644
index 0000000..0236b32
--- /dev/null
+++ b/checker/tests/index/GuavaPrimitives.java
@@ -0,0 +1,135 @@
+import java.util.AbstractList;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.checker.index.qual.HasSubsequence;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.IndexOrLow;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.LessThan;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.common.value.qual.MinLen;
+
+/**
+ * A simplified version of the Guava primitives classes (such as Bytes, Longs, Shorts, etc.) with
+ * all expected warnings suppressed.
+ */
+public class GuavaPrimitives extends AbstractList<Short> {
+  @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end")
+  final short @MinLen(1) [] array;
+
+  final @IndexFor("array") @LessThan("end") int start;
+  final @Positive @LTEqLengthOf("array") int end;
+
+  public static @IndexOrLow("#1") int indexOf(short[] array, short target) {
+    return indexOf(array, target, 0, array.length);
+  }
+
+  private static @IndexOrLow("#1") @LessThan("#4") int indexOf(
+      short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) {
+    for (int i = start; i < end; i++) {
+      if (array[i] == target) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  private static @IndexOrLow("#1") @LessThan("#4") int lastIndexOf(
+      short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) {
+    for (int i = end - 1; i >= start; i--) {
+      if (array[i] == target) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  GuavaPrimitives(short @MinLen(1) [] array) {
+    this(array, 0, array.length);
+  }
+
+  @SuppressWarnings(
+      "index" // these three fields need to be initialized in some order, and any ordering leads to
+  // the first two issuing errors - since each field is dependent on at least one of the others
+  )
+  GuavaPrimitives(
+      short @MinLen(1) [] array,
+      @IndexFor("#1") @LessThan("#3") int start,
+      @Positive @LTEqLengthOf("#1") int end) {
+    // warnings in here might just need to be suppressed. A single @SuppressWarnings("index") to
+    // establish rep. invariant might be okay?
+    this.array = array;
+    this.start = start;
+    this.end = end;
+  }
+
+  public @Positive @LTLengthOf(
+      value = {"this", "array"},
+      offset = {"-1", "start - 1"}) int
+      size() { // INDEX: Annotation on a public method refers to private member.
+    return end - start;
+  }
+
+  public boolean isEmpty() {
+    return false;
+  }
+
+  public Short get(@IndexFor("this") int index) {
+    return array[start + index];
+  }
+
+  @SuppressWarnings(
+      "lowerbound") // https://github.com/kelloggm/checker-framework/issues/227 indexOf()
+  public @IndexOrLow("this") int indexOf(Object target) {
+    // Overridden to prevent a ton of boxing
+    if (target instanceof Short) {
+      int i = GuavaPrimitives.indexOf(array, (Short) target, start, end);
+      if (i >= 0) {
+        return i - start;
+      }
+    }
+    return -1;
+  }
+
+  @SuppressWarnings(
+      "lowerbound") // https://github.com/kelloggm/checker-framework/issues/227 lastIndexOf()
+  public @IndexOrLow("this") int lastIndexOf(Object target) {
+    // Overridden to prevent a ton of boxing
+    if (target instanceof Short) {
+      int i = GuavaPrimitives.lastIndexOf(array, (Short) target, start, end);
+      if (i >= 0) {
+        return i - start;
+      }
+    }
+    return -1;
+  }
+
+  public Short set(@IndexFor("this") int index, Short element) {
+    short oldValue = array[start + index];
+    // checkNotNull for GWT (do not optimize)
+    array[start + index] = element;
+    return oldValue;
+  }
+
+  @SuppressWarnings("index") // needs https://github.com/kelloggm/checker-framework/issues/229
+  public List<Short> subList(
+      @IndexOrHigh("this") @LessThan("#2") int fromIndex, @IndexOrHigh("this") int toIndex) {
+    int size = size();
+    if (fromIndex == toIndex) {
+      return Collections.emptyList();
+    }
+    return new GuavaPrimitives(array, start + fromIndex, start + toIndex);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder(size() * 6);
+    builder.append('[').append(array[start]);
+    for (int i = start + 1; i < end; i++) {
+      builder.append(", ").append(array[i]);
+    }
+    return builder.append(']').toString();
+  }
+}
diff --git a/checker/tests/index/HexEncode.java b/checker/tests/index/HexEncode.java
new file mode 100644
index 0000000..77f3be1
--- /dev/null
+++ b/checker/tests/index/HexEncode.java
@@ -0,0 +1,16 @@
+@SuppressWarnings("array.access.unsafe.high")
+public class HexEncode {
+  private static final char[] digits = {
+    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+  };
+
+  public static String hexEncode(byte[] bytes) {
+    StringBuffer s = new StringBuffer(bytes.length * 2);
+    for (int i = 0; i < bytes.length; i++) {
+      byte b = bytes[i];
+      s.append(digits[(b & 0xf0) >> 4]);
+      s.append(digits[b & 0x0f]);
+    }
+    return s.toString();
+  }
+}
diff --git a/checker/tests/index/Index115.java b/checker/tests/index/Index115.java
new file mode 100644
index 0000000..ae9cc8e
--- /dev/null
+++ b/checker/tests/index/Index115.java
@@ -0,0 +1,28 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Index115 {
+
+  public static void main(String[] args) {
+    if ((args.length > 1) && (args[1].equals("foo"))) {
+      System.out.println("First argument is foo");
+    }
+  }
+
+  public static void main2(String... args) {
+    if ((args.length > 1) && (args[1].equals("foo"))) {
+      System.out.println("First argument is foo");
+    }
+  }
+
+  public static void main3(String @ArrayLen({1, 2}) [] args) {
+    if ((args.length > 1) && (args[1].equals("foo"))) {
+      System.out.println("First argument is foo");
+    }
+  }
+
+  public static void main4(String @ArrayLen({1, 2}) ... args) {
+    if ((args.length > 1) && (args[1].equals("foo"))) {
+      System.out.println("First argument is foo");
+    }
+  }
+}
diff --git a/checker/tests/index/Index118.java b/checker/tests/index/Index118.java
new file mode 100644
index 0000000..411851b
--- /dev/null
+++ b/checker/tests/index/Index118.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.common.value.qual.*;
+
+public class Index118 {
+
+  public static void foo(String @ArrayLen(4) [] args) {
+    for (int i = 1; i <= 3; i++) {
+      @IntRange(from = 1, to = 3) int x = i;
+      System.out.println(args[i]);
+    }
+  }
+
+  public static void bar(@NonNegative int i, String @ArrayLen(4) [] args) {
+    if (i <= 3) {
+      System.out.println(args[i]);
+    }
+  }
+}
diff --git a/checker/tests/index/Index118NoLoop.java b/checker/tests/index/Index118NoLoop.java
new file mode 100644
index 0000000..6a3ba94
--- /dev/null
+++ b/checker/tests/index/Index118NoLoop.java
@@ -0,0 +1,10 @@
+import org.checkerframework.common.value.qual.ArrayLen;
+
+public class Index118NoLoop {
+
+  public static void foo(String @ArrayLen(4) [] args, int i) {
+    if (i >= 1 && i <= 3) {
+      System.out.println(args[i]);
+    }
+  }
+}
diff --git a/checker/tests/index/Index132.java b/checker/tests/index/Index132.java
new file mode 100644
index 0000000..4cbfe26
--- /dev/null
+++ b/checker/tests/index/Index132.java
@@ -0,0 +1,11 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Index132 {
+  public static String @ArrayLen({3, 4}) [] esc_quantify(String @ArrayLen({1, 2}) ... vars) {
+    if (vars.length == 1) {
+      return new String[] {"hello", vars[0], ")"};
+    } else {
+      return new String[] {"hello", vars[0], vars[1], ")"};
+    }
+  }
+}
diff --git a/checker/tests/index/Index166.java b/checker/tests/index/Index166.java
new file mode 100644
index 0000000..163e21f
--- /dev/null
+++ b/checker/tests/index/Index166.java
@@ -0,0 +1,15 @@
+// Test case for Issue 166:
+// https://github.com/kelloggm/checker-framework/issues/166
+
+import org.checkerframework.checker.index.qual.IndexFor;
+
+public class Index166 {
+
+  public void testMethodInvocation() {
+    requiresIndex("012345", 5);
+    // :: error: (argument)
+    requiresIndex("012345", 6);
+  }
+
+  public void requiresIndex(String str, @IndexFor("#1") int index) {}
+}
diff --git a/checker/tests/index/Index167.java b/checker/tests/index/Index167.java
new file mode 100644
index 0000000..53b456e
--- /dev/null
+++ b/checker/tests/index/Index167.java
@@ -0,0 +1,27 @@
+// Test case for Issue 167:
+// https://github.com/kelloggm/checker-framework/issues/167
+
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LTOMLengthOf;
+import org.checkerframework.checker.index.qual.NonNegative;
+
+public class Index167 {
+  static void fn1(int[] arr, @IndexFor("#1") int i) {
+    if (i >= 33) {
+      // :: error: (argument)
+      fn2(arr, i);
+    }
+    if (i > 33) {
+      // :: error: (argument)
+      fn2(arr, i);
+    }
+    if (i != 33) {
+      // :: error: (argument)
+      fn2(arr, i);
+    }
+  }
+
+  static void fn2(int[] arr, @NonNegative @LTOMLengthOf("#1") int i) {
+    int c = arr[i + 1];
+  }
+}
diff --git a/checker/tests/index/Index176.java b/checker/tests/index/Index176.java
new file mode 100644
index 0000000..0c5649d
--- /dev/null
+++ b/checker/tests/index/Index176.java
@@ -0,0 +1,16 @@
+// Test case for Issue 176:
+// https://github.com/kelloggm/checker-framework/issues/176
+
+import org.checkerframework.checker.index.qual.IndexFor;
+
+public class Index176 {
+  void test(String arglist, @IndexFor("#1") int pos) {
+    int semi_pos = arglist.indexOf(";");
+    if (semi_pos == -1) {
+      throw new Error("Malformed arglist: " + arglist);
+    }
+    arglist.substring(pos, semi_pos + 1);
+    // :: error: (argument)
+    arglist.substring(pos, semi_pos + 2);
+  }
+}
diff --git a/checker/tests/index/IndexByChar.java b/checker/tests/index/IndexByChar.java
new file mode 100644
index 0000000..df049c0
--- /dev/null
+++ b/checker/tests/index/IndexByChar.java
@@ -0,0 +1,10 @@
+public class IndexByChar {
+  public int m(char c) {
+    int[] i = new int[128];
+    if (c < 128) {
+      return i[c];
+    } else {
+      return -1;
+    }
+  }
+}
diff --git a/checker/tests/index/IndexConditionalReport.java b/checker/tests/index/IndexConditionalReport.java
new file mode 100644
index 0000000..469b1fd
--- /dev/null
+++ b/checker/tests/index/IndexConditionalReport.java
@@ -0,0 +1,13 @@
+// test case for https://github.com/typetools/checker-framework/issues/2345
+
+public class IndexConditionalReport {
+
+  public int getI(int len) {
+    for (int i = 0; i < len; i++) {
+      if (false) {
+        return i == 0 ? -1 : i; // unexpected error issued here
+      }
+    }
+    return -1;
+  }
+}
diff --git a/checker/tests/index/IndexForAverage.java b/checker/tests/index/IndexForAverage.java
new file mode 100644
index 0000000..01ac9ee
--- /dev/null
+++ b/checker/tests/index/IndexForAverage.java
@@ -0,0 +1,16 @@
+// test case for issue 86: https://github.com/kelloggm/checker-framework/issues/86
+
+import org.checkerframework.checker.index.qual.*;
+
+public class IndexForAverage {
+
+  public static void bug(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) {
+    @IndexFor("a") int k = (i + j) / 2;
+  }
+
+  public static void bug2(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) {
+    @LTLengthOf("a") int k = ((i - 1) + j) / 2;
+    // :: error: (assignment)
+    @LTLengthOf("a") int h = ((i + 1) + j) / 2;
+  }
+}
diff --git a/checker/tests/index/IndexForTest.java b/checker/tests/index/IndexForTest.java
new file mode 100644
index 0000000..abd0c1f
--- /dev/null
+++ b/checker/tests/index/IndexForTest.java
@@ -0,0 +1,78 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class IndexForTest {
+  int @MinLen(1) [] array = {0};
+
+  void test1(@IndexFor("array") int i) {
+    int x = array[i];
+  }
+
+  void callTest1(int x) {
+    test1(0);
+    // ::  error: (argument)
+    test1(1);
+    // ::  error: (argument)
+    test1(2);
+    // ::  error: (argument)
+    test1(array.length);
+
+    if (array.length > 0) {
+      test1(array.length - 1);
+    }
+
+    test1(array.length - 1);
+
+    // ::  error: (argument)
+    test1(this.array.length);
+
+    if (array.length > 0) {
+      test1(this.array.length - 1);
+    }
+
+    test1(this.array.length - 1);
+
+    if (this.array.length > x && x >= 0) {
+      test1(x);
+    }
+
+    if (array.length == x) {
+      // ::  error: (argument)
+      test1(x);
+    }
+  }
+
+  void test2(@IndexFor("this.array") int i) {
+    int x = array[i];
+  }
+
+  void callTest2(int x) {
+    test2(0);
+    // ::  error: (argument)
+    test2(1);
+    // ::  error: (argument)
+    test2(2);
+    // ::  error: (argument)
+    test2(array.length);
+
+    if (array.length > 0) {
+      test2(array.length - 1);
+    }
+
+    test2(array.length - 1);
+
+    // ::  error: (argument)
+    test2(this.array.length);
+
+    if (array.length > 0) {
+      test2(this.array.length - 1);
+    }
+
+    test2(this.array.length - 1);
+
+    if (array.length == x && x >= 0) {
+      // ::  error: (argument)
+      test2(x);
+    }
+  }
+}
diff --git a/checker/tests/index/IndexForTestLBC.java b/checker/tests/index/IndexForTestLBC.java
new file mode 100644
index 0000000..f926774
--- /dev/null
+++ b/checker/tests/index/IndexForTestLBC.java
@@ -0,0 +1,29 @@
+package index;
+
+import org.checkerframework.checker.index.qual.IndexFor;
+
+@SuppressWarnings("upperbound")
+public class IndexForTestLBC {
+  int[] array = {0};
+
+  void test1(@IndexFor("array") int i) {
+    int x = this.array[i];
+  }
+
+  void callTest1(int x) {
+    test1(0);
+    test1(1);
+    test1(2);
+    test1(array.length);
+    // :: error: (argument)
+    test1(array.length - 1);
+    if (array.length > x) {
+      // :: error: (argument)
+      test1(x);
+    }
+
+    if (array.length == x) {
+      test1(x);
+    }
+  }
+}
diff --git a/checker/tests/index/IndexForTwoArrays.java b/checker/tests/index/IndexForTwoArrays.java
new file mode 100644
index 0000000..4e31190
--- /dev/null
+++ b/checker/tests/index/IndexForTwoArrays.java
@@ -0,0 +1,15 @@
+public class IndexForTwoArrays {
+
+  public int compare(double[] a1, double[] a2) {
+    if (a1 == a2) {
+      return 0;
+    }
+    int len = Math.min(a1.length, a2.length);
+    for (int i = 0; i < len; i++) {
+      if (a1[i] != a2[i]) {
+        return ((a1[i] > a2[i]) ? 1 : -1);
+      }
+    }
+    return a1.length - a2.length;
+  }
+}
diff --git a/checker/tests/index/IndexForTwoArrays2.java b/checker/tests/index/IndexForTwoArrays2.java
new file mode 100644
index 0000000..9606190
--- /dev/null
+++ b/checker/tests/index/IndexForTwoArrays2.java
@@ -0,0 +1,18 @@
+// Test case for issue #34: https://github.com/kelloggm/checker-framework/issues/34
+
+public class IndexForTwoArrays2 {
+
+  public boolean equals(int[] da1, int[] da2) {
+    if (da1.length != da2.length) {
+      return false;
+    }
+    int k = 0;
+
+    for (int i = 0; i < da1.length; i++) {
+      if (da1[i] != da2[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/checker/tests/index/IndexForVarargs.java b/checker/tests/index/IndexForVarargs.java
new file mode 100644
index 0000000..c01775d
--- /dev/null
+++ b/checker/tests/index/IndexForVarargs.java
@@ -0,0 +1,33 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+
+public class IndexForVarargs {
+  String get(@IndexFor("#2") int i, String... varargs) {
+    return varargs[i];
+  }
+
+  void method(@IndexFor("#2") int i, String[]... varargs) {}
+
+  void m() {
+    // :: error: (argument)
+    get(1);
+
+    get(1, "a", "b");
+
+    // :: error: (argument)
+    get(2, "abc");
+
+    String[] stringArg1 = new String[] {"a", "b"};
+    String[] stringArg2 = new String[] {"c", "d", "e"};
+    String[] stringArg3 = new String[] {"a", "b", "c"};
+
+    method(1, stringArg1, stringArg2);
+
+    // :: error: (argument)
+    method(2, stringArg3);
+
+    get(1, stringArg1);
+
+    // :: error: (argument)
+    get(3, stringArg2);
+  }
+}
diff --git a/checker/tests/index/IndexIntValVsConstant.java b/checker/tests/index/IndexIntValVsConstant.java
new file mode 100644
index 0000000..8c0c8f5
--- /dev/null
+++ b/checker/tests/index/IndexIntValVsConstant.java
@@ -0,0 +1,29 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.LessThan;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.IntVal;
+
+public class IndexIntValVsConstant {
+
+  void m() {
+
+    int @ArrayLen(7) [] a1 = new int[] {1, 2, 3, 4, 5, 6, 7};
+
+    @IntVal(2) int i = 2;
+    @IntVal(4) int j = 4;
+
+    int[] s0 = internSubsequence(a1, 2, 4);
+    int[] s1 = internSubsequence(a1, i, j);
+  }
+
+  int @Interned [] internSubsequence(
+      int @Interned [] seq,
+      @IndexFor("#1") @LessThan("#3") int start,
+      @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int end) {
+    // dummy implementation
+    return new int[0];
+  }
+}
diff --git a/checker/tests/index/IndexOf.java b/checker/tests/index/IndexOf.java
new file mode 100644
index 0000000..0596d40
--- /dev/null
+++ b/checker/tests/index/IndexOf.java
@@ -0,0 +1,15 @@
+// Test case for issue #169: https://github.com/kelloggm/checker-framework/issues/169
+
+// @skip-test until the issue is fixed
+
+public class IndexOf {
+
+  public static String m(String arg) {
+    int split_pos = arg.indexOf(",-");
+    if (split_pos == 0) {
+      // Just discard the ',' if ",-" occurs at begining of string
+      arg = arg.substring(1);
+    }
+    return arg;
+  }
+}
diff --git a/checker/tests/index/IndexOrLowTests.java b/checker/tests/index/IndexOrLowTests.java
new file mode 100644
index 0000000..38f9b73
--- /dev/null
+++ b/checker/tests/index/IndexOrLowTests.java
@@ -0,0 +1,32 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.IndexOrLow;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class IndexOrLowTests {
+  int[] array = {1, 2};
+
+  @IndexOrLow("array") int index = -1;
+
+  void test() {
+
+    if (index != -1) {
+      array[index] = 1;
+    }
+
+    @IndexOrHigh("array") int y = index + 1;
+    // :: error: (array.access.unsafe.high)
+    array[y] = 1;
+    if (y < array.length) {
+      array[y] = 1;
+    }
+    // :: error: (assignment)
+    index = array.length;
+  }
+
+  void test2(@LTLengthOf("array") @GTENegativeOne int param) {
+    index = array.length - 1;
+    @LTLengthOf("array") @GTENegativeOne int x = index;
+    index = param;
+  }
+}
diff --git a/checker/tests/index/IndexSameLen.java b/checker/tests/index/IndexSameLen.java
new file mode 100644
index 0000000..90d2bb1
--- /dev/null
+++ b/checker/tests/index/IndexSameLen.java
@@ -0,0 +1,16 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class IndexSameLen {
+
+  public static void bug2() {
+    int[] a = {1, 2, 3, 4, 5};
+    int @SameLen("a") [] b = a;
+
+    @IndexFor("a") int i = 2;
+    a[i] = b[i];
+
+    for (int j = 0; j < a.length; j++) {
+      a[j] = b[j];
+    }
+  }
+}
diff --git a/checker/tests/index/IntroAdd.java b/checker/tests/index/IntroAdd.java
new file mode 100644
index 0000000..8adcc7b
--- /dev/null
+++ b/checker/tests/index/IntroAdd.java
@@ -0,0 +1,19 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class IntroAdd {
+  void test(int[] arr) {
+    // :: error: (assignment)
+    @LTLengthOf({"arr"}) int a = 3;
+  }
+
+  void test(int[] arr, @LTLengthOf({"#1"}) int a) {
+    // :: error: (assignment)
+    @LTLengthOf({"arr"}) int c = a + 1;
+    @LTEqLengthOf({"arr"}) int c1 = a + 1;
+    @LTLengthOf({"arr"}) int d = a + 0;
+    @LTLengthOf({"arr"}) int e = a + (-7);
+    // :: error: (assignment)
+    @LTLengthOf({"arr"}) int f = a + 7;
+  }
+}
diff --git a/checker/tests/index/IntroAnd.java b/checker/tests/index/IntroAnd.java
new file mode 100644
index 0000000..5851963
--- /dev/null
+++ b/checker/tests/index/IntroAnd.java
@@ -0,0 +1,39 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class IntroAnd {
+  void test() {
+    @NonNegative int a = 1 & 0;
+    @NonNegative int b = a & 5;
+
+    // :: error: (assignment)
+    @Positive int c = a & b;
+    @NonNegative int d = a & b;
+    @NonNegative int e = b & a;
+  }
+
+  void test_ubc_and(
+      @IndexFor("#2") int i, int[] a, @LTLengthOf("#2") int j, int k, @NonNegative int m) {
+    int x = a[i & k];
+    int x1 = a[k & i];
+    // :: error: (array.access.unsafe.low) :: error: (array.access.unsafe.high)
+    int y = a[j & k];
+    if (j > -1) {
+      int z = a[j & k];
+    }
+    // :: error: (array.access.unsafe.high)
+    int w = a[m & k];
+    if (m < a.length) {
+      int u = a[m & k];
+    }
+  }
+
+  void two_arrays(int[] a, int[] b, @IndexFor("#1") int i, @IndexFor("#2") int j) {
+    int l = a[i & j];
+    l = b[i & j];
+  }
+
+  void test_pos(@Positive int x, @Positive int y) {
+    // :: error: (assignment)
+    @Positive int z = x & y;
+  }
+}
diff --git a/checker/tests/index/IntroRules.java b/checker/tests/index/IntroRules.java
new file mode 100644
index 0000000..1ac1ce5
--- /dev/null
+++ b/checker/tests/index/IntroRules.java
@@ -0,0 +1,35 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.LowerBoundUnknown;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class IntroRules {
+
+  void test() {
+    @Positive int a = 10;
+    @NonNegative int b = 9;
+    @GTENegativeOne int c = 8;
+    @LowerBoundUnknown int d = 7;
+
+    // :: error: (assignment)
+    @Positive int e = 0;
+    // :: error: (assignment)
+    @Positive int f = -1;
+    // :: error: (assignment)
+    @Positive int g = -6;
+
+    @NonNegative int h = 0;
+    @GTENegativeOne int i = 0;
+    @LowerBoundUnknown int j = 0;
+    // :: error: (assignment)
+    @NonNegative int k = -1;
+    // :: error: (assignment)
+    @NonNegative int l = -4;
+
+    @GTENegativeOne int m = -1;
+    @LowerBoundUnknown int n = -1;
+    // :: error: (assignment)
+    @GTENegativeOne int o = -9;
+  }
+}
+// a comment
diff --git a/checker/tests/index/IntroShift.java b/checker/tests/index/IntroShift.java
new file mode 100644
index 0000000..f89a9e6
--- /dev/null
+++ b/checker/tests/index/IntroShift.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.index.qual.NonNegative;
+
+public class IntroShift {
+  void test() {
+    @NonNegative int a = 1 >> 1;
+    // :: error: (assignment)
+    @NonNegative int b = -1 >> 0;
+  }
+}
diff --git a/checker/tests/index/IntroSub.java b/checker/tests/index/IntroSub.java
new file mode 100644
index 0000000..bbfa211
--- /dev/null
+++ b/checker/tests/index/IntroSub.java
@@ -0,0 +1,22 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class IntroSub {
+  void test(int[] arr) {
+    // :: error: (assignment)
+    @LTLengthOf({"arr"}) int a = 3;
+  }
+
+  void test(int[] arr, @LTLengthOf({"#1"}) int a) {
+    // :: error: (assignment)
+    @LTLengthOf({"arr"}) int c = a - (-1);
+    @LTEqLengthOf({"arr"}) int c1 = a - (-1);
+    @LTLengthOf({"arr"}) int d = a - 0;
+    @LTLengthOf({"arr"}) int e = a - 7;
+    // :: error: (assignment)
+    @LTLengthOf({"arr"}) int f = a - (-7);
+
+    // :: error: (assignment)
+    @LTEqLengthOf({"arr"}) int j = 7;
+  }
+}
diff --git a/checker/tests/index/InvalidSubsequence.java b/checker/tests/index/InvalidSubsequence.java
new file mode 100644
index 0000000..e4e2965
--- /dev/null
+++ b/checker/tests/index/InvalidSubsequence.java
@@ -0,0 +1,46 @@
+import org.checkerframework.checker.index.qual.HasSubsequence;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.LessThan;
+
+public class InvalidSubsequence {
+  // :: error: flowexpr.parse.error :: error: not.final
+  @HasSubsequence(subsequence = "banana", from = "this.from", to = "this.to")
+  int[] a;
+
+  // :: error: flowexpr.parse.error :: error: not.final
+  @HasSubsequence(subsequence = "this", from = "banana", to = "this.to")
+  int[] b;
+
+  // :: error: flowexpr.parse.error :: error: not.final
+  @HasSubsequence(subsequence = "this", from = "this.from", to = "banana")
+  int[] c;
+
+  // :: error: not.final
+  @HasSubsequence(subsequence = "this", from = "this.from", to = "10")
+  int[] e;
+
+  @IndexFor("a") @LessThan("to") int from;
+
+  @IndexOrHigh("a") int to;
+
+  void assignA(int[] d) {
+    // :: error: to.not.ltel
+    a = d;
+  }
+
+  void assignB(int[] d) {
+    // :: error: from.gt.to :: error: from.not.nonnegative :: error: to.not.ltel
+    b = d;
+  }
+
+  void assignC(int[] d) {
+    // :: error: from.gt.to :: error: to.not.ltel
+    c = d;
+  }
+
+  void assignE(int[] d) {
+    // :: error: from.gt.to :: error: to.not.ltel
+    e = d;
+  }
+}
diff --git a/checker/tests/index/Issue1411.java b/checker/tests/index/Issue1411.java
new file mode 100644
index 0000000..75b9bf1
--- /dev/null
+++ b/checker/tests/index/Issue1411.java
@@ -0,0 +1,17 @@
+// Test case for Issue 1411:
+// https://github.com/typetools/checker-framework/issues/1411
+
+import org.checkerframework.dataflow.qual.Pure;
+
+interface IGeneric<V> {
+  @Pure
+  public V get();
+}
+
+interface IConcrete extends IGeneric<char[]> {}
+
+public class Issue1411 {
+  static void m(IConcrete ic) {
+    char[] val = ic.get();
+  }
+}
diff --git a/checker/tests/index/Issue194.java b/checker/tests/index/Issue194.java
new file mode 100644
index 0000000..19e06f1
--- /dev/null
+++ b/checker/tests/index/Issue194.java
@@ -0,0 +1,43 @@
+// Test case for kelloggm 194
+// https://github.com/kelloggm/checker-framework/issues/194
+
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LengthOf;
+import org.checkerframework.checker.index.qual.SameLen;
+
+public class Issue194 {
+  class Custom {
+    public @LengthOf("this") int length() {
+      throw new RuntimeException();
+    }
+
+    public Object get(@IndexFor("this") int i) {
+      return null;
+    }
+
+    void call() {
+      length();
+    }
+  }
+
+  public boolean m(Custom a, Custom b) {
+    if (a.length() != b.length()) {
+      return false;
+    }
+    for (int i = 0; i < a.length(); ++i) {
+      if (a.get(i) != b.get(i)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @SuppressWarnings("anno.on.irrelevant")
+  public void m2(Custom a, Custom b) {
+    if (a.length() != b.length()) {
+      return;
+    }
+    @SameLen("a") Custom a2 = b;
+    @SameLen("b") Custom b2 = a;
+  }
+}
diff --git a/checker/tests/index/Issue1984.java b/checker/tests/index/Issue1984.java
new file mode 100644
index 0000000..1916f00
--- /dev/null
+++ b/checker/tests/index/Issue1984.java
@@ -0,0 +1,11 @@
+// Test case for Issue 1984
+// https://github.com/typetools/checker-framework/issues/1984
+
+import org.checkerframework.common.value.qual.IntRange;
+
+public class Issue1984 {
+  public int m(int[] a, @IntRange(from = 0, to = 12) int i) {
+    // :: error: (array.access.unsafe.high.range)
+    return a[i];
+  }
+}
diff --git a/checker/tests/index/Issue20.java b/checker/tests/index/Issue20.java
new file mode 100644
index 0000000..67d4a51
--- /dev/null
+++ b/checker/tests/index/Issue20.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class Issue20 {
+  // An issue with LUB that results in losing information when unifying.
+  int[] a, b;
+
+  void test(@LTLengthOf("a") int i, @LTEqLengthOf({"a", "b"}) int j, boolean flag) {
+    @LTEqLengthOf("a") int k = flag ? i : j;
+  }
+}
diff --git a/checker/tests/index/Issue2029.java b/checker/tests/index/Issue2029.java
new file mode 100644
index 0000000..c93562f
--- /dev/null
+++ b/checker/tests/index/Issue2029.java
@@ -0,0 +1,27 @@
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.LessThan;
+import org.checkerframework.checker.index.qual.NonNegative;
+
+public class Issue2029 {
+  void lessThanUpperBound(@NonNegative @LessThan("#2") int index, @NonNegative int size, char val) {
+    char[] arr = new char[size];
+    arr[index] = val;
+  }
+
+  void LessThanOffsetLowerBound(
+      int[] array, @NonNegative @LTLengthOf("#1") int n, @NonNegative @LessThan("#2 + 1") int k) {
+    array[n - k] = 10;
+  }
+
+  void LessThanOffsetUpperBound(
+      @NonNegative int n,
+      @NonNegative @LessThan("#1 + 1") int k,
+      @NonNegative int size,
+      @NonNegative @LessThan("#3 + 1") int index) {
+    @NonNegative int m = n - k;
+    int[] arr = new int[size];
+    for (; index < arr.length - 1; index++) {
+      arr[index] = 10;
+    }
+  }
+}
diff --git a/checker/tests/index/Issue2030.java b/checker/tests/index/Issue2030.java
new file mode 100644
index 0000000..b6465b0
--- /dev/null
+++ b/checker/tests/index/Issue2030.java
@@ -0,0 +1,9 @@
+public class Issue2030 {
+  double roundIntermediate(double x) {
+    if (x >= 0.0) {
+      return x;
+    } else {
+      return (long) x - 1;
+    }
+  }
+}
diff --git a/checker/tests/index/Issue21.java b/checker/tests/index/Issue21.java
new file mode 100644
index 0000000..226a558
--- /dev/null
+++ b/checker/tests/index/Issue21.java
@@ -0,0 +1,8 @@
+public class Issue21 {
+
+  void test(int[] arr, int[] arr2) {
+    for (int i = 0; i < arr2.length && i < arr.length; i++) {
+      arr[i] = arr2[i];
+    }
+  }
+}
diff --git a/checker/tests/index/Issue2334.java b/checker/tests/index/Issue2334.java
new file mode 100644
index 0000000..bd8c57d
--- /dev/null
+++ b/checker/tests/index/Issue2334.java
@@ -0,0 +1,40 @@
+// Test case for issue #2334: http://tinyurl.com/cfissue/2334
+
+import org.checkerframework.checker.index.qual.NonNegative;
+
+public class Issue2334 {
+
+  void hasSideEffect() {}
+
+  String stringField;
+
+  void m1(String stringFormal) {
+    if (stringFormal.indexOf('d') != -1) {
+      hasSideEffect();
+      @NonNegative int i = stringFormal.indexOf('d');
+    }
+  }
+
+  void m2() {
+    if (stringField.indexOf('d') != -1) {
+      hasSideEffect();
+      // :: error: (assignment)
+      @NonNegative int i = stringField.indexOf('d');
+    }
+  }
+
+  void m3(String stringFormal) {
+    if (stringFormal.indexOf('d') != -1) {
+      System.out.println("hey");
+      @NonNegative int i = stringFormal.indexOf('d');
+    }
+  }
+
+  void m4() {
+    if (stringField.indexOf('d') != -1) {
+      System.out.println("hey");
+      // :: error: (assignment)
+      @NonNegative int i = stringField.indexOf('d');
+    }
+  }
+}
diff --git a/checker/tests/index/Issue2420.java b/checker/tests/index/Issue2420.java
new file mode 100644
index 0000000..d6f2f79
--- /dev/null
+++ b/checker/tests/index/Issue2420.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.common.value.qual.*;
+
+public class Issue2420 {
+  static void str(String argStr) {
+    if (argStr.isEmpty()) {
+      return;
+    }
+    if (argStr == "abc") {
+      return;
+    }
+    // :: error: (argument)
+    char c = "abc".charAt(argStr.length() - 1);
+    // :: error: (argument)
+    char c2 = "abc".charAt(argStr.length());
+  }
+}
diff --git a/checker/tests/index/Issue2452.java b/checker/tests/index/Issue2452.java
new file mode 100644
index 0000000..e321b7d
--- /dev/null
+++ b/checker/tests/index/Issue2452.java
@@ -0,0 +1,39 @@
+// Test case for https://github.com/typetools/checker-framework/issues/2452
+
+import java.lang.reflect.Array;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.common.value.qual.MinLen;
+
+class Issue2452 {
+  Object m1(Object[] a1) {
+    if (Array.getLength(a1) > 0) {
+      return Array.get(a1, 0);
+    } else {
+      return null;
+    }
+  }
+
+  void m2() {
+    int[] arr = {1, 2, 3};
+    @LTEqLengthOf({"arr"}) int a = Array.getLength(arr);
+  }
+
+  void testMinLenSubtractPositive(String @MinLen(10) [] s) {
+    @Positive int i1 = s.length - 9;
+    @NonNegative int i0 = Array.getLength(s) - 10;
+    // ::  error: (assignment)
+    @NonNegative int im1 = Array.getLength(s) - 11;
+  }
+
+  void testLessThanLength(String[] s, @IndexOrHigh("#1") int i, @IndexOrHigh("#1") int j) {
+    if (i < Array.getLength(s)) {
+      @IndexFor("s") int in = i;
+      // ::  error: (assignment)
+      @IndexFor("s") int jn = j;
+    }
+  }
+}
diff --git a/checker/tests/index/Issue2493.java b/checker/tests/index/Issue2493.java
new file mode 100644
index 0000000..8dc2747
--- /dev/null
+++ b/checker/tests/index/Issue2493.java
@@ -0,0 +1,9 @@
+// test case for issue 2493: http://tinyurl.com/cfissue/2493
+
+import org.checkerframework.checker.index.qual.*;
+
+public class Issue2493 {
+  public static void test(int a[], int @SameLen("#1") [] b) {
+    for (@IndexOrHigh("b") int i = 0; i < a.length; i++) {}
+  }
+}
diff --git a/checker/tests/index/Issue2494.java b/checker/tests/index/Issue2494.java
new file mode 100644
index 0000000..a4bf87b
--- /dev/null
+++ b/checker/tests/index/Issue2494.java
@@ -0,0 +1,27 @@
+// Test case for issue #2494: http://tinyurl.com/cfissue/2494
+
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.LessThan;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.common.value.qual.MinLen;
+
+public final class Issue2494 {
+
+  static final long @MinLen(1) [] factorials = {
+    1L,
+    1L,
+    1L * 2,
+    1L * 2 * 3,
+    1L * 2 * 3 * 4,
+    1L * 2 * 3 * 4 * 5,
+    1L * 2 * 3 * 4 * 5 * 6,
+    1L * 2 * 3 * 4 * 5 * 6 * 7
+  };
+
+  static void binomialA(
+      @NonNegative @LTLengthOf("Issue2494.factorials") int n,
+      @NonNegative @LessThan("#1 + 1") int k) {
+    @IndexFor("factorials") int j = k;
+  }
+}
diff --git a/checker/tests/index/Issue2505.java b/checker/tests/index/Issue2505.java
new file mode 100644
index 0000000..5318f5b
--- /dev/null
+++ b/checker/tests/index/Issue2505.java
@@ -0,0 +1,10 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+public class Issue2505 {
+  public static void warningIfStatement(int @MinLen(1) [] a) {
+    int i = a.length;
+    if (--i >= 0) {
+      a[i] = 0;
+    }
+  }
+}
diff --git a/checker/tests/index/Issue2613.java b/checker/tests/index/Issue2613.java
new file mode 100644
index 0000000..0dc7603
--- /dev/null
+++ b/checker/tests/index/Issue2613.java
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.LessThan;
+
+public class Issue2613 {
+
+  private static final String STRING_CONSTANT = "Hello";
+
+  void integerConstant() {
+    require_lt(0, Integer.MAX_VALUE);
+  }
+
+  void StringConstant() {
+    require_lt(0, STRING_CONSTANT);
+  }
+
+  void require_lt(@LessThan("#2") int a, int b) {}
+
+  void require_lt(@LTLengthOf("#2") int a, String b) {}
+
+  void method(@LessThan("Integer.MAX_VALUE") long x, @LessThan("Integer.MAX_VALUE") long y) {
+    x = y;
+    @LessThan("2147483647") long z = y;
+  }
+}
diff --git a/checker/tests/index/Issue2629.java b/checker/tests/index/Issue2629.java
new file mode 100644
index 0000000..851c8ac
--- /dev/null
+++ b/checker/tests/index/Issue2629.java
@@ -0,0 +1,10 @@
+// Test case for Issue 2629
+// https://github.com/typetools/checker-framework/issues/2629
+
+import org.checkerframework.checker.index.qual.LessThan;
+
+public class Issue2629 {
+  @LessThan("#1 + 1") int test(int a) {
+    return a;
+  }
+}
diff --git a/checker/tests/index/Issue3207.java b/checker/tests/index/Issue3207.java
new file mode 100644
index 0000000..01b6e30
--- /dev/null
+++ b/checker/tests/index/Issue3207.java
@@ -0,0 +1,20 @@
+// Test case for https://tinyurl.com/cfissue/3207
+
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class Issue3207 {
+
+  void m(int @MinLen(1) [] arr) {
+    @LTLengthOf("arr") int j = 0;
+  }
+
+  void m2(int @MinLen(1) [] @MinLen(1) [] arr) {
+    @LTLengthOf("arr[0]") int j = 0;
+  }
+
+  void m3(int @MinLen(1) [] @MinLen(1) [] arr) {
+    int @MinLen(1) [] arr0 = arr[0];
+    @LTLengthOf("arr0") int j = 0;
+  }
+}
diff --git a/checker/tests/index/Issue3224.java b/checker/tests/index/Issue3224.java
new file mode 100644
index 0000000..d0770a2
--- /dev/null
+++ b/checker/tests/index/Issue3224.java
@@ -0,0 +1,40 @@
+// Test case for https://tinyurl.com/cfissue/3224
+
+import java.util.Arrays;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class Issue3224 {
+  static class Arrays {
+    static String[] copyOf(String[] args, int length) {
+      return args;
+    }
+  }
+
+  public static void m1(String @MinLen(1) [] args) {
+    int i = 4;
+    String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, i);
+  }
+
+  public static void m2(String @MinLen(1) [] args) {
+    String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, args.length);
+  }
+
+  public static void m3(String @MinLen(1) ... args) {
+    String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, args.length);
+  }
+
+  public static void m4(String @MinLen(1) [] args, @IntRange(from = 10, to = 200) int len) {
+    String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, len);
+  }
+
+  public static void m5(String @MinLen(1) [] args, String[] otherArray) {
+    // :: error: assignment
+    String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, otherArray.length);
+  }
+
+  public static void m6(String @MinLen(1) [] args) {
+    // :: error: assignment
+    String @MinLen(1) [] args2 = Arrays.copyOf(args, args.length);
+  }
+}
diff --git a/checker/tests/index/Issue58Minimization.java b/checker/tests/index/Issue58Minimization.java
new file mode 100644
index 0000000..d90bae4
--- /dev/null
+++ b/checker/tests/index/Issue58Minimization.java
@@ -0,0 +1,62 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.checker.index.qual.SameLen;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class Issue58Minimization {
+
+  void test(@GTENegativeOne int x) {
+    int z;
+    if ((z = x) != -1) {
+      @NonNegative int y = z;
+    }
+    if ((z = x) != 1) {
+      // :: error: (assignment)
+      @NonNegative int y = z;
+    }
+  }
+
+  void test2(@NonNegative int x) {
+    int z;
+    if ((z = x) != 0) {
+      @Positive int y = z;
+    }
+    if ((z = x) == 0) {
+      // do nothing
+      int y = 5;
+    } else {
+      @Positive int y = x;
+    }
+  }
+
+  void ubc_test(int[] a, @LTEqLengthOf("#1") int x) {
+    int z;
+    if ((z = x) != a.length) {
+      @LTLengthOf("a") int y = z;
+    }
+  }
+
+  void samelen_test(int[] a, int[] c) {
+    int[] b;
+    if ((b = a) == c) {
+      int @SameLen({"a", "b", "c"}) [] d = b;
+    }
+  }
+
+  void minlen_test(int[] a, int @MinLen(1) [] c) {
+    int[] b;
+    if ((b = a) == c) {
+      int @MinLen(1) [] d = b;
+    }
+  }
+
+  void minlen_test2(int[] a, int x) {
+    int one = 1;
+    if ((x = one) == a.length) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+}
diff --git a/checker/tests/index/Issue60.java b/checker/tests/index/Issue60.java
new file mode 100644
index 0000000..7c678b0
--- /dev/null
+++ b/checker/tests/index/Issue60.java
@@ -0,0 +1,20 @@
+// Testcase for Issue 60
+// https://github.com/kelloggm/checker-framework/issues/60
+
+import org.checkerframework.checker.index.qual.IndexFor;
+
+public class Issue60 {
+
+  public static int[] fn_compose(@IndexFor("#2") int[] a, int[] b) {
+    int[] result = new int[a.length];
+    for (int i = 0; i < a.length; i++) {
+      int inner = a[i];
+      if (inner == -1) {
+        result[i] = -1;
+      } else {
+        result[i] = b[inner];
+      }
+    }
+    return result;
+  }
+}
diff --git a/checker/tests/index/IteratorVoid.java b/checker/tests/index/IteratorVoid.java
new file mode 100644
index 0000000..db77a3f
--- /dev/null
+++ b/checker/tests/index/IteratorVoid.java
@@ -0,0 +1,13 @@
+import java.util.Iterator;
+
+// If quals are configured incorrectly, there will be an incompatible assignment error; this ensures
+// that Void is given the Positive type.
+
+public class IteratorVoid<T> {
+  T next1;
+  Iterator<T> itor1;
+
+  private void setnext1() {
+    next1 = itor1.hasNext() ? itor1.next() : null;
+  }
+}
diff --git a/checker/tests/index/Kelloggm225.java b/checker/tests/index/Kelloggm225.java
new file mode 100644
index 0000000..1cb54ba
--- /dev/null
+++ b/checker/tests/index/Kelloggm225.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.common.value.qual.*;
+
+public class Kelloggm225 {
+  void method(int @MinLen(1) [] bar) {
+    foo(bar, 0, bar.length);
+  }
+
+  void foo(
+      int @MinLen(1) [] bar,
+      @IndexFor("#1") @LessThan("#3") int start,
+      @IndexOrHigh("#1") int end) {}
+}
diff --git a/checker/tests/index/Kelloggm228.java b/checker/tests/index/Kelloggm228.java
new file mode 100644
index 0000000..4be1b6b
--- /dev/null
+++ b/checker/tests/index/Kelloggm228.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.LessThan;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class Kelloggm228 {
+  public void subList(
+      @IndexOrHigh("this") @LessThan("#2 + 1") int fromIndex, @IndexOrHigh("this") int toIndex) {
+    if (fromIndex == toIndex) {
+      return;
+    }
+
+    @Positive int x = toIndex;
+  }
+}
diff --git a/checker/tests/index/LBCSubtyping.java b/checker/tests/index/LBCSubtyping.java
new file mode 100644
index 0000000..0d2338a
--- /dev/null
+++ b/checker/tests/index/LBCSubtyping.java
@@ -0,0 +1,46 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.LowerBoundUnknown;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class LBCSubtyping {
+
+  void foo() {
+
+    @GTENegativeOne int i = -1;
+
+    @LowerBoundUnknown int j = i;
+
+    int k = -4;
+
+    // not this one though
+    // :: error: (assignment)
+    @GTENegativeOne int l = k;
+
+    @NonNegative int n = 0;
+
+    @Positive int a = 1;
+
+    // check that everything is aboveboard
+    j = a;
+    j = n;
+    l = n;
+    n = a;
+
+    // error cases
+
+    // :: error: (assignment)
+    @NonNegative int p = i;
+    // :: error: (assignment)
+    @Positive int b = i;
+
+    // :: error: (assignment)
+    @NonNegative int r = k;
+    // :: error: (assignment)
+    @Positive int c = k;
+
+    // :: error: (assignment)
+    @Positive int d = r;
+  }
+}
+// a comment
diff --git a/checker/tests/index/LTLDivide.java b/checker/tests/index/LTLDivide.java
new file mode 100644
index 0000000..35bb606
--- /dev/null
+++ b/checker/tests/index/LTLDivide.java
@@ -0,0 +1,26 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class LTLDivide {
+  int[] test(int[] array) {
+    //        @LTLengthOf("array") int len = array.length / 2;
+    int len = array.length / 2;
+    int[] arr = new int[len];
+    for (int a = 0; a < len; a++) {
+      arr[a] = array[a];
+    }
+    return arr;
+  }
+
+  void test2(int[] array) {
+    int len = array.length;
+    int lenM1 = array.length - 1;
+    int lenP1 = array.length + 1;
+    // :: error: (assignment)
+    @LTLengthOf("array") int x = len / 2;
+    @LTLengthOf("array") int y = lenM1 / 3;
+    @LTEqLengthOf("array") int z = len / 1;
+    // :: error: (assignment)
+    @LTLengthOf("array") int w = lenP1 / 2;
+  }
+}
diff --git a/checker/tests/index/LTLengthOfPostcondition.java b/checker/tests/index/LTLengthOfPostcondition.java
new file mode 100644
index 0000000..033d66a
--- /dev/null
+++ b/checker/tests/index/LTLengthOfPostcondition.java
@@ -0,0 +1,42 @@
+import java.util.Arrays;
+import org.checkerframework.checker.index.qual.EnsuresLTLengthOf;
+import org.checkerframework.checker.index.qual.EnsuresLTLengthOfIf;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.NonNegative;
+
+public class LTLengthOfPostcondition {
+
+  Object[] array;
+
+  @NonNegative @LTEqLengthOf("array") int end;
+
+  @EnsuresLTLengthOf(value = "end", targetValue = "array", offset = "#1 - 1")
+  public void shiftIndex(@NonNegative int x) {
+    int newEnd = end - x;
+    if (newEnd < 0) throw new RuntimeException();
+    end = newEnd;
+  }
+
+  public void useShiftIndex(@NonNegative int x) {
+    // :: error: (argument)
+    Arrays.fill(array, end, end + x, null);
+    shiftIndex(x);
+    Arrays.fill(array, end, end + x, null);
+  }
+
+  @EnsuresLTLengthOfIf(expression = "end", result = true, targetValue = "array", offset = "#1 - 1")
+  public boolean tryShiftIndex(@NonNegative int x) {
+    int newEnd = end - x;
+    if (newEnd < 0) {
+      return false;
+    }
+    end = newEnd;
+    return true;
+  }
+
+  public void useTryShiftIndex(@NonNegative int x) {
+    if (tryShiftIndex(x)) {
+      Arrays.fill(array, end, end + x, null);
+    }
+  }
+}
diff --git a/checker/tests/index/LengthOfArrayMinusOne.java b/checker/tests/index/LengthOfArrayMinusOne.java
new file mode 100644
index 0000000..7130020
--- /dev/null
+++ b/checker/tests/index/LengthOfArrayMinusOne.java
@@ -0,0 +1,10 @@
+public class LengthOfArrayMinusOne {
+  void test(int[] arr) {
+    // :: error: (array.access.unsafe.low)
+    int i = arr[arr.length - 1];
+
+    if (arr.length > 0) {
+      int j = arr[arr.length - 1];
+    }
+  }
+}
diff --git a/checker/tests/index/LengthOfTest.java b/checker/tests/index/LengthOfTest.java
new file mode 100644
index 0000000..4e37161
--- /dev/null
+++ b/checker/tests/index/LengthOfTest.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class LengthOfTest {
+  void foo(int[] a, @LengthOf("#1") int x) {
+    @IndexOrHigh("a") int y = x;
+    // :: error: (assignment)
+    @IndexFor("a") int w = x;
+    @LengthOf("a") int z = a.length;
+  }
+}
diff --git a/checker/tests/index/LengthTransfer.java b/checker/tests/index/LengthTransfer.java
new file mode 100644
index 0000000..f8ad5db
--- /dev/null
+++ b/checker/tests/index/LengthTransfer.java
@@ -0,0 +1,21 @@
+public class LengthTransfer {
+  void exceptional_control_flow(int[] a) {
+    if (a.length == 0) {
+      throw new IllegalArgumentException();
+    }
+    int i = a[0];
+  }
+
+  void equal_to_return(int[] a) {
+    if (a.length == 0) {
+      return;
+    }
+    int i = a[0];
+  }
+
+  void gt_check(int[] a) {
+    if (a.length > 0) {
+      int i = a[0];
+    }
+  }
+}
diff --git a/checker/tests/index/LengthTransfer2.java b/checker/tests/index/LengthTransfer2.java
new file mode 100644
index 0000000..108a83c
--- /dev/null
+++ b/checker/tests/index/LengthTransfer2.java
@@ -0,0 +1,19 @@
+// Test case for issue #64: https://github.com/kelloggm/checker-framework/issues/64
+
+import org.checkerframework.common.value.qual.*;
+
+public class LengthTransfer2 {
+  public static void main(String[] args) {
+    if (args.length != 2) {
+      System.err.println("Needs 2 arguments, got " + args.length);
+      System.exit(1);
+    }
+    int limit = Integer.parseInt(args[0]);
+    int period = Integer.parseInt(args[1]);
+  }
+
+  void m(String @ArrayLen(2) [] args) {
+    int limit = Integer.parseInt(args[0]);
+    int period = Integer.parseInt(args[1]);
+  }
+}
diff --git a/checker/tests/index/LengthTransferForMinLen.java b/checker/tests/index/LengthTransferForMinLen.java
new file mode 100644
index 0000000..e1f62cd
--- /dev/null
+++ b/checker/tests/index/LengthTransferForMinLen.java
@@ -0,0 +1,23 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+public class LengthTransferForMinLen {
+  void exceptional_control_flow(int[] a) {
+    if (a.length == 0) {
+      throw new IllegalArgumentException();
+    }
+    int @MinLen(1) [] b = a;
+  }
+
+  void equal_to_return(int[] a) {
+    if (a.length == 0) {
+      return;
+    }
+    int @MinLen(1) [] b = a;
+  }
+
+  void gt_check(int[] a) {
+    if (a.length > 0) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+}
diff --git a/checker/tests/index/LessThanConstantAddition.java b/checker/tests/index/LessThanConstantAddition.java
new file mode 100644
index 0000000..1986089
--- /dev/null
+++ b/checker/tests/index/LessThanConstantAddition.java
@@ -0,0 +1,10 @@
+// Test case for issue #2541: https://github.com/typetools/checker-framework/issues/2541
+
+public class LessThanConstantAddition {
+
+  public static void checkedPow(int b) {
+    if (b <= 2) {
+      int c = (int) b;
+    }
+  }
+}
diff --git a/checker/tests/index/LessThanCustomCollection.java b/checker/tests/index/LessThanCustomCollection.java
new file mode 100644
index 0000000..c2f5ff1
--- /dev/null
+++ b/checker/tests/index/LessThanCustomCollection.java
@@ -0,0 +1,76 @@
+package lessthan;
+
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.IndexOrLow;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.LengthOf;
+import org.checkerframework.checker.index.qual.LessThan;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.dataflow.qual.Pure;
+
+// This class has a similar implementation to several Immutable*Array class in Guava,
+// such as com.google.common.primitives.ImmutableDoubleArray.
+public class LessThanCustomCollection {
+  // This object is a subset of array. So, if something is an index for "this"
+  // then it is >= start and < end.
+  private final int[] array;
+  private final @IndexOrHigh("array") @LessThan("end + 1") int start;
+  private final @LTLengthOf(
+      value = {"array", "this"},
+      offset = {" - 1", "- start"}) int end;
+
+  private LessThanCustomCollection(int[] array) {
+    this(array, 0, array.length);
+  }
+
+  private LessThanCustomCollection(
+      int[] array, @IndexOrHigh("#1") @LessThan("#3 + 1") int start, @IndexOrHigh("#1") int end) {
+    this.array = array;
+    // can't est. that end - start is the length of this.
+    // :: error: (assignment)
+    this.end = end;
+    // start is @LessThan(end + 1) but should be @LessThan(this.end + 1)
+    // :: error: (assignment)
+    this.start = start;
+  }
+
+  @Pure
+  public @LengthOf("this") int length() {
+    return end - start;
+  }
+
+  public double get(@IndexFor("this") int index) {
+    // TODO: This is a bug.
+    // :: error: (argument)
+    checkElementIndex(index, length());
+    // Because index is an index for "this" the index + start
+    // must be an index for array.
+    // :: error: (array.access.unsafe.high)
+    return array[start + index];
+  }
+
+  public static @NonNegative int checkElementIndex(
+      @LessThan("#2") @NonNegative int index, @NonNegative int size) {
+    if (index < 0 || index >= size) {
+      throw new IndexOutOfBoundsException();
+    }
+    return index;
+  }
+
+  public @IndexOrLow("this") int indexOf(double target) {
+    for (int i = start; i < end; i++) {
+      if (areEqual(array[i], target)) {
+        // Don't know that is is greater than start.
+        // :: error: (return)
+        return i - start;
+      }
+    }
+    return -1;
+  }
+
+  static boolean areEqual(int item, double target) {
+    // implementation not relevant
+    return true;
+  }
+}
diff --git a/checker/tests/index/LessThanDec.java b/checker/tests/index/LessThanDec.java
new file mode 100644
index 0000000..17bfebc
--- /dev/null
+++ b/checker/tests/index/LessThanDec.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.IndexOrLow;
+import org.checkerframework.checker.index.qual.LessThan;
+
+public class LessThanDec {
+  private static @IndexOrLow("#1") @LessThan("#4") int lastIndexOf(
+      short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) {
+    for (int i = end - 1; i >= start; i--) {
+      if (array[i] == target) {
+        return i;
+      }
+    }
+    return -1;
+  }
+}
diff --git a/checker/tests/index/LessThanFloat.java b/checker/tests/index/LessThanFloat.java
new file mode 100644
index 0000000..519f8c9
--- /dev/null
+++ b/checker/tests/index/LessThanFloat.java
@@ -0,0 +1,62 @@
+import org.checkerframework.checker.index.qual.LessThan;
+
+public class LessThanFloat {
+  int bigger;
+
+  @LessThan("bigger") byte b;
+
+  @LessThan("bigger") short s;
+
+  @LessThan("bigger") int i;
+
+  @LessThan("bigger") long l;
+
+  // :: error: (anno.on.irrelevant)
+  @LessThan("bigger") float f;
+
+  // :: error: (anno.on.irrelevant)
+  @LessThan("bigger") double d;
+
+  // :: error: (anno.on.irrelevant)
+  @LessThan("bigger") boolean bool;
+
+  @LessThan("bigger") char c;
+
+  @LessThan("bigger") Byte bBoxed;
+
+  @LessThan("bigger") Short sBoxed;
+
+  @LessThan("bigger") Integer iBoxed;
+
+  @LessThan("bigger") Long lBoxed;
+
+  // :: error: (anno.on.irrelevant)
+  @LessThan("bigger") Float fBoxed;
+
+  // :: error: (anno.on.irrelevant)
+  @LessThan("bigger") Double dBoxed;
+
+  // :: error: (anno.on.irrelevant)
+  @LessThan("bigger") Boolean boolBoxed;
+
+  @LessThan("bigger") Character cBoxed;
+
+  java.lang.@LessThan("bigger") Byte bBoxed2;
+
+  java.lang.@LessThan("bigger") Short sBoxed2;
+
+  java.lang.@LessThan("bigger") Integer iBoxed2;
+
+  java.lang.@LessThan("bigger") Long lBoxed2;
+
+  // :: error: (anno.on.irrelevant)
+  java.lang.@LessThan("bigger") Float fBoxed2;
+
+  // :: error: (anno.on.irrelevant)
+  java.lang.@LessThan("bigger") Double dBoxed2;
+
+  // :: error: (anno.on.irrelevant)
+  java.lang.@LessThan("bigger") Boolean boolBoxed2;
+
+  java.lang.@LessThan("bigger") Character cBoxed2;
+}
diff --git a/checker/tests/index/LessThanFloatLiteral.java b/checker/tests/index/LessThanFloatLiteral.java
new file mode 100644
index 0000000..d3d7963
--- /dev/null
+++ b/checker/tests/index/LessThanFloatLiteral.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.index.qual.LessThan;
+
+class LessThanFloatLiteral {
+  void test(int x) {
+    if (1.0 > x) {
+      // TODO: It might be nice to handle comparisons against floats,
+      // but an array index is not generally compared to a float.
+      // :: error: assignment
+      @LessThan("1") int y = x;
+    }
+  }
+}
diff --git a/checker/tests/index/LessThanLen.java b/checker/tests/index/LessThanLen.java
new file mode 100644
index 0000000..4cef7ee
--- /dev/null
+++ b/checker/tests/index/LessThanLen.java
@@ -0,0 +1,57 @@
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class LessThanLen {
+
+  public static void m1() {
+    int[] shorter = new int[5];
+    int[] longer = new int[shorter.length * 2];
+    for (int i = 0; i < shorter.length; i++) {
+      longer[i] = shorter[i];
+    }
+  }
+
+  public static void m2(int @MinLen(1) [] shorter) {
+    int[] longer = new int[shorter.length * 2];
+    for (int i = 0; i < shorter.length; i++) {
+      longer[i] = shorter[i];
+    }
+  }
+
+  public static void m3(int[] shorter) {
+    int[] longer = new int[shorter.length + 1];
+    for (int i = 0; i < shorter.length; i++) {
+      longer[i] = shorter[i];
+    }
+  }
+
+  public static void m4(int @MinLen(1) [] shorter) {
+    int[] longer = new int[shorter.length * 1];
+    // :: error: (assignment)
+    @LTLengthOf("longer") int x = shorter.length;
+    @LTEqLengthOf("longer") int y = shorter.length;
+  }
+
+  public static void m5(int[] shorter) {
+    // :: error: (array.length.negative)
+    int[] longer = new int[shorter.length * -1];
+    // :: error: (assignment)
+    @LTLengthOf("longer") int x = shorter.length;
+    // :: error: (assignment)
+    @LTEqLengthOf("longer") int y = shorter.length;
+  }
+
+  public static void m6(int @MinLen(1) [] shorter) {
+    int[] longer = new int[4 * shorter.length];
+    // TODO: enable when https://github.com/kelloggm/checker-framework/issues/211 is fixed
+    // // :: error: (assignment)
+    // @LTLengthOf("longer") int x = shorter.length;
+    @LTEqLengthOf("longer") int y = shorter.length;
+  }
+
+  public static void m7(int @MinLen(1) [] shorter) {
+    int[] longer = new int[4 * shorter.length];
+    @LTLengthOf("longer") int x = shorter.length;
+    @LTEqLengthOf("longer") int y = shorter.length;
+  }
+}
diff --git a/checker/tests/index/LessThanLenBug.java b/checker/tests/index/LessThanLenBug.java
new file mode 100644
index 0000000..7d5d115
--- /dev/null
+++ b/checker/tests/index/LessThanLenBug.java
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class LessThanLenBug {
+  public static void m1(int[] shorter) {
+    int[] longer = new int[4 * shorter.length];
+    // :: error: (assignment)
+    @LTLengthOf("longer") int x = shorter.length;
+    int i = longer[x];
+  }
+
+  public static void m2(int @MinLen(1) [] shorter) {
+    int[] longer = new int[4 * shorter.length];
+    @LTLengthOf("longer") int x = shorter.length;
+    int i = longer[x];
+  }
+
+  public static void main(String[] args) {
+    m1(new int[0]);
+  }
+}
diff --git a/checker/tests/index/LessThanOrEqualTransfer.java b/checker/tests/index/LessThanOrEqualTransfer.java
new file mode 100644
index 0000000..0d6fefe
--- /dev/null
+++ b/checker/tests/index/LessThanOrEqualTransfer.java
@@ -0,0 +1,16 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+public class LessThanOrEqualTransfer {
+  void lte_check(int[] a) {
+    if (1 <= a.length) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+
+  void lte_bad_check(int[] a) {
+    if (1 <= a.length) {
+      // :: error: (assignment)
+      int @MinLen(2) [] b = a;
+    }
+  }
+}
diff --git a/checker/tests/index/LessThanTransferTest.java b/checker/tests/index/LessThanTransferTest.java
new file mode 100644
index 0000000..80834b8
--- /dev/null
+++ b/checker/tests/index/LessThanTransferTest.java
@@ -0,0 +1,16 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+public class LessThanTransferTest {
+  void lt_check(int[] a) {
+    if (0 < a.length) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+
+  void lt_bad_check(int[] a) {
+    if (0 < a.length) {
+      // :: error: (assignment)
+      int @MinLen(2) [] b = a;
+    }
+  }
+}
diff --git a/checker/tests/index/LessThanValue.java b/checker/tests/index/LessThanValue.java
new file mode 100644
index 0000000..4882a8c
--- /dev/null
+++ b/checker/tests/index/LessThanValue.java
@@ -0,0 +1,120 @@
+package lessthan;
+
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LessThan;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.common.value.qual.*;
+
+// Test for LessThanChecker
+public class LessThanValue {
+
+  void subtyping(int x, int y, @LessThan({"#1", "#2"}) int a, @LessThan("#1") int b) {
+    @LessThan("x") int q = a;
+    @LessThan({"x", "y"})
+    // :: error: (assignment)
+    int r = b;
+  }
+
+  public static boolean flag;
+
+  void lub(int x, int y, @LessThan({"#1", "#2"}) int a, @LessThan("#1") int b) {
+    @LessThan("x") int r = flag ? a : b;
+    @LessThan({"x", "y"})
+    // :: error: (assignment)
+    int s = flag ? a : b;
+  }
+
+  void transitive(int a, int b, int c) {
+    if (a < b) {
+      if (b < c) {
+        @LessThan("c") int x = a;
+      }
+    }
+  }
+
+  void calls() {
+    isLessThan(0, 1);
+    isLessThanOrEqual(0, 0);
+  }
+
+  void isLessThan(@LessThan("#2") @NonNegative int start, int end) {
+    @NonNegative int x = end - start - 1;
+    @Positive int y = end - start;
+  }
+
+  @NonNegative int isLessThanOrEqual(@LessThan("#2 + 1") @NonNegative int start, int end) {
+    return end - start;
+  }
+
+  public void setMaximumItemCount(int maximum) {
+    if (maximum < 0) {
+      throw new IllegalArgumentException("Negative 'maximum' argument.");
+    }
+    int count = getCount();
+    if (count > maximum) {
+      @Positive int y = count - maximum;
+      @NonNegative int deleteIndex = count - maximum - 1;
+    }
+  }
+
+  int getCount() {
+    throw new RuntimeException();
+  }
+
+  void method(@NonNegative int m) {
+    boolean[] has_modulus = new boolean[m];
+    @LessThan("m") int x = foo(m);
+    @IndexFor("has_modulus") int rem = foo(m);
+  }
+
+  @LessThan("#1") @NonNegative int foo(int in) {
+    throw new RuntimeException();
+  }
+
+  void test(int maximum, int count) {
+    if (maximum < 0) {
+      throw new IllegalArgumentException("Negative 'maximum' argument.");
+    }
+    if (count > maximum) {
+      int deleteIndex = count - maximum - 1;
+      // TODO: shouldn't error
+      // :: error: (argument)
+      isLessThanOrEqual(0, deleteIndex);
+    }
+  }
+
+  void count(int count) {
+    if (count > 0) {
+      if (count % 2 == 1) {
+
+      } else {
+        // TODO: improve value checker
+        // :: error: (assignment)
+        @IntRange(from = 0) int countDivMinus = count / 2 - 1;
+        // Reasign to update the value in the store.
+        countDivMinus = countDivMinus;
+        // :: error: (argument)
+        isLessThan(0, countDivMinus);
+        isLessThanOrEqual(0, countDivMinus);
+      }
+    }
+  }
+
+  static @NonNegative @LessThan("#2 + 1") int expandedCapacity(
+      @NonNegative int oldCapacity, @NonNegative int minCapacity) {
+    if (minCapacity < 0) {
+      throw new AssertionError("cannot store more than MAX_VALUE elements");
+    }
+    // careful of overflow!
+    int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; // expand by %50
+    if (newCapacity < minCapacity) {
+      newCapacity = Integer.highestOneBit(minCapacity - 1) << 1;
+    }
+    if (newCapacity < 0) {
+      newCapacity = Integer.MAX_VALUE;
+      // guaranteed to be >= newCapacity
+    }
+    return newCapacity;
+  }
+}
diff --git a/checker/tests/index/LessThanZeroArrayLength.java b/checker/tests/index/LessThanZeroArrayLength.java
new file mode 100644
index 0000000..ba58d4c
--- /dev/null
+++ b/checker/tests/index/LessThanZeroArrayLength.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.index.qual.LessThan;
+
+public class LessThanZeroArrayLength {
+  void test(int[] a) {
+    foo(0, a.length);
+  }
+
+  void foo(@LessThan("#2 + 1") int x, int y) {}
+}
diff --git a/checker/tests/index/ListAdd.java b/checker/tests/index/ListAdd.java
new file mode 100644
index 0000000..2fd3250
--- /dev/null
+++ b/checker/tests/index/ListAdd.java
@@ -0,0 +1,75 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.LTOMLengthOf;
+
+// @skip-test until we bring list support back
+
+public class ListAdd {
+
+  List<Integer> listField;
+
+  void ListAdd(@LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List<Integer> list) {
+    list.add(index, 4);
+
+    // :: error: (list.access.unsafe.high)
+    list.add(notIndex + 1, 4);
+  }
+
+  int[] arr = {0};
+
+  void ListAddWrongName(@LTLengthOf("arr") int index, List<Integer> list) {
+    // :: error: (list.access.unsafe.high)
+    list.add(index, 4);
+  }
+
+  void ListAddField() {
+    listField.add(listField.size() - 1, 4);
+    listField.add(this.listField.size() - 1, 4);
+    this.listField.add(listField.size() - 1, 4);
+    this.listField.add(this.listField.size() - 1, 4);
+
+    // :: error: (list.access.unsafe.high)
+    listField.add(listField.size(), 4);
+    // :: error: (list.access.unsafe.high)
+    listField.add(this.listField.size(), 4);
+    // :: error: (list.access.unsafe.high)
+    this.listField.add(listField.size(), 4);
+    // :: error: (list.access.unsafe.high)
+    this.listField.add(this.listField.size(), 4);
+  }
+
+  void ListAddFieldUserAnnotation(@IndexFor("listField") int i) {
+    listField.add(i, 4);
+    this.listField.add(i, 4);
+
+    // :: error: (list.access.unsafe.high)
+    listField.add(i + 4, 4);
+    // :: error: (list.access.unsafe.high)
+    this.listField.add(i + 4, 4);
+  }
+
+  void ListAddUserAnnotation(@IndexFor("#2") int i, List<Integer> list) {
+    list.add(i, 4);
+
+    // :: error: (list.access.unsafe.high)
+    list.add(i + 4, 4);
+  }
+
+  void ListAddUpdateValue(List<Integer> list) {
+    @LTEqLengthOf("list") int i = list.size();
+    @LTLengthOf("list") int r = list.size() - 1;
+    list.add(0);
+    @LTLengthOf("list") int k = i;
+    @LTOMLengthOf("list") int p = r;
+  }
+
+  void ListAddTwo(@LTEqLengthOf({"#2", "#3"}) int i, List<Integer> list, List<Integer> list2) {
+    @LTEqLengthOf({"list", "list2"}) int j = i;
+    list.add(0);
+    // :: error: (list.access.unsafe.high)
+    list.get(i);
+    // :: error: (list.access.unsafe.high)
+    list2.get(i);
+  }
+}
diff --git a/checker/tests/index/ListAddAll.java b/checker/tests/index/ListAddAll.java
new file mode 100644
index 0000000..b858491
--- /dev/null
+++ b/checker/tests/index/ListAddAll.java
@@ -0,0 +1,59 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+// @skip-test until we bring list support back
+
+public class ListAddAll {
+
+  List<Integer> listField;
+  List<Integer> coll;
+
+  void ListAddAll(
+      @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List<Integer> list) {
+    list.addAll(index, coll);
+
+    // :: error: (list.access.unsafe.high)
+    list.addAll(notIndex, coll);
+  }
+
+  int[] arr = {0};
+
+  void ListAddAllWrongName(@LTLengthOf("arr") int index, List<Integer> list) {
+    // :: error: (list.access.unsafe.high)
+    list.addAll(index, coll);
+  }
+
+  void ListAddAllField() {
+    listField.addAll(listField.size() - 1, coll);
+    listField.addAll(this.listField.size() - 1, coll);
+    this.listField.addAll(listField.size() - 1, coll);
+    this.listField.addAll(this.listField.size() - 1, coll);
+
+    // :: error: (list.access.unsafe.high)
+    listField.addAll(listField.size(), coll);
+    // :: error: (list.access.unsafe.high)
+    listField.addAll(this.listField.size(), coll);
+    // :: error: (list.access.unsafe.high)
+    this.listField.addAll(listField.size(), coll);
+    // :: error: (list.access.unsafe.high)
+    this.listField.addAll(this.listField.size(), coll);
+  }
+
+  void ListAddAllFieldUserAnnotation(@IndexFor("listField") int i) {
+    listField.addAll(i, coll);
+    this.listField.addAll(i, coll);
+
+    // :: error: (list.access.unsafe.high)
+    listField.addAll(i + 1, coll);
+    // :: error: (list.access.unsafe.high)
+    this.listField.addAll(i + 1, coll);
+  }
+
+  void ListAddAllUserAnnotation(@IndexFor("#2") int i, List<Integer> list) {
+    list.addAll(i, coll);
+
+    // :: error: (list.access.unsafe.high)
+    list.addAll(i + 1, coll);
+  }
+}
diff --git a/checker/tests/index/ListAddInfiniteLoop.java b/checker/tests/index/ListAddInfiniteLoop.java
new file mode 100644
index 0000000..36a36bb
--- /dev/null
+++ b/checker/tests/index/ListAddInfiniteLoop.java
@@ -0,0 +1,10 @@
+// @skip-test until we bring list support back
+
+public class ListAddInfiniteLoop {
+
+  void ListLoop(List<Integer> list) {
+    while (true) {
+      list.add(4);
+    }
+  }
+}
diff --git a/checker/tests/index/ListGet.java b/checker/tests/index/ListGet.java
new file mode 100644
index 0000000..7487ab7
--- /dev/null
+++ b/checker/tests/index/ListGet.java
@@ -0,0 +1,56 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+// @skip-test until we bring list support back
+
+public class ListGet {
+
+  List<Integer> listField;
+  int[] arr = {0};
+
+  void ListGet(@LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List<Integer> list) {
+    list.get(index);
+
+    // :: error: (list.access.unsafe.high)
+    list.get(notIndex);
+  }
+
+  void ListGetWrongName(@LTLengthOf("arr") int index, List<Integer> list) {
+    // :: error: (list.access.unsafe.high)
+    list.get(index);
+  }
+
+  void ListGetField() {
+    listField.get(listField.size() - 1);
+    listField.get(this.listField.size() - 1);
+    this.listField.get(listField.size() - 1);
+    this.listField.get(this.listField.size() - 1);
+
+    // :: error: (list.access.unsafe.high)
+    listField.get(listField.size());
+    // :: error: (list.access.unsafe.high)
+    listField.get(this.listField.size());
+    // :: error: (list.access.unsafe.high)
+    this.listField.get(listField.size());
+    // :: error: (list.access.unsafe.high)
+    this.listField.get(this.listField.size());
+  }
+
+  void ListGetFieldUserAnnotation(@IndexFor("listField") int i) {
+    listField.get(i);
+    this.listField.get(i);
+
+    // :: error: (list.access.unsafe.high)
+    listField.get(i + 1);
+    // :: error: (list.access.unsafe.high)
+    this.listField.get(i + 1);
+  }
+
+  void ListGetUserAnnotation(@IndexFor("#2") int i, List<Integer> list) {
+    list.get(i);
+
+    // :: error: (list.access.unsafe.high)
+    list.get(i + 1);
+  }
+}
diff --git a/checker/tests/index/ListIterator.java b/checker/tests/index/ListIterator.java
new file mode 100644
index 0000000..8e29d02
--- /dev/null
+++ b/checker/tests/index/ListIterator.java
@@ -0,0 +1,58 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+// @skip-test until we bring list support back
+
+public class ListIterator {
+
+  List<Integer> listField;
+
+  void ListIterator(
+      @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List<Integer> list) {
+    list.listIterator(index);
+
+    // :: error: (list.access.unsafe.high)
+    list.listIterator(notIndex);
+  }
+
+  int[] arr = {0};
+
+  void ListIteratorWrongName(@LTLengthOf("arr") int index, List<Integer> list) {
+    // :: error: (list.access.unsafe.high)
+    list.listIterator(index);
+  }
+
+  void ListIteratorField() {
+    listField.listIterator(listField.size() - 1);
+    listField.listIterator(this.listField.size() - 1);
+    this.listField.listIterator(listField.size() - 1);
+    this.listField.listIterator(this.listField.size() - 1);
+
+    // :: error: (list.access.unsafe.high)
+    listField.listIterator(listField.size());
+    // :: error: (list.access.unsafe.high)
+    listField.listIterator(this.listField.size());
+    // :: error: (list.access.unsafe.high)
+    this.listField.listIterator(listField.size());
+    // :: error: (list.access.unsafe.high)
+    this.listField.listIterator(this.listField.size());
+  }
+
+  void ListIteratorFieldUserAnnotation(@IndexFor("listField") int i) {
+    listField.listIterator(i);
+    this.listField.listIterator(i);
+
+    // :: error: (list.access.unsafe.high)
+    listField.listIterator(i + 1);
+    // :: error: (list.access.unsafe.high)
+    this.listField.listIterator(i + 1);
+  }
+
+  void ListIteratorUserAnnotation(@IndexFor("#2") int i, List<Integer> list) {
+    list.listIterator(i);
+
+    // :: error: (list.access.unsafe.high)
+    list.listIterator(i + 1);
+  }
+}
diff --git a/checker/tests/index/ListLowerBound.java b/checker/tests/index/ListLowerBound.java
new file mode 100644
index 0000000..cec30a8
--- /dev/null
+++ b/checker/tests/index/ListLowerBound.java
@@ -0,0 +1,18 @@
+// @skip-test until we bring list support back
+import java.util.List;
+import java.util.ListIterator;
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+
+public class ListLowerBound {
+
+  private void m(List<Object> l) {
+    // :: error: (argument)
+    l.get(-1);
+    // :: error: (argument)
+    ListIterator<Object> li = l.listIterator(-1);
+
+    @NonNegative int ni = li.nextIndex();
+    @GTENegativeOne int pi = li.previousIndex();
+  }
+}
diff --git a/checker/tests/index/ListRemove.java b/checker/tests/index/ListRemove.java
new file mode 100644
index 0000000..8561a33
--- /dev/null
+++ b/checker/tests/index/ListRemove.java
@@ -0,0 +1,79 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+// @skip-test until we bring list support back
+// @skip-test can't handle until TreeUtils.getMethod has a way to precisely handle method
+// overloading
+
+public class ListRemove {
+
+  List<Integer> listField;
+
+  void ListRemove(
+      @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List<Integer> list) {
+    list.remove(index);
+
+    // :: error: (list.access.unsafe.high)
+    list.remove(notIndex);
+  }
+
+  void ListRemoveWrongName(@LTLengthOf("arr") int index, List<Integer> list) {
+    // :: error: (list.access.unsafe.high)
+    list.remove(index);
+  }
+
+  void ListRemoveField() {
+    listField.remove(listField.size() - 1);
+    listField.remove(this.listField.size() - 1);
+    this.listField.remove(listField.size() - 1);
+    this.listField.remove(this.listField.size() - 1);
+
+    // :: error: (list.access.unsafe.high)
+    listField.remove(listField.size());
+    // :: error: (list.access.unsafe.high)
+    listField.remove(this.listField.size());
+    // :: error: (list.access.unsafe.high)
+    this.listField.remove(listField.size());
+    // :: error: (list.access.unsafe.high)
+    this.listField.remove(this.listField.size());
+  }
+
+  void ListRemoveFieldUserAnnotation(@IndexFor("listField") int i) {
+    listField.remove(i);
+    this.listField.remove(i);
+
+    // :: error: (list.access.unsafe.high)
+    listField.remove(i + 1);
+    // :: error: (list.access.unsafe.high)
+    this.listField.remove(i + 1);
+  }
+
+  void ListRemoveUserAnnotation(@IndexFor("list") int i, List<Integer> list) {
+    list.remove(i);
+
+    // :: error: (list.access.unsafe.high)
+    list.remove(i + 1);
+    // :: error: (list.access.unsafe.high)
+    list.remove(i);
+  }
+
+  void FailRemove(List<Integer> list) {
+    @LTLengthOf("list") int i = list.size() - 1;
+    try {
+      list.remove(1);
+    } catch (Exception e) {
+    }
+
+    @LTLengthOf("list") int m = i;
+  }
+
+  void RemoveUpdate(List<Integer> list) {
+    @LTLength("list")
+    int m = list.size() - 1;
+    list.get(m);
+    list.remove(m);
+    // :: error: (list.access.unsafe.high)
+    list.get(m);
+  }
+}
diff --git a/checker/tests/index/ListSet.java b/checker/tests/index/ListSet.java
new file mode 100644
index 0000000..9b4a70c
--- /dev/null
+++ b/checker/tests/index/ListSet.java
@@ -0,0 +1,57 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+// @skip-test until we bring list support back
+
+public class ListSet {
+
+  List<Integer> listField;
+
+  void ListSet(@LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List<Integer> list) {
+    list.set(index, 4);
+
+    // :: error: (list.access.unsafe.high)
+    list.set(notIndex, 4);
+  }
+
+  int[] arr = {0};
+
+  void ListSetWrongName(@LTLengthOf("arr") int index, List<Integer> list) {
+    // :: error: (list.access.unsafe.high)
+    list.set(index, 4);
+  }
+
+  void ListSetField() {
+    listField.set(listField.size() - 1, 4);
+    listField.set(this.listField.size() - 1, 4);
+    this.listField.set(listField.size() - 1, 4);
+    this.listField.set(this.listField.size() - 1, 4);
+
+    // :: error: (list.access.unsafe.high)
+    listField.set(listField.size(), 4);
+    // :: error: (list.access.unsafe.high)
+    listField.set(this.listField.size(), 4);
+    // :: error: (list.access.unsafe.high)
+    this.listField.set(listField.size(), 4);
+    // :: error: (list.access.unsafe.high)
+    this.listField.set(this.listField.size(), 4);
+  }
+
+  void ListSetFieldUserAnnotation(@IndexFor("listField") int i) {
+    listField.set(i, 4);
+    this.listField.set(i, 4);
+
+    // :: error: (list.access.unsafe.high)
+    listField.set(i + 1, 4);
+    // :: error: (list.access.unsafe.high)
+    this.listField.set(i + 1, 4);
+  }
+
+  void ListSetUserAnnotation(@IndexFor("#2") int i, List<Integer> list) {
+    list.set(i, 4);
+
+    // :: error: (list.access.unsafe.high)
+    list.set(i + 1, 4);
+  }
+}
diff --git a/checker/tests/index/ListSupport.java b/checker/tests/index/ListSupport.java
new file mode 100644
index 0000000..3f80720
--- /dev/null
+++ b/checker/tests/index/ListSupport.java
@@ -0,0 +1,66 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.UpperBoundBottom;
+
+// @skip-test until we bring list support back
+
+public class ListSupport {
+
+  void indexOf(List<Integer> list) {
+    int index = list.indexOf(0);
+
+    @LTLengthOf("list") int i = index;
+
+    // :: error: (assignment)
+    @UpperBoundBottom int i2 = index;
+  }
+
+  void lastIndexOf(List<Integer> list) {
+    int index = list.lastIndexOf(0);
+
+    @LTLengthOf("list") int i = index;
+
+    // :: error: (assignment)
+    @UpperBoundBottom int i2 = index;
+  }
+
+  void subList(List<Integer> list, @LTLengthOf("#1") int index, @LTEqLengthOf("#1") int endIndex) {
+    List<Integer> list2 = list.subList(index, endIndex);
+
+    // start index must be strictly lessthanlength
+    // :: error: (argument)
+    list2 = list.subList(endIndex, endIndex);
+
+    // edindex must be less than or equal to Length
+    // :: error: (argument)
+    list2 = list.subList(index, 28);
+  }
+
+  void size(List<Integer> list) {
+    int i = list.size();
+    @LTEqLengthOf("list") int k = i;
+
+    // :: error: (assignment)
+    @LTLengthOf("list") int m = i;
+  }
+
+  void clear(List<Integer> list) {
+    int lessThanLength = list.size() - 1;
+    int lessThanOrEq = list.size();
+    list.get(lessThanLength);
+
+    list.clear();
+
+    // :: error: (list.access.unsafe.high)
+    list.get(lessThanLength);
+
+    // :: error: (assignment)
+    @LTEqLengthOf("list") int m = lessThanLength;
+
+    // :: error: (assignment)
+    m = lessThanOrEq;
+
+    // :: error: (assignment)
+    @LTLengthOf("list") int i = lessThanLength;
+  }
+}
diff --git a/checker/tests/index/ListSupportLBC.java b/checker/tests/index/ListSupportLBC.java
new file mode 100644
index 0000000..6d60e2a
--- /dev/null
+++ b/checker/tests/index/ListSupportLBC.java
@@ -0,0 +1,89 @@
+import java.util.ArrayList;
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+// @skip-test until we bring list support back
+
+public class ListSupportLBC {
+
+  void testGet() {
+
+    List<Integer> list = new ArrayList<>();
+    int i = -1;
+    int j = 0;
+
+    // try and use a negative to get, should fail
+    // :: error: (argument)
+    Integer m = list.get(i);
+
+    // try and use a nonnegative, should work
+    Integer l = list.get(j);
+  }
+
+  void testArrayListGet() {
+
+    ArrayList<Integer> list = new ArrayList<>();
+    int i = -1;
+    int j = 0;
+
+    // try and use a negative to get, should fail
+    // :: error: (argument)
+    Integer m = list.get(i);
+
+    // try and use a nonnegative, should work
+    Integer l = list.get(j);
+  }
+
+  void testSet() {
+    List<Integer> list = new ArrayList<>();
+    int i = -1;
+    int j = 0;
+
+    // try and use a negative to get, should fail
+    // :: error: (argument)
+    Integer m = list.set(i, 34);
+
+    // try and use a nonnegative, should work
+    Integer l = list.set(j, 34);
+  }
+
+  void testIndexOf() {
+    List<Integer> list = new ArrayList<>();
+    @GTENegativeOne int a = list.indexOf(1);
+
+    // :: error: (assignment)
+    @NonNegative int n = a;
+
+    @GTENegativeOne int b = list.lastIndexOf(1);
+
+    // :: error: (assignment)
+    @NonNegative int m = b;
+  }
+
+  void testSize() {
+    List<Integer> list = new ArrayList<>();
+    @NonNegative int s = list.size();
+
+    // :: error: (assignment)
+    @Positive int r = s;
+  }
+
+  void testSublist() {
+    List<Integer> list = new ArrayList<>();
+    int i = -1;
+    int j = 0;
+
+    // :: error: (argument)
+    List<Integer> k = list.subList(i, i);
+
+    // :: error: (argument)
+    List<Integer> a = list.subList(i, j);
+
+    // :: error: (argument)
+    List<Integer> b = list.subList(j, i);
+
+    // should work since both are nonnegative
+    List<Integer> c = list.subList(j, j);
+  }
+}
diff --git a/checker/tests/index/ListSupportML.java b/checker/tests/index/ListSupportML.java
new file mode 100644
index 0000000..89f32fb
--- /dev/null
+++ b/checker/tests/index/ListSupportML.java
@@ -0,0 +1,70 @@
+import java.util.ArrayList;
+import org.checkerframework.common.value.qual.MinLen;
+
+// @skip-test until we bring list support back
+
+public class ListSupportML {
+
+  void newListMinLen() {
+    List<Integer> list = new ArrayList<>();
+
+    // :: error: (assignment)
+    @MinLen(1) List<Integer> list2 = list;
+
+    @MinLen(0) List<Integer> list3 = list;
+  }
+
+  void listRemove(@MinLen(10) List<Integer> lst) {
+    List<Integer> list = lst;
+    list.remove(0);
+
+    // :: error: (assignment)
+    @MinLen(10) List<Integer> list2 = list;
+
+    @MinLen(9) List<Integer> list3 = list;
+  }
+
+  void listRemoveAliasing(@MinLen(10) List<Integer> lst) {
+    List<Integer> list = lst;
+    @MinLen(10) List<Integer> list2 = list;
+
+    list2.remove(0);
+
+    // :: error: (assignment)
+    @MinLen(10) List<Integer> list3 = list;
+
+    @MinLen(9) List<Integer> list4 = list;
+  }
+
+  void listAdd(@MinLen(10) List<Integer> lst) {
+    List<Integer> list = lst;
+    list.add(0);
+
+    @MinLen(11) List<Integer> list2 = list;
+  }
+
+  void listClear(@MinLen(10) List<Integer> lst) {
+    List<Integer> list = lst;
+    list.clear();
+
+    // :: error: (assignment)
+    @MinLen(1) List<Integer> list2 = list;
+
+    @MinLen(0) List<Integer> list3 = list;
+  }
+
+  void listRemoveArrayAlter(@MinLen(10) List<Integer> lst) {
+    int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+    int @MinLen(10) [] arr1 = arr;
+    List<Integer> list = lst;
+    @MinLen(10) List<Integer> list2 = list;
+
+    list2.remove(0);
+
+    // :: error: (assignment)
+    @MinLen(10) List<Integer> list3 = list;
+
+    int @MinLen(10) [] arr2 = arr;
+    @MinLen(9) List<Integer> list4 = list;
+  }
+}
diff --git a/checker/tests/index/LiteralArray.java b/checker/tests/index/LiteralArray.java
new file mode 100644
index 0000000..78148df
--- /dev/null
+++ b/checker/tests/index/LiteralArray.java
@@ -0,0 +1,19 @@
+// test case for issue #67:
+// https://github.com/kelloggm/checker-framework/issues/67
+
+import org.checkerframework.checker.index.qual.*;
+
+public class LiteralArray {
+
+  private static final String[] timeFormat = {
+    ("#.#"), ("#.#"), ("#.#"), ("#.#"), ("#.#"),
+  };
+
+  public String format() {
+    return format(1);
+  }
+
+  public String format(@IndexFor("LiteralArray.timeFormat") int digits) {
+    return "";
+  }
+}
diff --git a/checker/tests/index/LiteralString.java b/checker/tests/index/LiteralString.java
new file mode 100644
index 0000000..3e8866d
--- /dev/null
+++ b/checker/tests/index/LiteralString.java
@@ -0,0 +1,20 @@
+// Test case for issue #55:
+// https://github.com/kelloggm/checker-framework/issues/55
+
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class LiteralString {
+
+  private static final String[] finalField = {"This", "is", "an", "array"};
+
+  void testLiteralString() {
+    @MinLen(10) String s = "This string is long enough";
+  }
+
+  void testLiteralArray() {
+    String @MinLen(2) [] a = new String[] {"This", "array", "is", "long", "enough"};
+    String @MinLen(2) [] b = finalField;
+    @IndexFor("finalField") int i = 0;
+  }
+}
diff --git a/checker/tests/index/LongAndIntegerBitsMethods.java b/checker/tests/index/LongAndIntegerBitsMethods.java
new file mode 100644
index 0000000..1ce36bb
--- /dev/null
+++ b/checker/tests/index/LongAndIntegerBitsMethods.java
@@ -0,0 +1,14 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+public class LongAndIntegerBitsMethods {
+  void caseInteger(
+      int index, int @MinLen(33) [] arr1, int @MinLen(33) [] arr2, int val1, int val2) {
+    arr1[Integer.numberOfLeadingZeros(index)] = val1;
+    arr2[Integer.numberOfTrailingZeros(index)] = val2;
+  }
+
+  void caseLong(int index, int @MinLen(65) [] arr1, int @MinLen(65) [] arr2, int val1, int val2) {
+    arr1[Long.numberOfLeadingZeros(index)] = val1;
+    arr2[Long.numberOfTrailingZeros(index)] = val2;
+  }
+}
diff --git a/checker/tests/index/Loops.java b/checker/tests/index/Loops.java
new file mode 100644
index 0000000..1f83445
--- /dev/null
+++ b/checker/tests/index/Loops.java
@@ -0,0 +1,83 @@
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public final class Loops {
+  public static boolean flag = false;
+
+  public void test1a(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) {
+    while (flag) {
+      // :: error: (unary.increment)
+      offset++;
+    }
+  }
+
+  public void test1b(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) {
+    while (flag) {
+      // :: error: (compound.assignment)
+      offset += 1;
+    }
+  }
+
+  public void test1c(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) {
+    while (flag) {
+      // :: error: (compound.assignment)
+      offset2 += offset;
+    }
+  }
+
+  public void test2(int[] a, int[] array) {
+    int offset = array.length - 1;
+    int offset2 = array.length - 1;
+
+    while (flag) {
+      offset++;
+      offset2 += offset;
+    }
+    // :: error: (assignment)
+    @LTLengthOf("array") int x = offset;
+    // :: error: (assignment)
+    @LTLengthOf("array") int y = offset2;
+  }
+
+  public void test3(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) {
+    while (flag) {
+      offset--;
+      // :: error: (compound.assignment)
+      offset2 -= offset;
+    }
+  }
+
+  public void test4(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) {
+    while (flag) {
+      // :: error: (unary.increment)
+      offset++;
+      // :: error: (compound.assignment)
+      offset += 1;
+      // :: error: (compound.assignment)
+      offset2 += offset;
+    }
+  }
+
+  public void test4(int[] src) {
+    int patternLength = src.length;
+    int[] optoSft = new int[patternLength];
+    for (int i = patternLength; i > 0; i--) {}
+  }
+
+  public void test5(
+      int[] a,
+      @LTLengthOf(value = "#1", offset = "-1000") int offset,
+      @LTLengthOf("#1") int offset2) {
+    int otherOffset = offset;
+    while (flag) {
+      otherOffset += 1;
+      // :: error: (unary.increment)
+      offset++;
+      // :: error: (compound.assignment)
+      offset += 1;
+      // :: error: (compound.assignment)
+      offset2 += offset;
+    }
+    // :: error: (assignment)
+    @LTLengthOf(value = "#1", offset = "-1000") int x = otherOffset;
+  }
+}
diff --git a/checker/tests/index/LubIndex.java b/checker/tests/index/LubIndex.java
new file mode 100644
index 0000000..0cc87b6
--- /dev/null
+++ b/checker/tests/index/LubIndex.java
@@ -0,0 +1,44 @@
+import org.checkerframework.common.value.qual.BottomVal;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class LubIndex {
+
+  public static void MinLen(int @MinLen(10) [] arg, int @MinLen(4) [] arg2) {
+    int[] arr;
+    if (true) {
+      arr = arg;
+    } else {
+      arr = arg2;
+    }
+    // :: error: (assignment)
+    int @MinLen(10) [] res = arr;
+    int @MinLen(4) [] res2 = arr;
+    // :: error: (assignment)
+    int @BottomVal [] res3 = arr;
+  }
+
+  public static void Bottom(int @BottomVal [] arg, int @MinLen(4) [] arg2) {
+    int[] arr;
+    if (true) {
+      arr = arg;
+    } else {
+      arr = arg2;
+    }
+    // :: error: (assignment)
+    int @MinLen(10) [] res = arr;
+    int @MinLen(4) [] res2 = arr;
+    // :: error: (assignment)
+    int @BottomVal [] res3 = arr;
+  }
+
+  public static void BothBottom(int @BottomVal [] arg, int @BottomVal [] arg2) {
+    int[] arr;
+    if (true) {
+      arr = arg;
+    } else {
+      arr = arg2;
+    }
+    int @MinLen(10) [] res = arr;
+    int @BottomVal [] res2 = arr;
+  }
+}
diff --git a/checker/tests/index/MLEqualTo.java b/checker/tests/index/MLEqualTo.java
new file mode 100644
index 0000000..1f96bce
--- /dev/null
+++ b/checker/tests/index/MLEqualTo.java
@@ -0,0 +1,10 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+public class MLEqualTo {
+
+  public static void equalToMinLen(int @MinLen(2) [] m, int @MinLen(0) [] r) {
+    if (r == m) {
+      int @MinLen(2) [] j = r;
+    }
+  }
+}
diff --git a/checker/tests/index/MethodOverrides.java b/checker/tests/index/MethodOverrides.java
new file mode 100644
index 0000000..5cbf69b
--- /dev/null
+++ b/checker/tests/index/MethodOverrides.java
@@ -0,0 +1,20 @@
+// This class should not issues any errors from the value checker.
+// The index checker should issue the errors instead.
+
+// There is a copy of this test at checker/tests/value-index-interaction/MethodOverrides.java,
+// which does not include expected failures.
+
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+
+public class MethodOverrides {
+  @GTENegativeOne int read() {
+    return -1;
+  }
+}
+
+class MethodOverrides2 extends MethodOverrides {
+  // :: error: (override.return)
+  int read() {
+    return -1;
+  }
+}
diff --git a/checker/tests/index/MinLenFieldInvar.java b/checker/tests/index/MinLenFieldInvar.java
new file mode 100644
index 0000000..1794052
--- /dev/null
+++ b/checker/tests/index/MinLenFieldInvar.java
@@ -0,0 +1,63 @@
+package fieldinvar;
+
+import org.checkerframework.common.value.qual.BottomVal;
+import org.checkerframework.common.value.qual.MinLen;
+import org.checkerframework.common.value.qual.MinLenFieldInvariant;
+import org.checkerframework.framework.qual.FieldInvariant;
+
+public class MinLenFieldInvar {
+  class Super {
+    public final int @MinLen(2) [] minlen2;
+
+    public Super(int @MinLen(2) [] minlen2) {
+      this.minlen2 = minlen2;
+    }
+  }
+
+  // :: error: (field.invariant.not.subtype)
+  @MinLenFieldInvariant(field = "minlen2", minLen = 1)
+  class InvalidSub extends Super {
+    public InvalidSub() {
+      super(new int[] {1, 2});
+    }
+  }
+
+  @MinLenFieldInvariant(field = "minlen2", minLen = 4)
+  class ValidSub extends Super {
+    public final int[] validSubField;
+
+    public ValidSub(int[] validSubField) {
+      super(new int[] {1, 2, 3, 4});
+      this.validSubField = validSubField;
+    }
+  }
+
+  // :: error: (field.invariant.not.found.superclass)
+  @MinLenFieldInvariant(field = "validSubField", minLen = 3)
+  class InvalidSubSub1 extends ValidSub {
+    public InvalidSubSub1() {
+      super(new int[] {1, 2});
+    }
+  }
+
+  // :: error: (field.invariant.not.subtype.superclass)
+  @MinLenFieldInvariant(field = "minlen2", minLen = 3)
+  class InvalidSubSub2 extends ValidSub {
+    public InvalidSubSub2() {
+      super(new int[] {1, 2});
+    }
+  }
+
+  @FieldInvariant(field = "minlen2", qualifier = BottomVal.class)
+  @MinLenFieldInvariant(field = "validSubField", minLen = 4)
+  class ValidSubSub extends ValidSub {
+    public ValidSubSub() {
+      super(null);
+    }
+
+    void test() {
+      int @BottomVal [] bot = minlen2;
+      int @MinLen(4) [] four = validSubField;
+    }
+  }
+}
diff --git a/checker/tests/index/MinLenFourShenanigans.java b/checker/tests/index/MinLenFourShenanigans.java
new file mode 100644
index 0000000..6dcb8e1
--- /dev/null
+++ b/checker/tests/index/MinLenFourShenanigans.java
@@ -0,0 +1,23 @@
+package index;
+
+public class MinLenFourShenanigans {
+  public static boolean isInterned(Object value) {
+    if (value == null) {
+      // nothing to do
+      return true;
+    } else if (value instanceof String) {
+      // Used to issue the below error.
+      // MinLenFourShenanigans.java:7: warning: [cast.unsafe] "@MinLen(0) Object" may not be
+      // casted to the type "@MinLen(4) String"
+      return (value == ((String) value).intern());
+    }
+    return false;
+  }
+
+  public static boolean isInterned2(Object value) {
+    if (value instanceof String) {
+      return (value == ((String) value).intern());
+    }
+    return false;
+  }
+}
diff --git a/checker/tests/index/MinLenFromPositive.java b/checker/tests/index/MinLenFromPositive.java
new file mode 100644
index 0000000..2dc1a09
--- /dev/null
+++ b/checker/tests/index/MinLenFromPositive.java
@@ -0,0 +1,63 @@
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.common.value.qual.*;
+
+public class MinLenFromPositive {
+
+  void test(@Positive int x) {
+    int @MinLen(1) [] y = new int[x];
+    @IntRange(from = 1) int z = x;
+    @Positive int q = x;
+  }
+
+  @SuppressWarnings("index")
+  void foo(int x) {
+    test(x);
+  }
+
+  void foo2(int x) {
+    // :: error: (argument)
+    test(x);
+  }
+
+  void test_lub1(boolean flag, @Positive int x, @IntRange(from = 6, to = 25) int y) {
+    int z;
+    if (flag) {
+      z = x;
+    } else {
+      z = y;
+    }
+    @Positive int q = z;
+    @IntRange(from = 1) int w = z;
+  }
+
+  void test_lub2(boolean flag, @Positive int x, @IntRange(from = -1, to = 11) int y) {
+    int z;
+    if (flag) {
+      z = x;
+    } else {
+      z = y;
+    }
+    // :: error: (assignment)
+    @Positive int q = z;
+    @IntRange(from = -1) int w = z;
+  }
+
+  @Positive int id(@Positive int x) {
+    return x;
+  }
+
+  void test_id(int param) {
+    @Positive int x = id(5);
+    @IntRange(from = 1) int y = id(5);
+
+    int @MinLen(1) [] a = new int[id(100)];
+    // :: error: (assignment)
+    int @MinLen(10) [] c = new int[id(100)];
+
+    int q = id(10);
+
+    if (param == q) {
+      int @MinLen(1) [] d = new int[param];
+    }
+  }
+}
diff --git a/checker/tests/index/MinLenIndexFor.java b/checker/tests/index/MinLenIndexFor.java
new file mode 100644
index 0000000..9cdd5fc
--- /dev/null
+++ b/checker/tests/index/MinLenIndexFor.java
@@ -0,0 +1,41 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class MinLenIndexFor {
+  int @MinLen(2) [] arrayLen2 = {0, 1, 2};
+
+  void test(@IndexFor("this.arrayLen2") int i) {
+    int j = arrayLen2[i];
+    int j2 = arrayLen2[1];
+  }
+
+  void callTest(int x) {
+    test(0);
+    test(1);
+    // :: error: (argument)
+    test(2);
+    // :: error: (argument)
+    test(3);
+    test(arrayLen2.length - 1);
+  }
+
+  int @MinLen(4) [] arrayLen4 = {0, 1, 2, 4, 5};
+
+  void test2(@IndexOrHigh("this.arrayLen4") int i) {
+    if (i > 0) {
+      int j = arrayLen4[i - 1];
+    }
+    int j2 = arrayLen4[1];
+  }
+
+  void callTest2(int x) {
+    test2(0);
+    test2(1);
+    test2(2);
+    test2(4);
+    // :: error: (argument)
+    test2(5);
+    test2(arrayLen4.length);
+  }
+}
diff --git a/checker/tests/index/MinLenOneAndLength.java b/checker/tests/index/MinLenOneAndLength.java
new file mode 100644
index 0000000..5cb6811
--- /dev/null
+++ b/checker/tests/index/MinLenOneAndLength.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class MinLenOneAndLength {
+  public void m1(int @MinLen(1) [] a, int[] b) {
+    @IndexFor("a") int i = a.length / 2;
+    // :: error: (assignment)
+    @IndexFor("b") int j = b.length / 2;
+  }
+}
diff --git a/checker/tests/index/MinLenSameLenInteraction.java b/checker/tests/index/MinLenSameLenInteraction.java
new file mode 100644
index 0000000..51ac8a7
--- /dev/null
+++ b/checker/tests/index/MinLenSameLenInteraction.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class MinLenSameLenInteraction {
+  void test(int @SameLen("#2") [] a, int @SameLen("#1") [] b) {
+    if (a.length == 1) {
+      int x = b[0];
+    }
+  }
+}
diff --git a/checker/tests/index/MinMax.java b/checker/tests/index/MinMax.java
new file mode 100644
index 0000000..fe69377
--- /dev/null
+++ b/checker/tests/index/MinMax.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class MinMax {
+  // They call me a power gamer. I stole the test cases from issue 26.
+  void mathmax() {
+    @Positive int i = Math.max(-15, 2);
+  }
+
+  void mathmin() {
+    @GTENegativeOne int i = Math.min(-1, 2);
+  }
+}
diff --git a/checker/tests/index/MinMaxIndex.java b/checker/tests/index/MinMaxIndex.java
new file mode 100644
index 0000000..3e72c51
--- /dev/null
+++ b/checker/tests/index/MinMaxIndex.java
@@ -0,0 +1,33 @@
+// Tests handling Math.min and Math.max methods.
+// The upper bound of Math.max is issue panacekcz#20:
+// https://github.com/panacekcz/checker-framework/issues/20
+
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+
+public class MinMaxIndex {
+  // Both min and max preserve IndexFor
+  void indexFor(char[] array, @IndexFor("#1") int i1, @IndexFor("#1") int i2) {
+    char c = array[Math.max(i1, i2)];
+    char d = array[Math.min(i1, i2)];
+  }
+  // Both min and max preserve IndexOrHigh
+  void indexOrHigh(String str, @IndexOrHigh("#1") int i1, @IndexOrHigh("#1") int i2) {
+    str.substring(Math.max(i1, i2));
+    str.substring(Math.min(i1, i2));
+  }
+  // Combining IndexFor and IndexOrHigh
+  void indexForOrHigh(String str, @IndexFor("#1") int i1, @IndexOrHigh("#1") int i2) {
+    str.substring(Math.max(i1, i2));
+    str.substring(Math.min(i1, i2));
+    // :: error: (argument)
+    str.charAt(Math.max(i1, i2));
+    str.charAt(Math.min(i1, i2));
+  }
+  // max does not work with different sequences, min does
+  void twoSequences(String str1, String str2, @IndexFor("#1") int i1, @IndexFor("#2") int i2) {
+    // :: error: (argument)
+    str1.charAt(Math.max(i1, i2));
+    str1.charAt(Math.min(i1, i2));
+  }
+}
diff --git a/checker/tests/index/Modulo.java b/checker/tests/index/Modulo.java
new file mode 100644
index 0000000..313200d
--- /dev/null
+++ b/checker/tests/index/Modulo.java
@@ -0,0 +1,26 @@
+// Test case for kelloggm 218
+// https://github.com/kelloggm/checker-framework/issues/218
+
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class Modulo {
+  void m1(Object[] a, @IndexOrHigh("#1") int i, @NonNegative int j) {
+    @IndexFor("a") int k = j % i;
+  }
+
+  void m1p(Object[] a, @Positive @LTEqLengthOf("#1") int i, @Positive int j) {
+    @IndexFor("a") int k = j % i;
+  }
+
+  void m2(Object[] a, int i, @IndexFor("#1") int j) {
+    @IndexFor("a") int k = j % i;
+  }
+
+  void m2(Object[] a, Object[] b, @IndexFor("#1") int i, @IndexFor("#2") int j) {
+    @IndexFor({"a", "b"}) int k = j % i;
+  }
+}
diff --git a/checker/tests/index/NegativeArray.java b/checker/tests/index/NegativeArray.java
new file mode 100644
index 0000000..7335ce6
--- /dev/null
+++ b/checker/tests/index/NegativeArray.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+
+public class NegativeArray {
+
+  public static void negativeArray(@GTENegativeOne int len) {
+    // :: error: (array.length.negative)
+    int[] arr = new int[len];
+  }
+}
diff --git a/checker/tests/index/NegativeIndex.java b/checker/tests/index/NegativeIndex.java
new file mode 100644
index 0000000..f21fc10
--- /dev/null
+++ b/checker/tests/index/NegativeIndex.java
@@ -0,0 +1,11 @@
+// Test case for kelloggm#216
+// https://github.com/kelloggm/checker-framework/issues/216
+
+import org.checkerframework.common.value.qual.*;
+
+public class NegativeIndex {
+  @SuppressWarnings("lowerbound")
+  void m(int[] a) {
+    int x = a[-100];
+  }
+}
diff --git a/checker/tests/index/NonNegArrayLength.java b/checker/tests/index/NonNegArrayLength.java
new file mode 100644
index 0000000..7c14042
--- /dev/null
+++ b/checker/tests/index/NonNegArrayLength.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class NonNegArrayLength {
+
+  public static void NonNegArrayLength(int @MinLen(4) [] arr) {
+    @Positive int i = arr.length - 2;
+  }
+}
diff --git a/checker/tests/index/NonNegativeCharValue.java b/checker/tests/index/NonNegativeCharValue.java
new file mode 100644
index 0000000..4dcfa45
--- /dev/null
+++ b/checker/tests/index/NonNegativeCharValue.java
@@ -0,0 +1,7 @@
+import org.checkerframework.checker.index.qual.NonNegative;
+
+public class NonNegativeCharValue {
+  public static String toString(final @NonNegative Character ch) {
+    return toString(ch.charValue());
+  }
+}
diff --git a/checker/tests/index/NonnegativeChar.java b/checker/tests/index/NonnegativeChar.java
new file mode 100644
index 0000000..9b8faca
--- /dev/null
+++ b/checker/tests/index/NonnegativeChar.java
@@ -0,0 +1,37 @@
+import java.util.ArrayList;
+import org.checkerframework.checker.index.qual.LowerBoundBottom;
+import org.checkerframework.checker.index.qual.PolyLowerBound;
+
+public class NonnegativeChar {
+  void foreach(char[] array) {
+    for (char value : array) {}
+  }
+
+  char constant() {
+    return Character.MAX_VALUE;
+  }
+
+  char conversion(int i) {
+    return (char) i;
+  }
+
+  public void takeList(ArrayList<Character> z) {}
+
+  public void passList() {
+    takeList(new ArrayList<Character>());
+  }
+
+  static class CustomList extends ArrayList<Character> {}
+
+  public void passCustomList() {
+    takeList(new CustomList());
+  }
+
+  public @LowerBoundBottom char bottomLB(@LowerBoundBottom char c) {
+    return c;
+  }
+
+  public @PolyLowerBound char polyLB(@PolyLowerBound char c) {
+    return c;
+  }
+}
diff --git a/checker/tests/index/NotEnoughOffsets.java b/checker/tests/index/NotEnoughOffsets.java
new file mode 100644
index 0000000..d311537
--- /dev/null
+++ b/checker/tests/index/NotEnoughOffsets.java
@@ -0,0 +1,22 @@
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class NotEnoughOffsets {
+
+  int[] a;
+  int[] b;
+  int c, d;
+
+  void badParam(
+      // :: error: (different.length.sequences.offsets)
+      @LTLengthOf(
+              value = {"a", "b"},
+              offset = {"c"})
+          int x) {}
+
+  void badParam2(
+      // :: error: (different.length.sequences.offsets)
+      @LTLengthOf(
+              value = {"a"},
+              offset = {"c", "d"})
+          int x) {}
+}
diff --git a/checker/tests/index/NotEqualTransfer.java b/checker/tests/index/NotEqualTransfer.java
new file mode 100644
index 0000000..cedf58f
--- /dev/null
+++ b/checker/tests/index/NotEqualTransfer.java
@@ -0,0 +1,26 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+public class NotEqualTransfer {
+  void neq_check(int[] a) {
+    if (1 != a.length) {
+      int x = 1; // do nothing.
+    } else {
+      int @MinLen(1) [] b = a;
+    }
+  }
+
+  void neq_bad_check(int[] a) {
+    if (1 != a.length) {
+      int x = 1; // do nothing.
+    } else {
+      // :: error: (assignment)
+      int @MinLen(2) [] b = a;
+    }
+  }
+
+  void neq_zero_special_case(int[] a) {
+    if (a.length != 0) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+}
diff --git a/checker/tests/index/ObjectClone.java b/checker/tests/index/ObjectClone.java
new file mode 100644
index 0000000..ad7f8fc
--- /dev/null
+++ b/checker/tests/index/ObjectClone.java
@@ -0,0 +1,27 @@
+// Test case for issue 146: https://github.com/kelloggm/checker-framework/issues/146
+
+import java.util.Arrays;
+import org.checkerframework.checker.index.qual.*;
+
+public class ObjectClone {
+
+  void test(int[] a, int @SameLen("#1") [] b) {
+    int @SameLen("a") [] c = b.clone();
+    int @SameLen({"a", "d"}) [] d = b.clone();
+    int @SameLen({"a", "e"}) [] e = b;
+    int @SameLen("f") [] f = b;
+  }
+
+  public static void main(String[] args) {
+    String @SameLen("args") [] args2 = args;
+    String @SameLen({"args", "args_sorted"}) [] args_sorted = args.clone();
+    Arrays.sort(args_sorted);
+    String @SameLen({"args", "args_sorted"}) [] args_sorted2 = args_sorted.clone();
+    if (args_sorted.length == 1) {
+      @IndexFor("args_sorted") int i = 0;
+      @IndexFor("args") int j = 0;
+      String @SameLen({"args", "args_sorted"}) [] k = args;
+      System.out.println(args[0]);
+    }
+  }
+}
diff --git a/checker/tests/index/Offset97.java b/checker/tests/index/Offset97.java
new file mode 100644
index 0000000..057736f
--- /dev/null
+++ b/checker/tests/index/Offset97.java
@@ -0,0 +1,14 @@
+// Test case for issue 97: https://github.com/kelloggm/checker-framework/issues/97
+
+import org.checkerframework.checker.index.qual.*;
+
+public class Offset97 {
+  public static void m2() {
+    int[] a = {1, 2, 3, 4, 5};
+    @IndexFor("a") int i = 4;
+    @IndexFor("a") int j = 4;
+    if (j < a.length - i) {
+      @IndexFor("a") int k = i + j;
+    }
+  }
+}
diff --git a/checker/tests/index/OffsetAnnotations.java b/checker/tests/index/OffsetAnnotations.java
new file mode 100644
index 0000000..8150f5d
--- /dev/null
+++ b/checker/tests/index/OffsetAnnotations.java
@@ -0,0 +1,17 @@
+import java.io.*;
+
+public class OffsetAnnotations {
+  public static void OffsetAnnotationsReader() throws IOException {
+    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
+    char[] buffer = new char[10];
+    // :: error: (argument)
+    bufferedReader.read(buffer, 5, 7);
+  }
+
+  public static void OffsetAnnotationsWriter() throws IOException {
+    BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));
+    char[] buffer = new char[10];
+    // :: error: (argument)
+    bufferedWriter.write(buffer, 5, 7);
+  }
+}
diff --git a/checker/tests/index/OffsetExample.java b/checker/tests/index/OffsetExample.java
new file mode 100644
index 0000000..5adf372
--- /dev/null
+++ b/checker/tests/index/OffsetExample.java
@@ -0,0 +1,86 @@
+import java.util.List;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.common.value.qual.MinLen;
+
+@SuppressWarnings("lowerbound")
+public class OffsetExample {
+  void example1(int @MinLen(2) [] a) {
+    int j = 2;
+    int x = a.length;
+    int y = x - j;
+    for (int i = 0; i < y; i++) {
+      a[i + j] = 1;
+    }
+  }
+
+  void example2(int @MinLen(2) [] a) {
+    int j = 2;
+    int x = a.length;
+    int y = x - j;
+    a[y] = 0;
+    for (int i = 0; i < y; i++) {
+      a[i + j] = 1;
+      a[j + i] = 1;
+      a[i + 0] = 1;
+      a[i - 1] = 1;
+      // ::error: (array.access.unsafe.high)
+      a[i + 2 + j] = 1;
+    }
+  }
+
+  void example3(int @MinLen(2) [] a) {
+    int j = 2;
+    for (int i = 0; i < a.length - 2; i++) {
+      a[i + j] = 1;
+    }
+  }
+
+  void example4(int[] a, int offset) {
+    int max_index = a.length - offset;
+    for (int i = 0; i < max_index; i++) {
+      a[i + offset] = 0;
+    }
+  }
+
+  void example5(int[] a, int offset) {
+    for (int i = 0; i < a.length - offset; i++) {
+      a[i + offset] = 0;
+    }
+  }
+
+  void test(@IndexFor("#3") int start, @IndexOrHigh("#3") int end, int[] a) {
+    if (end > start) {
+      // If start == 0, then end - start is end.  end might be equal to the length of a.  So
+      // the array access might be too high.
+      // ::error: (array.access.unsafe.high)
+      a[end - start] = 0;
+    }
+
+    if (end > start) {
+      a[end - start - 1] = 0;
+    }
+  }
+
+  public static boolean isSubarray(Object[] a, Object[] sub, int a_offset) {
+    int a_len = a.length - a_offset;
+    int sub_len = sub.length;
+    if (a_len < sub_len) {
+      return false;
+    }
+    for (int i = 0; i < sub_len; i++) {
+      if (sub[i] == a[a_offset + i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  public void test2(int[] a, List<Object> b) {
+    int b_size = b.size();
+    Object[] result = new Object[a.length + b_size];
+    for (int i = 0; i < b_size; i++) {
+      result[i + a.length] = b.get(i);
+    }
+  }
+}
diff --git a/checker/tests/index/OffsetsAndConstants.java b/checker/tests/index/OffsetsAndConstants.java
new file mode 100644
index 0000000..c9d01e4
--- /dev/null
+++ b/checker/tests/index/OffsetsAndConstants.java
@@ -0,0 +1,30 @@
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.NonNegative;
+
+public class OffsetsAndConstants {
+  static int read(
+      char[] a,
+      @IndexOrHigh("#1") int off,
+      @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int len) {
+    int sum = 0;
+    for (int i = 0; i < len; i++) {
+      sum += a[i + off];
+    }
+    return sum;
+  }
+
+  public static void main(String[] args) {
+    char[] a = new char[10];
+
+    read(a, 5, 4);
+
+    read(a, 5, 5);
+
+    // :: error: (argument)
+    read(a, 5, 6);
+
+    // :: error: (argument)
+    read(a, 5, 7);
+  }
+}
diff --git a/checker/tests/index/OneLTL.java b/checker/tests/index/OneLTL.java
new file mode 100644
index 0000000..de83bb6
--- /dev/null
+++ b/checker/tests/index/OneLTL.java
@@ -0,0 +1,10 @@
+public class OneLTL {
+  public static boolean sorted(int[] a) {
+    for (int i = 0; i < a.length - 1; i++) {
+      if (a[i + 1] < a[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/checker/tests/index/OneOrTwo.java b/checker/tests/index/OneOrTwo.java
new file mode 100644
index 0000000..7718be2
--- /dev/null
+++ b/checker/tests/index/OneOrTwo.java
@@ -0,0 +1,17 @@
+import org.checkerframework.common.value.qual.*;
+
+public class OneOrTwo {
+  @IntVal({1, 2}) int getOneOrTwo() {
+    return 1;
+  }
+
+  void test(@BottomVal int x) {
+    int[] a = new int[Integer.valueOf(getOneOrTwo())];
+    // :: error: (array.length.negative)
+    int[] b = new int[Integer.valueOf(x)];
+  }
+
+  @PolyValue int poly(@PolyValue int y) {
+    return y;
+  }
+}
diff --git a/checker/tests/index/OnlyCheckSubsequenceWhenAssigningToArray.java b/checker/tests/index/OnlyCheckSubsequenceWhenAssigningToArray.java
new file mode 100644
index 0000000..a7780d2
--- /dev/null
+++ b/checker/tests/index/OnlyCheckSubsequenceWhenAssigningToArray.java
@@ -0,0 +1,27 @@
+import org.checkerframework.checker.index.qual.HasSubsequence;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+
+public class OnlyCheckSubsequenceWhenAssigningToArray {
+  @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end")
+  int[] array;
+
+  final @IndexFor("array") int start;
+
+  final @IndexOrHigh("array") int end;
+
+  private OnlyCheckSubsequenceWhenAssigningToArray(
+      @IndexFor("array") int s, @IndexOrHigh("array") int e) {
+    start = s;
+    end = e;
+  }
+
+  void testAssignmentToArrayElement(@IndexFor("this") int x, int y) {
+    array[start + x] = y;
+  }
+
+  void testAssignmentToArray(int[] a) {
+    // :: error: to.not.ltel :: error: from.gt.to
+    array = a;
+  }
+}
diff --git a/checker/tests/index/OuterThisJavaExpression.java b/checker/tests/index/OuterThisJavaExpression.java
new file mode 100644
index 0000000..ef99789
--- /dev/null
+++ b/checker/tests/index/OuterThisJavaExpression.java
@@ -0,0 +1,59 @@
+// Test case for issue #4558: https://tinyurl.com/cfissue/4558
+
+// @skip-test until the issue is fixed
+
+import org.checkerframework.checker.index.qual.SameLen;
+
+public abstract class OuterThisJavaExpression {
+
+  String s;
+
+  OuterThisJavaExpression(String s) {
+    this.s = s;
+  }
+
+  final class Inner {
+
+    String s = "different from " + OuterThisJavaExpression.this.s;
+
+    @SameLen("s") String f1() {
+      return s;
+    }
+
+    @SameLen("s") String f2() {
+      return this.s;
+    }
+
+    @SameLen("s") String f3() {
+      // :: error: (return)
+      return OuterThisJavaExpression.this.s;
+    }
+
+    @SameLen("this.s") String f4() {
+      return s;
+    }
+
+    @SameLen("this.s") String f5() {
+      return this.s;
+    }
+
+    @SameLen("this.s") String f6() {
+      // :: error: (return)
+      return OuterThisJavaExpression.this.s;
+    }
+
+    @SameLen("OuterThisJavaExpression.this.s") String f7() {
+      // :: error: (return)
+      return s;
+    }
+
+    @SameLen("OuterThisJavaExpression.this.s") String f8() {
+      // :: error: (return)
+      return this.s;
+    }
+
+    @SameLen("OuterThisJavaExpression.this.s") String f9() {
+      return OuterThisJavaExpression.this.s;
+    }
+  }
+}
diff --git a/checker/tests/index/ParserOffsetTest.java b/checker/tests/index/ParserOffsetTest.java
new file mode 100644
index 0000000..1305fd4
--- /dev/null
+++ b/checker/tests/index/ParserOffsetTest.java
@@ -0,0 +1,93 @@
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.common.value.qual.*;
+
+public class ParserOffsetTest {
+
+  public void subtraction1(String[] a, @IndexFor("#1") int i) {
+    int length = a.length;
+    if (i >= length - 1 || a[i + 1] == null) {
+      // body is irrelevant
+    }
+  }
+
+  public void addition1(String[] a, @IndexFor("#1") int i) {
+    int length = a.length;
+    if ((i + 1) >= length || a[i + 1] == null) {
+      // body is irrelevant
+    }
+  }
+
+  public void subtraction2(String[] a, @IndexFor("#1") int i) {
+    if (i < a.length - 1) {
+      @IndexFor("a") int j = i + 1;
+    }
+  }
+
+  public void addition2(String[] a, @IndexFor("#1") int i) {
+    if ((i + 1) < a.length) {
+      @IndexFor("a") int j = i + 1;
+    }
+  }
+
+  public void addition3(String[] a, @IndexFor("#1") int i) {
+    if ((i + 5) < a.length) {
+      @IndexFor("a") int j = i + 5;
+    }
+  }
+
+  @SuppressWarnings("lowerbound")
+  public void subtraction3(String[] a, @NonNegative int k) {
+    if (k - 5 < a.length) {
+      String s = a[k - 5];
+      @IndexFor("a") int j = k - 5;
+    }
+  }
+
+  @SuppressWarnings("lowerbound")
+  public void subtraction4(String[] a, @IndexFor("#1") int i) {
+    if (1 - i < a.length) {
+      // The error on this assignment is a false positive.
+      // :: error: (assignment)
+      @IndexFor("a") int j = 1 - i;
+
+      // :: error: (assignment)
+      @LTLengthOf(value = "a", offset = "1") int k = i;
+    }
+  }
+
+  @SuppressWarnings("lowerbound")
+  public void subtraction5(String[] a, int i) {
+    if (1 - i < a.length) {
+      // :: error: (assignment)
+      @IndexFor("a") int j = i;
+    }
+  }
+
+  @SuppressWarnings("lowerbound")
+  public void subtraction6(String[] a, int i, int j) {
+    if (i - j < a.length - 1) {
+      @IndexFor("a") int k = i - j;
+      // :: error: (assignment)
+      @IndexFor("a") int k1 = i;
+    }
+  }
+
+  public void multiplication1(String[] a, int i, @Positive int j) {
+    if ((i * j) < (a.length + j)) {
+      // :: error: (assignment)
+      @IndexFor("a") int k = i;
+      // :: error: (assignment)
+      @IndexFor("a") int k1 = j;
+    }
+  }
+
+  public void multiplication2(String @ArrayLen(5) [] a, @IntVal(-2) int i, @IntVal(20) int j) {
+    if ((i * j) < (a.length - 20)) {
+      @LTLengthOf("a") int k1 = i;
+      // :: error: (assignment)
+      @LTLengthOf(value = "a", offset = "20") int k2 = i;
+      // :: error: (assignment)
+      @LTLengthOf("a") int k3 = j;
+    }
+  }
+}
diff --git a/checker/tests/index/ParsingBug.java b/checker/tests/index/ParsingBug.java
new file mode 100644
index 0000000..153e559
--- /dev/null
+++ b/checker/tests/index/ParsingBug.java
@@ -0,0 +1,10 @@
+public class ParsingBug {
+  void test() {
+    String[] saOrig = new String[] {"foo", "bar"};
+    Object o1 = do_things((Object) saOrig);
+  }
+
+  Object do_things(Object o) {
+    return o;
+  }
+}
diff --git a/checker/tests/index/Pilot2HalfLength.java b/checker/tests/index/Pilot2HalfLength.java
new file mode 100644
index 0000000..04696f1
--- /dev/null
+++ b/checker/tests/index/Pilot2HalfLength.java
@@ -0,0 +1,13 @@
+// test case for issue 158: https://github.com/kelloggm/checker-framework/issues/158
+
+// @skip-test until fixed
+
+public class Pilot2HalfLength {
+  private static int[] getFirstHalf(int[] array) {
+    int[] firstHalf = new int[array.length / 2];
+    for (int i = 0; i < firstHalf.length; i++) {
+      firstHalf[i] = array[i];
+    }
+    return firstHalf;
+  }
+}
diff --git a/checker/tests/index/Pilot3ArrayCreation.java b/checker/tests/index/Pilot3ArrayCreation.java
new file mode 100644
index 0000000..6494c07
--- /dev/null
+++ b/checker/tests/index/Pilot3ArrayCreation.java
@@ -0,0 +1,10 @@
+// This test case is for issue 44: https://github.com/kelloggm/checker-framework/issues/44
+
+public class Pilot3ArrayCreation {
+  void test(int[] firstArray, int[] secondArray[]) {
+    int[] newArray = new int[firstArray.length + secondArray.length];
+    for (int i = 0; i < firstArray.length; i++) {
+      newArray[i] = firstArray[i]; // or newArray[i] = secondArray[i];
+    }
+  }
+}
diff --git a/checker/tests/index/Pilot4Subtraction.java b/checker/tests/index/Pilot4Subtraction.java
new file mode 100644
index 0000000..36e85c4
--- /dev/null
+++ b/checker/tests/index/Pilot4Subtraction.java
@@ -0,0 +1,17 @@
+// Test case for issue 42: https://github.com/kelloggm/checker-framework/issues/42
+
+// @skip-test until bug is fixed
+
+public class Pilot4Subtraction {
+
+  private static int[] getSecondHalf(int[] array) {
+    int len = array.length / 2;
+    int b = len - 1;
+    int[] arr = new int[len];
+    for (int a = 0; a < len; a++) {
+      arr[a] = array[b];
+      b--;
+    }
+    return arr;
+  }
+}
diff --git a/checker/tests/index/PlumeFail.java b/checker/tests/index/PlumeFail.java
new file mode 100644
index 0000000..12fe95a
--- /dev/null
+++ b/checker/tests/index/PlumeFail.java
@@ -0,0 +1,18 @@
+// Test case affected by eisop Issue 22:
+// https://github.com/eisop/checker-framework/issues/22
+
+import java.util.Arrays;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class PlumeFail {
+  void method() {
+    // Workaround by casting.
+    @SuppressWarnings({"index", "value"})
+    String @MinLen(1) [] args = (String @MinLen(1) []) getArray();
+    String[] argArray = Arrays.copyOfRange(args, 1, args.length);
+  }
+
+  String[] getArray() {
+    return null;
+  }
+}
diff --git a/checker/tests/index/PlumeFailMin.java b/checker/tests/index/PlumeFailMin.java
new file mode 100644
index 0000000..5ae51d9
--- /dev/null
+++ b/checker/tests/index/PlumeFailMin.java
@@ -0,0 +1,23 @@
+// Test case for eisop Issue 22:
+// https://github.com/eisop/checker-framework/issues/22
+
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.common.value.qual.MinLen;
+
+abstract class PlumeFailMin {
+  void ok() {
+    String @MinLen(1) [] args = getArrayOk();
+    @IndexOrHigh("args") int x = 1;
+  }
+
+  abstract String @MinLen(1) [] getArrayOk();
+
+  void fail() {
+    // Workaround by casting.
+    @SuppressWarnings({"index", "value"})
+    String @MinLen(1) [] args = (String @MinLen(1) []) getArrayFail();
+    @IndexOrHigh("args") int x = 1;
+  }
+
+  abstract String[] getArrayFail();
+}
diff --git a/checker/tests/index/PlusPlusBug.java b/checker/tests/index/PlusPlusBug.java
new file mode 100644
index 0000000..ca93170
--- /dev/null
+++ b/checker/tests/index/PlusPlusBug.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class PlusPlusBug {
+  int[] array = {};
+
+  void test(@LTLengthOf("array") int x) {
+    // :: error: (unary.increment)
+    x++;
+    // :: error: (unary.increment)
+    ++x;
+    // :: error: (assignment)
+    x = x + 1;
+  }
+}
diff --git a/checker/tests/index/PolyCrash.java b/checker/tests/index/PolyCrash.java
new file mode 100644
index 0000000..4db86d6
--- /dev/null
+++ b/checker/tests/index/PolyCrash.java
@@ -0,0 +1,5 @@
+public class PolyCrash {
+  void test1(Integer integer) {
+    ++integer;
+  }
+}
diff --git a/checker/tests/index/PolyLengthTest.java b/checker/tests/index/PolyLengthTest.java
new file mode 100644
index 0000000..5220581
--- /dev/null
+++ b/checker/tests/index/PolyLengthTest.java
@@ -0,0 +1,20 @@
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.common.value.qual.*;
+
+public class PolyLengthTest {
+  int @PolyLength [] id(int @PolyLength [] a) {
+    return a;
+  }
+
+  int @SameLen("#2") [] test0(int @SameLen("#2") [] a, int @SameLen("#1") [] b) {
+    return id(a);
+  }
+
+  int @ArrayLen(3) [] test1(int @ArrayLen(3) [] a) {
+    return id(a);
+  }
+
+  int @MinLen(3) [] test2(int @MinLen(3) [] a) {
+    return id(a);
+  }
+}
diff --git a/checker/tests/index/Polymorphic.java b/checker/tests/index/Polymorphic.java
new file mode 100644
index 0000000..d462c9a
--- /dev/null
+++ b/checker/tests/index/Polymorphic.java
@@ -0,0 +1,75 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.PolyLowerBound;
+import org.checkerframework.checker.index.qual.PolySameLen;
+import org.checkerframework.checker.index.qual.PolyUpperBound;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.checker.index.qual.SameLen;
+
+public class Polymorphic {
+
+  // Identity functions
+
+  @PolyLowerBound int lbc_identity(@PolyLowerBound int a) {
+    return a;
+  }
+
+  int @PolySameLen [] samelen_identity(int @PolySameLen [] a) {
+    int @SameLen("a") [] x = a;
+    return a;
+  }
+
+  @PolyUpperBound int ubc_identity(@PolyUpperBound int a) {
+    return a;
+  }
+
+  // SameLen tests
+  void samelen_id(int @SameLen("#2") [] a, int[] a2) {
+    int[] banana;
+    int @SameLen("a2") [] b = samelen_identity(a);
+    // :: error: (assignment)
+    int @SameLen("banana") [] c = samelen_identity(b);
+  }
+
+  // UpperBound tests
+  void ubc_id(
+      int[] a,
+      int[] b,
+      @LTLengthOf("#1") int ai,
+      @LTEqLengthOf("#1") int al,
+      @LTLengthOf({"#1", "#2"}) int abi,
+      @LTEqLengthOf({"#1", "#2"}) int abl) {
+    int[] c;
+
+    @LTLengthOf("a") int ai1 = ubc_identity(ai);
+    // :: error: (assignment)
+    @LTLengthOf("b") int ai2 = ubc_identity(ai);
+
+    @LTEqLengthOf("a") int al1 = ubc_identity(al);
+    // :: error: (assignment)
+    @LTLengthOf("a") int al2 = ubc_identity(al);
+
+    @LTLengthOf({"a", "b"}) int abi1 = ubc_identity(abi);
+    // :: error: (assignment)
+    @LTLengthOf({"a", "b", "c"}) int abi2 = ubc_identity(abi);
+
+    @LTEqLengthOf({"a", "b"}) int abl1 = ubc_identity(abl);
+    // :: error: (assignment)
+    @LTEqLengthOf({"a", "b", "c"}) int abl2 = ubc_identity(abl);
+  }
+
+  // LowerBound tests
+  void lbc_id(@NonNegative int n, @Positive int p, @GTENegativeOne int g) {
+    @NonNegative int an = lbc_identity(n);
+    // :: error: (assignment)
+    @Positive int bn = lbc_identity(n);
+
+    @GTENegativeOne int ag = lbc_identity(g);
+    // :: error: (assignment)
+    @NonNegative int bg = lbc_identity(g);
+
+    @Positive int ap = lbc_identity(p);
+  }
+}
diff --git a/checker/tests/index/Polymorphic2.java b/checker/tests/index/Polymorphic2.java
new file mode 100644
index 0000000..ed657cb
--- /dev/null
+++ b/checker/tests/index/Polymorphic2.java
@@ -0,0 +1,51 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.PolyLowerBound;
+import org.checkerframework.checker.index.qual.PolySameLen;
+import org.checkerframework.checker.index.qual.PolyUpperBound;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.checker.index.qual.SameLen;
+
+public class Polymorphic2 {
+  public static boolean flag = false;
+
+  int @PolySameLen [] mergeSameLen(int @PolySameLen [] a, int @PolySameLen [] b) {
+    return flag ? a : b;
+  }
+
+  int[] array1 = new int[2];
+  int[] array2 = new int[2];
+
+  void testSameLen(int @SameLen("array1") [] a, int @SameLen("array2") [] b) {
+    int[] x = mergeSameLen(a, b);
+    // :: error: (assignment)
+    int @SameLen("array1") [] y = mergeSameLen(a, b);
+  }
+
+  @PolyUpperBound int mergeUpperBound(@PolyUpperBound int a, @PolyUpperBound int b) {
+    return flag ? a : b;
+  }
+  // UpperBound tests
+  void testUpperBound(@LTLengthOf("array1") int a, @LTLengthOf("array2") int b) {
+    int z = mergeUpperBound(a, b);
+    // :: error: (assignment)
+    @LTLengthOf("array1") int zz = mergeUpperBound(a, b);
+  }
+
+  void testUpperBound2(@LTLengthOf("array1") int a, @LTEqLengthOf("array1") int b) {
+    @LTEqLengthOf("array1") int z = mergeUpperBound(a, b);
+    // :: error: (assignment)
+    @LTLengthOf("array1") int zz = mergeUpperBound(a, b);
+  }
+
+  @PolyLowerBound int mergeLowerBound(@PolyLowerBound int a, @PolyLowerBound int b) {
+    return flag ? a : b;
+  }
+  // LowerBound tests
+  void lbc_id(@NonNegative int n, @Positive int p) {
+    @NonNegative int z = mergeLowerBound(n, p);
+    // :: error: (assignment)
+    @Positive int zz = mergeLowerBound(n, p);
+  }
+}
diff --git a/checker/tests/index/Polymorphic3.java b/checker/tests/index/Polymorphic3.java
new file mode 100644
index 0000000..7ed315a
--- /dev/null
+++ b/checker/tests/index/Polymorphic3.java
@@ -0,0 +1,50 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class Polymorphic3 {
+
+  // Identity functions
+
+  @PolyIndex int identity(@PolyIndex int a) {
+    return a;
+  }
+
+  // UpperBound tests
+  void ubc_id(
+      int[] a,
+      int[] b,
+      @LTLengthOf("#1") int ai,
+      @LTEqLengthOf("#1") int al,
+      @LTLengthOf({"#1", "#2"}) int abi,
+      @LTEqLengthOf({"#1", "#2"}) int abl) {
+    int[] c;
+
+    @LTLengthOf("a") int ai1 = identity(ai);
+    // :: error: (assignment)
+    @LTLengthOf("b") int ai2 = identity(ai);
+
+    @LTEqLengthOf("a") int al1 = identity(al);
+    // :: error: (assignment)
+    @LTLengthOf("a") int al2 = identity(al);
+
+    @LTLengthOf({"a", "b"}) int abi1 = identity(abi);
+    // :: error: (assignment)
+    @LTLengthOf({"a", "b", "c"}) int abi2 = identity(abi);
+
+    @LTEqLengthOf({"a", "b"}) int abl1 = identity(abl);
+    // :: error: (assignment)
+    @LTEqLengthOf({"a", "b", "c"}) int abl2 = identity(abl);
+  }
+
+  // LowerBound tests
+  void lbc_id(@NonNegative int n, @Positive int p, @GTENegativeOne int g) {
+    @NonNegative int an = identity(n);
+    // :: error: (assignment)
+    @Positive int bn = identity(n);
+
+    @GTENegativeOne int ag = identity(g);
+    // :: error: (assignment)
+    @NonNegative int bg = identity(g);
+
+    @Positive int ap = identity(p);
+  }
+}
diff --git a/checker/tests/index/Polymorphic4.java b/checker/tests/index/Polymorphic4.java
new file mode 100644
index 0000000..f57677b
--- /dev/null
+++ b/checker/tests/index/Polymorphic4.java
@@ -0,0 +1,13 @@
+import org.checkerframework.common.value.qual.PolyValue;
+
+// @skip-test until #153 is resolved.
+
+public class Polymorphic4 {
+
+  public static String @PolyValue [] quantify(String @PolyValue [] vars) {
+
+    String[] result = new String[vars.length];
+
+    return result;
+  }
+}
diff --git a/checker/tests/index/PreAndPostDec.java b/checker/tests/index/PreAndPostDec.java
new file mode 100644
index 0000000..ff87616
--- /dev/null
+++ b/checker/tests/index/PreAndPostDec.java
@@ -0,0 +1,34 @@
+public class PreAndPostDec {
+
+  void pre1(int[] args) {
+    int ii = 0;
+    while ((ii < args.length)) {
+      // :: error: (array.access.unsafe.high)
+      int m = args[++ii];
+    }
+  }
+
+  void pre2(int[] args) {
+    int ii = 0;
+    while ((ii < args.length)) {
+      ii++;
+      // :: error: (array.access.unsafe.high)
+      int m = args[ii];
+    }
+  }
+
+  void post1(int[] args) {
+    int ii = 0;
+    while ((ii < args.length)) {
+      int m = args[ii++];
+    }
+  }
+
+  void post2(int[] args) {
+    int ii = 0;
+    while ((ii < args.length)) {
+      int m = args[ii];
+      ii++;
+    }
+  }
+}
diff --git a/checker/tests/index/PredecrementTest.java b/checker/tests/index/PredecrementTest.java
new file mode 100644
index 0000000..bc5e152
--- /dev/null
+++ b/checker/tests/index/PredecrementTest.java
@@ -0,0 +1,41 @@
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.common.value.qual.*;
+
+public class PredecrementTest {
+
+  public static void warningForLoop(int @MinLen(1) [] a) {
+    for (int i = a.length; --i >= 0; ) {
+      a[i] = 0;
+    }
+  }
+
+  public static void warningIfStatement(int @MinLen(1) [] a) {
+    int i = a.length;
+    if (--i >= 0) {
+      a[i] = 0;
+    }
+  }
+
+  public static void warningIfStatementRange1(
+      int @MinLen(1) [] a, @IntRange(from = 1, to = 1) int j) {
+    int i = j;
+    if (--i >= 0) {
+      a[i] = 0;
+    }
+  }
+
+  public static void warningIfStatementVal1(int @MinLen(1) [] a, @IntVal(1) int j) {
+    int i = j;
+    if (--i >= 0) {
+      a[i] = 0;
+    }
+  }
+
+  public static void warningIfStatementRange12(
+      int @MinLen(2) [] a, @IntRange(from = 1, to = 2) int j) {
+    int i = j;
+    if (--i >= 0) {
+      a[i] = 0;
+    }
+  }
+}
diff --git a/checker/tests/index/PrimitiveWrappers.java b/checker/tests/index/PrimitiveWrappers.java
new file mode 100644
index 0000000..ab066ff
--- /dev/null
+++ b/checker/tests/index/PrimitiveWrappers.java
@@ -0,0 +1,18 @@
+// Test for issue 65: https://github.com/kelloggm/checker-framework/issues/65
+
+import org.checkerframework.checker.index.qual.*;
+
+// This test ensures that the checker functions on primitive wrappers in
+// addition to literal primitives. Primarily it focuses on Integer/int.
+
+public class PrimitiveWrappers {
+
+  void int_Integer_access_equivalent(@IndexFor("#3") Integer i, @IndexFor("#3") int j, int[] a) {
+    a[i] = a[j];
+  }
+
+  void array_creation(@NonNegative Integer i, @NonNegative int j) {
+    int[] a = new int[j];
+    int[] b = new int[i];
+  }
+}
diff --git a/checker/tests/index/RandomTest.java b/checker/tests/index/RandomTest.java
new file mode 100644
index 0000000..14e3aa2
--- /dev/null
+++ b/checker/tests/index/RandomTest.java
@@ -0,0 +1,14 @@
+import java.util.Random;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class RandomTest {
+  void test() {
+    Random rand = new Random();
+    int[] a = new int[8];
+    // :: error: (anno.on.irrelevant)
+    @LTLengthOf("a") double d1 = Math.random() * a.length;
+    @LTLengthOf("a") int deref = (int) (Math.random() * a.length);
+    @LTLengthOf("a") int deref2 = (int) (rand.nextDouble() * a.length);
+    @LTLengthOf("a") int deref3 = rand.nextInt(a.length);
+  }
+}
diff --git a/checker/tests/index/RandomTestLBC.java b/checker/tests/index/RandomTestLBC.java
new file mode 100644
index 0000000..bbf1691
--- /dev/null
+++ b/checker/tests/index/RandomTestLBC.java
@@ -0,0 +1,14 @@
+import java.util.Random;
+import org.checkerframework.checker.index.qual.NonNegative;
+
+public class RandomTestLBC {
+  void test() {
+    Random rand = new Random();
+    int[] a = new int[8];
+    // :: error: (anno.on.irrelevant)
+    @NonNegative double d1 = Math.random() * a.length;
+    @NonNegative int deref = (int) (Math.random() * a.length);
+    @NonNegative int deref2 = (int) (rand.nextDouble() * a.length);
+    @NonNegative int deref3 = rand.nextInt(a.length);
+  }
+}
diff --git a/checker/tests/index/RangeIndex.java b/checker/tests/index/RangeIndex.java
new file mode 100644
index 0000000..ba1740e
--- /dev/null
+++ b/checker/tests/index/RangeIndex.java
@@ -0,0 +1,8 @@
+import org.checkerframework.common.value.qual.*;
+
+public class RangeIndex {
+  void foo(@IntRange(from = 0, to = 11) int x, int @MinLen(10) [] a) {
+    // :: error: (array.access.unsafe.high.range)
+    int y = a[x];
+  }
+}
diff --git a/checker/tests/index/Reassignment.java b/checker/tests/index/Reassignment.java
new file mode 100644
index 0000000..fbec46c
--- /dev/null
+++ b/checker/tests/index/Reassignment.java
@@ -0,0 +1,13 @@
+// @skip-test until we solve the underlying issue. The code that caused this to pass has been
+// removed, because an incomplete solution that masks the problem but still permits some unsoundness
+// is worse than no solution and an obvious issue.
+
+public class Reassignment {
+  void test(int[] arr, int i) {
+    if (i > 0 && i < arr.length) {
+      arr = new int[0];
+      // :: error: (array.access.unsafe.high)
+      int j = arr[i];
+    }
+  }
+}
diff --git a/checker/tests/index/RefineEq.java b/checker/tests/index/RefineEq.java
new file mode 100644
index 0000000..256a9b3
--- /dev/null
+++ b/checker/tests/index/RefineEq.java
@@ -0,0 +1,39 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class RefineEq {
+  int[] arr = {1};
+
+  void testLTL(@LTLengthOf("arr") int test) {
+    // :: error: (assignment)
+    @LTLengthOf("arr") int a = Integer.parseInt("1");
+
+    int b = 1;
+    if (test == b) {
+      @LTLengthOf("arr") int c = b;
+
+    } else {
+      // :: error: (assignment)
+      @LTLengthOf("arr") int e = b;
+    }
+    // :: error: (assignment)
+    @LTLengthOf("arr") int d = b;
+  }
+
+  void testLTEL(@LTEqLengthOf("arr") int test) {
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int a = Integer.parseInt("1");
+
+    int b = 1;
+    if (test == b) {
+      @LTEqLengthOf("arr") int c = b;
+
+      @LTLengthOf("arr") int g = b;
+    } else {
+      // :: error: (assignment)
+      @LTEqLengthOf("arr") int e = b;
+    }
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int d = b;
+  }
+}
diff --git a/checker/tests/index/RefineGT.java b/checker/tests/index/RefineGT.java
new file mode 100644
index 0000000..cdb66e9
--- /dev/null
+++ b/checker/tests/index/RefineGT.java
@@ -0,0 +1,53 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class RefineGT {
+  int[] arr = {1};
+
+  void testLTL(@LTLengthOf("arr") int test) {
+    // The reason for the parsing is so that the Value Checker
+    // can't figure it out but normal humans can.
+
+    // :: error: (assignment)
+    @LTLengthOf("arr") int a = Integer.parseInt("1");
+
+    // :: error: (assignment)
+    @LTLengthOf("arr") int a3 = Integer.parseInt("3");
+
+    int b = 2;
+    if (test > b) {
+      @LTLengthOf("arr") int c = b;
+    }
+    // :: error: (assignment)
+    @LTLengthOf("arr") int c1 = b;
+
+    if (a > b) {
+      int potato = 7;
+    } else {
+      // :: error: (assignment)
+      @LTLengthOf("arr") int d = b;
+    }
+  }
+
+  void testLTEL(@LTEqLengthOf("arr") int test) {
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int a = Integer.parseInt("1");
+
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int a3 = Integer.parseInt("3");
+
+    int b = 2;
+    if (test > b) {
+      @LTLengthOf("arr") int c = b;
+    }
+    // :: error: (assignment)
+    @LTLengthOf("arr") int c1 = b;
+
+    if (a > b) {
+      int potato = 7;
+    } else {
+      // :: error: (assignment)
+      @LTLengthOf("arr") int d = b;
+    }
+  }
+}
diff --git a/checker/tests/index/RefineGTE.java b/checker/tests/index/RefineGTE.java
new file mode 100644
index 0000000..a03bf89
--- /dev/null
+++ b/checker/tests/index/RefineGTE.java
@@ -0,0 +1,53 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class RefineGTE {
+  int[] arr = {1};
+
+  void testLTL(@LTLengthOf("arr") int test) {
+    // The reason for the parsing is so that the Value Checker
+    // can't figure it out but normal humans can.
+
+    // :: error: (assignment)
+    @LTLengthOf("arr") int a = Integer.parseInt("1");
+
+    // :: error: (assignment)
+    @LTLengthOf("arr") int a3 = Integer.parseInt("3");
+
+    int b = 2;
+    if (test >= b) {
+      @LTLengthOf("arr") int c = b;
+    }
+    // :: error: (assignment)
+    @LTLengthOf("arr") int c1 = b;
+
+    if (a >= b) {
+      int potato = 7;
+    } else {
+      // :: error: (assignment)
+      @LTLengthOf("arr") int d = b;
+    }
+  }
+
+  void testLTEL(@LTEqLengthOf("arr") int test) {
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int a = Integer.parseInt("1");
+
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int a3 = Integer.parseInt("3");
+
+    int b = 2;
+    if (test >= b) {
+      @LTEqLengthOf("arr") int c = b;
+    }
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int c1 = b;
+
+    if (a >= b) {
+      int potato = 7;
+    } else {
+      // :: error: (assignment)
+      @LTEqLengthOf("arr") int d = b;
+    }
+  }
+}
diff --git a/checker/tests/index/RefineLT.java b/checker/tests/index/RefineLT.java
new file mode 100644
index 0000000..9b3098d
--- /dev/null
+++ b/checker/tests/index/RefineLT.java
@@ -0,0 +1,44 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class RefineLT {
+  int[] arr = {1};
+
+  void testLTL(@LTLengthOf("arr") int test, @LTLengthOf("arr") int a, @LTLengthOf("arr") int a3) {
+    int b = 2;
+    if (b < test) {
+      @LTLengthOf("arr") int c = b;
+    }
+    // :: error: (assignment)
+    @LTLengthOf("arr") int c1 = b;
+
+    if (b < a3) {
+      int potato = 7;
+    } else {
+      // :: error: (assignment)
+      @LTLengthOf("arr") int d = b;
+    }
+  }
+
+  void testLTEL(@LTLengthOf("arr") int test) {
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int a = Integer.parseInt("1");
+
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int a3 = Integer.parseInt("3");
+
+    int b = 2;
+    if (b < test) {
+      @LTEqLengthOf("arr") int c = b;
+    }
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int c1 = b;
+
+    if (b < a) {
+      int potato = 7;
+    } else {
+      // :: error: (assignment)
+      @LTEqLengthOf("arr") int d = b;
+    }
+  }
+}
diff --git a/checker/tests/index/RefineLTE.java b/checker/tests/index/RefineLTE.java
new file mode 100644
index 0000000..14c37ac
--- /dev/null
+++ b/checker/tests/index/RefineLTE.java
@@ -0,0 +1,53 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class RefineLTE {
+  int[] arr = {1};
+
+  void testLTL(@LTLengthOf("arr") int test) {
+    // The reason for the parsing is so that the Value Checker
+    // can't figure it out but normal humans can.
+
+    // :: error: (assignment)
+    @LTLengthOf("arr") int a = Integer.parseInt("1");
+
+    // :: error: (assignment)
+    @LTLengthOf("arr") int a3 = Integer.parseInt("3");
+
+    int b = 2;
+    if (b <= test) {
+      @LTLengthOf("arr") int c = b;
+    }
+    // :: error: (assignment)
+    @LTLengthOf("arr") int c1 = b;
+
+    if (b <= a) {
+      int potato = 7;
+    } else {
+      // :: error: (assignment)
+      @LTLengthOf("arr") int d = b;
+    }
+  }
+
+  void testLTEL(@LTEqLengthOf("arr") int test) {
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int a = Integer.parseInt("1");
+
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int a3 = Integer.parseInt("3");
+
+    int b = 2;
+    if (b <= test) {
+      @LTEqLengthOf("arr") int c = b;
+    }
+    // :: error: (assignment)
+    @LTLengthOf("arr") int c1 = b;
+
+    if (b <= a) {
+      int potato = 7;
+    } else {
+      // :: error: (assignment)
+      @LTLengthOf("arr") int d = b;
+    }
+  }
+}
diff --git a/checker/tests/index/RefineLTE2.java b/checker/tests/index/RefineLTE2.java
new file mode 100644
index 0000000..ddef6c6
--- /dev/null
+++ b/checker/tests/index/RefineLTE2.java
@@ -0,0 +1,24 @@
+// Test case for issue #62:
+// https://github.com/kelloggm/checker-framework/issues/62
+
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.common.value.qual.MinLen;
+
+@SuppressWarnings("lowerbound")
+public class RefineLTE2 {
+
+  protected int @MinLen(1) [] values;
+
+  @LTEqLengthOf("values") int num_values;
+
+  public void add(int elt) {
+    if (num_values == values.length) {
+      values = null;
+      // :: error: (unary.increment)
+      num_values++;
+      return;
+    }
+    values[num_values] = elt;
+    num_values++;
+  }
+}
diff --git a/checker/tests/index/RefineNeq.java b/checker/tests/index/RefineNeq.java
new file mode 100644
index 0000000..3a007aa
--- /dev/null
+++ b/checker/tests/index/RefineNeq.java
@@ -0,0 +1,40 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class RefineNeq {
+  int[] arr = {1};
+
+  void testLTL(@LTLengthOf("arr") int test) {
+    // :: error: (assignment)
+    @LTLengthOf("arr") int a = Integer.parseInt("1");
+
+    int b = 1;
+    if (test != b) {
+      // :: error: (assignment)
+      @LTLengthOf("arr") int e = b;
+
+    } else {
+
+      @LTLengthOf("arr") int c = b;
+    }
+    // :: error: (assignment)
+    @LTLengthOf("arr") int d = b;
+  }
+
+  void testLTEL(@LTEqLengthOf("arr") int test) {
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int a = Integer.parseInt("1");
+
+    int b = 1;
+    if (test != b) {
+      // :: error: (assignment)
+      @LTEqLengthOf("arr") int e = b;
+    } else {
+      @LTEqLengthOf("arr") int c = b;
+
+      @LTLengthOf("arr") int g = b;
+    }
+    // :: error: (assignment)
+    @LTEqLengthOf("arr") int d = b;
+  }
+}
diff --git a/checker/tests/index/RefineNeqLength.java b/checker/tests/index/RefineNeqLength.java
new file mode 100644
index 0000000..e360e26
--- /dev/null
+++ b/checker/tests/index/RefineNeqLength.java
@@ -0,0 +1,81 @@
+// Test case for Issue panacekcz#12:
+// https://github.com/panacekcz/checker-framework/issues/12
+
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.LTOMLengthOf;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.common.value.qual.IntVal;
+
+public class RefineNeqLength {
+  void refineNeqLength(int[] array, @IndexOrHigh("#1") int i) {
+    // Refines i <= array.length to i < array.length
+    if (i != array.length) {
+      refineNeqLengthMOne(array, i);
+    }
+    // No refinement
+    if (i != array.length - 1) {
+      // :: error: (argument)
+      refineNeqLengthMOne(array, i);
+    }
+  }
+
+  void refineNeqLengthMOne(int[] array, @IndexFor("#1") int i) {
+    // Refines i < array.length to i < array.length - 1
+    if (i != array.length - 1) {
+      refineNeqLengthMTwo(array, i);
+      // :: error: (argument)
+      refineNeqLengthMThree(array, i);
+    }
+  }
+
+  void refineNeqLengthMTwo(int[] array, @NonNegative @LTOMLengthOf("#1") int i) {
+    // Refines i < array.length - 1 to i < array.length - 2
+    if (i != array.length - 2) {
+      refineNeqLengthMThree(array, i);
+    }
+    // No refinement
+    if (i != array.length - 1) {
+      // :: error: (argument)
+      refineNeqLengthMThree(array, i);
+    }
+  }
+
+  void refineNeqLengthMTwoNonLiteral(
+      int[] array,
+      @NonNegative @LTOMLengthOf("#1") int i,
+      @IntVal(3) int c3,
+      @IntVal({2, 3}) int c23) {
+    // Refines i < array.length - 1 to i < array.length - 2
+    if (i != array.length - (5 - c3)) {
+      refineNeqLengthMThree(array, i);
+    }
+    // No refinement
+    if (i != array.length - c23) {
+      // :: error: (argument)
+      refineNeqLengthMThree(array, i);
+    }
+  }
+
+  @LTLengthOf(value = "#1", offset = "3") int refineNeqLengthMThree(
+      int[] array, @NonNegative @LTLengthOf(value = "#1", offset = "2") int i) {
+    // Refines i < array.length - 2 to i < array.length - 3
+    if (i != array.length - 3) {
+      return i;
+    }
+    // :: error: (return)
+    return i;
+  }
+
+  // The same test for a string.
+  @LTLengthOf(value = "#1", offset = "3") int refineNeqLengthMThree(
+      String str, @NonNegative @LTLengthOf(value = "#1", offset = "2") int i) {
+    // Refines i < str.length() - 2 to i < str.length() - 3
+    if (i != str.length() - 3) {
+      return i;
+    }
+    // :: error: (return)
+    return i;
+  }
+}
diff --git a/checker/tests/index/RefineSubtrahend.java b/checker/tests/index/RefineSubtrahend.java
new file mode 100644
index 0000000..ded8a7e
--- /dev/null
+++ b/checker/tests/index/RefineSubtrahend.java
@@ -0,0 +1,52 @@
+// Test case for kelloggm 215
+// https://github.com/kelloggm/checker-framework/issues/215
+
+import org.checkerframework.checker.index.qual.NonNegative;
+
+public class RefineSubtrahend {
+  void withConstant(int[] a, @NonNegative int l) {
+    if (a.length - l > 10) {
+      int x = a[l + 10];
+    }
+    if (a.length - 10 > l) {
+      int x = a[l + 10];
+    }
+    if (a.length - l >= 10) {
+      // :: error: (array.access.unsafe.high)
+      int x = a[l + 10];
+      int x1 = a[l + 9];
+    }
+  }
+
+  void withVariable(int[] a, @NonNegative int l, @NonNegative int j, @NonNegative int k) {
+    if (a.length - l > j) {
+      if (k <= j) {
+        int x = a[l + k];
+      }
+    }
+    if (a.length - j > l) {
+      if (k <= j) {
+        int x = a[l + k];
+      }
+    }
+    if (a.length - j >= l) {
+      if (k <= j) {
+        // :: error: (array.access.unsafe.high)
+        int x = a[l + k];
+        // :: error: (array.access.unsafe.low)
+        int x1 = a[l + k - 1];
+      }
+    }
+  }
+
+  void cases(int[] a, @NonNegative int l) {
+    switch (a.length - l) {
+      case 1:
+        int x = a[l];
+        break;
+      case 2:
+        int y = a[l + 1];
+        break;
+    }
+  }
+}
diff --git a/checker/tests/index/RefinementEq.java b/checker/tests/index/RefinementEq.java
new file mode 100644
index 0000000..48fbbb7
--- /dev/null
+++ b/checker/tests/index/RefinementEq.java
@@ -0,0 +1,31 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class RefinementEq {
+
+  void test_equal(int a, int j, int s) {
+
+    if (-1 == a) {
+      @GTENegativeOne int b = a;
+    } else {
+      // :: error: (assignment)
+      @GTENegativeOne int c = a;
+    }
+
+    if (0 == j) {
+      @NonNegative int k = j;
+    } else {
+      // :: error: (assignment)
+      @NonNegative int l = j;
+    }
+
+    if (1 == s) {
+      @Positive int t = s;
+    } else {
+      // :: error: (assignment)
+      @Positive int u = s;
+    }
+  }
+}
+// a comment
diff --git a/checker/tests/index/RefinementGT.java b/checker/tests/index/RefinementGT.java
new file mode 100644
index 0000000..967ad44
--- /dev/null
+++ b/checker/tests/index/RefinementGT.java
@@ -0,0 +1,61 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class RefinementGT {
+
+  void test_forward(int a, int j, int s) {
+    /** forwards greater than */
+    // :: error: (assignment)
+    @NonNegative int aa = a;
+    if (a > -1) {
+      /** a is NN now */
+      @NonNegative int b = a;
+    } else {
+      // :: error: (assignment)
+      @NonNegative int c = a;
+    }
+
+    if (j > 0) {
+      /** j is POS now */
+      @Positive int k = j;
+    } else {
+      // :: error: (assignment)
+      @Positive int l = j;
+    }
+
+    if (s > 1) {
+      @Positive int t = s;
+    } else {
+      // :: error: (assignment)
+      @Positive int u = s;
+    }
+  }
+
+  void test_backwards(int a, int j, int s) {
+    /** backwards greater than */
+    // :: error: (assignment)
+    @NonNegative int aa = a;
+    if (-1 > a) {
+      // :: error: (assignment)
+      @GTENegativeOne int b = a;
+    } else {
+      @GTENegativeOne int c = a;
+    }
+
+    if (0 > j) {
+      // :: error: (assignment)
+      @NonNegative int k = j;
+    } else {
+      @NonNegative int l = j;
+    }
+
+    if (1 > s) {
+      // :: error: (assignment)
+      @Positive int t = s;
+    } else {
+      @Positive int u = s;
+    }
+  }
+}
+// a comment
diff --git a/checker/tests/index/RefinementGTE.java b/checker/tests/index/RefinementGTE.java
new file mode 100644
index 0000000..8932b48
--- /dev/null
+++ b/checker/tests/index/RefinementGTE.java
@@ -0,0 +1,52 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class RefinementGTE {
+
+  void test_forward(int a, int j, int s) {
+    /** forwards greater than or equals */
+    // :: error: (assignment)
+    @GTENegativeOne int aa = a;
+    if (a >= -1) {
+      @GTENegativeOne int b = a;
+    } else {
+      // :: error: (assignment)
+      @GTENegativeOne int c = a;
+    }
+
+    if (j >= 0) {
+      @NonNegative int k = j;
+    } else {
+      // :: error: (assignment)
+      @NonNegative int l = j;
+    }
+  }
+
+  void test_backwards(int a, int j, int s) {
+    /** backwards greater than or equal */
+    // :: error: (assignment)
+    @NonNegative int aa = a;
+    if (-1 >= a) {
+      // :: error: (assignment)
+      @NonNegative int b = a;
+    } else {
+      @NonNegative int c = a;
+    }
+
+    if (0 >= j) {
+      // :: error: (assignment)
+      @Positive int k = j;
+    } else {
+      @Positive int l = j;
+    }
+
+    if (1 >= s) {
+      // :: error: (assignment)
+      @Positive int t = s;
+    } else {
+      @Positive int u = s;
+    }
+  }
+}
+// a comment
diff --git a/checker/tests/index/RefinementLT.java b/checker/tests/index/RefinementLT.java
new file mode 100644
index 0000000..b1a5da5
--- /dev/null
+++ b/checker/tests/index/RefinementLT.java
@@ -0,0 +1,59 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class RefinementLT {
+
+  void test_backwards(int a, int j, int s) {
+    /** backwards less than */
+    // :: error: (assignment)
+    @NonNegative int aa = a;
+    if (-1 < a) {
+      @NonNegative int b = a;
+    } else {
+      // :: error: (assignment)
+      @NonNegative int c = a;
+    }
+
+    if (0 < j) {
+      @Positive int k = j;
+    } else {
+      // :: error: (assignment)
+      @Positive int l = j;
+    }
+
+    if (1 < s) {
+      @Positive int t = s;
+    } else {
+      // :: error: (assignment)
+      @Positive int u = s;
+    }
+  }
+
+  void test_forwards(int a, int j, int s) {
+    /** forwards less than */
+    // :: error: (assignment)
+    @NonNegative int aa = a;
+    if (a < -1) {
+      // :: error: (assignment)
+      @GTENegativeOne int b = a;
+    } else {
+      @GTENegativeOne int c = a;
+    }
+
+    if (j < 0) {
+      // :: error: (assignment)
+      @NonNegative int k = j;
+    } else {
+      @NonNegative int l = j;
+    }
+
+    if (s < 1) {
+      // :: error: (assignment)
+      @Positive int t = s;
+    } else {
+      @Positive int u = s;
+    }
+  }
+}
+// a comment
diff --git a/checker/tests/index/RefinementLTE.java b/checker/tests/index/RefinementLTE.java
new file mode 100644
index 0000000..19c1aea
--- /dev/null
+++ b/checker/tests/index/RefinementLTE.java
@@ -0,0 +1,59 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class RefinementLTE {
+
+  void test_backwards(int a, int j, int s) {
+    /** backwards less than or equals */
+    // :: error: (assignment)
+    @GTENegativeOne int aa = a;
+    if (-1 <= a) {
+      @GTENegativeOne int b = a;
+    } else {
+      // :: error: (assignment)
+      @GTENegativeOne int c = a;
+    }
+
+    if (0 <= j) {
+      @NonNegative int k = j;
+    } else {
+      // :: error: (assignment)
+      @NonNegative int l = j;
+    }
+
+    if (1 <= s) {
+      @Positive int t = s;
+    } else {
+      // :: error: (assignment)
+      @Positive int u = s;
+    }
+  }
+
+  void test_forwards(int a, int j, int s) {
+    /** forwards less than or equal */
+    // :: error: (assignment)
+    @NonNegative int aa = a;
+    if (a <= -1) {
+      // :: error: (assignment)
+      @NonNegative int b = a;
+    } else {
+      @NonNegative int c = a;
+    }
+
+    if (j <= 0) {
+      // :: error: (assignment)
+      @Positive int k = j;
+    } else {
+      @Positive int l = j;
+    }
+
+    if (s <= 1) {
+      // :: error: (assignment)
+      @Positive int t = s;
+    } else {
+      @Positive int u = s;
+    }
+  }
+}
+// a comment
diff --git a/checker/tests/index/RefinementNEq.java b/checker/tests/index/RefinementNEq.java
new file mode 100644
index 0000000..0a13772
--- /dev/null
+++ b/checker/tests/index/RefinementNEq.java
@@ -0,0 +1,33 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class RefinementNEq {
+
+  void test_not_equal(int a, int j, int s) {
+
+    // :: error: (assignment)
+    @NonNegative int aa = a;
+    if (-1 != a) {
+      // :: error: (assignment)
+      @GTENegativeOne int b = a;
+    } else {
+      @GTENegativeOne int c = a;
+    }
+
+    if (0 != j) {
+      // :: error: (assignment)
+      @NonNegative int k = j;
+    } else {
+      @NonNegative int l = j;
+    }
+
+    if (1 != s) {
+      // :: error: (assignment)
+      @Positive int t = s;
+    } else {
+      @Positive int u = s;
+    }
+  }
+}
+// a comment
diff --git a/checker/tests/index/ReflectArray.java b/checker/tests/index/ReflectArray.java
new file mode 100644
index 0000000..e2ff0e4
--- /dev/null
+++ b/checker/tests/index/ReflectArray.java
@@ -0,0 +1,25 @@
+import java.lang.reflect.Array;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class ReflectArray {
+
+  void testNewInstance(int i) {
+    // :: error: (argument)
+    Array.newInstance(Object.class, i);
+    if (i >= 0) {
+      Array.newInstance(Object.class, i);
+    }
+  }
+
+  void testFor(Object a) {
+    for (int i = 0; i < Array.getLength(a); ++i) {
+      Array.setInt(a, i, 1 + Array.getInt(a, i));
+    }
+  }
+
+  void testMinLen(Object @MinLen(1) [] a) {
+    Array.get(a, 0);
+    // :: error: (argument)
+    Array.get(a, 1);
+  }
+}
diff --git a/checker/tests/index/RegexMatcher.java b/checker/tests/index/RegexMatcher.java
new file mode 100644
index 0000000..4507819
--- /dev/null
+++ b/checker/tests/index/RegexMatcher.java
@@ -0,0 +1,24 @@
+// Test case for Issue panacekcz#8:
+// https://github.com/panacekcz/checker-framework/issues/8
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.checkerframework.checker.index.qual.NonNegative;
+
+public class RegexMatcher {
+  static void m(String p, String s) {
+    Matcher matcher = Pattern.compile(p).matcher(s);
+    // The following line cannot be used as a test, because the relation of matcher to p is not
+    // tracked, so the upper bound is not known.
+
+    // s.substring(matcher.start(), matcher.end());
+
+    @NonNegative int i;
+    i = matcher.start();
+    i = matcher.end();
+    // :: error: (assignment)
+    i = matcher.start(1);
+    // :: error: (assignment)
+    i = matcher.end(1);
+  }
+}
diff --git a/checker/tests/index/RepeatLTLengthOf.java b/checker/tests/index/RepeatLTLengthOf.java
new file mode 100644
index 0000000..03307ec
--- /dev/null
+++ b/checker/tests/index/RepeatLTLengthOf.java
@@ -0,0 +1,99 @@
+import org.checkerframework.checker.index.qual.EnsuresLTLengthOf;
+import org.checkerframework.checker.index.qual.EnsuresLTLengthOfIf;
+
+public class RepeatLTLengthOf {
+
+  protected String value1;
+  protected String value2;
+  protected String value3;
+  protected int v1;
+  protected int v2;
+  protected int v3;
+
+  public void func1() {
+    v1 = value1.length() - 3;
+    v2 = value2.length() - 3;
+    v3 = value3.length() - 3;
+  }
+
+  public boolean func2() {
+    v1 = value1.length() - 3;
+    v2 = value2.length() - 3;
+    v3 = value3.length() - 3;
+    return true;
+  }
+
+  @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2")
+  @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1")
+  @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0")
+  public void client1() {
+    withpostconditionsfunc1();
+  }
+
+  @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true)
+  @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true)
+  @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true)
+  public boolean client2() {
+    return withcondpostconditionsfunc2();
+  }
+
+  @EnsuresLTLengthOf.List({
+    @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2"),
+    @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1")
+  })
+  @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0")
+  public void client3() {
+    withpostconditionfunc1();
+  }
+
+  @EnsuresLTLengthOfIf.List({
+    @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true),
+    @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true)
+  })
+  @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true)
+  public boolean client4() {
+    return withcondpostconditionfunc2();
+  }
+
+  @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2")
+  @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1")
+  @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0")
+  public void withpostconditionsfunc1() {
+    v1 = value1.length() - 3;
+    v2 = value2.length() - 3;
+    v3 = value3.length() - 3;
+  }
+
+  @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true)
+  @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true)
+  @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true)
+  public boolean withcondpostconditionsfunc2() {
+    v1 = value1.length() - 3;
+    v2 = value2.length() - 3;
+    v3 = value3.length() - 3;
+    return true;
+  }
+
+  @EnsuresLTLengthOf.List({
+    @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2"),
+    @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1")
+  })
+  @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0")
+  public void withpostconditionfunc1() {
+    v1 = value1.length() - 3;
+    v2 = value2.length() - 3;
+    v3 = value3.length() - 3;
+  }
+
+  @EnsuresLTLengthOfIf.List({
+    @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true),
+    @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true)
+  })
+  @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true)
+  public boolean withcondpostconditionfunc2() {
+    v1 = value1.length() - 3;
+    v2 = value2.length() - 3;
+    v3 = value3.length() - 3;
+    return true;
+  }
+}
diff --git a/checker/tests/index/RepeatLTLengthOfWithError.java b/checker/tests/index/RepeatLTLengthOfWithError.java
new file mode 100644
index 0000000..cd69679
--- /dev/null
+++ b/checker/tests/index/RepeatLTLengthOfWithError.java
@@ -0,0 +1,103 @@
+import org.checkerframework.checker.index.qual.EnsuresLTLengthOf;
+import org.checkerframework.checker.index.qual.EnsuresLTLengthOfIf;
+
+public class RepeatLTLengthOfWithError {
+
+  protected String value1;
+  protected String value2;
+  protected String value3;
+  protected int v1;
+  protected int v2;
+  protected int v3;
+
+  public void func1() {
+    v1 = value1.length() - 3;
+    v2 = value2.length() - 3;
+    v3 = value3.length() - 3;
+  }
+
+  public boolean func2() {
+    v1 = value1.length() - 3;
+    v2 = value2.length() - 3;
+    v3 = value3.length() - 3;
+    return true;
+  }
+
+  @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3")
+  @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2")
+  @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1")
+  public void client1() {
+    withpostconditionsfunc1();
+  }
+
+  @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true)
+  @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true)
+  @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true)
+  public boolean client2() {
+    return withcondpostconditionsfunc2();
+  }
+
+  @EnsuresLTLengthOf.List({
+    @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3"),
+    @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2")
+  })
+  @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1")
+  public void client3() {
+    withpostconditionfunc1();
+  }
+
+  @EnsuresLTLengthOfIf.List({
+    @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true),
+    @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true)
+  })
+  @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true)
+  public boolean client4() {
+    return withcondpostconditionfunc2();
+  }
+
+  @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3")
+  @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2")
+  @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1")
+  // :: error:  (contracts.postcondition)
+  public void withpostconditionsfunc1() {
+    v1 = value1.length() - 3; // condition not satisfied here
+    v2 = value2.length() - 3;
+    v3 = value3.length() - 3;
+  }
+
+  @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true)
+  @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true)
+  @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true)
+  public boolean withcondpostconditionsfunc2() {
+    v1 = value1.length() - 3; // condition not satisfied here
+    v2 = value2.length() - 3;
+    v3 = value3.length() - 3;
+    // :: error:  (contracts.conditional.postcondition)
+    return true;
+  }
+
+  @EnsuresLTLengthOf.List({
+    @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3"),
+    @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2")
+  })
+  @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1")
+  // :: error:  (contracts.postcondition)
+  public void withpostconditionfunc1() {
+    v1 = value1.length() - 3; // condition not satisfied here
+    v2 = value2.length() - 3;
+    v3 = value3.length() - 3;
+  }
+
+  @EnsuresLTLengthOfIf.List({
+    @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true),
+    @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true)
+  })
+  @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true)
+  public boolean withcondpostconditionfunc2() {
+    v1 = value1.length() - 3; // condition not satisfied here
+    v2 = value2.length() - 3;
+    v3 = value3.length() - 3;
+    // :: error:  (contracts.conditional.postcondition)
+    return true;
+  }
+}
diff --git a/checker/tests/index/Return.java b/checker/tests/index/Return.java
new file mode 100644
index 0000000..71df927
--- /dev/null
+++ b/checker/tests/index/Return.java
@@ -0,0 +1,5 @@
+public class Return {
+  int[] test() {
+    return null;
+  }
+}
diff --git a/checker/tests/index/SLSubtyping.java b/checker/tests/index/SLSubtyping.java
new file mode 100644
index 0000000..efba985
--- /dev/null
+++ b/checker/tests/index/SLSubtyping.java
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.index.qual.SameLen;
+
+// This test checks whether the SameLen type system works as expected.
+
+public class SLSubtyping {
+  int[] f = {1};
+
+  void subtype(int @SameLen("#2") [] a, int[] b) {
+    int @SameLen({"a", "b"}) [] c = a;
+
+    // :: error: (assignment)
+    int @SameLen("c") [] q = {1, 2};
+    int @SameLen("c") [] d = q;
+
+    // :: error: (assignment)
+    int @SameLen("f") [] e = a;
+  }
+
+  void subtype2(int[] a, int @SameLen("#1") [] b) {
+    a = b;
+    int @SameLen("b") [] c = b;
+    int @SameLen("f") [] d = f;
+  }
+}
diff --git a/checker/tests/index/SameLenAssignmentTransfer.java b/checker/tests/index/SameLenAssignmentTransfer.java
new file mode 100644
index 0000000..850ed85
--- /dev/null
+++ b/checker/tests/index/SameLenAssignmentTransfer.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class SameLenAssignmentTransfer {
+  void transfer5(int @SameLen("#2") [] a, int[] b) {
+    int[] c = a;
+    for (int i = 0; i < c.length; i++) { // i's type is @LTL("c")
+      b[i] = 1;
+    }
+  }
+}
diff --git a/checker/tests/index/SameLenEqualsRefinement.java b/checker/tests/index/SameLenEqualsRefinement.java
new file mode 100644
index 0000000..a19dd6d
--- /dev/null
+++ b/checker/tests/index/SameLenEqualsRefinement.java
@@ -0,0 +1,42 @@
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class SameLenEqualsRefinement {
+  void transfer3(int @SameLen("#2") [] a, int[] b, int[] c) {
+    if (a == c) {
+      for (int i = 0; i < c.length; i++) { // i's type is @LTL("c")
+        b[i] = 1;
+        int @SameLen({"a", "b", "c"}) [] d = c;
+      }
+    }
+  }
+
+  void transfer4(int[] a, int[] b, int[] c) {
+    if (b == c) {
+      if (a == b) {
+        for (int i = 0; i < c.length; i++) { // i's type is @LTL("c")
+          a[i] = 1;
+          int @SameLen({"a", "b", "c"}) [] d = c;
+        }
+      }
+    }
+  }
+
+  void transfer5(int[] a, int[] b, int[] c, int[] d) {
+    if (a == b && b == c) {
+      int[] x = a;
+      int[] y = x;
+      int index = x.length - 1;
+      if (index > 0) {
+        f(a[index]);
+        f(b[index]);
+        f(c[index]);
+        f(x[index]);
+        f(y[index]);
+      }
+    }
+  }
+
+  @Pure
+  void f(Object o) {}
+}
diff --git a/checker/tests/index/SameLenFormalParameter2.java b/checker/tests/index/SameLenFormalParameter2.java
new file mode 100644
index 0000000..46a1012
--- /dev/null
+++ b/checker/tests/index/SameLenFormalParameter2.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class SameLenFormalParameter2 {
+
+  void lib(Object @SameLen({"#1", "#2"}) [] valsArg, int @SameLen({"#1", "#2"}) [] modsArg) {}
+
+  void client(Object[] myvals, int[] mymods) {
+    // :: error: (argument)
+    lib(myvals, mymods);
+  }
+}
diff --git a/checker/tests/index/SameLenLUBStrangeness.java b/checker/tests/index/SameLenLUBStrangeness.java
new file mode 100644
index 0000000..b90ca09
--- /dev/null
+++ b/checker/tests/index/SameLenLUBStrangeness.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.index.qual.SameLen;
+
+public class SameLenLUBStrangeness {
+  void test(int[] a, boolean cond) {
+    int[] b;
+    if (cond) {
+      b = a;
+    }
+    // :: error: (assignment)
+    int @SameLen({"a", "b"}) [] c = a;
+  }
+}
diff --git a/checker/tests/index/SameLenManyArrays.java b/checker/tests/index/SameLenManyArrays.java
new file mode 100644
index 0000000..db502c4
--- /dev/null
+++ b/checker/tests/index/SameLenManyArrays.java
@@ -0,0 +1,56 @@
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class SameLenManyArrays {
+  void transfer1(int @SameLen("#2") [] a, int[] b) {
+    int[] c = new int[a.length];
+    for (int i = 0; i < c.length; i++) { // i's type is @LTL("c")
+      b[i] = 1;
+      int @SameLen({"a", "b", "c"}) [] d = c;
+    }
+  }
+
+  void transfer2(int @SameLen("#2") [] a, int[] b) {
+    for (int i = 0; i < b.length; i++) { // i's type is @LTL("b")
+      a[i] = 1;
+    }
+  }
+
+  void transfer3(int @SameLen("#2") [] a, int[] b, int[] c) {
+    if (a.length == c.length) {
+      for (int i = 0; i < c.length; i++) { // i's type is @LTL("c")
+        b[i] = 1;
+        int @SameLen({"a", "b", "c"}) [] d = c;
+      }
+    }
+  }
+
+  void transfer4(int[] a, int[] b, int[] c) {
+    if (b.length == c.length) {
+      if (a.length == b.length) {
+        for (int i = 0; i < c.length; i++) { // i's type is @LTL("c")
+          a[i] = 1;
+          int @SameLen({"a", "b", "c"}) [] d = c;
+        }
+      }
+    }
+  }
+
+  void transfer5(int[] a, int[] b, int[] c, int[] d) {
+    if (a.length == b.length && b.length == c.length) {
+      int[] x = a;
+      int[] y = x;
+      int index = x.length - 1;
+      if (index > 0) {
+        f(a[index]);
+        f(b[index]);
+        f(c[index]);
+        f(x[index]);
+        f(y[index]);
+      }
+    }
+  }
+
+  @Pure
+  void f(Object o) {}
+}
diff --git a/checker/tests/index/SameLenNewArrayWithSameLength.java b/checker/tests/index/SameLenNewArrayWithSameLength.java
new file mode 100644
index 0000000..3461431
--- /dev/null
+++ b/checker/tests/index/SameLenNewArrayWithSameLength.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class SameLenNewArrayWithSameLength {
+  public void m1(int[] a) {
+    int @SameLen("a") [] b = new int[a.length];
+  }
+
+  public void m2(int[] a, int @SameLen("#1") [] b) {
+    int @SameLen({"a", "b"}) [] c = new int[b.length];
+  }
+}
diff --git a/checker/tests/index/SameLenOnFormalParameter.java b/checker/tests/index/SameLenOnFormalParameter.java
new file mode 100644
index 0000000..bef9d7d
--- /dev/null
+++ b/checker/tests/index/SameLenOnFormalParameter.java
@@ -0,0 +1,28 @@
+// Test case for issue #2434: http://tinyurl.com/cfissue/2434
+
+import org.checkerframework.checker.index.qual.*;
+
+public class SameLenOnFormalParameter {
+  public void requiresSameLen1(String x1, @SameLen("#1") String y1) {}
+
+  public void requiresSameLen2(@SameLen("#2") String x2, String y2) {}
+
+  public void m1(@SameLen("#2") String a1, String b1) {
+    requiresSameLen1(a1, b1);
+  }
+
+  public void m2(@SameLen("#2") String a2, String b2) {
+    @SameLen("a2") String b22 = b2;
+    requiresSameLen1(a2, b22);
+  }
+
+  public void m3(@SameLen("#2") String a3, String b3) {
+    @SameLen("b3") String a2 = a3;
+    @SameLen("a3") String b32 = b3;
+    requiresSameLen1(a3, b32);
+  }
+
+  public void m4(@SameLen("#2") String a4, String b4) {
+    requiresSameLen2(a4, b4);
+  }
+}
diff --git a/checker/tests/index/SameLenOnFormalParameterSimple.java b/checker/tests/index/SameLenOnFormalParameterSimple.java
new file mode 100644
index 0000000..a067e97
--- /dev/null
+++ b/checker/tests/index/SameLenOnFormalParameterSimple.java
@@ -0,0 +1,11 @@
+// Test case for issue #2434: http://tinyurl.com/cfissue/2434
+
+import org.checkerframework.checker.index.qual.SameLen;
+
+public class SameLenOnFormalParameterSimple {
+  public void requiresSameLen1(String x1, @SameLen("#1") String y1) {}
+
+  public void m1(@SameLen("#2") String a1, String b1) {
+    requiresSameLen1(a1, b1);
+  }
+}
diff --git a/checker/tests/index/SameLenSelf.java b/checker/tests/index/SameLenSelf.java
new file mode 100644
index 0000000..1eb3371
--- /dev/null
+++ b/checker/tests/index/SameLenSelf.java
@@ -0,0 +1,14 @@
+// Test case for issue 146: https://github.com/kelloggm/checker-framework/issues/146
+
+import org.checkerframework.checker.index.qual.*;
+
+public class SameLenSelf {
+  int @SameLen("this.field") [] field = new int[10];
+  int @SameLen("field2") [] field2 = new int[10];
+  int @SameLen("field3") [] field3 = field2;
+
+  void foo(int[] b) {
+    int @SameLen("a") [] a = b;
+    int @SameLen("c") [] c = new int[10];
+  }
+}
diff --git a/checker/tests/index/SameLenSimpleCase.java b/checker/tests/index/SameLenSimpleCase.java
new file mode 100644
index 0000000..eef3a5c
--- /dev/null
+++ b/checker/tests/index/SameLenSimpleCase.java
@@ -0,0 +1,13 @@
+public class SameLenSimpleCase {
+  public int compare(int[] a1, int[] a2) {
+    if (a1.length != a2.length) {
+      return a1.length - a2.length;
+    }
+    for (int i = 0; i < a1.length; i++) {
+      if (a1[i] != a2[i]) {
+        return ((a1[i] > a2[i]) ? 1 : -1);
+      }
+    }
+    return 0;
+  }
+}
diff --git a/checker/tests/index/SameLenTripleThreat.java b/checker/tests/index/SameLenTripleThreat.java
new file mode 100644
index 0000000..6bd016a
--- /dev/null
+++ b/checker/tests/index/SameLenTripleThreat.java
@@ -0,0 +1,32 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class SameLenTripleThreat {
+  public void foo(String[] vars) {
+    String[] qrets = new String[vars.length];
+    String @SameLen("vars") [] y = qrets;
+    String[] indices = new String[vars.length];
+    String @SameLen("qrets") [] x = indices;
+  }
+
+  String[] indices;
+
+  public void foo2(String... vars) {
+    String[] qrets = new String[vars.length];
+    indices = new String[vars.length];
+    String[] indicesLocal = new String[vars.length];
+    for (int i = 0; i < qrets.length; i++) {
+      indices[i] = "hello";
+      indicesLocal[i] = "hello";
+    }
+  }
+
+  public void foo3(String... vars) {
+    String[] qrets = new String[vars.length];
+    String[] indicesLocal = new String[vars.length];
+    indices = new String[vars.length];
+    for (int i = 0; i < qrets.length; i++) {
+      indices[i] = "hello";
+      indicesLocal[i] = "hello";
+    }
+  }
+}
diff --git a/checker/tests/index/SameLenWithObjects.java b/checker/tests/index/SameLenWithObjects.java
new file mode 100644
index 0000000..9b1c96b
--- /dev/null
+++ b/checker/tests/index/SameLenWithObjects.java
@@ -0,0 +1,19 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class SameLenWithObjects {
+
+  class SimpleCollection {
+    Object[] var_infos;
+  }
+
+  static final class Invocation1 {
+    SimpleCollection sc;
+    Object @SameLen({"vals1", "this.sc.var_infos"}) [] vals1;
+
+    void format1() {
+      for (int j = 0; j < vals1.length; j++) {
+        System.out.println(sc.var_infos[j]);
+      }
+    }
+  }
+}
diff --git a/checker/tests/index/SearchIndexTests.java b/checker/tests/index/SearchIndexTests.java
new file mode 100644
index 0000000..9b7d22f
--- /dev/null
+++ b/checker/tests/index/SearchIndexTests.java
@@ -0,0 +1,54 @@
+import java.util.Arrays;
+import org.checkerframework.checker.index.qual.*;
+
+public class SearchIndexTests {
+  public void test(short[] a, short instant) {
+    int i = Arrays.binarySearch(a, instant);
+    @SearchIndexFor("a") int z = i;
+    // :: error: (assignment)
+    @SearchIndexFor("a") int y = 7;
+    @LTLengthOf("a") int x = i;
+  }
+
+  void test2(int[] a, @SearchIndexFor("#1") int xyz) {
+    if (0 > xyz) {
+      @NegativeIndexFor("a") int w = xyz;
+      @NonNegative int y = ~xyz;
+      @LTEqLengthOf("a") int z = ~xyz;
+    }
+  }
+
+  void test3(int[] a, @SearchIndexFor("#1") int xyz) {
+    if (-1 >= xyz) {
+      @NegativeIndexFor("a") int w = xyz;
+      @NonNegative int y = ~xyz;
+      @LTEqLengthOf("a") int z = ~xyz;
+    }
+  }
+
+  void test4(int[] a, @SearchIndexFor("#1") int xyz) {
+    if (xyz < 0) {
+      @NegativeIndexFor("a") int w = xyz;
+      @NonNegative int y = ~xyz;
+      @LTEqLengthOf("a") int z = ~xyz;
+    }
+  }
+
+  void test5(int[] a, @SearchIndexFor("#1") int xyz) {
+    if (xyz <= -1) {
+      @NegativeIndexFor("a") int w = xyz;
+      @NonNegative int y = ~xyz;
+      @LTEqLengthOf("a") int z = ~xyz;
+    }
+  }
+
+  void subtyping1(
+      @SearchIndexFor({"#3", "#4"}) int x, @NegativeIndexFor("#3") int y, int[] a, int[] b) {
+    // :: error: (assignment)
+    @SearchIndexFor({"a", "b"}) int z = y;
+    @SearchIndexFor("a") int w = y;
+    @SearchIndexFor("b") int p = x;
+    // :: error: (assignment)
+    @NegativeIndexFor({"a", "b"}) int q = x;
+  }
+}
diff --git a/checker/tests/index/ShiftRight.java b/checker/tests/index/ShiftRight.java
new file mode 100644
index 0000000..7cc5109
--- /dev/null
+++ b/checker/tests/index/ShiftRight.java
@@ -0,0 +1,27 @@
+// Test case for kelloggm 214
+// https://github.com/kelloggm/checker-framework/issues/214
+
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class ShiftRight {
+  void indexFor(Object[] a, @IndexFor("#1") int i) {
+    @IndexFor("a") int o = i >> 2;
+    @IndexFor("a") int p = i >>> 2;
+  }
+
+  void indexOrHigh(Object[] a, @IndexOrHigh("#1") int i) {
+    @IndexOrHigh("a") int o = i >> 2;
+    @IndexOrHigh("a") int p = i >>> 2;
+    // Not true if a.length == 0
+    // :: error: (assignment)
+    @IndexFor("a") int q = i >> 2;
+  }
+
+  void negative(Object[] a, @LTLengthOf(value = "#1", offset = "100") int i) {
+    // Not true for some negative i
+    // :: error: (assignment)
+    @LTLengthOf(value = "#1", offset = "100") int q = i >> 2;
+  }
+}
diff --git a/checker/tests/index/ShiftRightAverage.java b/checker/tests/index/ShiftRightAverage.java
new file mode 100644
index 0000000..9b945bc
--- /dev/null
+++ b/checker/tests/index/ShiftRightAverage.java
@@ -0,0 +1,16 @@
+// Test case for kelloggm 217
+// https://github.com/kelloggm/checker-framework/issues/217
+
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class ShiftRightAverage {
+  public static void m(Object[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) {
+    @IndexFor("#1") int k = (i + j) >> 1;
+  }
+
+  public static void m2(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) {
+    // :: error: (assignment)
+    @LTLengthOf("a") int h = ((i + 1) + j) >> 1;
+  }
+}
diff --git a/checker/tests/index/SimpleCollection.java b/checker/tests/index/SimpleCollection.java
new file mode 100644
index 0000000..e263534
--- /dev/null
+++ b/checker/tests/index/SimpleCollection.java
@@ -0,0 +1,20 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class SimpleCollection {
+  private int[] values;
+
+  @IndexOrHigh("values") int size() {
+    return values.length;
+  }
+
+  void interact_with_other(SimpleCollection other) {
+    int[] othervalues = other.values;
+    int @SameLen("other.values") [] x = othervalues;
+    for (int i = 0; i < other.size(); i++) {
+      int k = othervalues[i];
+    }
+    for (int j = 0; j < other.size(); j++) {
+      int k = other.values[j];
+    }
+  }
+}
diff --git a/checker/tests/index/SimpleTransferAdd.java b/checker/tests/index/SimpleTransferAdd.java
new file mode 100644
index 0000000..25a90dd
--- /dev/null
+++ b/checker/tests/index/SimpleTransferAdd.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class SimpleTransferAdd {
+  void test() {
+    int bs = -1;
+    // :: error: (assignment)
+    @NonNegative int es = bs;
+
+    // @NonNegative int ds = 2 + bs;
+    int ds = 0;
+    // :: error: (assignment)
+    @Positive int cs = ds++;
+    @Positive int fs = ds;
+  }
+}
+// a comment
diff --git a/checker/tests/index/SimpleTransferSub.java b/checker/tests/index/SimpleTransferSub.java
new file mode 100644
index 0000000..27b193d
--- /dev/null
+++ b/checker/tests/index/SimpleTransferSub.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.index.qual.Positive;
+
+public class SimpleTransferSub {
+  void test() {
+    // shows a bug in the checker framework. I don't think we can get around this bit...
+    int bs = 0;
+    // :: error: (assignment)
+    @Positive int ds = bs--;
+  }
+}
+// a comment
diff --git a/checker/tests/index/SizeVsLength.java b/checker/tests/index/SizeVsLength.java
new file mode 100644
index 0000000..4459461
--- /dev/null
+++ b/checker/tests/index/SizeVsLength.java
@@ -0,0 +1,14 @@
+// test case for issue 91: https://github.com/kelloggm/checker-framework/issues/91
+
+import org.checkerframework.checker.index.qual.*;
+
+public class SizeVsLength {
+
+  public int[] getArray(@NonNegative int size) {
+    int[] values = new int[size];
+    for (int i = 0; i < size; i++) {
+      values[i] = 22;
+    }
+    return values;
+  }
+}
diff --git a/checker/tests/index/SkipBufferedReader.java b/checker/tests/index/SkipBufferedReader.java
new file mode 100644
index 0000000..d083903
--- /dev/null
+++ b/checker/tests/index/SkipBufferedReader.java
@@ -0,0 +1,14 @@
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+public class SkipBufferedReader {
+  public static void method() throws IOException {
+    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
+
+    // :: error: (argument)
+    bufferedReader.skip(-1);
+
+    bufferedReader.skip(1);
+  }
+}
diff --git a/checker/tests/index/SpecialTransfersForEquality.java b/checker/tests/index/SpecialTransfersForEquality.java
new file mode 100644
index 0000000..3ce5de8
--- /dev/null
+++ b/checker/tests/index/SpecialTransfersForEquality.java
@@ -0,0 +1,22 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class SpecialTransfersForEquality {
+
+  void gteN1Test(@GTENegativeOne int y) {
+    int[] arr = new int[10];
+    if (-1 != y) {
+      @NonNegative int z = y;
+      if (z < 10) {
+        int k = arr[z];
+      }
+    }
+  }
+
+  void nnTest(@NonNegative int i) {
+    if (i != 0) {
+      @Positive int m = i;
+    }
+  }
+}
diff --git a/checker/tests/index/Split.java b/checker/tests/index/Split.java
new file mode 100644
index 0000000..38ec5c2
--- /dev/null
+++ b/checker/tests/index/Split.java
@@ -0,0 +1,10 @@
+import java.util.regex.Pattern;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class Split {
+  Pattern p = Pattern.compile(".*");
+
+  void test() {
+    String @MinLen(1) [] s = p.split("sdf");
+  }
+}
diff --git a/checker/tests/index/StartsEndsWith.java b/checker/tests/index/StartsEndsWith.java
new file mode 100644
index 0000000..f3ad8eb
--- /dev/null
+++ b/checker/tests/index/StartsEndsWith.java
@@ -0,0 +1,19 @@
+// Tests string length refinement after startsWith or endsWith return true
+// https://github.com/kelloggm/checker-framework/issues/56
+
+import org.checkerframework.common.value.qual.MinLen;
+
+public class StartsEndsWith {
+
+  // This particular test is here rather than in the framework tests because it depends on purity
+  // annotations for these particular JDK methods.
+  void refineStartsConditional(String str, String prefix) {
+    if (prefix.length() > 10 && str.startsWith(prefix)) {
+      @MinLen(11) String s11 = str;
+    }
+  }
+}
+
+class StartsEndsWithExternal {
+  public static final String staticFinalField = "str";
+}
diff --git a/checker/tests/index/StaticInitializer.java b/checker/tests/index/StaticInitializer.java
new file mode 100644
index 0000000..8c79557
--- /dev/null
+++ b/checker/tests/index/StaticInitializer.java
@@ -0,0 +1,3 @@
+public final class StaticInitializer {
+  static final int MAX_SIGNED_POWER_OF_TWO = 1 << (Integer.SIZE - 2);
+}
diff --git a/checker/tests/index/Stopwatch.java b/checker/tests/index/Stopwatch.java
new file mode 100644
index 0000000..08a4b52
--- /dev/null
+++ b/checker/tests/index/Stopwatch.java
@@ -0,0 +1,16 @@
+import java.text.DecimalFormat;
+import org.checkerframework.checker.index.qual.IndexFor;
+
+public final class Stopwatch {
+  private static final DecimalFormat[] timeFormat = {
+    new DecimalFormat("#.#"),
+    new DecimalFormat("#.#"),
+    new DecimalFormat("#.#"),
+    new DecimalFormat("#.#"),
+    new DecimalFormat("#.#"),
+  };
+
+  public DecimalFormat format(@IndexFor("Stopwatch.timeFormat") int digits) {
+    return Stopwatch.timeFormat[digits];
+  }
+}
diff --git a/checker/tests/index/StringBuilderOffset.java b/checker/tests/index/StringBuilderOffset.java
new file mode 100644
index 0000000..c2354c9
--- /dev/null
+++ b/checker/tests/index/StringBuilderOffset.java
@@ -0,0 +1,11 @@
+public class StringBuilderOffset {
+  public static void OffsetStringBuilder() {
+    StringBuilder stringBuilder = new StringBuilder();
+    char[] chars = new char[10];
+
+    // :: error: (argument)
+    stringBuilder.append(chars, 5, 7);
+
+    stringBuilder.append(chars, 5, 4);
+  }
+}
diff --git a/checker/tests/index/StringIndexOf.java b/checker/tests/index/StringIndexOf.java
new file mode 100644
index 0000000..abb2d24
--- /dev/null
+++ b/checker/tests/index/StringIndexOf.java
@@ -0,0 +1,44 @@
+// Tests using the index returned from String.indexOf
+
+public class StringIndexOf {
+
+  public static String remove(String l, String s) {
+    int i = l.indexOf(s);
+    if (i != -1) {
+      return l.substring(0, i) + l.substring(i + s.length());
+    }
+    return l;
+  }
+
+  public static String nocheck(String l, String s) {
+    int i = l.indexOf(s);
+    // :: error: (argument)
+    return l.substring(0, i) + l.substring(i + s.length());
+  }
+
+  public static String remove(String l, String s, int from, boolean last) {
+    int i = last ? l.lastIndexOf(s, from) : l.indexOf(s, from);
+    if (i >= 0) {
+      return l.substring(0, i) + l.substring(i + s.length());
+    }
+    return l;
+  }
+
+  public static String stringLiteral(String l) {
+    int i = l.indexOf("constant");
+    if (i != -1) {
+      return l.substring(0, i) + l.substring(i + "constant".length());
+    }
+    // :: error: (argument)
+    return l.substring(0, i) + l.substring(i + "constant".length());
+  }
+
+  public static char character(String l, char c) {
+    int i = l.indexOf(c);
+    if (i > -1) {
+      return l.charAt(i);
+    }
+    // :: error: (argument)
+    return l.charAt(i);
+  }
+}
diff --git a/checker/tests/index/StringLenRefinement.java b/checker/tests/index/StringLenRefinement.java
new file mode 100644
index 0000000..dd55e36
--- /dev/null
+++ b/checker/tests/index/StringLenRefinement.java
@@ -0,0 +1,42 @@
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.ArrayLenRange;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class StringLenRefinement {
+
+  void refineLenRange(
+      @ArrayLenRange(from = 3, to = 10) String range,
+      @ArrayLen({4, 6, 12}) String lens,
+      @StringVal({"aaaa", "bbbb", "cccccc", "dddddddddddd"}) String vals) {
+    if (range.length() <= 7) {
+      @ArrayLenRange(from = 3, to = 7) String shortRange = range;
+    } else {
+      @ArrayLenRange(from = 8, to = 10) String longRange = range;
+    }
+
+    if (lens.length() <= 7) {
+      @ArrayLen({4, 6}) String shortLens = lens;
+    } else {
+      @ArrayLen({12}) String longLens = lens;
+    }
+
+    if (vals.length() <= 7) {
+      @StringVal({"aaaa", "bbbb", "cccccc"}) String shortVals = vals;
+    } else {
+
+      @StringVal({"dddddddddddd"}) String longVals = vals;
+    }
+  }
+
+  void refineLen(
+      @ArrayLenRange(from = 3, to = 10) String range, @ArrayLen({4, 8, 12}) String lens) {
+
+    if (range.length() == 5 || range.length() == 8 || range.length() == 13) {
+      @ArrayLen({5, 8}) String refinedArg = range;
+    }
+
+    if (lens.length() == 5 || lens.length() == 8 || lens.length() == 13) {
+      @ArrayLen({8}) String refinedLens = lens;
+    }
+  }
+}
diff --git a/checker/tests/index/StringLength.java b/checker/tests/index/StringLength.java
new file mode 100644
index 0000000..34f239d
--- /dev/null
+++ b/checker/tests/index/StringLength.java
@@ -0,0 +1,80 @@
+// Tests that String.length() is supported in the same situations as array length
+
+import java.util.Random;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.checker.index.qual.SameLen;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class StringLength {
+  void testMinLenSubtractPositive(@MinLen(10) String s) {
+    @Positive int i1 = s.length() - 9;
+    @NonNegative int i0 = s.length() - 10;
+    // ::  error: (assignment)
+    @NonNegative int im1 = s.length() - 11;
+  }
+
+  void testNewArraySameLen(String s) {
+    int @SameLen("s") [] array = new int[s.length()];
+    // ::  error: (assignment)
+    int @SameLen("s") [] array1 = new int[s.length() + 1];
+  }
+
+  void testStringAssignSameLen(String s, String r) {
+    @SameLen("s") String t = s;
+    // ::  error: (assignment)
+    @SameLen("s") String tN = r;
+  }
+
+  void testStringLenEqualSameLen(String s, String r) {
+    if (s.length() == r.length()) {
+      @SameLen("s") String tN = r;
+    }
+  }
+
+  void testStringEqualSameLen(String s, String r) {
+    if (s == r) {
+      @SameLen("s") String tN = r;
+    }
+  }
+
+  void testOffsetRemoval(
+      String s,
+      String t,
+      @LTLengthOf(value = "#1", offset = "#2.length()") int i,
+      @LTLengthOf(value = "#2") int j,
+      int k) {
+    @LTLengthOf("s") int ij = i + j;
+    // ::  error: (assignment)
+    @LTLengthOf("s") int ik = i + k;
+  }
+
+  void testLengthDivide(@MinLen(1) String s) {
+    @IndexFor("s") int i = s.length() / 2;
+  }
+
+  void testAddDivide(@MinLen(1) String s, @IndexFor("#1") int i, @IndexFor("#1") int j) {
+    @IndexFor("s") int ij = (i + j) / 2;
+  }
+
+  void testRandomMultiply(@MinLen(1) String s, Random r) {
+    @LTLengthOf("s") int i = (int) (Math.random() * s.length());
+    @LTLengthOf("s") int j = (int) (r.nextDouble() * s.length());
+  }
+
+  void testNotEqualLength(String s, @IndexOrHigh("#1") int i, @IndexOrHigh("#1") int j) {
+    if (i != s.length()) {
+      @IndexFor("s") int in = i;
+      // ::  error: (assignment)
+      @IndexFor("s") int jn = j;
+    }
+  }
+
+  void testLength(String s) {
+    @IndexOrHigh("s") int i = s.length();
+    @LTLengthOf("s") int j = s.length() - 1;
+  }
+}
diff --git a/checker/tests/index/StringMethods.java b/checker/tests/index/StringMethods.java
new file mode 100644
index 0000000..3ab1a04
--- /dev/null
+++ b/checker/tests/index/StringMethods.java
@@ -0,0 +1,44 @@
+// Tests for index annotations on string methods in the annotated JDK
+
+public class StringMethods {
+
+  void testCharAt(String s, int i) {
+    // ::  error: (argument)
+    s.charAt(i);
+    // ::  error: (argument)
+    s.codePointAt(i);
+
+    if (i >= 0 && i < s.length()) {
+      s.charAt(i);
+      s.codePointAt(i);
+    }
+  }
+
+  void testCodePointBefore(String s) {
+    // ::  error: (argument)
+    s.codePointBefore(0);
+
+    if (s.length() > 0) {
+      s.codePointBefore(s.length());
+    }
+  }
+
+  void testSubstring(String s) {
+    s.substring(0);
+    s.substring(0, 0);
+    s.substring(s.length());
+    s.substring(s.length(), s.length());
+    s.substring(0, s.length());
+    // ::  error: (argument)
+    s.substring(1);
+    // ::  error: (argument)
+    s.substring(0, 1);
+  }
+
+  void testIndexOf(String s, char c) {
+    int i = s.indexOf(c);
+    if (i != -1) {
+      s.charAt(i);
+    }
+  }
+}
diff --git a/checker/tests/index/StringOffsetTest.java b/checker/tests/index/StringOffsetTest.java
new file mode 100644
index 0000000..bff5780
--- /dev/null
+++ b/checker/tests/index/StringOffsetTest.java
@@ -0,0 +1,10 @@
+public class StringOffsetTest {
+  public static void OffsetString() {
+    char[] chars = new char[10];
+
+    // :: error: (argument)
+    String string2 = new String(chars, 5, 7);
+
+    String string3 = new String(chars, 5, 4);
+  }
+}
diff --git a/checker/tests/index/StringSameLen.java b/checker/tests/index/StringSameLen.java
new file mode 100644
index 0000000..fc854b8
--- /dev/null
+++ b/checker/tests/index/StringSameLen.java
@@ -0,0 +1,53 @@
+public class StringSameLen {
+  public void m(String s) {
+    String t = s;
+
+    for (int i = 0; i < s.length(); ++i) {
+      char c = t.charAt(i);
+    }
+  }
+
+  public void m2(String s) {
+    String t = s.toString();
+
+    for (int i = 0; i < s.length(); ++i) {
+      char c = t.charAt(i);
+    }
+  }
+
+  public void m4(String s) {
+    char[] t = s.toCharArray();
+
+    for (int i = 0; i < s.length(); ++i) {
+      char c = t[i];
+    }
+  }
+
+  public void m6(char[] s) {
+    String t = String.valueOf(s);
+
+    for (int i = 0; i < s.length; ++i) {
+      char c = t.charAt(i);
+    }
+  }
+
+  public void m7(char[] s) {
+    String t = String.copyValueOf(s);
+
+    for (int i = 0; i < s.length; ++i) {
+      char c = t.charAt(i);
+    }
+  }
+
+  public void m8(String s) {
+    String t = s.intern();
+
+    for (int i = 0; i < s.length(); ++i) {
+      char c = t.charAt(i);
+    }
+  }
+
+  public void constructor(String s) {
+    String t = new String(new char[] {'a'});
+  }
+}
diff --git a/checker/tests/index/StringTokenizerMinLen.java b/checker/tests/index/StringTokenizerMinLen.java
new file mode 100644
index 0000000..97b6976
--- /dev/null
+++ b/checker/tests/index/StringTokenizerMinLen.java
@@ -0,0 +1,14 @@
+// Test case for Issue panacekcz#16:
+// https://github.com/panacekcz/checker-framework/issues/16
+
+import java.util.StringTokenizer;
+
+public class StringTokenizerMinLen {
+  void test(String str, String delim, boolean returnDelims) {
+    StringTokenizer st = new StringTokenizer(str, delim, returnDelims);
+    while (st.hasMoreTokens()) {
+      String token = st.nextToken();
+      char c = token.charAt(0);
+    }
+  }
+}
diff --git a/checker/tests/index/SubtractingNonNegatives.java b/checker/tests/index/SubtractingNonNegatives.java
new file mode 100644
index 0000000..e9cd238
--- /dev/null
+++ b/checker/tests/index/SubtractingNonNegatives.java
@@ -0,0 +1,32 @@
+// Test case for issue 98: https://github.com/kelloggm/checker-framework/issues/98
+
+import org.checkerframework.checker.index.qual.*;
+
+public class SubtractingNonNegatives {
+  public static void m4(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) {
+    int k = i;
+    if (k >= j) {
+      @IndexFor("a") int y = k;
+    }
+    for (k = i; k >= j; k -= j) {
+      @IndexFor("a") int x = k;
+    }
+  }
+
+  @SuppressWarnings("lowerbound")
+  void test(int[] a, @Positive int y) {
+    @LTLengthOf("a") int x = a.length - 1;
+    @LTLengthOf(
+        value = {"a", "a"},
+        offset = {"0", "y"})
+    int z = x - y;
+    a[z + y] = 0;
+  }
+
+  @SuppressWarnings("lowerbound")
+  void test2(int[] a, @Positive int y) {
+    @LTLengthOf("a") int x = a.length - 1;
+    int z = x - y;
+    a[z + y] = 0;
+  }
+}
diff --git a/checker/tests/index/SubtractionIndex.java b/checker/tests/index/SubtractionIndex.java
new file mode 100644
index 0000000..9345d72
--- /dev/null
+++ b/checker/tests/index/SubtractionIndex.java
@@ -0,0 +1,31 @@
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.common.value.qual.MinLen;
+
+// @skip-test until the type system is enriched so it can express either
+//   * N = Grid.length and N-1 = Grid.length-1, or
+//   * i < N  and i <= N-1
+
+public class SubtractionIndex {
+
+  // Version without annotations
+  public static void main(String[] args) {
+    int N = 8;
+    int[] grid = new int[N];
+    for (int i = 0; i < N; i++) {
+      System.out.println(grid[(N - 1) - i]);
+    }
+  }
+
+  // Version with annotations
+  public static void mainAnnotated(String[] args) {
+    int N = 8;
+    int @MinLen(8) [] grid = new int[N];
+    @SuppressWarnings("upperbound")
+    @LTLengthOf("grid") int zero = 0;
+    for (@LTLengthOf("grid") int i = zero; i < N; i++) {
+      System.out.println(grid[(N - 1) - i]);
+      System.out.println(grid[(N - i)]);
+      System.out.println(grid[(N - i) - 1]);
+    }
+  }
+}
diff --git a/checker/tests/index/SwitchDataflowRefinement.java b/checker/tests/index/SwitchDataflowRefinement.java
new file mode 100644
index 0000000..6d0ea16
--- /dev/null
+++ b/checker/tests/index/SwitchDataflowRefinement.java
@@ -0,0 +1,23 @@
+public class SwitchDataflowRefinement {
+
+  void readInfo(String[] parts) {
+
+    if (parts.length >= 1) {
+      Integer.parseInt(parts[0]);
+    }
+
+    switch (parts.length) {
+      case 1:
+        Integer.parseInt(parts[0]);
+        break;
+    }
+
+    switch (parts.length) {
+      case 0:
+        break;
+      default:
+        Integer.parseInt(parts[0]);
+        break;
+    }
+  }
+}
diff --git a/checker/tests/index/SwitchTest.java b/checker/tests/index/SwitchTest.java
new file mode 100644
index 0000000..2dd6ec7
--- /dev/null
+++ b/checker/tests/index/SwitchTest.java
@@ -0,0 +1,18 @@
+import org.checkerframework.common.value.qual.IntVal;
+
+public class SwitchTest {
+
+  public String findSlice_unordered(String[] vis) {
+    switch (vis.length) {
+      case 1:
+        @IntVal(1) int x = vis.length;
+        return vis[0];
+      case 2:
+        return vis[0] + vis[1];
+      case 3:
+        return vis[0] + vis[1] + vis[2];
+      default:
+        throw new RuntimeException("Bad length " + vis.length);
+    }
+  }
+}
diff --git a/checker/tests/index/TestAgainstLength.java b/checker/tests/index/TestAgainstLength.java
new file mode 100644
index 0000000..aad4fc0
--- /dev/null
+++ b/checker/tests/index/TestAgainstLength.java
@@ -0,0 +1,19 @@
+// Test case for issue #68:
+// https://github.com/kelloggm/checker-framework/issues/68
+
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+
+public class TestAgainstLength {
+
+  protected int[] values;
+  /** The number of active elements (equivalently, the first unused index). */
+  @IndexOrHigh("values") int num_values;
+
+  public void add(int elt) {
+    if (num_values == values.length) {
+      return;
+    }
+    values[num_values] = elt;
+    num_values++;
+  }
+}
diff --git a/checker/tests/index/ToArrayIndex.java b/checker/tests/index/ToArrayIndex.java
new file mode 100644
index 0000000..7b438c5
--- /dev/null
+++ b/checker/tests/index/ToArrayIndex.java
@@ -0,0 +1,11 @@
+import java.util.ArrayList;
+import org.checkerframework.common.value.qual.MinLen;
+
+// @skip-test until we bring list support back
+
+public class ToArrayIndex {
+
+  public String @MinLen(1) [] m(@MinLen(1) ArrayList<String> compiler) {
+    return compiler.toArray(new String[0]);
+  }
+}
diff --git a/checker/tests/index/TransferAdd.java b/checker/tests/index/TransferAdd.java
new file mode 100644
index 0000000..cd3a8d8
--- /dev/null
+++ b/checker/tests/index/TransferAdd.java
@@ -0,0 +1,85 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class TransferAdd {
+
+  void test() {
+
+    // adding zero and one and two
+
+    int a = -1;
+
+    @Positive int a1 = a + 2;
+
+    @NonNegative int b = a + 1;
+    @NonNegative int c = 1 + a;
+
+    @GTENegativeOne int d = a + 0;
+    @GTENegativeOne int e = 0 + a;
+
+    // :: error: (assignment)
+    @Positive int f = a + 1;
+
+    @NonNegative int g = b + 0;
+
+    @Positive int h = b + 1;
+
+    @Positive int i = h + 1;
+    @Positive int j = h + 0;
+
+    // adding values
+
+    @Positive int k = i + j;
+    // :: error: (assignment)
+    @Positive int l = b + c;
+    // :: error: (assignment)
+    @Positive int m = d + c;
+    // :: error: (assignment)
+    @Positive int n = d + e;
+
+    @Positive int o = h + g;
+    // :: error: (assignment)
+    @Positive int p = h + d;
+
+    @NonNegative int q = b + c;
+    // :: error: (assignment)
+    @NonNegative int r = q + d;
+
+    @NonNegative int s = k + d;
+    @GTENegativeOne int t = s + d;
+
+    // increments
+
+    // :: error: (assignment)
+    @Positive int u = b++;
+
+    @Positive int u1 = b;
+
+    @Positive int v = ++c;
+
+    @Positive int v1 = c;
+
+    int n1p1 = -1, n1p2 = -1;
+
+    @NonNegative int w = ++n1p1;
+
+    @NonNegative int w1 = n1p1;
+
+    // :: error: (assignment)
+    @Positive int w2 = n1p1;
+    // :: error: (assignment)
+    @Positive int w3 = n1p1++;
+
+    // :: error: (assignment)
+    @NonNegative int x = n1p2++;
+
+    @NonNegative int x1 = n1p2;
+
+    // :: error: (assignment)
+    @Positive int y = ++d;
+    // :: error: (assignment)
+    @Positive int z = e++;
+  }
+}
+// a comment
diff --git a/checker/tests/index/TransferDivide.java b/checker/tests/index/TransferDivide.java
new file mode 100644
index 0000000..b52ab90
--- /dev/null
+++ b/checker/tests/index/TransferDivide.java
@@ -0,0 +1,61 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class TransferDivide {
+
+  void test() {
+    int a = -1;
+    int b = 0;
+    int c = 1;
+    int d = 2;
+
+    /** literals */
+    @Positive int e = -1 / -1;
+
+    /** 0 / * -> NN */
+    @NonNegative int f = 0 / a;
+    @NonNegative int g = 0 / d;
+
+    /** * / 1 -> * */
+    @GTENegativeOne int h = a / 1;
+    @NonNegative int i = b / 1;
+    @Positive int j = c / 1;
+    @Positive int k = d / 1;
+
+    /** pos / pos -> nn */
+    @NonNegative int l = d / c;
+    @NonNegative int m = c / d;
+    // :: error: (assignment)
+    @Positive int n = c / d;
+
+    /** nn / pos -> nn */
+    @NonNegative int o = b / c;
+    // :: error: (assignment)
+    @Positive int p = b / d;
+
+    /** pos / nn -> nn */
+    @NonNegative int q = d / l;
+    // :: error: (assignment)
+    @Positive int r = c / l;
+
+    /** nn / nn -> nn */
+    @NonNegative int s = b / q;
+    // :: error: (assignment)
+    @Positive int t = b / q;
+
+    /** n1p / pos -> n1p */
+    @GTENegativeOne int u = a / d;
+    @GTENegativeOne int v = a / c;
+    // :: error: (assignment)
+    @NonNegative int w = a / c;
+
+    /** n1p / nn -> n1p */
+    @GTENegativeOne int x = a / l;
+  }
+
+  void testDivideByTwo(@NonNegative int x) {
+    @NonNegative int y = x / 2;
+  }
+}
+// a comment
diff --git a/checker/tests/index/TransferMod.java b/checker/tests/index/TransferMod.java
new file mode 100644
index 0000000..2144ac4
--- /dev/null
+++ b/checker/tests/index/TransferMod.java
@@ -0,0 +1,34 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class TransferMod {
+
+  void test() {
+    int aa = -100;
+    int a = -1;
+    int b = 0;
+    int c = 1;
+    int d = 2;
+
+    @Positive int e = 5 % 3;
+    @NonNegative int f = -100 % 1;
+
+    @NonNegative int g = aa % -1;
+    @NonNegative int h = aa % 1;
+    @NonNegative int i = d % -1;
+    @NonNegative int j = d % 1;
+
+    @NonNegative int k = d % c;
+    @NonNegative int l = b % c;
+    @NonNegative int m = c % d;
+
+    @NonNegative int n = c % a;
+    @NonNegative int o = b % a;
+
+    @GTENegativeOne int p = a % a;
+    @GTENegativeOne int q = a % d;
+    @GTENegativeOne int r = a % c;
+  }
+}
+// a comment
diff --git a/checker/tests/index/TransferSub.java b/checker/tests/index/TransferSub.java
new file mode 100644
index 0000000..216ed89
--- /dev/null
+++ b/checker/tests/index/TransferSub.java
@@ -0,0 +1,76 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class TransferSub {
+
+  void test() {
+    // zero, one, and two
+    int a = 1;
+
+    @NonNegative int b = a - 1;
+    // :: error: (assignment)
+    @Positive int c = a - 1;
+    @GTENegativeOne int d = a - 2;
+
+    // :: error: (assignment)
+    @NonNegative int e = a - 2;
+
+    @GTENegativeOne int f = b - 1;
+    // :: error: (assignment)
+    @NonNegative int g = b - 1;
+
+    // :: error: (assignment)
+    @GTENegativeOne int h = f - 1;
+
+    @GTENegativeOne int i = f - 0;
+    @NonNegative int j = b - 0;
+    @Positive int k = a - 0;
+
+    // :: error: (assignment)
+    @Positive int l = j - 0;
+    // :: error: (assignment)
+    @NonNegative int m = i - 0;
+
+    // :: error: (assignment)
+    @Positive int n = a - k;
+    // this would be an error if the values of b and j (both zero) weren't known at compile time
+    @NonNegative int o = b - j;
+    /* i and d both have compile time value -1, so this is legal.
+    The general case of GTEN1 - GTEN1 is not, though. */
+    @GTENegativeOne int p = i - d;
+
+    // decrements
+
+    // :: error: (unary.decrement) :: error: (assignment)
+    @Positive int q = --k; // k = 0
+
+    // :: error: (unary.decrement)
+    @NonNegative int r = k--; // after this k = -1
+
+    int k1 = 0;
+    @NonNegative int s = k1--;
+
+    // :: error: (assignment)
+    @NonNegative int s1 = k1;
+
+    // transferred to SimpleTransferSub.java
+    // this section is failing due to CF bug
+    // int k2 = 0;
+    // // :: error: (assignment)
+    // @Positive int s2 = k2--;
+
+    k1 = 1;
+    @NonNegative int t = --k1;
+
+    k1 = 1;
+    // :: error: (assignment)
+    @Positive int t1 = --k1;
+
+    int u1 = -1;
+    @GTENegativeOne int x = u1--;
+    // :: error: (assignment)
+    @GTENegativeOne int x1 = u1;
+  }
+}
+// a comment
diff --git a/checker/tests/index/TransferTimes.java b/checker/tests/index/TransferTimes.java
new file mode 100644
index 0000000..a368a35
--- /dev/null
+++ b/checker/tests/index/TransferTimes.java
@@ -0,0 +1,28 @@
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+
+public class TransferTimes {
+
+  void test() {
+    int a = 1;
+    @Positive int b = a * 1;
+    @Positive int c = 1 * a;
+    @NonNegative int d = 0 * a;
+    // :: error: (assignment)
+    @NonNegative int e = -1 * a;
+
+    int g = -1;
+    @NonNegative int h = g * 0;
+    // :: error: (assignment)
+    @Positive int i = g * 0;
+    // :: error: (assignment)
+    @Positive int j = g * a;
+
+    int k = 0;
+    int l = 1;
+    @Positive int m = a * l;
+    @NonNegative int n = k * l;
+    @NonNegative int o = k * k;
+  }
+}
+// a comment
diff --git a/checker/tests/index/TypeArrayLengthWithSameLen.java b/checker/tests/index/TypeArrayLengthWithSameLen.java
new file mode 100644
index 0000000..e134075
--- /dev/null
+++ b/checker/tests/index/TypeArrayLengthWithSameLen.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.index.qual.*;
+
+public class TypeArrayLengthWithSameLen {
+  void test(int @SameLen("#2") [] a, int @SameLen("#1") [] b, int[] c) {
+    if (a.length == c.length) {
+      @LTEqLengthOf({"a", "b", "c"}) int x = b.length;
+    }
+  }
+}
diff --git a/checker/tests/index/UBLiteralFlow.java b/checker/tests/index/UBLiteralFlow.java
new file mode 100644
index 0000000..61dc8a6
--- /dev/null
+++ b/checker/tests/index/UBLiteralFlow.java
@@ -0,0 +1,186 @@
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.index.qual.IndexOrLow;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class UBLiteralFlow {
+
+  private static @IndexOrLow("#1") int lineStartIndexPartial(
+      String s, @GTENegativeOne int lineStart) {
+    int result;
+    if (lineStart >= s.length()) {
+      result = -1;
+    } else {
+      result = lineStart;
+    }
+    return result;
+  }
+
+  private static @LTLengthOf("#1") int lineStartIndexPartial2(
+      String s, @GTENegativeOne int lineStart) {
+    int result;
+    if (lineStart >= s.length()) {
+      result = -1;
+    } else {
+      result = lineStart;
+    }
+    return result;
+  }
+
+  private static @LTLengthOf(value = "#1", offset = "1") int lineStartIndexPartial3(
+      String s, @GTENegativeOne int lineStart) {
+    int result;
+    if (lineStart >= s.length()) {
+      result = -1;
+    } else {
+      result = lineStart;
+    }
+    // :: error: (return)
+    return result;
+  }
+
+  private static @LTLengthOf(value = "#1", offset = "-1") int lineStartIndexPartial4(
+      String s, @GTENegativeOne int lineStart) {
+    int result;
+    if (lineStart >= s.length()) {
+      result = -1;
+    } else {
+      result = lineStart;
+    }
+    return result;
+  }
+
+  /**
+   * Given a string, return the index of the start of a line, after {@code start}.
+   *
+   * @param s the string in which to find the start of a line
+   * @param start the index at which to start looking for the start of a line
+   * @return the index of the start of a line, or -1 if no such exists
+   */
+  private static @IndexOrLow("#1") int lineStartIndex(String s, int start) {
+    if (s.length() == 0) {
+      return -1;
+    }
+    if (start == 0) {
+      // It doesn't make sense to call this routine with 0, but return 0 anyway.
+      return 0;
+    }
+    if (start > s.length()) {
+      return -1;
+    }
+    // possible line terminators:  "\n", "\r\n", "\r".
+    int newlinePos = s.indexOf("\n", start - 1);
+    int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1;
+    int returnPos1 = s.indexOf("\r\n", start - 2);
+    int returnPos2 = s.indexOf("\r", start - 1);
+    int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2;
+    int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1;
+    int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2));
+    if (lineStart >= s.length()) {
+      return -1;
+    } else {
+      return lineStart;
+    }
+  }
+
+  /**
+   * Given a string, return the index of the start of a line, after {@code start}.
+   *
+   * @param s the string in which to find the start of a line
+   * @param start the index at which to start looking for the start of a line
+   * @return the index of the start of a line, or -1 if no such exists
+   */
+  private static @LTLengthOf("#1") int lineStartIndex2(String s, int start) {
+    if (s.length() == 0) {
+      return -1;
+    }
+    if (start == 0) {
+      // It doesn't make sense to call this routine with 0, but return 0 anyway.
+      return 0;
+    }
+    if (start > s.length()) {
+      return -1;
+    }
+    // possible line terminators:  "\n", "\r\n", "\r".
+    int newlinePos = s.indexOf("\n", start - 1);
+    int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1;
+    int returnPos1 = s.indexOf("\r\n", start - 2);
+    int returnPos2 = s.indexOf("\r", start - 1);
+    int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2;
+    int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1;
+    int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2));
+    if (lineStart >= s.length()) {
+      return -1;
+    } else {
+      return lineStart;
+    }
+  }
+
+  /**
+   * Given a string, return the index of the start of a line, after {@code start}.
+   *
+   * @param s the string in which to find the start of a line
+   * @param start the index at which to start looking for the start of a line
+   * @return the index of the start of a line, or -1 if no such exists
+   */
+  private static @LTLengthOf(value = "#1", offset = "1") int lineStartIndex3(String s, int start) {
+    if (s.length() == 0) {
+      // :: error: (return)
+      return -1;
+    }
+    if (start == 0) {
+      // It doesn't make sense to call this routine with 0, but return 0 anyway.
+      // :: error: (return)
+      return 0;
+    }
+    if (start > s.length()) {
+      return -1;
+    }
+    // possible line terminators:  "\n", "\r\n", "\r".
+    int newlinePos = s.indexOf("\n", start - 1);
+    int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1;
+    int returnPos1 = s.indexOf("\r\n", start - 2);
+    int returnPos2 = s.indexOf("\r", start - 1);
+    int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2;
+    int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1;
+    int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2));
+    if (lineStart >= s.length()) {
+      return -1;
+    } else {
+      // :: error: (return)
+      return lineStart;
+    }
+  }
+
+  /**
+   * Given a string, return the index of the start of a line, after {@code start}.
+   *
+   * @param s the string in which to find the start of a line
+   * @param start the index at which to start looking for the start of a line
+   * @return the index of the start of a line, or -1 if no such exists
+   */
+  private static @LTLengthOf(value = "#1", offset = "-1") int lineStartIndex4(String s, int start) {
+    if (s.length() == 0) {
+      return -1;
+    }
+    if (start == 0) {
+      // It doesn't make sense to call this routine with 0, but return 0 anyway.
+      return 0;
+    }
+    if (start > s.length()) {
+      return -1;
+    }
+    // possible line terminators:  "\n", "\r\n", "\r".
+    int newlinePos = s.indexOf("\n", start - 1);
+    int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1;
+    int returnPos1 = s.indexOf("\r\n", start - 2);
+    int returnPos2 = s.indexOf("\r", start - 1);
+    int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2;
+    int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1;
+    int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2));
+    if (lineStart >= s.length()) {
+      return -1;
+    } else {
+      return lineStart;
+    }
+  }
+}
diff --git a/checker/tests/index/UBPoly.java b/checker/tests/index/UBPoly.java
new file mode 100644
index 0000000..c16b4f7
--- /dev/null
+++ b/checker/tests/index/UBPoly.java
@@ -0,0 +1,21 @@
+// test case for issue 163: https://github.com/kelloggm/checker-framework/issues/163
+
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.PolyUpperBound;
+
+public class UBPoly {
+  public static void main(String[] args) {
+    char[] a = new char[10];
+    poly(a, 100);
+  }
+
+  public static void poly(char[] a, @NonNegative @PolyUpperBound int i) {
+    // :: error: (argument)
+    access(a, i);
+  }
+
+  public static void access(char[] a, @NonNegative @LTLengthOf("#1") int j) {
+    char c = a[j];
+  }
+}
diff --git a/checker/tests/index/UBSubtyping.java b/checker/tests/index/UBSubtyping.java
new file mode 100644
index 0000000..aad087d
--- /dev/null
+++ b/checker/tests/index/UBSubtyping.java
@@ -0,0 +1,30 @@
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+import org.checkerframework.checker.index.qual.UpperBoundUnknown;
+
+public class UBSubtyping {
+  int[] arr = {1};
+  int[] arr2 = {1};
+  int[] arr3 = {1};
+
+  void test(@LTEqLengthOf({"arr", "arr2", "arr3"}) int test) {
+    // :: error: (assignment)
+    @LTEqLengthOf({"arr"}) int a = 1;
+    // :: error: (assignment)
+    @LTLengthOf({"arr"}) int a1 = 1;
+
+    // :: error: (assignment)
+    @LTLengthOf({"arr"}) int b = a;
+    @UpperBoundUnknown int d = a;
+
+    // :: error: (assignment)
+    @LTLengthOf({"arr2"}) int g = a;
+
+    // :: error: (assignment)
+    @LTEqLengthOf({"arr", "arr2", "arr3"}) int h = 2;
+
+    @LTEqLengthOf({"arr", "arr2"}) int h2 = test;
+    @LTEqLengthOf({"arr"}) int i = test;
+    @LTEqLengthOf({"arr", "arr3"}) int j = test;
+  }
+}
diff --git a/checker/tests/index/UnaryOperationParsedIncorrectly.java b/checker/tests/index/UnaryOperationParsedIncorrectly.java
new file mode 100644
index 0000000..7096493
--- /dev/null
+++ b/checker/tests/index/UnaryOperationParsedIncorrectly.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.index.qual.LessThan;
+
+public class UnaryOperationParsedIncorrectly {
+  void method1(@LessThan("#2") int var1, int var2) {
+    // Function implementation
+  }
+
+  void method2() {
+    method1(-10, 10);
+    method1(-10, +10);
+  }
+}
diff --git a/checker/tests/index/UncheckedMinLen.java b/checker/tests/index/UncheckedMinLen.java
new file mode 100644
index 0000000..944d161
--- /dev/null
+++ b/checker/tests/index/UncheckedMinLen.java
@@ -0,0 +1,45 @@
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.MinLen;
+
+// test case for kelloggm#183: https://github.com/kelloggm/checker-framework/issues/183
+
+public class UncheckedMinLen {
+  void addToNonNegative(@NonNegative int l, Object v) {
+    // :: error: (assignment)
+    Object @MinLen(100) [] o = new Object[l + 1];
+    o[99] = v;
+  }
+
+  void addToPositive(@Positive int l, Object v) {
+    // :: error: (assignment)
+    Object @MinLen(100) [] o = new Object[l + 1];
+    o[99] = v;
+  }
+
+  void addToUnboundedIntRange(@IntRange(from = 0) int l, Object v) {
+    // :: error: (assignment)
+    Object @MinLen(100) [] o = new Object[l + 1];
+    o[99] = v;
+  }
+
+  // Similar code that correctly gives warnings
+  void addToPositiveOK(@NonNegative int l, Object v) {
+    Object[] o = new Object[l + 1];
+    // :: error: (array.access.unsafe.high.constant)
+    o[99] = v;
+  }
+
+  void addToBoundedIntRangeOK(@IntRange(from = 0, to = 1) int l, Object v) {
+    // :: error: (assignment)
+    Object @MinLen(100) [] o = new Object[l + 1];
+    o[99] = v;
+  }
+
+  void subtractFromPositiveOK(@Positive int l, Object v) {
+    // :: error: (assignment)
+    Object @MinLen(100) [] o = new Object[l - 1];
+    o[99] = v;
+  }
+}
diff --git a/checker/tests/index/UpperBoundRefinement.java b/checker/tests/index/UpperBoundRefinement.java
new file mode 100644
index 0000000..302a6ca
--- /dev/null
+++ b/checker/tests/index/UpperBoundRefinement.java
@@ -0,0 +1,40 @@
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+@SuppressWarnings("lowerbound")
+public class UpperBoundRefinement {
+  // If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int and expression
+  // j is less than or equal to the length of f1, then the type of i + j is @LTLengthOf("f2")
+  void test(int[] f1, int[] f2) {
+    @LTLengthOf(value = "f2", offset = "f1.length") int i = (f2.length - 1) - f1.length;
+    @LTLengthOf("f1") int j = f1.length - 1;
+    @LTLengthOf("f2") int x = i + j;
+    @LTLengthOf("f2") int y = i + f1.length;
+  }
+
+  void test2() {
+    double[] f1 = new double[10];
+    double[] f2 = new double[20];
+
+    for (int j = 0; j < f2.length; j++) {
+      f2[j] = j;
+    }
+    for (int i = 0; i < f2.length - f1.length; i++) {
+      // fill up f1 with elements of f2
+      for (int j = 0; j < f1.length; j++) {
+        f1[j] = f2[i + j];
+      }
+    }
+  }
+
+  public void test3(double[] a, double[] sub) {
+    int a_index_max = a.length - sub.length;
+    // Has type @LTL(value={"a","sub"}, offset={"-1 + sub.length", "-1 + a.length"})
+
+    for (int i = 0; i <= a_index_max; i++) { // i has the same type as a_index_max
+      for (int j = 0; j < sub.length; j++) { // j is @LTL("sub")
+        // i + j is safe here. Because j is LTL("sub"), it should count as ("-1 + sub.length")
+        double d = a[i + j];
+      }
+    }
+  }
+}
diff --git a/checker/tests/index/ValueCheckerProblem.java b/checker/tests/index/ValueCheckerProblem.java
new file mode 100644
index 0000000..2377ca0
--- /dev/null
+++ b/checker/tests/index/ValueCheckerProblem.java
@@ -0,0 +1,5 @@
+public class ValueCheckerProblem {
+  void test() {
+    Object o = new Object[][] {null};
+  }
+}
diff --git a/checker/tests/index/VarArgsIncompatible.java b/checker/tests/index/VarArgsIncompatible.java
new file mode 100644
index 0000000..5bf8739
--- /dev/null
+++ b/checker/tests/index/VarArgsIncompatible.java
@@ -0,0 +1,10 @@
+public class VarArgsIncompatible {
+
+  public static void test(int[] arr) {
+    help(arr);
+  }
+
+  @SafeVarargs
+  @SuppressWarnings("varargs")
+  public static <T> void help(T... arr) {}
+}
diff --git a/checker/tests/index/VarLteVar.java b/checker/tests/index/VarLteVar.java
new file mode 100644
index 0000000..4ea0eb1
--- /dev/null
+++ b/checker/tests/index/VarLteVar.java
@@ -0,0 +1,41 @@
+// Test case for https://github.com/kelloggm/checker-framework/issues/158
+// It is easy to see that:
+//   * i is an index for intermediate
+//   * length <= i (or, at least length <= i+1)
+// but I don't see how to verify that length is an index for intermediate.
+
+// @skip-test
+
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+
+public class VarLteVar {
+
+  /** Returns an array that is equivalent to the set difference of seq1 and seq2. */
+  public static boolean[] setDiff(boolean[] seq1, boolean[] seq2) {
+    boolean[] intermediate = new boolean[seq1.length];
+    int length = 0;
+    for (int i = 0; i < seq1.length; i++) {
+      if (!memberOf(seq1[i], seq2)) {
+        intermediate[length++] = seq1[i];
+      }
+    }
+    return subarray(intermediate, 0, length);
+  }
+
+  public static boolean memberOf(boolean elt, boolean[] arr) {
+    for (int i = 0; i < arr.length; i++) {
+      if (arr[i] == elt) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @SuppressWarnings("index") // not relevant to this test case
+  public static boolean[] subarray(
+      boolean[] a, @IndexOrHigh("#1") int startindex, @IndexOrHigh("#1") int length) {
+    boolean[] result = new boolean[length];
+    System.arraycopy(a, startindex, result, 0, length);
+    return result;
+  }
+}
diff --git a/checker/tests/index/ViewpointAdaptTest.java b/checker/tests/index/ViewpointAdaptTest.java
new file mode 100644
index 0000000..c3440ea
--- /dev/null
+++ b/checker/tests/index/ViewpointAdaptTest.java
@@ -0,0 +1,16 @@
+// @skip-test
+
+import org.checkerframework.checker.index.qual.LTEqLengthOf;
+import org.checkerframework.checker.index.qual.LTLengthOf;
+
+public class ViewpointAdaptTest {
+
+  void ListGet(
+      @LTLengthOf("list") int index, @LTEqLengthOf("list") int notIndex, List<Integer> list) {
+    // :: error: (argument)
+    list.get(index);
+
+    // :: error: (argument)
+    list.get(notIndex);
+  }
+}
diff --git a/checker/tests/index/VoidType.java b/checker/tests/index/VoidType.java
new file mode 100644
index 0000000..ad6ec89
--- /dev/null
+++ b/checker/tests/index/VoidType.java
@@ -0,0 +1,3 @@
+public class VoidType {
+  private Class<?> main_class = Void.TYPE;
+}
diff --git a/checker/tests/index/ZeroMinLen.java b/checker/tests/index/ZeroMinLen.java
new file mode 100644
index 0000000..d89255e
--- /dev/null
+++ b/checker/tests/index/ZeroMinLen.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class ZeroMinLen {
+
+  int @MinLen(1) [] nums;
+  int[] nums2;
+
+  @IndexFor("nums") int current_index;
+
+  @IndexFor("nums2") int current_index2;
+
+  void test() {
+    current_index = 0;
+    // :: error: (assignment)
+    current_index2 = 0;
+  }
+}
diff --git a/checker/tests/initialization/CastInit.java b/checker/tests/initialization/CastInit.java
new file mode 100644
index 0000000..2f9b457
--- /dev/null
+++ b/checker/tests/initialization/CastInit.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.initialization.qual.Initialized;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+
+public class CastInit {
+
+  public CastInit() {
+    @UnknownInitialization CastInit t1 = (@UnknownInitialization CastInit) this;
+    // :: error: (initialization.cast)
+    @Initialized CastInit t2 = (@Initialized CastInit) this;
+  }
+}
diff --git a/checker/tests/initialization/ChainedInitialization.java b/checker/tests/initialization/ChainedInitialization.java
new file mode 100644
index 0000000..7892432
--- /dev/null
+++ b/checker/tests/initialization/ChainedInitialization.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class ChainedInitialization {
+
+  @NonNull String f;
+  @NonNull String g = f = "hello";
+
+  // Adding this empty initializer suppresses the warning.
+  //     {}
+
+  // Adding this constructor does not suppress the warning.
+  // ChainedInitialization() {}
+
+}
diff --git a/checker/tests/initialization/Commitment.java b/checker/tests/initialization/Commitment.java
new file mode 100644
index 0000000..47e980c
--- /dev/null
+++ b/checker/tests/initialization/Commitment.java
@@ -0,0 +1,76 @@
+import org.checkerframework.checker.initialization.qual.Initialized;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Commitment {
+
+  @NonNull String t;
+
+  // :: error: (initialization.field.type)
+  @NonNull @UnderInitialization String a;
+  // :: error: (initialization.field.type)
+  @Initialized String b;
+  @UnknownInitialization @Nullable String c;
+
+  // :: error: (initialization.constructor.return.type)
+  public @UnderInitialization Commitment(int i) {
+    a = "";
+    t = "";
+    b = "";
+  }
+
+  // :: error: (initialization.constructor.return.type)
+  public @Initialized Commitment(int i, int j) {
+    a = "";
+    t = "";
+    b = "";
+  }
+
+  // :: error: (initialization.constructor.return.type)
+  // :: error: (nullness.on.constructor)
+  public @Initialized @NonNull Commitment(boolean i) {
+    a = "";
+    t = "";
+    b = "";
+  }
+
+  // :: error: (nullness.on.constructor)
+  public @Nullable Commitment(char i) {
+    a = "";
+    t = "";
+    b = "";
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public Commitment() {
+    // :: error: (dereference.of.nullable)
+    t.toLowerCase();
+
+    t = "";
+
+    @UnderInitialization @NonNull Commitment c = this;
+
+    @UnknownInitialization @NonNull Commitment c1 = this;
+
+    // :: error: (assignment)
+    @Initialized @NonNull Commitment c2 = this;
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public Commitment(@UnknownInitialization Commitment arg) {
+    t = "";
+
+    // :: error: (argument)
+    @UnderInitialization Commitment t = new Commitment(this, 1);
+
+    // :: error: (assignment)
+    @Initialized Commitment t1 = new Commitment(this);
+
+    @UnderInitialization Commitment t2 = new Commitment(this);
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public Commitment(Commitment arg, int i) {}
+}
diff --git a/checker/tests/initialization/Commitment2.java b/checker/tests/initialization/Commitment2.java
new file mode 100644
index 0000000..1e7a068
--- /dev/null
+++ b/checker/tests/initialization/Commitment2.java
@@ -0,0 +1,35 @@
+import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+
+public class Commitment2 {
+
+  // :: error: (assignment)
+  Commitment2 g = create();
+
+  Commitment2 h;
+
+  @NotOnlyInitialized Commitment2 c;
+
+  @NotOnlyInitialized Commitment2 f;
+
+  public void test(@UnderInitialization Commitment2 c) {
+    // :: error: (initialization.field.write.initialized)
+    f = c;
+  }
+
+  public static @UnknownInitialization Commitment2 create() {
+    return new Commitment2();
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public Commitment2() {}
+
+  // :: error: (initialization.fields.uninitialized)
+  public Commitment2(@UnderInitialization Commitment2 likeAnEagle) {
+    // :: error: (assignment)
+    h = likeAnEagle;
+
+    c = likeAnEagle;
+  }
+}
diff --git a/checker/tests/initialization/CommitmentFlow.java b/checker/tests/initialization/CommitmentFlow.java
new file mode 100644
index 0000000..e1bc415
--- /dev/null
+++ b/checker/tests/initialization/CommitmentFlow.java
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.initialization.qual.Initialized;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class CommitmentFlow {
+
+  @NonNull CommitmentFlow t;
+
+  public CommitmentFlow(CommitmentFlow arg) {
+    t = arg;
+  }
+
+  void foo(
+      @UnknownInitialization CommitmentFlow mystery, @Initialized CommitmentFlow triedAndTrue) {
+    CommitmentFlow local = null;
+
+    local = mystery;
+    // :: error: (method.invocation)
+    local.hashCode();
+
+    local = triedAndTrue;
+    local.hashCode(); // should determine that it is Initialized based on flow
+  }
+}
diff --git a/checker/tests/initialization/FBCList.java b/checker/tests/initialization/FBCList.java
new file mode 100644
index 0000000..a04bc8f
--- /dev/null
+++ b/checker/tests/initialization/FBCList.java
@@ -0,0 +1,54 @@
+import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+// This example is taken from the FBC paper, figure 1 (and has some additional code in main below).
+// We made the list generic.
+public class FBCList<T> {
+  @NotOnlyInitialized FBCNode<T> sentinel;
+
+  public FBCList() {
+    this.sentinel = new FBCNode<>(this);
+  }
+
+  void insert(@Nullable T data) {
+    this.sentinel.insertAfter(data);
+  }
+
+  public static void main() {
+    FBCList<Integer> l = new FBCList<>();
+    l.insert(1);
+    l.insert(2);
+  }
+}
+
+class FBCNode<T> {
+  @NotOnlyInitialized FBCNode<T> prev;
+
+  @NotOnlyInitialized FBCNode<T> next;
+
+  @NotOnlyInitialized FBCList parent;
+
+  @Nullable T data;
+
+  // for sentinel construction
+  FBCNode(@UnderInitialization FBCList parent) {
+    this.parent = parent;
+    this.prev = this;
+    this.next = this;
+  }
+
+  // for data node construction
+  FBCNode(FBCNode<T> prev, FBCNode<T> next, @Nullable T data) {
+    this.parent = prev.parent;
+    this.prev = prev;
+    this.next = next;
+    this.data = data;
+  }
+
+  void insertAfter(@Nullable T data) {
+    FBCNode<T> n = new FBCNode<>(this, this.next, data);
+    this.next.prev = n;
+    this.next = n;
+  }
+}
diff --git a/checker/tests/initialization/FieldSuppressWarnings.java b/checker/tests/initialization/FieldSuppressWarnings.java
new file mode 100644
index 0000000..38b5498
--- /dev/null
+++ b/checker/tests/initialization/FieldSuppressWarnings.java
@@ -0,0 +1,29 @@
+public class FieldSuppressWarnings {
+
+  static class FieldSuppressWarnings1 {
+    // :: error: (initialization.field.uninitialized)
+    private Object notInitialized;
+  }
+
+  static class FieldSuppressWarnings2 {
+    @SuppressWarnings("initialization.field.uninitialized")
+    private Object notInitializedButSuppressed1;
+  }
+
+  static class FieldSuppressWarnings3 {
+    @SuppressWarnings("initialization")
+    private Object notInitializedButSuppressed2;
+  }
+
+  static class FieldSuppressWarnings4 {
+    private Object initialized1;
+
+    {
+      initialized1 = new Object();
+    }
+  }
+
+  static class FieldSuppressWarnings5 {
+    private Object initialized2 = new Object();
+  }
+}
diff --git a/checker/tests/initialization/FlowFbc.java b/checker/tests/initialization/FlowFbc.java
new file mode 100644
index 0000000..eab3672
--- /dev/null
+++ b/checker/tests/initialization/FlowFbc.java
@@ -0,0 +1,40 @@
+import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class FlowFbc {
+
+  @NonNull String f;
+  @NotOnlyInitialized @NonNull String g;
+
+  public FlowFbc(String arg) {
+    // :: error: (dereference.of.nullable)
+    f.toLowerCase();
+    // :: error: (dereference.of.nullable)
+    g.toLowerCase();
+    f = arg;
+    g = arg;
+    foo();
+    f.toLowerCase();
+    // :: error: (method.invocation)
+    g.toLowerCase();
+    f = arg;
+  }
+
+  void test() {
+    @Nullable String s = null;
+    s = "a";
+    s.toLowerCase();
+  }
+
+  void test2(@Nullable String s) {
+    if (s != null) {
+      s.toLowerCase();
+    }
+  }
+
+  void foo(@UnknownInitialization FlowFbc this) {}
+
+  // TODO Pure, etc.
+}
diff --git a/checker/tests/initialization/Issue556a.java b/checker/tests/initialization/Issue556a.java
new file mode 100644
index 0000000..8321eb0
--- /dev/null
+++ b/checker/tests/initialization/Issue556a.java
@@ -0,0 +1,17 @@
+// @skip-test
+
+// Minimal test case for issue #556: https://github.com/typetools/checker-framework/issues/556
+// For explanations, see file Issue556b.java .
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class Issue556a {
+
+  public static final Issue556a SELF = new Issue556a();
+  private static final Object OBJ = new Object();
+
+  private Issue556a() {
+    // :: error: (assignment)
+    @NonNull Object o = OBJ;
+  }
+}
diff --git a/checker/tests/initialization/Issue556b.java b/checker/tests/initialization/Issue556b.java
new file mode 100644
index 0000000..8904e3e
--- /dev/null
+++ b/checker/tests/initialization/Issue556b.java
@@ -0,0 +1,96 @@
+// @skip-test
+
+// To reproduce the problem, run:
+//   javac -processor nullness Issue556b.java
+//   java Issue556b
+// and observe that the javac execution issues no warnings but the java execution suffers a null
+// pointer exception.
+
+// Before the constructor is invoked, static initializers and static blocks are executed.  This
+// suggests that the Initialization Checker can assume that static fields are initialized in the
+// constructor.
+//
+// However, if any user-defined code -- including callbacks such as executions of equals() and
+// hashCode() -- appears in a static initializer or static block, then static fields cannot be
+// assumed to be initialized within the constructor.
+
+public class Issue556b {
+  static class Parent {
+    private final Object o;
+
+    public Parent(final Object o) {
+      this.o = o;
+    }
+
+    @Override
+    public String toString() {
+      return o.toString();
+    }
+  }
+
+  static class Child extends Parent {
+    public static final Child CHILD = new Child();
+    private static final Object OBJ = new Object();
+
+    private Child() {
+      // This call should not be legal, because at the time that the call occurs, the static
+      // initializers of Child have not yet finished executing and therefore CHILD and OBJ are not
+      // necessarily initialized and are not necessarily non-null.
+      // :: error: (method.invocation)
+      super(OBJ);
+    }
+  }
+
+  static class Child2 extends Parent {
+    public static final Child2 CHILD;
+    private static final Object OBJ;
+
+    static {
+      CHILD = new Child2();
+      OBJ = new Object();
+    }
+
+    private Child2() {
+      // This call should not be legal, because at the time that the call occurs, the static
+      // initializers of Child have not yet finished executing and therefore CHILD and OBJ are not
+      // necessarily initialized and are not necessarily non-null.
+      // :: error: (method.invocation)
+      super(OBJ);
+    }
+  }
+
+  // Changing the order of the OBJ and CHILD fields prevents a null pointer exception.
+  static class ChildOk1 extends Parent {
+    private static final Object OBJ = new Object();
+    public static final Child CHILD = new Child();
+
+    private ChildOk1() {
+      // This call is legal, because OBJ is non-null at the time of the
+      // call.  That's because OBJ is initialized before CHILD and
+      // therefore before the call to "new Child()".
+      super(OBJ);
+    }
+  }
+
+  // Changing the order of the OBJ and CHILD field assignments prevents a null pointer exception.
+  static class ChildOk2 extends Parent {
+    public static final ChildOk2 CHILD;
+    private static final Object OBJ;
+
+    static {
+      OBJ = new Object();
+      CHILD = new ChildOk2();
+    }
+
+    private ChildOk2() {
+      // This call is legal, because OBJ is non-null at the time of the
+      // call.  That's because OBJ is initialized before CHILD and
+      // therefore before the call to "new Child()".
+      super(OBJ);
+    }
+  }
+
+  public static void main(final String[] args) {
+    System.out.println(Child.CHILD);
+  }
+}
diff --git a/checker/tests/initialization/Issue574.java b/checker/tests/initialization/Issue574.java
new file mode 100644
index 0000000..e002324
--- /dev/null
+++ b/checker/tests/initialization/Issue574.java
@@ -0,0 +1,53 @@
+// Test case for issue #574: https://github.com/typetools/checker-framework/issues/574
+
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+
+@SuppressWarnings({
+  // A warning is issued that fields are not initialized in the constructor.
+  // That is expected and it is not what is being verified in this test.
+  "initialization.field.uninitialized",
+  // Normally @UnknownInitialization is the only initialization annotation allowed on fields.
+  // However, for the purposes of this test, fields must be annotated with @UnderInitialization.
+  "initialization.field.type"
+})
+public class Issue574 {
+  @UnderInitialization(Object.class) Object o1;
+
+  @UnderInitialization(String.class) Object o2;
+
+  @UnderInitialization(Character.class) Object o3;
+
+  @UnderInitialization(Number.class) Object o4;
+
+  @UnderInitialization(Double.class) Object o5;
+
+  @UnderInitialization(Integer.class) Object o6;
+
+  @UnderInitialization(CharSequence.class) Object i1; // CharSequence is an interface
+
+  void testLubOfClasses(boolean flag) {
+    @UnderInitialization(Object.class) Object l1 = flag ? o2 : o3;
+    @UnderInitialization(Number.class) Object l2 = flag ? o5 : o6;
+
+    @UnderInitialization(Object.class) Object l3 = flag ? o1 : o2;
+    @UnderInitialization(Object.class) Object l4 = flag ? o1 : o3;
+
+    @UnderInitialization(Number.class) Object l5 = flag ? o4 : o5;
+    @UnderInitialization(Number.class) Object l6 = flag ? o4 : o6;
+
+    // :: error: (assignment)
+    @UnderInitialization(Character.class) Object l7 = flag ? o1 : o2;
+    // :: error: (assignment)
+    @UnderInitialization(Integer.class) Object l8 = flag ? o4 : o5;
+  }
+
+  void testLubOfClassesAndInterfaces(boolean flag) {
+    @UnderInitialization(Object.class) Object l1 = flag ? i1 : o3;
+
+    @UnderInitialization(Object.class) Object l2 = flag ? o1 : i1;
+    @UnderInitialization(Object.class) Object l3 = flag ? o1 : o3;
+
+    // :: error: (assignment)
+    @UnderInitialization(Character.class) Object l4 = flag ? o1 : i1;
+  }
+}
diff --git a/checker/tests/initialization/Issue813.java b/checker/tests/initialization/Issue813.java
new file mode 100644
index 0000000..fc8187d
--- /dev/null
+++ b/checker/tests/initialization/Issue813.java
@@ -0,0 +1,26 @@
+// Test case for Issue 813
+// https://github.com/typetools/checker-framework/issues/813
+// @skip-test
+
+import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+
+public class Issue813 {
+  static interface MyInterface {}
+
+  static class MyClass {
+    MyClass(@UnderInitialization MyInterface stuff) {}
+  }
+
+  static class Fails implements MyInterface {
+    @NotOnlyInitialized MyClass bar = new MyClass(this);
+  }
+
+  static class Works implements MyInterface {
+    @NotOnlyInitialized MyClass bar;
+
+    {
+      bar = new MyClass(this); // works
+    }
+  }
+}
diff --git a/checker/tests/initialization/Issue905.java b/checker/tests/initialization/Issue905.java
new file mode 100644
index 0000000..aacafe8
--- /dev/null
+++ b/checker/tests/initialization/Issue905.java
@@ -0,0 +1,28 @@
+// Test case for Issue 905:
+// https://github.com/typetools/checker-framework/issues/905
+
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+
+public class Issue905 {
+  final Object mBar;
+
+  Issue905() {
+    // this should be @UnderInitialization(Object.class), so this call should be forbidden.
+    // :: error: (method.invocation)
+    baz();
+    mBar = "";
+  }
+
+  Issue905(int i) {
+    mBar = "";
+    baz();
+  }
+
+  void baz(@UnknownInitialization(Issue905.class) Issue905 this) {
+    mBar.toString();
+  }
+
+  public static void main(String[] args) {
+    new Issue905();
+  }
+}
diff --git a/checker/tests/initialization/NotOnlyInitializedTest.java b/checker/tests/initialization/NotOnlyInitializedTest.java
new file mode 100644
index 0000000..9ac57f7
--- /dev/null
+++ b/checker/tests/initialization/NotOnlyInitializedTest.java
@@ -0,0 +1,34 @@
+import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+
+public class NotOnlyInitializedTest {
+
+  @NotOnlyInitialized NotOnlyInitializedTest f;
+  NotOnlyInitializedTest g;
+
+  public NotOnlyInitializedTest() {
+    f = new NotOnlyInitializedTest();
+    g = new NotOnlyInitializedTest();
+  }
+
+  public NotOnlyInitializedTest(char i) {
+    // we can store something that is under initialization (like this) in f, but not in g
+    f = this;
+    // :: error: (assignment)
+    g = this;
+  }
+
+  static void testDeref(NotOnlyInitializedTest o) {
+    // o is fully iniatlized, so we can dereference its fields
+    o.f.toString();
+    o.g.toString();
+  }
+
+  static void testDeref2(@UnderInitialization NotOnlyInitializedTest o) {
+    // o is not fully iniatlized, so we cannot dereference its fields
+    // :: error: (dereference.of.nullable)
+    o.f.toString();
+    // :: error: (dereference.of.nullable)
+    o.g.toString();
+  }
+}
diff --git a/checker/tests/initialization/ReceiverSuperInvocation.java b/checker/tests/initialization/ReceiverSuperInvocation.java
new file mode 100644
index 0000000..d744d47
--- /dev/null
+++ b/checker/tests/initialization/ReceiverSuperInvocation.java
@@ -0,0 +1,16 @@
+// Test case for issue 2263
+// https://github.com/typetools/checker-framework/issues/2263
+
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+
+public class ReceiverSuperInvocation {
+  void foo(@UnderInitialization(ReceiverSuperInvocation.class) ReceiverSuperInvocation this) {}
+}
+
+class ReceiverSuperInvocationSubclass extends ReceiverSuperInvocation {
+  @Override
+  void foo(@UnderInitialization(Object.class) ReceiverSuperInvocationSubclass this) {
+    // :: error: (method.invocation)
+    super.foo();
+  }
+}
diff --git a/checker/tests/initialization/SimpleFbc.java b/checker/tests/initialization/SimpleFbc.java
new file mode 100644
index 0000000..eca5860
--- /dev/null
+++ b/checker/tests/initialization/SimpleFbc.java
@@ -0,0 +1,57 @@
+import org.checkerframework.checker.initialization.qual.Initialized;
+import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class SimpleFbc {
+
+  SimpleFbc f;
+  @NotOnlyInitialized SimpleFbc g;
+
+  @Pure
+  int pure() {
+    return 1;
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public SimpleFbc(String arg) {}
+
+  void test() {
+    @NonNull String s = "234";
+
+    // :: error: (assignment)
+    s = null;
+    System.out.println(s);
+  }
+
+  void test2(@UnknownInitialization @NonNull SimpleFbc t) {
+    // :: error: (assignment)
+    @NonNull SimpleFbc a = t.f;
+  }
+
+  // check initialized-only semantics for fields
+  void test3(@UnknownInitialization @NonNull SimpleFbc t) {
+    @Initialized @Nullable SimpleFbc a = t.f;
+
+    // :: error: (assignment)
+    @Initialized @Nullable SimpleFbc b = t.g;
+  }
+
+  void simplestTestEver() {
+    @NonNull String a = "abc";
+
+    // :: error: (assignment)
+    a = null;
+
+    // :: error: (assignment)
+    @NonNull String b = null;
+  }
+
+  void anotherMethod() {
+    @Nullable String s = null;
+
+    @Initialized @Nullable String t = s;
+  }
+}
diff --git a/checker/tests/initialization/Subtyping.java b/checker/tests/initialization/Subtyping.java
new file mode 100644
index 0000000..9158f60
--- /dev/null
+++ b/checker/tests/initialization/Subtyping.java
@@ -0,0 +1,51 @@
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+
+public class Subtyping {
+  void test1(
+      @UnknownInitialization(Object.class) Object unknownObject,
+      @UnderInitialization(Object.class) Object underObject,
+      @UnknownInitialization(Subtyping.class) Object unknownSubtyping,
+      @UnderInitialization(Subtyping.class) Object underSubtyping) {
+    // ::error: (assignment)
+    underObject = unknownObject;
+    underObject = underSubtyping;
+    // ::error: (assignment)
+    underObject = unknownSubtyping;
+  }
+
+  void test2(
+      @UnknownInitialization(Object.class) Object unknownObject,
+      @UnderInitialization(Object.class) Object underObject,
+      @UnknownInitialization(Subtyping.class) Object unknownSubtyping,
+      @UnderInitialization(Subtyping.class) Object underSubtyping) {
+    unknownObject = underSubtyping;
+    unknownObject = unknownSubtyping;
+    unknownObject = underObject;
+  }
+
+  void test3(
+      @UnknownInitialization(Object.class) Object unknownObject,
+      @UnderInitialization(Object.class) Object underObject,
+      @UnknownInitialization(Subtyping.class) Object unknownSubtyping,
+      @UnderInitialization(Subtyping.class) Object underSubtyping) {
+    // ::error: (assignment)
+    underSubtyping = unknownObject;
+    // ::error: (assignment)
+    underSubtyping = unknownSubtyping;
+    // ::error: (assignment)
+    underSubtyping = underObject;
+  }
+
+  void test4(
+      @UnknownInitialization(Object.class) Object unknownObject,
+      @UnderInitialization(Object.class) Object underObject,
+      @UnknownInitialization(Subtyping.class) Object unknownSubtyping,
+      @UnderInitialization(Subtyping.class) Object underSubtyping) {
+    // ::error: (assignment)
+    unknownSubtyping = unknownObject;
+    unknownSubtyping = underSubtyping;
+    // ::error: (assignment)
+    unknownSubtyping = underObject;
+  }
+}
diff --git a/checker/tests/initialization/Suppression.java b/checker/tests/initialization/Suppression.java
new file mode 100644
index 0000000..01d4a2e
--- /dev/null
+++ b/checker/tests/initialization/Suppression.java
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class Suppression {
+
+  @NonNull Suppression t;
+
+  @SuppressWarnings("initialization.fields.uninitialized")
+  public Suppression(Suppression arg) {}
+
+  @SuppressWarnings({"nullness"})
+  void foo(@UnknownInitialization Suppression arg) {
+    t = arg; // initialization error
+    t = null; // nullness error
+  }
+
+  void test() {
+    @SuppressWarnings("nullness:assignment")
+    @NonNull String s = null;
+  }
+}
diff --git a/checker/tests/initialization/TryFinally.java b/checker/tests/initialization/TryFinally.java
new file mode 100644
index 0000000..cba4ad7
--- /dev/null
+++ b/checker/tests/initialization/TryFinally.java
@@ -0,0 +1,449 @@
+// Test case for issue #293: https://github.com/typetools/checker-framework/issues/293
+// Thanks to Ed Price for the test case.
+
+// Design space:
+//  try
+//    ...
+//  catch: 7 varieties
+//    * absent
+//    * catch Throwable
+//    * catch Exception
+//    for each of the "present" types of catch:
+//     * no action
+//     * call method
+//     * assign string
+//  finally: 2 varieties
+//    * absent
+//    * no action
+// Naming indicates which one; overall 14 tests.
+
+// Empty, to give this file its name.
+public class TryFinally {}
+
+class TestCabsentFabsent {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private final String foo;
+
+  public TestCabsentFabsent() {
+    this.foo = getFoo();
+  }
+}
+
+class TestCabsentFnoaction {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private final String foo;
+
+  public TestCabsentFnoaction() {
+    try {
+      this.foo = getFoo();
+    } finally {
+      // no action in finally clause
+    }
+  }
+}
+
+// Not legal in Java: error: variable foo might not have been initialized
+// class TestCtnoactionFabsent {
+//   static String getFoo() { return "foo"; }
+//   private final String foo;
+//   public TestCtnoactionFabsent() {
+//     try {
+//       this.foo = getFoo();
+//     } catch (Throwable t) {
+//       // no action on exception
+//     }
+//   }
+// }
+
+// Not legal in Java: error: variable foo might not have been initialized
+// class TestCtnoactionFnoaction {
+//   static String getFoo() { return "foo"; }
+//   private final String foo;
+//   public TestCtnoactionFnoaction() {
+//     try {
+//       this.foo = getFoo();
+//     } catch (Throwable t) {
+//       // no action on exception
+//     } finally {
+//       // no action in finally clause
+//     }
+//   }
+// }
+
+// Not legal in Java: error: variable foo might already have been assigned
+// class TestCtmethodFabsent {
+//   static String getFoo() { return "foo"; }
+//   private final String foo;
+//   public TestCtmethodFabsent() {
+//     try {
+//       this.foo = getFoo();
+//     } catch (Throwable t) {
+//       this.foo = getFoo();
+//     }
+//   }
+// }
+
+// Not legal in Java: error: variable foo might already have been assigned
+// class TestCtmethodFnoaction {
+//   static String getFoo() { return "foo"; }
+//   private final String foo;
+//   public TestCtmethodFnoaction() {
+//     try {
+//       this.foo = getFoo();
+//     } catch (Throwable t) {
+//       this.foo = getFoo();
+//     } finally {
+//       // no action in finally clause
+//     }
+//   }
+// }
+
+// Not legal in Java: error: variable foo might already have been assigned
+// class TestCtstringFabsent {
+//   static String getFoo() { return "foo"; }
+//   private final String foo;
+//   public TestCtstringFabsent() {
+//     try {
+//       this.foo = getFoo();
+//     } catch (Throwable t) {
+//       this.foo = "foo";
+//     }
+//   }
+// }
+
+// Not legal in Java: error: variable foo might already have been assigned
+// class TestCtstringFnoaction {
+//   static String getFoo() { return "foo"; }
+//   private final String foo;
+//   public TestCtstringFnoaction() {
+//     try {
+//       this.foo = getFoo();
+//     } catch (Throwable t) {
+//       this.foo = "foo";
+//     } finally {
+//       // no action in finally clause
+//     }
+//   }
+// }
+
+// Not legal in Java: error: variable foo might not have been initialized
+// class TestCenoactionFabsent {
+//   static String getFoo() { return "foo"; }
+//   private final String foo;
+//   public TestCenoactionFabsent() {
+//     try {
+//       this.foo = getFoo();
+//     } catch (Exception t) {
+//       // no action on exception
+//     }
+//   }
+// }
+
+// Not legal in Java: error: variable foo might not have been initialized
+// class TestCenoactionFnoaction {
+//   static String getFoo() { return "foo"; }
+//   private final String foo;
+//   public TestCenoactionFnoaction() {
+//     try {
+//       this.foo = getFoo();
+//     } catch (Exception t) {
+//       // no action on exception
+//     } finally {
+//       // no action in finally clause
+//     }
+//   }
+// }
+
+// Not legal in Java: error: variable foo might already have been assigned
+// class TestCemethodFabsent {
+//   static String getFoo() { return "foo"; }
+//   private final String foo;
+//   public TestCemethodFabsent() {
+//     try {
+//       this.foo = getFoo();
+//     } catch (Exception t) {
+//       this.foo = getFoo();
+//     }
+//   }
+// }
+
+// Not legal in Java: error: variable foo might already have been assigned
+// class TestCemethodFnoaction {
+//   static String getFoo() { return "foo"; }
+//   private final String foo;
+//   public TestCemethodFnoaction() {
+//     try {
+//       this.foo = getFoo();
+//     } catch (Exception t) {
+//       this.foo = getFoo();
+//     } finally {
+//       // no action in finally clause
+//     }
+//   }
+// }
+
+// Not legal in Java: error: variable foo might already have been assigned
+// class TestCestringFabsent {
+//   static String getFoo() { return "foo"; }
+//   private final String foo;
+//   public TestCestringFabsent() {
+//     try {
+//       this.foo = getFoo();
+//     } catch (Exception t) {
+//       this.foo = "foo";
+//     }
+//   }
+// }
+
+// Not legal in Java: error: variable foo might already have been assigned
+// class TestCestringFnoaction {
+//   static String getFoo() { return "foo"; }
+//   private final String foo;
+//   public TestCestringFnoaction() {
+//     try {
+//       this.foo = getFoo();
+//     } catch (Exception t) {
+//       this.foo = "foo";
+//     } finally {
+//       // no action in finally clause
+//     }
+//   }
+// }
+
+class TestCabsentFabsentNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+
+  public TestCabsentFabsentNonfinal() {
+    this.foo = getFoo();
+  }
+}
+
+class TestCabsentFnoactionNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+
+  public TestCabsentFnoactionNonfinal() {
+    try {
+      this.foo = getFoo();
+    } finally {
+      // no action in finally clause
+    }
+  }
+}
+
+class TestCtnoactionFabsentNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+  // :: error: initialization.fields.uninitialized
+  public TestCtnoactionFabsentNonfinal() {
+    try {
+      this.foo = getFoo();
+    } catch (Throwable t) {
+      // no action on exception
+    }
+  }
+}
+
+class TestCtnoactionFnoactionNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+  // :: error: initialization.fields.uninitialized
+  public TestCtnoactionFnoactionNonfinal() {
+    try {
+      this.foo = getFoo();
+    } catch (Throwable t) {
+      // no action on exception
+    } finally {
+      // no action in finally clause
+    }
+  }
+}
+
+class TestCtmethodFabsentNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+
+  public TestCtmethodFabsentNonfinal() {
+    try {
+      this.foo = getFoo();
+    } catch (Throwable t) {
+      this.foo = getFoo();
+    }
+  }
+}
+
+class TestCtmethodFnoactionNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+
+  public TestCtmethodFnoactionNonfinal() {
+    try {
+      this.foo = getFoo();
+    } catch (Throwable t) {
+      this.foo = getFoo();
+    } finally {
+      // no action in finally clause
+    }
+  }
+}
+
+class TestCtstringFabsentNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+
+  public TestCtstringFabsentNonfinal() {
+    try {
+      this.foo = getFoo();
+    } catch (Throwable t) {
+      this.foo = "foo";
+    }
+  }
+}
+
+class TestCtstringFnoactionNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+
+  public TestCtstringFnoactionNonfinal() {
+    try {
+      this.foo = getFoo();
+    } catch (Throwable t) {
+      this.foo = "foo";
+    } finally {
+      // no action in finally clause
+    }
+  }
+}
+
+class TestCenoactionFabsentNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+  // :: error: initialization.fields.uninitialized
+  public TestCenoactionFabsentNonfinal() {
+    try {
+      this.foo = getFoo();
+    } catch (Exception t) {
+      // no action on exception
+    }
+  }
+}
+
+class TestCenoactionFnoactionNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+  // :: error: initialization.fields.uninitialized
+  public TestCenoactionFnoactionNonfinal() {
+    try {
+      this.foo = getFoo();
+    } catch (Exception t) {
+      // no action on exception
+    } finally {
+      // no action in finally clause
+    }
+  }
+}
+
+class TestCemethodFabsentNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+
+  public TestCemethodFabsentNonfinal() {
+    try {
+      this.foo = getFoo();
+    } catch (Exception t) {
+      this.foo = getFoo();
+    }
+  }
+}
+
+class TestCemethodFnoactionNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+
+  public TestCemethodFnoactionNonfinal() {
+    try {
+      this.foo = getFoo();
+    } catch (Exception t) {
+      this.foo = getFoo();
+    } finally {
+      // no action in finally clause
+    }
+  }
+}
+
+class TestCestringFabsentNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+
+  public TestCestringFabsentNonfinal() {
+    try {
+      this.foo = getFoo();
+    } catch (Exception t) {
+      this.foo = "foo";
+    }
+  }
+}
+
+class TestCestringFnoactionNonfinal {
+  static String getFoo() {
+    return "foo";
+  }
+
+  private String foo;
+
+  public TestCestringFnoactionNonfinal() {
+    try {
+      this.foo = getFoo();
+    } catch (Exception t) {
+      this.foo = "foo";
+    } finally {
+      // no action in finally clause
+    }
+  }
+}
diff --git a/checker/tests/initialization/TryFinally2.java b/checker/tests/initialization/TryFinally2.java
new file mode 100644
index 0000000..dcc1a83
--- /dev/null
+++ b/checker/tests/initialization/TryFinally2.java
@@ -0,0 +1,30 @@
+// Test case for Issue 1500:
+// https://github.com/typetools/checker-framework/issues/1500
+
+import java.io.InputStream;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class TryFinally2 {
+
+  @SuppressWarnings("nullness") // dummy implementation
+  Process getProcess() {
+    return null;
+  }
+
+  void performCommand() {
+    Process proc = null;
+    InputStream in = null;
+    try {
+      proc = getProcess();
+      in = proc.getInputStream();
+      return;
+    } finally {
+      closeQuietly(in);
+      if (proc != null) {
+        proc.destroy();
+      }
+    }
+  }
+
+  public static void closeQuietly(final @Nullable InputStream input) {}
+}
diff --git a/checker/tests/initialization/TryFinallyBreak.java b/checker/tests/initialization/TryFinallyBreak.java
new file mode 100644
index 0000000..a348f9c
--- /dev/null
+++ b/checker/tests/initialization/TryFinallyBreak.java
@@ -0,0 +1,572 @@
+// Test case for Issue 548:
+// https://github.com/typetools/checker-framework/issues/548
+
+public class TryFinallyBreak {
+  String testWhile1() {
+    String ans = "x";
+    while (this.hashCode() > 10000) {
+      try {
+        // empty body
+      } finally {
+        ans = null;
+      }
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testWhile2() {
+    String ans = "x";
+    while (true) {
+      try {
+        // Note the additional break;
+        break;
+      } finally {
+        ans = null;
+      }
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testWhile3() {
+    String ans = "x";
+    while (true) {
+      try {
+        testWhile3();
+      } catch (Exception e) {
+        break;
+      } finally {
+        ans = null;
+      }
+      ans = "x";
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testWhile4() {
+    String ans = "x";
+    while (true) {
+      if (true) {
+        try {
+          break;
+        } finally {
+          ans = null;
+          break;
+        }
+      }
+      ans = "x";
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testWhile5() {
+    String ans = "x";
+    while (true) {
+      while (true) {
+        try {
+          // Note the additional break;
+          break;
+        } finally {
+          ans = null;
+        }
+      }
+      ans = "x";
+      break;
+    }
+    return ans;
+  }
+
+  String testWhile6(boolean cond) {
+    String ans = "x";
+    OUTER:
+    while (cond) {
+      while (cond) {
+        try {
+          if (cond) {
+            break OUTER;
+          }
+        } finally {
+          ans = null;
+        }
+      }
+      ans = "x";
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testWhile7(boolean cond) {
+    String ans = "x";
+    OUTER:
+    while (cond) {
+      try {
+        while (cond) {
+          try {
+            if (cond) {
+              break OUTER;
+            }
+          } finally {
+            ans = null;
+          }
+        }
+      } finally {
+        ans = "x";
+      }
+    }
+    return ans;
+  }
+
+  String testWhile8(boolean cond) {
+    String ans = "x";
+    OUTER:
+    while (cond) {
+      try {
+        while (cond) {
+          try {
+            if (cond) {
+              break OUTER;
+            }
+          } finally {
+            ans = "x";
+          }
+        }
+      } finally {
+        ans = null;
+      }
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testDoWhile1() {
+    String ans = "x";
+    do {
+      try {
+        // empty body
+      } finally {
+        ans = null;
+      }
+    } while (this.hashCode() > 10000);
+    // :: error: (return)
+    return ans;
+  }
+
+  String testDoWhile2() {
+    String ans = "x";
+    do {
+      try {
+        // Note the additional break;
+        break;
+      } finally {
+        ans = null;
+      }
+    } while (true);
+    // :: error: (return)
+    return ans;
+  }
+
+  String testDoWhile3() {
+    String ans = "x";
+    do {
+      try {
+        testWhile3();
+      } catch (Exception e) {
+        break;
+      } finally {
+        ans = null;
+      }
+      ans = "x";
+    } while (true);
+    // :: error: (return)
+    return ans;
+  }
+
+  String testDoWhile4() {
+    String ans = "x";
+    do {
+      if (true) {
+        try {
+          break;
+        } finally {
+          ans = null;
+          break;
+        }
+      }
+      ans = "x";
+    } while (true);
+    // :: error: (return)
+    return ans;
+  }
+
+  String testDoWhile5() {
+    String ans = "x";
+    do {
+      do {
+        try {
+          // Note the additional break;
+          break;
+        } finally {
+          ans = null;
+        }
+      } while (true);
+      ans = "x";
+      break;
+    } while (true);
+    return ans;
+  }
+
+  String testDoWhile6(boolean cond) {
+    String ans = "x";
+    OUTER:
+    do {
+      do {
+        try {
+          if (cond) {
+            break OUTER;
+          }
+        } finally {
+          ans = null;
+        }
+      } while (cond);
+      ans = "x";
+    } while (cond);
+    // :: error: (return)
+    return ans;
+  }
+
+  String testDoWhile7(boolean cond) {
+    String ans = "x";
+    OUTER:
+    do {
+      try {
+        do {
+          try {
+            if (cond) {
+              break OUTER;
+            }
+          } finally {
+            ans = null;
+          }
+        } while (cond);
+      } finally {
+        ans = "x";
+      }
+    } while (cond);
+    return ans;
+  }
+
+  String testDoWhile8(boolean cond) {
+    String ans = "x";
+    OUTER:
+    do {
+      try {
+        do {
+          try {
+            if (cond) {
+              break OUTER;
+            }
+          } finally {
+            ans = "x";
+          }
+        } while (cond);
+      } finally {
+        ans = null;
+      }
+    } while (cond);
+    // :: error: (return)
+    return ans;
+  }
+
+  String testFor1() {
+    String ans = "x";
+    for (; this.hashCode() > 10000; ) {
+      try {
+        // empty body
+      } finally {
+        ans = null;
+      }
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testFor2() {
+    String ans = "x";
+    for (; ; ) {
+      try {
+        // Note the additional break;
+        break;
+      } finally {
+        ans = null;
+      }
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testFor3() {
+    String ans = "x";
+    for (; ; ) {
+      try {
+        testFor3();
+      } catch (Exception e) {
+        break;
+      } finally {
+        ans = null;
+      }
+      ans = "x";
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testFor4() {
+    String ans = "x";
+    for (; ; ) {
+      if (true) {
+        try {
+          break;
+        } finally {
+          ans = null;
+          break;
+        }
+      }
+      ans = "x";
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testFor5() {
+    String ans = "x";
+    for (; ; ) {
+      for (; ; ) {
+        try {
+          // Note the additional break;
+          break;
+        } finally {
+          ans = null;
+        }
+      }
+      ans = "x";
+      break;
+    }
+    return ans;
+  }
+
+  String testFor6(boolean cond) {
+    String ans = "x";
+    OUTER:
+    for (; ; ) {
+      for (; cond; ) {
+        try {
+          if (cond) {
+            break OUTER;
+          }
+        } finally {
+          ans = null;
+        }
+      }
+      ans = "x";
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testFor7(boolean cond) {
+    String ans = "x";
+    OUTER:
+    for (; ; ) {
+      try {
+        for (; ; ) {
+          try {
+            if (cond) {
+              break OUTER;
+            }
+          } finally {
+            ans = null;
+          }
+        }
+      } finally {
+        ans = "x";
+      }
+    }
+    return ans;
+  }
+
+  String testFor8(boolean cond) {
+    String ans = "x";
+    OUTER:
+    for (; ; ) {
+      try {
+        for (; ; ) {
+          try {
+            if (cond) {
+              break OUTER;
+            }
+          } finally {
+            ans = "x";
+          }
+        }
+      } finally {
+        ans = null;
+      }
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testIf1() {
+    String ans = "x";
+    IF:
+    if (true) {
+      try {
+        break IF;
+      } finally {
+        ans = null;
+      }
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testIf2(boolean cond) {
+    String ans = "x";
+    IF:
+    if (cond) {
+      if (cond) {
+        try {
+          if (cond) {
+            break IF;
+          }
+        } finally {
+          ans = null;
+        }
+      }
+      ans = "x";
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testIf3(boolean cond) {
+    String ans = "x";
+    IF:
+    if (cond) {
+      try {
+        if (cond) {
+          try {
+            if (cond) {
+              break IF;
+            }
+          } finally {
+            ans = null;
+          }
+        }
+      } finally {
+        ans = "x";
+      }
+    }
+    return ans;
+  }
+
+  String testIf4(boolean cond) {
+    String ans = "x";
+    IF:
+    if (cond) {
+      try {
+        if (cond) {
+          try {
+            if (cond) {
+              break IF;
+            }
+          } finally {
+            ans = "x";
+          }
+        }
+      } finally {
+        ans = null;
+      }
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testSwitch1() {
+    String ans = "x";
+    switch (ans) {
+      case "x":
+        try {
+          break;
+        } finally {
+          ans = null;
+        }
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testSwitch2(boolean cond) {
+    String ans = "x";
+    SWITCH:
+    switch (ans) {
+      case "x":
+        switch (ans) {
+          case "x":
+            try {
+              break SWITCH;
+            } finally {
+              ans = null;
+            }
+        }
+    }
+    // :: error: (return)
+    return ans;
+  }
+
+  String testSwitch3(boolean cond) {
+    String ans = "x";
+    SWITCH:
+    switch (ans) {
+      case "x":
+        try {
+          switch (ans) {
+            case "x":
+              try {
+                break SWITCH;
+              } finally {
+                ans = null;
+              }
+          }
+        } finally {
+          ans = "x";
+        }
+    }
+    return ans;
+  }
+
+  String testSwitch4(boolean cond) {
+    String ans = "x";
+    SWITCH:
+    switch (ans) {
+      case "x":
+        try {
+          switch (ans) {
+            case "x":
+              try {
+                break SWITCH;
+              } finally {
+                ans = "x";
+              }
+          }
+        } finally {
+          ans = null;
+        }
+    }
+    // :: error: (return)
+    return ans;
+  }
+}
diff --git a/checker/tests/initialization/TryFinallyContinue.java b/checker/tests/initialization/TryFinallyContinue.java
new file mode 100644
index 0000000..aecf8ce
--- /dev/null
+++ b/checker/tests/initialization/TryFinallyContinue.java
@@ -0,0 +1,124 @@
+// Test case for Issue 548:
+// https://github.com/typetools/checker-framework/issues/548
+
+public class TryFinallyContinue {
+  String testWhile1() {
+    String ans = "x";
+    while (true) {
+      if (true) {
+        // :: error: (return)
+        return ans;
+      }
+      if (true) {
+        try {
+          continue;
+        } finally {
+          ans = null;
+        }
+      }
+      ans = "x";
+    }
+  }
+
+  String testWhile2(boolean cond) {
+    String ans = "x";
+    while (cond) {
+      if (true) {
+        return ans;
+      }
+      try {
+        ans = null;
+        continue;
+      } finally {
+        ans = "x";
+      }
+    }
+    return ans;
+  }
+
+  String testWhile3(boolean cond) {
+    String ans = "x";
+    OUTER:
+    while (true) {
+      if (true) {
+        // :: error: (return)
+        return ans;
+      }
+
+      try {
+        while (cond) {
+          if (true) {
+            try {
+              continue OUTER;
+            } finally {
+              ans = "x";
+            }
+          }
+        }
+      } finally {
+        ans = null;
+      }
+      ans = "x";
+    }
+  }
+
+  String testFor1() {
+    String ans = "x";
+    for (; ; ) {
+      if (true) {
+        // :: error: (return)
+        return ans;
+      }
+      if (true) {
+        try {
+          continue;
+        } finally {
+          ans = null;
+        }
+      }
+      ans = "x";
+    }
+  }
+
+  String testFor2(boolean cond) {
+    String ans = "x";
+    for (; cond; ) {
+      if (true) {
+        return ans;
+      }
+      try {
+        ans = null;
+        continue;
+      } finally {
+        ans = "x";
+      }
+    }
+    return ans;
+  }
+
+  String testFor3(boolean cond) {
+    String ans = "x";
+    OUTER:
+    for (; ; ) {
+      if (true) {
+        // :: error: (return)
+        return ans;
+      }
+
+      try {
+        for (; cond; ) {
+          if (true) {
+            try {
+              continue OUTER;
+            } finally {
+              ans = "x";
+            }
+          }
+        }
+      } finally {
+        ans = null;
+      }
+      ans = "x";
+    }
+  }
+}
diff --git a/checker/tests/initialization/TypeFrames.java b/checker/tests/initialization/TypeFrames.java
new file mode 100644
index 0000000..acadf9c
--- /dev/null
+++ b/checker/tests/initialization/TypeFrames.java
@@ -0,0 +1,40 @@
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class TypeFrames {
+
+  class A {
+    @NonNull String a;
+
+    public A() {
+      @UnderInitialization A l1 = this;
+      // :: error: (assignment)
+      @UnderInitialization(A.class) A l2 = this;
+      a = "";
+      @UnderInitialization(A.class) A l3 = this;
+    }
+  }
+
+  interface I {}
+
+  class B extends A implements I {
+    @NonNull String b;
+
+    public B() {
+      super();
+      @UnderInitialization(A.class) A l1 = this;
+      // :: error: (assignment)
+      @UnderInitialization(B.class) A l2 = this;
+      b = "";
+      @UnderInitialization(B.class) A l3 = this;
+    }
+  }
+
+  // subtyping
+  void t1(@UnderInitialization(A.class) B b1, @UnderInitialization(B.class) B b2) {
+    @UnderInitialization(A.class) B l1 = b1;
+    @UnderInitialization(A.class) B l2 = b2;
+    // :: error: (assignment)
+    @UnderInitialization(B.class) B l3 = b1;
+  }
+}
diff --git a/checker/tests/initialization/TypeFrames2.java b/checker/tests/initialization/TypeFrames2.java
new file mode 100644
index 0000000..148905e
--- /dev/null
+++ b/checker/tests/initialization/TypeFrames2.java
@@ -0,0 +1,33 @@
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class TypeFrames2 {
+
+  class A {
+    @NonNull String a;
+
+    public A() {
+      // :: error: (method.invocation)
+      this.foo();
+      a = "";
+      this.foo();
+    }
+
+    public void foo(@UnderInitialization(A.class) A this) {}
+  }
+
+  class B extends A {
+    @NonNull String b;
+
+    public B() {
+      super();
+      this.foo();
+      // :: error: (method.invocation)
+      this.bar();
+      b = "";
+      this.bar();
+    }
+
+    public void bar(@UnderInitialization(B.class) B this) {}
+  }
+}
diff --git a/checker/tests/interning/ArrayInitializers.java b/checker/tests/interning/ArrayInitializers.java
new file mode 100644
index 0000000..d2cf14d
--- /dev/null
+++ b/checker/tests/interning/ArrayInitializers.java
@@ -0,0 +1,8 @@
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class ArrayInitializers {
+  public static final String STATIC_FIELD = "m";
+  public static final @Interned String OTHER_FIELD = "n";
+
+  public static final @Interned String[] STATIC_ARRAY = {STATIC_FIELD, OTHER_FIELD};
+}
diff --git a/checker/tests/interning/Arrays.java b/checker/tests/interning/Arrays.java
new file mode 100644
index 0000000..2f7631a
--- /dev/null
+++ b/checker/tests/interning/Arrays.java
@@ -0,0 +1,76 @@
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.checker.interning.qual.PolyInterned;
+
+public class Arrays {
+
+  public static Integer[] arrayclone_simple(Integer[] a_old) {
+    int len = a_old.length;
+    Integer[] a_new = new Integer[len];
+    for (int i = 0; i < len; i++) {
+      a_new[i] = Integer.valueOf(a_old[i]); // valid
+    }
+    return a_new;
+  }
+
+  public static void test(@Interned Integer i, @Interned String s) {
+    String @Interned [] iarray1 = new String @Interned [2];
+    String @Interned [] iarray2 = new String @Interned [] {"foo", "bar"};
+    // :: error: (assignment)
+    s = iarray1[1]; // error
+
+    String[] sa = new String[22];
+    // :: error: (assignment)
+    iarray1 = sa; // error
+    sa = iarray1; // OK
+
+    @Interned String[] istrings1 = new @Interned String[2];
+    @Interned String[] istrings2 = new @Interned String[] {"foo", "bar"};
+    s = istrings1[1]; // OK
+
+    @Interned String @Interned [][] multi1 = new @Interned String @Interned [2][3];
+    @Interned String @Interned [][] multi2 = new @Interned String @Interned [2][];
+  }
+
+  public final @Interned class InternedClass {}
+
+  private static InternedClass[] returnToArray() {
+    List<InternedClass> li = new ArrayList<>();
+    return li.toArray(new InternedClass[li.size()]);
+  }
+
+  private static void sortIt() {
+    java.util.Arrays.sort(new InternedClass[22]);
+  }
+
+  private @Interned String[] elts_String;
+
+  public @Interned String min_elt() {
+    return elts_String[0];
+  }
+
+  private double @Interned [] @Interned [] elts_da;
+
+  public void add_mod_elem(double @Interned [] v, int count) {
+    elts_da[0] = v;
+  }
+
+  public static @PolyInterned Object[] subarray(
+      @PolyInterned Object[] a, int startindex, int length) {
+    @PolyInterned Object[] result = new @PolyInterned Object[length];
+    System.arraycopy(a, startindex, result, 0, length);
+    return result;
+  }
+
+  public static void trim(int len) {
+    @Interned Object @Interned [] vals = null;
+    @Interned Object[] new_vals = subarray(vals, 0, len);
+  }
+
+  public static @Interned Object @Interned [] internSubsequence(
+      @Interned Object @Interned [] seq, int start, int end) {
+    @Interned Object[] subseq_uninterned = subarray(seq, start, end - start);
+    return null;
+  }
+}
diff --git a/checker/tests/interning/ArraysMDETest.java b/checker/tests/interning/ArraysMDETest.java
new file mode 100644
index 0000000..1549538
--- /dev/null
+++ b/checker/tests/interning/ArraysMDETest.java
@@ -0,0 +1,13 @@
+// @skip-test
+
+import org.checkerframework.checker.interning.qual.PolyInterned;
+
+public final class ArraysMDETest {
+
+  public static @PolyInterned Object[] subarray(
+      @PolyInterned Object[] a, int startindex, int length) {
+    @PolyInterned Object[] result = new @PolyInterned Object[length];
+    System.arraycopy(a, startindex, result, 0, length);
+    return result;
+  }
+}
diff --git a/checker/tests/interning/Autoboxing.java b/checker/tests/interning/Autoboxing.java
new file mode 100644
index 0000000..b1c2c74
--- /dev/null
+++ b/checker/tests/interning/Autoboxing.java
@@ -0,0 +1,170 @@
+public class Autoboxing {
+  Byte b;
+  Short s;
+  Short sInterned;
+  Integer i;
+  Integer iInterned;
+  Long l;
+  Float f;
+  Double d;
+  Boolean z;
+  Character c;
+  Character cInterned;
+
+  Autoboxing() {
+    b = -126;
+    s = 32000;
+    sInterned = 32;
+    i = 1234567;
+    iInterned = 123;
+    l = 1234567L;
+    f = 3.14f;
+    d = 3.14;
+    z = true;
+    c = 65000;
+    cInterned = 65;
+  }
+
+  public static void main(String[] args) {
+    new Autoboxing().test();
+  }
+
+  public void test() {
+    System.out.println();
+    System.out.println("Byte");
+    Byte b1 = -126;
+    Byte b2 = -126;
+    Byte b3 = Byte.valueOf((byte) -126);
+    System.out.println(b1 == b2);
+    // :: warning: (unnecessary.equals)
+    System.out.println(b1.equals(b2));
+    // :: warning: (unnecessary.equals)
+    System.out.println(b3.equals(b2));
+    System.out.println(b.equals(b2));
+    System.out.println(b == -126);
+    // :: warning: (unnecessary.equals)
+    System.out.println(b1.equals(126));
+
+    System.out.println();
+    System.out.println("Short");
+    Short s1 = 32000;
+    Short s2 = 32000;
+    Short s3 = Short.valueOf((short) 32000);
+    // :: error: (not.interned)
+    System.out.println(s1 == s2);
+    System.out.println(s1.equals(s2));
+    System.out.println(s3.equals(s2));
+    System.out.println(s.equals(s2));
+    // TODO
+    //     Short s1interned = 32;
+    //     Short s2interned = 32;
+    //     Short s3interned = Short.valueOf((short) 32);
+    //     System.out.println(s1interned==s2interned);
+    //     // :: warning: (unnecessary.equals)
+    //     System.out.println(s1interned.equals(s2interned));
+    //     // :: warning: (unnecessary.equals)
+    //     System.out.println(s3interned.equals(s2interned));
+    //     // :: warning: (unnecessary.equals)
+    //     System.out.println(sInterned.equals(s2interned));
+
+    System.out.println();
+    System.out.println("Integer");
+    Integer i1 = 1234567;
+    Integer i2 = 1234567;
+    Integer i3 = Integer.valueOf(1234567);
+    // :: error: (not.interned)
+    System.out.println(i1 == i2);
+    System.out.println(i1.equals(i2));
+    System.out.println(i3.equals(i2));
+    System.out.println(i.equals(i2));
+
+    System.out.println();
+    Integer i1interned = 123;
+    Integer i2interned = 123;
+    Integer i3interned = Integer.valueOf(123);
+    // TODO:
+    // Would be legal to use ==, but Interning Checker does not check the
+    // actual int value when deciding whether to warn for unnecessary.equals.
+    //     // :: warning: (unnecessary.equals)
+    //     System.out.println(i1interned==i2interned);
+    //     // :: warning: (unnecessary.equals)
+    //     System.out.println(i1interned.equals(i2interned));
+    //     // :: warning: (unnecessary.equals)
+    //     System.out.println(i3interned.equals(i2interned));
+    //     // :: warning: (unnecessary.equals)
+    //     System.out.println(iInterned.equals(i2interned));
+    //     System.out.println(i1interned==123); // ok
+    //     // :: warning: (unnecessary.equals)
+    //     System.out.println(i1interned.equals(123));
+
+    System.out.println();
+    System.out.println("Long");
+    Long l1 = 1234567L;
+    Long l2 = 1234567L;
+    Long l3 = Long.valueOf(1234567L);
+    // :: error: (not.interned)
+    System.out.println(l1 == l2);
+    System.out.println(l1.equals(l2));
+    System.out.println(l3.equals(l2));
+    System.out.println(l.equals(l2));
+
+    System.out.println();
+    System.out.println("Float");
+    Float f1 = 3.14f;
+    Float f2 = 3.14f;
+    Float f3 = Float.valueOf(3.14f);
+    // :: error: (not.interned)
+    System.out.println(f1 == f2);
+    System.out.println(f1.equals(f2));
+    System.out.println(f3.equals(f2));
+    System.out.println(f.equals(f2));
+
+    System.out.println();
+    System.out.println("Double");
+    Double d1 = 3.14;
+    Double d2 = 3.14;
+    Double d3 = Double.valueOf(3.14);
+    // :: error: (not.interned)
+    System.out.println(d1 == d2);
+    System.out.println(d1.equals(d2));
+    System.out.println(d3.equals(d2));
+    System.out.println(d.equals(d2));
+
+    System.out.println();
+    System.out.println("Boolean");
+    Boolean z1 = true;
+    Boolean z2 = true;
+    Boolean z3 = Boolean.valueOf(true);
+    System.out.println(z1 == z2);
+    // :: warning: (unnecessary.equals)
+    System.out.println(z1.equals(z2));
+    // :: warning: (unnecessary.equals)
+    System.out.println(z3.equals(z2));
+    System.out.println(z.equals(z2));
+    System.out.println(z1 == true); // ok
+    // :: warning: (unnecessary.equals)
+    System.out.println(z1.equals(true));
+
+    System.out.println();
+    System.out.println("Character");
+    Character c1 = 65000;
+    Character c2 = 65000;
+    Character c3 = Character.valueOf((char) 65000);
+    // :: error: (not.interned)
+    System.out.println(c1 == c2);
+    System.out.println(c1.equals(c2));
+    System.out.println(c3.equals(c2));
+    System.out.println(c.equals(c2));
+    // TODO
+    //     Character c1interned = 65;
+    //     Character c2interned = 65;
+    //     Character c3interned = Character.valueOf((char) 65);
+    //     System.out.println(c1interned==c2interned);
+    //     // :: warning: (unnecessary.equals)
+    //     System.out.println(c1interned.equals(c2interned));
+    //     // :: warning: (unnecessary.equals)
+    //     System.out.println(c3interned.equals(c2interned));
+    //     // :: warning: (unnecessary.equals)
+    //     System.out.println(cInterned.equals(c2interned));
+  }
+}
diff --git a/checker/tests/interning/BoxingInterning.java b/checker/tests/interning/BoxingInterning.java
new file mode 100644
index 0000000..0f701ef
--- /dev/null
+++ b/checker/tests/interning/BoxingInterning.java
@@ -0,0 +1,79 @@
+// @skip-test until these issues are fixed:
+// https://github.com/typetools/checker-framework/issues/84
+// https://github.com/typetools/checker-framework/issues/766
+
+import org.checkerframework.checker.interning.qual.Interned;
+
+// Per JLS 5.1.7:
+//  * autoboxed Characters in the range '\u0000' to '\u007f' are interned
+//  * autoboxed Booleans are interned
+//  * autoboxed integral types (Byte, Stort, Integer, Long) in the range -128..127 inclusive are
+// interned
+
+public class BoxingInterning {
+
+  void needsInterned(@Interned Object arg) {}
+
+  void method() {
+
+    boolean aprimitive = true;
+    needsInterned(aprimitive);
+    @Interned Boolean aboxed = aprimitive;
+
+    byte bprimitive = 5;
+    needsInterned(bprimitive);
+    @Interned Byte bboxed = bprimitive;
+
+    char cprimitive = 'a';
+    needsInterned(cprimitive);
+    @Interned Character c2 = c;
+
+    char cprimitive2 = (char) 0x2202;
+    // :: (argument)
+    needsInterned(cprimitive2);
+    // :: (assignment)
+    @Interned Character cboxed2 = cprimitive2;
+
+    short dprimitive = 5;
+    needsInterned(dprimitive);
+    @Interned Short dboxed = dprimitive;
+
+    short dprimitive2 = 500;
+    // :: (argument)
+    needsInterned(dprimitive2);
+    // :: (assignment)
+    @Interned Short dboxed2 = dprimitive2;
+
+    int eprimitive = 5;
+    needsInterned(eprimitive);
+    @Interned Integer eboxed = eprimitive;
+
+    int eprimitive2 = 500;
+    // :: (argument)
+    needsInterned(eprimitive2);
+    // :: (assignment)
+    @Interned Integer eboxed2 = eprimitive2;
+
+    long fprimitive = 5;
+    needsInterned(fprimitive);
+    @Interned Long fboxed = fboxed;
+
+    long fprimitive2 = 500;
+    // :: (argument)
+    needsInterned(fprimitive2);
+    // :: (assignment)
+    @Interned Long fboxed2 = fboxed2;
+
+    float g = (float) 3.14;
+    // :: (argument)
+    needsInterned(g);
+    // :: (assignment)
+    @Interned Float gboxed = g;
+
+    double h = 3.14;
+    // :: (argument)
+    needsInterned(h);
+    // :: (assignment)
+    @Interned Double hboxed = h;
+  }
+}
diff --git a/checker/tests/interning/Casts.java b/checker/tests/interning/Casts.java
new file mode 100644
index 0000000..2a239ca
--- /dev/null
+++ b/checker/tests/interning/Casts.java
@@ -0,0 +1,5 @@
+public class Casts {
+  void method(Object o) {
+    char c = (char) o;
+  }
+}
diff --git a/checker/tests/interning/ClassDefaults.java b/checker/tests/interning/ClassDefaults.java
new file mode 100644
index 0000000..7ac0683
--- /dev/null
+++ b/checker/tests/interning/ClassDefaults.java
@@ -0,0 +1,27 @@
+import java.util.List;
+import org.checkerframework.checker.interning.qual.Interned;
+
+/*
+ * This test case excercises the interaction between class annotations
+ * and method type argument inference.
+ * A previously existing Unqualified annotation wasn't correctly removed.
+ */
+public class ClassDefaults {
+  @Interned class Test {}
+
+  public static interface Visitor<T> {}
+
+  class GuardingVisitor implements Visitor<List<Test>> {
+    void call() {
+      test(this);
+    }
+  }
+
+  <T> T test(Visitor<T> p) {
+    return null;
+  }
+
+  void call(GuardingVisitor p) {
+    test(p);
+  }
+}
diff --git a/checker/tests/interning/Comparison.java b/checker/tests/interning/Comparison.java
new file mode 100644
index 0000000..cf9d84b
--- /dev/null
+++ b/checker/tests/interning/Comparison.java
@@ -0,0 +1,42 @@
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class Comparison {
+
+  void testInterned() {
+
+    @Interned String a = "foo";
+    @Interned String b = "bar";
+
+    if (a == b) {
+      System.out.println("yes");
+    } else {
+      System.out.println("no");
+    }
+
+    if (a != b) {
+      System.out.println("no");
+    } else {
+      System.out.println("yes");
+    }
+  }
+
+  void testNotInterned() {
+
+    String c = new String("foo");
+    String d = new String("bar");
+
+    // :: error: (not.interned)
+    if (c == d) {
+      System.out.println("yes");
+    } else {
+      System.out.println("no");
+    }
+
+    // :: error: (not.interned)
+    if (c != d) {
+      System.out.println("no");
+    } else {
+      System.out.println("yes");
+    }
+  }
+}
diff --git a/checker/tests/interning/CompileTimeConstants.java b/checker/tests/interning/CompileTimeConstants.java
new file mode 100644
index 0000000..28597dc
--- /dev/null
+++ b/checker/tests/interning/CompileTimeConstants.java
@@ -0,0 +1,20 @@
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class CompileTimeConstants {
+  class A {
+    static final String a1 = "hello";
+    @Interned String a2 = "a2";
+
+    void method() {
+      if (a1 == "hello") {}
+    }
+  }
+
+  class B {
+    static final String b1 = "hello";
+
+    void method() {
+      if (b1 == A.a1) {}
+    }
+  }
+}
diff --git a/checker/tests/interning/CompileTimeConstants2.java b/checker/tests/interning/CompileTimeConstants2.java
new file mode 100644
index 0000000..efa5ae1
--- /dev/null
+++ b/checker/tests/interning/CompileTimeConstants2.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class CompileTimeConstants2 {
+  @Interned String s1 = "" + ("" + 1);
+
+  @Interned String s2 = (("" + ("" + 1)));
+
+  @Interned String s3 = ("" + (("")) + 1);
+
+  @Interned String s4 = "" + Math.PI;
+
+  // To make sure that we would get an error if the RHS is not interned
+  // :: error: (assignment)
+  @Interned String err = "" + new Object();
+}
diff --git a/checker/tests/interning/ComplexComparison.java b/checker/tests/interning/ComplexComparison.java
new file mode 100644
index 0000000..d50c804
--- /dev/null
+++ b/checker/tests/interning/ComplexComparison.java
@@ -0,0 +1,93 @@
+import java.util.Comparator;
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class ComplexComparison {
+
+  void testInterned() {
+
+    @Interned String a = "foo";
+    @Interned String b = "bar";
+
+    if (a != null && b != null && a == b) {
+      System.out.println("yes");
+    } else {
+      System.out.println("no");
+    }
+  }
+
+  void testInternedDueToFlow() {
+
+    String c = "foo";
+    String d = "bar";
+
+    if (c != null && d != null && c == d) {
+      System.out.println("yes");
+    } else {
+      System.out.println("no");
+    }
+  }
+
+  void testNotInterned() {
+
+    String e = new String("foo");
+    String f = new String("bar");
+
+    // :: error: (not.interned)
+    if (e != null && f != null && e == f) {
+      System.out.println("yes");
+    } else {
+      System.out.println("no");
+    }
+  }
+
+  /* @ pure */ public class DoubleArrayComparatorLexical implements Comparator<double[]> {
+
+    /**
+     * Lexically compares o1 and o2 as double arrays.
+     *
+     * @return positive if o1 > 02, 0 if 01 == 02, negative if 01 < 02
+     */
+    public int compare(double[] a1, double[] a2) {
+      // Heuristic: permit "arg1 == arg2" in a test in the first statement
+      // of a "Comparator.compare" method, if the body just returns 0.
+      if (a1 == a2) {
+        return 0;
+      }
+      int len = Math.min(a1.length, a2.length);
+      for (int i = 0; i < len; i++) {
+        if (a1[i] != a2[i]) {
+          return ((a1[i] > a2[i]) ? 1 : -1);
+        }
+      }
+      return a1.length - a2.length;
+    }
+  }
+
+  class C {
+    @Override
+    @org.checkerframework.dataflow.qual.Pure
+    public boolean equals(Object other) {
+      // Heuristic: permit "this == arg1" in a test in the first statement
+      // of a "Comparator.compare" method, if the body just returns true.
+      if (this == other) {
+        return true;
+      }
+      return super.equals(other);
+    }
+  }
+
+  // // TODO
+  // class D {
+  //     @Override
+  //     public boolean equals(Object other) {
+  //         // Don't suppress warnings at "this == arg1" if arg1 has been reassigned
+  //         other = new Object();
+  //
+  //         if (this == other) {
+  //             return true;
+  //         }
+  //         return super.equals(other);
+  //     }
+  // }
+
+}
diff --git a/checker/tests/interning/ConditionalInterning.java b/checker/tests/interning/ConditionalInterning.java
new file mode 100644
index 0000000..0ae5b59
--- /dev/null
+++ b/checker/tests/interning/ConditionalInterning.java
@@ -0,0 +1,7 @@
+public class ConditionalInterning {
+  int a, b, c;
+
+  boolean cmp() {
+    return (a > b ? a < c : a > c);
+  }
+}
diff --git a/checker/tests/interning/ConstantsInterning.java b/checker/tests/interning/ConstantsInterning.java
new file mode 100644
index 0000000..24b9d83
--- /dev/null
+++ b/checker/tests/interning/ConstantsInterning.java
@@ -0,0 +1,36 @@
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class ConstantsInterning {
+
+  // All but D should be inferred to be @Interned String.
+  final String A = "A";
+  final String B = "B";
+  final String AB = A + B;
+  final String AC = A + "C";
+  final String D = new String("D");
+  final @Interned String E = new String("E").intern();
+  final Object F = "F";
+
+  void foo() {
+    @Interned String is;
+    is = A;
+    is = B;
+    is = AB;
+    is = A + B;
+    is = AC;
+    is = A + "C";
+    is = A + B + "C";
+    // :: error: (assignment)
+    is = D;
+    // :: error: (assignment)
+    is = A + E;
+    // :: error: (assignment)
+    is = is + is;
+    is = Constants2.E;
+    is = (String) F;
+  }
+}
+
+class Constants2 {
+  public static final String E = "e";
+}
diff --git a/checker/tests/interning/Creation.java b/checker/tests/interning/Creation.java
new file mode 100644
index 0000000..7bd6396
--- /dev/null
+++ b/checker/tests/interning/Creation.java
@@ -0,0 +1,48 @@
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class Creation {
+  @Interned Foo[] a = new @Interned Foo[22]; // valid
+
+  class Foo {}
+
+  @Interned Foo[] fa_field1 = new @Interned Foo[22]; // valid
+  @Interned Foo[] fa_field2 = new @Interned Foo[22]; // valid
+
+  public void test() {
+    // :: error: (assignment)
+    @Interned Foo f = new Foo(); // error
+    Foo g = new Foo(); // valid
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Interned Foo h = new @Interned Foo(); // valid
+    // :: error: (not.interned)
+    boolean b = (f == g); // error
+
+    @Interned Foo[] fa1 = new @Interned Foo[22]; // valid
+    @Interned Foo[] fa2 = new @Interned Foo[22]; // valid
+  }
+
+  public @Interned Object read_data_0() {
+    // :: error: (return)
+    return new Object();
+  }
+
+  public @Interned Object read_data_1() {
+    // :: error: (return)
+    return Integer.valueOf(22);
+  }
+
+  public @Interned Integer read_data_2() {
+    // :: error: (return)
+    return Integer.valueOf(22);
+  }
+
+  public @Interned Object read_data_3() {
+    // :: error: (return)
+    return new String("hello");
+  }
+
+  public @Interned String read_data_4() {
+    // :: error: (return)
+    return new String("hello");
+  }
+}
diff --git a/checker/tests/interning/Creation2.java b/checker/tests/interning/Creation2.java
new file mode 100644
index 0000000..3c204d9
--- /dev/null
+++ b/checker/tests/interning/Creation2.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class Creation2 {
+
+  @Interned class Baz {
+    @SuppressWarnings({"inconsistent.constructor.type", "super.invocation"})
+    @Interned Baz() {}
+  }
+
+  void test() {
+    Baz b = new Baz();
+  }
+}
diff --git a/checker/tests/interning/Distinct.java b/checker/tests/interning/Distinct.java
new file mode 100644
index 0000000..3c4e3aa
--- /dev/null
+++ b/checker/tests/interning/Distinct.java
@@ -0,0 +1,68 @@
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+
+public class Distinct {
+
+  class Foo {}
+
+  Foo f1;
+  Foo f2;
+  @Interned Foo i1;
+  @Interned Foo i2;
+  @InternedDistinct Foo d1;
+  @InternedDistinct Foo d2;
+
+  public void testEquals() {
+    // :: error: not.interned
+    if (f1 == f2) {}
+    // :: error: not.interned
+    if (f1 == i2) {}
+    if (f1 == d2) {}
+    // :: error: not.interned
+    if (i1 == f2) {}
+    if (i1 == i2) {}
+    if (i1 == d2) {}
+    if (d1 == f2) {}
+    if (d1 == i2) {}
+    if (d1 == d2) {}
+  }
+
+  public void testAssignment1() {
+    f1 = f2;
+  }
+
+  public void testAssignment2() {
+    f1 = i2;
+  }
+
+  public void testAssignment3() {
+    f1 = d2;
+  }
+
+  public void testAssignment4() {
+    // :: error: assignment
+    i1 = f2;
+  }
+
+  public void testAssignment5() {
+    i1 = i2;
+  }
+
+  public void testAssignment6() {
+    i1 = d2;
+  }
+
+  public void testAssignment7() {
+    // :: error: assignment
+    d1 = f2;
+  }
+
+  public void testAssignment8() {
+    // :: error: assignment
+    d1 = i2;
+  }
+
+  public void testAssignment9() {
+    d1 = d2;
+  }
+}
diff --git a/checker/tests/interning/DontCrash.java b/checker/tests/interning/DontCrash.java
new file mode 100644
index 0000000..979fff9
--- /dev/null
+++ b/checker/tests/interning/DontCrash.java
@@ -0,0 +1,27 @@
+// This code is illegal (javac issues an error), but nonetheless the org.checkerframework.checker
+// shouldn't crash.  (Maybe they shouldn't run at all if javac issues any errors?)
+// @skip-test
+
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class DontCrash {
+
+  // from VarInfoAux
+  static class VIA {
+    // :: non-static variable this cannot be referenced from a static context
+    // :: inner classes cannot have static declarations
+    // :: non-static variable this cannot be referenced from a static context
+    // :: inner classes cannot have static declarations
+    private static VIA theDefault = new VIA();
+    private Map<@Interned String, @Interned String> map;
+
+    void testMap() {
+      Map<@Interned String, @Interned String> mymap;
+      mymap = theDefault.map;
+      mymap = new HashMap<@Interned String, @Interned String>(theDefault.map);
+      mymap = new HashMap<>(theDefault.map);
+    }
+  }
+}
diff --git a/checker/tests/interning/Enumerations.java b/checker/tests/interning/Enumerations.java
new file mode 100644
index 0000000..449a447
--- /dev/null
+++ b/checker/tests/interning/Enumerations.java
@@ -0,0 +1,29 @@
+public class Enumerations {
+
+  // All enumeration instances are interned; there should be no need for an annotation.
+  enum StudentYear {
+    FRESHMAN,
+    SOPHOMORE,
+    JUNIOR,
+    SENIOR;
+
+    // check that receiver is OK
+    @org.checkerframework.dataflow.qual.Pure
+    public String toString() {
+      return "StudentYear: ...";
+    }
+  }
+
+  public boolean isSophomore(StudentYear sy) {
+    return sy == StudentYear.SOPHOMORE;
+  }
+
+  public boolean flow(StudentYear s) {
+    StudentYear m = StudentYear.SOPHOMORE;
+    return s == m;
+  }
+
+  StudentYear cast(Object o) {
+    return (StudentYear) o;
+  }
+}
diff --git a/checker/tests/interning/ExpressionsInterning.java b/checker/tests/interning/ExpressionsInterning.java
new file mode 100644
index 0000000..35c260f
--- /dev/null
+++ b/checker/tests/interning/ExpressionsInterning.java
@@ -0,0 +1,54 @@
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class ExpressionsInterning {
+
+  class A {
+    B b;
+  }
+
+  class B {
+    C c;
+
+    D d() {
+      return new D();
+    }
+
+    Boolean bBoolean() {
+      return true;
+    }
+  }
+
+  class C {}
+
+  class D {}
+
+  public Boolean fieldThenMethod(A a) {
+    Boolean temp = a.b.bBoolean();
+    return temp;
+  }
+
+  class Foo {
+    public @Interned Foo returnThis(@Interned Foo this, @Interned Foo other) {
+      if (other == this) {
+        return this;
+      } else {
+        return null;
+      }
+    }
+  }
+
+  // :: warning: (cast.unsafe.constructor.invocation)
+  public @Interned Foo THEONE = new @Interned Foo();
+
+  public boolean isItTheOne(Foo f) {
+    return THEONE.equals(f);
+  }
+
+  // A warning when interned objects are compared via .equals helps me in determining whether it is
+  // a good idea to convert a given class or reference to @Interned -- I can see whether there are
+  // places that it is compared with .equals, which I might need to examine.
+  public boolean dontUseEqualsMethod(@Interned Foo f1, @Interned Foo f2) {
+    // :: warning: (unnecessary.equals)
+    return f1.equals(f2);
+  }
+}
diff --git a/checker/tests/interning/FieldsImplicits.java b/checker/tests/interning/FieldsImplicits.java
new file mode 100644
index 0000000..e46a07e
--- /dev/null
+++ b/checker/tests/interning/FieldsImplicits.java
@@ -0,0 +1,13 @@
+/** Tests that a final field annotation is inferred. */
+public class FieldsImplicits {
+  final String finalField = "asdf";
+  static final String finalStaticField = "asdf";
+  String nonFinalField = "asdf";
+
+  void test() {
+    boolean a = finalField == "asdf";
+    boolean b = finalStaticField == "asdf";
+    // :: error: (not.interned)
+    boolean c = nonFinalField == "asdf";
+  }
+}
diff --git a/checker/tests/interning/FindDistinctTest.java b/checker/tests/interning/FindDistinctTest.java
new file mode 100644
index 0000000..7baa273
--- /dev/null
+++ b/checker/tests/interning/FindDistinctTest.java
@@ -0,0 +1,31 @@
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+
+public class FindDistinctTest {
+
+  public void ok1(@FindDistinct Object o) {
+    // TODO: The fact that this type-checks is an (undesired) artifact of the current
+    // implementation of @FindDistinct.
+    @InternedDistinct Object o2 = o;
+  }
+
+  public void ok2(@FindDistinct Object findIt, Object other) {
+    boolean b = findIt == other;
+  }
+
+  public void useOk1(Object notinterned, @Interned Object interned) {
+    ok1(notinterned);
+    ok1(interned);
+  }
+
+  public void bad1(Object o) {
+    // :: error: (assignment)
+    @InternedDistinct Object o2 = o;
+  }
+
+  public void bad2(Object findIt, Object other) {
+    // :: error: (not.interned)
+    boolean b = findIt == other;
+  }
+}
diff --git a/checker/tests/interning/FlowInterning.java b/checker/tests/interning/FlowInterning.java
new file mode 100644
index 0000000..247e335
--- /dev/null
+++ b/checker/tests/interning/FlowInterning.java
@@ -0,0 +1,60 @@
+import java.util.ArrayList;
+import java.util.List;
+
+public class FlowInterning {
+
+  // @skip-test
+  // Look at issue 47
+  //  public boolean isSame(Object a, Object b) {
+  //    return ((a == null)
+  //            ? (a == b)
+  //            : (a.equals(b)));
+  //  }
+
+  public void testAppendingChar() {
+    String arg = "";
+    arg += ' ';
+
+    // Interning Checker should NOT suggest == here.
+    if (!arg.equals("")) {}
+  }
+
+  public String[] parse(String args) {
+
+    // Split the args string on whitespace boundaries accounting for quoted strings.
+    args = args.trim();
+    List<String> arg_list = new ArrayList<>();
+    String arg = "";
+    char active_quote = 0;
+    for (int ii = 0; ii < args.length(); ii++) {
+      char ch = args.charAt(ii);
+      if ((ch == '\'') || (ch == '"')) {
+        arg += ch;
+        ii++;
+        while ((ii < args.length()) && (args.charAt(ii) != ch)) {
+          arg += args.charAt(ii++);
+        }
+        arg += ch;
+      } else if (Character.isWhitespace(ch)) {
+        // System.out.printf ("adding argument '%s'%n", arg);
+        arg_list.add(arg);
+        arg = "";
+        while ((ii < args.length()) && Character.isWhitespace(args.charAt(ii))) {
+          ii++;
+        }
+        if (ii < args.length()) {
+          ii--;
+        }
+      } else { // must be part of current argument
+        arg += ch;
+      }
+    }
+    // Interning Checker should NOT suggest == here.
+    if (!arg.equals("")) {
+      arg_list.add(arg);
+    }
+
+    String[] argsArray = arg_list.toArray(new String[arg_list.size()]);
+    return argsArray;
+  }
+}
diff --git a/checker/tests/interning/FlowInterning1.java b/checker/tests/interning/FlowInterning1.java
new file mode 100644
index 0000000..ef13e1f
--- /dev/null
+++ b/checker/tests/interning/FlowInterning1.java
@@ -0,0 +1,16 @@
+/*
+ * A test inspired by some problem in Daikon.
+ */
+public class FlowInterning1 {
+
+  void test(String[] tokens, int i) {
+    String arg_type_name = tokens[i].intern();
+    if (i + 1 >= tokens.length) {
+      throw new RuntimeException("No matching arg val for argument type " + arg_type_name);
+    }
+    String arg_val = tokens[i + 1];
+    if (arg_type_name == "boolean") { // interned
+      // ...
+    }
+  }
+}
diff --git a/checker/tests/interning/Generics.java b/checker/tests/interning/Generics.java
new file mode 100644
index 0000000..c4a39b4
--- /dev/null
+++ b/checker/tests/interning/Generics.java
@@ -0,0 +1,140 @@
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class Generics {
+
+  void testGenerics() {
+
+    Map<String, @Interned String> map = null;
+    map = new HashMap<>();
+
+    String a = new String("foo");
+    @Interned String b = "bar";
+
+    String notInterned;
+    @Interned String interned;
+
+    map.put(a, b); // valid
+    // :: error: (argument)
+    map.put(b, a); // error
+
+    notInterned = map.get(a); // valid
+    interned = map.get(b); // valid
+
+    Collection<@Interned String> internedSet;
+    Collection<String> notInternedSet;
+
+    notInternedSet = map.keySet(); // valid
+    // :: error: (assignment)
+    internedSet = map.keySet(); // error
+
+    // :: error: (assignment)
+    notInternedSet = map.values(); // error
+    internedSet = map.values(); // valid
+
+    HashMap<@Interned String, Vector<@Interned Integer>> all_nums = new HashMap<>();
+    Vector<@Interned Integer> v = all_nums.get("Hello");
+  }
+
+  // The cells aren't interned, but their contents are
+  class CellOfImm<T extends @Interned Object> {
+    T value;
+
+    boolean equals(CellOfImm<T> other) {
+      return value == other.value; // valid
+    }
+  }
+
+  List<@Interned String> istrings = new ArrayList<>();
+  List<String> strings = new ArrayList<>();
+  @Interned String istring = "interned";
+  String string = new String("uninterned");
+
+  void testGenerics2() {
+    istrings.add(istring);
+    // :: error: (argument)
+    istrings.add(string); // invalid
+    strings.add(istring);
+    strings.add(string);
+    istring = istrings.get(0);
+    string = istrings.get(0);
+    // :: error: (assignment)
+    istring = strings.get(0); // invalid
+    string = strings.get(0);
+  }
+
+  void testCollections() {
+    Collection<String> strings = Collections.unmodifiableCollection(new ArrayList<String>());
+
+    Collection<@Interned String> istrings =
+        Collections.unmodifiableCollection(new ArrayList<@Interned String>()); // valid
+  }
+
+  class MyList extends ArrayList<@Interned String> {
+    // Correct return value is Iterator<@Interned String>
+    // :: error: (override.return)
+    public Iterator<String> iterator() {
+      return null;
+    }
+  }
+
+  // from VarInfoAux
+  static class VIA {
+    private static VIA theDefault = new VIA();
+    private Map<@Interned String, @Interned String> map;
+
+    void testMap() {
+      Map<@Interned String, @Interned String> mymap;
+      mymap = theDefault.map;
+      mymap = new HashMap<@Interned String, @Interned String>(theDefault.map);
+      mymap = new HashMap<>(theDefault.map);
+    }
+  }
+
+  // type inference
+  <T> T id(T m, Object t) {
+    return m;
+  }
+
+  void useID() {
+    String o = id("m", null);
+  }
+
+  // raw types again
+  void testRawTypes() {
+    ArrayList lst = null;
+    Collections.sort(lst);
+  }
+
+  public static class Pair<T1, T2> {
+    public T1 a;
+    public T2 b;
+
+    public Pair(T1 a, T2 b) {
+      this.a = a;
+      this.b = b;
+    }
+
+    /** Factory method with short name and no need to name type parameters. */
+    public static <A, B> Pair<A, B> of(A a, B b) {
+      return new Pair<>(a, b);
+    }
+  }
+
+  static class C<T> {
+    T next1;
+
+    // @skip-test
+    // This test might be faulty
+    //        private Pair<T,T> return1() {
+    //            Pair<T,T> result = Pair.of(next1, (T)null);
+    //            return result;
+    //        }
+  }
+}
diff --git a/checker/tests/interning/HeuristicsTest.java b/checker/tests/interning/HeuristicsTest.java
new file mode 100644
index 0000000..bda33b5
--- /dev/null
+++ b/checker/tests/interning/HeuristicsTest.java
@@ -0,0 +1,238 @@
+import java.util.Comparator;
+import org.checkerframework.checker.interning.qual.CompareToMethod;
+import org.checkerframework.checker.interning.qual.EqualsMethod;
+
+public class HeuristicsTest implements Comparable<HeuristicsTest> {
+
+  public static final class MyComparator implements Comparator<String> {
+    // Using == is OK if it's the first statement in the compare method,
+    // it's comparing the arguments, and the return value is 0.
+    public int compare(String s1, String s2) {
+      if (s1 == s2) {
+        return 0;
+      }
+      return String.CASE_INSENSITIVE_ORDER.compare(s1, s2);
+    }
+  }
+
+  @Override
+  @org.checkerframework.dataflow.qual.Pure
+  public boolean equals(Object o) {
+    // Using == is OK if it's the first statement in the equals method
+    // and it compares "this" against the argument.
+    if (this == o) {
+      return true;
+    }
+    // Not the first statement in the method.
+    // :: error: (not.interned)
+    if (o == this) {
+      return true;
+    }
+    return false;
+  }
+
+  @EqualsMethod
+  @org.checkerframework.dataflow.qual.Pure
+  public boolean equals2(Object o) {
+    // Using == is OK if it's the first statement in the equals method
+    // and it compares "this" against the argument.
+    if (this == o) {
+      return true;
+    }
+    // Not the first statement in the method.
+    // :: error: (not.interned)
+    if (o == this) {
+      return true;
+    }
+    return false;
+  }
+
+  @org.checkerframework.dataflow.qual.Pure
+  public boolean equals3(Object o) {
+    // Not equals() or annotated as @EqualsMethod.
+    // :: error: (not.interned)
+    if (this == o) {
+      return true;
+    }
+    // Not the first statement in the method.
+    // :: error: (not.interned)
+    if (o == this) {
+      return true;
+    }
+    return false;
+  }
+
+  @EqualsMethod
+  @org.checkerframework.dataflow.qual.Pure
+  public static boolean equals4(Object thisOne, Object o) {
+    // Using == is OK if it's the first statement in the equals method
+    // and it compares "this" against the argument.
+    if (thisOne == o) {
+      return true;
+    }
+    // Not the first statement in the method.
+    // :: error: (not.interned)
+    if (o == thisOne) {
+      return true;
+    }
+    return false;
+  }
+
+  @org.checkerframework.dataflow.qual.Pure
+  public static boolean equals5(Object thisOne, Object o) {
+    // Not equals() or annotated as @EqualsMethod.
+    // :: error: (not.interned)
+    if (thisOne == o) {
+      return true;
+    }
+    // Not the first statement in the method.
+    // :: error: (not.interned)
+    if (o == thisOne) {
+      return true;
+    }
+    return false;
+  }
+
+  @EqualsMethod
+  // :: error: (invalid.method.annotation)
+  public boolean equals6() {
+    return true;
+  }
+
+  @EqualsMethod
+  // :: error: (invalid.method.annotation)
+  public boolean equals7(int a, int b, int c) {
+    return true;
+  }
+
+  @Override
+  @org.checkerframework.dataflow.qual.Pure
+  public int compareTo(HeuristicsTest o) {
+    // Using == is OK if it's the first statement in the equals method
+    // and it compares "this" against the argument.
+
+    if (o == this) {
+      return 0;
+    }
+    // Not the first statement in the method.
+    // :: error: (not.interned)
+    if (this == o) {
+      return 0;
+    }
+    return 0;
+  }
+
+  @CompareToMethod
+  @org.checkerframework.dataflow.qual.Pure
+  public int compareTo2(HeuristicsTest o) {
+    // Using == is OK if it's the first statement in the equals method
+    // and it compares "this" against the argument.
+
+    if (o == this) {
+      return 0;
+    }
+    // Not the first statement in the method.
+    // :: error: (not.interned)
+    if (this == o) {
+      return 0;
+    }
+    return 0;
+  }
+
+  @org.checkerframework.dataflow.qual.Pure
+  public int compareTo3(HeuristicsTest o) {
+    // Not compareTo or annotated as @CompareToMethod
+    // :: error: (not.interned)
+    if (o == this) {
+      return 0;
+    }
+    // Not the first statement in the method.
+    // :: error: (not.interned)
+    if (this == o) {
+      return 0;
+    }
+    return 0;
+  }
+
+  @CompareToMethod
+  @org.checkerframework.dataflow.qual.Pure
+  public static int compareTo4(HeuristicsTest thisOne, HeuristicsTest o) {
+    // Using == is OK if it's the first statement in the equals method
+    // and it compares "this" against the argument.
+
+    if (o == thisOne) {
+      return 0;
+    }
+    // Not the first statement in the method.
+    // :: error: (not.interned)
+    if (thisOne == o) {
+      return 0;
+    }
+    return 0;
+  }
+
+  @org.checkerframework.dataflow.qual.Pure
+  public static int compareTo5(HeuristicsTest thisOne, HeuristicsTest o) {
+    // Not compareTo or annotated as @CompareToMethod
+    // :: error: (not.interned)
+    if (o == thisOne) {
+      return 0;
+    }
+    // Not the first statement in the method.
+    // :: error: (not.interned)
+    if (thisOne == o) {
+      return 0;
+    }
+    return 0;
+  }
+
+  @EqualsMethod
+  // :: error: (invalid.method.annotation)
+  public boolean compareTo6() {
+    return true;
+  }
+
+  @EqualsMethod
+  // :: error: (invalid.method.annotation)
+  public boolean compareTo7(int a, int b, int c) {
+    return true;
+  }
+
+  public boolean optimizeEqualsClient(Object a, Object b, Object[] arr) {
+    // Using == is OK if it's the left-hand side of an || whose right-hand
+    // side is a call to equals with the same arguments.
+    if (a == b || a.equals(b)) {
+      System.out.println("one");
+    }
+    if (a == b || b.equals(a)) {
+      System.out.println("two");
+    }
+
+    boolean c = (a == b || a.equals(b));
+    c = (a == b || b.equals(a));
+
+    boolean d = (a == b) || (a != null ? a.equals(b) : false);
+
+    boolean e = (a == b || (a != null && a.equals(b)));
+
+    boolean f = (arr[0] == a || arr[0].equals(a));
+
+    return (a == b || a.equals(b));
+  }
+
+  public <T extends Comparable<T>> boolean optimizeCompareToClient(T a, T b) {
+    // Using == is OK if it's the left-hand side of an || whose right-hand
+    // side is a call to compareTo with the same arguments.
+    if (a == b || a.compareTo(b) == 0) {
+      System.out.println("one");
+    }
+    if (a == b || b.compareTo(a) == 0) {
+      System.out.println("two");
+    }
+
+    boolean c = (a == b || a.compareTo(b) == 0);
+    c = (a == b || a.compareTo(b) == 0);
+
+    return (a == b || a.compareTo(b) == 0);
+  }
+}
diff --git a/checker/tests/interning/InterfaceUseObjectEquals.java b/checker/tests/interning/InterfaceUseObjectEquals.java
new file mode 100644
index 0000000..3a312ad
--- /dev/null
+++ b/checker/tests/interning/InterfaceUseObjectEquals.java
@@ -0,0 +1,4 @@
+import org.checkerframework.checker.interning.qual.UsesObjectEquals;
+
+@UsesObjectEquals
+interface InterfaceUsesObjectEquals {}
diff --git a/checker/tests/interning/InternMethodTest.java b/checker/tests/interning/InternMethodTest.java
new file mode 100644
index 0000000..3b7a436
--- /dev/null
+++ b/checker/tests/interning/InternMethodTest.java
@@ -0,0 +1,28 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class InternMethodTest {
+
+  private static Map<Foo, @Interned Foo> pool = new HashMap<>();
+
+  class Foo {
+
+    @SuppressWarnings("interning")
+    public @Interned Foo intern() {
+      if (!pool.containsKey(this)) {
+        pool.put(this, (@Interned Foo) this);
+      }
+      return pool.get(this);
+    }
+  }
+
+  void test() {
+    Foo f = new Foo();
+    @Interned Foo g = f.intern();
+  }
+
+  public static @Interned String intern(String a) {
+    return (a == null) ? null : a.intern();
+  }
+}
diff --git a/checker/tests/interning/InternedClass.java b/checker/tests/interning/InternedClass.java
new file mode 100644
index 0000000..4bc92bc
--- /dev/null
+++ b/checker/tests/interning/InternedClass.java
@@ -0,0 +1,156 @@
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Vector;
+import org.checkerframework.checker.interning.qual.InternMethod;
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.checker.interning.qual.UnknownInterned;
+
+// The @Interned annotation indicates that much like an enum, all variables
+// declared of this type are interned (except the constructor return value).
+public @Interned class InternedClass {
+
+  int value;
+
+  InternedClass factory(int i) {
+    return new InternedClass(i).intern();
+  }
+
+  // Private constructor
+  private InternedClass(int i) {
+    value = i;
+    // "this" in the constructor is not interned.
+    // :: error: (assignment)
+    @Interned InternedClass that = this;
+  }
+
+  // Overriding method
+  @org.checkerframework.dataflow.qual.Pure
+  public String toString() {
+    @Interned InternedClass c = this;
+    return Integer.valueOf(value).toString();
+  }
+
+  // Factory method
+  private InternedClass(InternedClass ic) {
+    value = ic.value;
+  }
+
+  // Equals method (used only by interning; clients should use ==)
+  @org.checkerframework.dataflow.qual.Pure
+  public boolean equals(Object other) {
+    if (!(other instanceof InternedClass)) {
+      return false;
+    }
+    return value == ((InternedClass) other).value;
+  }
+
+  // Interning method
+  @SuppressWarnings("annotations.on.use")
+  private static Map<@UnknownInterned InternedClass, @Interned InternedClass> pool =
+      new HashMap<>();
+
+  @InternMethod
+  public @Interned InternedClass intern() {
+    if (!pool.containsKey(this)) {
+      pool.put(this, (@Interned InternedClass) this);
+    }
+    return pool.get(this);
+  }
+
+  public void myMethod(InternedClass ic, InternedClass[] ica) {
+    boolean b1 = (this == ic); // valid
+    boolean b2 = (this == returnInternedObject()); // valid
+    boolean b3 = (this == ica[0]); // valid
+    InternedClass ic2 = returnArray()[0]; // valid
+    // :: error: (interned.object.creation)
+    ica[0] = new InternedClass(22);
+    InternedClass[] arr1 = returnArray(); // valid
+    InternedClass[] arr2 = new InternedClass[22]; // valid
+    InternedClass[] arr3 = new InternedClass[] {}; // valid
+
+    Map<InternedClass, Integer> map = new LinkedHashMap<>();
+    for (Map.Entry<InternedClass, Integer> e : map.entrySet()) {
+      InternedClass ic3 = e.getKey(); // valid
+    }
+  }
+
+  public InternedClass returnInternedObject() {
+    return this;
+  }
+
+  public InternedClass[] returnArray() {
+    return new InternedClass[] {};
+  }
+
+  public void internedVarargs(String name, InternedClass... args) {
+    InternedClass arg = args[0]; // valid
+  }
+
+  public void internedVarargs2(String name, @Interned String... args) {
+    @Interned String arg = args[0]; // valid
+  }
+
+  public static InternedClass[] arrayclone_simple(InternedClass[] a_old) {
+    int len = a_old.length;
+    InternedClass[] a_new = new InternedClass[len];
+    for (int i = 0; i < len; i++) {
+      // :: error: (interned.object.creation)
+      a_new[i] = new InternedClass(a_old[i]);
+    }
+    return a_new;
+  }
+
+  public @Interned class Subclass extends InternedClass {
+    // Private constructor
+    private Subclass(int i) {
+      super(i);
+    }
+  }
+
+  public static void castFromInternedClass(InternedClass ic) {
+    Subclass s = (Subclass) ic;
+  }
+
+  public static void castToInternedClass(Object o) {
+    InternedClass ic = (InternedClass) o;
+  }
+
+  // Default implementation
+  @org.checkerframework.dataflow.qual.Pure
+  public InternedClass clone() throws CloneNotSupportedException {
+    return (InternedClass) super.clone();
+  }
+
+  // java.lang.Class should be considered interned
+  public static void classTest() {
+    Integer i = 5;
+    assert i.getClass() == Integer.class;
+  }
+
+  // java.lang.Class is interned
+  public static void arrayOfClass() throws Exception {
+    Class<?> c = String.class;
+    Class[] parameterTypes = new Class[1];
+    parameterTypes[0] = String.class;
+    java.lang.reflect.Constructor<?> ctor = c.getConstructor(parameterTypes);
+  }
+
+  Class[] getSuperClasses(Class<?> c) {
+    Vector<Class<?>> v = new Vector<>();
+    while (true) {
+      // :: warning: (unnecessary.equals)
+      if (c.getSuperclass().equals((new Object()).getClass())) {
+        break;
+      }
+      c = c.getSuperclass();
+      v.addElement(c);
+    }
+    return (Class[]) v.toArray(new Class[0]);
+  }
+
+  void testCast(Object o) {
+    Object i = (InternedClass) o;
+    if (i == this) {}
+  }
+}
diff --git a/checker/tests/interning/InternedClass2.java b/checker/tests/interning/InternedClass2.java
new file mode 100644
index 0000000..ae30c62
--- /dev/null
+++ b/checker/tests/interning/InternedClass2.java
@@ -0,0 +1,77 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.interning.qual.InternMethod;
+import org.checkerframework.checker.interning.qual.Interned;
+
+public @Interned class InternedClass2 {
+  private final int i;
+  // @UnknownInterned is the default annotation on constructor results even for @Interned classes.
+  private InternedClass2(int i) {
+    // Type of "this" inside a constructor of an @Interned class is @UnknownInterned.
+    // :: error: (assignment)
+    @Interned InternedClass2 that = this;
+    this.i = i;
+  }
+
+  InternedClass2 factory(int i) {
+    // :: error: (interned.object.creation) :: error: (method.invocation)
+    new InternedClass2(i).someMethod(); // error, call to constructor on for @Interned class.
+    (new InternedClass2(i)).intern(); // ok, call to constructor receiver to @InternMethod
+    ((((new InternedClass2(i))))).intern(); // ok, call to constructor receiver to @InternMethod
+    return new InternedClass2(i).intern(); // ok, call to constructor receiver to @InternMethod
+  }
+
+  void someMethod() {
+    // Type of "this" inside a method (not marked @InternedMethod) is @Interned,
+    // assuming the method is declared in an @Interned class.
+    @Interned InternedClass2 that = this; // ok
+  }
+
+  private static Map<Integer, InternedClass2> pool = new HashMap<>();
+
+  @InternMethod
+  public InternedClass2 intern() {
+    // Type of "this" inside an @InternMethod is @UnknownInterned
+    // :: error: (assignment)
+    @Interned InternedClass2 that = this;
+    if (!pool.containsKey(this.i)) {
+      // The above check proves "this" is interned.
+      @SuppressWarnings("interning:assignment")
+      @Interned InternedClass2 internedThis = this;
+      pool.put(this.i, internedThis);
+    }
+    return pool.get(this.i);
+  }
+
+  @Override // ok, no override invalid receiver error is issued.
+  public String toString() {
+    @Interned InternedClass2 that = this; // ok
+    return super.toString();
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (this == object) {
+      return true;
+    }
+    if (object == null || getClass() != object.getClass()) {
+      return false;
+    }
+
+    InternedClass2 that = (InternedClass2) object;
+
+    return i == that.i;
+  }
+
+  @Override
+  public int hashCode() {
+    return i;
+  }
+
+  public boolean hasNodeOfType(Class<?> type) {
+    if (type == this.getClass()) {
+      return true;
+    }
+    return false;
+  }
+}
diff --git a/checker/tests/interning/InternedClassDecl.java b/checker/tests/interning/InternedClassDecl.java
new file mode 100644
index 0000000..b297454
--- /dev/null
+++ b/checker/tests/interning/InternedClassDecl.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class InternedClassDecl {
+  static @Interned class InternedClass {}
+
+  static class Generic<T extends InternedClass, S extends T> {}
+
+  static @Interned class RecursiveClass2<G extends RecursiveClass2<G>> {}
+}
diff --git a/checker/tests/interning/Issue2809.java b/checker/tests/interning/Issue2809.java
new file mode 100644
index 0000000..c5981f2
--- /dev/null
+++ b/checker/tests/interning/Issue2809.java
@@ -0,0 +1,31 @@
+// test cases for #2809
+// https://github.com/typetools/checker-framework/issues/2809
+
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.checker.interning.qual.UnknownInterned;
+
+public class Issue2809 {
+
+  void new1(MyType<int @Interned []> t, int @Interned [] non) {
+    t.self(new MyType<>(non));
+  }
+
+  void new2(MyType<int @Interned []> t, int @Interned [] non) {
+    t.self(new MyType<int @Interned []>(non));
+  }
+
+  void new3(MyType<@Interned MyType<Object>> t, @Interned MyType<Object> non) {
+    t.self(new MyType<>(non));
+  }
+
+  void newFail(MyType<int @Interned []> t, int @UnknownInterned [] non) {
+    // :: error: (argument)
+    t.self(new MyType<>(non));
+  }
+
+  class MyType<T> {
+    MyType(T p) {}
+
+    void self(MyType<T> myType) {}
+  }
+}
diff --git a/checker/tests/interning/Issue3594.java b/checker/tests/interning/Issue3594.java
new file mode 100644
index 0000000..ab461fa
--- /dev/null
+++ b/checker/tests/interning/Issue3594.java
@@ -0,0 +1,10 @@
+public class Issue3594 {
+
+  // Throwable is annotated with @UsesObjectEquals, which is an inherited annotation.
+  // So, MyThrowable should be treated as @UsesObjectEquals, too.
+  static class MyThrowable extends Throwable {}
+
+  void use(MyThrowable t, MyThrowable t2) {
+    boolean b = t == t2;
+  }
+}
diff --git a/checker/tests/interning/IterableGenerics.java b/checker/tests/interning/IterableGenerics.java
new file mode 100644
index 0000000..6b194e1
--- /dev/null
+++ b/checker/tests/interning/IterableGenerics.java
@@ -0,0 +1,11 @@
+public class IterableGenerics {
+  interface Data extends Iterable<String> {}
+
+  <T extends Data> void typeParam(T t) {
+    for (String s : t) {}
+  }
+
+  void wildcard(Iterable<? extends Data> t) {
+    for (Object a : t.iterator().next()) {}
+  }
+}
diff --git a/checker/tests/interning/MapEntryLubError.java b/checker/tests/interning/MapEntryLubError.java
new file mode 100644
index 0000000..e20d570
--- /dev/null
+++ b/checker/tests/interning/MapEntryLubError.java
@@ -0,0 +1,8 @@
+import java.util.Map;
+
+public class MapEntryLubError<V> {
+  public boolean lubError(Map.Entry<Object, V> ent) {
+    Object v;
+    return (v = ent.getValue()) == null;
+  }
+}
diff --git a/checker/tests/interning/MethodInvocation.java b/checker/tests/interning/MethodInvocation.java
new file mode 100644
index 0000000..d0cf6ac
--- /dev/null
+++ b/checker/tests/interning/MethodInvocation.java
@@ -0,0 +1,53 @@
+import org.checkerframework.checker.interning.qual.*;
+
+public class MethodInvocation {
+  @Interned MethodInvocation interned;
+  MethodInvocation nonInterned;
+
+  void nonInternedMethod() {
+    nonInternedMethod();
+    // :: error: (method.invocation)
+    internedMethod(); // should emit error
+
+    this.nonInternedMethod();
+    // :: error: (method.invocation)
+    this.internedMethod(); // should emit error
+
+    interned.nonInternedMethod();
+    interned.internedMethod();
+
+    nonInterned.nonInternedMethod();
+    // :: error: (method.invocation)
+    nonInterned.internedMethod(); // should emit error
+  }
+
+  void internedMethod(@Interned MethodInvocation this) {
+    nonInternedMethod();
+    internedMethod();
+
+    this.nonInternedMethod();
+    this.internedMethod();
+
+    interned.nonInternedMethod();
+    interned.internedMethod();
+
+    nonInterned.nonInternedMethod();
+    // :: error: (method.invocation)
+    nonInterned.internedMethod(); // should emit error
+  }
+
+  // Now, test method parameters
+  void internedCharacterParameter(@Interned Character a) {}
+
+  // See https://github.com/typetools/checker-framework/issues/84
+  void internedCharacterParametersClient() {
+    // TODO: autoboxing from char to Character // :: error: (argument)
+    internedCharacterParameter('\u00E4'); // lowercase a with umlaut
+    // TODO: autoboxing from char to Character // :: error: (argument)
+    internedCharacterParameter('a');
+    // :: error: (argument)
+    internedCharacterParameter(Character.valueOf('a'));
+    // :: error: (argument)
+    internedCharacterParameter(Character.valueOf('a'));
+  }
+}
diff --git a/checker/tests/interning/NestedGenerics.java b/checker/tests/interning/NestedGenerics.java
new file mode 100644
index 0000000..1c186f4
--- /dev/null
+++ b/checker/tests/interning/NestedGenerics.java
@@ -0,0 +1,13 @@
+import java.util.List;
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class NestedGenerics {
+
+  public void test() {
+    List<List<@Interned Object>> foo = bar();
+  }
+
+  public List<List<@Interned Object>> bar() {
+    return null;
+  }
+}
diff --git a/checker/tests/interning/Options.java b/checker/tests/interning/Options.java
new file mode 100644
index 0000000..cc392fd
--- /dev/null
+++ b/checker/tests/interning/Options.java
@@ -0,0 +1,90 @@
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.interning.qual.*;
+
+// Test case lifted from plume.Options
+public class Options {
+
+  public void minimal(String s) {
+    String arg = ""; // interned here
+    @Interned String arg2 = arg;
+    arg += s; // no longer interned
+    // :: error: (assignment)
+    arg2 = arg;
+  }
+
+  public void minimal2(char c) {
+    String arg = ""; // interned here
+    @Interned String arg2 = arg;
+    arg += c; // no longer interned
+    // :: error: (assignment)
+    arg2 = arg;
+  }
+
+  public String[] otherparse(String args) {
+
+    // Split the args string on whitespace boundaries accounting for quoted strings.
+    args = args.trim();
+    List<String> arg_list = new ArrayList<>();
+    String arg = "";
+    char active_quote = 0;
+    // for (int ii = 0; ii < args.length(); ii++) {
+    char ch = args.charAt(0);
+    // arg = arg + ch;
+
+    // if ((ch == '\'') || (ch == '"')) {
+    arg += ch;
+    // }
+    // }
+    // :: error: (assignment)
+    @Interned String arg2 = arg;
+
+    if (!arg.equals("")) {
+      arg_list.add(arg);
+    }
+
+    String[] argsArray = arg_list.toArray(new String[arg_list.size()]);
+    return null;
+  }
+
+  public String[] parse(String args) {
+
+    // Split the args string on whitespace boundaries accounting for quoted strings.
+    args = args.trim();
+    List<String> arg_list = new ArrayList<>();
+    String arg = "";
+    char active_quote = 0;
+    for (int ii = 0; ii < args.length(); ii++) {
+      char ch = args.charAt(ii);
+      if ((ch == '\'') || (ch == '"')) {
+        arg += ch;
+        ii++;
+        while ((ii < args.length()) && (args.charAt(ii) != ch)) {
+          arg += args.charAt(ii++);
+        }
+        arg += ch;
+      } else if (Character.isWhitespace(ch)) {
+        // System.out.printf ("adding argument '%s'%n", arg);
+        arg_list.add(arg);
+        arg = "";
+        while ((ii < args.length()) && Character.isWhitespace(args.charAt(ii))) {
+          ii++;
+        }
+        if (ii < args.length()) {
+          ii--;
+        }
+      } else { // must be part of current argument
+        arg += ch;
+      }
+    }
+    // :: error: (assignment)
+    @Interned String arg2 = arg;
+
+    if (!arg.equals("")) {
+      arg_list.add(arg);
+    }
+
+    String[] argsArray = arg_list.toArray(new String[arg_list.size()]);
+    return null;
+  }
+}
diff --git a/checker/tests/interning/OverrideInterned.java b/checker/tests/interning/OverrideInterned.java
new file mode 100644
index 0000000..9ab789e
--- /dev/null
+++ b/checker/tests/interning/OverrideInterned.java
@@ -0,0 +1,61 @@
+import org.checkerframework.checker.interning.qual.Interned;
+
+public class OverrideInterned {
+
+  // This code is extracted from FreePastry
+
+  @Interned class NodeHandle {}
+
+  public interface TransportLayer<IDENTIFIER> {
+    public void sendMessage(IDENTIFIER i);
+  }
+
+  public class CommonAPITransportLayerImpl<IDENTIFIER extends NodeHandle>
+      implements TransportLayer<IDENTIFIER> {
+    public void sendMessage(IDENTIFIER i) {}
+  }
+
+  interface MessageReceipt {
+    public NodeHandle getHint();
+  }
+
+  void useAnonymousClass() {
+    MessageReceipt ret =
+        new MessageReceipt() {
+          public NodeHandle getHint() {
+            return null;
+          }
+        };
+  }
+
+  // This code is from Daikon
+
+  public abstract class TwoSequenceString {
+    public abstract Object check_modified1(@Interned String @Interned [] v1);
+
+    public abstract Object check_modified2(String @Interned [] v1);
+  }
+
+  /* Changing the array component type in the overriding method is illegal. */
+  public class PairwiseStringEqualBad extends TwoSequenceString {
+    // TODOINVARR:: error: (override.param)
+    public Object check_modified1(String @Interned [] a1) {
+      return new Object();
+    }
+    // :: error: (override.param)
+    public Object check_modified2(@Interned String @Interned [] a1) {
+      return new Object();
+    }
+  }
+
+  /* Changing the main reference type is allowed, if it is a supertype. */
+  public class PairwiseStringEqualGood extends TwoSequenceString {
+    public Object check_modified1(@Interned String[] a1) {
+      return new Object();
+    }
+
+    public Object check_modified2(String[] a1) {
+      return new Object();
+    }
+  }
+}
diff --git a/checker/tests/interning/Polymorphism.java b/checker/tests/interning/Polymorphism.java
new file mode 100644
index 0000000..9864e43
--- /dev/null
+++ b/checker/tests/interning/Polymorphism.java
@@ -0,0 +1,90 @@
+import java.lang.ref.WeakReference;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.checker.interning.qual.*;
+
+public class Polymorphism {
+  // Test parameter
+  public @PolyInterned String identity(@PolyInterned String s) {
+    return s;
+  }
+
+  void testParam() {
+    String notInterned = new String("not interned");
+    @Interned String interned = "interned";
+
+    interned = identity(interned);
+    // :: error: (assignment)
+    interned = identity(notInterned); // invalid
+  }
+
+  // test as receiver
+  @PolyInterned Polymorphism getSelf(@PolyInterned Polymorphism this) {
+    return this;
+  }
+
+  void testReceiver() {
+    Polymorphism notInterned = new Polymorphism();
+    @Interned Polymorphism interned = null;
+
+    interned = interned.getSelf();
+    // :: error: (assignment)
+    interned = notInterned.getSelf(); // invalid
+  }
+
+  // Test assinging interned to PolyInterned
+  public @PolyInterned String always(@PolyInterned String s) {
+    if (s.equals("n")) {
+      // This code type-checkd when the hierarchy contained just @UnknownInterned and
+      // @Interned, but no longer does because of @InternedDistinct.
+      // :: error: (return)
+      return "m";
+    } else {
+      // :: error: (return)
+      return new String("m"); // invalid
+    }
+  }
+
+  public static @PolyInterned Object[] id(@PolyInterned Object[] a) {
+    return a;
+  }
+
+  public static void idTest(@Interned Object @Interned [] seq) {
+    @Interned Object[] copy_uninterned = id(seq);
+  }
+
+  private static Map<
+          List<@Interned String @Interned []>, WeakReference<@Interned String @Interned []>>
+      internedStringSequenceAndIndices;
+  private static List<@Interned String @Interned []> sai;
+  private static WeakReference<@Interned String @Interned []> wr;
+
+  public static void testArrayInGeneric() {
+    internedStringSequenceAndIndices.put(sai, wr);
+  }
+
+  // check for a crash when using raw types
+  void processMap(Map<String, String> map) {}
+
+  void testRaw() {
+    Map m = null;
+    // TODO: RAW TYPES WILL EVENTUALLY REQUIRE THAT THERE BOUNDS BE EXACTLY THE QUALIFIER
+    // EXPECTED.
+    // :: warning: [unchecked] unchecked method invocation: method processMap in class
+    // Polymorphism is applied to given types :: warning: [unchecked] unchecked conversion
+    processMap(m);
+  }
+
+  // test anonymous classes
+  private void testAnonymous() {
+    new Object() {
+      @org.checkerframework.dataflow.qual.Pure
+      public boolean equals(Object o) {
+        return true;
+      }
+    }.equals(null);
+
+    Date d = new Date() {};
+  }
+}
diff --git a/checker/tests/interning/PrimitivesInterning.java b/checker/tests/interning/PrimitivesInterning.java
new file mode 100644
index 0000000..683637c
--- /dev/null
+++ b/checker/tests/interning/PrimitivesInterning.java
@@ -0,0 +1,123 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.interning.qual.*;
+
+public class PrimitivesInterning {
+
+  void test() {
+    int a = 3;
+
+    if (a == 3) {
+      System.out.println("yes");
+    } else {
+      System.out.println("no");
+    }
+
+    if (a != 2) {
+      System.out.println("yes");
+    } else {
+      System.out.println("no");
+    }
+
+    String name = "Interning";
+    if ((name.indexOf('[') == -1) && (name.indexOf('(') == -1)) {
+      System.out.println("has no open punctuation");
+    } else {
+      System.out.println("has open punctuation");
+    }
+
+    Number n = Integer.valueOf(22);
+    boolean is_double = (n instanceof Double); // valid
+
+    int index = 0;
+    index = Integer.decode("22"); // valid: auto-unboxing conversion
+
+    // auto-unboxing conversion again
+    Map<String, Integer> m = new HashMap<>();
+    if (m.get("hello") == 22) {
+      System.out.println("hello maps to 22");
+    }
+  }
+
+  public static int pow_fast(int base, int expt) throws ArithmeticException {
+    if (expt < 0) {
+      throw new ArithmeticException("Negative base passed to pow");
+    }
+
+    int this_square_pow = base;
+    int result = 1;
+    while (expt > 0) {
+      if ((expt & 1) != 0) {
+        result *= this_square_pow;
+      }
+      expt >>= 1;
+      this_square_pow *= this_square_pow;
+    }
+    return result;
+  }
+
+  /** Return the greatest common divisor of the two arguments. */
+  public static int gcd(int a, int b) {
+
+    // Euclid's method
+    if (b == 0) {
+      return (Math.abs(a));
+    }
+    a = Math.abs(a);
+    b = Math.abs(b);
+    while (b != 0) {
+      int tmp = b;
+      b = a % b;
+      a = tmp;
+    }
+    return a;
+  }
+
+  /** Return the greatest common divisor of the elements of int array a. */
+  public static int gcd(int[] a) {
+    // Euclid's method
+    if (a.length == 0) {
+      return 0;
+    }
+    int result = a[0];
+    for (int i = 1; i < a.length; i++) {
+      result = gcd(a[i], result);
+      if ((result == 1) || (result == 0)) {
+        return result;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Return the gcd (greatest common divisor) of the differences between the elements of int array
+   * a.
+   */
+  public static int gcd_differences(int[] a) {
+    // Euclid's method
+    if (a.length < 2) {
+      return 0;
+    }
+    int result = a[1] - a[0];
+    for (int i = 2; i < a.length; i++) {
+      result = gcd(a[i] - a[i - 1], result);
+      if ((result == 1) || (result == 0)) {
+        return result;
+      }
+    }
+    return result;
+  }
+
+  void compounds() {
+    int res = 0;
+    res += 5;
+    res /= 9;
+  }
+
+  // TODO: enable after boxing is improved in AST creation
+  // void negation() {
+  //   Boolean t = new Boolean(true);
+  //   boolean b = !t;
+  // }
+
+}
diff --git a/checker/tests/interning/Raw3.java b/checker/tests/interning/Raw3.java
new file mode 100644
index 0000000..bd768b7
--- /dev/null
+++ b/checker/tests/interning/Raw3.java
@@ -0,0 +1,84 @@
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.interning.qual.*;
+
+/*
+ * TODO: Make diamond cleverer:
+ *     List<@Interned String> sl = new ArrayList<>();
+ * currently is interpreted as
+ *     List<@Interned String> sl = new ArrayList<String>();
+ * and then the assignment fails.
+ */
+public class Raw3 {
+
+  // We would like behavior that is as similar as possible between the
+  // versions with no raw types and those with raw types.
+
+  // no raw types
+  List<String> foo1() {
+    List<String> sl = new ArrayList<>();
+    return (List<String>) sl;
+  }
+
+  // with raw types
+  List<String> foo2() {
+    List<String> sl = new ArrayList<>();
+    // :: warning: [unchecked] unchecked conversion
+    return (List) sl;
+  }
+
+  // no raw types
+  List<String> foo3() {
+    List<@Interned String> sl = new ArrayList<>();
+    // :: error: (return)
+    return (List<@Interned String>) sl;
+  }
+
+  // with raw types
+  List<String> foo4() {
+    List<@Interned String> sl = new ArrayList<>();
+    // :: warning: [unchecked] unchecked conversion
+    return (List) sl;
+  }
+
+  // no raw types
+  List<@Interned String> foo5() {
+    List<String> sl = new ArrayList<>();
+    // :: error: (return)
+    return (List<String>) sl;
+  }
+
+  // with raw types
+  List<@Interned String> foo6() {
+    List<String> sl = new ArrayList<>();
+    // :: warning: [unchecked] unchecked conversion
+    return (List) sl;
+  }
+
+  class TestList<T> {
+    List<String> bar1() {
+      List<String> sl = new ArrayList<>();
+      return (List<String>) sl;
+    }
+
+    List<String> bar2() {
+      List<String> sl = new ArrayList<>();
+      // :: warning: [unchecked] unchecked conversion
+      return (List) sl;
+    }
+
+    List<String> bar3(List<String> sl) {
+      // :: warning: [unchecked] unchecked conversion
+      return (List) sl;
+    }
+
+    class DuoList<S, T> extends ArrayList<S> {}
+
+    List<String> bar4(List<String> sl) {
+      // This line was previously failing because we couldn't adequately infer the type of DuoList
+      // as a List; it works now, though the future checking of rawtypes may be more strict.
+      // :: warning: [unchecked] unchecked conversion
+      return (DuoList) sl;
+    }
+  }
+}
diff --git a/checker/tests/interning/RecursiveClass.java b/checker/tests/interning/RecursiveClass.java
new file mode 100644
index 0000000..b100994
--- /dev/null
+++ b/checker/tests/interning/RecursiveClass.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.interning.qual.Interned;
+
+public @Interned class RecursiveClass<
+    T extends RecursiveClass<T, F>, F extends RecursiveClass<T, F>> {
+  static @Interned class InternedClass {}
+
+  static class Generic<T extends InternedClass, S extends T> {}
+
+  static @Interned class RecursiveClass2<G extends RecursiveClass2<G>> {}
+}
diff --git a/checker/tests/interning/SequenceAndIndices.java b/checker/tests/interning/SequenceAndIndices.java
new file mode 100644
index 0000000..e5c4e3b
--- /dev/null
+++ b/checker/tests/interning/SequenceAndIndices.java
@@ -0,0 +1,53 @@
+import org.checkerframework.checker.interning.qual.*;
+import org.checkerframework.dataflow.qual.Pure;
+
+/**
+ * Data structure for storing triples of a sequence and start and end indices, to represent a
+ * subsequence. Requires that the sequence be interned. Used for interning the repeated finding of
+ * subsequences on the same sequence.
+ */
+public final class SequenceAndIndices<T extends @Interned Object> {
+  public T seq;
+  public int start;
+  public int end;
+
+  /**
+   * Create a SequenceAndIndices.
+   *
+   * @param seqpar an interned array
+   */
+  public SequenceAndIndices(T seqpar, int start, int end) {
+    this.seq = seqpar;
+    this.start = start;
+    this.end = end;
+    // assert isInterned(seq);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Pure
+  public boolean equals(Object other) {
+    if (other instanceof SequenceAndIndices) {
+      // Warning only with -AcheckCastElementType.
+      // TODO:: warning: (cast.unsafe)
+      return equals((SequenceAndIndices<T>) other); // unchecked
+    } else {
+      return false;
+    }
+  }
+
+  public boolean equals(SequenceAndIndices<T> other) {
+    return (this.seq == other.seq) && this.start == other.start && this.end == other.end;
+  }
+
+  @Pure
+  public int hashCode() {
+    return seq.hashCode() + start * 30 - end * 2;
+  }
+
+  // For debugging
+  @Pure
+  public String toString() {
+    // return "SAI(" + start + "," + end + ") from: " + ArraysMDE.toString(seq);
+    return "SAI(" + start + "," + end + ") from: " + seq;
+  }
+}
diff --git a/checker/tests/interning/StaticInternMethod.java b/checker/tests/interning/StaticInternMethod.java
new file mode 100644
index 0000000..a270256
--- /dev/null
+++ b/checker/tests/interning/StaticInternMethod.java
@@ -0,0 +1,29 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.interning.qual.*;
+
+public class StaticInternMethod {
+
+  private static Map<Integer, @Interned Foo> pool = new HashMap<>();
+
+  @SuppressWarnings("interning")
+  public static @Interned Foo intern(Integer i) {
+    if (pool.containsKey(i)) {
+      return pool.get(i);
+    }
+
+    @Interned Foo f = new @Interned Foo(i);
+    pool.put(i, f);
+    return f;
+  }
+
+  static class Foo {
+    public Foo(Integer i) {}
+  }
+
+  void test() {
+    Integer i = 0;
+    Foo f = new Foo(i);
+    @Interned Foo g = intern(i);
+  }
+}
diff --git a/checker/tests/interning/StringIntern.java b/checker/tests/interning/StringIntern.java
new file mode 100644
index 0000000..29cc943
--- /dev/null
+++ b/checker/tests/interning/StringIntern.java
@@ -0,0 +1,64 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.interning.qual.*;
+
+public class StringIntern {
+
+  // It would be very handy (and would eliminate quite a few annotations)
+  // if any final variable that is initialized to something interned
+  // (essentially, to a literal) were treated as implicitly @Interned.
+  final String finalStringInitializedToInterned = "foo"; // implicitly @Interned
+  final String finalString2 = new String("foo");
+  static final String finalStringStatic1 = "foo"; // implicitly @Interned
+  static final String finalStringStatic2 = new String("foo");
+
+  static class HasFields {
+    static final String finalStringStatic3 = "foo"; // implicitly @Interned
+    static final String finalStringStatic4 = new String("foo");
+  }
+
+  static class Foo {
+    private static Map<Foo, @Interned Foo> pool = new HashMap<>();
+
+    @SuppressWarnings("interning")
+    public @Interned Foo intern() {
+      if (!pool.containsKey(this)) {
+        pool.put(this, (@Interned Foo) this);
+      }
+      return pool.get(this);
+    }
+  }
+
+  // Another example of the "final initialized to interned" rule
+  final Foo finalFooInitializedToInterned = new Foo().intern();
+
+  public void test(@Interned String arg) {
+    String notInternedStr = new String("foo");
+    @Interned String internedStr = notInternedStr.intern();
+    internedStr = finalStringInitializedToInterned; // OK
+    // :: error: (assignment)
+    internedStr = finalString2; // error
+    @Interned Foo internedFoo = finalFooInitializedToInterned; // OK
+    if (arg == finalStringStatic1) {} // OK
+    // :: error: (not.interned)
+    if (arg == finalStringStatic2) {} // error
+    if (arg == HasFields.finalStringStatic3) {} // OK
+    // :: error: (not.interned)
+    if (arg == HasFields.finalStringStatic4) {} // error
+  }
+
+  private @Interned String base;
+  static final String BASE_HASHCODE = "hashcode";
+
+  public void foo() {
+    if (base == BASE_HASHCODE) {}
+  }
+
+  public @Interned String emptyString(boolean b) {
+    if (b) {
+      return "";
+    } else {
+      return ("");
+    }
+  }
+}
diff --git a/checker/tests/interning/Subclass.java b/checker/tests/interning/Subclass.java
new file mode 100644
index 0000000..8fe2a85
--- /dev/null
+++ b/checker/tests/interning/Subclass.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.interning.qual.*;
+import org.checkerframework.dataflow.qual.Pure;
+
+public abstract class Subclass implements Comparable<Subclass> // note non-generic
+{
+
+  @Pure
+  public int compareTo(Subclass other) {
+    return 0;
+  }
+}
diff --git a/checker/tests/interning/SuppressWarningsClass.java b/checker/tests/interning/SuppressWarningsClass.java
new file mode 100644
index 0000000..30a788d
--- /dev/null
+++ b/checker/tests/interning/SuppressWarningsClass.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.interning.qual.*;
+
+@SuppressWarnings("interning")
+public class SuppressWarningsClass {
+
+  public static void myMethod() {
+
+    @Interned String s = new String();
+  }
+}
diff --git a/checker/tests/interning/SuppressWarningsVar.java b/checker/tests/interning/SuppressWarningsVar.java
new file mode 100644
index 0000000..7aa4b6c
--- /dev/null
+++ b/checker/tests/interning/SuppressWarningsVar.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.interning.qual.*;
+
+public class SuppressWarningsVar {
+
+  public static void myMethod() {
+
+    @SuppressWarnings("interning")
+    @Interned String s = new String();
+  }
+}
diff --git a/checker/tests/interning/TVWCSuper.java b/checker/tests/interning/TVWCSuper.java
new file mode 100644
index 0000000..d3a6415
--- /dev/null
+++ b/checker/tests/interning/TVWCSuper.java
@@ -0,0 +1,5 @@
+public class TVWCSuper {
+  class L<T> {}
+
+  public static <T extends Comparable<? super T>> void sort(L<T> t) {}
+}
diff --git a/checker/tests/interning/TestExtSup.java b/checker/tests/interning/TestExtSup.java
new file mode 100644
index 0000000..c05555d
--- /dev/null
+++ b/checker/tests/interning/TestExtSup.java
@@ -0,0 +1,16 @@
+// Test case for issue #237: https://github.com/typetools/checker-framework/issues/237
+
+import java.util.List;
+
+interface A<T> {
+  public abstract int transform(List<? super T> function);
+}
+
+class B implements A<Object> {
+  @Override
+  public int transform(List<Object> function) {
+    return 0;
+  }
+}
+
+public class TestExtSup {}
diff --git a/checker/tests/interning/TestInfer.java b/checker/tests/interning/TestInfer.java
new file mode 100644
index 0000000..e08103d
--- /dev/null
+++ b/checker/tests/interning/TestInfer.java
@@ -0,0 +1,30 @@
+// Test case for issue #238: https://github.com/typetools/checker-framework/issues/238
+
+import java.util.ArrayList;
+import java.util.List;
+
+class TestInfer1 {
+  <T> T getValue(List<T> l) {
+    return l.get(0);
+  }
+
+  void bar(Object o) {}
+
+  void foo() {
+    List<?> ls = new ArrayList<>();
+    bar(getValue(ls));
+  }
+}
+
+class TestInfer2 {
+  <T extends String> T getValue(List<T> l) {
+    return l.get(0);
+  }
+
+  void bar(String o) {}
+
+  void foo() {
+    List<? extends String> ls = new ArrayList<>();
+    bar(getValue(ls));
+  }
+}
diff --git a/checker/tests/interning/ThreadUsesObjectEquals.java b/checker/tests/interning/ThreadUsesObjectEquals.java
new file mode 100644
index 0000000..ee96770
--- /dev/null
+++ b/checker/tests/interning/ThreadUsesObjectEquals.java
@@ -0,0 +1,5 @@
+public class ThreadUsesObjectEquals {
+  boolean p(Thread a, Thread b) {
+    return a == b;
+  }
+}
diff --git a/checker/tests/interning/TypeVarPrimitivesInterning.java b/checker/tests/interning/TypeVarPrimitivesInterning.java
new file mode 100644
index 0000000..497fbbd
--- /dev/null
+++ b/checker/tests/interning/TypeVarPrimitivesInterning.java
@@ -0,0 +1,21 @@
+// Unannotated version in framework/tests/all-systems/TypeVarPrimitives.java
+
+import org.checkerframework.checker.interning.qual.*;
+
+public class TypeVarPrimitivesInterning {
+  <T extends @UnknownInterned Long> void method(T tLong) {
+    long l = tLong;
+  }
+
+  <T extends @UnknownInterned Long & @UnknownInterned Cloneable> void methodIntersection(T tLong) {
+    long l = tLong;
+  }
+
+  <T extends @Interned Long> void method2(T tLong) {
+    long l = tLong;
+  }
+
+  <T extends @Interned Long & @Interned Cloneable> void methodIntersection2(T tLong) {
+    long l = tLong;
+  }
+}
diff --git a/checker/tests/interning/UnboxUninterned.java b/checker/tests/interning/UnboxUninterned.java
new file mode 100644
index 0000000..46490d0
--- /dev/null
+++ b/checker/tests/interning/UnboxUninterned.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.interning.qual.*;
+
+public class UnboxUninterned {
+  void negation() {
+    Boolean t = Boolean.valueOf(true);
+    boolean b1 = !t.booleanValue();
+    boolean b2 = !t;
+
+    Integer x = Integer.valueOf(222222);
+    int i1 = -x.intValue();
+    int i2 = -x;
+  }
+}
diff --git a/checker/tests/interning/UsesObjectEqualsTest.java b/checker/tests/interning/UsesObjectEqualsTest.java
new file mode 100644
index 0000000..8a713ad
--- /dev/null
+++ b/checker/tests/interning/UsesObjectEqualsTest.java
@@ -0,0 +1,94 @@
+import java.util.LinkedList;
+import java.util.prefs.*;
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.checker.interning.qual.UsesObjectEquals;
+
+public class UsesObjectEqualsTest {
+
+  public @UsesObjectEquals class A {
+    public A() {}
+  }
+
+  @UsesObjectEquals
+  class B extends A {}
+
+  // :: error: (overrides.equals)
+  class B2 extends A {
+    @Override
+    public boolean equals(Object o) {
+      return super.equals(o);
+    }
+  }
+
+  @UsesObjectEquals
+  class B3 extends A {
+    @Override
+    public boolean equals(Object o3) {
+      return this == o3;
+    }
+  }
+
+  @UsesObjectEquals
+  class B4 extends A {
+    @Override
+    public boolean equals(Object o4) {
+      return o4 == this;
+    }
+  }
+
+  // changed to inherited, no (superclass.annotated) warning
+  class C extends A {}
+
+  class D {}
+
+  @UsesObjectEquals
+  // :: error: (superclass.notannotated)
+  class E extends D {}
+
+  @UsesObjectEquals
+  // :: error: (overrides.equals)
+  class TestEquals {
+
+    @org.checkerframework.dataflow.qual.Pure
+    public boolean equals(Object o) {
+      return true;
+    }
+  }
+
+  class TestComparison {
+
+    public void comp(@Interned Object o, A a1, A a2) {
+      if (a1 == a2) {
+        System.out.println("one");
+      }
+      if (a1 == o) {
+        System.out.println("two");
+      }
+      if (o == a1) {
+        System.out.println("three");
+      }
+    }
+  }
+
+  @UsesObjectEquals
+  class ExtendsInner1 extends UsesObjectEqualsTest.A {}
+
+  class ExtendsInner2 extends UsesObjectEqualsTest.A {}
+
+  class MyList extends LinkedList {}
+
+  class DoesNotUseObjectEquals {
+    @Override
+    public boolean equals(Object o) {
+      return super.equals(o);
+    }
+  }
+
+  @UsesObjectEquals
+  class SubclassUsesObjectEquals extends DoesNotUseObjectEquals {
+    @Override
+    public boolean equals(Object o) {
+      return this == o;
+    }
+  }
+}
diff --git a/checker/tests/lock-safedefaults/BasicLockTest.java b/checker/tests/lock-safedefaults/BasicLockTest.java
new file mode 100644
index 0000000..0854c47
--- /dev/null
+++ b/checker/tests/lock-safedefaults/BasicLockTest.java
@@ -0,0 +1,85 @@
+import java.util.concurrent.locks.*;
+import org.checkerframework.checker.lock.qual.*;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+public class BasicLockTest {
+  class MyClass {
+    public Object field;
+  }
+
+  MyClass myUnannotatedMethod(MyClass param) {
+    return param;
+  }
+
+  void myUnannotatedMethod2() {}
+
+  @AnnotatedFor("lock")
+  MyClass myAnnotatedMethod(MyClass param) {
+    return param;
+  }
+
+  @AnnotatedFor("lock")
+  void myAnnotatedMethod2() {}
+
+  final @GuardedBy({}) ReentrantLock lockField = new ReentrantLock();
+
+  @GuardedBy("lockField") MyClass m;
+
+  @GuardedBy({}) MyClass o1 = new MyClass(), p1;
+
+  @AnnotatedFor("lock")
+  @MayReleaseLocks
+  void testFields() {
+    // Test in two ways that return values are @GuardedByUnknown.
+    // The first way is more durable as cannot.dereference is tied specifically to
+    // @GuardedByUnknown (and @GuardedByBottom, but it is unlikely to become the default for
+    // return values on unannotated methods).
+    // :: error: (lock.not.held) :: error: (argument)
+    myUnannotatedMethod(o1).field = new Object();
+    // The second way is less durable because the default for fields is currently @GuardedBy({})
+    // but could be changed to @GuardedByUnknown.
+    // :: error: (assignment) :: error: (argument)
+    p1 = myUnannotatedMethod(o1);
+
+    // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks
+    lockField.lock();
+    myAnnotatedMethod2();
+    m.field = new Object();
+    myUnannotatedMethod2();
+    // :: error: (lock.not.held)
+    m.field = new Object();
+  }
+
+  void unannotatedReleaseLock(ReentrantLock lock) {
+    lock.unlock();
+  }
+
+  @AnnotatedFor("lock")
+  @MayReleaseLocks
+  void testLocalVariables() {
+    MyClass o2 = new MyClass(), p2;
+    // :: error: (argument)
+    p2 = myUnannotatedMethod(o2);
+    MyClass o3 = new MyClass();
+    myAnnotatedMethod(o3);
+
+    // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks
+    final @GuardedBy({}) ReentrantLock lock = new ReentrantLock();
+    @GuardedBy("lock") MyClass q = new MyClass();
+    lock.lock();
+    myAnnotatedMethod2();
+    q.field = new Object();
+    // Should behave as @MayReleaseLocks, and *should* reset @LockHeld assumption about local
+    // variable lock.
+    myUnannotatedMethod2();
+    // :: error: (lock.not.held)
+    q.field = new Object();
+    lock.lock();
+    // Should behave as @MayReleaseLocks, and *should* reset @LockHeld assumption about local
+    // variable lock.
+    // :: error: (argument)
+    unannotatedReleaseLock(lock);
+    // :: error: (lock.not.held)
+    q.field = new Object();
+  }
+}
diff --git a/checker/tests/lock/ChapterExamples.java b/checker/tests/lock/ChapterExamples.java
new file mode 100644
index 0000000..7b9dde9
--- /dev/null
+++ b/checker/tests/lock/ChapterExamples.java
@@ -0,0 +1,595 @@
+// This test contains the sample code from the Lock Checker manual chapter modified to fit testing
+// instead of illustrative purposes, and contains other miscellaneous Lock Checker testing.
+
+import java.util.AbstractCollection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.concurrent.locks.ReentrantLock;
+import org.checkerframework.checker.lock.qual.GuardSatisfied;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.GuardedByBottom;
+import org.checkerframework.checker.lock.qual.GuardedByUnknown;
+import org.checkerframework.checker.lock.qual.Holding;
+import org.checkerframework.checker.lock.qual.LockingFree;
+import org.checkerframework.checker.lock.qual.MayReleaseLocks;
+import org.checkerframework.checker.lock.qual.ReleasesNoLocks;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class ChapterExamples {
+  // This code crashed when there was a bug before issue 524 was fixed.
+  // An attempt to take the LUB between 'val' in the store with type 'long'
+  // and 'val' in another store with type 'none' resulted in a crash.
+  private void foo(boolean b, int a) {
+    if (b) {
+      if (a == 0) {
+        boolean val = false;
+      } else if (a == 1) {
+        int val = 0;
+      } else if (a == 2) {
+        long val = 0;
+      } else if (a == 3) {
+      }
+    } else {
+      if (true) {}
+    }
+  }
+
+  private abstract class Values<V> extends AbstractCollection<V> {
+    @SuppressWarnings("method.guarantee.violated") // side effect is only to local iterator
+    public <T> T[] toArray(T[] a) {
+      Collection<V> c = new ArrayList<V>(size());
+      for (Iterator<V> i = iterator(); i.hasNext(); ) {
+        c.add(i.next());
+      }
+      return c.toArray(a);
+    }
+  }
+
+  // @GuardedByBottom, which represents the 'null' literal, is the default lower bound,
+  // so null can be returned in the following two methods:
+  <T> T method1(T t, boolean b) {
+    return b ? null : t;
+  }
+
+  <T> T method2(T t, boolean b) {
+    return null;
+  }
+
+  void bar(@NonNull Object nn1, boolean b) {
+    @NonNull Object nn2 = method1(nn1, b);
+    @NonNull Object nn3 = method2(nn1, b);
+  }
+
+  void bar2(@GuardedByBottom Object bottomParam, boolean b) {
+    @GuardedByUnknown Object refinedToBottom1 = method1(bottomParam, b);
+    @GuardedByUnknown Object refinedToBottom2 = method2(bottomParam, b);
+    @GuardedByBottom Object bottom1 = method1(bottomParam, b);
+    @GuardedByBottom Object bottom2 = method2(bottomParam, b);
+  }
+
+  private static boolean eq(@GuardSatisfied Object o1, @GuardSatisfied Object o2) {
+    return (o1 == null ? o2 == null : o1.equals(o2));
+  }
+
+  public <K extends @GuardedBy({}) Object, V extends @GuardedBy({}) Object> void put(
+      K key, V value) {
+    @SuppressWarnings("unchecked")
+    K k = (K) maskNull(key);
+  }
+
+  class GuardedByUnknownTest<T extends @GuardedByUnknown MyClass> {
+
+    T m;
+
+    void test() {
+      // :: error: (method.invocation)
+      m.method();
+
+      @GuardedByUnknown MyClass local = new @GuardedByUnknown MyClass();
+      // :: error: (lock.not.held)
+      local.field = new Object();
+      // :: error: (method.invocation)
+      local.method();
+
+      // :: error: (lock.not.held)
+      m.field = new Object();
+    }
+  }
+
+  class MyClass {
+    Object field = new Object();
+
+    @LockingFree
+    Object method(@GuardSatisfied MyClass this) {
+      return new Object();
+    }
+
+    @LockingFree
+    public @GuardSatisfied(1) MyClass append(
+        @GuardSatisfied(1) MyClass this, @GuardSatisfied(2) MyClass m) {
+      return this;
+    }
+
+    final Object myLock = new Object();
+
+    void testCallToMethod(@GuardedBy("myLock") MyClass this) {
+      // :: error: (lock.not.held)
+      this.method(); // method()'s receiver is annotated as @GuardSatisfied
+    }
+  }
+
+  @MayReleaseLocks
+  @ReleasesNoLocks
+  // TODO: enable (multiple.sideeffect.annotation)
+  void testMultipleSideEffectAnnotations() {}
+
+  void guardedByItselfOnReceiver(@GuardedBy("<self>") ChapterExamples this) {
+    synchronized (this) { // Tests translation of '<self>' to 'this'
+      // myField = new MyClass();
+      myField.toString();
+      this.myField = new MyClass();
+      this.myField.toString();
+    }
+    // :: error: (lock.not.held)
+    myField = new MyClass();
+    // :: error: (lock.not.held)
+    myField.toString();
+    // :: error: (lock.not.held)
+    this.myField = new MyClass();
+    // :: error: (lock.not.held)
+    this.myField.toString();
+  }
+
+  void guardedByThisOnReceiver(@GuardedBy("this") ChapterExamples this) {
+    // :: error: (lock.not.held)
+    myField = new MyClass();
+    // :: error: (lock.not.held)
+    myField.toString();
+    // :: error: (lock.not.held)
+    this.myField = new MyClass();
+    // :: error: (lock.not.held)
+    this.myField.toString();
+    synchronized (this) {
+      myField = new MyClass();
+      myField.toString();
+      this.myField = new MyClass();
+      this.myField.toString();
+    }
+  }
+
+  void testDereferenceOfReceiverAndParameter(
+      @GuardedBy("lock") ChapterExamples this, @GuardedBy("lock") MyClass m) {
+    // :: error: (lock.not.held)
+    myField = new MyClass();
+    // :: error: (lock.not.held)
+    myField.toString();
+    // :: error: (lock.not.held)
+    this.myField = new MyClass();
+    // :: error: (lock.not.held)
+    this.myField.toString();
+    // :: error: (lock.not.held)
+    m.field = new Object();
+    // :: error: (lock.not.held)
+    m.field.toString();
+    // The following error is due to the fact that you cannot access "this.lock" without first
+    // having acquired "lock".  The right fix in a user scenario would be to not guard "this" with
+    // "this.lock". The current object could instead be guarded by "<self>" or by some other lock
+    // expression that is not one of its fields. We are keeping this test case here to make sure
+    // this scenario issues a warning.
+    // :: error: (lock.not.held)
+    synchronized (lock) {
+      myField = new MyClass();
+      myField.toString();
+      this.myField = new MyClass();
+      this.myField.toString();
+      m.field = new Object();
+      m.field.toString();
+    }
+  }
+
+  @GuardedBy("lock") MyClass myObj = new MyClass();
+
+  @LockingFree
+  @GuardedBy("lock") MyClass myMethodReturningMyObj() {
+    return myObj;
+  }
+
+  ChapterExamples() {
+    lock = new Object();
+  }
+
+  void myMethod8() {
+    // :: error: (lock.not.held)
+    boolean b4 = compare(p1, myMethod());
+
+    // An error is issued indicating that p2 might be dereferenced without
+    // "lock" being held. The method call need not be modified, since
+    // @GuardedBy({}) <: @GuardedByUnknown and @GuardedBy("lock") <: @GuardedByUnknown,
+    // but the lock must be acquired prior to the method call.
+    // :: error: (lock.not.held)
+    boolean b2 = compare(p1, p2);
+    // :: error: (lock.not.held)
+    boolean b3 = compare(p1, this.p2);
+    // :: error: (lock.not.held)
+    boolean b5 = compare(p1, this.myMethod());
+    synchronized (lock) {
+      boolean b6 = compare(p1, p2); // OK
+      boolean b7 = compare(p1, this.p2); // OK
+      boolean b8 = compare(p1, myMethod()); // OK
+      boolean b9 = compare(p1, this.myMethod()); // OK
+    }
+  }
+
+  // Keep in mind, the expression itself may or may not be a
+  // method call. Simple examples of expression.identifier :
+  // myObject.field
+  // myMethod().field
+  // myObject.method()
+  // myMethod().method()
+
+  void myMethod7() {
+    // :: error: (lock.not.held)
+    Object f = myObj.field;
+    // :: error: (lock.not.held)
+    Object f2 = myMethodReturningMyObj().field;
+    // :: error: (lock.not.held)
+    myObj.method(); // method()'s receiver is annotated as @GuardSatisfied
+    // :: error: (lock.not.held)
+    myMethodReturningMyObj().method(); // method()'s receiver is annotated as @GuardSatisfied
+
+    synchronized (lock) {
+      f = myObj.field;
+      f2 = myMethodReturningMyObj().field;
+      myObj.method();
+      myMethodReturningMyObj().method();
+    }
+
+    // :: error: (lock.not.held)
+    myMethodReturningMyObj().field = new Object();
+    // :: error: (lock.not.held)
+    x.field = new Object();
+    synchronized (lock) {
+      myMethod().field = new Object();
+    }
+    synchronized (lock) {
+      x.field = new Object(); // toString is not LockingFree. How annoying.
+    }
+
+    this.x = new MyClass();
+  }
+
+  final Object lock; // Initialized in the constructor
+
+  @GuardedBy("lock") MyClass x = new MyClass();
+
+  @GuardedBy("lock") MyClass y = x; // OK, because dereferences of y will require "lock" to be held.
+  // :: error: (assignment)
+  @GuardedBy({}) MyClass z = x; // ILLEGAL because dereferences of z do not require "lock" to be held.
+
+  @LockingFree
+  @GuardedBy("lock") MyClass myMethod() {
+    return x; // OK because the return type is @GuardedBy("lock")
+  }
+
+  void exampleMethod() {
+    // :: error: (lock.not.held)
+    x.field = new Object(); // ILLEGAL because the lock is not known to be held
+    // :: error: (lock.not.held)
+    y.field = new Object(); // ILLEGAL because the lock is not known to be held
+    // :: error: (lock.not.held)
+    myMethod().field = new Object(); // ILLEGAL because the lock is not known to be held
+    synchronized (lock) {
+      x.field = new Object(); // OK: the lock is known to be held
+      y.field = new Object(); // OK: the lock is known to be held
+      myMethod().field = new Object(); // OK: the lock is known to be held
+    }
+  }
+
+  final MyClass a = new MyClass();
+  final MyClass b = new MyClass();
+
+  @GuardedBy("a") MyClass x5 = new MyClass();
+
+  @GuardedBy({"a", "b"}) MyClass y5 = new MyClass();
+
+  void myMethod2() {
+    // :: error: (assignment)
+    y5 = x5; // ILLEGAL
+  }
+
+  // :: error: (immutable.type.guardedby)
+  @GuardedBy("a") String s = "string";
+
+  @GuardedBy({}) MyClass o1;
+
+  @GuardedBy("lock") MyClass o2;
+
+  @GuardedBy("lock") MyClass o3;
+
+  void someMethod() {
+    o3 = o2; // OK, since o2 and o3 are guarded by exactly the same lock set.
+
+    // :: error: (assignment)
+    o1 = o2; // Assignment type incompatible errors are issued for both assignments, since
+    // :: error: (assignment)
+    o2 = o1; // {"lock"} and {} are not identical sets.
+  }
+
+  @SuppressWarnings("lock:cast.unsafe")
+  void someMethod2() {
+    // A cast can be used if the user knows it is safe to do so.
+    // However, the @SuppressWarnings must be added.
+    o1 = (@GuardedBy({}) MyClass) o2;
+  }
+
+  static final Object myLock = new Object();
+
+  @GuardedBy("ChapterExamples.myLock") MyClass myMethod3() {
+    return new MyClass();
+  }
+
+  // reassignments without holding the lock are OK.
+  @GuardedBy("ChapterExamples.myLock") MyClass x2 = myMethod3();
+
+  @GuardedBy("ChapterExamples.myLock") MyClass y2 = x2;
+
+  void myMethod4() {
+    // :: error: (lock.not.held)
+    x2.field = new Object(); // ILLEGAL because the lock is not held
+    synchronized (ChapterExamples.myLock) {
+      y2.field = new Object(); // OK: the lock is held
+    }
+  }
+
+  void myMethod5(@GuardedBy("ChapterExamples.myLock") MyClass a) {
+    // :: error: (lock.not.held)
+    a.field = new Object(); // ILLEGAL: the lock is not held
+    synchronized (ChapterExamples.myLock) {
+      a.field = new Object(); // OK: the lock is held
+    }
+  }
+
+  @LockingFree
+  boolean compare(@GuardSatisfied MyClass a, @GuardSatisfied MyClass b) {
+    return true;
+  }
+
+  @GuardedBy({}) MyClass p1;
+
+  @GuardedBy("lock") MyClass p2;
+
+  void myMethod6() {
+    // It is the responsibility of callers to 'compare' to acquire the lock.
+    synchronized (lock) {
+      boolean b1 = compare(p1, p2); // OK. No error issued.
+    }
+    // :: error: (lock.not.held)
+    p2.field = new Object();
+    // An error is issued indicating that p2 might be dereferenced without "lock" being held.  The
+    // method call need not be modified, since @GuardedBy({}) <: @GuardedByUnknown and
+    // @GuardedBy("lock") <: @GuardedByUnknown, but the lock must be acquired prior to the method
+    // call.
+    // :: error: (lock.not.held)
+    boolean b2 = compare(p1, p2);
+  }
+
+  void helper1(@GuardedBy("ChapterExamples.myLock") MyClass a) {
+    // :: error: (lock.not.held)
+    a.field = new Object(); // ILLEGAL: the lock is not held
+    synchronized (ChapterExamples.myLock) {
+      a.field = new Object(); // OK: the lock is held
+    }
+  }
+
+  @Holding("ChapterExamples.myLock")
+  @LockingFree
+  void helper2(@GuardedBy("ChapterExamples.myLock") MyClass b) {
+    b.field = new Object(); // OK: the lock is held
+  }
+
+  @LockingFree
+  void helper3(@GuardSatisfied MyClass c) {
+    c.field = new Object(); // OK: the guard is satisfied
+  }
+
+  @LockingFree
+  void helper4(@GuardedBy("ChapterExamples.myLock") MyClass d) {
+    // :: error: (lock.not.held)
+    d.field = new Object(); // ILLEGAL: the lock is not held
+  }
+
+  @ReleasesNoLocks
+  void helper5() {}
+  // No annotation means @ReleasesNoLocks
+  void helper6() {}
+
+  void myMethod2(@GuardedBy("ChapterExamples.myLock") MyClass e) {
+    helper1(e); // OK to pass to another routine without holding the lock.
+    // :: error: (lock.not.held)
+    e.field = new Object(); // ILLEGAL: the lock is not held
+    // :: error: (contracts.precondition)
+    helper2(e);
+    // :: error: (lock.not.held)
+    helper3(e);
+    synchronized (ChapterExamples.myLock) {
+      helper2(e);
+      helper3(e); // OK, since parameter is @GuardSatisfied
+      helper4(e); // OK, but helper4's body still has an error.
+      helper5();
+      helper6();
+      helper2(e); // Can still be called after helper5() and helper6()
+    }
+  }
+
+  private @GuardedBy({}) MyClass myField;
+
+  // TODO: For now, boxed types are treated as primitive types. This may change in the future.
+  @SuppressWarnings("deprecation") // new Integer
+  void unboxing() {
+    int a = 1;
+    // :: error: (immutable.type.guardedby)
+    @GuardedBy("lock") Integer c;
+    synchronized (lock) {
+      // :: error: (assignment)
+      c = a;
+    }
+
+    // :: error: (immutable.type.guardedby)
+    @GuardedBy("lock") Integer b = 1;
+    int d;
+    synchronized (lock) {
+      d = b;
+
+      // Expected, since b cannot be @GuardedBy("lock") since it is a boxed primitive.
+      // :: error: (method.invocation)
+      d = b.intValue(); // The de-sugared version does not issue an error.
+    }
+
+    c = c + b; // Syntactic sugar for c = new Integer(c.intValue() + b.intValue()).
+
+    // Expected, since b and c cannot be @GuardedBy("lock") since they are boxed primitives.
+    // :: error: (method.invocation)
+    c = new Integer(c.intValue() + b.intValue()); // The de-sugared version
+
+    synchronized (lock) {
+      c = c + b; // Syntactic sugar for c = new Integer(c.intValue() + b.intValue()).
+
+      // Expected, since b and c cannot be @GuardedBy("lock") since they are boxed primitives.
+      // :: error: (method.invocation)
+      c = new Integer(c.intValue() + b.intValue()); // The de-sugared version
+    }
+
+    a = b;
+    b = c; // OK
+  }
+
+  /* TODO Re-enable when guarding primitives is supported by the Lock Checker.
+  void boxingUnboxing() {
+    @GuardedBy("lock") int a = 1;
+    @GuardedBy({}) Integer c;
+    synchronized(lock) {
+      c = a;
+    }
+
+    @GuardedBy("lock") Integer b = 1;
+    @GuardedBy({}) int d;
+    synchronized(lock) {
+      // TODO re-enable this error (assignment)
+      d = b; // TODO: This should not result in assignment because 'b' is actually syntactic sugar for b.intValue().
+      d = b.intValue(); // The de-sugared version does not issue an error.
+    }
+
+    // TODO re-enable this error (lock.not.held)
+    c = c + b; // Syntactic sugar for c = new Integer(c.intValue() + b.intValue()), hence 'lock' must be held.
+    // TODO re-enable this error (lock.not.held)
+    c = new Integer(c.intValue() + b.intValue()); // The de-sugared version
+
+    synchronized(lock) {
+      c = c + b; // Syntactic sugar for c = new Integer(c.intValue() + b.intValue()), hence 'lock' must be held.
+      c = new Integer(c.intValue() + b.intValue()); // The de-sugared version
+    }
+
+    // TODO re-enable this error (lock.not.held)
+    a = b; // TODO: This assignment between two reference types should not require a lock to be held.
+  }*/
+
+  final ReentrantLock lock1 = new ReentrantLock();
+  final ReentrantLock lock2 = new ReentrantLock();
+
+  @GuardedBy("lock1") MyClass filename;
+
+  @GuardedBy("lock2") MyClass extension;
+
+  void method0() {
+    // :: error: (lock.not.held) :: error: (lock.not.held)
+    filename = filename.append(extension);
+  }
+
+  void method1() {
+    lock1.lock();
+    // :: error: (lock.not.held)
+    filename = filename.append(extension);
+  }
+
+  void method2() {
+    lock2.lock();
+    // :: error: (lock.not.held)
+    filename = filename.append(extension);
+  }
+
+  void method3() {
+    lock1.lock();
+    lock2.lock();
+    filename = filename.append(extension);
+    filename = filename.append(null);
+    // :: error: (assignment)
+    filename = extension.append(extension);
+    // :: error: (assignment)
+    filename = extension.append(filename);
+  }
+
+  void matchingGSparams(@GuardSatisfied(1) MyClass m1, @GuardSatisfied(1) MyClass m2) {}
+
+  void method4() {
+    lock1.lock();
+    lock2.lock();
+    matchingGSparams(filename, null);
+    matchingGSparams(null, filename);
+  }
+
+  public static boolean deepEquals(Object o1, Object o2) {
+    if (o1 instanceof Object[] && o2 instanceof Object[]) {
+      return Arrays.deepEquals((Object[]) o1, (Object[]) o2);
+    }
+    return false;
+  }
+
+  public static final class Comparer<T extends Comparable<T>> {
+    public boolean compare(T[] a1, T[] a2) {
+      T elt1 = a1[0];
+      T elt2 = a2[0];
+      return elt1.equals(elt2);
+    }
+  }
+
+  public static <T extends @GuardedBy({}) Object> boolean indexOf(T[] a, Object elt) {
+    if (elt.equals(a[0])) {
+      return false;
+    }
+    return true;
+    //    found   : (@org.checkerframework.checker.lock.qual.GuardedBy({}) :: T)[ extends
+    // @GuardedByUnknown @LockPossiblyHeld Object super @GuardedBy({}) @LockHeld Void]
+    //        required: @GuardedBy @LockPossiblyHeld Object
+  }
+
+  private static final Object NULL_KEY = new Object();
+  // A guardsatisfied.location.disallowed error is issued for the cast.
+  @SuppressWarnings({"cast.unsafe", "guardsatisfied.location.disallowed"})
+  private static @GuardSatisfied(1) Object maskNull(@GuardSatisfied(1) Object key) {
+    return (key == null ? (@GuardSatisfied(1) Object) NULL_KEY : key);
+  }
+
+  public void assignmentOfGSWithNoIndex(@GuardSatisfied Object a, @GuardSatisfied Object b) {
+    // :: error: (guardsatisfied.assignment.disallowed)
+    a = b;
+  }
+
+  class Session {
+    @Holding("this")
+    public void kill(@GuardSatisfied Session this) {}
+  }
+
+  class SessionManager {
+    private @GuardedBy("<self>") Session session = new Session();
+
+    private void session_done() {
+      final @GuardedBy("<self>") Session tmp = session;
+      session = null;
+      synchronized (tmp) {
+        tmp.kill();
+      }
+    }
+  }
+}
diff --git a/checker/tests/lock/ClassLiterals.java b/checker/tests/lock/ClassLiterals.java
new file mode 100644
index 0000000..6024614
--- /dev/null
+++ b/checker/tests/lock/ClassLiterals.java
@@ -0,0 +1,32 @@
+package testpackage;
+
+import org.checkerframework.checker.lock.qual.Holding;
+
+public class ClassLiterals {
+  @Holding("ClassLiterals.class")
+  static Object method1() {
+    return new Object();
+  }
+
+  // a class literal may not terminate a JavaExpression string
+  @Holding("ClassLiterals")
+  // :: error: (flowexpr.parse.error)
+  static void method2() {}
+
+  @Holding("ClassLiterals.method1()")
+  static void method3() {}
+
+  @Holding("testpackage.ClassLiterals.class")
+  static void method4() {}
+
+  // a class literal may not terminate a JavaExpression string
+  @Holding("testpackage.ClassLiterals")
+  // :: error: (flowexpr.parse.error)
+  static void method5() {}
+
+  @Holding("testpackage.ClassLiterals.method1()")
+  static void method6() {}
+
+  @Holding("java.lang.Comparable.class")
+  static void method7() {}
+}
diff --git a/checker/tests/lock/ConstructorReturnNPE.java b/checker/tests/lock/ConstructorReturnNPE.java
new file mode 100644
index 0000000..93241a9
--- /dev/null
+++ b/checker/tests/lock/ConstructorReturnNPE.java
@@ -0,0 +1,11 @@
+// Test case for Issue 2229:
+// https://github.com/typetools/checker-framework/issues/2229
+
+import org.checkerframework.checker.lock.qual.*;
+
+// :: error: (expression.unparsable)
+@GuardedBy("lock") class ConstructorReturnNPE {
+  // :: error: (expression.unparsable) :: error: (super.invocation)
+  // :: warning: (inconsistent.constructor.type)
+  @GuardedBy("lock") ConstructorReturnNPE() {}
+}
diff --git a/checker/tests/lock/ConstructorsLock.java b/checker/tests/lock/ConstructorsLock.java
new file mode 100644
index 0000000..1eb02fa
--- /dev/null
+++ b/checker/tests/lock/ConstructorsLock.java
@@ -0,0 +1,47 @@
+import org.checkerframework.checker.lock.qual.*;
+
+// Initializers and constructors are synchronized over 'this'
+// but not over their class's fields
+public @GuardedBy({}) class ConstructorsLock {
+
+  static class MyClass {
+    public Object field;
+  }
+
+  final MyClass unlocked = new MyClass();
+
+  @GuardedBy("this") MyClass guardedThis = new MyClass();
+
+  @GuardedBy("unlocked") MyClass guardedOther = new MyClass();
+
+  static final MyClass unlockedStatic = new MyClass();
+
+  @GuardedBy("unlockedStatic") MyClass nonstaticGuardedByStatic = new MyClass();
+  // :: error: (expression.unparsable)
+  static @GuardedBy("unlocked") MyClass staticGuardedByNonStatic = new MyClass();
+  static @GuardedBy("unlockedStatic") MyClass staticGuardedByStatic = new MyClass();
+
+  Object initializedObject1 = unlocked.field;
+  Object initializedObject2 = guardedThis.field;
+  // :: error: (lock.not.held)
+  Object initializedObject3 = guardedOther.field;
+  // :: error: (expression.unparsable)
+  Object initializedObject4 = staticGuardedByNonStatic.field;
+  // :: error: (lock.not.held)
+  Object initializedObject5 = nonstaticGuardedByStatic.field;
+  // :: error: (lock.not.held)
+  Object initializedObject6 = staticGuardedByStatic.field;
+
+  ConstructorsLock() {
+    unlocked.field.toString();
+    guardedThis.field.toString();
+    // :: error: (lock.not.held)
+    guardedOther.field.toString();
+    // :: error: (expression.unparsable)
+    staticGuardedByNonStatic.field.toString();
+    // :: error: (lock.not.held)
+    nonstaticGuardedByStatic.field.toString();
+    // :: error: (lock.not.held)
+    staticGuardedByStatic.field.toString();
+  }
+}
diff --git a/checker/tests/lock/Fields.java b/checker/tests/lock/Fields.java
new file mode 100644
index 0000000..27c6c44
--- /dev/null
+++ b/checker/tests/lock/Fields.java
@@ -0,0 +1,103 @@
+import org.checkerframework.checker.lock.qual.*;
+
+public class Fields {
+  class MyClass {
+    public Object field;
+  }
+
+  static @GuardedBy("Fields.class") MyClass lockedStatically;
+
+  static synchronized void ssMethod() {
+    lockedStatically.field = new Object();
+  }
+
+  @GuardedBy("lockingObject") MyClass locked;
+
+  final Object lockingObject = new Object();
+
+  synchronized void wrongLock1() {
+    // locking over wrong lock
+    // :: error: (lock.not.held)
+    locked.field = new Object(); // error
+  }
+
+  synchronized void wrongLock2() {
+    // locking over wrong lock
+    synchronized (this) {
+      // :: error: (lock.not.held)
+      locked.field = new Object(); // error
+    }
+  }
+
+  void rightLock() {
+    synchronized (lockingObject) {
+      locked.field = new Object();
+    }
+
+    // accessing after the synchronized object
+    // :: error: (lock.not.held)
+    locked.field = new Object(); // error
+  }
+
+  @Holding("lockingObject")
+  void usingHolding() {
+    locked.field = new Object();
+  }
+
+  @GuardedBy("this") MyClass lockedByThis;
+
+  void wrongLocksb() {
+    // without locking
+    // :: error: (lock.not.held)
+    lockedByThis.field = new Object(); // error
+
+    synchronized (Fields.class) {
+      // :: error: (lock.not.held)
+      lockedByThis.field = new Object(); // error
+    }
+  }
+
+  void rightLockb() {
+    synchronized (this) {
+      lockedByThis.field = new Object();
+    }
+
+    // accessing after the synchronized object
+    // :: error: (lock.not.held)
+    lockedByThis.field = new Object(); // error
+  }
+
+  synchronized void synchronizedMethodb() {
+    lockedByThis.field = new Object();
+  }
+
+  void test() {
+    // synchronized over the right object
+    final Fields a = new Fields();
+    final Fields b = new Fields();
+
+    synchronized (this) {
+      lockedByThis.field = new Object();
+      // :: error: (lock.not.held)
+      a.lockedByThis.field = new Object(); // error
+      // :: error: (lock.not.held)
+      b.lockedByThis.field = new Object(); // error
+    }
+
+    synchronized (a) {
+      // :: error: (lock.not.held)
+      lockedByThis.field = new Object(); // error
+      a.lockedByThis.field = new Object();
+      // :: error: (lock.not.held)
+      b.lockedByThis.field = new Object(); // error
+    }
+
+    synchronized (b) {
+      // :: error: (lock.not.held)
+      lockedByThis.field = new Object(); // error
+      // :: error: (lock.not.held)
+      a.lockedByThis.field = new Object(); // error
+      b.lockedByThis.field = new Object();
+    }
+  }
+}
diff --git a/checker/tests/lock/FlowExpressionsTest.java b/checker/tests/lock/FlowExpressionsTest.java
new file mode 100644
index 0000000..1acb3e2
--- /dev/null
+++ b/checker/tests/lock/FlowExpressionsTest.java
@@ -0,0 +1,31 @@
+import org.checkerframework.checker.lock.qual.*;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class FlowExpressionsTest {
+  class MyClass {
+    public Object field;
+  }
+
+  private final @GuardedBy({"<self>"}) MyClass m = new MyClass();
+  // private @GuardedBy({"nonexistentfield"}) MyClass m2;
+  @Pure
+  private @GuardedBy({"<self>"}) MyClass getm() {
+    return m;
+  }
+
+  public void method() {
+    // :: error: (lock.not.held)
+    getm().field = new Object();
+    // :: error: (lock.not.held)
+    m.field = new Object();
+    // TODO: fix the Lock Checker code so that a flowexpr.parse.error is issued (due to the
+    // guard of "nonexistentfield" on m2)
+    // m2.field = new Object();
+    synchronized (m) {
+      m.field = new Object();
+    }
+    synchronized (getm()) {
+      getm().field = new Object();
+    }
+  }
+}
diff --git a/checker/tests/lock/FullyQualified.java b/checker/tests/lock/FullyQualified.java
new file mode 100644
index 0000000..ba37de4
--- /dev/null
+++ b/checker/tests/lock/FullyQualified.java
@@ -0,0 +1,15 @@
+package com.example.mypackage;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+
+public class FullyQualified {
+  public static final @GuardedBy("<self>") List<Object> all_classes = new ArrayList<>();
+
+  void test() {
+    synchronized (com.example.mypackage.FullyQualified.all_classes) {
+      com.example.mypackage.FullyQualified.all_classes.add(new Object());
+    }
+  }
+}
diff --git a/checker/tests/lock/GuardSatisfiedArray.java b/checker/tests/lock/GuardSatisfiedArray.java
new file mode 100644
index 0000000..f64ef02
--- /dev/null
+++ b/checker/tests/lock/GuardSatisfiedArray.java
@@ -0,0 +1,20 @@
+// @skip-test
+
+// Test case for Issue #917:
+// https://github.com/typetools/checker-framework/issues/917
+
+import java.util.List;
+import org.checkerframework.checker.lock.qual.GuardSatisfied;
+
+public class GuardSatisfiedArray {
+
+  void foo(@GuardSatisfied Object arg1, @GuardSatisfied Object arg2) {}
+
+  void bar(@GuardSatisfied Object[] args) {
+    foo(args[0], args[1]);
+  }
+
+  void baz(@GuardSatisfied List<@GuardSatisfied Object> args) {
+    foo(args.get(0), args.get(1));
+  }
+}
diff --git a/checker/tests/lock/GuardSatisfiedTest.java b/checker/tests/lock/GuardSatisfiedTest.java
new file mode 100644
index 0000000..bb0dfbc
--- /dev/null
+++ b/checker/tests/lock/GuardSatisfiedTest.java
@@ -0,0 +1,293 @@
+import org.checkerframework.checker.lock.qual.GuardSatisfied;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.GuardedByUnknown;
+import org.checkerframework.checker.lock.qual.MayReleaseLocks;
+
+public class GuardSatisfiedTest {
+  void testGuardSatisfiedIndexMatching(
+      @GuardSatisfied GuardSatisfiedTest this,
+      @GuardSatisfied(1) Object o,
+      @GuardSatisfied(2) Object p,
+      @GuardSatisfied Object q) {
+    methodToCall1(o, o);
+    methodToCall1(p, p);
+    // :: error: (guardsatisfied.parameters.must.match)
+    methodToCall1(o, p);
+    // :: error: (guardsatisfied.parameters.must.match)
+    methodToCall1(p, o);
+  }
+
+  // Test defaulting of parameters - they must default to @GuardedBy({}), not @GuardSatisfied
+  void testDefaulting(Object mustDefaultToGuardedByNothing, @GuardSatisfied Object p) {
+    // Must assign in this direction to test the defaulting because assigning a RHS of
+    // @GuardedBy({}) to a LHS @GuardSatisfied is legal.
+    // :: error: (assignment)
+    mustDefaultToGuardedByNothing = p;
+    @GuardedBy({}) Object q = mustDefaultToGuardedByNothing;
+  }
+
+  void testMethodCall(
+      @GuardSatisfied GuardSatisfiedTest this,
+      @GuardedBy("lock1") Object o,
+      @GuardedBy("lock2") Object p,
+      @GuardSatisfied Object q) {
+    // Test matching parameters
+
+    // :: error: (lock.not.held)
+    methodToCall1(o, o);
+    // :: error: (lock.not.held) :: error: (guardsatisfied.parameters.must.match)
+    methodToCall1(o, p);
+    // :: error: (lock.not.held)
+    methodToCall1(p, p);
+    synchronized (lock2) {
+      // :: error: (lock.not.held)
+      methodToCall1(o, o);
+      // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held)
+      methodToCall1(o, p);
+      methodToCall1(p, p);
+      synchronized (lock1) {
+        methodToCall1(o, o);
+        // :: error: (guardsatisfied.parameters.must.match)
+        methodToCall1(o, p);
+        methodToCall1(p, p);
+      }
+    }
+
+    // Test a return type matching a parameter.
+
+    // :: error: (lock.not.held)
+    o = methodToCall2(o);
+    // :: error: (lock.not.held) :: error: (assignment)
+    p = methodToCall2(o);
+    // :: error: (lock.not.held)
+    methodToCall2(o);
+    // :: error: (lock.not.held)
+    methodToCall2(p);
+    synchronized (lock2) {
+      // :: error: (lock.not.held)
+      o = methodToCall2(o);
+      // :: error: (lock.not.held) :: error: (assignment)
+      p = methodToCall2(o);
+      // :: error: (lock.not.held)
+      methodToCall2(o);
+      methodToCall2(p);
+    }
+    synchronized (lock1) {
+      o = methodToCall2(o);
+      // :: error: (assignment)
+      p = methodToCall2(o);
+      methodToCall2(o);
+      // :: error: (lock.not.held)
+      methodToCall2(p);
+    }
+
+    // Test the receiver type matching a parameter
+
+    // Two @GS parameters with no index are incomparable (as is the case for 'this' and 'q').
+    // :: error: (guardsatisfied.parameters.must.match)
+    methodToCall3(q);
+
+    // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held)
+    methodToCall3(p);
+    synchronized (lock1) {
+      // Two @GS parameters with no index are incomparable (as is the case for 'this' and 'q').
+      // :: error: (guardsatisfied.parameters.must.match)
+      methodToCall3(q);
+      // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held)
+      methodToCall3(p);
+      synchronized (lock2) {
+        // Two @GS parameters with no index are incomparable (as is the case for 'this' and 'q').
+        // :: error: (guardsatisfied.parameters.must.match)
+        methodToCall3(q);
+        // :: error: (guardsatisfied.parameters.must.match)
+        methodToCall3(p);
+      }
+    }
+
+    // Test the return type matching the receiver type
+
+    methodToCall4();
+  }
+
+  // Test the return type NOT matching the receiver type
+  void testMethodCall(@GuardedBy("lock1") GuardSatisfiedTest this) {
+    @GuardedBy("lock2") Object g;
+    // :: error: (lock.not.held)
+    methodToCall4();
+    // TODO: lock.not.held is getting swallowed below
+    //  error (assignment) error (lock.not.held)
+    // g = methodToCall4();
+
+    // Separate the above test case into two for now
+    // :: error: (lock.not.held)
+    methodToCall4();
+
+    // The following error is due to the fact that you cannot access "this.lock1" without first
+    // having acquired "lock1".  The right fix in a user scenario would be to not guard "this"
+    // with "this.lock1". The current object could instead be guarded by "<self>" or by some
+    // other lock expression that is not one of its fields. We are keeping this test case here
+    // to make sure this scenario issues a warning.
+    // :: error: (lock.not.held)
+    synchronized (lock1) {
+      // :: error: (assignment)
+      g = methodToCall4();
+    }
+  }
+
+  // :: error: (guardsatisfied.return.must.have.index)
+  @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex1(@GuardSatisfied Object o) {
+    // If the two @GuardSatisfied had an index, this error would not be issued:
+    // :: error: (guardsatisfied.assignment.disallowed)
+    return o;
+  }
+
+  @GuardSatisfied(1) Object testReturnTypesMustMatchAndMustHaveAnIndex2(@GuardSatisfied(1) Object o) {
+    return o;
+  }
+
+  @GuardSatisfied(0) Object testReturnTypesMustMatchAndMustHaveAnIndex3(@GuardSatisfied(0) Object o) {
+    return o;
+  }
+
+  // @GuardSatisfied is equivalent to @GuardSatisfied(-1).
+  // :: error: (guardsatisfied.return.must.have.index)
+  @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex4(@GuardSatisfied(-1) Object o) {
+    // If the two @GuardSatisfied had an index, this error would not be issued:
+    // :: error: (guardsatisfied.assignment.disallowed)
+    return o;
+  }
+
+  @GuardSatisfied(1) Object testReturnTypesMustMatchAndMustHaveAnIndex5(@GuardSatisfied(2) Object o) {
+    // :: error: (return)
+    return o;
+  }
+
+  // :: error: (guardsatisfied.return.must.have.index)
+  @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex6(@GuardSatisfied(2) Object o) {
+    // :: error: (return)
+    return o;
+  }
+
+  void testParamsMustMatch(@GuardSatisfied(1) Object o, @GuardSatisfied(2) Object p) {
+    // :: error: (assignment)
+    o = p;
+  }
+
+  void methodToCall1(
+      @GuardSatisfied GuardSatisfiedTest this,
+      @GuardSatisfied(1) Object o,
+      @GuardSatisfied(1) Object p) {}
+
+  @GuardSatisfied(1) Object methodToCall2(@GuardSatisfied GuardSatisfiedTest this, @GuardSatisfied(1) Object o) {
+    return o;
+  }
+
+  void methodToCall3(@GuardSatisfied(1) GuardSatisfiedTest this, @GuardSatisfied(1) Object o) {}
+
+  @GuardSatisfied(1) Object methodToCall4(@GuardSatisfied(1) GuardSatisfiedTest this) {
+    return this;
+  }
+
+  final Object lock1 = new Object(), lock2 = new Object();
+
+  void testAssignment(@GuardSatisfied Object o) {
+    @GuardedBy({"lock1", "lock2"}) Object p = new Object();
+    // :: error: (lock.not.held)
+    o = p;
+    synchronized (lock1) {
+      // :: error: (lock.not.held)
+      o = p;
+      synchronized (lock2) {
+        o = p;
+      }
+    }
+  }
+
+  // Test disallowed @GuardSatisfied locations.
+  // Whenever a disallowed location can be located within a method return type, receiver or
+  // parameter, test it there, because it's important to check that those are not mistakenly
+  // allowed, since annotations on method return types, receivers and parameters are allowed.  By
+  // definition, fields and non-parameter local variables cannot be in one of these locations on a
+  // method declaration, but other locations can be.
+
+  // :: error: (guardsatisfied.location.disallowed)
+  @GuardSatisfied Object field;
+  // :: error: (guardsatisfied.location.disallowed)
+  void testGuardSatisfiedOnArrayElementAndLocalVariable(@GuardSatisfied Object[] array) {
+    // :: error: (guardsatisfied.location.disallowed)
+    @GuardSatisfied Object local;
+  }
+
+  // :: error: (guardsatisfied.location.disallowed)
+  <T extends @GuardSatisfied Object> T testGuardSatisfiedOnBound(T t) {
+    return t;
+  }
+
+  class MyParameterizedClass1<T extends @GuardedByUnknown Object> {
+    void testGuardSatisfiedOnReceiverOfParameterizedClass(
+        @GuardSatisfied MyParameterizedClass1<T> this) {}
+
+    void testGuardSatisfiedOnArrayOfParameterizedType(
+        MyParameterizedClass1<T> @GuardSatisfied [] array) {}
+
+    void testGuardSatisfiedOnArrayComponentOfParameterizedType(
+        // :: error: (guardsatisfied.location.disallowed)
+        @GuardSatisfied MyParameterizedClass1<T>[] array) {}
+  }
+
+  void testGuardSatisfiedOnWildCardExtendsBound(
+      // :: error: (guardsatisfied.location.disallowed)
+      MyParameterizedClass1<? extends @GuardSatisfied Object> l) {}
+
+  void testGuardSatisfiedOnWildCardSuperBound(
+      // :: error: (guardsatisfied.location.disallowed)
+      MyParameterizedClass1<? super @GuardSatisfied Object> l) {}
+
+  @GuardSatisfied(1) Object testGuardSatisfiedOnParameters(
+      @GuardSatisfied GuardSatisfiedTest this,
+      Object @GuardSatisfied [] array,
+      @GuardSatisfied Object param,
+      @GuardSatisfied(1) Object param2) {
+    return param2;
+  }
+
+  void testGuardSatisfiedOnArray1(Object @GuardSatisfied [][][] array) {}
+  // :: error: (guardsatisfied.location.disallowed)
+  void testGuardSatisfiedOnArray2(@GuardSatisfied Object[][][] array) {}
+  // :: error: (guardsatisfied.location.disallowed)
+  void testGuardSatisfiedOnArray3(Object[] @GuardSatisfied [][] array) {}
+  // :: error: (guardsatisfied.location.disallowed)
+  void testGuardSatisfiedOnArray4(Object[][] @GuardSatisfied [] array) {}
+}
+
+class Foo {
+  @MayReleaseLocks
+  void m1() {}
+
+  @MayReleaseLocks
+  // :: error: (guardsatisfied.with.mayreleaselocks)
+  void m2(@GuardSatisfied Foo f) {
+    // :: error: (method.invocation)
+    f.m1();
+  }
+
+  @MayReleaseLocks
+  void m2_2(Foo f) {
+    f.m1();
+  }
+
+  void m3(@GuardSatisfied Foo f) {
+    // :: error: (method.guarantee.violated) :: error: (method.invocation)
+    f.m1();
+  }
+
+  @MayReleaseLocks
+  void m4(Foo f) {
+    f.m1();
+  }
+
+  @MayReleaseLocks
+  void m5(Foo f) {
+    m3(f);
+  }
+}
diff --git a/checker/tests/lock/GuardedByLocalVariable.java b/checker/tests/lock/GuardedByLocalVariable.java
new file mode 100644
index 0000000..3aec3a6
--- /dev/null
+++ b/checker/tests/lock/GuardedByLocalVariable.java
@@ -0,0 +1,30 @@
+// Test for Checker Framework issue 795
+// https://github.com/typetools/checker-framework/issues/795
+
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.lock.qual.*;
+
+public class GuardedByLocalVariable {
+
+  public static void localVariableShadowing() {
+    // :: error: (expression.unparsable)
+    @GuardedBy("m0") Object kk;
+    {
+      final Map<Object, Integer> m0 = new HashMap<>();
+      @GuardedBy("m0") Object k = "key";
+      // :: error: (assignment)
+      kk = k;
+    }
+    {
+      final Map<Object, Integer> m0 = new HashMap<>();
+      // :: error: (assignment)
+      @GuardedBy("m0") Object k2 = kk;
+    }
+  }
+
+  public static void invalidLocalVariable() {
+    // :: error: (expression.unparsable)
+    @GuardedBy("foobar") Object kk;
+  }
+}
diff --git a/checker/tests/lock/Issue152.java b/checker/tests/lock/Issue152.java
new file mode 100644
index 0000000..c377623
--- /dev/null
+++ b/checker/tests/lock/Issue152.java
@@ -0,0 +1,36 @@
+// Test case for Issue 152:
+// https://github.com/typetools/checker-framework/issues/152
+// @skip-test
+
+import org.checkerframework.checker.lock.qual.GuardedBy;
+
+public class Issue152 {
+  static class SuperClass {
+    protected final Object myLock = new Object();
+
+    private @GuardedBy("myLock") Object locked;
+  }
+
+  static class SubClass extends SuperClass {
+    private final Object myLock = new Object();
+
+    private @GuardedBy("myLock") Object locked;
+
+    void method() {
+      // :: error: (assignment)
+      this.locked = super.locked;
+    }
+  }
+
+  class OuterClass {
+    private final Object lock = new Object();
+
+    @GuardedBy("this.lock") Object field;
+
+    class InnerClass {
+      private final Object lock = new Object();
+      // :: error: (assignment)
+      @GuardedBy("this.lock") Object field2 = field;
+    }
+  }
+}
diff --git a/checker/tests/lock/Issue2163Lock.java b/checker/tests/lock/Issue2163Lock.java
new file mode 100644
index 0000000..5a842bc
--- /dev/null
+++ b/checker/tests/lock/Issue2163Lock.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.lock.qual.*;
+
+public class Issue2163Lock {
+  @GuardedBy Issue2163Lock() {}
+
+  void test() {
+    // :: error: (constructor.invocation) :: error: (guardsatisfied.location.disallowed)
+    new @GuardSatisfied Issue2163Lock();
+  }
+}
diff --git a/checker/tests/lock/Issue523.java b/checker/tests/lock/Issue523.java
new file mode 100644
index 0000000..96f2a06
--- /dev/null
+++ b/checker/tests/lock/Issue523.java
@@ -0,0 +1,23 @@
+// Test case for Issue 523:
+// https://github.com/typetools/checker-framework/issues/523
+
+import org.checkerframework.checker.lock.qual.*;
+
+public class Issue523 {
+  static class MyClass {
+    Object field;
+  }
+
+  static final @GuardedBy("<self>") MyClass m = new MyClass();
+
+  static void foo() {
+    Thread t =
+        new Thread() {
+          public void run() {
+            synchronized (m) {
+              m.field = new Object();
+            }
+          }
+        };
+  }
+}
diff --git a/checker/tests/lock/Issue524.java b/checker/tests/lock/Issue524.java
new file mode 100644
index 0000000..b20f2ed
--- /dev/null
+++ b/checker/tests/lock/Issue524.java
@@ -0,0 +1,38 @@
+// Test case for Issue 524:
+// https://github.com/typetools/checker-framework/issues/524
+
+import java.util.concurrent.locks.ReentrantLock;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+
+// WARNING: this test is nondeterministic, and has already been minimized - if you modify it by
+// removing what appears to be redundant code, it may no longer reproduce the issue or provide
+// coverage for the issue after a fix for the issue has been made.
+
+// About the nondeterminism:
+// The desired behavior, with the fix for issue 524 in place, is for the test to type check without
+// issuing any warnings.
+// (Notice that there are no expected warnings below.)
+// However even without a fix for issue 524 in place, the test sometimes type checks.
+// Unfortunately a test case that always fails to typecheck using a Checker Framework build
+// prior to the fix for issue 524 has not been found.
+public class Issue524 {
+  class MyClass {
+    public Object field;
+  }
+
+  void testLocalVariables() {
+    @GuardedBy({}) ReentrantLock localLock = new ReentrantLock();
+
+    {
+      // :: error: (lock.expression.not.final)
+      @GuardedBy("localLock") MyClass q = new MyClass();
+      localLock.lock();
+      localLock.lock();
+      // Without a fix for issue 524 in place, the error lock.not.held
+      // (unguarded access to field, variable or parameter 'q' guarded by 'localLock') is
+      // issued for the following line.
+      // :: error: (expression.unparsable)
+      q.field.toString();
+    }
+  }
+}
diff --git a/checker/tests/lock/Issue753.java b/checker/tests/lock/Issue753.java
new file mode 100644
index 0000000..1ce9971
--- /dev/null
+++ b/checker/tests/lock/Issue753.java
@@ -0,0 +1,127 @@
+// Test case for Issue 753:
+// https://github.com/typetools/checker-framework/issues/753
+
+import java.util.concurrent.locks.ReentrantLock;
+import org.checkerframework.checker.lock.qual.*;
+import org.checkerframework.dataflow.qual.*;
+
+@SuppressWarnings({
+  "purity",
+  "contracts.precondition",
+  "lock.expression.possibly.not.final"
+}) // Only test parsing
+public class Issue753 extends ReentrantLock {
+  final Issue753 field = new Issue753();
+  final Issue753[] fields = {this, field};
+  final Issue753[][] fieldsArray = {fields};
+  final int zero = 0;
+  final int[] zeros = {0};
+
+  @Pure
+  Issue753 getField(Object param) {
+    return field;
+  }
+
+  @Pure
+  Issue753 getField2() {
+    return field;
+  }
+
+  @Pure
+  Issue753 getField3(String str) {
+    return field;
+  }
+
+  @Pure
+  Issue753[] getFields() {
+    return fields;
+  }
+
+  @Pure
+  Issue753[][] getFieldsArray() {
+    return fieldsArray;
+  }
+
+  @Pure
+  int length(String str) {
+    return str.length();
+  }
+
+  @Pure
+  int[] zeros() {
+    return zeros;
+  }
+
+  void method() {
+    getField(field.field).field.lock();
+    method2();
+    getField(field.field).getField(field.field).field.lock();
+    method3();
+    getField(field.field).getField2().field.lock();
+    method4();
+    getField2().getField2().field.lock();
+    method5();
+    getField2().getField2().lock();
+    method6();
+    getField(getField(getField2()).field).field.lock();
+    method7();
+    this.getField3(")(in string.;))\")(still so.)\"").field.lock();
+    method8();
+    this.fieldsArray[zeros()[0]][zeros()[0]].fields[zeros()[0]].lock();
+    method9();
+    this.fieldsArray[length("[")][length("[")].fields[length("[")].field.lock();
+    method10();
+    this.fieldsArray["[".length()]["[".length()].fields["[".length()].field.lock();
+    method11();
+    this.getFields()[this.zero].field.lock();
+    method12();
+    this.getFieldsArray()[this.getField2().zero][this.zero].field.lock();
+    method13();
+    this.getFields()["][in string.;)]\"][still so.]\"".length()].field.lock();
+    method14();
+    this.fields[(("][in string.;)]\"][still so.]\"").length())].field.lock();
+    method15();
+  }
+
+  @Holding("this.getField(this.field.field).field")
+  void method2() {}
+
+  @Holding("this.getField(this.field.field).getField(this.field.field).field")
+  void method3() {}
+
+  @Holding("this.getField(this.field.field).getField2().field")
+  void method4() {}
+
+  @Holding("this.getField2().getField2().field")
+  void method5() {}
+
+  @Holding("this.getField2().getField2()")
+  void method6() {}
+
+  @Holding("this.getField(this.getField(this.getField2()).field).field")
+  void method7() {}
+
+  @Holding("this.getField3(\")(in string.;))\\\")(still so.)\\\"\").field")
+  void method8() {}
+
+  @Holding("this.fieldsArray[zeros()[0]][zeros()[0]].fields[zeros()[0]]")
+  void method9() {}
+
+  @Holding("this.fieldsArray[length(\"[\")][length(\"[\")].fields[length(\"[\")].field")
+  void method10() {}
+
+  @Holding("this.fieldsArray[\"[\".length()][\"[\".length()].fields[\"[\".length()].field")
+  void method11() {}
+
+  @Holding("this.getFields()[this.zero].field")
+  void method12() {}
+
+  @Holding("this.getFieldsArray()[this.getField2().zero][this.zero].field")
+  void method13() {}
+
+  @Holding("this.getFields()[\"][in string.;)]\\\"][still so.]\\\"\".length()].field")
+  void method14() {}
+
+  @Holding("this.fields[((\"][in string.;)]\\\"][still so.]\\\"\").length())].field")
+  void method15() {}
+}
diff --git a/checker/tests/lock/Issue804.java b/checker/tests/lock/Issue804.java
new file mode 100644
index 0000000..be279ca
--- /dev/null
+++ b/checker/tests/lock/Issue804.java
@@ -0,0 +1,21 @@
+// Test case for Issue 804:
+// https://github.com/typetools/checker-framework/issues/804
+
+import java.util.concurrent.locks.*;
+import org.checkerframework.checker.lock.qual.*;
+
+public class Issue804 extends ReentrantLock {
+  @Holding("this")
+  @MayReleaseLocks
+  void bar() {
+    this.unlock();
+  }
+
+  @Holding("this")
+  @MayReleaseLocks
+  void method() {
+    bar();
+    // :: error: (contracts.precondition)
+    bar();
+  }
+}
diff --git a/checker/tests/lock/Issue805.java b/checker/tests/lock/Issue805.java
new file mode 100644
index 0000000..301212d
--- /dev/null
+++ b/checker/tests/lock/Issue805.java
@@ -0,0 +1,16 @@
+// Test case for Issue 805:
+// https://github.com/typetools/checker-framework/issues/805
+
+import org.checkerframework.checker.lock.qual.Holding;
+
+public class Issue805 {
+  @Holding("this.Issue805.class")
+  // :: error: (flowexpr.parse.error)
+  void method() {}
+
+  @Holding("Issue805.class")
+  void method2() {}
+
+  @Holding("java.lang.String.class")
+  void method3() {}
+}
diff --git a/checker/tests/lock/ItselfExpressionCases.java b/checker/tests/lock/ItselfExpressionCases.java
new file mode 100644
index 0000000..eaebdb4
--- /dev/null
+++ b/checker/tests/lock/ItselfExpressionCases.java
@@ -0,0 +1,140 @@
+import org.checkerframework.checker.lock.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.dataflow.qual.*;
+
+public class ItselfExpressionCases {
+  final Object somelock = new Object();
+
+  private final @GuardedBy({"<self>"}) MyClass m = new MyClass();
+
+  @Pure
+  private @GuardedBy({"<self>"}) MyClass getm() {
+    return m;
+  }
+
+  @Pure
+  private @GuardedBy({"<self>"}) MyClass getm2(@GuardedBy("<self>") ItselfExpressionCases this) {
+    // The following error is due to the precondition of the this.m field dereference not being
+    // satisfied.
+    // :: error: (lock.not.held)
+    return m;
+  }
+
+  @Pure
+  private Object getmfield() {
+    // :: error: (lock.not.held)
+    return getm().field;
+  }
+
+  public void arrayTest(final Object @GuardedBy("<self>") [] a1) {
+    // :: error: (lock.not.held)
+    Object a = a1[0];
+    synchronized (a1) {
+      a = a1[0];
+    }
+  }
+
+  Object @GuardedBy("<self>") [] a2;
+
+  @Pure
+  public Object @GuardedBy("<self>") [] geta2() {
+    return a2;
+  }
+
+  public void arrayTest() {
+    // :: error: (lock.not.held)
+    Object a = geta2()[0];
+    synchronized (geta2()) {
+      a = geta2()[0];
+    }
+  }
+
+  public void testCheckPreconditions(
+      final @GuardedBy("<self>") MyClass o,
+      @GuardSatisfied Object gs,
+      @GuardSatisfied MyClass gsMyClass) {
+    // :: error: (lock.not.held)
+    getm().field = new Object();
+    synchronized (getm()) {
+      getm().field = new Object();
+    }
+
+    // :: error: (lock.not.held)
+    m.field = new Object();
+    synchronized (m) {
+      m.field = new Object();
+    }
+
+    // :: error: (lock.not.held)
+    gs = m.field;
+    synchronized (m) {
+      gs = m.field;
+    }
+
+    // :: error: (lock.not.held)
+    gs = getm().field;
+    synchronized (getm()) {
+      gs = getm().field;
+    }
+
+    // :: error: (lock.not.held)
+    gsMyClass = getm();
+    synchronized (getm()) {
+      gsMyClass = getm();
+    }
+
+    // :: error: (lock.not.held) :: error: (contracts.precondition)
+    o.foo();
+    synchronized (o) {
+      // :: error: (contracts.precondition)
+      o.foo();
+      synchronized (somelock) {
+        // o.foo() requires o.somelock is held, not this.somelock.
+        // :: error: (contracts.precondition)
+        o.foo();
+      }
+    }
+
+    // :: error: (lock.not.held)
+    o.foo2();
+    synchronized (o) {
+      o.foo2();
+    }
+  }
+
+  class MyClass {
+    Object field = new Object();
+
+    @Holding("somelock")
+    void foo(@GuardSatisfied MyClass this) {}
+
+    void foo2(@GuardSatisfied MyClass this) {}
+
+    void method(@GuardedBy("<self>") MyClass this) {
+      // :: error: (lock.not.held) :: error: (contracts.precondition)
+      this.foo();
+      // :: error: (lock.not.held):: error: (contracts.precondition)
+      foo();
+      // :: error: (lock.not.held)
+      synchronized (somelock) {
+        // :: error: (lock.not.held)
+        this.foo();
+        // :: error: (lock.not.held)
+        foo();
+        synchronized (this) {
+          this.foo();
+          foo();
+        }
+      }
+
+      // :: error: (lock.not.held)
+      this.foo2();
+      // :: error: (lock.not.held)
+      foo2();
+      synchronized (this) {
+        this.foo2();
+        foo2();
+      }
+    }
+  }
+}
diff --git a/checker/tests/lock/JCIPAnnotations.java b/checker/tests/lock/JCIPAnnotations.java
new file mode 100644
index 0000000..faed7d9
--- /dev/null
+++ b/checker/tests/lock/JCIPAnnotations.java
@@ -0,0 +1,114 @@
+import net.jcip.annotations.GuardedBy;
+import org.checkerframework.checker.lock.qual.GuardSatisfied;
+import org.checkerframework.checker.lock.qual.Holding;
+import org.checkerframework.checker.lock.qual.LockingFree;
+
+// Smoke test for supporting JCIP and Javax annotations.
+// Note that JCIP and Javax @GuardedBy can only be written on fields and methods.
+public class JCIPAnnotations {
+  class MyClass {
+    Object field;
+
+    @LockingFree
+    void methodWithUnguardedReceiver() {}
+
+    @LockingFree
+    void methodWithGuardedReceiver(
+        @org.checkerframework.checker.lock.qual.GuardedBy("lock") MyClass this) {}
+
+    @LockingFree
+    void methodWithGuardSatisfiedReceiver(@GuardSatisfied MyClass this) {}
+  }
+
+  final Object lock = new Object();
+
+  @GuardedBy("lock") MyClass jcipGuardedField;
+
+  @javax.annotation.concurrent.GuardedBy("lock") MyClass javaxGuardedField;
+
+  // Tests that Javax and JCIP @GuardedBy(...) typecheck against the Lock Checker @GuardedBy on a
+  // receiver.
+  void testReceivers() {
+    // :: error: (method.invocation)
+    jcipGuardedField.methodWithUnguardedReceiver();
+    // :: error: (method.invocation)
+    jcipGuardedField.methodWithGuardedReceiver();
+    // :: error: (lock.not.held)
+    jcipGuardedField.methodWithGuardSatisfiedReceiver();
+    // :: error: (method.invocation)
+    javaxGuardedField.methodWithUnguardedReceiver();
+    // :: error: (method.invocation)
+    javaxGuardedField.methodWithGuardedReceiver();
+    // :: error: (lock.not.held)
+    javaxGuardedField.methodWithGuardSatisfiedReceiver();
+  }
+
+  void testDereferences() {
+    // :: error: (lock.not.held)
+    this.jcipGuardedField.field.toString();
+    // :: error: (lock.not.held)
+    this.javaxGuardedField.field.toString();
+    synchronized (lock) {
+      this.jcipGuardedField.field.toString();
+      this.javaxGuardedField.field.toString();
+    }
+  }
+
+  @GuardedBy("lock")
+  void testGuardedByAsHolding() {
+    this.jcipGuardedField.field.toString();
+    this.javaxGuardedField.field.toString();
+    jcipGuardedField.field.toString();
+    javaxGuardedField.field.toString();
+  }
+
+  @GuardedBy("lock") Object testGuardedByAsHolding2(
+      @org.checkerframework.checker.lock.qual.GuardedBy({}) Object param) {
+    testGuardedByAsHolding();
+    // Test that the JCIP GuardedBy applies to the method but not the return type.
+    return param;
+  }
+
+  void testGuardedByAsHolding3() {
+    synchronized (lock) {
+      testGuardedByAsHolding();
+    }
+    // :: error: (contracts.precondition)
+    testGuardedByAsHolding();
+  }
+
+  @Holding("lock")
+  @GuardedBy("lock")
+  // :: error: (multiple.lock.precondition.annotations)
+  void testMultipleMethodAnnotations1() {}
+
+  @Holding("lock")
+  @javax.annotation.concurrent.GuardedBy("lock")
+  // :: error: (multiple.lock.precondition.annotations)
+  void testMultipleMethodAnnotations2() {}
+
+  @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock")
+  // :: error: (multiple.lock.precondition.annotations)
+  void testMultipleMethodAnnotations3() {}
+
+  @Holding("lock")
+  @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock")
+  // :: error: (multiple.lock.precondition.annotations)
+  void testMultipleMethodAnnotations4() {}
+
+  @GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock")
+  // :: error: (multiple.guardedby.annotations)
+  Object fieldWithMultipleGuardedByAnnotations1;
+
+  @javax.annotation.concurrent.GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock")
+  // :: error: (multiple.guardedby.annotations)
+  Object fieldWithMultipleGuardedByAnnotations2;
+
+  @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock")
+  // :: error: (multiple.guardedby.annotations)
+  Object fieldWithMultipleGuardedByAnnotations3;
+
+  @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock")
+  // :: error: (multiple.guardedby.annotations)
+  Object fieldWithMultipleGuardedByAnnotations4;
+}
diff --git a/checker/tests/lock/LockEffectAnnotations.java b/checker/tests/lock/LockEffectAnnotations.java
new file mode 100644
index 0000000..9785821
--- /dev/null
+++ b/checker/tests/lock/LockEffectAnnotations.java
@@ -0,0 +1,152 @@
+import java.util.concurrent.locks.ReentrantLock;
+import org.checkerframework.checker.lock.qual.GuardSatisfied;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.GuardedByBottom;
+import org.checkerframework.checker.lock.qual.GuardedByUnknown;
+import org.checkerframework.checker.lock.qual.LockingFree;
+import org.checkerframework.checker.lock.qual.MayReleaseLocks;
+import org.checkerframework.checker.lock.qual.ReleasesNoLocks;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+
+public class LockEffectAnnotations {
+  class MyClass {
+    Object field = new Object();
+  }
+
+  private @GuardedBy({}) MyClass myField;
+
+  private final ReentrantLock myLock2 = new ReentrantLock();
+  private @GuardedBy("myLock2") MyClass x3;
+
+  // This method does not use locks or synchronization but cannot
+  // be annotated as @SideEffectFree since it alters myField.
+  @LockingFree
+  void myMethod5() {
+    myField = new MyClass();
+  }
+
+  @SideEffectFree
+  int mySideEffectFreeMethod() {
+    return 0;
+  }
+
+  @MayReleaseLocks
+  void myUnlockingMethod() {
+    myLock2.unlock();
+  }
+
+  @MayReleaseLocks
+  void myReleaseLocksEmptyMethod() {}
+
+  @MayReleaseLocks
+  // :: error: (guardsatisfied.with.mayreleaselocks)
+  void methodGuardSatisfiedReceiver(@GuardSatisfied LockEffectAnnotations this) {}
+
+  @MayReleaseLocks
+  // :: error: (guardsatisfied.with.mayreleaselocks)
+  void methodGuardSatisfiedParameter(@GuardSatisfied Object o) {}
+
+  @MayReleaseLocks
+  void myOtherMethod() {
+    if (myLock2.tryLock()) {
+      x3.field = new Object(); // OK: the lock is held
+      myMethod5();
+      x3.field = new Object(); // OK: the lock is still held since myMethod is locking-free
+      mySideEffectFreeMethod();
+      x3.field = new Object(); // OK: the lock is still held since mySideEffectFreeMethod is
+      // side-effect-free
+      myUnlockingMethod();
+      // :: error: (lock.not.held)
+      x3.field = new Object(); // ILLEGAL: myLockingMethod is not locking-free
+    }
+    if (myLock2.tryLock()) {
+      x3.field = new Object(); // OK: the lock is held
+      myReleaseLocksEmptyMethod();
+      // :: error: (lock.not.held)
+      x3.field = new Object(); // ILLEGAL: even though myUnannotatedEmptyMethod is empty, since
+      // myReleaseLocksEmptyMethod() is annotated with @MayReleaseLocks and the Lock Checker
+      // no longer knows the state of the lock.
+      if (myLock2.isHeldByCurrentThread()) {
+        x3.field = new Object(); // OK: the lock is known to be held
+      }
+    }
+  }
+
+  @ReleasesNoLocks
+  void innerClassTest() {
+    class InnerClass {
+      @MayReleaseLocks
+      void innerClassMethod() {}
+    }
+
+    InnerClass ic = new InnerClass();
+    // :: error: (method.guarantee.violated)
+    ic.innerClassMethod();
+  }
+
+  @MayReleaseLocks
+  synchronized void mayReleaseLocksSynchronizedMethod() {}
+
+  @ReleasesNoLocks
+  synchronized void releasesNoLocksSynchronizedMethod() {}
+
+  @LockingFree
+  // :: error: (lockingfree.synchronized.method)
+  synchronized void lockingFreeSynchronizedMethod() {}
+
+  @SideEffectFree
+  // :: error: (lockingfree.synchronized.method)
+  synchronized void sideEffectFreeSynchronizedMethod() {}
+
+  @Pure
+  // :: error: (lockingfree.synchronized.method)
+  synchronized void pureSynchronizedMethod() {}
+
+  @MayReleaseLocks
+  void mayReleaseLocksMethodWithSynchronizedBlock() {
+    synchronized (this) {
+    }
+  }
+
+  @ReleasesNoLocks
+  void releasesNoLocksMethodWithSynchronizedBlock() {
+    synchronized (this) {
+    }
+  }
+
+  @LockingFree
+  void lockingFreeMethodWithSynchronizedBlock() {
+    // :: error: (synchronized.block.in.lockingfree.method)
+    synchronized (this) {
+    }
+  }
+
+  @SideEffectFree
+  void sideEffectFreeMethodWithSynchronizedBlock() {
+    // :: error: (synchronized.block.in.lockingfree.method)
+    synchronized (this) {
+    }
+  }
+
+  @Pure
+  void pureMethodWithSynchronizedBlock() {
+    // :: error: (synchronized.block.in.lockingfree.method)
+    synchronized (this) {
+    }
+  }
+
+  @GuardedByUnknown class MyClass2 {}
+
+  // :: error: (expression.unparsable) :: error: (super.invocation)
+  // :: warning: (inconsistent.constructor.type)
+  @GuardedBy("lock") class MyClass3 {}
+
+  @GuardedBy({}) class MyClass4 {}
+  // :: error: (guardsatisfied.location.disallowed) :: error: (super.invocation)
+  // :: warning: (inconsistent.constructor.type)
+  @GuardSatisfied class MyClass5 {}
+
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  @GuardedByBottom class MyClass6 {}
+}
diff --git a/checker/tests/lock/LockExpressionIsFinal.java b/checker/tests/lock/LockExpressionIsFinal.java
new file mode 100644
index 0000000..c8bd1be
--- /dev/null
+++ b/checker/tests/lock/LockExpressionIsFinal.java
@@ -0,0 +1,322 @@
+import java.util.concurrent.locks.ReentrantLock;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.GuardedByUnknown;
+import org.checkerframework.checker.lock.qual.MayReleaseLocks;
+import org.checkerframework.dataflow.qual.Deterministic;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class LockExpressionIsFinal {
+
+  class MyClass {
+    Object field = new Object();
+  }
+
+  class C1 {
+    final C1 field = new C1(); // Infinite loop. This code is not meant to be executed, only type
+    // checked.
+    C1 field2;
+
+    @Deterministic
+    C1 getFieldDeterministic() {
+      return field;
+    }
+
+    @Pure
+    C1 getFieldPure(Object param1, Object param2) {
+      return field;
+    }
+
+    @Pure
+    C1 getFieldPure2() {
+      return field;
+    }
+
+    C1 getField() {
+      return field;
+    }
+  }
+
+  final C1 c1 = new C1();
+
+  // Analogous to testExplicitLockExpressionIsFinal and testGuardedByExpressionIsFinal, but for
+  // monitor locks acquired in synchronized blocks.
+  void testSynchronizedExpressionIsFinal(boolean b) {
+    synchronized (c1) {
+    }
+
+    Object o1 = new Object(); // o1 is effectively final - it is never reassigned
+    Object o2 = new Object(); // o2 is reassigned later - it is not effectively final
+    synchronized (o1) {
+    }
+    // :: error: (lock.expression.not.final)
+    synchronized (o2) {
+    }
+
+    o2 = new Object(); // Reassignment that makes o2 not have been effectively final earlier.
+
+    // Tests that package names are considered final.
+    synchronized (java.lang.String.class) {
+    }
+
+    // Test a tree that is not supported by LockVisitor.ensureExpressionIsEffectivelyFinal
+    // :: error: (lock.expression.possibly.not.final)
+    synchronized (c1.getFieldPure(b ? c1 : o1, c1)) {
+    }
+
+    synchronized (
+        c1.field.field.field.getFieldPure(
+                c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field))
+            .field) {
+    }
+
+    // The following negative test cases are the same as the one above but with one modification
+    // in each.
+
+    synchronized (
+        // :: error: (lock.expression.not.final)
+        c1.field.field2.field.getFieldPure(
+                c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field))
+            .field) {
+    }
+    synchronized (
+        c1.field.field.field.getFieldPure(
+                // :: error: (lock.expression.not.final)
+                c1.field, c1.getField().getFieldPure(c1, c1.field))
+            .field) {
+    }
+  }
+
+  class C2 extends ReentrantLock {
+    final C2 field =
+        new C2(); // Infinite loop. The code is not meant to be executed, only type-checked.
+    C2 field2;
+
+    @Deterministic
+    C2 getFieldDeterministic() {
+      return field;
+    }
+
+    @Pure
+    C2 getFieldPure(Object param1, Object param2) {
+      return field;
+    }
+
+    C2 getField() {
+      return field;
+    }
+  }
+
+  final C2 c2 = new C2();
+
+  // Analogous to testSynchronizedExpressionIsFinal and testGuardedByExpressionIsFinal, but for
+  // explicit locks.
+  @MayReleaseLocks
+  void testExplicitLockExpressionIsFinal(boolean b) {
+    c2.lock();
+
+    ReentrantLock rl1 = new ReentrantLock(); // rl1 is effectively final - it is never reassigned
+    ReentrantLock rl2 =
+        new ReentrantLock(); // rl2 is reassigned later - it is not effectively final
+    rl1.lock();
+    rl1.unlock();
+    // :: error: (lock.expression.not.final)
+    rl2.lock();
+    // :: error: (lock.expression.not.final)
+    rl2.unlock();
+
+    rl2 = new ReentrantLock(); // Reassignment that makes rl2 not have been effectively final
+    // earlier.
+
+    // Test a tree that is not supported by LockVisitor.ensureExpressionIsEffectivelyFinal
+    // :: error: (lock.expression.possibly.not.final)
+    c2.getFieldPure(b ? c2 : rl1, c2).lock();
+    // :: error: (lock.expression.possibly.not.final)
+    c2.getFieldPure(b ? c2 : rl1, c2).unlock();
+
+    c2.field
+        .field
+        .field
+        .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field))
+        .field
+        .lock();
+    c2.field
+        .field
+        .field
+        .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field))
+        .field
+        .unlock();
+
+    // The following negative test cases are the same as the one above but with one modification
+    // in each.
+
+    c2.field
+        // :: error: (lock.expression.not.final)
+        .field2
+        .field
+        .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field))
+        .field
+        .lock();
+    c2.field
+        // :: error: (lock.expression.not.final)
+        .field2
+        .field
+        .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field))
+        .field
+        .unlock();
+
+    c2.field
+        .field
+        .field
+        // :: error: (lock.expression.not.final)
+        .getFieldPure(c2.field, c2.getField().getFieldPure(c2, c2.field))
+        .field
+        .lock();
+    c2.field
+        .field
+        .field
+        // :: error: (lock.expression.not.final)
+        .getFieldPure(c2.field, c2.getField().getFieldPure(c2, c2.field))
+        .field
+        .unlock();
+  }
+
+  // Analogous to testSynchronizedExpressionIsFinal and testExplicitLockExpressionIsFinal, but for
+  // expressions in @GuardedBy annotations.
+  void testGuardedByExpressionIsFinal() {
+    @GuardedBy("c1") Object guarded1;
+
+    final Object o1 = new Object();
+    Object o2 = new Object();
+    // reassign so it's not effectively final
+    o2 = new Object();
+
+    @GuardedBy("o1") Object guarded2 = new Object();
+    // :: error: (lock.expression.not.final)
+    @GuardedBy("o2") Object guarded3 = new Object();
+
+    // Test expressions that are not supported by LockVisitor.ensureExpressionIsEffectivelyFinal
+    @GuardedBy("java.lang.String.class") Object guarded4;
+    // :: error: (expression.unparsable)
+    @GuardedBy("c1.getFieldPure(b ? c1 : o1, c1)") Object guarded5;
+
+    @GuardedBy(
+        "c1.field.field.field.getFieldPure"
+            + "(c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)).field")
+    Object guarded6;
+
+    @GuardedBy("c1.field.field.field.getFieldPure2().getFieldDeterministic().field") Object guarded7;
+
+    // The following negative test cases are the same as the one above but with one modification
+    // in each.
+
+    // :: error: (lock.expression.not.final)
+    @GuardedBy("c1.field.field2.field.getFieldPure2().getFieldDeterministic().field") Object guarded8;
+    // :: error: (lock.expression.not.final)
+    @GuardedBy("c1.field.field.field.getField().getFieldDeterministic().field") Object guarded9;
+
+    // Additional test cases to test that method parameters (in this case the parameters to
+    // getFieldPure) are parsed.
+    @GuardedBy("c1.field.field.field.getFieldPure(c1, c1).getFieldDeterministic().field") Object guarded10;
+    @GuardedBy("c1.field.field.field.getFieldPure(c1, o1).getFieldDeterministic().field") Object guarded11;
+    // :: error: (lock.expression.not.final)
+    @GuardedBy("c1.field.field.field.getFieldPure(c1, o2).getFieldDeterministic().field") Object guarded12;
+
+    // Test that @GuardedBy annotations on various tree kinds inside a method are visited
+
+    Object guarded13 = (@GuardedBy("o1") Object) guarded2;
+    // :: error: (lock.expression.not.final)
+    Object guarded14 = (@GuardedBy("o2") Object) guarded3;
+
+    Object guarded15[] = new @GuardedBy("o1") MyClass[3];
+    // :: error: (lock.expression.not.final)
+    Object guarded16[] = new @GuardedBy("o2") MyClass[3];
+
+    // Tests that the location of the @GB annotation inside a VariableTree does not matter (i.e.
+    // it does not need to be the leftmost subtree).
+    Object guarded17 @GuardedBy("o1") [];
+    // :: error: (lock.expression.not.final)
+    Object guarded18 @GuardedBy("o2") [];
+
+    @GuardedBy("o1") Object guarded19[];
+    // :: error: (lock.expression.not.final)
+    @GuardedBy("o2") Object guarded20[];
+
+    MyParameterizedClass1<@GuardedBy("o1") Object> m1;
+    // :: error: (lock.expression.not.final)
+    MyParameterizedClass1<@GuardedBy("o2") Object> m2;
+
+    boolean b = c1 instanceof @GuardedBy("o1") Object;
+    // instanceof expression have not effect on the type.
+    // // :: error: (lock.expression.not.final)
+    b = c1 instanceof @GuardedBy("o2") Object;
+
+    // Additional tests just outside of this method below:
+  }
+
+  // Test that @GuardedBy annotations on various tree kinds outside a method are visited
+
+  // Test that @GuardedBy annotations on method return types are visited. No need to test method
+  // receivers and parameters as they are covered by tests above that visit VariableTree.
+
+  final Object finalField = new Object();
+  Object nonFinalField = new Object();
+
+  @GuardedBy("finalField") Object testGuardedByExprIsFinal1() {
+    return null;
+  }
+
+  // :: error: (lock.expression.not.final)
+  @GuardedBy("nonFinalField") Object testGuardedByExprIsFinal2() {
+    return null;
+  }
+
+  <T extends @GuardedBy("finalField") Object> T myMethodThatReturnsT_1(T t) {
+    return t;
+  }
+
+  // :: error: (lock.expression.not.final)
+  <T extends @GuardedBy("nonFinalField") Object> T myMethodThatReturnsT_2(T t) {
+    return t;
+  }
+
+  class MyParameterizedClass1<T extends @GuardedByUnknown Object> {}
+
+  MyParameterizedClass1<? super @GuardedBy("finalField") Object> m1;
+  // :: error: (lock.expression.not.final)
+  MyParameterizedClass1<? super @GuardedBy("nonFinalField") Object> m2;
+
+  MyParameterizedClass1<? extends @GuardedBy("finalField") Object> m3;
+  // :: error: (lock.expression.not.final)
+  MyParameterizedClass1<? extends @GuardedBy("nonFinalField") Object> m4;
+
+  class MyClassContainingALock {
+    final ReentrantLock finalLock = new ReentrantLock();
+    ReentrantLock nonFinalLock = new ReentrantLock();
+    Object field;
+  }
+
+  void testItselfFinalLock() {
+    final @GuardedBy("<self>.finalLock") MyClassContainingALock m = new MyClassContainingALock();
+    // :: error: (lock.not.held)
+    m.field = new Object();
+    // Ignore this error: it is expected that an error will be issued for dereferencing 'm' in
+    // order to take the 'm.finalLock' lock.  Typically, the Lock Checker does not support an
+    // object being guarded by one of its fields, but this is sometimes done in user code with a
+    // ReentrantLock field guarding its containing object. This unfortunately makes it a bit
+    // difficult for users since they have to add a @SuppressWarnings for this call while still
+    // making sure that warnings for other dereferences are not suppressed.
+    // :: error: (lock.not.held)
+    m.finalLock.lock();
+    m.field = new Object();
+  }
+
+  void testItselfNonFinalLock() {
+    final @GuardedBy("<self>.nonFinalLock") MyClassContainingALock m = new MyClassContainingALock();
+    // ::error: (lock.not.held) :: error: (lock.expression.not.final)
+    m.field = new Object();
+    // ::error: (lock.not.held) :: error: (lock.expression.not.final)
+    m.nonFinalLock.lock();
+    // :: error: (lock.expression.not.final)
+    m.field = new Object();
+  }
+}
diff --git a/checker/tests/lock/LockInterfaceTest.java b/checker/tests/lock/LockInterfaceTest.java
new file mode 100644
index 0000000..434bdf6
--- /dev/null
+++ b/checker/tests/lock/LockInterfaceTest.java
@@ -0,0 +1,55 @@
+// Test of use of Lock interface
+
+import java.util.Date;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import org.checkerframework.checker.lock.qual.Holding;
+import org.checkerframework.checker.lock.qual.MayReleaseLocks;
+import org.checkerframework.checker.lock.qual.ReleasesNoLocks;
+
+public class LockInterfaceTest {
+
+  static final Lock myStaticLock = new ReentrantLock(true);
+
+  private static final Date x = new Date((long) (System.currentTimeMillis() * Math.random()));
+
+  @Holding("myStaticLock")
+  @ReleasesNoLocks
+  static void method4() {
+    System.out.println(x);
+  }
+
+  @Holding("LockInterfaceTest.myStaticLock")
+  @ReleasesNoLocks
+  static void method5() {
+    System.out.println(x);
+  }
+
+  @MayReleaseLocks
+  public static void test1() {
+    LockInterfaceTest.myStaticLock.lock();
+    method4();
+    LockInterfaceTest.myStaticLock.unlock();
+  }
+
+  @MayReleaseLocks
+  public static void test2() {
+    LockInterfaceTest.myStaticLock.lock();
+    method5();
+    LockInterfaceTest.myStaticLock.unlock();
+  }
+
+  @MayReleaseLocks
+  public static void test3() {
+    myStaticLock.lock();
+    method4();
+    myStaticLock.unlock();
+  }
+
+  @MayReleaseLocks
+  public static void test4() {
+    myStaticLock.lock();
+    method5();
+    myStaticLock.unlock();
+  }
+}
diff --git a/checker/tests/lock/Methods.java b/checker/tests/lock/Methods.java
new file mode 100644
index 0000000..52c1b64
--- /dev/null
+++ b/checker/tests/lock/Methods.java
@@ -0,0 +1,56 @@
+import org.checkerframework.checker.lock.qual.*;
+
+public class Methods {
+
+  final Object lock = new Object();
+
+  @Holding("lock")
+  void lockedByLock() {}
+
+  @Holding("this")
+  void lockedByThis() {}
+
+  // unguarded calls
+  void unguardedCalls() {
+    // :: error: (contracts.precondition)
+    lockedByLock(); // error
+    // :: error: (contracts.precondition)
+    lockedByThis(); // error
+  }
+
+  @Holding("lock")
+  void usingHolding1() {
+    lockedByLock();
+    // :: error: (contracts.precondition)
+    lockedByThis(); // error
+  }
+
+  @Holding("this")
+  void usingHolding2() {
+    // :: error: (contracts.precondition)
+    lockedByLock(); // error
+    lockedByThis();
+  }
+
+  void usingSynchronization1() {
+    synchronized (lock) {
+      lockedByLock();
+      // :: error: (contracts.precondition)
+      lockedByThis(); // error
+    }
+  }
+
+  void usingSynchronization2() {
+    synchronized (this) {
+      // :: error: (contracts.precondition)
+      lockedByLock(); // error
+      lockedByThis();
+    }
+  }
+
+  synchronized void usingMethodModifier() {
+    // :: error: (contracts.precondition)
+    lockedByLock(); // error
+    lockedByThis();
+  }
+}
diff --git a/checker/tests/lock/NestedSynchronizedBlocks.java b/checker/tests/lock/NestedSynchronizedBlocks.java
new file mode 100644
index 0000000..ccbf344
--- /dev/null
+++ b/checker/tests/lock/NestedSynchronizedBlocks.java
@@ -0,0 +1,42 @@
+import org.checkerframework.checker.lock.qual.GuardedBy;
+
+public class NestedSynchronizedBlocks {
+  class MyClass {
+    public Object field;
+  }
+
+  @GuardedBy("lock1") MyClass m1;
+
+  @GuardedBy("lock2") MyClass m2;
+
+  @GuardedBy("lock3") MyClass m3;
+
+  @GuardedBy("lock4") MyClass m4;
+
+  final Object lock1 = new Object(),
+      lock2 = new Object(),
+      lock3 = new Object(),
+      lock4 = new Object();
+
+  void foo() {
+    synchronized (lock1) {
+      synchronized (lock2) {
+        synchronized (lock3) {
+          synchronized (lock4) {
+          }
+        }
+      }
+    }
+
+    // Test that the locks are known to have been released.
+
+    // :: error:(lock.not.held)
+    m1.field = new Object();
+    // :: error:(lock.not.held)
+    m2.field = new Object();
+    // :: error:(lock.not.held)
+    m3.field = new Object();
+    // :: error:(lock.not.held)
+    m4.field = new Object();
+  }
+}
diff --git a/checker/tests/lock/Overriding.java b/checker/tests/lock/Overriding.java
new file mode 100644
index 0000000..18a8ade
--- /dev/null
+++ b/checker/tests/lock/Overriding.java
@@ -0,0 +1,160 @@
+import org.checkerframework.checker.lock.qual.*;
+import org.checkerframework.dataflow.qual.*;
+
+public class Overriding {
+
+  class SuperClass {
+    protected Object a, b, c;
+
+    @Holding("a")
+    void guardedByOne() {}
+
+    @Holding({"a", "b"})
+    void guardedByTwo() {}
+
+    @Holding({"a", "b", "c"})
+    void guardedByThree() {}
+
+    @ReleasesNoLocks
+    void rnlMethod() {
+      // :: error: (method.guarantee.violated)
+      mrlMethod();
+      rnlMethod();
+      implicitRnlMethod();
+      lfMethod();
+    }
+
+    void implicitRnlMethod() {
+      // :: error: (method.guarantee.violated)
+      mrlMethod();
+      rnlMethod();
+      implicitRnlMethod();
+      lfMethod();
+    }
+
+    @LockingFree
+    void lfMethod() {
+      // :: error: (method.guarantee.violated)
+      mrlMethod();
+      // :: error: (method.guarantee.violated)
+      rnlMethod();
+      // :: error: (method.guarantee.violated)
+      implicitRnlMethod();
+      lfMethod();
+    }
+
+    @MayReleaseLocks
+    void mrlMethod() {
+      mrlMethod();
+      rnlMethod();
+      implicitRnlMethod();
+      lfMethod();
+    }
+
+    @ReleasesNoLocks
+    void rnlMethod2() {}
+
+    void implicitRnlMethod2() {}
+
+    @LockingFree
+    void lfMethod2() {}
+
+    @MayReleaseLocks
+    void mrlMethod2() {}
+
+    @ReleasesNoLocks
+    void rnlMethod3() {}
+
+    void implicitRnlMethod3() {}
+
+    @LockingFree
+    void lfMethod3() {}
+  }
+
+  class SubClass extends SuperClass {
+    @Holding({"a", "b"}) // error
+    @Override
+    // :: error: (contracts.precondition.override)
+    void guardedByOne() {}
+
+    @Holding({"a", "b"})
+    @Override
+    void guardedByTwo() {}
+
+    @Holding({"a", "b"})
+    @Override
+    void guardedByThree() {}
+
+    @MayReleaseLocks
+    @Override
+    // :: error: (override.sideeffect)
+    void rnlMethod() {}
+
+    @MayReleaseLocks
+    @Override
+    // :: error: (override.sideeffect)
+    void implicitRnlMethod() {}
+
+    @ReleasesNoLocks
+    @Override
+    // :: error: (override.sideeffect)
+    void lfMethod() {}
+
+    @MayReleaseLocks
+    @Override
+    void mrlMethod() {}
+
+    @ReleasesNoLocks
+    @Override
+    void rnlMethod2() {}
+
+    @Override
+    void implicitRnlMethod2() {}
+
+    @LockingFree
+    @Override
+    void lfMethod2() {}
+
+    @ReleasesNoLocks
+    @Override
+    void mrlMethod2() {}
+
+    @LockingFree
+    @Override
+    void rnlMethod3() {}
+
+    @LockingFree
+    @Override
+    void implicitRnlMethod3() {}
+
+    @SideEffectFree
+    @Override
+    void lfMethod3() {}
+  }
+
+  // Test overriding @Holding with JCIP @GuardedBy.
+  class SubClassJcip extends SuperClass {
+    @net.jcip.annotations.GuardedBy({"a", "b"}) @Override
+    // :: error: (contracts.precondition.override)
+    void guardedByOne() {}
+
+    @net.jcip.annotations.GuardedBy({"a", "b"}) @Override
+    void guardedByTwo() {}
+
+    @net.jcip.annotations.GuardedBy({"a", "b"}) @Override
+    void guardedByThree() {}
+  }
+
+  // Test overriding @Holding with Javax @GuardedBy.
+  class SubClassJavax extends SuperClass {
+    @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override
+    // :: error: (contracts.precondition.override)
+    void guardedByOne() {}
+
+    @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override
+    void guardedByTwo() {}
+
+    @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override
+    void guardedByThree() {}
+  }
+}
diff --git a/checker/tests/lock/PrimitivesLocking.java b/checker/tests/lock/PrimitivesLocking.java
new file mode 100644
index 0000000..38ab284
--- /dev/null
+++ b/checker/tests/lock/PrimitivesLocking.java
@@ -0,0 +1,161 @@
+// @skip-test
+// TODO: Reenable this test after a @GuardedByName annotation is implemented that can guard
+// primitives, and uncomment all the @GuardedByName annotations below.
+
+// Note that testing of the immutable.type.guardedby error message is done in TestTreeKinds.java
+
+public class PrimitivesLocking {
+  // @GuardedByName("lock")
+  int primitive = 1;
+
+  // @GuardedByName("lock")
+  boolean primitiveBoolean;
+
+  public void testOperationsWithPrimitives() {
+    // @GuardedByName("lock")
+    int i = 0;
+    // @GuardedByName("lock")
+    boolean b;
+
+    // TODO reenable this error: (lock.not.held)
+    i = i >>> primitive;
+    // TODO reenable this error: (lock.not.held)
+    i = primitive >>> i;
+
+    // TODO reenable this error: (lock.not.held)
+    i >>>= primitive;
+    // TODO reenable this error: (lock.not.held)
+    primitive >>>= i;
+
+    // TODO reenable this error: (lock.not.held)
+    i %= primitive;
+    // TODO reenable this error: (lock.not.held)
+    i = 4 % primitive;
+    // TODO reenable this error: (lock.not.held)
+    i = primitive % 4;
+
+    // TODO reenable this error: (lock.not.held)
+    primitive++;
+    // TODO reenable this error: (lock.not.held)
+    primitive--;
+    // TODO reenable this error: (lock.not.held)
+    ++primitive;
+    // TODO reenable this error: (lock.not.held)
+    --primitive;
+
+    // TODO reenable this error: (lock.not.held)
+    if (primitive != 5) {}
+
+    // TODO reenable this error: (lock.not.held)
+    i = primitive >> i;
+    // TODO reenable this error: (lock.not.held)
+    i = primitive << i;
+    // TODO reenable this error: (lock.not.held)
+    i = i >> primitive;
+    // TODO reenable this error: (lock.not.held)
+    i = i << primitive;
+
+    // TODO reenable this error: (lock.not.held)
+    i <<= primitive;
+    // TODO reenable this error: (lock.not.held)
+    i >>= primitive;
+    // TODO reenable this error: (lock.not.held)
+    primitive <<= i;
+    // TODO reenable this error: (lock.not.held)
+    primitive >>= i;
+
+    // TODO reenable this error: (lock.not.held)
+    assert (primitiveBoolean);
+
+    // TODO reenable this error: (lock.not.held)
+    b = primitive >= i;
+    // TODO reenable this error: (lock.not.held)
+    b = primitive <= i;
+    // TODO reenable this error: (lock.not.held)
+    b = primitive > i;
+    // TODO reenable this error: (lock.not.held)
+    b = primitive < i;
+    // TODO reenable this error: (lock.not.held)
+    b = i >= primitive;
+    // TODO reenable this error: (lock.not.held)
+    b = i <= primitive;
+    // TODO reenable this error: (lock.not.held)
+    b = i > primitive;
+    // TODO reenable this error: (lock.not.held)
+    b = i < primitive;
+
+    // TODO reenable this error: (lock.not.held)
+    i += primitive;
+    // TODO reenable this error: (lock.not.held)
+    i -= primitive;
+    // TODO reenable this error: (lock.not.held)
+    i *= primitive;
+    // TODO reenable this error: (lock.not.held)
+    i /= primitive;
+
+    // TODO reenable this error: (lock.not.held)
+    i = 4 + primitive;
+    // TODO reenable this error: (lock.not.held)
+    i = 4 - primitive;
+    // TODO reenable this error: (lock.not.held)
+    i = 4 * primitive;
+    // TODO reenable this error: (lock.not.held)
+    i = 4 / primitive;
+
+    // TODO reenable this error: (lock.not.held)
+    i = primitive + 4;
+    // TODO reenable this error: (lock.not.held)
+    i = primitive - 4;
+    // TODO reenable this error: (lock.not.held)
+    i = primitive * 4;
+    // TODO reenable this error: (lock.not.held)
+    i = primitive / 4;
+
+    // TODO reenable this error: (lock.not.held)
+    if (primitiveBoolean) {}
+
+    // TODO reenable this error: (lock.not.held)
+    i = ~primitive;
+
+    // TODO reenable this error: (lock.not.held)
+    b = primitiveBoolean || false;
+    // TODO reenable this error: (lock.not.held)
+    b = primitiveBoolean | false;
+
+    // TODO reenable this error: (lock.not.held)
+    b = primitiveBoolean ^ true;
+
+    // TODO reenable this error: (lock.not.held)
+    b &= primitiveBoolean;
+
+    // TODO reenable this error: (lock.not.held)
+    b |= primitiveBoolean;
+
+    // TODO reenable this error: (lock.not.held)
+    b ^= primitiveBoolean;
+
+    // TODO reenable this error: (lock.not.held)
+    b = !primitiveBoolean;
+
+    // TODO reenable this error: (lock.not.held)
+    i = primitive;
+
+    // TODO reenable this error: (lock.not.held)
+    b = true && primitiveBoolean;
+    // TODO reenable this error: (lock.not.held)
+    b = true & primitiveBoolean;
+
+    // TODO reenable this error: (lock.not.held)
+    b = false || primitiveBoolean;
+    // TODO reenable this error: (lock.not.held)
+    b = false | primitiveBoolean;
+
+    // TODO reenable this error: (lock.not.held)
+    b = false ^ primitiveBoolean;
+
+    // TODO reenable this error: (lock.not.held)
+    b = primitiveBoolean && true;
+    // TODO reenable this error: (lock.not.held)
+    b = primitiveBoolean & true;
+  }
+}
diff --git a/checker/tests/lock/SimpleLockTest.java b/checker/tests/lock/SimpleLockTest.java
new file mode 100644
index 0000000..c232849
--- /dev/null
+++ b/checker/tests/lock/SimpleLockTest.java
@@ -0,0 +1,31 @@
+import org.checkerframework.checker.lock.qual.GuardedBy;
+
+public class SimpleLockTest {
+  final Object lock1 = new Object(), lock2 = new Object();
+
+  void testMethodCall(@GuardedBy("lock1") SimpleLockTest this) {
+    // :: error: (lock.not.held)
+    synchronized (lock1) {
+    }
+    // :: error: (lock.not.held)
+    synchronized (this.lock1) {
+    }
+    // :: error: (lock.not.held)
+    synchronized (lock2) {
+    }
+    // :: error: (lock.not.held)
+    synchronized (this.lock2) {
+    }
+
+    final @GuardedBy("myClass.field") MyClass myClass = new MyClass();
+    // :: error: (lock.not.held)
+    synchronized (myClass.field) {
+    }
+    synchronized (myClass) {
+    }
+  }
+
+  class MyClass {
+    final Object field = new Object();
+  }
+}
diff --git a/checker/tests/lock/Strings.java b/checker/tests/lock/Strings.java
new file mode 100644
index 0000000..5328a0d
--- /dev/null
+++ b/checker/tests/lock/Strings.java
@@ -0,0 +1,63 @@
+import org.checkerframework.checker.lock.qual.GuardSatisfied;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.GuardedByBottom;
+import org.checkerframework.checker.lock.qual.GuardedByUnknown;
+
+public class Strings {
+  final Object lock = new Object();
+
+  // These casts are safe because if the casted Object is a String, it must be @GuardedBy({})
+  void StringIsGBnothing(
+      @GuardedByUnknown Object o1,
+      @GuardedBy("lock") Object o2,
+      @GuardSatisfied Object o3,
+      @GuardedByBottom Object o4) {
+    String s1 = (String) o1;
+    String s2 = (String) o2;
+    String s3 = (String) o3;
+    String s4 = (String) o4; // OK
+  }
+
+  // Tests that the resulting type of string concatenation is always @GuardedBy({})
+  // (and not @GuardedByUnknown, which is the LUB of @GuardedBy({}) (the type of the
+  // string literal "a") and @GuardedBy("lock") (the type of param))
+  void StringConcat(@GuardedBy("lock") MyClass param) {
+    {
+      String s1a = "a" + "a";
+      // :: error: (lock.not.held)
+      String s1b = "a" + param;
+      // :: error: (lock.not.held)
+      String s1c = param + "a";
+      // :: error: (lock.not.held)
+      String s1d = param.toString();
+
+      String s2 = "a";
+      // :: error: (lock.not.held)
+      s2 += param;
+
+      String s3 = "a";
+      // In addition to testing whether "lock" is held, tests that the result of a string
+      // concatenation has type @GuardedBy({}).
+      // :: error: (lock.not.held)
+      String s4 = s3 += param;
+    }
+    synchronized (lock) {
+      String s1a = "a" + "a";
+      String s1b = "a" + param;
+      String s1c = param + "a";
+      String s1d = param.toString();
+
+      String s2 = "a";
+      s2 += param;
+
+      String s3 = "a";
+      // In addition to testing whether "lock" is held, tests that the result of a string
+      // concatenation has type @GuardedBy({}).
+      String s4 = s3 += param;
+    }
+  }
+
+  class MyClass {
+    Object field = new Object();
+  }
+}
diff --git a/checker/tests/lock/TestAnon.java b/checker/tests/lock/TestAnon.java
new file mode 100644
index 0000000..0bb105e
--- /dev/null
+++ b/checker/tests/lock/TestAnon.java
@@ -0,0 +1,10 @@
+public class TestAnon {
+  public void foo() {
+    String s = "";
+    new Object() {
+      public String bar() {
+        return s;
+      }
+    };
+  }
+}
diff --git a/checker/tests/lock/TestConcurrentSemantics1.java b/checker/tests/lock/TestConcurrentSemantics1.java
new file mode 100644
index 0000000..d3cce3b
--- /dev/null
+++ b/checker/tests/lock/TestConcurrentSemantics1.java
@@ -0,0 +1,53 @@
+import java.util.concurrent.locks.ReentrantLock;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.GuardedByUnknown;
+
+public class TestConcurrentSemantics1 {
+  /* This class tests the following critical scenario.
+   *
+   * Suppose the following lines from method1 are executed on thread A.
+   *
+   * <pre>{@code
+   * @GuardedBy("lock1") MyClass local;
+   * m = local;
+   * }</pre>
+   *
+   * Then a context switch occurs to method2 on thread B and the following lines are executed:
+   *
+   * <pre>{@code
+   * @GuardedBy("lock2") MyClass local;
+   * m = local;
+   * }</pre>
+   *
+   * Then a context switch back to method1 on thread A occurs and the following lines are executed:
+   *
+   * <pre>{@code
+   * lock1.lock();
+   * m.field = new Object();
+   * }</pre>
+   *
+   * In this case, it is absolutely critical that the dereference above not be allowed.
+   *
+   */
+
+  @GuardedByUnknown MyClass m;
+  final ReentrantLock lock1 = new ReentrantLock();
+  final ReentrantLock lock2 = new ReentrantLock();
+
+  void method1() {
+    @GuardedBy("lock1") MyClass local = new MyClass();
+    m = local;
+    lock1.lock();
+    // :: error: (lock.not.held)
+    m.field = new Object();
+  }
+
+  void method2() {
+    @GuardedBy("lock2") MyClass local = new MyClass();
+    m = local;
+  }
+
+  class MyClass {
+    Object field = new Object();
+  }
+}
diff --git a/checker/tests/lock/TestConcurrentSemantics2.java b/checker/tests/lock/TestConcurrentSemantics2.java
new file mode 100644
index 0000000..e7d38aa
--- /dev/null
+++ b/checker/tests/lock/TestConcurrentSemantics2.java
@@ -0,0 +1,28 @@
+import org.checkerframework.checker.lock.qual.GuardedBy;
+
+public class TestConcurrentSemantics2 {
+  final Object a = new Object();
+  final Object b = new Object();
+
+  @GuardedBy("a") Object o;
+
+  void method() {
+    o = null;
+    // Assume the following happens:
+    //  * Context switch to a different thread.
+    //  * bar() is called on the other thread.
+    //  * Context switch back to this thread.
+    // o is no longer null and an assignment error should be issued.
+    // :: error: (assignment)
+    @GuardedBy("b") Object o2 = o;
+  }
+
+  void bar() {
+    o = new Object();
+  }
+
+  // Test that field assignments do not cause their type to be refined:
+  @GuardedBy("a") Object myObject1 = null;
+  // :: error: (assignment)
+  @GuardedBy("b") Object myObject2 = myObject1;
+}
diff --git a/checker/tests/lock/TestTreeKinds.java b/checker/tests/lock/TestTreeKinds.java
new file mode 100644
index 0000000..1a8553f
--- /dev/null
+++ b/checker/tests/lock/TestTreeKinds.java
@@ -0,0 +1,509 @@
+import java.util.Random;
+import java.util.concurrent.locks.ReentrantLock;
+import org.checkerframework.checker.lock.qual.*;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+
+public class TestTreeKinds {
+  class MyClass {
+    Object field = new Object();
+
+    @LockingFree
+    Object method(@GuardSatisfied MyClass this) {
+      return new Object();
+    }
+
+    void method2(@GuardSatisfied MyClass this) {}
+  }
+
+  @GuardedBy("lock") MyClass m;
+
+  {
+    // In constructor/initializer, it's OK not to hold the lock on 'this', but other locks must
+    // be respected.
+    // :: error: (lock.not.held)
+    m.field = new Object();
+  }
+
+  final ReentrantLock lock = new ReentrantLock();
+  final ReentrantLock lock2 = new ReentrantLock();
+
+  @GuardedBy("lock") MyClass foo = new MyClass();
+
+  MyClass unguardedFoo = new MyClass();
+
+  @EnsuresLockHeld("lock")
+  void lockTheLock() {
+    lock.lock();
+  }
+
+  @EnsuresLockHeld("lock2")
+  void lockTheLock2() {
+    lock2.lock();
+  }
+
+  @EnsuresLockHeldIf(expression = "lock", result = true)
+  boolean tryToLockTheLock() {
+    return lock.tryLock();
+  }
+
+  // This @MayReleaseLocks annotation causes dataflow analysis to assume 'lock' is released after
+  // unlockTheLock() is called.
+  @MayReleaseLocks
+  void unlockTheLock() {}
+
+  @SideEffectFree
+  void sideEffectFreeMethod() {}
+
+  @LockingFree
+  void lockingFreeMethod() {}
+
+  @MayReleaseLocks
+  void nonSideEffectFreeMethod() {}
+
+  @Holding("lock")
+  void requiresLockHeldMethod() {}
+
+  MyClass fooArray @GuardedBy("lock") [] = new MyClass[3];
+
+  @GuardedBy("lock") MyClass fooArray2[] = new MyClass[3];
+
+  @GuardedBy("lock") MyClass fooArray3[][] = new MyClass[3][3];
+
+  MyClass fooArray4 @GuardedBy("lock") [][] = new MyClass[3][3];
+
+  MyClass fooArray5[] @GuardedBy("lock") [] = new MyClass[3][3];
+
+  class myClass {
+    int i = 0;
+  }
+
+  @GuardedBy("lock") myClass myClassInstance = new myClass();
+
+  @GuardedBy("lock") Exception exception = new Exception();
+
+  class MyParametrizedType<T> {
+    T foo;
+    int l;
+  }
+
+  @GuardedBy("lock") MyParametrizedType<MyClass> myParametrizedType = new MyParametrizedType<>();
+
+  MyClass getFooWithWrongReturnType() {
+    // :: error: (return)
+    return foo; // return of guarded object
+  }
+
+  @GuardedBy("lock") MyClass getFoo() {
+    return foo;
+  }
+
+  MyClass @GuardedBy("lock") [] getFooArray() {
+    return fooArray;
+  }
+
+  @GuardedBy("lock") MyClass[] getFooArray2() {
+    return fooArray2;
+  }
+
+  @GuardedBy("lock") MyClass[][] getFooArray3() {
+    return fooArray3;
+  }
+
+  MyClass @GuardedBy("lock") [][] getFooArray4() {
+    return fooArray4;
+  }
+
+  MyClass[] @GuardedBy("lock") [] getFooArray5() {
+    return fooArray5;
+  }
+
+  enum myEnumType {
+    ABC,
+    DEF
+  }
+
+  @GuardedBy("lock") myEnumType myEnum;
+
+  void testEnumType() {
+    // TODO: assignment is technically correct, but we could
+    // make it friendlier for the user if constant enum values on the RHS
+    // automatically cast to the @GuardedBy annotation of the LHS.
+    // :: error: (assignment)
+    myEnum = myEnumType.ABC;
+  }
+
+  final Object intrinsicLock = new Object();
+
+  void testThreadHoldsLock(@GuardedBy("intrinsicLock") MyClass m) {
+    if (Thread.holdsLock(intrinsicLock)) {
+      m.field.toString();
+    } else {
+      // :: error: (lock.not.held)
+      m.field.toString();
+    }
+  }
+
+  void testTreeTypes() {
+    int i, l;
+
+    MyClass o = new MyClass();
+    MyClass f = new MyClass();
+
+    // The following test cases were inspired from annotator.find.ASTPathCriterion.isSatisfiedBy
+    // in the Annotation File Utilities
+
+    // TODO: File a bug for the dataflow issue mentioned in the line below.
+    // TODO: uncomment: Hits a bug in dataflow:
+    // do {
+    //     break;
+    // } while (foo.field != null); // access to guarded object in condition of do/while loop
+    // :: error: (lock.not.held)
+    for (foo = new MyClass(); foo.field != null; foo = new MyClass()) {
+      break;
+    } // access to guarded object in condition of for loop
+    // assignment to guarded object (OK) --- foo is still refined to @GuardedBy("lock") after
+    // this point, though.
+    foo = new MyClass();
+    // A simple method call to a guarded object is not considered a dereference (only field
+    // accesses are considered dereferences).
+    unguardedFoo.method2();
+    // Same as above, but the guard must be satisfied if the receiver is @GuardSatisfied.
+    // :: error: (lock.not.held)
+    foo.method2();
+    // attempt to use guarded object in a switch statement
+    // :: error: (lock.not.held)
+    switch (foo.field.hashCode()) {
+    }
+    // attempt to use guarded object inside a try with resources
+    // try(foo = new MyClass()) { foo.field.toString(); }
+
+    // Retrieving an element from a guarded array is a dereference
+    // :: error: (lock.not.held)
+    MyClass m = fooArray[0];
+
+    // method call on dereference of unguarded element of *guarded* array
+    // :: error: (lock.not.held)
+    fooArray[0].field.toString();
+    // :: error: (lock.not.held)
+    l = fooArray.length; // dereference of guarded array itself
+
+    // method call on dereference of guarded array element
+    // :: error: (lock.not.held)
+    fooArray2[0].field.toString();
+    // method call on dereference of unguarded array - TODO: currently preconditions are not
+    // retrieved correctly from array types. This is not unique to the Lock Checker.
+    // fooArray2.field.toString();
+
+    // method call on dereference of guarded array element of multidimensional array
+    // :: error: (lock.not.held)
+    fooArray3[0][0].field.toString();
+    // method call on dereference of unguarded single-dimensional array element of unguarded
+    // multidimensional array - TODO: currently preconditions are not retrieved correctly from
+    // array types. This is not unique to the Lock Checker.
+    // fooArray3[0].field.toString();
+    // method call on dereference of unguarded multidimensional array - TODO: currently
+    // preconditions are not retrieved correctly from array types. This is not unique to the
+    // Lock Checker.
+    // fooArray3.field.toString();
+
+    // method call on dereference of unguarded array element of *guarded* multidimensional array
+    // :: error: (lock.not.held)
+    fooArray4[0][0].field.toString();
+    // dereference of unguarded single-dimensional array element of *guarded* multidimensional
+    // array
+    // :: error: (lock.not.held)
+    l = fooArray4[0].length;
+    // dereference of guarded multidimensional array
+    // :: error: (lock.not.held)
+    l = fooArray4.length;
+
+    // method call on dereference of unguarded array element of *guarded subarray* of
+    // multidimensional array
+    // :: error: (lock.not.held)
+    fooArray5[0][0].field.toString();
+    // dereference of guarded single-dimensional array element of multidimensional array
+    // :: error: (lock.not.held)
+    l = fooArray5[0].length;
+    // dereference of unguarded multidimensional array
+    l = fooArray5.length;
+
+    // :: error: (lock.not.held)
+    l = getFooArray().length; // dereference of guarded array returned by a method
+
+    // method call on dereference of guarded array element returned by a method
+    // :: error: (lock.not.held)
+    getFooArray2()[0].field.toString();
+    // dereference of unguarded array returned by a method
+    l = getFooArray2().length;
+
+    // method call on dereference of guarded array element of multidimensional array returned by
+    // a method
+    // :: error: (lock.not.held)
+    getFooArray3()[0][0].field.toString();
+    // dereference of unguarded single-dimensional array element of multidimensional array
+    // returned by a method
+    l = getFooArray3()[0].length;
+    // dereference of unguarded multidimensional array returned by a method
+    l = getFooArray3().length;
+
+    // method call on dereference of unguarded array element of *guarded* multidimensional array
+    // returned by a method
+    // :: error: (lock.not.held)
+    getFooArray4()[0][0].field.toString();
+    // dereference of unguarded single-dimensional array element of *guarded* multidimensional
+    // array returned by a method
+    // :: error: (lock.not.held)
+    l = getFooArray4()[0].length;
+    // dereference of guarded multidimensional array returned by a method
+    // :: error: (lock.not.held)
+    l = getFooArray4().length;
+
+    // method call on dereference of unguarded array element of *guarded subarray* of
+    // multidimensional array returned by a method
+    // :: error: (lock.not.held)
+    getFooArray5()[0][0].field.toString();
+    // dereference of guarded single-dimensional array element of multidimensional array
+    // returned by a method
+    // :: error: (lock.not.held)
+    l = getFooArray5()[0].length;
+    // dereference of unguarded multidimensional array returned by a method
+    l = getFooArray5().length;
+
+    // Test different @GuardedBy(...) present on the element and array locations.
+    @GuardedBy("lock") MyClass @GuardedBy("lock2") [] array = new MyClass[3];
+    // :: error: (lock.not.held)
+    array[0].field = new Object();
+    if (lock.isHeldByCurrentThread()) {
+      // :: error: (lock.not.held)
+      array[0].field = new Object();
+      if (lock2.isHeldByCurrentThread()) {
+        array[0].field = new Object();
+      }
+    }
+
+    // method call on guarded object within parenthesized expression
+    // :: error: (lock.not.held)
+    String s = (foo.field.toString());
+    // :: error: (lock.not.held)
+    foo.field.toString(); // method call on guarded object
+    // :: error: (lock.not.held)
+    getFoo().field.toString(); // method call on guarded object returned by a method
+    // :: error: (lock.not.held)
+    this.foo.field.toString(); // method call on guarded object using 'this' literal
+    // dereference of guarded object in labeled statement
+    label:
+    // :: error: (lock.not.held)
+    foo.field.toString();
+    // access to guarded object in instanceof expression (OK)
+    if (foo instanceof MyClass) {}
+    // access to guarded object in while condition of while loop (OK)
+    while (foo != null) {
+      break;
+    }
+    // binary operator on guarded object in else if condition (OK)
+    if (false) {
+    } else if (foo == o) {
+    }
+    // access to guarded object in a lambda expression
+    Runnable rn =
+        () -> {
+          // :: error: (lock.not.held)
+          foo.field.toString();
+        };
+    // :: error: (lock.not.held)
+    i = myClassInstance.i; // access to member field of guarded object
+    // MemberReferenceTrees? how do they work
+    fooArray = new MyClass[3]; // second allocation of guarded array (OK)
+    // dereference of guarded object in conditional expression tree
+    // :: error: (lock.not.held)
+    s = i == 5 ? foo.field.toString() : f.field.toString();
+    // dereference of guarded object in conditional expression tree
+    // :: error: (lock.not.held)
+    s = i == 5 ? f.field.toString() : foo.field.toString();
+    // Testing of 'return' is done in getFooWithWrongReturnType()
+    // throwing a guarded object - when throwing an exception, it must be @GuardedBy({}). Even
+    // @GuardedByUnknown is not allowed.
+    try {
+      // :: error: (throw)
+      throw exception;
+    } catch (Exception e) {
+    }
+    // casting of a guarded object to an unguarded object
+    // :: error: (assignment)
+    @GuardedBy({}) Object e1 = (Object) exception;
+    // OK, since the local variable's type gets refined to @GuardedBy("lock")
+    Object e2 = (Object) exception;
+    // :: error: (lock.not.held)
+    l = myParametrizedType.l; // dereference of guarded object having a parameterized type
+
+    // We need to support locking on local variables and protecting local variables because
+    // these locals may contain references to fields. Somehow we need to pass along the
+    // information of which field it was.
+
+    if (foo == o) { // binary operator on guarded object (OK)
+      o.field.toString();
+    }
+
+    if (foo == null) {
+      // With -AconcurrentSemantics turned off, a cannot.dereference error would be expected,
+      // since there is an attempt to dereference an expression whose type has been refined to
+      // @GuardedByBottom (due to the comparison to null). However, with -AconcurrentSemantics
+      // turned on, foo may no longer be null by now, the refinement to @GuardedByBottom is
+      // lost and the refined type of foo is now the declared type ( @GuardedBy("lock") ),
+      // resulting in the lock.not.held error.
+      // :: error: (lock.not.held)
+      foo.field.toString();
+    }
+
+    // TODO: Reenable:
+    // @PolyGuardedBy should not be written here, but it is not explicitly forbidden by the
+    // framework.
+    // @PolyGuardedBy MyClass m2 = new MyClass();
+    // (cannot.dereference)
+    // m2.field.toString();
+  }
+
+  @MayReleaseLocks
+  public void testLocals() {
+    final ReentrantLock localLock = new ReentrantLock();
+
+    @GuardedBy("localLock") MyClass guardedByLocalLock = new MyClass();
+
+    // :: error: (lock.not.held)
+    guardedByLocalLock.field.toString();
+
+    @GuardedBy("lock") MyClass local = new MyClass();
+
+    // :: error: (lock.not.held)
+    local.field.toString();
+
+    lockTheLock();
+
+    local.field.toString(); // No warning output
+
+    unlockTheLock();
+  }
+
+  @MayReleaseLocks
+  public void testMethodAnnotations() {
+    Random r = new Random();
+
+    if (r.nextBoolean()) {
+      lockTheLock();
+      requiresLockHeldMethod();
+    } else {
+      // :: error: (contracts.precondition)
+      requiresLockHeldMethod();
+    }
+
+    if (r.nextBoolean()) {
+      lockTheLock();
+      foo.field.toString();
+
+      unlockTheLock();
+
+      // :: error: (lock.not.held)
+      foo.field.toString();
+    } else {
+      // :: error: (lock.not.held)
+      foo.field.toString();
+    }
+
+    if (tryToLockTheLock()) {
+      foo.field.toString();
+    } else {
+      // :: error: (lock.not.held)
+      foo.field.toString();
+    }
+
+    if (r.nextBoolean()) {
+      lockTheLock();
+      sideEffectFreeMethod();
+      foo.field.toString();
+    } else {
+      lockTheLock();
+      nonSideEffectFreeMethod();
+      // :: error: (lock.not.held)
+      foo.field.toString();
+    }
+
+    if (r.nextBoolean()) {
+      lockTheLock();
+      lockingFreeMethod();
+      foo.field.toString();
+    } else {
+      lockTheLock();
+      nonSideEffectFreeMethod();
+      // :: error: (lock.not.held)
+      foo.field.toString();
+    }
+  }
+
+  void methodThatTakesAnInteger(Integer i) {}
+
+  void testBoxedPrimitiveType() {
+    Integer i = null;
+    if (i == null) {}
+
+    methodThatTakesAnInteger(i);
+  }
+
+  void testReceiverGuardedByItself(@GuardedBy("<self>") TestTreeKinds this) {
+    // :: error: (lock.not.held)
+    method();
+    synchronized (this) {
+      method();
+    }
+  }
+
+  void method(@GuardSatisfied TestTreeKinds this) {}
+
+  void testOtherClassReceiverGuardedByItself(final @GuardedBy("<self>") OtherClass o) {
+    // :: error: (lock.not.held)
+    o.foo();
+    synchronized (o) {
+      o.foo();
+    }
+  }
+
+  class OtherClass {
+    void foo(@GuardSatisfied OtherClass this) {}
+  }
+
+  void testExplicitLockSynchronized() {
+    final ReentrantLock lock = new ReentrantLock();
+    // :: error: (explicit.lock.synchronized)
+    synchronized (lock) {
+    }
+  }
+
+  void testPrimitiveTypeGuardedby() {
+    // :: error: (immutable.type.guardedby)
+    @GuardedBy("lock") int a = 0;
+    // :: error: (immutable.type.guardedby)
+    @GuardedBy int b = 0;
+    // :: error: (immutable.type.guardedby) :: error: (guardsatisfied.location.disallowed)
+    @GuardSatisfied int c = 0;
+    // :: error: (immutable.type.guardedby) :: error: (guardsatisfied.location.disallowed)
+    @GuardSatisfied(1) int d = 0;
+    int e = 0;
+    // :: error: (immutable.type.guardedby)
+    @GuardedByUnknown int f = 0;
+    // :: error: (immutable.type.guardedby) :: error: (assignment)
+    @GuardedByBottom int g = 0;
+  }
+
+  void testBinaryOperatorBooleanResultIsAlwaysGuardedByNothing() {
+    @GuardedBy("lock") Object o1 = new Object();
+    Object o2 = new Object();
+    // boolean variables are implicitly @GuardedBy({}).
+    boolean b1 = o1 == o2;
+    boolean b2 = o2 == o1;
+    boolean b3 = o1 != o2;
+    boolean b4 = o2 != o1;
+    boolean b5 = o1 instanceof Object;
+    boolean b6 = o2 instanceof Object;
+    boolean b7 = o1 instanceof @GuardedBy("lock") Object;
+    boolean b8 = o2 instanceof @GuardedBy("lock") Object;
+  }
+}
diff --git a/checker/tests/lock/ThisPostCondition.java b/checker/tests/lock/ThisPostCondition.java
new file mode 100644
index 0000000..59c5824
--- /dev/null
+++ b/checker/tests/lock/ThisPostCondition.java
@@ -0,0 +1,68 @@
+import org.checkerframework.checker.lock.qual.EnsuresLockHeld;
+import org.checkerframework.checker.lock.qual.EnsuresLockHeldIf;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.Holding;
+
+class MyReentrantLock {
+  final Object myfield = new Object();
+
+  @Holding("myfield")
+  @EnsuresLockHeld("this")
+  void lock() {
+    this.lock();
+  }
+
+  @EnsuresLockHeld("this")
+  void lock2() {
+    this.lock2();
+  }
+
+  @Holding("myfield")
+  void notLock() {}
+
+  boolean b = false;
+
+  @EnsuresLockHeldIf(expression = "this", result = true)
+  boolean tryLock() {
+    if (b) {
+      lock2();
+      return true;
+    }
+    return false;
+  }
+}
+
+public class ThisPostCondition {
+  final MyReentrantLock myLock = new MyReentrantLock();
+
+  @GuardedBy("myLock") Bar bar = new Bar();
+
+  @Holding("myLock.myfield")
+  void lockTheLock() {
+    myLock.lock();
+    bar.field.toString();
+  }
+
+  void lockTheLock2() {
+    myLock.lock2();
+    bar.field.toString();
+  }
+
+  void doNotLock() {
+    // :: error: (lock.not.held)
+    bar.field.toString();
+  }
+
+  void tryTryLock() {
+    if (myLock.tryLock()) {
+      bar.field.toString();
+    } else {
+      // :: error: (lock.not.held)
+      bar.field.toString();
+    }
+  }
+}
+
+class Bar {
+  Object field;
+}
diff --git a/checker/tests/lock/ThisSuper.java b/checker/tests/lock/ThisSuper.java
new file mode 100644
index 0000000..4fe9a02
--- /dev/null
+++ b/checker/tests/lock/ThisSuper.java
@@ -0,0 +1,90 @@
+// Test case for Issue #152
+// https://github.com/typetools/checker-framework/issues/152
+
+// @skip-test
+
+import org.checkerframework.checker.lock.qual.GuardedBy;
+
+public class ThisSuper {
+
+  class MyClass {
+    Object field;
+  }
+
+  class LockExample {
+    protected final Object myLock = new Object();
+
+    protected @GuardedBy("myLock") MyClass locked;
+
+    @GuardedBy("this.myLock") MyClass m1;
+
+    protected @GuardedBy("this.myLock") MyClass locked2;
+
+    public void accessLock() {
+      synchronized (myLock) {
+        this.locked.field = new Object();
+      }
+    }
+  }
+
+  class LockExampleSubclass extends LockExample {
+    private final Object myLock = new Object();
+
+    private @GuardedBy("this.myLock") MyClass locked;
+
+    @GuardedBy("this.myLock") MyClass m2;
+
+    public LockExampleSubclass() {
+      final LockExampleSubclass les1 = new LockExampleSubclass();
+      final LockExampleSubclass les2 = new LockExampleSubclass();
+      final LockExampleSubclass les3 = les2;
+      LockExample le1 = new LockExample();
+
+      synchronized (super.myLock) {
+        super.locked.toString();
+        super.locked2.toString();
+        // :: error: (contracts.precondition)
+        locked.toString();
+      }
+      synchronized (myLock) {
+        // :: error: (contracts.precondition)
+        super.locked.toString();
+        // :: error: (contracts.precondition)
+        super.locked2.toString();
+        locked.toString();
+      }
+
+      // :: error: (assignment)
+      les1.locked = le1.locked;
+      // :: error: (assignment)
+      les1.locked = le1.locked2;
+
+      // :: error: (assignment)
+      les1.locked = les2.locked;
+
+      // :: error: (assignment)
+      this.locked = super.locked;
+      // :: error: (assignment)
+      this.locked = super.locked2;
+
+      // :: error: (assignment)
+      m1 = m2;
+    }
+
+    @Override
+    public void accessLock() {
+      synchronized (myLock) {
+        this.locked.field = new Object();
+        // :: error: (lock.not.held)
+        super.locked.field = new Object();
+        System.out.println(
+            this.locked.field
+                + " "
+                +
+                // :: error: (lock.not.held)
+                super.locked.field);
+        System.out.println("Are locks equal? " + (super.locked == this.locked ? "yes" : "no"));
+      }
+    }
+  }
+}
diff --git a/checker/tests/lock/TypeVarNull.java b/checker/tests/lock/TypeVarNull.java
new file mode 100644
index 0000000..c350977
--- /dev/null
+++ b/checker/tests/lock/TypeVarNull.java
@@ -0,0 +1,3 @@
+public class TypeVarNull<T> {
+  T t = null;
+}
diff --git a/checker/tests/lock/Update.java b/checker/tests/lock/Update.java
new file mode 100644
index 0000000..d1f3cd1
--- /dev/null
+++ b/checker/tests/lock/Update.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.lock.qual.GuardedBy;
+
+public class Update {
+
+  void test() {
+    Object o1 = new Object();
+    @GuardedBy({}) Object o2 = o1;
+    synchronized (o1) {
+    }
+    // o1 used to loss it refinement because of a bug.
+    @GuardedBy({}) Object o3 = o1;
+  }
+}
diff --git a/checker/tests/lock/ViewpointAdaptation.java b/checker/tests/lock/ViewpointAdaptation.java
new file mode 100644
index 0000000..805750e
--- /dev/null
+++ b/checker/tests/lock/ViewpointAdaptation.java
@@ -0,0 +1,51 @@
+// Test case for Issue #770
+// https://github.com/typetools/checker-framework/issues/770
+
+import org.checkerframework.checker.lock.qual.GuardedBy;
+
+public class ViewpointAdaptation {
+  // :: error: (expression.unparsable)
+  private final @GuardedBy("a") ViewpointAdaptation f = new ViewpointAdaptation();
+
+  private @GuardedBy("this.lock") ViewpointAdaptation g = new ViewpointAdaptation();
+
+  private final Object lock = new Object();
+
+  private int counter;
+
+  public void method1(final String a) {
+    synchronized (a) {
+      // The expression "a" from the @GuardedBy annotation
+      // on f is not valid at the declaration site of f.
+      // :: error: (expression.unparsable)
+      f.counter++;
+    }
+  }
+
+  public void method2() {
+    ViewpointAdaptation t = new ViewpointAdaptation();
+
+    // :: error: (assignment)
+    t.g = g; // "t.lock" != "this.lock"
+
+    synchronized (t.lock) {
+      // :: error: (lock.not.held)
+      g.counter++;
+    }
+  }
+
+  public void method3() {
+    final ViewpointAdaptation t = new ViewpointAdaptation();
+    // The type of 'g' is refined from @GuardedByUnknown (the default for
+    // a local variable due to CLIMB-to-top semantics) to @GuardedBy("t.g")
+    final ViewpointAdaptation g = t.g;
+    Object l = t.lock;
+
+    synchronized (l) {
+      // Aliasing of lock expressions is not tracked by the Lock Checker.
+      // The Lock Checker does not know that l == t.lock
+      // :: error: (lock.not.held)
+      g.counter++;
+    }
+  }
+}
diff --git a/checker/tests/lock/ViewpointAdaptation2.java b/checker/tests/lock/ViewpointAdaptation2.java
new file mode 100644
index 0000000..35e0977
--- /dev/null
+++ b/checker/tests/lock/ViewpointAdaptation2.java
@@ -0,0 +1,44 @@
+// Test case for Issue #770
+// https://github.com/typetools/checker-framework/issues/770
+
+import org.checkerframework.checker.lock.qual.GuardedBy;
+
+public class ViewpointAdaptation2 {
+
+  class LockExample {
+    protected final Object myLock = new Object();
+
+    protected @GuardedBy("myLock") Object locked;
+
+    protected @GuardedBy("this.myLock") Object locked2;
+
+    public @GuardedBy("myLock") Object getLocked() {
+      return locked;
+    }
+  }
+
+  class Use {
+    final LockExample lockExample1 = new LockExample();
+    final Object myLock = new Object();
+
+    @GuardedBy("lockExample1.myLock") Object o1 = lockExample1.locked;
+
+    @GuardedBy("lockExample1.myLock") Object o2 = lockExample1.locked2;
+    // :: error: (assignment)
+    @GuardedBy("myLock") Object o3 = lockExample1.locked;
+    // :: error: (assignment)
+    @GuardedBy("this.myLock") Object o4 = lockExample1.locked2;
+
+    @GuardedBy("lockExample1.myLock") Object oM1 = lockExample1.getLocked();
+    // :: error: (assignment)
+    @GuardedBy("myLock") Object oM2 = lockExample1.getLocked();
+    // :: error: (assignment)
+    @GuardedBy("this.myLock") Object oM3 = lockExample1.getLocked();
+
+    void uses() {
+      lockExample1.locked = o1;
+      // :: error: (assignment)
+      lockExample1.locked = o3;
+    }
+  }
+}
diff --git a/checker/tests/lock/ViewpointAdaptation3.java b/checker/tests/lock/ViewpointAdaptation3.java
new file mode 100644
index 0000000..f25c688
--- /dev/null
+++ b/checker/tests/lock/ViewpointAdaptation3.java
@@ -0,0 +1,114 @@
+// Test case for Issue #770
+// https://github.com/typetools/checker-framework/issues/770
+
+import org.checkerframework.checker.lock.qual.GuardedBy;
+
+public class ViewpointAdaptation3 {
+
+  class MyClass {
+    Object field;
+  }
+
+  class LockExample {
+    protected final Object myLock = new Object();
+
+    protected @GuardedBy("myLock") MyClass locked;
+
+    @GuardedBy("this.myLock") MyClass m1;
+
+    protected @GuardedBy("this.myLock") MyClass locked2;
+
+    public void accessLock() {
+      synchronized (myLock) {
+        this.locked.field = new Object();
+      }
+    }
+  }
+
+  class LockExampleSubclass extends LockExample {
+    private final Object myLock = new Object();
+
+    private @GuardedBy("this.myLock") MyClass locked;
+
+    @GuardedBy("this.myLock") MyClass m2;
+
+    public LockExampleSubclass() {
+      final LockExampleSubclass les1 = new LockExampleSubclass();
+      final LockExampleSubclass les2 = new LockExampleSubclass();
+      final LockExampleSubclass les3 = les2;
+      LockExample le1 = new LockExample();
+
+      // :: error: (assignment)
+      les1.locked = le1.locked;
+      // :: error: (assignment)
+      les1.locked = le1.locked2;
+
+      // :: error: (assignment)
+      les1.locked = les2.locked;
+    }
+  }
+
+  class Class1 {
+    public final Object lock = new Object();
+
+    @GuardedBy("lock") MyClass m = new MyClass();
+  }
+
+  class Class2 {
+    public final Object lock = new Object();
+
+    @GuardedBy("lock") MyClass m = new MyClass();
+
+    void method(final Class1 a) {
+      final Object lock = new Object();
+      @GuardedBy("lock") MyClass local = new MyClass();
+
+      // :: error: (assignment)
+      local = m;
+
+      // :: error: (lock.not.held)
+      local.field = new Object();
+
+      synchronized (lock) {
+        // :: error: (lock.not.held)
+        a.m.field = new Object();
+      }
+      synchronized (this.lock) {
+        // :: error: (lock.not.held)
+        a.m.field = new Object();
+      }
+      synchronized (a.lock) {
+        a.m.field = new Object();
+      }
+
+      synchronized (lock) {
+        local.field = new Object();
+      }
+      synchronized (this.lock) {
+        // :: error: (lock.not.held)
+        local.field = new Object();
+      }
+      synchronized (a.lock) {
+        // :: error: (lock.not.held)
+        local.field = new Object();
+      }
+
+      synchronized (lock) {
+        // :: error: (lock.not.held)
+        this.m.field = new Object();
+        // :: error: (lock.not.held)
+        m.field = new Object();
+      }
+      synchronized (this.lock) {
+        this.m.field = new Object();
+        m.field = new Object();
+      }
+      synchronized (a.lock) {
+        // :: error: (lock.not.held)
+        this.m.field = new Object();
+        // :: error: (lock.not.held)
+        m.field = new Object();
+      }
+    }
+  }
+}
diff --git a/checker/tests/mustcall/BinaryInputArchive.java b/checker/tests/mustcall/BinaryInputArchive.java
new file mode 100644
index 0000000..8f414af
--- /dev/null
+++ b/checker/tests/mustcall/BinaryInputArchive.java
@@ -0,0 +1,17 @@
+// Test case based on a false positive that was
+// caused by not respecting ownership transfer rules for constructor params.
+
+import java.io.*;
+
+class BinaryInputArchive {
+
+  private DataInput in;
+
+  public BinaryInputArchive(DataInput in) {
+    this.in = in;
+  }
+
+  public static BinaryInputArchive getArchive(InputStream strm) {
+    return new BinaryInputArchive(new DataInputStream(strm));
+  }
+}
diff --git a/checker/tests/mustcall/BorrowOnReturn.java b/checker/tests/mustcall/BorrowOnReturn.java
new file mode 100644
index 0000000..bd47840
--- /dev/null
+++ b/checker/tests/mustcall/BorrowOnReturn.java
@@ -0,0 +1,47 @@
+// tests that the Must Call checker respects the Owning and NotOwning annotations on return values.
+
+import org.checkerframework.checker.mustcall.qual.*;
+
+class BorrowOnReturn {
+  @MustCall("a") class Foo {
+    void a() {}
+  }
+
+  @Owning
+  Object getOwnedFoo() {
+    // :: error: return
+    return new Foo();
+  }
+
+  Object getNoAnnoFoo() {
+    // Treat as owning, so warn
+    // :: error: return
+    return new Foo();
+  }
+
+  @NotOwning
+  Object getNotOwningFooWrong() {
+    // OCC must-call checker will warn about this; MC checker isn't responsible
+    return new Foo();
+  }
+
+  Object getNotOwningFooRightButNoNotOwningAnno() {
+    Foo f = new Foo();
+    f.a();
+    // This is still an error for now, because it's treated as an owning pointer. TODO: fix this
+    // kind of FP?
+    // :: error: return
+    return f;
+  }
+
+  @NotOwning
+  Object getNotOwningFooRight() {
+    Foo f = new Foo();
+    f.a();
+    return f;
+  }
+
+  @MustCall("a") Object getNotOwningFooRight2() {
+    return new Foo();
+  }
+}
diff --git a/checker/tests/mustcall/ClassForNameInit.java b/checker/tests/mustcall/ClassForNameInit.java
new file mode 100644
index 0000000..de5904b
--- /dev/null
+++ b/checker/tests/mustcall/ClassForNameInit.java
@@ -0,0 +1,39 @@
+// Based on a number of false positives in Zookeeper that all use this pattern to reflectively
+// initialize a class. -AresolveReflection fixes this version, but most (4/5) of the failures in
+// Zookeeper
+// persist even with that flag, which also imposes about a 50% perf overhead. So these are now
+// expected warnings.
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+
+class ClassForNameInit {
+
+  public static InputStream inputStreamFactory() throws Exception {
+    // FYI this code will always fail if you run it, so don't.
+    // There's no ByteArrayInputStream constructor that takes no arguments.
+    Class<?> baisClass = Class.forName("java.io.ByteArrayInputStream");
+    Object bais = baisClass.getConstructor().newInstance();
+    return (InputStream) bais;
+  }
+
+  public static Object objectFactory() throws Exception {
+    Class<?> objClass = Class.forName("java.lang.Object");
+    Object obj = objClass.getConstructor().newInstance();
+    return (Object) obj;
+  }
+
+  private static Object getAuditLogger(String auditLoggerClass) {
+    if (auditLoggerClass == null) {
+      auditLoggerClass = Object.class.getName();
+    }
+    try {
+      Constructor<?> clientCxnConstructor =
+          Class.forName(auditLoggerClass).getDeclaredConstructor();
+      Object auditLogger = (Object) clientCxnConstructor.newInstance();
+      return auditLogger;
+    } catch (Exception e) {
+      throw new RuntimeException("Couldn't instantiate " + auditLoggerClass, e);
+    }
+  }
+}
diff --git a/checker/tests/mustcall/CommandResponse.java b/checker/tests/mustcall/CommandResponse.java
new file mode 100644
index 0000000..43968f0
--- /dev/null
+++ b/checker/tests/mustcall/CommandResponse.java
@@ -0,0 +1,21 @@
+// Based on a false positive in Zookeeper.
+
+import java.util.Map;
+
+class CommandResponse {
+  Map<String, Object> data;
+
+  public void putAll(Map<? extends String, ?> m) {
+    // This is a false positive. The fix is to change the declaration to match what's below.
+    // The cause is that implicit upper bounds are defaulted to top, to match the intuition
+    // that e.g. List<E> actually means List<E extends @Top Object>. In this case, that
+    // causes an incompatibility with putAll, whose type requires @Bottom Object as the second
+    // type parameter, because of the type of the data field.
+    // :: error: argument
+    data.putAll(m);
+  }
+
+  public void putAll2(Map<? extends String, ? extends Object> m) {
+    data.putAll(m);
+  }
+}
diff --git a/checker/tests/mustcall/CreatesObligationSimple.java b/checker/tests/mustcall/CreatesObligationSimple.java
new file mode 100644
index 0000000..1b2d962
--- /dev/null
+++ b/checker/tests/mustcall/CreatesObligationSimple.java
@@ -0,0 +1,56 @@
+// A simple test that @CreatesObligation works as intended wrt the Must Call Checker.
+
+import org.checkerframework.checker.mustcall.qual.*;
+
+@MustCall("a") class CreatesObligationSimple {
+
+  @CreatesObligation
+  void reset() {}
+
+  @CreatesObligation("this")
+  void resetThis() {}
+
+  static @MustCall({}) CreatesObligationSimple makeNoMC() {
+    return null;
+  }
+
+  static void test1() {
+    CreatesObligationSimple cos = makeNoMC();
+    @MustCall({}) CreatesObligationSimple a = cos;
+    cos.reset();
+    // :: error: assignment
+    @MustCall({}) CreatesObligationSimple b = cos;
+    @MustCall("a") CreatesObligationSimple c = cos;
+  }
+
+  static void test2() {
+    CreatesObligationSimple cos = makeNoMC();
+    @MustCall({}) CreatesObligationSimple a = cos;
+    cos.resetThis();
+    // :: error: assignment
+    @MustCall({}) CreatesObligationSimple b = cos;
+    @MustCall("a") CreatesObligationSimple c = cos;
+  }
+
+  static void test3() {
+    Object cos = makeNoMC();
+    @MustCall({}) Object a = cos;
+    // :: error: createsobligation.target.unparseable
+    ((CreatesObligationSimple) cos).reset();
+    // It would be better to issue an assignment incompatible error here, but the
+    // error above is okay too.
+    @MustCall({}) Object b = cos;
+    @MustCall("a") Object c = cos;
+  }
+
+  // Rewrite of test3 that follows the instructions in the error message.
+  static void test4() {
+    Object cos = makeNoMC();
+    @MustCall({}) Object a = cos;
+    CreatesObligationSimple r = ((CreatesObligationSimple) cos);
+    r.reset();
+    // :: error: assignment
+    @MustCall({}) Object b = r;
+    @MustCall("a") Object c = r;
+  }
+}
diff --git a/checker/tests/mustcall/EditLogInputStream.java b/checker/tests/mustcall/EditLogInputStream.java
new file mode 100644
index 0000000..4fbb58b
--- /dev/null
+++ b/checker/tests/mustcall/EditLogInputStream.java
@@ -0,0 +1,11 @@
+import java.io.*;
+import java.util.*;
+
+abstract class EditLogInputStream implements Closeable {
+  public abstract boolean isLocalLog();
+}
+
+interface JournalSet extends Closeable {
+  static final Comparator<EditLogInputStream> LOCAL_LOG_PREFERENCE_COMPARATOR =
+      Comparator.comparing(EditLogInputStream::isLocalLog).reversed();
+}
diff --git a/checker/tests/mustcall/FieldInitializationWithGeneric.java b/checker/tests/mustcall/FieldInitializationWithGeneric.java
new file mode 100644
index 0000000..717d0b3
--- /dev/null
+++ b/checker/tests/mustcall/FieldInitializationWithGeneric.java
@@ -0,0 +1,9 @@
+// based on a false positive I found in Zookeeper
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+class FieldInitializationWithGeneric {
+  private Set<String> activeObservers =
+      Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
+}
diff --git a/checker/tests/mustcall/FileDescriptors.java b/checker/tests/mustcall/FileDescriptors.java
new file mode 100644
index 0000000..b48345a
--- /dev/null
+++ b/checker/tests/mustcall/FileDescriptors.java
@@ -0,0 +1,18 @@
+// A test for some issues related to the getFD() method in RandomAccessFile.
+
+import java.io.*;
+import org.checkerframework.checker.mustcall.qual.*;
+
+class FileDescriptors {
+  void test(@Owning RandomAccessFile r) throws Exception {
+    @MustCall("close") FileDescriptor fd = r.getFD();
+    // :: error: assignment
+    @MustCall({}) FileDescriptor fd2 = r.getFD();
+  }
+
+  void test2(@Owning RandomAccessFile r) throws Exception {
+    @MustCall("close") FileInputStream f = new FileInputStream(r.getFD());
+    // :: error: assignment
+    @MustCall({}) FileInputStream f2 = new FileInputStream(r.getFD());
+  }
+}
diff --git a/checker/tests/mustcall/InferTypeArgs.java b/checker/tests/mustcall/InferTypeArgs.java
new file mode 100644
index 0000000..318bdcc
--- /dev/null
+++ b/checker/tests/mustcall/InferTypeArgs.java
@@ -0,0 +1,22 @@
+// A test case from the CF's all-systems tests that fails with the MC checker unless an
+// implicit upper bound is made explicit.
+
+class CFAbstractValue<V extends CFAbstractValue<V>> {}
+
+class CFAbstractAnalysis<V extends CFAbstractValue<V>> {}
+
+class GenericAnnotatedTypeFactory<
+    Value extends CFAbstractValue<Value>, FlowAnalysis extends CFAbstractAnalysis<Value>> {
+
+  protected FlowAnalysis createFlowAnalysis() {
+    FlowAnalysis result = invokeConstructorFor();
+    return result;
+  }
+
+  // The difference between this version of this test and the all-systems version is the "extends
+  // Object" on
+  // the next line.
+  public static <T extends Object> T invokeConstructorFor() {
+    return null;
+  }
+}
diff --git a/checker/tests/mustcall/ListOfMustCall.java b/checker/tests/mustcall/ListOfMustCall.java
new file mode 100644
index 0000000..37e63c8
--- /dev/null
+++ b/checker/tests/mustcall/ListOfMustCall.java
@@ -0,0 +1,31 @@
+// A test that checks that parameterized classes in the JDK don't cause false positives
+// when they are used with an @MustCall-annotated class.
+
+import java.util.*;
+import org.checkerframework.checker.mustcall.qual.*;
+
+@MustCall("a") class ListOfMustCall {
+  static void test(ListOfMustCall lm) {
+    List<ListOfMustCall> l = new ArrayList<>();
+    // add(E e) takes an object of the type argument's type
+    l.add(lm);
+    // remove(Object e) takes an object
+    l.remove(lm);
+  }
+
+  static void test2(ListOfMustCall lm) {
+    List<@MustCall("a") ListOfMustCall> l = new ArrayList<>();
+    l.add(lm);
+    l.remove(lm);
+  }
+
+  static void test3(ListOfMustCall lm) {
+    List<? extends ListOfMustCall> l = new ArrayList<>();
+    l.remove(lm);
+  }
+
+  static void test4(ListOfMustCall lm) {
+    List<? extends @MustCall("a") ListOfMustCall> l = new ArrayList<>();
+    l.remove(lm);
+  }
+}
diff --git a/checker/tests/mustcall/LogTheSocket.java b/checker/tests/mustcall/LogTheSocket.java
new file mode 100644
index 0000000..cf80617
--- /dev/null
+++ b/checker/tests/mustcall/LogTheSocket.java
@@ -0,0 +1,74 @@
+// This test case was intended to simulate the code below, which issued
+// a false positive at the call to LOG.warn() because the socket is @MustCall("close"):
+//
+//     synchronized void closeSockets() {
+//       for (ServerSocket serverSocket : serverSockets) {
+//           if (!serverSocket.isClosed()) {
+//               try {
+//                   serverSocket.close();
+//               } catch (IOException e) {
+//                   LOG.warn("Ignoring unexpected exception during close {}", serverSocket, e);
+//               }
+//           }
+//       }
+//    }
+//
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.nio.channels.SocketChannel;
+import org.checkerframework.checker.mustcall.qual.*;
+
+class LogTheSocket {
+
+  @NotOwning ServerSocket s;
+
+  @MustCall("") Object s2;
+
+  void testAssign(@Owning ServerSocket s1) {
+    s = s1;
+    // :: error: assignment
+    s2 = s1;
+  }
+
+  void logVarargs(String s, Object... objects) {}
+
+  void logNoVarargs(String s, Object object) {}
+
+  void test(ServerSocket serverSocket) {
+    if (!serverSocket.isClosed()) {
+      try {
+        serverSocket.close();
+      } catch (IOException e) {
+        logVarargs("Ignoring unexpected exception during close {}", serverSocket, e);
+      }
+    }
+  }
+
+  void test2(ServerSocket serverSocket) {
+    if (!serverSocket.isClosed()) {
+      try {
+        serverSocket.close();
+      } catch (IOException e) {
+        logNoVarargs("Ignoring unexpected exception during close {}", serverSocket);
+      }
+    }
+  }
+
+  // This is (mostly) copied from ACSocketTest; under a previous implementation of the
+  // ownership-transfer scheme,
+  // it caused false positive warnings from the Must Call checker.
+  SocketChannel createSock() throws IOException {
+    SocketChannel sock;
+    sock = SocketChannel.open();
+    sock.configureBlocking(false);
+    sock.socket().setSoLinger(false, -1);
+    sock.socket().setTcpNoDelay(true);
+    return sock;
+  }
+
+  @SuppressWarnings("mustcall") // https://github.com/typetools/checker-framework/pull/3867
+  void testPrintln(ServerSocket s) {
+    System.out.println(s);
+  }
+}
diff --git a/checker/tests/mustcall/MapWrap.java b/checker/tests/mustcall/MapWrap.java
new file mode 100644
index 0000000..f2a65cd
--- /dev/null
+++ b/checker/tests/mustcall/MapWrap.java
@@ -0,0 +1,21 @@
+// A test for a class that wraps a map. I found a similar example in Zookeeper that causes false
+// positives.
+
+import java.util.HashMap;
+import org.checkerframework.checker.mustcall.qual.*;
+
+class MapWrap<E> {
+  HashMap<E, String> impl = new HashMap<E, String>();
+
+  String remove(E e) {
+    // remove should permit any object: its signature is remove(Object key), *not* remove(E key)
+    String old = impl.remove(e);
+    return old;
+  }
+
+  String remove2(@MustCall({}) E e) {
+    // remove should permit any object: its signature is remove(Object key), *not* remove(E key)
+    String old = impl.remove(e);
+    return old;
+  }
+}
diff --git a/checker/tests/mustcall/MustCallAliasImpl.java b/checker/tests/mustcall/MustCallAliasImpl.java
new file mode 100644
index 0000000..995dd76
--- /dev/null
+++ b/checker/tests/mustcall/MustCallAliasImpl.java
@@ -0,0 +1,21 @@
+// A simple test that the extra obligations that MustCallAlias imposes are
+// respected.
+
+// @skip-test until the checks are implemented
+
+import java.io.*;
+import org.checkerframework.checker.mustcall.qual.*;
+
+public class MustCallAliasImpl implements Closeable {
+
+  @Owning final Closeable foo;
+
+  public @MustCallAlias MustCallAliasImpl(@MustCallAlias Closeable foo) {
+    this.foo = foo;
+  }
+
+  @Override
+  public void close() throws IOException {
+    this.foo.close();
+  }
+}
diff --git a/checker/tests/mustcall/MustCallAliasImplNoOwning.java b/checker/tests/mustcall/MustCallAliasImplNoOwning.java
new file mode 100644
index 0000000..8b7fe45
--- /dev/null
+++ b/checker/tests/mustcall/MustCallAliasImplNoOwning.java
@@ -0,0 +1,23 @@
+// A simple test that the extra obligations that MustCallAlias imposes are
+// respected. Identical to MustCallAliasImpl.java except the @Owning annotation
+// on foo has been removed, making this unverifiable.
+
+// @skip-test until the checks are implemented
+
+import java.io.*;
+import org.checkerframework.checker.mustcall.qual.*;
+
+public class MustCallAliasImplNoOwning implements Closeable {
+
+  final Closeable foo;
+
+  // :: error: mustcall.alias.invalid
+  public @MustCallAlias MustCallAliasImplNoOwning(@MustCallAlias Closeable foo) {
+    this.foo = foo;
+  }
+
+  @Override
+  public void close() throws IOException {
+    this.foo.close();
+  }
+}
diff --git a/checker/tests/mustcall/MyDataInputStream.java b/checker/tests/mustcall/MyDataInputStream.java
new file mode 100644
index 0000000..5aeca21
--- /dev/null
+++ b/checker/tests/mustcall/MyDataInputStream.java
@@ -0,0 +1,23 @@
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+public class MyDataInputStream extends DataInputStream {
+  public MyDataInputStream(InputStream in) {
+    super(in);
+  }
+
+  public void readFully(long position, ByteBuffer buf) throws IOException {
+    if (in instanceof ByteBufferPositionedReadable) {
+      ((ByteBufferPositionedReadable) in).readFully(position, buf);
+    } else {
+      throw new UnsupportedOperationException(
+          "Byte-buffer pread " + "unsupported by " + in.getClass().getCanonicalName());
+    }
+  }
+
+  interface ByteBufferPositionedReadable {
+    void readFully(long position, ByteBuffer buf) throws IOException;
+  }
+}
diff --git a/checker/tests/mustcall/NonOwningPolyInteraction.java b/checker/tests/mustcall/NonOwningPolyInteraction.java
new file mode 100644
index 0000000..e2f0aaf
--- /dev/null
+++ b/checker/tests/mustcall/NonOwningPolyInteraction.java
@@ -0,0 +1,43 @@
+// A test that non-owning method parameters are really treated as @MustCall({})
+// wrt polymorphic types. Based on some false positives in Zookeeper.
+
+import java.io.*;
+import org.checkerframework.checker.mustcall.qual.*;
+
+class NonOwningPolyInteraction {
+  void foo(@NotOwning InputStream instream) {
+    @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream);
+    @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream);
+  }
+
+  void bar(@Owning InputStream instream) {
+    // :: error: assignment
+    @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream);
+    @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream);
+  }
+
+  // default anno for params in @NotOwning
+  void baz(InputStream instream) {
+    @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream);
+    @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream);
+  }
+
+  NonOwningPolyInteraction(@NotOwning InputStream instream) {
+    @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream);
+    @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream);
+  }
+
+  // extra param(s) here and on the next constructor because Java requires constructors to have
+  // different signatures.
+  NonOwningPolyInteraction(@Owning InputStream instream, int x) {
+    // :: error: assignment
+    @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream);
+    @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream);
+  }
+
+  // default anno for params in @NotOwning
+  NonOwningPolyInteraction(InputStream instream, int x, int y) {
+    @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream);
+    @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream);
+  }
+}
diff --git a/checker/tests/mustcall/NullableTransfer.java b/checker/tests/mustcall/NullableTransfer.java
new file mode 100644
index 0000000..b3755e5
--- /dev/null
+++ b/checker/tests/mustcall/NullableTransfer.java
@@ -0,0 +1,18 @@
+// A test that the must-call type of an object tested against null
+// is always empty.
+
+import java.io.*;
+import org.checkerframework.checker.mustcall.qual.*;
+
+class NullableTransfer {
+
+  void test(@Owning InputStream is) {
+    if (is == null) {
+      @MustCall({}) InputStream is2 = is;
+    } else {
+      // :: error: assignment
+      @MustCall({}) InputStream is3 = is;
+      @MustCall("close") InputStream is4 = is;
+    }
+  }
+}
diff --git a/checker/tests/mustcall/OwningParams.java b/checker/tests/mustcall/OwningParams.java
new file mode 100644
index 0000000..7d7f5e0
--- /dev/null
+++ b/checker/tests/mustcall/OwningParams.java
@@ -0,0 +1,19 @@
+// Tests that parameters (including receiver parameters) marked as @Owning are still checked.
+
+import org.checkerframework.checker.mustcall.qual.*;
+
+class OwningParams {
+  static void o1(@Owning OwningParams o) {}
+
+  void o2(@Owning OwningParams this) {}
+
+  void test(@Owning @MustCall({"a"}) OwningParams o, @Owning OwningParams p) {
+    // :: error: argument
+    o1(o);
+    // TODO: this error doesn't show up! See MustCallVisitor#skipReceiverSubtypeCheck
+    //  error: method.invocation
+    o.o2();
+    o1(p);
+    p.o2();
+  }
+}
diff --git a/checker/tests/mustcall/PlumeUtilRequiredAnnotations.java b/checker/tests/mustcall/PlumeUtilRequiredAnnotations.java
new file mode 100644
index 0000000..91f5257
--- /dev/null
+++ b/checker/tests/mustcall/PlumeUtilRequiredAnnotations.java
@@ -0,0 +1,51 @@
+// This is a test case that shows off some places in plume-util where
+// annotations were required, even though we'd have preferred the defaulting
+// rules to result in those annotations being the defaults.
+// See the discussion on https://github.com/kelloggm/object-construction-checker/pull/363
+// and https://github.com/plume-lib/plume-util/pull/126 for more details, especially
+// on why changing the default isn't feasible.
+
+import java.util.*;
+import org.checkerframework.checker.mustcall.qual.*;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class PlumeUtilRequiredAnnotations {
+  // In the real version of this code, there is only one type parameter.
+  // T is the unannotated version of the parameter - i.e., what it was before
+  // we first ran the Must Call Checker. S is the annotated version. Adding the
+  // annotation itself is immaterial - what's important is that the bound
+  // must be explicit rather than implicit (see that the eqR field never issue errors,
+  // just like the eqS fields).
+  class MultiRandSelector<T, S extends @Nullable @MustCall Object, R extends Object> {
+    // :: error: type.argument
+    private Partitioner<T, T> eqT;
+    private Partitioner<S, S> eqS;
+    private Partitioner<R, R> eqR;
+
+    // Adding annotations to the definition of Partitioner doesn't fix this problem:
+    // :: error: type.argument
+    private Partitioner2<T, T> eqT2;
+    private Partitioner2<S, S> eqS2;
+    private Partitioner2<R, R> eqR2;
+
+    // But removing the explicit bounds on Partitioner does (not feasible in this case, though,
+    // because
+    // of the @Nullable annotations):
+    private Partitioner3<T, T> eqT3;
+    private Partitioner3<S, S> eqS3;
+    private Partitioner3<R, R> eqR3;
+  }
+
+  interface Partitioner<ELEMENT extends @Nullable Object, CLASS extends @Nullable Object> {
+    CLASS assignToBucket(ELEMENT obj);
+  }
+
+  interface Partitioner2<
+      ELEMENT extends @Nullable @MustCall Object, CLASS extends @Nullable @MustCall Object> {
+    CLASS assignToBucket(ELEMENT obj);
+  }
+
+  interface Partitioner3<ELEMENT, CLASS> {
+    CLASS assignToBucket(ELEMENT obj);
+  }
+}
diff --git a/checker/tests/mustcall/PolyTests.java b/checker/tests/mustcall/PolyTests.java
new file mode 100644
index 0000000..21c0f0b
--- /dev/null
+++ b/checker/tests/mustcall/PolyTests.java
@@ -0,0 +1,41 @@
+// Unit tests for the poly annotation.
+
+import org.checkerframework.checker.mustcall.qual.*;
+
+@MustCall("close") class PolyTests {
+  static @PolyMustCall Object id(@PolyMustCall Object obj) {
+    return obj;
+  }
+
+  static void test1(@Owning @MustCall("close") Object o) {
+    @MustCall("close") Object o1 = id(o);
+    // :: error: assignment
+    @MustCall({}) Object o2 = id(o);
+  }
+
+  static void test2(@Owning @MustCall({}) Object o) {
+    @MustCall("close") Object o1 = id(o);
+    @MustCall({}) Object o2 = id(o);
+  }
+
+  // These sort of constructors will always appear in stub files and are unverifiable for now.
+  @SuppressWarnings("mustcall:annotations.on.use")
+  @PolyMustCall PolyTests(@PolyMustCall Object obj) {}
+
+  static void test3(@Owning @MustCall({"close"}) Object o) {
+    @MustCall("close") Object o1 = new PolyTests(o);
+    // :: error: assignment
+    @MustCall({}) Object o2 = new PolyTests(o);
+  }
+
+  static void test4(@Owning @MustCall({}) Object o) {
+    @MustCall("close") Object o1 = new PolyTests(o);
+    @MustCall({}) Object o2 = new PolyTests(o);
+  }
+
+  static void testArbitary(@Owning PolyTests p) {
+    @MustCall("close") Object o1 = p;
+    // :: error: assignment
+    @MustCall({}) Object o2 = p;
+  }
+}
diff --git a/checker/tests/mustcall/SimpleException.java b/checker/tests/mustcall/SimpleException.java
new file mode 100644
index 0000000..b989d99
--- /dev/null
+++ b/checker/tests/mustcall/SimpleException.java
@@ -0,0 +1,15 @@
+// A test that throwing and catching exceptions doesn't cause false positives.
+
+class SimpleException {
+  void thrower() throws Exception {
+    throw new RuntimeException("some exception");
+  }
+
+  void test() {
+    try {
+      thrower();
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/checker/tests/mustcall/SimpleStreamExample.java b/checker/tests/mustcall/SimpleStreamExample.java
new file mode 100644
index 0000000..510978b
--- /dev/null
+++ b/checker/tests/mustcall/SimpleStreamExample.java
@@ -0,0 +1,9 @@
+// Based on a false positive in Zookeeper
+
+import java.util.*;
+
+class SimpleStreamExample {
+  static void test(List<SimpleStreamExample> s) {
+    s.stream().filter(str -> str == null);
+  }
+}
diff --git a/checker/tests/mustcall/SocketBufferedReader.java b/checker/tests/mustcall/SocketBufferedReader.java
new file mode 100644
index 0000000..3416ce7
--- /dev/null
+++ b/checker/tests/mustcall/SocketBufferedReader.java
@@ -0,0 +1,23 @@
+// a test for missing mustcall propagation that might have caused a false positive?
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.*;
+import org.checkerframework.checker.mustcall.qual.*;
+
+class SocketBufferedReader {
+  void test(String address, int port) {
+    try {
+      Socket socket = new Socket(address, 80);
+      PrintStream out = new PrintStream(socket.getOutputStream());
+      BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+      @MustCall("close") BufferedReader reader = in;
+      // :: error: assignment
+      @MustCall({}) BufferedReader reader2 = in;
+      in.close();
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/checker/tests/mustcall/StreamBool.java b/checker/tests/mustcall/StreamBool.java
new file mode 100644
index 0000000..f6c2b0f
--- /dev/null
+++ b/checker/tests/mustcall/StreamBool.java
@@ -0,0 +1,11 @@
+// A test case for a false positive in hfds.
+
+import java.io.InputStream;
+
+class StreamBool {
+  InputStream stream;
+
+  boolean isActive() {
+    return stream != null;
+  }
+}
diff --git a/checker/tests/mustcall/StringSort.java b/checker/tests/mustcall/StringSort.java
new file mode 100644
index 0000000..c76a097
--- /dev/null
+++ b/checker/tests/mustcall/StringSort.java
@@ -0,0 +1,10 @@
+// Another false positive I found in Zookeeper.
+
+import java.util.*;
+
+class StringSort {
+  public static void sort() {
+    List<String> myList = new ArrayList<String>();
+    Collections.sort(myList);
+  }
+}
diff --git a/checker/tests/mustcall/Subtype0.java b/checker/tests/mustcall/Subtype0.java
new file mode 100644
index 0000000..1e75de4
--- /dev/null
+++ b/checker/tests/mustcall/Subtype0.java
@@ -0,0 +1,61 @@
+// A test that the @InheritedMustCall declaration annotation works correctly.
+
+import org.checkerframework.checker.mustcall.qual.*;
+
+@InheritableMustCall("a")
+public class Subtype0 {
+
+  public class Subtype1 extends Subtype0 {
+    void m1() {}
+  }
+
+  public class Subtype2 extends Subtype1 {}
+
+  static void test(
+      @Owning Subtype0 s0,
+      @Owning Subtype1 s1,
+      @Owning Subtype2 s2,
+      @Owning Subtype3 s3,
+      @Owning Subtype4 s4) {
+    // :: error: assignment
+    @MustCall({}) Object obj1 = s0;
+    @MustCall({"a"}) Object obj2 = s0;
+
+    // :: error: assignment
+    @MustCall({}) Object obj3 = s1;
+    @MustCall({"a"}) Object obj4 = s1;
+
+    // :: error: assignment
+    @MustCall({}) Object obj5 = s2;
+    @MustCall({"a"}) Object obj6 = s2;
+
+    @MustCall({}) Object obj7 = s3;
+    @MustCall({"a"}) Object obj8 = s3;
+
+    @MustCall({}) Object obj9 = s4;
+    @MustCall({"a"}) Object obj10 = s4;
+  }
+
+  @MustCall({})
+  // :: error: inconsistent.mustcall.subtype :: error: super.invocation
+  public class Subtype3 extends Subtype0 {}
+
+  @InheritableMustCall({})
+  // :: error: super.invocation
+  public class Subtype4 extends Subtype0 {}
+
+  @MustCall({"a"}) public class Subtype5 extends Subtype0 {}
+
+  @InheritableMustCall({"a"})
+  public class Subtype6 extends Subtype0 {}
+
+  public class Container {
+    Subtype0 in;
+
+    void test() {
+      if (in instanceof Subtype1) {
+        ((Subtype1) in).m1();
+      }
+    }
+  }
+}
diff --git a/checker/tests/mustcall/Subtyping.java b/checker/tests/mustcall/Subtyping.java
new file mode 100644
index 0000000..3c563a6
--- /dev/null
+++ b/checker/tests/mustcall/Subtyping.java
@@ -0,0 +1,57 @@
+// simple subtyping test for the MustCall annotation
+
+import org.checkerframework.checker.mustcall.qual.*;
+
+class Subtyping {
+
+  Object unannotatedObj;
+
+  void test_act(@Owning @MustCallUnknown Object o) {
+    @MustCallUnknown Object act = o;
+    // :: error: assignment
+    @MustCall("close") Object file = o;
+    // :: error: assignment
+    @MustCall({"close", "read"}) Object f2 = o;
+    // :: error: assignment
+    @MustCall({}) Object notAfile = o;
+    // :: error: assignment
+    unannotatedObj = o;
+  }
+
+  void test_close(@Owning @MustCall("close") Object o) {
+    @MustCallUnknown Object act = o;
+    @MustCall("close") Object file = o;
+    @MustCall({"close", "read"}) Object f2 = o;
+    // :: error: assignment
+    @MustCall({}) Object notAfile = o;
+    // :: error: assignment
+    unannotatedObj = o;
+  }
+
+  void test_close_read(@Owning @MustCall({"close", "read"}) Object o) {
+    @MustCallUnknown Object act = o;
+    // :: error: assignment
+    @MustCall("close") Object file = o;
+    @MustCall({"close", "read"}) Object f2 = o;
+    // :: error: assignment
+    @MustCall({}) Object notAfile = o;
+    // :: error: assignment
+    unannotatedObj = o;
+  }
+
+  void test_blank(@Owning @MustCall({}) Object o) {
+    @MustCallUnknown Object act = o;
+    @MustCall("close") Object file = o;
+    @MustCall({"close", "read"}) Object f2 = o;
+    @MustCall({}) Object notAfile = o;
+    unannotatedObj = o;
+  }
+
+  void test_unannotated(@Owning Object o) {
+    @MustCallUnknown Object act = o;
+    @MustCall("close") Object file = o;
+    @MustCall({"close", "read"}) Object f2 = o;
+    @MustCall({}) Object notAfile = o;
+    unannotatedObj = o;
+  }
+}
diff --git a/checker/tests/mustcall/SystemInOut.java b/checker/tests/mustcall/SystemInOut.java
new file mode 100644
index 0000000..944974f
--- /dev/null
+++ b/checker/tests/mustcall/SystemInOut.java
@@ -0,0 +1,14 @@
+// A test that the checker doesn't ask you to close System.in, System.out, or System.err.
+
+import java.io.*;
+import java.util.Scanner;
+import org.checkerframework.checker.mustcall.qual.*;
+
+class SystemInOut {
+  void test() {
+    @MustCall({}) InputStream in = System.in;
+    @MustCall({}) OutputStream out = System.out;
+    @MustCall({}) OutputStream err = System.err;
+    @MustCall({}) Scanner sysIn = new Scanner(System.in);
+  }
+}
diff --git a/checker/tests/mustcall/ToStringOnSocket.java b/checker/tests/mustcall/ToStringOnSocket.java
new file mode 100644
index 0000000..02d80d8
--- /dev/null
+++ b/checker/tests/mustcall/ToStringOnSocket.java
@@ -0,0 +1,18 @@
+// A test for a false positive I found in Zookeeper. Sockets are must-close, but the
+// result of calling toString on them shouldn't be!
+
+import java.net.Socket;
+
+class ToStringOnSocket {
+  void log(String string) {
+    System.out.println(string);
+  }
+
+  void test(Socket socket) {
+    log("bad socket: " + socket);
+  }
+
+  void test2(Socket socket) {
+    log("bad socket: " + socket.toString());
+  }
+}
diff --git a/checker/tests/mustcall/TryWithResourcesCrash.java b/checker/tests/mustcall/TryWithResourcesCrash.java
new file mode 100644
index 0000000..8990b8d
--- /dev/null
+++ b/checker/tests/mustcall/TryWithResourcesCrash.java
@@ -0,0 +1,31 @@
+// A test case for a crash while checking hfds.
+
+import java.io.Closeable;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+class TryWithResourcesCrash {
+  void test(FileSystem fs, byte[] bytes, String path) throws IOException {
+    try (FSDataOutputStream out = fs.createFile(path).overwrite(true).build()) {
+      out.write(bytes);
+    }
+  }
+
+  class FSDataOutputStream extends DataOutputStream {
+    FSDataOutputStream(OutputStream os) {
+      super(os);
+    }
+  }
+
+  abstract class FSDataOutputStreamBuilder<
+      S extends FSDataOutputStream, B extends FSDataOutputStreamBuilder<S, B>> {
+    abstract S build();
+
+    abstract B overwrite(boolean b);
+  }
+
+  abstract class FileSystem implements Closeable {
+    abstract FSDataOutputStreamBuilder createFile(String s);
+  }
+}
diff --git a/checker/tests/mustcall/TryWithResourcesSimple.java b/checker/tests/mustcall/TryWithResourcesSimple.java
new file mode 100644
index 0000000..6563e06
--- /dev/null
+++ b/checker/tests/mustcall/TryWithResourcesSimple.java
@@ -0,0 +1,49 @@
+// A test that try-with-resources variables are always @MustCall({}).
+
+import java.io.*;
+import java.net.*;
+import org.checkerframework.checker.mustcall.qual.MustCall;
+
+public class TryWithResourcesSimple {
+  static void test(String address, int port) {
+    try (Socket socket = new Socket(address, port)) {
+      @MustCall({}) Object s = socket;
+    } catch (Exception e) {
+
+    }
+  }
+
+  @SuppressWarnings("mustcall:annotations.on.use")
+  public static @MustCall({"close", "myMethod"}) Socket getFancySocket() {
+    return null;
+  }
+
+  void test_fancy_sock(String address, int port) {
+    // This is illegal, because getFancySock()'s return type has another MC method beyond "close",
+    // which is the only MC method for Socket itself.
+    // :: error: assignment
+    try (Socket socket = getFancySocket()) {
+      @MustCall({}) Object s = socket;
+    } catch (Exception e) {
+
+    }
+  }
+
+  static void test_poly(String address, int port) {
+    try (Socket socket = new Socket(address, port)) {
+      // getChannel is @MustCallAlias (= poly) with the socket, so it should also be @MC({})
+      @MustCall({}) Object s = socket.getChannel();
+    } catch (Exception e) {
+
+    }
+  }
+
+  static void test_two_mca_variables(String address, int port) {
+    try (Socket socket = new Socket(address, port);
+        InputStream in = socket.getInputStream()) {
+      @MustCall({}) Object s = in;
+    } catch (Exception e) {
+
+    }
+  }
+}
diff --git a/checker/tests/mustcall/TypeArgs.java b/checker/tests/mustcall/TypeArgs.java
new file mode 100644
index 0000000..cccec6b
--- /dev/null
+++ b/checker/tests/mustcall/TypeArgs.java
@@ -0,0 +1,92 @@
+import org.checkerframework.checker.mustcall.qual.MustCall;
+
+public class TypeArgs {
+
+  static class A<Q extends @MustCall({"carly"}) Object> {}
+
+  // :: error: (type.argument)
+  static class B<S> extends A<S> {}
+
+  public <T> void f1(Generic<T> real, Generic<? super T> other, boolean flag) {
+    // :: error: (type.argument)
+    f2(flag ? real : other);
+  }
+
+  <@MustCall({"carly"}) Q extends @MustCall({"carly"}) Object> void f2(Generic<? extends Q> parm) {}
+
+  interface Generic<F> {}
+
+  void m3(
+      @MustCall({}) Object a,
+      @MustCall({"foo"}) Object b,
+      @MustCall({"bar"}) Object c,
+      @MustCall({"foo", "bar"}) Object d) {
+    requireNothing1(a);
+    requireNothing2(a);
+    requireNothing1(b);
+    requireNothing2(b);
+    requireNothing1(c);
+    requireNothing2(c);
+    requireNothing1(d);
+    requireNothing2(d);
+
+    requireFoo1(a);
+    requireFoo2(a);
+    requireFoo1(b);
+    requireFoo2(b);
+    requireFoo1(c);
+    requireFoo2(c);
+    requireFoo1(d);
+    requireFoo2(d);
+
+    requireBar1(a);
+    requireBar2(a);
+    requireBar1(b);
+    requireBar2(b);
+    requireBar1(c);
+    requireBar2(c);
+    requireBar1(d);
+    requireBar2(d);
+
+    requireFooBar1(a);
+    requireFooBar2(a);
+    requireFooBar1(b);
+    requireFooBar2(b);
+    requireFooBar1(c);
+    requireFooBar2(c);
+    requireFooBar1(d);
+    requireFooBar2(d);
+  }
+
+  public static <T extends @MustCall({}) Object> T requireNothing1(T obj) {
+    return obj;
+  }
+
+  public static <T> @MustCall({}) T requireNothing2(@MustCall({}) T obj) {
+    return obj;
+  }
+
+  public static <T extends @MustCall({"foo"}) Object> T requireFoo1(T obj) {
+    return obj;
+  }
+
+  public static <T> @MustCall({"foo"}) T requireFoo2(@MustCall({"foo"}) T obj) {
+    return obj;
+  }
+
+  public static <T extends @MustCall({"bar"}) Object> T requireBar1(T obj) {
+    return obj;
+  }
+
+  public static <T> @MustCall({"bar"}) T requireBar2(@MustCall({"bar"}) T obj) {
+    return obj;
+  }
+
+  public static <T extends @MustCall({"foo", "bar"}) Object> T requireFooBar1(T obj) {
+    return obj;
+  }
+
+  public static <T> @MustCall({"foo", "bar"}) T requireFooBar2(@MustCall({"foo", "bar"}) T obj) {
+    return obj;
+  }
+}
diff --git a/checker/tests/nolightweightownership/BorrowOnReturn.java b/checker/tests/nolightweightownership/BorrowOnReturn.java
new file mode 100644
index 0000000..63c7991
--- /dev/null
+++ b/checker/tests/nolightweightownership/BorrowOnReturn.java
@@ -0,0 +1,48 @@
+// tests that the Must Call checker respects the Owning and NotOwning annotations on return values.
+
+import org.checkerframework.checker.mustcall.qual.*;
+
+class BorrowOnReturn {
+  @MustCall("a") class Foo {
+    void a() {}
+  }
+
+  @Owning
+  Object getOwnedFoo() {
+    // :: error: return
+    return new Foo();
+  }
+
+  Object getNoAnnoFoo() {
+    // Treat as owning, so warn
+    // :: error: return
+    return new Foo();
+  }
+
+  @NotOwning
+  Object getNotOwningFooWrong() {
+    // :: error: return
+    return new Foo();
+  }
+
+  Object getNotOwningFooRightButNoNotOwningAnno() {
+    Foo f = new Foo();
+    f.a();
+    // This is still an error for now, because it's treated as an owning pointer. TODO: fix this
+    // kind of FP?
+    // :: error: return
+    return f;
+  }
+
+  @NotOwning
+  Object getNotOwningFooRight() {
+    Foo f = new Foo();
+    f.a();
+    // :: error: return
+    return f;
+  }
+
+  @MustCall("a") Object getNotOwningFooRight2() {
+    return new Foo();
+  }
+}
diff --git a/checker/tests/nolightweightownership/NonOwningPolyInteraction.java b/checker/tests/nolightweightownership/NonOwningPolyInteraction.java
new file mode 100644
index 0000000..a93deae
--- /dev/null
+++ b/checker/tests/nolightweightownership/NonOwningPolyInteraction.java
@@ -0,0 +1,44 @@
+// A test that non-owning method parameters are really treated as @MustCall({})
+// wrt polymorphic types. Based on some false positives in Zookeeper.
+//
+// This version is modified to expect that owning/notowning annotations do nothing,
+// for the -AnoLightweightOwnership flag.
+
+import java.io.*;
+import org.checkerframework.checker.mustcall.qual.*;
+
+class NonOwningPolyInteraction {
+  void foo(@NotOwning InputStream instream) {
+    @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream);
+    @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream);
+  }
+
+  void bar(@Owning InputStream instream) {
+    @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream);
+    @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream);
+  }
+
+  // default anno for params in @NotOwning
+  void baz(InputStream instream) {
+    @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream);
+    @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream);
+  }
+
+  NonOwningPolyInteraction(@NotOwning InputStream instream) {
+    @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream);
+    @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream);
+  }
+
+  // extra param(s) here and on the next constructor because Java requires constructors to have
+  // different signatures.
+  NonOwningPolyInteraction(@Owning InputStream instream, int x) {
+    @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream);
+    @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream);
+  }
+
+  // default anno for params in @NotOwning
+  NonOwningPolyInteraction(InputStream instream, int x, int y) {
+    @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream);
+    @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream);
+  }
+}
diff --git a/checker/tests/nolightweightownership/OwningParams.java b/checker/tests/nolightweightownership/OwningParams.java
new file mode 100644
index 0000000..29dd91a
--- /dev/null
+++ b/checker/tests/nolightweightownership/OwningParams.java
@@ -0,0 +1,13 @@
+// This tests normally tests that parameters (including receiver parameters) marked as @Owning are
+// still checked.
+// This version is modified for -AnoLightweightOwnership to expect the opposite behavior.
+
+import org.checkerframework.checker.mustcall.qual.*;
+
+class OwningParams {
+  static void o1(@Owning OwningParams o) {}
+
+  void test(@Owning @MustCall({"a"}) OwningParams o) {
+    o1(o);
+  }
+}
diff --git a/checker/tests/nullness-asserts/NonNullMapValue.java b/checker/tests/nullness-asserts/NonNullMapValue.java
new file mode 100644
index 0000000..8032b46
--- /dev/null
+++ b/checker/tests/nullness-asserts/NonNullMapValue.java
@@ -0,0 +1,222 @@
+import java.io.PrintStream;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class NonNullMapValue {
+
+  // Discussion:
+  //
+  // It can be useful to indicate that all the values in a map are non-null.
+  // ("@NonNull" is redundant in this declaration, but I've written it
+  // explicitly because it is the annotation we are talking about.)
+  //   HashMap<String,@NonNull String> myMap;
+  //
+  // However, the get method's declaration is misleading (in the context of the nullness type
+  // system), since it can always return null no matter whether the map values are non-null:
+  //   V get(Object key) { ... return null; }
+  //
+  // Here are potential solutions:
+  //  * Forbid declaring values as non-null.  This is the wrong approach.  (It would also be hard to
+  //    express syntactically.)
+  //  * The checker could recognize a special new annotation on the return value of get, indicating
+  //    that its return type isn't merely inferred from the generic type, but is always nullable.
+  //    (This special new annotations could even be "@Nullable".  A different annotation may be
+  //    better, becase in general we would like to issue an error message when someone applies an
+  //    annotation to a generic type parameter.)
+  // Additionally, to reduce the number of false positive warnings caused by the fact that get's
+  // return value is nullable:
+  //  * Build a more specialized sophisticated flow analysis that checks that the passed key to
+  //    Map.containsKey() is either checked against Map.containsKey() or Map.keySet().
+
+  Map<String, @NonNull String> myMap;
+
+  NonNullMapValue(Map<String, @NonNull String> myMap) {
+    this.myMap = myMap;
+  }
+
+  void testMyMap(String key) {
+    @NonNull String value;
+    // :: error: (assignment)
+    value = myMap.get(key); // should issue warning
+    if (myMap.containsKey(key)) {
+      value = myMap.get(key);
+    }
+    for (String keyInMap : myMap.keySet()) {
+      // :: error: (assignment)
+      value = myMap.get(key); // should issue warning
+    }
+    for (String keyInMap : myMap.keySet()) {
+      value = myMap.get(keyInMap);
+    }
+    for (Map.Entry<@KeyFor("myMap") String, @NonNull String> entry : myMap.entrySet()) {
+      String keyInMap = entry.getKey();
+      value = entry.getValue();
+    }
+    for (Iterator<@KeyFor("myMap") String> iter = myMap.keySet().iterator(); iter.hasNext(); ) {
+      String keyInMap = iter.next();
+      // value = myMap.get(keyInMap);
+    }
+    value = myMap.containsKey(key) ? myMap.get(key) : "hello";
+  }
+
+  public static <T extends @NonNull Object> void print(
+      Map<T, List<T>> graph, PrintStream ps, int indent) {
+    for (T node : graph.keySet()) {
+      for (T child : graph.get(node)) {
+        ps.printf("  %s%n", child);
+      }
+      @NonNull List<T> children = graph.get(node);
+      for (T child : children) {
+        ps.printf("  %s%n", child);
+      }
+    }
+  }
+
+  public static <T extends @NonNull Object> void testAssertFlow(Map<T, List<T>> preds, T node) {
+    assert preds.containsKey(node);
+    for (T pred : preds.get(node)) {}
+  }
+
+  public static <T extends @NonNull Object> void testContainsKey1(Map<T, List<T>> dom, T pred) {
+    assert dom.containsKey(pred);
+    // Both of the next two lines should type-check.  The second one won't
+    // unless the checker knows that pred is a key in the map.
+    List<T> dom_of_pred1 = dom.get(pred);
+    @NonNull List<T> dom_of_pred2 = dom.get(pred);
+  }
+
+  public static <T extends @NonNull Object> void testContainsKey2(Map<T, List<T>> dom, T pred) {
+    if (!dom.containsKey(pred)) {
+      throw new Error();
+    }
+    // Both of the next two lines should type-check.  The second one won't
+    // unless the checker knows that pred is a key in the map.
+    List<T> dom_of_pred1 = dom.get(pred);
+    @NonNull List<T> dom_of_pred2 = dom.get(pred);
+  }
+
+  public static void process_unmatched_procedure_entries() {
+    HashMap<Integer, Date> call_hashmap = new HashMap<>();
+    for (Integer i : call_hashmap.keySet()) {
+      @NonNull Date d = call_hashmap.get(i);
+    }
+    Set<@KeyFor("call_hashmap") Integer> keys = call_hashmap.keySet();
+    for (Integer i : keys) {
+      @NonNull Date d = call_hashmap.get(i);
+    }
+    Set<@KeyFor("call_hashmap") Integer> keys_sorted =
+        new TreeSet<@KeyFor("call_hashmap") Integer>(call_hashmap.keySet());
+    for (Integer i : keys_sorted) {
+      @NonNull Date d = call_hashmap.get(i);
+    }
+  }
+
+  public static Object testPut(Map<Object, Object> map, Object key) {
+    if (!map.containsKey(key)) {
+      map.put(key, new Object());
+    }
+    return map.get(key);
+  }
+
+  public static Object testAssertGet(Map<Object, Object> map, Object key) {
+    assert map.get(key) != null;
+    return map.get(key);
+  }
+
+  public static Object testThrow(Map<Object, Object> map, Object key) {
+    if (!map.containsKey(key)) {
+      if (true) {
+        return "m";
+      } else {
+        throw new RuntimeException();
+      }
+    }
+    return map.get(key);
+  }
+
+  public void negateMap(Map<Object, Object> map, Object key) {
+    if (!map.containsKey(key)) {
+    } else {
+      @NonNull Object v = map.get(key);
+    }
+  }
+
+  public void withinElseInvalid(Map<Object, Object> map, Object key) {
+    if (map.containsKey(key)) {
+    } else {
+      // :: error: (assignment)
+      @NonNull Object v = map.get(key); // should issue warning
+    }
+  }
+
+  // Map.get should be annotated as @org.checkerframework.dataflow.qual.Pure
+  public static int mapGetSize(MyMap<Object, List<Object>> covered, Object file) {
+    return (covered.get(file) == null) ? 0 : covered.get(file).size();
+  }
+
+  interface MyMap<K, V> extends Map<K, V> {
+    // TODO: @AssertGenericNullnessIfTrue("get(#1)")
+    @org.checkerframework.dataflow.qual.Pure
+    public abstract boolean containsKey(@Nullable Object a1);
+
+    // We get an override warning, because we do not use the annotated JDK in the
+    // test suite. Ignore this.
+    @SuppressWarnings("override.return")
+    @org.checkerframework.dataflow.qual.Pure
+    public @Nullable V get(@Nullable Object o);
+  }
+
+  private static final String KEY = "key";
+  private static final String KEY2 = "key2";
+
+  void testAnd(MyMap<String, String> map, MyMap<String, @Nullable String> map2) {
+    if (map.containsKey(KEY)) {
+      map.get(KEY).toString();
+    }
+    // :: warning: (nulltest.redundant)
+    if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) {}
+    // :: error: (dereference.of.nullable) :: warning: (nulltest.redundant)
+    if (map2.containsKey(KEY2) && map2.get(KEY2).toString() != null) {}
+  }
+
+  void testAndWithIllegalMapAnnotation(MyMap2<String, String> map) {
+    if (map.containsKey(KEY)) {
+      map.get(KEY).toString();
+    }
+    // :: warning: (nulltest.redundant)
+    if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) {
+      // do nothing
+    }
+  }
+
+  interface MyMap2<K, V> {
+    @org.checkerframework.dataflow.qual.Pure
+    // This annotation is not legal on containsKey in general.  If the Map is declared as (say)
+    // Map<Object, @Nullable Object>, then get returns a nullable value.  We really want to say that
+    // if containsKey returns non-null, then get returns V rather than @Nullable V, but I don't know
+    // how to say that.
+    @EnsuresNonNullIf(result = true, expression = "get(#1)")
+    public abstract boolean containsKey(@Nullable Object a1);
+
+    @org.checkerframework.dataflow.qual.Pure
+    public abstract @Nullable V get(@Nullable Object a1);
+  }
+
+  interface MyMap3<K, V> {
+    @org.checkerframework.dataflow.qual.Pure
+    @EnsuresNonNullIf(result = true, expression = "get(#1)")
+    // The following error is issued because, unlike in interface MyMap2,
+    // this interface has no get() method.
+    // :: error: (flowexpr.parse.error)
+    boolean containsKey(@Nullable Object a1);
+  }
+}
diff --git a/checker/tests/nullness-asserts/TestAssumeAssertionsAreEnabled.java b/checker/tests/nullness-asserts/TestAssumeAssertionsAreEnabled.java
new file mode 100644
index 0000000..56aaed9
--- /dev/null
+++ b/checker/tests/nullness-asserts/TestAssumeAssertionsAreEnabled.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class TestAssumeAssertionsAreEnabled {
+
+  void foo(@Nullable String s1, @Nullable String s2) {
+    // :: error: (dereference.of.nullable)
+    assert s2.equals(s1);
+  }
+
+  void bar(@Nullable String s1, @Nullable String s2) {
+    // :: error: (dereference.of.nullable)
+    assert s2.equals(s1) : "@AssumeAssertion(nullness)";
+  }
+}
diff --git a/checker/tests/nullness-assumeassertions/TestAssumeAssertionsAreDisabled.java b/checker/tests/nullness-assumeassertions/TestAssumeAssertionsAreDisabled.java
new file mode 100644
index 0000000..ae3f090
--- /dev/null
+++ b/checker/tests/nullness-assumeassertions/TestAssumeAssertionsAreDisabled.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class TestAssumeAssertionsAreDisabled {
+
+  void foo(@Nullable String s1, @Nullable String s2) {
+
+    // If assertions are disabled, then this cannot throw a NullPointerException
+    assert s2.equals(s1);
+
+    // However, even with assertions disabled, @AssumeAssertion is still respected
+    // :: error: (dereference.of.nullable)
+    assert s2.equals(s1) : "@AssumeAssertion(nullness)";
+  }
+}
diff --git a/checker/tests/nullness-assumekeyfor/AssumeKeyForTest.java b/checker/tests/nullness-assumekeyfor/AssumeKeyForTest.java
new file mode 100644
index 0000000..fc2eaa6
--- /dev/null
+++ b/checker/tests/nullness-assumekeyfor/AssumeKeyForTest.java
@@ -0,0 +1,41 @@
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class AssumeKeyForTest {
+
+  void m1(Map<String, Integer> m, String k) {
+    @NonNull Integer x = m.get(k);
+  }
+
+  void m2(Map<String, Integer> m, String k) {
+    @Nullable Integer x = m.get(k);
+  }
+
+  void m3(Map<String, @Nullable Integer> m, String k) {
+    // :: error: (assignment)
+    @NonNull Integer x = m.get(k);
+  }
+
+  void m4(Map<String, @Nullable Integer> m, String k) {
+    @Nullable Integer x = m.get(k);
+  }
+
+  void m5(Map<String, Integer> m, @KeyFor("#1") String k) {
+    @NonNull Integer x = m.get(k);
+  }
+
+  void m6(Map<String, Integer> m, @KeyFor("#1") String k) {
+    @Nullable Integer x = m.get(k);
+  }
+
+  void m7(Map<String, @Nullable Integer> m, @KeyFor("#1") String k) {
+    // :: error: (assignment)
+    @NonNull Integer x = m.get(k);
+  }
+
+  void m8(Map<String, @Nullable Integer> m, @KeyFor("#1") String k) {
+    @Nullable Integer x = m.get(k);
+  }
+}
diff --git a/checker/tests/nullness-checkcastelementtype/Issue1315.java b/checker/tests/nullness-checkcastelementtype/Issue1315.java
new file mode 100644
index 0000000..c3df577
--- /dev/null
+++ b/checker/tests/nullness-checkcastelementtype/Issue1315.java
@@ -0,0 +1,36 @@
+// Test case for Issue 1315
+// https://github.com/typetools/checker-framework/issues/1315
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1315 {
+  static class Box<T> {
+    T f;
+
+    Box(T p) {
+      f = p;
+    }
+
+    @SuppressWarnings("unchecked")
+    T test1(@Nullable Object p) {
+      // :: warning: (cast.unsafe)
+      return (T) p;
+    }
+    // The Nullness Checker should not issue a cast.unsafe warning,
+    // but the KeyFor Checker does, so suppress that warning.
+    @SuppressWarnings({"unchecked", "keyfor:cast.unsafe"})
+    T test2(Object p) {
+      return (T) p;
+    }
+  }
+
+  static class Casts {
+    public static void test() {
+      Box<String> bs = new Box<>("");
+      bs.f = bs.test1(null);
+      // :: error: (argument)
+      bs.f = bs.test2(null);
+      bs.f.toString();
+    }
+  }
+}
diff --git a/checker/tests/nullness-concurrent-semantics/Issue350.java b/checker/tests/nullness-concurrent-semantics/Issue350.java
new file mode 100644
index 0000000..858acba
--- /dev/null
+++ b/checker/tests/nullness-concurrent-semantics/Issue350.java
@@ -0,0 +1,51 @@
+// Test case for Issue 350:
+// https://github.com/typetools/checker-framework/issues/350
+
+import org.checkerframework.checker.nullness.qual.*;
+
+class Test1 {
+
+  public @Nullable String y;
+
+  public void test2() {
+    y = "";
+    // Sanity check that -AconcurrentSemantics is set
+    // :: error: (dereference.of.nullable)
+    y.toString();
+  }
+
+  private @MonotonicNonNull String x;
+
+  void test() {
+    if (x == null) {
+      x = "";
+    }
+    x.toString();
+  }
+}
+
+class Test2 {
+
+  private @MonotonicNonNull String x;
+
+  void setX(String x) {
+    this.x = x;
+  }
+
+  void test() {
+    if (x == null) {
+      x = "";
+    }
+    setX(x);
+  }
+}
+
+class Test3 {
+
+  private @MonotonicNonNull String x;
+
+  @EnsuresNonNull("#1")
+  void setX(final String x) {
+    this.x = x;
+  }
+}
diff --git a/checker/tests/nullness-extra/Bug109_A.java b/checker/tests/nullness-extra/Bug109_A.java
new file mode 100644
index 0000000..b3019e2
--- /dev/null
+++ b/checker/tests/nullness-extra/Bug109_A.java
@@ -0,0 +1,9 @@
+public class Bug109_A {
+  int one = "1".length();
+
+  // fix 1: public final int one; { one = "1".length(); }
+  // fix 2: public final int one = 0 + "1".length();
+
+  int nl = 5;
+  int two = nl;
+}
diff --git a/checker/tests/nullness-extra/Bug109_B.java b/checker/tests/nullness-extra/Bug109_B.java
new file mode 100644
index 0000000..9dfaf20
--- /dev/null
+++ b/checker/tests/nullness-extra/Bug109_B.java
@@ -0,0 +1,11 @@
+public class Bug109_B extends Bug109_A {
+  public Bug109_B() {
+    // Accessing field one causes NPE
+    // at org.checkerframework.checker.nullness.MapGetHeuristics.handle
+    //   (MapGetHeuristics.java:91)
+
+    int myone = one;
+
+    int mytwo = two;
+  }
+}
diff --git a/checker/tests/nullness-extra/Bug109_README b/checker/tests/nullness-extra/Bug109_README
new file mode 100644
index 0000000..c867b10
--- /dev/null
+++ b/checker/tests/nullness-extra/Bug109_README
@@ -0,0 +1,24 @@
+These two classes illustrate dependencies between different
+compilation units.
+As of 09/06/2011 these generated two different exceptions in the
+compiler.
+Both of those are fixed, but we should investigate further whether all
+similar cases are correctly handled now. TODO!
+
+If the two classes are compiled individually or in the superclass then
+subclass order:
+
+javac -processor org.checkerframework.checker.nullness.NullnessChecker Bug109_A.java Bug109_B.java
+
+everything works as expected.
+If they are compiled in the subclass then superclass order:
+
+javac -processor org.checkerframework.checker.nullness.NullnessChecker Bug109_B.java Bug109_A.java
+
+the compiler used to throw exceptions.
+These two problems are now fixed.
+The problem was that AnnotatedTypeFactory.getAnnotatedType looks at a
+field initializer to determine its type. When the field was in a
+different class, this lead to a call of getPath with a tree in a
+different compilation unit, which cannot be handled.
+We now conservatively return null sooner.
diff --git a/checker/tests/nullness-extra/Makefile b/checker/tests/nullness-extra/Makefile
new file mode 100644
index 0000000..d425e5a
--- /dev/null
+++ b/checker/tests/nullness-extra/Makefile
@@ -0,0 +1,60 @@
+# Tests that are currently passing
+PASSING_TESTS = Bug109 compat issue265 issue594 issue607 multiple-errors package-anno shorthand issue3597
+ifeq (,$(findstring 1.8,$(shell javac -version)))
+  # issue309 and issue502 fail with Java 11 because of differences between Java 8 and Java 11 bytecode.
+  # TODO: issue559 should work with an annotated jdk11.
+  PASSING_TESTS_JDK8 = issue309 issue502 issue559
+else
+  PASSING_TESTS_JDK8 =
+endif
+
+# Tests that are currently not passing
+FAILING_TESTS =
+
+# Tests that are currently passing
+all: ${PASSING_TESTS} ${PASSING_TESTS_JDK8}
+
+# Tests that are currently not passing
+skipped: ${FAILING_TESTS}
+
+
+Bug109:
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker Bug109_A.java Bug109_B.java
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker Bug109_B.java Bug109_A.java
+
+
+compat:
+	$(MAKE) -C compat
+
+issue265:
+	$(MAKE) -C issue265
+
+issue309:
+	$(MAKE) -C issue309
+
+issue502:
+	$(MAKE) -C issue502
+
+issue559:
+	$(MAKE) -C issue559
+
+issue594:
+	$(MAKE) -C issue594
+
+multiple-errors:
+	$(MAKE) -C multiple-errors
+
+package-anno:
+	$(MAKE) -C package-anno
+
+shorthand:
+	$(MAKE) -C shorthand
+
+issue607:
+	$(MAKE) -C issue607
+
+issue3597:
+	$(MAKE) -C issue3597
+
+# All tests: passing and failing
+.PHONY: all skipped ${PASSING_TESTS}
diff --git a/checker/tests/nullness-extra/README b/checker/tests/nullness-extra/README
new file mode 100644
index 0000000..fc83c6d
--- /dev/null
+++ b/checker/tests/nullness-extra/README
@@ -0,0 +1,4 @@
+This directory contains test cases that do not fit into
+our usual testing framework because they depend on multiple
+compilation units being compiled together.
+TODO: how can we better integrate such tests?
diff --git a/checker/tests/nullness-extra/compat/CompatTest.java b/checker/tests/nullness-extra/compat/CompatTest.java
new file mode 100644
index 0000000..49dc9df
--- /dev/null
+++ b/checker/tests/nullness-extra/compat/CompatTest.java
@@ -0,0 +1,8 @@
+import lib.Lib;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class CompatTest {
+  void m() {
+    @NonNull Object o = Lib.maybeGetObject();
+  }
+}
diff --git a/checker/tests/nullness-extra/compat/Expected.txt b/checker/tests/nullness-extra/compat/Expected.txt
new file mode 100644
index 0000000..b6c4948
--- /dev/null
+++ b/checker/tests/nullness-extra/compat/Expected.txt
@@ -0,0 +1,6 @@
+CompatTest.java:6: error: [assignment] incompatible types in assignment.
+    @NonNull Object o = Lib.maybeGetObject();
+                                          ^
+  found   : @Initialized @Nullable Object
+  required: @UnknownInitialization @NonNull Object
+1 error
diff --git a/checker/tests/nullness-extra/compat/Makefile b/checker/tests/nullness-extra/compat/Makefile
new file mode 100644
index 0000000..59a3413
--- /dev/null
+++ b/checker/tests/nullness-extra/compat/Makefile
@@ -0,0 +1,6 @@
+.PHONY: all
+
+all:
+	rm -f Out.txt CompatTest.class lib/Lib.class javax/annotation/Nullable.class
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker lib/Lib.java javax/annotation/Nullable.java CompatTest.java > Out.txt 2>&1 || true
+	diff -u Expected.txt Out.txt
diff --git a/checker/tests/nullness-extra/compat/README b/checker/tests/nullness-extra/compat/README
new file mode 100644
index 0000000..5f9c96f
--- /dev/null
+++ b/checker/tests/nullness-extra/compat/README
@@ -0,0 +1,2 @@
+A test case to ensure that the declaration annotation
+is correctly interpreted as a type annotation.
diff --git a/checker/tests/nullness-extra/compat/javax/annotation/Nullable.java b/checker/tests/nullness-extra/compat/javax/annotation/Nullable.java
new file mode 100644
index 0000000..d9024b4
--- /dev/null
+++ b/checker/tests/nullness-extra/compat/javax/annotation/Nullable.java
@@ -0,0 +1,9 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Nullable {}
diff --git a/checker/tests/nullness-extra/compat/lib/Lib.java b/checker/tests/nullness-extra/compat/lib/Lib.java
new file mode 100644
index 0000000..4625a83
--- /dev/null
+++ b/checker/tests/nullness-extra/compat/lib/Lib.java
@@ -0,0 +1,9 @@
+package lib;
+
+import javax.annotation.Nullable;
+
+public class Lib {
+  @Nullable public static Object maybeGetObject() {
+    return null;
+  }
+}
diff --git a/checker/tests/nullness-extra/issue265/Delta.java b/checker/tests/nullness-extra/issue265/Delta.java
new file mode 100644
index 0000000..bb5bb28
--- /dev/null
+++ b/checker/tests/nullness-extra/issue265/Delta.java
@@ -0,0 +1,9 @@
+import java.util.List;
+
+public class Delta<E> {
+  List<E> field;
+
+  Delta(List<E> field) {
+    this.field = ImmutableList.copyOf(field);
+  }
+}
diff --git a/checker/tests/nullness-extra/issue265/ImmutableList.java b/checker/tests/nullness-extra/issue265/ImmutableList.java
new file mode 100644
index 0000000..b1b690b
--- /dev/null
+++ b/checker/tests/nullness-extra/issue265/ImmutableList.java
@@ -0,0 +1,8 @@
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class ImmutableList<E> implements List<E> {
+  public static <E> List<E> copyOf(Iterable<? extends E> elements) {
+    return new ArrayList<E>();
+  }
+}
diff --git a/checker/tests/nullness-extra/issue265/Makefile b/checker/tests/nullness-extra/issue265/Makefile
new file mode 100644
index 0000000..c9f7bff
--- /dev/null
+++ b/checker/tests/nullness-extra/issue265/Makefile
@@ -0,0 +1,8 @@
+.PHONY: all
+
+all:
+	mkdir -p bin
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker *.java -d bin/
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker Delta.java -cp bin/ -d bin/
+	rm bin/*.class
+	rmdir bin
diff --git a/checker/tests/nullness-extra/issue309/Expected.txt b/checker/tests/nullness-extra/issue309/Expected.txt
new file mode 100644
index 0000000..20239e3
--- /dev/null
+++ b/checker/tests/nullness-extra/issue309/Expected.txt
@@ -0,0 +1,2 @@
+warning: (annotation.not.completed)
+1 warning
diff --git a/checker/tests/nullness-extra/issue309/Issue309.java b/checker/tests/nullness-extra/issue309/Issue309.java
new file mode 100644
index 0000000..07e5186
--- /dev/null
+++ b/checker/tests/nullness-extra/issue309/Issue309.java
@@ -0,0 +1,7 @@
+import lib.Lib;
+
+public class Issue309 {
+  void bar() {
+    Lib.foo();
+  }
+}
diff --git a/checker/tests/nullness-extra/issue309/Lib.astub b/checker/tests/nullness-extra/issue309/Lib.astub
new file mode 100644
index 0000000..12ba50c
--- /dev/null
+++ b/checker/tests/nullness-extra/issue309/Lib.astub
@@ -0,0 +1,5 @@
+package lib;
+
+public class Lib {
+    public static void foo();
+}
diff --git a/checker/tests/nullness-extra/issue309/Makefile b/checker/tests/nullness-extra/issue309/Makefile
new file mode 100644
index 0000000..a7318e8
--- /dev/null
+++ b/checker/tests/nullness-extra/issue309/Makefile
@@ -0,0 +1,14 @@
+.PHONY: all
+
+all:
+	rm -f Out.txt
+	mkdir -p bin
+	$(JAVAC) lib/*.java -d bin/
+	jar cvf lib.jar -C bin/ lib/Lib.class
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Issue309.java -d bin/ -cp lib.jar > Out.txt 2>&1
+	diff -u Expected.txt Out.txt
+# Run again with stub file. (This used to cause a crash.)
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Issue309.java -d bin/ -cp lib.jar -Astubs=Lib.astub > Out.txt 2>&1
+	diff -u Expected.txt Out.txt
+	rm Out.txt lib.jar bin/*.class bin/lib/*.class
+	rmdir bin/lib bin/
diff --git a/checker/tests/nullness-extra/issue309/lib/Anno.java b/checker/tests/nullness-extra/issue309/lib/Anno.java
new file mode 100644
index 0000000..53e65ae
--- /dev/null
+++ b/checker/tests/nullness-extra/issue309/lib/Anno.java
@@ -0,0 +1,10 @@
+package lib;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(value = {ElementType.METHOD, ElementType.CONSTRUCTOR})
+@Retention(value = RetentionPolicy.RUNTIME)
+public @interface Anno {}
diff --git a/checker/tests/nullness-extra/issue309/lib/Lib.java b/checker/tests/nullness-extra/issue309/lib/Lib.java
new file mode 100644
index 0000000..ead9c6b
--- /dev/null
+++ b/checker/tests/nullness-extra/issue309/lib/Lib.java
@@ -0,0 +1,6 @@
+package lib;
+
+public class Lib {
+  @Anno
+  public static void foo() {}
+}
diff --git a/checker/tests/nullness-extra/issue348/Expected.txt b/checker/tests/nullness-extra/issue348/Expected.txt
new file mode 100644
index 0000000..20239e3
--- /dev/null
+++ b/checker/tests/nullness-extra/issue348/Expected.txt
@@ -0,0 +1,2 @@
+warning: (annotation.not.completed)
+1 warning
diff --git a/checker/tests/nullness-extra/issue348/Issue348.java b/checker/tests/nullness-extra/issue348/Issue348.java
new file mode 100644
index 0000000..72ba064
--- /dev/null
+++ b/checker/tests/nullness-extra/issue348/Issue348.java
@@ -0,0 +1,9 @@
+import lib.Lib;
+
+public class Issue348 {
+
+  void test() {
+    Lib lib = new Lib();
+    lib.foo();
+  }
+}
diff --git a/checker/tests/nullness-extra/issue348/Makefile b/checker/tests/nullness-extra/issue348/Makefile
new file mode 100644
index 0000000..5ed3eb1
--- /dev/null
+++ b/checker/tests/nullness-extra/issue348/Makefile
@@ -0,0 +1,12 @@
+.PHONY: all
+
+all:
+	rm -f Out.txt
+	mkdir -p bin
+	$(JAVAC) lib/*.java -d bin/
+	rm -f bin/lib/Anno.class
+	jar cvf lib.jar -C bin/ lib/
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Issue348.java -d bin/ -cp lib.jar > Out.txt 2>&1
+	diff -u Expected.txt Out.txt
+	rm Out.txt lib.jar bin/*.class bin/lib/*.class
+	rmdir bin/lib bin/
diff --git a/checker/tests/nullness-extra/issue348/lib/Anno.java b/checker/tests/nullness-extra/issue348/lib/Anno.java
new file mode 100644
index 0000000..53e65ae
--- /dev/null
+++ b/checker/tests/nullness-extra/issue348/lib/Anno.java
@@ -0,0 +1,10 @@
+package lib;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(value = {ElementType.METHOD, ElementType.CONSTRUCTOR})
+@Retention(value = RetentionPolicy.RUNTIME)
+public @interface Anno {}
diff --git a/checker/tests/nullness-extra/issue348/lib/Lib.java b/checker/tests/nullness-extra/issue348/lib/Lib.java
new file mode 100644
index 0000000..aed0f3b
--- /dev/null
+++ b/checker/tests/nullness-extra/issue348/lib/Lib.java
@@ -0,0 +1,6 @@
+package lib;
+
+public class Lib extends LibSuper {
+  @Override
+  public void foo() {}
+}
diff --git a/checker/tests/nullness-extra/issue348/lib/LibSuper.java b/checker/tests/nullness-extra/issue348/lib/LibSuper.java
new file mode 100644
index 0000000..c995eed
--- /dev/null
+++ b/checker/tests/nullness-extra/issue348/lib/LibSuper.java
@@ -0,0 +1,6 @@
+package lib;
+
+public class LibSuper {
+  @Anno
+  public void foo() {}
+}
diff --git a/checker/tests/nullness-extra/issue3597/Makefile b/checker/tests/nullness-extra/issue3597/Makefile
new file mode 100644
index 0000000..a25e91b
--- /dev/null
+++ b/checker/tests/nullness-extra/issue3597/Makefile
@@ -0,0 +1,8 @@
+.PHONY: all
+
+all: clean
+	$(JAVAC) testpkg/Issue3597B.java
+	$(JAVAC) -Astubs=issue3597.astub -processor Nullness -sourcepath : -cp . testpkg/Issue3597A.java
+
+clean:
+	rm -f testpkg/*.class
diff --git a/checker/tests/nullness-extra/issue3597/issue3597.astub b/checker/tests/nullness-extra/issue3597/issue3597.astub
new file mode 100644
index 0000000..d10cf64
--- /dev/null
+++ b/checker/tests/nullness-extra/issue3597/issue3597.astub
@@ -0,0 +1,7 @@
+package testpkg;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class Issue3597B {
+  Object f();
+}
diff --git a/checker/tests/nullness-extra/issue3597/testpkg/Issue3597A.java b/checker/tests/nullness-extra/issue3597/testpkg/Issue3597A.java
new file mode 100644
index 0000000..11d72d6
--- /dev/null
+++ b/checker/tests/nullness-extra/issue3597/testpkg/Issue3597A.java
@@ -0,0 +1,7 @@
+package testpkg;
+
+public class Issue3597A {
+  void f() {
+    System.err.println(new Issue3597B().f().toString());
+  }
+}
diff --git a/checker/tests/nullness-extra/issue3597/testpkg/Issue3597B.java b/checker/tests/nullness-extra/issue3597/testpkg/Issue3597B.java
new file mode 100644
index 0000000..4d0df95
--- /dev/null
+++ b/checker/tests/nullness-extra/issue3597/testpkg/Issue3597B.java
@@ -0,0 +1,9 @@
+package testpkg;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue3597B {
+  @Nullable Object f() {
+    return new Object();
+  }
+}
diff --git a/checker/tests/nullness-extra/issue502/Expected.txt b/checker/tests/nullness-extra/issue502/Expected.txt
new file mode 100644
index 0000000..8b062ad
--- /dev/null
+++ b/checker/tests/nullness-extra/issue502/Expected.txt
@@ -0,0 +1,3 @@
+    RuntimeVisibleAnnotations:
+      0: #12()
+    RuntimeVisibleTypeAnnotations:
diff --git a/checker/tests/nullness-extra/issue502/Issue502.java b/checker/tests/nullness-extra/issue502/Issue502.java
new file mode 100644
index 0000000..a1d3816
--- /dev/null
+++ b/checker/tests/nullness-extra/issue502/Issue502.java
@@ -0,0 +1,9 @@
+// Test case for Issue 502:
+// https://github.com/typetools/checker-framework/issues/502
+
+public class Issue502 {
+  @Override
+  public String toString() {
+    return "";
+  }
+}
diff --git a/checker/tests/nullness-extra/issue502/Makefile b/checker/tests/nullness-extra/issue502/Makefile
new file mode 100644
index 0000000..5f2aa25
--- /dev/null
+++ b/checker/tests/nullness-extra/issue502/Makefile
@@ -0,0 +1,11 @@
+.PHONY: all
+
+all: clean
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker Issue502.java
+# TODO: This test is rather unstable, as Expected.txt relies on
+# @SideEffectFree being constant #12().
+	$(JAVAP) -v Issue502.class | grep "RuntimeVisibleAnnotations:" -A 2 > Out.txt 2>&1
+	diff -u Expected.txt Out.txt
+
+clean:
+	rm -f Issue502.class Out.txt
diff --git a/checker/tests/nullness-extra/issue559/Expected.txt b/checker/tests/nullness-extra/issue559/Expected.txt
new file mode 100644
index 0000000..a4777bd
--- /dev/null
+++ b/checker/tests/nullness-extra/issue559/Expected.txt
@@ -0,0 +1,8 @@
+warning: AnnotationFileParser: in file myjdk.astub at line 6 ignored existing annotations on type: T[ extends @Initialized @Nullable Object super @Initialized @Nullable Void]
+Issue559.java:10: error: [argument] incompatible types in argument.
+        o.orElse(null);
+                 ^
+  found   : null
+  required: @Initialized @NonNull String
+1 error
+1 warning
diff --git a/checker/tests/nullness-extra/issue559/Issue559.java b/checker/tests/nullness-extra/issue559/Issue559.java
new file mode 100644
index 0000000..5d65ebd
--- /dev/null
+++ b/checker/tests/nullness-extra/issue559/Issue559.java
@@ -0,0 +1,13 @@
+// Test case for Issue 559:
+// https://github.com/typetools/checker-framework/issues/559
+
+import java.util.Optional;
+
+public class Issue559 {
+  void bar(Optional<String> o) {
+    // With myjdk.astub the following should fail with an
+    // argument error.
+    o.orElse(null);
+    o.orElse("Hi");
+  }
+}
diff --git a/checker/tests/nullness-extra/issue559/Makefile b/checker/tests/nullness-extra/issue559/Makefile
new file mode 100644
index 0000000..db0d6b8
--- /dev/null
+++ b/checker/tests/nullness-extra/issue559/Makefile
@@ -0,0 +1,8 @@
+.PHONY: all
+
+all: clean
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=myjdk.astub -AstubWarnIfOverwritesBytecode Issue559.java > Out.txt 2>&1 || true
+	diff -u Expected.txt Out.txt
+
+clean:
+	rm -f Issue559.class Out.txt
diff --git a/checker/tests/nullness-extra/issue559/myjdk.astub b/checker/tests/nullness-extra/issue559/myjdk.astub
new file mode 100644
index 0000000..2bb670b
--- /dev/null
+++ b/checker/tests/nullness-extra/issue559/myjdk.astub
@@ -0,0 +1,7 @@
+package java.util;
+
+public class Optional<T> {
+    // In contrast to the annotated JDK, here
+    // we have no @Nullable on the parameter and return types.
+    public T orElse(T other);
+}
diff --git a/checker/tests/nullness-extra/issue594/Expected.txt b/checker/tests/nullness-extra/issue594/Expected.txt
new file mode 100644
index 0000000..3097449
--- /dev/null
+++ b/checker/tests/nullness-extra/issue594/Expected.txt
@@ -0,0 +1,6 @@
+Issue594.java:17: error: [return] incompatible types in return.
+    return result;
+           ^
+  type of expression: T[ extends @Initialized @Nullable Object super @Initialized @Nullable Void]
+  method return type: T[ extends @Initialized @Nullable Object super @Initialized @NonNull Void]
+1 error
diff --git a/checker/tests/nullness-extra/issue594/Issue594.java b/checker/tests/nullness-extra/issue594/Issue594.java
new file mode 100644
index 0000000..e56f445
--- /dev/null
+++ b/checker/tests/nullness-extra/issue594/Issue594.java
@@ -0,0 +1,19 @@
+// Issue #594: https://github.com/typetools/checker-framework/issues/594
+// The bug is that the error message has identical found and required:
+//
+//                 return result;
+//                        ^
+//   found   : T extends @Initialized @Nullable Object
+//   required: T extends @Initialized @Nullable Object
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue594<T> {
+  private @Nullable T result = null;
+
+  // Should return @Nullable T
+  private T getResult() {
+    // :: error: (return)
+    return result;
+  }
+}
diff --git a/checker/tests/nullness-extra/issue594/Makefile b/checker/tests/nullness-extra/issue594/Makefile
new file mode 100644
index 0000000..9445d4b
--- /dev/null
+++ b/checker/tests/nullness-extra/issue594/Makefile
@@ -0,0 +1,8 @@
+.PHONY: all
+
+all: clean
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker Issue594.java > Out.txt 2>&1 || true
+	diff  Expected.txt Out.txt
+
+clean:
+	rm -f Issue594.class Out.txt
diff --git a/checker/tests/nullness-extra/issue607/Issue607.java b/checker/tests/nullness-extra/issue607/Issue607.java
new file mode 100644
index 0000000..100e38a
--- /dev/null
+++ b/checker/tests/nullness-extra/issue607/Issue607.java
@@ -0,0 +1,7 @@
+public class Issue607 extends Issue607SuperClass {
+  static String simpleString = "a";
+
+  Issue607() {
+    super(Issue607SuperClass.issue, string -> simpleString);
+  }
+}
diff --git a/checker/tests/nullness-extra/issue607/Issue607Interface.java b/checker/tests/nullness-extra/issue607/Issue607Interface.java
new file mode 100644
index 0000000..4e942a4
--- /dev/null
+++ b/checker/tests/nullness-extra/issue607/Issue607Interface.java
@@ -0,0 +1,3 @@
+interface Issue607Interface {
+  Object method(Object string);
+}
diff --git a/checker/tests/nullness-extra/issue607/Issue607SuperClass.java b/checker/tests/nullness-extra/issue607/Issue607SuperClass.java
new file mode 100644
index 0000000..ec05dd3
--- /dev/null
+++ b/checker/tests/nullness-extra/issue607/Issue607SuperClass.java
@@ -0,0 +1,5 @@
+public class Issue607SuperClass {
+  static Issue607Interface issue;
+
+  public Issue607SuperClass(Issue607Interface... issue) {}
+}
diff --git a/checker/tests/nullness-extra/issue607/Makefile b/checker/tests/nullness-extra/issue607/Makefile
new file mode 100644
index 0000000..08fb457
--- /dev/null
+++ b/checker/tests/nullness-extra/issue607/Makefile
@@ -0,0 +1,8 @@
+.PHONY: all
+
+all: clean
+	$(JAVAC) Issue607SuperClass.java Issue607Interface.java
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker Issue607.java
+
+clean:
+	rm -f *.class
diff --git a/checker/tests/nullness-extra/multiple-errors/C1.java b/checker/tests/nullness-extra/multiple-errors/C1.java
new file mode 100644
index 0000000..206a161
--- /dev/null
+++ b/checker/tests/nullness-extra/multiple-errors/C1.java
@@ -0,0 +1,3 @@
+public class C1 {
+  Object o;
+}
diff --git a/checker/tests/nullness-extra/multiple-errors/C2.java b/checker/tests/nullness-extra/multiple-errors/C2.java
new file mode 100644
index 0000000..ab0a5a5
--- /dev/null
+++ b/checker/tests/nullness-extra/multiple-errors/C2.java
@@ -0,0 +1,3 @@
+public class C2 {
+  Object o = null;
+}
diff --git a/checker/tests/nullness-extra/multiple-errors/C3.java b/checker/tests/nullness-extra/multiple-errors/C3.java
new file mode 100644
index 0000000..c5cf1a6
--- /dev/null
+++ b/checker/tests/nullness-extra/multiple-errors/C3.java
@@ -0,0 +1,9 @@
+public class C3 {
+  void m() {
+    class C3b {
+      void bad(XXX p) {
+        p.toString();
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness-extra/multiple-errors/C4.java b/checker/tests/nullness-extra/multiple-errors/C4.java
new file mode 100644
index 0000000..b363b30
--- /dev/null
+++ b/checker/tests/nullness-extra/multiple-errors/C4.java
@@ -0,0 +1,5 @@
+public class C4 {
+  void m(@org.checkerframework.checker.nullness.qual.Nullable Object p) {
+    p.toString();
+  }
+}
diff --git a/checker/tests/nullness-extra/multiple-errors/Expected.txt b/checker/tests/nullness-extra/multiple-errors/Expected.txt
new file mode 100644
index 0000000..71b41b7
--- /dev/null
+++ b/checker/tests/nullness-extra/multiple-errors/Expected.txt
@@ -0,0 +1,20 @@
+C1.java:2: error: [initialization.field.uninitialized] the default constructor does not initialize field o
+  Object o;
+         ^
+C2.java:2: error: [assignment] incompatible types in assignment.
+  Object o = null;
+             ^
+  found   : null (NullType)
+  required: @Initialized @NonNull Object
+C3.java:4: error: cannot find symbol
+      void bad(XXX p) {
+               ^
+  symbol:   class XXX
+  location: class C3b
+C3.java:1: error: [type.checking.not.run] NullnessChecker did not run because of a previous error issued by javac
+public class C3 {
+       ^
+C4.java:1: error: [type.checking.not.run] NullnessChecker did not run because of a previous error issued by javac
+public class C4 {
+       ^
+5 errors
diff --git a/checker/tests/nullness-extra/multiple-errors/Makefile b/checker/tests/nullness-extra/multiple-errors/Makefile
new file mode 100644
index 0000000..5bb4e1d
--- /dev/null
+++ b/checker/tests/nullness-extra/multiple-errors/Makefile
@@ -0,0 +1,6 @@
+.PHONY: all
+
+all:
+	rm -f Out.txt
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker *.java > Out.txt 2>&1 || true
+	diff -u Expected.txt Out.txt
diff --git a/checker/tests/nullness-extra/multiple-errors/README b/checker/tests/nullness-extra/multiple-errors/README
new file mode 100644
index 0000000..db14d3f
--- /dev/null
+++ b/checker/tests/nullness-extra/multiple-errors/README
@@ -0,0 +1,12 @@
+The classes in this directory contain errors.
+We want to process as many of the files as possible and not
+stop at the first error.
+See the message thread at
+
+https://groups.google.com/forum/#!topic/checker-framework-discuss/4A0Z3z8vcXA
+
+Performing
+
+javac -processor org.checkerframework.checker.nonnull.NonNullFbcChecker *.java
+
+should result in 2 errors and 1 warning, not only 1 each.
diff --git a/checker/tests/nullness-extra/package-anno/Expected.txt b/checker/tests/nullness-extra/package-anno/Expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/checker/tests/nullness-extra/package-anno/Expected.txt
diff --git a/checker/tests/nullness-extra/package-anno/Makefile b/checker/tests/nullness-extra/package-anno/Makefile
new file mode 100644
index 0000000..bf468b8
--- /dev/null
+++ b/checker/tests/nullness-extra/package-anno/Makefile
@@ -0,0 +1,6 @@
+.PHONY: all
+
+all:
+	rm -f Out.txt
+	$(JAVAC) -processor org.checkerframework.checker.nullness.NullnessChecker test/*.java > Out.txt 2>&1 || true
+	diff -u Expected.txt Out.txt
diff --git a/checker/tests/nullness-extra/package-anno/test/PackageAnnotationTest.java b/checker/tests/nullness-extra/package-anno/test/PackageAnnotationTest.java
new file mode 100644
index 0000000..dd9d005
--- /dev/null
+++ b/checker/tests/nullness-extra/package-anno/test/PackageAnnotationTest.java
@@ -0,0 +1,6 @@
+package test;
+
+public class PackageAnnotationTest {
+  // Allowed because of package annotation.
+  Object f = null;
+}
diff --git a/checker/tests/nullness-extra/package-anno/test/package-info.java b/checker/tests/nullness-extra/package-anno/test/package-info.java
new file mode 100644
index 0000000..a797ded
--- /dev/null
+++ b/checker/tests/nullness-extra/package-anno/test/package-info.java
@@ -0,0 +1,5 @@
+@DefaultQualifier(Nullable.class)
+package test;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
diff --git a/checker/tests/nullness-extra/shorthand/Makefile b/checker/tests/nullness-extra/shorthand/Makefile
new file mode 100644
index 0000000..6fb7f90
--- /dev/null
+++ b/checker/tests/nullness-extra/shorthand/Makefile
@@ -0,0 +1,23 @@
+.PHONY: all  nullnessOnly nullnessRegex nullnessBad nonsense
+
+all: nullnessOnly nullnessRegex nullnessBad nonsense
+
+nullnessOnly:
+	rm -f Out.txt
+	$(JAVAC) -processor NullnessChecker -XDrawDiagnostics -Anomsgtext -Awarns NullnessRegexWithErrors.java > Out.txt 2>&1 || true
+	diff -u NullnessOnlyExpected.txt Out.txt
+
+nullnessRegex:
+	rm -f Out.txt
+	$(JAVAC) -processor NullnessChecker,RegexChecker -XDrawDiagnostics -Anomsgtext -Awarns NullnessRegexWithErrors.java > Out.txt 2>&1 || true
+	diff -u NullnessRegexExpected.txt Out.txt
+
+nullnessBad:
+	rm -f Out.txt
+	$(JAVAC) -processor nullness.NullnessChecker -XDrawDiagnostics -Anomsgtext -Awarns NullnessRegexWithErrors.java > Out.txt 2>&1 || true
+	diff -u NullnessBadExpected.txt Out.txt
+
+nonsense:
+	rm -f Out.txt
+	$(JAVAC) -processor NonsenseChecker -XDrawDiagnostics -Anomsgtext -Awarns NullnessRegexWithErrors.java > Out.txt 2>&1 || true
+	diff -u NonsenseExpected.txt Out.txt
diff --git a/checker/tests/nullness-extra/shorthand/NonsenseExpected.txt b/checker/tests/nullness-extra/shorthand/NonsenseExpected.txt
new file mode 100644
index 0000000..1edf13e
--- /dev/null
+++ b/checker/tests/nullness-extra/shorthand/NonsenseExpected.txt
@@ -0,0 +1,2 @@
+- compiler.err.proc.processor.not.found: NonsenseChecker
+1 error
diff --git a/checker/tests/nullness-extra/shorthand/NullnessBadExpected.txt b/checker/tests/nullness-extra/shorthand/NullnessBadExpected.txt
new file mode 100644
index 0000000..ed7ccff
--- /dev/null
+++ b/checker/tests/nullness-extra/shorthand/NullnessBadExpected.txt
@@ -0,0 +1,2 @@
+- compiler.err.proc.processor.not.found: nullness.NullnessChecker
+1 error
diff --git a/checker/tests/nullness-extra/shorthand/NullnessOnlyExpected.txt b/checker/tests/nullness-extra/shorthand/NullnessOnlyExpected.txt
new file mode 100644
index 0000000..a903605
--- /dev/null
+++ b/checker/tests/nullness-extra/shorthand/NullnessOnlyExpected.txt
@@ -0,0 +1,2 @@
+NullnessRegexWithErrors.java:9:11: compiler.warn.proc.messager: (assignment)
+1 warning
diff --git a/checker/tests/nullness-extra/shorthand/NullnessRegexExpected.txt b/checker/tests/nullness-extra/shorthand/NullnessRegexExpected.txt
new file mode 100644
index 0000000..2f1eec5
--- /dev/null
+++ b/checker/tests/nullness-extra/shorthand/NullnessRegexExpected.txt
@@ -0,0 +1,3 @@
+NullnessRegexWithErrors.java:9:11: compiler.warn.proc.messager: (assignment)
+NullnessRegexWithErrors.java:10:21: compiler.warn.proc.messager: (argument)
+2 warnings
diff --git a/checker/tests/nullness-extra/shorthand/NullnessRegexWithErrors.java b/checker/tests/nullness-extra/shorthand/NullnessRegexWithErrors.java
new file mode 100644
index 0000000..487d633
--- /dev/null
+++ b/checker/tests/nullness-extra/shorthand/NullnessRegexWithErrors.java
@@ -0,0 +1,12 @@
+package nullness;
+
+import java.util.regex.Pattern;
+
+public class NullnessRegexWithErrors {
+  String str = "(str";
+
+  void context() {
+    str = null;
+    Pattern.compile("\\I");
+  }
+}
diff --git a/checker/tests/nullness-extra/shorthand/README b/checker/tests/nullness-extra/shorthand/README
new file mode 100644
index 0000000..a406bbc
--- /dev/null
+++ b/checker/tests/nullness-extra/shorthand/README
@@ -0,0 +1,24 @@
+This is a test of the "checker shorthand" feature.
+See: https://checkerframework.org/manual/#shorthand-for-checkers
+This test is not Nullness specific.
+
+We have placed this test in this location because it has two preconditions:
+1) It must be run after checker.jar is created
+2) It must be run via CheckerMain and not via bootclasspath manipulation in the build file
+The normal testing methods do not require a fully built checker.jar and we did not want to force this
+requirement on them.  Furthermore, the JUnit tests do not run through CheckerMain.  This cannot be placed in
+the jtreg test suite because it does not run through CheckerMain.
+
+This test ensures that:
+  a) Single checkers can be called using the shorthand notation
+     see NullnessOnlyExpected.txt
+
+  b) Multiple checkers can be called using the shorthand notation
+     see NullnessRegexExpected.txt
+
+  c) That adding a package-name in front of a checker eliminates it as a candidate for shorthand.
+     see NullnessBadExpected.txt
+
+  d) That names that aren't checkers actually lead to the usual error message for missing annotation processors
+     even when they are of the correct form for a shorthand processor.
+     see NonsenseExpected.txt
diff --git a/checker/tests/nullness-genericwildcard/GenericWildcardInheritance.java b/checker/tests/nullness-genericwildcard/GenericWildcardInheritance.java
new file mode 100644
index 0000000..da40884
--- /dev/null
+++ b/checker/tests/nullness-genericwildcard/GenericWildcardInheritance.java
@@ -0,0 +1,8 @@
+// Test case for issue #511: https://github.com/typetools/checker-framework/issues/511
+// If GwiParent is compiled together with this file, no error occurs.
+// If GwiParent is read from bytecode, the error occurs.
+
+public class GenericWildcardInheritance extends GwiParent {
+  @Override
+  public void syntaxError(Recognizer<?> recognizer) {}
+}
diff --git a/checker/tests/nullness-genericwildcard/Issue511.java b/checker/tests/nullness-genericwildcard/Issue511.java
new file mode 100644
index 0000000..5266ad7
--- /dev/null
+++ b/checker/tests/nullness-genericwildcard/Issue511.java
@@ -0,0 +1,31 @@
+// Additional test case for issue #511:
+// https://github.com/typetools/checker-framework/issues/511
+class MyGeneric<T extends Number> {}
+
+class MySuperClass {
+  public void method(MyGeneric<? extends Object> x) {}
+}
+
+public class Issue511 extends MySuperClass {
+  @Override
+  public void method(MyGeneric<?> x) {
+    super.method(x);
+  }
+
+  // public void method(MyGeneric<? extends Number> x) {}
+  // On the above method, javac issues the following error:
+  // Issue511.java:19: error: name clash: method(MyGeneric<? extends Number>) in Issue511 and
+  // method(MyGeneric<? extends Object>) in MySuperClass have the same erasure, yet neither
+  // overrides the other
+  // public void method(MyGeneric<? extends Number> x) {}
+  //    ^
+  //            1 error
+
+}
+
+class Use {
+  MyGeneric<? extends Object> wildCardExtendsObject = new MyGeneric<>();
+  MyGeneric<? extends Number> wildCardExtendsNumber = wildCardExtendsObject;
+  MyGeneric<?> wildCardNoBound = new MyGeneric<>();
+  MyGeneric<? extends Number> wildCardExtendsNumber2 = wildCardNoBound;
+}
diff --git a/checker/tests/nullness-genericwildcardlib/GwiParent.java b/checker/tests/nullness-genericwildcardlib/GwiParent.java
new file mode 100644
index 0000000..60826b2
--- /dev/null
+++ b/checker/tests/nullness-genericwildcardlib/GwiParent.java
@@ -0,0 +1,9 @@
+// Library for issue #511: https://github.com/typetools/checker-framework/issues/511
+
+public abstract class GwiParent {
+  abstract void syntaxError(Recognizer<?> recognizer);
+}
+
+abstract class ATNSimulator {}
+
+class Recognizer<ATNInterpreter extends ATNSimulator> {}
diff --git a/checker/tests/nullness-invariantarrays/TwoDimensionalArray.java b/checker/tests/nullness-invariantarrays/TwoDimensionalArray.java
new file mode 100644
index 0000000..b0fceb2
--- /dev/null
+++ b/checker/tests/nullness-invariantarrays/TwoDimensionalArray.java
@@ -0,0 +1,13 @@
+// @skip-test
+// Test case for issue 440: https://github.com/typetools/checker-framework/issues/440
+
+public class TwoDimensionalArray {
+
+  public static void main(String[] args) {
+    assert any_null(new Object[][] {null}) == true;
+  }
+
+  public static boolean any_null(Object[] a) {
+    return true;
+  }
+}
diff --git a/checker/tests/nullness-javac-errors/BadCast1.java b/checker/tests/nullness-javac-errors/BadCast1.java
new file mode 100644
index 0000000..2d9cf3c
--- /dev/null
+++ b/checker/tests/nullness-javac-errors/BadCast1.java
@@ -0,0 +1,6 @@
+public class BadCast1 {
+  public void m() {
+    // :: error: illegal start of type :: error: not a statement
+    (@NonNull) "";
+  }
+}
diff --git a/checker/tests/nullness-javac-errors/BadCast2.java b/checker/tests/nullness-javac-errors/BadCast2.java
new file mode 100644
index 0000000..174f85c
--- /dev/null
+++ b/checker/tests/nullness-javac-errors/BadCast2.java
@@ -0,0 +1,6 @@
+public class BadCast2 {
+  public static void main(String[] args) {
+    // :: error: illegal start of type
+    String example = (@NonNull) "";
+  }
+}
diff --git a/checker/tests/nullness-javac-errors/README b/checker/tests/nullness-javac-errors/README
new file mode 100644
index 0000000..e5493b5
--- /dev/null
+++ b/checker/tests/nullness-javac-errors/README
@@ -0,0 +1,3 @@
+Java files in this directory are allowed to contain Java errors
+(that is, to cause javac without a processor to issue an error).
+This is an exception to the rules in ../README.
diff --git a/checker/tests/nullness-javadoc/JavadocJdkAnnotations.java b/checker/tests/nullness-javadoc/JavadocJdkAnnotations.java
new file mode 100644
index 0000000..7d9886b
--- /dev/null
+++ b/checker/tests/nullness-javadoc/JavadocJdkAnnotations.java
@@ -0,0 +1,18 @@
+import com.sun.javadoc.Doc;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class JavadocJdkAnnotations {
+
+  @Nullable Object f = null;
+
+  @SuppressWarnings("removal")
+  void testPureAnnotation(Doc d) {
+    f = "non-null value";
+    d.isIncluded();
+    @NonNull Object x = f;
+    d.tags();
+    // :: error: (assignment)
+    @NonNull Object y = f;
+  }
+}
diff --git a/checker/tests/nullness-permitClearProperty/PermitClearProperty.java b/checker/tests/nullness-permitClearProperty/PermitClearProperty.java
new file mode 100644
index 0000000..17ad0b1
--- /dev/null
+++ b/checker/tests/nullness-permitClearProperty/PermitClearProperty.java
@@ -0,0 +1,125 @@
+// Same code (but different expected errors) as test PreventClearProperty.java .
+
+import java.util.Properties;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class PermitClearProperty {
+
+  static final @StringVal("line.separator") String LINE_SEPARATOR = "line.separator";
+
+  static final @StringVal("my.property.name") String MY_PROPERTY_NAME = "my.property.name";
+
+  @NonNull String getLineSeparator1() {
+    // :: error: (return)
+    return System.getProperty("line.separator");
+  }
+
+  @NonNull String getLineSeparator2() {
+    // :: error: (return)
+    return System.getProperty(LINE_SEPARATOR);
+  }
+
+  @NonNull String getMyProperty1() {
+    // :: error: (return)
+    return System.getProperty("my.property.name");
+  }
+
+  @NonNull String getMyProperty2() {
+    // :: error: (return)
+    return System.getProperty(MY_PROPERTY_NAME);
+  }
+
+  @NonNull String getAProperty(String propName) {
+    // :: error: (return)
+    return System.getProperty(propName);
+  }
+
+  @NonNull String clearLineSeparator1() {
+    // :: error: (return)
+    return System.clearProperty("line.separator");
+  }
+
+  @NonNull String clearLineSeparator2() {
+    // :: error: (return)
+    return System.clearProperty(LINE_SEPARATOR);
+  }
+
+  @NonNull String clearMyProperty1() {
+    // :: error: (return)
+    return System.clearProperty("my.property.name");
+  }
+
+  @NonNull String clearMyProperty2() {
+    // :: error: (return)
+    return System.clearProperty(MY_PROPERTY_NAME);
+  }
+
+  @NonNull String clearAProperty(String propName) {
+    // :: error: (return)
+    return System.clearProperty(propName);
+  }
+
+  void callSetProperties(Properties p) {
+    System.setProperties(p);
+  }
+
+  // All calls to setProperty are legal because they cannot unset a property.
+
+  @NonNull String setLineSeparator1() {
+    // :: error: (return)
+    return System.setProperty("line.separator", "somevalue");
+  }
+
+  @NonNull String setLineSeparator2() {
+    // :: error: (return)
+    return System.setProperty(LINE_SEPARATOR, "somevalue");
+  }
+
+  @NonNull String setMyProperty1() {
+    // :: error: (return)
+    return System.setProperty("my.property.name", "somevalue");
+  }
+
+  @NonNull String setMyProperty2() {
+    // :: error: (return)
+    return System.setProperty(MY_PROPERTY_NAME, "somevalue");
+  }
+
+  @NonNull String setAProperty(String propName) {
+    // :: error: (return)
+    return System.setProperty(propName, "somevalue");
+  }
+
+  // These calls to setProperty are illegal because null is not a permitted value.
+
+  @NonNull String setLineSeparatorNull1() {
+    // :: error: (return)
+    // :: error: (argument)
+    return System.setProperty("line.separator", null);
+  }
+
+  @NonNull String setLineSeparatorNull2() {
+    // :: error: (argument)
+    // :: error: (return)
+    return System.setProperty(LINE_SEPARATOR, null);
+  }
+
+  @NonNull String setMyPropertyNull1() {
+    // :: error: (argument)
+    // :: error: (return)
+    return System.setProperty("my.property.name", null);
+  }
+
+  @NonNull String setMyPropertyNull2() {
+    // :: error: (argument)
+    // :: error: (return)
+    return System.setProperty(MY_PROPERTY_NAME, null);
+  }
+
+  @NonNull String setAPropertyNull(String propName) {
+    // :: error: (argument)
+    // :: error: (return)
+    return System.setProperty(propName, null);
+  }
+}
diff --git a/checker/tests/nullness-permitClearProperty/README b/checker/tests/nullness-permitClearProperty/README
new file mode 100644
index 0000000..c0fd983
--- /dev/null
+++ b/checker/tests/nullness-permitClearProperty/README
@@ -0,0 +1,8 @@
+This directory contains tests for the Nullness Checker, with the -Alint=permitClearProperty flag.
+
+To add a new file to the test suite, just add it to this directory.
+For more details, see
+  ../README
+
+To run the tests, do this:
+  (cd $CHECKERFRAMEWORK && ./gradlew PermitClearPropertyTest)
diff --git a/checker/tests/nullness-reflection/NullnessReflectionExampleTest.java b/checker/tests/nullness-reflection/NullnessReflectionExampleTest.java
new file mode 100644
index 0000000..343d9e7
--- /dev/null
+++ b/checker/tests/nullness-reflection/NullnessReflectionExampleTest.java
@@ -0,0 +1,30 @@
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.common.reflection.qual.MethodVal;
+
+/** Example used in the reflection resolution section of the Checker Framework manual. */
+public class NullnessReflectionExampleTest {
+  @NonNull Location getCurrentLocation() {
+    // ...
+    return new Location();
+  }
+
+  String getCurrentCity()
+      throws NoSuchMethodException, SecurityException, IllegalAccessException,
+          IllegalArgumentException, InvocationTargetException {
+    @MethodVal(
+        className = "NullnessReflectionExampleTest",
+        methodName = "getCurrentLocation",
+        params = 0)
+    Method toLowerCase = getClass().getMethod("getCurrentLocation");
+    Location currentLocation = (Location) toLowerCase.invoke(this);
+    return currentLocation.nameOfCity();
+  }
+}
+
+class Location {
+  String nameOfCity() {
+    return "Seattle";
+  }
+}
diff --git a/checker/tests/nullness-reflection/NullnessReflectionResolutionTest.java b/checker/tests/nullness-reflection/NullnessReflectionResolutionTest.java
new file mode 100644
index 0000000..e5d55c8
--- /dev/null
+++ b/checker/tests/nullness-reflection/NullnessReflectionResolutionTest.java
@@ -0,0 +1,49 @@
+import java.lang.reflect.Method;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.reflection.qual.MethodVal;
+
+/** Testing that reflection resolution uses more precise annotations for the Nullness Checker. */
+public class NullnessReflectionResolutionTest {
+  @NonNull Object returnNonNull() {
+    return new Object();
+  }
+
+  void testReturnNonNull(
+      @MethodVal(
+              className = "NullnessReflectionResolutionTest",
+              methodName = "returnNonNull",
+              params = 0)
+          Method m)
+      throws Exception {
+    @NonNull Object o = m.invoke(this);
+  }
+
+  void paramNullable(@Nullable Object param1, @Nullable Object param2) {}
+
+  void testParamNullable(
+      @MethodVal(
+              className = "NullnessReflectionResolutionTest",
+              methodName = "paramNullable",
+              params = 2)
+          Method m)
+      throws Exception {
+    @NonNull Object o = m.invoke(this, null, null);
+  }
+
+  static @NonNull Object paramAndReturnNonNullStatic(
+      @Nullable Object param1, @Nullable Object param2) {
+    return new Object();
+  }
+
+  void testParamAndReturnNonNullStatic(
+      @MethodVal(
+              className = "NullnessReflectionResolutionTest",
+              methodName = "paramAndReturnNonNullStatic",
+              params = 2)
+          Method m)
+      throws Exception {
+    @NonNull Object o1 = m.invoke(this, null, null);
+    @NonNull Object o2 = m.invoke(null, null, null);
+  }
+}
diff --git a/checker/tests/nullness-reflection/VoidTest.java b/checker/tests/nullness-reflection/VoidTest.java
new file mode 100644
index 0000000..663612d
--- /dev/null
+++ b/checker/tests/nullness-reflection/VoidTest.java
@@ -0,0 +1,6 @@
+public class VoidTest {
+
+  Class<?> test() {
+    return void.class;
+  }
+}
diff --git a/checker/tests/nullness-safedefaultsbytecode/ArraysMDE.java b/checker/tests/nullness-safedefaultsbytecode/ArraysMDE.java
new file mode 100644
index 0000000..7dd3163
--- /dev/null
+++ b/checker/tests/nullness-safedefaultsbytecode/ArraysMDE.java
@@ -0,0 +1,30 @@
+// Test case for issue #455: https://github.com/typetools/checker-framework/issues/455
+
+import java.util.Objects;
+
+public final class ArraysMDE {
+
+  public static int indexOf(Object[] a, Object[] sub) {
+    int a_index_max = a.length - sub.length + 1;
+    for (int i = 0; i <= a_index_max; i++) {
+      if (isSubarray(a, sub, i)) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  public static boolean isSubarray(Object[] a, Object[] sub, int a_offset) {
+    int a_len = a.length - a_offset;
+    int sub_len = sub.length;
+    if (a_len < sub_len) {
+      return false;
+    }
+    for (int i = 0; i < sub_len; i++) {
+      if (!Objects.equals(sub[i], a[a_offset + i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/checker/tests/nullness-safedefaultsbytecode/BytecodeDefaultsTest.java b/checker/tests/nullness-safedefaultsbytecode/BytecodeDefaultsTest.java
new file mode 100644
index 0000000..06813e2
--- /dev/null
+++ b/checker/tests/nullness-safedefaultsbytecode/BytecodeDefaultsTest.java
@@ -0,0 +1,27 @@
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+// Tests that the unchecked bytecode defaults option does not
+// affect defaulting nor suppress errors in source code.
+
+public class BytecodeDefaultsTest {
+  void f() {
+    g("");
+  }
+
+  void g(String s) {}
+}
+
+@AnnotatedFor("nullness")
+class HasErrors {
+  Object f() {
+    // :: error: (return)
+    return null;
+  }
+}
+
+class HasErrors2 {
+  Object f() {
+    // :: error: (return)
+    return null;
+  }
+}
diff --git a/checker/tests/nullness-safedefaultssourcecode/BasicSafeDefaultsTest.java b/checker/tests/nullness-safedefaultssourcecode/BasicSafeDefaultsTest.java
new file mode 100644
index 0000000..f8df59e
--- /dev/null
+++ b/checker/tests/nullness-safedefaultssourcecode/BasicSafeDefaultsTest.java
@@ -0,0 +1,50 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+// Tests that lack of @AnnotatedFor suppresses warnings, when
+// -AuseConservativeDefaultsForUncheckedCode=source is supplied.
+
+@AnnotatedFor("nullness")
+public class BasicSafeDefaultsTest {
+
+  void m1() {
+    @NonNull Object x1 = SdfuscLib.unannotated();
+    // :: error: (assignment)
+    @NonNull Object x2 = SdfuscLib.returnsNullable();
+    @NonNull Object x3 = SdfuscLib.returnsNonNull();
+    // :: error: (assignment)
+    @NonNull Object x4 = SdfuscLibNotAnnotatedFor.unannotated();
+    // :: error: (assignment)
+    @NonNull Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable();
+    @NonNull Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull();
+  }
+
+  void m2() {
+    @Nullable Object x1 = SdfuscLib.unannotated();
+    @Nullable Object x2 = SdfuscLib.returnsNullable();
+    @Nullable Object x3 = SdfuscLib.returnsNonNull();
+    @Nullable Object x4 = SdfuscLibNotAnnotatedFor.unannotated();
+    @Nullable Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable();
+    @Nullable Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull();
+  }
+}
+
+class BasicTestNotAnnotatedFor {
+  void m1() {
+    @NonNull Object x1 = SdfuscLib.unannotated();
+    @NonNull Object x2 = SdfuscLib.returnsNullable();
+    @NonNull Object x3 = SdfuscLib.returnsNonNull();
+    @NonNull Object x4 = SdfuscLibNotAnnotatedFor.unannotated();
+    @NonNull Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable();
+    @NonNull Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull();
+  }
+
+  void m2() {
+    @Nullable Object x1 = SdfuscLib.unannotated();
+    @Nullable Object x2 = SdfuscLib.returnsNullable();
+    @Nullable Object x3 = SdfuscLib.returnsNonNull();
+    @Nullable Object x4 = SdfuscLibNotAnnotatedFor.unannotated();
+    @Nullable Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable();
+    @Nullable Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull();
+  }
+}
diff --git a/checker/tests/nullness-safedefaultssourcecode/Issue3449.java b/checker/tests/nullness-safedefaultssourcecode/Issue3449.java
new file mode 100644
index 0000000..b334898
--- /dev/null
+++ b/checker/tests/nullness-safedefaultssourcecode/Issue3449.java
@@ -0,0 +1,15 @@
+// Test case for https://tinyurl.com/cfissue/3449
+
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+@AnnotatedFor("nullness")
+public class Issue3449 {
+
+  int length;
+  Object[] objs;
+
+  public Issue3449(Object... args) {
+    length = args.length;
+    objs = args;
+  }
+}
diff --git a/checker/tests/nullness-safedefaultssourcecode/PrimitiveClassLiteral.java b/checker/tests/nullness-safedefaultssourcecode/PrimitiveClassLiteral.java
new file mode 100644
index 0000000..a2d2be0
--- /dev/null
+++ b/checker/tests/nullness-safedefaultssourcecode/PrimitiveClassLiteral.java
@@ -0,0 +1,33 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+@AnnotatedFor("nullness")
+public class PrimitiveClassLiteral {
+  private static @Nullable Class<?> unwrapPrimitive(Class<?> c) {
+    if (c == Byte.class) {
+      return byte.class;
+    }
+    if (c == Character.class) {
+      return char.class;
+    }
+    if (c == Short.class) {
+      return short.class;
+    }
+    if (c == Integer.class) {
+      return int.class;
+    }
+    if (c == Long.class) {
+      return long.class;
+    }
+    if (c == Float.class) {
+      return float.class;
+    }
+    if (c == Double.class) {
+      return double.class;
+    }
+    if (c == Boolean.class) {
+      return boolean.class;
+    }
+    return c;
+  }
+}
diff --git a/checker/tests/nullness-safedefaultssourcecodelib/Lib.java b/checker/tests/nullness-safedefaultssourcecodelib/Lib.java
new file mode 100644
index 0000000..330bd55
--- /dev/null
+++ b/checker/tests/nullness-safedefaultssourcecodelib/Lib.java
@@ -0,0 +1,35 @@
+// A library to be compiled with
+//   -AuseConservativeDefaultsForUncheckedCode=source,bytecode
+// and then to be read from bytecode.
+
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+@AnnotatedFor("nullness")
+class SdfuscLib {
+  static Object unannotated() {
+    return new Object();
+  }
+
+  static @Nullable Object returnsNullable() {
+    return new Object();
+  }
+
+  static @NonNull Object returnsNonNull() {
+    return new Object();
+  }
+}
+
+class SdfuscLibNotAnnotatedFor {
+  static Object unannotated() {
+    return new Object();
+  }
+
+  static @Nullable Object returnsNullable() {
+    return new Object();
+  }
+
+  static @NonNull Object returnsNonNull() {
+    return new Object();
+  }
+}
diff --git a/checker/tests/nullness-skipdefs/OnlyUses.java-TODO b/checker/tests/nullness-skipdefs/OnlyUses.java-TODO
new file mode 100644
index 0000000..98c61f6
--- /dev/null
+++ b/checker/tests/nullness-skipdefs/OnlyUses.java-TODO
@@ -0,0 +1,71 @@
+// This test case illustrates the complications of the -AonlyUses command line option.  It would be run with -AonlyUses=OnlyMyUses.
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class OnlyUses {
+
+  static class OnlyMyUses {
+    @Nullable Object foo(@NonNull Object o) { return null; }
+    static @Nullable Object bar(@NonNull Object o) { return null; }
+
+    @Nullable OnlyMyUses foo2(@NonNull OnlyMyUses o) { return null; }
+    static @Nullable OnlyMyUses bar2(@NonNull OnlyMyUses o) { return null; }
+  }
+
+  static class NotMyUses {
+    @Nullable Object foo(@NonNull Object o) { return null; }
+    static @Nullable Object bar(@NonNull Object o) { return null; }
+  }
+
+  static class Client {
+    void m1(@NonNull Object nn, @Nullable Object nble) {
+      @NonNull Object x;
+
+      OnlyMyUses omu = new OnlyMyUses();
+      omu.foo(nn);
+      omu.foo(nble);
+      x = omu.foo(nn);
+
+      @NonNull OnlyMyUses omuX = new OnlyMyUses();
+      @NonNull OnlyMyUses omuNn = new OnlyMyUses();
+      @Nullable OnlyMyUses omuNble = null;
+      omu.foo2(omuNn);
+      omu.foo2(omuNble);
+      omuX = omu.foo2(omuNn);
+    }
+
+    void m2(@NonNull Object nn, @Nullable Object nble) {
+      @NonNull Object x;
+
+      OnlyMyUses.bar(nn);
+      OnlyMyUses.bar(nble);
+      x = OnlyMyUses.bar(nn);
+
+      @NonNull OnlyMyUses omuX = new OnlyMyUses();
+      @NonNull OnlyMyUses omuNn = new OnlyMyUses();
+      @Nullable OnlyMyUses omuNble = null;
+
+      OnlyMyUses.bar2(omuNn);
+      OnlyMyUses.bar2(omuNble);
+      omuX = OnlyMyUses.bar2(omuNn);
+    }
+
+    void m3(@NonNull Object nn, @Nullable Object nble) {
+      @NonNull Object x;
+
+      NotMyUses nmu = new NotMyUses();
+      nmu.foo(nn);
+      nmu.foo(nble);
+      x = nmu.foo(nn);
+    }
+
+    void m4(@NonNull Object nn, @Nullable Object nble) {
+      @NonNull Object x;
+
+      NotMyUses.bar(nn);
+      NotMyUses.bar(nble);
+      x = NotMyUses.bar(nn);
+    }
+  }
+
+}
diff --git a/checker/tests/nullness-skipdefs/README b/checker/tests/nullness-skipdefs/README
new file mode 100644
index 0000000..cd7ded5
--- /dev/null
+++ b/checker/tests/nullness-skipdefs/README
@@ -0,0 +1,9 @@
+This directory contains tests for the Nullness Checker, with the
+-AskipDefs=SkipMe flag.
+
+To add a new file to the test suite, just add it to this directory.
+For more details, see
+  ../README
+
+To run the tests, do this:
+  (cd $CHECKERFRAMEWORK && ./gradlew NullnessSkipDefsTest)
diff --git a/checker/tests/nullness-skipdefs/SkipDefs1.java b/checker/tests/nullness-skipdefs/SkipDefs1.java
new file mode 100644
index 0000000..63f2749
--- /dev/null
+++ b/checker/tests/nullness-skipdefs/SkipDefs1.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class SkipDefs1 {
+
+  static class SkipMe {
+    static Object foo() {
+      return null;
+    }
+  }
+
+  static class DontSkip {
+    static Object foo() {
+      // :: error: (return)
+      return null;
+    }
+  }
+}
diff --git a/checker/tests/nullness-skipdefs/SkipDefs2.java b/checker/tests/nullness-skipdefs/SkipDefs2.java
new file mode 100644
index 0000000..aaab65a
--- /dev/null
+++ b/checker/tests/nullness-skipdefs/SkipDefs2.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class SkipDefs2 {
+
+  static class SkipMe {
+    @Nullable Object f;
+
+    @EnsuresNonNull("f")
+    static void foo() {}
+  }
+
+  static class DontSkip {
+    static Object foo() {
+      // :: error: (return)
+      return null;
+    }
+  }
+}
diff --git a/checker/tests/nullness-skipuses/README b/checker/tests/nullness-skipuses/README
new file mode 100644
index 0000000..f40387f
--- /dev/null
+++ b/checker/tests/nullness-skipuses/README
@@ -0,0 +1,9 @@
+This directory contains tests for the Nullness Checker, with the
+-AskipUses=SkipMe flag.
+
+To add a new file to the test suite, just add it to this directory.
+For more details, see
+  ../README
+
+To run the tests, do this:
+  (cd $CHECKERFRAMEWORK && ./gradlew NullnessSkipUsesTest)
diff --git a/checker/tests/nullness-skipuses/SkipUses1.java b/checker/tests/nullness-skipuses/SkipUses1.java
new file mode 100644
index 0000000..08f350d
--- /dev/null
+++ b/checker/tests/nullness-skipuses/SkipUses1.java
@@ -0,0 +1,27 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class SkipUses1 {
+
+  static class SkipMe {
+    static @Nullable Object foo() {
+      return null;
+    }
+  }
+
+  static class DontSkip {
+    static @Nullable Object foo() {
+      return null;
+    }
+  }
+
+  static class Main {
+    void bar(boolean b) {
+      @NonNull Object x = SkipMe.foo();
+      // :: error: (assignment)
+      @NonNull Object y = DontSkip.foo();
+
+      // :: error: (assignment)
+      @NonNull Object z = b ? SkipMe.foo() : DontSkip.foo();
+    }
+  }
+}
diff --git a/checker/tests/nullness-skipuses/SkipUses2.java b/checker/tests/nullness-skipuses/SkipUses2.java
new file mode 100644
index 0000000..126d459
--- /dev/null
+++ b/checker/tests/nullness-skipuses/SkipUses2.java
@@ -0,0 +1,31 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public class SkipUses2 {
+
+  static class SkipMe {
+    static @Nullable Object f;
+
+    @RequiresNonNull("f")
+    static void foo() {}
+  }
+
+  static class DontSkip {
+    static @Nullable Object f;
+
+    @RequiresNonNull("f")
+    static @Nullable Object foo() {
+      return null;
+    }
+  }
+
+  static class Main {
+    void bar(boolean b) {
+      SkipMe.f = null;
+      SkipMe.foo();
+      DontSkip.f = null;
+      // :: error: (contracts.precondition)
+      DontSkip.foo();
+    }
+  }
+}
diff --git a/checker/tests/nullness-stubfile/Issue4598.java b/checker/tests/nullness-stubfile/Issue4598.java
new file mode 100644
index 0000000..9613ba4
--- /dev/null
+++ b/checker/tests/nullness-stubfile/Issue4598.java
@@ -0,0 +1,15 @@
+// Test case for Issue #4598
+
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue4598 {
+
+  final @Nullable Object d = null;
+
+  public Object foo() {
+    Objects.requireNonNull(d, "destination");
+    // :: error: (return)
+    return d;
+  }
+}
diff --git a/checker/tests/nullness-stubfile/NullnessStubfileMerge.java b/checker/tests/nullness-stubfile/NullnessStubfileMerge.java
new file mode 100644
index 0000000..acd127d
--- /dev/null
+++ b/checker/tests/nullness-stubfile/NullnessStubfileMerge.java
@@ -0,0 +1,46 @@
+// warning: stubfile1.astub:(line 16,col 6): Package-private method notReal(String) not found in
+// type java.lang.String
+// warning: stubfile1.astub:(line 19,col 1): Type not found: java.lang.NotARealClass
+// warning: stubfile1.astub:(line 21,col 1): Type not found: not.real.NotARealClassInNotRealPackage
+
+import org.checkerframework.checker.nullness.qual.*;
+
+/*
+ * This test reads two stub files:
+ * tests/nullness-stubfile/stubfile1.astub
+ * tests/nullness-stubfile/stubfile2.astub
+ *
+ * The annotations on the methods are merged such that reading the two
+ * stub files is equavlent to the following stubfile:
+ *
+public final class  String {
+     public @Nullable String intern();
+     public @NonNull String substring(@Nullable int beginIndex) @Nullable;
+     String(@Nullable String arg0);
+     void getChars(@Nullable int arg0, @NonNull int arg1, @NonNull char @NonNull [] arg2, @NonNull int arg3) @NonNull;
+}
+*/
+public class NullnessStubfileMerge {
+  @Nullable String nullString = null;
+  @NonNull String nonNull = "Hello!";
+
+  void method() {
+    // below fails because of stub file overruling annotated JDK
+    // :: error: (type.argument)
+    java.util.List<@NonNull String> l;
+
+    // :: error: (assignment)
+    @NonNull String error1 = nonNull.intern();
+
+    nonNull.substring('!');
+
+    @NonNull String y = nonNull.substring('!');
+
+    char[] nonNullChars = {'1', '1'};
+    char[] nullChars = null;
+    nonNull.getChars(1, 1, nonNullChars, 1);
+
+    // :: error: (argument)
+    nonNull.getChars(1, 1, nullChars, 1);
+  }
+}
diff --git a/checker/tests/nullness-stubfile/requireNonNull.astub b/checker/tests/nullness-stubfile/requireNonNull.astub
new file mode 100644
index 0000000..d8b3deb
--- /dev/null
+++ b/checker/tests/nullness-stubfile/requireNonNull.astub
@@ -0,0 +1,15 @@
+package java.util;
+
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public final class Objects {
+
+  // `@EnsuresNonNull({})` overrides `@EnsuresNonNull("#1")` in the annotated JDK.
+
+  @EnsuresNonNull({})
+  public static <T> T requireNonNull(@Nullable T obj);
+
+  @EnsuresNonNull({})
+  public static <T> T requireNonNull(@Nullable T obj, String message);
+}
diff --git a/checker/tests/nullness-stubfile/stubfile1.astub b/checker/tests/nullness-stubfile/stubfile1.astub
new file mode 100644
index 0000000..66605d1
--- /dev/null
+++ b/checker/tests/nullness-stubfile/stubfile1.astub
@@ -0,0 +1,21 @@
+// This stub file will be read first.
+
+import org.checkerframework.checker.nullness.qual.*;
+
+package java.util;
+public interface List<@Nullable T> {}  // overrules annotated JDK
+
+package java.lang;
+
+public final class String {
+     public @NonNull String intern();
+     public @Nullable String substring(@NonNull int beginIndex);
+     String(@Nullable String arg0);
+     void getChars(@Nullable int arg0, @NonNull int arg1, @NonNull char @NonNull [] arg2, @NonNull int arg3);
+// Used to test stub file warnings.
+     void notReal(String s);
+}
+// The rest of these are used to test stub file warnings.
+public class NotARealClass{}
+package not.real;
+public class NotARealClassInNotRealPackage{}
diff --git a/checker/tests/nullness-stubfile/stubfile2.astub b/checker/tests/nullness-stubfile/stubfile2.astub
new file mode 100644
index 0000000..f4ad5f9
--- /dev/null
+++ b/checker/tests/nullness-stubfile/stubfile2.astub
@@ -0,0 +1,14 @@
+// This stub file will be read second.
+// If any of these conflict with stubfile1,
+// then the annotations in the stub file will be used.
+
+import org.checkerframework.checker.nullness.qual.*;
+
+package java.lang;
+
+public final class String {
+    public @Nullable String intern();
+    public @NonNull String substring(@Nullable int beginIndex);
+    String(@Nullable String arg0);
+    void getChars(@Nullable int arg0, @NonNull int arg1, @NonNull char @NonNull [] arg2, int arg3);
+}
diff --git a/checker/tests/nullness-temp/README b/checker/tests/nullness-temp/README
new file mode 100644
index 0000000..8a5785b
--- /dev/null
+++ b/checker/tests/nullness-temp/README
@@ -0,0 +1,16 @@
+This directory is for temporary tests for the Nullness Checker
+
+The point is that putting them here permits them to be run quickly, rather
+than (say) putting them in the main nullness test suite and having to run
+that entire suite.
+
+Don't check any files into this directory, just use them in your local
+copy.
+
+
+To add a new file to the test suite, just add it to this directory.
+For more details, see
+  ../README
+
+To run the tests, do
+  (cd $CHECKERFRAMEWORK && ./gradlew NullnessTempTest)
diff --git a/checker/tests/nullness/AliasedAnnotations.java b/checker/tests/nullness/AliasedAnnotations.java
new file mode 100644
index 0000000..a88b5a7
--- /dev/null
+++ b/checker/tests/nullness/AliasedAnnotations.java
@@ -0,0 +1,86 @@
+// This uses all the aliases listed in section "Other tools for nullness
+// checking" of the Checker Framework manual.
+
+public class AliasedAnnotations {
+
+  void useNonNullAnnotations() {
+    // :: error: (assignment)
+    @org.checkerframework.checker.nullness.qual.NonNull Object nn1 = null;
+    // :: error: (assignment)
+    @com.sun.istack.internal.NotNull Object nn2 = null;
+    // :: error: (assignment)
+    @edu.umd.cs.findbugs.annotations.NonNull Object nn3 = null;
+    // :: error: (assignment)
+    @javax.annotation.Nonnull Object nn4 = null;
+    // Invalid location for NonNull :: error: (assignment)
+    // @javax.validation.constraints.NotNull Object nn5 = null;
+    // :: error: (assignment)
+    @org.eclipse.jdt.annotation.NonNull Object nn6 = null;
+    // :: error: (assignment)
+    @org.jetbrains.annotations.NotNull Object nn7 = null;
+    // :: error: (assignment)
+    @org.netbeans.api.annotations.common.NonNull Object nn8 = null;
+    // :: error: (assignment)
+    @org.jmlspecs.annotation.NonNull Object nn9 = null;
+  }
+
+  void useNullableAnnotations1(@org.checkerframework.checker.nullness.qual.Nullable Object nble) {
+    // :: error: (dereference.of.nullable)
+    nble.toString();
+  }
+
+  void useNullableAnnotations2(@com.sun.istack.internal.Nullable Object nble) {
+    // :: error: (dereference.of.nullable)
+    nble.toString();
+  }
+
+  void useNullableAnnotations3(@edu.umd.cs.findbugs.annotations.Nullable Object nble) {
+    // :: error: (dereference.of.nullable)
+    nble.toString();
+  }
+
+  void useNullableAnnotations4(@edu.umd.cs.findbugs.annotations.CheckForNull Object nble) {
+    // :: error: (dereference.of.nullable)
+    nble.toString();
+  }
+
+  void useNullableAnnotations5(@edu.umd.cs.findbugs.annotations.UnknownNullness Object nble) {
+    // :: error: (dereference.of.nullable)
+    nble.toString();
+  }
+
+  void useNullableAnnotations6(@javax.annotation.Nullable Object nble) {
+    // :: error: (dereference.of.nullable)
+    nble.toString();
+  }
+
+  void useNullableAnnotations7(@javax.annotation.CheckForNull Object nble) {
+    // :: error: (dereference.of.nullable)
+    nble.toString();
+  }
+
+  void useNullableAnnotations9(@org.eclipse.jdt.annotation.Nullable Object nble) {
+    // :: error: (dereference.of.nullable)
+    nble.toString();
+  }
+
+  void useNullableAnnotations10(@org.jetbrains.annotations.Nullable Object nble) {
+    // :: error: (dereference.of.nullable)
+    nble.toString();
+  }
+
+  void useNullableAnnotations12(@org.netbeans.api.annotations.common.NullAllowed Object nble) {
+    // :: error: (dereference.of.nullable)
+    nble.toString();
+  }
+
+  void useNullableAnnotations13(@org.netbeans.api.annotations.common.NullUnknown Object nble) {
+    // :: error: (dereference.of.nullable)
+    nble.toString();
+  }
+
+  void useNullableAnnotations14(@org.jmlspecs.annotation.Nullable Object nble) {
+    // :: error: (dereference.of.nullable)
+    nble.toString();
+  }
+}
diff --git a/checker/tests/nullness/Aliasing.java b/checker/tests/nullness/Aliasing.java
new file mode 100644
index 0000000..17314f5
--- /dev/null
+++ b/checker/tests/nullness/Aliasing.java
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Aliasing {
+  @NonNull Object nno = new Object();
+  @Nullable Object no = null;
+
+  public static void main(String[] args) {
+    Aliasing a = new Aliasing();
+    Aliasing b = new Aliasing();
+    m(a, b);
+  }
+
+  static void m(@NonNull Aliasing a, @NonNull Aliasing b) {
+    a.no = b.nno;
+    // Changing a.no to nonnull does not mean that b.no is also nonnull
+    // :: error: (assignment)
+    b.nno = b.no;
+
+    System.out.println("@NonNull field b.nno is: " + b.nno);
+  }
+}
diff --git a/checker/tests/nullness/AnnotatedJdkEqualsTest.java b/checker/tests/nullness/AnnotatedJdkEqualsTest.java
new file mode 100644
index 0000000..fd91bd7
--- /dev/null
+++ b/checker/tests/nullness/AnnotatedJdkEqualsTest.java
@@ -0,0 +1,18 @@
+// @skip-test
+
+// Test case for issue 286: https://github.com/typetools/checker-framework/issues/286
+
+import java.net.URL;
+
+public class AnnotatedJdkEqualsTest {
+  void foo(URL u) {
+    // As of this writing, the annotated JDK does not contain a URL.java file
+    // for the java.net.URL class.
+    // Nonetheless, the following code should type-check.
+    // This could be handled via inheritance of annotations from superclasses either during JDK
+    // creation or during type-checking.  It would be impractical to manually annotate every method
+    // in the entire JDK:  it would be too labor-intensive and there would be certain to be some
+    // oversights.
+    u.equals(null);
+  }
+}
diff --git a/checker/tests/nullness/AnnotatedJdkTest.java b/checker/tests/nullness/AnnotatedJdkTest.java
new file mode 100644
index 0000000..86401e5
--- /dev/null
+++ b/checker/tests/nullness/AnnotatedJdkTest.java
@@ -0,0 +1,17 @@
+// Test case for issue 370: https://github.com/typetools/checker-framework/issues/370
+
+import java.util.Arrays;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class AnnotatedJdkTest {
+  // This code should type-check because of the annotated JDK, which contains:
+  //   class Arrays {
+  //     public static <T> List<T> asList(T... a);
+  //   }
+  // That JDK annotation should be equivalent to
+  //     public static <T extends @Nullable Object> List<T> asList(T... a);
+  // because of the CLIMB-to-top defaulting rule.
+
+  List<@Nullable String> lns = Arrays.asList("foo", null, "bar");
+}
diff --git a/checker/tests/nullness/AnnotatedSupertype.java b/checker/tests/nullness/AnnotatedSupertype.java
new file mode 100644
index 0000000..8922a98
--- /dev/null
+++ b/checker/tests/nullness/AnnotatedSupertype.java
@@ -0,0 +1,18 @@
+import java.io.Serializable;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class AnnotatedSupertype {
+
+  class NullableSupertype
+      // :: error: (nullness.on.supertype)
+      extends @Nullable Object
+      // :: error: (nullness.on.supertype)
+      implements @Nullable Serializable {}
+
+  @NonNull class NonNullSupertype
+      // :: error: (nullness.on.supertype)
+      extends @NonNull Object
+      // :: error: (nullness.on.supertype)
+      implements @NonNull Serializable {}
+}
diff --git a/checker/tests/nullness/AnonymousSkipDefs.java b/checker/tests/nullness/AnonymousSkipDefs.java
new file mode 100644
index 0000000..2eb9ee1
--- /dev/null
+++ b/checker/tests/nullness/AnonymousSkipDefs.java
@@ -0,0 +1,22 @@
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+public class AnonymousSkipDefs {
+
+  public static void main(String[] args) {
+    call(
+        new Runnable() {
+          @Override
+          public void run() {
+            @Nullable Object veryNull = null;
+            // :: error: (assignment)
+            @NonNull Object notNull = veryNull;
+            notNull.toString();
+          }
+        });
+  }
+
+  private static void call(Runnable r) {
+    r.run();
+  }
+}
diff --git a/checker/tests/nullness/ArrayArgs.java b/checker/tests/nullness/ArrayArgs.java
new file mode 100644
index 0000000..8916343
--- /dev/null
+++ b/checker/tests/nullness/ArrayArgs.java
@@ -0,0 +1,32 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+@org.checkerframework.framework.qual.DefaultQualifier(Nullable.class)
+public class ArrayArgs {
+
+  public void test(@NonNull String[] args) {}
+
+  public void test(Class<@NonNull ? extends java.lang.annotation.Annotation> cls) {}
+
+  public void test() {
+    test(NonNull.class);
+
+    String[] s1 = new String[] {null, null, null};
+    // :: error: (argument)
+    test(s1);
+    String[] s2 = new String[] {"hello", null, "goodbye"};
+    // :: error: (argument)
+    test(s2);
+    // :: error: (assignment)
+    @NonNull String[] s3 = new String[] {"hello", null, "goodbye"};
+    // :: error: (new.array)
+    @NonNull String[] s4 = new String[3];
+
+    // TODO: when issue 25 is fixed, the following is safe
+    // and no error needs to be raised.
+    String[] s5 = new String[] {"hello", "goodbye"};
+    // :: error: (argument)
+    test(s5);
+    @NonNull String[] s6 = new String[] {"hello", "goodbye"};
+    test(s6);
+  }
+}
diff --git a/checker/tests/nullness/ArrayAssignmentFlow.java b/checker/tests/nullness/ArrayAssignmentFlow.java
new file mode 100644
index 0000000..6fa0bad
--- /dev/null
+++ b/checker/tests/nullness/ArrayAssignmentFlow.java
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ArrayAssignmentFlow {
+
+  public void add_combined(MyPptTopLevel ppt) {
+
+    @Nullable Object[] vals = new Object[10];
+
+    if (ppt.last_values != null) {
+      // Assigning to an array element should not cause flow information
+      // about ppt.last_values to be discarded.
+      vals[0] = ppt.last_values.vals;
+      ppt.last_values.toString();
+    }
+  }
+}
+
+class MyPptTopLevel {
+  public @Nullable MyValueTuple last_values = null;
+}
+
+class MyValueTuple {
+  public Object vals = new Object();
+}
diff --git a/checker/tests/nullness/ArrayCreationNullable.java b/checker/tests/nullness/ArrayCreationNullable.java
new file mode 100644
index 0000000..b042b05
--- /dev/null
+++ b/checker/tests/nullness/ArrayCreationNullable.java
@@ -0,0 +1,185 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+/**
+ * The component type of newly created arrays is always @Nullable, also for boxed types. This is an
+ * expanded version of the test case for Issue 151:
+ * https://github.com/typetools/checker-framework/issues/151
+ */
+public class ArrayCreationNullable {
+
+  void testObjectArray(@NonNull Object @NonNull [] p) {
+    @NonNull Object @NonNull [] objs;
+    // :: error: (new.array)
+    objs = new Object[10];
+    objs[0].toString();
+    // :: error: (assignment)
+    objs = new @Nullable Object[10];
+    objs[0].toString();
+    // :: error: (new.array)
+    objs = new @NonNull Object[10];
+    objs[0].toString();
+    // Allowed.
+    objs = p;
+    objs[0].toString();
+  }
+
+  @DefaultQualifier(NonNull.class)
+  void testObjectArray2() {
+    Object[] objs;
+    // Even if the default qualifier is NonNull, array component
+    // types must be Nullable.
+    // :: error: (new.array)
+    objs = new Object[10];
+    objs[0].toString();
+  }
+
+  void testInitializers() {
+    Object[] objs = {1, 2, 3};
+    objs = new Integer[] {1, 2, 3};
+    objs = new Object[] {new Object(), "ha"};
+
+    @NonNull Object[] objs2 = {};
+    // :: error: (assignment)
+    objs2 = new Integer[] {1, null, 3};
+    // :: error: (assignment)
+    objs2 = new Object[] {new Object(), "ha", null};
+
+    @NonNull Object[] objs3 = new Integer[] {1, 2, 3};
+    objs3 = new Integer[] {1, 2, 3};
+    // :: error: (assignment)
+    objs3 = new Integer[] {1, 2, 3, null};
+
+    (new Integer[] {1, 2, 3})[0].toString();
+    // :: error: (dereference.of.nullable)
+    (new Integer[] {1, 2, 3, null})[0].toString();
+
+    // The assignment context is used to infer a @Nullable component type.
+    @Nullable Object[] objs4 = new Integer[] {1, 2, 3};
+    // :: error: (dereference.of.nullable)
+    objs4[0].toString();
+    objs4 = new Integer[] {1, 2, 3};
+  }
+
+  void testStringArray(@NonNull String @NonNull [] p) {
+    @NonNull String @NonNull [] strs;
+    // :: error: (new.array)
+    strs = new String[10];
+    strs[0].toString();
+    // :: error: (assignment)
+    strs = new @Nullable String[10];
+    strs[0].toString();
+    // :: error: (new.array)
+    strs = new @NonNull String[10];
+    strs[0].toString();
+    // Allowed.
+    strs = p;
+    strs[0].toString();
+  }
+
+  void testIntegerArray(@NonNull Integer @NonNull [] p) {
+    @NonNull Integer @NonNull [] ints;
+    // :: error: (new.array)
+    ints = new Integer[10];
+    ints[0].toString();
+    // :: error: (assignment)
+    ints = new @Nullable Integer[10];
+    ints[0].toString();
+    // :: error: (new.array)
+    ints = new @NonNull Integer[10];
+    ints[0].toString();
+    // Allowed.
+    ints = p;
+    ints[0].toString();
+  }
+
+  // The component type of zero-length arrays can be non-null - they will always generate
+  // IndexOutOfBoundsExceptions, but are usually just used for the type, e.g. in List.toArray.
+  void testLengthZero() {
+    @NonNull Object @NonNull [] objs;
+    objs = new Object[0];
+  }
+
+  /* Test case for Issue 153.
+  // toArray re-uses the passed array, if it is of appropriate size.
+  // It is only guaranteed to be non-null, if it is at most the same size.
+  void testToArray(java.util.Set<Object> nns) {
+      @NonNull Object [] nna = nns.toArray(new Object[nns.size()]);
+      // Given array is too small -> new one is created.
+      nna = nns.toArray(new Object[nns.size()-2]);
+      // Padding elements will be null.
+      // TODO:: error: (assignment)
+      nna = nns.toArray(new Object[nns.size() + 2]);
+      @Nullable Object [] nbla = nns.toArray(new Object[nns.size() + 2]);
+  }
+  */
+
+  void testMultiDim() {
+    // new double[10][10] has type double @NonNull[] @Nullable[]
+    // :: error: (new.array)
+    double @NonNull [] @NonNull [] daa = new double[10][10];
+    double @NonNull [] @Nullable [] daa2 = new double[10][10];
+
+    // new Object[10][10] has type @Nullable Object @NonNull[] @Nullable[]
+    // :: error: (new.array)
+    @Nullable Object @NonNull [] @NonNull [] oaa = new Object[10][10];
+    @Nullable Object @NonNull [] @Nullable [] oaa2 = new Object[10][10];
+
+    // new Object[10][10] has type @Nullable Object @NonNull[] @Nullable[]
+    // :: error: (new.array)
+    oaa2 = new Object @NonNull [10] @NonNull [10];
+
+    @MonotonicNonNull Object @NonNull [] @MonotonicNonNull [] oaa3 =
+        new @MonotonicNonNull Object @NonNull [10] @MonotonicNonNull [10];
+    oaa3[0] = new @MonotonicNonNull Object[4];
+    // :: error: (assignment)
+    oaa3[0] = null;
+    // :: error: (assignment) :: error: (accessing.nullable)
+    oaa3[0][0] = null;
+  }
+
+  @PolyNull Object[] testPolyNull(@PolyNull Object[] in) {
+    @PolyNull Object[] out = new @PolyNull Object[in.length];
+    for (int i = 0; i < in.length; ++i) {
+      if (in[i] == null) {
+        out[i] = null;
+      } else {
+        out[i] = in[i].getClass().toString();
+        // :: error: (assignment)
+        out[i] = null;
+      }
+    }
+    return out;
+  }
+
+  void testMonotonicNonNull() {
+    @MonotonicNonNull Object @NonNull [] loa = new @MonotonicNonNull Object @NonNull [10];
+    loa = new Object @NonNull [10];
+    loa[0] = new Object();
+    @MonotonicNonNull Object @NonNull [] loa2 = new Object @NonNull [10];
+    // :: error: (dereference.of.nullable)
+    loa2[0].toString();
+  }
+
+  @MonotonicNonNull Object @NonNull [] testReturnContext() {
+    return new Object[10];
+  }
+
+  // :: error: (new.array)
+  @NonNull Object @NonNull [] oa0 = new Object[10];
+
+  // OK
+  @MonotonicNonNull Object @NonNull [] loa0 = new @MonotonicNonNull Object @NonNull [10];
+
+  Object[] oa1 = new Object[] {new Object()};
+
+  // :: error: (assignment)
+  Object[] oa2 = new Object[] {new Object(), null};
+
+  public static void main(String[] args) {
+    ArrayCreationNullable e = new ArrayCreationNullable();
+    Integer[] ints = new Integer[] {5, 6};
+    // This would result in a NPE, if there were no error.
+    e.testIntegerArray(ints);
+  }
+}
diff --git a/checker/tests/nullness/ArrayCreationSubArray.java b/checker/tests/nullness/ArrayCreationSubArray.java
new file mode 100644
index 0000000..75a13b1
--- /dev/null
+++ b/checker/tests/nullness/ArrayCreationSubArray.java
@@ -0,0 +1,23 @@
+// Test case for issue #599:
+// https://github.com/typetools/checker-framework/issues/599
+
+// @skip-test Commented out until the bug is fixed
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ArrayCreationSubArray {
+
+  void m() {
+
+    Object o1a = new Integer[] {1, 2, null, 3, 4};
+    @Nullable Integer[] o1b = new Integer[] {1, 2, null, 3, 4};
+
+    Object o2a = new Object[] {null};
+    Object o2b = new @Nullable Object[] {null};
+
+    Object o3a = new Object[][] {new Object[] {null}};
+    Object o3b = new Object[][] {new @Nullable Object[] {null}};
+    Object o3c = new @Nullable Object[][] {new @Nullable Object[] {null}};
+    Object o3d = new Object[][] {{null}};
+  }
+}
diff --git a/checker/tests/nullness/ArrayIndex.java b/checker/tests/nullness/ArrayIndex.java
new file mode 100644
index 0000000..6b7800d
--- /dev/null
+++ b/checker/tests/nullness/ArrayIndex.java
@@ -0,0 +1,16 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class ArrayIndex {
+  void foo(@Nullable Object[] a, int i) {
+    if (a[i] != null) {
+      a[i].hashCode();
+    }
+    if (a[i + 1] != null) {
+      a[i + 1].hashCode();
+    }
+    if (a[i + 1] != null) {
+      // :: error: (dereference.of.nullable)
+      a[i].hashCode();
+    }
+  }
+}
diff --git a/checker/tests/nullness/ArrayInitBug.java b/checker/tests/nullness/ArrayInitBug.java
new file mode 100644
index 0000000..a0fd8f4
--- /dev/null
+++ b/checker/tests/nullness/ArrayInitBug.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ArrayInitBug {
+
+  @Nullable Object @Nullable [] aa;
+
+  public ArrayInitBug() {
+    aa = null;
+  }
+}
diff --git a/checker/tests/nullness/ArrayLazyNN.java b/checker/tests/nullness/ArrayLazyNN.java
new file mode 100644
index 0000000..eda610b
--- /dev/null
+++ b/checker/tests/nullness/ArrayLazyNN.java
@@ -0,0 +1,19 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+/* Use @MonotonicNonNull as component type to ensure that null can never be
+ * assigned into a component. Then, after a single iteration over the array,
+ * we can be sure that all elements are non-null.
+ * TODO: support for (i=0; i < a.length.... and change component type to non-null.
+ */
+public class ArrayLazyNN {
+  void test1() {
+    @MonotonicNonNull Object[] o1 = new @MonotonicNonNull Object[10];
+    o1[0] = new Object();
+    // :: error: (assignment)
+    o1[0] = null;
+    // :: error: (assignment)
+    @NonNull Object[] o2 = o1;
+    @SuppressWarnings("nullness")
+    @NonNull Object[] o3 = o1;
+  }
+}
diff --git a/checker/tests/nullness/ArrayNew.java b/checker/tests/nullness/ArrayNew.java
new file mode 100644
index 0000000..4561732
--- /dev/null
+++ b/checker/tests/nullness/ArrayNew.java
@@ -0,0 +1,21 @@
+import java.util.Collection;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ArrayNew {
+  void m(Collection<? extends @NonNull Integer> seq1) {
+    Integer[] seq1_array = new @NonNull Integer[] {5};
+    Integer[] seq2_array = seq1.toArray(new @NonNull Integer[0]);
+  }
+
+  void takePrim1d(int[] ar) {}
+
+  void callPrim1d() {
+    takePrim1d(new int[] {1, 2, 1});
+  }
+
+  void takePrim2d(int[][] ar) {}
+
+  void callPrim2d() {
+    takePrim2d(new int[][] {{1, 2, 1}, {3, 3, 7}});
+  }
+}
diff --git a/checker/tests/nullness/ArrayRefs.java b/checker/tests/nullness/ArrayRefs.java
new file mode 100644
index 0000000..d830ef5
--- /dev/null
+++ b/checker/tests/nullness/ArrayRefs.java
@@ -0,0 +1,39 @@
+import java.util.Arrays;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ArrayRefs {
+
+  public void test() {
+
+    String[] s = null;
+
+    // :: error: (dereference.of.nullable)
+    if (s.length > 0) {
+      System.out.println("s.length > 0");
+    }
+  }
+
+  public static void test2() {
+    Object a = new Object();
+    takeNNList(Arrays.<Object>asList(new Object[] {a}));
+    takeNNList(Arrays.asList(new Object[] {a}));
+    takeNNList(Arrays.asList(a, a, a));
+    // :: error: (argument)
+    takeNNList(Arrays.asList(a, a, null));
+  }
+
+  static void takeNNList(List<Object> p) {}
+
+  <T> void test(T[] a) {
+    test(a);
+  }
+
+  List<Object> @Nullable [] antecedents_for_suppressors() {
+    return null;
+  }
+
+  public void find_suppressed_invs() {
+    List<Object>[] antecedents = antecedents_for_suppressors();
+  }
+}
diff --git a/checker/tests/nullness/AssertAfter.java b/checker/tests/nullness/AssertAfter.java
new file mode 100644
index 0000000..328bd42
--- /dev/null
+++ b/checker/tests/nullness/AssertAfter.java
@@ -0,0 +1,76 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+
+public class AssertAfter {
+
+  protected @Nullable String value;
+
+  @EnsuresNonNull("value")
+  public boolean setRepNonNull() {
+    value = "";
+    return true;
+  }
+
+  public void plain() {
+    // :: error: (dereference.of.nullable)
+    value.toString();
+  }
+
+  public void testAfter() {
+    setRepNonNull();
+    value.toString();
+  }
+
+  public void testBefore() {
+    // :: error: (dereference.of.nullable)
+    value.toString();
+    setRepNonNull();
+  }
+
+  public void withCondition(@Nullable String t) {
+    if (t == null) {
+      setRepNonNull();
+    }
+    // :: error: (dereference.of.nullable)
+    value.toString();
+  }
+
+  public void inConditionInTrue() {
+    if (setRepNonNull()) {
+      value.toString();
+    } else {
+      // nothing to do
+    }
+  }
+
+  // skip-test: Come back when working on improved flow
+  public void asCondition() {
+    if (setRepNonNull()) {
+    } else {
+      value.toString(); // valid!
+    }
+  }
+}
+
+// Test that private fields can be mentioned in pre- and post-conditions.
+
+class A {
+  private @Nullable String privateField = null;
+
+  @EnsuresNonNull("privateField")
+  public void m1() {
+    privateField = "hello";
+  }
+
+  @RequiresNonNull("privateField")
+  public void m2() {}
+}
+
+class B {
+
+  void f() {
+    A a = new A();
+    a.m1();
+    a.m2();
+  }
+}
diff --git a/checker/tests/nullness/AssertAfter2.java b/checker/tests/nullness/AssertAfter2.java
new file mode 100644
index 0000000..fe06499
--- /dev/null
+++ b/checker/tests/nullness/AssertAfter2.java
@@ -0,0 +1,105 @@
+import java.util.HashMap;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+
+public class AssertAfter2 {
+
+  public class Graph<T> {
+
+    HashMap<T, List<@KeyFor("childMap") T>> childMap;
+
+    public Graph(HashMap<T, List<@KeyFor("childMap") T>> childMap) {
+      this.childMap = childMap;
+    }
+
+    @SuppressWarnings("contracts.postcondition")
+    @EnsuresNonNull("childMap.get(#1)")
+    public void addNode(final T n) {
+      // body omitted, not relevant to test case
+    }
+
+    public void addEdge(T parent, T child) {
+      addNode(parent);
+      @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent);
+    }
+
+    public void addEdgeBad1(T parent, T child) {
+      // :: error: (assignment)
+      @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent);
+    }
+
+    public void addEdgeBad2(T parent, T child) {
+      addNode(parent);
+      // :: error: (assignment)
+      @NonNull List<@KeyFor("childMap") T> l = childMap.get(child);
+    }
+
+    public void addEdgeBad3(T parent, T child) {
+      addNode(parent);
+      parent = child;
+      // :: error: (assignment)
+      @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent);
+    }
+
+    public void addEdgeOK(T parent, T child) {
+      List<@KeyFor("childMap") T> l = childMap.get(parent);
+    }
+  }
+
+  class MultiParam {
+
+    MultiParam(MultiParam thing, Object f1, Object f2, Object f3) {
+      this.thing = thing;
+      this.f1 = f1;
+      this.f2 = f2;
+      this.f3 = f3;
+    }
+
+    MultiParam thing;
+
+    @SuppressWarnings("contracts.postcondition")
+    @EnsuresNonNull("get(#1, #2, #3)")
+    void add(final Object o1, final Object o2, final Object o3) {
+      // body omitted, not relevant to test case
+    }
+
+    @org.checkerframework.dataflow.qual.Pure
+    @Nullable Object get(Object o1, Object o2, Object o3) {
+      return null;
+    }
+
+    Object f1, f2, f3;
+
+    void addGood1() {
+      thing.add(f1, f2, f3);
+      @NonNull Object nn = thing.get(f1, f2, f3);
+    }
+
+    void addBad1() {
+      // :: error: (assignment)
+      @NonNull Object nn = get(f1, f2, f3);
+    }
+
+    void addBad2() {
+      thing.add(f1, f2, f3);
+      f1 = new Object();
+      // :: error: (assignment)
+      @NonNull Object nn = thing.get(f1, f2, f3);
+    }
+
+    void addBad3() {
+      thing.add(f1, f2, f3);
+      f2 = new Object();
+      // :: error: (assignment)
+      @NonNull Object nn = thing.get(f1, f2, f3);
+    }
+
+    void addBad4() {
+      thing.add(f1, f2, f3);
+      f3 = new Object();
+      // :: error: (assignment)
+      @NonNull Object nn = thing.get(f1, f2, f3);
+    }
+  }
+}
diff --git a/checker/tests/nullness/AssertAfterChecked.java b/checker/tests/nullness/AssertAfterChecked.java
new file mode 100644
index 0000000..b503471
--- /dev/null
+++ b/checker/tests/nullness/AssertAfterChecked.java
@@ -0,0 +1,151 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+
+public class AssertAfterChecked {
+
+  class InitField {
+    @Nullable Object f;
+
+    @EnsuresNonNull("f")
+    void init() {
+      f = new Object();
+    }
+
+    @EnsuresNonNull("f")
+    // :: error: (contracts.postcondition)
+    void initBad() {}
+
+    void testInit() {
+      init();
+      f.toString();
+    }
+  }
+
+  static class InitStaticField {
+    static @Nullable Object f;
+
+    @EnsuresNonNull("f")
+    void init() {
+      f = new Object();
+    }
+
+    @EnsuresNonNull("f")
+    void init2() {
+      InitStaticField.f = new Object();
+    }
+
+    @EnsuresNonNull("f")
+    // :: error: (contracts.postcondition)
+    void initBad() {}
+
+    void testInit() {
+      init();
+      f.toString();
+    }
+
+    @EnsuresNonNull("InitStaticField.f")
+    void initE() {
+      f = new Object();
+    }
+
+    @EnsuresNonNull("InitStaticField.f")
+    void initE2() {
+      InitStaticField.f = new Object();
+    }
+
+    @EnsuresNonNull("InitStaticField.f")
+    // :: error: (contracts.postcondition)
+    void initBadE() {}
+
+    void testInitE() {
+      initE();
+      // TODO: we need to also support the unqualified static field access?
+      // f.toString();
+    }
+
+    void testInitE2() {
+      initE();
+      InitStaticField.f.toString();
+    }
+  }
+
+  class TestParams {
+    @EnsuresNonNull("get(#1)")
+    // :: error: (contracts.postcondition)
+    void init(final TestParams p) {}
+
+    @org.checkerframework.dataflow.qual.Pure
+    @Nullable Object get(Object o) {
+      return null;
+    }
+
+    void testInit1() {
+      init(this);
+      get(this).toString();
+    }
+
+    void testInit1b() {
+      init(this);
+      this.get(this).toString();
+    }
+
+    void testInit2(TestParams p) {
+      init(p);
+      get(p).toString();
+    }
+
+    void testInit3(TestParams p) {
+      p.init(this);
+      p.get(this).toString();
+    }
+
+    void testInit4(TestParams p) {
+      p.init(this);
+      // :: error: (dereference.of.nullable)
+      this.get(this).toString();
+    }
+  }
+
+  class WithReturn {
+    @Nullable Object f;
+
+    @EnsuresNonNull("f")
+    int init1() {
+      f = new Object();
+      return 0;
+    }
+
+    @EnsuresNonNull("f")
+    int init2() {
+      if (5 == 5) {
+        f = new Object();
+        return 0;
+      } else {
+        f = new Object();
+        return 1;
+      }
+    }
+
+    @EnsuresNonNull("f")
+    // :: error: (contracts.postcondition)
+    int initBad1() {
+      return 0;
+    }
+
+    @EnsuresNonNull("f")
+    // :: error: (contracts.postcondition)
+    int initBad2() {
+      if (5 == 5) {
+        return 0;
+      } else {
+        f = new Object();
+        return 1;
+      }
+    }
+
+    void testInit() {
+      init1();
+      f.toString();
+    }
+  }
+}
diff --git a/checker/tests/nullness/AssertIfChecked.java b/checker/tests/nullness/AssertIfChecked.java
new file mode 100644
index 0000000..f5f9b1b
--- /dev/null
+++ b/checker/tests/nullness/AssertIfChecked.java
@@ -0,0 +1,163 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+public class AssertIfChecked {
+
+  boolean unknown = false;
+
+  @Nullable Object value;
+
+  @EnsuresNonNullIf(result = true, expression = "value")
+  // :: error: (contracts.conditional.postcondition.returntype)
+  public void badform1() {}
+
+  @EnsuresNonNullIf(result = true, expression = "value")
+  // :: error: (contracts.conditional.postcondition.returntype)
+  public Object badform2() {
+    return new Object();
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "value")
+  // :: error: (contracts.conditional.postcondition.returntype)
+  public void badform3() {}
+
+  @EnsuresNonNullIf(result = false, expression = "value")
+  // :: error: (contracts.conditional.postcondition.returntype)
+  public Object badform4() {
+    return new Object();
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "value")
+  public boolean goodt1() {
+    return value != null;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "value")
+  public boolean badt1() {
+    // :: error: (contracts.conditional.postcondition)
+    return value == null;
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "value")
+  public boolean goodf1() {
+    return value == null;
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "value")
+  public boolean badf1() {
+    // :: error: (contracts.conditional.postcondition)
+    return value != null;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "value")
+  public boolean bad2() {
+    // :: error: (contracts.conditional.postcondition)
+    return value == null || unknown;
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "value")
+  public boolean bad3() {
+    // :: error: (contracts.conditional.postcondition)
+    return value == null && unknown;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "#1")
+  boolean testParam(final @Nullable Object param) {
+    return param != null;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "#1")
+  boolean testLitTTgood1(final @Nullable Object param) {
+    if (param == null) {
+      return false;
+    }
+    return true;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "#1")
+  boolean testLitTTbad1(final @Nullable Object param) {
+    // :: error: (contracts.conditional.postcondition)
+    return true;
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "#1")
+  boolean testLitFFgood1(final @Nullable Object param) {
+    return true;
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "#1")
+  boolean testLitFFgood2(final @Nullable Object param) {
+    if (param == null) {
+      return true;
+    }
+    return false;
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "#1")
+  boolean testLitFFbad1(final @Nullable Object param) {
+    if (param == null) {
+      // :: error: (contracts.conditional.postcondition)
+      return false;
+    }
+    return true;
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "#1")
+  boolean testLitFFbad2(final @Nullable Object param) {
+    // :: error: (contracts.conditional.postcondition)
+    return false;
+  }
+
+  @Nullable Object getValueUnpure() {
+    return value;
+  }
+
+  @org.checkerframework.dataflow.qual.Pure
+  @Nullable Object getValuePure() {
+    return value;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "getValuePure()")
+  public boolean hasValuePure() {
+    return getValuePure() != null;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "#1")
+  public static final boolean isComment(@Nullable String s) {
+    return s != null && (s.startsWith("//") || s.startsWith("#"));
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "#1")
+  public boolean myEquals(@Nullable Object o) {
+    return (o instanceof String) && equals((String) o);
+  }
+
+  /*
+   * The next two methods are from Daikon's class Quant. They verify that
+   * EnsuresNonNullIf is correctly added to the assumptions after a check.
+   */
+
+  @EnsuresNonNullIf(
+      result = true,
+      expression = {"#1", "#2"})
+  /* pure */ public static boolean sameLength(
+      boolean @Nullable [] seq1, boolean @Nullable [] seq2) {
+    return ((seq1 != null) && (seq2 != null) && seq1.length == seq2.length);
+  }
+
+  /* pure */ public static boolean isReverse(boolean @Nullable [] seq1, boolean @Nullable [] seq2) {
+    if (!sameLength(seq1, seq2)) {
+      return false;
+    }
+    // This assert is not needed for inference.
+    // assert seq1 != null && seq2 != null; // because sameLength() = true
+
+    int length = seq1.length;
+    for (int i = 0; i < length; i++) {
+      if (seq1[i] != seq2[length - i - 1]) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/checker/tests/nullness/AssertIfClient.java b/checker/tests/nullness/AssertIfClient.java
new file mode 100644
index 0000000..90b4a07
--- /dev/null
+++ b/checker/tests/nullness/AssertIfClient.java
@@ -0,0 +1,59 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.dataflow.qual.*;
+
+public class AssertIfClient {
+
+  @RequiresNonNull("#1.rpcResponse()")
+  void rpcResponseNonNull(Proxy proxy) {
+    @NonNull Object response = proxy.rpcResponse();
+  }
+
+  void rpcResponseNullable(Proxy proxy) {
+    @Nullable Object response = proxy.rpcResponse();
+  }
+
+  void rpcResponseTypestate() {
+    Proxy proxy = new Proxy();
+    // :: error: (assignment)
+    @NonNull Object response1 = proxy.rpcResponse();
+    // :: error: (contracts.precondition)
+    rpcResponseNonNull(proxy);
+    rpcResponseNullable(proxy);
+
+    proxy.issueRpc();
+    @NonNull Object response2 = proxy.rpcResponse();
+    @NonNull Object response3 = proxy.rpcResponse();
+    rpcResponseNonNull(proxy);
+    rpcResponseNullable(proxy);
+  }
+}
+
+class Proxy {
+
+  // the RPC response, or null if not yet received
+  @MonotonicNonNull Object response = null;
+
+  @SuppressWarnings("contracts.postcondition")
+  @EnsuresNonNull({"response", "rpcResponse()"})
+  void issueRpc() {
+    response = new Object();
+  }
+
+  // If this method returns true,
+  // then response is non-null and rpcResponse() returns non-null
+  @SuppressWarnings("contracts.conditional.postcondition")
+  @EnsuresNonNullIf(
+      expression = {"response", "rpcResponse()"},
+      result = true)
+  boolean rpcResponseReceived() {
+    return response != null;
+  }
+
+  // Returns non-null if the response has been received, null otherwise; but an
+  // @AssertNonNullIfNonNull annotation would states the converse, that if the result is non-null
+  // then the response hs been received.  See rpcResponseReceived.
+  @Pure
+  @Nullable Object rpcResponse() {
+    return response;
+  }
+}
diff --git a/checker/tests/nullness/AssertIfFalseTest.java b/checker/tests/nullness/AssertIfFalseTest.java
new file mode 100644
index 0000000..49f0135
--- /dev/null
+++ b/checker/tests/nullness/AssertIfFalseTest.java
@@ -0,0 +1,54 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+public class AssertIfFalseTest {
+
+  @org.checkerframework.dataflow.qual.Pure
+  @Nullable Object get() {
+    return "m";
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "get()")
+  boolean isGettable() {
+    // don't bother with the implementation
+    // :: error: (contracts.conditional.postcondition)
+    return false;
+  }
+
+  void simple() {
+    // :: error: (dereference.of.nullable)
+    get().toString();
+  }
+
+  void checkWrongly() {
+    if (isGettable()) {
+      // :: error: (dereference.of.nullable)
+      get().toString();
+    }
+  }
+
+  void checkCorrectly() {
+    if (!isGettable()) {
+      get().toString();
+    }
+  }
+
+  /** Returns whether or not constant_value is a legal constant. */
+  @EnsuresNonNullIf(result = false, expression = "#1")
+  static boolean legalConstant(final @Nullable Object constant_value) {
+    if ((constant_value == null)
+        || ((constant_value instanceof Long) || (constant_value instanceof Double))) return true;
+    return false;
+  }
+
+  void useLegalConstant1(@Nullable Object static_constant_value) {
+    if (!legalConstant(static_constant_value)) {
+      throw new AssertionError("unexpected constant class " + static_constant_value.getClass());
+    }
+  }
+
+  void useLegalConstant2(@Nullable Object static_constant_value) {
+    assert legalConstant(static_constant_value)
+        : "unexpected constant class " + static_constant_value.getClass();
+  }
+}
diff --git a/checker/tests/nullness/AssertIfFalseTest2.java b/checker/tests/nullness/AssertIfFalseTest2.java
new file mode 100644
index 0000000..da9f98b
--- /dev/null
+++ b/checker/tests/nullness/AssertIfFalseTest2.java
@@ -0,0 +1,30 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class AssertIfFalseTest2 {
+
+  public static void usePriorityQueue(PriorityQueue1<@NonNull Object> active) {
+    while (!(active.isEmpty())) {
+      @NonNull Object queueMinPathNode = active.peek();
+    }
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Classes copied from the annotated JDK
+  ///
+
+  public class PriorityQueue1<E extends @NonNull Object> {
+    @EnsuresNonNullIf(
+        result = false,
+        expression = {"peek()"})
+    @Pure
+    public boolean isEmpty() {
+      return true;
+    }
+
+    @Pure
+    public @Nullable E peek() {
+      return null;
+    }
+  }
+}
diff --git a/checker/tests/nullness/AssertIfNonNullTest.java b/checker/tests/nullness/AssertIfNonNullTest.java
new file mode 100644
index 0000000..020748f
--- /dev/null
+++ b/checker/tests/nullness/AssertIfNonNullTest.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class AssertIfNonNullTest {
+
+  Long id;
+
+  public AssertIfNonNullTest(Long id) {
+    this.id = id;
+  }
+
+  @AssertNonNullIfNonNull("id")
+  @Pure
+  public @Nullable Long getId() {
+    return id;
+  }
+}
diff --git a/checker/tests/nullness/AssertInStatic.java b/checker/tests/nullness/AssertInStatic.java
new file mode 100644
index 0000000..0996285
--- /dev/null
+++ b/checker/tests/nullness/AssertInStatic.java
@@ -0,0 +1,11 @@
+public class AssertInStatic {
+
+  static {
+    long x = 0;
+    try {
+      x = 0;
+    } catch (Throwable e) {
+      assert true;
+    }
+  }
+}
diff --git a/checker/tests/nullness/AssertNonNullIfNonNullTest.java b/checker/tests/nullness/AssertNonNullIfNonNullTest.java
new file mode 100644
index 0000000..fc049c2
--- /dev/null
+++ b/checker/tests/nullness/AssertNonNullIfNonNullTest.java
@@ -0,0 +1,46 @@
+import org.checkerframework.checker.nullness.qual.AssertNonNullIfNonNull;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+
+// @skip-test Re-enable when @AssertNonNullIfNonNull checking is enhanced
+
+public class AssertNonNullIfNonNullTest {
+
+  private @Nullable String value;
+
+  @Pure
+  @AssertNonNullIfNonNull("value")
+  public @Nullable String getValue() {
+    return value;
+  }
+
+  public void setValue(String value) {
+    this.value = value;
+  }
+
+  @EnsuresNonNullIf(expression = "value", result = true)
+  public boolean isValueNonNull1() {
+    return value != null;
+  }
+
+  @EnsuresNonNullIf(expression = "getValue()", result = true)
+  public boolean isValueNonNull2() {
+    // The @AssertNonNullIfNonNull annotation implies that if getValue() is
+    // non-null, then is non-null, then value is non-null, but not the
+    // converse, so an error should be issued here.
+    // :: error: (contracts.conditional.postcondition)
+    return value != null;
+  }
+
+  // The @AssertNonNullIfNonNull annotation should enable suppressing this error.
+  @EnsuresNonNullIf(expression = "value", result = true)
+  public boolean isValueNonNull3() {
+    return getValue() != null;
+  }
+
+  @EnsuresNonNullIf(expression = "getValue()", result = true)
+  public boolean isValueNonNull4() {
+    return getValue() != null;
+  }
+}
diff --git a/checker/tests/nullness/AssertNonNullTest.java b/checker/tests/nullness/AssertNonNullTest.java
new file mode 100644
index 0000000..4d643d6
--- /dev/null
+++ b/checker/tests/nullness/AssertNonNullTest.java
@@ -0,0 +1,20 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+
+public class AssertNonNullTest {
+  public @Nullable String s;
+
+  // :: error: (contracts.postcondition)
+  public @EnsuresNonNull("s") void makeNN() {
+    s = null;
+  }
+
+  public static void main(String[] args) {
+    AssertNonNullTest a = new AssertNonNullTest();
+    // :: error: (dereference.of.nullable)
+    a.s.equals("we");
+    AssertNonNullTest b = new AssertNonNullTest();
+    b.makeNN();
+    b.s.equals("we");
+  }
+}
diff --git a/checker/tests/nullness/AssertNullable.java b/checker/tests/nullness/AssertNullable.java
new file mode 100644
index 0000000..814538f
--- /dev/null
+++ b/checker/tests/nullness/AssertNullable.java
@@ -0,0 +1,23 @@
+public class AssertNullable {
+  public static void main(String[] args) {
+    if (args.length >= 1) {
+      Boolean b = null;
+      // This will result in an NPE, not an AssertionError:
+      // Exception in thread "main" java.lang.NullPointerException
+      // Therefore, the Nullness Checker warns about this.
+      // :: error: (condition.nullable)
+      assert b;
+    } else {
+      String s = null;
+      // This is OK, the message will look like:
+      // Exception in thread "main" java.lang.AssertionError: null
+      assert 4 < 3 : s;
+    }
+  }
+
+  void foo() {
+    String s = 3 > 2 ? null : "ba";
+    // :: error: (dereference.of.nullable)
+    assert s.hashCode() > 4;
+  }
+}
diff --git a/checker/tests/nullness/AssertParameterNullness.java b/checker/tests/nullness/AssertParameterNullness.java
new file mode 100644
index 0000000..8b9e1e1
--- /dev/null
+++ b/checker/tests/nullness/AssertParameterNullness.java
@@ -0,0 +1,31 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+public class AssertParameterNullness {
+
+  /** True iff both sequences are non-null and have the same length. */
+  @EnsuresNonNullIf(
+      result = true,
+      expression = {"#1", "#2"})
+  /* pure */ public static boolean sameLength(
+      final boolean @Nullable [] seq1, final boolean @Nullable [] seq2) {
+    if ((seq1 != null) && (seq2 != null) && seq1.length == seq2.length) {
+      return true;
+    }
+    return false;
+  }
+
+  /* pure */ public static boolean pairwiseEqual(
+      boolean @Nullable [] seq3, boolean @Nullable [] seq4) {
+    if (sameLength(seq3, seq4)) {
+      boolean b1 = seq3[0];
+      boolean b2 = seq4[0];
+    } else {
+      // :: error: (accessing.nullable)
+      boolean b1 = seq3[0];
+      // :: error: (accessing.nullable)
+      boolean b2 = seq4[0];
+    }
+    return true;
+  }
+}
diff --git a/checker/tests/nullness/AssertTwice.java b/checker/tests/nullness/AssertTwice.java
new file mode 100644
index 0000000..41e45d5
--- /dev/null
+++ b/checker/tests/nullness/AssertTwice.java
@@ -0,0 +1,31 @@
+public class AssertTwice {
+
+  private void assertOnce() {
+    String methodDeclaration = null;
+    assert methodDeclaration != null;
+    methodDeclaration = null;
+  }
+
+  private void assertTwice() {
+    String methodDeclaration = null;
+    assert methodDeclaration != null;
+    assert methodDeclaration != null;
+    methodDeclaration = null;
+  }
+
+  private void assertTwiceWithUse() {
+    String methodDeclaration = null;
+    assert methodDeclaration != null : "@AssumeAssertion(nullness)";
+    methodDeclaration.toString();
+    // :: warning: (nulltest.redundant)
+    assert methodDeclaration != null;
+    methodDeclaration = null;
+  }
+
+  public static @org.checkerframework.checker.nullness.qual.Nullable Object n = "m";
+
+  private void twiceWithChecks() {
+    assert n != null;
+    n = null;
+  }
+}
diff --git a/checker/tests/nullness/AssertWithStatic.java b/checker/tests/nullness/AssertWithStatic.java
new file mode 100644
index 0000000..a3c802f
--- /dev/null
+++ b/checker/tests/nullness/AssertWithStatic.java
@@ -0,0 +1,56 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+public class AssertWithStatic {
+
+  static @Nullable String f;
+
+  @EnsuresNonNullIf(result = true, expression = "AssertWithStatic.f")
+  public boolean hasSysOut1() {
+    return AssertWithStatic.f != null;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "f")
+  public boolean hasSysOut2() {
+    return AssertWithStatic.f != null;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "AssertWithStatic.f")
+  public boolean hasSysOut3() {
+    return f != null;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "f")
+  public boolean hasSysOut4() {
+    return f != null;
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "AssertWithStatic.f")
+  public boolean noSysOut1() {
+    return AssertWithStatic.f == null;
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "f")
+  public boolean noSysOut2() {
+    return AssertWithStatic.f == null;
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "AssertWithStatic.f")
+  public boolean noSysOut3() {
+    return f == null;
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "f")
+  public boolean noSysOut4() {
+    return f == null;
+  }
+
+  @EnsuresNonNull("AssertWithStatic.f")
+  // :: error: (contracts.postcondition)
+  public void sysOutAfter1() {}
+
+  @EnsuresNonNull("f")
+  // :: error: (contracts.postcondition)
+  public void sysOutAfter2() {}
+}
diff --git a/checker/tests/nullness/Asserts.java b/checker/tests/nullness/Asserts.java
new file mode 100644
index 0000000..35e5ea6
--- /dev/null
+++ b/checker/tests/nullness/Asserts.java
@@ -0,0 +1,78 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+public class Asserts {
+
+  void propogateToExpr() {
+    String s = "m";
+    assert false : s.getClass();
+  }
+
+  void incorrectAssertExpr() {
+    String s = null;
+    assert s != null : "@AssumeAssertion(nullness)"; // error
+    s.getClass(); // OK
+  }
+
+  void correctAssertExpr() {
+    String s = null;
+    assert s == null : "@AssumeAssertion(nullness)";
+    // :: error: (dereference.of.nullable)
+    s.getClass(); // error
+  }
+
+  class ArrayCell {
+    @Nullable Object[] vals = new @Nullable Object[0];
+  }
+
+  void assertComplexExpr(ArrayCell ac, int i) {
+    assert ac.vals[i] != null : "@AssumeAssertion(nullness)";
+    @NonNull Object o = ac.vals[i];
+    i = 10;
+    // :: error: (assignment)
+    @NonNull Object o2 = ac.vals[i];
+  }
+
+  boolean pairwiseEqual(boolean @Nullable [] seq1, boolean @Nullable [] seq2) {
+    if (!sameLength(seq1, seq2)) {
+      return false;
+    }
+    if (ne(seq1[0], seq2[0])) {}
+    return true;
+  }
+
+  @EnsuresNonNullIf(
+      result = true,
+      expression = {"#1", "#2"})
+  boolean sameLength(final boolean @Nullable [] seq1, final boolean @Nullable [] seq2) {
+    // don't bother with the implementation
+    // :: error: (contracts.conditional.postcondition)
+    return true;
+  }
+
+  static boolean ne(boolean a, boolean b) {
+    return true;
+  }
+
+  void testAssertBad(boolean @Nullable [] seq1, boolean @Nullable [] seq2) {
+    assert sameLength(seq1, seq2);
+    // the @EnsuresNonNullIf is not taken from the assert, as it doesn't contain "nullness"
+    // :: error: (accessing.nullable)
+    if (seq1[0]) {}
+  }
+
+  void testAssertGood(boolean @Nullable [] seq1, boolean @Nullable [] seq2) {
+    assert sameLength(seq1, seq2) : "@AssumeAssertion(nullness)";
+    // The explanation contains "nullness" and we therefore take the additional assumption
+    if (seq1[0]) {}
+  }
+
+  void testAssertAnd(@Nullable Object o) {
+    assert o != null && o.hashCode() > 6;
+  }
+
+  void testAssertOr(@Nullable Object o) {
+    // :: error: (dereference.of.nullable)
+    assert o != null || o.hashCode() > 6;
+  }
+}
diff --git a/checker/tests/nullness/AssignmentDuringInitialization.java b/checker/tests/nullness/AssignmentDuringInitialization.java
new file mode 100644
index 0000000..61f4788
--- /dev/null
+++ b/checker/tests/nullness/AssignmentDuringInitialization.java
@@ -0,0 +1,42 @@
+// This test covers Issue345 at:
+// https://github.com/typetools/checker-framework/issues/345
+public class AssignmentDuringInitialization {
+  String f1;
+  String f2;
+
+  String f3;
+  String f4;
+
+  String f5;
+  String f6;
+
+  {
+    // :: error:  (assignment)
+    f1 = f2;
+    f2 = f1;
+    f2.toString(); // Null pointer exception here
+  }
+
+  public AssignmentDuringInitialization() {
+    // :: error:  (assignment)
+    f3 = f4;
+    f4 = f3;
+    f4.toString(); // Null pointer exception here
+
+    f5 = "hello";
+    f6 = f5;
+  }
+
+  public void goodBehavior() {
+    // This isn't a constructor or initializer.
+    // The receiver of this method should already be initialized
+    // and therefore f1 and f2 should already be initialized.
+    f5 = f6;
+    f6 = f5;
+    f6.toString(); // No exception here
+  }
+
+  public static void main(String[] args) {
+    AssignmentDuringInitialization a = new AssignmentDuringInitialization();
+  }
+}
diff --git a/checker/tests/nullness/BinaryOp.java b/checker/tests/nullness/BinaryOp.java
new file mode 100644
index 0000000..5ee9d3d
--- /dev/null
+++ b/checker/tests/nullness/BinaryOp.java
@@ -0,0 +1,8 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class BinaryOp {
+  void test(@UnknownInitialization Object obj) {
+    throw new Error("" + obj);
+  }
+}
diff --git a/checker/tests/nullness/BinarySearch.java b/checker/tests/nullness/BinarySearch.java
new file mode 100644
index 0000000..aa38254
--- /dev/null
+++ b/checker/tests/nullness/BinarySearch.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// Allow searching through nullable arrays components
+// and for nullable keys.
+public class BinarySearch {
+  @Nullable Object @NonNull [] arr = {"a", "b", null};
+
+  void search(@Nullable Object key) {
+    int res = java.util.Arrays.binarySearch(arr, key);
+    res = java.util.Arrays.binarySearch(arr, 0, 4, key);
+  }
+}
diff --git a/checker/tests/nullness/BoxingNullness.java b/checker/tests/nullness/BoxingNullness.java
new file mode 100644
index 0000000..112aec2
--- /dev/null
+++ b/checker/tests/nullness/BoxingNullness.java
@@ -0,0 +1,145 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class BoxingNullness {
+  void withinOperation() {
+    Integer i1 = 3;
+    int i1u = i1 + 2; // valid
+    Integer i2 = null;
+    // :: error: (unboxing.of.nullable)
+    int i2u = i2 + 2; // invalid
+    Integer i3 = i1;
+    i3.toString();
+  }
+
+  void withinAssignment() {
+    Integer i1 = 5;
+    int i1u = i1;
+    Integer i2 = null;
+    // :: error: (unboxing.of.nullable)
+    int i2u = i2; // invalid
+  }
+
+  void validWithinUnary() {
+    // within blocks to stop flow
+    Integer i1 = 1, i2 = 1, i3 = 1, i4 = 1;
+    ++i1;
+    i2++;
+  }
+
+  void invalidWithinUnary() {
+    // within blocks to stop flow
+    Integer i1 = null, i2 = null, i3 = null, i4 = null;
+    // :: error: (unboxing.of.nullable)
+    ++i1; // invalid
+    // :: error: (unboxing.of.nullable)
+    i2++; // invalid
+  }
+
+  void validCompoundAssignmentsAsVariable() {
+    @NonNull Integer i = 0; // nonnull is needed because flow is buggy
+    i += 1;
+    i -= 1;
+    @NonNull Boolean b = true;
+    b &= true;
+  }
+
+  void invalidCompoundAssignmentsAsVariable() {
+    Integer i = null;
+    // :: error: (unboxing.of.nullable)
+    i += 1; // invalid
+    Boolean b = null;
+    // :: error: (unboxing.of.nullable)
+    b &= true; // invalid
+  }
+
+  void invalidCompoundAssignmentAsValue() {
+    @NonNull Integer var = 3;
+    Integer val = null;
+    // :: error: (unboxing.of.nullable)
+    var += val;
+    Boolean b1 = null;
+    boolean b2 = true;
+    // :: error: (unboxing.of.nullable)
+    b2 &= b1; // invalid
+  }
+
+  void randomValidStringOperations() {
+    String s = null;
+    s += null;
+  }
+
+  void equalityTest() {
+    Integer bN = null;
+    Integer bN1 = null;
+    Integer bN2 = null;
+    Integer bN3 = null;
+    Integer bN4 = null;
+    Integer bN5 = null;
+    Integer bN6 = null;
+    Integer bN7 = null;
+    Integer b1 = 1;
+    int u1 = 1;
+    System.out.println(bN == bN1); // valid
+    System.out.println(bN2 == b1); // valid
+    System.out.println(bN3 != bN4); // valid
+    System.out.println(bN5 != b1); // valid
+
+    System.out.println(u1 == b1);
+    System.out.println(u1 != b1);
+    System.out.println(u1 == u1);
+    System.out.println(u1 != u1);
+
+    // :: error: (unboxing.of.nullable)
+    System.out.println(bN6 == u1); // invalid
+    // :: error: (unboxing.of.nullable)
+    System.out.println(bN7 != u1); // invalid
+  }
+
+  void addition() {
+    Integer bN = null;
+    Integer bN1 = null;
+    Integer bN2 = null;
+    Integer bN3 = null;
+    Integer b1 = 1;
+    int u1 = 1;
+    // :: error: (unboxing.of.nullable)
+    System.out.println(bN + bN1); // invalid
+    // :: error: (unboxing.of.nullable)
+    System.out.println(bN2 + b1); // invalid
+
+    System.out.println(u1 + b1);
+    System.out.println(u1 + u1);
+
+    // :: error: (unboxing.of.nullable)
+    System.out.println(bN3 + u1); // invalid
+  }
+
+  void visitCast() {
+    Integer bN = null;
+    Integer bN2 = null;
+    Integer b1 = 1;
+    int u1 = 1;
+
+    println(bN);
+    // :: error: (unboxing.of.nullable)
+    println((int) bN2); // invalid
+
+    println(b1);
+    println((int) b1);
+
+    println(u1);
+    println((int) u1);
+  }
+
+  void println(@Nullable Object o) {}
+
+  void testObjectString() {
+    Object o = null;
+    o += "m";
+  }
+
+  void testCharString() {
+    CharSequence cs = null;
+    cs += "m";
+  }
+}
diff --git a/checker/tests/nullness/Bug102.java b/checker/tests/nullness/Bug102.java
new file mode 100644
index 0000000..b360260
--- /dev/null
+++ b/checker/tests/nullness/Bug102.java
@@ -0,0 +1,19 @@
+// Test case for Issue 102
+public final class Bug102 {
+  class C<T extends @org.checkerframework.checker.nullness.qual.Nullable Object> {}
+
+  void bug1() {
+    C<String> c = new C<>();
+    m(c);
+    m(c); // note: the bug disapear if calling m only once
+  }
+
+  void bug2() {
+    C<String> c = new C<>();
+    m(c);
+  }
+
+  // :: error: (invalid.polymorphic.qualifier)
+  <@org.checkerframework.checker.nullness.qual.PolyNull S> void m(
+      final C<@org.checkerframework.checker.nullness.qual.PolyNull String> a) {}
+}
diff --git a/checker/tests/nullness/Bug103.java b/checker/tests/nullness/Bug103.java
new file mode 100644
index 0000000..f6d251c
--- /dev/null
+++ b/checker/tests/nullness/Bug103.java
@@ -0,0 +1,87 @@
+// Test case for Issue 103
+
+import java.util.ArrayList;
+import java.util.List;
+
+class CC {}
+
+class HR {}
+
+// Crazy: remove the "extends HR" and it compiles
+public class Bug103 extends HR {
+
+  // Crazy: add a 23th element as for example "hello" and it compiles
+  // Crazy: replace IG.C with IG.C+"" and it compiles
+  // Crazy: remove final and it compiles
+  // Crazy: replace with new String[22] and it compiles
+  // Crazy: reduce to less than 5 distinct values and it compiles  (replace IG.D with IG.C)
+  final String[] ids = {
+    IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C,
+    IG.C, IG.C, IG.D, IG.E, IG.F, IG.G
+  };
+
+  // Crazy: remove arg u and it compiles
+  // Crazy: remove any line of m1 and it compiles
+  // Crazy: replace two o args by null and it compiles
+  void m1(CC o, Object u) {
+    String cc = m2(o);
+    String dd = m2(o);
+  }
+
+  String m2(final CC c) {
+    return "a";
+  }
+
+  // Crazy: remove ids.length and it compiles
+  // replace return type List with ArrayList and it compiles
+  List<CC> m3(CC c) {
+    ArrayList<CC> lc = new ArrayList<>(ids.length);
+    return lc;
+  }
+
+  // Crazy: comment out the whole unused LV class and it compiles
+  // Crazy: comment one of the following 4 lines out and it compiles
+  static class LV {
+    static String a = "a";
+    static String b = "a";
+    static String c = "a";
+    static String d = "a";
+  }
+
+  class IG {
+    // Crazy: comment one of the following 8 lines out and it compiles
+    String C1 = "1";
+    String C2 = "1";
+    String C3 = "1";
+    String C4 = "1";
+    String C5 = "1";
+    String C6 = "1";
+    String C7 = "1";
+    String C8 = "1";
+
+    static final String C = "c";
+    static final String D = C;
+    static final String E = C;
+    static final String F = C;
+
+    // Crazy: comment one of the following 18 lines out and it compiles
+    static final String G = C;
+    static final String H = C;
+    static final String I = C;
+    static final String J = C;
+    static final String K = C;
+    static final String L = C;
+    static final String M = C;
+    static final String N = C;
+    static final String O = C;
+    static final String P = C;
+    static final String Q = C;
+    static final String R = C;
+    static final String S = C;
+    static final String T = C;
+    static final String U = C;
+    static final String V = C;
+    static final String W = C;
+    static final String X = C;
+  }
+}
diff --git a/checker/tests/nullness/CallSuper.java b/checker/tests/nullness/CallSuper.java
new file mode 100644
index 0000000..988be63
--- /dev/null
+++ b/checker/tests/nullness/CallSuper.java
@@ -0,0 +1,15 @@
+import java.io.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+class MyFilterInputStream {
+  MyFilterInputStream(InputStream in) {}
+}
+
+public class CallSuper extends MyFilterInputStream {
+  CallSuper(@Nullable InputStream in) {
+    // The MyFilterInputStream constructor takes a NonNull argument
+    // (but that's not true of FilterInputStream itself).
+    // :: error: (argument)
+    super(in);
+  }
+}
diff --git a/checker/tests/nullness/CastTypeVariable.java b/checker/tests/nullness/CastTypeVariable.java
new file mode 100644
index 0000000..367af70
--- /dev/null
+++ b/checker/tests/nullness/CastTypeVariable.java
@@ -0,0 +1,17 @@
+import java.util.Map;
+
+class MyAnnotatedTypeMirror {
+  void addAnnotations() {}
+}
+
+class MyAnnotatedTypeVariable extends MyAnnotatedTypeMirror {}
+
+public class CastTypeVariable {
+  public static <K extends MyAnnotatedTypeMirror, V extends MyAnnotatedTypeMirror> V mapGetHelper(
+      Map<K, V> mappings, MyAnnotatedTypeVariable key) {
+    V possValue = (V) mappings.get(key);
+    // :: error: (dereference.of.nullable)
+    possValue.addAnnotations();
+    return possValue;
+  }
+}
diff --git a/checker/tests/nullness/CastsNullness.java b/checker/tests/nullness/CastsNullness.java
new file mode 100644
index 0000000..feeba2b
--- /dev/null
+++ b/checker/tests/nullness/CastsNullness.java
@@ -0,0 +1,102 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class CastsNullness {
+
+  void test(String nonNullParam) {
+    Object lc1 = (Object) nonNullParam;
+    lc1.toString();
+
+    String nullable = null;
+    Object lc2 = (Object) nullable;
+    // :: error: (dereference.of.nullable)
+    lc2.toString(); // error
+  }
+
+  void testBoxing() {
+    Integer b = null;
+    // :: error: (unboxing.of.nullable)
+    int i = b;
+    // no error, because there was already a nullpointer exception
+    Object o = (int) b;
+  }
+
+  void testUnsafeCast(@Nullable Object x) {
+    // :: warning: (cast.unsafe)
+    @NonNull Object y = (@NonNull Object) x;
+    y.toString();
+  }
+
+  void testUnsafeCastArray1(@Nullable Object[] x) {
+    // Warning only with -AcheckCastElementType.
+    // TODO:: warning: (cast.unsafe)
+    @NonNull Object[] y = (@NonNull Object[]) x;
+    y[0].toString();
+  }
+
+  void testUnsafeCastArray2(@NonNull Object x) {
+    // We don't know about the component type of x -> warn
+    // Warning only with -AcheckCastElementType.
+    // TODO:: warning: (cast.unsafe)
+    @NonNull Object[] y = (@NonNull Object[]) x;
+    y[0].toString();
+  }
+
+  void testUnsafeCastList1(java.util.ArrayList<@Nullable Object> x) {
+    // Warning only with -AcheckCastElementType.
+    // TODO:: warning: (cast.unsafe)
+    java.util.List<@NonNull Object> y = (java.util.List<@NonNull Object>) x;
+    y.get(0).toString();
+    // TODO:: warning: (cast.unsafe)
+    java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x;
+    java.util.List<@Nullable Object> y3 = (java.util.List<@Nullable Object>) x;
+  }
+
+  void testUnsafeCastList2(java.util.List<@Nullable Object> x) {
+    java.util.List<@Nullable Object> y = (java.util.ArrayList<@Nullable Object>) x;
+    // Warning only with -AcheckCastElementType.
+    // TODO:: warning: (cast.unsafe)
+    java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x;
+  }
+
+  void testUnsafeCastList3(@NonNull Object x) {
+    // Warning only with -AcheckCastElementType.
+    // TODO:: warning: (cast.unsafe)
+    // :: warning: [unchecked] unchecked cast
+    java.util.List<@Nullable Object> y = (java.util.List<@Nullable Object>) x;
+    // TODO:: warning: (cast.unsafe)
+    // :: warning: [unchecked] unchecked cast
+    java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x;
+  }
+
+  void testSuppression(@Nullable Object x) {
+    // :: error: (assignment)
+    @NonNull String s1 = (String) x;
+    @SuppressWarnings("nullness")
+    @NonNull String s2 = (String) x;
+  }
+
+  class Generics<T extends Object> {
+    T t;
+    @Nullable T nt;
+
+    Generics(T t) {
+      this.t = t;
+      this.nt = t;
+    }
+
+    void m() {
+      // :: error: (assignment)
+      t = (@Nullable T) null;
+      nt = (@Nullable T) null;
+      // :: warning: (cast.unsafe)
+      t = (T) null;
+      // :: warning: (cast.unsafe)
+      nt = (T) null;
+    }
+  }
+
+  void testSafeCasts() {
+    // :: error: (nullness.on.primitive)
+    Integer x = (@Nullable int) 1;
+  }
+}
diff --git a/checker/tests/nullness/ChainAssignment.java b/checker/tests/nullness/ChainAssignment.java
new file mode 100644
index 0000000..b826cc1
--- /dev/null
+++ b/checker/tests/nullness/ChainAssignment.java
@@ -0,0 +1,44 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class ChainAssignment {
+  @Nullable Object a;
+  @Nullable Object b;
+  Object x = new Object();
+  Object y = new Object();
+
+  void m1() {
+    a = b = new Object();
+  }
+
+  void m2() {
+    this.a = this.b = new Object();
+  }
+
+  void m3() {
+    a = this.b = new Object();
+  }
+
+  void m4() {
+    this.a = b = new Object();
+  }
+
+  void n1() {
+    // :: error: (assignment)
+    x = y = null;
+  }
+
+  void n2() {
+    // :: error: (assignment)
+    this.x = this.y = null;
+  }
+
+  void n3() {
+    // :: error: (assignment)
+    x = this.y = null;
+  }
+
+  void n4() {
+    // :: error: (assignment)
+    this.x = y = null;
+  }
+}
diff --git a/checker/tests/nullness/ChicoryPremain.java b/checker/tests/nullness/ChicoryPremain.java
new file mode 100644
index 0000000..98110b7
--- /dev/null
+++ b/checker/tests/nullness/ChicoryPremain.java
@@ -0,0 +1,13 @@
+package daikon.chicory;
+
+public class ChicoryPremain {
+  public static void premain(ClassLoader loader) {
+    Object transformer = null;
+    try {
+      transformer = loader.loadClass("Foo").getDeclaredConstructor().newInstance();
+      transformer.getClass();
+    } catch (Exception e1) {
+      throw new RuntimeException("Exception", e1);
+    }
+  }
+}
diff --git a/checker/tests/nullness/ClassGetCanonicalName.java b/checker/tests/nullness/ClassGetCanonicalName.java
new file mode 100644
index 0000000..0c1779f
--- /dev/null
+++ b/checker/tests/nullness/ClassGetCanonicalName.java
@@ -0,0 +1,5 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class ClassGetCanonicalName {
+  @NonNull String s = ClassGetCanonicalName.class.getCanonicalName();
+}
diff --git a/checker/tests/nullness/CompoundAssign.java b/checker/tests/nullness/CompoundAssign.java
new file mode 100644
index 0000000..ed7c532
--- /dev/null
+++ b/checker/tests/nullness/CompoundAssign.java
@@ -0,0 +1,13 @@
+public class CompoundAssign {
+  void m(String args) {
+    String arg = "";
+    for (int ii = 0; ii < args.length(); ii++) {
+      if ('x' == 'y') {
+        arg += 'x';
+      } else {
+        arg = "";
+      }
+    }
+    if (arg.equals("")) {}
+  }
+}
diff --git a/checker/tests/nullness/ConditionalNullness.java b/checker/tests/nullness/ConditionalNullness.java
new file mode 100644
index 0000000..9c8982a
--- /dev/null
+++ b/checker/tests/nullness/ConditionalNullness.java
@@ -0,0 +1,96 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+public class ConditionalNullness {
+
+  @EnsuresNonNullIf(
+      expression = {"field", "method()"},
+      result = true)
+  boolean checkNonNull() {
+    // don't bother with the implementation
+    // :: error: (contracts.conditional.postcondition)
+    return true;
+  }
+
+  @Nullable Object field = null;
+
+  @org.checkerframework.dataflow.qual.Pure
+  @Nullable Object method() {
+    return "m";
+  }
+
+  void testSelfWithCheck() {
+    ConditionalNullness other = new ConditionalNullness();
+    if (checkNonNull()) {
+      field.toString();
+      method().toString();
+      // :: error: (dereference.of.nullable)
+      other.field.toString(); // error
+      // :: error: (dereference.of.nullable)
+      other.method().toString(); // error
+    }
+    // :: error: (dereference.of.nullable)
+    method().toString(); // error
+  }
+
+  void testSelfWithoutCheck() {
+    // :: error: (dereference.of.nullable)
+    field.toString(); // error
+    // :: error: (dereference.of.nullable)
+    method().toString(); // error
+  }
+
+  void testSelfWithCheckNegation() {
+    if (checkNonNull()) {
+      // nothing to do
+    } else {
+      // :: error: (dereference.of.nullable)
+      field.toString(); // error
+    }
+    field.toString(); // error
+  }
+
+  void testOtherWithCheck() {
+    ConditionalNullness other = new ConditionalNullness();
+    if (other.checkNonNull()) {
+      other.field.toString();
+      other.method().toString();
+      // :: error: (dereference.of.nullable)
+      field.toString(); // error
+      // :: error: (dereference.of.nullable)
+      method().toString(); // error
+    }
+    // :: error: (dereference.of.nullable)
+    other.method().toString(); // error
+    // :: error: (dereference.of.nullable)
+    method().toString(); // error
+  }
+
+  void testOtherWithoutCheck() {
+    ConditionalNullness other = new ConditionalNullness();
+    // :: error: (dereference.of.nullable)
+    other.field.toString(); // error
+    // :: error: (dereference.of.nullable)
+    other.method().toString(); // error
+    // :: error: (dereference.of.nullable)
+    field.toString(); // error
+    // :: error: (dereference.of.nullable)
+    method().toString(); // error
+  }
+
+  void testOtherWithCheckNegation() {
+    ConditionalNullness other = new ConditionalNullness();
+    if (other.checkNonNull()) {
+      // nothing to do
+    } else {
+      // :: error: (dereference.of.nullable)
+      other.field.toString(); // error
+      // :: error: (dereference.of.nullable)
+      other.method().toString(); // error
+      // :: error: (dereference.of.nullable)
+      field.toString(); // error
+    }
+    // :: error: (dereference.of.nullable)
+    field.toString(); // error
+  }
+}
diff --git a/checker/tests/nullness/ConditionalOr.java b/checker/tests/nullness/ConditionalOr.java
new file mode 100644
index 0000000..a4b66f2
--- /dev/null
+++ b/checker/tests/nullness/ConditionalOr.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ConditionalOr {
+
+  void test(@Nullable Object o) {
+    if (o == null || o.toString() == "...") {
+      // ...
+    }
+  }
+}
diff --git a/checker/tests/nullness/Conditions.java b/checker/tests/nullness/Conditions.java
new file mode 100644
index 0000000..fb7b52a
--- /dev/null
+++ b/checker/tests/nullness/Conditions.java
@@ -0,0 +1,40 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+public class Conditions {
+
+  @Nullable Object f;
+
+  void test1(Conditions c) {
+    if (!(c.f != null)) {
+      return;
+    }
+    c.f.hashCode();
+  }
+
+  void test2(Conditions c) {
+    if (!(c.f != null) || 5 > 9) {
+      return;
+    }
+    c.f.hashCode();
+  }
+
+  @EnsuresNonNullIf(expression = "f", result = true)
+  public boolean isNN() {
+    return (f != null);
+  }
+
+  void test1m(Conditions c) {
+    if (!(c.isNN())) {
+      return;
+    }
+    c.f.hashCode();
+  }
+
+  void test2m(Conditions c) {
+    if (!(c.isNN()) || 5 > 9) {
+      return;
+    }
+    c.f.hashCode();
+  }
+}
diff --git a/checker/tests/nullness/ControlFlow.java b/checker/tests/nullness/ControlFlow.java
new file mode 100644
index 0000000..f8e614b
--- /dev/null
+++ b/checker/tests/nullness/ControlFlow.java
@@ -0,0 +1,16 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// test-case for issue 160
+public class ControlFlow {
+  public static void main(String[] args) {
+    String s = null;
+    if (s == null) {
+      // Important!
+    } else {
+      // Can also throw exception or call System#exit
+      return;
+    }
+    // :: error: (dereference.of.nullable)
+    System.out.println(s.toString());
+  }
+}
diff --git a/checker/tests/nullness/CopyOfArray.java b/checker/tests/nullness/CopyOfArray.java
new file mode 100644
index 0000000..f223bbb
--- /dev/null
+++ b/checker/tests/nullness/CopyOfArray.java
@@ -0,0 +1,13 @@
+import java.util.Arrays;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class CopyOfArray {
+  protected void makeCopy(Object[] args, int i) {
+    Object[] copyExact1 = Arrays.copyOf(args, args.length);
+    @Nullable Object[] copyExact2 = Arrays.copyOf(args, args.length);
+
+    // :: error: (assignment)
+    Object[] copyInexact1 = Arrays.copyOf(args, i);
+    @Nullable Object[] copyInexact2 = Arrays.copyOf(args, i);
+  }
+}
diff --git a/checker/tests/nullness/DaikonEnhancedFor.java b/checker/tests/nullness/DaikonEnhancedFor.java
new file mode 100644
index 0000000..bd30c42
--- /dev/null
+++ b/checker/tests/nullness/DaikonEnhancedFor.java
@@ -0,0 +1,30 @@
+// Based on a false positive encountered in Daikon related to common CFGs
+// by the KeyFor checker.
+
+import java.util.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+class DaikonEnhancedFor {
+
+  @SuppressWarnings("nullness")
+  Map<Object, Set<@KeyFor("cmap") Object>> cmap = null;
+
+  @SuppressWarnings("nullness")
+  Object[] getObjects() {
+    return null;
+  }
+
+  void process(@KeyFor("this.cmap") Object super_c) {
+    @SuppressWarnings("keyfor") // the loop below makes all these keys to cmap
+    @KeyFor("this.cmap") Object[] clazzes = getObjects();
+    // go through all of the classes and intialize the map
+    for (Object cd : clazzes) {
+      cmap.put(cd, new TreeSet<@KeyFor("cmap") Object>());
+    }
+    // go through the list again and put in the derived class information
+    for (Object cd : clazzes) {
+      Set<@KeyFor("this.cmap") Object> derived = cmap.get(super_c);
+      derived.add(cd);
+    }
+  }
+}
diff --git a/checker/tests/nullness/DaikonEnhancedForNoThis.java b/checker/tests/nullness/DaikonEnhancedForNoThis.java
new file mode 100644
index 0000000..da3b678
--- /dev/null
+++ b/checker/tests/nullness/DaikonEnhancedForNoThis.java
@@ -0,0 +1,32 @@
+// Based on a false positive encountered in Daikon related to common CFGs
+// for subcheckers. This shows the original version of the Daikon code,
+// before it was modified to avoid missing standardization. See DaikonEnhancedFor.java
+// for the "fixed" version. There are no longer expected errors in this test.
+
+import java.util.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+class DaikonEnhancedForNoThis {
+
+  @SuppressWarnings("nullness")
+  Map<Object, Set<@KeyFor("cmap") Object>> cmap = null;
+
+  @SuppressWarnings("nullness")
+  Object[] getObjects() {
+    return null;
+  }
+
+  void process(@KeyFor("this.cmap") Object super_c) {
+    @SuppressWarnings("keyfor") // the loop below makes all these keys to cmap
+    @KeyFor("cmap") Object[] clazzes = getObjects();
+    // go through all of the classes and intialize the map
+    for (Object cd : clazzes) {
+      cmap.put(cd, new TreeSet<@KeyFor("cmap") Object>());
+    }
+    // go through the list again and put in the derived class information
+    for (Object cd : clazzes) {
+      Set<@KeyFor("this.cmap") Object> derived = cmap.get(super_c);
+      derived.add(cd);
+    }
+  }
+}
diff --git a/checker/tests/nullness/DaikonTests.java b/checker/tests/nullness/DaikonTests.java
new file mode 100644
index 0000000..bd910a0
--- /dev/null
+++ b/checker/tests/nullness/DaikonTests.java
@@ -0,0 +1,138 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+/*
+ * Miscellaneous tests based on problems found when checking Daikon.
+ */
+public class DaikonTests {
+
+  // Based on a problem found in PPtSlice.
+  class Bug1 {
+    @Nullable Object field;
+
+    public void cond1() {
+      if (this.hashCode() > 6 && Bug1Other.field != null) {
+        // spurious dereference error
+        Bug1Other.field.toString();
+      }
+    }
+
+    public void cond1(Bug1 p) {
+      if (this.hashCode() > 6 && p.field != null) {
+        // works
+        p.field.toString();
+      }
+    }
+
+    public void cond2() {
+      if (Bug1Other.field != null && this.hashCode() > 6) {
+        // works
+        Bug1Other.field.toString();
+      }
+    }
+  }
+
+  // Based on problem found in PptCombined.
+  // Not yet able to reproduce the problem :-(
+
+  class Bug2Data {
+    Bug2Data(Bug2Super o) {}
+  }
+
+  class Bug2Super {
+    public @MonotonicNonNull Bug2Data field;
+  }
+
+  class Bug2 extends Bug2Super {
+    private void m() {
+      field = new Bug2Data(this);
+      field.hashCode();
+    }
+  }
+
+  // Based on problem found in FloatEqual.
+  class Bug3 {
+    @EnsuresNonNullIf(expression = "derived", result = true)
+    public boolean isDerived() {
+      return (derived != null);
+    }
+
+    @Nullable Object derived;
+
+    void good1(Bug3 v1) {
+      if (!v1.isDerived() || !(5 > 9)) {
+        return;
+      }
+      v1.derived.hashCode();
+    }
+
+    // TODO: this is currently not supported
+    //        void good2(Bug3 v1) {
+    //            if (!(v1.isDerived() && (5 > 9)))
+    //                return;
+    //            v1.derived.hashCode();
+    //        }
+
+    void good3(Bug3 v1) {
+      if (!v1.isDerived() || !(v1 instanceof Bug3)) {
+        return;
+      }
+      Object o = (Object) v1.derived;
+      o.hashCode();
+    }
+  }
+
+  // Based on problem found in PrintInvariants.
+  // Not yet able to reproduce the problem :-(
+
+  class Bug4 {
+    @MonotonicNonNull Object field;
+
+    void m(Bug4 p) {
+      if (false && p.field != null) {
+        p.field.hashCode();
+      }
+    }
+  }
+
+  // Based on problem found in chicory.Runtime:
+  class Bug5 {
+    @Nullable Object clazz;
+
+    @EnsuresNonNull("clazz")
+    void init() {
+      clazz = new Object();
+    }
+
+    void test(Bug5 b) {
+      if (b.clazz == null) {
+        b.init();
+      }
+
+      // The problem is:
+      // In the "then" branch, we have in "nnExpr" that "clazz" is non-null.
+      // In the "else" branch, we have in "annos" that the variable is non-null.
+      // However, as these are facts in two different representations, the merge keeps neither!
+      //
+      // no error message expected
+      b.clazz.hashCode();
+    }
+  }
+
+  // From LimitedSizeSet.  The following initialization of the values array
+  // has caused a NullPointerException.
+  class Bug6<T> {
+    protected @Nullable T @Nullable [] values;
+
+    public Bug6() {
+      // :: warning: [unchecked] unchecked cast
+      @Nullable T[] new_values_array = (@Nullable T[]) new @Nullable Object[4];
+      values = new_values_array;
+    }
+  }
+}
+
+class Bug1Other {
+  static @Nullable Object field;
+}
diff --git a/checker/tests/nullness/DefaultAnnotation.java b/checker/tests/nullness/DefaultAnnotation.java
new file mode 100644
index 0000000..0b7edc7
--- /dev/null
+++ b/checker/tests/nullness/DefaultAnnotation.java
@@ -0,0 +1,139 @@
+import java.util.Iterator;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+public class DefaultAnnotation {
+
+  public void testNoDefault() {
+
+    String s = null;
+  }
+
+  @DefaultQualifier.List(
+      @DefaultQualifier(
+          value = org.checkerframework.checker.nullness.qual.NonNull.class,
+          locations = {TypeUseLocation.ALL}))
+  public void testDefault() {
+
+    // :: error: (assignment)
+    String s = null; // error
+    List<String> lst = new List<>(); // valid
+    // :: error: (argument)
+    lst.add(null); // error
+  }
+
+  @DefaultQualifier(
+      value = org.checkerframework.checker.nullness.qual.NonNull.class,
+      locations = {TypeUseLocation.ALL})
+  public class InnerDefault {
+
+    public void testDefault() {
+      // :: error: (assignment)
+      String s = null; // error
+      List<String> lst = new List<>(); // valid
+      // :: error: (argument)
+      lst.add(null); // error
+      s = lst.get(0); // valid
+
+      List<@Nullable String> nullList = new List<>(); // valid
+      nullList.add(null); // valid
+      // :: error: (assignment)
+      s = nullList.get(0); // error
+    }
+  }
+
+  @DefaultQualifier(
+      value = org.checkerframework.checker.nullness.qual.NonNull.class,
+      locations = {TypeUseLocation.ALL})
+  public static class DefaultDefs {
+
+    public String getNNString() {
+      return "foo"; // valid
+    }
+
+    public String getNNString2() {
+      // :: error: (return)
+      return null; // error
+    }
+
+    public <T extends @Nullable Object> T getNull(T t) {
+      // :: error: (return)
+      return null; // invalid
+    }
+
+    public <T extends @NonNull Object> T getNonNull(T t) {
+      // :: error: (return)
+      return null; // error
+    }
+  }
+
+  public class DefaultUses {
+
+    public void test() {
+
+      DefaultDefs d = new DefaultDefs();
+
+      @NonNull String s = d.getNNString(); // valid
+    }
+
+    @DefaultQualifier(
+        value = org.checkerframework.checker.nullness.qual.NonNull.class,
+        locations = {TypeUseLocation.ALL})
+    public void testDefaultArgs() {
+
+      DefaultDefs d = new DefaultDefs();
+
+      // :: error: (assignment)
+      String s1 = d.<@Nullable String>getNull(null); // error
+      String s2 = d.<String>getNonNull("foo"); // valid
+      // :: error: (type.argument) :: error: (assignment)
+      String s3 = d.<@Nullable String>getNonNull("foo"); // error
+    }
+  }
+
+  @DefaultQualifier(value = org.checkerframework.checker.nullness.qual.NonNull.class)
+  static class DefaultExtends implements Iterator<String>, Iterable<String> {
+
+    @Override
+    public boolean hasNext() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String next() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Iterator<String> iterator() {
+      return this;
+    }
+  }
+
+  class List<E extends @Nullable Object> {
+    public E get(int i) {
+      throw new RuntimeException();
+    }
+
+    public boolean add(E e) {
+      throw new RuntimeException();
+    }
+  }
+
+  @DefaultQualifier(value = NonNull.class)
+  public void testDefaultUnqualified() {
+
+    // :: error: (assignment)
+    String s = null; // error
+    List<String> lst = new List<>(); // valid
+    // :: error: (argument)
+    lst.add(null); // error
+  }
+}
diff --git a/checker/tests/nullness/DefaultFlow.java b/checker/tests/nullness/DefaultFlow.java
new file mode 100644
index 0000000..04fa42f
--- /dev/null
+++ b/checker/tests/nullness/DefaultFlow.java
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+@org.checkerframework.framework.qual.DefaultQualifier(NonNull.class)
+public class DefaultFlow {
+
+  void test() {
+
+    @Nullable String reader = null;
+    if (reader == null) {
+      return;
+    }
+
+    reader.startsWith("hello");
+  }
+
+  void tesVariableInitialization() {
+    @Nullable Object elts = null;
+    assert elts != null : "@AssumeAssertion(nullness)";
+    @NonNull Object elem = elts;
+  }
+}
diff --git a/checker/tests/nullness/DefaultInterface.java b/checker/tests/nullness/DefaultInterface.java
new file mode 100644
index 0000000..4da53e4
--- /dev/null
+++ b/checker/tests/nullness/DefaultInterface.java
@@ -0,0 +1,16 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(org.checkerframework.checker.nullness.qual.NonNull.class)
+interface Foo {
+  void foo(String a, String b);
+}
+
+@DefaultQualifier(org.checkerframework.checker.nullness.qual.NonNull.class)
+public class DefaultInterface {
+
+  public void test() {
+
+    @Nullable Foo foo = null;
+  }
+}
diff --git a/checker/tests/nullness/DefaultLoops.java b/checker/tests/nullness/DefaultLoops.java
new file mode 100644
index 0000000..f440169
--- /dev/null
+++ b/checker/tests/nullness/DefaultLoops.java
@@ -0,0 +1,36 @@
+import java.util.LinkedList;
+import org.checkerframework.checker.nullness.qual.*;
+
+class MyTS extends LinkedList {}
+
+public class DefaultLoops {
+  void m() {
+    MyTS ts = new MyTS();
+    // s should default to @Nullable
+    for (Object s : ts) {}
+  }
+
+  void bar() {
+    for (int i = 0; i < 100; ++i) {
+      // nullable by default
+      Object o;
+      o = null;
+      // :: error: (dereference.of.nullable)
+      o.hashCode();
+      o = new Object();
+      o.hashCode();
+    }
+    for (int i = 0; i < 100; ++i) {
+      // nullable by default
+      Object o;
+      o = new Object();
+      o.hashCode();
+    }
+    int i = 0;
+    // nullable by default
+    for (Object o2; i < 100; ++i) {
+      o2 = null;
+      int i3 = new Object().hashCode();
+    }
+  }
+}
diff --git a/checker/tests/nullness/DefaultingForEach.java b/checker/tests/nullness/DefaultingForEach.java
new file mode 100644
index 0000000..4b8b170
--- /dev/null
+++ b/checker/tests/nullness/DefaultingForEach.java
@@ -0,0 +1,44 @@
+// Test case for issue #4248: https://github.com/typetools/checker-framework/issues/4248
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+class DefaultForEach {
+  @DefaultQualifier(Nullable.class)
+  Object @NonNull [] foo() {
+    return new Object[] {null};
+  }
+
+  void bar() {
+    for (Object p : foo()) {
+      // :: error: dereference.of.nullable
+      p.toString();
+    }
+  }
+
+  @DefaultQualifier(Nullable.class)
+  @NonNull List<Object> foo2() {
+    throw new RuntimeException();
+  }
+
+  void bar2() {
+    for (Object p : foo2()) {
+      // :: error: (dereference.of.nullable)
+      p.toString();
+    }
+  }
+
+  double[][] foo3() {
+    throw new RuntimeException();
+  }
+
+  void bar3() {
+    for (double[] pa : foo3()) {
+      for (Double p : pa) {
+        p.toString();
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/DefaultsNullness.java b/checker/tests/nullness/DefaultsNullness.java
new file mode 100644
index 0000000..f34f145
--- /dev/null
+++ b/checker/tests/nullness/DefaultsNullness.java
@@ -0,0 +1,19 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class DefaultsNullness {
+
+  // local variable defaults
+  void test(@UnknownInitialization DefaultsNullness para, @Initialized DefaultsNullness comm) {
+    // @Nullable @UnknownInitialization by default
+    String s = "abc";
+
+    s = null;
+
+    DefaultsNullness d;
+    d = null; // null okay (default == @Nullable)
+
+    d = comm; // initialized okay (default == @Initialized)
+    d.hashCode();
+  }
+}
diff --git a/checker/tests/nullness/DotClass.java b/checker/tests/nullness/DotClass.java
new file mode 100644
index 0000000..ecd9c9f
--- /dev/null
+++ b/checker/tests/nullness/DotClass.java
@@ -0,0 +1,16 @@
+import java.lang.annotation.Annotation;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class DotClass {
+
+  void test() {
+    doStuff(NonNull.class);
+  }
+
+  void doStuff(Class<? extends Annotation> cl) {}
+
+  void access() {
+    Object.class.toString();
+    int.class.toString();
+  }
+}
diff --git a/checker/tests/nullness/EmptyConstructor.java b/checker/tests/nullness/EmptyConstructor.java
new file mode 100644
index 0000000..e0ab849
--- /dev/null
+++ b/checker/tests/nullness/EmptyConstructor.java
@@ -0,0 +1,25 @@
+// @skip-test Change error key to one with a clearer message that explicitly mentions the superclass
+// constructor
+
+import org.checkerframework.dataflow.qual.*;
+
+public class SuperClass {
+  static int count = 0;
+
+  public SuperClass() {
+    count++;
+  }
+}
+
+// The error message is very confusing:
+//   EmptyConstructor.java:22: error: call to non-side-effect-free method not allowed in
+// side-effect-free method
+//     public EmptyConstructor() {}
+//                               ^
+// because there's no obvious call.  The message key should be changed to one whose message is "call
+// to non-side-effect-free superclass constructor not allowed in side-effect-free constructor"
+
+public class EmptyConstructor extends SuperClass {
+  @SideEffectFree
+  public EmptyConstructor() {}
+}
diff --git a/checker/tests/nullness/EnsuresKeyForOverriding.java b/checker/tests/nullness/EnsuresKeyForOverriding.java
new file mode 100644
index 0000000..bdcde72
--- /dev/null
+++ b/checker/tests/nullness/EnsuresKeyForOverriding.java
@@ -0,0 +1,23 @@
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.EnsuresKeyFor;
+
+public class EnsuresKeyForOverriding {
+  static class MyClass {
+    Object field = new Object();
+  }
+
+  MyClass o = new MyClass();
+
+  @EnsuresKeyFor(value = "#1.field", map = "#2")
+  void method(MyClass o, Map<Object, Object> map) {
+    map.put(o.field, "Hello");
+  }
+
+  static class SubEnsuresKeyForOverriding extends EnsuresKeyForOverriding {
+    @Override
+    @EnsuresKeyFor(value = "#1.field", map = "#2")
+    void method(MyClass q, Map<Object, Object> subMap) {
+      super.method(q, subMap);
+    }
+  }
+}
diff --git a/checker/tests/nullness/EnsuresNonNullIfInheritedTest.java b/checker/tests/nullness/EnsuresNonNullIfInheritedTest.java
new file mode 100644
index 0000000..b5b94d3
--- /dev/null
+++ b/checker/tests/nullness/EnsuresNonNullIfInheritedTest.java
@@ -0,0 +1,44 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+class Node {
+  int id;
+  @Nullable Node next;
+
+  Node(int id, @Nullable Node next) {
+    this.id = id;
+    this.next = next;
+  }
+}
+
+class SubEnumerate {
+  protected @Nullable Node current;
+
+  public SubEnumerate(Node node) {
+    this.current = node;
+  }
+
+  @EnsuresNonNullIf(expression = "current", result = true)
+  public boolean hasMoreElements() {
+    return (current != null);
+  }
+}
+
+class Enumerate extends SubEnumerate {
+
+  public Enumerate(Node node) {
+    super(node);
+  }
+
+  public boolean hasMoreElements() {
+    return (current != null);
+  }
+}
+
+class Main {
+  public static final void main(String args[]) {
+    Node n2 = new Node(2, null);
+    Node n1 = new Node(1, n2);
+    Enumerate e = new Enumerate(n1);
+    while (e.hasMoreElements()) {}
+  }
+}
diff --git a/checker/tests/nullness/EnsuresNonNullIfTest.java b/checker/tests/nullness/EnsuresNonNullIfTest.java
new file mode 100644
index 0000000..ef5ee28
--- /dev/null
+++ b/checker/tests/nullness/EnsuresNonNullIfTest.java
@@ -0,0 +1,66 @@
+// Test case for issue 62: https://github.com/typetools/checker-framework/issues/62
+
+// @skip-test until the issue is fixed
+
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+public class EnsuresNonNullIfTest {
+
+  public static void fromDirPos(File1 dbdir) {
+    if (dbdir.isDirectory()) {
+      File1 @NonNull [] files = dbdir.listFiles();
+    }
+  }
+
+  public static void fromDirNeg(File1 dbdir) {
+    if (!dbdir.isDirectory()) {
+      throw new Error("Not a directory: " + dbdir);
+    }
+    File1 @NonNull [] files = dbdir.listFiles();
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////
+/// Classes copied from the annotated JDK
+///
+
+// NOTE:  These annotations are actually incorrect (& not in the JDK).
+// But, the test remains valid in how it exercises nullness checking.
+
+// TODO: Have a way of saying the property holds no matter what value is used in a given expression.
+
+public class File1 {
+  @EnsuresNonNullIf(
+      result = true,
+      expression = {
+        "list()",
+        "list(String)", // TODO: has no effect
+        "listFiles()",
+        "listFiles(String)", // TODO: has no effect
+        "listFiles(Double)" // TODO: has no effect
+      })
+  public boolean isDirectory() {
+    throw new RuntimeException("skeleton method");
+  }
+
+  public String @Nullable [] list() {
+    throw new RuntimeException("skeleton method");
+  }
+
+  public String @Nullable [] list(@Nullable String FilenameFilter_a1) {
+    throw new RuntimeException("skeleton method");
+  }
+
+  public File1 @Nullable [] listFiles() {
+    throw new RuntimeException("skeleton method");
+  }
+
+  public File1 @Nullable [] listFiles(@Nullable String FilenameFilter_a1) {
+    throw new RuntimeException("skeleton method");
+  }
+
+  public File1 @Nullable [] listFiles(@Nullable Double FileFilter_a1) {
+    throw new RuntimeException("skeleton method");
+  }
+}
diff --git a/checker/tests/nullness/EnsuresNonNullIfTest2.java b/checker/tests/nullness/EnsuresNonNullIfTest2.java
new file mode 100644
index 0000000..f447651
--- /dev/null
+++ b/checker/tests/nullness/EnsuresNonNullIfTest2.java
@@ -0,0 +1,67 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+/** Test case for issue 53: https://github.com/typetools/checker-framework/issues/53 */
+public class EnsuresNonNullIfTest2 {
+
+  private @Nullable Long id;
+
+  public @org.checkerframework.dataflow.qual.Pure @Nullable Long getId() {
+    return id;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "getId()")
+  public boolean hasId2() {
+    return getId() != null;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "id")
+  public boolean hasId11() {
+    return id != null;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "id")
+  public boolean hasId12() {
+    return this.id != null;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "this.id")
+  public boolean hasId13() {
+    return id != null;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "this.id")
+  public boolean hasId14() {
+    return this.id != null;
+  }
+
+  void client() {
+    if (hasId11()) {
+      id.toString();
+    }
+    if (hasId12()) {
+      id.toString();
+    }
+    if (hasId13()) {
+      id.toString();
+    }
+    if (hasId14()) {
+      id.toString();
+    }
+    // :: error: (dereference.of.nullable)
+    id.toString();
+  }
+
+  // Expressions referring to enclosing classes should be resolved.
+  class Inner {
+    @EnsuresNonNullIf(result = true, expression = "getId()")
+    public boolean innerHasGetIdMethod() {
+      return getId() != null;
+    }
+
+    @EnsuresNonNullIf(result = true, expression = "id")
+    public boolean innerHasIdField() {
+      return id != null;
+    }
+  }
+}
diff --git a/checker/tests/nullness/EnsuresNonNullIfTest4.java b/checker/tests/nullness/EnsuresNonNullIfTest4.java
new file mode 100644
index 0000000..c6ab3f7
--- /dev/null
+++ b/checker/tests/nullness/EnsuresNonNullIfTest4.java
@@ -0,0 +1,31 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.dataflow.qual.*;
+
+public class EnsuresNonNullIfTest4 {
+
+  public void add_bottom_up(MyInvariant inv) {
+
+    // The problem goes away if the below line is deleted or replaced with:
+    //   Object x = new Object[100];
+    Object x = new @Nullable Object[100];
+
+    if (inv.is_ni_suppressed()) {
+      Object ss = inv.get_ni_suppressions();
+      ss.toString();
+    }
+  }
+}
+
+class MyInvariant {
+  @Pure
+  public @Nullable Object get_ni_suppressions() {
+    return (null);
+  }
+
+  @SuppressWarnings("nullness")
+  @EnsuresNonNullIf(result = true, expression = "get_ni_suppressions()")
+  @Pure
+  public boolean is_ni_suppressed() {
+    return true;
+  }
+}
diff --git a/checker/tests/nullness/EnsuresNonNullIfTestSimple.java b/checker/tests/nullness/EnsuresNonNullIfTestSimple.java
new file mode 100644
index 0000000..da88600
--- /dev/null
+++ b/checker/tests/nullness/EnsuresNonNullIfTestSimple.java
@@ -0,0 +1,50 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+/*
+ * These tests ensure that EnsuresNonNullIf methods
+ * are verified.
+ */
+public class EnsuresNonNullIfTestSimple {
+
+  protected int @Nullable [] values;
+
+  @EnsuresNonNullIf(result = true, expression = "values")
+  public boolean repNulledBAD() {
+    // :: error: (contracts.conditional.postcondition)
+    return values == null;
+  }
+
+  @EnsuresNonNullIf(result = false, expression = "values")
+  public boolean repNulled() {
+    return values == null;
+  }
+
+  public void addAll(EnsuresNonNullIfTestSimple s) {
+    if (repNulled()) {
+      return;
+    }
+    @NonNull Object x = values;
+
+    /* TODO skip-tests
+     * The two errors are not raised currently
+     * The assumption that "values" is NN is added above.
+     * However, as repNulled is not pure, it should be removed again here.
+    if (s.repNulled()) {
+        // : : (dereference.of.nullable)
+        values.hashCode();
+    } else {
+        // we called on "s", so we don't know anything about "values".
+        // : : (assignment)
+        @NonNull Object y = values;
+    }
+    */
+
+    if (s.repNulled()) {
+      // :: error: (dereference.of.nullable)
+      s.values.hashCode();
+    } else {
+      @NonNull Object y = s.values;
+    }
+  }
+}
diff --git a/checker/tests/nullness/EnumStaticBlock.java b/checker/tests/nullness/EnumStaticBlock.java
new file mode 100644
index 0000000..a2defa0
--- /dev/null
+++ b/checker/tests/nullness/EnumStaticBlock.java
@@ -0,0 +1,16 @@
+import java.util.ArrayList;
+import java.util.List;
+
+public class EnumStaticBlock {
+  public enum Section {
+    ME,
+    OTHER;
+    private static final List<Integer> l = new ArrayList<>();
+
+    static {
+      for (int i = 0; i < 10; ++i) {
+        l.add(i);
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/EnumsNullness.java b/checker/tests/nullness/EnumsNullness.java
new file mode 100644
index 0000000..09ec035
--- /dev/null
+++ b/checker/tests/nullness/EnumsNullness.java
@@ -0,0 +1,38 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class EnumsNullness {
+
+  enum MyEnum {
+    A,
+    B,
+    C,
+    D
+  }
+  // :: error: (assignment)
+  MyEnum myEnum = null; // invalid
+  @Nullable MyEnum myNullableEnum = null;
+
+  void testLocalEnum() {
+    // Enums are allowed to be null:  no error here.
+    MyEnum myNullableEnum = null;
+    // :: error: (assignment)
+    @NonNull MyEnum myEnum = null; // invalid
+  }
+
+  enum EnumBadAnnos {
+    A,
+    // :: error: (nullness.on.enum)
+    @NonNull B,
+    // :: error: (nullness.on.enum)
+    @Nullable C,
+    D;
+
+    public static final EnumBadAnnos A2 = A;
+    public static final @NonNull EnumBadAnnos B2 = B;
+    public static final @Nullable EnumBadAnnos C2 = C;
+
+    @Nullable String method() {
+      return null;
+    }
+  }
+}
diff --git a/checker/tests/nullness/EqualToNullness.java b/checker/tests/nullness/EqualToNullness.java
new file mode 100644
index 0000000..2c2dcca
--- /dev/null
+++ b/checker/tests/nullness/EqualToNullness.java
@@ -0,0 +1,39 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class EqualToNullness {
+
+  //    @Nullable String f;
+  //
+  //    void t1(@Nullable String g) {
+  //        // :: error: (dereference.of.nullable)
+  //        g.toLowerCase();
+  //        if (g != null) {
+  //            g.toLowerCase();
+  //        }
+  //    }
+  //
+  //    void t2() {
+  //        // :: error: (dereference.of.nullable)
+  //        f.toLowerCase();
+  //        if (f == null) {} else {
+  //            f.toLowerCase();
+  //        }
+  //    }
+  //
+  //    void t1b(@Nullable String g) {
+  //        // :: error: (dereference.of.nullable)
+  //        g.toLowerCase();
+  //        if (null != g) {
+  //            g.toLowerCase();
+  //        }
+  //    }
+  //
+  //    void t2b() {
+  //        // :: error: (dereference.of.nullable)
+  //        f.toLowerCase();
+  //        if (null == f) {} else {
+  //            f.toLowerCase();
+  //        }
+  //    }
+}
diff --git a/checker/tests/nullness/ExceptionParam.java b/checker/tests/nullness/ExceptionParam.java
new file mode 100644
index 0000000..5275ca9
--- /dev/null
+++ b/checker/tests/nullness/ExceptionParam.java
@@ -0,0 +1,30 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+/** Exception parameters are non-null, even if the default is nullable. */
+@DefaultQualifier(org.checkerframework.checker.nullness.qual.Nullable.class)
+public class ExceptionParam {
+  void exc1() {
+    try {
+    } catch (AssertionError e) {
+      @NonNull Object o = e;
+    }
+  }
+
+  void exc2() {
+    try {
+      // :: warning: (nullness.on.exception.parameter)
+    } catch (@NonNull AssertionError e) {
+      @NonNull Object o = e;
+    }
+  }
+
+  void exc3() {
+    try {
+      // :: warning: (nullness.on.exception.parameter)
+    } catch (@Nullable AssertionError e) {
+      @NonNull Object o = e;
+    }
+  }
+}
diff --git a/checker/tests/nullness/Exceptions.java b/checker/tests/nullness/Exceptions.java
new file mode 100644
index 0000000..571a578
--- /dev/null
+++ b/checker/tests/nullness/Exceptions.java
@@ -0,0 +1,48 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Exceptions {
+  void exceptionParam(@Nullable Exception m) {
+    // :: error: (dereference.of.nullable)
+    m.getClass(); // should emit error
+  }
+
+  void nonnullExceptionParam(@NonNull Exception m) {
+    m.getClass();
+  }
+
+  void exception(@Nullable Exception m) {
+    try {
+
+    } catch (Exception e) {
+      // Note that this code is dead.
+      e.getClass();
+      // :: error: (dereference.of.nullable)
+      m.getClass(); // should emit error
+    }
+  }
+
+  void throwException() {
+    int a = 0;
+    if (a == 0) {
+      // :: error: (throwing.nullable)
+      throw null;
+    } else if (a == 1) {
+      RuntimeException e = null;
+      // :: error: (throwing.nullable)
+      throw e;
+    } else {
+      RuntimeException e = new RuntimeException();
+      throw e;
+    }
+  }
+
+  void reassignException() {
+    try {
+    } catch (RuntimeException e) {
+      // Note that this code is dead.
+      // :: error: (assignment)
+      e = null;
+      throw e;
+    }
+  }
+}
diff --git a/checker/tests/nullness/ExplictTypeVarAnnos.java b/checker/tests/nullness/ExplictTypeVarAnnos.java
new file mode 100644
index 0000000..e03d8a5
--- /dev/null
+++ b/checker/tests/nullness/ExplictTypeVarAnnos.java
@@ -0,0 +1,47 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class ExplictTypeVarAnnos<E extends @Nullable Object, @Nullable F> {
+  interface Consumer<A extends @Nullable Object> {}
+
+  public static <B extends @Nullable Object> Consumer<B> cast(
+      final @Nullable Consumer<? super B> consumer) {
+    throw new RuntimeException();
+  }
+
+  public static <C extends @Nullable Object> Consumer<C> getConsumer0() {
+    Consumer<@Nullable Object> nullConsumer = null;
+    Consumer<C> result = ExplictTypeVarAnnos.<C>cast(nullConsumer);
+    return result;
+  }
+
+  public static <@Nullable D> Consumer<D> getConsumer1() {
+    Consumer<@Nullable Object> nullConsumer = null;
+    Consumer<D> result = ExplictTypeVarAnnos.<D>cast(nullConsumer);
+    return result;
+  }
+
+  public Consumer<E> getConsumer2() {
+    Consumer<@Nullable Object> nullConsumer = null;
+    Consumer<E> result = ExplictTypeVarAnnos.<E>cast(nullConsumer);
+    return result;
+  }
+
+  public Consumer<F> getConsumer3() {
+    Consumer<@Nullable Object> nullConsumer = null;
+    Consumer<F> result = ExplictTypeVarAnnos.<F>cast(nullConsumer);
+    return result;
+  }
+
+  @SuppressWarnings("method.invocation")
+  Consumer<E> field = getConsumer2();
+
+  public Consumer<E> getField() {
+    return field;
+  }
+
+  static class A<Q extends @NonNull Object> {}
+
+  // :: error: (type.argument)
+  static class B<S> extends A<S> {}
+}
diff --git a/checker/tests/nullness/ExpressionsNullness.java b/checker/tests/nullness/ExpressionsNullness.java
new file mode 100644
index 0000000..b0f026f
--- /dev/null
+++ b/checker/tests/nullness/ExpressionsNullness.java
@@ -0,0 +1,69 @@
+import java.io.*;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.*;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public class ExpressionsNullness {
+
+  public static double[] returnDoubleArray() {
+    return new double[] {3.14, 2.7};
+  }
+
+  public static void staticMembers() {
+    Pattern.compile("^>entry *()");
+    System.out.println(ExpressionsNullness.class);
+    ExpressionsNullness.class.getAnnotations(); // valid
+  }
+
+  private HashMap<String, String> map = new HashMap<>();
+
+  public void test() {
+    @SuppressWarnings("nullness")
+    String s = map.get("foo");
+
+    Class<?> cl = Boolean.TYPE;
+
+    List<?> foo = new LinkedList<Object>();
+    // :: error: (dereference.of.nullable)
+    foo.get(0).toString(); // default applies to wildcard extends
+
+    Set set = new HashSet();
+    for (@Nullable Object o : set) System.out.println();
+  }
+
+  void test2() {
+    List<? extends @NonNull String> lst = new LinkedList<@NonNull String>();
+    for (String s : lst) {
+      s.length();
+    }
+  }
+
+  <T extends @NonNull Object> void test3(T o) {
+    o.getClass(); // valid
+  }
+
+  void test4(List<? extends @NonNull Object> o) {
+    o.get(0).getClass(); // valid
+  }
+
+  void test5() {
+    Comparable<Date> d = new Date();
+  }
+
+  void testIntersection() {
+    java.util.Arrays.asList("m", 1);
+  }
+
+  Object obj;
+
+  public ExpressionsNullness(Object obj) {
+    this.obj = obj;
+  }
+}
diff --git a/checker/tests/nullness/ExtendsArrayList.java b/checker/tests/nullness/ExtendsArrayList.java
new file mode 100644
index 0000000..82429b6
--- /dev/null
+++ b/checker/tests/nullness/ExtendsArrayList.java
@@ -0,0 +1,12 @@
+import java.util.ArrayList;
+import java.util.List;
+
+public final class ExtendsArrayList extends ArrayList<String> {
+
+  public int removeMany(List<String> toRemove) {
+    for (String inv : this) {
+      if (!toRemove.contains(inv)) {}
+    }
+    return 0;
+  }
+}
diff --git a/checker/tests/nullness/FieldInit.java b/checker/tests/nullness/FieldInit.java
new file mode 100644
index 0000000..ead10ec
--- /dev/null
+++ b/checker/tests/nullness/FieldInit.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class FieldInit {
+  // :: error: (argument) :: error: (method.invocation)
+  String f = init(this);
+
+  String init(FieldInit o) {
+    return "";
+  }
+
+  void test() {
+    String local = init(this);
+  }
+}
diff --git a/checker/tests/nullness/FinalFields.java b/checker/tests/nullness/FinalFields.java
new file mode 100644
index 0000000..9f82333
--- /dev/null
+++ b/checker/tests/nullness/FinalFields.java
@@ -0,0 +1,47 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class Upper {
+  @Nullable String fs = "NonNull init";
+  final @Nullable String ffs = "NonNull init";
+
+  void access() {
+    // Error, because non-final field type is not refined
+    // :: error: (dereference.of.nullable)
+    fs.hashCode();
+    // Final field in the same class is refined
+    ffs.hashCode();
+  }
+}
+
+public class FinalFields {
+  public void foo(Upper u) {
+    // Error, because final field in different class is not refined
+    // :: error: (dereference.of.nullable)
+    u.fs.hashCode();
+  }
+
+  public void bar(Lower l) {
+    // Error, because final field in different class is not refined
+    // :: error: (dereference.of.nullable)
+    l.fs.hashCode();
+  }
+
+  public void local() {
+    @Nullable String ls = "Locals";
+    // Local variable is refined
+    ls.hashCode();
+  }
+}
+
+class Lower {
+  @Nullable String fs = "NonNull init, too";
+  final @Nullable String ffs = "NonNull init, too";
+
+  void access() {
+    // Error, because non-final field type is not refined
+    // :: error: (dereference.of.nullable)
+    fs.hashCode();
+    // Final field in the same class is refined
+    ffs.hashCode();
+  }
+}
diff --git a/checker/tests/nullness/FinalVar.java b/checker/tests/nullness/FinalVar.java
new file mode 100644
index 0000000..216a99e
--- /dev/null
+++ b/checker/tests/nullness/FinalVar.java
@@ -0,0 +1,19 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class FinalVar {
+
+  public Object pptIterator() {
+    // Only test with (effectively) final variables; Java only permits final or
+    // effectively final variables to be accessed from an anonymous class.
+    final String iter_view_1 = "I am not null";
+    @NonNull String iter_view_2 = "Neither am I";
+    final @NonNull String iter_view_3 = "Dittos";
+    return new Object() {
+      public void useFinalVar() {
+        iter_view_1.hashCode();
+        iter_view_2.hashCode();
+        iter_view_3.hashCode();
+      }
+    };
+  }
+}
diff --git a/checker/tests/nullness/FinalVar2.java b/checker/tests/nullness/FinalVar2.java
new file mode 100644
index 0000000..9201396
--- /dev/null
+++ b/checker/tests/nullness/FinalVar2.java
@@ -0,0 +1,47 @@
+// Test case for issue 266: https://github.com/typetools/checker-framework/issues/266
+// The problem is limited refinement of final variables.
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class FinalVar2 {
+
+  static Object method1(@Nullable Object arg) {
+    final Object tmp = arg;
+    if (tmp == null) {
+      return "hello";
+    }
+    return new Object() {
+      public void useFinalVar() {
+        // should be OK
+        tmp.hashCode();
+      }
+    };
+  }
+
+  static Object method2(final @Nullable Object arg) {
+    if (arg == null) {
+      return "hello";
+    }
+    return new Object() {
+      public void useFinalVar() {
+        // should be OK
+        arg.hashCode();
+      }
+    };
+  }
+
+  static Object method3(@Nullable Object arg) {
+    final Object tmp = arg;
+    Object result =
+        new Object() {
+          public void useFinalVar() {
+            // :: error: (dereference.of.nullable)
+            tmp.hashCode();
+          }
+        };
+    if (tmp == null) {
+      return "hello";
+    }
+    return result;
+  }
+}
diff --git a/checker/tests/nullness/FinalVar3.java b/checker/tests/nullness/FinalVar3.java
new file mode 100644
index 0000000..05be5b2
--- /dev/null
+++ b/checker/tests/nullness/FinalVar3.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class FinalVar3 {
+
+  static Object method1(@Nullable Object arg) {
+    final Object tmp = arg;
+    if (tmp == null) {
+      return "hello";
+    }
+    // The type of the final variable is correctly refined.
+    tmp.hashCode();
+    return "bye";
+  }
+}
diff --git a/checker/tests/nullness/FindBugs.java b/checker/tests/nullness/FindBugs.java
new file mode 100644
index 0000000..8684a4d
--- /dev/null
+++ b/checker/tests/nullness/FindBugs.java
@@ -0,0 +1,31 @@
+import edu.umd.cs.findbugs.annotations.*;
+
+public class FindBugs {
+
+  @CheckForNull
+  Object getNull() {
+    return null;
+  }
+
+  @NonNull MyList<@org.checkerframework.checker.nullness.qual.Nullable Object> getListOfNulls() {
+    // :: error: (return)
+    return null; // error
+  }
+
+  void test() {
+    Object o = getNull();
+    // :: error: (dereference.of.nullable)
+    o.toString(); // error
+
+    MyList<@org.checkerframework.checker.nullness.qual.Nullable Object> l = getListOfNulls();
+    l.toString();
+    // :: error: (dereference.of.nullable)
+    l.get().toString(); // error
+  }
+}
+
+class MyList<T extends @org.checkerframework.checker.nullness.qual.Nullable Object> {
+  T get() {
+    throw new RuntimeException();
+  }
+}
diff --git a/checker/tests/nullness/FlowAssignment.java b/checker/tests/nullness/FlowAssignment.java
new file mode 100644
index 0000000..8d77c0b
--- /dev/null
+++ b/checker/tests/nullness/FlowAssignment.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class FlowAssignment {
+
+  void test() {
+    @NonNull String s = "foo";
+
+    String t = s;
+    t.startsWith("f");
+  }
+}
diff --git a/checker/tests/nullness/FlowCompound.java b/checker/tests/nullness/FlowCompound.java
new file mode 100644
index 0000000..80affb3
--- /dev/null
+++ b/checker/tests/nullness/FlowCompound.java
@@ -0,0 +1,57 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class FlowCompound {
+
+  @org.checkerframework.dataflow.qual.Pure
+  public boolean equals(@Nullable Object o) {
+    return o != null && this.getClass() != o.getClass();
+  }
+
+  void test(@Nullable String s) {
+
+    if (s == null || s.length() > 0) {
+      // :: error: (assignment)
+      @NonNull String test = s;
+    }
+
+    String tmp;
+    @NonNull String notNull;
+    tmp = "hello";
+    notNull = tmp;
+    notNull = tmp = "hello";
+  }
+
+  public static boolean equal(@Nullable Object a, @Nullable Object b) {
+    assert b != null : "suppress nullness";
+    return a == b || (a != null && a.equals(b));
+  }
+
+  public static void testCompoundAssignmentWithString() {
+    String s = "m";
+    s += "n";
+    s.toString();
+  }
+
+  public static void testCompoundAssignmentWithChar() {
+    String s = "m";
+    s += 'n';
+    s.toString();
+  }
+
+  public static void testCompoundAssignWithNull() {
+    String s = "m";
+    s += null;
+    s.toString();
+  }
+
+  public static void testPrimitiveArray() {
+    int[] a = {0};
+    a[0] += 2;
+    System.out.println(a[0]);
+  }
+
+  public static void testPrimitive() {
+    Integer i = 1;
+    i -= 2;
+  }
+}
diff --git a/checker/tests/nullness/FlowCompoundConcatenation.java b/checker/tests/nullness/FlowCompoundConcatenation.java
new file mode 100644
index 0000000..81819d7
--- /dev/null
+++ b/checker/tests/nullness/FlowCompoundConcatenation.java
@@ -0,0 +1,17 @@
+public class FlowCompoundConcatenation {
+  static String getNonNullString() {
+    return "";
+  }
+
+  public static void testCompoundAssignWithNullAndMethodCall() {
+    String s = null;
+    s += getNonNullString();
+    s.toString();
+  }
+
+  public static void testCompoundAssignWithNull() {
+    String s = null;
+    s += "hello";
+    s.toString();
+  }
+}
diff --git a/checker/tests/nullness/FlowConditions.java b/checker/tests/nullness/FlowConditions.java
new file mode 100644
index 0000000..71c0e8b
--- /dev/null
+++ b/checker/tests/nullness/FlowConditions.java
@@ -0,0 +1,40 @@
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class FlowConditions {
+  void m(@Nullable Object x, @Nullable Object y) {
+    if (x == null || y == null) {
+      // :: error: (dereference.of.nullable)
+      x.toString();
+      // :: error: (dereference.of.nullable)
+      y.toString();
+    } else {
+      x.toString();
+      y.toString();
+    }
+  }
+
+  private final Map<String, Set<String>> graph = new HashMap<>();
+
+  public void addEdge1(String e, String parent, String child) {
+    if (!graph.containsKey(parent)) {
+      throw new NoSuchElementException();
+    }
+    if (!graph.containsKey(child)) {
+      throw new NoSuchElementException();
+    }
+    @NonNull Set<String> edges = graph.get(parent);
+  }
+
+  // TODO: Re-enable when issue 221 is resolved.
+  // public void addEdge2(String e, String parent, String child) {
+  //     if ( (!graph.containsKey(parent)) ||
+  //          (!graph.containsKey(child)))
+  //         throw new NoSuchElementException();
+  //     @NonNull Set<String> edges = graph.get(parent);
+  // }
+
+}
diff --git a/checker/tests/nullness/FlowConstructor.java b/checker/tests/nullness/FlowConstructor.java
new file mode 100644
index 0000000..02a9e88
--- /dev/null
+++ b/checker/tests/nullness/FlowConstructor.java
@@ -0,0 +1,38 @@
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class FlowConstructor {
+
+  String a;
+  String b;
+
+  public FlowConstructor(float f) {
+    a = "m";
+    b = "n";
+    semiRawMethod();
+  }
+
+  public FlowConstructor(int p) {
+    a = "m";
+    b = "n";
+    // :: error: (method.invocation)
+    nonRawMethod();
+  }
+
+  public FlowConstructor(double p) {
+    a = "m";
+    // :: error: (method.invocation)
+    nonRawMethod(); // error
+    b = "n";
+  }
+
+  void nonRawMethod() {
+    a.toString();
+    b.toString();
+  }
+
+  void semiRawMethod(@UnderInitialization(FlowConstructor.class) FlowConstructor this) {
+    a.toString();
+    b.toString();
+  }
+}
diff --git a/checker/tests/nullness/FlowConstructor2.java b/checker/tests/nullness/FlowConstructor2.java
new file mode 100644
index 0000000..06b8409
--- /dev/null
+++ b/checker/tests/nullness/FlowConstructor2.java
@@ -0,0 +1,8 @@
+public class FlowConstructor2 {
+  String f;
+
+  public FlowConstructor2() {
+    // :: error: (dereference.of.nullable)
+    f.hashCode();
+  }
+}
diff --git a/checker/tests/nullness/FlowExpressionParsingBug.java b/checker/tests/nullness/FlowExpressionParsingBug.java
new file mode 100644
index 0000000..a4a42a9
--- /dev/null
+++ b/checker/tests/nullness/FlowExpressionParsingBug.java
@@ -0,0 +1,92 @@
+import javax.swing.JMenuBar;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public abstract class FlowExpressionParsingBug {
+
+  //// Check that JavaExpressions with explicit and implicit 'this' work
+
+  protected @Nullable JMenuBar menuBar = null;
+
+  @RequiresNonNull("menuBar")
+  public void addFavorite() {}
+
+  @RequiresNonNull("this.menuBar")
+  public void addFavorite1() {}
+
+  //// Check JavaExpressions for static fields with different ways to access the field
+
+  static @Nullable String i = null;
+
+  @RequiresNonNull("FlowExpressionParsingBug.i")
+  public void a() {}
+
+  @RequiresNonNull("i")
+  public void b() {}
+
+  @RequiresNonNull("this.i")
+  public void c() {}
+
+  void test1() {
+    // :: error: (contracts.precondition)
+    a();
+    FlowExpressionParsingBug.i = "";
+    a();
+  }
+
+  void test1b() {
+    // :: error: (contracts.precondition)
+    a();
+    i = "";
+    a();
+  }
+
+  void test1c() {
+    // :: error: (contracts.precondition)
+    a();
+    this.i = "";
+    a();
+  }
+
+  void test2() {
+    // :: error: (contracts.precondition)
+    b();
+    FlowExpressionParsingBug.i = "";
+    b();
+  }
+
+  void test2b() {
+    // :: error: (contracts.precondition)
+    b();
+    i = "";
+    b();
+  }
+
+  void test2c() {
+    // :: error: (contracts.precondition)
+    b();
+    this.i = "";
+    b();
+  }
+
+  void test3() {
+    // :: error: (contracts.precondition)
+    c();
+    FlowExpressionParsingBug.i = "";
+    c();
+  }
+
+  void test3b() {
+    // :: error: (contracts.precondition)
+    c();
+    i = "";
+    c();
+  }
+
+  void test3c() {
+    // :: error: (contracts.precondition)
+    c();
+    this.i = "";
+    c();
+  }
+}
diff --git a/checker/tests/nullness/FlowField.java b/checker/tests/nullness/FlowField.java
new file mode 100644
index 0000000..fa46ce3
--- /dev/null
+++ b/checker/tests/nullness/FlowField.java
@@ -0,0 +1,76 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+@org.checkerframework.framework.qual.DefaultQualifier(Nullable.class)
+public class FlowField {
+
+  public @Nullable String s = null;
+
+  void test() {
+    if (s != null) {
+      s.startsWith("foo");
+    }
+  }
+
+  static String field = "asdf";
+
+  static {
+    field = "asdf";
+  }
+
+  void testFields() {
+    // :: error: (dereference.of.nullable)
+    System.out.println(field.length());
+  }
+
+  // Fowrard reference to static finals
+  void test1() {
+    nonnull.toString();
+  }
+
+  static final String nonnull = new String();
+
+  class A {
+    protected String field = null;
+  }
+
+  class B extends A {
+    void test() {
+      assert field != null : "@AssumeAssertion(nullness)";
+      field.length();
+    }
+  }
+
+  static class BooleanWrapper {
+    @Nullable Object b;
+  }
+
+  @Nullable BooleanWrapper bw;
+
+  void testBitwise(@NonNull FlowField a, @NonNull FlowField b) {
+    Object r;
+    if (a.bw != null) {
+      if (b.bw != null) {
+        r = a.bw.b;
+        r = b.bw.b;
+      }
+    }
+    if (a.bw != null && b.bw != null) {
+      r = a.bw.b;
+      r = b.bw.b;
+    }
+  }
+
+  void testInstanceOf(@NonNull FlowField a) {
+    if (!(a.s instanceof String)) {
+      return;
+    }
+    @NonNull String s = a.s;
+  }
+
+  void testTwoLevels(@NonNull FlowField a, BooleanWrapper bwArg) {
+    // :: error: (dereference.of.nullable)
+    if (!(a.bw.hashCode() == 0)) // warning here
+    return;
+    Object o = a.bw.b; // but not here
+  }
+}
diff --git a/checker/tests/nullness/FlowInitialization.java b/checker/tests/nullness/FlowInitialization.java
new file mode 100644
index 0000000..df55a57
--- /dev/null
+++ b/checker/tests/nullness/FlowInitialization.java
@@ -0,0 +1,54 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.EnsuresQualifier;
+
+public class FlowInitialization {
+
+  @NonNull String f;
+  @Nullable String g;
+
+  // :: error: (initialization.fields.uninitialized)
+  public FlowInitialization() {}
+
+  public FlowInitialization(long l) {
+    g = "";
+    f = g;
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public FlowInitialization(boolean b) {
+    if (b) {
+      f = "";
+    }
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public FlowInitialization(int i) {
+    if (i == 0) {
+      throw new RuntimeException();
+    }
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public FlowInitialization(char c) {
+    if (c == 'c') {
+      return;
+    }
+    f = "";
+  }
+
+  public FlowInitialization(double d) {
+    setField();
+  }
+
+  @EnsuresQualifier(expression = "f", qualifier = NonNull.class)
+  public void setField(@UnknownInitialization FlowInitialization this) {
+    f = "";
+  }
+}
+
+class FlowPrimitives {
+  boolean b;
+  int t;
+  char c;
+}
diff --git a/checker/tests/nullness/FlowLoop.java b/checker/tests/nullness/FlowLoop.java
new file mode 100644
index 0000000..1beef9f
--- /dev/null
+++ b/checker/tests/nullness/FlowLoop.java
@@ -0,0 +1,162 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class FlowLoop {
+  void simpleWhileLoop() {
+    String s = "m";
+
+    while (s != null) {
+      s.toString();
+      s = null;
+    }
+    // :: error: (dereference.of.nullable)
+    s.toString(); // error
+  }
+
+  void whileConditionError() {
+    String s = "m";
+
+    // :: error: (dereference.of.nullable)
+    while (s.toString() == "m") { // error
+      s.toString();
+      s = null;
+    }
+    s.toString();
+  }
+
+  void simpleForLoop() {
+    for (String s = "m"; s != null; s = null) {
+      s.toString();
+    }
+  }
+
+  void forLoopConditionError() {
+    for (String s = "m";
+        // :: error: (dereference.of.nullable)
+        s.toString() != "m"; // error
+        s = null) {
+      s.toString();
+    }
+  }
+
+  class Link {
+    Object val;
+    @Nullable Link next;
+
+    public Link(Object val, @Nullable Link next) {
+      this.val = val;
+      this.next = next;
+    }
+  }
+
+  // Both dereferences of l should succeed
+  void test(@Nullable Link in) {
+    for (@Nullable Link l = in; l != null; l = l.next) {
+      Object o;
+      o = l.val;
+    }
+  }
+
+  void multipleRuns() {
+    String s = "m";
+    while (true) {
+      // :: error: (dereference.of.nullable)
+      s.toString(); // error
+      s = null;
+    }
+  }
+
+  void multipleRunsDo() {
+    String s = "m";
+    do {
+      // :: error: (dereference.of.nullable)
+      s.toString(); // error
+      s = null;
+    } while (true);
+  }
+
+  void alwaysRunForLoop() {
+    String s = "m";
+    for (s = null; s != null; s = "m") {
+      s.toString(); // ok
+    }
+    // :: error: (dereference.of.nullable)
+    s.toString(); // error
+  }
+
+  public void badIterator() {
+    Class<?> opt_doc1 = null;
+    // :: error: (dereference.of.nullable)
+    opt_doc1.getInterfaces();
+    Class<?> opt_doc2 = null;
+    // :: error: (dereference.of.nullable)
+    for (Class<? extends @Nullable Object> fd : opt_doc2.getInterfaces()) {
+      // empty loop body
+    }
+  }
+
+  void testContinue(@Nullable Object o) {
+    for (; ; ) {
+      // :: error: (dereference.of.nullable)
+      o.toString();
+      if (true) continue;
+    }
+  }
+
+  void testBreak(@Nullable Object o) {
+    while (true) {
+      // :: error: (dereference.of.nullable)
+      o.toString();
+      if (true) break;
+    }
+  }
+
+  void testSimpleNull() {
+    String r1 = null;
+    while (r1 != null) {}
+    // :: error: (dereference.of.nullable)
+    r1.toString(); // error
+  }
+
+  void testMulticheckNull() {
+    String r1 = null;
+    while (r1 != null && r1.equals("m")) {}
+    // :: error: (dereference.of.nullable)
+    r1.toString(); // error
+  }
+
+  void testAssignInLoopSimple() {
+    String r1 = "";
+    while (r1 != null) {
+      r1 = null;
+    }
+    // :: error: (dereference.of.nullable)
+    r1.toString(); // error
+  }
+
+  void testAssignInLoopMulti() {
+    String r1 = "";
+    while (r1 != null && r1.isEmpty()) {
+      r1 = null;
+    }
+    // :: error: (dereference.of.nullable)
+    r1.toString(); // error
+  }
+
+  void testBreakWithCheck() {
+    String s = null;
+    while (true) {
+      if (s == null) break;
+      s.toString();
+    }
+  }
+
+  void test1() {
+    while (true) {
+      String s = null;
+      if (s == null) {
+        return;
+      }
+      s.toString();
+    }
+  }
+}
diff --git a/checker/tests/nullness/FlowNegation.java b/checker/tests/nullness/FlowNegation.java
new file mode 100644
index 0000000..c6e4f14
--- /dev/null
+++ b/checker/tests/nullness/FlowNegation.java
@@ -0,0 +1,99 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class FlowNegation {
+
+  void testSimpleValid() {
+    String s = "m";
+    s.toString();
+  }
+
+  void testCase1() {
+    String s = "m";
+    // :: warning: (nulltest.redundant)
+    if (s != null) {
+    } else {
+      // nothing to do
+    }
+    s.toString();
+  }
+
+  void testCase2() {
+    String s = "m";
+    // :: warning: (nulltest.redundant)
+    if (s == null) {
+    } else {
+      // nothing to do
+    }
+    s.toString();
+  }
+
+  void testInvalidCase1() {
+    String s = "m";
+    // :: warning: (nulltest.redundant)
+    if (s != null) {
+      s = null;
+    } else {
+      // nothing to do
+    }
+    // :: error: (dereference.of.nullable)
+    s.toString(); // error
+  }
+
+  void testInvalidCase2() {
+    String s = "m";
+    // :: warning: (nulltest.redundant)
+    if (s != null) {
+      // nothing to do
+    } else {
+      s = null;
+    }
+    // :: error: (dereference.of.nullable)
+    s.toString(); // error
+  }
+
+  void testSimpleValidTernary() {
+    String s = "m";
+    s.toString();
+  }
+
+  void testTernaryCase1() {
+    String s = "m";
+    // :: warning: (nulltest.redundant)
+    Object m = (s != null) ? "m" : "n";
+    s.toString();
+  }
+
+  void testTernaryCase2() {
+    String s = "m";
+    // :: warning: (nulltest.redundant)
+    Object m = (s == null) ? "m" : "n";
+    s.toString();
+  }
+
+  void testTernaryInvalidCase1() {
+    String s = "m";
+    // :: warning: (nulltest.redundant)
+    Object m = (s != null) ? (s = null) : "n";
+    // :: error: (dereference.of.nullable)
+    s.toString(); // error
+  }
+
+  void testTernaryInvalidCase2() {
+    String s = "m";
+    // :: warning: (nulltest.redundant)
+    Object m = (s != null) ? "m" : (s = null);
+    // :: error: (dereference.of.nullable)
+    s.toString(); // error
+  }
+
+  void testAssignInCond() {
+    String s = "m";
+    if ((s = null) != "m") {
+      // :: error: (assignment)
+      @NonNull String l0 = s;
+    } else {
+    }
+    // :: error: (assignment)
+    @NonNull String l1 = s;
+  }
+}
diff --git a/checker/tests/nullness/FlowNonThis.java b/checker/tests/nullness/FlowNonThis.java
new file mode 100644
index 0000000..6a13e4b
--- /dev/null
+++ b/checker/tests/nullness/FlowNonThis.java
@@ -0,0 +1,42 @@
+import java.io.*;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+
+public class FlowNonThis {
+
+  @Nullable String c;
+
+  public static void main(String[] args) {
+    FlowNonThis t = new FlowNonThis();
+    t.setup();
+    System.out.println(t.c.length());
+    t.erase();
+    // :: error: (dereference.of.nullable)
+    System.out.println(t.c.length());
+  }
+
+  public void setupThenErase() {
+    setup();
+    System.out.println(c.length());
+    erase();
+    // :: error: (dereference.of.nullable)
+    System.out.println(c.length());
+  }
+
+  public void justErase() {
+    // :: error: (dereference.of.nullable)
+    System.out.println(c.length());
+    erase();
+    // :: error: (dereference.of.nullable)
+    System.out.println(c.length());
+  }
+
+  @EnsuresNonNull("c")
+  public void setup() {
+    c = "setup";
+  }
+
+  public void erase() {
+    c = null;
+  }
+}
diff --git a/checker/tests/nullness/FlowNullness.java b/checker/tests/nullness/FlowNullness.java
new file mode 100644
index 0000000..2ef24c2
--- /dev/null
+++ b/checker/tests/nullness/FlowNullness.java
@@ -0,0 +1,338 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class FlowNullness {
+
+  public void testIf() {
+
+    String str = "foo";
+    @NonNull String a;
+    // :: warning: (nulltest.redundant)
+    if (str != null) {
+      a = str;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testIfNoBlock() {
+
+    String str = "foo";
+    @NonNull String a;
+    // :: warning: (nulltest.redundant)
+    if (str != null) {
+      a = str;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testElse() {
+
+    String str = "foo";
+    @NonNull String a;
+    // :: warning: (nulltest.redundant)
+    if (str == null) {
+      testAssert();
+    } else {
+      a = str;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testElseNoBlock() {
+
+    String str = "foo";
+    @NonNull String a;
+    // :: warning: (nulltest.redundant)
+    if (str == null) {
+      testAssert();
+    } else {
+      a = str;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testReturnIf() {
+
+    String str = "foo";
+    // :: warning: (nulltest.redundant)
+    if (str == null) {
+      testAssert();
+      return;
+    }
+
+    @NonNull String a = str;
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testReturnElse() {
+
+    String str = "foo";
+    // :: warning: (nulltest.redundant)
+    if (str != null) {
+      testAssert();
+    } else {
+      return;
+    }
+
+    @NonNull String a = str;
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testThrowIf() {
+
+    String str = "foo";
+    // :: warning: (nulltest.redundant)
+    if (str == null) {
+      testAssert();
+      throw new RuntimeException("foo");
+    }
+
+    @NonNull String a = str;
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testThrowElse() {
+
+    String str = "foo";
+    // :: warning: (nulltest.redundant)
+    if (str != null) {
+      testAssert();
+    } else {
+      throw new RuntimeException("foo");
+    }
+
+    @NonNull String a = str;
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testAssert() {
+
+    String str = "foo";
+    // :: warning: (nulltest.redundant)
+    assert str != null;
+
+    @NonNull String a = str;
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testWhile() {
+
+    String str = "foo";
+    // :: warning: (nulltest.redundant)
+    while (str != null) {
+      @NonNull String a = str;
+      break;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testIfInstanceOf() {
+
+    String str = "foo";
+    @NonNull String a;
+    if (str instanceof String) {
+      a = str;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testNew() {
+
+    String str = "foo";
+    @NonNull String a = str;
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+
+    String s2 = new String();
+    s2.toString();
+  }
+
+  public void testExit() {
+
+    String str = "foo";
+    // :: warning: (nulltest.redundant)
+    if (str == null) {
+      System.exit(0);
+    }
+
+    @NonNull String a = str;
+  }
+
+  void testMore() {
+    String str = null + " foo";
+    @NonNull String a = str;
+  }
+
+  void orderOfEvaluation() {
+    class MyClass {
+      @org.checkerframework.dataflow.qual.Pure
+      public boolean equals(@Nullable Object o) {
+        return o != null;
+      }
+
+      void test(@Nullable Object a, @Nullable Object b) {}
+    }
+    MyClass m = new MyClass();
+    m.equals(m = null);
+
+    MyClass n = new MyClass();
+    // :: error: (dereference.of.nullable)
+    n.test(n = null, n.toString()); // error
+
+    MyClass o = null;
+    // :: error: (dereference.of.nullable)
+    o.equals(o == new MyClass()); // error
+  }
+
+  void instanceOf(@Nullable Object o) {
+    if (o instanceof String) {
+      // cannot be null here
+      o.toString();
+      return;
+    }
+    // :: error: (dereference.of.nullable)
+    o.toString(); // error
+  }
+
+  public static void checkConditional1(@Nullable Object a) {
+    if (a == null) {
+    } else {
+      a.getClass(); // not an error
+    }
+  }
+
+  public static void checkConditional2(@Nullable Object a) {
+    if (a == null) {
+    } else if (a instanceof String) {
+    } else {
+      a.getClass(); // not an error
+    }
+  }
+
+  public static String spf(String format, @NonNull Object[] args) {
+    int current_arg = 0;
+    Object arg = args[current_arg];
+    if (false) {
+      return arg.toString(); // not an error
+    }
+    if (arg instanceof long[]) {
+      return "foo";
+    } else {
+      return arg.toString(); // still not an error
+    }
+  }
+
+  void empty_makes_no_change() {
+    String o1 = "not null!";
+    if (false) {
+      // empty branch
+    } else {
+      o1 = "still not null!";
+    }
+    System.out.println(o1.toString());
+  }
+
+  @org.checkerframework.dataflow.qual.Pure
+  public boolean equals(@Nullable Object o) {
+    if (!(o instanceof Integer)) {
+      return false;
+    }
+    @NonNull Object nno = o;
+    @NonNull Integer nni = (Integer) o;
+    return true;
+  }
+
+  void while_set_and_test(@Nullable String s) {
+    String line;
+    // imagine "s" is "reader.readLine()" (but avoid use of libraries in unit tests)
+    while ((line = s) != null) {
+      line.trim();
+    }
+  }
+
+  void equality_test(@Nullable String s) {
+    @NonNull String n = "m";
+    if (s == n) {
+      s.toString();
+    }
+  }
+
+  @Nullable Object returnNullable() {
+    return null;
+  }
+
+  void testNullableCall() {
+    if (returnNullable() != null) {
+      // :: error: (dereference.of.nullable)
+      returnNullable().toString(); // error
+    }
+  }
+
+  void nonNullArg(@NonNull Object arg) {
+    // empty body
+  }
+
+  void testNonNullArg(@Nullable Object arg) {
+    // :: error: (argument)
+    nonNullArg(arg); // error
+    nonNullArg(arg); // no error
+  }
+
+  void test() {
+    String[] s = null;
+    // :: error: (dereference.of.nullable)
+    for (int i = 0; i < s.length; ++i) { // error
+      String m = s[i]; // fine.. s cannot be null
+    }
+  }
+
+  private double @MonotonicNonNull []
+      intersect; // = null; TODO: do we want to allow assignments of null to MonotonicNonNull?
+
+  public void add_modified(double[] a, int count) {
+    // System.out.println ("common: " + ArraysMDE.toString (a));
+    // :: warning: (nulltest.redundant)
+    if (a == null) {
+      return;
+    } else if (intersect == null) {
+      intersect = a;
+      return;
+    }
+
+    double[] tmp = new double[intersect.length];
+  }
+}
diff --git a/checker/tests/nullness/FlowSelf.java b/checker/tests/nullness/FlowSelf.java
new file mode 100644
index 0000000..8a49f5b
--- /dev/null
+++ b/checker/tests/nullness/FlowSelf.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class FlowSelf {
+
+  void test(@Nullable String s) {
+
+    if (s == null) {
+      return;
+    }
+    // :: warning: (nulltest.redundant)
+    assert s != null;
+
+    s = s.substring(1);
+  }
+}
diff --git a/checker/tests/nullness/ForEachMin.java b/checker/tests/nullness/ForEachMin.java
new file mode 100644
index 0000000..de66b83
--- /dev/null
+++ b/checker/tests/nullness/ForEachMin.java
@@ -0,0 +1,21 @@
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+class MyTop {
+  List<String> children = new ArrayList<>();
+}
+
+abstract class PptRelationMin {
+  abstract MyTop getPpt();
+
+  void init_hierarchy_new() {
+    MyTop ppt = getPpt();
+
+    @NonNull Object o1 = ppt.children;
+
+    for (String rel : ppt.children) {}
+
+    @NonNull Object o2 = ppt.children;
+  }
+}
diff --git a/checker/tests/nullness/FullyQualifiedAnnotation.java b/checker/tests/nullness/FullyQualifiedAnnotation.java
new file mode 100644
index 0000000..fda3122
--- /dev/null
+++ b/checker/tests/nullness/FullyQualifiedAnnotation.java
@@ -0,0 +1,28 @@
+import java.util.Iterator;
+
+public class FullyQualifiedAnnotation {
+
+  void client1(Iterator i) {
+    @SuppressWarnings("nullness")
+    @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next();
+    handle2.toString();
+  }
+
+  void client2(Iterator i) {
+    @SuppressWarnings("nullness")
+    @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next();
+    handle2.toString();
+  }
+
+  void client3(Iterator<Object> i) {
+    @SuppressWarnings("nullness")
+    @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next();
+    handle2.toString();
+  }
+
+  void client4(Iterator<Object> i) {
+    @SuppressWarnings("nullness")
+    @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next();
+    handle2.toString();
+  }
+}
diff --git a/checker/tests/nullness/GeneralATFStore.java b/checker/tests/nullness/GeneralATFStore.java
new file mode 100644
index 0000000..fa09f95
--- /dev/null
+++ b/checker/tests/nullness/GeneralATFStore.java
@@ -0,0 +1,22 @@
+// Test case for a mysterious compilation failure.
+// The underlying reason was that the GeneralATF tried storing defaulted declaration annotations.
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@interface FailAnno {
+  String value();
+
+  boolean flag() default false;
+}
+
+class Fail {
+  @FailAnno(value = "Fail", flag = true)
+  String f = "fail";
+
+  Object x = Fail.class;
+}
diff --git a/checker/tests/nullness/GenericCast.java b/checker/tests/nullness/GenericCast.java
new file mode 100644
index 0000000..739703f
--- /dev/null
+++ b/checker/tests/nullness/GenericCast.java
@@ -0,0 +1,14 @@
+// @skip-test Fails, but commented out to avoid breaking the build
+public class GenericCast<T> {
+
+  @SuppressWarnings("unchecked")
+  T tObject = (T) new Object();
+
+  T field1 = tObject;
+
+  T field2;
+
+  GenericCast() {
+    field2 = tObject;
+  }
+}
diff --git a/checker/tests/nullness/GetConstantStr.java b/checker/tests/nullness/GetConstantStr.java
new file mode 100644
index 0000000..59a3a5d
--- /dev/null
+++ b/checker/tests/nullness/GetConstantStr.java
@@ -0,0 +1,6 @@
+public class GetConstantStr {
+  public static void get_constant_str(Object obj) {
+    // :: warning: (nulltest.redundant)
+    assert obj != null;
+  }
+}
diff --git a/checker/tests/nullness/GetInterfacesPurity.java b/checker/tests/nullness/GetInterfacesPurity.java
new file mode 100644
index 0000000..3d4e048
--- /dev/null
+++ b/checker/tests/nullness/GetInterfacesPurity.java
@@ -0,0 +1,11 @@
+import org.checkerframework.dataflow.qual.Pure;
+
+public class GetInterfacesPurity {
+
+  @Pure
+  public static boolean isSubtype(Class<?> sub, Class<?> sup) {
+    // :: error: (purity.not.deterministic.call)
+    Class<?>[] interfaces = sub.getInterfaces();
+    return interfaces.length == 0;
+  }
+}
diff --git a/checker/tests/nullness/GetPackage1.java b/checker/tests/nullness/GetPackage1.java
new file mode 100644
index 0000000..9ae68d3
--- /dev/null
+++ b/checker/tests/nullness/GetPackage1.java
@@ -0,0 +1,12 @@
+// Test case for interaction between ClassVal and getPackage()
+
+// @skip-test
+
+public class GetPackage {
+
+  void callGetPackage() {
+
+    @NonNull Package p1 = GetPackage.class.getPackage();
+    @NonNull Package p2 = java.util.List.class.getPackage();
+  }
+}
diff --git a/checker/tests/nullness/GetProperty.java b/checker/tests/nullness/GetProperty.java
new file mode 100644
index 0000000..c9feb74
--- /dev/null
+++ b/checker/tests/nullness/GetProperty.java
@@ -0,0 +1,25 @@
+import java.util.Properties;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class GetProperty {
+
+  @NonNull Object nno = new Object();
+
+  void m(Properties p) {
+
+    String s = "line.separator";
+
+    nno = System.getProperty("line.separator");
+    // :: error: (assignment)
+    nno = System.getProperty(s);
+    // :: error: (assignment)
+    nno = System.getProperty("not.a.builtin.property");
+
+    // :: error: (assignment)
+    nno = p.getProperty("line.separator");
+    // :: error: (assignment)
+    nno = p.getProperty(s);
+    // :: error: (assignment)
+    nno = p.getProperty("not.a.builtin.property");
+  }
+}
diff --git a/checker/tests/nullness/GetRefArg.java b/checker/tests/nullness/GetRefArg.java
new file mode 100644
index 0000000..7df75fa
--- /dev/null
+++ b/checker/tests/nullness/GetRefArg.java
@@ -0,0 +1,10 @@
+import java.lang.reflect.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class GetRefArg {
+  private void get_ref_arg(Constructor<?> constructor) throws Exception {
+    Object val = constructor.newInstance();
+    // :: warning: (nulltest.redundant)
+    assert val != null;
+  }
+}
diff --git a/checker/tests/nullness/HasInnerClass.java b/checker/tests/nullness/HasInnerClass.java
new file mode 100644
index 0000000..298ef0f
--- /dev/null
+++ b/checker/tests/nullness/HasInnerClass.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class HasInnerClass<E> {
+  public class InternalEdge {
+    public void m() {
+      HasInnerClass<?>.InternalEdge other = null;
+    }
+  }
+}
diff --git a/checker/tests/nullness/HierarchicalInit.java b/checker/tests/nullness/HierarchicalInit.java
new file mode 100644
index 0000000..6987dd2
--- /dev/null
+++ b/checker/tests/nullness/HierarchicalInit.java
@@ -0,0 +1,19 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class HierarchicalInit {
+
+  String a;
+
+  public HierarchicalInit() {
+    a = "";
+  }
+
+  public static class B extends HierarchicalInit {
+    String b;
+
+    public B() {
+      super();
+      b = "";
+    }
+  }
+}
diff --git a/checker/tests/nullness/ImplementInterface.java b/checker/tests/nullness/ImplementInterface.java
new file mode 100644
index 0000000..4554059
--- /dev/null
+++ b/checker/tests/nullness/ImplementInterface.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+interface TestInterface {
+  public char @Nullable [] getChars();
+}
+
+public class ImplementInterface implements TestInterface {
+  @Override
+  public char @Nullable [] getChars() {
+    return null;
+  }
+}
diff --git a/checker/tests/nullness/Imports1.java b/checker/tests/nullness/Imports1.java
new file mode 100644
index 0000000..d218d71
--- /dev/null
+++ b/checker/tests/nullness/Imports1.java
@@ -0,0 +1,7 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Imports1 {
+  void call() {
+    java.util.Arrays.asList("m", 1);
+  }
+}
diff --git a/checker/tests/nullness/Imports2.java b/checker/tests/nullness/Imports2.java
new file mode 100644
index 0000000..806ad16
--- /dev/null
+++ b/checker/tests/nullness/Imports2.java
@@ -0,0 +1,5 @@
+public class Imports2 {
+  void call() {
+    java.util.Arrays.asList("m", 1);
+  }
+}
diff --git a/checker/tests/nullness/InferListParam.java b/checker/tests/nullness/InferListParam.java
new file mode 100644
index 0000000..c1d8c40
--- /dev/null
+++ b/checker/tests/nullness/InferListParam.java
@@ -0,0 +1,10 @@
+import java.util.Collections;
+import java.util.List;
+
+public class InferListParam<V> {
+  List<Integer> fieldValues;
+
+  InferListParam() {
+    fieldValues = Collections.emptyList();
+  }
+}
diff --git a/checker/tests/nullness/InferNullType.java b/checker/tests/nullness/InferNullType.java
new file mode 100644
index 0000000..b4996a6
--- /dev/null
+++ b/checker/tests/nullness/InferNullType.java
@@ -0,0 +1,36 @@
+// Version of framework/tests/all-systems/InferNullType.java with expected Nullness Checker warnings
+public class InferNullType {
+
+  <T extends Object> T toInfer(T input) {
+    return input;
+  }
+
+  <T> T toInfer2(T input) {
+    return input;
+  }
+
+  <T, S extends T> T toInfer3(T input, S p2) {
+    return input;
+  }
+
+  <T extends Number, S extends T> T toInfer4(T input, S p2) {
+    return input;
+  }
+
+  void x() {
+    // :: error: (type.argument)
+    Object m = toInfer(null);
+    Object m2 = toInfer2(null);
+
+    Object m3 = toInfer3(null, null);
+    Object m4 = toInfer3(1, null);
+    Object m5 = toInfer3(null, 1);
+
+    // :: error: (type.argument)
+    Object m6 = toInfer4(null, null);
+    // :: error: (type.argument)
+    Object m7 = toInfer4(1, null);
+    // :: error: (type.argument)
+    Object m8 = toInfer4(null, 1);
+  }
+}
diff --git a/checker/tests/nullness/InferTypeArgsConditionalExpression.java b/checker/tests/nullness/InferTypeArgsConditionalExpression.java
new file mode 100644
index 0000000..478b97a
--- /dev/null
+++ b/checker/tests/nullness/InferTypeArgsConditionalExpression.java
@@ -0,0 +1,19 @@
+// Used to cause crash similar to the one reported in #579
+// https://github.com/typetools/checker-framework/issues/579
+// Issue 579 test case is in checker/tests/nullness/java8/Issue579.java
+// A similar test case appears in
+// checker-framework/framework/tests/all-systems/InferTypeArgsConditionalExpression.java
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class InferTypeArgsConditionalExpression {
+
+  public <T> void foo(Generic<T> real, Generic<? super T> other, boolean flag) {
+    // :: error: (type.argument)
+    bar(flag ? real : other);
+  }
+
+  <@NonNull Q extends @NonNull Object> void bar(Generic<? extends Q> parm) {}
+
+  interface Generic<F> {}
+}
diff --git a/checker/tests/nullness/InfiniteLoopIsSameType.java b/checker/tests/nullness/InfiniteLoopIsSameType.java
new file mode 100644
index 0000000..b37cbe1
--- /dev/null
+++ b/checker/tests/nullness/InfiniteLoopIsSameType.java
@@ -0,0 +1,28 @@
+// Test case for eisop issue 24:
+// https://github.com/eisop/checker-framework/issues/24
+
+// This test case is extracted from ConcurrentHashMap.java,
+// namely from the method `compute`.
+
+import org.checkerframework.checker.nullness.qual.PolyNull;
+
+public class InfiniteLoopIsSameType {
+  private interface Intf1<R> {
+    R apply();
+  }
+
+  public void compute(
+      // checker works fine if not annotated
+      Intf1<? extends @PolyNull Object> remappingFunction) {
+    // must assign null
+    Object nullval = null;
+    for (; ; ) {
+      // must assign to the null object
+      nullval = remappingFunction.apply();
+      // break must be in an if statement
+      if (true) {
+        break;
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/InitSuppressWarnings.java b/checker/tests/nullness/InitSuppressWarnings.java
new file mode 100644
index 0000000..6354ef8
--- /dev/null
+++ b/checker/tests/nullness/InitSuppressWarnings.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class InitSuppressWarnings {
+
+  private void init_vars(@UnderInitialization(Object.class) InitSuppressWarnings this) {
+    @SuppressWarnings({"nullness"})
+    @Initialized InitSuppressWarnings initializedThis = this;
+  }
+}
diff --git a/checker/tests/nullness/InitThrows.java b/checker/tests/nullness/InitThrows.java
new file mode 100644
index 0000000..c496279
--- /dev/null
+++ b/checker/tests/nullness/InitThrows.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class InitThrows {
+  private final Object o;
+
+  {
+    try {
+      o = new Object();
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/checker/tests/nullness/InitializationAssertionFailure.java b/checker/tests/nullness/InitializationAssertionFailure.java
new file mode 100644
index 0000000..a083b04
--- /dev/null
+++ b/checker/tests/nullness/InitializationAssertionFailure.java
@@ -0,0 +1,11 @@
+import java.io.*;
+
+// An assertion failure occurred after CFAbstractTransfer.initialStore inserted
+// a FieldAccess for serialVersionUID with the type InitializationAssertionFailure.
+
+public class InitializationAssertionFailure implements Serializable {
+
+  static final long serialVersionUID = 20030819L;
+
+  private InitializationAssertionFailure() {}
+}
diff --git a/checker/tests/nullness/InitializedField.java b/checker/tests/nullness/InitializedField.java
new file mode 100644
index 0000000..4fd0e2b
--- /dev/null
+++ b/checker/tests/nullness/InitializedField.java
@@ -0,0 +1,22 @@
+import java.util.Stack;
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.dataflow.qual.*;
+
+public final class InitializedField {
+  private Stack<Object> stack;
+
+  InitializedField() {
+    stack = new Stack<Object>();
+    iPeek();
+  }
+
+  @RequiresNonNull("stack")
+  public Object iPeek(@UnknownInitialization InitializedField this) {
+    return stack.peek();
+  }
+
+  public static void testJavaClass(InitializedField initField) {
+    initField.iPeek();
+  }
+}
diff --git a/checker/tests/nullness/Initializer.java b/checker/tests/nullness/Initializer.java
new file mode 100644
index 0000000..c72846d
--- /dev/null
+++ b/checker/tests/nullness/Initializer.java
@@ -0,0 +1,91 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.EnsuresQualifier;
+import org.checkerframework.framework.qual.EnsuresQualifierIf;
+
+public class Initializer {
+
+  public String a;
+  public String b = "abc";
+
+  // :: error: (assignment)
+  public String c = null;
+
+  public String d = ("");
+
+  public Initializer() {
+    // :: error: (assignment)
+    a = null;
+    a = "";
+    c = "";
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public Initializer(boolean foo) {}
+
+  public Initializer(int foo) {
+    a = "";
+    c = "";
+  }
+
+  public Initializer(float foo) {
+    setField();
+    c = "";
+  }
+
+  public Initializer(double foo) {
+    if (!setFieldMaybe()) {
+      a = "";
+    }
+    c = "";
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public Initializer(double foo, boolean t) {
+    if (!setFieldMaybe()) {
+      // on this path, 'a' is not initialized
+    }
+    c = "";
+  }
+
+  @EnsuresQualifier(expression = "a", qualifier = NonNull.class)
+  public void setField(@UnknownInitialization Initializer this) {
+    a = "";
+  }
+
+  @EnsuresQualifierIf(result = true, expression = "a", qualifier = NonNull.class)
+  public boolean setFieldMaybe(@UnknownInitialization Initializer this) {
+    a = "";
+    return true;
+  }
+
+  String f = "";
+
+  void t1(@UnknownInitialization Initializer this) {
+    // :: error: (dereference.of.nullable)
+    this.f.toString();
+  }
+
+  String fieldF = "";
+}
+
+class SubInitializer extends Initializer {
+
+  String f = "";
+
+  void subt1(@UnknownInitialization(Initializer.class) SubInitializer this) {
+    fieldF.toString();
+    super.f.toString();
+    // :: error: (dereference.of.nullable)
+    this.f.toString();
+  }
+
+  void subt2(@UnknownInitialization SubInitializer this) {
+    // :: error: (dereference.of.nullable)
+    fieldF.toString();
+    // :: error: (dereference.of.nullable)
+    super.f.toString();
+    // :: error: (dereference.of.nullable)
+    this.f.toString();
+  }
+}
diff --git a/checker/tests/nullness/InvariantTypes.java b/checker/tests/nullness/InvariantTypes.java
new file mode 100644
index 0000000..6b27f1b
--- /dev/null
+++ b/checker/tests/nullness/InvariantTypes.java
@@ -0,0 +1,30 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class InvariantTypes {
+  // The RHS is @NonNull [], but context decides to make it @Nullable
+  @Nullable Object[] noa = {"non-null!"};
+
+  // Type for array creation is propagated from LHS
+  @MonotonicNonNull Object[] f = new Object[5];
+
+  void testAsLocal() {
+    @MonotonicNonNull Object[] lo;
+    lo = new Object[5];
+    // :: error: (assignment)
+    lo[0] = null;
+    lo[0] = new Object();
+    // :: error: (dereference.of.nullable)
+    lo[1].toString();
+  }
+
+  // Type for array creation is propagated from LHS
+  @SuppressWarnings("invalid.polymorphic.qualifier.use")
+  @PolyNull Object[] po = new Object[5];
+
+  void testDecl(@MonotonicNonNull Object[] p) {}
+
+  void testCall() {
+    // Type for array creation is propaged from parameter type
+    testDecl(new Object[5]);
+  }
+}
diff --git a/checker/tests/nullness/IsEmptyPoll.java b/checker/tests/nullness/IsEmptyPoll.java
new file mode 100644
index 0000000..e1466a2
--- /dev/null
+++ b/checker/tests/nullness/IsEmptyPoll.java
@@ -0,0 +1,30 @@
+// Test case for Issue 399:
+// https://github.com/typetools/checker-framework/issues/399
+
+// @skip-test until the issue is fixed
+
+import java.util.ArrayList;
+import java.util.Queue;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public final class IsEmptyPoll extends ArrayList<String> {
+
+  void mNonNull(Queue<String> q) {
+    while (!q.isEmpty()) {
+      @NonNull String firstNode = q.poll();
+    }
+  }
+
+  void mNullable(Queue<@Nullable String> q) {
+    while (!q.isEmpty()) {
+      // :: error: (assignment)
+      @NonNull String firstNode = q.poll();
+    }
+  }
+
+  void mNoCheck(Queue<@Nullable String> q) {
+    // :: error: (assignment)
+    @NonNull String firstNode = q.poll();
+  }
+}
diff --git a/checker/tests/nullness/Issue1027.java b/checker/tests/nullness/Issue1027.java
new file mode 100644
index 0000000..48a445d
--- /dev/null
+++ b/checker/tests/nullness/Issue1027.java
@@ -0,0 +1,44 @@
+// Test case for Issue 1027:
+// https://github.com/typetools/checker-framework/issues/1027
+
+// Use  -J-XX:MaxJavaStackTraceDepth=1000000 as parameter
+// to javac to see a longer stacktrace.
+
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+
+public class Issue1027 {
+
+  // Stand-alone reproduction
+
+  class Repr<T> {
+    void bar(Function<T, String> p) {}
+  }
+
+  @SuppressWarnings("nullness")
+  Repr<@KeyFor("this") String> foo() {
+    return null;
+  }
+
+  void zoo(Issue1027 p) {
+    p.foo().bar(x -> "");
+  }
+
+  // Various longer versions that also used to give SOE
+
+  void foo(Map<String, String> arg) {
+    arg.keySet().stream().map(key -> key);
+  }
+
+  Stream<String> foo(Set<String> arg) {
+    return arg.stream().map(key -> key);
+  }
+
+  String foo(Stream<String> stream) {
+    return stream.map(key -> key).collect(Collectors.joining());
+  }
+}
diff --git a/checker/tests/nullness/Issue1046Java7.java b/checker/tests/nullness/Issue1046Java7.java
new file mode 100644
index 0000000..83a9088
--- /dev/null
+++ b/checker/tests/nullness/Issue1046Java7.java
@@ -0,0 +1,34 @@
+// Test case for Issue 1046:
+// https://github.com/typetools/checker-framework/issues/1046
+// Additional test case: checker/tests/nullness/java8/Issue1046.java
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1046Java7 {
+  interface MyInterface {}
+
+  class MyClass implements MyInterface {}
+
+  class Function<T> {}
+
+  abstract static class NotSubtype2 {
+    static void transform(Function<? super MyClass> q) {}
+
+    static void transform2(Function<? super @Nullable MyClass> q) {}
+
+    void test1(Function<? super MyInterface> p, Function<? super @Nullable MyInterface> p2) {
+      transform(p);
+      // :: error: (argument)
+      transform2(p);
+      transform(p2);
+      transform2(p2);
+    }
+
+    @Nullable Function<Object> NULL = null;
+
+    <T> void test2(@Nullable Function<? super @NonNull T> queue) {
+      Function<? super @NonNull T> x = (queue == null) ? NULL : queue;
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue1059.java b/checker/tests/nullness/Issue1059.java
new file mode 100644
index 0000000..1c2e7f4
--- /dev/null
+++ b/checker/tests/nullness/Issue1059.java
@@ -0,0 +1,22 @@
+// Test case for Issue 1059:
+// https://github.com/typetools/checker-framework/issues/1059
+
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1059 {
+  @Nullable Object f;
+
+  @EnsuresNonNull({"f"})
+  void foo() {
+    f = new Object();
+  }
+
+  void bar() {
+    switch (this.hashCode()) {
+      case 1:
+        foo();
+        Object dada = f.toString();
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue1096.java b/checker/tests/nullness/Issue1096.java
new file mode 100644
index 0000000..e60a1d9
--- /dev/null
+++ b/checker/tests/nullness/Issue1096.java
@@ -0,0 +1,63 @@
+// Test case for Issue 1096:
+// https://github.com/typetools/checker-framework/issues/1027
+
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+class PreCond {
+  Object f;
+  @Nullable Object g;
+
+  PreCond() {
+    f = new Object();
+    early();
+    g = new Object();
+    doNullable();
+  }
+
+  void earlyBad(@UnknownInitialization PreCond this) {
+    // :: error: (dereference.of.nullable)
+    f.toString();
+  }
+
+  @RequiresNonNull("this.f")
+  void early(@UnknownInitialization PreCond this) {
+    f.toString();
+  }
+
+  @RequiresNonNull("this.g")
+  void doNullable(@UnknownInitialization PreCond this) {
+    g.toString();
+  }
+
+  void foo(@UnknownInitialization PreCond this) {
+    // The receiver is not fully initialized, so raise an error.
+    // :: error: (contracts.precondition)
+    early();
+  }
+
+  void bar() {
+    // The receiver is initialized, so non-null field f is definitely non-null.
+    early();
+    // Nullable fields stay nullable
+    // :: error: (contracts.precondition)
+    doNullable();
+  }
+}
+
+class User {
+  void foo(@UnknownInitialization PreCond pc) {
+    // The receiver is not fully initialized, so raise an error.
+    // :: error: (contracts.precondition)
+    pc.early();
+  }
+
+  void bar(PreCond pc) {
+    // The receiver is initialized, so non-null field f is definitely non-null.
+    pc.early();
+    // Nullable fields stay nullable
+    // :: error: (contracts.precondition)
+    pc.doNullable();
+  }
+}
diff --git a/checker/tests/nullness/Issue1102.java b/checker/tests/nullness/Issue1102.java
new file mode 100644
index 0000000..80a727b
--- /dev/null
+++ b/checker/tests/nullness/Issue1102.java
@@ -0,0 +1,28 @@
+// Test case for Issue 1102:
+// https://github.com/typetools/checker-framework/issues/1102
+// Additional test in framework/tests/all-systems/Issue1102.java
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+interface Issue1102Itf {}
+
+class Issue1102Base {}
+
+class Issue1102Decl extends Issue1102Base {
+  static <S extends Object, T extends Issue1102Base & Issue1102Itf> Issue1102Decl newInstance(T s) {
+    return new Issue1102Decl();
+  }
+}
+
+class Issue1102Use<U extends Issue1102Base & Issue1102Itf> {
+  @SuppressWarnings("initialization.field.uninitialized")
+  U f;
+
+  @Nullable U g = null;
+
+  void bar() {
+    Issue1102Decl d = Issue1102Decl.newInstance(f);
+    // :: error: (type.argument)
+    d = Issue1102Decl.newInstance(g);
+  }
+}
diff --git a/checker/tests/nullness/Issue1147.java b/checker/tests/nullness/Issue1147.java
new file mode 100644
index 0000000..4992167
--- /dev/null
+++ b/checker/tests/nullness/Issue1147.java
@@ -0,0 +1,17 @@
+// Issue 1147 https://github.com/typetools/checker-framework/issues/1147
+
+import java.util.StringJoiner;
+
+public class Issue1147 {
+
+  public static void main(String[] args) {
+
+    StringJoiner sj = new StringJoiner(",");
+
+    sj.add("a");
+
+    sj.add(null);
+
+    System.out.println(sj);
+  }
+}
diff --git a/checker/tests/nullness/Issue1307.java b/checker/tests/nullness/Issue1307.java
new file mode 100644
index 0000000..55c26de
--- /dev/null
+++ b/checker/tests/nullness/Issue1307.java
@@ -0,0 +1,15 @@
+// Test case for part of issue 1307:
+// https://github.com/typetools/checker-framework/issues/1307
+
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.*;
+
+@DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.FIELD)
+@DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.PARAMETER)
+public class Issue1307 {
+  Object nullableField = null;
+
+  void perl(Integer a) {
+    a = null;
+  }
+}
diff --git a/checker/tests/nullness/Issue1406.java b/checker/tests/nullness/Issue1406.java
new file mode 100644
index 0000000..2da408d
--- /dev/null
+++ b/checker/tests/nullness/Issue1406.java
@@ -0,0 +1,37 @@
+// Test case for Issue 1406
+// https://github.com/typetools/checker-framework/issues/1406
+
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.dataflow.qual.Pure;
+
+@SuppressWarnings({"purity", "contracts.postcondition"}) // Only test parsing
+public class Issue1406 {
+
+  public static void main(String[] args) {}
+
+  @Pure
+  @EnsuresNonNull("myMethod(#1).get(0)")
+  List<String> myMethod(int arg) {
+    List<String> result = new ArrayList<>();
+    result.add("non-null value");
+    return result;
+  }
+
+  String client(int arg) {
+    return myMethod(arg).get(0);
+  }
+
+  @Pure
+  @EnsuresNonNull("myMethod2().get(0)")
+  List<String> myMethod2() {
+    List<String> result = new ArrayList<>();
+    result.add("non-null value");
+    return result;
+  }
+
+  String client2() {
+    return myMethod2().get(0);
+  }
+}
diff --git a/checker/tests/nullness/Issue1522.java b/checker/tests/nullness/Issue1522.java
new file mode 100644
index 0000000..fdabd0b
--- /dev/null
+++ b/checker/tests/nullness/Issue1522.java
@@ -0,0 +1,52 @@
+// Test case for Issue 1522
+// https://github.com/typetools/checker-framework/issues/1522
+
+import java.util.Vector;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1522 {
+  void copyInto(String p) {}
+
+  void bar() {
+    copyInto("Hi");
+  }
+
+  void copyVector(Vector<String> v, Integer[] intArray, String[] stringArray) {
+    // Java types aren't compatible
+    // :: error: (vector.copyinto)
+    v.copyInto(intArray);
+    v.copyInto(stringArray);
+  }
+
+  void copyStack(SubClassVector<String> v, Integer[] intArray, String[] stringArray) {
+    // Java types aren't compatible
+    // :: error: (vector.copyinto)
+    v.copyInto(intArray);
+    v.copyInto(stringArray);
+  }
+
+  void copyVectorErrors(Vector<@Nullable String> v, String[] stringArray) {
+    // :: error: (vector.copyinto)
+    v.copyInto(stringArray);
+  }
+
+  void copyStackErrors(SubClassVector<@Nullable String> v, String[] stringArray) {
+    // :: error: (vector.copyinto)
+    v.copyInto(stringArray);
+  }
+
+  void copyVectorNullable(Vector<@Nullable String> v, @Nullable String[] stringArray) {
+    v.copyInto(stringArray);
+  }
+
+  void copyStackNullable(SubClassVector<@Nullable String> v, @Nullable String[] stringArray) {
+    v.copyInto(stringArray);
+  }
+
+  static class SubClassVector<T> extends Vector<T> {
+    @Override
+    public synchronized void copyInto(@Nullable Object[] anArray) {
+      super.copyInto(anArray);
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue1555.java b/checker/tests/nullness/Issue1555.java
new file mode 100644
index 0000000..7c355c0
--- /dev/null
+++ b/checker/tests/nullness/Issue1555.java
@@ -0,0 +1,14 @@
+// Test case for Issue 1555
+// https://github.com/typetools/checker-framework/issues/1555
+
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.util.NullnessUtil;
+
+public class Issue1555 {
+
+  private @MonotonicNonNull String x;
+
+  String test() {
+    return NullnessUtil.castNonNull(x);
+  }
+}
diff --git a/checker/tests/nullness/Issue1590.java b/checker/tests/nullness/Issue1590.java
new file mode 100644
index 0000000..d4186ef
--- /dev/null
+++ b/checker/tests/nullness/Issue1590.java
@@ -0,0 +1,14 @@
+@SuppressWarnings("initialization")
+public class Issue1590 {
+
+  private String a;
+
+  public Issue1590() {
+    // :: error: method.invocation
+    init();
+  }
+
+  public void init() {
+    a = "gude";
+  }
+}
diff --git a/checker/tests/nullness/Issue1590a.java b/checker/tests/nullness/Issue1590a.java
new file mode 100644
index 0000000..bffd160
--- /dev/null
+++ b/checker/tests/nullness/Issue1590a.java
@@ -0,0 +1,16 @@
+// This test case shows that @SuppressWarnings("initialization.") has no effect
+@SuppressWarnings("initialization.")
+public class Issue1590a {
+
+  private String a;
+
+  // :: error: (initialization.fields.uninitialized)
+  public Issue1590a() {
+    // :: error: (method.invocation)
+    init();
+  }
+
+  public void init() {
+    a = "gude";
+  }
+}
diff --git a/checker/tests/nullness/Issue160.java b/checker/tests/nullness/Issue160.java
new file mode 100644
index 0000000..9832262
--- /dev/null
+++ b/checker/tests/nullness/Issue160.java
@@ -0,0 +1,58 @@
+public class Issue160 {
+  public static void t1() {
+    String s = null;
+    if (s != null) {
+    } else {
+      return;
+    }
+    System.out.println(s.toString());
+  }
+
+  public static void t2() {
+    String s = null;
+    if (s != null) {
+    } else {
+      throw new RuntimeException();
+    }
+    System.out.println(s.toString());
+  }
+
+  public static void t3() {
+    String s = null;
+    if (s != null) {
+    } else {
+      System.exit(0);
+    }
+    System.out.println(s.toString());
+  }
+
+  public static void t1b() {
+    String s = null;
+    if (s == null) {
+    } else {
+      return;
+    }
+    // :: error: (dereference.of.nullable)
+    System.out.println(s.toString());
+  }
+
+  public static void t2b() {
+    String s = null;
+    if (s == null) {
+    } else {
+      throw new RuntimeException();
+    }
+    // :: error: (dereference.of.nullable)
+    System.out.println(s.toString());
+  }
+
+  public static void t3b() {
+    String s = null;
+    if (s == null) {
+    } else {
+      System.exit(0);
+    }
+    // :: error: (dereference.of.nullable)
+    System.out.println(s.toString());
+  }
+}
diff --git a/checker/tests/nullness/Issue1628.java b/checker/tests/nullness/Issue1628.java
new file mode 100644
index 0000000..5dc324c
--- /dev/null
+++ b/checker/tests/nullness/Issue1628.java
@@ -0,0 +1,21 @@
+// Test case for Issue 1628
+// https://github.com/typetools/checker-framework/issues/1628
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class Issue1628<V extends Comparable<? super V>> implements Issue1628R<V> {
+
+  public boolean isEmpty() {
+    return false;
+  }
+
+  public boolean equals(@Nullable Object o) {
+    return (o instanceof Issue1628R) && ((Issue1628R) o).isEmpty();
+  }
+}
+
+interface Issue1628R<V extends Comparable<? super V>> {
+  @Pure
+  boolean isEmpty();
+}
diff --git a/checker/tests/nullness/Issue1712.java b/checker/tests/nullness/Issue1712.java
new file mode 100644
index 0000000..e5cddb4
--- /dev/null
+++ b/checker/tests/nullness/Issue1712.java
@@ -0,0 +1,26 @@
+// Test case for Issue 1712
+// https://github.com/typetools/checker-framework/issues/1712
+
+abstract class Issue1712 {
+
+  abstract <T> T match(
+      Function<? super SubclassA, T> visitA, Function<? super SubclassB, T> visitB);
+
+  class SubclassA extends Issue1712 {
+    @Override
+    <T> T match(Function<? super SubclassA, T> visitA, Function<? super SubclassB, T> visitB) {
+      return visitA.apply(this); // line 11
+    }
+  }
+
+  class SubclassB extends Issue1712 {
+    @Override
+    <T> T match(Function<? super SubclassA, T> visitA, Function<? super SubclassB, T> visitB) {
+      return visitB.apply(this); // line 18
+    }
+  }
+
+  abstract class Function<T1, T2> {
+    abstract T2 apply(T1 arg);
+  }
+}
diff --git a/checker/tests/nullness/Issue1797.java b/checker/tests/nullness/Issue1797.java
new file mode 100644
index 0000000..92e3179
--- /dev/null
+++ b/checker/tests/nullness/Issue1797.java
@@ -0,0 +1,258 @@
+// Test case for Issue 1797:
+// https://github.com/typetools/checker-framework/issues/1797
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1797 {
+  void fooReturn(@Nullable Object o) {
+    try {
+      return;
+    } finally {
+      if (o != null) {
+        o.toString();
+      }
+    }
+  }
+
+  void fooWhileReturn(@Nullable Object o) {
+    while (this.hashCode() < 5) {
+      try {
+        return;
+      } finally {
+        if (o != null) {
+          o.toString();
+        }
+      }
+    }
+  }
+
+  void fooReturnNested(@Nullable Object o) {
+    while (this.hashCode() < 10) {
+      while (this.hashCode() < 5) {
+        try {
+          return;
+        } finally {
+          if (o != null) {
+            o.toString();
+          }
+          // go to exit, not either loop
+        }
+      }
+    }
+  }
+
+  void fooBreak(@Nullable Object o) {
+    while (this.hashCode() < 5) {
+      try {
+        break;
+      } finally {
+        if (o != null) {
+          o.toString();
+        }
+      }
+    }
+  }
+
+  void fooBreakLabel(@Nullable Object o) {
+    outer:
+    while (this.hashCode() < 10) {
+      while (this.hashCode() < 5) {
+        try {
+          break outer;
+        } finally {
+          if (o != null) {
+            o.toString();
+          }
+          // continue after outer
+        }
+      }
+    }
+  }
+
+  void fooBreakLabel2(@Nullable Object o) {
+    outer:
+    while (this.hashCode() < 10) {
+      try {
+        inner:
+        while (this.hashCode() < 5) {
+          if (this.hashCode() < 2) {
+            break outer;
+            // go to finally
+          } else {
+            break inner;
+            // do not go to finally
+          }
+        }
+      } finally {
+        if (o != null) {
+          o.toString();
+        }
+        // continue either at outer or after outer
+      }
+    }
+  }
+
+  void fooBreakNoLabel(@Nullable Object o) {
+    outer:
+    while (this.hashCode() < 10) {
+      inner:
+      while (this.hashCode() < 5) {
+        try {
+          break;
+        } finally {
+          if (o != null) {
+            o.toString();
+          }
+          // continue at outer
+        }
+      }
+    }
+  }
+
+  void fooContinue(@Nullable Object o) {
+    while (this.hashCode() < 5) {
+      try {
+        continue;
+      } finally {
+        if (o != null) {
+          o.toString();
+        }
+      }
+    }
+  }
+
+  void fooContinueLabel(@Nullable Object o) {
+    outer:
+    while (this.hashCode() < 10) {
+      while (this.hashCode() < 5) {
+        try {
+          continue outer;
+        } finally {
+          if (o != null) {
+            o.toString();
+          }
+        }
+      }
+    }
+  }
+
+  void fooSwitch(@Nullable Object o) {
+    switch (this.hashCode()) {
+      case 1:
+        try {
+          break;
+        } finally {
+          if (o != null) {
+            o.toString();
+          }
+        }
+      default:
+    }
+  }
+
+  // A few tests to make sure also return with expression works.
+
+  int barReturn(@Nullable Object o) {
+    try {
+      return 5;
+    } finally {
+      if (o != null) {
+        o.toString();
+      }
+    }
+  }
+
+  int barReturnInFinally(@Nullable Object o) {
+    try {
+      return 5;
+    } finally {
+      if (o != null) {
+        o.toString();
+      }
+      return 10;
+    }
+  }
+
+  int barReturnNested(@Nullable Object o) {
+    while (this.hashCode() < 10) {
+      while (this.hashCode() < 5) {
+        try {
+          return 5;
+        } finally {
+          if (o != null) {
+            o.toString();
+          }
+          // goes to return 5, not either loop!
+        }
+      }
+    }
+    return 10;
+  }
+
+  @FunctionalInterface
+  interface NullableParamFunction {
+    String takeVal(@Nullable Object x);
+  }
+
+  void testLambda() {
+    NullableParamFunction n1 = (@Nullable Object x) -> (x == null) ? "null" : x.toString();
+    try {
+      NullableParamFunction n2 = (@Nullable Object x) -> (x == null) ? "null" : x.toString();
+    } finally {
+      NullableParamFunction n3 = (x) -> (x == null) ? "null" : x.toString();
+    }
+  }
+
+  boolean nestedCFGConstructionTest(@Nullable Object o) {
+    boolean result = true;
+    java.io.BufferedWriter out = null;
+    try {
+      try {
+      } finally {
+        out = new java.io.BufferedWriter(new java.io.OutputStreamWriter(System.err));
+      }
+      if (o != null) {
+        o.toString();
+        out.write(' ');
+      }
+    } catch (Exception e) {
+    } finally {
+    }
+    return result;
+  }
+
+  void nestedTryFinally() {
+    try {
+      try {
+      } finally {
+      }
+    } finally {
+    }
+  }
+
+  boolean nestedCFGConstructionTest2() throws java.io.IOException {
+    java.io.BufferedWriter out =
+        new java.io.BufferedWriter(new java.io.OutputStreamWriter(System.err));
+    try {
+      try {
+        return true;
+      } finally {
+      }
+    } finally {
+      out.write(' ');
+      out.close();
+    }
+  }
+
+  void nestedTryFinally2(java.io.BufferedWriter out) throws java.io.IOException {
+    try {
+      try {
+        return;
+      } finally {
+      }
+    } finally {
+      out.write(' ');
+      out.close();
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue1847.java b/checker/tests/nullness/Issue1847.java
new file mode 100644
index 0000000..50e2b5a
--- /dev/null
+++ b/checker/tests/nullness/Issue1847.java
@@ -0,0 +1,30 @@
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.function.Function;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+
+public class Issue1847 {
+  final Map<String, String> map = new HashMap<>();
+
+  public void test() {
+    // Should give null error here:
+    // :: error: (dereference.of.nullable)
+    withLookup((String myVar) -> map.get(myVar).length());
+    for (Iterator<Map.Entry<@KeyFor("map") String, String>> iterator = map.entrySet().iterator();
+        iterator.hasNext(); ) {
+      Map.Entry<@KeyFor("map") String, String> entry = iterator.next();
+      // Problem is that myVar gets inferred as @KeyFor("map") here,
+      // and this variable is not distinguished from the lambda variables of the same name,
+      // even though their scopes do not overlap and they are different variables.
+      // Change this variable name to myVar2 and you will see the null errors on the lambdas:
+      String myVar = entry.getKey();
+    }
+
+    // Should also give null error here:
+    // :: error: (dereference.of.nullable)
+    withLookup(myVar -> map.get(myVar).length());
+  }
+
+  public void withLookup(Function<String, Integer> getFromMap) {}
+}
diff --git a/checker/tests/nullness/Issue1847B.java b/checker/tests/nullness/Issue1847B.java
new file mode 100644
index 0000000..ca198ca
--- /dev/null
+++ b/checker/tests/nullness/Issue1847B.java
@@ -0,0 +1,19 @@
+import java.util.function.Function;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1847B {
+  public void test1() {
+    // :: error: (dereference.of.nullable)
+    Function<@Nullable String, String> f1 = (@Nullable String myVar) -> myVar.toString();
+    {
+      String myVar = "hello";
+    }
+    // :: error: (dereference.of.nullable)
+    Function<@Nullable String, String> f2 = (@Nullable String myVar) -> myVar.toString();
+  }
+
+  public void test2() {
+    // :: error: (dereference.of.nullable)
+    Function<String, String> f1 = (@Nullable String myVar) -> myVar.toString();
+  }
+}
diff --git a/checker/tests/nullness/Issue1922.java b/checker/tests/nullness/Issue1922.java
new file mode 100644
index 0000000..40269e5
--- /dev/null
+++ b/checker/tests/nullness/Issue1922.java
@@ -0,0 +1,32 @@
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1922 {
+  // A method to find a K in the collection and return it, or return null.
+  public static <K> @Nullable K findKey(Collection<@NonNull K> keys, Object target) {
+    for (K key : keys) {
+      if (target.equals(key)) {
+        return key;
+      }
+    }
+    return null;
+  }
+
+  // Find a key in a map and return String version of its value.
+  public static String findKeyAndFetchString(Map<String, Object> someMap) {
+    // :: error: (type.argument)
+    @Nullable @KeyFor("someMap") String myKey = Issue1922.<@KeyFor("someMap") String>findKey(someMap.keySet(), "Foo");
+
+    // :: error: (argument)
+    Object value = someMap.get(myKey);
+    return value.toString();
+  }
+
+  public static void main(String[] args) {
+    findKeyAndFetchString(new HashMap<>());
+  }
+}
diff --git a/checker/tests/nullness/Issue1949.java b/checker/tests/nullness/Issue1949.java
new file mode 100644
index 0000000..0bd3216
--- /dev/null
+++ b/checker/tests/nullness/Issue1949.java
@@ -0,0 +1,21 @@
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue1949 {
+  public interface Base<R> {}
+
+  public interface Child<R> extends Base<@Nullable R> {}
+
+  public abstract static class BaseClass<R> implements Child<R> {
+    abstract List<Child<R>> foo();
+  }
+
+  public static class ChildClass extends BaseClass<String> {
+
+    @Override
+    public List<Child<String>> foo() {
+      return new ArrayList<>();
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue1981.java b/checker/tests/nullness/Issue1981.java
new file mode 100644
index 0000000..bd7e1f4
--- /dev/null
+++ b/checker/tests/nullness/Issue1981.java
@@ -0,0 +1,21 @@
+// Test case for Issue 1981:
+// https://github.com/typetools/checker-framework/issues/1981
+
+import java.util.List;
+
+public class Issue1981 {
+
+  void test(List<Integer> ids) {
+    for (List<Integer> l : func2(func1(ids))) {}
+  }
+
+  static <E extends Comparable<? super E>> List<E> func1(Iterable<? extends E> elements) {
+    // :: error: (return)
+    return null;
+  }
+
+  static <T> List<List<T>> func2(List<T> list) {
+    // :: error: (return)
+    return null;
+  }
+}
diff --git a/checker/tests/nullness/Issue1983.java b/checker/tests/nullness/Issue1983.java
new file mode 100644
index 0000000..038cccb
--- /dev/null
+++ b/checker/tests/nullness/Issue1983.java
@@ -0,0 +1,39 @@
+// Test case for Issue 1983:
+// https://github.com/typetools/checker-framework/issues/1983
+
+import java.util.List;
+import java.util.function.Function;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1983 {
+
+  @SuppressWarnings("initialization.field.uninitialized")
+  Converter<String> converter;
+
+  void test(List<Object[]> params) {
+    func1(transform(params, p -> of(converter.as((String) p[0]))));
+  }
+
+  static class Converter<T> {
+
+    @SuppressWarnings("nullness")
+    public Converter<T> as(@Nullable T value) {
+      return null;
+    }
+  }
+
+  @SuppressWarnings("nullness")
+  static <T> List<T> of(T t) {
+    return null;
+  }
+
+  @SuppressWarnings("nullness")
+  <V> V func1(List<List<Converter<?>>> bulkParameterValues) {
+    return null;
+  }
+
+  @SuppressWarnings("nullness")
+  static <F, T> List<T> transform(List<F> fromList, Function<? super F, ? extends T> function) {
+    return null;
+  }
+}
diff --git a/checker/tests/nullness/Issue2013.java b/checker/tests/nullness/Issue2013.java
new file mode 100644
index 0000000..cc61d55
--- /dev/null
+++ b/checker/tests/nullness/Issue2013.java
@@ -0,0 +1,108 @@
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+import org.checkerframework.dataflow.qual.Pure;
+
+// @skip-test
+public class Issue2013 {
+  static class Super {
+    private @Nullable String name = null;
+
+    @EnsuresNonNull("name()")
+    // :: error: (contracts.postcondition)
+    void ensureNameNonNull() {
+      name = "name";
+    }
+
+    @RequiresNonNull("name()")
+    void requiresNameNonNull() {
+      name().equals("name");
+    }
+
+    @Pure
+    @Nullable String name() {
+      return name;
+    }
+  }
+
+  static class Sub extends Super {
+    @Nullable String subname = null;
+
+    @Override
+    // :: error: (contracts.postcondition)
+    void ensureNameNonNull() {
+      super.ensureNameNonNull();
+      subname = "Sub";
+    }
+
+    public static boolean flag;
+
+    @Override
+    @RequiresNonNull("name()")
+    void requiresNameNonNull() {
+      if (flag) {
+        name().toString();
+      } else {
+        super.requiresNameNonNull();
+      }
+    }
+
+    @Override
+    @Nullable String name() {
+      return subname;
+    }
+
+    void use() {
+      if (super.name() != null) {
+        // :: error: (contracts.precondition)
+        requiresNameNonNull();
+      }
+
+      if (this.name() != null) {
+        requiresNameNonNull();
+      }
+
+      if (super.name() != null) {
+        // :: error: (contracts.precondition)
+        super.requiresNameNonNull();
+      }
+
+      if (this.name() != null) {
+        super.requiresNameNonNull();
+      }
+
+      super.ensureNameNonNull();
+      // :: error: (contracts.precondition)
+      requiresNameNonNull();
+
+      super.ensureNameNonNull();
+      // :: error: (contracts.precondition)
+      super.requiresNameNonNull();
+
+      ensureNameNonNull();
+      super.requiresNameNonNull();
+
+      ensureNameNonNull();
+      requiresNameNonNull();
+    }
+  }
+
+  void method(Super superObj) {
+    if (superObj.name() != null) {
+      superObj.requiresNameNonNull();
+    }
+
+    superObj.ensureNameNonNull();
+    superObj.requiresNameNonNull();
+  }
+
+  void method2(Sub subObj) {
+    if (subObj.name() != null) {
+      subObj.requiresNameNonNull();
+    }
+
+    if (subObj.name() != null) {
+      subObj.requiresNameNonNull();
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue2031.java b/checker/tests/nullness/Issue2031.java
new file mode 100644
index 0000000..1ea36ed
--- /dev/null
+++ b/checker/tests/nullness/Issue2031.java
@@ -0,0 +1,42 @@
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue2031 {
+  public interface InterfaceA<A> {}
+
+  public interface InterfaceB<B> {}
+
+  abstract static class OperatorSection<C extends InterfaceA<C> & InterfaceB<C>> {
+    C makeExpression(Map<String, C> expressions) {
+      @Nullable C e = expressions.get("");
+      if (e != null) {
+        return e;
+      } else {
+        throw new RuntimeException("");
+      }
+    }
+  }
+
+  static class RecursiveTypes {
+    public interface A<EXPRESSION> {}
+
+    public interface B<EXPRESSION> {}
+
+    abstract static class OperatorSection<EXPRESSION extends A<EXPRESSION> & B<EXPRESSION>> {
+      abstract EXPRESSION makeExpression(Map<String, EXPRESSION> expressions);
+    }
+
+    static class BinaryOperatorSection<EXPRESSION extends A<EXPRESSION> & B<EXPRESSION>>
+        extends OperatorSection<EXPRESSION> {
+      @Override
+      EXPRESSION makeExpression(Map<String, EXPRESSION> expressions) {
+        @Nullable EXPRESSION e = expressions.get("");
+        if (e != null) {
+          return e;
+        } else {
+          throw new RuntimeException("");
+        }
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue2048.java b/checker/tests/nullness/Issue2048.java
new file mode 100644
index 0000000..115d5e9
--- /dev/null
+++ b/checker/tests/nullness/Issue2048.java
@@ -0,0 +1,23 @@
+// Test case for Issue #2048:
+// https://github.com/typetools/checker-framework/issues/2048
+//
+// There are two versions:
+// framework/tests/all-systems
+// checker/tests/nullness
+
+public class Issue2048 {
+  interface Foo {}
+
+  interface Fooer<R extends Foo> {}
+
+  class UseNbl<T> {
+    // T by default is @Nullable and therefore doesn't
+    // fulfill the bound of R.
+    // :: error: (type.argument)
+    void foo(Fooer<? extends T> fooer) {}
+  }
+
+  class UseNN<T extends Object> {
+    void foo(Fooer<? extends T> fooer) {}
+  }
+}
diff --git a/checker/tests/nullness/Issue2052.java b/checker/tests/nullness/Issue2052.java
new file mode 100644
index 0000000..01b01cb
--- /dev/null
+++ b/checker/tests/nullness/Issue2052.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+
+public class Issue2052 {
+  public static class ParentW<S> {
+    protected final String field;
+
+    public ParentW() {
+      // Initializing "field" at the declaration, did not trigger the bug.
+      field = "";
+    }
+  }
+
+  public static class ChildW extends ParentW<String> {
+    public String getField(@UnknownInitialization(ParentW.class) ChildW this) {
+      return this.field;
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue2171.java b/checker/tests/nullness/Issue2171.java
new file mode 100644
index 0000000..481b013
--- /dev/null
+++ b/checker/tests/nullness/Issue2171.java
@@ -0,0 +1,35 @@
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue2171 {
+  static void varArgsMethod(@PolyNull Object... args) {}
+
+  static void callToVarArgsObject(@PolyNull Object pn, @NonNull Object nn, @Nullable Object nble) {
+    varArgsMethod(nble, nble);
+    varArgsMethod(nble, nn);
+    varArgsMethod(nble, pn);
+    varArgsMethod(nn, nble);
+    varArgsMethod(nn, nn);
+    varArgsMethod(nn, pn);
+    varArgsMethod(pn, nble);
+    varArgsMethod(pn, nn);
+    varArgsMethod(pn, pn);
+  }
+
+  @SuppressWarnings("unchecked")
+  static void genVarArgsMethod(List<? extends @PolyNull Object>... args) {}
+
+  @SuppressWarnings("unchecked")
+  static void genCallToVarArgsObject(
+      List<@PolyNull Object> pn, List<@NonNull Object> nn, List<@Nullable Object> nble) {
+    genVarArgsMethod(nble, nble);
+    genVarArgsMethod(nble, nn);
+    genVarArgsMethod(nble, pn);
+    genVarArgsMethod(nn, nble);
+    genVarArgsMethod(nn, nn);
+    genVarArgsMethod(nn, pn);
+    genVarArgsMethod(pn, nble);
+    genVarArgsMethod(pn, nn);
+    genVarArgsMethod(pn, pn);
+  }
+}
diff --git a/checker/tests/nullness/Issue2247.java b/checker/tests/nullness/Issue2247.java
new file mode 100644
index 0000000..a402769
--- /dev/null
+++ b/checker/tests/nullness/Issue2247.java
@@ -0,0 +1,41 @@
+// This is a test case for issue 2247:
+// https://github.com/typetools/checker-framework/issues/2247
+
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue2247 {
+
+  static @NonNull class DeclaredClass {}
+
+  class ValidUseType {
+
+    // :: error: (annotations.on.use)
+    void test1(@Nullable DeclaredClass object) {}
+
+    // :: error: (annotations.on.use)
+    @Nullable DeclaredClass test2() {
+      return null;
+    }
+
+    // :: error: (annotations.on.use)
+    void test3(List<@Nullable DeclaredClass> param) {
+      @Nullable DeclaredClass object = null;
+      // :: error: (annotations.on.use)
+      @Nullable DeclaredClass[] array = null;
+    }
+
+    // :: error: (annotations.on.use)
+    <T extends @Nullable DeclaredClass> void test4(@NonNull T t) {}
+
+    void test5(Map<String, DeclaredClass> map) {
+      @Nullable DeclaredClass value = map.get("somekey");
+      System.out.println(value);
+      if (value != null) {
+        @NonNull DeclaredClass nonnull = value;
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue2407.java b/checker/tests/nullness/Issue2407.java
new file mode 100644
index 0000000..45f43c8
--- /dev/null
+++ b/checker/tests/nullness/Issue2407.java
@@ -0,0 +1,23 @@
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public class Issue2407 {
+
+  @RequiresNonNull("#1")
+  void setMessage(String message) {}
+
+  @EnsuresNonNull("1")
+  // :: error: (flowexpr.parse.error)
+  void method() {}
+
+  @EnsuresNonNullIf(expression = "1", result = true)
+  // :: error: (flowexpr.parse.error)
+  void method2() {}
+
+  void main() {
+    Issue2407 object = new Issue2407();
+    // :: error: (contracts.precondition)
+    object.setMessage(new Object() + "bar");
+  }
+}
diff --git a/checker/tests/nullness/Issue2432.java b/checker/tests/nullness/Issue2432.java
new file mode 100644
index 0000000..976f40a
--- /dev/null
+++ b/checker/tests/nullness/Issue2432.java
@@ -0,0 +1,119 @@
+// Test case for issue 2432:
+// https://github.com/typetools/checker-framework/issues/2432
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.PolyNull;
+
+public class Issue2432 {
+
+  void jdkAnnotation(List<@PolyNull Object> nl, @Nullable Object no, @PolyNull Object po) {
+    // :: error: (argument)
+    nl.add(null);
+    // :: error: (argument)
+    nl.add(no);
+    // OK
+    nl.add(po);
+  }
+
+  // receiver's poly annotations in declaration are different from the ones in invocation
+  void polyReceiverType(
+      TypeArgClass<@PolyNull Object> tc,
+      @NonNull Object nno,
+      @Nullable Object no,
+      @PolyNull Object po) {
+    // :: error: (argument)
+    Object object = tc.echo(no);
+    // :: error: (assignment)
+    nno = tc.echo(po);
+    // No error. Return value remains @PolyNull.
+    // Note po's @PolyNull is unsubstitutable (from parameter but not from declaration)
+    po = tc.echo(po);
+  }
+
+  // the following two methods tests pesudo assignment of arguments with poly annotation
+  void polyAssignment(TypeArgClass<@NonNull Object> nnc, @PolyNull Object po) {
+    // :: error: (argument)
+    nnc.echo(po);
+  }
+
+  void polyAssignment2(TypeArgClass<@Nullable Object> nc, @PolyNull Object po) {
+    // No error
+    nc.echo(po);
+    // :: error: (assignment)
+    po = nc.echo(po);
+  }
+
+  // a "foo function" with 2 poly annotations, where one of them appears in type argument
+  // purpose: test invocation without using explicit receiver
+  @PolyNull Object foo2PolyTypeArg(
+      TypeArgClass<@PolyNull Object> pc, @PolyNull Object po, @NonNull Object nno) {
+    return pc.add(nno, po);
+  }
+
+  // lub tests without receiver
+  // lub combination: (@Nullable, @Nullable) = @Nullable
+  void lubWithTypeArgNoReceiver1(TypeArgClass<@Nullable Object> nc, @Nullable Object no) {
+    @NonNull Object nno = new Object();
+    // No error
+    foo2PolyTypeArg(nc, no, nno);
+  }
+
+  // lub combination: (@NonNull, @NonNull) = @NonNull
+  void lubWithTypeArgNoReceiver2(TypeArgClass<@NonNull Object> nnc, @NonNull Object nno) {
+    // No error
+    foo2PolyTypeArg(nnc, nno, nno);
+  }
+
+  // lub combination: (@Nullable, @NonNull) = @Nullable
+  void lubWithTypeArgNoReceiver3(TypeArgClass<@Nullable Object> nc, @NonNull Object nno) {
+    // No error
+    foo2PolyTypeArg(nc, nno, nno);
+  }
+
+  // lub combination: (@NonNull, @Nullable) = @Nullable
+  void lubWithTypeArgNoReceiver4(TypeArgClass<@NonNull Object> nnc, @Nullable Object no) {
+    // :: error: (argument)
+    foo2PolyTypeArg(nnc, no, new Object());
+  }
+
+  // lub test with receiver
+  // T dummy in tripleAdd is to ensure poly annotations from declaration is handled separately
+  void lubWithReceiver(
+      TypeArgClass<@PolyNull Object> pc, @Nullable Object no, @NonNull Object nno) {
+    // :: error: (argument)
+    pc.tripleAdd(no, nno, no);
+    // No error
+    pc.tripleAdd(no, nno, nno);
+  }
+
+  // ensure poly annotations from declaration is handled separately from poly from other context
+  void declarationPolyInParameter(
+      TypeArgClass<@PolyNull Object> pc, @Nullable Object no, @NonNull Object nno) {
+    // No error
+    pc.echo(nno, no);
+
+    // the invocation is valid, while the assignment is not
+    // :: error: (assignment)
+    @NonNull Object nonnull = pc.echo(nno, no);
+  }
+
+  private class TypeArgClass<T> {
+    @PolyNull Object add(@PolyNull Object obj, T dummy) {
+      return obj;
+    }
+
+    @PolyNull Object tripleAdd(@PolyNull Object o1, @PolyNull Object o2, T dummy) {
+      return o1;
+    }
+
+    T echo(T obj) {
+      return obj;
+    }
+
+    T echo(T obj, @PolyNull Object dummy) {
+      return obj;
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue2432b.java b/checker/tests/nullness/Issue2432b.java
new file mode 100644
index 0000000..de24310
--- /dev/null
+++ b/checker/tests/nullness/Issue2432b.java
@@ -0,0 +1,42 @@
+// Ensure correct handling of type parameters and arrays.
+// https://github.com/typetools/checker-framework/issues/2432
+
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.PolyNull;
+
+public class Issue2432b {
+  void objectAsTypeArg() {
+    List<Object> objs = new ArrayList<>();
+    // no error
+    Object[] objarray = objs.toArray();
+  }
+
+  void myClassAsTypeArg() {
+    MyClass<Object> objs = new MyClass<>();
+    Object[] objarray = objs.toArray();
+    // no error
+    Object[] objarray2 = objs.toArrayPoly();
+  }
+
+  void stringAsTypeArg() {
+    List<String> strs = new ArrayList<>();
+    Object[] strarray = strs.toArray();
+  }
+
+  void listAsTypeArg() {
+    List<List> lists = new ArrayList<>();
+    Object[] listarray = lists.toArray();
+  }
+
+  private static class MyClass<MyTypeParam> {
+
+    Object[] toArray() {
+      return new Object[] {new Object()};
+    }
+
+    @PolyNull Object[] toArrayPoly(MyClass<@PolyNull MyTypeParam> this) {
+      return new Object[] {new Object()};
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue2470.java b/checker/tests/nullness/Issue2470.java
new file mode 100644
index 0000000..b1972af
--- /dev/null
+++ b/checker/tests/nullness/Issue2470.java
@@ -0,0 +1,62 @@
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public class Issue2470 {
+  static class Example {
+    @MonotonicNonNull String s;
+
+    public Example() {}
+
+    @EnsuresNonNull("this.s")
+    public Example setS(String s1) {
+      this.s = s1;
+      return this;
+    }
+
+    // TODO: Support "return" in Java Expression syntax.
+    // @EnsuresNonNull("return.s")
+    @EnsuresNonNull("this.s")
+    public Example setS2(String s1) {
+      this.s = s1;
+      return this;
+    }
+
+    @RequiresNonNull("this.s")
+    public void print() {
+      System.out.println(this.s.toString());
+    }
+  }
+
+  static void buggy() {
+    new Example()
+        // :: error: (contracts.precondition)
+        .print();
+  }
+
+  static void ok() {
+    Example e = new Example();
+    e.setS("test");
+    e.print();
+  }
+
+  static void buggy2() {
+    new Example()
+        .setS("test")
+        // :: error:(contracts.precondition)
+        .print();
+  }
+
+  // TODO: These should be legal, once "return" is supported in Java Expression syntax.
+  // of a method.
+  /*
+  static void ok3() {
+      Example e = new Example().setS2("test");
+      e.print();
+  }
+
+  static void ok2() {
+      new Example().setS2("test").print();
+  }
+  */
+}
diff --git a/checker/tests/nullness/Issue2564.java b/checker/tests/nullness/Issue2564.java
new file mode 100644
index 0000000..ba3a765
--- /dev/null
+++ b/checker/tests/nullness/Issue2564.java
@@ -0,0 +1,20 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+
+public abstract class Issue2564 {
+  public enum EnumType {
+    // :: error: (assignment)
+    @KeyFor("myMap") MY_KEY,
+    // :: error: (assignment)
+    @KeyFor("enumMap") ENUM_KEY;
+    private static final Map<String, Integer> enumMap = new HashMap<>();
+
+    void method() {
+      @KeyFor("enumMap") EnumType t = ENUM_KEY;
+      int x = enumMap.get(ENUM_KEY);
+    }
+  }
+
+  private static final Map<String, Integer> myMap = new HashMap<>();
+}
diff --git a/checker/tests/nullness/Issue2565.java b/checker/tests/nullness/Issue2565.java
new file mode 100644
index 0000000..64e8b55
--- /dev/null
+++ b/checker/tests/nullness/Issue2565.java
@@ -0,0 +1,11 @@
+import java.util.List;
+
+public abstract class Issue2565 {
+
+  // Broken Case:
+  abstract void processErrors(List<Error<? extends Enum<?>>> errors);
+
+  static class Error<T extends Enum<T> & Hoo> {}
+
+  interface Hoo {}
+}
diff --git a/checker/tests/nullness/Issue2587.java b/checker/tests/nullness/Issue2587.java
new file mode 100644
index 0000000..71acb70
--- /dev/null
+++ b/checker/tests/nullness/Issue2587.java
@@ -0,0 +1,35 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+
+@SuppressWarnings("assignment") // These warnings are not relevant
+public abstract class Issue2587 {
+  public enum EnumType {
+    // :: error: (expression.unparsable)
+    @KeyFor("myMap") MY_KEY,
+
+    @KeyFor("enumMap") ENUM_KEY;
+    private static final Map<String, Integer> enumMap = new HashMap<>();
+
+    void method() {
+      @KeyFor("enumMap") EnumType t = ENUM_KEY;
+      int x = enumMap.get(ENUM_KEY);
+    }
+  }
+
+  public static class Inner {
+    // :: error: (expression.unparsable)
+    @KeyFor("myMap") String MY_KEY = "";
+
+    public static class Inner2 {
+      // :: error: (expression.unparsable)
+      @KeyFor("myMap") String MY_KEY2 = "";
+
+      @KeyFor("innerMap") String MY_KEY3 = "";
+    }
+
+    private static final Map<String, Integer> innerMap = new HashMap<>();
+  }
+
+  private final Map<String, Integer> myMap = new HashMap<>();
+}
diff --git a/checker/tests/nullness/Issue261.java b/checker/tests/nullness/Issue261.java
new file mode 100644
index 0000000..f247cf7
--- /dev/null
+++ b/checker/tests/nullness/Issue261.java
@@ -0,0 +1,18 @@
+// Test case for Issue 261
+// https://github.com/typetools/checker-framework/issues/261
+public class Issue261 {
+  boolean b;
+
+  class Flag<T> {
+    // :: error: (initialization.field.uninitialized)
+    T value;
+  }
+
+  static <T> T getValue(Flag<T> flag) {
+    return flag.value;
+  }
+
+  Issue261(Flag<Boolean> flag) {
+    this.b = getValue(flag);
+  }
+}
diff --git a/checker/tests/nullness/Issue2619.java b/checker/tests/nullness/Issue2619.java
new file mode 100644
index 0000000..c89632f
--- /dev/null
+++ b/checker/tests/nullness/Issue2619.java
@@ -0,0 +1,51 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class Issue2619 {
+  public Map<String, String> map = new HashMap<>();
+
+  void m00(Aux aux1) {
+    if (aux1.hasValue(Aux.MINIMUM_VALUE)) {
+      @KeyFor({"aux1.map"}) String s1 = Aux.MINIMUM_VALUE;
+    }
+  }
+
+  void m01(Aux aux1, Aux aux2) {
+    if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) {
+      @KeyFor({"aux1.map", "aux2.map"}) String s1 = Aux.MINIMUM_VALUE;
+    }
+  }
+
+  void m02(Aux aux1, Aux aux2) {
+    if (aux1.hasValue(Aux.MINIMUM_VALUE) && map.containsKey(Aux.MINIMUM_VALUE)) {
+      @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE;
+    }
+  }
+
+  void m03(Aux aux1, Aux aux2) {
+    if (map.containsKey(Aux.MINIMUM_VALUE) && aux1.hasValue(Aux.MINIMUM_VALUE)) {
+      @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE;
+    }
+  }
+
+  static class Aux {
+
+    public Map<String, String> map = new HashMap<>();
+
+    public static final String MINIMUM_VALUE = "minvalue";
+
+    @Pure
+    @EnsuresKeyForIf(result = true, expression = "#1", map = "map")
+    public boolean hasValue(String key) {
+      return map.containsKey(key);
+    }
+
+    @Pure
+    public int getInt(@KeyFor("this.map") String key) {
+      return 22;
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue2619b.java b/checker/tests/nullness/Issue2619b.java
new file mode 100644
index 0000000..24285bd
--- /dev/null
+++ b/checker/tests/nullness/Issue2619b.java
@@ -0,0 +1,54 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+
+public class Issue2619b {
+  public Map<String, String> map = new HashMap<>();
+
+  void m01(Aux aux1, Aux aux2) {
+    if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) {
+      // hasValue is not side-effect-free, so the @KeyFor("aux1.map") is cleared rather than glb'ed.
+      // :: error: (assignment)
+      @KeyFor({"aux1.map", "aux2.map"}) String s1 = Aux.MINIMUM_VALUE;
+    }
+  }
+
+  void m02(Aux aux1, Aux aux2) {
+    if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) {
+      @KeyFor("aux2.map") String s1 = Aux.MINIMUM_VALUE;
+    }
+  }
+
+  void m03(Aux aux1, Aux aux2) {
+    if (aux1.hasValue(Aux.MINIMUM_VALUE) && map.containsKey(Aux.MINIMUM_VALUE)) {
+      // ok because map.containsKey is side-effect-free.
+      @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE;
+      @KeyFor("map") String s2 = Aux.MINIMUM_VALUE;
+    }
+  }
+
+  void m04(Aux aux1, Aux aux2) {
+    if (map.containsKey(Aux.MINIMUM_VALUE) && aux1.hasValue(Aux.MINIMUM_VALUE)) {
+      // :: error: (assignment)
+      @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE;
+      @KeyFor("aux1.map") String s2 = Aux.MINIMUM_VALUE;
+    }
+  }
+
+  static class Aux {
+
+    public Map<String, String> map = new HashMap<>();
+
+    public static String MINIMUM_VALUE = "minvalue";
+
+    @EnsuresKeyForIf(result = true, expression = "#1", map = "map")
+    public boolean hasValue(String key) {
+      return map.containsKey(key);
+    }
+
+    public int getInt(@KeyFor("this.map") String key) {
+      return 22;
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue266.java b/checker/tests/nullness/Issue266.java
new file mode 100644
index 0000000..bead254
--- /dev/null
+++ b/checker/tests/nullness/Issue266.java
@@ -0,0 +1,23 @@
+// Test case for issue 266:
+// https://github.com/typetools/checker-framework/issues/266
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue266 {
+
+  abstract static class Inner {
+    abstract String getThing();
+  }
+
+  static @Nullable Inner method(@Nullable Object arg) {
+    final Object tmp = arg;
+    if (tmp == null) {
+      return null;
+    }
+    return new Inner() {
+      String getThing() {
+        return tmp.toString();
+      }
+    };
+  }
+}
diff --git a/checker/tests/nullness/Issue266a.java b/checker/tests/nullness/Issue266a.java
new file mode 100644
index 0000000..397dbda
--- /dev/null
+++ b/checker/tests/nullness/Issue266a.java
@@ -0,0 +1,21 @@
+// Test case for issue 266:
+// https://github.com/typetools/checker-framework/issues/266
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue266a {
+  private final Object mBar;
+
+  public Issue266a() {
+    mBar = "test";
+    Runnable runnable =
+        new Runnable() {
+          @Override
+          public void run() {
+            // unexpected [dereference.of.nullable] error here
+            mBar.toString();
+          }
+        };
+    runnable.run();
+  }
+}
diff --git a/checker/tests/nullness/Issue2721.java b/checker/tests/nullness/Issue2721.java
new file mode 100644
index 0000000..00ce5ee
--- /dev/null
+++ b/checker/tests/nullness/Issue2721.java
@@ -0,0 +1,14 @@
+import static java.util.Arrays.asList;
+
+import java.util.List;
+
+@SuppressWarnings("all") // Just check for crashes.
+public class Issue2721 {
+  void foo() {
+    passThrough(asList(asList(1))).get(0).get(0).intValue();
+  }
+
+  <T> List<? extends T> passThrough(List<? extends T> object) {
+    return object;
+  }
+}
diff --git a/checker/tests/nullness/Issue273.java b/checker/tests/nullness/Issue273.java
new file mode 100644
index 0000000..11d89df
--- /dev/null
+++ b/checker/tests/nullness/Issue273.java
@@ -0,0 +1,26 @@
+// Test case for issue #273:
+// https://github.com/typetools/checker-framework/issues/273
+
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue273 {
+  public static void main(String... p) {
+    Map<String, Integer> m0 = new HashMap<>();
+    Map<String, Integer> m1 = new HashMap<>();
+    @SuppressWarnings("assignment")
+    @KeyFor("m0") String k = "key";
+    m0.put(k, 1);
+
+    // :: error: (argument)
+    getMap2(m0, m1, k).toString();
+  }
+
+  public static @NonNull Integer getMap2(
+      Map<String, Integer> m1, // m1,m0 flipped
+      Map<String, Integer> m0,
+      @KeyFor("#2") String k) {
+    return m0.get(k);
+  }
+}
diff --git a/checker/tests/nullness/Issue2865.java b/checker/tests/nullness/Issue2865.java
new file mode 100644
index 0000000..611d097
--- /dev/null
+++ b/checker/tests/nullness/Issue2865.java
@@ -0,0 +1,25 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue2865<T extends @Nullable Object> {
+  public class C {
+    public C(T a) {}
+
+    public void f(T a) {
+      new C(a);
+      // :: error: (argument)
+      new C(null);
+    }
+  }
+
+  void test(Issue2865<@NonNull String> s) {
+    // :: error: (argument)
+    s.new C(null);
+    s.new C("");
+  }
+
+  void test2(Issue2865<@Nullable String> s) {
+    s.new C(null);
+    s.new C("");
+  }
+}
diff --git a/checker/tests/nullness/Issue2888.java b/checker/tests/nullness/Issue2888.java
new file mode 100644
index 0000000..023eb0a
--- /dev/null
+++ b/checker/tests/nullness/Issue2888.java
@@ -0,0 +1,33 @@
+import com.sun.istack.internal.Nullable;
+
+public class Issue2888 {
+  @Nullable Object[] noa;
+
+  void foo() {
+    noa = null;
+    // :: error: (accessing.nullable) :: error: (assignment)
+    noa[0] = null;
+  }
+
+  @Nullable Object[] foo2(@Nullable Object[] p) {
+    noa = p;
+    noa = foo2(noa);
+    noa = foo2(p);
+    return p;
+  }
+
+  // The below is copied from Issue 2923.
+  public void bar1(@Nullable String... args) {
+    bar2(args);
+  }
+
+  private void bar2(@Nullable String... args) {
+    if (args != null && args.length > 0) {
+      @Nullable final String arg0 = args[0];
+      // :: warning: (nulltest.redundant)
+      if (arg0 != null) {
+        System.out.println("arg0: " + arg0);
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue289.java b/checker/tests/nullness/Issue289.java
new file mode 100644
index 0000000..68846dd
--- /dev/null
+++ b/checker/tests/nullness/Issue289.java
@@ -0,0 +1,41 @@
+// Test for Issue 289:
+// https://github.com/typetools/checker-framework/issues/289
+
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue289 {
+  void simple() {
+    List<Object> lo = new ArrayList<>();
+    List<String> ls = new ArrayList<>();
+
+    List<@Nullable Object> lno = new ArrayList<>();
+    List<@Nullable String> lns = new ArrayList<>();
+
+    List<List<String>> lls = new ArrayList<>();
+    lls.add(new ArrayList<>());
+
+    // TODO: add a similar test that uses method type variables.
+  }
+
+  // TODO: work on more complex examples:
+
+  class Upper<U1> {}
+
+  class Middle<M1, M2> extends Upper<M2> {}
+
+  class Lower1<L1> extends Middle<L1, @Nullable String> {}
+
+  class Lower2<L1> extends Middle<L1, @NonNull String> {}
+
+  void complex() {
+    Upper<@Nullable String> uns = new Lower1<>();
+    // :: error: (assignment)
+    Upper<String> us = new Lower1<>();
+
+    // :: error: (assignment)
+    uns = new Lower2<>();
+    us = new Lower2<>();
+  }
+}
diff --git a/checker/tests/nullness/Issue293.java b/checker/tests/nullness/Issue293.java
new file mode 100644
index 0000000..841f8a6
--- /dev/null
+++ b/checker/tests/nullness/Issue293.java
@@ -0,0 +1,60 @@
+// Test for Issue 293:
+// https://github.com/typetools/checker-framework/issues/293
+
+public class Issue293 {
+  void test1() {
+    String s;
+    try {
+      s = read();
+    } catch (Exception e) {
+      // Because of definite assignment, s cannot be mentioned here.
+      write("Catch.");
+      return;
+    } finally {
+      // Because of definite assignment, s cannot be mentioned here.
+      write("Finally.");
+    }
+
+    // s is definitely initialized here.
+    write(s);
+  }
+
+  void test2() {
+    String s2 = "";
+    try {
+    } finally {
+      write(s2);
+    }
+  }
+
+  void test3() throws Exception {
+    String s = "";
+    try {
+      throw new Exception();
+    } finally {
+      write(s);
+    }
+  }
+
+  void test4() throws Exception {
+    String s = "";
+    try {
+      if (true) {
+        throw new Exception();
+      } else {
+        s = null;
+      }
+    } finally {
+      // :: error: argument
+      write(s);
+    }
+  }
+
+  String read() throws Exception {
+    throw new Exception();
+  }
+
+  void write(String p) {
+    System.out.println(p);
+  }
+}
diff --git a/checker/tests/nullness/Issue295.java b/checker/tests/nullness/Issue295.java
new file mode 100644
index 0000000..95bc55e
--- /dev/null
+++ b/checker/tests/nullness/Issue295.java
@@ -0,0 +1,50 @@
+// Test case for issue 295:
+// https://github.com/typetools/checker-framework/issues/295
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+abstract class Issue295 {
+
+  static class Box<T> {
+    T value;
+
+    Box(T value) {
+      this.value = value;
+    }
+  }
+
+  abstract <MTL> MTL load(Factory<MTL> p);
+
+  abstract class Factory<TF> {
+    abstract TF create();
+  }
+
+  <MT1> void f1(Factory<Box<@Nullable MT1>> f) {
+    Box<@Nullable MT1> v = f.create();
+    v = load(f);
+  }
+
+  <MT2> void f2(Factory<Box<@Nullable MT2>> f) {
+    Box<? extends @Nullable MT2> v = load(f);
+  }
+
+  <MT3> void f3(Factory<Box<MT3>> f) {
+    Box<MT3> v = load(f);
+  }
+
+  <MT4> void f4(Factory<Box<@Nullable MT4>> f) {
+    Box<? extends @Nullable MT4> v = load(f);
+  }
+
+  <MT5 extends @Nullable Object> void f5(Factory<Box<MT5>> f) {
+    Box<MT5> v = load(f);
+  }
+
+  <MT6 extends @Nullable Object> void f6(Factory<Box<MT6>> f) {
+    Box<? extends MT6> v = load(f);
+  }
+
+  <MT7> void f1noquals(Factory<Box<String>> f) {
+    Box<String> v = load(f);
+  }
+}
diff --git a/checker/tests/nullness/Issue296.java b/checker/tests/nullness/Issue296.java
new file mode 100644
index 0000000..93e175a
--- /dev/null
+++ b/checker/tests/nullness/Issue296.java
@@ -0,0 +1,29 @@
+// Test case for Issue 296:
+// https://github.com/typetools/checker-framework/issues/296
+
+import java.util.Arrays;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+// Note that with -AinvariantArrays we would get additional errors.
+public class Issue296 {
+  public static <T> void f1(T[] a) {
+    @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1);
+    // :: error: (argument)
+    @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1);
+    @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1);
+  }
+
+  public static <T> void f2(@NonNull T[] a) {
+    @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1);
+    @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1);
+    @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1);
+  }
+
+  public static <T> void f3(@Nullable T[] a) {
+    @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1);
+    // :: error: (argument)
+    @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1);
+    @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1);
+  }
+}
diff --git a/checker/tests/nullness/Issue3020.java b/checker/tests/nullness/Issue3020.java
new file mode 100644
index 0000000..4d10b8a
--- /dev/null
+++ b/checker/tests/nullness/Issue3020.java
@@ -0,0 +1,17 @@
+enum Issue3020 {
+  INSTANCE;
+
+  void retrieveConstant() {
+    Class<?> theClass = Issue3020.class;
+    // :: error: (accessing.nullable)
+    Object unused = passThrough(theClass.getEnumConstants())[0];
+  }
+
+  void nonNullArray(String[] p) {
+    Object unused = passThrough(p)[0];
+  }
+
+  <T> T passThrough(T t) {
+    return t;
+  }
+}
diff --git a/checker/tests/nullness/Issue3022.java b/checker/tests/nullness/Issue3022.java
new file mode 100644
index 0000000..dd890d5
--- /dev/null
+++ b/checker/tests/nullness/Issue3022.java
@@ -0,0 +1,11 @@
+abstract class Super3022<T> {
+  class Wrapper {
+    Wrapper(T key) {}
+  }
+}
+
+class Sub3022<K> extends Super3022<K> {
+  void wrap(K key) {
+    new Wrapper(key);
+  }
+}
diff --git a/checker/tests/nullness/Issue3033.java b/checker/tests/nullness/Issue3033.java
new file mode 100644
index 0000000..aeec592
--- /dev/null
+++ b/checker/tests/nullness/Issue3033.java
@@ -0,0 +1,27 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue3033 {
+
+  class Test {
+
+    void main() {
+      Test obj1 = new Test();
+
+      // No error as no explicit @Nullable or @NonNull annotation is given.
+      if (obj1 instanceof Test) {
+        Test obj2 = new Test();
+
+        // :: error: (instanceof.nullable)
+        if (obj1 instanceof @Nullable Test) {
+          obj1 = null;
+        }
+
+        // :: warning: (instanceof.nonnull.redundant)
+        if (obj2 instanceof @NonNull Test) {
+          obj2 = obj1;
+        }
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue306.java b/checker/tests/nullness/Issue306.java
new file mode 100644
index 0000000..c20e4aa
--- /dev/null
+++ b/checker/tests/nullness/Issue306.java
@@ -0,0 +1,43 @@
+// Test case for Issue 306:
+// https://github.com/typetools/checker-framework/issues/306
+
+// @skip-test until the issue is fixed
+
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue306 {
+  @MonotonicNonNull Object x;
+
+  static <T> T check(T var) {
+    return var;
+  }
+
+  void fakeMethod() {
+    // @MonotonicNonNull is not reflexive.
+    // However, it is the most specific type argument inferred for check. Therefore, an error is
+    // raised.
+    // We need a mechanism to not consider a qualifier in type inference.
+    check(x);
+
+    // Ugly way around the problem:
+    Issue306.<@Nullable Object>check(x);
+
+    // The following error has to be raised: from the signature it is not guaranteed that the
+    // parameter is returned.
+    // :: error: (monotonic)
+    x = check(x);
+  }
+
+  @MonotonicNonNull Object y;
+
+  void realError(@MonotonicNonNull Object p) {
+    // :: error: (monotonic)
+    x = y;
+    // :: error: (monotonic)
+    x = p;
+    // It would be nice not to raise the following error.
+    // :: error: (monotonic)
+    x = x;
+  }
+}
diff --git a/checker/tests/nullness/Issue308.java b/checker/tests/nullness/Issue308.java
new file mode 100644
index 0000000..6af58df
--- /dev/null
+++ b/checker/tests/nullness/Issue308.java
@@ -0,0 +1,23 @@
+import javax.validation.constraints.NotNull;
+import org.checkerframework.checker.nullness.qual.*;
+
+// @skip-test The clean-room implementation of javax.validation.constraints.NotNull is not in this
+// repository because Oracle claims a license over its specification and is lawsuit-happy.
+
+public class Issue308 {
+  @NonNull Object nonnull = new Object();
+  @Nullable Object nullable;
+
+  @NotNull(message = "hi") Object notnull1 = new Object();
+
+  @NotNull(groups = {Object.class}) Object notnull2 = new Object();
+
+  void foo() {
+    nonnull = notnull1;
+    notnull2 = nonnull;
+    notnull1 = notnull2;
+
+    nullable = notnull1;
+    nullable = notnull2;
+  }
+}
diff --git a/checker/tests/nullness/Issue3150.java b/checker/tests/nullness/Issue3150.java
new file mode 100644
index 0000000..864118b
--- /dev/null
+++ b/checker/tests/nullness/Issue3150.java
@@ -0,0 +1,29 @@
+// Test case for https://tinyurl.com/cfissue/3150 .
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue3150 {
+  void foo(@Nullable Object nble, @NonNull Object nn) {
+    // :: error: (type.argument)
+    requireNonNull1(null);
+    // :: error: (type.argument)
+    requireNonNull1(nble);
+    requireNonNull1("hello");
+    requireNonNull1(nn);
+    // :: error: (argument)
+    requireNonNull2(null);
+    // :: error: (argument)
+    requireNonNull2(nble);
+    requireNonNull1("hello");
+    requireNonNull1(nn);
+  }
+
+  public static <T extends @NonNull Object> T requireNonNull1(T obj) {
+    return obj;
+  }
+
+  public static <T> @NonNull T requireNonNull2(@NonNull T obj) {
+    return obj;
+  }
+}
diff --git a/checker/tests/nullness/Issue328.java b/checker/tests/nullness/Issue328.java
new file mode 100644
index 0000000..94febdb
--- /dev/null
+++ b/checker/tests/nullness/Issue328.java
@@ -0,0 +1,17 @@
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue328 {
+  public static void m(Map<Object, Object> a, Map<Object, Object> b, Object ka, Object kb) {
+    if (a.containsKey(ka)) {
+      @NonNull Object i = a.get(ka); // OK
+    }
+    if (b.containsKey(kb)) {
+      @NonNull Object i = b.get(kb); // OK
+    }
+    if (a.containsKey(ka) && b.containsKey(kb)) {
+      @NonNull Object i = a.get(ka); // ERROR
+      @NonNull Object j = b.get(kb); // ERROR
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue331.java b/checker/tests/nullness/Issue331.java
new file mode 100644
index 0000000..efb2263
--- /dev/null
+++ b/checker/tests/nullness/Issue331.java
@@ -0,0 +1,11 @@
+// Test case for Issue 331:
+// https://github.com/typetools/checker-framework/issues/331
+
+import java.util.List;
+
+class TestTeranry {
+  void foo(boolean b, List<Object> res) {
+    Object o = b ? "x" : (b ? "y" : "z");
+    res.add(o);
+  }
+}
diff --git a/checker/tests/nullness/Issue3349.java b/checker/tests/nullness/Issue3349.java
new file mode 100644
index 0000000..7cd64d7
--- /dev/null
+++ b/checker/tests/nullness/Issue3349.java
@@ -0,0 +1,10 @@
+import java.io.Serializable;
+import org.checkerframework.checker.nullness.qual.*;
+
+// :: warning: (explicit.annotation.ignored)
+public class Issue3349<T extends @NonNull Object & @Nullable Serializable> {
+  void foo(T p1) {
+    @NonNull Serializable s = p1;
+    @NonNull Object o = p1;
+  }
+}
diff --git a/checker/tests/nullness/Issue338.java b/checker/tests/nullness/Issue338.java
new file mode 100644
index 0000000..d7c2278
--- /dev/null
+++ b/checker/tests/nullness/Issue338.java
@@ -0,0 +1,9 @@
+interface Foo338<T> {
+  Class<T> get();
+}
+
+public class Issue338 {
+  static void m2(Foo338<?> foo) {
+    Class<?> clazz = foo.get();
+  }
+}
diff --git a/checker/tests/nullness/Issue3443.java b/checker/tests/nullness/Issue3443.java
new file mode 100644
index 0000000..3478569
--- /dev/null
+++ b/checker/tests/nullness/Issue3443.java
@@ -0,0 +1,19 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue3443 {
+  static <T extends Supplier3443<@Nullable String>> Supplier3443<String> passThrough(T t) {
+    // :: error: (return)
+    return t;
+  }
+
+  public static void main(String[] args) {
+    Supplier3443<@Nullable String> s1 = () -> null;
+    // TODO: passThrough(s1) should cause an error. #979.
+    Supplier3443<String> s2 = passThrough(s1);
+    s2.get().toString();
+  }
+}
+
+interface Supplier3443<T> {
+  T get();
+}
diff --git a/checker/tests/nullness/Issue345.java b/checker/tests/nullness/Issue345.java
new file mode 100644
index 0000000..6e4bec0
--- /dev/null
+++ b/checker/tests/nullness/Issue345.java
@@ -0,0 +1,17 @@
+// This is a test case for Issue 345:
+// https://github.com/typetools/checker-framework/issues/345
+public class Issue345 {
+  String f1;
+  String f2;
+
+  {
+    // :: error: (assignment)
+    f1 = f2;
+    f2 = f1;
+    f2.toString(); // Null pointer exception here
+  }
+
+  public static void main(String[] args) {
+    Issue345 a = new Issue345();
+  }
+}
diff --git a/checker/tests/nullness/Issue354.java b/checker/tests/nullness/Issue354.java
new file mode 100644
index 0000000..cdeae0e
--- /dev/null
+++ b/checker/tests/nullness/Issue354.java
@@ -0,0 +1,19 @@
+public class Issue354 {
+
+  String a;
+
+  {
+    Object o =
+        new Object() {
+          @Override
+          public String toString() {
+            // :: error: (dereference.of.nullable)
+            return a.toString();
+          }
+        }.toString();
+
+    // This is needed to avoid the initialization.fields.uninitialized warning.
+    // The NPE still occurs
+    a = "";
+  }
+}
diff --git a/checker/tests/nullness/Issue355.java b/checker/tests/nullness/Issue355.java
new file mode 100644
index 0000000..d7289cf
--- /dev/null
+++ b/checker/tests/nullness/Issue355.java
@@ -0,0 +1,58 @@
+// Test case for Issue 355:
+// https://github.com/typetools/checker-framework/issues/355
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue355 {
+  static <T extends @Nullable Object> @NonNull T checkNotNull(@Nullable T sample) {
+    throw new RuntimeException();
+  }
+
+  void m(List<String> xs) {
+    for (String x : checkNotNull(xs)) {}
+  }
+}
+
+class Issue355b {
+  static <T> T checkNotNull(T sample) {
+    throw new RuntimeException();
+  }
+
+  void m(List<String> xs) {
+    for (Object x : checkNotNull(xs)) {}
+  }
+}
+
+class Issue355c {
+  static <T> T checkNotNull(@NonNull T sample) {
+    throw new RuntimeException();
+  }
+
+  void m(List<String> xs) {
+    for (Object x : checkNotNull(xs)) {}
+  }
+}
+
+class Issue355d {
+  static <T> @Nullable T checkNotNull(@NonNull T sample) {
+    throw new RuntimeException();
+  }
+
+  void m(List<String> xs) {
+    // :: error: (iterating.over.nullable)
+    for (Object x : checkNotNull(xs)) {}
+  }
+}
+
+class Issue355e {
+  static <T> @NonNull T checkNotNull(@NonNull T sample) {
+    throw new RuntimeException();
+  }
+
+  void m(@Nullable List<String> xs) {
+    // :: error: (argument)
+    for (Object x : checkNotNull(xs)) {}
+  }
+}
diff --git a/checker/tests/nullness/Issue3614.java b/checker/tests/nullness/Issue3614.java
new file mode 100644
index 0000000..ae0cf67
--- /dev/null
+++ b/checker/tests/nullness/Issue3614.java
@@ -0,0 +1,117 @@
+// Test case for https://tinyurl.com/cfissue/3614
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.PolyNull;
+
+public class Issue3614 {
+
+  public static @Nullable Boolean m1(@PolyNull Boolean b) {
+    return (b == null) ? b : b;
+  }
+
+  public static @NonNull Boolean m2(@PolyNull Boolean b) {
+    return (b == null) ? Boolean.TRUE : !b;
+  }
+
+  public static @PolyNull Boolean m3(@PolyNull Boolean b) {
+    return (b == null) ? null : Boolean.TRUE;
+  }
+
+  public static @PolyNull Boolean m4(@PolyNull Boolean b) {
+    return (b == null) ? null : b;
+  }
+
+  public static @PolyNull Boolean m5(@PolyNull Boolean b) {
+    return (b == null) ? b : Boolean.TRUE;
+  }
+
+  public static @PolyNull Boolean not1(@PolyNull Boolean b) {
+    return (b == null) ? null : !b;
+  }
+
+  public static @PolyNull Boolean not2(@PolyNull Boolean b) {
+    // :: error: (unboxing.of.nullable)
+    return (b == null) ? b : !b;
+  }
+
+  public static @PolyNull Boolean not3(@PolyNull Boolean b) {
+    if (b == null) {
+      return null;
+    } else {
+      return !b;
+    }
+  }
+
+  public static <@Nullable T> T of1(T a) {
+    return a == null ? null : a;
+  }
+
+  public static <@Nullable T> T of2(T a) {
+    if (a == null) {
+      return null;
+    } else {
+      return a;
+    }
+  }
+
+  public static @PolyNull Integer plus1(@PolyNull Integer b0, @PolyNull Integer b1) {
+    return (b0 == null || b1 == null) ? null : (b0 + b1);
+  }
+
+  public static @PolyNull Integer plus2(@PolyNull Integer b0, @PolyNull Integer b1) {
+    if (b0 == null || b1 == null) {
+      return null;
+    } else {
+      return b0 + b1;
+    }
+  }
+
+  public static @PolyNull Integer plus3(@PolyNull Integer a, @PolyNull Integer b) {
+    if (a == null) {
+      return null;
+    }
+    if (b == null) {
+      return null;
+    }
+    return a + b;
+  }
+
+  public static @PolyNull Integer plus1Err(@PolyNull Integer b0, @PolyNull Integer b1) {
+    // :: error: (return) :: error: (unboxing.of.nullable)
+    return (b0 == null) ? null : (b0 + b1);
+  }
+
+  public static @PolyNull Integer plus2Err(@PolyNull Integer b0, @PolyNull Integer b1) {
+    if (b0 == null) {
+      return null;
+    } else {
+      // :: error: (unboxing.of.nullable)
+      return b0 + b1;
+    }
+  }
+
+  public static @PolyNull Integer plus3Err(@PolyNull Integer a, @PolyNull Integer b) {
+    if (a == null) {
+      return null;
+    }
+    // :: error: (unboxing.of.nullable)
+    return a + b;
+  }
+
+  public static @PolyNull /*("elt")*/ String @PolyNull /*("container")*/ [] typeArray(
+      @PolyNull /*("elt")*/ Object @PolyNull /*("container")*/ [] seq) {
+    if (seq == null) {
+      return null;
+    }
+    @PolyNull /*("elt")*/ String[] retval = new @PolyNull /*("elt")*/ String[seq.length];
+    for (int i = 0; i < seq.length; i++) {
+      if (seq[i] == null) {
+        retval[i] = null;
+      } else {
+        retval[i] = seq[i].getClass().toString();
+      }
+    }
+    return retval;
+  }
+}
diff --git a/checker/tests/nullness/Issue3622.java b/checker/tests/nullness/Issue3622.java
new file mode 100644
index 0000000..f5e6933
--- /dev/null
+++ b/checker/tests/nullness/Issue3622.java
@@ -0,0 +1,111 @@
+// Test case for https://tinyurl.com/cfissue/3622
+
+// @skip-test until the issue is fixed
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue3622 {
+
+  // These currently pass (no warnings)
+
+  public class ImmutableIntList1 {
+
+    @Override
+    public boolean equals(@Nullable Object obj1) {
+      if (obj1 instanceof ImmutableIntList1) {
+        return true;
+      } else {
+        return obj1 instanceof List;
+      }
+    }
+  }
+
+  public class ImmutableIntList2 {
+
+    @Override
+    public boolean equals(@Nullable Object obj2) {
+      return obj2 instanceof ImmutableIntList2;
+    }
+  }
+
+  public class ImmutableIntList3 {
+
+    @Override
+    public boolean equals(@Nullable Object obj3) {
+      if (obj3 instanceof ImmutableIntList3) {
+        return true;
+      } else {
+        return false;
+      }
+    }
+  }
+
+  public class ImmutableIntList7 {
+
+    @Override
+    public boolean equals(@Nullable Object obj7) {
+      return obj7 instanceof ImmutableIntList7 ? true : false;
+    }
+  }
+
+  public class ImmutableIntList8 {
+
+    @Override
+    public boolean equals(@Nullable Object obj8) {
+      return obj8 instanceof ImmutableIntList8 ? true : obj8 instanceof List;
+    }
+  }
+
+  public class ImmutableIntList9 {
+
+    @Override
+    public boolean equals(@Nullable Object obj9) {
+      return obj9 instanceof ImmutableIntList9
+          ? obj9 instanceof ImmutableIntList9
+          : obj9 instanceof ImmutableIntList9;
+    }
+  }
+
+  // These currently fail (false positive warnings)
+
+  public class ImmutableIntList4 {
+
+    @Override
+    @SuppressWarnings(
+        "contracts.conditional.postcondition" // TODO: give `if` the BOTH_TO_THEN treatment like ?:
+    )
+    public boolean equals(@Nullable Object obj4) {
+      boolean b;
+      if (obj4 instanceof ImmutableIntList4) {
+        b = true;
+      } else {
+        b = false;
+      }
+      return b;
+    }
+  }
+
+  public class ImmutableIntList5 {
+
+    @Override
+    @SuppressWarnings(
+        "contracts.conditional.postcondition" // TODO: Need special treatment for true and false
+    // boolean  literals (cut off dead parts of graph).
+    )
+    public boolean equals(@Nullable Object obj5) {
+      return true ? obj5 instanceof ImmutableIntList5 : obj5 instanceof ImmutableIntList5;
+    }
+  }
+
+  public class ImmutableIntList6 {
+
+    @Override
+    @SuppressWarnings("contracts.conditional.postcondition" // TODO: Need special treatment
+    // for true and false boolean  literals (cut off dead parts of graph).
+    )
+    public boolean equals(@Nullable Object obj6) {
+      return true ? obj6 instanceof ImmutableIntList6 : false;
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue3631.java b/checker/tests/nullness/Issue3631.java
new file mode 100644
index 0000000..4128889
--- /dev/null
+++ b/checker/tests/nullness/Issue3631.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public class Issue3631 {
+
+  void f(Object otherArg) {
+    // Casts aren't a supported JavaExpression.
+    // :: error: (contracts.precondition)
+    ((Issue3631Helper) otherArg).m();
+  }
+}
+
+class Issue3631Helper {
+
+  String type = "foo";
+
+  @RequiresNonNull("type")
+  void m() {}
+}
diff --git a/checker/tests/nullness/Issue3681.java b/checker/tests/nullness/Issue3681.java
new file mode 100644
index 0000000..b4a7f8d
--- /dev/null
+++ b/checker/tests/nullness/Issue3681.java
@@ -0,0 +1,49 @@
+// Test case for isse #3681: https://tinyurl.com/cfissue/3681
+
+// @below-java11-jdk-skip-test
+package org.jro.tests.checkerfwk.utils;
+
+public class Issue3681 {
+  interface PartialFunction<T, R> {
+    R apply(T t);
+
+    boolean isDefinedAt(T value);
+  }
+
+  interface Either<L, R> {
+    R get();
+
+    boolean isRight();
+  }
+
+  public static <L, R> PartialFunction<Either<L, R>, R> createKeepRight() {
+    return new PartialFunction<>() {
+
+      @Override
+      public R apply(final Either<L, R> either) {
+        return either.get();
+      }
+
+      @Override
+      public boolean isDefinedAt(final Either<L, R> value) {
+        return value.isRight();
+      }
+    };
+  }
+
+  public static <L, R>
+      PartialFunction<Either<L, ? extends R>, ? extends R> createRCovariantKeepRight() {
+    return new PartialFunction<>() {
+
+      @Override
+      public R apply(final Either<L, ? extends R> either) {
+        return either.get();
+      }
+
+      @Override
+      public boolean isDefinedAt(final Either<L, ? extends R> value) {
+        return value.isRight();
+      }
+    };
+  }
+}
diff --git a/checker/tests/nullness/Issue369.java b/checker/tests/nullness/Issue369.java
new file mode 100644
index 0000000..ae1acda
--- /dev/null
+++ b/checker/tests/nullness/Issue369.java
@@ -0,0 +1,12 @@
+// Test case for Issue 369:
+// https://github.com/typetools/checker-framework/issues/369
+
+import static java.util.stream.Collectors.toSet;
+
+import java.util.stream.Stream;
+
+public class Issue369 {
+  static void test(Stream<Integer> stream) {
+    stream.collect(toSet());
+  }
+}
diff --git a/checker/tests/nullness/Issue370.java b/checker/tests/nullness/Issue370.java
new file mode 100644
index 0000000..fae04fa
--- /dev/null
+++ b/checker/tests/nullness/Issue370.java
@@ -0,0 +1,11 @@
+// Test case for issue 370:
+// https://github.com/typetools/checker-framework/issues/370
+
+import java.util.Collections;
+
+public class Issue370 {
+
+  <T> Iterable<T> foo() {
+    return Collections.<T>emptyList();
+  }
+}
diff --git a/checker/tests/nullness/Issue372.java b/checker/tests/nullness/Issue372.java
new file mode 100644
index 0000000..f807735
--- /dev/null
+++ b/checker/tests/nullness/Issue372.java
@@ -0,0 +1,15 @@
+// Test case for Issue 372:
+// https://github.com/typetools/checker-framework/issues/372
+
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.EnsuresKeyFor;
+
+public class Issue372 {
+  private final Map<String, String> labels = new HashMap<>();
+
+  @EnsuresKeyFor(value = "#1", map = "labels")
+  void foo(String v) {
+    labels.put(v, "");
+  }
+}
diff --git a/checker/tests/nullness/Issue376.java b/checker/tests/nullness/Issue376.java
new file mode 100644
index 0000000..4a918ee
--- /dev/null
+++ b/checker/tests/nullness/Issue376.java
@@ -0,0 +1,11 @@
+// Test case for Issue 376:
+// https://github.com/typetools/checker-framework/issues/376
+
+public class Issue376 {
+
+  interface I {}
+
+  <Q extends Enum<Q> & I> void m(Class<Q> clazz, String name) {
+    I i = Enum.valueOf(clazz, name);
+  }
+}
diff --git a/checker/tests/nullness/Issue3792.java b/checker/tests/nullness/Issue3792.java
new file mode 100644
index 0000000..4f8f0b9
--- /dev/null
+++ b/checker/tests/nullness/Issue3792.java
@@ -0,0 +1,14 @@
+import java.util.Collection;
+import java.util.NavigableMap;
+
+public abstract class Issue3792<T> {
+  static class Instant {}
+
+  void method(
+      NavigableMap<Instant, Collection<T>> contents, Instant minTimestamp, Instant limitTimestamp) {
+    contents.subMap(minTimestamp, true, limitTimestamp, false).entrySet().stream()
+        .flatMap(e -> e.getValue().stream().map(v -> of(v, e.getKey())));
+  }
+
+  abstract Object of(T v, Instant key);
+}
diff --git a/checker/tests/nullness/Issue3850.java b/checker/tests/nullness/Issue3850.java
new file mode 100644
index 0000000..a41d868
--- /dev/null
+++ b/checker/tests/nullness/Issue3850.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.nullness.qual.PolyNull;
+
+public class Issue3850 {
+
+  private static Iterable<@PolyNull String> toPos(Iterable<? extends @PolyNull Object> nodes) {
+    // :: error: (return)
+    return transform(nodes, node -> node == null ? null : node.toString());
+  }
+
+  public static <F, T> Iterable<T> transform(
+      Iterable<? extends F> iterable,
+      java.util.function.Function<? super F, ? extends T> function) {
+    throw new Error("implementation is irrelevant");
+  }
+}
diff --git a/checker/tests/nullness/Issue388.java b/checker/tests/nullness/Issue388.java
new file mode 100644
index 0000000..6712f9e
--- /dev/null
+++ b/checker/tests/nullness/Issue388.java
@@ -0,0 +1,18 @@
+// Test case for Issue 388:
+// https://github.com/typetools/checker-framework/issues/388
+
+import java.util.Map;
+
+public class Issue388 {
+  static class Holder {
+    static final String KEY = "key";
+  }
+
+  public String getOrDefault(Map<String, String> map, String defaultValue) {
+    if (map.containsKey(Holder.KEY)) {
+      return map.get(Holder.KEY);
+    } else {
+      return defaultValue;
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue3884.java b/checker/tests/nullness/Issue3884.java
new file mode 100644
index 0000000..e59c071
--- /dev/null
+++ b/checker/tests/nullness/Issue3884.java
@@ -0,0 +1,18 @@
+interface Issue3884 {
+  String go(Kind kind);
+
+  Issue3884 FOO =
+      kind -> {
+        switch (kind) {
+          case A:
+            break;
+        }
+        return "";
+      };
+
+  enum Kind {
+    A,
+    B,
+    C;
+  }
+}
diff --git a/checker/tests/nullness/Issue3888.java b/checker/tests/nullness/Issue3888.java
new file mode 100644
index 0000000..f999c50
--- /dev/null
+++ b/checker/tests/nullness/Issue3888.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.nullness.qual.PolyNull;
+
+abstract class Issue3888 {
+
+  interface L<X> {}
+
+  interface E {}
+
+  public interface F<V, Y> {
+    Y a(V v);
+  }
+
+  abstract <T> void f(F<T, Boolean> f);
+
+  void c(F<E, @PolyNull L> o) {
+    f((E vm) -> o.a(vm) == null);
+  }
+}
diff --git a/checker/tests/nullness/Issue391.java b/checker/tests/nullness/Issue391.java
new file mode 100644
index 0000000..e3ca20f
--- /dev/null
+++ b/checker/tests/nullness/Issue391.java
@@ -0,0 +1,49 @@
+// Test case for Issue 391:
+// https://github.com/typetools/checker-framework/issues/391
+
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+class ClassA {
+  private @Nullable String value = null;
+
+  @EnsuresNonNull("value")
+  public void ensuresNonNull() {
+    value = "";
+  }
+
+  @RequiresNonNull("value")
+  public String getValue() {
+    return value;
+  }
+}
+
+public class Issue391 {
+  ClassA field = new ClassA();
+
+  @RequiresNonNull("field.value")
+  void method() {}
+
+  @EnsuresNonNull("field.value")
+  void ensuresNonNull() {
+    field.ensuresNonNull();
+  }
+
+  void method2() {
+    ClassA a = new ClassA();
+    // :: error: (contracts.precondition)
+    a.getValue();
+    // :: error: (contracts.precondition)
+    method();
+  }
+
+  void method3() {
+    ensuresNonNull();
+    method();
+
+    ClassA a = new ClassA();
+    a.ensuresNonNull();
+    a.getValue();
+  }
+}
diff --git a/checker/tests/nullness/Issue3929.java b/checker/tests/nullness/Issue3929.java
new file mode 100644
index 0000000..25b67c8
--- /dev/null
+++ b/checker/tests/nullness/Issue3929.java
@@ -0,0 +1,33 @@
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue3929 {
+
+  public void endElement(MyClass3929 arg) {
+    for (Object o : arg.getKeys()) {
+      o.toString();
+    }
+  }
+
+  public void endElement(NullableMyClass3929 arg) {
+    for (Object o : arg.getKeys()) {
+      // :: error: (dereference.of.nullable)
+      o.toString();
+    }
+  }
+}
+
+class MyClass3929<K extends Comparable<K>> {
+  public List<K> getKeys() {
+    return new ArrayList<>();
+  }
+}
+// TODO: This is a false positive.
+// See https://github.com/typetools/checker-framework/issues/2174
+// :: error: (type.argument)
+class NullableMyClass3929<K extends @Nullable Comparable<K>> {
+  public List<K> getKeys() {
+    return new ArrayList<>();
+  }
+}
diff --git a/checker/tests/nullness/Issue3935.java b/checker/tests/nullness/Issue3935.java
new file mode 100644
index 0000000..14a0a5e
--- /dev/null
+++ b/checker/tests/nullness/Issue3935.java
@@ -0,0 +1,10 @@
+import android.annotation.Nullable;
+
+public class Issue3935 {
+  // Note: Nullable is a declaration annotation and applies to the array, not the array element.
+  private @Nullable byte[] data;
+
+  // Declaration annotations on primitives are ignored, but this should issue
+  // a nullness.on.primitive error.
+  @Nullable byte b;
+}
diff --git a/checker/tests/nullness/Issue3970.java b/checker/tests/nullness/Issue3970.java
new file mode 100644
index 0000000..bd55ef2
--- /dev/null
+++ b/checker/tests/nullness/Issue3970.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue3970 {
+
+  public interface InterfaceA<T extends InterfaceA<T>> extends InterfaceB<T> {}
+
+  public interface InterfaceB<T extends InterfaceB<T>> {
+    int f();
+
+    @Nullable T g();
+  }
+
+  void t(InterfaceA<?> a) {
+    if (a.f() == 1) {
+      InterfaceA<?> a2 = a.g();
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue400.java b/checker/tests/nullness/Issue400.java
new file mode 100644
index 0000000..460bd49
--- /dev/null
+++ b/checker/tests/nullness/Issue400.java
@@ -0,0 +1,19 @@
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class Issue400 {
+  final class YYPair<T, V> {
+    // :: error: (initialization.field.uninitialized)
+    T first;
+    // :: error: (initialization.field.uninitialized)
+    V second;
+  }
+
+  class YY {
+    public Collection<YYPair<String, String>> getX() {
+      final Collection<YYPair<String, String>> out = new ArrayList<YYPair<String, String>>();
+      out.add(new YYPair<String, String>());
+      return out;
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue4007.java b/checker/tests/nullness/Issue4007.java
new file mode 100644
index 0000000..dd59d94
--- /dev/null
+++ b/checker/tests/nullness/Issue4007.java
@@ -0,0 +1,34 @@
+// Test case for issue #4007: https://tinyurl.com/cfissue/4007
+
+// @skip-test until the issue is fixed
+
+import java.util.List;
+import java.util.Optional;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+final class Issue4007 {
+  Optional<String> m1(List<String> list) {
+    return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0));
+  }
+
+  Optional<Optional<String>> m2(List<String> list) {
+    return Optional.of(list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)));
+  }
+
+  Optional<Optional<String>> m3(List<String> list) {
+    return Optional.of(
+        list.isEmpty() ? Optional.<@NonNull String>empty() : Optional.of(list.get(0)));
+  }
+
+  Optional<Optional<String>> m4(List<String> list) {
+    return Optional.of(
+        list.isEmpty() ? Optional.empty() : Optional.<@NonNull String>of(list.get(0)));
+  }
+
+  Optional<Optional<String>> m5(List<String> list) {
+    return Optional.of(
+        list.isEmpty()
+            ? Optional.<@NonNull String>empty()
+            : Optional.<@NonNull String>of(list.get(0)));
+  }
+}
diff --git a/checker/tests/nullness/Issue408.java b/checker/tests/nullness/Issue408.java
new file mode 100644
index 0000000..b322969
--- /dev/null
+++ b/checker/tests/nullness/Issue408.java
@@ -0,0 +1,30 @@
+// Test case for Issue 408
+// https://github.com/typetools/checker-framework/issues/408
+
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+
+public class Issue408 {
+  static class Bar {
+    Bar() {
+      doIssue408();
+    }
+
+    String doIssue408(@UnderInitialization Bar this) {
+      return "";
+    }
+  }
+
+  static class Baz extends Bar {
+    String myString = "hello";
+
+    @Override
+    String doIssue408(@UnderInitialization Baz this) {
+      // :: error: (dereference.of.nullable)
+      return myString.toLowerCase();
+    }
+  }
+
+  public static void main(String[] args) {
+    new Baz();
+  }
+}
diff --git a/checker/tests/nullness/Issue411.java b/checker/tests/nullness/Issue411.java
new file mode 100644
index 0000000..4e5cb14
--- /dev/null
+++ b/checker/tests/nullness/Issue411.java
@@ -0,0 +1,30 @@
+// Test case for issue 411:
+// https://github.com/typetools/checker-framework/issues/411
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue411 {
+
+  @MonotonicNonNull Object field1 = null;
+  final @Nullable Object field2 = null;
+
+  void m() {
+    if (field1 != null) {
+      new Object() {
+        void f() {
+          field1.toString();
+        }
+      };
+    }
+  }
+
+  void n() {
+    if (field2 != null) {
+      new Object() {
+        void f() {
+          field2.toString();
+        }
+      };
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue414.java b/checker/tests/nullness/Issue414.java
new file mode 100644
index 0000000..5da328b
--- /dev/null
+++ b/checker/tests/nullness/Issue414.java
@@ -0,0 +1,54 @@
+// Test case for Issue 414.
+// https://github.com/typetools/checker-framework/issues/414
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+
+public class Issue414 {
+
+  void simple(String s) {
+    Map<String, Integer> mymap = new HashMap<>();
+    mymap.put(s, 1);
+    @KeyFor("mymap") String s2 = s;
+  }
+
+  Map<String, Integer> someField = new HashMap<>();
+
+  void semiSimple(@KeyFor("this.someField") String s) {
+    Map<String, Integer> mymap = new HashMap<>();
+    mymap.put(s, 1);
+    @KeyFor({"this.someField", "mymap"}) String s2 = s;
+  }
+
+  void dominatorsNoGenerics(Map<String, Integer> preds) {
+
+    Map<String, Integer> dom = new HashMap<>();
+    @KeyFor({"preds", "dom"}) String root;
+
+    List<@KeyFor({"preds", "dom"}) String> roots = new ArrayList<String>();
+
+    for (String node : preds.keySet()) {
+      dom.put(node, 1);
+      root = node;
+      roots.add(node);
+    }
+  }
+
+  <T> void dominators(Map<T, List<T>> preds) {
+
+    Map<T, Integer> dom = new HashMap<>();
+
+    @KeyFor({"preds", "dom"}) T root;
+
+    List<@KeyFor({"preds", "dom"}) T> roots = new ArrayList<T>();
+
+    for (T node : preds.keySet()) {
+      dom.put(node, 1);
+      root = node;
+      roots.add(node);
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue415.java b/checker/tests/nullness/Issue415.java
new file mode 100644
index 0000000..ca6befd
--- /dev/null
+++ b/checker/tests/nullness/Issue415.java
@@ -0,0 +1,44 @@
+// Test case for Issue 415
+// https://github.com/typetools/checker-framework/issues/415
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.dataflow.qual.*;
+
+public final class Issue415 {
+
+  Map<String, Integer> mymap = new HashMap<>();
+  // :: error: (expression.unparsable)
+  public static void usesField(Set<@KeyFor("this.mymap") String> keySet) {
+    // :: error: (expression.unparsable)
+    new ArrayList<@KeyFor("this.mymap") String>(keySet);
+  }
+
+  public static void usesParameter(Map<String, Integer> m, Set<@KeyFor("#1") String> keySet) {
+    new ArrayList<@KeyFor("#1") String>(keySet);
+  }
+
+  public static void sortedKeySet1(Map<String, Integer> m, Set<@KeyFor("#1") String> keySet) {
+    new ArrayList<@KeyFor("#1") String>(keySet);
+  }
+
+  public static void sortedKeySet2(Map<String, Integer> m) {
+    Set<@KeyFor("#1") String> keySet = m.keySet();
+  }
+
+  public static void sortedKeySet3(Map<String, Integer> m) {
+    Set<@KeyFor("#1") String> keySet = m.keySet();
+    new ArrayList<@KeyFor("#1") String>(keySet);
+  }
+
+  public static void sortedKeySet4(Map<String, Integer> m) {
+    new ArrayList<@KeyFor("#1") String>(m.keySet());
+  }
+
+  public static <K extends Comparable<? super K>, V> void sortedKeySet(Map<K, V> m) {
+    new ArrayList<@KeyFor("#1") K>(m.keySet());
+  }
+}
diff --git a/checker/tests/nullness/Issue419.java b/checker/tests/nullness/Issue419.java
new file mode 100644
index 0000000..3851455
--- /dev/null
+++ b/checker/tests/nullness/Issue419.java
@@ -0,0 +1,19 @@
+// Test case for Issue 419:
+// https://github.com/typetools/checker-framework/issues/419
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue419 {
+  @SuppressWarnings("nullness")
+  <T> @NonNull T verifyNotNull(@Nullable T o) {
+    return o;
+  }
+
+  interface Pair<A, B> {
+    @Nullable A getFirst();
+  }
+
+  void m(Pair<String[], int[]> p) {
+    for (String s : verifyNotNull(p.getFirst())) {}
+  }
+}
diff --git a/checker/tests/nullness/Issue425.java b/checker/tests/nullness/Issue425.java
new file mode 100644
index 0000000..a581f03
--- /dev/null
+++ b/checker/tests/nullness/Issue425.java
@@ -0,0 +1,34 @@
+// Test case for Issue #425:
+// https://github.com/typetools/checker-framework/issues/425
+
+// @skip-test until the issue is fixed
+
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue425 {
+  private @Nullable Set<Integer> field = null;
+
+  class EvilSet<T> extends HashSet<T> {
+    public boolean add(T e) {
+      field = null;
+      return super.add(e);
+    }
+  }
+
+  public void fail() {
+    if (field == null) {
+      field = new EvilSet<>();
+    }
+
+    field.add(1);
+    // This line throws an exception at run time.
+    field.add(2);
+  }
+
+  public static void main(String[] args) throws Exception {
+    Issue425 m = new Issue425();
+    m.fail();
+  }
+}
diff --git a/checker/tests/nullness/Issue427.java b/checker/tests/nullness/Issue427.java
new file mode 100644
index 0000000..fc86027
--- /dev/null
+++ b/checker/tests/nullness/Issue427.java
@@ -0,0 +1,22 @@
+// Test case for Issue 427:
+// https://github.com/typetools/checker-framework/issues/427
+
+// We need to add a warning when an @AssumeAssertion is missing its @ symbol (as below).
+
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue427 {
+
+  public static void assumeAssertionKeyFor1(String var, Map<String, Integer> m) {
+    assert m.containsKey(var)
+        : "@AssumeAssertion(keyfor): keys of leaders and timeKilled are the same";
+    boolean b = (m.get(var) >= 22);
+  }
+
+  public static void assumeAssertionKeyFor2(String var, Map<String, Integer> m) {
+    assert m.containsKey(var)
+        : "@AssumeAssertion(keyfor): keys of leaders and timeKilled are the same";
+    int x = m.get(var);
+  }
+}
diff --git a/checker/tests/nullness/Issue4372.java b/checker/tests/nullness/Issue4372.java
new file mode 100644
index 0000000..90858d7
--- /dev/null
+++ b/checker/tests/nullness/Issue4372.java
@@ -0,0 +1,20 @@
+import java.util.Map;
+import java.util.Optional;
+
+public class Issue4372 {
+  private Optional<? extends Map<String, Integer>> o;
+
+  public Issue4372() {
+    this.o = Optional.<Map<String, Integer>>empty();
+  }
+
+  void f(String k, Optional<Integer> x) {
+    if (!o.isPresent() || !o.get().containsKey(k)) {
+      return;
+    }
+    Integer y = o.get().get(k);
+    if (!x.isPresent()) {
+      return;
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue4381.java b/checker/tests/nullness/Issue4381.java
new file mode 100644
index 0000000..8e07187
--- /dev/null
+++ b/checker/tests/nullness/Issue4381.java
@@ -0,0 +1,19 @@
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+abstract class Issue4381 {
+
+  public void t() {
+    int m = 0;
+    try {
+      f();
+    } catch (IllegalArgumentException | IOException e) {
+    } catch (GeneralSecurityException e) {
+      g(m);
+    }
+  }
+
+  abstract void g(int x);
+
+  abstract void f() throws IllegalArgumentException, IOException, GeneralSecurityException;
+}
diff --git a/checker/tests/nullness/Issue4412.java b/checker/tests/nullness/Issue4412.java
new file mode 100644
index 0000000..6474cae
--- /dev/null
+++ b/checker/tests/nullness/Issue4412.java
@@ -0,0 +1,210 @@
+// @skip-test This test passes, but is slow. So skip it until performance improves.
+
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public interface Issue4412<
+    LeftLeftType extends Issue4412.LeftLeft<LeftLeftType, LeftType, RightType, RightRightType>,
+    LeftType extends Issue4412.Left<LeftLeftType, LeftType, RightType, RightRightType>,
+    RightType extends Issue4412.Right<LeftLeftType, LeftType, RightType, RightRightType>,
+    RightRightType extends
+        Issue4412.RightRight<LeftLeftType, LeftType, RightType, RightRightType>> {
+
+  <T> T reduce(
+      @NonNull Function1<? super LeftLeftType, ? extends T> leftLeftReducer,
+      @NonNull Function1<? super LeftType, ? extends T> leftReducer,
+      @NonNull Function1<? super RightType, ? extends T> rightReducer,
+      @NonNull Function1<? super RightRightType, ? extends T> rightRightReducer);
+
+  void act(
+      @NonNull VoidFunction1<? super LeftLeftType> leftLeftAction,
+      @NonNull VoidFunction1<? super LeftType> leftAction,
+      @NonNull VoidFunction1<? super RightType> rightAction,
+      @NonNull VoidFunction1<? super RightRightType> rightRightAction);
+
+  interface LeftLeft<
+          LeftLeftType extends LeftLeft<LeftLeftType, LeftType, RightType, RightRightType>,
+          LeftType extends Left<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightType extends Right<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightRightType extends RightRight<LeftLeftType, LeftType, RightType, RightRightType>>
+      extends Issue4412<LeftLeftType, LeftType, RightType, RightRightType> {}
+
+  // Not final to allow reification
+  abstract class LeftLeftImpl<
+          LeftLeftType extends LeftLeft<LeftLeftType, LeftType, RightType, RightRightType>,
+          LeftType extends Left<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightType extends Right<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightRightType extends RightRight<LeftLeftType, LeftType, RightType, RightRightType>>
+      implements LeftLeft<LeftLeftType, LeftType, RightType, RightRightType> {
+
+    private final Class<LeftLeftType> selfClass;
+
+    protected LeftLeftImpl(Class<LeftLeftType> selfClass) {
+      this.selfClass = selfClass;
+    }
+
+    @Override
+    public final <T> T reduce(
+        @NonNull Function1<? super LeftLeftType, ? extends T> leftLeftReducer,
+        @NonNull Function1<? super LeftType, ? extends T> leftReducer,
+        @NonNull Function1<? super RightType, ? extends T> rightReducer,
+        @NonNull Function1<? super RightRightType, ? extends T> rightRightReducer) {
+      return leftLeftReducer.apply(getSelf());
+    }
+
+    @Override
+    public final void act(
+        @NonNull VoidFunction1<? super LeftLeftType> leftLeftAction,
+        @NonNull VoidFunction1<? super LeftType> leftAction,
+        @NonNull VoidFunction1<? super RightType> rightAction,
+        @NonNull VoidFunction1<? super RightRightType> rightRightAction) {
+      leftLeftAction.apply(getSelf());
+    }
+
+    private LeftLeftType getSelf() {
+      return Objects.requireNonNull(selfClass.cast(this));
+    }
+  }
+
+  interface Left<
+          LeftLeftType extends LeftLeft<LeftLeftType, LeftType, RightType, RightRightType>,
+          LeftType extends Left<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightType extends Right<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightRightType extends RightRight<LeftLeftType, LeftType, RightType, RightRightType>>
+      extends Issue4412<LeftLeftType, LeftType, RightType, RightRightType> {}
+
+  // Not final to allow reification
+  abstract class LeftImpl<
+          LeftLeftType extends LeftLeft<LeftLeftType, LeftType, RightType, RightRightType>,
+          LeftType extends Left<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightType extends Right<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightRightType extends RightRight<LeftLeftType, LeftType, RightType, RightRightType>>
+      implements Left<LeftLeftType, LeftType, RightType, RightRightType> {
+
+    private final Class<LeftType> selfClass;
+
+    protected LeftImpl(@NonNull Class<LeftType> selfClass) {
+      this.selfClass = selfClass;
+    }
+
+    @Override
+    public final <T> T reduce(
+        @NonNull Function1<? super LeftLeftType, ? extends T> leftLeftReducer,
+        @NonNull Function1<? super LeftType, ? extends T> leftReducer,
+        @NonNull Function1<? super RightType, ? extends T> rightReducer,
+        @NonNull Function1<? super RightRightType, ? extends T> rightRightReducer) {
+      return leftReducer.apply(getSelf());
+    }
+
+    @Override
+    public final void act(
+        @NonNull VoidFunction1<? super LeftLeftType> leftLeftAction,
+        @NonNull VoidFunction1<? super LeftType> leftAction,
+        @NonNull VoidFunction1<? super RightType> rightAction,
+        @NonNull VoidFunction1<? super RightRightType> rightRightAction) {
+      leftAction.apply(getSelf());
+    }
+
+    private LeftType getSelf() {
+      return Objects.requireNonNull(selfClass.cast(this));
+    }
+  }
+
+  interface Right<
+          LeftLeftType extends LeftLeft<LeftLeftType, LeftType, RightType, RightRightType>,
+          LeftType extends Left<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightType extends Right<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightRightType extends RightRight<LeftLeftType, LeftType, RightType, RightRightType>>
+      extends Issue4412<LeftLeftType, LeftType, RightType, RightRightType> {}
+
+  // Not final to allow reification
+  abstract class RightImpl<
+          LeftLeftType extends LeftLeft<LeftLeftType, LeftType, RightType, RightRightType>,
+          LeftType extends Left<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightType extends Right<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightRightType extends RightRight<LeftLeftType, LeftType, RightType, RightRightType>>
+      implements Right<LeftLeftType, LeftType, RightType, RightRightType> {
+
+    private final Class<RightType> selfClass;
+
+    protected RightImpl(@NonNull Class<RightType> selfClass) {
+      this.selfClass = selfClass;
+    }
+
+    @Override
+    public final <T> T reduce(
+        @NonNull Function1<? super LeftLeftType, ? extends T> leftLeftReducer,
+        @NonNull Function1<? super LeftType, ? extends T> leftReducer,
+        @NonNull Function1<? super RightType, ? extends T> rightReducer,
+        @NonNull Function1<? super RightRightType, ? extends T> rightRightReducer) {
+      return rightReducer.apply(getSelf());
+    }
+
+    @Override
+    public final void act(
+        @NonNull VoidFunction1<? super LeftLeftType> leftLeftAction,
+        @NonNull VoidFunction1<? super LeftType> leftAction,
+        @NonNull VoidFunction1<? super RightType> rightAction,
+        @NonNull VoidFunction1<? super RightRightType> rightRightAction) {
+      rightAction.apply(getSelf());
+    }
+
+    private RightType getSelf() {
+      return Objects.requireNonNull(selfClass.cast(this));
+    }
+  }
+
+  interface RightRight<
+          LeftLeftType extends LeftLeft<LeftLeftType, LeftType, RightType, RightRightType>,
+          LeftType extends Left<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightType extends Right<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightRightType extends RightRight<LeftLeftType, LeftType, RightType, RightRightType>>
+      extends Issue4412<LeftLeftType, LeftType, RightType, RightRightType> {}
+
+  // Not final to allow reification
+  abstract class RightRightImpl<
+          LeftLeftType extends LeftLeft<LeftLeftType, LeftType, RightType, RightRightType>,
+          LeftType extends Left<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightType extends Right<LeftLeftType, LeftType, RightType, RightRightType>,
+          RightRightType extends RightRight<LeftLeftType, LeftType, RightType, RightRightType>>
+      implements RightRight<LeftLeftType, LeftType, RightType, RightRightType> {
+
+    private final Class<RightRightType> selfClass;
+
+    protected RightRightImpl(@NonNull Class<RightRightType> selfClass) {
+      this.selfClass = selfClass;
+    }
+
+    @Override
+    public final <T> T reduce(
+        @NonNull Function1<? super LeftLeftType, ? extends T> leftLeftReducer,
+        @NonNull Function1<? super LeftType, ? extends T> leftReducer,
+        @NonNull Function1<? super RightType, ? extends T> rightReducer,
+        @NonNull Function1<? super RightRightType, ? extends T> rightRightReducer) {
+      return rightRightReducer.apply(getSelf());
+    }
+
+    @Override
+    public final void act(
+        @NonNull VoidFunction1<? super LeftLeftType> leftLeftAction,
+        @NonNull VoidFunction1<? super LeftType> leftAction,
+        @NonNull VoidFunction1<? super RightType> rightAction,
+        @NonNull VoidFunction1<? super RightRightType> rightRightAction) {
+      rightRightAction.apply(getSelf());
+    }
+
+    private RightRightType getSelf() {
+      return Objects.requireNonNull(selfClass.cast(this));
+    }
+  }
+
+  interface VoidFunction1<T> {
+
+    void apply(@NonNull T t);
+  }
+
+  interface Function1<T, R> {
+
+    @NonNull R apply(@NonNull T t);
+  }
+}
diff --git a/checker/tests/nullness/Issue4579.java b/checker/tests/nullness/Issue4579.java
new file mode 100644
index 0000000..bd1ea4b
--- /dev/null
+++ b/checker/tests/nullness/Issue4579.java
@@ -0,0 +1,40 @@
+// Test case for issue #4579
+
+// @skip-test until the issue is fixed
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.JButton;
+
+public class Issue4579 {
+
+  public Issue4579() {
+    final JButton button = new JButton();
+
+    // this reports a warning about under initialization
+    button.addActionListener(l -> doAction());
+
+    // this reports no warnings
+    button.addActionListener(new ListenerClass());
+
+    // this reports a warning about under initialization
+    button.addActionListener(
+        new ActionListener() {
+          public void actionPerformed(final ActionEvent e) {
+            doAction();
+          }
+        });
+  }
+
+  private void doAction() {
+    System.out.println("Action");
+  }
+
+  private class ListenerClass implements ActionListener {
+
+    @Override
+    public void actionPerformed(final ActionEvent e) {
+      doAction();
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue4593.java b/checker/tests/nullness/Issue4593.java
new file mode 100644
index 0000000..698127e
--- /dev/null
+++ b/checker/tests/nullness/Issue4593.java
@@ -0,0 +1,20 @@
+// Test case for https://github.com/typetools/checker-framework/issues/4593 .
+
+// @skip-test until the bug is fixed
+
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue4593 {
+
+  void getContext(@Nullable String nble) {
+    Map<String, @Nullable Object> map = new HashMap<>();
+    map.put("configDir", nble);
+  }
+
+  void getContextWithVar(@Nullable String nble) {
+    var map = new HashMap<String, @Nullable Object>();
+    map.put("configDir", nble);
+  }
+}
diff --git a/checker/tests/nullness/Issue4614.java b/checker/tests/nullness/Issue4614.java
new file mode 100644
index 0000000..ea67892
--- /dev/null
+++ b/checker/tests/nullness/Issue4614.java
@@ -0,0 +1,26 @@
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public final class Issue4614 {
+
+  public static Map<String, String> getAllVersionInformation() {
+    return new HashMap<>();
+  }
+
+  public void method1() {
+    final String versionInfo =
+        Issue4614.getAllVersionInformation().entrySet().stream() //
+            .map(e -> String.format("%s:%s", e.getKey(), e.getValue())) //
+            .collect(Collectors.joining("\n"));
+  }
+
+  Map<String, String> allVersionInformation = new HashMap<>();
+
+  public void method2() {
+    final String versionInfo =
+        allVersionInformation.entrySet().stream() //
+            .map(e -> String.format("%s:%s", e.getKey(), e.getValue())) //
+            .collect(Collectors.joining("\n"));
+  }
+}
diff --git a/checker/tests/nullness/Issue471.java b/checker/tests/nullness/Issue471.java
new file mode 100644
index 0000000..2666c72
--- /dev/null
+++ b/checker/tests/nullness/Issue471.java
@@ -0,0 +1,13 @@
+// Test case for Issue 471
+// https://github.com/typetools/checker-framework/issues/471
+// javacheck -processor nullness Issue471.java -AprintVerboseGenerics -AprintAllQualifiers
+
+import javax.annotation.Nullable;
+
+public class Issue471<T> {
+  @Nullable T t;
+
+  Issue471(@Nullable T t) {
+    this.t = t;
+  }
+}
diff --git a/checker/tests/nullness/Issue500.java b/checker/tests/nullness/Issue500.java
new file mode 100644
index 0000000..a6e3c71
--- /dev/null
+++ b/checker/tests/nullness/Issue500.java
@@ -0,0 +1,24 @@
+// Test case for Issue 500:
+// https://github.com/typetools/checker-framework/issues/500
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue500<M> {
+  // Tests GLB
+  public Issue500(@Nullable List<M> list) {
+    if (list instanceof ArrayList<?>) {}
+  }
+  // Tests GLB
+  public Issue500(@Nullable AbstractList<M> list) {
+    if (list instanceof ArrayList<?>) {}
+  }
+
+  // Tests LUB
+  void foo(
+      @Nullable AbstractList<M> l1, ArrayList<?> l2, @Nullable AbstractList<?> list, boolean b) {
+    list = b ? l1 : l2;
+  }
+}
diff --git a/checker/tests/nullness/Issue520.java b/checker/tests/nullness/Issue520.java
new file mode 100644
index 0000000..26d0cfa
--- /dev/null
+++ b/checker/tests/nullness/Issue520.java
@@ -0,0 +1,42 @@
+// @skip-test
+
+// Test case for issue #520: https://github.com/typetools/checker-framework/issues/520
+
+// compile with: $CHECKERFRAMEWORK/checker/bin/javac -g Issue520.java -processor nullness
+// -AprintAllQualifiers
+
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue520 {}
+
+abstract class Parent<T> {
+  protected final List<? super @KeyForBottom T> list;
+
+  public Parent(List<? super @KeyForBottom T> list) {
+    this.list = list;
+  }
+}
+
+abstract class Child extends Parent<CharSequence> {
+  public Child(List<? super CharSequence> list) {
+    super(list);
+  }
+
+  public void add(CharSequence seq) {
+    list.add(seq);
+  }
+}
+
+class WildCardAdd {
+  List<@UnknownKeyFor ? super @KeyForBottom CharSequence> wildCardList =
+      new ArrayList<@KeyForBottom CharSequence>();
+
+  void foo(
+      List<@KeyFor("m") CharSequence> keyForMCharSeq, @UnknownKeyFor CharSequence unknownCharSeq) {
+    wildCardList = keyForMCharSeq;
+    wildCardList.add(unknownCharSeq);
+    @KeyFor("y") Object o = wildCardList.get(0);
+  }
+}
diff --git a/checker/tests/nullness/Issue531.java b/checker/tests/nullness/Issue531.java
new file mode 100644
index 0000000..2aab9d9
--- /dev/null
+++ b/checker/tests/nullness/Issue531.java
@@ -0,0 +1,25 @@
+// Test case for issue #979: https://github.com/typetools/checker-framework/issues/979
+
+// @skip-test
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue531 {
+  public MyList<String> test(MyStream<String> stream) {
+    return stream.collect(toList());
+  }
+
+  void foo(MyStream<String> stream) {}
+
+  static <T> MyCollector<T, ?, MyList<T>> toList() {
+    return new MyCollector<>();
+  }
+}
+
+class MyList<T> {}
+
+class MyCollector<T, A, R> {}
+
+abstract class MyStream<T> {
+  public abstract <R, A> R collect(MyCollector<? super T, A, R> c);
+}
diff --git a/checker/tests/nullness/Issue554.java b/checker/tests/nullness/Issue554.java
new file mode 100644
index 0000000..e81a741
--- /dev/null
+++ b/checker/tests/nullness/Issue554.java
@@ -0,0 +1,69 @@
+// @skip-test
+
+// Test case for issue #554: https://github.com/typetools/checker-framework/issues/554
+
+import org.checkerframework.checker.nullness.qual.*;
+
+class MonotonicNonNullConstructorTest1 {
+  static class Data {
+    @MonotonicNonNull Object field;
+  }
+
+  Data data;
+  Object object;
+
+  @RequiresNonNull("#1.field")
+  MonotonicNonNullConstructorTest1(final Data data) {
+    this.data = data;
+    this.object = data.field;
+  }
+}
+
+class MonotonicNonNullConstructorTest2 {
+  static class Data {
+    @MonotonicNonNull Object field;
+  }
+
+  Data data;
+  Object object;
+
+  @RequiresNonNull("#1.field")
+  MonotonicNonNullConstructorTest2(final Data data) {
+    // reverse the assignments
+    this.object = data.field;
+    this.data = data;
+  }
+}
+
+class MonotonicNonNullConstructorTest3 {
+  static class Data {
+    @MonotonicNonNull Object field;
+  }
+
+  Data data;
+  Object object;
+
+  @RequiresNonNull("#1.field")
+  MonotonicNonNullConstructorTest3(final Data dataParam) {
+    // use a parameter name that does not shadow the field
+    this.data = dataParam;
+    this.object = dataParam.field;
+  }
+}
+
+class MonotonicNonNullConstructorTest4 {
+  static class Data {
+    @MonotonicNonNull Object field;
+  }
+
+  Data data;
+  Object object;
+
+  @RequiresNonNull("#1.field")
+  MonotonicNonNullConstructorTest4(final Data dataParam) {
+    // use a parameter name that does not shadow the field
+    // and reverse the assignments
+    this.object = dataParam.field;
+    this.data = dataParam;
+  }
+}
diff --git a/checker/tests/nullness/Issue563.java b/checker/tests/nullness/Issue563.java
new file mode 100644
index 0000000..238c378
--- /dev/null
+++ b/checker/tests/nullness/Issue563.java
@@ -0,0 +1,10 @@
+// Test case for Issue 563:
+// https://github.com/typetools/checker-framework/issues/563
+public class Issue563 {
+  void bar() {
+    Object x = null;
+    if (Object.class.isInstance(x)) {
+      x.toString();
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue577.java b/checker/tests/nullness/Issue577.java
new file mode 100644
index 0000000..27c7a22
--- /dev/null
+++ b/checker/tests/nullness/Issue577.java
@@ -0,0 +1,73 @@
+// Test case for issue #577 with expected errors:
+// Also see test case in framework/tests/all-systems/Issue577.java
+// https://github.com/typetools/checker-framework/issues/577
+
+import org.checkerframework.checker.nullness.qual.*;
+
+class Banana<T extends Number> extends Apple<int[]> {
+  @Override
+  void fooOuter(int[] array) {}
+
+  class InnerBanana extends InnerApple<long[]> {
+    @Override
+    // :: error: (override.param)
+    <F2 extends Object> void foo(int[] array, long[] array2, F2 param3) {}
+  }
+}
+
+class Apple<T> {
+  void fooOuter(T param) {}
+
+  class InnerApple<E> {
+    <F> void foo(T param, E param2, F param3) {}
+  }
+}
+
+class Pineapple<E extends Object> extends Apple<E> {
+  @Override
+  void fooOuter(E array) {}
+
+  class InnerPineapple extends InnerApple<@Nullable String> {
+    @Override
+    // :: error: (override.param)
+    <F3> void foo(E array, String array2, F3 param3) {}
+  }
+}
+
+class IntersectionAsMemberOf {
+  interface MyGenericInterface<F> {
+    F getF();
+  }
+
+  <T extends Object & MyGenericInterface<@NonNull String>> void foo(T param) {
+    @NonNull String s = param.getF();
+  }
+}
+
+class UnionAsMemberOf {
+  interface MyInterface<T> {
+    T getT();
+  }
+
+  class MyExceptionA extends Throwable implements Cloneable, MyInterface<@NonNull String> {
+    public String getT() {
+      return "t";
+    }
+  }
+
+  class MyExceptionB extends Throwable implements Cloneable, MyInterface<String> {
+    public String getT() {
+      return "t";
+    }
+  }
+
+  void bar() throws MyExceptionA, MyExceptionB {}
+
+  void foo1(MyInterface<Throwable> param) throws Throwable {
+    try {
+      bar();
+    } catch (MyExceptionA | MyExceptionB ex1) {
+      @NonNull String s = ex1.getT();
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue578.java b/checker/tests/nullness/Issue578.java
new file mode 100644
index 0000000..13228ae
--- /dev/null
+++ b/checker/tests/nullness/Issue578.java
@@ -0,0 +1,16 @@
+// Test case for issue #578: https://github.com/typetools/checker-framework/issues/578
+public class Issue578 {
+  <A, B> void eval(Helper<B> helper, Interface<A> anInterface) {
+    Object o = new SomeGenericClass<>(helper.helperMethod(anInterface));
+  }
+}
+
+abstract class Helper<C> {
+  abstract <D> Interface<C> helperMethod(Interface<D> anInterface);
+}
+
+interface Interface<E> {}
+
+final class SomeGenericClass<F> {
+  SomeGenericClass(Interface<F> s) {}
+}
diff --git a/checker/tests/nullness/Issue579Error.java b/checker/tests/nullness/Issue579Error.java
new file mode 100644
index 0000000..1e9716c
--- /dev/null
+++ b/checker/tests/nullness/Issue579Error.java
@@ -0,0 +1,16 @@
+// Additional Test case for Issue579
+// https://github.com/typetools/checker-framework/issues/579
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class Issue579Error {
+
+  public <T> void foo(Generic<T> real, Generic<? super T> other, boolean flag) {
+    // :: error: (type.argument)
+    bar(flag ? real : other);
+  }
+
+  <@NonNull Q extends @NonNull Object> void bar(Generic<? extends Q> parm) {}
+
+  interface Generic<F> {}
+}
diff --git a/checker/tests/nullness/Issue580.java b/checker/tests/nullness/Issue580.java
new file mode 100644
index 0000000..b24c441
--- /dev/null
+++ b/checker/tests/nullness/Issue580.java
@@ -0,0 +1,9 @@
+// Test case for issue #580: https://github.com/typetools/checker-framework/issues/580
+
+abstract class InitCheckAssertionFailure {
+  public static <F extends Enum<F>> void noneOf(F[] array) {
+    Enum<?>[] universe = array;
+    // Accessing universe on this line causes the error.
+    int len = universe.length;
+  }
+}
diff --git a/checker/tests/nullness/Issue602.java b/checker/tests/nullness/Issue602.java
new file mode 100644
index 0000000..5c9dcde
--- /dev/null
+++ b/checker/tests/nullness/Issue602.java
@@ -0,0 +1,23 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.PolyNull;
+
+// Test case for Issue 602
+// https://github.com/typetools/checker-framework/issues/602
+// @skip-test
+public class Issue602 {
+  @PolyNull String id(@PolyNull String o) {
+    return o;
+  }
+
+  void loop(boolean condition) {
+    @NonNull String notNull = "hello";
+    String nullable = "";
+    while (condition) {
+      // :: error: (assignment)
+      notNull = nullable;
+      // :: error: (assignment)
+      notNull = id(nullable);
+      nullable = null;
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue653.java b/checker/tests/nullness/Issue653.java
new file mode 100644
index 0000000..651eddc
--- /dev/null
+++ b/checker/tests/nullness/Issue653.java
@@ -0,0 +1,37 @@
+// Test case for Issue 653
+// https://github.com/typetools/checker-framework/issues/653
+
+// @skip-test Commented out until the bug is fixed
+
+public class Issue653 {
+
+  public static @PolyNull String[] concat(
+      @PolyNull String @Nullable [] a, @PolyNull String @Nullable [] b) {
+    if (a == null) {
+      if (b == null) {
+        return new String[0];
+      } else {
+        return b;
+      }
+    } else {
+      if (b == null) {
+        return a;
+      } else {
+        @PolyNull String[] result = new String[a.length + b.length];
+
+        System.arraycopy(a, 0, result, 0, a.length);
+        System.arraycopy(b, 0, result, a.length, b.length);
+        return result;
+      }
+    }
+  }
+
+  public static String[] debugTrackPpt = {};
+
+  public static void add_track(String ppt) {
+    String[] newArray = new String[] {ppt};
+    debugTrackPpt = concat(debugTrackPpt, newArray);
+
+    debugTrackPpt = concat(debugTrackPpt, new String[] {ppt});
+  }
+}
diff --git a/checker/tests/nullness/Issue67.java b/checker/tests/nullness/Issue67.java
new file mode 100644
index 0000000..804137c
--- /dev/null
+++ b/checker/tests/nullness/Issue67.java
@@ -0,0 +1,21 @@
+// Test case for Issue 67
+// https://github.com/typetools/checker-framework/issues/67
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Issue67 {
+  private static final String KEY = "key";
+  private static final String KEY2 = "key2";
+
+  void test() {
+    Map<String, String> map = new HashMap<>();
+    if (map.containsKey(KEY)) {
+      map.get(KEY).toString(); // no problem
+    }
+    // :: warning: (nulltest.redundant)
+    if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) { // error
+      // do nothing
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue672.java b/checker/tests/nullness/Issue672.java
new file mode 100644
index 0000000..343cb34
--- /dev/null
+++ b/checker/tests/nullness/Issue672.java
@@ -0,0 +1,22 @@
+// Testcase for Issue 672
+// https://github.com/typetools/checker-framework/issues/672
+
+final class Issue672 extends Throwable {
+  final Throwable ex;
+
+  Issue672(Throwable x) {
+    ex = x;
+  }
+
+  static Issue672 test1(Throwable x, boolean flag) {
+    return new Issue672(x instanceof Exception ? x : ((flag ? x : new Issue672(x))));
+  }
+
+  static Issue672 test2(Throwable x, boolean flag) {
+    return (new Issue672(x instanceof Exception ? x : ((flag ? x : new Issue672(x)))));
+  }
+
+  static Issue672 test3(Throwable x) {
+    return test1(x instanceof Exception ? x : new Issue672(x), false);
+  }
+}
diff --git a/checker/tests/nullness/Issue679.java b/checker/tests/nullness/Issue679.java
new file mode 100644
index 0000000..a6c182f
--- /dev/null
+++ b/checker/tests/nullness/Issue679.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+// Testcase for Issue #679
+// https://github.com/typetools/checker-framework/issues/679
+// @skip-test
+public class Issue679 {
+  interface Interface<T> {}
+
+  class B implements Interface<@NonNull Number> {}
+
+  // :: error: Interface cannot be inherited with different arguments: <@NonNull Number> and
+  // <@Nullable Number>
+  class A extends B implements Interface<@Nullable Number> {}
+}
diff --git a/checker/tests/nullness/Issue738.java b/checker/tests/nullness/Issue738.java
new file mode 100644
index 0000000..99d1cd1
--- /dev/null
+++ b/checker/tests/nullness/Issue738.java
@@ -0,0 +1,39 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+// Testcase for #738
+// https://github.com/typetools/checker-framework/issues/738
+// Also, see framework/tests/all-systems/Issue738.java
+public class Issue738 {
+  void methodA(int[] is, Object @Nullable [] os, int i) {
+    // The type argument to methodB* for each call below is Cloneable & Serializable
+
+    // NullnessTransfer changes the type of an argument that is assigned to a @NonNull parameter
+    // to @NonNull. Use a switch statement to prevent this.
+    switch (i) {
+      case 1:
+        methodB(is, os);
+        break;
+      case 2:
+        // :: error: (argument)
+        methodB2(is, os);
+        break;
+      case 3:
+        // :: error: (type.argument)
+        methodB3(is, os);
+        break;
+      case 4:
+        // :: error: (type.argument)
+        methodB4(is, os);
+        break;
+    }
+  }
+
+  <T> void methodB(T paramA, T paramB) {}
+
+  <T> void methodB2(T paramA, @NonNull T paramB) {}
+
+  <@NonNull T extends @NonNull Object> void methodB3(T paramA, T paramB) {}
+
+  <T extends @NonNull Cloneable> void methodB4(T paramA, T paramB) {}
+}
diff --git a/checker/tests/nullness/Issue741.java b/checker/tests/nullness/Issue741.java
new file mode 100644
index 0000000..2661c6d
--- /dev/null
+++ b/checker/tests/nullness/Issue741.java
@@ -0,0 +1,19 @@
+// Testcase for Issue 741
+// https://github.com/typetools/checker-framework/pull/741
+// @skip-test
+public class Issue741 {
+  @SuppressWarnings("unchecked")
+  public <T> T incompatibleTypes(Object o) {
+    final T x = (T) o;
+    if (x != null) {}
+    // invaild error here
+    return x;
+  }
+
+  @SuppressWarnings("unchecked")
+  public <T> T noIncompatibleTypes(Object o) {
+    final T x = (T) o;
+    // no error here
+    return x;
+  }
+}
diff --git a/checker/tests/nullness/Issue752.java b/checker/tests/nullness/Issue752.java
new file mode 100644
index 0000000..6ad259e
--- /dev/null
+++ b/checker/tests/nullness/Issue752.java
@@ -0,0 +1,70 @@
+package a.b.c;
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue752 {
+
+  Issue752 field = new Issue752();
+  static Issue752 staticField = new Issue752();
+
+  Issue752 method() {
+    return field;
+  }
+
+  static Issue752 staticMethod() {
+    return staticField;
+  }
+
+  // A package name without a class name is not a valid JavaExpression string.
+  @RequiresNonNull("java.lang")
+  // :: error: (flowexpr.parse.error)
+  void method1() {}
+
+  @RequiresNonNull("java.lang.String.class")
+  void method2() {}
+
+  // A package name without a class name is not a valid JavaExpression string.
+  @RequiresNonNull("a.b.c")
+  // :: error: (flowexpr.parse.error)
+  void method3() {}
+
+  // notaclass does not exist.
+  @RequiresNonNull("a.b.c.notaclass")
+  // :: error: (flowexpr.parse.error)
+  void method4() {}
+
+  @RequiresNonNull("a.b.c.Issue752.class")
+  void method5() {}
+
+  @RequiresNonNull("a.b.c.Issue752.staticField")
+  void method6() {}
+
+  @RequiresNonNull("a.b.c.Issue752.staticField.field")
+  void method7() {}
+
+  // field is an instance field, and Issue752 is a class.
+  @RequiresNonNull("a.b.c.Issue752.field")
+  // :: error: (flowexpr.parse.error)
+  void method8() {}
+
+  // field is an instance field, and Issue752 is a class.
+  @RequiresNonNull("a.b.c.Issue752.field.field")
+  // :: error: (flowexpr.parse.error)
+  void method9() {}
+
+  @RequiresNonNull("a.b.c.Issue752.staticMethod()")
+  void method10() {}
+
+  @RequiresNonNull("a.b.c.Issue752.staticMethod().field")
+  void method11() {}
+
+  // method() is an instance method, and Issue752 is a class.
+  @RequiresNonNull("a.b.c.Issue752.method()")
+  // :: error: (flowexpr.parse.error)
+  void method12() {}
+
+  // method() is an instance method, and Issue752 is a class.
+  @RequiresNonNull("a.b.c.Issue752.method().field")
+  // :: error: (flowexpr.parse.error)
+  void method13() {}
+}
diff --git a/checker/tests/nullness/Issue759.java b/checker/tests/nullness/Issue759.java
new file mode 100644
index 0000000..8af54dd
--- /dev/null
+++ b/checker/tests/nullness/Issue759.java
@@ -0,0 +1,51 @@
+// Testcase for Issue759
+// https://github.com/typetools/checker-framework/issues/759
+// Also, see framework/tests/all-systems/Issue759.java
+
+import org.checkerframework.checker.nullness.qual.*;
+
+@SuppressWarnings("unchecked")
+public class Issue759 {
+  void possibleValues(final Class<? extends Enum> enumType) {
+    lowercase(enumType.getEnumConstants());
+    lowercase2(enumType.getEnumConstants());
+    lowercase3(enumType.getEnumConstants());
+  }
+
+  <T extends Enum<T>> void lowercase(final T @Nullable ... items) {}
+
+  <T extends Enum<T>> void lowercase2(final T @Nullable [] items) {}
+
+  <T> void lowercase3(final T items) {}
+}
+
+interface Gen<T extends Gen<T>> {
+  T[] getConstants();
+
+  T @Nullable [] getNullableConstants();
+}
+
+class IncompatibleTypes {
+  void possibleValues(final Gen<?> genType) {
+    lowercase(genType.getConstants());
+    lowercase(genType.getNullableConstants());
+  }
+
+  <S> void lowercase(final S items) {}
+
+  void possibleValues2(final Gen<?> genType) {
+    lowercase2(genType.getConstants());
+    // :: error: (type.argument)
+    lowercase2(genType.getNullableConstants());
+  }
+
+  <S extends Object> void lowercase2(final S items) {}
+
+  void possibleValues3(final Gen<?> genType) {
+    lowercase3(genType.getConstants());
+    // :: error: (argument)
+    lowercase3(genType.getNullableConstants());
+  }
+
+  <S> void lowercase3(final @NonNull S items) {}
+}
diff --git a/checker/tests/nullness/Issue764.java b/checker/tests/nullness/Issue764.java
new file mode 100644
index 0000000..ee5b0a7
--- /dev/null
+++ b/checker/tests/nullness/Issue764.java
@@ -0,0 +1,23 @@
+// Test case for issue #764:
+// https://github.com/typetools/checker-framework/issues/764
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue764 {
+  public static @Nullable Object field = null;
+
+  static class MyClass {
+    @RequiresNonNull("field")
+    public static void method() {}
+
+    public void otherMethod() {
+      field = new Object();
+      method();
+    }
+
+    public void otherMethod2() {
+      // :: error: (contracts.precondition)
+      method();
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue765.java b/checker/tests/nullness/Issue765.java
new file mode 100644
index 0000000..10d54fd
--- /dev/null
+++ b/checker/tests/nullness/Issue765.java
@@ -0,0 +1,14 @@
+// Test case for Issue 765
+// https://github.com/typetools/checker-framework/issues/765
+public class Issue765 {
+  Thread thread = new Thread() {};
+
+  void execute() {
+    thread =
+        new Thread() {
+          @Override
+          public void run() {}
+        };
+    thread.start();
+  }
+}
diff --git a/checker/tests/nullness/Issue811.java b/checker/tests/nullness/Issue811.java
new file mode 100644
index 0000000..976981a
--- /dev/null
+++ b/checker/tests/nullness/Issue811.java
@@ -0,0 +1,28 @@
+// Test case for Issue 266
+// https://github.com/typetools/checker-framework/issues/266
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class Issue811 {
+  static class T {
+    void xyz() {}
+  }
+
+  interface U {
+    void method();
+  }
+
+  private final @NonNull T tField;
+  private U uField;
+
+  public Issue811(@NonNull T t) {
+    tField = t;
+    uField =
+        new U() {
+          @Override
+          public void method() {
+            tField.xyz();
+          }
+        };
+  }
+}
diff --git a/checker/tests/nullness/Issue829.java b/checker/tests/nullness/Issue829.java
new file mode 100644
index 0000000..f7ded5a
--- /dev/null
+++ b/checker/tests/nullness/Issue829.java
@@ -0,0 +1,15 @@
+// Test case for Issue 829
+// https://github.com/typetools/checker-framework/issues/829
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue829 {
+  public static @Nullable Double getDouble(boolean flag) {
+    return flag ? null : 1.0;
+  }
+
+  public static Double getDoubleError(boolean flag) {
+    // :: error: (return)
+    return flag ? null : 1.0;
+  }
+}
diff --git a/checker/tests/nullness/Issue868.java b/checker/tests/nullness/Issue868.java
new file mode 100644
index 0000000..b548f32
--- /dev/null
+++ b/checker/tests/nullness/Issue868.java
@@ -0,0 +1,58 @@
+// Test case for Issue 868
+// https://github.com/typetools/checker-framework/issues/868
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue868 {
+  interface MyList {}
+
+  <E extends @Nullable Object & @Nullable MyList> void test1(E e) {
+    // :: error: (dereference.of.nullable)
+    e.toString();
+  }
+
+  <E extends Object & @Nullable MyList> void test2(E e) {
+    // :: error: (dereference.of.nullable)
+    e.toString();
+  }
+
+  <E extends @Nullable Object & MyList> void test3(E e) {
+    // :: error: (dereference.of.nullable)
+    e.toString();
+  }
+
+  <E extends Object & MyList> void test4(E e) {
+    e.toString();
+  }
+  // :: warning: (explicit.annotation.ignored)
+  <E extends @NonNull Object & @Nullable MyList> void test5(E e) {
+    e.toString();
+  }
+
+  // :: warning: (explicit.annotation.ignored)
+  <E extends @Nullable Object & @NonNull MyList> void test6(E e) {
+    // :: error: (dereference.of.nullable)
+    e.toString();
+  }
+
+  void use() {
+    this.<@Nullable MyList>test1(null);
+    this.<@Nullable MyList>test2(null);
+    this.<@Nullable MyList>test3(null);
+    // :: error: (type.argument)
+    this.<@Nullable MyList>test4(null);
+    // :: error: (type.argument)
+    this.<@Nullable MyList>test5(null);
+    this.<@Nullable MyList>test6(null);
+  }
+
+  <T extends @Nullable Object & @Nullable MyList> void use2(T t, @NonNull T nonNullT) {
+    this.<T>test1(t);
+    // :: error: (argument)
+    this.<@NonNull T>test3(t);
+    this.<@NonNull T>test3(nonNullT);
+    // :: error: (type.argument)
+    this.<T>test5(t);
+  }
+}
diff --git a/checker/tests/nullness/Issue906.java b/checker/tests/nullness/Issue906.java
new file mode 100644
index 0000000..997dcb0
--- /dev/null
+++ b/checker/tests/nullness/Issue906.java
@@ -0,0 +1,13 @@
+// Test case for Issue 906
+// https://github.com/typetools/checker-framework/issues/906
+/** @author Michael Grafl */
+public class Issue906 {
+  @SuppressWarnings("unchecked")
+  public <B, A extends B> void start(A a, Class<B> cb) {
+    // :: error: (dereference.of.nullable)
+    Class<? extends A> c = (Class<? extends A>) a.getClass();
+    x(a, c);
+  }
+
+  private <C, B extends C> void x(B a, Class<? extends B> c) {}
+}
diff --git a/checker/tests/nullness/Issue961.java b/checker/tests/nullness/Issue961.java
new file mode 100644
index 0000000..ff11a65
--- /dev/null
+++ b/checker/tests/nullness/Issue961.java
@@ -0,0 +1,48 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+// Test case for Issue 961
+// https://github.com/typetools/checker-framework/issues/961
+public class Issue961 {
+  <T extends @NonNull Object> T method(T param, Map<T, Object> map) {
+    if (map.containsKey(param)) {
+      @NonNull Object o = map.get(param);
+      return param;
+    }
+    return param;
+  }
+
+  abstract class MapContains<K extends @NonNull Object, V extends @NonNull Object> {
+    // this isn't initialized, but just ignore the error.
+    @SuppressWarnings("method.invocation")
+    V def = setDef();
+
+    Map<K, V> map = new HashMap<>();
+
+    V get(K p) {
+      if (!map.containsKey(p)) {
+        return def;
+      }
+      return map.get(p);
+    }
+
+    abstract V setDef();
+  }
+
+  class MapContains2 {
+    String get1(Map<Object, String> map, Object k) {
+      if (!map.containsKey(k)) {
+        return "";
+      }
+      return map.get(k);
+    }
+
+    <KeyTV extends @NonNull Object> String get2(Map<Object, String> map, KeyTV k) {
+      if (!map.containsKey(k)) {
+        return "";
+      }
+      return map.get(k);
+    }
+  }
+}
diff --git a/checker/tests/nullness/Issue986.java b/checker/tests/nullness/Issue986.java
new file mode 100644
index 0000000..da9e793
--- /dev/null
+++ b/checker/tests/nullness/Issue986.java
@@ -0,0 +1,35 @@
+// Test case for issue #986:
+// https://github.com/typetools/checker-framework/issues/986
+
+// @skip-test until the issue is fixed
+
+public class Issue986 {
+
+  public static void main(String[] args) {
+    String array[] = new String[3];
+    array[0].length(); // NPE here
+  }
+
+  // Flow should refine @MonotonicNonNull component types to @NonNull.
+  void testArr4(@NonNull Object @NonNull [] nno1, @MonotonicNonNull Object @NonNull [] lnno1) {
+    @MonotonicNonNull Object[] lnno2;
+    @NonNull Object[] nno2;
+    nno2 = nno1;
+    lnno2 = lnno1;
+    lnno2 = nno1;
+    // :: error: (assignment)
+    nno2 = lnno1;
+    lnno2 = NullnessUtil.castNonNullDeep(nno1);
+    nno2 = NullnessUtil.castNonNullDeep(lnno1);
+    lnno2 = NullnessUtil.castNonNullDeep(nno1);
+    nno2 = NullnessUtil.castNonNullDeep(lnno1);
+  }
+
+  // Flow should refine @MonotonicNonNull component types to @NonNull.
+  // This is a prerequisite for issue #986 (or for workarounds to issue #986).
+  void testArr5(@MonotonicNonNull Object @NonNull [] a) {
+    @MonotonicNonNull Object[] l5 = NullnessUtil.castNonNullDeep(a);
+    @NonNull Object[] l6 = l5;
+    @NonNull Object[] l7 = NullnessUtil.castNonNullDeep(a);
+  }
+}
diff --git a/checker/tests/nullness/Issue989.java b/checker/tests/nullness/Issue989.java
new file mode 100644
index 0000000..01f3117
--- /dev/null
+++ b/checker/tests/nullness/Issue989.java
@@ -0,0 +1,27 @@
+// Test case for Issue 989:
+// https://github.com/typetools/checker-framework/issues/989
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+interface ListWrapper989a<E> extends List<@NonNull E> {}
+
+interface ListWrapper989b<E> extends Serializable, List<@NonNull E> {}
+
+interface ListWrapper989c<E> extends Collection<@NonNull E>, Serializable, List<@NonNull E> {}
+
+public class Issue989 {
+  void usea(ListWrapper989a<Boolean> list) {
+    list.get(0);
+  }
+
+  void useb(ListWrapper989b<Boolean> list) {
+    list.get(0);
+  }
+
+  void usec(ListWrapper989c<Boolean> list) {
+    list.get(0);
+  }
+}
diff --git a/checker/tests/nullness/IteratorEarlyExit.java b/checker/tests/nullness/IteratorEarlyExit.java
new file mode 100644
index 0000000..44b30e5
--- /dev/null
+++ b/checker/tests/nullness/IteratorEarlyExit.java
@@ -0,0 +1,38 @@
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class IteratorEarlyExit {
+  public static void m1() {
+    List<String> array = new ArrayList<>();
+    String local = null;
+    for (String str : array) {
+      local = str;
+      break;
+    }
+    // :: error: (dereference.of.nullable)
+    System.out.println(local.length());
+  }
+
+  public static void m2() {
+    List<String> array = new ArrayList<>();
+    String local = null;
+    for (String str : array) {
+      local = str;
+    }
+    // :: error: (dereference.of.nullable)
+    System.out.println(local.length());
+  }
+
+  public static void m3() {
+    List<String> array = new ArrayList<>();
+    Object local = new Object();
+    for (String str : array) {
+      // :: error: (dereference.of.nullable)
+      System.out.println(local.toString());
+      // The next iteration might throw a NPE
+      local = null;
+    }
+  }
+}
diff --git a/checker/tests/nullness/JPanelTest.java b/checker/tests/nullness/JPanelTest.java
new file mode 100644
index 0000000..1ffe2db
--- /dev/null
+++ b/checker/tests/nullness/JPanelTest.java
@@ -0,0 +1,3 @@
+// Minimal test case for Issue #244
+// https://github.com/typetools/checker-framework/issues/244
+public class JPanelTest extends javax.swing.JPanel {}
diff --git a/checker/tests/nullness/JavaCopExplosion.java b/checker/tests/nullness/JavaCopExplosion.java
new file mode 100644
index 0000000..510ee3e
--- /dev/null
+++ b/checker/tests/nullness/JavaCopExplosion.java
@@ -0,0 +1,123 @@
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+@org.checkerframework.framework.qual.DefaultQualifier(Nullable.class)
+public class JavaCopExplosion {
+  public static class ExplosiveException extends Exception {}
+
+  @NonNull Integer m_nni = 1;
+  final String m_astring;
+
+  JavaCopExplosion() {
+    // m_nni = 1;\
+    m_astring = "hi";
+    try {
+      throw new RuntimeException();
+    } catch (Exception e) {
+      System.out.println(m_astring.length());
+    }
+    return;
+  }
+
+  static void main(String @NonNull [] args) {
+    @NonNull String s = "Dan";
+    String s2;
+    s2 = null;
+    // :: warning: (nulltest.redundant)
+    if (s2 != null || s != null) {
+      // :: error: (assignment)
+      s = s2;
+    } else {
+      s = new String("Levitan");
+    }
+    s2 = args[0];
+    // :: error: (dereference.of.nullable)
+    System.out.println("Possibly cause null pointer with this: " + s2.length());
+    // :: warning: (nulltest.redundant)
+    if (s2 == null) {
+      // do nothing
+    } else {
+      System.out.println("Can't cause null pointer here: " + s2.length());
+      s = s2;
+    }
+    // :: warning: (nulltest.redundant)
+    if (s == null ? s2 != null : s2 != null) {
+      s = s2;
+    }
+    System.out.println("Hello " + s);
+    System.out.println("Hello " + s.length());
+    f();
+  }
+
+  private static int f() {
+    while (true) {
+      try {
+        throw new ExplosiveException();
+      } finally {
+        // break;
+        return 1;
+        // throw new RuntimeException();
+      }
+    }
+  }
+
+  public static int foo() {
+    final int v;
+    int x;
+    Integer z;
+    Integer y;
+    @NonNull Integer nnz = 3;
+    z = Integer.valueOf(5);
+    try {
+      x = 3;
+      x = 5;
+      // y = z;
+      nnz = z;
+      z = null;
+      // :: error: (assignment)
+      nnz = z;
+
+      while (z == null) {
+        break;
+      }
+      // :: error: (assignment)
+      nnz = z;
+      while (z == null) {
+        // do nothing
+      }
+      nnz = z;
+      // v = 1;
+      return 1;
+      // v = 2;
+      // throw new RuntimeException ();
+    } catch (NullPointerException e) {
+      e.printStackTrace();
+      // e = null;
+      // v = 1;
+    } catch (RuntimeException e) {
+      // nnz = z;
+      // v = 2;
+    } finally {
+      // v = 1 + x;
+    }
+    return 1;
+    // return v + x;
+  }
+
+  private void bar(List<@NonNull String> ss, String b, String c) {
+    @NonNull String a;
+    // :: error: (iterating.over.nullable)
+    for (@NonNull String s : ss) {
+      a = s;
+    }
+    if (b == null || b.length() == 0) {
+      System.out.println("hey");
+    }
+    if (b != null) {
+      // :: error: (dereference.of.nullable)
+      for (; b.length() > 0; b = null) {
+        System.out.println(b.length());
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/JavaCopFlow.java b/checker/tests/nullness/JavaCopFlow.java
new file mode 100644
index 0000000..9c7c860
--- /dev/null
+++ b/checker/tests/nullness/JavaCopFlow.java
@@ -0,0 +1,220 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+@org.checkerframework.framework.qual.DefaultQualifier(Nullable.class)
+public class JavaCopFlow {
+
+  public void testIf(String str) {
+
+    // String str = "foo";
+    @NonNull String a;
+    if (str != null) {
+      a = str;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testIfNoBlock(String str) {
+
+    // String str = "foo";
+    @NonNull String a;
+    if (str != null) {
+      a = str;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testElse(String str) {
+
+    // String str = "foo";
+    @NonNull String a;
+    if (str == null) {
+      testAssert("");
+    } else {
+      a = str;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testElseNoBlock(String str) {
+
+    // String str = "foo";
+    @NonNull String a;
+    if (str == null) {
+      testAssert("");
+    } else {
+      a = str;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testReturnIf(String str) {
+
+    // String str = "foo";
+    if (str == null) {
+      testAssert("");
+      return;
+    }
+
+    @NonNull String a = str;
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testReturnElse(String str) {
+
+    //        String str = "foo";
+    if (str != null) {
+      testAssert("");
+    } else {
+      return;
+    }
+
+    @NonNull String a = str;
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testThrowIf(String str) {
+
+    // String str = "foo";
+    if (str == null) {
+      testAssert("");
+      throw new RuntimeException("foo");
+    }
+
+    @NonNull String a = str;
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testThrowElse(String str) {
+
+    // String str = "foo";
+    if (str != null) {
+      testAssert("");
+    } else {
+      throw new RuntimeException("foo");
+    }
+
+    @NonNull String a = str;
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testAssert(@Nullable String str) {
+
+    assert str != null : "@AssumeAssertion(nullness)";
+
+    @NonNull String a = str;
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testWhile(String str) {
+
+    // String str = "foo";
+    while (str != null) {
+      @NonNull String a = str;
+      break;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testIfInstanceOf(String str) {
+
+    // String str = "foo";
+    @NonNull String a;
+    if (str instanceof String) {
+      a = str;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testNew() {
+
+    String str = "foo";
+    @NonNull String a = str;
+
+    str = null;
+    // :: error: (assignment)
+    @NonNull String b = str;
+  }
+
+  public void testExit(String str) {
+
+    // String str = null;
+    if (str == null) {
+      System.exit(0);
+    }
+
+    @NonNull String a = str;
+  }
+
+  void methodThatThrowsRuntime() {
+    throw new RuntimeException();
+  }
+
+  public void retestWhile(@Nullable String str) {
+
+    while (str != null) {
+      @NonNull String a = str;
+      break;
+    }
+
+    int i = 0;
+    while (true) {
+      // :: error: (assignment)
+      @NonNull String a = str;
+      str = null;
+      i++;
+      if (i > 2) break;
+    }
+
+    str = null;
+    @NonNull String b = "hi";
+    try {
+      // :: error: (assignment)
+      b = str;
+      methodThatThrowsRuntime();
+      str = "bar";
+    } finally {
+      // :: error: (assignment)
+      b = str;
+    }
+
+    str = null;
+    // :: error: (assignment)
+    b = str;
+
+    str = "hi";
+    b = (String) str;
+  }
+}
diff --git a/checker/tests/nullness/JavaCopRandomTests.java b/checker/tests/nullness/JavaCopRandomTests.java
new file mode 100644
index 0000000..185cc9b
--- /dev/null
+++ b/checker/tests/nullness/JavaCopRandomTests.java
@@ -0,0 +1,27 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class JavaCopRandomTests {
+  final int a;
+  final int b = 1;
+  final int c;
+
+  JavaCopRandomTests() {
+    String s = null;
+    a = 2;
+  }
+
+  JavaCopRandomTests(String s) throws Exception {
+    // this();
+    a = 2;
+    if (a > 1) {
+      throw new Exception("dude");
+    }
+    throw new RuntimeException("dude");
+  }
+
+  // initializer block
+  {
+    c = 4;
+    // throw new Exception("dude");
+  }
+}
diff --git a/checker/tests/nullness/JavaExprContext.java b/checker/tests/nullness/JavaExprContext.java
new file mode 100644
index 0000000..66891e4
--- /dev/null
+++ b/checker/tests/nullness/JavaExprContext.java
@@ -0,0 +1,166 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+// See issue 241: https://github.com/typetools/checker-framework/issues/241
+
+// Cover all 8 combinations of:
+// -(Non)static class
+// -(Non)static field
+// -(Non)static method
+
+// Also test:
+// -(Non)static field initialization
+
+public class JavaExprContext {
+
+  // Classes to perform tests on
+
+  // The methods return booleans instead of void simply so they can
+  // be tested as field initializers.
+
+  public static class staticGraphClass {
+    private Map<String, Integer> adjList = new HashMap<>();
+
+    public boolean addEdge(@KeyFor("adjList") String source) {
+      return true;
+    }
+
+    public static boolean addEdge2(@KeyFor("#2.adjList") String source, staticGraphClass theGraph) {
+      return true;
+    }
+
+    public boolean addEdge3(@KeyFor("this.adjList") String source) {
+      return true;
+    }
+  }
+
+  public class nonstaticGraphClass {
+    private Map<String, Integer> adjList = new HashMap<>();
+
+    public boolean addEdge(@KeyFor("adjList") String source) {
+      return true;
+    }
+
+    public boolean addEdge2(@KeyFor("this.adjList") String source) {
+      return true;
+    }
+  }
+
+  // Non-static field initialization
+
+  staticGraphClass graphField1 = new staticGraphClass();
+  nonstaticGraphClass graphField2 = new nonstaticGraphClass();
+
+  @SuppressWarnings("assignment")
+  @KeyFor("graphField1.adjList") String key1 = "";
+
+  @SuppressWarnings("assignment")
+  @KeyFor("graphField2.adjList") String key2 = "";
+
+  boolean b1 = staticGraphClass.addEdge2(key1, graphField1);
+  boolean b2 = graphField1.addEdge(key1);
+  boolean b3 = graphField1.addEdge2(key1, graphField1);
+  boolean b4 = graphField1.addEdge3(key1);
+
+  boolean b5 = graphField2.addEdge(key2);
+  boolean b6 = graphField2.addEdge2(key2);
+
+  // Classes that perform tests
+
+  public class nonstaticTestClass {
+
+    staticGraphClass graphField1 = new staticGraphClass();
+    nonstaticGraphClass graphField2 = new nonstaticGraphClass();
+
+    public void buildGraph1(@KeyFor("graphField1.adjList") String hero) {
+      staticGraphClass.addEdge2(hero, graphField1);
+      graphField1.addEdge(hero);
+      graphField1.addEdge2(
+          hero, graphField1); // Calling a static method from an instance object. Ensuring this
+      // doesn't confuse the JavaExpression parsing.
+      graphField1.addEdge3(hero);
+    }
+
+    public void buildGraph2(@KeyFor("graphField2.adjList") String hero) {
+      graphField2.addEdge(hero);
+      graphField2.addEdge2(hero);
+    }
+
+    public void buildGraph3(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) {
+      staticGraphClass.addEdge2(hero, myGraph);
+      myGraph.addEdge(hero);
+      myGraph.addEdge2(
+          hero, myGraph); // Calling a static method from an instance object. Ensuring this
+      // doesn't confuse the JavaExpression parsing.
+      myGraph.addEdge3(hero);
+    }
+
+    public void buildGraph4(nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) {
+      myGraph.addEdge(hero);
+      myGraph.addEdge2(hero);
+    }
+  }
+
+  public static class staticTestClass {
+
+    staticGraphClass graphField1 = new staticGraphClass();
+    static staticGraphClass graphField2 = new staticGraphClass();
+
+    public void buildGraph1(@KeyFor("graphField1.adjList") String hero) {
+      staticGraphClass.addEdge2(hero, graphField1);
+      graphField1.addEdge(hero);
+      graphField1.addEdge2(
+          hero, graphField1); // Calling a static method from an instance object. Ensuring this
+      // doesn't confuse the JavaExpression parsing.
+      graphField1.addEdge3(hero);
+    }
+
+    public void buildGraph3(@KeyFor("graphField2.adjList") String hero) {
+      staticGraphClass.addEdge2(hero, graphField2);
+      graphField2.addEdge(hero);
+      graphField2.addEdge2(
+          hero, graphField2); // Calling a static method from an instance object. Ensuring this
+      // doesn't confuse the JavaExpression parsing.
+      graphField2.addEdge3(hero);
+    }
+
+    public void buildGraph5(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) {
+      staticGraphClass.addEdge2(hero, myGraph);
+      myGraph.addEdge(hero);
+      myGraph.addEdge2(
+          hero, myGraph); // Calling a static method from an instance object. Ensuring this
+      // doesn't confuse the JavaExpression parsing.
+      myGraph.addEdge3(hero);
+    }
+
+    public void buildGraph6(nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) {
+      myGraph.addEdge(hero);
+      myGraph.addEdge2(hero);
+    }
+
+    public static void buildGraph7(@KeyFor("graphField2.adjList") String hero) {
+      staticGraphClass.addEdge2(hero, graphField2);
+      graphField2.addEdge(hero);
+      graphField2.addEdge2(
+          hero, graphField2); // Calling a static method from an instance object. Ensuring this
+      // doesn't confuse the JavaExpression parsing.
+      graphField2.addEdge3(hero);
+    }
+
+    public static void buildGraph9(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) {
+      staticGraphClass.addEdge2(hero, myGraph);
+      myGraph.addEdge(hero);
+      myGraph.addEdge2(
+          hero, myGraph); // Calling a static method from an instance object. Ensuring this
+      // doesn't confuse the JavaExpression parsing.
+      myGraph.addEdge3(hero);
+    }
+
+    public static void buildGraph10(
+        nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) {
+      myGraph.addEdge(hero);
+      myGraph.addEdge2(hero);
+    }
+  }
+}
diff --git a/checker/tests/nullness/KeyForAutoboxing.java b/checker/tests/nullness/KeyForAutoboxing.java
new file mode 100644
index 0000000..c960d6e
--- /dev/null
+++ b/checker/tests/nullness/KeyForAutoboxing.java
@@ -0,0 +1,62 @@
+// Test case for issue #595:
+// https://github.com/typetools/checker-framework/issues/595
+
+// @skip-test until the issue is fixed
+
+import java.util.Map;
+
+public abstract class KeyForAutoboxing {
+
+  public void working1(Object key, Map<Object, Object> m) {
+    if (!m.containsKey(key)) {
+      m.put(key, new Object());
+    }
+    m.get(key).toString();
+  }
+
+  public void working2(Integer key, Map<Integer, Object> m) {
+    if (!m.containsKey(key)) {
+      m.put(key, new Object());
+    }
+    m.get(key).toString();
+  }
+
+  public void working3(Double key, Map<Double, Object> m) {
+    if (!m.containsKey(key)) {
+      m.put(key, new Object());
+    }
+    m.get(key).toString();
+  }
+
+  public void notWorking1(int key, Map<Integer, Object> m) {
+    if (!m.containsKey(key)) {
+      m.put(key, new Object());
+    }
+    m.get(key).toString(); // Should not generate error but does
+  }
+
+  public void notWorking2(double key, Map<Double, Object> m) {
+    if (!m.containsKey(key)) {
+      m.put(key, new Object());
+    }
+    m.get(key).toString(); // Should not generate error but does
+  }
+
+  public void notWorking3(double key, Map<Double, Object> m) {
+    if (m.containsKey(key)) {
+      m.get(key).toString(); // Should not generate error but does
+    }
+  }
+
+  public void notWorking4(double key, Map<Double, Object> m) {
+    if (m.get(key) != null) {
+      m.get(key).toString(); // Should not generate error but does
+    }
+  }
+
+  public void notWorking5(double key, Map<Double, Object> m) {
+    if (m.get(Double.valueOf(key)) != null) {
+      m.get(Double.valueOf(key)).toString(); // Should not generate error but does
+    }
+  }
+}
diff --git a/checker/tests/nullness/KeyForChecked.java b/checker/tests/nullness/KeyForChecked.java
new file mode 100644
index 0000000..9bc2dba
--- /dev/null
+++ b/checker/tests/nullness/KeyForChecked.java
@@ -0,0 +1,159 @@
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.KeyForBottom;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.qual.Covariant;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+@DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.IMPLICIT_UPPER_BOUND)
+public class KeyForChecked {
+
+  interface KFMap<@KeyForBottom K extends @NonNull Object, V extends @NonNull Object> {
+    @Covariant(0)
+    public static interface Entry<
+        @KeyForBottom K1 extends @Nullable Object, V1 extends @Nullable Object> {
+      K1 getKey();
+
+      V1 getValue();
+    }
+
+    @Pure
+    boolean containsKey(@Nullable Object a1);
+
+    @Pure
+    @Nullable V get(@Nullable Object a1);
+
+    @Nullable V put(K a1, V a2);
+
+    Set<@KeyFor("this") K> keySet();
+
+    Set<KFMap.Entry<@KeyFor("this") K, V>> entrySet();
+
+    KFIterator<K> iterator();
+  }
+
+  class KFHashMap<@KeyForBottom K2 extends @NonNull Object, V2 extends @NonNull Object>
+      implements KFMap<K2, V2> {
+    @Pure
+    public boolean containsKey(@Nullable Object a1) {
+      return false;
+    }
+
+    @Pure
+    public @Nullable V2 get(@Nullable Object a1) {
+      return null;
+    }
+
+    public @Nullable V2 put(K2 a1, V2 a2) {
+      return null;
+    }
+
+    public Set<@KeyFor("this") K2> keySet() {
+      return new HashSet<@KeyFor("this") K2>();
+    }
+
+    public Set<KFMap.Entry<@KeyFor("this") K2, V2>> entrySet() {
+      return new HashSet<KFMap.Entry<@KeyFor("this") K2, V2>>();
+    }
+
+    public KFIterator<K2> iterator() {
+      return new KFIterator<K2>();
+    }
+  }
+
+  @Covariant(0)
+  class KFIterator<@KeyForBottom E extends @Nullable Object> {}
+
+  void incorrect1(Object map) {
+    String nonkey = "";
+    // :: error: (assignment)
+    @KeyFor("map") String key = nonkey;
+  }
+
+  void correct1(Object map) {
+    String nonkey = "";
+    @SuppressWarnings("assignment")
+    @KeyFor("map") String key = nonkey;
+  }
+
+  void incorrect2() {
+    KFMap<String, Object> m = new KFHashMap<>();
+    m.put("a", new Object());
+    m.put("b", new Object());
+    m.put("c", new Object());
+
+    Collection<@KeyFor("m") String> coll = m.keySet();
+
+    @SuppressWarnings("assignment")
+    @KeyFor("m") String newkey = "new";
+
+    coll.add(newkey);
+    // TODO: at this point, the @KeyFor annotation is violated
+    m.put("new", new Object());
+  }
+
+  void correct2() {
+    KFMap<String, Object> m = new KFHashMap<>();
+    m.put("a", new Object());
+    m.put("b", new Object());
+    m.put("c", new Object());
+
+    Collection<@KeyFor("m") String> coll = m.keySet();
+
+    @SuppressWarnings("assignment")
+    @KeyFor("m") String newkey = "new";
+
+    m.put(newkey, new Object());
+    coll.add(newkey);
+  }
+
+  void iter() {
+    KFMap<String, Object> emap = new KFHashMap<>();
+    Set<@KeyFor("emap") String> s = emap.keySet();
+    Iterator<@KeyFor("emap") String> it = emap.keySet().iterator();
+    Iterator<@KeyFor("emap") String> it2 = s.iterator();
+
+    Collection<@KeyFor("emap") String> x = Collections.unmodifiableSet(emap.keySet());
+
+    for (@KeyFor("emap") String st : s) {}
+    for (String st : s) {}
+    Object bubu = new Object();
+    // :: error: (enhancedfor)
+    for (@KeyFor("bubu") String st : s) {}
+  }
+
+  <T> void dominators(KFMap<T, List<T>> preds) {
+    for (T node : preds.keySet()) {}
+
+    for (@KeyFor("preds") T node : preds.keySet()) {}
+  }
+
+  void entrySet() {
+    KFMap<String, Object> emap = new KFHashMap<>();
+    Set<KFMap.Entry<@KeyFor("emap") String, Object>> es = emap.entrySet();
+
+    // KeyFor has to be explicit on the component to Entry sets because
+    //   a) it's not clear which map the Entry set may have come from
+    //   b) and there is no guarantee the map is still accessible
+    // :: error: (assignment)
+    Set<KFMap.Entry<String, Object>> es2 = emap.entrySet();
+  }
+
+  public static <K, V> void mapToString(KFMap<K, V> m) {
+    Set<KFMap.Entry<@KeyFor("m") K, V>> eset = m.entrySet();
+
+    for (KFMap.Entry<@KeyFor("m") K, V> entry : m.entrySet()) {}
+  }
+
+  void testWF(KFMap<String, Object> m) {
+    KFIterator<String> it = m.iterator();
+  }
+}
diff --git a/checker/tests/nullness/KeyForDiamond.java b/checker/tests/nullness/KeyForDiamond.java
new file mode 100644
index 0000000..7a3a02a
--- /dev/null
+++ b/checker/tests/nullness/KeyForDiamond.java
@@ -0,0 +1,16 @@
+import java.util.*;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+
+public class KeyForDiamond {
+  private final Map<Integer, Double> map = new HashMap<>();
+
+  public void method() {
+    Map<@KeyFor("map") Integer, Double> paths = new HashMap<>();
+    Set<@KeyFor("map") Integer> set = new HashSet<>();
+    List<@KeyFor("map") Integer> list = new ArrayList<>();
+  }
+
+  public static void main(String[] args) {
+    KeyForDiamond t = new KeyForDiamond();
+  }
+}
diff --git a/checker/tests/nullness/KeyForFlow.java b/checker/tests/nullness/KeyForFlow.java
new file mode 100644
index 0000000..d5d824c
--- /dev/null
+++ b/checker/tests/nullness/KeyForFlow.java
@@ -0,0 +1,172 @@
+import java.util.HashMap;
+import java.util.Vector;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class KeyForFlow extends HashMap<String, Object> {
+
+  String k = "key";
+  HashMap<String, Object> m = new HashMap<>();
+
+  void testContainsKeyForLocalKeyAndLocalMap() {
+    String k_local = "key";
+    HashMap<String, Object> m_local = new HashMap<>();
+
+    if (m_local.containsKey(k_local)) {
+      @KeyFor("m_local") Object s = k_local;
+    }
+
+    // :: error: (assignment)
+    @KeyFor("m_local") String s2 = k_local;
+  }
+
+  void testContainsKeyForLocalKeyAndFieldMap() {
+    String k_local = "key";
+
+    if (m.containsKey(k_local)) {
+      @KeyFor("m") Object s = k_local;
+    }
+
+    // :: error: (assignment)
+    @KeyFor("m") String s2 = k_local;
+  }
+
+  void testContainsKeyForFieldKeyAndLocalMap() {
+    HashMap<String, Object> m_local = new HashMap<>();
+
+    if (m_local.containsKey(k)) {
+      @KeyFor("m_local") Object s = k;
+    }
+
+    // :: error: (assignment)
+    @KeyFor("m_local") String s2 = k;
+  }
+
+  void testContainsKeyForFieldKeyAndFieldMap() {
+    if (m.containsKey(k)) {
+      @KeyFor("m") Object s = k;
+    }
+
+    // :: error: (assignment)
+    @KeyFor("m") String s2 = k;
+  }
+
+  static String k_s = "key";
+
+  void testContainsKeyForStaticKeyAndFieldMap() {
+    if (m.containsKey(k_s)) {
+      @KeyFor("m") Object s = k_s;
+    }
+
+    // :: error: (assignment)
+    @KeyFor("m") String s2 = k_s;
+  }
+
+  static HashMap<String, Object> m_s = new HashMap<>();
+
+  void testContainsKeyForFieldKeyAndStaticMap() {
+    if (m_s.containsKey(k)) {
+      // Currently for this to work, the user must write @KeyFor("classname.static_field")
+      @KeyFor("m_s") Object s = k;
+    }
+
+    // :: error: (assignment)
+    @KeyFor("m_s") String s2 = k;
+  }
+
+  void testContainsKeyForFieldKeyAndReceiverMap() {
+    if (containsKey(k)) {
+      @KeyFor("this") Object s = k;
+    }
+
+    // :: error: (assignment)
+    @KeyFor("this") String s2 = k;
+  }
+
+  // TODO: The diamond operator does not work here:
+  //    Vector<@KeyFor("m2") String> coll = new Vector<>();
+  // Figure out why not.
+  Vector<@KeyFor("m2") String> coll = new Vector<@KeyFor("m2") String>();
+  HashMap<String, Object> m2 = new HashMap<>();
+  String k2 = "key2";
+
+  void testCallingPutAfterAdd() {
+    // :: error: (argument)
+    coll.add(k2);
+    m2.put(k2, new Object());
+  }
+
+  void testPutForLocalKeyAndLocalMap() {
+    HashMap<String, Object> m2_local = new HashMap<>();
+    Vector<@KeyFor("m2_local") String> coll_local = new Vector<>();
+    String k2_local = "key2";
+
+    m2_local.put(k2_local, new Object());
+    coll_local.add(k2_local);
+  }
+
+  void testPutForLocalKeyAndFieldMap() {
+    String k2_local = "key2";
+
+    m2.put(k2_local, new Object());
+    coll.add(k2_local);
+  }
+
+  void testPutForFieldKeyAndLocalMap() {
+    HashMap<String, Object> m2_local = new HashMap<>();
+    Vector<@KeyFor("m2_local") String> coll_local = new Vector<>();
+
+    m2_local.put(k2, new Object());
+    coll_local.add(k2);
+  }
+
+  void testPutForFieldKeyAndFieldMap() {
+    m2.put(k2, new Object());
+    coll.add(k2);
+  }
+
+  /*
+  This scenario is not working since in Vector, "this" gets translated to "coll_local".
+  The same thing happens if the collection is a field instead of a local.
+  However this seems like a low-priority scenario to enable.
+
+  void testPutForFieldKeyAndReceiverMap() {
+    Vector<@KeyFor("this") String> coll_local = new Vector<>();
+
+    put(k2, new Object());
+    coll_local.add(k2);
+  }*/
+
+  class foo {
+    public HashMap<String, Object> m = new HashMap<>();
+  }
+
+  void testContainsKeyForFieldKeyAndMapFieldOfOtherClass() {
+    foo f = new foo();
+
+    if (f.m.containsKey(k)) {
+      @KeyFor("f.m") Object s = k;
+    }
+
+    // :: error: (assignment)
+    @KeyFor("f.m") String s2 = k;
+  }
+
+  void testPutForFieldKeyAndMapFieldOfOtherClass() {
+    foo f = new foo();
+    Vector<@KeyFor("f.m") String> coll_local = new Vector<>();
+    f.m.put(k2, new Object());
+    coll_local.add(k2);
+  }
+
+  /*public void testAddToListInsteadOfMap(List<@KeyFor("#4") String> la, String b, @KeyFor("#4") String c, Map<String, String> a) {
+    // Disabled error (assignment)
+    List<String> ls1 = la;
+    List<@KeyFor("#4") String> ls2 = la;
+    ls1.add(b);
+    // Disabled error (argument)
+    la.add(b);
+    ls2.add(c);
+    la.add(c);
+    @NonNull String astr = a.get(ls2.get(0));
+  }*/
+}
diff --git a/checker/tests/nullness/KeyForIssue328.java b/checker/tests/nullness/KeyForIssue328.java
new file mode 100644
index 0000000..24ab6fa
--- /dev/null
+++ b/checker/tests/nullness/KeyForIssue328.java
@@ -0,0 +1,20 @@
+// Test case for Issue 328:
+// https://github.com/typetools/checker-framework/issues/328
+
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class KeyForIssue328 {
+  public static void m(Map<Object, Object> a, Map<Object, Object> b, Object ka, Object kb) {
+    if (a.containsKey(ka)) {
+      @NonNull Object i = a.get(ka); // OK
+    }
+    if (b.containsKey(kb)) {
+      @NonNull Object i = b.get(kb); // OK
+    }
+    if (a.containsKey(ka) && b.containsKey(kb)) {
+      @NonNull Object i = a.get(ka); // OK
+      @NonNull Object j = b.get(kb); // OK
+    }
+  }
+}
diff --git a/checker/tests/nullness/KeyForLocalSideEffect.java b/checker/tests/nullness/KeyForLocalSideEffect.java
new file mode 100644
index 0000000..c58f34c
--- /dev/null
+++ b/checker/tests/nullness/KeyForLocalSideEffect.java
@@ -0,0 +1,23 @@
+import java.util.HashMap;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class KeyForLocalSideEffect {
+
+  String k = "key";
+  HashMap<String, Integer> m = new HashMap<>();
+
+  void testContainsKeyForFieldKeyAndLocalMap() {
+    HashMap<String, Integer> m_local = m;
+
+    if (m_local.containsKey(k)) {
+      @KeyFor("m_local") String s = k;
+      havoc();
+      // TODO: This should be an error, because s is no longer a key for m_local.
+      @NonNull Integer val = m_local.get(s);
+    }
+  }
+
+  void havoc() {
+    m = new HashMap<>();
+  }
+}
diff --git a/checker/tests/nullness/KeyForLocalVariable.java b/checker/tests/nullness/KeyForLocalVariable.java
new file mode 100644
index 0000000..da164e6
--- /dev/null
+++ b/checker/tests/nullness/KeyForLocalVariable.java
@@ -0,0 +1,31 @@
+// Test for Checker Framework issue 795
+// https://github.com/typetools/checker-framework/issues/795
+
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class KeyForLocalVariable {
+
+  public static void localVariableShadowing() {
+    // :: error: (expression.unparsable)
+    @KeyFor("m0") String kk;
+    {
+      Map<String, Integer> m0 = new HashMap<>();
+      @SuppressWarnings("keyfor")
+      @KeyFor("m0") String k = "key";
+      // :: error: (assignment)
+      kk = k;
+    }
+    {
+      Map<String, Integer> m0 = new HashMap<>();
+      // :: error: (assignment)
+      @KeyFor("m0") String k2 = kk;
+    }
+  }
+
+  public static void invalidLocalVariable() {
+    // :: error: (expression.unparsable)
+    @KeyFor("foobar") String kk;
+  }
+}
diff --git a/checker/tests/nullness/KeyForLub.java b/checker/tests/nullness/KeyForLub.java
new file mode 100644
index 0000000..ef0e343
--- /dev/null
+++ b/checker/tests/nullness/KeyForLub.java
@@ -0,0 +1,41 @@
+package keyfor;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.KeyForBottom;
+import org.checkerframework.checker.nullness.qual.PolyKeyFor;
+import org.checkerframework.checker.nullness.qual.UnknownKeyFor;
+
+public class KeyForLub {
+  public static boolean flag;
+  Map<Object, Object> map1 = new HashMap<>();
+  Map<Object, Object> map2 = new HashMap<>();
+  Map<Object, Object> map3 = new HashMap<>();
+
+  void method(
+      @KeyFor({"map1", "map2"}) String key12,
+      @KeyFor({"map1", "map3"}) String key13,
+      @UnknownKeyFor String unknown) {
+    @KeyFor("map1") String key1 = flag ? key12 : key13;
+
+    // :: error: (assignment)
+    @KeyFor({"map1", "map2"}) String key2 = flag ? key12 : key13;
+
+    // :: error: (assignment)
+    @KeyFor({"map1", "map2"}) String key3 = flag ? key12 : unknown;
+  }
+
+  @PolyKeyFor String poly1(@KeyFor("map1") String key1, @PolyKeyFor String poly) {
+    // :: error: (return)
+    return flag ? key1 : poly;
+  }
+
+  void poly2(@PolyKeyFor String poly, @UnknownKeyFor String unknown, @KeyForBottom String bot) {
+    // :: error: (assignment)
+    @PolyKeyFor String s1 = flag ? poly : unknown;
+    @PolyKeyFor String s2 = flag ? poly : bot;
+    // :: error: (assignment)
+    @KeyForBottom String s3 = flag ? poly : bot;
+  }
+}
diff --git a/checker/tests/nullness/KeyForMultiple.java b/checker/tests/nullness/KeyForMultiple.java
new file mode 100644
index 0000000..58c4ac6
--- /dev/null
+++ b/checker/tests/nullness/KeyForMultiple.java
@@ -0,0 +1,43 @@
+// Test case for issue #2358: https://tinyurl.com/cfissue/#2358
+
+// @skip-test until the bug is fixed.
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+
+public class KeyForMultiple {
+
+  void m1() {
+
+    Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>();
+
+    Map<@KeyFor({"sharedBooks"}) String, Integer> sharedCounts1 = new HashMap<>();
+    Set<@KeyFor({"sharedCounts1"}) String> sharedCountsKeys1 = sharedCounts1.keySet();
+  }
+
+  void m2() {
+
+    Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>();
+
+    Map<@KeyFor({"sharedBooks"}) String, Integer> sharedCounts1 = new HashMap<>();
+    Set<@KeyFor({"sharedBooks", "sharedCounts1"}) String> otherChars1 = sharedCounts1.keySet();
+  }
+
+  void m3() {
+
+    Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>();
+
+    Map<@KeyFor({"sharedBooks", "sharedCounts2"}) String, Integer> sharedCounts2 = new HashMap<>();
+    Set<@KeyFor({"sharedCounts2"}) String> sharedCountsKeys2 = sharedCounts2.keySet();
+  }
+
+  void m4() {
+
+    Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>();
+
+    Map<@KeyFor({"sharedBooks", "sharedCounts2"}) String, Integer> sharedCounts2 = new HashMap<>();
+    Set<@KeyFor({"sharedBooks", "sharedCounts2"}) String> otherChars2 = sharedCounts2.keySet();
+  }
+}
diff --git a/checker/tests/nullness/KeyForPolymorphism.java b/checker/tests/nullness/KeyForPolymorphism.java
new file mode 100644
index 0000000..8acc3b2
--- /dev/null
+++ b/checker/tests/nullness/KeyForPolymorphism.java
@@ -0,0 +1,19 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+// test related to issue 429: https://github.com/typetools/checker-framework/issues/429
+public class KeyForPolymorphism {
+
+  Map<String, Object> m1 = new HashMap<>();
+  Map<String, Object> m2 = new HashMap<>();
+
+  void method(@KeyFor("m1") String k1m1, @KeyFor("m2") String k1m2) {
+    @KeyFor("m1") String k2m1 = identity1(k1m1);
+    @KeyFor("m2") String k2m2 = identity1(k1m2);
+  }
+
+  static @PolyKeyFor String identity1(@PolyKeyFor String arg) {
+    return arg;
+  }
+}
diff --git a/checker/tests/nullness/KeyForPostcondition.java b/checker/tests/nullness/KeyForPostcondition.java
new file mode 100644
index 0000000..8bd2dac
--- /dev/null
+++ b/checker/tests/nullness/KeyForPostcondition.java
@@ -0,0 +1,47 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.EnsuresKeyFor;
+import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+
+public class KeyForPostcondition {
+
+  public static Map<String, Integer> m = new HashMap<>();
+
+  // public static @KeyFor("m") String key = "hello";
+
+  public static boolean b;
+
+  @EnsuresKeyFor(value = "#1", map = "m")
+  public void putKey(String x) {
+    m.put(x, 22);
+  }
+
+  public void usePutKey(String x) {
+    // :: error: (assignment)
+    @KeyFor("m") String a = x;
+    putKey(x);
+    @KeyFor("m") String b = x;
+  }
+
+  @EnsuresKeyForIf(expression = "#1", result = true, map = "m")
+  public boolean tryPutKey(String x) {
+    if (b) {
+      putKey(x);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  public void useTryPutKey(String x) {
+    // :: error: (assignment)
+    @KeyFor("m") String a = x;
+    if (tryPutKey(x)) {
+      @KeyFor("m") String b = x;
+    }
+
+    // :: error: (assignment)
+    @KeyFor("m") String c = x;
+  }
+}
diff --git a/checker/tests/nullness/KeyForPropagation.java b/checker/tests/nullness/KeyForPropagation.java
new file mode 100644
index 0000000..efec4c9
--- /dev/null
+++ b/checker/tests/nullness/KeyForPropagation.java
@@ -0,0 +1,35 @@
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+
+// interface Dest<DA,DB,DC,DD,DE> {
+// }
+//
+// interface Inter1<I1A,I1B,I1C,I1D,I1E> extends Dest<I1A, I1A, I1C, I1D, String> {}
+//
+// interface Inter2<I2A,I2B,I2C,I2D,I2E> extends Dest<I2D,I2E,I2C,I2D,I2E> {}
+//
+// class Source<SA,SB,SC,SD,SE> extends HashMap<SA,SB> implements Inter2<SA,SB,SB,SD,SE> {}
+
+public class KeyForPropagation {
+
+  {
+    List<@KeyFor("a") String> a = new ArrayList<String>();
+  }
+
+  static {
+    List<@KeyFor("b") String> b = new ArrayList<String>();
+  }
+
+  List<@KeyFor("c") String> c = new ArrayList<String>();
+
+  void method() {
+    List<@KeyFor("d") String> d = new ArrayList<String>();
+  }
+
+  void method(Map<String, String> v) {
+    Set<String> ks = v.keySet();
+  }
+}
diff --git a/checker/tests/nullness/KeyForShadowing.java b/checker/tests/nullness/KeyForShadowing.java
new file mode 100644
index 0000000..54656f5
--- /dev/null
+++ b/checker/tests/nullness/KeyForShadowing.java
@@ -0,0 +1,72 @@
+// Test for Checker Framework issue 273:
+// https://github.com/typetools/checker-framework/issues/273
+
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class KeyForShadowing {
+  public static void main(String... p) {
+    Map<String, Integer> m0 = new HashMap<>();
+    Map<String, Integer> m1 = new HashMap<>();
+    String k = "key";
+    m0.put(k, 1); // k is @KeyFor("m0") after this line
+
+    // We expect an error for the next one since we are not respecting the method contract. It
+    // expects the key to be for the second parameter, not the first.
+
+    // :: error: (argument)
+    getMap3(m0, m1, k).toString();
+
+    // We expect an error for the next one since although we are respecting the method contract,
+    // since the key is for the first parameter, the Nullness Checker is misinterpreting "m1" to be
+    // the local m1 to this method, and not the first parameter to the method.
+
+    // :: error: (argument)
+    getMap2(m0, m1, k).toString();
+
+    // :: error: (argument)
+    getMap1(m0, m1, k).toString();
+
+    getMap4(m0, m1, k).toString();
+  }
+
+  public static @NonNull Integer getMap1(
+      Map<String, Integer> m1, // m1,m0 flipped
+      Map<String, Integer> m0,
+      // :: error: (expression.unparsable)
+      @KeyFor("m0") String k) {
+    // :: error: (return)
+    return m0.get(k);
+  }
+
+  public static @NonNull Integer getMap2(
+      Map<String, Integer> m1, // m1,m0 flipped
+      Map<String, Integer> m0,
+      // :: error: (expression.unparsable)
+      @KeyFor("m1") String k) {
+    // This method body is incorrect.
+    // We expect this error because we are indicating that
+    // the key is for m1, so m0.get(k) is @Nullable.
+    // :: error: (return)
+    return m0.get(k);
+  }
+
+  public static @NonNull Integer getMap3(
+      Map<String, Integer> m1, // m1,m0 flipped
+      Map<String, Integer> m0,
+      @KeyFor("#2") String k) {
+    return m0.get(k);
+  }
+
+  public static @NonNull Integer getMap4(
+      Map<String, Integer> m1, // m1,m0 flipped
+      Map<String, Integer> m0,
+      @KeyFor("#1") String k) {
+    // This method body is incorrect.
+    // We expect this error because we are indicating that
+    // the key is for m1, so m0.get(k) is @Nullable.
+    // :: error: (return)
+    return m0.get(k);
+  }
+}
diff --git a/checker/tests/nullness/KeyForStaticField.java b/checker/tests/nullness/KeyForStaticField.java
new file mode 100644
index 0000000..38c0da1
--- /dev/null
+++ b/checker/tests/nullness/KeyForStaticField.java
@@ -0,0 +1,31 @@
+// Test case for Issue 877:
+// https://github.com/typetools/checker-framework/issues/877
+// @skip-test until the issue is fixed.
+
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class KeyForStaticField {
+  @SuppressWarnings("keyfor")
+  public static final @KeyFor("this.map") String STATIC_KEY = "some text";
+
+  private Map<String, Integer> map;
+
+  public KeyForStaticField() {
+    map = new HashMap<>();
+    map.put(STATIC_KEY, 0);
+  }
+
+  /** Returns the value for the given key, which must be present in the map. */
+  public Integer getValue(@KeyFor("this.map") String key) {
+    assert map.containsKey(key) : "Map does not contain key " + key;
+    return map.get(key);
+  }
+
+  public void m(KeyForStaticField other) {
+    getValue(STATIC_KEY);
+    this.getValue(STATIC_KEY);
+    other.getValue(STATIC_KEY);
+  }
+}
diff --git a/checker/tests/nullness/KeyForSubst.java b/checker/tests/nullness/KeyForSubst.java
new file mode 100644
index 0000000..0572c08
--- /dev/null
+++ b/checker/tests/nullness/KeyForSubst.java
@@ -0,0 +1,43 @@
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class KeyForSubst {
+  /*
+  static class MyClass<T> {
+      public T next() { return null; }
+  }
+  */
+
+  @KeyFor("#1") String getMain(Object m) {
+    throw new RuntimeException();
+  }
+
+  List<@KeyFor("#1") String> getDeep(Object m) {
+    throw new RuntimeException();
+  }
+
+  @KeyFor("#1") List<@KeyFor("#2") String> getBoth(Object l, Object m) {
+    throw new RuntimeException();
+  }
+
+  // OK, I think the annotation on the index is overdoing it, but it works.
+  @KeyFor("#1") String @KeyFor("#2") [] getArray(Object l, Object m) {
+    throw new RuntimeException();
+  }
+
+  public void testAssignMain(Object lastMap) {
+    @KeyFor("lastMap") String key = getMain(lastMap);
+  }
+
+  public void testAssignDeep(Object lastMap) {
+    List<@KeyFor("lastMap") String> key = getDeep(lastMap);
+  }
+
+  public void testAssignBoth(Object lastMap, Object newMap) {
+    @KeyFor("lastMap") List<@KeyFor("newMap") String> key = getBoth(lastMap, newMap);
+  }
+
+  public void testAssignArray(Object lastMap, Object newMap) {
+    @KeyFor("lastMap") String @KeyFor("newMap") [] key = getArray(lastMap, newMap);
+  }
+}
diff --git a/checker/tests/nullness/KeyForSubtyping.java b/checker/tests/nullness/KeyForSubtyping.java
new file mode 100644
index 0000000..21908e7
--- /dev/null
+++ b/checker/tests/nullness/KeyForSubtyping.java
@@ -0,0 +1,129 @@
+import java.util.HashMap;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class KeyForSubtyping {
+  HashMap<String, String> mapA = new HashMap<>();
+  HashMap<String, String> mapB = new HashMap<>();
+  HashMap<String, String> mapC = new HashMap<>();
+
+  public void testSubtypeAssignments(
+      String not_a_key,
+      @KeyFor("this.mapA") String a,
+      @KeyFor("this.mapB") String b,
+      @KeyFor({"this.mapA", "this.mapB"}) String ab) {
+    // Try the error cases first, otherwise dataflow will change the inferred annotations on the
+    // variables such that a line of code can have an effect on a subsequent line of code. We want
+    // each of these tests to be independent.
+
+    // :: error: (assignment)
+    ab = a;
+    // :: error: (assignment)
+    ab = b;
+    // :: error: (assignment)
+    a = b;
+    // :: error: (assignment)
+    a = not_a_key;
+    // :: error: (assignment)
+    b = not_a_key;
+    // :: error: (assignment)
+    ab = not_a_key;
+
+    // Now try the success cases
+
+    a = ab;
+    b = ab;
+    not_a_key = ab;
+    not_a_key = a;
+  }
+
+  public void testDataFlow(
+      String not_yet_a_key,
+      @KeyFor("this.mapA") String a,
+      @KeyFor("this.mapB") String b,
+      @KeyFor({"this.mapA", "this.mapB"}) String ab) {
+    // Test that when a valid assignment is made, dataflow transfers the
+    // KeyFor type qualifier from the right hand side to the left hand side.
+
+    // :: error: (argument)
+    method1(not_yet_a_key);
+    not_yet_a_key = a;
+    method1(not_yet_a_key);
+
+    method1(a);
+    // :: error: (argument)
+    method1(b);
+    method1(ab);
+
+    b = ab;
+    method1(b);
+  }
+
+  public void testSetOrdering(
+      @KeyFor({"this.mapC", "this.mapA"}) String ac,
+      @KeyFor({"this.mapA", "this.mapB", "this.mapC"}) String abc) {
+    // Test that the order of elements in the set doesn't matter when doing subtyping checks,
+    // @KeyFor("A, B, C") <: @KeyFor("C, A")
+
+    // Try the error case first - see the note in method testSubtypeAssignments
+
+    // :: error: (assignment)
+    abc = ac;
+
+    ac = abc;
+  }
+
+  public void testDataflowTransitivity(
+      @KeyFor({"this.mapA"}) String a,
+      @KeyFor({"this.mapA", "this.mapB"}) String ab,
+      @KeyFor({"this.mapA", "this.mapB", "this.mapC"}) String abc) {
+    ab = abc;
+    // At this point, dataflow should have refined the type of ab to
+    // @KeyFor({"this.mapA","this.mapB","this.mapC"})
+    a = ab;
+    // At this point, dataflow should have refined the type of a to
+    // @KeyFor({"this.mapA","this.mapB","this.mapC"})
+
+    // This would not succeed without the previous two assignments, but should now because of
+    // dataflow.
+    abc = a;
+  }
+
+  private void method1(@KeyFor("this.mapA") String a) {}
+
+  private void testWithNullnessAnnotation(
+      String not_a_key,
+      @KeyFor("this.mapA") String a,
+      @KeyFor("this.mapB") String b,
+      @Nullable @KeyFor({"this.mapA", "this.mapB"}) String ab) {
+    // These fail only because a @Nullable RHS cannot be assigned to a @NonNull LHS.
+
+    // :: error: (assignment)
+    a = ab;
+    // :: error: (assignment)
+    b = ab;
+    // :: error: (assignment)
+    not_a_key = ab;
+
+    not_a_key = a; // Succeeds because both sides are @NonNull
+  }
+
+  // Test overriding
+
+  static class Super {
+    HashMap<String, String> map1 = new HashMap<>();
+    HashMap<String, String> map2 = new HashMap<>();
+
+    void method1(@KeyFor({"this.map1", "this.map2"}) String s) {}
+
+    void method2(@KeyFor("this.map1") String s) {}
+  }
+
+  static class Sub extends Super {
+    @Override
+    void method1(@KeyFor("this.map1") String s) {}
+
+    @Override
+    // :: error: (override.param)
+    void method2(@KeyFor({"this.map1", "this.map2"}) String s) {}
+  }
+}
diff --git a/checker/tests/nullness/KeyForValidation.java b/checker/tests/nullness/KeyForValidation.java
new file mode 100644
index 0000000..e7e6848
--- /dev/null
+++ b/checker/tests/nullness/KeyForValidation.java
@@ -0,0 +1,126 @@
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class KeyForValidation {
+
+  // :: error: (expression.unparsable)
+  // :: error: (initialization.static.field.uninitialized)
+  static @KeyFor("this") Object f;
+
+  // :: error: (initialization.field.uninitialized)
+  @KeyFor("this") Object g;
+
+  // :: error: (expression.unparsable)
+  void m(@KeyFor("#0") Object p) {}
+
+  // :: error: (expression.unparsable)
+  void m2(@KeyFor("#4") Object p) {}
+
+  // OK
+  void m3(@KeyFor("#2") Object p, Map m) {}
+
+  // TODO: index for a non-map
+  void m4(@KeyFor("#1") Object p, Map m) {}
+
+  // TODO: index with wrong type
+  void m4(@KeyFor("#2") String p, Map<Integer, Integer> m) {}
+
+  // :: error: (expression.unparsable)
+  // :: error: (initialization.field.uninitialized)
+  @KeyFor("INVALID") Object h;
+
+  // :: error: (initialization.field.uninitialized)
+  @KeyFor("f") Object i;
+
+  void foo(Object p) {
+    // :: error: (expression.unparsable)
+    @KeyFor("ALSOBAD") Object j;
+
+    @KeyFor("j") Object k;
+    @KeyFor("f") Object l;
+
+    @KeyFor("p") Object o;
+  }
+
+  // :: error: (expression.unparsable)
+  void foo2(@KeyFor("ALSOBAD") Object o) {}
+  // :: error: (expression.unparsable)
+  void foo3(@KeyFor("ALSOBAD") Object[] o) {}
+  // :: error: (expression.unparsable)
+  void foo4(Map<@KeyFor("ALSOBAD") Object, Object> o) {}
+  // :: error: (expression.unparsable)
+  @KeyFor("ALSOBAD") Object[] foo5() {
+    throw new RuntimeException();
+  }
+  // :: error: (expression.unparsable)
+  @KeyFor("ALSOBAD") Object foo6() {
+    throw new RuntimeException();
+  }
+  // :: error: (expression.unparsable)
+  Map<@KeyFor("ALSOBAD") Object, Object> foo7() {
+    throw new RuntimeException();
+  }
+  // :: error: (expression.unparsable)
+  <@KeyFor("ALSOBAD") T> void foo8() {
+    throw new RuntimeException();
+  }
+  // :: error: (expression.unparsable)
+  <@KeyForBottom T extends @KeyFor("ALSOBAD") Object> void foo9() {}
+  // :: error: (expression.unparsable)
+  void foo10(@KeyFor("ALSOBAD") KeyForValidation this) {}
+
+  // :: error: (expression.unparsable)
+  public void test(Set<@KeyFor("BAD") String> keySet) {
+    // :: error: (expression.unparsable)
+    new ArrayList<@KeyFor("BAD") String>(keySet);
+    // :: error: (expression.unparsable)
+    List<@KeyFor("BAD") String> list = new ArrayList<>();
+
+    // :: error: (expression.unparsable)
+    for (@KeyFor("BAD") String s : list) {}
+  }
+
+  // Test static context.
+
+  Object instanceField = new Object();
+
+  // :: error: (expression.unparsable)
+  static void bar2(@KeyFor("this.instanceField") Object o) {}
+  // :: error: (expression.unparsable)
+  static void bar3(@KeyFor("this.instanceField") Object[] o) {}
+  // :: error: (expression.unparsable)
+  static void bar4(Map<@KeyFor("this.instanceField") Object, Object> o) {}
+  // :: error: (expression.unparsable)
+  static @KeyFor("this.instanceField") Object[] bar5() {
+    throw new RuntimeException();
+  }
+  // :: error: (expression.unparsable)
+  static @KeyFor("this.instanceField") Object bar6() {
+    throw new RuntimeException();
+  }
+  // :: error: (expression.unparsable)
+  static Map<@KeyFor("this.instanceField") Object, Object> bar7() {
+    throw new RuntimeException();
+  }
+  // :: error: (expression.unparsable)
+  static <@KeyFor("this.instanceField") T> void bar8() {
+    throw new RuntimeException();
+  }
+  // :: error: (expression.unparsable)
+  static <@KeyForBottom T extends @KeyFor("this.instanceField") Object> void bar9() {}
+
+  // :: error: (expression.unparsable)
+  public static void test2(Set<@KeyFor("this.instanceField") String> keySet) {
+    // :: error: (expression.unparsable)
+    new ArrayList<@KeyFor("this.instanceField") String>(keySet);
+    // :: error: (expression.unparsable)
+    new ArrayList<@KeyFor("this.instanceField") String>();
+
+    List<String> list = new ArrayList<>();
+    // :: error: (enhancedfor) :: error: (expression.unparsable)
+    for (@KeyFor("this.instanceField") String s : list) {}
+  }
+}
diff --git a/checker/tests/nullness/KeyFor_DirectionsFinder.java b/checker/tests/nullness/KeyFor_DirectionsFinder.java
new file mode 100644
index 0000000..f5190ad
--- /dev/null
+++ b/checker/tests/nullness/KeyFor_DirectionsFinder.java
@@ -0,0 +1,44 @@
+// @skip-test
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class KeyFor_DirectionsFinder {
+
+  class GeoPoint {}
+
+  class StreetSegment {}
+
+  class Graph {
+    public void addEdge(StreetSegment endSeg, StreetSegment beginSeg) {}
+  }
+
+  public void buildGraph(List<StreetSegment> segs) {
+    Map<GeoPoint, Set<StreetSegment>> endMap = new HashMap<>();
+    Map<@KeyFor("endMap") GeoPoint, Set<StreetSegment>> beginMap = new HashMap<>();
+    Graph graph = new Graph();
+
+    for (StreetSegment seg : segs) {
+      GeoPoint p1 = new GeoPoint();
+
+      if (!(beginMap.containsKey(p1))) {
+        endMap.put(p1, new HashSet<StreetSegment>());
+        beginMap.put(p1, new HashSet<StreetSegment>());
+      }
+      endMap.get(p1).add(seg);
+      beginMap.get(p1).add(seg);
+    }
+
+    for (@KeyFor("endMap") GeoPoint p : beginMap.keySet()) {
+      for (StreetSegment beginSeg : beginMap.get(p)) {
+        for (StreetSegment endSeg : endMap.get(p)) {
+          graph.addEdge(endSeg, beginSeg); // endSeg and beginSeg are @NonNull
+        }
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/KeyFors.java b/checker/tests/nullness/KeyFors.java
new file mode 100644
index 0000000..c0761ec
--- /dev/null
+++ b/checker/tests/nullness/KeyFors.java
@@ -0,0 +1,129 @@
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class KeyFors {
+
+  public void withoutKeyFor() {
+    Map<String, String> map = new HashMap<>();
+    String key = "key";
+
+    // :: error: (assignment)
+    @NonNull String value = map.get(key);
+  }
+
+  public void withKeyFor() {
+    Map<String, String> map = new HashMap<>();
+    @SuppressWarnings("assignment")
+    @KeyFor("map") String key = "key";
+
+    @NonNull String value = map.get(key);
+  }
+
+  public void withCollection() {
+    Map<String, String> map = new HashMap<>();
+    List<@KeyFor("map") String> keys = new ArrayList<>();
+
+    @KeyFor("map") String key = keys.get(0);
+    @NonNull String value = map.get(key);
+    value = map.get(keys.get(0));
+  }
+
+  public void withIndirectReference() {
+    class Container {
+      Map<String, String> map = new HashMap<>();
+    }
+
+    Container container = new Container();
+    @SuppressWarnings("assignment")
+    @KeyFor("container.map") String key = "m";
+
+    @NonNull String value = container.map.get(key);
+  }
+
+  /** Returns a sorted version of m.keySet(). */
+  public static <K extends Comparable<? super K>, V> Collection<@KeyFor("#1") K> sortedKeySet(
+      Map<K, V> m) {
+    throw new RuntimeException();
+  }
+
+  static HashMap<Integer, Object> call_hashmap = new HashMap<>();
+
+  public void testForLoop(HashMap<String, String> lastMap) {
+    Collection<@KeyFor("lastMap") String> sorted = sortedKeySet(lastMap);
+    for (@KeyFor("lastMap") String key : sorted) {
+      @NonNull String al = lastMap.get(key);
+    }
+    for (@KeyFor("call_hashmap") Integer i : sortedKeySet(call_hashmap)) {}
+  }
+
+  static class Otherclass {
+    static Map<String, String> map = new HashMap<>();
+  }
+
+  public void testStaticKeyFor(@KeyFor("Otherclass.map") String s1, String s2) {
+    Otherclass.map.get(s1).toString();
+    // :: error: (dereference.of.nullable)
+    Otherclass.map.get(s2).toString();
+
+    Otherclass o = new Otherclass();
+    o.map.get(s1).toString();
+    // TODO:: error: (dereference.of.nullable)
+    o.map.get(s2).toString();
+  }
+
+  public class Graph<T> {
+
+    HashMap<T, List<@KeyFor("childMap") T>> childMap;
+
+    public Graph(HashMap<T, List<@KeyFor("childMap") T>> childMap) {
+      this.childMap = childMap;
+    }
+
+    public void addNode(T n) {
+      // body omitted, not relevant to test case
+    }
+
+    public void addEdge2(T parent, T child) {
+      addNode(parent);
+      @SuppressWarnings("cast.unsafe")
+      @KeyFor("childMap") T parent2 = (@KeyFor("childMap") T) parent;
+      @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent2);
+    }
+
+    // TODO: This is a feature request to have KeyFor inferred
+    //    public void addEdge3( T parent, T child ) {
+    //      addNode(parent);
+    //      parent = (@KeyFor("childMap") T) parent;
+    //      @NonNull List<T> l = childMap.get(parent);
+    //    }
+
+  }
+
+  /* TODO: add logic that after a call to "put" the first argument is
+  annotated with @KeyFor. A "@KeyForAfter" annotation to
+  support this in a general way might be overkill.
+  Similarly, for calls to "remove" we need to invalidate all (?)
+  KeyFor annotations.*/
+
+  void keyForFlow() {
+    Map<String, String> leaders = new LinkedHashMap<>();
+    Set<@KeyFor("leaders") String> varsUsedPreviously =
+        new LinkedHashSet<@KeyFor("leaders") String>();
+    String varName = "hello";
+    leaders.put(varName, "goodbye");
+    @KeyFor("leaders") String kf = varName;
+  }
+
+  public static void mapPut(String start) {
+    Map<String, Integer> n2e = new HashMap<>();
+    n2e.put(start, Integer.valueOf(0));
+    @KeyFor("n2e") String start2 = start;
+  }
+}
diff --git a/checker/tests/nullness/Lazy.java b/checker/tests/nullness/Lazy.java
new file mode 100644
index 0000000..27483da
--- /dev/null
+++ b/checker/tests/nullness/Lazy.java
@@ -0,0 +1,57 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Lazy {
+
+  @NonNull String f;
+  @MonotonicNonNull String g;
+  @MonotonicNonNull String g2;
+  @org.checkerframework.checker.nullness.qual.MonotonicNonNull String _g;
+  @org.checkerframework.checker.nullness.qual.MonotonicNonNull String _g2;
+
+  // Initialization with null is allowed for legacy reasons.
+  @MonotonicNonNull String init = null;
+
+  public Lazy() {
+    f = "";
+    // does not have to initialize g
+  }
+
+  void test() {
+    g = "";
+    test2(); // retain non-null property across method calls
+    g.toLowerCase();
+  }
+
+  void _test() {
+    _g = "";
+    test2(); // retain non-null property across method calls
+    _g.toLowerCase();
+  }
+
+  void test2() {}
+
+  void test3() {
+    // :: error: (dereference.of.nullable)
+    g.toLowerCase();
+  }
+
+  void test4() {
+    // :: error: (assignment)
+    g = null;
+    // :: error: (monotonic)
+    g = g2;
+  }
+
+  void _test3() {
+    // :: error: (dereference.of.nullable)
+    _g.toLowerCase();
+  }
+
+  void _test4() {
+    // :: error: (assignment)
+    _g = null;
+    // :: error: (monotonic)
+    _g = _g2;
+  }
+}
diff --git a/checker/tests/nullness/LazyInitialization.java b/checker/tests/nullness/LazyInitialization.java
new file mode 100644
index 0000000..74b684c
--- /dev/null
+++ b/checker/tests/nullness/LazyInitialization.java
@@ -0,0 +1,100 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class LazyInitialization {
+  @Nullable Object nullable;
+  @NonNull Object nonnull;
+  @MonotonicNonNull Object lazy;
+  @MonotonicNonNull Object lazy2 = null;
+  final @Nullable Object lazy3;
+
+  public LazyInitialization(@Nullable Object arg) {
+    lazy3 = arg;
+    nonnull = new Object();
+  }
+
+  void randomMethod() {}
+
+  void testAssignment() {
+    lazy = "m";
+    // :: error: (assignment)
+    lazy = null; // null
+  }
+
+  void testLazyBeingNull() {
+    // :: error: (dereference.of.nullable)
+    nullable.toString(); // error
+    nonnull.toString();
+    // :: error: (dereference.of.nullable)
+    lazy.toString(); // error
+    // :: error: (dereference.of.nullable)
+    lazy3.toString(); // error
+  }
+
+  void testAfterInvocation() {
+    nullable = "m";
+    nonnull = "m";
+    lazy = "m";
+    if (lazy3 == null) {
+      return;
+    }
+
+    randomMethod();
+
+    // :: error: (dereference.of.nullable)
+    nullable.toString(); // error
+    nonnull.toString();
+    lazy.toString();
+    lazy3.toString();
+  }
+
+  private double @MonotonicNonNull [] intersect;
+
+  public void check_modified(double[] a, int count) {
+    if (intersect != null) {
+      double @NonNull [] nnda = intersect;
+    }
+  }
+
+  class PptRelation1 {
+    public void init_hierarchy_new(PptTopLevel ppt, Object eq) {
+      ppt.equality_view = eq;
+      ppt.equality_view.toString();
+    }
+  }
+
+  class PptTopLevel {
+    public @MonotonicNonNull Object equality_view;
+  }
+
+  class PptRelation1b {
+    // This is the same code as in PptRelation1, but comes after the class
+    // declaration of PptTopLevel. This works as expected.
+    public void init_hierarchy_new(PptTopLevel ppt, Object eq) {
+      ppt.equality_view = eq;
+      ppt.equality_view.toString();
+    }
+  }
+
+  class PptRelation2 {
+    public @MonotonicNonNull Object equality_view2;
+
+    public void init_hierarchy_new(PptRelation2 pr1, PptRelation2 pr2, Object eq) {
+      // :: error: (dereference.of.nullable)
+      pr1.equality_view2.toString();
+
+      pr1.equality_view2 = eq;
+      pr1.equality_view2.toString();
+
+      // :: error: (dereference.of.nullable)
+      pr2.equality_view2.toString();
+      // :: error: (dereference.of.nullable)
+      this.equality_view2.toString();
+
+      pr2.equality_view2 = eq;
+      pr2.equality_view2.toString();
+
+      this.equality_view2 = eq;
+      this.equality_view2.toString();
+    }
+  }
+}
diff --git a/checker/tests/nullness/Listener.java b/checker/tests/nullness/Listener.java
new file mode 100644
index 0000000..8f08045
--- /dev/null
+++ b/checker/tests/nullness/Listener.java
@@ -0,0 +1,28 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Listener {
+
+  @NonNull String f;
+
+  public Listener() {
+    Talker w = new Talker();
+    // :: error: (argument)
+    w.register(this);
+
+    f = "abc";
+  }
+
+  public void callback() {
+    System.out.println(f.toLowerCase());
+  }
+
+  public static class Talker {
+    public void register(Listener s) {
+      s.callback();
+    }
+  }
+
+  public static void main(String[] args) {
+    new Listener();
+  }
+}
diff --git a/checker/tests/nullness/LogRecordTest.java b/checker/tests/nullness/LogRecordTest.java
new file mode 100644
index 0000000..9d605da
--- /dev/null
+++ b/checker/tests/nullness/LogRecordTest.java
@@ -0,0 +1,26 @@
+// Test Case for adding nullness annotations to java.util.logging.LogRecord
+
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+public class LogRecordTest {
+
+  void test(Level level) {
+
+    LogRecord logRecord = new LogRecord(level, null);
+
+    logRecord.setLoggerName(null);
+
+    logRecord.setResourceBundle(null);
+
+    logRecord.setSourceClassName(null);
+
+    logRecord.setMessage(null);
+
+    logRecord.setSourceMethodName(null);
+
+    logRecord.setParameters(null);
+
+    logRecord.setThrown(null);
+  }
+}
diff --git a/checker/tests/nullness/LogicOperations.java b/checker/tests/nullness/LogicOperations.java
new file mode 100644
index 0000000..7b90035
--- /dev/null
+++ b/checker/tests/nullness/LogicOperations.java
@@ -0,0 +1,72 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class LogicOperations {
+  void andTrueClause(@Nullable Object a) {
+    if (a != null && helper()) {
+      a.toString();
+    }
+  }
+
+  void andTrueClauseReverse(@Nullable Object a) {
+    if (helper() && a != null) {
+      a.toString();
+    }
+  }
+
+  void oneAndComplement(@Nullable Object a) {
+    if (a != null && helper()) {
+      a.toString();
+      return;
+    }
+    // :: error: (dereference.of.nullable)
+    a.toString(); // error
+  }
+
+  void repAndComplement(@Nullable Object a, @Nullable Object b) {
+    if (a == null && b == null) {
+      // :: error: (dereference.of.nullable)
+      a.toString(); // error
+      return;
+    }
+    // :: error: (dereference.of.nullable)
+    a.toString(); // error
+  }
+
+  void oneOrComplement(@Nullable Object a) {
+    if (a == null || helper()) {
+      // :: error: (dereference.of.nullable)
+      a.toString(); // error
+      return;
+    }
+    a.toString();
+  }
+
+  void simpleOr1(@Nullable Object a, @Nullable Object b) {
+    if (a != null || b != null) {
+      // :: error: (dereference.of.nullable)
+      a.toString(); // error
+    }
+  }
+
+  void simpleOr2(@Nullable Object a, @Nullable Object b) {
+    if (a != null || b != null) {
+      // :: error: (dereference.of.nullable)
+      b.toString(); // error
+    }
+  }
+
+  void sideeffect() {
+    Object a = "m";
+    if ((a = null) != "n") {
+      // :: error: (assignment)
+      @NonNull Object l1 = a;
+    }
+    // :: error: (assignment)
+    @NonNull Object l2 = a;
+  }
+
+  static boolean helper() {
+    return true;
+  }
+}
diff --git a/checker/tests/nullness/LubTest.java b/checker/tests/nullness/LubTest.java
new file mode 100644
index 0000000..708cb4d
--- /dev/null
+++ b/checker/tests/nullness/LubTest.java
@@ -0,0 +1,26 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class LubTest {
+
+  @Nullable String str;
+
+  public void setStr(@Nullable String text) {
+    str = text;
+  }
+
+  public @Nullable String getStr() {
+    return str;
+  }
+
+  public void ok(@Nullable LubTest t) {
+    if (t == null) {
+      this.setStr("");
+    } else {
+      this.setStr(t.getStr());
+    }
+  }
+
+  public void notok(@Nullable LubTest t) {
+    this.setStr((t == null) ? "" : t.getStr());
+  }
+}
diff --git a/checker/tests/nullness/MapGetNullable.java b/checker/tests/nullness/MapGetNullable.java
new file mode 100644
index 0000000..d648b65
--- /dev/null
+++ b/checker/tests/nullness/MapGetNullable.java
@@ -0,0 +1,146 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class MapGetNullable {
+
+  void foo0(Map<String, @Nullable Integer> m, @KeyFor("#1") String key) {
+    // :: error: (assignment)
+    @NonNull Integer val = m.get(key);
+  }
+
+  <K, V> V get0(Map<K, V> m, @KeyFor("#1") String key) {
+    return m.get(key);
+  }
+
+  public static class MyMap1<K, V> extends HashMap<K, V> {
+    // TODO: These test cases do not work yet, because of the generic types.
+    // void useget(@KeyFor("this") String k) {
+    //     V val = get(k);
+    // }
+    // void useget2(@KeyFor("this") String k) {
+    //     V val = this.get(k);
+    // }
+  }
+
+  void foo1(MyMap1<String, @Nullable Integer> m, @KeyFor("#1") String key) {
+    // :: error: (assignment)
+    @NonNull Integer val = m.get(key);
+  }
+
+  <K, V> V get1(MyMap1<K, V> m, @KeyFor("#1") String key) {
+    return m.get(key);
+  }
+
+  public static class MyMap2<V, K> extends HashMap<K, V> {}
+
+  void foo2(MyMap2<@Nullable Integer, String> m, @KeyFor("#1") String key) {
+    // :: error: (assignment)
+    @NonNull Integer val = m.get(key);
+  }
+
+  <K, V> V get2(MyMap2<V, K> m, @KeyFor("#1") String key) {
+    return m.get(key);
+  }
+
+  public static class MyMap3<K> extends HashMap<K, @Nullable Integer> {}
+
+  void foo3(MyMap3<String> m, @KeyFor("#1") String key) {
+    // :: error: (assignment)
+    @NonNull Integer val = m.get(key);
+  }
+
+  <K> @Nullable Integer get3(MyMap3<K> m, @KeyFor("#1") String key) {
+    return m.get(key);
+  }
+
+  public static class MyMap4<K> extends HashMap<K, Integer> {}
+
+  void foo4(MyMap4<String> m, @KeyFor("#1") String key) {
+    Integer val = m.get(key);
+  }
+
+  <K> Integer get4(MyMap4<K> m, @KeyFor("#1") String key) {
+    return m.get(key);
+  }
+
+  public static class MyMap5<V> extends HashMap<String, V> {}
+
+  void foo5(MyMap5<@Nullable Integer> m, @KeyFor("#1") String key) {
+    // :: error: (assignment)
+    @NonNull Integer val = m.get(key);
+  }
+
+  <V> V get5(MyMap5<V> m, @KeyFor("#1") String key) {
+    return m.get(key);
+  }
+
+  public static class MyMap6 extends HashMap<String, Integer> {
+    void useget(@KeyFor("this") String k) {
+      @NonNull Integer val = get(k);
+    }
+
+    void useget2(@KeyFor("this") String k) {
+      @NonNull Integer val = this.get(k);
+    }
+  }
+
+  void foo6(MyMap6 m, @KeyFor("#1") String key) {
+    @NonNull Integer val = m.get(key);
+  }
+
+  Integer get6(MyMap6 m, @KeyFor("#1") String key) {
+    return m.get(key);
+  }
+
+  public static class MyMap7 extends HashMap<String, @Nullable Integer> {
+    void useget(@KeyFor("this") String k) {
+      // :: error: (assignment)
+      @NonNull Integer val = get(k);
+    }
+
+    void useget2(@KeyFor("this") String k) {
+      // :: error: (assignment)
+      @NonNull Integer val = this.get(k);
+    }
+  }
+
+  void foo7(MyMap7 m, @KeyFor("#1") String key) {
+    // :: error: (assignment)
+    @NonNull Integer val = m.get(key);
+  }
+
+  Integer get7(MyMap7 m, @KeyFor("#1") String key) {
+    // :: error: (return)
+    return m.get(key);
+  }
+
+  // MyMap9 ensures that no changes are made to the return type of overloaded versions of get().
+
+  public static class MyMap9<K, V> extends HashMap<K, V> {
+    @Nullable V get(@Nullable Object key, int itIsOverloaded) {
+      return null;
+    }
+  }
+
+  void foo9(MyMap9<String, @Nullable Integer> m, @KeyFor("#1") String key) {
+    // :: error: (assignment)
+    @NonNull Integer val = m.get(key);
+  }
+
+  void foo9a(MyMap9<String, @Nullable Integer> m, @KeyFor("#1") String key) {
+    // :: error: (assignment)
+    @NonNull Integer val = m.get(key, 22);
+  }
+
+  <K, V> V get9(MyMap9<K, V> m, @KeyFor("#1") String key) {
+    return m.get(key);
+  }
+
+  <K, V> V get9a(MyMap9<K, V> m, @KeyFor("#1") String key) {
+    // :: error: (return)
+    return m.get(key, 22);
+  }
+}
diff --git a/checker/tests/nullness/MapMerge.java b/checker/tests/nullness/MapMerge.java
new file mode 100644
index 0000000..6ba00a0
--- /dev/null
+++ b/checker/tests/nullness/MapMerge.java
@@ -0,0 +1,25 @@
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+public class MapMerge {
+  public static void main(String[] args) {
+    Map<String, String> map = new HashMap<>();
+    map.put("k", "v");
+    // :: error: (return)
+    map.merge("k", "v", (a, b) -> null).toString();
+  }
+
+  void foo(Map<String, String> map) {
+    // :: error: (return)
+    merge(map, "k", "v", (a, b) -> null).toString();
+  }
+
+  <K, V> V merge(
+      Map<K, V> map,
+      K key,
+      V value,
+      BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+    return value;
+  }
+}
diff --git a/checker/tests/nullness/Marino.java b/checker/tests/nullness/Marino.java
new file mode 100644
index 0000000..39dc3c0
--- /dev/null
+++ b/checker/tests/nullness/Marino.java
@@ -0,0 +1,65 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// Test cases originally written by Dan Marino, UCLA, 10/8/2007.
+@org.checkerframework.framework.qual.DefaultQualifier(Nullable.class)
+public class Marino {
+
+  @NonNull String m_str;
+  static String ms_str;
+  String m_nullableStr;
+
+  public Marino(@NonNull String m_str, String m_nullableStr) {
+    this.m_str = m_str;
+    this.m_nullableStr = m_nullableStr;
+  }
+
+  void testWhile() throws Exception {
+    String s = "foo";
+    while (true) {
+      @NonNull String a = s;
+      System.out.println("a has length: " + a.length());
+      break;
+    }
+    int i = 1;
+    while (true) {
+
+      @NonNull String a = s; // s cannot be null here
+      s = null;
+      // :: error: (dereference.of.nullable)
+      System.out.println("hi" + s.length());
+      if (i > 2) break;
+      // :: error: (assignment)
+      a = null;
+    }
+    // Checker doesn't catch that m_str not initialized.
+    // This is Caveat 2 in the manual, but note that it is not limited to contructors.
+    System.out.println("Member string has length: " + m_str.length());
+
+    // Dereference of any static field is allowed.
+    // I suppose this is a design decision for practicality in interacting with libraries...?
+    // :: error: (dereference.of.nullable)
+    System.out.println("Member string has length: " + ms_str.length());
+    System.out.println(
+        "Everyone should get this error: "
+            +
+            // :: error: (dereference.of.nullable)
+            m_nullableStr.length());
+
+    s = null;
+    @NonNull String b = "hi";
+    try {
+      System.out.println("b has length: " + b.length());
+      methodThatThrowsEx();
+      s = "bye";
+    } finally {
+      // Checker doesn't catch that s will be null here.
+      // :: error: (assignment)
+      b = s;
+      System.out.println("b has length: " + b.length());
+    }
+  }
+
+  void methodThatThrowsEx() throws Exception {
+    throw new Exception();
+  }
+}
diff --git a/checker/tests/nullness/MethodInvocation.java b/checker/tests/nullness/MethodInvocation.java
new file mode 100644
index 0000000..b2c9ed7
--- /dev/null
+++ b/checker/tests/nullness/MethodInvocation.java
@@ -0,0 +1,33 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class MethodInvocation {
+
+  String s;
+
+  public MethodInvocation() {
+    // :: error: (method.invocation)
+    a();
+    b();
+    c();
+    s = "abc";
+  }
+
+  public MethodInvocation(boolean p) {
+    // :: error: (method.invocation)
+    a(); // still not okay to be initialized
+    s = "abc";
+  }
+
+  public void a() {}
+
+  public void b(@UnderInitialization MethodInvocation this) {
+    // :: error: (dereference.of.nullable)
+    s.hashCode();
+  }
+
+  public void c(@UnknownInitialization MethodInvocation this) {
+    // :: error: (dereference.of.nullable)
+    s.hashCode();
+  }
+}
diff --git a/checker/tests/nullness/MethodOverloadingContractsKeyFor.java b/checker/tests/nullness/MethodOverloadingContractsKeyFor.java
new file mode 100644
index 0000000..9afffdd
--- /dev/null
+++ b/checker/tests/nullness/MethodOverloadingContractsKeyFor.java
@@ -0,0 +1,41 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.EnsuresKeyFor;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class MethodOverloadingContractsKeyFor {
+
+  static class ClassA {}
+
+  static class ClassB extends ClassA {}
+
+  @Pure
+  String name(ClassA classA) {
+    return "asClassA";
+  }
+
+  @Pure
+  Object name(ClassB classB) {
+    return "asClassB";
+  }
+
+  Map<Object, Object> map = new HashMap<>();
+
+  @EnsuresKeyFor(value = "name(#1)", map = "map")
+  void put(ClassA classA) {
+    map.put(name(classA), "");
+  }
+
+  void test(ClassA classA, ClassB classB) {
+    put(classA);
+    map.get(name(classA)).toString();
+
+    put(classB);
+    // :: error: (dereference.of.nullable)
+    map.get(name(classB)).toString();
+  }
+
+  public static void main(String[] args) {
+    new MethodOverloadingContractsKeyFor().test(new ClassA(), new ClassB());
+  }
+}
diff --git a/checker/tests/nullness/MethodTypeVars4.java b/checker/tests/nullness/MethodTypeVars4.java
new file mode 100644
index 0000000..d8d92b6
--- /dev/null
+++ b/checker/tests/nullness/MethodTypeVars4.java
@@ -0,0 +1,29 @@
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+public class MethodTypeVars4 {
+  @DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.IMPLICIT_UPPER_BOUND)
+  interface I {
+    <T> T doit();
+
+    <T> List<T> doit2();
+
+    <T extends @Nullable Object> T doit3();
+  }
+
+  void f1(I i) {
+    // s is implicitly Nullable
+    String s = i.doit();
+    List<String> ls = i.doit2();
+    String s2 = i.doit3();
+  }
+
+  void f2(I i) {
+    @NonNull String s = i.doit();
+    s = i.doit3();
+    // :: error: (type.argument)
+    List<@Nullable String> ls = i.doit2();
+  }
+}
diff --git a/checker/tests/nullness/MissingBoundAnnotations.java b/checker/tests/nullness/MissingBoundAnnotations.java
new file mode 100644
index 0000000..c96a8bd
--- /dev/null
+++ b/checker/tests/nullness/MissingBoundAnnotations.java
@@ -0,0 +1,21 @@
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+public final class MissingBoundAnnotations {
+
+  public static <K extends Comparable<? super K>, V> Collection<@KeyFor("#1") K> sortedKeySet(
+      Map<K, V> m) {
+    ArrayList<@KeyFor("m") K> theKeys = new ArrayList<>(m.keySet());
+    Collections.sort(theKeys);
+    return theKeys;
+  }
+
+  public static <K extends Comparable<? super K>, V>
+      Collection<@KeyFor("#1") K> sortedKeySetSimpler(ArrayList<@KeyFor("#1") K> theKeys) {
+    Collections.sort(theKeys);
+    return theKeys;
+  }
+}
diff --git a/checker/tests/nullness/MisuseProperties.java b/checker/tests/nullness/MisuseProperties.java
new file mode 100644
index 0000000..01ad616
--- /dev/null
+++ b/checker/tests/nullness/MisuseProperties.java
@@ -0,0 +1,84 @@
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class MisuseProperties {
+
+  void propertiesToHashtable(Properties p) {
+    // :: error: (argument)
+    p.setProperty("line.separator", null);
+    // :: error: (argument)
+    p.put("line.separator", null);
+    Hashtable h = p;
+    // Error, because HashTable value has NonNull bound.
+    // TODO: false negative. See #365.
+    //// :: error: (argument) :: warning: [unchecked] unchecked call to
+    //// put(K,V) as a member of the raw type java.util.Hashtable
+    // :: warning: [unchecked] unchecked call to put(K,V) as a member of the raw type
+    // java.util.Hashtable
+    h.put("line.separator", null);
+    // :: error: (argument)
+    System.setProperty("line.separator", null);
+
+    Dictionary d1 = p;
+    // No error, because Dictionary value has Nullable bound.
+    // :: warning: [unchecked] unchecked call to put(K,V) as a member of the raw type
+    // java.util.Dictionary
+    d1.put("line.separator", null);
+
+    // :: error: (assignment)
+    Dictionary<Object, @Nullable Object> d2 = p;
+    d2.put("line.separator", null);
+
+    // :: error: (clear.system.property)
+    System.setProperties(p); // OK; p has no null values
+
+    System.clearProperty("foo.bar"); // OK
+
+    // Each of the following should cause an error, because it leaves line.separator null.
+
+    // These first few need to be special-cased, I think:
+
+    // :: error: (clear.system.property)
+    System.clearProperty("line.separator");
+
+    p.remove("line.separator");
+    p.clear();
+
+    // These are OK because they seem to only add, not remove, properties:
+    // p.load(InputStream), p.load(Reader), p.loadFromXML(InputStream)
+
+    // The following problems are a result of treating a Properties as one
+    // of its supertypes.  Here are some solutions:
+    //  * Forbid treating a Properties object as any of its supertypes.
+    //  * Create an annotation on a Properties object, such as
+    //    @HasSystemProperties, and forbid some operations (or any
+    //    treatment as a supertype) for such properties.
+
+    Set<@KeyFor("p") Object> keys = p.keySet();
+    // now remove  "line.separator" from the set
+    keys.remove("line.separator");
+    keys.removeAll(keys);
+    keys.clear();
+    keys.retainAll(Collections.EMPTY_SET);
+
+    Set<Map.Entry<@KeyFor("p") Object, Object>> entries = p.entrySet();
+    // now remove the pair containing "line.separator" from the set, as above
+
+    Collection<Object> values = p.values();
+    // now remove the line separator value from values, as above
+
+    Hashtable h9 = p;
+    h9.remove("line.separator");
+    h9.clear();
+    // also access via entrySet, keySet, values
+
+    Dictionary d9 = p;
+    d9.remove("line.separator");
+  }
+}
diff --git a/checker/tests/nullness/MonotonicNonNullFieldTest.java b/checker/tests/nullness/MonotonicNonNullFieldTest.java
new file mode 100644
index 0000000..3666d5d
--- /dev/null
+++ b/checker/tests/nullness/MonotonicNonNullFieldTest.java
@@ -0,0 +1,23 @@
+// Testcase for Issue553
+// https://github.com/typetools/checker-framework/issues/553
+import org.checkerframework.checker.nullness.qual.*;
+
+public class MonotonicNonNullFieldTest {
+  class Data {
+    @MonotonicNonNull Object field;
+  }
+
+  void method(Object object) {}
+
+  @RequiresNonNull("#1.field")
+  void test(final Data data) {
+    method(data.field); // checks OK
+
+    Runnable callback =
+        new Runnable() {
+          public void run() {
+            method(data.field); // used to issue error
+          }
+        };
+  }
+}
diff --git a/checker/tests/nullness/MonotonicNonNullTest.java b/checker/tests/nullness/MonotonicNonNullTest.java
new file mode 100644
index 0000000..8059c00
--- /dev/null
+++ b/checker/tests/nullness/MonotonicNonNullTest.java
@@ -0,0 +1,16 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public final class MonotonicNonNullTest {
+
+  public static @MonotonicNonNull Boolean new_decl_format = null;
+
+  static final class SerialFormat {
+
+    public boolean new_decl_format = false;
+
+    @RequiresNonNull("MonotonicNonNullTest.new_decl_format")
+    public SerialFormat() {
+      this.new_decl_format = MonotonicNonNullTest.new_decl_format;
+    }
+  }
+}
diff --git a/checker/tests/nullness/MultiAnnotations.java b/checker/tests/nullness/MultiAnnotations.java
new file mode 100644
index 0000000..e0c2a78
--- /dev/null
+++ b/checker/tests/nullness/MultiAnnotations.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.interning.qual.Interned;
+
+public final @Interned class MultiAnnotations {
+
+  private MultiAnnotations() {}
+
+  public static final MultiAnnotations NO_CHANGE = new MultiAnnotations();
+
+  MultiAnnotations foo() {
+    return MultiAnnotations.NO_CHANGE;
+  }
+}
diff --git a/checker/tests/nullness/MultiConstructorInit.java b/checker/tests/nullness/MultiConstructorInit.java
new file mode 100644
index 0000000..a0091a7
--- /dev/null
+++ b/checker/tests/nullness/MultiConstructorInit.java
@@ -0,0 +1,27 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class MultiConstructorInit {
+
+  String a;
+
+  public MultiConstructorInit(boolean t) {
+    a = "";
+  }
+
+  public MultiConstructorInit() {
+    this(true);
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public MultiConstructorInit(int t) {
+    new MultiConstructorInit();
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public MultiConstructorInit(float t) {}
+
+  public static void main(String[] args) {
+    new MultiConstructorInit();
+  }
+}
diff --git a/checker/tests/nullness/MultipleErrors.java b/checker/tests/nullness/MultipleErrors.java
new file mode 100644
index 0000000..cfa942b
--- /dev/null
+++ b/checker/tests/nullness/MultipleErrors.java
@@ -0,0 +1,17 @@
+// Make sure that errors in multiple types in
+// the same compilation unit are all shown.
+
+class MultipleErrors1 {
+  // :: error: (assignment)
+  Object o1 = null;
+}
+
+class MultipleErrors2 {
+  // :: error: (assignment)
+  Object o2 = null;
+}
+
+interface MultipleErrors3 {
+  // :: error: (assignment)
+  Object o3 = null;
+}
diff --git a/checker/tests/nullness/MyException.java b/checker/tests/nullness/MyException.java
new file mode 100644
index 0000000..eefe652
--- /dev/null
+++ b/checker/tests/nullness/MyException.java
@@ -0,0 +1,22 @@
+@org.checkerframework.framework.qual.DefaultQualifier(
+    org.checkerframework.checker.nullness.qual.Nullable.class)
+public class MyException extends Exception {
+
+  public MyException() {}
+
+  public final String getTotalTrace() {
+    final StringBuilder sb = new StringBuilder();
+    // :: error: (iterating.over.nullable)
+    for (StackTraceElement st : getStackTrace()) {
+      // :: error: (dereference.of.nullable)
+      sb.append(st.toString());
+      sb.append(System.lineSeparator());
+    }
+    return sb.toString();
+  }
+
+  @SuppressWarnings("nullness")
+  public StackTraceElement[] getStackTrace() {
+    throw new RuntimeException("not implemented yet");
+  }
+}
diff --git a/checker/tests/nullness/NNOEMoreTests.java b/checker/tests/nullness/NNOEMoreTests.java
new file mode 100644
index 0000000..988efec
--- /dev/null
+++ b/checker/tests/nullness/NNOEMoreTests.java
@@ -0,0 +1,47 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public class NNOEMoreTests {
+  class NNOEMain {
+    protected @Nullable String nullable = null;
+    @Nullable String otherNullable = null;
+
+    @RequiresNonNull("nullable")
+    void test1() {
+      nullable.toString();
+    }
+
+    @RequiresNonNull("xxx")
+    // :: error: (flowexpr.parse.error)
+    void test2() {
+      // :: error: (dereference.of.nullable)
+      nullable.toString();
+    }
+  }
+
+  class NNOESeparate {
+    void call1(NNOEMain p) {
+      // :: error: (contracts.precondition)
+      p.test1();
+
+      Object xxx = new Object();
+      // :: error: (flowexpr.parse.error)
+      p.test2();
+    }
+
+    void call2(NNOEMain p) {
+      p.nullable = "";
+      p.test1();
+    }
+  }
+
+  @Nullable Object field1;
+
+  @RequiresNonNull("field1")
+  void methWithIf1() {
+    if (5 < 99) {
+    } else {
+      field1.hashCode();
+    }
+  }
+}
diff --git a/checker/tests/nullness/NNOEStaticFields.java b/checker/tests/nullness/NNOEStaticFields.java
new file mode 100644
index 0000000..b725b10
--- /dev/null
+++ b/checker/tests/nullness/NNOEStaticFields.java
@@ -0,0 +1,114 @@
+import java.util.Collections;
+import java.util.Set;
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public class NNOEStaticFields {
+  static @Nullable String nullable = null;
+  static @Nullable String otherNullable = null;
+
+  @RequiresNonNull("nullable")
+  void testF() {
+    nullable.toString();
+  }
+
+  @RequiresNonNull("NNOEStaticFields.nullable")
+  void testF2() {
+    nullable.toString();
+  }
+
+  @RequiresNonNull("nullable")
+  void testF3() {
+    NNOEStaticFields.nullable.toString();
+  }
+
+  @RequiresNonNull("NNOEStaticFields.nullable")
+  void testF4() {
+    NNOEStaticFields.nullable.toString();
+  }
+
+  class Inner {
+    void m1(NNOEStaticFields out) {
+      NNOEStaticFields.nullable = "haha!";
+      out.testF4();
+    }
+
+    @RequiresNonNull("NNOEStaticFields.nullable")
+    void m2(NNOEStaticFields out) {
+      out.testF4();
+    }
+  }
+
+  @RequiresNonNull("NoClueWhatThisShouldBe")
+  // :: error: (flowexpr.parse.error)
+  void testF5() {
+    // :: error: (dereference.of.nullable)
+    NNOEStaticFields.nullable.toString();
+  }
+
+  void trueNegative() {
+    // :: error: (dereference.of.nullable)
+    nullable.toString();
+    // :: error: (dereference.of.nullable)
+    otherNullable.toString();
+  }
+
+  @RequiresNonNull("nullable")
+  void test1() {
+    nullable.toString();
+    // :: error: (dereference.of.nullable)
+    otherNullable.toString();
+  }
+
+  @RequiresNonNull("otherNullable")
+  void test2() {
+    // :: error: (dereference.of.nullable)
+    nullable.toString();
+    otherNullable.toString();
+  }
+
+  @RequiresNonNull({"nullable", "otherNullable"})
+  void test3() {
+    nullable.toString();
+    otherNullable.toString();
+  }
+
+  @RequiresNonNull("System.out")
+  void test4() {
+    @NonNull Object f = System.out;
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Copied from Daikon's ChicoryPremain
+  ///
+
+  static class ChicoryPremain1 {
+
+    // Non-null if doPurity == true
+    private static @MonotonicNonNull Set<String> pureMethods = null;
+
+    private static boolean doPurity = false;
+
+    @EnsuresNonNullIf(result = true, expression = "ChicoryPremain1.pureMethods")
+    // this postcondition cannot be proved with the Checker Framework, as the relation
+    // between doPurity and pureMethods is not explicit
+    public static boolean shouldDoPurity() {
+      // :: error: (contracts.conditional.postcondition)
+      return doPurity;
+    }
+
+    @RequiresNonNull("ChicoryPremain1.pureMethods")
+    public static Set<String> getPureMethods() {
+      return Collections.unmodifiableSet(pureMethods);
+    }
+  }
+
+  static class ClassInfo1 {
+    public void initViaReflection() {
+      if (ChicoryPremain1.shouldDoPurity()) {
+        for (String pureMeth : ChicoryPremain1.getPureMethods()) {}
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/NegatingConditionalNullness.java b/checker/tests/nullness/NegatingConditionalNullness.java
new file mode 100644
index 0000000..c28602d
--- /dev/null
+++ b/checker/tests/nullness/NegatingConditionalNullness.java
@@ -0,0 +1,57 @@
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+public class NegatingConditionalNullness {
+  public @MonotonicNonNull List<Object> splitters = null;
+
+  @EnsuresNonNullIf(result = true, expression = "splitters")
+  public boolean has_splitters() {
+    return (splitters != null);
+  }
+
+  static void test(NegatingConditionalNullness ppt) {
+    if (!ppt.has_splitters()) {
+      return;
+    }
+    @NonNull Object s2 = ppt.splitters;
+  }
+
+  static void testAssert(NegatingConditionalNullness ppt) {
+    assert ppt.has_splitters() : "@AssumeAssertion(nullness)";
+    @NonNull Object s2 = ppt.splitters;
+  }
+
+  static void testSimple(NegatingConditionalNullness ppt) {
+    if (ppt.has_splitters()) {
+      @NonNull Object s2 = ppt.splitters;
+    }
+  }
+
+  // False tests
+  static void testFalse(NegatingConditionalNullness ppt) {
+    // :: error: (dereference.of.nullable)
+    ppt.splitters.toString(); // error
+  }
+
+  static void testFalseNoAssertion(NegatingConditionalNullness ppt) {
+    ppt.has_splitters();
+    // :: error: (dereference.of.nullable)
+    ppt.splitters.toString(); // error
+  }
+
+  static void testFalseIf(NegatingConditionalNullness ppt) {
+    if (ppt.has_splitters()) {
+      return;
+    }
+    // :: error: (dereference.of.nullable)
+    ppt.splitters.toString(); // error
+  }
+
+  //    static void testFalseIfBody(NegatingConditionalNullness ppt) {
+  //        if (!ppt.has_splitters()) {
+  //            // :: error: (dereference.of.nullable)
+  //            ppt.splitters.toString();   // error
+  //        }
+  //    }
+}
diff --git a/checker/tests/nullness/NewNullable.java b/checker/tests/nullness/NewNullable.java
new file mode 100644
index 0000000..2f91a35
--- /dev/null
+++ b/checker/tests/nullness/NewNullable.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class NewNullable {
+  Object o = new Object();
+  Object nn = new @NonNull Object();
+  // :: warning: (new.class)
+  @Nullable Object lazy = new @MonotonicNonNull Object();
+  // :: warning: (new.class)
+  @Nullable Object poly = new @PolyNull Object();
+  // :: warning: (new.class)
+  @Nullable Object nbl = new @Nullable Object();
+}
diff --git a/checker/tests/nullness/NewObjectNonNull.java b/checker/tests/nullness/NewObjectNonNull.java
new file mode 100644
index 0000000..41a426a
--- /dev/null
+++ b/checker/tests/nullness/NewObjectNonNull.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+public class NewObjectNonNull {
+  @DefaultQualifier(Nullable.class)
+  class A {
+    A() {}
+  }
+
+  void m() {
+    new A().toString();
+  }
+}
diff --git a/checker/tests/nullness/NonEmptyCollection.java b/checker/tests/nullness/NonEmptyCollection.java
new file mode 100644
index 0000000..8745f46
--- /dev/null
+++ b/checker/tests/nullness/NonEmptyCollection.java
@@ -0,0 +1,49 @@
+import java.io.*;
+import java.util.SortedMap;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+
+public class NonEmptyCollection {
+
+  public static @NonNull String returnRemove(@NonNull PriorityQueue1<@NonNull String> pq) {
+    return pq.remove();
+  }
+
+  public static @NonNull String returnPoll1(PriorityQueue1<@NonNull String> pq) {
+    // :: error: (return)
+    return pq.poll();
+  }
+
+  public static @NonNull String returnPoll2(PriorityQueue1<@NonNull String> pq) {
+    if (pq.isEmpty()) {
+      return "hello";
+    } else {
+      return pq.poll();
+    }
+  }
+
+  public static @NonNull String returnFirstKey(SortedMap<String, String> sm) {
+    return sm.firstKey();
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Helper classes copied from JDK
+  ///
+
+  public class PriorityQueue1<E> {
+    @SuppressWarnings("purity") // object creation is forbidden in pure methods
+    @org.checkerframework.dataflow.qual.Pure
+    public @Nullable E poll() {
+      throw new RuntimeException("skeleton method");
+    }
+
+    public E remove() {
+      throw new RuntimeException("skeleton method");
+    }
+
+    @EnsuresNonNullIf(result = false, expression = "poll()")
+    public boolean isEmpty() {
+      throw new RuntimeException("skeleton method");
+    }
+  }
+}
diff --git a/checker/tests/nullness/NonNullInitialization.java b/checker/tests/nullness/NonNullInitialization.java
new file mode 100644
index 0000000..a8acf65
--- /dev/null
+++ b/checker/tests/nullness/NonNullInitialization.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class NonNullInitialization {
+  private String test = "test";
+
+  public static void main(String[] args) {
+    NonNullInitialization n = new NonNullInitialization();
+    n.test.equals("ASD");
+  }
+}
diff --git a/checker/tests/nullness/NonNullIteratorNext.java b/checker/tests/nullness/NonNullIteratorNext.java
new file mode 100644
index 0000000..bd2e94b
--- /dev/null
+++ b/checker/tests/nullness/NonNullIteratorNext.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class NonNullIteratorNext {
+  interface MyIterator<E> extends java.util.Iterator<E> {
+    @NonNull E next();
+  }
+
+  interface MyList<E> extends java.util.Collection<E> {
+    MyIterator<E> iterator();
+  }
+
+  <T> void forEachLoop(MyList<T> list) {
+    for (T elem : list) {}
+  }
+}
diff --git a/checker/tests/nullness/NullableArrays.java b/checker/tests/nullness/NullableArrays.java
new file mode 100644
index 0000000..f068d18
--- /dev/null
+++ b/checker/tests/nullness/NullableArrays.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class NullableArrays {
+  private byte @Nullable [] padding;
+
+  public NullableArrays(byte @Nullable [] padding) {
+    this.padding = padding;
+  }
+}
diff --git a/checker/tests/nullness/NullableConstructor.java b/checker/tests/nullness/NullableConstructor.java
new file mode 100644
index 0000000..a6f8d4b
--- /dev/null
+++ b/checker/tests/nullness/NullableConstructor.java
@@ -0,0 +1,7 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class NullableConstructor {
+
+  // :: error: (nullness.on.constructor)
+  @Nullable NullableConstructor() {}
+}
diff --git a/checker/tests/nullness/NullnessFieldInvar.java b/checker/tests/nullness/NullnessFieldInvar.java
new file mode 100644
index 0000000..85c12b5
--- /dev/null
+++ b/checker/tests/nullness/NullnessFieldInvar.java
@@ -0,0 +1,174 @@
+package fieldinvar;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.FieldInvariant;
+
+public class NullnessFieldInvar {
+  public class Super {
+    public final @Nullable Object o;
+    public @Nullable Object nonfinal = null;
+
+    public Super(@Nullable Object o) {
+      this.o = o;
+    }
+  }
+
+  @FieldInvariant(field = "o", qualifier = NonNull.class)
+  class Sub extends Super {
+    public final @Nullable Object subO;
+
+    public Sub(@NonNull Object o) {
+      super(o);
+      subO = null;
+    }
+
+    public Sub(@NonNull Object o, @Nullable Object subO) {
+      super(o);
+      this.subO = subO;
+    }
+
+    void test() {
+      @NonNull Object x1 = this.o;
+      @NonNull Object x2 = o;
+      @NonNull Object x3 = super.o;
+    }
+  }
+
+  class SubSub1 extends Sub {
+    public SubSub1(@NonNull Object o) {
+      super(o);
+    }
+  }
+
+  @FieldInvariant(
+      field = {"o", "subO"},
+      qualifier = NonNull.class)
+  class SubSub2 extends Sub {
+    public SubSub2(@NonNull Object o) {
+      super(o);
+    }
+  }
+
+  class Use {
+    void test(Super superO, Sub sub, SubSub1 subSub1, SubSub2 subSub2) {
+      // :: error: (assignment)
+      @NonNull Object x1 = superO.o;
+      @NonNull Object x2 = sub.o;
+      @NonNull Object x3 = subSub1.o;
+
+      // :: error: (assignment)
+      @NonNull Object x5 = sub.subO;
+      // :: error: (assignment)
+      @NonNull Object x6 = subSub1.subO;
+      @NonNull Object x7 = subSub2.subO;
+    }
+
+    <SP extends Super, SB extends Sub, SS1 extends SubSub1, SS2 extends SubSub2> void test2(
+        SP superO, SB sub, SS1 subSub1, SS2 subSub2) {
+      // :: error: (assignment)
+      @NonNull Object x1 = superO.o;
+      @NonNull Object x2 = sub.o;
+      @NonNull Object x3 = subSub1.o;
+
+      // :: error: (assignment)
+      @NonNull Object x5 = sub.subO;
+      // :: error: (assignment)
+      @NonNull Object x6 = subSub1.subO;
+      @NonNull Object x7 = subSub2.subO;
+    }
+  }
+
+  class SuperWithNonFinal {
+    @Nullable Object nonfinal = null;
+  }
+  // nonfinal isn't final
+  // :: error: (field.invariant.not.final)
+  @FieldInvariant(field = "nonfinal", qualifier = NonNull.class)
+  class SubSubInvalid extends SuperWithNonFinal {}
+
+  // field is declared in this class
+  // :: error: (field.invariant.not.found)
+  @FieldInvariant(field = "field", qualifier = NonNull.class)
+  class Invalid {
+    final Object field = new Object();
+  }
+
+  @FieldInvariant(
+      field = {"o", "subO"},
+      qualifier = NonNull.class)
+  class Shadowing extends SubSub2 {
+    @Nullable Object o;
+    @Nullable Object subO;
+
+    void test() {
+      // :: error: (assignment)
+      @NonNull Object x = o; // error
+      // :: error: (assignment)
+      @NonNull Object x2 = subO; // error
+
+      @NonNull Object x3 = super.o;
+      @NonNull Object x4 = super.subO;
+    }
+
+    public Shadowing() {
+      super("");
+    }
+  }
+
+  // inherits: @FieldInvariant(field = {"o", "subO"}, qualifier = NonNull.class)
+  class Inherits extends SubSub2 {
+    @Nullable Object o;
+    @Nullable Object subO;
+
+    void test() {
+      // :: error: (assignment)
+      @NonNull Object x = o; // error
+      // :: error: (assignment)
+      @NonNull Object x2 = subO; // error
+
+      @NonNull Object x3 = super.o;
+      @NonNull Object x4 = super.subO;
+    }
+
+    public Inherits() {
+      super("");
+    }
+  }
+
+  class Super2 {}
+
+  // :: error: (field.invariant.not.wellformed)
+  @FieldInvariant(
+      field = {},
+      qualifier = NonNull.class)
+  class Invalid1 extends Super2 {}
+  // :: error: (field.invariant.not.wellformed)
+  @FieldInvariant(
+      field = {"a", "b"},
+      qualifier = {NonNull.class, NonNull.class, NonNull.class})
+  class Invalid2 extends Super2 {}
+
+  // :: error: (field.invariant.not.found)
+  @FieldInvariant(field = "x", qualifier = NonNull.class)
+  class NoSuper {}
+
+  class SuperManyFields {
+    public final @Nullable Object field1 = null;
+    public final @Nullable Object field2 = null;
+    public final @Nullable Object field3 = null;
+    public final @Nullable Object field4 = null;
+  }
+
+  @FieldInvariant(
+      field = {"field1", "field2", "field3", "field4"},
+      qualifier = NonNull.class)
+  class SubManyFields extends SuperManyFields {
+    void test() {
+      field1.toString();
+      field2.toString();
+      field3.toString();
+      field4.toString();
+    }
+  }
+}
diff --git a/checker/tests/nullness/ObjectArrayParam.java b/checker/tests/nullness/ObjectArrayParam.java
new file mode 100644
index 0000000..70c560a
--- /dev/null
+++ b/checker/tests/nullness/ObjectArrayParam.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+class ObjectArrayParam {
+  void test(@UnknownInitialization Object... args) {
+    for (Object obj : args) {
+      boolean isClass = obj instanceof Class<?>;
+      // :: error: initialization.cast
+      @Initialized @NonNull Class<?> clazz = (isClass ? (@Initialized @NonNull Class<?>) obj : obj.getClass());
+    }
+  }
+}
diff --git a/checker/tests/nullness/ObjectListParam.java b/checker/tests/nullness/ObjectListParam.java
new file mode 100644
index 0000000..9082b7b
--- /dev/null
+++ b/checker/tests/nullness/ObjectListParam.java
@@ -0,0 +1,14 @@
+import java.util.List;
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+class ObjectListParam {
+  // :: error: type.argument
+  void test(List<@UnknownInitialization Object> args) {
+    for (Object obj : args) {
+      boolean isClass = obj instanceof Class<?>;
+      // :: error: initialization.cast
+      @Initialized Class<?> clazz = (isClass ? (@Initialized Class<?>) obj : obj.getClass());
+    }
+  }
+}
diff --git a/checker/tests/nullness/ObjectsRequireNonNull.java b/checker/tests/nullness/ObjectsRequireNonNull.java
new file mode 100644
index 0000000..1e0f9f2
--- /dev/null
+++ b/checker/tests/nullness/ObjectsRequireNonNull.java
@@ -0,0 +1,16 @@
+// Test case for https://tinyurl.com/cfissue/3149 .
+
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class ObjectsRequireNonNull {
+  void foo(@Nullable Object nble, @NonNull Object nn) {
+    // :: error: (argument)
+    Objects.requireNonNull(null);
+    // :: error: (argument)
+    Objects.requireNonNull(nble);
+    Objects.requireNonNull("hello");
+    Objects.requireNonNull(nn);
+  }
+}
diff --git a/checker/tests/nullness/ObjectsRequireNonNullElse.java b/checker/tests/nullness/ObjectsRequireNonNullElse.java
new file mode 100644
index 0000000..160c641
--- /dev/null
+++ b/checker/tests/nullness/ObjectsRequireNonNullElse.java
@@ -0,0 +1,18 @@
+// Test case for https://tinyurl.com/cfissue/3056 and https://tinyurl.com/cfissue/3149 .
+// Test case for https://tinyurl.com/cfissue/3149 .
+// @below-java11-jdk-skip-test
+
+import static java.util.Objects.requireNonNullElse;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class ObjectsRequireNonNullElse {
+  public static void main(String[] args) {
+    @NonNull String value = requireNonNullElse(null, "Something");
+    System.err.println(requireNonNullElse(null, "Something"));
+
+    // This should fail typechecks, because it fails at run time.
+    // :: error: (argument)
+    System.err.println((Object) requireNonNullElse(null, null));
+  }
+}
diff --git a/checker/tests/nullness/OverrideANNA.java b/checker/tests/nullness/OverrideANNA.java
new file mode 100644
index 0000000..00c074a
--- /dev/null
+++ b/checker/tests/nullness/OverrideANNA.java
@@ -0,0 +1,28 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class OverrideANNA {
+  static class Super {
+    Object f;
+
+    @EnsuresNonNull("f")
+    void setf(@UnknownInitialization Super this) {
+      f = new Object();
+    }
+
+    Super() {
+      setf();
+    }
+  }
+
+  static class Sub extends Super {
+    @Override
+    // :: error: (contracts.postcondition)
+    void setf(@UnknownInitialization Sub this) {}
+  }
+
+  public static void main(String[] args) {
+    Super s = new Sub();
+    s.f.hashCode();
+  }
+}
diff --git a/checker/tests/nullness/OverrideANNA2.java b/checker/tests/nullness/OverrideANNA2.java
new file mode 100644
index 0000000..95f4b35
--- /dev/null
+++ b/checker/tests/nullness/OverrideANNA2.java
@@ -0,0 +1,39 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class OverrideANNA2 {
+  static class Super {
+    Object f;
+
+    @EnsuresNonNull("f") // Super.f must be non-null
+    void setf(@UnknownInitialization Super this) {
+      f = new Object();
+    }
+
+    Super() {
+      setf();
+    }
+  }
+
+  static class Sub extends Super {
+    Object f; // This shadows super.f
+
+    @Override
+    @EnsuresNonNull("f")
+    // We cannot ensure that Super.f is non-null since it is
+    // shadowed by Sub.f, hence we get an error.
+    // :: error: (contracts.postcondition.override)
+    void setf(@UnknownInitialization Sub this) {
+      f = new Object();
+    }
+
+    Sub() {
+      setf();
+    }
+  }
+
+  public static void main(String[] args) {
+    Super s = new Sub();
+    s.f.hashCode();
+  }
+}
diff --git a/checker/tests/nullness/OverrideANNA3.java b/checker/tests/nullness/OverrideANNA3.java
new file mode 100644
index 0000000..17beeb4
--- /dev/null
+++ b/checker/tests/nullness/OverrideANNA3.java
@@ -0,0 +1,33 @@
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+
+public class OverrideANNA3 {
+  static class Super {
+    Object f;
+    Object g;
+
+    @EnsuresNonNull({"f", "g"})
+    void setfg(@UnknownInitialization Super this) {
+      f = new Object();
+      g = new Object();
+    }
+
+    Super() {
+      setfg();
+    }
+  }
+
+  static class Sub extends Super {
+    @Override
+    @EnsuresNonNull("f")
+    // :: error: (contracts.postcondition.override)
+    void setfg(@UnknownInitialization Sub this) {
+      f = new Object();
+    }
+  }
+
+  public static void main(String[] args) {
+    Super s = new Sub();
+    s.g.hashCode();
+  }
+}
diff --git a/checker/tests/nullness/OverrideGenerics.java b/checker/tests/nullness/OverrideGenerics.java
new file mode 100644
index 0000000..920f016
--- /dev/null
+++ b/checker/tests/nullness/OverrideGenerics.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+class Super<S extends @Nullable Object> {
+  public void m(S p) {}
+}
+
+class Impl1<T extends @NonNull Object> extends Super<T> {
+  public void m(T p) {}
+}
+
+class Impl2<T> extends Super<T> {
+  public void m(T p) {}
+}
diff --git a/checker/tests/nullness/OverrideNNOE.java b/checker/tests/nullness/OverrideNNOE.java
new file mode 100644
index 0000000..b203b19
--- /dev/null
+++ b/checker/tests/nullness/OverrideNNOE.java
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public class OverrideNNOE {
+  static class Super {
+    @Nullable Object f;
+
+    void call() {}
+  }
+
+  static class Sub extends Super {
+    @Override
+    @RequiresNonNull("f")
+    // :: error: (contracts.precondition.override)
+    void call() {
+      f.hashCode();
+    }
+  }
+
+  public static void main(String[] args) {
+    Super s = new Sub();
+    s.call();
+  }
+}
diff --git a/checker/tests/nullness/OverrideNNOE2.java b/checker/tests/nullness/OverrideNNOE2.java
new file mode 100644
index 0000000..cc7c945
--- /dev/null
+++ b/checker/tests/nullness/OverrideNNOE2.java
@@ -0,0 +1,28 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public class OverrideNNOE2 {
+  static class Super {
+    @Nullable Object f;
+
+    @RequiresNonNull("f")
+    void call() {}
+  }
+
+  static class Sub extends Super {
+    @Nullable Object g;
+
+    @Override
+    @RequiresNonNull({"f", "g"})
+    // :: error: (contracts.precondition.override)
+    void call() {
+      g.hashCode();
+    }
+  }
+
+  public static void main(String[] args) {
+    Super s = new Sub();
+    s.f = new Object();
+    s.call();
+  }
+}
diff --git a/checker/tests/nullness/PackageDecl.java b/checker/tests/nullness/PackageDecl.java
new file mode 100644
index 0000000..1e299be
--- /dev/null
+++ b/checker/tests/nullness/PackageDecl.java
@@ -0,0 +1,5 @@
+package foo.bar;
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class PackageDecl {}
diff --git a/checker/tests/nullness/ParameterExpression.java b/checker/tests/nullness/ParameterExpression.java
new file mode 100644
index 0000000..39eb741
--- /dev/null
+++ b/checker/tests/nullness/ParameterExpression.java
@@ -0,0 +1,170 @@
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ParameterExpression {
+  public void m1(
+      @Nullable Object o, @Nullable Object o1, @Nullable Object o2, @Nullable Object o3) {
+    // :: error: (flowexpr.parse.error.postcondition)
+    m2(o);
+    // :: error: (dereference.of.nullable)
+    o.toString();
+    m3(o);
+    o.toString();
+    m4(o1, o2, o3);
+    // :: error: (dereference.of.nullable)
+    o1.toString();
+    // :: error: (dereference.of.nullable)
+    o2.toString();
+    o3.toString();
+  }
+
+  @SuppressWarnings("assert.postcondition")
+  // "#0" is illegal syntax; it should be "#1"
+  @EnsuresNonNull("#0")
+  // :: error: (flowexpr.parse.error)
+  public void m2(final @Nullable Object o) {}
+
+  @SuppressWarnings("contracts.postcondition")
+  @EnsuresNonNull("#1")
+  public void m3(final @Nullable Object o) {}
+
+  @SuppressWarnings("contracts.postcondition")
+  @EnsuresNonNull("#3")
+  public void m4(@Nullable Object x1, @Nullable Object x2, final @Nullable Object x3) {}
+
+  // Formal parameter names should not be used in signatures (pre/postcondition, conditional
+  // postcondition, and formal parameter annotations).  Use "#paramNum", because the parameter
+  // names are not saved in bytecode.
+
+  @Nullable Object field = null;
+
+  // Postconditions
+  @EnsuresNonNull("field") // OK
+  public void m5() {
+    field = new Object();
+  }
+
+  @EnsuresNonNull("param")
+  // :: error: (flowexpr.parse.error)
+  public void m6a(Object param) {
+    param = new Object();
+  }
+
+  @EnsuresNonNull("param")
+  // :: error: (flowexpr.parse.error)
+  public void m6b(Object param) {
+    // :: error: (assignment)
+    param = null;
+  }
+
+  @EnsuresNonNull("param")
+  // :: error: (flowexpr.parse.error)
+  public void m6c(@Nullable Object param) {
+    param = new Object();
+  }
+
+  @EnsuresNonNull("param")
+  // :: error: (flowexpr.parse.error)
+  public void m6d(@Nullable Object param) {
+    param = null;
+  }
+
+  @EnsuresNonNull("param.toString()")
+  // :: error: (flowexpr.parse.error)
+  public void m6e(@Nullable Object param) {
+    param = null;
+  }
+
+  @EnsuresNonNull("field")
+  // :: error: (contracts.postcondition)
+  // :: warning: (expression.parameter.name.shadows.field)
+  public void m7a(Object field) {
+    field = new Object();
+  }
+
+  @EnsuresNonNull("field")
+  // :: error: (contracts.postcondition)
+  // :: warning: (expression.parameter.name.shadows.field)
+  public void m7b(Object field) {
+    // :: error: (assignment)
+    field = null;
+  }
+
+  @EnsuresNonNull("field")
+  // :: error: (contracts.postcondition)
+  // :: warning: (expression.parameter.name.shadows.field)
+  public void m7c(@Nullable Object field) {
+    field = new Object();
+  }
+
+  @EnsuresNonNull("field")
+  // :: error: (contracts.postcondition)
+  // :: warning: (expression.parameter.name.shadows.field)
+  public void m7d(@Nullable Object field) {
+    field = null;
+  }
+
+  // Preconditions
+  @RequiresNonNull("field") // OK
+  public void m8() {}
+
+  @RequiresNonNull("param")
+  // :: error: (flowexpr.parse.error)
+  public void m9(Object param) {}
+
+  // Warning issued. 'field' is a field, but in this case what matters is that it is the name of a
+  // formal parameter.
+  @RequiresNonNull("field")
+  // :: warning: (expression.parameter.name.shadows.field)
+  public void m10(Object field) {}
+
+  // Conditional postconditions
+  @EnsuresNonNullIf(result = true, expression = "field") // OK
+  public boolean m11() {
+    field = new Object();
+    return true;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "param")
+  // :: error: (flowexpr.parse.error)
+  public boolean m12(Object param) {
+    param = new Object();
+    return true;
+  }
+
+  // Warning issued. 'field' is a field, but in this case what matters is that it is the name of a
+  // formal parameter.
+  @EnsuresNonNullIf(result = true, expression = "field")
+  // :: warning: (expression.parameter.name.shadows.field)
+  public boolean m13a(@Nullable Object field) {
+    field = new Object();
+    // :: error: (contracts.conditional.postcondition)
+    return true;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "field")
+  // :: warning: (expression.parameter.name.shadows.field)
+  public boolean m13b(@Nullable Object field) {
+    field = new Object();
+    return false;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "field")
+  // :: warning: (expression.parameter.name.shadows.field)
+  public boolean m13c(@Nullable Object field) {
+    field = null;
+    // :: error: (contracts.conditional.postcondition)
+    return true;
+  }
+
+  @EnsuresNonNullIf(result = true, expression = "field")
+  // :: warning: (expression.parameter.name.shadows.field)
+  public boolean m13d(@Nullable Object field) {
+    field = null;
+    return false;
+  }
+
+  // Annotations on formal parameters referring to a formal parameter of the same method.
+  // :: error: (expression.unparsable)
+  public void m14(@KeyFor("param2") Object param1, Map<Object, Object> param2) {}
+}
diff --git a/checker/tests/nullness/Polymorphism.java b/checker/tests/nullness/Polymorphism.java
new file mode 100644
index 0000000..902c024
--- /dev/null
+++ b/checker/tests/nullness/Polymorphism.java
@@ -0,0 +1,40 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Polymorphism {
+  // Test parameters
+  @PolyNull String identity(@PolyNull String s) {
+    return s;
+  }
+
+  void testParam() {
+    // Test without inference
+    String nullable = null;
+    @NonNull String nonNull = "m";
+
+    // :: error: (assignment)
+    nonNull = identity(nullable); // invalid
+    nonNull = identity(nonNull);
+
+    // test flow
+    nullable = "m";
+    nonNull = identity(nullable); // valid
+  }
+
+  // Test within a method
+  @PolyNull String random(@PolyNull String m) {
+    if (m == "d") {
+      // :: error: (return)
+      return null; // invalid
+    }
+    return "m"; // valid
+  }
+
+  public static @PolyNull Object staticIdentity(@PolyNull Object a) {
+    return a;
+  }
+
+  void testStatic(@Nullable Object nullable, @NonNull Object nonnull) {
+    @Nullable Object nullable2 = staticIdentity(nullable);
+    @NonNull Object nonnull2 = staticIdentity(nonnull);
+  }
+}
diff --git a/checker/tests/nullness/PolymorphismArrays.java b/checker/tests/nullness/PolymorphismArrays.java
new file mode 100644
index 0000000..0681fb4
--- /dev/null
+++ b/checker/tests/nullness/PolymorphismArrays.java
@@ -0,0 +1,57 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class PolymorphismArrays {
+
+  public PolymorphismArrays(String[][] elts) {
+    this.elts = elts;
+  }
+
+  public static boolean @PolyNull [] bad(boolean @PolyNull [] seq) {
+    // Cannot directly return null;
+    // :: error: (return)
+    return null;
+  }
+
+  public static boolean @PolyNull [] slice(boolean @PolyNull [] seq, int start, int end) {
+    // Know from comparison that argument is nullable -> also return is nullable.
+    if (seq == null) {
+      return null;
+    }
+    return new boolean[] {};
+  }
+
+  public static boolean @PolyNull [] slice(boolean @PolyNull [] seq, long start, int end) {
+    return slice(seq, (int) start, end);
+  }
+
+  public static @PolyNull String[] intern(@PolyNull String[] a) {
+    return a;
+  }
+
+  // from OneOfStringSequence.java
+  private String[][] elts;
+
+  @SuppressWarnings("purity") // ignore, analysis too strict.
+  @org.checkerframework.dataflow.qual.Pure
+  public PolymorphismArrays clone() {
+    PolymorphismArrays result = new PolymorphismArrays(elts.clone());
+    for (int i = 0; i < elts.length; i++) {
+      result.elts[i] = intern(elts[i].clone());
+    }
+    return result;
+  }
+
+  public void simplified() {
+    String[][] elts = new String[0][0];
+    String[][] clone = elts.clone();
+    String[] results = intern(elts[0].clone());
+  }
+
+  public static <T> int indexOf(T[] a) {
+    return indexOfEq(a);
+  }
+
+  public static int indexOfEq(@PolyNull Object[] a) {
+    return -1;
+  }
+}
diff --git a/checker/tests/nullness/PostconditionBug.java b/checker/tests/nullness/PostconditionBug.java
new file mode 100644
index 0000000..064af64
--- /dev/null
+++ b/checker/tests/nullness/PostconditionBug.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class PostconditionBug {
+
+  void a(@UnknownInitialization PostconditionBug this) {
+    @NonNull String f = "abc";
+    // :: error: (assignment)
+    f = null;
+  }
+}
diff --git a/checker/tests/nullness/PreventClearProperty.java b/checker/tests/nullness/PreventClearProperty.java
new file mode 100644
index 0000000..2aa77ca
--- /dev/null
+++ b/checker/tests/nullness/PreventClearProperty.java
@@ -0,0 +1,127 @@
+// Same code (but different expected errors) as test PermitClearProperty.java .
+
+import java.util.Properties;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class PreventClearProperty {
+
+  static final @StringVal("line.separator") String LINE_SEPARATOR = "line.separator";
+
+  static final @StringVal("my.property.name") String MY_PROPERTY_NAME = "my.property.name";
+
+  @NonNull String getLineSeparator1() {
+    return System.getProperty("line.separator");
+  }
+
+  @NonNull String getLineSeparator2() {
+    // :: error: (return)
+    return System.getProperty(LINE_SEPARATOR);
+  }
+
+  @NonNull String getMyProperty1() {
+    // :: error: (return)
+    return System.getProperty("my.property.name");
+  }
+
+  @NonNull String getMyProperty2() {
+    // :: error: (return)
+    return System.getProperty(MY_PROPERTY_NAME);
+  }
+
+  @NonNull String getAProperty(String propName) {
+    // :: error: (return)
+    return System.getProperty(propName);
+  }
+
+  @NonNull String clearLineSeparator1() {
+    // :: error: (return)
+    // :: error: (clear.system.property)
+    return System.clearProperty("line.separator");
+  }
+
+  @NonNull String clearLineSeparator2() {
+    // :: error: (return)
+    // :: error: (clear.system.property)
+    return System.clearProperty(LINE_SEPARATOR);
+  }
+
+  @NonNull String clearMyProperty1() {
+    // :: error: (return)
+    return System.clearProperty("my.property.name");
+  }
+
+  @NonNull String clearMyProperty2() {
+    // :: error: (return)
+    // :: error: (clear.system.property)
+    return System.clearProperty(MY_PROPERTY_NAME);
+  }
+
+  @NonNull String clearAProperty(String propName) {
+    // :: error: (return)
+    // :: error: (clear.system.property)
+    return System.clearProperty(propName);
+  }
+
+  void callSetProperties(Properties p) {
+    // :: error: (clear.system.property)
+    System.setProperties(p);
+  }
+
+  // All calls to setProperty are legal because they cannot unset a property.
+
+  @NonNull String setLineSeparator1() {
+    return System.setProperty("line.separator", "somevalue");
+  }
+
+  @NonNull String setLineSeparator2() {
+    // :: error: (return)
+    return System.setProperty(LINE_SEPARATOR, "somevalue");
+  }
+
+  @NonNull String setMyProperty1() {
+    // :: error: (return)
+    return System.setProperty("my.property.name", "somevalue");
+  }
+
+  @NonNull String setMyProperty2() {
+    // :: error: (return)
+    return System.setProperty(MY_PROPERTY_NAME, "somevalue");
+  }
+
+  @NonNull String setAProperty(String propName) {
+    // :: error: (return)
+    return System.setProperty(propName, "somevalue");
+  }
+
+  // These calls to setProperty are illegal because null is not a permitted value.
+
+  @NonNull String setLineSeparatorNull1() {
+    // :: error: (argument)
+    return System.setProperty("line.separator", null);
+  }
+
+  @NonNull String setLineSeparatorNull2() {
+    // :: error: (argument)
+    // :: error: (return)
+    return System.setProperty(LINE_SEPARATOR, null);
+  }
+
+  @NonNull String setMyPropertyNull1() {
+    // :: error: (argument)
+    // :: error: (return)
+    return System.setProperty("my.property.name", null);
+  }
+
+  @NonNull String setMyPropertyNull2() {
+    // :: error: (argument)
+    // :: error: (return)
+    return System.setProperty(MY_PROPERTY_NAME, null);
+  }
+
+  @NonNull String setAPropertyNull(String propName) {
+    // :: error: (argument)
+    // :: error: (return)
+    return System.setProperty(propName, null);
+  }
+}
diff --git a/checker/tests/nullness/PrimitivesNullness.java b/checker/tests/nullness/PrimitivesNullness.java
new file mode 100644
index 0000000..570a563
--- /dev/null
+++ b/checker/tests/nullness/PrimitivesNullness.java
@@ -0,0 +1,6 @@
+public class PrimitivesNullness {
+
+  public static void main(String[] args) {
+    for (String line : new String[] {"a"}) {}
+  }
+}
diff --git a/checker/tests/nullness/PrivateMethodUnknownInit.java b/checker/tests/nullness/PrivateMethodUnknownInit.java
new file mode 100644
index 0000000..efaa460
--- /dev/null
+++ b/checker/tests/nullness/PrivateMethodUnknownInit.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.initialization.qual.*;
+
+public class PrivateMethodUnknownInit {
+
+  int x;
+
+  public PrivateMethodUnknownInit() {
+    x = 1;
+    m1();
+    // :: error: (method.invocation)
+    m2();
+  }
+
+  private void m1(
+      @UnknownInitialization(PrivateMethodUnknownInit.class) PrivateMethodUnknownInit this) {}
+
+  public void m2() {}
+}
diff --git a/checker/tests/nullness/PureTest.java b/checker/tests/nullness/PureTest.java
new file mode 100644
index 0000000..2523da4
--- /dev/null
+++ b/checker/tests/nullness/PureTest.java
@@ -0,0 +1,132 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.dataflow.qual.*;
+
+public class PureTest {
+  @org.checkerframework.dataflow.qual.Pure
+  @Nullable Object puremethod(@Nullable Object a) {
+    return a;
+  }
+
+  public void test() {
+    // :: error: (assignment)
+    @NonNull Object l0 = puremethod(null);
+
+    if (puremethod(null) == null) {
+      // :: error: (assignment)
+      @NonNull Object l1 = puremethod(null);
+    }
+
+    if (puremethod("m") != null) {
+      @NonNull Object l1 = puremethod("m");
+    }
+
+    if (puremethod("m") != null) {
+      // :: error: (assignment)
+      @NonNull Object l1 = puremethod(null);
+    }
+
+    if (puremethod("m") != null) {
+      // :: error: (assignment)
+      @NonNull Object l1 = puremethod("n");
+    }
+
+    Object x = new Object();
+
+    if (puremethod(x) == null) {
+      return;
+    }
+
+    @NonNull Object l2 = puremethod(x);
+
+    x = new Object();
+
+    // :: error: (assignment)
+    @NonNull Object l3 = puremethod(x);
+
+    // :: error: (assignment)
+    @NonNull Object l4 = puremethod("n");
+  }
+
+  public @org.checkerframework.dataflow.qual.Pure @Nullable Object getSuperclass() {
+    return null;
+  }
+
+  static void shortCircuitAnd(PureTest pt) {
+    if ((pt.getSuperclass() != null) && pt.getSuperclass().equals(Enum.class)) {
+      // empty body
+    }
+  }
+
+  static void shortCircuitOr(PureTest pt) {
+    if ((pt.getSuperclass() == null) || pt.getSuperclass().equals(Enum.class)) {
+      // empty body
+    }
+  }
+
+  static void testInstanceofNegative(PureTest pt) {
+    if (pt.getSuperclass() instanceof Object) {
+      return;
+    }
+    // :: error: (dereference.of.nullable)
+    pt.getSuperclass().toString();
+  }
+
+  static void testInstanceofPositive(PureTest pt) {
+    if (!(pt.getSuperclass() instanceof Object)) {
+      return;
+    }
+    pt.getSuperclass().toString();
+  }
+
+  static void testInstanceofPositive2(PureTest pt) {
+    if (!(pt.getSuperclass() instanceof Object)) {
+    } else {
+      pt.getSuperclass().toString();
+    }
+  }
+
+  static void testInstanceofNegative2(PureTest pt) {
+    if (pt.getSuperclass() instanceof Object) {
+    } else {
+      return;
+    }
+    pt.getSuperclass().toString();
+  }
+
+  static void testInstanceofString(PureTest pt) {
+    if (!(pt.getSuperclass() instanceof String)) {
+      return;
+    }
+    pt.getSuperclass().toString();
+  }
+
+  static void testContinue(PureTest pt) {
+    for (; ; ) {
+      if (pt.getSuperclass() == null) {
+        System.out.println("m");
+        continue;
+      }
+      pt.getSuperclass().toString();
+    }
+  }
+
+  void setSuperclass(@Nullable Object no) {
+    // set the field returned by getSuperclass.
+  }
+
+  static void testInstanceofPositive3(PureTest pt) {
+    if (!(pt.getSuperclass() instanceof Object)) {
+      return;
+    } else {
+      pt.setSuperclass(null);
+    }
+    // :: error: (dereference.of.nullable)
+    pt.getSuperclass().toString();
+  }
+
+  @Override
+  @SideEffectFree
+  public String toString() {
+    return "foo";
+  }
+}
diff --git a/checker/tests/nullness/README b/checker/tests/nullness/README
new file mode 100644
index 0000000..fa0e619
--- /dev/null
+++ b/checker/tests/nullness/README
@@ -0,0 +1,6 @@
+To add a new file to the test suite, just add it to this directory.
+For more details, see
+  ../README
+
+To run the tests, do
+  (cd $CHECKERFRAMEWORK && ./gradlew NullnessTest)
diff --git a/checker/tests/nullness/Raw2.java b/checker/tests/nullness/Raw2.java
new file mode 100644
index 0000000..73654ed
--- /dev/null
+++ b/checker/tests/nullness/Raw2.java
@@ -0,0 +1,30 @@
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Raw2 {
+  private @NonNull Object field;
+  // :: error: (initialization.fields.uninitialized)
+  public Raw2(int i) {
+    this.method(this);
+  }
+
+  public Raw2() {
+    try {
+      this.method(this);
+    } catch (NullPointerException e) {
+      e.printStackTrace();
+    }
+    field = 0L;
+  }
+
+  private void method(@UnknownInitialization Raw2 this, @UnknownInitialization Raw2 arg) {
+    // :: error: (dereference.of.nullable)
+    arg.field.hashCode();
+    // :: error: (dereference.of.nullable)
+    this.field.hashCode();
+  }
+
+  public static void test() {
+    new Raw2();
+  }
+}
diff --git a/checker/tests/nullness/RawAndPrimitive.java b/checker/tests/nullness/RawAndPrimitive.java
new file mode 100644
index 0000000..1b106ce
--- /dev/null
+++ b/checker/tests/nullness/RawAndPrimitive.java
@@ -0,0 +1,15 @@
+public class RawAndPrimitive<T> {
+  public T foo(T startValue) {
+    return startValue;
+  }
+
+  // this tests that DefaultTypeHierarchy.visitPrimitive_Wildcard works
+  public static void bar(float f) {
+    // the lower bound of the resultant wildcard (which replaces the raw type argument) will be
+    // lower than the default annotation on float
+
+    // :: warning: [unchecked] unchecked call to foo(T) as a member of the raw type
+    // RawAndPrimitive
+    new RawAndPrimitive().foo(f);
+  }
+}
diff --git a/checker/tests/nullness/RawField.java b/checker/tests/nullness/RawField.java
new file mode 100644
index 0000000..ab89451
--- /dev/null
+++ b/checker/tests/nullness/RawField.java
@@ -0,0 +1,68 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+// This test does not correctly work in the FBC system,
+// see https://github.com/typetools/checker-framework/issues/223
+class RawField {
+
+  public @UnknownInitialization RawField a;
+
+  public RawField() {
+    // :: error: (assignment)
+    a = null;
+    this.a = this;
+    a = this;
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public RawField(boolean foo) {}
+
+  void t1() {
+    // :: error: (method.invocation)
+    a.t1();
+  }
+
+  void t2(@UnknownInitialization RawField a) {
+    this.a = a;
+  }
+}
+
+class Options {
+
+  @UnknownInitialization Object arg;
+
+  public Options(@UnknownInitialization Object arg) {
+    this.arg = arg;
+  }
+
+  public void parse_or_usage() {
+    // use arg only under the assumption that it is @UnknownInitialization
+  }
+}
+
+class MultiVersionControl {
+
+  @SuppressWarnings("nullness") // see https://github.com/typetools/checker-framework/issues/223
+  public void parseArgs(@UnknownInitialization MultiVersionControl this) {
+    Options options = new Options(this);
+    options.parse_or_usage();
+  }
+}
+
+// TODO: This checks that forbidden field assignments do not occur.  (The
+// FBC type system permits arbitrary assignments in a constructor, but it
+// also makes assumptions that our implementation does not currently check.)
+// class HasStaticUnknownInitializationField {
+//     static @UnknownInitialization Object f;
+// }
+//
+// class UseUnknownInitializationField {
+//
+//     Object f;
+//
+//     public UseUnknownInitializationField() {
+//         // :: (initialization.field.write.in.constructor)
+//         f = HasStaticUnknownInitializationField.f;
+//     }
+//
+// }
diff --git a/checker/tests/nullness/RawInt.java b/checker/tests/nullness/RawInt.java
new file mode 100644
index 0000000..2337e2b
--- /dev/null
+++ b/checker/tests/nullness/RawInt.java
@@ -0,0 +1,15 @@
+public class RawInt {
+
+  public void compare(int name1in2, int name2in1, VarInfo vi) {
+    int cmp1 = (name1in2 == -1) ? 0 : vi.varinfo_index - name1in2;
+    MathMDE.sign(cmp1);
+  }
+}
+
+class VarInfo {
+  int varinfo_index;
+}
+
+class MathMDE {
+  public static void sign(int a) {}
+}
diff --git a/checker/tests/nullness/RawInt2.java b/checker/tests/nullness/RawInt2.java
new file mode 100644
index 0000000..f936301
--- /dev/null
+++ b/checker/tests/nullness/RawInt2.java
@@ -0,0 +1,18 @@
+public abstract class RawInt2 {
+
+  public void compare(MyVarInfo vi1, MyVarInfo vi2) {
+
+    int name1in2 = 1;
+    int name2in1 = 2;
+    int cmp1 = (name1in2 == -1) ? 0 : vi1.varinfo_index - 1;
+    // Removing this line eliminates the error, even though cmp2 is not used
+    int cmp2 = false ? 0 : 15;
+    sign(cmp1);
+  }
+
+  public void sign(int x) {}
+}
+
+final class MyVarInfo {
+  public int varinfo_index = 22;
+}
diff --git a/checker/tests/nullness/RawMethodInvocation.java b/checker/tests/nullness/RawMethodInvocation.java
new file mode 100644
index 0000000..e9443b5
--- /dev/null
+++ b/checker/tests/nullness/RawMethodInvocation.java
@@ -0,0 +1,39 @@
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+
+@org.checkerframework.framework.qual.DefaultQualifier(Nullable.class)
+public class RawMethodInvocation {
+  Object a;
+  Object b;
+
+  RawMethodInvocation(boolean constructor_inits_a) {
+    a = 1;
+    init_b();
+  }
+
+  @EnsuresNonNull("b")
+  void init_b(@UnknownInitialization RawMethodInvocation this) {
+    b = 2;
+  }
+
+  RawMethodInvocation(int constructor_inits_none) {
+    init_ab();
+  }
+
+  @EnsuresNonNull({"a", "b"})
+  void init_ab(@UnknownInitialization RawMethodInvocation this) {
+    a = 1;
+    b = 2;
+  }
+
+  RawMethodInvocation(long constructor_escapes_raw) {
+    a = 1;
+    // this call is not valid, this is still raw
+    // :: error: (method.invocation)
+    nonRawMethod();
+    b = 2;
+  }
+
+  void nonRawMethod() {}
+}
diff --git a/checker/tests/nullness/RawParameter.java b/checker/tests/nullness/RawParameter.java
new file mode 100644
index 0000000..f192c79
--- /dev/null
+++ b/checker/tests/nullness/RawParameter.java
@@ -0,0 +1,13 @@
+// Test case for issue #516: https://github.com/typetools/checker-framework/issues/516
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class RawParameter<T> {
+  private @Nullable T payload;
+
+  // Using a parameterized type here avoids the exception
+  @SuppressWarnings("unchecked")
+  public void clearPayload(RawParameter node) {
+    node.payload = null;
+  }
+}
diff --git a/checker/tests/nullness/RawSuper.java b/checker/tests/nullness/RawSuper.java
new file mode 100644
index 0000000..b2a8aeb
--- /dev/null
+++ b/checker/tests/nullness/RawSuper.java
@@ -0,0 +1,84 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// @skip-test
+// This test is broken as it uses multiple classes.  Javac halts when seeing the first error
+public class RawSuper {
+
+  class A {
+    @NonNull Object afield;
+
+    A() {
+      super();
+      mRA(this);
+      // :: error: (type.incompatible)
+      mA(this);
+      afield = new Object();
+      mRA(this);
+      mA(this);
+    }
+
+    A(int ignore) {
+      this.raw();
+      afield = new Object();
+    }
+
+    void raw(A this) {}
+
+    void nonRaw() {}
+  }
+
+  class B extends A {
+    @NonNull Object bfield;
+
+    B() {
+      mRA(this);
+      mA(this);
+      mRB(this);
+      // :: error: (type.incompatible)
+      mB(this);
+      bfield = new Object();
+      mRA(this);
+      mA(this);
+      mRB(this);
+      mB(this);
+    }
+
+    void raw(B this) {
+      // :: error: (type.incompatible)
+      super.nonRaw();
+    }
+  }
+  // This test may be extraneous
+  class C extends B {
+    @NonNull Object cfield;
+
+    C() {
+      mRA(this);
+      mA(this);
+      mRB(this);
+      mB(this);
+      mRC(this);
+      // :: error: (type.incompatible)
+      mC(this);
+      cfield = new Object();
+      mRA(this);
+      mA(this);
+      mRB(this);
+      mB(this);
+      mRC(this);
+      mC(this);
+    }
+  }
+
+  void mA(A a) {}
+
+  void mRA(A a) {}
+
+  void mB(B b) {}
+
+  void mRB(B b) {}
+
+  void mC(C c) {}
+
+  void mRC(C c) {}
+}
diff --git a/checker/tests/nullness/RawTypesAssignment.java b/checker/tests/nullness/RawTypesAssignment.java
new file mode 100644
index 0000000..965733f
--- /dev/null
+++ b/checker/tests/nullness/RawTypesAssignment.java
@@ -0,0 +1,30 @@
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class RawTypesAssignment {
+  Map rawMap = new HashMap();
+  Map<String, List<String>> notRawMapDiamondRec = new HashMap<>();
+  // :: warning: [unchecked] unchecked conversion
+  Map<String, List<String>> notRawMapRawHashMapRec = new HashMap();
+
+  Map<String, CharSequence> notRawMapDiamond = new HashMap<>();
+  // :: warning: [unchecked] unchecked conversion
+  Map<String, CharSequence> notRawMapRawHashMap = new HashMap();
+
+  Map<Object, Object> notRawMapDiamondObjectObject = new HashMap<>();
+  // :: warning: [unchecked] unchecked conversion
+  Map<Object, Object> notRawMapDiamondObjectObjectRaw = new HashMap();
+
+  RecursiveGeneric rawRecursiveGeneric = new RecursiveGeneric();
+  RecursiveGeneric<MyClass> notRawRecursiveGenericDiamond = new RecursiveGeneric<>();
+  // :: warning: [unchecked] unchecked conversion
+  RecursiveGeneric<MyClass> notRawRecursiveGenericRaw = new RecursiveGeneric();
+
+  class Generic<G extends @Nullable Object> {}
+
+  class RecursiveGeneric<R extends Generic<R>> {}
+
+  class MyClass extends Generic<MyClass> {}
+}
diff --git a/checker/tests/nullness/RawTypesBounded.java b/checker/tests/nullness/RawTypesBounded.java
new file mode 100644
index 0000000..44178a0
--- /dev/null
+++ b/checker/tests/nullness/RawTypesBounded.java
@@ -0,0 +1,233 @@
+// Note that this file is a near duplicate in /nullness and /nullness-uninit
+
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.*;
+
+@org.checkerframework.framework.qual.DefaultQualifier(Nullable.class)
+public class RawTypesBounded {
+
+  class Bad {
+    @NonNull String field;
+
+    public Bad() {
+      // :: error: (method.invocation)
+      this.init(); // error
+      // :: error: (method.invocation)
+      init(); // error
+
+      this.field = "field"; // valid
+      // :: error: (assignment)
+      this.field = null; // error
+      field = "field"; // valid
+      // :: error: (assignment)
+      field = null; // error
+    }
+
+    void init() {
+      output(this.field.length()); // valid
+    }
+  }
+
+  class A {
+    @NonNull String field;
+
+    public A() {
+      this.field = "field"; // valid
+      field = "field"; // valid
+      this.init(); // valid
+      init(); // valid
+    }
+
+    public void init(@UnknownInitialization A this) {
+      // :: error: (dereference.of.nullable)
+      output(this.field.length());
+    }
+
+    public void initExpl2(@UnknownInitialization A this) {
+      // :: error: (argument)
+      output(this.field);
+    }
+
+    public void initImpl1(@UnknownInitialization A this) {
+      // :: error: (dereference.of.nullable)
+      output(field.length());
+    }
+
+    public void initImpl2(@UnknownInitialization A this) {
+      // :: error: (argument)
+      output(field);
+    }
+  }
+
+  class B extends A {
+    @NonNull String otherField;
+
+    public B() {
+      super();
+      // :: error: (assignment)
+      this.otherField = null; // error
+      this.otherField = "otherField"; // valid
+    }
+
+    @Override
+    public void init(@UnknownInitialization B this) {
+      // :: error: (dereference.of.nullable)
+      output(this.field.length()); // error (TODO: substitution)
+      super.init(); // valid
+    }
+
+    public void initImpl1(@UnknownInitialization B this) {
+      // :: error: (dereference.of.nullable)
+      output(field.length()); // error (TODO: substitution)
+    }
+
+    public void initExpl2(@UnknownInitialization B this) {
+      // :: error: (dereference.of.nullable)
+      output(this.otherField.length()); // error
+    }
+
+    public void initImpl2(@UnknownInitialization B this) {
+      // :: error: (dereference.of.nullable)
+      output(otherField.length()); // error
+    }
+
+    void other() {
+      init(); // valid
+      this.init(); // valid
+    }
+
+    void otherRaw(@UnknownInitialization B this) {
+      init(); // valid
+      this.init(); // valid
+    }
+  }
+
+  class C extends B {
+
+    @NonNull String[] strings;
+
+    @Override
+    public void init(@UnknownInitialization C this) {
+      // :: error: (dereference.of.nullable)
+      output(this.strings.length); // error
+      System.out.println(); // valid
+    }
+  }
+
+  // To test whether the argument is @NonNull and @Initialized
+  static void output(@NonNull Object o) {}
+
+  class D extends C {
+    @Override
+    public void init(@UnknownInitialization D this) {
+      this.field = "s";
+      output(this.field.length());
+    }
+  }
+
+  class MyTest {
+    int i;
+
+    MyTest(int i) {
+      this.i = i;
+    }
+
+    void myTest(@UnknownInitialization MyTest this) {
+      i++;
+    }
+  }
+
+  class AllFieldsInitialized {
+    long elapsedMillis = 0;
+    long startTime = 0;
+
+    // If all fields have an initializer, then the type of "this"
+    // should still not be non-raw (there might be uninitilized subclasses)
+    public AllFieldsInitialized() {
+      // :: error: (method.invocation)
+      nonRawMethod();
+    }
+
+    public void nonRawMethod() {}
+  }
+
+  class AFSIICell {
+    AllFieldsSetInInitializer afsii;
+  }
+
+  class AllFieldsSetInInitializer {
+    long elapsedMillis;
+    long startTime;
+
+    public AllFieldsSetInInitializer() {
+      elapsedMillis = 0;
+      // :: error: (method.invocation)
+      nonRawMethod();
+      startTime = 0;
+      // :: error: (method.invocation)
+      nonRawMethod(); // still error (subclasses...)
+    }
+
+    public AllFieldsSetInInitializer(boolean b) {
+      // :: error: (method.invocation)
+      nonRawMethod();
+    }
+
+    public void nonRawMethod() {}
+  }
+
+  class ConstructorInvocations {
+    int v;
+
+    public ConstructorInvocations(int v) {
+      this.v = v;
+    }
+
+    public ConstructorInvocations() {
+      this(0);
+      // :: error: (method.invocation)
+      nonRawMethod();
+    }
+
+    public void nonRawMethod() {}
+  }
+
+  class MethodAccess {
+    public MethodAccess() {
+      @NonNull String s = string();
+    }
+
+    public @NonNull String string(@UnknownInitialization MethodAccess this) {
+      return "nonnull";
+    }
+  }
+
+  void cast(@UnknownInitialization Object... args) {
+
+    @SuppressWarnings("rawtypes")
+    // :: error: (assignment)
+    Object[] argsNonRaw1 = args;
+
+    @SuppressWarnings("cast")
+    Object[] argsNonRaw2 = (Object[]) args;
+  }
+
+  // default qualifier is @Nullable, so this is OK.
+  class RawAfterConstructorBad {
+    Object o;
+
+    RawAfterConstructorBad() {}
+  }
+
+  class RawAfterConstructorOK1 {
+    @Nullable Object o;
+
+    RawAfterConstructorOK1() {}
+  }
+
+  class RawAfterConstructorOK2 {
+    int a;
+
+    RawAfterConstructorOK2() {}
+  }
+}
diff --git a/checker/tests/nullness/RawTypesNullness.java b/checker/tests/nullness/RawTypesNullness.java
new file mode 100644
index 0000000..78e02c1
--- /dev/null
+++ b/checker/tests/nullness/RawTypesNullness.java
@@ -0,0 +1,77 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class Generic<G extends @Nullable Object> {}
+
+class MyClass extends Generic<MyClass> {}
+
+class BoundedGeneric<B extends @Nullable CharSequence> {}
+
+class RawTypesNullness {
+  Generic rawReturn() {
+    return new Generic();
+  }
+
+  Generic rawField = new Generic();
+
+  void use() {
+    Generic rawLocal = new Generic<>();
+    Generic<?> generic1 = rawReturn();
+    Generic<?> generic2 = rawField;
+    Generic<?> generic3 = rawLocal;
+  }
+}
+
+class TestBounded {
+  BoundedGeneric rawReturn() {
+    return new BoundedGeneric<>();
+  }
+
+  BoundedGeneric rawField = new BoundedGeneric();
+
+  void useWildCard() {
+    BoundedGeneric rawLocal = new BoundedGeneric<String>();
+    BoundedGeneric<?> generic1 = rawReturn();
+    BoundedGeneric<?> generic2 = rawField;
+    BoundedGeneric<?> generic3 = rawLocal;
+  }
+
+  void useBoundedWildCard() {
+    BoundedGeneric rawLocal = new BoundedGeneric<String>();
+    // :: warning: [unchecked] unchecked conversion
+    BoundedGeneric<? extends Object> generic1 = rawReturn();
+    // :: warning: [unchecked] unchecked conversion
+    BoundedGeneric<? extends Object> generic2 = rawField;
+    // :: warning: [unchecked] unchecked conversion
+    BoundedGeneric<? extends Object> generic3 = rawLocal;
+  }
+
+  void useBoundedWildCard2() {
+    BoundedGeneric rawLocal = new BoundedGeneric<String>();
+    // :: warning: [unchecked] unchecked conversion
+    BoundedGeneric<? extends CharSequence> generic1 = rawReturn();
+    // :: warning: [unchecked] unchecked conversion
+    BoundedGeneric<? extends CharSequence> generic2 = rawField;
+    // :: warning: [unchecked] unchecked conversion
+    BoundedGeneric<? extends CharSequence> generic3 = rawLocal;
+  }
+
+  void useTypeArg() {
+    BoundedGeneric rawLocal = new BoundedGeneric<String>();
+    // :: warning: [unchecked] unchecked conversion
+    BoundedGeneric<String> generic1 = rawReturn();
+    // :: warning: [unchecked] unchecked conversion
+    BoundedGeneric<String> generic2 = rawField;
+    // :: warning: [unchecked] unchecked conversion
+    BoundedGeneric<String> generic3 = rawLocal;
+  }
+
+  void useAnnotatedTypeArg() {
+    BoundedGeneric rawLocal = new BoundedGeneric<String>();
+    // :: warning: [unchecked] unchecked conversion
+    BoundedGeneric<@Nullable String> generic1 = rawReturn();
+    // :: warning: [unchecked] unchecked conversion
+    BoundedGeneric<@Nullable String> generic2 = rawField;
+    // :: warning: [unchecked] unchecked conversion
+    BoundedGeneric<@Nullable String> generic3 = rawLocal;
+  }
+}
diff --git a/checker/tests/nullness/RawTypesUses.java b/checker/tests/nullness/RawTypesUses.java
new file mode 100644
index 0000000..00856f5
--- /dev/null
+++ b/checker/tests/nullness/RawTypesUses.java
@@ -0,0 +1,57 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public abstract class RawTypesUses {
+  class Generic<G extends @Nullable Object> {
+    G foo() {
+      throw new RuntimeException();
+    }
+  }
+
+  void foo() {
+    Generic<@Nullable String> notRawNullable = new Generic<>();
+    // :: error: (assignment)
+    @NonNull Object o1 = notRawNullable.foo();
+
+    Generic rawNullable = new Generic<@Nullable String>();
+    // TODO: false negative. See #635.
+    //// :: error: (assignment)
+    @NonNull Object o2 = rawNullable.foo();
+
+    Generic<@NonNull String> notRawNonNull = new Generic<>();
+    @NonNull Object o3 = notRawNonNull.foo();
+
+    Generic rawNonNull = new Generic<@NonNull String>();
+    Generic rawNonNullAlais = rawNonNull;
+    // TODO: false negative. See #635.
+    //// :: error: (assignment)
+    @NonNull Object o4 = rawNonNull.foo();
+    // TODO: false negative. See #635.
+    //// :: error: (assignment)
+    @NonNull Object o5 = rawNonNullAlais.foo();
+  }
+
+  abstract Generic rawReturn();
+
+  void bar() {
+    // :: warning: [unchecked] unchecked conversion
+    Generic<@Nullable String> notRawNullable = rawReturn();
+    // :: error: (assignment)
+    @NonNull Object o1 = notRawNullable.foo();
+
+    Generic rawNullable = rawReturn();
+    // TODO: false negative. See #635.
+    //// :: error: (assignment)
+    @NonNull Object o2 = rawNullable.foo();
+
+    // TODO: false negative. See #635.
+    //// :: error: (assignment)
+    @NonNull Object o3 = rawReturn().foo();
+
+    Generic local = rawReturn();
+    Generic localAlias = local;
+    // TODO: false negative. See #635.
+    //// :: error: (assignment)
+    @NonNull Object o4 = local.foo();
+  }
+}
diff --git a/checker/tests/nullness/ReadyReadLine.java b/checker/tests/nullness/ReadyReadLine.java
new file mode 100644
index 0000000..7408262
--- /dev/null
+++ b/checker/tests/nullness/ReadyReadLine.java
@@ -0,0 +1,33 @@
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class ReadyReadLine {
+
+  void m(MyBufferedReader buf) throws Exception {
+    if (buf.ready()) {
+      String line = buf.readLine();
+      line.toString();
+    }
+
+    if (buf.readLine() != null) {
+      // :: error: (dereference.of.nullable)
+      buf.readLine().toString();
+    }
+  }
+}
+
+// This is a replication of the JDK BufferedReader, with only the relevant methods.
+class MyBufferedReader {
+  public @Nullable String readLine() throws Exception {
+    return null;
+  }
+
+  @EnsuresNonNullIf(expression = "readLine()", result = true)
+  @Pure
+  public boolean ready() throws Exception {
+    // don't bother with implementation.
+    // :: error: (contracts.conditional.postcondition)
+    return true;
+  }
+}
diff --git a/checker/tests/nullness/ReceiverAnnotation.java b/checker/tests/nullness/ReceiverAnnotation.java
new file mode 100644
index 0000000..1a4cef2
--- /dev/null
+++ b/checker/tests/nullness/ReceiverAnnotation.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class ReceiverAnnotation {
+
+  void receiver1(ReceiverAnnotation this) {}
+
+  // :: error: (nullness.on.receiver)
+  void receiver2(@NonNull ReceiverAnnotation this) {}
+
+  // :: error: (nullness.on.receiver)
+  void receiver3(@Nullable ReceiverAnnotation this) {}
+}
diff --git a/checker/tests/nullness/ReferencesDefaults.java b/checker/tests/nullness/ReferencesDefaults.java
new file mode 100644
index 0000000..9e21548
--- /dev/null
+++ b/checker/tests/nullness/ReferencesDefaults.java
@@ -0,0 +1,17 @@
+public class ReferencesDefaults {
+  @org.checkerframework.framework.qual.DefaultQualifier(
+      org.checkerframework.checker.nullness.qual.Nullable.class)
+  class Decl {
+    Object test() {
+      // legal, because of changed default.
+      return null;
+    }
+  }
+
+  class Use {
+    Decl d = new Decl();
+    // here the default for f is NonNull -> error
+    // :: error: (assignment)
+    Object f = d.test();
+  }
+}
diff --git a/checker/tests/nullness/RefineArray.java b/checker/tests/nullness/RefineArray.java
new file mode 100644
index 0000000..7c40ef7
--- /dev/null
+++ b/checker/tests/nullness/RefineArray.java
@@ -0,0 +1,27 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// TODO: Add as test
+public class RefineArray {
+  public static <T> T[] concat(T @Nullable [] a, T @Nullable [] b) {
+    if (a == null) {
+      if (b != null) {
+        return b;
+      } else {
+        @SuppressWarnings("unchecked")
+        T[] result = (T[]) new Object[0];
+        return result;
+      }
+    } else {
+      if (b == null) {
+        return a;
+      } else {
+        @SuppressWarnings("unchecked")
+        T[] result = (T[]) new @MonotonicNonNull Object[a.length + b.length];
+
+        System.arraycopy(a, 0, result, 0, a.length);
+        System.arraycopy(b, 0, result, a.length, b.length);
+        return result;
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/RefineOverride.java b/checker/tests/nullness/RefineOverride.java
new file mode 100644
index 0000000..5a6b124
--- /dev/null
+++ b/checker/tests/nullness/RefineOverride.java
@@ -0,0 +1,218 @@
+// Test case for issue #611: https://github.com/typetools/checker-framework/issues/611
+// @skip-test Disabled until the issue is fixed.
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class RefineOverride {
+
+  void m(Sub<@Nullable String> snb, Sub<@NonNull String> snn) {
+    snb.m7(null);
+    snn.m7(null);
+  }
+
+  class Super<T> {
+
+    void m1(@NonNull String s) {}
+
+    void m2(@NonNull String s) {}
+
+    void m5(@NonNull String s) {}
+
+    void m6(@Nullable String s) {}
+
+    void m7(T s) {}
+
+    void m11(@NonNull String s1, @NonNull String s2) {}
+
+    void m12(@NonNull String s1, @Nullable String s2) {}
+
+    void m13(@Nullable String s1, @NonNull String s2) {}
+
+    void m14(@Nullable String s1, @Nullable String s2) {}
+
+    void m15(T s1, T s2) {}
+
+    void m16(@NonNull T s1, @NonNull T s2) {}
+
+    void m17(@NonNull T s1, @Nullable T s2) {}
+
+    void m18(@Nullable T s1, @NonNull T s2) {}
+
+    void m19(@Nullable T s1, @Nullable T s2) {}
+
+    void m21(@Nullable String[] a) {}
+
+    void m22(@NonNull String[] a) {}
+
+    void m23(@Nullable String[] a) {}
+
+    void m24(@NonNull String[] a) {}
+
+    void m25(T[] a) {}
+
+    void m26(@Nullable T[] a) {}
+
+    void m27(@NonNull T[] a) {}
+
+    void m28(@Nullable T[] a) {}
+
+    void m29(@NonNull T[] a) {}
+  }
+
+  class Sub<T> extends Super<T> {
+
+    @Override
+    void m1(@Nullable String s) {}
+
+    @Override
+    void m2(@PolyNull String s) {}
+
+    // In the following declarations, all previously-valid invocations remain
+    // valid, so the compiler should permit the overriding.
+
+    // Case 1.  A single parameter type is changed from anything to @PolyNull
+    // in an overriding method.
+
+    @Override
+    void m5(@PolyNull String s) {}
+
+    // TODO: should be legal
+    @Override
+    void m6(@PolyNull String s) {}
+
+    // TODO: should be legal
+    @Override
+    void m7(@PolyNull T s) {}
+
+    // Case 2.  Multiple parameter types are changed to @PolyNull in an
+    // overriding method.
+
+    // (The types for m14 might be better written as "@PolyNull(1)
+    // ... @PolyNull(2)", but all invocations remain valid.
+
+    @Override
+    void m11(@PolyNull String s1, @PolyNull String s2) {}
+
+    // TODO: should be legal
+    @Override
+    void m12(@PolyNull String s1, @PolyNull String s2) {}
+
+    // TODO: should be legal
+    @Override
+    void m13(@PolyNull String s1, @PolyNull String s2) {}
+
+    // TODO: should be legal
+    @Override
+    void m14(@PolyNull String s1, @PolyNull String s2) {}
+
+    // TODO: should be legal
+    @Override
+    void m15(@PolyNull T s1, @PolyNull T s2) {}
+
+    @Override
+    void m16(@PolyNull T s1, @PolyNull T s2) {}
+
+    // TODO: should be legal
+    @Override
+    void m17(@PolyNull T s1, @PolyNull T s2) {}
+
+    // TODO: should be legal
+    @Override
+    void m18(@PolyNull T s1, @PolyNull T s2) {}
+
+    // TODO: should be legal
+    @Override
+    void m19(@PolyNull T s1, @PolyNull T s2) {}
+
+    // Case 3.  Expand the element type of an array.
+    // The new permissible types are not supertypes of the old types,
+    // but they still expand the set of permitted invocations.
+
+    // :: error: (override.param)
+    @Override
+    void m21(@NonNull String[] a) {}
+
+    // :: error: Changing incompatibly to forbid old invocations is not permitted.
+    @Override
+    void m22(@Nullable String[] a) {}
+
+    // TODO: should be legal
+    @Override
+    void m23(@PolyNull String[] a) {}
+
+    @Override
+    void m24(@PolyNull String[] a) {}
+
+    @Override
+    void m25(@PolyNull T[] a) {}
+
+    // :: error: (override.param)
+    @Override
+    void m26(@NonNull T[] a) {}
+
+    // :: error: Changing incompatibly to forbid old invocations is not permitted.
+    @Override
+    void m27(@Nullable T[] a) {}
+
+    // TODO: should be legal
+    @Override
+    void m28(@PolyNull T[] a) {}
+
+    @Override
+    void m29(@PolyNull T[] a) {}
+  }
+
+  class Super2<T> {
+
+    void t1(String s) {}
+
+    void t2(String s) {}
+
+    void t3(@Nullable String s) {}
+
+    void t4(String s) {}
+
+    void t5(String[] s) {}
+
+    void t6(T s) {}
+
+    void t7(T s) {}
+
+    void t8(T[] s) {}
+
+    void t9(T[] s) {}
+  }
+
+  class Sub2<T> extends Super2<T> {
+
+    @Override
+    void t1(String s) {}
+
+    @Override
+    void t2(@Nullable String s) {}
+
+    // :: error: (override.param)
+    @Override
+    void t3(String s) {}
+
+    @Override
+    void t4(@PolyNull String s) {}
+
+    @Override
+    void t5(@PolyNull String[] s) {}
+
+    @Override
+    void t6(@Nullable T s) {}
+
+    // TODO: should be legal
+    @Override
+    void t7(@PolyNull T s) {}
+
+    @Override
+    void t8(@Nullable T[] s) {}
+
+    // TODO: should be legal
+    @Override
+    void t9(@PolyNull T[] s) {}
+  }
+}
diff --git a/checker/tests/nullness/RepeatEnsuresKeyFor.java b/checker/tests/nullness/RepeatEnsuresKeyFor.java
new file mode 100644
index 0000000..f911bb8
--- /dev/null
+++ b/checker/tests/nullness/RepeatEnsuresKeyFor.java
@@ -0,0 +1,101 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.EnsuresKeyFor;
+import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf;
+
+public class RepeatEnsuresKeyFor {
+
+  Map<String, Integer> map = new HashMap<>();
+
+  public void func1(String a, String b, String c) {
+    map.put(a, 1);
+    map.put(b, 2);
+    map.put(c, 3);
+  }
+
+  public boolean func2(String a, String b, String c) {
+    map.put(a, 1);
+    map.put(b, 2);
+    map.put(c, 3);
+    return true;
+  }
+
+  @EnsuresKeyFor(
+      value = {"#1", "#2"},
+      map = "map")
+  @EnsuresKeyFor(value = "#3", map = "map")
+  public void client1(String a, String b, String c) {
+    withpostconditionsfunc1(a, b, c);
+  }
+
+  @EnsuresKeyFor(
+      value = {"#1", "#2"},
+      map = "map")
+  @EnsuresKeyFor(value = "#3", map = "map")
+  public void client2(String a, String b, String c) {
+    withpostconditionfunc1(a, b, c);
+  }
+
+  @EnsuresKeyForIf(
+      expression = {"#1", "#2"},
+      map = "map",
+      result = true)
+  @EnsuresKeyForIf(expression = "#3", map = "map", result = true)
+  public boolean client3(String a, String b, String c) {
+    return withcondpostconditionsfunc2(a, b, c);
+  }
+
+  @EnsuresKeyForIf.List({
+    @EnsuresKeyForIf(expression = "#1", map = "map", result = true),
+    @EnsuresKeyForIf(expression = "#2", map = "map", result = true)
+  })
+  @EnsuresKeyForIf(expression = "#3", map = "map", result = true)
+  public boolean client4(String a, String b, String c) {
+    return withcondpostconditionfunc2(a, b, c);
+  }
+
+  @EnsuresKeyFor(
+      value = {"#1", "#2"},
+      map = "map")
+  @EnsuresKeyFor(value = "#3", map = "map")
+  public void withpostconditionsfunc1(String a, String b, String c) {
+    map.put(a, 1);
+    map.put(b, 2);
+    map.put(c, 3);
+  }
+
+  @EnsuresKeyForIf(
+      expression = {"#1", "#2"},
+      map = "map",
+      result = true)
+  @EnsuresKeyForIf(expression = "#3", map = "map", result = true)
+  public boolean withcondpostconditionsfunc2(String a, String b, String c) {
+    map.put(a, 1);
+    map.put(b, 2);
+    map.put(c, 3);
+    return true;
+  }
+
+  @EnsuresKeyFor.List({
+    @EnsuresKeyFor(value = "#1", map = "map"),
+    @EnsuresKeyFor(value = "#2", map = "map"),
+  })
+  @EnsuresKeyFor(value = "#3", map = "map")
+  public void withpostconditionfunc1(String a, String b, String c) {
+    map.put(a, 1);
+    map.put(b, 2);
+    map.put(c, 3);
+  }
+
+  @EnsuresKeyForIf.List({
+    @EnsuresKeyForIf(expression = "#1", map = "map", result = true),
+    @EnsuresKeyForIf(expression = "#2", map = "map", result = true)
+  })
+  @EnsuresKeyForIf(expression = "#3", map = "map", result = true)
+  public boolean withcondpostconditionfunc2(String a, String b, String c) {
+    map.put(a, 1);
+    map.put(b, 2);
+    map.put(c, 3);
+    return true;
+  }
+}
diff --git a/checker/tests/nullness/RepeatEnsuresKeyForWithError.java b/checker/tests/nullness/RepeatEnsuresKeyForWithError.java
new file mode 100644
index 0000000..2a300df
--- /dev/null
+++ b/checker/tests/nullness/RepeatEnsuresKeyForWithError.java
@@ -0,0 +1,99 @@
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.EnsuresKeyFor;
+import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf;
+
+public class RepeatEnsuresKeyForWithError {
+
+  Map<String, Integer> map = new HashMap<>();
+
+  public void func1(String a, String b, String c) {
+    map.put(a, 1);
+    map.put(c, 3);
+  }
+
+  public boolean func2(String a, String b, String c) {
+    map.put(a, 1);
+    map.put(c, 3);
+    return true;
+  }
+
+  @EnsuresKeyFor(
+      value = {"#1", "#2"},
+      map = "map")
+  @EnsuresKeyFor(value = "#3", map = "map")
+  public void client1(String a, String b, String c) {
+    withpostconditionsfunc1(a, b, c);
+  }
+
+  @EnsuresKeyFor(
+      value = {"#1", "#2"},
+      map = "map")
+  @EnsuresKeyFor(value = "#3", map = "map")
+  public void client2(String a, String b, String c) {
+    withpostconditionfunc1(a, b, c);
+  }
+
+  @EnsuresKeyForIf(
+      expression = {"#1", "#2"},
+      map = "map",
+      result = true)
+  @EnsuresKeyForIf(expression = "#3", map = "map", result = true)
+  public boolean client3(String a, String b, String c) {
+    return withcondpostconditionsfunc2(a, b, c);
+  }
+
+  @EnsuresKeyForIf.List({
+    @EnsuresKeyForIf(expression = "#1", map = "map", result = true),
+    @EnsuresKeyForIf(expression = "#2", map = "map", result = true)
+  })
+  @EnsuresKeyForIf(expression = "#3", map = "map", result = true)
+  public boolean client4(String a, String b, String c) {
+    return withcondpostconditionfunc2(a, b, c);
+  }
+
+  @EnsuresKeyFor(
+      value = {"#1", "#2"},
+      map = "map")
+  @EnsuresKeyFor(value = "#3", map = "map")
+  // :: error:  (contracts.postcondition)
+  public void withpostconditionsfunc1(String a, String b, String c) {
+    map.put(a, 1);
+    map.put(c, 3);
+  }
+
+  @EnsuresKeyForIf(
+      expression = {"#1", "#2"},
+      map = "map",
+      result = true)
+  @EnsuresKeyForIf(expression = "#3", map = "map", result = true)
+  public boolean withcondpostconditionsfunc2(String a, String b, String c) {
+    map.put(a, 1);
+    map.put(c, 3);
+    // :: error:  (contracts.conditional.postcondition)
+    return true;
+  }
+
+  @EnsuresKeyFor.List({
+    @EnsuresKeyFor(value = "#1", map = "map"),
+    @EnsuresKeyFor(value = "#2", map = "map"),
+  })
+  @EnsuresKeyFor(value = "#3", map = "map")
+  // :: error:  (contracts.postcondition)
+  public void withpostconditionfunc1(String a, String b, String c) {
+    map.put(a, 1);
+    map.put(c, 3);
+  }
+
+  @EnsuresKeyForIf.List({
+    @EnsuresKeyForIf(expression = "#1", map = "map", result = true),
+    @EnsuresKeyForIf(expression = "#2", map = "map", result = true)
+  })
+  @EnsuresKeyForIf(expression = "#3", map = "map", result = true)
+  public boolean withcondpostconditionfunc2(String a, String b, String c) {
+    map.put(a, 1);
+    map.put(c, 3);
+    // :: error:  (contracts.conditional.postcondition)
+    return true;
+  }
+}
diff --git a/checker/tests/nullness/RepeatEnsuresNonNull.java b/checker/tests/nullness/RepeatEnsuresNonNull.java
new file mode 100644
index 0000000..7c82a52
--- /dev/null
+++ b/checker/tests/nullness/RepeatEnsuresNonNull.java
@@ -0,0 +1,91 @@
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class RepeatEnsuresNonNull {
+
+  protected @Nullable String value1;
+  protected @Nullable String value2;
+  protected @Nullable String value3;
+
+  public boolean func1() {
+    value1 = "value1";
+    value2 = "value2";
+    value3 = "value3";
+    return true;
+  }
+
+  public void func2() {
+    value1 = "value1";
+    value2 = "value2";
+    value3 = "value3";
+  }
+
+  @EnsuresNonNullIf(
+      expression = {"value1", "value2"},
+      result = true)
+  @EnsuresNonNullIf(expression = "value3", result = true)
+  public boolean client1() {
+    return withcondpostconditionsfunc1();
+  }
+
+  @EnsuresNonNull("value1")
+  @EnsuresNonNull(value = {"value2", "value3"})
+  public void client2() {
+    withpostconditionsfunc2();
+  }
+
+  @EnsuresNonNullIf.List({
+    @EnsuresNonNullIf(expression = "value1", result = true),
+    @EnsuresNonNullIf(expression = "value2", result = true),
+  })
+  @EnsuresNonNullIf(expression = "value3", result = true)
+  public boolean client3() {
+    return withcondpostconditionfunc1();
+  }
+
+  @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")})
+  @EnsuresNonNull("value3")
+  public void client4() {
+    withpostconditionfunc2();
+  }
+
+  @EnsuresNonNullIf(
+      expression = {"value1", "value2"},
+      result = true)
+  @EnsuresNonNullIf(expression = "value3", result = true)
+  public boolean withcondpostconditionsfunc1() {
+    value1 = "value1";
+    value2 = "value2";
+    value3 = "value3";
+    return true;
+  }
+
+  @EnsuresNonNull("value1")
+  @EnsuresNonNull(value = {"value2", "value3"})
+  public void withpostconditionsfunc2() {
+    value1 = "value1";
+    value2 = "value2";
+    value3 = "value3";
+  }
+
+  @EnsuresNonNullIf.List({
+    @EnsuresNonNullIf(expression = "value1", result = true),
+    @EnsuresNonNullIf(expression = "value2", result = true),
+  })
+  @EnsuresNonNullIf(expression = "value3", result = true)
+  public boolean withcondpostconditionfunc1() {
+    value1 = "value1";
+    value2 = "value2";
+    value3 = "value3";
+    return true;
+  }
+
+  @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")})
+  @EnsuresNonNull("value3")
+  public void withpostconditionfunc2() {
+    value1 = "value1";
+    value2 = "value2";
+    value3 = "value3";
+  }
+}
diff --git a/checker/tests/nullness/RepeatEnsuresNonNullWithError.java b/checker/tests/nullness/RepeatEnsuresNonNullWithError.java
new file mode 100644
index 0000000..6941075
--- /dev/null
+++ b/checker/tests/nullness/RepeatEnsuresNonNullWithError.java
@@ -0,0 +1,95 @@
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class RepeatEnsuresNonNullWithError {
+
+  protected @Nullable String value1;
+  protected @Nullable String value2;
+  protected @Nullable String value3;
+
+  public boolean func1() {
+    value1 = "value1";
+    value2 = "value2";
+    value3 = null;
+    return true;
+  }
+
+  public void func2() {
+    value1 = "value1";
+    value2 = "value2";
+    value3 = null;
+  }
+
+  @EnsuresNonNullIf(
+      expression = {"value1", "value2"},
+      result = true)
+  @EnsuresNonNullIf(expression = "value3", result = true)
+  public boolean client1() {
+    return withcondpostconditionsfunc1();
+  }
+
+  @EnsuresNonNull("value1")
+  @EnsuresNonNull(value = {"value2", "value3"})
+  public void client2() {
+    withpostconditionsfunc2();
+  }
+
+  @EnsuresNonNullIf.List({
+    @EnsuresNonNullIf(expression = "value1", result = true),
+    @EnsuresNonNullIf(expression = "value2", result = true),
+  })
+  @EnsuresNonNullIf(expression = "value3", result = true)
+  public boolean client3() {
+    return withcondpostconditionfunc1();
+  }
+
+  @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")})
+  @EnsuresNonNull("value3")
+  public void client4() {
+    withpostconditionfunc2();
+  }
+
+  @EnsuresNonNullIf(
+      expression = {"value1", "value2"},
+      result = true)
+  @EnsuresNonNullIf(expression = "value3", result = true)
+  public boolean withcondpostconditionsfunc1() {
+    value1 = "value1";
+    value2 = "value2";
+    value3 = null; // condition not satisfied here
+    // :: error:  (contracts.conditional.postcondition)
+    return true;
+  }
+
+  @EnsuresNonNull("value1")
+  @EnsuresNonNull(value = {"value2", "value3"})
+  // :: error:  (contracts.postcondition)
+  public void withpostconditionsfunc2() {
+    value1 = "value1";
+    value2 = "value2";
+    value3 = null; // condition not satisfied here
+  }
+
+  @EnsuresNonNullIf.List({
+    @EnsuresNonNullIf(expression = "value1", result = true),
+    @EnsuresNonNullIf(expression = "value2", result = true),
+  })
+  @EnsuresNonNullIf(expression = "value3", result = true)
+  public boolean withcondpostconditionfunc1() {
+    value1 = "value1";
+    value2 = "value2";
+    value3 = null; // condition not satisfied here
+    // :: error:  (contracts.conditional.postcondition)
+    return true;
+  }
+
+  @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")})
+  @EnsuresNonNull("value3")
+  // :: error:  (contracts.postcondition)
+  public void withpostconditionfunc2() {
+    value1 = "value1";
+    value2 = "value2";
+    value3 = null; // condition not satisfied here
+  }
+}
diff --git a/checker/tests/nullness/RequiresNonNullTest.java b/checker/tests/nullness/RequiresNonNullTest.java
new file mode 100644
index 0000000..e48a931
--- /dev/null
+++ b/checker/tests/nullness/RequiresNonNullTest.java
@@ -0,0 +1,153 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class RequiresNonNullTest {
+
+  @Nullable Object field1;
+  @Nullable Object field2;
+
+  @RequiresNonNull("field1")
+  void method1() {
+    field1.toString(); // OK, field1 is known to be non-null
+    this.field1.toString(); // OK, field1 is known to be non-null
+    // :: error: (dereference.of.nullable)
+    field2.toString(); // error, might throw NullPointerException
+  }
+
+  @RequiresNonNull("field1")
+  void method1also() {
+    // ok, precondition satisfied by NNOE
+    method1();
+  }
+
+  void method2() {
+    field1 = new Object();
+    method1(); // OK, satisfies method precondition
+    field1 = null;
+    // :: error: (contracts.precondition)
+    method1(); // error, does not satisfy method precondition
+  }
+
+  protected @Nullable Object field;
+
+  @RequiresNonNull("field")
+  public void requiresNonNullField() {}
+
+  public void clientFail(RequiresNonNullTest arg1) {
+    // :: error: (contracts.precondition)
+    arg1.requiresNonNullField();
+  }
+
+  public void clientOK(RequiresNonNullTest arg2) {
+    arg2.field = new Object();
+    // note that the following line works
+    @NonNull Object o = arg2.field;
+
+    arg2.requiresNonNullField(); // OK, field is known to be non-null
+  }
+
+  // TODO: forbid the field in @NNOE to be less visible than the method
+
+  protected static @Nullable Object staticfield;
+
+  @Pure
+  @RequiresNonNull("staticfield")
+  // :: warning: (purity.deterministic.void.method)
+  public void reqStaticName() {
+    reqStaticQualName();
+  }
+
+  @Pure
+  @RequiresNonNull("RequiresNonNullTest.staticfield")
+  // :: warning: (purity.deterministic.void.method)
+  public void reqStaticQualName() {
+    reqStaticName();
+  }
+
+  public void statClientOK(RequiresNonNullTest arg1) {
+    staticfield = new Object();
+    arg1.reqStaticName();
+
+    staticfield = new Object();
+    arg1.reqStaticQualName();
+
+    RequiresNonNullTest.staticfield = new Object();
+    arg1.reqStaticName();
+    RequiresNonNullTest.staticfield = new Object();
+    arg1.reqStaticQualName();
+  }
+
+  public void statClientFail(RequiresNonNullTest arg1) {
+    // :: error: (contracts.precondition)
+    arg1.reqStaticName();
+    // :: error: (contracts.precondition)
+    arg1.reqStaticQualName();
+  }
+
+  class NNOESubTest extends RequiresNonNullTest {
+    public void subClientOK(NNOESubTest arg3) {
+      arg3.field = new Object();
+      arg3.requiresNonNullField();
+    }
+
+    public void subClientFail(NNOESubTest arg4) {
+      // :: error: (contracts.precondition)
+      arg4.requiresNonNullField();
+    }
+
+    public void subStat(NNOESubTest arg5) {
+      RequiresNonNullTest.staticfield = new Object();
+      arg5.reqStaticQualName();
+
+      staticfield = new Object();
+      arg5.reqStaticQualName();
+
+      NNOESubTest.staticfield = new Object();
+      arg5.reqStaticQualName();
+    }
+  }
+
+  private @Nullable Object notHidden;
+
+  class NNOEHidingTest extends RequiresNonNullTest {
+
+    protected @Nullable String field;
+
+    public void hidingClient1(NNOEHidingTest arg5) {
+      arg5.field = "ha!";
+
+      // TODO: The error message should say something about the hidden field.
+      // :: error: (contracts.precondition)
+      arg5.requiresNonNullField();
+
+      // TODO: Add test like:
+      // arg5.ensuresNonNullField();
+      // arg5.requiresNonNullField();
+    }
+
+    public void hidingClient2(NNOEHidingTest arg6) {
+      // :: error: (contracts.precondition)
+      arg6.requiresNonNullField();
+    }
+
+    protected @Nullable Object notHidden;
+
+    @RequiresNonNull("notHidden")
+    void notHiddenTest() {
+      // the field in the superclass is private -> don't complain about hiding
+    }
+  }
+
+  static @Nullable Object o = "m";
+
+  @RequiresNonNull("o")
+  void test() {
+    o = null;
+  }
+
+  @RequiresNonNull("thisShouldIssue1Error")
+  // Test case for Issue 1051
+  // https://github.com/typetools/checker-framework/issues/1051
+  // :: error: (flowexpr.parse.error)
+  void testIssue1051() {}
+}
diff --git a/checker/tests/nullness/RequiresPrivateField.java b/checker/tests/nullness/RequiresPrivateField.java
new file mode 100644
index 0000000..127f818
--- /dev/null
+++ b/checker/tests/nullness/RequiresPrivateField.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// @skip-test Test case for future feature:  @RequiresNonNull is permitted to access a private field
+// (maybe just those that are annotated with @SpecPublic)
+
+public class RequiresPrivateField {
+
+  @RequiresNonNull("PptCombined.assemblies")
+  public void testFindIntermediateBlocks1() {
+    // no body
+  }
+}
+
+class PptCombined {
+
+  @SpecPublic private static @MonotonicNonNull String assemblies = null;
+}
diff --git a/checker/tests/nullness/SAMLineParser.java b/checker/tests/nullness/SAMLineParser.java
new file mode 100644
index 0000000..f02dc8b
--- /dev/null
+++ b/checker/tests/nullness/SAMLineParser.java
@@ -0,0 +1,8 @@
+public class SAMLineParser {
+
+  private int x;
+
+  private String makeErrorString() {
+    return "" + (this.x <= 0 ? "" : this.x);
+  }
+}
diff --git a/checker/tests/nullness/SamFileValidator.java b/checker/tests/nullness/SamFileValidator.java
new file mode 100644
index 0000000..8a10b24
--- /dev/null
+++ b/checker/tests/nullness/SamFileValidator.java
@@ -0,0 +1,13 @@
+// @skip-test Crashes the Checker Framework, but skipped to avoid breaking the build
+
+import java.util.AbstractMap;
+import java.util.Map;
+
+public class SamFileValidator {
+
+  private class Codec {
+    public Map.Entry<String, String> decode() {
+      return new AbstractMap.SimpleEntry("hello", "goodbye");
+    }
+  }
+}
diff --git a/checker/tests/nullness/ScopingConstruct.java b/checker/tests/nullness/ScopingConstruct.java
new file mode 100644
index 0000000..e8cb7e4
--- /dev/null
+++ b/checker/tests/nullness/ScopingConstruct.java
@@ -0,0 +1,510 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+@SuppressWarnings("initialization.field.uninitialized")
+public class ScopingConstruct {
+
+  // TODO: add nested classes within these two?
+  static class StaticNested implements AutoCloseable {
+    public void close() {}
+
+    static class NestedNested implements AutoCloseable {
+      public void close() {}
+    }
+
+    class NestedInner implements AutoCloseable {
+      public void close() {}
+    }
+  }
+
+  class Inner implements AutoCloseable {
+    public void close() {}
+
+    // This is a Java error.
+    // static class InnerNested {}
+
+    class InnerInner implements AutoCloseable {
+      public void close() {}
+    }
+  }
+
+  StaticNested sn;
+
+  @Nullable StaticNested nsn;
+
+  Inner i;
+
+  @Nullable Inner ni;
+
+  ScopingConstruct.StaticNested scsn;
+
+  // This is a Java error.
+  // @Nullable ScopingConstruct.StaticNested nscsn;
+
+  ScopingConstruct.@Nullable StaticNested scnsn;
+
+  // This is a Java error.
+  // ScopingConstruct.@Nullable StaticNested.NestedNested scnsnnn;
+
+  // This is a Java error.
+  // ScopingConstruct.@Nullable StaticNested.@Nullable NestedNested scnsnnnn;
+
+  // :: error: (nullness.on.outer)
+  ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni;
+
+  // :: error: (nullness.on.outer)
+  ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni;
+
+  ScopingConstruct.Inner sci;
+
+  ScopingConstruct.Inner.InnerInner sciii;
+
+  ScopingConstruct.Inner.@Nullable InnerInner scinii;
+
+  // :: error: (nullness.on.outer)
+  @Nullable ScopingConstruct.Inner nsci;
+
+  // :: error: (nullness.on.outer)
+  @Nullable ScopingConstruct.Inner.InnerInner nsciii;
+
+  // :: error: (nullness.on.outer)
+  @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii;
+
+  ScopingConstruct.@Nullable Inner scni;
+
+  // :: error: (nullness.on.outer)
+  ScopingConstruct.@Nullable Inner.InnerInner scniii;
+
+  // :: error: (nullness.on.outer)
+  ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii;
+
+  ScopingConstruct.StaticNested.NestedInner scsnni;
+
+  ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni;
+
+  // This is a Java error.
+  // @Nullable ScopingConstruct.StaticNested.NestedInner nscsnni;
+
+  // This is a Java error.
+  // @Nullable ScopingConstruct.StaticNested.@Nullable NestedInner nscsnnni;
+
+  // This is a Java error.
+  // @Nullable ScopingConstruct.@Nullable StaticNested.NestedInner nscnsnni;
+
+  // This is a Java error.
+  // @Nullable ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner nscnsnnni;
+
+  ScopingConstruct.Inner @Nullable [] scina;
+
+  ScopingConstruct.Inner.InnerInner @Nullable [] sciiina;
+
+  ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] sciniina;
+
+  // :: error: (nullness.on.outer)
+  @Nullable ScopingConstruct.Inner @Nullable [] nscina;
+
+  // :: error: (nullness.on.outer)
+  @Nullable ScopingConstruct.Inner.InnerInner @Nullable [] nsciiina;
+
+  // :: error: (nullness.on.outer)
+  @Nullable ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] nsciniina;
+
+  ScopingConstruct.@Nullable Inner @Nullable [] scnina;
+
+  // :: error: (nullness.on.outer)
+  ScopingConstruct.@Nullable Inner.InnerInner @Nullable [] scniina;
+
+  // :: error: (nullness.on.outer)
+  ScopingConstruct.@Nullable Inner.@Nullable InnerInner @Nullable [] scniniina;
+
+  ScopingConstruct.Inner sci() {
+    throw new Error("not implemented");
+  }
+
+  ScopingConstruct.Inner.InnerInner sciii() {
+    throw new Error("not implemented");
+  }
+
+  ScopingConstruct.Inner.@Nullable InnerInner scinii() {
+    throw new Error("not implemented");
+  }
+
+  // :: error: (nullness.on.outer)
+  @Nullable ScopingConstruct.Inner nsci() {
+    throw new Error("not implemented");
+  }
+
+  // :: error: (nullness.on.outer)
+  @Nullable ScopingConstruct.Inner.InnerInner nsciii() {
+    throw new Error("not implemented");
+  }
+
+  // :: error: (nullness.on.outer)
+  @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii() {
+    throw new Error("not implemented");
+  }
+
+  ScopingConstruct.@Nullable Inner scni() {
+    throw new Error("not implemented");
+  }
+
+  // :: error: (nullness.on.outer)
+  ScopingConstruct.@Nullable Inner.InnerInner scniii() {
+    throw new Error("not implemented");
+  }
+
+  // :: error: (nullness.on.outer)
+  ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii() {
+    throw new Error("not implemented");
+  }
+
+  ScopingConstruct.Inner @Nullable [] scin() {
+    throw new Error("not implemented");
+  }
+
+  ScopingConstruct.Inner.InnerInner @Nullable [] sciiin() {
+    throw new Error("not implemented");
+  }
+
+  ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] sciniin() {
+    throw new Error("not implemented");
+  }
+
+  // :: error: (nullness.on.outer)
+  @Nullable ScopingConstruct.Inner @Nullable [] nscin() {
+    throw new Error("not implemented");
+  }
+
+  // :: error: (nullness.on.outer)
+  @Nullable ScopingConstruct.Inner.InnerInner @Nullable [] nsciiin() {
+    throw new Error("not implemented");
+  }
+
+  // :: error: (nullness.on.outer)
+  @Nullable ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] nsciniin() {
+    throw new Error("not implemented");
+  }
+
+  ScopingConstruct.@Nullable Inner @Nullable [] scnin() {
+    throw new Error("not implemented");
+  }
+
+  // :: error: (nullness.on.outer)
+  ScopingConstruct.@Nullable Inner.InnerInner @Nullable [] scniiin() {
+    throw new Error("not implemented");
+  }
+
+  // :: error: (nullness.on.outer)
+  ScopingConstruct.@Nullable Inner.@Nullable InnerInner @Nullable [] scniniin() {
+    throw new Error("not implemented");
+  }
+
+  ///
+  /// Formal parameters
+  ///
+
+  void fsn(StaticNested sn) {}
+
+  void fnsn(@Nullable StaticNested nsn) {}
+
+  void fi(Inner i) {}
+
+  void fni(@Nullable Inner ni) {}
+
+  void fscsn(ScopingConstruct.StaticNested scsn) {}
+
+  void fscnsn(ScopingConstruct.@Nullable StaticNested scnsn) {}
+
+  // :: error: (nullness.on.outer)
+  void fscnsnni(ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni) {}
+
+  // :: error: (nullness.on.outer)
+  void fscnsnnni(ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni) {}
+
+  void fsci(ScopingConstruct.Inner sci) {}
+
+  void fsciii(ScopingConstruct.Inner.InnerInner sciii) {}
+
+  void fscinii(ScopingConstruct.Inner.@Nullable InnerInner scinii) {}
+
+  // :: error: (nullness.on.outer)
+  void fnsci(@Nullable ScopingConstruct.Inner nsci) {}
+
+  // :: error: (nullness.on.outer)
+  void fnsciii(@Nullable ScopingConstruct.Inner.InnerInner nsciii) {}
+
+  // :: error: (nullness.on.outer)
+  void fnscinii(@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii) {}
+
+  void fscni(ScopingConstruct.@Nullable Inner scni) {}
+
+  // :: error: (nullness.on.outer)
+  void fscniii(ScopingConstruct.@Nullable Inner.InnerInner scniii) {}
+
+  // :: error: (nullness.on.outer)
+  void fscninii(ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii) {}
+
+  void fscsnni(ScopingConstruct.StaticNested.NestedInner scsnni) {}
+
+  void fscsnnni(ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni) {}
+
+  ///
+  /// Local variables
+  ///
+
+  void lvsn() {
+    StaticNested sn;
+  }
+
+  void lvnsn() {
+    @Nullable StaticNested nsn;
+  }
+
+  void lvi() {
+    Inner i;
+  }
+
+  void lvni() {
+    @Nullable Inner ni;
+  }
+
+  void lvscsn() {
+    ScopingConstruct.StaticNested scsn;
+  }
+
+  void lvscnsn() {
+    ScopingConstruct.@Nullable StaticNested scnsn;
+  }
+
+  void lvscnsnni() {
+    // :: error: (nullness.on.outer)
+    ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni;
+  }
+
+  void lvscnsnnni() {
+    // :: error: (nullness.on.outer)
+    ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni;
+  }
+
+  void lvsci() {
+    ScopingConstruct.Inner sci;
+  }
+
+  void lvsciii() {
+    ScopingConstruct.Inner.InnerInner sciii;
+  }
+
+  void lvscinii() {
+    ScopingConstruct.Inner.@Nullable InnerInner scinii;
+  }
+
+  void lvnsci() {
+    // :: error: (nullness.on.outer)
+    @Nullable ScopingConstruct.Inner nsci;
+  }
+
+  void lvnsciii() {
+    // :: error: (nullness.on.outer)
+    @Nullable ScopingConstruct.Inner.InnerInner nsciii;
+  }
+
+  void lvnscinii() {
+    // :: error: (nullness.on.outer)
+    @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii;
+  }
+
+  void lvscni() {
+    ScopingConstruct.@Nullable Inner scni;
+  }
+
+  void lvscniii() {
+    // :: error: (nullness.on.outer)
+    ScopingConstruct.@Nullable Inner.InnerInner scniii;
+  }
+
+  void lvscninii() {
+    // :: error: (nullness.on.outer)
+    ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii;
+  }
+
+  void lvscsnni() {
+    ScopingConstruct.StaticNested.NestedInner scsnni;
+  }
+
+  void lvscsnnni() {
+    ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni;
+  }
+
+  ///
+  /// Resource variables
+  ///
+
+  void rvsn() {
+    try (StaticNested sn = null) {}
+  }
+
+  void rvnsn() {
+    try (@Nullable StaticNested nsn = null) {}
+  }
+
+  void rvi() {
+    try (Inner i = null) {}
+  }
+
+  void rvni() {
+    try (@Nullable Inner ni = null) {}
+  }
+
+  void rvscsn() {
+    try (ScopingConstruct.StaticNested scsn = null) {}
+  }
+
+  void rvscnsn() {
+    try (ScopingConstruct.@Nullable StaticNested scnsn = null) {}
+  }
+
+  void rvscnsnni() {
+    // :: error: (nullness.on.outer)
+    try (ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni = null) {}
+  }
+
+  void rvscnsnnni() {
+    // :: error: (nullness.on.outer)
+    try (ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni = null) {}
+  }
+
+  void rvsci() {
+    try (ScopingConstruct.Inner sci = null) {}
+  }
+
+  void rvsciii() {
+    try (ScopingConstruct.Inner.InnerInner sciii = null) {}
+  }
+
+  void rvscinii() {
+    try (ScopingConstruct.Inner.@Nullable InnerInner scinii = null) {}
+  }
+
+  void rvnsci() {
+    // :: error: (nullness.on.outer)
+    try (@Nullable ScopingConstruct.Inner nsci = null) {}
+  }
+
+  void rvnsciii() {
+    // :: error: (nullness.on.outer)
+    try (@Nullable ScopingConstruct.Inner.InnerInner nsciii = null) {}
+  }
+
+  void rvnscinii() {
+    // :: error: (nullness.on.outer)
+    try (@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii = null) {}
+  }
+
+  void rvscni() {
+    try (ScopingConstruct.@Nullable Inner scni = null) {}
+  }
+
+  void rvscniii() {
+    // :: error: (nullness.on.outer)
+    try (ScopingConstruct.@Nullable Inner.InnerInner scniii = null) {}
+  }
+
+  void rvscninii() {
+    // :: error: (nullness.on.outer)
+    try (ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii = null) {}
+  }
+
+  void rvscsnni() {
+    try (ScopingConstruct.StaticNested.NestedInner scsnni = null) {}
+  }
+
+  void rvscsnnni() {
+    try (ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni = null) {}
+  }
+
+  ///
+  /// For variables
+  ///
+
+  void fvsn() {
+    for (StaticNested sn = null; ; ) {}
+  }
+
+  void fvnsn() {
+    for (@Nullable StaticNested nsn = null; ; ) {}
+  }
+
+  void fvi() {
+    for (Inner i = null; ; ) {}
+  }
+
+  void fvni() {
+    for (@Nullable Inner ni = null; ; ) {}
+  }
+
+  void fvscsn() {
+    for (ScopingConstruct.StaticNested scsn = null; ; ) {}
+  }
+
+  void fvscnsn() {
+    for (ScopingConstruct.@Nullable StaticNested scnsn = null; ; ) {}
+  }
+
+  void fvscnsnni() {
+    // :: error: (nullness.on.outer)
+    for (ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni = null; ; ) {}
+  }
+
+  void fvscnsnnni() {
+    // :: error: (nullness.on.outer)
+    for (ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni = null; ; ) {}
+  }
+
+  void fvsci() {
+    for (ScopingConstruct.Inner sci = null; ; ) {}
+  }
+
+  void fvsciii() {
+    for (ScopingConstruct.Inner.InnerInner sciii = null; ; ) {}
+  }
+
+  void fvscinii() {
+    for (ScopingConstruct.Inner.@Nullable InnerInner scinii = null; ; ) {}
+  }
+
+  void fvnsci() {
+    // :: error: (nullness.on.outer)
+    for (@Nullable ScopingConstruct.Inner nsci = null; ; ) {}
+  }
+
+  void fvnsciii() {
+    // :: error: (nullness.on.outer)
+    for (@Nullable ScopingConstruct.Inner.InnerInner nsciii = null; ; ) {}
+  }
+
+  void fvnscinii() {
+    // :: error: (nullness.on.outer)
+    for (@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii = null; ; ) {}
+  }
+
+  void fvscni() {
+    for (ScopingConstruct.@Nullable Inner scni = null; ; ) {}
+  }
+
+  void fvscniii() {
+    // :: error: (nullness.on.outer)
+    for (ScopingConstruct.@Nullable Inner.InnerInner scniii = null; ; ) {}
+  }
+
+  void fvscninii() {
+    // :: error: (nullness.on.outer)
+    for (ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii = null; ; ) {}
+  }
+
+  void fvscsnni() {
+    for (ScopingConstruct.StaticNested.NestedInner scsnni = null; ; ) {}
+  }
+
+  void fvscsnnni() {
+    for (ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni = null; ; ) {}
+  }
+}
diff --git a/checker/tests/nullness/SelfAssignment.java b/checker/tests/nullness/SelfAssignment.java
new file mode 100644
index 0000000..6f85b0d
--- /dev/null
+++ b/checker/tests/nullness/SelfAssignment.java
@@ -0,0 +1,19 @@
+// Test case for issue #231
+
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class SelfAssignment {
+
+  void test(@Nullable String s) {
+    assertNonNull(s);
+    s = s.trim();
+  }
+
+  @EnsuresNonNull("#1")
+  void assertNonNull(final @Nullable Object o) {
+    if (o == null) {
+      throw new AssertionError();
+    }
+  }
+}
diff --git a/checker/tests/nullness/SelfDependentType.java b/checker/tests/nullness/SelfDependentType.java
new file mode 100644
index 0000000..a3eb305
--- /dev/null
+++ b/checker/tests/nullness/SelfDependentType.java
@@ -0,0 +1,152 @@
+// Test case for issue #4142: https://tinyurl.com/cfissue/4142
+
+// @skip-test until the issue is fixed
+
+import java.util.HashMap;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class SelfDependentType {
+
+  public void copy1(
+      HashMap<String, List<@KeyFor("#1") String>> a,
+      HashMap<String, List<@KeyFor("#2") String>> b) {
+    a = b;
+  }
+
+  public void copy2() {
+    HashMap<String, List<@KeyFor("a") String>> a = null;
+    HashMap<String, List<@KeyFor("b") String>> b = null;
+    a = b;
+  }
+
+  class SdtGraph1<T> {
+
+    HashMap<T, List<@KeyFor("childMap") T>> childMap;
+
+    // :: error: (expression.parameter.name)
+    public SdtGraph1(HashMap<T, List<@KeyFor("childMap") T>> childMap) {
+      this.childMap = childMap;
+    }
+  }
+
+  class SdtGraph2<T> {
+
+    HashMap<T, List<@KeyFor("this.childMap") T>> childMap;
+
+    // :: error: (expression.parameter.name)
+    public SdtGraph2(HashMap<T, List<@KeyFor("childMap") T>> childMap) {
+      this.childMap = childMap;
+    }
+  }
+
+  class SdtGraph3<T> {
+
+    HashMap<T, List<@KeyFor("childMap") T>> childMap;
+
+    public SdtGraph3(HashMap<T, List<@KeyFor("#1") T>> childMap) {
+      this.childMap = childMap;
+    }
+  }
+
+  class SdtGraph4<T> {
+
+    HashMap<T, List<@KeyFor("this.childMap") T>> childMap;
+
+    public SdtGraph4(HashMap<T, List<@KeyFor("#1") T>> childMap) {
+      this.childMap = childMap;
+    }
+  }
+
+  class SdtGraph5<T> {
+
+    HashMap<T, List<@KeyFor("childMap") T>> childMap;
+
+    public SdtGraph5(HashMap<T, List<@KeyFor("this.childMap") T>> childMap) {
+      this.childMap = childMap;
+    }
+  }
+
+  class SdtGraph6<T> {
+
+    HashMap<T, List<@KeyFor("this.childMap") T>> childMap;
+
+    public SdtGraph6(HashMap<T, List<@KeyFor("this.childMap") T>> childMap) {
+      this.childMap = childMap;
+    }
+  }
+
+  class SdtGraph11<T> {
+
+    HashMap<T, List<@KeyFor("childMapField") T>> childMapField;
+
+    // :: error: (expression.parameter.name)
+    public SdtGraph11(HashMap<T, List<@KeyFor("childMap") T>> childMap) {
+      this.childMapField = childMap;
+    }
+  }
+
+  class SdtGraph12<T> {
+
+    HashMap<T, List<@KeyFor("this.childMapField") T>> childMapField;
+
+    // :: error: (expression.parameter.name)
+    public SdtGraph12(HashMap<T, List<@KeyFor("childMap") T>> childMap) {
+      this.childMapField = childMap;
+    }
+  }
+
+  class SdtGraph13<T> {
+
+    HashMap<T, List<@KeyFor("childMapField") T>> childMapField;
+
+    public SdtGraph13(HashMap<T, List<@KeyFor("#1") T>> childMap) {
+      this.childMapField = childMap;
+    }
+  }
+
+  class SdtGraph14<T> {
+
+    HashMap<T, List<@KeyFor("this.childMapField") T>> childMapField;
+
+    public SdtGraph14(HashMap<T, List<@KeyFor("#1") T>> childMap) {
+      this.childMapField = childMap;
+    }
+  }
+
+  class SdtGraph15<T> {
+
+    HashMap<T, List<@KeyFor("childMapField") T>> childMapField;
+
+    public SdtGraph15(HashMap<T, List<@KeyFor("childMapField") T>> childMap) {
+      this.childMapField = childMap;
+    }
+  }
+
+  class SdtGraph16<T> {
+
+    HashMap<T, List<@KeyFor("this.childMapField") T>> childMapField;
+
+    public SdtGraph16(HashMap<T, List<@KeyFor("this.childMapField") T>> childMap) {
+      this.childMapField = childMap;
+    }
+  }
+
+  class SdtGraph17<T> {
+
+    HashMap<T, List<@KeyFor("childMapField") T>> childMapField;
+
+    public SdtGraph17(HashMap<T, List<@KeyFor("this.childMapField") T>> childMap) {
+      this.childMapField = childMap;
+    }
+  }
+
+  class SdtGraph18<T> {
+
+    HashMap<T, List<@KeyFor("this.childMapField") T>> childMapField;
+
+    public SdtGraph18(HashMap<T, List<@KeyFor("childMapField") T>> childMap) {
+      this.childMapField = childMap;
+    }
+  }
+}
diff --git a/checker/tests/nullness/SequenceAndIndices.java b/checker/tests/nullness/SequenceAndIndices.java
new file mode 100644
index 0000000..f94a0a9
--- /dev/null
+++ b/checker/tests/nullness/SequenceAndIndices.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.interning.qual.*;
+
+public final class SequenceAndIndices<T extends @Interned Object> {
+  public T seq;
+
+  public SequenceAndIndices(T seq) {
+    this.seq = seq;
+  }
+}
diff --git a/checker/tests/nullness/SetIteratorTest.java b/checker/tests/nullness/SetIteratorTest.java
new file mode 100644
index 0000000..9c60dbb
--- /dev/null
+++ b/checker/tests/nullness/SetIteratorTest.java
@@ -0,0 +1,53 @@
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+public class SetIteratorTest {
+
+  private SortedSet<String> nodes;
+  private Map<String, TreeMap<String, TreeSet<String>>> edges;
+
+  public SetIteratorTest() {
+    nodes = new TreeSet<String>();
+    edges = new HashMap<String, TreeMap<String, TreeSet<String>>>();
+  }
+
+  public Set<String> listNodes() {
+    return Collections.unmodifiableSet(nodes);
+  }
+
+  public String listChildren(String parentNode) {
+    String childrenString = "";
+
+    if (edges.get(parentNode) != null) {
+      for (String childNode : edges.get(parentNode).keySet()) {
+        // :: error: (dereference.of.nullable)
+        edges.get(parentNode).toString();
+        for (String childNodeEdgeX : edges.get(parentNode).get(childNode)) {
+          childrenString += " " + childNode + "(" + childNodeEdgeX + ")";
+        }
+      }
+    }
+
+    return childrenString;
+  }
+
+  public void listChildren2(String parentNode) {
+    if (edges.get(parentNode) != null) {
+      Iterator<String> itor = edges.get(parentNode).keySet().iterator();
+      edges.get(parentNode).toString();
+      String s = itor.next();
+      // :: error: (dereference.of.nullable)
+      edges.get(parentNode).toString();
+    }
+  }
+
+  public boolean containsNode(String node) {
+    return nodes.contains(node);
+  }
+}
diff --git a/checker/tests/nullness/Simple2.java b/checker/tests/nullness/Simple2.java
new file mode 100644
index 0000000..df0cddb
--- /dev/null
+++ b/checker/tests/nullness/Simple2.java
@@ -0,0 +1,27 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Simple2 {
+
+  @NonNull String f;
+
+  public Simple2() {
+    // :: error: (method.invocation)
+    test();
+
+    f = "abc";
+  }
+
+  public void test() {
+    System.out.println(f.toLowerCase());
+  }
+
+  public void a(Simple2 arg) {
+    @Nullable String s = null;
+    // :: error: (dereference.of.nullable)
+    s.hashCode();
+  }
+
+  public static void main(String[] args) {
+    new Simple2();
+  }
+}
diff --git a/checker/tests/nullness/SortingCollection.java b/checker/tests/nullness/SortingCollection.java
new file mode 100644
index 0000000..8b3adef
--- /dev/null
+++ b/checker/tests/nullness/SortingCollection.java
@@ -0,0 +1,23 @@
+import java.util.TreeSet;
+
+// @skip-test Crashes the Checker Framework, but skipped to avoid breaking the build
+//
+// It looks like we are relying on name equality at some point when resolving
+// a type parameter.  If you replace T by E, changing the code to:
+//    static class PollableTreeSet<E> extends TreeSet<E> {
+//    }
+// then the assertion failure goes away.  Evidently this is because
+// the annotated TreeSet.java file uses the type variable E.
+
+public class SortingCollection<T> {
+
+  class MergingIterator {
+    private final PollableTreeSet<String> queue = null;
+
+    public boolean hasNext() {
+      return !queue.isEmpty();
+    }
+  }
+
+  static class PollableTreeSet<T> extends TreeSet<T> {}
+}
diff --git a/checker/tests/nullness/StaticInLoop.java b/checker/tests/nullness/StaticInLoop.java
new file mode 100644
index 0000000..736bc14
--- /dev/null
+++ b/checker/tests/nullness/StaticInLoop.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.dataflow.qual.*;
+
+public final class StaticInLoop {
+
+  public static @MonotonicNonNull String data_trace_state = null;
+
+  @RequiresNonNull("StaticInLoop.data_trace_state")
+  private static void read_vals_and_mods_from_trace_file(Object[] vals, int[] mods) {
+    for (; ; ) {
+      data_trace_state.toString();
+      vals[0] = "hello";
+    }
+  }
+}
diff --git a/checker/tests/nullness/StaticInitialization.java b/checker/tests/nullness/StaticInitialization.java
new file mode 100644
index 0000000..b11f4e5
--- /dev/null
+++ b/checker/tests/nullness/StaticInitialization.java
@@ -0,0 +1,11 @@
+// @skip-test How do you write a localized @SuppressWarnings rather than one that covers the whole
+// class?
+
+public class StaticInitialization {
+
+  @SuppressWarnings({"nullness", "initialization.fields.uninitialized"})
+  public static Object dontWarnAboutThisField;
+
+  static {
+  }
+}
diff --git a/checker/tests/nullness/StaticInitializer.java b/checker/tests/nullness/StaticInitializer.java
new file mode 100644
index 0000000..5d87679
--- /dev/null
+++ b/checker/tests/nullness/StaticInitializer.java
@@ -0,0 +1,60 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class StaticInitializer {
+
+  public static String a;
+  // :: error: (initialization.static.field.uninitialized)
+  public static String b;
+
+  static {
+    a = "";
+  }
+
+  public StaticInitializer() {}
+}
+
+class StaticInitializer2 {
+  // :: error: (initialization.static.field.uninitialized)
+  public static String a;
+  // :: error: (initialization.static.field.uninitialized)
+  public static String b;
+}
+
+class StaticInitializer3 {
+  public static String a = "";
+}
+
+class StaticInitializer4 {
+  public static String a = "";
+  public static String b;
+
+  static {
+    b = "";
+  }
+}
+
+class StaticInitializer5 {
+  public static String a = "";
+
+  static {
+    a.toString();
+  }
+
+  public static String b = "";
+}
+
+class StaticInitializer6 {
+  public static String a = "";
+
+  public static String b;
+
+  static {
+    // TODO error expected. See #556.
+    b.toString();
+  }
+
+  static {
+    b = "";
+  }
+}
diff --git a/checker/tests/nullness/StaticInitializer2.java b/checker/tests/nullness/StaticInitializer2.java
new file mode 100644
index 0000000..5e10073
--- /dev/null
+++ b/checker/tests/nullness/StaticInitializer2.java
@@ -0,0 +1,19 @@
+// Test for Checker Framework issue 353:
+// https://github.com/typetools/checker-framework/issues/353
+// There are also a couple of tests commented out in
+// checker/tests/nullness/java8/lambda/Initialization.java
+
+// @skip-test until the issue is fixed
+
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class StaticInitializer2 {
+
+  static String a;
+
+  static {
+    // :: error: (dereference.of.nullable)
+    a.toString();
+  }
+}
diff --git a/checker/tests/nullness/Stats.java b/checker/tests/nullness/Stats.java
new file mode 100644
index 0000000..58a550f
--- /dev/null
+++ b/checker/tests/nullness/Stats.java
@@ -0,0 +1,18 @@
+// @skip-tests Failing, but commented out to avoid breaking the build
+
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Stats {
+
+  @Nullable Map<Integer, String> inv_map = null;
+
+  void dump() {
+
+    assert inv_map != null : "@AssumeAssertion(nullness)";
+
+    for (Integer inv_class : inv_map.keySet()) {
+      inv_map.get(inv_class);
+    }
+  }
+}
diff --git a/checker/tests/nullness/StringTernaryConcat.java b/checker/tests/nullness/StringTernaryConcat.java
new file mode 100644
index 0000000..b72e8e6
--- /dev/null
+++ b/checker/tests/nullness/StringTernaryConcat.java
@@ -0,0 +1,6 @@
+public class StringTernaryConcat {
+
+  public String s(Integer start) {
+    return start + (start.equals(start) ? "" : "-");
+  }
+}
diff --git a/checker/tests/nullness/SuperCall.java b/checker/tests/nullness/SuperCall.java
new file mode 100644
index 0000000..5849562
--- /dev/null
+++ b/checker/tests/nullness/SuperCall.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class SuperCall {
+
+  public static class A {
+    public A(@NonNull Object arg) {}
+  }
+
+  public static class B extends A {
+    public B(@Nullable Object arg) {
+      // :: error: (argument)
+      super(arg);
+    }
+  }
+}
diff --git a/checker/tests/nullness/SuperConstructorInit.java b/checker/tests/nullness/SuperConstructorInit.java
new file mode 100644
index 0000000..4e4820e
--- /dev/null
+++ b/checker/tests/nullness/SuperConstructorInit.java
@@ -0,0 +1,19 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class SuperConstructorInit {
+
+  String a;
+
+  public SuperConstructorInit() {
+    a = "";
+  }
+
+  public static class B extends SuperConstructorInit {
+    String b;
+    // :: error: (initialization.fields.uninitialized)
+    public B() {
+      super();
+      a.toString();
+    }
+  }
+}
diff --git a/checker/tests/nullness/SuppressDeprecation.java b/checker/tests/nullness/SuppressDeprecation.java
new file mode 100644
index 0000000..453155d
--- /dev/null
+++ b/checker/tests/nullness/SuppressDeprecation.java
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+class SuppressDeprecationOther {
+  @Deprecated
+  void old() {}
+}
+
+public class SuppressDeprecation {
+
+  @MonotonicNonNull String tz1;
+
+  @SuppressWarnings("deprecation")
+  void processOptions(String tz, SuppressDeprecationOther o) {
+    tz1 = tz;
+
+    // There should be no deprecation warning here.
+    o.old();
+
+    parseTime("hello");
+  }
+
+  @RequiresNonNull("tz1")
+  void parseTime(String time) {}
+}
diff --git a/checker/tests/nullness/SuppressWarningsPartialKeys.java b/checker/tests/nullness/SuppressWarningsPartialKeys.java
new file mode 100644
index 0000000..c9ab87b
--- /dev/null
+++ b/checker/tests/nullness/SuppressWarningsPartialKeys.java
@@ -0,0 +1,73 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class SuppressWarningsPartialKeys {
+
+  @SuppressWarnings("return")
+  @NonNull Object suppressed2() {
+    return null;
+  }
+
+  @SuppressWarnings("return")
+  @NonNull Object suppressed3() {
+    return null;
+  }
+
+  @SuppressWarnings("type")
+  @NonNull Object suppressed5() {
+    // :: error: (return)
+    return null;
+  }
+
+  @SuppressWarnings("nullness:return")
+  @NonNull Object suppressedn2() {
+    return null;
+  }
+
+  @SuppressWarnings("i")
+  @NonNull Object err1() {
+    // :: error: (return)
+    return null;
+  }
+
+  @SuppressWarnings("")
+  @NonNull Object err6() {
+    // :: error: (return)
+    return null;
+  }
+
+  @SuppressWarnings("nullness:i")
+  @NonNull Object errn1() {
+    // :: error: (return)
+    return null;
+  }
+
+  @SuppressWarnings("nullness:eturn.type")
+  @NonNull Object errn2() {
+    // :: error: (return)
+    return null;
+  }
+
+  @SuppressWarnings("nullness:typ")
+  @NonNull Object errn3() {
+    // :: error: (return)
+    return null;
+  }
+
+  @SuppressWarnings("nullness:ype.incompatible")
+  @NonNull Object errn4() {
+    // :: error: (return)
+    return null;
+  }
+
+  @SuppressWarnings("nullness:return.type.")
+  @NonNull Object errn5() {
+    // :: error: (return)
+    return null;
+  }
+
+  @SuppressWarnings("nullness:")
+  @NonNull Object errn6() {
+    // :: error: (return)
+    return null;
+  }
+}
diff --git a/checker/tests/nullness/SuppressWarningsTest.java b/checker/tests/nullness/SuppressWarningsTest.java
new file mode 100644
index 0000000..73e09dc
--- /dev/null
+++ b/checker/tests/nullness/SuppressWarningsTest.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class SuppressWarningsTest {
+
+  @SuppressWarnings("all")
+  void test() {
+    String a = null;
+    a.toString();
+  }
+}
diff --git a/checker/tests/nullness/SwitchTest.java b/checker/tests/nullness/SwitchTest.java
new file mode 100644
index 0000000..b972fe2
--- /dev/null
+++ b/checker/tests/nullness/SwitchTest.java
@@ -0,0 +1,36 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class SwitchTest {
+  public static void main(String[] args) {
+    // :: error: (switching.nullable)
+    switch (getNbl()) {
+      case X:
+        System.out.println("X");
+        break;
+      default:
+        System.out.println("default");
+    }
+  }
+
+  public static void goodUse() {
+    switch (getNN()) {
+      case X:
+        System.out.println("X");
+        break;
+      default:
+        System.out.println("default");
+    }
+  }
+
+  public static @Nullable A getNbl() {
+    return null;
+  }
+
+  public static A getNN() {
+    return A.X;
+  }
+
+  public static enum A {
+    X
+  }
+}
diff --git a/checker/tests/nullness/Synchronization.java b/checker/tests/nullness/Synchronization.java
new file mode 100644
index 0000000..5a4b073
--- /dev/null
+++ b/checker/tests/nullness/Synchronization.java
@@ -0,0 +1,35 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Synchronization {
+
+  // Plain
+  public void bad() {
+    Object o = null;
+    // :: error: (locking.nullable)
+    synchronized (o) {
+    } // should emit error
+  }
+
+  public void ok() {
+    // NonNull specifically
+    @NonNull Object o1 = "m";
+    synchronized (o1) {
+    }
+  }
+
+  public void flow() {
+    Object o = null;
+    o = "m";
+    synchronized (o) {
+    } // valid
+    o = null;
+    // :: error: (locking.nullable)
+    synchronized (o) {
+    } // invalid
+  }
+
+  public Synchronization() {
+    synchronized (this) {
+    }
+  }
+}
diff --git a/checker/tests/nullness/TernaryNested.java b/checker/tests/nullness/TernaryNested.java
new file mode 100644
index 0000000..293e327
--- /dev/null
+++ b/checker/tests/nullness/TernaryNested.java
@@ -0,0 +1,18 @@
+// Test case for Issue 331:
+// https://github.com/typetools/checker-framework/issues/331
+
+import java.util.List;
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class TernaryNested {
+  Object foo(boolean b) {
+    Object o = b ? "" : (b ? "" : "");
+    return o;
+  }
+
+  void bar(List<Object> l, boolean b) {
+    Object o = b ? "" : (b ? "" : "");
+    l.add(o);
+  }
+}
diff --git a/checker/tests/nullness/TernaryNullness.java b/checker/tests/nullness/TernaryNullness.java
new file mode 100644
index 0000000..43806b6
--- /dev/null
+++ b/checker/tests/nullness/TernaryNullness.java
@@ -0,0 +1,11 @@
+// Test case for issue #277: https://github.com/typetools/checker-framework/issues/277
+
+import org.checkerframework.checker.nullness.qual.*;
+
+abstract class TernaryNullness {
+  void f(@Nullable Object o) {
+    g(42, o != null ? o.hashCode() : 0);
+  }
+
+  abstract void g(Object x, Object xs);
+}
diff --git a/checker/tests/nullness/TestAssignment.java b/checker/tests/nullness/TestAssignment.java
new file mode 100644
index 0000000..1233c38
--- /dev/null
+++ b/checker/tests/nullness/TestAssignment.java
@@ -0,0 +1,18 @@
+package examples;
+
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class TestAssignment {
+
+  void a() {
+    @NonNull String f = "abc";
+
+    // :: error: (assignment)
+    f = null;
+  }
+
+  void b() {
+    @UnknownInitialization @NonNull TestAssignment f = new TestAssignment();
+  }
+}
diff --git a/checker/tests/nullness/TestFromPullRequest880.java b/checker/tests/nullness/TestFromPullRequest880.java
new file mode 100644
index 0000000..7d3104c
--- /dev/null
+++ b/checker/tests/nullness/TestFromPullRequest880.java
@@ -0,0 +1,26 @@
+// Test case from pull request 880:
+//   https://github.com/typetools/checker-framework/pull/880
+// Test might also be relevant to issue 989:
+//   https://github.com/typetools/checker-framework/issues/989
+// Also note a test that uses multiple compilation units at:
+//   checker/jtreg/nullness/annotationsOnExtends/
+
+import java.io.Serializable;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+class TFPR880Test implements Serializable {}
+
+class TFPR880Use {
+  void foo() {
+    TFPR880Test other = null;
+  }
+}
+
+abstract class TFPR880TestSub extends TFPR880Test implements List<@NonNull String> {}
+
+class TFPR880SubUse {
+  void foo() {
+    TFPR880TestSub other = null;
+  }
+}
diff --git a/checker/tests/nullness/TestInfer.java b/checker/tests/nullness/TestInfer.java
new file mode 100644
index 0000000..82fbd08
--- /dev/null
+++ b/checker/tests/nullness/TestInfer.java
@@ -0,0 +1,21 @@
+// Test case for issue #238: https://github.com/typetools/checker-framework/issues/238
+
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.UnknownKeyFor;
+
+public class TestInfer {
+  <T extends Object> T getValue(List<T> l) {
+    return l.get(0);
+  }
+
+  void bar(Object o) {}
+
+  void foo() {
+    List<@UnknownKeyFor ? extends Object> ls = new ArrayList<>();
+    bar(getValue(ls)); // this fails, but just getValue(ls) is OK
+    // casting is also OK, ie bar((Object)getValue(ls))
+    // The constraint should be T<:Object, which should typecheck since ls:List<? extends Object>
+    // unifies with List<T> where T<:Object.
+  }
+}
diff --git a/checker/tests/nullness/TestPolyNull.java b/checker/tests/nullness/TestPolyNull.java
new file mode 100644
index 0000000..791a429
--- /dev/null
+++ b/checker/tests/nullness/TestPolyNull.java
@@ -0,0 +1,50 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class TestPolyNull {
+  @PolyNull String identity(@PolyNull String str) {
+    return str;
+  }
+
+  void test1() {
+    identity(null);
+  }
+
+  void test2() {
+    identity((@Nullable String) null);
+  }
+
+  public static @PolyNull String[] typeArray(@PolyNull Object[] seq, @Nullable String nullable) {
+    @SuppressWarnings("nullness") // ignore array initialization here.
+    @PolyNull String[] retval = new @Nullable String[seq.length];
+    for (int i = 0; i < seq.length; i++) {
+      if (seq[i] == null) {
+        // null can be assigned into the PolyNull array, because we
+        // performed a test on seq and know that it is nullable.
+        retval[i] = null;
+        // and so can something that is nullable
+        retval[i] = nullable;
+        // One can always add a dummy value: nonnull is the bottom
+        // type and legal for any instantiation of PolyNull.
+        retval[i] = "dummy";
+      } else {
+        retval[i] = seq[i].getClass().toString();
+        // :: error: (assignment)
+        retval[i] = null;
+        // :: error: (assignment)
+        retval[i] = nullable;
+      }
+    }
+    return retval;
+  }
+
+  public static @PolyNull String identity2(@PolyNull String a) {
+    return (a == null) ? null : a;
+  }
+
+  public static @PolyNull String identity3(@PolyNull String a) {
+    if (a == null) {
+      return null;
+    }
+    return a;
+  }
+}
diff --git a/checker/tests/nullness/TestValOf.java b/checker/tests/nullness/TestValOf.java
new file mode 100644
index 0000000..32cf9af
--- /dev/null
+++ b/checker/tests/nullness/TestValOf.java
@@ -0,0 +1,14 @@
+// Test case for issue #243: https://github.com/typetools/checker-framework/issues/243
+
+public class TestValOf<T extends Enum<T>> {
+
+  private final Class<T> enumClass;
+
+  private TestValOf(Class<T> enumClass) {
+    this.enumClass = enumClass;
+  }
+
+  T foo(String value) {
+    return Enum.valueOf(enumClass, value);
+  }
+}
diff --git a/checker/tests/nullness/ThisIsNN.java b/checker/tests/nullness/ThisIsNN.java
new file mode 100644
index 0000000..d9d9041
--- /dev/null
+++ b/checker/tests/nullness/ThisIsNN.java
@@ -0,0 +1,29 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ThisIsNN {
+  Object out = new Object();
+
+  class Inner {
+    void test1() {
+      out = this;
+      out = ThisIsNN.this;
+    }
+
+    Object in = new Object();
+
+    void test2(Inner this) {
+      Object nonRawThis = this;
+      out = nonRawThis;
+    }
+
+    void test3(Inner this) {
+      Object nonRawThis = ThisIsNN.this;
+      out = nonRawThis;
+    }
+  }
+
+  void test4(ThisIsNN this) {
+    Object nonRawThis = this;
+    out = nonRawThis;
+  }
+}
diff --git a/checker/tests/nullness/ThisNodeTest.java b/checker/tests/nullness/ThisNodeTest.java
new file mode 100644
index 0000000..0076d2d
--- /dev/null
+++ b/checker/tests/nullness/ThisNodeTest.java
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ThisNodeTest {
+  public ThisNodeTest() {
+    new Object() {
+      void test() {
+        @UnderInitialization ThisNodeTest l1 = ThisNodeTest.this;
+        // :: error: (assignment)
+        @Initialized ThisNodeTest l2 = ThisNodeTest.this;
+
+        // :: error: (method.invocation)
+        ThisNodeTest.this.foo();
+        // :: error: (method.invocation)
+        foo();
+      }
+    };
+  }
+
+  void foo() {}
+}
diff --git a/checker/tests/nullness/ThisQualified.java b/checker/tests/nullness/ThisQualified.java
new file mode 100644
index 0000000..fb182d9
--- /dev/null
+++ b/checker/tests/nullness/ThisQualified.java
@@ -0,0 +1,12 @@
+// Test case for Issue #2208:
+// https://github.com/typetools/checker-framework/issues/2208
+
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+
+public class ThisQualified {
+  public ThisQualified() {
+    super();
+    @UnderInitialization ThisQualified a = this;
+    @UnderInitialization ThisQualified b = ThisQualified.this;
+  }
+}
diff --git a/checker/tests/nullness/ThisTest.java b/checker/tests/nullness/ThisTest.java
new file mode 100644
index 0000000..8961a8a
--- /dev/null
+++ b/checker/tests/nullness/ThisTest.java
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+@org.checkerframework.framework.qual.DefaultQualifier(
+    org.checkerframework.checker.nullness.qual.NonNull.class)
+public class ThisTest {
+
+  public String field;
+
+  public ThisTest(String field) {
+    this.field = field;
+  }
+
+  void doNothing() {}
+
+  class InnerClass {
+    public void accessOuterThis() {
+      ThisTest.this.doNothing();
+      String s = ThisTest.this.field;
+    }
+  }
+}
diff --git a/checker/tests/nullness/ThreadLocalTest.java b/checker/tests/nullness/ThreadLocalTest.java
new file mode 100644
index 0000000..b401570
--- /dev/null
+++ b/checker/tests/nullness/ThreadLocalTest.java
@@ -0,0 +1,23 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ThreadLocalTest {
+
+  // implementation MUST override initialValue(), or SuppressWarnings is unsound
+  @SuppressWarnings("nullness:type.argument")
+  class MyThreadLocalNN extends ThreadLocal<@NonNull Integer> {
+    @Override
+    protected Integer initialValue() {
+      return Integer.valueOf(0);
+    }
+  }
+
+  void foo() {
+    // :: error: (type.argument)
+    new ThreadLocal<@NonNull Object>();
+    // :: error: (type.argument)
+    new InheritableThreadLocal<@NonNull Object>();
+    new ThreadLocal<@Nullable Object>();
+    new InheritableThreadLocal<@Nullable Object>();
+    new MyThreadLocalNN();
+  }
+}
diff --git a/checker/tests/nullness/ThreadLocalTest2.java b/checker/tests/nullness/ThreadLocalTest2.java
new file mode 100644
index 0000000..3d0f027
--- /dev/null
+++ b/checker/tests/nullness/ThreadLocalTest2.java
@@ -0,0 +1,71 @@
+// Test case for issue #1572 and issue #1330
+// https://github.com/typetools/checker-framework/issues/1572
+// https://github.com/typetools/checker-framework/issues/1330
+
+// @skip-test until the issues are fixed
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ThreadLocalTest2 {
+
+  private static int unwrap(final ThreadLocal<Integer> tl) {
+    return tl.get().intValue();
+  }
+
+  private static void wrap(final ThreadLocal<Integer> tl, final int value) {
+    tl.set(Integer.valueOf(value));
+  }
+
+  private static ThreadLocal<Integer> consumed_chars =
+      new ThreadLocal<Integer>() {
+
+        @Override
+        protected Integer initialValue() {
+          return Integer.valueOf(0);
+        }
+      };
+
+  class MyThreadLocalNN extends ThreadLocal<@NonNull Integer> {
+    @Override
+    protected Integer initialValue() {
+      return new Integer(0);
+    }
+  }
+
+  class MyThreadLocalNnIncorrectOverride extends ThreadLocal<@NonNull Integer> {
+    @Override
+    // :: error: (override.return)
+    protected @Nullable Integer initialValue() {
+      return null;
+    }
+  }
+
+  // :: error: (method.not.overridden)
+  class MyThreadLocalNnNoOverride extends ThreadLocal<@NonNull Integer> {}
+
+  class MyThreadLocalNble extends ThreadLocal<@Nullable Integer> {
+    @Override
+    protected @Nullable Integer initialValue() {
+      return null;
+    }
+  }
+
+  class MyThreadLocalNbleStrongerOverride extends ThreadLocal<@Nullable Integer> {
+    @Override
+    protected @NonNull Integer initialValue() {
+      return new Integer(0);
+    }
+  }
+
+  class MyThreadLocalNbleNoOverride extends ThreadLocal<@Nullable Integer> {}
+
+  void foo() {
+    // :: error: (type.argument)
+    new ThreadLocal<@NonNull Object>();
+    // :: error: (type.argument)
+    new InheritableThreadLocal<@NonNull Object>();
+    new ThreadLocal<@Nullable Object>();
+    new InheritableThreadLocal<@Nullable Object>();
+    new MyThreadLocalNN();
+  }
+}
diff --git a/checker/tests/nullness/Throwing.java b/checker/tests/nullness/Throwing.java
new file mode 100644
index 0000000..63e43bb
--- /dev/null
+++ b/checker/tests/nullness/Throwing.java
@@ -0,0 +1,22 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Throwing {
+
+  String a;
+
+  // :: error: (initialization.fields.uninitialized)
+  public Throwing(boolean throwError) {
+    if (throwError) {
+      throw new RuntimeException("not a real error");
+    }
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  public Throwing(int input) {
+    try {
+      throw new RuntimeException("not a real error");
+    } catch (RuntimeException e) {
+      // do nothing
+    }
+  }
+}
diff --git a/checker/tests/nullness/ToArrayDiagnostics.java b/checker/tests/nullness/ToArrayDiagnostics.java
new file mode 100644
index 0000000..de023eb
--- /dev/null
+++ b/checker/tests/nullness/ToArrayDiagnostics.java
@@ -0,0 +1,30 @@
+import java.util.ArrayList;
+
+public class ToArrayDiagnostics {
+
+  String[] ok2(ArrayList<String> list) {
+    return list.toArray(new String[] {});
+  }
+
+  String[] ok3(ArrayList<String> list) {
+    return list.toArray(new String[0]);
+  }
+
+  String[] ok4(ArrayList<String> list) {
+    return list.toArray(new String[list.size()]);
+  }
+
+  String[] warn1(ArrayList<String> list) {
+    // :: error: (new.array)
+    String[] resultArray = new String[list.size()];
+    // :: error: (return) :: warning: (toarray.nullable.elements.not.newarray)
+    return list.toArray(resultArray);
+  }
+
+  String[] warn2(ArrayList<String> list) {
+    int size = list.size();
+    // :: error: (new.array) :: error: (return) :: warning:
+    // (toarray.nullable.elements.mismatched.size)
+    return list.toArray(new String[size]);
+  }
+}
diff --git a/checker/tests/nullness/ToArrayNullness.java b/checker/tests/nullness/ToArrayNullness.java
new file mode 100644
index 0000000..affcb8e
--- /dev/null
+++ b/checker/tests/nullness/ToArrayNullness.java
@@ -0,0 +1,103 @@
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ToArrayNullness {
+  private List<@Nullable String> nullableList = new ArrayList<>();
+  private List<@NonNull String> nonnullList = new ArrayList<>();
+
+  void listToArrayObject() {
+    for (@Nullable Object o : nullableList.toArray()) {}
+    // :: error: (enhancedfor)
+    for (@NonNull Object o : nullableList.toArray()) {}
+
+    for (@Nullable Object o : nonnullList.toArray()) {}
+    for (@NonNull Object o : nonnullList.toArray()) {}
+  }
+
+  void listToArrayE() {
+    for (@Nullable String o : nullableList.toArray(new @Nullable String[0])) {}
+    // :: error: (enhancedfor)
+    for (@NonNull String o : nullableList.toArray(new @Nullable String[0])) {}
+    // TODOINVARR:: error: (argument)
+    for (@Nullable String o : nullableList.toArray(new @NonNull String[0])) {}
+    // TODOINVARR:: error: (argument)
+    // :: error: (enhancedfor)
+    for (@NonNull String o : nullableList.toArray(new @NonNull String[0])) {}
+
+    for (@Nullable String o : nonnullList.toArray(new String[0])) {}
+    // No error expected here. Note that the heuristics determine that the given array
+    // is not used and that a new one will be created.
+    for (@NonNull String o : nonnullList.toArray(new @Nullable String[0])) {}
+    for (@Nullable String o : nonnullList.toArray(new @NonNull String[0])) {}
+    for (@NonNull String o : nonnullList.toArray(new @NonNull String[0])) {}
+  }
+
+  private Collection<@Nullable String> nullableCol = new ArrayList<@Nullable String>();
+  private Collection<@NonNull String> nonnullCol = new ArrayList<@NonNull String>();
+
+  void colToArrayObject() {
+    for (@Nullable Object o : nullableCol.toArray()) {}
+    // :: error: (enhancedfor)
+    for (@NonNull Object o : nullableCol.toArray()) {}
+
+    for (@Nullable Object o : nonnullCol.toArray()) {}
+    for (@NonNull Object o : nonnullCol.toArray()) {}
+  }
+
+  void colToArrayE() {
+    for (@Nullable String o : nullableCol.toArray(new @Nullable String[0])) {}
+    // :: error: (enhancedfor)
+    for (@NonNull String o : nullableCol.toArray(new @Nullable String[0])) {}
+    // TODOINVARR:: error: (argument)
+    for (@Nullable String o : nullableCol.toArray(new @NonNull String[0])) {}
+    // TODOINVARR:: error: (argument)
+    // :: error: (enhancedfor)
+    for (@NonNull String o : nullableCol.toArray(new @NonNull String[0])) {}
+
+    for (@Nullable String o : nonnullCol.toArray(new String[0])) {}
+    // No error expected here. Note that the heuristics determine that the given array
+    // is not used and that a new one will be created.
+    for (@NonNull String o : nonnullCol.toArray(new @Nullable String[0])) {}
+    for (@Nullable String o : nonnullCol.toArray(new @NonNull String[0])) {}
+    for (@NonNull String o : nonnullCol.toArray(new @NonNull String[0])) {}
+  }
+
+  void testHearusitics() {
+    for (@Nullable String o : nonnullCol.toArray(new String[] {})) {}
+    for (@NonNull String o : nonnullCol.toArray(new String[] {})) {}
+    for (@Nullable String o : nonnullCol.toArray(new String[0])) {}
+    for (@NonNull String o : nonnullCol.toArray(new String[0])) {}
+    for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size()])) {}
+    for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size()])) {}
+
+    // :: warning: (toarray.nullable.elements.mismatched.size)
+    for (@Nullable String o : nonnullCol.toArray(new @Nullable String[] {null})) {}
+    // :: error: (enhancedfor) :: warning: (toarray.nullable.elements.mismatched.size)
+    for (@NonNull String o : nonnullCol.toArray(new @Nullable String[] {null})) {}
+    // Size 1 is too big for an empty array. Complain. TODO: Could allow as result is Nullable.
+    // :: error: (new.array) :: warning: (toarray.nullable.elements.mismatched.size)
+    for (@Nullable String o : nonnullCol.toArray(new String[1])) {}
+    // :: error: (enhancedfor) :: error: (new.array) :: warning:
+    // (toarray.nullable.elements.mismatched.size)
+    for (@NonNull String o : nonnullCol.toArray(new String[1])) {}
+    // Array too big -> complain. TODO: Could allow as result is Nullable.
+    // :: error: (new.array) :: warning: (toarray.nullable.elements.mismatched.size)
+    for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size() + 1])) {}
+    // Array too big -> complain.
+    // :: error: (enhancedfor) :: error: (new.array) :: warning:
+    // (toarray.nullable.elements.mismatched.size)
+    for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size() + 1])) {}
+
+    // cannot handle the following cases for now
+    // new array not size 0 or .size -> complain about cration. TODO: Could allow as result is
+    // Nullable.
+    // :: error: (new.array) :: warning: (toarray.nullable.elements.mismatched.size)
+    for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size() - 1])) {}
+    // New array not size 0 or .size -> complain about creation.
+    // :: error: (enhancedfor) :: error: (new.array) :: warning:
+    // (toarray.nullable.elements.mismatched.size)
+    for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size() - 1])) {}
+  }
+}
diff --git a/checker/tests/nullness/TryCatch.java b/checker/tests/nullness/TryCatch.java
new file mode 100644
index 0000000..e6ddbb6
--- /dev/null
+++ b/checker/tests/nullness/TryCatch.java
@@ -0,0 +1,41 @@
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+class EntryReader {
+  public EntryReader() throws IOException {}
+}
+
+public class TryCatch {
+  void constructorException() throws IOException {
+    List<Exception> file_errors = new ArrayList<>();
+    try {
+      new EntryReader();
+    } catch (FileNotFoundException e) {
+      file_errors.add(e);
+    }
+  }
+
+  void unreachableCatch(String[] xs) {
+    String t = "";
+    t.toString();
+    try {
+    } catch (Throwable e) {
+      // Note that this code is dead.
+      // :: error: (dereference.of.nullable)
+      t.toString();
+    }
+  }
+
+  void noClassDefFoundError(@Nullable Object x) {
+    try {
+      Class cls = EntryReader.class;
+    } catch (NoClassDefFoundError e) {
+      if (x != null) {
+        // OK
+        x.toString();
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/TryWithResources.java b/checker/tests/nullness/TryWithResources.java
new file mode 100644
index 0000000..5436db0
--- /dev/null
+++ b/checker/tests/nullness/TryWithResources.java
@@ -0,0 +1,45 @@
+import java.io.*;
+import java.util.zip.ZipFile;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class TryWithResources {
+  void m1(InputStream stream) {
+    try (BufferedReader in = new BufferedReader(new InputStreamReader(stream))) {
+      in.toString();
+    } catch (Exception e) {
+    }
+  }
+
+  void m2() {
+    try (BufferedReader in = null) {
+      // :: error: (dereference.of.nullable)
+      in.toString();
+    } catch (Exception e) {
+    }
+  }
+
+  // Check that catch blocks and code after try-catch are part of CFG (and flow-sensitive
+  // type-refinements work there).
+  boolean m3(@Nullable Object x) {
+    try (ZipFile f = openZipFile()) {
+      return true;
+    } catch (IOException e) {
+      if (x != null) {
+        // OK
+        x.toString();
+      }
+    }
+
+    if (x != null) {
+      // OK
+      return x.equals(x);
+    }
+
+    return false;
+  }
+
+  // Helper
+  private static ZipFile openZipFile() throws IOException {
+    throw new IOException("No zip-file for you!");
+  }
+}
diff --git a/checker/tests/nullness/TryWithResourcesAnno.java b/checker/tests/nullness/TryWithResourcesAnno.java
new file mode 100644
index 0000000..18b6cec
--- /dev/null
+++ b/checker/tests/nullness/TryWithResourcesAnno.java
@@ -0,0 +1,25 @@
+// Test case for https://tinyurl.com/cfissue/3305 .
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class TryWithResourcesAnno {
+  public static void f() {
+    try (@Nullable AutoCloseable obj = null) {
+    } catch (Exception e) {
+    }
+  }
+
+  public static void g() {
+    try (@Nullable AutoCloseable obj1 = null;
+        AutoCloseable obj2 = null) {
+    } catch (Exception e) {
+    }
+  }
+
+  public static void h() {
+    try (AutoCloseable obj1 = null;
+        @Nullable AutoCloseable obj2 = null) {
+    } catch (Exception e) {
+    }
+  }
+}
diff --git a/checker/tests/nullness/TwoStaticInitBlocks.java b/checker/tests/nullness/TwoStaticInitBlocks.java
new file mode 100644
index 0000000..9265767
--- /dev/null
+++ b/checker/tests/nullness/TwoStaticInitBlocks.java
@@ -0,0 +1,45 @@
+import java.util.regex.*;
+
+// this is a test-case for initialization that covers multiple initializer blocks, field
+// initializers and a few other things
+public class TwoStaticInitBlocks {
+
+  String f2;
+  String f1 = (f2 = "");
+
+  {
+    t = "";
+    f1.toString();
+    f2.toString();
+  }
+
+  final String ws_regexp;
+  String t;
+  String s;
+
+  {
+    ws_regexp = "hello";
+    t.toString();
+    // :: error: (dereference.of.nullable)
+    s.toString();
+  }
+}
+
+class TwoStaticInitBlocks2 {
+  static String f2;
+  static String f1 = (f2 = "");
+
+  static {
+    t = "";
+    f1.toString();
+    f2.toString();
+  }
+
+  static final String ws_regexp;
+  static String t;
+
+  static {
+    ws_regexp = "hello";
+    t.toString();
+  }
+}
diff --git a/checker/tests/nullness/TypeVarPrimitivesNullness.java b/checker/tests/nullness/TypeVarPrimitivesNullness.java
new file mode 100644
index 0000000..0049b46
--- /dev/null
+++ b/checker/tests/nullness/TypeVarPrimitivesNullness.java
@@ -0,0 +1,33 @@
+// Unannotated version in framework/tests/all-systems/TypeVarPrimitives.java
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class TypeVarPrimitivesNullness {
+  <T extends @Nullable Long> void method(T tLong) {
+    // :: error: (unboxing.of.nullable)
+    long l = tLong;
+  }
+
+  <T extends @Nullable Long & @Nullable Cloneable> void methodIntersection(T tLong) {
+    // :: error: (unboxing.of.nullable)
+    long l = tLong;
+  }
+
+  <T extends @Nullable Long> void method2(@NonNull T tLong) {
+    long l = tLong;
+  }
+
+  <T extends @Nullable Long & @Nullable Cloneable> void methodIntersection2(@NonNull T tLong) {
+    long l = tLong;
+  }
+
+  <T extends @Nullable Long> void method3(@Nullable T tLong) {
+    // :: error: (unboxing.of.nullable)
+    long l = tLong;
+  }
+
+  <T extends @Nullable Long & @Nullable Cloneable> void methodIntersection3(@Nullable T tLong) {
+    // :: error: (unboxing.of.nullable)
+    long l = tLong;
+  }
+}
diff --git a/checker/tests/nullness/UnannoPrimitives.java b/checker/tests/nullness/UnannoPrimitives.java
new file mode 100644
index 0000000..c2bb0ce
--- /dev/null
+++ b/checker/tests/nullness/UnannoPrimitives.java
@@ -0,0 +1,58 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class UnannoPrimitives {
+  // :: error: (nullness.on.primitive)
+  @Nullable int f;
+
+  // :: error: (nullness.on.primitive)
+  @NonNull int g;
+
+  void local() {
+    // test whether an arbitrary declaration annotation gets confused
+    @SuppressWarnings("tata")
+    int h = Integer.valueOf(5);
+
+    int i = Integer.valueOf(99) + 1900;
+    int j = 7 + 1900;
+
+    // :: error: (nullness.on.primitive)
+    @Nullable int f;
+
+    // :: error: (nullness.on.primitive)
+    @NonNull int g;
+  }
+
+  static void testDate() {
+    @SuppressWarnings("deprecation") // for iCal4j
+    int year = new java.util.Date().getYear() + 1900;
+    String strDate = "/" + year;
+  }
+
+  // :: error: (nullness.on.primitive)
+  @Nullable byte[] d1 = {4};
+  byte @Nullable [] d1b = {4};
+
+  // :: error: (nullness.on.primitive)
+  @Nullable byte[][] twoD = {{4}};
+
+  // :: error: (nullness.on.primitive)
+  @Nullable byte[][][] threeD = {{{4}}};
+
+  // :: error: (nullness.on.primitive)
+  @Nullable byte[][][][] fourD = {{{{4}}}};
+
+  @SuppressWarnings("ha!")
+  byte[] d2 = {4};
+
+  // :: error: (nullness.on.primitive)
+  Object ar = new @Nullable byte[] {4};
+
+  // :: error: (nullness.on.primitive)
+  Object ar2 = new @NonNull byte[] {42};
+
+  void testCasts(Integer i1) {
+    Object i2 = (int) i1;
+    // :: error: (nullness.on.primitive)
+    Object i3 = (@Nullable int) i1;
+  }
+}
diff --git a/checker/tests/nullness/UnannoPrimitivesDefaults.java b/checker/tests/nullness/UnannoPrimitivesDefaults.java
new file mode 100644
index 0000000..016d059
--- /dev/null
+++ b/checker/tests/nullness/UnannoPrimitivesDefaults.java
@@ -0,0 +1,16 @@
+public class UnannoPrimitivesDefaults {
+  @org.checkerframework.framework.qual.DefaultQualifier(
+      org.checkerframework.checker.nullness.qual.NonNull.class)
+  class Decl {
+    // The return type is not annotated with @NonNull, because
+    // the implicit annotation for @Primitive takes precedence.
+    int test() {
+      return 5;
+    }
+  }
+
+  class Use {
+    Decl d = new Decl();
+    int x = d.test();
+  }
+}
diff --git a/checker/tests/nullness/UnboxConditions.java b/checker/tests/nullness/UnboxConditions.java
new file mode 100644
index 0000000..8ac1993
--- /dev/null
+++ b/checker/tests/nullness/UnboxConditions.java
@@ -0,0 +1,26 @@
+public class UnboxConditions {
+  public static void main(String[] args) {
+    Boolean b = null;
+    Boolean b1 = null;
+    Boolean b2 = null;
+    Boolean b3 = null;
+    Boolean b4 = null;
+    // :: error: (condition.nullable)
+    if (b) {}
+    // :: error: (condition.nullable)
+    b = b1 ? b : b;
+    // :: error: (condition.nullable)
+    while (b2) {}
+    do {
+      // :: error: (condition.nullable)
+    } while (b3);
+    // :: error: (condition.nullable)
+    for (; b4; ) {}
+    // legal!
+    for (; ; ) {
+      break;
+    }
+    // Eliding the condition in a "while" is illegal Java syntax.
+    // while () {}
+  }
+}
diff --git a/checker/tests/nullness/Unboxing.java b/checker/tests/nullness/Unboxing.java
new file mode 100644
index 0000000..30682b4
--- /dev/null
+++ b/checker/tests/nullness/Unboxing.java
@@ -0,0 +1,43 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Unboxing {
+
+  @Nullable Integer f;
+
+  public void t1() {
+    // :: error: (unboxing.of.nullable)
+    int l = f + 1;
+    // no error, since f has been unboxed
+    f.toString();
+  }
+
+  public void t2() {
+    try {
+      // :: error: (unboxing.of.nullable)
+      int l = f + 1;
+    } catch (NullPointerException npe) {
+      // f is known to be null on the exception edge
+      // :: error: (unboxing.of.nullable)
+      int m = f + 1;
+    }
+    // after the merge, f cannot be null
+    f.toString();
+  }
+
+  void foo(@Nullable Integer in) {
+    // :: error: (unboxing.of.nullable)
+    int q = in;
+  }
+
+  int bar(@Nullable Integer in) {
+    // :: error: (unboxing.of.nullable)
+    return in;
+  }
+
+  <T extends @Nullable Integer> int barT(T in) {
+    // :: error: (unboxing.of.nullable)
+    int q = in;
+    // :: error: (unboxing.of.nullable)
+    return in;
+  }
+}
diff --git a/checker/tests/nullness/UnexpectedRaw.java b/checker/tests/nullness/UnexpectedRaw.java
new file mode 100644
index 0000000..e673847
--- /dev/null
+++ b/checker/tests/nullness/UnexpectedRaw.java
@@ -0,0 +1,58 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+interface Consumer<A extends @Nullable Object> {
+  public void consume(A object);
+}
+
+class Utils {
+
+  public static <B extends @Nullable Object> Consumer<B> cast(
+      final @Nullable Consumer<? super B> consumer) {
+    throw new RuntimeException();
+  }
+
+  public static <C extends @Nullable Object> Consumer<C> getConsumer() {
+    // null for simplicity, but could be anything
+    Consumer<@Nullable Object> nullConsumer = null;
+
+    // Previous reasoning for this to generate an (argument) error was:
+    // C could be @NonNull Object, so argument is incompatible?
+    //
+    // This is poor reasoning, however, because the type of the formal parameter should be:
+    // @Nullable Consumer< ? [
+    //                         super C[ extends @Nullable Object
+    //                                  super @NonNull  Void
+    //                                ]
+    //                         extends @Nullable Object
+    // ]
+    // The primary annotations on nullConsumer and the formal parameter consumer are
+    // identical, so it comes down to the annotations on the type arguments.
+
+    // Let X stand in for the type argument of nullConsumer.  For it to be a valid parameter, X must
+    // be contained by the type argument of the formal parameter, ? super C.
+    //
+    // In other words, the following constraints must hold:
+    //
+    // C1: X <: upper bound of (? super C)
+    // C2: lower bound of (? super C) <: X
+    //
+    // we can simplify these constraints by substituting out the lower and upper bound of
+    // ? super C.
+    // C1: X <: @Nullable Object
+    // C2: C <: X
+    //
+    // we can simplify the constraints again by substituting X with the actual type argument to
+    // nullConsumer and in C2, we can substitute C with its upper bound, since for the
+    // constraint to hold X must be above C's upper bound.  This yields:
+    //
+    // C1: @Nullable Object <: @Nullable Object
+    // C2: @Nullable Object <: @Nullable Object
+    //
+    // Since, for all type's T => T <: T, both C1 and C2 are upheld and the following statement
+    // should NOT report an error
+    Consumer<C> result = Utils.<C>cast(nullConsumer);
+
+    // on a side note, I am not sure why this is called unexpected raw
+    return result;
+  }
+}
diff --git a/checker/tests/nullness/UnusedNullness.java b/checker/tests/nullness/UnusedNullness.java
new file mode 100644
index 0000000..bcd7b75
--- /dev/null
+++ b/checker/tests/nullness/UnusedNullness.java
@@ -0,0 +1,53 @@
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.Unused;
+
+// TODO: feature request: the Nullness Checker should be aware of the @Unused annotation.
+// This is difficult to implement: one needs to determine the correct AnnotatedTypeFactory for the
+// "when" type system and use it to determine the right annotated type. We currently don't have a
+// mechanism to do this.
+//
+// @skip-test
+public class UnusedNullness {
+
+  @SubtypeOf({})
+  @Target(ElementType.TYPE_USE)
+  public @interface Prototype {}
+
+  @Unused(when = Prototype.class)
+  public Object ppt;
+
+  protected @Prototype UnusedNullness() {
+    // It should be legal to initialize an unused field to null in
+    // a constructor with @Prototype receiver.
+    this.ppt = null;
+  }
+
+  protected @Prototype UnusedNullness(int disambiguate_overloading) {
+    // It should be legal to NOT initialize an unused field in
+    // a constructor with @Prototype receiver.
+  }
+
+  protected void protometh(@Prototype UnusedNullness this) {
+    // It should be legal to initialize the unused field to null in
+    // a method with @Prototype receiver.
+    this.ppt = null;
+  }
+
+  protected void meth() {
+    // Otherwise it's not legal.
+    // :: error: (assignment)
+    this.ppt = null;
+  }
+
+  protected void useUnusedField1(@Prototype UnusedNullness this) {
+    // :: error: (assignment)
+    @NonNull Object x = this.ppt;
+  }
+
+  protected void useUnusedField2() {
+    @NonNull Object x = this.ppt;
+  }
+}
diff --git a/checker/tests/nullness/UnusedOnClass.java b/checker/tests/nullness/UnusedOnClass.java
new file mode 100644
index 0000000..ec2c73c
--- /dev/null
+++ b/checker/tests/nullness/UnusedOnClass.java
@@ -0,0 +1,26 @@
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.Unused;
+
+public final class UnusedOnClass {
+  public static void read_serialized_pptmap2(@MyNonPrototype MyInvariant2 inv) {
+    inv.ppt.toString();
+  }
+}
+
+@MyPrototype
+abstract class MyInvariant2 {
+  @Unused(when = MyPrototype.class)
+  public String ppt = "hello";
+}
+
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@interface MyPrototype {}
+
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(MyPrototype.class)
+@DefaultQualifierInHierarchy
+@interface MyNonPrototype {}
diff --git a/checker/tests/nullness/ValidType.java b/checker/tests/nullness/ValidType.java
new file mode 100644
index 0000000..3b8e766
--- /dev/null
+++ b/checker/tests/nullness/ValidType.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ValidType {
+
+  void t1() {
+    // :: error: (conflicting.annos)
+    @NonNull @Nullable String l1;
+    // :: error: (conflicting.annos)
+    @UnderInitialization @UnknownInitialization String f;
+  }
+}
diff --git a/checker/tests/nullness/VarInfoName.java b/checker/tests/nullness/VarInfoName.java
new file mode 100644
index 0000000..6bb352d
--- /dev/null
+++ b/checker/tests/nullness/VarInfoName.java
@@ -0,0 +1,15 @@
+public abstract class VarInfoName {
+
+  public abstract <T extends Object> T accept(Visitor<T> v);
+
+  public abstract static class Visitor<T extends Object> {}
+
+  public abstract static class BooleanAndVisitor extends Visitor<Boolean> {
+    private boolean result;
+
+    public BooleanAndVisitor(VarInfoName name) {
+      // :: error: (argument) :: warning: (nulltest.redundant)
+      result = (name.accept(this) != null);
+    }
+  }
+}
diff --git a/checker/tests/nullness/VarargsNullness.java b/checker/tests/nullness/VarargsNullness.java
new file mode 100644
index 0000000..0c18f12
--- /dev/null
+++ b/checker/tests/nullness/VarargsNullness.java
@@ -0,0 +1,53 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class VarargsNullness {
+
+  public void test(@NonNull Object @NonNull ... o) {
+    for (@NonNull Object p : o) {
+      System.out.println(p);
+    }
+  }
+
+  public void test2(Object o1, Object o2) {
+    System.out.println(o1);
+    System.out.println(o2);
+  }
+
+  public void testVarargs() {
+    test("foo", "bar", "baz");
+  }
+
+  public void testVarargsNoArgs() {
+    test();
+  }
+
+  public void testNonVarargs() {
+    test2("foo", "bar");
+  }
+
+  public void format1(java.lang.String a1, java.lang.@Nullable Object... a2) {
+    int x = a2.length; // no warning
+    // :: error: (enhancedfor)
+    for (@NonNull Object p : a2) // warning
+    System.out.println(p);
+  }
+
+  public void format2(java.lang.String a1, java.lang.Object @Nullable ... a2) {
+    // :: error: (dereference.of.nullable)
+    int x = a2.length; // warning
+    for (@NonNull Object p : a2) // no warning
+    System.out.println(p);
+  }
+
+  public void testPrintf() {
+    String s = null;
+    printf("%s", s);
+    // tests do not use annotated JDK
+    // System.out.printf ("%s", s);
+  }
+
+  // printf declaration is taken from PrintStream
+  public java.io.PrintStream printf(java.lang.String a1, java.lang.@Nullable Object... a2) {
+    throw new RuntimeException("skeleton method");
+  }
+}
diff --git a/checker/tests/nullness/VoidUse.java b/checker/tests/nullness/VoidUse.java
new file mode 100644
index 0000000..3702e5b
--- /dev/null
+++ b/checker/tests/nullness/VoidUse.java
@@ -0,0 +1,53 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class VoidUse {
+
+  private Class<?> main_class1 = Void.TYPE;
+
+  private Class<? extends @Nullable Object> main_class2 = Void.TYPE;
+
+  public Void voidReturn(Void p) {
+    voidReturn(null);
+    return null;
+  }
+
+  // Void is treated as Nullable.  Is there a value on having it be NonNull?
+  public abstract static class VoidTestNode<T extends Object> {}
+
+  public static class VoidTestInvNode extends VoidTestNode<@NonNull Void> {}
+
+  class Scanner<P extends Object> {
+    public void scan(Object tree, P p) {}
+  }
+
+  // :: error: (type.argument)
+  class MyScanner extends Scanner<Void> {
+    void use(MyScanner ms) {
+      ms.scan(new Object(), null);
+    }
+  }
+
+  // :: error: (type.argument)
+  class MyScanner2 extends Scanner<@Nullable Object> {
+    void use(MyScanner2 ms) {
+      ms.scan(new Object(), null);
+    }
+  }
+
+  // Test case for issue #230
+  Class<? extends @Nullable Object> voidClass() {
+    return void.class;
+  }
+
+  Class<? extends @Nullable Object> VoidClass() {
+    return Void.class;
+  }
+
+  Class<?> intClass() {
+    return int.class;
+  }
+
+  Class<?> ListClass() {
+    return java.util.List.class;
+  }
+}
diff --git a/checker/tests/nullness/WeakHasherMapNonNull.java b/checker/tests/nullness/WeakHasherMapNonNull.java
new file mode 100644
index 0000000..62613a2
--- /dev/null
+++ b/checker/tests/nullness/WeakHasherMapNonNull.java
@@ -0,0 +1,20 @@
+import java.util.AbstractMap;
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.regex.qual.*;
+
+public abstract class WeakHasherMapNonNull<K, V> extends AbstractMap<K, V> implements Map<K, V> {
+  private Map<Object, V> hash = new HashMap<>();
+
+  @org.checkerframework.dataflow.qual.Pure
+  public boolean containsKey(@NonNull Object key) {
+    // :: warning: [unchecked] unchecked cast
+    K kkey = (K) key;
+    // :: error: (argument)
+    hash.containsKey(null);
+    // :: error: (contracts.conditional.postcondition)
+    return true;
+  }
+}
diff --git a/checker/tests/nullness/WeakHasherMapNullable.java b/checker/tests/nullness/WeakHasherMapNullable.java
new file mode 100644
index 0000000..2ff04ae
--- /dev/null
+++ b/checker/tests/nullness/WeakHasherMapNullable.java
@@ -0,0 +1,19 @@
+import java.util.AbstractMap;
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+
+public abstract class WeakHasherMapNullable<K, V> extends AbstractMap<K, V> implements Map<K, V> {
+  private Map<Object, V> hash = new HashMap<>();
+
+  @Pure
+  public boolean containsKey(@Nullable Object key) {
+    // :: warning: [unchecked] unchecked cast
+    K kkey = (K) key;
+    // :: error: (argument)
+    hash.containsKey(null);
+    // :: error: (contracts.conditional.postcondition)
+    return true;
+  }
+}
diff --git a/checker/tests/nullness/WeakIdentityPair.java b/checker/tests/nullness/WeakIdentityPair.java
new file mode 100644
index 0000000..4a16b36
--- /dev/null
+++ b/checker/tests/nullness/WeakIdentityPair.java
@@ -0,0 +1,15 @@
+import java.lang.ref.WeakReference;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class WeakIdentityPair<T1 extends Object> {
+
+  private final WeakReference<T1> a;
+
+  public WeakIdentityPair(T1 a) {
+    this.a = new WeakReference<>(a);
+  }
+
+  public @Nullable T1 getA() {
+    return a.get();
+  }
+}
diff --git a/checker/tests/nullness/WeakRef.java b/checker/tests/nullness/WeakRef.java
new file mode 100644
index 0000000..5b7fcdd
--- /dev/null
+++ b/checker/tests/nullness/WeakRef.java
@@ -0,0 +1,8 @@
+import java.lang.ref.WeakReference;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class WeakRef {
+  @PolyNull Object @Nullable [] foo(WeakReference<@PolyNull Object[]> lookup) {
+    return lookup.get();
+  }
+}
diff --git a/checker/tests/nullness/Wellformed.java b/checker/tests/nullness/Wellformed.java
new file mode 100644
index 0000000..c9467c5
--- /dev/null
+++ b/checker/tests/nullness/Wellformed.java
@@ -0,0 +1,65 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Wellformed {
+  // :: error: (conflicting.annos)
+  @NonNull @Nullable Object f = null;
+
+  // :: error: (conflicting.annos)
+  class Gen1a<T extends @NonNull @Nullable Object> {}
+
+  class Gen1b {
+    // :: error: (conflicting.annos)
+    <T extends @NonNull @Nullable Object> void m(T p) {}
+    // :: error: (conflicting.annos)
+    <@NonNull @Nullable T> void m2(T p) {}
+  }
+  // :: error: (conflicting.annos)
+  class Gen1c<@NonNull @Nullable TTT> {}
+
+  class Gen2a<@Nullable T> {}
+
+  // :: error: (bound)
+  class Gen2b<@Nullable T extends Object> {}
+
+  // :: error: (bound)
+  class Gen2c<@Nullable T extends @NonNull Object> {}
+
+  class Gen3a<T> {
+    @Nullable T f;
+
+    @Nullable T get() {
+      return null;
+    }
+  }
+
+  class Gen3b<T extends @NonNull Object> {
+    @Nullable T f;
+
+    @Nullable T get() {
+      return null;
+    }
+  }
+
+  class Gen4<T extends @Nullable Object> {
+    // :: error: (initialization.field.uninitialized)
+    @NonNull T f;
+
+    @NonNull T get() {
+      throw new RuntimeException();
+    }
+
+    void set(@NonNull T p) {}
+  }
+
+  class Gen5a<T extends @Nullable Object> {}
+
+  class Gen5b<S> extends Gen5a<@Nullable Object> {}
+
+  class Gen5c<S> extends Gen5a<@Nullable S> {}
+
+  class Gen6a<T extends Object> {}
+  // :: error: (type.argument)
+  class Gen6b<S> extends Gen6a<@Nullable Object> {}
+  // :: error: (type.argument)
+  class Gen6c<S> extends Gen6a<@Nullable S> {}
+}
diff --git a/checker/tests/nullness/WhileTest.java b/checker/tests/nullness/WhileTest.java
new file mode 100644
index 0000000..44d1b07
--- /dev/null
+++ b/checker/tests/nullness/WhileTest.java
@@ -0,0 +1,60 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class WhileTest {
+  @Nullable Integer z;
+  @NonNull Integer nnz = Integer.valueOf(22);
+
+  public static void main(String[] args) {
+    new WhileTest().testwhile1();
+  }
+
+  public void testwhile1() {
+    z = null;
+    // :: error: (assignment)
+    nnz = z;
+
+    while (z == null) {
+      break;
+    }
+    // :: error: (assignment)
+    nnz = z;
+    nnz.toString();
+  }
+
+  public void testwhile2() {
+    z = null;
+    while (z == null) {}
+    nnz = z;
+  }
+
+  public void testdo1() {
+    z = null;
+    do {
+      break;
+    } while (z == null);
+    // :: error: (assignment)
+    nnz = z;
+  }
+
+  public void testdo2() {
+    z = null;
+    do {} while (z == null);
+    nnz = z;
+  }
+
+  public void testfor1() {
+    z = null;
+    for (; z == null; ) {
+      break;
+    }
+    // :: error: (assignment)
+    nnz = z;
+  }
+
+  public void testfor2() {
+    z = null;
+    for (; z == null; ) {}
+    nnz = z;
+  }
+}
diff --git a/checker/tests/nullness/Widening.java b/checker/tests/nullness/Widening.java
new file mode 100644
index 0000000..53f51e3
--- /dev/null
+++ b/checker/tests/nullness/Widening.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Widening {
+  @Nullable Integer i;
+
+  void inc(long amt) {}
+
+  void foo() {
+    inc(i == null ? 0 : i);
+  }
+}
diff --git a/checker/tests/nullness/WildcardSubtype.java b/checker/tests/nullness/WildcardSubtype.java
new file mode 100644
index 0000000..fc76e0a
--- /dev/null
+++ b/checker/tests/nullness/WildcardSubtype.java
@@ -0,0 +1,54 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class WildcardSubtype {
+  class MyClass {}
+
+  class Visitor<T> {
+    String visit(T p) {
+      return "";
+    }
+  }
+
+  class MyClassVisitor extends Visitor<@Nullable MyClass> {}
+
+  class NonNullMyClassVisitor extends Visitor<@NonNull MyClass> {}
+
+  void test(MyClassVisitor myClassVisitor, NonNullMyClassVisitor nonNullMyClassVisitor) {
+    // :: error: (argument)
+    take(new Visitor<@Nullable Object>());
+    // :: error: (argument)
+    take(new Visitor<@Nullable Object>());
+
+    Visitor<?> visitor1 = myClassVisitor;
+    Visitor<?> visitor2 = nonNullMyClassVisitor;
+
+    // :: error: (assignment)
+    Visitor<? extends @NonNull Object> visitor3 = myClassVisitor;
+    Visitor<? extends @NonNull Object> visitor4 = nonNullMyClassVisitor;
+
+    // :: error: (assignment)
+    Visitor<? extends @NonNull Object> visitor5 = new MyClassVisitor();
+    // :: error: (assignment)
+    Visitor<? extends @NonNull Object> visitor6 = new MyClassVisitor();
+    // :: error: (argument)
+    take(new MyClassVisitor());
+    // :: error: (argument)
+    take(new MyClassVisitor());
+  }
+
+  void take(Visitor<@NonNull ? extends @NonNull Object> v) {}
+
+  void bar() {
+    // :: error: (argument)
+    take(new Visitor<@Nullable Object>());
+    // :: error: (argument)
+    take(new MyClassVisitor());
+  }
+
+  void baz() {
+    // :: error: (argument)
+    take(new MyClassVisitor());
+    take(new NonNullMyClassVisitor());
+  }
+}
diff --git a/checker/tests/nullness/WildcardSubtype2.java b/checker/tests/nullness/WildcardSubtype2.java
new file mode 100644
index 0000000..c8e2305
--- /dev/null
+++ b/checker/tests/nullness/WildcardSubtype2.java
@@ -0,0 +1,55 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class WildcardSubtype2 {
+  class MyClass {}
+
+  class Visitor<T, S> {
+    String visit(T p) {
+      return "";
+    }
+  }
+
+  class MyClassVisitor extends Visitor<@Nullable MyClass, @Nullable MyClass> {}
+
+  class NonNullMyClassVisitor extends Visitor<@NonNull MyClass, @NonNull MyClass> {}
+
+  void test(MyClassVisitor myClassVisitor, NonNullMyClassVisitor nonNullMyClassVisitor) {
+    // :: error: (argument)
+    take(new Visitor<@Nullable Object, @Nullable Object>());
+    // :: error: (argument)
+    take(new Visitor<@Nullable Object, @Nullable Object>());
+    Visitor<?, ?> visitor1 = myClassVisitor;
+    Visitor<?, ?> visitor2 = nonNullMyClassVisitor;
+
+    // :: error: (assignment)
+    Visitor<? extends @NonNull Object, ? extends @NonNull Object> visitor3 = myClassVisitor;
+    Visitor<? extends @NonNull Object, ? extends @NonNull Object> visitor4 = nonNullMyClassVisitor;
+
+    Visitor<? extends @NonNull Object, ? extends @NonNull Object> visitor5 =
+        // :: error: (assignment)
+        new MyClassVisitor();
+    Visitor<? extends @NonNull Object, ? extends @NonNull Object> visitor6 =
+        // :: error: (assignment)
+        new MyClassVisitor();
+    // :: error: (argument)
+    take(new MyClassVisitor());
+    // :: error: (argument)
+    take(new MyClassVisitor());
+  }
+
+  void take(Visitor<@NonNull ? extends @NonNull Object, @NonNull ? extends @NonNull Object> v) {}
+
+  void bar() {
+    // :: error: (argument)
+    take(new Visitor<@Nullable Object, @Nullable Object>());
+    // :: error: (argument)
+    take(new MyClassVisitor());
+  }
+
+  void baz() {
+    // :: error: (argument)
+    take(new MyClassVisitor());
+    take(new NonNullMyClassVisitor());
+  }
+}
diff --git a/checker/tests/nullness/Wildcards.java b/checker/tests/nullness/Wildcards.java
new file mode 100644
index 0000000..5ee09b8
--- /dev/null
+++ b/checker/tests/nullness/Wildcards.java
@@ -0,0 +1,37 @@
+// Test case for issue #234.
+
+import java.util.Iterator;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Wildcards {
+
+  public static void client1(List<String> strings) {
+    join1(strings.iterator());
+    join2(strings.iterator());
+    join3(strings.iterator());
+    join4(strings.iterator());
+  }
+
+  public static void client2(Iterator<String> itor) {
+    join1(itor);
+    join2(itor);
+    join3(itor);
+    join4(itor);
+  }
+
+  public static void client3(Iterator<String> itor) {
+    Iterator<?> parts1 = itor;
+    Iterator<? extends Object> parts2 = itor;
+    Iterator<? extends @Nullable Object> parts3 = itor;
+    Iterator<? extends @NonNull Object> parts4 = itor;
+  }
+
+  static void join1(Iterator<?> parts) {}
+
+  static void join2(Iterator<? extends Object> parts) {}
+
+  static void join3(Iterator<? extends @Nullable Object> parts) {}
+
+  static void join4(Iterator<? extends @NonNull Object> parts) {}
+}
diff --git a/checker/tests/nullness/flow/Issue1214.java b/checker/tests/nullness/flow/Issue1214.java
new file mode 100644
index 0000000..a74567d
--- /dev/null
+++ b/checker/tests/nullness/flow/Issue1214.java
@@ -0,0 +1,126 @@
+// Test case for issue #1214:
+// https://github.com/typetools/checker-framework/issues/1214
+
+public class Issue1214 {
+  static String ng1() {
+    String s = "not null";
+    try {
+      int data = 50 / 0;
+    } catch (Exception e) {
+      s = null;
+    }
+    // :: error: (return)
+    return s;
+  }
+
+  static String ng2(int x) {
+    String s = "not null";
+    try {
+      short data = (short) (50 / x);
+    } catch (Exception e) {
+      try {
+        s = null;
+      } catch (Exception ee) {
+      }
+    }
+    // :: error: (return)
+    return s;
+  }
+
+  static String ng3() {
+    String s = "not null";
+    try {
+      int data = 50 % 0;
+    } catch (Exception e) {
+      try {
+        // some statements...
+      } catch (Exception ee) {
+      } finally {
+        s = null;
+      }
+    }
+    // :: error: (return)
+    return s;
+  }
+
+  static String ng4(int data) {
+    String s = "not null";
+    try {
+      data /= 0;
+    } catch (Exception e) {
+      s = null;
+    }
+    // :: error: (return)
+    return s;
+  }
+
+  static String ng5(short data) {
+    String s = "not null";
+    try {
+      data /= 0;
+    } catch (Exception e) {
+      try {
+        s = null;
+      } catch (Exception ee) {
+      }
+    }
+    // :: error: (return)
+    return s;
+  }
+
+  static String ng6(int data) {
+    String s = "not null";
+    try {
+      data %= 0;
+    } catch (Exception e) {
+      try {
+        // some statements...
+      } catch (Exception ee) {
+      } finally {
+        s = null;
+      }
+    }
+    // :: error: (return)
+    return s;
+  }
+
+  static String ok1() {
+    String s = "not null";
+    try {
+      double data = 50 / 0.0;
+    } catch (Exception e) {
+      s = null;
+    }
+    return s;
+  }
+
+  static String ok2() {
+    String s = "not null";
+    try {
+      double data = 50 % 0.0;
+    } catch (Exception e) {
+      s = null;
+    }
+    return s;
+  }
+
+  static String ok3(double data) {
+    String s = "not null";
+    try {
+      data /= 0;
+    } catch (Exception e) {
+      s = null;
+    }
+    return s;
+  }
+
+  static String ok4(float data) {
+    String s = "not null";
+    try {
+      data %= 0;
+    } catch (Exception e) {
+      s = null;
+    }
+    return s;
+  }
+}
diff --git a/checker/tests/nullness/flow/Issue1345.java b/checker/tests/nullness/flow/Issue1345.java
new file mode 100644
index 0000000..1dcb154
--- /dev/null
+++ b/checker/tests/nullness/flow/Issue1345.java
@@ -0,0 +1,25 @@
+// Test case for issue #1345:
+// https://github.com/typetools/checker-framework/issues/1345
+
+// @skip-test until the issue is resolved
+
+import java.math.BigDecimal;
+import java.util.stream.Stream;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.util.Opt;
+
+public class Issue1345 {
+
+  @EnsuresNonNullIf(expression = "#1", result = true)
+  static boolean isNonNull(@Nullable Object o) {
+    return o != null;
+  }
+
+  void filterPresent_Optional(Stream<@Nullable BigDecimal> s) {
+    Stream<@NonNull BigDecimal> filtered = s.<BigDecimal>filter(Issue1345::isNonNull);
+  }
+
+  void filterPresent_Opt(@Nullable Object p) {
+    @NonNull Object o = Opt.filter(p, Opt::isPresent);
+  }
+}
diff --git a/checker/tests/nullness/flow/Issue1727.java b/checker/tests/nullness/flow/Issue1727.java
new file mode 100644
index 0000000..8a6dc26
--- /dev/null
+++ b/checker/tests/nullness/flow/Issue1727.java
@@ -0,0 +1,31 @@
+// Test case for Issue 1727:
+// https://github.com/typetools/checker-framework/issues/1727
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class B {}
+
+public class Issue1727 {
+
+  private B foo() {
+    // Default type for local variable b is @UnknownInitialization @Nullable
+    B b;
+
+    while (true) {
+      B op = getB();
+      if (op == null) {
+        b = new B();
+        break;
+      } else {
+        b = op;
+        break;
+      }
+    }
+
+    return b;
+  }
+
+  private @Nullable B getB() {
+    return new B();
+  }
+}
diff --git a/checker/tests/nullness/flow/Issue3249.java b/checker/tests/nullness/flow/Issue3249.java
new file mode 100644
index 0000000..7baf899
--- /dev/null
+++ b/checker/tests/nullness/flow/Issue3249.java
@@ -0,0 +1,64 @@
+// Test case for Issue 3249:
+// https://github.com/typetools/checker-framework/issues/3249
+
+public class Issue3249 {
+
+  private final double field;
+
+  Issue3249() {
+    double local;
+    while (true) {
+      local = 1;
+      break;
+    }
+    field = local;
+  }
+
+  Issue3249(int x) {
+    double local;
+    while (!false) {
+      local = 1;
+      break;
+    }
+    field = local;
+  }
+
+  Issue3249(float x) {
+    double local;
+    while (true || x > 0) {
+      local = 1;
+      break;
+    }
+    field = local;
+  }
+
+  Issue3249(double x) {
+    double local;
+    while (!false && true && !false) {
+      local = 1;
+      break;
+    }
+    field = local;
+  }
+
+  // Case for while conditions that contain final variables,
+  // which are treated as constant.
+  Issue3249(String x) {
+    double local;
+    final int i = 1;
+    while ((i > 0) && !false) {
+      local = 1;
+      break;
+    }
+    field = local;
+  }
+
+  Issue3249(boolean x) {
+    double local;
+    while (6 > 4) {
+      local = 1;
+      break;
+    }
+    field = local;
+  }
+}
diff --git a/checker/tests/nullness/flow/Issue3267.java b/checker/tests/nullness/flow/Issue3267.java
new file mode 100644
index 0000000..fac6ccd
--- /dev/null
+++ b/checker/tests/nullness/flow/Issue3267.java
@@ -0,0 +1,39 @@
+// Test case for issue #3267:
+// https://github.com/typetools/checker-framework/issues/3267
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue3267 {
+  void m1(@Nullable Object obj) {
+    if (true) {
+      // :: error: (dereference.of.nullable)
+      obj.toString();
+    }
+  }
+
+  void m2(@Nullable Object obj) {
+    if (obj != null) {}
+    if (true) {
+      // :: error: (dereference.of.nullable)
+      obj.toString();
+    }
+  }
+
+  void m3(@Nullable Object obj) {
+    if (obj != null) {
+    } else {
+    }
+    if (true) {
+      // :: error: (dereference.of.nullable)
+      obj.toString();
+    }
+  }
+
+  void m4(@Nullable Object obj) {
+    boolean bool = obj != null;
+    if (true) {
+      // :: error: (dereference.of.nullable)
+      obj.toString();
+    }
+  }
+}
diff --git a/checker/tests/nullness/flow/Issue3275.java b/checker/tests/nullness/flow/Issue3275.java
new file mode 100644
index 0000000..86b7c2e
--- /dev/null
+++ b/checker/tests/nullness/flow/Issue3275.java
@@ -0,0 +1,206 @@
+// Test case for issue #3275:
+// https://github.com/typetools/checker-framework/issues/3275
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue3275 {
+  public @NonNull Object f = new Object();
+  public boolean b = false;
+
+  void return_n(@Nullable Object obj) {
+    if (obj != null) {
+      obj.toString();
+    }
+  }
+
+  void return_np(@Nullable Object obj) {
+    if ((obj != null)) {
+      obj.toString();
+    }
+  }
+
+  void return_en(@Nullable Object obj) {
+    if (!(obj == null)) {
+      obj.toString();
+    }
+  }
+
+  void return_eet(@Nullable Object obj) {
+    if ((obj == null) == true) {
+      // :: error: (dereference.of.nullable)
+      obj.toString();
+    }
+  }
+
+  void return_eef(@Nullable Object obj) {
+    if ((obj == null) == false) {
+      obj.toString();
+    }
+  }
+
+  void return_eeb(@Nullable Object obj) {
+    if ((obj == null) == b) {
+      // :: error: (dereference.of.nullable)
+      obj.toString();
+    }
+  }
+
+  void return_ent(@Nullable Object obj) {
+    if ((obj == null) != true) {
+      obj.toString();
+    }
+  }
+
+  void return_enf(@Nullable Object obj) {
+    if ((obj == null) != false) {
+      // :: error: (dereference.of.nullable)
+      obj.toString();
+    }
+  }
+
+  void return_enb(@Nullable Object obj) {
+    if ((obj == null) != b) {
+      // :: error: (dereference.of.nullable)
+      obj.toString();
+    }
+  }
+
+  void return_net(@Nullable Object obj) {
+    if ((obj != null) == true) {
+      obj.toString();
+    }
+  }
+
+  void return_nef(@Nullable Object obj) {
+    if ((obj != null) == false) {
+      // :: error: (dereference.of.nullable)
+      obj.toString();
+    }
+  }
+
+  void return_neb(@Nullable Object obj) {
+    if ((obj != null) == b) {
+      // :: error: (dereference.of.nullable)
+      obj.toString();
+    }
+  }
+
+  void return_nnt(@Nullable Object obj) {
+    if ((obj != null) != true) {
+      // :: error: (dereference.of.nullable)
+      obj.toString();
+    }
+  }
+
+  void return_nnf(@Nullable Object obj) {
+    if ((obj != null) != false) {
+      obj.toString();
+    }
+  }
+
+  void return_nnb(@Nullable Object obj) {
+    if ((obj != null) != b) {
+      // :: error: (dereference.of.nullable)
+      obj.toString();
+    }
+  }
+
+  void assign_n(@Nullable Object obj) {
+    if (obj != null) {
+      f = obj;
+    }
+  }
+
+  void assign_np(@Nullable Object obj) {
+    if ((obj != null)) {
+      f = obj;
+    }
+  }
+
+  void assign_en(@Nullable Object obj) {
+    if (!(obj == null)) {
+      f = obj;
+    }
+  }
+
+  void assign_eet(@Nullable Object obj) {
+    if ((obj == null) == true) {
+      // :: error: (assignment)
+      f = obj;
+    }
+  }
+
+  void assign_eef(@Nullable Object obj) {
+    if ((obj == null) == false) {
+      f = obj;
+    }
+  }
+
+  void assign_eeb(@Nullable Object obj) {
+    if ((obj == null) == b) {
+      // :: error: (assignment)
+      f = obj;
+    }
+  }
+
+  void assign_ent(@Nullable Object obj) {
+    if ((obj == null) != true) {
+      f = obj;
+    }
+  }
+
+  void assign_enf(@Nullable Object obj) {
+    if ((obj == null) != false) {
+      // :: error: (assignment)
+      f = obj;
+    }
+  }
+
+  void assign_enb(@Nullable Object obj) {
+    if ((obj == null) != b) {
+      // :: error: (assignment)
+      f = obj;
+    }
+  }
+
+  void assign_net(@Nullable Object obj) {
+    if ((obj != null) == true) {
+      f = obj;
+    }
+  }
+
+  void assign_nef(@Nullable Object obj) {
+    if ((obj != null) == false) {
+      // :: error: (assignment)
+      f = obj;
+    }
+  }
+
+  void assign_neb(@Nullable Object obj) {
+    if ((obj != null) == b) {
+      // :: error: (assignment)
+      f = obj;
+    }
+  }
+
+  void assign_nnt(@Nullable Object obj) {
+    if ((obj != null) != true) {
+      // :: error: (assignment)
+      f = obj;
+    }
+  }
+
+  void assign_nnf(@Nullable Object obj) {
+    if ((obj != null) != false) {
+      f = obj;
+    }
+  }
+
+  void assign_nnb(@Nullable Object obj) {
+    if ((obj != null) != b) {
+      // :: error: (assignment)
+      f = obj;
+    }
+  }
+}
diff --git a/checker/tests/nullness/flow/Issue341.java b/checker/tests/nullness/flow/Issue341.java
new file mode 100644
index 0000000..ce96a4d
--- /dev/null
+++ b/checker/tests/nullness/flow/Issue341.java
@@ -0,0 +1,18 @@
+// Test case for issue #341:
+// https://github.com/typetools/checker-framework/issues/341
+
+public class Issue341 {
+
+  static class Provider {
+    public final Object get = new Object();
+  }
+
+  Object execute(Provider p) {
+    final Object result;
+    try {
+      result = p.get;
+    } finally {
+    }
+    return result;
+  }
+}
diff --git a/checker/tests/nullness/flow/Issue818.java b/checker/tests/nullness/flow/Issue818.java
new file mode 100644
index 0000000..d0ac6e4
--- /dev/null
+++ b/checker/tests/nullness/flow/Issue818.java
@@ -0,0 +1,72 @@
+// Test case for Issue #818
+// https://github.com/typetools/checker-framework/issues/818
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue818 {
+  public static @Nullable Object o = null;
+
+  void method() {
+    Issue818.o = new Object();
+    o.toString();
+  }
+
+  void method2() {
+    o = new Object();
+    Issue818.o.toString();
+  }
+
+  void method3() {
+    o = new Object();
+    o.toString();
+  }
+
+  void method4() {
+    Issue818.o = new Object();
+    Issue818.o.toString();
+  }
+
+  static class StaticInnerClass {
+    void method() {
+      Issue818.o = new Object();
+      o.toString();
+    }
+
+    void method2() {
+      o = new Object();
+      Issue818.o.toString();
+    }
+
+    void method3() {
+      o = new Object();
+      o.toString();
+    }
+
+    void method4() {
+      Issue818.o = new Object();
+      Issue818.o.toString();
+    }
+  }
+
+  class NonStaticInnerClass {
+    void method() {
+      Issue818.o = new Object();
+      o.toString();
+    }
+
+    void method2() {
+      o = new Object();
+      Issue818.o.toString();
+    }
+
+    void method3() {
+      o = new Object();
+      o.toString();
+    }
+
+    void method4() {
+      Issue818.o = new Object();
+      Issue818.o.toString();
+    }
+  }
+}
diff --git a/checker/tests/nullness/flow/MapGet.java b/checker/tests/nullness/flow/MapGet.java
new file mode 100644
index 0000000..f66def0
--- /dev/null
+++ b/checker/tests/nullness/flow/MapGet.java
@@ -0,0 +1,28 @@
+// Test case for issue #372:
+// https://github.com/typetools/checker-framework/issues/372
+
+// @skip-test until the issue is fixed
+
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class MapGet {
+  private final Map<String, String> labels = new HashMap<>();
+
+  void foo1(String v) {
+    labels.put(v, "");
+    labels.get(v).toString();
+  }
+
+  @NonNull String foo2(String v) {
+    labels.put(v, "");
+    return labels.get(v);
+  }
+
+  @EnsuresNonNull("labels.get(#1)")
+  void foo3(String v) {
+    labels.put(v, "");
+  }
+}
diff --git a/checker/tests/nullness/flow/PathJoins.java b/checker/tests/nullness/flow/PathJoins.java
new file mode 100644
index 0000000..7e8c88a
--- /dev/null
+++ b/checker/tests/nullness/flow/PathJoins.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class PathJoins {
+
+  public void testJoiningMultipleBranches() {
+    Object intersect = null;
+    if (false) {
+      return;
+    } else if (intersect == null) {
+      return;
+    } else {
+      intersect = "m";
+    }
+
+    intersect.toString();
+  }
+}
diff --git a/checker/tests/nullness/flow/PureAndFlow.java b/checker/tests/nullness/flow/PureAndFlow.java
new file mode 100644
index 0000000..6e06a2f
--- /dev/null
+++ b/checker/tests/nullness/flow/PureAndFlow.java
@@ -0,0 +1,60 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public abstract class PureAndFlow {
+
+  @Nullable String s1;
+  @Nullable String s2;
+
+  void nonpure(String s1) {}
+
+  @org.checkerframework.dataflow.qual.Pure
+  // :: warning: (purity.deterministic.void.method)
+  void pure(String s2) {}
+
+  @org.checkerframework.dataflow.qual.Deterministic
+  // :: warning: (purity.deterministic.void.method)
+  void det(String s3) {}
+
+  @org.checkerframework.dataflow.qual.Pure
+  // :: warning: (purity.deterministic.void.method)
+  abstract void abstractpure(String s4);
+
+  @org.checkerframework.dataflow.qual.Deterministic
+  // :: warning: (purity.deterministic.void.method)
+  abstract void abstractdet(String s4);
+
+  void withNonRow() {
+    if (s2 != null) {
+      nonpure("m");
+      // :: error: (argument)
+      pure(s2);
+    }
+  }
+
+  void withPure() {
+    if (s2 != null) {
+      pure("m");
+      pure(s2);
+    }
+  }
+
+  interface IFace {
+    @org.checkerframework.dataflow.qual.Pure
+    // :: warning: (purity.deterministic.void.method)
+    void ifacepure(String s);
+
+    @org.checkerframework.dataflow.qual.Deterministic
+    // :: warning: (purity.deterministic.void.method)
+    void ifacedet(String s);
+  }
+
+  class Cons {
+    @org.checkerframework.dataflow.qual.Pure
+    // :: warning: (purity.deterministic.constructor)
+    Cons(String s) {}
+
+    @org.checkerframework.dataflow.qual.Deterministic
+    // :: warning: (purity.deterministic.constructor)
+    Cons(int i) {}
+  }
+}
diff --git a/checker/tests/nullness/flow/PurityError.java b/checker/tests/nullness/flow/PurityError.java
new file mode 100644
index 0000000..9e52cdc
--- /dev/null
+++ b/checker/tests/nullness/flow/PurityError.java
@@ -0,0 +1,14 @@
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+
+public class PurityError {
+  @SideEffectFree
+  void method() {}
+
+  @Pure
+  Object method2() {
+    // :: error: (purity.not.deterministic.call)
+    method();
+    return "";
+  }
+}
diff --git a/checker/tests/nullness/flow/TestNullnessUtil.java b/checker/tests/nullness/flow/TestNullnessUtil.java
new file mode 100644
index 0000000..06539dd
--- /dev/null
+++ b/checker/tests/nullness/flow/TestNullnessUtil.java
@@ -0,0 +1,89 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.util.NullnessUtil;
+
+/** Test class org.checkerframework.checker.nullness.util.NullnessUtil. */
+public class TestNullnessUtil {
+  void testRef1(@Nullable Object o) {
+    // one way to use as a cast:
+    @NonNull Object l1 = NullnessUtil.castNonNull(o);
+  }
+
+  void testRef2(@Nullable Object o) {
+    // another way to use as a cast:
+    NullnessUtil.castNonNull(o).toString();
+  }
+
+  void testRef3(@Nullable Object o) {
+    // use as statement:
+    NullnessUtil.castNonNull(o);
+    o.toString();
+  }
+
+  void testArr1(@Nullable Object @NonNull [] a) {
+    // one way to use as a cast:
+    @NonNull Object[] l2 = NullnessUtil.castNonNullDeep(a);
+    // Careful, the non-deep version only casts the main modifier.
+    // :: error: (assignment)
+    @NonNull Object[] l2b = NullnessUtil.castNonNull(a);
+    // OK
+    @Nullable Object[] l2c = NullnessUtil.castNonNull(a);
+  }
+
+  void testArr1b(@Nullable Object @Nullable [] a) {
+    // one way to use as a cast:
+    @NonNull Object[] l2 = NullnessUtil.castNonNullDeep(a);
+    // Careful, the non-deep version only casts the main modifier.
+    // :: error: (assignment)
+    @NonNull Object[] l2b = NullnessUtil.castNonNull(a);
+    // OK
+    @Nullable Object[] l2c = NullnessUtil.castNonNull(a);
+  }
+
+  void testArr2(@Nullable Object @NonNull [] a) {
+    // another way to use as a cast:
+    NullnessUtil.castNonNullDeep(a)[0].toString();
+  }
+
+  void testArr3(@Nullable Object @NonNull [] a) {
+    // use as statement:
+    NullnessUtil.castNonNullDeep(a);
+    a.toString();
+    // TODO: @EnsuresNonNull cannot express that
+    // all the array components are non-null.
+    // a[0].toString();
+  }
+
+  /*
+  // TODO: flow does not propagate component types.
+  void testArr3(@Nullable Object @NonNull [] a) {
+      // one way to use as a statement:
+      NullnessUtil.castNonNull(a);
+      a[0].toString();
+  }
+  */
+
+  void testMultiArr1(@Nullable Object @NonNull [] @Nullable [] a) {
+    // :: error: (assignment) :: error: (accessing.nullable)
+    @NonNull Object l3 = a[0][0];
+    // one way to use as a cast:
+    @NonNull Object[][] l4 = NullnessUtil.castNonNullDeep(a);
+  }
+
+  void testMultiArr2(@Nullable Object @NonNull [] @Nullable [] a) {
+    // another way to use as a cast:
+    NullnessUtil.castNonNullDeep(a)[0][0].toString();
+  }
+
+  void testMultiArr3(@Nullable Object @Nullable [] @Nullable [] @Nullable [] a) {
+    // :: error: (dereference.of.nullable) :: error: (accessing.nullable)
+    a[0][0][0].toString();
+    // another way to use as a cast:
+    NullnessUtil.castNonNullDeep(a)[0][0][0].toString();
+  }
+
+  public static void main(String[] args) {
+    Object[] @Nullable [] err = new Object[10][10];
+    Object[][] e1 = NullnessUtil.castNonNullDeep(err);
+    e1[0][0].toString();
+  }
+}
diff --git a/checker/tests/nullness/flow/TestOpt.java b/checker/tests/nullness/flow/TestOpt.java
new file mode 100644
index 0000000..c81fe32
--- /dev/null
+++ b/checker/tests/nullness/flow/TestOpt.java
@@ -0,0 +1,75 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.util.Opt;
+
+/** Test class org.checkerframework.checker.nullness.util.Opt. */
+public class TestOpt {
+  void foo1(@Nullable Object p) {
+    if (Opt.isPresent(p)) {
+      p.toString(); // Flow refinement
+    }
+  }
+
+  void foo1b(@Nullable Object p) {
+    if (!Opt.isPresent(p)) {
+      // :: error: (dereference.of.nullable)
+      p.toString();
+    }
+  }
+
+  void foo2(@Nullable Object p) {
+    Opt.ifPresent(p, x -> System.out.println("Got: " + x));
+  }
+
+  void foo2b(@Nullable Object p) {
+    Opt.ifPresent(p, x -> System.out.println("Got: " + x.toString()));
+  }
+
+  void foo3(@Nullable Object p) {
+    Object o = Opt.filter(p, x -> x.hashCode() > 10);
+  }
+
+  void foo4(@Nullable Object p) {
+    String s = Opt.map(p, x -> x.toString());
+  }
+
+  void foo4b(@Nullable Object p) {
+    // :: error: (argument)
+    String s = Opt.map(p, null);
+  }
+
+  void foo5(@Nullable Object p) {
+    @NonNull Object o = Opt.orElse(p, new Object());
+  }
+
+  void foo5b(@Nullable Object p) {
+    // :: error: (argument)
+    @NonNull Object o = Opt.orElse(p, null);
+  }
+
+  void foo6(@Nullable Object p) {
+    @NonNull Object o = Opt.orElseGet(p, () -> new Object());
+  }
+
+  void foo6b(@Nullable Object p) {
+    // :: error: (return)
+    @NonNull Object o = Opt.orElseGet(p, () -> null);
+  }
+
+  void foo7(Object p) {
+    try {
+      @NonNull Object o = Opt.orElseThrow(p, () -> new Throwable());
+    } catch (Throwable t) {
+      // p was null
+    }
+  }
+
+  void foo7b(@Nullable Object p) {
+    try {
+      // :: error: (assignment) :: error: (type.argument)
+      // :: error: (return)
+      @NonNull Object o = Opt.orElseThrow(p, () -> null);
+    } catch (Throwable t) {
+      // p was null
+    }
+  }
+}
diff --git a/checker/tests/nullness/generics/AnnotatedGenerics.java b/checker/tests/nullness/generics/AnnotatedGenerics.java
new file mode 100644
index 0000000..f7a7784
--- /dev/null
+++ b/checker/tests/nullness/generics/AnnotatedGenerics.java
@@ -0,0 +1,85 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.dataflow.qual.*;
+
+public class AnnotatedGenerics {
+  public static void testNullableTypeVariable() {
+    class Test<T extends @Nullable Object> {
+      // :: error: (initialization.field.uninitialized)
+      T f;
+
+      @Nullable T get() {
+        return f;
+      }
+    }
+    Test<Iterable<String>> l = new Test<>();
+    // :: error: (iterating.over.nullable)
+    for (String s : l.get()) {}
+  }
+
+  public static void testNonNullTypeVariable() {
+    class Test<T extends @Nullable Object> {
+      @NonNull T get() {
+        throw new RuntimeException();
+      }
+    }
+    Test<@Nullable Iterable<String>> l = new Test<>();
+    for (String s : l.get()) {}
+    Test<Iterable<String>> n = new Test<>();
+    for (String s : n.get()) {}
+  }
+
+  static class MyClass<T> implements MyIterator<@Nullable T> {
+    public boolean hasNext() {
+      return true;
+    }
+
+    public @Nullable T next() {
+      return null;
+    }
+
+    public void remove() {}
+
+    static void test() {
+      MyClass<String> c = new MyClass<>();
+      String c1 = c.next();
+      @Nullable String c2 = c.next();
+      // :: error: (assignment)
+      @NonNull String c3 = c.next();
+    }
+  }
+
+  public static final class MyComprator<T extends MyComparable<T>> {
+    public void compare(T a1, T a2) {
+      a1.compareTo(a2);
+    }
+
+    public void compare2(@NonNull T a1, @NonNull T a2) {
+      a1.compareTo(a2);
+    }
+
+    public void compare3(T a1, @Nullable T a2) {
+      // :: error: (argument)
+      a1.compareTo(a2);
+    }
+  }
+
+  class MyComparable<T> {
+    @Pure
+    public int compareTo(@NonNull T a1) {
+      return 0;
+    }
+  }
+
+  <T> T test(java.util.List<? super Iterable<?>> l) {
+    test(new java.util.ArrayList<Object>());
+    throw new Error();
+  }
+
+  public interface MyIterator<E extends @Nullable Object> {
+    boolean hasNext();
+
+    E next();
+
+    void remove();
+  }
+}
diff --git a/checker/tests/nullness/generics/AnnotatedGenerics2.java b/checker/tests/nullness/generics/AnnotatedGenerics2.java
new file mode 100644
index 0000000..cc42d56
--- /dev/null
+++ b/checker/tests/nullness/generics/AnnotatedGenerics2.java
@@ -0,0 +1,135 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class AnnotatedGenerics2 {
+  // Top-level class to ensure that both classes are processed.
+
+  class AnnotatedGenerics2Nble<T extends @Nullable Object> {
+    // :: error: (initialization.field.uninitialized)
+    @NonNull T myFieldNN;
+    @Nullable T myFieldNble;
+    // :: error: (initialization.field.uninitialized)
+    T myFieldT;
+
+    /* TODO: This test case gets affected by flow inference.
+      * Investigate what the desired behavior is later.
+     void fields() {
+         myFieldNN = myFieldNN;
+         myFieldNble = myFieldNN;
+         myFieldT = myFieldNN;
+
+         // TODO:: error: (assignment)
+         myFieldNN = myFieldNble;
+         myFieldNble = myFieldNble;
+         // TODO:: error: (assignment)
+         myFieldT = myFieldNble;
+
+         // TODO:: error: (assignment)
+         myFieldNN = myFieldT;
+         myFieldNble = myFieldT;
+         myFieldT = myFieldT;
+     }
+    */
+
+    void fields1() {
+      myFieldNN = myFieldNN;
+      myFieldNble = myFieldNN;
+      myFieldT = myFieldNN;
+    }
+
+    void fields2() {
+      // :: error: (assignment)
+      myFieldNN = myFieldNble;
+      myFieldNble = myFieldNble;
+      // :: error: (assignment)
+      myFieldT = myFieldNble;
+    }
+
+    void fields3() {
+      // :: error: (assignment)
+      myFieldNN = myFieldT;
+      myFieldNble = myFieldT;
+      myFieldT = myFieldT;
+    }
+
+    void params(@NonNull T myParamNN, @Nullable T myParamNble, T myParamT) {
+      myFieldNN = myParamNN;
+      myFieldNble = myParamNN;
+      myFieldT = myParamNN;
+
+      // :: error: (assignment)
+      myFieldNN = myParamNble;
+      myFieldNble = myParamNble;
+      // :: error: (assignment)
+      myFieldT = myParamNble;
+
+      // :: error: (assignment)
+      myFieldNN = myParamT;
+      myFieldNble = myParamT;
+      myFieldT = myParamT;
+    }
+  }
+
+  class AnnotatedGenerics2NN<T extends @NonNull Object> {
+    // :: error: (initialization.field.uninitialized)
+    @NonNull T myFieldNN;
+    @Nullable T myFieldNble;
+    // :: error: (initialization.field.uninitialized)
+    T myFieldT;
+
+    /* TODO: This test case gets affected by flow inference.
+     * Investigate what the desired behavior is later.
+    void fields() {
+        myFieldNN = myFieldNN;
+        myFieldNble = myFieldNN;
+        myFieldT = myFieldNN;
+
+        // TODO:: error: (assignment)
+        myFieldNN = myFieldNble;
+        myFieldNble = myFieldNble;
+        // TODO:: error: (assignment)
+        myFieldT = myFieldNble;
+
+        // TODO:: error: (assignment)
+        myFieldNN = myFieldT;
+        myFieldNble = myFieldT;
+        myFieldT = myFieldT;
+    }
+    */
+
+    void fields1() {
+      myFieldNN = myFieldNN;
+      myFieldNble = myFieldNN;
+      myFieldT = myFieldNN;
+    }
+
+    void fields2() {
+      // :: error: (assignment)
+      myFieldNN = myFieldNble;
+      myFieldNble = myFieldNble;
+      // :: error: (assignment)
+      myFieldT = myFieldNble;
+    }
+
+    void fields3() {
+      myFieldNN = myFieldT;
+      myFieldNble = myFieldT;
+      myFieldT = myFieldT;
+    }
+
+    void params(@NonNull T myParamNN, @Nullable T myParamNble, T myParamT) {
+      myFieldNN = myParamNN;
+      myFieldNble = myParamNN;
+      myFieldT = myParamNN;
+
+      // :: error: (assignment)
+      myFieldNN = myParamNble;
+      myFieldNble = myParamNble;
+      // :: error: (assignment)
+      myFieldT = myParamNble;
+
+      myFieldNN = myParamT;
+      myFieldNble = myParamT;
+      myFieldT = myParamT;
+    }
+  }
+}
diff --git a/checker/tests/nullness/generics/AnnotatedGenerics3.java b/checker/tests/nullness/generics/AnnotatedGenerics3.java
new file mode 100644
index 0000000..3abb3e9
--- /dev/null
+++ b/checker/tests/nullness/generics/AnnotatedGenerics3.java
@@ -0,0 +1,42 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class AnnotatedGenerics3 {
+  class Cell<T extends @Nullable Object> {
+    T f;
+
+    Cell(T i) {
+      f = i;
+    }
+
+    void setNull(Cell<@Nullable T> p) {
+      p.f = null;
+    }
+
+    void indirect(Cell<T> p) {
+      // :: error: (argument)
+      setNull(p);
+    }
+
+    void setField(@Nullable T p) {
+      // :: error: (assignment)
+      this.f = p;
+    }
+  }
+
+  void run() {
+    Cell<@NonNull Object> c = new Cell<>(new Object());
+    // :: error: (argument)
+    c.setNull(c);
+    c.f.hashCode();
+
+    c.indirect(c);
+    c.f.hashCode();
+
+    c.setField(null);
+    c.f.hashCode();
+  }
+
+  public static void main(String[] args) {
+    new AnnotatedGenerics3().run();
+  }
+}
diff --git a/checker/tests/nullness/generics/AnnotatedTypeParams.java b/checker/tests/nullness/generics/AnnotatedTypeParams.java
new file mode 100644
index 0000000..bb05de2
--- /dev/null
+++ b/checker/tests/nullness/generics/AnnotatedTypeParams.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+class MyClass<@Nullable T> {
+  T get() {
+    throw new RuntimeException();
+  }
+
+  void testPositive() {
+    MyClass<@Nullable String> l = new MyClass<>();
+    // :: error: (dereference.of.nullable)
+    l.get().toString();
+  }
+
+  void testInvalidParam() {
+    // :: error: (type.argument)
+    MyClass<@NonNull String> l;
+  }
+}
diff --git a/checker/tests/nullness/generics/AnnotatedTypeParams2.java b/checker/tests/nullness/generics/AnnotatedTypeParams2.java
new file mode 100644
index 0000000..4f42b09
--- /dev/null
+++ b/checker/tests/nullness/generics/AnnotatedTypeParams2.java
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+class SomeClass<@Nullable T> {
+  T get() {
+    throw new RuntimeException();
+  }
+}
+
+public class AnnotatedTypeParams2 {
+
+  void testPositive() {
+    SomeClass<@Nullable String> l = new SomeClass<>();
+    // :: error: (dereference.of.nullable)
+    l.get().toString();
+  }
+
+  void testInvalidParam() {
+    // :: error: (type.argument)
+    SomeClass<@NonNull String> l;
+  }
+}
diff --git a/checker/tests/nullness/generics/AnnotatedTypeParams4.java b/checker/tests/nullness/generics/AnnotatedTypeParams4.java
new file mode 100644
index 0000000..499c0ff
--- /dev/null
+++ b/checker/tests/nullness/generics/AnnotatedTypeParams4.java
@@ -0,0 +1,75 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class AnnotatedTypeParams4 {
+
+  class Test1<CONTENT extends @Nullable Object> {
+    CONTENT a;
+    // To prevent the warning about un-initialized fields.
+    Test1(CONTENT p1) {
+      a = p1;
+    }
+
+    public CONTENT get() {
+      return a;
+    }
+
+    @org.checkerframework.dataflow.qual.Pure
+    public CONTENT get2() {
+      return a;
+    }
+  }
+
+  class Test2<CONTENT extends @Nullable Object> {
+    @NonNull CONTENT a;
+    // To prevent the warning about un-initialized fields.
+    Test2(@NonNull CONTENT p1) {
+      a = p1;
+    }
+
+    public @NonNull CONTENT get() {
+      return a;
+    }
+
+    @org.checkerframework.dataflow.qual.Pure
+    public @NonNull CONTENT get2() {
+      return a;
+    }
+  }
+
+  /*
+  class Test3<CONTENT extend @Nullable Object> {
+      // Change @Pure to be allowed on fields, or add some other anno.
+      @Pure CONTENT f;
+      // Strangely this assignment succeeded
+      Test3(CONTENT p1) { f = p1; }
+      // But this assignment failed, because the @Pure caused the
+      // other annotations to be erased.
+      public void get3(CONTENT p) {
+          f = p;
+      }
+  }
+  */
+
+  class Test4<CONTENT extends @Nullable Object> {
+    private MyPair<CONTENT, CONTENT> userObject;
+
+    Test4(MyPair<CONTENT, CONTENT> p) {
+      userObject = p;
+    }
+
+    @org.checkerframework.dataflow.qual.Pure
+    public CONTENT getUserLeft() {
+      return userObject.a;
+    }
+
+    public class MyPair<T1 extends @Nullable Object, T2 extends @Nullable Object> {
+      public T1 a;
+      public T2 b;
+
+      public MyPair(T1 a, T2 b) {
+        this.a = a;
+        this.b = b;
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/generics/AnonymousClass.java b/checker/tests/nullness/generics/AnonymousClass.java
new file mode 100644
index 0000000..8349d77
--- /dev/null
+++ b/checker/tests/nullness/generics/AnonymousClass.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class AnonymousClass {
+
+  class Bound<X extends @NonNull Object> {}
+
+  void test() {
+    // :: error: (type.argument)
+    new Bound<@Nullable String>() {};
+  }
+
+  // The dummy parameter tests ParamApplier
+  void test(Object dummy) {
+    // :: error: (type.argument)
+    new Bound<@Nullable String>() {};
+  }
+}
diff --git a/checker/tests/nullness/generics/BoundedWildcardTest.java b/checker/tests/nullness/generics/BoundedWildcardTest.java
new file mode 100644
index 0000000..7ecbc8a
--- /dev/null
+++ b/checker/tests/nullness/generics/BoundedWildcardTest.java
@@ -0,0 +1,44 @@
+// Test case from
+// http://stackoverflow.com/questions/38339332/in-a-bounded-wildcard-where-does-the-annotation-belong
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class Styleable {}
+
+public class BoundedWildcardTest {
+
+  private void locChildren(Styleable c) {
+    // ...
+  }
+
+  public void initLoc(List<? extends Styleable> s) {
+    for (Styleable c : s) {
+      locChildren(c);
+    }
+  }
+
+  // :: error: (bound)
+  public void initLoc1(@Nullable List<@Nullable ? extends Styleable> s) {
+    // :: error: (iterating.over.nullable)
+    for (Styleable c : s) {
+      locChildren(c);
+    }
+  }
+
+  public void initLoc2(@Nullable List<@Nullable ? extends @Nullable Styleable> s) {
+    // :: error: (iterating.over.nullable)
+    for (Styleable c : s) {
+      // :: error: argument
+      locChildren(c);
+    }
+  }
+
+  public void initLoc3(@Nullable List<? extends @Nullable Styleable> s) {
+    // :: error: (iterating.over.nullable)
+    for (Styleable c : s) {
+      // :: error: argument
+      locChildren(c);
+    }
+  }
+}
diff --git a/checker/tests/nullness/generics/BoxingGenerics.java b/checker/tests/nullness/generics/BoxingGenerics.java
new file mode 100644
index 0000000..a97441a
--- /dev/null
+++ b/checker/tests/nullness/generics/BoxingGenerics.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class BoxingGenerics {
+  static class X<T> {
+    public static <T> X<T> foo(T x) {
+      return new X<>();
+    }
+
+    public void bar(X<T> x) {}
+  }
+
+  public void getText() {
+    X<Integer> var = new X<>();
+    X.foo(Integer.valueOf(5)).bar(var);
+    X.foo(5).bar(var);
+  }
+}
diff --git a/checker/tests/nullness/generics/CollectionsAnnotations.java b/checker/tests/nullness/generics/CollectionsAnnotations.java
new file mode 100644
index 0000000..3c8754b
--- /dev/null
+++ b/checker/tests/nullness/generics/CollectionsAnnotations.java
@@ -0,0 +1,68 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// This is how I propose the Collection interface be annotated:
+interface Collection1<E extends @Nullable Object> {
+  public void add(E elt);
+}
+
+class PriorityQueue1<E extends @NonNull Object> implements Collection1<E> {
+  public void add(E elt) {
+    // just to dereference elt
+    elt.hashCode();
+  }
+}
+
+class PriorityQueue2<E extends @NonNull Object> implements Collection1<E> {
+  public void add(E elt) {
+    // just to dereference elt
+    elt.hashCode();
+  }
+}
+
+// This is how the Collection interface is currently annotated
+interface Collection2<E extends @NonNull Object> {
+  public void add(E elt);
+}
+
+class PriorityQueue3<E extends @NonNull Object> implements Collection2<E> {
+  public void add(E elt) {
+    // just to dereference elt
+    elt.hashCode();
+  }
+}
+
+class Methods {
+  static void addNull1(Collection1 l) {
+    // Allowed, because upper bound of Collection1 is Nullable.
+    // :: warning: [unchecked] unchecked call to add(E) as a member of the raw type Collection1
+    l.add(null);
+  }
+
+  static void bad1() {
+    addNull1(new PriorityQueue1());
+  }
+
+  // If the types are parameterized (as they should be)
+  static <@Nullable E extends @Nullable Object> void addNull2(Collection1<E> l) {
+    l.add(null);
+  }
+
+  static void bad2() {
+    // :: error: (type.argument)
+    addNull2(new PriorityQueue1<@NonNull Object>());
+  }
+
+  public static void main(String[] args) {
+    bad2();
+  }
+
+  static void bad3() {
+    // :: error: (type.argument)
+    addNull2(new PriorityQueue2<@NonNull Object>());
+  }
+
+  // :: error: (type.argument)
+  static <@Nullable E> void addNull3(Collection2<E> l) {
+    l.add(null);
+  }
+}
diff --git a/checker/tests/nullness/generics/CollectionsAnnotationsMin.java b/checker/tests/nullness/generics/CollectionsAnnotationsMin.java
new file mode 100644
index 0000000..5508af0
--- /dev/null
+++ b/checker/tests/nullness/generics/CollectionsAnnotationsMin.java
@@ -0,0 +1,58 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class CollectionsAnnotationsMin {
+  static class Collection1<E extends @Nullable Object> {
+    public void add(E elt) {
+      // :: error: (dereference.of.nullable)
+      elt.hashCode();
+    }
+  }
+
+  static class PriorityQueue1<E extends @NonNull Object> extends Collection1<E> {
+    public void add(E elt) {
+      // dereference allowed here
+      elt.hashCode();
+    }
+  }
+
+  // This is allowed, as "null" cannot be added to f1
+  static Collection1<? extends @Nullable Object> f1 = new PriorityQueue1<@NonNull Object>();
+
+  // :: error: (assignment)
+  static Collection1<@Nullable Object> f2 = new PriorityQueue1<@NonNull Object>();
+
+  static void addNull1(Collection1<@Nullable Object> l) {
+    l.add(null);
+  }
+
+  // The upper bound on E is implicitly from Collection1
+  static <E extends @Nullable Object> void addNull2(Collection1<E> l) {
+    // :: error: (argument)
+    l.add(null);
+  }
+
+  // The upper bound on E is implicitly from Collection1
+  static <E extends @Nullable Object> E addNull2b(Collection1<E> l, E p) {
+    // :: error: (argument)
+    l.add(null);
+    return p;
+  }
+
+  static <@Nullable E extends @Nullable Object> void addNull3(Collection1<E> l) {
+    l.add(null);
+  }
+
+  static void bad() {
+    // :: error: (argument)
+    addNull1(new PriorityQueue1<@NonNull Object>());
+
+    addNull2(new PriorityQueue1<@NonNull Object>());
+    addNull2b(new PriorityQueue1<@NonNull Object>(), new Object());
+
+    // :: error: (type.argument)
+    addNull3(new PriorityQueue1<@NonNull Object>());
+
+    // :: error: (argument)
+    f1.add(null);
+  }
+}
diff --git a/checker/tests/nullness/generics/GenericArgs.java b/checker/tests/nullness/generics/GenericArgs.java
new file mode 100644
index 0000000..6e39c35
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericArgs.java
@@ -0,0 +1,60 @@
+import java.io.*;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.dataflow.qual.*;
+
+@org.checkerframework.framework.qual.DefaultQualifier(Nullable.class)
+public class GenericArgs {
+
+  public @NonNull Set<@NonNull String> strings = new HashSet<>();
+
+  void test() {
+    @NonNull HashSet<@NonNull String> s = new HashSet<>();
+
+    strings.addAll(s);
+    strings.add("foo");
+  }
+
+  static class X<@NonNull T extends @NonNull Object> {
+    T value() {
+      // :: error: (return)
+      return null;
+    }
+  }
+
+  public static void test2() {
+    // :: error: (type.argument)
+    Object o = new X<Object>().value();
+  }
+
+  static <@NonNull Z extends @NonNull Object> void test3(Z z) {}
+
+  void test4() {
+    // :: error: (type.argument)
+    GenericArgs.<@Nullable Object>test3(null);
+    // :: error: (argument)
+    GenericArgs.<@NonNull Object>test3(null);
+  }
+
+  static class GenericConstructor {
+    <@NonNull T extends @NonNull Object> GenericConstructor(T t) {}
+  }
+
+  void test5() {
+    // :: error: (argument)
+    new <@NonNull String>GenericConstructor(null);
+  }
+
+  void testRecursiveDeclarations() {
+    class MyComparator<@NonNull T extends @NonNull Comparable<T>>
+        implements Comparator<T @NonNull []> {
+      @Pure
+      public int compare(T[] a, T[] b) {
+        return 0;
+      }
+    }
+    Comparator<@NonNull String @NonNull []> temp = new MyComparator<@NonNull String>();
+  }
+}
diff --git a/checker/tests/nullness/generics/GenericArgs2.java b/checker/tests/nullness/generics/GenericArgs2.java
new file mode 100644
index 0000000..52a31be
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericArgs2.java
@@ -0,0 +1,44 @@
+import java.io.*;
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+class Cell<T extends @Nullable Object> {
+  void add(T arg) {}
+}
+
+public class GenericArgs2 {
+  static <F extends Object> void test1(Cell<F> collection) {
+    // :: error: (argument)
+    collection.add(null); // should fail
+  }
+
+  static <F extends @Nullable Object> void test2(Cell<F> collection) {
+    // :: error: (argument)
+    collection.add(null); // should fail
+  }
+
+  static void test3(Cell<@Nullable Object> collection) {
+    collection.add(null); // valid
+  }
+  // No "<F super Object>" version of the above, as that is illegal in Java.
+
+  static class InvariantFilter {}
+
+  static class Invariant {}
+
+  HashMap<Class<? extends InvariantFilter>, Map<Class<? extends Invariant>, Integer>> filter_map1;
+  MyMap<@Nullable Class<? extends InvariantFilter>, Map<Class<? extends Invariant>, Integer>>
+      filter_map2;
+
+  public GenericArgs2(
+      HashMap<Class<? extends InvariantFilter>, Map<Class<? extends Invariant>, Integer>>
+          filter_map1,
+      MyMap<@Nullable Class<? extends InvariantFilter>, Map<Class<? extends Invariant>, Integer>>
+          filter_map2) {
+    this.filter_map1 = filter_map1;
+    this.filter_map2 = filter_map2;
+  }
+
+  class MyMap<K extends @Nullable Object, V extends @Nullable Object> {}
+}
diff --git a/checker/tests/nullness/generics/GenericArgs3.java b/checker/tests/nullness/generics/GenericArgs3.java
new file mode 100644
index 0000000..6f8f5ee
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericArgs3.java
@@ -0,0 +1,88 @@
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+
+class Other {
+  public static final class StaticIterator<T> implements Iterator<T> {
+    Enumeration<T> e;
+
+    public StaticIterator(Enumeration<T> e) {
+      this.e = e;
+    }
+
+    public boolean hasNext() {
+      return e.hasMoreElements();
+    }
+
+    public T next() {
+      return e.nextElement();
+    }
+
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  public final class FinalIterator<T> implements Iterator<T> {
+    Enumeration<T> e;
+
+    public FinalIterator(Enumeration<T> e) {
+      this.e = e;
+    }
+
+    public boolean hasNext() {
+      return e.hasMoreElements();
+    }
+
+    public T next() {
+      return e.nextElement();
+    }
+
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+  }
+}
+
+class Entry<K, V> implements Map.Entry<K, V> {
+  public V setValue(V newValue) {
+    throw new RuntimeException();
+  }
+
+  @SuppressWarnings("purity") // new and throw are not allowed, ignore
+  @Pure
+  public K getKey() {
+    throw new RuntimeException();
+  }
+
+  @SuppressWarnings("purity") // new and throw are not allowed, ignore
+  @Pure
+  public V getValue() {
+    throw new RuntimeException();
+  }
+}
+
+interface Function<F, T extends @Nullable Object> {
+  T apply(@Nullable F from);
+
+  @Pure
+  boolean equals(@Nullable Object obj);
+}
+
+enum IdentityFunction implements Function<Object, @Nullable Object> {
+  INSTANCE;
+
+  public @Nullable Object apply(@Nullable Object o) {
+    return o;
+  }
+}
+
+abstract class FilteredCollection<E> implements Collection<E> {
+  public boolean addAll(Collection<? extends E> collection) {
+    for (E element : collection) {}
+    return true;
+  }
+}
diff --git a/checker/tests/nullness/generics/GenericBoundsExplicit.java b/checker/tests/nullness/generics/GenericBoundsExplicit.java
new file mode 100644
index 0000000..42fdf78
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericBoundsExplicit.java
@@ -0,0 +1,51 @@
+package nullness.generics;
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class GenericBoundsExplicit<@NonNull T extends @Nullable Object> {
+
+  @SuppressWarnings("initialization.field.uninitialized")
+  T t;
+
+  public void method() {
+    // :: error: (dereference.of.nullable)
+    String str = t.toString();
+  }
+
+  public static void doSomething() {
+    final GenericBoundsExplicit<@Nullable String> b = new GenericBoundsExplicit<@Nullable String>();
+    b.method();
+  }
+}
+
+class GenericBoundsExplicit2<@NonNull TT extends @Nullable Object> {
+  @Nullable TT tt1;
+  // :: error: (initialization.field.uninitialized)
+  @NonNull TT tt2;
+  // :: error: (initialization.field.uninitialized)
+  TT tt3;
+
+  public void context() {
+
+    // :: error: (dereference.of.nullable)
+    tt1.toString();
+    tt2.toString();
+
+    // :: error: (dereference.of.nullable)
+    tt3.toString();
+  }
+}
+
+@SuppressWarnings("initialization.field.uninitialized")
+class GenericBoundsExplicit3<@NonNull TTT extends @NonNull Object> {
+  @Nullable TTT ttt1;
+  @NonNull TTT ttt2;
+  TTT ttt3;
+
+  public void context() {
+    // :: error: (dereference.of.nullable)
+    ttt1.toString();
+    ttt2.toString();
+    ttt3.toString();
+  }
+}
diff --git a/checker/tests/nullness/generics/GenericReturnField.java b/checker/tests/nullness/generics/GenericReturnField.java
new file mode 100644
index 0000000..c89fc7c
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericReturnField.java
@@ -0,0 +1,16 @@
+// Related to issue #594
+//   https://github.com/typetools/checker-framework/issues/594
+// but does not reproduce the problem because that issue depends on
+// the error message.  See ../../nullness-extra/issue594/
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class GenericReturnField<T> {
+  private @Nullable T result = null;
+
+  // Should return @Nullable T
+  private T getResult() {
+    // :: error: (return)
+    return result;
+  }
+}
diff --git a/checker/tests/nullness/generics/GenericTest11.java b/checker/tests/nullness/generics/GenericTest11.java
new file mode 100644
index 0000000..6abd7d3
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericTest11.java
@@ -0,0 +1,23 @@
+package com.example;
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class GenericTest11 {
+  public void m(BeanManager beanManager) {
+    Bean<?> bean = beanManager.getBeans(GenericTest11.class).iterator().next();
+    CreationalContext<?> context = beanManager.createCreationalContext(bean);
+  }
+
+  static interface BeanManager {
+    java.util.Set<Bean<?>> getBeans(
+        java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1);
+
+    <T1> CreationalContext<T1> createCreationalContext(Contextual<T1> arg0);
+  }
+
+  static interface Contextual<T2> {}
+
+  static interface Bean<T3> extends Contextual<T3> {}
+
+  static interface CreationalContext<T4> {}
+}
diff --git a/checker/tests/nullness/generics/GenericsBounds1.java b/checker/tests/nullness/generics/GenericsBounds1.java
new file mode 100644
index 0000000..a95788a
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericsBounds1.java
@@ -0,0 +1,27 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+interface GBList<E extends @Nullable Object> {
+  void add(E p);
+}
+
+/*
+ * Illustrate a problem with annotations on type variables.
+ * The annotation on the upper bound of a type variable is confused with an
+ * annotation on the type variable itself.
+ */
+public class GenericsBounds1<X extends @Nullable Object> {
+  void m1(@NonNull GBList<X> g1, @NonNull GBList<@Nullable X> g2) {
+    // :: error: (assignment)
+    g1 = null;
+    // :: error: (argument)
+    g1.add(null);
+
+    // :: error: (assignment)
+    g2 = null;
+    g2.add(null);
+
+    // :: error: (assignment)
+    g2 = g1;
+    g2.add(null);
+  }
+}
diff --git a/checker/tests/nullness/generics/GenericsBounds2.java b/checker/tests/nullness/generics/GenericsBounds2.java
new file mode 100644
index 0000000..684512a
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericsBounds2.java
@@ -0,0 +1,29 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+/*
+ * Illustrate a problem with annotations on type variables.
+ */
+public class GenericsBounds2<X extends @Nullable Object> {
+  void m1(X @NonNull [] a1, @Nullable X @NonNull [] a2) {
+    // :: error: (assignment)
+    a1 = null;
+    // :: error: (assignment)
+    a1[0] = null;
+
+    // :: error: (assignment)
+    a2 = null;
+    a2[0] = null;
+
+    // This error is expected when arrays are invariant.
+    // Currently, this error is not raised.
+    // TODOINVARR:: error: (assignment)
+    a2 = a1;
+    a2[0] = null;
+  }
+
+  void aaa(@Nullable Object[] p1, @NonNull Object[] p2) {
+    // This one is only expected when we switch the default for arrays to be invariant.
+    // TODOINVARR:: error: (assignment)
+    p1 = p2;
+  }
+}
diff --git a/checker/tests/nullness/generics/GenericsBounds3.java b/checker/tests/nullness/generics/GenericsBounds3.java
new file mode 100644
index 0000000..19c3640
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericsBounds3.java
@@ -0,0 +1,20 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class GenericsBounds3 {
+  class Sup<X extends @NonNull Object> {}
+
+  // :: error: (type.argument)
+  class Sub extends Sup<@Nullable Object> {}
+
+  class SubGood extends Sup<@NonNull Object> {}
+
+  interface ISup<X extends @NonNull Object> {}
+
+  // :: error: (type.argument)
+  class ISub implements ISup<@Nullable Object> {}
+
+  class ISubGood implements ISup<@NonNull Object> {}
+
+  // :: error: (type.argument)
+  class ISub2 extends Sup<Object> implements java.io.Serializable, ISup<@Nullable Object> {}
+}
diff --git a/checker/tests/nullness/generics/GenericsBounds4.java b/checker/tests/nullness/generics/GenericsBounds4.java
new file mode 100644
index 0000000..609b5d0
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericsBounds4.java
@@ -0,0 +1,28 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class GenericsBounds4 {
+  class Collection1<E extends @Nullable Object> {
+    public void add(E elt) {
+      // :: error: (dereference.of.nullable)
+      elt.hashCode();
+    }
+  }
+
+  Collection1<? extends @Nullable Object> f1 = new Collection1<@NonNull Object>();
+  // :: error: (assignment)
+  Collection1<@Nullable ? extends @Nullable Object> f2 = new Collection1<@NonNull Object>();
+  Collection1<@Nullable ? extends @Nullable Object> f3 = new Collection1<@Nullable Object>();
+
+  void bad() {
+    // This has to be forbidden, because f1 might refer to a
+    // collection that has NonNull as type argument.
+    // :: error: (argument)
+    f1.add(null);
+
+    // This is forbidden by the Java type rules:
+    // f1.add(new Object());
+
+    // ok
+    f3.add(null);
+  }
+}
diff --git a/checker/tests/nullness/generics/GenericsBounds5.java b/checker/tests/nullness/generics/GenericsBounds5.java
new file mode 100644
index 0000000..0fc78e3
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericsBounds5.java
@@ -0,0 +1,46 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class GenericsBounds5 {
+  class Collection1<E extends @Nullable Object> {
+    public void add(E elt) {
+      // This call is forbidden, because elt might be null.
+      // :: error: (dereference.of.nullable)
+      elt.hashCode();
+    }
+  }
+
+  <@Nullable F extends @Nullable Object> void addNull1(Collection1<F> l) {
+    // This call is allowed, because F is definitely @Nullable.
+    l.add(null);
+  }
+
+  // Effectively, this should be the same signature as above.
+  // TODO: the type "@Nullable ?" is "@Nullable ? extends @NonNull Object",
+  // with the wrong extends bound.
+  void addNull2(Collection1<@Nullable ? extends @Nullable Object> l) {
+    // This call has to pass, like above.
+    l.add(null);
+  }
+
+  <@Nullable F extends @Nullable Object> void addNull3(Collection1<F> l, F p) {
+    // This call is allowed, because F is definitely @Nullable.
+    l.add(null);
+    l.add(p);
+  }
+
+  // :: error: (assignment)
+  Collection1<@Nullable ? extends @Nullable Integer> f = new Collection1<@NonNull Integer>();
+
+  void bad(Collection1<@NonNull Integer> nnarg) {
+    // These have to be forbidden, because f1 might refer to a
+    // collection that has NonNull as type argument.
+    // :: error: (type.argument)
+    addNull1(nnarg);
+
+    // :: error: (argument)
+    addNull2(nnarg);
+
+    // :: error: (type.argument)
+    addNull3(nnarg, Integer.valueOf(4));
+  }
+}
diff --git a/checker/tests/nullness/generics/GenericsConstructor.java b/checker/tests/nullness/generics/GenericsConstructor.java
new file mode 100644
index 0000000..764b441
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericsConstructor.java
@@ -0,0 +1,15 @@
+public class GenericsConstructor {
+  class Test {
+    <T> Test(T param) {}
+
+    <T1, T2 extends T1> Test(T1 p1, T2 p2) {}
+  }
+
+  void call() {
+    new Test("Ha!");
+    new <String>Test("Ha!");
+    new Test(new Object());
+
+    // new <String, String>Test("Hi", "Ho");
+  }
+}
diff --git a/checker/tests/nullness/generics/GenericsExample.java b/checker/tests/nullness/generics/GenericsExample.java
new file mode 100644
index 0000000..3714e2e
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericsExample.java
@@ -0,0 +1,179 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// This is the example from manual section:
+// "Generics (parametric polymorphism or type polymorphism)"
+// whose source code is ../../../docs/manual/advanced-features.tex
+public class GenericsExample {
+
+  class MyList1<@Nullable T> {
+    T t;
+    @Nullable T nble;
+    @NonNull T nn;
+
+    MyList1(T t, @Nullable T nble, @NonNull T nn) {
+      this.t = t;
+      this.nble = nble;
+      this.nn = nn;
+    }
+
+    void add(T arg) {}
+
+    T get(int i) {
+      return t;
+    }
+
+    void m() {
+      t = null;
+      t = nble;
+      nble = null;
+      // :: error: (assignment)
+      nn = null;
+      t = this.get(0);
+      nble = this.get(0);
+      // :: error: (assignment)
+      nn = this.get(0);
+      this.add(t);
+      this.add(nble);
+      this.add(nn);
+    }
+  }
+
+  class MyList1a<@Nullable T extends @Nullable Object> {
+    T t;
+    @Nullable T nble;
+    @NonNull T nn;
+
+    MyList1a(T t, @Nullable T nble, @NonNull T nn) {
+      this.t = t;
+      this.nble = nble;
+      this.nn = nn;
+    }
+
+    void add(T arg) {}
+
+    T get(int i) {
+      return t;
+    }
+
+    void m() {
+      t = null;
+      t = nble;
+      nble = null;
+      // :: error: (assignment)
+      nn = null;
+      t = this.get(0);
+      nble = this.get(0);
+      // :: error: (assignment)
+      nn = this.get(0);
+      this.add(t);
+      this.add(nble);
+      this.add(nn);
+    }
+  }
+
+  class MyList2<@NonNull T extends @NonNull Object> {
+    T t;
+    @Nullable T nble;
+    @NonNull T nn;
+
+    MyList2(T t, @Nullable T nble, @NonNull T nn) {
+      this.t = t;
+      this.nble = nble;
+      this.nn = nn;
+    }
+
+    void add(T arg) {}
+
+    T get(int i) {
+      return t;
+    }
+
+    void m() {
+      // :: error: (assignment)
+      t = null;
+      // :: error: (assignment)
+      t = nble;
+      nble = null;
+      // :: error: (assignment)
+      nn = null;
+      t = this.get(0);
+      nble = this.get(0);
+      nn = this.get(0);
+      this.add(t);
+      // :: error: (argument)
+      this.add(nble);
+      this.add(nn);
+    }
+  }
+
+  class MyList2a<T extends @NonNull Object> { // same as MyList2
+    T t;
+    @Nullable T nble;
+    @NonNull T nn;
+
+    MyList2a(T t, @Nullable T nble, @NonNull T nn) {
+      this.t = t;
+      this.nble = nble;
+      this.nn = nn;
+    }
+
+    void add(T arg) {}
+
+    T get(int i) {
+      return t;
+    }
+
+    void m() {
+      // :: error: (assignment)
+      t = null;
+      // :: error: (assignment)
+      t = nble;
+      nble = null;
+      // :: error: (assignment)
+      nn = null;
+      t = this.get(0);
+      nble = this.get(0);
+      nn = this.get(0);
+      this.add(t);
+      // :: error: (argument)
+      this.add(nble);
+      this.add(nn);
+    }
+  }
+
+  class MyList3<T extends @Nullable Object> {
+    T t;
+    @Nullable T nble;
+    @NonNull T nn;
+
+    MyList3(T t, @Nullable T nble, @NonNull T nn) {
+      this.t = t;
+      this.nble = nble;
+      this.nn = nn;
+    }
+
+    void add(T arg) {}
+
+    T get(int i) {
+      return t;
+    }
+
+    void m() {
+      // :: error: (assignment)
+      t = null;
+      // :: error: (assignment)
+      t = nble;
+      nble = null;
+      // :: error: (assignment)
+      nn = null;
+      t = this.get(0);
+      nble = this.get(0);
+      // :: error: (assignment)
+      nn = this.get(0);
+      this.add(t);
+      // :: error: (argument)
+      this.add(nble);
+      this.add(nn);
+    }
+  }
+}
diff --git a/checker/tests/nullness/generics/GenericsExampleMin.java b/checker/tests/nullness/generics/GenericsExampleMin.java
new file mode 100644
index 0000000..b40d08a
--- /dev/null
+++ b/checker/tests/nullness/generics/GenericsExampleMin.java
@@ -0,0 +1,93 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// This is the example from manual section:
+// "Generics (parametric polymorphism or type polymorphism)"
+// whose source code is ../../../docs/manual/advanced-features.tex
+public class GenericsExampleMin {
+
+  class MyList1<@Nullable T> {
+    T t;
+    @Nullable T nble;
+    @NonNull T nn;
+
+    public MyList1(T t, @Nullable T nble, @NonNull T nn) {
+      this.t = t;
+      this.nble = nble;
+      this.nn = nn;
+      this.t = this.nble;
+    }
+
+    T get(int i) {
+      return t;
+    }
+
+    // This method works.
+    // Note that it fails to work if it is moved after m2() in the syntax tree.
+    // TODO: the above comment seems out-of-date, as method m3 below works.
+    void m1() {
+      t = this.get(0);
+      nble = this.get(0);
+    }
+
+    // When the assignment to nn is added, the assignments to t and nble also fail, which is
+    // unexpected.
+    void m2() {
+      // :: error: (assignment)
+      nn = null;
+      t = this.get(0);
+      nble = this.get(0);
+    }
+
+    void m3() {
+      t = this.get(0);
+      nble = this.get(0);
+    }
+  }
+
+  class MyList2<@NonNull T> {
+    T t;
+    @Nullable T nble;
+
+    public MyList2(T t, @Nullable T nble) {
+      // :: error: (assignment)
+      this.t = this.nble; // error
+      // :: error: (assignment)
+      this.t = nble; // error
+    }
+  }
+
+  class MyList3<T extends @Nullable Object> {
+    T t;
+    @Nullable T nble;
+    @NonNull T nn;
+
+    public MyList3(T t, @Nullable T nble, @NonNull T nn) {
+      // :: error: (assignment)
+      this.t = nble;
+      this.t = nn;
+      // :: error: (assignment)
+      this.nn = t;
+      // :: error: (assignment)
+      this.nn = nble;
+      this.nn = nn;
+    }
+  }
+
+  class MyList4<T extends @NonNull Object> {
+    T t;
+    @Nullable T nble;
+    @NonNull T nn;
+
+    public MyList4(T t, @Nullable T nble, @NonNull T nn) {
+      // :: error: (assignment)
+      this.t = nble;
+      this.t = nn;
+      this.nn = t;
+      // :: error: (assignment)
+      this.nn = nble;
+      this.nn = nn;
+      this.nn = t;
+      this.nble = t;
+    }
+  }
+}
diff --git a/checker/tests/nullness/generics/InferMethod.java b/checker/tests/nullness/generics/InferMethod.java
new file mode 100644
index 0000000..49f3e85
--- /dev/null
+++ b/checker/tests/nullness/generics/InferMethod.java
@@ -0,0 +1,37 @@
+// From issue #216:
+// https://github.com/typetools/checker-framework/issues/216
+
+public class InferMethod {
+  public abstract static class Generic<T> {
+    public class Nested {
+      public void nestedMethod(T item) {}
+    }
+
+    public static class NestedStatic<TInner> {
+      public void nestedMethod2(TInner item) {}
+    }
+
+    public abstract void method();
+
+    public abstract void method2();
+
+    public void method3(T item) {}
+  }
+
+  public static class Concrete extends Generic<String> {
+
+    @Override
+    public void method() {
+      Nested o = new Nested();
+      o.nestedMethod("test");
+    }
+
+    @Override
+    public void method2() {
+      NestedStatic<String> o = new NestedStatic<>();
+      o.nestedMethod2("test");
+
+      this.method3("test");
+    }
+  }
+}
diff --git a/checker/tests/nullness/generics/InferredPrimitive.java b/checker/tests/nullness/generics/InferredPrimitive.java
new file mode 100644
index 0000000..fbe6140
--- /dev/null
+++ b/checker/tests/nullness/generics/InferredPrimitive.java
@@ -0,0 +1,6 @@
+/** Test case for Issue 143: https://github.com/typetools/checker-framework/issues/143 */
+public class InferredPrimitive {
+  public static void main(String[] args) {
+    java.util.Set<Long> s = java.util.Collections.singleton(123L);
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue134.java b/checker/tests/nullness/generics/Issue134.java
new file mode 100644
index 0000000..8ad53cf
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue134.java
@@ -0,0 +1,27 @@
+// Test case for Issue 134:
+// https://github.com/typetools/checker-framework/issues/134
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class Wrap<T> {
+  class Inner {
+    T of(T in) {
+      return in;
+    }
+  }
+
+  Inner get() {
+    return new Inner();
+  }
+}
+
+class Bug {
+  void bar(Wrap<Integer> w, Integer f) {
+    w.get().of(f).toString();
+  }
+
+  void baz(Wrap<@Nullable Integer> w, Integer f) {
+    // :: error: (dereference.of.nullable)
+    w.get().of(f).toString();
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue1838.java b/checker/tests/nullness/generics/Issue1838.java
new file mode 100644
index 0000000..f8c4ada
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue1838.java
@@ -0,0 +1,29 @@
+// Test case for Issue 1838:
+// https://github.com/typetools/checker-framework/issues/1838
+
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1838 {
+  public static void main(String[] args) {
+    f();
+  }
+
+  public static void f() {
+    List<@Nullable Object> list = new ArrayList<>();
+    list.add(null);
+    List<List<@Nullable Object>> listList = new ArrayList<List<@Nullable Object>>();
+    listList.add(list);
+    // :: error: (argument)
+    processElements(listList);
+  }
+
+  private static void processElements(List<? extends List<Object>> listList) {
+    for (List<Object> list : listList) {
+      for (Object element : list) {
+        element.toString();
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue1838Min.java b/checker/tests/nullness/generics/Issue1838Min.java
new file mode 100644
index 0000000..73eae2b
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue1838Min.java
@@ -0,0 +1,12 @@
+// Test case for Issue 1838:
+// https://github.com/typetools/checker-framework/issues/1838
+
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1838Min {
+  List<List<@Nullable Object>> llno = new ArrayList<>();
+  // :: error: (assignment)
+  List<? extends List<Object>> lweo = llno;
+}
diff --git a/checker/tests/nullness/generics/Issue240.java b/checker/tests/nullness/generics/Issue240.java
new file mode 100644
index 0000000..e5c6686
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue240.java
@@ -0,0 +1,16 @@
+// Test case for Issue 240:
+// https://github.com/typetools/checker-framework/issues/240
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class I<A> {}
+
+// This should compile, because the implicit upper
+// bound of I is "@Nullable Object"
+class Use extends I<@Nullable String> {}
+
+class I2<A extends Object> {}
+
+// This use must be an error.
+// :: error: (type.argument)
+class Use2 extends I2<@Nullable String> {}
diff --git a/checker/tests/nullness/generics/Issue269.java b/checker/tests/nullness/generics/Issue269.java
new file mode 100644
index 0000000..32f9f35
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue269.java
@@ -0,0 +1,29 @@
+// Test case for Issue 269
+// https://github.com/typetools/checker-framework/issues/269
+class Issue269 {
+  // Implicitly G has bound @Nullable Object
+  interface Callback<G> {
+    public boolean handler(G arg);
+  }
+
+  void method1(Callback callback) {
+    // Allow this call.
+    // :: warning: [unchecked] unchecked call to handler(G) as a member of the raw type
+    // Issue269.Callback
+    callback.handler(this);
+  }
+
+  // Implicitly H has bound @NonNull Object
+  interface CallbackNN<H extends Object> {
+    public boolean handler(H arg);
+  }
+
+  void method2(CallbackNN callback) {
+    // Forbid this call, because the bound is not respected.
+    // TODO: false negative. See #635.
+    //// :: error: (argument)
+    // :: warning: [unchecked] unchecked call to handler(H) as a member of the raw type
+    // Issue269.CallbackNN
+    callback.handler(null);
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue270.java b/checker/tests/nullness/generics/Issue270.java
new file mode 100644
index 0000000..820f7d6
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue270.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// ::error: (bound)
+public class Issue270<@Nullable TypeParam extends @NonNull Object> {
+  public static void main() {
+
+    // ::error: (type.argument)
+    @Nullable Issue270<@Nullable String> strWAtv = null;
+    // ::error: (type.argument)
+    @Nullable Issue270<@NonNull Integer> intWAtv = null;
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue282.java b/checker/tests/nullness/generics/Issue282.java
new file mode 100644
index 0000000..635b883
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue282.java
@@ -0,0 +1,29 @@
+// Test case for Issue 282
+// https://github.com/typetools/checker-framework/issues/282
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+
+@SuppressWarnings("nullness")
+abstract class ImmutableSortedSet<E extends @NonNull Object> implements Set<E> {
+  static <E> ImmutableSortedSet<E> copyOf(
+      Comparator<? super E> comparator, Collection<? extends E> elements) {
+    return null;
+  }
+}
+
+@SuppressWarnings("nullness")
+abstract class Ordering<T> implements Comparator<T> {
+  static Ordering<Object> usingToString() {
+    return null;
+  }
+}
+
+abstract class Example {
+  private static <@NonNull T extends @NonNull Object> ImmutableSortedSet<T> setSortedByToString(
+      Collection<T> set) {
+    return ImmutableSortedSet.copyOf(Ordering.usingToString(), set);
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue282Min.java b/checker/tests/nullness/generics/Issue282Min.java
new file mode 100644
index 0000000..6d6dabe
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue282Min.java
@@ -0,0 +1,20 @@
+// Test case for Issue 282 (minimized)
+// https://github.com/typetools/checker-framework/issues/282
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue282Min {
+  static <M> Set<M> copyOf(Comparator<? super M> comparator, Collection<? extends M> elements) {
+    // :: error: (return)
+    return null;
+  }
+}
+
+class Example282Min {
+  <T extends @NonNull Object> Set<T> foo(Comparator<Object> ord, Collection<T> set) {
+    return Issue282Min.copyOf(ord, set);
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue312.java b/checker/tests/nullness/generics/Issue312.java
new file mode 100644
index 0000000..daf35b9
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue312.java
@@ -0,0 +1,42 @@
+// Test case for Issue 312:
+// https://github.com/typetools/checker-framework/issues/312
+
+import java.util.List;
+
+// A mock-up of the Guava API used in the test case;
+// see below for the code that uses Guava.
+@SuppressWarnings("nullness")
+class Ordering312<T> {
+  public static <C extends Comparable> Ordering312<C> natural() {
+    return null;
+  }
+
+  public <S extends T> Ordering312<S> reverse() {
+    return null;
+  }
+
+  public <E extends T> List<E> sortedCopy(Iterable<E> elements) {
+    return null;
+  }
+}
+
+public class Issue312 {
+  void test(List<String> list) {
+    Ordering312.natural().reverse().sortedCopy(list);
+  }
+}
+
+/* Original test using Guava:
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering312;
+import java.util.List;
+
+public class Issue312 {
+    void test() {
+        List<String> list = Lists.newArrayList();
+        Ordering.natural().reverse().sortedCopy(list);
+    }
+}
+
+*/
diff --git a/checker/tests/nullness/generics/Issue313.java b/checker/tests/nullness/generics/Issue313.java
new file mode 100644
index 0000000..583ce67
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue313.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue313 {
+  class A<@NonNull T extends @Nullable Object> {}
+
+  <@NonNull X extends @Nullable Object> void m() {
+    new A<X>();
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue314.java b/checker/tests/nullness/generics/Issue314.java
new file mode 100644
index 0000000..529ed05
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue314.java
@@ -0,0 +1,29 @@
+// Test case for Issue 314:
+// https://github.com/typetools/checker-framework/issues/314
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue314 {
+  <T extends @NonNull Object> List<T> m1(List<@NonNull T> l1) {
+    return l1;
+  }
+
+  <T extends @Nullable Object> List<T> m2(List<@NonNull T> l1) {
+    // :: error: (return)
+    return l1;
+  }
+
+  class Also<S extends @NonNull Object> {
+    S f1;
+    @NonNull S f2;
+
+    {
+      // :: error: (assignment)
+      f1 = f2;
+      // :: error: (assignment)
+      f2 = f1;
+    }
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue319.java b/checker/tests/nullness/generics/Issue319.java
new file mode 100644
index 0000000..5d316f7
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue319.java
@@ -0,0 +1,49 @@
+// Test case for Issue 319:
+// https://github.com/typetools/checker-framework/issues/319
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue319 {
+  class Foo<T> {
+    Foo(@Nullable T t) {}
+  }
+
+  <T> Foo<T> newFoo(@Nullable T t) {
+    return new Foo<>(t);
+  }
+
+  void pass() {
+    Foo<Boolean> f = newFoo(Boolean.FALSE);
+  }
+
+  void fail() {
+    Foo<Boolean> f = newFoo(null);
+  }
+
+  void workaround() {
+    Foo<Boolean> f = Issue319.this.<Boolean>newFoo(null);
+  }
+}
+
+class Issue319NN {
+  class Foo<T> {
+    Foo(@NonNull T t) {}
+  }
+
+  <T> Foo<T> newFoo(@NonNull T t) {
+    return new Foo<>(t);
+  }
+
+  void pass() {
+    Foo<Boolean> f = newFoo(Boolean.FALSE);
+  }
+
+  void fail() {
+    // :: error: (argument)
+    Foo<Boolean> f = newFoo(null);
+  }
+
+  void pass2() {
+    Foo<@Nullable Boolean> f = newFoo(Boolean.FALSE);
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue326.java b/checker/tests/nullness/generics/Issue326.java
new file mode 100644
index 0000000..dc275a9
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue326.java
@@ -0,0 +1,11 @@
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue326 {
+  {
+    Set<@Nullable String> local = new HashSet<>();
+  }
+
+  Set<@Nullable String> field = new HashSet<>();
+}
diff --git a/checker/tests/nullness/generics/Issue329.java b/checker/tests/nullness/generics/Issue329.java
new file mode 100644
index 0000000..ff583b5
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue329.java
@@ -0,0 +1,40 @@
+// Test case for Issue 329:
+// https://github.com/typetools/checker-framework/issues/329
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+abstract class Issue329 {
+  interface Flag<T> {}
+
+  abstract <X> void setExtension(X value);
+
+  abstract <T> T getValue(Flag<T> flag);
+
+  void f(Flag<String> flag) {
+    String s = getValue(flag);
+    setExtension(s);
+
+    setExtension(getValue(flag));
+  }
+}
+
+abstract class Issue329NN {
+  interface Flag<T> {}
+
+  // Explicit bound makes it NonNull
+  abstract <X extends Object> void setExtension(X value);
+
+  abstract <T> T getValue(Flag<T> flag);
+
+  void f1(Flag<@Nullable String> flag) {
+    String s = getValue(flag);
+    // :: error: (type.argument)
+    setExtension(s);
+  }
+
+  void f2(Flag<@Nullable String> flag) {
+    // TODO: false negative. See #979.
+    //// :: error: (type.argument)
+    setExtension(getValue(flag));
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue335.java b/checker/tests/nullness/generics/Issue335.java
new file mode 100644
index 0000000..ddf023c
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue335.java
@@ -0,0 +1,22 @@
+// Test case for Issue 335:
+// https://github.com/typetools/checker-framework/issues/335
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class Pair<A, B> {
+  static <C, D> Pair<C, D> of(@Nullable C first, @Nullable D second) {
+    throw new RuntimeException();
+  }
+}
+
+class Optional<S> {
+  static <T> Optional<T> of(T reference) {
+    throw new RuntimeException();
+  }
+}
+
+public class Issue335 {
+  Optional<Pair<String, String>> m(String one, String two) {
+    return Optional.of(Pair.of(one, two));
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue337.java b/checker/tests/nullness/generics/Issue337.java
new file mode 100644
index 0000000..4e76ab5
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue337.java
@@ -0,0 +1,32 @@
+// Test case for Issue 337:
+// https://github.com/typetools/checker-framework/issues/337
+
+import javax.annotation.Nullable;
+
+abstract class Issue337<R> {
+  abstract R getThing(String key);
+
+  @Nullable R m1(@Nullable String key) {
+    return (key == null) ? null : getThing(key);
+  }
+
+  @Nullable R m1b(@Nullable String key) {
+    return (key != null) ? getThing(key) : null;
+  }
+
+  @Nullable R m2(@Nullable String key) {
+    return (key == null)
+        ?
+        // :: error: (argument)
+        getThing(key)
+        : null;
+  }
+
+  @Nullable R m2b(@Nullable String key) {
+    return (key != null)
+        ? null
+        :
+        // :: error: (argument)
+        getThing(key);
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue339.java b/checker/tests/nullness/generics/Issue339.java
new file mode 100644
index 0000000..a092331
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue339.java
@@ -0,0 +1,16 @@
+// Test case for Issue 339:
+// https://github.com/typetools/checker-framework/issues/339
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue339<S> {
+  static <T> @NonNull T checkNotNull(T p) {
+    throw new RuntimeException();
+  }
+
+  void m(@Nullable S s) {
+    @NonNull S r1 = Issue339.<@Nullable S>checkNotNull(s);
+    @NonNull S r2 = Issue339.checkNotNull(s);
+    @NonNull S r3 = Issue339.checkNotNull(null);
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue421.java b/checker/tests/nullness/generics/Issue421.java
new file mode 100644
index 0000000..fd0d400
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue421.java
@@ -0,0 +1,16 @@
+public class Issue421<IE> {
+  abstract static class C<CE> {
+    abstract X<? extends CE> getX();
+  }
+
+  interface X<T> {}
+
+  abstract static class R<RE> {
+    abstract boolean d(X<? extends RE> id);
+  }
+
+  private void f(C<IE> c, R<IE> r) {
+    X<? extends IE> x = c.getX();
+    boolean bval = r.d(x);
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue422.java b/checker/tests/nullness/generics/Issue422.java
new file mode 100644
index 0000000..5ebec4c
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue422.java
@@ -0,0 +1,6 @@
+public class Issue422 {
+  public <T> boolean f(T newValue, T oldValue) {
+    return (oldValue instanceof Boolean || oldValue instanceof Integer)
+        && oldValue.equals(newValue);
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue428.java b/checker/tests/nullness/generics/Issue428.java
new file mode 100644
index 0000000..26bf858
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue428.java
@@ -0,0 +1,12 @@
+// Test case for issue #428:
+// https://github.com/typetools/checker-framework/issues/428
+
+import java.util.List;
+
+public interface Issue428<T extends Number> {}
+
+class Test428 {
+  void m(List<Issue428<? extends Object>> is) {
+    Issue428<?> i = is.get(0);
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue459.java b/checker/tests/nullness/generics/Issue459.java
new file mode 100644
index 0000000..18a7f84
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue459.java
@@ -0,0 +1,20 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue459 {
+  public class Generic<K, V> {}
+
+  interface Iface<K, V> {
+    public <K1 extends K, V1 extends V> Generic<K, V> foo(Generic<? super K1, ? super V1> arg);
+
+    public <K2 extends K, V2 extends V> Generic<K, V> foo2(
+        Generic<? super K2, ? super V2> arg, K2 strArg);
+  }
+
+  void f(Iface<Object, Object> arg, @NonNull String nnString) {
+    final Generic<String, Integer> obj = new Generic<>();
+    arg.foo(obj);
+
+    final Generic<@Nullable String, Integer> obj2 = new Generic<>();
+    arg.foo2(obj2, nnString);
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue783a.java b/checker/tests/nullness/generics/Issue783a.java
new file mode 100644
index 0000000..7b3649e
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue783a.java
@@ -0,0 +1,22 @@
+// Test case for issue #783:
+// https://github.com/typetools/checker-framework/issues/783
+
+// This file suffers no type-checking warning, but Issue783b.java does, and
+// it differs only in the import statement.
+
+// You may find it helpful to run this as:
+// $ch/bin/javac -cp $ch/dist/checker.jar -processor nullness -AprintVerboseGenerics Issue783b.java
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue783a<T> {
+  private @Nullable T val;
+
+  public void set(@Nullable T val) {
+    this.val = val;
+  }
+
+  public @Nullable T get() {
+    return val;
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue783b.java b/checker/tests/nullness/generics/Issue783b.java
new file mode 100644
index 0000000..5e1bc1a
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue783b.java
@@ -0,0 +1,22 @@
+// Test case for issue #783:
+// https://github.com/typetools/checker-framework/issues/783
+
+// Note that Issue783a.java, which differs only in the import statement,
+// suffers no type-checking warning.
+
+// You may find it helpful to run this as:
+// $ch/bin/javac -cp $ch/dist/checker.jar -processor nullness -AprintVerboseGenerics Issue783b.java
+
+import javax.annotation.Nullable;
+
+public class Issue783b<T> {
+  private @Nullable T val;
+
+  public void set(@Nullable T val) {
+    this.val = val;
+  }
+
+  @Nullable public T get() {
+    return val;
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue783c.java b/checker/tests/nullness/generics/Issue783c.java
new file mode 100644
index 0000000..c84e72b
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue783c.java
@@ -0,0 +1,12 @@
+public class Issue783c<T> {
+  // :: error: (initialization.field.uninitialized)
+  private T val;
+
+  public void set(T val) {
+    this.val = val;
+  }
+
+  public T get() {
+    return val;
+  }
+}
diff --git a/checker/tests/nullness/generics/Issue849.java b/checker/tests/nullness/generics/Issue849.java
new file mode 100644
index 0000000..a886358
--- /dev/null
+++ b/checker/tests/nullness/generics/Issue849.java
@@ -0,0 +1,14 @@
+// Test case for Issue 849:
+// https://github.com/typetools/checker-framework/issues/849
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue849 {
+  class Gen<T> {}
+
+  void nullness(Gen<Gen<@NonNull Object>> genGenNonNull) {
+    // :: error: (assignment)
+    Gen<@Nullable ? extends @Nullable Gen<@Nullable Object>> a = genGenNonNull;
+  }
+}
diff --git a/checker/tests/nullness/generics/KeyForPolyKeyFor.java b/checker/tests/nullness/generics/KeyForPolyKeyFor.java
new file mode 100644
index 0000000..a554aa5
--- /dev/null
+++ b/checker/tests/nullness/generics/KeyForPolyKeyFor.java
@@ -0,0 +1,26 @@
+package nullness.generics;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+
+// test related to issue 429: https://github.com/typetools/checker-framework/issues/429
+public class KeyForPolyKeyFor {
+  // TODO: Figure out why diamond operator does not work:
+  // Map<@KeyFor("dict") String, String> dict = new HashMap<>();
+  Map<@KeyFor("dict") String, String> dict = new HashMap<@KeyFor("dict") String, String>();
+
+  void m() {
+    Set<@KeyFor("dict") String> s = nounSubset(dict.keySet());
+
+    for (@KeyFor("dict") String noun : nounSubset(dict.keySet())) {}
+  }
+
+  // This method's declaration uses no @KeyFor annotations because in addition to being used by the
+  // dictionary feature, it is also used by a spell checker that only stores sets of words and does
+  // not use the notions of dictionaries, maps or keys.
+  Set<@PolyKeyFor String> nounSubset(Set<@PolyKeyFor String> words) {
+    return words;
+  }
+}
diff --git a/checker/tests/nullness/generics/MapLoop.java b/checker/tests/nullness/generics/MapLoop.java
new file mode 100644
index 0000000..616ac73
--- /dev/null
+++ b/checker/tests/nullness/generics/MapLoop.java
@@ -0,0 +1,17 @@
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class MapLoop {
+  void test1(Map<String, String> map) {
+    for (Map.Entry<@KeyFor("map") String, String> entry : map.entrySet()) {}
+  }
+
+  void test2(Map<String, String> map) {
+    for (Map.Entry<? extends String, ? extends String> entry : map.entrySet()) {}
+  }
+
+  void test3(Map<String, @Nullable Object> map) {
+    for (Map.Entry<? extends String, @Nullable Object> entry : map.entrySet()) {}
+    for (Object val : map.values()) {}
+  }
+}
diff --git a/checker/tests/nullness/generics/MethodTypeVars.java b/checker/tests/nullness/generics/MethodTypeVars.java
new file mode 100644
index 0000000..7263a21
--- /dev/null
+++ b/checker/tests/nullness/generics/MethodTypeVars.java
@@ -0,0 +1,38 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+/*
+ * This test is based on Issue 93:
+ * https://github.com/typetools/checker-framework/issues/93
+ */
+public class MethodTypeVars {
+  void m() {
+    // :: error: (type.argument)
+    Object a = A.badMethod(null);
+    Object b = A.badMethod(new Object());
+
+    // :: error: (type.argument)
+    A.goodMethod(null);
+    A.goodMethod(new Object());
+  }
+}
+
+class A {
+  public static <T extends @NonNull Object> T badMethod(T t) {
+    // :: warning: [unchecked] unchecked cast
+    return (T) new Object();
+  }
+
+  public static <T extends @NonNull Object> void goodMethod(T t) {}
+}
+
+class B {
+  public <T> void indexOf1(T[] a, @Nullable Object elt) {}
+  // This is not valid Java syntax.
+  // public void indexOf2(?[] a, @Nullable Object elt) {}
+
+  void call() {
+    Integer[] arg = new Integer[] {1, 2, 3, 4};
+    indexOf1(arg, Integer.valueOf(5));
+    // indexOf2(arg, new Integer(5));
+  }
+}
diff --git a/checker/tests/nullness/generics/MethodTypeVars2.java b/checker/tests/nullness/generics/MethodTypeVars2.java
new file mode 100644
index 0000000..603b706
--- /dev/null
+++ b/checker/tests/nullness/generics/MethodTypeVars2.java
@@ -0,0 +1,35 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class MethodTypeVars2 {
+
+  class GeoSegment {}
+
+  interface Path<N1, P1 extends Path<N1, P1>> {}
+
+  private static <N2 extends GeoSegment, P2 extends Path<N2, P2>> @Nullable Object pathToRoute(
+      Path<N2, P2> path) {
+    return null;
+  }
+
+  class StreetSegment extends GeoSegment {}
+
+  class StreetSegmentPath implements Path<StreetSegment, StreetSegmentPath> {}
+
+  void call(StreetSegmentPath p) {
+    Object r = pathToRoute(p);
+  }
+
+  static class WorkingWithOne {
+    interface GPath<P extends GPath<P>> {}
+
+    class GStreetSegmentPath implements GPath<GStreetSegmentPath> {}
+
+    private static <P extends GPath<P>> @Nullable Object pathToRoute(GPath<P> path) {
+      return null;
+    }
+
+    void call(GStreetSegmentPath p) {
+      Object r = pathToRoute(p);
+    }
+  }
+}
diff --git a/checker/tests/nullness/generics/MethodTypeVars3.java b/checker/tests/nullness/generics/MethodTypeVars3.java
new file mode 100644
index 0000000..3880bcb
--- /dev/null
+++ b/checker/tests/nullness/generics/MethodTypeVars3.java
@@ -0,0 +1,43 @@
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class MethodTypeVars3 {
+  public static <@KeyFor("#1") T extends @KeyFor("#1") Object> Map<T, List<T>> dominators(
+      Map<T, List<T>> preds) {
+    List<T> nodes = new ArrayList<>(preds.keySet());
+
+    // Compute roots & non-roots, for convenience
+    List<@KeyFor("preds") T> roots = new ArrayList<>();
+    List<@KeyFor("preds") T> non_roots = new ArrayList<>();
+
+    Map<@KeyFor("preds") T, List<T>> dom = new HashMap<>();
+
+    // Initialize result:  for roots just the root, otherwise everything
+    for (@KeyFor("preds") T node : preds.keySet()) {
+      if (preds.get(node).isEmpty()) {
+        // This is a root
+        roots.add(node);
+        // Its only dominator is itself.
+        Set<@KeyFor("preds") T> set = Collections.singleton(node);
+
+        dom.put(node, new ArrayList<T>(set));
+
+        dom.put(node, new ArrayList<T>(Collections.singleton(node)));
+      } else {
+        non_roots.add(node);
+        dom.put(node, new ArrayList<T>(nodes));
+      }
+    }
+
+    return dom;
+  }
+
+  <XXX extends Object> void test(Map<XXX, List<XXX>> dom, XXX node) {
+    dom.put(node, new ArrayList<XXX>(Collections.singleton(node)));
+  }
+}
diff --git a/checker/tests/nullness/generics/MethodTypeVars5.java b/checker/tests/nullness/generics/MethodTypeVars5.java
new file mode 100644
index 0000000..a4945bb
--- /dev/null
+++ b/checker/tests/nullness/generics/MethodTypeVars5.java
@@ -0,0 +1,89 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class MethodTypeVars5 {
+  class B<S extends @Nullable Object> {
+    S t;
+
+    B(S t) {
+      this.t = t;
+    }
+
+    S get() {
+      return t;
+    }
+  }
+
+  B<String> b = new B<>("Hello World");
+
+  String doit1() {
+    return doit1(b);
+  }
+
+  <U extends @Nullable Object> U doit1(B<U> x) {
+    return x.get();
+  }
+
+  String doit2() {
+    // Passing the null argument has no effect on the inferred type argument:
+    // the second parameter type doesn't contain the type variable at all.
+    return doit2(b, null);
+  }
+
+  <T extends @Nullable Object> T doit2(B<T> x, @Nullable String y) {
+    return x.get();
+  }
+
+  String doit3() {
+    // Passing the null argument has no effect on the inferred type argument:
+    // the type variable only appears as nested type.
+    return doit3(null);
+  }
+
+  String doit3b() {
+    return doit3(new B<String>("Hi"));
+  }
+
+  String doit3b2() {
+    return doit3(new B<>("Hi"));
+  }
+
+  String doit3c() {
+    // :: error: (return)
+    return doit3(new B<@Nullable String>("Hi"));
+  }
+
+  void doit3d() {
+    // :: error: (assignment)
+    @NonNull String s = doit3(new B<@Nullable String>("Hi"));
+  }
+
+  void doit3e() {
+    String s = doit3(new B<String>("Hi"));
+  }
+
+  void doit3e2() {
+    String s = doit3(new B<>("Hi"));
+  }
+
+  <T extends @Nullable Object> T doit3(@Nullable B<T> x) {
+    if (x != null) {
+      return x.get();
+    } else {
+      // This won't work at runtime, but whatever.
+      @SuppressWarnings("unchecked")
+      T res = (T) new Object();
+      return res;
+    }
+  }
+
+  String doit4() {
+    // Passing the null argument has an impact on the inferred type argument:
+    // the type variable appears as the top-level type.
+    // :: error: (return)
+    return doit4("Ha!", null);
+  }
+
+  <T extends @Nullable Object> T doit4(T x, T y) {
+    return x;
+  }
+}
diff --git a/checker/tests/nullness/generics/MethodTypeVars6.java b/checker/tests/nullness/generics/MethodTypeVars6.java
new file mode 100644
index 0000000..266eee6
--- /dev/null
+++ b/checker/tests/nullness/generics/MethodTypeVars6.java
@@ -0,0 +1,62 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+class APair<S extends @Nullable Object, T extends @Nullable Object> {
+  static <U extends @Nullable Object, V extends @Nullable Object> APair<U, V> of(U p1, V p2) {
+    return new APair<U, V>();
+  }
+
+  static <U extends @Nullable Object, V extends @Nullable Object> APair<U, V> of2(U p1, V p2) {
+    return new APair<>();
+  }
+}
+
+class PairSub<SS extends @Nullable Object, TS extends @Nullable Object> extends APair<SS, TS> {
+  static <US extends @Nullable Object, VS extends @Nullable Object> PairSub<US, VS> of(
+      US p1, VS p2) {
+    return new PairSub<US, VS>();
+  }
+}
+
+class PairSubSwitching<SS extends @Nullable Object, TS extends @Nullable Object>
+    extends APair<TS, SS> {
+  static <US extends @Nullable Object, VS extends @Nullable Object> PairSubSwitching<US, VS> ofPSS(
+      US p1, VS p2) {
+    return new PairSubSwitching<US, VS>();
+  }
+}
+
+class Test1<X extends @Nullable Object> {
+  APair<@Nullable X, @Nullable X> test1(@Nullable X p) {
+    return APair.<@Nullable X, @Nullable X>of(p, (X) null);
+  }
+}
+
+class Test2<X extends @Nullable Object> {
+  APair<@Nullable X, @Nullable X> test1(@Nullable X p) {
+    return APair.of(p, (@Nullable X) null);
+  }
+  /*
+  APair<@Nullable X, @Nullable X> test2(@Nullable X p) {
+      // TODO cast: should this X mean the same as above??
+      return APair.of(p, (X) null);
+  }
+  */
+}
+
+class Test3<X extends @Nullable Object> {
+  APair<@NonNull X, @NonNull X> test1(@Nullable X p) {
+    // :: error: (return)
+    return APair.of(p, (X) null);
+  }
+}
+
+class Test4 {
+  APair<@Nullable String, Integer> psi = PairSub.of("Hi", 42);
+  APair<@Nullable String, Integer> psi2 = PairSub.of(null, 42);
+  // :: error: (assignment)
+  APair<String, Integer> psi3 = PairSub.of(null, 42);
+
+  APair<@Nullable String, Integer> psisw = PairSubSwitching.ofPSS(42, null);
+  // :: error: (assignment)
+  APair<String, Integer> psisw2 = PairSubSwitching.ofPSS(42, null);
+}
diff --git a/checker/tests/nullness/generics/MethodTypeVars7.java b/checker/tests/nullness/generics/MethodTypeVars7.java
new file mode 100644
index 0000000..29d28f7
--- /dev/null
+++ b/checker/tests/nullness/generics/MethodTypeVars7.java
@@ -0,0 +1,81 @@
+// Test case based on checker-framework discuss mailing list discussion
+// "Generics problem with @Nullable method parameter" from May 16, 2014
+// https://groups.google.com/d/msg/checker-framework-discuss/-gPGQ7mHjYI/YxCtjjBWx5cJ
+
+import org.checkerframework.checker.nullness.qual.*;
+
+abstract class MethodTypeVars7 {
+
+  abstract <T> T val(@Nullable T value, T defaultValue);
+
+  void tests(@Nullable String t1, @NonNull String t2) {
+    @Nullable String s3 = val(t1, null);
+  }
+
+  <T> T validate(@Nullable T value, T defaultValue) {
+    return value != null && !value.toString().isEmpty() ? value : defaultValue;
+  }
+
+  <T> T validateIf(@Nullable T value, T defaultValue) {
+    if (value != null && !value.toString().isEmpty()) {
+      return value;
+    } else {
+      return defaultValue;
+    }
+  }
+
+  <T> T validate2(@Nullable T value, T defaultValue) {
+    return value == null || value.toString().isEmpty() ? defaultValue : value;
+  }
+
+  <T> T validate3(@Nullable T value, T defaultValue) {
+    return value != null ? value : defaultValue;
+  }
+
+  <T> T validate4(@Nullable T value, T defaultValue) {
+    return value == null ? defaultValue : value;
+  }
+
+  <T> T validatefail(@Nullable T value, T defaultValue) {
+    // :: error: (return)
+    return ((value == null || !value.toString().isEmpty()) ? value : defaultValue);
+  }
+
+  <T> T validate2fail(@Nullable T value, T defaultValue) {
+    // :: error: (return)
+    return ((value != null && value.toString().isEmpty()) ? defaultValue : value);
+  }
+
+  <T> T validate3fail(@Nullable T value3, T defaultValue3) {
+    // :: error: (return)
+    return value3 == null ? value3 : defaultValue3;
+  }
+
+  <T> T validate4fail(@Nullable T value, T defaultValue) {
+    // :: error: (return)
+    return value != null ? defaultValue : value;
+  }
+
+  String test1(@Nullable String t1, @NonNull String t2) {
+    @Nullable String s1 = validate(t1, null);
+    @Nullable String s2 = validate(t2, null);
+    @NonNull String s3 = validate(t1, "N/A");
+    @NonNull String s4 = validate(t2, "N/A");
+    return "[" + s1 + "\t" + s2 + "\t" + s3 + "\t" + s4 + "]";
+  }
+
+  String test2(@Nullable String t1, @NonNull String t2) {
+    @Nullable String s1 = validate(t1, t1);
+    @Nullable String s2 = validate(t2, t1);
+    @NonNull String s3 = validate(t1, t2);
+    @NonNull String s4 = validate(t2, t2);
+    return "[" + s1 + "\t" + s2 + "\t" + s3 + "\t" + s4 + "]";
+  }
+
+  void main(String[] args) {
+    System.out.println("test 1 " + test1("s_1", "s_2"));
+    System.out.println("test 2 " + test2("s_1", "s_2"));
+    System.out.println("test 1 " + test1(null, "s_2"));
+    System.out.println("test 2 " + test2(null, "s_2"));
+  }
+}
diff --git a/checker/tests/nullness/generics/MyMap.java b/checker/tests/nullness/generics/MyMap.java
new file mode 100644
index 0000000..f0313f6
--- /dev/null
+++ b/checker/tests/nullness/generics/MyMap.java
@@ -0,0 +1,19 @@
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+// Test case for Issue 173
+// https://github.com/typetools/checker-framework/issues/173
+public abstract class MyMap<K, V> implements Map<K, V> {
+  @Override
+  // :: error: (contracts.postcondition)
+  public @Nullable V put(K key, V value) {
+    return null;
+  }
+
+  @Override
+  public void putAll(Map<? extends K, ? extends V> map) {
+    for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
+      put(entry.getKey(), entry.getValue());
+    }
+  }
+}
diff --git a/checker/tests/nullness/generics/NullableGeneric.java b/checker/tests/nullness/generics/NullableGeneric.java
new file mode 100644
index 0000000..26eefa1
--- /dev/null
+++ b/checker/tests/nullness/generics/NullableGeneric.java
@@ -0,0 +1,37 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class NullableGeneric<T> {
+
+  public static class NullablePair<T1 extends @Nullable Object, T2 extends @Nullable Object> {
+    public @Nullable T1 a;
+    public @Nullable T2 b;
+    public @NonNull T1 nna;
+    public @NonNull T2 nnb;
+
+    public NullablePair(T1 a, T2 b) {
+      this.a = a;
+      this.b = b;
+      // :: error: (assignment)
+      this.nna = a;
+      // :: error: (assignment)
+      this.nnb = b;
+    }
+  }
+
+  @Nullable T next1 = null, next2 = null;
+
+  private NullablePair<@Nullable T, @Nullable T> return1() {
+    NullablePair<@Nullable T, @Nullable T> result =
+        new NullablePair<@Nullable T, @Nullable T>(next1, null);
+    // setnext1();
+    return result;
+  }
+
+  public static <T3> @NonNull T3 checkNotNull(@Nullable T3 object) {
+    if (object == null) {
+      throw new NullPointerException();
+    } else {
+      return object;
+    }
+  }
+}
diff --git a/checker/tests/nullness/generics/NullableLUB.java b/checker/tests/nullness/generics/NullableLUB.java
new file mode 100644
index 0000000..07ebdff
--- /dev/null
+++ b/checker/tests/nullness/generics/NullableLUB.java
@@ -0,0 +1,33 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+/* Test case that illustrated an unsoundness in the unification of
+ * type variables with non-type variables. The error did not previously
+ * get raised, leading to a missed NPE.
+ */
+public class NullableLUB<T extends @Nullable Object> {
+  // :: error: (initialization.field.uninitialized)
+  T t;
+  @Nullable T nt;
+
+  T m(boolean b, T p) {
+    T r1 = b ? p : null;
+    nt = r1;
+    // :: error: (assignment)
+    t = r1;
+    // :: error: (return)
+    return r1;
+  }
+
+  public static void main(String[] args) {
+    new NullableLUB<@NonNull Object>().m(false, new Object()).toString();
+  }
+
+  T m2(boolean b, T p) {
+    T r1 = b ? null : p;
+    nt = r1;
+    // :: error: (assignment)
+    t = r1;
+    // :: error: (return)
+    return r1;
+  }
+}
diff --git a/checker/tests/nullness/generics/NullnessBound.java b/checker/tests/nullness/generics/NullnessBound.java
new file mode 100644
index 0000000..ed24677
--- /dev/null
+++ b/checker/tests/nullness/generics/NullnessBound.java
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class NullnessBound {
+
+  public void test() {
+    Gen1<@Nullable String> t1 = new Gen1<>();
+    t1.add(null);
+
+    Gen2<@Nullable String> t2 = new Gen2<>();
+    t2.add(null);
+
+    Gen1<@NonNull String> t3;
+    // :: error: (type.argument)
+    Gen2<@NonNull String> t4;
+  }
+
+  class Gen1<E extends @Nullable Object> {
+    public void add(E e) {}
+  }
+
+  class Gen2<@Nullable E> {
+    public void add(E e) {}
+  }
+}
diff --git a/checker/tests/nullness/generics/OptionsTest.java b/checker/tests/nullness/generics/OptionsTest.java
new file mode 100644
index 0000000..6b1b1da
--- /dev/null
+++ b/checker/tests/nullness/generics/OptionsTest.java
@@ -0,0 +1,31 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class OptionsTest {
+
+  class MyAnnotation {}
+
+  // Annotated identically to java.lang.reflect.Field.getAnnotation
+  public static <T1 extends @Nullable MyAnnotation> @Nullable T1 getAnnotation(
+      Class<@NonNull T1> obj) {
+    return null;
+  }
+
+  public static @Nullable MyAnnotation safeGetAnnotationNonGeneric(
+      Class<@NonNull MyAnnotation> annotationClass) {
+    @Nullable MyAnnotation cast = getAnnotation(annotationClass);
+    @Nullable MyAnnotation annotation = cast;
+    return annotation;
+  }
+
+  public static <T2 extends MyAnnotation> @Nullable T2 safeGetAnnotationGeneric(
+      Class<@NonNull T2> annotationClass) {
+    @Nullable T2 cast = getAnnotation(annotationClass);
+    @Nullable T2 annotation = cast;
+    return annotation;
+  }
+}
+
+/* Local Variables: */
+/* compile-command: "javac -processor org.checkerframework.checker.nullness.NullnessChecker OptionsTest.java" */
+/* compile-history: ("javac -processor org.checkerframework.checker.nullness.NullnessChecker OptionsTest.java") */
+/* End: */
diff --git a/checker/tests/nullness/generics/RawTypesGenerics.java b/checker/tests/nullness/generics/RawTypesGenerics.java
new file mode 100644
index 0000000..0ef4187
--- /dev/null
+++ b/checker/tests/nullness/generics/RawTypesGenerics.java
@@ -0,0 +1,26 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class RawTypesGenerics {
+  void m() throws ClassNotFoundException {
+    Class c1 = Class.forName("bla");
+    Class<? extends @Nullable Object> c2 = Class.forName("bla");
+  }
+
+  class Test<X extends Number> {}
+
+  void bar() {
+    // Java will complain about this:
+    // Test x = new Test<Object>();
+
+    // ok
+    Test y = new Test<Integer>();
+
+    // :: error: (type.argument)
+    Test z = new Test<@Nullable Integer>();
+  }
+
+  void m(java.lang.reflect.Constructor<?> c) {
+    Class cls1 = c.getParameterTypes()[0];
+    Class<? extends @Nullable Object> cls2 = c.getParameterTypes()[0];
+  }
+}
diff --git a/checker/tests/nullness/generics/SourceVsJdk.java b/checker/tests/nullness/generics/SourceVsJdk.java
new file mode 100644
index 0000000..415fe6a
--- /dev/null
+++ b/checker/tests/nullness/generics/SourceVsJdk.java
@@ -0,0 +1,14 @@
+// In source code, whether an upper bound is explicit affects what type
+// qualifier is imputed for that upper bound.
+// The JDK should be treated consistently with source code.
+
+// @skip-test Commented out to avoid breaking the build
+
+import java.util.Collections;
+import java.util.Map;
+
+public class SourceVsJdk<K, V> {
+  public Map<K, V> getMap() {
+    return Collections.<K, V>emptyMap();
+  }
+}
diff --git a/checker/tests/nullness/generics/SuperRawness.java b/checker/tests/nullness/generics/SuperRawness.java
new file mode 100644
index 0000000..d506b7a
--- /dev/null
+++ b/checker/tests/nullness/generics/SuperRawness.java
@@ -0,0 +1,12 @@
+import java.util.Arrays;
+import java.util.Set;
+
+public class SuperRawness {
+  // :: warning: [unchecked] Possible heap pollution from parameterized vararg type
+  // java.util.Set<X>
+  static <X> void test(Set<X>... args) {
+    test2(Arrays.asList(args));
+  }
+
+  static <X> void test2(Iterable<Set<X>> args) {}
+}
diff --git a/checker/tests/nullness/generics/TernaryGenerics.java b/checker/tests/nullness/generics/TernaryGenerics.java
new file mode 100644
index 0000000..065da64
--- /dev/null
+++ b/checker/tests/nullness/generics/TernaryGenerics.java
@@ -0,0 +1,48 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class TernaryGenerics {
+  class Generic1<T extends @NonNull Object> {
+    void cond(boolean b, T p) {
+      // :: error: (assignment)
+      @NonNull T r1 = b ? p : null;
+      // :: error: (assignment)
+      @NonNull T r2 = b ? null : p;
+    }
+  }
+
+  class Generic2<T extends @Nullable Object> {
+    void cond(boolean b, T p) {
+      // :: error: (assignment)
+      @NonNull T r1 = b ? p : null;
+      // :: error: (assignment)
+      @NonNull T r2 = b ? null : p;
+    }
+  }
+
+  class Generic3<T> {
+    void cond(boolean b, @Nullable T p) {
+      @Nullable T r1 = b ? p : null;
+      @Nullable T r2 = b ? null : p;
+      // :: error: (assignment)
+      @NonNull T r3 = b ? null : p;
+    }
+  }
+
+  void array(boolean b) {
+    String[] s = b ? new String[] {""} : null;
+    // :: error: (dereference.of.nullable)
+    s.toString();
+  }
+
+  void generic(boolean b, Generic1<String> p) {
+    Generic1<String> s = b ? p : null;
+    // :: error: (dereference.of.nullable)
+    s.toString();
+  }
+
+  void primarray(boolean b) {
+    long[] result = b ? null : new long[10];
+    // :: error: (dereference.of.nullable)
+    result.toString();
+  }
+}
diff --git a/checker/tests/nullness/generics/VarArgsTest.java b/checker/tests/nullness/generics/VarArgsTest.java
new file mode 100644
index 0000000..835ae0c
--- /dev/null
+++ b/checker/tests/nullness/generics/VarArgsTest.java
@@ -0,0 +1,12 @@
+import java.util.Arrays;
+import java.util.Set;
+
+public class VarArgsTest {
+  // :: warning: [unchecked] Possible heap pollution from parameterized vararg type
+  // java.util.Set<? super X>
+  <X> void test(Set<? super X>... args) {
+    Arrays.asList(args);
+  }
+  //  static <X> void test(Set<? super X>... args) { test2(Arrays.asList(args)); }
+  //  static <X> void test2(Iterable<Set<? super X>> args) {}
+}
diff --git a/checker/tests/nullness/generics/WellformedBounds.java b/checker/tests/nullness/generics/WellformedBounds.java
new file mode 100644
index 0000000..152b946
--- /dev/null
+++ b/checker/tests/nullness/generics/WellformedBounds.java
@@ -0,0 +1,39 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+class Param<T extends @NonNull Object> {
+  // Field f needs to be set, because the upper bound is @Initialized
+  // :: error: (initialization.field.uninitialized)
+  T f;
+
+  void foo() {
+    // Valid, because upper bound is @Initialized @NonNull
+    f.toString();
+  }
+}
+
+// :: error: (type.argument)
+class Invalid<S extends Param<@Nullable Object>> {
+  void bar(S s) {
+    s.foo();
+  }
+
+  // :: error: (type.argument)
+  <M extends Param<@Nullable Object>> void foobar(M p) {}
+}
+
+interface ParamI<T extends @NonNull Object> {}
+
+class Invalid2<
+    S extends
+        Number &
+            // :: error: (type.argument)
+            ParamI<@Nullable Object>> {}
+
+class Invalid3 {
+  <
+          M extends
+              Number &
+                  // :: error: (type.argument)
+                  ParamI<@Nullable Object>>
+      void foobar(M p) {}
+}
diff --git a/checker/tests/nullness/generics/WildcardAnnos.java b/checker/tests/nullness/generics/WildcardAnnos.java
new file mode 100644
index 0000000..7f35f29
--- /dev/null
+++ b/checker/tests/nullness/generics/WildcardAnnos.java
@@ -0,0 +1,33 @@
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class WildcardAnnos {
+  // :: error: (bound)
+  @Nullable List<@Nullable ? extends @NonNull Object> l1 = null;
+  @Nullable List<@NonNull ? extends @Nullable Object> l2 = null;
+
+  // The implicit upper bound is Nullable, because the annotation
+  // on the wildcard is propagated. Therefore this type is:
+  // @Nullable List<? super @NonNull Object extends @Nullable Object> l3 = null;
+  @Nullable List<@Nullable ? super @NonNull Object> l3 = null;
+
+  // :: error: (bound)
+  @Nullable List<@NonNull ? super @Nullable Object> l4 = null;
+
+  @Nullable List<? super @Nullable Object> l5 = null;
+
+  @Nullable List<? extends @Nullable Object> inReturn() {
+    return null;
+  }
+
+  void asParam(List<? extends @Nullable Object> p) {}
+
+  // :: error: (conflicting.annos)
+  @Nullable List<@Nullable @NonNull ? extends @Nullable Object> l6 = null;
+  // :: error: (conflicting.annos)
+  @Nullable List<@Nullable @NonNull ? super @NonNull Object> l7 = null;
+  // :: error: (conflicting.annos)
+  @Nullable List<? extends @Nullable @NonNull Object> l8 = null;
+  // :: error: (conflicting.annos)
+  @Nullable List<? super @Nullable @NonNull Object> l9 = null;
+}
diff --git a/checker/tests/nullness/generics/WildcardBoundDefault.java b/checker/tests/nullness/generics/WildcardBoundDefault.java
new file mode 100644
index 0000000..ee6a3fa
--- /dev/null
+++ b/checker/tests/nullness/generics/WildcardBoundDefault.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+class MyGenClass<T extends @Nullable Object> {}
+
+@DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.UPPER_BOUND)
+public class WildcardBoundDefault {
+  void test() {
+    ignore(newInstance());
+  }
+
+  static void ignore(MyGenClass<?>... consumer) {}
+
+  static <T> MyGenClass<T> newInstance() {
+    return new MyGenClass<T>();
+  }
+}
diff --git a/checker/tests/nullness/generics/WildcardOverride.java b/checker/tests/nullness/generics/WildcardOverride.java
new file mode 100644
index 0000000..9023785
--- /dev/null
+++ b/checker/tests/nullness/generics/WildcardOverride.java
@@ -0,0 +1,37 @@
+package nullness.generics;
+
+// see also framework/tests/all-systems/WildcardSuper2
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+interface ToOverride<T> {
+  // For nullness this should default to type @NonNull List<? [ extends @Nullable Object
+  //                                                             super @NonNull  T ]>
+  public abstract int transform(List<? super T> function);
+}
+
+public class WildcardOverride implements ToOverride<Object> {
+  // invalid because the overriden method takes @Nullable args and this one doesn't
+  @Override
+  // :: error: (override.param)
+  public int transform(List<Object> function) {
+    return 0;
+  }
+}
+
+interface ToOverride2<T> {
+  // For nullness this should be typed as
+  // @NonNull List<? [ extends @NonNull Object super T [ extends @Nullable super @NonNull ]>
+  // :: error: (bound)
+  public abstract int transform(List<@NonNull ? super T> function);
+}
+
+class WildcardOverride2 implements ToOverride2<Object> {
+  // valid because the overriden method takes ONLY @NonNull args and this one takes @NonNull args
+  // as well
+  @Override
+  public int transform(List<Object> function) {
+    return 0;
+  }
+}
diff --git a/checker/tests/nullness/generics/WildcardSubtyping.java b/checker/tests/nullness/generics/WildcardSubtyping.java
new file mode 100644
index 0000000..f483283
--- /dev/null
+++ b/checker/tests/nullness/generics/WildcardSubtyping.java
@@ -0,0 +1,66 @@
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.*;
+
+class Utils {
+
+  <A extends @Nullable Object> void test(List<? super A> list, A object) {
+    list.add(object);
+  }
+
+  interface Consumer<A extends @Nullable Object> {
+    public void consume(A object);
+  }
+
+  public static <A extends @Nullable Object> Consumer<A> cast(
+      final Consumer<@Nullable ? super A> consumer) {
+    return new Consumer<A>() {
+      @Override
+      public void consume(A object) {
+        consumer.consume(object);
+      }
+    };
+  }
+
+  public static <A extends @Nullable Object> Consumer<A> getConsumer(
+      Consumer<@Nullable Object> nullConsumer) {
+    return Utils.<A>cast(nullConsumer);
+  }
+
+  Map<String, Set<?>> mss = new HashMap<>();
+
+  Set<Class<? extends Annotation>> foo() {
+    Set<Class<? extends Annotation>> l = new HashSet<>(this.foo());
+    return l;
+  }
+}
+
+class MyGeneric<@NonNull T extends @Nullable Number> {}
+
+class UseMyGeneric {
+  MyGeneric<?> wildcardUnbounded = new MyGeneric<>();
+
+  // :: error: (assignment)
+  MyGeneric<? extends @NonNull Object> wildcardOutsideUB = wildcardUnbounded;
+  MyGeneric<? extends @NonNull Number> wildcardInsideUB = wildcardOutsideUB;
+  // :: error: (assignment)
+  MyGeneric<? extends @NonNull Number> wildcardInsideUB2 = wildcardUnbounded;
+
+  MyGeneric<? extends @Nullable Number> wildcardInsideUBNullable = wildcardOutsideUB;
+}
+
+class MyGenericExactBounds<@NonNull T extends @NonNull Number> {}
+
+class UseMyGenericExactBounds {
+  // :: error: (type.argument)
+  MyGenericExactBounds<? extends @Nullable Object> wildcardOutsideUBError =
+      new MyGenericExactBounds<>();
+  MyGenericExactBounds<? extends @NonNull Object> wildcardOutside = new MyGenericExactBounds<>();
+  MyGenericExactBounds<? extends @NonNull Number> wildcardInsideUB = wildcardOutside;
+
+  MyGenericExactBounds<?> wildcardOutsideUB = wildcardOutside;
+}
diff --git a/checker/tests/nullness/generics/WildcardSubtyping2.java b/checker/tests/nullness/generics/WildcardSubtyping2.java
new file mode 100644
index 0000000..bb3b61a
--- /dev/null
+++ b/checker/tests/nullness/generics/WildcardSubtyping2.java
@@ -0,0 +1,28 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class WildcardSubtyping2 {
+  class MyClass {}
+
+  class MyCloneClass extends MyClass implements Cloneable {}
+
+  class MyGeneric<@NonNull T extends @Nullable MyClass> {}
+
+  class UseMyGeneric {
+    MyGeneric<@NonNull MyCloneClass> nonNull = new MyGeneric<>();
+    MyGeneric<@Nullable MyCloneClass> nullable = new MyGeneric<>();
+
+    MyGeneric<? extends @NonNull Cloneable> interfaceNN = nonNull;
+    MyGeneric<? extends @Nullable Cloneable> interfaceNull = nullable;
+  }
+
+  class MyGenericEB<@NonNull T extends @NonNull MyClass> {}
+
+  class UseMyGenericEB {
+    MyGenericEB<@NonNull MyCloneClass> nonNull = new MyGenericEB<>();
+    // :: error: (type.argument)
+    MyGenericEB<@Nullable MyCloneClass> nullable = new MyGenericEB<>();
+
+    MyGenericEB<? extends @NonNull Cloneable> interfaceNN = nonNull;
+    MyGenericEB<? extends @Nullable Cloneable> interfaceNull = nullable;
+  }
+}
diff --git a/checker/tests/nullness/generics/WildcardSubtypingTypeArray.java b/checker/tests/nullness/generics/WildcardSubtypingTypeArray.java
new file mode 100644
index 0000000..e202a66
--- /dev/null
+++ b/checker/tests/nullness/generics/WildcardSubtypingTypeArray.java
@@ -0,0 +1,10 @@
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class WildcardSubtypingTypeArray<A extends @Nullable Object> {
+  void test(List<? extends A> list) {
+    test2(list.get(0));
+  }
+
+  void test2(A x) {}
+}
diff --git a/checker/tests/nullness/generics/WildcardSuper.java b/checker/tests/nullness/generics/WildcardSuper.java
new file mode 100644
index 0000000..7e0dc55
--- /dev/null
+++ b/checker/tests/nullness/generics/WildcardSuper.java
@@ -0,0 +1,45 @@
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+public class WildcardSuper {
+
+  void testWithSuper(Cell<? super @NonNull String> cell) {
+    // TODO: Address comments.  Since ? is explicitly lower bounded, I have made a judgment that
+    // it should be implicitly upper bounded.
+    // This is valid because the default upper bound is NonNull
+    // :: error: (dereference.of.nullable)
+    cell.get().toString();
+  }
+
+  // TODO: THIS SHOULD JUST ISSUE A WARNING, WHY WOULD PEOPLE WANT TO WRITE CONTRADICTING BOUNDS?
+  void testWithContradiction(Cell<? super @Nullable String> cell) {
+    // This is actually valid, because it's a contradiction, because
+    // the implicit upper bound is NonNull.
+    // We are free to do anything, as the method is not callable.
+    // TODO: test whether all calls of method fail.
+    // :: error: (dereference.of.nullable)
+    cell.get().toString();
+  }
+
+  @DefaultQualifier(Nullable.class)
+  void testWithImplicitNullable(@NonNull Cell<? super @NonNull String> cell) {
+    // :: error: (dereference.of.nullable)
+    cell.get().toString();
+  }
+
+  void testWithExplicitNullable(Cell<@Nullable ? extends @Nullable String> cell) {
+    // :: error: (dereference.of.nullable)
+    cell.get().toString();
+  }
+
+  void testWithDoubleNullable(Cell<@Nullable ? extends @Nullable String> cell) {
+    // :: error: (dereference.of.nullable)
+    cell.get().toString();
+  }
+
+  class Cell<E extends @Nullable Object> {
+    E get() {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/checker/tests/nullness/init/AnonymousInit.java b/checker/tests/nullness/init/AnonymousInit.java
new file mode 100644
index 0000000..984217f
--- /dev/null
+++ b/checker/tests/nullness/init/AnonymousInit.java
@@ -0,0 +1,21 @@
+// Ensure field initialization checks for anonymous
+// classes work.
+public class AnonymousInit {
+  Object o1 =
+      new Object() {
+        // :: error: (initialization.field.uninitialized)
+        Object s;
+
+        public String toString() {
+          return s.toString();
+        }
+      };
+  Object o2 =
+      new Object() {
+        Object s = "hi";
+
+        public String toString() {
+          return s.toString();
+        }
+      };
+}
diff --git a/checker/tests/nullness/init/FieldWithInit.java b/checker/tests/nullness/init/FieldWithInit.java
new file mode 100644
index 0000000..223ac5c
--- /dev/null
+++ b/checker/tests/nullness/init/FieldWithInit.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+
+public class FieldWithInit {
+  Object f = foo();
+
+  Object foo(@UnknownInitialization FieldWithInit this) {
+    return new Object();
+  }
+}
diff --git a/checker/tests/nullness/init/GenericTest12b.java b/checker/tests/nullness/init/GenericTest12b.java
new file mode 100644
index 0000000..f76c266
--- /dev/null
+++ b/checker/tests/nullness/init/GenericTest12b.java
@@ -0,0 +1,22 @@
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class GenericTest12b {
+  class Cell<T1 extends @Nullable Object> {}
+
+  class Node<CONTENT extends @Nullable Object> {
+    public Node(Cell<CONTENT> userObject) {}
+
+    void nodecall(@UnderInitialization Node<CONTENT> this, Cell<CONTENT> userObject) {}
+  }
+
+  class RootNode extends Node<Void> {
+    public RootNode() {
+      super(new Cell<Void>());
+      call(new Cell<Void>());
+      nodecall(new Cell<Void>());
+    }
+
+    void call(@UnderInitialization RootNode this, Cell<Void> userObject) {}
+  }
+}
diff --git a/checker/tests/nullness/init/InstanceOf.java b/checker/tests/nullness/init/InstanceOf.java
new file mode 100644
index 0000000..7affe40
--- /dev/null
+++ b/checker/tests/nullness/init/InstanceOf.java
@@ -0,0 +1,26 @@
+// Test case for pull request 735
+// https://github.com/typetools/checker-framework/pull/735
+
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+
+class PptTopLevel {
+  class Ppt {
+    Object method() {
+      return "";
+    }
+  }
+
+  class OtherPpt extends Ppt {}
+}
+
+public class InstanceOf {
+  void foo(PptTopLevel.@UnknownInitialization(PptTopLevel.class) Ppt ppt) {
+    // :: error: (method.invocation)
+    ppt.method();
+    if (ppt instanceof PptTopLevel.OtherPpt) {
+      PptTopLevel.OtherPpt pslice = (PptTopLevel.OtherPpt) ppt;
+      // :: error: (method.invocation)
+      String samp_str = " s" + pslice.method();
+    }
+  }
+}
diff --git a/checker/tests/nullness/init/Issue1044.java b/checker/tests/nullness/init/Issue1044.java
new file mode 100644
index 0000000..e6f6d7a
--- /dev/null
+++ b/checker/tests/nullness/init/Issue1044.java
@@ -0,0 +1,70 @@
+// Test case for Issue 1044
+// https://github.com/typetools/checker-framework/issues/1044
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1044 {
+  static class Inner1<V> {
+    // :: error: (initialization.field.uninitialized)
+    V f;
+  }
+
+  static class Inner2<@Nullable T extends @Nullable Object> {
+    // :: error: (initialization.field.uninitialized)
+    @NonNull T f;
+  }
+
+  static class Inner3<V> {
+    V f;
+    // :: error: (initialization.fields.uninitialized)
+    Inner3() {}
+  }
+
+  static class Inner4<@Nullable T extends @Nullable Object> {
+    @NonNull T f;
+    // :: error: (initialization.fields.uninitialized)
+    Inner4() {}
+  }
+
+  static class Inner5<V> {
+    @Nullable V f;
+  }
+
+  static class Inner6<@Nullable T extends @Nullable Object> {
+    T f;
+  }
+
+  static class Inner7<V> {
+    @Nullable V f;
+
+    Inner7() {}
+  }
+
+  static class Inner8<@Nullable T extends @Nullable Object> {
+    T f;
+
+    Inner8() {}
+  }
+
+  static class Inner9<V extends Object> {
+    // :: error: (initialization.field.uninitialized)
+    V f;
+  }
+
+  static class Inner10<V extends Object> {
+    V f;
+    // :: error: (initialization.fields.uninitialized)
+    Inner10() {}
+  }
+
+  static class Inner11<V extends Object> {
+    @Nullable V f;
+  }
+
+  static class Inner12<V extends Object> {
+    @Nullable V f;
+
+    Inner12() {}
+  }
+}
diff --git a/checker/tests/nullness/init/Issue1120.java b/checker/tests/nullness/init/Issue1120.java
new file mode 100644
index 0000000..a090b63
--- /dev/null
+++ b/checker/tests/nullness/init/Issue1120.java
@@ -0,0 +1,35 @@
+// Test case for Issue #1120:
+// https://github.com/typetools/checker-framework/issues/1120
+
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+
+class Issue1120Super {
+  Object f = new Object();
+}
+
+final class Issue1120Sub extends Issue1120Super {
+  Object g;
+
+  Issue1120Sub() {
+    this.party();
+    // this is @UnderInitialization(A.class)
+    g = new Object();
+    // this is @Initialized now
+    this.party();
+    this.bar();
+  }
+
+  Issue1120Sub(int i) {
+    // this is @UnderInitialization(A.class)
+    this.party();
+    // :: error: (method.invocation)
+    this.bar();
+    g = new Object();
+  }
+
+  void bar() {
+    g.toString();
+  }
+
+  void party(@UnknownInitialization Issue1120Sub this) {}
+}
diff --git a/checker/tests/nullness/init/Issue1347.java b/checker/tests/nullness/init/Issue1347.java
new file mode 100644
index 0000000..b473f2e
--- /dev/null
+++ b/checker/tests/nullness/init/Issue1347.java
@@ -0,0 +1,24 @@
+// Test case for Issue 1347.
+// https://github.com/typetools/checker-framework/issues/1347
+
+public class Issue1347<T> {
+  T t;
+  T t2;
+  Object o;
+
+  Issue1347(T t) {
+    this(t, 0);
+  }
+
+  Issue1347(T t, int i) {
+    this.t = t;
+    this.t2 = t;
+    this.o = new Object();
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  Issue1347(T t, String s) {
+    this.t = t;
+    this.o = new Object();
+  }
+}
diff --git a/checker/tests/nullness/init/Issue3407.java b/checker/tests/nullness/init/Issue3407.java
new file mode 100644
index 0000000..a3ad600
--- /dev/null
+++ b/checker/tests/nullness/init/Issue3407.java
@@ -0,0 +1,20 @@
+// @below-java9-jdk-skip-test
+public class Issue3407 {
+  final String foo;
+
+  String getFoo() {
+    return foo;
+  }
+
+  Issue3407() {
+    var anon =
+        new Object() {
+          String bar() {
+            // :: error: (method.invocation)
+            return Issue3407.this.getFoo().substring(1);
+          }
+        };
+    anon.bar(); // / WHOOPS... NPE, `getFoo()` returns `foo` which is still null
+    this.foo = "Hello world";
+  }
+}
diff --git a/checker/tests/nullness/init/Issue408Init.java b/checker/tests/nullness/init/Issue408Init.java
new file mode 100644
index 0000000..bfa60a2
--- /dev/null
+++ b/checker/tests/nullness/init/Issue408Init.java
@@ -0,0 +1,30 @@
+// Test case for Issue 408:
+// https://github.com/typetools/checker-framework/issues/408
+
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+
+public class Issue408Init {
+  static class Bar {
+    Bar() {
+      doFoo();
+    }
+
+    String doFoo(@UnderInitialization Bar this) {
+      return "";
+    }
+  }
+
+  static class Baz extends Bar {
+    String myString = "hello";
+
+    @Override
+    String doFoo(@UnderInitialization Baz this) {
+      // :: error: (dereference.of.nullable)
+      return myString.toLowerCase();
+    }
+  }
+
+  public static void main(String[] args) {
+    new Baz();
+  }
+}
diff --git a/checker/tests/nullness/init/Issue409.java b/checker/tests/nullness/init/Issue409.java
new file mode 100644
index 0000000..ccfdf25
--- /dev/null
+++ b/checker/tests/nullness/init/Issue409.java
@@ -0,0 +1,34 @@
+// Test case for Issue #409
+// https://github.com/typetools/checker-framework/issues/409
+
+// @skip-test until the issue is fixed
+
+public class Issue409 {
+  public static void main(String[] args) {
+    new Callback();
+  }
+}
+
+class Callback {
+
+  class MyProc {
+    public void call() {
+      doStuff();
+    }
+  }
+
+  String foo;
+
+  Callback() {
+    MyProc p = new MyProc();
+    // This call is illegal.  It passes an @UnderInitialization outer this, but MyProc.call is
+    // declared to take an @Initialized outer this (whith is the default type).
+    // :: error: (method.invocation)
+    p.call();
+    foo = "hello";
+  }
+
+  void doStuff() {
+    System.out.println(foo.toLowerCase()); // this line throws a NullPointerException at run time
+  }
+}
diff --git a/checker/tests/nullness/init/Issue4567.java b/checker/tests/nullness/init/Issue4567.java
new file mode 100644
index 0000000..ecdb2f2
--- /dev/null
+++ b/checker/tests/nullness/init/Issue4567.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue4567 {
+
+  public Issue4567() {
+    this(null);
+  }
+
+  protected Issue4567(final @UnderInitialization @Nullable Object variableScope) {}
+}
diff --git a/checker/tests/nullness/init/Issue779.java b/checker/tests/nullness/init/Issue779.java
new file mode 100644
index 0000000..97e49c2
--- /dev/null
+++ b/checker/tests/nullness/init/Issue779.java
@@ -0,0 +1,32 @@
+// Test case for Issue #779
+// https://github.com/typetools/checker-framework/issues/779
+
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+class A {
+  Object g = new Object();
+
+  A() {
+    foo();
+  }
+
+  void foo(@UnderInitialization(A.class) A this) {
+    System.out.println("foo A " + g.toString());
+  }
+}
+
+class B extends A {
+  Object f = new Object();
+
+  void foo(@UnderInitialization(A.class) B this) {
+    // :: error: (dereference.of.nullable)
+    System.out.println("foo B " + this.f.toString());
+  }
+}
+
+public class Issue779 {
+  public static void main(String[] args) {
+    new B();
+  }
+}
diff --git a/checker/tests/nullness/init/Issue904.java b/checker/tests/nullness/init/Issue904.java
new file mode 100644
index 0000000..f8c2806
--- /dev/null
+++ b/checker/tests/nullness/init/Issue904.java
@@ -0,0 +1,23 @@
+// Test case for Issue 904:
+// https://github.com/typetools/checker-framework/issues/904
+
+public class Issue904 {
+  final Object mBar;
+  final Runnable mRunnable =
+      new Runnable() {
+        @Override
+        public void run() {
+          // :: error: (dereference.of.nullable)
+          mBar.toString();
+        }
+      };
+
+  public Issue904() {
+    mRunnable.run();
+    mBar = "";
+  }
+
+  public static void main(String[] args) {
+    new Issue904();
+  }
+}
diff --git a/checker/tests/nullness/init/RawMethodInvocation.java b/checker/tests/nullness/init/RawMethodInvocation.java
new file mode 100644
index 0000000..910953c
--- /dev/null
+++ b/checker/tests/nullness/init/RawMethodInvocation.java
@@ -0,0 +1,49 @@
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+
+@org.checkerframework.framework.qual.DefaultQualifier(Nullable.class)
+public class RawMethodInvocation {
+  @NonNull String a;
+  @NonNull String b;
+
+  RawMethodInvocation(boolean constructor_inits_a) {
+    a = "";
+    init_b();
+  }
+
+  @EnsuresNonNull("b")
+  void init_b(@UnknownInitialization RawMethodInvocation this) {
+    b = "";
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  RawMethodInvocation(Byte constructor_inits_b) {
+    init_b();
+  }
+
+  // :: error: (initialization.fields.uninitialized)
+  RawMethodInvocation(byte constructor_inits_b) {
+    b = "";
+    init_b();
+  }
+
+  RawMethodInvocation(int constructor_inits_none) {
+    init_ab();
+  }
+
+  @EnsuresNonNull({"a", "b"})
+  void init_ab(@UnknownInitialization RawMethodInvocation this) {
+    a = "";
+    b = "";
+  }
+
+  RawMethodInvocation(long constructor_escapes_raw) {
+    a = "";
+    // :: error: (method.invocation)
+    nonRawMethod();
+    b = "";
+  }
+
+  void nonRawMethod() {}
+}
diff --git a/checker/tests/nullness/init/RawTypesInit.java b/checker/tests/nullness/init/RawTypesInit.java
new file mode 100644
index 0000000..fffd254
--- /dev/null
+++ b/checker/tests/nullness/init/RawTypesInit.java
@@ -0,0 +1,282 @@
+// Note that this file is a near duplicate in /nullness and /nullness-uninit
+
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public class RawTypesInit {
+
+  class Bad {
+    @NonNull String field;
+
+    public Bad() {
+      // :: error: (method.invocation)
+      this.init(); // error
+      // :: error: (method.invocation)
+      init(); // error
+
+      this.field = "field"; // valid
+      // :: error: (assignment)
+      this.field = null; // error
+      field = "field"; // valid
+      // :: error: (assignment)
+      field = null; // error
+    }
+
+    void init() {
+      output(this.field.length()); // valid
+    }
+  }
+
+  class A {
+    @NonNull String field;
+
+    public A() {
+      this.field = "field"; // valid
+      field = "field"; // valid
+      this.init(); // valid
+      init(); // valid
+    }
+
+    public void init(@UnknownInitialization A this) {
+      // :: error: (dereference.of.nullable)
+      output(this.field.length());
+    }
+
+    public void initExpl2(@UnknownInitialization A this) {
+      // :: error: (argument)
+      output(this.field);
+    }
+
+    public void initImpl1(@UnknownInitialization A this) {
+      // :: error: (dereference.of.nullable)
+      output(field.length());
+    }
+
+    public void initImpl2(@UnknownInitialization A this) {
+      // :: error: (argument)
+      output(field);
+    }
+  }
+
+  class B extends A {
+    @NonNull String otherField;
+
+    public B() {
+      super();
+      // :: error: (assignment)
+      this.otherField = null; // error
+      this.otherField = "otherField"; // valid
+    }
+
+    @Override
+    public void init(@UnknownInitialization B this) {
+      // :: error: (dereference.of.nullable)
+      output(this.field.length()); // error (TODO: substitution)
+      super.init(); // valid
+    }
+
+    public void initImpl1(@UnknownInitialization B this) {
+      // :: error: (dereference.of.nullable)
+      output(field.length()); // error (TODO: substitution)
+    }
+
+    public void initExpl2(@UnknownInitialization B this) {
+      // :: error: (dereference.of.nullable)
+      output(this.otherField.length()); // error
+    }
+
+    public void initImpl2(@UnknownInitialization B this) {
+      // :: error: (dereference.of.nullable)
+      output(otherField.length()); // error
+    }
+
+    void other() {
+      init(); // valid
+      this.init(); // valid
+    }
+
+    void otherRaw(@UnknownInitialization B this) {
+      init(); // valid
+      this.init(); // valid
+    }
+  }
+
+  class C extends B {
+
+    // :: error: (initialization.field.uninitialized)
+    @NonNull String[] strings;
+
+    @Override
+    public void init(@UnknownInitialization C this) {
+      // :: error: (dereference.of.nullable)
+      output(this.strings.length); // error
+      System.out.println(); // valid
+    }
+  }
+
+  // To test whether the argument is @NonNull and @Initialized
+  static void output(@NonNull Object o) {}
+
+  class D extends C {
+    @Override
+    public void init(@UnknownInitialization D this) {
+      this.field = "s";
+      output(this.field.length());
+    }
+  }
+
+  class MyTest {
+    Integer i;
+
+    MyTest(int i) {
+      this.i = i;
+    }
+
+    void myTest(@UnknownInitialization MyTest this) {
+      // :: error: (unboxing.of.nullable)
+      i = i + 1;
+    }
+  }
+
+  class AllFieldsInitialized {
+    Integer elapsedMillis = 0;
+    Integer startTime = 0;
+
+    public AllFieldsInitialized() {
+      // :: error: (method.invocation)
+      nonRawMethod();
+    }
+
+    public void nonRawMethod() {}
+  }
+
+  class AFSIICell {
+    // :: error: (initialization.field.uninitialized)
+    AllFieldsSetInInitializer afsii;
+  }
+
+  class AllFieldsSetInInitializer {
+    Integer elapsedMillis;
+    Integer startTime;
+
+    public AllFieldsSetInInitializer() {
+      elapsedMillis = 0;
+      // :: error: (method.invocation)
+      nonRawMethod(); // error
+      startTime = 0;
+      // :: error: (method.invocation)
+      nonRawMethod(); // error
+      // :: error: (initialization.field.write.initialized)
+      new AFSIICell().afsii = this;
+    }
+
+    // :: error: (initialization.fields.uninitialized)
+    public AllFieldsSetInInitializer(boolean b) {
+      // :: error: (method.invocation)
+      nonRawMethod(); // error
+    }
+
+    public void nonRawMethod() {}
+  }
+
+  class ConstructorInvocations {
+    Integer v;
+
+    public ConstructorInvocations(int v) {
+      this.v = v;
+    }
+
+    public ConstructorInvocations() {
+      this(0);
+      // :: error: (method.invocation)
+      nonRawMethod(); // invalid
+    }
+
+    public void nonRawMethod() {}
+  }
+
+  class MethodAccess {
+    public MethodAccess() {
+      @NonNull String s = string();
+    }
+
+    public @NonNull String string(@UnknownInitialization MethodAccess this) {
+      return "nonnull";
+    }
+  }
+
+  void cast(@UnknownInitialization Object... args) {
+
+    @SuppressWarnings("rawtypes")
+    // :: error: (assignment)
+    Object[] argsNonRaw1 = args;
+
+    @SuppressWarnings("cast")
+    Object[] argsNonRaw2 = (Object[]) args;
+  }
+
+  class RawAfterConstructorBad {
+    Object o;
+    // :: error: (initialization.fields.uninitialized)
+    RawAfterConstructorBad() {}
+  }
+
+  class RawAfterConstructorOK1 {
+    @Nullable Object o;
+
+    RawAfterConstructorOK1() {}
+  }
+
+  class RawAfterConstructorOK2 {
+    Integer a;
+    // :: error: (initialization.fields.uninitialized)
+    RawAfterConstructorOK2() {}
+  }
+
+  // TODO: reinstate.  This shows desired features, for initialization in
+  // a helper method rather than in the constructor.
+  class InitInHelperMethod {
+    Integer a;
+    Integer b;
+
+    InitInHelperMethod(short constructor_inits_ab) {
+      a = 1;
+      b = 1;
+      // :: error: (method.invocation)
+      nonRawMethod();
+    }
+
+    InitInHelperMethod(boolean constructor_inits_a) {
+      a = 1;
+      init_b();
+      // :: error: (method.invocation)
+      nonRawMethod();
+    }
+
+    @RequiresNonNull("a")
+    @EnsuresNonNull("b")
+    void init_b(@UnknownInitialization InitInHelperMethod this) {
+      b = 2;
+      // :: error: (method.invocation)
+      nonRawMethod();
+    }
+
+    InitInHelperMethod(int constructor_inits_none) {
+      init_ab();
+      // :: error: (method.invocation)
+      nonRawMethod();
+    }
+
+    @EnsuresNonNull({"a", "b"})
+    void init_ab(@UnknownInitialization InitInHelperMethod this) {
+      a = 1;
+      b = 2;
+      // :: error: (method.invocation)
+      nonRawMethod();
+    }
+
+    void nonRawMethod() {}
+  }
+}
diff --git a/checker/tests/nullness/init/StaticInit.java b/checker/tests/nullness/init/StaticInit.java
new file mode 100644
index 0000000..01827e0
--- /dev/null
+++ b/checker/tests/nullness/init/StaticInit.java
@@ -0,0 +1,12 @@
+// Test case for Issue 353:
+// https://github.com/typetools/checker-framework/issues/353
+// @skip-test
+
+public class StaticInit {
+
+  static String a;
+
+  static {
+    a.toString();
+  }
+}
diff --git a/checker/tests/nullness/init/Uninit.java b/checker/tests/nullness/init/Uninit.java
new file mode 100644
index 0000000..3ac1787
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit.java
@@ -0,0 +1,4 @@
+public class Uninit {
+  // :: error: (initialization.field.uninitialized)
+  Object a;
+}
diff --git a/checker/tests/nullness/init/Uninit10.java b/checker/tests/nullness/init/Uninit10.java
new file mode 100644
index 0000000..8179491
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit10.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Uninit10 {
+
+  @NonNull String[] strings;
+
+  // :: error: (initialization.fields.uninitialized)
+  Uninit10() {}
+
+  public class Inner {
+
+    @NonNull String[] stringsInner;
+
+    // :: error: (initialization.fields.uninitialized)
+    Inner() {}
+  }
+}
diff --git a/checker/tests/nullness/init/Uninit11.java b/checker/tests/nullness/init/Uninit11.java
new file mode 100644
index 0000000..fa0f1d8
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit11.java
@@ -0,0 +1,25 @@
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.Unused;
+
+@SubtypeOf({})
+@Target(ElementType.TYPE_USE)
+@interface DoesNotUseF {}
+
+public class Uninit11 {
+
+  @Unused(when = DoesNotUseF.class)
+  public Object f;
+
+  // parameter disambiguate_overloads is just to distinguish the overloaded constructors
+  public @DoesNotUseF Uninit11(int disambiguate_overloads) {}
+
+  // :: error: (initialization.fields.uninitialized)
+  public Uninit11(boolean disambiguate_overloads) {}
+
+  public Uninit11(long x) {
+    f = new Object();
+  }
+}
diff --git a/checker/tests/nullness/init/Uninit12.java b/checker/tests/nullness/init/Uninit12.java
new file mode 100644
index 0000000..5d30e1f
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit12.java
@@ -0,0 +1,33 @@
+// This is a test case for issue #105:
+// https://github.com/typetools/checker-framework/issues/105
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Uninit12 {
+
+  // :: error: (initialization.static.field.uninitialized)
+  static Object f;
+
+  public Uninit12() {
+    f.toString();
+  }
+
+  static Object g = new Object();
+
+  static Object h;
+
+  static {
+    h = new Object();
+  }
+}
+
+class Uninit12_OK {
+
+  static Object g = new Object();
+
+  static Object h;
+
+  static {
+    h = new Object();
+  }
+}
diff --git a/checker/tests/nullness/init/Uninit13.java b/checker/tests/nullness/init/Uninit13.java
new file mode 100644
index 0000000..27678e8
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit13.java
@@ -0,0 +1,9 @@
+public class Uninit13 {
+  {
+    x = 1;
+    o = new Object();
+  }
+
+  int x;
+  Object o;
+}
diff --git a/checker/tests/nullness/init/Uninit14.java b/checker/tests/nullness/init/Uninit14.java
new file mode 100644
index 0000000..b6fc213
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit14.java
@@ -0,0 +1,13 @@
+// Test case for Issue 144 (now fixed):
+// https://github.com/typetools/checker-framework/issues/144
+public class Uninit14 {
+  private final Object o;
+
+  {
+    try {
+      o = new Object();
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/checker/tests/nullness/init/Uninit2.java b/checker/tests/nullness/init/Uninit2.java
new file mode 100644
index 0000000..4f68bd4
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit2.java
@@ -0,0 +1,7 @@
+public class Uninit2 {
+  Object a;
+
+  Uninit2() {
+    a = new Object();
+  }
+}
diff --git a/checker/tests/nullness/init/Uninit3.java b/checker/tests/nullness/init/Uninit3.java
new file mode 100644
index 0000000..3b23d99
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit3.java
@@ -0,0 +1,3 @@
+public class Uninit3 {
+  Object a = new Object();
+}
diff --git a/checker/tests/nullness/init/Uninit4.java b/checker/tests/nullness/init/Uninit4.java
new file mode 100644
index 0000000..45fcb9b
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit4.java
@@ -0,0 +1,36 @@
+public class Uninit4 {
+
+  class Mam {
+    Object a = new Object();
+  }
+
+  class BadSon {
+    // :: error: (initialization.field.uninitialized)
+    Object b;
+  }
+
+  class GoodSon {
+    Object b = new Object();
+  }
+
+  class WeirdSon {
+    Object b;
+
+    // :: error: (initialization.fields.uninitialized)
+    WeirdSon() {
+      super();
+    }
+  }
+
+  class Daughter {
+    Object b;
+
+    // :: error: (initialization.fields.uninitialized)
+    Daughter() {}
+
+    Daughter(Object val) {
+      this();
+      b = val;
+    }
+  }
+}
diff --git a/checker/tests/nullness/init/Uninit5.java b/checker/tests/nullness/init/Uninit5.java
new file mode 100644
index 0000000..9a2c078
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit5.java
@@ -0,0 +1,4 @@
+public class Uninit5 {
+  // :: error: (initialization.field.uninitialized)
+  String x;
+}
diff --git a/checker/tests/nullness/init/Uninit6.java b/checker/tests/nullness/init/Uninit6.java
new file mode 100644
index 0000000..7ca5094
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit6.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Uninit6 {
+  // Failure to initialize these fields does not directly compromise the
+  // guarantee of no null pointer errors.
+  @MonotonicNonNull Object f;
+  @Nullable Object g;
+
+  Uninit6() {}
+}
diff --git a/checker/tests/nullness/init/Uninit7.java b/checker/tests/nullness/init/Uninit7.java
new file mode 100644
index 0000000..da19da8
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit7.java
@@ -0,0 +1,7 @@
+public class Uninit7 {
+  Object f;
+
+  Uninit7() {
+    throw new Error();
+  }
+}
diff --git a/checker/tests/nullness/init/Uninit8.java b/checker/tests/nullness/init/Uninit8.java
new file mode 100644
index 0000000..f19f310
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit8.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.initialization.qual.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Uninit8 {
+
+  Object f;
+
+  Uninit8() {
+    setFields();
+    f.toString();
+  }
+
+  @EnsuresNonNull("f")
+  void setFields(@UnknownInitialization Uninit8 this) {
+    f = new Object();
+  }
+}
diff --git a/checker/tests/nullness/init/Uninit9.java b/checker/tests/nullness/init/Uninit9.java
new file mode 100644
index 0000000..f94971c
--- /dev/null
+++ b/checker/tests/nullness/init/Uninit9.java
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+public class Uninit9 {
+  public Object f;
+
+  Uninit9() {
+    f = new Object();
+  }
+}
+
+class Uninit9Sub extends Uninit9 {
+  Uninit9Sub() {
+    super();
+    fIsSetOnEntry();
+  }
+
+  @RequiresNonNull("f")
+  void fIsSetOnEntry(@UnknownInitialization Uninit9Sub this) {}
+}
diff --git a/checker/tests/nullness/java-unsound/Figure1.java b/checker/tests/nullness/java-unsound/Figure1.java
new file mode 100644
index 0000000..38a62f3
--- /dev/null
+++ b/checker/tests/nullness/java-unsound/Figure1.java
@@ -0,0 +1,23 @@
+// Unsound only in Java 8, Java 9+ already gives an error
+// @skip-test no need to test for the javac error.
+
+public class Figure1 {
+  static class Constrain<A, B extends A> {}
+
+  static class Bind<A> {
+    <B extends A> A upcast(Constrain<A, B> constrain, B b) {
+      return b;
+    }
+  }
+
+  static <T, U> U coerce(T t) {
+    Constrain<U, ? super T> constrain = null;
+    Bind<U> bind = new Bind<U>();
+    // :: error: method upcast in class Figure1.Bind<A> cannot be applied to given types;
+    return bind.upcast(constrain, t);
+  }
+
+  public static void main(String[] args) {
+    String zero = Figure1.<Integer, String>coerce(0);
+  }
+}
diff --git a/checker/tests/nullness/java-unsound/Figure3.java b/checker/tests/nullness/java-unsound/Figure3.java
new file mode 100644
index 0000000..190a694
--- /dev/null
+++ b/checker/tests/nullness/java-unsound/Figure3.java
@@ -0,0 +1,37 @@
+public class Figure3 {
+  static class Type<A> {
+    class Constraint<B extends A> extends Type<B> {}
+
+    <B> Constraint<? super B> bad() {
+      // :: error: (return)
+      return null;
+    }
+
+    <B> A coerce(B b) {
+      return pair(this.<B>bad(), b).value;
+    }
+  }
+
+  static class Sum<T> {
+    Type<T> type;
+    T value;
+
+    Sum(Type<T> t, T v) {
+      type = t;
+      value = v;
+    }
+  }
+
+  static <T> Sum<T> pair(Type<T> type, T value) {
+    return new Sum<T>(type, value);
+  }
+
+  static <T, U> U coerce(T t) {
+    Type<U> type = new Type<U>();
+    return type.<T>coerce(t);
+  }
+
+  public static void main(String[] args) {
+    String zero = Figure3.<Integer, String>coerce(0);
+  }
+}
diff --git a/checker/tests/nullness/java-unsound/Figure3NC.java b/checker/tests/nullness/java-unsound/Figure3NC.java
new file mode 100644
index 0000000..f579d79
--- /dev/null
+++ b/checker/tests/nullness/java-unsound/Figure3NC.java
@@ -0,0 +1,40 @@
+// Passes the Nullness Checker but fails with a ClassCastException!
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Figure3NC {
+  static class Type<A> {
+    class Constraint<B extends A> extends Type<B> {}
+
+    <B> @Nullable Constraint<? super B> bad() {
+      return null;
+    }
+
+    <B> A coerce(B b) {
+      return pair(this.<B>bad(), b).value;
+    }
+  }
+
+  static class Sum<T> {
+    @Nullable Type<T> type;
+    T value;
+
+    Sum(@Nullable Type<T> t, T v) {
+      type = t;
+      value = v;
+    }
+  }
+
+  static <T> Sum<T> pair(@Nullable Type<T> type, T value) {
+    return new Sum<T>(type, value);
+  }
+
+  static <T, U> U coerce(T t) {
+    Type<U> type = new Type<U>();
+    return type.<T>coerce(t);
+  }
+
+  public static void main(String[] args) {
+    String zero = Figure3NC.<Integer, String>coerce(0);
+  }
+}
diff --git a/checker/tests/nullness/java-unsound/Figure4.java b/checker/tests/nullness/java-unsound/Figure4.java
new file mode 100644
index 0000000..efb885e
--- /dev/null
+++ b/checker/tests/nullness/java-unsound/Figure4.java
@@ -0,0 +1,20 @@
+// Unsound only in Java 8, Java 9+ already gives an error
+// @skip-test no need to test for the javac error.
+
+public class Figure4 {
+  static class Constrain<A, B extends A> {}
+
+  static <A, B extends A> A upcast(Constrain<A, B> constrain, B b) {
+    return b;
+  }
+
+  static <T, U> U coerce(T t) {
+    Constrain<U, ? super T> constrain = null;
+    // :: error: method upcast in class Figure4 cannot be applied to given types;
+    return upcast(constrain, t);
+  }
+
+  public static void main(String[] args) {
+    String zero = coerce(0);
+  }
+}
diff --git a/checker/tests/nullness/java-unsound/Figure6.java b/checker/tests/nullness/java-unsound/Figure6.java
new file mode 100644
index 0000000..2399a96
--- /dev/null
+++ b/checker/tests/nullness/java-unsound/Figure6.java
@@ -0,0 +1,26 @@
+public class Figure6<Ignore> {
+  static class Bind<A> {
+    class Curry<B extends A> {
+      A curry(B b) {
+        return b;
+      }
+    }
+
+    <B extends A> Curry<B> upcast(Constraint<B> constraint) {
+      return new Curry<B>();
+    }
+
+    class Constraint<B extends A> {}
+
+    <B> A coerce(B t) {
+      Constraint<? super B> constraint = null;
+      // :: error: (argument)
+      return upcast(constraint).curry(t);
+    }
+  }
+
+  public static void main(String[] args) {
+    Bind<String> bind = new Bind<String>();
+    String zero = bind.<Integer>coerce(0);
+  }
+}
diff --git a/checker/tests/nullness/java-unsound/Figure6NC.java b/checker/tests/nullness/java-unsound/Figure6NC.java
new file mode 100644
index 0000000..a6047ed
--- /dev/null
+++ b/checker/tests/nullness/java-unsound/Figure6NC.java
@@ -0,0 +1,29 @@
+// Passes the Nullness Checker but fails with a ClassCastException!
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Figure6NC<Ignore> {
+  static class Bind<A> {
+    class Curry<B extends A> {
+      A curry(B b) {
+        return b;
+      }
+    }
+
+    <B extends A> Curry<B> upcast(@Nullable Constraint<B> constraint) {
+      return new Curry<B>();
+    }
+
+    class Constraint<B extends A> {}
+
+    <B> A coerce(B t) {
+      Constraint<? super B> constraint = null;
+      return upcast(constraint).curry(t);
+    }
+  }
+
+  public static void main(String[] args) {
+    Bind<String> bind = new Bind<String>();
+    String zero = bind.<Integer>coerce(0);
+  }
+}
diff --git a/checker/tests/nullness/java-unsound/Figure7.java b/checker/tests/nullness/java-unsound/Figure7.java
new file mode 100644
index 0000000..e75b05d
--- /dev/null
+++ b/checker/tests/nullness/java-unsound/Figure7.java
@@ -0,0 +1,31 @@
+// Unsound only in Java 8, Java 9+ already gives an error
+// @skip-test no need to test for the javac error.
+
+public class Figure7<T, U> {
+  class Constrain<B extends U> {}
+
+  final Constrain<? super T> constrain;
+  final U u;
+
+  Figure7(T t) {
+    u = coerce(t);
+    constrain = getConstrain();
+  }
+
+  <B extends U> U upcast(Constrain<B> constrain, B b) {
+    return b;
+  }
+
+  U coerce(T t) {
+    // :: error: method upcast in class Figure7<T,U> cannot be applied to given types;
+    return upcast(constrain, t);
+  }
+
+  Constrain<? super T> getConstrain() {
+    return constrain;
+  }
+
+  public static void main(String[] args) {
+    String zero = new Figure7<Integer, String>(0).u;
+  }
+}
diff --git a/checker/tests/nullness/java-unsound/README b/checker/tests/nullness/java-unsound/README
new file mode 100644
index 0000000..61c6fc9
--- /dev/null
+++ b/checker/tests/nullness/java-unsound/README
@@ -0,0 +1,9 @@
+This directory discusses the examples from the paper
+"Java and Scala's Type Systems are Unsound - The Existential Crisis of Null Pointers"
+by Amin and Tate, OOPSLA 2016.
+
+The un-annotated example programs all get rejected either by a Java 9+ javac or by the
+Nullness Checker.
+
+Figure3NC and Figure6NC are variations of the examples with nullness annotations.
+Those versions pass the Nullness Checker, but result in ClassCastExceptions!
diff --git a/checker/tests/nullness/java8/DefaultMethods.java b/checker/tests/nullness/java8/DefaultMethods.java
new file mode 100644
index 0000000..80f9add
--- /dev/null
+++ b/checker/tests/nullness/java8/DefaultMethods.java
@@ -0,0 +1,15 @@
+interface DefaultMethods {
+
+  default void method(String param) {
+    // :: error: (assignment)
+    param = null;
+
+    String s = null;
+    // :: error: (dereference.of.nullable)
+    s.toString();
+
+    // Ensure dataflow is running
+    s = "";
+    s.toString();
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue1000.java b/checker/tests/nullness/java8/Issue1000.java
new file mode 100644
index 0000000..6e0ad7e
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue1000.java
@@ -0,0 +1,18 @@
+// Test case for issue #1000:
+// https://github.com/typetools/checker-framework/issues/1000
+
+import java.util.Optional;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1000 {
+  void illegalInstantiation(Optional<@Nullable String> arg) {}
+
+  String orElseAppliedToNonNull(Optional<String> opt) {
+    return opt.orElse("");
+  }
+
+  String orElseAppliedToNullable(Optional<String> opt) {
+    // :: error: (return)
+    return opt.orElse(null);
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue1046Java8.java b/checker/tests/nullness/java8/Issue1046Java8.java
new file mode 100644
index 0000000..9a525c0
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue1046Java8.java
@@ -0,0 +1,36 @@
+// Test case for Issue 1046:
+// https://github.com/typetools/checker-framework/issues/1046
+// Additonal test case: checker/tests/nullness/Issue1046.java
+
+import java.util.List;
+import java.util.function.Function;
+import org.checkerframework.checker.nullness.qual.UnknownKeyFor;
+
+public class Issue1046Java8 {
+  interface EnumMarker {}
+
+  enum MyEnum implements EnumMarker {
+    A,
+    B;
+  }
+
+  static class NS2Lists {
+    @SuppressWarnings("nullness")
+    static <F, T> List<T> transform(List<F> p, Function<? super F, ? extends T> q) {
+      return null;
+    }
+
+    static <F, T> List<F> transform2(List<F> p, Function<? super F, ? extends T> q) {
+      return p;
+    }
+  }
+
+  abstract class NotSubtype2 {
+    void test(List<MyEnum> p) {
+      NS2Lists.transform2(p, foo());
+      NS2Lists.transform(p, foo());
+    }
+
+    abstract Function<? super @UnknownKeyFor EnumMarker, Number> foo();
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue1098.java b/checker/tests/nullness/java8/Issue1098.java
new file mode 100644
index 0000000..da8f891
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue1098.java
@@ -0,0 +1,18 @@
+// Test case for Issue 1098:
+// https://github.com/typetools/checker-framework/issues/1098
+
+import java.util.Optional;
+
+public class Issue1098 {
+  <T> void opt(Optional<T> p1, T p2) {}
+
+  <T> void cls(Class<T> p1, T p2) {}
+
+  void use() {
+    opt(Optional.empty(), null);
+    // TODO: false positive, because type argument inference does not account for @Covariant.
+    // See https://github.com/typetools/checker-framework/issues/979.
+    // :: error: (argument)
+    cls(this.getClass(), null);
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue1098NoJdk.java b/checker/tests/nullness/java8/Issue1098NoJdk.java
new file mode 100644
index 0000000..ea9b262
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue1098NoJdk.java
@@ -0,0 +1,20 @@
+// Test case for Issue 1098:
+// https://github.com/typetools/checker-framework/issues/1098
+
+@SuppressWarnings({"nullness", "initialization.fields.uninitialized"})
+class MyObject {
+  Class<?> getMyClass() {
+    return null;
+  }
+}
+
+class Issue1098NoJdk {
+  <T> void cls2(Class<T> p1, T p2) {}
+
+  void use2(MyObject ths) {
+    // TODO: false positive, because type argument inference does not account for @Covariant.
+    // See https://github.com/typetools/checker-framework/issues/979.
+    // :: error: (argument)
+    cls2(ths.getMyClass(), null);
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue1633.java b/checker/tests/nullness/java8/Issue1633.java
new file mode 100644
index 0000000..f06bcba
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue1633.java
@@ -0,0 +1,126 @@
+// Test case for Issue 1633:
+// https://github.com/typetools/checker-framework/issues/1633
+
+import java.util.function.Supplier;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.Covariant;
+
+public class Issue1633 {
+
+  // supplyNullable is a supplier that may return null.
+  // supplyNonNull is a supplier that does not return null.
+
+  void foo1(Optional1633<String> o, Supplier<@Nullable String> supplyNullable) {
+    // :: error: (argument)
+    @Nullable String str = o.orElseGetUnannotated(supplyNullable);
+  }
+
+  void foo2(Optional1633<String> o, Supplier<@Nullable String> supplyNullable) {
+    @Nullable String str1 = o.orElseGetNullable(supplyNullable);
+  }
+
+  void foo2nw(Optional1633<String> o, Supplier<@Nullable String> supplyNullable) {
+    @Nullable String str1 = o.orElseGetNullableNoWildcard(supplyNullable);
+  }
+
+  void foo3(Optional1633<String> o, Supplier<@Nullable String> supplyNullable) {
+    // :: error: (argument)
+    @Nullable String str2 = o.orElseGetNonNull(supplyNullable);
+  }
+
+  void foo4(Optional1633<String> o, Supplier<@Nullable String> supplyNullable) {
+    @Nullable String str3 = o.orElseGetPolyNull(supplyNullable);
+  }
+
+  void foo4nw(Optional1633<String> o, Supplier<@Nullable String> supplyNullable) {
+    @Nullable String str3 = o.orElseGetPolyNullNoWildcard(supplyNullable);
+  }
+
+  void foo41(Optional1633<String> o) {
+    @SuppressWarnings("return") // https://tinyurl.com/cfissue/979
+    @Nullable String str3 = o.orElseGetPolyNull(() -> null);
+  }
+
+  void foo41nw(Optional1633<String> o) {
+    @SuppressWarnings("return") // https://tinyurl.com/cfissue/979
+    @Nullable String str3 = o.orElseGetPolyNullNoWildcard(() -> null);
+  }
+
+  void foo5(Optional1633<String> o, Supplier<@NonNull String> supplyNonNull) {
+    @NonNull String str = o.orElseGetUnannotated(supplyNonNull);
+  }
+
+  void foo6(Optional1633<String> o, Supplier<@NonNull String> supplyNonNull) {
+    // :: error: (assignment)
+    @NonNull String str1 = o.orElseGetNullable(supplyNonNull);
+  }
+
+  void foo6nw(Optional1633<String> o, Supplier<@NonNull String> supplyNonNull) {
+    // :: error: (assignment)
+    @NonNull String str1 = o.orElseGetNullableNoWildcard(supplyNonNull);
+  }
+
+  void foo7(Optional1633<String> o, Supplier<@NonNull String> supplyNonNull) {
+    @NonNull String str2 = o.orElseGetNonNull(supplyNonNull);
+  }
+
+  void foo8(Optional1633<String> o, Supplier<@NonNull String> supplyNonNull) {
+    @NonNull String str3 = o.orElseGetPolyNull(supplyNonNull);
+  }
+
+  void foo8nw(Optional1633<String> o, Supplier<@NonNull String> supplyNonNull) {
+    @NonNull String str3 = o.orElseGetPolyNullNoWildcard(supplyNonNull);
+  }
+}
+
+// From the JDK
+@Covariant(0)
+final @NonNull class Optional1633<T extends @Nullable Object> {
+
+  /** If non-null, the value; if null, indicates no value is present. */
+  private final @Nullable T value = null;
+
+  // TODO: there are conceptually two versions of this method:
+  //   public @Nullable T orElseGet(Supplier<? extends @Nullable T> other) {
+  //   public @NonNull T orElseGet(Supplier<? extends @NonNull T> other) {
+  // Issue #1633 says that this annotation doesn't help at all:
+  //   public @PolyNull T orElseGet(Supplier<? extends @PolyNull T> other) {
+  // but it does seem to work in this test case.
+  public T orElseGetUnannotated(Supplier<? extends T> other) {
+    return value != null ? value : other.get();
+  }
+
+  public @Nullable T orElseGetNullable(Supplier<@Nullable ? extends @Nullable T> other) {
+    return value != null ? value : other.get();
+  }
+
+  public @Nullable T orElseGetNullableNoWildcard(Supplier<? extends @Nullable T> other) {
+    // The commented-out line fails to typecheck due to issue #979
+    // return value != null ? value : other.get();
+    if (value != null) {
+      return value;
+    } else {
+      return other.get();
+    }
+  }
+
+  public @NonNull T orElseGetNonNull(Supplier<@NonNull ? extends @NonNull T> other) {
+    return value != null ? value : other.get();
+  }
+
+  public @PolyNull T orElseGetPolyNull(Supplier<@PolyNull ? extends @PolyNull T> other) {
+    return value != null ? value : other.get();
+  }
+
+  public @PolyNull T orElseGetPolyNullNoWildcard(Supplier<? extends @PolyNull T> other) {
+    // The commented-out line fails to typecheck due to issue #979
+    // return value != null ? value : other.get();
+    if (value != null) {
+      return value;
+    } else {
+      return other.get();
+    }
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue363.java b/checker/tests/nullness/java8/Issue363.java
new file mode 100644
index 0000000..900ab48
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue363.java
@@ -0,0 +1,13 @@
+// Test case for Issue 363:
+// https://github.com/typetools/checker-framework/issues/363
+
+public class Issue363 {
+  void foo(java.util.OptionalInt value) {
+    value.orElseThrow(() -> new Error());
+  }
+
+  void bar(java.util.OptionalInt value) {
+    java.util.function.Supplier<Error> s = () -> new Error();
+    value.orElseThrow(s);
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue366.java b/checker/tests/nullness/java8/Issue366.java
new file mode 100644
index 0000000..01d3a0f
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue366.java
@@ -0,0 +1,18 @@
+// Test case for Issue 366:
+// https://github.com/typetools/checker-framework/issues/366
+// but amended for Issue 1098:
+// https://github.com/typetools/checker-framework/issues/1098
+
+import java.util.Optional;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue366 {
+  static Optional<@NonNull String> getPossiblyEmptyString() {
+    return Optional.ofNullable(null);
+  }
+
+  static Optional<@Nullable String> getPossiblyEmptyString2() {
+    return Optional.ofNullable(null);
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue448.java b/checker/tests/nullness/java8/Issue448.java
new file mode 100644
index 0000000..271492e
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue448.java
@@ -0,0 +1,12 @@
+// Test case for issue 448:
+// https://github.com/typetools/checker-framework/issues/448
+
+import java.util.Arrays;
+
+enum Issue448 {
+  ONE;
+
+  void method() {
+    Arrays.stream(values()).filter(key -> true);
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue448Ext.java b/checker/tests/nullness/java8/Issue448Ext.java
new file mode 100644
index 0000000..9356e39
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue448Ext.java
@@ -0,0 +1,20 @@
+// Test case for issue 448:
+// https://github.com/typetools/checker-framework/issues/448
+
+import java.util.Arrays;
+import java.util.function.IntPredicate;
+import java.util.stream.IntStream;
+
+public class Issue448Ext {
+  void getFor(int[] ia, int index) {
+    Arrays.stream(ia).filter(x -> true);
+  }
+
+  Object getFor(int[] ia, IntPredicate p) {
+    return Arrays.stream(ia).filter(p);
+  }
+
+  Object getFor(IntStream is, int index) {
+    return is.filter(key -> key == index).findFirst().orElseThrow(IllegalArgumentException::new);
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue496.java b/checker/tests/nullness/java8/Issue496.java
new file mode 100644
index 0000000..071be67
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue496.java
@@ -0,0 +1,21 @@
+// Test case for issue 496:
+// https://github.com/typetools/checker-framework/issues/496
+
+import java.util.Optional;
+
+public class Issue496 {
+
+  public static class Entity<T> {
+    public final T value;
+    public final Class<T> cls;
+
+    public Entity(T value, Class<T> cls) {
+      this.value = value;
+      this.cls = cls;
+    }
+  }
+
+  public static <T> Optional<Entity<T>> testCase(Class<T> targetClass) {
+    return Optional.<T>empty().map((T val) -> new Entity<T>(val, targetClass));
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue529.java b/checker/tests/nullness/java8/Issue529.java
new file mode 100644
index 0000000..158f91a
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue529.java
@@ -0,0 +1,23 @@
+// Test case for issue 529:
+// https://github.com/typetools/checker-framework/issues/529
+
+import java.util.List;
+import java.util.stream.*;
+
+public class Issue529 {
+
+  // Crashes:
+  public Stream<String> test(List<String> list) {
+    return list.stream().map(e -> e);
+  }
+
+  // OK:
+  public Stream<String> test2(List<String> list) {
+    return list.stream();
+  }
+
+  // OK:
+  public Stream<String> test3(Stream<String> stream) {
+    return stream.map(e -> e);
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue557.java b/checker/tests/nullness/java8/Issue557.java
new file mode 100644
index 0000000..9fed28c
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue557.java
@@ -0,0 +1,88 @@
+// Test case for issue 557:
+// https://github.com/typetools/checker-framework/issues/557
+
+import java.util.Optional;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+@SuppressWarnings("nullness")
+class MyOpt<T> {
+  static <S> MyOpt<S> of(S p) {
+    return null;
+  }
+
+  static <S> MyOpt<S> empty() {
+    return null;
+  }
+}
+
+@SuppressWarnings("nullness")
+class MyOpt2<T extends Object> {
+  static <S extends Object> MyOpt2<S> of(S p) {
+    return null;
+  }
+
+  static <S extends Object> MyOpt2<S> empty() {
+    return null;
+  }
+}
+
+@SuppressWarnings("nullness")
+class MyOpt3<T extends @Nullable Object> {
+  static <S extends @Nullable Object> MyOpt3<S> of(S p) {
+    return null;
+  }
+
+  static <S extends @Nullable Object> MyOpt3<S> empty() {
+    return null;
+  }
+}
+
+class Issue557a {
+  MyOpt<String> opt(boolean flag) {
+    return flag ? MyOpt.of("Hello") : MyOpt.empty();
+  }
+
+  MyOpt<String> opt2() {
+    return MyOpt.empty();
+  }
+
+  MyOpt<String> opt3(boolean flag) {
+    return flag ? MyOpt.of("Hello") : (flag ? MyOpt.empty() : MyOpt.empty());
+  }
+
+  void foo(MyOpt<String> param) {}
+
+  void callFoo(boolean flag) {
+    foo(flag ? MyOpt.of("Hello") : (flag ? MyOpt.empty() : MyOpt.empty()));
+  }
+}
+
+class Issue557b {
+  MyOpt2<String> opt(boolean flag) {
+    return flag ? MyOpt2.of("Hello") : MyOpt2.empty();
+  }
+
+  MyOpt2<String> opt2() {
+    return MyOpt2.empty();
+  }
+}
+
+class Issue557c {
+  MyOpt3<String> opt(boolean flag) {
+    return flag ? MyOpt3.of("Hello") : MyOpt3.empty();
+  }
+
+  MyOpt3<String> opt2() {
+    return MyOpt3.empty();
+  }
+}
+
+class Issue557d {
+  Optional<String> opt(boolean flag) {
+    return flag ? Optional.of("Hello") : Optional.empty();
+  }
+
+  Optional<String> opt2() {
+    return Optional.empty();
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue579.java b/checker/tests/nullness/java8/Issue579.java
new file mode 100644
index 0000000..7e25ded
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue579.java
@@ -0,0 +1,24 @@
+// Test case for Issue579
+// https://github.com/typetools/checker-framework/issues/579
+
+import java.util.Comparator;
+
+public class Issue579<T> implements Comparator<T> {
+  private final Comparator<T> real;
+
+  @SuppressWarnings("unchecked")
+  Issue579(Comparator<? super T> real) {
+    this.real = (Comparator<T>) real;
+  }
+
+  @Override
+  public int compare(T a, T b) {
+    throw new RuntimeException();
+  }
+
+  @Override
+  public Comparator<T> thenComparing(Comparator<? super T> other) {
+    // :: warning: (nulltest.redundant)
+    return new Issue579<>(real == null ? other : real.thenComparing(other));
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue596.java b/checker/tests/nullness/java8/Issue596.java
new file mode 100644
index 0000000..c056bcc
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue596.java
@@ -0,0 +1,24 @@
+// Test case for Issue 596:
+// https://github.com/typetools/checker-framework/issues/596
+
+import java.util.concurrent.atomic.AtomicReference;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue596 {
+
+  private static String getOrEmpty(AtomicReference<String> ref) {
+    return Optional596.fromNullable(ref.get()).or("");
+  }
+}
+
+// From Google Guava
+class Optional596<T> {
+
+  public static <T> Optional596<T> fromNullable(@Nullable T nullableReference) {
+    return new Optional596<T>();
+  }
+
+  public T or(T defaultValue) {
+    return defaultValue;
+  }
+}
diff --git a/checker/tests/nullness/java8/Issue704.java b/checker/tests/nullness/java8/Issue704.java
new file mode 100644
index 0000000..401c8db
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue704.java
@@ -0,0 +1,8 @@
+// Test case for Issue 704:
+// https://github.com/typetools/checker-framework/issues/704
+
+import java.util.function.IntSupplier;
+
+interface Issue704 {
+  IntSupplier zero = () -> 0;
+}
diff --git a/checker/tests/nullness/java8/Issue720.java b/checker/tests/nullness/java8/Issue720.java
new file mode 100644
index 0000000..92beb01
--- /dev/null
+++ b/checker/tests/nullness/java8/Issue720.java
@@ -0,0 +1,12 @@
+// Test case for issue #720:
+// https://github.com/typetools/checker-framework/issues/720
+
+import java.util.function.IntConsumer;
+
+public class Issue720 {
+  static IntConsumer consumer = Issue720::method;
+
+  static int method(int x) {
+    return x;
+  }
+}
diff --git a/checker/tests/nullness/java8/UnionTypeBug.java b/checker/tests/nullness/java8/UnionTypeBug.java
new file mode 100644
index 0000000..4140e30
--- /dev/null
+++ b/checker/tests/nullness/java8/UnionTypeBug.java
@@ -0,0 +1,39 @@
+// Test case for Issue 384:
+// https://github.com/typetools/checker-framework/issues/384
+
+// TODO: no longer crashes, but still need to ensure annotations
+// are stored correctly.
+
+import org.checkerframework.checker.guieffect.qual.PolyUIType;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+abstract class UnionTypeBug {
+
+  void method() {
+    try {
+
+      badBoy();
+
+      // :: warning: (nullness.on.exception.parameter)
+    } catch (@NonNull InnerException1 | @NonNull InnerException2 e) {
+
+      // :: warning: (nullness.on.exception.parameter)
+    } catch (@NonNull InnerException3 | @NonNull InnerException4 e) {
+
+    }
+  }
+
+  abstract void badBoy() throws InnerException1, InnerException2, InnerException3, InnerException4;
+
+  @PolyUIType
+  class InnerException1 extends Exception {}
+
+  @PolyUIType
+  class InnerException2 extends Exception {}
+
+  @PolyUIType
+  class InnerException3 extends Exception {}
+
+  @PolyUIType
+  class InnerException4 extends Exception {}
+}
diff --git a/checker/tests/nullness/java8/lambda/Dataflow.java b/checker/tests/nullness/java8/lambda/Dataflow.java
new file mode 100644
index 0000000..49946fc
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Dataflow.java
@@ -0,0 +1,20 @@
+// Test dataflow refinement in the body of a lambda.
+
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Dataflow {
+  void context() {
+    FunctionDF<@Nullable Object, Object> o =
+        a -> {
+          // :: error: (dereference.of.nullable)
+          a.toString();
+          a = "";
+          a.toString();
+          return "";
+        };
+  }
+}
+
+interface FunctionDF<T extends @Nullable Object, R> {
+  R apply(T t);
+}
diff --git a/checker/tests/nullness/java8/lambda/FinalLocalVariables.java b/checker/tests/nullness/java8/lambda/FinalLocalVariables.java
new file mode 100644
index 0000000..aa04782
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/FinalLocalVariables.java
@@ -0,0 +1,103 @@
+// Test enclosing final local variables
+
+import org.checkerframework.checker.nullness.qual.*;
+
+interface FunctionLE<T extends @Nullable Object, R> {
+  R apply(T t);
+}
+
+class LambdaEnclosing {
+
+  // Test static initializer
+  static {
+    String local1 = "";
+    String local2 = null;
+    FunctionLE<String, String> f0 =
+        s -> {
+          local1.toString();
+          // :: error: (dereference.of.nullable)
+          local2.toString();
+          return "";
+        };
+  }
+
+  // Test instance initializer
+  {
+    String local1 = "";
+    String local2 = null;
+    FunctionLE<String, String> f0 =
+        s -> {
+          local1.toString();
+          // :: error: (dereference.of.nullable)
+          local2.toString();
+          return "";
+        };
+  }
+
+  FunctionLE<String, String> functionField =
+      s -> {
+        String local1 = "";
+        String local2 = null;
+        FunctionLE<String, String> f0 =
+            s2 -> {
+              local1.toString();
+              // :: error: (dereference.of.nullable)
+              local2.toString();
+              return "";
+            };
+        return "";
+      };
+
+  void context() {
+    String local1 = "";
+    String local2 = null;
+
+    FunctionLE<String, String> f1 =
+        s -> {
+          local1.toString();
+          // :: error: (dereference.of.nullable)
+          local2.toString();
+          class Inner {
+
+            void context2() {
+              String local3 = "";
+              String local4 = null;
+
+              FunctionLE<String, String> f2 =
+                  s2 -> {
+                    local1.toString();
+                    local2.toString();
+                    local3.toString();
+                    // :: error: (dereference.of.nullable)
+                    local4.toString();
+
+                    return "";
+                  };
+            }
+          }
+
+          new Object() {
+
+            @Override()
+            public String toString() {
+              String local3 = "";
+              String local4 = null;
+
+              FunctionLE<String, String> f2 =
+                  s2 -> {
+                    local1.toString();
+                    local2.toString();
+                    local3.toString();
+                    // :: error: (dereference.of.nullable)
+                    local4.toString();
+
+                    return "";
+                  };
+              return "";
+            }
+          }.toString();
+
+          return "";
+        };
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/Issue1864.java b/checker/tests/nullness/java8/lambda/Issue1864.java
new file mode 100644
index 0000000..895efc0
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Issue1864.java
@@ -0,0 +1,18 @@
+// Test case for Issue 1864:
+// https://github.com/typetools/checker-framework/issues/1864
+
+import java.util.List;
+import java.util.function.Supplier;
+
+abstract class Issue1864 {
+  interface A {}
+
+  abstract <T extends A> List<T> g();
+
+  abstract void h(Supplier<Iterable<A>> s);
+
+  void f() {
+    Iterable<A> xs = g();
+    h(() -> xs);
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/Issue1864b.java b/checker/tests/nullness/java8/lambda/Issue1864b.java
new file mode 100644
index 0000000..0b6c6ab
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Issue1864b.java
@@ -0,0 +1,13 @@
+// Test case for Issue 1864:
+// https://github.com/typetools/checker-framework/issues/1864
+
+public class Issue1864b {
+  interface Supplier {
+    Object get();
+  }
+
+  Supplier foo() {
+    Object foo = new Object();
+    return () -> foo;
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/Issue1897.java b/checker/tests/nullness/java8/lambda/Issue1897.java
new file mode 100644
index 0000000..0aefd11
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Issue1897.java
@@ -0,0 +1,13 @@
+// Test case for Issue 1897:
+// https://github.com/typetools/checker-framework/issues/1897
+
+import java.util.function.Function;
+
+public class Issue1897 {
+  Issue1897() {
+    final int length = 1;
+    takesLambda(s -> length);
+  }
+
+  static void takesLambda(Function<String, Integer> function) {}
+}
diff --git a/checker/tests/nullness/java8/lambda/Issue3217.java b/checker/tests/nullness/java8/lambda/Issue3217.java
new file mode 100644
index 0000000..2af7061
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Issue3217.java
@@ -0,0 +1,20 @@
+import java.util.function.Function;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue3217<ModelA, ModelB, Value> {
+  private final Function<Function<ModelA, @Nullable Value>, Function<ModelB, @Nullable Value>>
+      proxyFunction;
+
+  public Issue3217(
+      Function<Function<ModelA, @Nullable Value>, Function<ModelB, @Nullable Value>>
+          proxyFunction) {
+    this.proxyFunction = proxyFunction;
+  }
+}
+
+class SubClass<A, V> extends Issue3217<A, A, V> {
+  public SubClass() {
+    super(x -> x);
+    Function<Function<A, @Nullable V>, Function<A, @Nullable V>> p = y -> y;
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/Issue367.java b/checker/tests/nullness/java8/lambda/Issue367.java
new file mode 100644
index 0000000..cad3410
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Issue367.java
@@ -0,0 +1,7 @@
+// Test case for Issue 367:
+// https://github.com/typetools/checker-framework/issues/367
+public class Issue367 {
+  static void test(Iterable<? extends Thread> threads) {
+    threads.forEach(thread -> System.out.println(thread));
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/Issue403.java b/checker/tests/nullness/java8/lambda/Issue403.java
new file mode 100644
index 0000000..3b84fc9
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Issue403.java
@@ -0,0 +1,14 @@
+// Test case for Issue 403:
+// https://github.com/typetools/checker-framework/issues/403
+
+import java.util.Comparator;
+
+public class Issue403 {
+  Comparator<Issue403> COMPARATOR = Comparator.comparing(w -> w.value);
+
+  String value;
+
+  Issue403(final String value) {
+    this.value = value;
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/Issue436.java b/checker/tests/nullness/java8/lambda/Issue436.java
new file mode 100644
index 0000000..2dddaa8
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Issue436.java
@@ -0,0 +1,19 @@
+import static java.util.Arrays.asList;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+public class Issue436 {
+  public void makeALongFormConditionalLambdaReturningGenerics(boolean makeAll) {
+    // TypeArgInferenceUtil.assignedTo used to try to use the method return rather than the
+    // lambda return for those return statements below
+    Supplier<List<String>> supplier =
+        () -> {
+          if (makeAll) {
+            return asList("beer", "peanuts");
+          } else {
+            return asList("cheese", "wine");
+          }
+        };
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/Issue572.java b/checker/tests/nullness/java8/lambda/Issue572.java
new file mode 100644
index 0000000..d0ec0a0
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Issue572.java
@@ -0,0 +1,16 @@
+// Test case for issue #572: https://github.com/typetools/checker-framework/issues/572
+
+import java.util.function.BiFunction;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue572 {
+  static <A, B, C> C biApply(BiFunction<A, B, C> f, A a, B b) {
+    return f.apply(a, b);
+  }
+
+  public <A, B> A konst(@NonNull A a, @Nullable B b) {
+    // :: error: (argument)
+    return biApply(((first, second) -> first), a, b);
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/Issue870.java b/checker/tests/nullness/java8/lambda/Issue870.java
new file mode 100644
index 0000000..389c3ac
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Issue870.java
@@ -0,0 +1,17 @@
+// Test case for issue #870: https://github.com/typetools/checker-framework/issues/870
+
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class Issue870 {
+  public static Stream<? extends ZipEntry> entries(ZipFile zipFile) {
+    return zipFile.stream()
+        .filter(entry -> !entry.isDirectory() && entry.getName().endsWith(".xml"));
+  }
+
+  public static Stream<? extends ZipEntry> entries2(ZipFile zipFile) {
+    return zipFile.stream()
+        .filter((ZipEntry entry) -> !entry.isDirectory() && entry.getName().endsWith(".xml"));
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/Issue953bLambda.java b/checker/tests/nullness/java8/lambda/Issue953bLambda.java
new file mode 100644
index 0000000..ee85568
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Issue953bLambda.java
@@ -0,0 +1,26 @@
+// Test case for #953
+// https://github.com/typetools/checker-framework/issues/953
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+@SuppressWarnings("all")
+public class Issue953bLambda {
+  private static List<List<?>> strs = new ArrayList<>();
+
+  public static <R, T> List<@NonNull R> mapList(
+      List<@NonNull T> list, Function<@NonNull T, @NonNull R> func) {
+    throw new RuntimeException();
+  }
+
+  public static void test() {
+    List<String> list =
+        mapList(
+            strs,
+            s -> {
+              return "";
+            });
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/LambdaInit.java b/checker/tests/nullness/java8/lambda/LambdaInit.java
new file mode 100644
index 0000000..66f35f4
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/LambdaInit.java
@@ -0,0 +1,202 @@
+// Test field initialization
+// fields, initializers, static initializers, constructors.
+
+import org.checkerframework.checker.nullness.qual.*;
+
+interface FunctionInit<T extends @Nullable Object, R> {
+  R apply(T t);
+}
+
+interface Consumer<T> {
+  void consume(T t);
+}
+
+public class LambdaInit {
+  String f1;
+  String f2 = "";
+  @Nullable String f3 = "";
+
+  String f1b;
+  FunctionInit<String, String> ff0 =
+      s -> {
+        // :: error: (dereference.of.nullable)
+        f1.toString();
+        // :: error: (dereference.of.nullable)
+        f1b.toString();
+        f2.toString();
+        // :: error: (dereference.of.nullable)
+        f3.toString();
+        return "";
+      };
+  // Test field value refinement after initializer. f1b should still be @Nullable in the lambda.
+  Object o1 = f1b = "";
+
+  String f4;
+
+  {
+    f3 = "";
+    f4 = "";
+    FunctionInit<String, String> ff0 =
+        s -> {
+          // :: error: (dereference.of.nullable)
+          f1.toString();
+          f2.toString();
+          // :: error: (dereference.of.nullable)
+          f3.toString();
+          f4.toString();
+          return "";
+        };
+  }
+
+  String f5;
+
+  @SuppressWarnings("initialization.fields.uninitialized") // f1 is not initialized
+  LambdaInit() {
+    f5 = "";
+    FunctionInit<String, String> ff0 =
+        s -> {
+          // :: error: (dereference.of.nullable)
+          f1.toString();
+          f2.toString();
+          // :: error: (dereference.of.nullable)
+          f3.toString();
+          f5.toString();
+          return "";
+        };
+  }
+
+  //    // This is a bug
+  //    // Could probably be fixed with CommittmentTreeAnnotator::visitMethod
+  //    // Or more likely, TypeFromTree::212
+  //    // AnnotatedTypeFactory::getImplicitReceiverType::1146(there is a todo...)
+  //    Object o = new Object() {
+  //        @Override
+  //        public String toString() {
+  //            f1.toString();
+  //            f2.toString();
+  //            return "";
+  //        }
+  //    };
+  //
+
+  //  Works!
+  void method() {
+    FunctionInit<String, String> ff0 =
+        s -> {
+          f1.toString();
+          f2.toString();
+          // :: error: (dereference.of.nullable)
+          f3.toString();
+          return "";
+        };
+  }
+
+  // Test for nested
+  class Nested {
+    FunctionInit<String, String> ff0 =
+        s -> {
+          f1.toString();
+          f2.toString();
+          // :: error: (dereference.of.nullable)
+          f3.toString();
+          return "";
+        };
+
+    String f4;
+
+    {
+      f3 = "";
+      f4 = "";
+      FunctionInit<String, String> ff0 =
+          s -> {
+            f1.toString();
+            f2.toString();
+            // :: error: (dereference.of.nullable)
+            f3.toString();
+            f4.toString();
+            return "";
+          };
+    }
+
+    String f5;
+
+    Nested() {
+      f5 = "";
+      FunctionInit<String, String> ff0 =
+          s -> {
+            f1.toString();
+            f2.toString();
+            // :: error: (dereference.of.nullable)
+            f3.toString();
+            f5.toString();
+            return "";
+          };
+    }
+
+    void method() {
+      FunctionInit<String, String> ff0 =
+          s -> {
+            f1.toString();
+            f2.toString();
+            // :: error: (dereference.of.nullable)
+            f3.toString();
+            return "";
+          };
+    }
+  }
+
+  // Test for nested in a lambda
+  Consumer<String> func =
+      s -> {
+        Consumer<String> ff0 =
+            s2 -> {
+              // :: error: (dereference.of.nullable)
+              f1.toString();
+              f2.toString();
+              // :: error: (dereference.of.nullable)
+              f3.toString();
+            };
+      };
+
+  // Tests for static initializers.
+  // :: error: (initialization.static.field.uninitialized)
+  static String sf1;
+  static String sf2 = "";
+  static @Nullable String sf3 = "";
+  static String sf1b;
+  static FunctionInit<String, String> sff0 =
+      s -> {
+
+        // This is an issue with static initializers in general
+        // // :: error: (dereference.of.nullable)
+        sf1.toString();
+        // This is an issue with static initializers in general
+        // // :: error: (dereference.of.nullable)
+        sf1b.toString();
+        sf2.toString();
+        // :: error: (dereference.of.nullable)
+        sf3.toString();
+        return "";
+      };
+  // Test field value refinement after initializer. f1b should still be null.
+  static Object so1 = sf1b = "";
+
+  static String sf4;
+
+  static {
+    sf3 = "";
+    sf4 = "";
+    FunctionInit<String, String> sff0 =
+        s -> {
+
+          // This is an issue with static initializers in general
+          // // :: error: (dereference.of.nullable)
+          sf1.toString();
+          sf2.toString();
+          // :: error: (dereference.of.nullable)
+          sf3.toString();
+          sf4.toString();
+          return "";
+        };
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/LambdaNullness.java b/checker/tests/nullness/java8/lambda/LambdaNullness.java
new file mode 100644
index 0000000..a0d5c0f
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/LambdaNullness.java
@@ -0,0 +1,109 @@
+// Test file for nullness parameter and return checks.
+
+import org.checkerframework.checker.nullness.qual.*;
+
+interface Noop {
+  void noop();
+}
+
+interface FunctionNull<T extends @Nullable Object, R> {
+  R apply(T t);
+}
+
+interface Supplier<R extends @Nullable Object> {
+  R supply();
+}
+
+interface BiFunctionNull<T, U, R> {
+  R apply(T t, U u);
+}
+
+public class LambdaNullness {
+
+  // Annotations in lamba expressions, in static, instance of fields initializers are stored on
+  // the last declared constructor.
+  //
+  // For example, the annotation for @Nullable Integer x on f7's initializer
+  // is stored on here because it is the last defined constructor.
+  //
+  // See TypeFromElement::annotateParam
+  LambdaNullness(FunctionNull<String, String> f, Object e) {}
+
+  // No parameters; result is void
+  Noop f1 = () -> {};
+  // No parameters, expression body
+  Supplier<Integer> f2a = () -> 42;
+
+  // No parameters, expression body
+  // :: error: (return)
+  Supplier<Integer> f2b = () -> null;
+
+  // No parameters, expression body
+  Supplier<@Nullable Void> f3 = () -> null;
+  // No parameters, block body with return
+  Supplier<Integer> f4a =
+      () -> {
+        return 42;
+      };
+  // No parameters, block body with return
+  Supplier<@Nullable Integer> f4b =
+      () -> {
+        // :: error: (assignment)
+        @NonNull String s = null;
+
+        return null;
+      };
+  // No parameters, void block body
+  Noop f5 =
+      () -> {
+        System.gc();
+      };
+
+  // Complex block body with returns
+  Supplier<Integer> f6 =
+      () -> {
+        if (true) {
+          return 12;
+        } else {
+          int result = 15;
+          for (int i = 1; i < 10; i++) {
+            result *= i;
+          }
+          // :: error: (return)
+          return null;
+        }
+      };
+
+  // Single declared-type parameter
+  FunctionNull<@Nullable Integer, Integer> f7 = (@Nullable Integer x) -> 1;
+
+  // Single declared-type parameter
+  FunctionNull<@Nullable String, String> f9 =
+      // :: error: (lambda.param)
+      (@NonNull String x) -> {
+        return x + "";
+      };
+  // Single inferred-type parameter
+  FunctionNull<@NonNull Integer, Integer> f10 = (x) -> x + 1;
+  // Parentheses optional for single
+  FunctionNull<@Nullable Integer, Integer> f11 = x -> 1;
+
+  // Multiple declared-type parameters
+  BiFunctionNull<Integer, Integer, Integer> f16 =
+      (@Nullable Integer x, final Integer y) -> {
+        x = null;
+        // :: error: (unboxing.of.nullable)
+        return x + y;
+      };
+
+  // Multiple inferred-type parameters
+  BiFunctionNull<String, String, String> f18 = (x, y) -> x + y;
+
+  // Infer based on context.
+  FunctionNull<@Nullable String, String> fn =
+      (s) -> {
+        // :: error: (dereference.of.nullable)
+        s.toString();
+        return "";
+      };
+}
diff --git a/checker/tests/nullness/java8/lambda/Parameters.java b/checker/tests/nullness/java8/lambda/Parameters.java
new file mode 100644
index 0000000..133ae24
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Parameters.java
@@ -0,0 +1,67 @@
+// The location of a lambda affects locating the annotation for the lambda.
+
+import org.checkerframework.checker.nullness.qual.*;
+
+interface NullConsumer {
+  void method(@Nullable String s);
+}
+
+interface NNConsumer {
+  void method(@NonNull String s);
+}
+
+class LambdaParam {
+
+  NullConsumer fn1 =
+      // :: error: (lambda.param)
+      (@NonNull String i) -> {};
+  NullConsumer fn2 = (@Nullable String i) -> {};
+  NullConsumer fn3 = (String i) -> {};
+  NNConsumer fn4 = (String i) -> {};
+  NNConsumer fn5 = (@Nullable String i) -> {};
+  NNConsumer fn6 = (@NonNull String i) -> {};
+
+  // Initializer blocks with annotations don't work yet because of javac compiler bug.
+  // https://bugs.openjdk.java.net/browse/JDK-8056970
+  //    {
+  //          // :: error: (lambda.param)
+  //        NullConsumer fn1 = (@NonNull String i) -> {};
+  //        NullConsumer fn2 = (@Nullable String i) -> {};
+  //        NullConsumer fn3 = (String i) -> {};
+  //        NNConsumer fn4 = (String i) -> {};
+  //        NNConsumer fn5 = (@Nullable String i) -> {};
+  //        NNConsumer fn6 = (@NonNull String i) -> {};
+  //    }
+  //
+  //    static {
+  //          // :: error: (lambda.param)
+  //        NullConsumer fn1 = (@NonNull String i) -> {};
+  //        NullConsumer fn2 = (@Nullable String i) -> {};
+  //        NullConsumer fn3 = (String i) -> {};
+  //        NNConsumer fn4 = (String i) -> {};
+  //        NNConsumer fn5 = (@Nullable String i) -> {};
+  //        NNConsumer fn6 = (@NonNull String i) -> {};
+  //    }
+
+  static void foo() {
+    NullConsumer fn1 =
+        // :: error: (lambda.param)
+        (@NonNull String i) -> {};
+    NullConsumer fn2 = (@Nullable String i) -> {};
+    NullConsumer fn3 = (String i) -> {};
+    NNConsumer fn4 = (String i) -> {};
+    NNConsumer fn5 = (@Nullable String i) -> {};
+    NNConsumer fn6 = (@NonNull String i) -> {};
+  }
+
+  void bar() {
+    NullConsumer fn1 =
+        // :: error: (lambda.param)
+        (@NonNull String i) -> {};
+    NullConsumer fn2 = (@Nullable String i) -> {};
+    NullConsumer fn3 = (String i) -> {};
+    NNConsumer fn4 = (String i) -> {};
+    NNConsumer fn5 = (@Nullable String i) -> {};
+    NNConsumer fn6 = (@NonNull String i) -> {};
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/ParametersInBody.java b/checker/tests/nullness/java8/lambda/ParametersInBody.java
new file mode 100644
index 0000000..d5088d4
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/ParametersInBody.java
@@ -0,0 +1,50 @@
+// Test that parameter annotations are correct in the body of a lambda
+
+import org.checkerframework.checker.nullness.qual.*;
+
+interface ConsumerLPB {
+  void method(@Nullable String s);
+}
+
+interface NNConsumerLPB {
+  void method(@NonNull String s);
+}
+
+class LambdaParamBody {
+
+  // :: error: (dereference.of.nullable)
+  ConsumerLPB fn0 = (String i) -> i.toString();
+  ConsumerLPB fn2 =
+      (@Nullable String i) -> {
+        // :: error: (dereference.of.nullable)
+        i.toString();
+      };
+  ConsumerLPB fn3 =
+      (String i) -> {
+        // :: error: (dereference.of.nullable)
+        i.toString();
+      };
+  ConsumerLPB fn3b =
+      (i) -> {
+        // :: error: (dereference.of.nullable)
+        i.toString();
+      };
+
+  NNConsumerLPB fn4 =
+      (String i) -> {
+        i.toString();
+      };
+  NNConsumerLPB fn4b =
+      (i) -> {
+        i.toString();
+      };
+  NNConsumerLPB fn5 =
+      (@Nullable String i) -> {
+        // :: error: (dereference.of.nullable)
+        i.toString();
+      };
+  NNConsumerLPB fn6 =
+      (@NonNull String i) -> {
+        i.toString();
+      };
+}
diff --git a/checker/tests/nullness/java8/lambda/ParametersInBodyGenerics.java b/checker/tests/nullness/java8/lambda/ParametersInBodyGenerics.java
new file mode 100644
index 0000000..2f7e17f
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/ParametersInBodyGenerics.java
@@ -0,0 +1,43 @@
+// Test that parameter annotations are correct in the body of a lambda
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class ParametersInBodyGenerics {
+  interface NullableConsumer {
+    void method(List<@Nullable String> s);
+  }
+
+  interface NonNullConsumer {
+    void method(@NonNull List<String> s);
+  }
+
+  void test() {
+    // :: error: (lambda.param)
+    NullableConsumer fn0 = (List<String> i) -> i.get(0).toString();
+    NullableConsumer fn2 =
+        (List<@Nullable String> i) -> {
+          // :: error: (dereference.of.nullable)
+          i.get(0).toString();
+        };
+    NullableConsumer fn3 =
+        // :: error: (lambda.param)
+        (List<String> i) -> {
+          i.get(0).toString();
+        };
+    NullableConsumer fn3b =
+        (i) -> {
+          // :: error: (dereference.of.nullable)
+          i.get(0).toString();
+        };
+
+    NonNullConsumer fn4 =
+        (List<String> i) -> {
+          i.get(0).toString();
+        };
+    NonNullConsumer fn4b =
+        (i) -> {
+          i.get(0).toString();
+        };
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/ReceiversLambda.java b/checker/tests/nullness/java8/lambda/ReceiversLambda.java
new file mode 100644
index 0000000..3a76f11
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/ReceiversLambda.java
@@ -0,0 +1,33 @@
+// Test references to this and super in a lambda.
+
+import org.checkerframework.checker.nullness.qual.*;
+
+// Tests for the nullable type system
+interface SupplierR {
+  @NonNull ReceiverTest supply();
+}
+
+interface FunctionRT<T extends @Nullable Object, R> {
+  R apply(T t);
+}
+
+class ReceiverTest {
+
+  // :: error: (method.invocation)
+  FunctionRT<String, String> f1 = s -> this.toString();
+  // :: error: (method.invocation)
+  FunctionRT<String, String> f2 = s -> super.toString();
+
+  // :: error: (nullness.on.receiver)
+  void context1(@NonNull ReceiverTest this) {
+    SupplierR s = () -> this;
+  }
+
+  // :: error: (nullness.on.receiver)
+  void context2(@Nullable ReceiverTest this) {
+    // TODO: This is bug that is not specific to lambdas
+    // https://github.com/typetools/checker-framework/issues/352
+    // :: error: (return)
+    SupplierR s = () -> this;
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/RefinedLocalInLambda.java b/checker/tests/nullness/java8/lambda/RefinedLocalInLambda.java
new file mode 100644
index 0000000..b9370f6
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/RefinedLocalInLambda.java
@@ -0,0 +1,34 @@
+// Test case for issue #1248:
+// https://github.com/typetools/checker-framework/issues/1248
+
+import java.util.function.Predicate;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class RefinedLocalInLambda {
+
+  public static void main(String[] args) {
+    printIntegersGreaterThan(10);
+  }
+
+  public static void printIntegersGreaterThan(@Nullable Integer limit) {
+    // :: error: (unboxing.of.nullable)
+    printIntegersWithPredicate(i -> i > limit); // type-checking fails
+    if (limit == null) {
+      return;
+    }
+    printIntegersWithPredicate(i -> i > limit); // type-checking succeeds
+    @NonNull Integer limit2 = limit;
+    printIntegersWithPredicate(i -> i > limit2); // type-checking succeeds
+    Integer limit3 = limit;
+    printIntegersWithPredicate(i -> i > limit3); // type-checking succeeds
+  }
+
+  public static void printIntegersWithPredicate(Predicate<Integer> tester) {
+    for (int i = 0; i < 100; i++) {
+      if (tester.test(i)) {
+        System.out.println(i);
+      }
+    }
+  }
+}
diff --git a/checker/tests/nullness/java8/lambda/Returns.java b/checker/tests/nullness/java8/lambda/Returns.java
new file mode 100644
index 0000000..1d92e9c
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Returns.java
@@ -0,0 +1,43 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// The return of a lambda is a lambda
+
+interface ConsumerSupplier {
+  ConsumerR get();
+}
+
+interface ConsumerR {
+  void method(@Nullable String s);
+}
+
+interface SupplierSupplier {
+  SupplierRe get();
+}
+
+interface SupplierRe {
+  @NonNull String method();
+}
+
+class MetaReturn {
+
+  // :: error: (dereference.of.nullable)
+  ConsumerSupplier t1 = () -> (s) -> s.toString();
+  ConsumerSupplier t2 =
+      () -> {
+        return (String s) -> {
+          // :: error: (dereference.of.nullable)
+          s.toString();
+        };
+      };
+
+  SupplierSupplier t3 =
+      () -> {
+        // :: error: (return)
+        return () -> null;
+      };
+
+  SupplierSupplier t4 =
+      () -> {
+        return ""::toString;
+      };
+}
diff --git a/checker/tests/nullness/java8/lambda/Shadowed.java b/checker/tests/nullness/java8/lambda/Shadowed.java
new file mode 100644
index 0000000..aa2b665
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/Shadowed.java
@@ -0,0 +1,28 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// Test shadowing of parameters
+
+interface ConsumerS {
+  void take(@Nullable String s);
+}
+
+interface NNConsumerS {
+  void take(String s);
+}
+
+public class Shadowed {
+
+  ConsumerS c =
+      s -> {
+        // :: error: (dereference.of.nullable)
+        s.toString();
+
+        class Inner {
+          NNConsumerS n =
+              s -> {
+                // No error
+                s.toString();
+              };
+        }
+      };
+}
diff --git a/checker/tests/nullness/java8/lambda/TypeVarAssign.java b/checker/tests/nullness/java8/lambda/TypeVarAssign.java
new file mode 100644
index 0000000..8e24949
--- /dev/null
+++ b/checker/tests/nullness/java8/lambda/TypeVarAssign.java
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// @skip-test We can only handle this after we get better method inference.
+
+interface Fn<T> {
+  T func(T t);
+}
+
+class TestAssign {
+  <M extends @NonNull Object> void foo(Fn<M> f) {}
+
+  void context() {
+    foo((@NonNull String s) -> s);
+  }
+}
diff --git a/checker/tests/nullness/java8/methodref/AssignmentContextTest.java b/checker/tests/nullness/java8/methodref/AssignmentContextTest.java
new file mode 100644
index 0000000..0e157ef
--- /dev/null
+++ b/checker/tests/nullness/java8/methodref/AssignmentContextTest.java
@@ -0,0 +1,43 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+interface FunctionAC {
+  String apply(String s);
+}
+
+interface FunctionAC2 {
+  String apply(@Nullable String s);
+}
+
+public class AssignmentContextTest {
+  // Test assign
+  FunctionAC f1 = String::toString;
+  // :: error: (methodref.receiver)
+  FunctionAC2 f2 = String::toString;
+
+  // Test casts
+  Object o1 = (Object) (FunctionAC) String::toString;
+  // :: error: (methodref.receiver)
+  Object o2 = (Object) (FunctionAC2) String::toString;
+
+  void take(FunctionAC f) {
+    // Test argument assingment
+    take(String::toString);
+  }
+
+  void take2(FunctionAC2 f) {
+    // Test argument assingment
+    // :: error: (methodref.receiver)
+    take2(String::toString);
+  }
+
+  FunctionAC supply() {
+    // Test return assingment
+    return String::toString;
+  }
+
+  FunctionAC2 supply2() {
+    // Test return assingment
+    // :: error: (methodref.receiver)
+    return String::toString;
+  }
+}
diff --git a/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java b/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java
new file mode 100644
index 0000000..e70c03c
--- /dev/null
+++ b/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java
@@ -0,0 +1,38 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class ClassTypeArgInference {
+  public static void main(String[] args) {
+    Gen<String> o = new Gen<>("");
+    // :: error: (methodref.param)
+    Factory f = Gen::make;
+    // :: error: (methodref.param)
+    Factory f2 = Gen<String>::make;
+    // :: error: (methodref.receiver) :: error: (methodref.return)
+    Factory f3 = Gen<@Nullable String>::make;
+    f2.make(o, null).toString();
+  }
+
+  static class Gen<G> {
+    G field;
+
+    Gen(G g) {
+      field = g;
+    }
+
+    public G getField() {
+      return field;
+    }
+
+    G make(G g) {
+      return g;
+    }
+
+    Gen<G> id() {
+      return this;
+    }
+  }
+
+  interface Factory {
+    String make(Gen<String> g, @Nullable String t);
+  }
+}
diff --git a/checker/tests/nullness/java8/methodref/FromByteCode.java b/checker/tests/nullness/java8/methodref/FromByteCode.java
new file mode 100644
index 0000000..663a7f7
--- /dev/null
+++ b/checker/tests/nullness/java8/methodref/FromByteCode.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+interface FunctionBC<T extends @Nullable Object, R> {
+  R apply(T t);
+}
+
+public class FromByteCode {
+
+  FunctionBC<String, String> f1 = String::toString;
+
+  // Make sure there aren't any issues generating an error with a method from byte code
+  // :: error: (methodref.param)
+  FunctionBC<@Nullable String, String> f2 = String::new;
+}
diff --git a/checker/tests/nullness/java8/methodref/GenericArity.java b/checker/tests/nullness/java8/methodref/GenericArity.java
new file mode 100644
index 0000000..fac2ce7
--- /dev/null
+++ b/checker/tests/nullness/java8/methodref/GenericArity.java
@@ -0,0 +1,25 @@
+// Test case for Issue #803
+// https://github.com/typetools/checker-framework/issues/803
+// @skip-test
+
+import org.checkerframework.checker.nullness.qual.*;
+
+interface GenFunc {
+  <T extends @Nullable Number, U extends @Nullable Number> T apply(U u);
+}
+
+interface GenFunc2 {
+  <T extends @Nullable Number, U extends @NonNull Number> T apply(U u);
+}
+
+class TestGenFunc {
+  static <V extends @NonNull Number, P extends @Nullable Number> V apply(P u) {
+    throw new RuntimeException("");
+  }
+
+  void context() {
+    GenFunc f = TestGenFunc::apply;
+    // :: error: (methodref.param)
+    GenFunc2 f2 = TestGenFunc::apply;
+  }
+}
diff --git a/checker/tests/nullness/java8/methodref/GroundTargetTypeLub.java b/checker/tests/nullness/java8/methodref/GroundTargetTypeLub.java
new file mode 100644
index 0000000..9fd60dc
--- /dev/null
+++ b/checker/tests/nullness/java8/methodref/GroundTargetTypeLub.java
@@ -0,0 +1,25 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+interface Supplier<T extends @NonNull Object> {
+  T supply();
+}
+
+interface Supplier2<T extends @Nullable Object> {
+  T supply();
+}
+
+class GroundTargetType {
+
+  static @Nullable Object myMethod() {
+    return null;
+  }
+
+  // :: error: (type.argument)
+  Supplier<? extends @Nullable Object> fn = GroundTargetType::myMethod;
+  // :: error: (methodref.return)
+  Supplier<? extends @NonNull Object> fn2 = GroundTargetType::myMethod;
+
+  // Supplier2
+  // :: error: (methodref.return)
+  Supplier2<? extends @NonNull Object> fn3 = GroundTargetType::myMethod;
+}
diff --git a/checker/tests/nullness/java8/methodref/MemberReferences.java b/checker/tests/nullness/java8/methodref/MemberReferences.java
new file mode 100644
index 0000000..125b63f
--- /dev/null
+++ b/checker/tests/nullness/java8/methodref/MemberReferences.java
@@ -0,0 +1,47 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+abstract class References {
+
+  void context(References c) {
+
+    // No error
+    FuncA funcA1 = References::aMethod1;
+    // No error, covariant parameters
+    FuncA funcA2 = References::aMethod2;
+    // :: error: (methodref.return)
+    FuncA funcA3 = References::aMethod3;
+    // :: error: (methodref.return)
+    FuncA funcA4 = References::aMethod4;
+
+    // :: error: (methodref.param)
+    FuncB funcB1 = References::aMethod1;
+    // No error
+    FuncB funcB2 = References::aMethod2;
+    // :: error: (methodref.return) :: error: (methodref.param)
+    FuncB funcB3 = References::aMethod3;
+    // :: error: (methodref.return)
+    FuncB funcB4 = References::aMethod4;
+
+    FuncA typeArg1 = References::<@NonNull String>aMethod5;
+    // :: error: (methodref.param)
+    FuncB typeArg2 = References::<@NonNull String>aMethod5;
+  }
+
+  abstract @NonNull String aMethod1(@NonNull String s);
+
+  abstract @NonNull String aMethod2(@Nullable String s);
+
+  abstract @Nullable String aMethod3(@NonNull String s);
+
+  abstract @Nullable String aMethod4(@Nullable String s);
+
+  abstract <T> T aMethod5(T t);
+
+  interface FuncA {
+    @NonNull String method(References a, @NonNull String b);
+  }
+
+  interface FuncB {
+    @NonNull String method(References a, @Nullable String b);
+  }
+}
diff --git a/checker/tests/nullness/java8/methodref/PolyNullness.java b/checker/tests/nullness/java8/methodref/PolyNullness.java
new file mode 100644
index 0000000..a793919
--- /dev/null
+++ b/checker/tests/nullness/java8/methodref/PolyNullness.java
@@ -0,0 +1,34 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+interface PolyFunc {
+  @PolyNull String method(@PolyNull String in);
+}
+
+interface NonNullFunc {
+  @NonNull String method(@NonNull String in);
+}
+
+interface MixedFunc {
+  @NonNull String method(@Nullable String in);
+}
+
+class Context {
+
+  static @PolyNull String poly(@PolyNull String in) {
+    return in;
+  }
+
+  static String nonPoly(String in) {
+    return in;
+  }
+
+  void context() {
+    PolyFunc f1 = Context::poly;
+    // :: error: (methodref.param)
+    PolyFunc f2 = Context::nonPoly;
+
+    NonNullFunc f3 = Context::poly;
+    // :: error: (methodref.return)
+    MixedFunc f4 = Context::poly;
+  }
+}
diff --git a/checker/tests/nullness/java8/methodref/Postconditions.java b/checker/tests/nullness/java8/methodref/Postconditions.java
new file mode 100644
index 0000000..cf53f38
--- /dev/null
+++ b/checker/tests/nullness/java8/methodref/Postconditions.java
@@ -0,0 +1,33 @@
+// Test case for Issue #801
+// https://github.com/typetools/checker-framework/issues/801
+// @skip-test
+// TODO: Enable postcondition override checks for method references
+
+import org.checkerframework.checker.nullness.qual.*;
+
+interface AssertFunc {
+  @EnsuresNonNullIf(result = true, expression = "#1")
+  boolean testParam(final @Nullable Object param);
+}
+
+interface AssertFunc2 {
+  @EnsuresNonNullIf(result = true, expression = "#1")
+  boolean testParam(final @Nullable Object param);
+}
+
+public class AssertionTest {
+  @EnsuresNonNullIf(result = true, expression = "#1")
+  static boolean override(final @Nullable Object param) {
+    return param != null;
+  }
+
+  static boolean overrideAssertFunc2(final @Nullable Object param) {
+    return param != null;
+  }
+
+  void context() {
+    AssertFunc f = AssertionTest::override;
+    // :: error: (methodref.receiver.postcondition)
+    AssertFunc2 f2 = AssertionTest::overrideAssertFunc2;
+  }
+}
diff --git a/checker/tests/nullness/java8/methodref/ReceiversMethodref.java b/checker/tests/nullness/java8/methodref/ReceiversMethodref.java
new file mode 100644
index 0000000..883007c
--- /dev/null
+++ b/checker/tests/nullness/java8/methodref/ReceiversMethodref.java
@@ -0,0 +1,96 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+// Nullable receivers don't make a lot of sense
+// But this class tests the sub supertype recevier relationships.
+// It could just use tainted.
+
+interface Unbound1 {
+  void apply(@NonNull MyClass my);
+}
+
+interface Unbound2 {
+  void apply(@Nullable MyClass my);
+}
+
+interface Supplier1<R extends @Nullable Object> {
+  R supply();
+}
+
+interface Bound {
+  void apply();
+}
+
+class MyClass {
+  // :: error: (nullness.on.receiver)
+  void take(@NonNull MyClass this) {}
+
+  // :: error: (nullness.on.receiver)
+  void context1(@Nullable MyClass this, @NonNull MyClass my1, @Nullable MyClass my2) {
+
+    Unbound1 u1 = MyClass::take;
+    // :: error: (methodref.receiver)
+    Unbound2 u2 = MyClass::take;
+
+    Bound b1 = my1::take;
+    // :: error: (methodref.receiver.bound)
+    Bound b2 = my2::take;
+
+    // :: error: (methodref.receiver.bound)
+    Bound b11 = this::take;
+  }
+
+  // :: error: (nullness.on.receiver)
+  void context2(@NonNull MyClass this) {
+    Bound b21 = this::take;
+  }
+
+  class MySubClass extends MyClass {
+
+    // :: error: (nullness.on.receiver)
+    void context1(@Nullable MySubClass this) {
+      // :: error: (methodref.receiver.bound)
+      Bound b = super::take;
+    }
+
+    // :: error: (nullness.on.receiver)
+    void context2(@NonNull MySubClass this) {
+      Bound b = super::take;
+    }
+
+    class Nested {
+      // :: error: (nullness.on.receiver)
+      void context1(@Nullable Nested this) {
+        // :: error: (methodref.receiver.bound)
+        Bound b = MySubClass.super::take;
+      }
+
+      // :: error: (nullness.on.receiver)
+      void context2(@NonNull Nested this) {
+        Bound b = MySubClass.super::take;
+      }
+    }
+  }
+}
+
+class Outer {
+  class Inner1 {
+    // :: error: (nullness.on.receiver)
+    Inner1(@Nullable Outer Outer.this) {}
+  }
+
+  class Inner2 {
+    // :: error: (nullness.on.receiver)
+    Inner2(@NonNull Outer Outer.this) {}
+  }
+
+  // :: error: (nullness.on.receiver)
+  void context(@Nullable Outer this) {
+    // This one is unbound and needs an Outer as a param
+    Supplier1<Inner1> f1 = Inner1::new;
+    // :: error: (methodref.receiver.bound)
+    Supplier1<Inner2> f2 = Inner2::new;
+
+    // Supplier1</*3*/Inner> f = /*4*/Inner::new;
+    // 4 <: 3? Constructor annotations?
+  }
+}
diff --git a/checker/tests/nullness/java8inference/FalsePositives.java b/checker/tests/nullness/java8inference/FalsePositives.java
new file mode 100644
index 0000000..c8ff17b
--- /dev/null
+++ b/checker/tests/nullness/java8inference/FalsePositives.java
@@ -0,0 +1,63 @@
+// Some of these code was submitted in #1384.
+// https://github.com/typetools/checker-framework/issues/1384
+// Other parts are from the following comment.
+// https://github.com/typetools/checker-framework/pull/1387#issuecomment-316147360
+// The rest is from plume-lib.
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Queue;
+
+public class FalsePositives {
+  static class Partitioning<F> {}
+
+  public static <T> List<Partitioning<T>> partitionInto(Queue<T> elts, int k) {
+    if (elts.size() < k) {
+      throw new IllegalArgumentException();
+    }
+    return partitionIntoHelper(elts, Arrays.asList(new Partitioning<T>()), k, 0);
+  }
+
+  public static <T> List<Partitioning<T>> partitionIntoHelper(
+      Queue<T> elts, List<Partitioning<T>> resultSoFar, int numEmptyParts, int numNonemptyParts) {
+    throw new RuntimeException();
+  }
+
+  interface Box<T> {}
+
+  interface Function<P, R> {
+    R apply(P p);
+  }
+
+  interface Utils {
+    <I, O> Box<O> foo(Box<I> input, Function<? super I, ? extends O> function);
+
+    <I, O> Function<I, O> bar(Function<? super I, ? extends O> function);
+  }
+
+  class Test {
+    Box<Integer> demo(Utils u, Box<String> bs) {
+      return u.foo(bs, u.bar((String s) -> 5));
+    }
+
+    Integer ugh(String n) {
+      return 5;
+    }
+
+    Box<Integer> demo2(Utils u, Box<String> bs) {
+      return u.foo(bs, u.bar(this::ugh));
+    }
+  }
+
+  abstract class Test2 {
+    abstract <T> Box<Box<T>> foo(Box<? extends Box<? extends T>> p);
+
+    abstract <T> Box<Box<T>> bar(Function<Number, T> f);
+
+    abstract String baz(Number p);
+
+    Box<Box<String>> demo() {
+      return foo(bar(this::baz));
+    }
+  }
+}
diff --git a/checker/tests/nullness/java8inference/InLambda.java b/checker/tests/nullness/java8inference/InLambda.java
new file mode 100644
index 0000000..b82b9dc
--- /dev/null
+++ b/checker/tests/nullness/java8inference/InLambda.java
@@ -0,0 +1,29 @@
+public class InLambda {
+  static class Mine<T> {
+    @SuppressWarnings("nullness") // just a utility
+    static <S> Mine<S> some() {
+      return null;
+    }
+  }
+
+  interface Function<T, R> {
+    R apply(T t);
+  }
+
+  interface Box<V> {}
+
+  static class Boxes {
+    @SuppressWarnings("nullness") // just a utility
+    static <O> Box<O> transform(Function<String, ? extends O> function) {
+      return null;
+    }
+  }
+
+  class Infer {
+    Box<Mine<Integer>> f =
+        Boxes.transform(
+            el -> {
+              return Mine.some();
+            });
+  }
+}
diff --git a/checker/tests/nullness/java8inference/InLambdaAnnotated.java b/checker/tests/nullness/java8inference/InLambdaAnnotated.java
new file mode 100644
index 0000000..79f4968
--- /dev/null
+++ b/checker/tests/nullness/java8inference/InLambdaAnnotated.java
@@ -0,0 +1,38 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class InLambdaAnnotated {
+  static class Mine<T> {
+    @SuppressWarnings("nullness") // just a utility
+    static <S> Mine<S> some() {
+      return null;
+    }
+  }
+
+  interface Function<T, R> {
+    R apply(T t);
+  }
+
+  interface Box<V> {}
+
+  static class Boxes {
+    @SuppressWarnings("nullness") // just a utility
+    static <O> Box<O> transform(Function<String, ? extends O> function) {
+      return null;
+    }
+  }
+
+  class Infer {
+    // The nested Mine.some() needs to infer the right type.
+    Box<Mine<@Nullable Integer>> g =
+        // TODO: This is a false positive.
+        // :: error: (assignment)
+        Boxes.transform(
+            el -> {
+              return Mine.some();
+            });
+
+    void bar(Function<String, Mine<@Nullable Integer>> fun) {
+      Box<Mine<@Nullable Integer>> h = Boxes.transform(fun);
+    }
+  }
+}
diff --git a/checker/tests/nullness/java8inference/Inference.java b/checker/tests/nullness/java8inference/Inference.java
new file mode 100644
index 0000000..3aec441
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Inference.java
@@ -0,0 +1,29 @@
+// Test case for Issue 979:
+// https://github.com/typetools/checker-framework/issues/979
+
+class MyStream<T> {
+  @SuppressWarnings("nullness")
+  <R, A> R collect(MyCollector<? super T, A, R> collector) {
+    return null;
+  }
+}
+
+interface MyCollector<T, A, R> {}
+
+public class Inference {
+
+  @SuppressWarnings("nullness")
+  static <E> MyCollector<E, ?, MyStream<E>> toImmutableStream() {
+    return null;
+  }
+
+  MyStream<String> test(MyStream<String> p) {
+    /* Need Java 8 assignment context to correctly infer type arguments.
+         return p.collect(toImmutableStream());
+                    ^
+       found   : @Initialized @NonNull MyStream<? extends @Initialized @Nullable Object>
+       required: @Initialized @NonNull MyStream<@Initialized @NonNull String>
+    */
+    return p.collect(toImmutableStream());
+  }
+}
diff --git a/checker/tests/nullness/java8inference/InferenceSimpler.java b/checker/tests/nullness/java8inference/InferenceSimpler.java
new file mode 100644
index 0000000..f7ea9b8
--- /dev/null
+++ b/checker/tests/nullness/java8inference/InferenceSimpler.java
@@ -0,0 +1,21 @@
+// Test case for Issue 979:
+// https://github.com/typetools/checker-framework/issues/979
+
+import java.util.List;
+
+@SuppressWarnings("nullness") // don't bother with implementations
+class ISOuter {
+  static <V> List<V> wrap(V value) {
+    return null;
+  }
+
+  static <T> List<T> empty() {
+    return null;
+  }
+}
+
+public class InferenceSimpler {
+  List<List<String>> foo() {
+    return ISOuter.wrap(ISOuter.empty());
+  }
+}
diff --git a/checker/tests/nullness/java8inference/Issue1032.java b/checker/tests/nullness/java8inference/Issue1032.java
new file mode 100644
index 0000000..af01865
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue1032.java
@@ -0,0 +1,32 @@
+// Test case for issue #1032:
+// https://github.com/typetools/checker-framework/issues/1032
+
+import java.util.stream.Stream;
+import org.checkerframework.checker.nullness.qual.*;
+
+public class Issue1032 {
+
+  @SuppressWarnings("nullness")
+  static @NonNull String castStringToNonNull(@Nullable String arg) {
+    return (@NonNull String) arg;
+  }
+
+  Stream<@NonNull String> mapStringCast1(Stream<@Nullable String> arg) {
+    return arg.map(Issue1032::castStringToNonNull);
+  }
+
+  @SuppressWarnings("nullness")
+  static <T> @NonNull T castTToNonNull(@Nullable T arg) {
+    return (@NonNull T) arg;
+  }
+
+  Stream<@NonNull String> mapStringCast2(Stream<@Nullable String> arg) {
+    return arg.map(Issue1032::<String>castTToNonNull);
+  }
+
+  <T> Stream<@NonNull T> mapTCast(Stream<@Nullable T> arg) {
+    // TODO: false postive
+    // :: error: (return)
+    return arg.map(Issue1032::<T>castTToNonNull);
+  }
+}
diff --git a/checker/tests/nullness/java8inference/Issue1084.java b/checker/tests/nullness/java8inference/Issue1084.java
new file mode 100644
index 0000000..b0fd270
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue1084.java
@@ -0,0 +1,22 @@
+// Test case for Issue 1084
+// https://github.com/typetools/checker-framework/issues/1084
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+class MyOpt<T extends Object> {
+  static <S> MyOpt<@NonNull S> empty() {
+    throw new RuntimeException();
+  }
+
+  static <S> MyOpt<S> of(S p) {
+    throw new RuntimeException();
+  }
+}
+
+public class Issue1084 {
+  MyOpt<Long> get() {
+    return this.hashCode() > 0 ? MyOpt.of(5L) : MyOpt.empty();
+  }
+
+  MyOpt<byte[]> oba = MyOpt.empty();
+}
diff --git a/checker/tests/nullness/java8inference/Issue1366.java b/checker/tests/nullness/java8inference/Issue1366.java
new file mode 100644
index 0000000..9cb3b37
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue1366.java
@@ -0,0 +1,13 @@
+// Test case for Issue 1366.
+// https://github.com/typetools/checker-framework/issues/1366
+abstract class Issue1366<T> {
+  abstract <S> Issue1366<S> m1(Issue1366<S> p1, Issue1366<?> p2);
+
+  abstract <S> Issue1366<S> m2(Issue1366<? extends S> p);
+
+  abstract void m3(Issue1366<Number> p);
+
+  void foo(Issue1366<Number> s) {
+    s.m3(s.m2(s.m1(s, s)));
+  }
+}
diff --git a/checker/tests/nullness/java8inference/Issue1464.java b/checker/tests/nullness/java8inference/Issue1464.java
new file mode 100644
index 0000000..743f014
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue1464.java
@@ -0,0 +1,22 @@
+// Test case for issue 1464
+// https://github.com/typetools/checker-framework/issues/1464
+
+public class Issue1464 {
+
+  public interface Variable<T> {
+
+    void addChangedListener(VariableChangedListener<T> listener);
+  }
+
+  public interface VariableChangedListener<T> {
+
+    void variableChanged(final Variable<T> variable);
+  }
+
+  protected <T> void addChangedListener(
+      final Variable<T> variable, final VariableChangedListener<T> listener) {}
+
+  public void main(final Variable<?> tmp) {
+    addChangedListener(tmp, variable -> System.out.println(variable));
+  }
+}
diff --git a/checker/tests/nullness/java8inference/Issue1630.java b/checker/tests/nullness/java8inference/Issue1630.java
new file mode 100644
index 0000000..c242af2
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue1630.java
@@ -0,0 +1,18 @@
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1630 {
+  static @Nullable String toString(Object o) {
+    return null;
+  }
+
+  @SuppressWarnings("nullness") // Issue 979
+  public static List<String> f(List<Integer> xs) {
+    return xs != null
+        ? xs.stream().map(Issue1630::toString).filter(Objects::nonNull).collect(Collectors.toList())
+        : Collections.emptyList();
+  }
+}
diff --git a/checker/tests/nullness/java8inference/Issue1818.java b/checker/tests/nullness/java8inference/Issue1818.java
new file mode 100644
index 0000000..df18d9c
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue1818.java
@@ -0,0 +1,11 @@
+import java.util.List;
+import java.util.function.Consumer;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1818 {
+  void f() {
+    Consumer<List<?>> c = values -> values.forEach(value -> g(value));
+  }
+
+  void g(@Nullable Object o) {}
+}
diff --git a/checker/tests/nullness/java8inference/Issue1954.java b/checker/tests/nullness/java8inference/Issue1954.java
new file mode 100644
index 0000000..f5091c8
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue1954.java
@@ -0,0 +1,26 @@
+import java.util.function.*;
+import java.util.stream.*;
+import org.checkerframework.checker.nullness.qual.*;
+
+// @skip-test
+public class Issue1954 {
+  public interface Getter<R> {
+    R get();
+  }
+
+  public interface NullStringGetter extends Getter<@Nullable String> {}
+
+  public <T, R> Getter<R> transform(Function<Stream<T>, R> fn, Getter<T> getter) {
+    return () -> fn.apply(Stream.of(getter.get()));
+  }
+
+  public static <T> @Nullable T fn(Stream<T> arg) {
+    return arg.findFirst().orElse(null);
+  }
+
+  public void doo() {
+    NullStringGetter nullStringGetter = () -> null;
+    // :: error: type inference failed.
+    transform(Issue1954::fn, nullStringGetter).get();
+  }
+}
diff --git a/checker/tests/nullness/java8inference/Issue2235.java b/checker/tests/nullness/java8inference/Issue2235.java
new file mode 100644
index 0000000..4d0eb00
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue2235.java
@@ -0,0 +1,30 @@
+// Test case that was submitted in Issue 2235, but is caused
+// by a false negative from Issue 979
+// https://github.com/typetools/checker-framework/issues/979
+
+// Skip until correct error is issued.
+// @skip-test
+
+public class Issue2235 {
+  // Simple wrapper class with a public generic method
+  // to make an instance:
+  static class Holder<T> {
+    T t;
+
+    private Holder(T t) {
+      this.t = t;
+    }
+
+    public static <T> Holder<T> make(T t) {
+      return new Holder<>(t);
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    // Null is hidden via nested calls, but assigned to a non-null type:
+    // :: error: (TODO)
+    Holder<Holder<String>> h = Holder.make(Holder.make(null));
+    // NullPointerException will fire here:
+    h.t.t.toString();
+  }
+}
diff --git a/checker/tests/nullness/java8inference/Issue2719.java b/checker/tests/nullness/java8inference/Issue2719.java
new file mode 100644
index 0000000..b6ea4e7
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue2719.java
@@ -0,0 +1,18 @@
+import static java.util.Arrays.asList;
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue2719 {
+  public static void main(String[] args) {
+    List<Integer> iList = asList(0);
+    List<@Nullable Integer> jList = asList((Integer) null);
+    // TODO:: error:  (assignment)
+    List<List<Integer>> both = passThrough(asList(iList, jList));
+    System.out.println(both.get(1).get(0).intValue());
+  }
+
+  static <T> T passThrough(T object) {
+    return object;
+  }
+}
diff --git a/checker/tests/nullness/java8inference/Issue402.java b/checker/tests/nullness/java8inference/Issue402.java
new file mode 100644
index 0000000..8aa18c8
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue402.java
@@ -0,0 +1,33 @@
+// Test case that was submitted in Issue 402, but was combined with Issue 979
+// https://github.com/typetools/checker-framework/issues/979
+
+import java.util.Comparator;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+// Type argument inference does not infer the correct types.
+// Once Issue 979 is fixed, this suppression should be removed.
+@SuppressWarnings({"nullness", "keyfor"}) // Issue 979
+public final class Issue402 {
+  static final Comparator<Issue402> COMPARATOR =
+      Comparator.comparing(Issue402::getStr1, Comparator.nullsFirst(Comparator.naturalOrder()))
+          .thenComparing(Issue402::getStr2, Comparator.nullsFirst(Comparator.naturalOrder()));
+
+  @CheckForNull private final String str1;
+  @CheckForNull private final String str2;
+
+  Issue402(@Nullable final String str1, @Nullable final String str2) {
+    this.str1 = str1;
+    this.str2 = str2;
+  }
+
+  @CheckForNull
+  String getStr1() {
+    return this.str1;
+  }
+
+  @CheckForNull
+  String getStr2() {
+    return this.str2;
+  }
+}
diff --git a/checker/tests/nullness/java8inference/Issue4048.java b/checker/tests/nullness/java8inference/Issue4048.java
new file mode 100644
index 0000000..846b8a0
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue4048.java
@@ -0,0 +1,20 @@
+// Test case for issue #4048: https://tinyurl.com/cfissue/4048
+
+// @skip-test until the issue is fixed
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+abstract class Issue4048 {
+  @Nullable Number m1(List<? extends Number> numbers) {
+    return getOnlyElement1(numbers);
+  }
+
+  abstract <T> @Nullable T getOnlyElement1(Iterable<T> values);
+
+  @Nullable Number m2(List<? extends Number> numbers) {
+    return getOnlyElement2(numbers);
+  }
+
+  abstract <T> @Nullable T getOnlyElement2(Iterable<? extends T> values);
+}
diff --git a/checker/tests/nullness/java8inference/Issue887.java b/checker/tests/nullness/java8inference/Issue887.java
new file mode 100644
index 0000000..09538bc
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue887.java
@@ -0,0 +1,22 @@
+// Test case for Issue 887
+// https://github.com/typetools/checker-framework/issues/887
+// Additional test case in framework/tests/all-systems/Issue887.java
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+public abstract class Issue887 {
+  void test() {
+    // :: error: (argument) :: error: (type.argument)
+    method(foo(null).get(0));
+    methodNullable(fooNullable(null).get(0));
+  }
+
+  void method(Number o) {}
+
+  void methodNullable(@Nullable Number o) {}
+
+  abstract <T extends Number> List<? extends T> foo(T t);
+
+  abstract <T extends @Nullable Number> List<? extends T> fooNullable(T t);
+}
diff --git a/checker/tests/nullness/java8inference/Issue953bInference.java b/checker/tests/nullness/java8inference/Issue953bInference.java
new file mode 100644
index 0000000..3c68a87
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue953bInference.java
@@ -0,0 +1,25 @@
+// Test case that was submitted in Issue 953, but was combined with Issue 979
+// https://github.com/typetools/checker-framework/issues/979
+
+import java.util.*;
+import java.util.function.Function;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class Issue953bInference {
+  private static List<List<?>> strs = new ArrayList<>();
+
+  public static <R, T> List<@NonNull R> mapList(
+      List<@NonNull T> list, Function<@NonNull T, @NonNull R> func) {
+    ArrayList<@NonNull R> r = new ArrayList<>(list.size());
+    for (T t : list) r.add(func.apply(t));
+    return r;
+  }
+
+  public static List<String> test() {
+    return mapList(
+        strs,
+        s -> {
+          return new String();
+        });
+  }
+}
diff --git a/checker/tests/nullness/java8inference/Issue980.java b/checker/tests/nullness/java8inference/Issue980.java
new file mode 100644
index 0000000..9fa8abe
--- /dev/null
+++ b/checker/tests/nullness/java8inference/Issue980.java
@@ -0,0 +1,23 @@
+// Test case that was submitted in Issue 402, but was combined with Issue 979
+// https://github.com/typetools/checker-framework/issues/979
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue980 {
+
+  void m(List<String> strings) {
+    Stream<String> s = strings.stream();
+
+    // This works:
+    List<String> collectedStrings1 = s.collect(Collectors.<String>toList());
+    // This works:
+    List<@Nullable String> collectedStrings2 = s.collect(Collectors.toList());
+
+    List<String> collectedStrings = s.collect(Collectors.toList());
+
+    collectedStrings.forEach(System.out::println);
+  }
+}
diff --git a/checker/tests/nullness/java8inference/OneOf.java b/checker/tests/nullness/java8inference/OneOf.java
new file mode 100644
index 0000000..224812a
--- /dev/null
+++ b/checker/tests/nullness/java8inference/OneOf.java
@@ -0,0 +1,23 @@
+// Test case for Issue 979:
+// https://github.com/typetools/checker-framework/issues/979
+
+import java.util.List;
+
+@SuppressWarnings("nullness") // don't bother with implementations
+public class OneOf {
+  static List<String> alist;
+
+  static <V> V oneof(V v1, V v2) {
+    return v1;
+  }
+
+  static <T> List<T> empty() {
+    return null;
+  }
+}
+
+class OneOfUse {
+  List<String> foo() {
+    return OneOf.oneof(OneOf.alist, OneOf.empty());
+  }
+}
diff --git a/checker/tests/nullness/java8inference/SimpleLambda.java b/checker/tests/nullness/java8inference/SimpleLambda.java
new file mode 100644
index 0000000..59f6427
--- /dev/null
+++ b/checker/tests/nullness/java8inference/SimpleLambda.java
@@ -0,0 +1,17 @@
+// @skip-test until Issue 979 is fixed.
+
+import java.util.function.Supplier;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class SimpleLambda {
+  <T> T perform(Supplier<T> p) {
+    return p.get();
+  }
+
+  void test() {
+    @Nullable String s1 = perform(() -> (String) null);
+    @Nullable String s2 = this.<@Nullable String>perform(() -> (String) null);
+    @NonNull String s3 = perform(() -> "");
+  }
+}
diff --git a/checker/tests/nullness/jdkannotations/HashtableTest.java b/checker/tests/nullness/jdkannotations/HashtableTest.java
new file mode 100644
index 0000000..c56180f
--- /dev/null
+++ b/checker/tests/nullness/jdkannotations/HashtableTest.java
@@ -0,0 +1,20 @@
+import java.util.Hashtable;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class HashtableTest {
+
+  public static void main(String[] args) {
+
+    // :: error: (type.argument)
+    Hashtable<@Nullable Integer, String> ht1 = new Hashtable<>();
+
+    // Suffers null pointer exception
+    ht1.put(null, "hello");
+
+    // :: error: (type.argument)
+    Hashtable<Integer, @Nullable String> ht2 = new Hashtable<>();
+
+    // Suffers null pointer exception
+    ht2.put(42, null);
+  }
+}
diff --git a/checker/tests/nullness/jdkannotations/Issue1142.java b/checker/tests/nullness/jdkannotations/Issue1142.java
new file mode 100644
index 0000000..0bbeaca
--- /dev/null
+++ b/checker/tests/nullness/jdkannotations/Issue1142.java
@@ -0,0 +1,13 @@
+// Issue 1142 https://github.com/typetools/checker-framework/issues/1142
+
+import java.util.concurrent.ConcurrentHashMap;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class Issue1142 {
+
+  void foo() {
+    // :: error: (type.argument)
+    ConcurrentHashMap<Integer, @Nullable Integer> chm1 = new ConcurrentHashMap<>();
+    chm1.put(1, null);
+  }
+}
diff --git a/checker/tests/nullness/jdkannotations/Issue1402EnumName.java b/checker/tests/nullness/jdkannotations/Issue1402EnumName.java
new file mode 100644
index 0000000..880e780
--- /dev/null
+++ b/checker/tests/nullness/jdkannotations/Issue1402EnumName.java
@@ -0,0 +1,15 @@
+// Test annotation on Enum.name()
+
+// Test case for Issue 1402:
+// https://github.com/typetools/checker-framework/issues/1402
+
+public enum Issue1402EnumName {
+  TEST_ONE("abc"),
+  TEST_TWO("def");
+
+  private final String newName;
+
+  Issue1402EnumName(String customData) {
+    this.newName = name();
+  }
+}
diff --git a/checker/tests/nullness/jdkannotations/TreeSetTest.java b/checker/tests/nullness/jdkannotations/TreeSetTest.java
new file mode 100644
index 0000000..bd20e60
--- /dev/null
+++ b/checker/tests/nullness/jdkannotations/TreeSetTest.java
@@ -0,0 +1,19 @@
+// Partial test case for issue #1330: https://github.com/typetools/checker-framework/issues/1330
+// This should be expanded to include all the cases in the issue.
+
+// @skip-test until we fix the issue
+
+import java.util.TreeSet;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class TreeSetTest {
+
+  public static void main(String[] args) {
+
+    // :: error: (type.argument)
+    TreeSet<@Nullable Integer> ts = new TreeSet<>();
+
+    // This throws a null pointer exception
+    ts.add(null);
+  }
+}
diff --git a/checker/tests/optional/FlowSensitivity.java b/checker/tests/optional/FlowSensitivity.java
new file mode 100644
index 0000000..69a05d9
--- /dev/null
+++ b/checker/tests/optional/FlowSensitivity.java
@@ -0,0 +1,26 @@
+import java.util.Optional;
+
+/** Test case for flow-sensitivity of Optional.isPresent(). */
+@SuppressWarnings("optional.parameter")
+public class FlowSensitivity {
+
+  String noCheck(Optional<String> opt) {
+    // :: error: (method.invocation)
+    return opt.get();
+  }
+
+  String hasCheck1(Optional<String> opt) {
+    if (opt.isPresent()) {
+      return opt.get();
+    } else {
+      return "default";
+    }
+  }
+
+  String hasCheck2(Optional<String> opt) {
+    if (!opt.isPresent()) {
+      return "default";
+    }
+    return opt.get();
+  }
+}
diff --git a/checker/tests/optional/JdkCheck.java b/checker/tests/optional/JdkCheck.java
new file mode 100644
index 0000000..3927704
--- /dev/null
+++ b/checker/tests/optional/JdkCheck.java
@@ -0,0 +1,79 @@
+import java.util.Optional;
+import java.util.function.Supplier;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.optional.qual.Present;
+
+/** Test JDK annotations. */
+@SuppressWarnings("optional.parameter")
+public class JdkCheck {
+
+  boolean isPresentTest1(@Present Optional<String> pos) {
+    return pos.isPresent();
+  }
+
+  boolean isPresentTest2(Optional<String> mos) {
+    return mos.isPresent();
+  }
+
+  String orElseThrowTest1(
+      @Present Optional<String> pos, Supplier<RuntimeException> exceptionSupplier) {
+    return pos.orElseThrow(exceptionSupplier);
+  }
+
+  String orElseThrowTest2(Optional<String> mos, Supplier<RuntimeException> exceptionSupplier) {
+    // :: error: (method.invocation)
+    return mos.orElseThrow(exceptionSupplier);
+  }
+
+  String orElseThrowTestFlow(Optional<String> mos, Supplier<RuntimeException> exceptionSupplier) {
+    // :: error: (method.invocation)
+    mos.orElseThrow(exceptionSupplier);
+    return mos.get();
+  }
+
+  String getTest1(@Present Optional<String> pos) {
+    return pos.get();
+  }
+
+  String getTest2(Optional<String> mos) {
+    // :: error: (method.invocation)
+    return mos.get();
+  }
+
+  @Present Optional<String> ofTestPNn(String s) {
+    return Optional.of(s);
+  }
+
+  Optional<String> ofTestMNn(String s) {
+    return Optional.of(s);
+  }
+
+  @Present Optional<String> ofTestPNble(@Nullable String s) {
+    // TODO :: error: (of.nullable.argument) :: error: (return)
+    return Optional.of(s);
+  }
+
+  Optional<String> ofTestMNble(@Nullable String s) {
+    // TODO :: error: (of.nullable.argument) :: error: (return)
+    return Optional.of(s);
+  }
+
+  @Present Optional<String> ofNullableTestPNble(@Nullable String s) {
+    // :: error: (return)
+    return Optional.ofNullable(s);
+  }
+
+  /* TODO: ofNullable with non-null arg gives @Present (+ a warning?)
+  @Present Optional<String> ofNullableTestPNn(String s) {
+      return Optional.ofNullable(s);
+  }
+  */
+
+  Optional<String> ofNullableTestMNble(@Nullable String s) {
+    return Optional.ofNullable(s);
+  }
+
+  Optional<String> ofNullableTestMNn(String s) {
+    return Optional.ofNullable(s);
+  }
+}
diff --git a/checker/tests/optional/JdkCheck11.java b/checker/tests/optional/JdkCheck11.java
new file mode 100644
index 0000000..e27485b
--- /dev/null
+++ b/checker/tests/optional/JdkCheck11.java
@@ -0,0 +1,31 @@
+// @below-java11-jdk-skip-test
+
+import java.util.Optional;
+import org.checkerframework.checker.optional.qual.Present;
+
+/** Test JDK annotations, for methods added after JDK 8. */
+@SuppressWarnings("optional.parameter")
+public class JdkCheck11 {
+
+  String isEmptyTest1(Optional<String> pos, String fallback) {
+    if (pos.isEmpty()) {
+      return fallback;
+    }
+    return pos.get();
+  }
+
+  String orElseThrowTest1(@Present Optional<String> pos) {
+    return pos.orElseThrow();
+  }
+
+  String orElseThrowTest2(Optional<String> mos) {
+    // :: error: (method.invocation)
+    return mos.orElseThrow();
+  }
+
+  String orElseThrowTestFlow(Optional<String> mos) {
+    // :: error: (method.invocation)
+    mos.orElseThrow();
+    return mos.get();
+  }
+}
diff --git a/checker/tests/optional/Marks2.java b/checker/tests/optional/Marks2.java
new file mode 100644
index 0000000..dbaf829
--- /dev/null
+++ b/checker/tests/optional/Marks2.java
@@ -0,0 +1,25 @@
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Test case for rule #2: "Never use Optional.get() unless you can prove that the Optional is
+ * present."
+ */
+public class Marks2 {
+
+  class Customer {
+    int getID() {
+      return 42;
+    }
+
+    String getName() {
+      return "Fozzy Bear";
+    }
+  }
+
+  String customerNameByID(List<Customer> custList, int custID) {
+    Optional<Customer> opt = custList.stream().filter(c -> c.getID() == custID).findFirst();
+    // :: error: (method.invocation)
+    return opt.get().getName();
+  }
+}
diff --git a/checker/tests/optional/Marks3a.java b/checker/tests/optional/Marks3a.java
new file mode 100644
index 0000000..3850624
--- /dev/null
+++ b/checker/tests/optional/Marks3a.java
@@ -0,0 +1,38 @@
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Test case for rule #3: "Prefer alternative APIs over Optional.isPresent() and Optional.get()."
+ */
+public class Marks3a {
+
+  class Customer {
+    int getID() {
+      return 42;
+    }
+
+    String getName() {
+      return "Fozzy Bear";
+    }
+  }
+
+  String customerNameByID_acceptable(List<Customer> custList, int custID) {
+    Optional<Customer> opt = custList.stream().filter(c -> c.getID() == custID).findFirst();
+
+    // :: warning: (prefer.map.and.orelse)
+    return opt.isPresent() ? opt.get().getName() : "UNKNOWN";
+  }
+
+  String customerNameByID_acceptable2(List<Customer> custList, int custID) {
+    Optional<Customer> opt = custList.stream().filter(c -> c.getID() == custID).findFirst();
+
+    // :: warning: (prefer.map.and.orelse)
+    return !opt.isPresent() ? "UNKNOWN" : opt.get().getName();
+  }
+
+  String customerNameByID_better(List<Customer> custList, int custID) {
+    Optional<Customer> opt = custList.stream().filter(c -> c.getID() == custID).findFirst();
+
+    return opt.map(Customer::getName).orElse("UNKNOWN");
+  }
+}
diff --git a/checker/tests/optional/Marks3aJdk11.java b/checker/tests/optional/Marks3aJdk11.java
new file mode 100644
index 0000000..ab38e5d
--- /dev/null
+++ b/checker/tests/optional/Marks3aJdk11.java
@@ -0,0 +1,27 @@
+// @below-java11-jdk-skip-test
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Test case for rule #3: "Prefer alternative APIs over Optional.isPresent() and Optional.get()."
+ */
+public class Marks3aJdk11 {
+
+  class Customer {
+    int getID() {
+      return 42;
+    }
+
+    String getName() {
+      return "Fozzy Bear";
+    }
+  }
+
+  String customerNameByID_acceptable3(List<Customer> custList, int custID) {
+    Optional<Customer> opt = custList.stream().filter(c -> c.getID() == custID).findFirst();
+
+    // :: warning: (prefer.map.and.orelse)
+    return opt.isEmpty() ? "UNKNOWN" : opt.get().getName();
+  }
+}
diff --git a/checker/tests/optional/Marks3b.java b/checker/tests/optional/Marks3b.java
new file mode 100644
index 0000000..877cf3e
--- /dev/null
+++ b/checker/tests/optional/Marks3b.java
@@ -0,0 +1,32 @@
+import java.util.Optional;
+
+/**
+ * Test case for rule #3: "Prefer alternative APIs over Optional.isPresent() and Optional.get()."
+ */
+@SuppressWarnings("optional.parameter")
+public class Marks3b {
+
+  class Task {}
+
+  class Executor {
+    void runTask(Task t) {}
+  }
+
+  Executor executor = new Executor();
+
+  void bad(Optional<Task> oTask) {
+    // :: warning: (prefer.ifpresent)
+    if (oTask.isPresent()) {
+      executor.runTask(oTask.get());
+    }
+  }
+
+  void better(Optional<Task> oTask) {
+    // no warning; better code is possible but has nothing to do with Optional
+    oTask.ifPresent(task -> executor.runTask(task));
+  }
+
+  void best(Optional<Task> oTask) {
+    oTask.ifPresent(executor::runTask);
+  }
+}
diff --git a/checker/tests/optional/Marks3bJdk11.java b/checker/tests/optional/Marks3bJdk11.java
new file mode 100644
index 0000000..feae955
--- /dev/null
+++ b/checker/tests/optional/Marks3bJdk11.java
@@ -0,0 +1,33 @@
+// @below-java11-jdk-skip-test
+
+import java.util.Optional;
+
+/**
+ * Test case for rule #3: "Prefer alternative APIs over Optional.isPresent() and Optional.get()."
+ */
+@SuppressWarnings("optional.parameter")
+public class Marks3bJdk11 {
+
+  class Task {}
+
+  class Executor {
+    void runTask(Task t) {}
+  }
+
+  Executor executor = new Executor();
+
+  void bad2(Optional<Task> oTask) {
+    // :: warning: (prefer.ifpresent)
+    if (!oTask.isEmpty()) {
+      executor.runTask(oTask.get());
+    }
+  }
+
+  void bad3(Optional<Task> oTask) {
+    // :: warning: (prefer.ifpresent)
+    if (oTask.isEmpty()) {
+    } else {
+      executor.runTask(oTask.get());
+    }
+  }
+}
diff --git a/checker/tests/optional/Marks4.java b/checker/tests/optional/Marks4.java
new file mode 100644
index 0000000..647cb2b
--- /dev/null
+++ b/checker/tests/optional/Marks4.java
@@ -0,0 +1,21 @@
+import java.util.Optional;
+
+/**
+ * Test case for rule #4: "It's generally a bad idea to create an Optional for the specific purpose
+ * of chaining from it to get a value."
+ */
+public class Marks4 {
+
+  String getDefault() {
+    return "Fozzy Bear";
+  }
+
+  String process_bad(String s) {
+    // :: warning: (introduce.eliminate)
+    return Optional.ofNullable(s).orElseGet(this::getDefault);
+  }
+
+  String process_good(String s) {
+    return (s != null) ? s : getDefault();
+  }
+}
diff --git a/checker/tests/optional/Marks5.java b/checker/tests/optional/Marks5.java
new file mode 100644
index 0000000..5af91d7
--- /dev/null
+++ b/checker/tests/optional/Marks5.java
@@ -0,0 +1,51 @@
+import java.math.BigDecimal;
+import java.util.Optional;
+import java.util.stream.Stream;
+import org.checkerframework.checker.optional.qual.Present;
+
+/**
+ * Test case for rule #5: "If an Optional chain has a nested Optional chain, or has an intermediate
+ * result of Optional, it's probably too complex."
+ */
+@SuppressWarnings("optional.parameter")
+public class Marks5 {
+
+  // Each method adds first and second, treating empty as zero, returning an Optional of the sum,
+  // unless BOTH are empty, in which case return an empty Optional.
+
+  Optional<BigDecimal> clever(Optional<BigDecimal> first, Optional<BigDecimal> second) {
+    @SuppressWarnings({"methodref.inference.unimplemented", "methodref.receiver"})
+    Optional<BigDecimal> result =
+        Stream.of(first, second)
+            .filter(Optional::isPresent)
+            .map(Optional::get)
+            .reduce(BigDecimal::add);
+    return result;
+  }
+
+  Optional<BigDecimal> clever2(Optional<BigDecimal> first, Optional<BigDecimal> second) {
+    Stream<Optional<BigDecimal>> s = Stream.of(first, second);
+    @SuppressWarnings("assignment")
+    Stream<@Present Optional<BigDecimal>> filtered =
+        s.<Optional<BigDecimal>>filter(Optional::isPresent);
+    Stream<BigDecimal> present = filtered.<BigDecimal>map(Optional::get);
+    Optional<BigDecimal> result = present.reduce(BigDecimal::add);
+    return result;
+  }
+
+  Optional<BigDecimal> moreClever(Optional<BigDecimal> first, Optional<BigDecimal> second) {
+    Optional<BigDecimal> result =
+        first.map(b -> second.map(b::add).orElse(b)).map(Optional::of).orElse(second);
+    return result;
+  }
+
+  Optional<BigDecimal> clear(Optional<BigDecimal> first, Optional<BigDecimal> second) {
+    Optional<BigDecimal> result;
+    if (!first.isPresent() && !second.isPresent()) {
+      result = Optional.empty();
+    } else {
+      result = Optional.of(first.orElse(BigDecimal.ZERO).add(second.orElse(BigDecimal.ZERO)));
+    }
+    return result;
+  }
+}
diff --git a/checker/tests/optional/Marks6.java b/checker/tests/optional/Marks6.java
new file mode 100644
index 0000000..4d780a5
--- /dev/null
+++ b/checker/tests/optional/Marks6.java
@@ -0,0 +1,29 @@
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/** Test cases for Rule #6: "Avoid using Optional in fields, method parameters, and collections." */
+public class Marks6 {
+
+  // :: warning: (optional.field)
+  Optional<String> optionalField = Optional.ofNullable(null);
+
+  // :: warning: (optional.parameter)
+  void optionalParameter(Optional<String> arg) {}
+
+  Optional<String> okUses() {
+    Optional<String> os = Optional.of("hello world");
+    return os;
+  }
+
+  void illegalInstantiations() {
+    // :: warning: (optional.as.element.type)
+    List<Optional<String>> los = new ArrayList<>();
+    // :: warning: (optional.as.element.type)
+    List<Optional<String>> los2 = new ArrayList<Optional<String>>();
+    // :: warning: (optional.as.element.type)
+    Set<Optional<String>> sos = new HashSet<>();
+  }
+}
diff --git a/checker/tests/optional/Marks7.java b/checker/tests/optional/Marks7.java
new file mode 100644
index 0000000..81e2cff
--- /dev/null
+++ b/checker/tests/optional/Marks7.java
@@ -0,0 +1,19 @@
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Test cases for Rule #7: "Don't use an Optional to wrap any collection type (List, Set, Map).
+ * Instead, use an empty collection to represent the absence of values.
+ */
+public class Marks7 {
+
+  void illegalInstantiations() {
+    // :: error: (optional.collection)
+    Optional<List<String>> ols = Optional.of(new ArrayList<String>());
+    // :: error: (optional.collection)
+    Optional<Set<String>> oss = Optional.of(new HashSet<String>());
+  }
+}
diff --git a/checker/tests/optional/SubtypeCheck.java b/checker/tests/optional/SubtypeCheck.java
new file mode 100644
index 0000000..f4019ab
--- /dev/null
+++ b/checker/tests/optional/SubtypeCheck.java
@@ -0,0 +1,27 @@
+import java.util.Optional;
+import org.checkerframework.checker.optional.qual.MaybePresent;
+import org.checkerframework.checker.optional.qual.OptionalBottom;
+import org.checkerframework.checker.optional.qual.Present;
+
+/** Basic test of subtyping. */
+public class SubtypeCheck {
+
+  @SuppressWarnings("optional.parameter")
+  void foo(
+      @MaybePresent Optional<String> mp,
+      @Present Optional<String> p,
+      @OptionalBottom Optional<String> ob) {
+    @MaybePresent Optional<String> mp2 = mp;
+    @MaybePresent Optional<String> mp3 = p;
+    @MaybePresent Optional<String> mp4 = ob;
+    // :: error: assignment
+    @Present Optional<String> p2 = mp;
+    @Present Optional<String> p3 = p;
+    @Present Optional<String> p4 = ob;
+    // :: error: assignment
+    @OptionalBottom Optional<String> ob2 = mp;
+    // :: error: assignment
+    @OptionalBottom Optional<String> ob3 = p;
+    @OptionalBottom Optional<String> ob4 = ob;
+  }
+}
diff --git a/checker/tests/parse-all-jdk/Test.java b/checker/tests/parse-all-jdk/Test.java
new file mode 100644
index 0000000..1b6f4d8
--- /dev/null
+++ b/checker/tests/parse-all-jdk/Test.java
@@ -0,0 +1,2 @@
+/** This class doesn't need a body because it is only used to test parsing all the JDK files. */
+public class Test {}
diff --git a/checker/tests/regex/AllowedTypes.java b/checker/tests/regex/AllowedTypes.java
new file mode 100644
index 0000000..ffb1ef5
--- /dev/null
+++ b/checker/tests/regex/AllowedTypes.java
@@ -0,0 +1,59 @@
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.swing.text.Segment;
+import org.checkerframework.checker.regex.qual.Regex;
+
+public class AllowedTypes {
+  @Regex CharSequence cs;
+  @Regex String s11;
+  @Regex StringBuilder sb;
+  @Regex Segment s21;
+  @Regex char c;
+  @Regex Pattern p;
+  @Regex Matcher m;
+  @Regex Character c2;
+  @Regex Object o;
+
+  abstract static class MyMatchResult implements MatchResult {}
+
+  @Regex MyMatchResult mp;
+
+  // :: error: (anno.on.irrelevant)
+  @Regex List<String> l;
+  // :: error: (anno.on.irrelevant)
+  ArrayList<@Regex Double> al;
+  // :: error: (anno.on.irrelevant)
+  @Regex int i;
+  // :: error: (anno.on.irrelevant)
+  @Regex boolean b;
+  // :: error: (anno.on.irrelevant)
+  @Regex Integer i2;
+
+  void testAllowedTypes() {
+    @Regex CharSequence cs;
+    @Regex String s11;
+    @Regex StringBuilder sb;
+    @Regex Segment s21;
+    @Regex char c;
+    @Regex Object o;
+
+    // :: error: (anno.on.irrelevant)
+    @Regex List<String> l; // error
+    // :: error: (anno.on.irrelevant)
+    ArrayList<@Regex Double> al; // error
+    // :: error: (anno.on.irrelevant)
+    @Regex int i; // error
+    // :: error: (anno.on.irrelevant)
+    @Regex boolean b; // error
+
+    @Regex String regex = "a";
+    // :: error: (compound.assignment)
+    regex += "(";
+
+    String nonRegex = "a";
+    nonRegex += "(";
+  }
+}
diff --git a/checker/tests/regex/AnnotatedTypeParams3.java b/checker/tests/regex/AnnotatedTypeParams3.java
new file mode 100644
index 0000000..031e88c
--- /dev/null
+++ b/checker/tests/regex/AnnotatedTypeParams3.java
@@ -0,0 +1,49 @@
+import java.lang.annotation.Annotation;
+import java.lang.reflect.*;
+import org.checkerframework.checker.regex.qual.Regex;
+
+public class AnnotatedTypeParams3 {
+  private <T extends Annotation> T safeGetAnnotation(Field f, Class<T> annotationClass) {
+    T annotation;
+    try {
+      annotation = f.getAnnotation((Class<T>) annotationClass);
+    } catch (Exception e) {
+      annotation = null;
+    }
+    return annotation;
+  }
+
+  private <T extends Annotation> T safeGetAnnotation2(Field f, Class<T> annotationClass) {
+    T annotation;
+    try {
+      annotation = f.getAnnotation(annotationClass);
+    } catch (Exception e) {
+      annotation = null;
+    }
+    return annotation;
+  }
+
+  <@Regex T extends @Regex Object> void test(T p) {
+    Object o = p;
+    @Regex Object re = o;
+  }
+
+  <T extends @Regex Object> void test2(T p) {
+    Object o = p;
+    @Regex Object re = o;
+  }
+
+  // TODO: do we want to infer the type variable annotation on local variable "o"?
+  <T> void test3(@Regex T p) {
+    T o = p;
+    @Regex T re = o;
+  }
+}
+
+class OuterClass<E> {
+  public InnerClass<E> method() {
+    return new InnerClass<>();
+  }
+
+  class InnerClass<A extends E> {}
+}
diff --git a/checker/tests/regex/Annotation.java b/checker/tests/regex/Annotation.java
new file mode 100644
index 0000000..db17e52
--- /dev/null
+++ b/checker/tests/regex/Annotation.java
@@ -0,0 +1,21 @@
+@interface A1 {
+  String[] value() default {};
+}
+
+@interface A2 {
+  String[] value();
+}
+
+public class Annotation {
+  @A1({"a", "b"})
+  void m1() {}
+
+  @A1(value = {"a", "b"})
+  void m2() {}
+
+  @A2({"a", "b"})
+  void m3() {}
+
+  @A2(value = {"a", "b"})
+  void m4() {}
+}
diff --git a/checker/tests/regex/Continue.java b/checker/tests/regex/Continue.java
new file mode 100644
index 0000000..e497e0f
--- /dev/null
+++ b/checker/tests/regex/Continue.java
@@ -0,0 +1,55 @@
+import java.util.regex.Pattern;
+import org.checkerframework.checker.regex.util.RegexUtil;
+
+public class Continue {
+
+  void test1(String[] a) {
+    for (String s : a) {
+      if (!RegexUtil.isRegex(s)) {
+        continue;
+      }
+      Pattern.compile(s);
+    }
+  }
+
+  void test2(String[] a, boolean b) {
+    for (String s : a) {
+      if (!RegexUtil.isRegex(s)) {
+        continue;
+      } else if (b) {
+        continue;
+      }
+      Pattern.compile(s);
+    }
+  }
+
+  // Reverse the if statements from the previous test.
+  void test3(String[] a, boolean b) {
+    for (String s : a) {
+      if (b) {
+        continue;
+      } else if (!RegexUtil.isRegex(s)) {
+        continue;
+      }
+      Pattern.compile(s);
+    }
+  }
+
+  void twoThrows(String s) {
+    if (s == null) {
+      throw new RuntimeException();
+    } else if (!RegexUtil.isRegex(s)) {
+      throw new RuntimeException();
+    }
+    Pattern.compile(s);
+  }
+
+  void twoReturns(String s) {
+    if (s == null) {
+      return;
+    } else if (!RegexUtil.isRegex(s)) {
+      return;
+    }
+    Pattern.compile(s);
+  }
+}
diff --git a/checker/tests/regex/ForEach.java b/checker/tests/regex/ForEach.java
new file mode 100644
index 0000000..ffa8d21
--- /dev/null
+++ b/checker/tests/regex/ForEach.java
@@ -0,0 +1,8 @@
+public class ForEach {
+  <T extends Object> T iterate(T[] constants) {
+    for (T constant : constants) {
+      return constant;
+    }
+    return null;
+  }
+}
diff --git a/checker/tests/regex/GenericsBoundsRange.java b/checker/tests/regex/GenericsBoundsRange.java
new file mode 100644
index 0000000..763cbdb
--- /dev/null
+++ b/checker/tests/regex/GenericsBoundsRange.java
@@ -0,0 +1,37 @@
+package regex;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.checkerframework.checker.regex.qual.Regex;
+
+/** Designed to test whether or not a bounds range of generics actually works. */
+public class GenericsBoundsRange<@Regex(3) T extends @Regex(1) String> {
+  public T t;
+
+  public GenericsBoundsRange(T t) {
+    Matcher matcher = Pattern.compile(t).matcher("some str");
+    if (matcher.matches()) {
+      matcher.group(0);
+      matcher.group(1);
+
+      // T has at least 1 group so the above 2 group calls are good
+      // however, T MAY or MAY NOT have 2 or 3 groups, so issue an error
+
+      // :: error: (group.count)
+      matcher.group(2);
+
+      // :: error: (group.count)
+      matcher.group(3);
+
+      // T definitely does not have 4 groups, issue an error
+
+      // :: error: (group.count)
+      matcher.group(4);
+    }
+  }
+
+  // Bounds used to not actually be bounds but instead exactly the lower bound
+  // so line below would fail because the argument could only be Regex(0).  So this
+  // tests BaseTypeValidator.checkTypeArguments range checking.
+  public void method(GenericsBoundsRange<@Regex(2) String> gbr) {}
+}
diff --git a/checker/tests/regex/GenericsEnclosing.java b/checker/tests/regex/GenericsEnclosing.java
new file mode 100644
index 0000000..04a2f3e
--- /dev/null
+++ b/checker/tests/regex/GenericsEnclosing.java
@@ -0,0 +1,30 @@
+import org.checkerframework.checker.regex.qual.*;
+
+/**
+ * Resolution of outer classes must take substitution of generic types into account. Thanks to EMS
+ * for finding this problem.
+ *
+ * <p>Also see all-systems/GenericsEnclosing for the type-system independent test.
+ */
+class MyG<X> {
+  X f;
+
+  void m(X p) {}
+}
+
+class ExtMyG extends MyG<@Regex String> {
+  class EInner1 {
+    class EInner2 {
+      void bar() {
+        String s = f;
+        f = "hi";
+        // :: error: (assignment)
+        f = "\\ no regex(";
+
+        m("hi!");
+        // :: error: (argument)
+        m("\\ no regex(");
+      }
+    }
+  }
+}
diff --git a/checker/tests/regex/GroupCounts.java b/checker/tests/regex/GroupCounts.java
new file mode 100644
index 0000000..ae0ed90
--- /dev/null
+++ b/checker/tests/regex/GroupCounts.java
@@ -0,0 +1,115 @@
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.checkerframework.checker.regex.qual.Regex;
+import org.checkerframework.checker.regex.util.RegexUtil;
+
+public class GroupCounts {
+  void testGroupCount() {
+    @Regex(0) String s1 = "abc";
+    @Regex(1) String s2 = "(abc)";
+    @Regex(2) String s3 = "()(abc)";
+    @Regex(3) String s4 = "(abc())()";
+    @Regex(4) String s5 = "((((abc))))";
+
+    @Regex(0) String s7 = "(abc)";
+    @Regex String s9 = "()()(())";
+    @Regex(2) String s10 = "()()(())";
+    @Regex(3) String s11 = "()()(())";
+
+    // :: error: (assignment)
+    @Regex(2) String s6 = "nonregex("; // error
+    // :: error: (assignment)
+    @Regex(1) String s8 = "abc"; // error
+    // :: error: (assignment)
+    @Regex(3) String s12 = "()()"; // error
+    // :: error: (assignment)
+    @Regex(4) String s13 = "(())()"; // error
+  }
+
+  void testPatternCompileGroupCount(@Regex String r, @Regex(3) String r3, @Regex(5) String r5) {
+    @Regex(5) Pattern p1 = Pattern.compile(r5);
+    @Regex Pattern p2 = Pattern.compile(r5);
+    @Regex Pattern p3 = Pattern.compile(r);
+
+    // :: error: (assignment)
+    @Regex(6) Pattern p4 = Pattern.compile(r5); // error
+    // :: error: (assignment)
+    @Regex(6) Pattern p5 = Pattern.compile(r3); // error
+
+    // Make sure Pattern.compile still works when passed an @UnknownRegex String
+    // that's actually a regex, with the warning suppressed.
+    @SuppressWarnings("regex:argument")
+    Pattern p6 = Pattern.compile("(" + r + ")");
+  }
+
+  void testConcatenationGroupCount(@Regex String r, @Regex(3) String r3, @Regex(5) String r5) {
+    @Regex(0) String s1 = r + r;
+    @Regex(3) String s2 = r + r3;
+    @Regex(8) String s3 = r3 + r5;
+
+    // :: error: (assignment)
+    @Regex(1) String s4 = r + r;
+    // :: error: (assignment)
+    @Regex(4) String s5 = r + r3;
+    // :: error: (assignment)
+    @Regex(9) String s6 = r3 + r5;
+  }
+
+  void testCompoundConcatenationWithGroups(
+      @Regex String s0, @Regex(1) String s1, @Regex(3) String s3) {
+    s0 += s0;
+    @Regex String test0 = s0;
+    // :: error: (assignment)
+    @Regex(1) String test01 = s0;
+
+    s0 += s1;
+    @Regex(1) String test1 = s0;
+    // :: error: (assignment)
+    @Regex(2) String test12 = s0;
+
+    s1 += s3;
+    @Regex(4) String test4 = s1;
+    // :: error: (assignment)
+    @Regex(5) String test45 = s1;
+  }
+
+  void testAsRegexGroupCounts(String s) {
+    @Regex String test1 = RegexUtil.asRegex(s);
+    // :: error: (assignment)
+    @Regex(1) String test2 = RegexUtil.asRegex(s);
+
+    @Regex(3) String test3 = RegexUtil.asRegex(s, 3);
+    // :: error: (assignment)
+    @Regex(4) String test4 = RegexUtil.asRegex(s, 3);
+  }
+
+  void testMatcherGroupCounts(
+      @Regex Matcher m0, @Regex(1) Matcher m1, @Regex(4) Matcher m4, int n) {
+    m0.end(0);
+    m0.group(0);
+    m0.start(0);
+
+    // :: error: (group.count)
+    m0.end(1);
+    // :: error: (group.count)
+    m0.group(1);
+    // :: error: (group.count)
+    m0.start(1);
+
+    m1.start(0);
+    m1.start(1);
+
+    // :: error: (group.count)
+    m1.start(2);
+
+    m4.start(0);
+    m4.start(2);
+    m4.start(4);
+
+    // :: error: (group.count)
+    m4.start(5);
+
+    // :: warning: (group.count.unknown)
+    m0.start(n);
+  }
+}
diff --git a/checker/tests/regex/IntCast.java b/checker/tests/regex/IntCast.java
new file mode 100644
index 0000000..100690c
--- /dev/null
+++ b/checker/tests/regex/IntCast.java
@@ -0,0 +1,6 @@
+public class IntCast {
+
+  int m() {
+    return (int) '\n';
+  }
+}
diff --git a/checker/tests/regex/InvariantTypes.java b/checker/tests/regex/InvariantTypes.java
new file mode 100644
index 0000000..32fa09f
--- /dev/null
+++ b/checker/tests/regex/InvariantTypes.java
@@ -0,0 +1,119 @@
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.checker.regex.qual.*;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+public class InvariantTypes {
+  String[] sa = {"a"};
+  String[] sa2 = {"a", "b"};
+  public String[] sa3 = {"a", "b"};
+  public static String[] sa4 = {"a", "b"};
+  public final String[] sa5 = {"a", "b"};
+  public static final String[] sa6 = {"a", "b"};
+  final String[] sa7 = {"a", "b"};
+
+  // tested above:  String[] sa = {"a"};
+  @Regex String[] rsa = {"a"};
+  String[] nrsa = {"(a"};
+  // :: error: (array.initializer) :: error: (assignment)
+  @Regex String[] rsaerr = {"(a"};
+
+  List<String> ls = Arrays.asList("alice", "bob", "carol");
+  List<@Regex String> lrs = Arrays.asList("alice", "bob", "carol");
+  List<String> lnrs = Arrays.asList("(alice", "bob", "carol");
+  // :: error: (assignment)
+  List<@Regex String> lrserr = Arrays.asList("(alice", "bob", "carol");
+
+  void unqm(String[] sa) {}
+
+  void rem(@Regex String[] rsa) {}
+
+  void recalls() {
+    unqm(new String[] {"a"});
+    // TODOINVARR:: error: (argument)
+    unqm(new @Regex String[] {"a"});
+    rem(new String[] {"a"});
+    rem(new @Regex String[] {"a"});
+  }
+
+  void unqcalls() {
+    unqm(new String[] {"a("});
+    // TODOINVARR:: error: (argument)
+    // :: error: (array.initializer)
+    unqm(new @Regex String[] {"a("});
+    // :: error: (argument)
+    rem(new String[] {"a("});
+    // :: error: (array.initializer)
+    rem(new @Regex String[] {"a("});
+  }
+
+  // method argument context
+
+  String[] retunqm(String[] sa) {
+    return sa;
+  }
+
+  @Regex String[] retrem(@Regex String[] rsa) {
+    return rsa;
+  }
+
+  @Regex String[] mixedm(String[] rsa) {
+    return null;
+  }
+
+  void retunqcalls() {
+    @Regex String[] re = mixedm(new String[] {"a("});
+    // TODOINVARR:: error: (argument)
+    String[] u = retunqm(new String[] {"a"});
+    // TODOINVARR:: error: (argument)
+    re = mixedm(new String[2]);
+  }
+
+  void lrem(List<@Regex String> p) {}
+
+  void lunqm(List<String> p) {}
+
+  void listcalls() {
+    lunqm(Arrays.asList("alice", "bob", "carol"));
+    lrem(Arrays.asList("alice", "bob", "carol"));
+    lunqm(Arrays.asList("(alice", "bob", "carol"));
+    // :: error: (argument)
+    lrem(Arrays.asList("(alice", "bob", "carol"));
+  }
+
+  class ReTests {
+    ReTests(List<@Regex String> p) {}
+
+    ReTests(List<String> p, int i) {}
+  }
+
+  void listctrs() {
+    new ReTests(Arrays.asList("alice", "bob", "carol"), 0);
+    new ReTests(Arrays.asList("alice", "bob", "carol"));
+    new ReTests(Arrays.asList("(alice", "bob", "carol"), 0);
+    // :: error: (argument)
+    new ReTests(Arrays.asList("(alice", "bob", "carol"));
+  }
+
+  <J> String join(final String delimiter, final Collection<J> objs) {
+    return delimiter;
+  }
+
+  String s1 = join(" ", Arrays.asList("1", "2", "3"));
+  String s2 = "xxx" + join(" ", Arrays.asList("1", "2", "3"));
+
+  <K extends AnnotatedTypeMirror, V extends AnnotatedTypeMirror> V mapGetHelper(
+      Map<K, V> mappings) {
+    return null;
+  }
+
+  Map<? extends AnnotatedTypeMirror, ? extends AnnotatedTypeMirror> mappings;
+  AnnotatedTypeMirror found = mapGetHelper(mappings);
+
+  class TV<T> {
+    List<List<T>> emptylist = Collections.emptyList();
+  }
+}
diff --git a/checker/tests/regex/Issue3267.java b/checker/tests/regex/Issue3267.java
new file mode 100644
index 0000000..02010bd
--- /dev/null
+++ b/checker/tests/regex/Issue3267.java
@@ -0,0 +1,17 @@
+// Test case for issue #3267:
+// https://github.com/typetools/checker-framework/issues/3267
+
+import java.util.regex.Pattern;
+import org.checkerframework.checker.regex.util.RegexUtil;
+
+public class Issue3267 {
+  void foo(String s) {
+    if (RegexUtil.isRegex(s)) {
+    } else {
+    }
+    if (true) {
+      // :: error: (argument)
+      Pattern.compile(s);
+    }
+  }
+}
diff --git a/checker/tests/regex/Issue3281.java b/checker/tests/regex/Issue3281.java
new file mode 100644
index 0000000..b7b9476
--- /dev/null
+++ b/checker/tests/regex/Issue3281.java
@@ -0,0 +1,66 @@
+// Test case for Issue 3281:
+// https://github.com/typetools/checker-framework/issues/3281
+
+// @skip-test until the bug is fixed
+
+import org.checkerframework.checker.regex.qual.Regex;
+import org.checkerframework.checker.regex.util.RegexUtil;
+
+public class Issue3281 {
+
+  @Regex String f = null;
+
+  public boolean b = false;
+
+  void m1(String s) {
+    if (true) {
+      // :: error: (argument)
+      Pattern.compile(s);
+    }
+  }
+
+  void m2(String s) {
+    RegexUtil.isRegex(s);
+    if (true) {
+      // :: error: (argument)
+      Pattern.compile(s);
+    }
+  }
+
+  void m2f(String s) {
+    RegexUtil.isRegex(s);
+    if (true) {
+      // :: error: (assignment)
+      f = s;
+    }
+  }
+
+  void m3(String s) {
+    if (RegexUtil.isRegex(s)) {
+      Pattern.compile(s);
+    }
+  }
+
+  void m4(String s, String s2) {
+    RegexUtil.isRegex(s);
+    if (RegexUtil.isRegex(s2)) {
+      Pattern.compile(s);
+    }
+  }
+
+  void m4f(String s, String s2) {
+    RegexUtil.isRegex(s);
+    if (RegexUtil.isRegex(s2)) {
+      // :: error: (assignment)
+      f = s;
+    }
+  }
+
+  void m5f(String s, String s2) {
+    RegexUtil.isRegex(s);
+    if (b) {
+      // :: error: (assignment)
+      f = s;
+    }
+  }
+}
diff --git a/checker/tests/regex/Issue809.java b/checker/tests/regex/Issue809.java
new file mode 100644
index 0000000..d064ecd
--- /dev/null
+++ b/checker/tests/regex/Issue809.java
@@ -0,0 +1,12 @@
+// Test case for issue #809:
+// https://github.com/typetools/checker-framework/issues/809
+
+public class Issue809<K extends Enum<K>> {
+  K[] array;
+
+  int index = 0;
+
+  String m() {
+    return array[index] + "=";
+  }
+}
diff --git a/checker/tests/regex/LubRegex.java b/checker/tests/regex/LubRegex.java
new file mode 100644
index 0000000..b9a4a7c
--- /dev/null
+++ b/checker/tests/regex/LubRegex.java
@@ -0,0 +1,66 @@
+import org.checkerframework.checker.regex.qual.Regex;
+
+public class LubRegex {
+
+  void test1(@Regex(4) String s4, boolean b) {
+    String s = null;
+    if (b) {
+      s = s4;
+    }
+    @Regex(4) String test = s;
+
+    // :: error: (assignment)
+    @Regex(5) String test2 = s;
+  }
+
+  void test2(@Regex(2) String s2, @Regex(4) String s4, boolean b) {
+    String s = s4;
+    if (b) {
+      s = s2;
+    }
+    @Regex(2) String test = s;
+
+    // :: error: (assignment)
+    @Regex(3) String test2 = s;
+  }
+
+  void test3(@Regex(6) String s6, boolean b) {
+    String s;
+    if (b) {
+      s = s6;
+    } else {
+      s = null;
+    }
+    @Regex(6) String test = s;
+
+    // :: error: (assignment)
+    @Regex(7) String test2 = s;
+  }
+
+  void test4(@Regex(8) String s8, @Regex(9) String s9, boolean b) {
+    String s;
+    if (b) {
+      s = s8;
+    } else {
+      s = s9;
+    }
+    @Regex(8) String test = s;
+
+    // :: error: (assignment)
+    @Regex(9) String test2 = s;
+  }
+
+  void test5(@Regex(10) String s10, @Regex(11) String s11, boolean b) {
+    String s;
+    if (b) {
+      s = s11;
+    } else {
+      s = s10;
+      return;
+    }
+    @Regex(11) String test = s;
+
+    // :: error: (assignment)
+    @Regex(12) String test2 = s;
+  }
+}
diff --git a/checker/tests/regex/MatcherGroupCount.java b/checker/tests/regex/MatcherGroupCount.java
new file mode 100644
index 0000000..dcd8930
--- /dev/null
+++ b/checker/tests/regex/MatcherGroupCount.java
@@ -0,0 +1,48 @@
+// Test case for Issue 291
+// https://github.com/typetools/checker-framework/issues/291
+
+import java.util.regex.*;
+import org.checkerframework.checker.regex.util.RegexUtil;
+
+public class MatcherGroupCount {
+  public static void main(String[] args) {
+    String regex = args[0];
+    String content = args[1];
+
+    if (!RegexUtil.isRegex(regex)) {
+      System.out.println(
+          "Error parsing regex \"" + regex + "\": " + RegexUtil.regexException(regex).getMessage());
+      System.exit(1);
+    }
+
+    Pattern pat = Pattern.compile(regex);
+    Matcher mat = pat.matcher(content);
+
+    if (mat.matches()) {
+      if (mat.groupCount() > 0) {
+        System.out.println("Group: " + mat.group(1));
+      } else {
+        System.out.println("No group found!");
+      }
+      if (mat.groupCount() >= 2) {
+        System.out.println("Group: " + mat.group(2));
+      }
+      if (mat.groupCount() >= 2) {
+        // :: error: (group.count)
+        System.out.println("Group: " + mat.group(3));
+      }
+      if (2 < mat.groupCount()) {
+        System.out.println("Group: " + mat.group(3));
+      }
+      if (!(mat.groupCount() > 4)) {
+        System.out.println("Group: " + mat.group(0));
+      } else {
+        System.out.println("Group: " + mat.group(5));
+        // :: error: (group.count)
+        System.out.println("Group: " + mat.group(6));
+      }
+    } else {
+      System.out.println("No match!");
+    }
+  }
+}
diff --git a/checker/tests/regex/MyMatchResult.java b/checker/tests/regex/MyMatchResult.java
new file mode 100644
index 0000000..e083627
--- /dev/null
+++ b/checker/tests/regex/MyMatchResult.java
@@ -0,0 +1,49 @@
+import java.util.regex.MatchResult;
+
+// Outside of scope of the Regex Checker to verify an implementation of MatchResult,
+// so just check for crashes.
+@SuppressWarnings("regex")
+public class MyMatchResult implements MatchResult {
+
+  @Override
+  public int start() {
+    group(0);
+    group();
+    end();
+    end(1);
+    groupCount();
+    start();
+    start(19);
+    return 0;
+  }
+
+  @Override
+  public int start(int group) {
+    return 0;
+  }
+
+  @Override
+  public int end() {
+    return 0;
+  }
+
+  @Override
+  public int end(int group) {
+    return 0;
+  }
+
+  @Override
+  public String group() {
+    return null;
+  }
+
+  @Override
+  public String group(int group) {
+    return null;
+  }
+
+  @Override
+  public int groupCount() {
+    return 0;
+  }
+}
diff --git a/checker/tests/regex/PartialRegex.java b/checker/tests/regex/PartialRegex.java
new file mode 100644
index 0000000..7ecd5b8
--- /dev/null
+++ b/checker/tests/regex/PartialRegex.java
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.regex.qual.Regex;
+
+public class PartialRegex {
+  void m(@Regex String re, String non) {
+    String l = "(";
+    String r = ")";
+
+    @Regex String test1 = l + r;
+    @Regex String test2 = l + re + r;
+    @Regex String test3 = l + r + l + r;
+    @Regex String test4 = l + l + r + r;
+    @Regex String test5 = l + l + re + r + r;
+
+    // :: error: (assignment)
+    @Regex String fail1 = r + l;
+    // :: error: (assignment)
+    @Regex String fail2 = r + non + l;
+    // :: error: (assignment)
+    @Regex String fail3 = l + r + r;
+  }
+}
diff --git a/checker/tests/regex/RawTypeTest.java b/checker/tests/regex/RawTypeTest.java
new file mode 100644
index 0000000..332d42f
--- /dev/null
+++ b/checker/tests/regex/RawTypeTest.java
@@ -0,0 +1,102 @@
+import java.lang.ref.WeakReference;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.List;
+import org.checkerframework.checker.regex.qual.*;
+
+public class RawTypeTest {
+
+  public void m1(Class<?> c) {
+    Class<? extends I2> x = c.asSubclass(I2.class);
+
+    new WeakReference<Object>(x);
+    new WeakReference<Class>(x);
+    new WeakReference<Class<? extends I2>>(x);
+
+    new WeakReference<Object>(c.asSubclass(I2.class));
+    new WeakReference<Class>(c.asSubclass(I2.class));
+    new WeakReference<Class<? extends I2>>(c.asSubclass(I2.class));
+  }
+
+  /* It would be desirable to optionally check the following code without
+   * warnings. See issue 119:
+   *
+   * https://github.com/typetools/checker-framework/issues/119
+   *
+  class Raw {
+      public void m2(Class<Object> c) {}
+
+      public void m3(Class c) {
+          m2(c);
+      }
+
+      public void m4() {
+          AccessController.doPrivileged(new PrivilegedAction() {
+              public Object run() {
+                  return null;
+              }});
+      }
+
+      public void m5(List list, C4 c) {
+          list.add(c);
+      }
+
+      public void m6(List list, long l) {
+          list.add(l);
+      }
+  }*/
+
+  class NonRaw {
+    public void m2(Class<Object> c) {}
+
+    public void m3(Class<Object> c) {
+      m2(c);
+    }
+
+    public void m4() {
+      AccessController.doPrivileged(
+          new PrivilegedAction<Object>() {
+            public Object run() {
+              return null;
+            }
+          });
+    }
+
+    public void m5(List<C4> list, C4 c) {
+      list.add(c);
+    }
+
+    public void m6(List<Long> list, long l) {
+      list.add(l);
+    }
+  }
+
+  class MyList<X extends @Regex String> {
+    X f;
+  }
+
+  interface I1 {
+    public void m(MyList<? extends @Regex String> l);
+  }
+
+  class C1 implements I1 {
+    public void m(MyList par) {
+      @Regex String xxx = par.f;
+    }
+  }
+
+  interface I2 {
+    public void m(MyList<@Regex String> l);
+  }
+
+  class C2 implements I2 {
+    public void m(MyList<@Regex String> l) {}
+  }
+
+  class C3 implements I2 {
+    // :: error: (override.param) :: error: (type.argument)
+    public void m(MyList<String> l) {}
+  }
+
+  class C4 {}
+}
diff --git a/checker/tests/regex/RegexUtilClient.java b/checker/tests/regex/RegexUtilClient.java
new file mode 100644
index 0000000..2884f9d
--- /dev/null
+++ b/checker/tests/regex/RegexUtilClient.java
@@ -0,0 +1,97 @@
+import org.checkerframework.checker.regex.qual.Regex;
+import org.checkerframework.framework.qual.EnsuresQualifierIf;
+
+public class RegexUtilClient {
+  void fullyQualifiedRegexUtil(String s) {
+    if (org.checkerframework.checker.regex.util.RegexUtil.isRegex(s, 2)) {
+      @Regex(2) String s2 = s;
+    }
+    @Regex(2) String s2 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s, 2);
+  }
+
+  void unqualifiedRegexUtil(String s) {
+    if (RegexUtil.isRegex(s, 2)) {
+      @Regex(2) String s2 = s;
+    }
+    @Regex(2) String s2 = RegexUtil.asRegex(s, 2);
+  }
+
+  void fullyQualifiedRegexUtilNoParamsArg(String s) {
+    if (org.checkerframework.checker.regex.util.RegexUtil.isRegex(s)) {
+      @Regex String s2 = s;
+      @Regex(0) String s3 = s;
+    }
+    @Regex String s2 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s);
+    @Regex(0) String s3 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s);
+  }
+
+  void unqualifiedRegexUtilNoParamsArg(String s) {
+    if (RegexUtil.isRegex(s)) {
+      @Regex String s2 = s;
+      @Regex(0) String s3 = s;
+    }
+    @Regex String s2 = RegexUtil.asRegex(s, 2);
+    @Regex(0) String s3 = RegexUtil.asRegex(s, 2);
+  }
+
+  void illegalName(String s) {
+    if (IllegalName.isRegex(s, 2)) {
+      // :: error: (assignment)
+      @Regex(2) String s2 = s;
+    }
+    // :: error: (assignment)
+    @Regex(2) String s2 = IllegalName.asRegex(s, 2);
+  }
+
+  void illegalNameRegexUtil(String s) {
+    if (IllegalNameRegexUtil.isRegex(s, 2)) {
+      // :: error: (assignment)
+      @Regex(2) String s2 = s;
+    }
+    // :: error: (assignment)
+    @Regex(2) String s2 = IllegalNameRegexUtil.asRegex(s, 2);
+  }
+}
+
+// A dummy RegexUtil class to make sure RegexUtil in no package works.
+class RegexUtil {
+  @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class)
+  public static boolean isRegex(final String s, int n) {
+    return false;
+  }
+
+  public static @Regex String asRegex(String s, int n) {
+    return null;
+  }
+
+  @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class)
+  public static boolean isRegex(final String s) {
+    return false;
+  }
+
+  public static @Regex String asRegex(String s) {
+    return null;
+  }
+}
+
+// These methods shouldn't work.
+class IllegalName {
+  public static boolean isRegex(String s, int n) {
+    return false;
+  }
+
+  public static @Regex String asRegex(String s, int n) {
+    return null;
+  }
+}
+
+// These methods shouldn't work.
+class IllegalNameRegexUtil {
+  public static boolean isRegex(String s, int n) {
+    return false;
+  }
+
+  public static @Regex String asRegex(String s, int n) {
+    return null;
+  }
+}
diff --git a/checker/tests/regex/SimpleRegex.java b/checker/tests/regex/SimpleRegex.java
new file mode 100644
index 0000000..03e6077
--- /dev/null
+++ b/checker/tests/regex/SimpleRegex.java
@@ -0,0 +1,141 @@
+import java.util.regex.Pattern;
+import org.checkerframework.checker.regex.qual.Regex;
+
+public class SimpleRegex {
+
+  void regString() {
+    String s1 = "validRegex";
+    String s2 = "(InvalidRegex";
+  }
+
+  void validRegString() {
+    @Regex String s1 = "validRegex";
+    // :: error: (assignment)
+    @Regex String s2 = "(InvalidRegex"; // error
+  }
+
+  void compileCall() {
+    Pattern.compile("test.*[^123]$");
+    // :: error: (argument)
+    Pattern.compile("$test.*[^123"); // error
+  }
+
+  void requireValidReg(@Regex String reg, String nonReg) {
+    Pattern.compile(reg);
+    // :: error: (argument)
+    Pattern.compile(nonReg); // error
+  }
+
+  void testAddition(@Regex String reg, String nonReg) {
+    @Regex String s1 = reg;
+    @Regex String s2 = reg + "d.*sf";
+    @Regex String s3 = reg + reg;
+
+    // :: error: (assignment)
+    @Regex String n1 = nonReg; // error
+    // :: error: (assignment)
+    @Regex String n2 = reg + "(df"; // error
+    // :: error: (assignment)
+    @Regex String n3 = reg + nonReg; // error
+
+    // :: error: (assignment)
+    @Regex String o1 = nonReg; // error
+    // :: error: (assignment)
+    @Regex String o2 = nonReg + "sdf"; // error
+    // :: error: (assignment)
+    @Regex String o3 = nonReg + reg; // error
+  }
+
+  @Regex String regex = "()";
+  String nonRegex = "()";
+
+  void testCompoundConcatenation() {
+    takesRegex(regex);
+    // :: error: (compound.assignment)
+    regex += ")"; // error
+    takesRegex(regex);
+
+    nonRegex = "()";
+    // nonRegex is refined by flow to be a regular expression
+    takesRegex(nonRegex);
+    nonRegex += ")";
+    // :: error: (argument)
+    takesRegex(nonRegex); // error
+  }
+
+  void takesRegex(@Regex String s) {}
+
+  void testChar() {
+    @Regex char c1 = 'c';
+    @Regex Character c2 = 'c';
+
+    // :: error: (assignment)
+    @Regex char c3 = '('; // error
+    // :: error: (assignment)
+    @Regex Character c4 = '('; // error
+  }
+
+  void testCharConcatenation() {
+    @Regex String s1 = "rege" + 'x';
+    @Regex String s2 = 'r' + "egex";
+
+    // :: error: (assignment)
+    @Regex String s4 = "rege" + '('; // error
+    // :: error: (assignment)
+    @Regex String s5 = "reg(" + 'x'; // error
+    // :: error: (assignment)
+    @Regex String s6 = '(' + "egex"; // error
+    // :: error: (assignment)
+    @Regex String s7 = 'r' + "ege("; // error
+  }
+
+  void testPatternLiteral() {
+    Pattern.compile("non(", Pattern.LITERAL);
+    Pattern.compile(foo("regex"), Pattern.LITERAL);
+
+    // :: error: (argument)
+    Pattern.compile(foo("regex("), Pattern.LITERAL); // error
+    // :: error: (argument)
+    Pattern.compile("non("); // error
+    // :: error: (argument)
+    Pattern.compile(foo("regex")); // error
+    // :: error: (argument)
+    Pattern.compile("non(", Pattern.CASE_INSENSITIVE); // error
+  }
+
+  public static String foo(@Regex String s) {
+    return "non((";
+  }
+
+  //    TODO: This is not supported until the framework can read explicit
+  //    annotations from arrays.
+  //    void testArrayAllowedTypes() {
+  //        @Regex char[] ca1;
+  //        char @Regex [] ca2;
+  //        @Regex char @Regex [] ca3;
+  //        @Regex String[] s1;
+  //
+  //        // :: error: (type.invalid)
+  //        @Regex double[] da1;   // error
+  //        // :: error: (type.invalid)
+  //        double @Regex [] da2;   // error
+  //        // :: error: (type.invalid)
+  //        @Regex double @Regex [] da3;   // error
+  //        // :: error: (type.invalid)
+  //        String @Regex [] s2;    // error
+  //    }
+
+  //    TODO: This is not supported until the Regex Checker supports flow
+  //    sensitivity. See the associated comment at
+  //    org.checkerframework.checker/regex/RegexAnnotatedTypeFactory.java:visitNewArray
+  //    void testCharArrays(char c, @Regex char r) {
+  //        char @Regex [] c1 = {'r', 'e', 'g', 'e', 'x'};
+  //        char @Regex [] c2 = {'(', 'r', 'e', 'g', 'e', 'x', ')', '.', '*'};
+  //        char @Regex [] c3 = {r, 'e', 'g', 'e', 'x'};
+  //
+  //        // :: error: (assignment)
+  //        char @Regex [] c4 = {'(', 'r', 'e', 'g', 'e', 'x'};   // error
+  //        // :: error: (assignment)
+  //        char @Regex [] c5 = {c, '.', '*'};   // error
+  //    }
+}
diff --git a/checker/tests/regex/TestIsRegex.java b/checker/tests/regex/TestIsRegex.java
new file mode 100644
index 0000000..3ac4508
--- /dev/null
+++ b/checker/tests/regex/TestIsRegex.java
@@ -0,0 +1,125 @@
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.checkerframework.checker.regex.qual.*;
+import org.checkerframework.checker.regex.util.RegexUtil;
+
+public class TestIsRegex {
+  void test1(String str1) throws Exception {
+    if (!RegexUtil.isRegex(str1)) {
+      throw new Exception();
+    }
+    Pattern.compile(str1);
+  }
+
+  void test2(String str2) throws Exception {
+    if (!RegexUtil.isRegex(str2)) {
+      // :: error: (argument)
+      Pattern.compile(str2);
+    }
+  }
+
+  void test3(String str3) throws Exception {
+    if (RegexUtil.isRegex(str3)) {
+      Pattern.compile(str3);
+    } else {
+      throw new Exception();
+    }
+  }
+
+  void test4(String str4) throws Exception {
+    if (RegexUtil.isRegex(str4)) {
+      Pattern.compile(str4);
+    } else {
+      // :: error: (argument)
+      Pattern.compile(str4);
+    }
+  }
+
+  void test5(String str5) throws Exception {
+    if (!RegexUtil.isRegex(str5, 3)) {
+      throw new Exception();
+    }
+    Pattern.compile(str5).matcher("test").group(3);
+  }
+
+  void test6(String str6) throws Exception {
+    if (RegexUtil.isRegex(str6, 4)) {
+      Pattern.compile(str6).matcher("4kdfj").group(4);
+    } else {
+      // :: error: (argument)
+      Pattern.compile(str6);
+    }
+  }
+
+  void test7(String str7) throws Exception {
+    if (RegexUtil.isRegex(str7, 5)) {
+      // :: error: (group.count)
+      Pattern.compile(str7).matcher("4kdfj").group(6);
+    }
+  }
+
+  @Regex Pattern test8(String input) {
+    String datePattern = null;
+
+    if (input != null) {
+      datePattern = "regexkdafj";
+      if (!RegexUtil.isRegex(datePattern, 1)) {
+        throw new Error(
+            "error parsing regex " + datePattern + ": " + RegexUtil.regexError(datePattern));
+      }
+      return Pattern.compile(datePattern);
+    }
+    @Regex(1) String dp = datePattern;
+
+    if (input != null) { // just some test...
+      Pattern pattern = datePattern != null ? Pattern.compile(datePattern) : null;
+      return pattern;
+    } else {
+      Pattern pattern = datePattern != null ? Pattern.compile(dp) : null;
+      return pattern;
+    }
+  }
+
+  @Regex(1) Pattern test9(String input) {
+    String datePattern = null;
+
+    if (input != null) {
+      datePattern = "regexkdafj";
+      if (!RegexUtil.isRegex(datePattern, 1)) {
+        throw new Error(
+            "error parsing regex " + datePattern + ": " + RegexUtil.regexError(datePattern));
+      }
+      return Pattern.compile(datePattern);
+    }
+    @Regex(1) String dp = datePattern;
+
+    if (input != null) { // just some test...
+      Pattern pattern = datePattern != null ? Pattern.compile(datePattern) : null;
+      return pattern;
+    } else {
+      Pattern pattern = datePattern != null ? Pattern.compile(dp) : null;
+      return pattern;
+    }
+  }
+
+  void test10(String s) throws Exception {
+    if (!RegexUtil.isRegex(s, 2)) {
+      throw new Exception();
+    }
+    Pattern p = Pattern.compile(s);
+    Matcher m = p.matcher("abc");
+    String g = m.group(1);
+  }
+
+  void test11(String s) throws Exception {
+    @Regex(2) String l1 = RegexUtil.asRegex(s, 2);
+    @Regex(1) String l2 = RegexUtil.asRegex(s, 2);
+    @Regex String l3 = RegexUtil.asRegex(s, 2);
+    // :: error: (assignment)
+    @Regex(3) String l4 = RegexUtil.asRegex(s, 2);
+  }
+
+  @Regex(2) String test12(String s, boolean b) throws Exception {
+    return b ? null : RegexUtil.asRegex(s, 2);
+  }
+}
diff --git a/checker/tests/regex/TestRegex.java b/checker/tests/regex/TestRegex.java
new file mode 100644
index 0000000..b38fbb1
--- /dev/null
+++ b/checker/tests/regex/TestRegex.java
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.regex.qual.*;
+
+// test-case for issue 128
+public class TestRegex {
+
+  public void Concatenation2() {
+    @Regex String a = "a";
+    // :: error: (compound.assignment)
+    a += "(";
+  }
+}
+
+// test-case for issue 148
+class Search {
+  public static void main(String[] args) {
+    if (!org.checkerframework.checker.regex.util.RegexUtil.isRegex(args[0], 4)) {
+      return;
+    }
+    @Regex(4) String regex = args[0];
+  }
+}
diff --git a/checker/tests/regex/TypeParamSubtype.java b/checker/tests/regex/TypeParamSubtype.java
new file mode 100644
index 0000000..f7fdbfc
--- /dev/null
+++ b/checker/tests/regex/TypeParamSubtype.java
@@ -0,0 +1,23 @@
+import java.util.Collection;
+import org.checkerframework.checker.regex.qual.Regex;
+
+public class TypeParamSubtype {
+  // These are legal because null has type @Regex String
+  // <T extends @Regex String> void nullRegexSubtype(Collection<T> col) {
+  //     // :: error: (argument)
+  //     col.add(null);
+  // }
+  //
+  // <T extends String> void nullSimpleSubtype(Collection<T> col) {
+  //     // :: error: (argument)
+  //     col.add(null);
+  // }
+
+  <T extends @Regex String, U extends T> void nullRegexSubtype(Collection<T> col, U u) {
+    col.add(u);
+  }
+
+  <T extends String, U extends T> void nullSimpleSubtype(Collection<T> col, U u) {
+    col.add(u);
+  }
+}
diff --git a/checker/tests/regex/TypeVarMemberSelect.java b/checker/tests/regex/TypeVarMemberSelect.java
new file mode 100644
index 0000000..2a0b850
--- /dev/null
+++ b/checker/tests/regex/TypeVarMemberSelect.java
@@ -0,0 +1,19 @@
+import org.checkerframework.checker.regex.qual.*;
+
+class Box<T extends @Regex(1) Object> {
+  @Regex(1) T t1;
+
+  T t2;
+}
+
+class TypeVarMemberSelect<V extends Box<@Regex(2) String>> {
+
+  void test(V v) {
+    // :: error: (assignment)
+    @Regex(2) String local1 = v.t1;
+
+    // Previously the type of the right hand side would have been T which is wrong.  This test
+    // was added to make sure we call viewpoint adaptation when type variables are the receiver.
+    @Regex(2) String local2 = v.t2;
+  }
+}
diff --git a/checker/tests/regex/WildcardInvoke.java b/checker/tests/regex/WildcardInvoke.java
new file mode 100644
index 0000000..646bb0f
--- /dev/null
+++ b/checker/tests/regex/WildcardInvoke.java
@@ -0,0 +1,10 @@
+public class WildcardInvoke {
+  class Demo<T> {
+    void call(T p) {}
+  }
+
+  void m() {
+    Demo<?> d = null;
+    d.call(null);
+  }
+}
diff --git a/checker/tests/regex_poly/PolyRegexTests.java b/checker/tests/regex_poly/PolyRegexTests.java
new file mode 100644
index 0000000..7b69774
--- /dev/null
+++ b/checker/tests/regex_poly/PolyRegexTests.java
@@ -0,0 +1,82 @@
+import org.checkerframework.checker.regex.qual.PolyRegex;
+import org.checkerframework.checker.regex.qual.Regex;
+
+public class PolyRegexTests {
+
+  @Regex(0) String field1 = "abc".toString();
+
+  public static @PolyRegex String method(@PolyRegex String s) {
+    return s;
+  }
+
+  public void testRegex(@Regex String str) {
+    @Regex String s = method(str);
+  }
+
+  public void testNonRegex(String str) {
+    // :: error: (assignment)
+    @Regex String s = method(str); // error
+  }
+
+  public void testInternRegex(@Regex String str) {
+    @Regex String s = str.intern();
+  }
+
+  public void testInternNonRegex(String str) {
+    // :: error: (assignment)
+    @Regex String s = str.intern(); // error
+  }
+
+  public void testToStringRegex(@Regex String str) {
+    @Regex String s = str.toString();
+  }
+
+  public void testToStringNonRegex(String str) {
+    // :: error: (assignment)
+    @Regex String s = str.toString(); // error
+  }
+
+  public @PolyRegex String testPolyRegexConcat(@PolyRegex String s1, @PolyRegex String s2) {
+    return s1 + s2;
+  }
+
+  public void testPolyRegexConcatErrors(@PolyRegex String polyReg, String nonPolyReg) {
+    // :: error: (assignment)
+    @PolyRegex String test1 = polyReg + nonPolyReg; // error
+    // :: error: (assignment)
+    @PolyRegex String test2 = nonPolyReg + polyReg; // error
+    // :: error: (assignment)
+    @PolyRegex String test3 = nonPolyReg + nonPolyReg; // error
+  }
+
+  public void testRegexPolyRegexConcat(@PolyRegex String polyReg, @Regex String reg) {
+    @PolyRegex String test1 = polyReg + reg;
+    @PolyRegex String test2 = reg + polyReg;
+  }
+
+  public void testRegexPolyRegexConcatErrors(
+      @PolyRegex String polyReg, @Regex String reg, String str) {
+    // :: error: (assignment)
+    @PolyRegex String test1 = polyReg + str; // error
+    // :: error: (assignment)
+    @PolyRegex String test2 = str + polyReg; // error
+    // :: error: (assignment)
+    @PolyRegex String test3 = reg + str; // error
+    // :: error: (assignment)
+    @PolyRegex String test4 = str + reg; // error
+
+    // :: error: (assignment)
+    @PolyRegex String test5 = str + str; // error
+  }
+
+  public static @PolyRegex String slice(@PolyRegex String seq, int start, int end) {
+    if (seq == null) {
+      return null;
+    }
+    return seq;
+  }
+
+  public static @PolyRegex String slice(@PolyRegex String seq, long start, int end) {
+    return slice(seq, (int) start, end);
+  }
+}
diff --git a/checker/tests/signature/ArraysAsList.java b/checker/tests/signature/ArraysAsList.java
new file mode 100644
index 0000000..b8ae275
--- /dev/null
+++ b/checker/tests/signature/ArraysAsList.java
@@ -0,0 +1,10 @@
+import java.util.Arrays;
+import java.util.List;
+import org.checkerframework.checker.signature.qual.*;
+
+public class ArraysAsList {
+
+  List<String> m() {
+    return Arrays.asList("id", "department_id", "permission_id", "expected_connection_time");
+  }
+}
diff --git a/checker/tests/signature/CanonicalNameNonEmptyTest.java b/checker/tests/signature/CanonicalNameNonEmptyTest.java
new file mode 100644
index 0000000..91720c6
--- /dev/null
+++ b/checker/tests/signature/CanonicalNameNonEmptyTest.java
@@ -0,0 +1,29 @@
+import org.checkerframework.checker.signature.qual.*;
+
+public class CanonicalNameNonEmptyTest {
+
+  @CanonicalName String nonEmpty1(@CanonicalNameOrEmpty String s) {
+    if (s.isEmpty()) {
+      return null;
+    } else {
+      return s;
+    }
+  }
+
+  @CanonicalName String nonEmpty2(@CanonicalNameOrEmpty String s) {
+    if (!s.isEmpty()) {
+      return s;
+    } else {
+      return null;
+    }
+  }
+
+  @CanonicalName String nonEmpty3(@FullyQualifiedName String s) {
+    if (s.isEmpty()) {
+      return null;
+    } else {
+      // :: error: (return)
+      return s;
+    }
+  }
+}
diff --git a/checker/tests/signature/ClassGetNameBinaryName.java b/checker/tests/signature/ClassGetNameBinaryName.java
new file mode 100644
index 0000000..e8b59c5
--- /dev/null
+++ b/checker/tests/signature/ClassGetNameBinaryName.java
@@ -0,0 +1,120 @@
+import org.checkerframework.checker.signature.qual.CanonicalNameAndBinaryName;
+import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
+import org.checkerframework.checker.signature.qual.PrimitiveType;
+
+public class ClassGetNameBinaryName {
+
+  static class Nested {}
+
+  class Inner {}
+
+  class TestGetName {
+
+    @DotSeparatedIdentifiers String s1 = ClassGetNameBinaryName.class.getName();
+
+    @DotSeparatedIdentifiers String s2a = Integer.class.getName();
+
+    @DotSeparatedIdentifiers String s2b = java.lang.Integer.class.getName();
+
+    @DotSeparatedIdentifiers String s4a = Boolean.class.getName();
+
+    // :: error: (assignment)
+    @PrimitiveType String s4b = Boolean.class.getName();
+
+    // :: error: (assignment)
+    @DotSeparatedIdentifiers String s12 = Nested.class.getName();
+
+    // :: error: (assignment)
+    @DotSeparatedIdentifiers String s13 = Inner.class.getName();
+
+    /// Primitive types
+
+    @PrimitiveType String prim1 = int.class.getName();
+
+    // :: error: (assignment)
+    @DotSeparatedIdentifiers String prim2 = int.class.getName();
+
+    @PrimitiveType String prim3 = boolean.class.getName();
+
+    // :: error: (assignment)
+    @DotSeparatedIdentifiers String prim4 = boolean.class.getName();
+
+    // :: error: (assignment)
+    @DotSeparatedIdentifiers String prim5 = void.class.getName();
+
+    // :: error: (assignment)
+    @PrimitiveType String prim6 = void.class.getName();
+
+    /// Arrays
+
+    // :: error: (assignment)
+    @DotSeparatedIdentifiers String s6 = int[].class.getName();
+
+    // :: error: (assignment)
+    @DotSeparatedIdentifiers String s7 = int[][].class.getName();
+
+    // :: error: (assignment)
+    @DotSeparatedIdentifiers String s8 = boolean[].class.getName();
+
+    // :: error: (assignment)
+    @DotSeparatedIdentifiers String s9 = Integer[].class.getName();
+
+    // :: error: (assignment)
+    @DotSeparatedIdentifiers String s10 = Boolean[].class.getName();
+  }
+
+  class TestGetCanonicalName {
+
+    @CanonicalNameAndBinaryName String s1 = ClassGetNameBinaryName.class.getCanonicalName();
+
+    @CanonicalNameAndBinaryName String s2a = Integer.class.getCanonicalName();
+
+    @CanonicalNameAndBinaryName String s2b = java.lang.Integer.class.getCanonicalName();
+
+    @CanonicalNameAndBinaryName String s4a = Boolean.class.getCanonicalName();
+
+    // :: error: (assignment)
+    @PrimitiveType String s4b = Boolean.class.getCanonicalName();
+
+    // :: error: (assignment)
+    @CanonicalNameAndBinaryName String s12 = Nested.class.getCanonicalName();
+
+    // :: error: (assignment)
+    @CanonicalNameAndBinaryName String s13 = Inner.class.getName();
+
+    /// Primitive types
+
+    @PrimitiveType String prim1 = int.class.getCanonicalName();
+
+    // :: error: (assignment)
+    @CanonicalNameAndBinaryName String prim2 = int.class.getCanonicalName();
+
+    @PrimitiveType String prim3 = boolean.class.getCanonicalName();
+
+    // :: error: (assignment)
+    @CanonicalNameAndBinaryName String prim4 = boolean.class.getCanonicalName();
+
+    // :: error: (assignment)
+    @CanonicalNameAndBinaryName String prim5 = void.class.getCanonicalName();
+
+    // :: error: (assignment)
+    @PrimitiveType String prim6 = void.class.getCanonicalName();
+
+    /// Arrays
+
+    // :: error: (assignment)
+    @CanonicalNameAndBinaryName String s6 = int[].class.getCanonicalName();
+
+    // :: error: (assignment)
+    @CanonicalNameAndBinaryName String s7 = int[][].class.getCanonicalName();
+
+    // :: error: (assignment)
+    @CanonicalNameAndBinaryName String s8 = boolean[].class.getCanonicalName();
+
+    // :: error: (assignment)
+    @CanonicalNameAndBinaryName String s9 = Integer[].class.getCanonicalName();
+
+    // :: error: (assignment)
+    @CanonicalNameAndBinaryName String s10 = Boolean[].class.getCanonicalName();
+  }
+}
diff --git a/checker/tests/signature/Conversion.java b/checker/tests/signature/Conversion.java
new file mode 100644
index 0000000..4dfceda
--- /dev/null
+++ b/checker/tests/signature/Conversion.java
@@ -0,0 +1,104 @@
+import org.checkerframework.checker.signature.qual.*;
+
+public class Conversion {
+
+  class CharChar {
+    @InternalForm String binaryNameToInternalForm(@BinaryName String bn) {
+      return bn.replace('.', '/');
+    }
+
+    @BinaryName String internalFormToBinaryName(@InternalForm String iform) {
+      return iform.replace('/', '.');
+    }
+
+    @InternalForm String binaryNameToInternalFormWRONG1(@BinaryName String bn) {
+      // :: error: (return)
+      return bn.replace('/', '.');
+    }
+
+    @InternalForm String binaryNameToInternalFormWRONG2(@BinaryName String bn) {
+      // :: error: (return)
+      return bn.replace(':', '/');
+    }
+
+    @InternalForm String binaryNameToInternalFormWRONG3(String bn) {
+      // :: error: (return)
+      return bn.replace('.', '/');
+    }
+
+    @BinaryName String internalFormToBinaryNameWRONG1(@InternalForm String iform) {
+      // :: error: (return)
+      return iform.replace('.', '/');
+    }
+
+    @BinaryName String internalFormToBinaryNameWRONG2(@InternalForm String iform) {
+      // :: error: (return)
+      return iform.replace('/', ':');
+    }
+
+    @BinaryName String internalFormToBinaryNameWRONG3(String iform) {
+      // :: error: (return)
+      return iform.replace('/', '.');
+    }
+
+    @DotSeparatedIdentifiers String binaryNameToDotSeparatedIdentifiers(@BinaryName String bn) {
+      // :: error: (return)
+      return bn.replace('$', '.');
+    }
+
+    @FullyQualifiedName String binaryNameToFullyQualifiedName(@BinaryName String bn) {
+      // :: error: (return)
+      return bn.replace('$', '.');
+    }
+  }
+
+  class CharSequenceCharSequence {
+    @InternalForm String binaryNameToInternalForm(@BinaryName String bn) {
+      return bn.replace(".", "/");
+    }
+
+    @BinaryName String internalFormToBinaryName(@InternalForm String iform) {
+      return iform.replace("/", ".");
+    }
+
+    @InternalForm String binaryNameToInternalFormWRONG1(@BinaryName String bn) {
+      // :: error: (return)
+      return bn.replace("/", ".");
+    }
+
+    @InternalForm String binaryNameToInternalFormWRONG2(@BinaryName String bn) {
+      // :: error: (return)
+      return bn.replace(":", "/");
+    }
+
+    @InternalForm String binaryNameToInternalFormWRONG3(String bn) {
+      // :: error: (return)
+      return bn.replace(".", "/");
+    }
+
+    @BinaryName String internalFormToBinaryNameWRONG1(@InternalForm String iform) {
+      // :: error: (return)
+      return iform.replace(".", "/");
+    }
+
+    @BinaryName String internalFormToBinaryNameWRONG2(@InternalForm String iform) {
+      // :: error: (return)
+      return iform.replace("/", ":");
+    }
+
+    @BinaryName String internalFormToBinaryNameWRONG3(String iform) {
+      // :: error: (return)
+      return iform.replace("/", ".");
+    }
+
+    @DotSeparatedIdentifiers String binaryNameToDotSeparatedIdentifiers(@BinaryName String bn) {
+      // :: error: (return)
+      return bn.replace("$", ".");
+    }
+
+    @FullyQualifiedName String binaryNameToFullyQualifiedName(@BinaryName String bn) {
+      // :: error: (return)
+      return bn.replace("$", ".");
+    }
+  }
+}
diff --git a/checker/tests/signature/DiamondTest.java b/checker/tests/signature/DiamondTest.java
new file mode 100644
index 0000000..e23d790
--- /dev/null
+++ b/checker/tests/signature/DiamondTest.java
@@ -0,0 +1,9 @@
+import java.util.ArrayList;
+import org.checkerframework.checker.signature.qual.*;
+
+public class DiamondTest {
+
+  void m() {
+    ArrayList<String> list = new ArrayList<>();
+  }
+}
diff --git a/checker/tests/signature/FakeOverridePoly.java b/checker/tests/signature/FakeOverridePoly.java
new file mode 100644
index 0000000..a4e6978
--- /dev/null
+++ b/checker/tests/signature/FakeOverridePoly.java
@@ -0,0 +1,11 @@
+// @skip-test until fake overrides affect formal parameter types as well as return types
+
+import javax.lang.model.element.Name;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+
+public class FakeOverridePoly {
+
+  void m(@CanonicalName Name n) {
+    @CanonicalName String s = n.toString();
+  }
+}
diff --git a/checker/tests/signature/PolySignatureTest.java b/checker/tests/signature/PolySignatureTest.java
new file mode 100644
index 0000000..de87622
--- /dev/null
+++ b/checker/tests/signature/PolySignatureTest.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.signature.qual.*;
+
+public class PolySignatureTest {
+
+  @PolySignature String polyMethod(@PolySignature String arg) {
+    return arg;
+  }
+
+  void m(@ClassGetName String s) {
+    @ClassGetName String s1 = polyMethod(s);
+    @ClassGetName String s2 = s.intern();
+  }
+}
diff --git a/checker/tests/signature/PolySignatureTest2.java b/checker/tests/signature/PolySignatureTest2.java
new file mode 100644
index 0000000..7c69beb
--- /dev/null
+++ b/checker/tests/signature/PolySignatureTest2.java
@@ -0,0 +1,18 @@
+// Test for stub files and https://tinyurl.com/cfissue/658 .
+// Commented in part because that issue is not yet fixed.
+
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import org.checkerframework.checker.signature.qual.*;
+
+public class PolySignatureTest2 {
+
+  @CanonicalNameOrEmpty Name m1(TypeElement e) {
+    return e.getQualifiedName();
+  }
+
+  @DotSeparatedIdentifiers String m2(@DotSeparatedIdentifiers Name n) {
+    // :: error: (return)
+    return n.toString();
+  }
+}
diff --git a/checker/tests/signature/RefinedReturnTest.java b/checker/tests/signature/RefinedReturnTest.java
new file mode 100644
index 0000000..1628b07
--- /dev/null
+++ b/checker/tests/signature/RefinedReturnTest.java
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.signature.qual.*;
+
+// Not on classpath when running the Checker Framework tests.
+// import org.apache.bcel.generic.ClassGen;
+
+public class RefinedReturnTest {
+
+  public class Super {
+    public @FullyQualifiedName String aString() {
+      return "java.lang.Integer[][]";
+    }
+  }
+
+  public class Sub extends Super {
+    @Override
+    public @ArrayWithoutPackage String aString() {
+      return "Integer[]";
+    }
+  }
+
+  void m() {
+    @ArrayWithoutPackage String s = new Sub().aString();
+  }
+}
diff --git a/checker/tests/signature/SignatureConcatenation.java b/checker/tests/signature/SignatureConcatenation.java
new file mode 100644
index 0000000..c9bef1b
--- /dev/null
+++ b/checker/tests/signature/SignatureConcatenation.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.signature.qual.*;
+
+public class SignatureConcatenation {
+
+  @ClassGetSimpleName String m(@ClassGetSimpleName String arg1, @ClassGetSimpleName String arg2) {
+    // :: error: (return)
+    return arg1 + arg2;
+  }
+}
diff --git a/checker/tests/signature/SignatureLiteralTest.java b/checker/tests/signature/SignatureLiteralTest.java
new file mode 100644
index 0000000..11739b5
--- /dev/null
+++ b/checker/tests/signature/SignatureLiteralTest.java
@@ -0,0 +1,7 @@
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+
+public class SignatureLiteralTest {
+
+  protected static final @FullyQualifiedName String FORMAT_NAME =
+      "org.checkerframework.checker.formatter.qual.Format";
+}
diff --git a/checker/tests/signature/SignatureTypeFactoryTest.java b/checker/tests/signature/SignatureTypeFactoryTest.java
new file mode 100644
index 0000000..12ead30
--- /dev/null
+++ b/checker/tests/signature/SignatureTypeFactoryTest.java
@@ -0,0 +1,880 @@
+import org.checkerframework.checker.signature.qual.*;
+
+public class SignatureTypeFactoryTest {
+
+  // The hierarchy of type representations contains:
+  //
+  //     SignatureUnknown.class,
+  //
+  //     FullyQualifiedName.class,
+  //     ClassGetName.class,
+  //     FieldDescriptor.class,
+  //     InternalForm.class,
+  //     ClassGetSimpleName.class,
+  //     FqBinaryName.class,
+  //
+  //     BinaryName.class,
+  //     FieldDescriptorWithoutPackage.class,
+  //
+  //     ArrayWithoutPackage.class,
+  //     DotSeparatedIdentifiers.class,
+  //     BinaryNameWithoutPackage.class,
+  //
+  //     Identifier.class,
+  //
+  //     FieldDescriptorForPrimitive.class
+  //
+  //     SignatureBottom.class
+  //
+  // There are also signature representations, which are not handled yet.
+
+  void m() {
+
+    String s1 = "a";
+    String s2 = "a.b";
+    String s3 = "a.b$c";
+    String s4 = "B";
+    String s5 = "[B";
+    String s6 = "Ljava/lang/String;";
+    String s7 = "Ljava/lang/String";
+    // TODO: Should be @MethodDescriptor
+    String s8 = "foo()V";
+    String s9 = "java.lang.annotation.Retention";
+    String s10 = "dummy";
+    String s11 = null;
+    String s12 = "a.b$c[][]";
+    String s13 = "a.b.c[][]";
+    String s14 = "[[Ljava/lang/String;";
+    String s15 = "";
+    String s16 = "[]";
+    String s17 = "[][]";
+    String s18 = "null";
+    String s19 = "abstract";
+    String s20 = "float";
+    String s21 = "float ";
+    String s22 = " Foo";
+
+    // All the examples from the manual
+    String t13 = "int";
+    String t14 = "int[][]";
+    String t1 = "I";
+    String t12 = "[[I";
+
+    String t5 = "MyClass";
+    String t2 = "LMyClass;";
+    String t6 = "MyClass[]";
+    String t7 = "[LMyClass;";
+
+    String t29 = "";
+    String t33 = "[]";
+
+    String t15 = "java.lang.Integer";
+    String t16 = "java.lang.Integer[]";
+    String t22 = "java/lang/Integer";
+    String t23 = "java/lang/Integer[]";
+    String t3 = "Ljava/lang/Integer;";
+    String t8 = "[Ljava.lang.Integer;";
+    String t9 = "[Ljava/lang/Integer;";
+
+    String t24 = "pakkage/Outer$Inner";
+    String t25 = "pakkage/Outer$Inner[]";
+
+    String t28 = "pakkage/Outer$22";
+    String t27 = "Lpakkage/Outer$22;";
+    String t26 = "pakkage.Outer$22";
+    String t32 = "pakkage/Outer$22[]";
+    String t30 = "pakkage.Outer$22[]";
+    String t31 = "[Lpakkage.Outer$22;";
+
+    String t34 = "org.plumelib.reflection.TestReflectionPlume$Inner.InnerInner";
+    String t17 = "pakkage.Outer.Inner";
+    String t18 = "pakkage.Outer.Inner[]";
+    String t19 = "pakkage.Outer$Inner";
+    String t21 = "pakkage.Outer$Inner[]";
+    String t20 = "Lpakkage.Outer$Inner;";
+    String t10 = "[Lpakkage.Outer$Inner;";
+    String t4 = "Lpakkage/Outer$Inner;";
+    String t11 = "[Lpakkage/Outer$Inner;";
+
+    String us; // @SignatureUnknown
+    @FullyQualifiedName String fqn;
+    @ClassGetName String cgn;
+    @FieldDescriptor String fd;
+    @InternalForm String iform;
+    @ClassGetSimpleName String sn;
+    @FqBinaryName String fbn;
+    @BinaryName String bn;
+    // not public, so a user can't write it.
+    // @SignatureBottom String sb;
+
+    us = s1;
+    fqn = s1;
+    cgn = s1;
+    // :: error: (assignment)
+    fd = s1;
+    iform = s1;
+    sn = s1;
+    bn = s1;
+    fbn = s1;
+
+    us = s2;
+    fqn = s2;
+    cgn = s2;
+    // :: error: (assignment)
+    fd = s2;
+    // :: error: (assignment)
+    iform = s2;
+    // :: error: (assignment)
+    sn = s2;
+    bn = s2;
+    fbn = s2;
+
+    us = s3;
+    fqn = s3;
+    cgn = s3;
+    // :: error: (assignment)
+    fd = s3;
+    // :: error: (assignment)
+    iform = s3;
+    // :: error: (assignment)
+    sn = s3;
+    bn = s3;
+    fbn = s3;
+
+    us = s4;
+    fqn = s4;
+    cgn = s4;
+    fd = s4;
+    iform = s4;
+    sn = s4;
+    bn = s4;
+    fbn = s4;
+
+    us = s5;
+    // :: error: (assignment)
+    fqn = s5;
+    cgn = s5;
+    fd = s5;
+    // :: error: (assignment)
+    iform = s5;
+    // :: error: (assignment)
+    sn = s5;
+    // :: error: (assignment)
+    bn = s5;
+    // :: error: (assignment)
+    fbn = s5;
+
+    us = s6;
+    // :: error: (assignment)
+    fqn = s6;
+    // :: error: (assignment)
+    cgn = s6;
+    fd = s6;
+    // :: error: (assignment)
+    iform = s6;
+    // :: error: (assignment)
+    sn = s6;
+    // :: error: (assignment)
+    bn = s6;
+    // :: error: (assignment)
+    fbn = s6;
+
+    us = s7;
+    // :: error: (assignment)
+    fqn = s7;
+    // :: error: (assignment)
+    cgn = s7;
+    // :: error: (assignment)
+    fd = s7;
+    iform = s7;
+    // :: error: (assignment)
+    sn = s7;
+    // :: error: (assignment)
+    bn = s7;
+    // :: error: (assignment)
+    fbn = s7;
+
+    us = s8;
+    // :: error: (assignment)
+    fqn = s8;
+    // :: error: (assignment)
+    cgn = s8;
+    // :: error: (assignment)
+    fd = s8;
+    // :: error: (assignment)
+    iform = s8;
+    // :: error: (assignment)
+    sn = s8;
+    // :: error: (assignment)
+    bn = s8;
+    // :: error: (assignment)
+    fbn = s8;
+
+    us = s9;
+    fqn = s9;
+    cgn = s9;
+    // :: error: (assignment)
+    fd = s9;
+    // :: error: (assignment)
+    iform = s9;
+    // :: error: (assignment)
+    sn = s9;
+    bn = s9;
+    fbn = s9;
+
+    us = s10;
+    fqn = s10;
+    cgn = s10;
+    // :: error: (assignment)
+    fd = s10;
+    iform = s10;
+    sn = s10;
+    bn = s10;
+    fbn = s10;
+
+    us = s11;
+    fqn = s11;
+    cgn = s11;
+    fd = s11;
+    iform = s11;
+    sn = s11;
+    bn = s11;
+    fbn = s11;
+
+    us = s12;
+    fqn = s12;
+    // :: error: (assignment)
+    cgn = s12;
+    // :: error: (assignment)
+    fd = s12;
+    // :: error: (assignment)
+    iform = s12;
+    // :: error: (assignment)
+    sn = s12;
+    // :: error: (assignment)
+    bn = s12;
+    fbn = s12;
+
+    us = s13;
+    fqn = s13;
+    // :: error: (assignment)
+    cgn = s13;
+    // :: error: (assignment)
+    fd = s13;
+    // :: error: (assignment)
+    iform = s13;
+    // :: error: (assignment)
+    sn = s13;
+    // :: error: (assignment)
+    bn = s13;
+    fbn = s13;
+
+    us = s14;
+    // :: error: (assignment)
+    fqn = s14;
+    // :: error: (assignment)
+    cgn = s14;
+    fd = s14;
+    // :: error: (assignment)
+    iform = s14;
+    // :: error: (assignment)
+    sn = s14;
+    // :: error: (assignment)
+    bn = s14;
+    // :: error: (assignment)
+    fbn = s14;
+
+    us = s15;
+    // :: error: (assignment)
+    fqn = s15;
+    // :: error: (assignment)
+    cgn = s15;
+    // :: error: (assignment)
+    fd = s15;
+    // :: error: (assignment)
+    iform = s15;
+    sn = s15;
+    // :: error: (assignment)
+    bn = s15;
+    // :: error: (assignment)
+    fbn = s15;
+
+    us = s16;
+    // :: error: (assignment)
+    fqn = s16;
+    // :: error: (assignment)
+    cgn = s16;
+    // :: error: (assignment)
+    fd = s16;
+    // :: error: (assignment)
+    iform = s16;
+    sn = s16;
+    // :: error: (assignment)
+    bn = s16;
+    // :: error: (assignment)
+    fbn = s16;
+
+    us = s17;
+    // :: error: (assignment)
+    fqn = s17;
+    // :: error: (assignment)
+    cgn = s17;
+    // :: error: (assignment)
+    fd = s17;
+    // :: error: (assignment)
+    iform = s17;
+    sn = s17;
+    // :: error: (assignment)
+    bn = s17;
+    // :: error: (assignment)
+    fbn = s17;
+
+    us = s18;
+    // :: error: (assignment)
+    fqn = s18;
+    // :: error: (assignment)
+    cgn = s18;
+    // :: error: (assignment)
+    fd = s18;
+    // :: error: (assignment)
+    iform = s18;
+    // :: error: (assignment)
+    sn = s18;
+    // :: error: (assignment)
+    bn = s18;
+    // :: error: (assignment)
+    fbn = s18;
+
+    us = s19;
+    // :: error: (assignment)
+    fqn = s19;
+    // :: error: (assignment)
+    cgn = s19;
+    // :: error: (assignment)
+    fd = s19;
+    // :: error: (assignment)
+    iform = s19;
+    // :: error: (assignment)
+    sn = s19;
+    // :: error: (assignment)
+    bn = s19;
+    // :: error: (assignment)
+    fbn = s19;
+
+    us = s20;
+    fqn = s20;
+    cgn = s20;
+    // :: error: (assignment)
+    fd = s20;
+    // :: error: (assignment)
+    iform = s20;
+    sn = s20;
+    // :: error: (assignment)
+    bn = s20;
+    fbn = s20;
+
+    us = s21;
+    // :: error: (assignment)
+    fqn = s21;
+    // :: error: (assignment)
+    cgn = s21;
+    // :: error: (assignment)
+    fd = s21;
+    // :: error: (assignment)
+    iform = s21;
+    // :: error: (assignment)
+    sn = s21;
+    // :: error: (assignment)
+    bn = s21;
+    // :: error: (assignment)
+    fbn = s21;
+
+    us = s22;
+    // :: error: (assignment)
+    fqn = s22;
+    // :: error: (assignment)
+    cgn = s22;
+    // :: error: (assignment)
+    fd = s22;
+    // :: error: (assignment)
+    iform = s22;
+    // :: error: (assignment)
+    sn = s22;
+    // :: error: (assignment)
+    bn = s22;
+    // :: error: (assignment)
+    fbn = s22;
+
+    // Examples from the manual start here
+
+    us = t13;
+    fqn = t13;
+    cgn = t13;
+    // :: error: (assignment)
+    fd = t13;
+    // :: error: (assignment)
+    iform = t13;
+    sn = t13;
+    // :: error: (assignment)
+    bn = t13;
+    fbn = t13;
+
+    us = t14;
+    fqn = t14;
+    // :: error: (assignment)
+    cgn = t14;
+    // :: error: (assignment)
+    fd = t14;
+    // :: error: (assignment)
+    iform = t14;
+    sn = t14;
+    // :: error: (assignment)
+    bn = t14; // t14 is int[][]
+
+    us = t1;
+    fqn = t1;
+    cgn = t1;
+    fd = t1;
+    iform = t1;
+    sn = t1;
+    bn = t1;
+    fbn = t1;
+
+    us = t12;
+    // :: error: (assignment)
+    fqn = t12;
+    cgn = t12;
+    fd = t12;
+    // :: error: (assignment)
+    iform = t12;
+    // :: error: (assignment)
+    sn = t12;
+    // :: error: (assignment)
+    bn = t12;
+    // :: error: (assignment)
+    fbn = t12;
+
+    us = t5;
+    fqn = t5;
+    cgn = t5;
+    // :: error: (assignment)
+    fd = t5;
+    iform = t5;
+    sn = t5;
+    bn = t5;
+    fbn = t5;
+
+    us = t2;
+    // :: error: (assignment)
+    fqn = t2;
+    // :: error: (assignment)
+    cgn = t2;
+    fd = t2;
+    // :: error: (assignment)
+    iform = t2;
+    // :: error: (assignment)
+    sn = t2;
+    // :: error: (assignment)
+    bn = t2;
+    // :: error: (assignment)
+    fbn = t2;
+
+    us = t6;
+    fqn = t6;
+    // :: error: (assignment)
+    cgn = t6;
+    // :: error: (assignment)
+    fd = t6;
+    // :: error: (assignment)
+    iform = t6;
+    sn = t6;
+    // :: error: (assignment)
+    bn = t6;
+    fbn = t6;
+
+    us = t7;
+    // :: error: (assignment)
+    fqn = t7;
+    cgn = t7;
+    fd = t7;
+    // :: error: (assignment)
+    iform = t7;
+    // :: error: (assignment)
+    sn = t7;
+    // :: error: (assignment)
+    bn = t7;
+    // :: error: (assignment)
+    fbn = t7;
+
+    us = t29;
+    // :: error: (assignment)
+    fqn = t29;
+    // :: error: (assignment)
+    cgn = t29;
+    // :: error: (assignment)
+    fd = t29;
+    // :: error: (assignment)
+    iform = t29;
+    sn = t29;
+    // :: error: (assignment)
+    bn = t29;
+    // :: error: (assignment)
+    fbn = t29;
+
+    us = t33;
+    // :: error: (assignment)
+    fqn = t33;
+    // :: error: (assignment)
+    cgn = t33;
+    // :: error: (assignment)
+    fd = t33;
+    // :: error: (assignment)
+    iform = t33;
+    sn = t33;
+    // :: error: (assignment)
+    bn = t33;
+    // :: error: (assignment)
+    fbn = t33;
+
+    us = t15;
+    fqn = t15;
+    cgn = t15;
+    // :: error: (assignment)
+    fd = t15;
+    // :: error: (assignment)
+    iform = t15;
+    // :: error: (assignment)
+    sn = t15;
+    bn = t15;
+    fbn = t15;
+
+    us = t16;
+    fqn = t16;
+    // :: error: (assignment)
+    cgn = t16;
+    // :: error: (assignment)
+    fd = t16;
+    // :: error: (assignment)
+    iform = t16;
+    // :: error: (assignment)
+    sn = t16;
+    // :: error: (assignment)
+    bn = t16; // t16 is java.lang.Integer[]
+
+    us = t22;
+    // :: error: (assignment)
+    fqn = t22;
+    // :: error: (assignment)
+    cgn = t22;
+    // :: error: (assignment)
+    fd = t22;
+    iform = t22;
+    // :: error: (assignment)
+    sn = t22;
+    // :: error: (assignment)
+    bn = t22;
+    // :: error: (assignment)
+    fbn = t22;
+
+    us = t23;
+    // :: error: (assignment)
+    fqn = t23;
+    // :: error: (assignment)
+    cgn = t23;
+    // :: error: (assignment)
+    fd = t23;
+    // :: error: (assignment)
+    iform = t23; // t23 is java/lang/Integer[]
+    // :: error: (assignment)
+    sn = t23;
+    // :: error: (assignment)
+    bn = t23;
+    // :: error: (assignment)
+    fbn = t23;
+
+    us = t3;
+    // :: error: (assignment)
+    fqn = t3;
+    // :: error: (assignment)
+    cgn = t3;
+    fd = t3;
+    // :: error: (assignment)
+    iform = t3;
+    // :: error: (assignment)
+    sn = t3;
+    // :: error: (assignment)
+    bn = t3;
+    // :: error: (assignment)
+    fbn = t3;
+
+    us = t8;
+    // :: error: (assignment)
+    fqn = t8;
+    cgn = t8;
+    // :: error: (assignment)
+    fd = t8;
+    // :: error: (assignment)
+    iform = t8;
+    // :: error: (assignment)
+    sn = t8;
+    // :: error: (assignment)
+    bn = t8;
+    // :: error: (assignment)
+    fbn = t8;
+
+    us = t9;
+    // :: error: (assignment)
+    fqn = t9;
+    // :: error: (assignment)
+    cgn = t9;
+    fd = t9;
+    // :: error: (assignment)
+    iform = t9;
+    // :: error: (assignment)
+    sn = t9;
+    // :: error: (assignment)
+    bn = t9;
+    // :: error: (assignment)
+    fbn = t9;
+
+    us = t24;
+    // :: error: (assignment)
+    fqn = t24;
+    // :: error: (assignment)
+    cgn = t24;
+    // :: error: (assignment)
+    fd = t24;
+    iform = t24;
+    // :: error: (assignment)
+    sn = t24;
+    // :: error: (assignment)
+    bn = t24;
+    // :: error: (assignment)
+    fbn = t24;
+
+    us = t25;
+    // :: error: (assignment)
+    fqn = t25;
+    // :: error: (assignment)
+    cgn = t25;
+    // :: error: (assignment)
+    fd = t25;
+    // :: error: (assignment)
+    iform = t25; // rhs is pakkage/Outer$Inner[]
+    // :: error: (assignment)
+    sn = t25;
+    // :: error: (assignment)
+    bn = t25;
+    // :: error: (assignment)
+    fbn = t25;
+
+    us = t28;
+    // :: error: (assignment)
+    fqn = t28;
+    // :: error: (assignment)
+    cgn = t28;
+    // :: error: (assignment)
+    fd = t28;
+    iform = t28;
+    // :: error: (assignment)
+    sn = t28;
+    // :: error: (assignment)
+    bn = t28;
+    // :: error: (assignment)
+    fbn = t28;
+
+    us = t27;
+    // :: error: (assignment)
+    fqn = t27;
+    // :: error: (assignment)
+    cgn = t27;
+    fd = t27;
+    // :: error: (assignment)
+    iform = t27;
+    // :: error: (assignment)
+    sn = t27;
+    // :: error: (assignment)
+    bn = t27;
+    // :: error: (assignment)
+    fbn = t27;
+
+    us = t26;
+    fqn = t26;
+    cgn = t26;
+    // :: error: (assignment)
+    fd = t26;
+    // :: error: (assignment)
+    iform = t26;
+    // :: error: (assignment)
+    sn = t26;
+    bn = t26;
+    fbn = t26;
+
+    us = t32;
+    // :: error: (assignment)
+    fqn = t32;
+    // :: error: (assignment)
+    cgn = t32;
+    // :: error: (assignment)
+    fd = t32;
+    // :: error: (assignment)
+    iform = t32; // t32 is array
+    // :: error: (assignment)
+    sn = t32;
+    // :: error: (assignment)
+    bn = t32;
+    // :: error: (assignment)
+    fbn = t32;
+
+    us = t30;
+    fqn = t30;
+    // :: error: (assignment)
+    cgn = t30;
+    // :: error: (assignment)
+    fd = t30;
+    // :: error: (assignment)
+    iform = t30;
+    // :: error: (assignment)
+    sn = t30;
+    // :: error: (assignment)
+    bn = t30; // rhs is array
+
+    us = t31;
+    // :: error: (assignment)
+    fqn = t31;
+    cgn = t31;
+    // :: error: (assignment)
+    fd = t31;
+    // :: error: (assignment)
+    iform = t31;
+    // :: error: (assignment)
+    sn = t31;
+    // :: error: (assignment)
+    bn = t31;
+    // :: error: (assignment)
+    fbn = t31;
+
+    us = t34;
+    fqn = t34;
+    cgn = t34;
+    // :: error: (assignment)
+    fd = t34;
+    // :: error: (assignment)
+    iform = t34;
+    // :: error: (assignment)
+    sn = t34;
+    bn = t34;
+    fbn = t34;
+
+    us = t17;
+    fqn = t17;
+    cgn = t17;
+    // :: error: (assignment)
+    fd = t17;
+    // :: error: (assignment)
+    iform = t17;
+    // :: error: (assignment)
+    sn = t17;
+    bn = t17;
+    fbn = t17;
+
+    us = t18;
+    fqn = t18;
+    // :: error: (assignment)
+    cgn = t18;
+    // :: error: (assignment)
+    fd = t18;
+    // :: error: (assignment)
+    iform = t18;
+    // :: error: (assignment)
+    sn = t18;
+    // :: error: (assignment)
+    bn = t18; // t18 is pakkage.Outer.Inner[]
+
+    us = t19;
+    fqn = t19;
+    cgn = t19;
+    // :: error: (assignment)
+    fd = t19;
+    // :: error: (assignment)
+    iform = t19;
+    // :: error: (assignment)
+    sn = t19;
+    bn = t19;
+    fbn = t19;
+
+    us = t21;
+    fqn = t21;
+    // :: error: (assignment)
+    cgn = t21;
+    // :: error: (assignment)
+    fd = t21;
+    // :: error: (assignment)
+    iform = t21;
+    // :: error: (assignment)
+    sn = t21;
+    // :: error: (assignment)
+    bn = t21; // t21 is pakkage.Outer$Inner[]
+
+    us = t20;
+    // :: error: (assignment)
+    fqn = t20;
+    // :: error: (assignment)
+    cgn = t20;
+    // :: error: (assignment)
+    fd = t20;
+    // :: error: (assignment)
+    iform = t20;
+    // :: error: (assignment)
+    sn = t20;
+    // :: error: (assignment)
+    bn = t20;
+    // :: error: (assignment)
+    fbn = t20;
+
+    us = t10;
+    // :: error: (assignment)
+    fqn = t10;
+    cgn = t10;
+    // :: error: (assignment)
+    fd = t10;
+    // :: error: (assignment)
+    iform = t10;
+    // :: error: (assignment)
+    sn = t10;
+    // :: error: (assignment)
+    bn = t10;
+    // :: error: (assignment)
+    fbn = t10;
+
+    us = t4;
+    // :: error: (assignment)
+    fqn = t4;
+    // :: error: (assignment)
+    cgn = t4;
+    fd = t4;
+    // :: error: (assignment)
+    iform = t4;
+    // :: error: (assignment)
+    sn = t4;
+    // :: error: (assignment)
+    bn = t4;
+    // :: error: (assignment)
+    fbn = t4;
+
+    us = t11;
+    // :: error: (assignment)
+    fqn = t11;
+    // :: error: (assignment)
+    cgn = t11;
+    fd = t11;
+    // :: error: (assignment)
+    iform = t11;
+    // :: error: (assignment)
+    sn = t11;
+    // :: error: (assignment)
+    bn = t11;
+    // :: error: (assignment)
+    fbn = t11;
+  }
+}
diff --git a/checker/tests/signature/StubLibraryTest.java b/checker/tests/signature/StubLibraryTest.java
new file mode 100644
index 0000000..18a0716
--- /dev/null
+++ b/checker/tests/signature/StubLibraryTest.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.signature.qual.*;
+
+// Not on classpath when running the Checker Framework tests.
+// import org.apache.bcel.generic.ClassGen;
+
+public class StubLibraryTest {
+
+  void testJdk() {
+    @ClassGetName String s3 = String.class.getName();
+  }
+
+  //   void testBcel(ClassGen cg) {
+  //     @ClassGetName String cgn = cg.getClassName();
+  //     @BinaryName String bn = cg.getClassName();
+  //   }
+
+}
diff --git a/checker/tests/signedness-unchecked-defaults/TestUncheckedByteCode.java b/checker/tests/signedness-unchecked-defaults/TestUncheckedByteCode.java
new file mode 100644
index 0000000..3549937
--- /dev/null
+++ b/checker/tests/signedness-unchecked-defaults/TestUncheckedByteCode.java
@@ -0,0 +1,20 @@
+import org.checkerframework.framework.testchecker.lib.UncheckedByteCode;
+
+public class TestUncheckedByteCode {
+  Object field;
+
+  void test(UncheckedByteCode<Object> param, Integer i) {
+    field = param.getCT();
+    // :: error: (argument)
+    field = param.getInt(1);
+    // Signedness Checker doesn't default boxed primitives correctly.
+    // https://github.com/typetools/checker-framework/issues/797
+    // :: error: (argument)
+    field = param.getInteger(i);
+    // :: error: (argument)
+    field = param.getObject(new Object());
+    // :: error: (argument)
+    field = param.getString("hello");
+    field = param.identity("hello");
+  }
+}
diff --git a/checker/tests/signedness/AdditionWithChar.java b/checker/tests/signedness/AdditionWithChar.java
new file mode 100644
index 0000000..8066d42
--- /dev/null
+++ b/checker/tests/signedness/AdditionWithChar.java
@@ -0,0 +1,7 @@
+public class AdditionWithChar {
+  int i2;
+
+  void additionWithChar(int i1, char c) {
+    i2 = i1 + c;
+  }
+}
diff --git a/checker/tests/signedness/AnnoBeforeModifier.java b/checker/tests/signedness/AnnoBeforeModifier.java
new file mode 100644
index 0000000..2e4177f
--- /dev/null
+++ b/checker/tests/signedness/AnnoBeforeModifier.java
@@ -0,0 +1,100 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+public class AnnoBeforeModifier {
+
+  // :: warning: (type.anno.before.modifier)
+  @Unsigned public int i = 0;
+
+  public @Unsigned int j = 0;
+
+  // :: warning: (type.anno.before.modifier)
+  public @Unsigned final int k = 0;
+
+  @SuppressWarnings("foobar")
+  @Unsigned public int l = 0;
+
+  public @SuppressWarnings("foobar") @Unsigned int m = 0;
+
+  @SuppressWarnings("foobar")
+  @Unsigned public int n = 0;
+
+  // TODO: :: warning: (type.anno.before.modifier)
+  public @SuppressWarnings("foobar") @Unsigned final int o = 0;
+
+  // :: warning: (type.anno.before.decl.anno) :: warning: (type.anno.before.modifier)
+  public @Unsigned @SuppressWarnings("foobar") final int p = 0;
+
+  public @SuppressWarnings("foobar") final @Unsigned int q = 0;
+
+  @SuppressWarnings("foobar")
+  public int r = 0;
+
+  public @SuppressWarnings("foobar") int s = 0;
+
+  public @SuppressWarnings("foobar") final int t = 0;
+
+  // :: warning: (type.anno.before.modifier)
+  @Unsigned public int iMethod() {
+    return 0;
+  }
+
+  public @Unsigned int jMethod() {
+    return 0;
+  }
+
+  // :: warning: (type.anno.before.modifier)
+  public @Unsigned final int kMethod() {
+    return 0;
+  }
+
+  @SuppressWarnings("foobar")
+  @Unsigned public int lMethod() {
+    return 0;
+  }
+
+  public @SuppressWarnings("foobar") @Unsigned int mMethod() {
+    return 0;
+  }
+
+  @SuppressWarnings("foobar")
+  @Unsigned public int nMethod() {
+    return 0;
+  }
+
+  // TODO: :: warning: (type.anno.before.modifier)
+  public @SuppressWarnings("foobar") @Unsigned final int oMethod() {
+    return 0;
+  }
+
+  // :: warning: (type.anno.before.decl.anno) :: warning: (type.anno.before.modifier)
+  public @Unsigned @SuppressWarnings("foobar") final int pMethod() {
+    return 0;
+  }
+
+  public @SuppressWarnings("foobar") final @Unsigned int qMethod() {
+    return 0;
+  }
+
+  @SuppressWarnings("foobar")
+  public int rMethod() {
+    return 0;
+  }
+
+  public @SuppressWarnings("foobar") int sMethod() {
+    return 0;
+  }
+
+  public @SuppressWarnings("foobar") final int tMethod() {
+    return 0;
+  }
+
+  // Use @NonNull rather than a signedness annotation to avoid errors.
+  @NonNull public enum MyEnum {
+    @NonNull CONSTANT
+  }
+
+  interface MyInterface {
+    @NonNull String myMethod();
+  }
+}
diff --git a/checker/tests/signedness/Arrays.java b/checker/tests/signedness/Arrays.java
new file mode 100644
index 0000000..3d839d0
--- /dev/null
+++ b/checker/tests/signedness/Arrays.java
@@ -0,0 +1,5 @@
+public class Arrays {
+  void test() {
+    Object[] os = new Double[234];
+  }
+}
diff --git a/checker/tests/signedness/BinaryOperations.java b/checker/tests/signedness/BinaryOperations.java
new file mode 100644
index 0000000..06064a7
--- /dev/null
+++ b/checker/tests/signedness/BinaryOperations.java
@@ -0,0 +1,162 @@
+import org.checkerframework.checker.signedness.qual.*;
+
+public class BinaryOperations {
+
+  public void DivModTest(
+      @Unsigned int unsigned,
+      @PolySigned int polysigned,
+      @UnknownSignedness int unknown,
+      @SignednessGlb int constant) {
+
+    @Unsigned int unsignedresult;
+    @UnknownSignedness int unknownresult;
+
+    // :: error: (operation.unsignedrhs)
+    unknownresult = unknown / unsigned;
+
+    // :: error: (operation.unsignedlhs)
+    unknownresult = unsigned / unknown;
+
+    // :: error: (operation.unsignedlhs)
+    unsignedresult = unsigned / constant;
+
+    // :: error: (operation.unsignedrhs)
+    unsignedresult = constant / unsigned;
+
+    // :: error: (operation.unsignedrhs)
+    unknownresult = unknown / polysigned;
+
+    // :: error: (operation.unsignedlhs)
+    unknownresult = polysigned / unknown;
+
+    // :: error: (operation.unsignedlhs)
+    unknownresult = polysigned / constant;
+
+    // :: error: (operation.unsignedrhs)
+    unknownresult = constant / polysigned;
+
+    // :: error: (operation.unsignedrhs)
+    unknownresult = unknown % unsigned;
+
+    // :: error: (operation.unsignedlhs)
+    unknownresult = unsigned % unknown;
+
+    // :: error: (operation.unsignedrhs)
+    unknownresult = unknown % polysigned;
+
+    // :: error: (operation.unsignedlhs)
+    unknownresult = polysigned % unknown;
+
+    // :: error: (operation.unsignedlhs)
+    unsignedresult = unsigned % constant;
+
+    // :: error: (operation.unsignedrhs)
+    unsignedresult = constant % unsigned;
+
+    // :: error: (operation.unsignedlhs)
+    unknownresult = polysigned % constant;
+
+    // :: error: (operation.unsignedrhs)
+    unknownresult = constant % polysigned;
+  }
+
+  public void SignedRightShiftTest(
+      @Unsigned int unsigned,
+      @PolySigned int polysigned,
+      @UnknownSignedness int unknown,
+      @SignednessGlb int constant) {
+
+    @Unsigned int unsignedresult;
+    @PolySigned int polysignedresult;
+    @UnknownSignedness int unknownresult;
+    int result;
+
+    // :: error: (shift.signed)
+    unsignedresult = unsigned >> constant;
+
+    result = constant >> unsigned;
+
+    // :: error: (shift.signed)
+    polysignedresult = polysigned >> constant;
+
+    result = constant >> polysigned;
+
+    // :: error: (shift.signed)
+    unsignedresult = unsigned >> unknown;
+
+    unknownresult = unknown >> unsigned;
+
+    // :: error: (shift.signed)
+    polysignedresult = polysigned >> unknown;
+
+    unknownresult = unknown >> polysigned;
+  }
+
+  public void UnsignedRightShiftTest(
+      @Signed int signed,
+      @PolySigned int polysigned,
+      @UnknownSignedness int unknown,
+      @SignednessGlb int constant) {
+
+    @PolySigned int polysignedresult;
+    @UnknownSignedness int unknownresult;
+    int result;
+
+    // :: error: (shift.unsigned)
+    result = signed >>> constant;
+
+    result = constant >>> signed;
+
+    // :: error: (shift.unsigned)
+    result = signed >>> unknown;
+
+    unknownresult = unknown >>> signed;
+
+    // :: error: (shift.unsigned)
+    polysignedresult = polysigned >>> constant;
+
+    result = constant >>> polysigned;
+
+    // :: error: (shift.unsigned)
+    polysignedresult = polysigned >>> unknown;
+
+    unknownresult = unknown >>> polysigned;
+  }
+
+  public void LeftShiftTest(
+      @Signed int signed,
+      @Unsigned int unsigned,
+      @PolySigned int polysigned,
+      @UnknownSignedness int unknown,
+      @SignednessGlb int constant) {
+
+    @PolySigned int polysignedresult;
+    @UnknownSignedness int unknownresult;
+    @Unsigned int unsignedresult;
+    int result;
+
+    result = signed << constant;
+
+    result = constant << signed;
+
+    result = signed << unknown;
+
+    unknownresult = unknown << signed;
+
+    unsignedresult = unsigned << constant;
+
+    result = constant << unsigned;
+
+    unsignedresult = unsigned << unknown;
+
+    unknownresult = unknown << unsigned;
+
+    polysignedresult = polysigned << constant;
+
+    result = constant << polysigned;
+
+    polysignedresult = polysigned << unknown;
+
+    unknownresult = unknown << polysigned;
+  }
+}
diff --git a/checker/tests/signedness/BoxedPrimitives.java b/checker/tests/signedness/BoxedPrimitives.java
new file mode 100644
index 0000000..b3a899c
--- /dev/null
+++ b/checker/tests/signedness/BoxedPrimitives.java
@@ -0,0 +1,83 @@
+import java.util.LinkedList;
+import org.checkerframework.checker.signedness.qual.Signed;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+public class BoxedPrimitives {
+
+  @Signed int si;
+  @Unsigned int ui;
+
+  @Signed Integer sbi;
+  @Unsigned Integer ubi;
+
+  void argSigned(@Signed int x) {
+    si = x;
+    sbi = x;
+    // :: error: (assignment)
+    ui = x;
+    // :: error: (assignment)
+    ubi = x;
+  }
+
+  void argUnsigned(@Unsigned int x) {
+    // :: error: (assignment)
+    si = x;
+    // :: error: (assignment)
+    sbi = x;
+    ui = x;
+    ubi = x;
+  }
+
+  void argSignedBoxed(@Signed Integer x) {
+    si = x;
+    sbi = x;
+    // :: error: (assignment)
+    ui = x;
+    // :: error: (assignment)
+    ubi = x;
+  }
+
+  void argUnsignedBoxed(@Unsigned Integer x) {
+    // :: error: (assignment)
+    si = x;
+    // :: error: (assignment)
+    sbi = x;
+    ui = x;
+    ubi = x;
+  }
+
+  void client() {
+    argSigned(si);
+    argSignedBoxed(si);
+    argSigned(sbi);
+    argSignedBoxed(sbi);
+    // :: error: (argument)
+    argUnsigned(si);
+    // :: error: (argument)
+    argUnsignedBoxed(si);
+    // :: error: (argument)
+    argUnsigned(sbi);
+    // :: error: (argument)
+    argUnsignedBoxed(sbi);
+    // :: error: (argument)
+    argSigned(ui);
+    // :: error: (argument)
+    argSignedBoxed(ui);
+    // :: error: (argument)
+    argSigned(ubi);
+    // :: error: (argument)
+    argSignedBoxed(ubi);
+    argUnsigned(ui);
+    argUnsignedBoxed(ui);
+    argUnsigned(ubi);
+    argUnsignedBoxed(ubi);
+  }
+
+  public LinkedList<Integer> commands;
+
+  void forLoop() {
+    for (Integer ix : this.commands) {
+      argSigned(ix);
+    }
+  }
+}
diff --git a/checker/tests/signedness/Cast.java b/checker/tests/signedness/Cast.java
new file mode 100644
index 0000000..eb79cf6
--- /dev/null
+++ b/checker/tests/signedness/Cast.java
@@ -0,0 +1,23 @@
+import org.checkerframework.checker.signedness.qual.*;
+
+public class Cast {
+
+  static final Object object = 1;
+
+  void client() {
+    objectiveParameter(object);
+  }
+
+  void objectiveParameter(Object object) {
+    // :: error: (argument)
+    integralParameter((Integer) object);
+  }
+
+  // This passes when object is initialized within objectiveArgument().
+  void objectiveArgument() {
+    Object object = -3;
+    integralParameter((Integer) object);
+  }
+
+  void integralParameter(int x) {}
+}
diff --git a/checker/tests/signedness/CastedShifts.java b/checker/tests/signedness/CastedShifts.java
new file mode 100644
index 0000000..1f7af4f
--- /dev/null
+++ b/checker/tests/signedness/CastedShifts.java
@@ -0,0 +1,396 @@
+import org.checkerframework.checker.signedness.qual.*;
+
+public class CastedShifts {
+
+  public void CastedIntShifts(@Unsigned int unsigned, @Signed int signed) {
+    // Cast to byte.
+    @UnknownSignedness byte byteRes;
+
+    // Shifting right by 23, the introduced bits are cast away
+    byteRes = (@Unsigned byte) (unsigned >>> 23);
+    byteRes = (@Unsigned byte) (unsigned >> 23);
+    byteRes = (@Signed byte) (signed >>> 23);
+    byteRes = (@Signed byte) (signed >> 23);
+    byteRes = (byte) (signed >> 23);
+
+    // Shifting right by 24, the introduced bits are still cast away.
+    byteRes = (@Unsigned byte) (unsigned >>> 24);
+    byteRes = (@Unsigned byte) (unsigned >> 24);
+    byteRes = (@Signed byte) (signed >>> 24);
+    byteRes = (@Signed byte) (signed >> 24);
+
+    // Shifting right by 25, now the MSB matters.
+    byteRes = (@Unsigned byte) (unsigned >>> 25);
+
+    // :: error: (shift.signed)
+    byteRes = (@Unsigned byte) (unsigned >> 25);
+
+    // :: error: (shift.unsigned)
+    byteRes = (@Signed byte) (signed >>> 25);
+    byteRes = (@Signed byte) (signed >> 25);
+
+    // Shifting right by zero should behave as assignment
+    byteRes = (@Unsigned byte) (unsigned >>> 0);
+    byteRes = (@Unsigned byte) (unsigned >> 0);
+    byteRes = (@Signed byte) (signed >>> 0);
+    byteRes = (@Signed byte) (signed >> 0);
+
+    // Cast to char.
+    char charRes;
+
+    // Shifting right by 23, the introduced bits are cast away
+    charRes = (@Unsigned char) (unsigned >>> 23);
+    charRes = (@Unsigned char) (unsigned >> 23);
+
+    // Shifting right by 24, the introduced bits are still cast away.
+    charRes = (@Unsigned char) (unsigned >>> 24);
+    charRes = (@Unsigned char) (unsigned >> 24);
+
+    // Shifting right by 25, now the MSB matters.
+    charRes = (@Unsigned char) (unsigned >>> 25);
+
+    // :: error: (shift.signed)
+    charRes = (@Unsigned char) (unsigned >> 25);
+
+    // Shifting right by zero should behave as assignment
+    charRes = (@Unsigned char) (unsigned >>> 0);
+    charRes = (@Unsigned char) (unsigned >> 0);
+
+    // Cast to short.
+    @UnknownSignedness short shortRes;
+
+    // Shifting right by 15, the introduced bits are cast away
+    shortRes = (@Unsigned short) (unsigned >>> 15);
+    shortRes = (@Unsigned short) (unsigned >> 15);
+    shortRes = (@Signed short) (signed >>> 15);
+    shortRes = (@Signed short) (signed >> 15);
+
+    // Shifting right by 16, the introduced bits are still cast away.
+    shortRes = (@Unsigned short) (unsigned >>> 16);
+    shortRes = (@Unsigned short) (unsigned >> 16);
+    shortRes = (@Signed short) (signed >>> 16);
+    shortRes = (@Signed short) (signed >> 16);
+
+    // Shifting right by 17, now the MSB matters.
+    shortRes = (@Unsigned short) (unsigned >>> 17);
+
+    // :: error: (shift.signed)
+    shortRes = (@Unsigned short) (unsigned >> 17);
+
+    // :: error: (shift.unsigned)
+    shortRes = (@Signed short) (signed >>> 17);
+    shortRes = (@Signed short) (signed >> 17);
+
+    // Shifting right by zero should behave as assignment
+    shortRes = (@Unsigned short) (unsigned >>> 0);
+    shortRes = (@Unsigned short) (unsigned >> 0);
+    shortRes = (@Signed short) (signed >>> 0);
+    shortRes = (@Signed short) (signed >> 0);
+
+    // Cast to int.
+    @UnknownSignedness int intRes;
+
+    // Now shift signedness matters again
+    intRes = (@Unsigned int) (unsigned >>> 1);
+
+    // :: error: (shift.signed)
+    intRes = (@Unsigned int) (unsigned >> 1);
+
+    // :: error: (shift.unsigned)
+    intRes = (@Signed int) (signed >>> 1);
+    intRes = (@Signed int) (signed >> 1);
+
+    // Shifting right by zero should behave as assignment
+    intRes = (@Unsigned int) (unsigned >>> 0);
+    intRes = (@Unsigned int) (unsigned >> 0);
+    intRes = (@Signed int) (signed >>> 0);
+    intRes = (@Signed int) (signed >> 0);
+
+    // Cast to long.
+    @UnknownSignedness long longRes;
+
+    // Now shift signedness matters again
+    longRes = (@Unsigned long) (unsigned >>> 1);
+
+    // :: error: (shift.signed)
+    longRes = (@Unsigned long) (unsigned >> 1);
+
+    // :: error: (shift.unsigned)
+    longRes = (@Signed long) (signed >>> 1);
+    longRes = (@Signed long) (signed >> 1);
+
+    // Shifting right by zero should behave as assignment
+    longRes = (@Unsigned long) (unsigned >>> 0);
+    longRes = (@Unsigned long) (unsigned >> 0);
+    longRes = (@Signed long) (signed >>> 0);
+    longRes = (@Signed long) (signed >> 0);
+
+    // Tests with double parenthesis (only byte and int)
+
+    // Cast to byte.
+    // Shifting right by 23, the introduced bits are cast away
+    byteRes = (@Unsigned byte) ((unsigned >>> 23));
+    byteRes = (@Unsigned byte) ((unsigned >> 23));
+    byteRes = (@Signed byte) ((signed >>> 23));
+    byteRes = (@Signed byte) ((signed >> 23));
+
+    // Shifting right by 24, the introduced bits are still cast away.
+    byteRes = (@Unsigned byte) ((unsigned >>> 24));
+    byteRes = (@Unsigned byte) ((unsigned >> 24));
+    byteRes = (@Signed byte) ((signed >>> 24));
+    byteRes = (@Signed byte) ((signed >> 24));
+
+    // Shifting right by 25, now the MSB matters.
+    byteRes = (@Unsigned byte) ((unsigned >>> 25));
+
+    // :: error: (shift.signed)
+    byteRes = (@Unsigned byte) ((unsigned >> 25));
+
+    // :: error: (shift.unsigned)
+    byteRes = (@Signed byte) ((signed >>> 25));
+    byteRes = (@Signed byte) ((signed >> 25));
+
+    // Shifting right by zero should behave as assignment
+    byteRes = (@Unsigned byte) ((unsigned >>> 0));
+    byteRes = (@Unsigned byte) ((unsigned >> 0));
+    byteRes = (@Signed byte) ((signed >>> 0));
+    byteRes = (@Signed byte) ((signed >> 0));
+
+    // Cast to int.
+    // Now shift signedness matters again
+    intRes = (@Unsigned int) ((unsigned >>> 1));
+
+    // :: error: (shift.signed)
+    intRes = (@Unsigned int) ((unsigned >> 1));
+
+    // :: error: (shift.unsigned)
+    intRes = (@Signed int) ((signed >>> 1));
+    intRes = (@Signed int) ((signed >> 1));
+
+    // Shifting right by zero should behave as assignment
+    intRes = (@Unsigned int) ((unsigned >>> 0));
+    intRes = (@Unsigned int) ((unsigned >> 0));
+    intRes = (@Signed int) ((signed >>> 0));
+    intRes = (@Signed int) ((signed >> 0));
+
+    // Test outside Java Specification shift ranges
+    // Cast to int.
+    // Now shift signedness matters again
+    intRes = (@Unsigned int) ((unsigned >>> 33));
+
+    // :: error: (shift.signed)
+    intRes = (@Unsigned int) ((unsigned >> 33));
+
+    // :: error: (shift.unsigned)
+    intRes = (@Signed int) ((signed >>> 33));
+    intRes = (@Signed int) ((signed >> 33));
+
+    // Shifting right by zero should behave as assignment
+    intRes = (@Unsigned int) ((unsigned >>> 32));
+    intRes = (@Unsigned int) ((unsigned >> 32));
+    intRes = (@Signed int) ((signed >>> 32));
+    intRes = (@Signed int) ((signed >> 32));
+  }
+
+  public void CastedLongShifts(@Unsigned long unsigned, @Signed long signed) {
+    // Cast to byte.
+    @UnknownSignedness byte byteRes;
+
+    // Shifting right by 55, the introduced bits are cast away
+    byteRes = (@Unsigned byte) (unsigned >>> 55);
+    byteRes = (@Unsigned byte) (unsigned >> 55);
+    byteRes = (@Signed byte) (signed >>> 55);
+    byteRes = (@Signed byte) (signed >> 55);
+
+    // Shifting right by 56, the introduced bits are still cast away.
+    byteRes = (@Unsigned byte) (unsigned >>> 56);
+    byteRes = (@Unsigned byte) (unsigned >> 56);
+    byteRes = (@Signed byte) (signed >>> 56);
+    byteRes = (@Signed byte) (signed >> 56);
+
+    // Shifting right by 57, now the MSB matters.
+    byteRes = (@Unsigned byte) (unsigned >>> 57);
+
+    // :: error: (shift.signed)
+    byteRes = (@Unsigned byte) (unsigned >> 57);
+
+    // :: error: (shift.unsigned)
+    byteRes = (@Signed byte) (signed >>> 57);
+    byteRes = (@Signed byte) (signed >> 57);
+
+    // Shifting right by zero should behave as assignment
+    byteRes = (@Unsigned byte) (unsigned >>> 0);
+    byteRes = (@Unsigned byte) (unsigned >> 0);
+    byteRes = (@Signed byte) (signed >>> 0);
+    byteRes = (@Signed byte) (signed >> 0);
+
+    // Cast to char.
+    char charRes;
+
+    // Shifting right by 55, the introduced bits are cast away
+    charRes = (@Unsigned char) (unsigned >>> 55);
+    charRes = (@Unsigned char) (unsigned >> 55);
+
+    // Shifting right by 56, the introduced bits are still cast away.
+    charRes = (@Unsigned char) (unsigned >>> 56);
+    charRes = (@Unsigned char) (unsigned >> 56);
+
+    // Shifting right by 57, now the MSB matters.
+    charRes = (@Unsigned char) (unsigned >>> 57);
+
+    // :: error: (shift.signed)
+    charRes = (@Unsigned char) (unsigned >> 57);
+
+    // Shifting right by zero should behave as assignment
+    charRes = (@Unsigned char) (unsigned >>> 0);
+    charRes = (@Unsigned char) (unsigned >> 0);
+
+    // Cast to short.
+    @UnknownSignedness short shortRes;
+
+    // Shifting right by 47, the introduced bits are cast away
+    shortRes = (@Unsigned short) (unsigned >>> 47);
+    shortRes = (@Unsigned short) (unsigned >> 47);
+    shortRes = (@Signed short) (signed >>> 47);
+    shortRes = (@Signed short) (signed >> 47);
+
+    // Shifting right by 48, the introduced bits are still cast away.
+    shortRes = (@Unsigned short) (unsigned >>> 48);
+    shortRes = (@Unsigned short) (unsigned >> 48);
+    shortRes = (@Signed short) (signed >>> 48);
+    shortRes = (@Signed short) (signed >> 48);
+
+    // Shifting right by 49, now the MSB matters.
+    shortRes = (@Unsigned short) (unsigned >>> 49);
+
+    // :: error: (shift.signed)
+    shortRes = (@Unsigned short) (unsigned >> 49);
+
+    // :: error: (shift.unsigned)
+    shortRes = (@Signed short) (signed >>> 49);
+    shortRes = (@Signed short) (signed >> 49);
+
+    // Shifting right by zero should behave as assignment
+    shortRes = (@Unsigned short) (unsigned >>> 0);
+    shortRes = (@Unsigned short) (unsigned >> 0);
+    shortRes = (@Signed short) (signed >>> 0);
+    shortRes = (@Signed short) (signed >> 0);
+
+    // Cast to int.
+    @UnknownSignedness int intRes;
+
+    // Shifting right by 31, the introduced bits are cast away
+    intRes = (@Unsigned int) (unsigned >>> 31);
+    intRes = (@Unsigned int) (unsigned >> 31);
+    intRes = (@Signed int) (signed >>> 31);
+    intRes = (@Signed int) (signed >> 31);
+
+    // Shifting right by 32, the introduced bits are still cast away.
+    intRes = (@Unsigned int) (unsigned >>> 32);
+    intRes = (@Unsigned int) (unsigned >> 32);
+    intRes = (@Signed int) (signed >>> 32);
+    intRes = (@Signed int) (signed >> 32);
+
+    // Shifting right by 33, now the MSB matters.
+    intRes = (@Unsigned int) (unsigned >>> 33);
+
+    // :: error: (shift.signed)
+    intRes = (@Unsigned int) (unsigned >> 33);
+
+    // :: error: (shift.unsigned)
+    intRes = (@Signed int) (signed >>> 33);
+    intRes = (@Signed int) (signed >> 33);
+
+    // Shifting right by zero should behave as assignment
+    intRes = (@Unsigned int) (unsigned >>> 0);
+    intRes = (@Unsigned int) (unsigned >> 0);
+    intRes = (@Signed int) (signed >>> 0);
+    intRes = (@Signed int) (signed >> 0);
+
+    // Cast to long.
+    @UnknownSignedness long longRes;
+
+    // Now shift signedness matters again
+    longRes = (@Unsigned long) (unsigned >>> 1);
+
+    // :: error: (shift.signed)
+    longRes = (@Unsigned long) (unsigned >> 1);
+
+    // :: error: (shift.unsigned)
+    longRes = (@Signed long) (signed >>> 1);
+    longRes = (@Signed long) (signed >> 1);
+
+    // Shifting right by zero should behave as assignment
+    longRes = (@Unsigned long) (unsigned >>> 0);
+    longRes = (@Unsigned long) (unsigned >> 0);
+    longRes = (@Signed long) (signed >>> 0);
+    longRes = (@Signed long) (signed >> 0);
+
+    // Tests with double parenthesis (only byte and long)
+
+    // Cast to byte.
+    // Shifting right by 55, the introduced bits are cast away
+    byteRes = (@Unsigned byte) ((unsigned >>> 55));
+    byteRes = (@Unsigned byte) ((unsigned >> 55));
+    byteRes = (@Signed byte) ((signed >>> 55));
+    byteRes = (@Signed byte) ((signed >> 55));
+
+    // Shifting right by 56, the introduced bits are still cast away.
+    byteRes = (@Unsigned byte) ((unsigned >>> 56));
+    byteRes = (@Unsigned byte) ((unsigned >> 56));
+    byteRes = (@Signed byte) ((signed >>> 56));
+    byteRes = (@Signed byte) ((signed >> 56));
+
+    // Shifting right by 9, now the MSB matters.
+    byteRes = (@Unsigned byte) ((unsigned >>> 57));
+
+    // :: error: (shift.signed)
+    byteRes = (@Unsigned byte) ((unsigned >> 57));
+
+    // :: error: (shift.unsigned)
+    byteRes = (@Signed byte) ((signed >>> 57));
+    byteRes = (@Signed byte) ((signed >> 57));
+
+    // Shifting right by zero should behave as assignment
+    byteRes = (@Unsigned byte) ((unsigned >>> 0));
+    byteRes = (@Unsigned byte) ((unsigned >> 0));
+    byteRes = (@Signed byte) ((signed >>> 0));
+    byteRes = (@Signed byte) ((signed >> 0));
+
+    // Cast to long.
+    // Now shift signedness matters again
+    longRes = (@Unsigned long) ((unsigned >>> 1));
+
+    // :: error: (shift.signed)
+    longRes = (@Unsigned long) ((unsigned >> 1));
+
+    // :: error: (shift.unsigned)
+    longRes = (@Signed long) ((signed >>> 1));
+    longRes = (@Signed long) ((signed >> 1));
+
+    // Shifting right by zero should behave as assignment
+    longRes = (@Unsigned long) ((unsigned >>> 0));
+    longRes = (@Unsigned long) ((unsigned >> 0));
+    longRes = (@Signed long) ((signed >>> 0));
+    longRes = (@Signed long) ((signed >> 0));
+
+    // Test outside Java Specification shift ranges
+    // Cast to long.
+    // Now shift signedness matters again
+    longRes = (@Unsigned long) ((unsigned >>> 65));
+
+    // :: error: (shift.signed)
+    longRes = (@Unsigned long) ((unsigned >> 65));
+
+    // :: error: (shift.unsigned)
+    longRes = (@Signed long) ((signed >>> 65));
+    longRes = (@Signed long) ((signed >> 65));
+
+    // Shifting right by zero should behave as assignment
+    longRes = (@Unsigned long) ((unsigned >>> 64));
+    longRes = (@Unsigned long) ((unsigned >> 64));
+    longRes = (@Signed long) ((signed >>> 64));
+    longRes = (@Signed long) ((signed >> 64));
+    longRes = (long) ((signed >> 64));
+  }
+}
diff --git a/checker/tests/signedness/CharCast.java b/checker/tests/signedness/CharCast.java
new file mode 100644
index 0000000..5d17303
--- /dev/null
+++ b/checker/tests/signedness/CharCast.java
@@ -0,0 +1,29 @@
+import org.checkerframework.checker.signedness.qual.SignedPositive;
+
+public class CharCast {
+
+  void m(@SignedPositive int i) {
+    char c = (char) i;
+  }
+
+  void m1(short s) {
+    int x = s;
+    char c = (char) x;
+  }
+
+  void m2(int i) {
+    int x = (short) i;
+    char c = (char) x;
+  }
+
+  void m3() {
+    int x = (short) 1;
+    char c = (char) x;
+  }
+
+  void m4() {
+    short x = 1;
+    int y = x;
+    char c = (char) y;
+  }
+}
diff --git a/checker/tests/signedness/CharCastedToInt.java b/checker/tests/signedness/CharCastedToInt.java
new file mode 100644
index 0000000..25f99f1
--- /dev/null
+++ b/checker/tests/signedness/CharCastedToInt.java
@@ -0,0 +1,8 @@
+public class CharCastedToInt {
+  int charCastToInt(char c) {
+    intParameter((int) c);
+    return (int) c;
+  }
+
+  void intParameter(int x) {}
+}
diff --git a/checker/tests/signedness/CharComparisons.java b/checker/tests/signedness/CharComparisons.java
new file mode 100644
index 0000000..90df906
--- /dev/null
+++ b/checker/tests/signedness/CharComparisons.java
@@ -0,0 +1,22 @@
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+public class CharComparisons {
+  char c;
+  @Unsigned byte b;
+
+  void unsignedComparison(char c, @Unsigned byte b) {
+    boolean res = c > b;
+    res = c >= b;
+    res = c < b;
+    res = c <= b;
+    res = c == b;
+  }
+
+  void unsignedComparisonFields() {
+    boolean res = this.c > this.b;
+    res = this.c >= this.b;
+    res = this.c < this.b;
+    res = this.c <= this.b;
+    res = this.c == this.b;
+  }
+}
diff --git a/checker/tests/signedness/CharToFloat.java b/checker/tests/signedness/CharToFloat.java
new file mode 100644
index 0000000..6a2a6b3
--- /dev/null
+++ b/checker/tests/signedness/CharToFloat.java
@@ -0,0 +1,17 @@
+// Test case for issue #3711: https://github.com/typetools/checker-framework/issues/3711
+
+public class CharToFloat {
+  void castCharacter(Object o) {
+    floatParameter((Character) o);
+    doubleParameter((Character) o);
+  }
+
+  void passCharacter(Character c) {
+    floatParameter(c);
+    doubleParameter(c);
+  }
+
+  void floatParameter(float f) {}
+
+  void doubleParameter(double d) {}
+}
diff --git a/checker/tests/signedness/CompareChars.java b/checker/tests/signedness/CompareChars.java
new file mode 100644
index 0000000..35c5fde
--- /dev/null
+++ b/checker/tests/signedness/CompareChars.java
@@ -0,0 +1,12 @@
+// Test case for issue 3668:
+// https://github.com/typetools/checker-framework/issues/3669
+
+public class CompareChars {
+  void compareUnsignedChars(char c2) {
+    char c1 = 'a';
+    boolean res = c1 > c2;
+    res = c1 >= c2;
+    res = c1 < c2;
+    res = c1 <= c2;
+  }
+}
diff --git a/checker/tests/signedness/Comparisons.java b/checker/tests/signedness/Comparisons.java
new file mode 100644
index 0000000..680c003
--- /dev/null
+++ b/checker/tests/signedness/Comparisons.java
@@ -0,0 +1,75 @@
+import org.checkerframework.checker.signedness.qual.*;
+
+public class Comparisons {
+
+  public void ComparisonTest(
+      @Unsigned int unsigned, @PolySigned int polysigned, @UnknownSignedness int unknown) {
+
+    boolean testRes;
+
+    // :: error: (comparison.unsignedlhs)
+    testRes = unsigned < unknown;
+
+    // :: error: (comparison.unsignedlhs)
+    testRes = polysigned < unknown;
+
+    // :: error: (comparison.unsignedrhs)
+    testRes = unknown < unsigned;
+
+    // :: error: (comparison.unsignedrhs)
+    testRes = unknown < polysigned;
+
+    // :: error: (comparison.unsignedlhs)
+    testRes = unsigned <= unknown;
+
+    // :: error: (comparison.unsignedlhs)
+    testRes = polysigned <= unknown;
+
+    // :: error: (comparison.unsignedrhs)
+    testRes = unknown <= unsigned;
+
+    // :: error: (comparison.unsignedrhs)
+    testRes = unknown <= polysigned;
+
+    // :: error: (comparison.unsignedlhs)
+    testRes = unsigned > unknown;
+
+    // :: error: (comparison.unsignedlhs)
+    testRes = polysigned > unknown;
+
+    // :: error: (comparison.unsignedrhs)
+    testRes = unknown > unsigned;
+
+    // :: error: (comparison.unsignedrhs)
+    testRes = unknown > polysigned;
+
+    // :: error: (comparison.unsignedlhs)
+    testRes = unsigned >= unknown;
+
+    // :: error: (comparison.unsignedrhs)
+    testRes = unknown >= unsigned;
+
+    // :: error: (comparison.unsignedlhs)
+    testRes = polysigned >= unknown;
+
+    // :: error: (comparison.unsignedrhs)
+    testRes = unknown >= polysigned;
+  }
+
+  public void EqualsTest(@Unsigned int unsigned, @Signed int signed) {
+
+    boolean testRes;
+
+    // :: error: (comparison.mixed.unsignedlhs)
+    testRes = unsigned == signed;
+
+    // :: error: (comparison.mixed.unsignedrhs)
+    testRes = signed == unsigned;
+
+    // :: error: (comparison.mixed.unsignedlhs)
+    testRes = unsigned != signed;
+
+    // :: error: (comparison.mixed.unsignedrhs)
+    testRes = signed != unsigned;
+  }
+}
diff --git a/checker/tests/signedness/CompoundAssignmentsSignedness.java b/checker/tests/signedness/CompoundAssignmentsSignedness.java
new file mode 100644
index 0000000..8ff92c2
--- /dev/null
+++ b/checker/tests/signedness/CompoundAssignmentsSignedness.java
@@ -0,0 +1,166 @@
+import org.checkerframework.checker.signedness.qual.*;
+
+public class CompoundAssignmentsSignedness {
+
+  public void DivModTest(
+      @Unsigned int unsigned,
+      @PolySigned int polysigned,
+      @UnknownSignedness int unknown,
+      @SignednessGlb int constant) {
+
+    // :: error: (compound.assignment.unsigned.expression)
+    unknown /= unsigned;
+
+    // :: error: (compound.assignment.unsigned.variable)
+    // :: error: (compound.assignment)
+    unsigned /= unknown;
+
+    // :: error: (compound.assignment.unsigned.variable)
+    unsigned /= constant;
+
+    // :: error: (compound.assignment.unsigned.expression)
+    // :: error: (compound.assignment)
+    constant /= unsigned;
+
+    // :: error: (compound.assignment.unsigned.expression)
+    unknown /= polysigned;
+
+    // :: error: (compound.assignment.unsigned.variable)
+    // :: error: (compound.assignment)
+    polysigned /= unknown;
+
+    // :: error: (compound.assignment.unsigned.variable)
+    // :: error: (compound.assignment)
+    polysigned /= constant;
+
+    // :: error: (compound.assignment.unsigned.expression)
+    // :: error: (compound.assignment)
+    constant /= polysigned;
+
+    // :: error: (compound.assignment.unsigned.expression)
+    unknown %= unsigned;
+
+    // :: error: (compound.assignment.unsigned.variable)
+    // :: error: (compound.assignment)
+    unsigned %= unknown;
+
+    // :: error: (compound.assignment.unsigned.expression)
+    unknown %= polysigned;
+
+    // :: error: (compound.assignment.unsigned.variable)
+    // :: error: (compound.assignment)
+    polysigned %= unknown;
+
+    // :: error: (compound.assignment.unsigned.variable)
+    unsigned %= constant;
+
+    // :: error: (compound.assignment.unsigned.expression)
+    // :: error: (compound.assignment)
+    constant %= unsigned;
+
+    // :: error: (compound.assignment.unsigned.variable)
+    // :: error: (compound.assignment)
+    polysigned %= constant;
+
+    // :: error: (compound.assignment.unsigned.expression)
+    // :: error: (compound.assignment)
+    constant %= polysigned;
+  }
+
+  public void SignedRightShiftTest(
+      @Unsigned int unsigned,
+      @PolySigned int polysigned,
+      @UnknownSignedness int unknown,
+      @SignednessGlb int constant) {
+
+    // :: error: (compound.assignment.shift.signed)
+    unsigned >>= constant;
+
+    constant >>= unsigned;
+
+    // :: error: (compound.assignment.shift.signed)
+    polysigned >>= constant;
+
+    constant >>= polysigned;
+
+    // :: error: (compound.assignment.shift.signed)
+    unsigned >>= unknown;
+
+    unknown >>= unsigned;
+
+    // :: error: (compound.assignment.shift.signed)
+    polysigned >>= unknown;
+
+    unknown >>= polysigned;
+  }
+
+  public void UnsignedRightShiftTest(
+      @Signed int signed,
+      @PolySigned int polysigned,
+      @UnknownSignedness int unknown,
+      @SignednessGlb int constant) {
+
+    // :: error: (compound.assignment.shift.unsigned)
+    signed >>>= constant;
+
+    constant >>>= signed;
+
+    // :: error: (compound.assignment.shift.unsigned)
+    signed >>>= unknown;
+
+    unknown >>>= signed;
+
+    // :: error: (compound.assignment.shift.unsigned)
+    polysigned >>>= constant;
+
+    constant >>>= polysigned;
+
+    // :: error: (compound.assignment.shift.unsigned)
+    polysigned >>>= unknown;
+
+    unknown >>>= polysigned;
+  }
+
+  public void LeftShiftTest(
+      @Signed int signed,
+      @Unsigned int unsigned,
+      @PolySigned int polysigned,
+      @UnknownSignedness int unknown,
+      @SignednessGlb int constant) {
+
+    signed <<= constant;
+
+    constant <<= signed;
+
+    signed <<= unknown;
+
+    unknown <<= signed;
+
+    unsigned <<= constant;
+
+    constant <<= unsigned;
+
+    unsigned <<= unknown;
+
+    unknown <<= unsigned;
+
+    polysigned <<= constant;
+
+    constant <<= polysigned;
+
+    polysigned <<= unknown;
+
+    unknown <<= polysigned;
+  }
+
+  public void mixedTest(@Unsigned int unsigned, @Signed int signed) {
+
+    // :: error: (compound.assignment.mixed.unsigned.variable)
+    // :: error: (compound.assignment)
+    unsigned += signed;
+
+    // :: error: (compound.assignment.mixed.unsigned.expression)
+    // :: error: (compound.assignment)
+    signed += unsigned;
+  }
+}
diff --git a/checker/tests/signedness/CompoundAssignmentsSignedness2.java b/checker/tests/signedness/CompoundAssignmentsSignedness2.java
new file mode 100644
index 0000000..cb49f0a
--- /dev/null
+++ b/checker/tests/signedness/CompoundAssignmentsSignedness2.java
@@ -0,0 +1,63 @@
+// Test case for issue #3709: https://github.com/typetools/checker-framework/issues/3709
+
+public class CompoundAssignmentsSignedness2 {
+  void additionWithCompoundAssignment(char c, int i1) {
+    i1 += c;
+  }
+
+  void additionWithoutCompoundAssignment1(char c, int i1) {
+    i1 = (int) (i1 + c);
+  }
+
+  void additionWithoutCompoundAssignment2(char c, int i1) {
+    i1 = i1 + c;
+  }
+
+  void subtractionWithCompoundAssignment(char c, int i1) {
+    i1 -= c;
+  }
+
+  void subtractionWithoutCompoundAssignment1(char c, int i1) {
+    i1 = (int) (i1 - c);
+  }
+
+  void subtractionWithoutCompoundAssignment2(char c, int i1) {
+    i1 = i1 - c;
+  }
+
+  void multiplicationWithCompoundAssignment(char c, int i1) {
+    i1 *= c;
+  }
+
+  void multiplicationWithoutCompoundAssignment1(char c, int i1) {
+    i1 = (int) (i1 * c);
+  }
+
+  void multiplicationWithoutCompoundAssignment2(char c, int i1) {
+    i1 = i1 * c;
+  }
+
+  void divisionWithCompoundAssignment(char c, int i1) {
+    i1 /= c;
+  }
+
+  void divisionWithoutCompoundAssignment1(char c, int i1) {
+    i1 = (int) (i1 / c);
+  }
+
+  void divisionWithoutCompoundAssignment2(char c, int i1) {
+    i1 = i1 / c;
+  }
+
+  void modulusWithCompoundAssignment(char c, int i1) {
+    i1 %= c;
+  }
+
+  void modulusWithoutCompoundAssignment1(char c, int i1) {
+    i1 = (int) (i1 % c);
+  }
+
+  void modulusWithoutCompoundAssignment2(char c, int i1) {
+    i1 = i1 % c;
+  }
+}
diff --git a/checker/tests/signedness/ConstantTests.java b/checker/tests/signedness/ConstantTests.java
new file mode 100644
index 0000000..55b50a1
--- /dev/null
+++ b/checker/tests/signedness/ConstantTests.java
@@ -0,0 +1,33 @@
+// Tests of constants
+
+import org.checkerframework.checker.signedness.qual.UnknownSignedness;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+public class ConstantTests {
+
+  @Unsigned int uint_negative_one = (@Unsigned int) -1;
+
+  @Unsigned int u1lit = 0xFFFFFFFE; // unsigned: 2^32 - 2, signed: -2
+
+  void m() {
+
+    int s = -2 / -1;
+
+    @Unsigned int u = -1 / -2;
+
+    int a = -1;
+    int b = -2;
+    int c = a / b;
+
+    @UnknownSignedness int x = 0xFFFFFFFE / 2;
+
+    int s1 = 0xFFFFFFFE;
+    @UnknownSignedness int y = s1 / 2;
+
+    // :: error: (operation.unsignedlhs)
+    @UnknownSignedness int z = (uint_negative_one) / -2;
+
+    // :: error: (operation.unsignedlhs)
+    @UnknownSignedness int w = u1lit / 2;
+  }
+}
diff --git a/checker/tests/signedness/DefaultsSignedness.java b/checker/tests/signedness/DefaultsSignedness.java
new file mode 100644
index 0000000..c16c1da
--- /dev/null
+++ b/checker/tests/signedness/DefaultsSignedness.java
@@ -0,0 +1,187 @@
+import org.checkerframework.checker.signedness.qual.*;
+
+public class DefaultsSignedness {
+
+  public void ConstantTest() {
+
+    // Test bytes with literal values
+    @SignednessGlb byte conByte;
+    @SignednessBottom byte botByte;
+
+    byte testByte = 0;
+
+    conByte = testByte;
+
+    // :: error: (assignment)
+    botByte = testByte;
+
+    // Test shorts with literal values
+    @SignednessGlb short conShort;
+    @SignednessBottom short botShort;
+
+    short testShort = 128;
+
+    conShort = testShort;
+
+    // :: error: (assignment)
+    botShort = testShort;
+
+    // Test ints with literal values
+    @SignednessGlb int conInt;
+    @SignednessBottom int botInt;
+
+    int testInt = 32768;
+
+    conInt = testInt;
+
+    // :: error: (assignment)
+    botInt = testInt;
+
+    // Test longs with literal values
+    @SignednessGlb long conLong;
+    @SignednessBottom long botLong;
+
+    long testLong = 2147483648L;
+
+    conLong = testLong;
+
+    // :: error: (assignment)
+    botLong = testLong;
+
+    // Test chars with literal values
+    @SignednessGlb char conChar;
+    @SignednessBottom char botChar;
+
+    char testChar = 'a';
+
+    conChar = testChar;
+
+    // :: error: (assignment)
+    botChar = testChar;
+  }
+
+  public void SignedTest(
+      byte testByte,
+      short testShort,
+      int testInt,
+      long testLong,
+      float testFloat,
+      double testDouble,
+      char testChar,
+      boolean testBool,
+      Byte testBoxedByte,
+      Short testBoxedShort,
+      Integer testBoxedInteger,
+      Long testBoxedLong) {
+
+    // Test bytes
+    @Signed byte sinByte;
+    @SignednessGlb byte conByte;
+
+    sinByte = testByte;
+
+    // :: error: (assignment)
+    conByte = testByte;
+
+    // Test shorts
+    @Signed short sinShort;
+    @SignednessGlb short conShort;
+
+    sinShort = testShort;
+
+    // :: error: (assignment)
+    conShort = testShort;
+
+    // Test ints
+    @Signed int sinInt;
+    @SignednessGlb int conInt;
+
+    sinInt = testInt;
+
+    // :: error: (assignment)
+    conInt = testInt;
+
+    // Test longs
+    @Signed long sinLong;
+    @SignednessGlb long conLong;
+
+    sinLong = testLong;
+
+    // :: error: (assignment)
+    conLong = testLong;
+
+    // Test floats
+    // :: error: (anno.on.irrelevant)
+    @Signed float sinFloat;
+
+    sinFloat = testFloat;
+
+    // Test doubles
+    // :: error: (anno.on.irrelevant)
+    @Signed double sinDouble;
+
+    sinDouble = testDouble;
+
+    /*
+    // Test boxed bytes
+    @Signed Byte sinBoxedByte;
+    @SignednessGlb Byte conBoxedByte;
+
+    sinBoxedByte = testBoxedByte;
+
+    //// :: error: (assignment)
+    conBoxedByte = testBoxedByte;
+
+    // Test boxed shorts
+    @Signed Short sinBoxedShort;
+    @SignednessGlb Short conBoxedShort;
+
+    sinBoxedShort = testBoxedShort;
+
+    //// :: error: (assignment)
+    conBoxedShort = testBoxedShort;
+
+    // Test boxed Integers
+    @Signed Integer sinBoxedInteger;
+    @SignednessGlb Integer conBoxedInteger;
+
+    sinBoxedInteger = testBoxedInteger;
+
+    //// :: error: (assignment)
+    conBoxedInteger = testBoxedInteger;
+
+    // Test boxed Longs
+    @Signed Long sinBoxedLong;
+    @SignednessGlb Long conBoxedLong;
+
+    sinBoxedLong = testBoxedLong;
+
+    //// :: error: (assignment)
+    conBoxedLong = testBoxedLong;
+    */
+  }
+
+  public void SignednessBottom() {
+
+    @SignednessBottom Object botObj;
+
+    Object testObj = null;
+
+    botObj = testObj;
+  }
+
+  public void UnknownSignedness(Object testObj, @Unsigned int unsigned, @Signed int signed) {
+
+    @UnknownSignedness Object unkObj;
+    @Signed Object sinObj;
+
+    unkObj = testObj;
+
+    // :: error: (assignment)
+    sinObj = testObj;
+  }
+
+  public void booleanProblem(@Unsigned int unsigned, @Signed int signed) {
+    boolean testBool = unsigned == 1 || signed > 1;
+  }
+}
diff --git a/checker/tests/signedness/Issue2482.java b/checker/tests/signedness/Issue2482.java
new file mode 100644
index 0000000..d2bdc72
--- /dev/null
+++ b/checker/tests/signedness/Issue2482.java
@@ -0,0 +1,68 @@
+public class Issue2482 {
+
+  void regularAssignment(byte[] b, int c) {
+    int a = b.length;
+    a = a + c;
+  }
+
+  void compoundAssignment(byte[] b, int c) {
+    int a = b.length;
+    a += c;
+  }
+
+  void stringLenAdd(String s, int a) {
+    int len = s.length();
+    len += a;
+  }
+
+  void stringLenSub(String s, int a) {
+    int len = s.length();
+    len -= a;
+  }
+
+  void stringLenDiv(String s, int a) {
+    int len = s.length();
+    len /= a;
+  }
+
+  void stringLenMul(String s, int a) {
+    int len = s.length();
+    len *= a;
+  }
+
+  void arrayLenAdd(byte[] b, int a) {
+    int len = b.length;
+    len += a;
+  }
+
+  void arrayLenSub(byte[] b, int a) {
+    int len = b.length;
+    len -= a;
+  }
+
+  void arrayLenDiv(byte[] b, int a) {
+    int len = b.length;
+    len /= a;
+  }
+
+  void arrayLenMul(byte[] b, int a) {
+    int len = b.length;
+    len *= a;
+  }
+
+  void m3(int a) {
+
+    int len = -1; // Negative
+    int len2 = 1; // Positive
+
+    len += a;
+    len -= a;
+    len /= a;
+    len *= a;
+
+    len2 += a;
+    len2 -= a;
+    len2 /= a;
+    len2 *= a;
+  }
+}
diff --git a/checker/tests/signedness/Issue2483.java b/checker/tests/signedness/Issue2483.java
new file mode 100644
index 0000000..916ea70
--- /dev/null
+++ b/checker/tests/signedness/Issue2483.java
@@ -0,0 +1,8 @@
+import org.checkerframework.checker.signedness.qual.*;
+
+public class Issue2483 {
+  void foo(String a, byte[] b) {
+    @Unsigned int len = a.length();
+    @Unsigned int len2 = b.length;
+  }
+}
diff --git a/checker/tests/signedness/Issue2534.java b/checker/tests/signedness/Issue2534.java
new file mode 100644
index 0000000..9f1de83
--- /dev/null
+++ b/checker/tests/signedness/Issue2534.java
@@ -0,0 +1,27 @@
+import org.checkerframework.checker.signedness.qual.Unsigned;
+import org.checkerframework.common.value.qual.IntRange;
+
+public class Issue2534 {
+
+  @IntRange(from = 0, to = Integer.MAX_VALUE) int field = 3;
+
+  @IntRange(from = 0, to = Integer.MAX_VALUE) int qwe() {
+    return 3;
+  }
+
+  void m1() {
+    @Unsigned int c = qwe();
+  }
+
+  void m2() {
+    @Unsigned int c = field;
+  }
+
+  void m3() {
+    @Unsigned int c = this.field;
+  }
+
+  void m4(@IntRange(from = 0, to = Integer.MAX_VALUE) int array[]) {
+    @Unsigned int c = array[0];
+  }
+}
diff --git a/checker/tests/signedness/Issue2543.java b/checker/tests/signedness/Issue2543.java
new file mode 100644
index 0000000..a6c25d8
--- /dev/null
+++ b/checker/tests/signedness/Issue2543.java
@@ -0,0 +1,44 @@
+import org.checkerframework.checker.signedness.qual.PolySigned;
+import org.checkerframework.checker.signedness.qual.Signed;
+import org.checkerframework.checker.signedness.qual.UnknownSignedness;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+public class Issue2543 {
+
+  public static @PolySigned int rotateRightPart1(@PolySigned int i, int distance) {
+    // :: error: (shift.unsigned)
+    return i >>> distance;
+  }
+
+  public static @PolySigned int rotateRightPart2(@PolySigned int i, int distance) {
+    return i << -distance;
+  }
+
+  public static @PolySigned int rotateRight(@PolySigned int i, int distance) {
+    // :: error: (shift.unsigned)
+    return (i >>> distance) | (i << -distance);
+  }
+
+  public static @Signed int rotateRightSignedPart1(@Signed int i, int distance) {
+    // :: error: (shift.unsigned)
+    return i >>> distance;
+  }
+
+  public static @Signed int rotateRightSignedPart2(@Signed int i, int distance) {
+    return i << -distance;
+  }
+
+  public static @Signed int rotateRightSigned(@Signed int i, int distance) {
+    // :: error: (shift.unsigned)
+    return (i >>> distance) | (i << -distance);
+  }
+
+  public static @Unsigned int rotateRightUnsigned(@Unsigned int i, int distance) {
+    return (i >>> distance) | (i << -distance);
+  }
+
+  public static @Unsigned int rotateRightUnknownSignedness(@UnknownSignedness int i, int distance) {
+    // :: error: (return)
+    return (i >>> distance) | (i << -distance);
+  }
+}
diff --git a/checker/tests/signedness/Issue3710.java b/checker/tests/signedness/Issue3710.java
new file mode 100644
index 0000000..4293121
--- /dev/null
+++ b/checker/tests/signedness/Issue3710.java
@@ -0,0 +1,21 @@
+// Test case for issue #3711: https://github.com/typetools/checker-framework/issues/3710
+
+public class Issue3710 {
+  int returnIntWithLocalVariable(char c) {
+    int i = c;
+    return i;
+  }
+
+  long returnLongWithLocalVariable(char c) {
+    long l = c;
+    return l;
+  }
+
+  int returnIntWithoutLocalVariable(char c) {
+    return c;
+  }
+
+  long returnLongWithoutLocalVariable(char c) {
+    return c;
+  }
+}
diff --git a/checker/tests/signedness/LocalVarDefaults.java b/checker/tests/signedness/LocalVarDefaults.java
new file mode 100644
index 0000000..8eca120
--- /dev/null
+++ b/checker/tests/signedness/LocalVarDefaults.java
@@ -0,0 +1,29 @@
+import org.checkerframework.checker.signedness.qual.Signed;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+public class LocalVarDefaults {
+
+  void methodInt(@Unsigned int unsignedInt, @Signed int signedInt) {
+    int local = unsignedInt;
+    int local2 = signedInt;
+  }
+
+  // :: error: (annotations.on.use) :: error:  (anno.on.irrelevant)
+  void methodDouble(@Unsigned double unsigned, @Signed double signed) {
+    // :: error: (assignment)
+    double local = unsigned;
+    double local2 = signed;
+  }
+
+  void methodInteger(@Unsigned Integer unsignedInt, @Signed Integer signedInt) {
+    Integer local = unsignedInt;
+    Integer local2 = signedInt;
+  }
+
+  // :: error: (annotations.on.use) :: error:  (anno.on.irrelevant)
+  void methodDoubleWrapper(@Unsigned Double unsigned, @Signed Double signed) {
+    // :: error: (assignment)
+    Double local = unsigned;
+    Double local2 = signed;
+  }
+}
diff --git a/checker/tests/signedness/LowerUpperBound.java b/checker/tests/signedness/LowerUpperBound.java
new file mode 100644
index 0000000..0f28eb7
--- /dev/null
+++ b/checker/tests/signedness/LowerUpperBound.java
@@ -0,0 +1,37 @@
+import org.checkerframework.checker.signedness.qual.*;
+
+public class LowerUpperBound {
+
+  public void LowerUpperBoundTest(
+      @UnknownSignedness int unknown,
+      @Unsigned int unsigned,
+      @Signed int signed,
+      @SignednessGlb int constant) {
+
+    @UnknownSignedness int unkTest;
+    @Unsigned int unsTest;
+    @Signed int sinTest;
+    @SignednessGlb int conTest;
+    @SignednessBottom int botTest;
+
+    unkTest = unknown + unknown;
+
+    // :: error: (assignment)
+    sinTest = unknown + unknown;
+
+    unkTest = unknown + signed;
+
+    // :: error: (assignment)
+    sinTest = unknown + signed;
+
+    sinTest = signed + signed;
+
+    // :: error: (assignment)
+    conTest = signed + signed;
+
+    sinTest = signed + constant;
+
+    // :: error: (assignment)
+    conTest = signed + constant;
+  }
+}
diff --git a/checker/tests/signedness/MaskedShifts.java b/checker/tests/signedness/MaskedShifts.java
new file mode 100644
index 0000000..adcc9d9
--- /dev/null
+++ b/checker/tests/signedness/MaskedShifts.java
@@ -0,0 +1,363 @@
+import org.checkerframework.checker.signedness.qual.*;
+
+public class MaskedShifts {
+
+  public void MaskedAndShifts(@Unsigned int unsigned, @Signed int signed) {
+
+    @UnknownSignedness int testRes;
+
+    // Use mask that renders the 9 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are masked away
+    testRes = (unsigned >>> 8) & 0x7FFFFF;
+    testRes = (unsigned >> 8) & 0x7FFFFF;
+    testRes = (signed >>> 8) & 0x7FFFFF;
+    testRes = (signed >> 8) & 0x7FFFFF;
+
+    // Use mask that renders the 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = (unsigned >>> 8) & 0xFFFFFF;
+    testRes = (unsigned >> 8) & 0xFFFFFF;
+    testRes = (signed >>> 8) & 0xFFFFFF;
+    testRes = (signed >> 8) & 0xFFFFFF;
+
+    // Use mask that renders the 7 MSB_s irrelevant
+
+    // Now the right-most introduced bit matters
+    testRes = (unsigned >>> 8) & 0x1FFFFFF;
+
+    // :: error: (shift.signed)
+    testRes = (unsigned >> 8) & 0x1FFFFFF;
+
+    // :: error: (shift.unsigned)
+    testRes = (signed >>> 8) & 0x1FFFFFF;
+    testRes = (signed >> 8) & 0x1FFFFFF;
+
+    // Use mask that doesn't render the MSB irrelevent, but does render the next 7 MSB_s irrelevant.
+
+    // Now the left-most introduced bit matters
+    testRes = (unsigned >>> 8) & 0x90FFFFFF;
+
+    // :: error: (shift.signed)
+    testRes = (unsigned >> 8) & 0x90FFFFFF;
+
+    // :: error: (shift.unsigned)
+    testRes = (signed >>> 8) & 0x90FFFFFF;
+    testRes = (signed >> 8) & 0x90FFFFFF;
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = (unsigned >>> 8) & 0xFFFFFFFF;
+
+    // :: error: (shift.signed)
+    testRes = (unsigned >> 8) & 0xFFFFFFFF;
+
+    // :: error: (shift.unsigned)
+    testRes = (signed >>> 8) & 0xFFFFFFFF;
+    testRes = (signed >> 8) & 0xFFFFFFFF;
+
+    // Tests with no parenthesis (only 8 and 0 MSB_s)
+
+    // Use mask that renders the 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = unsigned >>> 8 & 0xFFFFFF;
+    testRes = unsigned >> 8 & 0xFFFFFF;
+    testRes = signed >>> 8 & 0xFFFFFF;
+    testRes = signed >> 8 & 0xFFFFFF;
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = unsigned >>> 8 & 0xFFFFFFFF;
+
+    // :: error: (shift.signed)
+    testRes = unsigned >> 8 & 0xFFFFFFFF;
+
+    // :: error: (shift.unsigned)
+    testRes = signed >>> 8 & 0xFFFFFFFF;
+    testRes = signed >> 8 & 0xFFFFFFFF;
+
+    // Tests with double parenthesis (only 8 and 0 MSB_s)
+
+    // Use mask that renders the 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = ((unsigned >>> 8)) & 0xFFFFFF;
+    testRes = ((unsigned >> 8)) & 0xFFFFFF;
+    testRes = ((signed >>> 8)) & 0xFFFFFF;
+    testRes = ((signed >> 8)) & 0xFFFFFF;
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = ((unsigned >>> 8)) & 0xFFFFFFFF;
+
+    // :: error: (shift.signed)
+    testRes = ((unsigned >> 8)) & 0xFFFFFFFF;
+
+    // :: error: (shift.unsigned)
+    testRes = ((signed >>> 8)) & 0xFFFFFFFF;
+    testRes = ((signed >> 8)) & 0xFFFFFFFF;
+
+    // Tests shift on right (only 8 and 0 MSB_s)
+
+    // Use mask that renders the 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = 0xFFFFFF & (unsigned >>> 8);
+    testRes = 0xFFFFFF & (unsigned >> 8);
+    testRes = 0xFFFFFF & (signed >>> 8);
+    testRes = 0xFFFFFF & (signed >> 8);
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = 0xFFFFFFFF & (unsigned >>> 8);
+
+    // :: error: (shift.signed)
+    testRes = 0xFFFFFFFF & (unsigned >> 8);
+
+    // :: error: (shift.unsigned)
+    testRes = 0xFFFFFFFF & (signed >>> 8);
+    testRes = 0xFFFFFFFF & (signed >> 8);
+
+    // Tests shift on right (only 8 and 0 MSB_s), with no parenthesis on right
+
+    // Use mask that renders the 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = 0xFFFFFF & unsigned >>> 8;
+    testRes = 0xFFFFFF & unsigned >> 8;
+    testRes = 0xFFFFFF & signed >>> 8;
+    testRes = 0xFFFFFF & signed >> 8;
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = 0xFFFFFFFF & unsigned >>> 8;
+
+    // :: error: (shift.signed)
+    testRes = 0xFFFFFFFF & unsigned >> 8;
+
+    // :: error: (shift.unsigned)
+    testRes = 0xFFFFFFFF & signed >>> 8;
+    testRes = 0xFFFFFFFF & signed >> 8;
+
+    // Tests with parenthesis on mask (only 8 and 0 MSB_s)
+
+    // Use mask that renders the 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = unsigned >>> 8 & (0xFFFFFF);
+    testRes = unsigned >> 8 & (0xFFFFFF);
+    testRes = signed >>> 8 & (0xFFFFFF);
+    testRes = signed >> 8 & (0xFFFFFF);
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = unsigned >>> 8 & (0xFFFFFFFF);
+
+    // :: error: (shift.signed)
+    testRes = unsigned >> 8 & (0xFFFFFFFF);
+
+    // :: error: (shift.unsigned)
+    testRes = signed >>> 8 & (0xFFFFFFFF);
+    testRes = signed >> 8 & (0xFFFFFFFF);
+
+    // Tests with double parenthesis on mask (only 8 and 0 MSB_s)
+
+    // Use mask that renders the 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = unsigned >>> 8 & ((0xFFFFFF));
+    testRes = unsigned >> 8 & ((0xFFFFFF));
+    testRes = signed >>> 8 & ((0xFFFFFF));
+    testRes = signed >> 8 & ((0xFFFFFF));
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = unsigned >>> 8 & ((0xFFFFFFFF));
+
+    // :: error: (shift.signed)
+    testRes = unsigned >> 8 & ((0xFFFFFFFF));
+
+    // :: error: (shift.unsigned)
+    testRes = signed >>> 8 & ((0xFFFFFFFF));
+    testRes = signed >> 8 & ((0xFFFFFFFF));
+  }
+
+  public void MaskedOrShifts(@Unsigned int unsigned, @Signed int signed) {
+
+    @UnknownSignedness int testRes;
+
+    // Use mask that renders the 9 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are masked away.
+    testRes = (unsigned >>> 8) | 0xFF800000;
+    testRes = (unsigned >> 8) | 0xFF800000;
+    testRes = (signed >>> 8) | 0xFF800000;
+    testRes = (signed >> 8) | 0xFF800000;
+
+    // Use mask that render ths 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = (unsigned >>> 8) | 0xFF000000;
+    testRes = (unsigned >> 8) | 0xFF000000;
+    testRes = (signed >>> 8) | 0xFF000000;
+    testRes = (signed >> 8) | 0xFF000000;
+
+    // Use mask that renders the 7 MSB_s irrelevant.
+
+    // The right-most introduced bit now matters.
+    testRes = (unsigned >>> 8) | 0xFE000000;
+
+    // :: error: (shift.signed)
+    testRes = (unsigned >> 8) | 0xFE000000;
+
+    // :: error: (shift.unsigned)
+    testRes = (signed >>> 8) | 0xFE000000;
+    testRes = (signed >> 8) | 0xFE000000;
+
+    // Use mask that doesn't render the MSB irrelevent, but does render the next 7 MSB_s irrelevant.
+
+    // Now the left-most introduced bit matters
+    testRes = (unsigned >>> 8) | 0x8F000000;
+
+    // :: error: (shift.signed)
+    testRes = (unsigned >> 8) | 0x8F000000;
+
+    // :: error: (shift.unsigned)
+    testRes = (signed >>> 8) | 0x8F000000;
+    testRes = (signed >> 8) | 0x8F000000;
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = (unsigned >>> 8) | 0x0;
+
+    // :: error: (shift.signed)
+    testRes = (unsigned >> 8) | 0x0;
+
+    // :: error: (shift.unsigned)
+    testRes = (signed >>> 8) | 0x0;
+    testRes = (signed >> 8) | 0x0;
+
+    // Tests with no parenthesis (only 8 and 0 MSB_s)
+
+    // Use mask that renders the 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = unsigned >>> 8 | 0xFF000000;
+    testRes = unsigned >> 8 | 0xFF000000;
+    testRes = signed >>> 8 | 0xFF000000;
+    testRes = signed >> 8 | 0xFF000000;
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = unsigned >>> 8 | 0x0;
+
+    // :: error: (shift.signed)
+    testRes = unsigned >> 8 | 0x0;
+
+    // :: error: (shift.unsigned)
+    testRes = signed >>> 8 | 0x0;
+    testRes = signed >> 8 | 0x0;
+
+    // Tests with double parenthesis (only 8 and 0 MSB_s)
+
+    // Use mask that renders the 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = ((unsigned >>> 8)) | 0xFF000000;
+    testRes = ((unsigned >> 8)) | 0xFF000000;
+    testRes = ((signed >>> 8)) | 0xFF000000;
+    testRes = ((signed >> 8)) | 0xFF000000;
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = ((unsigned >>> 8)) | 0x0;
+
+    // :: error: (shift.signed)
+    testRes = ((unsigned >> 8)) | 0x0;
+
+    // :: error: (shift.unsigned)
+    testRes = ((signed >>> 8)) | 0x0;
+    testRes = ((signed >> 8)) | 0x0;
+
+    // Tests shift on right (only 8 and 0 MSB_s)
+
+    // Use mask that renders the 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = 0xFF000000 | (unsigned >>> 8);
+    testRes = 0xFF000000 | (unsigned >> 8);
+    testRes = 0xFF000000 | (signed >>> 8);
+    testRes = 0xFF000000 | (signed >> 8);
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = 0x0 | (unsigned >>> 8);
+
+    // :: error: (shift.signed)
+    testRes = 0x0 | (unsigned >> 8);
+
+    // :: error: (shift.unsigned)
+    testRes = 0x0 | (signed >>> 8);
+    testRes = 0x0 | (signed >> 8);
+
+    // Tests with parenthesis on mask (only 8 and 0 MSB_s)
+
+    // Use mask that renders the 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = unsigned >>> 8 | (0xFF000000);
+    testRes = unsigned >> 8 | (0xFF000000);
+    testRes = signed >>> 8 | (0xFF000000);
+    testRes = signed >> 8 | (0xFF000000);
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = unsigned >>> 8 | (0x0);
+
+    // :: error: (shift.signed)
+    testRes = unsigned >> 8 | (0x0);
+
+    // :: error: (shift.unsigned)
+    testRes = signed >>> 8 | (0x0);
+    testRes = signed >> 8 | (0x0);
+
+    // Tests with double parenthesis on mask (only 8 and 0 MSB_s)
+
+    // Use mask that renders the 8 MSB_s irrelevant.
+
+    // Shifting right by 8, the introduced bits are still masked away.
+    testRes = unsigned >>> 8 | ((0xFF000000));
+    testRes = unsigned >> 8 | ((0xFF000000));
+    testRes = signed >>> 8 | ((0xFF000000));
+    testRes = signed >> 8 | ((0xFF000000));
+
+    // Use mask that doesn't render any bits irrelevent
+
+    testRes = unsigned >>> 8 | ((0x0));
+
+    // :: error: (shift.signed)
+    testRes = unsigned >> 8 | ((0x0));
+
+    // :: error: (shift.unsigned)
+    testRes = signed >>> 8 | ((0x0));
+    testRes = signed >> 8 | ((0x0));
+  }
+
+  public void ZeroShiftTests(@Unsigned int unsigned, @Signed int signed) {
+    @UnknownSignedness int testRes;
+
+    // Tests shift by zero followed by "and" mask
+    testRes = (unsigned >>> 0) & 0xFFFFFFFF;
+    testRes = (unsigned >> 0) & 0xFFFFFFFF;
+    testRes = (signed >>> 0) & 0xFFFFFFFF;
+    testRes = (signed >> 0) & 0xFFFFFFFF;
+
+    // Tests shift by zero followed by "or" mask
+    testRes = (unsigned >>> 0) | 0x0;
+    testRes = (unsigned >> 0) | 0x0;
+    testRes = (signed >>> 0) | 0x0;
+    testRes = (signed >> 0) | 0x0;
+  }
+}
diff --git a/checker/tests/signedness/ObjectCasts.java b/checker/tests/signedness/ObjectCasts.java
new file mode 100644
index 0000000..be39f22
--- /dev/null
+++ b/checker/tests/signedness/ObjectCasts.java
@@ -0,0 +1,112 @@
+// Test case for issue 3668:
+// https://github.com/typetools/checker-framework/issues/3668
+
+import org.checkerframework.checker.signedness.qual.*;
+
+public class ObjectCasts {
+
+  Integer castObjectToInteger1(Object o) {
+    // :: error: (return)
+    return (Integer) o;
+  }
+
+  Integer castObjectToInteger2(@Unsigned Object o) {
+    // :: error: (return)
+    return (Integer) o;
+  }
+
+  Integer castObjectToInteger3(@Signed Object o) {
+    return (Integer) o;
+  }
+
+  @Signed Integer castObjectToInteger4(Object o) {
+    // :: error: (return)
+    return (Integer) o;
+  }
+
+  @Signed Integer castObjectToInteger5(@Unsigned Object o) {
+    // :: error: (return)
+    return (Integer) o;
+  }
+
+  @Signed Integer castObjectToInteger6(@Signed Object o) {
+    return (Integer) o;
+  }
+
+  @Unsigned Integer castObjectToInteger7(Object o) {
+    // :: error: (return)
+    return (Integer) o;
+  }
+
+  @Unsigned Integer castObjectToInteger8(@Unsigned Object o) {
+    return (Integer) o;
+  }
+
+  @Unsigned Integer castObjectToInteger9(@Signed Object o) {
+    // :: error: (return)
+    return (Integer) o;
+  }
+
+  Object castIntegerToObject1(Integer o) {
+    return (Object) o;
+  }
+
+  Object castIntegerToObject2(@Unsigned Integer o) {
+    return (Object) o;
+  }
+
+  Object castIntegerToObject3(@Signed Integer o) {
+    return (Object) o;
+  }
+
+  @Signed Object castIntegerToObject4(Integer o) {
+    return (Object) o;
+  }
+
+  @Signed Object castIntegerToObject5(@Unsigned Integer o) {
+    // :: error: (return)
+    return (Object) o;
+  }
+
+  @Signed Object castIntegerToObject6(@Signed Integer o) {
+    return (Object) o;
+  }
+
+  @Unsigned Object castIntegerToObject7(Integer o) {
+    // :: error: (return)
+    return (Object) o;
+  }
+
+  @Unsigned Object castIntegerToObject8(@Unsigned Integer o) {
+    return (Object) o;
+  }
+
+  @Unsigned Object castIntegerToObject9(@Signed Integer o) {
+    // :: error: (return)
+    return (Object) o;
+  }
+
+  void castObjectToBoxedVariants() {
+    byte b1 = 1;
+    short s1 = 1;
+    int i1 = 1;
+    long l1 = 1;
+    Object[] obj = new Object[] {b1, s1, i1, l1};
+    // :: error: (argument)
+    byteParameter((Byte) obj[0]);
+    // :: error: (argument)
+    shortParameter((Short) obj[1]);
+    // :: error: (argument)
+    integralParameter((Integer) obj[2]);
+    // :: error: (argument)
+    longParameter((Long) obj[3]);
+  }
+
+  void byteParameter(byte b) {}
+
+  void shortParameter(short s) {}
+
+  void integralParameter(int i) {}
+
+  void longParameter(long l) {}
+}
diff --git a/checker/tests/signedness/Operations.java b/checker/tests/signedness/Operations.java
new file mode 100644
index 0000000..8dd068d
--- /dev/null
+++ b/checker/tests/signedness/Operations.java
@@ -0,0 +1,78 @@
+import org.checkerframework.checker.signedness.qual.*;
+
+public class Operations {
+
+  public void DivModTest(@Unsigned int unsigned) {
+
+    @UnknownSignedness int testRes;
+
+    // :: error: (operation.unsignedlhs)
+    testRes = unsigned / 1;
+
+    // :: error: (operation.unsignedrhs)
+    testRes = 1 / unsigned;
+
+    // :: error: (operation.unsignedlhs)
+    testRes = unsigned % 1;
+
+    // :: error: (operation.unsignedrhs)
+    testRes = 1 % unsigned;
+  }
+
+  public void SignedRightShiftTest(@Unsigned int unsigned) {
+
+    @UnknownSignedness int testRes;
+
+    // :: error: (shift.signed)
+    testRes = unsigned >> 1;
+  }
+
+  public void UnsignedRightShiftTest(@Signed int signed) {
+
+    @UnknownSignedness int testRes;
+
+    // :: error: (shift.unsigned)
+    testRes = signed >>> 1;
+  }
+
+  public void BinaryOperationTest(@Unsigned int unsigned, @Signed int signed) {
+
+    @UnknownSignedness int testRes;
+
+    // :: error: (operation.mixed.unsignedlhs)
+    testRes = unsigned * signed;
+
+    // :: error: (operation.mixed.unsignedrhs)
+    testRes = signed * unsigned;
+
+    // :: error: (operation.mixed.unsignedlhs)
+    testRes = unsigned + signed;
+
+    // :: error: (operation.mixed.unsignedrhs)
+    testRes = signed + unsigned;
+
+    // :: error: (operation.mixed.unsignedlhs)
+    testRes = unsigned - signed;
+
+    // :: error: (operation.mixed.unsignedrhs)
+    testRes = signed - unsigned;
+
+    // :: error: (operation.mixed.unsignedlhs)
+    testRes = unsigned & signed;
+
+    // :: error: (operation.mixed.unsignedrhs)
+    testRes = signed & unsigned;
+
+    // :: error: (operation.mixed.unsignedlhs)
+    testRes = unsigned ^ signed;
+
+    // :: error: (operation.mixed.unsignedrhs)
+    testRes = signed ^ unsigned;
+
+    // :: error: (operation.mixed.unsignedlhs)
+    testRes = unsigned | signed;
+
+    // :: error: (operation.mixed.unsignedrhs)
+    testRes = signed | unsigned;
+  }
+}
diff --git a/checker/tests/signedness/PolymorphicReturnType.java b/checker/tests/signedness/PolymorphicReturnType.java
new file mode 100644
index 0000000..58cff45
--- /dev/null
+++ b/checker/tests/signedness/PolymorphicReturnType.java
@@ -0,0 +1,12 @@
+// Test case for Issue #1209
+// https://github.com/typetools/checker-framework/issues/1209
+
+import org.checkerframework.checker.signedness.qual.PolySigned;
+
+public class PolymorphicReturnType {
+
+  public @PolySigned byte get() {
+    // :: error: (return)
+    return 0;
+  }
+}
diff --git a/checker/tests/signedness/PrimitiveCasts.java b/checker/tests/signedness/PrimitiveCasts.java
new file mode 100644
index 0000000..2f8394b
--- /dev/null
+++ b/checker/tests/signedness/PrimitiveCasts.java
@@ -0,0 +1,41 @@
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+public class PrimitiveCasts {
+
+  void shortToChar1(short s) {
+    // :: warning: (cast.unsafe)
+    char c = (char) s;
+  }
+
+  // These are Java errors.
+  // void shortToChar2(short s) {
+  //     char c = s;
+  // }
+  // char shortToChar3(short s) {
+  //     return s;
+  // }
+
+  void intToDouble1(@Unsigned int ui) {
+    double d = (double) ui;
+  }
+
+  void intToDouble2(@Unsigned int ui) {
+    double d = ui;
+  }
+
+  double intToDouble3(@Unsigned int ui) {
+    return ui;
+  }
+
+  void shortToDouble1(@Unsigned short ui) {
+    double d = (double) ui;
+  }
+
+  void shortToDouble2(@Unsigned short ui) {
+    double d = ui;
+  }
+
+  double shortToDouble3(@Unsigned short ui) {
+    return ui;
+  }
+}
diff --git a/checker/tests/signedness/ShiftAndMask.java b/checker/tests/signedness/ShiftAndMask.java
new file mode 100644
index 0000000..95a7e10
--- /dev/null
+++ b/checker/tests/signedness/ShiftAndMask.java
@@ -0,0 +1,7 @@
+public class ShiftAndMask {
+
+  void m(long longValue) {
+    byte b1 = (byte) ((longValue >>> 32) & 0xFF);
+    byte b2 = (byte) ((longValue >>> 40) & 0xFF);
+  }
+}
diff --git a/checker/tests/signedness/ShiftPropogation.java b/checker/tests/signedness/ShiftPropogation.java
new file mode 100644
index 0000000..f84ea5a
--- /dev/null
+++ b/checker/tests/signedness/ShiftPropogation.java
@@ -0,0 +1,30 @@
+import org.checkerframework.checker.signedness.qual.*;
+
+public class ShiftPropogation {
+
+  public void ShiftOperationTests(@Unsigned int unsigned, @Signed int signed) {
+    @Unsigned int uur = unsigned >>> unsigned;
+    @Unsigned int usr = unsigned >>> signed;
+
+    @Signed int sur = signed >> unsigned;
+    @Signed int ssr = signed >> signed;
+
+    @Unsigned int uul = unsigned << unsigned;
+    @Unsigned int usl = unsigned << signed;
+    @Signed int sul = signed << unsigned;
+    @Signed int ssl = signed << signed;
+  }
+
+  public void ShiftAssignmentTests(@Unsigned int unsigned, @Signed int signed) {
+    @Unsigned int uur = unsigned >>>= unsigned;
+    @Unsigned int usr = unsigned >>>= signed;
+
+    @Signed int sur = signed >>= unsigned;
+    @Signed int ssr = signed >>= signed;
+
+    @Unsigned int uul = unsigned <<= unsigned;
+    @Unsigned int usl = unsigned <<= signed;
+    @Signed int sul = signed <<= unsigned;
+    @Signed int ssl = signed <<= signed;
+  }
+}
diff --git a/checker/tests/signedness/SignednessAssignments.java b/checker/tests/signedness/SignednessAssignments.java
new file mode 100644
index 0000000..5bfcd83
--- /dev/null
+++ b/checker/tests/signedness/SignednessAssignments.java
@@ -0,0 +1,121 @@
+import org.checkerframework.checker.signedness.qual.Signed;
+import org.checkerframework.checker.signedness.qual.SignedPositive;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+public class SignednessAssignments {
+
+  @Signed byte sb;
+  @Unsigned byte ub;
+  @Signed Byte sB;
+  @Unsigned Byte uB;
+
+  @Unsigned char uc;
+  @Unsigned Character uC;
+
+  @Signed short ss;
+  @Unsigned short us;
+  @Signed Short sS;
+  @Unsigned Short uS;
+
+  @Signed int si;
+  @Unsigned int ui;
+  @Signed Integer sI;
+  @Unsigned Integer uI;
+
+  @Signed long sl;
+  @Unsigned long ul;
+  @Signed Long sL;
+  @Unsigned Long uL;
+
+  void assignmentsByte() {
+    @Signed byte i1 = sb;
+    @Unsigned byte i2 = ub;
+    @Signed byte i3 = sB;
+    @Unsigned byte i4 = uB;
+
+    @Signed Byte i91 = sb;
+    @Unsigned Byte i92 = ub;
+    @Signed Byte i93 = sB;
+    @Unsigned Byte i94 = uB;
+  }
+
+  void assignmentsShort() {
+    @SignedPositive short i1 = sb;
+    @SignedPositive short i2 = ub;
+    @SignedPositive short i3 = sB;
+    @SignedPositive short i4 = uB;
+
+    @Signed short i9 = ss;
+    @Unsigned short i10 = us;
+    @Signed short i11 = sS;
+    @Unsigned short i12 = uS;
+
+    @Signed Short i91 = ss;
+    @Unsigned Short i92 = us;
+    @Signed Short i93 = sS;
+    @Unsigned Short i94 = uS;
+  }
+
+  void assignmentsChar() {
+    // These are commented out because they are Java errors.
+    // @Unsigned char i2 = ub;
+    // @Unsigned char i4 = uB;
+    // @Unsigned char i10 = us;
+    // @Unsigned char i12 = uS;
+  }
+
+  void assignmentsInt() {
+    @SignedPositive int i1 = sb;
+    @SignedPositive int i2 = ub;
+    @SignedPositive int i3 = sB;
+    @SignedPositive int i4 = uB;
+
+    @SignedPositive int i6 = uc;
+    @SignedPositive int i8 = uC;
+
+    @SignedPositive int i9 = ss;
+    @SignedPositive int i10 = us;
+    @SignedPositive int i11 = sS;
+    @SignedPositive int i12 = uS;
+
+    @Signed int i13 = si;
+    @Unsigned int i14 = ui;
+    @Signed int i15 = sI;
+    @Unsigned int i16 = uI;
+
+    @Signed Integer i91 = si;
+    @Unsigned Integer i92 = ui;
+    @Signed Integer i93 = sI;
+    @Unsigned Integer i94 = uI;
+  }
+
+  void assignmentsLong() {
+    @SignedPositive long i1 = sb;
+    @SignedPositive long i2 = ub;
+    @SignedPositive long i3 = sB;
+    @SignedPositive long i4 = uB;
+
+    @SignedPositive long i6 = uc;
+    @SignedPositive long i8 = uC;
+
+    @SignedPositive long i9 = ss;
+    @SignedPositive long i10 = us;
+    @SignedPositive long i11 = sS;
+    @SignedPositive long i12 = uS;
+
+    @SignedPositive long i13 = si;
+    @SignedPositive long i14 = ui;
+    @SignedPositive long i15 = sI;
+    @SignedPositive long i16 = uI;
+
+    @Signed long i17 = sl;
+    @Unsigned long i18 = ul;
+    @Signed long i19 = sL;
+    @Unsigned long i20 = uL;
+
+    @Signed Long i91 = sl;
+    @Unsigned Long i92 = ul;
+    @Signed Long i93 = sL;
+    @Unsigned Long i94 = uL;
+  }
+}
diff --git a/checker/tests/signedness/SignednessManualExample.java b/checker/tests/signedness/SignednessManualExample.java
new file mode 100644
index 0000000..6f45522
--- /dev/null
+++ b/checker/tests/signedness/SignednessManualExample.java
@@ -0,0 +1,51 @@
+// Example code from the user manual
+
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+public class SignednessManualExample {
+
+  int s1 = -2;
+  int s2 = -1;
+
+  @Unsigned int u1 = 2147483646; // unsigned: 2^32 - 2, signed: -2
+  @Unsigned int u2 = 2147483647; // unsigned: 2^32 - 1, signed: -1
+
+  void m() {
+    int w = s1 / s2; // OK: result is 2, which is correct for -2 / -1
+    // :: error: (operation.unsignedlhs)
+    int x = u1 / u2; // ERROR: result is 2, which is incorrect for (2^32 - 2) / (2^32 - 1)
+  }
+
+  int s3 = -1;
+  int s4 = 5;
+
+  @Unsigned int u3 = 2147483647; // unsigned: 2^32 - 1, signed: -1
+  @Unsigned int u4 = 5;
+
+  void m2() {
+    int y = s3 % s4; // OK: result is -1, which is correct for -1 % 5
+    // :: error: (operation.unsignedlhs)
+    int z = u3 % u4; // ERROR: result is -1, which is incorrect for (2^32 - 1) % 5 = 2
+  }
+
+  void useLocalVariables() {
+
+    int s1 = -2;
+    int s2 = -1;
+
+    @Unsigned int u1 = 2147483646; // unsigned: 2^32 - 2, signed: -2
+    @Unsigned int u2 = 2147483647; // unsigned: 2^32 - 1, signed: -1
+
+    int w = s1 / s2; // OK: result is 2, which is correct for -2 / -1
+    int x = u1 / u2; // OK; computation over constants, interpreted as signed; result is signed
+
+    int s3 = -1;
+    int s4 = 5;
+
+    @Unsigned int u3 = 2147483647; // unsigned: 2^32 - 1, signed: -1
+    @Unsigned int u4 = 5;
+
+    int y = s3 % s4; // OK: result is -1, which is correct for -1 % 5
+    int z = u3 % u4; // OK; computation over constants, interpreted as signed; result is signed
+  }
+}
diff --git a/checker/tests/signedness/TestPrintln.java b/checker/tests/signedness/TestPrintln.java
new file mode 100644
index 0000000..2e0823f
--- /dev/null
+++ b/checker/tests/signedness/TestPrintln.java
@@ -0,0 +1,19 @@
+// Test case for issue #2366:
+// https://github.com/typetools/checker-framework/issues/2366
+
+import org.checkerframework.checker.signedness.qual.*;
+
+public class TestPrintln {
+  public static void main(String[] args) {
+    // The first call produces the intended result, but the next two do not.
+
+    @Unsigned int a = Integer.parseUnsignedInt("2147483647");
+    System.out.println(a);
+    @Unsigned int b = Integer.parseUnsignedInt("2147483648");
+    // :: error: (argument)
+    System.out.println(b);
+    @Unsigned int c = Integer.parseUnsignedInt("4000000000");
+    // :: error: (argument)
+    System.out.println(c);
+  }
+}
diff --git a/checker/tests/signedness/ToHexString.java b/checker/tests/signedness/ToHexString.java
new file mode 100644
index 0000000..128e141
--- /dev/null
+++ b/checker/tests/signedness/ToHexString.java
@@ -0,0 +1,16 @@
+import org.checkerframework.checker.signedness.qual.Signed;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+public class ToHexString {
+  void toHexString(int x) {
+    Integer.toHexString(x);
+  }
+
+  void toHexStringU(@Unsigned int x) {
+    Integer.toHexString(x);
+  }
+
+  void toHexStringS(@Signed int x) {
+    Integer.toHexString(x);
+  }
+}
diff --git a/checker/tests/signedness/UnsignedRightShiftTest.java b/checker/tests/signedness/UnsignedRightShiftTest.java
new file mode 100644
index 0000000..414f7aa
--- /dev/null
+++ b/checker/tests/signedness/UnsignedRightShiftTest.java
@@ -0,0 +1,50 @@
+// Test case for issue 3667:
+// https://github.com/typetools/checker-framework/issues/3667
+
+import org.checkerframework.checker.signedness.qual.Signed;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+public class UnsignedRightShiftTest {
+  int length;
+
+  void unsignedRightShiftWithLiteral() {
+    int length = Integer.MAX_VALUE;
+    byte b = (byte) (length >>> 24);
+  }
+
+  void unsignedRightShiftWithParameter(int length) {
+    byte b1 = (byte) (length >>> 24);
+    byte b2 = (@Signed byte) (length >>> 24);
+    byte b3 = (@Unsigned byte) (length >>> 24);
+  }
+
+  void unsignedRightShiftWithField() {
+    byte b = (byte) (this.length >>> 24);
+  }
+
+  void unsignedRightShiftComplex() {
+    int length = return12();
+    byte[] byteArray = new byte[4];
+    byteArray[0] = (byte) (length >>> 24);
+    byteArray[1] = (byte) (length >>> 16);
+    byteArray[2] = (byte) (length >>> 8);
+    byteArray[3] = (byte) length;
+  }
+
+  void testWrite64(long x) {
+    write32((int) (x >>> 32));
+  }
+
+  void testWrite64() {
+    long myLong = Long.MAX_VALUE;
+    int z = (int) (myLong >>> 32);
+    int myInt = 2;
+    short w = (short) (myInt >>> 16);
+  }
+
+  int return12() {
+    return 12;
+  }
+
+  void write32(int x) {}
+}
diff --git a/checker/tests/signedness/Utils.java b/checker/tests/signedness/Utils.java
new file mode 100644
index 0000000..9874213
--- /dev/null
+++ b/checker/tests/signedness/Utils.java
@@ -0,0 +1,166 @@
+import java.nio.ByteBuffer;
+import org.checkerframework.checker.signedness.qual.*;
+import org.checkerframework.checker.signedness.util.SignednessUtil;
+
+public class Utils {
+
+  public void getTests(
+      @Unsigned int uint,
+      @Signed int sint,
+      @Unsigned short ushort,
+      @Signed short sshort,
+      @Unsigned byte ubyte,
+      @Signed byte sbyte,
+      @Unsigned byte[] ubyteArr,
+      @Signed byte[] sbyteArr,
+      ByteBuffer b) {
+
+    // :: error: (assignment)
+    sint = SignednessUtil.getUnsignedInt(b);
+
+    uint = SignednessUtil.getUnsignedInt(b);
+
+    // :: error: (assignment)
+    sshort = SignednessUtil.getUnsignedShort(b);
+
+    ushort = SignednessUtil.getUnsignedShort(b);
+
+    // :: error: (assignment)
+    sbyte = SignednessUtil.getUnsigned(b);
+
+    ubyte = SignednessUtil.getUnsigned(b);
+
+    // :: error: (argument)
+    SignednessUtil.getUnsigned(b, sbyteArr);
+
+    SignednessUtil.getUnsigned(b, ubyteArr);
+  }
+
+  public void compTests(
+      @Unsigned long ulong,
+      @Signed long slong,
+      @Unsigned int uint,
+      @Signed int sint,
+      @Unsigned short ushort,
+      @Signed short sshort,
+      @Unsigned byte ubyte,
+      @Signed byte sbyte) {
+
+    int res;
+
+    // :: error: (argument)
+    res = Long.compareUnsigned(slong, slong);
+
+    // :: error: (argument)
+    res = Long.compareUnsigned(slong, ulong);
+
+    // :: error: (argument)
+    res = Long.compareUnsigned(ulong, slong);
+
+    res = Long.compareUnsigned(ulong, ulong);
+
+    // :: error: (argument)
+    res = Integer.compareUnsigned(sint, sint);
+
+    // :: error: (argument)
+    res = Integer.compareUnsigned(sint, uint);
+
+    // :: error: (argument)
+    res = Integer.compareUnsigned(uint, sint);
+
+    res = Integer.compareUnsigned(uint, uint);
+
+    // :: error: (argument)
+    res = SignednessUtil.compareUnsigned(sshort, sshort);
+
+    // :: error: (argument)
+    res = SignednessUtil.compareUnsigned(sshort, ushort);
+
+    // :: error: (argument)
+    res = SignednessUtil.compareUnsigned(ushort, sshort);
+
+    res = SignednessUtil.compareUnsigned(ushort, ushort);
+
+    // :: error: (argument)
+    res = SignednessUtil.compareUnsigned(sbyte, sbyte);
+
+    // :: error: (argument)
+    res = SignednessUtil.compareUnsigned(sbyte, ubyte);
+
+    // :: error: (argument)
+    res = SignednessUtil.compareUnsigned(ubyte, sbyte);
+
+    res = SignednessUtil.compareUnsigned(ubyte, ubyte);
+  }
+
+  public void stringTests(
+      @Unsigned long ulong,
+      @Signed long slong,
+      @Unsigned int uint,
+      @Signed int sint,
+      @Unsigned short ushort,
+      @Signed short sshort,
+      @Unsigned byte ubyte,
+      @Signed byte sbyte) {
+
+    String res;
+
+    // :: error: (argument)
+    res = Long.toUnsignedString(slong);
+
+    res = Long.toUnsignedString(ulong);
+
+    // :: error: (argument)
+    res = Long.toUnsignedString(slong, 10);
+
+    res = Long.toUnsignedString(ulong, 10);
+
+    // :: error: (argument)
+    res = Integer.toUnsignedString(sint);
+
+    res = Integer.toUnsignedString(uint);
+
+    // :: error: (argument)
+    res = Integer.toUnsignedString(sint, 10);
+
+    res = Integer.toUnsignedString(uint, 10);
+
+    // :: error: (argument)
+    res = SignednessUtil.toUnsignedString(sshort);
+
+    res = SignednessUtil.toUnsignedString(ushort);
+
+    // :: error: (argument)
+    res = SignednessUtil.toUnsignedString(sshort, 10);
+
+    res = SignednessUtil.toUnsignedString(ushort, 10);
+
+    // :: error: (argument)
+    res = SignednessUtil.toUnsignedString(sbyte);
+
+    res = SignednessUtil.toUnsignedString(ubyte);
+
+    // :: error: (argument)
+    res = SignednessUtil.toUnsignedString(sbyte, 10);
+
+    res = SignednessUtil.toUnsignedString(ubyte, 10);
+  }
+
+  public void floatingPointConversionTests(
+      @Unsigned long ulong, @Unsigned int uint, @Unsigned short ushort, @Unsigned byte ubyte) {
+
+    float resFloat;
+
+    resFloat = SignednessUtil.toFloat(ubyte);
+    resFloat = SignednessUtil.toFloat(ushort);
+    resFloat = SignednessUtil.toFloat(uint);
+    resFloat = SignednessUtil.toFloat(ulong);
+
+    double resDouble;
+
+    resDouble = SignednessUtil.toDouble(ubyte);
+    resDouble = SignednessUtil.toDouble(ushort);
+    resDouble = SignednessUtil.toDouble(uint);
+    resDouble = SignednessUtil.toDouble(ulong);
+  }
+}
diff --git a/checker/tests/signedness/UtilsJava8.java b/checker/tests/signedness/UtilsJava8.java
new file mode 100644
index 0000000..7aa56f0
--- /dev/null
+++ b/checker/tests/signedness/UtilsJava8.java
@@ -0,0 +1,142 @@
+import org.checkerframework.checker.signedness.qual.Signed;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+// Test Java 8 unsigned utils
+public class UtilsJava8 {
+
+  public void annotatedJDKTests(
+      @Unsigned long ulong,
+      @Signed long slong,
+      @Unsigned int uint,
+      @Signed int sint,
+      char[] buf,
+      String s) {
+
+    String resString;
+    int resInt;
+    long resLong;
+
+    // :: error: (argument)
+    resString = Long.toUnsignedString(slong, 10);
+
+    resString = Long.toUnsignedString(ulong, 10);
+
+    // :: error: (argument)
+    resString = Long.toUnsignedString(slong);
+
+    resString = Long.toUnsignedString(ulong);
+
+    // :: error: (assignment)
+    slong = Long.parseUnsignedLong(s, 10);
+
+    ulong = Long.parseUnsignedLong(s, 10);
+
+    // :: error: (assignment)
+    slong = Long.parseUnsignedLong(s);
+
+    ulong = Long.parseUnsignedLong(s);
+
+    // :: error: (argument)
+    resInt = Long.compareUnsigned(slong, slong);
+
+    // :: error: (argument)
+    resInt = Long.compareUnsigned(slong, ulong);
+
+    // :: error: (argument)
+    resInt = Long.compareUnsigned(ulong, slong);
+
+    resInt = Long.compareUnsigned(ulong, ulong);
+
+    // :: error: (argument)
+    ulong = Long.divideUnsigned(slong, slong);
+
+    // :: error: (argument)
+    ulong = Long.divideUnsigned(slong, ulong);
+
+    // :: error: (argument)
+    ulong = Long.divideUnsigned(ulong, slong);
+
+    // :: error: (assignment)
+    slong = Long.divideUnsigned(ulong, ulong);
+
+    ulong = Long.divideUnsigned(ulong, ulong);
+
+    // :: error: (argument)
+    ulong = Long.remainderUnsigned(slong, slong);
+
+    // :: error: (argument)
+    ulong = Long.remainderUnsigned(slong, ulong);
+
+    // :: error: (argument)
+    ulong = Long.remainderUnsigned(ulong, slong);
+
+    // :: error: (assignment)
+    slong = Long.remainderUnsigned(ulong, ulong);
+
+    ulong = Long.remainderUnsigned(ulong, ulong);
+
+    // :: error: (argument)
+    resString = Integer.toUnsignedString(sint, 10);
+
+    resString = Integer.toUnsignedString(uint, 10);
+
+    // :: error: (argument)
+    resString = Integer.toUnsignedString(sint);
+
+    resString = Integer.toUnsignedString(uint);
+
+    // :: error: (assignment)
+    sint = Integer.parseUnsignedInt(s, 10);
+
+    uint = Integer.parseUnsignedInt(s, 10);
+
+    // :: error: (assignment)
+    sint = Integer.parseUnsignedInt(s);
+
+    uint = Integer.parseUnsignedInt(s);
+
+    // :: error: (argument)
+    resInt = Integer.compareUnsigned(sint, sint);
+
+    // :: error: (argument)
+    resInt = Integer.compareUnsigned(sint, uint);
+
+    // :: error: (argument)
+    resInt = Integer.compareUnsigned(uint, sint);
+
+    resInt = Integer.compareUnsigned(uint, uint);
+
+    resLong = Integer.toUnsignedLong(sint);
+
+    // :: error: (argument)
+    ulong = Integer.toUnsignedLong(uint);
+
+    // :: error: (argument)
+    uint = Integer.divideUnsigned(sint, sint);
+
+    // :: error: (argument)
+    uint = Integer.divideUnsigned(sint, uint);
+
+    // :: error: (argument)
+    uint = Integer.divideUnsigned(uint, sint);
+
+    // :: error: (assignment)
+    sint = Integer.divideUnsigned(uint, uint);
+
+    uint = Integer.divideUnsigned(uint, uint);
+
+    // :: error: (argument)
+    uint = Integer.remainderUnsigned(sint, sint);
+
+    // :: error: (argument)
+    uint = Integer.remainderUnsigned(sint, uint);
+
+    // :: error: (argument)
+    uint = Integer.remainderUnsigned(uint, sint);
+
+    // :: error: (assignment)
+    sint = Integer.remainderUnsigned(uint, uint);
+
+    uint = Integer.remainderUnsigned(uint, uint);
+  }
+}
diff --git a/checker/tests/signedness/ValueIntegration.java b/checker/tests/signedness/ValueIntegration.java
new file mode 100644
index 0000000..ede42e0
--- /dev/null
+++ b/checker/tests/signedness/ValueIntegration.java
@@ -0,0 +1,497 @@
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.checker.signedness.qual.Signed;
+import org.checkerframework.checker.signedness.qual.SignedPositive;
+import org.checkerframework.checker.signedness.qual.SignednessGlb;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.IntVal;
+
+public class ValueIntegration {
+  public void ByteValRules(
+      @IntVal({0, 127}) byte c,
+      @IntVal({128, 255}) byte upure,
+      @IntVal({0, 128}) byte umixed, // 128 is another way to write -128
+      @IntVal({-128, -1}) byte spure,
+      @IntVal({-1, 127}) byte smixed,
+      @IntVal({-128, 0, 128}) byte bmixed) {
+    @Signed byte stest;
+    @SignednessGlb byte gtest;
+    @SignedPositive byte ptest;
+
+    stest = c;
+    gtest = c;
+    ptest = c;
+
+    stest = upure;
+    // :: error: (assignment)
+    gtest = upure;
+    // :: error: (assignment)
+    ptest = upure;
+
+    stest = umixed;
+    // :: error: (assignment)
+    gtest = umixed;
+    // :: error: (assignment)
+    ptest = umixed;
+
+    stest = spure;
+    // :: error: (assignment)
+    gtest = spure;
+    // :: error: (assignment)
+    ptest = spure;
+
+    stest = smixed;
+    // :: error: (assignment)
+    gtest = smixed;
+    // :: error: (assignment)
+    ptest = smixed;
+
+    stest = bmixed;
+    // :: error: (assignment)
+    gtest = bmixed;
+    // :: error: (assignment)
+    ptest = bmixed;
+  }
+
+  // Character and char are always @Unsigned, never @Signed.
+  /*
+  public void CharValRules(
+          @IntVal({0, 127}) char c,
+          @IntVal({128, 255}) char upure,
+          @IntVal({0, 128}) char umixed,
+          @IntVal({-128, -1}) char spure,
+          @IntVal({-1, 127}) char smixed,
+          @IntVal({-128, 0, 128}) char bmixed) {
+      @Signed char stest;
+      @SignednessGlb char gtest;
+      @SignedPositive char ptest;
+
+      stest = c;
+      gtest = c;
+      ptest = c;
+
+      stest = upure;
+      // XX error: (assignment)
+      gtest = upure;
+      // XX error: (assignment)
+      ptest = upure;
+
+      stest = umixed;
+      // XX error: (assignment)
+      gtest = umixed;
+      // XX error: (assignment)
+      ptest = umixed;
+
+      stest = spure;
+      // XX error: (assignment)
+      gtest = spure;
+      // XX error: (assignment)
+      ptest = spure;
+
+      stest = smixed;
+      // XX error: (assignment)
+      gtest = smixed;
+      // XX error: (assignment)
+      ptest = smixed;
+
+      stest = bmixed;
+      // XX error: (assignment)
+      gtest = bmixed;
+      // XX error: (assignment)
+      ptest = bmixed;
+  }
+  */
+
+  public void ShortValRules(
+      @IntVal({0, 32767}) short c,
+      @IntVal({32768, 65535}) short upure,
+      @IntVal({0, 32768}) short umixed,
+      @IntVal({-32768, -1}) short spure,
+      @IntVal({-1, 32767}) short smixed,
+      @IntVal({-32768, 0, 32768}) short bmixed) {
+    @Signed short stest;
+    @SignednessGlb short gtest;
+    @SignedPositive short ptest;
+
+    stest = c;
+    gtest = c;
+    ptest = c;
+
+    stest = upure;
+    // :: error: (assignment)
+    gtest = upure;
+    // :: error: (assignment)
+    ptest = upure;
+
+    stest = umixed;
+    // :: error: (assignment)
+    gtest = umixed;
+    // :: error: (assignment)
+    ptest = umixed;
+
+    stest = spure;
+    // :: error: (assignment)
+    gtest = spure;
+    // :: error: (assignment)
+    ptest = spure;
+
+    stest = smixed;
+    // :: error: (assignment)
+    gtest = smixed;
+    // :: error: (assignment)
+    ptest = smixed;
+
+    stest = bmixed;
+    // :: error: (assignment)
+    gtest = bmixed;
+    // :: error: (assignment)
+    ptest = bmixed;
+  }
+
+  public void IntValRules(
+      @IntVal({0, 2147483647}) int c,
+      @IntVal({2147483648L, 4294967295L}) int upure,
+      @IntVal({0, 2147483648L}) int umixed,
+      @IntVal({-2147483648, -1}) int spure,
+      @IntVal({-1, 2147483647}) int smixed,
+      @IntVal({-2147483648, 0, 2147483648L}) int bmixed) {
+    @Signed int stest;
+    @SignednessGlb int gtest;
+    @SignedPositive int ptest;
+
+    stest = c;
+    gtest = c;
+    ptest = c;
+
+    stest = upure;
+    // :: error: (assignment)
+    gtest = upure;
+    // :: error: (assignment)
+    ptest = upure;
+
+    stest = umixed;
+    // :: error: (assignment)
+    gtest = umixed;
+    // :: error: (assignment)
+    ptest = umixed;
+
+    stest = spure;
+    // :: error: (assignment)
+    gtest = spure;
+    // :: error: (assignment)
+    ptest = spure;
+
+    stest = smixed;
+    // :: error: (assignment)
+    gtest = smixed;
+    // :: error: (assignment)
+    ptest = smixed;
+
+    stest = bmixed;
+    // :: error: (assignment)
+    gtest = bmixed;
+    // :: error: (assignment)
+    ptest = bmixed;
+  }
+
+  public void LongValRules(
+      @IntVal({0, Long.MAX_VALUE}) long c,
+      @IntVal({Long.MIN_VALUE, -1}) long spure,
+      @IntVal({-1, Long.MAX_VALUE}) long smixed,
+      @IntVal({Long.MIN_VALUE, 0, Long.MAX_VALUE}) long bmixed) {
+    @Signed long stest;
+    @SignednessGlb long gtest;
+    @SignedPositive long ptest;
+
+    stest = c;
+    gtest = c;
+    ptest = c;
+
+    stest = spure;
+    // :: error: (assignment)
+    gtest = spure;
+    // :: error: (assignment)
+    ptest = spure;
+
+    stest = smixed;
+    // :: error: (assignment)
+    gtest = smixed;
+    // :: error: (assignment)
+    ptest = smixed;
+
+    stest = bmixed;
+    // :: error: (assignment)
+    gtest = bmixed;
+    // :: error: (assignment)
+    ptest = bmixed;
+  }
+
+  public void ByteRangeRules(
+      @IntRange(from = 0, to = 127) byte c,
+      @NonNegative byte nnc,
+      @Positive byte pc,
+      @IntRange(from = 128, to = 255) byte upure,
+      @IntRange(from = 0, to = 128) byte umixed,
+      @IntRange(from = -128, to = -1) byte spure,
+      @IntRange(from = -1, to = 127) byte smixed,
+      @IntRange(from = -128, to = 128) byte bmixed) {
+    @Signed byte stest;
+    @SignednessGlb byte gtest;
+    @SignedPositive byte ptest;
+
+    stest = c;
+    gtest = c;
+    ptest = c;
+
+    stest = nnc;
+    gtest = nnc;
+    ptest = nnc;
+
+    stest = pc;
+    gtest = pc;
+    ptest = pc;
+
+    stest = upure;
+    // :: error: (assignment)
+    gtest = upure;
+    // :: error: (assignment)
+    ptest = upure;
+
+    stest = umixed;
+    // :: error: (assignment)
+    gtest = umixed;
+    // :: error: (assignment)
+    ptest = umixed;
+
+    stest = spure;
+    // :: error: (assignment)
+    gtest = spure;
+    // :: error: (assignment)
+    ptest = spure;
+
+    stest = smixed;
+    // :: error: (assignment)
+    gtest = smixed;
+    // :: error: (assignment)
+    ptest = smixed;
+
+    stest = bmixed;
+    // :: error: (assignment)
+    gtest = bmixed;
+    // :: error: (assignment)
+    ptest = bmixed;
+  }
+
+  // Character and char are always @Unsigned, never @Signed.
+  /*
+  public void CharRangeRules(
+          @IntRange(from = 0, to = 127) char c,
+          @NonNegative char nnc,
+          @Positive char pc,
+          @IntRange(from = 128, to = 255) char upure,
+          @IntRange(from = 0, to = 128) char umixed,
+          @IntRange(from = -128, to = -1) char spure,
+          @IntRange(from = -1, to = 127) char smixed,
+          @IntRange(from = -128, to = 128) char bmixed) {
+      @Signed char stest;
+      @SignednessGlb char gtest;
+      @SignedPositive char ptest;
+
+      stest = c;
+      gtest = c;
+      ptest = c;
+
+      stest = nnc;
+      gtest = nnc;
+      ptest = nnc;
+
+      stest = pc;
+      gtest = pc;
+      ptest = pc;
+
+      stest = upure;
+      // XX error: (assignment)
+      gtest = upure;
+      // XX error: (assignment)
+      ptest = upure;
+
+      stest = umixed;
+      // XX error: (assignment)
+      gtest = umixed;
+      // XX error: (assignment)
+      ptest = umixed;
+
+      stest = spure;
+      // XX error: (assignment)
+      gtest = spure;
+      // XX error: (assignment)
+      ptest = spure;
+
+      stest = smixed;
+      // XX error: (assignment)
+      gtest = smixed;
+      // XX error: (assignment)
+      ptest = smixed;
+
+      stest = bmixed;
+      // XX error: (assignment)
+      gtest = bmixed;
+      // XX error: (assignment)
+      ptest = bmixed;
+  }
+  */
+
+  public void ShortRangeRules(
+      @IntRange(from = 0, to = 32767) short c,
+      @NonNegative short nnc,
+      @Positive short pc,
+      @IntRange(from = 32768, to = 65535) short upure,
+      @IntRange(from = 0, to = 32768) short umixed,
+      @IntRange(from = -32768, to = -1) short spure,
+      @IntRange(from = -1, to = 32767) short smixed,
+      @IntRange(from = -32768, to = 32768) short bmixed) {
+    @Signed short stest;
+    @SignednessGlb short gtest;
+    @SignedPositive short ptest;
+
+    stest = c;
+    gtest = c;
+    ptest = c;
+
+    stest = nnc;
+    gtest = nnc;
+    ptest = nnc;
+
+    stest = pc;
+    gtest = pc;
+    ptest = pc;
+
+    stest = upure;
+    // :: error: (assignment)
+    gtest = upure;
+    // :: error: (assignment)
+    ptest = upure;
+
+    stest = umixed;
+    // :: error: (assignment)
+    gtest = umixed;
+    // :: error: (assignment)
+    ptest = umixed;
+
+    stest = spure;
+    // :: error: (assignment)
+    gtest = spure;
+    // :: error: (assignment)
+    ptest = spure;
+
+    stest = smixed;
+    // :: error: (assignment)
+    gtest = smixed;
+    // :: error: (assignment)
+    ptest = smixed;
+
+    stest = bmixed;
+    // :: error: (assignment)
+    gtest = bmixed;
+    // :: error: (assignment)
+    ptest = bmixed;
+  }
+
+  public void IntRangeRules(
+      @IntRange(from = 0, to = 2147483647) int c,
+      @NonNegative int nnc,
+      @Positive int pc,
+      @IntRange(from = 2147483648L, to = 4294967295L) int upure,
+      @IntRange(from = 0, to = 2147483648L) int umixed,
+      @IntRange(from = -2147483648, to = -1) int spure,
+      @IntRange(from = -1, to = 2147483647) int smixed,
+      @IntRange(from = -2147483648, to = 2147483648L) int bmixed) {
+    @Signed int stest;
+    @SignednessGlb int gtest;
+    @SignedPositive int ptest;
+
+    stest = c;
+    gtest = c;
+    ptest = c;
+
+    stest = nnc;
+    gtest = nnc;
+    ptest = nnc;
+
+    stest = pc;
+    gtest = pc;
+    ptest = pc;
+
+    stest = upure;
+    // :: error: (assignment)
+    gtest = upure;
+    // :: error: (assignment)
+    ptest = upure;
+
+    stest = umixed;
+    // :: error: (assignment)
+    gtest = umixed;
+    // :: error: (assignment)
+    ptest = umixed;
+
+    stest = spure;
+    // :: error: (assignment)
+    gtest = spure;
+    // :: error: (assignment)
+    ptest = spure;
+
+    stest = smixed;
+    // :: error: (assignment)
+    gtest = smixed;
+    // :: error: (assignment)
+    ptest = smixed;
+
+    stest = bmixed;
+    // :: error: (assignment)
+    gtest = bmixed;
+    // :: error: (assignment)
+    ptest = bmixed;
+  }
+
+  public void LongRangeRules(
+      @IntRange(from = 0, to = Long.MAX_VALUE) long c,
+      @NonNegative long nnc,
+      @Positive long pc,
+      @IntRange(from = Long.MIN_VALUE, to = -1) long spure,
+      @IntRange(from = -1, to = Long.MAX_VALUE) long smixed,
+      @IntRange(from = Long.MIN_VALUE, to = Long.MAX_VALUE) long bmixed) {
+    @Signed long stest;
+    @SignednessGlb long gtest;
+    @SignedPositive long ptest;
+
+    stest = c;
+    gtest = c;
+    ptest = c;
+
+    stest = nnc;
+    gtest = nnc;
+    ptest = nnc;
+
+    stest = pc;
+    gtest = pc;
+    ptest = pc;
+
+    stest = spure;
+    // :: error: (assignment)
+    gtest = spure;
+    // :: error: (assignment)
+    ptest = spure;
+
+    stest = smixed;
+    // :: error: (assignment)
+    gtest = smixed;
+    // :: error: (assignment)
+    ptest = smixed;
+
+    stest = bmixed;
+    // :: error: (assignment)
+    gtest = bmixed;
+    // :: error: (assignment)
+    ptest = bmixed;
+  }
+}
diff --git a/checker/tests/signedness/WideningConversion.java b/checker/tests/signedness/WideningConversion.java
new file mode 100644
index 0000000..737f0f8
--- /dev/null
+++ b/checker/tests/signedness/WideningConversion.java
@@ -0,0 +1,95 @@
+import org.checkerframework.checker.signedness.qual.Signed;
+import org.checkerframework.checker.signedness.qual.Unsigned;
+
+public class WideningConversion {
+
+  char c1;
+  char c2;
+  int i1;
+  int i2;
+  @Signed int si1;
+  @Signed int si2;
+  @Unsigned int ui1;
+  @Unsigned int ui2;
+
+  void compare() {
+    boolean b;
+    b = c1 > c2;
+    b = c1 > i2;
+    b = i1 > c2;
+    b = i1 > i2;
+  }
+
+  void plus() {
+    // Not just "int si" because it's defaulted to TOP so every assignment would work.
+    @Signed int si;
+    si = c1 + c2;
+    si = c1 + i2;
+    si = i1 + c2;
+    si = i1 + i2;
+
+    si = c1 + c2;
+    si = c1 + si2;
+    si = si1 + c2;
+    si = si1 + si2;
+
+    si = c1 + c2;
+    // :: error: (assignment)
+    si = c1 + ui2;
+    // :: error: (assignment)
+    si = ui1 + c2;
+    // :: error: (assignment)
+    si = ui1 + ui2;
+
+    @Unsigned int ui;
+    ui = c1 + c2;
+    // :: error: (assignment)
+    ui = c1 + i2;
+    // :: error: (assignment)
+    ui = i1 + c2;
+    // :: error: (assignment)
+    ui = i1 + i2;
+
+    ui = c1 + c2;
+    // :: error: (assignment)
+    ui = c1 + si2;
+    // :: error: (assignment)
+    ui = si1 + c2;
+    // :: error: (assignment)
+    ui = si1 + si2;
+
+    ui = c1 + c2;
+    ui = c1 + ui2;
+    ui = ui1 + c2;
+    ui = ui1 + ui2;
+
+    // All of these are illegal in Java, without an explicit cast.
+    // char c;
+    // c = c1 + c2;
+    // c = c1 + i2;
+    // c = i1 + c2;
+    // c = i1 + i2;
+
+    char c;
+    c = (char) (c1 + c2);
+    // :: warning: (cast.unsafe)
+    c = (char) (c1 + i2);
+    // :: warning: (cast.unsafe)
+    c = (char) (i1 + c2);
+    // :: warning: (cast.unsafe)
+    c = (char) (i1 + i2);
+
+    c = (char) (c1 + c2);
+    // :: warning: (cast.unsafe)
+    c = (char) (c1 + si2);
+    // :: warning: (cast.unsafe)
+    c = (char) (si1 + c2);
+    // :: warning: (cast.unsafe)
+    c = (char) (si1 + si2);
+
+    c = (char) (c1 + c2);
+    c = (char) (c1 + ui2);
+    c = (char) (ui1 + c2);
+    c = (char) (ui1 + ui2);
+  }
+}
diff --git a/checker/tests/signedness/WideningFloat.java b/checker/tests/signedness/WideningFloat.java
new file mode 100644
index 0000000..cbdeb73
--- /dev/null
+++ b/checker/tests/signedness/WideningFloat.java
@@ -0,0 +1,22 @@
+import org.checkerframework.checker.signedness.qual.UnknownSignedness;
+
+public class WideningFloat {
+
+  void floatArg(float x) {}
+
+  void m(Object arg) {
+    floatArg((Byte) arg);
+  }
+
+  void m2(@UnknownSignedness Byte arg) {
+    floatArg(arg);
+  }
+
+  void m3(@UnknownSignedness Byte arg) {
+    float f = arg;
+  }
+
+  void m3(@UnknownSignedness byte arg) {
+    float f = arg;
+  }
+}
diff --git a/checker/tests/signedness/WideningInitialization.java b/checker/tests/signedness/WideningInitialization.java
new file mode 100644
index 0000000..469ca80
--- /dev/null
+++ b/checker/tests/signedness/WideningInitialization.java
@@ -0,0 +1,34 @@
+import org.checkerframework.checker.signedness.qual.Signed;
+
+public class WideningInitialization {
+  public int findLineNr(int pc, char startPC, char lineNr) {
+    int ln = 0;
+    for (int i = 0; i < 3; i++) {
+      if (startPC <= pc) {
+        ln = lineNr;
+      } else {
+        return ln;
+      }
+    }
+    return ln;
+  }
+
+  public int findLineNr2(char lineNr) {
+    int ln = 0;
+    ln = lineNr;
+    return ln;
+  }
+
+  public void findLineNr3a(char lineNr) {
+    @Signed int ln = lineNr;
+  }
+
+  public int findLineNr3b(char lineNr) {
+    int ln = lineNr;
+    return ln;
+  }
+
+  public int findLineNr4(char lineNr) {
+    return lineNr;
+  }
+}
diff --git a/checker/tests/stubparser-nullness/MultidimentionalArrayAnnotationTest.java b/checker/tests/stubparser-nullness/MultidimentionalArrayAnnotationTest.java
new file mode 100644
index 0000000..b507141
--- /dev/null
+++ b/checker/tests/stubparser-nullness/MultidimentionalArrayAnnotationTest.java
@@ -0,0 +1,216 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+/*
+ * The test checks annotations in multidimention arrays.
+ * Each array dimention is beeing annotated with eather @Nullable or @NonNull
+ * to check error is thrown if assignment type is incompatible on eather
+ * array level.
+ * Tests uses 3 dimentional arrays. Each annotaion combination is used once starting
+ * with @Nullable [] @Nullable [] @Nullable [] and
+ * ends with @NonNull [] @NonNull [] @NonNull [].
+ *
+ * Test has 8 methods that returns 3-dimentional arrays where each dimention is annotated
+ * with eather @Nullable or @NonNull.
+ *
+ * Test containg 8 methods where all variables are beeing assign with one of arrays from
+ * method that returns annotated arrays.
+ *
+ * Errors are expected if one or more array levels in a declaration are annotated with @NonNull, but
+ * in assignment are annotated with @Nullable.
+ */
+public class MultidimentionalArrayAnnotationTest {
+
+  int numb = 1;
+
+  // Declared 8 3-dimentional variables.
+  Object @Nullable [] @Nullable [] @Nullable [] obj1 = new Object[numb][numb][numb];
+  Object @NonNull [] @Nullable [] @Nullable [] obj2 = new Object[numb][numb][numb];
+  Object @Nullable [] @NonNull [] @Nullable [] obj3 = new Object[numb][numb][numb];
+  Object @Nullable [] @Nullable [] @NonNull [] obj4 = new Object[numb][numb][numb];
+  Object @NonNull [] @NonNull [] @Nullable [] obj5 = new Object[numb][numb][numb];
+  Object @NonNull [] @Nullable [] @NonNull [] obj6 = new Object[numb][numb][numb];
+  Object @Nullable [] @NonNull [] @NonNull [] obj7 = new Object[numb][numb][numb];
+  Object @NonNull [] @NonNull [] @NonNull [] obj8 = new Object[numb][numb][numb];
+
+  /*
+   * Call to method 1 that returns Object @NonNull [] @NonNull [] @NonNull [].
+   * Errors are not expected.
+   */
+  void callTomethod1() {
+    obj1 = method1();
+    obj2 = method1();
+    obj3 = method1();
+    obj4 = method1();
+    obj5 = method1();
+    obj6 = method1();
+    obj7 = method1();
+    obj8 = method1();
+  }
+
+  /*
+   * Call to method 2 that returns Object @Nullable [] @NonNull [] @NonNull [].
+   */
+  void callTomethod2() {
+    obj1 = method2();
+    // :: error: (assignment)
+    obj2 = method2();
+    obj3 = method2();
+    obj4 = method2();
+    // :: error: (assignment)
+    obj5 = method2();
+    // :: error: (assignment)
+    obj6 = method2();
+    obj7 = method2();
+    // :: error: (assignment)
+    obj8 = method2();
+  }
+
+  /*
+   * Call to method 3 that returns Object @NonNull [] @Nullable [] @NonNull [].
+   */
+  void callTomethod3() {
+    obj1 = method3();
+    obj2 = method3();
+    // :: error: (assignment)
+    obj3 = method3();
+    obj4 = method3();
+    // :: error: (assignment)
+    obj5 = method3();
+    obj6 = method3();
+    // :: error: (assignment)
+    obj7 = method3();
+    // :: error: (assignment)
+    obj8 = method3();
+  }
+
+  /*
+   * Call to method 4 that returns Object @NonNull [] @NonNull [] @Nullable [].
+   */
+  void callTomethod4() {
+    obj1 = method4();
+    obj2 = method4();
+    obj3 = method4();
+    // :: error: (assignment)
+    obj4 = method4();
+    obj5 = method4();
+    // :: error: (assignment)
+    obj6 = method4();
+    // :: error: (assignment)
+    obj7 = method4();
+    // :: error: (assignment)
+    obj8 = method4();
+  }
+
+  /*
+   * Call to method 5 that returns Object @Nullable [] @Nullable [] @NonNull [].
+   */
+  void callTomethod5() {
+    obj1 = method5();
+    // :: error: (assignment)
+    obj2 = method5();
+    // :: error: (assignment)
+    obj3 = method5();
+    obj4 = method5();
+    // :: error: (assignment)
+    obj5 = method5();
+    // :: error: (assignment)
+    obj6 = method5();
+    // :: error: (assignment)
+    obj7 = method5();
+    // :: error: (assignment)
+    obj8 = method5();
+  }
+
+  /*
+   * Call to method 6 that returns Object @Nullable [] @NonNull [] @Nullable [].
+   */
+  void callTomethod6() {
+    obj1 = method6();
+    // :: error: (assignment)
+    obj2 = method6();
+    obj3 = method6();
+    // :: error: (assignment)
+    obj4 = method6();
+    // :: error: (assignment)
+    obj5 = method6();
+    // :: error: (assignment)
+    obj6 = method6();
+    // :: error: (assignment)
+    obj7 = method6();
+    // :: error: (assignment)
+    obj8 = method6();
+  }
+
+  /*
+   * Call to method 7 that returns Object @NonNull [] @Nullable [] @Nullable [].
+   */
+  void callTomethod7() {
+    obj1 = method7();
+    obj2 = method7();
+    // :: error: (assignment)
+    obj3 = method7();
+    // :: error: (assignment)
+    obj4 = method7();
+    // :: error: (assignment)
+    obj5 = method7();
+    // :: error: (assignment)
+    obj6 = method7();
+    // :: error: (assignment)
+    obj7 = method7();
+    // :: error: (assignment)
+    obj8 = method7();
+  }
+
+  /*
+   * Call to method 8 that returns Object @Nullable [] @Nullable [] @Nullable [].
+   */
+  void callTomethod8() {
+    obj1 = method8();
+    // :: error: (assignment)
+    obj2 = method8();
+    // :: error: (assignment)
+    obj3 = method8();
+    // :: error: (assignment)
+    obj4 = method8();
+    // :: error: (assignment)
+    obj5 = method8();
+    // :: error: (assignment)
+    obj6 = method8();
+    // :: error: (assignment)
+    obj7 = method8();
+    // :: error: (assignment)
+    obj8 = method8();
+  }
+
+  Object[][][] method1() {
+    return new Object[numb][numb][numb];
+  }
+
+  Object @Nullable [][][] method2() {
+    return new Object[numb][numb][numb];
+  }
+
+  Object[] @Nullable [][] method3() {
+    return new Object[numb][numb][numb];
+  }
+
+  Object[][] @Nullable [] method4() {
+    return new Object[numb][numb][numb];
+  }
+
+  Object @Nullable [] @Nullable [][] method5() {
+    return new Object[numb][numb][numb];
+  }
+
+  Object @Nullable [][] @Nullable [] method6() {
+    return new Object[numb][numb][numb];
+  }
+
+  Object[] @Nullable [] @Nullable [] method7() {
+    return new Object[numb][numb][numb];
+  }
+
+  Object @Nullable [] @Nullable [] @Nullable [] method8() {
+    return new Object[numb][numb][numb];
+  }
+}
diff --git a/checker/tests/stubparser-nullness/NoExplicitAnnotations.astub b/checker/tests/stubparser-nullness/NoExplicitAnnotations.astub
new file mode 100644
index 0000000..8591868
--- /dev/null
+++ b/checker/tests/stubparser-nullness/NoExplicitAnnotations.astub
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class NoExplicitAnnotationsSub1 {
+    // There is no explicit annotation here.
+    String method1();
+}
+
+class NoExplicitAnnotationsSub2 {
+    // There is an explicit annotation here.
+    @NonNull String method2();
+}
+
+class NoExplicitAnnotationsSub3 {
+    // There is an explicit annotation here.
+    @Nullable String method3();
+}
diff --git a/checker/tests/stubparser-nullness/NoExplicitAnnotations.java b/checker/tests/stubparser-nullness/NoExplicitAnnotations.java
new file mode 100644
index 0000000..5654262
--- /dev/null
+++ b/checker/tests/stubparser-nullness/NoExplicitAnnotations.java
@@ -0,0 +1,64 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class NoExplicitAnnotations {}
+
+class NoExplicitAnnotationsSuper {
+  @Nullable String method1() {
+    return helper();
+  }
+
+  @Nullable String method2() {
+    return helper();
+  }
+
+  @Nullable String method3() {
+    return helper();
+  }
+
+  @Nullable String helper() {
+    return null;
+  }
+}
+
+class NoExplicitAnnotationsSub1 extends NoExplicitAnnotationsSuper {
+  @Override
+  String helper() {
+    return "hello";
+  }
+}
+
+class NoExplicitAnnotationsSub2 extends NoExplicitAnnotationsSuper {
+  @Override
+  String helper() {
+    return "hello";
+  }
+}
+
+class NoExplicitAnnotationsSub3 extends NoExplicitAnnotationsSuper {
+  @Override
+  String helper() {
+    return "hello";
+  }
+}
+
+class NoExplicitAnnotationsUse {
+  @Nullable String nble = null;
+  @NonNull String nn = "hello";
+
+  void use(
+      NoExplicitAnnotationsSub1 sub1,
+      NoExplicitAnnotationsSub2 sub2,
+      NoExplicitAnnotationsSub3 sub3) {
+    nble = sub1.method1();
+    nn = sub1.method1();
+    nble = sub2.method2();
+    nn = sub2.method2();
+    nble = sub3.method3();
+    // :: error: (assignment)
+    nn = sub3.method3();
+
+    // :: error: (assignment)
+    nn = nble;
+  }
+}
diff --git a/checker/tests/stubparser-nullness/VarargConstructorParameterAnnotationTest.java b/checker/tests/stubparser-nullness/VarargConstructorParameterAnnotationTest.java
new file mode 100644
index 0000000..71a3d4d
--- /dev/null
+++ b/checker/tests/stubparser-nullness/VarargConstructorParameterAnnotationTest.java
@@ -0,0 +1,25 @@
+import org.checkerframework.checker.nullness.qual.*;
+
+/*
+ * Tests parsing annotations on parameter represented by an array or vararg to the constructor.
+ */
+public class VarargConstructorParameterAnnotationTest {
+
+  public void strArraysNonNull(@NonNull String[] parameter) {
+    new ProcessBuilder(parameter);
+  }
+
+  public void strArraysNullable(@Nullable String[] parameter) {
+    // :: error: (argument)
+    new ProcessBuilder(parameter);
+  }
+
+  public void strVarargNonNull(@NonNull String... parameter) {
+    new ProcessBuilder(parameter);
+  }
+
+  public void strVarargNullable(@Nullable String... parameter) {
+    // :: error: (argument)
+    new ProcessBuilder(parameter);
+  }
+}
diff --git a/checker/tests/stubparser-tainting/FakeOverrideFormalParameter.astub-TODO b/checker/tests/stubparser-tainting/FakeOverrideFormalParameter.astub-TODO
new file mode 100644
index 0000000..a0c127b
--- /dev/null
+++ b/checker/tests/stubparser-tainting/FakeOverrideFormalParameter.astub-TODO
@@ -0,0 +1,19 @@
+// TODO: Fake overrides only work for return types, not formal parameters yet.
+
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+class FakeOverrideFPMid {
+
+    void requiresTaintedIntWithFakeOverride(@Untainted int i) {}
+
+    void requiresUntaintedIntWithFakeOverride(@Tainted int i) {}
+
+    void requiresTaintedIntegerWithFakeOverride(@Untainted Integer i) {}
+
+    void requiresUntaintedIntegerWithFakeOverride(@Tainted Integer i) {}
+
+    void requiresTaintedFqIntegerWithFakeOverride(java.lang. @Untainted Integer i) {}
+
+    void requiresUntaintedFqIntegerWithFakeOverride(java.lang. @Tainted Integer i) {}
+
+}
diff --git a/checker/tests/stubparser-tainting/FakeOverrideFormalParameter.java-TODO b/checker/tests/stubparser-tainting/FakeOverrideFormalParameter.java-TODO
new file mode 100644
index 0000000..7bcf103
--- /dev/null
+++ b/checker/tests/stubparser-tainting/FakeOverrideFormalParameter.java-TODO
@@ -0,0 +1,86 @@
+// TODO: Fake overrides only work for return types, not formal parameters yet.
+
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+// Define this class because it's the name of the file
+public class FakeOverrideFormalParameter {}
+
+class FakeOverrideFPSuper {
+
+    void requiresTaintedInt(@Tainted int i) {}
+
+    void requiresUntaintedInt(@Untainted int i) {}
+
+    void requiresTaintedIntWithFakeOverride(@Tainted int i) {}
+
+    void requiresUntaintedIntWithFakeOverride(@Untainted int i) {}
+
+    void requiresTaintedInteger(@Tainted Integer i) {}
+
+    void requiresUntaintedInteger(@Untainted Integer i) {}
+
+    void requiresTaintedIntegerWithFakeOverride(@Tainted Integer i) {}
+
+    void requiresUntaintedIntegerWithFakeOverride(@Untainted Integer i) {}
+
+    void requiresTaintedFqInteger(java.lang.@Tainted Integer i) {}
+
+    void requiresUntaintedFqInteger(java.lang.@Untainted Integer i) {}
+
+    void requiresTaintedFqIntegerWithFakeOverride(java.lang.@Tainted Integer i) {}
+
+    void requiresUntaintedFqIntegerWithFakeOverride(java.lang.@Untainted Integer i) {}
+}
+
+class FakeOverrideFPMid extends FakeOverrideFPSuper {}
+
+class FakeOverrideFPSub extends FakeOverrideFPMid {}
+
+class FakeOverrideFPClient {
+
+    @Tainted int tf;
+
+    @Untainted int uf;
+
+    void m(@Tainted int t, @Untainted int u) {
+
+        FakeOverrideFPSuper sup = new FakeOverrideFPSuper();
+        FakeOverrideFPMid mid = new FakeOverrideFPMid();
+        FakeOverrideFPSub sub = new FakeOverrideFPSub();
+
+        sup.requiresTaintedInt(t);
+        mid.requiresTaintedInt(t);
+        sub.requiresTaintedInt(t);
+        sup.requiresTaintedInt(u);
+        mid.requiresTaintedInt(u);
+        sub.requiresTaintedInt(u);
+
+        // :: error: (argument)
+        sup.requiresUntaintedInt(t);
+        // :: error: (argument)
+        mid.requiresUntaintedInt(t);
+        // :: error: (argument)
+        sub.requiresUntaintedInt(t);
+        sup.requiresUntaintedInt(u);
+        mid.requiresUntaintedInt(u);
+        sub.requiresUntaintedInt(u);
+
+        sup.requiresTaintedIntWithFakeOverride(t);
+        // :: error: (argument)
+        mid.requiresTaintedIntWithFakeOverride(t);
+        // :: error: (argument)
+        sub.requiresTaintedIntWithFakeOverride(t);
+        sup.requiresTaintedIntWithFakeOverride(u);
+        mid.requiresTaintedIntWithFakeOverride(u);
+        sub.requiresTaintedIntWithFakeOverride(u);
+
+        // :: error: (argument)
+        sup.requiresUntaintedIntWithFakeOverride(t);
+        mid.requiresUntaintedIntWithFakeOverride(t);
+        sub.requiresUntaintedIntWithFakeOverride(t);
+        sup.requiresUntaintedIntWithFakeOverride(u);
+        mid.requiresUntaintedIntWithFakeOverride(u);
+        sub.requiresUntaintedIntWithFakeOverride(u);
+    }
+}
diff --git a/checker/tests/stubparser-tainting/FakeOverrideRMid.java b/checker/tests/stubparser-tainting/FakeOverrideRMid.java
new file mode 100644
index 0000000..ec9b9e4
--- /dev/null
+++ b/checker/tests/stubparser-tainting/FakeOverrideRMid.java
@@ -0,0 +1 @@
+public class FakeOverrideRMid extends FakeOverrideRSuper {}
diff --git a/checker/tests/stubparser-tainting/FakeOverrideRSub.java b/checker/tests/stubparser-tainting/FakeOverrideRSub.java
new file mode 100644
index 0000000..1e1e108
--- /dev/null
+++ b/checker/tests/stubparser-tainting/FakeOverrideRSub.java
@@ -0,0 +1 @@
+public class FakeOverrideRSub extends FakeOverrideRMid {}
diff --git a/checker/tests/stubparser-tainting/FakeOverrideRSuper.java b/checker/tests/stubparser-tainting/FakeOverrideRSuper.java
new file mode 100644
index 0000000..6af907d
--- /dev/null
+++ b/checker/tests/stubparser-tainting/FakeOverrideRSuper.java
@@ -0,0 +1,32 @@
+// The type qualifier hierarchy is: @Tainted :> @Untainted
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+@SuppressWarnings("tainting")
+public class FakeOverrideRSuper {
+
+  public @Tainted int returnsTaintedInt() {
+    return 0;
+  }
+
+  public @Untainted int returnsUntaintedInt() {
+    return 0;
+  }
+
+  public @Tainted int returnsTaintedIntWithFakeOverride() {
+    return 0;
+  }
+
+  public @Untainted int returnsUntaintedIntWithFakeOverride() {
+    return 0;
+  }
+
+  public @Untainted int returnsUntaintedIntWithFakeOverride2() {
+    return 0;
+  }
+
+  public @PolyTainted int returnsPolyTaintedIntWithFakeOverride() {
+    return 0;
+  }
+}
diff --git a/checker/tests/stubparser-tainting/FakeOverrideReturn.astub b/checker/tests/stubparser-tainting/FakeOverrideReturn.astub
new file mode 100644
index 0000000..813ca9c
--- /dev/null
+++ b/checker/tests/stubparser-tainting/FakeOverrideReturn.astub
@@ -0,0 +1,15 @@
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+class FakeOverrideRMid {
+
+  public @Untainted int returnsTaintedIntWithFakeOverride();
+
+  public @Tainted int returnsUntaintedIntWithFakeOverride();
+
+  public @PolyTainted int returnsUntaintedIntWithFakeOverride2();
+
+  public @Untainted int returnsPolyTaintedIntWithFakeOverride();
+
+}
diff --git a/checker/tests/stubparser-tainting/FakeOverrideReturn.java b/checker/tests/stubparser-tainting/FakeOverrideReturn.java
new file mode 100644
index 0000000..ca8a261
--- /dev/null
+++ b/checker/tests/stubparser-tainting/FakeOverrideReturn.java
@@ -0,0 +1,59 @@
+// The type qualifier hierarchy is: @Tainted :> @Untainted
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class FakeOverrideReturn {
+
+  @Tainted int tf;
+
+  @Untainted int uf;
+
+  void m(@Tainted int t, @Untainted int u) {
+
+    FakeOverrideRSuper sup = new FakeOverrideRSuper();
+    FakeOverrideRMid mid = new FakeOverrideRMid();
+    FakeOverrideRSub sub = new FakeOverrideRSub();
+
+    tf = sup.returnsTaintedInt();
+    tf = mid.returnsTaintedInt();
+    tf = sub.returnsTaintedInt();
+    // :: error: (assignment)
+    uf = sup.returnsTaintedInt();
+    // :: error: (assignment)
+    uf = mid.returnsTaintedInt();
+    // :: error: (assignment)
+    uf = sub.returnsTaintedInt();
+
+    tf = sup.returnsUntaintedInt();
+    tf = mid.returnsUntaintedInt();
+    tf = sub.returnsUntaintedInt();
+    uf = sup.returnsUntaintedInt();
+    uf = mid.returnsUntaintedInt();
+    uf = sub.returnsUntaintedInt();
+
+    tf = sup.returnsTaintedIntWithFakeOverride();
+    tf = mid.returnsTaintedIntWithFakeOverride();
+    tf = sub.returnsTaintedIntWithFakeOverride();
+    // :: error: (assignment)
+    uf = sup.returnsTaintedIntWithFakeOverride();
+    uf = mid.returnsTaintedIntWithFakeOverride();
+    uf = sub.returnsTaintedIntWithFakeOverride();
+
+    tf = sup.returnsUntaintedIntWithFakeOverride();
+    tf = mid.returnsUntaintedIntWithFakeOverride();
+    tf = sub.returnsUntaintedIntWithFakeOverride();
+    uf = sup.returnsUntaintedIntWithFakeOverride();
+    // :: error: (assignment)
+    uf = mid.returnsUntaintedIntWithFakeOverride();
+    // :: error: (assignment)
+    uf = sub.returnsUntaintedIntWithFakeOverride();
+  }
+
+  void poly() {
+    FakeOverrideRSuper sup = new FakeOverrideRSuper();
+    FakeOverrideRMid mid = new FakeOverrideRMid();
+
+    @Untainted int j = mid.returnsUntaintedIntWithFakeOverride2();
+    @Untainted int k = sup.returnsPolyTaintedIntWithFakeOverride();
+  }
+}
diff --git a/checker/tests/stubparser-tainting/TypeParamWithInner.astub b/checker/tests/stubparser-tainting/TypeParamWithInner.astub
new file mode 100644
index 0000000..351e928
--- /dev/null
+++ b/checker/tests/stubparser-tainting/TypeParamWithInner.astub
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+// T extends @Untainted String in stub file. Tests bug where the presence of an inner class causes
+// annotations on type variables to be forgotten.
+public class TypeParamWithInner<T extends @Untainted String> {
+    public class Inner {
+    }
+
+    public void requiresUntainted(T param) {
+        @Untainated String s = param;
+    }
+}
diff --git a/checker/tests/stubparser-tainting/TypeParamWithInner.java b/checker/tests/stubparser-tainting/TypeParamWithInner.java
new file mode 100644
index 0000000..e491bc0
--- /dev/null
+++ b/checker/tests/stubparser-tainting/TypeParamWithInner.java
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+// T extends @Untainted String in stub file. Tests bug where the presence of an inner class causes
+// annotations on type variables to be forgotten.
+public class TypeParamWithInner<T extends String> {
+  public class Inner {}
+
+  public void requiresUntainted(T param) {
+    @Untainted String s = param;
+  }
+}
diff --git a/checker/tests/tainting/AnonymousProblem.java b/checker/tests/tainting/AnonymousProblem.java
new file mode 100644
index 0000000..a1b78a7
--- /dev/null
+++ b/checker/tests/tainting/AnonymousProblem.java
@@ -0,0 +1,5 @@
+import java.nio.file.SimpleFileVisitor;
+
+public class AnonymousProblem {
+  SimpleFileVisitor s = new SimpleFileVisitor<String>() {};
+}
diff --git a/checker/tests/tainting/Buffer.java b/checker/tests/tainting/Buffer.java
new file mode 100644
index 0000000..b5b2fd3
--- /dev/null
+++ b/checker/tests/tainting/Buffer.java
@@ -0,0 +1,83 @@
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+@HasQualifierParameter(Tainted.class)
+public class Buffer {
+  final List<@PolyTainted String> list = new ArrayList<>();
+  @PolyTainted String someString = "";
+  // :: error: (invalid.polymorphic.qualifier.use)
+  static @PolyTainted Object staticField;
+
+  public @PolyTainted Buffer() {}
+
+  public @Untainted Buffer(@Tainted String s) {
+    // :: error: (assignment)
+    this.someString = s;
+  }
+
+  public @PolyTainted Buffer(@PolyTainted Buffer copy) {}
+
+  public @PolyTainted Buffer append(@PolyTainted Buffer this, @PolyTainted String s) {
+    list.add(s);
+    someString = s;
+    return this;
+  }
+
+  public @PolyTainted String prettyPrint(@PolyTainted Buffer this) {
+    String prettyString = "";
+    for (String s : list) {
+      prettyString += s + " ~~ ";
+    }
+    return prettyString;
+  }
+
+  public @PolyTainted String unTaintedOnly(@Untainted Buffer this, @PolyTainted String s) {
+    // :: error: (argument)
+    list.add(s);
+    // :: error: (assignment)
+    someString = s;
+    return s;
+  }
+
+  static class Use {
+    void passingUses(@Untainted String untainted, @Untainted Buffer buffer) {
+      buffer.list.add(untainted);
+      buffer.someString = untainted;
+      buffer.append(untainted);
+    }
+
+    void failingUses(@Tainted String tainted, @Untainted Buffer buffer) {
+      // :: error: (argument)
+      buffer.list.add(tainted);
+      // :: error: (assignment)
+      buffer.someString = tainted;
+      // :: error: (argument)
+      buffer.append(tainted);
+    }
+
+    void casts(@Untainted Object untainted, @Tainted Object tainted) {
+      @Untainted Buffer b1 = (@Untainted Buffer) untainted; // ok
+      // :: error: (invariant.cast.unsafe)
+      @Untainted Buffer b2 = (@Untainted Buffer) tainted;
+
+      // :: error: (invariant.cast.unsafe)
+      @Tainted Buffer b3 = (@Tainted Buffer) untainted; // error
+      // :: error: (invariant.cast.unsafe)
+      @Tainted Buffer b4 = (@Tainted Buffer) tainted; // error
+
+      @Untainted Buffer b5 = (Buffer) untainted; // ok
+      // :: error: (invariant.cast.unsafe)
+      @Tainted Buffer b6 = (Buffer) tainted;
+    }
+
+    void creation() {
+      @Untainted Buffer b1 = new @Untainted Buffer();
+      @Tainted Buffer b2 = new @Tainted Buffer();
+      @PolyTainted Buffer b3 = new @PolyTainted Buffer();
+    }
+  }
+}
diff --git a/checker/tests/tainting/Casts.java b/checker/tests/tainting/Casts.java
new file mode 100644
index 0000000..8fd5c99
--- /dev/null
+++ b/checker/tests/tainting/Casts.java
@@ -0,0 +1,70 @@
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+public class Casts {
+  @HasQualifierParameter(Tainted.class)
+  static class Buffer {}
+
+  @HasQualifierParameter(Tainted.class)
+  static class MyBuffer extends Buffer {}
+
+  void test(
+      @Tainted Buffer taintedBuf,
+      @Untainted Buffer untaintedBuf,
+      @Tainted Object taintedObj,
+      @Untainted Object untaintedObj) {
+    @Tainted Object o = (@Tainted Object) taintedBuf;
+    o = (@Tainted Object) untaintedObj;
+    o = (@Tainted Object) untaintedBuf;
+
+    // :: error: (invariant.cast.unsafe)
+    o = (@Tainted Buffer) taintedObj;
+    // :: error: (invariant.cast.unsafe)
+    o = (@Tainted Buffer) untaintedBuf;
+    // :: error: (invariant.cast.unsafe)
+    o = (@Tainted Buffer) untaintedObj;
+
+    // :: warning: (cast.unsafe)
+    o = (@Untainted Object) taintedObj;
+    // :: warning: (cast.unsafe)
+    o = (@Untainted Object) taintedBuf;
+    o = (@Untainted Object) untaintedBuf;
+
+    // :: error: (invariant.cast.unsafe)
+    o = (@Untainted Buffer) taintedObj;
+    // :: error: (invariant.cast.unsafe)
+    o = (@Untainted Buffer) taintedBuf;
+    o = (@Untainted Buffer) untaintedObj;
+  }
+
+  void test2(
+      @Tainted Buffer taintedBuf,
+      @Untainted Buffer untaintedBuf,
+      @Tainted MyBuffer taintedMyBuf,
+      @Untainted MyBuffer untaintedMyBuff) {
+    @Tainted Object o = (@Tainted Buffer) taintedMyBuf;
+    // :: error: (invariant.cast.unsafe)
+    o = (@Tainted Buffer) untaintedBuf;
+    // :: error: (invariant.cast.unsafe)
+    o = (@Tainted Buffer) untaintedMyBuff;
+
+    o = (@Tainted MyBuffer) taintedBuf;
+    // :: error: (invariant.cast.unsafe)
+    o = (@Tainted MyBuffer) untaintedBuf;
+    // :: error: (invariant.cast.unsafe)
+    o = (@Tainted MyBuffer) untaintedMyBuff;
+
+    // :: error: (invariant.cast.unsafe)
+    o = (@Untainted Buffer) taintedMyBuf;
+    // :: error: (invariant.cast.unsafe)
+    o = (@Untainted Buffer) taintedMyBuf;
+    o = (@Untainted Buffer) untaintedMyBuff;
+
+    // :: error: (invariant.cast.unsafe)
+    o = (@Untainted MyBuffer) taintedBuf;
+    // :: error: (invariant.cast.unsafe)
+    o = (@Untainted MyBuffer) taintedMyBuf;
+    o = (@Untainted MyBuffer) untaintedMyBuff;
+  }
+}
diff --git a/checker/tests/tainting/ClassQPTypeVarTest.java b/checker/tests/tainting/ClassQPTypeVarTest.java
new file mode 100644
index 0000000..a2db91d
--- /dev/null
+++ b/checker/tests/tainting/ClassQPTypeVarTest.java
@@ -0,0 +1,37 @@
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+public class ClassQPTypeVarTest {
+  @HasQualifierParameter(Tainted.class)
+  interface Buffer {
+    void append(@PolyTainted String s);
+  }
+
+  <T> @Tainted T cast(T param) {
+    return param;
+  }
+
+  void bug(@Untainted Buffer b, @Tainted String s) {
+    // :: error: (argument)
+    b.append(s);
+    // :: error: (type.argument.hasqualparam)
+    cast(b).append(s);
+  }
+
+  <T extends Buffer> @Tainted T castBuffer(T param) {
+    return param;
+  }
+
+  <T extends @Tainted Buffer> T identity(T param) {
+    @Tainted Buffer b = param;
+    return param; // ok
+  }
+
+  void use(@Untainted Buffer ub, @Tainted Buffer tb) {
+    // :: error: (type.argument)
+    identity(ub);
+    identity(tb); // ok
+  }
+}
diff --git a/checker/tests/tainting/EnumTypeArgs.java b/checker/tests/tainting/EnumTypeArgs.java
new file mode 100644
index 0000000..bd1c46a
--- /dev/null
+++ b/checker/tests/tainting/EnumTypeArgs.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class EnumTypeArgs {
+
+  enum MyEnum {
+    CONST1,
+    CONST2,
+  }
+
+  void method(@Untainted MyEnum e1, @Tainted MyEnum e2) {
+    e1.compareTo(e2);
+  }
+}
diff --git a/checker/tests/tainting/ExtendHasQual.java b/checker/tests/tainting/ExtendHasQual.java
new file mode 100644
index 0000000..501bfe4
--- /dev/null
+++ b/checker/tests/tainting/ExtendHasQual.java
@@ -0,0 +1,41 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+public class ExtendHasQual {
+  static class Super {
+    @SuppressWarnings("super.invocation")
+    @Untainted Super() {}
+  }
+
+  @HasQualifierParameter(Tainted.class)
+  static class Buffer extends Super {}
+
+  static class MyBuffer1 extends Buffer {}
+
+  @HasQualifierParameter(Tainted.class)
+  static class MyBuffer2 extends Buffer {}
+
+  @HasQualifierParameter(Nullable.class)
+  // :: error: (missing.has.qual.param)
+  static class MyBuffer3 extends Buffer {}
+
+  @HasQualifierParameter({Tainted.class, Nullable.class})
+  static class MyBuffer4 extends Buffer {}
+
+  @HasQualifierParameter(Tainted.class)
+  interface BufferInterface {}
+
+  static class ImplementsBufferInterface1 implements BufferInterface {}
+
+  @HasQualifierParameter(Tainted.class)
+  static class ImplementsBufferInterface2 implements BufferInterface {}
+
+  static class Both1 extends Buffer implements BufferInterface {}
+
+  @HasQualifierParameter(Tainted.class)
+  static class Both2 extends Buffer implements BufferInterface {}
+
+  static class Both3 extends Super implements BufferInterface {}
+}
diff --git a/checker/tests/tainting/ExtendsAndAnnotation.java b/checker/tests/tainting/ExtendsAndAnnotation.java
new file mode 100644
index 0000000..f49bfe9
--- /dev/null
+++ b/checker/tests/tainting/ExtendsAndAnnotation.java
@@ -0,0 +1,19 @@
+// Test case for issue 278: https://github.com/typetools/checker-framework/issues/278
+
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+public class ExtendsAndAnnotation extends @Tainted Object {
+  void test(@Untainted ExtendsAndAnnotation c) {
+    // :: warning: (cast.unsafe.constructor.invocation)
+    Object o = new @Untainted ExtendsAndAnnotation();
+    o = new @Tainted ExtendsAndAnnotation();
+  }
+
+  @HasQualifierParameter(Tainted.class)
+  // :: error: (invalid.polymorphic.qualifier)
+  // :: error: (declaration.inconsistent.with.extends.clause)
+  static class Banana extends @PolyTainted Object {}
+}
diff --git a/checker/tests/tainting/GenericsEnclosing.java b/checker/tests/tainting/GenericsEnclosing.java
new file mode 100644
index 0000000..c50b561
--- /dev/null
+++ b/checker/tests/tainting/GenericsEnclosing.java
@@ -0,0 +1,27 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+/**
+ * Resolution of outer classes must take substitution of generic types into account. Thanks to EMS
+ * for finding this problem.
+ *
+ * <p>Also see all-systems/GenericsEnclosing for the type-system independent test.
+ */
+class MyG<X> {
+  X f;
+
+  void m(X p) {}
+}
+
+class ExtMyG extends MyG<@Untainted Object> {
+  class EInner1 {
+    class EInner2 {
+      void bar() {
+        // :: error: (assignment)
+        f = 1;
+        m("test");
+        // :: error: (argument)
+        m(1);
+      }
+    }
+  }
+}
diff --git a/checker/tests/tainting/HasQualParamDefaults.java b/checker/tests/tainting/HasQualParamDefaults.java
new file mode 100644
index 0000000..ffa82b7
--- /dev/null
+++ b/checker/tests/tainting/HasQualParamDefaults.java
@@ -0,0 +1,156 @@
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+public class HasQualParamDefaults {
+  @HasQualifierParameter(Tainted.class)
+  public class Buffer {
+    final List<@PolyTainted String> list = new ArrayList<>();
+    @PolyTainted String someString = "";
+
+    public Buffer() {}
+
+    public @Untainted Buffer(@Tainted String s) {
+      // :: error: (assignment)
+      this.someString = s;
+    }
+
+    public Buffer(Buffer copy) {
+      this.list.addAll(copy.list);
+      this.someString = copy.someString;
+    }
+
+    public Buffer append(@PolyTainted String s) {
+      list.add(s);
+      someString = s;
+      return this;
+    }
+
+    public @PolyTainted String prettyPrint() {
+      String prettyString = list.get(1);
+      for (@PolyTainted String s : list) {
+        prettyString += s + " ~~ ";
+      }
+      return prettyString;
+    }
+
+    public @PolyTainted String unTaintedOnly(@Untainted Buffer this, @PolyTainted String s) {
+      // :: error: (argument)
+      list.add(s);
+      // :: error: (assignment)
+      someString = s;
+      return s;
+    }
+
+    void initializeLocalTainted(@Tainted Buffer b) {
+      Buffer local = b;
+      @Tainted Buffer copy1 = local;
+      // :: error: (assignment)
+      @Untainted Buffer copy2 = local;
+    }
+
+    void initializeLocalUntainted(@Untainted Buffer b) {
+      Buffer local = b;
+      @Untainted Buffer copy1 = local;
+      // :: error: (assignment)
+      @Tainted Buffer copy2 = local;
+    }
+
+    void initializeLocalPolyTainted(@PolyTainted Buffer b) {
+      Buffer local = b;
+      @PolyTainted Buffer copy = local;
+    }
+
+    void noInitializer(@Untainted Buffer b) {
+      Buffer local;
+      // :: error: (assignment)
+      local = b;
+    }
+  }
+
+  class Use {
+    void passingUses(@Untainted String untainted, @Untainted Buffer buffer) {
+      buffer.list.add(untainted);
+      buffer.someString = untainted;
+      buffer.append(untainted);
+    }
+
+    void failingUses(@Tainted String tainted, @Untainted Buffer buffer) {
+      // :: error: (argument)
+      buffer.list.add(tainted);
+      // :: error: (assignment)
+      buffer.someString = tainted;
+      // :: error: (argument)
+      buffer.append(tainted);
+    }
+
+    void casts(@Untainted Object untainted, @Tainted Object tainted) {
+      @Untainted Buffer b1 = (@Untainted Buffer) untainted; // ok
+      // :: error: (invariant.cast.unsafe)
+      @Untainted Buffer b2 = (@Untainted Buffer) tainted;
+
+      // :: error: (invariant.cast.unsafe)
+      @Tainted Buffer b3 = (@Tainted Buffer) untainted; // error
+      // :: error: (invariant.cast.unsafe)
+      @Tainted Buffer b4 = (@Tainted Buffer) tainted; // error
+
+      @Untainted Buffer b5 = (Buffer) untainted; // ok
+      // :: error: (invariant.cast.unsafe)
+      @Tainted Buffer b6 = (Buffer) tainted;
+    }
+
+    void creation() {
+      @Untainted Buffer b1 = new @Untainted Buffer();
+      @Tainted Buffer b2 = new @Tainted Buffer();
+      @PolyTainted Buffer b3 = new @PolyTainted Buffer();
+    }
+  }
+
+  // For classes with @HasQualifierParameter, different defaulting rules are applied on that type
+  // inside the class body and outside the class body, so local variables need to be tested
+  // outside the class as well.
+  class LocalVars {
+    void initializeLocalTainted(@Tainted Buffer b) {
+      Buffer local = b;
+      @Tainted Buffer copy1 = local;
+      // :: error: (assignment)
+      @Untainted Buffer copy2 = local;
+    }
+
+    void initializeLocalUntainted(@Untainted Buffer b) {
+      Buffer local = b;
+      @Untainted Buffer copy1 = local;
+      // :: error: (assignment)
+      @Tainted Buffer copy2 = local;
+    }
+
+    void initializeLocalPolyTainted(@PolyTainted Buffer b) {
+      Buffer local = b;
+      @PolyTainted Buffer copy = local;
+    }
+
+    void noInitializer(@Untainted Buffer b) {
+      Buffer local;
+      // :: error: (assignment)
+      local = b;
+    }
+
+    // These next two cases test circular dependencies. Calculating the type of a local variable
+    // looks at the type of initializer, but if the type of the initializer depends on the type
+    // of the variable, then infinite recursion could occur.
+
+    void testTypeVariableInference() {
+      GenericWithQualParam<String> set = new GenericWithQualParam<>();
+    }
+
+    void testVariableInOwnInitializer() {
+      Buffer b = (b = null);
+    }
+  }
+
+  @HasQualifierParameter(Tainted.class)
+  static class GenericWithQualParam<T> {}
+}
diff --git a/checker/tests/tainting/InheritQualifierParameter.java b/checker/tests/tainting/InheritQualifierParameter.java
new file mode 100644
index 0000000..4f4dbb2
--- /dev/null
+++ b/checker/tests/tainting/InheritQualifierParameter.java
@@ -0,0 +1,31 @@
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+import org.checkerframework.framework.qual.NoQualifierParameter;
+
+@HasQualifierParameter(Tainted.class)
+public class InheritQualifierParameter {}
+
+class SubHasQualifierParameter extends InheritQualifierParameter {
+  void test(@Untainted SubHasQualifierParameter arg) {
+    // :: error: (assignment)
+    @Tainted SubHasQualifierParameter local = arg;
+  }
+}
+
+@NoQualifierParameter(Tainted.class)
+// :: error: (conflicting.qual.param)
+class SubHasQualifierParameter1 extends InheritQualifierParameter {}
+
+@NoQualifierParameter(Tainted.class)
+class InheritNoQualifierParameter {}
+
+class SubNoQualifierParameter extends InheritNoQualifierParameter {
+  void test(@Untainted SubNoQualifierParameter arg) {
+    @Tainted SubNoQualifierParameter local = arg;
+  }
+}
+
+@HasQualifierParameter(Tainted.class)
+// :: error: (conflicting.qual.param)
+@Tainted class SubNoQualifierParameter1 extends InheritNoQualifierParameter {}
diff --git a/checker/tests/tainting/InitializerDataflow.java b/checker/tests/tainting/InitializerDataflow.java
new file mode 100644
index 0000000..7e3afe0
--- /dev/null
+++ b/checker/tests/tainting/InitializerDataflow.java
@@ -0,0 +1,23 @@
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+public class InitializerDataflow {
+  @HasQualifierParameter(Tainted.class)
+  static class Buffer {}
+
+  @PolyTainted Buffer id(@PolyTainted String s) {
+    return null;
+  }
+
+  void methodBuffer(@Untainted String s) {
+    Buffer b1 = id(s);
+
+    String local = s;
+    Buffer b2 = id(local);
+
+    @Untainted String local2 = s;
+    Buffer b3 = id(local2);
+  }
+}
diff --git a/checker/tests/tainting/InnerHasQualifierParameter.java b/checker/tests/tainting/InnerHasQualifierParameter.java
new file mode 100644
index 0000000..34af014
--- /dev/null
+++ b/checker/tests/tainting/InnerHasQualifierParameter.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+@HasQualifierParameter(Tainted.class)
+public class InnerHasQualifierParameter {
+
+  @HasQualifierParameter(Tainted.class)
+  interface TestInterface {
+    public void testMethod();
+  }
+
+  public void test() {
+    TestInterface test =
+        new TestInterface() {
+          public void testMethod() {}
+        };
+  }
+}
diff --git a/checker/tests/tainting/Issue1111.java b/checker/tests/tainting/Issue1111.java
new file mode 100644
index 0000000..3135870
--- /dev/null
+++ b/checker/tests/tainting/Issue1111.java
@@ -0,0 +1,21 @@
+// Test case for Issue1111
+// https://github.com/typetools/checker-framework/issues/1111
+// Additional test case in framework/tests/all-systems/Issue1111.java
+
+import java.util.List;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class Issue1111 {
+  void foo(Box<? super Integer> box, List<Integer> list) {
+    bar(box, list);
+  }
+
+  void foo2(Box<@Untainted ? super Integer> box, List<Integer> list) {
+    // :: error: (argument)
+    bar(box, list);
+  }
+
+  <T extends Number> void bar(Box<T> box, Iterable<? extends T> list) {}
+
+  class Box<T extends Number> {}
+}
diff --git a/checker/tests/tainting/Issue1705.java b/checker/tests/tainting/Issue1705.java
new file mode 100644
index 0000000..351fbc3
--- /dev/null
+++ b/checker/tests/tainting/Issue1705.java
@@ -0,0 +1,26 @@
+// Test case for Issue 1705
+// https://github.com/typetools/checker-framework/issues/1705
+
+import java.util.function.Function;
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class Issue1705 {
+  static class MySecondClass<X> {
+    @PolyTainted MySecondClass<X> doOnComplete(@PolyTainted MySecondClass<X> this) {
+      throw new RuntimeException();
+    }
+  }
+
+  <R> @PolyTainted R to(@PolyTainted Issue1705 this, Function<? super Issue1705, R> arg0) {
+    throw new RuntimeException();
+  }
+
+  static <T> Function<? super T, MySecondClass<T>> empty() {
+    throw new RuntimeException();
+  }
+
+  void test(@Untainted Issue1705 a) {
+    @Untainted Object z = a.to(empty()).doOnComplete();
+  }
+}
diff --git a/checker/tests/tainting/Issue1942.java b/checker/tests/tainting/Issue1942.java
new file mode 100644
index 0000000..8b87479
--- /dev/null
+++ b/checker/tests/tainting/Issue1942.java
@@ -0,0 +1,18 @@
+import java.util.List;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class Issue1942 {
+  public interface LoadableExpression<EXPRESSION> {}
+
+  abstract static class OperatorSection<A extends LoadableExpression<A>> {
+    abstract A makeExpression(List<@Untainted A> expressions);
+  }
+
+  static class BinaryOperatorSection<B extends LoadableExpression<B>> extends OperatorSection<B> {
+    @Override
+    // Override used to fail.
+    B makeExpression(List<@Untainted B> expressions) {
+      throw new RuntimeException("");
+    }
+  }
+}
diff --git a/checker/tests/tainting/Issue2107.java b/checker/tests/tainting/Issue2107.java
new file mode 100644
index 0000000..6dc0f1f
--- /dev/null
+++ b/checker/tests/tainting/Issue2107.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+
+public abstract class Issue2107 {
+
+  abstract @PolyTainted int method(@PolyTainted Issue2107 this);
+
+  @PolyTainted int method2(@PolyTainted Issue2107 this) {
+    return this.method();
+  }
+
+  @PolyTainted int method3(@PolyTainted Issue2107 this) {
+    return method();
+  }
+}
diff --git a/checker/tests/tainting/Issue2156.java b/checker/tests/tainting/Issue2156.java
new file mode 100644
index 0000000..30396b6
--- /dev/null
+++ b/checker/tests/tainting/Issue2156.java
@@ -0,0 +1,22 @@
+// Test case for issue #2156:
+// https://github.com/typetools/checker-framework/issues/2156
+
+// @skip-test until the bug is fixed
+
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+enum SampleEnum {
+  @Untainted FIRST,
+  @Tainted SECOND;
+}
+
+public class Issue2156 {
+  void test() {
+    requireUntainted(SampleEnum.FIRST);
+    // :: error: assignment
+    requireUntainted(SampleEnum.SECOND);
+  }
+
+  void requireUntainted(@Untainted SampleEnum sEnum) {}
+}
diff --git a/checker/tests/tainting/Issue2159.java b/checker/tests/tainting/Issue2159.java
new file mode 100644
index 0000000..915d7fe
--- /dev/null
+++ b/checker/tests/tainting/Issue2159.java
@@ -0,0 +1,31 @@
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class Issue2159 {
+  @Tainted Issue2159() {}
+
+  static class MyClass extends Issue2159 {
+    MyClass() {}
+
+    // :: error: (super.invocation)
+    @PolyTainted MyClass(@PolyTainted Object x) {}
+
+    void testPolyTaintedLocal(
+        @PolyTainted Object input, @Untainted Object untainted, @Tainted Object tainted) {
+      // :: warning: (cast.unsafe)
+      @PolyTainted Object local = (@PolyTainted MyClass) new MyClass();
+      // :: warning: (cast.unsafe.constructor.invocation)
+      @PolyTainted Object local1 = new @PolyTainted MyClass();
+      // :: warning: (cast.unsafe.constructor.invocation)
+      @Untainted Object local2 = new @Untainted MyClass();
+
+      @PolyTainted Object local3 = new @PolyTainted MyClass(input);
+      // :: warning: (cast.unsafe.constructor.invocation)
+      @Untainted Object local4 = new @Untainted MyClass(input);
+      // :: warning: (cast.unsafe.constructor.invocation)
+      @PolyTainted Object local5 = new @PolyTainted MyClass(tainted);
+      @Untainted Object local6 = new @Untainted MyClass(untainted);
+    }
+  }
+}
diff --git a/checker/tests/tainting/Issue2243.java b/checker/tests/tainting/Issue2243.java
new file mode 100644
index 0000000..f1de2bd
--- /dev/null
+++ b/checker/tests/tainting/Issue2243.java
@@ -0,0 +1,32 @@
+// Test cases for issue 2243
+// https://github.com/typetools/checker-framework/issues/2243
+
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+// :: error: (declaration.inconsistent.with.extends.clause)
+public @Tainted class Issue2243 extends Y2243 {}
+
+// :: error: (declaration.inconsistent.with.extends.clause)
+class ExtendsSubTypingExplicit2243 extends @Untainted X2243 {}
+
+class X2243 {}
+
+class MyClass2243 {
+  @Tainted MyClass2243() {}
+}
+
+@Untainted class Y2243 extends MyClass2243 {
+  // :: error: (super.invocation)
+  @Untainted Y2243() {}
+}
+
+@Untainted interface SuperClass2243 {}
+
+// :: error: (declaration.inconsistent.with.implements.clause)
+@Tainted class Z2243 implements SuperClass2243 {}
+
+class Issue2243Test {
+  @Untainted ExtendsSubTypingExplicit2243 field;
+  @Tainted ExtendsSubTypingExplicit2243 field2;
+}
diff --git a/checker/tests/tainting/Issue2330.java b/checker/tests/tainting/Issue2330.java
new file mode 100644
index 0000000..c796c81
--- /dev/null
+++ b/checker/tests/tainting/Issue2330.java
@@ -0,0 +1,18 @@
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class Issue2330<T extends @Tainted Object> {
+  // Checker can't verify that this creates an untainted Issue2330
+  @SuppressWarnings("tainting")
+  public @Untainted Issue2330(@PolyTainted int i) {}
+
+  // Checker can't verify that this creates an untainted Issue2330
+  @SuppressWarnings("tainting")
+  public @Untainted Issue2330() {}
+
+  public static void f(@PolyTainted int i) {
+    new @Untainted Issue2330<@PolyTainted Integer>(i);
+    new @Untainted Issue2330<@PolyTainted Integer>();
+  }
+}
diff --git a/checker/tests/tainting/Issue3033.java b/checker/tests/tainting/Issue3033.java
new file mode 100644
index 0000000..fe3b4ff
--- /dev/null
+++ b/checker/tests/tainting/Issue3033.java
@@ -0,0 +1,20 @@
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class Issue3033 {
+
+  void main() {
+    @Tainted String a = getTainted();
+    // :: warning: (instanceof.unsafe)
+    if (a instanceof @Untainted String) {
+      // `a` is now refined to @Untainted String
+      isUntainted(a);
+    }
+  }
+
+  static void isUntainted(@Untainted String a) {}
+
+  static @Tainted String getTainted() {
+    return "hi";
+  }
+}
diff --git a/checker/tests/tainting/Issue352.java b/checker/tests/tainting/Issue352.java
new file mode 100644
index 0000000..5132e2c
--- /dev/null
+++ b/checker/tests/tainting/Issue352.java
@@ -0,0 +1,12 @@
+// Test case for Issue 352:
+// https://github.com/typetools/checker-framework/issues/352
+
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class Issue352 {
+  class Nested {
+    @Untainted Issue352 context(@Untainted Issue352.@Untainted Nested this) {
+      return Issue352.this;
+    }
+  }
+}
diff --git a/checker/tests/tainting/Issue3561.java b/checker/tests/tainting/Issue3561.java
new file mode 100644
index 0000000..26993dd
--- /dev/null
+++ b/checker/tests/tainting/Issue3561.java
@@ -0,0 +1,16 @@
+import org.checkerframework.checker.tainting.qual.*;
+
+public class Issue3561 {
+  void outerMethod(@Untainted Issue3561 this) {}
+
+  class Inner {
+    void innerMethod(@Untainted Issue3561.@Untainted Inner this) {
+      Issue3561.this.outerMethod();
+    }
+
+    void innerMethod2(@Tainted Issue3561.@Untainted Inner this) {
+      // :: error: (method.invocation)
+      Issue3561.this.outerMethod();
+    }
+  }
+}
diff --git a/checker/tests/tainting/Issue3562.java b/checker/tests/tainting/Issue3562.java
new file mode 100644
index 0000000..857bb28
--- /dev/null
+++ b/checker/tests/tainting/Issue3562.java
@@ -0,0 +1,8 @@
+import org.checkerframework.checker.tainting.qual.*;
+
+public class Issue3562 {
+  // This used to issue conflicting.annos
+  @Tainted Issue3562.@Untainted Inner field;
+
+  class Inner {}
+}
diff --git a/checker/tests/tainting/Issue3776.java b/checker/tests/tainting/Issue3776.java
new file mode 100644
index 0000000..f63d9c1
--- /dev/null
+++ b/checker/tests/tainting/Issue3776.java
@@ -0,0 +1,45 @@
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class Issue3776 {
+  class MyInnerClass {
+    public MyInnerClass() {}
+
+    public MyInnerClass(@Untainted String s) {}
+
+    public MyInnerClass(int... i) {}
+  }
+
+  static class MyClass {
+    public MyClass() {}
+
+    public MyClass(@Untainted String s) {}
+
+    public MyClass(int... i) {}
+  }
+
+  void test(Issue3776 outer, @Tainted String tainted) {
+    new MyInnerClass("1") {};
+    this.new MyInnerClass("2") {};
+    new MyClass() {};
+    // :: error: (argument)
+    new MyClass(tainted) {};
+    new MyClass(1, 2, 3) {};
+    new MyClass(1) {};
+    new MyInnerClass() {};
+    // :: error: (argument)
+    new MyInnerClass(tainted) {};
+    new MyInnerClass(1) {};
+    new MyInnerClass(1, 2, 3) {};
+    this.new MyInnerClass() {};
+    // :: error: (argument)
+    this.new MyInnerClass(tainted) {};
+    this.new MyInnerClass(1) {};
+    this.new MyInnerClass(1, 2, 3) {};
+    outer.new MyInnerClass() {};
+    // :: error: (argument)
+    outer.new MyInnerClass(tainted) {};
+    outer.new MyInnerClass(1) {};
+    outer.new MyInnerClass(1, 2, 3) {};
+  }
+}
diff --git a/checker/tests/tainting/NestedTypeConstructor.java b/checker/tests/tainting/NestedTypeConstructor.java
new file mode 100644
index 0000000..641d40a
--- /dev/null
+++ b/checker/tests/tainting/NestedTypeConstructor.java
@@ -0,0 +1,10 @@
+import org.checkerframework.checker.tainting.qual.Tainted;
+
+// Test case for Issue 275
+// https://github.com/typetools/checker-framework/issues/275
+// Not tainting-specific, but a convenient location.
+public class NestedTypeConstructor {
+  class Inner {
+    @Tainted Inner() {}
+  }
+}
diff --git a/checker/tests/tainting/ObjectCreation.java b/checker/tests/tainting/ObjectCreation.java
new file mode 100644
index 0000000..b335a25
--- /dev/null
+++ b/checker/tests/tainting/ObjectCreation.java
@@ -0,0 +1,51 @@
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+public class ObjectCreation {
+
+  @HasQualifierParameter(Tainted.class)
+  static class Buffer { // Which constructors and super calls are legal?
+    @PolyTainted Buffer() {
+      super(); // ok this creates an @Untainted object
+    }
+
+    @Untainted Buffer(int p) {
+      super(); // ok, super is untainted and creating an untainted buffer
+    }
+
+    @Tainted Buffer(String s) {
+      super(); // ok, super is not @HasQualifierParameter @Tainted Object >: the type of
+      // super.
+    }
+  }
+
+  @HasQualifierParameter(Tainted.class)
+  static class MyBuffer extends Buffer {
+    @PolyTainted MyBuffer() {
+      super(); // ok, if super is @PolyTainted.
+    }
+
+    @Untainted MyBuffer(int p) {
+      super(p);
+    }
+
+    @Tainted MyBuffer(String s) {
+      super(s);
+    }
+
+    @PolyTainted MyBuffer(Object o) {
+      // :: error: (super.invocation)
+      super("");
+    }
+
+    @Untainted MyBuffer(Object o, int p) {
+      super();
+    }
+
+    @Tainted MyBuffer(Object o, String s) {
+      super();
+    }
+  }
+}
diff --git a/checker/tests/tainting/PolyClassDecl.java b/checker/tests/tainting/PolyClassDecl.java
new file mode 100644
index 0000000..333dff3
--- /dev/null
+++ b/checker/tests/tainting/PolyClassDecl.java
@@ -0,0 +1,28 @@
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+
+public class PolyClassDecl {
+  // :: error: (invalid.polymorphic.qualifier)
+  @PolyTainted static class Class1 {}
+  // :: error: (invalid.polymorphic.qualifier)
+  static class Class2<@PolyTainted T> {}
+  // :: error: (invalid.polymorphic.qualifier)
+  abstract static class Class3<T extends List<@PolyTainted String>> {}
+  // :: error: (invalid.polymorphic.qualifier)
+  interface Class4 extends List<@PolyTainted String> {}
+  // :: error: (invalid.polymorphic.qualifier)
+  // :: error: (declaration.inconsistent.with.implements.clause)
+  interface Class5 extends @PolyTainted List<String> {}
+  // :: error: (invalid.polymorphic.qualifier)
+  abstract static class Class6 implements List<@PolyTainted String> {}
+
+  void method() {
+    ArrayList<@PolyTainted String> s = new ArrayList<@PolyTainted String>() {};
+  }
+
+  // :: error: (invalid.polymorphic.qualifier)
+  <@PolyTainted T> T identity(T arg) {
+    return arg;
+  }
+}
diff --git a/checker/tests/tainting/PolyConstructor.java b/checker/tests/tainting/PolyConstructor.java
new file mode 100644
index 0000000..ccc4a19
--- /dev/null
+++ b/checker/tests/tainting/PolyConstructor.java
@@ -0,0 +1,27 @@
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+@HasQualifierParameter(Tainted.class)
+public class PolyConstructor {
+
+  @PolyTainted PolyConstructor() {}
+
+  @PolyTainted PolyConstructor(@PolyTainted Object o) {}
+
+  static void uses(@Tainted Object tainted, @Untainted Object untainted) {
+    @Untainted PolyConstructor o1 = new @Untainted PolyConstructor();
+    @Tainted PolyConstructor o2 = new @Tainted PolyConstructor();
+    @PolyTainted PolyConstructor o3 = new @PolyTainted PolyConstructor();
+
+    // :: error: (assignment)
+    @Untainted PolyConstructor o4 = new @Tainted PolyConstructor(untainted);
+    @Untainted PolyConstructor o5 = new PolyConstructor(untainted);
+
+    // This currently isn't supported, but could be in the future.
+    @Untainted PolyConstructor o6 = new PolyConstructor();
+    // :: error: (assignment)
+    @Tainted PolyConstructor o7 = new PolyConstructor();
+  }
+}
diff --git a/checker/tests/tainting/PolyReceivers.java b/checker/tests/tainting/PolyReceivers.java
new file mode 100644
index 0000000..162daca
--- /dev/null
+++ b/checker/tests/tainting/PolyReceivers.java
@@ -0,0 +1,43 @@
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+
+public class PolyReceivers {
+
+  static class MyClass {
+    public void start(@PolyTainted MyClass this) {}
+  }
+
+  PolyReceivers(int i, Runnable... runnables) {}
+
+  PolyReceivers(Consumer<String> consumer) {
+    consumer.consume("hello"); // Use lambda as a constructor argument
+  }
+
+  interface Top {
+    public void consume(String s);
+  }
+
+  interface Sub extends Top {
+    public default void otherMethod() {}
+  }
+
+  interface Consumer<T> {
+    void consume(T t);
+  }
+
+  void varargs(Runnable... runnables) {}
+
+  public static void consumeStr(String str) {}
+
+  public static void consumeStr2(String str) {}
+
+  <E extends Consumer<String>> void context(E e, Sub s) {
+    new PolyReceivers(PolyReceivers::consumeStr);
+
+    Consumer<String> cs1 = (false) ? PolyReceivers::consumeStr2 : PolyReceivers::consumeStr;
+    Consumer<String> cs2 = (false) ? e : PolyReceivers::consumeStr;
+    Top t = (false) ? s : PolyReceivers::consumeStr;
+
+    new PolyReceivers(42, new MyClass()::start); // Use lambda as a constructor argument
+    varargs(new MyClass()::start, new MyClass()::start); // Use lambda in a var arg list of method
+  }
+}
diff --git a/checker/tests/tainting/PolyReturn.java b/checker/tests/tainting/PolyReturn.java
new file mode 100644
index 0000000..038c775
--- /dev/null
+++ b/checker/tests/tainting/PolyReturn.java
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+@SuppressWarnings({"inconsistent.constructor.type", "super.invocation"}) // ignore these warnings
+public class PolyReturn {
+  @PolyTainted PolyReturn() {}
+
+  @PolyTainted PolyReturn method() {
+    return new PolyReturn();
+  }
+
+  void use() {
+    @Untainted PolyReturn untainted = new PolyReturn();
+    @Untainted PolyReturn untainted2 = new @Untainted PolyReturn();
+
+    @Untainted PolyReturn untainted3 = method();
+
+    @Tainted PolyReturn tainted = new PolyReturn();
+    @Tainted PolyReturn tainted2 = new @Tainted PolyReturn();
+
+    @Tainted PolyReturn tainted3 = method();
+  }
+}
diff --git a/checker/tests/tainting/Refine.java b/checker/tests/tainting/Refine.java
new file mode 100644
index 0000000..afc1c49
--- /dev/null
+++ b/checker/tests/tainting/Refine.java
@@ -0,0 +1,32 @@
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+@HasQualifierParameter(Tainted.class)
+public class Refine {
+  void method(@Tainted Refine tainted, @Untainted Refine untainted) {
+    // :: error: (assignment)
+    @Tainted Refine local = untainted;
+    // :: error: (assignment)
+    @Untainted Refine untaintedLocal = local;
+    @Untainted Refine untaintedLocal2 = untaintedLocal;
+  }
+
+  void methodNull() {
+    @Tainted Refine local = null;
+    @Untainted Refine untaintedLocal = local;
+  }
+
+  public static class SuperClass {
+    @Untainted SuperClass() {}
+  }
+
+  @HasQualifierParameter(Tainted.class)
+  public static class SubClass extends SuperClass {}
+
+  static void method2(@Untainted SubClass subClass) {
+    @Untainted SuperClass untainted1 = subClass;
+    @Tainted SuperClass superClass = subClass;
+    @Untainted SuperClass untainted2 = superClass;
+  }
+}
diff --git a/checker/tests/tainting/SimplePrims.java b/checker/tests/tainting/SimplePrims.java
new file mode 100644
index 0000000..4d0e207
--- /dev/null
+++ b/checker/tests/tainting/SimplePrims.java
@@ -0,0 +1,50 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class SimplePrims {
+
+  void execute(@Untainted int s) {}
+
+  void tainted(int s) {}
+
+  void intLiteral() {
+    // :: error: (argument)
+    execute(5);
+    tainted(6);
+  }
+
+  void intRef(int ref) {
+    // :: error: (argument)
+    execute(ref);
+    tainted(ref);
+  }
+
+  void untaintedRef(@Untainted int ref) {
+    execute(ref);
+    tainted(ref);
+  }
+
+  void concatenation(@Untainted int s1, int s2) {
+    execute(s1 + s1);
+    execute(s1 += s1);
+    // :: error: (argument)
+    execute(s1 + 3);
+
+    // :: error: (argument)
+    execute(s1 + s2);
+
+    // :: error: (argument)
+    execute(s2 + s1);
+    // :: error: (argument)
+    execute(s2 + 4);
+    // :: error: (argument)
+    execute(s2 + s2);
+
+    tainted(s1 + s1);
+    tainted(s1 + 7);
+    tainted(s1 + s2);
+
+    tainted(s2 + s1);
+    tainted(s2 + 8);
+    tainted(s2 + s2);
+  }
+}
diff --git a/checker/tests/tainting/SimpleTainting.java b/checker/tests/tainting/SimpleTainting.java
new file mode 100644
index 0000000..6fc2794
--- /dev/null
+++ b/checker/tests/tainting/SimpleTainting.java
@@ -0,0 +1,47 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class SimpleTainting {
+
+  void execute(@Untainted String s) {}
+
+  void tainted(String s) {}
+
+  void stringLiteral() {
+    execute("ldskjfldj");
+    tainted("lksjdflkjdf");
+  }
+
+  void stringRef(String ref) {
+    // :: error: (argument)
+    execute(ref); // error
+    tainted(ref);
+  }
+
+  void untaintedRef(@Untainted String ref) {
+    execute(ref);
+    tainted(ref);
+  }
+
+  void concatenation(@Untainted String s1, String s2) {
+    execute(s1 + s1);
+    execute(s1 += s1);
+    execute(s1 + "m");
+    // :: error: (argument)
+    execute(s1 + s2); // error
+
+    // :: error: (argument)
+    execute(s2 + s1); // error
+    // :: error: (argument)
+    execute(s2 + "m"); // error
+    // :: error: (argument)
+    execute(s2 + s2); // error
+
+    tainted(s1 + s1);
+    tainted(s1 + "m");
+    tainted(s1 + s2);
+
+    tainted(s2 + s1);
+    tainted(s2 + "m");
+    tainted(s2 + s2);
+  }
+}
diff --git a/checker/tests/tainting/SubClassHasQP.java b/checker/tests/tainting/SubClassHasQP.java
new file mode 100644
index 0000000..7726375
--- /dev/null
+++ b/checker/tests/tainting/SubClassHasQP.java
@@ -0,0 +1,49 @@
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+// @skip-test https://github.com/typetools/checker-framework/issues/3400
+public class SubClassHasQP {
+  @HasQualifierParameter(Tainted.class)
+  static class Buffer {
+    void append(@PolyTainted Buffer this, @PolyTainted String s) {}
+
+    void append2(@PolyTainted Buffer this, @PolyTainted String s) {}
+  }
+
+  @HasQualifierParameter(Tainted.class)
+  static @Untainted class UntaintedBuffer extends @Untainted Buffer {
+    @Override
+    // :: error: (annotations.on.use)
+    void append(@Tainted UntaintedBuffer this, @Tainted String s) {}
+
+    @Override
+    void append2(@Untainted UntaintedBuffer this, @Untainted String s) {}
+  }
+
+  @HasQualifierParameter(Tainted.class)
+  static @Tainted class TaintedBuffer extends @Tainted Buffer {
+    @Override
+    void append(@Tainted TaintedBuffer this, @Tainted String s) {} // legal override
+
+    @Override
+    void append2(@Untainted TaintedBuffer this, String s) {
+      @Untainted Buffer that = this;
+    }
+  }
+
+  @HasQualifierParameter(Tainted.class)
+  // :: error: (super.invocation)
+  static class MyTaintedBuffer extends TaintedBuffer {
+    @Override
+    // :: error: (override.receiver)
+    void append(MyTaintedBuffer this, String s) {} // legal override
+  }
+
+  @HasQualifierParameter(Tainted.class)
+  @Tainted class MyTaintedBuffer2 extends TaintedBuffer {
+    @Override
+    void append(@Tainted MyTaintedBuffer2 this, String s) {} // legal override
+  }
+}
diff --git a/checker/tests/tainting/TaintedIntersections.java b/checker/tests/tainting/TaintedIntersections.java
new file mode 100644
index 0000000..c16b90c
--- /dev/null
+++ b/checker/tests/tainting/TaintedIntersections.java
@@ -0,0 +1,49 @@
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class TaintedIntersections {
+  interface MyInterface {}
+
+  void test1() {
+    // null is @Untainted
+    @Untainted Object o1 = (@Untainted Object & @Untainted MyInterface) null;
+    // :: warning: (explicit.annotation.ignored)
+    @Untainted Object o2 = (@Untainted Object & @Tainted MyInterface) null;
+    // :: error: (assignment) :: warning: (explicit.annotation.ignored)
+    @Untainted Object o3 = (@Tainted Object & @Untainted MyInterface) null;
+    // :: error: (assignment)
+    @Untainted Object o4 = (@Tainted Object & @Tainted MyInterface) null;
+  }
+
+  void test2() {
+    // null is @Untainted
+    @Untainted Object o1 = (@Untainted Object & MyInterface) null;
+    @Untainted Object o3 = (Object & @Untainted MyInterface) null;
+    // :: error: (assignment)
+    @Untainted Object o2 = (Object & @Tainted MyInterface) null;
+    // :: error: (assignment)
+    @Untainted Object o4 = (@Tainted Object & MyInterface) null;
+  }
+
+  void test3(@Tainted MyInterface i) {
+    // :: warning: (cast.unsafe)
+    @Untainted Object o1 = (@Untainted Object & @Untainted MyInterface) i;
+    // :: warning: (explicit.annotation.ignored) :: warning: (cast.unsafe)
+    @Untainted Object o2 = (@Untainted Object & @Tainted MyInterface) i;
+    // :: error: (assignment) :: warning: (explicit.annotation.ignored)
+    @Untainted Object o3 = (@Tainted Object & @Untainted MyInterface) i;
+    // :: error: (assignment)
+    @Untainted Object o4 = (@Tainted Object & @Tainted MyInterface) i;
+  }
+
+  void test4(@Tainted MyInterface i) {
+    // :: warning: (cast.unsafe)
+    @Untainted Object o1 = (@Untainted Object & MyInterface) i;
+    // :: warning: (cast.unsafe)
+    @Untainted Object o3 = (Object & @Untainted MyInterface) i;
+    // :: error: (assignment)
+    @Untainted Object o2 = (Object & @Tainted MyInterface) i;
+    // :: error: (assignment)
+    @Untainted Object o4 = (@Tainted Object & MyInterface) i;
+  }
+}
diff --git a/checker/tests/tainting/TaintingDiamondInference.java b/checker/tests/tainting/TaintingDiamondInference.java
new file mode 100644
index 0000000..a232435
--- /dev/null
+++ b/checker/tests/tainting/TaintingDiamondInference.java
@@ -0,0 +1,17 @@
+// Test case for issue #660: https://github.com/typetools/checker-framework/issues/660
+
+import java.util.Set;
+import java.util.TreeSet;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class TaintingDiamondInference {
+
+  private @Untainted Set<@Untainted String> s;
+
+  public TaintingDiamondInference() {
+    // :: warning: (cast.unsafe.constructor.invocation)
+    s = new @Untainted TreeSet<>();
+    // :: warning: (cast.unsafe.constructor.invocation)
+    s = new @Untainted TreeSet<@Untainted String>();
+  }
+}
diff --git a/checker/tests/tainting/TaintingPolyFields.java b/checker/tests/tainting/TaintingPolyFields.java
new file mode 100644
index 0000000..e7c69ce
--- /dev/null
+++ b/checker/tests/tainting/TaintingPolyFields.java
@@ -0,0 +1,49 @@
+import java.util.List;
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class TaintingPolyFields {
+  // :: error: (invalid.polymorphic.qualifier.use)
+  @PolyTainted Integer x;
+  // :: error: (invalid.polymorphic.qualifier.use)
+  @PolyTainted List<@PolyTainted String> lst;
+  // :: error: (invalid.polymorphic.qualifier.use)
+  @PolyTainted String @PolyTainted [] str;
+  // :: error: (invalid.polymorphic.qualifier.use)
+  List<@PolyTainted String> lst1;
+  // :: error: (invalid.polymorphic.qualifier.use)
+  @PolyTainted String[] str1;
+  // :: error: (invalid.polymorphic.qualifier.use)
+  @PolyTainted List<String> lst2;
+  // :: error: (invalid.polymorphic.qualifier.use)
+  String @PolyTainted [] str2;
+  // :: error: (invalid.polymorphic.qualifier.use)
+  @PolyTainted int z;
+
+  // Access of poly fields outside of the declaring class.
+  static void test() {
+    @Tainted TaintingPolyFields obj = new @Tainted TaintingPolyFields();
+    // :: error: (assignment)
+    @Untainted Integer myX = obj.x;
+    // :: error: (assignment)
+    @Untainted List<@Untainted String> myLst = obj.lst;
+    // :: error: (assignment)
+    @Untainted String @Untainted [] myStr = obj.str;
+
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Untainted TaintingPolyFields obj1 = new @Untainted TaintingPolyFields();
+    @Untainted Integer myX1 = obj1.x;
+    TaintingPolyFields obj2 = new TaintingPolyFields();
+    // :: error: (assignment)
+    @Untainted List<@Untainted String> myLst2 = obj2.lst;
+  }
+
+  static void polyTest(@PolyTainted TaintingPolyFields o) {
+    @PolyTainted Integer f = o.x;
+  }
+}
+
+class TypeParam<T> {
+  T field;
+}
diff --git a/checker/tests/tainting/TestFieldPolymorphism.java b/checker/tests/tainting/TestFieldPolymorphism.java
new file mode 100644
index 0000000..4584ce1
--- /dev/null
+++ b/checker/tests/tainting/TestFieldPolymorphism.java
@@ -0,0 +1,58 @@
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+
+@HasQualifierParameter(Tainted.class)
+public class TestFieldPolymorphism {
+  @PolyTainted String field;
+
+  @PolyTainted TestFieldPolymorphism(@PolyTainted String s) {
+    this.field = s;
+  }
+
+  @PolyTainted TestFieldPolymorphism testConstructor(@PolyTainted String s) {
+    return new TestFieldPolymorphism(s);
+  }
+
+  void testSetter1(@PolyTainted TestFieldPolymorphism this, @PolyTainted String s) {
+    this.field = s;
+  }
+
+  void testSetter2(@PolyTainted TestFieldPolymorphism this, @Untainted String s) {
+    this.field = s;
+  }
+
+  void testSetter3(@PolyTainted TestFieldPolymorphism this, @Tainted String s) {
+    // :: error: (assignment)
+    this.field = s;
+  }
+
+  @PolyTainted String testGetter1(@PolyTainted TestFieldPolymorphism this) {
+    return this.field;
+  }
+
+  @Untainted String testGetter2(@PolyTainted TestFieldPolymorphism this) {
+    // :: error: (return)
+    return this.field;
+  }
+
+  static @Untainted String testInstantiateUntaintedGetter(@Untainted TestFieldPolymorphism c) {
+    return c.field;
+  }
+
+  static void testInstantiateUntaintedSetter(
+      @Untainted TestFieldPolymorphism c, @Tainted String s) {
+    // :: error: (assignment)
+    c.field = s;
+  }
+
+  static @Untainted String testInstantiateTaintedGetter(@Tainted TestFieldPolymorphism c) {
+    // :: error: (return)
+    return c.field;
+  }
+
+  static void testInstantiateTaintedSetter(@Tainted TestFieldPolymorphism c, @Tainted String s) {
+    c.field = s;
+  }
+}
diff --git a/checker/tests/tainting/TestNoQualifierParameterConflicting.java b/checker/tests/tainting/TestNoQualifierParameterConflicting.java
new file mode 100644
index 0000000..0e8b2e4
--- /dev/null
+++ b/checker/tests/tainting/TestNoQualifierParameterConflicting.java
@@ -0,0 +1,16 @@
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+import org.checkerframework.framework.qual.NoQualifierParameter;
+
+@HasQualifierParameter(Tainted.class)
+@NoQualifierParameter(Tainted.class)
+// :: error: (conflicting.qual.param)
+public class TestNoQualifierParameterConflicting {
+
+  @HasQualifierParameter(Tainted.class)
+  static class Super {}
+
+  @NoQualifierParameter(Tainted.class)
+  // :: error: (conflicting.qual.param)
+  static class Sup extends Super {}
+}
diff --git a/checker/tests/tainting/TypeInvalid.java b/checker/tests/tainting/TypeInvalid.java
new file mode 100644
index 0000000..0d06e38
--- /dev/null
+++ b/checker/tests/tainting/TypeInvalid.java
@@ -0,0 +1,60 @@
+// Test case for Issue 292:
+// https://github.com/typetools/checker-framework/issues/292
+
+// TODO: ensure that type validation is consistently performed for each possible tree.
+// We should also add a jtreg version of this test to
+// ensure that each error is only output once and in the right place.
+
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+abstract class TypeInvalid {
+  // :: error: (conflicting.annos)
+  static @Untainted @Tainted class Inner {}
+  // Duplication forbidden
+  // :: error: (conflicting.annos)
+  void bad(@Tainted @Untainted TypeInvalid c) {
+    // :: error: (conflicting.annos)
+    Object o = new @Tainted @Untainted Object();
+    // :: error: (conflicting.annos)
+    o = new @Tainted @Untainted Object();
+    // :: error: (conflicting.annos)
+    o = o.equals(new @Tainted @Untainted Object());
+    // :: error: (conflicting.annos)
+    o = (Object) new @Tainted @Untainted Object();
+    // :: error: (conflicting.annos)
+    o = (@Tainted @Untainted TypeInvalid) o;
+    // :: error: (conflicting.annos)
+    o = (new @Tainted @Untainted Object()) instanceof Object;
+    // :: error: (conflicting.annos)
+    // :: warning: (instanceof.unsafe)
+    o = o instanceof @Tainted @Untainted TypeInvalid;
+  }
+
+  // :: error: (conflicting.annos)
+  @Tainted @Untainted Object bar() {
+    return null;
+  }
+
+  // :: error: (conflicting.annos)
+  abstract @Tainted @Untainted Object absbar();
+
+  void voidmethod() {}
+
+  TypeInvalid() {}
+
+  // :: error: (conflicting.annos)
+  @Tainted @Untainted TypeInvalid(int p) {}
+
+  // :: error: (conflicting.annos)
+  void recv(@Tainted @Untainted TypeInvalid this) {}
+
+  // :: error: (conflicting.annos)
+  @Tainted @Untainted Object field;
+
+  // TODO: Note the error marker positions for the errors on fields
+  // and method return types. Maybe these should be improved.
+
+  // :: error: (conflicting.annos)
+  void athro() throws @Tainted @Untainted Exception {}
+}
diff --git a/checker/tests/tainting/withdefault/NoQualifierTest.java b/checker/tests/tainting/withdefault/NoQualifierTest.java
new file mode 100644
index 0000000..409daf0
--- /dev/null
+++ b/checker/tests/tainting/withdefault/NoQualifierTest.java
@@ -0,0 +1,11 @@
+package withdefault;
+
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.framework.qual.NoQualifierParameter;
+
+@NoQualifierParameter(Tainted.class)
+public class NoQualifierTest {
+  // :: error: (invalid.polymorphic.qualifier.use)
+  @PolyTainted int field;
+}
diff --git a/checker/tests/tainting/withdefault/WithDefault.java b/checker/tests/tainting/withdefault/WithDefault.java
new file mode 100644
index 0000000..2e10f8d
--- /dev/null
+++ b/checker/tests/tainting/withdefault/WithDefault.java
@@ -0,0 +1,7 @@
+package withdefault;
+
+import org.checkerframework.checker.tainting.qual.PolyTainted;
+
+public class WithDefault {
+  @PolyTainted int field;
+}
diff --git a/checker/tests/tainting/withdefault/package-info.java b/checker/tests/tainting/withdefault/package-info.java
new file mode 100644
index 0000000..9879e50
--- /dev/null
+++ b/checker/tests/tainting/withdefault/package-info.java
@@ -0,0 +1,5 @@
+@HasQualifierParameter(Tainted.class)
+package withdefault;
+
+import org.checkerframework.checker.tainting.qual.Tainted;
+import org.checkerframework.framework.qual.HasQualifierParameter;
diff --git a/checker/tests/units/Addition.java b/checker/tests/units/Addition.java
new file mode 100644
index 0000000..c7c524f
--- /dev/null
+++ b/checker/tests/units/Addition.java
@@ -0,0 +1,426 @@
+import org.checkerframework.checker.units.qual.A;
+import org.checkerframework.checker.units.qual.Acceleration;
+import org.checkerframework.checker.units.qual.Area;
+import org.checkerframework.checker.units.qual.C;
+import org.checkerframework.checker.units.qual.Current;
+import org.checkerframework.checker.units.qual.Force;
+import org.checkerframework.checker.units.qual.K;
+import org.checkerframework.checker.units.qual.Length;
+import org.checkerframework.checker.units.qual.Luminance;
+import org.checkerframework.checker.units.qual.Mass;
+import org.checkerframework.checker.units.qual.N;
+import org.checkerframework.checker.units.qual.Substance;
+import org.checkerframework.checker.units.qual.Temperature;
+import org.checkerframework.checker.units.qual.Time;
+import org.checkerframework.checker.units.qual.cd;
+import org.checkerframework.checker.units.qual.g;
+import org.checkerframework.checker.units.qual.h;
+import org.checkerframework.checker.units.qual.kN;
+import org.checkerframework.checker.units.qual.kg;
+import org.checkerframework.checker.units.qual.km;
+import org.checkerframework.checker.units.qual.km2;
+import org.checkerframework.checker.units.qual.km3;
+import org.checkerframework.checker.units.qual.kmPERh;
+import org.checkerframework.checker.units.qual.m;
+import org.checkerframework.checker.units.qual.m2;
+import org.checkerframework.checker.units.qual.m3;
+import org.checkerframework.checker.units.qual.mPERs;
+import org.checkerframework.checker.units.qual.mPERs2;
+import org.checkerframework.checker.units.qual.min;
+import org.checkerframework.checker.units.qual.mm;
+import org.checkerframework.checker.units.qual.mm2;
+import org.checkerframework.checker.units.qual.mm3;
+import org.checkerframework.checker.units.qual.mol;
+import org.checkerframework.checker.units.qual.s;
+import org.checkerframework.checker.units.qual.t;
+import org.checkerframework.checker.units.util.UnitsTools;
+
+public class Addition {
+  // Addition is legal when the operands have the same units.
+  void good() {
+    // Units
+    // Amperes
+    @A int aAmpere = 5 * UnitsTools.A;
+    @A int bAmpere = 5 * UnitsTools.A;
+    @A int sAmpere = aAmpere + bAmpere;
+
+    // Candela
+    @cd int aCandela = 5 * UnitsTools.cd;
+    @cd int bCandela = 5 * UnitsTools.cd;
+    @cd int sCandela = aCandela + bCandela;
+
+    // Celsius
+    @C int aCelsius = 5 * UnitsTools.C;
+    @C int bCelsius = 5 * UnitsTools.C;
+    @C int sCelsius = aCelsius + bCelsius;
+
+    // Gram
+    @g int aGram = 5 * UnitsTools.g;
+    @g int bGram = 5 * UnitsTools.g;
+    @g int sGram = aGram + bGram;
+
+    // Hour
+    @h int aHour = 5 * UnitsTools.h;
+    @h int bHour = 5 * UnitsTools.h;
+    @h int sHour = aHour + bHour;
+
+    // Kelvin
+    @K int aKelvin = 5 * UnitsTools.K;
+    @K int bKelvin = 5 * UnitsTools.K;
+    @K int sKelvin = aKelvin + bKelvin;
+
+    // Kilogram
+    @kg int aKilogram = 5 * UnitsTools.kg;
+    @kg int bKilogram = 5 * UnitsTools.kg;
+    @kg int sKilogram = aKilogram + bKilogram;
+
+    // Kilometer
+    @km int aKilometer = 5 * UnitsTools.km;
+    @km int bKilometer = 5 * UnitsTools.km;
+    @km int sKilometer = aKilometer + bKilometer;
+
+    // Square kilometer
+    @km2 int aSquareKilometer = 5 * UnitsTools.km2;
+    @km2 int bSquareKilometer = 5 * UnitsTools.km2;
+    @km2 int sSquareKilometer = aSquareKilometer + bSquareKilometer;
+
+    // Cubic kilometer
+    @km3 int aCubicKilometer = 5 * UnitsTools.km3;
+    @km3 int bCubicKilometer = 5 * UnitsTools.km3;
+    @km3 int sCubicKilometer = aCubicKilometer + bCubicKilometer;
+
+    // Kilometer per hour
+    @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh;
+    @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh;
+    @kmPERh int sKilometerPerHour = aKilometerPerHour + bKilometerPerHour;
+
+    // Meter
+    @m int aMeter = 5 * UnitsTools.m;
+    @m int bMeter = 5 * UnitsTools.m;
+    @m int sMeter = aMeter + bMeter;
+
+    // Square meter
+    @m2 int aSquareMeter = 5 * UnitsTools.m2;
+    @m2 int bSquareMeter = 5 * UnitsTools.m2;
+    @m2 int sSquareMeter = aSquareMeter + bSquareMeter;
+
+    // Cubic meter
+    @m3 int aCubicMeter = 5 * UnitsTools.m3;
+    @m3 int bCubicMeter = 5 * UnitsTools.m3;
+    @m3 int sCubicMeter = aCubicMeter + bCubicMeter;
+
+    // Meter per second
+    @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs;
+    @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs;
+    @mPERs int sMeterPerSecond = aMeterPerSecond + bMeterPerSecond;
+
+    // Meter per second square
+    @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2;
+    @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2;
+    @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare + bMeterPerSecondSquare;
+
+    // Minute
+    @min int aMinute = 5 * UnitsTools.min;
+    @min int bMinute = 5 * UnitsTools.min;
+    @min int sMinute = aMinute + bMinute;
+
+    // Millimeter
+    @mm int aMillimeter = 5 * UnitsTools.mm;
+    @mm int bMillimeter = 5 * UnitsTools.mm;
+    @mm int sMillimeter = aMillimeter + bMillimeter;
+
+    // Square millimeter
+    @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2;
+    @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2;
+    @mm2 int sSquareMillimeter = aSquareMillimeter + bSquareMillimeter;
+
+    // Cubic millimeter
+    @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3;
+    @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3;
+    @mm3 int sCubicMillimeter = aCubicMillimeter + bCubicMillimeter;
+
+    // Mole
+    @mol int aMole = 5 * UnitsTools.mol;
+    @mol int bMole = 5 * UnitsTools.mol;
+    @mol int sMole = aMole + bMole;
+
+    // Newton
+    @N int aNewton = 5 * UnitsTools.N;
+    @N int bNewton = 5 * UnitsTools.N;
+    @N int sNewton = aNewton + bNewton;
+
+    // Kilonewton
+    @kN int aKilonewton = 5 * UnitsTools.kN;
+    @kN int bKilonewton = 5 * UnitsTools.kN;
+    @kN int sKilonewton = aKilonewton + bKilonewton;
+
+    // Second
+    @s int aSecond = 5 * UnitsTools.s;
+    @s int bSecond = 5 * UnitsTools.s;
+    @s int sSecond = aSecond + bSecond;
+  }
+
+  // Addition is illegal when the operands have different units or one is unqualified.  In these
+  // tests, we cycle between the result and the first or second operand having an incorrect type.
+  void bad() {
+    // Dimensions
+    // Acceleration
+    @Acceleration int aAcceleration = 5 * UnitsTools.mPERs2;
+    @Acceleration int bAcceleration = 5 * UnitsTools.mPERs2;
+
+    // Area
+    @Area int aArea = 5 * UnitsTools.km2;
+    @Area int bArea = 5 * UnitsTools.mm2;
+
+    // Current
+    @Current int aCurrent = 5 * UnitsTools.A;
+    @Current int bCurrent = 5 * UnitsTools.A;
+
+    // Force
+    @Force int aForce = 5 * UnitsTools.N;
+    @Force int bForce = 5 * UnitsTools.N;
+
+    // Length
+    @Length int aLength = 5 * UnitsTools.m;
+    @Length int bLength = 5 * UnitsTools.mm;
+
+    // Luminance
+    @Luminance int aLuminance = 5 * UnitsTools.cd;
+    @Luminance int bLuminance = 5 * UnitsTools.cd;
+
+    // Mass
+    @Mass int aMass = 5 * UnitsTools.kg;
+    @Mass int bMass = 5 * UnitsTools.g;
+
+    // Substance
+    @Substance int aSubstance = 5 * UnitsTools.mol;
+    @Substance int bSubstance = 5 * UnitsTools.mol;
+
+    // Temperature
+    @Temperature int aTemperature = 5 * UnitsTools.K;
+    @Temperature int bTemperature = 5 * UnitsTools.K;
+
+    // Time
+    @Time int aTime = 5 * UnitsTools.min;
+    @Time int bTime = 5 * UnitsTools.h;
+
+    // Dimensions
+    // Acceleration
+    // :: error: (assignment)
+    @Acceleration int sAcceleration = aAcceleration + bMass;
+
+    // Area
+    // :: error: (assignment)
+    @Luminance int sLuminance = aArea + bArea;
+
+    // Current
+    // :: error: (assignment)
+    @Current int sCurrent = aMass + bCurrent;
+
+    // Length
+    // :: error: (assignment)
+    @Length int sLength = aLength + bSubstance;
+
+    // Luminance
+    // :: error: (assignment)
+    @Temperature int sTemperature = aLuminance + bLuminance;
+
+    // Mass
+    // :: error: (assignment)
+    @Mass int sMass = aTemperature + bMass;
+
+    // Substance
+    // :: error: (assignment)
+    @Substance int sSubstance = aSubstance + bCurrent;
+
+    // Temperature
+    // :: error: (assignment)
+    @Area int sArea = aTemperature + bTemperature;
+
+    // Time
+    // :: error: (assignment)
+    @Time int sTime = aArea + bTime;
+
+    // Force
+    // :: error: (assignment)
+    sMass = aForce + bForce;
+
+    // Units
+    // Amperes
+    @A int aAmpere = 5 * UnitsTools.A;
+    @A int bAmpere = 5 * UnitsTools.A;
+
+    // Candela
+    @cd int aCandela = 5 * UnitsTools.cd;
+    @cd int bCandela = 5 * UnitsTools.cd;
+
+    // Celsius
+    @C int aCelsius = 5 * UnitsTools.C;
+    @C int bCelsius = 5 * UnitsTools.C;
+
+    // Gram
+    @g int aGram = 5 * UnitsTools.g;
+    @g int bGram = 5 * UnitsTools.g;
+
+    // Hour
+    @h int aHour = 5 * UnitsTools.h;
+    @h int bHour = 5 * UnitsTools.h;
+
+    // Kelvin
+    @K int aKelvin = 5 * UnitsTools.K;
+    @K int bKelvin = 5 * UnitsTools.K;
+
+    // Kilogram
+    @kg int aKilogram = 5 * UnitsTools.kg;
+    @kg int bKilogram = 5 * UnitsTools.kg;
+
+    // Kilometer
+    @km int aKilometer = 5 * UnitsTools.km;
+    @km int bKilometer = 5 * UnitsTools.km;
+
+    // Square kilometer
+    @km2 int aSquareKilometer = 5 * UnitsTools.km2;
+    @km2 int bSquareKilometer = 5 * UnitsTools.km2;
+
+    // Kilometer per hour
+    @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh;
+    @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh;
+
+    // Meter
+    @m int aMeter = 5 * UnitsTools.m;
+    @m int bMeter = 5 * UnitsTools.m;
+
+    // Square meter
+    @m2 int aSquareMeter = 5 * UnitsTools.m2;
+    @m2 int bSquareMeter = 5 * UnitsTools.m2;
+
+    // Meter per second
+    @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs;
+    @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs;
+
+    // Meter per second square
+    @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2;
+    @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2;
+
+    // Minute
+    @min int aMinute = 5 * UnitsTools.min;
+    @min int bMinute = 5 * UnitsTools.min;
+
+    // Millimeter
+    @mm int aMillimeter = 5 * UnitsTools.mm;
+    @mm int bMillimeter = 5 * UnitsTools.mm;
+
+    // Square millimeter
+    @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2;
+    @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2;
+
+    // Mole
+    @mol int aMole = 5 * UnitsTools.mol;
+    @mol int bMole = 5 * UnitsTools.mol;
+
+    // Second
+    @s int aSecond = 5 * UnitsTools.s;
+    @s int bSecond = 5 * UnitsTools.s;
+
+    // Metric Ton
+    @t int aMetricTon = 5 * UnitsTools.t;
+    @t int bMetricTon = 5 * UnitsTools.t;
+
+    // Newton
+    @N int aNewton = 5 * UnitsTools.N;
+    @N int bNewton = 5 * UnitsTools.N;
+
+    // Kilonewton
+    @kN int aKilonewton = 5 * UnitsTools.kN;
+    @kN int bKilonewton = 5 * UnitsTools.kN;
+
+    // Units
+    // Amperes
+    // :: error: (assignment)
+    @g int sGram = aAmpere + bAmpere;
+
+    // Candela
+    // :: error: (assignment)
+    @cd int sCandela = aTemperature + bCandela;
+
+    // Celsius
+    // :: error: (assignment)
+    @C int sCelsius = aCelsius + bMillimeter;
+
+    // Gram
+    // :: error: (assignment)
+    @kg int sKilogram = aGram + bGram;
+
+    // Hour
+    // :: error: (assignment)
+    @h int sHour = aSquareMeter + bHour;
+
+    // Kelvin
+    // :: error: (assignment)
+    @K int sKelvin = aKelvin + bSecond;
+
+    // Kilogram
+    // :: error: (assignment)
+    @kmPERh int sKilometerPerHour = aKilogram + bKilogram;
+
+    // Kilometer
+    // :: error: (assignment)
+    @km int sKilometer = aCandela + bKilometer;
+
+    // Square kilometer
+    // :: error: (assignment)
+    @km2 int sSquareKilometer = aSquareKilometer + bAmpere;
+
+    // Kilometer per hour
+    // :: error: (assignment)
+    @mPERs int sMeterPerSecond = aKilometerPerHour + bKilometerPerHour;
+
+    // Meter
+    // :: error: (assignment)
+    @m int sMeter = aHour + bMeter;
+
+    // Square meter
+    // :: error: (assignment)
+    @m2 int sSquareMeter = aSquareMeter + bGram;
+
+    // Meter per second
+    // :: error: (assignment)
+    @mm2 int sSquareMillimeter = aMeterPerSecond + bMeterPerSecond;
+
+    // Meter per second square
+    // :: error: (assignment)
+    @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare + bMeter;
+
+    // Minute
+    // :: error: (assignment)
+    @min int sMinute = aMole + bMinute;
+
+    // Millimeter
+    // :: error: (assignment)
+    @mm int sMillimeter = aMillimeter + bHour;
+
+    // Square millimeter
+    // :: error: (assignment)
+    @A int sAmpere = aSquareMillimeter + bSquareMillimeter;
+
+    // Mole
+    // :: error: (assignment)
+    @mol int sMole = aCandela + bMole;
+
+    // Second
+    // :: error: (assignment)
+    @s int sSecond = aSecond + bSquareKilometer;
+
+    // Newton
+    // :: error: (assignment)
+    sKilogram = aNewton + bNewton;
+
+    // Kilonewton
+    // :: error: (assignment)
+    @kN int sKilonewton = aKilonewton + bNewton;
+
+    // Metric Ton
+    // :: error: (assignment)
+    @N int sNewton = aNewton + bMetricTon;
+  }
+}
diff --git a/checker/tests/units/BasicUnits.java b/checker/tests/units/BasicUnits.java
new file mode 100644
index 0000000..5453759
--- /dev/null
+++ b/checker/tests/units/BasicUnits.java
@@ -0,0 +1,149 @@
+import org.checkerframework.checker.units.qual.Area;
+import org.checkerframework.checker.units.qual.Prefix;
+import org.checkerframework.checker.units.qual.Volume;
+import org.checkerframework.checker.units.qual.degrees;
+import org.checkerframework.checker.units.qual.h;
+import org.checkerframework.checker.units.qual.kg;
+import org.checkerframework.checker.units.qual.km;
+import org.checkerframework.checker.units.qual.km2;
+import org.checkerframework.checker.units.qual.km3;
+import org.checkerframework.checker.units.qual.kmPERh;
+import org.checkerframework.checker.units.qual.m;
+import org.checkerframework.checker.units.qual.m2;
+import org.checkerframework.checker.units.qual.m3;
+import org.checkerframework.checker.units.qual.mPERs;
+import org.checkerframework.checker.units.qual.mPERs2;
+import org.checkerframework.checker.units.qual.radians;
+import org.checkerframework.checker.units.qual.s;
+import org.checkerframework.checker.units.qual.t;
+import org.checkerframework.checker.units.util.UnitsTools;
+
+public class BasicUnits {
+
+  void demo() {
+    // :: error: (assignment)
+    @m int merr = 5;
+
+    @m int m = 5 * UnitsTools.m;
+    @s int s = 9 * UnitsTools.s;
+
+    // :: error: (assignment)
+    @km int kmerr = 10;
+    @km int km = 10 * UnitsTools.km;
+
+    // this is allowed, unqualified is a supertype of all units
+    int bad = m / s;
+
+    @mPERs int good = m / s;
+
+    // :: error: (assignment)
+    @mPERs int b1 = s / m;
+
+    // :: error: (assignment)
+    @mPERs int b2 = m * s;
+
+    @mPERs2 int goodaccel = m / s / s;
+
+    // :: error: (assignment)
+    @mPERs2 int badaccel1 = s / m / s;
+
+    // :: error: (assignment)
+    @mPERs2 int badaccel2 = s / s / m;
+
+    // :: error: (assignment)
+    @mPERs2 int badaccel3 = s * s / m;
+
+    // :: error: (assignment)
+    @mPERs2 int badaccel4 = m * s * s;
+
+    @Area int ae = m * m;
+    @m2 int gae = m * m;
+
+    // :: error: (assignment)
+    @Area int bae = m * m * m;
+
+    // :: error: (assignment)
+    @km2 int bae1 = m * m;
+
+    @Volume int vol = m * m * m;
+    @m3 int gvol = m * m * m;
+
+    // :: error: (assignment)
+    @Volume int bvol = m * m * m * m;
+
+    // :: error: (assignment)
+    @km3 int bvol1 = m * m * m;
+
+    @radians double rad = 20.0d * UnitsTools.rad;
+    @degrees double deg = 30.0d * UnitsTools.deg;
+
+    @degrees double rToD1 = UnitsTools.toDegrees(rad);
+    // :: error: (argument)
+    @degrees double rToD2 = UnitsTools.toDegrees(deg);
+    // :: error: (assignment)
+    @radians double rToD3 = UnitsTools.toDegrees(rad);
+
+    @radians double dToR1 = UnitsTools.toRadians(deg);
+    // :: error: (argument)
+    @radians double rToR2 = UnitsTools.toRadians(rad);
+    // :: error: (assignment)
+    @degrees double rToR3 = UnitsTools.toRadians(deg);
+
+    // speed conversion
+    @mPERs int mPs = 30 * UnitsTools.mPERs;
+    @kmPERh int kmPhr = 20 * UnitsTools.kmPERh;
+
+    @kmPERh int kmPhrRes = (int) UnitsTools.fromMeterPerSecondToKiloMeterPerHour(mPs);
+    @mPERs int mPsRes = (int) UnitsTools.fromKiloMeterPerHourToMeterPerSecond(kmPhr);
+
+    // :: error: (assignment)
+    @mPERs int mPsResBad = (int) UnitsTools.fromMeterPerSecondToKiloMeterPerHour(mPs);
+    // :: error: (assignment)
+    @kmPERh int kmPhrResBad = (int) UnitsTools.fromKiloMeterPerHourToMeterPerSecond(kmPhr);
+
+    // speeds
+    @km int kilometers = 10 * UnitsTools.km;
+    @h int hours = UnitsTools.h;
+    @kmPERh int speed = kilometers / hours;
+
+    // Addition/substraction only accepts another @kmPERh value
+    // :: error: (assignment)
+    speed = speed + 5;
+    // :: error: (compound.assignment)
+    speed += 5;
+
+    speed += speed;
+    speed = (speed += speed);
+
+    // Multiplication/division with an unqualified type is allowed
+    speed = kilometers / hours * 2;
+    speed /= 2;
+
+    speed = (speed /= 2);
+
+    @kg int kiloGrams = 1000 * UnitsTools.kg;
+    @t int metricTons = UnitsTools.fromKiloGramToMetricTon(kiloGrams);
+    kiloGrams = UnitsTools.fromMetricTonToKiloGram(metricTons);
+  }
+
+  void prefixOutputTest() {
+    @m int x = 5 * UnitsTools.m;
+    @m(Prefix.kilo) int y = 2 * UnitsTools.km;
+    @m(Prefix.one) int z = 3 * UnitsTools.m;
+    @km int y2 = 3 * UnitsTools.km;
+
+    // :: error: (assignment)
+    y2 = z;
+    // :: error: (assignment)
+    y2 = x;
+    // :: error: (assignment)
+    y = z;
+    // :: error: (assignment)
+    y = x;
+
+    // :: error: (assignment)
+    y2 = x * x;
+    // :: error: (assignment)
+    y2 = z * z;
+  }
+}
diff --git a/checker/tests/units/Consistency.java b/checker/tests/units/Consistency.java
new file mode 100644
index 0000000..727d39b
--- /dev/null
+++ b/checker/tests/units/Consistency.java
@@ -0,0 +1,47 @@
+import org.checkerframework.checker.units.qual.Area;
+import org.checkerframework.checker.units.qual.Length;
+import org.checkerframework.checker.units.qual.km;
+import org.checkerframework.checker.units.qual.km2;
+import org.checkerframework.checker.units.qual.m;
+import org.checkerframework.checker.units.qual.m2;
+import org.checkerframework.checker.units.util.UnitsTools;
+
+/**
+ * One possible future extension is adding method annotations to check for consistency of arguments.
+ * This is not implemented yet; send us a message if you think this would help you!
+ *
+ * @skip-test
+ */
+public class Consistency {
+
+  @UnitsSame({0, 1})
+  @UnitsProduct({0, 1, -1})
+  @Area int calcArea(@Length int width, @Length int height) {
+    return width * height;
+  }
+
+  void use() {
+    @m int m1, m2;
+    m1 = UnitsTools.toMeter(5);
+    m2 = UnitsTools.toMeter(51);
+
+    @km int km1, km2;
+    km1 = UnitsTools.toMeter(5);
+    km2 = UnitsTools.toMeter(5);
+
+    @m2 int msq;
+    @km2 int kmsq;
+
+    // good
+    msq = calcArea(m1, m2);
+
+    // :: bad args
+    msq = calcArea(m1, km2);
+
+    // :: bad return
+    kmsq = calcArea(m1, m2);
+
+    // good
+    kmsq = calcArea(km1, km2);
+  }
+}
diff --git a/checker/tests/units/Division.java b/checker/tests/units/Division.java
new file mode 100644
index 0000000..5d87e7b
--- /dev/null
+++ b/checker/tests/units/Division.java
@@ -0,0 +1,145 @@
+import org.checkerframework.checker.units.qual.N;
+import org.checkerframework.checker.units.qual.h;
+import org.checkerframework.checker.units.qual.kN;
+import org.checkerframework.checker.units.qual.kg;
+import org.checkerframework.checker.units.qual.km;
+import org.checkerframework.checker.units.qual.km2;
+import org.checkerframework.checker.units.qual.km3;
+import org.checkerframework.checker.units.qual.kmPERh;
+import org.checkerframework.checker.units.qual.m;
+import org.checkerframework.checker.units.qual.m2;
+import org.checkerframework.checker.units.qual.m3;
+import org.checkerframework.checker.units.qual.mPERs;
+import org.checkerframework.checker.units.qual.mPERs2;
+import org.checkerframework.checker.units.qual.mm;
+import org.checkerframework.checker.units.qual.mm2;
+import org.checkerframework.checker.units.qual.mm3;
+import org.checkerframework.checker.units.qual.s;
+import org.checkerframework.checker.units.qual.t;
+import org.checkerframework.checker.units.util.UnitsTools;
+
+public class Division {
+  void d() {
+    // Basic division of same units, no units constraint on x
+    @m int am = 6 * UnitsTools.m, bm = 3 * UnitsTools.m;
+    int x = am / bm;
+
+    // :: error: (assignment)
+    @m int bad = am / bm;
+
+    // Division removes the unit.
+    // As unqualified would be a supertype, we add another multiplication
+    // to make sure the result of the division is unqualified.
+    @s int div = (am / UnitsTools.m) * UnitsTools.s;
+
+    // units setup
+    @m int m = 2 * UnitsTools.m;
+    @mm int mm = 8 * UnitsTools.mm;
+    @km int km = 4 * UnitsTools.km;
+    @s int s = 3 * UnitsTools.s;
+    @h int h = 5 * UnitsTools.h;
+    @m2 int m2 = 25 * UnitsTools.m2;
+    @km2 int km2 = 9 * UnitsTools.km2;
+    @mm2 int mm2 = 16 * UnitsTools.mm2;
+    @mPERs int mPERs = 20 * UnitsTools.mPERs;
+    @kmPERh int kmPERh = 2 * UnitsTools.kmPERh;
+    @mPERs2 int mPERs2 = 30 * UnitsTools.mPERs2;
+    @m3 int m3 = 125 * UnitsTools.m3;
+    @km3 int km3 = 27 * UnitsTools.km3;
+    @mm3 int mm3 = 64 * UnitsTools.mm3;
+    @kg int kg = 11 * UnitsTools.kg;
+    @t int t = 19 * UnitsTools.t;
+    @N int N = 7 * UnitsTools.N;
+    @kN int kN = 13 * UnitsTools.kN;
+
+    // m / s = mPERs
+    @mPERs int velocitym = m / s;
+    // :: error: (assignment)
+    velocitym = m / h;
+
+    // km / h = kmPERh
+    @kmPERh int velocitykm = km / h;
+    // :: error: (assignment)
+    velocitykm = km / s;
+
+    // m2 / m = m
+    @m int distancem = m2 / m;
+    // :: error: (assignment)
+    distancem = m2 / km;
+
+    // km2 / km = km
+    @km int distancekm = km2 / km;
+    // :: error: (assignment)
+    distancekm = km2 / m;
+
+    // mm2 / mm = mm
+    @mm int distancemm = mm2 / mm;
+    // :: error: (assignment)
+    distancemm = km2 / mm;
+
+    // m3 / m2 = m
+    distancem = m3 / m2;
+    // :: error: (assignment)
+    distancem = m3 / km2;
+
+    // km3 / km2 = km
+    distancekm = km3 / km2;
+    // :: error: (assignment)
+    distancekm = km3 / m2;
+
+    // mm3 / mm2 = mm
+    distancemm = mm3 / mm2;
+    // :: error: (assignment)
+    distancemm = km3 / mm2;
+
+    // m / mPERs = s
+    @s int times = m / mPERs;
+    // :: error: (assignment)
+    times = km / mPERs;
+
+    // km / kmPERh = h
+    @h int timeh = km / kmPERh;
+    // :: error: (assignment)
+    timeh = m / kmPERh;
+
+    // mPERs / s = mPERs2
+    @mPERs2 int accel1 = mPERs / s;
+    // :: error: (assignment)
+    accel1 = kmPERh / s;
+
+    // mPERs / mPERs2 = s
+    @s int times2 = mPERs / mPERs2;
+    // :: error: (assignment)
+    times2 = kmPERh / mPERs2;
+
+    // mPERs2 = N / kg
+    @mPERs2 int accel2 = N / kg;
+    // :: error: (assignment)
+    accel2 = N / km;
+
+    // mPERs2 = kN / t
+    @mPERs2 int accel3 = kN / t;
+    // :: error: (assignment)
+    accel3 = N / t;
+
+    // kg = N / mPERs2
+    @kg int mass = N / mPERs2;
+    // :: error: (assignment)
+    mass = s / mPERs2;
+
+    // t = kN / mPERs2
+    @t int mass2 = kN / mPERs2;
+    // :: error: (assignment)
+    mass2 = N / mPERs2;
+  }
+
+  void SpeedOfSoundTests() {
+    @mPERs double speedOfSound = (340.29 * UnitsTools.m) / (UnitsTools.s);
+
+    @s double tenSeconds = 10.0 * UnitsTools.s;
+    @m double soundIn10Seconds = speedOfSound * tenSeconds;
+
+    @m double length = 100.0 * UnitsTools.m;
+    @s double soundNeedTimeForLength = length / speedOfSound;
+  }
+}
diff --git a/checker/tests/units/Manual.java b/checker/tests/units/Manual.java
new file mode 100644
index 0000000..608b783
--- /dev/null
+++ b/checker/tests/units/Manual.java
@@ -0,0 +1,14 @@
+import org.checkerframework.checker.units.qual.m;
+import org.checkerframework.checker.units.qual.mPERs;
+import org.checkerframework.checker.units.qual.s;
+import org.checkerframework.checker.units.util.UnitsTools;
+
+// Include all the examples from the manual here,
+// to ensure they work as expected.
+public class Manual {
+  void demo1() {
+    @m int meters = 5 * UnitsTools.m;
+    @s int secs = 2 * UnitsTools.s;
+    @mPERs int speed = meters / secs;
+  }
+}
diff --git a/checker/tests/units/Multiples.java b/checker/tests/units/Multiples.java
new file mode 100644
index 0000000..8e3b64b
--- /dev/null
+++ b/checker/tests/units/Multiples.java
@@ -0,0 +1,207 @@
+import org.checkerframework.checker.units.qual.N;
+import org.checkerframework.checker.units.qual.Prefix;
+import org.checkerframework.checker.units.qual.g;
+import org.checkerframework.checker.units.qual.h;
+import org.checkerframework.checker.units.qual.kN;
+import org.checkerframework.checker.units.qual.kg;
+import org.checkerframework.checker.units.qual.km;
+import org.checkerframework.checker.units.qual.km2;
+import org.checkerframework.checker.units.qual.km3;
+import org.checkerframework.checker.units.qual.kmPERh;
+import org.checkerframework.checker.units.qual.m;
+import org.checkerframework.checker.units.qual.m2;
+import org.checkerframework.checker.units.qual.m3;
+import org.checkerframework.checker.units.qual.mPERs;
+import org.checkerframework.checker.units.qual.mPERs2;
+import org.checkerframework.checker.units.qual.mm;
+import org.checkerframework.checker.units.qual.mm2;
+import org.checkerframework.checker.units.qual.mm3;
+import org.checkerframework.checker.units.qual.s;
+import org.checkerframework.checker.units.qual.t;
+import org.checkerframework.checker.units.util.UnitsTools;
+
+public class Multiples {
+  void m() {
+    // Prefix assignment tests
+    // kg
+    @kg int kg = 5 * UnitsTools.kg;
+    @g(Prefix.kilo) int alsokg = kg;
+    // :: error: (assignment)
+    @g(Prefix.giga) int notkg = kg;
+    // :: error: (assignment)
+    kg = notkg;
+    kg = alsokg;
+
+    // g
+    @g int g = 5 * UnitsTools.g;
+    @g(Prefix.one) int alsog = g;
+    // :: error: (assignment)
+    @g(Prefix.milli) int notg = g;
+    // :: error: (assignment)
+    notg = g;
+    g = alsog;
+
+    // m
+    @m int m = 5 * UnitsTools.m;
+    @m(Prefix.one) int alsom = m;
+    // :: error: (assignment)
+    @m(Prefix.giga) int notm = m;
+    // :: error: (assignment)
+    m = notm;
+    m = alsom;
+
+    // km
+    @km int km = 5 * UnitsTools.km;
+    @m(Prefix.kilo) int alsokm = km;
+    // :: error: (assignment)
+    @m(Prefix.giga) int notkm = km;
+    // :: error: (assignment)
+    km = notkm;
+    km = alsokm;
+
+    // mm
+    @mm int mm = 5 * UnitsTools.mm;
+    @m(Prefix.milli) int alsomm = mm;
+    // :: error: (assignment)
+    @m(Prefix.giga) int notmm = mm;
+    // :: error: (assignment)
+    mm = notmm;
+    mm = alsomm;
+
+    // N
+    @N int N = 5 * UnitsTools.N;
+    @N(Prefix.one) int alsoN = N;
+    @N(Prefix.giga)
+    // :: error: (assignment)
+    int notN = N;
+    // :: error: (assignment)
+    N = notN;
+    N = alsoN;
+
+    // kN
+    @kN int kN = 5 * UnitsTools.kN;
+    @N(Prefix.kilo) int alsokN = kN;
+    @N(Prefix.giga)
+    // :: error: (assignment)
+    int notkN = kN;
+    // :: error: (assignment)
+    kN = notkN;
+    kN = alsokN;
+
+    // s
+    @s int s = 5 * UnitsTools.s;
+
+    // h
+    @h int h = 5 * UnitsTools.h;
+
+    // m * m = m2
+    @m2 int area = m * m;
+    // :: error: (assignment)
+    @km2 int areambad1 = m * m;
+    // :: error: (assignment)
+    @mm2 int areambad2 = m * m;
+
+    // km * km = km2
+    @km2 int karea = km * km;
+    // :: error: (assignment)
+    @m2 int areakmbad1 = km * km;
+    // :: error: (assignment)
+    @mm2 int areakmbad2 = km * km;
+
+    // mm * mm = mm2
+    @mm2 int marea = mm * mm;
+    // :: error: (assignment)
+    @m2 int areammbad1 = mm * mm;
+    // :: error: (assignment)
+    @km2 int areammbad2 = mm * mm;
+
+    // m * m2 = m3
+    @m3 int volume = m * area;
+    // :: error: (assignment)
+    @km3 int volumembad1 = m * area;
+    // :: error: (assignment)
+    @mm3 int volumembad2 = m * area;
+
+    // km * km2 = km3
+    @km3 int kvolume = km * karea;
+    // :: error: (assignment)
+    @m3 int volumekmbad1 = km * karea;
+    // :: error: (assignment)
+    @mm3 int volumekmbad2 = km * karea;
+
+    // mm * mm2 = mm3
+    @mm3 int mvolume = mm * marea;
+    // :: error: (assignment)
+    @m3 int volumemmbad1 = mm * marea;
+    // :: error: (assignment)
+    @km3 int volumemmbad2 = mm * marea;
+
+    // m2 * m = m3
+    volume = area * m;
+    // :: error: (assignment)
+    volumembad1 = area * m;
+    // :: error: (assignment)
+    volumembad2 = area * m;
+
+    // km2 * km = km3
+    kvolume = karea * km;
+    // :: error: (assignment)
+    volumekmbad1 = karea * km;
+    // :: error: (assignment)
+    volumekmbad2 = karea * km;
+
+    // mm2 * mm = mm3
+    mvolume = marea * mm;
+    // :: error: (assignment)
+    volumemmbad1 = marea * mm;
+    // :: error: (assignment)
+    volumemmbad2 = marea * mm;
+
+    // s * mPERs = m
+    @mPERs int speedm = 10 * UnitsTools.mPERs;
+    @m int lengthm = s * speedm;
+    lengthm = speedm * s;
+    // :: error: (assignment)
+    @km int lengthmbad1 = s * speedm;
+    // :: error: (assignment)
+    @mm int lengthmbad2 = s * speedm;
+
+    // s * mPERs2 = mPERs
+    @mPERs2 int accelm = 20 * UnitsTools.mPERs2;
+    @mPERs int speedm2 = s * accelm;
+    speedm2 = accelm * s;
+    // :: error: (assignment)
+    @kmPERh int speedm2bad1 = s * accelm;
+
+    // h * kmPERh = km
+    @kmPERh int speedkm = 30 * UnitsTools.kmPERh;
+    @km int lengthkm = h * speedkm;
+    lengthkm = speedkm * h;
+    // :: error: (assignment)
+    @m int lengthkmbad1 = h * speedkm;
+    // :: error: (assignment)
+    @mm int lengthkmbad2 = h * speedkm;
+
+    // kg * mPERs2 = N
+    @kg int mass = 40 * UnitsTools.kg;
+    @mPERs2 int accel = 50 * UnitsTools.mPERs2;
+    @N int force = mass * accel;
+
+    // mPERs2 * kg = N
+    @N int alsoforce = accel * mass;
+
+    @t int massMetricTons = 50 * UnitsTools.t;
+    @kN int forceKiloNewtons = massMetricTons * accel;
+    forceKiloNewtons = accel * massMetricTons;
+
+    // s * s * mPERs2 = m
+    // TODO: fix checker so it is insensitive to order of operations as long as final results'
+    // unit makes sense.
+    // Currently due to left associativity, and the lack of an s2 annotation, this tries to
+    // evaluate (s * s) * mPERs2 which causes the type assignment incompatible error.
+    // :: error: (assignment)
+    @m int distance = s * s * accelm;
+    // if we bracket for order of operations, it works fine
+    distance = s * (s * accelm);
+  }
+}
diff --git a/checker/tests/units/PolyUnitTest.java b/checker/tests/units/PolyUnitTest.java
new file mode 100644
index 0000000..b8b4257
--- /dev/null
+++ b/checker/tests/units/PolyUnitTest.java
@@ -0,0 +1,22 @@
+import org.checkerframework.checker.units.qual.PolyUnit;
+import org.checkerframework.checker.units.qual.m;
+import org.checkerframework.checker.units.qual.s;
+import org.checkerframework.checker.units.util.UnitsTools;
+
+public class PolyUnitTest {
+
+  @PolyUnit int triplePolyUnit(@PolyUnit int amount) {
+    return 3 * amount;
+  }
+
+  void testPolyUnit() {
+    @m int m1 = 7 * UnitsTools.m;
+    @m int m2 = triplePolyUnit(m1);
+
+    @s int sec1 = 7 * UnitsTools.s;
+    @s int sec2 = triplePolyUnit(sec1);
+
+    // :: error: (assignment)
+    @s int sec3 = triplePolyUnit(m1);
+  }
+}
diff --git a/checker/tests/units/SubtractionUnits.java b/checker/tests/units/SubtractionUnits.java
new file mode 100644
index 0000000..b700b69
--- /dev/null
+++ b/checker/tests/units/SubtractionUnits.java
@@ -0,0 +1,454 @@
+import org.checkerframework.checker.units.qual.A;
+import org.checkerframework.checker.units.qual.Acceleration;
+import org.checkerframework.checker.units.qual.Area;
+import org.checkerframework.checker.units.qual.C;
+import org.checkerframework.checker.units.qual.Current;
+import org.checkerframework.checker.units.qual.Force;
+import org.checkerframework.checker.units.qual.K;
+import org.checkerframework.checker.units.qual.Length;
+import org.checkerframework.checker.units.qual.Luminance;
+import org.checkerframework.checker.units.qual.Mass;
+import org.checkerframework.checker.units.qual.N;
+import org.checkerframework.checker.units.qual.Substance;
+import org.checkerframework.checker.units.qual.Temperature;
+import org.checkerframework.checker.units.qual.Time;
+import org.checkerframework.checker.units.qual.Volume;
+import org.checkerframework.checker.units.qual.cd;
+import org.checkerframework.checker.units.qual.g;
+import org.checkerframework.checker.units.qual.h;
+import org.checkerframework.checker.units.qual.kN;
+import org.checkerframework.checker.units.qual.kg;
+import org.checkerframework.checker.units.qual.km;
+import org.checkerframework.checker.units.qual.km2;
+import org.checkerframework.checker.units.qual.km3;
+import org.checkerframework.checker.units.qual.kmPERh;
+import org.checkerframework.checker.units.qual.m;
+import org.checkerframework.checker.units.qual.m2;
+import org.checkerframework.checker.units.qual.m3;
+import org.checkerframework.checker.units.qual.mPERs;
+import org.checkerframework.checker.units.qual.mPERs2;
+import org.checkerframework.checker.units.qual.min;
+import org.checkerframework.checker.units.qual.mm;
+import org.checkerframework.checker.units.qual.mm2;
+import org.checkerframework.checker.units.qual.mm3;
+import org.checkerframework.checker.units.qual.mol;
+import org.checkerframework.checker.units.qual.s;
+import org.checkerframework.checker.units.qual.t;
+import org.checkerframework.checker.units.util.UnitsTools;
+
+public class SubtractionUnits {
+  // Subtraction is legal when the operands have the same units.
+  void good() {
+    // Units
+    // Amperes
+    @A int aAmpere = 5 * UnitsTools.A;
+    @A int bAmpere = 5 * UnitsTools.A;
+    @A int sAmpere = aAmpere - bAmpere;
+
+    // Candela
+    @cd int aCandela = 5 * UnitsTools.cd;
+    @cd int bCandela = 5 * UnitsTools.cd;
+    @cd int sCandela = aCandela - bCandela;
+
+    // Celsius
+    @C int aCelsius = 5 * UnitsTools.C;
+    @C int bCelsius = 5 * UnitsTools.C;
+    @C int sCelsius = aCelsius - bCelsius;
+
+    // Gram
+    @g int aGram = 5 * UnitsTools.g;
+    @g int bGram = 5 * UnitsTools.g;
+    @g int sGram = aGram - bGram;
+
+    // Hour
+    @h int aHour = 5 * UnitsTools.h;
+    @h int bHour = 5 * UnitsTools.h;
+    @h int sHour = aHour - bHour;
+
+    // Kelvin
+    @K int aKelvin = 5 * UnitsTools.K;
+    @K int bKelvin = 5 * UnitsTools.K;
+    @K int sKelvin = aKelvin - bKelvin;
+
+    // Kilonewton
+    @kN int aKilonewton = 5 * UnitsTools.kN;
+    @kN int bKilonewton = 5 * UnitsTools.kN;
+    @kN int sKiloewton = aKilonewton - bKilonewton;
+
+    // Kilogram
+    @kg int aKilogram = 5 * UnitsTools.kg;
+    @kg int bKilogram = 5 * UnitsTools.kg;
+    @kg int sKilogram = aKilogram - bKilogram;
+
+    // Kilometer
+    @km int aKilometer = 5 * UnitsTools.km;
+    @km int bKilometer = 5 * UnitsTools.km;
+    @km int sKilometer = aKilometer - bKilometer;
+
+    // Square kilometer
+    @km2 int aSquareKilometer = 5 * UnitsTools.km2;
+    @km2 int bSquareKilometer = 5 * UnitsTools.km2;
+    @km2 int sSquareKilometer = aSquareKilometer - bSquareKilometer;
+
+    // Cubic kilometer
+    @km3 int aCubicKilometer = 5 * UnitsTools.km3;
+    @km3 int bCubicKilometer = 5 * UnitsTools.km3;
+    @km3 int sCubicKilometer = aCubicKilometer - bCubicKilometer;
+
+    // Kilometer per hour
+    @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh;
+    @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh;
+    @kmPERh int sKilometerPerHour = aKilometerPerHour - bKilometerPerHour;
+
+    // Meter
+    @m int aMeter = 5 * UnitsTools.m;
+    @m int bMeter = 5 * UnitsTools.m;
+    @m int sMeter = aMeter - bMeter;
+
+    // Square meter
+    @m2 int aSquareMeter = 5 * UnitsTools.m2;
+    @m2 int bSquareMeter = 5 * UnitsTools.m2;
+    @m2 int sSquareMeter = aSquareMeter - bSquareMeter;
+
+    // Cubic meter
+    @m3 int aCubicMeter = 5 * UnitsTools.m3;
+    @m3 int bCubicMeter = 5 * UnitsTools.m3;
+    @m3 int sCubicMeter = aCubicMeter - bCubicMeter;
+
+    // Meter per second
+    @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs;
+    @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs;
+    @mPERs int sMeterPerSecond = aMeterPerSecond - bMeterPerSecond;
+
+    // Meter per second square
+    @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2;
+    @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2;
+    @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare - bMeterPerSecondSquare;
+
+    // Minute
+    @min int aMinute = 5 * UnitsTools.min;
+    @min int bMinute = 5 * UnitsTools.min;
+    @min int sMinute = aMinute - bMinute;
+
+    // Millimeter
+    @mm int aMillimeter = 5 * UnitsTools.mm;
+    @mm int bMillimeter = 5 * UnitsTools.mm;
+    @mm int sMillimeter = aMillimeter - bMillimeter;
+
+    // Square millimeter
+    @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2;
+    @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2;
+    @mm2 int sSquareMillimeter = aSquareMillimeter - bSquareMillimeter;
+
+    // Cubic millimeter
+    @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3;
+    @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3;
+    @mm3 int sCubicMillimeter = aCubicMillimeter - bCubicMillimeter;
+
+    // Mole
+    @mol int aMole = 5 * UnitsTools.mol;
+    @mol int bMole = 5 * UnitsTools.mol;
+    @mol int sMole = aMole - bMole;
+
+    // Newton
+    @N int aNewton = 5 * UnitsTools.N;
+    @N int bNewton = 5 * UnitsTools.N;
+    @N int sNewton = aNewton - bNewton;
+
+    // Second
+    @s int aSecond = 5 * UnitsTools.s;
+    @s int bSecond = 5 * UnitsTools.s;
+    @s int sSecond = aSecond - bSecond;
+  }
+
+  // Subtraction is illegal when the operands have different units or one is unqualified.  In these
+  // tests, we cycle between the result and the first or second operand having an incorrect type.
+  void bad() {
+    // Dimensions
+    // Acceleration
+    @Acceleration int aAcceleration = 5 * UnitsTools.mPERs2;
+    @Acceleration int bAcceleration = 5 * UnitsTools.mPERs2;
+
+    // Area
+    @Area int aArea = 5 * UnitsTools.km2;
+    @Area int bArea = 5 * UnitsTools.mm2;
+
+    // Current
+    @Current int aCurrent = 5 * UnitsTools.A;
+    @Current int bCurrent = 5 * UnitsTools.A;
+
+    // Force
+    @Force int aForce = 5 * UnitsTools.N;
+    @Force int bForce = 5 * UnitsTools.N;
+
+    // Length
+    @Length int aLength = 5 * UnitsTools.m;
+    @Length int bLength = 5 * UnitsTools.mm;
+
+    // Luminance
+    @Luminance int aLuminance = 5 * UnitsTools.cd;
+    @Luminance int bLuminance = 5 * UnitsTools.cd;
+
+    // Mass
+    @Mass int aMass = 5 * UnitsTools.kg;
+    @Mass int bMass = 5 * UnitsTools.g;
+
+    // Substance
+    @Substance int aSubstance = 5 * UnitsTools.mol;
+    @Substance int bSubstance = 5 * UnitsTools.mol;
+
+    // Temperature
+    @Temperature int aTemperature = 5 * UnitsTools.K;
+    @Temperature int bTemperature = 5 * UnitsTools.K;
+
+    // Time
+    @Time int aTime = 5 * UnitsTools.min;
+    @Time int bTime = 5 * UnitsTools.h;
+
+    // Volume
+    @Volume int aVolume = 5 * UnitsTools.m3;
+    @Volume int bVolume = 5 * UnitsTools.km3;
+
+    // Dimensions
+    // :: error: (assignment)
+    @Acceleration int sAcceleration = aAcceleration - bMass;
+
+    // Area
+    // :: error: (assignment)
+    @Luminance int sLuminance = aArea - bArea;
+
+    // Current
+    // :: error: (assignment)
+    @Current int sCurrent = aMass - bCurrent;
+
+    // Length
+    // :: error: (assignment)
+    @Length int sLength = aLength - bSubstance;
+
+    // Luminance
+    // :: error: (assignment)
+    @Temperature int sTemperature = aLuminance - bLuminance;
+
+    // Mass
+    // :: error: (assignment)
+    @Mass int sMass = aTemperature - bMass;
+
+    // Substance
+    // :: error: (assignment)
+    @Substance int sSubstance = aSubstance - bCurrent;
+
+    // Temperature
+    // :: error: (assignment)
+    @Area int sArea = aTemperature - bTemperature;
+
+    // Time
+    // :: error: (assignment)
+    @Time int sTime = aArea - bTime;
+
+    // Volume
+    // :: error: (assignment)
+    @Volume int sVolume = aVolume - bArea;
+
+    // Force
+    // :: error: (assignment)
+    sMass = aForce - bForce;
+
+    // Units
+    // Amperes
+    @A int aAmpere = 5 * UnitsTools.A;
+    @A int bAmpere = 5 * UnitsTools.A;
+
+    // Candela
+    @cd int aCandela = 5 * UnitsTools.cd;
+    @cd int bCandela = 5 * UnitsTools.cd;
+
+    // Celsius
+    @C int aCelsius = 5 * UnitsTools.C;
+    @C int bCelsius = 5 * UnitsTools.C;
+
+    // Gram
+    @g int aGram = 5 * UnitsTools.g;
+    @g int bGram = 5 * UnitsTools.g;
+
+    // Hour
+    @h int aHour = 5 * UnitsTools.h;
+    @h int bHour = 5 * UnitsTools.h;
+
+    // Kelvin
+    @K int aKelvin = 5 * UnitsTools.K;
+    @K int bKelvin = 5 * UnitsTools.K;
+
+    // Kilonewton
+    @kN int aKilonewton = 5 * UnitsTools.kN;
+    @kN int bKilonewton = 5 * UnitsTools.kN;
+
+    // Kilogram
+    @kg int aKilogram = 5 * UnitsTools.kg;
+    @kg int bKilogram = 5 * UnitsTools.kg;
+
+    // Kilometer
+    @km int aKilometer = 5 * UnitsTools.km;
+    @km int bKilometer = 5 * UnitsTools.km;
+
+    // Square kilometer
+    @km2 int aSquareKilometer = 5 * UnitsTools.km2;
+    @km2 int bSquareKilometer = 5 * UnitsTools.km2;
+
+    // Cubic kilometer
+    @km3 int aCubicKilometer = 5 * UnitsTools.km3;
+    @km3 int bCubicKilometer = 5 * UnitsTools.km3;
+
+    // Kilometer per hour
+    @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh;
+    @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh;
+
+    // Meter
+    @m int aMeter = 5 * UnitsTools.m;
+    @m int bMeter = 5 * UnitsTools.m;
+
+    // Square meter
+    @m2 int aSquareMeter = 5 * UnitsTools.m2;
+    @m2 int bSquareMeter = 5 * UnitsTools.m2;
+
+    // Cubic meter
+    @m3 int aCubicMeter = 5 * UnitsTools.m3;
+    @m3 int bCubicMeter = 5 * UnitsTools.m3;
+
+    // Meter per second
+    @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs;
+    @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs;
+
+    // Meter per second square
+    @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2;
+    @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2;
+
+    // Minute
+    @min int aMinute = 5 * UnitsTools.min;
+    @min int bMinute = 5 * UnitsTools.min;
+
+    // Millimeter
+    @mm int aMillimeter = 5 * UnitsTools.mm;
+    @mm int bMillimeter = 5 * UnitsTools.mm;
+
+    // Square millimeter
+    @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2;
+    @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2;
+
+    // Cubic millimeter
+    @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3;
+    @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3;
+
+    // Mole
+    @mol int aMole = 5 * UnitsTools.mol;
+    @mol int bMole = 5 * UnitsTools.mol;
+
+    // Second
+    @s int aSecond = 5 * UnitsTools.s;
+    @s int bSecond = 5 * UnitsTools.s;
+
+    // Metric Tons
+    @t int aMetricTon = 5 * UnitsTools.t;
+    @t int bMetricTon = 5 * UnitsTools.t;
+
+    // Newton
+    @N int aNewton = 5 * UnitsTools.N;
+    @N int bNewton = 5 * UnitsTools.N;
+
+    // Units
+    // Amperes
+    // :: error: (assignment)
+    @g int sGram = aAmpere - bAmpere;
+
+    // Candela
+    // :: error: (assignment)
+    @cd int sCandela = aTemperature - bCandela;
+
+    // Celsius
+    // :: error: (assignment)
+    @C int sCelsius = aCelsius - bMillimeter;
+
+    // Gram
+    // :: error: (assignment)
+    @kg int sKilogram = aGram - bGram;
+
+    // Hour
+    // :: error: (assignment)
+    @h int sHour = aSquareMeter - bHour;
+
+    // Kelvin
+    // :: error: (assignment)
+    @K int sKelvin = aKelvin - bSecond;
+
+    // Kilogram
+    // :: error: (assignment)
+    @kmPERh int sKilometerPerHour = aKilogram - bKilogram;
+
+    // Kilometer
+    // :: error: (assignment)
+    @km int sKilometer = aCandela - bKilometer;
+
+    // Square kilometer
+    // :: error: (assignment)
+    @km2 int sSquareKilometer = aSquareKilometer - bAmpere;
+
+    // Cubic kilometer
+    // :: error: (assignment)
+    @km3 int sCubicKilometer = aCubicKilometer - bAmpere;
+
+    // Kilometer per hour
+    // :: error: (assignment)
+    @mPERs int sMeterPerSecond = aKilometerPerHour - bKilometerPerHour;
+
+    // Meter
+    // :: error: (assignment)
+    @m int sMeter = aHour - bMeter;
+
+    // Square meter
+    // :: error: (assignment)
+    @m2 int sSquareMeter = aSquareMeter - bGram;
+
+    // Cubic meter
+    // :: error: (assignment)
+    @m3 int sCubicMeter = aCubicMeter - bGram;
+
+    // Meter per second
+    // :: error: (assignment)
+    @mm2 int sSquareMillimeter = aMeterPerSecond - bMeterPerSecond;
+
+    // Meter per second square
+    // :: error: (assignment)
+    @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare - bMeter;
+
+    // Minute
+    // :: error: (assignment)
+    @min int sMinute = aMole - bMinute;
+
+    // Millimeter
+    // :: error: (assignment)
+    @mm int sMillimeter = aMillimeter - bHour;
+
+    // Square millimeter
+    // :: error: (assignment)
+    @A int sAmpere = aSquareMillimeter - bSquareMillimeter;
+
+    // Mole
+    // :: error: (assignment)
+    @mol int sMole = aCandela - bMole;
+
+    // Second
+    // :: error: (assignment)
+    @s int sSecond = aSecond - bSquareKilometer;
+
+    // Newton
+    // :: error: (assignment)
+    sKilogram = aNewton - bNewton;
+
+    // Kilonewton
+    // :: error: (assignment)
+    @kN int sKilonewton = aKilonewton - bNewton;
+
+    // Metric Ton
+    // :: error: (assignment)
+    @N int sNewton = aNewton - bMetricTon;
+  }
+}
diff --git a/checker/tests/units/TypeVarsArrays.java b/checker/tests/units/TypeVarsArrays.java
new file mode 100644
index 0000000..629d2eb
--- /dev/null
+++ b/checker/tests/units/TypeVarsArrays.java
@@ -0,0 +1,8 @@
+public class TypeVarsArrays<T> {
+  private T[] array;
+
+  public void triggerBug(int index, T val) {
+    array[index] = val;
+    array[index] = null;
+  }
+}
diff --git a/checker/tests/units/Units.java b/checker/tests/units/Units.java
new file mode 100644
index 0000000..712c315
--- /dev/null
+++ b/checker/tests/units/Units.java
@@ -0,0 +1,16 @@
+import static org.checkerframework.checker.units.util.UnitsTools.s;
+
+import org.checkerframework.checker.units.qual.m;
+import org.checkerframework.checker.units.qual.s;
+import org.checkerframework.checker.units.util.UnitsTools;
+
+public class Units {
+  @m int m1 = 5 * UnitsTools.m;
+
+  // The advantage of using the multiplication with a unit is that also double, float, etc. are
+  // easily handled and we don't need to end a huge number of methods to UnitsTools.
+  @m double dm = 9.34d * UnitsTools.m;
+
+  // With a static import:
+  @s float time = 5.32f * s;
+}
diff --git a/checker/tests/units/UnqualTest.java b/checker/tests/units/UnqualTest.java
new file mode 100644
index 0000000..e9f3d9b
--- /dev/null
+++ b/checker/tests/units/UnqualTest.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.units.qual.kg;
+
+public class UnqualTest {
+  // :: error: (assignment)
+  @kg int kg = 5;
+  int nonkg = kg;
+  // :: error: (assignment)
+  @kg int alsokg = nonkg;
+}
diff --git a/checker/tests/value-index-interaction/MethodOverrides.java b/checker/tests/value-index-interaction/MethodOverrides.java
new file mode 100644
index 0000000..80f8345
--- /dev/null
+++ b/checker/tests/value-index-interaction/MethodOverrides.java
@@ -0,0 +1,19 @@
+// This class should not issues any errors from the value checker.
+// The index checker should issue the errors instead.
+
+// There is a copy of this test at checker/tests/index/MethodOverrides.java,
+// which includes expected failures.
+
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+
+public class MethodOverrides {
+  @GTENegativeOne int read() {
+    return -1;
+  }
+}
+
+class MethodOverrides2 extends MethodOverrides {
+  int read() {
+    return -1;
+  }
+}
diff --git a/checker/tests/value-index-interaction/MethodOverrides3.java b/checker/tests/value-index-interaction/MethodOverrides3.java
new file mode 100644
index 0000000..5cc8792
--- /dev/null
+++ b/checker/tests/value-index-interaction/MethodOverrides3.java
@@ -0,0 +1,17 @@
+// This class should not issues any errors, since these annotations are identical to the ones
+// on java.io.PrintWriter in the Index JDK.
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+import org.checkerframework.checker.index.qual.IndexFor;
+import org.checkerframework.checker.index.qual.IndexOrHigh;
+
+public class MethodOverrides3 extends PrintWriter {
+  public MethodOverrides3(File file) throws FileNotFoundException {
+    super(file);
+  }
+
+  @Override
+  public void write(char[] buf, @IndexFor("#1") int off, @IndexOrHigh("#1") int len) {}
+}
diff --git a/checker/tests/value-index-interaction/MinLenFromPositive.java b/checker/tests/value-index-interaction/MinLenFromPositive.java
new file mode 100644
index 0000000..d0ba48c
--- /dev/null
+++ b/checker/tests/value-index-interaction/MinLenFromPositive.java
@@ -0,0 +1,56 @@
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class MinLenFromPositive {
+
+  public @Positive int x = 0;
+
+  void testField() {
+    this.x = -1;
+    @IntRange(from = 1) int f = this.x;
+    int @MinLen(1) [] y = new int[x];
+  }
+
+  void testArray(@Positive int @ArrayLen(1) [] x) {
+    int @MinLen(1) [] array = new int[x[0]];
+  }
+
+  void useTestArray(int @ArrayLen(1) [] x, int[] y) {
+    testArray(x);
+    // :: error: (argument)
+    testArray(y);
+  }
+
+  void test(@Positive int x) {
+    @IntRange(from = 1) int z = x;
+    @Positive int q = x;
+    @Positive int a = -1;
+    int @MinLen(1) [] array = new int[a];
+  }
+
+  // Ensure that just running the value checker doesn't result in an LHS warning.
+  void foo2(int x) {
+    test(x);
+  }
+
+  @Positive int id(@Positive int x) {
+    return -1;
+  }
+
+  @Positive int plus(@Positive int x, @Positive int y) {
+    // :: error: (assignment)
+    @IntRange(from = 0) int z = x + y;
+    // :: error: (assignment)
+    @IntRange(from = 1) int q = x + y;
+
+    return x + y;
+  }
+
+  // Ensure that LHS warnings aren't issued even for arrays of Positives
+  @Positive int[] array_test() {
+    int[] a = {-1, 2, 3};
+    return a;
+  }
+}
diff --git a/checker/tests/value-index-interaction/OverrideIntVal.java b/checker/tests/value-index-interaction/OverrideIntVal.java
new file mode 100644
index 0000000..8e3e37a
--- /dev/null
+++ b/checker/tests/value-index-interaction/OverrideIntVal.java
@@ -0,0 +1,64 @@
+import org.checkerframework.checker.index.qual.*;
+import org.checkerframework.common.value.qual.BottomVal;
+import org.checkerframework.common.value.qual.IntVal;
+
+public class OverrideIntVal {
+
+  @NonNegative int foo(@IntVal(0) int zero) {
+    return zero;
+  }
+
+  @NonNegative int bar(@BottomVal int bottom) {
+    return bottom;
+  }
+
+  @NonNegative int m() {
+    return 0;
+  }
+
+  @IntVal({0, 1, 2, 3}) int m2() {
+    return 0;
+  }
+
+  @GTENegativeOne int n() {
+    return -1;
+  }
+
+  @Positive int p() {
+    return 1;
+  }
+}
+
+class OverrideIntValSub extends OverrideIntVal {
+  @Override
+  @IntVal(0) int m() {
+    return 0;
+  }
+
+  @Override
+  @IntVal(0) int m2() {
+    return 0;
+  }
+
+  @Override
+  @IntVal(0) int n() {
+    return 0;
+  }
+
+  @Override
+  @IntVal(2) int p() {
+    return 2;
+  }
+}
+
+class OverrideIntValBottom extends OverrideIntVal {
+  @Override
+  @BottomVal int m() {
+    throw new Error("never returns normally");
+  }
+
+  @Override
+  @BottomVal int m2() {
+    throw new Error("never returns normally");
+  }
+}
diff --git a/checker/tests/wpi-README b/checker/tests/wpi-README
new file mode 100644
index 0000000..7da3412
--- /dev/null
+++ b/checker/tests/wpi-README
@@ -0,0 +1,31 @@
+All tests for the whole-program-inference feature must be added to the
+"non-annotated" folder. These tests must have expected error comments
+(// :: error...) in places where the type-checker issues an error.
+These are locations that an annotation is needed for it to
+type-check. Also, these tests must ONLY have expected error comments for those
+cases -- no other expected errors comments should be added.
+
+The task wholeProgramInferenceTests tests the whole-program inference
+in three steps:
+
+1. The WholeProgramInferenceTest Checker will type-check all files in the
+"non-annotated" folder, writing the inferred types of some elements into
+.jaif files. The inferred types are written into .jaif files, but are not
+considered during this type-check -- for that reason the expected error comments
+are necessary.
+
+2. All tests in "non-annotated" are copied to a temporary directory, named
+"annotated". All expected error comments are removed from the files in
+"annotated", and the insert-annotations-to-source tool will insert the
+annotations that were inferred in the previous step into the files of the
+temporary directory.
+
+3. The WholeProgramInferenceValidationTest Checker will type-check all files in
+the temporary "annotated" folder. The expected error comments were removed,
+but the inferred types that were inserted should remove their occurrence during
+the type-checking.
+
+If an error should persist even after the whole-program inference, add the test
+to the "non-annotated/ExpectedErrors.java" file. This is the only file
+where the expected error comments are not removed when copied to
+"annotated/ExpectedErrors.java".
diff --git a/checker/tests/wpi-many/README.md b/checker/tests/wpi-many/README.md
new file mode 100644
index 0000000..26353c1
--- /dev/null
+++ b/checker/tests/wpi-many/README.md
@@ -0,0 +1,28 @@
+The file `testin.txt` in this directory contains a list of github repositories that
+are used to test the `wpi-many.sh` script (stored in `checker/bin`). Each entry is a
+git URL and commit hash, separated by whitespace.
+
+The projects listed in `testin.txt` are derived from plume-lib projects; each is a hard fork.
+These forks have had their (inferrable) annotations removed, and their typical checker
+build infrastructure disabled. The `./gradlew testWpiScripts` task defined in `checker/build.gradle`
+runs the `wpi-many.sh` script on these projects, and then checks that they typecheck afterwards.
+
+To add a new project (named `$PROJECT` below) to `testin.txt`, follow these steps:
+1. Create a new GitHub repository under your own user name with the name "wpi-many-tests-$PROJECT".
+Do not initialize it with any content.
+2. Clone that repo and the project you intend to add locally.
+3. Copy the contents of the project you are adding to your new repo and commit the result.
+4. Run `java -cp $CHECKERFRAMEWORK/checker/dist/checker.jar org.checkerframework.framework.stub.RemoveAnnotationsForInference .` on your new repository.
+5. Edit the build file of the new project and disable the Checker Framework. This is usually done
+by commenting out the checkerframework-gradle-plugin and checkerFramework lines for Gradle users.
+In addition, you probably will need to add `org.checkerframework:checker-qual:$LATEST-CF-VERSION` as an
+`implementation` dependency. Ensure that the build still succeeds (correcting any errors) and then commit.
+6. Run `wpi.sh` on the project yourself and locate any annotations that cannot be inferred by WPI and/or
+any false positives from the relevant typecheckers (see `checker/build.gradle` for the list of
+checkers that the `testWpiScripts` task uses). You must add annotations or warning suppressions to your
+project until running `wpi.sh` on the project with those checkers produces no errors in its final iteration.
+When that is the case, commit the result and note the commit hash.
+7. Push the state of your new repo to GitHub.
+8. Add the HTTPS version of the GitHub URL (without the `.git`!) and the commit hash you noted in step 6
+to `testin.txt`.
+9. Run `./gradlew testWpiScripts` to ensure that the tests still pass, and correct any errors.
diff --git a/checker/tests/wpi-many/testin.txt b/checker/tests/wpi-many/testin.txt
new file mode 100644
index 0000000..cc97bb8
--- /dev/null
+++ b/checker/tests/wpi-many/testin.txt
@@ -0,0 +1,4 @@
+https://github.com/kelloggm/wpi-many-tests-bcel-util f15bc9cd3f6f62bbea459663be49137a64797611
+https://github.com/kelloggm/wpi-many-tests-bibtex-clean 409461c024a3b73d68af5eb7b5861cc047794eff
+https://github.com/kelloggm/wpi-many-tests-html-pretty-print c1c12d7f296061ef4c8a180c2832b29a5461e5c5
+https://github.com/kelloggm/-wpi-many-tests-bibtex-clean c24239d895236a88aa3bdc1459b2d2253723233c
diff --git a/checker/tests/wpi-nullness/non-annotated/MonotonicNonNullInferenceTest.java b/checker/tests/wpi-nullness/non-annotated/MonotonicNonNullInferenceTest.java
new file mode 100644
index 0000000..13342f2
--- /dev/null
+++ b/checker/tests/wpi-nullness/non-annotated/MonotonicNonNullInferenceTest.java
@@ -0,0 +1,71 @@
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class MonotonicNonNullInferenceTest {
+
+  // :: warning: (initialization.static.field.uninitialized)
+  static String staticString1;
+
+  // :: warning: (assignment)
+  static String staticString2 = null;
+
+  static String staticString3;
+
+  String instanceString1;
+
+  // :: warning: (assignment)
+  String instanceString2 = null;
+
+  String instanceString3;
+
+  static {
+    // :: warning: (assignment)
+    staticString3 = null;
+  }
+
+  // :: warning: (initialization.fields.uninitialized)
+  MonotonicNonNullInferenceTest() {
+    String instanceString3 = "hello";
+  }
+
+  static void m1(String arg) {
+    staticString1 = arg;
+    staticString2 = arg;
+    staticString3 = arg;
+  }
+
+  void m2(String arg) {
+    instanceString1 = arg;
+    instanceString2 = arg;
+    instanceString3 = arg;
+  }
+
+  void hasSideEffect() {}
+
+  void testMonotonicNonNull() {
+    @NonNull String s;
+    if (staticString1 != null) {
+      hasSideEffect();
+      s = staticString1;
+    }
+    if (staticString2 != null) {
+      hasSideEffect();
+      s = staticString2;
+    }
+    if (staticString3 != null) {
+      hasSideEffect();
+      s = staticString3;
+    }
+    if (instanceString1 != null) {
+      hasSideEffect();
+      s = instanceString1;
+    }
+    if (instanceString2 != null) {
+      hasSideEffect();
+      s = instanceString2;
+    }
+    if (instanceString3 != null) {
+      hasSideEffect();
+      s = instanceString3;
+    }
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/AnnotationWithFieldTest.java b/checker/tests/wpi-testchecker/non-annotated/AnnotationWithFieldTest.java
new file mode 100644
index 0000000..2f41e70
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/AnnotationWithFieldTest.java
@@ -0,0 +1,50 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.SiblingWithFields;
+
+public class AnnotationWithFieldTest {
+
+  private String fields;
+
+  private String emptyFields;
+
+  void testAnnotationWithFields() {
+    fields = getSiblingWithFields();
+    // :: warning: (argument)
+    expectsSiblingWithFields(fields);
+  }
+
+  void testAnnotationWithEmptyFields() {
+    emptyFields = getSiblingWithFieldsEmpty();
+    // :: warning: (argument)
+    expectsSiblingWithEmptyFields(emptyFields);
+  }
+
+  void expectsSiblingWithFields(
+      @SiblingWithFields(
+              value = {"test", "test2"},
+              value2 = "test3")
+          String s) {}
+
+  void expectsSiblingWithEmptyFields(
+      @SiblingWithFields(
+              value = {},
+              value2 = "")
+          String s) {}
+
+  @SuppressWarnings("cast.unsafe")
+  String getSiblingWithFields() {
+    return (@SiblingWithFields(
+            value = {"test", "test2"},
+            value2 = "test3")
+        String)
+        "";
+  }
+
+  @SuppressWarnings("cast.unsafe")
+  String getSiblingWithFieldsEmpty() {
+    return (@SiblingWithFields(
+            value = {},
+            value2 = "")
+        String)
+        "";
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/Anonymous.java b/checker/tests/wpi-testchecker/non-annotated/Anonymous.java
new file mode 100644
index 0000000..33eb4f0
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/Anonymous.java
@@ -0,0 +1,37 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Parent;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Top;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.WholeProgramInferenceBottom;
+
+public class Anonymous {
+  public static int field1; // parent
+  public static int field2; // sib2
+
+  public Anonymous() {
+    field1 = getSibling1();
+  }
+
+  void testPublicInference() {
+    // :: warning: (argument)
+    expectsSibling2(field2);
+    // :: warning: (argument)
+    expectsParent(field1);
+    // :: warning: (argument)
+    expectsParent(field2);
+  }
+
+  void expectsBottom(@WholeProgramInferenceBottom int t) {}
+
+  void expectsSibling1(@Sibling1 int t) {}
+
+  void expectsSibling2(@Sibling2 int t) {}
+
+  void expectsTop(@Top int t) {}
+
+  void expectsParent(@Parent int t) {}
+
+  @Sibling1 int getSibling1() {
+    return (@Sibling1 int) 0;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/AnonymousClassField.java b/checker/tests/wpi-testchecker/non-annotated/AnonymousClassField.java
new file mode 100644
index 0000000..74a2511
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/AnonymousClassField.java
@@ -0,0 +1,8 @@
+// A test that ensures that stub-based inference correctly handles fields
+// with inferred types that are anonymous classes.
+
+import java.util.*;
+
+public class AnonymousClassField {
+  public static final List foo = new ArrayList<String>() {};
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/CompoundTypeTest.java b/checker/tests/wpi-testchecker/non-annotated/CompoundTypeTest.java
new file mode 100644
index 0000000..31d5ecc
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/CompoundTypeTest.java
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+
+public class CompoundTypeTest {
+  // The default type for fields is @DefaultType.
+  Object[] field;
+
+  void assign() {
+    field = getCompoundType();
+  }
+
+  void test() {
+    // :: warning: (argument)
+    expectsCompoundType(field);
+  }
+
+  void expectsCompoundType(@Sibling1 Object @Sibling2 [] obj) {}
+
+  @Sibling1 Object @Sibling2 [] getCompoundType() {
+    @SuppressWarnings("cast.unsafe")
+    @Sibling1 Object @Sibling2 [] out = (@Sibling1 Object @Sibling2 []) new Object[1];
+    return out;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/CompoundTypeTest2.java b/checker/tests/wpi-testchecker/non-annotated/CompoundTypeTest2.java
new file mode 100644
index 0000000..7b51f68
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/CompoundTypeTest2.java
@@ -0,0 +1,4 @@
+public class CompoundTypeTest2 {
+  private static int[] foo = new int[10];
+  private static String[] bar = new String[10];
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/ConflictingAnnotationsTest.java b/checker/tests/wpi-testchecker/non-annotated/ConflictingAnnotationsTest.java
new file mode 100644
index 0000000..4b595d0
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/ConflictingAnnotationsTest.java
@@ -0,0 +1,29 @@
+// Tests whether inferring an @Sibling1 annotation when another @Sibling1 annotation in the default
+// package is present causes problems. Conflicting annotations that are not in the default package
+// are not a problem, because TypeMirror#toString prints their fully-qualified names, making
+// namespace collisions impossible.
+
+public class ConflictingAnnotationsTest {
+
+  int getWPINamespaceSibling1() {
+    return getSibling1();
+  }
+
+  // This version of Sibling1 is not typechecked - it doesn't belong to the checker and instead is
+  // defined in the Sibling1.java file in this directory.
+  @Sibling1 Object getLocalSibling1(Object o) {
+    return o;
+  }
+
+  void test() {
+    // :: warning: argument
+    expectsSibling1(getWPINamespaceSibling1());
+  }
+
+  @org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1 int getSibling1() {
+    return 1;
+  }
+
+  void expectsSibling1(
+      @org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1 int i) {}
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/ConstructorTest.java b/checker/tests/wpi-testchecker/non-annotated/ConstructorTest.java
new file mode 100644
index 0000000..b2d68b4
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/ConstructorTest.java
@@ -0,0 +1,12 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Top;
+
+public class ConstructorTest {
+
+  public ConstructorTest(int top) {}
+
+  void test() {
+    @Top int top = (@Top int) 0;
+    // :: warning: (argument)
+    new ConstructorTest(top);
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/DefaultsTest.java b/checker/tests/wpi-testchecker/non-annotated/DefaultsTest.java
new file mode 100644
index 0000000..2c4d5f2
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/DefaultsTest.java
@@ -0,0 +1,28 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.DefaultType;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.WholeProgramInferenceBottom;
+
+// The @DefaultType annotation, which is the default for every location, is forbidden to be written
+// anywhere. This class attempts to infer @DefaultType in several locations, and the annotated
+// version of this class (in the annotated folder) should have no explicit @DefaultType annotations.
+public class DefaultsTest {
+  String defaultField = "";
+  String defaultField2;
+
+  void test() {
+    @SuppressWarnings("all") // To allow the use of the explicit @DefaultType.
+    @DefaultType String explicitDefault = "";
+    defaultField2 = explicitDefault;
+  }
+
+  // This method's return type should not be updated by the whole-program inference
+  // since it is the default.
+  String lubTest() {
+    if (Math.random() > 0.5) {
+      return ""; // @DefaultType
+    } else {
+      @SuppressWarnings("cast.unsafe")
+      @WholeProgramInferenceBottom String s = (@WholeProgramInferenceBottom String) "";
+      return s;
+    }
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/DeviceTypeTest.java b/checker/tests/wpi-testchecker/non-annotated/DeviceTypeTest.java
new file mode 100644
index 0000000..ca9c101
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/DeviceTypeTest.java
@@ -0,0 +1,14 @@
+// In the wild, code like this caused WPI to generate a stub file for DeviceType that used "class"
+// instead of "enum".
+
+public class DeviceTypeTest {
+  public enum DeviceType {
+    TRACKER;
+  }
+
+  private final DeviceType deviceType;
+
+  public DeviceTypeTest() {
+    deviceType = DeviceType.valueOf("tracker");
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/DoubleGeneric.java b/checker/tests/wpi-testchecker/non-annotated/DoubleGeneric.java
new file mode 100644
index 0000000..fc968b0
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/DoubleGeneric.java
@@ -0,0 +1,8 @@
+// Tests that stub files are printed correctly for fields with multiple levels of generic types.
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DoubleGeneric {
+  static Map<String, Map<String, String>> map10 = new HashMap<String, Map<String, String>>();
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/EnsuresQualifierTest.java b/checker/tests/wpi-testchecker/non-annotated/EnsuresQualifierTest.java
new file mode 100644
index 0000000..edae696
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/EnsuresQualifierTest.java
@@ -0,0 +1,82 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Parent;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Top;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.WholeProgramInferenceBottom;
+
+class EnsuresQualifierTest {
+
+  @Top int field1;
+  @Top int field2;
+
+  @Top int top;
+  @Parent int parent;
+  @Sibling1 int sibling1;
+  @Sibling2 int sibling2;
+  @WholeProgramInferenceBottom int bottom;
+
+  void field1IsParent() {
+    field1 = parent;
+  }
+
+  void field1IsParent_2(boolean b) {
+    if (b) {
+      field1 = sibling1;
+    } else {
+      field1 = sibling2;
+    }
+  }
+
+  void field1IsSibling2() {
+    field1 = sibling2;
+  }
+
+  void field1IsSibling2_2(boolean b) {
+    if (b) {
+      field1 = sibling2;
+    } else {
+      field1 = bottom;
+    }
+  }
+
+  void parentIsSibling1() {
+    parent = sibling1;
+  }
+
+  // Prevent refinement of the `parent` field variable.
+  void parentIsParent(@Parent int x) {
+    parent = x;
+  }
+
+  void noEnsures() {}
+
+  void client1() {
+    field1IsParent();
+    // :: warning: (assignment)
+    @Parent int p = field1;
+  }
+
+  void client2() {
+    field1IsParent_2(true);
+    // :: warning: (assignment)
+    @Parent int p = field1;
+  }
+
+  void client3() {
+    field1IsSibling2();
+    // :: warning: (assignment)
+    @Sibling2 int x = field1;
+  }
+
+  void client4() {
+    field1IsSibling2_2(true);
+    // :: warning: (assignment)
+    @Sibling2 int x = field1;
+  }
+
+  void client5() {
+    parentIsSibling1();
+    // :: warning: (assignment)
+    @Sibling1 int x = parent;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/EnumConstants.java b/checker/tests/wpi-testchecker/non-annotated/EnumConstants.java
new file mode 100644
index 0000000..c6dc6ed
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/EnumConstants.java
@@ -0,0 +1,21 @@
+// @skip-test
+
+// Check that types on enum constants can be inferred.  This test doesn't succeed for either kind of
+// WPI, because WPI doesn't learn anything about enum constants from how they're used. They also
+// cannot be assigned to, so there's no way for WPI to learn their types.
+
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+public class EnumConstants {
+  enum MyEnum {
+    ONE,
+    TWO;
+  }
+
+  void requiresS1(@Sibling1 MyEnum e) {}
+
+  void test() {
+    // :: warning: argument
+    requiresS1(MyEnum.ONE);
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/EnumTest.java b/checker/tests/wpi-testchecker/non-annotated/EnumTest.java
new file mode 100644
index 0000000..183fa6d
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/EnumTest.java
@@ -0,0 +1,29 @@
+public class EnumTest {
+  public enum MyEnum {
+    ONE("ONE"),
+    TWO("TWO"),
+    THREE("THREE");
+
+    private final String value;
+
+    private MyEnum(String value) {
+      this.value = value;
+    }
+
+    @Override
+    public String toString() {
+      return value;
+    }
+
+    public static MyEnum fromValue(String value) throws IllegalArgumentException {
+      for (MyEnum method : MyEnum.values()) {
+        String methodString = method.toString();
+        if (methodString != null && methodString.equals(value)) {
+          return method;
+        }
+      }
+
+      throw new IllegalArgumentException("Cannot create enum from: " + value);
+    }
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/EnumWithInnerClass.java b/checker/tests/wpi-testchecker/non-annotated/EnumWithInnerClass.java
new file mode 100644
index 0000000..3ce916e
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/EnumWithInnerClass.java
@@ -0,0 +1,21 @@
+// This test ensures that enums with inner classes are printed properly to avoid crashing the stub
+// parser, which was a problem with an earlier version of stub-based WPI.
+
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+enum EnumWithInnerClass {
+  CONSTANT;
+
+  private static class MyInnerClass {
+    int getSibling1() {
+      return (@Sibling1 int) 0;
+    }
+
+    void requireSibling1(@Sibling1 int x) {}
+
+    void test() {
+      // :: warning: argument
+      requireSibling1(getSibling1());
+    }
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/ExpectedErrors.java b/checker/tests/wpi-testchecker/non-annotated/ExpectedErrors.java
new file mode 100644
index 0000000..3afeb18
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/ExpectedErrors.java
@@ -0,0 +1,243 @@
+import java.lang.reflect.Field;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Parent;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.ToIgnore;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Top;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.WholeProgramInferenceBottom;
+import org.checkerframework.framework.qual.IgnoreInWholeProgramInference;
+
+/**
+ * This file contains expected errors that should exist even after the jaif type inference occurs.
+ */
+public class ExpectedErrors {
+
+  // Case where the declared type is a supertype of the refined type.
+  private @Top int privateDeclaredField;
+  public @Top int publicDeclaredField;
+
+  // The type of both privateDeclaredField and publicDeclaredField are
+  // not refined to @WholeProgramInferenceBottom.
+  void assignFieldsToSibling1() {
+    privateDeclaredField = getSibling1();
+    publicDeclaredField = getSibling1();
+  }
+
+  void testFields() {
+    // :: warning: (argument)
+    expectsSibling1(privateDeclaredField);
+    // :: warning: (argument)
+    expectsSibling1(publicDeclaredField);
+  }
+
+  // Case where the declared type is a subtype of the refined type.
+  private @WholeProgramInferenceBottom int privateDeclaredField2;
+  public @WholeProgramInferenceBottom int publicDeclaredField2;
+
+  // The refinement cannot happen and an assignemnt type incompatible error occurs.
+  void assignFieldsToTop() {
+    // :: warning: (assignment)
+    privateDeclaredField2 = getTop();
+    // :: warning: (assignment)
+    publicDeclaredField2 = getTop();
+  }
+
+  // No errors should be issued below:
+  void assignFieldsToBot() {
+    privateDeclaredField2 = getBottom();
+    publicDeclaredField2 = getBottom();
+  }
+
+  // Testing that the types above were not widened.
+  void testFields2() {
+    expectsBottom(privateDeclaredField2);
+    expectsBottom(publicDeclaredField2);
+  }
+
+  // LUB TEST
+  // The default type for fields is @Top.
+  private static int lubPrivateField;
+  public static int lubPublicField;
+
+  void assignLubFieldsToSibling1() {
+    lubPrivateField = getSibling1();
+    lubPublicField = getSibling1();
+  }
+
+  static {
+    lubPrivateField = getSibling2();
+    lubPublicField = getSibling2();
+  }
+
+  void testLUBFields1() {
+    // :: warning: (argument)
+    expectsSibling1(lubPrivateField);
+    // :: warning: (argument)
+    expectsSibling1(lubPublicField);
+  }
+
+  void testLUBFields2() {
+    // :: warning: (argument)
+    expectsSibling2(lubPrivateField);
+    // :: warning: (argument)
+    expectsSibling2(lubPublicField);
+  }
+
+  private static boolean bool = false;
+
+  public static int lubTest() {
+    if (bool) {
+      return (@Sibling1 int) 0;
+    } else {
+      return (@Sibling2 int) 0;
+    }
+  }
+
+  public @Sibling1 int getSibling1Wrong() {
+    int x = lubTest();
+    // :: warning: (return)
+    return x;
+  }
+
+  public @Sibling2 int getSibling2Wrong() {
+    int x = lubTest();
+    // :: warning: (return)
+    return x;
+  }
+
+  void expectsSibling1(@Sibling1 int t) {}
+
+  void expectsSibling2(@Sibling2 int t) {}
+
+  void expectsBottom(@WholeProgramInferenceBottom int t) {}
+
+  void expectsBottom(@WholeProgramInferenceBottom String t) {}
+
+  void expectsTop(@Top int t) {}
+
+  void expectsParent(@Parent int t) {}
+
+  static @Sibling1 int getSibling1() {
+    return 0;
+  }
+
+  static @Sibling2 int getSibling2() {
+    return 0;
+  }
+
+  @WholeProgramInferenceBottom int getBottom() {
+    return 0;
+  }
+
+  @Top int getTop() {
+    return 0;
+  }
+
+  // Method Field.setBoolean != ExpectedErrors.setBoolean.
+  // No refinement should happen.
+  void test(Field f) throws Exception {
+    f.setBoolean(null, false);
+  }
+
+  void setBoolean(Object o, boolean b) {
+    // :: warning: (assignment)
+    @WholeProgramInferenceBottom Object bot = o;
+  }
+
+  public class SuppressWarningsTest {
+    // Tests that whole-program inference in a @SuppressWarnings block is ignored.
+    private int i;
+    private int i2;
+
+    @SuppressWarnings("all")
+    public void suppressWarningsTest() {
+      i = (@Sibling1 int) 0;
+      i2 = getSibling1();
+    }
+
+    public void suppressWarningsTest2() {
+      SuppressWarningsInner.i = (@Sibling1 int) 0;
+      SuppressWarningsInner.i2 = getSibling1();
+    }
+
+    public void suppressWarningsValidation() {
+      // :: warning: (argument)
+      expectsSibling1(i);
+      // :: warning: (argument)
+      expectsSibling1(i2);
+      // :: warning: (argument)
+      expectsSibling1(SuppressWarningsInner.i);
+      // :: warning: (argument)
+      expectsSibling1(SuppressWarningsInner.i2);
+      // :: warning: (argument)
+      expectsSibling1(suppressWarningsMethodReturn());
+
+      suppressWarningsMethodParams(getSibling1());
+    }
+
+    @SuppressWarnings("all")
+    public int suppressWarningsMethodReturn() {
+      return getSibling1();
+    }
+
+    // It is problematic to automatically test whole-program inference for method params when
+    // suppressing warnings.
+    // Since we must use @SuppressWarnings() for the method, we won't be able to catch any error
+    // inside the method body.  Verified manually that in the "annotated" folder param's type wasn't
+    // updated.
+    @SuppressWarnings("all")
+    public void suppressWarningsMethodParams(int param) {}
+  }
+
+  @SuppressWarnings("all")
+  static class SuppressWarningsInner {
+    public static int i;
+    public static int i2;
+  }
+
+  class NullTest {
+    // The default type for fields is @DefaultType.
+    private String privateField;
+    public String publicField;
+
+    // The types of both fields are not refined to @WholeProgramInferenceBottom, as whole-program
+    // inference never performs refinement in the presence of the null literal.
+    @SuppressWarnings("value")
+    void assignFieldsToBottom() {
+      privateField = null;
+      publicField = null;
+    }
+
+    // Testing the refinement above.
+    void testFields() {
+      // :: warning: (argument)
+      expectsBottom(privateField);
+      // :: warning: (argument)
+      expectsBottom(publicField);
+    }
+  }
+
+  class IgnoreMetaAnnotationTest2 {
+    @ToIgnore int field;
+    @IgnoreInWholeProgramInference int field2;
+
+    void foo() {
+      field = getSibling1();
+      field2 = getSibling1();
+    }
+
+    void test() {
+      // :: warning: (argument)
+      expectsSibling1(field);
+      // :: warning: (argument)
+      expectsSibling1(field2);
+    }
+  }
+
+  class AssignParam {
+    public void f(@WholeProgramInferenceBottom Object param) {
+      // :: warning: assignment
+      param = ((@Top Object) null);
+    }
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/IgnoreMetaAnnotationTest1.java b/checker/tests/wpi-testchecker/non-annotated/IgnoreMetaAnnotationTest1.java
new file mode 100644
index 0000000..f3ce7a6
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/IgnoreMetaAnnotationTest1.java
@@ -0,0 +1,22 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+// See ExpectedErrors#IgnoreMetaAnnotationTest2
+public class IgnoreMetaAnnotationTest1 {
+
+  int field2;
+
+  void foo() {
+    field2 = getSibling1();
+  }
+
+  void test() {
+    // :: warning: (argument)
+    expectsSibling1(field2);
+  }
+
+  void expectsSibling1(@Sibling1 int t) {}
+
+  static @Sibling1 int getSibling1() {
+    return 0;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/ImplicitAnnosTest.java b/checker/tests/wpi-testchecker/non-annotated/ImplicitAnnosTest.java
new file mode 100644
index 0000000..a5de877
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/ImplicitAnnosTest.java
@@ -0,0 +1,7 @@
+public class ImplicitAnnosTest {
+
+  void test() {
+    StringBuffer sb = new StringBuffer();
+    StringBuffer sb2 = sb;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/InheritanceTest.java b/checker/tests/wpi-testchecker/non-annotated/InheritanceTest.java
new file mode 100644
index 0000000..551e4fd
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/InheritanceTest.java
@@ -0,0 +1,22 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.WholeProgramInferenceBottom;
+
+class IParent {
+  int field;
+
+  public void expectsBotNoSignature(int t) {
+    // :: warning: (argument)
+    expectsBot(t);
+    // :: warning: (argument)
+    expectsBot(field);
+  }
+
+  void expectsBot(@WholeProgramInferenceBottom int t) {}
+}
+
+class IChild extends IParent {
+  void test1() {
+    @WholeProgramInferenceBottom int bot = (@WholeProgramInferenceBottom int) 0;
+    expectsBotNoSignature(bot);
+    field = bot;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/InnerTypeTest.java b/checker/tests/wpi-testchecker/non-annotated/InnerTypeTest.java
new file mode 100644
index 0000000..65d56a4
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/InnerTypeTest.java
@@ -0,0 +1,17 @@
+public class InnerTypeTest {
+  public static String toStringQuoted(Object[] a) {
+    return toString(a, true);
+  }
+
+  public static String toString(Object[] a, boolean quoted) {
+    if (a == null) {
+      return "null";
+    }
+    StringBuffer sb = new StringBuffer();
+    return sb.toString();
+  }
+
+  public void bar() {
+    assert InnerTypeTest.toStringQuoted((Object[]) null).equals("null");
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/InnerTypeTest2.java b/checker/tests/wpi-testchecker/non-annotated/InnerTypeTest2.java
new file mode 100644
index 0000000..3c90c7d
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/InnerTypeTest2.java
@@ -0,0 +1,10 @@
+public class InnerTypeTest2 {
+  public static int[] min_max(int[] a) {
+    if (a.length == 0) {
+      return null;
+    }
+    int result_min = a[0];
+    int result_max = a[0];
+    return new int[] {result_min, result_max};
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/InnerTypeTest3.java b/checker/tests/wpi-testchecker/non-annotated/InnerTypeTest3.java
new file mode 100644
index 0000000..522d352
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/InnerTypeTest3.java
@@ -0,0 +1,9 @@
+public class InnerTypeTest3 {
+  private int[] nums;
+
+  private static byte[] buffer = new byte[4096];
+
+  private static final char[] digits = {
+    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+  };
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/InterfaceTest.java b/checker/tests/wpi-testchecker/non-annotated/InterfaceTest.java
new file mode 100644
index 0000000..4c9ec33
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/InterfaceTest.java
@@ -0,0 +1,19 @@
+// checks that types can be inferred for constants defined in interfaces
+
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+@SuppressWarnings("cast.unsafe")
+interface InterfaceTest {
+  public String toaster = getSibling1();
+
+  public static @Sibling1 String getSibling1() {
+    return (@Sibling1 String) "foo";
+  }
+
+  default void requireSibling1(@Sibling1 String x) {}
+
+  default void testX() {
+    // :: warning: argument
+    requireSibling1(toaster);
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/LUBAssignmentTest.java b/checker/tests/wpi-testchecker/non-annotated/LUBAssignmentTest.java
new file mode 100644
index 0000000..583c353
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/LUBAssignmentTest.java
@@ -0,0 +1,50 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Parent;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+
+public class LUBAssignmentTest {
+  // The default type for fields is @DefaultType.
+  private static int privateField;
+  public static int publicField;
+
+  void assignFieldsToSibling1() {
+    privateField = getSibling1();
+    publicField = getSibling1();
+  }
+
+  static {
+    privateField = getSibling2();
+    publicField = getSibling2();
+  }
+
+  // LUB between @Sibling1 and @Sibling2 is @Parent, therefore the assignments
+  // above refine the type of privateField to @Parent.
+  void testFields() {
+    // :: warning: (argument)
+    expectsParent(privateField);
+    // :: warning: (argument)
+    expectsParent(publicField);
+  }
+
+  void expectsParent(@Parent int t) {}
+
+  static @Sibling1 int getSibling1() {
+    return 0;
+  }
+
+  static @Sibling2 int getSibling2() {
+    return 0;
+  }
+
+  String lubTest2() {
+    if (Math.random() > 0.5) {
+      @SuppressWarnings("cast.unsafe")
+      @Sibling1 String s = (@Sibling1 String) "";
+      return s;
+    } else {
+      @SuppressWarnings("cast.unsafe")
+      @Sibling2 String s = (@Sibling2 String) "";
+      return s;
+    }
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/LambdaReturn.java b/checker/tests/wpi-testchecker/non-annotated/LambdaReturn.java
new file mode 100644
index 0000000..44300af
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/LambdaReturn.java
@@ -0,0 +1,18 @@
+// This test checks that WPI doesn't try to infer a return type for lambda expressions.
+// This specific example came up in a case study.
+
+import java.io.FileFilter;
+
+public class LambdaReturn {
+  void test() {
+    FileFilter docxFilter =
+        pathname -> {
+          // We only want to process *.docx files, everything else can be skipped.
+          if (pathname.isFile() && pathname.getName().matches(".*\\.docx")) {
+            return true;
+          }
+
+          return false;
+        };
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/LocalClassTest.java b/checker/tests/wpi-testchecker/non-annotated/LocalClassTest.java
new file mode 100644
index 0000000..b124cef
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/LocalClassTest.java
@@ -0,0 +1,11 @@
+// test case for https://github.com/typetools/checker-framework/issues/3461
+
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+public class LocalClassTest {
+  public void method() {
+    class Local {
+      Object o = (@Sibling1 Object) null;
+    }
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/MethodDefinedInSupertype.java b/checker/tests/wpi-testchecker/non-annotated/MethodDefinedInSupertype.java
new file mode 100644
index 0000000..1246b64
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/MethodDefinedInSupertype.java
@@ -0,0 +1,23 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Parent;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+abstract class MethodDefinedInSupertype {
+
+  void test() {
+    // :: warning: argument
+    expectsSibling1(shouldReturnSibling1());
+  }
+
+  public void expectsSibling1(@Sibling1 int t) {}
+
+  public abstract int shouldReturnSibling1();
+
+  void testMultipleOverrides() {
+    // :: warning: argument
+    expectsParent(shouldReturnParent());
+  }
+
+  public void expectsParent(@Parent int t1) {}
+
+  public abstract int shouldReturnParent();
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/MethodOverrideInSubtype.java b/checker/tests/wpi-testchecker/non-annotated/MethodOverrideInSubtype.java
new file mode 100644
index 0000000..dded4fd
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/MethodOverrideInSubtype.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+public class MethodOverrideInSubtype extends MethodDefinedInSupertype {
+  @java.lang.Override
+  public int shouldReturnSibling1() {
+    return getSibling1();
+  }
+
+  private @Sibling1 int getSibling1() {
+    return 0;
+  }
+
+  @java.lang.Override
+  public int shouldReturnParent() {
+    return getSibling1();
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/MethodOverrideInSubtype2.java b/checker/tests/wpi-testchecker/non-annotated/MethodOverrideInSubtype2.java
new file mode 100644
index 0000000..46bea80
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/MethodOverrideInSubtype2.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+
+abstract class MethodOverrideInSubtype2 extends MethodDefinedInSupertype {
+
+  private @Sibling2 int getSibling2() {
+    return 0;
+  }
+
+  @java.lang.Override
+  public int shouldReturnParent() {
+    return getSibling2();
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/MethodParameterInferenceTest.java b/checker/tests/wpi-testchecker/non-annotated/MethodParameterInferenceTest.java
new file mode 100644
index 0000000..7780f6d
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/MethodParameterInferenceTest.java
@@ -0,0 +1,13 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+// TODO: Like this one, some tests must verify that it contains the expected
+// output after performing the whole-program inference.
+public class MethodParameterInferenceTest {
+  void foo(int i) {
+    i = getSibling1(); // The type of i must be inferred to @Sibling1.
+  }
+
+  @Sibling1 int getSibling1() {
+    return (@Sibling1 int) 0;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/MethodReturnTest.java b/checker/tests/wpi-testchecker/non-annotated/MethodReturnTest.java
new file mode 100644
index 0000000..9d0e3d8
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/MethodReturnTest.java
@@ -0,0 +1,52 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Parent;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+
+public class MethodReturnTest {
+
+  static int getSibling1NotAnnotated() {
+    return (@Sibling1 int) 0;
+  }
+
+  static @Sibling1 int getSibling1() {
+    // :: warning: (return)
+    return getSibling1NotAnnotated();
+  }
+
+  public static boolean bool = false;
+
+  public static int lubTest() {
+    if (bool) {
+      return (@Sibling1 int) 0;
+    } else {
+      return (@Sibling2 int) 0;
+    }
+  }
+
+  public static @Parent int getParent() {
+    int x = lubTest();
+    // :: warning: (return)
+    return x;
+  }
+
+  class InnerClass {
+    int field = 0;
+
+    int getParent2() {
+      field = getParent();
+      return getParent();
+    }
+
+    void receivesSibling1(int i) {
+      // :: warning: (argument)
+      expectsSibling1(i);
+    }
+
+    void expectsSibling1(@Sibling1 int i) {}
+
+    void test() {
+      @Sibling1 int sib = (@Sibling1 int) 0;
+      receivesSibling1(sib);
+    }
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/MultiDimensionalArrays.java b/checker/tests/wpi-testchecker/non-annotated/MultiDimensionalArrays.java
new file mode 100644
index 0000000..06f66cb
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/MultiDimensionalArrays.java
@@ -0,0 +1,258 @@
+// This test ensures that annotations on different component types of multidimensional arrays
+// are printed correctly.
+
+import java.util.List;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.SiblingWithFields;
+import org.checkerframework.common.aliasing.qual.MaybeAliased;
+import org.checkerframework.common.aliasing.qual.NonLeaked;
+import org.checkerframework.common.aliasing.qual.Unique;
+
+public class MultiDimensionalArrays {
+
+  // two dimensional arrays
+
+  void requiresS1S2(@Sibling1 int @Sibling2 [] x) {}
+
+  int[] twoDimArray;
+
+  void testField() {
+    // :: warning: argument
+    requiresS1S2(twoDimArray);
+  }
+
+  void useField(@Sibling1 int @Sibling2 [] x) {
+    twoDimArray = x;
+  }
+
+  void testParam(int[] x) {
+    // :: warning: argument
+    requiresS1S2(x);
+  }
+
+  void useParam(@Sibling1 int @Sibling2 [] x) {
+    testParam(x);
+  }
+
+  int[] useReturn(@Sibling1 int @Sibling2 [] x) {
+    return x;
+  }
+
+  void testReturn() {
+    requiresS1S2(
+        // :: warning: argument
+        useReturn(
+            // :: warning: argument
+            twoDimArray));
+  }
+
+  // three dimensional arrays
+
+  void requiresS1S2S1(@Sibling1 int @Sibling2 [] @Sibling1 [] x) {}
+
+  int[][] threeDimArray;
+
+  void testField2() {
+    // :: warning: argument
+    requiresS1S2S1(threeDimArray);
+  }
+
+  void useField2(@Sibling1 int @Sibling2 [] @Sibling1 [] x) {
+    threeDimArray = x;
+  }
+
+  void testParam2(int[][] x) {
+    // :: warning: argument
+    requiresS1S2S1(x);
+  }
+
+  void useParam2(@Sibling1 int @Sibling2 [] @Sibling1 [] x) {
+    testParam2(x);
+  }
+
+  int[][] useReturn2(@Sibling1 int @Sibling2 [] @Sibling1 [] x) {
+    return x;
+  }
+
+  void testReturn2() {
+    // :: warning: argument
+    requiresS1S2S1(useReturn2(threeDimArray));
+  }
+
+  // three dimensional array with annotations only on two inner types
+
+  void requiresS1S2N(@Sibling1 int @Sibling2 [][] x) {}
+
+  int[][] threeDimArray2;
+
+  void testField3() {
+    // :: warning: argument
+    requiresS1S2N(threeDimArray2);
+  }
+
+  void useField3(@Sibling1 int @Sibling2 [][] x) {
+    threeDimArray2 = x;
+  }
+
+  void testParam3(int[][] x) {
+    // :: warning: argument
+    requiresS1S2N(x);
+  }
+
+  void useParam3(@Sibling1 int @Sibling2 [][] x) {
+    testParam3(x);
+  }
+
+  int[][] useReturn3(@Sibling1 int @Sibling2 [][] x) {
+    return x;
+  }
+
+  void testReturn3() {
+    // :: warning: argument
+    requiresS1S2N(useReturn3(threeDimArray2));
+  }
+
+  // three dimensional array with annotations only on two array types, not innermost type
+
+  void requiresS2S1(int @Sibling2 [] @Sibling1 [] x) {}
+
+  int[][] threeDimArray3;
+
+  void testField4() {
+    // :: warning: argument
+    requiresS2S1(threeDimArray3);
+  }
+
+  void useField4(int @Sibling2 [] @Sibling1 [] x) {
+    threeDimArray3 = x;
+  }
+
+  void testParam4(int[][] x) {
+    // :: warning: argument
+    requiresS2S1(x);
+  }
+
+  void useParam4(int @Sibling2 [] @Sibling1 [] x) {
+    testParam4(x);
+  }
+
+  int[][] useReturn4(int @Sibling2 [] @Sibling1 [] x) {
+    return x;
+  }
+
+  void testReturn4() {
+    // :: warning: argument
+    requiresS2S1(useReturn4(threeDimArray3));
+  }
+
+  // three-dimensional arrays with arguments in annotations
+
+  void requiresSf1Sf2Sf3(
+          @SiblingWithFields(value = {"test1", "test1"}) int @SiblingWithFields(value = {"test2", "test2"}) []
+                  @SiblingWithFields(value = {"test3"}) []
+              x) {}
+
+  int[][] threeDimArray4;
+
+  void testField5() {
+    // :: warning: argument
+    requiresSf1Sf2Sf3(threeDimArray4);
+  }
+
+  void useField5(
+          @SiblingWithFields(value = {"test1", "test1"}) int @SiblingWithFields(value = {"test2", "test2"}) []
+                  @SiblingWithFields(value = {"test3"}) []
+              x) {
+    threeDimArray4 = x;
+  }
+
+  void testParam5(int[][] x) {
+    // :: warning: argument
+    requiresSf1Sf2Sf3(x);
+  }
+
+  void useParam5(
+          @SiblingWithFields(value = {"test1", "test1"}) int @SiblingWithFields(value = {"test2", "test2"}) []
+                  @SiblingWithFields(value = {"test3"}) []
+              x) {
+    testParam5(x);
+  }
+
+  int[][] useReturn5(
+          @SiblingWithFields(value = {"test1", "test1"}) int @SiblingWithFields(value = {"test2", "test2"}) []
+                  @SiblingWithFields(value = {"test3"}) []
+              x) {
+    return x;
+  }
+
+  void testReturn5() {
+    // :: warning: argument
+    requiresSf1Sf2Sf3(useReturn5(threeDimArray4));
+  }
+
+  // three dimensional array with annotations from other hierarchies that ought to be preserved
+
+  int[][] threeDimArray5;
+
+  void testField6() {
+    // :: warning: argument
+    requiresS1S2S1(threeDimArray5);
+  }
+
+  void useField6(@Sibling1 @Unique int @Sibling2 @NonLeaked [] @Sibling1 @MaybeAliased [] x) {
+    threeDimArray5 = x;
+  }
+
+  void testParam6(int[][] x) {
+    // :: warning: argument
+    requiresS1S2S1(x);
+  }
+
+  void useParam6(@Sibling1 @Unique int @Sibling2 @NonLeaked [] @Sibling1 @MaybeAliased [] x) {
+    testParam6(x);
+  }
+
+  int[][] useReturn6(@Sibling1 @Unique int @Sibling2 @NonLeaked [] @Sibling1 @MaybeAliased [] x) {
+    return x;
+  }
+
+  void testReturn6() {
+    // :: warning: argument
+    requiresS1S2S1(useReturn6(threeDimArray));
+  }
+
+  // Shenanigans with lists + arrays; commented out annotations can't be inferred by either
+  // jaif or stub based WPI for now due to limitations in generics inference.
+
+  List<String[]>[] arrayofListsOfStringArrays;
+
+  void testField7() {
+    // :: warning: argument
+    requiresS1S2L(arrayofListsOfStringArrays);
+  }
+
+  void requiresS1S2L(@Sibling1 List</*@Sibling1*/ String /*@Sibling2*/ []> @Sibling2 [] la) {}
+
+  void useField7(@Sibling1 List</*@Sibling1*/ String /*@Sibling2*/ []> @Sibling2 [] x) {
+    arrayofListsOfStringArrays = x;
+  }
+
+  void testParam7(List<String[]>[] x) {
+    // :: warning: argument
+    requiresS1S2L(x);
+  }
+
+  void useParam7(@Sibling1 List</*@Sibling1*/ String /*@Sibling2*/ []> @Sibling2 [] x) {
+    testParam7(x);
+  }
+
+  List<String[]>[] useReturn7(@Sibling1 List</*@Sibling1*/ String /*@Sibling2*/ []> @Sibling2 [] x) {
+    return x;
+  }
+
+  void testReturn7() {
+    // :: warning: argument
+    requiresS1S2L(useReturn7(arrayofListsOfStringArrays));
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/MultidimensionalAnnotatedArray.java b/checker/tests/wpi-testchecker/non-annotated/MultidimensionalAnnotatedArray.java
new file mode 100644
index 0000000..d8127c9
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/MultidimensionalAnnotatedArray.java
@@ -0,0 +1,11 @@
+// test case for https://github.com/typetools/checker-framework/issues/3422
+
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+public class MultidimensionalAnnotatedArray {
+  boolean[][] field = getArray();
+
+  public boolean[] @Sibling1 [] getArray() {
+    return null;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/NamedInnerClassInAnonymous.java b/checker/tests/wpi-testchecker/non-annotated/NamedInnerClassInAnonymous.java
new file mode 100644
index 0000000..8c51a12
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/NamedInnerClassInAnonymous.java
@@ -0,0 +1,19 @@
+// Tests whether the stub writer correctly handles named inner classes
+// in anonymous classes.
+
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+public class NamedInnerClassInAnonymous {
+  void test() {
+    Object o =
+        new NamedInnerClassInAnonymous() {
+          class NamedInner {
+            // The stub parser cannot parse inner classes, so stub-based WPI should
+            // not attempt to print a stub file for this.
+            public int mySibling1() {
+              return ((@Sibling1 int) 0);
+            }
+          }
+        };
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/OtherAnnotations.java b/checker/tests/wpi-testchecker/non-annotated/OtherAnnotations.java
new file mode 100644
index 0000000..76b0f5f
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/OtherAnnotations.java
@@ -0,0 +1,42 @@
+// Test that having other, unrelated annotations on fields/methods/etc doesn't foul up inference.
+
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.common.aliasing.qual.Unique;
+
+public class OtherAnnotations {
+
+  void requireSibling1(@Sibling1 int a) {}
+
+  @Unique int x;
+
+  void assignX(@Sibling1 int y) {
+    x = y;
+  }
+
+  void useX() {
+    // :: warning: argument
+    requireSibling1(x);
+  }
+
+  void methodWithAnnotatedParam(@Unique int z) {
+    // :: warning: argument
+    requireSibling1(z);
+  }
+
+  void useMethodWithAnnotatedParam(@Sibling1 int w) {
+    methodWithAnnotatedParam(w);
+  }
+
+  @Sibling1 int getSibling1() {
+    return 5;
+  }
+
+  @Unique int getIntVal5() {
+    return getSibling1();
+  }
+
+  void useGetIntVal5() {
+    // :: warning: argument
+    requireSibling1(getIntVal5());
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/OuterClassWithTypeParam.java b/checker/tests/wpi-testchecker/non-annotated/OuterClassWithTypeParam.java
new file mode 100644
index 0000000..04c7a50
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/OuterClassWithTypeParam.java
@@ -0,0 +1,9 @@
+// test file for https://github.com/typetools/checker-framework/issues/3438
+
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+public class OuterClassWithTypeParam<T> {
+  public class InnerClass {
+    Object o = (@Sibling1 Object) null;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/OverloadedMethodsTest.java b/checker/tests/wpi-testchecker/non-annotated/OverloadedMethodsTest.java
new file mode 100644
index 0000000..a06fa64
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/OverloadedMethodsTest.java
@@ -0,0 +1,20 @@
+// This test ensures that overloaded methods with different return types aren't confused.
+
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+public class OverloadedMethodsTest {
+
+  String f;
+
+  String m1() {
+    return this.f;
+  }
+
+  String m1(String x) {
+    return getSibling1();
+  }
+
+  @Sibling1 String getSibling1() {
+    return null;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/OverriddenMethodsTest.java b/checker/tests/wpi-testchecker/non-annotated/OverriddenMethodsTest.java
new file mode 100644
index 0000000..4b0bcfb
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/OverriddenMethodsTest.java
@@ -0,0 +1,54 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+
+class OverriddenMethodsTestParent {
+  public void foo(@Sibling1 Object obj, @Sibling2 Object obj2) {}
+
+  public void bar(@Sibling1 OverriddenMethodsTestParent this, @Sibling2 Object obj) {}
+
+  public void barz(@Sibling1 OverriddenMethodsTestParent this, @Sibling2 Object obj) {}
+
+  public void qux(Object obj1, Object obj2) {
+    // :: warning: argument
+    foo(obj1, obj2);
+  }
+
+  public void thud(Object obj1, Object obj2) {
+    // :: warning: argument
+    foo(obj1, obj2);
+  }
+}
+
+class OverriddenMethodsTestChild extends OverriddenMethodsTestParent {
+  @Override
+  public void foo(Object obj, Object obj2) {
+    // :: warning: (assignment)
+    @Sibling1 Object o = obj;
+    // :: warning: (assignment)
+    @Sibling2 Object o2 = obj2;
+  }
+
+  @Override
+  public void bar(Object obj) {
+    // :: warning: (assignment)
+    @Sibling1 OverriddenMethodsTestChild child = this;
+    // :: warning: (assignment)
+    @Sibling2 Object o = obj;
+  }
+
+  @SuppressWarnings("all")
+  @Override
+  public void barz(Object obj) {}
+
+  public void callbarz(Object obj) {
+    // If the @SuppressWarnings("all") on the overridden version of barz above is not
+    // respected, and the annotations on the receiver and parameter of barz are
+    // inferred, then the following call to barz will result in a method.invocation
+    // and an argument type checking errors.
+    barz(obj);
+  }
+
+  public void callqux(@Sibling1 Object obj1, @Sibling2 Object obj2) {
+    qux(obj1, obj2);
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/OverriddenMethodsTestChildInAnotherCompilationUnit.java b/checker/tests/wpi-testchecker/non-annotated/OverriddenMethodsTestChildInAnotherCompilationUnit.java
new file mode 100644
index 0000000..1104f9f
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/OverriddenMethodsTestChildInAnotherCompilationUnit.java
@@ -0,0 +1,9 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+
+public class OverriddenMethodsTestChildInAnotherCompilationUnit
+    extends OverriddenMethodsTestParent {
+  public void callthud(@Sibling1 Object obj1, @Sibling2 Object obj2) {
+    thud(obj1, obj2);
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/ParameterInferenceTest.java b/checker/tests/wpi-testchecker/non-annotated/ParameterInferenceTest.java
new file mode 100644
index 0000000..799bbcd
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/ParameterInferenceTest.java
@@ -0,0 +1,23 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Parent;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Top;
+
+public class ParameterInferenceTest {
+
+  void test1() {
+    @Parent int parent = (@Parent int) 0;
+    expectsParentNoSignature(parent);
+  }
+
+  void expectsParentNoSignature(int t) {
+    // :: warning: (assignment)
+    @Parent int parent = t;
+  }
+
+  void test2() {
+    @Top int top = (@Top int) 0;
+    // :: warning: (argument)
+    expectsTopNoSignature(top);
+  }
+
+  void expectsTopNoSignature(int t) {}
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/Planet.java b/checker/tests/wpi-testchecker/non-annotated/Planet.java
new file mode 100644
index 0000000..fb41de7
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/Planet.java
@@ -0,0 +1,56 @@
+// This test checks that enums with fields and methods are handled correctly
+
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+@SuppressWarnings(
+    "value" // Do not generate Value Checker annotations, because IndexFileParser cannot handle
+// scientific notation.
+)
+public enum Planet {
+  MERCURY(3.303e+23, 2.4397e6),
+  VENUS(4.869e+24, 6.0518e6),
+  EARTH(5.976e+24, 6.37814e6),
+  MARS(6.421e+23, 3.3972e6),
+  JUPITER(1.9e+27, 7.1492e7),
+  SATURN(5.688e+26, 6.0268e7),
+  URANUS(8.686e+25, 2.5559e7),
+  NEPTUNE(1.024e+26, 2.4746e7);
+
+  public int foo;
+
+  private final double mass; // in kilograms
+  private final double radius; // in meters
+
+  Planet(double mass, double radius) {
+    this.mass = mass;
+    this.radius = radius;
+  }
+
+  private double mass() {
+    return mass;
+  }
+
+  private double radius() {
+    return radius;
+  }
+
+  // universal gravitational constant  (m3 kg-1 s-2)
+  public static final double G = 6.67300E-11;
+
+  double surfaceGravity() {
+    return G * mass / (radius * radius);
+  }
+
+  double surfaceWeight(double otherMass) {
+    return otherMass * surfaceGravity();
+  }
+
+  void test(@Sibling1 int x) {
+    foo = x;
+  }
+
+  void test2() {
+    // :: warning: argument
+    test(foo);
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/PublicFieldTest.java b/checker/tests/wpi-testchecker/non-annotated/PublicFieldTest.java
new file mode 100644
index 0000000..6b59918
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/PublicFieldTest.java
@@ -0,0 +1,67 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Parent;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Top;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.WholeProgramInferenceBottom;
+
+public class PublicFieldTest {
+  public static int field1; // parent
+  public static int field2; // sib2
+
+  public PublicFieldTest() {
+    field1 = getSibling1();
+  }
+
+  void testPublicInference() {
+    // :: warning: (argument)
+    expectsSibling2(field2);
+    // :: warning: (argument)
+    expectsParent(field1);
+    // :: warning: (argument)
+    expectsParent(field2);
+  }
+
+  void expectsBottom(@WholeProgramInferenceBottom int t) {}
+
+  void expectsSibling1(@Sibling1 int t) {}
+
+  void expectsSibling2(@Sibling2 int t) {}
+
+  void expectsTop(@Top int t) {}
+
+  void expectsParent(@Parent int t) {}
+
+  @Sibling1 int getSibling1() {
+    return (@Sibling1 int) 0;
+  }
+}
+
+class AnotherClass {
+
+  int innerField;
+
+  public AnotherClass() {
+    PublicFieldTest.field1 = getSibling2();
+    PublicFieldTest.field2 = getSibling2();
+    innerField = getSibling2();
+  }
+
+  void innerFieldTest() {
+    // :: warning: (argument)
+    expectsSibling2(innerField);
+  }
+
+  @WholeProgramInferenceBottom int getBottom() {
+    return (@WholeProgramInferenceBottom int) 0;
+  }
+
+  @Top int getTop() {
+    return (@Top int) 0;
+  }
+
+  @Sibling2 int getSibling2() {
+    return (@Sibling2 int) 0;
+  }
+
+  void expectsSibling2(@Sibling2 int t) {}
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/RequiresQualifierTest.java b/checker/tests/wpi-testchecker/non-annotated/RequiresQualifierTest.java
new file mode 100644
index 0000000..7e48fdb
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/RequiresQualifierTest.java
@@ -0,0 +1,54 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Parent;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Top;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.WholeProgramInferenceBottom;
+
+class RequiresQualifierTest {
+
+  @Top int field1;
+  @Top int field2;
+
+  @Top int top;
+  @Parent int parent;
+  @Sibling1 int sibling1;
+  @Sibling2 int sibling2;
+  @WholeProgramInferenceBottom int bottom;
+
+  void field1IsParent() {
+    // :: warning: (assignment)
+    @Parent int x = field1;
+  }
+
+  void field1IsSibling2() {
+    // :: warning: (assignment)
+    @Sibling2 int x = field1;
+  }
+
+  void parentIsSibling1() {
+    // :: warning: (assignment)
+    @Sibling1 int x = parent;
+  }
+
+  void noRequirements() {}
+
+  void client2(@Parent int p) {
+    field1 = p;
+    field1IsParent();
+  }
+
+  void client1() {
+    noRequirements();
+
+    field1 = parent;
+    field1IsParent();
+
+    field1 = sibling2;
+    field1IsSibling2();
+    field1 = bottom;
+    field1IsSibling2();
+
+    parent = sibling1;
+    parentIsSibling1();
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/Sibling1.java b/checker/tests/wpi-testchecker/non-annotated/Sibling1.java
new file mode 100644
index 0000000..d5bfa5b
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/Sibling1.java
@@ -0,0 +1,8 @@
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Copy of the Sibling1 annotation, to test how WPI handles annotations with the same simple name.
+ */
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Sibling1 {}
diff --git a/checker/tests/wpi-testchecker/non-annotated/StringConcatenationTest.java b/checker/tests/wpi-testchecker/non-annotated/StringConcatenationTest.java
new file mode 100644
index 0000000..1007e22
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/StringConcatenationTest.java
@@ -0,0 +1,26 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+
+public class StringConcatenationTest {
+
+  private String options_str;
+  private String options_str2;
+
+  void foo() {
+    options_str = getSibling1();
+    options_str2 += getSibling1();
+  }
+
+  void test() {
+    // :: warning: (argument)
+    expectsSibling1(options_str);
+    // :: warning: (argument)
+    expectsSibling1(options_str2);
+  }
+
+  void expectsSibling1(@Sibling1 String t) {}
+
+  @SuppressWarnings("cast.unsafe")
+  @Sibling1 String getSibling1() {
+    return (@Sibling1 String) " ";
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/Tempvars.java b/checker/tests/wpi-testchecker/non-annotated/Tempvars.java
new file mode 100644
index 0000000..dc0b1a4
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/Tempvars.java
@@ -0,0 +1,8 @@
+// test case for https://github.com/typetools/checker-framework/issues/3442
+
+public class Tempvars {
+  static {
+    int i = 0;
+    i++;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/TwoMethodsSameName.java b/checker/tests/wpi-testchecker/non-annotated/TwoMethodsSameName.java
new file mode 100644
index 0000000..72876bb
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/TwoMethodsSameName.java
@@ -0,0 +1,25 @@
+// This test makes sure that if a class has two methods with the same name,
+// the parameters are inferred correctly and are not confused.
+
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+
+public class TwoMethodsSameName {
+
+  void test(int x, int y) {
+    // :: warning: assignment
+    @Sibling1 int x1 = x;
+    // :: warning: assignment
+    @Sibling2 int y1 = y;
+  }
+
+  void test(int z) {
+    // :: warning: assignment
+    @Sibling2 int z1 = z;
+  }
+
+  void uses(@Sibling1 int a, @Sibling2 int b) {
+    test(a, b);
+    test(b);
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/TypeVariablesTest.java b/checker/tests/wpi-testchecker/non-annotated/TypeVariablesTest.java
new file mode 100644
index 0000000..8517675
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/TypeVariablesTest.java
@@ -0,0 +1,32 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Parent;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+
+public class TypeVariablesTest<T1 extends @Parent Object, T2 extends @Parent Object> {
+
+  // This method's parameter type should not be updated by the whole-program inference.
+  // Even though there is only one call to foo with argument of type @WholeProgramInferenceBottom,
+  // the method has in its signature that the parameter is a subtype of @Parent,
+  // therefore no annotation should be added.
+  public static <A extends @Parent Object, B extends @Parent Object> TypeVariablesTest<A, B> foo(
+      A a, B b) {
+    return null;
+  }
+
+  public static <A extends @Parent Object, B extends A> void typeVarWithTypeVarUB(A a, B b) {}
+
+  void test1() {
+    @SuppressWarnings("cast.unsafe")
+    @Parent String s = (@Parent String) "";
+    foo(getSibling1(), getSibling2());
+    typeVarWithTypeVarUB(getSibling1(), getSibling2());
+  }
+
+  static @Sibling1 int getSibling1() {
+    return 0;
+  }
+
+  static @Sibling2 int getSibling2() {
+    return 0;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/TypeVariablesTest2.java b/checker/tests/wpi-testchecker/non-annotated/TypeVariablesTest2.java
new file mode 100644
index 0000000..b9e1c00
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/TypeVariablesTest2.java
@@ -0,0 +1,11 @@
+import java.util.HashMap;
+import java.util.Map;
+
+public class TypeVariablesTest2<K extends String, V extends Integer> {
+
+  Map<K, V> map = new HashMap<>();
+
+  public V getValue(K key) {
+    return map.get(key);
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/TypeVariablesTest3.java b/checker/tests/wpi-testchecker/non-annotated/TypeVariablesTest3.java
new file mode 100644
index 0000000..39f6d02
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/TypeVariablesTest3.java
@@ -0,0 +1,23 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+
+public class TypeVariablesTest3<@Sibling1 T extends @Sibling1 Object> {
+  public @Sibling2 T sibling2;
+  public @Sibling1 T sibling1;
+
+  public T tField;
+
+  void foo(T param) {
+    // :: warning: (assignment)
+    param = sibling2;
+  }
+
+  void baz(T param) {
+    param = sibling1;
+  }
+
+  void bar(@Sibling2 T param) {
+    // :: warning: (assignment)
+    tField = param;
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/UsesAnonymous.java b/checker/tests/wpi-testchecker/non-annotated/UsesAnonymous.java
new file mode 100644
index 0000000..792bd13
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/UsesAnonymous.java
@@ -0,0 +1,37 @@
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling2;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Top;
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.WholeProgramInferenceBottom;
+
+public class UsesAnonymous {
+  void method() {
+    Anonymous a =
+        new Anonymous() {
+          int innerField;
+
+          public void method2() {
+            Anonymous.field1 = getSibling2();
+            Anonymous.field2 = getSibling2();
+            innerField = getSibling2();
+          }
+
+          void innerFieldTest() {
+            // :: warning: (argument)
+            expectsSibling2(innerField);
+          }
+
+          @WholeProgramInferenceBottom int getBottom() {
+            return (@WholeProgramInferenceBottom int) 0;
+          }
+
+          @Top int getTop() {
+            return (@Top int) 0;
+          }
+
+          @Sibling2 int getSibling2() {
+            return (@Sibling2 int) 0;
+          }
+
+          void expectsSibling2(@Sibling2 int t) {}
+        };
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/ValueCheck.java b/checker/tests/wpi-testchecker/non-annotated/ValueCheck.java
new file mode 100644
index 0000000..cfc156a
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/ValueCheck.java
@@ -0,0 +1,26 @@
+// Checks that annotations from the Value Checker (which is a subchecker of the WPI test checker)
+// are actually present in the generated files, even when there is also an annotation from the main
+// checker.
+
+import org.checkerframework.checker.testchecker.wholeprograminference.qual.Sibling1;
+import org.checkerframework.common.value.qual.IntVal;
+
+public class ValueCheck {
+
+  // return value should be @Sibling1 @IntVal(5) int
+  int getSibling1withValue5() {
+    return ((@Sibling1 int) 5);
+  }
+
+  void requireSibling1(@Sibling1 int x) {}
+
+  void requireIntVal5(@IntVal(5) int x) {}
+
+  void test() {
+    int x = getSibling1withValue5();
+    // :: warning: argument
+    requireSibling1(x);
+    // :: warning: argument
+    requireIntVal5(x);
+  }
+}
diff --git a/checker/tests/wpi-testchecker/non-annotated/WildcardReturn.java b/checker/tests/wpi-testchecker/non-annotated/WildcardReturn.java
new file mode 100644
index 0000000..c3245e1
--- /dev/null
+++ b/checker/tests/wpi-testchecker/non-annotated/WildcardReturn.java
@@ -0,0 +1,18 @@
+// This test ensures that generated stub files handle methods that have an
+// inferred wildcard return type correctly.
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class WildcardReturn {
+  public Set<Object> getCredentialIdsForUsername(String username) {
+    return getRegistrationsByUsername(username).stream()
+        .map(registration -> registration.toString())
+        .collect(Collectors.toSet());
+  }
+
+  public Collection<Object> getRegistrationsByUsername(String username) {
+    return null;
+  }
+}
diff --git a/dataflow/build.gradle b/dataflow/build.gradle
new file mode 100644
index 0000000..effe4e2
--- /dev/null
+++ b/dataflow/build.gradle
@@ -0,0 +1,144 @@
+plugins {
+    id 'java-library'
+}
+
+dependencies {
+    api project(':javacutil')
+    api project(':checker-qual')
+
+    // Node implements org.plumelib.util.UniqueId, so this dependency must be "api".
+    api 'org.plumelib:plume-util:1.5.3'
+
+    // External dependencies:
+    // If you add an external dependency, you must shadow its packages both in the dataflow-shaded
+    // artifact (see shadowJar block below) and also in checker.jar (see the comment in
+    // ../build.gradle in the shadowJar block).
+}
+
+shadowJar {
+    archiveFileName = "dataflow-shaded.jar"
+    // Without this line, the Maven artifact will have the classifier "all".
+    archiveClassifier = ''
+
+    relocate ('org.checkerframework', 'org.checkerframework.shaded') {
+        // Shade all Checker Framework packages, except for the dataflow qualifiers.
+        exclude 'org.checkerframework.dataflow.qual.*'
+    }
+
+    // Relocate external dependencies
+    relocate 'org.plume', "org.checkerframework.shaded.org.plume"
+}
+
+artifacts {
+    archives shadowJar
+}
+
+task liveVariableTest(dependsOn: compileTestJava, group: 'Verification') {
+    description 'Test the live variable analysis test for dataflow framework.'
+    inputs.file('tests/live-variable/Expected.txt')
+    inputs.file('tests/live-variable/Test.java')
+
+    outputs.file('tests/live-variable/Out.txt')
+    outputs.file('tests/live-variable/Test.class')
+
+    delete('tests/live-variable/Out.txt')
+    delete('tests/live-variable/Test.class')
+    doLast {
+        javaexec {
+            workingDir = 'tests/live-variable'
+            if (!JavaVersion.current().java9Compatible) {
+                jvmArgs += "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString()
+            }
+            classpath = sourceSets.test.runtimeClasspath
+            classpath += sourceSets.test.output
+            main = 'livevar.LiveVariable'
+        }
+        exec {
+            workingDir = 'tests/live-variable'
+            executable 'diff'
+            args = ['-u', 'Expected.txt', 'Out.txt']
+        }
+    }
+}
+
+task issue3447Test(dependsOn: compileTestJava, group: 'Verification') {
+    description 'Test issue 3447 test case for backward analysis.'
+    inputs.file('tests/issue3447/Test.java')
+    delete('tests/issue3447/Out.txt')
+    delete('tests/issue3447/Test.class')
+    doLast {
+        javaexec {
+            workingDir = 'tests/issue3447'
+            if (!JavaVersion.current().java9Compatible) {
+                jvmArgs += "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString()
+            }
+            classpath = sourceSets.test.runtimeClasspath
+            classpath += sourceSets.test.output
+
+            main = 'livevar.LiveVariable'
+        }
+    }
+}
+
+apply from: rootProject.file("gradle-mvn-push.gradle")
+
+/** Adds information to the publication for uploading the dataflow artifacts to Maven repositories. */
+final dataflowPom(publication) {
+    sharedPublicationConfiguration(publication)
+    publication.from components.java
+    // Information that is in all pom files is configured in checker-framework/gradle-mvn-push.gradle.
+    publication.pom {
+        name = 'Dataflow'
+        description = 'Dataflow is a dataflow framework based on the javac compiler.'
+        licenses {
+            license {
+                name = 'GNU General Public License, version 2 (GPL2), with the classpath exception'
+                url = 'http://www.gnu.org/software/classpath/license.html'
+                distribution = 'repo'
+            }
+        }
+    }
+}
+
+/** Adds information to the publication for uploading the dataflow-shaded artifacts to Maven repositories. */
+final dataflowShadedPom(publication) {
+    sharedPublicationConfiguration(publication)
+
+    publication.artifactId = 'dataflow-shaded'
+    publication.pom {
+        name = 'Dataflow (shaded)'
+        description = 'dataflow-shaded is a dataflow framework based on the javac compiler.\n' +
+                '\n' +
+                'It differs from the org.checkerframework:dataflow artifact in two ways.\n' +
+                'First, the packages in this artifact have been renamed to org.checkerframework.shaded.*.\n' +
+                'Second, unlike the dataflow artifact, this artifact contains the dependencies it requires.'
+        licenses {
+            license {
+                name = 'GNU General Public License, version 2 (GPL2), with the classpath exception'
+                url = 'http://www.gnu.org/software/classpath/license.html'
+                distribution = 'repo'
+            }
+        }
+    }
+}
+
+
+publishing {
+    publications {
+        dataflow(MavenPublication) {
+            dataflowPom it
+        }
+
+        dataflowShaded(MavenPublication) {
+            dataflowShadedPom it
+
+            artifact shadowJar
+            artifact sourcesJar
+            artifact javadocJar
+        }
+    }
+}
+signing {
+    sign publishing.publications.dataflow
+    sign publishing.publications.dataflowShaded
+}
diff --git a/dataflow/manual/Makefile b/dataflow/manual/Makefile
new file mode 100644
index 0000000..149fe75
--- /dev/null
+++ b/dataflow/manual/Makefile
@@ -0,0 +1,6 @@
+dataflow.pdf: dataflow.tex content.tex examples
+	pdflatex dataflow
+	pdflatex dataflow
+
+clean:
+	\rm -f dataflow.aux dataflow.log dataflow.out dataflow.pdf
diff --git a/dataflow/manual/content.tex b/dataflow/manual/content.tex
new file mode 100644
index 0000000..581f47c
--- /dev/null
+++ b/dataflow/manual/content.tex
@@ -0,0 +1,2002 @@
+
+\section{Introduction}
+
+This document describes a \emph{Dataflow Framework} for the Java
+Programming Language.  The framework is used in the
+\href{https://checkerframework.org/}{Checker Framework},
+\href{http://errorprone.info/}{Error Prone},
+\href{https://github.com/uber/NullAway}{NullAway},
+at Facebook, and in other contexts.
+
+The primary purpose of the Dataflow Framework is to estimate values:
+for each line of source code, it determines what
+values each variable might contain.
+
+The Dataflow Framework's result (\autoref{sec:analysis_result_class})
+is an abstract value for each expression (an estimate of the
+expression's run-time value) and a store at each program point.  A
+store maps variables and other expressions to abstract
+values.  As a pre-pass, the Dataflow Framework transforms an input
+AST into a control flow graph (\autoref{sec:cfg}) consisting of basic
+blocks made up of nodes representing single operations.  An analysis
+performs iterative data flow
+analysis over the control flow graph.  The effect of a single node on
+the dataflow store is represented by a transfer function, which takes
+an input store and a node and produces an output store.  Once the
+analysis reaches a fix point, the result can be accessed by client
+code.
+
+In the Checker Framework, the abstract values to be computed are
+annotated types.  An individual checker can customize its analysis by
+extending the abstract value class and by overriding the behavior of
+the transfer function for particular node types.
+
+The Dataflow Framework was
+designed with several goals in mind.  First, to encourage use
+beyond the Checker Framework, it is written as a separate package that can be
+built and used with no dependence on the Checker Framework.  Second,
+the framework supports analysis but not
+transformation, so it provides information that can be used by a type
+checker or an IDE, but it does not support optimization.  Third, the
+framework aims to minimize the burden on developers who build on top
+of it.  In particular, the hierarchy of analysis classes is designed
+to reduce the effort required to implement a new flow-sensitive type
+checker in the Checker Framework. The
+\href{https://docs.google.com/document/d/1oYzbOrrS4ZEEx4wQgIHbijNzcI5CiQAq_-1NrOS8JME/edit?usp=sharing}{Dataflow User's Guide}
+gives an introduction to customizing dataflow to add checker-specific
+enhancements.
+
+\begin{workinprogress}
+    Paragraphs colored in gray with a gray bar on the left side (just
+    like this one) contain questions, additional comments or indicate
+    missing parts. Eventually, these paragraphs will be removed.
+\end{workinprogress}
+
+
+\paragraph{Potential version conflicts if you export the Dataflow Framework}
+Suppose that two tools both utilize the Dataflow Framework.  If neither one
+exports the Dataflow Framework's classes or the Checker Framework
+annotations, both tools can be run at the same time.  If both export the
+classes, then it may be impossible for users to run both tools, because
+they may require a different version of the Dataflow Framework.  The
+Checker Framework necessarily exports the Dataflow Framework.  If your tool
+cannot shadow the Checker Framework and Dataflow Framework classes, and you
+cannot conform to the Checker Framework release schedule, then instead of
+using the \code{dataflow} artifact at
+\url{https://search.maven.org/artifact/org.checkerframework/dataflow/},
+you can use the \code{dataflow-shaded} artifact at
+\url{https://search.maven.org/artifact/org.checkerframework/dataflow-shaded/}.
+It contains the Dataflow Framework in package
+\code{org.checkerframework.shaded.dataflow} and also its dependencies in
+their usual packages, namely the Checker Framework
+qualifiers and the plume-util project
+(\url{https://github.com/plume-lib/plume-util}).
+(There may still be a problem if two different non-Checker-Framework tools
+both export different, incompatible versions of the \code{dataflow-shaded}
+artifact.)
+
+
+\section{Organization}
+
+\subsection{Projects}
+% TODO: Update
+
+The source code of the combined Checker Framework and Dataflow
+Framework is divided into multiple projects: \code{javacutil},
+\code{dataflow}, \code{framework}, and \code{checker},
+which can be built into distinct jar files.  \code{checker.jar} is a fat
+jar that contains all of these, plus the Stub Parser.
+
+\code{javacutil} provides convenient interfaces to routines in
+Oracle's javac library.  There are utility classes for interacting
+with annotations, elements, trees and types, as well as
+\code{InternalUtils}, which gives direct access to internal features
+of javac that are not part of a supported interface.  There are
+interfaces or abstract classes for reporting errors, for processing
+types in an AST, and for providing the annotations present on an
+Element.
+The \code{org.checkerframework.javacutil.trees} package provides a
+class to parse expressions into javac Trees (\code{TreeParser}), a
+class to build new Trees from  scratch (\code{TreeBuilder}), and a
+class to represent newly introduced variables that are not part of an
+input program (\code{DetachedVarSymbol}).
+
+\code{dataflow} contains the classes to represent and construct
+control flow graphs and the base classes required for flow analysis.
+These classes are described in detail in \autoref{sec:node_classes}.
+
+\code{framework} contains the framework aspects of the Checker
+Framework, including the derived classes for flow analysis of
+annotated types which are described later in this document.
+
+\code{checker} contains the type system-specific checkers.
+
+The \code{dataflow} project depends only on \code{javacutil}.
+
+
+\subsection{Classes}
+
+This section gives an overview of the major Java classes and
+interfaces in the implementation of the Dataflow Framework and the
+flow-sensitive type checking feature of the Checker Framework.  It
+includes both the base classes in the \code{dataflow} project and the
+derived classes in the \code{framework} project.  The class and
+interface declarations are given with full package names to indicate
+which project they belong to.
+
+\subsubsection{Nodes}
+\label{sec:node_classes}
+
+Dataflow doesn't actually work on trees; it works on Nodes.
+A Node class represents an individual operation of a program,
+including arithmetic operations, logical operations, method calls,
+variable references, array accesses, etc.
+Nodes
+simplify writing a dataflow analysis by separating the dataflow
+analysis from the original source code.
+\autoref{tab:nodes} on page~\pageref{tab:nodes} lists
+the Node types.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.cfg.node;
+
+abstract class Node
+class *Node extends Node
+\end{verbatim}
+
+
+\subsubsection{Blocks}
+\label{sec:block_classes}
+
+The Block
+classes represent basic blocks.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.cfg.block;
+
+interface Block
+abstract class BlockImpl implements Block
+interface SingleSuccessorBlock extends Block
+abstract class SingleSuccessorBlockImpl extends BlockImpl implements SingleSuccessorBlock
+\end{verbatim}
+
+A RegularBlock contains no exception-raising operations and has a
+single control-flow successor.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.cfg.block;
+interface RegularBlock extends SingleSuccessorBlock
+class RegularBlockImpl extends SingleSuccessorBlockImpl implements RegularBlock
+\end{verbatim}
+
+An ExceptionBlock contains a single operation that may raise an
+exception, with one or more exceptional successors and a single normal
+control-flow successor.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.cfg.block;
+interface ExceptionBlock extends SingleSuccessorBlock
+class ExceptionBlockImpl extends SingleSuccessorBlockImpl implements ExceptionBlock
+\end{verbatim}
+
+A SpecialBlock represents method entry or exit, including exceptional
+exit which is represented separately from normal exit.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.cfg.block;
+interface SpecialBlock extends SingleSuccessorBlock
+class SpecialBlockImpl extends SingleSuccessorBlockImpl implements SpecialBlock
+\end{verbatim}
+
+A ConditionalBlock contains no operations at all.  It represents a
+control-flow split to either a `then' or an `else' successor based on
+the immediately preceding boolean-valued Node.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.cfg.block;
+interface ConditionalBlock extends Block
+class ConditionalBlockImpl extends BlockImpl implements ConditionalBlock
+\end{verbatim}
+
+
+
+\subsubsection{ControlFlowGraph}
+\label{sec:control_flow_graph_class}
+
+A ControlFlowGraph represents the body of a method or an initializer
+expression as a graph of Blocks with distinguished entry, exit, and
+exceptional exit SpecialBlocks.  ControlFlowGraphs are produced by the
+CFGBuilder classes and are treated as immutable once they are built.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.cfg;
+class ControlFlowGraph
+\end{verbatim}
+
+\subsubsubsection{CFGBuilder}
+\label{sec:cfg_builder_classes}
+
+The CFGBuilder classes visit an AST and produce a corresponding
+ControlFlowGraph as described in \autoref{sec:ast_to_cfg_translation}.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.cfg;
+class CFGBuilder
+\end{verbatim}
+
+The Checker Framework derives from CFGBuilder in order to desugar
+enhanced for loops that make explicit use of type annotations provided
+by the checker in use.
+
+\begin{verbatim}
+package org.checkerframework.framework.flow;
+class CFCFGBuilder extends CFGBuilder
+\end{verbatim}
+
+\subsubsubsection{CFGVisualizeLauncher}
+\label{sec:cfg_visualize_launcher_class}
+
+The CFGVisualizeLauncher generates a DOT or String representation of
+the control flow graph for a given method in a given class.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.cfg;
+class CFGVisualizeLauncher
+\end{verbatim}
+
+\subsubsection{JavaExpressions}
+\label{sec:flow_expressions_class}
+
+The Dataflow Framework records the abstract values of certain
+expressions, called JavaExpressions: local variables, field accesses,
+array accesses, references to \code{this}, and pure method calls.
+JavaExpressions are keys in the store of abstract values.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.analysis;
+class JavaExpressions
+\end{verbatim}
+
+Java expressions that appear in method pre- and postconditions are
+parsed into JavaExpressions using helper routines in
+\code{org.checkerframework.framework.util.JavaExpressionParseUtil}.
+
+
+\subsubsection{AbstractValue}
+\label{sec:abstract_value_classes}
+
+AbstractValue is the internal representation of dataflow information
+produced by an analysis.  An AbstractValue is an estimate about the
+run-time values that an expression may evaluate to.  The client of the
+Dataflow Framework defines the abstract value, so the information may
+vary widely among different users of the Dataflow Framework, but they
+share a common feature that one can compute the least upper bound of
+two AbstractValues.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.analysis;
+interface AbstractValue<V extends AbstractValue<V>>
+\end{verbatim}
+
+For the Checker Framework, abstract values are essentially
+AnnotatedTypeMirrors.
+
+\begin{verbatim}
+package org.checkerframework.framework.flow;
+abstract class CFAbstractValue<V extends CFAbstractValue<V>> implements AbstractValue<V>
+class CFValue extends CFAbstractValue<CFValue>
+\end{verbatim}
+
+For the Nullness Checker, abstract values additionally track the
+meaning of PolyNull, which may be either Nullable or NonNull.  The
+meaning of PolyNull can change when a PolyNull value is compared to
+the null literal, which is specific to the NullnessChecker.  Other
+checkers often also support a Poly* qualifier, but only the
+NullnessChecker tracks the meaning of its poly qualifier using the
+dataflow analysis.
+
+\begin{verbatim}
+package org.checkerframework.checker.nullness;
+class NullnessValue extends CFAbstractValue<NullnessValue>
+\end{verbatim}
+
+
+\subsubsection{Store}
+\label{sec:store_classes}
+
+A Store is a set of dataflow facts computed by an analysis, so it is a
+mapping from JavaExpressions to AbstractValues.  As with
+AbstractValues, one can take the least upper bound of two Stores.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.analysis;
+interface Store<S extends Store<S>>
+\end{verbatim}
+
+The Checker Framework store restricts the type of abstract values it
+may contain.
+
+\begin{verbatim}
+package org.checkerframework.framework.flow;
+abstract class CFAbstractStore<V extends CFAbstractValue<V>,
+        S extends CFAbstractStore<V, S>>
+    implements Store<S>
+class CFStore extends CFAbstractStore<CFValue, CFStore>
+\end{verbatim}
+
+An InitializationStore tracks which fields of the `self' reference
+have been initialized.
+
+\begin{verbatim}
+package org.checkerframework.checker.initialization;
+class InitializationStore<V extends CFAbstractValue<V>,
+        S extends InitializationStore<V, S>>
+    extends CFAbstractStore<V, S>
+\end{verbatim}
+
+A NullnessStore additionally tracks the meaning of PolyNull.
+
+\begin{verbatim}
+package org.checkerframework.checker.nullness;
+class NullnessStore extends InitializationStore<NullnessValue, NullnessStore>
+\end{verbatim}
+
+
+\subsubsection{Transfer functions}
+\label{sec:transfer_functions}
+
+A transfer function (\autoref{sec:transfer_function_classes}) is
+explicitly represented as a node visitor that takes a TransferInput
+(\autoref{sec:transfer_input_classes}) and produces a TransferResult
+(\autoref{sec:transfer_result_classes}).
+
+\subsubsubsection{TransferInput}
+\label{sec:transfer_input_classes}
+
+The TransferInput represents the set of dataflow facts known to be
+true immediately before the node to be analyzed.  A TransferInput may
+contain a single store, or a pair of `then' and `else' stores when
+following a boolean-valued expression.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.analysis;
+class TransferInput<V extends AbstractValue<V>,
+        S extends Store<S>>
+\end{verbatim}
+
+\subsubsubsection{TransferResult}
+\label{sec:transfer_result_classes}
+
+A TransferResult is the output of a transfer function.  In other
+words, it is the set of dataflow facts known to be true immediately
+after a node.  A Boolean-valued expression produces a
+ConditionalTransferResult that contains both a `then' and an `else'
+store, while most other Nodes produce a RegularTransferResult with a
+single store.
+
+\begin{verbatim}
+package class org.checkerframework.dataflow.analysis;
+abstract TransferResult<V extends AbstractValue<V>,
+        S extends Store<S>>
+class ConditionalTransferResult<V extends AbstractValue<V>,
+        S extends Store<S>>
+    extends TransferResult<A, S>
+class RegularTransferResult<V extends AbstractValue<V>,
+        S extends Store<S>>
+    extends TransferResult<A, S>
+\end{verbatim}
+
+\subsubsubsection{TransferFunction}
+\label{sec:transfer_function_classes}
+
+A TransferFunction is a NodeVisitor that takes an input and produces an output.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.analysis;
+interface TransferFunction<V extends AbstractValue<V>,
+        S extends Store<S>>
+    extends NodeVisitor<TransferResult<V, S>, TransferInput<V, S>>
+interface ForwardTransferFunction<V extends AbstractValue<V>,
+        S extends Store<S>>
+    extends TransferFunction<V, S>
+interface BackwardTransferFunction<V extends AbstractValue<V>,
+        S extends Store<S>>
+    extends TransferFunction<V, S>
+\end{verbatim}
+
+The Checker Framework defines a derived class of TransferFunction to
+serve as the default for most checkers.  The class constrains the type
+of abstract values and it overrides many node visitor methods to
+refine the abstract values in their TransferResults.
+
+\begin{verbatim}
+package org.checkerframework.framework.flow;
+abstract class CFAbstractTransfer<V extends CFAbstractValue<V>,
+        S extends CFAbstractStore<V, S>,
+        T extends CFAbstractTransfer<V, S, T>>
+    extends AbstractNodeVisitor<TransferResult<V, S>, TransferInput<V, S>>
+    implements ForwardTransferFunction<V, S>
+
+class CFTransfer extends CFAbstractTransfer<CFValue, CFStore, CFTransfer>
+\end{verbatim}
+
+The Initialization Checker's transfer function tracks which fields of
+the `self' reference have been initialized.
+
+\begin{verbatim}
+package org.checkerframework.checker.initialization;
+class InitializationTransfer<V extends CFAbstractValue<V>,
+        T extends InitializationTransfer<V, T, S>,
+        S extends InitializationStore<V, S>>
+    extends CFAbstractTransfer<V, S, T>
+\end{verbatim}
+
+The Regex Checker's transfer function overrides visitMethodInvocation
+to special-case the \code{isRegex} and \code{asRegex} methods.
+
+\begin{verbatim}
+package org.checkerframework.checker.regex;
+class RegexTransfer extends CFAbstractTransfer<CFValue, CFStore, RegexTransfer>
+\end{verbatim}
+
+
+\subsubsection{Analysis}
+\label{sec:analysis_classes}
+
+An Analysis performs iterative dataflow analysis over a control flow
+graph using a given transfer function.  Both forward and backward
+analyses are supported.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.analysis;
+interface Analysis<V extends AbstractValue<V>,
+        S extends Store<S>,
+        T extends TransferFunction<V, S>>
+abstract class AbstractAnalysis<V extends AbstractValue<V>,
+        S extends Store<S>,
+        T extends TransferFunction<V, S>>
+    implements Analysis<V, S, T>
+interface ForwardAnalysis<V extends AbstractValue<V>,
+        S extends Store<S>,
+        T extends ForwardTransferFunction<V, S>>
+    extends Analysis<V, S, T>
+interface BackwardAnalysis<V extends AbstractValue<V>,
+        S extends Store<S>,
+        T extends BackwardTransferFunction<V, S>>
+    extends Analysis<V, S, T>
+\end{verbatim}
+
+The Checker Framework defines a derived class of Analysis for use as
+the default analysis of most checkers.  This class adds information
+about the type hierarchy being analyzed and acts as a factory for
+abstract values, stores, and the transfer function.
+
+\begin{verbatim}
+package org.checkerframework.framework.flow;
+abstract class CFAbstractAnalysis<V extends CFAbstractValue<V>,
+        S extends CFAbstractStore<V, S>,
+        T extends CFAbstractTransfer<V, S, T>>
+    extends ForwardAnalysisImpl<V, S, T>
+
+class CFAnalysis extends CFAbstractAnalysis<CFValue, CFStore, CFTransfer>
+\end{verbatim}
+
+The Nullness Checkers' analysis overrides the factory methods for
+abstract values, stores, and the transfer function.
+
+\begin{verbatim}
+package org.checkerframework.checker.nullness;
+class NullnessAnalysis extends CFAbstractAnalysis<NullnessValue,
+        NullnessStore, NullnessTransfer>
+\end{verbatim}
+
+The RegexChecker's analysis overrides the factory methods for abstract
+values, stores, and the transfer function.
+
+\begin{verbatim}
+package org.checkerframework.checker.regex;
+class RegexAnalysis extends CFAbstractAnalysis<CFValue, CFStore, RegexTransfer>
+\end{verbatim}
+
+
+\subsubsection{AnalysisResult}
+\label{sec:analysis_result_class}
+
+An AnalysisResult preserves the dataflow information computed by an
+Analysis for later use by clients.  The information consists of an
+AbstractValue for each node in the CFG and a Store that is valid at
+the start of each Block.  The AnalysisResult class can return
+AbstractValues for either Nodes or Trees and it can re-run the
+transfer function to compute Stores that are valid immediately before
+or after any Tree.
+
+\begin{verbatim}
+package org.checkerframework.dataflow.analysis;
+class AnalysisResult<V extends AbstractValue<V>,
+        S extends Store<S>>
+\end{verbatim}
+
+
+\subsubsection{AnnotatedTypeFactory}
+\label{sec:annotated_type_factory_classes}
+
+AnnotatedTypeFactorys are not part of the Dataflow Framework, per se,
+but they are parameterized by the Dataflow Framework classes that they
+use.
+
+\begin{verbatim}
+package org.checkerframework.framework.type;
+class AnnotatedTypeFactory implements AnnotationProvider
+\end{verbatim}
+
+In the Checker Framework, dataflow analysis is performed on demand,
+one class at a time, the first time that a ClassTree is passed to
+getAnnotatedType.  This is implemented in the abstract class
+GenericAnnotatedTypeFactory with concrete implementation in
+BaseAnnotatedTypeFactory.
+
+\begin{verbatim}
+package org.checkerframework.framework.type;
+abstract class GenericAnnotatedTypeFactory<Checker extends BaseTypeChecker<?>,
+        Value extends CFAbstractValue<Value>,
+        Store extends CFAbstractStore<Value, Store>,
+        TransferFunction extends CFAbstractTransfer<Value, Store, TransferFunction>,
+        FlowAnalysis extends CFAbstractAnalysis<Value, Store, TransferFunction>>
+    extends AnnotatedTypeFactory
+
+package org.checkerframework.common.basetype;
+class BaseAnnotatedTypeFactory
+    extends GenericAnnotatedTypeFactory<CFValue, CFStore, CFTransfer, CFAnalysis>
+\end{verbatim}
+
+
+\section{The Control-Flow Graph}
+\label{sec:cfg}
+
+A control-flow graph (CFG) represents a single method or field
+initialization.  (The Dataflow Framework performs an intra-procedural
+analysis.  This analysis is modular and every method is considered in
+isolation.)
+This section also describes the translation from the abstract syntax tree
+(AST) to the CFG\@.
+We start with a simple example, then give a more formal
+definition of the CFG and its properties, and finally describe the
+translation from the AST to the CFG.
+
+As is standard, a control-flow graph is a set of
+basic blocks that are linked by control-flow edges. Possibly less
+standard, every basic block consists of a sequence of so-called nodes,
+each of which represents a minimal Java operation or expression.
+
+
+\flow{CFGSimple}{.33}{1.1}{A simple Java code snippet to introduce the CFG.
+In CFG visualizations, special basic blocks are shown as ovals;
+conditional basic blocks are polygons with eight sides; and regular and exception
+basic blocks are rectangles.}
+
+Consider the method \code{test} of \autoref{fig:CFGSimple}. The if
+conditional got translated to a \emph{conditional basic block}
+(octagon) with two successors. There are also two special basic blocks
+(ovals) to denote the entry and exit point of the method.
+
+
+\subsection{Formal Definition of the Control-Flow Graph}
+\label{sec:cfg-formal}
+
+The control-flow graph models all paths that can possibly be taken by an
+execution of the method.
+
+\begin{definition}[Control-Flow Graph]
+    A \emph{control-flow graph} consists of a set of \emph{basic
+      blocks} and a set of directed edges between these basic blocks,
+    some of which are labeled.
+\end{definition}
+
+\begin{definition}[Basic Block]
+    A \emph{basic block} is a sequence of \emph{nodes}, where the only
+    control flow between the nodes inside
+    the basic block is sequential.  Furthermore, there is no
+    control flow occurring between those nodes and nodes of other basic
+    blocks, except between the last node of one block $b_1$ and the first node
+    of another block $b_2$, if $b_2$ is a successor of $b_1$.  A basic
+    block may have multiple successors.
+\end{definition}
+
+
+
+\begin{definition}[Types of Basic Blocks]
+    There are four \emph{types} of basic blocks in a control-flow graph:
+    \begin{enumerate}
+        \item \textbf{Regular basic block.} A \emph{regular basic
+          block} contains any non-empty sequence of nodes and has
+          exactly one successor.  None of the nodes in the block can
+          throw an exception at runtime.
+
+        \item \textbf{Special basic blocks.} A \emph{special basic
+          block} contains the empty sequence of nodes (i.e., is empty)
+          and denotes either the entry or one of the exit blocks of a
+          method. There are three types of special basic blocks:
+        \begin{itemize}
+            \item Entry block. This basic block is the (only) entry
+              point of the method and thus is the only basic block
+              without predecessors.
+            \item Exit block. This basic block denotes the (normal)
+              exit of a method, and it has no successors.
+            \item Exceptional exit block, which indicates exceptional
+              termination of the method. As an exit block, this block
+              has no successors.
+        \end{itemize}
+        Every method has exactly one entry block, zero or one exit blocks,
+        and zero or one exceptional exit blocks. There is always
+        either an exit block, an exceptional exit block, or both.
+
+        \item \textbf{Exception basic block.} An \emph{exception basic
+          block} contains exactly one node that \emph{might} throw an
+          exception at runtime (e.g., a method call).  There are zero
+          or one non-exceptional successors (only a basic block containing a
+          \code{throw} statement does not have a non-exceptional
+          successor).  There are one or more
+          exceptional successors (see \autoref{def:edges}). In all
+          cases there is at least one successor (regular or
+          exceptional).
+
+        \item \textbf{Conditional basic block.} A \emph{conditional
+          basic block} does not contain any nodes and is used as a
+          \emph{split point} after the execution of a node of boolean
+          type. It has exactly two successors (both non-exceptional):
+          the \emph{then} successor that is reached when the previous node
+          evaluates to true and the \emph{else} successor that is reached
+          when the previous node evaluates to false.  There is always
+          exactly a single predecessor block for every conditional
+          basic block, which is either a regular basic block or an
+          exception basic block. In both cases, the last node in the
+          predecessor will be of boolean type and the boolean value
+          controls which successor of the conditional block is
+          executed.
+    \end{enumerate}
+\end{definition}
+
+The Java implementation of the four block types above is described in
+\autoref{sec:block_classes}.
+
+\begin{definition}[Control-Flow Graph Edges]
+\label{def:edges}
+The basic blocks of a control-flow graph are connected by directed
+\emph{edges}.  If $b_1$ and $b_2$ are connected by a directed edge
+$(b_1,b_2)$, we call $b_1$ a predecessor of $b_2$, and we call $b_2$
+a successor of $b_1$.  In a control-flow graph, there are three
+types of edges:
+\begin{enumerate}
+    \item \textbf{Exceptional edges}. An \emph{exceptional edge}
+      connects an exception basic block with its exceptional
+      successors, and it is labeled by the most general exception that
+      might cause execution to take this edge during run time.  Note
+      that the outgoing exceptional edges of a basic block do not need
+      to have mutually exclusive labels; the semantics is that the
+      control flow follows the most specific edge. For instance, if
+      one edge is labeled with type \code{A} and another is labeled
+      with type \code{B} where \code{B} is a subtype of \code{A}, then
+      the execution only takes the first edge if the exception is of a
+      subtype of \code{A}, but not a subtype of \code{B}.
+
+\begin{workinprogress}
+There is not necessarily a most specific exception type in the program
+text; in that case, does the translation add a most specific case that will
+never be executed at run time?
+
+In general, what is the relation of the ordering in source code to the
+one here?
+\end{workinprogress}
+
+    There is at most one successor for every exception type.
+
+    \item \textbf{Conditional edges}. A \emph{conditional edge} is a
+      non-exceptional edge that connects a conditional basic block
+      with one of its successors, and is labeled with either ``true''
+      or ``false''.
+
+    \item \textbf{Regular, non-conditional edge.} Any other edge is a
+      \emph{regular edge}, and does not carry a label. Only regular
+      basic blocks, the entry basic block, and exception basic blocks
+      have outgoing regular edges.
+\end{enumerate}
+\end{definition}
+
+
+\begin{definition}[Nodes]
+    \label{def:node}
+    A \emph{node} is a minimal Java operation or expression.  It is
+    minimal in the sense that it cannot be decomposed further into
+    subparts between which control flow occurs. Examples for such
+    nodes include integer literals, an addition node (which performs
+    the mathematical addition of two nodes) or a method call.  Control
+    flow such as \code{if} and \code{break} are not represented as
+    nodes.  The full list of nodes is given in \autoref{tab:nodes} and
+    several of them are described in more detail in
+    \autoref{sec:noteworthy-translations}.
+
+    It is important to note that, even though nodes can contain
+    references to other nodes, it is only the ``top-level'' node which
+    is considered at that point in the basic block. In the example of
+    the addition node, this means that only the addition operation is
+    to be executed, and its operands would occur earlier in the
+    control-flow graph (as they are evaluated first, before performing
+    the addition).
+\end{definition}
+
+In the visualization, a string representation of the node is used,
+followed by the node type in square brackets. Note that the string
+representation often also includes more than just the ``top-level''
+node. For instance, the addition expression \code{a + b[0];} will
+appear as ``a + b[0] [ NumericalAddition ]'' rather than ``a'' plus
+some temporary variable.  This is done for clarity, so that it is easy
+to see what expressions are summed up and because we don't create
+internal names for expression results.
+
+\autoref{tab:nodes} lists all node types in the framework. We use the
+Java class name of the implementation, but leave out the suffix
+\code{Node}, which is present for every type.
+All classes are in package \code{org.checkerframework.dataflow.cfg.node}.
+
+    \begin{longtable}{lp{0.4\linewidth}l}
+        \midrule
+        \multicolumn{3}{c}{\autoref{tab:nodes}: All node types in the Dataflow Framework.} \\ \\
+        \textbf{Node type} & \textbf{Notes} & \textbf{Example} \\ \midrule \endfirsthead
+
+        \textbf{Node type} & \textbf{Notes} & \textbf{Example} \\ \midrule \endhead
+        \hline \multicolumn{3}{|c|}{{Continued on next page}} \\ \hline \endfoot
+        \endlastfoot
+
+        \code{Node} & The base class of all nodes. & \\
+        \midrule
+
+        \code{ValueLiteral} & The base class of literal value nodes. & \\
+        \code{BooleanLiteral} & & \code{true} \\
+        \code{CharacterLiteral} & & \code{'c'} \\
+        \code{DoubleLiteral} & & \code{3.14159} \\
+        \code{FloatLiteral} & & \code{1.414f} \\
+        \code{IntegerLiteral} & & \code{42} \\
+        \code{LongLiteral} & & \code{1024L} \\
+        \code{NullLiteral} & & \code{null} \\
+        \code{ShortLiteral} & & \code{512} \\
+        \code{StringLiteral} & & \code{"memo"} \\
+        \midrule
+
+        & Accessor expressions & \\
+        \code{ArrayAccess} & & \code{args[i]} \\
+        \code{FieldAccess} & & \code{f}, \code{obj.f} \\
+        \code{MethodAccess} & & \code{obj.hashCode} \\
+        \code{This} & Base class of references to \code{this} & \\
+        \code{ExplicitThis} & Explicit use of \code{this} in an expression & \\
+        \code{ImplicitThis} & Implicit use of \code{this} in an expression & \\
+        \code{Super} & Explicit use of \code{super} in expression. & \code{super(x, y)} \\
+        \code{LocalVariable} & Use of a local variable, either as l-value or r-value & \\
+        \midrule
+
+        \code{MethodInvocation} & Note that access and invocation are distinct. & \code{hashCode()} \\
+        \midrule
+
+        & Arithmetic and logical operations. & \\
+        \code{BitwiseAnd} & & a \& \code{b} \\
+        \code{BitwiseComplement} & & \verb|~b| \\
+        \code{BitwiseOr} & & \code{a | b} \\
+        \code{BitwiseXor} & & \code{a ^ b} \\
+        \code{ConditionalAnd} & Short-circuiting. & a \&\& \code{b} \\
+        \code{ConditionalNot} & & \code{!a} \\
+        \code{ConditionalOr} & Short-circuiting. & \code{a || b} \\
+        \code{FloatingDivision} & & \code{1.0 / 2.0} \\
+        \code{FloatingRemainder} & & \code{13.0 \% 4.0} \\
+        \code{LeftShift} & & \code{x << 3} \\
+        \code{IntegerDivision} & & \code{3 / 2} \\
+        \code{IntegerRemainder} & & \code{13 \% 4} \\
+        \code{NumericalAddition} & & \code{x + y} \\
+        \code{NumericalMinus} & & \code{-x} \\
+        \code{NumericalMultiplication} & & \code{x * y} \\
+        \code{NumericalPlus} & & \code{+x} \\
+        \code{NumericalSubtraction} & & \code{x - y} \\
+        \code{SignedRightShift} & & \code{x >> 3} \\
+        \code{StringConcatenate} & & \code{s + ".txt"} \\
+        \code{TernaryExpression} & & \code{c ? t : f} \\
+        \code{UnsignedRightShift} & & \code{x >>> 5} \\
+        \midrule
+
+        & Relational operations & \\
+        \code{EqualTo} & & \code{x == y} \\
+        \code{NotEqual} & & \code{x != y} \\
+        \code{GreaterThan} & & \code{x > y} \\
+        \code{GreaterThanOrEqual} & & \code{x >= y} \\
+        \code{LessThan} & & \code{x < y} \\
+        \code{LessThanOrEqual} & & \code{x <= y} \\
+
+        \code{Case} & Case of a switch.  Acts as an equality test. & \\
+        \midrule
+
+        \code{Assignment} & & \code{x = 1} \\
+%        \midrule
+
+        \code{StringConcatenateAssignment} & A compound assignment. & \code{s += ".txt"} \\
+        \midrule
+
+        \code{ArrayCreation} & & \code{new double[]} \\
+        \code{ObjectCreation} & & \code{new Object()} \\
+        \midrule
+
+        \code{TypeCast} & & \code{(float) 42} \\
+        \code{InstanceOf} & & \code{x instanceof Float} \\
+        \midrule
+
+        & Conversion nodes. & \\
+        \code{NarrowingConversion} & Implicit conversion. & \\
+        \code{StringConversion} & Might be implicit. & \code{obj.toString()} \\
+        \code{WideningConversion} & Implicit conversion. & \\
+        \midrule
+
+        \midrule
+        & \textbf{Non-value nodes} & \\
+
+        & Types appearing in expressions, such as \code{MyType.class} & \\
+        \code{ArrayType} & & \\
+        \code{ParameterizedType} & & \\
+        \code{PrimitiveType} & & \\
+        \midrule
+
+        \code{ClassName} & Identifier referring to Java class or interface. & \code{java.util.HashMap} \\
+        \code{PackageName} & Identifier referring to Java package. & \code{java.util} \\
+        \midrule
+
+        \code{Throw} & Throw an exception. & \\
+        \code{Return} & Return from a method. & \\
+        \midrule
+
+        \code{AssertionError} & & \code{assert x != null : "Hey"} \\
+        \midrule
+
+        \code{Marker} & No-op nodes used to annotate a CFG with
+        information of the underlying Java source code.  Mostly useful
+        for debugging and visualization. An example is indicating the
+        start/end of switch statements. & \\ \midrule
+
+        \code{NullChk} & Null checks inserted by javac & \\
+        \midrule
+
+        \code{VariableDeclaration} & Declaration of a local variable & \\
+        \midrule
+
+        \caption{All node types in the Dataflow Framework.}
+        \label{tab:nodes}
+    \end{longtable}
+
+
+In theory, nearly any statement can throw an \code{Error} such as
+\code{OutOfMemoryError} or \code{NoSuchFieldError}.  The Dataflow Framework
+does not represent all that possible flow.  It only creates the exceptional
+edges shown in \autoref{tab:nodesWithException}.
+
+\begin{table}
+    \begin{tabular}{ll}
+        \hline
+        \textbf{Node type} & \textbf{Exception type} \\  \hline
+
+        \code{ArrayAccess} & \code{NullPointerException}, \code{ArrayIndexOutOfBoundsException} \\
+        \code{FieldAccess} & \code{NullPointerException} \\
+        \code{MethodAccess} & \code{NullPointerException} \\
+        \code{MethodInvocation} & \code{Throwable}, types in throws clause of the signature \\
+        \code{IntegerDivision} & \code{ArithmeticException} \\
+        \code{IntegerRemainder} & \code{ArithmeticException} \\
+        \code{ObjectCreation} & \code{Throwable}, types in throws clause of the signature \\
+        \code{ArrayCreation} & \code{NegativeArraySizeException}, \code{OutOfMemoryError} \\
+        \code{TypeCast} & \code{ClassCastException} \\
+        \code{Throw} & Type of \code{e} when \code{throw e} \\
+        \code{AssertionError} & \code{AssertionError} \\
+        \code{ClassName} & \code{ClassCircularityError}, \code{ClassFormatError}, \\
+        & \code{NoClassDefFoundError}, \code{OutOfMemoryError} \\
+        \hline
+    \end{tabular}
+
+        \caption{All node types that could throw Exception, and the types
+          to be thrown.
+          Java class name of nodes are simplified as with \autoref{tab:nodes}.
+          All exception types are in package \code{java.lang}.}
+        \label{tab:nodesWithException}
+\end{table}
+
+\begin{workinprogress}
+Can \code{StringConversion} be implicit?  I think so, but in any event discuss.
+\end{workinprogress}
+
+\begin{workinprogress}
+For each non-expression, explain its purpose, just like the explanation for
+Marker that still needs to be fleshed out.
+\end{workinprogress}
+
+\begin{workinprogress}
+``Could be desugared'' on ``StringConcatenateAssignment'' and ``Conversion
+nodes'' was confusing.  What is the design rationale for
+desugaring in the Dataflow Framework?   Discuss that.  Here, at least
+forward-reference to \autoref{sec:desugaring}, if that's relevant. \\
+More generally, for any cases that will be discussed in the text, add a
+forward reference to the section with the discussion.
+\end{workinprogress}
+
+\begin{workinprogress}
+There is a \code{StringConcatenateAssignmentNode}.
+What about other compound assignments? Are they desugared?
+\end{workinprogress}
+
+\begin{workinprogress}
+When we added Java~8 support, did we add additional nodes that are not
+listed here? Cross-check with implementation.
+\end{workinprogress}
+
+\begin{workinprogress}
+Why is it \code{AssertionErrorNode} instead of \code{AssertNode}?
+\end{workinprogress}
+
+
+\subsection{Noteworthy Translations and Node Types}
+\label{sec:noteworthy-translations}
+
+In this section we mention any non-straightforward translations from the AST to
+the CFG, or special properties about individual nodes.
+
+
+\subsubsection{Program Structure}
+\label{sec:prog-structure}
+
+Java programs are structured using high-level programming constructs
+such as loops, if-then-else constructs,
+try-catch-finally blocks or switch statements.  During the translation
+from the AST to the CFG some of this program structure is lost and all
+non-sequential control flow is represented by two low-level
+constructs: conditional basic blocks and control-flow edges between
+basic blocks. For instance, a \code{while} loop is translated into its
+condition followed by a conditional basic block that models the two
+possible outcomes of the condition: either the control flow follows
+the `true' branch and continues with the loop's body, or control goes to the
+`false' successor and executes the first statement after the loop.
+
+
+\subsubsection{Assignment}
+
+As described in \jlsref{15.26.1}, the execution of an assignment is in
+general not strictly left-to-right. Rather, the right-hand side might
+be evaluated even if the left-hand side of the assignment causes an
+exception. This semantics is faithfully represented in the CFG
+produced by the translation.  An example of a field assignment
+exhibiting this behavior is shown in \autoref{fig:CFGFieldAssignment}.
+
+\flow{CFGFieldAssignment}{.33}{1}{Control flow for a field assignment is not strictly
+left-to-right (cf.\ \jlsref{15.26.1}),
+which is properly handled by the translation.}
+
+\subsubsection{Postfix/Prefix Increment/Decrement}
+\label{sec:postpre-incdec}
+Postfix and prefix increment and decrement have a side effect to
+update the variable or field. To represent this side effect, the Dataflow
+Framework creates an artificial assignment node like \code{n = n + 1}
+for \code{++n} or \code{n++}. This artificial assignment node is stored
+in \code{unaryAssignNodeLookup} of \code{ControlFlowGraph}. The assignment
+node is also stored in \code{treeLookup} for prefix increment or decrement
+so that the result of it is after the assignment. However, the node before
+the assignment is stored in \code{treeLookup} for postfix increment or decrement
+because the result of it should be before the assignment. For further information
+about node-tree mapping, see \autoref{sec:conversions}.
+
+\subsubsection{Conditional stores}
+\label{sec:cond-stores}
+
+The Dataflow Framework extracts information from control-flow splits
+that occur in \code{if}, \code{for}, \code{while}, and \code{switch} statements.  In order to have
+the information available at the split, we eagerly produce two stores
+contained in a \code{ConditionalTransferResult} after certain
+boolean-valued expressions.  The stores are called the \emph{then} and
+\emph{else} stores.  So, for example, after the expression \code{x ==
+  null}, two different stores will be created.  The Nullness Checker
+would produce a then store that maps \code{x} to @Nullable and an else
+store that maps \code{x} to @NonNull.
+
+The Dataflow Framework allows a maximum of two stores and when there
+are two distinct stores, they always refer to the most recent
+boolean-valued expression.  Stores are propagated through most nodes
+and they are reversed for conditional not expressions.  The transfer
+functions for many nodes merge conditional stores back together
+because they cannot maintain the distinction between them.  Merging
+just means taking the least upper bound of the two stores and it
+happens automatically by calling \code{TransferInput.getRegularStore}.
+
+
+\subsubsection{Branches}
+
+The control flow graph represents all non-exceptional control-flow
+splits, or branches, as \code{ConditionalBlock}s that contain no
+nodes.  If there is one store flowing into a conditional block, then
+it is duplicated to both successors.  If there are two stores flowing
+into a conditional block, the then store is propagated to the block's
+then successor and the else store is propagated to the block's else
+successor.
+
+Consider the control flow graph generated for the simple if statement
+in \autoref{fig:CFGIfStatement}.  The conditional expression \code{b1}
+immediately precedes the \code{ConditionalBlock}, represented by the
+octagonal node.  The \code{ConditionalBlock} is followed by both a
+then and an else successor block, after which control flow merges back
+together at the exit block.  The edge labels \code{EACH_TO_EACH},
+\code{THEN_TO_BOTH}, and \code{ELSE_TO_BOTH} are flow rules described
+in \autoref{sec:flow-rules}.  As described above, the then store
+propagates to (both stores of) the block's then successor according to
+rule \code{THEN_TO_BOTH} and the else store propagates to (both stores
+of) the block's else successor according to rule \code{ELSE_TO_BOTH}.
+More precise rules are used to preserve dataflow information for
+short-circuiting expressions, as described in \autoref{sec:cond-exp}.
+
+\flow{CFGIfStatement}{.33}{1.25}{Example of an if statement translated into a
+  \code{ConditionalBlock}.}
+
+\subsubsection{Conditional Expressions}
+\label{sec:cond-exp}
+
+The conditional and (\code{&&}, cf. \jlsref{15.23}) and the
+conditional or (\code{||}, cf. \jlsref{15.24}) expressions are subject
+to short-circuiting: if evaluating the left-hand side already
+determines the result, then the right-hand side is not evaluated. This
+semantics is faithfully represented in the constructed CFG and more
+precise flow rules (\autoref{sec:flow-rules}) are used to preserve
+additional dataflow information.
+
+An example program using conditional or is shown in
+\autoref{fig:CFGConditionalOr}.  Note that the CFG correctly
+represents short-circuiting.  The expression \code{b2 || b3} is only
+executed if \code{b1} is false and \code{b3} is only evaluated if
+\code{b1} and \code{b2} are false.
+
+Observe in \autoref{fig:CFGConditionalOr} that the flow rule between
+the first conditional block and its then successor is
+\code{THEN_TO_THEN}, rather than the default flow rule for such edges
+\code{THEN_TO_BOTH}, which is present on the edge from the last
+conditional block to its then successor.  \code{THEN_TO_THEN} is a
+more precise rule which propagates the then store from the predecessor
+of the conditional block to the then store of the then successor and
+leaves the else store of the successor untouched.  This is a valid
+rule for propagating information along the short-circuit edge of a
+conditional or expression because \code{b1 || (b2 || b3)} being false
+implies that \code{b1} is false, so dataflow information that obtains
+when \code{b1} is true has no effect on the dataflow information
+obtains when \code{b1 || (b2 || b3)} is false.  To put it another way,
+if control reaches the block containing \code{b1 || (b2 || b3)} and
+that expression is false, then control must have flowed along the else
+branches of both conditional blocks and only the facts that obtain
+along those edges need to be kept in the else store of the block
+containing \code{b1 || (b2 || b3)}.
+
+\flow{CFGConditionalOr}{.33}{1.33}{Example of a conditional or expression
+  (\code{||}) with short-circuiting and more precise flow rules.}
+
+
+\subsubsection{Implicit \code{this} access}
+
+The Java compiler AST uses the same type (\code{IdentifierTree}) for
+local variables and implicit field accesses (where \code{this.} is
+left out).  To relieve the user of the Dataflow Framework from
+manually determining the two cases, we consistently use
+\code{FieldAccessNode} for field accesses, where the receiver might be
+an \code{ImplicitThisNode}.
+
+
+\subsubsection{Assert statements}
+\label{sec:assert-stmts}
+
+Assert statements are treated specially by the CFG builder because it
+is unknown at CFG construction time whether or not assertions will be
+enabled when the program is run.  When assertions are enabled, the
+dataflow information gained by analyzing the assert statement can
+improve precision and allow the programmer to avoid redundant
+annotations.  However, when assertions are disabled, it would be
+unsound to assume that they had any effect on dataflow information.
+
+The user of the Dataflow Framework may specify that
+assertions are enabled or disabled.  When assertions are assumed to be
+disabled, no CFG Nodes are built for the assert statement.
+When assertions are assumed to be enabled, CFG Nodes are built to
+represent the condition of the assert statement and, in the else
+successor of a ConditionalBlock, CFG Nodes are built to represent the
+detail expression of the assert, if any.
+
+If assertions are not assumed to be enabled or disabled, then
+the CFG is conservative and represents the fact that the
+assert statement may execute or may not.  This takes the form of a
+ConditionalBlock that branches on a fake variable.  For example, see
+\autoref{fig:CFGAssert}.  The fake variable named
+\code{assertionsEnabled#num0} controls the first ConditionalBlock.
+The then successor of the ConditionalBlock is the same subgraph of CFG
+Nodes that would be created if assertions were assumed to be enabled,
+while the else successor of the ConditionalBlock is the same, empty,
+subgraph of CFG Nodes that would be created if assertions were assumed
+to be disabled.
+
+\flow{CFGAssert}{.15}{2.9}{Example of an assert statement translated with
+  assertions neither assumed to be enabled nor assumed to be
+  disabled.}
+
+
+\subsubsection{Varargs method invocation}
+\label{sec:varargs}
+In Java, varargs in method or constructor invocation is compiled
+as new array creation (cf.\ \jlsref{15.12.4.2}). For example,
+\code{m(1, 2, 3)} will be compiled as \code{m(new int[]\{1, 2, 3\})}
+when the signature of \code{m} is \code{m(int... args)}. Dataflow
+Framework creates an \code{ArrayCreationNode} with initializer for varargs
+in the same way as the Java compiler does.
+Note that it doesn't create an \code{ArrayCreationNode}
+when the varargs is an array with the same depth as the type of
+the formal parameter, or if \code{null} is given as the actual varargs argument.
+
+\subsubsection{Default case and fall through for switch statement}
+\label{sec:default-switch}
+A switch statement is handled as a chain of \code{CaseNode} and nodes in
+the case. \code{CaseNode} makes a branch by comparing the equality of
+the expression of the switch statement and the expression of the case.
+Note that the expression of a switch statement must be executed just only
+once at the beginning of the switch statement. To refer to its value, a fake variable
+is created and it is assigned to a fake variable. \code{THEN_TO_BOTH}
+edge goes to nodes in the case and \code{ELSE_TO_BOTH} edge goes to next
+\code{CaseNode}. When a next is default case, it goes to nodes in the default
+case. If a break statement is in nodes, it creates an edge to next node of
+the switch statement. If there is any possibility of fall-through, an edge
+to the first of nodes in the next case is created after nodes in the case.
+For example, see \autoref{fig:CFGSwitch}. The fake variable named \code{switch#num0}
+is created and each of case nodes creates the branches.
+
+\flow{CFGSwitch}{.21}{1.45}{Example of a switch statement with case, default and fall through.}
+
+\subsubsection{Handling \code{finally} blocks}
+\label{sec:try-finally}
+
+Control flow statements, like \code{return}, \code{break}, and \code{continue},
+within \code{try} blocks will cause execution of the \code{finally} block before
+continuing at the target of the jump. The Dataflow Framework models this
+behavior by adding a jump to a duplicate of the finally block before the
+jump to the original target of the control flow statement.
+
+
+\subsection{AST to CFG Translation}
+\label{sec:ast_to_cfg_translation}
+
+This section gives a high-level overview of the translation process
+from the abstract syntax tree to the control-flow graph as described
+in \autoref{sec:cfg-formal}.
+
+First, we define several entities, which will be used in the translation.
+
+\begin{definition}[Extended Node]
+    In the translation process the data type \emph{extended node} is used.
+    An extended node can be one of four possibilities:
+    \begin{itemize}
+        \item \textbf{Simple extended node.} An extended node can just be a
+          wrapper for a node that does not throw an exception,
+          as defined in Definition~\ref{def:node}.
+        \item \textbf{Exception extended node.} Similar to a simple
+          node, an exception extended node contains a node, but this
+          node might throw an exception at runtime.
+        \item \textbf{Unconditional jump.} An unconditional jump
+          indicates that control flow proceeds non-sequentially to a
+          location indicated by a target label.
+        \item \textbf{Conditional jump.} A conditional jump can follow
+          an extended node that contains a node of boolean type. It
+          contains two target labels, one if the node evaluates to
+          true and one for false.
+    \end{itemize}
+\end{definition}
+
+\textbf{Comparison of nodes and extended nodes.}
+Nodes themselves never contain control flow information; they only
+represent computation.
+
+An extended node is a wrapper around a node that represents control flow
+information.  It contains:  a node, a label, a predecessor, and a
+successor.
+
+An extended node is temporarily used to keep track of some control flow
+information.  Later, the basic block data structures are created, and they
+represent the control flow.  (And at that point the extended nodes are
+discarded.)
+\begin{definition}[Label]
+    A \emph{label} is a marker that is used to refer to extended
+    nodes. It is used only temporarily during CFG construction.
+\end{definition}
+
+% I agree, we should keep this in mind in the future.
+%\begin{workinprogress}
+%I find it useful to never name phases ``one'', ``two'', ``three'', but
+%always to give them English names.  This makes the meaning clearer to
+%readers --- often because it forces the writer to think harder about the
+%meaning of each.  It can still be useful to number the phases even after
+%they have more mnemonic names.
+%\end{workinprogress}
+
+
+The process of translating an AST to a CFG proceeds in three distinct phases.
+\begin{enumerate}
+    \item \textbf{Phase one.} In the first phase, a single linear
+      sequence of extended nodes is created. The control flow is
+      implicitly assumed to be sequential through the sequence of
+      extended nodes, until a (conditional or unconditional) jump is
+      encountered in an if, for, while, or switch statement, in which
+      case the jump target decides where execution proceeds.
+
+    The labels used as targets of jumps are associated with positions
+    in this sequence and are managed by maintaining a binding function
+    from labels to sequence positions. The advantage of having this
+    indirection is that one can create a label and associate with the
+    next free position in the sequence, without knowing which exact
+    extended node will be placed there.  Furthermore, labels can be
+    created and used before they are actually bound to their correct
+    position in the sequence (e.g., when that position is not yet
+    known).  At the end, the binding function can be used to resolve
+    labels to extended nodes.
+
+    Furthermore, phase one also computes a mapping from AST tree
+    elements to nodes, as well as a set of leaders. A \emph{leader} is
+    an extended node for which one of the following conditions
+    applies:
+    \begin{itemize}
+    \item It is the first extended node in the sequence.
+    \item It is the target of a jump (i.e. there is a label bound to
+      the location of the node in the sequence).
+    \item It is the first node following a jump.
+    \end{itemize}
+
+    \item \textbf{Phase two.} Phase two translates the linear
+      representation to a control-flow graph by performing the
+      following transformations:
+    \begin{itemize}
+        \item Simple extended nodes are translated to regular basic
+          blocks, where multiple nodes can be grouped in one regular
+          basic block.
+        \item Exception extended nodes are translated to exception
+          basic blocks with the correct edges.
+        \item Unconditional jumps are replaced with edges between the
+          correct basic blocks.
+        \item Conditional jumps are replaced by a conditional basic
+          block.
+    \end{itemize}
+    To greatly simplify the implementation, phase two is allowed to
+    produce a degenerated control-flow graph. In particular, the
+    following deficiencies are possible:
+    \begin{itemize}
+    \item Regular basic blocks might be empty.
+    \item Some conditional basic blocks might be unnecessary, in that
+      they have the same target for both the `then' as well as the
+      `else' branch.
+    \item Two consecutive, non-empty, regular basic blocks can exist,
+      even if the second block has only exactly one predecessor and
+      the two blocks could thus be merged.
+    \end{itemize}
+    \item \textbf{Phase three.} In the third and last phase, the
+      control-flow graph is transformed such that the deficiencies
+      remaining from phase two are removed. It is ensured that
+      removing one kind of deficiency does not create another
+      degenerate case.
+\end{enumerate}
+
+
+
+\subsubsection{Desugaring}
+\label{sec:desugaring}
+
+Desugaring means replacing complicated source language constructs by
+simpler ones, or removing syntactic sugar from an input program.
+Originally, we intended for the control flow graph representation to
+be as close as possible to the Java abstract syntax tree to simplify
+the mapping from tree to CFG node and back and to reuse existing
+checker code written in terms of trees.  However, we ran into several
+cases that were better handled by desugaring.
+
+    \begin{itemize}
+    \item We decided to represent implicit conversion operations like
+      boxing, unboxing, widening, and narrowing as explicit CFG nodes
+      because they change the type of a value.  For example, implicit
+      unboxing of an \code{Integer} will be translated into a call to
+      \code{Integer.intValue}.  The pre-conversion type can be
+      associated with the original node and the post-conversion type
+      can be associated with the explicit conversion node.  It also
+      makes it possible for the transfer function to operate on the
+      conversion nodes.
+
+    \item Enhanced for loops are defined in terms of a complicated
+      translation into simpler operations, including field accesses,
+      branches, and method calls that could affect dataflow
+      information.  It would be prohibitively difficult for a checker
+      writer to write a transfer function that correctly accounted for
+      all of those operations, so we desugar enhanced for loops.
+
+    \item Once we decided to make conversion nodes explicit it made
+      sense to desugar compound assignments.  A compound assignment
+      like \begin{verbatim}Integer i; i += 3;\end{verbatim} performs
+      both an unboxing and a boxing operation on \code{i}.  Desugaring
+      all compound assignments greatly reduced the total number of
+      node classes.
+
+    \end{itemize}
+
+In order to desugar code and still maintain the invariant that every
+CFG node maps to a tree, we needed to create new AST tree nodes that
+were not present in the input program.  Javac allows us to do this
+through non-supported APIs and we wrote some utility classes in
+\code{javacutil} to make the process easier.  The new trees are
+created during CFG building and they persist as long as some CFG node
+refers to them.  However, the trees are not inserted into the AST, so
+they are not type-checked or seen by other tree visitors.  Their main
+purpose is to carry Java types and to satisfy AnnotatedTypeFactory
+methods.
+
+A further complication is that these newly-introduced AST trees are
+not part of the TreePath when visiting the AST.  We work around this
+problem by giving the AnnotatedTypeFactory a mapping, called the
+\code{pathHack}, from newly-introduced trees to their containing
+MethodTree and ClassTree.
+
+Possibly even worse, we needed to create fake symbols for variables
+created when desugaring enhanced for loops.  Javac does not expose the
+ability to create a symbol, so we created a new subclass of
+Symbol.VarSymbol called \code{javacutil.tree.DetachedVarSymbol} for
+this purpose.  AnnotatedTypeFactory explicitly checks for
+DetachedVarSymbols in its DeclarationFromElement method.
+
+
+
+\subsubsection{Conversions and node-tree mapping}
+\label{sec:conversions}
+
+As mentioned in \autoref{sec:desugaring}, we represent implicit Java
+type conversions such as boxing, unboxing, widening, and narrowing by
+explicit CFG nodes.  This means that some AST tree nodes correspond to
+multiple CFG nodes: a pre-conversion node and a post-conversion node.
+We will describe how the conversions work and how the node-tree
+mappings are implemented.
+
+Boxing and unboxing are represented in terms of calls to Java standard
+library methods.  Boxing corresponds to a call to
+\code{BoxedClass.valueOf} while unboxing corresponds to a call to
+\code{BoxedClass.\*Value}.  This allows annotations on the library
+methods, as well as transfer functions for method invocations, to
+apply to the conversions with no special work on the part of a checker
+developer.
+
+Widening and narrowing conversions are still represented as special
+node types, although it would be more consistent to change them into
+type casts.
+
+\begin{workinprogress}
+Is the last point a to-do item?
+\end{workinprogress}
+
+We maintain the invariant that a CFG node maps to zero or one AST tree
+and almost all of them map to a single tree.  But we can't maintain a
+unique inverse mapping because some trees have both pre- and
+post-conversion nodes.  Instead, we remember two mappings, one from
+tree to pre-conversion node and, for those trees that were converted,
+one from tree to post-conversion node.  Both the CFGBuilder and the
+ControlFlowGraph store two separate mappings.  The Analysis class
+explicitly stores the tree to pre-conversion node mapping as
+\code{treeLookup} and it indirectly uses the tree to post-conversion
+mapping in \code{Analysis.getValue(Tree)}.  This has effectively
+hidden the distinction between pre and post-conversion nodes from the
+Checker Framework, but in the long run it may be necessary to expose
+it.
+
+
+
+\section{Dataflow Analysis}
+
+This section describes how the dataflow analysis over the control-flow
+graph is performed and how to implement a particular analysis.
+
+Roughly, a dataflow analysis in the framework works as follows. Given
+the abstract syntax tree of a method, the framework computes the
+corresponding control-flow graph as described in
+\autoref{sec:cfg}. Then, a simple forward or backward iterative
+algorithm is used to compute a fix-point, by iteratively applying a
+set of transfer functions to the nodes in the CFG\@.  These transfer
+functions are specific to the particular analysis and are used to
+approximate the runtime behavior of different statements and expressions.
+
+
+\subsection{Managing Intermediate Results of the Analysis}
+\label{sec:node-mapping}
+\label{sec:store-management}
+
+
+Conceptually, the dataflow analysis computes an abstract value for
+every node and flow expression\footnote{Certain dataflow analyses
+  might choose not to produce an abstract value for every node.  For
+  instance, a constant propagation analysis would only be concerned
+  with nodes of a numerical type, and could ignore other nodes.}.  The
+transfer function (\autoref{sec:transfer-fnc}) produces these abstract
+values, taking as input the abstract values computed earlier for
+sub-expressions.  For instance, in a constant propagation analysis,
+the transfer function for addition (\code{+}) would look at the
+abstract values for the left and right operand, and determine that the
+\code{AdditionNode} is a constant if and only if both operands are
+constant.
+
+An analysis result contains two parts:
+
+\begin{enumerate}
+\item
+The \emph{node-value mapping} (\code{Analysis.nodeValues}) maps \code{Node}s to their abstract
+values.  Only nodes that can take on an abstract value are
+used as keys.  For example, in the Checker Framework, the mapping is
+from expression nodes to annotated types.
+
+The framework consciously does not store the abstract value
+directly in the node, to remove any coupling between the control-flow
+graph and a particular analysis.  This allows the control-flow graph
+to be constructed only once, and then reused for different dataflow
+analyses.
+
+\item
+A set of \emph{stores}.  Each store maps a flow expression to an
+abstract value.  Each store is associated with a specific program point.
+
+The stores tracked by an analysis implement the \code{Store}
+interface, which defines the following operations:
+\begin{itemize}
+\item Least upper bound: Compute the least upper bound of two stores
+  (e.g., at a merge-point in the control-flow graph).
+\item Equivalence: Compare two stores if they are (semantically)
+  different, which is used to determine if a fix-point is reached in
+  the dataflow analysis. Note that reference-equality is most likely
+  not sufficient.
+\item Copy mechanism: Clone a store to get an exact copy.
+\end{itemize}
+The store is analysis-dependent, but the framework provides a default
+store implementation which can be reused.  The default implementation
+is
+\begin{verbatim}org.checkerframework.framework.flow.CFStore\end{verbatim}
+
+What information is tracked in the store depends on the analysis to be
+ performed.  Some examples of stores include
+\begin{verbatim}
+org.checkerframework.checker.initialization.InitializationStore
+org.checkerframework.checker.nullness.NullnessStore
+\end{verbatim}
+
+Every store is associated with a particular point in the control-flow
+graph, and all stores are managed by the framework. It saves an explicit store
+for the start of each basic block.
+When dataflow information
+is requested for a later point in a block, the analysis applies the
+transfer function to recompute it from the initial store.
+
+\end{enumerate}
+
+After an analysis has iterated to a fix-point, the computed dataflow
+information is maintained in an AnalysisResult, which can map either
+nodes or trees to abstract values.
+
+
+\subsection{Answering Questions}
+\label{sec:answering-questions}
+After the flow analysis for a particular method has been computed,
+there are two kinds of information that have been computed.  Firstly,
+the node-value mapping stores an abstract value for every node, and
+secondly, the information maintained in various stores is available.
+
+Two kinds of queries are possible to the dataflow analysis after the
+analysis is complete:
+\begin{enumerate}
+    \item For a given AST tree node, what is its abstract value?  Both
+      pre- and postconversion values can be retrieved.
+      A discussion of conversions can be found in \autoref{sec:conversions}.
+    \item For a given AST tree node, what is the state right after
+      this AST tree node?  Examples of questions include:
+    \begin{itemize}
+        \item Which locks are currently held?
+        \item Are all fields of a given object initialized?
+    \end{itemize}
+\end{enumerate}
+
+The store may first need to be (re-)computed, as the framework does not
+store all intermediate stores but rather only those for key positions
+as described in \autoref{sec:store-management}.
+
+To support both kinds of queries, the framework builds a map from AST
+tree nodes (of type \code{com.sun.source.tree.Tree}) to CFG nodes.  To
+answer questions of the first type it is then possible to go from the
+AST tree node to the CFG node and look up its abstract value in the
+node-value mapping (this is provided by the framework).  By default,
+the abstract value returned for a tree by
+\code{Analysis.getValue(Tree)} includes any implicit conversions
+because it uses the mapping from tree node to post-conversion CFG
+node.  To request the pre-conversion value, one currently uses the
+\code{ControlFlowGraph.treelookup} map directly.
+
+To support questions of the second kind, every node has a reference to
+the basic block it is part of. Thus, for a given AST tree node, the
+framework can determine the CFG node and thereby the CFG basic block,
+and compute the necessary store to answer the question.
+
+
+\subsection{Transfer Function}
+\label{sec:transfer-fnc}
+
+A transfer function is an object that has a transfer method for
+every \code{Node} type, and also a transfer method for procedure entry.
+
+\begin{itemize}
+\item A transfer method for a \code{Node} type takes a store
+  and the node, and produces an updated store. This is achieved by
+  implementing the \code{NodeVisitor<S, S>} interface for the store
+  type \code{S}.
+
+  These transfer methods also get access to the abstract value of any
+  sub-node of the node \code n under consideration.  This is not limited
+  to immediate children, but the abstract value for any node contained
+  in \code n can be queried.
+
+\item A transfer method for procedure entry returns the initial store, given the
+  list of parameters (as \code{LocalVariableNode}s that represent the formal
+  parameters) and the
+  \code{MethodTree} (useful if the initial store depends on the procedure
+  signature, for instance).
+
+\end{itemize}
+
+
+\subsection{Flow Rules}
+\label{sec:flow-rules}
+
+As mentioned in \autoref{sec:cond-stores}, dataflow analysis
+conceptually maintains two stores for each program point, a then store
+containing information valid when the previous boolean-valued
+expression was true and an else store containing information valid
+when the expression was false.  In many cases, there is only a single
+store because there is no boolean-valued expression to split on or
+there was an expression, but it yielded no useful dataflow
+information.  However, any CFG edge may potentially have a predecessor
+with two stores and a successor with two stores.
+
+We could simply propagate information from both predecessor stores to
+both successor stores, but that would throw away useful information,
+so we define five flow rules that allow more precise propagation.
+
+\begin{itemize}
+    \item \code{EACH_TO_EACH} is the default rule for an edge with a
+      predecessor that is not a \code{ConditionalBlock}.  It
+      propagates information from the then store of the predecessor to
+      the then store of the successor and from the else store of the
+      predecessor to the else store of the successor.
+    \item \code{THEN_TO_BOTH} is the default rule for an edge from a
+      \code{ConditionalBlock} to its then successor.  It propagates
+      information from the then store of its predecessor to both the
+      then and else stores of the then successor, thereby splitting
+      the conditional store to take advantage of the fact that the
+      condition is known to be true.
+    \item \code{ELSE_TO_BOTH} is the default rule for an edge from a
+      \code{ConditionalBlock} to its else successor.  It propagates
+      information from the else store of its predecessor to both the
+      then and else stores of the else successor, thereby splitting
+      the conditional store to take advantage of the fact that the
+      condition is known to be false.
+    \item \code{THEN_TO_THEN} is a special rule for a short-circuit edge
+      from a \code{ConditionalBlock} to its then successor.  It only
+      propagates information from the then store of its predecessor to
+      the then store of its successor.  This flow rule is used for
+      conditional or expressions because the else store of \code{a ||
+        b} is not influenced by the then store of \code{a}.
+    \item \code{ELSE_TO_ELSE} is a special rule for a short-circuit edge
+      from a \code{ConditionalBlock} to its else successor.  It only
+      propagates information from the else store of its predecessor to
+      the else store of its successor.  This flow rule is used for
+      conditional and expressions because the then store of \code{a &&
+        b} is not influenced by the else store of \code{a}.
+\end{itemize}
+
+Note that the more precise flow rules \code{THEN_TO_THEN} and
+\code{ELSE_TO_ELSE} improve the precision of the store they do not
+write to.  In other words, \code{THEN_TO_THEN} yields a more precise
+else store of its successor by not propagating information to the else
+store which might conflict with information already there, and
+conversely for \code{ELSE_TO_ELSE}.
+See \autoref{sec:cond-exp} for more details and an example.
+
+Currently, we only use flow rules for short-circuiting edges of
+conditional ands and ors.  The CFG builder sets the flow rule of each
+short-circuiting edge as it builds the CFG for the conditional and/or
+expression.
+
+The dataflow analysis logic requires that both the then and the else
+store of each block contain some information before the block is
+analyzed, so it is a requirement that at least one predecessor block
+writes the then store and at least one writes the else store.
+
+
+\subsection{Concurrency}
+
+By default, the Dataflow Framework analyzes the code as if it is
+executed sequentially.  This is unsound if the code is run
+concurrently.  Use the \code{-AconcurrentSemantics} command-line
+option to enable concurrent semantics.
+
+In the concurrent mode, the dataflow analysis cannot infer any
+local information for fields.  This is because after a local update,
+another thread might change the field's value before the next use.
+
+An exception to this are monotonic type properties, such as the
+\code{@MonotonicNonNull} annotation of the nullness type system.  The
+meta-annotation \code{@MonotonicQualifier} declares that a qualifier
+behaves monotonically, however it is not yet used to preserve dataflow
+information about fields under concurrent semantics.
+
+
+\section{Example: Constant Propagation}
+
+As a proof-of-concept, I (Stefan) implemented a constant propagation
+analysis for local variables and integer values.  The main class is
+\code{org.checkerframework.dataflow.cfg.playground.ConstantPropagationPlayground}.  I
+describe the most important aspects here.
+
+\textbf{Abstract values.} A class \code{Constant} is used as an
+abstract value, which can either be \emph{top} (more than one integer
+value seen), \emph{bottom} (no value seen yet), or \emph{constant}
+(exactly one value seen; in which case the value is also stored).
+
+\textbf{The store.} The store maps \code{Node}s to \code{Constant},
+where only \code{LocalVariableNode}s and \code{IntegerLiteralNode}s
+are used as keys.  Only those two nodes actually are of interest
+(there is no addition/multiplication/etc. yet, and other constructs
+like fields are not yet supported by the analysis).
+
+Two different instances of \code{LocalVariableNode} can be uses of the
+same local variable, and thus the \code{equals} method has been
+implemented accordingly. Therefore, every local variable occurs at
+most once in the store, even if multiple (equal)
+\code{LocalVariableNode}s for it exist.
+
+\textbf{The transfer function.} The transfer function is very
+simple. The initial store contains \emph{top} for all parameters, as
+any value could have been passed in.  When an integer literal is
+encountered, the store is extended to indicate what abstract value
+this literal stands for. Furthermore, for an assignment, if the
+left-hand side is a local variable, the transfer function updates its
+abstract value in the store with the abstract value of the right-hand
+side (which can be looked up in the store).
+
+To illustrate how we can have different information in the then and
+else block of a conditional, I also implemented another transfer
+function that considers the \code{EqualToNode}, and if it is of the
+form \code{a == e} for a local variable \code{a} and constant
+\code{e}, passes the correct information to one of the branches. This
+is also shown in \autoref{fig:ConstSimple}.
+
+\text{Example.} A small example is shown in \autoref{fig:ConstSimple}.
+
+\flow{ConstSimple}{.45}{1}{Simple sequential program to illustrate constant
+  propagation.  Intermediate analysis results are shown.}
+
+
+\section{Example: Live Variable}
+
+A live variable analysis for local variables and fields was implemented
+to show the backward analysis works properly. The main class is
+\code{org.checkerframework.dataflow.cfg.playground.LiveVariablePlayground}.
+
+\textbf{Abstract values.} A class \code{LiveVarValue} is a live
+variable (which is represented by a node) wrapper turning node into
+abstract value. A node can be \code{LocalVariableNode} or \code{FieldAccessNode}.
+
+\textbf{The store.} The live variable store \code{LiveVarStore} has a field
+\code{liveVarValueSet} which contains a set of \code{LiveVarValue}s. Only
+\code{LocalVariableNode} or \code{FieldAccessNode} will be considered as a
+live variable and added to \code{liveVarValueSet}. The store defines methods
+\code{putLiveVar(LiveVarValue)} and \code{killLiveVar(LiveVarValue)} to add
+and kill live variables.
+
+\textbf{The transfer function.} The transfer function \code{LiveVarTransfer}
+initializes empty stores at normal and exceptional exit blocks (because this
+is a backward transfer function). The transfer function visits assignments to
+update the information of live variables for each node in the stores.
+
+\textbf{Example.} An example is shown in \autoref{fig:LiveSimple}.
+
+\flow{LiveSimple}{.33}{1}{Simple sequential program to illustrate live variable.  Intermediate analysis results are shown.}
+
+
+\section{Default Analysis}
+
+
+\subsection{Overview}
+
+The default flow-sensitive analysis \code{org.checkerframework.framework.flow.CFAnalysis}
+works for any checker defined in the
+Checker Framework.  This generality is both a strength and a weakness
+because the default analysis can always run but the facts it can
+deduce are limited.  The default analysis is extensible so checkers
+can add logic specific to their own qualifiers.
+
+The default flow-sensitive analysis takes advantage of several forms
+of control-flow to improve the precision of type qualifiers.  It
+tracks assignments to flow expressions, propagating type qualifiers
+from the right-hand side of the assignment.  It considers equality and
+inequality tests to propagate the most precise qualifiers from the
+left or right-hand side to the true (resp. false) successor.  It also
+applies type qualifiers from method postconditions after calls.
+
+\begin{workinprogress}
+Preconditions are not mentioned at all in this manual. How are they handled?
+\end{workinprogress}
+
+
+\subsection{Interaction of the Checker Framework and the Dataflow Analysis}
+\label{sec:flow-cf-interaction}
+
+This section describes how the dataflow analysis is integrated into the
+Checker Framework to enable flow-sensitive type checking.
+
+A main purpose of a type factory is to create an \code{AnnotatedTypeMirror}
+based on an input tree node. Using the results of the dataflow analysis,
+the type factory can return a more refined type than otherwise possible.
+
+Type factories that extend from \code{GeneralAnnotatedTypeFactory}
+and set the constructor parameter \code{useFlow} to true will automatically
+run dataflow analysis and use the result of the analysis when creating an
+\code{AnnotatedTypeMirror}.  The first time that a \code{GenericAnnotatedTypeFactory}
+instance visits a \code{ClassTree}, the type factory runs the dataflow analysis on all the field initializers of the class first, then the bodies of methods in
+the class, and then finally the dataflow analysis is
+ran recursively on the members of nested classes. The result of
+dataflow analysis are stored in the \code{GenericAnnotatedTypeFactory} instance.
+
+When creating an \code{AnnotatedTypeMirror} for a tree node, the type factory
+queries the result of the dataflow analysis to determine if a more refined
+type for the tree node was inferred by the analysis. This is the first
+type of query described in \autoref{sec:answering-questions}.
+
+\begin{workinprogress}
+I found the below section confusing.  I had a hard time putting my finger
+on it, but perhaps you could re-read the section.  As one minor issue,
+``very similar'':  similar to what?  ``intermediary nodes'':  what are
+those?  ``we'':  is that the analysis writer or the framework implementor
+(or the runtime system)?
+\end{workinprogress}
+
+Dataflow itself uses the type factory to get the initial
+\code{AnnotatedTypeMirror} for a tree node in the following way.
+
+For a given node \code{n}
+\begin{itemize}
+\item
+    If it has no corresponding AST tree node, use ``top'' as its
+    abstract value.
+\item
+    If it has a corresponding AST tree node, ask the
+    \code{AnnotatedTypeFactory} about the type of the tree node.  The
+    factory will then use its checker-dependent logic to compute this
+    type.  A typical implementation will look at the type of sub-trees
+    and compute the overall type based on the information about these
+    sub-trees.
+
+    Note that the factory recursively uses information provided by the
+    flow analysis to determine the types of sub-trees.  There is a
+    check in \code{Analysis.getValue} that the node whose type is
+    being requested is a sub-node of the node to which the transfer
+    function is currently being applied.  For other nodes, the
+    analysis will return \code{null} (i.e., no information) and the
+    factory will return the flow-insensitive annotated type.
+
+\end{itemize}
+
+The \code{AnnotatedTypeFactory} and helper classes need to be prepared to work
+even when dataflow analysis is not finished yet. Code should either check
+whether dataflow analysis is still in progress (using
+\code{analysis.isRunning()}) or handle possible null values. The factory should
+return conservative, flow-insensitive types if the analysis is still in
+progress.
+
+
+\subsection{The Checker Framework Store and Dealing with Aliasing}
+
+\begin{workinprogress}
+    Word of caution: The exact rules of what information is
+        retained may or may not be implemented exactly as described
+        here.  This is a good starting point in any case, but if very
+        precise information is needed, then the source code is very
+        readable and well documented.
+\end{workinprogress}
+
+The Dataflow Framework provides a default implementation of a store
+with the class \code{CFAbstractStore}, which is used (as
+\code{CFStore}) as the default store if a checker does not provide its
+own implementation.  This implementation of a store tracks the
+following information:
+
+\begin{itemize}
+    \item Abstract values of local variables.
+    \item Abstract values of fields where the receiver is an
+          access sequence compose of the following:
+    \begin{itemize}
+    \item Field access.
+    \item Local variable.
+    \item Self reference (i.e., \code{this}).
+    \item Pure or non-pure method call.
+    \end{itemize}
+\end{itemize}
+
+The most challenging part is ensuring that the information about field
+accesses is kept up to date in the face of incomplete aliasing
+information.  In particular, at method calls and assignments care
+needs to be taken about which information is still valid afterwards.
+
+The store maintains a mapping from field accesses (as defined in
+Section~\ref{sec:field-access}) to abstract values, and the subsequent
+sections describe the operations that keep this mapping up-to-date.
+
+\begin{workinprogress}
+  There hasn't been a good introduction of pure vs. non-pure methods.
+  Maybe this is a good location for a discussion?
+  Introduce the purity annotations somewhere.
+\end{workinprogress}
+
+
+\subsubsection{Internal Representation of field access}
+\label{sec:field-access}
+
+To keep track of the abstract values of fields, the Dataflow Framework
+uses its own representation of field accesses (that is different from
+the \code{Node} type introduced earlier).  This data type is defined
+inductively as follows:
+
+\begin{bnfgrammar}
+    \production{\nonterminal{FieldAccess}}
+        {\nonterminal{Receiver} \nonterminal{Field} \qquad Java field (identified by its \code{Element})}
+    \production{\nonterminal{Receiver}}
+        {\nonterminal{SelfReference} \qquad \code{this}}
+    \altline{\nonterminal{LocalVariable} \qquad local variable (identified by its \code{Element})}
+    \altline{\nonterminal{FieldAccess}}
+    \altline{\nonterminal{MethodCall} \qquad Java method call of a method}
+    \altline{\nonterminal{Unknown} \qquad any other Java expression}
+    \production{\nonterminal{MethodCall}}
+        {\nonterminal{Receiver} \literal{.} \nonterminal{Method}
+            \literal{(} \nonterminal{Receiver}$^{,*}$ \literal{)}}
+\end{bnfgrammar}
+
+\nonterminal{Unknown} is only used to determine which
+information needs to be removed (e.g., after an assignment), but no field
+access that contains \nonterminal{Unknown} is stored in the mapping to
+abstract values.  For instance, \nonterminal{Unknown} could stand for
+a non-pure method call, an array access, or a ternary expression.
+
+
+\subsubsection{Updating Information in the Store}
+
+\newcommand{\alias}{\operatorname{might\_alias}}
+
+In the following, let $o$ be any \nonterminal{Receiver}, $x$ a local
+variable, $f$ a field, $m$ a pure method, and $e$ an expression.
+Furthermore, we assume to have access to a predicate $\alias(o_1,o_2)$
+that returns true if and only if~$o_1$ might alias~$o_2$; see
+Section~\ref{sec:alias}.
+
+\subsubsubsection{At Field Assignments}
+
+For a field update of the form $o_1.f_1 = e$, the
+dataflow analysis first determines the abstract value $e_\text{val}$ for $e$.
+Then, it updates the store $S$ as follows.
+\begin{enumerate}
+    \item For every field access $o_2.f_2$ that is a key in $S$, remove its
+      information if $f_1 = f_2$ and
+      $o_2$ lexically contains a \nonterminal{Receiver} that \emph{might}
+        alias $o_1.f$ as determined by the $\alias$
+        predicate.
+      Note that the ``lexically contains'' notion for pure method calls
+        includes both the receiver as well as the arguments.
+
+      This includes the case where $o_1 = o_2$ (that is, they are
+      syntactically the same) and the case where  $o_2$ and $o_1.f$ might be
+      aliases.
+
+      \begin{workinprogress}
+        Should the two occurrences of field $f$ in this paragraph be $f_1$?
+      \end{workinprogress}
+
+    \item $S[o_1.f_1] = e_\text{val}$
+\end{enumerate}
+
+
+\subsubsubsection{At Local Variable Assignments}
+
+For a local variable assignment of the form $x = e$,
+the dataflow analysis first determines the abstract value $e_\text{val}$ for
+$e$.
+Then, it updates the store $S$ as follows.
+\begin{enumerate}
+    \item For every field access $o_2.f_2$ that is a key in $S$,
+      remove its information if $o_2$ lexically contains a
+      \nonterminal{Receiver} that \emph{might} alias $x$ as determined
+      by the $\alias$ predicate.
+    \item $S[x] = e_\text{val}$
+\end{enumerate}
+
+\subsubsubsection{At Other Assignments}
+
+For any other assignment $z = e$ where the
+assignment target $z$ is not a field or local variable,
+the dataflow analysis first determines the abstract value $e_\text{val}$ for
+$e$.
+Then, it updates the store $S$ as follows.
+\begin{enumerate}
+    \item For every field access $o_2.f_2$, remove its information if
+    $o_2$ lexically contains a \nonterminal{Receiver} that \emph{might}
+    alias $z$ as determined by the $\alias$ predicate.
+\end{enumerate}
+
+
+\subsubsubsection{At Non-Pure Method Calls}
+
+A non-pure method call might modify the value of any field arbitrarily.
+Therefore, at a method call, any information about fields is lost.
+
+
+\subsubsubsection{Alias Information}
+\label{sec:alias}
+
+The Checker Framework does not include an aliasing analysis, which could
+provide precise aliasing information.  For this reason, we implement the
+predicate $\alias$ as follows:
+\[ \alias(o_1,o_2) :=
+\left(\operatorname{type}(o_1) <: \operatorname{type}(o_2)
+\;\;\text{or}\;\;
+\operatorname{type}(o_2) <: \operatorname{type}(o_1) \right) \]
+where $\operatorname{type}(o)$ determines the Java type of a reference $o$
+and $<:$ denotes standard Java subtyping.
+
+
+%%% Local Variables:
+%%% mode: latex
+%%% TeX-master: "dataflow"
+%%% TeX-command-default: "PDF"
+%%% End:
+
+%  LocalWords:  pre cfg javacutils InternalUtils DivideByZero BlockImpl se
+%  LocalWords:  SingleSuccessorBlock RegularBlock SingleSuccessorBlockImpl
+%  LocalWords:  ExceptionBlock SpecialBlock ConditionalBlock SpecialBlocks
+%  LocalWords:  ControlFlowGraph ControlFlowGraphs CFGBuilder ast CFValue
+%  LocalWords:  JavaExpressions AbstractValue AbstractValues PolyNull Poly
+%  LocalWords:  AnnotatedTypeMirrors CFAbstractValue NullnessValue CFStore
+%  LocalWords:  NullnessCheckers CFAbstractStore InitializationStore Regex
+%  LocalWords:  NullnessStore TransferInput TransferResult TransferInputs
+%  LocalWords:  ConditionalTransferResult RegularTransferResult CFTransfer
+%  LocalWords:  TransferFunction NodeVisitor TransferResults isRegex lst
+%  LocalWords:  CFAbstractTransfer AbstractNodeVisitor asRegex ClassTree
+%  LocalWords:  InitializationTransfer visitMethodInvocation RegexTransfer
+%  LocalWords:  CFAbstractAnalysis NullnessTransfer RegexChecker's lp Uber
+%  LocalWords:  AnalysisResult AnnotatedTypeFactory AnnotatedTypeFactorys
+%  LocalWords:  AnnotationProvider getAnnotatedType BaseTypeChecker
+%  LocalWords:  GenericAnnotatedTypeFactory FlowAnalysis CFAnalysis
+%  LocalWords:  BaseAnnotatedTypeFactory CFGSimple ValueLiteral txt
+%  LocalWords:  BooleanLiteral CharacterLiteral DoubleLiteral FloatLiteral
+%  LocalWords:  IntegerLiteral LongLiteral NullLiteral ShortLiteral args
+%  LocalWords:  StringLiteral Accessor ArrayAccess FieldAccess This
+%  LocalWords:  MethodAccess ExplicitThis ImplicitThis java NullAway
+%  LocalWords:  LocalVariable MethodInvocation BitwiseAnd BitwiseOr MyType
+%  LocalWords:  BitwiseComplement BitwiseXor ConditionalAnd ConditionalNot
+%  LocalWords:  ConditionalOr FloatingDivision FloatingRemainder LeftShift
+%  LocalWords:  IntegerDivision IntegerRemainder NumericalAddition EqualTo
+%  LocalWords:  NumericalMinus NumericalMultiplication NumericalPlus util
+%  LocalWords:  NumericalSubtraction SignedRightShift StringConcatenate
+%  LocalWords:  TernaryExpression UnsignedRightShift NotEqual GreaterThan
+%  LocalWords:  GreaterThanOrEqual LessThan LessThanOrEqual ArrayCreation
+%  LocalWords:  StringConcatenateAssignment ObjectCreation TypeCast cond
+%  LocalWords:  InstanceOf instanceof NarrowingConversion StringConversion
+%  LocalWords:  WideningConversion ArrayType ParameterizedType ClassName
+%  LocalWords:  PrimitiveType PackageName AssertionError NullChk accessor
+%  LocalWords:  VariableDeclaration desugared desugaring FieldAssignment
+%  LocalWords:  CFGFieldAssignment getRegularStore CFGConditionalOr fnc
+%  LocalWords:  CFGConditionalOr2 ConditionalOrNode ConditionalOr2 valueOf
+%  LocalWords:  IdentifierTree FieldAccessNode ImplicitThisNode unboxing
+%  LocalWords:  TreePath pathHack MethodTree VarSymbol DetachedVarSymbols
+%  LocalWords:  DeclarationFromElement BoxedClass treeLookup getValue
+%  LocalWords:  nodeValues AdditionNode postconversion treelookup desugar
+%  LocalWords:  LocalVariableNode AconcurrentSemantics MonotonicNonNull
+%  LocalWords:  MonotonicQualifier IntegerLiteralNode EqualToNode
+%  LocalWords:  ConstSimple SelfReference PureMethodCall javacutil
+%  LocalWords:  stubparser TreeParser TreeBuilder DetachedVarSymbol LiveSimple
diff --git a/dataflow/manual/dataflow-plse-presentation-2012-02-24.pdf b/dataflow/manual/dataflow-plse-presentation-2012-02-24.pdf
new file mode 100644
index 0000000..aefd0e3
--- /dev/null
+++ b/dataflow/manual/dataflow-plse-presentation-2012-02-24.pdf
Binary files differ
diff --git a/dataflow/manual/dataflow.tex b/dataflow/manual/dataflow.tex
new file mode 100644
index 0000000..26df71a
--- /dev/null
+++ b/dataflow/manual/dataflow.tex
@@ -0,0 +1,146 @@
+%
+% A Dataflow Framework for Java
+%
+% Original Authors: Stefan Heule, Charlie Garrett, 2011 - 2012
+%
+\documentclass[]{article}
+
+\usepackage[utf8]{inputenc}
+\usepackage[T1]{fontenc}
+\usepackage{lmodern}
+\usepackage{textcomp}
+% \usepackage[scaled=0.88]{luximono}
+\usepackage{graphicx}
+
+\usepackage{ucs}
+\usepackage{longtable}
+
+% At least 80% of every float page must be taken up by
+% floats; there will be no page with more than 20% white space.
+\def\topfraction{.95}
+\def\dbltopfraction{\topfraction}
+\def\floatpagefraction{\topfraction}     % default .5
+\def\dblfloatpagefraction{\topfraction}  % default .5
+\def\textfraction{.05}
+
+%% This doesn't work for longtable.  :-(
+% Add line between figure and text
+\makeatletter
+\def\topfigrule{\kern3\p@ \hrule \kern -3.4\p@} % the \hrule is .4pt high
+\def\botfigrule{\kern-3\p@ \hrule \kern 2.6\p@} % the \hrule is .4pt high
+\def\dblfigrule{\kern3\p@ \hrule \kern -3.4\p@} % the \hrule is .4pt high
+\makeatother
+
+
+\usepackage{listings}
+\lstset{
+    language=Java,
+    basicstyle=\ttfamily\mdseries,
+    identifierstyle=,
+    stringstyle=\color{gray},
+    numbers=left,
+    numbersep=5pt,
+    inputencoding=utf8x,
+    xleftmargin=8pt,
+    xrightmargin=8pt,
+    keywordstyle=[1]\bfseries,
+    keywordstyle=[2]\bfseries,
+    keywordstyle=[3]\bfseries,
+    keywordstyle=[4]\bfseries,
+    numberstyle=\tiny,
+    stepnumber=1,
+    breaklines=true,
+    breakatwhitespace,
+    frame=lines,
+    showstringspaces=false,
+    tabsize=2,
+    commentstyle=\color{gray},
+    captionpos=b
+}
+\newcommand{\code}[1]{\lstinline{#1}}
+
+\usepackage{fullpage}
+
+\def\sectionautorefname{Section}
+\def\subsectionautorefname{Section}
+\def\subsubsectionautorefname{Section}
+\def\paragraphautorefname{Section}
+\def\subsubsubsection{\paragraph}
+\setcounter{secnumdepth}{4}
+
+
+% macros for bnf grammers
+\newenvironment{bnfgrammar}{\begin{center}\renewcommand{\arraystretch}{1.5}\begin{tabular}{rcl}}{\end{tabular}\end{center}}
+\newcommand{\nonterminal}[1]{$\langle \textit{#1} \rangle$}
+\newcommand{\production}[2]{#1 &$::=$& #2\\}
+\newcommand{\literal}[1]{`$\text{\code{#1}}$'}
+\newcommand{\alt}{$~\!\!\mid~$}
+\newcommand{\altline}[1]{&$\mid$&#1\\}
+
+\usepackage{mathtools}
+\usepackage{amssymb,amsfonts,amsmath}
+\usepackage[svgnames]{xcolor}
+\usepackage[colorlinks=true,pdfborder={0 0 0},citecolor=DarkGreen,linkcolor=DarkBlue,urlcolor=DarkBlue]{hyperref}
+
+\usepackage{amsthm}
+% \usepackage{thmtools}
+%\theoremstyle{definition}
+\newtheorem{definition}{Definition}[section]
+
+% control-flow graph images and listings
+% Arguments 2 and 3 control how much horizontal space the code takes on the page.
+\newcommand{\flow}[4]{
+\begin{figure}
+\centering\vspace{0pt}
+\begin{minipage}[t]{#2\textwidth}
+\centering\vspace{0pt}
+\begin{minipage}[t]{#3\textwidth}
+\lstinputlisting{examples/#1.java}
+\end{minipage}%
+\end{minipage}%
+\begin{minipage}[t]{.6\textwidth}
+\centering\vspace{0pt}
+\includegraphics[scale=0.6]{examples/graphs/#1.pdf}
+\centering
+\end{minipage}
+\caption{#4}
+\label{fig:#1}
+\end{figure}}
+
+% references to the Java Language Specification.
+\newcommand{\jlsref}[1]{JLS~\textsection{}#1}
+
+
+\usepackage{framed}
+\newenvironment{workinprogress}
+{\color{gray} \begin{leftbar}}
+{\end{leftbar}}
+
+\newenvironment{new}{\begin{framed}}{\end{framed}}
+
+\usepackage{booktabs}
+
+\newcommand{\todo}[1]{{\textcolor{DarkRed}{ [\textbf{TODO}: #1]}}}
+
+\parindent 0em
+\setlength{\parskip}{.5em}
+
+\title{A Dataflow Framework for Java}
+\author{\url{https://checkerframework.org/}}
+
+\begin{document}
+
+\maketitle
+
+\input{content.tex}
+
+%\bibliographystyle{alpha}
+%\bibliography{}
+
+\end{document}
+
+%%% Local Variables:
+%%% mode: latex
+%%% TeX-master: t
+%%% TeX-command-default: "PDF"
+%%% End:
diff --git a/dataflow/manual/examples/CFGAssert.java b/dataflow/manual/examples/CFGAssert.java
new file mode 100644
index 0000000..1bc3eb5
--- /dev/null
+++ b/dataflow/manual/examples/CFGAssert.java
@@ -0,0 +1,6 @@
+class Test {
+  void testAssert(Object a) {
+    assert a != null
+      : "Argument is null";
+  }
+}
diff --git a/dataflow/manual/examples/CFGConditionalOr.java b/dataflow/manual/examples/CFGConditionalOr.java
new file mode 100644
index 0000000..03e263a
--- /dev/null
+++ b/dataflow/manual/examples/CFGConditionalOr.java
@@ -0,0 +1,8 @@
+class Test {
+  void test(boolean b1, boolean b2, boolean b3) {
+    int x = 0;
+    if (b1 || (b2 || b3)) {
+      x = 1;
+    }
+  }
+}
diff --git a/dataflow/manual/examples/CFGConditionalOr2.java b/dataflow/manual/examples/CFGConditionalOr2.java
new file mode 100644
index 0000000..dc9b285
--- /dev/null
+++ b/dataflow/manual/examples/CFGConditionalOr2.java
@@ -0,0 +1,6 @@
+class Test {
+  void test(boolean b1, boolean b2, boolean b3) {
+    int x = 0;
+    boolean b = b1 || (b2 || b3);
+  }
+}
diff --git a/dataflow/manual/examples/CFGFieldAssignment.java b/dataflow/manual/examples/CFGFieldAssignment.java
new file mode 100644
index 0000000..898e188
--- /dev/null
+++ b/dataflow/manual/examples/CFGFieldAssignment.java
@@ -0,0 +1,7 @@
+class Test {
+  int f;
+
+  void test(Test x) {
+    x.f = 1;
+  }
+}
diff --git a/dataflow/manual/examples/CFGIfStatement.java b/dataflow/manual/examples/CFGIfStatement.java
new file mode 100644
index 0000000..f4ffd99
--- /dev/null
+++ b/dataflow/manual/examples/CFGIfStatement.java
@@ -0,0 +1,10 @@
+class Test {
+  void testIf(boolean b1) {
+    int x = 0;
+    if (b1) {
+      x = 1;
+    } else {
+      x = 2;
+    }
+  }
+}
diff --git a/dataflow/manual/examples/CFGSimple.java b/dataflow/manual/examples/CFGSimple.java
new file mode 100644
index 0000000..2aedb61
--- /dev/null
+++ b/dataflow/manual/examples/CFGSimple.java
@@ -0,0 +1,8 @@
+class Test {
+  void test(boolean b) {
+    int x = 2;
+    if (b) {
+      x = 1;
+    }
+  }
+}
diff --git a/dataflow/manual/examples/CFGSwitch.java b/dataflow/manual/examples/CFGSwitch.java
new file mode 100644
index 0000000..ae8eea2
--- /dev/null
+++ b/dataflow/manual/examples/CFGSwitch.java
@@ -0,0 +1,14 @@
+class Test {
+  void test(int x) {
+    switch (x) {
+      case 1:
+        int a = x;
+        break;
+      case 2:
+        int b = x;
+      default:
+        int c = x;
+        break;
+    }
+  }
+}
diff --git a/dataflow/manual/examples/ConstSimple.java b/dataflow/manual/examples/ConstSimple.java
new file mode 100644
index 0000000..150dfd2
--- /dev/null
+++ b/dataflow/manual/examples/ConstSimple.java
@@ -0,0 +1,16 @@
+class Test {
+  void test(boolean b, int a) {
+    int x = 1;
+    int y = 0;
+    if (b) {
+      x = 2;
+    } else {
+      x = 2;
+      y = a;
+    }
+    x = 3;
+    if (a == 2) {
+      x = 4;
+    }
+  }
+}
diff --git a/dataflow/manual/examples/LiveSimple.java b/dataflow/manual/examples/LiveSimple.java
new file mode 100644
index 0000000..74c1c2d
--- /dev/null
+++ b/dataflow/manual/examples/LiveSimple.java
@@ -0,0 +1,10 @@
+class Test {
+  public void test() {
+    int a = 1, b = 2, c = 3;
+    if (a > 0) {
+      int d = a + c;
+    } else {
+      int e = a + b;
+    }
+  }
+}
diff --git a/dataflow/manual/examples/README.txt b/dataflow/manual/examples/README.txt
new file mode 100644
index 0000000..b32d1de
--- /dev/null
+++ b/dataflow/manual/examples/README.txt
@@ -0,0 +1 @@
+The graphs in ./graphs have been generated by running the Checker Framework on the Java files (using the -Aflowdotdir commmand-line option) and then running Graphviz on the output.
diff --git a/dataflow/manual/examples/graphs/CFGAssert.pdf b/dataflow/manual/examples/graphs/CFGAssert.pdf
new file mode 100644
index 0000000..6976c4b
--- /dev/null
+++ b/dataflow/manual/examples/graphs/CFGAssert.pdf
Binary files differ
diff --git a/dataflow/manual/examples/graphs/CFGConditionalOr.pdf b/dataflow/manual/examples/graphs/CFGConditionalOr.pdf
new file mode 100644
index 0000000..be87b1c
--- /dev/null
+++ b/dataflow/manual/examples/graphs/CFGConditionalOr.pdf
Binary files differ
diff --git a/dataflow/manual/examples/graphs/CFGConditionalOr2.pdf b/dataflow/manual/examples/graphs/CFGConditionalOr2.pdf
new file mode 100644
index 0000000..c4c4c9b
--- /dev/null
+++ b/dataflow/manual/examples/graphs/CFGConditionalOr2.pdf
Binary files differ
diff --git a/dataflow/manual/examples/graphs/CFGFieldAssignment.pdf b/dataflow/manual/examples/graphs/CFGFieldAssignment.pdf
new file mode 100644
index 0000000..044c2c3
--- /dev/null
+++ b/dataflow/manual/examples/graphs/CFGFieldAssignment.pdf
Binary files differ
diff --git a/dataflow/manual/examples/graphs/CFGIfStatement.pdf b/dataflow/manual/examples/graphs/CFGIfStatement.pdf
new file mode 100644
index 0000000..59b3b03
--- /dev/null
+++ b/dataflow/manual/examples/graphs/CFGIfStatement.pdf
Binary files differ
diff --git a/dataflow/manual/examples/graphs/CFGSimple.pdf b/dataflow/manual/examples/graphs/CFGSimple.pdf
new file mode 100644
index 0000000..465b20b
--- /dev/null
+++ b/dataflow/manual/examples/graphs/CFGSimple.pdf
Binary files differ
diff --git a/dataflow/manual/examples/graphs/CFGSwitch.pdf b/dataflow/manual/examples/graphs/CFGSwitch.pdf
new file mode 100644
index 0000000..df8e478
--- /dev/null
+++ b/dataflow/manual/examples/graphs/CFGSwitch.pdf
Binary files differ
diff --git a/dataflow/manual/examples/graphs/CondSimple.pdf b/dataflow/manual/examples/graphs/CondSimple.pdf
new file mode 100644
index 0000000..315b2da
--- /dev/null
+++ b/dataflow/manual/examples/graphs/CondSimple.pdf
Binary files differ
diff --git a/dataflow/manual/examples/graphs/ConstSimple.pdf b/dataflow/manual/examples/graphs/ConstSimple.pdf
new file mode 100644
index 0000000..ad83b42
--- /dev/null
+++ b/dataflow/manual/examples/graphs/ConstSimple.pdf
Binary files differ
diff --git a/dataflow/manual/examples/graphs/LiveSimple.pdf b/dataflow/manual/examples/graphs/LiveSimple.pdf
new file mode 100644
index 0000000..a1e44e5
--- /dev/null
+++ b/dataflow/manual/examples/graphs/LiveSimple.pdf
Binary files differ
diff --git a/dataflow/manual/listings.sty b/dataflow/manual/listings.sty
new file mode 100644
index 0000000..613a99c
--- /dev/null
+++ b/dataflow/manual/listings.sty
@@ -0,0 +1,2243 @@
+%%
+%% This is file `listings.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% listings.dtx  (with options: `kernel')
+%% 
+%% Please read the software license in listings-1.3.dtx or listings-1.3.pdf.
+%%
+%% (w)(c) 1996--2004 Carsten Heinz and/or any other author listed
+%% elsewhere in this file.
+%% (c) 2006 Brooks Moses
+%% (c) 2013- Jobst Hoffmann
+%%
+%% Send comments and ideas on the package, error reports and additional
+%% programming languages to Jobst Hoffmann at <j.hoffmann@fh-aachen.de>.
+%%
+\def\filedate{2015/06/04}
+\def\fileversion{1.6}
+\NeedsTeXFormat{LaTeX2e}
+\AtEndOfPackage{\ProvidesPackage{listings}
+             [\filedate\space\fileversion\space(Carsten Heinz)]}
+\def\lst@CheckVersion#1{\edef\reserved@a{#1}%
+    \ifx\lst@version\reserved@a \expandafter\@gobble
+                          \else \expandafter\@firstofone \fi}
+\let\lst@version\fileversion
+\def\lst@InputCatcodes{%
+    \makeatletter \catcode`\"12%
+    \catcode`\^^@\active
+    \catcode`\^^I9%
+    \catcode`\^^L9%
+    \catcode`\^^M9%
+    \catcode`\%14%
+    \catcode`\~\active}
+\def\lst@RestoreCatcodes#1{%
+    \ifx\relax#1\else
+        \noexpand\catcode`\noexpand#1\the\catcode`#1\relax
+        \expandafter\lst@RestoreCatcodes
+    \fi}
+\edef\lst@RestoreCatcodes{%
+    \noexpand\lccode`\noexpand\/`\noexpand\/%
+    \lst@RestoreCatcodes\"\^^I\^^M\~\^^@\relax
+    \catcode12\active}
+\lst@InputCatcodes
+\AtEndOfPackage{\lst@RestoreCatcodes}
+\def\@lst{lst}
+\def\lst@IfSubstring#1#2{%
+    \def\lst@temp##1#1##2##3\relax{%
+        \ifx \@empty##2\expandafter\@secondoftwo
+                 \else \expandafter\@firstoftwo \fi}%
+    \expandafter\lst@temp#2#1\@empty\relax}
+\def\lst@IfOneOf#1\relax#2{%
+    \def\lst@temp##1,#1,##2##3\relax{%
+        \ifx \@empty##2\expandafter\@secondoftwo
+                 \else \expandafter\@firstoftwo \fi}%
+    \expandafter\lst@temp\expandafter,#2,#1,\@empty\relax}
+\def\lst@DeleteKeysIn#1#2{%
+    \expandafter\lst@DeleteKeysIn@\expandafter#1#2,\relax,}
+\def\lst@DeleteKeysIn@#1#2,{%
+    \ifx\relax#2\@empty
+        \expandafter\@firstoftwo\expandafter\lst@RemoveCommas
+    \else
+        \ifx\@empty#2\@empty\else
+            \def\lst@temp##1,#2,##2{%
+                ##1%
+                \ifx\@empty##2\@empty\else
+                    \expandafter\lst@temp\expandafter,%
+                \fi ##2}%
+            \edef#1{\expandafter\lst@temp\expandafter,#1,#2,\@empty}%
+        \fi
+    \fi
+    \lst@DeleteKeysIn@#1}
+\def\lst@RemoveCommas#1{\edef#1{\expandafter\lst@RC@#1\@empty}}
+\def\lst@RC@#1{\ifx,#1\expandafter\lst@RC@ \else #1\fi}
+\def\lst@ReplaceIn#1#2{%
+    \expandafter\lst@ReplaceIn@\expandafter#1#2\@empty\@empty}
+\def\lst@ReplaceInArg#1#2{\lst@ReplaceIn@#1#2\@empty\@empty}
+\def\lst@ReplaceIn@#1#2#3{%
+    \ifx\@empty#3\relax\else
+        \def\lst@temp##1#2##2{%
+            \ifx\@empty##2%
+                \lst@lAddTo#1{##1}%
+            \else
+                \lst@lAddTo#1{##1#3}\expandafter\lst@temp
+            \fi ##2}%
+        \let\@tempa#1\let#1\@empty
+        \expandafter\lst@temp\@tempa#2\@empty
+        \expandafter\lst@ReplaceIn@\expandafter#1%
+    \fi}
+\providecommand*\@gobblethree[3]{}
+\def\lst@GobbleNil#1\@nil{}
+\def\lst@Swap#1#2{#2#1}
+\def\lst@true{\let\lst@if\iftrue}
+\def\lst@false{\let\lst@if\iffalse}
+\lst@false
+\def\lst@IfNextCharsArg#1{%
+    \def\lst@tofind{#1}\lst@IfNextChars\lst@tofind}
+\def\lst@IfNextChars#1#2#3{%
+    \let\lst@tofind#1\def\@tempa{#2}\def\@tempb{#3}%
+    \let\lst@eaten\@empty \lst@IfNextChars@}
+\def\lst@IfNextChars@{\expandafter\lst@IfNextChars@@\lst@tofind\relax}
+\def\lst@IfNextChars@@#1#2\relax#3{%
+    \def\lst@tofind{#2}\lst@lAddTo\lst@eaten{#3}%
+    \ifx#1#3%
+        \ifx\lst@tofind\@empty
+            \let\lst@next\@tempa
+        \else
+            \let\lst@next\lst@IfNextChars@
+        \fi
+        \expandafter\lst@next
+    \else
+        \expandafter\@tempb
+    \fi}
+\def\lst@IfNextCharActive#1#2#3{%
+    \begingroup \lccode`\~=`#3\lowercase{\endgroup
+    \ifx~}#3%
+        \def\lst@next{#1}%
+    \else
+        \def\lst@next{#2}%
+    \fi \lst@next #3}
+\def\lst@for#1\do#2{%
+  \def\lst@forbody##1{#2}%
+  \def\@tempa{#1}%
+  \ifx\@tempa\@empty\else\expandafter\lst@f@r#1,\@nil,\fi
+}
+\def\lst@f@r#1,{%
+  \def\@tempa{#1}%
+  \ifx\@tempa\@nnil\else\lst@forbody{#1}\expandafter\lst@f@r\fi
+}
+\def\lst@MakeActive#1{%
+    \let\lst@temp\@empty \lst@MakeActive@#1%
+    \relax\relax\relax\relax\relax\relax\relax\relax\relax}
+\begingroup
+\catcode`\^^@=\active \catcode`\^^A=\active \catcode`\^^B=\active
+\catcode`\^^C=\active \catcode`\^^D=\active \catcode`\^^E=\active
+\catcode`\^^F=\active \catcode`\^^G=\active \catcode`\^^H=\active
+\gdef\lst@MakeActive@#1#2#3#4#5#6#7#8#9{\let\lst@next\relax
+    \ifx#1\relax
+    \else \lccode`\^^@=`#1%
+    \ifx#2\relax
+        \lowercase{\lst@lAddTo\lst@temp{^^@}}%
+    \else \lccode`\^^A=`#2%
+    \ifx#3\relax
+        \lowercase{\lst@lAddTo\lst@temp{^^@^^A}}%
+    \else \lccode`\^^B=`#3%
+    \ifx#4\relax
+        \lowercase{\lst@lAddTo\lst@temp{^^@^^A^^B}}%
+    \else \lccode`\^^C=`#4%
+    \ifx#5\relax
+        \lowercase{\lst@lAddTo\lst@temp{^^@^^A^^B^^C}}%
+    \else \lccode`\^^D=`#5%
+    \ifx#6\relax
+        \lowercase{\lst@lAddTo\lst@temp{^^@^^A^^B^^C^^D}}%
+    \else \lccode`\^^E=`#6%
+    \ifx#7\relax
+        \lowercase{\lst@lAddTo\lst@temp{^^@^^A^^B^^C^^D^^E}}%
+    \else \lccode`\^^F=`#7%
+    \ifx#8\relax
+        \lowercase{\lst@lAddTo\lst@temp{^^@^^A^^B^^C^^D^^E^^F}}%
+    \else \lccode`\^^G=`#8%
+    \ifx#9\relax
+        \lowercase{\lst@lAddTo\lst@temp{^^@^^A^^B^^C^^D^^E^^F^^G}}%
+    \else \lccode`\^^H=`#9%
+        \lowercase{\lst@lAddTo\lst@temp{^^@^^A^^B^^C^^D^^E^^F^^G^^H}}%
+        \let\lst@next\lst@MakeActive@
+    \fi \fi \fi \fi \fi \fi \fi \fi \fi
+    \lst@next}
+\endgroup
+\def\lst@DefActive#1#2{\lst@MakeActive{#2}\let#1\lst@temp}
+\def\lst@DefOther#1#2{%
+    \begingroup \def#1{#2}\escapechar\m@ne \expandafter\endgroup
+    \expandafter\lst@DefOther@\meaning#1\relax#1}
+\def\lst@DefOther@#1>#2\relax#3{\edef#3{\zap@space#2 \@empty}}
+\def\lst@InsideConvert#1{%
+   \lst@ifmathescape
+      \lst@InsideConvert@e#1$\@nil
+      \lst@if
+         \lst@InsideConvert@ey#1\@nil
+      \else
+         \lst@InsideConvert@#1 \@empty
+         \expandafter\@gobbletwo
+      \fi
+      \expandafter\lst@next
+   \else
+      \lst@InsideConvert@#1 \@empty
+   \fi}
+\begingroup \lccode`\~=`\ \relax \lowercase{%
+\gdef\lst@InsideConvert@#1 #2{%
+    \lst@MakeActive{#1}%
+    \ifx\@empty#2%
+        \lst@lExtend\lst@arg{\lst@temp}%
+    \else
+        \lst@lExtend\lst@arg{\lst@temp~}%
+        \expandafter\lst@InsideConvert@
+    \fi #2}
+}\endgroup
+\def\lst@InsideConvert@e#1$#2\@nil{%
+   \ifx\@empty#2\@empty \lst@false \else \lst@true \fi}
+\def\lst@InsideConvert@ey#1$#2$#3\@nil{%
+   \lst@InsideConvert@#1 \@empty
+   \lst@lAddTo\lst@arg{%
+      \lst@ifdropinput\else
+         \lst@TrackNewLines\lst@OutputLostSpace \lst@XPrintToken
+         \setbox\@tempboxa=\hbox\bgroup$\lst@escapebegin
+         #2%
+         \lst@escapeend$\egroup \lst@CalcLostSpaceAndOutput
+         \lst@whitespacefalse
+      \fi}%
+   \def\lst@next{\lst@InsideConvert{#3}}%
+}
+\def\lst@XConvert{\@ifnextchar\bgroup \lst@XConvertArg\lst@XConvert@}
+\def\lst@XConvertArg#1{%
+    {\lst@false \let\lst@arg\@empty
+     \lst@XConvert#1\@nil
+     \global\let\@gtempa\lst@arg}%
+    \lst@lExtend\lst@arg{\expandafter{\@gtempa}}%
+    \lst@XConvertNext}
+\def\lst@XConvert@#1{%
+    \ifx\@nil#1\else
+        \begingroup\lccode`\~=`#1\lowercase{\endgroup
+        \lst@lAddTo\lst@arg~}%
+        \expandafter\lst@XConvertNext
+    \fi}
+\def\lst@XConvertNext{%
+    \lst@if \expandafter\lst@XConvertX
+      \else \expandafter\lst@XConvert \fi}
+\def\lst@XConvertX#1{%
+    \ifx\@nil#1\else
+        \lst@XConvertX@#1\relax
+        \expandafter\lst@XConvert
+    \fi}
+\def\lst@XConvertX@#1#2\relax{%
+    \begingroup\lccode`\~=`#1\lowercase{\endgroup
+    \lst@XCConvertX@@~}{#2}}
+\def\lst@XCConvertX@@#1#2{\lst@lAddTo\lst@arg{{#1#2}}}
+\def\lst@Require#1#2#3#4#5{%
+    \begingroup
+    \aftergroup\lst@true
+    \ifx\@empty#3\@empty\else
+        \def\lst@prefix{#2}\let\lst@require\@empty
+        \edef\lst@temp{\expandafter\zap@space#3 \@empty}%
+        \lst@for\lst@temp\do{%
+          \ifx\@empty##1\@empty\else \lstKV@OptArg[]{##1}{%
+            #4[####1]{####2}%
+            \@ifundefined{\@lst\lst@prefix @\lst@malias $\lst@oalias}%
+            {\edef\lst@require{\lst@require,\lst@malias $\lst@oalias}}%
+            {}}%
+          \fi}%
+        \global\let\lst@loadaspects\@empty
+        \lst@InputCatcodes
+        \ifx\lst@require\@empty\else
+            \lst@for{#5}\do{%
+                \ifx\lst@require\@empty\else
+                    \InputIfFileExists{##1}{}{}%
+                \fi}%
+        \fi
+        \ifx\lst@require\@empty\else
+            \PackageError{Listings}{Couldn't load requested #1}%
+            {The following #1s weren't loadable:^^J\@spaces
+             \lst@require^^JThis may cause errors in the sequel.}%
+            \aftergroup\lst@false
+        \fi
+        \ifx\lst@loadaspects\@empty\else
+            \lst@RequireAspects\lst@loadaspects
+        \fi
+    \fi
+    \endgroup}
+\def\lst@IfRequired[#1]#2{%
+    \lst@NormedDef\lst@temp{[#1]#2}%
+    \expandafter\lst@IfRequired@\lst@temp\relax}
+\def\lst@IfRequired@[#1]#2\relax#3{%
+    \lst@IfOneOf #2$#1\relax\lst@require
+        {\lst@DeleteKeysIn@\lst@require#2$#1,\relax,%
+         \global\expandafter\let
+             \csname\@lst\lst@prefix @#2$#1\endcsname\@empty
+         #3}}
+\let\lst@require\@empty
+\def\lst@NoAlias[#1]#2{%
+    \lst@NormedDef\lst@oalias{#1}\lst@NormedDef\lst@malias{#2}}
+\gdef\lst@LAS#1#2#3#4#5#6#7{%
+    \lst@Require{#1}{#2}{#3}#4#5%
+    #4#3%
+    \@ifundefined{lst#2@\lst@malias$\lst@oalias}%
+        {\PackageError{Listings}%
+         {#1 \ifx\@empty\lst@oalias\else \lst@oalias\space of \fi
+          \lst@malias\space undefined}%
+         {The #1 is not loadable. \@ehc}}%
+        {#6\csname\@lst#2@\lst@malias $\lst@oalias\endcsname #7}}
+\def\lst@RequireAspects#1{%
+    \lst@Require{aspect}{asp}{#1}\lst@NoAlias\lstaspectfiles}
+\let\lstloadaspects\lst@RequireAspects
+\@ifundefined{lstaspectfiles}
+    {\newcommand\lstaspectfiles{lstmisc0.sty,lstmisc.sty}}{}
+\gdef\lst@DefDriver#1#2#3#4{%
+    \@ifnextchar[{\lst@DefDriver@{#1}{#2}#3#4}%
+                 {\lst@DefDriver@{#1}{#2}#3#4[]}}
+\gdef\lst@DefDriver@#1#2#3#4[#5]#6{%
+    \def\lst@name{#1}\let\lst@if#4%
+    \lst@NormedDef\lst@driver{\@lst#2@#6$#5}%
+    \lst@IfRequired[#5]{#6}{\begingroup \lst@true}%
+                           {\begingroup}%
+    \lst@setcatcodes
+    \@ifnextchar[{\lst@XDefDriver{#1}#3}{\lst@DefDriver@@#3}}
+\gdef\lst@DefDriver@@#1#2{%
+    \lst@if
+        \global\@namedef{\lst@driver}{#1{#2}}%
+    \fi
+    \endgroup
+    \@ifnextchar[\lst@XXDefDriver\@empty}
+\gdef\lst@XXDefDriver[#1]{%
+    \ifx\@empty#1\@empty\else
+        \lst@if
+            \lstloadaspects{#1}%
+        \else
+            \@ifundefined{\lst@driver}{}%
+            {\xdef\lst@loadaspects{\lst@loadaspects,#1}}%
+        \fi
+    \fi}
+\gdef\lst@XDefDriver#1#2[#3]#4#5{\lst@DefDriver@@#2{also#1=[#3]#4,#5}}
+\let\lst@UserCommand\gdef
+\newcommand*\lst@BeginAspect[2][]{%
+    \def\lst@curraspect{#2}%
+    \ifx \lst@curraspect\@empty
+        \expandafter\lst@GobbleAspect
+    \else
+        \let\lst@next\@empty
+        \lst@IfRequired[]{#2}%
+            {\lst@RequireAspects{#1}%
+             \lst@if\else \let\lst@next\lst@GobbleAspect \fi}%
+            {\let\lst@next\lst@GobbleAspect}%
+        \expandafter\lst@next
+    \fi}
+\def\lst@EndAspect{%
+    \csname\@lst patch@\lst@curraspect\endcsname
+    \let\lst@curraspect\@empty}
+\long\def\lst@GobbleAspect#1\lst@EndAspect{\let\lst@curraspect\@empty}
+\def\lst@Key#1#2{%
+    \@ifnextchar[{\lstKV@def{#1}{#2}}%
+                 {\def\lst@temp{\lst@Key@{#1}{#2}}
+                  \afterassignment\lst@temp
+                  \global\@namedef{KV@\@lst @#1}####1}}
+\def\lstKV@def#1#2[#3]{%
+    \global\@namedef{KV@\@lst @#1@default\expandafter}\expandafter
+        {\csname KV@\@lst @#1\endcsname{#3}}%
+    \def\lst@temp{\lst@Key@{#1}{#2}}\afterassignment\lst@temp
+    \global\@namedef{KV@\@lst @#1}##1}
+\def\lst@Key@#1#2{%
+    \ifx\relax#2\@empty\else
+        \begingroup \globaldefs\@ne
+        \csname KV@\@lst @#1\endcsname{#2}%
+        \endgroup
+    \fi}
+\def\lst@UseHook#1{\csname\@lst hk@#1\endcsname}
+\def\lst@AddToHook{\lst@ATH@\iffalse\lst@AddTo}
+\def\lst@AddToHookExe{\lst@ATH@\iftrue\lst@AddTo}
+\def\lst@AddToHookAtTop{\lst@ATH@\iffalse\lst@AddToAtTop}
+\long\def\lst@ATH@#1#2#3#4{%
+    \@ifundefined{\@lst hk@#3}{%
+        \expandafter\gdef\csname\@lst hk@#3\endcsname{}}{}%
+    \expandafter#2\csname\@lst hk@#3\endcsname{#4}%
+    \def\lst@temp{#4}%
+    #1% \iftrue|false
+        \begingroup \globaldefs\@ne \lst@temp \endgroup
+    \fi}
+\long\def\lst@AddTo#1#2{%
+    \expandafter\gdef\expandafter#1\expandafter{#1#2}}
+\def\lst@AddToAtTop#1#2{\def\lst@temp{#2}%
+    \expandafter\expandafter\expandafter\gdef
+    \expandafter\expandafter\expandafter#1%
+    \expandafter\expandafter\expandafter{\expandafter\lst@temp#1}}
+\def\lst@lAddTo#1#2{\expandafter\def\expandafter#1\expandafter{#1#2}}
+\def\lst@Extend#1#2{%
+    \expandafter\lst@AddTo\expandafter#1\expandafter{#2}}
+\def\lst@lExtend#1#2{%
+    \expandafter\lst@lAddTo\expandafter#1\expandafter{#2}}
+\RequirePackage{keyval}[1997/11/10]
+\def\lstKV@TwoArg#1#2{\gdef\@gtempa##1##2{#2}\@gtempa#1{}{}}
+\def\lstKV@ThreeArg#1#2{\gdef\@gtempa##1##2##3{#2}\@gtempa#1{}{}{}}
+\def\lstKV@FourArg#1#2{\gdef\@gtempa##1##2##3##4{#2}\@gtempa#1{}{}{}{}}
+\def\lstKV@OptArg[#1]#2#3{%
+    \gdef\@gtempa[##1]##2{#3}\lstKV@OptArg@{#1}#2\@}
+\def\lstKV@OptArg@#1{\@ifnextchar[\lstKV@OptArg@@{\lstKV@OptArg@@[#1]}}
+\def\lstKV@OptArg@@[#1]#2\@{\@gtempa[#1]{#2}}
+\def\lstKV@XOptArg[#1]#2#3{%
+    \global\let\@gtempa#3\lstKV@OptArg@{#1}#2\@}
+\def\lstKV@CSTwoArg#1#2{%
+    \gdef\@gtempa##1,##2,##3\relax{#2}%
+    \@gtempa#1,,\relax}
+\def\lstKV@SetIf#1{\lstKV@SetIf@#1\relax}
+\def\lstKV@SetIf@#1#2\relax#3{\lowercase{%
+    \expandafter\let\expandafter#3%
+        \csname if\ifx #1t}true\else false\fi\endcsname}
+\def\lstKV@SwitchCases#1#2#3{%
+    \def\lst@temp##1\\#1&##2\\##3##4\@nil{%
+        \ifx\@empty##3%
+            #3%
+        \else
+            ##2%
+        \fi
+    }%
+    \lst@temp\\#2\\#1&\\\@empty\@nil}
+\lst@UserCommand\lstset{\begingroup \lst@setcatcodes \lstset@}
+\def\lstset@#1{\endgroup \ifx\@empty#1\@empty\else\setkeys{lst}{#1}\fi}
+\def\lst@setcatcodes{\makeatletter \catcode`\==12\relax}
+\def\lst@NewMode#1{%
+    \ifx\@undefined#1%
+        \lst@mode\lst@newmode\relax \advance\lst@mode\@ne
+        \xdef\lst@newmode{\the\lst@mode}%
+        \global\chardef#1=\lst@mode
+        \lst@mode\lst@nomode
+    \fi}
+\newcount\lst@mode
+\def\lst@newmode{\m@ne}% init
+\lst@NewMode\lst@nomode % init (of \lst@mode :-)
+\def\lst@UseDynamicMode{%
+    \@tempcnta\lst@dynamicmode\relax \advance\@tempcnta\@ne
+    \edef\lst@dynamicmode{\the\@tempcnta}%
+    \expandafter\lst@Swap\expandafter{\expandafter{\lst@dynamicmode}}}
+\lst@AddToHook{InitVars}{\let\lst@dynamicmode\lst@newmode}
+\def\lst@EnterMode#1#2{%
+    \bgroup \lst@mode=#1\relax #2%
+    \lst@FontAdjust
+    \lst@lAddTo\lst@entermodes{\lst@EnterMode{#1}{#2}}}
+\lst@AddToHook{InitVars}{\let\lst@entermodes\@empty}
+\let\lst@entermodes\@empty % init
+\def\lst@LeaveMode{%
+    \ifnum\lst@mode=\lst@nomode\else
+        \egroup \expandafter\lsthk@EndGroup
+    \fi}
+\lst@AddToHook{EndGroup}{}% init
+\def\lst@InterruptModes{%
+    \lst@Extend\lst@modestack{\expandafter{\lst@entermodes}}%
+    \lst@LeaveAllModes}
+\lst@AddToHook{InitVars}{\global\let\lst@modestack\@empty}
+\def\lst@ReenterModes{%
+    \ifx\lst@modestack\@empty\else
+        \lst@LeaveAllModes
+        \global\let\@gtempa\lst@modestack
+        \global\let\lst@modestack\@empty
+        \expandafter\lst@ReenterModes@\@gtempa\relax
+    \fi}
+\def\lst@ReenterModes@#1#2{%
+    \ifx\relax#2\@empty
+        \gdef\@gtempa##1{#1}%
+        \expandafter\@gtempa
+    \else
+        \lst@AddTo\lst@modestack{{#1}}%
+        \expandafter\lst@ReenterModes@
+    \fi
+    {#2}}
+\def\lst@LeaveAllModes{%
+    \ifnum\lst@mode=\lst@nomode
+        \expandafter\lsthk@EndGroup
+    \else
+        \expandafter\egroup\expandafter\lst@LeaveAllModes
+    \fi}
+\lst@AddToHook{ExitVars}{\lst@LeaveAllModes}
+\lst@NewMode\lst@Pmode
+\lst@NewMode\lst@GPmode
+\def\lst@modetrue{\let\lst@ifmode\iftrue \lsthk@ModeTrue}
+\let\lst@ifmode\iffalse % init
+\lst@AddToHook{ModeTrue}{}% init
+\def\lst@Lmodetrue{\let\lst@ifLmode\iftrue}
+\let\lst@ifLmode\iffalse % init
+\lst@AddToHook{EOL}{\@whilesw \lst@ifLmode\fi \lst@LeaveMode}
+\def\lst@NormedDef#1#2{\lowercase{\edef#1{\zap@space#2 \@empty}}}
+\def\lst@NormedNameDef#1#2{%
+    \lowercase{\edef\lst@temp{\zap@space#1 \@empty}%
+    \expandafter\xdef\csname\lst@temp\endcsname{\zap@space#2 \@empty}}}
+\def\lst@GetFreeMacro#1{%
+    \@tempcnta\z@ \def\lst@freemacro{#1\the\@tempcnta}%
+    \lst@GFM@}
+\def\lst@GFM@{%
+    \expandafter\ifx \csname\lst@freemacro\endcsname \relax
+        \edef\lst@freemacro{\csname\lst@freemacro\endcsname}%
+    \else
+        \advance\@tempcnta\@ne
+        \expandafter\lst@GFM@
+    \fi}
+\newbox\lst@gtempboxa
+\newtoks\lst@token \newcount\lst@length
+\def\lst@ResetToken{\lst@token{}\lst@length\z@}
+\lst@AddToHook{InitVarsBOL}{\lst@ResetToken \let\lst@lastother\@empty}
+\lst@AddToHook{EndGroup}{\lst@ResetToken \let\lst@lastother\@empty}
+\def\lst@lettertrue{\let\lst@ifletter\iftrue}
+\def\lst@letterfalse{\let\lst@ifletter\iffalse}
+\lst@AddToHook{InitVars}{\lst@letterfalse}
+\def\lst@Append#1{\advance\lst@length\@ne
+                  \lst@token=\expandafter{\the\lst@token#1}}
+\def\lst@AppendOther{%
+    \lst@ifletter \lst@Output\lst@letterfalse \fi
+    \futurelet\lst@lastother\lst@Append}
+\def\lst@AppendLetter{%
+    \lst@ifletter\else \lst@OutputOther\lst@lettertrue \fi
+    \lst@Append}
+\def\lst@SaveToken{%
+    \global\let\lst@gthestyle\lst@thestyle
+    \global\let\lst@glastother\lst@lastother
+    \xdef\lst@RestoreToken{\noexpand\lst@token{\the\lst@token}%
+                           \noexpand\lst@length\the\lst@length\relax
+                           \noexpand\let\noexpand\lst@thestyle
+                                        \noexpand\lst@gthestyle
+                           \noexpand\let\noexpand\lst@lastother
+                                        \noexpand\lst@glastother}}
+\def\lst@IfLastOtherOneOf#1{\lst@IfLastOtherOneOf@ #1\relax}
+\def\lst@IfLastOtherOneOf@#1{%
+    \ifx #1\relax
+        \expandafter\@secondoftwo
+    \else
+        \ifx\lst@lastother#1%
+            \lst@IfLastOtherOneOf@t
+        \else
+            \expandafter\expandafter\expandafter\lst@IfLastOtherOneOf@
+        \fi
+    \fi}
+\def\lst@IfLastOtherOneOf@t#1\fi\fi#2\relax{\fi\fi\@firstoftwo}
+\newdimen\lst@currlwidth % \global
+\newcount\lst@column \newcount\lst@pos % \global
+\lst@AddToHook{InitVarsBOL}
+    {\global\lst@currlwidth\z@ \global\lst@pos\z@ \global\lst@column\z@}
+\def\lst@CalcColumn{%
+            \@tempcnta\lst@column
+    \advance\@tempcnta\lst@length
+    \advance\@tempcnta-\lst@pos}
+\newdimen\lst@lostspace % \global
+\lst@AddToHook{InitVarsBOL}{\global\lst@lostspace\z@}
+\def\lst@UseLostSpace{\ifdim\lst@lostspace>\z@ \lst@InsertLostSpace \fi}
+\def\lst@InsertLostSpace{%
+    \lst@Kern\lst@lostspace \global\lst@lostspace\z@}
+\def\lst@InsertHalfLostSpace{%
+    \global\lst@lostspace.5\lst@lostspace \lst@Kern\lst@lostspace}
+\newdimen\lst@width
+\lst@Key{basewidth}{0.6em,0.45em}{\lstKV@CSTwoArg{#1}%
+    {\def\lst@widthfixed{##1}\def\lst@widthflexible{##2}%
+     \ifx\lst@widthflexible\@empty
+         \let\lst@widthflexible\lst@widthfixed
+     \fi
+     \def\lst@temp{\PackageError{Listings}%
+                                {Negative value(s) treated as zero}%
+                                \@ehc}%
+     \let\lst@error\@empty
+     \ifdim \lst@widthfixed<\z@
+         \let\lst@error\lst@temp \let\lst@widthfixed\z@
+     \fi
+     \ifdim \lst@widthflexible<\z@
+         \let\lst@error\lst@temp \let\lst@widthflexible\z@
+     \fi
+     \lst@error}}
+\lst@AddToHook{FontAdjust}
+    {\lst@width=\lst@ifflexible\lst@widthflexible
+                          \else\lst@widthfixed\fi \relax}
+\lst@Key{fontadjust}{false}[t]{\lstKV@SetIf{#1}\lst@iffontadjust}
+\def\lst@FontAdjust{\lst@iffontadjust \lsthk@FontAdjust \fi}
+\lst@AddToHook{InitVars}{\lsthk@FontAdjust}
+\def\lst@OutputBox#1{\lst@alloverstyle{\box#1}}
+\def\lst@alloverstyle#1{#1}% init
+\def\lst@Kern#1{%
+    \setbox\z@\hbox{{\lst@currstyle{\kern#1}}}%
+    \global\advance\lst@currlwidth \wd\z@
+    \lst@OutputBox\z@}
+\def\lst@CalcLostSpaceAndOutput{%
+    \global\advance\lst@lostspace \lst@length\lst@width
+    \global\advance\lst@lostspace-\wd\@tempboxa
+    \global\advance\lst@currlwidth \wd\@tempboxa
+    \global\advance\lst@pos -\lst@length
+    \setbox\@tempboxa\hbox{\let\lst@OutputBox\box
+        \ifdim\lst@lostspace>\z@ \lst@leftinsert \fi
+        \box\@tempboxa
+        \ifdim\lst@lostspace>\z@ \lst@rightinsert \fi}%
+    \lst@OutputBox\@tempboxa \lsthk@PostOutput}
+\lst@AddToHook{PostOutput}{}% init
+\def\lst@OutputToken{%
+    \lst@TrackNewLines \lst@OutputLostSpace
+    \lst@ifgobbledws
+        \lst@gobbledwhitespacefalse
+        \lst@@discretionary
+    \fi
+    \lst@CheckMerge
+    {\lst@thestyle{\lst@FontAdjust
+     \setbox\@tempboxa\lst@hbox
+        {\lsthk@OutputBox
+         \lst@lefthss
+         \expandafter\lst@FillOutputBox\the\lst@token\@empty
+         \lst@righthss}%
+     \lst@CalcLostSpaceAndOutput}}%
+    \lst@ResetToken}
+\lst@AddToHook{OutputBox}{}% init
+\def\lst@gobbledwhitespacetrue{\global\let\lst@ifgobbledws\iftrue}
+\def\lst@gobbledwhitespacefalse{\global\let\lst@ifgobbledws\iffalse}
+\lst@AddToHookExe{InitBOL}{\lst@gobbledwhitespacefalse}% init
+\def\lst@Delay#1{%
+    \lst@CheckDelay
+    #1%
+    \lst@GetOutputMacro\lst@delayedoutput
+    \edef\lst@delayed{\the\lst@token}%
+    \edef\lst@delayedlength{\the\lst@length}%
+    \lst@ResetToken}
+\def\lst@Merge#1{%
+    \lst@CheckMerge
+    #1%
+    \edef\lst@merged{\the\lst@token}%
+    \edef\lst@mergedlength{\the\lst@length}%
+    \lst@ResetToken}
+\def\lst@MergeToken#1#2{%
+    \advance\lst@length#2%
+    \lst@lExtend#1{\the\lst@token}%
+    \expandafter\lst@token\expandafter{#1}%
+    \let#1\@empty}
+\def\lst@CheckDelay{%
+    \ifx\lst@delayed\@empty\else
+        \lst@GetOutputMacro\@gtempa
+        \ifx\lst@delayedoutput\@gtempa
+            \lst@MergeToken\lst@delayed\lst@delayedlength
+        \else
+            {\lst@ResetToken
+             \lst@MergeToken\lst@delayed\lst@delayedlength
+             \lst@delayedoutput}%
+            \let\lst@delayed\@empty
+        \fi
+    \fi}
+\def\lst@CheckMerge{%
+    \ifx\lst@merged\@empty\else
+        \lst@MergeToken\lst@merged\lst@mergedlength
+    \fi}
+\let\lst@delayed\@empty % init
+\let\lst@merged\@empty % init
+\def\lst@column@fixed{%
+    \lst@flexiblefalse
+    \lst@width\lst@widthfixed\relax
+    \let\lst@OutputLostSpace\lst@UseLostSpace
+    \let\lst@FillOutputBox\lst@FillFixed
+    \let\lst@hss\hss
+    \def\lst@hbox{\hbox to\lst@length\lst@width}}
+\def\lst@FillFixed#1{#1\lst@FillFixed@}
+\def\lst@FillFixed@#1{%
+    \ifx\@empty#1\else \lst@hss#1\expandafter\lst@FillFixed@ \fi}
+\def\lst@column@flexible{%
+    \lst@flexibletrue
+    \lst@width\lst@widthflexible\relax
+    \let\lst@OutputLostSpace\lst@UseLostSpace
+    \let\lst@FillOutputBox\@empty
+    \let\lst@hss\@empty
+    \let\lst@hbox\hbox}
+\def\lst@column@fullflexible{%
+    \lst@column@flexible
+    \def\lst@OutputLostSpace{\lst@ifnewline \lst@UseLostSpace\fi}%
+    \let\lst@leftinsert\@empty
+    \let\lst@rightinsert\@empty}
+\def\lst@column@spaceflexible{%
+    \lst@column@flexible
+    \def\lst@OutputLostSpace{%
+      \lst@ifwhitespace
+        \ifx\lst@outputspace\lst@visiblespace
+        \else
+          \lst@UseLostSpace
+        \fi
+      \else
+        \lst@ifnewline \lst@UseLostSpace\fi
+      \fi}%
+    \let\lst@leftinsert\@empty
+    \let\lst@rightinsert\@empty}
+\def\lst@outputpos#1#2\relax{%
+    \def\lst@lefthss{\lst@hss}\let\lst@righthss\lst@lefthss
+    \let\lst@rightinsert\lst@InsertLostSpace
+    \ifx #1c%
+        \let\lst@leftinsert\lst@InsertHalfLostSpace
+    \else\ifx #1r%
+        \let\lst@righthss\@empty
+        \let\lst@leftinsert\lst@InsertLostSpace
+        \let\lst@rightinsert\@empty
+    \else
+        \let\lst@lefthss\@empty
+        \let\lst@leftinsert\@empty
+        \ifx #1l\else \PackageWarning{Listings}%
+            {Unknown positioning for output boxes}%
+        \fi
+    \fi\fi}
+\def\lst@flexibletrue{\let\lst@ifflexible\iftrue}
+\def\lst@flexiblefalse{\let\lst@ifflexible\iffalse}
+\lst@Key{columns}{[c]fixed}{\lstKV@OptArg[]{#1}{%
+    \ifx\@empty##1\@empty\else \lst@outputpos##1\relax\relax \fi
+    \expandafter\let\expandafter\lst@arg
+                                \csname\@lst @column@##2\endcsname
+    \lst@arg
+    \ifx\lst@arg\relax
+        \PackageWarning{Listings}{Unknown column format `##2'}%
+    \else
+        \lst@ifflexible
+            \let\lst@columnsflexible\lst@arg
+        \else
+            \let\lst@columnsfixed\lst@arg
+        \fi
+    \fi}}
+\let\lst@columnsfixed\lst@column@fixed % init
+\let\lst@columnsflexible\lst@column@flexible % init
+\lst@Key{flexiblecolumns}\relax[t]{%
+    \lstKV@SetIf{#1}\lst@ifflexible
+    \lst@ifflexible \lst@columnsflexible
+              \else \lst@columnsfixed \fi}
+\newcount\lst@newlines
+\lst@AddToHook{InitVars}{\global\lst@newlines\z@}
+\lst@AddToHook{InitVarsBOL}{\global\advance\lst@newlines\@ne}
+\def\lst@NewLine{%
+    \ifx\lst@OutputBox\@gobble\else
+        \par\noindent \hbox{}%
+    \fi
+    \global\advance\lst@newlines\m@ne
+    \lst@newlinetrue}
+\def\lst@newlinetrue{\global\let\lst@ifnewline\iftrue}
+\lst@AddToHookExe{PostOutput}{\global\let\lst@ifnewline\iffalse}% init
+\def\lst@TrackNewLines{%
+    \ifnum\lst@newlines>\z@
+        \lsthk@OnNewLine
+        \lst@DoNewLines
+    \fi}
+\lst@AddToHook{OnNewLine}{}% init
+\lst@Key{emptylines}\maxdimen{%
+    \@ifstar{\lst@true\@tempcnta\@gobble#1\relax\lst@GobbleNil}%
+            {\lst@false\@tempcnta#1\relax\lst@GobbleNil}#1\@nil
+    \advance\@tempcnta\@ne
+    \edef\lst@maxempty{\the\@tempcnta\relax}%
+    \let\lst@ifpreservenumber\lst@if}
+\def\lst@DoNewLines{
+    \@whilenum\lst@newlines>\lst@maxempty \do
+        {\lst@ifpreservenumber
+            \lsthk@OnEmptyLine
+            \global\advance\c@lstnumber\lst@advancelstnum
+         \fi
+         \global\advance\lst@newlines\m@ne}%
+    \@whilenum \lst@newlines>\@ne \do
+        {\lsthk@OnEmptyLine \lst@NewLine}%
+    \ifnum\lst@newlines>\z@ \lst@NewLine \fi}
+\lst@AddToHook{OnEmptyLine}{}% init
+\lst@Key{identifierstyle}{}{\def\lst@identifierstyle{#1}}
+\lst@AddToHook{EmptyStyle}{\let\lst@identifierstyle\@empty}
+\def\lst@GotoTabStop{%
+    \ifnum\lst@newlines=\z@
+        \setbox\@tempboxa\hbox{\lst@outputspace}%
+        \setbox\@tempboxa\hbox to\wd\@tempboxa{{\lst@currstyle{\hss}}}%
+        \lst@CalcLostSpaceAndOutput
+    \else
+        \global\advance\lst@lostspace \lst@length\lst@width
+        \global\advance\lst@column\lst@length \lst@length\z@
+    \fi}
+\def\lst@OutputOther{%
+    \lst@CheckDelay
+    \ifnum\lst@length=\z@\else
+        \let\lst@thestyle\lst@currstyle
+        \lsthk@OutputOther
+        \lst@OutputToken
+    \fi}
+\lst@AddToHook{OutputOther}{}% init
+\let\lst@currstyle\relax % init
+\def\lst@Output{%
+    \lst@CheckDelay
+    \ifnum\lst@length=\z@\else
+        \ifx\lst@currstyle\relax
+            \let\lst@thestyle\lst@identifierstyle
+        \else
+            \let\lst@thestyle\lst@currstyle
+        \fi
+        \lsthk@Output
+        \lst@OutputToken
+    \fi
+    \let\lst@lastother\relax}
+\lst@AddToHook{Output}{}% init
+\def\lst@GetOutputMacro#1{%
+    \lst@ifletter \global\let#1\lst@Output
+            \else \global\let#1\lst@OutputOther\fi}
+\def\lst@PrintToken{%
+    \lst@ifletter \lst@Output \lst@letterfalse
+            \else \lst@OutputOther \let\lst@lastother\@empty \fi}
+\def\lst@XPrintToken{%
+    \lst@PrintToken \lst@CheckMerge
+    \ifnum\lst@length=\z@\else \lst@PrintToken \fi}
+\def\lst@BeginDropOutput#1{%
+    \xdef\lst@BDOnewlines{\the\lst@newlines}%
+    \global\let\lst@BDOifnewline\lst@ifnewline
+    \lst@EnterMode{#1}%
+        {\lst@modetrue
+         \let\lst@OutputBox\@gobble
+         \aftergroup\lst@BDORestore}}
+\def\lst@BDORestore{%
+    \global\lst@newlines\lst@BDOnewlines
+    \global\let\lst@ifnewline\lst@BDOifnewline}
+\let\lst@EndDropOutput\lst@LeaveMode
+\def\lst@ProcessLetter{\lst@whitespacefalse \lst@AppendLetter}
+\def\lst@ProcessOther{\lst@whitespacefalse \lst@AppendOther}
+\def\lst@ProcessDigit{%
+    \lst@whitespacefalse
+    \lst@ifletter \expandafter\lst@AppendLetter
+            \else \expandafter\lst@AppendOther\fi}
+\def\lst@whitespacetrue{\global\let\lst@ifwhitespace\iftrue}
+\def\lst@whitespacefalse{\global\let\lst@ifwhitespace\iffalse}
+\lst@AddToHook{InitVarsBOL}{\lst@whitespacetrue}
+\lst@Key{tabsize}{8}
+    {\ifnum#1>\z@ \def\lst@tabsize{#1}\else
+         \PackageError{Listings}{Strict positive integer expected}%
+         {You can't use `#1' as tabsize. \@ehc}%
+     \fi}
+\lst@Key{showtabs}f[t]{\lstKV@SetIf{#1}\lst@ifshowtabs}
+\lst@Key{tab}{\kern.06em\hbox{\vrule\@height.3ex}%
+              \hrulefill\hbox{\vrule\@height.3ex}}
+    {\def\lst@tab{#1}}
+\def\lst@ProcessTabulator{%
+    \lst@XPrintToken \lst@whitespacetrue
+    \global\advance\lst@column -\lst@pos
+    \@whilenum \lst@pos<\@ne \do
+        {\global\advance\lst@pos\lst@tabsize}%
+    \lst@length\lst@pos
+    \lst@PreGotoTabStop}
+\def\lst@PreGotoTabStop{%
+    \lst@ifshowtabs
+        \lst@TrackNewLines
+        \setbox\@tempboxa\hbox to\lst@length\lst@width
+            {{\lst@currstyle{\hss\lst@tab}}}%
+        \lst@CalcLostSpaceAndOutput
+    \else
+        \lst@ifkeepspaces
+            \@tempcnta\lst@length \lst@length\z@
+            \@whilenum \@tempcnta>\z@ \do
+                {\lst@AppendOther\lst@outputspace
+                 \advance\@tempcnta\m@ne}%
+            \lst@OutputOther
+        \else
+            \lst@GotoTabStop
+        \fi
+    \fi
+    \lst@length\z@ \global\lst@pos\z@}
+\def\lst@outputspace{\ }
+\def\lst@visiblespace{\lst@ttfamily{\char32}\textvisiblespace}
+\lst@Key{showspaces}{false}[t]{\lstKV@SetIf{#1}\lst@ifshowspaces}
+\lst@Key{keepspaces}{false}[t]{\lstKV@SetIf{#1}\lst@ifkeepspaces}
+\lst@AddToHook{Init}
+    {\lst@ifshowspaces
+         \let\lst@outputspace\lst@visiblespace
+         \lst@keepspacestrue
+     \fi}
+\def\lst@keepspacestrue{\let\lst@ifkeepspaces\iftrue}
+\def\lst@ProcessSpace{%
+    \lst@ifkeepspaces
+        \lst@PrintToken
+        \lst@whitespacetrue
+        \lst@AppendOther\lst@outputspace
+        \lst@PrintToken
+    \else \ifnum\lst@newlines=\z@
+        \lst@AppendSpecialSpace
+    \else \ifnum\lst@length=\z@
+            \global\advance\lst@lostspace\lst@width
+            \global\advance\lst@pos\m@ne
+            \lst@whitespacetrue
+        \else
+            \lst@AppendSpecialSpace
+        \fi
+    \fi \fi}
+\def\lst@AppendSpecialSpace{%
+    \lst@ifwhitespace
+        \lst@PrintToken
+        \global\advance\lst@lostspace\lst@width
+        \global\advance\lst@pos\m@ne
+        \lst@gobbledwhitespacetrue
+    \else
+        \lst@PrintToken
+        \lst@whitespacetrue
+        \lst@AppendOther\lst@outputspace
+        \lst@PrintToken
+    \fi}
+\lst@Key{formfeed}{\bigbreak}{\def\lst@formfeed{#1}}
+\def\lst@ProcessFormFeed{%
+    \lst@XPrintToken
+    \ifnum\lst@newlines=\z@
+        \lst@EOLUpdate \lsthk@InitVarsBOL
+    \fi
+    \lst@formfeed
+    \lst@whitespacetrue}
+\def\lst@Def#1{\lccode`\~=#1\lowercase{\def~}}
+\def\lst@Let#1{\lccode`\~=#1\lowercase{\let~}}
+\lst@AddToAtTop{\try@load@fontshape}{\def\space{ }}
+\def\lst@SelectStdCharTable{%
+    \lst@Def{9}{\lst@ProcessTabulator}%
+    \lst@Def{12}{\lst@ProcessFormFeed}%
+    \lst@Def{32}{\lst@ProcessSpace}}
+\def\lst@CCPut#1#2{%
+    \ifnum#2=\z@
+        \expandafter\@gobbletwo
+    \else
+        \lccode`\~=#2\lccode`\/=#2\lowercase{\lst@CCPut@~{#1/}}%
+    \fi
+    \lst@CCPut#1}
+\def\lst@CCPut@#1#2{\lst@lAddTo\lst@SelectStdCharTable{\def#1{#2}}}
+\lst@CCPut \lst@ProcessOther
+    {"21}{"22}{"28}{"29}{"2B}{"2C}{"2E}{"2F}
+    {"3A}{"3B}{"3D}{"3F}{"5B}{"5D}
+    \z@
+\lst@CCPut \lst@ProcessDigit
+    {"30}{"31}{"32}{"33}{"34}{"35}{"36}{"37}{"38}{"39}
+    \z@
+\lst@CCPut \lst@ProcessLetter
+    {"40}{"41}{"42}{"43}{"44}{"45}{"46}{"47}
+    {"48}{"49}{"4A}{"4B}{"4C}{"4D}{"4E}{"4F}
+    {"50}{"51}{"52}{"53}{"54}{"55}{"56}{"57}
+    {"58}{"59}{"5A}
+         {"61}{"62}{"63}{"64}{"65}{"66}{"67}
+    {"68}{"69}{"6A}{"6B}{"6C}{"6D}{"6E}{"6F}
+    {"70}{"71}{"72}{"73}{"74}{"75}{"76}{"77}
+    {"78}{"79}{"7A}
+    \z@
+\def\lst@CCPutMacro#1#2#3{%
+    \ifnum#2=\z@ \else
+        \begingroup\lccode`\~=#2\relax \lccode`\/=#2\relax
+        \lowercase{\endgroup\expandafter\lst@CCPutMacro@
+            \csname\@lst @um/\expandafter\endcsname
+            \csname\@lst @um/@\endcsname /~}#1{#3}%
+        \expandafter\lst@CCPutMacro
+    \fi}
+\def\lst@CCPutMacro@#1#2#3#4#5#6{%
+    \lst@lAddTo\lst@SelectStdCharTable{\def#4{#5#1}}%
+    \def#1{\lst@UM#3}%
+    \def#2{#6}}
+\def\lst@UM#1{\csname\@lst @um#1@\endcsname}
+\lst@CCPutMacro
+    \lst@ProcessOther {"23}\#
+    \lst@ProcessLetter{"24}\textdollar
+    \lst@ProcessOther {"25}\%
+    \lst@ProcessOther {"26}\&
+    \lst@ProcessOther {"27}{\lst@ifupquote \textquotesingle
+                                     \else \char39\relax \fi}
+    \lst@ProcessOther {"2A}{\lst@ttfamily*\textasteriskcentered}
+    \lst@ProcessOther {"2D}{\lst@ttfamily{-{}}{$-$}}
+    \lst@ProcessOther {"3C}{\lst@ttfamily<\textless}
+    \lst@ProcessOther {"3E}{\lst@ttfamily>\textgreater}
+    \lst@ProcessOther {"5C}{\lst@ttfamily{\char92}\textbackslash}
+    \lst@ProcessOther {"5E}\textasciicircum
+    \lst@ProcessLetter{"5F}{\lst@ttfamily{\char95}\textunderscore}
+    \lst@ProcessOther {"60}{\lst@ifupquote \textasciigrave
+                                     \else \char96\relax \fi}
+    \lst@ProcessOther {"7B}{\lst@ttfamily{\char123}\textbraceleft}
+    \lst@ProcessOther {"7C}{\lst@ttfamily|\textbar}
+    \lst@ProcessOther {"7D}{\lst@ttfamily{\char125}\textbraceright}
+    \lst@ProcessOther {"7E}\textasciitilde
+    \lst@ProcessOther {"7F}-
+    \@empty\z@\@empty
+\def\lst@ttfamily#1#2{\ifx\f@family\ttdefault#1\relax\else#2\fi}
+\lst@AddToHook{Init}{\edef\ttdefault{\ttdefault}}
+\lst@Key{upquote}{false}[t]{\lstKV@SetIf{#1}\lst@ifupquote
+    \lst@ifupquote
+       \@ifundefined{textasciigrave}%
+          {\let\KV@lst@upquote\@gobble
+           \lstKV@SetIf f\lst@ifupquote \@gobble\fi
+           \PackageError{Listings}{Option `upquote' requires `textcomp'
+            package.\MessageBreak The option has been disabled}%
+          {Add \string\usepackage{textcomp} to your preamble.}}%
+          {}%
+    \fi}
+\AtBeginDocument{%
+  \@ifpackageloaded{upquote}{\RequirePackage{textcomp}%
+                             \lstset{upquote}}{}%
+  \@ifpackageloaded{upquote2}{\lstset{upquote}}{}}
+\def\lst@activecharstrue{\let\lst@ifactivechars\iftrue}
+\def\lst@activecharsfalse{\let\lst@ifactivechars\iffalse}
+\lst@activecharstrue
+\def\lst@SelectCharTable{%
+    \lst@SelectStdCharTable
+    \lst@ifactivechars
+        \catcode9\active \catcode12\active \catcode13\active
+        \@tempcnta=32\relax
+        \@whilenum\@tempcnta<128\do
+            {\catcode\@tempcnta\active\advance\@tempcnta\@ne}%
+    \fi
+    \lst@ifec \lst@DefEC \fi
+    \let\do\lst@do@noligs \verbatim@nolig@list
+    \lsthk@SelectCharTable
+    \lst@DeveloperSCT
+\lst@DefRange
+    \ifx\lst@Backslash\relax\else
+        \lst@LetSaveDef{"5C}\lsts@backslash\lst@Backslash
+    \fi}
+\lst@Key{SelectCharTable}{}{\def\lst@DeveloperSCT{#1}}
+\lst@Key{MoreSelectCharTable}\relax{\lst@lAddTo\lst@DeveloperSCT{#1}}
+\lst@AddToHook{SetLanguage}{\let\lst@DeveloperSCT\@empty}
+\def\lst@do@noligs#1{%
+    \begingroup \lccode`\~=`#1\lowercase{\endgroup
+    \lst@do@noligs@~}}
+\def\lst@do@noligs@#1{%
+    \expandafter\expandafter\expandafter\def
+    \expandafter\expandafter\expandafter#1%
+    \expandafter\expandafter\expandafter{\expandafter\lst@NoLig#1}}
+\def\lst@NoLig{\advance\lst@length\m@ne \lst@Append\lst@nolig}
+\def\lst@nolig{\lst@UM\@empty}%
+\@namedef{\@lst @um@}{\leavevmode\kern\z@}
+\def\lst@SaveOutputDef#1#2{%
+    \begingroup \lccode`\~=#1\relax \lowercase{\endgroup
+    \def\lst@temp##1\def~##2##3\relax}{%
+        \global\expandafter\let\expandafter#2\@gobble##2\relax}%
+    \expandafter\lst@temp\lst@SelectStdCharTable\relax}
+\lst@SaveOutputDef{"5C}\lstum@backslash
+\lst@Key{extendedchars}{true}[t]{\lstKV@SetIf{#1}\lst@ifec}
+\def\lst@DefEC{%
+    \lst@CCECUse \lst@ProcessLetter
+      ^^80^^81^^82^^83^^84^^85^^86^^87^^88^^89^^8a^^8b^^8c^^8d^^8e^^8f%
+      ^^90^^91^^92^^93^^94^^95^^96^^97^^98^^99^^9a^^9b^^9c^^9d^^9e^^9f%
+      ^^a0^^a1^^a2^^a3^^a4^^a5^^a6^^a7^^a8^^a9^^aa^^ab^^ac^^ad^^ae^^af%
+      ^^b0^^b1^^b2^^b3^^b4^^b5^^b6^^b7^^b8^^b9^^ba^^bb^^bc^^bd^^be^^bf%
+      ^^c0^^c1^^c2^^c3^^c4^^c5^^c6^^c7^^c8^^c9^^ca^^cb^^cc^^cd^^ce^^cf%
+      ^^d0^^d1^^d2^^d3^^d4^^d5^^d6^^d7^^d8^^d9^^da^^db^^dc^^dd^^de^^df%
+      ^^e0^^e1^^e2^^e3^^e4^^e5^^e6^^e7^^e8^^e9^^ea^^eb^^ec^^ed^^ee^^ef%
+      ^^f0^^f1^^f2^^f3^^f4^^f5^^f6^^f7^^f8^^f9^^fa^^fb^^fc^^fd^^fe^^ff%
+      ^^00}
+\def\lst@CCECUse#1#2{%
+    \ifnum`#2=\z@
+        \expandafter\@gobbletwo
+    \else
+        \ifnum\catcode`#2=\active
+            \lccode`\~=`#2\lccode`\/=`#2\lowercase{\lst@CCECUse@#1~/}%
+        \else
+            \lst@ifactivechars \catcode`#2=\active \fi
+            \lccode`\~=`#2\lccode`\/=`#2\lowercase{\def~{#1/}}%
+        \fi
+    \fi
+    \lst@CCECUse#1}
+\def\lst@CCECUse@#1#2#3{%
+    \expandafter\def\csname\@lst @EC#3\endcsname{\lst@UM#3}%
+    \expandafter\let\csname\@lst @um#3@\endcsname #2%
+    \edef#2{\noexpand#1%
+            \expandafter\noexpand\csname\@lst @EC#3\endcsname}}
+\lst@AddToHook{Init}
+    {\let\lsts@nfss@catcodes\nfss@catcodes
+     \let\nfss@catcodes\lst@nfss@catcodes}
+\def\lst@nfss@catcodes{%
+    \lst@makeletter
+        ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\relax
+    \@makeother (\@makeother )\@makeother ,\@makeother :\@makeother\&%
+    \@makeother 0\@makeother 1\@makeother 2\@makeother 3\@makeother 4%
+    \@makeother 5\@makeother 6\@makeother 7\@makeother 8\@makeother 9%
+    \@makeother =\lsts@nfss@catcodes}
+\def\lst@makeletter#1{%
+    \ifx\relax#1\else\catcode`#111\relax \expandafter\lst@makeletter\fi}
+\lst@Key{useoutput}{2}{\edef\lst@useoutput{\ifcase0#1 0\or 1\else 2\fi}}
+\lst@AddToHook{Init}
+{\edef\lst@OrgOutput{\the\output}%
+\ifcase\lst@useoutput\relax
+\or
+ \output{\global\setbox\lst@gtempboxa\box\@cclv
+         \expandafter\egroup
+         \lst@SaveToken
+     \lst@InterruptModes
+     \setbox\@cclv\box\lst@gtempboxa
+     \bgroup\lst@OrgOutput\egroup
+     \bgroup
+     \aftergroup\pagegoal\aftergroup\vsize
+     \aftergroup\lst@ReenterModes\aftergroup\lst@RestoreToken}%
+\else
+ \output{\lst@RestoreOrigCatcodes
+         \lst@ifec \lst@RestoreOrigExtendedCatcodes \fi
+         \lst@OrgOutput}%
+\fi}
+\def\lst@GetChars#1#2#3{%
+    \let#1\@empty
+    \@tempcnta#2\relax \@tempcntb#3\relax
+    \loop \ifnum\@tempcnta<\@tempcntb\relax
+        \lst@lExtend#1{\expandafter\catcode\the\@tempcnta=}%
+        \lst@lExtend#1{\the\catcode\@tempcnta\relax}%
+        \ifnum\the\catcode\@tempcnta=\active
+            \begingroup\lccode`\~=\@tempcnta
+            \lowercase{\endgroup
+            \lst@lExtend#1{\expandafter\let\expandafter~\csname
+                                    lstecs@\the\@tempcnta\endcsname}%
+            \expandafter\let\csname lstecs@\the\@tempcnta\endcsname~}%
+        \fi
+        \advance\@tempcnta\@ne
+    \repeat}
+\begingroup \catcode12=\active\let^^L\@empty
+\gdef\lst@ScanChars{%
+  \let\lsts@ssL^^L%
+  \def^^L{\par}%
+    \lst@GetChars\lst@RestoreOrigCatcodes\@ne {128}%
+  \let^^L\lsts@ssL
+    \lst@GetChars\lst@RestoreOrigExtendedCatcodes{128}{256}}
+\endgroup
+\lst@Key{rescanchars}\relax{\lst@ScanChars}
+\AtBeginDocument{\lst@ScanChars}
+\lst@Key{alsoletter}\relax{%
+    \lst@DoAlso{#1}\lst@alsoletter\lst@ProcessLetter}
+\lst@Key{alsodigit}\relax{%
+    \lst@DoAlso{#1}\lst@alsodigit\lst@ProcessDigit}
+\lst@Key{alsoother}\relax{%
+    \lst@DoAlso{#1}\lst@alsoother\lst@ProcessOther}
+\lst@AddToHook{SelectCharTable}
+    {\lst@alsoother \lst@alsodigit \lst@alsoletter}
+\lst@AddToHookExe{SetLanguage}% init
+    {\let\lst@alsoletter\@empty
+     \let\lst@alsodigit\@empty
+     \let\lst@alsoother\@empty}
+\def\lst@DoAlso#1#2#3{%
+    \lst@DefOther\lst@arg{#1}\let#2\@empty
+    \expandafter\lst@DoAlso@\expandafter#2\expandafter#3\lst@arg\relax}
+\def\lst@DoAlso@#1#2#3{%
+    \ifx\relax#3\expandafter\@gobblethree \else
+        \begingroup \lccode`\~=`#3\relax \lowercase{\endgroup
+        \def\lst@temp##1\def~##2##3\relax{%
+            \edef\lst@arg{\def\noexpand~{\noexpand#2\expandafter
+                                         \noexpand\@gobble##2}}}}%
+        \expandafter\lst@temp\lst@SelectStdCharTable\relax
+        \lst@lExtend#1{\lst@arg}%
+    \fi
+    \lst@DoAlso@#1#2}
+\def\lst@SaveDef#1#2{%
+    \begingroup \lccode`\~=#1\relax \lowercase{\endgroup\let#2~}}
+\def\lst@DefSaveDef#1#2{%
+    \begingroup \lccode`\~=#1\relax \lowercase{\endgroup\let#2~\def~}}
+\def\lst@LetSaveDef#1#2{%
+    \begingroup \lccode`\~=#1\relax \lowercase{\endgroup\let#2~\let~}}
+\def\lst@CDef#1{\lst@CDef@#1}
+\def\lst@CDef@#1#2#3#4{\lst@CDefIt#1{#2}{#3}{#4#2#3}#4}
+\def\lst@CDefX#1{\lst@CDefX@#1}
+\def\lst@CDefX@#1#2#3{\lst@CDefIt#1{#2}{#3}{}}
+\def\lst@CDefIt#1#2#3#4#5#6#7#8{%
+    \ifx\@empty#2\@empty
+        \def#1{#6\def\lst@next{#7#4#8}\lst@next}%
+    \else \ifx\@empty#3\@empty
+        \def#1##1{%
+            #6%
+            \ifx##1#2\def\lst@next{#7#4#8}\else
+                     \def\lst@next{#5##1}\fi
+            \lst@next}%
+    \else
+        \def#1{%
+            #6%
+            \lst@IfNextCharsArg{#2#3}{#7#4#8}%
+                                     {\expandafter#5\lst@eaten}}%
+    \fi \fi}
+\def\lst@CArgX#1#2\relax{%
+    \lst@DefActive\lst@arg{#1#2}%
+    \expandafter\lst@CArg\lst@arg\relax}
+\def\lst@CArg#1#2\relax{%
+    \lccode`\/=`#1\lowercase{\def\lst@temp{/}}%
+    \lst@GetFreeMacro{lst@c\lst@temp}%
+    \expandafter\lst@CArg@\lst@freemacro#1#2\@empty\@empty\relax}
+\def\lst@CArg@#1#2#3#4\@empty#5\relax#6{%
+    \let#1#2%
+    \ifx\@empty#3\@empty
+        \def\lst@next{#6{#2{}{}}}%
+    \else
+        \def\lst@next{#6{#2#3{#4}}}%
+    \fi
+    \lst@next #1}
+\def\lst@CArgEmpty#1\@empty{#1}
+\lst@Key{excludedelims}\relax
+    {\lsthk@ExcludeDelims \lst@NormedDef\lst@temp{#1}%
+     \expandafter\lst@for\lst@temp\do
+     {\expandafter\let\csname\@lst @ifex##1\endcsname\iftrue}}
+\def\lst@DelimPrint#1#2{%
+    #1%
+      \begingroup
+        \lst@mode\lst@nomode \lst@modetrue
+        #2\lst@XPrintToken
+      \endgroup
+      \lst@ResetToken
+    \fi}
+\def\lst@DelimOpen#1#2#3#4#5#6\@empty{%
+    \lst@TrackNewLines \lst@XPrintToken
+    \lst@DelimPrint#1{#6}%
+    \lst@EnterMode{#4}{\def\lst@currstyle#5}%
+    \lst@DelimPrint{#1#2}{#6}%
+    #3}
+\def\lst@DelimClose#1#2#3\@empty{%
+    \lst@TrackNewLines \lst@XPrintToken
+    \lst@DelimPrint{#1#2}{#3}%
+    \lst@LeaveMode
+    \lst@DelimPrint{#1}{#3}}
+\def\lst@BeginDelim{\lst@DelimOpen\iffalse\else{}}
+\def\lst@EndDelim{\lst@DelimClose\iffalse\else}
+\def\lst@BeginIDelim{\lst@DelimOpen\iffalse{}{}}
+\def\lst@EndIDelim{\lst@DelimClose\iffalse{}}
+\lst@AddToHook{SelectCharTable}{\lst@DefDelims}
+\lst@AddToHookExe{SetLanguage}{\let\lst@DefDelims\@empty}
+\def\lst@Delim#1{%
+    \lst@false \let\lst@cumulative\@empty \let\lst@arg\@empty
+    \@ifstar{\@ifstar{\lst@Delim@{#1}}%
+                     {\let\lst@cumulative\relax
+                      \lst@Delim@{#1}}}%
+            {\lst@true\lst@Delim@{#1}}}
+\def\lst@Delim@#1[#2]{%
+    \gdef\lst@delimtype{#2}%
+    \@ifnextchar[\lst@Delim@sty
+                 {\lst@Delim@sty[#1]}}
+\def\lst@Delim@sty[#1]{%
+    \def\lst@delimstyle{#1}%
+    \ifx\@empty#1\@empty\else
+        \lst@Delim@sty@ #1\@nil
+    \fi
+    \@ifnextchar[\lst@Delim@option
+                 \lst@Delim@delim}
+\def\lst@Delim@option[#1]{\def\lst@arg{[#1]}\lst@Delim@delim}
+\def\lst@Delim@sty@#1#2\@nil{%
+    \if\relax\noexpand#1\else
+        \edef\lst@delimstyle{\expandafter\noexpand
+                             \csname\@lst @\lst@delimstyle\endcsname}%
+    \fi}
+\def\lst@Delim@delim#1\relax#2#3#4#5#6#7#8{%
+    \ifx #4\@empty \lst@Delim@delall{#2}\fi
+    \ifx\@empty#1\@empty
+        \ifx #4\@nil
+            \@ifundefined{\@lst @#2DM@\lst@delimtype}%
+                {\lst@Delim@delall{#2@\lst@delimtype}}%
+                {\lst@Delim@delall{#2DM@\lst@delimtype}}%
+        \fi
+    \else
+        \expandafter\lst@Delim@args\expandafter
+            {\lst@delimtype}{#1}{#5}#6{#7}{#8}#4%
+        \let\lst@delim\@empty
+        \expandafter\lst@IfOneOf\lst@delimtype\relax#3%
+        {\@ifundefined{\@lst @#2DM@\lst@delimtype}%
+             {\lst@lExtend\lst@delim{\csname\@lst @#2@\lst@delimtype
+                                     \expandafter\endcsname\lst@arg}}%
+             {\lst@lExtend\lst@delim{\expandafter\lst@UseDynamicMode
+                                     \csname\@lst @#2DM@\lst@delimtype
+                                     \expandafter\endcsname\lst@arg}}%
+         \ifx #4\@nil
+             \let\lst@temp\lst@DefDelims \let\lst@DefDelims\@empty
+             \expandafter\lst@Delim@del\lst@temp\@empty\@nil\@nil\@nil
+         \else
+             \lst@lExtend\lst@DefDelims\lst@delim
+         \fi}%
+        {\PackageError{Listings}{Illegal type `\lst@delimtype'}%
+                                {#2 types are #3.}}%
+     \fi}
+\def\lst@Delim@args#1#2#3#4#5#6#7{%
+    \begingroup
+    \lst@false \let\lst@next\lst@XConvert
+    \@ifnextchar #4{\xdef\lst@delimtype{\expandafter\@gobble
+                                        \lst@delimtype}%
+                    #5\lst@next#2\@nil
+                    \lst@lAddTo\lst@arg{\@empty#6}%
+                    \lst@GobbleNil}%
+                   {\lst@next#2\@nil
+                    \lst@lAddTo\lst@arg{\@empty#3}%
+                    \lst@GobbleNil}%
+                 #1\@nil
+    \global\let\@gtempa\lst@arg
+    \endgroup
+    \let\lst@arg\@gtempa
+    \ifx #7\@nil\else
+        \expandafter\lst@Delim@args@\expandafter{\lst@delimstyle}%
+    \fi}
+\def\lst@Delim@args@#1{%
+    \lst@if
+        \lst@lAddTo\lst@arg{{{#1}\lst@modetrue}}%
+    \else
+        \ifx\lst@cumulative\@empty
+            \lst@lAddTo\lst@arg{{{}#1}}%
+        \else
+            \lst@lAddTo\lst@arg{{{#1}}}%
+        \fi
+    \fi}
+\def\lst@Delim@del#1\@empty#2#3#4{%
+    \ifx #2\@nil\else
+        \def\lst@temp{#1\@empty#2#3}%
+        \ifx\lst@temp\lst@delim\else
+            \lst@lAddTo\lst@DefDelims{#1\@empty#2#3{#4}}%
+        \fi
+        \expandafter\lst@Delim@del
+    \fi}
+\def\lst@Delim@delall#1{%
+    \begingroup
+    \edef\lst@delim{\expandafter\string\csname\@lst @#1\endcsname}%
+    \lst@false \global\let\@gtempa\@empty
+    \expandafter\lst@Delim@delall@\lst@DefDelims\@empty
+    \endgroup
+    \let\lst@DefDelims\@gtempa}
+\def\lst@Delim@delall@#1{%
+    \ifx #1\@empty\else
+        \ifx #1\lst@UseDynamicMode
+            \lst@true
+            \let\lst@next\lst@Delim@delall@do
+        \else
+            \def\lst@next{\lst@Delim@delall@do#1}%
+        \fi
+        \expandafter\lst@next
+    \fi}
+\def\lst@Delim@delall@do#1#2\@empty#3#4#5{%
+    \expandafter\lst@IfSubstring\expandafter{\lst@delim}{\string#1}%
+      {}%
+      {\lst@if \lst@AddTo\@gtempa\lst@UseDynamicMode \fi
+       \lst@AddTo\@gtempa{#1#2\@empty#3#4{#5}}}%
+    \lst@false \lst@Delim@delall@}
+\gdef\lst@DefDelimB#1#2#3#4#5#6#7#8{%
+    \lst@CDef{#1}#2%
+        {#3}%
+        {\let\lst@bnext\lst@CArgEmpty
+         \lst@ifmode #4\else
+             #5%
+             \def\lst@bnext{#6{#7}{#8}}%
+         \fi
+         \lst@bnext}%
+        \@empty}
+\gdef\lst@DefDelimE#1#2#3#4#5#6#7{%
+    \lst@CDef{#1}#2%
+        {#3}%
+        {\let\lst@enext\lst@CArgEmpty
+         \ifnum #7=\lst@mode%
+             #4%
+             \let\lst@enext#6%
+         \else
+             #5%
+         \fi
+         \lst@enext}%
+        \@empty}
+\lst@AddToHook{Init}{\let\lst@bnext\relax \let\lst@enext\relax}
+\gdef\lst@DefDelimBE#1#2#3#4#5#6#7#8#9{%
+    \lst@CDef{#1}#2%
+        {#3}%
+        {\let\lst@bnext\lst@CArgEmpty
+         \ifnum #7=\lst@mode
+             #4%
+             \let\lst@bnext#9%
+         \else
+             \lst@ifmode\else
+                 #5%
+                 \def\lst@bnext{#6{#7}{#8}}%
+             \fi
+         \fi
+         \lst@bnext}%
+        \@empty}
+\gdef\lst@delimtypes{s,l}
+\gdef\lst@DelimKey#1#2{%
+    \lst@Delim{}#2\relax
+        {Delim}\lst@delimtypes #1%
+                {\lst@BeginDelim\lst@EndDelim}
+        i\@empty{\lst@BeginIDelim\lst@EndIDelim}}
+\lst@Key{delim}\relax{\lst@DelimKey\@empty{#1}}
+\lst@Key{moredelim}\relax{\lst@DelimKey\relax{#1}}
+\lst@Key{deletedelim}\relax{\lst@DelimKey\@nil{#1}}
+\gdef\lst@DelimDM@l#1#2\@empty#3#4#5{%
+    \lst@CArg #2\relax\lst@DefDelimB{}{}{}#3{#1}{#5\lst@Lmodetrue}}
+\gdef\lst@DelimDM@s#1#2#3\@empty#4#5#6{%
+    \lst@CArg #2\relax\lst@DefDelimB{}{}{}#4{#1}{#6}%
+    \lst@CArg #3\relax\lst@DefDelimE{}{}{}#5{#1}}
+\def\lst@ReplaceInput#1{\lst@CArgX #1\relax\lst@CDefX{}{}}
+\def\lst@Literatekey#1\@nil@{\let\lst@ifxliterate\lst@if
+                             \def\lst@literate{#1}}
+\lst@Key{literate}{}{\@ifstar{\lst@true \lst@Literatekey}
+                             {\lst@false\lst@Literatekey}#1\@nil@}
+\lst@AddToHook{SelectCharTable}
+    {\ifx\lst@literate\@empty\else
+         \expandafter\lst@Literate\lst@literate{}\relax\z@
+     \fi}
+\def\lst@Literate#1#2#3{%
+    \ifx\relax#2\@empty\else
+        \lst@CArgX #1\relax\lst@CDef
+            {}
+            {\let\lst@next\@empty
+             \lst@ifxliterate
+                \lst@ifmode \let\lst@next\lst@CArgEmpty \fi
+             \fi
+             \ifx\lst@next\@empty
+                 \ifx\lst@OutputBox\@gobble\else
+                   \lst@XPrintToken \let\lst@scanmode\lst@scan@m
+                   \lst@token{#2}\lst@length#3\relax
+                   \lst@XPrintToken
+                 \fi
+                 \let\lst@next\lst@CArgEmptyGobble
+             \fi
+             \lst@next}%
+            \@empty
+        \expandafter\lst@Literate
+    \fi}
+\def\lst@CArgEmptyGobble#1\@empty{}
+\def\lst@BeginDropInput#1{%
+    \lst@EnterMode{#1}%
+    {\lst@modetrue
+     \let\lst@OutputBox\@gobble
+     \let\lst@ifdropinput\iftrue
+     \let\lst@ProcessLetter\@gobble
+     \let\lst@ProcessDigit\@gobble
+     \let\lst@ProcessOther\@gobble
+     \let\lst@ProcessSpace\@empty
+     \let\lst@ProcessTabulator\@empty
+     \let\lst@ProcessFormFeed\@empty}}
+\let\lst@ifdropinput\iffalse % init
+\lst@Key{basicstyle}\relax{\def\lst@basicstyle{#1}}
+\lst@Key{inputencoding}\relax{\def\lst@inputenc{#1}}
+\lst@AddToHook{Init}
+    {\lst@basicstyle
+     \ifx\lst@inputenc\@empty\else
+         \@ifundefined{inputencoding}{}%
+            {\inputencoding\lst@inputenc}%
+     \fi}
+\lst@AddToHookExe{EmptyStyle}
+    {\let\lst@basicstyle\@empty
+     \let\lst@inputenc\@empty}
+\lst@Key{multicols}{}{\@tempcnta=0#1\relax\def\lst@multicols{#1}}
+\def\lst@parshape{\parshape\@ne \z@ \linewidth}
+\lst@AddToHookAtTop{EveryLine}{\lst@parshape}
+\lst@AddToHookAtTop{EndGroup}{\lst@parshape}
+\newcount\lst@lineno % \global
+\lst@AddToHook{InitVars}{\global\lst@lineno\@ne}
+\lst@Key{print}{true}[t]{\lstKV@SetIf{#1}\lst@ifprint}
+\lst@Key{firstline}\relax{\def\lst@firstline{#1\relax}}
+\lst@Key{lastline}\relax{\def\lst@lastline{#1\relax}}
+\lst@AddToHook{PreSet}
+    {\let\lst@firstline\@ne \def\lst@lastline{9999999\relax}}
+\lst@Key{linerange}\relax{\lstKV@OptArg[]{#1}{%
+    \def\lst@interrange{##1}\def\lst@linerange{##2,}}}
+\lst@Key{rangeprefix}\relax{\def\lst@rangebeginprefix{#1}%
+                            \def\lst@rangeendprefix{#1}}
+\lst@Key{rangesuffix}\relax{\def\lst@rangebeginsuffix{#1}%
+                            \def\lst@rangeendsuffix{#1}}
+\lst@Key{rangebeginprefix}{}{\def\lst@rangebeginprefix{#1}}
+\lst@Key{rangebeginsuffix}{}{\def\lst@rangebeginsuffix{#1}}
+\lst@Key{rangeendprefix}{}{\def\lst@rangeendprefix{#1}}
+\lst@Key{rangeendsuffix}{}{\def\lst@rangeendsuffix{#1}}
+\lst@Key{includerangemarker}{true}[t]{\lstKV@SetIf{#1}\lst@ifincluderangemarker}
+\lst@AddToHook{PreSet}{\def\lst@firstline{1\relax}%
+                       \let\lst@linerange\@empty}
+\lst@AddToHook{Init}
+{\ifx\lst@linerange\@empty
+     \edef\lst@linerange{{\lst@firstline}-{\lst@lastline},}%
+ \fi
+ \lst@GetLineInterval}%
+\def\lst@GetLineInterval{\expandafter\lst@GLI\lst@linerange\@nil}
+\def\lst@GLI#1,#2\@nil{\def\lst@linerange{#2}\lst@GLI@#1--\@nil}
+\def\lst@GLI@#1-#2-#3\@nil{%
+    \lst@IfNumber{#1}%
+    {\ifx\@empty#1\@empty
+         \let\lst@firstline\@ne
+     \else
+         \def\lst@firstline{#1\relax}%
+     \fi
+     \ifx\@empty#3\@empty
+         \def\lst@lastline{9999999\relax}%
+     \else
+         \ifx\@empty#2\@empty
+             \let\lst@lastline\lst@firstline
+         \else
+             \def\lst@lastline{#2\relax}%
+         \fi
+     \fi}%
+    {\def\lst@firstline{9999999\relax}%
+     \let\lst@lastline\lst@firstline
+     \let\lst@rangebegin\lst@rangebeginprefix
+     \lst@AddTo\lst@rangebegin{#1}\lst@Extend\lst@rangebegin\lst@rangebeginsuffix
+     \ifx\@empty#3\@empty
+         \let\lst@rangeend\lst@rangeendprefix
+         \lst@AddTo\lst@rangeend{#1}\lst@Extend\lst@rangeend\lst@rangeendsuffix
+     \else
+         \ifx\@empty#2\@empty
+             \let\lst@rangeend\@empty
+         \else
+             \let\lst@rangeend\lst@rangeendprefix
+             \lst@AddTo\lst@rangeend{#2}\lst@Extend\lst@rangeend\lst@rangeendsuffix
+         \fi
+     \fi
+     \global\def\lst@DefRange{\expandafter\lst@CArgX\lst@rangebegin\relax\lst@DefRangeB}%
+     \ifnum\lst@mode=\lst@Pmode \expandafter\lst@DefRange \fi}}
+\lst@AddToHookExe{DeInit}{\global\let\lst@DefRange\@empty}
+\def\lst@DefRangeB#1#2{\lst@DefRangeB@#1#2}
+\def\lst@DefRangeB@#1#2#3#4{%
+    \lst@CDef{#1{#2}{#3}}#4{}%
+    {\lst@ifincluderangemarker
+         \lst@LeaveMode
+         \let#1#4%
+         \lst@DefRangeEnd
+         \lst@InitLstNumber
+     \else
+         \@tempcnta\lst@lineno \advance\@tempcnta\@ne
+         \edef\lst@firstline{\the\@tempcnta\relax}%
+         \gdef\lst@OnceAtEOL{\let#1#4\lst@DefRangeEnd}%
+         \lst@InitLstNumber
+     \fi
+ \global\let\lst@DefRange\lst@DefRangeEnd
+     \lst@CArgEmpty}%
+    \@empty}
+\def\lstpatch@labels{%
+\gdef\lst@SetFirstNumber{%
+    \ifx\lst@firstnumber\@undefined
+        \@tempcnta 0\csname\@lst no@\lst@intname\endcsname\relax
+        \ifnum\@tempcnta=\z@ \else
+            \lst@nololtrue
+            \advance\@tempcnta\lst@advancenumber
+            \edef\lst@firstnumber{\the\@tempcnta\relax}%
+        \fi
+    \fi}%
+}
+\def\lst@InitLstNumber{%
+     \global\c@lstnumber\lst@firstnumber
+     \global\advance\c@lstnumber\lst@advancenumber
+     \global\advance\c@lstnumber-\lst@advancelstnum
+     \ifx \lst@firstnumber\c@lstnumber
+         \global\advance\c@lstnumber-\lst@advancelstnum
+     \fi%
+     \lst@ifincluderangemarker\else%
+         \global\advance\c@lstnumber by 1%
+     \fi%
+     }
+\def\lst@DefRangeEnd{%
+    \ifx\lst@rangeend\@empty\else
+        \expandafter\lst@CArgX\lst@rangeend\relax\lst@DefRangeE
+    \fi}
+\def\lst@DefRangeE#1#2{\lst@DefRangeE@#1#2}
+\def\lst@DefRangeE@#1#2#3#4{%
+    \lst@CDef{#1#2{#3}}#4{}%
+    {\let#1#4%
+     \edef\lst@lastline{\the\lst@lineno\relax}%
+     \lst@DefRangeE@@}%
+    \@empty}
+\def\lst@DefRangeE@@#1\@empty{%
+    \lst@ifincluderangemarker
+        #1\lst@XPrintToken
+    \fi
+    \lst@LeaveModeToPmode
+    \lst@BeginDropInput{\lst@Pmode}}
+\def\lst@LeaveModeToPmode{%
+    \ifnum\lst@mode=\lst@Pmode
+        \expandafter\lsthk@EndGroup
+    \else
+        \expandafter\egroup\expandafter\lst@LeaveModeToPmode
+    \fi}
+\lst@AddToHook{EOL}{\lst@OnceAtEOL\global\let\lst@OnceAtEOL\@empty}
+\gdef\lst@OnceAtEOL{}% Init
+\def\lst@MSkipToFirst{%
+    \global\advance\lst@lineno\@ne
+    \ifnum \lst@lineno=\lst@firstline
+        \def\lst@next{\lst@LeaveMode \global\lst@newlines\z@
+        \lst@OnceAtEOL \global\let\lst@OnceAtEOL\@empty
+        \lst@InitLstNumber % Added to work with modified \lsthk@PreInit.
+        \lsthk@InitVarsBOL
+        \lst@BOLGobble}%
+        \expandafter\lst@next
+    \fi}
+\def\lst@SkipToFirst{%
+    \ifnum \lst@lineno<\lst@firstline
+        \def\lst@next{\lst@BeginDropInput\lst@Pmode
+        \lst@Let{13}\lst@MSkipToFirst
+        \lst@Let{10}\lst@MSkipToFirst}%
+        \expandafter\lst@next
+    \else
+        \expandafter\lst@BOLGobble
+    \fi}
+\def\lst@IfNumber#1{%
+    \ifx\@empty#1\@empty
+        \let\lst@next\@firstoftwo
+    \else
+        \lst@IfNumber@#1\@nil
+    \fi
+    \lst@next}
+\def\lst@IfNumber@#1#2\@nil{%
+    \let\lst@next\@secondoftwo
+    \ifnum`#1>47\relax \ifnum`#1>57\relax\else
+        \let\lst@next\@firstoftwo
+    \fi\fi}
+\lst@Key{nolol}{false}[t]{\lstKV@SetIf{#1}\lst@ifnolol}
+\def\lst@nololtrue{\let\lst@ifnolol\iftrue}
+\let\lst@ifnolol\iffalse % init
+\lst@Key{captionpos}{t}{\def\lst@captionpos{#1}}
+\lst@Key{abovecaptionskip}\smallskipamount{\def\lst@abovecaption{#1}}
+\lst@Key{belowcaptionskip}\smallskipamount{\def\lst@belowcaption{#1}}
+\lst@Key{label}\relax{\def\lst@label{#1}}
+\lst@Key{title}\relax{\def\lst@title{#1}\let\lst@caption\relax}
+\lst@Key{caption}\relax{\lstKV@OptArg[{#1}]{#1}%
+    {\def\lst@caption{##2}\def\lst@@caption{##1}}%
+     \let\lst@title\@empty}
+\lst@AddToHookExe{TextStyle}
+    {\let\lst@caption\@empty \let\lst@@caption\@empty
+     \let\lst@title\@empty \let\lst@label\@empty}
+\AtBeginDocument{
+  \@ifundefined{thechapter}{\let\lst@ifnumberbychapter\iffalse}{}
+  \lst@ifnumberbychapter
+      \newcounter{lstlisting}[chapter]
+      \gdef\thelstlisting%
+           {\ifnum \c@chapter>\z@ \thechapter.\fi \@arabic\c@lstlisting}
+  \else
+      \newcounter{lstlisting}
+      \gdef\thelstlisting{\@arabic\c@lstlisting}
+  \fi}
+\lst@UserCommand\lstlistingname{Listing}
+\lst@Key{numberbychapter}{true}[t]{\lstKV@SetIf{#1}\lst@ifnumberbychapter}
+\@ifundefined{abovecaptionskip}
+{\newskip\abovecaptionskip
+ \newskip\belowcaptionskip}{}
+\@ifundefined{@makecaption}
+{\long\def\@makecaption#1#2{%
+   \vskip\abovecaptionskip
+   \sbox\@tempboxa{#1: #2}%
+   \ifdim \wd\@tempboxa >\hsize
+     #1: #2\par
+   \else
+     \global \@minipagefalse
+     \hb@xt@\hsize{\hfil\box\@tempboxa\hfil}%
+   \fi
+   \vskip\belowcaptionskip}%
+}{}
+\def\fnum@lstlisting{%
+  \lstlistingname
+  \ifx\lst@@caption\@empty\else~\thelstlisting\fi}%
+\def\lst@MakeCaption#1{%
+  \lst@ifdisplaystyle
+    \ifx #1t%
+        \ifx\lst@@caption\@empty\expandafter\lst@HRefStepCounter \else
+                                \expandafter\refstepcounter
+        \fi {lstlisting}%
+        \ifx\lst@label\@empty\else \label{\lst@label}\fi
+        \let\lst@arg\lst@intname \lst@ReplaceIn\lst@arg\lst@filenamerpl
+        \global\let\lst@name\lst@arg \global\let\lstname\lst@name
+        \lst@ifnolol\else
+            \ifx\lst@@caption\@empty
+                \ifx\lst@caption\@empty
+                    \ifx\lst@intname\@empty \else \def\lst@temp{ }%
+                    \ifx\lst@intname\lst@temp \else
+                        \addcontentsline{lol}{lstlisting}\lst@name
+                    \fi\fi
+                \fi
+            \else
+                \addcontentsline{lol}{lstlisting}%
+                    {\protect\numberline{\thelstlisting}\lst@@caption}%
+            \fi
+         \fi
+     \fi
+    \ifx\lst@caption\@empty\else
+        \lst@IfSubstring #1\lst@captionpos
+            {\begingroup \let\@@vskip\vskip
+             \def\vskip{\afterassignment\lst@vskip \@tempskipa}%
+             \def\lst@vskip{\nobreak\@@vskip\@tempskipa\nobreak}%
+             \par\@parboxrestore\normalsize\normalfont % \noindent (AS)
+             \ifx #1t\allowbreak \fi
+             \ifx\lst@title\@empty
+                 \lst@makecaption\fnum@lstlisting{\ignorespaces \lst@caption}
+             \else
+                 \lst@maketitle\lst@title % (AS)
+             \fi
+             \ifx #1b\allowbreak \fi
+             \endgroup}{}%
+    \fi
+  \fi}
+\def\lst@makecaption{\@makecaption}
+\def\lst@maketitle{\@makecaption\lst@title@dropdelim}
+\def\lst@title@dropdelim#1{\ignorespaces}
+\AtBeginDocument{%
+\@ifundefined{captionlabelfalse}{}{%
+  \def\lst@maketitle{\captionlabelfalse\@makecaption\@empty}}%
+\@ifundefined{caption@startrue}{}{%
+  \def\lst@maketitle{\caption@startrue\@makecaption\@empty}}%
+}
+\def\lst@HRefStepCounter#1{%
+    \begingroup
+    \c@lstlisting\lst@neglisting
+    \advance\c@lstlisting\m@ne \xdef\lst@neglisting{\the\c@lstlisting}%
+    \ifx\hyper@refstepcounter\@undefined\else
+        \hyper@refstepcounter{#1}%
+    \fi
+    \endgroup}
+\gdef\lst@neglisting{\z@}% init
+\lst@Key{boxpos}{c}{\def\lst@boxpos{#1}}
+\def\lst@boxtrue{\let\lst@ifbox\iftrue}
+\let\lst@ifbox\iffalse
+\lst@Key{float}\relax[\lst@floatplacement]{%
+    \lstKV@SwitchCases{#1}%
+    {true&\let\lst@floatdefault\lst@floatplacement
+          \let\lst@float\lst@floatdefault\\%
+     false&\let\lst@floatdefault\relax
+           \let\lst@float\lst@floatdefault
+    }{\def\lst@next{\@ifstar{\let\lst@beginfloat\@dblfloat
+                             \let\lst@endfloat\end@dblfloat
+                             \lst@KFloat}%
+                            {\let\lst@beginfloat\@float
+                             \let\lst@endfloat\end@float
+                             \lst@KFloat}}
+      \edef\lst@float{#1}%
+      \expandafter\lst@next\lst@float\relax}}
+\def\lst@KFloat#1\relax{%
+    \ifx\@empty#1\@empty
+        \let\lst@float\lst@floatplacement
+    \else
+        \def\lst@float{#1}%
+    \fi}
+\lst@Key{floatplacement}{tbp}{\def\lst@floatplacement{#1}}
+\lst@AddToHook{PreSet}{\let\lst@float\lst@floatdefault}
+\lst@AddToHook{TextStyle}{\let\lst@float\relax}
+\let\lst@floatdefault\relax % init
+\lst@AddToHook{DeInit}{%
+    \ifx\lst@float\relax
+        \global\let\lst@doendpe\@doendpe
+    \else
+        \global\let\lst@doendpe\@empty
+    \fi}
+\AtBeginDocument{%
+\@ifundefined{c@float@type}%
+    {\edef\ftype@lstlisting{\ifx\c@figure\@undefined 1\else 4\fi}}
+    {\edef\ftype@lstlisting{\the\c@float@type}%
+     \addtocounter{float@type}{\value{float@type}}}%
+}
+\lst@Key{aboveskip}\medskipamount{\def\lst@aboveskip{#1}}
+\lst@Key{belowskip}\medskipamount{\def\lst@belowskip{#1}}
+\lst@AddToHook{TextStyle}
+    {\let\lst@aboveskip\z@ \let\lst@belowskip\z@}
+\lst@Key{everydisplay}{}{\def\lst@EveryDisplay{#1}}
+\lst@AddToHook{TextStyle}{\let\lst@ifdisplaystyle\iffalse}
+\lst@AddToHook{DisplayStyle}{\let\lst@ifdisplaystyle\iftrue}
+\let\lst@ifdisplaystyle\iffalse
+\def\lst@Init#1{%
+    \begingroup
+    \ifx\lst@float\relax\else
+        \edef\@tempa{\noexpand\lst@beginfloat{lstlisting}[\lst@float]}%
+        \expandafter\@tempa
+    \fi
+    \ifx\lst@multicols\@empty\else
+        \edef\lst@next{\noexpand\multicols{\lst@multicols}}
+        \expandafter\lst@next
+    \fi
+    \ifhmode\ifinner \lst@boxtrue \fi\fi
+    \lst@ifbox
+        \lsthk@BoxUnsafe
+        \hbox to\z@\bgroup
+             $\if t\lst@boxpos \vtop
+        \else \if b\lst@boxpos \vbox
+        \else \vcenter \fi\fi
+        \bgroup \par\noindent
+    \else
+        \lst@ifdisplaystyle
+            \lst@EveryDisplay
+            \par\penalty-50\relax
+            \vspace\lst@aboveskip
+        \fi
+    \fi
+    \normalbaselines
+    \abovecaptionskip\lst@abovecaption\relax
+    \belowcaptionskip\lst@belowcaption\relax
+    \lst@MakeCaption t%
+    \lsthk@PreInit \lsthk@Init
+    \lst@ifdisplaystyle
+        \global\let\lst@ltxlabel\@empty
+        \if@inlabel
+            \lst@ifresetmargins
+                \leavevmode
+            \else
+                \xdef\lst@ltxlabel{\the\everypar}%
+                \lst@AddTo\lst@ltxlabel{%
+                    \global\let\lst@ltxlabel\@empty
+                    \everypar{\lsthk@EveryLine\lsthk@EveryPar}}%
+            \fi
+        \fi
+        \everypar\expandafter{\lst@ltxlabel
+                              \lsthk@EveryLine\lsthk@EveryPar}%
+    \else
+        \everypar{}\let\lst@NewLine\@empty
+    \fi
+    \lsthk@InitVars \lsthk@InitVarsBOL
+    \lst@Let{13}\lst@MProcessListing
+    \let\lst@Backslash#1%
+    \lst@EnterMode{\lst@Pmode}{\lst@SelectCharTable}%
+    \lst@InitFinalize}
+\let\lst@InitFinalize\@empty % init
+\lst@AddToHook{PreInit}
+    {\rightskip\z@ \leftskip\z@ \parfillskip=\z@ plus 1fil
+     \let\par\@@par}
+\lst@AddToHook{EveryLine}{}% init
+\lst@AddToHook{EveryPar}{}% init
+\lst@Key{showlines}f[t]{\lstKV@SetIf{#1}\lst@ifshowlines}
+\def\lst@DeInit{%
+    \lst@XPrintToken \lst@EOLUpdate
+    \global\advance\lst@newlines\m@ne
+    \lst@ifshowlines
+        \lst@DoNewLines
+    \else
+        \setbox\@tempboxa\vbox{\lst@DoNewLines}%
+    \fi
+    \lst@ifdisplaystyle \par\removelastskip \fi
+    \lsthk@ExitVars\everypar{}\lsthk@DeInit\normalbaselines\normalcolor
+    \lst@MakeCaption b%
+    \lst@ifbox
+        \egroup $\hss \egroup
+        \vrule\@width\lst@maxwidth\@height\z@\@depth\z@
+    \else
+        \lst@ifdisplaystyle
+            \par\penalty-50\vspace\lst@belowskip
+        \fi
+    \fi
+    \ifx\lst@multicols\@empty\else
+        \def\lst@next{\global\let\@checkend\@gobble
+                      \endmulticols
+                      \global\let\@checkend\lst@@checkend}
+        \expandafter\lst@next
+    \fi
+    \ifx\lst@float\relax\else
+        \expandafter\lst@endfloat
+    \fi
+    \endgroup}
+\let\lst@@checkend\@checkend
+\newdimen\lst@maxwidth % \global
+\lst@AddToHook{InitVars}{\global\lst@maxwidth\z@}
+\lst@AddToHook{InitVarsEOL}
+    {\ifdim\lst@currlwidth>\lst@maxwidth
+         \global\lst@maxwidth\lst@currlwidth
+     \fi}
+\def\lst@EOLUpdate{\lsthk@EOL \lsthk@InitVarsEOL}
+\def\lst@MProcessListing{%
+    \lst@XPrintToken \lst@EOLUpdate \lsthk@InitVarsBOL
+    \global\advance\lst@lineno\@ne
+    \ifnum \lst@lineno>\lst@lastline
+        \lst@ifdropinput \lst@LeaveMode \fi
+        \ifx\lst@linerange\@empty
+            \expandafter\expandafter\expandafter\lst@EndProcessListing
+        \else
+            \lst@interrange
+            \lst@GetLineInterval
+            \expandafter\expandafter\expandafter\lst@SkipToFirst
+        \fi
+    \else
+        \expandafter\lst@BOLGobble
+    \fi}
+\let\lst@EndProcessListing\endinput
+\lst@Key{gobble}{0}{\def\lst@gobble{#1}}
+\def\lst@BOLGobble{%
+    \ifnum\lst@gobble>\z@
+        \@tempcnta\lst@gobble\relax
+        \expandafter\lst@BOLGobble@
+\fi}
+\def\lst@BOLGobble@@{%
+    \ifnum\@tempcnta>\z@
+        \expandafter\lst@BOLGobble@
+    \fi}
+\def\lstenv@BOLGobble@@{%
+    \lst@IfNextChars\lstenv@endstring{\lstenv@End}%
+    {\advance\@tempcnta\m@ne \expandafter\lst@BOLGobble@@\lst@eaten}}
+\def\lst@BOLGobble@#1{%
+    \let\lst@next#1%
+    \ifx \lst@next\relax\else
+    \ifx \lst@next\lst@MProcessListing\else
+    \ifx \lst@next\lst@processformfeed\else
+    \ifx \lst@next\lstenv@backslash
+        \let\lst@next\lstenv@BOLGobble@@
+    \else
+        \let\lst@next\lst@BOLGobble@@
+        \ifx #1\lst@processtabulator
+            \advance\@tempcnta-\lst@tabsize\relax
+            \ifnum\@tempcnta<\z@
+                \lst@length-\@tempcnta \lst@PreGotoTabStop
+            \fi
+        \else
+            \advance\@tempcnta\m@ne
+        \fi
+    \fi \fi \fi \fi
+    \lst@next}
+\def\lst@processformfeed{\lst@ProcessFormFeed}
+\def\lst@processtabulator{\lst@ProcessTabulator}
+\lst@Key{name}\relax{\def\lst@intname{#1}}
+\lst@AddToHookExe{PreSet}{\global\let\lst@intname\@empty}
+\lst@AddToHook{PreInit}{%
+    \let\lst@arg\lst@intname \lst@ReplaceIn\lst@arg\lst@filenamerpl
+    \global\let\lst@name\lst@arg \global\let\lstname\lst@name}
+\def\lst@filenamerpl{_\textunderscore $\textdollar -\textendash}
+\def\l@lstlisting#1#2{\@dottedtocline{1}{1.5em}{2.3em}{#1}{#2}}
+\lst@UserCommand\lstlistlistingname{Listings}
+\lst@UserCommand\lstlistoflistings{\bgroup
+    \let\contentsname\lstlistlistingname
+    \let\lst@temp\@starttoc \def\@starttoc##1{\lst@temp{lol}}%
+    \tableofcontents \egroup}
+\@ifundefined{float@listhead}{}{%
+  \renewcommand*{\lstlistoflistings}{%
+    \begingroup
+      \@ifundefined{@restonecoltrue}{}{%
+        \if@twocolumn
+          \@restonecoltrue\onecolumn
+        \else
+          \@restonecolfalse
+        \fi
+      }%
+      \float@listhead{\lstlistlistingname}%
+      \parskip\z@\parindent\z@\parfillskip \z@ \@plus 1fil%
+      \@starttoc{lol}%
+      \@ifundefined{@restonecoltrue}{}{%
+        \if@restonecol\twocolumn\fi
+      }%
+    \endgroup
+  }%
+}
+\AtBeginDocument{%
+  \@ifundefined{float@addtolists}%
+    {\gdef\float@addtolists#1{\addtocontents{lol}{#1}}}%
+    {\let\orig@float@addtolists\float@addtolists
+     \gdef\float@addtolists#1{%
+       \addtocontents{lol}{#1}%
+       \orig@float@addtolists{#1}}}%
+}%
+\newcommand\lstinline[1][]{%
+    \leavevmode\bgroup % \hbox\bgroup --> \bgroup
+      \def\lst@boxpos{b}%
+      \lsthk@PreSet\lstset{flexiblecolumns,#1}%
+      \lsthk@TextStyle
+      \@ifnextchar\bgroup{%
+        \afterassignment\lst@InlineG \let\@let@token}%
+                         \lstinline@}
+\def\lstinline@#1{%
+    \lst@Init\relax
+    \lst@IfNextCharActive{\lst@InlineM#1}{\lst@InlineJ#1}}
+\lst@AddToHook{TextStyle}{}% init
+\lst@AddToHook{SelectCharTable}{\lst@inlinechars}
+\global\let\lst@inlinechars\@empty
+\def\lst@InlineM#1{\gdef\lst@inlinechars{%
+    \lst@Def{`#1}{\lst@DeInit\egroup\global\let\lst@inlinechars\@empty}%
+    \lst@Def{13}{\lst@DeInit\egroup \global\let\lst@inlinechars\@empty
+        \PackageError{Listings}{lstinline ended by EOL}\@ehc}}%
+    \lst@inlinechars}
+\def\lst@InlineJ#1{%
+    \def\lst@temp##1#1{%
+        \let\lst@arg\@empty \lst@InsideConvert{##1}\lst@arg
+        \lst@DeInit\egroup}%
+    \lst@temp}
+\def\lst@InlineG{%
+    \lst@Init\relax
+    \lst@IfNextCharActive{\lst@InlineM\}}%
+                         {\let\lst@arg\@empty \lst@InlineGJ}}
+\def\lst@InlineGJ{\futurelet\@let@token\lst@InlineGJTest}
+\def\lst@InlineGJTest{%
+    \ifx\@let@token\egroup
+        \afterassignment\lst@InlineGJEnd
+        \expandafter\let\expandafter\@let@token
+    \else
+        \ifx\@let@token\@sptoken
+            \let\lst@next\lst@InlineGJReadSp
+        \else
+            \let\lst@next\lst@InlineGJRead
+        \fi
+        \expandafter\lst@next
+    \fi}
+\def\lst@InlineGJEnd{\lst@arg\lst@DeInit\egroup}
+\def\lst@InlineGJRead#1{%
+    \lccode`\~=`#1\lowercase{\lst@lAddTo\lst@arg~}%
+    \lst@InlineGJ}
+\def\lst@InlineGJReadSp#1{%
+    \lccode`\~=`\ \lowercase{\lst@lAddTo\lst@arg~}%
+    \lst@InlineGJ#1}
+\newcommand\lstMakeShortInline[1][]{%
+  \def\lst@shortinlinedef{\lstinline[#1]}%
+  \lstMakeShortInline@}%
+\def\lstMakeShortInline@#1{%
+  \expandafter\ifx\csname lst@ShortInlineOldCatcode\string#1\endcsname\relax
+    \lst@shortlstinlineinfo{Made }{#1}%
+    \lst@add@special{#1}%
+    \expandafter
+    \xdef\csname lst@ShortInlineOldCatcode\string#1\endcsname{\the\catcode`#1}%
+    \begingroup
+      \catcode`\~\active  \lccode`\~`#1%
+      \lowercase{%
+        \global\expandafter\let
+          \csname lst@ShortInlineOldMeaning\string#1\endcsname~%
+          \expandafter\gdef\expandafter~\expandafter{\lst@shortinlinedef#1}}%
+    \endgroup
+    \global\catcode`#1\active
+  \else
+    \PackageError{Listings}%
+    {\string\lstMakeShorterInline\ definitions cannot be nested}%
+    {Use \string\lstDeleteShortInline first.}%
+    {}%
+  \fi}
+\def\lstDeleteShortInline#1{%
+  \expandafter\ifx\csname lst@ShortInlineOldCatcode\string#1\endcsname\relax
+    \PackageError{Listings}%
+    {#1 is not a short reference for \string\lstinline}%
+    {Use \string\lstMakeShortInline first.}%
+    {}%
+  \else
+    \lst@shortlstinlineinfo{Deleted }{#1 as}%
+    \lst@rem@special{#1}%
+    \global\catcode`#1\csname lst@ShortInlineOldCatcode\string#1\endcsname
+    \global \expandafter\let%
+      \csname lst@ShortInlineOldCatcode\string#1\endcsname \relax
+    \ifnum\catcode`#1=\active
+      \begingroup
+        \catcode`\~\active  \lccode`\~`#1%
+        \lowercase{%
+          \global\expandafter\let\expandafter~%
+          \csname lst@ShortInlineOldMeaning\string#1\endcsname}%
+      \endgroup
+    \fi
+  \fi}
+\def\lst@shortlstinlineinfo#1#2{%
+     \PackageInfo{Listings}{%
+       #1\string#2 a short reference for \string\lstinline}}
+\def\lst@add@special#1{%
+  \lst@rem@special{#1}%
+  \expandafter\gdef\expandafter\dospecials\expandafter
+    {\dospecials \do #1}%
+  \expandafter\gdef\expandafter\@sanitize\expandafter
+    {\@sanitize \@makeother #1}}
+\def\lst@rem@special#1{%
+  \def\do##1{%
+    \ifnum`#1=`##1 \else \noexpand\do\noexpand##1\fi}%
+  \xdef\dospecials{\dospecials}%
+  \begingroup
+    \def\@makeother##1{%
+      \ifnum`#1=`##1 \else \noexpand\@makeother\noexpand##1\fi}%
+    \xdef\@sanitize{\@sanitize}%
+  \endgroup}
+\def\lst@MakePath#1{\ifx\@empty#1\@empty\else\lst@MakePath@#1/\@nil/\fi}
+\def\lst@MakePath@#1/{#1/\lst@MakePath@@}
+\def\lst@MakePath@@#1/{%
+    \ifx\@nil#1\expandafter\@gobble
+         \else \ifx\@empty#1\else #1/\fi \fi
+    \lst@MakePath@@}
+\lst@Key{inputpath}{}{\edef\lst@inputpath{\lst@MakePath{#1}}}
+\def\lstinputlisting{%
+    \begingroup \lst@setcatcodes \lst@inputlisting}
+\newcommand\lst@inputlisting[2][]{%
+    \endgroup
+    \def\lst@set{#1}%
+    \IfFileExists{\lst@inputpath#2}%
+        {\expandafter\lst@InputListing\expandafter{\lst@inputpath#2}}%
+        {\filename@parse{\lst@inputpath#2}%
+         \edef\reserved@a{\noexpand\lst@MissingFileError
+             {\filename@area\filename@base}%
+             {\ifx\filename@ext\relax tex\else\filename@ext\fi}}%
+         \reserved@a}%
+    \lst@doendpe \@newlistfalse \ignorespaces}
+\def\lst@MissingFileError#1#2{%
+    \typeout{^^J! Package Listings Error: File `#1(.#2)' not found.^^J%
+        ^^JType X to quit or <RETURN> to proceed,^^J%
+        or enter new name. (Default extension: #2)^^J}%
+    \message{Enter file name: }%
+    {\endlinechar\m@ne \global\read\m@ne to\@gtempa}%
+    \ifx\@gtempa\@empty \else
+        \def\reserved@a{x}\ifx\reserved@a\@gtempa\batchmode\@@end\fi
+        \def\reserved@a{X}\ifx\reserved@a\@gtempa\batchmode\@@end\fi
+        \filename@parse\@gtempa
+        \edef\filename@ext{%
+            \ifx\filename@ext\relax#2\else\filename@ext\fi}%
+        \edef\reserved@a{\noexpand\IfFileExists %
+                {\filename@area\filename@base.\filename@ext}%
+            {\noexpand\lst@InputListing %
+                {\filename@area\filename@base.\filename@ext}}%
+            {\noexpand\lst@MissingFileError
+                {\filename@area\filename@base}{\filename@ext}}}%
+        \expandafter\reserved@a %
+    \fi}
+\let\lst@ifdraft\iffalse
+\DeclareOption{draft}{\let\lst@ifdraft\iftrue}
+\DeclareOption{final}{\let\lst@ifdraft\iffalse}
+\lst@AddToHook{PreSet}
+    {\lst@ifdraft
+         \let\lst@ifprint\iffalse
+         \@gobbletwo\fi\fi
+     \fi}
+\def\lst@InputListing#1{%
+    \begingroup
+      \lsthk@PreSet \gdef\lst@intname{#1}%
+      \expandafter\lstset\expandafter{\lst@set}%
+      \lsthk@DisplayStyle
+      \catcode\active=\active
+      \lst@Init\relax \let\lst@gobble\z@
+      \lst@SkipToFirst
+      \lst@ifprint \def\lst@next{\input{#1}}%
+             \else \let\lst@next\@empty \fi
+      \lst@next
+      \lst@DeInit
+    \endgroup}
+\def\lst@SkipToFirst{%
+    \ifnum \lst@lineno<\lst@firstline
+        \lst@BeginDropInput\lst@Pmode
+        \lst@Let{13}\lst@MSkipToFirst
+        \lst@Let{10}\lst@MSkipToFirst
+    \else
+        \expandafter\lst@BOLGobble
+    \fi}
+\def\lst@MSkipToFirst{%
+    \global\advance\lst@lineno\@ne
+    \ifnum \lst@lineno=\lst@firstline
+        \lst@LeaveMode \global\lst@newlines\z@
+        \lsthk@InitVarsBOL
+        \expandafter\lst@BOLGobble
+    \fi}
+\def\lstenv@DroppedWarning{%
+    \ifx\lst@dropped\@undefined\else
+        \PackageWarning{Listings}{Text dropped after begin of listing}%
+    \fi}
+\let\lst@dropped\@undefined % init
+\begingroup \lccode`\~=`\^^M\lowercase{%
+\gdef\lstenv@Process#1{%
+    \ifx~#1%
+        \lstenv@DroppedWarning \let\lst@next\lst@SkipToFirst
+    \else\ifx^^J#1%
+        \lstenv@DroppedWarning \let\lst@next\lstenv@ProcessJ
+    \else
+        \let\lst@dropped#1\let\lst@next\lstenv@Process
+    \fi \fi
+    \lst@next}
+}\endgroup
+\def\lstenv@ProcessJ{%
+    \let\lst@arg\@empty
+    \ifx\@currenvir\lstenv@name
+        \expandafter\lstenv@ProcessJEnv
+    \else
+        \expandafter\def\expandafter\lst@temp\expandafter##1%
+            \csname end\lstenv@name\endcsname
+                {\lst@InsideConvert{##1}\lstenv@ProcessJ@}%
+        \expandafter\lst@temp
+    \fi}
+\begingroup \lccode`\~=`\\\lowercase{%
+\gdef\lstenv@ProcessJ@{%
+    \lst@lExtend\lst@arg
+        {\expandafter\ \expandafter~\lstenv@endstring}%
+    \catcode10=\active \lst@Let{10}\lst@MProcessListing
+    \lst@SkipToFirst \lst@arg}
+}\endgroup
+\def\lstenv@ProcessJEnv#1\end#2{\def\lst@temp{#2}%
+    \ifx\lstenv@name\lst@temp
+        \lst@InsideConvert{#1}%
+        \expandafter\lstenv@ProcessJ@
+    \else
+        \lst@InsideConvert{#1\\end\{#2\}}%
+        \expandafter\lstenv@ProcessJEnv
+    \fi}
+\def\lstenv@backslash{%
+    \lst@IfNextChars\lstenv@endstring
+        {\lstenv@End}%
+        {\expandafter\lsts@backslash \lst@eaten}}%
+\def\lstenv@End{%
+    \ifx\@currenvir\lstenv@name
+        \edef\lst@next{\noexpand\end{\lstenv@name}}%
+    \else
+        \def\lst@next{\csname end\lstenv@name\endcsname}%
+    \fi
+    \lst@next}
+\lst@UserCommand\lstnewenvironment#1#2#{%
+    \@ifundefined{#1}%
+        {\let\lst@arg\@empty
+         \lst@XConvert{#1}\@nil
+         \expandafter\lstnewenvironment@\lst@arg{#1}{#2}}%
+        {\PackageError{Listings}{Environment `#1' already defined}\@eha
+         \@gobbletwo}}
+\def\@tempa#1#2#3{%
+\gdef\lstnewenvironment@##1##2##3##4##5{%
+    \begingroup
+    \global\@namedef{end##2}{\lstenv@Error{##2}}%
+    \global\@namedef{##2}{\def\lstenv@name{##2}%
+        \begingroup \lst@setcatcodes \catcode\active=\active
+        \csname##2@\endcsname}%
+    \let\l@ngrel@x\global
+    \let\@xargdef\lstenv@xargdef
+    \expandafter\new@command\csname##2@\endcsname##3%
+        {\lsthk@PreSet ##4%
+         \ifx\@currenvir\lstenv@name
+             \def\lstenv@endstring{#1#2##1#3}%
+         \else
+             \def\lstenv@endstring{#1##1}%
+         \fi
+         \@namedef{end##2}{\lst@DeInit ##5\endgroup
+                          \lst@doendpe \@ignoretrue}%
+         \lsthk@DisplayStyle
+         \let\lst@EndProcessListing\lstenv@SkipToEnd
+         \lst@Init\lstenv@backslash
+         \lst@ifprint
+             \expandafter\expandafter\expandafter\lstenv@Process
+         \else
+             \expandafter\lstenv@SkipToEnd
+         \fi
+         \lst@insertargs}%
+    \endgroup}%
+}
+\let\lst@arg\@empty \lst@XConvert{end}\{\}\@nil
+\expandafter\@tempa\lst@arg
+\let\lst@insertargs\@empty
+\def\lstenv@xargdef#1{
+    \expandafter\lstenv@xargdef@\csname\string#1\endcsname#1}
+\def\lstenv@xargdef@#1#2[#3][#4]#5{%
+  \@ifdefinable#2{%
+       \gdef#2{%
+          \ifx\protect\@typeset@protect
+            \expandafter\lstenv@testopt
+          \else
+            \@x@protect#2%
+          \fi
+          #1%
+          {#4}}%
+       \@yargdef
+          #1%
+           \tw@
+           {#3}%
+           {#5}}}
+\long\def\lstenv@testopt#1#2{%
+  \@ifnextchar[{\catcode\active5\relax \lstenv@testopt@#1}%
+               {#1[{#2}]}}
+\def\lstenv@testopt@#1[#2]{%
+    \catcode\active\active
+    #1[#2]}
+\begingroup \lccode`\~=`\\\lowercase{%
+\gdef\lstenv@SkipToEnd{%
+    \long\expandafter\def\expandafter\lst@temp\expandafter##\expandafter
+        1\expandafter~\lstenv@endstring{\lstenv@End}%
+    \lst@temp}
+}\endgroup
+\def\lstenv@Error#1{\PackageError{Listings}{Extra \string\end#1}%
+    {I'm ignoring this, since I wasn't doing a \csname#1\endcsname.}}
+\begingroup \lccode`\~=`\^^M\lowercase{%
+\gdef\lst@TestEOLChar#1{%
+    \def\lst@insertargs{#1}%
+    \ifx ~#1\@empty \else
+    \ifx^^J#1\@empty \else
+        \global\let\lst@intname\lst@insertargs
+        \let\lst@insertargs\@empty
+    \fi \fi}
+}\endgroup
+\lstnewenvironment{lstlisting}[2][]
+    {\lst@TestEOLChar{#2}%
+     \lstset{#1}%
+     \csname\@lst @SetFirstNumber\endcsname}
+    {\csname\@lst @SaveFirstNumber\endcsname}
+\lst@Key{fancyvrb}\relax[t]{%
+    \lstKV@SetIf{#1}\lst@iffancyvrb
+    \lstFV@fancyvrb}
+\ifx\lstFV@fancyvrb\@undefined
+    \gdef\lstFV@fancyvrb{\lst@RequireAspects{fancyvrb}\lstFV@fancyvrb}
+\fi
+\@ifundefined{ocp}{}
+    {\lst@AddToHook{OutputBox}%
+         {\let\lst@ProcessLetter\@firstofone
+          \let\lst@ProcessDigit\@firstofone
+          \let\lst@ProcessOther\@firstofone}}
+\DeclareOption*{\expandafter\lst@ProcessOption\CurrentOption\relax}
+\def\lst@ProcessOption#1#2\relax{%
+    \ifx #1!%
+        \lst@DeleteKeysIn\lst@loadaspects{#2}%
+    \else
+        \lst@lAddTo\lst@loadaspects{,#1#2}%
+    \fi}
+\@ifundefined{lst@loadaspects}
+  {\def\lst@loadaspects{strings,comments,escape,style,language,%
+      keywords,labels,lineshape,frames,emph,index}%
+  }{}
+\InputIfFileExists{lstpatch.sty}{}{}
+\let\lst@ifsavemem\iffalse
+\DeclareOption{savemem}{\let\lst@ifsavemem\iftrue}
+\DeclareOption{noaspects}{\let\lst@loadaspects\@empty}
+\ProcessOptions
+\lst@RequireAspects\lst@loadaspects
+\let\lst@loadaspects\@empty
+\lst@UseHook{SetStyle}\lst@UseHook{EmptyStyle}
+\lst@UseHook{SetLanguage}\lst@UseHook{EmptyLanguage}
+\InputIfFileExists{listings.cfg}{}{}
+\InputIfFileExists{lstlocal.cfg}{}{}
+\endinput
+%%
+%% End of file `listings.sty'.
diff --git a/dataflow/manual/lstdoc.sty b/dataflow/manual/lstdoc.sty
new file mode 100644
index 0000000..a346490
--- /dev/null
+++ b/dataflow/manual/lstdoc.sty
@@ -0,0 +1,454 @@
+%%
+%% This is file `lstdoc.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% listings.dtx  (with options: `doc')
+%% 
+%% Please read the software license in listings-1.3.dtx or listings-1.3.pdf.
+%%
+%% (w)(c) 1996--2004 Carsten Heinz and/or any other author listed
+%% elsewhere in this file.
+%% (c) 2006 Brooks Moses
+%% (c) 2013- Jobst Hoffmann
+%%
+%% Send comments and ideas on the package, error reports and additional
+%% programming languages to Jobst Hoffmann at <j.hoffmann@fh-aachen.de>.
+%%
+\def\filedate{2015/06/04}
+\def\fileversion{1.6}
+\ProvidesPackage{lstdoc}
+             [\filedate\space\fileversion\space(Carsten Heinz)]
+\let\lstdoc@currversion\fileversion
+\RequirePackage[writefile]{listings}[2004/09/07]
+\newif\iffancyvrb \IfFileExists{fancyvrb.sty}{\fancyvrbtrue}{}
+\newif\ifcolor \IfFileExists{color.sty}{\colortrue}{}
+\lst@false
+\newif\ifhyper
+\@ifundefined{pdfoutput}
+    {}
+    {\ifnum\pdfoutput>\z@ \lst@true \fi}
+\@ifundefined{VTeXversion}
+    {}
+    {\ifnum\OpMode>\z@ \lst@true \fi}
+\lst@if \IfFileExists{hyperref.sty}{\hypertrue}{}\fi
+\newif\ifalgorithmicpkg \IfFileExists{algorithmic.sty}{\algorithmicpkgtrue}{}
+\newif\iflgrind \IfFileExists{lgrind.sty}{\lgrindtrue}{}
+\iffancyvrb \RequirePackage{fancyvrb}\fi
+\ifhyper \RequirePackage[colorlinks]{hyperref}\else
+    \def\href#1{\texttt}\fi
+\ifcolor \RequirePackage{color}\fi
+\ifalgorithmicpkg \RequirePackage{algorithmic}\fi
+\iflgrind \RequirePackage{lgrind}\fi
+\RequirePackage{nameref}
+\RequirePackage{url}
+\renewcommand\ref{\protect\T@ref}
+\renewcommand\pageref{\protect\T@pageref}
+\def\lst@BeginRemark#1{%
+    \begin{quote}\topsep0pt\let\small\footnotesize\small#1:}
+\def\lst@EndRemark{\end{quote}}
+\newenvironment{TODO}
+    {\lst@BeginRemark{To do}}{\lst@EndRemark}
+\newenvironment{ALTERNATIVE}
+    {\lst@BeginRemark{Alternative}}{\lst@EndRemark}
+\newenvironment{REMOVED}
+    {\lst@BeginRemark{Removed}}{\lst@EndRemark}
+\newenvironment{OLDDEF}
+    {\lst@BeginRemark{Old definition}}{\lst@EndRemark}
+\def\advise{\par\list\labeladvise
+    {\advance\linewidth\@totalleftmargin
+     \@totalleftmargin\z@
+     \@listi
+     \let\small\footnotesize \small\sffamily
+     \parsep \z@ \@plus\z@ \@minus\z@
+     \topsep6\p@ \@plus1\p@\@minus2\p@
+     \def\makelabel##1{\hss\llap{##1}}}}
+\let\endadvise\endlist
+\def\advisespace{\hbox{}\qquad}
+\def\labeladvise{$\to$}
+\newenvironment{syntax}
+   {\list{}{\itemindent-\leftmargin
+    \def\makelabel##1{\hss\lst@syntaxlabel##1,,,,\relax}}}
+   {\endlist}
+\def\lst@syntaxlabel#1,#2,#3,#4\relax{%
+    \llap{\scriptsize\itshape#3}%
+    \def\lst@temp{#2}%
+    \expandafter\lst@syntaxlabel@\meaning\lst@temp\relax
+    \rlap{\hskip-\itemindent\hskip\itemsep\hskip\linewidth
+          \llap{\ttfamily\lst@temp}\hskip\labelwidth
+          \def\lst@temp{#1}%
+          \ifx\lst@temp\lstdoc@currversion#1\fi}}
+\def\lst@syntaxlabel@#1>#2\relax
+    {\edef\lst@temp{\zap@space#2 \@empty}}
+\newcommand*\syntaxnewline{\newline\hbox{}\kern\labelwidth}
+\newcommand*\syntaxor{\qquad or\qquad}
+\newcommand*\syntaxbreak
+    {\hfill\kern0pt\discretionary{}{\kern\labelwidth}{}}
+\let\syntaxfill\hfill
+\def\alternative#1{\lst@true \alternative@#1,\relax,}
+\def\alternative@#1,{%
+    \ifx\relax#1\@empty
+        \expandafter\@gobble
+    \else
+        \ifx\@empty#1\@empty\else
+            \lst@if \lst@false \else $\vert$\fi
+            \textup{\texttt{#1}}%
+        \fi
+    \fi
+    \alternative@}
+\long\def\m@cro@#1#2#3{\endgroup \topsep\MacroTopsep \trivlist
+  \edef\saved@macroname{\string#3}%
+  \def\makelabel##1{\llap{##1}}%
+  \if@inlabel
+    \let\@tempa\@empty \count@\macro@cnt
+    \loop \ifnum\count@>\z@
+      \edef\@tempa{\@tempa\hbox{\strut}}\advance\count@\m@ne \repeat
+    \edef\makelabel##1{\llap{\vtop to\baselineskip
+                               {\@tempa\hbox{##1}\vss}}}%
+    \advance \macro@cnt \@ne
+  \else  \macro@cnt\@ne  \fi
+  \edef\@tempa{\noexpand\item[%
+     #1%
+       \noexpand\PrintMacroName
+     \else
+       \expandafter\noexpand\csname Print#2Name\endcsname % MODIFIED
+     \fi
+     {\string#3}]}%
+  \@tempa
+  \global\advance\c@CodelineNo\@ne
+   #1%
+      \SpecialMainIndex{#3}\nobreak
+      \DoNotIndex{#3}%
+   \else
+      \csname SpecialMain#2Index\endcsname{#3}\nobreak % MODIFIED
+   \fi
+  \global\advance\c@CodelineNo\m@ne
+  \ignorespaces}
+\def\macro{\begingroup
+   \catcode`\\12
+   \MakePrivateLetters \m@cro@ \iftrue {Macro}}% MODIFIED
+\def\environment{\begingroup
+   \catcode`\\12
+   \MakePrivateLetters \m@cro@ \iffalse {Env}}% MODIFIED
+\def\newdocenvironment#1#2#3#4{%
+    \@namedef{#1}{#3\begingroup \catcode`\\12\relax
+                  \MakePrivateLetters \m@cro@ \iffalse {#2}}%
+    \@namedef{end#1}{#4\endmacro}%
+    \@ifundefined{Print#2Name}{\expandafter
+        \let\csname Print#2Name\endcsname\PrintMacroName}{}%
+    \@ifundefined{SpecialMain#2Index}{\expandafter
+        \let\csname SpecialMain#2Index\endcsname\SpecialMainIndex}{}}
+\newdocenvironment{aspect}{Aspect}{}{}
+\def\PrintAspectName#1{}
+\def\SpecialMainAspectIndex#1{%
+    \@bsphack
+    \index{aspects:\levelchar\protect\aspectname{#1}}%
+    \@esphack}
+\newdocenvironment{lstkey}{Key}{}{}
+\def\PrintKeyName#1{\strut\keyname{#1}\ }
+\def\SpecialMainKeyIndex#1{%
+    \@bsphack
+    \index{keys\levelchar\protect\keyname{#1}}%
+    \@esphack}
+\newcounter{argcount}
+\def\labelargcount{\texttt{\#\arabic{argcount}}\hskip\labelsep$=$}
+\def\macroargs{\list\labelargcount
+    {\usecounter{argcount}\leftmargin=2\leftmargin
+     \parsep \z@ \@plus\z@ \@minus\z@
+     \topsep4\p@ \@plus\p@ \@minus2\p@
+     \itemsep\z@ \@plus\z@ \@minus\z@
+     \def\makelabel##1{\hss\llap{##1}}}}
+\def\endmacroargs{\endlist\@endparenv}
+\lst@RequireAspects{writefile}
+\newbox\lst@samplebox
+\lstnewenvironment{lstsample}[3][]
+    {\global\let\lst@intname\@empty
+     \gdef\lst@sample{#2}%
+     \setbox\lst@samplebox=\hbox\bgroup
+         \setkeys{lst}{language={},style={},tabsize=4,gobble=5,%
+             basicstyle=\small\ttfamily,basewidth=0.51em,point={#1}}
+         #3%
+         \lst@BeginAlsoWriteFile{\jobname.tmp}}
+    {\lst@EndWriteFile\egroup
+     \ifdim \wd\lst@samplebox>.5\linewidth
+         \begin{center}%
+             \hbox to\linewidth{\box\lst@samplebox\hss}%
+         \end{center}%
+         \lst@sampleInput
+     \else
+         \begin{center}%
+         \begin{minipage}{0.45\linewidth}\lst@sampleInput\end{minipage}%
+         \qquad
+         \begin{minipage}{0.45\linewidth}%
+             \hbox to\linewidth{\box\lst@samplebox\hss}%
+         \end{minipage}%
+         \end{center}%
+     \fi}
+\lst@InstallKeywords{p}{point}{pointstyle}\relax{keywordstyle}{}ld
+\lstnewenvironment{lstxsample}[1][]
+    {\begingroup
+         \setkeys{lst}{belowskip=-\medskipamount,language={},style={},%
+             tabsize=4,gobble=5,basicstyle=\small\ttfamily,%
+             basewidth=0.51em,point={#1}}
+         \lst@BeginAlsoWriteFile{\jobname.tmp}}
+    {\endgroup
+     \endgroup}
+\def\lst@sampleInput{%
+    \MakePercentComment\catcode`\^^M=10\relax
+    \small\lst@sample
+    {\setkeys{lst}{SelectCharTable=\lst@ReplaceInput{\^\^I}%
+                                  {\lst@ProcessTabulator}}%
+     \leavevmode \input{\jobname.tmp}}\MakePercentIgnore}
+\renewcommand\paragraph{\@startsection{paragraph}{4}{\z@}%
+                                      {1.25ex \@plus1ex \@minus.2ex}%
+                                      {-1em}%
+                                      {\normalfont\normalsize\bfseries}}
+\def\lstref#1{\emph{\ref{#1} \nameref{#1}}}
+\def\@part[#1]#2{\ifhyper\phantomsection\fi
+    \addcontentsline{toc}{part}{#1}%
+    {\parindent\z@ \raggedright \interlinepenalty\@M
+     \normalfont \huge \bfseries #2\markboth{}{}\par}%
+    \nobreak\vskip 3ex\@afterheading}
+\renewcommand*\l@section[2]{%
+    \addpenalty\@secpenalty
+    \addvspace{.25em \@plus\p@}%
+    \setlength\@tempdima{1.5em}%
+    \begingroup
+      \parindent \z@ \rightskip \@pnumwidth
+      \parfillskip -\@pnumwidth
+      \leavevmode
+      \advance\leftskip\@tempdima
+      \hskip -\leftskip
+      #1\nobreak\hfil \nobreak\hb@xt@\@pnumwidth{\hss #2}\par
+    \endgroup}
+\renewcommand*\l@subsection{\@dottedtocline{2}{0pt}{2.3em}}
+\renewcommand*\l@subsubsection{\@dottedtocline{3}{0pt}{3.2em}}
+\newcommand\ikeyname[1]{%
+    \lstkeyindex{#1}{}%
+    \lstaspectindex{#1}{}%
+    \keyname{#1}}
+\newcommand\ekeyname[1]{%
+    \@bsphack
+    \lstkeyindex{#1}{}%
+    \lstaspectindex{#1}{}%
+    \@esphack}
+\newcommand\rkeyname[1]{%
+    \@bsphack
+    \lstkeyindex{#1}{}%
+    \lstaspectindex{#1}{}%
+    \@esphack{\rstyle\keyname{#1}}}
+\newcommand\icmdname[1]{%
+    \@bsphack
+    \lstaspectindex{#1}{}%
+    \@esphack\texttt{\string#1}}
+\newcommand\rcmdname[1]{%
+    \@bsphack
+    \lstaspectindex{#1}{}%
+    \@esphack\texttt{\rstyle\string#1}}
+\def\lstaspectindex#1#2{%
+    \global\@namedef{lstkandc@\string#1}{}%
+    \@ifundefined{lstisaspect@\string#1}
+        {\index{unknown\levelchar
+                \protect\texttt{\protect\string\string#1}#2}}%
+        {\index{\@nameuse{lstisaspect@\string#1}\levelchar
+                \protect\texttt{\protect\string\string#1}#2}}%
+}
+\def\lstkeyindex#1#2{%
+}
+\def\lstisaspect[#1]#2{%
+    \global\@namedef{lstaspect@#1}{#2}%
+    \lst@AddTo\lst@allkeysandcmds{,#2}%
+    \@for\lst@temp:=#2\do
+    {\ifx\@empty\lst@temp\else
+         \global\@namedef{lstisaspect@\lst@temp}{#1}%
+     \fi}}
+\gdef\lst@allkeysandcmds{}
+\def\lstprintaspectkeysandcmds#1{%
+    \lst@true
+    \expandafter\@for\expandafter\lst@temp
+    \expandafter:\expandafter=\csname lstaspect@#1\endcsname\do
+    {\lst@if\lst@false\else, \fi \texttt{\lst@temp}}}
+\def\lstcheckreference{%
+   \@for\lst@temp:=\lst@allkeysandcmds\do
+   {\ifx\lst@temp\@empty\else
+        \@ifundefined{lstkandc@\lst@temp}
+        {\typeout{\lst@temp\space not in reference guide?}}{}%
+    \fi}}
+\newcommand*\lst{\texttt{lst}}
+\newcommand*\Cpp{C\texttt{++}}
+\let\keyname\texttt
+\let\keyvalue\texttt
+\let\hookname\texttt
+\newcommand*\aspectname[1]{{\normalfont\sffamily#1}}
+\DeclareRobustCommand\packagename[1]{%
+    {\leavevmode\text@command{#1}%
+     \switchfontfamily\sfdefault\rmdefault
+     \check@icl #1\check@icr
+     \expandafter}}%
+\renewcommand\packagename[1]{{\normalfont\sffamily#1}}
+\def\switchfontfamily#1#2{%
+    \begingroup\xdef\@gtempa{#1}\endgroup
+    \ifx\f@family\@gtempa\fontfamily#2%
+                    \else\fontfamily#1\fi
+    \selectfont}
+\ifcolor
+    \definecolor{darkgreen}{rgb}{0,0.5,0}
+    \def\rstyle{\color{darkgreen}}
+\else
+    \let\rstyle\empty
+\fi
+\gdef\lst@emails{}
+\newcommand*\lstthanks[2]
+    {#1\lst@AddTo\lst@emails{,#1,<#2>}%
+     \ifx\@empty#2\@empty\typeout{Missing email for #1}\fi}
+\newcommand*\lsthelper[3]
+    {{\let~\ #1}%
+     \lst@IfOneOf#1\relax\lst@emails
+     {}{\typeout{^^JWarning: Unknown helper #1.^^J}}}
+\lstdefinelanguage[doc]{Pascal}{%
+  morekeywords={alfa,and,array,begin,boolean,byte,case,char,const,div,%
+     do,downto,else,end,false,file,for,function,get,goto,if,in,%
+     integer,label,maxint,mod,new,not,of,or,pack,packed,page,program,%
+     procedure,put,read,readln,real,record,repeat,reset,rewrite,set,%
+     text,then,to,true,type,unpack,until,var,while,with,write,writeln},%
+  sensitive=false,%
+  morecomment=[s]{(*}{*)},%
+  morecomment=[s]{\{}{\}},%
+  morestring=[d]{'}}
+\lstdefinestyle{}
+    {basicstyle={},%
+     keywordstyle=\bfseries,identifierstyle={},%
+     commentstyle=\itshape,stringstyle={},%
+     numberstyle={},stepnumber=1,%
+     pointstyle=\pointstyle}
+\def\pointstyle{%
+    {\let\lst@um\@empty \xdef\@gtempa{\the\lst@token}}%
+    \expandafter\lstkeyindex\expandafter{\@gtempa}{}%
+    \expandafter\lstaspectindex\expandafter{\@gtempa}{}%
+    \rstyle}
+\lstset{defaultdialect=[doc]Pascal,language=Pascal,style={}}
+\def\lstscanlanguages#1#2#3{%
+    \begingroup
+        \def\lst@DefDriver@##1##2##3##4[##5]##6{%
+           \lst@false
+           \lst@lAddTo\lst@scan{##6(##5),}%
+           \begingroup
+           \@ifnextchar[{\lst@XDefDriver{##1}##3}{\lst@DefDriver@@##3}}%
+        \def\lst@XXDefDriver[##1]{}%
+        \lst@InputCatcodes
+        \def\lst@dontinput{#3}%
+        \let\lst@scan\@empty
+        \lst@for{#2}\do{%
+            \lst@IfOneOf##1\relax\lst@dontinput
+                {}%
+                {\InputIfFileExists{##1}{}{}}}%
+        \global\let\@gtempa\lst@scan
+    \endgroup
+    \let#1\@gtempa}
+\def\lstprintlanguages#1{%
+    \def\do##1{\setbox\@tempboxa\hbox{##1\space\space}%
+        \ifdim\wd\@tempboxa<.5\linewidth \wd\@tempboxa.5\linewidth
+                                   \else \wd\@tempboxa\linewidth \fi
+        \box\@tempboxa\allowbreak}%
+    \begin{quote}
+      \par\noindent
+      \hyphenpenalty=\@M \rightskip=\z@\@plus\linewidth\relax
+      \lst@BubbleSort#1%
+      \expandafter\lst@NextLanguage#1\relax(\relax),%
+    \end{quote}}
+\def\lst@NextLanguage#1(#2),{%
+    \ifx\relax#1\else
+        \def\lst@language{#1}\def\lst@dialects{(#2),}%
+        \expandafter\lst@NextLanguage@
+    \fi}
+\def\lst@NextLanguage@#1(#2),{%
+    \def\lst@temp{#1}%
+    \ifx\lst@temp\lst@language
+        \lst@lAddTo\lst@dialects{(#2),}%
+        \expandafter\lst@NextLanguage@
+    \else
+        \do{\lst@language
+        \ifx\lst@dialects\lst@emptydialect\else
+            \expandafter\lst@NormedDef\expandafter\lst@language
+                \expandafter{\lst@language}%
+            \space(%
+            \lst@BubbleSort\lst@dialects
+            \expandafter\lst@PrintDialects\lst@dialects(\relax),%
+            )%
+        \fi}%
+        \def\lst@next{\lst@NextLanguage#1(#2),}%
+        \expandafter\lst@next
+    \fi}
+\def\lst@emptydialect{(),}
+\def\lst@PrintDialects(#1),{%
+    \ifx\@empty#1\@empty empty\else
+        \lst@PrintDialect{#1}%
+    \fi
+    \lst@PrintDialects@}
+\def\lst@PrintDialects@(#1),{%
+    \ifx\relax#1\else
+        , \lst@PrintDialect{#1}%
+        \expandafter\lst@PrintDialects@
+    \fi}
+\def\lst@PrintDialect#1{%
+    \lst@NormedDef\lst@temp{#1}%
+    \expandafter\ifx\csname\@lst dd@\lst@language\endcsname\lst@temp
+        \texttt{\underbar{#1}}%
+    \else
+        \texttt{#1}%
+    \fi}
+\def\lst@IfLE#1#2\@empty#3#4\@empty{%
+    \ifx #1\relax
+        \let\lst@next\@firstoftwo
+    \else \ifx #3\relax
+        \let\lst@next\@secondoftwo
+    \else
+        \lowercase{\ifx#1#3}%
+            \def\lst@next{\lst@IfLE#2\@empty#4\@empty}%
+        \else
+            \lowercase{\ifnum`#1<`#3}\relax
+                \let\lst@next\@firstoftwo
+            \else
+                \let\lst@next\@secondoftwo
+            \fi
+        \fi
+    \fi \fi
+    \lst@next}
+\def\lst@BubbleSort#1{%
+    \ifx\@empty#1\else
+        \lst@false
+        \expandafter\lst@BubbleSort@#1\relax,\relax,%
+        \expandafter\lst@BubbleSort@\expandafter,\lst@sorted
+                                      \relax,\relax,%
+        \let#1\lst@sorted
+        \lst@if
+            \def\lst@next{\lst@BubbleSort#1}%
+            \expandafter\expandafter\expandafter\lst@next
+        \fi
+    \fi}
+\def\lst@BubbleSort@#1,#2,{%
+    \ifx\@empty#1\@empty
+        \def\lst@sorted{#2,}%
+        \def\lst@next{\lst@BubbleSort@@}%
+    \else
+        \let\lst@sorted\@empty
+        \def\lst@next{\lst@BubbleSort@@#1,#2,}%
+    \fi
+    \lst@next}
+\def\lst@BubbleSort@@#1,#2,{%
+    \ifx\relax#1\else
+        \ifx\relax#2%
+            \lst@lAddTo\lst@sorted{#1,}%
+            \expandafter\expandafter\expandafter\lst@BubbleSort@@@
+        \else
+            \lst@IfLE #1\relax\@empty #2\relax\@empty
+                          {\lst@lAddTo\lst@sorted{#1,#2,}}%
+                {\lst@true \lst@lAddTo\lst@sorted{#2,#1,}}%
+            \expandafter\expandafter\expandafter\lst@BubbleSort@@
+        \fi
+    \fi}
+\def\lst@BubbleSort@@@#1\relax,{}
+\endinput
+%%
+%% End of file `lstdoc.sty'.
diff --git a/dataflow/manual/lstlang1.sty b/dataflow/manual/lstlang1.sty
new file mode 100644
index 0000000..82c3602
--- /dev/null
+++ b/dataflow/manual/lstlang1.sty
@@ -0,0 +1,1615 @@
+%%
+%% This is file `lstlang1.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% lstdrvrs.dtx  (with options: `lang1')
+%% 
+%% The listings package is copyright 1996--2004 Carsten Heinz, and
+%% continued maintenance on the package is copyright 2006--2007 Brooks
+%% Moses. From 2013 on the maintenance is done by Jobst Hoffmann.
+%% The drivers are copyright 1997/1998/1999/2000/2001/2002/2003/2004/2006/
+%% 2007/2013 any individual author listed in this file.
+%%
+%% This file is distributed under the terms of the LaTeX Project Public
+%% License from CTAN archives in directory  macros/latex/base/lppl.txt.
+%% Either version 1.3 or, at your option, any later version.
+%%
+%% This file is completely free and comes without any warranty.
+%%
+%% Send comments and ideas on the package, error reports and additional
+%% programming languages to Jobst Hoffmann at <j.hoffmann@fh-aachen.de>.
+%%
+\ProvidesFile{lstlang1.sty}
+    [2015/06/04 1.6 listings language file]
+%%
+%% ACSL definition (c) 2000 by Andreas Matthias
+%%
+\lst@definelanguage{ACSL}[90]{Fortran}%
+   {morekeywords={algorithm,cinterval,constant,derivative,discrete,%
+         dynamic,errtag,initial,interval,maxterval,minterval,%
+         merror,xerror,nsteps,procedural,save,schedule,sort,%
+         table,terminal,termt,variable},%
+    sensitive=false,%
+    morecomment=[l]!%
+   }[keywords, comments]%
+%%
+%% Ada 95 definition (c) Torsten Neuer
+%%
+%% Ada 2005 definition (c) 2006 Santiago Urue\~{n}a Pascual
+%%                              <Santiago.Uruena@upm.es>
+%%
+\lst@definelanguage[2005]{Ada}[95]{Ada}%
+  {morekeywords={interface,overriding,synchronized}}%
+\lst@definelanguage[95]{Ada}[83]{Ada}%
+  {morekeywords={abstract,aliased,protected,requeue,tagged,until}}%
+\lst@definelanguage[83]{Ada}%
+  {morekeywords={abort,abs,accept,access,all,and,array,at,begin,body,%
+      case,constant,declare,delay,delta,digits,do,else,elsif,end,entry,%
+      exception,exit,for,function,generic,goto,if,in,is,limited,loop,%
+      mod,new,not,null,of,or,others,out,package,pragma,private,%
+      procedure,raise,range,record,rem,renames,return,reverse,select,%
+      separate,subtype,task,terminate,then,type,use,when,while,with,%
+      xor},%
+   sensitive=f,%
+   morecomment=[l]--,%
+   morestring=[m]",% percent not defined as stringizer so far
+   morestring=[m]'%
+  }[keywords,comments,strings]%
+%%
+%% awk definitions (c) Christoph Giess
+%%
+\lst@definelanguage[gnu]{Awk}[POSIX]{Awk}%
+  {morekeywords={and,asort,bindtextdomain,compl,dcgettext,gensub,%
+      lshift,mktime,or,rshift,strftime,strtonum,systime,xor,extension}%
+  }%
+\lst@definelanguage[POSIX]{Awk}%
+  {keywords={BEGIN,END,close,getline,next,nextfile,print,printf,%
+      system,fflush,atan2,cos,exp,int,log,rand,sin,sqrt,srand,gsub,%
+      index,length,match,split,sprintf,strtonum,sub,substr,tolower,%
+      toupper,if,while,do,for,break,continue,delete,exit,function,%
+      return},%
+   sensitive,%
+   morecomment=[l]\#,%
+   morecomment=[l]//,%
+   morecomment=[s]{/*}{*/},%
+   morestring=[b]"%
+  }[keywords,comments,strings]%
+%%
+%% Visual Basic definition (c) 2002 Robert Frank
+%%
+\lst@definelanguage[Visual]{Basic}
+  {morekeywords={Abs,Array,Asc,AscB,AscW,Atn,Avg,CBool,CByte,CCur,%
+      CDate,CDbl,Cdec,Choose,Chr,ChrB,ChrW,CInt,CLng,Command,Cos,%
+      Count,CreateObject,CSng,CStr,CurDir,CVar,CVDate,CVErr,Date,%
+      DateAdd,DateDiff,DatePart,DateSerial,DateValue,Day,DDB,Dir,%
+      DoEvents,Environ,EOF,Error,Exp,FileAttr,FileDateTime,FileLen,%
+      Fix,Format,FreeFile,FV,GetAllStrings,GetAttr,%
+      GetAutoServerSettings,GetObject,GetSetting,Hex,Hour,IIf,%
+      IMEStatus,Input,InputB,InputBox,InStr,InstB,Int,Integer,IPmt,%
+      IsArray,IsDate,IsEmpty,IsError,IsMissing,IsNull,IsNumeric,%
+      IsObject,LBound,LCase,Left,LeftB,Len,LenB,LoadPicture,Loc,LOF,%
+      Log,Ltrim,Max,Mid,MidB,Min,Minute,MIRR,Month,MsgBox,Now,NPer,%
+      NPV,Oct,Partition,Pmt,PPmt,PV,QBColor,Rate,RGB,Right,RightB,Rnd,%
+      Rtrim,Second,Seek,Sgn,Shell,Sin,SLN,Space,Spc,Sqr,StDev,StDevP,%
+      Str,StrComp,StrConv,String,Switch,Sum,SYD,Tab,Tan,Time,Timer,%
+      TimeSerial,TimeValue,Trim,TypeName,UBound,Ucase,Val,Var,VarP,%
+      VarType,Weekday,Year},% functions
+   morekeywords=[2]{Accept,Activate,Add,AddCustom,AddFile,AddFromFile,%
+      AddFromTemplate,AddItem,AddNew,AddToAddInToolbar,%
+      AddToolboxProgID,Append,AppendChunk,Arrange,Assert,AsyncRead,%
+      BatchUpdate,BeginTrans,Bind,Cancel,CancelAsyncRead,CancelBatch,%
+      CancelUpdate,CanPropertyChange,CaptureImage,CellText,CellValue,%
+      Circle,Clear,ClearFields,ClearSel,ClearSelCols,Clone,Close,Cls,%
+      ColContaining,ColumnSize,CommitTrans,CompactDatabase,Compose,%
+      Connect,Copy,CopyQueryDef,CreateDatabase,CreateDragImage,%
+      CreateEmbed,CreateField,CreateGroup,CreateIndex,CreateLink,%
+      CreatePreparedStatement,CreatePropery,CreateQuery,%
+      CreateQueryDef,CreateRelation,CreateTableDef,CreateUser,%
+      CreateWorkspace,Customize,Delete,DeleteColumnLabels,%
+      DeleteColumns,DeleteRowLabels,DeleteRows,DoVerb,Drag,Draw,Edit,%
+      EditCopy,EditPaste,EndDoc,EnsureVisible,EstablishConnection,%
+      Execute,ExtractIcon,Fetch,FetchVerbs,Files,FillCache,Find,%
+      FindFirst,FindItem,FindLast,FindNext,FindPrevious,Forward,%
+      GetBookmark,GetChunk,GetClipString,GetData,GetFirstVisible,%
+      GetFormat,GetHeader,GetLineFromChar,GetNumTicks,GetRows,%
+      GetSelectedPart,GetText,GetVisibleCount,GoBack,GoForward,Hide,%
+      HitTest,HoldFields,Idle,InitializeLabels,InsertColumnLabels,%
+      InsertColumns,InsertObjDlg,InsertRowLabels,InsertRows,Item,%
+      KillDoc,Layout,Line,LinkExecute,LinkPoke,LinkRequest,LinkSend,%
+      Listen,LoadFile,LoadResData,LoadResPicture,LoadResString,%
+      LogEvent,MakeCompileFile,MakeReplica,MoreResults,Move,MoveData,%
+      MoveFirst,MoveLast,MoveNext,MovePrevious,NavigateTo,NewPage,%
+      NewPassword,NextRecordset,OLEDrag,OnAddinsUpdate,OnConnection,%
+      OnDisconnection,OnStartupComplete,Open,OpenConnection,%
+      OpenDatabase,OpenQueryDef,OpenRecordset,OpenResultset,OpenURL,%
+      Overlay,PaintPicture,Paste,PastSpecialDlg,PeekData,Play,Point,%
+      PopulatePartial,PopupMenu,Print,PrintForm,PropertyChanged,Pset,%
+      Quit,Raise,RandomDataFill,RandomFillColumns,RandomFillRows,%
+      rdoCreateEnvironment,rdoRegisterDataSource,ReadFromFile,%
+      ReadProperty,Rebind,ReFill,Refresh,RefreshLink,RegisterDatabase,%
+      Reload,Remove,RemoveAddInFromToolbar,RemoveItem,Render,%
+      RepairDatabase,Reply,ReplyAll,Requery,ResetCustom,%
+      ResetCustomLabel,ResolveName,RestoreToolbar,Resync,Rollback,%
+      RollbackTrans,RowBookmark,RowContaining,RowTop,Save,SaveAs,%
+      SaveFile,SaveToFile,SaveToolbar,SaveToOle1File,Scale,ScaleX,%
+      ScaleY,Scroll,Select,SelectAll,SelectPart,SelPrint,Send,%
+      SendData,Set,SetAutoServerSettings,SetData,SetFocus,SetOption,%
+      SetSize,SetText,SetViewport,Show,ShowColor,ShowFont,ShowHelp,%
+      ShowOpen,ShowPrinter,ShowSave,ShowWhatsThis,SignOff,SignOn,Size,%
+      Span,SplitContaining,StartLabelEdit,StartLogging,Stop,%
+      Synchronize,TextHeight,TextWidth,ToDefaults,TwipsToChartPart,%
+      TypeByChartType,Update,UpdateControls,UpdateRecord,UpdateRow,%
+      Upto,WhatsThisMode,WriteProperty,ZOrder},% methods
+   morekeywords=[3]{AccessKeyPress,AfterAddFile,AfterChangeFileName,%
+      AfterCloseFile,AfterColEdit,AfterColUpdate,AfterDelete,%
+      AfterInsert,AfterLabelEdit,AfterRemoveFile,AfterUpdate,%
+      AfterWriteFile,AmbienChanged,ApplyChanges,Associate,%
+      AsyncReadComplete,AxisActivated,AxisLabelActivated,%
+      AxisLabelSelected,AxisLabelUpdated,AxisSelected,%
+      AxisTitleActivated,AxisTitleSelected,AxisTitleUpdated,%
+      AxisUpdated,BeforeClick,BeforeColEdit,BeforeColUpdate,%
+      BeforeConnect,BeforeDelete,BeforeInsert,BeforeLabelEdit,%
+      BeforeLoadFile,BeforeUpdate,ButtonClick,ButtonCompleted,%
+      ButtonGotFocus,ButtonLostFocus,Change,ChartActivated,%
+      ChartSelected,ChartUpdated,Click,ColEdit,Collapse,ColResize,%
+      ColumnClick,Compare,ConfigChageCancelled,ConfigChanged,%
+      ConnectionRequest,DataArrival,DataChanged,DataUpdated,DblClick,%
+      Deactivate,DeviceArrival,DeviceOtherEvent,DeviceQueryRemove,%
+      DeviceQueryRemoveFailed,DeviceRemoveComplete,DeviceRemovePending,%
+      DevModeChange,Disconnect,DisplayChanged,Dissociate,%
+      DoGetNewFileName,Done,DonePainting,DownClick,DragDrop,DragOver,%
+      DropDown,EditProperty,EnterCell,EnterFocus,Event,ExitFocus,%
+      Expand,FootnoteActivated,FootnoteSelected,FootnoteUpdated,%
+      GotFocus,HeadClick,InfoMessage,Initialize,IniProperties,%
+      ItemActivated,ItemAdded,ItemCheck,ItemClick,ItemReloaded,%
+      ItemRemoved,ItemRenamed,ItemSeletected,KeyDown,KeyPress,KeyUp,%
+      LeaveCell,LegendActivated,LegendSelected,LegendUpdated,%
+      LinkClose,LinkError,LinkNotify,LinkOpen,Load,LostFocus,%
+      MouseDown,MouseMove,MouseUp,NodeClick,ObjectMove,%
+      OLECompleteDrag,OLEDragDrop,OLEDragOver,OLEGiveFeedback,%
+      OLESetData,OLEStartDrag,OnAddNew,OnComm,Paint,PanelClick,%
+      PanelDblClick,PathChange,PatternChange,PlotActivated,%
+      PlotSelected,PlotUpdated,PointActivated,PointLabelActivated,%
+      PointLabelSelected,PointLabelUpdated,PointSelected,%
+      PointUpdated,PowerQuerySuspend,PowerResume,PowerStatusChanged,%
+      PowerSuspend,QueryChangeConfig,QueryComplete,QueryCompleted,%
+      QueryTimeout,QueryUnload,ReadProperties,Reposition,%
+      RequestChangeFileName,RequestWriteFile,Resize,ResultsChanged,%
+      RowColChange,RowCurrencyChange,RowResize,RowStatusChanged,%
+      SelChange,SelectionChanged,SendComplete,SendProgress,%
+      SeriesActivated,SeriesSelected,SeriesUpdated,SettingChanged,%
+      SplitChange,StateChanged,StatusUpdate,SysColorsChanged,%
+      Terminate,TimeChanged,TitleActivated,TitleSelected,%
+      TitleActivated,UnboundAddData,UnboundDeleteRow,%
+      UnboundGetRelativeBookmark,UnboundReadData,UnboundWriteData,%
+      Unload,UpClick,Updated,Validate,ValidationError,WillAssociate,%
+      WillChangeData,WillDissociate,WillExecute,WillUpdateRows,%
+      WithEvents,WriteProperties},% VB-events
+   morekeywords=[4]{AppActivate,Base,Beep,Call,Case,ChDir,ChDrive,%
+      Const,Declare,DefBool,DefByte,DefCur,DefDate,DefDbl,DefDec,%
+      DefInt,DefLng,DefObj,DefSng,DefStr,Deftype,DefVar,DeleteSetting,%
+      Dim,Do,Else,ElseIf,End,Enum,Erase,Event,Exit,Explicit,FileCopy,%
+      For,ForEach,Friend,Function,Get,GoSub,GoTo,If,Implements,Kill,%
+      Let,LineInput,Lock,Lset,MkDir,Name,Next,OnError,On,Option,%
+      Private,Property,Public,Put,RaiseEvent,Randomize,ReDim,Rem,%
+      Reset,Resume,Return,RmDir,Rset,SavePicture,SaveSetting,%
+      SendKeys,SetAttr,Static,Sub,Then,Type,Unlock,Wend,While,Width,%
+      With,Write},% statements
+   sensitive=false,%
+   keywordcomment=rem,%
+   MoreSelectCharTable=\def\lst@BeginKC@{% chmod
+      \lst@ResetToken
+      \lst@BeginComment\lst@GPmode{{\lst@commentstyle}%
+                       \lst@Lmodetrue\lst@modetrue}\@empty},%
+   morecomment=[l]{'},%
+   morecomment=[s]{/*}{*/},%
+   morestring=[b]",%
+   }[keywords,comments,strings,keywordcomments]
+\lst@definelanguage[11]{C++}[ISO]{C++}%
+  {morekeywords={alignas,alignof,char16_t,char32_t,constexpr,%
+      decltype,noexcept,nullptr,static_assert,thread_local},%
+  }%
+\lst@definelanguage[ANSI]{C++}[ISO]{C++}{}%
+\lst@definelanguage[GNU]{C++}[ISO]{C++}%
+  {morekeywords={__attribute__,__extension__,__restrict,__restrict__,%
+      typeof,__typeof__},%
+  }%
+\lst@definelanguage[Visual]{C++}[ISO]{C++}%
+  {morekeywords={__asm,__based,__cdecl,__declspec,dllexport,%
+      dllimport,__except,__fastcall,__finally,__inline,__int8,__int16,%
+      __int32,__int64,naked,__stdcall,thread,__try,__leave},%
+  }%
+\lst@definelanguage[ISO]{C++}[ANSI]{C}%
+  {morekeywords={and,and_eq,asm,bad_cast,bad_typeid,bitand,bitor,bool,%
+      catch,class,compl,const_cast,delete,dynamic_cast,explicit,export,%
+      false,friend,inline,mutable,namespace,new,not,not_eq,operator,or,%
+      or_eq,private,protected,public,reinterpret_cast,static_cast,%
+      template,this,throw,true,try,typeid,type_info,typename,using,%
+      virtual,wchar_t,xor,xor_eq},%
+  }%
+%%
+%% Objective-C definition (c) 1997 Detlev Droege
+%%
+\lst@definelanguage[Objective]{C}[ANSI]{C}
+  {morekeywords={bycopy,id,in,inout,oneway,out,self,super,%
+      @class,@defs,@encode,@end,@implementation,@interface,@private,%
+      @protected,@protocol,@public,@selector},%
+   moredirectives={import}%
+  }%
+%%
+%% Handel-C definition, refer http://www.celoxica.com
+%%
+\lst@definelanguage[Handel]{C}[ANSI]{C}
+  {morekeywords={assert,chan,chanin,chanout,clock,delay,expr,external,%
+      external_divide,family,ifselect,in,inline,interface,internal,%
+      internal_divid,intwidth,let,macro,mpram,par,part,prialt,proc,ram,%
+      releasesema,reset,rom,select,sema,set,seq,shared,signal,try,%
+      reset,trysema,typeof,undefined,width,with,wom},%
+  }%
+\lst@definelanguage[ANSI]{C}%
+  {morekeywords={auto,break,case,char,const,continue,default,do,double,%
+      else,enum,extern,float,for,goto,if,int,long,register,return,%
+      short,signed,sizeof,static,struct,switch,typedef,union,unsigned,%
+      void,volatile,while},%
+   sensitive,%
+   morecomment=[s]{/*}{*/},%
+   morecomment=[l]//,% nonstandard
+   morestring=[b]",%
+   morestring=[b]',%
+   moredelim=*[directive]\#,%
+   moredirectives={define,elif,else,endif,error,if,ifdef,ifndef,line,%
+      include,pragma,undef,warning}%
+  }[keywords,comments,strings,directives]%
+%%
+%% C-Sharp definition (c) 2002 Martin Brodbeck
+%%
+\lst@definelanguage[Sharp]{C}%
+  {morekeywords={abstract,base,bool,break,byte,case,catch,char,checked,%
+      class,const,continue,decimal,default,delegate,do,double,else,%
+      enum,event,explicit,extern,false,finally,fixed,float,for,foreach,%
+      goto,if,implicit,in,int,interface,internal,is,lock,long,%
+      namespace,new,null,object,operator,out,override,params,private,%
+      protected,public,readonly,ref,return,sbyte,sealed,short,sizeof,%
+      static,string,struct,switch,this,throw,true,try,typeof,uint,%
+      ulong,unchecked,unsafe,ushort,using,virtual,void,while,%
+      as,volatile,stackalloc},% Kai K\"ohne
+   sensitive,%
+   morecomment=[s]{/*}{*/},%
+   morecomment=[l]//,%
+   morestring=[b]"
+  }[keywords,comments,strings]%
+%%
+%% csh definition (c) 1998 Kai Below
+%%
+\lst@definelanguage{csh}
+  {morekeywords={alias,awk,cat,echo,else,end,endif,endsw,exec,exit,%
+      foreach,glob,goto,history,if,logout,nice,nohup,onintr,repeat,sed,%
+      set,setenv,shift,source,switch,then,time,while,umask,unalias,%
+      unset,wait,while,@,env,argv,child,home,ignoreeof,noclobber,%
+      noglob,nomatch,path,prompt,shell,status,verbose,print,printf,%
+      sqrt,BEGIN,END},%
+   morecomment=[l]\#,%
+   morestring=[d]"%
+  }[keywords,comments,strings]%
+%%
+%% bash,sh definition (c) 2003 Riccardo Murri <riccardo.murri@gmx.it>
+%%
+\lst@definelanguage{bash}[]{sh}%
+  {morekeywords={alias,bg,bind,builtin,caller,command,compgen,compopt,%
+      complete,coproc,declare,disown,dirs,enable,fc,fg,help,history,%
+      jobs,let,local,logout,mapfile,printf,pushd,popd,readarray,select,%
+      set,suspend,shopt,source,times,type,typeset,ulimit,unalias,wait},%
+  }%
+\lst@definelanguage{sh}%
+  {morekeywords={awk,break,case,cat,cd,continue,do,done,echo,elif,else,%
+      env,esac,eval,exec,exit,export,expr,false,fi,for,function,getopts,%
+      hash,history,if,in,kill,login,newgrp,nice,nohup,ps,pwd,read,%
+      readonly,return,set,sed,shift,test,then,times,trap,true,type,%
+      ulimit,umask,unset,until,wait,while},%
+   morecomment=[l]\#,%
+   morestring=[d]"%
+  }[keywords,comments,strings]%
+\lst@definelanguage[08]{Fortran}[03]{Fortran}{%
+  morekeywords={ALL, BLOCK, CODIMENSION, CONCURRENT, CONTIGUOUS, CRITICAL,%
+    ERROR, LOCK, SUBMODULE, SYNC, UNLOCK},%
+    morekeywords=[3]{ACOSH,ASINH,ATANH,ATOMIC_DEFINE,ATOMIC_REF,BESSEL_J0,%
+      BESSEL_J1,BESSEL_JN,BESSEL_Y0,BESSEL_Y1,BESSEL_YN,BGE,BGT,BLE,BLT,%
+      C_SIZEOF,COMPILER_OPTIONS,COMPILER_VERSION,DSHIFTL,DSHIFTR,ERF,ERFC,%
+      ERFC_SCALED,EXECUTE_COMMAND_LINE,GAMMA,HYPOT,IALL,IANY,IMAGE_INDEX,%
+      IPARITY,LCOBOUND,LEADZ,LOG_GAMMA,MASKL,MASKR,MERGE_BITS,NORM2,%
+      NUM_IMAGES,PARITY,POPCNT,POPPAR,SHIFTA,SHIFTL,SHIFTR,STORAGE_SIZE,%
+      THIS_IMAGE,TRAILZ,UCOBOUND}%
+}%
+\lst@definelanguage[03]{Fortran}[95]{Fortran}{%
+  morekeywords={ABSTRACT, ASSOCIATE, ASYNCHRONOUS, BIND, CLASS, DEFERRED,%
+    ENUM, ENUMERATOR, EXTENDS, FINAL, FLUSH, GENERIC, IMPORT,%
+    NON_OVERRIDABLE, NOPASS, PASS, PROTECTED, VALUE, VOLATILE, WAIT},%
+    morekeywords=[2]{DECIMAL,ENCODING,IOMSG,ROUND},% corrected NML from NMT
+    morekeywords=[3]{C_ASSOCIATED,C_F_POINTER,C_F_PROCPOINTER,C_FUNLOC,%
+    C_LOC,COMMAND_ARGUMENT_COUNT,EXTENDS_TYPE_OF,GET_COMMAND,GET_COMMAND_ARGUMENT,%
+    GET_ENVIRONMENT_VARIABLE,IS_IOSTAT_END,MOVE_ALLOC,NEW_LINE,SAME_TYPE_AS,%
+    SELECTED_CHAR_KIND}%
+}%
+\lst@definelanguage[90]{Fortran}[95]{Fortran}{}
+\lst@definelanguage[95]{Fortran}[77]{Fortran}%
+  {deletekeywords=SAVE,%
+   morekeywords={ALLOCATABLE,ALLOCATE,ASSIGNMENT,CASE,%
+      CONTAINS,CYCLE,DEALLOCATE,DEFAULT,EXIT,INCLUDE,IN,NONE,%
+      OUT,INTENT,INTERFACE,MODULE,NAMELIST,%
+      NULLIFY,ONLY,OPERATOR,OPTIONAL,OUT,POINTER,PRIVATE,%
+      PUBLIC,RECURSIVE,RESULT,SELECT,SEQUENCE,%
+      TARGET,USE,WHERE,WHILE,BLOCKDATA,DOUBLEPRECISION,%
+      ENDBLOCKDATA,ENDFILE,ENDFUNCTION,ENDINTERFACE,%
+      ENDMODULE,ENDPROGRAM,ENDSELECT,ENDSUBROUTINE,ENDTYPE,ENDWHERE,%
+      INOUT,SELECTCASE,%
+      ELEMENTAL, ELSEWHERE, FORALL, PURE},%
+    morekeywords=[2]{ACTION,ADVANCE,DELIM,IOLENGTH,LEN,NAME,%
+      NML,PAD,POSITION,READWRITE,SIZE,STAT},% corrected NML from NMT
+    morekeywords=[3]{ADJUSTL,ADJUSTR,ALL,ALLOCATED,ANY,ASSOCIATED,BIT_SIZE,%
+    BTEST,CEILING,COUNT,CPU_TIME,CSHIFT,DATE_AND_TIME,DIGITS,DOT_PRODUCT,%
+    EOSHIFT,EPSILON,EXPONENT,FLOOR,FRACTION,HUGE,IACHAR,IAND,IBCLR,
+    IBITS,IBSET,ICHAR,IEOR,IOR,ISHFT,ISHFTC,KIND,LBOUND,LEN_TRIM,% left out LOGICAL
+    MATMUL,MAXEXPONENT,MAXLOC,MAXVAL,MERGE,MINEXPONENT,MINLOC,MINVAL,%
+    MODULO,MVBITS,NEAREST,NOT,NULL,PACK,PRECISION,PRESENT,PRODUCT,%
+    RADIX,RANDOM_NUMBER,RANDOM_SEED,RANGE,RANK,REPEAT,RESHAPE,RRSPACING,%
+    SCALE,SCAN,SELECTED_INT_KIND,SELECTED_REAL_KIND,SET_EXPONENT,SHAPE,%
+    SINH,SIZE,SPACING,SPREAD,SUM,SYSTEM_CLOCK,TINY,TRANSFER,TRANSPOSE,%
+    TRIM,UBOUND,UNPACK,VERIFY},%
+   deletecomment=[f],% no fixed comment line: 1998 Magne Rudshaug
+   morecomment=[l]!%
+  }%
+\lst@definelanguage[77]{Fortran}%
+  {morekeywords={ASSIGN,BACKSPACE,CALL,CHARACTER,%
+      CLOSE,COMMON,COMPLEX,CONTINUE,DATA,DIMENSION,DO,DOUBLE,%
+      ELSE,ELSEIF,END,ENDIF,ENDDO,ENTRY,EQUIVALENCE,EXTERNAL,%
+      FILE,FORMAT,FUNCTION,GO,TO,GOTO,IF,IMPLICIT,%
+      INQUIRE,INTEGER,INTRINSIC,LOGICAL,%
+      OPEN,PARAMETER,PAUSE,PRECISION,PRINT,PROGRAM,READ,REAL,%
+      RETURN,REWIND,STOP,SUBROUTINE,THEN,%
+      WRITE,SAVE},%
+    morekeywords=[2]{ACCESS,BLANK,BLOCK,DIRECT,EOF,ERR,EXIST,%
+      FMT,FORM,FORMATTED,IOSTAT,NAMED,NEXTREC,NUMBER,OPENED,%
+      REC,RECL,SEQUENTIAL,STATUS,TYPE,UNFORMATTED,UNIT},%
+    morekeywords=[3]{INT,DBLE,CMPLX,ICHAR,CHAR,AINT,ANINT,% left out real
+      NINT,ABS,MOD,SIGN,DIM,DPROD,MAX,MIN,AIMAG,CONJG,SQRT,EXP,LOG,%
+      LOG10,SIN,COS,TAN,ASIN,ACOS,ATAN,ATAN2,SINH,COSH,TANH,LGE,LLE,LLT,%
+      LEN,INDEX},%
+    morekeywords=[4]{AND,EQ,EQV,FALSE,GE,GT,OR,LE,LT,NE,NEQV,NOT,TRUE},%
+   sensitive=f,%% not Fortran-77 standard, but allowed in Fortran-95 %%
+   morecomment=[f]*,%
+   morecomment=[f]C,%
+   morecomment=[f]c,%
+   morestring=[d]",%% not Fortran-77 standard, but allowed in Fortran-95 %%
+   morestring=[d]'%
+  }[keywords,comments,strings]%
+\lst@definelanguage{HTML}%
+  {morekeywords={A,ABBR,ACRONYM,ADDRESS,APPLET,AREA,B,BASE,BASEFONT,%
+      BDO,BIG,BLOCKQUOTE,BODY,BR,BUTTON,CAPTION,CENTER,CITE,CODE,COL,%
+      COLGROUP,DD,DEL,DFN,DIR,DIV,DL,DOCTYPE,DT,EM,FIELDSET,FONT,FORM,%
+      FRAME,FRAMESET,HEAD,HR,H1,H2,H3,H4,H5,H6,HTML,I,IFRAME,IMG,INPUT,%
+      INS,ISINDEX,KBD,LABEL,LEGEND,LH,LI,LINK,LISTING,MAP,META,MENU,%
+      NOFRAMES,NOSCRIPT,OBJECT,OPTGROUP,OPTION,P,PARAM,PLAINTEXT,PRE,%
+      OL,Q,S,SAMP,SCRIPT,SELECT,SMALL,SPAN,STRIKE,STRING,STRONG,STYLE,%
+      SUB,SUP,TABLE,TBODY,TD,TEXTAREA,TFOOT,TH,THEAD,TITLE,TR,TT,U,UL,%
+      VAR,XMP,%
+      accesskey,action,align,alink,alt,archive,axis,background,bgcolor,%
+      border,cellpadding,cellspacing,charset,checked,cite,class,classid,%
+      code,codebase,codetype,color,cols,colspan,content,coords,data,%
+      datetime,defer,disabled,dir,event,error,for,frameborder,headers,%
+      height,href,hreflang,hspace,http-equiv,id,ismap,label,lang,link,%
+      longdesc,marginwidth,marginheight,maxlength,media,method,multiple,%
+      name,nohref,noresize,noshade,nowrap,onblur,onchange,onclick,%
+      ondblclick,onfocus,onkeydown,onkeypress,onkeyup,onload,onmousedown,%
+      profile,readonly,onmousemove,onmouseout,onmouseover,onmouseup,%
+      onselect,onunload,rel,rev,rows,rowspan,scheme,scope,scrolling,%
+      selected,shape,size,src,standby,style,tabindex,text,title,type,%
+      units,usemap,valign,value,valuetype,vlink,vspace,width,xmlns},%
+   tag=**[s]<>,%
+   sensitive=f,%
+   morestring=[d]",% ??? doubled
+   MoreSelectCharTable=%
+      \lst@CArgX--\relax\lst@DefDelimB{}{}%
+          {\ifnum\lst@mode=\lst@tagmode\else
+               \expandafter\@gobblethree
+           \fi}%
+          \lst@BeginComment\lst@commentmode{{\lst@commentstyle}}%
+      \lst@CArgX--\relax\lst@DefDelimE{}{}{}%
+          \lst@EndComment\lst@commentmode
+  }[keywords,comments,strings,html]%
+%%
+%% AspectJ definition (c) Robert Wenner
+%%
+\lst@definelanguage[AspectJ]{Java}[]{Java}%
+  {morekeywords={%
+      adviceexecution,after,args,around,aspect,aspectOf,before,%
+      call,cflow,cflowbelow,%
+      execution,get,handler,if,initialization,issingleton,pointcut,%
+      percflow,percflowbelow,perthis,pertarget,preinitialization,%
+      privileged,proceed,returning,set,staticinitialization,strictfp,%
+      target,this,thisEnclosingJoinPoint,thisJoinPoint,throwing,%
+      within,withincode},%
+   MoreSelectCharTable=%
+     \lst@DefSaveDef{`.}\lst@umdot{\lst@umdot\global\let\lst@derefop\@empty}%
+     \ifx\lst@derefinstalled\@empty\else
+        \global\let\lst@derefinstalled\@empty
+\lst@AddToHook{Output}%
+{\lst@ifkeywords
+    \ifx\lst@derefop\@empty
+       \global\let\lst@derefop\relax
+       \ifx\lst@thestyle\lst@gkeywords@sty
+          \ifx\lst@currstyle\relax
+             \let\lst@thestyle\lst@identifierstyle
+          \else
+             \let\lst@thestyle\lst@currstyle
+          \fi
+       \fi
+    \fi
+ \fi}
+\lst@AddToHook{BOL}{\global\let\lst@derefop\relax}%
+\lst@AddTo\lst@ProcessSpace{\global\let\lst@derefop\relax}%
+     \fi
+  }%
+\lst@definelanguage{Java}%
+  {morekeywords={abstract,boolean,break,byte,case,catch,char,class,%
+      const,continue,default,do,double,else,extends,false,final,%
+      finally,float,for,goto,if,implements,import,instanceof,int,%
+      interface,label,long,native,new,null,package,private,protected,%
+      public,return,short,static,super,switch,synchronized,this,throw,%
+      throws,transient,true,try,void,volatile,while},%
+   sensitive,%
+   morecomment=[l]//,%
+   morecomment=[s]{/*}{*/},%
+   morestring=[b]",%
+   morestring=[b]',%
+  }[keywords,comments,strings]%
+%%
+%% ByteCodeJava definition (c) 2004 Martine Gautier
+%%
+\lst@definelanguage{JVMIS}%
+  {morekeywords={aaload,astore,aconst_null,aload,aload_0,aload_1,%
+      aload_2,aload_3,anewarray,areturn,arraylength,astore,astore_0,%
+      astore_1,astore_2,astore_3,athrow,baload,bastore,bipush,caload,%
+      castore,checkcast,d2f,d2i,d2l,dadd,daload,dastore,dcmpg,dcmpl,%
+      dconst_0,dconst_1,ddiv,dload,dload_0,dload_1,dload_2,dload_3,%
+      dmul,dneg,drem,dreturn,dstore,dstore_0,dstore_1,dstore_2,%
+      dstore_3,dsub,dup,dup_x1,dup_x2,dup2,dup2_x1,dup2_x2,f2d,%
+      f2i,f2l,fadd,faload,fastore,fcmpg,fcmpl,fconst_0,fconst_1,%
+      fconst_2,fdiv,fload,fload_0,fload_1,fload_2,fload_3,fmul,%
+      fneg,frem,freturn,fstore,fstore_0,fstore_1,fstore_2,fstore_3,%
+      fsub,getfield,getstatic,goto,goto_w,i2b,i2c,i2d,i2f,i2l,i2s,%
+      iadd,iaload,iand,iastore,iconst_0,iconst_1,iconst_2,iconst_3,%
+      iconst_4,iconst_5,idiv,if_acmpeq,if_acmpne,if_icmpeq,if_icmpne,%
+      if_icmplt,if_cmpge,if_cmpgt,if_cmple,ifeq,ifne,iflt,ifge,ifgt,%
+      ifle,ifnonnull,ifnull,iinc,iload,iload_0,iload_1,iload_2,%
+      iload_3,imul,ineg,instanceof,invokeinterface,invokespecial,%
+      invokestatic,invokevirtual,ior,irem,ireturn,ishl,ishr,istore,%
+      istore_0,istore_1,istore_2,istore_3,isub,iushr,ixor,jsr,jsr_w,%
+      l2d,l2f,l2i,ladd,laload,land,lastore,lcmp,lconst_0,lconst_1,%
+      ldc,ldc_w,ldc2_w,ldiv,lload,lload_0,lload_1,lload_2,lload_3,%
+      lmul,lneg,lookupswitch,lor,lrem,lreturn,lshl,lshr,lstore,%
+      lstore_0,lstore_1,lstore_2,lstore_3,lsub,lushr,lxor,%
+      monitorenter,monitorexit,multianewarray,new,newarray,nop,pop,%
+      pop2,putfield,putstatic,ret,return,saload,sastore,sipush,swap,%
+      tableswitch,wide,limit,locals,stack},%
+  }[keywords]%
+\lst@definelanguage{Matlab}%
+  {morekeywords={gt,lt,gt,lt,amp,abs,acos,acosh,acot,acoth,acsc,acsch,%
+      all,angle,ans,any,asec,asech,asin,asinh,atan,atan2,atanh,auread,%
+      auwrite,axes,axis,balance,bar,bessel,besselk,bessely,beta,%
+      betainc,betaln,blanks,bone,break,brighten,capture,cart2pol,%
+      cart2sph,caxis,cd,cdf2rdf,cedit,ceil,chol,cla,clabel,clc,clear,%
+      clf,clock,close,colmmd,Colon,colorbar,colormap,ColorSpec,colperm,%
+      comet,comet3,compan,compass,computer,cond,condest,conj,contour,%
+      contour3,contourc,contrast,conv,conv2,cool,copper,corrcoef,cos,%
+      cosh,cot,coth,cov,cplxpair,cputime,cross,csc,csch,csvread,%
+      csvwrite,cumprod,cumsum,cylinder,date,dbclear,dbcont,dbdown,%
+      dbquit,dbstack,dbstatus,dbstep,dbstop,dbtype,dbup,ddeadv,ddeexec,%
+      ddeinit,ddepoke,ddereq,ddeterm,ddeunadv,deblank,dec2hex,deconv,%
+      del2,delete,demo,det,diag,diary,diff,diffuse,dir,disp,dlmread,%
+      dlmwrite,dmperm,dot,drawnow,echo,eig,ellipj,ellipke,else,elseif,%
+      end,engClose,engEvalString,engGetFull,engGetMatrix,engOpen,%
+      engOutputBuffer,engPutFull,engPutMatrix,engSetEvalCallback,%
+      engSetEvalTimeout,engWinInit,eps,erf,erfc,erfcx,erfinv,error,%
+      errorbar,etime,etree,eval,exist,exp,expint,expm,expo,eye,fclose,%
+      feather,feof,ferror,feval,fft,fft2,fftshift,fgetl,fgets,figure,%
+      fill,fill3,filter,filter2,find,findstr,finite,fix,flag,fliplr,%
+      flipud,floor,flops,fmin,fmins,fopen,for,format,fplot,fprintf,%
+      fread,frewind,fscanf,fseek,ftell,full,function,funm,fwrite,fzero,%
+      gallery,gamma,gammainc,gammaln,gca,gcd,gcf,gco,get,getenv,%
+      getframe,ginput,global,gplot,gradient,gray,graymon,grid,griddata,%
+      gtext,hadamard,hankel,help,hess,hex2dec,hex2num,hidden,hilb,hist,%
+      hold,home,hostid,hot,hsv,hsv2rgb,if,ifft,ifft2,imag,image,%
+      imagesc,Inf,info,input,int2str,interp1,interp2,interpft,inv,%
+      invhilb,isempty,isglobal,ishold,isieee,isinf,isletter,isnan,%
+      isreal,isspace,issparse,isstr,jet,keyboard,kron,lasterr,lcm,%
+      legend,legendre,length,lin2mu,line,linspace,load,log,log10,log2,%
+      loglog,logm,logspace,lookfor,lower,ls,lscov,lu,magic,matClose,%
+      matDeleteMatrix,matGetDir,matGetFp,matGetFull,matGetMatrix,%
+      matGetNextMatrix,matGetString,matlabrc,matlabroot,matOpen,%
+      matPutFull,matPutMatrix,matPutString,max,mean,median,menu,mesh,%
+      meshc,meshgrid,meshz,mexAtExit,mexCallMATLAB,mexdebug,%
+      mexErrMsgTxt,mexEvalString,mexFunction,mexGetFull,mexGetMatrix,%
+      mexGetMatrixPtr,mexPrintf,mexPutFull,mexPutMatrix,mexSetTrapFlag,%
+      min,more,movie,moviein,mu2lin,mxCalloc,mxCopyCharacterToPtr,%
+      mxCopyComplex16ToPtr,mxCopyInteger4ToPtr,mxCopyPtrToCharacter,%
+      mxCopyPtrToComplex16,mxCopyPtrToInteger4,mxCopyPtrToReal8,%
+      mxCopyReal8ToPtr,mxCreateFull,mxCreateSparse,mxCreateString,%
+      mxFree,mxFreeMatrix,mxGetIr,mxGetJc,mxGetM,mxGetN,mxGetName,%
+      mxGetNzmax,mxGetPi,mxGetPr,mxGetScalar,mxGetString,mxIsComplex,%
+      mxIsFull,mxIsNumeric,mxIsSparse,mxIsString,mxIsTypeDouble,%
+      mxSetIr,mxSetJc,mxSetM,mxSetN,mxSetName,mxSetNzmax,mxSetPi,%
+      mxSetPr,NaN,nargchk,nargin,nargout,newplot,nextpow2,nnls,nnz,%
+      nonzeros,norm,normest,null,num2str,nzmax,ode23,ode45,orient,orth,%
+      pack,pascal,patch,path,pause,pcolor,pi,pink,pinv,plot,plot3,%
+      pol2cart,polar,poly,polyder,polyeig,polyfit,polyval,polyvalm,%
+      pow2,print,printopt,prism,prod,pwd,qr,qrdelete,qrinsert,quad,%
+      quad8,quit,quiver,qz,rand,randn,randperm,rank,rat,rats,rbbox,%
+      rcond,real,realmax,realmin,refresh,rem,reset,reshape,residue,%
+      return,rgb2hsv,rgbplot,rootobject,roots,rose,rosser,rot90,rotate,%
+      round,rref,rrefmovie,rsf2csf,save,saxis,schur,sec,sech,semilogx,%
+      semilogy,set,setstr,shading,sign,sin,sinh,size,slice,sort,sound,%
+      spalloc,sparse,spaugment,spconvert,spdiags,specular,speye,spfun,%
+      sph2cart,sphere,spinmap,spline,spones,spparms,sprandn,sprandsym,%
+      sprank,sprintf,spy,sqrt,sqrtm,sscanf,stairs,startup,std,stem,%
+      str2mat,str2num,strcmp,strings,strrep,strtok,subplot,subscribe,%
+      subspace,sum,surf,surface,surfc,surfl,surfnorm,svd,symbfact,%
+      symmmd,symrcm,tan,tanh,tempdir,tempname,terminal,text,tic,title,%
+      toc,toeplitz,trace,trapz,tril,triu,type,uicontrol,uigetfile,%
+      uimenu,uiputfile,unix,unwrap,upper,vander,ver,version,view,%
+      viewmtx,waitforbuttonpress,waterfall,wavread,wavwrite,what,%
+      whatsnew,which,while,white,whitebg,who,whos,wilkinson,wk1read,%
+      wk1write,xlabel,xor,ylabel,zeros,zlabel,zoom},%
+   sensitive,%
+   morecomment=[l]\%,%
+   morestring=[m]'%
+  }[keywords,comments,strings]%
+\lst@definelanguage[5.2]{Mathematica}[3.0]{Mathematica}%%
+  {morekeywords={Above,AbsoluteOptions,AbsoluteTiming,AccountingForm,%
+      AccuracyGoal,Active,ActiveItem,AddOnHelpPath,%
+      AdjustmentBox,AdjustmentBoxOptions,After,AiryAiPrime,%
+      AlgebraicRulesData,Algebraics,Alias,AlignmentMarker,%
+      AllowInlineCells,AllowScriptLevelChange,Analytic,AnimationCycleOffset,%
+      AnimationCycleRepetitions,AnimationDirection,AnimationDisplayTime,ApartSquareFree,%
+      AppellF1,ArgumentCountQ,ArrayDepth,ArrayPlot,%
+      ArrayQ,ArrayRules,AspectRatioFixed,Assuming,%
+      Assumptions,AutoDelete,AutoEvaluateEvents,AutoGeneratedPackage,%
+      AutoIndent,AutoIndentSpacings,AutoItalicWords,AutoloadPath,%
+      AutoOpenNotebooks,AutoOpenPalettes,AutoScroll,AutoSpacing,%
+      AutoStyleOptions,Axis,BackgroundTasksSettings,Backsubstitution,%
+      Backward,Baseline,Before,BeginDialogPacket,%
+      BeginFrontEndInteractionPacket,Below,BezoutMatrix,BinaryFormat,%
+      BinaryGet,BinaryRead,BinaryReadList,BinaryWrite,%
+      BitAnd,BitNot,BitOr,BitXor,%
+      Black,BlankForm,Blue,Boole,%
+      Booleans,Bottom,Bounds,Box,%
+      BoxBaselineShift,BoxData,BoxDimensions,BoxFormFormatTypes,%
+      BoxFrame,BoxMargins,BoxRegion,Brown,%
+      Buchberger,Button,ButtonBox,ButtonBoxOptions,%
+      ButtonCell,ButtonContents,ButtonData,ButtonEvaluator,%
+      ButtonExpandable,ButtonFrame,ButtonFunction,ButtonMargins,%
+      ButtonMinHeight,ButtonNote,ButtonNotebook,ButtonSource,%
+      ButtonStyle,ButtonStyleMenuListing,ByteOrdering,CallPacket,%
+      CarmichaelLambda,Cell,CellAutoOverwrite,CellBaseline,%
+      CellBoundingBox,CellBracketOptions,CellContents,CellDingbat,%
+      CellEditDuplicate,CellElementsBoundingBox,CellElementSpacings,CellEvaluationDuplicate,%
+      CellFrame,CellFrameColor,CellFrameLabelMargins,CellFrameLabels,%
+      CellFrameMargins,CellGroup,CellGroupData,CellGrouping,%
+      CellGroupingRules,CellHorizontalScrolling,CellLabel,CellLabelAutoDelete,%
+      CellLabelMargins,CellLabelPositioning,CellMargins,CellObject,%
+      CellOpen,CellPasswords,CellPrint,CellSize,%
+      CellStyle,CellTags,CellularAutomaton,Center,%
+      CharacterEncoding,CharacterEncodingsPath,CharacteristicPolynomial,CharacterRange,%
+      CheckAll,CholeskyDecomposition,Clip,ClipboardNotebook,%
+      Closed,ClosingAutoSave,CoefficientArrays,CoefficientDomain,%
+      CofactorExpansion,ColonForm,ColorFunctionScaling,ColorRules,%
+      ColorSelectorSettings,Column,ColumnAlignments,ColumnLines,%
+      ColumnsEqual,ColumnSpacings,ColumnWidths,CommonDefaultFormatTypes,%
+      CompileOptimizations,CompletionsListPacket,Complexes,ComplexityFunction,%
+      Compose,ComposeSeries,ConfigurationPath,ConjugateTranspose,%
+      Connect,ConsoleMessage,ConsoleMessagePacket,ConsolePrint,%
+      ContentsBoundingBox,ContextToFileName,ContinuedFraction,ConversionOptions,%
+      ConversionRules,ConvertToBitmapPacket,ConvertToPostScript,ConvertToPostScriptPacket,%
+      Copyable,CoshIntegral,CounterAssignments,CounterBox,%
+      CounterBoxOptions,CounterEvaluator,CounterFunction,CounterIncrements,%
+      CounterStyle,CounterStyleMenuListing,CreatePalettePacket,Cross,%
+      CurrentlySpeakingPacket,Cyan,CylindricalDecomposition,DampingFactor,%
+      DataRange,Debug,DebugTag,Decimal,%
+      DedekindEta,DefaultDuplicateCellStyle,DefaultFontProperties,DefaultFormatType,%
+      DefaultFormatTypeForStyle,DefaultInlineFormatType,DefaultInputFormatType,
+      DefaultNaturalLanguage,%
+      DefaultNewCellStyle,DefaultNewInlineCellStyle,DefaultNotebook,DefaultOutputFormatType,%
+      DefaultStyleDefinitions,DefaultTextFormatType,DefaultTextInlineFormatType,DefaultValues,%
+      DefineExternal,DegreeLexicographic,DegreeReverseLexicographic,Deletable,%
+      DeleteContents,DeletionWarning,DelimiterFlashTime,DelimiterMatching,%
+      Delimiters,DependentVariables,DiacriticalPositioning,DialogLevel,%
+      DifferenceOrder,DigitCharacter,DigitCount,DiracDelta,%
+      Direction,DirectoryName,DisableConsolePrintPacket,DiscreteDelta,%
+      DisplayAnimation,DisplayEndPacket,DisplayFlushImagePacket,DisplayForm,%
+      DisplayPacket,DisplayRules,DisplaySetSizePacket,DisplayString,%
+      DivisionFreeRowReduction,DOSTextFormat,DoubleExponential,DoublyInfinite,%
+      Down,DragAndDrop,DrawHighlighted,DualLinearProgramming,%
+      DumpGet,DumpSave,Edit,Editable,%
+      EditButtonSettings,EditCellTagsSettings,EditDefinition,EditIn,%
+      Element,EliminationOrder,EllipticExpPrime,EllipticNomeQ,%
+      EllipticReducedHalfPeriods,EllipticThetaPrime,Empty,EnableConsolePrintPacket,%
+      Encoding,EndAdd,EndDialogPacket,EndFrontEndInteractionPacket,%
+      EndOfLine,EndOfString,Enter,EnterExpressionPacket,%
+      EnterTextPacket,EqualColumns,EqualRows,EquatedTo,%
+      Erfi,ErrorBox,ErrorBoxOptions,ErrorNorm,%
+      ErrorPacket,ErrorsDialogSettings,Evaluatable,EvaluatePacket,%
+      EvaluationCell,EvaluationCompletionAction,EvaluationMonitor,EvaluationNotebook,%
+      Evaluator,EvaluatorNames,EventEvaluator,ExactNumberQ,%
+      ExactRootIsolation,Except,ExcludedForms,Exists,%
+      ExitDialog,ExponentPosition,ExponentStep,Export,%
+      ExportAutoReplacements,ExportPacket,ExportString,ExpressionPacket,%
+      ExpToTrig,Extension,ExternalCall,ExternalDataCharacterEncoding,%
+      Extract,Fail,FEDisableConsolePrintPacket,FEEnableConsolePrintPacket,%
+      Fibonacci,File,FileFormat,FileInformation,%
+      FileName,FileNameDialogSettings,FindFit,FindInstance,%
+      FindMaximum,FindSettings,FitAll,FlushPrintOutputPacket,%
+      Font,FontColor,FontFamily,FontName,%
+      FontPostScriptName,FontProperties,FontReencoding,FontSize,%
+      FontSlant,FontSubstitutions,FontTracking,FontVariations,%
+      FontWeight,ForAll,FormatRules,FormatTypeAutoConvert,%
+      FormatValues,FormBox,FormBoxOptions,Forward,%
+      ForwardBackward,FourierCosTransform,FourierParameters,FourierSinTransform,%
+      FourierTransform,FractionalPart,FractionBox,FractionBoxOptions,%
+      FractionLine,FrameBox,FrameBoxOptions,FresnelC,%
+      FresnelS,FromContinuedFraction,FromDigits,FrontEndExecute,%
+      FrontEndObject,FrontEndStackSize,FrontEndToken,FrontEndTokenExecute,%
+      FrontEndVersion,Full,FullAxes,FullSimplify,%
+      FunctionExpand,FunctionInterpolation,GaussKronrod,GaussPoints,%
+      GenerateBitmapCaches,GenerateConditions,GeneratedCell,GeneratedParameters,%
+      Generic,GetBoundingBoxSizePacket,GetContext,GetFileName,%
+      GetFrontEndOptionsDataPacket,GetLinebreakInformationPacket,%
+      GetMenusPacket,GetPageBreakInformationPacket,%
+      Glaisher,GlobalPreferences,GlobalSession,Gradient,%
+      GraphicsData,GraphicsGrouping,Gray,Green,%
+      Grid,GridBaseline,GridBox,GridBoxOptions,%
+      GridCreationSettings,GridDefaultElement,GridFrame,GridFrameMargins,%
+      GroupPageBreakWithin,HarmonicNumber,Hash,HashTable,%
+      HeadCompose,HelpBrowserLookup,HelpBrowserNotebook,HelpBrowserSettings,%
+      HessenbergDecomposition,Hessian,HoldAllComplete,HoldComplete,%
+      HoldPattern,Horizontal,HorizontalForm,HorizontalScrollPosition,%
+      HTMLSave,Hypergeometric0F1Regularized,Hypergeometric1F1Regularized,%
+      Hypergeometric2F1Regularized,%
+      HypergeometricPFQ,HypergeometricPFQRegularized,HyperlinkCreationSettings,Hyphenation,%
+      HyphenationOptions,IgnoreCase,ImageCache,ImageCacheValid,%
+      ImageMargins,ImageOffset,ImageRangeCache,ImageRegion,%
+      ImageResolution,ImageRotated,ImageSize,Import,%
+      ImportAutoReplacements,ImportString,IncludeFileExtension,IncludeSingularTerm,%
+      IndentingNewlineSpacings,IndentMaxFraction,IndexCreationOptions,Inequality,%
+      InexactNumberQ,InexactNumbers,Inherited,InitializationCell,%
+      InitializationCellEvaluation,InitializationCellWarning,%
+      InlineCounterAssignments,InlineCounterIncrements,%
+      InlineRules,InputAliases,InputAutoFormat,InputAutoReplacements,%
+      InputGrouping,InputNamePacket,InputNotebook,InputPacket,%
+      InputSettings,InputStringPacket,InputToBoxFormPacket,InputToInputForm,%
+      InputToStandardForm,InsertionPointObject,IntegerExponent,IntegerPart,%
+      Integers,Interactive,Interlaced,InterpolationOrder,%
+      InterpolationPoints,InterpolationPrecision,InterpretationBox,%
+      InterpretationBoxOptions,%
+      InterpretTemplate,InterruptSettings,Interval,IntervalIntersection,%
+      IntervalMemberQ,IntervalUnion,InverseBetaRegularized,InverseEllipticNomeQ,%
+      InverseErf,InverseErfc,InverseFourierCosTransform,
+      InverseFourierSinTransform,%
+      InverseFourierTransform,InverseGammaRegularized,InverseJacobiCD,%
+      InverseJacobiCN,%
+      InverseJacobiCS,InverseJacobiDC,InverseJacobiDN,InverseJacobiDS,%
+      InverseJacobiNC,InverseJacobiND,InverseJacobiNS,InverseJacobiSC,%
+      InverseJacobiSD,InverseLaplaceTransform,InverseWeierstrassP,InverseZTransform,%
+      Jacobian,JacobiCD,JacobiCN,JacobiCS,%
+      JacobiDC,JacobiDN,JacobiDS,JacobiNC,%
+      JacobiND,JacobiNS,JacobiSC,JacobiSD,%
+      JordanDecomposition,K,Khinchin,KleinInvariantJ,%
+      KroneckerDelta,Language,LanguageCategory,LaplaceTransform,%
+      Larger,Launch,LayoutInformation,Left,%
+      LetterCharacter,Lexicographic,LicenseID,LimitsPositioning,%
+      LimitsPositioningTokens,LinearSolveFunction,LinebreakAdjustments,LineBreakWithin,%
+      LineForm,LineIndent,LineSpacing,LineWrapParts,%
+      LinkActivate,LinkClose,LinkConnect,LinkConnectedQ,%
+      LinkCreate,LinkError,LinkFlush,LinkHost,%
+      LinkInterrupt,LinkLaunch,LinkMode,LinkObject,%
+      LinkOpen,LinkOptions,LinkPatterns,LinkProtocol,%
+      LinkRead,LinkReadHeld,LinkReadyQ,Links,%
+      LinkWrite,LinkWriteHeld,ListConvolve,ListCorrelate,%
+      Listen,ListInterpolation,ListQ,LiteralSearch,%
+      LongestMatch,LongForm,Loopback,LUBackSubstitution,%
+      LUDecomposition,MachineID,MachineName,MachinePrecision,%
+      MacintoshSystemPageSetup,Magenta,Magnification,MakeBoxes,%
+      MakeExpression,MakeRules,Manual,MatchLocalNameQ,%
+      MathematicaNotation,MathieuC,MathieuCharacteristicA,MathieuCharacteristicB,%
+      MathieuCharacteristicExponent,MathieuCPrime,MathieuS,MathieuSPrime,%
+      MathMLForm,MathMLText,MatrixRank,Maximize,%
+      MaxIterations,MaxPlotPoints,MaxPoints,MaxRecursion,%
+      MaxStepFraction,MaxSteps,MaxStepSize,Mean,%
+      Median,MeijerG,MenuPacket,MessageOptions,%
+      MessagePacket,MessagesNotebook,MetaCharacters,Method,%
+      MethodOptions,Minimize,MinRecursion,MinSize,%
+      Mode,ModularLambda,MonomialOrder,MonteCarlo,%
+      Most,MousePointerNote,MultiDimensional,MultilaunchWarning,%
+      MultilineFunction,MultiplicativeOrder,Multiplicity,Nand,%
+      NeedCurrentFrontEndPackagePacket,NeedCurrentFrontEndSymbolsPacket,%
+      NestedScriptRules,NestWhile,%
+      NestWhileList,NevilleThetaC,NevilleThetaD,NevilleThetaN,%
+      NevilleThetaS,Newton,Next,NHoldAll,%
+      NHoldFirst,NHoldRest,NMaximize,NMinimize,%
+      NonAssociative,NonPositive,Nor,Norm,%
+      NormalGrouping,NormalSelection,NormFunction,Notebook,%
+      NotebookApply,NotebookAutoSave,NotebookClose,NotebookConvert,%
+      NotebookConvertSettings,NotebookCreate,NotebookCreateReturnObject,NotebookDefault,%
+      NotebookDelete,NotebookDirectory,NotebookFind,NotebookFindReturnObject,%
+      NotebookGet,NotebookGetLayoutInformationPacket,NotebookGetMisspellingsPacket,%
+      NotebookInformation,%
+      NotebookLocate,NotebookObject,NotebookOpen,NotebookOpenReturnObject,%
+      NotebookPath,NotebookPrint,NotebookPut,NotebookPutReturnObject,%
+      NotebookRead,NotebookResetGeneratedCells,Notebooks,NotebookSave,%
+      NotebookSaveAs,NotebookSelection,NotebookSetupLayoutInformationPacket,%
+      NotebooksMenu,%
+      NotebookWrite,NotElement,NProductExtraFactors,NProductFactors,%
+      NRoots,NSumExtraTerms,NSumTerms,NumberMarks,%
+      NumberMultiplier,NumberString,NumericFunction,NumericQ,%
+      NValues,Offset,OLEData,OneStepRowReduction,%
+      Open,OpenFunctionInspectorPacket,OpenSpecialOptions,OptimizationLevel,%
+      OptionInspectorSettings,OptionQ,OptionsPacket,OptionValueBox,%
+      OptionValueBoxOptions,Orange,Ordering,Oscillatory,%
+      OutputAutoOverwrite,OutputFormData,OutputGrouping,OutputMathEditExpression,%
+      OutputNamePacket,OutputToOutputForm,OutputToStandardForm,Over,%
+      Overflow,Overlaps,Overscript,OverscriptBox,%
+      OverscriptBoxOptions,OwnValues,PadLeft,PadRight,%
+      PageBreakAbove,PageBreakBelow,PageBreakWithin,PageFooterLines,%
+      PageFooters,PageHeaderLines,PageHeaders,PalettePath,%
+      PaperWidth,ParagraphIndent,ParagraphSpacing,ParameterVariables,%
+      ParentConnect,ParentForm,Parenthesize,PasteBoxFormInlineCells,%
+      Path,PatternTest,PeriodicInterpolation,Pick,%
+      Piecewise,PiecewiseExpand,Pink,Pivoting,%
+      PixelConstrained,Placeholder,Plain,Plot3Matrix,%
+      PointForm,PolynomialForm,PolynomialReduce,Polynomials,%
+      PowerModList,Precedence,PreferencesPath,PreserveStyleSheet,%
+      Previous,PrimaryPlaceholder,Primes,PrincipalValue,%
+      PrintAction,PrintingCopies,PrintingOptions,PrintingPageRange,%
+      PrintingStartingPageNumber,PrintingStyleEnvironment,PrintPrecision,%
+      PrivateCellOptions,%
+      PrivateEvaluationOptions,PrivateFontOptions,PrivateNotebookOptions,PrivatePaths,%
+      ProductLog,PromptForm,Purple,Quantile,%
+      QuasiMonteCarlo,QuasiNewton,RadicalBox,RadicalBoxOptions,%
+      RandomSeed,RationalFunctions,Rationals,RawData,%
+      RawMedium,RealBlockForm,Reals,Reap,%
+      Red,Refine,Refresh,RegularExpression,%
+      Reinstall,Release,Removed,RenderingOptions,%
+      RepeatedString,ReplaceList,Rescale,ResetMenusPacket,%
+      Resolve,ResumePacket,ReturnExpressionPacket,ReturnInputFormPacket,%
+      ReturnPacket,ReturnTextPacket,Right,Root,%
+      RootReduce,RootSum,Row,RowAlignments,%
+      RowBox,RowLines,RowMinHeight,RowsEqual,%
+      RowSpacings,RSolve,RuleCondition,RuleForm,%
+      RulerUnits,Saveable,SaveAutoDelete,ScreenRectangle,%
+      ScreenStyleEnvironment,ScriptBaselineShifts,ScriptLevel,ScriptMinSize,%
+      ScriptRules,ScriptSizeMultipliers,ScrollingOptions,ScrollPosition,%
+      Second,SectionGrouping,Selectable,SelectedNotebook,%
+      Selection,SelectionAnimate,SelectionCell,SelectionCellCreateCell,%
+      SelectionCellDefaultStyle,SelectionCellParentStyle,SelectionCreateCell,%
+      SelectionDuplicateCell,%
+      SelectionEvaluate,SelectionEvaluateCreateCell,SelectionMove,SelectionSetStyle,%
+      SelectionStrategy,SendFontInformationToKernel,SequenceHold,SequenceLimit,%
+      SeriesCoefficient,SetBoxFormNamesPacket,SetEvaluationNotebook,%
+      SetFileLoadingContext,%
+      SetNotebookStatusLine,SetOptionsPacket,SetSelectedNotebook,%
+      SetSpeechParametersPacket,%
+      SetValue,ShortestMatch,ShowAutoStyles,ShowCellBracket,%
+      ShowCellLabel,ShowCellTags,ShowClosedCellArea,ShowContents,%
+      ShowCursorTracker,ShowGroupOpenCloseIcon,ShowPageBreaks,ShowSelection,%
+      ShowShortBoxForm,ShowSpecialCharacters,ShowStringCharacters,%
+      ShrinkWrapBoundingBox,%
+      SingleLetterItalics,SingularityDepth,SingularValueDecomposition,%
+      SingularValueList,%
+      SinhIntegral,Smaller,Socket,SolveDelayed,%
+      SoundAndGraphics,Sow,Space,SpaceForm,%
+      SpanAdjustments,SpanCharacterRounding,SpanLineThickness,SpanMaxSize,%
+      SpanMinSize,SpanningCharacters,SpanSymmetric,Sparse,%
+      SparseArray,SpeakTextPacket,SpellingDictionaries,SpellingDictionariesPath,%
+      SpellingOptions,SpellingSuggestionsPacket,Spherical,Split,%
+      SqrtBox,SqrtBoxOptions,StandardDeviation,StandardForm,%
+      StartingStepSize,StartOfLine,StartOfString,StartupSound,%
+      StepMonitor,StieltjesGamma,StoppingTest,StringCases,%
+      StringCount,StringExpression,StringFreeQ,StringQ,%
+      StringReplaceList,StringReplacePart,StringSplit,StripBoxes,%
+      StripWrapperBoxes,StructuredSelection,StruveH,StruveL,%
+      StyleBox,StyleBoxAutoDelete,StyleBoxOptions,StyleData,%
+      StyleDefinitions,StyleForm,StyleMenuListing,StyleNameDialogSettings,%
+      StylePrint,StyleSheetPath,Subresultants,SubscriptBox,%
+      SubscriptBoxOptions,Subsets,Subsuperscript,SubsuperscriptBox,%
+      SubsuperscriptBoxOptions,SubtractFrom,SubValues,SugarCube,%
+      SuperscriptBox,SuperscriptBoxOptions,SuspendPacket,SylvesterMatrix,%
+      SymbolName,Syntax,SyntaxForm,SyntaxPacket,%
+      SystemException,SystemHelpPath,SystemStub,Tab,%
+      TabFilling,TabSpacings,TagBox,TagBoxOptions,%
+      TaggingRules,TagStyle,TargetFunctions,TemporaryVariable,%
+      TensorQ,TeXSave,TextAlignment,TextBoundingBox,%
+      TextData,TextJustification,TextLine,TextPacket,%
+      TextParagraph,TextRendering,TextStyle,ThisLink,%
+      TimeConstraint,TimeVariable,TitleGrouping,ToBoxes,%
+      ToColor,ToFileName,Toggle,ToggleFalse,%
+      Tolerance,TooBig,Top,ToRadicals,%
+      Total,Tr,TraceAction,TraceInternal,%
+      TraceLevel,TraditionalForm,TraditionalFunctionNotation,TraditionalNotation,%
+      TraditionalOrder,TransformationFunctions,TransparentColor,Trapezoidal,%
+      TrigExpand,TrigFactor,TrigFactorList,TrigReduce,%
+      TrigToExp,Tuples,UnAlias,Underflow,%
+      Underoverscript,UnderoverscriptBox,UnderoverscriptBoxOptions,Underscript,%
+      UnderscriptBox,UnderscriptBoxOptions,UndocumentedTestFEParserPacket,%
+      UndocumentedTestGetSelectionPacket,%
+      UnitStep,Up,URL,Using,%
+      V2Get,Value,ValueBox,ValueBoxOptions,%
+      ValueForm,Variance,Verbatim,Verbose,%
+      VerboseConvertToPostScriptPacket,VerifyConvergence,VerifySolutions,Version,%
+      VersionNumber,Vertical,VerticalForm,ViewPointSelectorSettings,%
+      Visible,VisibleCell,WeierstrassHalfPeriods,WeierstrassInvariants,%
+      WeierstrassSigma,WeierstrassZeta,White,Whitespace,%
+      WhitespaceCharacter,WindowClickSelect,WindowElements,WindowFloating,%
+      WindowFrame,WindowFrameElements,WindowMargins,WindowMovable,%
+      WindowSize,WindowTitle,WindowToolbars,WindowWidth,%
+      WordBoundary,WordCharacter,WynnDegree,XMLElement},%
+   morendkeywords={$,$AddOnsDirectory,$AnimationDisplayFunction,%
+      $AnimationFunction,%
+      $Assumptions,$BaseDirectory,$BoxForms,$ByteOrdering,%
+      $CharacterEncoding,$ConditionHold,$CurrentLink,$DefaultPath,%
+      $ExportEncodings,$ExportFormats,$FormatType,$FrontEnd,%
+      $HistoryLength,$HomeDirectory,$ImportEncodings,$ImportFormats,%
+      $InitialDirectory,$InstallationDate,$InstallationDirectory,%
+      $InterfaceEnvironment,%
+      $LaunchDirectory,$LicenseExpirationDate,$LicenseID,$LicenseProcesses,%
+      $LicenseServer,$MachineDomain,$MaxExtraPrecision,$MaxLicenseProcesses,%
+      $MaxNumber,$MaxPiecewiseCases,$MaxPrecision,$MaxRootDegree,%
+      $MinNumber,$MinPrecision,$NetworkLicense,$NumberMarks,%
+      $Off,$OutputForms,$ParentLink,$ParentProcessID,%
+      $PasswordFile,$PathnameSeparator,$PreferencesDirectory,$PrintForms,%
+      $PrintLiteral,$ProcessID,$ProcessorType,$ProductInformation,%
+      $ProgramName,$PSDirectDisplay,$RandomState,$RasterFunction,%
+      $RootDirectory,$SetParentLink,$SoundDisplay,$SuppressInputFormHeads,%
+      $SystemCharacterEncoding,$SystemID,$TemporaryPrefix,$TextStyle,%
+      $TopDirectory,$TraceOff,$TraceOn,$TracePattern,%
+      $TracePostAction,$TracePreAction,$UserAddOnsDirectory,$UserBaseDirectory,%
+      $UserName,Constant,Flat,HoldAll,%
+      HoldAllComplete,HoldFirst,HoldRest,Listable,%
+      Locked,NHoldAll,NHoldFirst,NHoldRest,%
+      NumericFunction,OneIdentity,Orderless,Protected,%
+      ReadProtected,SequenceHold},%
+  }%
+%%
+%% Mathematica definitions (c) 1999 Michael Wiese
+%%
+\lst@definelanguage[3.0]{Mathematica}[1.0]{Mathematica}%
+  {morekeywords={Abort,AbortProtect,AbsoluteDashing,AbsolutePointSize,%
+      AbsoluteThickness,AbsoluteTime,AccountingFormAiry,AiPrime,AiryBi,%
+      AiryBiPrime,Alternatives,AnchoredSearch,AxesEdge,AxesOrigin,%
+      AxesStyle,Background,BetaRegularized,BoxStyle,C,CheckAbort,%
+      Circle,ClebschGordan,CMYKColor,ColorFunction,ColorOutput,Compile,%
+      Compiled,CompiledFunction,ComplexExpand,ComposeList,Composition,%
+      ConstrainedMax,ConstrainedMin,Contexts,ContextToFilename,%
+      ContourLines,Contours,ContourShading,ContourSmoothing,%
+      ContourStyle,CopyDirectory,CopyFile,CosIntegral,CreateDirectory,%
+      Cuboid,Date,DeclarePackage,DefaultColor,DefaultFont,Delete,%
+      DeleteCases,DeleteDirectory,DeleteFile,Dialog,DialogIndent,%
+      DialogProlog,DialogSymbols,DigitQ,Directory,DirectoryStack,Disk,%
+      Dispatch,DownValues,DSolve,Encode,Epilog,Erfc,Evaluate,%
+      ExponentFunction,FaceGrids,FileByteCount,FileDate,FileNames,%
+      FileType,Find,FindList,FixedPointList,FlattenAt,Fold,FoldList,%
+      Frame,FrameLabel,FrameStyle,FrameTicks,FromCharacterCode,%
+      FromDate,FullGraphics,FullOptions,GammaRegularized,%
+      GaussianIntegers,GraphicsArray,GraphicsSpacing,GridLines,%
+      GroebnerBasis,Heads,HeldPart,HomeDirectory,Hue,IgnoreCases,%
+      InputStream,Install,InString,IntegerDigits,InterpolatingFunction,%
+      InterpolatingPolynomial,Interpolation,Interrupt,InverseFunction,%
+      InverseFunctions,JacobiZeta,LetterQ,LinearProgramming,ListPlay,%
+      LogGamma,LowerCaseQ,MachineNumberQ,MantissaExponent,MapIndexed,%
+      MapThread,MatchLocalNames,MatrixExp,MatrixPower,MeshRange,%
+      MeshStyle,MessageList,Module,NDSolve,NSolve,NullRecords,%
+      NullWords,NumberFormat,NumberPadding,NumberSigns,OutputStream,%
+      PaddedForm,ParentDirectory,Pause,Play,PlayRange,PlotRegion,%
+      PolygonIntersections,PolynomialGCD,PolynomialLCM,PolynomialMod,%
+      PostScript,PowerExpand,PrecisionGoal,PrimePi,Prolog,%
+      QRDecomposition,Raster,RasterArray,RealDigits,Record,RecordLists,%
+      RecordSeparators,ReleaseHold,RenameDirectory,RenameFile,%
+      ReplaceHeldPart,ReplacePart,ResetDirectory,Residue,%
+      RiemannSiegelTheta,RiemannSiegelZ,RotateLabel,SameTest,%
+      SampleDepth,SampledSoundFunction,SampledSoundList,SampleRate,%
+      SchurDecomposition,SessionTime,SetAccuracy,SetDirectory,%
+      SetFileDate,SetPrecision,SetStreamPosition,Shallow,SignPadding,%
+      SinIntegral,SixJSymbol,Skip,Sound,SpellingCorrection,%
+      SphericalRegion,Stack,StackBegin,StackComplete,StackInhibit,%
+      StreamPosition,Streams,StringByteCount,StringConversion,%
+      StringDrop,StringInsert,StringPosition,StringReplace,%
+      StringReverse,StringTake,StringToStream,SurfaceColor,%
+      SyntaxLength,SyntaxQ,TableAlignments,TableDepth,%
+      TableDirections,TableHeadings,TableSpacing,ThreeJSymbol,TimeUsed,%
+      TimeZone,ToCharacterCode,ToDate,ToHeldExpression,TokenWords,%
+      ToLowerCase,ToUpperCase,Trace,TraceAbove,TraceBackward,%
+      TraceDepth,TraceDialog,TraceForward,TraceOff,TraceOn,%
+      TraceOriginal,TracePrint,TraceScan,Trig,Unevaluated,Uninstall,%
+      UnsameQ,UpperCaseQ,UpValues,ViewCenter,ViewVertical,With,Word,%
+      WordSearch,WordSeparators},%
+   morendkeywords={Stub,Temporary,$Aborted,$BatchInput,$BatchOutput,%
+      $CreationDate,$DefaultFont,$DumpDates,$DumpSupported,$Failed,%
+      $Input,$Inspector,$IterationLimit,$Language,$Letters,$Linked,%
+      $LinkSupported,$MachineEpsilon,$MachineID,$MachineName,%
+      $MachinePrecision,$MachineType,$MaxMachineNumber,$MessageList,%
+      $MessagePrePrint,$MinMachineNumber,$ModuleNumber,$NewMessage,%
+      $NewSymbol,$Notebooks,$OperatingSystem,$Packages,$PipeSupported,%
+      $PreRead,$ReleaseNumber,$SessionID,$SoundDisplayFunction,%
+      $StringConversion,$StringOrder,$SyntaxHandler,$TimeUnit,%
+      $VersionNumber}%
+  }%
+\lst@definelanguage[1.0]{Mathematica}%
+  {morekeywords={Abs,Accuracy,AccurayGoal,AddTo,AiryAi,AlgebraicRules,%
+      AmbientLight,And,Apart,Append,AppendTo,Apply,ArcCos,ArcCosh,%
+      ArcCot,ArcCoth,ArcCsc,ArcCsch,ArcSec,ArcSech,ArcSin,ArcSinh,%
+      ArcTan,ArcTanh,Arg,ArithmeticGeometricMean,Array,AspectRatio,%
+      AtomQ,Attributes,Axes,AxesLabel,BaseForm,Begin,BeginPackage,%
+      BernoulliB,BesselI,BesselJ,BesselK,BesselY,Beta,Binomial,Blank,%
+      BlankNullSequence,BlankSequence,Block,Boxed,BoxRatios,Break,Byte,%
+      ByteCount,Cancel,Cases,Catch,Ceiling,CForm,Character,Characters,%
+      ChebyshevT,ChebyshevU,Check,Chop,Clear,ClearAll,ClearAttributes,%
+      ClipFill,Close,Coefficient,CoefficientList,Collect,ColumnForm,%
+      Complement,Complex,CompoundExpression,Condition,Conjugate,%
+      Constants,Context,Continuation,Continue,ContourGraphics,%
+      ContourPlot,Cos,Cosh,Cot,Coth,Count,Csc,Csch,Cubics,Cyclotomic,%
+      D,Dashing,Decompose,Decrement,Default,Definition,Denominator,%
+      DensityGraphics,DensityPlot,Depth,Derivative,Det,DiagonalMatrix,%
+      DigitBlock,Dimensions,DirectedInfinity,Display,DisplayFunction,%
+      Distribute,Divide,DivideBy,Divisors,DivisorSigma,Do,Dot,Drop,Dt,%
+      Dump,EdgeForm,Eigensystem,Eigenvalues,Eigenvectors,Eliminate,%
+      EllipticE,EllipticExp,EllipticF,EllipticK,EllipticLog,EllipticPi,%
+      EllipticTheta,End,EndPackage,EngineeringForm,Environment,Equal,%
+      Erf,EulerE,EulerPhi,EvenQ,Exit,Exp,Expand,ExpandAll,%
+      ExpandDenominator,ExpandNumerator,ExpIntegralE,ExpIntegralEi,%
+      Exponent,Expression,ExtendedGCD,FaceForm,Factor,FactorComplete,%
+      Factorial,Factorial2,FactorInteger,FactorList,FactorSquareFree,%
+      FactorSquareFreeList,FactorTerms,FactorTermsList,FindMinimum,%
+      FindRoot,First,Fit,FixedPoint,Flatten,Floor,FontForm,For,Format,%
+      FormatType,FortranForm,Fourier,FreeQ,FullDefinition,FullForm,%
+      Function,Gamma,GCD,GegenbauerC,General,Get,Goto,Graphics,%
+      Graphics3D,GrayLevel,Greater,GreaterEqual,Head,HermiteH,%
+      HiddenSurface,Hold,HoldForm,Hypergeometric0F1,Hypergeometric1F1,%
+      Hypergeometric2F1,HypergeometricU,Identity,IdentityMatrix,If,Im,%
+      Implies,In,Increment,Indent,Infix,Information,Inner,Input,%
+      InputForm,InputString,Insert,Integer,IntegerQ,Integrate,%
+      Intersection,Inverse,InverseFourier,InverseJacobiSN,%
+      InverseSeries,JacobiAmplitude,JacobiP,JacobiSN,JacobiSymbol,Join,%
+      Label,LaguerreL,Last,LatticeReduce,LCM,LeafCount,LegendreP,%
+      LegendreQ,LegendreType,Length,LerchPhi,Less,LessEqual,Level,%
+      Lighting,LightSources,Limit,Line,LinearSolve,LineBreak,List,%
+      ListContourPlot,ListDensityPlot,ListPlot,ListPlot3D,Literal,Log,%
+      LogicalExpand,LogIntegral,MainSolve,Map,MapAll,MapAt,MatchQ,%
+      MatrixForm,MatrixQ,Max,MaxBend,MaxMemoryUsed,MemberQ,%
+      MemoryConstrained,MemoryInUse,Mesh,Message,MessageName,Messages,%
+      Min,Minors,Minus,Mod,Modulus,MoebiusMu,Multinomial,N,NameQ,Names,%
+      NBernoulliB,Needs,Negative,Nest,NestList,NIntegrate,%
+      NonCommutativeMultiply,NonConstants,NonNegative,Normal,Not,%
+      NProduct,NSum,NullSpace,Number,NumberForm,NumberPoint,NumberQ,%
+      NumberSeparator,Numerator,O,OddQ,Off,On,OpenAppend,OpenRead,%
+      OpenTemporary,OpenWrite,Operate,Optional,Options,Or,Order,%
+      OrderedQ,Out,Outer,OutputForm,PageHeight,PageWidth,%
+      ParametricPlot,ParametricPlot3D,Part,Partition,PartitionsP,%
+      PartitionsQ,Pattern,Permutations,Plot,Plot3D,PlotDivision,%
+      PlotJoined,PlotLabel,PlotPoints,PlotRange,PlotStyle,Pochhammer,%
+      Plus,Point,PointSize,PolyGamma,Polygon,PolyLog,PolynomialQ,%
+      PolynomialQuotient,PolynomialRemainder,Position,Positive,Postfix,%
+      Power,PowerMod,PrecedenceForm,Precision,PreDecrement,Prefix,%
+      PreIncrement,Prepend,PrependTo,Prime,PrimeQ,Print,PrintForm,%
+      Product,Protect,PseudoInverse,Put,PutAppend,Quartics,Quit,%
+      Quotient,Random,Range,Rational,Rationalize,Raw,Re,Read,ReadList,%
+      Real,Rectangle,Reduce,Remove,RenderAll,Repeated,RepeatedNull,%
+      Replace,ReplaceAll,ReplaceRepeated,Rest,Resultant,Return,Reverse,%
+      RGBColor,Roots,RotateLeft,RotateRight,Round,RowReduce,Rule,%
+      RuleDelayed,Run,RunThrough,SameQ,Save,Scaled,Scan,ScientificForm,%
+      Sec,Sech,SeedRandom,Select,Sequence,SequenceForm,Series,%
+      SeriesData,Set,SetAttributes,SetDelayed,SetOptions,Shading,Share,%
+      Short,Show,Sign,Signature,Simplify,Sin,SingularValues,Sinh,%
+      Skeleton,Slot,SlotSequence,Solve,SolveAlways,Sort,%
+      SphericalHarmonicY,Splice,Sqrt,StirlingS1,StirlingS2,String,%
+      StringBreak,StringForm,StringJoin,StringLength,StringMatchQ,%
+      StringSkeleton,Subscript,Subscripted,Subtract,SubtractForm,Sum,%
+      Superscript,SurfaceGraphics,Switch,Symbol,Table,TableForm,TagSet,%
+      TagSetDelayed,TagUnset,Take,Tan,Tanh,ToString,TensorRank,TeXForm,%
+      Text,TextForm,Thickness,Thread,Through,Throw,Ticks,%
+      TimeConstrained,Times,TimesBy,Timing,ToExpression,Together,%
+      ToRules,ToString,TotalHeight,TotalWidth,Transpose,TreeForm,TrueQ,%
+      Unequal,Union,Unique,Unprotect,Unset,Update,UpSet,UpSetDelayed,%
+      ValueQ,Variables,VectorQ,ViewPoint,WeierstrassP,%
+      WeierstrassPPrime,Which,While,WorkingPrecision,Write,WriteString,%
+      Xor,ZeroTest,Zeta},%
+   morendkeywords={All,Automatic,Catalan,ComplexInfinity,Constant,%
+      Degree,E,EndOfFile,EulerGamma,False,Flat,GoldenRatio,HoldAll,%
+      HoldFirst,HoldRest,I,Indeterminate,Infinity,Listable,Locked,%
+      Modular,None,Null,OneIdentity,Orderless,Pi,Protected,%
+      ReadProtected,True,$CommandLine,$Context,$ContextPath,$Display,%
+      $DisplayFunction,$Echo,$Epilog,$IgnoreEOF,$Line,$Messages,%
+      $Output,$Path,$Post,$Pre,$PrePrint,$RecursionLimit,$System,%
+      $Urgent,$Version},%
+   sensitive,%
+   morecomment=[s]{(*}{*)},%
+   morestring=[d]"%
+  }[keywords,comments,strings]%
+%%
+%% Octave definition (c) 2001,2002 Ulrich G. Wortmann
+%%
+\lst@definelanguage{Octave}%
+  {morekeywords={gt,lt,amp,abs,acos,acosh,acot,acoth,acsc,acsch,%
+      all,angle,ans,any,asec,asech,asin,asinh,atan,atan2,atanh,auread,%
+      auwrite,axes,axis,balance,bar,bessel,besselk,bessely,beta,%
+      betainc,betaln,blanks,bone,break,brighten,capture,cart2pol,%
+      cart2sph,caxis,cd,cdf2rdf,cedit,ceil,chol,cla,clabel,clc,clear,%
+      clf,clock,close,colmmd,Colon,colorbar,colormap,ColorSpec,colperm,%
+      comet,comet3,compan,compass,computer,cond,condest,conj,contour,%
+      contour3,contourc,contrast,conv,conv2,cool,copper,corrcoef,cos,%
+      cosh,cot,coth,cov,cplxpair,cputime,cross,csc,csch,csvread,%
+      csvwrite,cumprod,cumsum,cylinder,date,dbclear,dbcont,dbdown,%
+      dbquit,dbstack,dbstatus,dbstep,dbstop,dbtype,dbup,ddeadv,ddeexec,%
+      ddeinit,ddepoke,ddereq,ddeterm,ddeunadv,deblank,dec2hex,deconv,%
+      del2,delete,demo,det,diag,diary,diff,diffuse,dir,disp,dlmread,%
+      dlmwrite,dmperm,dot,drawnow,echo,eig,ellipj,ellipke,else,elseif,%
+      end,engClose,engEvalString,engGetFull,engGetMatrix,engOpen,%
+      engOutputBuffer,engPutFull,engPutMatrix,engSetEvalCallback,%
+      engSetEvalTimeout,engWinInit,eps,erf,erfc,erfcx,erfinv,%
+      errorbar,etime,etree,eval,exist,exp,expint,expm,expo,eye,fclose,%
+      feather,feof,ferror,feval,fft,fft2,fftshift,fgetl,fgets,figure,%
+      fill,fill3,filter,filter2,find,findstr,finite,fix,flag,fliplr,%
+      flipud,floor,flops,fmin,fmins,fopen,for,format,fplot,fprintf,%
+      fread,frewind,fscanf,fseek,ftell,full,function,funm,fwrite,fzero,%
+      gallery,gamma,gammainc,gammaln,gca,gcd,gcf,gco,get,getenv,%
+      getframe,ginput,global,gplot,gradient,gray,graymon,grid,griddata,%
+      gtext,hadamard,hankel,help,hess,hex2dec,hex2num,hidden,hilb,hist,%
+      hold,home,hostid,hot,hsv,hsv2rgb,if,ifft,ifft2,imag,image,%
+      imagesc,Inf,info,input,int2str,interp1,interp2,interpft,inv,%
+      invhilb,isempty,isglobal,ishold,isieee,isinf,isletter,isnan,%
+      isreal,isspace,issparse,isstr,jet,keyboard,kron,lasterr,lcm,%
+      legend,legendre,length,lin2mu,line,linspace,load,log,log10,log2,%
+      loglog,logm,logspace,lookfor,lower,ls,lscov,lu,magic,matClose,%
+      matDeleteMatrix,matGetDir,matGetFp,matGetFull,matGetMatrix,%
+      matGetNextMatrix,matGetString,matlabrc,matlabroot,matOpen,%
+      matPutFull,matPutMatrix,matPutString,max,mean,median,menu,mesh,%
+      meshc,meshgrid,meshz,mexAtExit,mexCallMATLAB,mexdebug,%
+      mexErrMsgTxt,mexEvalString,mexFunction,mexGetFull,mexGetMatrix,%
+      mexGetMatrixPtr,mexPrintf,mexPutFull,mexPutMatrix,mexSetTrapFlag,%
+      min,more,movie,moviein,mu2lin,mxCalloc,mxCopyCharacterToPtr,%
+      mxCopyComplex16ToPtr,mxCopyInteger4ToPtr,mxCopyPtrToCharacter,%
+      mxCopyPtrToComplex16,mxCopyPtrToInteger4,mxCopyPtrToReal8,%
+      mxCopyReal8ToPtr,mxCreateFull,mxCreateSparse,mxCreateString,%
+      mxFree,mxFreeMatrix,mxGetIr,mxGetJc,mxGetM,mxGetN,mxGetName,%
+      mxGetNzmax,mxGetPi,mxGetPr,mxGetScalar,mxGetString,mxIsComplex,%
+      mxIsFull,mxIsNumeric,mxIsSparse,mxIsString,mxIsTypeDouble,%
+      mxSetIr,mxSetJc,mxSetM,mxSetN,mxSetName,mxSetNzmax,mxSetPi,%
+      mxSetPr,NaN,nargchk,nargin,nargout,newplot,nextpow2,nnls,nnz,%
+      nonzeros,norm,normest,null,num2str,nzmax,ode23,ode45,orient,orth,%
+      pack,pascal,patch,path,pause,pcolor,pi,pink,pinv,plot,plot3,%
+      pol2cart,polar,poly,polyder,polyeig,polyfit,polyval,polyvalm,%
+      pow2,print,printopt,prism,prod,pwd,qr,qrdelete,qrinsert,quad,%
+      quad8,quit,quiver,qz,rand,randn,randperm,rank,rat,rats,rbbox,%
+      rcond,real,realmax,realmin,refresh,rem,reset,reshape,residue,%
+      return,rgb2hsv,rgbplot,rootobject,roots,rose,rosser,rot90,rotate,%
+      round,rref,rrefmovie,rsf2csf,save,saxis,schur,sec,sech,semilogx,%
+      semilogy,set,setstr,shading,sign,sin,sinh,size,slice,sort,sound,%
+      spalloc,sparse,spaugment,spconvert,spdiags,specular,speye,spfun,%
+      sph2cart,sphere,spinmap,spline,spones,spparms,sprandn,sprandsym,%
+      sprank,sprintf,spy,sqrt,sqrtm,sscanf,stairs,startup,std,stem,%
+      str2mat,str2num,strcmp,strings,strrep,strtok,subplot,subscribe,%
+      subspace,sum,surf,surface,surfc,surfl,surfnorm,svd,symbfact,%
+      symmmd,symrcm,tan,tanh,tempdir,tempname,terminal,text,tic,title,%
+      toc,toeplitz,trace,trapz,tril,triu,type,uicontrol,uigetfile,%
+      uimenu,uiputfile,unix,unwrap,upper,vander,ver,version,view,%
+      viewmtx,waitforbuttonpress,waterfall,wavread,wavwrite,what,%
+      whatsnew,which,while,white,whitebg,who,whos,wilkinson,wk1read,%
+      stderr,stdout,plot,set,endif,wk1write,xlabel,xor,ylabel,zeros,%
+      zlabel,zoom,endwhile,endfunction,printf,case,switch,otherwise,%
+      system,lsode,endfor,error,ones,oneplot,__gnuplot_set__,do,until},%
+   sensitive=t,%
+   morecomment=[l]\#,%
+   morecomment=[l]\#\#,%
+   morecomment=[l]\%,%
+   morestring=[m]',%
+   morestring=[m]"%
+  }[keywords,comments,strings]%
+\lst@definelanguage[XSC]{Pascal}[Standard]{Pascal}
+  {deletekeywords={alfa,byte,pack,unpack},% 1998 Andreas Stephan
+   morekeywords={dynamic,external,forward,global,module,nil,operator,%
+      priority,sum,type,use,dispose,mark,page,release,cimatrix,%
+      cinterval,civector,cmatrix,complex,cvector,dotprecision,imatrix,%
+      interval,ivector,rmatrix,rvector,string,im,inf,re,sup,chr,comp,%
+      eof,eoln,expo,image,ival,lb,lbound,length,loc,mant,maxlength,odd,%
+      ord,pos,pred,round,rval,sign,substring,succ,trunc,ub,ubound}%
+  }%
+\lst@definelanguage[Borland6]{Pascal}[Standard]{Pascal}
+  {morekeywords={asm,constructor,destructor,implementation,inline,%
+      interface,nil,object,shl,shr,string,unit,uses,xor},%
+   morendkeywords={Abs,Addr,ArcTan,Chr,Concat,Copy,Cos,CSeg,DiskFree,%
+      DiskSize,DosExitCode,DosVersion,DSeg,EnvCount,EnvStr,Eof,Eoln,%
+      Exp,FExpand,FilePos,FileSize,Frac,FSearch,GetBkColor,GetColor,%
+      GetDefaultPalette,GetDriverName,GetEnv,GetGraphMode,GetMaxMode,%
+      GetMaxX,GetMaxY,GetModeName,GetPaletteSize,GetPixel,GetX,GetY,%
+      GraphErrorMsg,GraphResult,Hi,ImageSize,InstallUserDriver,%
+      InstallUserFont,Int,IOResult,KeyPressed,Length,Lo,MaxAvail,%
+      MemAvail,MsDos,Odd,Ofs,Ord,OvrGetBuf,OvrGetRetry,ParamCount,%
+      ParamStr,Pi,Pos,Pred,Ptr,Random,ReadKey,Round,SeekEof,SeekEoln,%
+      Seg,SetAspectRatio,Sin,SizeOf,Sound,SPtr,Sqr,Sqrt,SSeg,Succ,%
+      Swap,TextHeight,TextWidth,Trunc,TypeOf,UpCase,WhereX,WhereY,%
+      Append,Arc,Assign,AssignCrt,Bar,Bar3D,BlockRead,BlockWrite,ChDir,%
+      Circle,ClearDevice,ClearViewPort,Close,CloseGraph,ClrEol,ClrScr,%
+      Dec,Delay,Delete,DelLine,DetectGraph,Dispose,DrawPoly,Ellipse,%
+      Erase,Exec,Exit,FillChar,FillEllipse,FillPoly,FindFirst,FindNext,%
+      FloodFill,Flush,FreeMem,FSplit,GetArcCoords,GetAspectRatio,%
+      GetDate,GetDefaultPalette,GetDir,GetCBreak,GetFAttr,%
+      GetFillSettings,GetFTime,GetImage,GetIntVec,GetLineSettings,%
+      GetMem,GetPalette,GetTextSettings,GetTime,GetVerify,%
+      GetViewSettings,GoToXY,Halt,HighVideo,Inc,InitGraph,Insert,%
+      InsLine,Intr,Keep,Line,LineRel,LineTo,LowVideo,Mark,MkDir,Move,%
+      MoveRel,MoveTo,MsDos,New,NormVideo,NoSound,OutText,OutTextXY,%
+      OvrClearBuf,OvrInit,OvrInitEMS,OvrSetBuf,PackTime,PieSlice,%
+      PutImage,PutPixel,Randomize,Rectangle,Release,Rename,%
+      RestoreCrtMode,RmDir,RunError,Sector,Seek,SetActivePage,%
+      SetAllPalette,SetBkColor,SetCBreak,SetColor,SetDate,SetFAttr,%
+      SetFillPattern,SetFillStyle,SetFTime,SetGraphBufSize,%
+      SetGraphMode,SetIntVec,SetLineStyle,SetPalette,SetRGBPalette,%
+      SetTextBuf,SetTextJustify,SetTextStyle,SetTime,SetUserCharSize,%
+      SetVerify,SetViewPort,SetVisualPage,SetWriteMode,Sound,Str,%
+      SwapVectors,TextBackground,TextColor,TextMode,Truncate,%
+      UnpackTime,Val,Window}%
+  }%
+\lst@definelanguage[Standard]{Pascal}%
+  {morekeywords={alfa,and,array,begin,boolean,byte,case,char,const,div,%
+      do,downto,else,end,false,file,for,function,get,goto,if,in,%
+      integer,label,maxint,mod,new,not,of,or,pack,packed,page,program,%
+      put,procedure,read,readln,real,record,repeat,reset,rewrite,set,%
+      text,then,to,true,type,unpack,until,var,while,with,write,%
+      writeln},%
+   sensitive=f,%
+   morecomment=[s]{(*}{*)},%
+   morecomment=[s]{\{}{\}},%
+   morestring=[d]'%
+  }[keywords,comments,strings]%
+\lst@definelanguage{Perl}%
+  {morekeywords={abs,accept,alarm,atan2,bind,binmode,bless,caller,%
+      chdir,chmod,chomp,chop,chown,chr,chroot,close,closedir,connect,%
+      continue,cos,crypt,dbmclose,dbmopen,defined,delete,die,do,dump,%
+      each,else,elsif,endgrent,endhostent,endnetent,endprotoent,%
+      endpwent,endservent,eof,eval,exec,exists,exit,exp,fcntl,fileno,%
+      flock,for,foreach,fork,format,formline,getc,getgrent,getgrgid,%
+      getgrnam,gethostbyaddr,gethostbyname,gethostent,getlogin,%
+      getnetbyaddr,getnetbyname,getnetent,getpeername,getpgrp,%
+      getppid,getpriority,getprotobyname,getprotobynumber,getprotoent,%
+      getpwent,getpwnam,getpwuid,getservbyname,getservbyport,%
+      getservent,getsockname,getsockopt,glob,gmtime,goto,grep,hex,if,%
+      import,index,int,ioctl,join,keys,kill,last,lc,lcfirst,length,%
+      link,listen,local,localtime,log,lstat,m,map,mkdir,msgctl,msgget,%
+      msgrcv,msgsnd,my,next,no,oct,open,opendir,ord,pack,package,pipe,%
+      pop,pos,print,printf,prototype,push,q,qq,quotemeta,qw,qx,rand,%
+      read,readdir,readlink,recv,redo,ref,rename,require,reset,return,%
+      reverse,rewinddir,rindex,rmdir,s,scalar,seek,seekdir,select,%
+      semctl,semget,semop,send,setgrent,sethostent,setnetent,setpgrp,%
+      setpriority,setprotoent,setpwent,setservent,setsockopt,shift,%
+      shmctl,shmget,shmread,shmwrite,shutdown,sin,sleep,socket,%
+      socketpair,sort,splice,split,sprintf,sqrt,srand,stat,study,sub,%
+      substr,symlink,syscall,sysopen,sysread,system,syswrite,tell,%
+      telldir,tie,tied,time,times,tr,truncate,uc,ucfirst,umask,undef,%
+      unless,unlink,unpack,unshift,untie,until,use,utime,values,vec,%
+      wait,waitpid,wantarray,warn,while,write,y},%
+   sensitive,%
+   morecomment=[l]\#,%
+   morestring=[b]",%
+   morestring=[b]',%
+   MoreSelectCharTable=%
+      \lst@ReplaceInput{\$\#}{\lst@ProcessOther\$\lst@ProcessOther\#}%
+  }[keywords,comments,strings]%
+%%
+%% POV definition (c) 1999 Berthold H\"ollmann
+%%
+\lst@definelanguage{POV}%
+  {morekeywords={abs,absorption,acos,acosh,adaptive,adc_bailout,agate,%
+      agate_turb,all,alpha,ambient,ambient_light,angle,aperture,append,%
+      arc_angle,area_light,array,asc,asin,asinh,assumed_gamma,atan,%
+      atan2,atanh,average,background,bezier_spline,bicubic_patch,%
+      black_hole,blob,blue,blur_samples,bounded_by,box,boxed,bozo,%
+      break,brick,brick_size,brightness,brilliance,bumps,bump_map,%
+      bump_size,camera,case,caustics,ceil,checker,chr,clipped_by,clock,%
+      clock_delta,color,color_map,colour,colour_map,component,%
+      composite,concat,cone,confidence,conic_sweep,control0,control1,%
+      cos,cosh,count,crackle,crand,cube,cubic,cubic_spline,cubic_wave,%
+      cylinder,cylindrical,debug,declare,default,defined,degrees,%
+      density,density_file,density_map,dents,difference,diffuse,%
+      dimensions,dimension_size,direction,disc,distance,%
+      distance_maximum,div,eccentricity,else,emission,end,error,%
+      error_bound,exp,extinction,fade_distance,fade_power,falloff,%
+      falloff_angle,false,fclose,file_exists,filter,finish,fisheye,%
+      flatness,flip,floor,focal_point,fog,fog_alt,fog_offset,fog_type,%
+      fopen,frequency,gif,global_settings,gradient,granite,%
+      gray_threshold,green,height_field,hexagon,hf_gray_16,hierarchy,%
+      hollow,hypercomplex,if,ifdef,iff,ifndef,image_map,include,int,%
+      interior,interpolate,intersection,intervals,inverse,ior,irid,%
+      irid_wavelength,jitter,julia_fractal,lambda,lathe,leopard,%
+      light_source,linear_spline,linear_sweep,local,location,log,%
+      looks_like,look_at,low_error_factor,macro,mandel,map_type,marble,%
+      material,material_map,matrix,max,max_intersections,max_iteration,%
+      max_trace_level,media,media_attenuation,media_interaction,merge,%
+      mesh,metallic,min,minimum_reuse,mod,mortar,nearest_count,no,%
+      normal,normal_map,no_shadow,number_of_waves,object,octaves,off,%
+      offset,omega,omnimax,on,once,onion,open,orthographic,panoramic,%
+      perspective,pgm,phase,phong,phong_size,pi,pigment,pigment_map,%
+      planar,plane,png,point_at,poly,polygon,poly_wave,pot,pow,ppm,%
+      precision,prism,pwr,quadratic_spline,quadric,quartic,quaternion,%
+      quick_color,quick_colour,quilted,radial,radians,radiosity,radius,%
+      rainbow,ramp_wave,rand,range,ratio,read,reciprocal,%
+      recursion_limit,red,reflection,reflection_exponent,refraction,%
+      render,repeat,rgb,rgbf,rgbft,rgbt,right,ripples,rotate,roughness,%
+      samples,scale,scallop_wave,scattering,seed,shadowless,sin,%
+      sine_wave,sinh,sky,sky_sphere,slice,slope_map,smooth,%
+      smooth_triangle,sor,specular,sphere,spherical,spiral1,spiral2,%
+      spotlight,spotted,sqr,sqrt,statistics,str,strcmp,strength,strlen,%
+      strlwr,strupr,sturm,substr,superellipsoid,switch,sys,t,tan,tanh,%
+      text,texture,texture_map,tga,thickness,threshold,tightness,tile2,%
+      tiles,torus,track,transform,translate,transmit,triangle,%
+      triangle_wave,true,ttf,turbulence,turb_depth,type,u,%
+      ultra_wide_angle,undef,union,up,use_color,use_colour,use_index,%
+      u_steps,v,val,variance,vaxis_rotate,vcross,vdot,version,vlength,%
+      vnormalize,vrotate,v_steps,warning,warp,water_level,waves,while,%
+      width,wood,wrinkles,write,x,y,yes,z},%
+   moredirectives={break,case,debug,declare,default,else,end,fclose,%
+      fopen,local,macro,read,render,statistics,switch,undef,version,%
+      warning,write},%
+   moredelim=*[directive]\#,%
+   sensitive,%
+   morecomment=[l]//,%
+   morecomment=[s]{/*}{*/},%
+   morestring=[d]",%
+  }[keywords,directives,comments,strings]%
+%%
+%% Python definition (c) 1998 Michael Weber
+%% Additional definitions (2013) Alexis Dimitriadis
+%%
+\lst@definelanguage{Python}%
+  {morekeywords={access,and,break,class,continue,def,del,elif,else,%
+      except,exec,finally,for,from,global,if,import,in,is,lambda,not,%
+      or,pass,print,raise,return,try,while},%
+  % Built-ins
+   morekeywords=[2]{abs,all,any,basestring,bin,bool,bytearray,callable,chr,
+     classmethod,cmp,compile,complex,delattr,dict,dir,divmod,enumerate,eval,
+     execfile,file,filter,float,format,frozenset,getattr,globals,hasattr,hash,
+     help,hex,id,input,int,isinstance,issubclass,iter,len,list,locals,long,map,
+     max,memoryview,min,next,object,oct,open,ord,pow,property,range,raw_input,
+     reduce,reload,repr,reversed,round,set,setattr,slice,sorted,staticmethod,str,
+     sum,super,tuple,type,unichr,unicode,vars,xrange,zip,apply,buffer,coerce,
+     intern},
+   sensitive=true,%
+   morecomment=[l]\#,%
+   morestring=[b]',%
+   morestring=[b]",%
+   morecomment=[s]{'''}{'''},% used for documentation text (mulitiline strings)
+   morecomment=[s]{"""}{"""},% added by Philipp Matthias Hahn
+   morestring=[s]{r'}{'},% `raw' strings
+   morestring=[s]{r"}{"},%
+   morestring=[s]{r'''}{'''},%
+   morestring=[s]{r"""}{"""},%
+   morestring=[s]{u'}{'},% unicode strings
+   morestring=[s]{u"}{"},%
+   morestring=[s]{u'''}{'''},%
+   morestring=[s]{u"""}{"""}%
+  }%
+%%
+%% Scilab definition (c) 2002,2003 Jean-Philippe Grivet
+%%
+\lst@definelanguage{Scilab}%
+  {morekeywords={abcd,abinv,abort,abs,acoshm,acosh,acosm,acos,addcolor,%
+      addf,addinter,addmenu,add_edge,add_node,adj2sp,adj_lists,aff2ab,%
+      amell,analpf,analyze,ans,apropos,arc_graph,arc_number,argn,arhnk,%
+      arl2,arma2p,armac,armax1,armax,arma,arsimul,artest,articul,ascii,%
+      asinhm,asinh,asinm,asin,atanhm,atanh,atanm,atan,augment,auread,%
+      auwrite,balanc,balreal,bandwr,basename,bdiag,besseli,besselj,%
+      besselk,bessely,best_match,bezout,bifish,bilin,binomial,black,%
+      bloc2exp,bloc2ss,bode,bool2s,boolean,boucle,break,bstap,buttmag,%
+      bvode,cainv,calerf,calfrq,call,canon,casc,case,ccontrg,cdfbet,%
+      cdfbin,cdfchi,cdfchn,cdffnc,cdff,cdfgam,cdfnbn,cdfnor,cdfpoi,%
+      cdft,ceil,center,cepstrum,chaintest,chain_struct,champ1,champ,%
+      chart,chdir,cheb1mag,cheb2mag,check_graph,check_io,chepol,chfact,%
+      chol,chsolve,circuit,classmarkov,clean,clearfun,clearglobal,%
+      clear,close,cls2dls,cmb_lin,cmndred,cmoment,code2str,coeff,coffg,%
+      coff,colcompr,colcomp,colinout,colormap,colregul,companion,comp,%
+      cond,conj,connex,contour2di,contour2d,contourf,contour,%
+      contract_edge,contrss,contr,cont_frm,cont_mat,convex_hull,convol,%
+      convstr,con_nodes,copfac,copy,correl,corr,coshm,cosh,cosm,cos,%
+      cotg,cothm,coth,covar,csim,cspect,ctr_gram,cumprod,cumsum,%
+      curblock,cycle_basis,czt,c_link,dasrt,dassl,datafit,date,dbphi,%
+      dcf,ddp,debug,dec2hex,deff,definedfields,degree,delbpt,%
+      delete_arcs,delete_nodes,delete,delip,delmenu,demos,denom,%
+      derivative,derivat,des2ss,des2tf,determ,detr,det,dft,dhinf,%
+      dhnorm,diag,diary,diff,diophant,dirname,dispbpt,dispfiles,disp,%
+      dlgamma,double,dragrect,drawaxis,drawlater,drawnow,draw,driver,%
+      dscr,dsearch,dsimul,dtsi,dt_ility,duplicate,edge_number,%
+      edit_curv,edit_graph_menus,edit_graph,edit,eigenmarkov,ell1mag,%
+      elseif,else,emptystr,endfunction,end,eqfir,eqiir,equil1,equil,%
+      ereduc,erfcx,erfc,erf,errbar,errcatch,errclear,error,eval3dp,%
+      eval3d,eval,evans,evstr,excel2sci,execstr,exec,exists,exit,expm,%
+      exp,external,eye,fac3d,factors,faurre,fchamp,fcontour2d,fcontour,%
+      fec,feedback,feval,ffilt,fftshift,fft,fgrayplot,figure,fileinfo,%
+      file,filter,findm,findobj,findx0BD,find_freq,find_path,find,%
+      findABCD,findAC,findBD,findBDK,findR,fit_dat,fix,floor,flts,foo,%
+      formatman,format,fort,for,fourplan,fplot2d,fplot3d1,fplot3d,%
+      fprintf,fprintfMat,frep2tf,freq,freson,frexp,frfit,frmag,fscanf,%
+      fscanfMat,fsfirlin,fsolve,fspecg,fstabst,fstair,ftest,ftuneq,%
+      fullrfk,fullrf,full,fun2string,funcprot,functions,function,%
+      funptr,fusee,gainplot,gamitg,gammaln,gamma,gcare,gcd,gcf,%
+      genfac3d,genlib,genmarkov,gen_net,geom3d,geomean,getblocklabel,%
+      getcolor,getcurblock,getcwd,getdate,getd,getenv,getfield,getfont,%
+      getf,getio,getlinestyle,getmark,getpid,getscicosvars,getsymbol,%
+      getvalue,getversion,get_function_path,get,gfare,gfrancis,girth,%
+      givens,glever,glist,global,glue,gpeche,graduate,grand,%
+      graphics_entities,graph_2_mat,graph_center,graph_complement,%
+      graph_diameter,graph_power,graph_simp,graph_sum,graph_union,%
+      graph-list,graycolormap,grayplot,graypolarplot,grep,group,%
+      gr_menu,gschur,gsort,gspec,gstacksize,gtild,g_margin,h2norm,halt,%
+      hamilton,hankelsv,hank,harmean,havewindow,help,hermit,hess,%
+      hex2dec,hilb,hinf,hist3d,histplot,horner,host,hotcolormap,%
+      householder,hrmt,htrianr,hypermat,h_cl,h_inf_st,h_inf,h_norm,%
+      iconvert,icon_edit,ieee,if,iirgroup,iirlp,iir,ilib_build,%
+      ilib_compile,ilib_for_link,ilib_gen_gateway,ilib_gen_loader,%
+      ilib_gen_Make,imag,impl,imrep2ss,imult,im_inv,inistate,input,%
+      int16,int2d,int32,int3d,int8,intc,intdec,integrate,interpln,%
+      interp,intersci,intersect,intg,intl,intppty,intsplin,inttrap,%
+      inttype,int,invr,invsyslin,inv_coeff,inv,iqr,isdef,isdir,isequal,%
+      iserror,isglobal,isinf,isnan,isoview,isreal,is_connex,jmat,%
+      justify,kalm,karmarkar,kernel,keyboard,knapsack,kpure,krac2,%
+      kroneck,kron,lasterror,lattn,lattp,lcf,lcmdiag,lcm,ldivf,ldiv,%
+      leastsq,legends,length,leqr,levin,lev,lex_sort,lft,lgfft,library,%
+      lib,lin2mu,lincos,lindquist,lines,line_graph,linfn,linf,link,%
+      linmeq,linpro,linsolve,linspace,lin,listfiles,list,lmisolver,%
+      lmitool,loadmatfile,loadplots,loadwave,load_graph,load,locate,%
+      log10,log1p,log2,logm,logspace,log,lotest,lqe,lqg2stan,lqg_ltr,%
+      lqg,lqr,lsq,lsslist,lstcat,lstsize,ltitr,ludel,lufact,luget,%
+      lusolve,lu,lyap,macglov,macr2lst,macrovar,macro,mad,make_graph,%
+      make_index,manedit,man,mapsound,markp2ss,matfile2sci,matrix,%
+      mat_2_graph,maxi,max_cap_path,max_clique,max_flow,max,mclearerr,%
+      mclose,meanf,mean,median,meof,mese,mesh2d,mfft,mfile2sci,mgeti,%
+      mgetl,mgetstr,mget,milk_drop,mine,mini,minreal,minss,%
+      min_lcost_cflow,min_lcost_flow1,min_lcost_flow2,min_qcost_flow,%
+      min_weight_tree,min,mlist,mode,modulo,moment,mopen,move,%
+      mps2linpro,mputl,mputstr,mput,mrfit,msd,mseek,mtell,mtlb_load,%
+      mtlb_mode,mtlb_save,mtlb_sparse,mu2lin,mulf,mvvacov,m_circle,%
+      names,nand2mean,nanmax,nanmeanf,nanmean,nanmedian,nanmin,%
+      nanstdev,nansum,narsimul,ndims,nearfloat,nehari,neighbors,%
+      netclose,netwindows,netwindow,newest,newfun,nextpow2,nf3d,nfreq,%
+      nlev,nnz,nodes_2_path,nodes_degrees,node_number,noisegen,norm,%
+      null,numdiff,numer,nyquist,obscont1,obscont,observer,obsvss,%
+      obsv_mat,obs_gram,odedc,odedi,odeoptions,ode_discrete,ode_root,%
+      ode,oldload,oldsave,ones,optim,orth,param3d1,param3d,%
+      paramfplot2d,parrot,part,pathconvert,path_2_nodes,pause,pbig,%
+      pdiv,pen2ea,pencan,penlaur,perctl,perfect_match,pertrans,pfss,%
+      phasemag,phc,pinv,pipe_network,playsnd,plot2d1,plot2d2,plot2d3,%
+      plot2d4,plot2d,plot3d1,plot3d2,plot3d3,plot3d,plotframe,%
+      plotprofile,plot_graph,plot,plzr,pmodulo,pol2des,pol2str,pol2tex,%
+      polarplot,polar,polfact,poly,portr3d,portrait,power,ppol,prbs_a,%
+      predecessors,predef,printf,printing,print,prod,profile,projsl,%
+      projspec,proj,psmall,pspect,pvm_addhosts,pvm_barrier,pvm_bcast,%
+      pvm_bufinfo,pvm_config,pvm_delhosts,pvm_error,pvm_exit,%
+      pvm_f772sci,pvm_getinst,pvm_gettid,pvm_get_timer,pvm_gsize,%
+      pvm_halt,pvm_joingroup,pvm_kill,pvm_lvgroup,pvm_mytid,pvm_parent,%
+      pvm_probe,pvm_recv,pvm_reduce,pvm_sci2f77,pvm_send,pvm_set_timer,%
+      pvm_spawn_independent,pvm_spawn,pvm_start,pvm_tasks,%
+      pvm_tidtohost,pvm,pwd,p_margin,qassign,qr,quapro,quart,quaskro,%
+      quit,randpencil,rand,range,rankqr,rank,rat,rcond,rdivf,read4b,%
+      readb,readc_,readmps,read,real,recur,reglin,regress,remezb,remez,%
+      repfreq,replot,residu,resume,return,riccati,riccsl,ricc,ric_desc,%
+      rlist,roots,rotate,round,routh_t,rowcompr,rowcomp,rowinout,%
+      rowregul,rowshuff,rpem,rref,rtitr,rubberbox,salesman,savewave,%
+      save_graph,save,scaling,scanf,schur,sci2exp,sci2for,sci2map,%
+      sciargs,scicosim,scicos,scifunc_block,sd2sci,secto3d,select,%
+      semidef,sensi,setbpt,seteventhandler,setfield,setmenu,%
+      setscicosvars,set,sfact,sgrid,shortest_path,showprofile,%
+      show_arcs,show_graph,show_nodes,sident,signm,sign,simp_mode,simp,%
+      sincd,sinc,sinc,sinhm,sinh,sinm,sin,size,sm2des,sm2ss,smooth,%
+      solve,sorder,sort,sound,sp2adj,spaninter,spanplus,spantwo,sparse,%
+      spchol,spcompack,specfact,spec,speye,spget,splin,split_edge,%
+      spones,sprand,sprintf,spzeros,sqroot,sqrtm,sqrt,squarewave,%
+      square,srfaur,srkf,ss2des,ss2ss,ss2tf,sscanf,sskf,ssprint,ssrand,%
+      stabil,stacksize,standard_define,standard_draw,standard_input,%
+      standard_origin,standard_output,startup,stdevf,stdev,steadycos,%
+      str2code,strange,strcat,strindex,strings,string,stripblanks,%
+      strong_connex,strong_con_nodes,strsubst,st_deviation,st_ility,%
+      subf,subgraph,subplot,successors,sum,supernode,sva,svd,svplot,%
+      sylm,sylv,sysconv,sysdiag,sysfact,syslin,syssize,systems,system,%
+      systmat,tabul,tangent,tanhm,tanh,tanm,tan,tdinit,testmatrix,%
+      texprint,tf2des,tf2ss,then,thrownan,timer,time_id,titlepage,%
+      tk_getdir,tk_getfile,tlist,toeplitz,tokenpos,tokens,trace,%
+      translatepaths,trans_closure,trans,trfmod,trianfml,tril,trimmean,%
+      trisolve,triu,trzeros,typename,typeof,type,uicontrol,uimenu,%
+      uint16,uint32,uint8,ui_observer,ulink,unglue,union,unique,unix_g,%
+      unix_s,unix_w,unix_x,unix,unobs,unsetmenu,user,varargin,%
+      varargout,variancef,variance,varn,warning,wavread,wavwrite,%
+      wcenter,wfir,what,whereami,whereis,where,while,whos,who_user,who,%
+      wiener,wigner,window,winsid,with_gtk,with_pvm,with_texmacs,%
+      with_tk,writb,write4b,write,xarcs,xarc,xarrows,xaxis,xbasc,%
+      xbasimp,xbasr,xchange,xclear,xclea,xclick,xclip,xdel,xend,xfarcs,%
+      xfarc,xfpolys,xfpoly,xfrect,xgetech,xgetfile,xgetmouse,xget,%
+      xgraduate,xgrid,xinfo,xinit,xlfont,xload,xname,xnumb,xpause,%
+      xpolys,xpoly,xrects,xrect,xrpoly,xs2fig,xs2gif,xs2ppm,xs2ps,%
+      xsave,xsegs,select,xsetech,xsetm,xset,xstringb,xstringl,xstring,%
+      xtape,xtitle,x_choices,x_choose,x_dialog,x_matrix,x_mdialog,%
+      x_message_modeless,x_message,yulewalk,zeropen,zeros,zgrid,zpbutt,%
+      zpch1,zpch2,zpell,mfprintf,mfscanf,mprintf,mscanf,msprintf,%
+      msscanf,mucomp,%
+      ABSBLK_f,AFFICH_f,ANDLOG_f,ANIMXY_f,BIGSOM_f,CLINDUMMY_f,CLKIN_f,%
+      CLKINV_f,CLKOUT_f,CLKOUTV_f,CLKSOM_f,CLKSOMV_f,CLKSPLIT_f,%
+      CLOCK_f,CLR_f,CLSS_f,CONST_f,COSBLK_f,CURV_f,DELAY_f,DELAYV_f,%
+      DEMUX_f,DLR_f,DLRADAPT_f,DLSS_f,EVENTSCOPE_f,EVTDLY_f,EVTGEN_f,%
+      EXPBLK_f,G_make,GAIN_f,GAINBLK_f,GENERAL_f,GENERIC_f,GENSIN_f,%
+      GENSQR_f,HALT_f,IFTHEL_f,IN_f,INTEGRAL_f,INTRP2BLK_f,INTRPLBLK_f,%
+      INVBLK_f,LOGBLK_f,LOOKUP_f,Matplot1,Matplot,MAX_f,MCLOCK_f,%
+      MFCLCK_f,MIN_f,MUX_f,NDcost,NEGTOPOS_f,OUT_f,POSTONEG_f,POWBLK_f,%
+      PROD_f,QUANT_f,RAND_f,READC_f,REGISTER_f,RELAY_f,RFILE_f,%
+      ScilabEval,Sfgrayplot,Sgrayplot,SAMPLEHOLD_f,SAT_f,SAWTOOTH_f,%
+      SCOPE_f,SCOPXY_f,SELECT_f,SINBLK_f,SOM_f,SPLIT_f,STOP_f,SUPER_f,%
+      TANBLK_f,TCLSS_f,TEXT_f,TIME_f,TK_EvalFile,TK_EvalStr,TK_GetVar,%
+      TK_SetVar,TRASH_f,WFILE_f,WRITEC_f,ZCROSS_f,%
+      \%asn,\%helps,\%k,\%sn},%
+   alsoletter=\%,% chmod
+   sensitive,%
+   morecomment=[l]//,%
+   morestring=[b]",%
+   morestring=[m]'%
+  }[keywords,comments,strings]%
+%%
+%% SQL definition (c) 1998 Christian Haul
+%%                (c) 2002 Neil Conway
+%%                (c) 2002 Robert Frank
+%%                (c) 2003 Dirk Jesko
+%%
+\lst@definelanguage{SQL}%
+  {morekeywords={ABSOLUTE,ACTION,ADD,ALLOCATE,ALTER,ARE,AS,ASSERTION,%
+      AT,BETWEEN,BIT_LENGTH,BOTH,BY,CASCADE,CASCADED,CASE,CAST,%
+      CATALOG,CHAR_LENGTH,CHARACTER_LENGTH,CLUSTER,COALESCE,%
+      COLLATE,COLLATION,COLUMN,CONNECT,CONNECTION,CONSTRAINT,%
+      CONSTRAINTS,CONVERT,CORRESPONDING,CREATE,CROSS,CURRENT_DATE,%
+      CURRENT_TIME,CURRENT_TIMESTAMP,CURRENT_USER,DAY,DEALLOCATE,%
+      DEC,DEFERRABLE,DEFERED,DESCRIBE,DESCRIPTOR,DIAGNOSTICS,%
+      DISCONNECT,DOMAIN,DROP,ELSE,END,EXEC,EXCEPT,EXCEPTION,EXECUTE,%
+      EXTERNAL,EXTRACT,FALSE,FIRST,FOREIGN,FROM,FULL,GET,GLOBAL,%
+      GRAPHIC,HAVING,HOUR,IDENTITY,IMMEDIATE,INDEX,INITIALLY,INNER,%
+      INPUT,INSENSITIVE,INSERT,INTO,INTERSECT,INTERVAL,%
+      ISOLATION,JOIN,KEY,LAST,LEADING,LEFT,LEVEL,LIMIT,LOCAL,LOWER,%
+      MATCH,MINUTE,MONTH,NAMES,NATIONAL,NATURAL,NCHAR,NEXT,NO,NOT,NULL,%
+      NULLIF,OCTET_LENGTH,ON,ONLY,ORDER,ORDERED,OUTER,OUTPUT,OVERLAPS,%
+      PAD,PARTIAL,POSITION,PREPARE,PRESERVE,PRIMARY,PRIOR,READ,%
+      RELATIVE,RESTRICT,REVOKE,RIGHT,ROWS,SCROLL,SECOND,SELECT,SESSION,%
+      SESSION_USER,SIZE,SPACE,SQLSTATE,SUBSTRING,SYSTEM_USER,%
+      TABLE,TEMPORARY,THEN,TIMEZONE_HOUR,%
+      TIMEZONE_MINUTE,TRAILING,TRANSACTION,TRANSLATE,TRANSLATION,TRIM,%
+      TRUE,UNIQUE,UNKNOWN,UPPER,USAGE,USING,VALUE,VALUES,%
+      VARGRAPHIC,VARYING,WHEN,WHERE,WRITE,YEAR,ZONE,%
+      AND,ASC,avg,CHECK,COMMIT,count,DECODE,DESC,DISTINCT,GROUP,IN,% FF
+      LIKE,NUMBER,ROLLBACK,SUBSTR,sum,VARCHAR2,% FF
+      MIN,MAX,UNION,UPDATE,% RF
+      ALL,ANY,CUBE,CUBE,DEFAULT,DELETE,EXISTS,GRANT,OR,RECURSIVE,% DJ
+      ROLE,ROLLUP,SET,SOME,TRIGGER,VIEW},% DJ
+   morendkeywords={BIT,BLOB,CHAR,CHARACTER,CLOB,DATE,DECIMAL,FLOAT,% DJ
+      INT,INTEGER,NUMERIC,SMALLINT,TIME,TIMESTAMP,VARCHAR},% moved here
+   sensitive=false,% DJ
+   morecomment=[l]--,%
+   morecomment=[s]{/*}{*/},%
+   morestring=[d]',%
+   morestring=[d]"%
+  }[keywords,comments,strings]%
+%%
+%% VHDL definition (c) 1997 Kai Wollenweber
+%%
+\lst@definelanguage{VHDL}%
+  {morekeywords={ALL,ARCHITECTURE,ABS,AND,ASSERT,ARRAY,AFTER,ALIAS,%
+      ACCESS,ATTRIBUTE,BEGIN,BODY,BUS,BLOCK,BUFFER,CONSTANT,CASE,%
+      COMPONENT,CONFIGURATION,DOWNTO,ELSE,ELSIF,END,ENTITY,EXIT,%
+      FUNCTION,FOR,FILE,GENERIC,GENERATE,GUARDED,GROUP,IF,IN,INOUT,IS,%
+      INERTIAL,IMPURE,LIBRARY,LOOP,LABEL,LITERAL,LINKAGE,MAP,MOD,NOT,%
+      NOR,NAND,NULL,NEXT,NEW,OUT,OF,OR,OTHERS,ON,OPEN,PROCESS,PORT,%
+      PACKAGE,PURE,PROCEDURE,POSTPONED,RANGE,REM,ROL,ROR,REPORT,RECORD,%
+      RETURN,REGISTER,REJECT,SIGNAL,SUBTYPE,SLL,SRL,SLA,SRA,SEVERITY,%
+      SELECT,THEN,TYPE,TRANSPORT,TO,USE,UNITS,UNTIL,VARIABLE,WHEN,WAIT,%
+      WHILE,XOR,XNOR,%
+      DISCONNECT,ELIF,WITH},% Arnaud Tisserand
+   sensitive=f,% 1998 Gaurav Aggarwal
+   morecomment=[l]--,%
+   morestring=[d]{"}%
+  }[keywords,comments,strings]%
+%%
+%% VHDL-AMS definition (c) Steffen Klupsch
+%%
+\lst@definelanguage[AMS]{VHDL}[]{VHDL}%
+  {morekeywords={ACROSS,ARRAY,BREAK,DISCONNECT,NATURE,NOISE,PORT,%
+      PROCEDURAL,QUANTITY,SHARED,SPECTRUM,SUBNATURE,TERMINAL,THROUGH,%
+      TOLERANCE,UNAFFACTED,UNITS}}
+\lst@definelanguage{XSLT}[]{XML}%
+  {morekeywords={%
+     % main elements
+     xsl:stylesheet,xsl:transform,%
+     % childs of the main element
+     xsl:apply-imports,xsl:attribute-set,xsl:decimal-format,xsl:import,%
+     xsl:include,xsl:key,xsl:namespace-alias,xsl:output,xsl:param,%
+     xsl:preserve-space,xsl:strip-space,xsl:template,xsl:variable,%
+     % 21 directives
+     xsl:apply-imports,xsl:apply-templates,xsl:attribute,%
+     xsl:call-template,xsl:choose,xsl:comment,xsl:copy,xsl:copy-of,%
+     xsl:element,xsl:fallback,xsl:for-each,xsl:if,xsl:message,%
+     xsl:number,xsl:otherwise,xsl:processing-instruction,xsl:text,%
+     xsl:value-of,xsl:variable,xsl:when,xsl:with-param},%
+   alsodigit={-},%
+  }%
+\lst@definelanguage{Ant}[]{XML}%
+  {morekeywords={%
+     project,target,patternset,include,exclude,excludesfile,includesfile,filterset,%
+     filter,filtersfile,libfileset,custom,classpath,fileset,none,depend,mapper,%
+     filename,not,date,contains,selector,depth,or,and,present,majority,size,dirset,%
+     filelist,pathelement,path,param,filterreader,extension,filterchain,linecontainsregexp,%
+     regexp,classconstants,headfilter,tabstospaces,striplinebreaks,tailfilter,stripjavacomments,%
+     expandproperties,linecontains,replacetokens,token,striplinecomments,comment,prefixlines,%
+     classfileset,rootfileset,root,description,xmlcatalog,entity,dtd,substitution,%
+     extensionSet,propertyfile,entry,vsscheckin,sql,transaction,cvspass,csc,%
+     dirname,wlrun,wlclasspath,p4label,replaceregexp,get,jjtree,sleep,jarlib,%
+     dependset,targetfileset,srcfileset,srcfilelist,targetfilelist,zip,zipgroupfileset,zipfileset,%
+     patch,jspc,webapp,style,test,arg,jvmarg,sysproperty,testlet,env,tstamp,%
+     format,unwar,vsshistory,icontract,cvschangelog,user,p4submit,ccmcheckin,%
+     p4change,bzip2,vssadd,javadoc,bottom,source,doctitle,header,excludepackage,bootclasspath,%
+     doclet,taglet,packageset,sourcepath,link,footer,package,group,title,tag,%
+     translate,signjar,vajload,vajproject,jarlib,extensionset,WsdlToDotnet,buildnumber,%
+     jpcovmerge,tomcat,ejbjar,weblogictoplink,jboss,borland,weblogic,iplanet,jonas,%
+     support,websphere,wasclasspath,war,manifest,attribute,section,metainf,lib,%
+     classes,webinf,rename,sequential,serverdeploy,generic,property,move,%
+     copydir,cccheckin,wljspc,fixcrlf,sosget,pathconvert,map,record,p4sync,exec,%
+     p4edit,maudit,rulespath,searchpath,antlr,netrexxc,jpcovreport,reference,filters,%
+     coveragepath,execon,targetfile,srcfile,ccmcheckout,ant,xmlvalidate,xslt,%
+     iplanet,ccmcheckintask,gzip,native2ascii,starteam,ear,archives,input,%
+     rmic,extdirs,compilerarg,checksum,mail,bcc,message,cc,to,from,loadfile,vsscheckout,%
+     stylebook,soscheckin,mimemail,stlabel,gunzip,concat,cab,touch,parallel,splash,%
+     antcall,cccheckout,typedef,p4have,xmlproperty,copy,tomcat,antstructure,ccmcreatetask,%
+     rpm,delete,replace,replacefilter,replacetoken,replacevalue,mmetrics,waitfor,isfalse,%
+     equals,available,filepath,os,filesmatch,istrue,isset,socket,http,uptodate,srcfiles,%
+     untar,loadproperties,echoproperties,vajexport,stcheckout,bunzip2,copyfile,vsscreate,%
+     ejbc,unjar,tomcat,wsdltodotnet,mkdir,condition,cvs,commandline,marker,argument,%
+     tempfile,junitreport,report,taskdef,echo,ccupdate,java,renameext,vsslabel,basename,%
+     javadoc2,vsscp,tar,tarfileset,tomcat,vajimport,setproxy,wlstop,p4counter,ilasm,%
+     soscheckout,apply,ccuncheckout,jarlib,location,url,cvstagdiff,jlink,mergefiles,%
+     addfiles,javacc,pvcs,pvcsproject,jarlib,options,depends,chmod,jar,sound,fail,%
+     success,mparse,blgenclient,genkey,dname,javah,class,ccmreconfigure,unzip,javac,%
+     src,p4add,soslabel,jpcoverage,triggers,method,vssget,deltree,ddcreator},
+   deletekeywords={default},%
+  }
+\lst@definelanguage{XML}%
+  {keywords={,CDATA,DOCTYPE,ATTLIST,termdef,ELEMENT,EMPTY,ANY,ID,%
+      IDREF,IDREFS,ENTITY,ENTITIES,NMTOKEN,NMTOKENS,NOTATION,%
+      INCLUDE,IGNORE,SYSTEM,PUBLIC,NDATA,PUBLIC,%
+      PCDATA,REQUIRED,IMPLIED,FIXED,%%% preceded by #
+      xml,xml:space,xml:lang,version,standalone,default,preserve},%
+   alsoother=$,%
+   alsoletter=:,%
+   tag=**[s]<>,%
+   morestring=[d]",% ??? doubled
+   morestring=[d]',% ??? doubled
+   MoreSelectCharTable=%
+      \lst@CArgX--\relax\lst@DefDelimB{}{}%
+          {\ifnum\lst@mode=\lst@tagmode\else
+               \expandafter\@gobblethree
+           \fi}%
+          \lst@BeginComment\lst@commentmode{{\lst@commentstyle}}%
+      \lst@CArgX--\relax\lst@DefDelimE{}{}{}%
+          \lst@EndComment\lst@commentmode
+      \lst@CArgX[CDATA[\relax\lst@CDef{}%
+          {\ifnum\lst@mode=\lst@tagmode
+               \expandafter\lst@BeginCDATA
+           \else \expandafter\lst@CArgEmpty
+           \fi}%
+          \@empty
+      \lst@CArgX]]\relax\lst@CDef{}%
+          {\ifnum\lst@mode=\lst@GPmode
+               \expandafter\lst@EndComment
+           \else \expandafter\lst@CArgEmpty
+           \fi}%
+          \@empty
+  }[keywords,comments,strings,html]%
+\endinput
+%%
+%% End of file `lstlang1.sty'.
diff --git a/dataflow/manual/lstlang2.sty b/dataflow/manual/lstlang2.sty
new file mode 100644
index 0000000..d9a8fa3
--- /dev/null
+++ b/dataflow/manual/lstlang2.sty
@@ -0,0 +1,1816 @@
+%%
+%% This is file `lstlang2.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% lstdrvrs.dtx  (with options: `lang2')
+%% 
+%% The listings package is copyright 1996--2004 Carsten Heinz, and
+%% continued maintenance on the package is copyright 2006--2007 Brooks
+%% Moses. From 2013 on the maintenance is done by Jobst Hoffmann.
+%% The drivers are copyright 1997/1998/1999/2000/2001/2002/2003/2004/2006/
+%% 2007/2013 any individual author listed in this file.
+%%
+%% This file is distributed under the terms of the LaTeX Project Public
+%% License from CTAN archives in directory  macros/latex/base/lppl.txt.
+%% Either version 1.3 or, at your option, any later version.
+%%
+%% This file is completely free and comes without any warranty.
+%%
+%% Send comments and ideas on the package, error reports and additional
+%% programming languages to Jobst Hoffmann at <j.hoffmann@fh-aachen.de>.
+%%
+\ProvidesFile{lstlang2.sty}
+    [2015/06/04 1.6 listings language file]
+%%
+%% Abap definition by Knut Lickert
+%%
+\lst@definelanguage[R/3 6.10]{ABAP}[R/3 4.6C]{ABAP}%
+  {morekeywords={try,endtry},%
+  }[keywords,comments,strings]
+\lst@definelanguage[R/3 4.6C]{ABAP}[R/3 3.1]{ABAP}%
+  {morekeywords={method,ref,class,create,object,%
+        methods,endmethod,private,protected,public,section,%
+        catch,system-exceptions,endcatch,%
+        },%
+   moreprocnamekeys={class},%
+   literate={->}{{$\rightarrow$}}1{=>}{{$\Rightarrow$}}1,%
+  }[keywords,comments,strings,procnames]
+\lst@definelanguage[R/3 3.1]{ABAP}[R/2 5.0]{ABAP}{}%
+\lst@definelanguage[R/2 5.0]{ABAP}%
+  {sensitive=f,%
+   procnamekeys={report,program,form,function,module},%
+   morekeywords={*,add,after,alias,analyzer,and,append,appending,area,assign,at,%
+        authority-check,before,binary,blank,break-point,calendar,call,%
+        case,change,changing,check,clear,cnt,co,collect,commit,common,%
+        component,compute,condense,corresponding,cos,cp,cs,currency-conversion,%
+        cursor,data,database,dataset,decimals,define,delete,deleting,dequeue,%
+        describe,detail,dialog,directory,div,divide,do,documentation,%
+        during,dynpro,else,end-of-page,end-of-selection,endat,endcase,%
+        enddo,endfor,endform,endif,endloop,endmodule,endselect,%
+        endwhile,enqueue,exceptions,exit,exp,export,exporting,extract,%
+        field,fields,field-groups,field-symbols,find,for,form,format,free,%
+        from,function,generating,get,giving,hide,id,if,import,%
+        importing,in,incl,include,initial,initialization,input,insert,%
+        interrupt,into,is,language,leave,leading,left-justified,like,line,lines,line-count,
+        line-selection,list-processing,load,local,log,logfile,loop,%
+        margin,mark,mask,memory,menue,message,mod,modify,module,move,%
+        move-text,multiply,na,new,new-line,new-page,no-gaps,np,ns,%
+        number,obligatory,occurs,of,on,or,others,output,parameter,%
+        parameters,parts,perform,pf-status,places,position,process,%
+        raise,raising,ranges,read,refresh,refresh-dynpro,reject,remote,%
+        replace,report,reserve,reset,restart,right-justified,run,screen,scroll,search,%
+        segments,select,select-options,selection-screen,set,shift,sin,%
+        single,sqrt,start-of-selection,statement,structure,submit,%
+        subtract,summary,summing,suppress,system,table,tables,task,%
+        text,time,to,top-of-page,trace,transaction,transfer,%
+        transfer-dynpro,translate,type,unpack,update,user-command,%
+        using,value,when,where,while,window,with,workfile,write,},%
+   morecomment=[l]",%
+   morecomment=[f][commentstyle][0]*,%
+   morestring=[d]'%
+  }[keywords,comments,strings,procnames]
+\lst@definelanguage[R/2 4.3]{ABAP}[R/2 5.0]{ABAP}%
+  {deletekeywords={function,importing,exporting,changing,exceptions,%
+        raise,raising}%
+  }[keywords,comments,strings]
+%%
+%% ACM and ACMscript definition
+%% (c) 2013 Stefan Pinnow
+%%
+\lst@definelanguage{ACM}{
+  morekeywords={
+    abs,After,acos,And,As,asin,atan,At,Call,Compatibility,Connect,cos,cosh,%
+    Create,Delay,Description,Difference,Do,Else,ElseIf,End,EndFor,EndIf,%
+    EndParallel,EndState,EndSwitch,EndText,EndWith,exp,External,Fixed,For,%
+    ForEach,Free,Global,Hidden,If,Implementation,In,Initial,Input,InterSection,%
+    IntegerSet,Invoke,Is,Language,Library,Link,Log10,LogE,Max,Min,Model,Of,%
+    Once,Options,Output,Parallel,Parameter,Pause,Port,Print,Private,%
+    Procedure,Product,Ramp,Repeat,Restart,Return,Round,Runs,Sigma,sin,sinh,%
+    Size,SnapShot,sqr,sqrt,SRamp,State,Stream,StringSet,Structure,Switch,%
+    SubRoutine,SymDiff,tan,tanh,Task,Text,Time,Then,Truncate,Union,Until,%
+    Uses,Variable,Wait,When,With,WithIn,WorkSpace%
+  },%
+  sensitive=false,%
+  morecomment=[l]{//},%
+  morecomment=[s]{/*}{*/},%
+  string=[b]{"},%
+}[keywords,comments,strings]%
+\lst@definelanguage{ACMscript}[]{VBScript}{%
+  morekeywords={%
+    ElseIf,False,In,Resume,True%
+  },%
+  deletekeywords={%
+    Abs,Array,Clear,CreateObject,CStr,Err,ForReading,ForWriting,%
+    OpenTextFile,Replace,WriteLine%
+  }%
+}[keywords,comments,strings]%
+%%
+%% Corba IDL definition (c) 1999 Jens T. Berger Thielemann
+%%
+\lst@definelanguage[CORBA]{IDL}%
+  {morekeywords={any,attribute,boolean,case,char,const,context,default,%
+      double,enum,exception,fixed,float,in,inout,interface,long,module,%
+      native,Object,octet,oneway,out,raises,readonly,sequence,short,%
+      string,struct,switch,typedef,union,unsigned,void,wchar,wstring,%
+      FALSE,TRUE},%
+   sensitive,%
+   moredirectives={define,elif,else,endif,error,if,ifdef,ifndef,line,%
+      include,pragma,undef,warning},%
+   moredelim=*[directive]\#,%
+   morecomment=[l]//,%
+   morecomment=[s]{/*}{*/},%
+   morestring=[b]"%
+  }[keywords,comments,strings,directives]%
+%%
+%% (Objective) Caml definition (c) 1999 Patrick Cousot
+%%
+%% Objective CAML and Caml light are freely available, together with a
+%% reference manual, at URL ftp.inria.fr/lang/caml-light for the Unix,
+%% Windows and Macintosh OS operating systems.
+%%
+\lst@definelanguage[Objective]{Caml}[light]{Caml}
+  {deletekeywords={not,prefix,value,where},%
+   morekeywords={assert,asr,class,closed,constraint,external,false,%
+      functor,include,inherit,land,lazy,lor,lsl,lsr,lxor,method,mod,%
+      module,new,open,parser,private,sig,struct,true,val,virtual,when,%
+      object,ref},% TH
+  }%
+\lst@definelanguage[light]{Caml}
+  {morekeywords={and,as,begin,do,done,downto,else,end,exception,for,%
+      fun,function,if,in,let,match,mutable,not,of,or,prefix,rec,then,%
+      to,try,type,value,where,while,with},%
+   sensitive,%
+   morecomment=[n]{(*}{*)},%
+   morestring=[b]",%
+   moredelim=*[directive]\#,%
+   moredirectives={open,close,include}%
+  }[keywords,comments,strings,directives]%
+\lst@definelanguage[ibm]{Cobol}[1985]{Cobol}%
+  {morekeywords={ADDRESS,BEGINNING,COMP-3,COMP-4,COMPUTATIONAL,%
+      COMPUTATIONAL-3,COMPUTATIONAL-4,DISPLAY-1,EGCS,EJECT,ENDING,%
+      ENTRY,GOBACK,ID,MORE-LABELS,NULL,NULLS,PASSWORD,RECORDING,%
+      RETURN-CODE,SERVICE,SKIP1,SKIP2,SKIP3,SORT-CONTROL,SORT-RETURN,%
+      SUPPRESS,TITLE,WHEN-COMPILED},%
+  }%
+\lst@definelanguage[1985]{Cobol}[1974]{Cobol}%
+  {morekeywords={ALPHABET,ALPHABETIC-LOWER,ALPHABETIC-UPPER,%
+      ALPHANUMERIC,ALPHANUMERIC-EDITED,ANY,CLASS,COMMON,CONTENT,%
+      CONTINUE,DAY-OF-WEEK,END-ADD,END-CALL,END-COMPUTE,END-DELETE,%
+      END-DIVIDE,END-EVALUATE,END-IF,END-MULTIPLY,END-PERFORM,END-READ,%
+      END-RECEIVE,END-RETURN,END-REWRITE,END-SEARCH,END-START,%
+      END-STRING,END-SUBTRACT,END-UNSTRING,END-WRITE,EVALUATE,EXTERNAL,%
+      FALSE,GLOBAL,INITIALIZE,NUMERIC-EDITED,ORDER,OTHER,%
+      PACKED-DECIMAL,PADDING,PURGE,REFERENCE,RELOAD,REPLACE,STANDARD-1,%
+      STANDARD-2,TEST,THEN,TRUE},%
+  }%
+\lst@definelanguage[1974]{Cobol}%
+  {morekeywords={ACCEPT,ACCESS,ADD,ADVANCING,AFTER,ALL,ALPHABETIC,ALSO,%
+      ALTER,ALTERNATE,AND,ARE,AREA,AREAS,ASCENDING,ASSIGN,AT,AUTHOR,%
+      BEFORE,BINARY,BLANK,BLOCK,BOTTOM,BY,CALL,CANCEL,CD,CF,CH,%
+      CHARACTER,CHARACTERS,CLOCK-UNITS,CLOSE,COBOL,CODE,CODE-SET,%
+      COLLATING,COLUMN,COMMA,COMMUNICATION,COMP,COMPUTE,CONFIGURATION,%
+      CONTAINS,CONTROL,CONTROLS,CONVERTING,COPY,CORR,CORRESPONDING,%
+      COUNT,CURRENCY,DATA,DATE,DATE-COMPILED,DATE-WRITTEN,DAY,DE,%
+      DEBUG-CONTENTS,DEGUB-ITEM,DEBUG-LINE,DEBUG-NAME,DEBUG-SUB1,%
+      DEBUG-SUB2,DEBUG-SUB3,DEBUGGING,DECIMAL-POINT,DECLARATIVES,%
+      DELETE,DELIMITED,DELIMITER,DEPENDING,DESCENDING,DESTINATION,%
+      DETAIL,DISABLE,DISPLAY,DIVIDE,DIVISION,DOWN,DUPLICATES,DYNAMIC,%
+      EGI,ELSE,EMI,ENABLE,END,END-OF-PAGE,ENTER,ENVIRONMENT,EOP,EQUAL,%
+      ERROR,ESI,EVERY,EXCEPTION,EXIT,EXTEND,FD,FILE,FILE-CONTROL,%
+      FILLER,FINAL,FIRST,FOOTING,FOR,FROM,GENERATE,GIVING,GO,GREATER,%
+      GROUP,HEADING,HIGH-VALUE,HIGH-VALUES,I-O,I-O-CONTROL,%
+      IDENTIFICATION,IF,IN,INDEX,INDEXED,INDICATE,INITIAL,INITIATE,%
+      INPUT,INPUT-OUTPUT,INSPECT,INSTALLATION,INTO,INVALID,IS,JUST,%
+      JUSTIFIED,KEY,LABEL,LAST,LEADING,LEFT,LENGTH,LESS,LIMIT,LIMITS,%
+      LINAGE,LINAGE-COUNTER,LINE,LINE-COUNTER,LINES,LINKAGE,LOCK,%
+      LOW-VALUE,LOW-VALUES,MEMORY,MERGE,MESSAGE,MODE,MODULES,MOVE,%
+      MULTIPLE,MULTIPLY,NATIVE,NEGATIVE,NEXT,NO,NOT,NUMBER,NUMERIC,%
+      OBJECT-COMPUTER,OCCURS,OF,OFF,OMITTED,ON,OPEN,OPTIONAL,OR,%
+      ORGANIZATION,OUTPUT,OVERFLOW,PAGE,PAGE-COUNTER,PERFORM,PF,PH,PIC,%
+      PICTURE,PLUS,POINTER,POSITION,PRINTING,POSITIVE,PRINTING,%
+      PROCEDURE,PROCEDURES,PROCEED,PROGRAM,PROGRAM-ID,QUEUE,QUOTE,%
+      QUOTES,RANDOM,RD,READ,RECEIVE,RECORD,RECORDING,RECORDS,REDEFINES,%
+      REEL,REFERENCES,RELATIVE,RELEASE,REMAINDER,REMOVAL,RENAMES,%
+      REPLACING,REPORT,REPORTING,REPORTS,RERUN,RESERVE,RESET,RETURN,%
+      REVERSED,REWIND,REWRITE,RF,RH,RIGHT,ROUNDED,RUN,SAME,SD,SEARCH,%
+      SECTION,SECURITY,SEGMENT,SEGMENT-LIMIT,SELECT,SEND,SENTENCE,%
+      SEPARATE,SEQUENCE,SEQUENTIAL,SET,SIGN,SIZE,SORT,SORT-MERGE,%
+      SOURCE,SOURCE-COMPUTER,SPACE,SPACES,SPECIAL-NAMES,STANDARD,START,%
+      STATUS,STOP,STRING,SUB-QUEUE-1,SUB-QUEUE-2,SUB-QUEUE-3,SUBTRACT,%
+      SUM,SYMBOLIC,SYNC,SYNCHRONIZED,TABLE,TALLYING,TAPE,TERMINAL,%
+      TERMINATE,TEXT,THAN,THROUGH,THRU,TIME,TIMES,TO,TOP,TRAILING,TYPE,%
+      UNIT,UNSTRING,UNTIL,UP,UPON,USAGE,USE,USING,VALUE,VALUES,VARYING,%
+      WHEN,WITH,WORDS,WORKING-STORAGE,WRITE,ZERO,ZEROES,ZEROS},%
+   alsodigit=-,%
+   sensitive=f,% ???
+   morecomment=[f][commentstyle][6]*,%
+   morestring=[d]"% ??? doubled
+  }[keywords,comments,strings]%
+\lst@definelanguage{Delphi}%
+  {morekeywords={and,as,asm,array,begin,case,class,const,constructor,%
+      destructor,div,do,downto,else,end,except,exports,file,finally,%
+      for,function,goto,if,implementation,in,inherited,inline,%
+      initialization,interface,is,label,library,mod,nil,not,object,of,%
+      or,packed,procedure,program,property,raise,record,repeat,set,%
+      shl,shr,string,then,to,try,type,unit,until,uses,var,while,with,%
+      xor,%
+      absolute,abstract,assembler,at,cdecl,default,dynamic,export,%
+      external,far,forward,index,name,near,nodefault,on,override,%
+      private,protected,public,published,read,resident,storedDir,%
+      virtual,write},%
+   morendkeywords={Abs,AddExitProc,Addr,AllocMem,AnsiCompareStr,%
+      AnsiCompareText,AnsiLowerCase,AnsiUpperCase,Append,AppendStr,%
+      ArcTan,AssignCrt,Assigned,AssignFile,BlockRead,BlockWrite,Break,%
+      ChangeFileExt,ChDir,Chr,CloseFile,ClrEol,ClrScr,Concat,Continue,%
+      Copy,Cos,CSeg,CursorTo,Date,DateTimeToFileDate,DateTimeToStr,%
+      DateTimeToString,DateToStr,DayOfWeek,Dec,DecodeDate,DecodeTime,%
+      Delete,DeleteFile,DiskFree,DiskSize,Dispose,DisposeStr,%
+      DoneWinCrt,DSeg,EncodeDate,EncodeTime,Eof,Eoln,Erase,Exclude,%
+      Exit,Exp,ExpandFileName,ExtractFileExt,ExtractFileName,%
+      ExtractFilePath,FileAge,FileClose,FileDateToDateTime,FileExists,%
+      FileGetAttr,FileGetDate,FileOpen,FilePos,FileRead,FileSearch,%
+      FileSeek,FileSetAttr,FileSetDate,FileSize,FillChar,FindClose,%
+      FindFirst,FindNext,FloatToDecimal,FloatToStrF,FloatToStr,%
+      FloatToText,FloatToTextFmt,Flush,FmtLoadStr,FmtStr,Format,%
+      FormatBuf,FormatDateTime,FormatFloat,Frac,Free,FreeMem,GetDir,%
+      GetMem,GotoXY,Halt,Hi,High,Inc,Include,InitWinCrt,Insert,Int,%
+      IntToHex,IntToStr,IOResult,IsValidIdent,KeyPressed,Length,Ln,Lo,%
+      LoadStr,Low,LowerCase,MaxAvail,MemAvail,MkDir,Move,New,NewStr,%
+      Now,Odd,Ofs,Ord,ParamCount,ParamStr,Pi,Pos,Pred,Ptr,Random,%
+      Randomize,Read,ReadBuf,ReadKey,Readln,ReAllocMem,Rename,%
+      RenameFile,Reset,Rewrite,RmDir,Round,RunError,ScrollTo,Seek,%
+      SeekEof,SeekEoln,Seg,SetTextBuf,Sin,SizeOf,SPtr,Sqr,Sqrt,SSeg,%
+      Str,StrCat,StrComp,StrCopy,StrDispose,StrECopy,StrEnd,StrFmt,%
+      StrLCat,StrIComp,StrLComp,StrLCopy,StrLen,StrLFmt,StrLIComp,%
+      StrLower,StrMove,StrNew,StrPas,StrPCopy,StrPos,StrScan,StrRScan,%
+      StrToDate,StrToDateTime,StrToFloat,StrToInt,StrToIntDef,%
+      StrToTime,StrUpper,Succ,Swap,TextToFloat,Time,TimeToStr,%
+      TrackCursor,Trunc,Truncate,TypeOf,UpCase,UpperCase,Val,WhereX,%
+      WhereY,Write,WriteBuf,WriteChar,Writeln},%
+   sensitive=f,%
+   morecomment=[s]{(*}{*)},%
+   morecomment=[s]{\{}{\}},%
+   morecomment=[l]{//},% 2001 Christian Gudrian
+   morestring=[d]'%
+  }[keywords,comments,strings]%
+\lst@definelanguage{Eiffel}%
+  {morekeywords={alias,all,and,as,BIT,BOOLEAN,CHARACTER,check,class,%
+      creation,Current,debug,deferred,do,DOUBLE,else,elseif,end,%
+      ensure,expanded,export,external,false,feature,from,frozen,if,%
+      implies,indexing,infix,inherit,inspect,INTEGER,invariant,is,%
+      like,local,loop,NONE,not,obsolete,old,once,or,POINTER,prefix,%
+      REAL,redefine,rename,require,rescue,Result,retry,select,%
+      separate,STRING,strip,then,true,undefine,unique,until,variant,%
+      when,xor},%
+   sensitive,%
+   morecomment=[l]--,%
+   morestring=[d]",%
+  }[keywords,comments,strings]%
+%%
+%% Euphoria definition (c) 1998 Detlef Reimers
+%%
+\lst@definelanguage{Euphoria}%
+  {morekeywords={abort,and,and_bits,append,arctan,atom,by,call,%
+      call_proc,call_func,c_proc,c_func,clear_screen,close,%
+      command_line,compare,constant,cos,do,date,else,elsif,end,exit,%
+      find,floor,for,function,getc,getenv,get_key,gets,global,%
+      get_pixel,if,include,integer,length,log,match,machine_func,%
+      machine_proc,mem_copy,mem_set,not,not_bits,or,object,open,%
+      or_bits,procedure,puts,position,prepend,print,printf,power,peek,%
+      poke,pixel,poke4,peek4s,peek4u,return,rand,repeat,remainder,%
+      routine_id,sequence,sqrt,sin,system,sprintf,then,type,to,time,%
+      trace,tan,while,with,without,xor,xor_bits},%
+   sensitive,%
+   morecomment=[l]--,%
+   morestring=[d]',%
+   morestring=[d]"%
+  }[keywords,comments,strings]%
+%%
+%% GAP definition
+%% (c) 2013 Heiko Oberdiek
+%%
+\lst@definelanguage{GAP}{%
+  morekeywords={%
+    Assert,Info,IsBound,QUIT,%
+    TryNextMethod,Unbind,and,break,%
+    continue,do,elif,%
+    else,end,false,fi,for,%
+    function,if,in,local,%
+    mod,not,od,or,%
+    quit,rec,repeat,return,%
+    then,true,until,while%
+  },%
+  sensitive,%
+  morecomment=[l]\#,%
+  morestring=[b]",%
+  morestring=[b]',%
+}[keywords,comments,strings]
+%%
+%% Guarded Command Language (GCL)  definition
+%% (c) 2002 Mark van Eijk
+%%
+\lst@definelanguage{GCL}%
+  {morekeywords={const,con,var,array,of,skip,if,fi,do,od,div,mod},%
+   literate={|[}{\ensuremath{|\hskip -0.1em[}}2%
+            {]|}{\ensuremath{]\hskip -0.1em|}}2%
+    {[]}{\ensuremath{[\hskip -0.1em]}}2%
+    {->}{\ensuremath{\rightarrow}~}2%
+    {==}{\ensuremath{\equiv}~}2%
+    {>=}{\ensuremath{\geq}~}2%
+    {<=}{\ensuremath{\leq}~}2%
+    {/\\}{\ensuremath{\land}~}2%
+    {\\/}{\ensuremath{\lor}~}2%
+    {!}{\ensuremath{\lnot}}1%
+    {!=}{\ensuremath{\neq}~}2%
+    {max}{\ensuremath{\uparrow}}1%
+    {min}{\ensuremath{\downarrow}}1,%
+   sensitive=f,%
+   morecomment=[s]{\{}{\}},%
+   morestring=[d]'%
+  }[keywords,comments,strings]%
+%%
+%% gnuplot definition (c) Christoph Giess
+%%
+\lst@definelanguage{Gnuplot}%
+  {keywords={abs,acos,acosh,arg,asin,asinh,atan,atan2,atanh,besj0,%
+       besj1,besy0,besy1,ceil,cos,cosh,erf,erfc,exp,floor,gamma,ibeta,%
+       inverf,igamma,imag,invnorm,int,lgamma,log,log10,norm,rand,real,%
+       sgn,sin,sinh,sqrt,tan,tanh,column,tm_hour,tm_mday,tm_min,tm_mon,%
+       tm_sec,tm_wday,tm_yday,tm_year,valid,cd,call,clear,exit,fit,%
+       help,if,load,pause,plot,print,pwd,quit,replot,reread,reset,save,%
+       set,show,shell,splot,test,update,angles,arrow,autoscale,border,%
+       boxwidth,clabel,clip,cntrparam,contour,data,dgrid3d,dummy,%
+       format,function,functions,grid,hidden3d,isosamples,key,keytitle,%
+       label,logscale,mapping,offsets,output,parametric,pointsize,%
+       polar,rrange,samples,size,style,surface,terminal,tics,time,%
+       timefmt,title,trange,urange,variables,view,vrange,xdata,xlabel,%
+       xmargin,xrange,xtics,mxtics,mytics,xdtics,xmtics,xzeroaxis,%
+       ydata,ylabel,yrange,ytics,ydtics,ymtics,yzeroaxis,zdata,zero,%
+       zeroaxis,zlabel,zrange,ztics,zdtics,zmtics,timefm,using,title,%
+       with,index,every,thru,smooth},%
+   sensitive,%
+   comment=[l]\#,%
+   morestring=[b]",%
+   morestring=[b]',%
+  }[keywords,comments,strings]%
+%%
+%% http://gretl.sourceforge.net/gretl-help/cmdref.html
+%% (c) 2013 Ignacio D\'iaz-Emparanza
+%%
+\lst@definelanguage{hansl}{%
+  % $-variables are internal functions in hansl
+  keywordsprefix ={\$},
+  morekeywords={ % hansl commands:
+    add,adf,anova,append,ar,ar1,%
+    arbond,arch,arima,biprobit,boxplot,break,%
+    catch,chow,clear,coeffsum,coint,coint2,%
+    corr,corrgm,cusum,data,dataset,debug,%
+    delete,diff,difftest,discrete,dpanel,dummify,%
+    duration,elif,else,end,endif,endloop,%
+    eqnprint,equation,estimate,fcast,foreign,fractint,%
+    freq,function,garch,genr,gmm,gnuplot,%
+    graphpg,hausman,heckit,help,hsk,hurst,%
+    if,include,info,intreg,join,kalman,%
+    kpss,labels,lad,lags,ldiff,leverage,%
+    levinlin,logistic,logit,logs,loop,mahal,%
+    makepkg,markers,meantest,mle,modeltab,modprint,%
+    modtest,mpols,negbin,nls,normtest,nulldata,%
+    ols,omit,open,orthdev,outfile,panel,%
+    pca,pergm,poisson,print,printf,probit,%
+    pvalue,qlrtest,qqplot,quantreg,quit,rename,%
+    reset,restrict,rmplot,run,runs,scatters,%
+    sdiff,set,setinfo,setobs,setmiss,shell,%
+    smpl,spearman,sprintf,square,sscanf,store,%
+    summary,system,tabprint,textplot,tobit,tsls,%
+    var,varlist,vartest,vecm,vif,wls,%
+    xcorrgm,xtab,scalar,series,matrix,string},%
+  morekeywords=[2]{ %  Functions
+    abs,acos,acosh,aggregate,argname,%
+    asin,asinh,atan,atanh,atof,%
+    bessel,BFGSmax,bkfilt,boxcox,bwfilt,%
+    cdemean,cdf,cdiv,ceil,cholesky,%
+    chowlin,cmult,cnorm,colname,colnames,%
+    cols,corr,corrgm,cos,cosh,%
+    cov,critical,cum,deseas,det,%
+    diag,diagcat,diff,digamma,dnorm,%
+    dsort,dummify,eigengen,eigensym,eigsolve,%
+    epochday,errmsg,exp,fcstats,fdjac,%
+    fft,ffti,filter,firstobs,fixname,%
+    floor,fracdiff,gammafun,getenv,getline,%
+    ghk,gini,ginv,halton,hdprod,%
+    hpfilt,I,imaxc,imaxr,imhof,%
+    iminc,iminr,inbundle,infnorm,inlist,%
+    int,inv,invcdf,invmills,invpd,%
+    irf,irr,isconst,isnan,isnull,%
+    isodate,iwishart,kdensity,kfilter,ksimul,%
+    ksmooth,kurtosis,lags,lastobs,ldet,%
+    ldiff,lincomb,ljungbox,lngamma,log,%
+    log10,log2,loess,logistic,lower,%
+    lrvar,max,maxc,maxr,mcorr,%
+    mcov,mcovg,mean,meanc,meanr,%
+    median,mexp,min,minc,minr,%
+    missing,misszero,mlag,mnormal,mols,%
+    monthlen,movavg,mpols,mrandgen,mread,%
+    mreverse,mrls,mshape,msortby,muniform,%
+    mwrite,mxtab,nadarwat,nelem,ngetenv,%
+    nobs,normal,npv,NRmax,nullspace,%
+    obs,obslabel,obsnum,ok,onenorm,%
+    ones,orthdev,pdf,pergm,pmax,%
+    pmean,pmin,pnobs,polroots,polyfit,%
+    princomp,prodc,prodr,psd,psdroot,%
+    pshrink,psum,pvalue,pxsum,qform,%
+    qnorm,qrdecomp,quadtable,quantile,randgen,%
+    randgen1,randint,rank,ranking,rcond,%
+    readfile,regsub,remove,replace,resample,%
+    round,rownames,rows,sd,sdc,%
+    sdiff,selifc,selifr,seq,setnote,%
+    simann,sin,sinh,skewness,sort,%
+    sortby,sqrt,sscanf,sst,strlen,%
+    strncmp,strsplit,strstr,strstrip,strsub,%
+    sum,sumall,sumc,sumr,svd,%
+    tan,tanh,toepsolv,tolower,toupper,%
+    tr,transp,trimr,typestr,uniform,%
+    uniq,unvech,upper,urcpval,values,%
+    var,varname,varnum,varsimul,vec,%
+    vech,weekday,wmean,wsd,wvar,%
+    xmax,xmin,xpx,zeromiss,zeros,%
+  },%
+  sensitive=t,%
+  morecomment=[l]{\#},%
+  morecomment=[s]{/*}{*/},%
+  morestring=[b]{"}}%
+\lstalias{gretl}{hansl}
+%%
+%% Haskell98 as implemented in Hugs98. See http://www.haskell.org
+%% All keywords from Prelude and Standard Libraries
+%% (c) 1999 Peter Bartke
+%%
+\lst@definelanguage{Haskell}%
+  {otherkeywords={=>},%
+   morekeywords={abstype,if,then,else,case,class,data,default,deriving,%
+      hiding,if,in,infix,infixl,infixr,import,instance,let,module,%
+      newtype,of,qualified,type,where,do,AbsoluteSeek,AppendMode,%
+      Array,BlockBuffering,Bool,BufferMode,Char,Complex,Double,Either,%
+      FilePath,Float,Int,Integer,IO,IOError,Ix,LineBuffering,Maybe,%
+      Ordering,NoBuffering,ReadMode,ReadWriteMode,ReadS,RelativeSeek,%
+      SeekFromEnd,SeekMode,ShowS,StdGen,String,Void,Bounded,Enum,Eq,%
+      Eval,ExitCode,exitFailure,exitSuccess,Floating,Fractional,%
+      Functor,Handle,HandlePosn,IOMode,Integral,List,Monad,MonadPlus,%
+      MonadZero,Num,Numeric,Ord,Random,RandomGen,Ratio,Rational,Read,%
+      Real,RealFloat,RealFrac,Show,System,Prelude,EQ,False,GT,Just,%
+      Left,LT,Nothing,Right,WriteMode,True,abs,accum,accumArray,%
+      accumulate,acos,acosh,all,and,any,ap,appendFile,applyM,%
+      approxRational,array,asTypeOf,asin,asinh,assocs,atan,atan2,atanh,%
+      bounds,bracket,bracket_,break,catch,catMaybes,ceiling,chr,cis,%
+      compare,concat,concatMap,conjugate,const,cos,cosh,curry,cycle,%
+      decodeFloat,delete,deleteBy,deleteFirstsBy,denominator,%
+      digitToInt,div,divMod,drop,dropWhile,either,elem,elems,elemIndex,%
+      elemIndices,encodeFloat,enumFrom,enumFromThen,enumFromThenTo,%
+      enumFromTo,error,even,exitFailure,exitWith,exp,exponent,fail,%
+      filter,filterM,find,findIndex,findIndices,flip,floatDigits,%
+      floatRadix,floatRange,floatToDigits,floor,foldl,foldM,foldl1,%
+      foldr,foldr1,fromDouble,fromEnum,fromInt,fromInteger,%
+      fromIntegral,fromJust,fromMaybe,fromRat,fromRational,%
+      fromRealFrac,fst,gcd,genericLength,genericTake,genericDrop,%
+      genericSplitAt,genericIndex,genericReplicate,getArgs,getChar,%
+      getContents,getEnv,getLine,getProgName,getStdGen,getStdRandom,%
+      group,groupBy,guard,hClose,hFileSize,hFlush,hGetBuffering,%
+      hGetChar,hGetContents,hGetLine,hGetPosn,hIsClosed,hIsEOF,hIsOpen,%
+      hIsReadable,hIsSeekable,hIsWritable,hLookAhead,hPutChar,hPutStr,%
+      hPutStrLn,hPrint,hReady,hSeek,hSetBuffering,hSetPosn,head,%
+      hugsIsEOF,hugsHIsEOF,hugsIsSearchErr,hugsIsNameErr,%
+      hugsIsWriteErr,id,ioError,imagPart,index,indices,init,inits,%
+      inRange,insert,insertBy,interact,intersect,intersectBy,%
+      intersperse,intToDigit,ioeGetErrorString,ioeGetFileName,%
+      ioeGetHandle,isAlreadyExistsError,isAlreadyInUseError,isAlpha,%
+      isAlphaNum,isAscii,isControl,isDenormalized,isDoesNotExistError,%
+      isDigit,isEOF,isEOFError,isFullError,isHexDigit,isIEEE,%
+      isIllegalOperation,isInfinite,isJust,isLower,isNaN,%
+      isNegativeZero,isNothing,isOctDigit,isPermissionError,isPrefixOf,%
+      isPrint,isSpace,isSuffixOf,isUpper,isUserError,iterate,ixmap,%
+      join,last,lcm,length,lex,lexDigits,lexLitChar,liftM,liftM2,%
+      liftM3,liftM4,liftM5,lines,listArray,listToMaybe,log,logBase,%
+      lookup,magnitude,makePolar,map,mapAccumL,mapAccumR,mapAndUnzipM,%
+      mapM,mapM_,mapMaybe,max,maxBound,maximum,maximumBy,maybe,%
+      maybeToList,min,minBound,minimum,minimumBy,mkPolar,mkStdGen,%
+      mplus,mod,msum,mzero,negate,next,newStdGen,not,notElem,nub,nubBy,%
+      null,numerator,odd,openFile,or,ord,otherwise,partition,phase,pi,%
+      polar,pred,print,product,properFraction,putChar,putStr,putStrLn,%
+      quot,quotRem,random,randomIO,randomR,randomRIO,randomRs,randoms,%
+      rangeSize,read,readDec,readFile,readFloat,readHex,readInt,readIO,%
+      readList,readLitChar,readLn,readParen,readOct,readSigned,reads,%
+      readsPrec,realPart,realToFrac,recip,rem,repeat,replicate,return,%
+      reverse,round,scaleFloat,scanl,scanl1,scanr,scanr1,seq,sequence,%
+      sequence_,setStdGen,show,showChar,showEFloat,showFFloat,%
+      showFloat,showGFloat,showInt,showList,showLitChar,showParen,%
+      showSigned,showString,shows,showsPrec,significand,signum,sin,%
+      sinh,snd,sort,sortBy,span,split,splitAt,sqrt,stderr,stdin,stdout,%
+      strict,subtract,succ,sum,system,tail,tails,take,takeWhile,tan,%
+      tanh,toEnum,toInt,toInteger,toLower,toRational,toUpper,transpose,%
+      truncate,try,uncurry,undefined,unfoldr,union,unionBy,unless,%
+      unlines,until,unwords,unzip,unzip3,unzip4,unzip5,unzip6,unzip7,%
+      userError,when,words,writeFile,zero,zip,zip3,zip4,zip5,zip6,zip7,%
+      zipWith,zipWithM,zipWithM_,zipWith3,zipWith4,zipWith5,zipWith6,%
+      zipWith7},%
+   sensitive,%
+   morecomment=[l]--,%
+   morecomment=[n]{\{-}{-\}},%
+   morestring=[b]"%
+  }[keywords,comments,strings]%
+%%
+%% IDL definition (c) 1998 Juergen Heim
+%%
+\lst@definelanguage{IDL}%
+  {morekeywords={and,begin,case,common,do,else,end,endcase,endelse,%
+      endfor,endif,endrep,endwhile,eq,for,function,ge,goto,gt,if,le,lt,%
+      mod,ne,not,of,on_ioerror,or,pro,repeat,return,then,until,while,%
+      xor,on_error,openw,openr,openu,print,printf,printu,plot,read,%
+      readf,readu,writeu,stop},%
+   sensitive=f,%
+   morecomment=[l];,%
+   morestring=[d]'%
+  }[keywords,comments,strings]%
+%%
+%% Inform definition (c) 2003 Jonathan Sauer
+%%
+\lst@definelanguage{inform}{%
+    % Language keywords
+    morekeywords={breakdo,else,false,for,has,hasnt,if,%
+                in,indirect,jump,notin,nothing,NULL,objectloop,ofclass,%
+                private,property,provides,return,rfalse,rtrue,self,string,%
+                switch,to,true,until,while,with,%
+                creature,held,multiexcept,multiheld,multiinside,noun,number,%
+                scope,topic},%
+    %
+    % Inform functions
+    morekeywords=[2]{box,child,children,font,give,inversion,metaclass,move,%
+                new_line,parent,print,print_ret,read,remove,restore,sibling,%
+                save,spaces,quit,style,bold,underline,reverse,roman remaining,%
+                create,destroy,recreate,copy},%
+    %
+    % Inform definitions
+    morekeywords=[3]{Attribute,Array,Class,Constant,Default,End,Endif,Extend,%
+                Global,Ifdef,Iffalse,Ifndef,Ifnot,Iftrue,Include,Object,%
+                Property,Verb,Release,Serial,Statusline},%
+    %
+    % Library attributes
+    morekeywords=[4]{absent,animate,clothing,concealed,container,door,edible,%
+                enterable,female,general,light,lockable locked,male,moved,%
+                neuter,on,open,openable,pluralname,proper,scenery,scored,%
+                static,supporter,switchable,talkable,transparent,visited,%
+                workflag,worn},%
+    %
+    % Library properties
+    morekeywords=[5]{n_to,s_to,e_to,w_to,ne_to,nw_to,se_to,sw_to,in_to,%
+                out_to,u_to,d_to,add_to_scope,after,article,articles,before,%
+                cant_go,capacity,daemon,describe,description,door_dir,door_to,%
+                each_turn,found_in,grammar,initial,inside_description,invent,%
+                life,list_together,name number,orders,parse_name,plural,%
+                react_after,react_before,short_name,short_name_indef,time_left,%
+                time_out,when_closed,when_open,when_on,when_off,%
+                with_key},%
+    %
+    % Library routines
+    morekeywords=[6]{Achieved,AfterRoutines,AllowPushDir,Banner,ChangePlayer,%
+                CommonAncestor,DictionaryLookup,GetGNAOfObject,HasLightSource,%
+                IndirectlyContains,IsSeeThrough,Locale,LoopOverScope,LTI_Insert,%
+                MoveFloatingObjects,NextWord,NextWordStopped,NounDomain,%
+                ObjectIsUntouchable OffersLight,ParseToken,PlaceInScope,PlayerTo,%
+                PronounNotice,PronounValue,ScopeWithin,SetPronoun,SetTime,%
+                StartDaemon,StartTimer,StopDaemon,StopTimer,TestScope,TryNumber,%
+                UnsignedCompare,WordAddress,WordInProperty,WordLength,%
+                WriteListFrom,YesOrNo},%
+    %
+    % Library,entry points
+    morekeywords=[7]{AfterLife,AfterPrompt,Amusing,BeforeParsing,ChooseObjects,%
+                DarkToDark,DeathMessage,GamePostRoutine GamePreRoutine,%
+                Initialise,InScope,LookRoutine,NewRoom,ParseNoun,ParseNumber,%
+                ParserError,PrintRank,PrintTaskName,PrintVerb,TimePasses,%
+                UnknownVerb},%
+    %
+    % Library constants
+    morekeywords=[8]{NEWLINE_BIT,INDENT_BIT,FULLINV_BIT,ENGLISH_BIT,RECURSE_BIT,%
+                ALWAYS_BIT,TERSE_BIT,PARTINV_BIT,DEFART_BIT,WORKFLAG_BIT,%
+                ISARE_BIT,CONCEAL_BIT},%
+    %
+    % Library,meta actions
+    morekeywords=[9]{Pronouns,Quit,Restart,Restore,Save,Verify,ScriptOn,ScriptOff,%
+                NotifyOn,NotifyOff,Places,Objects,Score,FullScore,Version,LMode1,%
+                LMode2,Lmode3},%
+    %
+    % Library,main actions
+    morekeywords=[10]{Close,Disrobe,Drop,Eat,Empty,EmptyT,Enter,Examine,Exit,GetOff,%
+                Give,Go,GoIn,Insert,Inv,InvTall,InvWide,Lock,Look,Open,PutOn,Remove,%
+                Search,Show,SwitchOff,SwitchOn,Take,Transfer,Unlock VagueGo,%
+                Wear},%
+    %
+    % Library,stub actions
+    morekeywords=[11]{Answer,Ask,AskFor,Attack,Blow,Burn,Buy,Climb,Consult,Cut,Dig,%
+                Drink,Fill,Jump,JumpOver,Kiss,Listen,LookUnder,Mild,No,Pray,Pull,%
+                Push,PushDir,Rub,Set,SetTo,Sing,Sleep,Smell,,Sleep,Smell,Sorry,%
+                Squeeze,Strong,Swim,Swing,Taste,Tell,Think,ThrowAt,Tie,Touch,Turn,%
+                Wait,Wake,WakeOther,Wave,WaveHands,Yes},%
+    %
+    otherkeywords={->,-->},%
+    sensitive=false,%
+    morestring=[d]{"},%
+    morecomment=[l]{!}%
+  }[keywords,comments,strings]%
+\lst@definelanguage{Lisp}%
+  {morekeywords={abort,abs,acons,acos,acosh,adjoin,alphanumericp,alter,%
+      append,apply,apropos,aref,arrayp,ash,asin,asinh,assoc,atan,atanh,%
+      atom,bit,boole,boundp,break,butlast,byte,catenate,ceiling,cerror,%
+      char,character,characterp,choose,chunk,cis,close,clrhash,coerce,%
+      collect,commonp,compile,complement,complex,complexp,concatenate,%
+      conjugate,cons,consp,constantp,continue,cos,cosh,cotruncate,%
+      count,delete,denominator,describe,directory,disassemble,%
+      documentation,dpb,dribble,ed,eighth,elt,enclose,endp,eq,eql,%
+      equal,equalp,error,eval,evalhook,evenp,every,exp,expand,export,%
+      expt,fboundp,fceiling,fdefinition,ffloor,fifth,fill,find,first,%
+      float,floatp,floor,fmakunbound,format,fourth,fround,ftruncate,%
+      funcall,functionp,gatherer,gcd,generator,gensym,gentemp,get,getf,%
+      gethash,identity,imagpart,import,inspect,integerp,intern,%
+      intersection,tively,isqrt,keywordp,last,latch,lcm,ldb,ldiff,%
+      length,list,listen,listp,load,log,logand,logbitp,logcount,logeqv,%
+      logior,lognand,lognor,lognot,logtest,logxor,macroexpand,%
+      makunbound,map,mapc,mapcan,mapcar,mapcon,maphash,mapl,maplist,%
+      mask,max,member,merge,min,mingle,minusp,mismatch,mod,namestring,%
+      nbutlast,nconc,nintersection,ninth,not,notany,notevery,nreconc,%
+      nreverse,nsublis,nsubst,nth,nthcdr,null,numberp,numerator,nunion,%
+      oddp,open,packagep,pairlis,pathname,pathnamep,phase,plusp,%
+      position,positions,pprint,previous,princ,print,proclaim,provide,%
+      random,rassoc,rational,rationalize,rationalp,read,readtablep,%
+      realp,realpart,reduce,rem,remhash,remove,remprop,replace,require,%
+      rest,revappend,reverse,room,round,rplaca,rplacd,sbit,scan,schar,%
+      search,second,series,set,seventh,shadow,signal,signum,sin,sinh,%
+      sixth,sleep,some,sort,split,sqrt,streamp,string,stringp,sublis,%
+      subseq,subseries,subsetp,subst,substitute,subtypep,svref,sxhash,%
+      symbolp,tailp,tan,tanh,tenth,terpri,third,truename,truncate,%
+      typep,unexport,unintern,union,until,values,vector,vectorp,warn,%
+      write,zerop,and,assert,case,ccase,cond,ctypecase,decf,declaim,%
+      defclass,defconstant,defgeneric,defmacro,defmethod,defpackage,%
+      defparameter,defsetf,defstruct,deftype,defun,defvar,do,dolist,%
+      dotimes,ecase,encapsulated,etypecase,flet,formatter,gathering,%
+      incf,iterate,labels,let,locally,loop,macrolet,mapping,or,pop,%
+      producing,prog,psetf,psetq,push,pushnew,remf,return,rotatef,%
+      setf,shiftf,step,time,trace,typecase,unless,untrace,when},%
+   sensitive,% ???
+   alsodigit=-,%
+   morecomment=[l];,%
+   morecomment=[s]{\#|}{|\#},% 1997 Aslak Raanes
+   morestring=[b]"%
+  }[keywords,comments,strings]%
+%%
+%% AutoLISP/VisualLISP - Stefan Lagotzki, info@lagotzki.de
+%%
+\lst@definelanguage[Auto]{Lisp}%
+  {morekeywords={abs,acad_colordlg,acad_helpdlg,acad_strlsort,%
+      action_tile,add_list,alert,alloc,and,angle,angtof,angtos,append,%
+      apply,arx,arxload,arxunload,ascii,assoc,atan,atof,atoi,atom,%
+      atoms-family,autoarxload,autoload,Boole,boundp,caddr,cadr,car,%
+      cdr,chr,client_data_tile,close,command,cond,cons,cos,cvunit,%
+      defun,defun-q,defun-q-list-ref,defun-q-list-set,dictadd,dictnext,%
+      dictremove,dictrename,dictsearch,dimx_tile,dimy_tile,distance,%
+      distof,done_dialog,end_image,end_list,entdel,entget,entlast,%
+      entmake,entmakex,entmod,entnext,entsel,entupd,eq,equal,*error*,%
+      eval,exit,exp,expand,expt,fill_image,findfile,fix,float,foreach,%
+      function,gc,gcd,get_attr,get_tile,getangle,getcfg,getcname,%
+      getcorner,getdist,getenv,getfiled,getint,getkword,getorient,%
+      getpoint,getreal,getstring,getvar,graphscr,grclear,grdraw,grread,%
+      grtext,grvecs,handent,help,if,initdia,initget,inters,itoa,lambda,%
+      last,layoutlist,length,list,listp,load,load_dialog,log,logand,%
+      logior,lsh,mapcar,max,mem,member,menucmd,menugroup,min,minusp,%
+      mode_tile,namedobjdict,nentsel,nentselp,new_dialog,not,nth,%
+      null,numberp,open,or,osnap,polar,prin1,princ,print,progn,prompt,%
+      quit,quote,read,read-char,read-line,redraw,regapp,rem,repeat,%
+      reverse,rtos,set,set_tile,setcfg,setenv,setfunhelp,setq,%
+      setvar,setview,sin,slide_image,snvalid,sqrt,ssadd,ssdel,ssget,%
+      ssgetfirst,sslength,ssmemb,ssname,ssnamex,sssetfirst,startapp,%
+      start_dialog,start_image,start_list,strcase,strcat,strlen,subst,%
+      substr,tablet,tblnext,tblobjname,tblsearch,term_dialog,terpri,%
+      textbox,textpage,textscr,trace,trans,type,unload_dialog,untrace,%
+      vector_image,ver,vl-acad-defun,vl-acad-undefun,vl-arx-import,%
+      vl-bb-ref,vl-bb-set,vl-catch-all-apply,%
+      vl-catch-all-error-message,vl-catch-all-error-p,vl-cmdf,vl-consp,%
+      vl-directory-files,vl-doc-export,vl-doc-import,vl-doc-ref,%
+      vl-doc-set,vl-every,vl-exit-with-error,vl-exit-with-value,%
+      vl-file-copy,vl-file-delete,vl-file-directory-p,vl-file-rename,%
+      vl-file-size,vl-file-systime,vl-filename-base,%
+      vl-filename-directory,vl-filename-extension,vl-filename-mktemp,%
+      vl-get-resource,vl-list*,vl-list->string,%
+      vl-list-exported-functions,vl-list-length,vl-list-loaded-vlx,%
+      vl-load-all,vl-load-com,vl-load-reactors,vl-member-if,%
+      vl-member-if-not,vl-position,vl-prin1-to-string,%
+      vl-princ-to-string,vl-propagate,vl-registry-delete,%
+      vl-registry-descendents,vl-registry-read,vl-registry-write,%
+      vl-remove,vl-remove-if,vl-remove-if-not,vl-some,vl-sort,%
+      vl-sort-i,vl-string->list,vl-string-elt,vl-string-left-trim,%
+      vl-string-mismatch,vl-string-position,vl-string-right-trim,%
+      vl-string-search,vl-string-subst,vl-string-translate,%
+      vl-string-trim,vl-symbol-name,vl-symbol-value,vl-symbolp,%
+      vl-unload-vlx,vl-vbaload,vl-vbarun,vl-vlx-loaded-p,vlax-3D-point,%
+      vlax-add-cmd,vlax-create-object,vlax-curve-getArea,%
+      vlax-curve-getDistAtParam,vlax-curve-getDistAtPoint,%
+      vlax-curve-getEndParam,vlax-curve-getEndPoint,%
+      vlax-curve-getParamAtDist,vlax-curve-getParamAtPoint,%
+      vlax-curve-getPointAtDist,vlax-curve-getPointAtParam,%
+      vlax-curve-getStartParam,vlax-curve-getStartPoint,%
+      vlax-curve-isClosed,vlax-curve-isPeriodic,vlax-curve-isPlanar,%
+      vlax-curve-getClosestPointTo,%
+      vlax-curve-getClosestPointToProjection,vlax-curve-getFirstDeriv,%
+      vlax-curve-getSecondDeriv,vlax-dump-object,%
+      vlax-ename->vla-object,vlax-erased-p,vlax-for,%
+      vlax-get-acad-object,vlax-get-object,vlax-get-or-create-object,%
+      vlax-get-property,vlax-import-type-library,vlax-invoke-method,%
+      vlax-ldata-delete,vlax-ldata-get,vlax-ldata-list,vlax-ldata-put,%
+      vlax-ldata-test,vlax-make-safearray,vlax-make-variant,%
+      vlax-map-collection,vlax-method-applicable-p,%
+      vlax-object-released-p,vlax-product-key,%
+      vlax-property-available-p,vlax-put-property,vlax-read-enabled-p,%
+      vlax-release-object,vlax-remove-cmd,vlax-safearray-fill,%
+      vlax-safearray-get-dim,vlax-safearray-get-element,%
+      vlax-safearray-get-l-bound,vlax-safearray-get-u-bound,%
+      vlax-safearray-put-element,vlax-safearray-type,%
+      vlax-safearray->list,vlax-tmatrix,vlax-typeinfo-available-p,%
+      vlax-variant-change-type,vlax-variant-type,vlax-variant-value,%
+      vlax-vla-object->ename,vlax-write-enabled-p,vlisp-compile,%
+      vlr-acdb-reactor,vlr-add,vlr-added-p,vlr-beep-reaction,%
+      vlr-command-reactor,vlr-current-reaction-name,vlr-data,%
+      vlr-data-set,vlr-deepclone-reactor,vlr-docmanager-reactor,%
+      vlr-dwg-reactor,vlr-dxf-reactor,vlr-editor-reactor,%
+      vlr-insert-reactor,vlr-linker-reactor,vlr-lisp-reactor,%
+      vlr-miscellaneous-reactor,vlr-mouse-reactor,vlr-notification,%
+      vlr-object-reactor,vlr-owner-add,vlr-owner-remove,vlr-owners,%
+      vlr-pers,vlr-pers-list,vlr-pers-p,vlr-pers-release,%
+      vlr-reaction-names,vlr-reaction-set,vlr-reactions,vlr-reactors,%
+      vlr-remove,vlr-remove-all,vlr-set-notification,%
+      vlr-sysvar-reactor,vlr-toolbar-reactor,vlr-trace-reaction,%
+      vlr-type,vlr-types,vlr-undo-reactor,vlr-wblock-reactor,%
+      vlr-window-reactor,vlr-xref-reactor,vports,wcmatch,while,%
+      write-char,write-line,xdroom,xdsize,zerop},%
+   alsodigit=->,%
+   otherkeywords={1+,1-},%
+   sensitive=false,%
+   morecomment=[l];,%
+   morecomment=[l];;,%
+   morestring=[b]"%
+  }[keywords,comments,strings]%
+%%
+%% Lua definitions (c) 2013 Stephan Hennig
+%%
+\lst@definelanguage[5.0]{Lua}{%
+  alsoletter={.},%
+  morekeywords=[1]{%
+    and, break, do, else, elseif, end, false, for, function, if, in,%
+    local, nil, not, or, repeat, return, then, true, until, while,%
+  },%
+  morekeywords=[2]{%
+    _G, _LOADED, _REQUIREDNAME, _VERSION, LUA_PATH,%
+    assert, collectgarbage, dofile, error, gcinfo, getfenv,%
+    getmetatable, ipairs, loadfile, loadlib, loadstring, newproxy,%
+    next, pairs, pcall, print, rawequal, rawget, rawset, require,%
+    setfenv, setmetatable, tonumber, tostring, type, unpack, xpcall,%
+    coroutine, coroutine.create, coroutine.resume,%
+    coroutine.status, coroutine.wrap, coroutine.yield,%
+    _TRACEBACK, debug, debug.debug, debug.gethook, debug.getinfo,%
+    debug.getlocal, debug.getupvalue, debug.sethook, debug.setlocal,%
+    debug.setupvalue,debug.traceback,%
+    io, io.close, io.flush, io.input, io.lines, io.open, io.output,%
+    io.popen, io.read, io.stderr, io.stdin, io.stdout, io.tmpfile,%
+    io.type, io.write,%
+    __pow, math, math.abs, math.acos, math.asin, math.atan, math.atan2,%
+    math.ceil, math.cos, math.deg, math.exp, math.floor, math.frexp,%
+    math.ldexp, math.log, math.log10, math.max, math.min, math.mod,%
+    math.pi, math.pow, math.rad, math.random, math.randomseed, math.sin,%
+    math.sqrt, math.tan,%
+    os, os.clock, os.date, os.difftime, os.execute, os.exit, os.getenv,%
+    os.remove, os.rename, os.setlocale, os.time, os.tmpname,%
+    string, string.byte, string.char, string.dump, string.find,%
+    string.format, string.gfind, string.gsub, string.len, string.lower,%
+    string.rep, string.sub, string.upper,%
+    table, table.concat, table.foreach, table.foreachi, table.getn,%
+    table.insert, table.remove, table.setn, table.sort,%
+  },%
+  morekeywords=[2]{%
+    _PROMPT, _PROMPT2, arg,%
+  },%
+  sensitive=true,%
+  % single line comments
+  morecomment=[l]{--},%
+  % multi line comments
+  morecomment=[s]{--[[}{]]},%
+  % backslash escaped strings
+  morestring=[b]",%
+  morestring=[b]',%
+  % multi line strings
+  morestring=[s]{[[}{]]},%
+}[keywords,comments,strings]%
+\lst@definelanguage[5.1]{Lua}[5.0]{Lua}{%
+  deletekeywords=[2]{%
+    _LOADED, _REQUIREDNAME, LUA_PATH, gcinfo, loadlib,%
+    _TRACEBACK,%
+    __pow, math.mod,%
+    string.gfind,%
+    table.foreach, table.foreachi, table.getn, table.setn,%
+  },%
+  morekeywords=[2]{%
+    load, select,%
+    coroutine.running,%
+    debug.getfenv, debug.getmetatable, debug.getregistry, debug.setfenv,%
+    debug.setmetatable,%
+    math.cosh, math.fmod, math.huge, math.modf, math.sinh, math.tanh,%
+    module, package, package.config, package.cpath, package.loaded,%
+    package.loaders, package.loadlib, package.path, package.preload,%
+    package.seeall,%
+    string.gmatch, string.match, string.reverse,%
+    table.maxn,%
+  },%
+  morecomment=[s]{--[=[}{]=]},%
+  morecomment=[s]{--[==[}{]==]},%
+  morecomment=[s]{--[===[}{]===]},%
+  morecomment=[s]{--[====[}{]====]},%
+  morecomment=[s]{--[=====[}{]=====]},%
+  morecomment=[s]{--[======[}{]======]},%
+  morecomment=[s]{--[=======[}{]=======]},%
+  morecomment=[s]{--[========[}{]========]},%
+  morecomment=[s]{--[=========[}{]=========]},%
+  morecomment=[s]{--[==========[}{]==========]},%
+  morestring=[s]{[=[}{]=]},%
+  morestring=[s]{[==[}{]==]},%
+  morestring=[s]{[===[}{]===]},%
+  morestring=[s]{[====[}{]====]},%
+  morestring=[s]{[=====[}{]=====]},%
+  morestring=[s]{[======[}{]======]},%
+  morestring=[s]{[=======[}{]=======]},%
+  morestring=[s]{[========[}{]========]},%
+  morestring=[s]{[=========[}{]=========]},%
+  morestring=[s]{[==========[}{]==========]},%
+}[keywords,comments,strings]%
+\lst@definelanguage[5.2]{Lua}[5.1]{Lua}{%
+  morekeywords=[1]{%
+    goto,%
+  },%
+  deletekeywords=[2]{%
+    getfenv, loadstring, module, newproxy, setfenv, unpack,%
+    debug.getfenv, debug.setfenv,%
+    math.log10,%
+    package.loaders, package.seeall,%
+    table.maxn,%
+  },%
+  morekeywords=[2]{%
+    rawlen,%
+    bit32, bit32.arshift, bit32.band, bit32.bnot, bit32.bor,%
+    bit32.btest, bit32.bxor, bit32.extract, bit32.lrotate,%
+    bit32.lshift, bit32.replace, bit32.rrotate, bit32.rshift,%
+    debug.getuservalue, debug.setuservalue, debug.upvalueid,%
+    debug.upvaluejoin,%
+    package.searchers, package.searchpath,%
+    table.pack, table.unpack,%
+  },%
+  morekeywords=[2]{%
+    _ENV,%
+  },%
+  moredelim=[s][keywordstyle3]{::}{::},%
+}[keywords,comments,strings]%
+\lst@definelanguage[5.3]{Lua}[5.2]{Lua}{%
+  deletekeywords=[2]{%
+    bit32, bit32.arshift, bit32.band, bit32.bnot, bit32.bor,%
+    bit32.btest, bit32.bxor, bit32.extract, bit32.lrotate,%
+    bit32.lshift, bit32.replace, bit32.rrotate, bit32.rshift,%
+    math.atan2, math.cosh, math.frexp, math.ldexp, math.pow,%
+    math.sinh, math.tanh,%
+  },%
+  morekeywords=[2]{%
+    coroutine.isyieldable,%
+    math.maxinteger, math.mininteger, math.tointeger, math.type,%
+    math.ult,%
+    string.pack, string.packsize, string.unpack,%
+    table.move,%
+    utf8, utf8.char, utf8.charpattern, utf8.codepoint, utf8.codes,%
+    utf8.len, utf8.offset,%
+  },%
+}[keywords,comments,strings]%
+%%
+%% Make definitions (c) 2000 Rolf Niepraschk
+%%
+\lst@definelanguage[gnu]{make}%
+  {morekeywords={SHELL,MAKE,MAKEFLAGS,$@,$\%,$<,$?,$^,$+,$*,%
+      @,^,<,\%,+,?,*,% Markus Pahlow
+      export,unexport,include,override,define,ifdef,ifneq,ifeq,else,%
+      endif,vpath,subst,patsubst,strip,findstring,filter,filter-out,%
+      sort,dir,notdir,suffix,basename,addsuffix,addprefix,join,word,%
+      words,firstword,wildcard,shell,origin,foreach,%
+      @D,@F,*D,*F,\%D,\%F,<D,<F,^D,^F,+D,+F,?D,?F,%
+      AR,AS,CC,CXX,CO,CPP,FC,GET,LEX,PC,YACC,YACCR,MAKEINFO,TEXI2DVI,%
+      WEAVE,CWEAVE,TANGLE,CTANGLE,RM,M2C,LINT,COMPILE,LINK,PREPROCESS,%
+      CHECKOUT,%
+      ARFLAGS,ASFLAGS,CFLAGS,CXXFLAGS,COFLAGS,CPPFLAGS,FFLAGS,GFLAGS,%
+      LDFLAGS,LOADLIBES,LFLAGS,PFLAGS,RFLAGS,YFLAGS,M2FLAGS,MODFLAGS,%
+      LINTFLAGS,MAKEINFO_FLAGS,TEXI2DVI_FLAGS,COFLAGS,GFLAGS,%
+      OUTPUT_OPTION,SCCS_OUTPUT_OPTION,% missing comma: Markus Pahlow
+      .PHONY,.SUFFIXES,.DEFAULT,.PRECIOUS,.INTERMEDIATE,.SECONDARY,%
+      .IGNORE,.SILENT,.EXPORT_ALL_VARIABLES,MAKEFILES,VPATH,MAKESHELL,%
+      MAKELEVEL,MAKECMDGOALS,SUFFIXES},%
+   sensitive=true,
+   morecomment=[l]\#,%
+   morestring=[b]"%
+  }[keywords,comments,strings,make]%
+\lst@definelanguage{make}
+  {morekeywords={SHELL,MAKE,MAKEFLAGS,$@,$\%,$<,$?,$^,$+,$*},%
+   sensitive=true,%
+   morecomment=[l]\#,%
+   morestring=[b]"%
+  }[keywords,comments,strings,make]%
+%%
+%% Mercury definition (c) 1997 Dominique de Waleffe
+%% Extended (c) 2001 Ralph Becket
+%%
+\lst@definelanguage{Mercury}%
+  {otherkeywords={::,->,-->,--->,:-,==,=>,<=,<=>},%
+   morekeywords={module,include_module,import_module,interface,%
+      end_module,implementation,mode,is,failure,semidet,nondet,det,%
+      multi,erroneous,inst,in,out,di,uo,ui,type,typeclass,instance,%
+      where,with_type,pred,func,lambda,impure,semipure,if,then,else,%
+      some,all,not,true,fail,pragma,memo,no_inline,inline,loop_check,%
+      minimal_model,fact_table,type_spec,terminates,does_not_terminate,%
+      check_termination,promise_only_solution,unsafe_promise_unique,%
+      source_file,obsolete,import,export,c_header_code,c_code,%
+      foreign_code,foreign_proc,may_call_mercury,will_not_call_mercury,%
+      thread_safe,not_thread_safe},%
+   sensitive=t,%
+   morecomment=[l]\%,%
+   morecomment=[s]{/*}{*/},%
+   morestring=[bd]",%
+   morestring=[bd]'%
+  }[keywords,comments,strings]%
+%%
+%% Miranda definition (c) 1998 Peter Bartke
+%%
+%% Miranda: pure lazy functional language with polymorphic type system,
+%%          garbage collection and functions as first class citizens
+%%
+\lst@definelanguage{Miranda}%
+  {morekeywords={abstype,div,if,mod,otherwise,readvals,show,type,where,%
+     with,bool,char,num,sys_message,False,True,Appendfile,Closefile,%
+     Exit,Stderr,Stdout,System,Tofile,\%include,\%export,\%free,%
+     \%insert,abs,and,arctan,cjustify,code,concat,const,converse,cos,%
+     decode,digit,drop,dropwhile,entier,error,exp,filemode,filter,%
+     foldl,foldl1,foldr,foldr1,force,fst,getenv,hd,hugenum,id,index,%
+     init,integer,iterate,last,lay,layn,letter,limit,lines,ljustify,%
+     log,log10,map,map2,max,max2,member,merge,min,min2,mkset,neg,%
+     numval,or,pi,postfix,product,read,rep,repeat,reverse,rjustify,%
+     scan,seq,showfloat,shownum,showscaled,sin,snd,sort,spaces,sqrt,%
+     subtract,sum,system,take,takewhile,tinynum,tl,transpose,undef,%
+     until,zip2,zip3,zip4,zip5,zip6,zip},%
+   sensitive,%
+   morecomment=[l]||,%
+   morestring=[b]"%
+  }[keywords,comments,strings]%
+%%
+%% ML definition (c) 1999 Torben Hoffmann
+%%
+\lst@definelanguage{ML}%
+  {morekeywords={abstype,and,andalso,as,case,do,datatype,else,end,%
+       eqtype,exception,fn,fun,functor,handle,if,in,include,infix,%
+       infixr,let,local,nonfix,of,op,open,orelse,raise,rec,sharing,sig,%
+       signature,struct,structure,then,type,val,with,withtype,while},%
+   sensitive,%
+   morecomment=[n]{(*}{*)},%
+   morestring=[d]"%
+  }[keywords,comments,strings]%
+%%
+%% Oz definition (c) Andres Becerra Sandoval
+%%
+\lst@definelanguage{Oz}%
+  {morekeywords={andthen,at,attr,case,catch,choice,class,%
+      cond,declare,define,dis,div,else,elsecase,%
+      elseif,end,export,fail,false,feat,finally,%
+      from,fun,functor,if,import,in,local,%
+      lock,meth,mod,not,of,or,orelse,%
+      prepare,proc,prop,raise,require,self,skip,%
+      then,thread,true,try,unit},%
+   sensitive=true,%
+   morecomment=[l]{\%},%
+   morecomment=[s]{/*}{*/},%
+   morestring=[b]",%
+   morestring=[d]'%
+  }[keywords,comments,strings]%
+%%
+%% PHP definition by Luca Balzerani
+%%
+\lst@definelanguage{PHP}%
+  {morekeywords={%
+  %--- core language
+    <?,?>,::,break,case,continue,default,do,else,%
+    elseif,for,foreach,if,include,require,phpinfo,%
+    switch,while,false,FALSE,true,TRUE,%
+  %--- apache functions
+    apache_lookup_uri,apache_note,ascii2ebcdic,ebcdic2ascii,%
+    virtual,apache_child_terminate,apache_setenv,%
+  %--- array functions
+    array,array_change_key_case,array_chunk,array_count_values,%
+    array_filter,array_flip,array_fill,array_intersect,%
+    array_keys,array_map,array_merge,array_merge_recursive,%
+    array_pad,array_pop,array_push,array_rand,array_reverse,%
+    array_shift,array_slice,array_splice,array_sum,array_unique,%
+    array_values,array_walk,arsort,asort,compact,count,current,each,%
+    extract,in_array,array_search,key,krsort,ksort,list,natsort,%
+    next,pos,prev,range,reset,rsort,shuffle,sizeof,sort,uasort,%
+    usort,%
+  %--- aspell functions
+    aspell_new,aspell_check,aspell_check_raw,aspell_suggest,%
+  %--- bc functions
+    bcadd,bccomp,bcdiv,bcmod,bcmul,bcpow,bcscale,bcsqrt,bcsub,%
+  %--- bzip2 functions
+    bzclose,bzcompress,bzdecompress,bzerrno,bzerror,bzerrstr,%
+    bzopen,bzread,bzwrite,%
+  %--- calendar functions
+    JDToGregorian,GregorianToJD,JDToJulian,JulianToJD,JDToJewish,%
+    JDToFrench,FrenchToJD,JDMonthName,JDDayOfWeek,easter_date,%
+    unixtojd,jdtounix,cal_days_in_month,cal_to_jd,cal_from_jd,%
+  %--- ccvs functions
+    ccvs_init,ccvs_done,ccvs_new,ccvs_add,ccvs_delete,ccvs_auth,%
+    ccvs_reverse,ccvs_sale,ccvs_void,ccvs_status,ccvs_count,%
+    ccvs_report,ccvs_command,ccvs_textvalue,%
+  %--- classobj functions
+    call_user_method,call_user_method_array,class_exists,get_class,%
+    get_class_vars,get_declared_classes,get_object_vars,%
+    is_a,is_subclass_of,method_exists,%
+  %--- com functions
+    COM,VARIANT,com_load,com_invoke,com_propget,com_get,com_propput,%
+    com_set,com_addref,com_release,com_isenum,com_load_typelib,%
+  %--- cpdf functions
+    cpdf_add_annotation,cpdf_add_outline,cpdf_arc,cpdf_begin_text,%
+    cpdf_clip,cpdf_close,cpdf_closepath,cpdf_closepath_fill_stroke,%
+    cpdf_continue_text,cpdf_curveto,cpdf_end_text,cpdf_fill,%
+    cpdf_finalize,cpdf_finalize_page,%
+    cpdf_import_jpeg,cpdf_lineto,cpdf_moveto,cpdf_newpath,cpdf_open,%
+    cpdf_page_init,cpdf_place_inline_image,cpdf_rect,cpdf_restore,%
+    cpdf_rmoveto,cpdf_rotate,cpdf_rotate_text,cpdf_save,%
+    cpdf_scale,cpdf_set_char_spacing,cpdf_set_creator,%
+    cpdf_set_font,cpdf_set_horiz_scaling,cpdf_set_keywords,%
+    cpdf_set_page_animation,cpdf_set_subject,cpdf_set_text_matrix,%
+    cpdf_set_text_rendering,cpdf_set_text_rise,cpdf_set_title,%
+    cpdf_setdash,cpdf_setflat,cpdf_setgray,cpdf_setgray_fill,%
+    cpdf_setlinecap,cpdf_setlinejoin,cpdf_setlinewidth,%
+    cpdf_setrgbcolor,cpdf_setrgbcolor_fill,cpdf_setrgbcolor_stroke,%
+    cpdf_show_xy,cpdf_stringwidth,cpdf_set_font_directories,%
+    cpdf_set_viewer_preferences,cpdf_stroke,cpdf_text,%
+    cpdf_set_action_url,%
+  %--- crack functions
+    crack_opendict,crack_closedict,crack_check,crack_getlastmessage,%
+  %--- ctype functions
+    ctype_alnum,ctype_alpha,ctype_cntrl,ctype_digit,ctype_lower,%
+    ctype_print,ctype_punct,ctype_space,ctype_upper,ctype_xdigit,%
+  %--- curl functions
+    curl_init,curl_setopt,curl_exec,curl_close,curl_version,%
+    curl_error,curl_getinfo,%
+  %--- cybercash functions
+    cybercash_encr,cybercash_decr,cybercash_base64_encode,%
+  %--- cybermut functions
+    cybermut_creerformulairecm,cybermut_testmac,%
+  %--- cyrus functions
+    cyrus_connect,cyrus_authenticate,cyrus_bind,cyrus_unbind,%
+    cyrus_close,%
+  %--- datetime functions
+    checkdate,date,getdate,gettimeofday,gmdate,gmmktime,gmstrftime,%
+    microtime,mktime,strftime,time,strtotime,%
+  %--- dbase functions
+    dbase_create,dbase_open,dbase_close,dbase_pack,dbase_add_record,%
+    dbase_delete_record,dbase_get_record,%
+    dbase_numfields,dbase_numrecords,%
+  %--- dba functions
+    dba_close,dba_delete,dba_exists,dba_fetch,dba_firstkey,%
+    dba_nextkey,dba_popen,dba_open,dba_optimize,dba_replace,%
+  %--- dbm functions
+    dbmopen,dbmclose,dbmexists,dbmfetch,dbminsert,dbmreplace,%
+    dbmfirstkey,dbmnextkey,dblist,%
+  %--- dbx functions
+    dbx_close,dbx_connect,dbx_error,dbx_query,dbx_sort,dbx_compare,%
+  %--- dio functions
+    dio_open,dio_read,dio_write,dio_truncate,dio_stat,dio_seek,%
+    dio_close,%
+  %--- dir functions
+    chroot,chdir,dir,closedir,getcwd,opendir,readdir,rewinddir,%
+  %--- dotnet functions
+    dotnet_load,%
+  %--- errorfunc functions
+    error_log,error_reporting,restore_error_handler,%
+    trigger_error,user_error,%
+  %--- exec functions
+    escapeshellarg,escapeshellcmd,exec,passthru,system,shell_exec,%
+  %--- fbsql functions
+    fbsql_affected_rows,fbsql_autocommit,fbsql_change_user,%
+    fbsql_commit,fbsql_connect,fbsql_create_db,fbsql_create_blob,%
+    fbsql_database_password,fbsql_data_seek,fbsql_db_query,%
+    fbsql_drop_db,fbsql_errno,fbsql_error,fbsql_fetch_array,%
+    fbsql_fetch_field,fbsql_fetch_lengths,fbsql_fetch_object,%
+    fbsql_field_flags,fbsql_field_name,fbsql_field_len,%
+    fbsql_field_table,fbsql_field_type,fbsql_free_result,%
+    fbsql_list_dbs,fbsql_list_fields,fbsql_list_tables,%
+    fbsql_num_fields,fbsql_num_rows,fbsql_pconnect,fbsql_query,%
+    fbsql_read_clob,fbsql_result,fbsql_rollback,fbsql_set_lob_mode,%
+    fbsql_start_db,fbsql_stop_db,fbsql_tablename,fbsql_warnings,%
+    fbsql_get_autostart_info,fbsql_hostname,fbsql_password,%
+    fbsql_username,%
+  %--- fdf functions
+    fdf_open,fdf_close,fdf_create,fdf_save,fdf_get_value,%
+    fdf_next_field_name,fdf_set_ap,fdf_set_status,fdf_get_status,%
+    fdf_get_file,fdf_set_flags,fdf_set_opt,%
+    fdf_set_javascript_action,fdf_set_encoding,fdf_add_template,%
+  %--- filepro functions
+    filepro,filepro_fieldname,filepro_fieldtype,filepro_fieldwidth,%
+    filepro_fieldcount,filepro_rowcount,%
+  %--- filesystem functions
+    basename,chgrp,chmod,chown,clearstatcache,copy,delete,dirname,%
+    diskfreespace,disk_total_space,fclose,feof,fflush,fgetc,fgetcsv,%
+    fgetss,file_get_contents,file,file_exists,fileatime,filectime,%
+    fileinode,filemtime,fileowner,fileperms,filesize,filetype,flock,%
+    fopen,fpassthru,fputs,fread,fscanf,fseek,fstat,ftell,ftruncate,%
+    set_file_buffer,is_dir,is_executable,is_file,is_link,%
+    is_writable,is_writeable,is_uploaded_file,link,linkinfo,mkdir,%
+    parse_ini_file,pathinfo,pclose,popen,readfile,readlink,rename,%
+    rmdir,stat,lstat,realpath,symlink,tempnam,tmpfile,touch,umask,%
+  %--- fribidi functions
+    fribidi_log2vis,%
+  %--- ftp functions
+    ftp_connect,ftp_login,ftp_pwd,ftp_cdup,ftp_chdir,ftp_mkdir,%
+    ftp_nlist,ftp_rawlist,ftp_systype,ftp_pasv,ftp_get,ftp_fget,%
+    ftp_fput,ftp_size,ftp_mdtm,ftp_rename,ftp_delete,ftp_site,%
+    ftp_quit,ftp_exec,ftp_set_option,ftp_get_option,%
+  %--- funchand functions
+    call_user_func_array,call_user_func,create_function,%
+    func_get_args,func_num_args,function_exists,%
+    register_shutdown_function,register_tick_function,%
+  %--- gettext functions
+    bindtextdomain,bind_textdomain_codeset,dcgettext,dcngettext,%
+    dngettext,gettext,ngettext,textdomain,%
+  %--- gmp functions
+    gmp_init,gmp_intval,gmp_strval,gmp_add,gmp_sub,gmp_mul,%
+    gmp_div_r,gmp_div_qr,gmp_div,gmp_mod,gmp_divexact,gmp_cmp,%
+    gmp_com,gmp_abs,gmp_sign,gmp_fact,gmp_sqrt,gmp_sqrtrm,%
+    gmp_pow,gmp_powm,gmp_prob_prime,gmp_gcd,gmp_gcdext,gmp_invert,%
+    gmp_jacobi,gmp_random,gmp_and,gmp_or,gmp_xor,gmp_setbit,%
+    gmp_scan0,gmp_scan1,gmp_popcount,gmp_hamdist,%
+  %--- http functions
+    header,headers_sent,setcookie,%
+  %--- hw functions
+    hw_Array2Objrec,hw_Children,hw_ChildrenObj,hw_Close,hw_Connect,%
+    hw_Deleteobject,hw_DocByAnchor,hw_DocByAnchorObj,%
+    hw_Document_BodyTag,hw_Document_Content,hw_Document_SetContent,%
+    hw_ErrorMsg,hw_EditText,hw_Error,hw_Free_Document,hw_GetParents,%
+    hw_GetChildColl,hw_GetChildCollObj,hw_GetRemote,%
+    hw_GetSrcByDestObj,hw_GetObject,hw_GetAndLock,hw_GetText,%
+    hw_GetObjectByQueryObj,hw_GetObjectByQueryColl,%
+    hw_GetChildDocColl,hw_GetChildDocCollObj,hw_GetAnchors,%
+    hw_Mv,hw_Identify,hw_InCollections,hw_Info,hw_InsColl,hw_InsDoc,%
+    hw_InsertObject,hw_mapid,hw_Modifyobject,hw_New_Document,%
+    hw_Output_Document,hw_pConnect,hw_PipeDocument,hw_Root,%
+    hw_Who,hw_getusername,hw_stat,hw_setlinkroot,hw_connection_info,%
+    hw_insertanchors,hw_getrellink,hw_changeobject,%
+  %--- ibase functions
+    ibase_connect,ibase_pconnect,ibase_close,ibase_query,%
+    ibase_fetch_row,ibase_fetch_object,ibase_field_info,%
+    ibase_free_result,ibase_prepare,ibase_execute,ibase_trans,%
+    ibase_rollback,ibase_timefmt,ibase_num_fields,ibase_blob_add,%
+    ibase_blob_close,ibase_blob_create,ibase_blob_echo,%
+    ibase_blob_import,ibase_blob_info,ibase_blob_open,%
+  %--- icap functions
+    icap_open,icap_close,icap_fetch_event,icap_list_events,%
+    icap_delete_event,icap_snooze,icap_list_alarms,%
+    icap_rename_calendar,icap_delete_calendar,icap_reopen,%
+  %--- iconv functions
+    iconv,iconv_get_encoding,iconv_set_encoding,ob_iconv_handler,%
+  %--- ifx functions
+    ifx_connect,ifx_pconnect,ifx_close,ifx_query,ifx_prepare,ifx_do,%
+    ifx_errormsg,ifx_affected_rows,ifx_getsqlca,ifx_fetch_row,%
+    ifx_fieldtypes,ifx_fieldproperties,ifx_num_fields,ifx_num_rows,%
+    ifx_create_char,ifx_free_char,ifx_update_char,ifx_get_char,%
+    ifx_copy_blob,ifx_free_blob,ifx_get_blob,ifx_update_blob,%
+    ifx_textasvarchar,ifx_byteasvarchar,ifx_nullformat,%
+    ifxus_free_slob,ifxus_close_slob,ifxus_open_slob,%
+    ifxus_seek_slob,ifxus_read_slob,ifxus_write_slob,%
+  %--- iisfunc functions
+    iis_get_server_by_path,iis_get_server_by_comment,iis_add_server,%
+    iis_set_dir_security,iis_get_dir_security,iis_set_server_rights,%
+    iis_set_script_map,iis_get_script_map,iis_set_app_settings,%
+    iis_stop_server,iis_stop_service,iis_start_service,%
+  %--- image functions
+    exif_imagetype,exif_read_data,exif_thumbnail,getimagesize,%
+    imagealphablending,imagearc,imagefilledarc,imageellipse,%
+    imagechar,imagecharup,imagecolorallocate,imagecolordeallocate,%
+    imagecolorclosest,imagecolorclosestalpha,imagecolorclosestthwb,%
+    imagecolorexactalpha,imagecolorresolve,imagecolorresolvealpha,%
+    imagecolorset,imagecolorsforindex,imagecolorstotal,%
+    imagecopy,imagecopymerge,imagecopymergegray,imagecopyresized,%
+    imagecreate,imagecreatetruecolor,imagetruecolortopalette,%
+    imagecreatefromgd2,imagecreatefromgd2part,imagecreatefromgif,%
+    imagecreatefrompng,imagecreatefromwbmp,imagecreatefromstring,%
+    imagecreatefromxpm,imagedashedline,imagedestroy,imagefill,%
+    imagefilledrectangle,imagefilltoborder,imagefontheight,%
+    imagegd,imagegd2,imagegif,imagepng,imagejpeg,imagewbmp,%
+    imageline,imageloadfont,imagepalettecopy,imagepolygon,%
+    imagepsencodefont,imagepsfreefont,imagepsloadfont,%
+    imagepsslantfont,imagepstext,imagerectangle,imagesetpixel,%
+    imagesetstyle,imagesettile,imagesetthickness,imagestring,%
+    imagesx,imagesy,imagettfbbox,imageftbbox,imagettftext,%
+    imagetypes,jpeg2wbmp,png2wbmp,iptcembed,read_exif_data,%
+  %--- imap functions
+    imap_8bit,imap_alerts,imap_append,imap_base64,imap_binary,%
+    imap_bodystruct,imap_check,imap_clearflag_full,imap_close,%
+    imap_delete,imap_deletemailbox,imap_errors,imap_expunge,%
+    imap_fetchbody,imap_fetchheader,imap_fetchstructure,%
+    imap_getmailboxes,imap_getsubscribed,imap_header,%
+    imap_headers,imap_last_error,imap_listmailbox,%
+    imap_mail,imap_mail_compose,imap_mail_copy,imap_mail_move,%
+    imap_mime_header_decode,imap_msgno,imap_num_msg,imap_num_recent,%
+    imap_ping,imap_popen,imap_qprint,imap_renamemailbox,imap_reopen,%
+    imap_rfc822_parse_headers,imap_rfc822_write_address,%
+    imap_search,imap_setacl,imap_set_quota,imap_setflag_full,%
+    imap_status,imap_subscribe,imap_uid,imap_undelete,%
+    imap_utf7_decode,imap_utf7_encode,imap_utf8,imap_thread,%
+  %--- info functions
+    assert,assert_options,extension_loaded,dl,getenv,get_cfg_var,%
+    get_defined_constants,get_extension_funcs,getmygid,%
+    get_loaded_extensions,get_magic_quotes_gpc,%
+    getlastmod,getmyinode,getmypid,getmyuid,get_required_files,%
+    ini_alter,ini_get,ini_get_all,ini_restore,ini_set,phpcredits,%
+    phpversion,php_logo_guid,php_sapi_name,php_uname,putenv,%
+    set_time_limit,version_compare,zend_logo_guid,zend_version,%
+  %--- ircg functions
+    ircg_pconnect,ircg_fetch_error_msg,ircg_set_current,ircg_join,%
+    ircg_msg,ircg_notice,ircg_nick,ircg_topic,ircg_channel_mode,%
+    ircg_whois,ircg_kick,ircg_ignore_add,ircg_ignore_del,%
+    ircg_is_conn_alive,ircg_lookup_format_messages,%
+    ircg_set_on_die,ircg_set_file,ircg_get_username,%
+    ircg_nickname_unescape,%
+  %--- java functions
+    java_last_exception_clear,java_last_exception_get,%
+  %--- ldap functions
+    ldap_add,ldap_bind,ldap_close,ldap_compare,ldap_connect,%
+    ldap_delete,ldap_dn2ufn,ldap_err2str,ldap_errno,ldap_error,%
+    ldap_first_attribute,ldap_first_entry,ldap_free_result,%
+    ldap_get_dn,ldap_get_entries,ldap_get_option,ldap_get_values,%
+    ldap_list,ldap_modify,ldap_mod_add,ldap_mod_del,%
+    ldap_next_attribute,ldap_next_entry,ldap_read,ldap_rename,%
+    ldap_set_option,ldap_unbind,ldap_8859_to_t61,%
+    ldap_next_reference,ldap_parse_reference,ldap_parse_result,%
+    ldap_sort,ldap_start_tls,ldap_t61_to_8859,%
+  %--- mail functions
+    mail,ezmlm_hash,%
+  %--- math functions
+    abs,acos,acosh,asin,asinh,atan,atanh,atan2,base_convert,bindec,%
+    cos,cosh,decbin,dechex,decoct,deg2rad,exp,expm1,floor,%
+    hexdec,hypot,is_finite,is_infinite,is_nan,lcg_value,log,log10,%
+    max,min,mt_rand,mt_srand,mt_getrandmax,number_format,octdec,pi,%
+    rad2deg,rand,round,sin,sinh,sqrt,srand,tan,tanh,%
+  %--- mbstring functions
+    mb_language,mb_parse_str,mb_internal_encoding,mb_http_input,%
+    mb_detect_order,mb_substitute_character,mb_output_handler,%
+    mb_strlen,mb_strpos,mb_strrpos,mb_substr,mb_strcut,mb_strwidth,%
+    mb_convert_encoding,mb_detect_encoding,mb_convert_kana,%
+    mb_decode_mimeheader,mb_convert_variables,%
+    mb_decode_numericentity,mb_send_mail,mb_get_info,%
+    mb_ereg,mb_eregi,mb_ereg_replace,mb_eregi_replace,mb_split,%
+    mb_ereg_search,mb_ereg_search_pos,mb_ereg_search_regs,%
+    mb_ereg_search_getregs,mb_ereg_search_getpos,%
+  %--- mcal functions
+    mcal_open,mcal_popen,mcal_reopen,mcal_close,%
+    mcal_rename_calendar,mcal_delete_calendar,mcal_fetch_event,%
+    mcal_append_event,mcal_store_event,mcal_delete_event,%
+    mcal_list_alarms,mcal_event_init,mcal_event_set_category,%
+    mcal_event_set_description,mcal_event_set_start,%
+    mcal_event_set_alarm,mcal_event_set_class,mcal_is_leap_year,%
+    mcal_date_valid,mcal_time_valid,mcal_day_of_week,%
+    mcal_date_compare,mcal_next_recurrence,%
+    mcal_event_set_recur_daily,mcal_event_set_recur_weekly,%
+    mcal_event_set_recur_monthly_wday,mcal_event_set_recur_yearly,%
+    mcal_event_add_attribute,mcal_expunge,mcal_week_of_year,%
+  %--- mcrypt functions
+    mcrypt_get_cipher_name,mcrypt_get_block_size,%
+    mcrypt_create_iv,mcrypt_cbc,mcrypt_cfb,mcrypt_ecb,mcrypt_ofb,%
+    mcrypt_list_modes,mcrypt_get_iv_size,mcrypt_encrypt,%
+    mcrypt_module_open,mcrypt_module_close,mcrypt_generic_deinit,%
+    mcrypt_generic,mdecrypt_generic,mcrypt_generic_end,%
+    mcrypt_enc_is_block_algorithm_mode,%
+    mcrypt_enc_is_block_mode,mcrypt_enc_get_block_size,%
+    mcrypt_enc_get_supported_key_sizes,mcrypt_enc_get_iv_size,%
+    mcrypt_enc_get_modes_name,mcrypt_module_self_test,%
+    mcrypt_module_is_block_algorithm,mcrypt_module_is_block_mode,%
+    mcrypt_module_get_algo_key_size,%
+  %--- mhash functions
+    mhash_get_hash_name,mhash_get_block_size,mhash_count,mhash,%
+  %--- misc functions
+    connection_aborted,connection_status,connection_timeout,%
+    define,defined,die,eval,exit,get_browser,highlight_file,%
+    ignore_user_abort,iptcparse,leak,pack,show_source,sleep,uniqid,%
+    usleep,%
+  %--- mnogosearch functions
+    udm_add_search_limit,udm_alloc_agent,udm_api_version,%
+    udm_cat_list,udm_clear_search_limits,udm_errno,udm_error,%
+    udm_free_agent,udm_free_ispell_data,udm_free_res,%
+    udm_get_res_field,udm_get_res_param,udm_load_ispell_data,%
+    udm_check_charset,udm_check_stored,udm_close_stored,udm_crc32,%
+  %--- msession functions
+    msession_connect,msession_disconnect,msession_count,%
+    msession_destroy,msession_lock,msession_unlock,msession_set,%
+    msession_uniq,msession_randstr,msession_find,msession_list,%
+    msession_set_array,msession_listvar,msession_timeout,%
+    msession_getdata,msession_setdata,msession_plugin,%
+  %--- msql functions
+    msql,msql_affected_rows,msql_close,msql_connect,msql_create_db,%
+    msql_data_seek,msql_dbname,msql_drop_db,msql_dropdb,msql_error,%
+    msql_fetch_field,msql_fetch_object,msql_fetch_row,%
+    msql_field_seek,msql_fieldtable,msql_fieldtype,msql_fieldflags,%
+    msql_free_result,msql_freeresult,msql_list_fields,%
+    msql_list_dbs,msql_listdbs,msql_list_tables,msql_listtables,%
+    msql_num_rows,msql_numfields,msql_numrows,msql_pconnect,%
+    msql_regcase,msql_result,msql_select_db,msql_selectdb,%
+  %--- mssql functions
+    mssql_close,mssql_connect,mssql_data_seek,mssql_fetch_array,%
+    mssql_fetch_object,mssql_fetch_row,mssql_field_length,%
+    mssql_field_seek,mssql_field_type,mssql_free_result,%
+    mssql_min_error_severity,mssql_min_message_severity,%
+    mssql_num_fields,mssql_num_rows,mssql_pconnect,mssql_query,%
+    mssql_select_db,mssql_bind,mssql_execute,mssql_fetch_assoc,%
+    mssql_guid_string,mssql_init,mssql_rows_affected,%
+  %--- muscat functions
+    muscat_setup,muscat_setup_net,muscat_give,muscat_get,%
+  %--- mysql functions
+    mysql_affected_rows,mysql_change_user,mysql_character_set_name,%
+    mysql_connect,mysql_create_db,mysql_data_seek,mysql_db_name,%
+    mysql_drop_db,mysql_errno,mysql_error,mysql_escape_string,%
+    mysql_fetch_assoc,mysql_fetch_field,mysql_fetch_lengths,%
+    mysql_fetch_row,mysql_field_flags,mysql_field_name,%
+    mysql_field_seek,mysql_field_table,mysql_field_type,%
+    mysql_info,mysql_insert_id,mysql_list_dbs,mysql_list_fields,%
+    mysql_list_tables,mysql_num_fields,mysql_num_rows,%
+    mysql_ping,mysql_query,mysql_unbuffered_query,%
+    mysql_result,mysql_select_db,mysql_tablename,mysql_thread_id,%
+    mysql_get_host_info,mysql_get_proto_info,mysql_get_server_info,%
+  %--- network functions
+    checkdnsrr,closelog,debugger_off,debugger_on,%
+    fsockopen,gethostbyaddr,gethostbyname,gethostbynamel,getmxrr,%
+    getprotobynumber,getservbyname,getservbyport,ip2long,long2ip,%
+    pfsockopen,socket_get_status,socket_set_blocking,%
+    syslog,%
+  %--- nis functions
+    yp_get_default_domain,yp_order,yp_master,yp_match,yp_first,%
+    yp_errno,yp_err_string,yp_all,yp_cat,%
+  %--- oci8 functions
+    OCIDefineByName,OCIBindByName,OCILogon,OCIPLogon,OCINLogon,%
+    OCIExecute,OCICommit,OCIRollback,OCINewDescriptor,OCIRowCount,%
+    OCIResult,OCIFetch,OCIFetchInto,OCIFetchStatement,%
+    OCIColumnName,OCIColumnSize,OCIColumnType,OCIServerVersion,%
+    OCINewCursor,OCIFreeStatement,OCIFreeCursor,OCIFreeDesc,%
+    OCIError,OCIInternalDebug,OCICancel,OCISetPrefetch,%
+    OCISaveLobFile,OCISaveLob,OCILoadLob,OCIColumnScale,%
+    OCIColumnTypeRaw,OCINewCollection,OCIFreeCollection,%
+    OCICollAppend,OCICollAssignElem,OCICollGetElem,OCICollMax,%
+    OCICollTrim,%
+  %--- oracle functions
+    Ora_Bind,Ora_Close,Ora_ColumnName,Ora_ColumnSize,Ora_ColumnType,%
+    Ora_CommitOff,Ora_CommitOn,Ora_Do,Ora_Error,Ora_ErrorCode,%
+    Ora_Fetch,Ora_Fetch_Into,Ora_GetColumn,Ora_Logoff,Ora_Logon,%
+    Ora_Numcols,Ora_Numrows,Ora_Open,Ora_Parse,Ora_Rollback,%
+  %--- outcontrol functions
+    flush,ob_start,ob_get_contents,ob_get_length,ob_get_level,%
+    ob_flush,ob_clean,ob_end_flush,ob_end_clean,ob_implicit_flush,%
+  %--- ovrimos functions
+    ovrimos_connect,ovrimos_close,ovrimos_longreadlen,%
+    ovrimos_execute,ovrimos_cursor,ovrimos_exec,ovrimos_fetch_into,%
+    ovrimos_result,ovrimos_result_all,ovrimos_num_rows,%
+    ovrimos_field_name,ovrimos_field_type,ovrimos_field_len,%
+    ovrimos_free_result,ovrimos_commit,ovrimos_rollback,%
+  %--- pcntl functions
+    pcntl_fork,pcntl_signal,pcntl_waitpid,pcntl_wexitstatus,%
+    pcntl_wifsignaled,pcntl_wifstopped,pcntl_wstopsig,%
+    pcntl_exec,%
+  %--- pcre functions
+    preg_match,preg_match_all,preg_replace,preg_replace_callback,%
+    preg_quote,preg_grep,Pattern Modifiers,Pattern Syntax,%
+  %--- pdf functions
+    pdf_add_annotation,pdf_add_bookmark,pdf_add_launchlink,%
+    pdf_add_note,pdf_add_outline,pdf_add_pdflink,pdf_add_thumbnail,%
+    pdf_arc,pdf_arcn,pdf_attach_file,pdf_begin_page,%
+    pdf_begin_template,pdf_circle,pdf_clip,pdf_close,pdf_closepath,%
+    pdf_closepath_stroke,pdf_close_image,pdf_close_pdi,%
+    pdf_concat,pdf_continue_text,pdf_curveto,pdf_delete,%
+    pdf_endpath,pdf_end_pattern,pdf_end_template,pdf_fill,%
+    pdf_findfont,pdf_get_buffer,pdf_get_font,pdf_get_fontname,%
+    pdf_get_image_height,pdf_get_image_width,pdf_get_parameter,%
+    pdf_get_pdi_value,pdf_get_majorversion,pdf_get_minorversion,%
+    pdf_initgraphics,pdf_lineto,pdf_makespotcolor,pdf_moveto,%
+    pdf_open,pdf_open_CCITT,pdf_open_file,pdf_open_gif,%
+    pdf_open_image_file,pdf_open_jpeg,pdf_open_memory_image,%
+    pdf_open_pdi_page,pdf_open_png,pdf_open_tiff,pdf_place_image,%
+    pdf_rect,pdf_restore,pdf_rotate,pdf_save,pdf_scale,pdf_setcolor,%
+    pdf_setflat,pdf_setfont,pdf_setgray,pdf_setgray_fill,%
+    pdf_setlinecap,pdf_setlinejoin,pdf_setlinewidth,pdf_setmatrix,%
+    pdf_setpolydash,pdf_setrgbcolor,pdf_setrgbcolor_fill,%
+    pdf_set_border_color,pdf_set_border_dash,pdf_set_border_style,%
+    pdf_set_duration,pdf_set_font,pdf_set_horiz_scaling,%
+    pdf_set_info_author,pdf_set_info_creator,pdf_set_info_keywords,%
+    pdf_set_info_title,pdf_set_leading,pdf_set_parameter,%
+    pdf_set_text_rendering,pdf_set_text_rise,pdf_set_text_matrix,%
+    pdf_set_word_spacing,pdf_show,pdf_show_boxed,pdf_show_xy,%
+    pdf_stringwidth,pdf_stroke,pdf_translate,%
+  %--- pfpro functions
+    pfpro_init,pfpro_cleanup,pfpro_process,pfpro_process_raw,%
+  %--- pgsql functions
+    pg_close,pg_affected_rows,pg_connect,pg_dbname,pg_end_copy,%
+    pg_query,pg_fetch_array,pg_fetch_object,pg_fetch_row,%
+    pg_field_name,pg_field_num,pg_field_prtlen,pg_field_size,%
+    pg_free_result,pg_last_oid,pg_host,pg_last_notice,pg_lo_close,%
+    pg_lo_export,pg_lo_import,pg_lo_open,pg_lo_read,pg_lo_seek,%
+    pg_lo_read_all,pg_lo_unlink,pg_lo_write,pg_num_fields,%
+    pg_options,pg_pconnect,pg_port,pg_put_line,pg_fetch_result,%
+    pg_client_encoding,pg_trace,pg_tty,pg_untrace,pg_get_result,%
+    pg_send_query,pg_cancel_query,pg_connection_busy,%
+    pg_connection_status,pg_copy_from,pg_copy_to,pg_escape_bytea,%
+    pg_result_error,%
+  %--- posix functions
+    posix_kill,posix_getpid,posix_getppid,posix_getuid,%
+    posix_getgid,posix_getegid,posix_setuid,posix_seteuid,%
+    posix_setegid,posix_getgroups,posix_getlogin,posix_getpgrp,%
+    posix_setpgid,posix_getpgid,posix_getsid,posix_uname,%
+    posix_ctermid,posix_ttyname,posix_isatty,posix_getcwd,%
+    posix_getgrnam,posix_getgrgid,posix_getpwnam,posix_getpwuid,%
+  %--- printer functions
+    printer_open,printer_abort,printer_close,printer_write,%
+    printer_set_option,printer_get_option,printer_create_dc,%
+    printer_start_doc,printer_end_doc,printer_start_page,%
+    printer_create_pen,printer_delete_pen,printer_select_pen,%
+    printer_delete_brush,printer_select_brush,printer_create_font,%
+    printer_select_font,printer_logical_fontheight,%
+    printer_draw_rectangle,printer_draw_elipse,printer_draw_text,%
+    printer_draw_chord,printer_draw_pie,printer_draw_bmp,%
+  %--- pspell functions
+    pspell_add_to_personal,pspell_add_to_session,pspell_check,%
+    pspell_config_create,pspell_config_ignore,pspell_config_mode,%
+    pspell_config_repl,pspell_config_runtogether,%
+    pspell_new,pspell_new_config,pspell_new_personal,%
+    pspell_store_replacement,pspell_suggest,%
+  %--- qtdom functions
+    qdom_tree,qdom_error,%
+  %--- readline functions
+    readline,readline_add_history,readline_clear_history,%
+    readline_info,readline_list_history,readline_read_history,%
+  %--- recode functions
+    recode_string,recode,recode_file,%
+  %--- regex functions
+    ereg,ereg_replace,eregi,eregi_replace,split,spliti,sql_regcase,%
+  %--- sem functions
+    sem_get,sem_acquire,sem_release,sem_remove,shm_attach,%
+    shm_remove,shm_put_var,shm_get_var,shm_remove_var,ftok,%
+  %--- sesam functions
+    sesam_connect,sesam_disconnect,sesam_settransaction,%
+    sesam_rollback,sesam_execimm,sesam_query,sesam_num_fields,%
+    sesam_diagnostic,sesam_fetch_result,sesam_affected_rows,%
+    sesam_field_array,sesam_fetch_row,sesam_fetch_array,%
+    sesam_free_result,%
+  %--- session functions
+    session_start,session_destroy,session_name,session_module_name,%
+    session_id,session_register,session_unregister,session_unset,%
+    session_get_cookie_params,session_set_cookie_params,%
+    session_encode,session_set_save_handler,session_cache_limiter,%
+    session_write_close,%
+  %--- shmop functions
+    shmop_open,shmop_read,shmop_write,shmop_size,shmop_delete,%
+  %--- snmp functions
+    snmpget,snmpset,snmpwalk,snmpwalkoid,snmp_get_quick_print,%
+    snmprealwalk,%
+  %--- strings functions
+    addcslashes,addslashes,bin2hex,chop,chr,chunk_split,%
+    count_chars,crc32,crypt,echo,explode,get_html_translation_table,%
+    hebrev,hebrevc,htmlentities,htmlspecialchars,implode,join,%
+    localeconv,ltrim,md5,md5_file,metaphone,nl_langinfo,nl2br,ord,%
+    print,printf,quoted_printable_decode,quotemeta,str_rot13,rtrim,%
+    setlocale,similar_text,soundex,sprintf,strncasecmp,strcasecmp,%
+    strcmp,strcoll,strcspn,strip_tags,stripcslashes,stripslashes,%
+    strlen,strnatcmp,strnatcasecmp,strncmp,str_pad,strpos,strrchr,%
+    strrev,strrpos,strspn,strstr,strtok,strtolower,strtoupper,%
+    strtr,substr,substr_count,substr_replace,trim,ucfirst,ucwords,%
+    vsprintf,wordwrap,%
+  %--- swf functions
+    swf_openfile,swf_closefile,swf_labelframe,swf_showframe,%
+    swf_getframe,swf_mulcolor,swf_addcolor,swf_placeobject,%
+    swf_removeobject,swf_nextid,swf_startdoaction,%
+    swf_actiongeturl,swf_actionnextframe,swf_actionprevframe,%
+    swf_actionstop,swf_actiontogglequality,swf_actionwaitforframe,%
+    swf_actiongotolabel,swf_enddoaction,swf_defineline,%
+    swf_definepoly,swf_startshape,swf_shapelinesolid,%
+    swf_shapefillsolid,swf_shapefillbitmapclip,%
+    swf_shapemoveto,swf_shapelineto,swf_shapecurveto,%
+    swf_shapearc,swf_endshape,swf_definefont,swf_setfont,%
+    swf_fontslant,swf_fonttracking,swf_getfontinfo,swf_definetext,%
+    swf_definebitmap,swf_getbitmapinfo,swf_startsymbol,%
+    swf_startbutton,swf_addbuttonrecord,swf_oncondition,%
+    swf_viewport,swf_ortho,swf_ortho2,swf_perspective,swf_polarview,%
+    swf_pushmatrix,swf_popmatrix,swf_scale,swf_translate,swf_rotate,%
+  %--- sybase functions
+    sybase_affected_rows,sybase_close,sybase_connect,%
+    sybase_fetch_array,sybase_fetch_field,sybase_fetch_object,%
+    sybase_field_seek,sybase_free_result,sybase_get_last_message,%
+    sybase_min_error_severity,sybase_min_message_severity,%
+    sybase_num_fields,sybase_num_rows,sybase_pconnect,sybase_query,%
+    sybase_select_db,%
+  %--- uodbc functions
+    odbc_autocommit,odbc_binmode,odbc_close,odbc_close_all,%
+    odbc_connect,odbc_cursor,odbc_do,odbc_error,odbc_errormsg,%
+    odbc_execute,odbc_fetch_into,odbc_fetch_row,odbc_fetch_array,%
+    odbc_fetch_object,odbc_field_name,odbc_field_num,%
+    odbc_field_len,odbc_field_precision,odbc_field_scale,%
+    odbc_longreadlen,odbc_num_fields,odbc_pconnect,odbc_prepare,%
+    odbc_result,odbc_result_all,odbc_rollback,odbc_setoption,%
+    odbc_tableprivileges,odbc_columns,odbc_columnprivileges,%
+    odbc_primarykeys,odbc_foreignkeys,odbc_procedures,%
+    odbc_specialcolumns,odbc_statistics,%
+  %--- url functions
+    base64_decode,base64_encode,parse_url,rawurldecode,rawurlencode,%
+    urlencode,%
+  %--- var functions
+    doubleval,empty,floatval,gettype,get_defined_vars,%
+    import_request_variables,intval,is_array,is_bool,is_double,%
+    is_int,is_integer,is_long,is_null,is_numeric,is_object,is_real,%
+    is_scalar,is_string,isset,print_r,serialize,settype,strval,%
+    unset,var_dump,var_export,is_callable,%
+  %--- vpopmail functions
+    vpopmail_add_domain,vpopmail_del_domain,%
+    vpopmail_add_domain_ex,vpopmail_del_domain_ex,%
+    vpopmail_add_user,vpopmail_del_user,vpopmail_passwd,%
+    vpopmail_auth_user,vpopmail_alias_add,vpopmail_alias_del,%
+    vpopmail_alias_get,vpopmail_alias_get_all,vpopmail_error,%
+  %--- w32api functions
+    w32api_set_call_method,w32api_register_function,%
+    w32api_deftype,w32api_init_dtype,%
+  %--- wddx functions
+    wddx_serialize_value,wddx_serialize_vars,wddx_packet_start,%
+    wddx_add_vars,wddx_deserialize,%
+  %--- xml functions
+    xml_parser_create,xml_set_object,xml_set_element_handler,%
+    xml_set_processing_instruction_handler,xml_set_default_handler,%
+    xml_set_notation_decl_handler,%
+    xml_parse,xml_get_error_code,xml_error_string,%
+    xml_get_current_column_number,xml_get_current_byte_index,%
+    xml_parser_free,xml_parser_set_option,xml_parser_get_option,%
+    utf8_encode,xml_parser_create_ns,%
+    xml_set_start_namespace_decl_handler,%
+  %--- xslt functions
+    xslt_set_log,xslt_create,xslt_errno,xslt_error,xslt_free,%
+    xslt_set_sax_handler,xslt_set_scheme_handler,%
+    xslt_set_base,xslt_set_encoding,xslt_set_sax_handlers,%
+  %--- yaz functions
+    yaz_addinfo,yaz_close,yaz_connect,yaz_errno,yaz_error,yaz_hits,%
+    yaz_database,yaz_range,yaz_record,yaz_search,yaz_present,%
+    yaz_scan,yaz_scan_result,yaz_ccl_conf,yaz_ccl_parse,%
+    yaz_wait,yaz_sort,%
+  %--- zip functions
+    zip_close,zip_entry_close,zip_entry_compressedsize,%
+    zip_entry_filesize,zip_entry_name,zip_entry_open,zip_entry_read,%
+    zip_read,%
+  %--- zlib functions
+    gzclose,gzeof,gzfile,gzgetc,gzgets,gzgetss,gzopen,gzpassthru,%
+    gzread,gzrewind,gzseek,gztell,gzwrite,readgzfile,gzcompress,%
+    gzdeflate,gzinflate,gzencode,},%
+   sensitive,%
+   morecomment=[l]\#,%
+   morecomment=[l]//,%
+   morecomment=[s]{/*}{*/},%
+   morestring=[b]",%
+   morestring=[b]'%
+  }[keywords,comments,strings]%
+%%
+%% Prolog definition (c) 1997 Dominique de Waleffe
+%%
+\lst@definelanguage{Prolog}%
+  {morekeywords={op,mod,abort,ancestors,arg,ascii,ask,assert,asserta,%
+      assertz,atom,atomic,char,clause,close,concat,consult,ed,ef,em,%
+      eof,fail,file,findall,write,functor,getc,integer,is,length,%
+      listing,load,name,nl,nonvar,not,numbervars,op,or,pp,prin,print,%
+      private,prompt,putc,ratom,read,read_from_this_file,rename,repeat,%
+      retract,retractall,save,see,seeing,seen,sh,skip,statistics,%
+      subgoal_of,system,tab,tell,telling,time,told,trace,true,unload,%
+      untrace,var,write},%
+   sensitive=f,%
+   morecomment=[l]\%,%
+   morecomment=[s]{/*}{*/},%
+   morestring=[bd]",%
+   morestring=[bd]'%
+  }[keywords,comments,strings]%
+%%
+%% classic rexx listings definition
+%% by Patrick TJ McPhee <ptjm@interlog.com>
+%%
+\lst@definelanguage{Rexx}
+  {morekeywords={address,arg,call,do,drop,else,end,exit,if,iterate,%
+                 interpret,leave,nop,numeric,options,otherwise,parse,%
+                 procedure,pull,push,queue,return,say,signal,then,to,%
+                 trace,when},%
+   sensitive=false,%
+   morecomment=[n]{/*}{*/},%
+   morestring=[d]{'},%
+   morestring=[d]{"},%
+  }[keywords,comments,strings]%
+\lst@definelanguage{Ruby}%
+  {morekeywords={__FILE__,__LINE__,BEGIN,END,alias,and,begin,break,%
+      case,class,def,defined?,do,else,elsif,end,ensure,false,for,%
+      if,in,module,next,nil,not,or,redo,rescue,retry,return,self,%
+      super,then,true,undef,unless,until,when,while,yield},%
+   sensitive=true,%
+   morecomment=[l]\#,%
+   morecomment=[l]\#\#,%
+   morecomment=[s]{=BEGIN}{=END},%
+   morestring=[b]',%
+   morestring=[b]",%
+   morestring=[s]{\%q/}{/},%
+   morestring=[s]{\%q!}{!},%
+   morestring=[s]{\%q\{}{\}},%
+   morestring=[s]{\%q(}{)},%
+   morestring=[s]{\%q[}{]},%
+   morestring=[s]{\%q-}{-},%
+   morestring=[s]{\%Q/}{/},%
+   morestring=[s]{\%Q!}{!},%
+   morestring=[s]{\%Q\{}{\}},%
+   morestring=[s]{\%Q(}{)},%
+   morestring=[s]{\%Q[}{]},%
+   morestring=[s]{\%Q-}{-}%
+  }[keywords,comments,strings]%
+%%
+%% SHELXL definition (c) 1999 Aidan Philip Heerdegen
+%%
+\lst@definelanguage{SHELXL}%
+  {morekeywords={TITL,CELL,ZERR,LATT,SYMM,SFAC,DISP,UNIT,LAUE,%
+      REM,MORE,TIME,END,HKLF,OMIT,SHEL,BASF,TWIN,EXTI,SWAT,%
+      MERG,SPEC,RESI,MOVE,ANIS,AFIX,HFIX,FRAG,FEND,EXYZ,EADP,%
+      EQIV,OMIT,CONN,PART,BIND,FREE,DFIX,BUMP,SAME,SADI,CHIV,%
+      FLAT,DELU,SIMU,DEFS,ISOR,SUMP,L.S.,CGLS,SLIM,BLOC,DAMP,%
+      WGHT,FVAR,BOND,CONF,MPLA,RTAB,LIST,ACTA,SIZE,TEMP,WPDB,%
+      FMAP,GRID,PLAN,MOLE},%
+   sensitive=false,%
+   alsoother=_,% Makes the syntax highlighting ignore the underscores
+   morecomment=[l]{! },%
+  }%
+%%
+%% Tcl/Tk definition (c) Gerd Neugebauer
+%%
+\lst@definelanguage[tk]{tcl}[]{tcl}%
+  {morekeywords={activate,add,separator,radiobutton,checkbutton,%
+      command,cascade,all,bell,bind,bindtags,button,canvas,canvasx,%
+      canvasy,cascade,cget,checkbutton,config,configu,configur,%
+      configure,clipboard,create,arc,bitmap,image,line,oval,polygon,%
+      rectangle,text,textwindow,curselection,delete,destroy,end,entry,%
+      entrycget,event,focus,font,actual,families,measure,metrics,names,%
+      frame,get,grab,current,release,status,grid,columnconfigure,%
+      rowconfigure,image,image,create,bitmap,photo,delete,height,types,%
+      widt,names,index,insert,invoke,itemconfigure,label,listbox,lower,%
+      menu,menubutton,message,move,option,add,clear,get,readfile,pack,%
+      photo,place,radiobutton,raise,scale,scroll,scrollbar,search,see,%
+      selection,send,stdin,stdout,stderr,tag,bind,text,tk,tkerror,%
+      tkwait,window,variable,visibility,toplevel,unknown,update,winfo,%
+      class,exists,ismapped,parent,reqwidth,reqheight,rootx,rooty,%
+      width,height,wm,aspect,client,command,deiconify,focusmodel,frame,%
+      geometry,group,iconbitmap,iconify,iconmask,iconname,iconposition,%
+      iconwindow,maxsize,minsize,overrideredirect,positionfrom,%
+      protocol,sizefrom,state,title,transient,withdraw,xview,yview,%
+      yposition,%
+      -accelerator,-activebackground,-activeborderwidth,%
+      -activeforeground,-after,-anchor,-arrow,-arrowshape,-aspect,%
+      -async,-background,-before,-bg,-bigincrement,-bitmap,-bordermode,%
+      -borderwidth,-button,-capstyle,-channel,-class,-closeenough,%
+      -colormap,-column,-columnspan,-command,-confine,-container,%
+      -count,-cursor,-data,-default,-detail,-digits,-direction,%
+      -displayof,-disableforeground,-elementborderwidth,-expand,%
+      -exportselection,-extend,-family,-fg,-file,-fill,-focus,-font,%
+      -fontmap,-foreground,-format,-from,-gamma,-global,-height,%
+      -highlightbackground,-highlightcolor,-highlightthickness,-icon,%
+      -image,-in,-insertbackground,-insertborderwidth,-insertofftime,%
+      -insertontime,-imsertwidth,-ipadx,-ipady,-joinstyle,-jump,%
+      -justify,-keycode,-keysym,-label,-lastfor,-length,-maskdata,%
+      -maskfile,-menu,-message,-mode,-offvalue,-onvalue,-orient,%
+      -outlien,-outlinestipple,-overstrike,-override,-padx,-pady,%
+      -pageanchor,-pageheight,-pagewidth,-pagey,-pagey,-palette,%
+      -parent,-place,-postcommand,-relheight,-relief,-relwidth,-relx,%
+      -rely,-repeatdelay,-repeatinterval,-resolution,-root,-rootx,%
+      -rooty,-rotate,-row,-rowspan,-screen,-selectcolor,-selectimage,%
+      -sendevent,-serial,-setgrid,-showvalue,-shrink,-side,-size,%
+      -slant,-sliderlength,-sliderrelief,-smooth,-splinesteps,-state,%
+      -sticky,-stipple,-style,-subsample,-subwindow,-tags,-takefocus,%
+      -tearoff,-tearoffcommand,-text,-textvariable,-tickinterval,-time,%
+      -title,-to,-troughcolor,-type,-underline,-use,-value,-variable,%
+      -visual,-width,-wrap,-wraplength,-x,-xscrollcommand,-y,%
+      -bgstipple,-fgstipple,-lmargin1,-lmargin2,-rmargin,-spacing1,%
+      -spacing2,-spacing3,-tabs,-yscrollcommand,-zoom,%
+      activate,add,addtag,bbox,cget,clone,configure,coords,%
+      curselection,debug,delete,delta,deselect,dlineinfo,dtag,dump,%
+      entrycget,entryconfigure,find,flash,fraction,get,gettags,handle,%
+      icursor,identify,index,insert,invoke,itemcget,itemconfigure,mark,%
+      moveto,own,post,postcascade,postscript,put,redither,ranges,%
+      scale,select,show,tag,type,unpost,xscrollcommand,xview,%
+      yscrollcommand,yview,yposition}%
+  }%
+\lst@definelanguage[]{tcl}%
+  {alsoletter={.:,*=&-},%
+   morekeywords={after,append,array,names,exists,anymore,donesearch,%
+      get,nextelement,set,size,startsearch,auto_mkindex,binary,break,%
+      case,catch,cd,clock,close,concat,console,continue,default,else,%
+      elseif,eof,error,eval,exec,-keepnewline,exit,expr,fblocked,%
+      fconfigure,fcopy,file,atime,dirname,executable,exists,extension,%
+      isdirectory,isfile,join,lstat,mtime,owned,readable,readlink,%
+      rootname,size,stat,tail,type,writable,-permissions,-group,-owner,%
+      -archive,-hidden,-readonly,-system,-creator,-type,-force,%
+      fileevent,flush,for,foreach,format,gets,glob,global,history,if,%
+      incr,info,argsbody,cmdcount,commands,complete,default,exists,%
+      globals,level,library,locals,patchlevel,procs,script,tclversion,%
+      vars,interp,join,lappend,lindex,linsert,list,llength,lrange,%
+      lreplace,lsearch,-exact,-regexp,-glob,lsort,-ascii,-integer,%
+      -real,-dictionary,-increasing,-decreasing,-index,-command,load,%
+      namespace,open,package,forget,ifneeded,provide,require,unknown,%
+      vcompare,versions,vsatisfies,pid,proc,puts,-nonewline,pwd,read,%
+      regexp,-indices,regsub,-all,-nocaserename,return,scan,seek,set,%
+      socket,source,split,string,compare,first,index,last,length,match,%
+      range,tolower,toupper,trim,trimleft,trimright,subst,switch,tell,%
+      time,trace,variable,vdelete,vinfo,unknown,unset,uplevel,upvar,%
+      vwait,while,acos,asin,atan,atan2,ceil,cos,cosh,exp,floor,fmod,%
+      hypot,log,log10,pow,sin,sinh,sqrt,tan,tanh,abs,double,int,round%
+      },%
+   morestring=[d]",%
+   morecomment=[f]\#,%
+   morecomment=[l]{;\#},%
+   morecomment=[l]{[\#},%
+   morecomment=[l]{\{\#}%
+  }[keywords,comments,strings]%
+%%
+%% VBScript definition (c) 2000 Sonja Weidmann
+%%
+\lst@definelanguage{VBScript}%
+  {morekeywords={Call,Case,Const,Dim,Do,Each,Else,End,Erase,Error,Exit,%
+      Explicit,For,Function,If,Loop,Next,On,Option,Private,Public,%
+      Randomize,ReDim,Rem,Select,Set,Sub,Then,Wend,While,Abs,Array,Asc,%
+      Atn,CBool,CByte,CCur,CDate,CDbl,Chr,CInt,CLng,Cos,CreateObject,%
+      CSng,CStr,Date,DateAdd,DateDiff,DatePart,DateSerial,DateValue,%
+      Day,Exp,Filter,Fix,FormatCurrency,FormatDateTime,FormatNumber,%
+      FormatPercent,GetObject,Hex,Hour,InputBox,InStr,InStrRev,Int,%
+      IsArray,IsDate,IsEmpty,IsNull,IsNumeric,IsObject,Join,LBound,%
+      LCase,Left,Len,LoadPicture,Log,LTrim,Mid,Minute,Month,MonthName,%
+      MsgBox,Now,Oct,Replace,RGB,Right,Rnd,Round,RTrim,ScriptEngine,%
+      ScriptEngineBuildVersion,ScriptEngineMajorVersion,%
+      ScriptEngineMinorVersion,Second,Sgn,Sin,Space,Split,Sqr,StrComp,%
+      StrReverse,String,Tan,Time,TimeSerial,TimeValue,Trim,TypeName,%
+      UBound,UCase,VarType,Weekday,WeekdayName,Year, And,Eqv,Imp,Is,%
+      Mod,Not,Or,Xor,Add,BuildPath,Clear,Close,Copy,CopyFile,%
+      CopyFolder,CreateFolder,CreateTextFile,Delete,DeleteFile,%
+      DeleteFolder,Dictionary,Drive,DriveExists,Drives,Err,Exists,File,%
+      FileExists,FileSystemObject,Files,Folder,FolderExists,Folders,%
+      GetAbsolutePathName,GetBaseName,GetDrive,GetDriveName,%
+      GetExtensionName,GetFile,GetFileName,GetFolder,%
+      GetParentFolderName,GetSpecialFolder,GetTempName,Items,Keys,Move,%
+      MoveFile,MoveFolder,OpenAsTextStream,OpenTextFile,Raise,Read,%
+      ReadAll,ReadLine,Remove,RemoveAll,Skip,SkipLine,TextStream,Write,%
+      WriteBlankLines,WriteLine,Alias,Archive,CDROM,Compressed,%
+      Directory,Fixed,ForAppending,ForReading,ForWriting,Hidden,Normal,%
+      RAMDisk,ReadOnly,Remote,Removable,System,SystemFolder,%
+      TemporaryFolder,TristateFalse,TristateTrue,TristateUseDefault,%
+      Unknown,Volume,WindowsFolder,vbAbortRetryIgnore,%
+      vbApplicationModal,vbArray,vbBinaryCompare,vbBlack,vbBlue,%
+      vbBoolean,vbByte,vbCr,vbCrLf,vbCritical,vbCurrency,vbCyan,%
+      vbDataObject,vbDate,vbDecimal,vbDefaultButton1,vbDefaultButton2,%
+      vbDefaultButton3,vbDefaultButton4,vbDouble,vbEmpty,vbError,%
+      vbExclamation,vbFirstFourDays,vbFirstFullWeek,vbFirstJan1,%
+      vbFormFeed,vbFriday,vbGeneralDate,vbGreen,vbInformation,%
+      vbInteger,vbLf,vbLong,vbLongDate,vbLongTime,vbMagenta,vbMonday,%
+      vbNewLine,vbNull,vbNullChar,vbNullString,vbOKC,ancel,vbOKOnly,%
+      vbObject,vbObjectError,vbQuestion,vbRed,vbRetryCancel,vbSaturday,%
+      vbShortDate,vbShortTime,vbSingle,vbString,vbSunday,vbSystemModal,%
+      vbTab,vbTextCompare,vbThursday,vbTuesday,vbUseSystem,%
+      vbUseSystemDayOfWeek,vbVariant,vbVerticalTab,vbWednesday,vbWhite,%
+      vbYellow,vbYesNo,vbYesNoCancel},%
+   sensitive=f,%
+   morecomment=[l]',%
+   morestring=[d]"%
+  }[keywords,comments,strings]%
+%%
+%% VRML definition (c) 2001 Oliver Baum
+%%
+\lst@definelanguage[97]{VRML}
+  {morekeywords={DEF,EXTERNPROTO,FALSE,IS,NULL,PROTO,ROUTE,TO,TRUE,USE,%
+      eventIn,eventOut,exposedField,field,Introduction,Anchor,%
+      Appearance,AudioClip,Background,Billboard,Box,Collision,Color,%
+      ColorInterpolator,Cone,Coordinate,CoordinateInterpolator,%
+      Cylinder,CylinderSensor,DirectionalLight,ElevationGrid,Extrusion,%
+      Fog,FontStyle,Group,ImageTexture,IndexedFaceSet,IndexedLineSet,%
+      Inline,LOD,Material,MovieTexture,NavigationInfo,Normal,%
+      NormalInterpolator,OrientationInterpolator,PixelTexture,%
+      PlaneSensor,PointLight,PointSet,PositionInterpolator,%
+      ProximitySensor,ScalarInterpolator,Script,Shape,Sound,Sphere,%
+      SphereSensor,SpotLight,Switch,Text,TextureCoordinate,%
+      TextureTransform,TimeSensor,TouchSensor,Transform,Viewpoint,%
+      VisibilitySensor,WorldInfo},%
+   morecomment=[l]\#,% bug: starts comment in the first column
+   morestring=[b]"%
+  }[keywords,comments,strings]
+\endinput
+%%
+%% End of file `lstlang2.sty'.
diff --git a/dataflow/manual/lstlang3.sty b/dataflow/manual/lstlang3.sty
new file mode 100644
index 0000000..d4db105
--- /dev/null
+++ b/dataflow/manual/lstlang3.sty
@@ -0,0 +1,1616 @@
+%%
+%% This is file `lstlang3.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% lstdrvrs.dtx  (with options: `lang3')
+%% 
+%% The listings package is copyright 1996--2004 Carsten Heinz, and
+%% continued maintenance on the package is copyright 2006--2007 Brooks
+%% Moses. From 2013 on the maintenance is done by Jobst Hoffmann.
+%% The drivers are copyright 1997/1998/1999/2000/2001/2002/2003/2004/2006/
+%% 2007/2013 any individual author listed in this file.
+%%
+%% This file is distributed under the terms of the LaTeX Project Public
+%% License from CTAN archives in directory  macros/latex/base/lppl.txt.
+%% Either version 1.3 or, at your option, any later version.
+%%
+%% This file is completely free and comes without any warranty.
+%%
+%% Send comments and ideas on the package, error reports and additional
+%% programming languages to Jobst Hoffmann at <j.hoffmann@fh-aachen.de>.
+%%
+\ProvidesFile{lstlang3.sty}
+    [2015/06/04 1.6 listings language file]
+\lst@definelanguage[68]{Algol}%
+  {morekeywords={abs,and,arg,begin,bin,bits,bool,by,bytes,case,channel,%
+      char,co,comment,compl,conj,divab,do,down,elem,elif,else,empty,%
+      end,entier,eq,esac,exit,false,fi,file,flex,for,format,from,ge,%
+      goto,gt,heap,if,im,in,int,is,isnt,le,leng,level,loc,long,lt,lwb,%
+      minusab,mod,modab,mode,ne,nil,not,od,odd,of,op,or,ouse,out,over,%
+      overab,par,plusab,plusto,pr,pragmat,prio,proc,re,real,ref,repr,%
+      round,sema,shl,short,shorten,shr,sign,skip,string,struct,then,%
+      timesab,to,true,union,up,upb,void,while},%
+   sensitive=f,% ???
+   morecomment=[s]{\#}{\#},%
+   keywordcomment={co,comment}%
+  }[keywords,comments,keywordcomments]%
+\lst@definelanguage[60]{Algol}%
+  {morekeywords={array,begin,Boolean,code,comment,div,do,else,end,%
+      false,for,goto,if,integer,label,own,power,procedure,real,step,%
+      string,switch,then,true,until,value,while},%
+   sensitive=f,% ???
+   keywordcommentsemicolon={end}{else,end}{comment}%
+  }[keywords,keywordcomments]%
+%%
+%% Motorola 68K definition (c) 2006 Michael Franke
+%%
+\lst@definelanguage[Motorola68k]{Assembler}%
+ {morekeywords={ABCD,ADD,%
+ADDA,ADDI,ADDQ,ADDX,AND,ANDI,ASL,ASR,BCC,BLS,BCS,BLT,BEQ,BMI,BF,BNE,BGE,BPL,%
+BGT,BT,BHI,BVC,BLE,BVS,BCHG,BCLR,BRA,BSET,BSR,BTST,CHK,CLR,CMP,CMPA,CMPI,CMPM,%
+DBCC,DBLS,DBCS,DBLT,DBEQ,DBMI,DBF,DBNE,DBGE,DBPL,DBGT,DBT,DBHI,DBVC,DBLE,DBVS,DIVS,%
+DIVU,EOR,EORI,EXG,EXT,ILLEGAL,JMP,JSR,LEA,LINK,LSL,LSR,MOVE,MOVEA,MOVEM,MOVEP,MOVEQ,%
+MULS,MULU,NBCD,NEG,NEGX,NOP,NOT,OR,ORI,PEA,RESET,ROL,ROR,ROXL,ROXR,RTE,RTR,RTS,SBCD,%
+SCC,SLS,SCS,SLT,SEQ,SMI,SF,SNE,SGE,SPL,SGT,ST,SHI,SVC,SLE,SVS,STOP,SUB,SUBA,SUBI,SUBQ,%
+SUBX,SWAP,TAS,TRAP,TRAPV,TST,UNLK},%
+   sensitive=false,%
+   morecomment=[l]*,%
+   morecomment=[l];%
+   }[keywords,comments,strings]
+%%
+%% x86masm definition (c) 2002 Andrew Zabolotny
+%%
+\lst@definelanguage[x86masm]{Assembler}%
+  {morekeywords={al,ah,ax,eax,bl,bh,bx,ebx,cl,ch,cx,ecx,dl,dh,dx,edx,%
+      si,esi,di,edi,bp,ebp,sp,esp,cs,ds,es,ss,fs,gs,cr0,cr1,cr2,cr3,%
+      db0,db1,db2,db3,db4,db5,db6,db7,tr0,tr1,tr2,tr3,tr4,tr5,tr6,tr7,%
+      st,aaa,aad,aam,aas,adc,add,and,arpl,bound,bsf,bsr,bswap,bt,btc,%
+      btr,bts,call,cbw,cdq,clc,cld,cli,clts,cmc,cmp,cmps,cmpsb,cmpsw,%
+      cmpsd,cmpxchg,cwd,cwde,daa,das,dec,div,enter,hlt,idiv,imul,in,%
+      inc,ins,int,into,invd,invlpg,iret,ja,jae,jb,jbe,jc,jcxz,jecxz,%
+      je,jg,jge,jl,jle,jna,jnae,jnb,jnbe,jnc,jne,jng,jnge,jnl,jnle,%
+      jno,jnp,jns,jnz,jo,jp,jpe,jpo,js,jz,jmp,lahf,lar,lea,leave,lgdt,%
+      lidt,lldt,lmsw,lock,lods,lodsb,lodsw,lodsd,loop,loopz,loopnz,%
+      loope,loopne,lds,les,lfs,lgs,lss,lsl,ltr,mov,movs,movsb,movsw,%
+      movsd,movsx,movzx,mul,neg,nop,not,or,out,outs,pop,popa,popad,%
+      popf,popfd,push,pusha,pushad,pushf,pushfd,rcl,rcr,rep,repe,%
+      repne,repz,repnz,ret,retf,rol,ror,sahf,sal,sar,sbb,scas,seta,%
+      setae,setb,setbe,setc,sete,setg,setge,setl,setle,setna,setnae,%
+      setnb,setnbe,setnc,setne,setng,setnge,setnl,setnle,setno,setnp,%
+      setns,setnz,seto,setp,setpe,setpo,sets,setz,sgdt,shl,shld,shr,%
+      shrd,sidt,sldt,smsw,stc,std,sti,stos,stosb,stosw,stosd,str,sub,%
+      test,verr,verw,wait,wbinvd,xadd,xchg,xlatb,xor,fabs,fadd,fbld,%
+      fbstp,fchs,fclex,fcom,fcos,fdecstp,fdiv,fdivr,ffree,fiadd,ficom,%
+      fidiv,fidivr,fild,fimul,fincstp,finit,fist,fisub,fisubr,fld,fld1,%
+      fldl2e,fldl2t,fldlg2,fldln2,fldpi,fldz,fldcw,fldenv,fmul,fnop,%
+      fpatan,fprem,fprem1,fptan,frndint,frstor,fsave,fscale,fsetpm,%
+      fsin,fsincos,fsqrt,fst,fstcw,fstenv,fstsw,fsub,fsubr,ftst,fucom,%
+      fwait,fxam,fxch,fxtract,fyl2x,fyl2xp1,f2xm1},%
+   morekeywords=[2]{.align,.alpha,assume,byte,code,comm,comment,.const,%
+      .cref,.data,.data?,db,dd,df,dosseg,dq,dt,dw,dword,else,end,endif,%
+      endm,endp,ends,eq,equ,.err,.err1,.err2,.errb,.errdef,.errdif,%
+      .erre,.erridn,.errnb,.errndef,.errnz,event,exitm,extrn,far,%
+      .fardata,.fardata?,fword,ge,group,gt,high,if,if1,if2,ifb,ifdef,%
+      ifdif,ife,ifidn,ifnb,ifndef,include,includelib,irp,irpc,label,%
+      .lall,le,length,.lfcond,.list,local,low,lt,macro,mask,mod,.model,%
+      name,ne,near,offset,org,out,page,proc,ptr,public,purge,qword,.%
+      radix,record,rept,.sall,seg,segment,.seq,.sfcond,short,size,%
+      .stack,struc,subttl,tbyte,.tfcond,this,title,type,.type,width,%
+      word,.xall,.xcref,.xlist},%
+   alsoletter=.,alsodigit=?,%
+   sensitive=f,%
+   morestring=[b]",%
+   morestring=[b]',%
+   morecomment=[l];%
+   }[keywords,comments,strings]
+%%
+%% Clean definition (c) 1999 Jos\'e Romildo Malaquias
+%%
+%% Clean 1.3 :  some standard functional language: pure, lazy,
+%%              polymorphic type system, modules, type classes,
+%%              garbage collection, functions as first class citizens
+%%
+\lst@definelanguage{Clean}%
+  {otherkeywords={:,::,=,:==,=:,=>,->,<-,<-:,\{,\},\{|,|\},\#,\#!,|,\&,%
+      [,],!,.,\\\\,;,_},%
+   morekeywords={from,definition,implementation,import,module,system,%
+      case,code,if,in,let,let!,of,where,with,infix,infixl,infixr},%
+   morendkeywords={True,False,Start,Int,Real,Char,Bool,String,World,%
+      File,ProcId},%
+   sensitive,%
+   morecomment=[l]//,% missing comma: Markus Pahlow
+   morecomment=[n]{/*}{*/},%
+   morestring=[b]"%
+  }[keywords,comments,strings]%
+\lst@definelanguage{CIL}%
+  {morekeywords=[1]{assembly,beforefieldinit,class,default,cdecl,cil,corflags,%
+                    culture,custom,data,entrypoint,fastcall,field,file,%
+                    hidebysig,hash,il,imagebase,locals,managed,marshall,%
+                    maxstack,mresource,method,module,namespace,publickey,%
+                    stdcall,subsystem,thiscall,unmanaged,vararg,ver,vtfixup,%
+                   % types
+                    bool,char,float32,float64,int,int8,int16,int32,%
+                    int64,method,native,object,string,modopt,modreq,pinned,%
+                    typedref,valuetype,unsigned,void,%
+                   % defining types
+                    abstract,ansi,auto,autochar,beforefieldinit,boxed,class,%
+                    explicit,extends,implements,interface,famandassem,family,%
+                    famorassem,inherits,nested,override,pack,private,property,%
+                    public,rtspecialname,sealed,sequential,serializable,size,%
+                    specialname,static,unicode,%
+                   % postfix
+                    algorithm,alignment,extern,init,from,nometadata,with},%
+  morekeywords=[2]{add,and,arglist,beq,bge,bgt,ble,blt,bne,br,break,brfalse,%
+                    brtrue,call,calli,ceq,cgt,ckfinite,clt,conv,cpblk,div,%
+                    dup,endfilter,endfinally,initblk,jmp,ldarg,ldarga,ldc,%
+                    ldftn,ldind,ldloc,ldloca,ldnull,leave,localloc,mul,neg,%
+                    nop,not,or,pop,rem,ret,shl,shr,starg,stind,stloc,sub,%
+                    switch,xor,%
+                   % prefix
+                    tail,unaligned,volatile,%
+                   % postfix
+                    un,s,ovf,%
+                   % object
+                    box,callvirt,castclass,cpobj,cctor,ctor,initobj,isinst,%
+                    ldelem,ldelema,ldfld,ldflda,ldlen,ldobj,ldsfld,ldsflda,%
+                    ldstr,ldtoken,ldvirtftn,mkrefany,newarr,newobj,refanytype,%
+                    refanyval,rethrow,sizeof,stelem,stfld,stobj,stsfld,throw,%
+                    unbox},%
+  sensitive=true,%
+  morecomment=[l]{//},%
+  morestring=[b]"%
+}[keywords,comments,strings]%
+\lst@definelanguage{Comal 80}%
+  {morekeywords={AND,AUTO,CASE,DATA,DEL,DIM,DIV,DO,ELSE,ENDCASE,ENDIF,%
+      ENDPROC,ENDWHILE,EOD,EXEC,FALSE,FOR,GOTO,IF,INPUT,INT,LIST,LOAD,%
+      MOD,NEW,NEXT,NOT,OF,OR,PRINT,PROC,RANDOM,RENUM,REPEAT,RND,RUN,%
+      SAVE,SELECT,STOP,TAB,THEN,TRUE,UNTIL,WHILE,ZONE},%
+   sensitive=f,% ???
+   morecomment=[l]//,%
+   morestring=[d]"%
+  }[keywords,comments,strings]%
+\lst@definelanguage[WinXP]{command.com}%
+  {morekeywords={assoc,at,attrib,bootcfg,break,cacls,call,cd,chcp,chdir,%
+      chkdsk,chkntfs,cls,cmd,cmdextversion,color,comp,compact,convert,copy,%
+      date,defined,del,dir,diskcomp,diskcopy,do,doskey,echo,else,endlocal,%
+      erase,errorlevel,exist,exit,fc,find,findstr,for,format,ftype,goto,%
+      graftabl,help,if,in,label,md,mkdir,mode,more,move,not,off,path,%
+      pause,popd,print,prompt,pushd,rd,recover,ren,rename,replace,rmdir,%
+      set,setlocal,shift,sort,start,subst,time,title,tree,type,ver,%
+      verify,vol,xcopy},%
+   sensitive=false,%
+   alsoother={@},%
+   alsoletter={\%~:-/},%
+   morecomment=[l]{rem},%
+   morecomment=[l]{reM},%
+   morecomment=[l]{rEm},%
+   morecomment=[l]{rEM},%
+   morecomment=[l]{Rem},%
+   morecomment=[l]{ReM},%
+   morecomment=[l]{REm},%
+   morecomment=[l]{REM},%
+   morestring=[d]"%
+}[keywords,comments,strings]%
+\lst@definelanguage{Comsol}%
+  {morekeywords={%
+      adaption,arc1,arc2,arrayr,assemble,asseminit,beziercurve2,block2,%
+      block3,bsplinecurve2,bsplinecurve3,bsplinesurf3,bypassplot,cardg,%
+      ccoeffgroup,chamfer,checkgeom,circ1,circ2,coeff2cell,comsol,%
+      cone2,cone3,Contents,createhexes,createprisms,createquads,csgbl2,%
+      csgbl3,csgcmpbz,csgimplbz,csginitaux,csginitnr,csgproputil,%
+      csgrbconv,csgunique3,csguniquep,csgversion,csgvvovl,curve2,%
+      curve3,cylinder2,cylinder3,dat2str,defastget,display,drawgetobj,%
+      drawreobj,drawsetobj,dst,duplicate,dxflayers,dxfread,dxfwrite,%
+      econe2,econe3,eigloop,elcconstr,elcplbnd,elcplextr,elcplproj,%
+      elcplscalar,elempty,elemreobj,eleqc,eleqw,elevate,elgeom,ellip1,%
+      ellip2,ellipsoid2,ellipsoid3,ellipsoidgen_fl23,elmat,elovar,%
+      elpconstr,elshape,elvar,elvarm,embed,extrude,face3,faceprim3,%
+      fastsetop,fem2jxfem,femblocksu,femdiff,femeig,femexport,femgui,%
+      femimport,femiter,femlab,femlin,femmesh,femmeshexp,femnlin,%
+      femplot,femsfun,femsim,femsimlowlevel,femsimserver,femsol,%
+      femsolver,femstate,femstruct,femtime,femwave,festyle,fieldnames,%
+      fillet,fl1d,fl2d,fl3d,flaction,flafun,flappconvert,flappobj,%
+      flaxisequal,flbase,flbinary,flc1hs,flc2hs,flcanpnt,flcell2draw,%
+      flclear,flcolorbar,flcompact,flconeplot,flcontour2mesh,%
+      flcontour2meshaux,flconvreact,flconvreact1d,flconvreact2d,%
+      flconvreact3d,flcyl,fldc1hs,fldc2hs,fldegree,fldegreer3,%
+      fldegreet3,fldimvarsget,fldisp,fldraw2cell,fldrawnow,fldsmhs,%
+      fldsmsign,flevalmat,flexch,flexchprop,flfastgeom,flform,flgc,%
+      flgcbo,flgdconv,flgeom2cellstr,flgeomadj,flgeomarcize,flgeomec,%
+      flgeomed,flgeomepol,flgeomes,flgeomfc,flgeomfd,flgeomfdp,%
+      flgeomff1,flgeomff2,flgeomfn,flgeomfs,flgeomgetlocalsys,%
+      flgeominit,flgeominitprop,flgeomitransform,flgeomloft,flgeommesh,%
+      flgeomnbs,flgeomnes,flgeomnmr,flgeomnv,flgeompsinv,flgeomrmsing,%
+      flgeomrotp,flgeomsd,flgeomsdim,flgeomse,flgeomsf2,flgeomspm,%
+      flgeomtransform,flgeomud,flgeomvtx,flgetdraw,flheat,flheat1d,%
+      flheat2d,flheat3d,flhelmholtz,flhelmholtz1d,flhelmholtz2d,%
+      flhelmholtz3d,flim2curve,flinterp1,fliscont,flismember,%
+      flisnumeric,fljaction,fllaplace,fllaplace1d,fllaplace2d,%
+      fllaplace3d,flload,flloadfl,flloadmatfile,flloadmfile,%
+      fllobj2cellstr,flmakeevalstr,flmapsoljac,flmat2str,flmatch,%
+      flmesh2spline,flmesh2splineaux,flml65setup,flngdof,flnull,%
+      flnullorth,flpde,flpdeac,flpdec,flpdec1d,flpdec2d,flpdec3d,%
+      flpdedc,flpdedc2d,flpdedc3d,flpdedf,flpdedf1d,flpdedf2d,%
+      flpdedf3d,flpdees,flpdees2d,flpdees3d,flpdeg,flpdeg1d,flpdeg2d,%
+      flpdeg3d,flpdeht,flpdeht1d,flpdeht2d,flpdeht3d,flpdems,flpdems2d,%
+      flpdems3d,flpdens,flpdens2d,flpdens3d,flpdepn,flpdeps,flpdesm3d,%
+      flpdew,flpdew1d,flpdew2d,flpdew3d,flpdewb,flpdewb1d,flpdewb2d,%
+      flpdewb3d,flpdewc,flpdewc1d,flpdewc2d,flpdewc3d,flpdewe,%
+      flpdewe3d,flpdewp,flpdewp2d,flpdewp3d,flplot,flpoisson,%
+      flpoisson1d,flpoisson2d,flpoisson3d,flpric2,flpric3,flreobj,%
+      flreport,flresolvepath,flsave,flschrodinger,flschrodinger1d,%
+      flschrodinger2d,flschrodinger3d,flsde,flsdp,flsdt,flsetalpha,%
+      flsetdraw,flsmhs,flsmsign,flspnull,fltherm_cond1,fltrg,flversion,%
+      flversions,flverver,flwave,flwave1d,flwave2d,flwave3d,%
+      flwriteghist,formstr,gdsread,gencyl2,gencyl3,genextrude,%
+      genextrudeaux,geom,geom0,geom0get,geom1,geom1get,geom2,geom2get,%
+      geom3,geom3get,geom3j2m,geom3m2j,geomaddlblmargin,geomanalyze,%
+      geomarrayr,geomassign,geomcoerce,geomcomp,geomconnect,geomcopy,%
+      geomcsg,geomdel,geomedit,geomexport,geomfile,geomget,%
+      geomgetlabels,geomgetwrkpln,geomimport,geominfo,geominfoaux,%
+      geomlblplot,geomload,geomnumparse,geomobject,geomparse,geomplot,%
+      geomplot1,geomplot2,geomplot3,geomposition,geomproputil,%
+      geomreconstruct,geomreobj,geomserver,geomspline,geomsurf,%
+      geomupdate,get,getfemgeom,getisocurve,getjptr,getmesh,getsdim,%
+      getvmatrixexch,handlesolnumstr,helix1,helix2,helix3,hexahedron2,%
+      hexahedron3,histfrommat,idst,igesread,importplotdata,isempty,%
+      isfield,isfunc,isscript,javaclass,jproputil,jptr2geom,jptrgeom1,%
+      jptrgeom1_fl23,jptrgeom2,jptrgeom2_fl23,jptrgeom3,jptrgeom3_fl23,%
+      keiter,line1,line2,loadobj,loft,matlabinterpdata,mesh2geom,%
+      meshassign,meshcaseadd,meshcasedel,meshcaseutil,meshcheck,%
+      meshembed,meshenrich,meshenrich1,meshenrich2,meshenrich3,%
+      meshexport,meshextend,meshextrude,meshget,meshimport,meshinit,%
+      meshintegrate,meshmap,meshoptim,meshparse,meshplot,meshplot1,%
+      meshplot2,meshplot3,meshplotproputil,meshpoi,meshproputil,%
+      meshptplot,meshqual,meshrefine,meshrevolve,meshsmooth,%
+      meshsmooth2,meshsweep,meshvolume,minus,mirror,mkreflparams,%
+      mmsolve,modetype,move,moveglobalfields,mphproputil,mtimes,%
+      multiphysics,mypostinterp,notscript,onlyelsconstr,outassign,%
+      paramgeom,pde2draw,pde2equ,pde2fem,pde2geom,pdeblxpd,plus,point1,%
+      point2,point3,poisson,poly1,poly2,postanim,postapplysettings,%
+      postarrow,postarrowbnd,postcolorbar,postcont,postcontdomind,%
+      postcoord,postcopyprop,postcrossplot,postdistrprops,posteval,%
+      postflow,postfnd,postgeomplot,postgetfem,postgetstylecolor,%
+      postglobaleval,postglobalplot,postgp,postinit,postint,postinterp,%
+      postiso,postlin,postmakecontcol,postmax,postmaxmin,postmin,%
+      postmkcontbar,postmknormexpr,postmovie,postnewplot,%
+      postoldmaxminprops,postpd2pm,postplot,postplotconstants,%
+      postpm2pd,postprinc,postprincbnd,postprocgui,postproputil,%
+      postslice,postsurf,posttet,posttitle,print2file,pyramid2,%
+      pyramid3,rect1,rect2,restorefields,revolve,rmfield,rotate,%
+      rotmatrix,scale,serialize,set,setmesh,sh2str,sharg_2_5,shbub,%
+      shdisc,shdiv,shherm,shlag,shvec,simplecoerce,simreobj,slblocks,%
+      solassign,solid0,solid1,solid2,solid3,solidprim3,solproputil,%
+      solsize,solveraddcases,sphere2,sphere3,spiceimport,splineaux,%
+      split,splittoprim,square1,square2,stlread,submode,submodes,%
+      subsasgn,subsref,tangent,taucs,tetrahedron2,tetrahedron3,%
+      tobsplines,torus2,torus3,transform,update,updateassoc,%
+      updateassocinfo,updatefem,updateguistruct,updateobj,vrmlread,%
+      xmeshinfo,xmeshinit},%
+   sensitive=false,%
+   morecomment=[l]\%,%
+   morestring=[m]'%
+  }[keywords,comments,strings]%
+\lst@definelanguage{Elan}%
+  {morekeywords={ABS,AND,BOOL,CAND,CASE,CAT,COLUMNS,CONCR,CONJ,CONST,%
+      COR,DECR,DEFINES,DET,DIV,DOWNTO,ELIF,ELSE,END,ENDIF,ENDOP,%
+      ENDPACKET,ENDPROC,ENDREP,ENDSELECT,FALSE,FI,FILE,FOR,FROM,IF,%
+      INCR,INT,INV,LEAVE,LENGTH,LET,MOD,NOT,OF,OP,OR,OTHERWISE,PACKET,%
+      PROC,REAL,REP,REPEAT,ROW,ROWS,SELECT,SIGN,STRUCT,SUB,TEXT,THEN,%
+      TRANSP,TRUE,TYPE,UNTIL,UPTO,VAR,WHILE,WITH,XOR,%
+      maxint,sign,abs,min,max,random,initializerandom,subtext,code,%
+      replace,text,laenge,pos,compress,change,maxreal,smallreal,floor,%
+      pi,e,ln,log2,log10,sqrt,exp,tan,tand,sin,sind,cos,cosd,arctan,%
+      arctand,int,real,lastconversionok,put,putline,line,page,get,%
+      getline,input,output,sequentialfile,maxlinelaenge,reset,eof,%
+      close,complexzero,complexone,complexi,complex,realpart,imagpart,%
+      dphi,phi,vector,norm,replace,matrix,idn,row,column,sub,%
+      replacerow,replacecolumn,replaceelement,transp,errorsstop,stop},%
+   sensitive,%
+   morestring=[d]"%
+  }[keywords,strings]%
+%%
+%% Erlang definition (c) 2003 Daniel Gazard
+%%
+\lst@definelanguage{erlang}%
+  {morekeywords={abs,after,and,apply,atom,atom_to_list,band,binary,%
+      binary_to_list,binary_to_term,bor,bsl,bsr,bxor,case,catch,%
+      date,div,element,erase,end,exit,export,float,float_to_list,%
+      get,halt,hash,hd,if,info,import,integer,integer_to_list,%
+      length,link,list,list_to_atom,list_to_float,list_to_integer,%
+      list_to_tuple,module,node,nodes,now,of,or,pid,port,ports,%
+      processes,put,receive,reference,register,registered,rem,%
+      round,self,setelement,size,spawn,throw,time,tl,trace,trunc,%
+      tuple,tuple_to_list,unlink,unregister,whereis,error,false,%
+      infinity,nil,ok,true,undefined,when},%
+   otherkeywords={->,!,[,],\{,\}},%
+   morecomment=[l]\%,%
+   morestring=[b]",%
+   morestring=[b]'%
+  }[keywords,comments,strings]%
+\lst@definelanguage{Scala}%
+  {morekeywords={abstract,case,catch,class,def,%
+    do,else,extends,false,final,finally,%
+    for,if,implicit,import,lazy,match,mixin,%
+    new,null,object,override,package,%
+    private,protected,requires,return,sealed,%
+    super,this,trait,true,try,%
+    type,val,var,while,with,yield},%+
+otherkeywords={=,=>,<-,<\%,<:,>:,\#,@},%
+   sensitive,%
+   morecomment=[l]//,%
+   morecomment=[n]{/*}{*/},%
+   morestring=[b]",%
+   morestring=[b]',%
+   morestring=[b]""",%
+  }[keywords,comments,strings]%
+\lst@definelanguage{ksh}
+  {morekeywords={alias,awk,cat,echo,else,elif,fi,exec,exit,%
+      for,in,do,done,select,case,esac,while,until,function,%
+      time,export,cd,eval,fc,fg,kill,let,pwd,read,return,rm,%
+      glob,goto,history,if,logout,nice,nohup,onintr,repeat,sed,%
+      set,setenv,shift,source,switch,then,umask,unalias,%
+      unset,wait,@,env,argv,child,home,ignoreeof,noclobber,%
+      noglob,nomatch,path,prompt,shell,status,verbose,print,printf,%
+      sqrt,BEGIN,END},%
+   morecomment=[l]\#,%
+   morestring=[d]",%
+   morestring=[d]',%
+   morestring=[d]`%
+  }[keywords,comments,strings]%
+\lst@definelanguage{Lingo}
+  {morekeywords={abort,after,and,before,do,down,halt,me,new,not,of,%
+      on,or,otherwise,pass,put,result,return,set,tell,the,then,to,with,%
+      repeat,while,case,if,else,true,false,global,property,\_global,\_key,%
+      \_mouse,\_movie,\_player,\_sound,\_system,abbr,abbrev,abbreviated,abs,%
+      actionsenabled,activateapplication,activatewindow,active3drenderer,%
+      activecastlib,activewindow,actorlist,add,addat,addbackdrop,addcamera,%
+      addchild,addmodifier,addoverlay,addprop,addtoworld,addvertex,alert,%
+      alerthook,alignment,allowcustomcaching,allowgraphicmenu,allowsavelocal,%
+      allowtransportcontrol,allowvolumecontrol,allowzooming,alphathreshold,%
+      ambient,ambientcolor,ancestor,angle,anglebetween,animationenabled,%
+      antialias,antialiasthreshold,append,applicationname,applicationpath,%
+      appminimize,atan,attenuation,attributevalue,auto,autoblend,automask,%
+      autotab,axisangle,back,backcolor,backdrop,backgroundcolor,backspace,%
+      beep,beepon,beginrecording,beginsprite,beveldepth,beveltype,bgcolor,%
+      bias,bitand,bitmap,bitmapsizes,bitnot,bitor,bitrate,bitspersample,%
+      bitxor,blend,blendconstant,blendconstantlist,blendfactor,blendfunction,%
+      blendfunctionlist,blendlevel,blendrange,blendsource,blendsourcelist,%
+      blendtime,bone,bonesplayer,border,both,bottom,bottomcap,bottomradius,%
+      bottomspacing,boundary,boundingsphere,box,boxdropshadow,boxtype,%
+      breakconnection,breakloop,brightness,broadcastprops,browsername,%
+      buffersize,build,buttonsenabled,buttonstyle,buttontype,bytesstreamed,%
+      boolean,cachedocverify,cachesize,call,callancestor,camera,cameracount,%
+      cameraposition,camerarotation,cancelidleload,castlib,castlibnum,%
+      castmemberlist,center,centerregpoint,centerstage,changearea,channelcount,%
+      char,characterset,charpostoloc,chars,charspacing,chartonum,%
+      checkboxaccess,checkboxtype,checkmark,checknetmessages,child,chunksize,%
+      clearatrender,clearcache,clearerror,clearframe,clearglobals,clearvalue,%
+      clickloc,clickmode,clickon,clone,clonedeep,clonemodelfromcastmember,%
+      clonemotionfromcastmember,close,closed,closewindow,closexlib,collision,%
+      collisiondata,collisionnormal,color,world,colorbuffer,colorbufferdepth,%
+      colordepth,colorlist,colorrange,colors,colorsteps,commanddown,comments,%
+      compressed,connecttonetserver,constrainh,constraint,constrainv,,%
+      continue,controldown,controller,copypixels,copyrightinfo,copyto,%
+      copytoclipboard,cos,count,cpuhogticks,creaseangle,creases,[contains],%
+      createfolder,createmask,creatematte,creationdate,creator,crop,cross,%
+      crossproduct,cuepassed,cuepointnames,cuepointtimes,currentloopstate,%
+      currentspritenum,currenttime,cursor,cursorsize,curve,cylinder,ate,day,%
+      deactivateapplication,deactivatewindow,debug,debugplaybackenabled,%
+      decaymode,defaultrect,defaultrectmode,delay,delete,deleteall,deleteat,%
+      deletecamera,deletefolder,deleteframe,deletegroup,deletelight,%
+      deletemodel,deletemodelresource,deletemotion,deleteone,deleteprop,%
+      deleteshader,deletetexture,deletevertex,density,depth,depthbufferdepth,%
+      desktoprectlist,diffuse,diffusecolor,diffuselightmap,%
+      digitalvideotimescale,digitalvideotype,direction,directionalcolor,%
+      directionalpreset,directtostage,disableimagingtransformation,displayface,%
+      displaymode,distanceto,distribution,dither,done,doneparsing,dot,%
+      dotproduct,doubleclick,downloadnetthing,drag,draw,drawrect,dropshadow,%
+      duplicate,duplicateframe,duration,editable,editshortcutsenabled,%
+      elapsedtime,emissive,emitter,empty,emulatemultibuttonmouse,enabled,%
+      enablehotspot,end,endangle,endcolor,endframe,endrecording,endsprite,%
+      endtime,enter,enterframe,environment,erase,error,eventpassmode,%
+      exchange,exists,exit,exitframe,exitlock,exp,externalevent,%
+      externalparamcount,externalparamname,externalparamvalue,extractalpha,%
+      extrude3d,face,fadein,fadeout,fadeto,far,field,fieldofview,filename,%
+      fill,fillcolor,fillcycles,filldirection,filled,fillmode,filloffset,%
+      fillscale,findempty,findlabel,findpos,findposnear,finishidleload,%
+      firstindent,fixedlinespace,fixedrate,fixstagesize,flashrect,flashtostage,%
+      flat,fliph,flipv,float,floatp,floatprecision,flush,flushinputevents,%
+      fog,folderchar,font,fontsize,fontstyle,forecolor,forget,frame,%
+      framecount,framelabel,framepalette,framerate,frameready,framescript,%
+      framesound1,framesound2,framestohms,frametempo,frametransition,freeblock,%
+      freebytes,fromcastmember,fromimageobject,front,frontwindow,%
+      generatenormals,getaprop,getat,getbehaviordescription,getbehaviortooltip,%
+      getboneid,geterror,geterrorstring,gethardwareinfo,gethotspotrect,getlast,%
+      getlatestnetid,getnetaddresscookie,getneterrorstring,getnetmessage,%
+      getnetoutgoingbytes,getnettext,getnormalized,getnthfilenameinfolder,%
+      getnumberwaitingnetmessages,getone,getpeerconnectionlist,getpixel,%
+      getplaylist,getpos,getpref,getprop,getpropat,getpropertydescriptionlist,%
+      getrendererservices,getstreamstatus,gettemppath,getworldtransform,globals,%
+      glossmap,go,gotoframe,gotonetmovie,gotonetpage,gradienttype,gravity,%
+      group,handler,handlers,height,heightvertices,high,highlightpercentage,%
+      highlightstrength,hilite,hither,hittest,hmstoframes,hold,hotspot,html,%
+      hyperlink,hyperlinkclicked,hyperlinkrange,hyperlinks,hyperlinkstate,%
+      id3tags,identity,idle,idlehandlerperiod,idleloaddone,idleloadmode,%
+      idleloadperiod,idleloadtag,idlereadchunksize,ilk,image,imagecompression,%
+      imageenabled,imagequality,immovable,importfileinto,inflate,ink,inker,%
+      inlineimeenabled,insertbackdrop,insertframe,insertoverlay,inside,%
+      installmenu,instance,integer,integerp,interface,interpolate,%
+      interpolateto,intersect,index,interval,inverse,invert,invertmask,%
+      isbusy,isinworld,isoktoattach,ispastcuepoint,item,itemdelimiter,kerning,%
+      kerningthreshold,key,keyboardfocussprite,keycode,keydown,keydownscript,%
+      keyframeplayer,keypressed,keyup,keyupscript,label,labellist,last,%
+      lastchannel,lastclick,lastevent,lastframe,lastkey,lastroll,left,%
+      leftindent,length,lengthvertices,level,lifetime,light,line,linearlist,%
+      linecolor,linecount,linedirection,lineheight,lineoffset,linepostolocv,%
+      linesize,linkas,linked,list,listp,loaded,loadfile,loc,loch,locked,%
+      locktranslation,loctocharpos,locv,locvtolinepos,locz,lod,log,long,%
+      loop,loopcount,loopendtime,loopsremaining,loopstarttime,machinetype,%
+      magnitude,map,mapImageToStage,mapmembertostage,mapstagetomember,margin,%
+      marker,markerlist,mask,max,maxinteger,maxspeed,mci,media,mediaready,%
+      member,membernum,members,memorysize,menu,mesh,meshdeform,milliseconds,%
+      min,minspeed,modal,mode,model,modela,modelb,modelresource,%
+      modelsunderloc,modelsunderray,modelunderloc,modified,modifiedby,%
+      modifieddate,modifier,modifiers,month,mostrecentcuepoint,motion,%
+      mousechar,mousedown,mousedownscript,mouseenter,mouseh,mouseitem,%
+      mouseleave,mouselevel,mouseline,mouseloc,mousemember,mouseoverbutton,%
+      mouseup,mouseupoutside,mouseupscript,mousev,mousewithin,mouseword,move,%
+      moveablesprite,movetoback,movetofront,movevertex,movevertexhandle,%
+      movewindow,movie,movieaboutinfo,moviecopyrightinfo,moviefilefreesize,%
+      moviefilesize,moviefileversion,movieimagecompression,movieimagequality,%
+      moviename,moviepath,movierate,movietime,moviextralist,mpeglayer,%
+      multiply,multisound,name,near,nearfiltering,neighbor,netabort,netdone,%
+      neterror,netlastmoddate,netmime,netpresent,netstatus,nettextresult,%
+      netthrottleticks,newcamera,newcurve,newgroup,newlight,newmesh,newmodel,%
+      newmodelresource,newmotion,newshader,newtexture,next,none,normalize,%
+      normallist,normals,nothing,notify,nudge,number,numchannels,%
+      numparticles,numsegments,numtochar,objectp,offset,open,openresfile,%
+      openwindow,openxlib,optiondown,organizationname,originalfont,originh,%
+      originmode,originpoint,originv,orthoheight,overlay,pageheight,palette,%
+      palettemapping,paletteref,paletteindex,pan,paragraph,param,paramcount,%
+      parent,parsestring,particle,pasteclipboardinto,path,pathname,%
+      pathstrength,pattern,pause,pausedatstart,pausestate,percentplayed,%
+      percentstreamed,period,perpendicularto,persistent,pi,picture,picturep,%
+      plane,platform,play,playbackmode,playfile,playing,playlist,playnext,%
+      playrate,point,pointat,pointatorientation,pointinhyperlink,%
+      pointofcontact,pointtochar,pointtoitem,pointtoline,pointtoparagraph,%
+      pointtoword,position,positionreset,posterframe,postnettext,power,%
+      preferred3drenderer,preload,preloadbuffer,preloadeventabort,preloadmember,%
+      preloadmode,preloadmovie,preloadnetthing,preloadram,preloadtime,%
+      premultiply,prepareframe,preparemovie,prerotate,prescale,pretranslate,%
+      previous,primitives,printfrom,productversion,projection,projectionangle,%
+      propList,proxyserver,pttohotspotid,puppet,puppetpalette,puppetsound,%
+      puppetsprite,puppettempo,puppettransition,purgepriority,%
+      qtregisteraccesskey,qtunregisteraccesskey,quad,quality,queue,quit,quote,%
+      radius,ramneeded,random,randomseed,randomvector,rateshift,rawnew,read,%
+      readvalue,recordfont,rect,ref,reflectionmap,reflectivity,region,%
+      registerforevent,registerscript,regpoint,regpointvertex,removebackdrop,%
+      removefromworld,removelast,removemodifier,removeoverlay,rename,renderer,%
+      rendererdevicelist,renderformat,renderstyle,resetworld,resizewindow,%
+      resolution,resolve,resolvea,resolveb,resource,restart,resume,%
+      reverttoworlddefaults,rewind,rgb,rgba4444,rgba5550,rgba5551,rgba5650,%
+      rgba8880,rgba8888,right,rightindent,rightmousedown,rightmouseup,%
+      rollover,romanlingo,rootlock,rootnode,rotate,rotation,rotationreset,%
+      rtf,runmode,runpropertydialog,safeplayer,samplecount,samplerate,%
+      samplesize,save,savedlocal,savemovie,scale,scalemode,score,scorecolor,%
+      scoreselection,script,scriptexecutionstyle,scriptinstancelist,scriptlist,%
+      scriptnum,scriptsenabled,scripttext,scripttype,scrollbyline,scrollbypage,%
+      scrolltop,sds,searchcurrentfolder,searchpath,searchpaths,seconds,%
+      selectedtext,selection,selend,selstart,sendallsprites,sendevent,%
+      sendnetmessage,sendsprite,serialnumber,setalpha,setaprop,setat,%
+      setcollisioncallback,setflashproperty,setnetbufferlimits,%
+      setnetmessagehandler,setpixel,setplaylist,setpref,setprop,setscriptlist,%
+      settrackenabled,setvariable,shader,shaderlist,shadowpercentage,%
+      shadowstrength,shapetype,shiftdown,shininess,shockwave3d,short,%
+      showglobals,showlocals,showprops,showresfile,showxlib,shutdown,%
+      silhouettes,sin,size,sizerange,skew,sleep,smoothness,sort,sound,%
+      soundbusy,soundchannel,sounddevice,sounddevicelist,soundenabled,%
+      soundkeepdevice,soundlevel,soundmixmedia,source,sourcerect,space,%
+      specular,specularcolor,specularlightmap,sphere,spotangle,spotdecay,%
+      sprite,spritenum,spritespacetoworldspace,sqrt,stage,stagebottom,%
+      stagecolor,stageleft,stageright,stagetoflash,stagetop,standard,%
+      startangle,startframe,startmovie,starttime,starttimer,state,static,%
+      status,stepframe,stilldown,stop,stopevent,stopmovie,stoptime,stream,%
+      streammode,streamname,streamsize,streamstatus,string,stringp,%
+      strokecolor,strokewidth,style,subdivision,sweep,swing,switchcolordepth,%
+      symbol,symbolp,systemdate,tab,tabcount,tabs,tan,target,%
+      tellstreamstatus,tension,text,texture,texturecoordinatelist,%
+      texturecoordinates,texturelayer,texturelist,texturemember,texturemode,%
+      texturemodelist,texturerenderformat,texturerepeat,texturerepeatlist,%
+      texturetransform,texturetransformlist,texturetype,thumbnail,ticks,tilt,%
+      time,timeout,timeouthandler,timeoutkeydown,timeoutlapsed,timeoutlength,%
+      timeoutlist,timeoutmouse,timeoutplay,timeoutscript,timer,timescale,%
+      title,titlevisible,toon,top,topcap,topradius,topspacing,trace,%
+      traceload,tracelogfile,trackcount,trackenabled,tracknextkeytime,%
+      tracknextsampletime,trackpreviouskeytime,trackprevioussampletime,%
+      trackstarttime,trackstoptime,tracktext,tracktype,trails,transform,%
+      transitiontype,translate,triggercallback,trimwhitespace,tunneldepth,%
+      tweened,tweenmode,type,[transparent],union,unload,unloadmember,%
+      unloadmovie,unregisterallevents,update,updateframe,updatelock,%
+      updatemovieenabled,updatestage,url,usealpha,usediffusewithtexture,%
+      usefastquads,usehypertextstyles,uselineoffset,userdata,username,value,%
+      vector,version,vertex,vertexlist,vertices,video,videoforwindowspresent,%
+      viewh,viewpoint,viewscale,viewv,visibility,visible,void,voidp,volume,%
+      volumeinfo,wait,waitfornetconnection,warpmode,width,widthvertices,wind,%
+      window,windowlist,windowpresent,windowtype,word,wordwrap,world,%
+      worldposition,worldspacetospritespace,worldtransform,wraptransform,%
+      wraptransformlist,write,writevalue,,xaxis,xtra,xtralist,xtras,,yaxis,%
+      year,yon,zaxis,zoombox,zoomwindow,repeat,Conditional,Boolean,TypeDef,%
+      Statement,Operator,String,Comment,Identifier,Special,x,y,z}
+   sensitive=false,
+   morecomment=[l]{--},
+   morestring=[b]",
+  }[keywords,comments,strings]%
+\lst@definelanguage{LLVM}{%
+  morekeywords={%
+    ret,br,switch,indirectbr,invoke,resume,unreachable,%
+    add,fadd,sub,fsub,mul,fmul,udiv,sdiv,fdiv,urem,srem,frem,%
+    shl,lshr,ashr,and,or,xor,%
+    extractelement,insertelement,shufflevector,%
+    extractvalue,insertvalue,%
+    alloca,load,store,fence,cmpxchg,atomicrmw,getelementptr,%
+    trunc,zext,sext,fptrunc,fpext,fptoui,fptosi,uitofp,sitofp,ptrtoint,%
+    inttoptr,bitcast,to,%
+    icmp,fcmp,phi,select,call,va_arg,landingpad,%
+    xchg,add,sub,and,nand,or,xor,max,min,umax,umin,%
+    eq,ne,ugt,uge,ult,ule,sgt,sge,slt,sle,%
+    false,oeq,ogt,oge,olt,ole,one,ord,ueq,ugt,uge,ult,ule,une,uno,true,%
+    private,linker_private,linker_private_weak,linker_private_weak_def_auto,%
+    internal,available_externally,linkonce,common,weak,appending,extern_weak,%
+    linkonce_odr,weak_odr,external,dllimport,dllexport,%
+    define,declare,%
+    zeroext,signext,inreg,byval,sret,noalias,nocapture,next,%
+    gc,%
+    address_safety,alignstack,alwaysinline,nonlazybind,inlinehint,naked,%
+    noimplicitfloat,noinline,noredzone,noreturn,nounwind,optsize,readnone,%
+    readonly,returns_twice,ssp,sspreq,uwtable,%
+    module,asm,%
+    target,datalayout,%
+    sideeffect,alignstack,%
+    nuw,nsw,exact,inbounds,unnamed_addr},%
+  morekeywords=[2]{%
+    i1,i2,i4,i8,i16,i32,i64,i128,i256,i512,i1024,% <-- Most common integers
+    half,float,double,x86_fp80,fp128,ppc_fp128,x86mmx,%
+    void,label,metadata},%
+  alsoletter=.,%
+  sensitive=false,%
+  morecomment=[l];,%
+  morestring=[b]"%
+}
+\lst@definelanguage{Logo}%
+  {morekeywords={and,atan,arctan,both,break,bf,bl,butfirst,butlast,%
+      cbreak, close,co,continue,cos,count,clearscreen,cs,debquit,%
+      describe,diff,difference,ed,edit,either,emptyp,equalp,er,erase,%
+      errpause,errquit,fifp,filefprint,fifty,fileftype,fip,fileprint,%
+      fird,fileread,fity,filetype,fiwd,fileword,f,first,or,fp,fprint,%
+      fput,fty,ftype,full,fullscreen,go,bye,goodbye,gprop,greaterp,%
+      help,if,iff,iffalse,ift,iftrue,nth,item,keyp,llast,lessp,list,%
+      local,lput,make,max,maximum,memberp,memtrace,min,minimum,namep,%
+      not,numberp,oflush,openr,openread,openw,openwrite,op,output,%
+      pause,plist,pots,pow,pprop,pps,pr,print,product,quotient,random,%
+      rc,readchar,rl,readlist,remprop,repcount,repeat,request,rnd,run,%
+      se,sentence,sentencep,setc,setcolor,setipause,setqpause,po,show,%
+      sin,split,splitscreen,sqrt,stop,sum,test,text,textscreen,thing,%
+      to,tone,top,toplevel,type,untrace,wait,word,wordp,yaccdebug,is,%
+      mod,remainder,trace,zerop,back,bk,bto,btouch,fd,forward,fto,%
+      ftouch,getpen,heading,hit,hitoot,ht,hideturtle,loff,lampoff,lon,%
+      lampon,lt,left,lot,lotoot,lto,ltouch,penc,pencolor,pd,pendown,pe,%
+      penerase,penmode,pu,penup,px,penreverse,rt,right,rto,rtouch,%
+      scrunch,seth,setheading,setscrun,setscrunch,setxy,shownp,st,%
+      showturtle,towardsxy,clean,wipeclean,xcor,ycor,tur,turtle,%
+      display,dpy},%
+   sensitive=f% ???
+  }[keywords]%
+%%
+%% MetaPost definition (c) 2004 Brooks Moses
+%%   This definition is based on the language specifications
+%%   contained in the _User's Manual for Metapost_, with the core
+%%   language enhancements that are described in the _Drawing
+%%   Graphs with MetaPost_ documentation.
+%%
+\lst@definelanguage{MetaPost}%
+  {% keywords[1] = MetaPost primitives (not found in following tables)
+   morekeywords={end,begingroup,endgroup,beginfig,endfig,def,vardef,%
+      primary,secondary,tertiary,primarydef,secondarydef,tertiarydef,%
+      expr,suffix,text,enddef,if,fi,else,elseif,for,forsuffixes,%
+      forever,endfor,upto,downto,stop,until,tension,controls,on,off,%
+      btex,etex,within,input},
+   % keywords[2] = Operators (Tables 6-9 in MetaPost User's manual)
+   morekeywords=[2]{abs,and,angle,arclength,arctime,ASCII,bbox,bluepart,%
+      boolean,bot,ceiling,center,char,color,cosd,cutafter,cutbefore,%
+      cycle,decimal,dir,direction,directionpoint,directiontime,div,%
+      dotprod,floor,fontsize,greenpart,hex,infont,intersectionpoint,%
+      intersectiontimes,inverse,known,length,lft,llcorner,lrcorner,%
+      makepath,makepen,mexp,mlog,mod,normaldeviate,not,numeric,oct,%
+      odd,or,pair,path,pen,penoffset,picture,point,postcontrol,%
+      precontrol,redpart,reverse,rotated,round,rt,scaled,shifted,%
+      sind,slanted,sqrt,str,string,subpath,substring,top,transform,%
+      transformed,ulcorner,uniformdeviate,unitvector,unknown,%
+      urcorner,whatever,xpart,xscaled,xxpart,xypart,ypart,yscaled,%
+      yxpart,yypart,zscaled,of,reflectedabout,rotatedaround,ulft,urt,%
+      llft,lrt,readfrom,write,stroked,filled,textual,clipped,bounded,%
+      pathpart,penpart,dashpart,textpart,fontpart},%
+   % keywords[3] = Commands (Table 10)
+   morekeywords=[3]{addto,clip,cutdraw,draw,drawarrow,drawdblarrow,%
+      fill,filldraw,interim,let,loggingall,newinternal,pickup,%
+      save,setbounds,shipout,show,showdependencies,showtoken,%
+      showvariable,special,tracingall,tracingnone,undraw,unfill,%
+      unfilldraw,to,also,contour,doublepath,withcolor,withpen,%
+      dashed,randomseed},%
+   % keywords[4] = Function-Like Macros (Table 11)
+   morekeywords=[4]{boxit,boxjoin,bpath,buildcycle,circleit,dashpattern,%
+      decr,dotlabel,dotlabels,drawboxed,drawboxes,drawoptions,%
+      drawunboxed,fixpos,fixsize,incr,interpath,label,labels,max,min,pic,%
+      thelabel,z,image},%
+   % keywords[5] = Internal and Predefined Variables (Tables 3, 4)
+   morekeywords=[5]{ahangle,ahlength,bboxmargin,charcode,circmargin,%
+      day,defaultdx,defaultdy,defaultpen,defaultscale,labeloffset,%
+      linecap,linejoin,miterlimit,month,pausing,prologues,showstopping,%
+      time,tracingcapsules,tracingchoices,tracingcommands,%
+      tracingequations,tracinglostchars,tracingmacros,tracingonline,%
+      tracingoutput,tracingrestores,tracingspecs,tracingstats,%
+      tracingtitles,truecorners,warningcheck,year},
+   morekeywords=[5]{background,currentpen,currentpicture,cuttings,%
+      defaultfont},%
+   % keywords[6] = Predefined Constants (Table 5)
+   morekeywords=[6]{beveled,black,blue,bp,butt,cc,cm,dd,ditto,down,%
+      epsilon,evenly,false,fullcircle,green,halfcircle,identity,%
+      in,infinity,left,mitered,mm,nullpicture,origin,pc,pencircle,%
+      pt,quartercircle,red,right,rounded,squared,true,unitsquare,%
+      up,white,withdots},
+   sensitive=false,%
+   alsoother={0123456789$},%
+   morecomment=[l]\%,%
+   morestring=[mf]{input\ },%
+   morestring=[b]"%
+  }[keywords,comments,strings,mf]%
+%%
+%% Mizar definition (c) 2003 Adam Grabowski
+%%
+%% Mizar is freely available at URL www.mizar.org for the Linux x86,
+%% Solaris x86, and Windows operating systems.
+%%
+\lst@definelanguage{Mizar}%
+  {otherkeywords={->,(\#,\#),.=),\&},%
+   morekeywords={vocabulary,constructors,$1,$1,$2,$3,$4,$5,$6,$7,$8,%
+      @proof,according,aggregate,and,antonym,as,associativity,assume,%
+      asymmetry,attr,be,begin,being,by,canceled,case,cases,cluster,%
+      clusters,coherence,commutativity,compatibility,connectedness,%
+      consider,consistency,constructors,contradiction,correctness,def,%
+      deffunc,define,definition,definitions,defpred,end,environ,equals,%
+      ex,exactly,existence,for,from,func,given,hence,hereby,holds,%
+      idempotence,if,iff,implies,involutiveness,irreflexivity,is,it,%
+      let,means,mode,non,not,notation,now,of,or,otherwise,over,per,%
+      pred,prefix,projectivity,proof,provided,qua,reconsider,redefine,%
+      reflexivity,requirements,reserve,scheme,schemes,section,selector,%
+      set,st,struct,such,suppose,symmetry,synonym,take,that,the,then,%
+      theorem,theorems,thesis,thus,to,transitivity,uniqueness,%
+      vocabulary,where},%
+   sensitive=t,%
+   morecomment=[l]::%
+  }[keywords,comments]%
+\lst@definelanguage{Modula-2}%
+  {morekeywords={AND,ARRAY,BEGIN,BY,CASE,CONST,DIV,DO,ELSE,ELSIF,END,%
+      EXIT,EXPORT,FOR,FROM,IF,IMPLEMENTATION,IMPORT,IN,MOD,MODULE,NOT,%
+      OF,OR,POINTER,PROCEDURE,QUALIFIED,RECORD,REPEAT,RETURN,SET,THEN,%
+      TYPE,UNTIL,VAR,WHILE,WITH,ABS,BITSET,BOOLEAN,CAP,CARDINAL,CHAR,%
+      CHR,DEC,EXCL,FALSE,FLOAT,HALT,HIGH,INC,INCL,INTEGER,LONGCARD,%
+      LONGINT,LONGREAL,MAX,MIN,NIL,ODD,ORD,PROC,REAL,SIZE,TRUE,TRUNC,%
+      VAL,DEFINITION,LOOP},% added keywords due to Peter Bartke 99/07/22
+   sensitive,%
+   morecomment=[n]{(*}{*)},%
+   morestring=[d]',%
+   morestring=[d]"%
+  }[keywords,comments,strings]%
+\lst@definelanguage{MuPAD}{%
+   morekeywords={end,next,break,if,then,elif,else,end_if,case,end_case,%
+      otherwise,for,from,to,step,downto,in,end_for,while,end_while,%
+      repeat,until,end_repeat,or,and,not,xor,div,mod,union,minus,%
+      intersect,subset,proc,begin,end_proc,domain,end_domain,category,%
+      end_category,axiom,end_axiom,quit,delete,frame},%
+   morekeywords=[2]{NIL,FAIL,TRUE,FALSE,UNKNOWN,I,RD_INF,RD_NINF,%
+      RD_NAN,name,local,option,save,inherits,of,do},%
+   otherkeywords={\%if,?,!,:=,<,>,=,<=,<>,>=,==>,<=>,::,..,...,->,%
+      @,@@,\$},%
+   sensitive=true,%
+   morecomment=[l]{//},%
+   morecomment=[n]{/*}{*/},%
+   morestring=[b]",%
+   morestring=[d]{`}%
+  }[keywords,comments,strings]
+\lst@definelanguage{NASTRAN}
+  {morekeywords={ENDDATA},%
+   morecomment=[l]$,%
+   MoreSelectCharTable=%
+        \lst@CArgX BEGIN\ BULK\relax\lst@CDef{}%
+        {\lst@ifmode\else \ifnum\lst@length=\z@
+             \lst@EnterMode{\lst@GPmode}{\lst@modetrue
+                  \let\lst@currstyle\lst@gkeywords@sty}%
+         \fi \fi}%
+        {\ifnum\lst@mode=\lst@GPmode
+             \lst@XPrintToken \lst@LeaveMode
+         \fi}%
+  }[keywords,comments]%
+\lst@definelanguage{Oberon-2}%
+  {morekeywords={ARRAY,BEGIN,BOOLEAN,BY,CASE,CHAR,CONST,DIV,DO,ELSE,%
+      ELSIF,END,EXIT,FALSE,FOR,IF,IMPORT,IN,INTEGER,IS,LONGINT,%
+      LONGREAL,LOOP,MOD,MODULE,NIL,OF,OR,POINTER,PROCEDURE,REAL,RECORD,%
+      REPEAT,RETURN,SET,SHORTINT,THEN,TO,TRUE,TYPE,UNTIL,VAR,WHILE,%
+      WITH,ABS,ASH,CAP,CHR,COPY,DEC,ENTIER,EXCL,HALT,INC,INCL,LEN,LONG,%
+      MAX,MIN,NEW,ODD,ORD,SHORT,SIZE},%
+   sensitive,%
+   morecomment=[n]{(*}{*)},%
+   morestring=[d]',%
+   morestring=[d]"%
+  }[keywords,comments,strings]%
+%%
+%% OCL definition (c) 2000 Achim D. Brucker
+%%
+%% You are allowed to use, modify and distribute this code either under
+%% the terms of the LPPL (version 1.0 or later) or the GPL (version 2.0
+%% or later).
+%%
+\lst@definelanguage[decorative]{OCL}[OMG]{OCL}
+  {otherkeywords={@pre},%
+   morendkeywords={name,attributes,associatoinEnds,operations,%
+      supertypes,allSupertypes,allInstances,oclIsKindOf,oclIsTypeOf,%
+      oclAsType,oclInState,oclIsNew,evaluationType,abs,floor,round,max,%
+      min,div,mod,size,concat,toUpper,toLower,substring,includes,%
+      excludes,count,includesAll,exludesAll,isEmpty,notEmpty,sum,%
+      exists,forAll,isUnique,sortedBy,iterate,union,intersection,%
+      including,excluding,symmetricDifference,select,reject,collect,%
+      asSequence,asBag,asSequence,asSet,append,prepend,subSequence,at,%
+      first,last,true,false,isQuery}%
+  }%
+\lst@definelanguage[OMG]{OCL}%
+    {morekeywords={context,pre,inv,post},%
+    ndkeywords={or,xor,and,not,implies,if,then,else,endif},%
+    morekeywords=[3]{Boolean,Integer,Real,String,Set,Sequence,Bag,%
+       OclType,OclAny,OclExpression,Enumeration,Collection,},%
+    sensitive=t,%
+    morecomment=[l]--,%
+    morestring=[d]'%
+   }[keywords,comments,strings]%
+\lst@definelanguage{Plasm}%
+  {sensitive=false,%
+   morekeywords={aa,abs,ac,acolor,acos,actor,al,alias,align,and,%
+      animation,animation,appearance,apply,ar,arc,as,asin,assoc,atan,%
+      axialcamera,axialcameras,basehermite,bbox,bbox,bernstein,%
+      bernsteinbasis,bezier,beziercurve,beziermanifold,bezierstripe,%
+      beziersurface,bigger,biggest,bilinearsurface,binormal,%
+      biquadraticsurface,black,blend,blue,bottom,box,brown,bspize,%
+      bspline,bsplinebasis,c,cabinet,camera,cart,case,cat,catch,ceil,%
+      centeredcameras,centralcavalier,char,charseq,choose,circle,%
+      circumference,class,cmap,color,comp,computecoords,cone,%
+      conicalsurface,cons,control,convexcoords,convexhull,coonspatch,%
+      copy,cos,cosh,crease,crosspolytope,cube,cubiccardinal,%
+      cubiccardinalbasis,cubichermite,cubicubspline,cubicubsplinebasis,%
+      cuboid,curl,curvature,curve2cspath,curve2mapvect,cyan,cylinder,%
+      cylindricalsurface,d,deboor,def,depol,depth_sort,depth_test,%
+      derbernstein,derbernsteinbase,derbezier,determinant,difference,%
+      differencepr,dim,dimetric,dirproject,displaygraph,displaynubspline,%
+      displaynurbspline,distl,distr,div,divergence,dodecahedron,dot,down,%
+      dp,drawedges,drawforks,drawtree,ds,dsphere,dump,dumprep,ellipse,%
+      embed,end,eq,ex,exp,explode,export,extract_bodies,extract_polygons,%
+      extract_wires,extrude,extrusion,fact,false,feature,ff,fillcolor,%
+      filter,finitecone,first,flash,flashani,floor,fontcolor,fontheight,%
+      fontspacing,fontwidth,fractalsimplex,frame,frame,frameflash,fromto,%
+      gausscurvature,ge,grad,gradient,gradmap,gray,green,gt,help,hermite,%
+      hermitebasis,hermitesurface,hexahedron,icosahedron,id,idnt,if,in,%
+      inarcs,innerprod,inset,insl,insr,intersection,intersectionpr,%
+      intervals,intmax,intmin,intsto,inv,isa,isanimpol,isbool,ischar,%
+      isclosedshape,iscloseto,isempty,iseven,isfun,isfunvect,isge,isgt,%
+      isint,isintneg,isinto,isintpos,isle,islt,ismat,ismatof,isnat,%
+      isnull,isnum,isnumneg,isnumpos,isodd,isometric,isorthoshape,ispair,%
+      ispoint,ispointseq,ispol,ispoldim,ispolytope,ispurepol,isreal,%
+      isrealneg,isrealpos,isrealvect,isseq,isseqof,isshape,issimplex,%
+      issqrmat,isstring,isvect,iszero,jacobian,join,joints,k,last,le,%
+      left,leftcavalier,len,less,lesseq,lex,lift,light,linecolor,%
+      linesize,list,ln,load,loadlib,loop,lt,lxmy,magenta,map,mapshapes,%
+      markersize,mat,matdotprod,material,mathom,max,mean,meanpoint,med,%
+      merge,mesh,min,minkowski,mirror,mixedprod,mk,mkframe,mkpol,%
+      mkvector,mkversork,mod,model,move,mul,multextrude,mxby,mxmy,mxty,%
+      myfont,n,nat2string,neq,ngon,norm2,normalmap,not,nu_grid,nubspline,%
+      nubsplineknots,nurbspline,nurbsplineknots,octahedron,offset,%
+      onepoint,open,optimize,or,orange,ord,ortho,orthoproject,orthox,%
+      orthoy,orthoz,outarcs,outerloop,outerwarp,pairdiff,parallel,%
+      pascaltriangle,pdiff,pdifference,permutahedron,permutations,%
+      perspective,perspective,pi,pivotop,plane,planemapping,pmap,%
+      points2shape,polar,polyline,polymarker,polypoint,power,powerset,%
+      presort,principalnormal,print,prism,profileprodsurface,%
+      progressivesum,project,projection,purple,pyramid,q,quadarray,%
+      quadmesh,quote,r,raise,range,rationalbezier,rationalblend,%
+      rationalbspline,rationalize,red,rev,reverse,rgbacolor,right,%
+      rightcavalier,ring,rn,rotatedtext,rotationalsurface,rotn,rtail,%
+      ruledsurface,rxmy,s,save,scalarmatprod,scalarvectprod,schlegel2d,%
+      schlegel3d,sdifference,sdifferencepr,segment,sel,setand,setdiff,%
+      setfontcolor,setor,setxor,sex,shape_0,shape_1,shape2points,%
+      shape2pol,shapeclosed,shapecomb,shapediff,shapedist,%
+      shapeinbetweening,shapeinf,shapejoin,shapelen,shapenorm,%
+      shapenormal,shapeprod,shaperot,shapesum,shapesup,shapezero,shift,%
+      showprop,sign,signal,simplex,simplexpile,sin,sinh,size,skeleton,%
+      skew,smaller,smallest,solidifier,solidify,sort,sphere,spline,%
+      splinesampling,splitcells,splitpols,sqr,sqrt,star,string,%
+      stringtokens,struct,sub,svg,sweep,t,tail,tan,tangent,tanh,%
+      tensorprodsurface,tetrahedron,text,texture,textwithattributes,%
+      thinsolid,threepoints,time,tmax,tmin,top,torus,torusmap,trace,%
+      trans,tree,trianglefan,trianglestripe,trimetric,true,truncone,tt,%
+      tube,twopoints,uk,ukpol,ukpolf,union,unionpr,unitvect,unprune,up,%
+      vect2dtoangle,vect2mat,vectdiff,vectnorm,vectprod,vectsum,view,%
+      viewmodel,viewmodel,vrml,warp,warp,where,white,with,xcavalier,xor,%
+      xquadarray,xx,ycavalier,yellow},%
+   moredirectives={loadlib},%
+   otherkeywords={-,+,*,**,/,~,|,..,^,\&,\&\&,\#,\#\#},%
+   morecomment=[s]{\%}{\%},%
+   morestring=[b]',%
+   literate={~}{{$\sim$}}{1} {^}{$\wedge$}{1},%
+  }[keywords,directives,comments,strings]%
+\lst@definelanguage{PL/I}%
+  {morekeywords={ABS,ATAN,AUTOMATIC,AUTO,ATAND,BEGIN,BINARY,BIN,BIT,%
+      BUILTIN,BY,CALL,CHARACTER,CHAR,CHECK,COLUMN,COL,COMPLEX,CPLX,%
+      COPY,COS,COSD,COSH,DATA,DATE,DECIMAL,DEC,DECLARE,DCL,DO,EDIT,%
+      ELSE,END,ENDFILE,ENDPAGE,ENTRY,EXP,EXTERNAL,EXT,FINISH,FIXED,%
+      FIXEDOVERFLOW,FOFL,FLOAT,FORMAT,GET,GO,GOTO,IF,IMAG,INDEX,%
+      INITIAL,INIT,INTERNAL,INT,LABEL,LENGTH,LIKE,LINE,LIST,LOG,LOG2,%
+      LOG10,MAIN,MAX,MIN,MOD,NOCHECK,NOFIXEDOVERFLOW,NOFOFL,NOOVERFLOW,%
+      NOOFL,NOSIZE,NOUNDERFLOW,NOUFL,NOZERODIVIDE,NOZDIV,ON,OPTIONS,%
+      OVERFLOW,OFL,PAGE,PICTURE,PROCEDURE,PROC,PUT,READ,REPEAT,RETURN,%
+      RETURNS,ROUND,SIN,SIND,SINH,SIZE,SKIP,SQRT,STATIC,STOP,STRING,%
+      SUBSTR,SUM,SYSIN,SYSPRINT,TAN,TAND,TANH,THEN,TO,UNDERFLOW,UFL,%
+      VARYING,WHILE,WRITE,ZERODIVIDE,ZDIV},%
+   sensitive=f,%
+   morecomment=[s]{/*}{*/},%
+   morestring=[d]'%
+  }[keywords,comments,strings]%
+%%
+%% PostScript language definition (c) 2005 Christophe Jorssen.
+%%
+\lst@definelanguage{PostScript}{%
+  morekeywords={abs,add,aload,anchorsearch,and,arc,arcn,arct,arcto,array,ashow,
+    astore,atan,awidthshow,begin,bind,bitshift,bytesavailable,cachestatus,
+    ceiling,charpath,clear,cleartomark,cleardictstack,clip,clippath,closefile,
+    closepath,colorimage,concat,concatmatrix,condition,copy,copypage,cos,count,
+    countdictstack,countexecstack,counttomark,cshow,currentblackgeneration,
+    currentcacheparams,currentcmykcolor,currentcolor,currentcolorrendering,
+    currentcolorscreen,currentcolorspace,currentcolortransfer,currentcontext,
+    currentdash,currentdevparams,currentdict,currentfile,currentflat,currentfont,
+    currentglobal,currentgray,currentgstate,currenthalftone,currenthalftonephase,
+    currenthsbcolor,currentlinecap,currentlinejoin,currentlinewidth,currentmatrix,
+    currentmiterlimit,currentobjectformat,currentpacking,currentpagedevice,
+    currentpoint,currentrgbcolor,currentscreen,currentshared,currentstrokeadjust,
+    currentsystemparams,currenttransfer,currentundercolorremoval,currentuserparams,
+    curveto,cvi,cvlit,cvn,cvr,cvrs,cvs,cvx,def,defaultmatrix,definefont,
+    defineresource,defineusername,defineuserobject,deletefile,detach,deviceinfo,
+    dict,dictstack,div,dtransform,dup,
+    echo,eexec,end,eoclip,eofill,eoviewclip,eq,erasepage,errordict,exch,exec,
+    execform,execstack,execuserobject,executeonly,executive,exit,
+    exp,false,file,filenameforall,fileposition,fill,filter,findencoding,findfont,
+    findresource,flattenpath,floor,flush,flushfile,FontDirectory,for,forall,fork,ge,
+    get,getinterval,globaldict,GlobalFontDirectory,glyphshow,grestore,grestoreall,
+    gsave,gstate,gt,identmatrix,idiv,idtransform,if,ifelse,image,
+    imagemask,index,ineofill,infill,initclip,initgraphics,initmatrix,initviewclip,
+    instroke,internaldict,inueofill,inufill,inustroke,
+    invertmatrix,ISOLatin1Encoding,itransform,join,kshow,
+    known,languagelevel,le,length,lineto,ln,load,lock,log,loop,lt,
+    makefont,makepattern,mark,matrix,maxlength,mod,monitor,moveto,mul,ne,neg,
+    newpath,noaccess,not,notify,null,nulldevice,or,packedarray,
+    pathbbox,pathforall,pop,print,printobject,product,prompt,pstack,put,putinterval,
+    quit,rand,rcurveto,read,readhexstring,readline,readonly,readstring,
+    realtime,rectclip,rectfill,rectstroke,rectviewclip,renamefile,repeat,resetfile,
+    resourceforall,resourcestatus,restore,reversepath,revision,rlineto,rmoveto,roll,
+    rootfont,rotate,round,rrand,run,save,scale,scalefont,scheck,search,selectfont,
+    serialnumber,setbbox,setblackgeneration,setcachedevice,setcachedevice2,
+    setcachelimit,setcacheparams,setcharwidth,setcmykcolor,setcolor,
+    setcolorrendering,setcolorscreen,setcolorspace,setcolortransfer,setdash,
+    setdevparams,setfileposition,setflat,setfont,setglobal,setgray,setgstate,
+    sethalftone,sethalftonephase,sethsbcolor,setlinecap,setlinejoin,setlinewidth,
+    setmatrix,setmiterlimit,setobjectformat,setoverprint,setpacking,setpagedevice,
+    setpattern,setrgbcolor,setscreen,setshared,setstrokeadjust,setsystemparams,
+    settransfer,setucacheparams,setundercolorremoval,setuserparams,setvmthreshold,
+    shareddict,show,showpage,sin,sqrt,srand,stack,
+    StandardEncoding,start,startjob,status,statusdict,stop,stopped,store,string,
+    stringwidth,stroke,strokepath,sub,systemdict,transform,
+    translate,true,truncate,type,token,uappend,ucache,ucachestatus,
+    ueofill,ufill,undef,
+    upath,userdict,UserObjects,
+    usertime,ustroke,ustrokepath,version,viewclip,viewclippath,vmreclaim,
+    vmstatus,wait,wcheck,where,widthshow,write,writehexstring,writeobject,
+    writestring,wtranslation,xcheck,xor,xshow,xyshow,yield,yshow},
+  sensitive,
+  morecomment=[l]\%}[keywords,comments]
+%%
+%% Promela definition (c) 2004 William Thimbleby
+%%
+\lst@definelanguage{Promela}
+  {morekeywords={active,assert,atomic,bit,bool,break,byte,chan,d_step,%
+      Dproctype,do,else,empty,enabled,fi,full,goto,hidden,if,init,int,%
+      len,mtype,nempty,never,nfull,od,of,pcvalue,printf,priority,%
+      proctype,provided,run,short,skip,timeout,typedef,unless,unsigned,%
+      xr,xs,true,false,inline,eval},%
+   moredirectives={define,ifdef,ifndef,if,if,else,endif,undef,include},%
+   moredelim=*[directive]\#,%
+   morecomment=[s]{/*}{*/},%
+   morestring=[b]"%
+  }[keywords,comments,strings,directives]%
+%%
+%% PSTricks definition (c) 2006 Herbert Voss
+%%
+\lst@definelanguage{PSTricks}%
+  {morekeywords={%
+    begin,end,definecolor,multido,%
+    KillGlue,DontKillGlue,pslbrace,bsrbrace,psscalebox,psset,pstVerb,pstverb,%
+    pst@def,,psframebox,psclip,endclip,endpspicture,psframe,
+%%    pspicture,%
+    multirput,multips,Rput,rput,uput,cput,lput,%
+    newrgbcolor,newgray,newcmykcolor,
+%%
+%% pstricks-add
+    psStep,psgraph,psbrace,psPrintValue,
+%%
+%% pst-plot
+    psvlabel,pshlabel,psplot,psline,pscustom,pscurve,psccurve,%
+    readdata,savedata,fileplot,dataplot,listplot,%
+    psecurce,psgraph,parametricplot,%
+    psellipse,psaxes,ncline,nccurve,psbezier,parabola,%
+    qdisk,qline,clipbox,endpsclip,%
+    psgrid,pscircle,pscirclebox,psdiabox,pstribox,%
+    newpsfontdot,psdot,psdots,%
+    pspolygon,psdiamond,psoval,pstriangle,%
+    psarc,psarcn,psellipticarc,psellipticarcn,pswedge,psellipticwedge,
+    pcline,pcdiag,pcdiagg,pccurve,pccurve,pcecurve,%
+    scalebox,scaleboxto,psmathboxtrue,everypsbox,psverbboxtrue,overlaybox,%
+    psoverlay,putoverlaybox,%
+    newpsstyle,newpsobject,%
+    moveto,newpath,closepath,stroke,fill,gsave,grestore,msave,mrestore,translate,scale,%
+    swapaxes,rotate,openshadow,closedshadow,movepath,lineto,rlineto,curveto,rcurveto,%
+    code,dim,coor,rcoor,file,arrows,setcolor,%
+    rotateleft,rotateright,rotatedown,%
+%%
+%% pst-node
+    nput,naput,nbput,ncput,%
+    ncarc,ncbox,ncangle,ncangles,ncloop,ncdiag,ncdiagg,ncarcbox,ncbar,%
+    cnodeput,nccircle,%
+    pnode,rnode,Rnode,Cnode,cnode,fnode,%
+    circlenode,ovalnode,trinode,dianode,%
+    psmatrix,endpsmatrix,psspan,%
+%%
+%% pst-tree
+    pstree,Tcircle,TCircle,Ttri,Tn,TC,Tc,Tfan,TR,Tr,Tdia,Toval,Tdot,Tp,Tf,%
+    skiplevel,skiplevels,endskiplevels,tspace,tlput,%
+%%
+%% pst-text
+    pscharpath,pstextpath,
+%%
+%% pst-barcode
+    psbarcode,
+%%
+%% pst-coil
+    psboxfill,pscoil,psCoil,pszigzag,nccoil,
+    psshadow,pstilt,psTilt,ThreeDput,
+%%
+%% pst-gr3d
+    PstGridThreeDNodeProcessor,%
+%%
+%% pst-vue3d
+    PstGridThreeD,
+    AxesThreeD,LineThreeD,DieThreeD,FrameThreeD,SphereCircleThreeD,SphereMeridienThreeD,
+    QuadrillageThreeD,TetraedreThreeD,PyramideThreeD,ConeThreeD,CylindreThreeD,
+    DodecahedronThreeD,ConeThreeD,SphereThreeD,SphereInverseThreeD,DemiSphereThreeD,
+    SphereCreuseThreeD,SphereCircledThreeD,PortionSphereThreeD,pNodeThreeD,CubeThreeD,%
+%%
+%% pst-3dplot
+    pstThreeDCoor,pstThreeDDot,pstThreeDTriangle,pstThreeDCircle,pstPlanePut,%
+    pstThreeDBox,pstThreeDEllipse,pstThreeDLine,pstThreeDPut,%
+    pstThreeDNode,pstThreeDSquare,psplotThreeD,parametricplotThreeD,fileplotThreeD,%
+    dataplotThreeD,pstScalePoints,%
+%%
+%% pst-circ
+    resistor,battery,Ucc,Icc,capacitor,coil,diode,Zener,LED,lamp,switch,wire,tension,
+    circledipole,multidipole,OA,transistor,Tswitch,potentiometer,transformer,
+    optoCoupler,logic,
+%%
+%% pst-eucl
+    pstTriangle,pstMediatorAB,pstInterLL,pstMiddleAB,pstProjection,pstCircleOA,pstLineAB,%
+%%
+%% pst-func
+    psBessel,psPolynomial,psFourier,psGaussI,psGauss,psSi,pssi,psCi,psci,%
+%%
+%% pst-infixplot
+    psPlot,
+%%
+%% pst-ob3d
+    PstDie,PstCube,
+%%
+%% pst-poly
+    PstPolygon,pspolygonbox,
+%%
+%% pst-bar
+    psbarchart,readpsbardata,psbarscale,newpsbarstyle,%
+%%
+%% pst-lens
+    PstLens,%
+%%
+%% pst-geo
+    WorldMap,WorldMapII,WorldMapThreeD,WorldMapThreeDII,pnodeMap,MapPut,%
+%%
+%% pst-autoseg
+    asr,firstnode,merge,massoc,labelmerge,%
+%%
+%% gastex
+    node,imark,fmark,rmark,drawqbpedge,drawedge,drawloop,%
+%%
+%% pst-labo
+    Distillation,Ballon,
+%%
+%% pst-optic
+    lens,Transform,%
+%%
+%% pst-light3d
+    PstLightThreeDText,%
+%%
+%% calendrier
+    Calendrier,%
+%%
+%% pst-osci
+    Oscillo%
+  },%
+   sensitive,%
+   alsoother={0123456789$_},%
+   morecomment=[l]\% %
+  }[keywords,comments]%
+%%
+%% Reduce definition (c) 2002 Geraint Paul Bevan
+%%
+\lst@definelanguage{Reduce}%
+  {morekeywords={%
+%% reserved identifiers
+abs,acos,acosh,acot,acoth,acsc,acsch,%
+adjprec,algebraic,algint,allbranch,allfac,and,%
+antisymmetric,append,arglength,array,asec,asech,%
+asin,asinh,atan,atan2,atanh,begin,bfspace,bye,%
+card_no,ceiling,clear,clearrules,coeff,coeffn,%
+cofactor,combineexpt,combinelogs,comment,comp,%
+complex,conj,cons,cont,cos,cosh,cot,coth,cramer,%
+cref,csc,csch,decompose,define,defn,deg,demo,den,%
+depend,det,df,difference,dilog,display,div,do,e,%
+echo,ed,editdef,ei,end,eps,eq,equal,erf,errcont,%
+evallhseqp,eval_mode,even,evenp,exp,expandlogs,%
+expr,expt,ezgcd,factor,factorial,factorize,fexpr,%
+first,fix,fixp,floor,for,forall,foreach,fort,%
+fort_width,freeof,fullroots,g,gcd,geq,go,goto,%
+greaterp,high_pow,hypot,i,if,ifactor,impart,in,%
+index,infinity,infix,input,int,integer,interpol,%
+intstr,k,korder,lambda,lcm,lcof,length,leq,lessp,%
+let,lhs,linear,linelength,lisp,list,listargp,%
+listargs,ln,load,load_package,log,log10,logb,%
+low_pow,lterm,macro,mainvar,mass,mat,match,%
+mateigen,matrix,max,mcd,member,memq,min,minus,mkid,%
+modular,msg,mshell,multiplicities,nat,neq,nero,%
+nextprime,nil,nodepend,noncom,nonzero,nosplit,%
+nospur,nullspace,num,numberp,odd,off,on,operator,%
+or,order,ordp,out,output,part,pause,period,pf,pi,%
+plus,precedence,precise,precision,pret,pri,primep,%
+print_precision,procedure,product,quit,quotient,%
+random,random_new_seed,rank,rat,ratarg,rational,%
+rationalize,ratpri,real,rederr,reduct,remainder,%
+remfac,remind,repart,repeat,rest,resultant,retry,%
+return,reverse,revpri,rhs,rlisp88,%
+root_multiplicity,round,roundall,roundbf,rounded,%
+saveas,savestructr,scalar,sec,sech,second,set,%
+setmod,setq,share,showrules,showtime,shut,sign,sin,%
+sinh,smacro,solve,solvesingular,spur,sqrt,structr,%
+sub,sum,symbolic,symmetric,t,tan,tanh,third,time,%
+times,tp,tra,trace,trfac,trigform,trint,until,%
+varname,vecdim,vector,weight,when,where,while,%
+write,ws,wtlevel,%
+%% identifiers with spaces
+%% for all,for each,go to,such that,%
+},%
+  sensitive=false,%
+  morecomment=[l]\%,%
+  morecomment=[s]{COMMENT}{;},%
+  morecomment=[s]{COMMENT}{$},%
+  morestring="%
+ }[keywords,comments,strings]%
+%%
+%% RSL definition (c) 2004 Brian Christensen
+%%
+\lst@definelanguage{RSL}%
+  {morekeywords={Bool,Char,devt_relation,Int,Nat,Real,Text,Unit,abs,any,%
+      as,axiom,card,case,channel,chaos,class,do,dom,elems,else,elsif,end,%
+      extend,false,for,hd,hide,if,in,inds,initialise,int,len,let,local,%
+      object,of,out,post,pre,read,real,rng,scheme,skip,stop,swap,%
+      test_case,theory,then,tl,true,type,until,use,value,variable,while,%
+      with,write},%
+literate=%
+{<}{$<$}{1}%
+{>}{$>$}{1}%
+{[}{$[$}{1}%%
+{]}{$]$}{1}%%
+{^}{{\mbox{$\widehat{\;}$}}}{1}%%
+{'}{{\raisebox{1ex}[1ex][0ex]{\protect\scriptsize$\prime$}}}{1}%%
+{||}{{\mbox{$\parallel$}}}{2}%%
+{|-}{$\vdash$}{1}%%
+{|=|}{{\mbox{$\lceil\!\rceil\!\!\!\!\!\!\;\lfloor\!\rfloor$}}}{1}%%
+{**}{$\uparrow$}{1}%
+{/\\}{$\wedge$}{1}%%
+{inter}{$\cap$}{1}%%
+{-\\}{$\lambda$}{1}%%
+{->}{$\rightarrow$}{1}%%
+{-m->}{{\mbox{$\rightarrow \hspace{-2.5\lst@width} _{m}\;$}}}{1}%
+{-~m->}{{\mbox{$\stackrel{\sim}{\mbox{$\rightarrow\hspace{-2.5\lst@width} _{m}\;$}}$}}}{1}%
+{-~->}{{\mbox{$\stackrel{\sim}{\rightarrow}$}}}{1}%%
+{-set}{\bf{-set}}{4}%%
+{-list}{{$^{\ast}$}}{1}%%
+{-inflist}{$^\omega$}{1}%
+{-infset}{{\mbox{{\bf -infset}}}}{7}%
+{\#}{$\circ$}{1}%
+{:-}{{\raisebox{.4ex}{\tiny $\bullet$}}}{1}%%
+{=}{$=$}{1}%%
+{==}{$==$}{2}%%
+{=>}{$\Rightarrow$}{1}%%
+{\ is\protect\^^M}{{$\;\equiv$}}{2}%
+{\ is\ }{{$\equiv$}}{3}%%
+{\ isin\protect\^^M}{$\;\in$}{2}%%
+{~}{$\sim$}{1}%%
+{~=}{$\neq$}{1}%%
+{~isin}{$\notin$}{1}%%
+{+>}{$\mapsto$}{1}%%
+{++}{}{1}%
+{|^|}{{\mbox{$\lceil\!\rceil$}}}{1}%%
+{\\/}{$\vee$}{1}%%
+{exists}{$\exists$}{1}%%
+{union}{$\cup$}{1}%%
+{>=}{$\geq$}{1}%%
+{><}{$\times$}{1}%%
+{>>}{$\supset$}{1}%
+{>>=}{$\supseteq$}{1}%%
+{<=}{$\leq$}{1}%%
+{<<}{$\subset$}{1}%
+{<.}{$\langle$}{1}%%
+{<<=}{$\subseteq$}{1}%%
+{<->}{$\leftrightarrow$}{1}%%
+{[=}{$\sqsubseteq$}{1}%%
+{\{=}{$\preceq$}{1}%%
+{\ all\protect\^^M}{$\forall$}{2}%%
+{\ all\ }{$\forall$}{3}%%
+{!!}{$\dagger$}{1}%%
+{always}{$\Box$}{1}%%
+{.>}{$\rangle$}{1}%%
+{`alpha}{$\alpha$}{1}%
+{`beta}{$\beta$}{1}%
+{`gamma}{$\gamma$}{1}%
+{`delta}{$\delta$}{1}%
+{`epsilon}{$\epsilon$}{1}%
+{`zeta}{$\zeta$}{1}%
+{`eta}{$\eta$}{1}%
+{`theta}{$\theta$}{1}%
+{`iota}{$\iota$}{1}%
+{`kappa}{$\kappa$}{1}%
+{`mu}{$\mu$}{1}%
+{`nu}{$\nu$}{1}%
+{`xi}{$\xi$}{1}%
+{`pi}{$\pi$}{1}%
+{`rho}{$\rho$}{1}%
+{`sigma}{$\sigma$}{1}%
+{`tau}{$\tau$}{1}%
+{`upsilon}{$\upsilon$}{1}%
+{`phi}{$\phi$}{1}%
+{`chi}{$\chi$}{1}%
+{`psi}{$\psi$}{1}%
+{`omega}{$\omega$}{1}%
+{`Gamma}{$\Gamma$}{1}%
+{`Delta}{$\Delta$}{1}%
+{`Theta}{$\Theta$}{1}%
+{`Lambda}{$\Lambda$}{1}%
+{`Xi}{$\Xi$}{1}%
+{`Pi}{$\Pi$}{1}%
+{`Sigma}{$\Sigma$}{1}%
+{`Upsilon}{$\Upsilon$}{1}%
+{`Phi}{$\Phi$}{1}%
+{`Psi}{$\Psi$}{1}%
+{`Omega}{$\Omega$}{1},%
+   sensitive=true,%
+   morecomment=[l]{--},%
+   morecomment=[s]{/*}{*/}%
+  }[keywords,comments]%
+\lst@definelanguage[IBM]{Simula}[DEC]{Simula}{}%
+\lst@definelanguage[DEC]{Simula}[67]{Simula}%
+  {morekeywords={and,eq,eqv,ge,gt,hidden,imp,le,long,lt,ne,not,%
+      options,or,protected,short}%
+  }%
+\lst@definelanguage[CII]{Simula}[67]{Simula}%
+  {morekeywords={and,equiv,exit,impl,not,or,stop}}%
+\lst@definelanguage[67]{Simula}%
+  {morekeywords={activate,after,array,at,before,begin,boolean,%
+      character,class,comment,delay,detach,do,else,end,external,false,%
+      for,go,goto,if,in,inner,inspect,integer,is,label,name,new,none,%
+      notext,otherwise,prior,procedure,qua,reactivate,real,ref,resume,%
+      simset,simulation,step,switch,text,then,this,to,true,until,value,%
+      virtual,when,while},%
+   sensitive=f,%
+   keywordcommentsemicolon={end}{else,end,otherwise,when}{comment},%
+   morestring=[d]",%
+   morestring=[d]'%
+  }[keywords,keywordcomments,strings]%
+%%
+%% SPARQL definition (c) 2006 Christoph Kiefer
+%%
+\lst@definelanguage{SPARQL}%
+  {morekeywords={BASE,PREFIX,SELECT,DISTINCT,CONSTRUCT,DESCRIBE,ASK,%
+        FROM,NAMED,WHERE,ORDER,BY,ASC,DESC,LIMIT,OFFSET,OPTIONAL,%
+        GRAPH,UNION,FILTER,a,STR,LANG,LANGMATCHES,DATATYPE,BOUND,%
+        isIRI,isURI,isBLANK,isLITERAL,REGEX,true,false},%
+   sensitive=false,%
+   morecomment=[l]\#,%
+   morestring=[d]',%
+   morestring=[d]"%
+  }[keywords,comments,strings]%
+\lst@definelanguage{S}[]{R}{}
+\lst@definelanguage[PLUS]{S}[]{R}{}
+\lst@definelanguage{R}%
+  {keywords={abbreviate,abline,abs,acos,acosh,action,add1,add,%
+      aggregate,alias,Alias,alist,all,anova,any,aov,aperm,append,apply,%
+      approx,approxfun,apropos,Arg,args,array,arrows,as,asin,asinh,%
+      atan,atan2,atanh,attach,attr,attributes,autoload,autoloader,ave,%
+      axis,backsolve,barplot,basename,besselI,besselJ,besselK,besselY,%
+      beta,binomial,body,box,boxplot,break,browser,bug,builtins,bxp,by,%
+      c,C,call,Call,case,cat,category,cbind,ceiling,character,char,%
+      charmatch,check,chol,chol2inv,choose,chull,class,close,cm,codes,%
+      coef,coefficients,co,col,colnames,colors,colours,commandArgs,%
+      comment,complete,complex,conflicts,Conj,contents,contour,%
+      contrasts,contr,control,helmert,contrib,convolve,cooks,coords,%
+      distance,coplot,cor,cos,cosh,count,fields,cov,covratio,wt,CRAN,%
+      create,crossprod,cummax,cummin,cumprod,cumsum,curve,cut,cycle,D,%
+      data,dataentry,date,dbeta,dbinom,dcauchy,dchisq,de,debug,%
+      debugger,Defunct,default,delay,delete,deltat,demo,de,density,%
+      deparse,dependencies,Deprecated,deriv,description,detach,%
+      dev2bitmap,dev,cur,deviance,off,prev,,dexp,df,dfbetas,dffits,%
+      dgamma,dgeom,dget,dhyper,diag,diff,digamma,dim,dimnames,dir,%
+      dirname,dlnorm,dlogis,dnbinom,dnchisq,dnorm,do,dotplot,double,%
+      download,dpois,dput,drop,drop1,dsignrank,dt,dummy,dump,dunif,%
+      duplicated,dweibull,dwilcox,dyn,edit,eff,effects,eigen,else,%
+      emacs,end,environment,env,erase,eval,equal,evalq,example,exists,%
+      exit,exp,expand,expression,External,extract,extractAIC,factor,%
+      fail,family,fft,file,filled,find,fitted,fivenum,fix,floor,for,%
+      For,formals,format,formatC,formula,Fortran,forwardsolve,frame,%
+      frequency,ftable,ftable2table,function,gamma,Gamma,gammaCody,%
+      gaussian,gc,gcinfo,gctorture,get,getenv,geterrmessage,getOption,%
+      getwd,gl,glm,globalenv,gnome,GNOME,graphics,gray,grep,grey,grid,%
+      gsub,hasTsp,hat,heat,help,hist,home,hsv,httpclient,I,identify,if,%
+      ifelse,Im,image,\%in\%,index,influence,measures,inherits,install,%
+      installed,integer,interaction,interactive,Internal,intersect,%
+      inverse,invisible,IQR,is,jitter,kappa,kronecker,labels,lapply,%
+      layout,lbeta,lchoose,lcm,legend,length,levels,lgamma,library,%
+      licence,license,lines,list,lm,load,local,locator,log,log10,log1p,%
+      log2,logical,loglin,lower,lowess,ls,lsfit,lsf,ls,machine,Machine,%
+      mad,mahalanobis,make,link,margin,match,Math,matlines,mat,matplot,%
+      matpoints,matrix,max,mean,median,memory,menu,merge,methods,min,%
+      missing,Mod,mode,model,response,mosaicplot,mtext,mvfft,na,nan,%
+      names,omit,nargs,nchar,ncol,NCOL,new,next,NextMethod,nextn,%
+      nlevels,nlm,noquote,NotYetImplemented,NotYetUsed,nrow,NROW,null,%
+      numeric,\%o\%,objects,offset,old,on,Ops,optim,optimise,optimize,%
+      options,or,order,ordered,outer,package,packages,page,pairlist,%
+      pairs,palette,panel,par,parent,parse,paste,path,pbeta,pbinom,%
+      pcauchy,pchisq,pentagamma,persp,pexp,pf,pgamma,pgeom,phyper,pico,%
+      pictex,piechart,Platform,plnorm,plogis,plot,pmatch,pmax,pmin,%
+      pnbinom,pnchisq,pnorm,points,poisson,poly,polygon,polyroot,pos,%
+      postscript,power,ppoints,ppois,predict,preplot,pretty,Primitive,%
+      print,prmatrix,proc,prod,profile,proj,prompt,prop,provide,%
+      psignrank,ps,pt,ptukey,punif,pweibull,pwilcox,q,qbeta,qbinom,%
+      qcauchy,qchisq,qexp,qf,qgamma,qgeom,qhyper,qlnorm,qlogis,qnbinom,%
+      qnchisq,qnorm,qpois,qqline,qqnorm,qqplot,qr,Q,qty,qy,qsignrank,%
+      qt,qtukey,quantile,quasi,quit,qunif,quote,qweibull,qwilcox,%
+      rainbow,range,rank,rbeta,rbind,rbinom,rcauchy,rchisq,Re,read,csv,%
+      csv2,fwf,readline,socket,real,Recall,rect,reformulate,regexpr,%
+      relevel,remove,rep,repeat,replace,replications,report,require,%
+      resid,residuals,restart,return,rev,rexp,rf,rgamma,rgb,rgeom,R,%
+      rhyper,rle,rlnorm,rlogis,rm,rnbinom,RNGkind,rnorm,round,row,%
+      rownames,rowsum,rpois,rsignrank,rstandard,rstudent,rt,rug,runif,%
+      rweibull,rwilcox,sample,sapply,save,scale,scan,scan,screen,sd,se,%
+      search,searchpaths,segments,seq,sequence,setdiff,setequal,set,%
+      setwd,show,sign,signif,sin,single,sinh,sink,solve,sort,source,%
+      spline,splinefun,split,sqrt,stars,start,stat,stem,step,stop,%
+      storage,strstrheight,stripplot,strsplit,structure,strwidth,sub,%
+      subset,substitute,substr,substring,sum,summary,sunflowerplot,svd,%
+      sweep,switch,symbol,symbols,symnum,sys,status,system,t,table,%
+      tabulate,tan,tanh,tapply,tempfile,terms,terrain,tetragamma,text,%
+      time,title,topo,trace,traceback,transform,tri,trigamma,trunc,try,%
+      ts,tsp,typeof,unclass,undebug,undoc,union,unique,uniroot,unix,%
+      unlink,unlist,unname,untrace,update,upper,url,UseMethod,var,%
+      variable,vector,Version,vi,warning,warnings,weighted,weights,%
+      which,while,window,write,\%x\%,x11,X11,xedit,xemacs,xinch,xor,%
+      xpdrows,xy,xyinch,yinch,zapsmall,zip},%
+   otherkeywords={!,!=,~,$,*,\&,\%/\%,\%*\%,\%\%,<-,<<-,_,/},%
+   alsoother={._$},%
+   sensitive,%
+   morecomment=[l]\#,%
+   morestring=[d]",%
+   morestring=[d]'% 2001 Robert Denham
+  }%
+\lst@definelanguage{SAS}%
+  {procnamekeys={proc},%
+   morekeywords={DATA,AND,OR,NOT,EQ,GT,LT,GE,LE,NE,INFILE,INPUT,DO,BY,%
+      TO,SIN,COS,OUTPUT,END,PLOT,RUN,LIBNAME,VAR,TITLE,FIRSTOBS,OBS,%
+      DELIMITER,DLM,EOF,ABS,DIM,HBOUND,LBOUND,MAX,MIN,MOD,SIGN,SQRT,%
+      CEIL,FLOOR,FUZZ,INT,ROUND,TRUNC,DIGAMMA,ERF,ERFC,EXP,GAMMA,%
+      LGAMMA,LOG,LOG2,LOG10,ARCOS,ARSIN,ATAN,COSH,SINH,TANH,TAN,%
+      POISSON,PROBBETA,PROBBNML,PROBCHI,PROBF,PROBGAM,PROBHYPR,%
+      PROBNEGB,PROBNORM,PROBT,BETAINV,CINV,FINV,GAMINV,PROBIT,TINV,CSS,%
+      CV,KURTOSIS,MEAN,NMISS,RANGE,SKEWNESS,STD,STDERR,SUM,USS,NORMAL,%
+      RANBIN,RANCAU,RANEXP,RANGAM,RANNOR,RANPOI,RANTBL,RANTRI,RANUNI,%
+      UNIFORM,IF,THEN,ELSE,WHILE,UNTIL,DROP,KEEP,LABEL,DEFAULT,ARRAY,%
+      MERGE,CARDS,CARDS4,PUT,SET,UPDATE,ABORT,DELETE,DISPLAY,LIST,%
+      LOSTCARD,MISSING,STOP,WHERE,ARRAY,DROP,KEEP,WINDOW,LENGTH,RENAME,%
+      RETAIN,MEANS,UNIVARIATE,SUMMARY,TABULATE,CORR,FREQ,FOOTNOTE,NOTE,%
+      SHOW},%
+   otherkeywords={!,!=,~,$,*,\&,_,/,<,>=,=<,>},%
+   morestring=[d]'%
+   }[keywords,comments,strings,procnames]%
+\lst@definelanguage[AlLaTeX]{TeX}[LaTeX]{TeX}%
+  {moretexcs={AtBeginDocument,AtBeginDvi,AtEndDocument,AtEndOfClass,%
+      AtEndOfPackage,ClassError,ClassInfo,ClassWarning,%
+      ClassWarningNoLine,CurrentOption,DeclareErrorFont,%
+      DeclareFixedFont,DeclareFontEncoding,DeclareFontEncodingDefaults,%
+      DeclareFontFamily,DeclareFontShape,DeclareFontSubstitution,%
+      DeclareMathAccent,DeclareMathAlphabet,DeclareMathAlphabet,%
+      DeclareMathDelimiter,DeclareMathRadical,DeclareMathSizes,%
+      DeclareMathSymbol,DeclareMathVersion,DeclareOldFontCommand,%
+      DeclareOption,DeclarePreloadSizes,DeclareRobustCommand,%
+      DeclareSizeFunction,DeclareSymbolFont,DeclareSymbolFontAlphabet,%
+      DeclareTextAccent,DeclareTextAccentDefault,DeclareTextCommand,%
+      DeclareTextCommandDefault,DeclareTextComposite,%
+      DeclareTextCompositeCommand,DeclareTextFontCommand,%
+      DeclareTextSymbol,DeclareTextSymbolDefault,ExecuteOptions,%
+      GenericError,GenericInfo,GenericWarning,IfFileExists,%
+      InputIfFileExists,LoadClass,LoadClassWithOptions,MessageBreak,%
+      OptionNotUsed,PackageError,PackageInfo,PackageWarning,%
+      PackageWarningNoLine,PassOptionsToClass,PassOptionsToPackage,%
+      ProcessOptionsProvidesClass,ProvidesFile,ProvidesFile,%
+      ProvidesPackage,ProvideTextCommand,RequirePackage,%
+      RequirePackageWithOptions,SetMathAlphabet,SetSymbolFont,%
+      TextSymbolUnavailable,UseTextAccent,UseTextSymbol},%
+   morekeywords={array,center,displaymath,document,enumerate,eqnarray,%
+      equation,flushleft,flushright,itemize,list,lrbox,math,minipage,%
+      picture,sloppypar,tabbing,tabular,trivlist,verbatim}%
+  }%
+\lst@definelanguage[LaTeX]{TeX}[common]{TeX}%
+  {moretexcs={a,AA,aa,addcontentsline,addpenalty,addtocontents,%
+      addtocounter,addtolength,addtoversion,addvspace,alph,Alph,and,%
+      arabic,array,arraycolsep,arrayrulewidth,arraystretch,author,%
+      baselinestretch,begin,bezier,bfseries,bibcite,bibdata,bibitem,%
+      bibliography,bibliographystyle,bibstyle,bigskip,boldmath,%
+      botfigrule,bottomfraction,Box,caption,center,CheckCommand,circle,%
+      citation,cite,cleardoublepage,clearpage,cline,columnsep,%
+      columnseprule,columnwidth,contentsline,dashbox,date,dblfigrule,%
+      dblfloatpagefraction,dblfloatsep,dbltextfloatsep,dbltopfraction,%
+      defaultscriptratio,defaultscriptscriptratio,depth,Diamond,%
+      displaymath,document,documentclass,documentstyle,doublerulesep,%
+      em,emph,endarray,endcenter,enddisplaymath,enddocument,%
+      endenumerate,endeqnarray,endequation,endflushleft,endflushright,%
+      enditemize,endlist,endlrbox,endmath,endminipage,endpicture,%
+      endsloppypar,endtabbing,endtabular,endtrivlist,endverbatim,%
+      enlargethispage,ensuremath,enumerate,eqnarray,equation,%
+      evensidemargin,extracolsep,fbox,fboxrule,fboxsep,filecontents,%
+      fill,floatpagefraction,floatsep,flushbottom,flushleft,flushright,%
+      fnsymbol,fontencoding,fontfamily,fontseries,fontshape,fontsize,%
+      fontsubfuzz,footnotemark,footnotesep,footnotetext,footskip,frac,%
+      frame,framebox,fussy,glossary,headheight,headsep,height,hline,%
+      hspace,I,include,includeonly,index,inputlineno,intextsep,%
+      itemindent,itemize,itemsep,iterate,itshape,Join,kill,label,%
+      labelsep,labelwidth,LaTeX,LaTeXe,leadsto,lefteqn,leftmargin,%
+      leftmargini,leftmarginii,leftmarginiii,leftmarginiv,leftmarginv,%
+      leftmarginvi,leftmark,lhd,lim,linebreak,linespread,linethickness,%
+      linewidth,list,listfiles,listfiles,listparindent,lrbox,%
+      makeatletter,makeatother,makebox,makeglossary,makeindex,%
+      makelabel,MakeLowercase,MakeUppercase,marginpar,marginparpush,%
+      marginparsep,marginparwidth,markboth,markright,math,mathbf,%
+      mathellipsis,mathgroup,mathit,mathrm,mathsf,mathsterling,mathtt,%
+      mathunderscore,mathversion,mbox,mdseries,mho,minipage,%
+      multicolumn,multiput,NeedsTeXFormat,newcommand,newcounter,%
+      newenvironment,newfont,newhelp,newlabel,newlength,newline,%
+      newmathalphabet,newpage,newsavebox,newtheorem,nobreakspace,%
+      nobreakspace,nocite,nocorr,nocorrlist,nofiles,nolinebreak,%
+      nonumber,nopagebreak,normalcolor,normalfont,normalmarginpar,%
+      numberline,obeycr,oddsidemargin,oldstylenums,onecolumn,oval,%
+      pagebreak,pagenumbering,pageref,pagestyle,paperheight,paperwidth,%
+      paragraphmark,parbox,parsep,partopsep,picture,poptabs,pounds,%
+      protect,pushtabs,put,qbezier,qbeziermax,r,raggedleft,raisebox,%
+      ref,refstepcounter,renewcommand,renewenvironment,restorecr,%
+      reversemarginpar,rhd,rightmargin,rightmark,rmfamily,roman,Roman,%
+      rootbox,rule,samepage,sbox,scshape,secdef,section,sectionmark,%
+      selectfont,setcounter,settodepth,settoheight,settowidth,sffamily,%
+      shortstack,showoutput,showoverfull,sloppy,sloppypar,slshape,%
+      smallskip,sqsubset,sqsupset,SS,stackrel,stepcounter,stop,stretch,%
+      subparagraphmark,subsectionmark,subsubsectionmark,sum,%
+      suppressfloats,symbol,tabbing,tabbingsep,tabcolsep,tabular,%
+      tabularnewline,textasciicircum,textasciitilde,textbackslash,%
+      textbar,textbf,textbraceleft,textbraceright,textbullet,%
+      textcircled,textcompwordmark,textdagger,textdaggerdbl,textdollar,%
+      textellipsis,textemdash,textendash,textexclamdown,textfloatsep,%
+      textfraction,textgreater,textheight,textit,textless,textmd,%
+      textnormal,textparagraph,textperiodcentered,textquestiondown,%
+      textquotedblleft,textquotedblright,textquoteleft,textquoteright,%
+      textregistered,textrm,textsc,textsection,textsf,textsl,%
+      textsterling,textsuperscript,texttrademark,texttt,textunderscore,%
+      textup,textvisiblespace,textwidth,thanks,thefootnote,thempfn,%
+      thempfn,thempfootnote,thepage,thepage,thicklines,thinlines,%
+      thispagestyle,title,today,topfigrule,topfraction,topmargin,%
+      topsep,totalheight,tracingfonts,trivlist,ttfamily,twocolumn,%
+      typein,typeout,unboldmath,unitlength,unlhd,unrhd,upshape,usebox,%
+      usecounter,usefont,usepackage,value,vector,verb,verbatim,vline,%
+      vspace,width,%
+      normalsize,small,footnotesize,scriptsize,tiny,large,Large,LARGE,%
+      huge,Huge}%
+  }%
+\lst@definelanguage[plain]{TeX}[common]{TeX}%
+  {moretexcs={advancepageno,beginsection,bf,bffam,bye,cal,cleartabs,%
+      columns,dosupereject,endinsert,eqalign,eqalignno,fiverm,fivebf,%
+      fivei,fivesy,folio,footline,hang,headline,it,itemitem,itfam,%
+      leqalignno,magnification,makefootline,makeheadline,midinsert,mit,%
+      mscount,nopagenumbers,normalbottom,of,oldstyle,pagebody,%
+      pagecontents,pageinsert,pageno,plainoutput,preloaded,proclaim,rm,%
+      settabs,sevenbf,seveni,sevensy,sevenrm,sl,slfam,supereject,%
+      tabalign,tabs,tabsdone,tabsyet,tenbf,tenex,teni,tenit,tenrm,%
+      tensl,tensy,tentt,textindent,topglue,topins,topinsert,tt,ttfam,%
+      ttraggedright,vfootnote}%
+  }%
+\lst@definelanguage[common]{TeX}[primitive]{TeX}
+  {moretexcs={active,acute,ae,AE,aleph,allocationnumber,allowbreak,%
+      alpha,amalg,angle,approx,arccos,arcsin,arctan,arg,arrowvert,%
+      Arrowvert,ast,asymp,b,backslash,bar,beta,bgroup,big,Big,bigbreak,%
+      bigcap,bigcirc,bigcup,bigg,Bigg,biggl,Biggl,biggm,Biggm,biggr,%
+      Biggr,bigl,Bigl,bigm,Bigm,bigodot,bigoplus,bigotimes,bigr,Bigr,%
+      bigskip,bigskipamount,bigsqcup,bigtriangledown,bigtriangleup,%
+      biguplus,bigvee,bigwedge,bmod,bordermatrix,bot,bowtie,brace,%
+      braceld,bracelu,bracerd,braceru,bracevert,brack,break,breve,%
+      buildrel,bullet,c,cap,cases,cdot,cdotp,cdots,centering,%
+      centerline,check,chi,choose,circ,clubsuit,colon,cong,coprod,%
+      copyright,cos,cosh,cot,coth,csc,cup,d,dag,dagger,dashv,ddag,%
+      ddagger,ddot,ddots,deg,delta,Delta,det,diamond,diamondsuit,dim,%
+      displaylines,div,do,dospecials,dot,doteq,dotfill,dots,downarrow,%
+      Downarrow,downbracefill,egroup,eject,ell,empty,emptyset,endgraf,%
+      endline,enskip,enspace,epsilon,equiv,eta,exists,exp,filbreak,%
+      flat,fmtname,fmtversion,footins,footnote,footnoterule,forall,%
+      frenchspacing,frown,gamma,Gamma,gcd,ge,geq,gets,gg,goodbreak,%
+      grave,H,hat,hbar,heartsuit,hglue,hideskip,hidewidth,hom,%
+      hookleftarrow,hookrightarrow,hphantom,hrulefill,i,ialign,iff,Im,%
+      imath,in,inf,infty,int,interdisplaylinepenalty,%
+      interfootnotelinepenalty,intop,iota,item,j,jmath,joinrel,jot,%
+      kappa,ker,l,L,lambda,Lambda,land,langle,lbrace,lbrack,lceil,%
+      ldotp,ldots,le,leavevmode,leftarrow,Leftarrow,leftarrowfill,%
+      leftharpoondown,leftharpoonup,leftline,leftrightarrow,%
+      Leftrightarrow,leq,lfloor,lg,lgroup,lhook,lim,liminf,limsup,line,%
+      ll,llap,lmoustache,ln,lnot,log,longleftarrow,Longleftarrow,%
+      longleftrightarrow,Longleftrightarrow,longmapsto,longrightarrow,%
+      Longrightarrow,loop,lor,lq,magstep,magstep,magstephalf,mapsto,%
+      mapstochar,mathhexbox,mathpalette,mathstrut,matrix,max,maxdimen,%
+      medbreak,medskip,medskipamount,mid,min,models,mp,mu,multispan,%
+      nabla,narrower,natural,ne,nearrow,neg,negthinspace,neq,newbox,%
+      newcount,newdimen,newfam,newif,newinsert,newlanguage,newmuskip,%
+      newread,newskip,newtoks,newwrite,next,ni,nobreak,nointerlineskip,%
+      nonfrenchspacing,normalbaselines,normalbaselineskip,%
+      normallineskip,normallineskiplimit,not,notin,nu,null,nwarrow,o,O,%
+      oalign,obeylines,obeyspaces,odot,oe,OE,offinterlineskip,oint,%
+      ointop,omega,Omega,ominus,ooalign,openup,oplus,oslash,otimes,%
+      overbrace,overleftarrow,overrightarrow,owns,P,parallel,partial,%
+      perp,phantom,phi,Phi,pi,Pi,pm,pmatrix,pmod,Pr,prec,preceq,prime,%
+      prod,propto,psi,Psi,qquad,quad,raggedbottom,raggedright,rangle,%
+      rbrace,rbrack,rceil,Re,relbar,Relbar,removelastskip,repeat,%
+      rfloor,rgroup,rho,rhook,rightarrow,Rightarrow,rightarrowfill,%
+      rightharpoondown,rightharpoonup,rightleftharpoons,rightline,rlap,%
+      rmoustache,root,rq,S,sb,searrow,sec,setminus,sharp,showhyphens,%
+      sigma,Sigma,sim,simeq,sin,sinh,skew,slash,smallbreak,smallint,%
+      smallskip,smallskipamount,smash,smile,sp,space,spadesuit,sqcap,%
+      sqcup,sqrt,sqsubseteq,sqsupseteq,ss,star,strut,strutbox,subset,%
+      subseteq,succ,succeq,sum,sup,supset,supseteq,surd,swarrow,t,tan,%
+      tanh,tau,TeX,theta,Theta,thinspace,tilde,times,to,top,tracingall,%
+      triangle,triangleleft,triangleright,u,underbar,underbrace,%
+      uparrow,Uparrow,upbracefill,updownarrow,Updownarrow,uplus,%
+      upsilon,Upsilon,v,varepsilon,varphi,varpi,varrho,varsigma,%
+      vartheta,vdash,vdots,vec,vee,vert,Vert,vglue,vphantom,wedge,%
+      widehat,widetilde,wlog,wp,wr,xi,Xi,zeta}%
+  }%
+\lst@definelanguage[primitive]{TeX}%
+  {moretexcs={above,abovedisplayshortskip,abovedisplayskip,aftergroup,%
+      abovewithdelims,accent,adjdemerits,advance,afterassignment,atop,%
+      atopwithdelims,badness,baselineskip,batchmode,begingroup,%
+      belowdisplayshortskip,belowdisplayskip,binoppenalty,botmark,box,%
+      boxmaxdepth,brokenpenalty,catcode,char,chardef,cleaders,closein,%
+      closeout,clubpenalty,copy,count,countdef,cr,crcr,csname,day,%
+      deadcycles,def,defaulthyphenchar,defaultskewchar,delcode,%
+      delimiter,delimiterfactor,delimitershortfall,dimen,dimendef,%
+      discretionary,displayindent,displaylimits,displaystyle,%
+      displaywidowpenalty,displaywidth,divide,doublehyphendemerits,dp,%
+      edef,else,emergencystretch,end,endcsname,endgroup,endinput,%
+      endlinechar,eqno,errhelp,errmessage,errorcontextlines,%
+      errorstopmode,escapechar,everycr,everydisplay,everyhbox,everyjob,%
+      everymath,everypar,everyvbox,exhyphenpenalty,expandafter,fam,fi,%
+      finalhypendemerits,firstmark,floatingpenalty,font,fontdimen,%
+      fontname,futurelet,gdef,global,globaldefs,halign,hangafter,%
+      hangindent,hbadness,hbox,hfil,hfill,hfilneg,hfuzz,hoffset,%
+      holdinginserts,hrule,hsize,hskip,hss,ht,hyphenation,hyphenchar,%
+      hyphenpenalty,if,ifcase,ifcat,ifdim,ifeof,iffalse,ifhbox,ifhmode,%
+      ifinner,ifmmode,ifnum,ifodd,iftrue,ifvbox,ifvmode,ifvoid,ifx,%
+      ignorespaces,immediate,indent,input,insert,insertpenalties,%
+      interlinepenalty,jobname,kern,language,lastbox,lastkern,%
+      lastpenalty,lastskip,lccode,leaders,left,lefthyphenmin,leftskip,%
+      leqno,let,limits,linepenalty,lineskip,lineskiplimit,long,%
+      looseness,lower,lowercase,mag,mark,mathaccent,mathbin,mathchar,%
+      mathchardef,mathchoice,mathclose,mathcode,mathinner,mathop,%
+      mathopen,mathord,mathpunct,mathrel,mathsurround,maxdeadcycles,%
+      maxdepth,meaning,medmuskip,message,mkern,month,moveleft,%
+      moveright,mskip,multiply,muskip,muskipdef,newlinechar,noalign,%
+      noboundary,noexpand,noindent,nolimits,nonscript,nonstopmode,%
+      nulldelimiterspace,nullfont,number,omit,openin,openout,or,outer,%
+      output,outputpenalty,over,overfullrule,overline,overwithdelims,%
+      pagedepth,pagefilllstretch,pagefillstretch,pagefilstretch,%
+      pagegoal,pageshrink,pagestretch,pagetotal,par,parfillskip,%
+      parindent,parshape,parskip,patterns,pausing,penalty,%
+      postdisplaypenalty,predisplaypenalty,predisplaysize,pretolerance,%
+      prevdepth,prevgraf,radical,raise,read,relax,relpenalty,right,%
+      righthyphenmin,rightskip,romannumeral,scriptfont,%
+      scriptscriptfont,scriptscriptstyle,scriptspace,scriptstyle,%
+      scrollmode,setbox,setlanguage,sfcode,shipout,show,showbox,%
+      showboxbreadth,showboxdepth,showlists,showthe,skewchar,skip,%
+      skipdef,spacefactor,spaceskip,span,special,splitbotmark,%
+      splitfirstmark,splitmaxdepth,splittopskip,string,tabskip,%
+      textfont,textstyle,the,thickmuskip,thinmuskip,time,toks,toksdef,%
+      tolerance,topmark,topskip,tracingcommands,tracinglostchars,%
+      tracingmacros,tracingonline,tracingoutput,tracingpages,%
+      tracingparagraphs,tracingrestores,tracingstats,uccode,uchyph,%
+      underline,unhbox,unhcopy,unkern,unpenalty,unskip,unvbox,unvcopy,%
+      uppercase,vadjust,valign,vbadness,vbox,vcenter,vfil,vfill,%
+      vfilneg,vfuzz,voffset,vrule,vsize,vskip,vsplit,vss,vtop,wd,%
+      widowpenalty,write,xdef,xleaders,xspaceskip,year},%
+   sensitive,%
+   alsoother={0123456789$_},%$ to make Emacs fontlocking happy
+   morecomment=[l]\%%
+  }[keywords,tex,comments]%
+%%
+%% Verilog definition (c) 2003 Cameron H. G. Wright <c.h.g.wright@ieee.org>
+%%   Based on the IEEE 1364-2001 Verilog HDL standard
+%%   Ref: S. Palnitkar, "Verilog HDL: A Guide to Digital Design and Synthesis,"
+%%        Prentice Hall, 2003. ISBN: 0-13-044911-3
+%%
+\lst@definelanguage{Verilog}%
+  {morekeywords={% reserved keywords
+      always,and,assign,automatic,begin,buf,bufif0,bufif1,case,casex,%
+      casez,cell,cmos,config,deassign,default,defparam,design,disable,%
+      edge,else,end,endcase,endconfig,endfunction,endgenerate,%
+      endmodule,endprimitive,endspecify,endtable,endtask,event,for,%
+      force,forever,fork,function,generate,genvar,highz0,highz1,if,%
+      ifnone,incdir,include,initial,inout,input,instance,integer,join,%
+      large,liblist,library,localparam,macromodule,medium,module,nand,%
+      negedge,nmos,nor,noshowcancelled,not,notif0,notif1,or,output,%
+      parameter,pmos,posedge,primitive,pull0,pull1,pulldown,pullup,%
+      pulsestyle_onevent,pulsestyle_ondetect,rcmos,real,realtime,reg,%
+      release,repeat,rnmos,rpmos,rtran,rtranif0,rtranif1,scalared,%
+      showcancelled,signed,small,specify,specparam,strong0,strong1,%
+      supply0,supply1,table,task,time,tran,tranif0,tranif1,tri,tri0,%
+      tri1,triand,trior,trireg,unsigned,use,vectored,wait,wand,weak0,%
+      weak1,while,wire,wor,xnor,xor},%
+   morekeywords=[2]{% system tasks and functions
+      $bitstoreal,$countdrivers,$display,$fclose,$fdisplay,$fmonitor,%
+      $fopen,$fstrobe,$fwrite,$finish,$getpattern,$history,$incsave,%
+      $input,$itor,$key,$list,$log,$monitor,$monitoroff,$monitoron,%
+      $nokey},%
+   morekeywords=[3]{% compiler directives
+      `accelerate,`autoexpand_vectornets,`celldefine,`default_nettype,%
+      `define,`else,`elsif,`endcelldefine,`endif,`endprotect,%
+      `endprotected,`expand_vectornets,`ifdef,`ifndef,`include,%
+      `no_accelerate,`noexpand_vectornets,`noremove_gatenames,%
+      `nounconnected_drive,`protect,`protected,`remove_gatenames,%
+      `remove_netnames,`resetall,`timescale,`unconnected_drive},%
+   alsoletter=\`,%
+   sensitive,%
+   morecomment=[s]{/*}{*/},%
+   morecomment=[l]//,% nonstandard
+   morestring=[b]"%
+  }[keywords,comments,strings]%
+\endinput
+%%
+%% End of file `lstlang3.sty'.
diff --git a/dataflow/manual/lstmisc.sty b/dataflow/manual/lstmisc.sty
new file mode 100644
index 0000000..6a7e173
--- /dev/null
+++ b/dataflow/manual/lstmisc.sty
@@ -0,0 +1,2084 @@
+%%
+%% This is file `lstmisc.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% listings.dtx  (with options: `misc,0.21')
+%% 
+%% Please read the software license in listings-1.3.dtx or listings-1.3.pdf.
+%%
+%% (w)(c) 1996--2004 Carsten Heinz and/or any other author listed
+%% elsewhere in this file.
+%% (c) 2006 Brooks Moses
+%% (c) 2013- Jobst Hoffmann
+%%
+%% Send comments and ideas on the package, error reports and additional
+%% programming languages to Jobst Hoffmann at <j.hoffmann@fh-aachen.de>.
+%%
+\def\filedate{2015/06/04}
+\def\fileversion{1.6}
+\ProvidesFile{lstmisc.sty}
+             [\filedate\space\fileversion\space(Carsten Heinz)]
+\lst@CheckVersion\fileversion
+    {\typeout{^^J%
+     ***^^J%
+     *** This file requires `listings.sty' version \fileversion.^^J%
+     *** You have a serious problem, so I'm exiting ...^^J%
+     ***^^J}%
+     \batchmode \@@end}
+\lst@BeginAspect{writefile}
+\newtoks\lst@WFtoken % global
+\lst@AddToHook{InitVarsBOL}{\global\lst@WFtoken{}}
+\newwrite\lst@WF
+\global\let\lst@WFifopen\iffalse % init
+\gdef\lst@WFWriteToFile{%
+  \begingroup
+   \let\lst@UM\@empty
+   \expandafter\edef\expandafter\lst@temp\expandafter{\the\lst@WFtoken}%
+   \immediate\write\lst@WF{\lst@temp}%
+  \endgroup
+  \global\lst@WFtoken{}}
+\gdef\lst@WFAppend#1{%
+    \global\lst@WFtoken=\expandafter{\the\lst@WFtoken#1}}
+\gdef\lst@BeginWriteFile{\lst@WFBegin\@gobble}
+\gdef\lst@BeginAlsoWriteFile{\lst@WFBegin\lst@OutputBox}
+\begingroup \catcode`\^^I=11
+\gdef\lst@WFBegin#1#2{%
+    \begingroup
+    \let\lst@OutputBox#1%
+    \def\lst@Append##1{%
+        \advance\lst@length\@ne
+        \expandafter\lst@token\expandafter{\the\lst@token##1}%
+        \ifx ##1\lst@outputspace \else
+            \lst@WFAppend##1%
+        \fi}%
+    \lst@lAddTo\lst@PreGotoTabStop{\lst@WFAppend{^^I}}%
+    \lst@lAddTo\lst@ProcessSpace{\lst@WFAppend{ }}%
+    \let\lst@DeInit\lst@WFDeInit
+    \let\lst@MProcessListing\lst@WFMProcessListing
+    \lst@WFifopen\else
+        \immediate\openout\lst@WF=#2\relax
+        \global\let\lst@WFifopen\iftrue
+        \@gobbletwo\fi\fi
+    \fi}
+\endgroup
+\gdef\lst@EndWriteFile{%
+    \immediate\closeout\lst@WF \endgroup
+    \global\let\lst@WFifopen\iffalse}
+\global\let\lst@WFMProcessListing\lst@MProcessListing
+\global\let\lst@WFDeInit\lst@DeInit
+\lst@AddToAtTop\lst@WFMProcessListing{\lst@WFWriteToFile}
+\lst@AddToAtTop\lst@WFDeInit{%
+    \ifnum\lst@length=\z@\else \lst@WFWriteToFile \fi}
+\lst@EndAspect
+\lst@BeginAspect{strings}
+\gdef\lst@stringtypes{d,b,m,bd,db,s}
+\gdef\lst@StringKey#1#2{%
+    \lst@Delim\lst@stringstyle #2\relax
+        {String}\lst@stringtypes #1%
+                     {\lst@BeginString\lst@EndString}%
+        \@@end\@empty{}}
+\lst@Key{string}\relax{\lst@StringKey\@empty{#1}}
+\lst@Key{morestring}\relax{\lst@StringKey\relax{#1}}
+\lst@Key{deletestring}\relax{\lst@StringKey\@nil{#1}}
+\lst@Key{stringstyle}{}{\def\lst@stringstyle{#1}}
+\lst@AddToHook{EmptyStyle}{\let\lst@stringstyle\@empty}
+\lst@Key{showstringspaces}t[t]{\lstKV@SetIf{#1}\lst@ifshowstringspaces}
+\gdef\lst@BeginString{%
+    \lst@DelimOpen
+        \lst@ifexstrings\else
+        {\lst@ifshowstringspaces
+             \lst@keepspacestrue
+             \let\lst@outputspace\lst@visiblespace
+         \fi}}
+\lst@AddToHookExe{ExcludeDelims}{\let\lst@ifexstrings\iffalse}
+\gdef\lst@EndString{\lst@DelimClose\lst@ifexstrings\else}
+\gdef\lst@StringDM@d#1#2\@empty#3#4#5{%
+    \lst@CArg #2\relax\lst@DefDelimBE{}{}{}#3{#1}{#5}#4}
+\gdef\lst@StringDM@b#1#2\@empty#3#4#5{%
+    \let\lst@ifbstring\iftrue
+    \lst@CArg #2\relax\lst@DefDelimBE
+       {\lst@ifletter \lst@Output \lst@letterfalse \fi}%
+       {\ifx\lst@lastother\lstum@backslash
+            \expandafter\@gobblethree
+        \fi}{}#3{#1}{#5}#4}
+\global\let\lst@ifbstring\iffalse % init
+\lst@AddToHook{SelectCharTable}{%
+    \lst@ifbstring
+        \lst@CArgX \\\\\relax \lst@CDefX{}%
+           {\lst@ProcessOther\lstum@backslash
+            \lst@ProcessOther\lstum@backslash
+            \let\lst@lastother\relax}%
+           {}%
+    \fi}
+\global\let\lst@StringDM@bd\lst@StringDM@b
+\global\let\lst@StringDM@db\lst@StringDM@bd
+\gdef\lst@StringDM@m#1#2\@empty#3#4#5{%
+    \lst@CArg #2\relax\lst@DefDelimBE{}{}%
+        {\let\lst@next\@gobblethree
+         \lst@ifletter\else
+             \lst@IfLastOtherOneOf{)].0123456789\lstum@rbrace'}%
+                 {}%
+                 {\let\lst@next\@empty}%
+         \fi
+         \lst@next}#3{#1}{#5}#4}
+\gdef\lst@StringDM@s#1#2#3\@empty#4#5#6{%
+    \lst@CArg #2\relax\lst@DefDelimB{}{}{}#4{#1}{#6}%
+    \lst@CArg #3\relax\lst@DefDelimE{}{}{}#5{#1}}
+\lst@SaveOutputDef{"7D}\lstum@rbrace
+\lst@EndAspect
+\lst@BeginAspect{mf}
+\lst@AddTo\lst@stringtypes{,mf}
+\lst@NewMode\lst@mfinputmode
+\gdef\lst@String@mf#1\@empty#2#3#4{%
+  \lst@CArg #1\relax\lst@DefDelimB
+       {}{}{\lst@ifletter \expandafter\@gobblethree \fi}%
+       \lst@BeginStringMFinput\lst@mfinputmode{#4\lst@Lmodetrue}%
+  \@ifundefined{lsts@semicolon}%
+  {\lst@DefSaveDef{`\;}\lsts@semicolon{% ; and space end the filename
+      \ifnum\lst@mode=\lst@mfinputmode
+          \lst@XPrintToken
+          \expandafter\lst@LeaveMode
+      \fi
+      \lsts@semicolon}%
+   \lst@DefSaveDef{`\ }\lsts@space{%
+      \ifnum\lst@mode=\lst@mfinputmode
+          \lst@XPrintToken
+          \expandafter\lst@LeaveMode
+      \fi
+      \lsts@space}%
+  }{}}
+\gdef\lst@BeginStringMFinput#1#2#3\@empty{%
+    \lst@TrackNewLines \lst@XPrintToken
+      \begingroup
+        \lst@mode\lst@nomode
+        #3\lst@XPrintToken
+      \endgroup
+      \lst@ResetToken
+    \lst@EnterMode{#1}{\def\lst@currstyle#2}%
+    \lst@ifshowstringspaces
+         \lst@keepspacestrue
+         \let\lst@outputspace\lst@visiblespace
+    \fi}
+\lst@EndAspect
+\lst@BeginAspect{comments}
+\lst@NewMode\lst@commentmode
+\gdef\lst@commenttypes{l,f,s,n}
+\gdef\lst@CommentKey#1#2{%
+    \lst@Delim\lst@commentstyle #2\relax
+        {Comment}\lst@commenttypes #1%
+                {\lst@BeginComment\lst@EndComment}%
+        i\@empty{\lst@BeginInvisible\lst@EndInvisible}}
+\lst@Key{comment}\relax{\lst@CommentKey\@empty{#1}}
+\lst@Key{morecomment}\relax{\lst@CommentKey\relax{#1}}
+\lst@Key{deletecomment}\relax{\lst@CommentKey\@nil{#1}}
+\lst@Key{commentstyle}{}{\def\lst@commentstyle{#1}}
+\lst@AddToHook{EmptyStyle}{\let\lst@commentstyle\itshape}
+\gdef\lst@BeginComment{%
+    \lst@DelimOpen
+        \lst@ifexcomments\else
+        \lsthk@AfterBeginComment}
+\gdef\lst@EndComment{\lst@DelimClose\lst@ifexcomments\else}
+\lst@AddToHook{AfterBeginComment}{}
+\lst@AddToHookExe{ExcludeDelims}{\let\lst@ifexcomments\iffalse}
+\gdef\lst@BeginInvisible#1#2#3\@empty{%
+    \lst@TrackNewLines \lst@XPrintToken
+    \lst@BeginDropOutput{#1}}
+\gdef\lst@EndInvisible#1\@empty{\lst@EndDropOutput}
+\gdef\lst@CommentDM@l#1#2\@empty#3#4#5{%
+    \lst@CArg #2\relax\lst@DefDelimB{}{}{}#3{#1}{#5\lst@Lmodetrue}}
+\gdef\lst@CommentDM@f#1{%
+    \@ifnextchar[{\lst@Comment@@f{#1}}%
+                 {\lst@Comment@@f{#1}[0]}}
+\gdef\lst@Comment@@f#1[#2]#3\@empty#4#5#6{%
+    \lst@CArg #3\relax\lst@DefDelimB{}{}%
+        {\lst@CalcColumn
+         \ifnum #2=\@tempcnta\else
+             \expandafter\@gobblethree
+         \fi}%
+        #4{#1}{#6\lst@Lmodetrue}}
+\gdef\lst@CommentDM@s#1#2#3\@empty#4#5#6{%
+    \lst@CArg #2\relax\lst@DefDelimB{}{}{}#4{#1}{#6}%
+    \lst@CArg #3\relax\lst@DefDelimE{}{}{}#5{#1}}
+\gdef\lst@CommentDM@n#1#2#3\@empty#4#5#6{%
+    \ifx\@empty#3\@empty\else
+        \def\@tempa{#2}\def\@tempb{#3}%
+        \ifx\@tempa\@tempb
+            \PackageError{Listings}{Identical delimiters}%
+            {These delimiters make no sense with nested comments.}%
+        \else
+            \lst@CArg #2\relax\lst@DefDelimB
+                {}%
+                {\ifnum\lst@mode=#1\relax \expandafter\@gobble \fi}%
+                {}#4{#1}{#6}%
+            \lst@CArg #3\relax\lst@DefDelimE{}{}{}#5{#1}%
+        \fi
+    \fi}
+\lst@EndAspect
+\lst@BeginAspect{pod}
+\lst@Key{printpod}{false}[t]{\lstKV@SetIf{#1}\lst@ifprintpod}
+\lst@Key{podcomment}{false}[t]{\lstKV@SetIf{#1}\lst@ifpodcomment}
+\lst@AddToHookExe{SetLanguage}{\let\lst@ifpodcomment\iffalse}
+\lst@NewMode\lst@PODmode
+\lst@AddToHook{SelectCharTable}
+    {\lst@ifpodcomment
+         \lst@CArgX =\relax\lst@DefDelimB{}{}%
+           {\ifnum\@tempcnta=\z@
+                \lst@ifprintpod\else
+                    \def\lst@bnext{\lst@BeginDropOutput\lst@PODmode}%
+                    \expandafter\expandafter\expandafter\@gobblethree
+                \fi
+            \else
+               \expandafter\@gobblethree
+            \fi}%
+           \lst@BeginComment\lst@PODmode{{\lst@commentstyle}}%
+         \lst@CArgX =cut\^^M\relax\lst@DefDelimE
+           {\lst@CalcColumn}%
+           {\ifnum\@tempcnta=\z@\else
+                \expandafter\@gobblethree
+            \fi}%
+           {}%
+           \lst@EndComment\lst@PODmode
+     \fi}
+\lst@EndAspect
+\lst@BeginAspect[keywords]{html}
+\gdef\lst@tagtypes{s}
+\gdef\lst@TagKey#1#2{%
+    \lst@Delim\lst@tagstyle #2\relax
+        {Tag}\lst@tagtypes #1%
+                     {\lst@BeginTag\lst@EndTag}%
+        \@@end\@empty{}}
+\lst@Key{tag}\relax{\lst@TagKey\@empty{#1}}
+\lst@Key{tagstyle}{}{\def\lst@tagstyle{#1}}
+\lst@AddToHook{EmptyStyle}{\let\lst@tagstyle\@empty}
+\gdef\lst@BeginTag{%
+    \lst@DelimOpen
+        \lst@ifextags\else
+        {\let\lst@ifkeywords\iftrue
+         \lst@ifmarkfirstintag \lst@firstintagtrue \fi}}
+\lst@AddToHookExe{ExcludeDelims}{\let\lst@ifextags\iffalse}
+\gdef\lst@EndTag{\lst@DelimClose\lst@ifextags\else}
+\lst@Key{usekeywordsintag}t[t]{\lstKV@SetIf{#1}\lst@ifusekeysintag}
+\lst@Key{markfirstintag}f[t]{\lstKV@SetIf{#1}\lst@ifmarkfirstintag}
+\gdef\lst@firstintagtrue{\global\let\lst@iffirstintag\iftrue}
+\global\let\lst@iffirstintag\iffalse
+\lst@AddToHook{PostOutput}{\lst@tagresetfirst}
+\lst@AddToHook{Output}
+    {\gdef\lst@tagresetfirst{\global\let\lst@iffirstintag\iffalse}}
+\lst@AddToHook{OutputOther}{\gdef\lst@tagresetfirst{}}
+\lst@AddToHook{Output}
+    {\ifnum\lst@mode=\lst@tagmode
+         \lst@iffirstintag \let\lst@thestyle\lst@gkeywords@sty \fi
+         \lst@ifusekeysintag\else \let\lst@thestyle\lst@gkeywords@sty\fi
+     \fi}
+\lst@NewMode\lst@tagmode
+\lst@AddToHook{Init}{\global\let\lst@ifnotag\iftrue}
+\lst@AddToHook{SelectCharTable}{\let\lst@ifkeywords\lst@ifnotag}
+\gdef\lst@Tag@s#1#2\@empty#3#4#5{%
+    \global\let\lst@ifnotag\iffalse
+    \lst@CArg #1\relax\lst@DefDelimB {}{}%
+        {\ifnum\lst@mode=\lst@tagmode \expandafter\@gobblethree \fi}%
+        #3\lst@tagmode{#5}%
+    \lst@CArg #2\relax\lst@DefDelimE {}{}{}#4\lst@tagmode}%
+\gdef\lst@BeginCDATA#1\@empty{%
+    \lst@TrackNewLines \lst@PrintToken
+    \lst@EnterMode\lst@GPmode{}\let\lst@ifmode\iffalse
+    \lst@mode\lst@tagmode #1\lst@mode\lst@GPmode\relax\lst@modetrue}
+\lst@EndAspect
+\lst@BeginAspect{escape}
+\lst@Key{texcl}{false}[t]{\lstKV@SetIf{#1}\lst@iftexcl}
+\lst@AddToHook{TextStyle}{\let\lst@iftexcl\iffalse}
+\lst@AddToHook{EOL}
+    {\ifnum\lst@mode=\lst@TeXLmode
+         \expandafter\lst@escapeend
+         \expandafter\lst@LeaveAllModes
+         \expandafter\lst@ReenterModes
+     \fi}
+\lst@AddToHook{AfterBeginComment}
+    {\lst@iftexcl \lst@ifLmode \lst@ifdropinput\else
+         \lst@PrintToken
+         \lst@LeaveMode \lst@InterruptModes
+         \lst@EnterMode{\lst@TeXLmode}{\lst@modetrue\lst@commentstyle}%
+         \expandafter\expandafter\expandafter\lst@escapebegin
+     \fi \fi \fi}
+\lst@NewMode\lst@TeXLmode
+\gdef\lst@ActiveCDefX#1{\lst@ActiveCDefX@#1}
+\gdef\lst@ActiveCDefX@#1#2#3{
+    \catcode`#1\active\lccode`\~=`#1%
+    \lowercase{\lst@CDefIt~}{#2}{#3}{}}
+\gdef\lst@Escape#1#2#3#4{%
+    \lst@CArgX #1\relax\lst@CDefX
+        {}%
+        {\lst@ifdropinput\else
+         \lst@TrackNewLines\lst@OutputLostSpace \lst@XPrintToken
+         \lst@InterruptModes
+         \lst@EnterMode{\lst@TeXmode}{\lst@modetrue}%
+         \ifx\^^M#2%
+             \lst@CArg #2\relax\lst@ActiveCDefX
+                 {}%
+                 {\lst@escapeend #4\lst@LeaveAllModes\lst@ReenterModes}%
+                 {\lst@MProcessListing}%
+         \else
+             \lst@CArg #2\relax\lst@ActiveCDefX
+                 {}%
+                 {\lst@escapeend #4\lst@LeaveAllModes\lst@ReenterModes
+                  \lst@newlines\z@ \lst@whitespacefalse}%
+                 {}%
+         \fi
+         #3\lst@escapebegin
+         \fi}%
+        {}}
+\lst@NewMode\lst@TeXmode
+\lst@Key{escapebegin}{}{\def\lst@escapebegin{#1}}
+\lst@Key{escapeend}{}{\def\lst@escapeend{#1}}
+\lst@Key{escapechar}{}
+    {\ifx\@empty#1\@empty
+         \let\lst@DefEsc\relax
+     \else
+         \def\lst@DefEsc{\lst@Escape{#1}{#1}{}{}}%
+     \fi}
+\lst@AddToHook{TextStyle}{\let\lst@DefEsc\@empty}
+\lst@AddToHook{SelectCharTable}{\lst@DefEsc}
+\lst@Key{escapeinside}{}{\lstKV@TwoArg{#1}%
+    {\let\lst@DefEsc\@empty
+     \ifx\@empty##1@empty\else \ifx\@empty##2\@empty\else
+         \def\lst@DefEsc{\lst@Escape{##1}{##2}{}{}}%
+     \fi\fi}}
+\lst@Key{mathescape}{false}[t]{\lstKV@SetIf{#1}\lst@ifmathescape}
+\lst@AddToHook{SelectCharTable}
+    {\lst@ifmathescape \lst@Escape{\$}{\$}%
+        {\setbox\@tempboxa=\hbox\bgroup$}%
+        {$\egroup \lst@CalcLostSpaceAndOutput}\fi}
+\lst@EndAspect
+\lst@BeginAspect{keywords}
+\global\let\lst@ifsensitive\iftrue % init
+\global\let\lst@ifsensitivedefed\iffalse % init % \global
+\lst@ifsavemem\else
+\gdef\lst@KeywordTest#1#2#3{%
+    \begingroup \let\lst@UM\@empty
+    \global\expandafter\let\expandafter\@gtempa
+        \csname\@lst#1@\the\lst@token\endcsname
+    \endgroup
+    \ifx\@gtempa\relax\else
+        \let\lst@thestyle\@gtempa
+    \fi}
+\gdef\lst@KEYWORDTEST{%
+    \uppercase\expandafter{\expandafter
+        \lst@KEYWORDTEST@\the\lst@token}\relax}
+\gdef\lst@KEYWORDTEST@#1\relax#2#3#4{%
+    \begingroup \let\lst@UM\@empty
+    \global\expandafter\let\expandafter\@gtempa
+        \csname\@lst#2@#1\endcsname
+    \endgroup
+    \ifx\@gtempa\relax\else
+        \let\lst@thestyle\@gtempa
+    \fi}
+\gdef\lst@WorkingTest#1#2#3{%
+    \begingroup \let\lst@UM\@empty
+    \global\expandafter\let\expandafter\@gtempa
+        \csname\@lst#1@\the\lst@token\endcsname
+    \endgroup
+    \@gtempa}
+\gdef\lst@WORKINGTEST{%
+    \uppercase\expandafter{\expandafter
+        \lst@WORKINGTEST@\the\lst@token}\relax}
+\gdef\lst@WORKINGTEST@#1\relax#2#3#4{%
+    \begingroup \let\lst@UM\@empty
+    \global\expandafter\let\expandafter\@gtempa
+        \csname\@lst#2@#1\endcsname
+    \endgroup
+    \@gtempa}
+\gdef\lst@DefineKeywords#1#2#3{%
+    \lst@ifsensitive
+        \def\lst@next{\lst@for#2}%
+    \else
+        \def\lst@next{\uppercase\expandafter{\expandafter\lst@for#2}}%
+    \fi
+    \lst@next\do
+    {\expandafter\ifx\csname\@lst#1@##1\endcsname\relax
+        \global\expandafter\let\csname\@lst#1@##1\endcsname#3%
+     \fi}}
+\gdef\lst@UndefineKeywords#1#2#3{%
+    \lst@ifsensitivedefed
+        \def\lst@next{\lst@for#2}%
+    \else
+        \def\lst@next{\uppercase\expandafter{\expandafter\lst@for#2}}%
+    \fi
+    \lst@next\do
+    {\expandafter\ifx\csname\@lst#1@##1\endcsname#3%
+        \global\expandafter\let\csname\@lst#1@##1\endcsname\relax
+     \fi}}
+\fi
+\lst@ifsavemem
+\gdef\lst@IfOneOutOf#1\relax#2{%
+    \def\lst@temp##1,#1,##2##3\relax{%
+        \ifx\@empty##2\else \expandafter\lst@IOOOfirst \fi}%
+    \def\lst@next{\lst@IfOneOutOf@#1\relax}%
+    \expandafter\lst@next#2\relax\relax}
+\gdef\lst@IfOneOutOf@#1\relax#2#3{%
+    \ifx#2\relax
+        \expandafter\@secondoftwo
+    \else
+        \expandafter\lst@temp\expandafter,#2,#1,\@empty\relax
+        \expandafter\lst@next
+    \fi}
+\ifx\iffalse\else\fi
+\gdef\lst@IOOOfirst#1\relax#2#3{\fi#2}
+\gdef\lst@IFONEOUTOF#1\relax#2{%
+    \uppercase{\def\lst@temp##1,#1},##2##3\relax{%
+        \ifx\@empty##2\else \expandafter\lst@IOOOfirst \fi}%
+    \def\lst@next{\lst@IFONEOUTOF@#1\relax}%
+    \expandafter\lst@next#2\relax}
+\gdef\lst@IFONEOUTOF@#1\relax#2#3{%
+    \ifx#2\relax
+        \expandafter\@secondoftwo
+    \else
+        \uppercase
+            {\expandafter\lst@temp\expandafter,#2,#1,\@empty\relax}%
+        \expandafter\lst@next
+    \fi}
+\gdef\lst@KWTest{%
+    \begingroup \let\lst@UM\@empty
+    \expandafter\xdef\expandafter\@gtempa\expandafter{\the\lst@token}%
+    \endgroup
+    \expandafter\lst@IfOneOutOf\@gtempa\relax}
+\gdef\lst@KeywordTest#1#2#3{\lst@KWTest #2{\let\lst@thestyle#3}{}}
+\global\let\lst@KEYWORDTEST\lst@KeywordTest
+\gdef\lst@WorkingTest#1#2#3{\lst@KWTest #2#3{}}
+\global\let\lst@WORKINGTEST\lst@WorkingTest
+\fi
+\lst@Key{sensitive}\relax[t]{\lstKV@SetIf{#1}\lst@ifsensitive}
+\lst@AddToHook{SetLanguage}{\let\lst@ifsensitive\iftrue}
+\lst@AddToHook{Init}
+    {\lst@ifsensitive\else
+         \let\lst@KeywordTest\lst@KEYWORDTEST
+         \let\lst@WorkingTest\lst@WORKINGTEST
+         \let\lst@IfOneOutOf\lst@IFONEOUTOF
+     \fi}
+\gdef\lst@MakeMacroUppercase#1{%
+    \ifx\@undefined#1\else \uppercase\expandafter
+        {\expandafter\def\expandafter#1\expandafter{#1}}%
+    \fi}
+\gdef\lst@InstallTest#1#2#3#4#5#6#7#8{%
+    \lst@AddToHook{TrackKeywords}{\lst@TrackKeywords{#1}#2#4#6#7#8}%
+    \lst@AddToHook{PostTrackKeywords}{\lst@PostTrackKeywords#2#3#4#5}}
+\lst@AddToHook{Init}{\lsthk@TrackKeywords\lsthk@PostTrackKeywords}
+\lst@AddToHook{TrackKeywords}
+    {\global\let\lst@DoDefineKeywords\@empty}% init
+\lst@AddToHook{PostTrackKeywords}
+    {\lst@DoDefineKeywords
+     \global\let\lst@DoDefineKeywords\@empty}% init
+\lst@AddToHook{Output}{\lst@ifkeywords \lsthk@DetectKeywords \fi}
+\lst@AddToHook{DetectKeywords}{}% init
+\lst@AddToHook{ModeTrue}{\let\lst@ifkeywords\iffalse}
+\lst@AddToHookExe{Init}{\let\lst@ifkeywords\iftrue}
+\gdef\lst@InstallTestNow#1#2#3#4#5{%
+    \@ifundefined{\string#2#1}%
+    {\global\@namedef{\string#2#1}{}%
+     \edef\@tempa{%
+         \noexpand\lst@AddToHook{\ifx#5dDetectKeywords\else Output\fi}%
+         {\ifx #4w\noexpand\lst@WorkingTest
+             \else\noexpand\lst@KeywordTest \fi
+          {#1}\noexpand#2\noexpand#3}}%
+     \lst@ifsavemem
+         \@tempa
+     \else
+         \@ifundefined{\@lst#1@if@ins}%
+             {\@tempa \global\@namedef{\@lst#1@if@ins}{}}%
+             {}%
+     \fi}
+    {}}
+\gdef\lst@TrackKeywords#1#2#3#4#5#6{%
+    \lst@false
+    \def\lst@arg{{#1}#4}%
+    \expandafter\expandafter\expandafter\lst@TK@
+        \expandafter\lst@arg#2\relax\relax
+    \lst@ifsavemem\else
+        \def\lst@arg{{#1}#4#2}%
+        \expandafter\expandafter\expandafter\lst@TK@@
+            \expandafter\lst@arg#3\relax\relax
+    \fi
+    \lst@if \lst@InstallTestNow{#1}#2#4#5#6\fi}
+\gdef\lst@TK@#1#2#3#4{%
+  \ifx\lst@ifsensitive\lst@ifsensitivedefed
+    \ifx#3#4\else
+      \lst@true
+      \lst@ifsavemem\else
+          \lst@UndefineKeywords{#1}#4#2%
+          \lst@AddTo\lst@DoDefineKeywords{\lst@DefineKeywords{#1}#3#2}%
+      \fi
+    \fi
+  \else
+    \ifx#3\relax\else
+      \lst@true
+      \lst@ifsavemem\else
+          \lst@UndefineKeywords{#1}#4#2%
+          \lst@AddTo\lst@DoDefineKeywords{\lst@DefineKeywords{#1}#3#2}%
+      \fi
+    \fi
+  \fi
+  \lst@ifsavemem \ifx#3\relax\else
+      \lst@ifsensitive\else \lst@MakeMacroUppercase#3\fi
+  \fi \fi
+  \ifx#3\relax
+      \expandafter\@gobblethree
+  \fi
+  \lst@TK@{#1}#2}
+\gdef\lst@TK@@#1#2#3#4#5{%
+    \ifx#4\relax
+        \expandafter\@gobblefour
+    \else
+        \lst@IfSubstring{#4#5}#3{}{\lst@UndefineKeywords{#1}#5#2}%
+    \fi
+    \lst@TK@@{#1}#2#3}
+\lst@AddToHook{InitVars}
+    {\global\let\lst@ifsensitivedefed\lst@ifsensitive}
+\gdef\lst@PostTrackKeywords#1#2#3#4{%
+    \lst@ifsavemem\else
+        \global\let#3#1%
+        \global\let#4#2%
+    \fi}
+\lst@Key{classoffset}\z@{\def\lst@classoffset{#1}}
+\gdef\lst@InstallFamily#1#2#3#4#5{%
+    \lst@Key{#2}\relax{\lst@UseFamily{#2}##1\relax\lst@MakeKeywords}%
+    \lst@Key{more#2}\relax
+        {\lst@UseFamily{#2}##1\relax\lst@MakeMoreKeywords}%
+    \lst@Key{delete#2}\relax
+        {\lst@UseFamily{#2}##1\relax\lst@DeleteKeywords}%
+    \ifx\@empty#3\@empty\else
+        \lst@Key{#3}{#4}{\lstKV@OptArg[\@ne]{##1}%
+            {\@tempcnta\lst@classoffset \advance\@tempcnta####1\relax
+             \@namedef{lst@#3\ifnum\@tempcnta=\@ne\else \the\@tempcnta
+                             \fi}{####2}}}%
+    \fi
+    \expandafter\lst@InstallFamily@
+        \csname\@lst @#2@data\expandafter\endcsname
+        \csname\@lst @#5\endcsname {#1}{#2}{#3}}
+\gdef\lst@InstallFamily@#1#2#3#4#5#6#7#8{%
+    \gdef#1{{#3}{#4}{#5}#2#7}%
+    \long\def\lst@temp##1{#6}%
+    \ifx\lst@temp\@gobble
+        \lst@AddTo#1{s#8}%
+    \else
+        \lst@AddTo#1{w#8}%
+        \global\@namedef{lst@g#4@wp}##1{#6}%
+    \fi}
+\gdef\lst@UseFamily#1{%
+    \def\lst@family{#1}%
+    \@ifnextchar[\lst@UseFamily@{\lst@UseFamily@[\@ne]}}
+\gdef\lst@UseFamily@[#1]{%
+    \@tempcnta\lst@classoffset \advance\@tempcnta#1\relax
+    \lst@ProvideFamily\lst@family
+    \lst@UseFamily@a
+        {\lst@family\ifnum\@tempcnta=\@ne\else \the\@tempcnta \fi}}
+\gdef\lst@UseFamily@a#1{%
+    \expandafter\lst@UseFamily@b
+       \csname\@lst @#1@list\expandafter\endcsname
+       \csname\@lst @#1\expandafter\endcsname
+       \csname\@lst @#1@also\expandafter\endcsname
+       \csname\@lst @g#1\endcsname}
+\gdef\lst@UseFamily@b#1#2#3#4#5\relax#6{\lstKV@XOptArg[]{#5}#6#1#2#3#4}
+\gdef\lst@ProvideFamily#1{%
+    \@ifundefined{lstfam@#1\ifnum\@tempcnta=\@ne\else\the\@tempcnta\fi}%
+    {\global\@namedef{lstfam@#1\ifnum\@tempcnta=\@ne\else
+                                        \the\@tempcnta\fi}{}%
+     \expandafter\expandafter\expandafter\lst@ProvideFamily@
+         \csname\@lst @#1@data\endcsname
+         {\ifnum\@tempcnta=\@ne\else \the\@tempcnta \fi}}%
+    {}}%
+\gdef\lst@ProvideFamily@#1#2#3#4#5#6#7#8{%
+    \expandafter\xdef\csname\@lst @g#2#8@sty\endcsname
+    {\if #6w%
+         \expandafter\noexpand\csname\@lst @g#2@wp\endcsname{#8}%
+     \else
+         \expandafter\noexpand\csname\@lst @#3#8\endcsname
+     \fi}%
+    \ifx\@empty#3\@empty\else
+        \edef\lst@temp{\noexpand\lst@AddToHook{Init}{%
+            \noexpand\lst@ProvideStyle\expandafter\noexpand
+                \csname\@lst @#3#8\endcsname\noexpand#4}}%
+        \lst@temp
+    \fi
+    \expandafter\lst@ProvideFamily@@
+         \csname\@lst @#2#8@list\expandafter\endcsname
+         \csname\@lst @#2#8\expandafter\endcsname
+         \csname\@lst @#2#8@also\expandafter\endcsname
+         \csname\@lst @g#2#8@list\expandafter\endcsname
+         \csname\@lst @g#2#8\expandafter\endcsname
+         \csname\@lst @g#2#8@sty\expandafter\endcsname
+         {#1}#5#6#7}
+\gdef\lst@ProvideFamily@@#1#2#3#4#5#6#7#8{%
+    \gdef#1{#2#5}\global\let#2\@empty \global\let#3\@empty % init
+    \gdef#4{#2#5}\global\let#5\@empty % init
+    \if #8l\relax
+        \lst@AddToHook{SetLanguage}{\def#1{#2#5}\let#2\@empty}%
+    \fi
+    \lst@InstallTest{#7}#1#2#4#5#6}
+\gdef\lst@InstallKeywords#1#2#3#4#5{%
+    \lst@Key{#2}\relax
+        {\lst@UseFamily{#2}[\@ne]##1\relax\lst@MakeKeywords}%
+    \lst@Key{more#2}\relax
+        {\lst@UseFamily{#2}[\@ne]##1\relax\lst@MakeMoreKeywords}%
+    \lst@Key{delete#2}\relax
+        {\lst@UseFamily{#2}[\@ne]##1\relax\lst@DeleteKeywords}%
+    \ifx\@empty#3\@empty\else
+        \lst@Key{#3}{#4}{\@namedef{lst@#3}{##1}}%
+    \fi
+    \expandafter\lst@InstallFamily@
+        \csname\@lst @#2@data\expandafter\endcsname
+        \csname\@lst @#5\endcsname {#1}{#2}{#3}}
+\gdef\lst@ProvideStyle#1#2{%
+    \ifx#1\@undefined \let#1#2%
+    \else\ifx#1\relax \let#1#2\fi\fi}
+\gdef\lst@BuildClassList#1#2,{%
+    \ifx\relax#2\@empty\else
+        \ifx\@empty#2\@empty\else
+            \lst@lExtend#1{\csname\@lst @#2\expandafter\endcsname
+                           \csname\@lst @g#2\endcsname}%
+        \fi
+        \expandafter\lst@BuildClassList\expandafter#1
+    \fi}
+\gdef\lst@DeleteClassesIn#1#2{%
+    \expandafter\lst@DCI@\expandafter#1#2\relax\relax}
+\gdef\lst@DCI@#1#2#3{%
+    \ifx#2\relax
+        \expandafter\@gobbletwo
+    \else
+        \def\lst@temp##1#2#3##2{%
+            \lst@lAddTo#1{##1}%
+            \ifx ##2\relax\else
+                \expandafter\lst@temp
+            \fi ##2}%
+        \let\@tempa#1\let#1\@empty
+        \expandafter\lst@temp\@tempa#2#3\relax
+    \fi
+    \lst@DCI@#1}
+\gdef\lst@MakeKeywords[#1]#2#3#4#5#6{%
+    \def#3{#4#6}\let#4\@empty \let#5\@empty
+    \lst@MakeMoreKeywords[#1]{#2}#3#4#5#6}
+\gdef\lst@MakeMoreKeywords[#1]#2#3#4#5#6{%
+    \lst@BuildClassList#3#1,\relax,%
+    \lst@DefOther\lst@temp{,#2}\lst@lExtend#4\lst@temp}
+\gdef\lst@DeleteKeywords[#1]#2#3#4#5#6{%
+    \lst@MakeKeywords[#1]{#2}\@tempa\@tempb#5#6%
+    \lst@DeleteClassesIn#3\@tempa
+    \lst@DeleteKeysIn#4\@tempb}
+\lst@InstallFamily k{keywords}{keywordstyle}\bfseries{keywordstyle}{}ld
+\gdef\lst@DefKeywordstyle#1#2\@nil@{%
+   \@namedef{lst@keywordstyle\ifnum\@tempcnta=\@ne\else\the\@tempcnta
+                             \fi}{#1#2}}%
+\lst@Key{keywordstyle}{\bfseries}{\lstKV@OptArg[\@ne]{#1}%
+  {\@tempcnta\lst@classoffset \advance\@tempcnta##1\relax
+   \@ifstar{\lst@DefKeywordstyle{\uppercase\expandafter{%
+                                 \expandafter\lst@token
+                                 \expandafter{\the\lst@token}}}}%
+           {\lst@DefKeywordstyle{}}##2\@nil@}}
+\lst@Key{ndkeywords}\relax
+    {\lst@UseFamily{keywords}[\tw@]#1\relax\lst@MakeKeywords}%
+\lst@Key{morendkeywords}\relax
+    {\lst@UseFamily{keywords}[\tw@]#1\relax\lst@MakeMoreKeywords}%
+\lst@Key{deletendkeywords}\relax
+    {\lst@UseFamily{keywords}[\tw@]#1\relax\lst@DeleteKeywords}%
+\lst@Key{ndkeywordstyle}\relax{\@namedef{lst@keywordstyle2}{#1}}%
+\lst@Key{keywordsprefix}\relax{\lst@DefActive\lst@keywordsprefix{#1}}
+\global\let\lst@keywordsprefix\@empty
+\lst@AddToHook{SelectCharTable}
+    {\ifx\lst@keywordsprefix\@empty\else
+         \expandafter\lst@CArg\lst@keywordsprefix\relax
+             \lst@CDef{}%
+                      {\lst@ifletter\else
+                           \global\let\lst@prefixkeyword\@empty
+                       \fi}%
+                      {}%
+     \fi}
+\lst@AddToHook{Init}{\global\let\lst@prefixkeyword\relax}
+\lst@AddToHook{Output}
+    {\ifx\lst@prefixkeyword\@empty
+         \let\lst@thestyle\lst@gkeywords@sty
+         \global\let\lst@prefixkeyword\relax
+     \fi}%
+\lst@Key{otherkeywords}{}{%
+    \let\lst@otherkeywords\@empty
+    \lst@for{#1}\do{%
+      \lst@MakeActive{##1}%
+      \lst@lExtend\lst@otherkeywords{%
+          \expandafter\lst@CArg\lst@temp\relax\lst@CDef
+              {}\lst@PrintOtherKeyword\@empty}}}
+\lst@AddToHook{SelectCharTable}{\lst@otherkeywords}
+\gdef\lst@PrintOtherKeyword#1\@empty{%
+    \lst@XPrintToken
+    \begingroup
+      \lst@modetrue \lsthk@TextStyle
+      \let\lst@ProcessDigit\lst@ProcessLetter
+      \let\lst@ProcessOther\lst@ProcessLetter
+      \lst@lettertrue
+      #1%
+  \lst@SaveToken
+    \endgroup
+\lst@RestoreToken
+\global\let\lst@savedcurrstyle\lst@currstyle
+\let\lst@currstyle\lst@gkeywords@sty
+    \lst@Output
+\let\lst@currstyle\lst@savedcurrstyle}
+\lst@EndAspect
+\lst@BeginAspect[keywords]{emph}
+\lst@InstallFamily e{emph}{emphstyle}{}{emphstyle}{}od
+\lst@EndAspect
+\lst@BeginAspect[keywords]{tex}
+\lst@InstallFamily {cs}{texcs}{texcsstyle}\relax{keywordstyle}
+    {\ifx\lst@lastother\lstum@backslash
+         \expandafter\let\expandafter\lst@thestyle
+                         \csname lst@texcsstyle#1\endcsname
+     \fi}
+    ld
+\lst@Key{texcsstyle}\relax
+  {\@ifstar{\lst@true\lst@DefTexcsstyle}%
+           {\lst@false\lst@DefTexcsstyle}#1\@nil@}
+\gdef\lst@DefTexcsstyle#1\@nil@{%
+    \let\lst@iftexcsincludebs\lst@if
+    \lstKV@OptArg[\@ne]{#1}%
+    {\@tempcnta\lst@classoffset \advance\@tempcnta##1\relax
+     \@namedef{lst@texcsstyle\ifnum\@tempcnta=\@ne\else
+                                   \the\@tempcnta \fi}{##2}}}%
+\global\let\lst@iftexcsincludebs\iffalse
+\let\lst@iftexcsincludebs\iffalse
+\lst@AddToHook{SelectCharTable}
+{\lst@iftexcsincludebs \ifx\@empty\lst@texcs\else
+     \lst@DefSaveDef{`\\}\lsts@texcsbs
+      {\lst@ifletter
+           \lst@Output
+       \else
+           \lst@OutputOther
+       \fi
+       \lst@Merge\lsts@texcsbs}%
+ \fi \fi}
+\lst@EndAspect
+\lst@BeginAspect[keywords]{directives}
+\lst@NewMode\lst@CDmode
+\lst@AddToHook{EOL}{\ifnum\lst@mode=\lst@CDmode \lst@LeaveMode \fi}
+\lst@InstallKeywords{d}{directives}{directivestyle}\relax{keywordstyle}
+    {\ifnum\lst@mode=\lst@CDmode
+         \let\lst@thestyle\lst@directivestyle
+     \fi}
+    ld
+\global\let\lst@directives\@empty % init
+\lst@AddTo\lst@delimtypes{,directive}
+\gdef\lst@Delim@directive#1\@empty#2#3#4{%
+    \lst@CArg #1\relax\lst@DefDelimB
+        {\lst@CalcColumn}%
+        {}%
+        {\ifnum\@tempcnta=\z@
+             \def\lst@bnext{#2\lst@CDmode{#4\lst@Lmodetrue}%
+                \let\lst@currstyle\lst@directivestyle}%
+ \fi
+ \@gobblethree}%
+        #2\lst@CDmode{#4\lst@Lmodetrue}}
+\lst@AddTo\lst@stringtypes{,directive}
+\gdef\lst@StringDM@directive#1#2#3\@empty{%
+    \lst@CArg #2\relax\lst@CDef
+        {}%
+        {\let\lst@bnext\lst@CArgEmpty
+         \ifnum\lst@mode=\lst@CDmode
+             \def\lst@bnext{\lst@BeginString{#1}}%
+         \fi
+         \lst@bnext}%
+        \@empty
+    \lst@CArg #3\relax\lst@CDef
+        {}%
+        {\let\lst@enext\lst@CArgEmpty
+         \ifnum #1=\lst@mode
+             \let\lst@bnext\lst@EndString
+         \fi
+         \lst@bnext}%
+        \@empty}
+\lst@EndAspect
+\lst@BeginAspect[keywords,comments]{keywordcomments}
+\lst@NewMode\lst@KCmode \lst@NewMode\lst@KCSmode
+\gdef\lst@BeginKC{\aftergroup\aftergroup\aftergroup\lst@BeginKC@}%
+\gdef\lst@BeginKC@{%
+    \lst@ResetToken
+    \lst@BeginComment\lst@KCmode{{\lst@commentstyle}\lst@modetrue}%
+                     \@empty}%
+\gdef\lst@BeginKCS{\aftergroup\aftergroup\aftergroup\lst@BeginKCS@}%
+\gdef\lst@BeginKCS@{%
+    \lst@ResetToken
+    \lst@BeginComment\lst@KCSmode{{\lst@commentstyle}\lst@modetrue}%
+                     \@empty}%
+\lst@AddToHook{PostOutput}{\lst@KCpost \global\let\lst@KCpost\@empty}
+\global\let\lst@KCpost\@empty % init
+\gdef\lst@EndKC{\lst@SaveToken \lst@LeaveMode \lst@RestoreToken
+    \let\lst@thestyle\lst@identifierstyle \lsthk@Output}
+\lst@InstallKeywords{kc}{keywordcomment}{}\relax{}
+    {\ifnum\lst@mode=\lst@KCmode
+         \edef\lst@temp{\the\lst@token}%
+         \ifx\lst@temp\lst@KCmatch
+             \lst@EndKC
+         \fi
+     \else
+         \lst@ifmode\else
+             \xdef\lst@KCmatch{\the\lst@token}%
+             \global\let\lst@KCpost\lst@BeginKC
+         \fi
+     \fi}
+    lo
+\lst@Key{keywordcommentsemicolon}{}{\lstKV@ThreeArg{#1}%
+    {\def\lst@KCAkeywordsB{##1}%
+     \def\lst@KCAkeywordsE{##2}%
+     \def\lst@KCBkeywordsB{##3}%
+     \def\lst@KCkeywords{##1##2##3}}}
+\lst@AddToHook{SetLanguage}{%
+    \let\lst@KCAkeywordsB\@empty \let\lst@KCAkeywordsE\@empty
+    \let\lst@KCBkeywordsB\@empty \let\lst@KCkeywords\@empty}
+\lst@AddToHook{SelectCharTable}
+    {\ifx\lst@KCkeywords\@empty\else
+        \lst@DefSaveDef{`\;}\lsts@EKC
+            {\lst@XPrintToken
+             \ifnum\lst@mode=\lst@KCmode \lst@EndComment\@empty \else
+             \ifnum\lst@mode=\lst@KCSmode \lst@EndComment\@empty
+             \fi \fi
+             \lsts@EKC}%
+     \fi}
+\gdef\lst@KCAWorkB{%
+    \lst@ifmode\else \global\let\lst@KCpost\lst@BeginKC \fi}
+\gdef\lst@KCBWorkB{%
+    \lst@ifmode\else \global\let\lst@KCpost\lst@BeginKCS \fi}
+\gdef\lst@KCAWorkE{\ifnum\lst@mode=\lst@KCmode \lst@EndKC \fi}
+\lst@ProvideFamily@@
+    \lst@KCAkeywordsB@list\lst@KCAkeywordsB \lst@KC@also
+    \lst@gKCAkeywordsB@list\lst@gKCAkeywordsB \lst@KCAWorkB
+    {kcb}owo % prefix, other key, working procedure, Output hook
+\lst@ProvideFamily@@
+    \lst@KCAkeywordsE@list\lst@KCAkeywordsE \lst@KC@also
+    \lst@gKCAkeywordsE@list\lst@gKCAkeywordsE \lst@KCAWorkE
+    {kce}owo
+\lst@ProvideFamily@@
+    \lst@KCBkeywordsB@list\lst@KCBkeywordsB \lst@KC@also
+    \lst@gKCBkeywordsB@list\lst@gKCBkeywordsB \lst@KCBWorkB
+    {kcs}owo
+\lst@EndAspect
+\lst@BeginAspect[keywords]{index}
+\lst@InstallFamily w{index}{indexstyle}\lstindexmacro{indexstyle}
+    {\csname\@lst @indexstyle#1\expandafter\endcsname
+         \expandafter{\the\lst@token}}
+    od
+\lst@UserCommand\lstindexmacro#1{\index{{\ttfamily#1}}}
+\lst@EndAspect
+\lst@BeginAspect[keywords]{procnames}
+\gdef\lst@procnametrue{\global\let\lst@ifprocname\iftrue}
+\gdef\lst@procnamefalse{\global\let\lst@ifprocname\iffalse}
+\lst@AddToHook{Init}{\lst@procnamefalse}
+\lst@AddToHook{DetectKeywords}
+    {\lst@ifprocname
+         \let\lst@thestyle\lst@procnamestyle
+         \lst@ifindexproc \csname\@lst @gindex@sty\endcsname \fi
+         \lst@procnamefalse
+     \fi}
+\lst@Key{procnamestyle}{}{\def\lst@procnamestyle{#1}}
+\lst@Key{indexprocnames}{false}[t]{\lstKV@SetIf{#1}\lst@ifindexproc}
+\lst@AddToHook{Init}{\lst@ifindexproc \lst@indexproc \fi}
+\gdef\lst@indexproc{%
+    \@ifundefined{lst@indexstyle1}%
+        {\@namedef{lst@indexstyle1}##1{}}%
+        {}}
+\lst@InstallKeywords w{procnamekeys}{}\relax{}
+    {\global\let\lst@PNpost\lst@procnametrue}
+    od
+\lst@AddToHook{PostOutput}{\lst@PNpost\global\let\lst@PNpost\@empty}
+\global\let\lst@PNpost\@empty % init
+\lst@EndAspect
+\lst@BeginAspect{style}
+\@ifundefined{lststylefiles}
+    {\lst@UserCommand\lststylefiles{lststy0.sty}}{}
+\lst@UserCommand\lstdefinestyle{\lst@DefStyle\iftrue}
+\lst@UserCommand\lst@definestyle{\lst@DefStyle\iffalse}
+\gdef\lst@DefStyle{\lst@DefDriver{style}{sty}\lstset}
+\global\@namedef{lststy@$}{\lsthk@EmptyStyle}
+\lst@AddToHook{EmptyStyle}{}% init
+\lst@Key{style}\relax{%
+    \lst@LAS{style}{sty}{[]{#1}}\lst@NoAlias\lststylefiles
+        \lsthk@SetStyle
+        {}}
+\lst@AddToHook{SetStyle}{}% init
+\lst@EndAspect
+\lst@BeginAspect{language}
+\@ifundefined{lstdriverfiles}
+    {\lst@UserCommand\lstlanguagefiles{lstlang0.sty}}{}
+\lst@UserCommand\lstdefinelanguage{\lst@DefLang\iftrue}
+\lst@UserCommand\lst@definelanguage{\lst@DefLang\iffalse}
+\gdef\lst@DefLang{\lst@DefDriver{language}{lang}\lstset}
+\lstdefinelanguage{}{}
+\lst@Key{language}\relax{\lstKV@OptArg[]{#1}%
+    {\lst@LAS{language}{lang}{[##1]{##2}}\lst@FindAlias\lstlanguagefiles
+         \lsthk@SetLanguage
+         {\lst@FindAlias[##1]{##2}%
+          \let\lst@language\lst@malias
+          \let\lst@dialect\lst@oalias}}}
+\lst@Key{alsolanguage}\relax{\lstKV@OptArg[]{#1}%
+    {\lst@LAS{language}{lang}{[##1]{##2}}\lst@FindAlias\lstlanguagefiles
+         {}%
+         {\lst@FindAlias[##1]{##2}%
+          \let\lst@language\lst@malias
+          \let\lst@dialect\lst@oalias}}}
+\lst@AddToHook{SetLanguage}{}% init
+\lst@UserCommand\lstalias{\@ifnextchar[\lstalias@\lstalias@@}
+\gdef\lstalias@[#1]#2{\lstalias@b #2$#1}
+\gdef\lstalias@b#1[#2]#3{\lst@NormedNameDef{lsta@#1}{#3$#2}}
+\gdef\lstalias@@#1#2{\lst@NormedNameDef{lsta@#1}{#2}}
+\lst@Key{defaultdialect}\relax
+    {\lstKV@OptArg[]{#1}{\lst@NormedNameDef{lstdd@##2}{##1}}}
+\gdef\lst@FindAlias[#1]#2{%
+    \lst@NormedDef\lst@oalias{#1}%
+    \lst@NormedDef\lst@malias{#2}%
+    \@ifundefined{lsta@\lst@malias}{}%
+        {\edef\lst@malias{\csname\@lst a@\lst@malias\endcsname}}%
+    \ifx\@empty\lst@oalias \@ifundefined{lstdd@\lst@malias}{}%
+        {\edef\lst@oalias{\csname\@lst dd@\lst@malias\endcsname}}%
+    \fi
+    \edef\lst@temp{\lst@malias $\lst@oalias}%
+    \@ifundefined{lsta@\lst@temp}{}%
+        {\edef\lst@temp{\csname\@lst a@\lst@temp\endcsname}}%
+    \expandafter\lst@FindAlias@\lst@temp $}
+\gdef\lst@FindAlias@#1$#2${%
+    \def\lst@malias{#1}\def\lst@oalias{#2}%
+    \ifx\@empty\lst@oalias \@ifundefined{lstdd@\lst@malias}{}%
+        {\edef\lst@oalias{\csname\@lst dd@\lst@malias\endcsname}}%
+    \fi}
+\gdef\lst@RequireLanguages#1{%
+    \lst@Require{language}{lang}{#1}\lst@FindAlias\lstlanguagefiles
+    \ifx\lst@loadaspects\@empty\else
+        \lst@RequireAspects\lst@loadaspects
+    \fi}
+\global\let\lstloadlanguages\lst@RequireLanguages
+\lst@EndAspect
+\lst@BeginAspect{formats}
+\@ifundefined{lstformatfiles}
+    {\lst@UserCommand\lstformatfiles{lstfmt0.sty}}{}
+\lst@UserCommand\lstdefineformat{\lst@DefFormat\iftrue}
+\lst@UserCommand\lst@defineformat{\lst@DefFormat\iffalse}
+\gdef\lst@DefFormat{\lst@DefDriver{format}{fmt}\lst@UseFormat}
+\lstdefineformat{}{}
+\lst@Key{format}\relax{%
+    \lst@LAS{format}{fmt}{[]{#1}}\lst@NoAlias\lstformatfiles
+        \lsthk@SetFormat
+        {}}
+\lst@AddToHook{SetFormat}{\let\lst@fmtformat\@empty}% init
+\gdef\lst@fmtSplit#1#2{%
+    \def\lst@temp##1#2##2\relax##3{%
+        \ifnum##3=\z@
+            \ifx\@empty##2\@empty
+                \lst@false
+                \let\lst@fmta#1%
+                \let\lst@fmtb\@empty
+            \else
+                \expandafter\lst@temp#1\relax\@ne
+            \fi
+        \else
+            \def\lst@fmta{##1}\def\lst@fmtb{##2}%
+        \fi}%
+    \lst@true
+    \expandafter\lst@temp#1#2\relax\z@}
+\gdef\lst@IfNextCharWhitespace#1#2#3{%
+    \lst@IfSubstring#3\lst@whitespaces{#1}{#2}#3}
+\begingroup
+\catcode`\^^I=12\catcode`\^^J=12\catcode`\^^M=12\catcode`\^^L=12\relax%
+\lst@DefActive\lst@whitespaces{\ ^^I^^J^^M}% add ^^L
+\global\let\lst@whitespaces\lst@whitespaces%
+\endgroup
+\gdef\lst@fmtIfIdentifier#1{%
+    \ifx\relax#1\@empty
+        \expandafter\@secondoftwo
+    \else
+        \expandafter\lst@fmtIfIdentifier@\expandafter#1%
+    \fi}
+\gdef\lst@fmtIfIdentifier@#1#2\relax{%
+    \let\lst@next\@secondoftwo
+    \ifnum`#1=`_\else
+    \ifnum`#1<64\else
+    \ifnum`#1<91\let\lst@next\@firstoftwo\else
+    \ifnum`#1<97\else
+    \ifnum`#1<123\let\lst@next\@firstoftwo\else
+    \fi \fi \fi \fi \fi
+    \lst@next}
+\gdef\lst@fmtIfNextCharIn#1{%
+    \ifx\@empty#1\@empty \expandafter\@secondoftwo \else
+                         \def\lst@next{\lst@fmtIfNextCharIn@{#1}}%
+                         \expandafter\lst@next\fi}
+\gdef\lst@fmtIfNextCharIn@#1#2#3#4{%
+    \def\lst@temp##1#4##2##3\relax{%
+        \ifx \@empty##2\expandafter\@secondoftwo
+                 \else \expandafter\@firstoftwo \fi}%
+    \lst@temp#1#4\@empty\relax{#2}{#3}#4}
+\gdef\lst@fmtCDef#1{\lst@fmtCDef@#1}
+\gdef\lst@fmtCDef@#1#2#3#4#5#6#7{%
+    \lst@CDefIt#1{#2}{#3}%
+               {\lst@fmtIfNextCharIn{#5}{#4#2#3}{#6#4#2#3#7}}%
+               #4%
+               {}{}{}}
+\gdef\lst@fmtCDefX#1{\lst@fmtCDefX@#1}
+\gdef\lst@fmtCDefX@#1#2#3#4#5#6#7{%
+    \let#4#1%
+    \ifx\@empty#2\@empty
+        \def#1{\lst@fmtIfNextCharIn{#5}{#4}{#6#7}}%
+    \else \ifx\@empty#3\@empty
+        \def#1##1{%
+            \ifx##1#2%
+                \def\lst@next{\lst@fmtIfNextCharIn{#5}{#4##1}%
+                                                      {#6#7}}%
+            \else
+                 \def\lst@next{#4##1}%
+            \fi
+            \lst@next}%
+    \else
+        \def#1{%
+            \lst@IfNextCharsArg{#2#3}%
+                {\lst@fmtIfNextCharIn{#5}{\expandafter#4\lst@eaten}%
+                                         {#6#7}}%
+                {\expandafter#4\lst@eaten}}%
+    \fi \fi}
+\gdef\lst@UseFormat#1{%
+    \def\lst@fmtwhole{#1}%
+    \lst@UseFormat@}
+\gdef\lst@UseFormat@{%
+    \lst@fmtSplit\lst@fmtwhole,%
+    \let\lst@fmtwhole\lst@fmtb
+    \ifx\lst@fmta\@empty\else
+        \lst@fmtSplit\lst@fmta=%
+        \ifx\@empty\lst@fmta\else
+            \expandafter\lstKV@XOptArg\expandafter[\expandafter]%
+                \expandafter{\lst@fmtb}\lst@UseFormat@b
+        \fi
+    \fi
+    \ifx\lst@fmtwhole\@empty\else
+        \expandafter\lst@UseFormat@
+    \fi}
+\gdef\lst@UseFormat@b[#1]#2{%
+    \def\lst@fmtc{{#1}}\lst@lExtend\lst@fmtc{\expandafter{\lst@fmta}}%
+    \def\lst@fmtb{#2}%
+    \lst@fmtSplit\lst@fmtb\string
+    \ifx\@empty\lst@fmta
+        \lst@lAddTo\lst@fmtc{{}}%
+    \else
+        \lst@lExtend\lst@fmtc{\expandafter
+            {\expandafter\lst@fmtPre\expandafter{\lst@fmta}}}%
+    \fi
+    \ifx\@empty\lst@fmtb
+        \lst@lAddTo\lst@fmtc{{}}%
+    \else
+        \lst@lExtend\lst@fmtc{\expandafter
+            {\expandafter\lst@fmtPost\expandafter{\lst@fmtb}}}%
+    \fi
+    \expandafter\lst@UseFormat@c\lst@fmtc}
+\gdef\lst@UseFormat@c#1#2#3#4{%
+    \lst@fmtIfIdentifier#2\relax
+    {\lst@fmtIdentifier{#2}%
+     \lst@if\else \PackageWarning{Listings}%
+         {Cannot drop identifier in format definition}%
+     \fi}%
+    {\lst@if
+         \lst@lAddTo\lst@fmtformat{\lst@CArgX#2\relax\lst@fmtCDef}%
+     \else
+         \lst@lAddTo\lst@fmtformat{\lst@CArgX#2\relax\lst@fmtCDefX}%
+     \fi
+     \lst@DefActive\lst@fmtc{#1}%
+     \lst@lExtend\lst@fmtformat{\expandafter{\lst@fmtc}{#3}{#4}}}}
+\lst@AddToHook{SelectCharTable}{\lst@fmtformat}
+\global\let\lst@fmtformat\@empty
+\gdef\lst@fmtPre#1{%
+    \lst@PrintToken
+    \begingroup
+    \let\newline\lst@fmtEnsureNewLine
+    \let\space\lst@fmtEnsureSpace
+    \let\indent\lst@fmtIndent
+    \let\noindent\lst@fmtNoindent
+    #1%
+    \endgroup}
+\gdef\lst@fmtPost#1{%
+    \global\let\lst@fmtPostOutput\@empty
+    \begingroup
+    \def\newline{\lst@AddTo\lst@fmtPostOutput\lst@fmtEnsureNewLine}%
+    \def\space{\aftergroup\lst@fmtEnsurePostSpace}%
+    \def\indent{\lst@AddTo\lst@fmtPostOutput\lst@fmtIndent}%
+    \def\noindent{\lst@AddTo\lst@fmtPostOutput\lst@fmtNoindent}%
+    \aftergroup\lst@PrintToken
+    #1%
+    \endgroup}
+\lst@AddToHook{Init}{\global\let\lst@fmtPostOutput\@empty}
+\lst@AddToHook{PostOutput}
+    {\lst@fmtPostOutput \global\let\lst@fmtPostOutput\@empty}
+\gdef\lst@fmtEnsureSpace{%
+    \lst@ifwhitespace\else \expandafter\lst@ProcessSpace \fi}
+\gdef\lst@fmtEnsurePostSpace{%
+    \lst@IfNextCharWhitespace{}{\lst@ProcessSpace}}
+\lst@Key{fmtindent}{20pt}{\def\lst@fmtindent{#1}}
+\newdimen\lst@fmtcurrindent
+\lst@AddToHook{InitVars}{\global\lst@fmtcurrindent\z@}
+\gdef\lst@fmtIndent{\global\advance\lst@fmtcurrindent\lst@fmtindent}
+\gdef\lst@fmtNoindent{\global\advance\lst@fmtcurrindent-\lst@fmtindent}
+\gdef\lst@fmtEnsureNewLine{%
+    \global\advance\lst@newlines\@ne
+    \global\advance\lst@newlinesensured\@ne
+    \lst@fmtignoretrue}
+\lst@AddToAtTop\lst@DoNewLines{%
+    \ifnum\lst@newlines>\lst@newlinesensured
+        \global\advance\lst@newlines-\lst@newlinesensured
+    \fi
+    \global\lst@newlinesensured\z@}
+\newcount\lst@newlinesensured % global
+\lst@AddToHook{Init}{\global\lst@newlinesensured\z@}
+\gdef\lst@fmtignoretrue{\let\lst@fmtifignore\iftrue}
+\gdef\lst@fmtignorefalse{\let\lst@fmtifignore\iffalse}
+\lst@AddToHook{InitVars}{\lst@fmtignorefalse}
+\lst@AddToHook{Output}{\lst@fmtignorefalse}
+\gdef\lst@fmtUseLostSpace{%
+    \lst@ifnewline \kern\lst@fmtcurrindent \global\lst@lostspace\z@
+    \else
+        \lst@OldOLS
+    \fi}
+\lst@AddToHook{Init}
+    {\lst@true
+     \ifx\lst@fmtformat\@empty \ifx\lst@fmt\@empty \lst@false \fi\fi
+     \lst@if
+        \let\lst@OldOLS\lst@OutputLostSpace
+        \let\lst@OutputLostSpace\lst@fmtUseLostSpace
+        \let\lst@ProcessSpace\lst@fmtProcessSpace
+     \fi}
+\gdef\lst@fmtProcessSpace{%
+    \lst@ifletter
+        \lst@Output
+        \lst@fmtifignore\else
+            \lst@AppendOther\lst@outputspace
+        \fi
+    \else \lst@ifkeepspaces
+        \lst@AppendOther\lst@outputspace
+    \else \ifnum\lst@newlines=\z@
+        \lst@AppendSpecialSpace
+    \else \ifnum\lst@length=\z@
+            \global\advance\lst@lostspace\lst@width
+            \global\advance\lst@pos\m@ne
+        \else
+            \lst@AppendSpecialSpace
+        \fi
+    \fi \fi \fi
+    \lst@whitespacetrue}
+\lst@InstallTest{f}
+    \lst@fmt@list\lst@fmt \lst@gfmt@list\lst@gfmt
+    \lst@gfmt@wp
+    wd
+\gdef\lst@fmt@list{\lst@fmt\lst@gfmt}\global\let\lst@fmt\@empty
+\gdef\lst@gfmt@list{\lst@fmt\lst@gfmt}\global\let\lst@gfmt\@empty
+\gdef\lst@gfmt@wp{%
+    \begingroup \let\lst@UM\@empty
+    \let\lst@PrintToken\@empty
+    \csname\@lst @fmt$\the\lst@token\endcsname
+    \endgroup}
+\gdef\lst@fmtIdentifier#1#2#3#4{%
+    \lst@DefOther\lst@fmta{#2}\edef\lst@fmt{\lst@fmt,\lst@fmta}%
+    \@namedef{\@lst @fmt$\lst@fmta}{#3#4}}
+\lst@EndAspect
+\lst@BeginAspect{labels}
+\lst@Key{numbers}{none}{%
+    \let\lst@PlaceNumber\@empty
+    \lstKV@SwitchCases{#1}%
+    {none&\\%
+     left&\def\lst@PlaceNumber{\llap{\normalfont
+                \lst@numberstyle{\thelstnumber}\kern\lst@numbersep}}\\%
+     right&\def\lst@PlaceNumber{\rlap{\normalfont
+                \kern\linewidth \kern\lst@numbersep
+                \lst@numberstyle{\thelstnumber}}}%
+    }{\PackageError{Listings}{Numbers #1 unknown}\@ehc}}
+\lst@Key{numberstyle}{}{\def\lst@numberstyle{#1}}
+\lst@Key{numbersep}{10pt}{\def\lst@numbersep{#1}}
+\lst@Key{stepnumber}{1}{\def\lst@stepnumber{#1\relax}}
+\lst@AddToHook{EmptyStyle}{\let\lst@stepnumber\@ne}
+\lst@Key{numberblanklines}{true}[t]
+    {\lstKV@SetIf{#1}\lst@ifnumberblanklines}
+\lst@Key{numberfirstline}{f}[t]{\lstKV@SetIf{#1}\lst@ifnumberfirstline}
+\gdef\lst@numberfirstlinefalse{\let\lst@ifnumberfirstline\iffalse}
+\lst@Key{firstnumber}{auto}{%
+    \lstKV@SwitchCases{#1}%
+    {auto&\let\lst@firstnumber\@undefined\\%
+     last&\let\lst@firstnumber\c@lstnumber
+    }{\def\lst@firstnumber{#1\relax}}}
+\lst@AddToHook{PreSet}{\let\lst@advancenumber\z@}
+\lst@AddToHook{PreInit}
+    {\ifx\lst@firstnumber\@undefined
+         \def\lst@firstnumber{\lst@lineno}%
+     \fi}
+\gdef\lst@SetFirstNumber{%
+    \ifx\lst@firstnumber\@undefined
+        \@tempcnta 0\csname\@lst no@\lst@intname\endcsname\relax
+        \ifnum\@tempcnta=\z@ \@tempcnta\lst@firstline
+                       \else \lst@nololtrue \fi
+        \advance\@tempcnta\lst@advancenumber
+        \edef\lst@firstnumber{\the\@tempcnta\relax}%
+    \fi}
+\gdef\lst@SaveFirstNumber{%
+    \expandafter\xdef
+        \csname\@lst no\ifx\lst@intname\@empty @ \else @\lst@intname\fi
+        \endcsname{\the\c@lstnumber}}
+\newcounter{lstnumber}% \global
+\global\c@lstnumber\@ne % init
+\renewcommand*\thelstnumber{\@arabic\c@lstnumber}
+\lst@AddToHook{EveryPar}
+    {\global\advance\c@lstnumber\lst@advancelstnum
+     \global\advance\c@lstnumber\m@ne \refstepcounter{lstnumber}%
+     \lst@SkipOrPrintLabel}%
+\global\let\lst@advancelstnum\@ne
+\lst@AddToHook{Init}{\def\@currentlabel{\thelstnumber}}
+\lst@AddToHook{InitVars}
+    {\global\c@lstnumber\lst@firstnumber
+     \global\advance\c@lstnumber\lst@advancenumber
+     \global\advance\c@lstnumber-\lst@advancelstnum}
+\lst@AddToHook{ExitVars}
+    {\global\advance\c@lstnumber\lst@advancelstnum}
+\AtBeginDocument{%
+    \def\theHlstnumber{\ifx\lst@@caption\@empty \lst@neglisting
+                                          \else \thelstlisting \fi
+                       .\thelstnumber}}
+\newcount\lst@skipnumbers % \global
+\lst@AddToHook{Init}
+    {\ifnum \z@>\lst@stepnumber
+         \let\lst@advancelstnum\m@ne
+         \edef\lst@stepnumber{-\lst@stepnumber}%
+     \fi
+     \ifnum \z@<\lst@stepnumber
+         \global\lst@skipnumbers\lst@firstnumber
+         \global\divide\lst@skipnumbers\lst@stepnumber
+         \global\multiply\lst@skipnumbers-\lst@stepnumber
+         \global\advance\lst@skipnumbers\lst@firstnumber
+         \ifnum\lst@skipnumbers>\z@
+             \global\advance\lst@skipnumbers -\lst@stepnumber
+         \fi
+     \else
+         \let\lst@SkipOrPrintLabel\relax
+     \fi}
+\gdef\lst@SkipOrPrintLabel{%
+    \ifnum\lst@skipnumbers=\z@
+        \global\advance\lst@skipnumbers-\lst@stepnumber\relax
+        \lst@PlaceNumber
+        \lst@numberfirstlinefalse
+    \else
+        \lst@ifnumberfirstline
+            \lst@PlaceNumber
+            \lst@numberfirstlinefalse
+        \fi
+    \fi
+    \global\advance\lst@skipnumbers\@ne}%
+\lst@AddToHook{OnEmptyLine}{%
+    \lst@ifnumberblanklines\else \ifnum\lst@skipnumbers=\z@
+        \global\advance\lst@skipnumbers-\lst@stepnumber\relax
+    \fi\fi}
+\lst@EndAspect
+\lst@BeginAspect{lineshape}
+\lst@Key{xleftmargin}{\z@}{\def\lst@xleftmargin{#1}}
+\lst@Key{xrightmargin}{\z@}{\def\lst@xrightmargin{#1}}
+\lst@Key{resetmargins}{false}[t]{\lstKV@SetIf{#1}\lst@ifresetmargins}
+\lst@AddToHook{BoxUnsafe}{\let\lst@xleftmargin\z@
+                          \let\lst@xrightmargin\z@}
+\lst@AddToHook{TextStyle}{%
+    \let\lst@xleftmargin\z@ \let\lst@xrightmargin\z@
+    \let\lst@ifresetmargins\iftrue}
+\lst@Key{linewidth}\linewidth{\def\lst@linewidth{#1}}
+\lst@AddToHook{PreInit}{\linewidth\lst@linewidth\relax}
+\gdef\lst@parshape{%
+    \parshape\@ne \@totalleftmargin \linewidth}
+\lst@AddToHook{Init}
+    {\lst@ifresetmargins
+         \advance\linewidth\@totalleftmargin
+         \advance\linewidth\rightmargin
+         \@totalleftmargin\z@
+     \fi
+     \advance\linewidth-\lst@xleftmargin
+     \advance\linewidth-\lst@xrightmargin
+     \advance\@totalleftmargin\lst@xleftmargin\relax}
+\lst@Key{lineskip}{\z@}{\def\lst@lineskip{#1\relax}}
+\lst@AddToHook{Init}
+    {\parskip\z@
+     \ifdim\z@=\lst@lineskip\else
+         \@tempdima\baselineskip
+         \advance\@tempdima\lst@lineskip
+         \multiply\@tempdima\@cclvi
+         \divide\@tempdima\baselineskip\relax
+         \multiply\@tempdima\@cclvi
+         \edef\baselinestretch{\strip@pt\@tempdima}%
+         \selectfont
+     \fi}
+\lst@Key{breaklines}{false}[t]{\lstKV@SetIf{#1}\lst@ifbreaklines}
+\lst@Key{breakindent}{20pt}{\def\lst@breakindent{#1}}
+\lst@Key{breakautoindent}{t}[t]{\lstKV@SetIf{#1}\lst@ifbreakautoindent}
+\lst@Key{breakatwhitespace}{false}[t]%
+    {\lstKV@SetIf{#1}\lst@ifbreakatwhitespace}
+\lst@Key{prebreak}{}{\def\lst@prebreak{#1}}
+\lst@Key{postbreak}{}{\def\lst@postbreak{#1}}
+\lst@AddToHook{Init}
+    {\lst@ifbreaklines
+         \hbadness\@M \pretolerance\@M
+         \@rightskip\@flushglue \rightskip\@rightskip % \raggedright
+         \leftskip\z@skip \parindent\z@
+         \def\lst@parshape{\parshape\tw@ \@totalleftmargin\linewidth
+                           \lst@breakshape}%
+     \else
+         \let\lst@discretionary\@empty
+     \fi}
+\lst@AddToHook{OnNewLine}
+    {\lst@ifbreaklines \lst@breakNewLine \fi}
+\gdef\lst@discretionary{%
+    \lst@ifbreakatwhitespace
+        \lst@ifwhitespace \lst@@discretionary \fi
+    \else
+        \lst@@discretionary
+    \fi}%
+\gdef\lst@@discretionary{%
+    \discretionary{\let\space\lst@spacekern\lst@prebreak}%
+                  {\llap{\lsthk@EveryLine
+                   \kern\lst@breakcurrindent \kern-\@totalleftmargin}%
+                   \let\space\lst@spacekern\lst@postbreak}{}}
+\lst@AddToHook{PostOutput}{\lst@discretionary}
+\gdef\lst@spacekern{\kern\lst@width}
+\gdef\lst@breakNewLine{%
+    \@tempdima\lst@breakindent\relax
+    \lst@ifbreakautoindent \advance\@tempdima\lst@lostspace \fi
+    \@tempdimc-\@tempdima \advance\@tempdimc\linewidth
+                          \advance\@tempdima\@totalleftmargin
+    \xdef\lst@breakshape{\noexpand\lst@breakcurrindent \the\@tempdimc}%
+    \xdef\lst@breakcurrindent{\the\@tempdima}}
+\global\let\lst@breakcurrindent\z@ % init
+\gdef\lst@breakshape{\@totalleftmargin \linewidth}
+\gdef\lst@breakProcessOther#1{\lst@ProcessOther#1\lst@OutputOther}
+\lst@AddToHook{SelectCharTable}
+    {\lst@ifbreaklines \lst@Def{`)}{\lst@breakProcessOther)}\fi}
+\lst@EndAspect
+\lst@BeginAspect[lineshape]{frames}
+\lst@Key{framexleftmargin}{\z@}{\def\lst@framexleftmargin{#1}}
+\lst@Key{framexrightmargin}{\z@}{\def\lst@framexrightmargin{#1}}
+\lst@Key{framextopmargin}{\z@}{\def\lst@framextopmargin{#1}}
+\lst@Key{framexbottommargin}{\z@}{\def\lst@framexbottommargin{#1}}
+\lst@Key{backgroundcolor}{}{\def\lst@bkgcolor{#1}}
+\lst@Key{fillcolor}{}{\def\lst@fillcolor{#1}}
+\lst@Key{rulecolor}{}{\def\lst@rulecolor{#1}}
+\lst@Key{rulesepcolor}{}{\def\lst@rulesepcolor{#1}}
+\lst@AddToHook{Init}{%
+    \ifx\lst@fillcolor\@empty
+        \let\lst@fillcolor\lst@bkgcolor
+    \fi
+    \ifx\lst@rulesepcolor\@empty
+        \let\lst@rulesepcolor\lst@fillcolor
+    \fi}
+\lst@Key{rulesep}{2pt}{\def\lst@rulesep{#1}}
+\lst@Key{framerule}{.4pt}{\def\lst@framerulewidth{#1}}
+\lst@Key{framesep}{3pt}{\def\lst@frametextsep{#1}}
+\lst@Key{frameshape}{}{%
+    \let\lst@xrulecolor\@empty
+    \lstKV@FourArg{#1}%
+    {\uppercase{\def\lst@frametshape{##1}}%
+     \uppercase{\def\lst@framelshape{##2}}%
+     \uppercase{\def\lst@framershape{##3}}%
+     \uppercase{\def\lst@framebshape{##4}}%
+     \let\lst@ifframeround\iffalse
+     \lst@IfSubstring R\lst@frametshape{\let\lst@ifframeround\iftrue}{}%
+     \lst@IfSubstring R\lst@framebshape{\let\lst@ifframeround\iftrue}{}%
+     \def\lst@frame{##1##2##3##4}}}
+\lst@Key{frameround}\relax
+    {\uppercase{\def\lst@frameround{#1}}%
+     \expandafter\lstframe@\lst@frameround ffff\relax}
+\global\let\lst@frameround\@empty
+\lst@Key{frame}\relax{%
+    \let\lst@xrulecolor\@empty
+    \lstKV@SwitchCases{#1}%
+    {none&\let\lst@frame\@empty\\%
+     leftline&\def\lst@frame{l}\\%
+     topline&\def\lst@frame{t}\\%
+     bottomline&\def\lst@frame{b}\\%
+     lines&\def\lst@frame{tb}\\%
+     single&\def\lst@frame{trbl}\\%
+     shadowbox&\def\lst@frame{tRBl}%
+            \def\lst@xrulecolor{\lst@rulesepcolor}%
+            \def\lst@rulesep{\lst@frametextsep}%
+    }{\def\lst@frame{#1}}%
+    \expandafter\lstframe@\lst@frameround ffff\relax}
+\gdef\lstframe@#1#2#3#4#5\relax{%
+    \lst@IfSubstring T\lst@frame{\edef\lst@frame{t\lst@frame}}{}%
+    \lst@IfSubstring R\lst@frame{\edef\lst@frame{r\lst@frame}}{}%
+    \lst@IfSubstring B\lst@frame{\edef\lst@frame{b\lst@frame}}{}%
+    \lst@IfSubstring L\lst@frame{\edef\lst@frame{l\lst@frame}}{}%
+    \let\lst@frametshape\@empty \let\lst@framebshape\@empty
+    \lst@frameCheck
+        ltr\lst@framelshape\lst@frametshape\lst@framershape #4#1%
+    \lst@frameCheck
+        LTR\lst@framelshape\lst@frametshape\lst@framershape #4#1%
+    \lst@frameCheck
+        lbr\lst@framelshape\lst@framebshape\lst@framershape #3#2%
+    \lst@frameCheck
+        LBR\lst@framelshape\lst@framebshape\lst@framershape #3#2%
+    \let\lst@ifframeround\iffalse
+    \lst@IfSubstring R\lst@frametshape{\let\lst@ifframeround\iftrue}{}%
+    \lst@IfSubstring R\lst@framebshape{\let\lst@ifframeround\iftrue}{}%
+    \let\lst@framelshape\@empty \let\lst@framershape\@empty
+    \lst@IfSubstring L\lst@frame
+        {\def\lst@framelshape{YY}}%
+        {\lst@IfSubstring l\lst@frame{\def\lst@framelshape{Y}}{}}%
+    \lst@IfSubstring R\lst@frame
+        {\def\lst@framershape{YY}}%
+        {\lst@IfSubstring r\lst@frame{\def\lst@framershape{Y}}{}}}
+\gdef\lst@frameCheck#1#2#3#4#5#6#7#8{%
+    \lst@IfSubstring #1\lst@frame
+        {\if #7T\def#4{R}\else \def#4{Y}\fi}%
+        {\def#4{N}}%
+    \lst@IfSubstring #3\lst@frame
+        {\if #8T\def#6{R}\else \def#6{Y}\fi}%
+        {\def#6{N}}%
+    \lst@IfSubstring #2\lst@frame{\edef#5{#5#4Y#6}}{}}
+\lst@AddToHook{TextStyle}
+   {\let\lst@frame\@empty
+    \let\lst@frametshape\@empty
+    \let\lst@framershape\@empty
+    \let\lst@framebshape\@empty
+    \let\lst@framelshape\@empty
+    \let\lst@bkgcolor\@empty}
+\gdef\lst@frameMakeBoxV#1#2#3{%
+    \setbox#1\hbox{%
+      \color@begingroup \lst@rulecolor
+      \ifx\lst@framelshape\@empty
+      \else
+            \llap{%
+                \lst@frameBlock\lst@fillcolor\lst@frametextsep{#2}{#3}%
+                \kern\lst@framexleftmargin}%
+      \fi
+      \llap{\setbox\z@\hbox{\vrule\@width\z@\@height#2\@depth#3%
+                            \lst@frameL}%
+            \rlap{\lst@frameBlock\lst@rulesepcolor{\wd\z@}%
+                                                  {\ht\z@}{\dp\z@}}%
+            \box\z@
+            \kern\lst@frametextsep\relax
+            \kern\lst@framexleftmargin}%
+      \rlap{\kern-\lst@framexleftmargin
+                    \@tempdima\linewidth
+            \advance\@tempdima\lst@framexleftmargin
+            \advance\@tempdima\lst@framexrightmargin
+            \lst@frameBlock\lst@bkgcolor\@tempdima{#2}{#3}%
+            \ifx\lst@framershape\@empty
+                \kern\lst@frametextsep\relax
+            \else
+                \lst@frameBlock\lst@fillcolor\lst@frametextsep{#2}{#3}%
+            \fi
+            \setbox\z@\hbox{\vrule\@width\z@\@height#2\@depth#3%
+                            \lst@frameR}%
+            \rlap{\lst@frameBlock\lst@rulesepcolor{\wd\z@}%
+                                                  {\ht\z@}{\dp\z@}}%
+            \box\z@}%
+      \color@endgroup}}
+\gdef\lst@frameBlock#1#2#3#4{%
+    \color@begingroup
+      #1%
+      \setbox\z@\hbox{\vrule\@height#3\@depth#4%
+                      \ifx#1\@empty \@width\z@ \kern#2\relax
+                              \else \@width#2\relax \fi}%
+      \box\z@
+    \color@endgroup}
+\gdef\lst@frameR{%
+    \expandafter\lst@frameR@\lst@framershape\relax
+    \kern-\lst@rulesep}
+\gdef\lst@frameR@#1{%
+    \ifx\relax#1\@empty\else
+        \if #1Y\lst@framevrule \else \kern\lst@framerulewidth \fi
+        \kern\lst@rulesep
+        \expandafter\lst@frameR@b
+    \fi}
+\gdef\lst@frameR@b#1{%
+    \ifx\relax#1\@empty
+    \else
+        \if #1Y\color@begingroup
+               \lst@xrulecolor
+               \lst@framevrule
+               \color@endgroup
+        \else
+               \kern\lst@framerulewidth
+        \fi
+        \kern\lst@rulesep
+        \expandafter\lst@frameR@
+    \fi}
+\gdef\lst@frameL{%
+    \kern-\lst@rulesep
+    \expandafter\lst@frameL@\lst@framelshape\relax}
+\gdef\lst@frameL@#1{%
+    \ifx\relax#1\@empty\else
+        \kern\lst@rulesep
+        \if#1Y\lst@framevrule \else \kern\lst@framerulewidth \fi
+        \expandafter\lst@frameL@
+    \fi}
+\gdef\lst@frameH#1#2{%
+    \global\let\lst@framediml\z@ \global\let\lst@framedimr\z@
+    \setbox\z@\hbox{}\@tempcntb\z@
+    \expandafter\lst@frameH@\expandafter#1#2\relax\relax\relax
+            \@tempdimb\lst@frametextsep\relax
+    \advance\@tempdimb\lst@framerulewidth\relax
+            \@tempdimc-\@tempdimb
+    \advance\@tempdimc\ht\z@
+    \advance\@tempdimc\dp\z@
+    \setbox\z@=\hbox{%
+      \lst@frameHBkg\lst@fillcolor\@tempdimb\@firstoftwo
+      \if#1T\rlap{\raise\dp\@tempboxa\box\@tempboxa}%
+       \else\rlap{\lower\ht\@tempboxa\box\@tempboxa}\fi
+      \lst@frameHBkg\lst@rulesepcolor\@tempdimc\@secondoftwo
+      \advance\@tempdimb\ht\@tempboxa
+      \if#1T\rlap{\raise\lst@frametextsep\box\@tempboxa}%
+       \else\rlap{\lower\@tempdimb\box\@tempboxa}\fi
+      \rlap{\box\z@}%
+    }}
+\gdef\lst@frameH@#1#2#3#4{%
+    \ifx\relax#4\@empty\else
+        \lst@frameh \@tempcntb#1#2#3#4%
+        \advance\@tempcntb\@ne
+        \expandafter\lst@frameH@\expandafter#1%
+    \fi}
+\gdef\lst@frameHBkg#1#2#3{%
+    \setbox\@tempboxa\hbox{%
+        \kern-\lst@framexleftmargin
+        #3{\kern-\lst@framediml\relax}{\@tempdima\z@}%
+        \ifdim\lst@framediml>\@tempdimb
+            #3{\@tempdima\lst@framediml \advance\@tempdima-\@tempdimb
+               \lst@frameBlock\lst@rulesepcolor\@tempdima\@tempdimb\z@}%
+              {\kern-\lst@framediml
+               \advance\@tempdima\lst@framediml\relax}%
+        \fi
+        #3{\@tempdima\z@
+           \ifx\lst@framelshape\@empty\else
+               \advance\@tempdima\@tempdimb
+           \fi
+           \ifx\lst@framershape\@empty\else
+               \advance\@tempdima\@tempdimb
+           \fi}%
+          {\ifdim\lst@framedimr>\@tempdimb
+              \advance\@tempdima\lst@framedimr\relax
+           \fi}%
+        \advance\@tempdima\linewidth
+        \advance\@tempdima\lst@framexleftmargin
+        \advance\@tempdima\lst@framexrightmargin
+        \lst@frameBlock#1\@tempdima#2\z@
+        #3{\ifdim\lst@framedimr>\@tempdimb
+               \@tempdima-\@tempdimb
+               \advance\@tempdima\lst@framedimr\relax
+               \lst@frameBlock\lst@rulesepcolor\@tempdima\@tempdimb\z@
+           \fi}{}%
+        }}
+\gdef\lst@frameh#1#2#3#4#5{%
+    \lst@frameCalcDimA#1%
+    \lst@ifframeround \@getcirc\@tempdima \fi
+    \setbox\z@\hbox{%
+      \begingroup
+      \setbox\z@\hbox{%
+        \kern-\lst@framexleftmargin
+        \color@begingroup
+        \ifnum#1=\z@ \lst@rulecolor \else \lst@xrulecolor \fi
+        \lst@frameCornerX\llap{#2L}#3#1%
+        \ifdim\lst@framediml<\@tempdimb
+            \xdef\lst@framediml{\the\@tempdimb}%
+        \fi
+        \begingroup
+        \if#4Y\else \let\lst@framerulewidth\z@ \fi
+                \@tempdima\lst@framexleftmargin
+        \advance\@tempdima\lst@framexrightmargin
+        \advance\@tempdima\linewidth
+        \vrule\@width\@tempdima\@height\lst@framerulewidth \@depth\z@
+        \endgroup
+        \lst@frameCornerX\rlap{#2R}#5#1%
+        \ifdim\lst@framedimr<\@tempdimb
+            \xdef\lst@framedimr{\the\@tempdimb}%
+        \fi
+        \color@endgroup}%
+      \if#2T\rlap{\raise\dp\z@\box\z@}%
+       \else\rlap{\lower\ht\z@\box\z@}\fi
+      \endgroup
+      \box\z@}}
+\gdef\lst@frameCornerX#1#2#3#4{%
+    \setbox\@tempboxa\hbox{\csname\@lst @frame\if#3RR\fi #2\endcsname}%
+    \@tempdimb\wd\@tempboxa
+    \if #3R%
+        #1{\box\@tempboxa}%
+    \else
+        \if #3Y\expandafter#1\else
+               \@tempdimb\z@ \expandafter\vphantom \fi
+        {\box\@tempboxa}%
+    \fi}
+\gdef\lst@frameCalcDimA#1{%
+            \@tempdima\lst@rulesep
+    \advance\@tempdima\lst@framerulewidth
+    \multiply\@tempdima#1\relax
+    \advance\@tempdima\lst@frametextsep
+    \advance\@tempdima\lst@framerulewidth
+    \multiply\@tempdima\tw@}
+\lst@AddToHook{Init}{\lst@frameInit}
+\newbox\lst@framebox
+\gdef\lst@frameInit{%
+    \ifx\lst@framelshape\@empty \let\lst@frameL\@empty \fi
+    \ifx\lst@framershape\@empty \let\lst@frameR\@empty \fi
+    \def\lst@framevrule{\vrule\@width\lst@framerulewidth\relax}%
+    \lst@ifframeround
+        \lst@frameCalcDimA\z@ \@getcirc\@tempdima
+        \@tempdimb\@tempdima \divide\@tempdimb\tw@
+        \advance\@tempdimb -\@wholewidth
+        \edef\lst@frametextsep{\the\@tempdimb}%
+        \edef\lst@framerulewidth{\the\@wholewidth}%
+        \lst@frameCalcDimA\@ne \@getcirc\@tempdima
+        \@tempdimb\@tempdima \divide\@tempdimb\tw@
+        \advance\@tempdimb -\tw@\@wholewidth
+        \advance\@tempdimb -\lst@frametextsep
+        \edef\lst@rulesep{\the\@tempdimb}%
+    \fi
+    \lst@frameMakeBoxV\lst@framebox{\ht\strutbox}{\dp\strutbox}%
+    \def\lst@framelr{\copy\lst@framebox}%
+    \ifx\lst@frametshape\@empty\else
+        \lst@frameH T\lst@frametshape
+        \ifvoid\z@\else
+            \par\lst@parshape
+            \@tempdima-\baselineskip \advance\@tempdima\ht\z@
+            \ifdim\prevdepth<\@cclvi\p@\else
+                \advance\@tempdima\prevdepth
+            \fi
+            \ifdim\@tempdima<\z@
+                \vskip\@tempdima\vskip\lineskip
+            \fi
+            \noindent\box\z@\par
+            \lineskiplimit\maxdimen \lineskip\z@
+        \fi
+        \lst@frameSpreadV\lst@framextopmargin
+    \fi}
+\lst@AddToHook{EveryLine}{\lst@framelr}
+\global\let\lst@framelr\@empty
+\lst@AddToHook{DeInit}
+    {\ifx\lst@framebshape\@empty\else \lst@frameExit \fi}
+\gdef\lst@frameExit{%
+    \lst@frameSpreadV\lst@framexbottommargin
+    \lst@frameH B\lst@framebshape
+    \ifvoid\z@\else
+        \everypar{}\par\lst@parshape\nointerlineskip\noindent\box\z@
+    \fi}
+\gdef\lst@frameSpreadV#1{%
+    \ifdim\z@=#1\else
+        \everypar{}\par\lst@parshape\nointerlineskip\noindent
+        \lst@frameMakeBoxV\z@{#1}{\z@}%
+        \box\z@
+    \fi}
+\gdef\lst@frameTR{%
+    \vrule\@width.5\@tempdima\@height\lst@framerulewidth\@depth\z@
+    \kern-\lst@framerulewidth
+    \raise\lst@framerulewidth\hbox{%
+        \vrule\@width\lst@framerulewidth\@height\z@\@depth.5\@tempdima}}
+\gdef\lst@frameBR{%
+    \vrule\@width.5\@tempdima\@height\lst@framerulewidth\@depth\z@
+    \kern-\lst@framerulewidth
+    \vrule\@width\lst@framerulewidth\@height.5\@tempdima\@depth\z@}
+\gdef\lst@frameBL{%
+    \vrule\@width\lst@framerulewidth\@height.5\@tempdima\@depth\z@
+    \kern-\lst@framerulewidth
+    \vrule\@width.5\@tempdima\@height\lst@framerulewidth\@depth\z@}
+\gdef\lst@frameTL{%
+    \raise\lst@framerulewidth\hbox{%
+        \vrule\@width\lst@framerulewidth\@height\z@\@depth.5\@tempdima}%
+    \kern-\lst@framerulewidth
+    \vrule\@width.5\@tempdima\@height\lst@framerulewidth\@depth\z@}
+\gdef\lst@frameRoundT{%
+    \setbox\@tempboxa\hbox{\@circlefnt\char\@tempcnta}%
+    \ht\@tempboxa\lst@framerulewidth
+    \box\@tempboxa}
+\gdef\lst@frameRoundB{%
+    \setbox\@tempboxa\hbox{\@circlefnt\char\@tempcnta}%
+    \dp\@tempboxa\z@
+    \box\@tempboxa}
+\gdef\lst@frameRTR{%
+    \hb@xt@.5\@tempdima{\kern-\lst@framerulewidth
+                           \kern.5\@tempdima \lst@frameRoundT \hss}}
+\gdef\lst@frameRBR{%
+    \hb@xt@.5\@tempdima{\kern-\lst@framerulewidth
+    \advance\@tempcnta\@ne \kern.5\@tempdima \lst@frameRoundB \hss}}
+\gdef\lst@frameRBL{%
+    \advance\@tempcnta\tw@ \lst@frameRoundB
+    \kern-.5\@tempdima}
+\gdef\lst@frameRTL{%
+    \advance\@tempcnta\thr@@\lst@frameRoundT
+    \kern-.5\@tempdima}
+\lst@EndAspect
+\lst@BeginAspect[keywords]{make}
+\lst@NewMode\lst@makemode
+\lst@AddToHook{Output}{%
+    \ifnum\lst@mode=\lst@makemode
+        \ifx\lst@thestyle\lst@gkeywords@sty
+            \lst@makekeytrue
+        \fi
+    \fi}
+\gdef\lst@makekeytrue{\let\lst@ifmakekey\iftrue}
+\gdef\lst@makekeyfalse{\let\lst@ifmakekey\iffalse}
+\global\lst@makekeyfalse % init
+\lst@Key{makemacrouse}f[t]{\lstKV@SetIf{#1}\lst@ifmakemacrouse}
+\gdef\lst@MakeSCT{%
+    \lst@ifmakemacrouse
+        \lst@ReplaceInput{$(}{%
+            \lst@PrintToken
+            \lst@EnterMode\lst@makemode{\lst@makekeyfalse}%
+            \lst@Merge{\lst@ProcessOther\$\lst@ProcessOther(}}%
+        \lst@ReplaceInput{)}{%
+            \ifnum\lst@mode=\lst@makemode
+                \lst@PrintToken
+                \begingroup
+                    \lst@ProcessOther)%
+                    \lst@ifmakekey
+                        \let\lst@currstyle\lst@gkeywords@sty
+                    \fi
+                    \lst@OutputOther
+                \endgroup
+                \lst@LeaveMode
+            \else
+                \expandafter\lst@ProcessOther\expandafter)%
+            \fi}%
+    \else
+        \lst@ReplaceInput{$(}{\lst@ProcessOther\$\lst@ProcessOther(}%
+    \fi}
+\lst@EndAspect
+\lst@BeginAspect{0.21}
+\lst@Key{labelstyle}{}{\def\lst@numberstyle{#1}}
+\lst@Key{labelsep}{10pt}{\def\lst@numbersep{#1}}
+\lst@Key{labelstep}{0}{%
+    \ifnum #1=\z@ \KV@lst@numbers{none}%
+            \else \KV@lst@numbers{left}\fi
+    \def\lst@stepnumber{#1\relax}}
+\lst@Key{firstlabel}\relax{\def\lst@firstnumber{#1\relax}}
+\lst@Key{advancelabel}\relax{\def\lst@advancenumber{#1\relax}}
+\let\c@lstlabel\c@lstnumber
+\lst@AddToHook{Init}{\def\thelstnumber{\thelstlabel}}
+\newcommand*\thelstlabel{\@arabic\c@lstlabel}
+\lst@Key{first}\relax{\def\lst@firstline{#1\relax}}
+\lst@Key{last}\relax{\def\lst@lastline{#1\relax}}
+\lst@Key{framerulewidth}{.4pt}{\def\lst@framerulewidth{#1}}
+\lst@Key{framerulesep}{2pt}{\def\lst@rulesep{#1}}
+\lst@Key{frametextsep}{3pt}{\def\lst@frametextsep{#1}}
+\lst@Key{framerulecolor}{}{\lstKV@OptArg[]{#1}%
+    {\ifx\@empty##2\@empty
+         \let\lst@rulecolor\@empty
+     \else
+         \ifx\@empty##1\@empty
+             \def\lst@rulecolor{\color{##2}}%
+         \else
+             \def\lst@rulecolor{\color[##1]{##2}}%
+         \fi
+     \fi}}
+\lst@Key{backgroundcolor}{}{\lstKV@OptArg[]{#1}%
+    {\ifx\@empty##2\@empty
+         \let\lst@bkgcolor\@empty
+     \else
+         \ifx\@empty##1\@empty
+             \def\lst@bkgcolor{\color{##2}}%
+         \else
+             \def\lst@bkgcolor{\color[##1]{##2}}%
+         \fi
+     \fi}}
+\lst@Key{framespread}{\z@}{\def\lst@framespread{#1}}
+\lst@AddToHook{PreInit}
+    {\@tempdima\lst@framespread\relax \divide\@tempdima\tw@
+     \edef\lst@framextopmargin{\the\@tempdima}%
+     \let\lst@framexrightmargin\lst@framextopmargin
+     \let\lst@framexbottommargin\lst@framextopmargin
+     \advance\@tempdima\lst@xleftmargin\relax
+     \edef\lst@framexleftmargin{\the\@tempdima}}
+\newdimen\lst@innerspread \newdimen\lst@outerspread
+\lst@Key{spread}{\z@,\z@}{\lstKV@CSTwoArg{#1}%
+    {\lst@innerspread##1\relax
+     \ifx\@empty##2\@empty
+         \divide\lst@innerspread\tw@\relax
+         \lst@outerspread\lst@innerspread
+     \else
+         \lst@outerspread##2\relax
+     \fi}}
+\lst@AddToHook{BoxUnsafe}{\lst@outerspread\z@ \lst@innerspread\z@}
+\lst@Key{wholeline}{false}[t]{\lstKV@SetIf{#1}\lst@ifresetmargins}
+\lst@Key{indent}{\z@}{\def\lst@xleftmargin{#1}}
+\lst@AddToHook{PreInit}
+    {\lst@innerspread=-\lst@innerspread
+     \lst@outerspread=-\lst@outerspread
+     \ifodd\c@page \advance\lst@innerspread\lst@xleftmargin
+             \else \advance\lst@outerspread\lst@xleftmargin \fi
+     \ifodd\c@page
+         \edef\lst@xleftmargin{\the\lst@innerspread}%
+         \edef\lst@xrightmargin{\the\lst@outerspread}%
+     \else
+         \edef\lst@xleftmargin{\the\lst@outerspread}%
+         \edef\lst@xrightmargin{\the\lst@innerspread}%
+     \fi}
+\lst@Key{defaultclass}\relax{\def\lst@classoffset{#1}}
+\lst@Key{stringtest}\relax{}% dummy
+\lst@Key{outputpos}\relax{\lst@outputpos#1\relax\relax}
+\lst@Key{stringspaces}\relax[t]{\lstKV@SetIf{#1}\lst@ifshowstringspaces}
+\lst@Key{visiblespaces}\relax[t]{\lstKV@SetIf{#1}\lst@ifshowspaces}
+\lst@Key{visibletabs}\relax[t]{\lstKV@SetIf{#1}\lst@ifshowtabs}
+\lst@EndAspect
+\lst@BeginAspect{fancyvrb}
+\@ifundefined{FancyVerbFormatLine}
+    {\typeout{^^J%
+     ***^^J%
+     *** `listings.sty' needs `fancyvrb.sty' right now.^^J%
+     *** Please ensure its availability and try again.^^J%
+     ***^^J}%
+     \batchmode \@@end}{}
+\gdef\lstFV@fancyvrb{%
+    \lst@iffancyvrb
+        \ifx\FancyVerbFormatLine\lstFV@FancyVerbFormatLine\else
+            \let\lstFV@FVFL\FancyVerbFormatLine
+            \let\FancyVerbFormatLine\lstFV@FancyVerbFormatLine
+        \fi
+    \else
+        \ifx\lstFV@FVFL\@undefined\else
+            \let\FancyVerbFormatLine\lstFV@FVFL
+            \let\lstFV@FVFL\@undefined
+        \fi
+    \fi}
+\gdef\lstFV@VerbatimBegin{%
+    \ifx\FancyVerbFormatLine\lstFV@FancyVerbFormatLine
+        \lsthk@TextStyle \lsthk@BoxUnsafe
+        \lsthk@PreSet
+        \lst@activecharsfalse
+        \let\normalbaselines\relax
+\xdef\lstFV@RestoreData{\noexpand\linewidth\the\linewidth\relax}%
+        \lst@Init\relax
+        \lst@ifresetmargins \advance\linewidth-\@totalleftmargin \fi
+\lstFV@RestoreData
+        \everypar{}\global\lst@newlines\z@
+        \lst@mode\lst@nomode \let\lst@entermodes\@empty
+        \lst@InterruptModes
+%% D.G. modification begin - Nov. 25, 1998
+        \let\@noligs\relax
+%% D.G. modification end
+    \fi}
+\gdef\lstFV@VerbatimEnd{%
+    \ifx\FancyVerbFormatLine\lstFV@FancyVerbFormatLine
+        \global\setbox\lstFV@gtempboxa\box\@tempboxa
+        \global\let\@gtempa\FV@ProcessLine
+        \lst@mode\lst@Pmode
+        \lst@DeInit
+        \let\FV@ProcessLine\@gtempa
+        \setbox\@tempboxa\box\lstFV@gtempboxa
+        \par
+    \fi}
+\newbox\lstFV@gtempboxa
+\lst@AddTo\FV@VerbatimBegin\lstFV@VerbatimBegin
+\lst@AddToAtTop\FV@VerbatimEnd\lstFV@VerbatimEnd
+\lst@AddTo\FV@LVerbatimBegin\lstFV@VerbatimBegin
+\lst@AddToAtTop\FV@LVerbatimEnd\lstFV@VerbatimEnd
+\lst@AddTo\FV@BVerbatimBegin\lstFV@VerbatimBegin
+\lst@AddToAtTop\FV@BVerbatimEnd\lstFV@VerbatimEnd
+\gdef\lstFV@FancyVerbFormatLine#1{%
+    \let\lst@arg\@empty \lst@FVConvert#1\@nil
+    \global\lst@newlines\z@
+    \vtop{\noindent\lst@parshape
+          \lst@ReenterModes
+          \lst@arg \lst@PrintToken\lst@EOLUpdate\lsthk@InitVarsBOL
+          \lst@InterruptModes}}
+\lst@Key{fvcmdparams}%
+    {\overlay\@ne}%
+    {\def\lst@FVcmdparams{,#1}}
+\lst@Key{morefvcmdparams}\relax{\lst@lAddTo\lst@FVcmdparams{,#1}}
+\gdef\lst@FVConvert{\@tempcnta\z@ \lst@FVConvertO@}%
+\gdef\lst@FVConvertO@{%
+    \ifcase\@tempcnta
+        \expandafter\futurelet\expandafter\@let@token
+        \expandafter\lst@FVConvert@@
+    \else
+        \expandafter\lst@FVConvertO@a
+    \fi}
+\gdef\lst@FVConvertO@a#1{%
+    \lst@lAddTo\lst@arg{{#1}}\advance\@tempcnta\m@ne
+    \lst@FVConvertO@}%
+\gdef\lst@FVConvert@@{%
+    \ifcat\noexpand\@let@token\bgroup \expandafter\lst@FVConvertArg
+                                \else \expandafter\lst@FVConvert@ \fi}
+\gdef\lst@FVConvertArg#1{%
+    {\let\lst@arg\@empty
+     \lst@FVConvert#1\@nil
+     \global\let\@gtempa\lst@arg}%
+     \lst@lExtend\lst@arg{\expandafter{\@gtempa\lst@PrintToken}}%
+     \lst@FVConvert}
+\gdef\lst@FVConvert@#1{%
+    \ifx \@nil#1\else
+       \if\relax\noexpand#1%
+          \lst@lAddTo\lst@arg{\lst@OutputLostSpace\lst@PrintToken#1}%
+       \else
+          \lccode`\~=`#1\lowercase{\lst@lAddTo\lst@arg~}%
+       \fi
+       \expandafter\lst@FVConvert
+    \fi}
+\gdef\lst@FVConvert@#1{%
+    \ifx \@nil#1\else
+       \if\relax\noexpand#1%
+          \lst@lAddTo\lst@arg{\lst@OutputLostSpace\lst@PrintToken#1}%
+          \def\lst@temp##1,#1##2,##3##4\relax{%
+              \ifx##3\@empty \else \@tempcnta##2\relax \fi}%
+          \expandafter\lst@temp\lst@FVcmdparams,#1\z@,\@empty\relax
+       \else
+          \lccode`\~=`#1\lowercase{\lst@lAddTo\lst@arg~}%
+       \fi
+       \expandafter\lst@FVConvertO@
+    \fi}
+\lst@EndAspect
+\lst@BeginAspect[keywords,comments,strings,language]{lgrind}
+\gdef\lst@LGGetNames#1:#2\relax{%
+    \lst@NormedDef\lstlang@{#1}\lst@ReplaceInArg\lstlang@{|,}%
+    \def\lst@arg{:#2}}
+\gdef\lst@LGGetValue#1{%
+    \lst@false
+    \def\lst@temp##1:#1##2##3\relax{%
+        \ifx\@empty##2\else \lst@LGGetValue@{#1}\fi}
+    \expandafter\lst@temp\lst@arg:#1\@empty\relax}
+\gdef\lst@LGGetValue@#1{%
+    \lst@true
+    \def\lst@temp##1:#1##2:##3\relax{%
+        \@ifnextchar=\lst@LGGetValue@@{\lst@LGGetValue@@=}##2\relax
+        \def\lst@arg{##1:##3}}%
+    \expandafter\lst@temp\lst@arg\relax}
+\gdef\lst@LGGetValue@@=#1\relax{\def\lst@LGvalue{#1}}
+\gdef\lst@LGGetComment#1#2{%
+    \let#2\@empty
+    \lst@LGGetValue{#1b}%
+    \lst@if
+        \let#2\lst@LGvalue
+        \lst@LGGetValue{#1e}%
+        \ifx\lst@LGvalue\lst@LGEOL
+            \edef\lstlang@{\lstlang@,commentline={#2}}%
+            \let#2\@empty
+        \else
+            \edef#2{{#2}{\lst@LGvalue}}%
+        \fi
+    \fi}
+\gdef\lst@LGGetString#1#2{%
+    \lst@LGGetValue{#1b}%
+    \lst@if
+        \let#2\lst@LGvalue
+        \lst@LGGetValue{#1e}%
+        \ifx\lst@LGvalue\lst@LGEOL
+            \edef\lstlang@{\lstlang@,morestringizer=[l]{#2}}%
+        \else
+            \ifx #2\lst@LGvalue
+                \edef\lstlang@{\lstlang@,morestringizer=[d]{#2}}%
+            \else
+                \edef\lst@temp{\lst@LGe#2}%
+                \ifx \lst@temp\lst@LGvalue
+                    \edef\lstlang@{\lstlang@,morestringizer=[b]{#2}}%
+                \else
+                    \PackageWarning{Listings}%
+                    {String #2...\lst@LGvalue\space not supported}%
+                \fi
+            \fi
+        \fi
+    \fi}
+\gdef\lst@LGDefLang{%
+    \lst@LGReplace
+    \let\lstlang@\empty
+    \lst@LGGetValue{kw}%
+    \lst@if
+        \lst@ReplaceInArg\lst@LGvalue{{ },}%
+        \edef\lstlang@{\lstlang@,keywords={\lst@LGvalue}}%
+    \fi
+    \lst@LGGetValue{oc}%
+    \lst@if
+        \edef\lstlang@{\lstlang@,sensitive=f}%
+    \fi
+    \lst@LGGetValue{id}%
+    \lst@if
+        \edef\lstlang@{\lstlang@,alsoletter=\lst@LGvalue}%
+    \fi
+    \lst@LGGetComment a\lst@LGa
+    \lst@LGGetComment c\lst@LGc
+    \ifx\lst@LGa\@empty
+        \ifx\lst@LGc\@empty\else
+            \edef\lstlang@{\lstlang@,singlecomment=\lst@LGc}%
+        \fi
+    \else
+        \ifx\lst@LGc\@empty
+            \edef\lstlang@{\lstlang@,singlecomment=\lst@LGa}%
+        \else
+            \edef\lstlang@{\lstlang@,doublecomment=\lst@LGc\lst@LGa}%
+        \fi
+    \fi
+    \lst@LGGetString s\lst@LGa
+    \lst@LGGetString l\lst@LGa
+    \lst@LGGetValue{tc}%
+    \lst@if
+        \edef\lstlang@{\lstlang@,lgrindef=\lst@LGvalue}%
+    \fi
+    \expandafter\xdef\csname\@lst LGlang@\lst@language@\endcsname
+        {\noexpand\lstset{\lstlang@}}%
+    \lst@ReplaceInArg\lst@arg{{: :}:}\let\lst@LGvalue\@empty
+    \expandafter\lst@LGDroppedCaps\lst@arg\relax\relax
+    \ifx\lst@LGvalue\@empty\else
+        \PackageWarningNoLine{Listings}{Ignored capabilities for
+            \space `\lst@language@' are\MessageBreak\lst@LGvalue}%
+    \fi}
+\gdef\lst@LGDroppedCaps#1:#2#3{%
+    \ifx#2\relax
+        \lst@RemoveCommas\lst@LGvalue
+    \else
+        \edef\lst@LGvalue{\lst@LGvalue,#2#3}%
+        \expandafter\lst@LGDroppedCaps
+    \fi}
+\begingroup
+\catcode`\/=0
+\lccode`\z=`\:\lccode`\y=`\^\lccode`\x=`\$\lccode`\v=`\|
+\catcode`\\=12\relax
+/lowercase{%
+/gdef/lst@LGReplace{/lst@ReplaceInArg/lst@arg
+    {{\:}{z }{\^}{y}{\$}{x}{\|}{v}{ \ }{ }{:\ :}{:}{\ }{ }{\(}({\)})}}
+/gdef/lst@LGe{\e}
+}
+/endgroup
+\gdef\lst@LGRead#1\par{%
+    \lst@LGGetNames#1:\relax
+    \def\lst@temp{endoflanguagedefinitions}%
+    \ifx\lstlang@\lst@temp
+        \let\lst@next\endinput
+    \else
+        \expandafter\lst@IfOneOf\lst@language@\relax\lstlang@
+            {\lst@LGDefLang \let\lst@next\endinput}%
+            {\let\lst@next\lst@LGRead}%
+    \fi
+    \lst@next}
+\lst@Key{lgrindef}\relax{%
+    \lst@NormedDef\lst@language@{#1}%
+    \begingroup
+    \@ifundefined{lstLGlang@\lst@language@}%
+        {\everypar{\lst@LGRead}%
+         \catcode`\\=12\catcode`\{=12\catcode`\}=12\catcode`\%=12%
+         \catcode`\#=14\catcode`\$=12\catcode`\^=12\catcode`\_=12\relax
+         \input{\lstlgrindeffile}%
+        }{}%
+    \endgroup
+    \@ifundefined{lstLGlang@\lst@language@}%
+        {\PackageError{Listings}%
+         {LGrind language \lst@language@\space undefined}%
+         {The language is not loadable. \@ehc}}%
+        {\lsthk@SetLanguage
+         \csname\@lst LGlang@\lst@language@\endcsname}}
+\@ifundefined{lstlgrindeffile}
+    {\lst@UserCommand\lstlgrindeffile{lgrindef.}}{}
+\lst@EndAspect
+\lst@BeginAspect[keywords]{hyper}
+\lst@Key{hyperanchor}\hyper@@anchor{\let\lst@hyperanchor#1}
+\lst@Key{hyperlink}\hyperlink{\let\lst@hyperlink#1}
+\lst@InstallKeywords{h}{hyperref}{}\relax{}
+    {\begingroup
+         \let\lst@UM\@empty \xdef\@gtempa{\the\lst@token}%
+     \endgroup
+     \lst@GetFreeMacro{lstHR@\@gtempa}%
+     \global\expandafter\let\lst@freemacro\@empty
+     \@tempcntb\@tempcnta \advance\@tempcntb\m@ne
+     \edef\lst@alloverstyle##1{%
+         \let\noexpand\lst@alloverstyle\noexpand\@empty
+         \noexpand\smash{\raise\baselineskip\hbox
+             {\noexpand\lst@hyperanchor{lst.\@gtempa\the\@tempcnta}%
+                                       {\relax}}}%
+         \ifnum\@tempcnta=\z@ ##1\else
+             \noexpand\lst@hyperlink{lst.\@gtempa\the\@tempcntb}{##1}%
+         \fi}%
+    }
+    od
+\lst@EndAspect
+\endinput
+%%
+%% End of file `lstmisc.sty'.
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java
new file mode 100644
index 0000000..fa7968b
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java
@@ -0,0 +1,537 @@
+package org.checkerframework.dataflow.analysis;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.PriorityQueue;
+import java.util.Set;
+import javax.lang.model.element.Element;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.block.SpecialBlock;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+
+/**
+ * Implementation of common features for {@link BackwardAnalysisImpl} and {@link
+ * ForwardAnalysisImpl}.
+ *
+ * @param <V> the abstract value type to be tracked by the analysis
+ * @param <S> the store type used in the analysis
+ * @param <T> the transfer function type that is used to approximated runtime behavior
+ */
+public abstract class AbstractAnalysis<
+        V extends AbstractValue<V>, S extends Store<S>, T extends TransferFunction<V, S>>
+    implements Analysis<V, S, T> {
+
+  /** The direction of this analysis. */
+  protected final Direction direction;
+
+  /** Is the analysis currently running? */
+  protected boolean isRunning = false;
+
+  /** The transfer function for regular nodes. */
+  // TODO: make final. Currently, the transferFunction has a reference to the analysis, so it
+  //  can't be created until the Analysis is initialized.
+  protected @Nullable T transferFunction;
+
+  /** The current control flow graph to perform the analysis on. */
+  protected @MonotonicNonNull ControlFlowGraph cfg;
+
+  /**
+   * The transfer inputs of every basic block (assumed to be 'no information' if not present, inputs
+   * before blocks in forward analysis, after blocks in backward analysis).
+   */
+  protected final IdentityHashMap<Block, TransferInput<V, S>> inputs = new IdentityHashMap<>();
+
+  /** The worklist used for the fix-point iteration. */
+  protected final Worklist worklist;
+
+  /** Abstract values of nodes. */
+  protected final IdentityHashMap<Node, V> nodeValues = new IdentityHashMap<>();
+
+  /** Map from (effectively final) local variable elements to their abstract value. */
+  protected final HashMap<Element, V> finalLocalValues = new HashMap<>();
+
+  /**
+   * The node that is currently handled in the analysis (if it is running). The following invariant
+   * holds:
+   *
+   * <pre>
+   *   !isRunning &rArr; (currentNode == null)
+   * </pre>
+   */
+  // currentNode == null when isRunning is true.
+  // See https://github.com/typetools/checker-framework/issues/4115
+  protected @InternedDistinct @Nullable Node currentNode;
+
+  /**
+   * The tree that is currently being looked at. The transfer function can set this tree to make
+   * sure that calls to {@code getValue} will not return information for this given tree.
+   */
+  protected @InternedDistinct @Nullable Tree currentTree;
+
+  /** The current transfer input when the analysis is running. */
+  protected @Nullable TransferInput<V, S> currentInput;
+
+  /**
+   * Returns the tree that is currently being looked at. The transfer function can set this tree to
+   * make sure that calls to {@code getValue} will not return information for this given tree.
+   *
+   * @return the tree that is currently being looked at
+   */
+  public @Nullable Tree getCurrentTree() {
+    return currentTree;
+  }
+
+  /**
+   * Set the tree that is currently being looked at.
+   *
+   * @param currentTree the tree that should be currently looked at
+   */
+  public void setCurrentTree(@FindDistinct Tree currentTree) {
+    this.currentTree = currentTree;
+  }
+
+  /**
+   * Set the node that is currently being looked at.
+   *
+   * @param currentNode the node that should be currently looked at
+   */
+  protected void setCurrentNode(@FindDistinct @Nullable Node currentNode) {
+    this.currentNode = currentNode;
+  }
+
+  /**
+   * Implementation of common features for {@link BackwardAnalysisImpl} and {@link
+   * ForwardAnalysisImpl}.
+   *
+   * @param direction direction of the analysis
+   */
+  protected AbstractAnalysis(Direction direction) {
+    this.direction = direction;
+    this.worklist = new Worklist(this.direction);
+  }
+
+  /** Initialize the transfer inputs of every basic block before performing the analysis. */
+  @RequiresNonNull("cfg")
+  protected abstract void initInitialInputs();
+
+  /**
+   * Propagate the stores in {@code currentInput} to the next block in the direction of analysis,
+   * according to the {@code flowRule}.
+   *
+   * @param nextBlock the target block to propagate the stores to
+   * @param node the node of the target block
+   * @param currentInput the current transfer input
+   * @param flowRule the flow rule being used
+   * @param addToWorklistAgain whether the block should be added to {@link #worklist} again
+   */
+  protected abstract void propagateStoresTo(
+      Block nextBlock,
+      Node node,
+      TransferInput<V, S> currentInput,
+      Store.FlowRule flowRule,
+      boolean addToWorklistAgain);
+
+  @Override
+  public boolean isRunning() {
+    return isRunning;
+  }
+
+  @Override
+  public Direction getDirection() {
+    return this.direction;
+  }
+
+  @Override
+  @SuppressWarnings("nullness:contracts.precondition.override") // implementation field
+  @RequiresNonNull("cfg")
+  public AnalysisResult<V, S> getResult() {
+    if (isRunning) {
+      throw new BugInCF(
+          "AbstractAnalysis::getResult() shouldn't be called when the analysis is running.");
+    }
+    return new AnalysisResult<>(
+        nodeValues, inputs, cfg.getTreeLookup(), cfg.getUnaryAssignNodeLookup(), finalLocalValues);
+  }
+
+  @Override
+  public @Nullable T getTransferFunction() {
+    return transferFunction;
+  }
+
+  @Override
+  public @Nullable V getValue(Node n) {
+    if (isRunning) {
+      // we don't have a org.checkerframework.dataflow fact about the current node yet
+      if (currentNode == null
+          || currentNode == n
+          || (currentTree != null && currentTree == n.getTree())) {
+        return null;
+      }
+      // check that 'n' is a subnode of 'node'. Check immediate operands
+      // first for efficiency.
+      assert !n.isLValue() : "Did not expect an lvalue, but got " + n;
+      if (currentNode == n
+          || (!currentNode.getOperands().contains(n)
+              && !currentNode.getTransitiveOperands().contains(n))) {
+        return null;
+      }
+      // fall through when the current node is not 'n', and 'n' is not a subnode.
+    }
+    return nodeValues.get(n);
+  }
+
+  /**
+   * Returns all current node values.
+   *
+   * @return {@link #nodeValues}
+   */
+  public IdentityHashMap<Node, V> getNodeValues() {
+    return nodeValues;
+  }
+
+  /**
+   * Set all current node values to the given map.
+   *
+   * @param in the current node values
+   */
+  /*package-private*/ void setNodeValues(IdentityHashMap<Node, V> in) {
+    assert !isRunning;
+    nodeValues.clear();
+    nodeValues.putAll(in);
+  }
+
+  @Override
+  @SuppressWarnings("nullness:contracts.precondition.override") // implementation field
+  @RequiresNonNull("cfg")
+  public @Nullable S getRegularExitStore() {
+    SpecialBlock regularExitBlock = cfg.getRegularExitBlock();
+    if (inputs.containsKey(regularExitBlock)) {
+      return inputs.get(regularExitBlock).getRegularStore();
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  @SuppressWarnings("nullness:contracts.precondition.override") // implementation field
+  @RequiresNonNull("cfg")
+  public @Nullable S getExceptionalExitStore() {
+    SpecialBlock exceptionalExitBlock = cfg.getExceptionalExitBlock();
+    if (inputs.containsKey(exceptionalExitBlock)) {
+      S exceptionalExitStore = inputs.get(exceptionalExitBlock).getRegularStore();
+      return exceptionalExitStore;
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Get the set of {@link Node}s for a given {@link Tree}. Returns null for trees that don't
+   * produce a value.
+   *
+   * @param t the given tree
+   * @return the set of corresponding nodes to the given tree
+   */
+  public @Nullable Set<Node> getNodesForTree(Tree t) {
+    if (cfg == null) {
+      return null;
+    }
+    return cfg.getNodesCorrespondingToTree(t);
+  }
+
+  @Override
+  public @Nullable V getValue(Tree t) {
+    // we don't have a org.checkerframework.dataflow fact about the current node yet
+    if (t == currentTree) {
+      return null;
+    }
+    Set<Node> nodesCorrespondingToTree = getNodesForTree(t);
+    if (nodesCorrespondingToTree == null) {
+      return null;
+    }
+    V merged = null;
+    for (Node aNode : nodesCorrespondingToTree) {
+      if (aNode.isLValue()) {
+        return null;
+      }
+      V v = getValue(aNode);
+      if (merged == null) {
+        merged = v;
+      } else if (v != null) {
+        merged = merged.leastUpperBound(v);
+      }
+    }
+    return merged;
+  }
+
+  /**
+   * Get the {@link MethodTree} of the current CFG if the argument {@link Tree} maps to a {@link
+   * Node} in the CFG or {@code null} otherwise.
+   *
+   * @param t the given tree
+   * @return the contained method tree of the given tree
+   */
+  public @Nullable MethodTree getContainingMethod(Tree t) {
+    if (cfg == null) {
+      return null;
+    }
+    return cfg.getContainingMethod(t);
+  }
+
+  /**
+   * Get the {@link ClassTree} of the current CFG if the argument {@link Tree} maps to a {@link
+   * Node} in the CFG or {@code null} otherwise.
+   *
+   * @param t the given tree
+   * @return the contained class tree of the given tree
+   */
+  public @Nullable ClassTree getContainingClass(Tree t) {
+    if (cfg == null) {
+      return null;
+    }
+    return cfg.getContainingClass(t);
+  }
+
+  /**
+   * Call the transfer function for node {@code node}, and set that node as current node first. This
+   * method requires a {@code transferInput} that the method can modify.
+   *
+   * @param node the given node
+   * @param transferInput the transfer input
+   * @return the output of the transfer function
+   */
+  protected TransferResult<V, S> callTransferFunction(
+      Node node, TransferInput<V, S> transferInput) {
+    assert transferFunction != null : "@AssumeAssertion(nullness): invariant";
+    if (node.isLValue()) {
+      // TODO: should the default behavior return a regular transfer result, a conditional transfer
+      //  result (depending on store.containsTwoStores()), or is the following correct?
+      return new RegularTransferResult<>(null, transferInput.getRegularStore());
+    }
+    transferInput.node = node;
+    setCurrentNode(node);
+    @SuppressWarnings("nullness") // CF bug: "INFERENCE FAILED"
+    TransferResult<V, S> transferResult = node.accept(transferFunction, transferInput);
+    setCurrentNode(null);
+    if (node instanceof AssignmentNode) {
+      // store the flow-refined value effectively for final local variables
+      AssignmentNode assignment = (AssignmentNode) node;
+      Node lhst = assignment.getTarget();
+      if (lhst instanceof LocalVariableNode) {
+        LocalVariableNode lhs = (LocalVariableNode) lhst;
+        Element elem = lhs.getElement();
+        if (ElementUtils.isEffectivelyFinal(elem)) {
+          V resval = transferResult.getResultValue();
+          if (resval != null) {
+            finalLocalValues.put(elem, resval);
+          }
+        }
+      }
+    }
+    return transferResult;
+  }
+
+  /**
+   * Initialize the analysis with a new control flow graph.
+   *
+   * @param cfg the control flow graph to use
+   */
+  protected final void init(ControlFlowGraph cfg) {
+    initFields(cfg);
+    initInitialInputs();
+  }
+
+  /**
+   * Initialize fields of this object based on a given control flow graph. Sub-class may override
+   * this method to initialize customized fields.
+   *
+   * @param cfg a given control flow graph
+   */
+  @EnsuresNonNull("this.cfg")
+  protected void initFields(ControlFlowGraph cfg) {
+    inputs.clear();
+    nodeValues.clear();
+    finalLocalValues.clear();
+    this.cfg = cfg;
+  }
+
+  /**
+   * Updates the value of node {@code node} to the value of the {@code transferResult}. Returns true
+   * if the node's value changed, or a store was updated.
+   *
+   * @param node the node to update
+   * @param transferResult the transfer result being updated
+   * @return true if the node's value changed, or a store was updated
+   */
+  protected boolean updateNodeValues(Node node, TransferResult<V, S> transferResult) {
+    V newVal = transferResult.getResultValue();
+    boolean nodeValueChanged = false;
+    if (newVal != null) {
+      V oldVal = nodeValues.get(node);
+      nodeValues.put(node, newVal);
+      nodeValueChanged = !Objects.equals(oldVal, newVal);
+    }
+    return nodeValueChanged || transferResult.storeChanged();
+  }
+
+  /**
+   * Read the store for a particular basic block from a map of stores (or {@code null} if none
+   * exists yet).
+   *
+   * @param stores a map of stores
+   * @param b the target block
+   * @param <S> method return type should be a subtype of {@link Store}
+   * @return the store for the target block
+   */
+  protected static <S> @Nullable S readFromStore(Map<Block, S> stores, Block b) {
+    return stores.get(b);
+  }
+
+  /**
+   * Add a basic block to {@link #worklist}. If {@code b} is already present, the method does
+   * nothing.
+   *
+   * @param b the block to add to {@link #worklist}
+   */
+  protected void addToWorklist(Block b) {
+    // TODO: use a more efficient way to check if b is already present
+    if (!worklist.contains(b)) {
+      worklist.add(b);
+    }
+  }
+
+  /**
+   * A worklist is a priority queue of blocks in which the order is given by depth-first ordering to
+   * place non-loop predecessors ahead of successors.
+   */
+  protected static class Worklist {
+
+    /** Map all blocks in the CFG to their depth-first order. */
+    protected final IdentityHashMap<Block, Integer> depthFirstOrder = new IdentityHashMap<>();
+
+    /**
+     * Comparators to allow priority queue to order blocks by their depth-first order, using by
+     * forward analysis.
+     */
+    public class ForwardDFOComparator implements Comparator<Block> {
+      @SuppressWarnings("nullness:unboxing.of.nullable")
+      @Override
+      public int compare(Block b1, Block b2) {
+        return depthFirstOrder.get(b1) - depthFirstOrder.get(b2);
+      }
+    }
+
+    /**
+     * Comparators to allow priority queue to order blocks by their depth-first order, using by
+     * backward analysis.
+     */
+    public class BackwardDFOComparator implements Comparator<Block> {
+      @SuppressWarnings("nullness:unboxing.of.nullable")
+      @Override
+      public int compare(Block b1, Block b2) {
+        return depthFirstOrder.get(b2) - depthFirstOrder.get(b1);
+      }
+    }
+
+    /** The backing priority queue. */
+    protected final PriorityQueue<Block> queue;
+
+    /**
+     * Create a Worklist.
+     *
+     * @param direction the direction (forward or backward)
+     */
+    public Worklist(Direction direction) {
+      if (direction == Direction.FORWARD) {
+        queue = new PriorityQueue<>(new ForwardDFOComparator());
+      } else if (direction == Direction.BACKWARD) {
+        queue = new PriorityQueue<>(new BackwardDFOComparator());
+      } else {
+        throw new BugInCF("Unexpected Direction meet: " + direction.name());
+      }
+    }
+
+    /**
+     * Process the control flow graph, add the blocks to {@link #depthFirstOrder}.
+     *
+     * @param cfg the control flow graph to process
+     */
+    public void process(ControlFlowGraph cfg) {
+      depthFirstOrder.clear();
+      int count = 1;
+      for (Block b : cfg.getDepthFirstOrderedBlocks()) {
+        depthFirstOrder.put(b, count++);
+      }
+
+      queue.clear();
+    }
+
+    /**
+     * See {@link PriorityQueue#isEmpty}.
+     *
+     * @see PriorityQueue#isEmpty
+     * @return true if {@link #queue} is empty else false
+     */
+    @Pure
+    @EnsuresNonNullIf(result = false, expression = "poll()")
+    @SuppressWarnings("nullness:contracts.conditional.postcondition") // forwarded
+    public boolean isEmpty() {
+      return queue.isEmpty();
+    }
+
+    /**
+     * Check if {@link #queue} contains the block which is passed as the argument.
+     *
+     * @param block the given block to check
+     * @return true if {@link #queue} contains the given block
+     */
+    public boolean contains(Block block) {
+      return queue.contains(block);
+    }
+
+    /**
+     * Add the given block to {@link #queue}. Adds unconditionally: does not check containment
+     * first.
+     *
+     * @param block the block to add to {@link #queue}
+     */
+    public void add(Block block) {
+      queue.add(block);
+    }
+
+    /**
+     * See {@link PriorityQueue#poll}.
+     *
+     * @see PriorityQueue#poll
+     * @return the head of {@link #queue}
+     */
+    @Pure
+    public @Nullable Block poll() {
+      return queue.poll();
+    }
+
+    @Override
+    public String toString() {
+      return "Worklist(" + queue + ")";
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractValue.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractValue.java
new file mode 100644
index 0000000..f826854
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractValue.java
@@ -0,0 +1,21 @@
+package org.checkerframework.dataflow.analysis;
+
+/** An abstract value used in the org.checkerframework.dataflow analysis. */
+public interface AbstractValue<V extends AbstractValue<V>> {
+
+  /**
+   * Compute the least upper bound of two values.
+   *
+   * <p><em>Important</em>: This method must fulfill the following contract:
+   *
+   * <ul>
+   *   <li>Does not change {@code this}.
+   *   <li>Does not change {@code other}.
+   *   <li>Returns a fresh object which is not aliased yet.
+   *   <li>Returns an object of the same (dynamic) type as {@code this}, even if the signature is
+   *       more permissive.
+   *   <li>Is commutative.
+   * </ul>
+   */
+  V leastUpperBound(V other);
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java
new file mode 100644
index 0000000..92b5e9e
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java
@@ -0,0 +1,153 @@
+package org.checkerframework.dataflow.analysis;
+
+import com.sun.source.tree.Tree;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.node.Node;
+
+/**
+ * This interface defines a dataflow analysis, given a control flow graph and a transfer function. A
+ * dataflow analysis has a direction, either forward or backward. The direction of corresponding
+ * transfer function is consistent with the analysis, i.e. a forward analysis has a forward transfer
+ * function, and a backward analysis has a backward transfer function.
+ *
+ * @param <V> the abstract value type to be tracked by the analysis
+ * @param <S> the store type used in the analysis
+ * @param <T> the transfer function type that is used to approximated runtime behavior
+ */
+public interface Analysis<
+    V extends AbstractValue<V>, S extends Store<S>, T extends TransferFunction<V, S>> {
+
+  /** The direction of an analysis instance. */
+  enum Direction {
+    /** The forward direction. */
+    FORWARD,
+    /** The backward direction. */
+    BACKWARD
+  }
+
+  /**
+   * In calls to {@code Analysis#runAnalysisFor}, whether to return the store before or after the
+   * given node.
+   */
+  public static enum BeforeOrAfter {
+    /** Return the pre-store. */
+    BEFORE,
+    /** Return the post-store. */
+    AFTER
+  }
+
+  /**
+   * Get the direction of this analysis.
+   *
+   * @return the direction of this analysis
+   */
+  Direction getDirection();
+
+  /**
+   * Is the analysis currently running?
+   *
+   * @return true if the analysis is running currently, else false
+   */
+  boolean isRunning();
+
+  /**
+   * Perform the actual analysis.
+   *
+   * @param cfg the control flow graph
+   */
+  void performAnalysis(ControlFlowGraph cfg);
+
+  /**
+   * Perform the actual analysis on one block.
+   *
+   * @param b the block to analyze
+   */
+  void performAnalysisBlock(Block b);
+
+  /**
+   * Runs the analysis again within the block of {@code node} and returns the store at the location
+   * of {@code node}. If {@code before} is true, then the store immediately before the {@link Node}
+   * {@code node} is returned. Otherwise, the store immediately after {@code node} is returned. If
+   * {@code analysisCaches} is not null, this method uses a cache. {@code analysisCaches} is a map
+   * of a block of node to the cached analysis result. If the cache for {@code transferInput} is not
+   * in {@code analysisCaches}, this method creates new cache and stores it in {@code
+   * analysisCaches}. The cache is a map of nodes to the analysis results of the nodes.
+   *
+   * @param node the node to analyze
+   * @param preOrPost which store to return: the store immediately before {@code node} or the store
+   *     after {@code node}
+   * @param blockTransferInput the transfer input of the block of this node
+   * @param nodeValues abstract values of nodes
+   * @param analysisCaches caches of analysis results
+   * @return the store before or after {@code node} (depends on the value of {@code before}) after
+   *     running the analysis
+   */
+  S runAnalysisFor(
+      Node node,
+      Analysis.BeforeOrAfter preOrPost,
+      TransferInput<V, S> blockTransferInput,
+      IdentityHashMap<Node, V> nodeValues,
+      Map<TransferInput<V, S>, IdentityHashMap<Node, TransferResult<V, S>>> analysisCaches);
+
+  /**
+   * The result of running the analysis. This is only available once the analysis finished running.
+   *
+   * @return the result of running the analysis
+   */
+  AnalysisResult<V, S> getResult();
+
+  /**
+   * Get the transfer function of this analysis.
+   *
+   * @return the transfer function of this analysis
+   */
+  @Nullable T getTransferFunction();
+
+  /**
+   * Get the transfer input of a given {@link Block} b.
+   *
+   * @param b a given Block
+   * @return the transfer input of this Block
+   */
+  @Nullable TransferInput<V, S> getInput(Block b);
+
+  /**
+   * Returns the abstract value for {@link Node} {@code n}, or {@code null} if no information is
+   * available. Note that if the analysis has not finished yet, this value might not represent the
+   * final value for this node.
+   *
+   * @param n n a node
+   * @return the abstract value for node {@code n}, or {@code null} if no information is available
+   */
+  @Nullable V getValue(Node n);
+
+  /**
+   * Return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is
+   * available. Note that if the analysis has not finished yet, this value might not represent the
+   * final value for this node.
+   *
+   * @param t the given tree
+   * @return the abstract value for the given tree
+   */
+  @Nullable V getValue(Tree t);
+
+  /**
+   * Returns the regular exit store, or {@code null}, if there is no such store (because the method
+   * cannot exit through the regular exit block).
+   *
+   * @return the regular exit store, or {@code null}, if there is no such store (because the method
+   *     cannot exit through the regular exit block)
+   */
+  @Nullable S getRegularExitStore();
+
+  /**
+   * Returns the exceptional exit store.
+   *
+   * @return the exceptional exit store
+   */
+  @Nullable S getExceptionalExitStore();
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java
new file mode 100644
index 0000000..2d96928
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java
@@ -0,0 +1,502 @@
+package org.checkerframework.dataflow.analysis;
+
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.UnaryTree;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.lang.model.element.Element;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.block.ExceptionBlock;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+import org.plumelib.util.UniqueId;
+
+/**
+ * An {@link AnalysisResult} represents the result of a org.checkerframework.dataflow analysis by
+ * providing the abstract values given a node or a tree. Note that it does not keep track of custom
+ * results computed by some analysis.
+ *
+ * @param <V> type of the abstract value that is tracked
+ * @param <S> the store type used in the analysis
+ */
+public class AnalysisResult<V extends AbstractValue<V>, S extends Store<S>> implements UniqueId {
+
+  /** Abstract values of nodes. */
+  protected final IdentityHashMap<Node, V> nodeValues;
+
+  /**
+   * Map from AST {@link Tree}s to sets of {@link Node}s.
+   *
+   * <p>Some of those Nodes might not be keys in {@link #nodeValues}. One reason is that the Node is
+   * unreachable in the control flow graph, so dataflow never gave it a value.
+   */
+  protected final IdentityHashMap<Tree, Set<Node>> treeLookup;
+
+  /** Map from AST {@link UnaryTree}s to corresponding {@link AssignmentNode}s. */
+  protected final IdentityHashMap<UnaryTree, AssignmentNode> unaryAssignNodeLookup;
+
+  /** Map from (effectively final) local variable elements to their abstract value. */
+  protected final HashMap<Element, V> finalLocalValues;
+
+  /** The stores before every method call. */
+  protected final IdentityHashMap<Block, TransferInput<V, S>> stores;
+
+  /**
+   * Caches of the analysis results for each input for the block of the node and each node.
+   *
+   * @see #runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, IdentityHashMap, Map)
+   */
+  protected final Map<TransferInput<V, S>, IdentityHashMap<Node, TransferResult<V, S>>>
+      analysisCaches;
+
+  /** The unique ID for the next-created object. */
+  static final AtomicLong nextUid = new AtomicLong(0);
+  /** The unique ID of this object. */
+  final transient long uid = nextUid.getAndIncrement();
+
+  @Override
+  public long getUid(@UnknownInitialization AnalysisResult<V, S> this) {
+    return uid;
+  }
+
+  /**
+   * Initialize with given mappings.
+   *
+   * @param nodeValues {@link #nodeValues}
+   * @param stores {@link #stores}
+   * @param treeLookup {@link #treeLookup}
+   * @param unaryAssignNodeLookup {@link #unaryAssignNodeLookup}
+   * @param finalLocalValues {@link #finalLocalValues}
+   * @param analysisCaches {@link #analysisCaches}
+   */
+  protected AnalysisResult(
+      IdentityHashMap<Node, V> nodeValues,
+      IdentityHashMap<Block, TransferInput<V, S>> stores,
+      IdentityHashMap<Tree, Set<Node>> treeLookup,
+      IdentityHashMap<UnaryTree, AssignmentNode> unaryAssignNodeLookup,
+      HashMap<Element, V> finalLocalValues,
+      Map<TransferInput<V, S>, IdentityHashMap<Node, TransferResult<V, S>>> analysisCaches) {
+    this.nodeValues = new IdentityHashMap<>(nodeValues);
+    this.treeLookup = new IdentityHashMap<>(treeLookup);
+    this.unaryAssignNodeLookup = new IdentityHashMap<>(unaryAssignNodeLookup);
+    // TODO: why are stores and finalLocalValues captured?
+    this.stores = stores;
+    this.finalLocalValues = finalLocalValues;
+    this.analysisCaches = analysisCaches;
+  }
+
+  /**
+   * Initialize with given mappings and empty cache.
+   *
+   * @param nodeValues {@link #nodeValues}
+   * @param stores {@link #stores}
+   * @param treeLookup {@link #treeLookup}
+   * @param unaryAssignNodeLookup {@link #unaryAssignNodeLookup}
+   * @param finalLocalValues {@link #finalLocalValues}
+   */
+  public AnalysisResult(
+      IdentityHashMap<Node, V> nodeValues,
+      IdentityHashMap<Block, TransferInput<V, S>> stores,
+      IdentityHashMap<Tree, Set<Node>> treeLookup,
+      IdentityHashMap<UnaryTree, AssignmentNode> unaryAssignNodeLookup,
+      HashMap<Element, V> finalLocalValues) {
+    this(
+        nodeValues,
+        stores,
+        treeLookup,
+        unaryAssignNodeLookup,
+        finalLocalValues,
+        new IdentityHashMap<>());
+  }
+
+  /**
+   * Initialize empty result with specified cache.
+   *
+   * @param analysisCaches {@link #analysisCaches}
+   */
+  public AnalysisResult(
+      Map<TransferInput<V, S>, IdentityHashMap<Node, TransferResult<V, S>>> analysisCaches) {
+    this(
+        new IdentityHashMap<>(),
+        new IdentityHashMap<>(),
+        new IdentityHashMap<>(),
+        new IdentityHashMap<>(),
+        new HashMap<>(),
+        analysisCaches);
+  }
+
+  /**
+   * Combine with another analysis result.
+   *
+   * @param other an analysis result to combine with this
+   */
+  public void combine(AnalysisResult<V, S> other) {
+    nodeValues.putAll(other.nodeValues);
+    mergeTreeLookup(treeLookup, other.treeLookup);
+    unaryAssignNodeLookup.putAll(other.unaryAssignNodeLookup);
+    stores.putAll(other.stores);
+    finalLocalValues.putAll(other.finalLocalValues);
+  }
+
+  /**
+   * Merge all entries from otherTreeLookup into treeLookup. Merge sets if already present.
+   *
+   * @param treeLookup a map from abstract syntax trees to sets of nodes
+   * @param otherTreeLookup another treeLookup that will be merged into {@code treeLookup}
+   */
+  private static void mergeTreeLookup(
+      IdentityHashMap<Tree, Set<Node>> treeLookup,
+      IdentityHashMap<Tree, Set<Node>> otherTreeLookup) {
+    for (Map.Entry<Tree, Set<Node>> entry : otherTreeLookup.entrySet()) {
+      Set<Node> hit = treeLookup.get(entry.getKey());
+      if (hit == null) {
+        treeLookup.put(entry.getKey(), entry.getValue());
+      } else {
+        hit.addAll(entry.getValue());
+      }
+    }
+  }
+
+  /**
+   * Returns the value of effectively final local variables.
+   *
+   * @return the value of effectively final local variables
+   */
+  public HashMap<Element, V> getFinalLocalValues() {
+    return finalLocalValues;
+  }
+
+  /**
+   * Returns the abstract value for {@link Node} {@code n}, or {@code null} if no information is
+   * available. Note that if the analysis has not finished yet, this value might not represent the
+   * final value for this node.
+   *
+   * @param n a node
+   * @return the abstract value for {@link Node} {@code n}, or {@code null} if no information is
+   *     available
+   */
+  public @Nullable V getValue(Node n) {
+    return nodeValues.get(n);
+  }
+
+  /**
+   * Returns the abstract value for {@link Tree} {@code t}, or {@code null} if no information is
+   * available. Note that if the analysis has not finished yet, this value might not represent the
+   * final value for this node.
+   *
+   * @param t a tree
+   * @return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is
+   *     available
+   */
+  public @Nullable V getValue(Tree t) {
+    Set<Node> nodes = treeLookup.get(t);
+
+    if (nodes == null) {
+      return null;
+    }
+    V merged = null;
+    for (Node aNode : nodes) {
+      V a = getValue(aNode);
+      if (merged == null) {
+        merged = a;
+      } else if (a != null) {
+        merged = merged.leastUpperBound(a);
+      }
+    }
+    return merged;
+  }
+
+  /**
+   * Returns the {@code Node}s corresponding to a particular {@code Tree}. Multiple {@code Node}s
+   * can correspond to a single {@code Tree} because of several reasons:
+   *
+   * <ol>
+   *   <li>In a lambda expression such as {@code () -> 5} the {@code 5} is both an {@code
+   *       IntegerLiteralNode} and a {@code LambdaResultExpressionNode}.
+   *   <li>Widening and narrowing primitive conversions can result in {@code WideningConversionNode}
+   *       and {@code NarrowingConversionNode}.
+   *   <li>Automatic String conversion can result in a {@code StringConversionNode}.
+   *   <li>Trees for {@code finally} blocks are cloned to achieve a precise CFG. Any {@code Tree}
+   *       within a finally block can have multiple corresponding {@code Node}s attached to them.
+   * </ol>
+   *
+   * Callers of this method should always iterate through the returned set, possibly ignoring all
+   * {@code Node}s they are not interested in.
+   *
+   * @param tree a tree
+   * @return the set of {@link Node}s for a given {@link Tree}
+   */
+  public @Nullable Set<Node> getNodesForTree(Tree tree) {
+    return treeLookup.get(tree);
+  }
+
+  /**
+   * Returns the corresponding {@link AssignmentNode} for a given {@link UnaryTree}.
+   *
+   * @param tree a unary tree
+   * @return the corresponding assignment node
+   */
+  public AssignmentNode getAssignForUnaryTree(UnaryTree tree) {
+    if (!unaryAssignNodeLookup.containsKey(tree)) {
+      throw new BugInCF(tree + " is not in unaryAssignNodeLookup");
+    }
+    return unaryAssignNodeLookup.get(tree);
+  }
+
+  /**
+   * Returns the store immediately before a given {@link Tree}.
+   *
+   * @param tree a tree
+   * @return the store immediately before a given {@link Tree}
+   */
+  public @Nullable S getStoreBefore(Tree tree) {
+    Set<Node> nodes = getNodesForTree(tree);
+    if (nodes == null) {
+      return null;
+    }
+    S merged = null;
+    for (Node node : nodes) {
+      S s = getStoreBefore(node);
+      if (merged == null) {
+        merged = s;
+      } else if (s != null) {
+        merged = merged.leastUpperBound(s);
+      }
+    }
+    return merged;
+  }
+
+  /**
+   * Returns the store immediately before a given {@link Node}.
+   *
+   * @param node a node
+   * @return the store immediately before a given {@link Node}
+   */
+  public @Nullable S getStoreBefore(Node node) {
+    return runAnalysisFor(node, Analysis.BeforeOrAfter.BEFORE);
+  }
+
+  /**
+   * Returns the regular store immediately before a given {@link Block}.
+   *
+   * @param block a block
+   * @return the store right before the given block
+   */
+  public S getStoreBefore(Block block) {
+    TransferInput<V, S> transferInput = stores.get(block);
+    assert transferInput != null : "@AssumeAssertion(nullness): transferInput should be non-null";
+    Analysis<V, S, ?> analysis = transferInput.analysis;
+    switch (analysis.getDirection()) {
+      case FORWARD:
+        return transferInput.getRegularStore();
+      case BACKWARD:
+        Node firstNode;
+        switch (block.getType()) {
+          case REGULAR_BLOCK:
+            firstNode = block.getNodes().get(0);
+            break;
+          case EXCEPTION_BLOCK:
+            firstNode = ((ExceptionBlock) block).getNode();
+            break;
+          default:
+            firstNode = null;
+        }
+        if (firstNode == null) {
+          // This block doesn't contains any node, return the store in the transfer input
+          return transferInput.getRegularStore();
+        }
+        return analysis.runAnalysisFor(
+            firstNode, Analysis.BeforeOrAfter.BEFORE, transferInput, nodeValues, analysisCaches);
+      default:
+        throw new BugInCF("Unknown direction: " + analysis.getDirection());
+    }
+  }
+
+  /**
+   * Returns the regular store immediately after a given block.
+   *
+   * @param block a block
+   * @return the store after the given block
+   */
+  public S getStoreAfter(Block block) {
+    TransferInput<V, S> transferInput = stores.get(block);
+    assert transferInput != null : "@AssumeAssertion(nullness): transferInput should be non-null";
+    Analysis<V, S, ?> analysis = transferInput.analysis;
+    switch (analysis.getDirection()) {
+      case FORWARD:
+        Node lastNode = block.getLastNode();
+        if (lastNode == null) {
+          // This block doesn't contain any node, return the store in the transfer input
+          return transferInput.getRegularStore();
+        }
+        return analysis.runAnalysisFor(
+            lastNode, Analysis.BeforeOrAfter.AFTER, transferInput, nodeValues, analysisCaches);
+      case BACKWARD:
+        return transferInput.getRegularStore();
+      default:
+        throw new BugInCF("Unknown direction: " + analysis.getDirection());
+    }
+  }
+
+  /**
+   * Returns the store immediately after a given {@link Tree}.
+   *
+   * @param tree a tree
+   * @return the store immediately after a given {@link Tree}
+   */
+  public @Nullable S getStoreAfter(Tree tree) {
+    Set<Node> nodes = getNodesForTree(tree);
+    if (nodes == null) {
+      return null;
+    }
+    S merged = null;
+    for (Node node : nodes) {
+      S s = getStoreAfter(node);
+      if (merged == null) {
+        merged = s;
+      } else if (s != null) {
+        merged = merged.leastUpperBound(s);
+      }
+    }
+    return merged;
+  }
+
+  /**
+   * Returns the store immediately after a given {@link Node}.
+   *
+   * @param node a node
+   * @return the store immediately after a given {@link Node}
+   */
+  public @Nullable S getStoreAfter(Node node) {
+    return runAnalysisFor(node, Analysis.BeforeOrAfter.AFTER);
+  }
+
+  /**
+   * Runs the analysis again within the block of {@code node} and returns the store at the location
+   * of {@code node}. If {@code before} is true, then the store immediately before the {@link Node}
+   * {@code node} is returned. Otherwise, the store after {@code node} is returned.
+   *
+   * <p>If the given {@link Node} cannot be reached (in the control flow graph), then {@code null}
+   * is returned.
+   *
+   * @param node the node to analyze
+   * @param preOrPost which store to return: the store immediately before {@code node} or the store
+   *     after {@code node}
+   * @return the store before or after {@code node} (depends on the value of {@code before}) after
+   *     running the analysis
+   */
+  protected @Nullable S runAnalysisFor(Node node, Analysis.BeforeOrAfter preOrPost) {
+    Block block = node.getBlock();
+    assert block != null : "@AssumeAssertion(nullness): invariant";
+    TransferInput<V, S> transferInput = stores.get(block);
+    if (transferInput == null) {
+      return null;
+    }
+    return runAnalysisFor(node, preOrPost, transferInput, nodeValues, analysisCaches);
+  }
+
+  /**
+   * Runs the analysis again within the block of {@code node} and returns the store at the location
+   * of {@code node}. If {@code before} is true, then the store immediately before the {@link Node}
+   * {@code node} is returned. Otherwise, the store immediately after {@code node} is returned. If
+   * {@code analysisCaches} is not null, this method uses a cache. {@code analysisCaches} is a map
+   * of a block of node to the cached analysis result. If the cache for {@code transferInput} is not
+   * in {@code analysisCaches}, this method creates new cache and stores it in {@code
+   * analysisCaches}. The cache is a map of nodes to the analysis results of the nodes.
+   *
+   * @param <V> the abstract value type to be tracked by the analysis
+   * @param <S> the store type used in the analysis
+   * @param node the node to analyze
+   * @param preOrPost which store to return: the store immediately before {@code node} or the store
+   *     after {@code node}
+   * @param transferInput a transfer input
+   * @param nodeValues {@link #nodeValues}
+   * @param analysisCaches {@link #analysisCaches}
+   * @return the store before or after {@code node} (depends on the value of {@code before}) after
+   *     running the analysis
+   */
+  public static <V extends AbstractValue<V>, S extends Store<S>> S runAnalysisFor(
+      Node node,
+      Analysis.BeforeOrAfter preOrPost,
+      TransferInput<V, S> transferInput,
+      IdentityHashMap<Node, V> nodeValues,
+      Map<TransferInput<V, S>, IdentityHashMap<Node, TransferResult<V, S>>> analysisCaches) {
+    if (transferInput.analysis == null) {
+      throw new BugInCF("Analysis in transferInput cannot be null.");
+    }
+    return transferInput.analysis.runAnalysisFor(
+        node, preOrPost, transferInput, nodeValues, analysisCaches);
+  }
+
+  /**
+   * Returns a verbose string representation of this, useful for debugging.
+   *
+   * @return a string representation of this
+   */
+  public String toStringDebug() {
+    StringJoiner result =
+        new StringJoiner(
+            String.format("%n  "), String.format("AnalysisResult{%n  "), String.format("%n}"));
+    result.add("nodeValues = " + nodeValuesToString(nodeValues));
+    result.add("treeLookup = " + treeLookupToString(treeLookup));
+    result.add("unaryAssignNodeLookup = " + unaryAssignNodeLookup);
+    result.add("finalLocalValues = " + finalLocalValues);
+    result.add("stores = " + stores);
+    result.add("analysisCaches = " + analysisCaches);
+    return result.toString();
+  }
+
+  /**
+   * Returns a verbose string representation, useful for debugging. The map has the same type as the
+   * {@code nodeValues} field.
+   *
+   * @param <V> the type of values in the map
+   * @param nodeValues a map to format
+   * @return a printed representation of the given map
+   */
+  public static <V> String nodeValuesToString(Map<Node, V> nodeValues) {
+    if (nodeValues.isEmpty()) {
+      return "{}";
+    }
+    StringJoiner result = new StringJoiner(String.format("%n    "));
+    result.add("{");
+    for (Map.Entry<Node, V> entry : nodeValues.entrySet()) {
+      Node key = entry.getKey();
+      result.add(String.format("%s => %s", key.toStringDebug(), entry.getValue()));
+    }
+    result.add("}");
+    return result.toString();
+  }
+
+  /**
+   * Returns a verbose string representation of a map, useful for debugging. The map has the same
+   * type as the {@code treeLookup} field.
+   *
+   * @param treeLookup a map to format
+   * @return a printed representation of the given map
+   */
+  public static String treeLookupToString(Map<Tree, Set<Node>> treeLookup) {
+    if (treeLookup.isEmpty()) {
+      return "{}";
+    }
+    StringJoiner result = new StringJoiner(String.format("%n    "));
+    result.add("{");
+    for (Map.Entry<Tree, Set<Node>> entry : treeLookup.entrySet()) {
+      Tree key = entry.getKey();
+      result.add(
+          TreeUtils.toStringTruncated(key, 65)
+              + " => "
+              + Node.nodeCollectionToString(entry.getValue()));
+    }
+    result.add("}");
+    return result.toString();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysis.java
new file mode 100644
index 0000000..91c768b
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysis.java
@@ -0,0 +1,24 @@
+package org.checkerframework.dataflow.analysis;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * This interface defines a backward analysis, given a control flow graph and a backward transfer
+ * function.
+ *
+ * @param <V> the abstract value type to be tracked by the analysis
+ * @param <S> the store type used in the analysis
+ * @param <T> the backward transfer function type that is used to approximate runtime behavior
+ */
+public interface BackwardAnalysis<
+        V extends AbstractValue<V>, S extends Store<S>, T extends BackwardTransferFunction<V, S>>
+    extends Analysis<V, S, T> {
+
+  /**
+   * Get the output store at the entry block of a given control flow graph. For a backward analysis,
+   * the output store contains the analyzed flow information from the exit block to the entry block.
+   *
+   * @return the output store at the entry block of a given control flow graph
+   */
+  @Nullable S getEntryStore();
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java
new file mode 100644
index 0000000..f7a6c19
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java
@@ -0,0 +1,373 @@
+package org.checkerframework.dataflow.analysis;
+
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+import org.checkerframework.dataflow.analysis.Store.FlowRule;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.block.ConditionalBlock;
+import org.checkerframework.dataflow.cfg.block.ExceptionBlock;
+import org.checkerframework.dataflow.cfg.block.RegularBlock;
+import org.checkerframework.dataflow.cfg.block.SpecialBlock;
+import org.checkerframework.dataflow.cfg.block.SpecialBlock.SpecialBlockType;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * An implementation of a backward analysis to solve a org.checkerframework.dataflow problem given a
+ * control flow graph and a backward transfer function.
+ *
+ * @param <V> the abstract value type to be tracked by the analysis
+ * @param <S> the store type used in the analysis
+ * @param <T> the transfer function type that is used to approximate runtime behavior
+ */
+public class BackwardAnalysisImpl<
+        V extends AbstractValue<V>, S extends Store<S>, T extends BackwardTransferFunction<V, S>>
+    extends AbstractAnalysis<V, S, T> implements BackwardAnalysis<V, S, T> {
+
+  // TODO: Add widening support like what the forward analysis does.
+
+  /** Out stores after every basic block (assumed to be 'no information' if not present). */
+  protected final IdentityHashMap<Block, S> outStores = new IdentityHashMap<>();
+
+  /**
+   * Exception store of an exception block, propagated by exceptional successors of its exception
+   * block, and merged with the normal {@link TransferResult}.
+   */
+  protected final IdentityHashMap<ExceptionBlock, S> exceptionStores = new IdentityHashMap<>();
+
+  /** The store right before the entry block. */
+  protected @Nullable S storeAtEntry = null;
+
+  // `@code`, not `@link`, because dataflow module doesn't depend on framework module.
+  /**
+   * Construct an object that can perform a org.checkerframework.dataflow backward analysis over a
+   * control flow graph. When using this constructor, the transfer function is set later by the
+   * subclass, e.g., {@code org.checkerframework.framework.flow.CFAbstractAnalysis}.
+   */
+  public BackwardAnalysisImpl() {
+    super(Direction.BACKWARD);
+  }
+
+  /**
+   * Construct an object that can perform a org.checkerframework.dataflow backward analysis over a
+   * control flow graph given a transfer function.
+   *
+   * @param transferFunction the transfer function
+   */
+  public BackwardAnalysisImpl(@Nullable T transferFunction) {
+    this();
+    this.transferFunction = transferFunction;
+  }
+
+  @Override
+  public void performAnalysis(ControlFlowGraph cfg) {
+    if (isRunning) {
+      throw new BugInCF("performAnalysis() shouldn't be called when the analysis is running.");
+    }
+    isRunning = true;
+    try {
+      init(cfg);
+      while (!worklist.isEmpty()) {
+        Block b = worklist.poll();
+        performAnalysisBlock(b);
+      }
+    } finally {
+      assert isRunning;
+      // In case performAnalysisBlock crashed, reset isRunning to false.
+      isRunning = false;
+    }
+  }
+
+  @Override
+  public void performAnalysisBlock(Block b) {
+    switch (b.getType()) {
+      case REGULAR_BLOCK:
+        {
+          RegularBlock rb = (RegularBlock) b;
+          TransferInput<V, S> inputAfter = getInput(rb);
+          assert inputAfter != null : "@AssumeAssertion(nullness): invariant";
+          currentInput = inputAfter.copy();
+          Node firstNode = null;
+          boolean addToWorklistAgain = false;
+          List<Node> nodeList = rb.getNodes();
+          ListIterator<Node> reverseIter = nodeList.listIterator(nodeList.size());
+          while (reverseIter.hasPrevious()) {
+            Node node = reverseIter.previous();
+            assert currentInput != null : "@AssumeAssertion(nullness): invariant";
+            TransferResult<V, S> transferResult = callTransferFunction(node, currentInput);
+            addToWorklistAgain |= updateNodeValues(node, transferResult);
+            currentInput = new TransferInput<>(node, this, transferResult);
+            firstNode = node;
+          }
+          // Propagate store to predecessors
+          for (Block pred : rb.getPredecessors()) {
+            assert currentInput != null : "@AssumeAssertion(nullness): invariant";
+            propagateStoresTo(
+                pred, firstNode, currentInput, FlowRule.EACH_TO_EACH, addToWorklistAgain);
+          }
+          break;
+        }
+      case EXCEPTION_BLOCK:
+        {
+          ExceptionBlock eb = (ExceptionBlock) b;
+          TransferInput<V, S> inputAfter = getInput(eb);
+          assert inputAfter != null : "@AssumeAssertion(nullness): invariant";
+          currentInput = inputAfter.copy();
+          Node node = eb.getNode();
+          TransferResult<V, S> transferResult = callTransferFunction(node, currentInput);
+          boolean addToWorklistAgain = updateNodeValues(node, transferResult);
+          // Merge transferResult with exceptionStore if there exists one
+          S exceptionStore = exceptionStores.get(eb);
+          S mergedStore =
+              exceptionStore != null
+                  ? transferResult.getRegularStore().leastUpperBound(exceptionStore)
+                  : transferResult.getRegularStore();
+          for (Block pred : eb.getPredecessors()) {
+            addStoreAfter(pred, node, mergedStore, addToWorklistAgain);
+          }
+          break;
+        }
+      case CONDITIONAL_BLOCK:
+        {
+          ConditionalBlock cb = (ConditionalBlock) b;
+          TransferInput<V, S> inputAfter = getInput(cb);
+          assert inputAfter != null : "@AssumeAssertion(nullness): invariant";
+          TransferInput<V, S> input = inputAfter.copy();
+          for (Block pred : cb.getPredecessors()) {
+            propagateStoresTo(pred, null, input, FlowRule.EACH_TO_EACH, false);
+          }
+          break;
+        }
+      case SPECIAL_BLOCK:
+        {
+          // Special basic blocks are empty and cannot throw exceptions,
+          // thus there is no need to perform any analysis.
+          SpecialBlock sb = (SpecialBlock) b;
+          final SpecialBlockType sType = sb.getSpecialType();
+          if (sType == SpecialBlockType.ENTRY) {
+            // storage the store at entry
+            storeAtEntry = outStores.get(sb);
+          } else {
+            assert sType == SpecialBlockType.EXIT || sType == SpecialBlockType.EXCEPTIONAL_EXIT;
+            TransferInput<V, S> input = getInput(sb);
+            assert input != null : "@AssumeAssertion(nullness): invariant";
+            for (Block pred : sb.getPredecessors()) {
+              propagateStoresTo(pred, null, input, FlowRule.EACH_TO_EACH, false);
+            }
+          }
+          break;
+        }
+      default:
+        throw new BugInCF("Unexpected block type: " + b.getType());
+    }
+  }
+
+  @Override
+  public @Nullable TransferInput<V, S> getInput(Block b) {
+    return inputs.get(b);
+  }
+
+  @Override
+  public @Nullable S getEntryStore() {
+    return storeAtEntry;
+  }
+
+  @Override
+  protected void initFields(ControlFlowGraph cfg) {
+    super.initFields(cfg);
+    outStores.clear();
+    exceptionStores.clear();
+    // storeAtEntry is null before analysis begin
+    storeAtEntry = null;
+  }
+
+  @Override
+  @RequiresNonNull("cfg")
+  protected void initInitialInputs() {
+    worklist.process(cfg);
+    SpecialBlock regularExitBlock = cfg.getRegularExitBlock();
+    SpecialBlock exceptionExitBlock = cfg.getExceptionalExitBlock();
+    if (worklist.depthFirstOrder.get(regularExitBlock) == null
+        && worklist.depthFirstOrder.get(exceptionExitBlock) == null) {
+      throw new BugInCF(
+          "regularExitBlock and exceptionExitBlock should never both be null at the same time.");
+    }
+    UnderlyingAST underlyingAST = cfg.getUnderlyingAST();
+    List<ReturnNode> returnNodes = cfg.getReturnNodes();
+    assert transferFunction != null : "@AssumeAssertion(nullness): invariant";
+    S normalInitialStore = transferFunction.initialNormalExitStore(underlyingAST, returnNodes);
+    S exceptionalInitialStore = transferFunction.initialExceptionalExitStore(underlyingAST);
+    // If regularExitBlock or exceptionExitBlock is reachable in the control flow graph, then
+    // initialize it as a start point of the analysis.
+    if (worklist.depthFirstOrder.get(regularExitBlock) != null) {
+      worklist.add(regularExitBlock);
+      inputs.put(regularExitBlock, new TransferInput<>(null, this, normalInitialStore));
+      outStores.put(regularExitBlock, normalInitialStore);
+    }
+    if (worklist.depthFirstOrder.get(exceptionExitBlock) != null) {
+      worklist.add(exceptionExitBlock);
+      inputs.put(exceptionExitBlock, new TransferInput<>(null, this, exceptionalInitialStore));
+      outStores.put(exceptionExitBlock, exceptionalInitialStore);
+    }
+    if (worklist.isEmpty()) {
+      throw new BugInCF("The worklist needs at least one exit block as starting point.");
+    }
+    if (inputs.isEmpty() || outStores.isEmpty()) {
+      throw new BugInCF("At least one input and one output store are required.");
+    }
+  }
+
+  @Override
+  protected void propagateStoresTo(
+      Block pred,
+      @Nullable Node node,
+      TransferInput<V, S> currentInput,
+      FlowRule flowRule,
+      boolean addToWorklistAgain) {
+    if (flowRule != FlowRule.EACH_TO_EACH) {
+      throw new BugInCF(
+          "Backward analysis always propagates EACH to EACH, because there is no control flow.");
+    }
+
+    addStoreAfter(pred, node, currentInput.getRegularStore(), addToWorklistAgain);
+  }
+
+  /**
+   * Add a store after the basic block {@code pred} by merging with the existing stores for that
+   * location.
+   *
+   * @param pred the basic block
+   * @param node the node of the basic block {@code b}
+   * @param s the store being added
+   * @param addBlockToWorklist whether the basic block {@code b} should be added back to {@code
+   *     Worklist}
+   */
+  protected void addStoreAfter(Block pred, @Nullable Node node, S s, boolean addBlockToWorklist) {
+    // If the block pred is an exception block, decide whether the block of passing node is an
+    // exceptional successor of the block pred
+    if (pred instanceof ExceptionBlock
+        && ((ExceptionBlock) pred).getSuccessor() != null
+        && node != null) {
+      @Nullable Block succBlock = ((ExceptionBlock) pred).getSuccessor();
+      @Nullable Block block = node.getBlock();
+      if (succBlock != null && block != null && succBlock.getUid() == block.getUid()) {
+        // If the block of passing node is an exceptional successor of Block pred, propagate
+        // store to the exceptionStores. Currently it doesn't track the label of an
+        // exceptional edge from exception block to its exceptional successors in backward
+        // direction. Instead, all exception stores of exceptional successors of an
+        // exception block will merge to one exception store at the exception block
+        ExceptionBlock ebPred = (ExceptionBlock) pred;
+        S exceptionStore = exceptionStores.get(ebPred);
+        S newExceptionStore = (exceptionStore != null) ? exceptionStore.leastUpperBound(s) : s;
+        if (!newExceptionStore.equals(exceptionStore)) {
+          exceptionStores.put(ebPred, newExceptionStore);
+          inputs.put(ebPred, new TransferInput<V, S>(node, this, newExceptionStore));
+          addBlockToWorklist = true;
+        }
+      }
+    } else {
+      S predOutStore = getStoreAfter(pred);
+      S newPredOutStore = (predOutStore != null) ? predOutStore.leastUpperBound(s) : s;
+      if (!newPredOutStore.equals(predOutStore)) {
+        outStores.put(pred, newPredOutStore);
+        inputs.put(pred, new TransferInput<>(node, this, newPredOutStore));
+        addBlockToWorklist = true;
+      }
+    }
+    if (addBlockToWorklist) {
+      addToWorklist(pred);
+    }
+  }
+
+  /**
+   * Returns the store corresponding to the location right after the basic block {@code b}.
+   *
+   * @param b the given block
+   * @return the store right after the given block
+   */
+  protected @Nullable S getStoreAfter(Block b) {
+    return readFromStore(outStores, b);
+  }
+
+  @Override
+  public S runAnalysisFor(
+      @FindDistinct Node node,
+      Analysis.BeforeOrAfter preOrPost,
+      TransferInput<V, S> blockTransferInput,
+      IdentityHashMap<Node, V> nodeValues,
+      Map<TransferInput<V, S>, IdentityHashMap<Node, TransferResult<V, S>>> analysisCaches) {
+    Block block = node.getBlock();
+    assert block != null : "@AssumeAssertion(nullness): invariant";
+    Node oldCurrentNode = currentNode;
+    if (isRunning) {
+      assert currentInput != null : "@AssumeAssertion(nullness): invariant";
+      return currentInput.getRegularStore();
+    }
+    isRunning = true;
+    try {
+      switch (block.getType()) {
+        case REGULAR_BLOCK:
+          {
+            RegularBlock rBlock = (RegularBlock) block;
+            // Apply transfer function to contents until we found the node we are looking for.
+            TransferInput<V, S> store = blockTransferInput;
+            List<Node> nodeList = rBlock.getNodes();
+            ListIterator<Node> reverseIter = nodeList.listIterator(nodeList.size());
+            while (reverseIter.hasPrevious()) {
+              Node n = reverseIter.previous();
+              setCurrentNode(n);
+              if (n == node && preOrPost == Analysis.BeforeOrAfter.AFTER) {
+                return store.getRegularStore();
+              }
+              // Copy the store to avoid changing other blocks' transfer inputs in
+              // {@link #inputs}
+              TransferResult<V, S> transferResult = callTransferFunction(n, store.copy());
+              if (n == node) {
+                return transferResult.getRegularStore();
+              }
+              store = new TransferInput<>(n, this, transferResult);
+            }
+            throw new BugInCF("node %s is not in node.getBlock()=%s", node, block);
+          }
+        case EXCEPTION_BLOCK:
+          {
+            ExceptionBlock eb = (ExceptionBlock) block;
+            if (eb.getNode() != node) {
+              throw new BugInCF(
+                  "Node should be equal to eb.getNode(). But get: node: "
+                      + node
+                      + "\teb.getNode(): "
+                      + eb.getNode());
+            }
+            if (preOrPost == Analysis.BeforeOrAfter.AFTER) {
+              return blockTransferInput.getRegularStore();
+            }
+            setCurrentNode(node);
+            // Copy the store to avoid changing other blocks' transfer inputs in {@link #inputs}
+            TransferResult<V, S> transferResult =
+                callTransferFunction(node, blockTransferInput.copy());
+            // Merge transfer result with the exception store of this exceptional block
+            S exceptionStore = exceptionStores.get(eb);
+            return exceptionStore == null
+                ? transferResult.getRegularStore()
+                : transferResult.getRegularStore().leastUpperBound(exceptionStore);
+          }
+        default:
+          // Only regular blocks and exceptional blocks can hold nodes.
+          throw new BugInCF("Unexpected block type: " + block.getType());
+      }
+
+    } finally {
+      setCurrentNode(oldCurrentNode);
+      isRunning = false;
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardTransferFunction.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardTransferFunction.java
new file mode 100644
index 0000000..5f19bc4
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardTransferFunction.java
@@ -0,0 +1,40 @@
+package org.checkerframework.dataflow.analysis;
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+
+/**
+ * Interface of a backward transfer function for the abstract interpretation used for the backward
+ * flow analysis.
+ *
+ * <p><em>Important</em>: The individual transfer functions ( {@code visit*}) are allowed to use
+ * (and modify) the stores contained in the argument passed; the ownership is transferred from the
+ * caller to that function.
+ *
+ * @param <V> the abstract value type to be tracked by the analysis
+ * @param <S> the store type used in the analysis
+ */
+public interface BackwardTransferFunction<V extends AbstractValue<V>, S extends Store<S>>
+    extends TransferFunction<V, S> {
+
+  /**
+   * Returns the initial store that should be used at the normal exit block.
+   *
+   * @param underlyingAST the underlying AST of the given control flow graph
+   * @param returnNodes the return nodes of the given control flow graph if the underlying AST of
+   *     this graph is a method. Otherwise will be set to {@code null}
+   * @return the initial store that should be used at the normal exit block
+   */
+  S initialNormalExitStore(UnderlyingAST underlyingAST, @Nullable List<ReturnNode> returnNodes);
+
+  /**
+   * Returns the initial store that should be used at the exceptional exit block or given the
+   * underlying AST of a control flow graph.
+   *
+   * @param underlyingAST the underlying AST of the given control flow graph
+   * @return the initial store that should be used at the exceptional exit block
+   */
+  S initialExceptionalExitStore(UnderlyingAST underlyingAST);
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java
new file mode 100644
index 0000000..6e46afe
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java
@@ -0,0 +1,155 @@
+package org.checkerframework.dataflow.analysis;
+
+import java.util.Map;
+import java.util.StringJoiner;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Implementation of a {@link TransferResult} with two non-exceptional stores. The 'then' store
+ * contains information valid when the previous boolean-valued expression was true, and the 'else'
+ * store contains information valid when the expression was false.
+ *
+ * <p>The result of {@code getRegularStore} will be the least upper bound of the two underlying
+ * stores.
+ *
+ * @param <V> type of the abstract value that is tracked
+ * @param <S> the store type used in the analysis
+ */
+public class ConditionalTransferResult<V extends AbstractValue<V>, S extends Store<S>>
+    extends TransferResult<V, S> {
+
+  /** Whether the store changed. */
+  private final boolean storeChanged;
+
+  /** The 'then' result store. */
+  protected final S thenStore;
+
+  /** The 'else' result store. */
+  protected final S elseStore;
+
+  /**
+   * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)},
+   * using {@code null} for {@link #exceptionalStores}.
+   *
+   * <p><em>Exceptions</em>: If the corresponding {@link
+   * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then it is assumed that no
+   * special handling is necessary and the store before the corresponding {@link
+   * org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge.
+   *
+   * <p><em>Aliasing</em>: {@code thenStore} and {@code elseStore} are not allowed to be used
+   * anywhere outside of this class (including use through aliases). Complete control over the
+   * objects is transferred to this class.
+   *
+   * @param value the abstract value produced by the transfer function
+   * @param thenStore 'then' result store
+   * @param elseStore 'else' result store
+   * @param storeChanged whether the store changed
+   * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)
+   */
+  public ConditionalTransferResult(
+      @Nullable V value, S thenStore, S elseStore, boolean storeChanged) {
+    this(value, thenStore, elseStore, null, storeChanged);
+  }
+
+  /**
+   * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)},
+   * using {@code false} for whether the store changed and {@code null} for {@link
+   * #exceptionalStores}.
+   *
+   * @param value the abstract value produced by the transfer function
+   * @param thenStore {@link #thenStore}
+   * @param elseStore {@link #elseStore}
+   * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)
+   */
+  public ConditionalTransferResult(@Nullable V value, S thenStore, S elseStore) {
+    this(value, thenStore, elseStore, false);
+  }
+
+  /**
+   * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)},
+   * using {@code false} for the {@code storeChanged} formal parameter.
+   *
+   * @param value the abstract value produced by the transfer function
+   * @param thenStore {@link #thenStore}
+   * @param elseStore {@link #elseStore}
+   * @param exceptionalStores {@link #exceptionalStores}
+   * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)
+   */
+  public ConditionalTransferResult(
+      V value, S thenStore, S elseStore, Map<TypeMirror, S> exceptionalStores) {
+    this(value, thenStore, elseStore, exceptionalStores, false);
+  }
+
+  /**
+   * Create a {@code ConditionalTransferResult} with {@code thenStore} as the resulting store if the
+   * corresponding {@link org.checkerframework.dataflow.cfg.node.Node} evaluates to {@code true} and
+   * {@code elseStore} otherwise.
+   *
+   * <p><em>Exceptions</em>: If the corresponding {@link
+   * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then the corresponding store
+   * in {@code exceptionalStores} is used. If no exception is found in {@code exceptionalStores},
+   * then it is assumed that no special handling is necessary and the store before the corresponding
+   * {@link org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge.
+   *
+   * <p><em>Aliasing</em>: {@code thenStore}, {@code elseStore}, and any store in {@code
+   * exceptionalStores} are not allowed to be used anywhere outside of this class (including use
+   * through aliases). Complete control over the objects is transferred to this class.
+   *
+   * @param value the abstract value produced by the transfer function
+   * @param thenStore {@link #thenStore}
+   * @param elseStore {@link #elseStore}
+   * @param exceptionalStores {@link #exceptionalStores}
+   * @param storeChanged whether the store changed; see {@link
+   *     org.checkerframework.dataflow.analysis.TransferResult#storeChanged}.
+   */
+  public ConditionalTransferResult(
+      @Nullable V value,
+      S thenStore,
+      S elseStore,
+      @Nullable Map<TypeMirror, S> exceptionalStores,
+      boolean storeChanged) {
+    super(value, exceptionalStores);
+    this.thenStore = thenStore;
+    this.elseStore = elseStore;
+    this.storeChanged = storeChanged;
+  }
+
+  /** The regular result store. */
+  @Override
+  public S getRegularStore() {
+    return thenStore.leastUpperBound(elseStore);
+  }
+
+  @Override
+  public S getThenStore() {
+    return thenStore;
+  }
+
+  @Override
+  public S getElseStore() {
+    return elseStore;
+  }
+
+  @Override
+  public boolean containsTwoStores() {
+    return true;
+  }
+
+  @Override
+  public String toString() {
+    StringJoiner result = new StringJoiner(System.lineSeparator());
+    result.add("RegularTransferResult(");
+    result.add("  resultValue = " + StringsPlume.indentLinesExceptFirst(2, resultValue));
+    result.add("  thenStore = " + StringsPlume.indentLinesExceptFirst(2, thenStore));
+    result.add("  elseStore = " + StringsPlume.indentLinesExceptFirst(2, elseStore));
+    result.add(")");
+    return result.toString();
+  }
+
+  @Override
+  public boolean storeChanged() {
+    return storeChanged;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysis.java
new file mode 100644
index 0000000..2049448
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysis.java
@@ -0,0 +1,28 @@
+package org.checkerframework.dataflow.analysis;
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * This interface defines a forward analysis, given a control flow graph and a forward transfer
+ * function.
+ *
+ * @param <V> the abstract value type to be tracked by the analysis
+ * @param <S> the store type used in the analysis
+ * @param <T> the forward transfer function type that is used to approximated runtime behavior
+ */
+public interface ForwardAnalysis<
+        V extends AbstractValue<V>, S extends Store<S>, T extends ForwardTransferFunction<V, S>>
+    extends Analysis<V, S, T> {
+
+  /**
+   * Get stores at return statements. These stores are transfer results at return node. Thus for a
+   * forward analysis, these stores contain the analyzed flow information from entry nodes to return
+   * nodes.
+   *
+   * @return the transfer results for each return node in the CFG
+   */
+  List<Pair<ReturnNode, @Nullable TransferResult<V, S>>> getReturnStatementStores();
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java
new file mode 100644
index 0000000..c2412c6
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java
@@ -0,0 +1,557 @@
+package org.checkerframework.dataflow.analysis;
+
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MethodTree;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.block.ConditionalBlock;
+import org.checkerframework.dataflow.cfg.block.ExceptionBlock;
+import org.checkerframework.dataflow.cfg.block.RegularBlock;
+import org.checkerframework.dataflow.cfg.block.SpecialBlock;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.Pair;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * An implementation of a forward analysis to solve a org.checkerframework.dataflow problem given a
+ * control flow graph and a forward transfer function.
+ *
+ * @param <V> the abstract value type to be tracked by the analysis
+ * @param <S> the store type used in the analysis
+ * @param <T> the transfer function type that is used to approximate runtime behavior
+ */
+public class ForwardAnalysisImpl<
+        V extends AbstractValue<V>, S extends Store<S>, T extends ForwardTransferFunction<V, S>>
+    extends AbstractAnalysis<V, S, T> implements ForwardAnalysis<V, S, T> {
+
+  /**
+   * Number of times each block has been analyzed since the last time widening was applied. Null if
+   * maxCountBeforeWidening is -1, which implies widening isn't used for this analysis.
+   */
+  protected final @Nullable IdentityHashMap<Block, Integer> blockCount;
+
+  /**
+   * Number of times a block can be analyzed before widening. -1 implies that widening shouldn't be
+   * used.
+   */
+  protected final int maxCountBeforeWidening;
+
+  /** Then stores before every basic block (assumed to be 'no information' if not present). */
+  protected final IdentityHashMap<Block, S> thenStores;
+
+  /** Else stores before every basic block (assumed to be 'no information' if not present). */
+  protected final IdentityHashMap<Block, S> elseStores;
+
+  /** The stores after every return statement. */
+  protected final IdentityHashMap<ReturnNode, TransferResult<V, S>> storesAtReturnStatements;
+
+  // `@code`, not `@link`, because dataflow module doesn't depend on framework module.
+  /**
+   * Construct an object that can perform a org.checkerframework.dataflow forward analysis over a
+   * control flow graph. When using this constructor, the transfer function is set later by the
+   * subclass, e.g., {@code org.checkerframework.framework.flow.CFAbstractAnalysis}.
+   *
+   * @param maxCountBeforeWidening number of times a block can be analyzed before widening
+   */
+  public ForwardAnalysisImpl(int maxCountBeforeWidening) {
+    super(Direction.FORWARD);
+    this.maxCountBeforeWidening = maxCountBeforeWidening;
+    this.blockCount = maxCountBeforeWidening == -1 ? null : new IdentityHashMap<>();
+    this.thenStores = new IdentityHashMap<>();
+    this.elseStores = new IdentityHashMap<>();
+    this.storesAtReturnStatements = new IdentityHashMap<>();
+  }
+
+  /**
+   * Construct an object that can perform a org.checkerframework.dataflow forward analysis over a
+   * control flow graph given a transfer function.
+   *
+   * @param transfer the transfer function
+   */
+  public ForwardAnalysisImpl(@Nullable T transfer) {
+    this(-1);
+    this.transferFunction = transfer;
+  }
+
+  @Override
+  public void performAnalysis(ControlFlowGraph cfg) {
+    if (isRunning) {
+      throw new BugInCF(
+          "ForwardAnalysisImpl::performAnalysis() shouldn't be called when the analysis is"
+              + " running.");
+    }
+    isRunning = true;
+
+    try {
+      init(cfg);
+      while (!worklist.isEmpty()) {
+        Block b = worklist.poll();
+        performAnalysisBlock(b);
+      }
+    } finally {
+      assert isRunning;
+      // In case performAnalysisBlock crashed, reset isRunning to false.
+      isRunning = false;
+    }
+  }
+
+  @Override
+  public void performAnalysisBlock(Block b) {
+    switch (b.getType()) {
+      case REGULAR_BLOCK:
+        {
+          RegularBlock rb = (RegularBlock) b;
+          // Apply transfer function to contents
+          TransferInput<V, S> inputBefore = getInputBefore(rb);
+          assert inputBefore != null : "@AssumeAssertion(nullness): invariant";
+          currentInput = inputBefore.copy();
+          Node lastNode = null;
+          boolean addToWorklistAgain = false;
+          for (Node n : rb.getNodes()) {
+            assert currentInput != null : "@AssumeAssertion(nullness): invariant";
+            TransferResult<V, S> transferResult = callTransferFunction(n, currentInput);
+            addToWorklistAgain |= updateNodeValues(n, transferResult);
+            currentInput = new TransferInput<>(n, this, transferResult);
+            lastNode = n;
+          }
+          assert currentInput != null : "@AssumeAssertion(nullness): invariant";
+          // Loop will run at least once, making transferResult non-null
+          // Propagate store to successors
+          Block succ = rb.getSuccessor();
+          assert succ != null
+              : "@AssumeAssertion(nullness): regular basic block without non-exceptional successor"
+                  + " unexpected";
+          propagateStoresTo(succ, lastNode, currentInput, rb.getFlowRule(), addToWorklistAgain);
+          break;
+        }
+      case EXCEPTION_BLOCK:
+        {
+          ExceptionBlock eb = (ExceptionBlock) b;
+          // Apply transfer function to content
+          TransferInput<V, S> inputBefore = getInputBefore(eb);
+          assert inputBefore != null : "@AssumeAssertion(nullness): invariant";
+          currentInput = inputBefore.copy();
+          Node node = eb.getNode();
+          TransferResult<V, S> transferResult = callTransferFunction(node, currentInput);
+          boolean addToWorklistAgain = updateNodeValues(node, transferResult);
+          // Propagate store to successor
+          Block succ = eb.getSuccessor();
+          if (succ != null) {
+            currentInput = new TransferInput<>(node, this, transferResult);
+            propagateStoresTo(succ, node, currentInput, eb.getFlowRule(), addToWorklistAgain);
+          }
+          // Propagate store to exceptional successors
+          for (Map.Entry<TypeMirror, Set<Block>> e : eb.getExceptionalSuccessors().entrySet()) {
+            TypeMirror cause = e.getKey();
+            S exceptionalStore = transferResult.getExceptionalStore(cause);
+            if (exceptionalStore != null) {
+              for (Block exceptionSucc : e.getValue()) {
+                addStoreBefore(
+                    exceptionSucc, node, exceptionalStore, Store.Kind.BOTH, addToWorklistAgain);
+              }
+            } else {
+              for (Block exceptionSucc : e.getValue()) {
+                addStoreBefore(
+                    exceptionSucc,
+                    node,
+                    inputBefore.copy().getRegularStore(),
+                    Store.Kind.BOTH,
+                    addToWorklistAgain);
+              }
+            }
+          }
+          break;
+        }
+      case CONDITIONAL_BLOCK:
+        {
+          ConditionalBlock cb = (ConditionalBlock) b;
+          // Get store before
+          TransferInput<V, S> inputBefore = getInputBefore(cb);
+          assert inputBefore != null : "@AssumeAssertion(nullness): invariant";
+          TransferInput<V, S> input = inputBefore.copy();
+          // Propagate store to successor
+          Block thenSucc = cb.getThenSuccessor();
+          Block elseSucc = cb.getElseSuccessor();
+          propagateStoresTo(thenSucc, null, input, cb.getThenFlowRule(), false);
+          propagateStoresTo(elseSucc, null, input, cb.getElseFlowRule(), false);
+          break;
+        }
+      case SPECIAL_BLOCK:
+        {
+          // Special basic blocks are empty and cannot throw exceptions,
+          // thus there is no need to perform any analysis.
+          SpecialBlock sb = (SpecialBlock) b;
+          Block succ = sb.getSuccessor();
+          if (succ != null) {
+            TransferInput<V, S> input = getInputBefore(b);
+            assert input != null : "@AssumeAssertion(nullness): invariant";
+            propagateStoresTo(succ, null, input, sb.getFlowRule(), false);
+          }
+          break;
+        }
+      default:
+        throw new BugInCF("Unexpected block type: " + b.getType());
+    }
+  }
+
+  @Override
+  public @Nullable TransferInput<V, S> getInput(Block b) {
+    return getInputBefore(b);
+  }
+
+  @Override
+  @SuppressWarnings("nullness:contracts.precondition.override") // implementation field
+  @RequiresNonNull("cfg")
+  public List<Pair<ReturnNode, @Nullable TransferResult<V, S>>> getReturnStatementStores() {
+    return CollectionsPlume.<ReturnNode, Pair<ReturnNode, @Nullable TransferResult<V, S>>>mapList(
+        returnNode -> Pair.of(returnNode, storesAtReturnStatements.get(returnNode)),
+        cfg.getReturnNodes());
+  }
+
+  @Override
+  public S runAnalysisFor(
+      @FindDistinct Node node,
+      Analysis.BeforeOrAfter preOrPost,
+      TransferInput<V, S> blockTransferInput,
+      IdentityHashMap<Node, V> nodeValues,
+      Map<TransferInput<V, S>, IdentityHashMap<Node, TransferResult<V, S>>> analysisCaches) {
+    Block block = node.getBlock();
+    assert block != null : "@AssumeAssertion(nullness): invariant";
+    Node oldCurrentNode = currentNode;
+
+    // Prepare cache
+    IdentityHashMap<Node, TransferResult<V, S>> cache;
+    if (analysisCaches != null) {
+      cache = analysisCaches.computeIfAbsent(blockTransferInput, __ -> new IdentityHashMap<>());
+    } else {
+      cache = null;
+    }
+
+    if (isRunning) {
+      assert currentInput != null : "@AssumeAssertion(nullness): invariant";
+      return currentInput.getRegularStore();
+    }
+    setNodeValues(nodeValues);
+    isRunning = true;
+    try {
+      switch (block.getType()) {
+        case REGULAR_BLOCK:
+          {
+            RegularBlock rb = (RegularBlock) block;
+            // Apply transfer function to contents until we found the node we are looking for.
+            TransferInput<V, S> store = blockTransferInput;
+            TransferResult<V, S> transferResult;
+            for (Node n : rb.getNodes()) {
+              setCurrentNode(n);
+              if (n == node && preOrPost == Analysis.BeforeOrAfter.BEFORE) {
+                return store.getRegularStore();
+              }
+              if (cache != null && cache.containsKey(n)) {
+                transferResult = cache.get(n);
+              } else {
+                // Copy the store to avoid changing other blocks' transfer inputs in {@link #inputs}
+                transferResult = callTransferFunction(n, store.copy());
+                if (cache != null) {
+                  cache.put(n, transferResult);
+                }
+              }
+              if (n == node) {
+                return transferResult.getRegularStore();
+              }
+              store = new TransferInput<>(n, this, transferResult);
+            }
+            throw new BugInCF("node %s is not in node.getBlock()=%s", node, block);
+          }
+        case EXCEPTION_BLOCK:
+          {
+            ExceptionBlock eb = (ExceptionBlock) block;
+            // Apply the transfer function to content
+            if (eb.getNode() != node) {
+              throw new BugInCF(
+                  "Node should be equal to eb.getNode(). But get: node: "
+                      + node
+                      + "\teb.getNode(): "
+                      + eb.getNode());
+            }
+            if (preOrPost == Analysis.BeforeOrAfter.BEFORE) {
+              return blockTransferInput.getRegularStore();
+            }
+            setCurrentNode(node);
+            // Copy the store to avoid changing other blocks' transfer inputs in {@link #inputs}
+            TransferResult<V, S> transferResult;
+            if (cache != null && cache.containsKey(node)) {
+              transferResult = cache.get(node);
+            } else {
+              // Copy the store to avoid changing other blocks' transfer inputs in {@link #inputs}
+              transferResult = callTransferFunction(node, blockTransferInput.copy());
+              if (cache != null) {
+                cache.put(node, transferResult);
+              }
+            }
+            return transferResult.getRegularStore();
+          }
+        default:
+          // Only regular blocks and exceptional blocks can hold nodes.
+          throw new BugInCF("Unexpected block type: " + block.getType());
+      }
+    } finally {
+      setCurrentNode(oldCurrentNode);
+      isRunning = false;
+    }
+  }
+
+  @Override
+  protected void initFields(ControlFlowGraph cfg) {
+    thenStores.clear();
+    elseStores.clear();
+    if (blockCount != null) {
+      blockCount.clear();
+    }
+    storesAtReturnStatements.clear();
+    super.initFields(cfg);
+  }
+
+  @Override
+  @RequiresNonNull("cfg")
+  protected void initInitialInputs() {
+    worklist.process(cfg);
+    Block entry = cfg.getEntryBlock();
+    worklist.add(entry);
+    UnderlyingAST underlyingAST = cfg.getUnderlyingAST();
+    List<LocalVariableNode> parameters = getParameters(underlyingAST);
+    assert transferFunction != null : "@AssumeAssertion(nullness): invariant";
+    S initialStore = transferFunction.initialStore(underlyingAST, parameters);
+    thenStores.put(entry, initialStore);
+    elseStores.put(entry, initialStore);
+    inputs.put(entry, new TransferInput<>(null, this, initialStore));
+  }
+
+  /**
+   * Returns the formal parameters for a method.
+   *
+   * @param underlyingAST the AST for the method
+   * @return the formal parameters for the method
+   */
+  @SideEffectFree
+  private List<LocalVariableNode> getParameters(UnderlyingAST underlyingAST) {
+    switch (underlyingAST.getKind()) {
+      case METHOD:
+        MethodTree tree = ((CFGMethod) underlyingAST).getMethod();
+        // TODO: document that LocalVariableNode has no block that it belongs to
+        return CollectionsPlume.mapList(LocalVariableNode::new, tree.getParameters());
+      case LAMBDA:
+        LambdaExpressionTree lambda = ((CFGLambda) underlyingAST).getLambdaTree();
+        // TODO: document that LocalVariableNode has no block that it belongs to
+        return CollectionsPlume.mapList(LocalVariableNode::new, lambda.getParameters());
+      default:
+        return Collections.emptyList();
+    }
+  }
+
+  @Override
+  protected TransferResult<V, S> callTransferFunction(Node node, TransferInput<V, S> input) {
+    TransferResult<V, S> transferResult = super.callTransferFunction(node, input);
+
+    if (node instanceof ReturnNode) {
+      // Save a copy of the store to later check if some property holds at a given return statement
+      storesAtReturnStatements.put((ReturnNode) node, transferResult);
+    }
+    return transferResult;
+  }
+
+  @Override
+  protected void propagateStoresTo(
+      Block succ,
+      @Nullable Node node,
+      TransferInput<V, S> currentInput,
+      Store.FlowRule flowRule,
+      boolean addToWorklistAgain) {
+    switch (flowRule) {
+      case EACH_TO_EACH:
+        if (currentInput.containsTwoStores()) {
+          addStoreBefore(
+              succ, node, currentInput.getThenStore(), Store.Kind.THEN, addToWorklistAgain);
+          addStoreBefore(
+              succ, node, currentInput.getElseStore(), Store.Kind.ELSE, addToWorklistAgain);
+        } else {
+          addStoreBefore(
+              succ, node, currentInput.getRegularStore(), Store.Kind.BOTH, addToWorklistAgain);
+        }
+        break;
+      case THEN_TO_BOTH:
+        addStoreBefore(
+            succ, node, currentInput.getThenStore(), Store.Kind.BOTH, addToWorklistAgain);
+        break;
+      case ELSE_TO_BOTH:
+        addStoreBefore(
+            succ, node, currentInput.getElseStore(), Store.Kind.BOTH, addToWorklistAgain);
+        break;
+      case THEN_TO_THEN:
+        addStoreBefore(
+            succ, node, currentInput.getThenStore(), Store.Kind.THEN, addToWorklistAgain);
+        break;
+      case ELSE_TO_ELSE:
+        addStoreBefore(
+            succ, node, currentInput.getElseStore(), Store.Kind.ELSE, addToWorklistAgain);
+        break;
+      case BOTH_TO_THEN:
+        addStoreBefore(
+            succ, node, currentInput.getRegularStore(), Store.Kind.THEN, addToWorklistAgain);
+        break;
+      case BOTH_TO_ELSE:
+        addStoreBefore(
+            succ, node, currentInput.getRegularStore(), Store.Kind.ELSE, addToWorklistAgain);
+        break;
+    }
+  }
+
+  /**
+   * Add a store before the basic block {@code b} by merging with the existing stores for that
+   * location.
+   *
+   * @param b a basic block
+   * @param node the node of the basic block {@code b}
+   * @param s the store being added
+   * @param kind the kind of store {@code s}
+   * @param addBlockToWorklist whether the basic block {@code b} should be added back to {@code
+   *     Worklist}
+   */
+  protected void addStoreBefore(
+      Block b, @Nullable Node node, S s, Store.Kind kind, boolean addBlockToWorklist) {
+    S thenStore = getStoreBefore(b, Store.Kind.THEN);
+    S elseStore = getStoreBefore(b, Store.Kind.ELSE);
+    boolean shouldWiden = false;
+    if (blockCount != null) {
+      Integer count = blockCount.getOrDefault(b, 0);
+      shouldWiden = count >= maxCountBeforeWidening;
+      if (shouldWiden) {
+        blockCount.put(b, 0);
+      } else {
+        blockCount.put(b, count + 1);
+      }
+    }
+    switch (kind) {
+      case THEN:
+        {
+          // Update the then store
+          S newThenStore = mergeStores(s, thenStore, shouldWiden);
+          if (!newThenStore.equals(thenStore)) {
+            thenStores.put(b, newThenStore);
+            if (elseStore != null) {
+              inputs.put(b, new TransferInput<>(node, this, newThenStore, elseStore));
+              addBlockToWorklist = true;
+            }
+          }
+          break;
+        }
+      case ELSE:
+        {
+          // Update the else store
+          S newElseStore = mergeStores(s, elseStore, shouldWiden);
+          if (!newElseStore.equals(elseStore)) {
+            elseStores.put(b, newElseStore);
+            if (thenStore != null) {
+              inputs.put(b, new TransferInput<>(node, this, thenStore, newElseStore));
+              addBlockToWorklist = true;
+            }
+          }
+          break;
+        }
+      case BOTH:
+        @SuppressWarnings("interning:not.interned")
+        boolean sameStore = (thenStore == elseStore);
+        if (sameStore) {
+          // Currently there is only one regular store
+          S newStore = mergeStores(s, thenStore, shouldWiden);
+          if (!newStore.equals(thenStore)) {
+            thenStores.put(b, newStore);
+            elseStores.put(b, newStore);
+            inputs.put(b, new TransferInput<>(node, this, newStore));
+            addBlockToWorklist = true;
+          }
+        } else {
+          boolean storeChanged = false;
+          S newThenStore = mergeStores(s, thenStore, shouldWiden);
+          if (!newThenStore.equals(thenStore)) {
+            thenStores.put(b, newThenStore);
+            storeChanged = true;
+          }
+          S newElseStore = mergeStores(s, elseStore, shouldWiden);
+          if (!newElseStore.equals(elseStore)) {
+            elseStores.put(b, newElseStore);
+            storeChanged = true;
+          }
+          if (storeChanged) {
+            inputs.put(b, new TransferInput<>(node, this, newThenStore, newElseStore));
+            addBlockToWorklist = true;
+          }
+        }
+    }
+    if (addBlockToWorklist) {
+      addToWorklist(b);
+    }
+  }
+
+  /**
+   * Merge two stores, possibly widening the result.
+   *
+   * @param newStore the new Store
+   * @param previousStore the previous Store
+   * @param shouldWiden should widen or not
+   * @return the merged Store
+   */
+  private S mergeStores(S newStore, @Nullable S previousStore, boolean shouldWiden) {
+    if (previousStore == null) {
+      return newStore;
+    } else if (shouldWiden) {
+      return newStore.widenedUpperBound(previousStore);
+    } else {
+      return newStore.leastUpperBound(previousStore);
+    }
+  }
+
+  /**
+   * Return the store corresponding to the location right before the basic block {@code b}.
+   *
+   * @param b a block
+   * @param kind the kind of store which will be returned
+   * @return the store corresponding to the location right before the basic block {@code b}
+   */
+  protected @Nullable S getStoreBefore(Block b, Store.Kind kind) {
+    switch (kind) {
+      case THEN:
+        return readFromStore(thenStores, b);
+      case ELSE:
+        return readFromStore(elseStores, b);
+      default:
+        throw new BugInCF("Unexpected Store.Kind: " + kind);
+    }
+  }
+
+  /**
+   * Returns the transfer input corresponding to the location right before the basic block {@code
+   * b}.
+   *
+   * @param b a block
+   * @return the transfer input corresponding to the location right before the basic block {@code b}
+   */
+  protected @Nullable TransferInput<V, S> getInputBefore(Block b) {
+    return inputs.get(b);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardTransferFunction.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardTransferFunction.java
new file mode 100644
index 0000000..6bd3f5c
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardTransferFunction.java
@@ -0,0 +1,31 @@
+package org.checkerframework.dataflow.analysis;
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+
+/**
+ * Interface of a forward transfer function for the abstract interpretation used for the forward
+ * flow analysis.
+ *
+ * <p><em>Important</em>: The individual transfer functions ( {@code visit*}) are allowed to use
+ * (and modify) the stores contained in the argument passed; the ownership is transferred from the
+ * caller to that function.
+ *
+ * @param <V> the abstract value type to be tracked by the analysis
+ * @param <S> the store type used in the analysis
+ */
+public interface ForwardTransferFunction<V extends AbstractValue<V>, S extends Store<S>>
+    extends TransferFunction<V, S> {
+
+  /**
+   * Returns the initial store to be used by the org.checkerframework.dataflow analysis. {@code
+   * parameters} is non-null if the underlying AST is a method.
+   *
+   * @param underlyingAST an abstract syntax tree
+   * @param parameters a list of local variable nodes
+   * @return the initial store
+   */
+  S initialStore(UnderlyingAST underlyingAST, @Nullable List<LocalVariableNode> parameters);
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java
new file mode 100644
index 0000000..120347e
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java
@@ -0,0 +1,154 @@
+package org.checkerframework.dataflow.analysis;
+
+import java.util.Map;
+import java.util.StringJoiner;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Implementation of a {@link TransferResult} with just one non-exceptional store. The result of
+ * {@code getThenStore} and {@code getElseStore} is equal to the only underlying store.
+ *
+ * @param <V> type of the abstract value that is tracked
+ * @param <S> the store type used in the analysis
+ */
+public class RegularTransferResult<V extends AbstractValue<V>, S extends Store<S>>
+    extends TransferResult<V, S> {
+
+  /** The regular result store. */
+  protected final S store;
+
+  /**
+   * Whether the store changed; see {@link
+   * org.checkerframework.dataflow.analysis.TransferResult#storeChanged}.
+   */
+  private final boolean storeChanged;
+
+  /**
+   * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code
+   * null} for {@link org.checkerframework.dataflow.analysis.TransferResult#exceptionalStores}.
+   *
+   * <p><em>Exceptions</em>: If the corresponding {@link
+   * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then it is assumed that no
+   * special handling is necessary and the store before the corresponding {@link
+   * org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge.
+   *
+   * <p><em>Aliasing</em>: {@code resultStore} is not allowed to be used anywhere outside of this
+   * class (including use through aliases). Complete control over the object is transferred to this
+   * class.
+   *
+   * @param value the abstract value produced by the transfer function
+   * @param resultStore regular result store
+   * @param storeChanged whether the store changed; see {@link
+   *     org.checkerframework.dataflow.analysis.TransferResult#storeChanged}
+   * @see #RegularTransferResult(AbstractValue, Store, Map, boolean)
+   */
+  public RegularTransferResult(@Nullable V value, S resultStore, boolean storeChanged) {
+    this(value, resultStore, null, storeChanged);
+  }
+
+  /**
+   * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code
+   * null} for {@link org.checkerframework.dataflow.analysis.TransferResult#exceptionalStores} and
+   * {@code false} for {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}.
+   *
+   * @param value the abstract value produced by the transfer function
+   * @param resultStore regular result store
+   * @see #RegularTransferResult(AbstractValue, Store, Map, boolean)
+   */
+  public RegularTransferResult(@Nullable V value, S resultStore) {
+    this(value, resultStore, false);
+  }
+
+  /**
+   * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code
+   * false} for {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}.
+   *
+   * @param value the abstract value produced by the transfer function
+   * @param resultStore the regular result store
+   * @param exceptionalStores the stores in case the basic block throws an exception, or null if the
+   *     basic block does not throw any exceptions
+   * @see #RegularTransferResult(AbstractValue, Store, Map, boolean)
+   */
+  public RegularTransferResult(
+      @Nullable V value, S resultStore, Map<TypeMirror, S> exceptionalStores) {
+    this(value, resultStore, exceptionalStores, false);
+  }
+
+  /**
+   * Create a {@code TransferResult} with {@code resultStore} as the resulting store. If the
+   * corresponding {@link org.checkerframework.dataflow.cfg.node.Node} is a boolean node, then
+   * {@code resultStore} is used for both the 'then' and 'else' edge.
+   *
+   * <p><em>Exceptions</em>: If the corresponding {@link
+   * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then the corresponding store
+   * in {@code exceptionalStores} is used. If no exception is found in {@code exceptionalStores},
+   * then it is assumed that no special handling is necessary and the store before the corresponding
+   * {@link org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge.
+   *
+   * <p><em>Aliasing</em>: {@code resultStore} and any store in {@code exceptionalStores} are not
+   * allowed to be used anywhere outside of this class (including use through aliases). Complete
+   * control over the objects is transferred to this class.
+   *
+   * @param value the abstract value produced by the transfer function
+   * @param resultStore the regular result store
+   * @param exceptionalStores the stores in case the basic block throws an exception, or null if the
+   *     basic block does not throw any exceptions
+   * @param storeChanged see {@link
+   *     org.checkerframework.dataflow.analysis.TransferResult#storeChanged}
+   */
+  public RegularTransferResult(
+      @Nullable V value,
+      S resultStore,
+      @Nullable Map<TypeMirror, S> exceptionalStores,
+      boolean storeChanged) {
+    super(value, exceptionalStores);
+    this.store = resultStore;
+    this.storeChanged = storeChanged;
+  }
+
+  /** The regular result store. */
+  @Override
+  public S getRegularStore() {
+    return store;
+  }
+
+  @Override
+  public S getThenStore() {
+    return store;
+  }
+
+  @Override
+  public S getElseStore() {
+    // copy the store such that it is the same as the result of getThenStore
+    // (that is, identical according to equals), but two different objects.
+    return store.copy();
+  }
+
+  @Override
+  public boolean containsTwoStores() {
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    StringJoiner result = new StringJoiner(System.lineSeparator());
+    result.add("RegularTransferResult(");
+    result.add("  resultValue = " + StringsPlume.indentLinesExceptFirst(2, resultValue));
+    // "toString().trim()" works around bug where toString ends with a newline.
+    result.add(
+        "  store = " + StringsPlume.indentLinesExceptFirst(2, store.toString().trim()) + ")");
+    return result.toString();
+  }
+
+  /**
+   * See {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged()}.
+   *
+   * @see org.checkerframework.dataflow.analysis.TransferResult#storeChanged()
+   */
+  @Override
+  public boolean storeChanged() {
+    return storeChanged;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Store.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Store.java
new file mode 100644
index 0000000..5fdc6a6
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Store.java
@@ -0,0 +1,106 @@
+package org.checkerframework.dataflow.analysis;
+
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
+import org.checkerframework.dataflow.expression.JavaExpression;
+
+/**
+ * A store is used to keep track of the information that the org.checkerframework.dataflow analysis
+ * has accumulated at any given point in time.
+ *
+ * @param <S> the type of the store returned by {@code copy} and that is used in {@code
+ *     leastUpperBound}. Usually it is the implementing class itself, e.g. in {@code T extends
+ *     Store<T>}.
+ */
+public interface Store<S extends Store<S>> {
+
+  // We maintain a then store and an else store before each basic block.
+  // When they are identical (by reference equality), they can be treated
+  // as a regular unconditional store.
+  // Once we have some information for both the then and else store, we
+  // create a TransferInput for the block and allow it to be analyzed.
+  public static enum Kind {
+    THEN,
+    ELSE,
+    BOTH
+  }
+
+  /** A flow rule describes how stores flow along one edge between basic blocks. */
+  public static enum FlowRule {
+    /**
+     * The normal case: then store flows to the then store, and else store flows to the else store.
+     */
+    EACH_TO_EACH,
+    /** Then store flows to both then and else of successor. */
+    THEN_TO_BOTH,
+    /** Else store flows to both then and else of successor. */
+    ELSE_TO_BOTH,
+    /** Then store flows to the then of successor. Else store is ignored. */
+    THEN_TO_THEN,
+    /** Else store flows to the else of successor. Then store is ignored. */
+    ELSE_TO_ELSE,
+    /** Both stores flow to the then of successor. */
+    BOTH_TO_THEN,
+    /** Both stores flow to the else of successor. */
+    BOTH_TO_ELSE,
+  }
+
+  /**
+   * Returns an exact copy of this store.
+   *
+   * @return an exact copy of this store
+   */
+  S copy();
+
+  /**
+   * Compute the least upper bound of two stores.
+   *
+   * <p><em>Important</em>: This method must fulfill the following contract:
+   *
+   * <ul>
+   *   <li>Does not change {@code this}.
+   *   <li>Does not change {@code other}.
+   *   <li>Returns a fresh object which is not aliased yet.
+   *   <li>Returns an object of the same (dynamic) type as {@code this}, even if the signature is
+   *       more permissive.
+   *   <li>Is commutative.
+   * </ul>
+   */
+  S leastUpperBound(S other);
+
+  /**
+   * Compute an upper bound of two stores that is wider than the least upper bound of the two
+   * stores. Used to jump to a higher abstraction to allow faster termination of the fixed point
+   * computations in {@link Analysis}. {@code previous} must be the previous store.
+   *
+   * <p>A particular analysis might not require widening and should implement this method by calling
+   * leastUpperBound.
+   *
+   * <p><em>Important</em>: This method must fulfill the following contract:
+   *
+   * <ul>
+   *   <li>Does not change {@code this}.
+   *   <li>Does not change {@code previous}.
+   *   <li>Returns a fresh object which is not aliased yet.
+   *   <li>Returns an object of the same (dynamic) type as {@code this}, even if the signature is
+   *       more permissive.
+   *   <li>Is commutative.
+   * </ul>
+   *
+   * @param previous must be the previous store
+   */
+  S widenedUpperBound(S previous);
+
+  /**
+   * Can the objects {@code a} and {@code b} be aliases? Returns a conservative answer (i.e.,
+   * returns {@code true} if not enough information is available to determine aliasing).
+   */
+  boolean canAlias(JavaExpression a, JavaExpression b);
+
+  /**
+   * Delegate visualization responsibility to a visualizer.
+   *
+   * @param viz the visualizer to visualize this store
+   * @return the String representation of this store
+   */
+  String visualize(CFGVisualizer<?, S, ?> viz);
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferFunction.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferFunction.java
new file mode 100644
index 0000000..0469ede
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferFunction.java
@@ -0,0 +1,32 @@
+package org.checkerframework.dataflow.analysis;
+
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.NodeVisitor;
+
+/**
+ * Interface of a transfer function for the abstract interpretation used for the flow analysis.
+ *
+ * <p>A transfer function consists of the following components:
+ *
+ * <ul>
+ *   <li>Initial store method(s) that determines which initial store should be used in the
+ *       org.checkerframework.dataflow analysis.
+ *   <li>A function for every {@link Node} type that determines the behavior of the
+ *       org.checkerframework.dataflow analysis in that case. This method takes a {@link Node} and
+ *       an incoming store, and produces a {@link RegularTransferResult}.
+ * </ul>
+ *
+ * <p><em>Note</em>: Initial store method(s) are different between forward and backward transfer
+ * functions. Thus, this interface doesn't define any initial store method(s). {@link
+ * ForwardTransferFunction} and {@link BackwardTransferFunction} will create their own initial store
+ * method(s).
+ *
+ * <p><em>Important</em>: The individual transfer functions ( {@code visit*}) are allowed to use
+ * (and modify) the stores contained in the argument passed; the ownership is transferred from the
+ * caller to that function.
+ *
+ * @param <V> type of the abstract value that is tracked
+ * @param <S> the store type used in the analysis
+ */
+public interface TransferFunction<V extends AbstractValue<V>, S extends Store<S>>
+    extends NodeVisitor<TransferResult<V, S>, TransferInput<V, S>> {}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java
new file mode 100644
index 0000000..3d469db
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java
@@ -0,0 +1,313 @@
+package org.checkerframework.dataflow.analysis;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.plumelib.util.StringsPlume;
+import org.plumelib.util.UniqueId;
+
+/**
+ * {@code TransferInput} is used as the input type of the individual transfer functions of a {@link
+ * ForwardTransferFunction} or a {@link BackwardTransferFunction}. It also contains a reference to
+ * the node for which the transfer function will be applied.
+ *
+ * <p>A {@code TransferInput} contains one or two stores. If two stores are present, one belongs to
+ * 'then', and the other to 'else'.
+ *
+ * @param <V> type of the abstract value that is tracked
+ * @param <S> the store type used in the analysis
+ */
+public class TransferInput<V extends AbstractValue<V>, S extends Store<S>> implements UniqueId {
+
+  /** The corresponding node. */
+  // TODO: explain when the node is changed.
+  protected @Nullable Node node;
+
+  /**
+   * The regular result store (or {@code null} if none is present, because {@link #thenStore} and
+   * {@link #elseStore} are set). The following invariant is maintained:
+   *
+   * <pre><code>
+   * store == null &hArr; thenStore != null &amp;&amp; elseStore != null
+   * </code></pre>
+   */
+  protected final @Nullable S store;
+
+  /**
+   * The 'then' result store (or {@code null} if none is present). See invariant at {@link #store}.
+   */
+  protected final @Nullable S thenStore;
+
+  /**
+   * The 'else' result store (or {@code null} if none is present). See invariant at {@link #store}.
+   */
+  protected final @Nullable S elseStore;
+
+  /** The corresponding analysis class to get intermediate flow results. */
+  protected final Analysis<V, S, ?> analysis;
+
+  /** The unique ID for the next-created object. */
+  static final AtomicLong nextUid = new AtomicLong(0);
+  /** The unique ID of this object. */
+  final transient long uid = nextUid.getAndIncrement();
+
+  @Override
+  public long getUid(@UnknownInitialization TransferInput<V, S> this) {
+    return uid;
+  }
+
+  /**
+   * Private helper constructor; all TransferInput construction bottoms out here.
+   *
+   * @param node the corresponding node
+   * @param store the regular result store, or {@code null} if none is present
+   * @param thenStore the 'then' result store, or {@code null} if none is present
+   * @param elseStore the 'else' result store, or {@code null} if none is present
+   * @param analysis analysis the corresponding analysis class to get intermediate flow results
+   */
+  private TransferInput(
+      @Nullable Node node,
+      @Nullable S store,
+      @Nullable S thenStore,
+      @Nullable S elseStore,
+      Analysis<V, S, ?> analysis) {
+    if (store == null) {
+      assert thenStore != null && elseStore != null;
+    } else {
+      assert thenStore == null && elseStore == null;
+    }
+    this.node = node;
+    this.store = store;
+    this.thenStore = thenStore;
+    this.elseStore = elseStore;
+    this.analysis = analysis;
+  }
+
+  /**
+   * Create a {@link TransferInput}, given a {@link TransferResult} and a node-value mapping.
+   *
+   * <p><em>Aliasing</em>: The stores returned by any methods of {@code to} will be stored
+   * internally and are not allowed to be used elsewhere. Full control of them is transferred to
+   * this object.
+   *
+   * <p>The node-value mapping {@code nodeValues} is provided by the analysis and is only read from
+   * within this {@link TransferInput}.
+   *
+   * @param n {@link #node}
+   * @param analysis {@link #analysis}
+   * @param to a transfer result
+   */
+  public TransferInput(Node n, Analysis<V, S, ?> analysis, TransferResult<V, S> to) {
+    this(
+        n,
+        to.containsTwoStores() ? null : to.getRegularStore(),
+        to.containsTwoStores() ? to.getThenStore() : null,
+        to.containsTwoStores() ? to.getElseStore() : null,
+        analysis);
+  }
+
+  /**
+   * Create a {@link TransferInput}, given a store and a node-value mapping.
+   *
+   * <p><em>Aliasing</em>: The store {@code s} will be stored internally and is not allowed to be
+   * used elsewhere. Full control over {@code s} is transferred to this object.
+   *
+   * <p>The node-value mapping {@code nodeValues} is provided by the analysis and is only read from
+   * within this {@link TransferInput}.
+   *
+   * @param n {@link #node}
+   * @param analysis {@link #analysis}
+   * @param s {@link #store}
+   */
+  public TransferInput(@Nullable Node n, Analysis<V, S, ?> analysis, S s) {
+    this(n, s, null, null, analysis);
+  }
+
+  /**
+   * Create a {@link TransferInput}, given two stores and a node-value mapping.
+   *
+   * <p><em>Aliasing</em>: The two stores {@code s1} and {@code s2} will be stored internally and
+   * are not allowed to be used elsewhere. Full control of them is transferred to this object.
+   *
+   * @param n a node
+   * @param analysis {@link #analysis}
+   * @param s1 {@link #thenStore}
+   * @param s2 {@link #elseStore}
+   */
+  public TransferInput(@Nullable Node n, Analysis<V, S, ?> analysis, S s1, S s2) {
+    this(n, null, s1, s2, analysis);
+  }
+
+  /**
+   * Copy constructor.
+   *
+   * @param from a {@link TransferInput} to copy
+   */
+  @SuppressWarnings("nullness:dereference.of.nullable") // object invariant: store vs thenStore
+  protected TransferInput(TransferInput<V, S> from) {
+    this(
+        from.node,
+        from.store == null ? null : from.store.copy(),
+        from.store == null ? from.thenStore.copy() : null,
+        from.store == null ? from.elseStore.copy() : null,
+        from.analysis);
+  }
+
+  /**
+   * Returns the {@link Node} for this {@link TransferInput}.
+   *
+   * @return the {@link Node} for this {@link TransferInput}
+   */
+  public @Nullable Node getNode() {
+    return node;
+  }
+
+  /**
+   * Returns the abstract value of node {@code n}, which is required to be a 'sub-node' (that is, a
+   * direct or indirect child) of the node this transfer input is associated with. Furthermore,
+   * {@code n} cannot be a l-value node. Returns {@code null} if no value is available.
+   *
+   * @param n a node
+   * @return the abstract value of node {@code n}, or {@code null} if no value is available
+   */
+  public @Nullable V getValueOfSubNode(Node n) {
+    return analysis.getValue(n);
+  }
+
+  /**
+   * Returns the regular result store produced if no exception is thrown by the {@link Node}
+   * corresponding to this transfer function result.
+   *
+   * @return the regular result store produced if no exception is thrown by the {@link Node}
+   *     corresponding to this transfer function result
+   */
+  public S getRegularStore() {
+    if (store == null) {
+      assert thenStore != null && elseStore != null : "@AssumeAssertion(nullness): invariant";
+      return thenStore.leastUpperBound(elseStore);
+    } else {
+      return store;
+    }
+  }
+
+  /**
+   * Returns the result store produced if the {@link Node} this result belongs to evaluates to
+   * {@code true}.
+   *
+   * @return the result store produced if the {@link Node} this result belongs to evaluates to
+   *     {@code true}
+   */
+  public S getThenStore() {
+    if (store == null) {
+      assert thenStore != null : "@AssumeAssertion(nullness): invariant";
+      return thenStore;
+    }
+    return store;
+  }
+
+  /**
+   * Returns the result store produced if the {@link Node} this result belongs to evaluates to
+   * {@code false}.
+   *
+   * @return the result store produced if the {@link Node} this result belongs to evaluates to
+   *     {@code false}
+   */
+  public S getElseStore() {
+    if (store == null) {
+      assert elseStore != null : "@AssumeAssertion(nullness): invariant";
+      return elseStore;
+    }
+    // copy the store such that it is the same as the result of getThenStore
+    // (that is, identical according to equals), but two different objects.
+    return store.copy();
+  }
+
+  /**
+   * Returns {@code true} if and only if this transfer input contains two stores that are
+   * potentially not equal. Note that the result {@code true} does not imply that {@code
+   * getRegularStore} cannot be called (or vice versa for {@code false}). Rather, it indicates that
+   * {@code getThenStore} or {@code getElseStore} can be used to give more precise results.
+   * Otherwise, if the result is {@code false}, then all three methods {@code getRegularStore},
+   * {@code getThenStore}, and {@code getElseStore} return equivalent stores.
+   *
+   * @return {@code true} if and only if this transfer input contains two stores that are
+   *     potentially not equal
+   */
+  public boolean containsTwoStores() {
+    return store == null;
+  }
+
+  /**
+   * Returns an exact copy of this store.
+   *
+   * @return an exact copy of this store
+   */
+  public TransferInput<V, S> copy() {
+    return new TransferInput<>(this);
+  }
+
+  /**
+   * Compute the least upper bound of two stores.
+   *
+   * <p><em>Important</em>: This method must fulfill the same contract as {@code leastUpperBound} of
+   * {@link Store}.
+   *
+   * @param other a transfer input
+   * @return the least upper bound of this and {@code other}
+   */
+  public TransferInput<V, S> leastUpperBound(TransferInput<V, S> other) {
+    if (store == null) {
+      S newThenStore = getThenStore().leastUpperBound(other.getThenStore());
+      S newElseStore = getElseStore().leastUpperBound(other.getElseStore());
+      return new TransferInput<>(node, analysis, newThenStore, newElseStore);
+    } else {
+      if (other.store == null) {
+        // make sure we do not lose precision and keep two stores if at
+        // least one of the two TransferInput's has two stores.
+        return other.leastUpperBound(this);
+      }
+      return new TransferInput<>(node, analysis, store.leastUpperBound(other.getRegularStore()));
+    }
+  }
+
+  @Override
+  public boolean equals(@Nullable Object o) {
+    if (o instanceof TransferInput) {
+      @SuppressWarnings("unchecked")
+      TransferInput<V, S> other = (TransferInput<V, S>) o;
+      if (containsTwoStores()) {
+        if (other.containsTwoStores()) {
+          return getThenStore().equals(other.getThenStore())
+              && getElseStore().equals(other.getElseStore());
+        }
+      } else {
+        if (!other.containsTwoStores()) {
+          return getRegularStore().equals(other.getRegularStore());
+        }
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(this.analysis, this.node, this.store, this.thenStore, this.elseStore);
+  }
+
+  @Override
+  public String toString() {
+    if (store == null) {
+      return "[then="
+          + StringsPlume.indentLinesExceptFirst(2, thenStore)
+          + ","
+          + System.lineSeparator()
+          + "  else="
+          + StringsPlume.indentLinesExceptFirst(2, elseStore)
+          + "]";
+    } else {
+      return "[" + store + "]";
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferResult.java
new file mode 100644
index 0000000..3c7f210
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferResult.java
@@ -0,0 +1,141 @@
+package org.checkerframework.dataflow.analysis;
+
+import java.util.Map;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * {@code TransferResult} is used as the result type of the individual transfer functions of a
+ * {@link TransferFunction}. It always belongs to the result of the individual transfer function for
+ * a particular {@link org.checkerframework.dataflow.cfg.node.Node}, even though that {@code
+ * org.checkerframework.dataflow.cfg.node.Node} is not explicitly stored in {@code TransferResult}.
+ *
+ * <p>A {@code TransferResult} consists of a result value, plus one or more stores. It contains one
+ * or two stores (for 'then' and 'else'), and zero or more stores with a cause ({@link TypeMirror}).
+ *
+ * @param <V> type of the abstract value that is tracked
+ * @param <S> the store type used in the analysis
+ */
+public abstract class TransferResult<V extends AbstractValue<V>, S extends Store<S>> {
+
+  /**
+   * The abstract value of the {@link org.checkerframework.dataflow.cfg.node.Node} associated with
+   * this {@link TransferResult}, or {@code null} if no value has been produced.
+   */
+  protected @Nullable V resultValue;
+
+  /**
+   * The stores in case the basic block throws an exception (or {@code null} if the corresponding
+   * {@link org.checkerframework.dataflow.cfg.node.Node} does not throw any exceptions). Does not
+   * necessarily contain a store for every exception, in which case the in-store will be used.
+   */
+  protected final @Nullable Map<TypeMirror, S> exceptionalStores;
+
+  /**
+   * Create a new TransferResult, given {@link #resultValue} and {@link #exceptionalStores}.
+   *
+   * @param resultValue the abstract value of the {@link
+   *     org.checkerframework.dataflow.cfg.node.Node} associated with this {@link TransferResult}
+   * @param exceptionalStores the stores in case the basic block throws an exception (or {@code
+   *     null} if the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} does not
+   *     throw any exceptions)
+   */
+  protected TransferResult(
+      @Nullable V resultValue, @Nullable Map<TypeMirror, S> exceptionalStores) {
+    this.resultValue = resultValue;
+    this.exceptionalStores = exceptionalStores;
+  }
+
+  /**
+   * Returns the abstract value produced by the transfer function, {@code null} otherwise.
+   *
+   * @return the abstract value produced by the transfer function, {@code null} otherwise
+   */
+  public @Nullable V getResultValue() {
+    return resultValue;
+  }
+
+  /**
+   * Set the value of {@link #resultValue}.
+   *
+   * @param resultValue the abstract value of the {@link
+   *     org.checkerframework.dataflow.cfg.node.Node} associated with this {@link TransferResult}
+   */
+  public void setResultValue(V resultValue) {
+    this.resultValue = resultValue;
+  }
+
+  /**
+   * Returns the regular result store produced if no exception is thrown by the {@link
+   * org.checkerframework.dataflow.cfg.node.Node} corresponding to this transfer function result.
+   *
+   * @return the regular result store produced if no exception is thrown by the {@link
+   *     org.checkerframework.dataflow.cfg.node.Node} corresponding to this transfer function result
+   */
+  public abstract S getRegularStore();
+
+  /**
+   * Returns the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node}
+   * this result belongs to evaluates to {@code true}.
+   *
+   * @return the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node}
+   *     this result belongs to evaluates to {@code true}
+   */
+  public abstract S getThenStore();
+
+  /**
+   * Returns the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node}
+   * this result belongs to evaluates to {@code false}.
+   *
+   * @return the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node}
+   *     this result belongs to evaluates to {@code false}
+   */
+  public abstract S getElseStore();
+
+  /**
+   * Returns the store that flows along the outgoing exceptional edge labeled with {@code exception}
+   * (or {@code null} if no special handling is required for exceptional edges).
+   *
+   * @param exception an exception type
+   * @return the store that flows along the outgoing exceptional edge labeled with {@code exception}
+   *     (or {@code null} if no special handling is required for exceptional edges)
+   */
+  public @Nullable S getExceptionalStore(TypeMirror exception) {
+    if (exceptionalStores == null) {
+      return null;
+    }
+    return exceptionalStores.get(exception);
+  }
+
+  /**
+   * Returns a Map of {@link TypeMirror} to {@link Store}, {@code null} otherwise.
+   *
+   * @return a Map of {@link TypeMirror} to {@link Store}, {@code null} otherwise
+   * @see TransferResult#getExceptionalStore(TypeMirror)
+   */
+  public @Nullable Map<TypeMirror, S> getExceptionalStores() {
+    return exceptionalStores;
+  }
+
+  /**
+   * Returns {@code true} if and only if this transfer result contains two stores that are
+   * potentially not equal. Note that the result {@code true} does not imply that {@code
+   * getRegularStore} cannot be called (or vice versa for {@code false}). Rather, it indicates that
+   * {@code getThenStore} or {@code getElseStore} can be used to give more precise results.
+   * Otherwise, if the result is {@code false}, then all three methods {@code getRegularStore},
+   * {@code getThenStore}, and {@code getElseStore} return equivalent stores.
+   *
+   * @return {@code true} if and only if this transfer result contains two stores that are
+   *     potentially not equal
+   */
+  public abstract boolean containsTwoStores();
+
+  /**
+   * Returns {@code true} if and only if the transfer function returning this transfer result
+   * changed the regularStore, elseStore, or thenStore.
+   *
+   * @return {@code true} if and only if the transfer function returning this transfer result
+   *     changed the regularStore, elseStore, or thenStore
+   */
+  public abstract boolean storeChanged();
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGProcessor.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGProcessor.java
new file mode 100644
index 0000000..67e8359
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGProcessor.java
@@ -0,0 +1,193 @@
+package org.checkerframework.dataflow.cfg;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.util.TreePathScanner;
+import com.sun.tools.javac.util.Log;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.builder.CFGBuilder;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.javacutil.BasicTypeProcessor;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Generate the control flow graph of a given method in a given class. See {@link
+ * org.checkerframework.dataflow.cfg.visualize.CFGVisualizeLauncher} for example usage.
+ */
+@SupportedAnnotationTypes("*")
+public class CFGProcessor extends BasicTypeProcessor {
+
+  /**
+   * Qualified name of a specified class which includes a specified method to generate the CFG for.
+   */
+  private final String className;
+  /** Name of a specified method to generate the CFG for. */
+  private final String methodName;
+
+  /** AST for source file. */
+  private @Nullable CompilationUnitTree rootTree;
+  /** Tree node for the specified class. */
+  private @Nullable ClassTree classTree;
+  /** Tree node for the specified method. */
+  private @Nullable MethodTree methodTree;
+
+  /** Result of CFG process; is set by {@link #typeProcessingOver}. */
+  private @MonotonicNonNull CFGProcessResult result = null;
+
+  /**
+   * Create a CFG processor.
+   *
+   * @param className the qualified name of class which includes the specified method to generate
+   *     the CFG for
+   * @param methodName the name of the method to generate the CFG for
+   */
+  public CFGProcessor(String className, String methodName) {
+    this.className = className;
+    this.methodName = methodName;
+  }
+
+  /**
+   * Get the CFG process result.
+   *
+   * @return result of cfg process
+   */
+  public final @Nullable CFGProcessResult getCFGProcessResult() {
+    return result;
+  }
+
+  @Override
+  public void typeProcessingOver() {
+    if (rootTree == null) {
+      result = new CFGProcessResult("Root tree is null.");
+    } else if (classTree == null) {
+      result = new CFGProcessResult("Method tree is null.");
+    } else if (methodTree == null) {
+      result = new CFGProcessResult("Class tree is null.");
+    } else {
+      Log log = getCompilerLog();
+      if (log.nerrors > 0) {
+        result = new CFGProcessResult("Compilation issued an error.");
+      } else {
+        ControlFlowGraph cfg = CFGBuilder.build(rootTree, methodTree, classTree, processingEnv);
+        result = new CFGProcessResult(cfg);
+      }
+    }
+    super.typeProcessingOver();
+  }
+
+  @Override
+  protected TreePathScanner<?, ?> createTreePathScanner(CompilationUnitTree root) {
+    rootTree = root;
+    return new TreePathScanner<Void, Void>() {
+      @Override
+      public Void visitClass(ClassTree node, Void p) {
+        TypeElement el = TreeUtils.elementFromDeclaration(node);
+        if (el.getSimpleName().contentEquals(className)) {
+          classTree = node;
+        }
+        return super.visitClass(node, p);
+      }
+
+      @Override
+      public Void visitMethod(MethodTree node, Void p) {
+        ExecutableElement el = TreeUtils.elementFromDeclaration(node);
+        if (el.getSimpleName().contentEquals(methodName)) {
+          methodTree = node;
+          // Stop execution by throwing an exception. This makes sure that compilation does not
+          // proceed, and thus the AST is not modified by further phases of the compilation (and we
+          // save the work to do the compilation).
+          throw new RuntimeException();
+        }
+        return null;
+      }
+    };
+  }
+
+  @Override
+  public SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latestSupported();
+  }
+
+  /** The result of the CFG process, contains the control flow graph when successful. */
+  public static class CFGProcessResult {
+    /** Control flow graph. */
+    private final @Nullable ControlFlowGraph controlFlowGraph;
+    /** Did the CFG process succeed? */
+    private final boolean isSuccess;
+    /** Error message (when the CFG process failed). */
+    private final @Nullable String errMsg;
+
+    /**
+     * Create the result of the CFG process. Only called if the CFG was built successfully.
+     *
+     * @param cfg control flow graph
+     */
+    CFGProcessResult(final ControlFlowGraph cfg) {
+      this(cfg, true, null);
+    }
+
+    /**
+     * Create the result of the CFG process. Only called if the CFG was not built successfully.
+     *
+     * @param errMsg the error message
+     */
+    CFGProcessResult(final String errMsg) {
+      this(null, false, errMsg);
+    }
+
+    /**
+     * Create the result of CFG process.
+     *
+     * @param cfg the control flow graph
+     * @param isSuccess did the CFG process succeed?
+     * @param errMsg error message (when the CFG process failed)
+     */
+    private CFGProcessResult(
+        @Nullable ControlFlowGraph cfg, boolean isSuccess, @Nullable String errMsg) {
+      this.controlFlowGraph = cfg;
+      this.isSuccess = isSuccess;
+      this.errMsg = errMsg;
+    }
+
+    /**
+     * Check if the CFG process succeeded.
+     *
+     * @return true if the CFG process succeeded
+     */
+    @Pure
+    @EnsuresNonNullIf(expression = "getCFG()", result = true)
+    // TODO: add once #1307 is fixed
+    // @EnsuresNonNullIf(expression = "getErrMsg()", result = false)
+    @SuppressWarnings("nullness:contracts.conditional.postcondition")
+    public boolean isSuccess() {
+      return isSuccess;
+    }
+
+    /**
+     * Returns the generated control flow graph.
+     *
+     * @return the generated control flow graph
+     */
+    @Pure
+    public @Nullable ControlFlowGraph getCFG() {
+      return controlFlowGraph;
+    }
+
+    /**
+     * Returns the error message.
+     *
+     * @return the error message
+     */
+    @Pure
+    public @Nullable String getErrMsg() {
+      return errMsg;
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java
new file mode 100644
index 0000000..12bf2d9
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java
@@ -0,0 +1,355 @@
+package org.checkerframework.dataflow.cfg;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.UnaryTree;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicLong;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.AnalysisResult;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.block.ConditionalBlock;
+import org.checkerframework.dataflow.cfg.block.ExceptionBlock;
+import org.checkerframework.dataflow.cfg.block.RegularBlock;
+import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlock;
+import org.checkerframework.dataflow.cfg.block.SpecialBlock;
+import org.checkerframework.dataflow.cfg.block.SpecialBlockImpl;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
+import org.checkerframework.dataflow.cfg.visualize.StringCFGVisualizer;
+import org.plumelib.util.UniqueId;
+
+/**
+ * A control flow graph (CFG for short) of a single method.
+ *
+ * <p>The graph is represented by the successors (methods {@link SingleSuccessorBlock#getSuccessor},
+ * {@link ConditionalBlock#getThenSuccessor}, {@link ConditionalBlock#getElseSuccessor}, {@link
+ * ExceptionBlock#getExceptionalSuccessors}, {@link RegularBlock#getRegularSuccessor}) and
+ * predecessors (method {@link Block#getPredecessors}) of the entry and exit blocks.
+ */
+public class ControlFlowGraph implements UniqueId {
+
+  /** The entry block of the control flow graph. */
+  protected final SpecialBlock entryBlock;
+
+  /** The regular exit block of the control flow graph. */
+  protected final SpecialBlock regularExitBlock;
+
+  /** The exceptional exit block of the control flow graph. */
+  protected final SpecialBlock exceptionalExitBlock;
+
+  /** The AST this CFG corresponds to. */
+  public final UnderlyingAST underlyingAST;
+
+  /** The unique ID for the next-created object. */
+  static final AtomicLong nextUid = new AtomicLong(0);
+  /** The unique ID of this object. */
+  final transient long uid = nextUid.getAndIncrement();
+
+  @Override
+  public long getUid(@UnknownInitialization ControlFlowGraph this) {
+    return uid;
+  }
+
+  /**
+   * Maps from AST {@link Tree}s to sets of {@link Node}s.
+   *
+   * <ul>
+   *   <li>Most Trees that produce a value will have at least one corresponding Node.
+   *   <li>Trees that undergo conversions, such as boxing or unboxing, can map to two distinct
+   *       Nodes. The Node for the pre-conversion value is stored in {@link #treeLookup}, while the
+   *       Node for the post-conversion value is stored in {@link #convertedTreeLookup}.
+   * </ul>
+   *
+   * Some of the mapped-to nodes (in both {@link #treeLookup} and {@link #convertedTreeLookup}) do
+   * not appear in {@link #getAllNodes} because their blocks are not reachable in the control flow
+   * graph. Dataflow will not compute abstract values for these nodes.
+   */
+  protected final IdentityHashMap<Tree, Set<Node>> treeLookup;
+
+  /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */
+  protected final IdentityHashMap<Tree, Set<Node>> convertedTreeLookup;
+
+  /** Map from AST {@link UnaryTree}s to corresponding {@link AssignmentNode}s. */
+  protected final IdentityHashMap<UnaryTree, AssignmentNode> unaryAssignNodeLookup;
+
+  /**
+   * All return nodes (if any) encountered. Only includes return statements that actually return
+   * something
+   */
+  protected final List<ReturnNode> returnNodes;
+
+  /**
+   * Class declarations that have been encountered when building the control-flow graph for a
+   * method.
+   */
+  protected final List<ClassTree> declaredClasses;
+
+  /**
+   * Lambdas encountered when building the control-flow graph for a method, variable initializer, or
+   * initializer.
+   */
+  protected final List<LambdaExpressionTree> declaredLambdas;
+
+  public ControlFlowGraph(
+      SpecialBlock entryBlock,
+      SpecialBlockImpl regularExitBlock,
+      SpecialBlockImpl exceptionalExitBlock,
+      UnderlyingAST underlyingAST,
+      IdentityHashMap<Tree, Set<Node>> treeLookup,
+      IdentityHashMap<Tree, Set<Node>> convertedTreeLookup,
+      IdentityHashMap<UnaryTree, AssignmentNode> unaryAssignNodeLookup,
+      List<ReturnNode> returnNodes,
+      List<ClassTree> declaredClasses,
+      List<LambdaExpressionTree> declaredLambdas) {
+    super();
+    this.entryBlock = entryBlock;
+    this.underlyingAST = underlyingAST;
+    this.treeLookup = treeLookup;
+    this.unaryAssignNodeLookup = unaryAssignNodeLookup;
+    this.convertedTreeLookup = convertedTreeLookup;
+    this.regularExitBlock = regularExitBlock;
+    this.exceptionalExitBlock = exceptionalExitBlock;
+    this.returnNodes = returnNodes;
+    this.declaredClasses = declaredClasses;
+    this.declaredLambdas = declaredLambdas;
+  }
+
+  /**
+   * Returns the set of {@link Node}s to which the {@link Tree} {@code t} corresponds, or null for
+   * trees that don't produce a value.
+   *
+   * @param t a tree
+   * @return the set of {@link Node}s to which the {@link Tree} {@code t} corresponds, or null for
+   *     trees that don't produce a value
+   */
+  public @Nullable Set<Node> getNodesCorrespondingToTree(Tree t) {
+    if (convertedTreeLookup.containsKey(t)) {
+      return convertedTreeLookup.get(t);
+    } else {
+      return treeLookup.get(t);
+    }
+  }
+
+  /**
+   * Returns the entry block of the control flow graph.
+   *
+   * @return the entry block of the control flow graph
+   */
+  public SpecialBlock getEntryBlock() {
+    return entryBlock;
+  }
+
+  public List<ReturnNode> getReturnNodes() {
+    return returnNodes;
+  }
+
+  public SpecialBlock getRegularExitBlock() {
+    return regularExitBlock;
+  }
+
+  public SpecialBlock getExceptionalExitBlock() {
+    return exceptionalExitBlock;
+  }
+
+  /**
+   * Returns the AST this CFG corresponds to.
+   *
+   * @return the AST this CFG corresponds to
+   */
+  public UnderlyingAST getUnderlyingAST() {
+    return underlyingAST;
+  }
+
+  /**
+   * Returns the set of all basic blocks in this control flow graph.
+   *
+   * @return the set of all basic blocks in this control flow graph
+   */
+  public Set<Block> getAllBlocks(
+      @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this) {
+    Set<Block> visited = new HashSet<>();
+    // worklist is always a subset of visited; any block in worklist is also in visited.
+    Queue<Block> worklist = new ArrayDeque<>();
+    Block cur = entryBlock;
+    visited.add(entryBlock);
+
+    // traverse the whole control flow graph
+    while (true) {
+      if (cur == null) {
+        break;
+      }
+
+      for (Block b : cur.getSuccessors()) {
+        if (visited.add(b)) {
+          worklist.add(b);
+        }
+      }
+
+      cur = worklist.poll();
+    }
+
+    return visited;
+  }
+
+  /**
+   * Returns all nodes in this control flow graph.
+   *
+   * @return all nodes in this control flow graph
+   */
+  public List<Node> getAllNodes(
+      @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this) {
+    List<Node> result = new ArrayList<>();
+    for (Block b : getAllBlocks()) {
+      result.addAll(b.getNodes());
+    }
+    return result;
+  }
+
+  /**
+   * Returns all basic blocks in this control flow graph, in reversed depth-first postorder. Blocks
+   * may appear more than once in the sequence.
+   *
+   * @return the list of all basic block in this control flow graph in reversed depth-first
+   *     postorder sequence
+   */
+  public List<Block> getDepthFirstOrderedBlocks() {
+    List<Block> dfsOrderResult = new ArrayList<>();
+    Set<Block> visited = new HashSet<>();
+    // worklist can contain values that are not yet in visited.
+    Deque<Block> worklist = new ArrayDeque<>();
+    worklist.add(entryBlock);
+    while (!worklist.isEmpty()) {
+      Block cur = worklist.getLast();
+      if (visited.contains(cur)) {
+        dfsOrderResult.add(cur);
+        worklist.removeLast();
+      } else {
+        visited.add(cur);
+
+        for (Block b : cur.getSuccessors()) {
+          if (!visited.contains(b)) {
+            worklist.add(b);
+          }
+        }
+      }
+    }
+
+    Collections.reverse(dfsOrderResult);
+    return dfsOrderResult;
+  }
+
+  /**
+   * Returns the copied tree-lookup map. Ignores convertedTreeLookup, though {@link
+   * #getNodesCorrespondingToTree} uses that field.
+   *
+   * @return the copied tree-lookup map
+   */
+  public IdentityHashMap<Tree, Set<Node>> getTreeLookup() {
+    return new IdentityHashMap<>(treeLookup);
+  }
+
+  /**
+   * Returns the copied lookup-map of the assign node for unary operation.
+   *
+   * @return the copied lookup-map of the assign node for unary operation
+   */
+  public IdentityHashMap<UnaryTree, AssignmentNode> getUnaryAssignNodeLookup() {
+    return new IdentityHashMap<>(unaryAssignNodeLookup);
+  }
+
+  /**
+   * Get the {@link MethodTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in
+   * the CFG or null otherwise.
+   */
+  public @Nullable MethodTree getContainingMethod(Tree t) {
+    if (treeLookup.containsKey(t)) {
+      if (underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) {
+        UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST;
+        return cfgMethod.getMethod();
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Get the {@link ClassTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in the
+   * CFG or null otherwise.
+   */
+  public @Nullable ClassTree getContainingClass(Tree t) {
+    if (treeLookup.containsKey(t)) {
+      if (underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) {
+        UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST;
+        return cfgMethod.getClassTree();
+      }
+    }
+    return null;
+  }
+
+  public List<ClassTree> getDeclaredClasses() {
+    return declaredClasses;
+  }
+
+  public List<LambdaExpressionTree> getDeclaredLambdas() {
+    return declaredLambdas;
+  }
+
+  @Override
+  public String toString() {
+    CFGVisualizer<?, ?, ?> viz = new StringCFGVisualizer<>();
+    viz.init(Collections.singletonMap("verbose", true));
+    Map<String, Object> res = viz.visualize(this, this.getEntryBlock(), null);
+    viz.shutdown();
+    if (res == null) {
+      return "unvisualizable " + getClass().getCanonicalName();
+    }
+    String stringGraph = (String) res.get("stringGraph");
+    return stringGraph == null ? "unvisualizable " + getClass().getCanonicalName() : stringGraph;
+  }
+
+  /**
+   * Returns a verbose string representation of this, useful for debugging.
+   *
+   * @return a string representation of this
+   */
+  public String toStringDebug() {
+    String className = this.getClass().getSimpleName();
+    if (className.equals("ControlFlowGraph") && this.getClass() != ControlFlowGraph.class) {
+      className = this.getClass().getCanonicalName();
+    }
+
+    StringJoiner result = new StringJoiner(String.format("%n  "));
+    result.add(className + "{");
+    result.add("entryBlock=" + entryBlock);
+    result.add("regularExitBlock=" + regularExitBlock);
+    result.add("exceptionalExitBlock=" + exceptionalExitBlock);
+    String astString = underlyingAST.toString().replaceAll("\\s", " ");
+    if (astString.length() > 65) {
+      astString = "\"" + astString.substring(0, 60) + "\"";
+    }
+    result.add("underlyingAST=" + underlyingAST);
+    result.add("treeLookup=" + AnalysisResult.treeLookupToString(treeLookup));
+    result.add("convertedTreeLookup=" + AnalysisResult.treeLookupToString(convertedTreeLookup));
+    result.add("unaryAssignNodeLookup=" + unaryAssignNodeLookup);
+    result.add("returnNodes=" + Node.nodeCollectionToString(returnNodes));
+    result.add("declaredClasses=" + declaredClasses);
+    result.add("declaredLambdas=" + declaredLambdas);
+    result.add("}");
+    return result.toString();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java
new file mode 100644
index 0000000..e81d55a
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java
@@ -0,0 +1,241 @@
+package org.checkerframework.dataflow.cfg;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import java.util.concurrent.atomic.AtomicLong;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.plumelib.util.StringsPlume;
+import org.plumelib.util.UniqueId;
+
+/**
+ * Represents an abstract syntax tree of type {@link Tree} that underlies a given control flow
+ * graph.
+ */
+public abstract class UnderlyingAST implements UniqueId {
+  /** The kinds of underlying ASTs. */
+  public enum Kind {
+    /** The underlying code is a whole method. */
+    METHOD,
+    /** The underlying code is a lambda expression. */
+    LAMBDA,
+
+    /** The underlying code is an arbitrary Java statement or expression. */
+    ARBITRARY_CODE,
+  }
+
+  /** The kind of the underlying AST. */
+  protected final Kind kind;
+
+  /** The unique ID for the next-created object. */
+  static final AtomicLong nextUid = new AtomicLong(0);
+  /** The unique ID of this object. */
+  final transient long uid = nextUid.getAndIncrement();
+
+  @Override
+  public long getUid(@UnknownInitialization UnderlyingAST this) {
+    return uid;
+  }
+
+  /**
+   * Creates an UnderlyingAST.
+   *
+   * @param kind the kind of the AST
+   */
+  protected UnderlyingAST(Kind kind) {
+    this.kind = kind;
+  }
+
+  /**
+   * Returns the code that corresponds to the CFG. For a method or lamdda, this returns the body.
+   * For other constructs, it returns the tree itself (a statement or expression).
+   *
+   * @return the code that corresponds to the CFG
+   */
+  public abstract Tree getCode();
+
+  public Kind getKind() {
+    return kind;
+  }
+
+  /** If the underlying AST is a method. */
+  public static class CFGMethod extends UnderlyingAST {
+
+    /** The method declaration. */
+    protected final MethodTree method;
+
+    /** The class tree this method belongs to. */
+    protected final ClassTree classTree;
+
+    public CFGMethod(MethodTree method, ClassTree classTree) {
+      super(Kind.METHOD);
+      this.method = method;
+      this.classTree = classTree;
+    }
+
+    @Override
+    public Tree getCode() {
+      return method.getBody();
+    }
+
+    public MethodTree getMethod() {
+      return method;
+    }
+
+    /**
+     * Returns the name of the method.
+     *
+     * @return the name of the method
+     */
+    public String getMethodName() {
+      return method.getName().toString();
+    }
+
+    /**
+     * Returns the class tree this method belongs to.
+     *
+     * @return the class tree this method belongs to
+     */
+    public ClassTree getClassTree() {
+      return classTree;
+    }
+
+    /**
+     * Returns the simple name of the enclosing class.
+     *
+     * @return the simple name of the enclosing class
+     */
+    public String getSimpleClassName() {
+      return classTree.getSimpleName().toString();
+    }
+
+    @Override
+    public String toString() {
+      return StringsPlume.joinLines("CFGMethod(", method, ")");
+    }
+  }
+
+  /** If the underlying AST is a lambda. */
+  public static class CFGLambda extends UnderlyingAST {
+
+    /** The lambda expression. */
+    private final LambdaExpressionTree lambda;
+
+    /** The enclosing class of the lambda. */
+    private final ClassTree classTree;
+
+    /** The enclosing method of the lambda. */
+    private final MethodTree method;
+
+    /**
+     * Create a new CFGLambda.
+     *
+     * @param lambda the lambda expression
+     * @param classTree the enclosing class of the lambda
+     * @param method the enclosing method of the lambda
+     */
+    public CFGLambda(LambdaExpressionTree lambda, ClassTree classTree, MethodTree method) {
+      super(Kind.LAMBDA);
+      this.lambda = lambda;
+      this.method = method;
+      this.classTree = classTree;
+    }
+
+    @Override
+    public Tree getCode() {
+      return lambda.getBody();
+    }
+
+    /**
+     * Returns the lambda expression tree.
+     *
+     * @return the lambda expression tree
+     */
+    public LambdaExpressionTree getLambdaTree() {
+      return lambda;
+    }
+
+    /**
+     * Returns the enclosing class of the lambda.
+     *
+     * @return the enclosing class of the lambda
+     */
+    public ClassTree getClassTree() {
+      return classTree;
+    }
+
+    /**
+     * Returns the simple name of the enclosing class.
+     *
+     * @return the simple name of the enclosing class
+     */
+    public String getSimpleClassName() {
+      return classTree.getSimpleName().toString();
+    }
+
+    /**
+     * Returns the enclosing method of the lambda.
+     *
+     * @return the enclosing method of the lambda
+     */
+    public MethodTree getMethod() {
+      return method;
+    }
+
+    /**
+     * Returns the name of the enclosing method of the lambda.
+     *
+     * @return the name of the enclosing method of the lambda
+     */
+    public String getMethodName() {
+      return method.getName().toString();
+    }
+
+    @Override
+    public String toString() {
+      return StringsPlume.joinLines("CFGLambda(", lambda, ")");
+    }
+  }
+
+  /**
+   * If the underlying AST is a statement or expression. This is for field definitions (with
+   * initializers) and initializer blocks.
+   */
+  public static class CFGStatement extends UnderlyingAST {
+
+    protected final Tree code;
+
+    /** The class tree this method belongs to. */
+    protected final ClassTree classTree;
+
+    public CFGStatement(Tree code, ClassTree classTree) {
+      super(Kind.ARBITRARY_CODE);
+      this.code = code;
+      this.classTree = classTree;
+    }
+
+    @Override
+    public Tree getCode() {
+      return code;
+    }
+
+    public ClassTree getClassTree() {
+      return classTree;
+    }
+
+    /**
+     * Returns the simple name of the enclosing class.
+     *
+     * @return the simple name of the enclosing class
+     */
+    public String getSimpleClassName() {
+      return classTree.getSimpleName().toString();
+    }
+
+    @Override
+    public String toString() {
+      return StringsPlume.joinLines("CFGStatement(", code, ")");
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java
new file mode 100644
index 0000000..f1c050c
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java
@@ -0,0 +1,70 @@
+package org.checkerframework.dataflow.cfg.block;
+
+import java.util.List;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.qual.Pure;
+import org.plumelib.util.UniqueId;
+
+/** Represents a basic block in a control flow graph. */
+public interface Block extends UniqueId {
+
+  /** The types of basic blocks. */
+  public static enum BlockType {
+
+    /** A regular basic block. */
+    REGULAR_BLOCK,
+
+    /** A conditional basic block. */
+    CONDITIONAL_BLOCK,
+
+    /** A special basic block. */
+    SPECIAL_BLOCK,
+
+    /** A basic block that can throw an exception. */
+    EXCEPTION_BLOCK,
+  }
+
+  /**
+   * Returns the type of this basic block.
+   *
+   * @return the type of this basic block
+   */
+  BlockType getType();
+
+  /**
+   * Returns the predecessors of this basic block.
+   *
+   * @return the predecessors of this basic block
+   */
+  Set<Block> getPredecessors();
+
+  /**
+   * Returns the successors of this basic block.
+   *
+   * @return the successors of this basic block
+   */
+  Set<Block> getSuccessors();
+
+  /**
+   * Returns the nodes contained within this basic block. The list may be empty.
+   *
+   * <p>The following invariant holds.
+   *
+   * <pre>
+   * forall n in getNodes() :: n.getBlock() == this
+   * </pre>
+   *
+   * @return the nodes contained within this basic block
+   */
+  @Pure
+  List<Node> getNodes();
+
+  /**
+   * Returns the last node of this block, or null if none.
+   *
+   * @return the last node of this block or {@code null}
+   */
+  @Nullable Node getLastNode();
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java
new file mode 100644
index 0000000..8b39d79
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java
@@ -0,0 +1,59 @@
+package org.checkerframework.dataflow.cfg.block;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+
+/** Base class of the {@link Block} implementation hierarchy. */
+public abstract class BlockImpl implements Block {
+
+  /** The type of this basic block. */
+  protected final BlockType type;
+
+  /** The set of predecessors. */
+  protected final Set<BlockImpl> predecessors;
+
+  /** The unique ID for the next-created object. */
+  static final AtomicLong nextUid = new AtomicLong(0);
+  /** The unique ID of this object. */
+  final long uid = nextUid.getAndIncrement();
+  /**
+   * Returns the unique ID of this object.
+   *
+   * @return the unique ID of this object
+   */
+  @Override
+  public long getUid(@UnknownInitialization BlockImpl this) {
+    return uid;
+  }
+
+  /**
+   * Create a new BlockImpl.
+   *
+   * @param type the type of this basic block
+   */
+  protected BlockImpl(BlockType type) {
+    this.type = type;
+    this.predecessors = new LinkedHashSet<>();
+  }
+
+  @Override
+  public BlockType getType() {
+    return type;
+  }
+
+  @Override
+  public Set<Block> getPredecessors() {
+    // Not "Collections.unmodifiableSet(predecessors)" which has nondeterministic iteration order.
+    return new LinkedHashSet<>(predecessors);
+  }
+
+  public void addPredecessor(BlockImpl pred) {
+    predecessors.add(pred);
+  }
+
+  public void removePredecessor(BlockImpl pred) {
+    predecessors.remove(pred);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java
new file mode 100644
index 0000000..6ee0d62
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java
@@ -0,0 +1,53 @@
+package org.checkerframework.dataflow.cfg.block;
+
+import org.checkerframework.dataflow.analysis.Store.FlowRule;
+
+// Werner believes that a ConditionalBlock has to have exactly one RegularBlock (?) predecessor and
+// the last node of that predecessor has to be a node of boolean type. He's not totally sure,
+// though.  We should check whether that property holds.
+
+/** Represents a conditional basic block. */
+public interface ConditionalBlock extends Block {
+
+  /**
+   * Returns the entry block of the then branch.
+   *
+   * @return the entry block of the then branch
+   */
+  Block getThenSuccessor();
+
+  /**
+   * Returns the entry block of the else branch.
+   *
+   * @return the entry block of the else branch
+   */
+  Block getElseSuccessor();
+
+  /**
+   * Returns the flow rule for information flowing from this block to its then successor.
+   *
+   * @return the flow rule for information flowing from this block to its then successor
+   */
+  FlowRule getThenFlowRule();
+
+  /**
+   * Returns the flow rule for information flowing from this block to its else successor.
+   *
+   * @return the flow rule for information flowing from this block to its else successor
+   */
+  FlowRule getElseFlowRule();
+
+  /**
+   * Set the flow rule for information flowing from this block to its then successor.
+   *
+   * @param rule the new flow rule for information flowing from this block to its then successor
+   */
+  void setThenFlowRule(FlowRule rule);
+
+  /**
+   * Set the flow rule for information flowing from this block to its else successor.
+   *
+   * @param rule the new flow rule for information flowing from this block to its else successor
+   */
+  void setElseFlowRule(FlowRule rule);
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java
new file mode 100644
index 0000000..632a781
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java
@@ -0,0 +1,116 @@
+package org.checkerframework.dataflow.cfg.block;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store.FlowRule;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.javacutil.BugInCF;
+
+/** Implementation of a conditional basic block. */
+public class ConditionalBlockImpl extends BlockImpl implements ConditionalBlock {
+
+  /** Successor of the then branch. */
+  protected @Nullable BlockImpl thenSuccessor;
+
+  /** Successor of the else branch. */
+  protected @Nullable BlockImpl elseSuccessor;
+
+  /**
+   * The initial value says that the THEN store before a conditional block flows to BOTH of the
+   * stores of the then successor.
+   */
+  protected FlowRule thenFlowRule = FlowRule.THEN_TO_BOTH;
+
+  /**
+   * The initial value says that the ELSE store before a conditional block flows to BOTH of the
+   * stores of the else successor.
+   */
+  protected FlowRule elseFlowRule = FlowRule.ELSE_TO_BOTH;
+
+  /**
+   * Initialize an empty conditional basic block to be filled with contents and linked to other
+   * basic blocks later.
+   */
+  public ConditionalBlockImpl() {
+    super(BlockType.CONDITIONAL_BLOCK);
+  }
+
+  /** Set the then branch successor. */
+  public void setThenSuccessor(BlockImpl b) {
+    thenSuccessor = b;
+    b.addPredecessor(this);
+  }
+
+  /** Set the else branch successor. */
+  public void setElseSuccessor(BlockImpl b) {
+    elseSuccessor = b;
+    b.addPredecessor(this);
+  }
+
+  @Override
+  public Block getThenSuccessor() {
+    if (thenSuccessor == null) {
+      throw new BugInCF("Requested thenSuccessor for conditional block before initialization");
+    }
+    return thenSuccessor;
+  }
+
+  @Override
+  public Block getElseSuccessor() {
+    if (elseSuccessor == null) {
+      throw new BugInCF("Requested elseSuccessor for conditional block before initialization");
+    }
+    return elseSuccessor;
+  }
+
+  @Override
+  public Set<Block> getSuccessors() {
+    Set<Block> result = new LinkedHashSet<>(2);
+    result.add(getThenSuccessor());
+    result.add(getElseSuccessor());
+    return result;
+  }
+
+  @Override
+  public FlowRule getThenFlowRule() {
+    return thenFlowRule;
+  }
+
+  @Override
+  public FlowRule getElseFlowRule() {
+    return elseFlowRule;
+  }
+
+  @Override
+  public void setThenFlowRule(FlowRule rule) {
+    thenFlowRule = rule;
+  }
+
+  @Override
+  public void setElseFlowRule(FlowRule rule) {
+    elseFlowRule = rule;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>This implementation returns an empty list.
+   */
+  @Override
+  public List<Node> getNodes() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public @Nullable Node getLastNode() {
+    return null;
+  }
+
+  @Override
+  public String toString() {
+    return "ConditionalBlock()";
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java
new file mode 100644
index 0000000..cf9b3f3
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java
@@ -0,0 +1,36 @@
+package org.checkerframework.dataflow.cfg.block;
+
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.qual.Pure;
+
+/**
+ * Represents a basic block that contains exactly one {@link Node} which can throw an exception.
+ * This block has exactly one non-exceptional successor, and one or more exceptional successors.
+ *
+ * <p>The following invariant holds.
+ *
+ * <pre>
+ * getNode().getBlock() == this
+ * </pre>
+ */
+public interface ExceptionBlock extends SingleSuccessorBlock {
+
+  /**
+   * Returns the node of this block.
+   *
+   * @return the node of this block
+   */
+  @Pure
+  Node getNode();
+
+  /**
+   * Returns the list of exceptional successor blocks as an unmodifiable map.
+   *
+   * @return the list of exceptional successor blocks as an unmodifiable map
+   */
+  @Pure
+  Map<TypeMirror, Set<Block>> getExceptionalSuccessors();
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java
new file mode 100644
index 0000000..663eb15
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java
@@ -0,0 +1,91 @@
+package org.checkerframework.dataflow.cfg.block;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.javacutil.BugInCF;
+
+/** Base class of the {@link Block} implementation hierarchy. */
+public class ExceptionBlockImpl extends SingleSuccessorBlockImpl implements ExceptionBlock {
+
+  /** The node of this block. */
+  protected @Nullable Node node;
+
+  /** Set of exceptional successors. */
+  protected final Map<TypeMirror, Set<Block>> exceptionalSuccessors;
+
+  /** Create an empty exceptional block. */
+  public ExceptionBlockImpl() {
+    super(BlockType.EXCEPTION_BLOCK);
+    exceptionalSuccessors = new LinkedHashMap<>(2);
+  }
+
+  /** Set the node. */
+  public void setNode(Node c) {
+    node = c;
+    c.setBlock(this);
+  }
+
+  @Override
+  public Node getNode() {
+    if (node == null) {
+      throw new BugInCF("Requested node for exception block before initialization");
+    }
+    return node;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>This implementation returns a singleton list.
+   */
+  @Override
+  public List<Node> getNodes() {
+    return Collections.singletonList(getNode());
+  }
+
+  @Override
+  public @Nullable Node getLastNode() {
+    return null;
+  }
+
+  /**
+   * Add an exceptional successor.
+   *
+   * @param b the successor
+   * @param cause the exception type that leads to the given block
+   */
+  public void addExceptionalSuccessor(BlockImpl b, TypeMirror cause) {
+    Set<Block> blocks = exceptionalSuccessors.computeIfAbsent(cause, __ -> new LinkedHashSet<>());
+    blocks.add(b);
+    b.addPredecessor(this);
+  }
+
+  @Override
+  public Map<TypeMirror, Set<Block>> getExceptionalSuccessors() {
+    if (exceptionalSuccessors == null) {
+      return Collections.emptyMap();
+    }
+    return Collections.unmodifiableMap(exceptionalSuccessors);
+  }
+
+  @Override
+  public Set<Block> getSuccessors() {
+    Set<Block> result = new LinkedHashSet<>(super.getSuccessors());
+    for (Set<? extends Block> blocks : getExceptionalSuccessors().values()) {
+      result.addAll(blocks);
+    }
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "ExceptionBlock(" + node + ")";
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java
new file mode 100644
index 0000000..baa74e4
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java
@@ -0,0 +1,32 @@
+package org.checkerframework.dataflow.cfg.block;
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.qual.Pure;
+
+/** A regular basic block that contains a sequence of {@link Node}s. */
+public interface RegularBlock extends SingleSuccessorBlock {
+
+  /**
+   * Returns the unmodifiable sequence of {@link Node}s.
+   *
+   * @return the unmodifiable sequence of {@link Node}s
+   * @deprecated use {@link #getNodes} instead
+   */
+  @Deprecated // 2020-08-05
+  @Pure
+  List<Node> getContents();
+
+  /**
+   * Returns the regular successor block.
+   *
+   * @return the regular successor block
+   */
+  @Pure
+  @Nullable Block getRegularSuccessor();
+
+  /** Is this block empty (i.e., does it not contain any contents). */
+  @Pure
+  boolean isEmpty();
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java
new file mode 100644
index 0000000..5e4c47a
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java
@@ -0,0 +1,72 @@
+package org.checkerframework.dataflow.cfg.block;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.Node;
+
+/** Implementation of a regular basic block. */
+public class RegularBlockImpl extends SingleSuccessorBlockImpl implements RegularBlock {
+
+  /** Internal representation of the contents. */
+  protected final List<Node> contents;
+
+  /**
+   * Initialize an empty basic block to be filled with contents and linked to other basic blocks
+   * later.
+   */
+  public RegularBlockImpl() {
+    super(BlockType.REGULAR_BLOCK);
+    contents = new ArrayList<>();
+  }
+
+  /** Add a node to the contents of this basic block. */
+  public void addNode(Node t) {
+    contents.add(t);
+    t.setBlock(this);
+  }
+
+  /** Add multiple nodes to the contents of this basic block. */
+  public void addNodes(List<? extends Node> ts) {
+    for (Node t : ts) {
+      addNode(t);
+    }
+  }
+
+  @SuppressWarnings("deprecation") // implementation of deprecated method in interface
+  @Override
+  public List<Node> getContents() {
+    return getNodes();
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>This implementation returns an non-empty list.
+   */
+  @Override
+  public List<Node> getNodes() {
+    return Collections.unmodifiableList(contents);
+  }
+
+  @Override
+  public @Nullable Node getLastNode() {
+    return contents.get(contents.size() - 1);
+  }
+
+  @Override
+  public @Nullable BlockImpl getRegularSuccessor() {
+    return successor;
+  }
+
+  @Override
+  public String toString() {
+    return "RegularBlock(" + contents + ")";
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return contents.isEmpty();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java
new file mode 100644
index 0000000..8d38302
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java
@@ -0,0 +1,34 @@
+package org.checkerframework.dataflow.cfg.block;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store.FlowRule;
+import org.checkerframework.dataflow.qual.Pure;
+
+/** A basic block that has at exactly one non-exceptional successor. */
+public interface SingleSuccessorBlock extends Block {
+
+  /**
+   * Returns the non-exceptional successor block, or {@code null} if there is no non-exceptional
+   * successor.
+   *
+   * @return the non-exceptional successor block, or {@code null} if there is no non-exceptional
+   *     successor
+   */
+  @Pure
+  @Nullable Block getSuccessor();
+
+  /**
+   * Returns the flow rule for information flowing from this block to its successor.
+   *
+   * @return the flow rule for information flowing from this block to its successor
+   */
+  @Pure
+  FlowRule getFlowRule();
+
+  /**
+   * Set the flow rule for information flowing from this block to its successor.
+   *
+   * @param rule the new flow rule for information flowing from this block to its successor
+   */
+  void setFlowRule(FlowRule rule);
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java
new file mode 100644
index 0000000..8642d29
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java
@@ -0,0 +1,65 @@
+package org.checkerframework.dataflow.cfg.block;
+
+import java.util.Collections;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store.FlowRule;
+
+/**
+ * A basic block that has at most one successor. SpecialBlockImpl extends this, but exit blocks have
+ * no successor.
+ */
+public abstract class SingleSuccessorBlockImpl extends BlockImpl implements SingleSuccessorBlock {
+
+  /** Internal representation of the successor. */
+  protected @Nullable BlockImpl successor;
+
+  /**
+   * The initial value for the rule below says that EACH store at the end of a single successor
+   * block flows to the corresponding store of the successor.
+   */
+  protected FlowRule flowRule = FlowRule.EACH_TO_EACH;
+
+  /**
+   * Creates a new SingleSuccessorBlock.
+   *
+   * @param type the type of this basic block
+   */
+  protected SingleSuccessorBlockImpl(BlockType type) {
+    super(type);
+  }
+
+  @Override
+  public @Nullable Block getSuccessor() {
+    return successor;
+  }
+
+  @Override
+  public Set<Block> getSuccessors() {
+    if (successor == null) {
+      return Collections.emptySet();
+    } else {
+      return Collections.singleton(successor);
+    }
+  }
+
+  /**
+   * Set a basic block as the successor of this block.
+   *
+   * @param successor the block that will be the successor of this
+   */
+  public void setSuccessor(BlockImpl successor) {
+    this.successor = successor;
+    successor.addPredecessor(this);
+  }
+
+  @Override
+  public FlowRule getFlowRule() {
+    return flowRule;
+  }
+
+  @Override
+  public void setFlowRule(FlowRule rule) {
+    flowRule = rule;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java
new file mode 100644
index 0000000..da32708
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java
@@ -0,0 +1,33 @@
+package org.checkerframework.dataflow.cfg.block;
+
+/**
+ * Represents a special basic block; i.e., one of the following:
+ *
+ * <ul>
+ *   <li>Entry block of a method.
+ *   <li>Regular exit block of a method.
+ *   <li>Exceptional exit block of a method.
+ * </ul>
+ */
+public interface SpecialBlock extends SingleSuccessorBlock {
+
+  /** The types of special basic blocks. */
+  public static enum SpecialBlockType {
+
+    /** The entry block of a method. */
+    ENTRY,
+
+    /** The exit block of a method. */
+    EXIT,
+
+    /** A special exit block of a method for exceptional termination. */
+    EXCEPTIONAL_EXIT,
+  }
+
+  /**
+   * Returns the type of this special basic block.
+   *
+   * @return the type of this special basic block
+   */
+  SpecialBlockType getSpecialType();
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java
new file mode 100644
index 0000000..9a3bf14
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java
@@ -0,0 +1,43 @@
+package org.checkerframework.dataflow.cfg.block;
+
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.Node;
+
+/** The implementation of a {@link SpecialBlock}. */
+public class SpecialBlockImpl extends SingleSuccessorBlockImpl implements SpecialBlock {
+
+  /** The type of this special basic block. */
+  protected final SpecialBlockType specialType;
+
+  public SpecialBlockImpl(SpecialBlockType type) {
+    super(BlockType.SPECIAL_BLOCK);
+    this.specialType = type;
+  }
+
+  @Override
+  public SpecialBlockType getSpecialType() {
+    return specialType;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>This implementation returns an empty list.
+   */
+  @Override
+  public List<Node> getNodes() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public @Nullable Node getLastNode() {
+    return null;
+  }
+
+  @Override
+  public String toString() {
+    return "SpecialBlock(" + specialType + ")";
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGBuilder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGBuilder.java
new file mode 100644
index 0000000..a69af96
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGBuilder.java
@@ -0,0 +1,181 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.util.TreePath;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.block.ConditionalBlockImpl;
+import org.checkerframework.dataflow.cfg.block.ExceptionBlockImpl;
+import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.BasicAnnotationProvider;
+import org.checkerframework.javacutil.trees.TreeBuilder;
+
+/**
+ * Builds the control flow graph of some Java code (either a method, or an arbitrary statement).
+ *
+ * <p>The translation of the AST to the CFG is split into three phases:
+ *
+ * <ol>
+ *   <li><em>Phase one.</em> In the first phase, the AST is translated into a sequence of {@link
+ *       org.checkerframework.dataflow.cfg.builder.ExtendedNode}s. An extended node can either be a
+ *       {@link Node}, or one of several meta elements such as a conditional or unconditional jump
+ *       or a node with additional information about exceptions. Some of the extended nodes contain
+ *       labels (e.g., for the jump target), and phase one additionally creates a mapping from
+ *       labels to extended nodes. Finally, the list of leaders is computed: A leader is an extended
+ *       node which will give rise to a basic block in phase two.
+ *   <li><em>Phase two.</em> In this phase, the sequence of extended nodes is translated to a graph
+ *       of control flow blocks that contain nodes. The meta elements from phase one are translated
+ *       into the correct edges.
+ *   <li><em>Phase three.</em> The control flow graph generated in phase two can contain degenerate
+ *       basic blocks such as empty regular basic blocks or conditional basic blocks that have the
+ *       same block as both 'then' and 'else' successor. This phase removes these cases while
+ *       preserving the control flow structure.
+ * </ol>
+ */
+public abstract class CFGBuilder {
+
+  /**
+   * Build the control flow graph of some code.
+   *
+   * @param root the compilation unit
+   * @param underlyingAST the AST that underlies the control frow graph
+   * @param assumeAssertionsDisabled can assertions be assumed to be disabled?
+   * @param assumeAssertionsEnabled can assertions be assumed to be enabled?
+   * @param env annotation processing environment containing type utilities
+   * @return a control flow graph
+   */
+  public static ControlFlowGraph build(
+      CompilationUnitTree root,
+      UnderlyingAST underlyingAST,
+      boolean assumeAssertionsEnabled,
+      boolean assumeAssertionsDisabled,
+      ProcessingEnvironment env) {
+    TreeBuilder builder = new TreeBuilder(env);
+    AnnotationProvider annotationProvider = new BasicAnnotationProvider();
+    PhaseOneResult phase1result =
+        new CFGTranslationPhaseOne(
+                builder, annotationProvider, assumeAssertionsEnabled, assumeAssertionsDisabled, env)
+            .process(root, underlyingAST);
+    ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result);
+    ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result);
+    return phase3result;
+  }
+
+  /**
+   * Build the control flow graph of some code (method, initializer block, ...). bodyPath is the
+   * TreePath to the body of that code.
+   */
+  public static ControlFlowGraph build(
+      TreePath bodyPath,
+      UnderlyingAST underlyingAST,
+      boolean assumeAssertionsEnabled,
+      boolean assumeAssertionsDisabled,
+      ProcessingEnvironment env) {
+    TreeBuilder builder = new TreeBuilder(env);
+    AnnotationProvider annotationProvider = new BasicAnnotationProvider();
+    PhaseOneResult phase1result =
+        new CFGTranslationPhaseOne(
+                builder, annotationProvider, assumeAssertionsEnabled, assumeAssertionsDisabled, env)
+            .process(bodyPath, underlyingAST);
+    ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result);
+    ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result);
+    return phase3result;
+  }
+
+  /** Build the control flow graph of some code. */
+  public static ControlFlowGraph build(
+      CompilationUnitTree root, UnderlyingAST underlyingAST, ProcessingEnvironment env) {
+    return build(root, underlyingAST, false, false, env);
+  }
+
+  /** Build the control flow graph of a method. */
+  public static ControlFlowGraph build(
+      CompilationUnitTree root, MethodTree tree, ClassTree classTree, ProcessingEnvironment env) {
+    UnderlyingAST underlyingAST = new CFGMethod(tree, classTree);
+    return build(root, underlyingAST, false, false, env);
+  }
+
+  /**
+   * Return a printed representation of a collection of extended nodes.
+   *
+   * @param nodes a collection of extended nodes to format
+   * @return a printed representation of the given collection
+   */
+  public static String extendedNodeCollectionToStringDebug(
+      Collection<? extends ExtendedNode> nodes) {
+    StringJoiner result = new StringJoiner(", ", "[", "]");
+    for (ExtendedNode n : nodes) {
+      result.add(n.toStringDebug());
+    }
+    return result.toString();
+  }
+
+  static <A> A firstNonNull(A first, A second) {
+    if (first != null) {
+      return first;
+    } else if (second != null) {
+      return second;
+    } else {
+      throw new NullPointerException();
+    }
+  }
+
+  /* --------------------------------------------------------- */
+  /* Utility routines for debugging CFG building */
+  /* --------------------------------------------------------- */
+
+  /**
+   * Print a set of {@link Block}s and the edges between them. This is useful for examining the
+   * results of phase two.
+   *
+   * @param blocks the blocks to print
+   */
+  protected static void printBlocks(Set<Block> blocks) {
+    for (Block b : blocks) {
+      System.out.print(b.getUid() + ": " + b);
+      switch (b.getType()) {
+        case REGULAR_BLOCK:
+        case SPECIAL_BLOCK:
+          {
+            Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor();
+            System.out.println(" -> " + (succ != null ? succ.getUid() : "||"));
+            break;
+          }
+        case EXCEPTION_BLOCK:
+          {
+            Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor();
+            System.out.print(" -> " + (succ != null ? succ.getUid() : "||") + " {");
+            for (Map.Entry<TypeMirror, Set<Block>> entry :
+                ((ExceptionBlockImpl) b).getExceptionalSuccessors().entrySet()) {
+              System.out.print(entry.getKey() + " : " + entry.getValue() + ", ");
+            }
+            System.out.println("}");
+            break;
+          }
+        case CONDITIONAL_BLOCK:
+          {
+            Block tSucc = ((ConditionalBlockImpl) b).getThenSuccessor();
+            Block eSucc = ((ConditionalBlockImpl) b).getElseSuccessor();
+            System.out.println(
+                " -> T "
+                    + (tSucc != null ? tSucc.getUid() : "||")
+                    + " F "
+                    + (eSucc != null ? eSucc.getUid() : "||"));
+            break;
+          }
+      }
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java
new file mode 100644
index 0000000..09ad9f9
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java
@@ -0,0 +1,3673 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.AssertTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.BreakTree;
+import com.sun.source.tree.CaseTree;
+import com.sun.source.tree.CatchTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ContinueTree;
+import com.sun.source.tree.DoWhileLoopTree;
+import com.sun.source.tree.EmptyStatementTree;
+import com.sun.source.tree.EnhancedForLoopTree;
+import com.sun.source.tree.ErroneousTree;
+import com.sun.source.tree.ExpressionStatementTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.ForLoopTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.LabeledStatementTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModifiersTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.PrimitiveTypeTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.SwitchTree;
+import com.sun.source.tree.SynchronizedTree;
+import com.sun.source.tree.ThrowTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.TryTree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.TypeParameterTree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.UnionTypeTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.tree.WhileLoopTree;
+import com.sun.source.tree.WildcardTree;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.ReferenceType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store.FlowRule;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
+import org.checkerframework.dataflow.cfg.node.ArrayCreationNode;
+import org.checkerframework.dataflow.cfg.node.ArrayTypeNode;
+import org.checkerframework.dataflow.cfg.node.AssertionErrorNode;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.BitwiseAndNode;
+import org.checkerframework.dataflow.cfg.node.BitwiseComplementNode;
+import org.checkerframework.dataflow.cfg.node.BitwiseOrNode;
+import org.checkerframework.dataflow.cfg.node.BitwiseXorNode;
+import org.checkerframework.dataflow.cfg.node.BooleanLiteralNode;
+import org.checkerframework.dataflow.cfg.node.CaseNode;
+import org.checkerframework.dataflow.cfg.node.CharacterLiteralNode;
+import org.checkerframework.dataflow.cfg.node.ClassDeclarationNode;
+import org.checkerframework.dataflow.cfg.node.ClassNameNode;
+import org.checkerframework.dataflow.cfg.node.ConditionalAndNode;
+import org.checkerframework.dataflow.cfg.node.ConditionalNotNode;
+import org.checkerframework.dataflow.cfg.node.ConditionalOrNode;
+import org.checkerframework.dataflow.cfg.node.DoubleLiteralNode;
+import org.checkerframework.dataflow.cfg.node.EqualToNode;
+import org.checkerframework.dataflow.cfg.node.ExplicitThisNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.FloatLiteralNode;
+import org.checkerframework.dataflow.cfg.node.FloatingDivisionNode;
+import org.checkerframework.dataflow.cfg.node.FloatingRemainderNode;
+import org.checkerframework.dataflow.cfg.node.FunctionalInterfaceNode;
+import org.checkerframework.dataflow.cfg.node.GreaterThanNode;
+import org.checkerframework.dataflow.cfg.node.GreaterThanOrEqualNode;
+import org.checkerframework.dataflow.cfg.node.ImplicitThisNode;
+import org.checkerframework.dataflow.cfg.node.InstanceOfNode;
+import org.checkerframework.dataflow.cfg.node.IntegerDivisionNode;
+import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode;
+import org.checkerframework.dataflow.cfg.node.IntegerRemainderNode;
+import org.checkerframework.dataflow.cfg.node.LambdaResultExpressionNode;
+import org.checkerframework.dataflow.cfg.node.LeftShiftNode;
+import org.checkerframework.dataflow.cfg.node.LessThanNode;
+import org.checkerframework.dataflow.cfg.node.LessThanOrEqualNode;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.LongLiteralNode;
+import org.checkerframework.dataflow.cfg.node.MarkerNode;
+import org.checkerframework.dataflow.cfg.node.MethodAccessNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.NarrowingConversionNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.NotEqualNode;
+import org.checkerframework.dataflow.cfg.node.NullChkNode;
+import org.checkerframework.dataflow.cfg.node.NullLiteralNode;
+import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode;
+import org.checkerframework.dataflow.cfg.node.NumericalMinusNode;
+import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode;
+import org.checkerframework.dataflow.cfg.node.NumericalPlusNode;
+import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
+import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
+import org.checkerframework.dataflow.cfg.node.PackageNameNode;
+import org.checkerframework.dataflow.cfg.node.ParameterizedTypeNode;
+import org.checkerframework.dataflow.cfg.node.PrimitiveTypeNode;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+import org.checkerframework.dataflow.cfg.node.SignedRightShiftNode;
+import org.checkerframework.dataflow.cfg.node.StringConcatenateAssignmentNode;
+import org.checkerframework.dataflow.cfg.node.StringConcatenateNode;
+import org.checkerframework.dataflow.cfg.node.StringConversionNode;
+import org.checkerframework.dataflow.cfg.node.StringLiteralNode;
+import org.checkerframework.dataflow.cfg.node.SuperNode;
+import org.checkerframework.dataflow.cfg.node.SynchronizedNode;
+import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
+import org.checkerframework.dataflow.cfg.node.ThisNode;
+import org.checkerframework.dataflow.cfg.node.ThrowNode;
+import org.checkerframework.dataflow.cfg.node.TypeCastNode;
+import org.checkerframework.dataflow.cfg.node.UnsignedRightShiftNode;
+import org.checkerframework.dataflow.cfg.node.ValueLiteralNode;
+import org.checkerframework.dataflow.cfg.node.VariableDeclarationNode;
+import org.checkerframework.dataflow.cfg.node.WideningConversionNode;
+import org.checkerframework.dataflow.qual.TerminatesExecution;
+import org.checkerframework.dataflow.util.IdentityMostlySingleton;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypeKindUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.checkerframework.javacutil.trees.TreeBuilder;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * Class that performs phase one of the translation process. It generates the following information:
+ *
+ * <ul>
+ *   <li>A sequence of extended nodes.
+ *   <li>A set of bindings from {@link Label}s to positions in the node sequence.
+ *   <li>A set of leader nodes that give rise to basic blocks in phase two.
+ *   <li>A lookup map that gives the mapping from AST tree nodes to {@link Node}s.
+ * </ul>
+ *
+ * <p>The return type of this scanner is {@link Node}. For expressions, the corresponding node is
+ * returned to allow linking between different nodes.
+ *
+ * <p>However, for statements there is usually no single {@link Node} that is created, and thus no
+ * node is returned (rather, null is returned).
+ *
+ * <p>Every {@code visit*} method is assumed to add at least one extended node to the list of nodes
+ * (which might only be a jump).
+ */
+@SuppressWarnings("nullness") // TODO
+public class CFGTranslationPhaseOne extends TreePathScanner<Node, Void> {
+
+  /** Annotation processing environment and its associated type and tree utilities. */
+  final ProcessingEnvironment env;
+
+  final Elements elements;
+  final Types types;
+  final Trees trees;
+  public final TreeBuilder treeBuilder;
+  final AnnotationProvider annotationProvider;
+
+  /** Can assertions be assumed to be disabled? */
+  final boolean assumeAssertionsDisabled;
+
+  /** Can assertions be assumed to be enabled? */
+  final boolean assumeAssertionsEnabled;
+
+  /* --------------------------------------------------------- */
+  /* Extended Node Types and Labels */
+  /* --------------------------------------------------------- */
+
+  /** Special label to identify the regular exit. */
+  final Label regularExitLabel;
+
+  /** Special label to identify the exceptional exit. */
+  final Label exceptionalExitLabel;
+
+  /**
+   * Current {@link TryFinallyScopeCell} to which a return statement should jump, or null if there
+   * is no valid destination.
+   */
+  @Nullable TryFinallyScopeCell returnTargetL;
+
+  /**
+   * Current {@link TryFinallyScopeCell} to which a break statement with no label should jump, or
+   * null if there is no valid destination.
+   */
+  @Nullable TryFinallyScopeCell breakTargetL;
+
+  /**
+   * Map from AST label Names to CFG {@link Label}s for breaks. Each labeled statement creates two
+   * CFG {@link Label}s, one for break and one for continue.
+   */
+  Map<Name, Label> breakLabels;
+
+  /**
+   * Current {@link TryFinallyScopeCell} to which a continue statement with no label should jump, or
+   * null if there is no valid destination.
+   */
+  @Nullable TryFinallyScopeCell continueTargetL;
+
+  /**
+   * Map from AST label Names to CFG {@link Label}s for continues. Each labeled statement creates
+   * two CFG {@link Label}s, one for break and one for continue.
+   */
+  Map<Name, Label> continueLabels;
+
+  /** Nested scopes of try-catch blocks in force at the current program point. */
+  private final TryStack tryStack;
+
+  /**
+   * Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will
+   * have at least one corresponding Node. Trees that undergo conversions, such as boxing or
+   * unboxing, can map to two distinct Nodes. The Node for the pre-conversion value is stored in the
+   * treeLookupMap, while the Node for the post-conversion value is stored in the
+   * convertedTreeLookupMap.
+   */
+  final IdentityHashMap<Tree, Set<Node>> treeLookupMap;
+
+  /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */
+  final IdentityHashMap<Tree, Set<Node>> convertedTreeLookupMap;
+
+  /** Map from AST {@link UnaryTree}s to compound {@link AssignmentNode}s. */
+  final IdentityHashMap<UnaryTree, AssignmentNode> unaryAssignNodeLookupMap;
+
+  /** The list of extended nodes. */
+  final ArrayList<ExtendedNode> nodeList;
+
+  /** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */
+  final Map<Label, Integer> bindings;
+
+  /** The set of leaders (represented as indices into {@code nodeList}). */
+  final Set<Integer> leaders;
+
+  /**
+   * All return nodes (if any) encountered. Only includes return statements that actually return
+   * something
+   */
+  private final List<ReturnNode> returnNodes;
+
+  /**
+   * Class declarations that have been encountered when building the control-flow graph for a
+   * method.
+   */
+  final List<ClassTree> declaredClasses;
+
+  /**
+   * Lambdas encountered when building the control-flow graph for a method, variable initializer, or
+   * initializer.
+   */
+  final List<LambdaExpressionTree> declaredLambdas;
+
+  /** The ArithmeticException type. */
+  final TypeMirror arithmeticExceptionType;
+
+  /** The ArrayIndexOutOfBoundsException type. */
+  final TypeMirror arrayIndexOutOfBoundsExceptionType;
+
+  /** The AssertionError type. */
+  final TypeMirror assertionErrorType;
+
+  /** The ClassCastException type . */
+  final TypeMirror classCastExceptionType;
+
+  /** The Iterable type (erased). */
+  final TypeMirror iterableType;
+
+  /** The NegativeArraySizeException type. */
+  final TypeMirror negativeArraySizeExceptionType;
+
+  /** The NullPointerException type . */
+  final TypeMirror nullPointerExceptionType;
+
+  /** The OutOfMemoryError type. */
+  final @Nullable TypeMirror outOfMemoryErrorType;
+
+  /** The ClassCircularityError type. */
+  final @Nullable TypeMirror classCircularityErrorType;
+
+  /** The ClassFormatErrorType type. */
+  final @Nullable TypeMirror classFormatErrorType;
+
+  /** The NoClassDefFoundError type. */
+  final @Nullable TypeMirror noClassDefFoundErrorType;
+
+  /** The String type. */
+  final TypeMirror stringType;
+
+  /** The Throwable type. */
+  final TypeMirror throwableType;
+
+  /**
+   * Supertypes of all unchecked exceptions. The size is 2 and the contents are {@code
+   * RuntimeException} and {@code Error}.
+   */
+  final Set<TypeMirror> uncheckedExceptionTypes;
+
+  /**
+   * Exceptions that can be thrown by array creation "new SomeType[]". The size is 2 and the
+   * contents are {@code NegativeArraySizeException} and {@code OutOfMemoryError}. This list comes
+   * from JLS 15.10.1 "Run-Time Evaluation of Array Creation Expressions".
+   */
+  final Set<TypeMirror> newArrayExceptionTypes;
+
+  /**
+   * @param treeBuilder builder for new AST nodes
+   * @param annotationProvider extracts annotations from AST nodes
+   * @param assumeAssertionsDisabled can assertions be assumed to be disabled?
+   * @param assumeAssertionsEnabled can assertions be assumed to be enabled?
+   * @param env annotation processing environment containing type utilities
+   */
+  public CFGTranslationPhaseOne(
+      TreeBuilder treeBuilder,
+      AnnotationProvider annotationProvider,
+      boolean assumeAssertionsEnabled,
+      boolean assumeAssertionsDisabled,
+      ProcessingEnvironment env) {
+    this.env = env;
+    this.treeBuilder = treeBuilder;
+    this.annotationProvider = annotationProvider;
+
+    assert !(assumeAssertionsDisabled && assumeAssertionsEnabled);
+    this.assumeAssertionsEnabled = assumeAssertionsEnabled;
+    this.assumeAssertionsDisabled = assumeAssertionsDisabled;
+
+    elements = env.getElementUtils();
+    types = env.getTypeUtils();
+    trees = Trees.instance(env);
+
+    // initialize lists and maps
+    treeLookupMap = new IdentityHashMap<>();
+    convertedTreeLookupMap = new IdentityHashMap<>();
+    unaryAssignNodeLookupMap = new IdentityHashMap<>();
+    nodeList = new ArrayList<>();
+    bindings = new HashMap<>();
+    leaders = new HashSet<>();
+
+    regularExitLabel = new Label();
+    exceptionalExitLabel = new Label();
+    tryStack = new TryStack(exceptionalExitLabel);
+    returnTargetL = new TryFinallyScopeCell(regularExitLabel);
+    breakLabels = new HashMap<>(2);
+    continueLabels = new HashMap<>(2);
+    returnNodes = new ArrayList<>();
+    declaredClasses = new ArrayList<>();
+    declaredLambdas = new ArrayList<>();
+
+    arithmeticExceptionType = getTypeMirror(ArithmeticException.class);
+    arrayIndexOutOfBoundsExceptionType = getTypeMirror(ArrayIndexOutOfBoundsException.class);
+    assertionErrorType = getTypeMirror(AssertionError.class);
+    classCastExceptionType = getTypeMirror(ClassCastException.class);
+    iterableType = types.erasure(getTypeMirror(Iterable.class));
+    negativeArraySizeExceptionType = getTypeMirror(NegativeArraySizeException.class);
+    nullPointerExceptionType = getTypeMirror(NullPointerException.class);
+    outOfMemoryErrorType = maybeGetTypeMirror(OutOfMemoryError.class);
+    classCircularityErrorType = maybeGetTypeMirror(ClassCircularityError.class);
+    classFormatErrorType = maybeGetTypeMirror(ClassFormatError.class);
+    noClassDefFoundErrorType = maybeGetTypeMirror(NoClassDefFoundError.class);
+    stringType = getTypeMirror(String.class);
+    throwableType = getTypeMirror(Throwable.class);
+    uncheckedExceptionTypes = new LinkedHashSet<>(2);
+    uncheckedExceptionTypes.add(getTypeMirror(RuntimeException.class));
+    uncheckedExceptionTypes.add(getTypeMirror(Error.class));
+    newArrayExceptionTypes = new LinkedHashSet<>(2);
+    newArrayExceptionTypes.add(negativeArraySizeExceptionType);
+    if (outOfMemoryErrorType != null) {
+      newArrayExceptionTypes.add(outOfMemoryErrorType);
+    }
+  }
+
+  /**
+   * Performs the actual work of phase one.
+   *
+   * @param bodyPath path to the body of the underlying AST's method
+   * @param underlyingAST the AST for which the CFG is to be built
+   * @return the result of phase one
+   */
+  public PhaseOneResult process(TreePath bodyPath, UnderlyingAST underlyingAST) {
+    // traverse AST of the method body
+    Node finalNode = scan(bodyPath, null);
+
+    // If we are building the CFG for a lambda with a single expression as the body, then
+    // add an extra node for the result of that lambda
+    if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) {
+      LambdaExpressionTree lambdaTree = ((UnderlyingAST.CFGLambda) underlyingAST).getLambdaTree();
+      if (lambdaTree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) {
+        Node resultNode =
+            new LambdaResultExpressionNode(
+                (ExpressionTree) lambdaTree.getBody(), finalNode, env.getTypeUtils());
+        extendWithNode(resultNode);
+      }
+    }
+
+    // Add marker to indicate that the next block will be the exit block.
+    // Note: if there is a return statement earlier in the method (which is always the case for
+    // non-void methods), then this is not strictly necessary. However, it is also not a problem, as
+    // it will just generate a degenerated control graph case that will be removed in a later phase.
+    nodeList.add(new UnconditionalJump(regularExitLabel));
+
+    return new PhaseOneResult(
+        underlyingAST,
+        treeLookupMap,
+        convertedTreeLookupMap,
+        unaryAssignNodeLookupMap,
+        nodeList,
+        bindings,
+        leaders,
+        returnNodes,
+        regularExitLabel,
+        exceptionalExitLabel,
+        declaredClasses,
+        declaredLambdas);
+  }
+
+  public PhaseOneResult process(CompilationUnitTree root, UnderlyingAST underlyingAST) {
+    // TODO: Isn't this costly? Is there no cache we can reuse?
+    TreePath bodyPath = trees.getPath(root, underlyingAST.getCode());
+    assert bodyPath != null;
+    return process(bodyPath, underlyingAST);
+  }
+
+  /**
+   * Perform any actions required when CFG translation creates a new Tree that is not part of the
+   * original AST.
+   *
+   * @param tree the newly created Tree
+   */
+  public void handleArtificialTree(Tree tree) {}
+
+  /* --------------------------------------------------------- */
+  /* Nodes and Labels Management */
+  /* --------------------------------------------------------- */
+
+  /**
+   * Add a node to the lookup map if it not already present.
+   *
+   * @param node the node to add to the lookup map
+   */
+  protected void addToLookupMap(Node node) {
+    Tree tree = node.getTree();
+    if (tree == null) {
+      return;
+    }
+    Set<Node> existing = treeLookupMap.get(tree);
+    if (existing == null) {
+      treeLookupMap.put(tree, new IdentityMostlySingleton<>(node));
+    } else {
+      existing.add(node);
+    }
+
+    Tree enclosingParens = parenMapping.get(tree);
+    while (enclosingParens != null) {
+      Set<Node> exp = treeLookupMap.get(enclosingParens);
+      if (exp == null) {
+        treeLookupMap.put(enclosingParens, new IdentityMostlySingleton<>(node));
+      } else if (!existing.contains(node)) {
+        exp.add(node);
+      }
+      enclosingParens = parenMapping.get(enclosingParens);
+    }
+  }
+
+  /**
+   * Add a node in the post-conversion lookup map. The node should refer to a Tree and that Tree
+   * should already be in the pre-conversion lookup map. This method is used to update the Tree-Node
+   * mapping with conversion nodes.
+   *
+   * @param node the node to add to the lookup map
+   */
+  protected void addToConvertedLookupMap(Node node) {
+    Tree tree = node.getTree();
+    addToConvertedLookupMap(tree, node);
+  }
+
+  /**
+   * Add a node in the post-conversion lookup map. The tree argument should already be in the
+   * pre-conversion lookup map. This method is used to update the Tree-Node mapping with conversion
+   * nodes.
+   *
+   * @param tree the tree used as a key in the map
+   * @param node the node to add to the lookup map
+   */
+  protected void addToConvertedLookupMap(Tree tree, Node node) {
+    assert tree != null;
+    assert treeLookupMap.containsKey(tree);
+    Set<Node> existing = convertedTreeLookupMap.get(tree);
+    if (existing == null) {
+      convertedTreeLookupMap.put(tree, new IdentityMostlySingleton<>(node));
+    } else {
+      existing.add(node);
+    }
+  }
+
+  /**
+   * Add a unary tree in the compound assign lookup map. This method is used to update the
+   * UnaryTree-AssignmentNode mapping with compound assign nodes.
+   *
+   * @param tree the tree used as a key in the map
+   * @param unaryAssignNode the node to add to the lookup map
+   */
+  protected void addToUnaryAssignLookupMap(UnaryTree tree, AssignmentNode unaryAssignNode) {
+    unaryAssignNodeLookupMap.put(tree, unaryAssignNode);
+  }
+
+  /**
+   * Extend the list of extended nodes with a node.
+   *
+   * @param node the node to add
+   */
+  protected void extendWithNode(Node node) {
+    addToLookupMap(node);
+    extendWithExtendedNode(new NodeHolder(node));
+  }
+
+  /**
+   * Extend the list of extended nodes with a node, where {@code node} might throw the exception
+   * {@code cause}.
+   *
+   * @param node the node to add
+   * @param cause an exception that the node might throw
+   * @return the node holder
+   */
+  protected NodeWithExceptionsHolder extendWithNodeWithException(Node node, TypeMirror cause) {
+    addToLookupMap(node);
+    return extendWithNodeWithExceptions(node, Collections.singleton(cause));
+  }
+
+  /**
+   * Extend the list of extended nodes with a node, where {@code node} might throw any of the
+   * exceptions in {@code causes}.
+   *
+   * @param node the node to add
+   * @param causes set of exceptions that the node might throw
+   * @return the node holder
+   */
+  protected NodeWithExceptionsHolder extendWithNodeWithExceptions(
+      Node node, Set<TypeMirror> causes) {
+    addToLookupMap(node);
+    Map<TypeMirror, Set<Label>> exceptions = new LinkedHashMap<>(causes.size());
+    for (TypeMirror cause : causes) {
+      exceptions.put(cause, tryStack.possibleLabels(cause));
+    }
+    NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions);
+    extendWithExtendedNode(exNode);
+    return exNode;
+  }
+
+  /**
+   * Extend a list of extended nodes with a ClassName node.
+   *
+   * <p>Evaluating a class literal kicks off class loading (JLS 15.8.2) which can fail and throw one
+   * of the specified subclasses of a LinkageError or an OutOfMemoryError (JLS 12.2.1).
+   *
+   * @param node the ClassName node to add
+   * @return the node holder
+   */
+  protected NodeWithExceptionsHolder extendWithClassNameNode(ClassNameNode node) {
+    Set<TypeMirror> thrownSet = new LinkedHashSet<>(4);
+    if (classCircularityErrorType != null) {
+      thrownSet.add(classCircularityErrorType);
+    }
+    if (classFormatErrorType != null) {
+      thrownSet.add(classFormatErrorType);
+    }
+    if (noClassDefFoundErrorType != null) {
+      thrownSet.add(noClassDefFoundErrorType);
+    }
+    if (outOfMemoryErrorType != null) {
+      thrownSet.add(outOfMemoryErrorType);
+    }
+
+    return extendWithNodeWithExceptions(node, thrownSet);
+  }
+
+  /**
+   * Insert {@code node} after {@code pred} in the list of extended nodes, or append to the list if
+   * {@code pred} is not present.
+   *
+   * @param node the node to add
+   * @param pred the desired predecessor of node
+   * @return the node holder
+   */
+  protected <T extends Node> T insertNodeAfter(T node, Node pred) {
+    addToLookupMap(node);
+    insertExtendedNodeAfter(new NodeHolder(node), pred);
+    return node;
+  }
+
+  /**
+   * Insert a {@code node} that might throw the exceptions in {@code causes} after {@code pred} in
+   * the list of extended nodes, or append to the list if {@code pred} is not present.
+   *
+   * @param node the node to add
+   * @param causes set of exceptions that the node might throw
+   * @param pred the desired predecessor of node
+   * @return the node holder
+   */
+  protected NodeWithExceptionsHolder insertNodeWithExceptionsAfter(
+      Node node, Set<TypeMirror> causes, Node pred) {
+    addToLookupMap(node);
+    Map<TypeMirror, Set<Label>> exceptions = new LinkedHashMap<>(causes.size());
+    for (TypeMirror cause : causes) {
+      exceptions.put(cause, tryStack.possibleLabels(cause));
+    }
+    NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions);
+    insertExtendedNodeAfter(exNode, pred);
+    return exNode;
+  }
+
+  /**
+   * Extend the list of extended nodes with an extended node.
+   *
+   * @param n the extended node
+   */
+  protected void extendWithExtendedNode(ExtendedNode n) {
+    nodeList.add(n);
+  }
+
+  /**
+   * Insert {@code n} after the node {@code pred} in the list of extended nodes, or append {@code n}
+   * if {@code pred} is not present.
+   *
+   * @param n the extended node
+   * @param pred the desired predecessor
+   */
+  @SuppressWarnings("ModifyCollectionInEnhancedForLoop")
+  protected void insertExtendedNodeAfter(ExtendedNode n, @FindDistinct Node pred) {
+    int index = -1;
+    for (int i = 0; i < nodeList.size(); i++) {
+      ExtendedNode inList = nodeList.get(i);
+      if (inList instanceof NodeHolder || inList instanceof NodeWithExceptionsHolder) {
+        if (inList.getNode() == pred) {
+          index = i;
+          break;
+        }
+      }
+    }
+    if (index != -1) {
+      nodeList.add(index + 1, n);
+      // update bindings
+      for (Map.Entry<Label, Integer> e : bindings.entrySet()) {
+        if (e.getValue() >= index + 1) {
+          bindings.put(e.getKey(), e.getValue() + 1);
+        }
+      }
+      // update leaders
+      Set<Integer> oldLeaders = new HashSet<>(leaders);
+      leaders.clear();
+      for (Integer l : oldLeaders) {
+        if (l >= index + 1) {
+          leaders.add(l + 1);
+        } else {
+          leaders.add(l);
+        }
+      }
+    } else {
+      nodeList.add(n);
+    }
+  }
+
+  /** Add the label {@code l} to the extended node that will be placed next in the sequence. */
+  protected void addLabelForNextNode(Label l) {
+    assert !bindings.containsKey(l);
+    leaders.add(nodeList.size());
+    bindings.put(l, nodeList.size());
+  }
+
+  /* --------------------------------------------------------- */
+  /* Utility Methods */
+  /* --------------------------------------------------------- */
+
+  protected long uid = 0;
+
+  protected String uniqueName(String prefix) {
+    return prefix + "#num" + uid++;
+  }
+
+  /**
+   * If the input node is an unboxed primitive type, insert a call to the appropriate valueOf
+   * method, otherwise leave it alone.
+   *
+   * @param node in input node
+   * @return a Node representing the boxed version of the input, which may simply be the input node
+   */
+  protected Node box(Node node) {
+    // For boxing conversion, see JLS 5.1.7
+    if (TypesUtils.isPrimitive(node.getType())) {
+      PrimitiveType primitive = types.getPrimitiveType(node.getType().getKind());
+      TypeMirror boxedType = types.getDeclaredType(types.boxedClass(primitive));
+
+      TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement();
+      IdentifierTree classTree = treeBuilder.buildClassUse(boxedElement);
+      handleArtificialTree(classTree);
+      // No need to handle possible errors from evaluating a class literal here
+      // since this is synthetic code that can't fail.
+      ClassNameNode className = new ClassNameNode(classTree);
+      className.setInSource(false);
+      insertNodeAfter(className, node);
+
+      MemberSelectTree valueOfSelect = treeBuilder.buildValueOfMethodAccess(classTree);
+      handleArtificialTree(valueOfSelect);
+      MethodAccessNode valueOfAccess = new MethodAccessNode(valueOfSelect, className);
+      valueOfAccess.setInSource(false);
+      insertNodeAfter(valueOfAccess, className);
+
+      MethodInvocationTree valueOfCall =
+          treeBuilder.buildMethodInvocation(valueOfSelect, (ExpressionTree) node.getTree());
+      handleArtificialTree(valueOfCall);
+      Node boxed =
+          new MethodInvocationNode(
+              valueOfCall, valueOfAccess, Collections.singletonList(node), getCurrentPath());
+      boxed.setInSource(false);
+      // Add Throwable to account for unchecked exceptions
+      addToConvertedLookupMap(node.getTree(), boxed);
+      insertNodeWithExceptionsAfter(boxed, uncheckedExceptionTypes, valueOfAccess);
+      return boxed;
+    } else {
+      return node;
+    }
+  }
+
+  /**
+   * If the input node is a boxed type, unbox it, otherwise leave it alone.
+   *
+   * @param node in input node
+   * @return a Node representing the unboxed version of the input, which may simply be the input
+   *     node
+   */
+  protected Node unbox(Node node) {
+    if (TypesUtils.isBoxedPrimitive(node.getType())) {
+
+      MemberSelectTree primValueSelect = treeBuilder.buildPrimValueMethodAccess(node.getTree());
+      handleArtificialTree(primValueSelect);
+      MethodAccessNode primValueAccess = new MethodAccessNode(primValueSelect, node);
+      primValueAccess.setInSource(false);
+      // Method access may throw NullPointerException
+      insertNodeWithExceptionsAfter(
+          primValueAccess, Collections.singleton(nullPointerExceptionType), node);
+
+      MethodInvocationTree primValueCall = treeBuilder.buildMethodInvocation(primValueSelect);
+      handleArtificialTree(primValueCall);
+      Node unboxed =
+          new MethodInvocationNode(
+              primValueCall, primValueAccess, Collections.emptyList(), getCurrentPath());
+      unboxed.setInSource(false);
+
+      // Add Throwable to account for unchecked exceptions
+      addToConvertedLookupMap(node.getTree(), unboxed);
+      insertNodeWithExceptionsAfter(unboxed, uncheckedExceptionTypes, primValueAccess);
+      return unboxed;
+    } else {
+      return node;
+    }
+  }
+
+  private TreeInfo getTreeInfo(Tree tree) {
+    final TypeMirror type = TreeUtils.typeOf(tree);
+    final boolean boxed = TypesUtils.isBoxedPrimitive(type);
+    final TypeMirror unboxedType = boxed ? types.unboxedType(type) : type;
+
+    final boolean bool = TypesUtils.isBooleanType(type);
+    final boolean numeric = TypesUtils.isNumeric(unboxedType);
+
+    return new TreeInfo() {
+      @Override
+      public boolean isNumeric() {
+        return numeric;
+      }
+
+      @Override
+      public boolean isBoxed() {
+        return boxed;
+      }
+
+      @Override
+      public boolean isBoolean() {
+        return bool;
+      }
+
+      @Override
+      public TypeMirror unboxedType() {
+        return unboxedType;
+      }
+    };
+  }
+
+  /**
+   * Returns the unboxed tree if necessary, as described in JLS 5.1.8.
+   *
+   * @return the unboxed tree if necessary, as described in JLS 5.1.8
+   */
+  private Node unboxAsNeeded(Node node, boolean boxed) {
+    return boxed ? unbox(node) : node;
+  }
+
+  /**
+   * Convert the input node to String type, if it isn't already.
+   *
+   * @param node an input node
+   * @return a Node with the value promoted to String, which may be the input node
+   */
+  protected Node stringConversion(Node node) {
+    // For string conversion, see JLS 5.1.11
+    if (!TypesUtils.isString(node.getType())) {
+      Node converted = new StringConversionNode(node.getTree(), node, stringType);
+      addToConvertedLookupMap(converted);
+      insertNodeAfter(converted, node);
+      return converted;
+    } else {
+      return node;
+    }
+  }
+
+  /**
+   * Perform unary numeric promotion on the input node.
+   *
+   * @param node a node producing a value of numeric primitive or boxed type
+   * @return a Node with the value promoted to the int, long, float, or double; may return be the
+   *     input node
+   */
+  protected Node unaryNumericPromotion(Node node) {
+    // For unary numeric promotion, see JLS 5.6.1
+    node = unbox(node);
+
+    switch (node.getType().getKind()) {
+      case BYTE:
+      case CHAR:
+      case SHORT:
+        {
+          TypeMirror intType = types.getPrimitiveType(TypeKind.INT);
+          Node widened = new WideningConversionNode(node.getTree(), node, intType);
+          addToConvertedLookupMap(widened);
+          insertNodeAfter(widened, node);
+          return widened;
+        }
+      default:
+        // Nothing to do.
+        break;
+    }
+
+    return node;
+  }
+
+  /**
+   * Returns true if the argument type is a numeric primitive or a boxed numeric primitive and false
+   * otherwise.
+   */
+  protected boolean isNumericOrBoxed(TypeMirror type) {
+    if (TypesUtils.isBoxedPrimitive(type)) {
+      type = types.unboxedType(type);
+    }
+    return TypesUtils.isNumeric(type);
+  }
+
+  /**
+   * Compute the type to which two numeric types must be promoted before performing a binary numeric
+   * operation on them. The input types must both be numeric and the output type is primitive.
+   *
+   * @param left the type of the left operand
+   * @param right the type of the right operand
+   * @return a TypeMirror representing the binary numeric promoted type
+   */
+  protected TypeMirror binaryPromotedType(TypeMirror left, TypeMirror right) {
+    if (TypesUtils.isBoxedPrimitive(left)) {
+      left = types.unboxedType(left);
+    }
+    if (TypesUtils.isBoxedPrimitive(right)) {
+      right = types.unboxedType(right);
+    }
+    TypeKind promotedTypeKind = TypeKindUtils.widenedNumericType(left, right);
+    return types.getPrimitiveType(promotedTypeKind);
+  }
+
+  /**
+   * Perform binary numeric promotion on the input node to make it match the expression type.
+   *
+   * @param node a node producing a value of numeric primitive or boxed type
+   * @param exprType the type to promote the value to
+   * @return a Node with the value promoted to the exprType, which may be the input node
+   */
+  protected Node binaryNumericPromotion(Node node, TypeMirror exprType) {
+    // For binary numeric promotion, see JLS 5.6.2
+    node = unbox(node);
+
+    if (!types.isSameType(node.getType(), exprType)) {
+      Node widened = new WideningConversionNode(node.getTree(), node, exprType);
+      addToConvertedLookupMap(widened);
+      insertNodeAfter(widened, node);
+      return widened;
+    } else {
+      return node;
+    }
+  }
+
+  /**
+   * Perform widening primitive conversion on the input node to make it match the destination type.
+   *
+   * @param node a node producing a value of numeric primitive type
+   * @param destType the type to widen the value to
+   * @return a Node with the value widened to the exprType, which may be the input node
+   */
+  protected Node widen(Node node, TypeMirror destType) {
+    // For widening conversion, see JLS 5.1.2
+    assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType)
+        : "widening must be applied to primitive types";
+    if (types.isSubtype(node.getType(), destType) && !types.isSameType(node.getType(), destType)) {
+      Node widened = new WideningConversionNode(node.getTree(), node, destType);
+      addToConvertedLookupMap(widened);
+      insertNodeAfter(widened, node);
+      return widened;
+    } else {
+      return node;
+    }
+  }
+
+  /**
+   * Perform narrowing conversion on the input node to make it match the destination type.
+   *
+   * @param node a node producing a value of numeric primitive type
+   * @param destType the type to narrow the value to
+   * @return a Node with the value narrowed to the exprType, which may be the input node
+   */
+  protected Node narrow(Node node, TypeMirror destType) {
+    // For narrowing conversion, see JLS 5.1.3
+    assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType)
+        : "narrowing must be applied to primitive types";
+    if (types.isSubtype(destType, node.getType()) && !types.isSameType(destType, node.getType())) {
+      Node narrowed = new NarrowingConversionNode(node.getTree(), node, destType);
+      addToConvertedLookupMap(narrowed);
+      insertNodeAfter(narrowed, node);
+      return narrowed;
+    } else {
+      return node;
+    }
+  }
+
+  /**
+   * Perform narrowing conversion and optionally boxing conversion on the input node to make it
+   * match the destination type.
+   *
+   * @param node a node producing a value of numeric primitive type
+   * @param destType the type to narrow the value to (possibly boxed)
+   * @return a Node with the value narrowed and boxed to the destType, which may be the input node
+   */
+  protected Node narrowAndBox(Node node, TypeMirror destType) {
+    if (TypesUtils.isBoxedPrimitive(destType)) {
+      return box(narrow(node, types.unboxedType(destType)));
+    } else {
+      return narrow(node, destType);
+    }
+  }
+
+  /**
+   * Return whether a conversion from the type of the node to varType requires narrowing.
+   *
+   * @param varType the type of a variable (or general LHS) to be converted to
+   * @param node a node whose value is being converted
+   * @return whether this conversion requires narrowing to succeed
+   */
+  protected boolean conversionRequiresNarrowing(TypeMirror varType, Node node) {
+    // Narrowing is restricted to cases where the left hand side is byte, char, short or Byte, Char,
+    // Short and the right hand side is a constant.
+    TypeMirror unboxedVarType =
+        TypesUtils.isBoxedPrimitive(varType) ? types.unboxedType(varType) : varType;
+    TypeKind unboxedVarKind = unboxedVarType.getKind();
+    boolean isLeftNarrowableTo =
+        unboxedVarKind == TypeKind.BYTE
+            || unboxedVarKind == TypeKind.SHORT
+            || unboxedVarKind == TypeKind.CHAR;
+    boolean isRightConstant = node instanceof ValueLiteralNode;
+    return isLeftNarrowableTo && isRightConstant;
+  }
+
+  /**
+   * Assignment conversion and method invocation conversion are almost identical, except that
+   * assignment conversion allows narrowing. We factor out the common logic here.
+   *
+   * @param node a Node producing a value
+   * @param varType the type of a variable
+   * @param contextAllowsNarrowing whether to allow narrowing (for assignment conversion) or not
+   *     (for method invocation conversion)
+   * @return a Node with the value converted to the type of the variable, which may be the input
+   *     node itself
+   */
+  protected Node commonConvert(Node node, TypeMirror varType, boolean contextAllowsNarrowing) {
+    // For assignment conversion, see JLS 5.2
+    // For method invocation conversion, see JLS 5.3
+
+    // Check for identical types or "identity conversion"
+    TypeMirror nodeType = node.getType();
+    boolean isSameType = types.isSameType(nodeType, varType);
+    if (isSameType) {
+      return node;
+    }
+
+    boolean isRightNumeric = TypesUtils.isNumeric(nodeType);
+    boolean isRightPrimitive = TypesUtils.isPrimitive(nodeType);
+    boolean isRightBoxed = TypesUtils.isBoxedPrimitive(nodeType);
+    boolean isRightReference = nodeType instanceof ReferenceType;
+    boolean isLeftNumeric = TypesUtils.isNumeric(varType);
+    boolean isLeftPrimitive = TypesUtils.isPrimitive(varType);
+    // boolean isLeftBoxed = TypesUtils.isBoxedPrimitive(varType);
+    boolean isLeftReference = varType instanceof ReferenceType;
+    boolean isSubtype = types.isSubtype(nodeType, varType);
+
+    if (isRightNumeric && isLeftNumeric && isSubtype) {
+      node = widen(node, varType);
+      nodeType = node.getType();
+    } else if (isRightReference && isLeftReference && isSubtype) {
+      // widening reference conversion is a no-op, but if it
+      // applies, then later conversions do not.
+    } else if (isRightPrimitive && isLeftReference) {
+      if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) {
+        node = narrowAndBox(node, varType);
+        nodeType = node.getType();
+      } else {
+        node = box(node);
+        nodeType = node.getType();
+      }
+    } else if (isRightBoxed && isLeftPrimitive) {
+      node = unbox(node);
+      nodeType = node.getType();
+
+      if (types.isSubtype(nodeType, varType) && !types.isSameType(nodeType, varType)) {
+        node = widen(node, varType);
+        nodeType = node.getType();
+      }
+    } else if (isRightPrimitive && isLeftPrimitive) {
+      if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) {
+        node = narrow(node, varType);
+        nodeType = node.getType();
+      }
+    }
+
+    // TODO: if checkers need to know about null references of
+    // a particular type, add logic for them here.
+
+    return node;
+  }
+
+  /**
+   * Perform assignment conversion so that it can be assigned to a variable of the given type.
+   *
+   * @param node a Node producing a value
+   * @param varType the type of a variable
+   * @return a Node with the value converted to the type of the variable, which may be the input
+   *     node itself
+   */
+  protected Node assignConvert(Node node, TypeMirror varType) {
+    return commonConvert(node, varType, true);
+  }
+
+  /**
+   * Perform method invocation conversion so that the node can be passed as a formal parameter of
+   * the given type.
+   *
+   * @param node a Node producing a value
+   * @param formalType the type of a formal parameter
+   * @return a Node with the value converted to the type of the formal, which may be the input node
+   *     itself
+   */
+  protected Node methodInvocationConvert(Node node, TypeMirror formalType) {
+    return commonConvert(node, formalType, false);
+  }
+
+  /**
+   * Given a method element and as list of argument expressions, return a list of {@link Node}s
+   * representing the arguments converted for a call of the method. This method applies to both
+   * method invocations and constructor calls.
+   *
+   * @param method an ExecutableElement representing a method to be called
+   * @param actualExprs a List of argument expressions to a call
+   * @return a List of {@link Node}s representing arguments after conversions required by a call to
+   *     this method
+   */
+  protected List<Node> convertCallArguments(
+      ExecutableElement method, List<? extends ExpressionTree> actualExprs) {
+    // NOTE: It is important to convert one method argument before generating CFG nodes for the next
+    // argument, since label binding expects nodes to be generated in execution order.  Therefore,
+    // this method first determines which conversions need to be applied and then iterates over the
+    // actual arguments.
+    List<? extends VariableElement> formals = method.getParameters();
+    int numFormals = formals.size();
+
+    ArrayList<Node> convertedNodes = new ArrayList<>(numFormals);
+
+    int numActuals = actualExprs.size();
+    if (method.isVarArgs()) {
+      // Create a new array argument if the actuals outnumber the formals, or if the last actual is
+      // not assignable to the last formal.
+      int lastArgIndex = numFormals - 1;
+      TypeMirror lastParamType = formals.get(lastArgIndex).asType();
+      if (numActuals == numFormals
+          && types.isAssignable(TreeUtils.typeOf(actualExprs.get(numActuals - 1)), lastParamType)) {
+        // Normal call with no array creation, apply method
+        // invocation conversion to all arguments.
+        for (int i = 0; i < numActuals; i++) {
+          Node actualVal = scan(actualExprs.get(i), null);
+          if (actualVal == null) {
+            throw new BugInCF(
+                "CFGBuilder: scan returned null for %s [%s]",
+                actualExprs.get(i), actualExprs.get(i).getClass());
+          }
+          convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i).asType()));
+        }
+      } else {
+        assert lastParamType instanceof ArrayType : "variable argument formal must be an array";
+        // Apply method invocation conversion to lastArgIndex arguments and use the remaining ones
+        // to initialize an array.
+        for (int i = 0; i < lastArgIndex; i++) {
+          Node actualVal = scan(actualExprs.get(i), null);
+          convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i).asType()));
+        }
+
+        TypeMirror elemType = ((ArrayType) lastParamType).getComponentType();
+        List<ExpressionTree> inits = new ArrayList<>(numActuals - lastArgIndex);
+        List<Node> initializers = new ArrayList<>(numActuals - lastArgIndex);
+        for (int i = lastArgIndex; i < numActuals; i++) {
+          inits.add(actualExprs.get(i));
+          Node actualVal = scan(actualExprs.get(i), null);
+          initializers.add(assignConvert(actualVal, elemType));
+        }
+
+        NewArrayTree wrappedVarargs = treeBuilder.buildNewArray(elemType, inits);
+        handleArtificialTree(wrappedVarargs);
+
+        Node lastArgument =
+            new ArrayCreationNode(
+                wrappedVarargs,
+                lastParamType,
+                /*dimensions=*/ Collections.emptyList(),
+                initializers);
+        extendWithNode(lastArgument);
+
+        convertedNodes.add(lastArgument);
+      }
+    } else {
+      for (int i = 0; i < numActuals; i++) {
+        Node actualVal = scan(actualExprs.get(i), null);
+        convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i).asType()));
+      }
+    }
+
+    return convertedNodes;
+  }
+
+  /**
+   * Convert an operand of a conditional expression to the type of the whole expression.
+   *
+   * @param node a node occurring as the second or third operand of a conditional expression
+   * @param destType the type to promote the value to
+   * @return a Node with the value promoted to the destType, which may be the input node
+   */
+  protected Node conditionalExprPromotion(Node node, TypeMirror destType) {
+    // For rules on converting operands of conditional expressions,
+    // JLS 15.25
+    TypeMirror nodeType = node.getType();
+
+    // If the operand is already the same type as the whole
+    // expression, then do nothing.
+    if (types.isSameType(nodeType, destType)) {
+      return node;
+    }
+
+    // If the operand is a primitive and the whole expression is
+    // boxed, then apply boxing.
+    if (TypesUtils.isPrimitive(nodeType) && TypesUtils.isBoxedPrimitive(destType)) {
+      return box(node);
+    }
+
+    // If the operand is byte or Byte and the whole expression is
+    // short, then convert to short.
+    boolean isBoxedPrimitive = TypesUtils.isBoxedPrimitive(nodeType);
+    TypeMirror unboxedNodeType = isBoxedPrimitive ? types.unboxedType(nodeType) : nodeType;
+    TypeMirror unboxedDestType =
+        TypesUtils.isBoxedPrimitive(destType) ? types.unboxedType(destType) : destType;
+    if (TypesUtils.isNumeric(unboxedNodeType) && TypesUtils.isNumeric(unboxedDestType)) {
+      if (unboxedNodeType.getKind() == TypeKind.BYTE && destType.getKind() == TypeKind.SHORT) {
+        if (isBoxedPrimitive) {
+          node = unbox(node);
+        }
+        return widen(node, destType);
+      }
+
+      // If the operand is Byte, Short or Character and the whole expression
+      // is the unboxed version of it, then apply unboxing.
+      TypeKind destKind = destType.getKind();
+      if (destKind == TypeKind.BYTE || destKind == TypeKind.CHAR || destKind == TypeKind.SHORT) {
+        if (isBoxedPrimitive) {
+          return unbox(node);
+        } else if (nodeType.getKind() == TypeKind.INT) {
+          return narrow(node, destType);
+        }
+      }
+
+      return binaryNumericPromotion(node, destType);
+    }
+
+    // For the final case in JLS 15.25, apply boxing but not lub.
+    if (TypesUtils.isPrimitive(nodeType)
+        && (destType.getKind() == TypeKind.DECLARED
+            || destType.getKind() == TypeKind.UNION
+            || destType.getKind() == TypeKind.INTERSECTION)) {
+      return box(node);
+    }
+
+    return node;
+  }
+
+  /**
+   * Returns the label {@link Name} of the leaf in the argument path, or null if the leaf is not a
+   * labeled statement.
+   */
+  protected @Nullable Name getLabel(TreePath path) {
+    if (path.getParentPath() != null) {
+      Tree parent = path.getParentPath().getLeaf();
+      if (parent.getKind() == Tree.Kind.LABELED_STATEMENT) {
+        return ((LabeledStatementTree) parent).getLabel();
+      }
+    }
+    return null;
+  }
+
+  /* --------------------------------------------------------- */
+  /* Visitor Methods */
+  /* --------------------------------------------------------- */
+
+  @Override
+  public Node visitAnnotatedType(AnnotatedTypeTree tree, Void p) {
+    return scan(tree.getUnderlyingType(), p);
+  }
+
+  @Override
+  public Node visitAnnotation(AnnotationTree tree, Void p) {
+    throw new Error("AnnotationTree is unexpected in AST to CFG translation");
+  }
+
+  @Override
+  public MethodInvocationNode visitMethodInvocation(MethodInvocationTree tree, Void p) {
+
+    // see JLS 15.12.4
+
+    // First, compute the receiver, if any (15.12.4.1).
+    // Second, evaluate the actual arguments, left to right and possibly some arguments are stored
+    // into an array for variable arguments calls (15.12.4.2).
+    // Third, test the receiver, if any, for nullness (15.12.4.4).
+    // Fourth, convert the arguments to the type of the formal parameters (15.12.4.5).
+    // Fifth, if the method is synchronized, lock the receiving object or class (15.12.4.5).
+    ExecutableElement method = TreeUtils.elementFromUse(tree);
+    if (method == null) {
+      // The method wasn't found, e.g. because of a compilation error.
+      return null;
+    }
+
+    ExpressionTree methodSelect = tree.getMethodSelect();
+    assert TreeUtils.isMethodAccess(methodSelect)
+        : "Expected a method access, but got: " + methodSelect;
+
+    List<? extends ExpressionTree> actualExprs = tree.getArguments();
+
+    // Look up method to invoke and possibly throw NullPointerException
+    Node receiver = getReceiver(methodSelect);
+
+    MethodAccessNode target = new MethodAccessNode(methodSelect, receiver);
+
+    ExecutableElement element = TreeUtils.elementFromUse(tree);
+    if (ElementUtils.isStatic(element) || receiver instanceof ThisNode) {
+      // No NullPointerException can be thrown, use normal node
+      extendWithNode(target);
+    } else {
+      extendWithNodeWithException(target, nullPointerExceptionType);
+    }
+
+    List<Node> arguments;
+    if (TreeUtils.isEnumSuper(tree)) {
+      // Don't convert arguments for enum super calls.  The AST contains no actual arguments, while
+      // the method element expects two arguments, leading to an exception in convertCallArguments.
+      // Since no actual arguments are present in the AST that is being checked, it shouldn't cause
+      // any harm to omit the conversions.
+      // See also BaseTypeVisitor.visitMethodInvocation and QualifierPolymorphism.annotate.
+      arguments = Collections.emptyList();
+    } else {
+      arguments = convertCallArguments(method, actualExprs);
+    }
+
+    // TODO: lock the receiver for synchronized methods
+
+    MethodInvocationNode node = new MethodInvocationNode(tree, target, arguments, getCurrentPath());
+
+    List<? extends TypeMirror> thrownTypes = element.getThrownTypes();
+    Set<TypeMirror> thrownSet =
+        new LinkedHashSet<>(thrownTypes.size() + uncheckedExceptionTypes.size());
+    // Add exceptions explicitly mentioned in the throws clause.
+    thrownSet.addAll(thrownTypes);
+    // Add types to account for unchecked exceptions
+    thrownSet.addAll(uncheckedExceptionTypes);
+
+    ExtendedNode extendedNode = extendWithNodeWithExceptions(node, thrownSet);
+
+    /* Check for the TerminatesExecution annotation. */
+    Element methodElement = TreeUtils.elementFromTree(tree);
+    boolean terminatesExecution =
+        annotationProvider.getDeclAnnotation(methodElement, TerminatesExecution.class) != null;
+    if (terminatesExecution) {
+      extendedNode.setTerminatesExecution(true);
+    }
+
+    return node;
+  }
+
+  @Override
+  public Node visitAssert(AssertTree tree, Void p) {
+
+    // see JLS 14.10
+
+    // If assertions are enabled, then we can just translate the
+    // assertion.
+    if (assumeAssertionsEnabled || assumeAssertionsEnabledFor(tree)) {
+      translateAssertWithAssertionsEnabled(tree);
+      return null;
+    }
+
+    // If assertions are disabled, then nothing is executed.
+    if (assumeAssertionsDisabled) {
+      return null;
+    }
+
+    // Otherwise, we don't know if assertions are enabled, so we use a
+    // variable "ea" and case-split on it. One branch does execute the
+    // assertion, while the other assumes assertions are disabled.
+    VariableTree ea = getAssertionsEnabledVariable();
+
+    // all necessary labels
+    Label assertionEnabled = new Label();
+    Label assertionDisabled = new Label();
+
+    extendWithNode(new LocalVariableNode(ea));
+    extendWithExtendedNode(new ConditionalJump(assertionEnabled, assertionDisabled));
+
+    // 'then' branch (i.e. check the assertion)
+    addLabelForNextNode(assertionEnabled);
+
+    translateAssertWithAssertionsEnabled(tree);
+
+    // 'else' branch
+    addLabelForNextNode(assertionDisabled);
+
+    return null;
+  }
+
+  /**
+   * Should assertions be assumed to be executed for a given {@link AssertTree}? False by default.
+   */
+  protected boolean assumeAssertionsEnabledFor(AssertTree tree) {
+    return false;
+  }
+
+  /** The {@link VariableTree} that indicates whether assertions are enabled or not. */
+  protected VariableTree ea = null;
+
+  /** Get a synthetic {@link VariableTree} that indicates whether assertions are enabled or not. */
+  protected VariableTree getAssertionsEnabledVariable() {
+    if (ea == null) {
+      String name = uniqueName("assertionsEnabled");
+      Element owner = findOwner();
+      ExpressionTree initializer = null;
+      ea =
+          treeBuilder.buildVariableDecl(
+              types.getPrimitiveType(TypeKind.BOOLEAN), name, owner, initializer);
+    }
+    return ea;
+  }
+
+  /**
+   * Find nearest owner element(Method or Class) which holds current tree.
+   *
+   * @return nearest owner element of current tree
+   */
+  private Element findOwner() {
+    MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath());
+    if (enclosingMethod != null) {
+      return TreeUtils.elementFromDeclaration(enclosingMethod);
+    } else {
+      ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath());
+      return TreeUtils.elementFromDeclaration(enclosingClass);
+    }
+  }
+
+  /**
+   * Translates an assertion statement to the correct CFG nodes. The translation assumes that
+   * assertions are enabled.
+   */
+  protected void translateAssertWithAssertionsEnabled(AssertTree tree) {
+
+    // all necessary labels
+    Label assertEnd = new Label();
+    Label elseEntry = new Label();
+
+    // basic block for the condition
+    Node condition = unbox(scan(tree.getCondition(), null));
+    ConditionalJump cjump = new ConditionalJump(assertEnd, elseEntry);
+    extendWithExtendedNode(cjump);
+
+    // else branch
+    Node detail = null;
+    addLabelForNextNode(elseEntry);
+    if (tree.getDetail() != null) {
+      detail = scan(tree.getDetail(), null);
+    }
+    AssertionErrorNode assertNode =
+        new AssertionErrorNode(tree, condition, detail, assertionErrorType);
+    extendWithNode(assertNode);
+    NodeWithExceptionsHolder exNode =
+        extendWithNodeWithException(
+            new ThrowNode(null, assertNode, env.getTypeUtils()), assertionErrorType);
+    exNode.setTerminatesExecution(true);
+
+    // then branch (nothing happens)
+    addLabelForNextNode(assertEnd);
+  }
+
+  @Override
+  public Node visitAssignment(AssignmentTree tree, Void p) {
+
+    // see JLS 15.26.1
+
+    AssignmentNode assignmentNode;
+    ExpressionTree variable = tree.getVariable();
+    TypeMirror varType = TreeUtils.typeOf(variable);
+
+    // case 1: lhs is field access
+    if (TreeUtils.isFieldAccess(variable)) {
+      // visit receiver
+      Node receiver = getReceiver(variable);
+
+      // visit expression
+      Node expression = scan(tree.getExpression(), p);
+      expression = assignConvert(expression, varType);
+
+      // visit field access (throws null-pointer exception)
+      FieldAccessNode target = new FieldAccessNode(variable, receiver);
+      target.setLValue();
+
+      Element element = TreeUtils.elementFromUse(variable);
+      if (ElementUtils.isStatic(element) || receiver instanceof ThisNode) {
+        // No NullPointerException can be thrown, use normal node
+        extendWithNode(target);
+      } else {
+        extendWithNodeWithException(target, nullPointerExceptionType);
+      }
+
+      // add assignment node
+      assignmentNode = new AssignmentNode(tree, target, expression);
+      extendWithNode(assignmentNode);
+    }
+
+    // case 2: lhs is not a field access
+    else {
+      Node target = scan(variable, p);
+      target.setLValue();
+
+      assignmentNode = translateAssignment(tree, target, tree.getExpression());
+    }
+
+    return assignmentNode;
+  }
+
+  /** Translate an assignment. */
+  protected AssignmentNode translateAssignment(Tree tree, Node target, ExpressionTree rhs) {
+    Node expression = scan(rhs, null);
+    return translateAssignment(tree, target, expression);
+  }
+
+  /** Translate an assignment where the RHS has already been scanned. */
+  protected AssignmentNode translateAssignment(Tree tree, Node target, Node expression) {
+    assert tree instanceof AssignmentTree || tree instanceof VariableTree;
+    target.setLValue();
+    expression = assignConvert(expression, target.getType());
+    AssignmentNode assignmentNode = new AssignmentNode(tree, target, expression);
+    extendWithNode(assignmentNode);
+    return assignmentNode;
+  }
+
+  /**
+   * Note 1: Requires {@code tree} to be a field or method access tree.
+   *
+   * <p>Note 2: Visits the receiver and adds all necessary blocks to the CFG.
+   *
+   * @param tree the field access tree containing the receiver
+   * @return the receiver of the field access
+   */
+  private Node getReceiver(ExpressionTree tree) {
+    assert TreeUtils.isFieldAccess(tree) || TreeUtils.isMethodAccess(tree);
+    if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
+      MemberSelectTree mtree = (MemberSelectTree) tree;
+      return scan(mtree.getExpression(), null);
+    } else {
+      Element ele = TreeUtils.elementFromUse(tree);
+      TypeElement declaringClass = ElementUtils.enclosingTypeElement(ele);
+      TypeMirror type = ElementUtils.getType(declaringClass);
+      if (ElementUtils.isStatic(ele)) {
+        ClassNameNode node = new ClassNameNode(type, declaringClass);
+        extendWithClassNameNode(node);
+        return node;
+      } else {
+        Node node = new ImplicitThisNode(type);
+        extendWithNode(node);
+        return node;
+      }
+    }
+  }
+
+  /**
+   * Map an operation with assignment to the corresponding operation without assignment.
+   *
+   * @param kind a Tree.Kind representing an operation with assignment
+   * @return the Tree.Kind for the same operation without assignment
+   */
+  protected Tree.Kind withoutAssignment(Tree.Kind kind) {
+    switch (kind) {
+      case DIVIDE_ASSIGNMENT:
+        return Tree.Kind.DIVIDE;
+      case MULTIPLY_ASSIGNMENT:
+        return Tree.Kind.MULTIPLY;
+      case REMAINDER_ASSIGNMENT:
+        return Tree.Kind.REMAINDER;
+      case MINUS_ASSIGNMENT:
+        return Tree.Kind.MINUS;
+      case PLUS_ASSIGNMENT:
+        return Tree.Kind.PLUS;
+      case LEFT_SHIFT_ASSIGNMENT:
+        return Tree.Kind.LEFT_SHIFT;
+      case RIGHT_SHIFT_ASSIGNMENT:
+        return Tree.Kind.RIGHT_SHIFT;
+      case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
+        return Tree.Kind.UNSIGNED_RIGHT_SHIFT;
+      case AND_ASSIGNMENT:
+        return Tree.Kind.AND;
+      case OR_ASSIGNMENT:
+        return Tree.Kind.OR;
+      case XOR_ASSIGNMENT:
+        return Tree.Kind.XOR;
+      default:
+        return Tree.Kind.ERRONEOUS;
+    }
+  }
+
+  @Override
+  public Node visitCompoundAssignment(CompoundAssignmentTree tree, Void p) {
+    // According the JLS 15.26.2, E1 op= E2 is equivalent to
+    // E1 = (T) ((E1) op (E2)), where T is the type of E1,
+    // except that E1 is evaluated only once.
+    //
+
+    Tree.Kind kind = tree.getKind();
+    switch (kind) {
+      case DIVIDE_ASSIGNMENT:
+      case MULTIPLY_ASSIGNMENT:
+      case REMAINDER_ASSIGNMENT:
+        {
+          // see JLS 15.17 and 15.26.2
+          Node targetLHS = scan(tree.getVariable(), p);
+          Node value = scan(tree.getExpression(), p);
+
+          TypeMirror exprType = TreeUtils.typeOf(tree);
+          TypeMirror leftType = TreeUtils.typeOf(tree.getVariable());
+          TypeMirror rightType = TreeUtils.typeOf(tree.getExpression());
+          TypeMirror promotedType = binaryPromotedType(leftType, rightType);
+          Node targetRHS = binaryNumericPromotion(targetLHS, promotedType);
+          value = binaryNumericPromotion(value, promotedType);
+
+          BinaryTree operTree =
+              treeBuilder.buildBinary(
+                  promotedType, withoutAssignment(kind), tree.getVariable(), tree.getExpression());
+          handleArtificialTree(operTree);
+          Node operNode;
+          if (kind == Tree.Kind.MULTIPLY_ASSIGNMENT) {
+            operNode = new NumericalMultiplicationNode(operTree, targetRHS, value);
+          } else if (kind == Tree.Kind.DIVIDE_ASSIGNMENT) {
+            if (TypesUtils.isIntegralPrimitive(exprType)) {
+              operNode = new IntegerDivisionNode(operTree, targetRHS, value);
+
+              extendWithNodeWithException(operNode, arithmeticExceptionType);
+            } else {
+              operNode = new FloatingDivisionNode(operTree, targetRHS, value);
+            }
+          } else {
+            assert kind == Kind.REMAINDER_ASSIGNMENT;
+            if (TypesUtils.isIntegralPrimitive(exprType)) {
+              operNode = new IntegerRemainderNode(operTree, targetRHS, value);
+
+              extendWithNodeWithException(operNode, arithmeticExceptionType);
+            } else {
+              operNode = new FloatingRemainderNode(operTree, targetRHS, value);
+            }
+          }
+          extendWithNode(operNode);
+
+          TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree);
+          handleArtificialTree(castTree);
+          TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType, types);
+          castNode.setInSource(false);
+          extendWithNode(castNode);
+
+          AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode);
+          extendWithNode(assignNode);
+          return assignNode;
+        }
+
+      case MINUS_ASSIGNMENT:
+      case PLUS_ASSIGNMENT:
+        {
+          // see JLS 15.18 and 15.26.2
+
+          Node targetLHS = scan(tree.getVariable(), p);
+          Node value = scan(tree.getExpression(), p);
+
+          TypeMirror leftType = TreeUtils.typeOf(tree.getVariable());
+          TypeMirror rightType = TreeUtils.typeOf(tree.getExpression());
+
+          if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) {
+            assert (kind == Tree.Kind.PLUS_ASSIGNMENT);
+            Node targetRHS = stringConversion(targetLHS);
+            value = stringConversion(value);
+            Node r = new StringConcatenateAssignmentNode(tree, targetRHS, value);
+            extendWithNode(r);
+            return r;
+          } else {
+            TypeMirror promotedType = binaryPromotedType(leftType, rightType);
+            Node targetRHS = binaryNumericPromotion(targetLHS, promotedType);
+            value = binaryNumericPromotion(value, promotedType);
+
+            BinaryTree operTree =
+                treeBuilder.buildBinary(
+                    promotedType,
+                    withoutAssignment(kind),
+                    tree.getVariable(),
+                    tree.getExpression());
+            handleArtificialTree(operTree);
+            Node operNode;
+            if (kind == Tree.Kind.PLUS_ASSIGNMENT) {
+              operNode = new NumericalAdditionNode(operTree, targetRHS, value);
+            } else {
+              assert kind == Kind.MINUS_ASSIGNMENT;
+              operNode = new NumericalSubtractionNode(operTree, targetRHS, value);
+            }
+            extendWithNode(operNode);
+
+            TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree);
+            handleArtificialTree(castTree);
+            TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType, types);
+            castNode.setInSource(false);
+            extendWithNode(castNode);
+
+            // Map the compound assignment tree to an assignment node, which
+            // will have the correct type.
+            AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode);
+            extendWithNode(assignNode);
+            return assignNode;
+          }
+        }
+
+      case LEFT_SHIFT_ASSIGNMENT:
+      case RIGHT_SHIFT_ASSIGNMENT:
+      case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
+        {
+          // see JLS 15.19 and 15.26.2
+          Node targetLHS = scan(tree.getVariable(), p);
+          Node value = scan(tree.getExpression(), p);
+
+          TypeMirror leftType = TreeUtils.typeOf(tree.getVariable());
+
+          Node targetRHS = unaryNumericPromotion(targetLHS);
+          value = unaryNumericPromotion(value);
+
+          BinaryTree operTree =
+              treeBuilder.buildBinary(
+                  leftType, withoutAssignment(kind), tree.getVariable(), tree.getExpression());
+          handleArtificialTree(operTree);
+          Node operNode;
+          if (kind == Tree.Kind.LEFT_SHIFT_ASSIGNMENT) {
+            operNode = new LeftShiftNode(operTree, targetRHS, value);
+          } else if (kind == Tree.Kind.RIGHT_SHIFT_ASSIGNMENT) {
+            operNode = new SignedRightShiftNode(operTree, targetRHS, value);
+          } else {
+            assert kind == Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT;
+            operNode = new UnsignedRightShiftNode(operTree, targetRHS, value);
+          }
+          extendWithNode(operNode);
+
+          TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree);
+          handleArtificialTree(castTree);
+          TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType, types);
+          castNode.setInSource(false);
+          extendWithNode(castNode);
+
+          AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode);
+          extendWithNode(assignNode);
+          return assignNode;
+        }
+
+      case AND_ASSIGNMENT:
+      case OR_ASSIGNMENT:
+      case XOR_ASSIGNMENT:
+        // see JLS 15.22
+        Node targetLHS = scan(tree.getVariable(), p);
+        Node value = scan(tree.getExpression(), p);
+
+        TypeMirror leftType = TreeUtils.typeOf(tree.getVariable());
+        TypeMirror rightType = TreeUtils.typeOf(tree.getExpression());
+
+        Node targetRHS = null;
+        if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) {
+          TypeMirror promotedType = binaryPromotedType(leftType, rightType);
+          targetRHS = binaryNumericPromotion(targetLHS, promotedType);
+          value = binaryNumericPromotion(value, promotedType);
+        } else if (TypesUtils.isBooleanType(leftType) && TypesUtils.isBooleanType(rightType)) {
+          targetRHS = unbox(targetLHS);
+          value = unbox(value);
+        } else {
+          throw new Error("Both argument to logical operation must be numeric or boolean");
+        }
+
+        BinaryTree operTree =
+            treeBuilder.buildBinary(
+                leftType, withoutAssignment(kind), tree.getVariable(), tree.getExpression());
+        handleArtificialTree(operTree);
+        Node operNode;
+        if (kind == Tree.Kind.AND_ASSIGNMENT) {
+          operNode = new BitwiseAndNode(operTree, targetRHS, value);
+        } else if (kind == Tree.Kind.OR_ASSIGNMENT) {
+          operNode = new BitwiseOrNode(operTree, targetRHS, value);
+        } else {
+          assert kind == Kind.XOR_ASSIGNMENT;
+          operNode = new BitwiseXorNode(operTree, targetRHS, value);
+        }
+        extendWithNode(operNode);
+
+        TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree);
+        handleArtificialTree(castTree);
+        TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType, types);
+        castNode.setInSource(false);
+        extendWithNode(castNode);
+
+        AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode);
+        extendWithNode(assignNode);
+        return assignNode;
+      default:
+        throw new Error("unexpected compound assignment type");
+    }
+  }
+
+  @Override
+  public Node visitBinary(BinaryTree tree, Void p) {
+    // Note that for binary operations it is important to perform any required promotion on the left
+    // operand before generating any Nodes for the right operand, because labels must be inserted
+    // AFTER ALL preceding Nodes and BEFORE ALL following Nodes.
+    Node r = null;
+    Tree leftTree = tree.getLeftOperand();
+    Tree rightTree = tree.getRightOperand();
+
+    Tree.Kind kind = tree.getKind();
+    switch (kind) {
+      case DIVIDE:
+      case MULTIPLY:
+      case REMAINDER:
+        {
+          // see JLS 15.17
+
+          TypeMirror exprType = TreeUtils.typeOf(tree);
+          TypeMirror leftType = TreeUtils.typeOf(leftTree);
+          TypeMirror rightType = TreeUtils.typeOf(rightTree);
+          TypeMirror promotedType = binaryPromotedType(leftType, rightType);
+
+          Node left = binaryNumericPromotion(scan(leftTree, p), promotedType);
+          Node right = binaryNumericPromotion(scan(rightTree, p), promotedType);
+
+          if (kind == Tree.Kind.MULTIPLY) {
+            r = new NumericalMultiplicationNode(tree, left, right);
+          } else if (kind == Tree.Kind.DIVIDE) {
+            if (TypesUtils.isIntegralPrimitive(exprType)) {
+              r = new IntegerDivisionNode(tree, left, right);
+
+              extendWithNodeWithException(r, arithmeticExceptionType);
+            } else {
+              r = new FloatingDivisionNode(tree, left, right);
+            }
+          } else {
+            assert kind == Kind.REMAINDER;
+            if (TypesUtils.isIntegralPrimitive(exprType)) {
+              r = new IntegerRemainderNode(tree, left, right);
+
+              extendWithNodeWithException(r, arithmeticExceptionType);
+            } else {
+              r = new FloatingRemainderNode(tree, left, right);
+            }
+          }
+          break;
+        }
+
+      case MINUS:
+      case PLUS:
+        {
+          // see JLS 15.18
+
+          // TypeMirror exprType = InternalUtils.typeOf(tree);
+          TypeMirror leftType = TreeUtils.typeOf(leftTree);
+          TypeMirror rightType = TreeUtils.typeOf(rightTree);
+
+          if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) {
+            assert (kind == Tree.Kind.PLUS);
+            Node left = stringConversion(scan(leftTree, p));
+            Node right = stringConversion(scan(rightTree, p));
+            r = new StringConcatenateNode(tree, left, right);
+          } else {
+            TypeMirror promotedType = binaryPromotedType(leftType, rightType);
+            Node left = binaryNumericPromotion(scan(leftTree, p), promotedType);
+            Node right = binaryNumericPromotion(scan(rightTree, p), promotedType);
+
+            // TODO: Decide whether to deal with floating-point value
+            // set conversion.
+            if (kind == Tree.Kind.PLUS) {
+              r = new NumericalAdditionNode(tree, left, right);
+            } else {
+              assert kind == Kind.MINUS;
+              r = new NumericalSubtractionNode(tree, left, right);
+            }
+          }
+          break;
+        }
+
+      case LEFT_SHIFT:
+      case RIGHT_SHIFT:
+      case UNSIGNED_RIGHT_SHIFT:
+        {
+          // see JLS 15.19
+
+          Node left = unaryNumericPromotion(scan(leftTree, p));
+          Node right = unaryNumericPromotion(scan(rightTree, p));
+
+          if (kind == Tree.Kind.LEFT_SHIFT) {
+            r = new LeftShiftNode(tree, left, right);
+          } else if (kind == Tree.Kind.RIGHT_SHIFT) {
+            r = new SignedRightShiftNode(tree, left, right);
+          } else {
+            assert kind == Kind.UNSIGNED_RIGHT_SHIFT;
+            r = new UnsignedRightShiftNode(tree, left, right);
+          }
+          break;
+        }
+
+      case GREATER_THAN:
+      case GREATER_THAN_EQUAL:
+      case LESS_THAN:
+      case LESS_THAN_EQUAL:
+        {
+          // see JLS 15.20.1
+          TypeMirror leftType = TreeUtils.typeOf(leftTree);
+          if (TypesUtils.isBoxedPrimitive(leftType)) {
+            leftType = types.unboxedType(leftType);
+          }
+
+          TypeMirror rightType = TreeUtils.typeOf(rightTree);
+          if (TypesUtils.isBoxedPrimitive(rightType)) {
+            rightType = types.unboxedType(rightType);
+          }
+
+          TypeMirror promotedType = binaryPromotedType(leftType, rightType);
+          Node left = binaryNumericPromotion(scan(leftTree, p), promotedType);
+          Node right = binaryNumericPromotion(scan(rightTree, p), promotedType);
+
+          Node node;
+          if (kind == Tree.Kind.GREATER_THAN) {
+            node = new GreaterThanNode(tree, left, right);
+          } else if (kind == Tree.Kind.GREATER_THAN_EQUAL) {
+            node = new GreaterThanOrEqualNode(tree, left, right);
+          } else if (kind == Tree.Kind.LESS_THAN) {
+            node = new LessThanNode(tree, left, right);
+          } else {
+            assert kind == Tree.Kind.LESS_THAN_EQUAL;
+            node = new LessThanOrEqualNode(tree, left, right);
+          }
+
+          extendWithNode(node);
+
+          return node;
+        }
+
+      case EQUAL_TO:
+      case NOT_EQUAL_TO:
+        {
+          // see JLS 15.21
+          TreeInfo leftInfo = getTreeInfo(leftTree);
+          TreeInfo rightInfo = getTreeInfo(rightTree);
+          Node left = scan(leftTree, p);
+          Node right = scan(rightTree, p);
+
+          if (leftInfo.isNumeric()
+              && rightInfo.isNumeric()
+              && !(leftInfo.isBoxed() && rightInfo.isBoxed())) {
+            // JLS 15.21.1 numerical equality
+            TypeMirror promotedType =
+                binaryPromotedType(leftInfo.unboxedType(), rightInfo.unboxedType());
+            left = binaryNumericPromotion(left, promotedType);
+            right = binaryNumericPromotion(right, promotedType);
+          } else if (leftInfo.isBoolean()
+              && rightInfo.isBoolean()
+              && !(leftInfo.isBoxed() && rightInfo.isBoxed())) {
+            // JSL 15.21.2 boolean equality
+            left = unboxAsNeeded(left, leftInfo.isBoxed());
+            right = unboxAsNeeded(right, rightInfo.isBoxed());
+          }
+
+          Node node;
+          if (kind == Tree.Kind.EQUAL_TO) {
+            node = new EqualToNode(tree, left, right);
+          } else {
+            assert kind == Kind.NOT_EQUAL_TO;
+            node = new NotEqualNode(tree, left, right);
+          }
+          extendWithNode(node);
+
+          return node;
+        }
+
+      case AND:
+      case OR:
+      case XOR:
+        {
+          // see JLS 15.22
+          TypeMirror leftType = TreeUtils.typeOf(leftTree);
+          TypeMirror rightType = TreeUtils.typeOf(rightTree);
+          boolean isBooleanOp =
+              TypesUtils.isBooleanType(leftType) && TypesUtils.isBooleanType(rightType);
+
+          Node left;
+          Node right;
+
+          if (isBooleanOp) {
+            left = unbox(scan(leftTree, p));
+            right = unbox(scan(rightTree, p));
+          } else if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) {
+            TypeMirror promotedType = binaryPromotedType(leftType, rightType);
+            left = binaryNumericPromotion(scan(leftTree, p), promotedType);
+            right = binaryNumericPromotion(scan(rightTree, p), promotedType);
+          } else {
+            left = unbox(scan(leftTree, p));
+            right = unbox(scan(rightTree, p));
+          }
+
+          Node node;
+          if (kind == Tree.Kind.AND) {
+            node = new BitwiseAndNode(tree, left, right);
+          } else if (kind == Tree.Kind.OR) {
+            node = new BitwiseOrNode(tree, left, right);
+          } else {
+            assert kind == Kind.XOR;
+            node = new BitwiseXorNode(tree, left, right);
+          }
+
+          extendWithNode(node);
+
+          return node;
+        }
+
+      case CONDITIONAL_AND:
+      case CONDITIONAL_OR:
+        {
+          // see JLS 15.23 and 15.24
+
+          // all necessary labels
+          Label rightStartL = new Label();
+          Label shortCircuitL = new Label();
+
+          // left-hand side
+          Node left = scan(leftTree, p);
+
+          ConditionalJump cjump;
+          if (kind == Tree.Kind.CONDITIONAL_AND) {
+            cjump = new ConditionalJump(rightStartL, shortCircuitL);
+            cjump.setFalseFlowRule(FlowRule.ELSE_TO_ELSE);
+          } else {
+            cjump = new ConditionalJump(shortCircuitL, rightStartL);
+            cjump.setTrueFlowRule(FlowRule.THEN_TO_THEN);
+          }
+          extendWithExtendedNode(cjump);
+
+          // right-hand side
+          addLabelForNextNode(rightStartL);
+          Node right = scan(rightTree, p);
+
+          // conditional expression itself
+          addLabelForNextNode(shortCircuitL);
+          Node node;
+          if (kind == Tree.Kind.CONDITIONAL_AND) {
+            node = new ConditionalAndNode(tree, left, right);
+          } else {
+            node = new ConditionalOrNode(tree, left, right);
+          }
+          extendWithNode(node);
+          return node;
+        }
+      default:
+        throw new Error("unexpected binary tree: " + kind);
+    }
+    assert r != null : "unexpected binary tree";
+    extendWithNode(r);
+    return r;
+  }
+
+  @Override
+  public Node visitBlock(BlockTree tree, Void p) {
+    for (StatementTree n : tree.getStatements()) {
+      scan(n, null);
+    }
+    return null;
+  }
+
+  @Override
+  public Node visitBreak(BreakTree tree, Void p) {
+    Name label = tree.getLabel();
+    if (label == null) {
+      assert breakTargetL != null : "no target for break statement";
+
+      extendWithExtendedNode(new UnconditionalJump(breakTargetL.accessLabel()));
+    } else {
+      assert breakLabels.containsKey(label);
+
+      extendWithExtendedNode(new UnconditionalJump(breakLabels.get(label)));
+    }
+
+    return null;
+  }
+
+  @Override
+  public Node visitSwitch(SwitchTree tree, Void p) {
+    SwitchBuilder builder = new SwitchBuilder(tree);
+    builder.build();
+    return null;
+  }
+
+  /** Helper class for handling switch statements. */
+  private class SwitchBuilder {
+    /** The switch tree. */
+    private final SwitchTree switchTree;
+    /** The labels for the case bodies. */
+    private final Label[] caseBodyLabels;
+    /** The Node for the switch expression. */
+    private Node switchExpr;
+
+    /**
+     * Construct a SwitchBuilder.
+     *
+     * @param tree switch tree
+     */
+    private SwitchBuilder(SwitchTree tree) {
+      this.switchTree = tree;
+      this.caseBodyLabels = new Label[switchTree.getCases().size() + 1];
+    }
+
+    /** Build up the CFG for the switchTree. */
+    public void build() {
+      TryFinallyScopeCell oldBreakTargetL = breakTargetL;
+      breakTargetL = new TryFinallyScopeCell(new Label());
+      int cases = caseBodyLabels.length - 1;
+      for (int i = 0; i < cases; ++i) {
+        caseBodyLabels[i] = new Label();
+      }
+      caseBodyLabels[cases] = breakTargetL.peekLabel();
+
+      TypeMirror switchExprType = TreeUtils.typeOf(switchTree.getExpression());
+      VariableTree variable =
+          treeBuilder.buildVariableDecl(switchExprType, uniqueName("switch"), findOwner(), null);
+      handleArtificialTree(variable);
+
+      VariableDeclarationNode variableNode = new VariableDeclarationNode(variable);
+      variableNode.setInSource(false);
+      extendWithNode(variableNode);
+
+      IdentifierTree variableUse = treeBuilder.buildVariableUse(variable);
+      handleArtificialTree(variableUse);
+
+      LocalVariableNode variableUseNode = new LocalVariableNode(variableUse);
+      variableUseNode.setInSource(false);
+      extendWithNode(variableUseNode);
+
+      Node switchExprNode = unbox(scan(switchTree.getExpression(), null));
+
+      AssignmentTree assign = treeBuilder.buildAssignment(variableUse, switchTree.getExpression());
+      handleArtificialTree(assign);
+
+      switchExpr = new AssignmentNode(assign, variableUseNode, switchExprNode);
+      switchExpr.setInSource(false);
+      extendWithNode(switchExpr);
+
+      extendWithNode(
+          new MarkerNode(
+              switchTree,
+              "start of switch statement #" + TreeUtils.treeUids.get(switchTree),
+              env.getTypeUtils()));
+
+      Integer defaultIndex = null;
+      for (int i = 0; i < cases; ++i) {
+        CaseTree caseTree = switchTree.getCases().get(i);
+        if (caseTree.getExpression() == null) {
+          defaultIndex = i;
+        } else {
+          buildCase(caseTree, i);
+        }
+      }
+      if (defaultIndex != null) {
+        // The checks of all cases must happen before the default case, therefore we build the
+        // default case last.
+        // Fallthrough is still handled correctly with the caseBodyLabels.
+        buildCase(switchTree.getCases().get(defaultIndex), defaultIndex);
+      }
+
+      addLabelForNextNode(breakTargetL.peekLabel());
+      breakTargetL = oldBreakTargetL;
+
+      extendWithNode(
+          new MarkerNode(
+              switchTree,
+              "end of switch statement #" + TreeUtils.treeUids.get(switchTree),
+              env.getTypeUtils()));
+    }
+
+    private void buildCase(CaseTree tree, int index) {
+      final Label thisBodyL = caseBodyLabels[index];
+      final Label nextBodyL = caseBodyLabels[index + 1];
+      final Label nextCaseL = new Label();
+
+      ExpressionTree exprTree = tree.getExpression();
+      if (exprTree != null) {
+        // non-default cases
+        Node expr = scan(exprTree, null);
+        CaseNode test = new CaseNode(tree, switchExpr, expr, env.getTypeUtils());
+        extendWithNode(test);
+        extendWithExtendedNode(new ConditionalJump(thisBodyL, nextCaseL));
+      }
+      addLabelForNextNode(thisBodyL);
+      for (StatementTree stmt : tree.getStatements()) {
+        scan(stmt, null);
+      }
+      extendWithExtendedNode(new UnconditionalJump(nextBodyL));
+      addLabelForNextNode(nextCaseL);
+    }
+  }
+
+  @Override
+  public Node visitCase(CaseTree tree, Void p) {
+    throw new AssertionError("case visitor is implemented in SwitchBuilder");
+  }
+
+  @Override
+  public Node visitCatch(CatchTree tree, Void p) {
+    scan(tree.getParameter(), p);
+    scan(tree.getBlock(), p);
+    return null;
+  }
+
+  @Override
+  public Node visitClass(ClassTree tree, Void p) {
+    declaredClasses.add(tree);
+    Node classbody = new ClassDeclarationNode(tree);
+    extendWithNode(classbody);
+    return classbody;
+  }
+
+  @Override
+  public Node visitConditionalExpression(ConditionalExpressionTree tree, Void p) {
+    // see JLS 15.25
+    TypeMirror exprType = TreeUtils.typeOf(tree);
+
+    Label trueStart = new Label();
+    Label falseStart = new Label();
+    Label merge = new Label();
+
+    Node condition = unbox(scan(tree.getCondition(), p));
+    ConditionalJump cjump = new ConditionalJump(trueStart, falseStart);
+    extendWithExtendedNode(cjump);
+
+    addLabelForNextNode(trueStart);
+    Node trueExpr = scan(tree.getTrueExpression(), p);
+    trueExpr = conditionalExprPromotion(trueExpr, exprType);
+    extendWithExtendedNode(new UnconditionalJump(merge, FlowRule.BOTH_TO_THEN));
+
+    addLabelForNextNode(falseStart);
+    Node falseExpr = scan(tree.getFalseExpression(), p);
+    falseExpr = conditionalExprPromotion(falseExpr, exprType);
+    extendWithExtendedNode(new UnconditionalJump(merge, FlowRule.BOTH_TO_ELSE));
+
+    addLabelForNextNode(merge);
+    Node node = new TernaryExpressionNode(tree, condition, trueExpr, falseExpr);
+    extendWithNode(node);
+
+    return node;
+  }
+
+  @Override
+  public Node visitContinue(ContinueTree tree, Void p) {
+    Name label = tree.getLabel();
+    if (label == null) {
+      assert continueTargetL != null : "no target for continue statement";
+
+      extendWithExtendedNode(new UnconditionalJump(continueTargetL.accessLabel()));
+    } else {
+      assert continueLabels.containsKey(label);
+
+      extendWithExtendedNode(new UnconditionalJump(continueLabels.get(label)));
+    }
+
+    return null;
+  }
+
+  @Override
+  public Node visitDoWhileLoop(DoWhileLoopTree tree, Void p) {
+    Name parentLabel = getLabel(getCurrentPath());
+
+    Label loopEntry = new Label();
+    Label loopExit = new Label();
+
+    // If the loop is a labeled statement, then its continue target is identical for continues with
+    // no label and continues with the loop's label.
+    Label conditionStart;
+    if (parentLabel != null) {
+      conditionStart = continueLabels.get(parentLabel);
+    } else {
+      conditionStart = new Label();
+    }
+
+    TryFinallyScopeCell oldBreakTargetL = breakTargetL;
+    breakTargetL = new TryFinallyScopeCell(loopExit);
+
+    TryFinallyScopeCell oldContinueTargetL = continueTargetL;
+    continueTargetL = new TryFinallyScopeCell(conditionStart);
+
+    // Loop body
+    addLabelForNextNode(loopEntry);
+    assert tree.getStatement() != null;
+    scan(tree.getStatement(), p);
+
+    // Condition
+    addLabelForNextNode(conditionStart);
+    assert tree.getCondition() != null;
+    unbox(scan(tree.getCondition(), p));
+    ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit);
+    extendWithExtendedNode(cjump);
+
+    // Loop exit
+    addLabelForNextNode(loopExit);
+
+    breakTargetL = oldBreakTargetL;
+    continueTargetL = oldContinueTargetL;
+
+    return null;
+  }
+
+  @Override
+  public Node visitErroneous(ErroneousTree tree, Void p) {
+    throw new Error("ErroneousTree is unexpected in AST to CFG translation");
+  }
+
+  @Override
+  public Node visitExpressionStatement(ExpressionStatementTree tree, Void p) {
+    return scan(tree.getExpression(), p);
+  }
+
+  @Override
+  public Node visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) {
+    // see JLS 14.14.2
+    Name parentLabel = getLabel(getCurrentPath());
+
+    Label conditionStart = new Label();
+    Label loopEntry = new Label();
+    Label loopExit = new Label();
+
+    // If the loop is a labeled statement, then its continue target is identical for continues with
+    // no label and continues with the loop's label.
+    Label updateStart;
+    if (parentLabel != null) {
+      updateStart = continueLabels.get(parentLabel);
+    } else {
+      updateStart = new Label();
+    }
+
+    TryFinallyScopeCell oldBreakTargetL = breakTargetL;
+    breakTargetL = new TryFinallyScopeCell(loopExit);
+
+    TryFinallyScopeCell oldContinueTargetL = continueTargetL;
+    continueTargetL = new TryFinallyScopeCell(updateStart);
+
+    // Distinguish loops over Iterables from loops over arrays.
+
+    VariableTree variable = tree.getVariable();
+    VariableElement variableElement = TreeUtils.elementFromDeclaration(variable);
+    ExpressionTree expression = tree.getExpression();
+    StatementTree statement = tree.getStatement();
+
+    TypeMirror exprType = TreeUtils.typeOf(expression);
+
+    if (types.isSubtype(exprType, iterableType)) {
+      // Take the upper bound of a type variable or wildcard
+      exprType = TypesUtils.upperBound(exprType);
+
+      assert (exprType instanceof DeclaredType) : "an Iterable must be a DeclaredType";
+      DeclaredType declaredExprType = (DeclaredType) exprType;
+      declaredExprType.getTypeArguments();
+
+      MemberSelectTree iteratorSelect = treeBuilder.buildIteratorMethodAccess(expression);
+      handleArtificialTree(iteratorSelect);
+
+      MethodInvocationTree iteratorCall = treeBuilder.buildMethodInvocation(iteratorSelect);
+      handleArtificialTree(iteratorCall);
+
+      VariableTree iteratorVariable =
+          createEnhancedForLoopIteratorVariable(iteratorCall, variableElement);
+      handleArtificialTree(iteratorVariable);
+
+      VariableDeclarationNode iteratorVariableDecl = new VariableDeclarationNode(iteratorVariable);
+      iteratorVariableDecl.setInSource(false);
+
+      extendWithNode(iteratorVariableDecl);
+
+      Node expressionNode = scan(expression, p);
+
+      MethodAccessNode iteratorAccessNode = new MethodAccessNode(iteratorSelect, expressionNode);
+      iteratorAccessNode.setInSource(false);
+      extendWithNode(iteratorAccessNode);
+      MethodInvocationNode iteratorCallNode =
+          new MethodInvocationNode(
+              iteratorCall, iteratorAccessNode, Collections.emptyList(), getCurrentPath());
+      iteratorCallNode.setInSource(false);
+      extendWithNode(iteratorCallNode);
+
+      translateAssignment(
+          iteratorVariable, new LocalVariableNode(iteratorVariable), iteratorCallNode);
+
+      // Test the loop ending condition
+      addLabelForNextNode(conditionStart);
+      IdentifierTree iteratorUse1 = treeBuilder.buildVariableUse(iteratorVariable);
+      handleArtificialTree(iteratorUse1);
+
+      LocalVariableNode iteratorReceiverNode = new LocalVariableNode(iteratorUse1);
+      iteratorReceiverNode.setInSource(false);
+      extendWithNode(iteratorReceiverNode);
+
+      MemberSelectTree hasNextSelect = treeBuilder.buildHasNextMethodAccess(iteratorUse1);
+      handleArtificialTree(hasNextSelect);
+
+      MethodAccessNode hasNextAccessNode =
+          new MethodAccessNode(hasNextSelect, iteratorReceiverNode);
+      hasNextAccessNode.setInSource(false);
+      extendWithNode(hasNextAccessNode);
+
+      MethodInvocationTree hasNextCall = treeBuilder.buildMethodInvocation(hasNextSelect);
+      handleArtificialTree(hasNextCall);
+
+      MethodInvocationNode hasNextCallNode =
+          new MethodInvocationNode(
+              hasNextCall, hasNextAccessNode, Collections.emptyList(), getCurrentPath());
+      hasNextCallNode.setInSource(false);
+      extendWithNode(hasNextCallNode);
+      extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit));
+
+      // Loop body, starting with declaration of the loop iteration variable
+      addLabelForNextNode(loopEntry);
+      extendWithNode(new VariableDeclarationNode(variable));
+
+      IdentifierTree iteratorUse2 = treeBuilder.buildVariableUse(iteratorVariable);
+      handleArtificialTree(iteratorUse2);
+
+      LocalVariableNode iteratorReceiverNode2 = new LocalVariableNode(iteratorUse2);
+      iteratorReceiverNode2.setInSource(false);
+      extendWithNode(iteratorReceiverNode2);
+
+      MemberSelectTree nextSelect = treeBuilder.buildNextMethodAccess(iteratorUse2);
+      handleArtificialTree(nextSelect);
+
+      MethodAccessNode nextAccessNode = new MethodAccessNode(nextSelect, iteratorReceiverNode2);
+      nextAccessNode.setInSource(false);
+      extendWithNode(nextAccessNode);
+
+      MethodInvocationTree nextCall = treeBuilder.buildMethodInvocation(nextSelect);
+      handleArtificialTree(nextCall);
+
+      MethodInvocationNode nextCallNode =
+          new MethodInvocationNode(
+              nextCall, nextAccessNode, Collections.emptyList(), getCurrentPath());
+      // If the type of iteratorVariable is a capture, its type tree may be missing annotations, so
+      // save the expression in the node so that the full type can be found later.
+      nextCallNode.setIterableExpression(expression);
+      nextCallNode.setInSource(false);
+      extendWithNode(nextCallNode);
+
+      AssignmentNode assignNode =
+          translateAssignment(variable, new LocalVariableNode(variable), nextCall);
+      // translateAssignment() scans variable and creates new nodes, so set the expression
+      // there, too.
+      ((MethodInvocationNode) assignNode.getExpression()).setIterableExpression(expression);
+
+      assert statement != null;
+      scan(statement, p);
+
+      // Loop back edge
+      addLabelForNextNode(updateStart);
+      extendWithExtendedNode(new UnconditionalJump(conditionStart));
+
+    } else {
+      // TODO: Shift any labels after the initialization of the
+      // temporary array variable.
+
+      VariableTree arrayVariable = createEnhancedForLoopArrayVariable(expression, variableElement);
+      handleArtificialTree(arrayVariable);
+
+      VariableDeclarationNode arrayVariableNode = new VariableDeclarationNode(arrayVariable);
+      arrayVariableNode.setInSource(false);
+      extendWithNode(arrayVariableNode);
+      Node expressionNode = scan(expression, p);
+
+      translateAssignment(arrayVariable, new LocalVariableNode(arrayVariable), expressionNode);
+
+      // Declare and initialize the loop index variable
+      TypeMirror intType = types.getPrimitiveType(TypeKind.INT);
+
+      LiteralTree zero = treeBuilder.buildLiteral(Integer.valueOf(0));
+      handleArtificialTree(zero);
+
+      VariableTree indexVariable =
+          treeBuilder.buildVariableDecl(
+              intType, uniqueName("index"), variableElement.getEnclosingElement(), zero);
+      handleArtificialTree(indexVariable);
+      VariableDeclarationNode indexVariableNode = new VariableDeclarationNode(indexVariable);
+      indexVariableNode.setInSource(false);
+      extendWithNode(indexVariableNode);
+      IntegerLiteralNode zeroNode = new IntegerLiteralNode(zero);
+      extendWithNode(zeroNode);
+
+      translateAssignment(indexVariable, new LocalVariableNode(indexVariable), zeroNode);
+
+      // Compare index to array length
+      addLabelForNextNode(conditionStart);
+      IdentifierTree indexUse1 = treeBuilder.buildVariableUse(indexVariable);
+      handleArtificialTree(indexUse1);
+      LocalVariableNode indexNode1 = new LocalVariableNode(indexUse1);
+      indexNode1.setInSource(false);
+      extendWithNode(indexNode1);
+
+      IdentifierTree arrayUse1 = treeBuilder.buildVariableUse(arrayVariable);
+      handleArtificialTree(arrayUse1);
+      LocalVariableNode arrayNode1 = new LocalVariableNode(arrayUse1);
+      extendWithNode(arrayNode1);
+
+      MemberSelectTree lengthSelect = treeBuilder.buildArrayLengthAccess(arrayUse1);
+      handleArtificialTree(lengthSelect);
+      FieldAccessNode lengthAccessNode = new FieldAccessNode(lengthSelect, arrayNode1);
+      lengthAccessNode.setInSource(false);
+      extendWithNode(lengthAccessNode);
+
+      BinaryTree lessThan = treeBuilder.buildLessThan(indexUse1, lengthSelect);
+      handleArtificialTree(lessThan);
+
+      LessThanNode lessThanNode = new LessThanNode(lessThan, indexNode1, lengthAccessNode);
+      lessThanNode.setInSource(false);
+      extendWithNode(lessThanNode);
+      extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit));
+
+      // Loop body, starting with declaration of the loop iteration variable
+      addLabelForNextNode(loopEntry);
+      extendWithNode(new VariableDeclarationNode(variable));
+
+      IdentifierTree arrayUse2 = treeBuilder.buildVariableUse(arrayVariable);
+      handleArtificialTree(arrayUse2);
+      LocalVariableNode arrayNode2 = new LocalVariableNode(arrayUse2);
+      arrayNode2.setInSource(false);
+      extendWithNode(arrayNode2);
+
+      IdentifierTree indexUse2 = treeBuilder.buildVariableUse(indexVariable);
+      handleArtificialTree(indexUse2);
+      LocalVariableNode indexNode2 = new LocalVariableNode(indexUse2);
+      indexNode2.setInSource(false);
+      extendWithNode(indexNode2);
+
+      ArrayAccessTree arrayAccess = treeBuilder.buildArrayAccess(arrayUse2, indexUse2);
+      handleArtificialTree(arrayAccess);
+      ArrayAccessNode arrayAccessNode = new ArrayAccessNode(arrayAccess, arrayNode2, indexNode2);
+      arrayAccessNode.setArrayExpression(expression);
+      arrayAccessNode.setInSource(false);
+      extendWithNode(arrayAccessNode);
+      AssignmentNode arrayAccessAssignNode =
+          translateAssignment(variable, new LocalVariableNode(variable), arrayAccessNode);
+      extendWithNodeWithException(arrayAccessNode, nullPointerExceptionType);
+      // translateAssignment() scans variable and creates new nodes, so set the expression
+      // there, too.
+      Node arrayAccessAssignNodeExpr = arrayAccessAssignNode.getExpression();
+      if (arrayAccessAssignNodeExpr instanceof ArrayAccessNode) {
+        ((ArrayAccessNode) arrayAccessAssignNodeExpr).setArrayExpression(expression);
+      } else if (arrayAccessAssignNodeExpr instanceof MethodInvocationNode) {
+        // If the array component type is a primitive, there may be a boxing or unboxing
+        // conversion. Treat that as an iterator.
+        MethodInvocationNode boxingNode = (MethodInvocationNode) arrayAccessAssignNodeExpr;
+        boxingNode.setIterableExpression(expression);
+      }
+
+      assert statement != null;
+      scan(statement, p);
+
+      // Loop back edge
+      addLabelForNextNode(updateStart);
+
+      IdentifierTree indexUse3 = treeBuilder.buildVariableUse(indexVariable);
+      handleArtificialTree(indexUse3);
+      LocalVariableNode indexNode3 = new LocalVariableNode(indexUse3);
+      indexNode3.setInSource(false);
+      extendWithNode(indexNode3);
+
+      LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1));
+      handleArtificialTree(oneTree);
+      Node one = new IntegerLiteralNode(oneTree);
+      one.setInSource(false);
+      extendWithNode(one);
+
+      BinaryTree addOneTree = treeBuilder.buildBinary(intType, Tree.Kind.PLUS, indexUse3, oneTree);
+      handleArtificialTree(addOneTree);
+      Node addOneNode = new NumericalAdditionNode(addOneTree, indexNode3, one);
+      addOneNode.setInSource(false);
+      extendWithNode(addOneNode);
+
+      AssignmentTree assignTree = treeBuilder.buildAssignment(indexUse3, addOneTree);
+      handleArtificialTree(assignTree);
+      Node assignNode = new AssignmentNode(assignTree, indexNode3, addOneNode);
+      assignNode.setInSource(false);
+      extendWithNode(assignNode);
+
+      extendWithExtendedNode(new UnconditionalJump(conditionStart));
+    }
+
+    // Loop exit
+    addLabelForNextNode(loopExit);
+
+    breakTargetL = oldBreakTargetL;
+    continueTargetL = oldContinueTargetL;
+
+    return null;
+  }
+
+  protected VariableTree createEnhancedForLoopIteratorVariable(
+      MethodInvocationTree iteratorCall, VariableElement variableElement) {
+    TypeMirror iteratorType = TreeUtils.typeOf(iteratorCall);
+
+    // Declare and initialize a new, unique iterator variable
+    VariableTree iteratorVariable =
+        treeBuilder.buildVariableDecl(
+            iteratorType, // annotatedIteratorTypeTree,
+            uniqueName("iter"),
+            variableElement.getEnclosingElement(),
+            iteratorCall);
+    return iteratorVariable;
+  }
+
+  protected VariableTree createEnhancedForLoopArrayVariable(
+      ExpressionTree expression, VariableElement variableElement) {
+    TypeMirror arrayType = TreeUtils.typeOf(expression);
+
+    // Declare and initialize a temporary array variable
+    VariableTree arrayVariable =
+        treeBuilder.buildVariableDecl(
+            arrayType, uniqueName("array"), variableElement.getEnclosingElement(), expression);
+    return arrayVariable;
+  }
+
+  @Override
+  public Node visitForLoop(ForLoopTree tree, Void p) {
+    Name parentLabel = getLabel(getCurrentPath());
+
+    Label conditionStart = new Label();
+    Label loopEntry = new Label();
+    Label loopExit = new Label();
+
+    // If the loop is a labeled statement, then its continue target is identical for continues with
+    // no label and continues with the loop's label.
+    Label updateStart;
+    if (parentLabel != null) {
+      updateStart = continueLabels.get(parentLabel);
+    } else {
+      updateStart = new Label();
+    }
+
+    TryFinallyScopeCell oldBreakTargetL = breakTargetL;
+    breakTargetL = new TryFinallyScopeCell(loopExit);
+
+    TryFinallyScopeCell oldContinueTargetL = continueTargetL;
+    continueTargetL = new TryFinallyScopeCell(updateStart);
+
+    // Initializer
+    for (StatementTree init : tree.getInitializer()) {
+      scan(init, p);
+    }
+
+    // Condition
+    addLabelForNextNode(conditionStart);
+    if (tree.getCondition() != null) {
+      unbox(scan(tree.getCondition(), p));
+      ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit);
+      extendWithExtendedNode(cjump);
+    }
+
+    // Loop body
+    addLabelForNextNode(loopEntry);
+    assert tree.getStatement() != null;
+    scan(tree.getStatement(), p);
+
+    // Update
+    addLabelForNextNode(updateStart);
+    for (ExpressionStatementTree update : tree.getUpdate()) {
+      scan(update, p);
+    }
+
+    extendWithExtendedNode(new UnconditionalJump(conditionStart));
+
+    // Loop exit
+    addLabelForNextNode(loopExit);
+
+    breakTargetL = oldBreakTargetL;
+    continueTargetL = oldContinueTargetL;
+
+    return null;
+  }
+
+  @Override
+  public Node visitIdentifier(IdentifierTree tree, Void p) {
+    Node node;
+    if (TreeUtils.isFieldAccess(tree)) {
+      Node receiver = getReceiver(tree);
+      node = new FieldAccessNode(tree, receiver);
+    } else {
+      Element element = TreeUtils.elementFromUse(tree);
+      switch (element.getKind()) {
+        case FIELD:
+          // Note that "this"/"super" is a field, but not a field access.
+          if (element.getSimpleName().contentEquals("this")) {
+            node = new ExplicitThisNode(tree);
+          } else {
+            node = new SuperNode(tree);
+          }
+          break;
+        case EXCEPTION_PARAMETER:
+        case LOCAL_VARIABLE:
+        case RESOURCE_VARIABLE:
+        case PARAMETER:
+          node = new LocalVariableNode(tree);
+          break;
+        case PACKAGE:
+          node = new PackageNameNode(tree);
+          break;
+        default:
+          if (ElementUtils.isTypeDeclaration(element)) {
+            node = new ClassNameNode(tree);
+            break;
+          } else if (ElementUtils.isBindingVariable(element)) {
+            // Note: BINDING_VARIABLE should be added as a direct case above when instanceof pattern
+            // matching and Java15 are supported.
+            node = new LocalVariableNode(tree);
+            break;
+          }
+          throw new BugInCF("bad element kind " + element.getKind());
+      }
+    }
+    if (node instanceof ClassNameNode) {
+      extendWithClassNameNode((ClassNameNode) node);
+    } else {
+      extendWithNode(node);
+    }
+    return node;
+  }
+
+  @Override
+  public Node visitIf(IfTree tree, Void p) {
+    // all necessary labels
+    Label thenEntry = new Label();
+    Label elseEntry = new Label();
+    Label endIf = new Label();
+
+    // basic block for the condition
+    unbox(scan(tree.getCondition(), p));
+
+    ConditionalJump cjump = new ConditionalJump(thenEntry, elseEntry);
+    extendWithExtendedNode(cjump);
+
+    // then branch
+    addLabelForNextNode(thenEntry);
+    StatementTree thenStatement = tree.getThenStatement();
+    scan(thenStatement, p);
+    extendWithExtendedNode(new UnconditionalJump(endIf));
+
+    // else branch
+    addLabelForNextNode(elseEntry);
+    StatementTree elseStatement = tree.getElseStatement();
+    if (elseStatement != null) {
+      scan(elseStatement, p);
+    }
+
+    // label the end of the if statement
+    addLabelForNextNode(endIf);
+
+    return null;
+  }
+
+  @Override
+  public Node visitImport(ImportTree tree, Void p) {
+    throw new Error("ImportTree is unexpected in AST to CFG translation");
+  }
+
+  @Override
+  public Node visitArrayAccess(ArrayAccessTree tree, Void p) {
+    Node array = scan(tree.getExpression(), p);
+    Node index = unaryNumericPromotion(scan(tree.getIndex(), p));
+    Node arrayAccess = new ArrayAccessNode(tree, array, index);
+    extendWithNode(arrayAccess);
+    extendWithNodeWithException(arrayAccess, arrayIndexOutOfBoundsExceptionType);
+    extendWithNodeWithException(arrayAccess, nullPointerExceptionType);
+    return arrayAccess;
+  }
+
+  @Override
+  public Node visitLabeledStatement(LabeledStatementTree tree, Void p) {
+    // This method can set the break target after generating all Nodes in the contained statement,
+    // but it can't set the continue target, which may be in the middle of a sequence of
+    // nodes. Labeled loops must look up and use the continue Labels.
+    Name labelName = tree.getLabel();
+
+    Label breakL = new Label(labelName + "_break");
+    Label continueL = new Label(labelName + "_continue");
+
+    breakLabels.put(labelName, breakL);
+    continueLabels.put(labelName, continueL);
+
+    scan(tree.getStatement(), p);
+
+    addLabelForNextNode(breakL);
+
+    breakLabels.remove(labelName);
+    continueLabels.remove(labelName);
+
+    return null;
+  }
+
+  @Override
+  public Node visitLiteral(LiteralTree tree, Void p) {
+    Node r = null;
+    switch (tree.getKind()) {
+      case BOOLEAN_LITERAL:
+        r = new BooleanLiteralNode(tree);
+        break;
+      case CHAR_LITERAL:
+        r = new CharacterLiteralNode(tree);
+        break;
+      case DOUBLE_LITERAL:
+        r = new DoubleLiteralNode(tree);
+        break;
+      case FLOAT_LITERAL:
+        r = new FloatLiteralNode(tree);
+        break;
+      case INT_LITERAL:
+        r = new IntegerLiteralNode(tree);
+        break;
+      case LONG_LITERAL:
+        r = new LongLiteralNode(tree);
+        break;
+      case NULL_LITERAL:
+        r = new NullLiteralNode(tree);
+        break;
+      case STRING_LITERAL:
+        r = new StringLiteralNode(tree);
+        break;
+      default:
+        throw new Error("unexpected literal tree");
+    }
+    assert r != null : "unexpected literal tree";
+    extendWithNode(r);
+    return r;
+  }
+
+  @Override
+  public Node visitMethod(MethodTree tree, Void p) {
+    throw new Error("MethodTree is unexpected in AST to CFG translation");
+  }
+
+  @Override
+  public Node visitModifiers(ModifiersTree tree, Void p) {
+    throw new Error("ModifiersTree is unexpected in AST to CFG translation");
+  }
+
+  @Override
+  public Node visitNewArray(NewArrayTree tree, Void p) {
+    // see JLS 15.10
+
+    ArrayType type = (ArrayType) TreeUtils.typeOf(tree);
+    TypeMirror elemType = type.getComponentType();
+
+    List<? extends ExpressionTree> dimensions = tree.getDimensions();
+    List<? extends ExpressionTree> initializers = tree.getInitializers();
+    assert dimensions != null;
+
+    List<Node> dimensionNodes =
+        CollectionsPlume.mapList(dim -> unaryNumericPromotion(scan(dim, p)), dimensions);
+
+    List<Node> initializerNodes;
+    if (initializers == null) {
+      initializerNodes = Collections.emptyList();
+    } else {
+      initializerNodes =
+          CollectionsPlume.mapList(init -> assignConvert(scan(init, p), elemType), initializers);
+    }
+
+    Node node = new ArrayCreationNode(tree, type, dimensionNodes, initializerNodes);
+
+    extendWithNodeWithExceptions(node, newArrayExceptionTypes);
+    return node;
+  }
+
+  @Override
+  public Node visitNewClass(NewClassTree tree, Void p) {
+    // see JLS 15.9
+
+    Tree enclosingExpr = tree.getEnclosingExpression();
+    if (enclosingExpr != null) {
+      scan(enclosingExpr, p);
+    }
+
+    // Convert constructor arguments
+    ExecutableElement constructor = TreeUtils.elementFromUse(tree);
+
+    List<? extends ExpressionTree> actualExprs = tree.getArguments();
+
+    List<Node> arguments = convertCallArguments(constructor, actualExprs);
+
+    // TODO: for anonymous classes, don't use the identifier alone.
+    // See Issue 890.
+    Node constructorNode = scan(tree.getIdentifier(), p);
+
+    // Handle anonymous classes in visitClass.
+    // Note that getClassBody() and therefore classbody can be null.
+    ClassDeclarationNode classbody = (ClassDeclarationNode) scan(tree.getClassBody(), p);
+
+    Node node = new ObjectCreationNode(tree, constructorNode, arguments, classbody);
+
+    List<? extends TypeMirror> thrownTypes = constructor.getThrownTypes();
+    Set<TypeMirror> thrownSet =
+        new LinkedHashSet<>(thrownTypes.size() + uncheckedExceptionTypes.size());
+    // Add exceptions explicitly mentioned in the throws clause.
+    thrownSet.addAll(thrownTypes);
+    // Add types to account for unchecked exceptions
+    thrownSet.addAll(uncheckedExceptionTypes);
+
+    extendWithNodeWithExceptions(node, thrownSet);
+
+    return node;
+  }
+
+  /**
+   * Maps a {@code Tree} to its directly enclosing {@code ParenthesizedTree} if one exists.
+   *
+   * <p>This map is used by {@link CFGTranslationPhaseOne#addToLookupMap(Node)} to associate a
+   * {@code ParenthesizedTree} with the dataflow {@code Node} that was used during inference. This
+   * map is necessary because dataflow does not create a {@code Node} for a {@code
+   * ParenthesizedTree}.
+   */
+  private final Map<Tree, ParenthesizedTree> parenMapping = new HashMap<>();
+
+  @Override
+  public Node visitParenthesized(ParenthesizedTree tree, Void p) {
+    parenMapping.put(tree.getExpression(), tree);
+    return scan(tree.getExpression(), p);
+  }
+
+  @Override
+  public Node visitReturn(ReturnTree tree, Void p) {
+    ExpressionTree ret = tree.getExpression();
+    // TODO: also have a return-node if nothing is returned
+    ReturnNode result = null;
+    if (ret != null) {
+      Node node = scan(ret, p);
+      Tree enclosing =
+          TreePathUtil.enclosingOfKind(
+              getCurrentPath(), new HashSet<>(Arrays.asList(Kind.METHOD, Kind.LAMBDA_EXPRESSION)));
+      if (enclosing.getKind() == Kind.LAMBDA_EXPRESSION) {
+        LambdaExpressionTree lambdaTree = (LambdaExpressionTree) enclosing;
+        TreePath lambdaTreePath =
+            TreePath.getPath(getCurrentPath().getCompilationUnit(), lambdaTree);
+        Context ctx = ((JavacProcessingEnvironment) env).getContext();
+        Element overriddenElement =
+            com.sun.tools.javac.code.Types.instance(ctx)
+                .findDescriptorSymbol(((Type) trees.getTypeMirror(lambdaTreePath)).tsym);
+
+        result =
+            new ReturnNode(
+                tree, node, env.getTypeUtils(), lambdaTree, (MethodSymbol) overriddenElement);
+      } else {
+        result = new ReturnNode(tree, node, env.getTypeUtils(), (MethodTree) enclosing);
+      }
+      returnNodes.add(result);
+      extendWithNode(result);
+    }
+
+    extendWithExtendedNode(new UnconditionalJump(this.returnTargetL.accessLabel()));
+
+    return result;
+  }
+
+  @Override
+  public Node visitMemberSelect(MemberSelectTree tree, Void p) {
+    Node expr = scan(tree.getExpression(), p);
+    if (!TreeUtils.isFieldAccess(tree)) {
+      // Could be a selector of a class or package
+      Element element = TreeUtils.elementFromUse(tree);
+      if (ElementUtils.isTypeElement(element)) {
+        ClassNameNode result = new ClassNameNode(tree, expr);
+        extendWithClassNameNode(result);
+        return result;
+      } else if (element.getKind() == ElementKind.PACKAGE) {
+        Node result = new PackageNameNode(tree, (PackageNameNode) expr);
+        extendWithNode(result);
+        return result;
+      } else {
+        throw new Error("Unexpected element kind: " + element.getKind());
+      }
+    }
+
+    Node node = new FieldAccessNode(tree, expr);
+
+    Element element = TreeUtils.elementFromUse(tree);
+    if (ElementUtils.isStatic(element)
+        || expr instanceof ImplicitThisNode
+        || expr instanceof ExplicitThisNode) {
+      // No NullPointerException can be thrown, use normal node
+      extendWithNode(node);
+    } else {
+      extendWithNodeWithException(node, nullPointerExceptionType);
+    }
+
+    return node;
+  }
+
+  @Override
+  public Node visitEmptyStatement(EmptyStatementTree tree, Void p) {
+    return null;
+  }
+
+  @Override
+  public Node visitSynchronized(SynchronizedTree tree, Void p) {
+    // see JLS 14.19
+
+    Node synchronizedExpr = scan(tree.getExpression(), p);
+    SynchronizedNode synchronizedStartNode =
+        new SynchronizedNode(tree, synchronizedExpr, true, env.getTypeUtils());
+    extendWithNode(synchronizedStartNode);
+    scan(tree.getBlock(), p);
+    SynchronizedNode synchronizedEndNode =
+        new SynchronizedNode(tree, synchronizedExpr, false, env.getTypeUtils());
+    extendWithNode(synchronizedEndNode);
+
+    return null;
+  }
+
+  @Override
+  public Node visitThrow(ThrowTree tree, Void p) {
+    Node expression = scan(tree.getExpression(), p);
+    TypeMirror exception = expression.getType();
+    ThrowNode throwsNode = new ThrowNode(tree, expression, env.getTypeUtils());
+    NodeWithExceptionsHolder exNode = extendWithNodeWithException(throwsNode, exception);
+    exNode.setTerminatesExecution(true);
+    return throwsNode;
+  }
+
+  @Override
+  public Node visitCompilationUnit(CompilationUnitTree tree, Void p) {
+    throw new Error("CompilationUnitTree is unexpected in AST to CFG translation");
+  }
+
+  @Override
+  public Node visitTry(TryTree tree, Void p) {
+    List<? extends CatchTree> catches = tree.getCatches();
+    BlockTree finallyBlock = tree.getFinallyBlock();
+
+    extendWithNode(
+        new MarkerNode(
+            tree, "start of try statement #" + TreeUtils.treeUids.get(tree), env.getTypeUtils()));
+
+    List<Pair<TypeMirror, Label>> catchLabels =
+        CollectionsPlume.mapList(
+            (CatchTree c) -> {
+              return Pair.of(TreeUtils.typeOf(c.getParameter().getType()), new Label());
+            },
+            catches);
+
+    // Store return/break/continue labels, just in case we need them for a finally block.
+    TryFinallyScopeCell oldReturnTargetL = returnTargetL;
+    TryFinallyScopeCell oldBreakTargetL = breakTargetL;
+    Map<Name, Label> oldBreakLabels = breakLabels;
+    TryFinallyScopeCell oldContinueTargetL = continueTargetL;
+    Map<Name, Label> oldContinueLabels = continueLabels;
+
+    Label finallyLabel = null;
+    Label exceptionalFinallyLabel = null;
+
+    if (finallyBlock != null) {
+      finallyLabel = new Label();
+
+      exceptionalFinallyLabel = new Label();
+      tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel));
+
+      returnTargetL = new TryFinallyScopeCell();
+
+      breakTargetL = new TryFinallyScopeCell();
+      breakLabels = new TryFinallyScopeMap();
+
+      continueTargetL = new TryFinallyScopeCell();
+      continueLabels = new TryFinallyScopeMap();
+    }
+
+    Label doneLabel = new Label();
+
+    tryStack.pushFrame(new TryCatchFrame(types, catchLabels));
+
+    // Must scan the resources *after* we push frame to tryStack. Otherwise we can lose catch
+    // blocks.
+    // TODO: Should we handle try-with-resources blocks by also generating code for automatically
+    // closing the resources?
+    List<? extends Tree> resources = tree.getResources();
+    for (Tree resource : resources) {
+      scan(resource, p);
+    }
+
+    extendWithNode(
+        new MarkerNode(
+            tree, "start of try block #" + TreeUtils.treeUids.get(tree), env.getTypeUtils()));
+    scan(tree.getBlock(), p);
+    extendWithNode(
+        new MarkerNode(
+            tree, "end of try block #" + TreeUtils.treeUids.get(tree), env.getTypeUtils()));
+
+    extendWithExtendedNode(new UnconditionalJump(CFGBuilder.firstNonNull(finallyLabel, doneLabel)));
+
+    tryStack.popFrame();
+
+    int catchIndex = 0;
+    for (CatchTree c : catches) {
+      addLabelForNextNode(catchLabels.get(catchIndex).second);
+      extendWithNode(
+          new MarkerNode(
+              tree,
+              "start of catch block for "
+                  + c.getParameter().getType()
+                  + " #"
+                  + TreeUtils.treeUids.get(tree),
+              env.getTypeUtils()));
+      scan(c, p);
+      extendWithNode(
+          new MarkerNode(
+              tree,
+              "end of catch block for "
+                  + c.getParameter().getType()
+                  + " #"
+                  + TreeUtils.treeUids.get(tree),
+              env.getTypeUtils()));
+
+      catchIndex++;
+      extendWithExtendedNode(
+          new UnconditionalJump(CFGBuilder.firstNonNull(finallyLabel, doneLabel)));
+    }
+
+    if (finallyLabel != null) {
+      // Reset values before analyzing the finally block!
+
+      tryStack.popFrame();
+
+      { // Scan 'finallyBlock' for only 'finallyLabel' (a successful path)
+        addLabelForNextNode(finallyLabel);
+        extendWithNode(
+            new MarkerNode(
+                tree,
+                "start of finally block #" + TreeUtils.treeUids.get(tree),
+                env.getTypeUtils()));
+        scan(finallyBlock, p);
+        extendWithNode(
+            new MarkerNode(
+                tree, "end of finally block #" + TreeUtils.treeUids.get(tree), env.getTypeUtils()));
+        extendWithExtendedNode(new UnconditionalJump(doneLabel));
+      }
+
+      if (hasExceptionalPath(exceptionalFinallyLabel)) {
+        // If an exceptional path exists, scan 'finallyBlock' for 'exceptionalFinallyLabel', and
+        // scan copied 'finallyBlock' for 'finallyLabel' (a successful path). If there is no
+        // successful path, it will be removed in later phase.
+        // TODO: Don't we need a separate finally block for each kind of exception?
+        addLabelForNextNode(exceptionalFinallyLabel);
+        extendWithNode(
+            new MarkerNode(
+                tree,
+                "start of finally block for Throwable #" + TreeUtils.treeUids.get(tree),
+                env.getTypeUtils()));
+
+        scan(finallyBlock, p);
+
+        NodeWithExceptionsHolder throwing =
+            extendWithNodeWithException(
+                new MarkerNode(
+                    tree,
+                    "end of finally block for Throwable #" + TreeUtils.treeUids.get(tree),
+                    env.getTypeUtils()),
+                throwableType);
+
+        throwing.setTerminatesExecution(true);
+      }
+
+      if (returnTargetL.wasAccessed()) {
+        addLabelForNextNode(returnTargetL.peekLabel());
+        returnTargetL = oldReturnTargetL;
+
+        extendWithNode(
+            new MarkerNode(
+                tree,
+                "start of finally block for return #" + TreeUtils.treeUids.get(tree),
+                env.getTypeUtils()));
+        scan(finallyBlock, p);
+        extendWithNode(
+            new MarkerNode(
+                tree,
+                "end of finally block for return #" + TreeUtils.treeUids.get(tree),
+                env.getTypeUtils()));
+        extendWithExtendedNode(new UnconditionalJump(returnTargetL.accessLabel()));
+      } else {
+        returnTargetL = oldReturnTargetL;
+      }
+
+      if (breakTargetL.wasAccessed()) {
+        addLabelForNextNode(breakTargetL.peekLabel());
+        breakTargetL = oldBreakTargetL;
+
+        extendWithNode(
+            new MarkerNode(
+                tree,
+                "start of finally block for break #" + TreeUtils.treeUids.get(tree),
+                env.getTypeUtils()));
+        scan(finallyBlock, p);
+        extendWithNode(
+            new MarkerNode(
+                tree,
+                "end of finally block for break #" + TreeUtils.treeUids.get(tree),
+                env.getTypeUtils()));
+        extendWithExtendedNode(new UnconditionalJump(breakTargetL.accessLabel()));
+      } else {
+        breakTargetL = oldBreakTargetL;
+      }
+
+      Map<Name, Label> accessedBreakLabels = ((TryFinallyScopeMap) breakLabels).getAccessedNames();
+      if (!accessedBreakLabels.isEmpty()) {
+        breakLabels = oldBreakLabels;
+
+        for (Map.Entry<Name, Label> access : accessedBreakLabels.entrySet()) {
+          addLabelForNextNode(access.getValue());
+          extendWithNode(
+              new MarkerNode(
+                  tree,
+                  "start of finally block for break label "
+                      + access.getKey()
+                      + " #"
+                      + TreeUtils.treeUids.get(tree),
+                  env.getTypeUtils()));
+          scan(finallyBlock, p);
+          extendWithNode(
+              new MarkerNode(
+                  tree,
+                  "end of finally block for break label "
+                      + access.getKey()
+                      + " #"
+                      + TreeUtils.treeUids.get(tree),
+                  env.getTypeUtils()));
+          extendWithExtendedNode(new UnconditionalJump(breakLabels.get(access.getKey())));
+        }
+      } else {
+        breakLabels = oldBreakLabels;
+      }
+
+      if (continueTargetL.wasAccessed()) {
+        addLabelForNextNode(continueTargetL.peekLabel());
+        continueTargetL = oldContinueTargetL;
+
+        extendWithNode(
+            new MarkerNode(
+                tree,
+                "start of finally block for continue #" + TreeUtils.treeUids.get(tree),
+                env.getTypeUtils()));
+        scan(finallyBlock, p);
+        extendWithNode(
+            new MarkerNode(
+                tree,
+                "end of finally block for continue #" + TreeUtils.treeUids.get(tree),
+                env.getTypeUtils()));
+        extendWithExtendedNode(new UnconditionalJump(continueTargetL.accessLabel()));
+      } else {
+        continueTargetL = oldContinueTargetL;
+      }
+
+      Map<Name, Label> accessedContinueLabels =
+          ((TryFinallyScopeMap) continueLabels).getAccessedNames();
+      if (!accessedContinueLabels.isEmpty()) {
+        continueLabels = oldContinueLabels;
+
+        for (Map.Entry<Name, Label> access : accessedContinueLabels.entrySet()) {
+          addLabelForNextNode(access.getValue());
+          extendWithNode(
+              new MarkerNode(
+                  tree,
+                  "start of finally block for continue label "
+                      + access.getKey()
+                      + " #"
+                      + TreeUtils.treeUids.get(tree),
+                  env.getTypeUtils()));
+          scan(finallyBlock, p);
+          extendWithNode(
+              new MarkerNode(
+                  tree,
+                  "end of finally block for continue label "
+                      + access.getKey()
+                      + " #"
+                      + TreeUtils.treeUids.get(tree),
+                  env.getTypeUtils()));
+          extendWithExtendedNode(new UnconditionalJump(continueLabels.get(access.getKey())));
+        }
+      } else {
+        continueLabels = oldContinueLabels;
+      }
+    }
+
+    addLabelForNextNode(doneLabel);
+
+    return null;
+  }
+
+  /**
+   * Returns whether an exceptional node for {@code target} exists in {@link #nodeList} or not.
+   *
+   * @param target label for exception
+   * @return true when an exceptional node for {@code target} exists in {@link #nodeList}
+   */
+  private boolean hasExceptionalPath(Label target) {
+    for (ExtendedNode node : nodeList) {
+      if (node instanceof NodeWithExceptionsHolder) {
+        NodeWithExceptionsHolder exceptionalNode = (NodeWithExceptionsHolder) node;
+        for (Set<Label> labels : exceptionalNode.getExceptions().values()) {
+          if (labels.contains(target)) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public Node visitParameterizedType(ParameterizedTypeTree tree, Void p) {
+    Node result = new ParameterizedTypeNode(tree);
+    extendWithNode(result);
+    return result;
+  }
+
+  @Override
+  public Node visitUnionType(UnionTypeTree tree, Void p) {
+    throw new Error("UnionTypeTree is unexpected in AST to CFG translation");
+  }
+
+  @Override
+  public Node visitArrayType(ArrayTypeTree tree, Void p) {
+    Node result = new ArrayTypeNode(tree, types);
+    extendWithNode(new ArrayTypeNode(tree, types));
+    return result;
+  }
+
+  @Override
+  public Node visitTypeCast(TypeCastTree tree, Void p) {
+    final Node operand = scan(tree.getExpression(), p);
+    final TypeMirror type = TreeUtils.typeOf(tree.getType());
+    final Node node = new TypeCastNode(tree, operand, type, types);
+
+    extendWithNodeWithException(node, classCastExceptionType);
+    return node;
+  }
+
+  @Override
+  public Node visitPrimitiveType(PrimitiveTypeTree tree, Void p) {
+    Node result = new PrimitiveTypeNode(tree, types);
+    extendWithNode(result);
+    return result;
+  }
+
+  @Override
+  public Node visitTypeParameter(TypeParameterTree tree, Void p) {
+    throw new Error("TypeParameterTree is unexpected in AST to CFG translation");
+  }
+
+  @Override
+  public Node visitInstanceOf(InstanceOfTree tree, Void p) {
+    Node operand = scan(tree.getExpression(), p);
+    TypeMirror refType = TreeUtils.typeOf(tree.getType());
+    InstanceOfNode node = new InstanceOfNode(tree, operand, refType, types);
+    extendWithNode(node);
+    return node;
+  }
+
+  @Override
+  public Node visitUnary(UnaryTree tree, Void p) {
+    Node result = null;
+    Tree.Kind kind = tree.getKind();
+    switch (kind) {
+      case BITWISE_COMPLEMENT:
+      case UNARY_MINUS:
+      case UNARY_PLUS:
+        {
+          // see JLS 15.14 and 15.15
+          Node expr = scan(tree.getExpression(), p);
+          expr = unaryNumericPromotion(expr);
+
+          // TypeMirror exprType = InternalUtils.typeOf(tree);
+
+          switch (kind) {
+            case BITWISE_COMPLEMENT:
+              result = new BitwiseComplementNode(tree, expr);
+              break;
+            case UNARY_MINUS:
+              result = new NumericalMinusNode(tree, expr);
+              break;
+            case UNARY_PLUS:
+              result = new NumericalPlusNode(tree, expr);
+              break;
+            default:
+              throw new Error("Unexpected kind");
+          }
+          extendWithNode(result);
+          break;
+        }
+
+      case LOGICAL_COMPLEMENT:
+        {
+          // see JLS 15.15.6
+          Node expr = scan(tree.getExpression(), p);
+          result = new ConditionalNotNode(tree, unbox(expr));
+          extendWithNode(result);
+          break;
+        }
+
+      case POSTFIX_DECREMENT:
+      case POSTFIX_INCREMENT:
+      case PREFIX_DECREMENT:
+      case PREFIX_INCREMENT:
+        {
+          ExpressionTree exprTree = tree.getExpression();
+          Node expr = scan(exprTree, p);
+
+          boolean isIncrement =
+              kind == Tree.Kind.POSTFIX_INCREMENT || kind == Kind.PREFIX_INCREMENT;
+          boolean isPostfix = kind == Tree.Kind.POSTFIX_INCREMENT || kind == Kind.POSTFIX_DECREMENT;
+          AssignmentNode unaryAssign =
+              createIncrementOrDecrementAssign(isPostfix ? null : tree, expr, isIncrement);
+          addToUnaryAssignLookupMap(tree, unaryAssign);
+
+          if (isPostfix) {
+            TypeMirror exprType = TreeUtils.typeOf(exprTree);
+            VariableTree tempVarDecl =
+                treeBuilder.buildVariableDecl(
+                    exprType, uniqueName("tempPostfix"), findOwner(), tree.getExpression());
+            handleArtificialTree(tempVarDecl);
+            VariableDeclarationNode tempVarDeclNode = new VariableDeclarationNode(tempVarDecl);
+            tempVarDeclNode.setInSource(false);
+            extendWithNode(tempVarDeclNode);
+
+            Tree tempVar = treeBuilder.buildVariableUse(tempVarDecl);
+            handleArtificialTree(tempVar);
+            Node tempVarNode = new LocalVariableNode(tempVar);
+            tempVarNode.setInSource(false);
+            extendWithNode(tempVarNode);
+
+            AssignmentNode tempAssignNode = new AssignmentNode(tree, tempVarNode, expr);
+            tempAssignNode.setInSource(false);
+            extendWithNode(tempAssignNode);
+
+            Tree resultExpr = treeBuilder.buildVariableUse(tempVarDecl);
+            handleArtificialTree(resultExpr);
+            result = new LocalVariableNode(resultExpr);
+            result.setInSource(false);
+            extendWithNode(result);
+          } else {
+            result = unaryAssign;
+          }
+          break;
+        }
+
+      case OTHER:
+      default:
+        // special node NLLCHK
+        if (tree.toString().startsWith("<*nullchk*>")) {
+          Node expr = scan(tree.getExpression(), p);
+          result = new NullChkNode(tree, expr);
+          extendWithNode(result);
+          break;
+        }
+
+        throw new Error("Unknown kind (" + kind + ") of unary expression: " + tree);
+    }
+
+    return result;
+  }
+
+  /**
+   * Create assignment node which represent increment or decrement.
+   *
+   * @param target tree for assignment node. If it's null, corresponding assignment tree will be
+   *     generated.
+   * @param expr expression node to be incremented or decremented
+   * @param isIncrement true when it's increment
+   * @return assignment node for corresponding increment or decrement
+   */
+  private AssignmentNode createIncrementOrDecrementAssign(
+      Tree target, Node expr, boolean isIncrement) {
+    ExpressionTree exprTree = (ExpressionTree) expr.getTree();
+    TypeMirror exprType = expr.getType();
+    TypeMirror oneType = types.getPrimitiveType(TypeKind.INT);
+    TypeMirror promotedType = binaryPromotedType(exprType, oneType);
+
+    LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1));
+    handleArtificialTree(oneTree);
+
+    Node exprRHS = binaryNumericPromotion(expr, promotedType);
+    Node one = new IntegerLiteralNode(oneTree);
+    one.setInSource(false);
+    extendWithNode(one);
+    one = binaryNumericPromotion(one, promotedType);
+
+    BinaryTree operTree =
+        treeBuilder.buildBinary(
+            promotedType, isIncrement ? Tree.Kind.PLUS : Tree.Kind.MINUS, exprTree, oneTree);
+    handleArtificialTree(operTree);
+
+    Node operNode;
+    if (isIncrement) {
+      operNode = new NumericalAdditionNode(operTree, exprRHS, one);
+    } else {
+      operNode = new NumericalSubtractionNode(operTree, exprRHS, one);
+    }
+    operNode.setInSource(false);
+    extendWithNode(operNode);
+
+    Node narrowed = narrowAndBox(operNode, exprType);
+
+    if (target == null) {
+      target = treeBuilder.buildAssignment(exprTree, (ExpressionTree) narrowed.getTree());
+      handleArtificialTree(target);
+    }
+
+    AssignmentNode assignNode = new AssignmentNode(target, expr, narrowed);
+    assignNode.setInSource(false);
+    extendWithNode(assignNode);
+    return assignNode;
+  }
+
+  @Override
+  public Node visitVariable(VariableTree tree, Void p) {
+
+    // see JLS 14.4
+
+    boolean isField =
+        getCurrentPath().getParentPath() != null
+            && getCurrentPath().getParentPath().getLeaf().getKind() == Kind.CLASS;
+    Node node = null;
+
+    ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath());
+    TypeElement classElem = TreeUtils.elementFromDeclaration(enclosingClass);
+    Node receiver = new ImplicitThisNode(classElem.asType());
+
+    if (isField) {
+      ExpressionTree initializer = tree.getInitializer();
+      assert initializer != null;
+      node =
+          translateAssignment(
+              tree,
+              new FieldAccessNode(tree, TreeUtils.elementFromDeclaration(tree), receiver),
+              initializer);
+    } else {
+      // local variable definition
+      VariableDeclarationNode decl = new VariableDeclarationNode(tree);
+      extendWithNode(decl);
+
+      // initializer
+
+      ExpressionTree initializer = tree.getInitializer();
+      if (initializer != null) {
+        node = translateAssignment(tree, new LocalVariableNode(tree, receiver), initializer);
+      }
+    }
+
+    return node;
+  }
+
+  @Override
+  public Node visitWhileLoop(WhileLoopTree tree, Void p) {
+    Name parentLabel = getLabel(getCurrentPath());
+
+    Label loopEntry = new Label();
+    Label loopExit = new Label();
+
+    // If the loop is a labeled statement, then its continue target is identical for continues with
+    // no label and continues with the loop's label.
+    Label conditionStart;
+    if (parentLabel != null) {
+      conditionStart = continueLabels.get(parentLabel);
+    } else {
+      conditionStart = new Label();
+    }
+
+    TryFinallyScopeCell oldBreakTargetL = breakTargetL;
+    breakTargetL = new TryFinallyScopeCell(loopExit);
+
+    TryFinallyScopeCell oldContinueTargetL = continueTargetL;
+    continueTargetL = new TryFinallyScopeCell(conditionStart);
+
+    // Condition
+    addLabelForNextNode(conditionStart);
+    assert tree.getCondition() != null;
+    // Determine whether the loop condition has the constant value true, according to the
+    // compiler logic.
+    boolean isCondConstTrue = TreeUtils.isExprConstTrue(tree.getCondition());
+
+    unbox(scan(tree.getCondition(), p));
+
+    if (!isCondConstTrue) {
+      // If the loop condition does not have the constant value true, the control flow is
+      // split into two branches.
+      ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit);
+      extendWithExtendedNode(cjump);
+    }
+
+    // Loop body
+    addLabelForNextNode(loopEntry);
+    assert tree.getStatement() != null;
+    scan(tree.getStatement(), p);
+
+    if (isCondConstTrue) {
+      // The condition has the constant value true, so we can directly jump back to the loop entry.
+      extendWithExtendedNode(new UnconditionalJump(loopEntry));
+    } else {
+      // Otherwise, jump back to evaluate the condition.
+      extendWithExtendedNode(new UnconditionalJump(conditionStart));
+    }
+
+    // Loop exit
+    addLabelForNextNode(loopExit);
+
+    breakTargetL = oldBreakTargetL;
+    continueTargetL = oldContinueTargetL;
+
+    return null;
+  }
+
+  @Override
+  public Node visitLambdaExpression(LambdaExpressionTree tree, Void p) {
+    declaredLambdas.add(tree);
+    Node node = new FunctionalInterfaceNode(tree);
+    extendWithNode(node);
+    return node;
+  }
+
+  @Override
+  public Node visitMemberReference(MemberReferenceTree tree, Void p) {
+    Tree enclosingExpr = tree.getQualifierExpression();
+    if (enclosingExpr != null) {
+      scan(enclosingExpr, p);
+    }
+
+    Node node = new FunctionalInterfaceNode(tree);
+    extendWithNode(node);
+
+    return node;
+  }
+
+  @Override
+  public Node visitWildcard(WildcardTree tree, Void p) {
+    throw new BugInCF("WildcardTree is unexpected in AST to CFG translation");
+  }
+
+  @Override
+  public Node visitOther(Tree tree, Void p) {
+    throw new BugInCF("Unknown AST element encountered in AST to CFG translation.");
+  }
+
+  /**
+   * Returns the TypeMirror for the given class.
+   *
+   * @param clazz a class
+   * @return the TypeMirror for the class
+   */
+  private TypeMirror getTypeMirror(Class<?> clazz) {
+    return TypesUtils.typeFromClass(clazz, types, elements);
+  }
+
+  /**
+   * Returns the TypeMirror for the given class, or {@code null} if the type is not present.
+   *
+   * <p>This can be used to handle system types that are not present. For example, in Java code that
+   * is translated to JavaScript using j2cl, the custom bootclasspath contains APIs that are
+   * emulated in JavaScript, so some types such as OutOfMemoryError are deliberately not present.
+   *
+   * @param clazz a class, which must have a canonical name
+   * @return the TypeMirror for the class, or {@code null} if the type is not present
+   */
+  private @Nullable TypeMirror maybeGetTypeMirror(Class<?> clazz) {
+    String name = clazz.getCanonicalName();
+    assert name != null : clazz + " does not have a canonical name";
+    TypeElement element = elements.getTypeElement(name);
+    if (element == null) {
+      return null;
+    }
+    return element.asType();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java
new file mode 100644
index 0000000..bf83c7f
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java
@@ -0,0 +1,341 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.block.Block.BlockType;
+import org.checkerframework.dataflow.cfg.block.BlockImpl;
+import org.checkerframework.dataflow.cfg.block.ConditionalBlockImpl;
+import org.checkerframework.dataflow.cfg.block.ExceptionBlockImpl;
+import org.checkerframework.dataflow.cfg.block.RegularBlockImpl;
+import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl;
+
+/* --------------------------------------------------------- */
+/* Phase Three */
+/* --------------------------------------------------------- */
+
+/**
+ * Class that performs phase three of the translation process. In particular, the following
+ * degenerate cases of basic blocks are removed:
+ *
+ * <ol>
+ *   <li>Empty regular basic blocks: These blocks will be removed and their predecessors linked
+ *       directly to the successor.
+ *   <li>Conditional basic blocks that have the same basic block as the 'then' and 'else' successor:
+ *       The conditional basic block will be removed in this case.
+ *   <li>Two consecutive, non-empty, regular basic blocks where the second block has exactly one
+ *       predecessor (namely the other of the two blocks): In this case, the two blocks are merged.
+ *   <li>Some basic blocks might not be reachable from the entryBlock. These basic blocks are
+ *       removed, and the list of predecessors (in the doubly-linked structure of basic blocks) are
+ *       adapted correctly.
+ * </ol>
+ *
+ * Eliminating the second type of degenerate cases might introduce cases of the third problem. These
+ * are also removed.
+ */
+public class CFGTranslationPhaseThree {
+
+  /** A simple wrapper object that holds a basic block and allows to set one of its successors. */
+  protected interface PredecessorHolder {
+    void setSuccessor(BlockImpl b);
+
+    BlockImpl getBlock();
+  }
+
+  /**
+   * Perform phase three on the control flow graph {@code cfg}.
+   *
+   * @param cfg the control flow graph. Ownership is transfered to this method and the caller is not
+   *     allowed to read or modify {@code cfg} after the call to {@code process} any more.
+   * @return the resulting control flow graph
+   */
+  @SuppressWarnings("nullness") // TODO: successors
+  public static ControlFlowGraph process(ControlFlowGraph cfg) {
+    Set<Block> worklist = cfg.getAllBlocks();
+    Set<Block> dontVisit = new HashSet<>();
+
+    // note: this method has to be careful when relinking basic blocks
+    // to not forget to adjust the predecessors, too
+
+    // fix predecessor lists by removing any unreachable predecessors
+    for (Block c : worklist) {
+      BlockImpl cur = (BlockImpl) c;
+      for (Block pred : new HashSet<>(cur.getPredecessors())) {
+        if (!worklist.contains(pred)) {
+          cur.removePredecessor((BlockImpl) pred);
+        }
+      }
+    }
+
+    // remove empty blocks
+    for (Block cur : worklist) {
+      if (dontVisit.contains(cur)) {
+        continue;
+      }
+
+      if (cur.getType() == BlockType.REGULAR_BLOCK) {
+        RegularBlockImpl b = (RegularBlockImpl) cur;
+        if (b.isEmpty()) {
+          Set<RegularBlockImpl> emptyBlocks = new HashSet<>();
+          Set<PredecessorHolder> predecessors = new LinkedHashSet<>();
+          BlockImpl succ = computeNeighborhoodOfEmptyBlock(b, emptyBlocks, predecessors);
+          for (RegularBlockImpl e : emptyBlocks) {
+            succ.removePredecessor(e);
+            dontVisit.add(e);
+          }
+          for (PredecessorHolder p : predecessors) {
+            BlockImpl block = p.getBlock();
+            dontVisit.add(block);
+            succ.removePredecessor(block);
+            p.setSuccessor(succ);
+          }
+        }
+      }
+    }
+
+    // remove useless conditional blocks
+    /* Issue 3267 revealed that this is a dangerous optimization:
+       it merges a block that evaluates one condition onto an unrelated following block,
+       which can also be a condition. The then/else stores from the first block are still
+       set, leading to incorrect results for the then/else stores in the following block.
+       The correct result would be to merge the then/else stores from the previous block.
+       However, as this is late in the CFG construction, I didn't see how to add e.g. a
+       dummy variable declaration node in a dummy regular block, which would cause a merge.
+       So for now, let's not perform this optimization.
+       It would be interesting to know how large the impact of this optimization is.
+
+    worklist = cfg.getAllBlocks();
+    for (Block c : worklist) {
+        BlockImpl cur = (BlockImpl) c;
+
+        if (cur.getType() == BlockType.CONDITIONAL_BLOCK) {
+            ConditionalBlockImpl cb = (ConditionalBlockImpl) cur;
+            assert cb.getPredecessors().size() == 1;
+            if (cb.getThenSuccessor() == cb.getElseSuccessor()) {
+                BlockImpl pred = cb.getPredecessors().iterator().next();
+                PredecessorHolder predecessorHolder = getPredecessorHolder(pred, cb);
+                BlockImpl succ = (BlockImpl) cb.getThenSuccessor();
+                succ.removePredecessor(cb);
+                predecessorHolder.setSuccessor(succ);
+            }
+        }
+    }
+    */
+
+    // merge consecutive basic blocks if possible
+    worklist = cfg.getAllBlocks();
+    for (Block cur : worklist) {
+      if (cur.getType() == BlockType.REGULAR_BLOCK) {
+        RegularBlockImpl b = (RegularBlockImpl) cur;
+        Block succ = b.getRegularSuccessor();
+        if (succ.getType() == BlockType.REGULAR_BLOCK) {
+          RegularBlockImpl rs = (RegularBlockImpl) succ;
+          if (rs.getPredecessors().size() == 1) {
+            b.setSuccessor(rs.getRegularSuccessor());
+            b.addNodes(rs.getNodes());
+            rs.getRegularSuccessor().removePredecessor(rs);
+          }
+        }
+      }
+    }
+    return cfg;
+  }
+
+  /**
+   * Compute the set of empty regular basic blocks {@code emptyBlocks}, starting at {@code start}
+   * and going both forward and backwards. Furthermore, compute the predecessors of these empty
+   * blocks ({@code predecessors} ), and their single successor (return value).
+   *
+   * @param start the starting point of the search (an empty, regular basic block)
+   * @param emptyBlocks a set to be filled by this method with all empty basic blocks found
+   *     (including {@code start}).
+   * @param predecessors a set to be filled by this method with all predecessors
+   * @return the single successor of the set of the empty basic blocks
+   */
+  @SuppressWarnings({
+    "interning:not.interned", // AST node comparisons
+    "nullness" // successors
+  })
+  protected static BlockImpl computeNeighborhoodOfEmptyBlock(
+      RegularBlockImpl start,
+      Set<RegularBlockImpl> emptyBlocks,
+      Set<PredecessorHolder> predecessors) {
+
+    // get empty neighborhood that come before 'start'
+    computeNeighborhoodOfEmptyBlockBackwards(start, emptyBlocks, predecessors);
+
+    // go forward
+    BlockImpl succ = (BlockImpl) start.getSuccessor();
+    while (succ.getType() == BlockType.REGULAR_BLOCK) {
+      RegularBlockImpl cur = (RegularBlockImpl) succ;
+      if (cur.isEmpty()) {
+        computeNeighborhoodOfEmptyBlockBackwards(cur, emptyBlocks, predecessors);
+        assert emptyBlocks.contains(cur) : "cur ought to be in emptyBlocks";
+        succ = (BlockImpl) cur.getSuccessor();
+        if (succ == cur) {
+          // An infinite loop, making exit block unreachable
+          break;
+        }
+      } else {
+        break;
+      }
+    }
+    return succ;
+  }
+
+  /**
+   * Compute the set of empty regular basic blocks {@code emptyBlocks}, starting at {@code start}
+   * and looking only backwards in the control flow graph. Furthermore, compute the predecessors of
+   * these empty blocks ({@code predecessors}).
+   *
+   * @param start the starting point of the search (an empty, regular basic block)
+   * @param emptyBlocks a set to be filled by this method with all empty basic blocks found
+   *     (including {@code start}).
+   * @param predecessors a set to be filled by this method with all predecessors
+   */
+  protected static void computeNeighborhoodOfEmptyBlockBackwards(
+      RegularBlockImpl start,
+      Set<RegularBlockImpl> emptyBlocks,
+      Set<PredecessorHolder> predecessors) {
+
+    RegularBlockImpl cur = start;
+    emptyBlocks.add(cur);
+    for (final Block p : cur.getPredecessors()) {
+      BlockImpl pred = (BlockImpl) p;
+      switch (pred.getType()) {
+        case SPECIAL_BLOCK:
+          // add pred correctly to predecessor list
+          predecessors.add(getPredecessorHolder(pred, cur));
+          break;
+        case CONDITIONAL_BLOCK:
+          // add pred correctly to predecessor list
+          predecessors.add(getPredecessorHolder(pred, cur));
+          break;
+        case EXCEPTION_BLOCK:
+          // add pred correctly to predecessor list
+          predecessors.add(getPredecessorHolder(pred, cur));
+          break;
+        case REGULAR_BLOCK:
+          RegularBlockImpl r = (RegularBlockImpl) pred;
+          if (r.isEmpty()) {
+            // recursively look backwards
+            if (!emptyBlocks.contains(r)) {
+              computeNeighborhoodOfEmptyBlockBackwards(r, emptyBlocks, predecessors);
+            }
+          } else {
+            // add pred correctly to predecessor list
+            predecessors.add(getPredecessorHolder(pred, cur));
+          }
+          break;
+      }
+    }
+  }
+
+  /**
+   * Return a predecessor holder that can be used to set the successor of {@code pred} in the place
+   * where previously the edge pointed to {@code cur}. Additionally, the predecessor holder also
+   * takes care of unlinking (i.e., removing the {@code pred} from {@code cur's} predecessors).
+   *
+   * @param pred a block whose successor should be set
+   * @param cur the previous successor of {@code pred}
+   * @return a predecessor holder to set the successor of {@code pred}
+   */
+  @SuppressWarnings("interning:not.interned") // AST node comparisons
+  protected static PredecessorHolder getPredecessorHolder(
+      final BlockImpl pred, final BlockImpl cur) {
+    switch (pred.getType()) {
+      case SPECIAL_BLOCK:
+        SingleSuccessorBlockImpl s = (SingleSuccessorBlockImpl) pred;
+        return singleSuccessorHolder(s, cur);
+      case CONDITIONAL_BLOCK:
+        // add pred correctly to predecessor list
+        final ConditionalBlockImpl c = (ConditionalBlockImpl) pred;
+        if (c.getThenSuccessor() == cur) {
+          return new PredecessorHolder() {
+            @Override
+            public void setSuccessor(BlockImpl b) {
+              c.setThenSuccessor(b);
+              cur.removePredecessor(pred);
+            }
+
+            @Override
+            public BlockImpl getBlock() {
+              return c;
+            }
+          };
+        } else {
+          assert c.getElseSuccessor() == cur;
+          return new PredecessorHolder() {
+            @Override
+            public void setSuccessor(BlockImpl b) {
+              c.setElseSuccessor(b);
+              cur.removePredecessor(pred);
+            }
+
+            @Override
+            public BlockImpl getBlock() {
+              return c;
+            }
+          };
+        }
+      case EXCEPTION_BLOCK:
+        // add pred correctly to predecessor list
+        final ExceptionBlockImpl e = (ExceptionBlockImpl) pred;
+        if (e.getSuccessor() == cur) {
+          return singleSuccessorHolder(e, cur);
+        } else {
+          @SuppressWarnings("keyfor:assignment") // ignore keyfor type
+          Set<Map.Entry<TypeMirror, Set<Block>>> entrySet = e.getExceptionalSuccessors().entrySet();
+          for (final Map.Entry<TypeMirror, Set<Block>> entry : entrySet) {
+            if (entry.getValue().contains(cur)) {
+              return new PredecessorHolder() {
+                @Override
+                public void setSuccessor(BlockImpl b) {
+                  e.addExceptionalSuccessor(b, entry.getKey());
+                  cur.removePredecessor(pred);
+                }
+
+                @Override
+                public BlockImpl getBlock() {
+                  return e;
+                }
+              };
+            }
+          }
+        }
+        throw new Error("Unreachable");
+      case REGULAR_BLOCK:
+        RegularBlockImpl r = (RegularBlockImpl) pred;
+        return singleSuccessorHolder(r, cur);
+      default:
+        throw new Error("Unexpected block type " + pred.getType());
+    }
+  }
+
+  /**
+   * Returns a {@link PredecessorHolder} that sets the successor of a single successor block {@code
+   * s}.
+   *
+   * @return a {@link PredecessorHolder} that sets the successor of a single successor block {@code
+   *     s}
+   */
+  protected static PredecessorHolder singleSuccessorHolder(
+      final SingleSuccessorBlockImpl s, final BlockImpl old) {
+    return new PredecessorHolder() {
+      @Override
+      public void setSuccessor(BlockImpl b) {
+        s.setSuccessor(b);
+        old.removePredecessor(s);
+      }
+
+      @Override
+      public BlockImpl getBlock() {
+        return s;
+      }
+    };
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseTwo.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseTwo.java
new file mode 100644
index 0000000..4f4ded0
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseTwo.java
@@ -0,0 +1,218 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.block.BlockImpl;
+import org.checkerframework.dataflow.cfg.block.ConditionalBlockImpl;
+import org.checkerframework.dataflow.cfg.block.ExceptionBlockImpl;
+import org.checkerframework.dataflow.cfg.block.RegularBlockImpl;
+import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl;
+import org.checkerframework.dataflow.cfg.block.SpecialBlock.SpecialBlockType;
+import org.checkerframework.dataflow.cfg.block.SpecialBlockImpl;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.util.MostlySingleton;
+
+/** Class that performs phase two of the translation process. */
+@SuppressWarnings("nullness") // TODO
+public class CFGTranslationPhaseTwo {
+
+  private CFGTranslationPhaseTwo() {}
+
+  /**
+   * Perform phase two of the translation.
+   *
+   * @param in the result of phase one
+   * @return a control flow graph that might still contain degenerate basic block (such as empty
+   *     regular basic blocks or conditional blocks with the same block as 'then' and 'else'
+   *     successor)
+   */
+  @SuppressWarnings("interning:not.interned") // AST node comparisons
+  public static ControlFlowGraph process(PhaseOneResult in) {
+
+    Map<Label, Integer> bindings = in.bindings;
+    ArrayList<ExtendedNode> nodeList = in.nodeList;
+    // A leader is an extended node which will give rise to a basic block in phase two.
+    Set<Integer> leaders = in.leaders;
+
+    assert !in.nodeList.isEmpty();
+
+    // exit blocks
+    SpecialBlockImpl regularExitBlock = new SpecialBlockImpl(SpecialBlockType.EXIT);
+    SpecialBlockImpl exceptionalExitBlock = new SpecialBlockImpl(SpecialBlockType.EXCEPTIONAL_EXIT);
+
+    // record missing edges that will be added later
+    Set<MissingEdge> missingEdges = new MostlySingleton<>();
+
+    // missing exceptional edges
+    Set<MissingEdge> missingExceptionalEdges = new LinkedHashSet<>();
+
+    // create start block
+    SpecialBlockImpl startBlock = new SpecialBlockImpl(SpecialBlockType.ENTRY);
+    missingEdges.add(new MissingEdge(startBlock, 0));
+
+    // Loop through all 'leaders' (while dynamically detecting the leaders).
+    @NonNull RegularBlockImpl block = new RegularBlockImpl(); // block being processed/built
+    int i = 0;
+    for (ExtendedNode node : nodeList) {
+      switch (node.getType()) {
+        case NODE:
+          if (leaders.contains(i)) {
+            RegularBlockImpl b = new RegularBlockImpl();
+            block.setSuccessor(b);
+            block = b;
+          }
+          block.addNode(node.getNode());
+          node.setBlock(block);
+
+          // does this node end the execution (modeled as an edge to
+          // the exceptional exit block)
+          boolean terminatesExecution = node.getTerminatesExecution();
+          if (terminatesExecution) {
+            block.setSuccessor(exceptionalExitBlock);
+            block = new RegularBlockImpl();
+          }
+          break;
+        case CONDITIONAL_JUMP:
+          {
+            ConditionalJump cj = (ConditionalJump) node;
+            // Exception nodes may fall through to conditional jumps, so we set the block which is
+            // required for the insertion of missing edges.
+            node.setBlock(block);
+            assert block != null;
+            final ConditionalBlockImpl cb = new ConditionalBlockImpl();
+            if (cj.getTrueFlowRule() != null) {
+              cb.setThenFlowRule(cj.getTrueFlowRule());
+            }
+            if (cj.getFalseFlowRule() != null) {
+              cb.setElseFlowRule(cj.getFalseFlowRule());
+            }
+            block.setSuccessor(cb);
+            block = new RegularBlockImpl();
+
+            // use two anonymous SingleSuccessorBlockImpl that set the
+            // 'then' and 'else' successor of the conditional block
+            final Label thenLabel = cj.getThenLabel();
+            final Label elseLabel = cj.getElseLabel();
+            Integer target = bindings.get(thenLabel);
+            assert target != null;
+            missingEdges.add(
+                new MissingEdge(
+                    new RegularBlockImpl() {
+                      @Override
+                      public void setSuccessor(BlockImpl successor) {
+                        cb.setThenSuccessor(successor);
+                      }
+                    },
+                    target));
+            target = bindings.get(elseLabel);
+            assert target != null;
+            missingEdges.add(
+                new MissingEdge(
+                    new RegularBlockImpl() {
+                      @Override
+                      public void setSuccessor(BlockImpl successor) {
+                        cb.setElseSuccessor(successor);
+                      }
+                    },
+                    target));
+            break;
+          }
+        case UNCONDITIONAL_JUMP:
+          UnconditionalJump uj = (UnconditionalJump) node;
+          if (leaders.contains(i)) {
+            RegularBlockImpl b = new RegularBlockImpl();
+            block.setSuccessor(b);
+            block = b;
+          }
+          node.setBlock(block);
+          if (node.getLabel() == in.regularExitLabel) {
+            block.setSuccessor(regularExitBlock);
+            block.setFlowRule(uj.getFlowRule());
+          } else if (node.getLabel() == in.exceptionalExitLabel) {
+            block.setSuccessor(exceptionalExitBlock);
+            block.setFlowRule(uj.getFlowRule());
+          } else {
+            int target = bindings.get(node.getLabel());
+            missingEdges.add(new MissingEdge(block, target, uj.getFlowRule()));
+          }
+          block = new RegularBlockImpl();
+          break;
+        case EXCEPTION_NODE:
+          NodeWithExceptionsHolder en = (NodeWithExceptionsHolder) node;
+          // create new exception block and link with previous block
+          ExceptionBlockImpl e = new ExceptionBlockImpl();
+          Node nn = en.getNode();
+          e.setNode(nn);
+          node.setBlock(e);
+          block.setSuccessor(e);
+          block = new RegularBlockImpl();
+
+          // Ensure linking between e and next block (normal edge).
+          // Note: do not link to the next block for throw statements (these throw exceptions for
+          // sure).
+          if (!node.getTerminatesExecution()) {
+            missingEdges.add(new MissingEdge(e, i + 1));
+          }
+
+          // exceptional edges
+          for (Map.Entry<TypeMirror, Set<Label>> entry : en.getExceptions().entrySet()) {
+            TypeMirror cause = entry.getKey();
+            for (Label label : entry.getValue()) {
+              Integer target = bindings.get(label);
+              // TODO: This is sometimes null; is this a problem?
+              // assert target != null;
+              missingExceptionalEdges.add(new MissingEdge(e, target, cause));
+            }
+          }
+          break;
+      }
+      i++;
+    }
+
+    // add missing edges
+    for (MissingEdge p : missingEdges) {
+      Integer index = p.index;
+      assert index != null : "CFGBuilder: problem in CFG construction " + p.source;
+      ExtendedNode extendedNode = nodeList.get(index);
+      BlockImpl target = extendedNode.getBlock();
+      SingleSuccessorBlockImpl source = p.source;
+      source.setSuccessor(target);
+      if (p.flowRule != null) {
+        source.setFlowRule(p.flowRule);
+      }
+    }
+
+    // add missing exceptional edges
+    for (MissingEdge p : missingExceptionalEdges) {
+      Integer index = p.index;
+      TypeMirror cause = p.cause;
+      ExceptionBlockImpl source = (ExceptionBlockImpl) p.source;
+      if (index == null) {
+        // edge to exceptional exit
+        source.addExceptionalSuccessor(exceptionalExitBlock, cause);
+      } else {
+        // edge to specific target
+        ExtendedNode extendedNode = nodeList.get(index);
+        BlockImpl target = extendedNode.getBlock();
+        source.addExceptionalSuccessor(target, cause);
+      }
+    }
+
+    return new ControlFlowGraph(
+        startBlock,
+        regularExitBlock,
+        exceptionalExitBlock,
+        in.underlyingAST,
+        in.treeLookupMap,
+        in.convertedTreeLookupMap,
+        in.unaryAssignNodeLookupMap,
+        in.returnNodes,
+        in.declaredClasses,
+        in.declaredLambdas);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ConditionalJump.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ConditionalJump.java
new file mode 100644
index 0000000..b4a5e44
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ConditionalJump.java
@@ -0,0 +1,100 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import org.checkerframework.dataflow.analysis.Store.FlowRule;
+import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType;
+
+/**
+ * An extended node of type {@link ExtendedNodeType#CONDITIONAL_JUMP}.
+ *
+ * <p><em>Important:</em> In the list of extended nodes, there should not be any labels that point
+ * to a conditional jump. Furthermore, the node directly ahead of any conditional jump has to be a
+ * {@link NodeWithExceptionsHolder} or {@link NodeHolder}, and the node held by that extended node
+ * is required to be of boolean type.
+ */
+@SuppressWarnings("nullness") // TODO
+class ConditionalJump extends ExtendedNode {
+
+  /** The true successor label. */
+  protected final Label trueSucc;
+  /** The false successor label. */
+  protected final Label falseSucc;
+
+  /** The true branch flow rule. */
+  protected FlowRule trueFlowRule;
+  /** The false branch flow rule. */
+  protected FlowRule falseFlowRule;
+
+  /**
+   * Construct a ConditionalJump.
+   *
+   * @param trueSucc true successor label
+   * @param falseSucc false successor label
+   */
+  public ConditionalJump(Label trueSucc, Label falseSucc) {
+    super(ExtendedNodeType.CONDITIONAL_JUMP);
+    assert trueSucc != null;
+    this.trueSucc = trueSucc;
+    assert falseSucc != null;
+    this.falseSucc = falseSucc;
+  }
+
+  public Label getThenLabel() {
+    return trueSucc;
+  }
+
+  public Label getElseLabel() {
+    return falseSucc;
+  }
+
+  /**
+   * Returns the true branch flow rule.
+   *
+   * @return the true branch flow rule
+   */
+  public FlowRule getTrueFlowRule() {
+    return trueFlowRule;
+  }
+
+  /**
+   * Returns the false branch flow rule.
+   *
+   * @return the false branch flow rule
+   */
+  public FlowRule getFalseFlowRule() {
+    return falseFlowRule;
+  }
+
+  /**
+   * Sets the true branch flow rule.
+   *
+   * @param rule the new true branch flow rule
+   */
+  public void setTrueFlowRule(FlowRule rule) {
+    trueFlowRule = rule;
+  }
+
+  /**
+   * Sets the false branch flow rule.
+   *
+   * @param rule the new false branch flow rule
+   */
+  public void setFalseFlowRule(FlowRule rule) {
+    falseFlowRule = rule;
+  }
+
+  /**
+   * Produce a string representation.
+   *
+   * @return a string representation
+   * @see org.checkerframework.dataflow.cfg.builder.CFGBuilder.PhaseOneResult#nodeToString
+   */
+  @Override
+  public String toString() {
+    return "TwoTargetConditionalJump(" + getThenLabel() + ", " + getElseLabel() + ")";
+  }
+
+  @Override
+  public String toStringDebug() {
+    return toString();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ExtendedNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ExtendedNode.java
new file mode 100644
index 0000000..9b00db4
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ExtendedNode.java
@@ -0,0 +1,105 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import org.checkerframework.dataflow.cfg.block.BlockImpl;
+import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * An extended node can be one of several things (depending on its {@code type}):
+ *
+ * <ul>
+ *   <li><em>NODE</em>: {@link CFGBuilder.NodeHolder}. An extended node of this type is just a
+ *       wrapper for a {@link Node} (that cannot throw exceptions).
+ *   <li><em>EXCEPTION_NODE</em>: {@link CFGBuilder.NodeWithExceptionsHolder}. A wrapper for a
+ *       {@link Node} which can throw exceptions. It contains a label for every possible exception
+ *       type the node might throw.
+ *   <li><em>UNCONDITIONAL_JUMP</em>: {@link CFGBuilder.UnconditionalJump}. An unconditional jump to
+ *       a label.
+ *   <li><em>TWO_TARGET_CONDITIONAL_JUMP</em>: {@link CFGBuilder.ConditionalJump}. A conditional
+ *       jump with two targets for both the 'then' and 'else' branch.
+ * </ul>
+ */
+@SuppressWarnings("nullness") // TODO
+abstract class ExtendedNode {
+
+  /** The basic block this extended node belongs to (as determined in phase two). */
+  protected BlockImpl block;
+
+  /** Type of this node. */
+  protected final ExtendedNodeType type;
+
+  /** Does this node terminate the execution? (e.g., "System.exit()") */
+  protected boolean terminatesExecution = false;
+
+  /**
+   * Create a new ExtendedNode.
+   *
+   * @param type the type of this node
+   */
+  protected ExtendedNode(ExtendedNodeType type) {
+    this.type = type;
+  }
+
+  /** Extended node types (description see above). */
+  public enum ExtendedNodeType {
+    NODE,
+    EXCEPTION_NODE,
+    UNCONDITIONAL_JUMP,
+    CONDITIONAL_JUMP
+  }
+
+  public ExtendedNodeType getType() {
+    return type;
+  }
+
+  public boolean getTerminatesExecution() {
+    return terminatesExecution;
+  }
+
+  public void setTerminatesExecution(boolean terminatesExecution) {
+    this.terminatesExecution = terminatesExecution;
+  }
+
+  /**
+   * Returns the node contained in this extended node (only applicable if the type is {@code NODE}
+   * or {@code EXCEPTION_NODE}).
+   *
+   * @return the node contained in this extended node (only applicable if the type is {@code NODE}
+   *     or {@code EXCEPTION_NODE})
+   */
+  public Node getNode() {
+    throw new Error("Do not call");
+  }
+
+  /**
+   * Returns the label associated with this extended node (only applicable if type is {@link
+   * ExtendedNodeType#CONDITIONAL_JUMP} or {@link ExtendedNodeType#UNCONDITIONAL_JUMP}).
+   *
+   * @return the label associated with this extended node (only applicable if type is {@link
+   *     ExtendedNodeType#CONDITIONAL_JUMP} or {@link ExtendedNodeType#UNCONDITIONAL_JUMP})
+   */
+  public Label getLabel() {
+    throw new Error("Do not call");
+  }
+
+  public BlockImpl getBlock() {
+    return block;
+  }
+
+  public void setBlock(BlockImpl b) {
+    this.block = b;
+  }
+
+  @Override
+  public String toString() {
+    throw new BugInCF("DO NOT CALL ExtendedNode.toString(). Write your own.");
+  }
+
+  /**
+   * Returns a verbose string representation of this, useful for debugging.
+   *
+   * @return a string representation of this
+   */
+  public abstract String toStringDebug();
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/Label.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/Label.java
new file mode 100644
index 0000000..78cdca2
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/Label.java
@@ -0,0 +1,34 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+/**
+ * A label is used to refer to other extended nodes using a mapping from labels to extended nodes.
+ * Labels get their names either from labeled statements in the source code or from internally
+ * generated unique names.
+ */
+class Label {
+  private static int uid = 0;
+
+  protected final String name;
+
+  public Label(String name) {
+    this.name = name;
+  }
+
+  public Label() {
+    this.name = uniqueName();
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+
+  /**
+   * Return a new unique label name that cannot be confused with a Java source code label.
+   *
+   * @return a new unique label name
+   */
+  private static String uniqueName() {
+    return "%L" + uid++;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/MissingEdge.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/MissingEdge.java
new file mode 100644
index 0000000..91d9faa
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/MissingEdge.java
@@ -0,0 +1,81 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store.FlowRule;
+import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl;
+
+/* --------------------------------------------------------- */
+/* Phase Two */
+/* --------------------------------------------------------- */
+
+/** Represents a missing edge that will be added later. */
+class MissingEdge {
+  /** The source of the edge. */
+  final SingleSuccessorBlockImpl source;
+  /** The index (target?) of the edge. Null means go to exceptional exit. */
+  final @Nullable Integer index;
+  /** The cause exception type, for an exceptional edge; otherwise null. */
+  final @Nullable TypeMirror cause;
+
+  /** The flow rule for this edge. */
+  final @Nullable FlowRule flowRule;
+
+  /**
+   * Create a new MissingEdge.
+   *
+   * @param source the source of the edge
+   * @param index the index (target?) of the edge
+   */
+  public MissingEdge(SingleSuccessorBlockImpl source, int index) {
+    this(source, index, null, FlowRule.EACH_TO_EACH);
+  }
+
+  /**
+   * Create a new MissingEdge.
+   *
+   * @param source the source of the edge
+   * @param index the index (target?) of the edge
+   * @param flowRule the flow rule for this edge
+   */
+  public MissingEdge(SingleSuccessorBlockImpl source, int index, FlowRule flowRule) {
+    this(source, index, null, flowRule);
+  }
+
+  /**
+   * Create a new MissingEdge.
+   *
+   * @param source the source of the edge
+   * @param index the index (target?) of the edge; null means go to exceptional exit
+   * @param cause the cause exception type, for an exceptional edge; otherwise null
+   */
+  public MissingEdge(
+      SingleSuccessorBlockImpl source, @Nullable Integer index, @Nullable TypeMirror cause) {
+    this(source, index, cause, FlowRule.EACH_TO_EACH);
+  }
+
+  /**
+   * Create a new MissingEdge.
+   *
+   * @param source the source of the edge
+   * @param index the index (target?) of the edge; null means go to exceptional exit
+   * @param cause the cause exception type, for an exceptional edge; otherwise null
+   * @param flowRule the flow rule for this edge
+   */
+  public MissingEdge(
+      SingleSuccessorBlockImpl source,
+      @Nullable Integer index,
+      @Nullable TypeMirror cause,
+      FlowRule flowRule) {
+    assert (index != null) || (cause != null);
+    this.source = source;
+    this.index = index;
+    this.cause = cause;
+    this.flowRule = flowRule;
+  }
+
+  @Override
+  public String toString() {
+    return "MissingEdge(" + source + ", " + index + ", " + cause + ")";
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeHolder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeHolder.java
new file mode 100644
index 0000000..7654c29
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeHolder.java
@@ -0,0 +1,36 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType;
+import org.checkerframework.dataflow.cfg.node.Node;
+
+/** An extended node of type {@code NODE}. */
+class NodeHolder extends ExtendedNode {
+
+  /** The node to hold. */
+  protected final Node node;
+
+  /**
+   * Construct a NodeHolder for the given Node.
+   *
+   * @param node the node to hold
+   */
+  public NodeHolder(Node node) {
+    super(ExtendedNodeType.NODE);
+    this.node = node;
+  }
+
+  @Override
+  public Node getNode() {
+    return node;
+  }
+
+  @Override
+  public String toString() {
+    return "NodeHolder(" + node + ")";
+  }
+
+  @Override
+  public String toStringDebug() {
+    return "NodeHolder(" + node.toStringDebug() + ")";
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeWithExceptionsHolder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeWithExceptionsHolder.java
new file mode 100644
index 0000000..54fb089
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeWithExceptionsHolder.java
@@ -0,0 +1,63 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType;
+import org.checkerframework.dataflow.cfg.node.Node;
+
+/** An extended node of type {@code EXCEPTION_NODE}. */
+class NodeWithExceptionsHolder extends ExtendedNode {
+
+  /** The node to hold. */
+  protected final Node node;
+
+  /**
+   * Map from exception type to labels of successors that may be reached as a result of that
+   * exception.
+   */
+  protected final Map<TypeMirror, Set<Label>> exceptions;
+
+  /**
+   * Construct a NodeWithExceptionsHolder for the given node and exceptions.
+   *
+   * @param node the node to hold
+   * @param exceptions the exceptions to hold
+   */
+  public NodeWithExceptionsHolder(Node node, Map<TypeMirror, Set<Label>> exceptions) {
+    super(ExtendedNodeType.EXCEPTION_NODE);
+    this.node = node;
+    this.exceptions = exceptions;
+  }
+
+  /**
+   * Get the exceptions for the node.
+   *
+   * @return exceptions for the node
+   */
+  public Map<TypeMirror, Set<Label>> getExceptions() {
+    return exceptions;
+  }
+
+  @Override
+  public Node getNode() {
+    return node;
+  }
+
+  @Override
+  public String toString() {
+    return "NodeWithExceptionsHolder(" + node + ")";
+  }
+
+  @Override
+  public String toStringDebug() {
+    StringJoiner sj = new StringJoiner(String.format("%n    "));
+    sj.add("NodeWithExceptionsHolder(" + node.toStringDebug() + ") {");
+    for (Map.Entry<TypeMirror, Set<Label>> entry : exceptions.entrySet()) {
+      sj.add(entry.getKey() + " => " + entry.getValue());
+    }
+    sj.add("}");
+    return sj.toString();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/PhaseOneResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/PhaseOneResult.java
new file mode 100644
index 0000000..8cf1399
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/PhaseOneResult.java
@@ -0,0 +1,145 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.UnaryTree;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+
+/* --------------------------------------------------------- */
+/* Phase One */
+/* --------------------------------------------------------- */
+
+/**
+ * A wrapper object to pass around the result of phase one. For a documentation of the fields see
+ * {@link CFGTranslationPhaseOne}.
+ */
+public class PhaseOneResult {
+
+  final IdentityHashMap<Tree, Set<Node>> treeLookupMap;
+  final IdentityHashMap<Tree, Set<Node>> convertedTreeLookupMap;
+  final IdentityHashMap<UnaryTree, AssignmentNode> unaryAssignNodeLookupMap;
+  final UnderlyingAST underlyingAST;
+  final Map<Label, Integer> bindings;
+  final ArrayList<ExtendedNode> nodeList;
+  final Set<Integer> leaders;
+  final List<ReturnNode> returnNodes;
+  final Label regularExitLabel;
+  final Label exceptionalExitLabel;
+  final List<ClassTree> declaredClasses;
+  final List<LambdaExpressionTree> declaredLambdas;
+
+  public PhaseOneResult(
+      UnderlyingAST underlyingAST,
+      IdentityHashMap<Tree, Set<Node>> treeLookupMap,
+      IdentityHashMap<Tree, Set<Node>> convertedTreeLookupMap,
+      IdentityHashMap<UnaryTree, AssignmentNode> unaryAssignNodeLookupMap,
+      ArrayList<ExtendedNode> nodeList,
+      Map<Label, Integer> bindings,
+      Set<Integer> leaders,
+      List<ReturnNode> returnNodes,
+      Label regularExitLabel,
+      Label exceptionalExitLabel,
+      List<ClassTree> declaredClasses,
+      List<LambdaExpressionTree> declaredLambdas) {
+    this.underlyingAST = underlyingAST;
+    this.treeLookupMap = treeLookupMap;
+    this.convertedTreeLookupMap = convertedTreeLookupMap;
+    this.unaryAssignNodeLookupMap = unaryAssignNodeLookupMap;
+    this.nodeList = nodeList;
+    this.bindings = bindings;
+    this.leaders = leaders;
+    this.returnNodes = returnNodes;
+    this.regularExitLabel = regularExitLabel;
+    this.exceptionalExitLabel = exceptionalExitLabel;
+    this.declaredClasses = declaredClasses;
+    this.declaredLambdas = declaredLambdas;
+  }
+
+  @Override
+  public String toString() {
+    StringJoiner sj = new StringJoiner(System.lineSeparator());
+    for (ExtendedNode n : nodeList) {
+      sj.add(nodeToString(n));
+    }
+    return sj.toString();
+  }
+
+  protected String nodeToString(ExtendedNode n) {
+    if (n.getType() == ExtendedNodeType.CONDITIONAL_JUMP) {
+      ConditionalJump t = (ConditionalJump) n;
+      return "TwoTargetConditionalJump("
+          + resolveLabel(t.getThenLabel())
+          + ", "
+          + resolveLabel(t.getElseLabel())
+          + ")";
+    } else if (n.getType() == ExtendedNodeType.UNCONDITIONAL_JUMP) {
+      return "UnconditionalJump(" + resolveLabel(n.getLabel()) + ")";
+    } else {
+      return n.toString();
+    }
+  }
+
+  private String resolveLabel(Label label) {
+    Integer index = bindings.get(label);
+    if (index == null) {
+      return "unbound label: " + label;
+    }
+    return nodeToString(nodeList.get(index));
+  }
+
+  /**
+   * Returns a representation of a map, one entry per line.
+   *
+   * @param <K> the key type of the map
+   * @param <V> the value type of the map
+   * @param map a map
+   * @return a representation of a map, one entry per line
+   */
+  private <K, V> String mapToString(Map<K, V> map) {
+    if (map.isEmpty()) {
+      return "{}";
+    }
+    StringJoiner result =
+        new StringJoiner(
+            String.format("%n    "), String.format("{%n    "), String.format("%n    }"));
+    for (Map.Entry<K, V> entry : map.entrySet()) {
+      result.add(entry.getKey() + " => " + entry.getValue());
+    }
+    return result.toString();
+  }
+
+  /**
+   * Returns a verbose string representation of this, useful for debugging.
+   *
+   * @return a string representation of this
+   */
+  public String toStringDebug() {
+    StringJoiner result =
+        new StringJoiner(
+            String.format("%n  "), String.format("PhaseOneResult{%n  "), String.format("%n  }"));
+    result.add("treeLookupMap=" + mapToString(treeLookupMap));
+    result.add("convertedTreeLookupMap=" + mapToString(convertedTreeLookupMap));
+    result.add("unaryAssignNodeLookupMap=" + mapToString(unaryAssignNodeLookupMap));
+    result.add("underlyingAST=" + underlyingAST);
+    result.add("bindings=" + bindings);
+    result.add("nodeList=" + CFGBuilder.extendedNodeCollectionToStringDebug(nodeList));
+    result.add("leaders=" + leaders);
+    result.add("returnNodes=" + Node.nodeCollectionToString(returnNodes));
+    result.add("regularExitLabel=" + regularExitLabel);
+    result.add("exceptionalExitLabel=" + exceptionalExitLabel);
+    result.add("declaredClasses=" + declaredClasses);
+    result.add("declaredLambdas=" + declaredLambdas);
+    return result.toString();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java
new file mode 100644
index 0000000..c4af32b
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java
@@ -0,0 +1,14 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import javax.lang.model.type.TypeMirror;
+
+/** A tuple with 4 named elements. */
+interface TreeInfo {
+  boolean isBoxed();
+
+  boolean isNumeric();
+
+  boolean isBoolean();
+
+  TypeMirror unboxedType();
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryCatchFrame.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryCatchFrame.java
new file mode 100644
index 0000000..fed6e31
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryCatchFrame.java
@@ -0,0 +1,118 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.UnionType;
+import javax.lang.model.util.Types;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * A TryCatchFrame contains an ordered list of catch labels that apply to exceptions with specific
+ * types.
+ */
+class TryCatchFrame implements TryFrame {
+  /** The Types utilities. */
+  protected final Types types;
+
+  /** An ordered list of pairs because catch blocks are ordered. */
+  protected final List<Pair<TypeMirror, Label>> catchLabels;
+
+  /**
+   * Construct a TryCatchFrame.
+   *
+   * @param types the Types utilities
+   * @param catchLabels the catch labels
+   */
+  public TryCatchFrame(Types types, List<Pair<TypeMirror, Label>> catchLabels) {
+    this.types = types;
+    this.catchLabels = catchLabels;
+  }
+
+  @Override
+  public String toString() {
+    if (this.catchLabels.isEmpty()) {
+      return "TryCatchFrame: no catch labels.";
+    } else {
+      StringJoiner sb = new StringJoiner(System.lineSeparator(), "TryCatchFrame: ", "");
+      for (Pair<TypeMirror, Label> ptml : this.catchLabels) {
+        sb.add(ptml.first.toString() + " -> " + ptml.second.toString());
+      }
+      return sb.toString();
+    }
+  }
+
+  /**
+   * Given a type of thrown exception, add the set of possible control flow successor {@link Label}s
+   * to the argument set. Return true if the exception is known to be caught by one of those labels
+   * and false if it may propagate still further.
+   */
+  @Override
+  public boolean possibleLabels(TypeMirror thrown, Set<Label> labels) {
+    // A conservative approach would be to say that every catch block might execute for any thrown
+    // exception, but we try to do better.
+    //
+    // We rely on several assumptions that seem to hold as of Java 7.
+    // 1) An exception parameter in a catch block must be either a declared type or a union composed
+    //    of declared types, all of which are subtypes of Throwable.
+    // 2) A thrown type must either be a declared type or a variable that extends a declared type,
+    //    which is a subtype of Throwable.
+    //
+    // Under those assumptions, if the thrown type (or its bound) is a subtype of the caught type
+    // (or one of its alternatives), then the catch block must apply and none of the later ones can
+    // apply.
+    // Otherwise, if the thrown type (or its bound) is a supertype of the caught type (or one of its
+    // alternatives), then the catch block may apply, but so may later ones.
+    // Otherwise, the thrown type and the caught type are unrelated declared types, so they do not
+    // overlap on any non-null value.
+
+    while (!(thrown instanceof DeclaredType)) {
+      assert thrown instanceof TypeVariable : "thrown type must be a variable or a declared type";
+      thrown = ((TypeVariable) thrown).getUpperBound();
+    }
+    DeclaredType declaredThrown = (DeclaredType) thrown;
+    assert thrown != null : "thrown type must be bounded by a declared type";
+
+    for (Pair<TypeMirror, Label> pair : catchLabels) {
+      TypeMirror caught = pair.first;
+      boolean canApply = false;
+
+      if (caught.getKind() == TypeKind.DECLARED) {
+        DeclaredType declaredCaught = (DeclaredType) caught;
+        if (types.isSubtype(declaredThrown, declaredCaught)) {
+          // No later catch blocks can apply.
+          labels.add(pair.second);
+          return true;
+        } else if (types.isSubtype(declaredCaught, declaredThrown)) {
+          canApply = true;
+        }
+      } else {
+        assert caught.getKind() == TypeKind.UNION
+            : "caught type must be a union or a declared type";
+        UnionType caughtUnion = (UnionType) caught;
+        for (TypeMirror alternative : caughtUnion.getAlternatives()) {
+          assert alternative.getKind() == TypeKind.DECLARED
+              : "alternatives of an caught union type must be declared types";
+          DeclaredType declaredAlt = (DeclaredType) alternative;
+          if (types.isSubtype(declaredThrown, declaredAlt)) {
+            // No later catch blocks can apply.
+            labels.add(pair.second);
+            return true;
+          } else if (types.isSubtype(declaredAlt, declaredThrown)) {
+            canApply = true;
+          }
+        }
+      }
+
+      if (canApply) {
+        labels.add(pair.second);
+      }
+    }
+
+    return false;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryFinallyFrame.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryFinallyFrame.java
new file mode 100644
index 0000000..7ba3f6e
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryFinallyFrame.java
@@ -0,0 +1,30 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import java.util.Set;
+import javax.lang.model.type.TypeMirror;
+
+/** A TryFinallyFrame applies to exceptions of any type. */
+class TryFinallyFrame implements TryFrame {
+  /** The finally label. */
+  protected final Label finallyLabel;
+
+  /**
+   * Construct a TryFinallyFrame.
+   *
+   * @param finallyLabel finally label
+   */
+  public TryFinallyFrame(Label finallyLabel) {
+    this.finallyLabel = finallyLabel;
+  }
+
+  @Override
+  public String toString() {
+    return "TryFinallyFrame: finallyLabel: " + finallyLabel;
+  }
+
+  @Override
+  public boolean possibleLabels(TypeMirror thrown, Set<Label> labels) {
+    labels.add(finallyLabel);
+    return true;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryFinallyScopeCell.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryFinallyScopeCell.java
new file mode 100644
index 0000000..749e6a2
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryFinallyScopeCell.java
@@ -0,0 +1,38 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+
+/** Storage cell for a single Label, with tracking whether it was accessed. */
+class TryFinallyScopeCell {
+  private @MonotonicNonNull Label label;
+  private boolean accessed;
+
+  protected TryFinallyScopeCell() {
+    this.accessed = false;
+  }
+
+  protected TryFinallyScopeCell(Label label) {
+    assert label != null;
+    this.label = label;
+    this.accessed = false;
+  }
+
+  public Label accessLabel() {
+    if (label == null) {
+      label = new Label();
+    }
+    accessed = true;
+    return label;
+  }
+
+  public Label peekLabel() {
+    if (label == null) {
+      throw new Error("called peekLabel prematurely");
+    }
+    return label;
+  }
+
+  public boolean wasAccessed() {
+    return accessed;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryFinallyScopeMap.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryFinallyScopeMap.java
new file mode 100644
index 0000000..4db2e33
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryFinallyScopeMap.java
@@ -0,0 +1,50 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.lang.model.element.Name;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A map that keeps track of new labels added within a try block. For names that are outside of the
+ * try block, the finally label is returned. This ensures that a finally block is executed when
+ * control flows outside of the try block.
+ */
+@SuppressWarnings("serial")
+class TryFinallyScopeMap extends HashMap<Name, Label> {
+  /** New labels within a try block that were added by this implementation. */
+  private final Map<Name, Label> accessedNames;
+
+  /** Create a new TryFinallyScopeMap. */
+  protected TryFinallyScopeMap() {
+    this.accessedNames = new LinkedHashMap<>(2);
+  }
+
+  @Override
+  public Label get(@Nullable Object key) {
+    if (key == null) {
+      throw new IllegalArgumentException();
+    }
+    if (super.containsKey(key)) {
+      return super.get(key);
+    } else {
+      if (accessedNames.containsKey(key)) {
+        return accessedNames.get(key);
+      }
+      Label l = new Label();
+      accessedNames.put((Name) key, l);
+      return l;
+    }
+  }
+
+  @Override
+  @SuppressWarnings("keyfor:contracts.conditional.postcondition") // get adds everything
+  public boolean containsKey(@Nullable Object key) {
+    return true;
+  }
+
+  public Map<Name, Label> getAccessedNames() {
+    return accessedNames;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryFrame.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryFrame.java
new file mode 100644
index 0000000..c808243
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryFrame.java
@@ -0,0 +1,17 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import java.util.Set;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A TryFrame takes a thrown exception type and maps it to a set of possible control-flow
+ * successors.
+ */
+interface TryFrame {
+  /**
+   * Given a type of thrown exception, add the set of possible control flow successor {@link Label}s
+   * to the argument set. Return true if the exception is known to be caught by one of those labels
+   * and false if it may propagate still further.
+   */
+  public boolean possibleLabels(TypeMirror thrown, Set<Label> labels);
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryStack.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryStack.java
new file mode 100644
index 0000000..f22db4e
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryStack.java
@@ -0,0 +1,75 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import java.util.ArrayDeque;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.dataflow.util.MostlySingleton;
+
+/**
+ * An exception stack represents the set of all try-catch blocks in effect at a given point in a
+ * program. It maps an exception type to a set of Labels and it maps a block exit (via return or
+ * fall-through) to a single Label.
+ */
+class TryStack {
+  /** The exit label. */
+  protected final Label exitLabel;
+  /** The try frames. */
+  protected final ArrayDeque<TryFrame> frames;
+
+  /**
+   * Construct a TryStack.
+   *
+   * @param exitLabel exit label
+   */
+  public TryStack(Label exitLabel) {
+    this.exitLabel = exitLabel;
+    this.frames = new ArrayDeque<>();
+  }
+
+  /**
+   * Push a new frame.
+   *
+   * @param frame the frame to push
+   */
+  public void pushFrame(TryFrame frame) {
+    frames.addFirst(frame);
+  }
+
+  /** Pop a frame. */
+  public void popFrame() {
+    frames.removeFirst();
+  }
+
+  /**
+   * Returns the set of possible {@link Label}s where control may transfer when an exception of the
+   * given type is thrown.
+   *
+   * @param thrown an exception
+   * @return where control may transfer when {@code thrown} is thrown
+   */
+  public Set<Label> possibleLabels(TypeMirror thrown) {
+    // Work up from the innermost frame until the exception is known to be caught.
+    Set<Label> labels = new MostlySingleton<>();
+    for (TryFrame frame : frames) {
+      if (frame.possibleLabels(thrown, labels)) {
+        return labels;
+      }
+    }
+    labels.add(exitLabel);
+    return labels;
+  }
+
+  @Override
+  public String toString() {
+    StringJoiner sj = new StringJoiner(System.lineSeparator());
+    sj.add("TryStack: exitLabel: " + this.exitLabel);
+    if (this.frames.isEmpty()) {
+      sj.add("No TryFrames.");
+    }
+    for (TryFrame tf : this.frames) {
+      sj.add(tf.toString());
+    }
+    return sj.toString();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/UnconditionalJump.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/UnconditionalJump.java
new file mode 100644
index 0000000..fdc1632
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/UnconditionalJump.java
@@ -0,0 +1,66 @@
+package org.checkerframework.dataflow.cfg.builder;
+
+import org.checkerframework.dataflow.analysis.Store.FlowRule;
+import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType;
+
+/** An extended node of type {@link ExtendedNodeType#UNCONDITIONAL_JUMP}. */
+class UnconditionalJump extends ExtendedNode {
+
+  /** The jump target label. */
+  protected final Label jumpTarget;
+
+  /** The flow rule for this edge. */
+  protected final FlowRule flowRule;
+
+  /**
+   * Construct an UnconditionalJump.
+   *
+   * @param jumpTarget the jump target label
+   */
+  public UnconditionalJump(Label jumpTarget) {
+    this(jumpTarget, FlowRule.EACH_TO_EACH);
+  }
+
+  /**
+   * Construct an UnconditionalJump, specifying its flow rule.
+   *
+   * @param jumpTarget the jump target label
+   * @param flowRule the flow rule for this edge
+   */
+  public UnconditionalJump(Label jumpTarget, FlowRule flowRule) {
+    super(ExtendedNodeType.UNCONDITIONAL_JUMP);
+    assert jumpTarget != null;
+    this.jumpTarget = jumpTarget;
+    this.flowRule = flowRule;
+  }
+
+  @Override
+  public Label getLabel() {
+    return jumpTarget;
+  }
+
+  /**
+   * Returns the flow rule for this edge.
+   *
+   * @return the flow rule for this edge
+   */
+  public FlowRule getFlowRule() {
+    return flowRule;
+  }
+
+  /**
+   * Produce a string representation.
+   *
+   * @return a string representation
+   * @see org.checkerframework.dataflow.cfg.builder.CFGBuilder.PhaseOneResult#nodeToString
+   */
+  @Override
+  public String toString() {
+    return "JumpMarker(" + getLabel() + ")";
+  }
+
+  @Override
+  public String toStringDebug() {
+    return toString();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AbstractNodeVisitor.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AbstractNodeVisitor.java
new file mode 100644
index 0000000..8c7dad0
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AbstractNodeVisitor.java
@@ -0,0 +1,383 @@
+package org.checkerframework.dataflow.cfg.node;
+
+/**
+ * A default implementation of the node visitor interface. The class introduces several 'summary'
+ * methods, that can be overridden to change the behavior of several related visit methods at once.
+ * An example is the {@code visitValueLiteral} method, that is called for every {@link
+ * ValueLiteralNode}.
+ *
+ * <p>This is useful to implement a visitor that performs the same operation (e.g., nothing) for
+ * most {@link Node}s and only has special behavior for a few.
+ *
+ * @param <R> return type of the visitor
+ * @param <P> parameter type of the visitor
+ */
+public abstract class AbstractNodeVisitor<R, P> implements NodeVisitor<R, P> {
+
+  public abstract R visitNode(Node n, P p);
+
+  public R visitValueLiteral(ValueLiteralNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  // Literals
+  @Override
+  public R visitShortLiteral(ShortLiteralNode n, P p) {
+    return visitValueLiteral(n, p);
+  }
+
+  @Override
+  public R visitIntegerLiteral(IntegerLiteralNode n, P p) {
+    return visitValueLiteral(n, p);
+  }
+
+  @Override
+  public R visitLongLiteral(LongLiteralNode n, P p) {
+    return visitValueLiteral(n, p);
+  }
+
+  @Override
+  public R visitFloatLiteral(FloatLiteralNode n, P p) {
+    return visitValueLiteral(n, p);
+  }
+
+  @Override
+  public R visitDoubleLiteral(DoubleLiteralNode n, P p) {
+    return visitValueLiteral(n, p);
+  }
+
+  @Override
+  public R visitBooleanLiteral(BooleanLiteralNode n, P p) {
+    return visitValueLiteral(n, p);
+  }
+
+  @Override
+  public R visitCharacterLiteral(CharacterLiteralNode n, P p) {
+    return visitValueLiteral(n, p);
+  }
+
+  @Override
+  public R visitStringLiteral(StringLiteralNode n, P p) {
+    return visitValueLiteral(n, p);
+  }
+
+  @Override
+  public R visitNullLiteral(NullLiteralNode n, P p) {
+    return visitValueLiteral(n, p);
+  }
+
+  // Unary operations
+  @Override
+  public R visitNumericalMinus(NumericalMinusNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitNumericalPlus(NumericalPlusNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitBitwiseComplement(BitwiseComplementNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitNullChk(NullChkNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  // Binary operations
+  @Override
+  public R visitStringConcatenate(StringConcatenateNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitNumericalAddition(NumericalAdditionNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitNumericalSubtraction(NumericalSubtractionNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitNumericalMultiplication(NumericalMultiplicationNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitIntegerDivision(IntegerDivisionNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitFloatingDivision(FloatingDivisionNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitIntegerRemainder(IntegerRemainderNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitFloatingRemainder(FloatingRemainderNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitLeftShift(LeftShiftNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitSignedRightShift(SignedRightShiftNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitUnsignedRightShift(UnsignedRightShiftNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitBitwiseAnd(BitwiseAndNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitBitwiseOr(BitwiseOrNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitBitwiseXor(BitwiseXorNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  // Compound assignments
+  @Override
+  public R visitStringConcatenateAssignment(StringConcatenateAssignmentNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  // Comparison operations
+  @Override
+  public R visitLessThan(LessThanNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitLessThanOrEqual(LessThanOrEqualNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitGreaterThan(GreaterThanNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitGreaterThanOrEqual(GreaterThanOrEqualNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitEqualTo(EqualToNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitNotEqual(NotEqualNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  // Conditional operations
+  @Override
+  public R visitConditionalAnd(ConditionalAndNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitConditionalOr(ConditionalOrNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitConditionalNot(ConditionalNotNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitTernaryExpression(TernaryExpressionNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitAssignment(AssignmentNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitLocalVariable(LocalVariableNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitVariableDeclaration(VariableDeclarationNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitFieldAccess(FieldAccessNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitMethodAccess(MethodAccessNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitArrayAccess(ArrayAccessNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  public R visitThis(ThisNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitImplicitThis(ImplicitThisNode n, P p) {
+    return visitThis(n, p);
+  }
+
+  @Override
+  public R visitExplicitThis(ExplicitThisNode n, P p) {
+    return visitThis(n, p);
+  }
+
+  @Override
+  public R visitSuper(SuperNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitReturn(ReturnNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitLambdaResultExpression(LambdaResultExpressionNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitStringConversion(StringConversionNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitWideningConversion(WideningConversionNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitNarrowingConversion(NarrowingConversionNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitInstanceOf(InstanceOfNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitTypeCast(TypeCastNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  // Statements
+  @Override
+  public R visitAssertionError(AssertionErrorNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitSynchronized(SynchronizedNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitThrow(ThrowNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  // Cases
+  @Override
+  public R visitCase(CaseNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  // Method and constructor invocations
+  @Override
+  public R visitMethodInvocation(MethodInvocationNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitObjectCreation(ObjectCreationNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitClassDeclaration(ClassDeclarationNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitMemberReference(FunctionalInterfaceNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitArrayCreation(ArrayCreationNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  // Type, package and class names
+  @Override
+  public R visitArrayType(ArrayTypeNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitPrimitiveType(PrimitiveTypeNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitClassName(ClassNameNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  @Override
+  public R visitPackageName(PackageNameNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  // Parameterized types
+  @Override
+  public R visitParameterizedType(ParameterizedTypeNode n, P p) {
+    return visitNode(n, p);
+  }
+
+  // Marker nodes
+  @Override
+  public R visitMarker(MarkerNode n, P p) {
+    return visitNode(n, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayAccessNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayAccessNode.java
new file mode 100644
index 0000000..6b162db
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayAccessNode.java
@@ -0,0 +1,123 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.Tree;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for an array access:
+ *
+ * <pre>
+ *   <em>arrayref</em> [ <em>index</em> ]
+ * </pre>
+ *
+ * We allow array accesses without corresponding AST {@link Tree}s.
+ */
+public class ArrayAccessNode extends Node {
+
+  /** The corresponding ArrayAccessTree. */
+  protected final Tree tree;
+
+  /** The array expression being accessed. */
+  protected final Node array;
+
+  /** The index expresssion used to access the array. */
+  protected final Node index;
+
+  /**
+   * If this ArrayAccessNode is a node for an array desugared from an enhanced for loop, then the
+   * {@code arrayExpression} field is the expression in the for loop, e.g., {@code arr} in {@code
+   * for(Object o: arr}.
+   */
+  protected @Nullable ExpressionTree arrayExpression;
+
+  /**
+   * Create an ArrayAccessNode.
+   *
+   * @param t tree for the array access
+   * @param array the node for the array expression being accessed
+   * @param index the node for the index used to access the array
+   */
+  public ArrayAccessNode(Tree t, Node array, Node index) {
+    super(TreeUtils.typeOf(t));
+    assert t instanceof ArrayAccessTree;
+    this.tree = t;
+    this.array = array;
+    this.index = index;
+  }
+
+  /**
+   * If this ArrayAccessNode is a node for an array desugared from an enhanced for loop, then return
+   * the expression in the for loop, e.g., {@code arr} in {@code for(Object o: arr}. Otherwise,
+   * return null.
+   *
+   * @return the array expression, or null if this is not an array desugared from an enhanced for
+   *     loop
+   */
+  public @Nullable ExpressionTree getArrayExpression() {
+    return arrayExpression;
+  }
+
+  /**
+   * Set the array expression from a for loop.
+   *
+   * @param arrayExpression array expression
+   * @see #getArrayExpression()
+   */
+  public void setArrayExpression(@Nullable ExpressionTree arrayExpression) {
+    this.arrayExpression = arrayExpression;
+  }
+
+  /**
+   * Get the node that represents the array expression being accessed.
+   *
+   * @return the array expression node
+   */
+  public Node getArray() {
+    return array;
+  }
+
+  public Node getIndex() {
+    return index;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitArrayAccess(this, p);
+  }
+
+  @Override
+  public String toString() {
+    String base = getArray().toString() + "[" + getIndex() + "]";
+    return base;
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ArrayAccessNode)) {
+      return false;
+    }
+    ArrayAccessNode other = (ArrayAccessNode) obj;
+    return getArray().equals(other.getArray()) && getIndex().equals(other.getIndex());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getArray(), getIndex());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Arrays.asList(getArray(), getIndex());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java
new file mode 100644
index 0000000..69951d8
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java
@@ -0,0 +1,112 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.Tree;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * A node for new array creation.
+ *
+ * <pre>
+ *   <em>new type[1][2]</em>
+ *   <em>new type[] = { expr1, expr2, ... }</em>
+ * </pre>
+ */
+public class ArrayCreationNode extends Node {
+
+  /** The tree is null when an array is created for variable arity method calls. */
+  protected final @Nullable NewArrayTree tree;
+
+  /**
+   * The length of this list is the number of dimensions in the array. Each element is the size of
+   * the given dimension. It can be empty if initializers is non-empty, as in {@code new SomeType[]
+   * = { expr1, expr2, ... }}.
+   */
+  protected final List<Node> dimensions;
+
+  protected final List<Node> initializers;
+
+  public ArrayCreationNode(
+      @Nullable NewArrayTree tree,
+      TypeMirror type,
+      List<Node> dimensions,
+      List<Node> initializers) {
+    super(type);
+    this.tree = tree;
+    this.dimensions = dimensions;
+    this.initializers = initializers;
+  }
+
+  public List<Node> getDimensions() {
+    return dimensions;
+  }
+
+  public Node getDimension(int i) {
+    return dimensions.get(i);
+  }
+
+  public List<Node> getInitializers() {
+    return initializers;
+  }
+
+  public Node getInitializer(int i) {
+    return initializers.get(i);
+  }
+
+  @Override
+  public @Nullable Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitArrayCreation(this, p);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("new " + type);
+    if (!dimensions.isEmpty()) {
+      sb.append(" (");
+      sb.append(StringsPlume.join(", ", dimensions));
+      sb.append(")");
+    }
+    if (!initializers.isEmpty()) {
+      sb.append(" = {");
+      sb.append(StringsPlume.join(", ", initializers));
+      sb.append("}");
+    }
+    return sb.toString();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ArrayCreationNode)) {
+      return false;
+    }
+    ArrayCreationNode other = (ArrayCreationNode) obj;
+
+    return getDimensions().equals(other.getDimensions())
+        && getInitializers().equals(other.getInitializers());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(dimensions, initializers);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    ArrayList<Node> list = new ArrayList<>(dimensions.size() + initializers.size());
+    list.addAll(dimensions);
+    list.addAll(initializers);
+    return list;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java
new file mode 100644
index 0000000..f0afdcf
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java
@@ -0,0 +1,63 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node representing a array type used in an expression such as a field access.
+ *
+ * <p><em>type</em> .class
+ */
+public class ArrayTypeNode extends Node {
+
+  protected final ArrayTypeTree tree;
+
+  /** For Types.isSameType. */
+  protected final Types types;
+
+  public ArrayTypeNode(ArrayTypeTree tree, Types types) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+    this.types = types;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitArrayType(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return tree.toString();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ArrayTypeNode)) {
+      return false;
+    }
+    ArrayTypeNode other = (ArrayTypeNode) obj;
+    return types.isSameType(getType(), other.getType());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getType());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java
new file mode 100644
index 0000000..79d85b4
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java
@@ -0,0 +1,76 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the {@link AssertionError} when an assertion fails.
+ *
+ * <pre>
+ *   assert <em>condition</em> : <em>detail</em> ;
+ * </pre>
+ */
+public class AssertionErrorNode extends Node {
+
+  protected final Tree tree;
+  protected final Node condition;
+  protected final Node detail;
+
+  public AssertionErrorNode(Tree tree, Node condition, Node detail, TypeMirror type) {
+    // TODO: Find out the correct "type" for statements.
+    // Is it TypeKind.NONE?
+    super(type);
+    assert tree.getKind() == Kind.ASSERT;
+    this.tree = tree;
+    this.condition = condition;
+    this.detail = detail;
+  }
+
+  public Node getCondition() {
+    return condition;
+  }
+
+  public Node getDetail() {
+    return detail;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitAssertionError(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "AssertionError(" + getDetail() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof AssertionErrorNode)) {
+      return false;
+    }
+    AssertionErrorNode other = (AssertionErrorNode) obj;
+    return Objects.equals(getCondition(), other.getCondition())
+        && Objects.equals(getDetail(), other.getDetail());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getCondition(), getDetail());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Arrays.asList(getCondition(), getDetail());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentContext.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentContext.java
new file mode 100644
index 0000000..d629159
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentContext.java
@@ -0,0 +1,124 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * An assignment context is the left-hand side of an assignment or pseudo-assignment.
+ * Pseudo-assignments include regular Java assignments, method calls (for all the actual parameters
+ * which get assigned to their formal parameters), and method return statements.
+ *
+ * <p>The main use of {@link AssignmentContext} is to be able to get the declared type of the
+ * left-hand side of the assignment for proper type-refinement.
+ */
+public abstract class AssignmentContext {
+
+  /** An assignment context for an assignment 'lhs = rhs'. */
+  public static class AssignmentLhsContext extends AssignmentContext {
+
+    protected final Node node;
+
+    public AssignmentLhsContext(Node node) {
+      this.node = node;
+    }
+
+    @Override
+    public @Nullable Element getElementForType() {
+      Tree tree = node.getTree();
+      if (tree == null) {
+        return null;
+      } else if (tree instanceof ExpressionTree) {
+        return TreeUtils.elementFromUse((ExpressionTree) tree);
+      } else if (tree instanceof VariableTree) {
+        return TreeUtils.elementFromDeclaration((VariableTree) tree);
+      } else {
+        throw new Error("unexpected tree");
+      }
+    }
+
+    @Override
+    public @Nullable Tree getContextTree() {
+      return node.getTree();
+    }
+  }
+
+  /** An assignment context for a method parameter. */
+  public static class MethodParameterContext extends AssignmentContext {
+
+    protected final ExecutableElement method;
+    protected final int paramNum;
+
+    public MethodParameterContext(ExecutableElement method, int paramNum) {
+      this.method = method;
+      this.paramNum = paramNum;
+    }
+
+    @Override
+    public Element getElementForType() {
+      return method.getParameters().get(paramNum);
+    }
+
+    @Override
+    public @Nullable Tree getContextTree() {
+      // TODO: what is the right assignment context? We might not have
+      // a tree for the invoked method.
+      return null;
+    }
+  }
+
+  /** An assignment context for method return statements. */
+  public static class MethodReturnContext extends AssignmentContext {
+
+    protected final ExecutableElement method;
+    protected final Tree ret;
+
+    public MethodReturnContext(MethodTree method) {
+      this.method = TreeUtils.elementFromDeclaration(method);
+      this.ret = method.getReturnType();
+    }
+
+    @Override
+    public Element getElementForType() {
+      return method;
+    }
+
+    @Override
+    public Tree getContextTree() {
+      return ret;
+    }
+  }
+
+  /** An assignment context for lambda return statements. */
+  public static class LambdaReturnContext extends AssignmentContext {
+
+    protected final ExecutableElement method;
+
+    public LambdaReturnContext(ExecutableElement method) {
+      this.method = method;
+    }
+
+    @Override
+    public Element getElementForType() {
+      return method;
+    }
+
+    @Override
+    public @Nullable Tree getContextTree() {
+      // TODO: what is the right assignment context? We might not have
+      // a tree for the invoked method.
+      return null;
+    }
+  }
+
+  /** Returns an {@link Element} that has the type of this assignment context. */
+  public abstract @Nullable Element getElementForType();
+
+  /** Returns the context tree. */
+  public abstract @Nullable Tree getContextTree();
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java
new file mode 100644
index 0000000..c3c25c2
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java
@@ -0,0 +1,93 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.VariableTree;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.AssignmentContext.AssignmentLhsContext;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for an assignment:
+ *
+ * <pre>
+ *   <em>variable</em> = <em>expression</em>
+ *   <em>expression</em> . <em>field</em> = <em>expression</em>
+ *   <em>expression</em> [ <em>index</em> ] = <em>expression</em>
+ * </pre>
+ *
+ * We allow assignments without corresponding AST {@link Tree}s.
+ */
+public class AssignmentNode extends Node {
+
+  protected final Tree tree;
+  protected final Node lhs;
+  protected final Node rhs;
+
+  public AssignmentNode(Tree tree, Node target, Node expression) {
+    super(TreeUtils.typeOf(tree));
+    assert tree instanceof AssignmentTree
+        || tree instanceof VariableTree
+        || tree instanceof CompoundAssignmentTree
+        || tree instanceof UnaryTree;
+    assert target instanceof FieldAccessNode
+        || target instanceof LocalVariableNode
+        || target instanceof ArrayAccessNode;
+    this.tree = tree;
+    this.lhs = target;
+    this.rhs = expression;
+    rhs.setAssignmentContext(new AssignmentLhsContext(lhs));
+  }
+
+  /**
+   * Returns the left-hand-side of the assignment.
+   *
+   * @return the left-hand-side of the assignment
+   */
+  public Node getTarget() {
+    return lhs;
+  }
+
+  public Node getExpression() {
+    return rhs;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitAssignment(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return getTarget() + " = " + getExpression();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof AssignmentNode)) {
+      return false;
+    }
+    AssignmentNode other = (AssignmentNode) obj;
+    return getTarget().equals(other.getTarget()) && getExpression().equals(other.getExpression());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getTarget(), getExpression());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Arrays.asList(getTarget(), getExpression());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BinaryOperationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BinaryOperationNode.java
new file mode 100644
index 0000000..8a947f6
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BinaryOperationNode.java
@@ -0,0 +1,47 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import java.util.Arrays;
+import java.util.Collection;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for a binary expression.
+ *
+ * <p>For example:
+ *
+ * <pre>
+ *   <em>lefOperandNode</em> <em>operator</em> <em>rightOperandNode</em>
+ * </pre>
+ */
+public abstract class BinaryOperationNode extends Node {
+
+  protected final BinaryTree tree;
+  protected final Node left;
+  protected final Node right;
+
+  protected BinaryOperationNode(BinaryTree tree, Node left, Node right) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+    this.left = left;
+    this.right = right;
+  }
+
+  public Node getLeftOperand() {
+    return left;
+  }
+
+  public Node getRightOperand() {
+    return right;
+  }
+
+  @Override
+  public BinaryTree getTree() {
+    return tree;
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Arrays.asList(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java
new file mode 100644
index 0000000..3e7409a
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the bitwise or logical (single bit) and operation:
+ *
+ * <pre>
+ *   <em>expression</em> &amp; <em>expression</em>
+ * </pre>
+ */
+public class BitwiseAndNode extends BinaryOperationNode {
+
+  public BitwiseAndNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.AND;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitBitwiseAnd(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " & " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof BitwiseAndNode)) {
+      return false;
+    }
+    BitwiseAndNode other = (BitwiseAndNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java
new file mode 100644
index 0000000..45b7d6e
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java
@@ -0,0 +1,45 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.UnaryTree;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the bitwise complement operation:
+ *
+ * <pre>
+ *   ~ <em>expression</em>
+ * </pre>
+ */
+public class BitwiseComplementNode extends UnaryOperationNode {
+
+  public BitwiseComplementNode(UnaryTree tree, Node operand) {
+    super(tree, operand);
+    assert tree.getKind() == Kind.BITWISE_COMPLEMENT;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitBitwiseComplement(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(~ " + getOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof BitwiseComplementNode)) {
+      return false;
+    }
+    BitwiseComplementNode other = (BitwiseComplementNode) obj;
+    return getOperand().equals(other.getOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(BitwiseComplementNode.class, getOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java
new file mode 100644
index 0000000..280a67c
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the bitwise or logical (single bit) or operation:
+ *
+ * <pre>
+ *   <em>expression</em> | <em>expression</em>
+ * </pre>
+ */
+public class BitwiseOrNode extends BinaryOperationNode {
+
+  public BitwiseOrNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.OR;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitBitwiseOr(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " | " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof BitwiseOrNode)) {
+      return false;
+    }
+    BitwiseOrNode other = (BitwiseOrNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java
new file mode 100644
index 0000000..0f1c0b5
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the bitwise or logical (single bit) xor operation:
+ *
+ * <pre>
+ *   <em>expression</em> ^ <em>expression</em>
+ * </pre>
+ */
+public class BitwiseXorNode extends BinaryOperationNode {
+
+  public BitwiseXorNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.XOR;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitBitwiseXor(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " ^ " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof BitwiseXorNode)) {
+      return false;
+    }
+    BitwiseXorNode other = (BitwiseXorNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java
new file mode 100644
index 0000000..0327705
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java
@@ -0,0 +1,53 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for a boolean literal:
+ *
+ * <pre>
+ *   <em>true</em>
+ *   <em>false</em>
+ * </pre>
+ */
+public class BooleanLiteralNode extends ValueLiteralNode {
+
+  /**
+   * Create a new BooleanLiteralNode.
+   *
+   * @param t the tree for the literal value
+   */
+  public BooleanLiteralNode(LiteralTree t) {
+    super(t);
+    assert t.getKind() == Tree.Kind.BOOLEAN_LITERAL;
+  }
+
+  @Override
+  public Boolean getValue() {
+    return (Boolean) tree.getValue();
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitBooleanLiteral(this, p);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    // test that obj is a BooleanLiteralNode
+    if (!(obj instanceof BooleanLiteralNode)) {
+      return false;
+    }
+    // super method compares values
+    return super.equals(obj);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CaseNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CaseNode.java
new file mode 100644
index 0000000..d70a9cc
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CaseNode.java
@@ -0,0 +1,87 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.CaseTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for a case in a switch statement. Although a case has no abstract value, it can imply
+ * facts about the abstract values of its operands.
+ *
+ * <pre>
+ *   case <em>constant</em>:
+ * </pre>
+ */
+public class CaseNode extends Node {
+
+  /** The tree for this node. */
+  protected final CaseTree tree;
+  /** The switch expression. */
+  protected final Node switchExpr;
+  /** The case expression to match the switch expression against. */
+  protected final Node caseExpr;
+
+  /**
+   * Create a new CaseNode.
+   *
+   * @param tree the tree for this node
+   * @param switchExpr the switch expression
+   * @param caseExpr the case expression to match the switch expression against
+   * @param types a factory of utility methods for operating on types
+   */
+  public CaseNode(CaseTree tree, Node switchExpr, Node caseExpr, Types types) {
+    super(types.getNoType(TypeKind.NONE));
+    assert tree.getKind() == Kind.CASE;
+    this.tree = tree;
+    this.switchExpr = switchExpr;
+    this.caseExpr = caseExpr;
+  }
+
+  public Node getSwitchOperand() {
+    return switchExpr;
+  }
+
+  public Node getCaseOperand() {
+    return caseExpr;
+  }
+
+  @Override
+  public CaseTree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitCase(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "case " + getCaseOperand() + ":";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof CaseNode)) {
+      return false;
+    }
+    CaseNode other = (CaseNode) obj;
+    return getSwitchOperand().equals(other.getSwitchOperand())
+        && getCaseOperand().equals(other.getCaseOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getSwitchOperand(), getCaseOperand());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Arrays.asList(getSwitchOperand(), getCaseOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java
new file mode 100644
index 0000000..aededf4
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java
@@ -0,0 +1,54 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for a character literal. For example:
+ *
+ * <pre>
+ *   <em>'a'</em>
+ *   <em>'\t'</em>
+ *   <em>'\u03a9'</em>
+ * </pre>
+ */
+public class CharacterLiteralNode extends ValueLiteralNode {
+
+  /**
+   * Create a new CharacterLiteralNode.
+   *
+   * @param t the character literal
+   */
+  public CharacterLiteralNode(LiteralTree t) {
+    super(t);
+    assert t.getKind() == Tree.Kind.CHAR_LITERAL;
+  }
+
+  @Override
+  public Character getValue() {
+    return (Character) tree.getValue();
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitCharacterLiteral(this, p);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    // test that obj is a CharacterLiteralNode
+    if (!(obj instanceof CharacterLiteralNode)) {
+      return false;
+    }
+    // super method compares values
+    return super.equals(obj);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassDeclarationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassDeclarationNode.java
new file mode 100644
index 0000000..f4908a9
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassDeclarationNode.java
@@ -0,0 +1,61 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.ClassTree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node representing a class declaration that occurs within a method, for example, an anonymous
+ * class declaration. In contrast to a top-level class declaration, such a declaration has an
+ * initialization store that contains captured variables.
+ */
+public class ClassDeclarationNode extends Node {
+
+  protected final ClassTree tree;
+
+  public ClassDeclarationNode(ClassTree tree) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+  }
+
+  @Override
+  public ClassTree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitClassDeclaration(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return tree.toString();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    ClassDeclarationNode that = (ClassDeclarationNode) o;
+    return Objects.equals(tree, that.tree);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(tree);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java
new file mode 100644
index 0000000..1b8b58f
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java
@@ -0,0 +1,113 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node representing a class name used in an expression such as a static method invocation.
+ *
+ * <p>parent.<em>class</em> .forName(...)
+ */
+public class ClassNameNode extends Node {
+
+  /** The tree for this node. */
+  protected final @Nullable Tree tree;
+
+  /** The class named by this node. */
+  protected final Element element;
+
+  /** The parent name, if any. */
+  protected final @Nullable Node parent;
+
+  public ClassNameNode(IdentifierTree tree) {
+    super(TreeUtils.typeOf(tree));
+    assert tree.getKind() == Tree.Kind.IDENTIFIER;
+    this.tree = tree;
+    assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind";
+    this.element = TreeUtils.elementFromUse(tree);
+    this.parent = null;
+  }
+
+  /**
+   * Create a new ClassNameNode.
+   *
+   * @param tree the class tree for this node
+   */
+  public ClassNameNode(ClassTree tree) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+    this.element = TreeUtils.elementFromDeclaration(tree);
+    this.parent = null;
+  }
+
+  public ClassNameNode(MemberSelectTree tree, Node parent) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+    assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind";
+    this.element = TreeUtils.elementFromUse(tree);
+    this.parent = parent;
+  }
+
+  public ClassNameNode(TypeMirror type, Element element) {
+    super(type);
+    this.tree = null;
+    this.element = element;
+    this.parent = null;
+  }
+
+  public Element getElement() {
+    return element;
+  }
+
+  /** The parent node of the current node. */
+  public @Nullable Node getParent() {
+    return parent;
+  }
+
+  @Override
+  public @Nullable Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitClassName(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return getElement().getSimpleName().toString();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ClassNameNode)) {
+      return false;
+    }
+    ClassNameNode other = (ClassNameNode) obj;
+    return Objects.equals(getParent(), other.getParent())
+        && getElement().equals(other.getElement());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getElement(), getParent());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    if (parent == null) {
+      return Collections.emptyList();
+    }
+    return Collections.singleton(parent);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java
new file mode 100644
index 0000000..9b08fcd
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java
@@ -0,0 +1,53 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for a conditional and expression:
+ *
+ * <pre>
+ *   <em>expression</em> &amp;&amp; <em>expression</em>
+ * </pre>
+ */
+public class ConditionalAndNode extends BinaryOperationNode {
+
+  /**
+   * Create a new ConditionalAndNode.
+   *
+   * @param tree the conditional-and tree for this node
+   * @param left the first argument
+   * @param right the second argument
+   */
+  public ConditionalAndNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.CONDITIONAL_AND;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitConditionalAnd(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " && " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ConditionalAndNode)) {
+      return false;
+    }
+    ConditionalAndNode other = (ConditionalAndNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java
new file mode 100644
index 0000000..c6c46e6
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java
@@ -0,0 +1,51 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.UnaryTree;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for a conditional not expression:
+ *
+ * <pre>
+ *   ! <em>expression</em>
+ * </pre>
+ */
+public class ConditionalNotNode extends UnaryOperationNode {
+
+  /**
+   * Create a new ConditionalNotNode.
+   *
+   * @param tree the logical-complement tree for this node
+   * @param operand the boolean expression being negated
+   */
+  public ConditionalNotNode(UnaryTree tree, Node operand) {
+    super(tree, operand);
+    assert tree.getKind() == Kind.LOGICAL_COMPLEMENT;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitConditionalNot(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(!" + getOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ConditionalNotNode)) {
+      return false;
+    }
+    ConditionalNotNode other = (ConditionalNotNode) obj;
+    return getOperand().equals(other.getOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(ConditionalNotNode.class, getOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java
new file mode 100644
index 0000000..3f2d5d2
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java
@@ -0,0 +1,53 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for a conditional or expression:
+ *
+ * <pre>
+ *   <em>expression</em> || <em>expression</em>
+ * </pre>
+ */
+public class ConditionalOrNode extends BinaryOperationNode {
+
+  /**
+   * Create a new ConditionalOrNode.
+   *
+   * @param tree the conditional-or tree for this node
+   * @param left the first argument
+   * @param right the second argument
+   */
+  public ConditionalOrNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.CONDITIONAL_OR;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitConditionalOr(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " || " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ConditionalOrNode)) {
+      return false;
+    }
+    ConditionalOrNode other = (ConditionalOrNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java
new file mode 100644
index 0000000..568a575
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java
@@ -0,0 +1,53 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for a double literal. For example:
+ *
+ * <pre>
+ *   <em>-9.</em>
+ *   <em>3.14159D</em>
+ * </pre>
+ */
+public class DoubleLiteralNode extends ValueLiteralNode {
+
+  /**
+   * Create a new DoubleLiteralNode.
+   *
+   * @param t the tree for the literal value
+   */
+  public DoubleLiteralNode(LiteralTree t) {
+    super(t);
+    assert t.getKind() == Tree.Kind.DOUBLE_LITERAL;
+  }
+
+  @Override
+  public Double getValue() {
+    return (Double) tree.getValue();
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitDoubleLiteral(this, p);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    // test that obj is a DoubleLiteralNode
+    if (!(obj instanceof DoubleLiteralNode)) {
+      return false;
+    }
+    // super method compares values
+    return super.equals(obj);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java
new file mode 100644
index 0000000..4a48e7c
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java
@@ -0,0 +1,53 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for an equality check:
+ *
+ * <pre>
+ *   <em>expression</em> == <em>expression</em>
+ * </pre>
+ */
+public class EqualToNode extends BinaryOperationNode {
+
+  /**
+   * Create a new EqualToNode object.
+   *
+   * @param tree the tree for this node
+   * @param left the first argument
+   * @param right the second argument
+   */
+  public EqualToNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.EQUAL_TO;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitEqualTo(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " == " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof EqualToNode)) {
+      return false;
+    }
+    EqualToNode other = (EqualToNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExplicitThisNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExplicitThisNode.java
new file mode 100644
index 0000000..ccad3fd
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExplicitThisNode.java
@@ -0,0 +1,38 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.Tree;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for a reference to 'this'.
+ *
+ * <pre>
+ *   <em>this</em>
+ * </pre>
+ */
+public class ExplicitThisNode extends ThisNode {
+
+  protected final Tree tree;
+
+  public ExplicitThisNode(Tree t) {
+    super(TreeUtils.typeOf(t));
+    assert t instanceof IdentifierTree && ((IdentifierTree) t).getName().contentEquals("this");
+    tree = t;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitExplicitThis(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return getName();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java
new file mode 100644
index 0000000..5d0aac0
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java
@@ -0,0 +1,113 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for a field access, including a method accesses:
+ *
+ * <pre>
+ *   <em>expression</em> . <em>field</em>
+ * </pre>
+ */
+public class FieldAccessNode extends Node {
+
+  protected final Tree tree;
+  protected final VariableElement element;
+  protected final String field;
+  protected final Node receiver;
+
+  // TODO: add method to get modifiers (static, access level, ..)
+
+  /**
+   * Creates a new FieldAccessNode.
+   *
+   * @param tree the tree from which to create a FieldAccessNode
+   * @param receiver the receiver for the resuling FieldAccessNode
+   */
+  public FieldAccessNode(Tree tree, Node receiver) {
+    super(TreeUtils.typeOf(tree));
+    assert TreeUtils.isFieldAccess(tree);
+    this.tree = tree;
+    this.receiver = receiver;
+    this.field = TreeUtils.getFieldName(tree);
+
+    if (tree instanceof MemberSelectTree) {
+      MemberSelectTree mstree = (MemberSelectTree) tree;
+      assert TreeUtils.isUseOfElement(mstree) : "@AssumeAssertion(nullness): tree kind";
+      this.element = (VariableElement) TreeUtils.elementFromUse(mstree);
+    } else {
+      assert tree instanceof IdentifierTree;
+      IdentifierTree itree = (IdentifierTree) tree;
+      assert TreeUtils.isUseOfElement(itree) : "@AssumeAssertion(nullness): tree kind";
+      this.element = (VariableElement) TreeUtils.elementFromUse(itree);
+    }
+  }
+
+  public FieldAccessNode(Tree tree, VariableElement element, Node receiver) {
+    super(element.asType());
+    this.tree = tree;
+    this.element = element;
+    this.receiver = receiver;
+    this.field = element.getSimpleName().toString();
+  }
+
+  public VariableElement getElement() {
+    return element;
+  }
+
+  public Node getReceiver() {
+    return receiver;
+  }
+
+  public String getFieldName() {
+    return field;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitFieldAccess(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return getReceiver() + "." + field;
+  }
+
+  /** Is this a static field? */
+  public boolean isStatic() {
+    return ElementUtils.isStatic(getElement());
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof FieldAccessNode)) {
+      return false;
+    }
+    FieldAccessNode other = (FieldAccessNode) obj;
+    return getReceiver().equals(other.getReceiver()) && getFieldName().equals(other.getFieldName());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getReceiver(), getFieldName());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.singletonList(receiver);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java
new file mode 100644
index 0000000..dd1bf1f
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java
@@ -0,0 +1,53 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for a float literal. For example:
+ *
+ * <pre>
+ *   <em>8.0f</em>
+ *   <em>6.022137e+23F</em>
+ * </pre>
+ */
+public class FloatLiteralNode extends ValueLiteralNode {
+
+  /**
+   * Create a new FloatLiteralNode.
+   *
+   * @param t the tree for the literal value
+   */
+  public FloatLiteralNode(LiteralTree t) {
+    super(t);
+    assert t.getKind() == Tree.Kind.FLOAT_LITERAL;
+  }
+
+  @Override
+  public Float getValue() {
+    return (Float) tree.getValue();
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitFloatLiteral(this, p);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    // test that obj is a FloatLiteralNode
+    if (!(obj instanceof FloatLiteralNode)) {
+      return false;
+    }
+    // super method compares values
+    return super.equals(obj);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java
new file mode 100644
index 0000000..0554393
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the floating-point division:
+ *
+ * <pre>
+ *   <em>expression</em> / <em>expression</em>
+ * </pre>
+ */
+public class FloatingDivisionNode extends BinaryOperationNode {
+
+  public FloatingDivisionNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.DIVIDE;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitFloatingDivision(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " / " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof FloatingDivisionNode)) {
+      return false;
+    }
+    FloatingDivisionNode other = (FloatingDivisionNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java
new file mode 100644
index 0000000..2c5320d
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the floating-point remainder:
+ *
+ * <pre>
+ *   <em>expression</em> % <em>expression</em>
+ * </pre>
+ */
+public class FloatingRemainderNode extends BinaryOperationNode {
+
+  public FloatingRemainderNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.REMAINDER;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitFloatingRemainder(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " % " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof FloatingRemainderNode)) {
+      return false;
+    }
+    FloatingRemainderNode other = (FloatingRemainderNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java
new file mode 100644
index 0000000..8af6e5d
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java
@@ -0,0 +1,90 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for member references and lambdas.
+ *
+ * <p>The {@link Node#type} of a FunctionalInterfaceNode is determined by the assignment context the
+ * member reference or lambda is used in.
+ *
+ * <pre>
+ *   <em>FunctionalInterface func = param1, param2, ... &rarr; statement</em>
+ * </pre>
+ *
+ * <pre>
+ *   <em>FunctionalInterface func = param1, param2, ... &rarr; { ... }</em>
+ * </pre>
+ *
+ * <pre>
+ *   <em>FunctionalInterface func = member reference</em>
+ * </pre>
+ */
+public class FunctionalInterfaceNode extends Node {
+
+  protected final Tree tree;
+
+  public FunctionalInterfaceNode(MemberReferenceTree tree) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+  }
+
+  public FunctionalInterfaceNode(LambdaExpressionTree tree) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitMemberReference(this, p);
+  }
+
+  @Override
+  public String toString() {
+    if (tree instanceof LambdaExpressionTree) {
+      return "FunctionalInterfaceNode:" + ((LambdaExpressionTree) tree).getBodyKind();
+    } else if (tree instanceof MemberReferenceTree) {
+      return "FunctionalInterfaceNode:" + ((MemberReferenceTree) tree).getName();
+    } else {
+      // This should never happen.
+      throw new BugInCF("Invalid tree in FunctionalInterfaceNode");
+    }
+  }
+
+  @Override
+  public boolean equals(@Nullable Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    FunctionalInterfaceNode that = (FunctionalInterfaceNode) o;
+
+    return tree != null ? tree.equals(that.tree) : that.tree == null;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(tree);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java
new file mode 100644
index 0000000..62c9242
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the greater than comparison:
+ *
+ * <pre>
+ *   <em>expression</em> &gt; <em>expression</em>
+ * </pre>
+ */
+public class GreaterThanNode extends BinaryOperationNode {
+
+  public GreaterThanNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.GREATER_THAN;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitGreaterThan(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " > " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof GreaterThanNode)) {
+      return false;
+    }
+    GreaterThanNode other = (GreaterThanNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java
new file mode 100644
index 0000000..e680e38
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the greater than or equal comparison:
+ *
+ * <pre>
+ *   <em>expression</em> &gt;= <em>expression</em>
+ * </pre>
+ */
+public class GreaterThanOrEqualNode extends BinaryOperationNode {
+
+  public GreaterThanOrEqualNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.GREATER_THAN_EQUAL;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitGreaterThanOrEqual(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " >= " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof GreaterThanOrEqualNode)) {
+      return false;
+    }
+    GreaterThanOrEqualNode other = (GreaterThanOrEqualNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ImplicitThisNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ImplicitThisNode.java
new file mode 100644
index 0000000..25bac97
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ImplicitThisNode.java
@@ -0,0 +1,28 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/** A node to model the implicit {@code this}, e.g., in a field access. */
+public class ImplicitThisNode extends ThisNode {
+
+  public ImplicitThisNode(TypeMirror type) {
+    super(type);
+  }
+
+  @Override
+  public @Nullable Tree getTree() {
+    return null;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitImplicitThis(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getName() + ")";
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java
new file mode 100644
index 0000000..71697d2
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java
@@ -0,0 +1,88 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * A node for the instanceof operator:
+ *
+ * <p><em>x</em> instanceof <em>Point</em>
+ */
+public class InstanceOfNode extends Node {
+
+  /** The value being tested. */
+  protected final Node operand;
+
+  /** The reference type being tested against. */
+  protected final TypeMirror refType;
+
+  /** The tree associated with this node. */
+  protected final InstanceOfTree tree;
+
+  /** For Types.isSameType. */
+  protected final Types types;
+
+  /** Create an InstanceOfNode. */
+  public InstanceOfNode(Tree tree, Node operand, TypeMirror refType, Types types) {
+    super(types.getPrimitiveType(TypeKind.BOOLEAN));
+    assert tree.getKind() == Tree.Kind.INSTANCE_OF;
+    this.tree = (InstanceOfTree) tree;
+    this.operand = operand;
+    this.refType = refType;
+    this.types = types;
+  }
+
+  public Node getOperand() {
+    return operand;
+  }
+
+  /** The reference type being tested against. */
+  public TypeMirror getRefType() {
+    return refType;
+  }
+
+  @Override
+  public InstanceOfTree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitInstanceOf(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getOperand() + " instanceof " + TypesUtils.simpleTypeName(getRefType()) + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof InstanceOfNode)) {
+      return false;
+    }
+    InstanceOfNode other = (InstanceOfNode) obj;
+    // TODO: TypeMirror.equals may be too restrictive.
+    // Check whether Types.isSameType is the better comparison.
+    return getOperand().equals(other.getOperand())
+        && types.isSameType(getRefType(), other.getRefType());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(InstanceOfNode.class, getOperand());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.singletonList(getOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java
new file mode 100644
index 0000000..989fa3a
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the integer division:
+ *
+ * <pre>
+ *   <em>expression</em> / <em>expression</em>
+ * </pre>
+ */
+public class IntegerDivisionNode extends BinaryOperationNode {
+
+  public IntegerDivisionNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.DIVIDE;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitIntegerDivision(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " / " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof IntegerDivisionNode)) {
+      return false;
+    }
+    IntegerDivisionNode other = (IntegerDivisionNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java
new file mode 100644
index 0000000..a7ea733
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java
@@ -0,0 +1,52 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for an integer literal. For example:
+ *
+ * <pre>
+ *   <em>42</em>
+ * </pre>
+ */
+public class IntegerLiteralNode extends ValueLiteralNode {
+
+  /**
+   * Create a new IntegerLiteralNode.
+   *
+   * @param t the tree for the literal value
+   */
+  public IntegerLiteralNode(LiteralTree t) {
+    super(t);
+    assert t.getKind() == Tree.Kind.INT_LITERAL;
+  }
+
+  @Override
+  public Integer getValue() {
+    return (Integer) tree.getValue();
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitIntegerLiteral(this, p);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    // test that obj is a IntegerLiteralNode
+    if (!(obj instanceof IntegerLiteralNode)) {
+      return false;
+    }
+    // super method compares values
+    return super.equals(obj);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java
new file mode 100644
index 0000000..f70e746
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the integer remainder:
+ *
+ * <pre>
+ *   <em>expression</em> % <em>expression</em>
+ * </pre>
+ */
+public class IntegerRemainderNode extends BinaryOperationNode {
+
+  public IntegerRemainderNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.REMAINDER;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitIntegerRemainder(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " % " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof IntegerRemainderNode)) {
+      return false;
+    }
+    IntegerRemainderNode other = (IntegerRemainderNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LambdaResultExpressionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LambdaResultExpressionNode.java
new file mode 100644
index 0000000..ae025bb
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LambdaResultExpressionNode.java
@@ -0,0 +1,79 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.ExpressionTree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/** A node for the single expression body of a single expression lambda. */
+public class LambdaResultExpressionNode extends Node {
+
+  protected final ExpressionTree tree;
+  protected final @Nullable Node result;
+
+  public LambdaResultExpressionNode(ExpressionTree t, @Nullable Node result, Types types) {
+    super(TreeUtils.typeOf(t));
+    this.result = result;
+    tree = t;
+  }
+
+  /**
+   * Returns the final node of the CFG corresponding to the lambda expression body (see {@link
+   * #getTree()}).
+   */
+  public @Nullable Node getResult() {
+    return result;
+  }
+
+  /**
+   * Returns the {@link ExpressionTree} corresponding to the body of a lambda expression with an
+   * expression body (e.g. X for ({@code o -> X}) where X is an expression and not a {...} block).
+   */
+  @Override
+  public ExpressionTree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitLambdaResultExpression(this, p);
+  }
+
+  @Override
+  public String toString() {
+    if (result != null) {
+      return "-> " + result;
+    }
+    return "-> ()";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    // No need to compare tree, since in a well-formed LambdaResultExpressionNode, result will
+    // be the same only when tree is the same (this is similar to ReturnNode).
+    if (!(obj instanceof LambdaResultExpressionNode)) {
+      return false;
+    }
+    LambdaResultExpressionNode other = (LambdaResultExpressionNode) obj;
+    return Objects.equals(result, other.result);
+  }
+
+  @Override
+  public int hashCode() {
+    // No need to incorporate tree, since in a well-formed LambdaResultExpressionNode, result
+    // will be the same only when tree is the same (this is similar to ReturnNode).
+    return Objects.hash(LambdaResultExpressionNode.class, result);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    if (result == null) {
+      return Collections.emptyList();
+    } else {
+      return Collections.singletonList(result);
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java
new file mode 100644
index 0000000..a333ee7
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for bitwise left shift operations:
+ *
+ * <pre>
+ *   <em>expression</em> &lt;&lt; <em>expression</em>
+ * </pre>
+ */
+public class LeftShiftNode extends BinaryOperationNode {
+
+  public LeftShiftNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.LEFT_SHIFT;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitLeftShift(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " << " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof LeftShiftNode)) {
+      return false;
+    }
+    LeftShiftNode other = (LeftShiftNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java
new file mode 100644
index 0000000..8e32fcb
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java
@@ -0,0 +1,49 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the less than comparison:
+ *
+ * <pre>
+ *   <em>expression</em> &lt; <em>expression</em>
+ * </pre>
+ *
+ * We allow less than nodes without corresponding AST {@link Tree}s.
+ */
+public class LessThanNode extends BinaryOperationNode {
+
+  public LessThanNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.LESS_THAN;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitLessThan(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " < " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof LessThanNode)) {
+      return false;
+    }
+    LessThanNode other = (LessThanNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java
new file mode 100644
index 0000000..ba0a6d8
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the less than or equal comparison:
+ *
+ * <pre>
+ *   <em>expression</em> &lt;= <em>expression</em>
+ * </pre>
+ */
+public class LessThanOrEqualNode extends BinaryOperationNode {
+
+  public LessThanOrEqualNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.LESS_THAN_EQUAL;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitLessThanOrEqual(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " <= " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof LessThanOrEqualNode)) {
+      return false;
+    }
+    LessThanOrEqualNode other = (LessThanOrEqualNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java
new file mode 100644
index 0000000..a22ad00
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java
@@ -0,0 +1,115 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.element.Element;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for a local variable or a parameter:
+ *
+ * <pre>
+ *   <em>identifier</em>
+ * </pre>
+ *
+ * We allow local variable uses introduced by the {@link
+ * org.checkerframework.dataflow.cfg.builder.CFGBuilder} without corresponding AST {@link Tree}s.
+ */
+// TODO: don't use for parameters, as they don't have a tree
+public class LocalVariableNode extends Node {
+
+  /** The tree for the local variable. */
+  protected final Tree tree;
+
+  /** The receiver node for the local variable, {@code null} otherwise. */
+  protected final @Nullable Node receiver;
+
+  /**
+   * Create a new local variable node for the given tree.
+   *
+   * @param tree thre tree for the local variable: a VariableTree or an IdentifierTree
+   */
+  public LocalVariableNode(Tree tree) {
+    this(tree, null);
+  }
+
+  /**
+   * Create a new local variable node for the given tree and receiver.
+   *
+   * @param tree the tree for the local variable: a VariableTree or an IdentifierTree
+   * @param receiver the receiver for the local variable, or null if none
+   */
+  public LocalVariableNode(Tree tree, @Nullable Node receiver) {
+    super(TreeUtils.typeOf(tree));
+    // IdentifierTree for normal uses of the local variable or parameter,
+    // and VariableTree for declarations or the translation of an initializer block
+    assert tree != null;
+    assert tree instanceof IdentifierTree || tree instanceof VariableTree;
+    this.tree = tree;
+    this.receiver = receiver;
+  }
+
+  public Element getElement() {
+    Element el;
+    if (tree instanceof IdentifierTree) {
+      IdentifierTree itree = (IdentifierTree) tree;
+      assert TreeUtils.isUseOfElement(itree) : "@AssumeAssertion(nullness): tree kind";
+      el = TreeUtils.elementFromUse(itree);
+    } else {
+      assert tree instanceof VariableTree;
+      el = TreeUtils.elementFromDeclaration((VariableTree) tree);
+    }
+    return el;
+  }
+
+  /** The receiver node for the local variable, {@code null} otherwise. */
+  public @Nullable Node getReceiver() {
+    return receiver;
+  }
+
+  public String getName() {
+    if (tree instanceof IdentifierTree) {
+      return ((IdentifierTree) tree).getName().toString();
+    }
+    return ((VariableTree) tree).getName().toString();
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitLocalVariable(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return getName().toString();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof LocalVariableNode)) {
+      return false;
+    }
+    LocalVariableNode other = (LocalVariableNode) obj;
+    return getName().equals(other.getName());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getName());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java
new file mode 100644
index 0000000..3c077dd
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java
@@ -0,0 +1,53 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for a long literal. For example:
+ *
+ * <pre>
+ *   <em>-3l</em>
+ *   <em>0x80808080L</em>
+ * </pre>
+ */
+public class LongLiteralNode extends ValueLiteralNode {
+
+  /**
+   * Create a new LongLiteralNode.
+   *
+   * @param t the tree for the literal value
+   */
+  public LongLiteralNode(LiteralTree t) {
+    super(t);
+    assert t.getKind() == Tree.Kind.LONG_LITERAL;
+  }
+
+  @Override
+  public Long getValue() {
+    return (Long) tree.getValue();
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitLongLiteral(this, p);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    // test that obj is a LongLiteralNode
+    if (!(obj instanceof LongLiteralNode)) {
+      return false;
+    }
+    // super method compares values
+    return super.equals(obj);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java
new file mode 100644
index 0000000..fae7f7e
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java
@@ -0,0 +1,69 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * MarkerNodes are no-op Nodes used for debugging information. They can hold a Tree and a message,
+ * which will be part of the String representation of the MarkerNode.
+ *
+ * <p>An example use case for MarkerNodes is representing switch statements.
+ */
+public class MarkerNode extends Node {
+
+  protected final @Nullable Tree tree;
+  protected final String message;
+
+  public MarkerNode(@Nullable Tree tree, String message, Types types) {
+    super(types.getNoType(TypeKind.NONE));
+    this.tree = tree;
+    this.message = message;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+
+  @Override
+  public @Nullable Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitMarker(this, p);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("marker (");
+    sb.append(message);
+    sb.append(")");
+    return sb.toString();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof MarkerNode)) {
+      return false;
+    }
+    MarkerNode other = (MarkerNode) obj;
+    return Objects.equals(getTree(), other.getTree()) && getMessage().equals(other.getMessage());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(tree, getMessage());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java
new file mode 100644
index 0000000..302d7b4
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java
@@ -0,0 +1,77 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for a method access, including a method accesses:
+ *
+ * <pre>
+ *   <em>expression</em> . <em>method</em> ()
+ * </pre>
+ */
+public class MethodAccessNode extends Node {
+
+  protected final ExpressionTree tree;
+  protected final ExecutableElement method;
+  protected final Node receiver;
+
+  // TODO: add method to get modifiers (static, access level, ..)
+
+  public MethodAccessNode(ExpressionTree tree, Node receiver) {
+    super(TreeUtils.typeOf(tree));
+    assert TreeUtils.isMethodAccess(tree);
+    this.tree = tree;
+    assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind";
+    this.method = (ExecutableElement) TreeUtils.elementFromUse(tree);
+    this.receiver = receiver;
+  }
+
+  public ExecutableElement getMethod() {
+    return method;
+  }
+
+  public Node getReceiver() {
+    return receiver;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitMethodAccess(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return getReceiver() + "." + method.getSimpleName();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof MethodAccessNode)) {
+      return false;
+    }
+    MethodAccessNode other = (MethodAccessNode) obj;
+    return getReceiver().equals(other.getReceiver()) && getMethod().equals(other.getMethod());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getReceiver(), getMethod());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.singletonList(receiver);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java
new file mode 100644
index 0000000..bbce8e1
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java
@@ -0,0 +1,157 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.AssignmentContext.MethodParameterContext;
+import org.checkerframework.javacutil.TreeUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * A node for method invocation.
+ *
+ * <pre>
+ *   <em>target(arg1, arg2, ...)</em>
+ * </pre>
+ *
+ * CFGs may contain {@link MethodInvocationNode}s that correspond to no AST {@link Tree}, in which
+ * case, the tree field will be null.
+ */
+public class MethodInvocationNode extends Node {
+
+  /** The tree for the method invocation. */
+  protected final @Nullable MethodInvocationTree tree;
+
+  /**
+   * The MethodAccessNode for the method being invoked. Includes the receiver if any. For a static
+   * method, the receiver may be a class name.
+   */
+  protected final MethodAccessNode target;
+
+  /** The arguments of the method invocation. */
+  protected final List<Node> arguments;
+
+  /** The tree path to the method invocation. */
+  protected final TreePath treePath;
+
+  /**
+   * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an
+   * enhanced for loop, then the {@code iterExpression} field is the expression in the for loop,
+   * e.g., {@code iter} in {@code for(Object o: iter}.
+   */
+  protected @Nullable ExpressionTree iterableExpression;
+
+  /**
+   * Create a MethodInvocationNode.
+   *
+   * @param tree for the method invocation
+   * @param target the MethodAccessNode for the method being invoked
+   * @param arguments arguments of the method invocation
+   * @param treePath path to the method invocation
+   */
+  public MethodInvocationNode(
+      @Nullable MethodInvocationTree tree,
+      MethodAccessNode target,
+      List<Node> arguments,
+      TreePath treePath) {
+    super(tree != null ? TreeUtils.typeOf(tree) : target.getMethod().getReturnType());
+    this.tree = tree;
+    this.target = target;
+    this.arguments = arguments;
+    this.treePath = treePath;
+
+    // set assignment contexts for parameters
+    int i = 0;
+    for (Node arg : arguments) {
+      AssignmentContext ctx = new MethodParameterContext(target.getMethod(), i++);
+      arg.setAssignmentContext(ctx);
+    }
+  }
+
+  public MethodInvocationNode(MethodAccessNode target, List<Node> arguments, TreePath treePath) {
+    this(null, target, arguments, treePath);
+  }
+
+  public MethodAccessNode getTarget() {
+    return target;
+  }
+
+  public List<Node> getArguments() {
+    return arguments;
+  }
+
+  public Node getArgument(int i) {
+    return arguments.get(i);
+  }
+
+  public TreePath getTreePath() {
+    return treePath;
+  }
+
+  /**
+   * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an
+   * enhanced for loop, then return the expression in the for loop, e.g., {@code iter} in {@code
+   * for(Object o: iter}. Otherwise, return null.
+   *
+   * @return the iter expression, or null if this is not a {@link Iterator#next()} from an enhanced
+   *     for loop
+   */
+  public @Nullable ExpressionTree getIterableExpression() {
+    return iterableExpression;
+  }
+
+  /**
+   * Set the iterable expression from a for loop.
+   *
+   * @param iterableExpression iterable expression
+   * @see #getIterableExpression()
+   */
+  public void setIterableExpression(@Nullable ExpressionTree iterableExpression) {
+    this.iterableExpression = iterableExpression;
+  }
+
+  @Override
+  public @Nullable MethodInvocationTree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitMethodInvocation(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return target + "(" + StringsPlume.join(", ", arguments) + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof MethodInvocationNode)) {
+      return false;
+    }
+    MethodInvocationNode other = (MethodInvocationNode) obj;
+
+    return getTarget().equals(other.getTarget()) && getArguments().equals(other.getArguments());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(target, arguments);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    List<Node> list = new ArrayList<>(1 + arguments.size());
+    list.add(target);
+    list.addAll(arguments);
+    return list;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java
new file mode 100644
index 0000000..a7a8469
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java
@@ -0,0 +1,69 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * A node for the narrowing primitive conversion operation. See JLS 5.1.3 for the definition of
+ * narrowing primitive conversion.
+ *
+ * <p>A {@link NarrowingConversionNode} does not correspond to any tree node in the parsed AST. It
+ * is introduced when a value of some primitive type appears in a context that requires a different
+ * primitive with more bits of precision.
+ */
+public class NarrowingConversionNode extends Node {
+
+  protected final Tree tree;
+  protected final Node operand;
+
+  public NarrowingConversionNode(Tree tree, Node operand, TypeMirror type) {
+    super(type);
+    assert TypesUtils.isPrimitive(type) : "non-primitive type in narrowing conversion";
+    this.tree = tree;
+    this.operand = operand;
+  }
+
+  public Node getOperand() {
+    return operand;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitNarrowingConversion(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "NarrowingConversion(" + getOperand() + ", " + type + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof NarrowingConversionNode)) {
+      return false;
+    }
+    NarrowingConversionNode other = (NarrowingConversionNode) obj;
+    return getOperand().equals(other.getOperand())
+        && TypesUtils.areSamePrimitiveTypes(getType(), other.getType());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(NarrowingConversionNode.class, getOperand());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.singletonList(getOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/Node.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/Node.java
new file mode 100644
index 0000000..4f7aead
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/Node.java
@@ -0,0 +1,207 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.builder.CFGBuilder;
+import org.checkerframework.dataflow.qual.Pure;
+import org.plumelib.util.UniqueId;
+
+/**
+ * A node in the abstract representation used for Java code inside a basic block.
+ *
+ * <p>The following invariants hold:
+ *
+ * <pre>
+ * block == null || block instanceof RegularBlock || block instanceof ExceptionBlock
+ * block != null &hArr; block.getNodes().contains(this)
+ * </pre>
+ *
+ * <pre>
+ * type != null
+ * tree != null &rArr; node.getType() == InternalUtils.typeOf(node.getTree())
+ * </pre>
+ *
+ * Note that two {@code Node}s can be {@code .equals} but represent different CFG nodes. Take care
+ * to use reference equality, maps that handle identity {@code IdentityHashMap}, and sets like
+ * {@code IdentityMostlySingleton}.
+ *
+ * @see org.checkerframework.dataflow.util.IdentityMostlySingleton
+ */
+public abstract class Node implements UniqueId {
+
+  /**
+   * The basic block this node belongs to. If null, this object represents a method formal
+   * parameter.
+   */
+  protected @Nullable Block block;
+
+  /** Is this node an l-value? */
+  protected boolean lvalue = false;
+
+  /** The assignment context of this node. See {@link AssignmentContext}. */
+  protected @Nullable AssignmentContext assignmentContext;
+
+  /**
+   * Does this node represent a tree that appears in the source code (true) or one that the CFG
+   * builder added while desugaring (false).
+   */
+  protected boolean inSource = true;
+
+  /**
+   * The type of this node. For {@link Node}s with {@link Tree}s, this type is the type of the
+   * {@link Tree}. Otherwise, it is the type is set by the {@link CFGBuilder}.
+   */
+  protected final TypeMirror type;
+
+  /** The unique ID for the next-created object. */
+  static final AtomicLong nextUid = new AtomicLong(0);
+  /** The unique ID of this object. */
+  final long uid = nextUid.getAndIncrement();
+  /**
+   * Returns the unique ID of this object.
+   *
+   * @return the unique ID of this object
+   */
+  @Override
+  public long getUid(@UnknownInitialization Node this) {
+    return uid;
+  }
+
+  /**
+   * Creates a new Node.
+   *
+   * @param type the type of the node
+   */
+  protected Node(TypeMirror type) {
+    assert type != null;
+    this.type = type;
+  }
+
+  /**
+   * Returns the basic block this node belongs to (or {@code null} if it represents the parameter of
+   * a method).
+   *
+   * @return the basic block this node belongs to (or {@code null} if it represents the parameter of
+   *     a method)
+   */
+  public @Nullable Block getBlock() {
+    return block;
+  }
+
+  /** Set the basic block this node belongs to. */
+  public void setBlock(Block b) {
+    block = b;
+  }
+
+  /**
+   * Returns the {@link Tree} in the abstract syntax tree, or {@code null} if no corresponding tree
+   * exists. For instance, this is the case for an {@link ImplicitThisNode}.
+   *
+   * @return the corresponding {@link Tree} or {@code null}
+   */
+  @Pure
+  public abstract @Nullable Tree getTree();
+
+  /**
+   * Returns a {@link TypeMirror} representing the type of a {@link Node}. A {@link Node} will
+   * always have a type even when it has no {@link Tree}.
+   *
+   * @return a {@link TypeMirror} representing the type of this {@link Node}
+   */
+  public TypeMirror getType() {
+    return type;
+  }
+
+  /**
+   * Accept method of the visitor pattern.
+   *
+   * @param <R> result type of the operation
+   * @param <P> parameter type
+   * @param visitor the visitor to be applied to this node
+   * @param p the parameter for this operation
+   */
+  public abstract <R, P> R accept(NodeVisitor<R, P> visitor, P p);
+
+  /** Is the node an lvalue or not? */
+  @Pure
+  public boolean isLValue() {
+    return lvalue;
+  }
+
+  /** Make this node an l-value. */
+  public void setLValue() {
+    lvalue = true;
+  }
+
+  public boolean getInSource() {
+    return inSource;
+  }
+
+  public void setInSource(boolean inSrc) {
+    inSource = inSrc;
+  }
+
+  /** The assignment context for the node. */
+  public @Nullable AssignmentContext getAssignmentContext() {
+    return assignmentContext;
+  }
+
+  public void setAssignmentContext(AssignmentContext assignmentContext) {
+    this.assignmentContext = assignmentContext;
+  }
+
+  /**
+   * Returns a collection containing all of the operand {@link Node}s of this {@link Node}.
+   *
+   * @return a collection containing all of the operand {@link Node}s of this {@link Node}
+   */
+  public abstract Collection<Node> getOperands();
+
+  /**
+   * Returns a collection containing all of the operand {@link Node}s of this {@link Node}, as well
+   * as (transitively) the operands of its operands.
+   *
+   * @return a collection containing all of the operand {@link Node}s of this {@link Node}, as well
+   *     as (transitively) the operands of its operands
+   */
+  public Collection<Node> getTransitiveOperands() {
+    ArrayDeque<Node> operands = new ArrayDeque<>(getOperands());
+    ArrayDeque<Node> transitiveOperands = new ArrayDeque<>(operands.size());
+    while (!operands.isEmpty()) {
+      Node next = operands.removeFirst();
+      operands.addAll(next.getOperands());
+      transitiveOperands.add(next);
+    }
+    return transitiveOperands;
+  }
+
+  /**
+   * Returns a verbose string representation of this, useful for debugging.
+   *
+   * @return a printed representation of this
+   */
+  public String toStringDebug() {
+    return String.format("%s [%s]", this, this.getClassAndUid());
+  }
+
+  /**
+   * Returns a verbose string representation of a collection of nodes, useful for debugging..
+   *
+   * @param nodes a collection of nodes to format
+   * @return a printed representation of the given collection
+   */
+  public static String nodeCollectionToString(Collection<? extends Node> nodes) {
+    StringJoiner result = new StringJoiner(", ", "[", "]");
+    for (Node n : nodes) {
+      result.add(n.toStringDebug());
+    }
+    return result.toString();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java
new file mode 100644
index 0000000..73c07f5
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java
@@ -0,0 +1,164 @@
+package org.checkerframework.dataflow.cfg.node;
+
+/**
+ * A visitor for a {@link Node} tree.
+ *
+ * @param <R> return type of the visitor. Use {@link Void} if the visitor does not have a return
+ *     value.
+ * @param <P> parameter type of the visitor. Use {@link Void} if the visitor does not have a
+ *     parameter.
+ */
+public interface NodeVisitor<R, P> {
+  // Literals
+  R visitShortLiteral(ShortLiteralNode n, P p);
+
+  R visitIntegerLiteral(IntegerLiteralNode n, P p);
+
+  R visitLongLiteral(LongLiteralNode n, P p);
+
+  R visitFloatLiteral(FloatLiteralNode n, P p);
+
+  R visitDoubleLiteral(DoubleLiteralNode n, P p);
+
+  R visitBooleanLiteral(BooleanLiteralNode n, P p);
+
+  R visitCharacterLiteral(CharacterLiteralNode n, P p);
+
+  R visitStringLiteral(StringLiteralNode n, P p);
+
+  R visitNullLiteral(NullLiteralNode n, P p);
+
+  // Unary operations
+  R visitNumericalMinus(NumericalMinusNode n, P p);
+
+  R visitNumericalPlus(NumericalPlusNode n, P p);
+
+  R visitBitwiseComplement(BitwiseComplementNode n, P p);
+
+  R visitNullChk(NullChkNode n, P p);
+
+  // Binary operations
+  R visitStringConcatenate(StringConcatenateNode n, P p);
+
+  R visitNumericalAddition(NumericalAdditionNode n, P p);
+
+  R visitNumericalSubtraction(NumericalSubtractionNode n, P p);
+
+  R visitNumericalMultiplication(NumericalMultiplicationNode n, P p);
+
+  R visitIntegerDivision(IntegerDivisionNode n, P p);
+
+  R visitFloatingDivision(FloatingDivisionNode n, P p);
+
+  R visitIntegerRemainder(IntegerRemainderNode n, P p);
+
+  R visitFloatingRemainder(FloatingRemainderNode n, P p);
+
+  R visitLeftShift(LeftShiftNode n, P p);
+
+  R visitSignedRightShift(SignedRightShiftNode n, P p);
+
+  R visitUnsignedRightShift(UnsignedRightShiftNode n, P p);
+
+  R visitBitwiseAnd(BitwiseAndNode n, P p);
+
+  R visitBitwiseOr(BitwiseOrNode n, P p);
+
+  R visitBitwiseXor(BitwiseXorNode n, P p);
+
+  // Compound assignments
+  R visitStringConcatenateAssignment(StringConcatenateAssignmentNode n, P p);
+
+  // Comparison operations
+  R visitLessThan(LessThanNode n, P p);
+
+  R visitLessThanOrEqual(LessThanOrEqualNode n, P p);
+
+  R visitGreaterThan(GreaterThanNode n, P p);
+
+  R visitGreaterThanOrEqual(GreaterThanOrEqualNode n, P p);
+
+  R visitEqualTo(EqualToNode n, P p);
+
+  R visitNotEqual(NotEqualNode n, P p);
+
+  // Conditional operations
+  R visitConditionalAnd(ConditionalAndNode n, P p);
+
+  R visitConditionalOr(ConditionalOrNode n, P p);
+
+  R visitConditionalNot(ConditionalNotNode n, P p);
+
+  R visitTernaryExpression(TernaryExpressionNode n, P p);
+
+  R visitAssignment(AssignmentNode n, P p);
+
+  R visitLocalVariable(LocalVariableNode n, P p);
+
+  R visitVariableDeclaration(VariableDeclarationNode n, P p);
+
+  R visitFieldAccess(FieldAccessNode n, P p);
+
+  R visitMethodAccess(MethodAccessNode n, P p);
+
+  R visitArrayAccess(ArrayAccessNode n, P p);
+
+  R visitImplicitThis(ImplicitThisNode n, P p);
+
+  R visitExplicitThis(ExplicitThisNode n, P p);
+
+  R visitSuper(SuperNode n, P p);
+
+  R visitReturn(ReturnNode n, P p);
+
+  R visitLambdaResultExpression(LambdaResultExpressionNode n, P p);
+
+  R visitStringConversion(StringConversionNode n, P p);
+
+  R visitWideningConversion(WideningConversionNode n, P p);
+
+  R visitNarrowingConversion(NarrowingConversionNode n, P p);
+
+  R visitInstanceOf(InstanceOfNode n, P p);
+
+  R visitTypeCast(TypeCastNode n, P p);
+
+  // Blocks
+
+  R visitSynchronized(SynchronizedNode n, P p);
+
+  // Statements
+  R visitAssertionError(AssertionErrorNode n, P p);
+
+  R visitThrow(ThrowNode n, P p);
+
+  // Cases
+  R visitCase(CaseNode n, P p);
+
+  // Method and constructor invocations
+  R visitMethodInvocation(MethodInvocationNode n, P p);
+
+  R visitObjectCreation(ObjectCreationNode n, P p);
+
+  R visitMemberReference(FunctionalInterfaceNode n, P p);
+
+  R visitArrayCreation(ArrayCreationNode n, P p);
+
+  // Type, package and class names
+  R visitArrayType(ArrayTypeNode n, P p);
+
+  R visitPrimitiveType(PrimitiveTypeNode n, P p);
+
+  R visitClassName(ClassNameNode n, P p);
+
+  R visitPackageName(PackageNameNode n, P p);
+
+  // Parameterized types
+  R visitParameterizedType(ParameterizedTypeNode n, P p);
+
+  // Marker nodes
+  R visitMarker(MarkerNode n, P p);
+
+  // Anonymous/inner/nested class declaration within a method
+  R visitClassDeclaration(ClassDeclarationNode classDeclarationNode, P p);
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java
new file mode 100644
index 0000000..a4826ad
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the not equal comparison:
+ *
+ * <pre>
+ *   <em>expression</em> != <em>expression</em>
+ * </pre>
+ */
+public class NotEqualNode extends BinaryOperationNode {
+
+  public NotEqualNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.NOT_EQUAL_TO;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitNotEqual(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " != " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof NotEqualNode)) {
+      return false;
+    }
+    NotEqualNode other = (NotEqualNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java
new file mode 100644
index 0000000..b366915
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java
@@ -0,0 +1,67 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for the unary 'nullchk' operation (generated by the Java compiler):
+ *
+ * <pre>
+ *   &lt;*nullchk*&gt;<em>expression</em>
+ * </pre>
+ */
+public class NullChkNode extends Node {
+
+  protected final Tree tree;
+  protected final Node operand;
+
+  public NullChkNode(Tree tree, Node operand) {
+    super(TreeUtils.typeOf(tree));
+    assert tree.getKind() == Kind.OTHER;
+    this.tree = tree;
+    this.operand = operand;
+  }
+
+  public Node getOperand() {
+    return operand;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitNullChk(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(+ " + getOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof NumericalPlusNode)) {
+      return false;
+    }
+    NumericalPlusNode other = (NumericalPlusNode) obj;
+    return getOperand().equals(other.getOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(NullChkNode.class, getOperand());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.singletonList(getOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java
new file mode 100644
index 0000000..7e1ed03
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java
@@ -0,0 +1,54 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the null literal.
+ *
+ * <pre>
+ *   <em>null</em>
+ * </pre>
+ */
+public class NullLiteralNode extends ValueLiteralNode {
+
+  /**
+   * Create a new NullLiteralNode.
+   *
+   * @param t the tree for the literal value
+   */
+  public NullLiteralNode(LiteralTree t) {
+    super(t);
+    assert t.getKind() == Tree.Kind.NULL_LITERAL;
+  }
+
+  @Override
+  public Void getValue() {
+    return (Void) tree.getValue();
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitNullLiteral(this, p);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof NullLiteralNode)) {
+      return false;
+    }
+    // super method compares values
+    return super.equals(obj);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java
new file mode 100644
index 0000000..0d7dab0
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the numerical addition:
+ *
+ * <pre>
+ *   <em>expression</em> + <em>expression</em>
+ * </pre>
+ */
+public class NumericalAdditionNode extends BinaryOperationNode {
+
+  public NumericalAdditionNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.PLUS || tree.getKind() == Kind.PLUS_ASSIGNMENT;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitNumericalAddition(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " + " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof NumericalAdditionNode)) {
+      return false;
+    }
+    NumericalAdditionNode other = (NumericalAdditionNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java
new file mode 100644
index 0000000..032e8c8
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java
@@ -0,0 +1,45 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.UnaryTree;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the unary minus operation:
+ *
+ * <pre>
+ *   - <em>expression</em>
+ * </pre>
+ */
+public class NumericalMinusNode extends UnaryOperationNode {
+
+  public NumericalMinusNode(UnaryTree tree, Node operand) {
+    super(tree, operand);
+    assert tree.getKind() == Kind.UNARY_MINUS;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitNumericalMinus(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(- " + getOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof NumericalMinusNode)) {
+      return false;
+    }
+    NumericalMinusNode other = (NumericalMinusNode) obj;
+    return getOperand().equals(other.getOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(NumericalMinusNode.class, getOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java
new file mode 100644
index 0000000..1dbd87f
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the numerical multiplication:
+ *
+ * <pre>
+ *   <em>expression</em> * <em>expression</em>
+ * </pre>
+ */
+public class NumericalMultiplicationNode extends BinaryOperationNode {
+
+  public NumericalMultiplicationNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.MULTIPLY;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitNumericalMultiplication(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " * " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof NumericalMultiplicationNode)) {
+      return false;
+    }
+    NumericalMultiplicationNode other = (NumericalMultiplicationNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java
new file mode 100644
index 0000000..d729736
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java
@@ -0,0 +1,45 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.UnaryTree;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the unary plus operation:
+ *
+ * <pre>
+ *   + <em>expression</em>
+ * </pre>
+ */
+public class NumericalPlusNode extends UnaryOperationNode {
+
+  public NumericalPlusNode(UnaryTree tree, Node operand) {
+    super(tree, operand);
+    assert tree.getKind() == Kind.UNARY_PLUS;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitNumericalPlus(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(+ " + getOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof NumericalPlusNode)) {
+      return false;
+    }
+    NumericalPlusNode other = (NumericalPlusNode) obj;
+    return getOperand().equals(other.getOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(NumericalPlusNode.class, getOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java
new file mode 100644
index 0000000..a22a96e
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the numerical subtraction:
+ *
+ * <pre>
+ *   <em>expression</em> - <em>expression</em>
+ * </pre>
+ */
+public class NumericalSubtractionNode extends BinaryOperationNode {
+
+  public NumericalSubtractionNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.MINUS;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitNumericalSubtraction(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " - " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof NumericalSubtractionNode)) {
+      return false;
+    }
+    NumericalSubtractionNode other = (NumericalSubtractionNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java
new file mode 100644
index 0000000..791e1ee
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java
@@ -0,0 +1,117 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.NewClassTree;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * A node for new object creation.
+ *
+ * <pre>
+ *   <em>new constructor(arg1, arg2, ...)</em>
+ * </pre>
+ */
+public class ObjectCreationNode extends Node {
+
+  protected final NewClassTree tree;
+  protected final Node constructor;
+  protected final List<Node> arguments;
+
+  // Class body for anonymous classes, otherwise null.
+  protected final @Nullable ClassDeclarationNode classbody;
+
+  public ObjectCreationNode(
+      NewClassTree tree,
+      Node constructor,
+      List<Node> arguments,
+      @Nullable ClassDeclarationNode classbody) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+    this.constructor = constructor;
+    this.arguments = arguments;
+    this.classbody = classbody;
+
+    // set assignment contexts for parameters
+    int i = 0;
+    ExecutableElement elem = TreeUtils.elementFromUse(tree);
+    if (elem != null) {
+      for (Node arg : arguments) {
+        AssignmentContext ctx = new AssignmentContext.MethodParameterContext(elem, i++);
+        arg.setAssignmentContext(ctx);
+      }
+    }
+  }
+
+  public Node getConstructor() {
+    return constructor;
+  }
+
+  public List<Node> getArguments() {
+    return arguments;
+  }
+
+  public Node getArgument(int i) {
+    return arguments.get(i);
+  }
+
+  public @Nullable Node getClassBody() {
+    return classbody;
+  }
+
+  @Override
+  public NewClassTree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitObjectCreation(this, p);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("new " + constructor + "(");
+    sb.append(StringsPlume.join(", ", arguments));
+    sb.append(")");
+    if (classbody != null) {
+      // TODO: maybe this can be done nicer...
+      sb.append(" ");
+      sb.append(classbody.toString());
+    }
+    return sb.toString();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ObjectCreationNode)) {
+      return false;
+    }
+    ObjectCreationNode other = (ObjectCreationNode) obj;
+    if (constructor == null && other.getConstructor() != null) {
+      return false;
+    }
+
+    return getConstructor().equals(other.getConstructor())
+        && getArguments().equals(other.getArguments());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(constructor, arguments);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    ArrayList<Node> list = new ArrayList<>(1 + arguments.size());
+    list.add(constructor);
+    list.addAll(arguments);
+    return list;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java
new file mode 100644
index 0000000..6ac2213
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java
@@ -0,0 +1,91 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.element.Element;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node representing a package name used in an expression such as a constructor invocation.
+ *
+ * <p><em>package</em>.class.object(...)
+ *
+ * <p>parent.<em>package</em>.class.object(...)
+ */
+public class PackageNameNode extends Node {
+
+  protected final Tree tree;
+  /** The package named by this node. */
+  protected final Element element;
+
+  /** The parent name, if any. */
+  protected final @Nullable PackageNameNode parent;
+
+  public PackageNameNode(IdentifierTree tree) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+    assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind";
+    this.element = TreeUtils.elementFromUse(tree);
+    this.parent = null;
+  }
+
+  public PackageNameNode(MemberSelectTree tree, PackageNameNode parent) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+    assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind";
+    this.element = TreeUtils.elementFromUse(tree);
+    this.parent = parent;
+  }
+
+  public Element getElement() {
+    return element;
+  }
+
+  /** The package name node for the parent package, {@code null} otherwise. */
+  public @Nullable PackageNameNode getParent() {
+    return parent;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitPackageName(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return getElement().getSimpleName().toString();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof PackageNameNode)) {
+      return false;
+    }
+    PackageNameNode other = (PackageNameNode) obj;
+    return Objects.equals(getParent(), other.getParent())
+        && getElement().equals(other.getElement());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getElement(), getParent());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    if (parent == null) {
+      return Collections.emptyList();
+    }
+    return Collections.singleton((Node) parent);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java
new file mode 100644
index 0000000..fb5fc13
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java
@@ -0,0 +1,65 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for a parameterized type occurring in an expression:
+ *
+ * <pre>
+ *   <em>type&lt;arg1, arg2&gt;</em>
+ * </pre>
+ *
+ * Parameterized types don't represent any computation to be done at runtime, so we might choose to
+ * represent them differently by modifying the {@link Node}s in which parameterized types can occur,
+ * such as {@link ObjectCreationNode}s.
+ */
+public class ParameterizedTypeNode extends Node {
+
+  protected final Tree tree;
+
+  public ParameterizedTypeNode(Tree t) {
+    super(TreeUtils.typeOf(t));
+    assert t instanceof ParameterizedTypeTree;
+    tree = t;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitParameterizedType(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return getTree().toString();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ParameterizedTypeNode)) {
+      return false;
+    }
+    ParameterizedTypeNode other = (ParameterizedTypeNode) obj;
+    return getTree().equals(other.getTree());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getTree());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java
new file mode 100644
index 0000000..f88baf1
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java
@@ -0,0 +1,63 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.PrimitiveTypeTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node representing a primitive type used in an expression such as a field access.
+ *
+ * <p><em>type</em> .class
+ */
+public class PrimitiveTypeNode extends Node {
+
+  protected final PrimitiveTypeTree tree;
+
+  /** For Types.isSameType. */
+  protected final Types types;
+
+  public PrimitiveTypeNode(PrimitiveTypeTree tree, Types types) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+    this.types = types;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitPrimitiveType(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return tree.toString();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof PrimitiveTypeNode)) {
+      return false;
+    }
+    PrimitiveTypeNode other = (PrimitiveTypeNode) obj;
+    return types.isSameType(getType(), other.getType());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getType());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java
new file mode 100644
index 0000000..8c189a3
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java
@@ -0,0 +1,99 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.AssignmentContext.LambdaReturnContext;
+import org.checkerframework.dataflow.cfg.node.AssignmentContext.MethodReturnContext;
+
+/**
+ * A node for a return statement:
+ *
+ * <pre>
+ *   return
+ *   return <em>expression</em>
+ * </pre>
+ *
+ * No ReturnNode is created for implicit return statements.
+ */
+public class ReturnNode extends Node {
+
+  protected final ReturnTree tree;
+  protected final @Nullable Node result;
+
+  public ReturnNode(ReturnTree t, @Nullable Node result, Types types, MethodTree methodTree) {
+    super(types.getNoType(TypeKind.NONE));
+    this.result = result;
+    tree = t;
+    if (result != null) {
+      result.setAssignmentContext(new MethodReturnContext(methodTree));
+    }
+  }
+
+  public ReturnNode(
+      ReturnTree t,
+      @Nullable Node result,
+      Types types,
+      LambdaExpressionTree lambda,
+      MethodSymbol methodSymbol) {
+    super(types.getNoType(TypeKind.NONE));
+    this.result = result;
+    tree = t;
+    if (result != null) {
+      result.setAssignmentContext(new LambdaReturnContext(methodSymbol));
+    }
+  }
+
+  /** The result of the return node, {@code null} otherwise. */
+  public @Nullable Node getResult() {
+    return result;
+  }
+
+  @Override
+  public ReturnTree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitReturn(this, p);
+  }
+
+  @Override
+  public String toString() {
+    if (result != null) {
+      return "return " + result;
+    }
+    return "return";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ReturnNode)) {
+      return false;
+    }
+    ReturnNode other = (ReturnNode) obj;
+    return Objects.equals(result, other.result);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(ReturnNode.class, result);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    if (result == null) {
+      return Collections.emptyList();
+    } else {
+      return Collections.singletonList(result);
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java
new file mode 100644
index 0000000..e491ca0
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java
@@ -0,0 +1,57 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for a short literal. For example:
+ *
+ * <pre>
+ *   <em>5</em>
+ *   <em>0x8fff</em>
+ * </pre>
+ *
+ * Java source and the AST representation do not have "short" literals. They have integer literals
+ * that may be narrowed to shorts depending on context.
+ */
+// TODO: If we use explicit NarrowingConversionNodes, do we need ShortLiteralNodes too?
+public class ShortLiteralNode extends ValueLiteralNode {
+
+  /**
+   * Create a new ShortLiteralNode.
+   *
+   * @param t the tree for the literal value
+   */
+  public ShortLiteralNode(LiteralTree t) {
+    super(t);
+    assert t.getKind() == Tree.Kind.INT_LITERAL;
+  }
+
+  @Override
+  public Short getValue() {
+    return (Short) tree.getValue();
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitShortLiteral(this, p);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    // test that obj is a ShortLiteralNode
+    if (!(obj instanceof ShortLiteralNode)) {
+      return false;
+    }
+    // super method compares values
+    return super.equals(obj);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java
new file mode 100644
index 0000000..40f62d8
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for bitwise right shift operations with sign extension:
+ *
+ * <pre>
+ *   <em>expression</em> &gt;&gt; <em>expression</em>
+ * </pre>
+ */
+public class SignedRightShiftNode extends BinaryOperationNode {
+
+  public SignedRightShiftNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.RIGHT_SHIFT;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitSignedRightShift(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " >> " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof SignedRightShiftNode)) {
+      return false;
+    }
+    SignedRightShiftNode other = (SignedRightShiftNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateAssignmentNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateAssignmentNode.java
new file mode 100644
index 0000000..d56f65c
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateAssignmentNode.java
@@ -0,0 +1,73 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for the string concatenation compound assignment:
+ *
+ * <pre>
+ *   <em>variable</em> += <em>expression</em>
+ * </pre>
+ */
+public class StringConcatenateAssignmentNode extends Node {
+  protected final Tree tree;
+  protected final Node left;
+  protected final Node right;
+
+  public StringConcatenateAssignmentNode(Tree tree, Node left, Node right) {
+    super(TreeUtils.typeOf(tree));
+    assert tree.getKind() == Kind.PLUS_ASSIGNMENT;
+    this.tree = tree;
+    this.left = left;
+    this.right = right;
+  }
+
+  public Node getLeftOperand() {
+    return left;
+  }
+
+  public Node getRightOperand() {
+    return right;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitStringConcatenateAssignment(this, p);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Arrays.asList(getLeftOperand(), getRightOperand());
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " += " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (obj == null || !(obj instanceof StringConcatenateAssignmentNode)) {
+      return false;
+    }
+    StringConcatenateAssignmentNode other = (StringConcatenateAssignmentNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java
new file mode 100644
index 0000000..16a4c13
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for string concatenation:
+ *
+ * <pre>
+ *   <em>expression</em> + <em>expression</em>
+ * </pre>
+ */
+public class StringConcatenateNode extends BinaryOperationNode {
+
+  public StringConcatenateNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.PLUS;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitStringConcatenate(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " + " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof StringConcatenateNode)) {
+      return false;
+    }
+    StringConcatenateNode other = (StringConcatenateNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java
new file mode 100644
index 0000000..418bd98
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java
@@ -0,0 +1,74 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the string conversion operation. See JLS 5.1.11 for the definition of string
+ * conversion.
+ *
+ * <p>A {@link StringConversionNode} does not correspond to any tree node in the parsed AST. It is
+ * introduced when a value of non-string type appears in a context that requires a {@link String},
+ * such as in a string concatenation. A {@link StringConversionNode} should be treated as a
+ * potential call to the toString method of its operand, but does not necessarily call any method
+ * because null is converted to the string "null".
+ *
+ * <p>Conversion of primitive types to Strings requires first boxing and then string conversion.
+ */
+public class StringConversionNode extends Node {
+
+  protected final Tree tree;
+  protected final Node operand;
+
+  // TODO: The type of a string conversion should be a final
+  // TypeMirror representing java.lang.String. Currently we require
+  // the caller to pass in a TypeMirror instead of creating one
+  // through the javax.lang.model.type.Types interface.
+  public StringConversionNode(Tree tree, Node operand, TypeMirror type) {
+    super(type);
+    this.tree = tree;
+    this.operand = operand;
+  }
+
+  public Node getOperand() {
+    return operand;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitStringConversion(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "StringConversion(" + getOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof StringConversionNode)) {
+      return false;
+    }
+    StringConversionNode other = (StringConversionNode) obj;
+    return getOperand().equals(other.getOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(StringConversionNode.class, getOperand());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.singletonList(getOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java
new file mode 100644
index 0000000..8447ea6
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java
@@ -0,0 +1,57 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for an string literal. For example:
+ *
+ * <pre>
+ *   <em>"abc"</em>
+ * </pre>
+ */
+public class StringLiteralNode extends ValueLiteralNode {
+
+  /**
+   * Create a new StringLiteralNode.
+   *
+   * @param t the tree for the literal value
+   */
+  public StringLiteralNode(LiteralTree t) {
+    super(t);
+    assert t.getKind() == Tree.Kind.STRING_LITERAL;
+  }
+
+  @Override
+  public String getValue() {
+    return (String) tree.getValue();
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitStringLiteral(this, p);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    // test that obj is a StringLiteralNode
+    if (!(obj instanceof StringLiteralNode)) {
+      return false;
+    }
+    // super method compares values
+    return super.equals(obj);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public String toString() {
+    return "\"" + super.toString() + "\"";
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SuperNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SuperNode.java
new file mode 100644
index 0000000..cd068be
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SuperNode.java
@@ -0,0 +1,61 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for a reference to 'super'.
+ *
+ * <pre>
+ *   <em>super</em>
+ * </pre>
+ */
+public class SuperNode extends Node {
+
+  protected final Tree tree;
+
+  public SuperNode(Tree t) {
+    super(TreeUtils.typeOf(t));
+    assert t instanceof IdentifierTree && ((IdentifierTree) t).getName().contentEquals("super");
+    tree = t;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitSuper(this, p);
+  }
+
+  public String getName() {
+    return "super";
+  }
+
+  @Override
+  public String toString() {
+    return getName();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    return obj instanceof SuperNode;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getName());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java
new file mode 100644
index 0000000..3dd16bc
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java
@@ -0,0 +1,76 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * This represents the start and end of synchronized code block. If startOfBlock == true it is the
+ * node preceding a synchronized code block. Otherwise it is the node immediately after a
+ * synchronized code block.
+ */
+public class SynchronizedNode extends Node {
+
+  protected final @Nullable Tree tree;
+  protected final Node expression;
+  protected final boolean startOfBlock;
+
+  public SynchronizedNode(@Nullable Tree tree, Node expression, boolean startOfBlock, Types types) {
+    super(types.getNoType(TypeKind.NONE));
+    this.tree = tree;
+    this.expression = expression;
+    this.startOfBlock = startOfBlock;
+  }
+
+  @Override
+  public @Nullable Tree getTree() {
+    return tree;
+  }
+
+  public Node getExpression() {
+    return expression;
+  }
+
+  public boolean getIsStartOfBlock() {
+    return startOfBlock;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitSynchronized(this, p);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("synchronized (");
+    sb.append(expression);
+    sb.append(")");
+    return sb.toString();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof SynchronizedNode)) {
+      return false;
+    }
+    SynchronizedNode other = (SynchronizedNode) obj;
+    return Objects.equals(getTree(), other.getTree())
+        && getExpression().equals(other.getExpression())
+        && startOfBlock == other.startOfBlock;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(tree, startOfBlock, getExpression());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java
new file mode 100644
index 0000000..ae3d415
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java
@@ -0,0 +1,82 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for a conditional expression:
+ *
+ * <pre>
+ *   <em>expression</em> ? <em>expression</em> : <em>expression</em>
+ * </pre>
+ */
+public class TernaryExpressionNode extends Node {
+
+  protected final ConditionalExpressionTree tree;
+  protected final Node condition;
+  protected final Node thenOperand;
+  protected final Node elseOperand;
+
+  public TernaryExpressionNode(
+      ConditionalExpressionTree tree, Node condition, Node thenOperand, Node elseOperand) {
+    super(TreeUtils.typeOf(tree));
+    assert tree.getKind() == Kind.CONDITIONAL_EXPRESSION;
+    this.tree = tree;
+    this.condition = condition;
+    this.thenOperand = thenOperand;
+    this.elseOperand = elseOperand;
+  }
+
+  public Node getConditionOperand() {
+    return condition;
+  }
+
+  public Node getThenOperand() {
+    return thenOperand;
+  }
+
+  public Node getElseOperand() {
+    return elseOperand;
+  }
+
+  @Override
+  public ConditionalExpressionTree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitTernaryExpression(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getConditionOperand() + " ? " + getThenOperand() + " : " + getElseOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof TernaryExpressionNode)) {
+      return false;
+    }
+    TernaryExpressionNode other = (TernaryExpressionNode) obj;
+    return getConditionOperand().equals(other.getConditionOperand())
+        && getThenOperand().equals(other.getThenOperand())
+        && getElseOperand().equals(other.getElseOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getConditionOperand(), getThenOperand(), getElseOperand());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Arrays.asList(getConditionOperand(), getThenOperand(), getElseOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThisNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThisNode.java
new file mode 100644
index 0000000..9cf09ec
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThisNode.java
@@ -0,0 +1,40 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for a reference to 'this', either implicit or explicit.
+ *
+ * <pre>
+ *   <em>this</em>
+ * </pre>
+ */
+public abstract class ThisNode extends Node {
+
+  protected ThisNode(TypeMirror type) {
+    super(type);
+  }
+
+  public String getName() {
+    return "this";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    return obj instanceof ThisNode;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getName());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java
new file mode 100644
index 0000000..af72db4
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java
@@ -0,0 +1,67 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.ThrowTree;
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for exception throws:
+ *
+ * <pre>
+ *   <em>throw</em> expr
+ * </pre>
+ */
+public class ThrowNode extends Node {
+
+  protected final ThrowTree tree;
+  protected final Node expression;
+
+  public ThrowNode(ThrowTree tree, Node expression, Types types) {
+    super(types.getNoType(TypeKind.NONE));
+    this.tree = tree;
+    this.expression = expression;
+  }
+
+  public Node getExpression() {
+    return expression;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitThrow(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "throw " + expression;
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ThrowNode)) {
+      return false;
+    }
+    ThrowNode other = (ThrowNode) obj;
+    return getExpression().equals(other.getExpression());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(ThrowNode.class, expression);
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.singletonList(expression);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java
new file mode 100644
index 0000000..861832b
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java
@@ -0,0 +1,68 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for the cast operator:
+ *
+ * <p>(<em>Point</em>) <em>x</em>
+ */
+public class TypeCastNode extends Node {
+
+  protected final Tree tree;
+  protected final Node operand;
+
+  /** For Types.isSameType. */
+  protected final Types types;
+
+  public TypeCastNode(Tree tree, Node operand, TypeMirror type, Types types) {
+    super(type);
+    this.tree = tree;
+    this.operand = operand;
+    this.types = types;
+  }
+
+  public Node getOperand() {
+    return operand;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitTypeCast(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getType() + ")" + getOperand();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof TypeCastNode)) {
+      return false;
+    }
+    TypeCastNode other = (TypeCastNode) obj;
+    return getOperand().equals(other.getOperand()) && types.isSameType(getType(), other.getType());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getType(), getOperand());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.singletonList(getOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnaryOperationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnaryOperationNode.java
new file mode 100644
index 0000000..719afa3
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnaryOperationNode.java
@@ -0,0 +1,43 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.UnaryTree;
+import java.util.Collection;
+import java.util.Collections;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for a postfix or an unary expression.
+ *
+ * <p>For example:
+ *
+ * <pre>
+ *   <em>operator</em> <em>expressionNode</em>
+ *
+ *   <em>expressionNode</em> <em>operator</em>
+ * </pre>
+ */
+public abstract class UnaryOperationNode extends Node {
+
+  protected final UnaryTree tree;
+  protected final Node operand;
+
+  protected UnaryOperationNode(UnaryTree tree, Node operand) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+    this.operand = operand;
+  }
+
+  public Node getOperand() {
+    return this.operand;
+  }
+
+  @Override
+  public UnaryTree getTree() {
+    return tree;
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.singletonList(getOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java
new file mode 100644
index 0000000..7d7666f
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java
@@ -0,0 +1,46 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A node for bitwise right shift operations with zero extension:
+ *
+ * <pre>
+ *   <em>expression</em> &gt;&gt;&gt; <em>expression</em>
+ * </pre>
+ */
+public class UnsignedRightShiftNode extends BinaryOperationNode {
+
+  public UnsignedRightShiftNode(BinaryTree tree, Node left, Node right) {
+    super(tree, left, right);
+    assert tree.getKind() == Kind.UNSIGNED_RIGHT_SHIFT;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitUnsignedRightShift(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "(" + getLeftOperand() + " >>> " + getRightOperand() + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof UnsignedRightShiftNode)) {
+      return false;
+    }
+    UnsignedRightShiftNode other = (UnsignedRightShiftNode) obj;
+    return getLeftOperand().equals(other.getLeftOperand())
+        && getRightOperand().equals(other.getRightOperand());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLeftOperand(), getRightOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java
new file mode 100644
index 0000000..7953433
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java
@@ -0,0 +1,75 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.LiteralTree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for a literals that have some form of value:
+ *
+ * <ul>
+ *   <li>integer literal
+ *   <li>long literal
+ *   <li>char literal
+ *   <li>string literal
+ *   <li>float literal
+ *   <li>double literal
+ *   <li>boolean literal
+ *   <li>null literal
+ * </ul>
+ */
+public abstract class ValueLiteralNode extends Node {
+
+  /** The tree for the value literal. */
+  protected final LiteralTree tree;
+
+  /**
+   * Returns the value of the literal, null for the null literal.
+   *
+   * @return the value of the literal, null for the null literal
+   */
+  public abstract @Nullable Object getValue();
+
+  protected ValueLiteralNode(LiteralTree tree) {
+    super(TreeUtils.typeOf(tree));
+    this.tree = tree;
+  }
+
+  @Override
+  public LiteralTree getTree() {
+    return tree;
+  }
+
+  @Override
+  public String toString() {
+    return String.valueOf(getValue());
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof ValueLiteralNode)) {
+      return false;
+    }
+    ValueLiteralNode other = (ValueLiteralNode) obj;
+    Object val = getValue();
+    Object otherVal = other.getValue();
+    return Objects.equals(val, otherVal);
+  }
+
+  @Override
+  public int hashCode() {
+    // value might be null
+    return Objects.hash(this.getClass(), getValue());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java
new file mode 100644
index 0000000..3efc835
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java
@@ -0,0 +1,70 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.VariableTree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A node for a variable declaration, including local variables and fields:
+ *
+ * <pre>
+ *   <em>modifier</em> <em>type</em> <em>identifier</em>;
+ * </pre>
+ *
+ * Note: Does not have an initializer block, as that will be translated to a separate {@link
+ * AssignmentNode}.
+ */
+public class VariableDeclarationNode extends Node {
+
+  protected final VariableTree tree;
+  protected final String name;
+
+  // TODO: make modifier accessible
+
+  public VariableDeclarationNode(VariableTree t) {
+    super(TreeUtils.typeOf(t));
+    tree = t;
+    name = tree.getName().toString();
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  @Override
+  public VariableTree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitVariableDeclaration(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof VariableDeclarationNode)) {
+      return false;
+    }
+    VariableDeclarationNode other = (VariableDeclarationNode) obj;
+    return getName().equals(other.getName());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getName());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.emptyList();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java
new file mode 100644
index 0000000..ea389af
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java
@@ -0,0 +1,69 @@
+package org.checkerframework.dataflow.cfg.node;
+
+import com.sun.source.tree.Tree;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * A node for the widening primitive conversion operation. See JLS 5.1.2 for the definition of
+ * widening primitive conversion.
+ *
+ * <p>A {@link WideningConversionNode} does not correspond to any tree node in the parsed AST. It is
+ * introduced when a value of some primitive type appears in a context that requires a different
+ * primitive with more bits of precision.
+ */
+public class WideningConversionNode extends Node {
+
+  protected final Tree tree;
+  protected final Node operand;
+
+  public WideningConversionNode(Tree tree, Node operand, TypeMirror type) {
+    super(type);
+    assert TypesUtils.isPrimitive(type) : "non-primitive type in widening conversion";
+    this.tree = tree;
+    this.operand = operand;
+  }
+
+  public Node getOperand() {
+    return operand;
+  }
+
+  @Override
+  public Tree getTree() {
+    return tree;
+  }
+
+  @Override
+  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
+    return visitor.visitWideningConversion(this, p);
+  }
+
+  @Override
+  public String toString() {
+    return "WideningConversion(" + getOperand() + ", " + type + ")";
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof WideningConversionNode)) {
+      return false;
+    }
+    WideningConversionNode other = (WideningConversionNode) obj;
+    return getOperand().equals(other.getOperand())
+        && TypesUtils.areSamePrimitiveTypes(getType(), other.getType());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(WideningConversionNode.class, getOperand());
+  }
+
+  @Override
+  public Collection<Node> getOperands() {
+    return Collections.singletonList(getOperand());
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java
new file mode 100644
index 0000000..8a0e694
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java
@@ -0,0 +1,29 @@
+package org.checkerframework.dataflow.cfg.playground;
+
+import org.checkerframework.dataflow.analysis.ForwardAnalysis;
+import org.checkerframework.dataflow.analysis.ForwardAnalysisImpl;
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizeLauncher;
+import org.checkerframework.dataflow.constantpropagation.Constant;
+import org.checkerframework.dataflow.constantpropagation.ConstantPropagationStore;
+import org.checkerframework.dataflow.constantpropagation.ConstantPropagationTransfer;
+
+public class ConstantPropagationPlayground {
+
+  /** Run constant propagation for a specific file and create a PDF of the CFG in the end. */
+  public static void main(String[] args) {
+
+    /* Configuration: change as appropriate */
+    String inputFile = "cfg-input.java"; // input file name and path
+    String outputDir = "cfg"; // output directory
+    String method = "test"; // name of the method to analyze
+    String clazz = "Test"; // name of the class to consider
+
+    // run the analysis and create a PDF file
+    ConstantPropagationTransfer transfer = new ConstantPropagationTransfer();
+    ForwardAnalysis<Constant, ConstantPropagationStore, ConstantPropagationTransfer>
+        forwardAnalysis = new ForwardAnalysisImpl<>(transfer);
+    CFGVisualizeLauncher cfgVisualizeLauncher = new CFGVisualizeLauncher();
+    cfgVisualizeLauncher.generateDOTofCFG(
+        inputFile, outputDir, method, clazz, true, false, forwardAnalysis);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/LiveVariablePlayground.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/LiveVariablePlayground.java
new file mode 100644
index 0000000..f2770d5
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/LiveVariablePlayground.java
@@ -0,0 +1,34 @@
+package org.checkerframework.dataflow.cfg.playground;
+
+import org.checkerframework.dataflow.analysis.BackwardAnalysis;
+import org.checkerframework.dataflow.analysis.BackwardAnalysisImpl;
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizeLauncher;
+import org.checkerframework.dataflow.livevariable.LiveVarStore;
+import org.checkerframework.dataflow.livevariable.LiveVarTransfer;
+import org.checkerframework.dataflow.livevariable.LiveVarValue;
+
+/** The playground of live variable analysis. */
+public class LiveVariablePlayground {
+
+  /**
+   * Run live variable analysis for a specific file and create a PDF of the CFG in the end.
+   *
+   * @param args input arguments
+   */
+  public static void main(String[] args) {
+
+    /* Configuration: change as appropriate */
+    String inputFile = "Test.java"; // input file name and path
+    String outputDir = "cfg"; // output directory
+    String method = "test"; // name of the method to analyze
+    String clazz = "Test"; // name of the class to consider
+
+    // Run the analysis and create a PDF file
+    LiveVarTransfer transfer = new LiveVarTransfer();
+    BackwardAnalysis<LiveVarValue, LiveVarStore, LiveVarTransfer> backwardAnalysis =
+        new BackwardAnalysisImpl<>(transfer);
+    CFGVisualizeLauncher cfgVisualizeLauncher = new CFGVisualizeLauncher();
+    cfgVisualizeLauncher.generateDOTofCFG(
+        inputFile, outputDir, method, clazz, true, true, backwardAnalysis);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java
new file mode 100644
index 0000000..8dc1767
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java
@@ -0,0 +1,462 @@
+package org.checkerframework.dataflow.cfg.visualize;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.AbstractValue;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.dataflow.analysis.Analysis.Direction;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.analysis.TransferFunction;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.block.ConditionalBlock;
+import org.checkerframework.dataflow.cfg.block.ExceptionBlock;
+import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlock;
+import org.checkerframework.dataflow.cfg.block.SpecialBlock;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.javacutil.BugInCF;
+import org.plumelib.util.StringsPlume;
+import org.plumelib.util.UniqueId;
+
+/**
+ * This abstract class makes implementing a {@link CFGVisualizer} easier. Some of the methods in
+ * {@link CFGVisualizer} are already implemented in this abstract class, but can be overridden if
+ * necessary.
+ *
+ * @param <V> the abstract value type to be tracked by the analysis
+ * @param <S> the store type used in the analysis
+ * @param <T> the transfer function type that is used to approximate runtime behavior
+ * @see DOTCFGVisualizer
+ * @see StringCFGVisualizer
+ */
+public abstract class AbstractCFGVisualizer<
+        V extends AbstractValue<V>, S extends Store<S>, T extends TransferFunction<V, S>>
+    implements CFGVisualizer<V, S, T> {
+
+  /**
+   * If {@code true}, {@link CFGVisualizer} returns more detailed information.
+   *
+   * <p>Initialized in {@link #init(Map)}.
+   */
+  protected boolean verbose;
+
+  /** The line separator. */
+  protected final String lineSeparator = System.lineSeparator();
+
+  /** The indentation for elements of the store. */
+  protected final String storeEntryIndent = "  ";
+
+  @Override
+  public void init(Map<String, Object> args) {
+    this.verbose = toBoolean(args.get("verbose"));
+  }
+
+  /**
+   * Convert the value to boolean, by parsing a string or casting any other value. null converts to
+   * false.
+   *
+   * @param o an object to convert to boolean
+   * @return {@code o} converted to boolean
+   */
+  private static boolean toBoolean(@Nullable Object o) {
+    if (o == null) {
+      return false;
+    }
+    if (o instanceof String) {
+      return Boolean.parseBoolean((String) o);
+    }
+    return (boolean) o;
+  }
+
+  /**
+   * Visualize a control flow graph.
+   *
+   * @param cfg the current control flow graph
+   * @param entry the entry block of the control flow graph
+   * @param analysis the current analysis
+   * @return the representation of the control flow graph
+   */
+  protected String visualizeGraph(
+      ControlFlowGraph cfg, Block entry, @Nullable Analysis<V, S, T> analysis) {
+    return visualizeGraphHeader()
+        + visualizeGraphWithoutHeaderAndFooter(cfg, entry, analysis)
+        + visualizeGraphFooter();
+  }
+
+  /**
+   * Helper method to visualize a control flow graph, without outputting a header or footer.
+   *
+   * @param cfg the control flow graph
+   * @param entry the entry block of the control flow graph
+   * @param analysis the current analysis
+   * @return the String representation of the control flow graph
+   */
+  protected String visualizeGraphWithoutHeaderAndFooter(
+      ControlFlowGraph cfg, Block entry, @Nullable Analysis<V, S, T> analysis) {
+    Set<Block> visited = new LinkedHashSet<>();
+    StringBuilder sbGraph = new StringBuilder();
+    Queue<Block> workList = new ArrayDeque<>();
+    Block cur = entry;
+    visited.add(entry);
+    while (cur != null) {
+      handleSuccessorsHelper(cur, visited, workList, sbGraph);
+      cur = workList.poll();
+    }
+    sbGraph.append(lineSeparator);
+    sbGraph.append(visualizeNodes(visited, cfg, analysis));
+    return sbGraph.toString();
+  }
+
+  /**
+   * Outputs, to sbGraph, a visualization of a block's edges, but not the block itself. (The block
+   * itself is output elsewhere.) Also adds the successors of the block to the work list and the
+   * visited blocks list.
+   *
+   * @param cur the current block
+   * @param visited the set of blocks that have already been visited or are in the work list; side
+   *     effected by this method
+   * @param workList the queue of blocks to be processed; side effected by this method
+   * @param sbGraph the {@link StringBuilder} to store the graph; side effected by this method
+   */
+  protected void handleSuccessorsHelper(
+      Block cur, Set<Block> visited, Queue<Block> workList, StringBuilder sbGraph) {
+    if (cur.getType() == Block.BlockType.CONDITIONAL_BLOCK) {
+      ConditionalBlock ccur = ((ConditionalBlock) cur);
+      Block thenSuccessor = ccur.getThenSuccessor();
+      sbGraph.append(
+          visualizeEdge(ccur.getUid(), thenSuccessor.getUid(), ccur.getThenFlowRule().toString()));
+      sbGraph.append(lineSeparator);
+      addBlock(thenSuccessor, visited, workList);
+      Block elseSuccessor = ccur.getElseSuccessor();
+      sbGraph.append(
+          visualizeEdge(ccur.getUid(), elseSuccessor.getUid(), ccur.getElseFlowRule().toString()));
+      sbGraph.append(lineSeparator);
+      addBlock(elseSuccessor, visited, workList);
+    } else {
+      SingleSuccessorBlock sscur = (SingleSuccessorBlock) cur;
+      Block succ = sscur.getSuccessor();
+      if (succ != null) {
+        sbGraph.append(visualizeEdge(cur.getUid(), succ.getUid(), sscur.getFlowRule().name()));
+        sbGraph.append(lineSeparator);
+        addBlock(succ, visited, workList);
+      }
+    }
+    if (cur.getType() == Block.BlockType.EXCEPTION_BLOCK) {
+      ExceptionBlock ecur = (ExceptionBlock) cur;
+      for (Map.Entry<TypeMirror, Set<Block>> e : ecur.getExceptionalSuccessors().entrySet()) {
+        TypeMirror cause = e.getKey();
+        String exception = cause.toString();
+        if (exception.startsWith("java.lang.")) {
+          exception = exception.replace("java.lang.", "");
+        }
+        for (Block b : e.getValue()) {
+          sbGraph.append(visualizeEdge(cur.getUid(), b.getUid(), exception));
+          sbGraph.append(lineSeparator);
+          addBlock(b, visited, workList);
+        }
+      }
+    }
+  }
+
+  /**
+   * Checks whether a block exists in the visited blocks list, and, if not, adds it to the visited
+   * blocks list and the work list.
+   *
+   * @param b the block to check
+   * @param visited the set of blocks that have already been visited or are in the work list
+   * @param workList the queue of blocks to be processed
+   */
+  protected void addBlock(Block b, Set<Block> visited, Queue<Block> workList) {
+    if (visited.add(b)) {
+      workList.add(b);
+    }
+  }
+
+  /**
+   * Helper method to visualize a block.
+   *
+   * <p>NOTE: The output ends with a separator, only if an "after" store is visualized. The client
+   * {@link #visualizeBlock} should correct this if needed.
+   *
+   * @param bb the block
+   * @param analysis the current analysis
+   * @param separator the line separator. Examples: "\\l" for left justification in {@link
+   *     DOTCFGVisualizer} (this is really a terminator, not a separator), "\n" to add a new line in
+   *     {@link StringCFGVisualizer}
+   * @return the String representation of the block
+   */
+  protected String visualizeBlockHelper(
+      Block bb, @Nullable Analysis<V, S, T> analysis, String separator) {
+    StringBuilder sbBlock = new StringBuilder();
+    String contents = loopOverBlockContents(bb, analysis, separator);
+    if (!contents.isEmpty()) {
+      sbBlock.append(contents);
+    }
+    if (sbBlock.length() == 0) {
+      // Nothing got appended; use default text for empty block
+      if (bb.getType() == Block.BlockType.SPECIAL_BLOCK) {
+        sbBlock.append(visualizeSpecialBlock((SpecialBlock) bb));
+      } else if (bb.getType() == Block.BlockType.CONDITIONAL_BLOCK) {
+        sbBlock.append(visualizeConditionalBlock((ConditionalBlock) bb));
+      } else {
+        sbBlock.append("<empty block>");
+      }
+    }
+
+    // Visualize transfer input if necessary.
+    if (analysis != null) {
+      sbBlock.insert(0, visualizeBlockTransferInputBefore(bb, analysis) + separator);
+      if (verbose) {
+        Node lastNode = bb.getLastNode();
+        if (lastNode != null) {
+          if (!sbBlock.toString().endsWith(separator)) {
+            sbBlock.append(separator);
+          }
+          sbBlock.append(visualizeBlockTransferInputAfter(bb, analysis) + separator);
+        }
+      }
+    }
+    return sbBlock.toString();
+  }
+
+  /**
+   * Iterates over the block content and visualizes all the nodes in it.
+   *
+   * @param bb the block
+   * @param analysis the current analysis
+   * @param separator the separator between the nodes of the block
+   * @return the String representation of the contents of the block
+   */
+  protected String loopOverBlockContents(
+      Block bb, @Nullable Analysis<V, S, T> analysis, String separator) {
+
+    List<Node> contents = addBlockContent(bb);
+    StringJoiner sjBlockContents = new StringJoiner(separator);
+    for (Node t : contents) {
+      sjBlockContents.add(visualizeBlockNode(t, analysis));
+    }
+    return sjBlockContents.toString();
+  }
+
+  /**
+   * Returns the contents of the block.
+   *
+   * @param bb the block
+   * @return the contents of the block, as a list of nodes
+   */
+  protected List<Node> addBlockContent(Block bb) {
+    return bb.getNodes();
+  }
+
+  /**
+   * Format the given object as a String suitable for the output format, i.e. with format-specific
+   * characters escaped.
+   *
+   * @param obj an object
+   * @return the formatted String from the given object
+   */
+  protected abstract String format(Object obj);
+
+  @Override
+  public String visualizeBlockNode(Node t, @Nullable Analysis<V, S, T> analysis) {
+    StringBuilder sbBlockNode = new StringBuilder();
+    sbBlockNode.append(format(t)).append("   [ ").append(getNodeSimpleName(t)).append(" ]");
+    if (analysis != null) {
+      V value = analysis.getValue(t);
+      if (value != null) {
+        sbBlockNode.append("    > ").append(format(value));
+      }
+    }
+    return sbBlockNode.toString();
+  }
+
+  /** Whether to visualize before or after a block. */
+  protected enum VisualizeWhere {
+    /** Visualize before the block. */
+    BEFORE,
+    /** Visualize after the block. */
+    AFTER
+  }
+
+  /**
+   * Visualize the transfer input before or after the given block.
+   *
+   * @param where either BEFORE or AFTER
+   * @param bb a block
+   * @param analysis the current analysis
+   * @param separator the line separator. Examples: "\\l" for left justification in {@link
+   *     DOTCFGVisualizer} (which is actually a line TERMINATOR, not a separator!), "\n" to add a
+   *     new line in {@link StringCFGVisualizer}
+   * @return the visualization of the transfer input before or after the given block
+   */
+  protected String visualizeBlockTransferInputHelper(
+      VisualizeWhere where, Block bb, Analysis<V, S, T> analysis, String separator) {
+    if (analysis == null) {
+      throw new BugInCF(
+          "analysis must be non-null when visualizing the transfer input of a block.");
+    }
+
+    Direction analysisDirection = analysis.getDirection();
+
+    S regularStore;
+    S thenStore = null;
+    S elseStore = null;
+    boolean isTwoStores = false;
+
+    UniqueId storesFrom;
+
+    if (analysisDirection == Direction.FORWARD && where == VisualizeWhere.AFTER) {
+      regularStore = analysis.getResult().getStoreAfter(bb);
+      storesFrom = analysis.getResult();
+    } else if (analysisDirection == Direction.BACKWARD && where == VisualizeWhere.BEFORE) {
+      regularStore = analysis.getResult().getStoreBefore(bb);
+      storesFrom = analysis.getResult();
+    } else {
+      TransferInput<V, S> input = analysis.getInput(bb);
+      assert input != null : "@AssumeAssertion(nullness): invariant";
+      storesFrom = input;
+      isTwoStores = input.containsTwoStores();
+      regularStore = input.getRegularStore();
+      thenStore = input.getThenStore();
+      elseStore = input.getElseStore();
+    }
+
+    StringBuilder sbStore = new StringBuilder();
+    if (verbose) {
+      sbStore.append(storesFrom.getClassAndUid() + separator);
+    }
+    sbStore.append(where == VisualizeWhere.BEFORE ? "Before: " : "After: ");
+
+    if (!isTwoStores) {
+      sbStore.append(visualizeStore(regularStore));
+    } else {
+      assert thenStore != null : "@AssumeAssertion(nullness): invariant";
+      assert elseStore != null : "@AssumeAssertion(nullness): invariant";
+      sbStore.append("then=");
+      sbStore.append(visualizeStore(thenStore));
+      sbStore.append(",");
+      sbStore.append(separator);
+      sbStore.append("else=");
+      sbStore.append(visualizeStore(elseStore));
+    }
+    if (where == VisualizeWhere.BEFORE) {
+      sbStore.append(separator + "~~~~~~~~~");
+    } else {
+      sbStore.insert(0, "~~~~~~~~~" + separator);
+    }
+    return sbStore.toString();
+  }
+
+  /**
+   * Visualize a special block.
+   *
+   * @param sbb the special block
+   * @return the String representation of the special block
+   */
+  protected String visualizeSpecialBlockHelper(SpecialBlock sbb) {
+    switch (sbb.getSpecialType()) {
+      case ENTRY:
+        return "<entry>";
+      case EXIT:
+        return "<exit>";
+      case EXCEPTIONAL_EXIT:
+        return "<exceptional-exit>";
+      default:
+        throw new BugInCF("Unrecognized special block type: " + sbb.getType());
+    }
+  }
+
+  /**
+   * Generate the order of processing blocks. Because a block may appears more than once in {@link
+   * ControlFlowGraph#getDepthFirstOrderedBlocks()}, the orders of each block are stored in a
+   * separate array list.
+   *
+   * @param cfg the current control flow graph
+   * @return an IdentityHashMap that maps from blocks to their orders
+   */
+  protected IdentityHashMap<Block, List<Integer>> getProcessOrder(ControlFlowGraph cfg) {
+    IdentityHashMap<Block, List<Integer>> depthFirstOrder = new IdentityHashMap<>();
+    int count = 1;
+    for (Block b : cfg.getDepthFirstOrderedBlocks()) {
+      depthFirstOrder.computeIfAbsent(b, k -> new ArrayList<>());
+      @SuppressWarnings("nullness:assignment") // computeIfAbsent's function doesn't return null
+      @NonNull List<Integer> blockIds = depthFirstOrder.get(b);
+      blockIds.add(count++);
+    }
+    return depthFirstOrder;
+  }
+
+  @Override
+  public String visualizeStore(S store) {
+    return store.visualize(this);
+  }
+
+  /**
+   * Generate the String representation of the nodes of a control flow graph.
+   *
+   * @param blocks the set of all the blocks in a control flow graph
+   * @param cfg the control flow graph
+   * @param analysis the current analysis
+   * @return the String representation of the nodes
+   */
+  protected abstract String visualizeNodes(
+      Set<Block> blocks, ControlFlowGraph cfg, @Nullable Analysis<V, S, T> analysis);
+
+  /**
+   * Generate the String representation of an edge.
+   *
+   * @param sId a representation of the current block, such as its ID
+   * @param eId a representation of the successor block, such as its ID
+   * @param flowRule the content of the edge
+   * @return the String representation of the edge
+   */
+  protected abstract String visualizeEdge(Object sId, Object eId, String flowRule);
+
+  /**
+   * Return the header of the generated graph.
+   *
+   * @return the String representation of the header of the control flow graph
+   */
+  protected abstract String visualizeGraphHeader();
+
+  /**
+   * Return the footer of the generated graph.
+   *
+   * @return the String representation of the footer of the control flow graph
+   */
+  protected abstract String visualizeGraphFooter();
+
+  /**
+   * Given a list of process orders (integers), returns a string representation.
+   *
+   * <p>Examples: "Process order: 23", "Process order: 23,25".
+   *
+   * @param order a list of process orders
+   * @return a String representation of the given process orders
+   */
+  protected String getProcessOrderSimpleString(List<Integer> order) {
+    return "Process order: " + StringsPlume.join(",", order);
+  }
+
+  /**
+   * Get the simple name of a node.
+   *
+   * @param t a node
+   * @return the node's simple name, without "Node"
+   */
+  protected String getNodeSimpleName(Node t) {
+    String name = t.getClass().getSimpleName();
+    return name.replace("Node", "");
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java
new file mode 100644
index 0000000..721c57b
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java
@@ -0,0 +1,338 @@
+package org.checkerframework.dataflow.cfg.visualize;
+
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.main.JavaCompiler;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.Options;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.AbstractValue;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.analysis.TransferFunction;
+import org.checkerframework.dataflow.cfg.CFGProcessor;
+import org.checkerframework.dataflow.cfg.CFGProcessor.CFGProcessResult;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+
+/**
+ * Launcher to generate the DOT or String representation of the control flow graph of a given method
+ * in a given class.
+ *
+ * <p>Usage: Directly run it as the main class to generate the DOT representation of the control
+ * flow graph of a given method in a given class. See {@link
+ * org.checkerframework.dataflow.cfg.playground.ConstantPropagationPlayground} for another way to
+ * use it.
+ */
+public class CFGVisualizeLauncher {
+
+  /**
+   * The main entry point of CFGVisualizeLauncher.
+   *
+   * @param args the passed arguments, see {@link #printUsage()} for the usage
+   */
+  public static void main(String[] args) {
+    CFGVisualizeLauncher cfgVisualizeLauncher = new CFGVisualizeLauncher();
+    if (args.length == 0) {
+      cfgVisualizeLauncher.printUsage();
+      System.exit(1);
+    }
+    String input = args[0];
+    File file = new File(input);
+    if (!file.canRead()) {
+      cfgVisualizeLauncher.printError("Cannot read input file: " + file.getAbsolutePath());
+      cfgVisualizeLauncher.printUsage();
+      System.exit(1);
+    }
+
+    String method = "test";
+    String clas = "Test";
+    String output = ".";
+    boolean pdf = false;
+    boolean error = false;
+    boolean verbose = false;
+    boolean string = false;
+
+    for (int i = 1; i < args.length; i++) {
+      switch (args[i]) {
+        case "--outputdir":
+          if (i >= args.length - 1) {
+            cfgVisualizeLauncher.printError("Did not find <outputdir> after --outputdir.");
+            continue;
+          }
+          i++;
+          output = args[i];
+          break;
+        case "--pdf":
+          pdf = true;
+          break;
+        case "--method":
+          if (i >= args.length - 1) {
+            cfgVisualizeLauncher.printError("Did not find <name> after --method.");
+            continue;
+          }
+          i++;
+          method = args[i];
+          break;
+        case "--class":
+          if (i >= args.length - 1) {
+            cfgVisualizeLauncher.printError("Did not find <name> after --class.");
+            continue;
+          }
+          i++;
+          clas = args[i];
+          break;
+        case "--verbose":
+          verbose = true;
+          break;
+        case "--string":
+          string = true;
+          break;
+        default:
+          cfgVisualizeLauncher.printError("Unknown command line argument: " + args[i]);
+          error = true;
+          break;
+      }
+    }
+
+    if (error) {
+      System.exit(1);
+    }
+
+    if (!string) {
+      cfgVisualizeLauncher.generateDOTofCFGWithoutAnalysis(
+          input, output, method, clas, pdf, verbose);
+    } else {
+      String stringGraph =
+          cfgVisualizeLauncher.generateStringOfCFGWithoutAnalysis(input, method, clas, verbose);
+      System.out.println(stringGraph);
+    }
+  }
+
+  /**
+   * Generate the DOT representation of the CFG for a method, only. Does no dataflow analysis.
+   *
+   * @param inputFile java source input file
+   * @param outputDir output directory
+   * @param method name of the method to generate the CFG for
+   * @param clas name of the class which includes the method to generate the CFG for
+   * @param pdf also generate a PDF
+   * @param verbose show verbose information in CFG
+   */
+  protected void generateDOTofCFGWithoutAnalysis(
+      String inputFile,
+      String outputDir,
+      String method,
+      String clas,
+      boolean pdf,
+      boolean verbose) {
+    generateDOTofCFG(inputFile, outputDir, method, clas, pdf, verbose, null);
+  }
+
+  /**
+   * Generate the String representation of the CFG for a method, only. Does no dataflow analysis.
+   *
+   * @param inputFile java source input file
+   * @param method name of the method to generate the CFG for
+   * @param clas name of the class which includes the method to generate the CFG for
+   * @param verbose show verbose information in CFG
+   * @return the String representation of the CFG
+   */
+  protected String generateStringOfCFGWithoutAnalysis(
+      String inputFile, String method, String clas, boolean verbose) {
+    @Nullable Map<String, Object> res = generateStringOfCFG(inputFile, method, clas, verbose, null);
+    if (res != null) {
+      String stringGraph = (String) res.get("stringGraph");
+      if (stringGraph == null) {
+        return "Unexpected output from generating string control flow graph, shouldn't be null.";
+      }
+      return stringGraph;
+    } else {
+      return "Unexpected output from generating string control flow graph, shouldn't be null.";
+    }
+  }
+
+  /**
+   * Generate the DOT representation of the CFG for a method.
+   *
+   * @param <V> the abstract value type to be tracked by the analysis
+   * @param <S> the store type used in the analysis
+   * @param <T> the transfer function type that is used to approximated runtime behavior
+   * @param inputFile java source input file
+   * @param outputDir source output directory
+   * @param method name of the method to generate the CFG for
+   * @param clas name of the class which includes the method to generate the CFG for
+   * @param pdf also generate a PDF
+   * @param verbose show verbose information in CFG
+   * @param analysis analysis to perform before the visualization (or {@code null} if no analysis is
+   *     to be performed)
+   */
+  public <V extends AbstractValue<V>, S extends Store<S>, T extends TransferFunction<V, S>>
+      void generateDOTofCFG(
+          String inputFile,
+          String outputDir,
+          String method,
+          String clas,
+          boolean pdf,
+          boolean verbose,
+          @Nullable Analysis<V, S, T> analysis) {
+    ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method);
+    if (analysis != null) {
+      analysis.performAnalysis(cfg);
+    }
+
+    Map<String, Object> args = new HashMap<>(2);
+    args.put("outdir", outputDir);
+    args.put("verbose", verbose);
+
+    CFGVisualizer<V, S, T> viz = new DOTCFGVisualizer<>();
+    viz.init(args);
+    Map<String, Object> res = viz.visualize(cfg, cfg.getEntryBlock(), analysis);
+    viz.shutdown();
+
+    if (pdf && res != null) {
+      assert res.get("dotFileName") != null : "@AssumeAssertion(nullness): specification";
+      producePDF((String) res.get("dotFileName"));
+    }
+  }
+
+  /**
+   * Generate the control flow graph of a method in a class.
+   *
+   * @param file java source input file
+   * @param clas name of the class which includes the method to generate the CFG for
+   * @param method name of the method to generate the CFG for
+   * @return control flow graph of the specified method
+   */
+  protected ControlFlowGraph generateMethodCFG(String file, String clas, final String method) {
+
+    CFGProcessor cfgProcessor = new CFGProcessor(clas, method);
+
+    Context context = new Context();
+    Options.instance(context).put("compilePolicy", "ATTR_ONLY");
+    JavaCompiler javac = new JavaCompiler(context);
+
+    JavacFileManager fileManager = (JavacFileManager) context.get(JavaFileManager.class);
+
+    JavaFileObject l = fileManager.getJavaFileObjectsFromStrings(List.of(file)).iterator().next();
+
+    PrintStream err = System.err;
+    try {
+      // redirect syserr to nothing (and prevent the compiler from issuing
+      // warnings about our exception.
+      System.setErr(
+          new PrintStream(
+              new OutputStream() {
+                @Override
+                public void write(int b) throws IOException {}
+              }));
+      javac.compile(List.of(l), List.of(clas), List.of(cfgProcessor), List.nil());
+    } catch (Throwable e) {
+      // ok
+    } finally {
+      System.setErr(err);
+    }
+
+    CFGProcessResult res = cfgProcessor.getCFGProcessResult();
+
+    if (res == null) {
+      printError("internal error in type processor! method typeProcessOver() doesn't get called.");
+      System.exit(1);
+    }
+
+    if (!res.isSuccess()) {
+      printError(res.getErrMsg());
+      System.exit(1);
+    }
+
+    return res.getCFG();
+  }
+
+  /**
+   * Invoke "dot" command to generate a PDF.
+   *
+   * @param file name of the dot file
+   */
+  protected void producePDF(String file) {
+    try {
+      String command = "dot -Tpdf \"" + file + "\" -o \"" + file + ".pdf\"";
+      Process child = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", command});
+      child.waitFor();
+    } catch (InterruptedException | IOException e) {
+      e.printStackTrace();
+      System.exit(1);
+    }
+  }
+
+  /**
+   * Generate the String representation of the CFG for a method.
+   *
+   * @param <V> the abstract value type to be tracked by the analysis
+   * @param <S> the store type used in the analysis
+   * @param <T> the transfer function type that is used to approximated runtime behavior
+   * @param inputFile java source input file
+   * @param method name of the method to generate the CFG for
+   * @param clas name of the class which includes the method to generate the CFG for
+   * @param verbose show verbose information in CFG
+   * @param analysis analysis to perform before the visualization (or {@code null} if no analysis is
+   *     to be performed)
+   * @return a map which includes a key "stringGraph" and the String representation of CFG as the
+   *     value
+   */
+  public <V extends AbstractValue<V>, S extends Store<S>, T extends TransferFunction<V, S>>
+      @Nullable Map<String, Object> generateStringOfCFG(
+      String inputFile,
+      String method,
+      String clas,
+      boolean verbose,
+      @Nullable Analysis<V, S, T> analysis) {
+    ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method);
+    if (analysis != null) {
+      analysis.performAnalysis(cfg);
+    }
+
+    Map<String, Object> args = Collections.singletonMap("verbose", verbose);
+
+    CFGVisualizer<V, S, T> viz = new StringCFGVisualizer<>();
+    viz.init(args);
+    Map<String, Object> res = viz.visualize(cfg, cfg.getEntryBlock(), analysis);
+    viz.shutdown();
+    return res;
+  }
+
+  /** Print usage information. */
+  protected void printUsage() {
+    System.out.println(
+        "Generate the control flow graph of a Java method, represented as a DOT or String graph.");
+    System.out.println(
+        "Parameters: <inputfile> [--outputdir <outputdir>] [--method <name>] [--class <name>]"
+            + " [--pdf] [--verbose] [--string]");
+    System.out.println(
+        "    --outputdir: The output directory for the generated files (defaults to '.').");
+    System.out.println("    --method:    The method to generate the CFG for (defaults to 'test').");
+    System.out.println(
+        "    --class:     The class in which to find the method (defaults to 'Test').");
+    System.out.println("    --pdf:       Also generate the PDF by invoking 'dot'.");
+    System.out.println("    --verbose:   Show the verbose output (defaults to 'false').");
+    System.out.println(
+        "    --string:    Print the string representation of the control flow graph (defaults to"
+            + " 'false').");
+  }
+
+  /**
+   * Print error message.
+   *
+   * @param string error message
+   */
+  protected void printError(@Nullable String string) {
+    System.err.println("ERROR: " + string);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizer.java
new file mode 100644
index 0000000..af37014
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizer.java
@@ -0,0 +1,198 @@
+package org.checkerframework.dataflow.cfg.visualize;
+
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.AbstractValue;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.analysis.TransferFunction;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.block.ConditionalBlock;
+import org.checkerframework.dataflow.cfg.block.SpecialBlock;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.expression.ArrayAccess;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.MethodCall;
+
+/**
+ * Perform some visualization on a control flow graph. The particular operations depend on the
+ * implementation.
+ *
+ * @param <V> the abstract value type to be tracked by the analysis
+ * @param <S> the store type used in the analysis
+ * @param <T> the transfer function type that is used to approximate runtime behavior
+ */
+public interface CFGVisualizer<
+    V extends AbstractValue<V>, S extends Store<S>, T extends TransferFunction<V, S>> {
+  /**
+   * Initialization method guaranteed to be called once before the first invocation of {@link
+   * #visualize}.
+   *
+   * @param args implementation-dependent options
+   */
+  void init(Map<String, Object> args);
+
+  /**
+   * Returns the separator for lines within a node's representation.
+   *
+   * @return the separator for lines within a node's representation
+   */
+  public abstract String getSeparator();
+
+  /**
+   * Output a visualization representing the control flow graph starting at {@code entry}. The
+   * concrete actions are implementation dependent.
+   *
+   * <p>An invocation {@code visualize(cfg, entry, null);} does not output stores at the beginning
+   * of basic blocks.
+   *
+   * @param cfg the CFG to visualize
+   * @param entry the entry node of the control flow graph to be represented
+   * @param analysis an analysis containing information about the program represented by the CFG.
+   *     The information includes {@link Store}s that are valid at the beginning of basic blocks
+   *     reachable from {@code entry} and per-node information for value producing {@link Node}s.
+   *     Can also be {@code null} to indicate that this information should not be output.
+   * @return visualization results, e.g. generated file names ({@link DOTCFGVisualizer}) or a String
+   *     representation of the CFG ({@link StringCFGVisualizer})
+   */
+  @Nullable Map<String, Object> visualize(
+      ControlFlowGraph cfg, Block entry, @Nullable Analysis<V, S, T> analysis);
+
+  /**
+   * Delegate the visualization responsibility to the passed {@link Store} instance, which will call
+   * back to this visualizer instance for sub-components.
+   *
+   * @param store the store to visualize
+   * @return the String representation of the given store
+   */
+  String visualizeStore(S store);
+
+  /**
+   * Called by {@code CFAbstractStore#internalVisualize()} to visualize a local variable.
+   *
+   * @param localVar the local variable
+   * @param value the value of the local variable
+   * @return the String representation of the local variable
+   */
+  String visualizeStoreLocalVar(LocalVariable localVar, V value);
+
+  /**
+   * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of the current
+   * object {@code this} in this Store.
+   *
+   * @param value the value of the current object {@code this}
+   * @return the String representation of {@code this}
+   */
+  String visualizeStoreThisVal(V value);
+
+  /**
+   * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of one field
+   * collected by this Store.
+   *
+   * @param fieldAccess the field
+   * @param value the value of the field
+   * @return the String representation of the field
+   */
+  String visualizeStoreFieldVal(FieldAccess fieldAccess, V value);
+
+  /**
+   * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of one array
+   * collected by this Store.
+   *
+   * @param arrayValue the array
+   * @param value the value of the array
+   * @return the String representation of the array
+   */
+  String visualizeStoreArrayVal(ArrayAccess arrayValue, V value);
+
+  /**
+   * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of pure method
+   * calls collected by this Store.
+   *
+   * @param methodCall the pure method call
+   * @param value the value of the pure method call
+   * @return the String representation of the pure method call
+   */
+  String visualizeStoreMethodVals(MethodCall methodCall, V value);
+
+  /**
+   * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of class names
+   * collected by this Store.
+   *
+   * @param className the class name
+   * @param value the value of the class name
+   * @return the String representation of the class name
+   */
+  String visualizeStoreClassVals(ClassName className, V value);
+
+  /**
+   * Called by {@code CFAbstractStore#internalVisualize()} to visualize the specific information
+   * collected according to the specific kind of Store. Currently, these Stores call this method:
+   * {@code LockStore}, {@code NullnessStore}, and {@code InitializationStore} to visualize
+   * additional information.
+   *
+   * @param keyName the name of the specific information to be visualized
+   * @param value the value of the specific information to be visualized
+   * @return the String representation of the specific information
+   */
+  String visualizeStoreKeyVal(String keyName, Object value);
+
+  /**
+   * Visualize a block based on the analysis.
+   *
+   * @param bb the block
+   * @param analysis the current analysis
+   * @return the String representation of the given block
+   */
+  String visualizeBlock(Block bb, @Nullable Analysis<V, S, T> analysis);
+
+  /**
+   * Visualize a SpecialBlock.
+   *
+   * @param sbb the special block
+   * @return the String representation of the type of the special block {@code sbb}: entry, exit, or
+   *     exceptional-exit
+   */
+  String visualizeSpecialBlock(SpecialBlock sbb);
+
+  /**
+   * Visualize a ConditionalBlock.
+   *
+   * @param cbb the conditional block
+   * @return the String representation of the conditional block
+   */
+  String visualizeConditionalBlock(ConditionalBlock cbb);
+
+  /**
+   * Visualize the transferInput before a Block based on the analysis.
+   *
+   * @param bb the block
+   * @param analysis the current analysis
+   * @return the String representation of the transferInput before the given block
+   */
+  String visualizeBlockTransferInputBefore(Block bb, Analysis<V, S, T> analysis);
+
+  /**
+   * Visualize the transferInput after a Block based on the analysis.
+   *
+   * @param bb the block
+   * @param analysis the current analysis
+   * @return the String representation of the transferInput after the given block
+   */
+  String visualizeBlockTransferInputAfter(Block bb, Analysis<V, S, T> analysis);
+
+  /**
+   * Visualize a Node based on the analysis.
+   *
+   * @param t the node
+   * @param analysis the current analysis
+   * @return the String representation of the given node
+   */
+  String visualizeBlockNode(Node t, @Nullable Analysis<V, S, T> analysis);
+
+  /** Shutdown method called once from the shutdown hook of the {@code BaseTypeChecker}. */
+  void shutdown();
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java
new file mode 100644
index 0000000..d50f63b
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java
@@ -0,0 +1,348 @@
+package org.checkerframework.dataflow.cfg.visualize;
+
+import com.sun.source.tree.VariableTree;
+import com.sun.tools.javac.tree.JCTree;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.AbstractValue;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.analysis.TransferFunction;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGStatement;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.block.Block.BlockType;
+import org.checkerframework.dataflow.cfg.block.ConditionalBlock;
+import org.checkerframework.dataflow.cfg.block.SpecialBlock;
+import org.checkerframework.dataflow.cfg.visualize.AbstractCFGVisualizer.VisualizeWhere;
+import org.checkerframework.dataflow.expression.ArrayAccess;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.MethodCall;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.UserError;
+
+/** Generate a graph description in the DOT language of a control graph. */
+public class DOTCFGVisualizer<
+        V extends AbstractValue<V>, S extends Store<S>, T extends TransferFunction<V, S>>
+    extends AbstractCFGVisualizer<V, S, T> {
+
+  /** The output directory. */
+  @SuppressWarnings("nullness:initialization.field.uninitialized") // uses init method
+  protected String outDir;
+
+  /** The (optional) checker name. Used as a part of the name of the output dot file. */
+  protected @Nullable String checkerName;
+
+  /** Mapping from class/method representation to generated dot file. */
+  @SuppressWarnings("nullness:initialization.field.uninitialized") // uses init method
+  protected Map<String, String> generated;
+
+  /** Terminator for lines that are left-justified. */
+  protected static final String leftJustifiedTerminator = "\\l";
+
+  @Override
+  @SuppressWarnings("nullness") // assume arguments are set correctly
+  public void init(Map<String, Object> args) {
+    super.init(args);
+    this.outDir = (String) args.get("outdir");
+    if (this.outDir == null) {
+      throw new BugInCF(
+          "outDir should never be null,"
+              + " provide it in args when calling DOTCFGVisualizer.init(args).");
+    }
+    this.checkerName = (String) args.get("checkerName");
+    this.generated = new HashMap<>();
+  }
+
+  @Override
+  public String getSeparator() {
+    return leftJustifiedTerminator;
+  }
+
+  @Override
+  public @Nullable Map<String, Object> visualize(
+      ControlFlowGraph cfg, Block entry, @Nullable Analysis<V, S, T> analysis) {
+
+    String dotGraph = visualizeGraph(cfg, entry, analysis);
+    String dotFileName = dotOutputFileName(cfg.underlyingAST);
+
+    try {
+      FileWriter fStream = new FileWriter(dotFileName);
+      BufferedWriter out = new BufferedWriter(fStream);
+      out.write(dotGraph);
+      out.close();
+    } catch (IOException e) {
+      throw new UserError("Error creating dot file (is the path valid?): " + dotFileName, e);
+    }
+
+    return Collections.singletonMap("dotFileName", dotFileName);
+  }
+
+  @SuppressWarnings("keyfor:enhancedfor")
+  @Override
+  public String visualizeNodes(
+      Set<Block> blocks, ControlFlowGraph cfg, @Nullable Analysis<V, S, T> analysis) {
+
+    StringBuilder sbDotNodes = new StringBuilder();
+
+    IdentityHashMap<Block, List<Integer>> processOrder = getProcessOrder(cfg);
+
+    // Definition of all nodes including their labels.
+    for (@KeyFor("processOrder") Block v : blocks) {
+      sbDotNodes.append("    ").append(v.getUid()).append(" [");
+      if (v.getType() == BlockType.CONDITIONAL_BLOCK) {
+        sbDotNodes.append("shape=polygon sides=8 ");
+      } else if (v.getType() == BlockType.SPECIAL_BLOCK) {
+        sbDotNodes.append("shape=oval ");
+      } else {
+        sbDotNodes.append("shape=rectangle ");
+      }
+      sbDotNodes.append("label=\"");
+      if (verbose) {
+        sbDotNodes.append(getProcessOrderSimpleString(processOrder.get(v))).append(getSeparator());
+      }
+      String strBlock = visualizeBlock(v, analysis);
+      if (strBlock.length() == 0) {
+        if (v.getType() == BlockType.CONDITIONAL_BLOCK) {
+          // The footer of the conditional block.
+          sbDotNodes.append("\"];");
+        } else {
+          // The footer of the block which has no content and is not a special or conditional block.
+          sbDotNodes.append("?? empty ??\"];");
+        }
+      } else {
+        sbDotNodes.append(strBlock).append("\"];");
+      }
+      sbDotNodes.append(System.lineSeparator());
+    }
+    return sbDotNodes.toString();
+  }
+
+  @Override
+  protected String visualizeEdge(Object sId, Object eId, String flowRule) {
+    return "    " + format(sId) + " -> " + format(eId) + " [label=\"" + flowRule + "\"];";
+  }
+
+  @Override
+  public String visualizeBlock(Block bb, @Nullable Analysis<V, S, T> analysis) {
+    return super.visualizeBlockHelper(bb, analysis, getSeparator());
+  }
+
+  @Override
+  public String visualizeSpecialBlock(SpecialBlock sbb) {
+    return super.visualizeSpecialBlockHelper(sbb);
+  }
+
+  @Override
+  public String visualizeConditionalBlock(ConditionalBlock cbb) {
+    // No extra content in DOT output.
+    return "";
+  }
+
+  @Override
+  public String visualizeBlockTransferInputBefore(Block bb, Analysis<V, S, T> analysis) {
+    return super.visualizeBlockTransferInputHelper(
+        VisualizeWhere.BEFORE, bb, analysis, getSeparator());
+  }
+
+  @Override
+  public String visualizeBlockTransferInputAfter(Block bb, Analysis<V, S, T> analysis) {
+    return super.visualizeBlockTransferInputHelper(
+        VisualizeWhere.AFTER, bb, analysis, getSeparator());
+  }
+
+  /**
+   * Create a dot file and return its name.
+   *
+   * @param ast an abstract syntax tree
+   * @return the file name used for DOT output
+   */
+  protected String dotOutputFileName(UnderlyingAST ast) {
+    StringBuilder srcLoc = new StringBuilder();
+    StringBuilder outFile = new StringBuilder(outDir);
+
+    outFile.append("/");
+
+    if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) {
+      CFGStatement cfgStatement = (CFGStatement) ast;
+      String clsName = cfgStatement.getSimpleClassName();
+      outFile.append(clsName);
+      outFile.append("-initializer-");
+      outFile.append(ast.getUid());
+
+      srcLoc.append("<");
+      srcLoc.append(clsName);
+      srcLoc.append("::initializer::");
+      srcLoc.append(((JCTree) cfgStatement.getCode()).pos);
+      srcLoc.append(">");
+    } else if (ast.getKind() == UnderlyingAST.Kind.METHOD) {
+      CFGMethod cfgMethod = (CFGMethod) ast;
+      String clsName = cfgMethod.getSimpleClassName();
+      String methodName = cfgMethod.getMethodName();
+      StringJoiner params = new StringJoiner(",");
+      for (VariableTree tree : cfgMethod.getMethod().getParameters()) {
+        params.add(tree.getType().toString());
+      }
+      outFile.append(clsName);
+      outFile.append("-");
+      outFile.append(methodName);
+      if (params.length() != 0) {
+        outFile.append("-");
+        outFile.append(params);
+      }
+
+      srcLoc.append("<");
+      srcLoc.append(clsName);
+      srcLoc.append("::");
+      srcLoc.append(methodName);
+      srcLoc.append("(");
+      srcLoc.append(params);
+      srcLoc.append(")::");
+      srcLoc.append(((JCTree) cfgMethod.getMethod()).pos);
+      srcLoc.append(">");
+    } else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) {
+      CFGLambda cfgLambda = (CFGLambda) ast;
+      String clsName = cfgLambda.getSimpleClassName();
+      String methodName = cfgLambda.getMethodName();
+      long uid = TreeUtils.treeUids.get(cfgLambda.getCode());
+      outFile.append(clsName);
+      outFile.append("-");
+      outFile.append(methodName);
+      outFile.append("-");
+      outFile.append(uid);
+
+      srcLoc.append("<");
+      srcLoc.append(clsName);
+      srcLoc.append("::");
+      srcLoc.append(methodName);
+      srcLoc.append("(");
+      srcLoc.append(cfgLambda.getMethod().getParameters());
+      srcLoc.append(")::");
+      srcLoc.append(((JCTree) cfgLambda.getCode()).pos);
+      srcLoc.append(">");
+    } else {
+      throw new BugInCF("Unexpected AST kind: " + ast.getKind() + " value: " + ast);
+    }
+    if (checkerName != null && !checkerName.isEmpty()) {
+      outFile.append('-');
+      outFile.append(checkerName);
+    }
+    outFile.append(".dot");
+
+    // make path safe for Windows
+    String outFileName = outFile.toString().replace("<", "_").replace(">", "");
+
+    generated.put(srcLoc.toString(), outFileName);
+
+    return outFileName;
+  }
+
+  @Override
+  protected String format(Object obj) {
+    return escapeString(obj);
+  }
+
+  @Override
+  public String visualizeStoreThisVal(V value) {
+    return storeEntryIndent + "this > " + escapeString(value);
+  }
+
+  @Override
+  public String visualizeStoreLocalVar(LocalVariable localVar, V value) {
+    return storeEntryIndent + localVar + " > " + escapeString(value);
+  }
+
+  @Override
+  public String visualizeStoreFieldVal(FieldAccess fieldAccess, V value) {
+    return storeEntryIndent + fieldAccess + " > " + escapeString(value);
+  }
+
+  @Override
+  public String visualizeStoreArrayVal(ArrayAccess arrayValue, V value) {
+    return storeEntryIndent + arrayValue + " > " + escapeString(value);
+  }
+
+  @Override
+  public String visualizeStoreMethodVals(MethodCall methodCall, V value) {
+    return storeEntryIndent + escapeString(methodCall) + " > " + escapeString(value);
+  }
+
+  @Override
+  public String visualizeStoreClassVals(ClassName className, V value) {
+    return storeEntryIndent + className + " > " + escapeString(value);
+  }
+
+  @Override
+  public String visualizeStoreKeyVal(String keyName, Object value) {
+    return storeEntryIndent + keyName + " = " + value;
+  }
+
+  /**
+   * Escape the input String.
+   *
+   * @param str the string to be escaped
+   * @return the escaped version of the string
+   */
+  private static String escapeString(final String str) {
+    return str.replace("\"", "\\\"").replace("\r", "\\\\r").replace("\n", "\\\\n");
+  }
+
+  /**
+   * Escape the double quotes from the string representation of the given object.
+   *
+   * @param obj an object
+   * @return an escaped version of the string representation of the object
+   */
+  private static String escapeString(final Object obj) {
+    return escapeString(String.valueOf(obj));
+  }
+
+  /**
+   * Write a file {@code methods.txt} that contains a mapping from source code location to generated
+   * dot file.
+   */
+  @Override
+  public void shutdown() {
+    try {
+      // Open for append, in case of multiple sub-checkers.
+      FileWriter fstream = new FileWriter(outDir + "/methods.txt", true);
+      BufferedWriter out = new BufferedWriter(fstream);
+      for (Map.Entry<String, String> kv : generated.entrySet()) {
+        out.write(kv.getKey());
+        out.append("\t");
+        out.write(kv.getValue());
+        out.append(lineSeparator);
+      }
+      out.close();
+    } catch (IOException e) {
+      throw new UserError(
+          "Error creating methods.txt file in: " + outDir + "; ensure the path is valid", e);
+    }
+  }
+
+  @Override
+  protected String visualizeGraphHeader() {
+    return "digraph {" + lineSeparator;
+  }
+
+  @Override
+  protected String visualizeGraphFooter() {
+    return "}";
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java
new file mode 100644
index 0000000..aaa331f
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java
@@ -0,0 +1,168 @@
+package org.checkerframework.dataflow.cfg.visualize;
+
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.AbstractValue;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.analysis.TransferFunction;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.block.Block;
+import org.checkerframework.dataflow.cfg.block.ConditionalBlock;
+import org.checkerframework.dataflow.cfg.block.SpecialBlock;
+import org.checkerframework.dataflow.cfg.visualize.AbstractCFGVisualizer.VisualizeWhere;
+import org.checkerframework.dataflow.expression.ArrayAccess;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.MethodCall;
+
+/** Generate the String representation of a control flow graph. */
+public class StringCFGVisualizer<
+        V extends AbstractValue<V>, S extends Store<S>, T extends TransferFunction<V, S>>
+    extends AbstractCFGVisualizer<V, S, T> {
+
+  @Override
+  public String getSeparator() {
+    return "\n";
+  }
+
+  @Override
+  public Map<String, Object> visualize(
+      ControlFlowGraph cfg, Block entry, @Nullable Analysis<V, S, T> analysis) {
+    String stringGraph = visualizeGraph(cfg, entry, analysis);
+    return Collections.singletonMap("stringGraph", stringGraph);
+  }
+
+  @SuppressWarnings("keyfor:enhancedfor")
+  @Override
+  public String visualizeNodes(
+      Set<Block> blocks, ControlFlowGraph cfg, @Nullable Analysis<V, S, T> analysis) {
+    StringJoiner sjStringNodes = new StringJoiner(lineSeparator);
+    IdentityHashMap<Block, List<Integer>> processOrder = getProcessOrder(cfg);
+
+    // Generate all the Nodes.
+    for (@KeyFor("processOrder") Block v : blocks) {
+      sjStringNodes.add(v.getUid() + ":");
+      if (verbose) {
+        sjStringNodes.add(getProcessOrderSimpleString(processOrder.get(v)));
+      }
+      sjStringNodes.add(visualizeBlock(v, analysis));
+      sjStringNodes.add("");
+    }
+
+    return sjStringNodes.toString().trim();
+  }
+
+  @Override
+  protected String visualizeEdge(Object sId, Object eId, String flowRule) {
+    if (this.verbose) {
+      return sId + " -> " + eId + " " + flowRule;
+    }
+    return sId + " -> " + eId;
+  }
+
+  @Override
+  public String visualizeBlock(Block bb, @Nullable Analysis<V, S, T> analysis) {
+    return super.visualizeBlockHelper(bb, analysis, lineSeparator).trim();
+  }
+
+  @Override
+  public String visualizeSpecialBlock(SpecialBlock sbb) {
+    return super.visualizeSpecialBlockHelper(sbb);
+  }
+
+  @Override
+  public String visualizeConditionalBlock(ConditionalBlock cbb) {
+    return "ConditionalBlock: then: "
+        + cbb.getThenSuccessor().getUid()
+        + ", else: "
+        + cbb.getElseSuccessor().getUid();
+  }
+
+  @Override
+  public String visualizeBlockTransferInputBefore(Block bb, Analysis<V, S, T> analysis) {
+    return super.visualizeBlockTransferInputHelper(
+        VisualizeWhere.BEFORE, bb, analysis, lineSeparator);
+  }
+
+  @Override
+  public String visualizeBlockTransferInputAfter(Block bb, Analysis<V, S, T> analysis) {
+    return super.visualizeBlockTransferInputHelper(
+        VisualizeWhere.AFTER, bb, analysis, lineSeparator);
+  }
+
+  @Override
+  protected String format(Object obj) {
+    return obj.toString();
+  }
+
+  @Override
+  public String visualizeStoreThisVal(V value) {
+    return storeEntryIndent + "this > " + value;
+  }
+
+  @Override
+  public String visualizeStoreLocalVar(LocalVariable localVar, V value) {
+    return storeEntryIndent + localVar + " > " + value;
+  }
+
+  @Override
+  public String visualizeStoreFieldVal(FieldAccess fieldAccess, V value) {
+    return storeEntryIndent + fieldAccess + " > " + value;
+  }
+
+  @Override
+  public String visualizeStoreArrayVal(ArrayAccess arrayValue, V value) {
+    return storeEntryIndent + arrayValue + " > " + value;
+  }
+
+  @Override
+  public String visualizeStoreMethodVals(MethodCall methodCall, V value) {
+    return storeEntryIndent + methodCall + " > " + value;
+  }
+
+  @Override
+  public String visualizeStoreClassVals(ClassName className, V value) {
+    return storeEntryIndent + className + " > " + value;
+  }
+
+  @Override
+  public String visualizeStoreKeyVal(String keyName, Object value) {
+    return storeEntryIndent + keyName + " = " + value;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>StringCFGVisualizer does not write into file, so left intentionally blank.
+   */
+  @Override
+  public void shutdown() {}
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>StringCFGVisualizer does not need a specific header, so just return an empty string.
+   */
+  @Override
+  protected String visualizeGraphHeader() {
+    return "";
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>StringCFGVisualizer does not need a specific footer, so just return an empty string.
+   */
+  @Override
+  protected String visualizeGraphFooter() {
+    return "";
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java
new file mode 100644
index 0000000..361cc1a
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java
@@ -0,0 +1,124 @@
+package org.checkerframework.dataflow.constantpropagation;
+
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.AbstractValue;
+
+public class Constant implements AbstractValue<Constant> {
+
+  /** What kind of abstract value is this? */
+  protected Type type;
+
+  /** The value of this abstract value (or null). */
+  protected @Nullable Integer value;
+
+  public enum Type {
+    CONSTANT,
+    TOP,
+    BOTTOM,
+  }
+
+  /** Create a constant for {@code type}. */
+  public Constant(Type type) {
+    assert type != Type.CONSTANT;
+    this.type = type;
+  }
+
+  /** Create a constant for {@code value}. */
+  public Constant(Integer value) {
+    this.type = Type.CONSTANT;
+    this.value = value;
+  }
+
+  /**
+   * Returns whether or not the constant is TOP.
+   *
+   * @return whether or not the constant is TOP
+   */
+  public boolean isTop() {
+    return type == Type.TOP;
+  }
+
+  /**
+   * Returns whether or not the constant is BOTTOM.
+   *
+   * @return whether or not the constant is BOTTOM
+   */
+  public boolean isBottom() {
+    return type == Type.BOTTOM;
+  }
+
+  /**
+   * Returns whether or not the constant is CONSTANT.
+   *
+   * @return whether or not the constant is CONSTANT
+   */
+  @EnsuresNonNullIf(result = true, expression = "value")
+  public boolean isConstant() {
+    return type == Type.CONSTANT && value != null;
+  }
+
+  /**
+   * Returns the value.
+   *
+   * @return the value
+   */
+  public Integer getValue() {
+    assert isConstant() : "@AssumeAssertion(nullness): inspection";
+    return value;
+  }
+
+  public Constant copy() {
+    if (isConstant()) {
+      return new Constant(value);
+    }
+    return new Constant(type);
+  }
+
+  @Override
+  public Constant leastUpperBound(Constant other) {
+    if (other.isBottom()) {
+      return this.copy();
+    }
+    if (this.isBottom()) {
+      return other.copy();
+    }
+    if (other.isTop() || this.isTop()) {
+      return new Constant(Type.TOP);
+    }
+    if (other.getValue().equals(getValue())) {
+      return this.copy();
+    }
+    return new Constant(Type.TOP);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof Constant)) {
+      return false;
+    }
+    Constant other = (Constant) obj;
+    return type == other.type && Objects.equals(value, other.value);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(type, value);
+  }
+
+  @Override
+  public String toString() {
+    switch (type) {
+      case TOP:
+        return "T";
+      case BOTTOM:
+        return "-";
+      case CONSTANT:
+        assert isConstant() : "@AssumeAssertion(nullness)";
+        return value.toString();
+      default:
+        throw new Error("Unexpected type");
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java
new file mode 100644
index 0000000..a44d2c4
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java
@@ -0,0 +1,173 @@
+package org.checkerframework.dataflow.constantpropagation;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.plumelib.util.CollectionsPlume;
+
+/** A store that records information about constant values. */
+public class ConstantPropagationStore implements Store<ConstantPropagationStore> {
+
+  /** Information about variables gathered so far. */
+  Map<Node, Constant> contents;
+
+  /** Creates a new ConstantPropagationStore. */
+  public ConstantPropagationStore() {
+    contents = new LinkedHashMap<>();
+  }
+
+  protected ConstantPropagationStore(Map<Node, Constant> contents) {
+    this.contents = contents;
+  }
+
+  public Constant getInformation(Node n) {
+    if (contents.containsKey(n)) {
+      return contents.get(n);
+    }
+    return new Constant(Constant.Type.TOP);
+  }
+
+  public void mergeInformation(Node n, Constant val) {
+    Constant value;
+    if (contents.containsKey(n)) {
+      value = val.leastUpperBound(contents.get(n));
+    } else {
+      value = val;
+    }
+    // TODO: remove (only two nodes supported atm)
+    assert n instanceof IntegerLiteralNode || n instanceof LocalVariableNode;
+    contents.put(n, value);
+  }
+
+  public void setInformation(Node n, Constant val) {
+    // TODO: remove (only two nodes supported atm)
+    assert n instanceof IntegerLiteralNode || n instanceof LocalVariableNode;
+    contents.put(n, val);
+  }
+
+  @Override
+  public ConstantPropagationStore copy() {
+    return new ConstantPropagationStore(new LinkedHashMap<>(contents));
+  }
+
+  @Override
+  public ConstantPropagationStore leastUpperBound(ConstantPropagationStore other) {
+    Map<Node, Constant> newContents = new LinkedHashMap<>(contents.size() + other.contents.size());
+
+    // go through all of the information of the other class
+    for (Map.Entry<Node, Constant> e : other.contents.entrySet()) {
+      Node n = e.getKey();
+      Constant otherVal = e.getValue();
+      if (contents.containsKey(n)) {
+        // merge if both contain information about a variable
+        newContents.put(n, otherVal.leastUpperBound(contents.get(n)));
+      } else {
+        // add new information
+        newContents.put(n, otherVal);
+      }
+    }
+
+    for (Map.Entry<Node, Constant> e : contents.entrySet()) {
+      Node n = e.getKey();
+      Constant thisVal = e.getValue();
+      if (!other.contents.containsKey(n)) {
+        // add new information
+        newContents.put(n, thisVal);
+      }
+    }
+
+    return new ConstantPropagationStore(newContents);
+  }
+
+  @Override
+  public ConstantPropagationStore widenedUpperBound(ConstantPropagationStore previous) {
+    return leastUpperBound(previous);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object o) {
+    if (o == null) {
+      return false;
+    }
+    if (!(o instanceof ConstantPropagationStore)) {
+      return false;
+    }
+    ConstantPropagationStore other = (ConstantPropagationStore) o;
+    // go through all of the information of the other object
+    for (Map.Entry<Node, Constant> e : other.contents.entrySet()) {
+      Node n = e.getKey();
+      Constant otherVal = e.getValue();
+      if (otherVal.isBottom()) {
+        continue; // no information
+      }
+      if (contents.containsKey(n)) {
+        if (!otherVal.equals(contents.get(n))) {
+          return false;
+        }
+      } else {
+        return false;
+      }
+    }
+    // go through all of the information of the this object
+    for (Map.Entry<Node, Constant> e : contents.entrySet()) {
+      Node n = e.getKey();
+      Constant thisVal = e.getValue();
+      if (thisVal.isBottom()) {
+        continue; // no information
+      }
+      if (other.contents.containsKey(n)) {
+        continue;
+      } else {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int s = 0;
+    for (Map.Entry<Node, Constant> e : contents.entrySet()) {
+      if (!e.getValue().isBottom()) {
+        s += e.hashCode();
+      }
+    }
+    return s;
+  }
+
+  @Override
+  public String toString() {
+    // only output local variable information
+    Map<Node, Constant> contentsWithoutLocalVars =
+        new LinkedHashMap<>(CollectionsPlume.mapCapacity(contents));
+    for (Map.Entry<Node, Constant> e : contents.entrySet()) {
+      if (e.getKey() instanceof LocalVariableNode) {
+        contentsWithoutLocalVars.put(e.getKey(), e.getValue());
+      }
+    }
+    return contentsWithoutLocalVars.toString();
+  }
+
+  @Override
+  public boolean canAlias(JavaExpression a, JavaExpression b) {
+    return true;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>{@code value} is {@code null} because {@link ConstantPropagationStore} doesn't support
+   * visualization.
+   */
+  @Override
+  @SuppressWarnings("nullness")
+  public String visualize(CFGVisualizer<?, ConstantPropagationStore, ?> viz) {
+    return viz.visualizeStoreKeyVal("constant propagation", null);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java
new file mode 100644
index 0000000..768233c
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java
@@ -0,0 +1,86 @@
+package org.checkerframework.dataflow.constantpropagation;
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
+import org.checkerframework.dataflow.analysis.ForwardTransferFunction;
+import org.checkerframework.dataflow.analysis.RegularTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.node.AbstractNodeVisitor;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.EqualToNode;
+import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+
+public class ConstantPropagationTransfer
+    extends AbstractNodeVisitor<
+        TransferResult<Constant, ConstantPropagationStore>,
+        TransferInput<Constant, ConstantPropagationStore>>
+    implements ForwardTransferFunction<Constant, ConstantPropagationStore> {
+
+  @Override
+  public ConstantPropagationStore initialStore(
+      UnderlyingAST underlyingAST, @Nullable List<LocalVariableNode> parameters) {
+    ConstantPropagationStore store = new ConstantPropagationStore();
+    return store;
+  }
+
+  @Override
+  public TransferResult<Constant, ConstantPropagationStore> visitLocalVariable(
+      LocalVariableNode node, TransferInput<Constant, ConstantPropagationStore> before) {
+    ConstantPropagationStore store = before.getRegularStore();
+    Constant value = store.getInformation(node);
+    return new RegularTransferResult<>(value, store);
+  }
+
+  @Override
+  public TransferResult<Constant, ConstantPropagationStore> visitNode(
+      Node n, TransferInput<Constant, ConstantPropagationStore> p) {
+    return new RegularTransferResult<>(null, p.getRegularStore());
+  }
+
+  @Override
+  public TransferResult<Constant, ConstantPropagationStore> visitAssignment(
+      AssignmentNode n, TransferInput<Constant, ConstantPropagationStore> pi) {
+    ConstantPropagationStore p = pi.getRegularStore();
+    Node target = n.getTarget();
+    Constant info = null;
+    if (target instanceof LocalVariableNode) {
+      LocalVariableNode t = (LocalVariableNode) target;
+      info = p.getInformation(n.getExpression());
+      p.setInformation(t, info);
+    }
+    return new RegularTransferResult<>(info, p);
+  }
+
+  @Override
+  public TransferResult<Constant, ConstantPropagationStore> visitIntegerLiteral(
+      IntegerLiteralNode n, TransferInput<Constant, ConstantPropagationStore> pi) {
+    ConstantPropagationStore p = pi.getRegularStore();
+    Constant c = new Constant(n.getValue());
+    p.setInformation(n, c);
+    return new RegularTransferResult<>(c, p);
+  }
+
+  @Override
+  public TransferResult<Constant, ConstantPropagationStore> visitEqualTo(
+      EqualToNode n, TransferInput<Constant, ConstantPropagationStore> pi) {
+    ConstantPropagationStore p = pi.getRegularStore();
+    ConstantPropagationStore old = p.copy();
+    Node left = n.getLeftOperand();
+    Node right = n.getRightOperand();
+    process(p, left, right);
+    process(p, right, left);
+    return new ConditionalTransferResult<>(null, p, old);
+  }
+
+  protected void process(ConstantPropagationStore p, Node a, Node b) {
+    Constant val = p.getInformation(a);
+    if (b instanceof LocalVariableNode && val.isConstant()) {
+      p.setInformation(b, val);
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayAccess.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayAccess.java
new file mode 100644
index 0000000..764cdd8
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayAccess.java
@@ -0,0 +1,121 @@
+package org.checkerframework.dataflow.expression;
+
+import java.util.Objects;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.javacutil.AnnotationProvider;
+
+/** An array access. */
+public class ArrayAccess extends JavaExpression {
+
+  /** The array being accessed. */
+  protected final JavaExpression array;
+  /** The index; an expression of type int. */
+  protected final JavaExpression index;
+
+  /**
+   * Create a new ArrayAccess.
+   *
+   * @param type the type of the array access
+   * @param array the array being accessed
+   * @param index the index; an expression of type int
+   */
+  public ArrayAccess(TypeMirror type, JavaExpression array, JavaExpression index) {
+    super(type);
+    this.array = array;
+    this.index = index;
+  }
+
+  @Override
+  public boolean containsOfClass(Class<? extends JavaExpression> clazz) {
+    if (getClass() == clazz) {
+      return true;
+    }
+    if (array.containsOfClass(clazz)) {
+      return true;
+    }
+    return index.containsOfClass(clazz);
+  }
+
+  @Override
+  public boolean isDeterministic(AnnotationProvider provider) {
+    return array.isDeterministic(provider) && index.isDeterministic(provider);
+  }
+
+  /**
+   * Returns the array being accessed.
+   *
+   * @return the array being accessed
+   */
+  public JavaExpression getArray() {
+    return array;
+  }
+
+  public JavaExpression getIndex() {
+    return index;
+  }
+
+  @Override
+  public boolean isUnassignableByOtherCode() {
+    return false;
+  }
+
+  @Override
+  public boolean isUnmodifiableByOtherCode() {
+    return false;
+  }
+
+  @Override
+  public boolean syntacticEquals(JavaExpression je) {
+    if (!(je instanceof ArrayAccess)) {
+      return false;
+    }
+    ArrayAccess other = (ArrayAccess) je;
+    return array.syntacticEquals(other.array) && index.syntacticEquals(other.index);
+  }
+
+  @Override
+  public boolean containsSyntacticEqualJavaExpression(JavaExpression other) {
+    return syntacticEquals(other)
+        || array.containsSyntacticEqualJavaExpression(other)
+        || index.containsSyntacticEqualJavaExpression(other);
+  }
+
+  @Override
+  public boolean containsModifiableAliasOf(Store<?> store, JavaExpression other) {
+    if (array.containsModifiableAliasOf(store, other)) {
+      return true;
+    }
+    return index.containsModifiableAliasOf(store, other);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ArrayAccess)) {
+      return false;
+    }
+    ArrayAccess other = (ArrayAccess) obj;
+    return array.equals(other.array) && index.equals(other.index);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(array, index);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder result = new StringBuilder();
+    result.append(array.toString());
+    result.append("[");
+    result.append(index.toString());
+    result.append("]");
+    return result.toString();
+  }
+
+  @Override
+  public <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p) {
+    return visitor.visitArrayAccess(this, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java
new file mode 100644
index 0000000..2e90e94
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java
@@ -0,0 +1,160 @@
+package org.checkerframework.dataflow.expression;
+
+import java.util.List;
+import java.util.Objects;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.StringsPlume;
+
+/** JavaExpression for array creations. {@code new String[]()}. */
+public class ArrayCreation extends JavaExpression {
+
+  /**
+   * List of dimensions expressions. A {code null} element means that there is no dimension
+   * expression for the given array level.
+   */
+  protected final List<@Nullable JavaExpression> dimensions;
+  /** List of initializers. */
+  protected final List<JavaExpression> initializers;
+
+  /**
+   * Creates an ArrayCreation object.
+   *
+   * @param type array type
+   * @param dimensions list of dimension expressions; a {code null} element means that there is no
+   *     dimension expression for the given array level.
+   * @param initializers list of initializer expressions
+   */
+  public ArrayCreation(
+      TypeMirror type,
+      List<@Nullable JavaExpression> dimensions,
+      List<JavaExpression> initializers) {
+    super(type);
+    assert type.getKind() == TypeKind.ARRAY;
+    this.dimensions = dimensions;
+    this.initializers = initializers;
+  }
+
+  /**
+   * Returns a list representing the dimensions of this array creation. A {code null} element means
+   * that there is no dimension expression for the given array level.
+   *
+   * @return a list representing the dimensions of this array creation
+   */
+  public List<@Nullable JavaExpression> getDimensions() {
+    return dimensions;
+  }
+
+  public List<JavaExpression> getInitializers() {
+    return initializers;
+  }
+
+  @Override
+  public boolean containsOfClass(Class<? extends JavaExpression> clazz) {
+    for (JavaExpression n : dimensions) {
+      if (n != null && n.getClass() == clazz) {
+        return true;
+      }
+    }
+    for (JavaExpression n : initializers) {
+      if (n.getClass() == clazz) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public boolean isDeterministic(AnnotationProvider provider) {
+    return listIsDeterministic(dimensions, provider) && listIsDeterministic(initializers, provider);
+  }
+
+  @Override
+  public boolean isUnassignableByOtherCode() {
+    return false;
+  }
+
+  @Override
+  public boolean isUnmodifiableByOtherCode() {
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(dimensions, initializers, getType().toString());
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ArrayCreation)) {
+      return false;
+    }
+    ArrayCreation other = (ArrayCreation) obj;
+    return this.dimensions.equals(other.getDimensions())
+        && this.initializers.equals(other.getInitializers())
+        // It might be better to use Types.isSameType(getType(), other.getType()), but I
+        // don't have a Types object.
+        && getType().toString().equals(other.getType().toString());
+  }
+
+  @Override
+  public boolean syntacticEquals(JavaExpression je) {
+    if (!(je instanceof ArrayCreation)) {
+      return false;
+    }
+    ArrayCreation other = (ArrayCreation) je;
+    return JavaExpression.syntacticEqualsList(this.dimensions, other.dimensions)
+        && JavaExpression.syntacticEqualsList(this.initializers, other.initializers)
+        && getType().toString().equals(other.getType().toString());
+  }
+
+  @Override
+  public boolean containsSyntacticEqualJavaExpression(JavaExpression other) {
+    return syntacticEquals(other)
+        || JavaExpression.listContainsSyntacticEqualJavaExpression(dimensions, other)
+        || JavaExpression.listContainsSyntacticEqualJavaExpression(initializers, other);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    if (dimensions.isEmpty()) {
+      sb.append("new " + type);
+    } else {
+      sb.append("new " + TypesUtils.getInnermostComponentType((ArrayType) type));
+      for (JavaExpression dim : dimensions) {
+        sb.append("[");
+        sb.append(dim == null ? "" : dim);
+        sb.append("]");
+      }
+    }
+    if (!initializers.isEmpty()) {
+      sb.append(" {");
+      sb.append(StringsPlume.join(", ", initializers));
+      sb.append("}");
+    }
+    return sb.toString();
+  }
+
+  @Override
+  public String toStringDebug() {
+    return "\""
+        + super.toStringDebug()
+        + "\""
+        + " type="
+        + type
+        + " dimensions="
+        + dimensions
+        + " initializers="
+        + initializers;
+  }
+
+  @Override
+  public <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p) {
+    return visitor.visitArrayCreation(this, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/BinaryOperation.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/BinaryOperation.java
new file mode 100644
index 0000000..f4607da
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/BinaryOperation.java
@@ -0,0 +1,225 @@
+package org.checkerframework.dataflow.expression;
+
+import com.sun.source.tree.Tree;
+import java.util.Objects;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.cfg.node.BinaryOperationNode;
+import org.checkerframework.javacutil.AnnotationProvider;
+
+/** JavaExpression for binary operations. */
+public class BinaryOperation extends JavaExpression {
+
+  /** The binary operation kind. */
+  protected final Tree.Kind operationKind;
+  /** The left operand. */
+  protected final JavaExpression left;
+  /** The right operand. */
+  protected final JavaExpression right;
+
+  /**
+   * Create a binary operation.
+   *
+   * @param type the result type
+   * @param operationKind the operator
+   * @param left the left operand
+   * @param right the right operand
+   */
+  public BinaryOperation(
+      TypeMirror type, Tree.Kind operationKind, JavaExpression left, JavaExpression right) {
+    super(type);
+    this.operationKind = operationKind;
+    this.left = left;
+    this.right = right;
+  }
+
+  /**
+   * Create a binary operation.
+   *
+   * @param node the binary operation node
+   * @param left the left operand
+   * @param right the right operand
+   */
+  public BinaryOperation(BinaryOperationNode node, JavaExpression left, JavaExpression right) {
+    this(node.getType(), node.getTree().getKind(), left, right);
+  }
+
+  /**
+   * Returns the operator of this binary operation.
+   *
+   * @return the binary operation kind
+   */
+  public Tree.Kind getOperationKind() {
+    return operationKind;
+  }
+
+  /**
+   * Returns the left operand of this binary operation.
+   *
+   * @return the left operand
+   */
+  public JavaExpression getLeft() {
+    return left;
+  }
+
+  /**
+   * Returns the right operand of this binary operation.
+   *
+   * @return the right operand
+   */
+  public JavaExpression getRight() {
+    return right;
+  }
+
+  @Override
+  public boolean containsOfClass(Class<? extends JavaExpression> clazz) {
+    if (getClass() == clazz) {
+      return true;
+    }
+    return left.containsOfClass(clazz) || right.containsOfClass(clazz);
+  }
+
+  @Override
+  public boolean isDeterministic(AnnotationProvider provider) {
+    return left.isDeterministic(provider) && right.isDeterministic(provider);
+  }
+
+  @Override
+  public boolean isUnassignableByOtherCode() {
+    return left.isUnassignableByOtherCode() && right.isUnassignableByOtherCode();
+  }
+
+  @Override
+  public boolean isUnmodifiableByOtherCode() {
+    return left.isUnmodifiableByOtherCode() && right.isUnmodifiableByOtherCode();
+  }
+
+  @Override
+  public boolean syntacticEquals(JavaExpression je) {
+    if (!(je instanceof BinaryOperation)) {
+      return false;
+    }
+    BinaryOperation other = (BinaryOperation) je;
+    return operationKind == other.getOperationKind()
+        && left.syntacticEquals(other.left)
+        && right.syntacticEquals(other.right);
+  }
+
+  @Override
+  public boolean containsSyntacticEqualJavaExpression(JavaExpression other) {
+    return this.syntacticEquals(other)
+        || left.containsSyntacticEqualJavaExpression(other)
+        || right.containsSyntacticEqualJavaExpression(other);
+  }
+
+  @Override
+  public boolean containsModifiableAliasOf(Store<?> store, JavaExpression other) {
+    return left.containsModifiableAliasOf(store, other)
+        || right.containsModifiableAliasOf(store, other);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(operationKind, left, right);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object other) {
+    if (!(other instanceof BinaryOperation)) {
+      return false;
+    }
+    BinaryOperation biOp = (BinaryOperation) other;
+    if (!(operationKind == biOp.getOperationKind())) {
+      return false;
+    }
+    if (isCommutative()) {
+      return (left.equals(biOp.left) && right.equals(biOp.right))
+          || (left.equals(biOp.right) && right.equals(biOp.left));
+    }
+    return left.equals(biOp.left) && right.equals(biOp.right);
+  }
+
+  /**
+   * Returns true if the binary operation is commutative, e.g., x + y == y + x.
+   *
+   * @return true if the binary operation is commutative
+   */
+  private boolean isCommutative() {
+    switch (operationKind) {
+      case PLUS:
+      case MULTIPLY:
+      case AND:
+      case OR:
+      case XOR:
+      case EQUAL_TO:
+      case NOT_EQUAL_TO:
+      case CONDITIONAL_AND:
+      case CONDITIONAL_OR:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  @Override
+  public String toString() {
+    return left.toString() + " " + operationKindToString(operationKind) + " " + right.toString();
+  }
+
+  /**
+   * Return the Java source code representation of the given operation.
+   *
+   * @param operationKind an unary operation kind
+   * @return the Java source code representation of the given operation
+   */
+  private String operationKindToString(Tree.Kind operationKind) {
+    switch (operationKind) {
+      case CONDITIONAL_AND:
+        return "&&";
+      case AND:
+        return "&";
+      case OR:
+        return "|";
+      case DIVIDE:
+        return "/";
+      case EQUAL_TO:
+        return "==";
+      case GREATER_THAN:
+        return ">";
+      case GREATER_THAN_EQUAL:
+        return ">=";
+      case LEFT_SHIFT:
+        return "<<";
+      case LESS_THAN:
+        return "<";
+      case LESS_THAN_EQUAL:
+        return "<=";
+      case MINUS:
+        return "-";
+      case MULTIPLY:
+        return "*";
+      case NOT_EQUAL_TO:
+        return "!=";
+      case CONDITIONAL_OR:
+        return "||";
+      case PLUS:
+        return "+";
+      case REMAINDER:
+        return "%";
+      case RIGHT_SHIFT:
+        return ">>";
+      case UNSIGNED_RIGHT_SHIFT:
+        return ">>>";
+      case XOR:
+        return "^";
+      default:
+        throw new Error("unhandled " + operationKind);
+    }
+  }
+
+  @Override
+  public <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p) {
+    return visitor.visitBinaryOperation(this, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ClassName.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ClassName.java
new file mode 100644
index 0000000..9e15dbd
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ClassName.java
@@ -0,0 +1,96 @@
+package org.checkerframework.dataflow.expression;
+
+import java.util.Objects;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.javacutil.AnnotationProvider;
+
+/**
+ * A ClassName represents either a class literal or the occurrence of a class as part of a static
+ * field access or static method invocation.
+ */
+public class ClassName extends JavaExpression {
+  /** The string representation of the raw type of this. */
+  private final String typeString;
+
+  /**
+   * Creates a new ClassName object for the given type.
+   *
+   * @param type the type for the new ClassName. If it will represent a class literal, the type is
+   *     declared primitive, void, or array of one of them. If it represents part of a static field
+   *     access or static method invocation, the type is declared, type variable, or array
+   *     (including array of primitive).
+   */
+  public ClassName(TypeMirror type) {
+    super(type);
+    String typeString = type.toString();
+    if (typeString.endsWith(">")) {
+      typeString = typeString.substring(0, typeString.indexOf("<"));
+    }
+    this.typeString = typeString;
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ClassName)) {
+      return false;
+    }
+    ClassName other = (ClassName) obj;
+    return typeString.equals(other.typeString);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(typeString);
+  }
+
+  @Override
+  public String toString() {
+    return typeString + ".class";
+  }
+
+  @Override
+  public boolean containsOfClass(Class<? extends JavaExpression> clazz) {
+    return getClass() == clazz;
+  }
+
+  @Override
+  public boolean isDeterministic(AnnotationProvider provider) {
+    return true;
+  }
+
+  @Override
+  public boolean isUnassignableByOtherCode() {
+    return true;
+  }
+
+  @Override
+  public boolean isUnmodifiableByOtherCode() {
+    return true;
+  }
+
+  @Override
+  public boolean syntacticEquals(JavaExpression je) {
+    if (!(je instanceof ClassName)) {
+      return false;
+    }
+    ClassName other = (ClassName) je;
+    return typeString.equals(other.typeString);
+  }
+
+  @Override
+  public boolean containsSyntacticEqualJavaExpression(JavaExpression other) {
+    return this.syntacticEquals(other);
+  }
+
+  @Override
+  public boolean containsModifiableAliasOf(Store<?> store, JavaExpression other) {
+    return false; // not modifiable
+  }
+
+  @Override
+  public <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p) {
+    return visitor.visitClassName(this, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/FieldAccess.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/FieldAccess.java
new file mode 100644
index 0000000..e0316c3
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/FieldAccess.java
@@ -0,0 +1,173 @@
+package org.checkerframework.dataflow.expression;
+
+import com.sun.tools.javac.code.Symbol;
+import java.util.Objects;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * A FieldAccess represents a field access. It does not represent a class literal such as {@code
+ * SomeClass.class} or {@code int[].class}.
+ */
+public class FieldAccess extends JavaExpression {
+  /** The receiver of the field access. */
+  protected final JavaExpression receiver;
+  /** The field being accessed. */
+  protected final VariableElement field;
+
+  /**
+   * Returns the receiver.
+   *
+   * @return the receiver
+   */
+  public JavaExpression getReceiver() {
+    return receiver;
+  }
+
+  /**
+   * Returns the field.
+   *
+   * @return the field
+   */
+  public VariableElement getField() {
+    return field;
+  }
+
+  /**
+   * Create a {@code FieldAccess}.
+   *
+   * @param receiver receiver of the field access
+   * @param node FieldAccessNode
+   */
+  public FieldAccess(JavaExpression receiver, FieldAccessNode node) {
+    this(receiver, node.getType(), node.getElement());
+  }
+
+  /**
+   * Create a {@code FieldAccess}.
+   *
+   * @param receiver receiver of the field access
+   * @param fieldElement element of the field
+   */
+  public FieldAccess(JavaExpression receiver, VariableElement fieldElement) {
+    this(receiver, fieldElement.asType(), fieldElement);
+  }
+
+  /**
+   * Create a {@code FieldAccess}.
+   *
+   * @param receiver receiver of the field access
+   * @param type type of the field
+   * @param fieldElement element of the field
+   */
+  public FieldAccess(JavaExpression receiver, TypeMirror type, VariableElement fieldElement) {
+    super(type);
+    this.receiver = receiver;
+    this.field = fieldElement;
+    String fieldName = fieldElement.toString();
+    if (fieldName.equals("class") || fieldName.equals("this")) {
+      Error e =
+          new Error(
+              String.format(
+                  "bad field name \"%s\" in new FieldAccess(%s, %s, %s)%n",
+                  fieldName, receiver, type, fieldElement));
+      e.printStackTrace(System.out);
+      e.printStackTrace(System.err);
+      throw e;
+    }
+  }
+
+  public boolean isFinal() {
+    return ElementUtils.isFinal(field);
+  }
+
+  public boolean isStatic() {
+    return ElementUtils.isStatic(field);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof FieldAccess)) {
+      return false;
+    }
+    FieldAccess fa = (FieldAccess) obj;
+    return fa.getField().equals(getField()) && fa.getReceiver().equals(getReceiver());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getField(), getReceiver());
+  }
+
+  @Override
+  public boolean syntacticEquals(JavaExpression je) {
+    if (!(je instanceof FieldAccess)) {
+      return false;
+    }
+    FieldAccess other = (FieldAccess) je;
+    return this.receiver.syntacticEquals(other.receiver) && this.field.equals(other.field);
+  }
+
+  @Override
+  public boolean containsSyntacticEqualJavaExpression(JavaExpression other) {
+    return syntacticEquals(other) || receiver.containsSyntacticEqualJavaExpression(other);
+  }
+
+  @Override
+  public boolean containsModifiableAliasOf(Store<?> store, JavaExpression other) {
+    return super.containsModifiableAliasOf(store, other)
+        || receiver.containsModifiableAliasOf(store, other);
+  }
+
+  @Override
+  public String toString() {
+    if (receiver instanceof ClassName) {
+      return receiver.getType() + "." + field;
+    } else {
+      return receiver + "." + field;
+    }
+  }
+
+  @Override
+  public String toStringDebug() {
+    return String.format(
+        "FieldAccess(type=%s, receiver=%s, field=%s [%s] [%s] owner=%s)",
+        type,
+        receiver.toStringDebug(),
+        field,
+        field.getClass().getSimpleName(),
+        System.identityHashCode(field),
+        ((Symbol) field).owner);
+  }
+
+  @Override
+  public boolean containsOfClass(Class<? extends JavaExpression> clazz) {
+    return getClass() == clazz || receiver.containsOfClass(clazz);
+  }
+
+  @Override
+  public boolean isDeterministic(AnnotationProvider provider) {
+    return receiver.isDeterministic(provider);
+  }
+
+  @Override
+  public boolean isUnassignableByOtherCode() {
+    return isFinal() && getReceiver().isUnassignableByOtherCode();
+  }
+
+  @Override
+  public boolean isUnmodifiableByOtherCode() {
+    return isUnassignableByOtherCode() && TypesUtils.isImmutableTypeInJdk(getReceiver().type);
+  }
+
+  @Override
+  public <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p) {
+    return visitor.visitFieldAccess(this, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/FormalParameter.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/FormalParameter.java
new file mode 100644
index 0000000..6a05850
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/FormalParameter.java
@@ -0,0 +1,127 @@
+package org.checkerframework.dataflow.expression;
+
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import java.util.Objects;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TypeAnnotationUtils;
+
+/**
+ * A formal parameter, represented by its 1-based index.
+ *
+ * <p>{@link LocalVariable} represents a formal parameter expressed using its name.
+ */
+public class FormalParameter extends JavaExpression {
+
+  /** The 1-based index. */
+  protected final int index;
+
+  /** The element for this formal parameter. */
+  protected final VariableElement element;
+
+  /**
+   * Creates a FormalParameter.
+   *
+   * @param index the 1-based index
+   * @param element the element for the formal parameter
+   */
+  public FormalParameter(int index, VariableElement element) {
+    super(ElementUtils.getType(element));
+    this.index = index;
+    this.element = element;
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof FormalParameter)) {
+      return false;
+    }
+
+    FormalParameter other = (FormalParameter) obj;
+    return this.index == other.index && LocalVariable.sameElement(this.element, other.element);
+  }
+
+  /**
+   * Returns the 1-based index of this formal parameter.
+   *
+   * @return the 1-based index of this formal parameter
+   */
+  public int getIndex() {
+    return index;
+  }
+
+  /**
+   * Returns the element for this variable.
+   *
+   * @return the element for this variable
+   */
+  public VariableElement getElement() {
+    return element;
+  }
+
+  @Override
+  public int hashCode() {
+    VarSymbol vs = (VarSymbol) element;
+    return Objects.hash(
+        index,
+        vs.name.toString(),
+        TypeAnnotationUtils.unannotatedType(vs.type).toString(),
+        vs.owner.toString());
+  }
+
+  @Override
+  public String toString() {
+    return "#" + index;
+  }
+
+  @Override
+  public String toStringDebug() {
+    return super.toStringDebug()
+        + " [element="
+        + element
+        + ", owner="
+        + ((VarSymbol) element).owner
+        + "]";
+  }
+
+  @Override
+  public boolean containsOfClass(Class<? extends JavaExpression> clazz) {
+    return getClass() == clazz;
+  }
+
+  @Override
+  public boolean syntacticEquals(JavaExpression je) {
+    if (!(je instanceof FormalParameter)) {
+      return false;
+    }
+    FormalParameter other = (FormalParameter) je;
+    return index == other.index;
+  }
+
+  @Override
+  public boolean containsSyntacticEqualJavaExpression(JavaExpression other) {
+    return syntacticEquals(other);
+  }
+
+  @Override
+  public boolean isUnassignableByOtherCode() {
+    return true;
+  }
+
+  @Override
+  public boolean isUnmodifiableByOtherCode() {
+    return true;
+  }
+
+  @Override
+  public boolean isDeterministic(AnnotationProvider provider) {
+    return true;
+  }
+
+  @Override
+  public <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p) {
+    return visitor.visitFormalParameter(this, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java
new file mode 100644
index 0000000..19699e1
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java
@@ -0,0 +1,794 @@
+package org.checkerframework.dataflow.expression;
+
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.interning.qual.EqualsMethod;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
+import org.checkerframework.dataflow.cfg.node.ArrayCreationNode;
+import org.checkerframework.dataflow.cfg.node.BinaryOperationNode;
+import org.checkerframework.dataflow.cfg.node.ClassNameNode;
+import org.checkerframework.dataflow.cfg.node.ExplicitThisNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.NarrowingConversionNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.StringConversionNode;
+import org.checkerframework.dataflow.cfg.node.SuperNode;
+import org.checkerframework.dataflow.cfg.node.ThisNode;
+import org.checkerframework.dataflow.cfg.node.UnaryOperationNode;
+import org.checkerframework.dataflow.cfg.node.ValueLiteralNode;
+import org.checkerframework.dataflow.cfg.node.WideningConversionNode;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+
+// The Lock Checker also supports "<self>" as a JavaExpression, but that is implemented in the Lock
+// Checker.
+// There are no special subclasses (AST nodes) for "<self>".
+/**
+ * This class represents a Java expression and its type. It does not represent all possible Java
+ * expressions (for example, it does not represent a ternary conditional expression {@code ?:}; use
+ * {@link org.checkerframework.dataflow.expression.Unknown} for unrepresentable expressions).
+ *
+ * <p>This class's representation is like an AST: subparts are also expressions. For declared names
+ * (fields, local variables, and methods), it also contains an Element.
+ *
+ * <p>Each subclass represents a different type of expression, such as {@link
+ * org.checkerframework.dataflow.expression.MethodCall}, {@link
+ * org.checkerframework.dataflow.expression.ArrayAccess}, {@link
+ * org.checkerframework.dataflow.expression.LocalVariable}, etc.
+ *
+ * @see <a href="https://checkerframework.org/manual/#java-expressions-as-arguments">the syntax of
+ *     Java expressions supported by the Checker Framework</a>
+ */
+public abstract class JavaExpression {
+  /** The type of this expression. */
+  protected final TypeMirror type;
+
+  /**
+   * Create a JavaExpression.
+   *
+   * @param type the type of the expression
+   */
+  protected JavaExpression(TypeMirror type) {
+    assert type != null;
+    this.type = type;
+  }
+
+  public TypeMirror getType() {
+    return type;
+  }
+
+  public abstract boolean containsOfClass(Class<? extends JavaExpression> clazz);
+
+  public boolean containsUnknown() {
+    return containsOfClass(Unknown.class);
+  }
+
+  /**
+   * Returns true if the expression is deterministic.
+   *
+   * @param provider an annotation provider (a type factory)
+   * @return true if this expression is deterministic
+   */
+  public abstract boolean isDeterministic(AnnotationProvider provider);
+
+  /**
+   * Returns true if all the expressions in the list are deterministic.
+   *
+   * @param list the list whose elements to test
+   * @param provider an annotation provider (a type factory)
+   * @return true if all the expressions in the list are deterministic
+   */
+  @SuppressWarnings("nullness:dereference.of.nullable") // flow within a lambda
+  public static boolean listIsDeterministic(
+      List<? extends @Nullable JavaExpression> list, AnnotationProvider provider) {
+    return list.stream().allMatch(je -> je == null || je.isDeterministic(provider));
+  }
+
+  /**
+   * Returns true if and only if the value this expression stands for cannot be changed (with
+   * respect to ==) by a method call. This is the case for local variables, the self reference,
+   * final field accesses whose receiver is {@link #isUnassignableByOtherCode}, and operations whose
+   * operands are all {@link #isUnmodifiableByOtherCode}.
+   *
+   * @see #isUnmodifiableByOtherCode
+   */
+  public abstract boolean isUnassignableByOtherCode();
+
+  /**
+   * Returns true if and only if the value this expression stands for cannot be changed by a method
+   * call, including changes to any of its fields.
+   *
+   * <p>Approximately, this returns true if the expression is {@link #isUnassignableByOtherCode} and
+   * its type is immutable.
+   *
+   * @see #isUnassignableByOtherCode
+   */
+  public abstract boolean isUnmodifiableByOtherCode();
+
+  /**
+   * Returns true if and only if the two Java expressions are syntactically identical.
+   *
+   * <p>This exists for use by {@link #containsSyntacticEqualJavaExpression}.
+   *
+   * @param je the other Java expression to compare to this one
+   * @return true if and only if the two Java expressions are syntactically identical
+   */
+  @EqualsMethod
+  public abstract boolean syntacticEquals(JavaExpression je);
+
+  /**
+   * Returns true if the corresponding list elements satisfy {@link #syntacticEquals}.
+   *
+   * @param lst1 the first list to compare
+   * @param lst2 the second list to compare
+   * @return true if the corresponding list elements satisfy {@link #syntacticEquals}
+   */
+  static boolean syntacticEqualsList(
+      List<? extends @Nullable JavaExpression> lst1,
+      List<? extends @Nullable JavaExpression> lst2) {
+    if (lst1.size() != lst2.size()) {
+      return false;
+    }
+    for (int i = 0; i < lst1.size(); i++) {
+      JavaExpression dim1 = lst1.get(i);
+      JavaExpression dim2 = lst2.get(i);
+      if (dim1 == null && dim2 == null) {
+        continue;
+      } else if (dim1 == null || dim2 == null) {
+        return false;
+      } else {
+        if (!dim1.syntacticEquals(dim2)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns true if and only if this contains a JavaExpression that is syntactically equal to
+   * {@code other}.
+   *
+   * @param other the JavaExpression to search for
+   * @return true if and only if this contains a JavaExpression that is syntactically equal to
+   *     {@code other}
+   */
+  public abstract boolean containsSyntacticEqualJavaExpression(JavaExpression other);
+
+  /**
+   * Returns true if the given list contains a JavaExpression that is syntactically equal to {@code
+   * other}.
+   *
+   * @param list the list in which to search for a match
+   * @param other the JavaExpression to search for
+   * @return true if and only if the list contains a JavaExpression that is syntactically equal to
+   *     {@code other}
+   */
+  @SuppressWarnings("nullness:dereference.of.nullable") // flow within a lambda
+  public static boolean listContainsSyntacticEqualJavaExpression(
+      List<? extends @Nullable JavaExpression> list, JavaExpression other) {
+    return list.stream()
+        .anyMatch(je -> je != null && je.containsSyntacticEqualJavaExpression(other));
+  }
+
+  /**
+   * Returns true if and only if {@code other} appears anywhere in this or an expression appears in
+   * this such that {@code other} might alias this expression, and that expression is modifiable.
+   *
+   * <p>This is always true, except for cases where the Java type information prevents aliasing and
+   * none of the subexpressions can alias 'other'.
+   */
+  public boolean containsModifiableAliasOf(Store<?> store, JavaExpression other) {
+    return this.equals(other) || store.canAlias(this, other);
+  }
+
+  /**
+   * Format this verbosely, for debugging.
+   *
+   * @return a verbose string representation of this
+   */
+  public String toStringDebug() {
+    return String.format("%s(%s): %s", getClass().getSimpleName(), type, toString());
+  }
+
+  ///
+  /// Static methods
+  ///
+
+  /**
+   * Returns the Java expression for a {@link FieldAccessNode}. The result may contain {@link
+   * Unknown} as receiver.
+   *
+   * @param node the FieldAccessNode to convert to a JavaExpression
+   * @return the {@link FieldAccess} or {@link ClassName} that corresponds to {@code node}
+   */
+  public static JavaExpression fromNodeFieldAccess(FieldAccessNode node) {
+    Node receiverNode = node.getReceiver();
+    String fieldName = node.getFieldName();
+    if (fieldName.equals("this")) {
+      // The CFG represents "className.this" as a FieldAccessNode, but it isn't a field access.
+      return new ThisReference(receiverNode.getType());
+    } else if (fieldName.equals("class")) {
+      // The CFG represents "className.class" as a FieldAccessNode; bit it is a class literal.
+      return new ClassName(receiverNode.getType());
+    }
+    JavaExpression receiver;
+    if (node.isStatic()) {
+      receiver = new ClassName(receiverNode.getType());
+    } else {
+      receiver = fromNode(receiverNode);
+    }
+    return new FieldAccess(receiver, node);
+  }
+
+  /**
+   * Returns the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. The
+   * result may contain {@link Unknown} as receiver.
+   *
+   * @param node the ArrayAccessNode to convert to a JavaExpression
+   * @return the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. Can
+   *     contain {@link Unknown} as receiver.
+   */
+  public static ArrayAccess fromArrayAccess(ArrayAccessNode node) {
+    JavaExpression array = fromNode(node.getArray());
+    JavaExpression index = fromNode(node.getIndex());
+    return new ArrayAccess(node.getType(), array, index);
+  }
+
+  /**
+   * We ignore operations such as widening and narrowing when computing the internal representation.
+   *
+   * @param receiverNode a node to convert to a JavaExpression
+   * @return the internal representation of the given node. Might contain {@link Unknown}.
+   */
+  public static JavaExpression fromNode(Node receiverNode) {
+    JavaExpression result = null;
+    if (receiverNode instanceof FieldAccessNode) {
+      result = fromNodeFieldAccess((FieldAccessNode) receiverNode);
+    } else if (receiverNode instanceof ExplicitThisNode) {
+      result = new ThisReference(receiverNode.getType());
+    } else if (receiverNode instanceof ThisNode) {
+      result = new ThisReference(receiverNode.getType());
+    } else if (receiverNode instanceof SuperNode) {
+      result = new ThisReference(receiverNode.getType());
+    } else if (receiverNode instanceof LocalVariableNode) {
+      LocalVariableNode lv = (LocalVariableNode) receiverNode;
+      result = new LocalVariable(lv);
+    } else if (receiverNode instanceof ArrayAccessNode) {
+      ArrayAccessNode a = (ArrayAccessNode) receiverNode;
+      result = fromArrayAccess(a);
+    } else if (receiverNode instanceof StringConversionNode) {
+      // ignore string conversion
+      return fromNode(((StringConversionNode) receiverNode).getOperand());
+    } else if (receiverNode instanceof WideningConversionNode) {
+      // ignore widening
+      return fromNode(((WideningConversionNode) receiverNode).getOperand());
+    } else if (receiverNode instanceof NarrowingConversionNode) {
+      // ignore narrowing
+      return fromNode(((NarrowingConversionNode) receiverNode).getOperand());
+    } else if (receiverNode instanceof UnaryOperationNode) {
+      UnaryOperationNode uopn = (UnaryOperationNode) receiverNode;
+      return new UnaryOperation(uopn, fromNode(uopn.getOperand()));
+    } else if (receiverNode instanceof BinaryOperationNode) {
+      BinaryOperationNode bopn = (BinaryOperationNode) receiverNode;
+      return new BinaryOperation(
+          bopn, fromNode(bopn.getLeftOperand()), fromNode(bopn.getRightOperand()));
+    } else if (receiverNode instanceof ClassNameNode) {
+      ClassNameNode cn = (ClassNameNode) receiverNode;
+      result = new ClassName(cn.getType());
+    } else if (receiverNode instanceof ValueLiteralNode) {
+      ValueLiteralNode vn = (ValueLiteralNode) receiverNode;
+      result = new ValueLiteral(vn.getType(), vn);
+    } else if (receiverNode instanceof ArrayCreationNode) {
+      ArrayCreationNode an = (ArrayCreationNode) receiverNode;
+      List<@Nullable JavaExpression> dimensions =
+          CollectionsPlume.mapList(JavaExpression::fromNode, an.getDimensions());
+      List<JavaExpression> initializers =
+          CollectionsPlume.mapList(JavaExpression::fromNode, an.getInitializers());
+      result = new ArrayCreation(an.getType(), dimensions, initializers);
+    } else if (receiverNode instanceof MethodInvocationNode) {
+      MethodInvocationNode mn = (MethodInvocationNode) receiverNode;
+      MethodInvocationTree t = mn.getTree();
+      if (t == null) {
+        throw new BugInCF("Unexpected null tree for node: " + mn);
+      }
+      assert TreeUtils.isUseOfElement(t) : "@AssumeAssertion(nullness): tree kind";
+      ExecutableElement invokedMethod = TreeUtils.elementFromUse(t);
+
+      // Note that the method might be nondeterministic.
+      List<JavaExpression> parameters =
+          CollectionsPlume.mapList(JavaExpression::fromNode, mn.getArguments());
+      JavaExpression methodReceiver;
+      if (ElementUtils.isStatic(invokedMethod)) {
+        methodReceiver = new ClassName(mn.getTarget().getReceiver().getType());
+      } else {
+        methodReceiver = fromNode(mn.getTarget().getReceiver());
+      }
+      result = new MethodCall(mn.getType(), invokedMethod, methodReceiver, parameters);
+    }
+
+    if (result == null) {
+      result = new Unknown(receiverNode);
+    }
+    return result;
+  }
+
+  /**
+   * Converts a javac {@link ExpressionTree} to a CF JavaExpression. The result might contain {@link
+   * Unknown}.
+   *
+   * <p>We ignore operations such as widening and narrowing when computing the JavaExpression.
+   *
+   * @param tree a javac tree
+   * @return a JavaExpression for the given javac tree
+   */
+  public static JavaExpression fromTree(ExpressionTree tree) {
+    JavaExpression result;
+    switch (tree.getKind()) {
+      case ARRAY_ACCESS:
+        ArrayAccessTree a = (ArrayAccessTree) tree;
+        JavaExpression arrayAccessExpression = fromTree(a.getExpression());
+        JavaExpression index = fromTree(a.getIndex());
+        result = new ArrayAccess(TreeUtils.typeOf(a), arrayAccessExpression, index);
+        break;
+
+      case BOOLEAN_LITERAL:
+      case CHAR_LITERAL:
+      case DOUBLE_LITERAL:
+      case FLOAT_LITERAL:
+      case INT_LITERAL:
+      case LONG_LITERAL:
+      case NULL_LITERAL:
+      case STRING_LITERAL:
+        LiteralTree vn = (LiteralTree) tree;
+        result = new ValueLiteral(TreeUtils.typeOf(tree), vn.getValue());
+        break;
+
+      case NEW_ARRAY:
+        NewArrayTree newArrayTree = (NewArrayTree) tree;
+        List<@Nullable JavaExpression> dimensions;
+        if (newArrayTree.getDimensions() == null) {
+          dimensions = Collections.emptyList();
+        } else {
+          dimensions = new ArrayList<>(newArrayTree.getDimensions().size());
+          for (ExpressionTree dimension : newArrayTree.getDimensions()) {
+            dimensions.add(fromTree(dimension));
+          }
+        }
+        List<JavaExpression> initializers;
+        if (newArrayTree.getInitializers() == null) {
+          initializers = Collections.emptyList();
+        } else {
+          initializers = new ArrayList<>(newArrayTree.getInitializers().size());
+          for (ExpressionTree initializer : newArrayTree.getInitializers()) {
+            initializers.add(fromTree(initializer));
+          }
+        }
+
+        result = new ArrayCreation(TreeUtils.typeOf(tree), dimensions, initializers);
+        break;
+
+      case METHOD_INVOCATION:
+        MethodInvocationTree mn = (MethodInvocationTree) tree;
+        assert TreeUtils.isUseOfElement(mn) : "@AssumeAssertion(nullness): tree kind";
+        ExecutableElement invokedMethod = TreeUtils.elementFromUse(mn);
+
+        // Note that the method might be nondeterministic.
+        List<JavaExpression> parameters =
+            CollectionsPlume.mapList(JavaExpression::fromTree, mn.getArguments());
+        JavaExpression methodReceiver;
+        if (ElementUtils.isStatic(invokedMethod)) {
+          @SuppressWarnings(
+              "nullness:assignment" // enclosingTypeElement(ExecutableElement): @NonNull
+          )
+          @NonNull TypeElement methodType = ElementUtils.enclosingTypeElement(invokedMethod);
+          methodReceiver = new ClassName(methodType.asType());
+        } else {
+          methodReceiver = getReceiver(mn);
+        }
+        TypeMirror resultType = TreeUtils.typeOf(mn);
+        result = new MethodCall(resultType, invokedMethod, methodReceiver, parameters);
+        break;
+
+      case MEMBER_SELECT:
+        result = fromMemberSelect((MemberSelectTree) tree);
+        break;
+
+      case IDENTIFIER:
+        IdentifierTree identifierTree = (IdentifierTree) tree;
+        TypeMirror typeOfId = TreeUtils.typeOf(identifierTree);
+        Name identifierName = identifierTree.getName();
+        if (identifierName.contentEquals("this") || identifierName.contentEquals("super")) {
+          result = new ThisReference(typeOfId);
+          break;
+        }
+        assert TreeUtils.isUseOfElement(identifierTree) : "@AssumeAssertion(nullness): tree kind";
+        Element ele = TreeUtils.elementFromUse(identifierTree);
+        if (ElementUtils.isTypeElement(ele)) {
+          result = new ClassName(ele.asType());
+          break;
+        }
+        result = fromVariableElement(typeOfId, ele);
+        break;
+
+      case UNARY_PLUS:
+        return fromTree(((UnaryTree) tree).getExpression());
+      case BITWISE_COMPLEMENT:
+      case LOGICAL_COMPLEMENT:
+      case POSTFIX_DECREMENT:
+      case POSTFIX_INCREMENT:
+      case PREFIX_DECREMENT:
+      case PREFIX_INCREMENT:
+      case UNARY_MINUS:
+        JavaExpression operand = fromTree(((UnaryTree) tree).getExpression());
+        return new UnaryOperation(TreeUtils.typeOf(tree), tree.getKind(), operand);
+
+      case CONDITIONAL_AND:
+      case CONDITIONAL_OR:
+      case DIVIDE:
+      case EQUAL_TO:
+      case GREATER_THAN:
+      case GREATER_THAN_EQUAL:
+      case LEFT_SHIFT:
+      case LESS_THAN:
+      case LESS_THAN_EQUAL:
+      case MINUS:
+      case MULTIPLY:
+      case NOT_EQUAL_TO:
+      case OR:
+      case PLUS:
+      case REMAINDER:
+      case RIGHT_SHIFT:
+      case UNSIGNED_RIGHT_SHIFT:
+      case XOR:
+        BinaryTree binaryTree = (BinaryTree) tree;
+        JavaExpression left = fromTree(binaryTree.getLeftOperand());
+        JavaExpression right = fromTree(binaryTree.getRightOperand());
+        return new BinaryOperation(TreeUtils.typeOf(tree), tree.getKind(), left, right);
+
+      default:
+        result = null;
+    }
+
+    if (result == null) {
+      result = new Unknown(tree);
+    }
+    return result;
+  }
+
+  /**
+   * Returns the Java expression corresponding to the given variable tree {@code tree}.
+   *
+   * @param tree a variable tree
+   * @return a JavaExpression for {@code tree}
+   */
+  public static JavaExpression fromVariableTree(VariableTree tree) {
+    return fromVariableElement(TreeUtils.typeOf(tree), TreeUtils.elementFromDeclaration(tree));
+  }
+
+  /**
+   * Returns the Java expression corresponding to the given variable element {@code ele}.
+   *
+   * @param typeOfEle the type of {@code ele}
+   * @param ele element whose JavaExpression is returned
+   * @return the Java expression corresponding to the given variable element {@code ele}
+   */
+  private static JavaExpression fromVariableElement(TypeMirror typeOfEle, Element ele) {
+    switch (ele.getKind()) {
+      case LOCAL_VARIABLE:
+      case RESOURCE_VARIABLE:
+      case EXCEPTION_PARAMETER:
+      case PARAMETER:
+        return new LocalVariable(ele);
+      case FIELD:
+      case ENUM_CONSTANT:
+        // Implicit access expression, such as "this" or a class name
+        JavaExpression fieldAccessExpression;
+        @SuppressWarnings("nullness:dereference.of.nullable") // a field has enclosing class
+        TypeMirror enclosingTypeElement = ElementUtils.enclosingTypeElement(ele).asType();
+        if (ElementUtils.isStatic(ele)) {
+          fieldAccessExpression = new ClassName(enclosingTypeElement);
+        } else {
+          fieldAccessExpression = new ThisReference(enclosingTypeElement);
+        }
+        return new FieldAccess(fieldAccessExpression, typeOfEle, (VariableElement) ele);
+      default:
+        throw new BugInCF(
+            "Unexpected kind of VariableTree: kind: %s element: %s", ele.getKind(), ele);
+    }
+  }
+
+  /**
+   * Creates a JavaExpression from the {@code memberSelectTree}.
+   *
+   * @param memberSelectTree tree
+   * @return a JavaExpression for {@code memberSelectTree}
+   */
+  private static JavaExpression fromMemberSelect(MemberSelectTree memberSelectTree) {
+    TypeMirror expressionType = TreeUtils.typeOf(memberSelectTree.getExpression());
+    if (TreeUtils.isClassLiteral(memberSelectTree)) {
+      // the identifier is "class"
+      return new ClassName(expressionType);
+    }
+    if (TreeUtils.isExplicitThisDereference(memberSelectTree)) {
+      // the identifier is "class"
+      return new ThisReference(expressionType);
+    }
+
+    assert TreeUtils.isUseOfElement(memberSelectTree) : "@AssumeAssertion(nullness): tree kind";
+    Element ele = TreeUtils.elementFromUse(memberSelectTree);
+    if (ElementUtils.isTypeElement(ele)) {
+      // o instanceof MyClass.InnerClass
+      // o instanceof MyClass.InnerInterface
+      TypeMirror selectType = TreeUtils.typeOf(memberSelectTree);
+      return new ClassName(selectType);
+    }
+    switch (ele.getKind()) {
+      case METHOD:
+      case CONSTRUCTOR:
+        return fromTree(memberSelectTree.getExpression());
+      case ENUM_CONSTANT:
+      case FIELD:
+        TypeMirror fieldType = TreeUtils.typeOf(memberSelectTree);
+        JavaExpression je = fromTree(memberSelectTree.getExpression());
+        return new FieldAccess(je, fieldType, (VariableElement) ele);
+      default:
+        throw new BugInCF("Unexpected element kind: %s element: %s", ele.getKind(), ele);
+    }
+  }
+
+  /**
+   * Returns the parameters of {@code methodEle} as {@link LocalVariable}s.
+   *
+   * @param methodEle the method element
+   * @return list of parameters as {@link LocalVariable}s
+   */
+  public static List<JavaExpression> getParametersAsLocalVariables(ExecutableElement methodEle) {
+    return CollectionsPlume.mapList(LocalVariable::new, methodEle.getParameters());
+  }
+
+  /**
+   * Returns the parameters of {@code methodEle} as {@link FormalParameter}s.
+   *
+   * @param methodEle the method element
+   * @return list of parameters as {@link FormalParameter}s
+   */
+  public static List<FormalParameter> getFormalParameters(ExecutableElement methodEle) {
+    List<FormalParameter> parameters = new ArrayList<>(methodEle.getParameters().size());
+    int oneBasedIndex = 1;
+    for (VariableElement variableElement : methodEle.getParameters()) {
+      parameters.add(new FormalParameter(oneBasedIndex, variableElement));
+      oneBasedIndex++;
+    }
+    return parameters;
+  }
+
+  ///
+  /// Obtaining the receiver
+  ///
+
+  /**
+   * Returns the receiver of the given invocation
+   *
+   * @param accessTree method or constructor invocation
+   * @return the receiver of the given invocation
+   */
+  public static JavaExpression getReceiver(ExpressionTree accessTree) {
+    // TODO: Handle field accesses too?
+    assert accessTree instanceof MethodInvocationTree || accessTree instanceof NewClassTree;
+    ExpressionTree receiverTree = TreeUtils.getReceiverTree(accessTree);
+    if (receiverTree != null) {
+      return fromTree(receiverTree);
+    } else {
+      Element ele = TreeUtils.elementFromUse(accessTree);
+      if (ele == null) {
+        throw new BugInCF("TreeUtils.elementFromUse(" + accessTree + ") => null");
+      }
+      return getImplicitReceiver(ele);
+    }
+  }
+
+  /**
+   * Returns the implicit receiver of ele.
+   *
+   * <p>Returns either a new ClassName or a new ThisReference depending on whether ele is static or
+   * not. The passed element must be a field, method, or class.
+   *
+   * @param ele a field, method, or class
+   * @return either a new ClassName or a new ThisReference depending on whether ele is static or not
+   */
+  public static JavaExpression getImplicitReceiver(Element ele) {
+    TypeElement enclosingTypeElement = ElementUtils.enclosingTypeElement(ele);
+    if (enclosingTypeElement == null) {
+      throw new BugInCF("getImplicitReceiver's arg has no enclosing type: " + ele);
+    }
+    TypeMirror enclosingType = enclosingTypeElement.asType();
+    if (ElementUtils.isStatic(ele)) {
+      return new ClassName(enclosingType);
+    } else {
+      return new ThisReference(enclosingType);
+    }
+  }
+
+  /**
+   * Returns either a new ClassName or ThisReference JavaExpression object for the enclosingType.
+   *
+   * <p>The Tree should be an expression or a statement that does not have a receiver or an implicit
+   * receiver. For example, a local variable declaration.
+   *
+   * @param path TreePath to tree
+   * @param enclosingType type of the enclosing type
+   * @return a new ClassName or ThisReference that is a JavaExpression object for the enclosingType
+   */
+  public static JavaExpression getPseudoReceiver(TreePath path, TypeMirror enclosingType) {
+    if (TreePathUtil.isTreeInStaticScope(path)) {
+      return new ClassName(enclosingType);
+    } else {
+      return new ThisReference(enclosingType);
+    }
+  }
+
+  /**
+   * Accept method of the visitor pattern.
+   *
+   * @param visitor the visitor to be applied to this JavaExpression
+   * @param p the parameter for this operation
+   * @param <R> result type of the operation
+   * @param <P> parameter type
+   * @return the result of visiting this
+   */
+  public abstract <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p);
+
+  /**
+   * Viewpoint-adapts {@code this} to a field access with receiver {@code receiver}.
+   *
+   * @param receiver receiver of the field access
+   * @return viewpoint-adapted version of this
+   */
+  public JavaExpression atFieldAccess(JavaExpression receiver) {
+    return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiver);
+  }
+
+  /**
+   * Viewpoint-adapts {@code this} to the {@code methodTree} by converting any {@code
+   * FormalParameter} into {@code LocalVariable}s.
+   *
+   * @param methodTree method declaration tree
+   * @return viewpoint-adapted version of this
+   */
+  public final JavaExpression atMethodBody(MethodTree methodTree) {
+    List<JavaExpression> parametersJe =
+        CollectionsPlume.mapList(
+            (VariableTree param) -> new LocalVariable(TreeUtils.elementFromDeclaration(param)),
+            methodTree.getParameters());
+    return ViewpointAdaptJavaExpression.viewpointAdapt(this, parametersJe);
+  }
+
+  /**
+   * Viewpoint-adapts {@code this} to the {@code methodInvocationTree}.
+   *
+   * @param methodInvocationTree method invocation
+   * @return viewpoint-adapted version of this
+   */
+  public final JavaExpression atMethodInvocation(MethodInvocationTree methodInvocationTree) {
+    JavaExpression receiverJe = JavaExpression.getReceiver(methodInvocationTree);
+    List<JavaExpression> argumentsJe =
+        argumentTreesToJavaExpressions(
+            TreeUtils.elementFromUse(methodInvocationTree), methodInvocationTree.getArguments());
+    return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe);
+  }
+
+  /**
+   * Viewpoint-adapts {@code this} to the {@code invocationNode}.
+   *
+   * @param invocationNode method invocation
+   * @return viewpoint-adapted version of this
+   */
+  public final JavaExpression atMethodInvocation(MethodInvocationNode invocationNode) {
+    JavaExpression receiverJe = JavaExpression.fromNode(invocationNode.getTarget().getReceiver());
+    List<JavaExpression> argumentsJe =
+        CollectionsPlume.mapList(JavaExpression::fromNode, invocationNode.getArguments());
+    return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe);
+  }
+
+  /**
+   * Viewpoint-adapts {@code this} to the {@code newClassTree}.
+   *
+   * @param newClassTree constructor invocation
+   * @return viewpoint-adapted version of this
+   */
+  public JavaExpression atConstructorInvocation(NewClassTree newClassTree) {
+    JavaExpression receiverJe = JavaExpression.getReceiver(newClassTree);
+    List<JavaExpression> argumentsJe =
+        argumentTreesToJavaExpressions(
+            TreeUtils.elementFromUse(newClassTree), newClassTree.getArguments());
+    return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe);
+  }
+
+  /**
+   * Converts method or constructor arguments from Trees to JavaExpressions, accounting for varargs.
+   *
+   * @param method the method or constructor being invoked
+   * @param argTrees the arguments to the method or constructor
+   * @return the arguments, as JavaExpressions
+   */
+  private static List<JavaExpression> argumentTreesToJavaExpressions(
+      ExecutableElement method, List<? extends ExpressionTree> argTrees) {
+    if (isVarArgsInvocation(method, argTrees)) {
+      List<JavaExpression> result = new ArrayList<>(method.getParameters().size());
+      for (int i = 0; i < method.getParameters().size() - 1; i++) {
+        result.add(JavaExpression.fromTree(argTrees.get(i)));
+      }
+
+      List<JavaExpression> varargArgs =
+          new ArrayList<>(argTrees.size() - method.getParameters().size() + 1);
+      for (int i = method.getParameters().size() - 1; i < argTrees.size(); i++) {
+        varargArgs.add(JavaExpression.fromTree(argTrees.get(i)));
+      }
+      Element varargsElement = method.getParameters().get(method.getParameters().size() - 1);
+      TypeMirror tm = ElementUtils.getType(varargsElement);
+      result.add(new ArrayCreation(tm, Collections.emptyList(), varargArgs));
+
+      return result;
+    }
+
+    return CollectionsPlume.mapList(JavaExpression::fromTree, argTrees);
+  }
+
+  /**
+   * Returns true if method is a varargs method or constructor and its varargs arguments are not
+   * passed in an array.
+   *
+   * @param method the method or constructor
+   * @param args the arguments at the call site
+   * @return true if method is a varargs method and its varargs arguments are not passed in an array
+   */
+  private static boolean isVarArgsInvocation(
+      ExecutableElement method, List<? extends ExpressionTree> args) {
+    if (!method.isVarArgs()) {
+      return false;
+    }
+    if (method.getParameters().size() != args.size()) {
+      return true;
+    }
+    TypeMirror lastArgType = TreeUtils.typeOf(args.get(args.size() - 1));
+    if (lastArgType.getKind() != TypeKind.ARRAY) {
+      return true;
+    }
+    List<? extends VariableElement> paramElts = method.getParameters();
+    VariableElement lastParamElt = paramElts.get(paramElts.size() - 1);
+    return TypesUtils.getArrayDepth(ElementUtils.getType(lastParamElt))
+        != TypesUtils.getArrayDepth(lastArgType);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java
new file mode 100644
index 0000000..091228e
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java
@@ -0,0 +1,119 @@
+package org.checkerframework.dataflow.expression;
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.PolyNull;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * This class calls {@link #convert(JavaExpression)} on each subexpression of the {@link
+ * JavaExpression} and returns a new {@code JavaExpression} built from the result of calling {@code
+ * convert} on each subexpression. (If an expression has no subexpression, then the expression
+ * itself is returned.)
+ *
+ * <p>This class makes it easy to implement a subclass that converts subexpressions of a {@link
+ * JavaExpression} based on which kind of {@code JavaExpression} the subexpression is. Subclasses
+ * should override the visit method of kinds of JavaExpressions to convert.
+ */
+public abstract class JavaExpressionConverter extends JavaExpressionVisitor<JavaExpression, Void> {
+
+  /**
+   * Converts {@code javaExpr} and returns the resulting {@code JavaExpression}.
+   *
+   * @param javaExpr the expression to convert
+   * @return the converted expression
+   */
+  public JavaExpression convert(JavaExpression javaExpr) {
+    return super.visit(javaExpr, null);
+  }
+
+  /**
+   * Converts all the expressions in {@code list} and returns the resulting list.
+   *
+   * @param list the list of expressions to convert
+   * @return the list of converted expressions
+   */
+  public List<@PolyNull JavaExpression> convert(List<@PolyNull JavaExpression> list) {
+    return CollectionsPlume.mapList(
+        (@PolyNull JavaExpression expression) -> {
+          // Can't use a ternary operator because of:
+          // https://github.com/typetools/checker-framework/issues/1170
+          if (expression == null) {
+            return null;
+          }
+          return convert(expression);
+        },
+        list);
+  }
+
+  @Override
+  protected JavaExpression visitArrayAccess(ArrayAccess arrayAccessExpr, Void unused) {
+    JavaExpression array = convert(arrayAccessExpr.getArray());
+    JavaExpression index = convert(arrayAccessExpr.getIndex());
+    return new ArrayAccess(arrayAccessExpr.type, array, index);
+  }
+
+  @Override
+  protected JavaExpression visitArrayCreation(ArrayCreation arrayCreationExpr, Void unused) {
+    List<@Nullable JavaExpression> dims = convert(arrayCreationExpr.getDimensions());
+    List<JavaExpression> inits = convert(arrayCreationExpr.getInitializers());
+    return new ArrayCreation(arrayCreationExpr.getType(), dims, inits);
+  }
+
+  @Override
+  protected JavaExpression visitBinaryOperation(BinaryOperation binaryOpExpr, Void unused) {
+    JavaExpression left = convert(binaryOpExpr.getLeft());
+    JavaExpression right = convert(binaryOpExpr.getRight());
+    return new BinaryOperation(
+        binaryOpExpr.getType(), binaryOpExpr.getOperationKind(), left, right);
+  }
+
+  @Override
+  protected JavaExpression visitClassName(ClassName classNameExpr, Void unused) {
+    return classNameExpr;
+  }
+
+  @Override
+  protected JavaExpression visitFieldAccess(FieldAccess fieldAccessExpr, Void unused) {
+    JavaExpression receiver = convert(fieldAccessExpr.getReceiver());
+    return new FieldAccess(receiver, fieldAccessExpr.getType(), fieldAccessExpr.getField());
+  }
+
+  @Override
+  protected JavaExpression visitFormalParameter(FormalParameter parameterExpr, Void unused) {
+    return parameterExpr;
+  }
+
+  @Override
+  protected JavaExpression visitLocalVariable(LocalVariable localVarExpr, Void unused) {
+    return localVarExpr;
+  }
+
+  @Override
+  protected JavaExpression visitMethodCall(MethodCall methodCallExpr, Void unused) {
+    JavaExpression receiver = convert(methodCallExpr.getReceiver());
+    List<JavaExpression> args = convert(methodCallExpr.getArguments());
+    return new MethodCall(methodCallExpr.getType(), methodCallExpr.getElement(), receiver, args);
+  }
+
+  @Override
+  protected JavaExpression visitThisReference(ThisReference thisExpr, Void unused) {
+    return thisExpr;
+  }
+
+  @Override
+  protected JavaExpression visitUnaryOperation(UnaryOperation unaryOpExpr, Void unused) {
+    JavaExpression operand = convert(unaryOpExpr.getOperand());
+    return new UnaryOperation(unaryOpExpr.getType(), unaryOpExpr.getOperationKind(), operand);
+  }
+
+  @Override
+  protected JavaExpression visitUnknown(Unknown unknownExpr, Void unused) {
+    return unknownExpr;
+  }
+
+  @Override
+  protected JavaExpression visitValueLiteral(ValueLiteral literalExpr, Void unused) {
+    return literalExpr;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionScanner.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionScanner.java
new file mode 100644
index 0000000..6702bc1
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionScanner.java
@@ -0,0 +1,106 @@
+package org.checkerframework.dataflow.expression;
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A simple scanner for {@link JavaExpression}.
+ *
+ * @param <P> the parameter passed to the scan methods
+ */
+public abstract class JavaExpressionScanner<P> extends JavaExpressionVisitor<Void, P> {
+
+  /**
+   * Scans the JavaExpression.
+   *
+   * @param javaExpression the expression to scan.
+   * @param p parameter to pass
+   */
+  public void scan(JavaExpression javaExpression, P p) {
+    visit(javaExpression, p);
+  }
+
+  /**
+   * Scans each JavaExpression in {@code expressions}.
+   *
+   * @param expressions a list of JavaExpressions to scan
+   * @param p pameter to pass
+   */
+  public void scan(List<? extends @Nullable JavaExpression> expressions, P p) {
+    for (JavaExpression expression : expressions) {
+      if (expression != null) {
+        visit(expression, p);
+      }
+    }
+  }
+
+  @Override
+  protected Void visitArrayAccess(ArrayAccess arrayAccessExpr, P p) {
+    visit(arrayAccessExpr.getArray(), p);
+    visit(arrayAccessExpr.getIndex(), p);
+    return null;
+  }
+
+  @Override
+  protected Void visitArrayCreation(ArrayCreation arrayCreationExpr, P p) {
+    scan(arrayCreationExpr.getDimensions(), p);
+    scan(arrayCreationExpr.getInitializers(), p);
+    return null;
+  }
+
+  @Override
+  protected Void visitBinaryOperation(BinaryOperation binaryOpExpr, P p) {
+    visit(binaryOpExpr.getLeft(), p);
+    visit(binaryOpExpr.getRight(), p);
+    return null;
+  }
+
+  @Override
+  protected Void visitClassName(ClassName classNameExpr, P p) {
+    return null;
+  }
+
+  @Override
+  protected Void visitFormalParameter(FormalParameter parameterExpr, P p) {
+    return null;
+  }
+
+  @Override
+  protected Void visitFieldAccess(FieldAccess fieldAccessExpr, P p) {
+    visit(fieldAccessExpr.getReceiver(), p);
+    return null;
+  }
+
+  @Override
+  protected Void visitLocalVariable(LocalVariable localVarExpr, P p) {
+    return null;
+  }
+
+  @Override
+  protected Void visitMethodCall(MethodCall methodCallExpr, P p) {
+    visit(methodCallExpr.getReceiver(), p);
+    scan(methodCallExpr.getArguments(), p);
+    return null;
+  }
+
+  @Override
+  protected Void visitThisReference(ThisReference thisExpr, P p) {
+    return null;
+  }
+
+  @Override
+  protected Void visitUnaryOperation(UnaryOperation unaryOpExpr, P p) {
+    visit(unaryOpExpr.getOperand(), p);
+    return null;
+  }
+
+  @Override
+  protected Void visitUnknown(Unknown unknownExpr, P p) {
+    return null;
+  }
+
+  @Override
+  protected Void visitValueLiteral(ValueLiteral literalExpr, P p) {
+    return null;
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionVisitor.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionVisitor.java
new file mode 100644
index 0000000..baee378
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionVisitor.java
@@ -0,0 +1,128 @@
+package org.checkerframework.dataflow.expression;
+
+/**
+ * A simple visitor for {@link JavaExpression}.
+ *
+ * @param <R> the return type of the visit methods
+ * @param <P> the parameter passed to the visit methods
+ */
+public abstract class JavaExpressionVisitor<R, P> {
+
+  /**
+   * Visits the given {@code javaExpr}.
+   *
+   * @param javaExpr the expression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the expression
+   */
+  public R visit(JavaExpression javaExpr, P p) {
+    return javaExpr.accept(this, p);
+  }
+
+  /**
+   * Visit an {@link ArrayAccess}.
+   *
+   * @param arrayAccessExpr the JavaExpression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the {@code arrayAccessExpr}
+   */
+  protected abstract R visitArrayAccess(ArrayAccess arrayAccessExpr, P p);
+  /**
+   * Visit an {@link ArrayCreation}.
+   *
+   * @param arrayCreationExpr the JavaExpression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the {@code arrayCreationExpr}
+   */
+  protected abstract R visitArrayCreation(ArrayCreation arrayCreationExpr, P p);
+
+  /**
+   * Visit a {@link BinaryOperation}.
+   *
+   * @param binaryOpExpr the JavaExpression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the {@code binaryOpExpr}
+   */
+  protected abstract R visitBinaryOperation(BinaryOperation binaryOpExpr, P p);
+
+  /**
+   * Visit a {@link ClassName}.
+   *
+   * @param classNameExpr the JavaExpression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the {@code classNameExpr}
+   */
+  protected abstract R visitClassName(ClassName classNameExpr, P p);
+
+  /**
+   * Visit a {@link FieldAccess}.
+   *
+   * @param fieldAccessExpr the JavaExpression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the {@code fieldAccessExpr}
+   */
+  protected abstract R visitFieldAccess(FieldAccess fieldAccessExpr, P p);
+
+  /**
+   * Visit a {@link FormalParameter}.
+   *
+   * @param parameterExpr the JavaExpression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the {@code parameterExpr}
+   */
+  protected abstract R visitFormalParameter(FormalParameter parameterExpr, P p);
+
+  /**
+   * Visit a {@link LocalVariable}.
+   *
+   * @param localVarExpr the JavaExpression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the {@code localVarExpr}
+   */
+  protected abstract R visitLocalVariable(LocalVariable localVarExpr, P p);
+
+  /**
+   * Visit a {@link MethodCall}.
+   *
+   * @param methodCallExpr the JavaExpression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the {@code methodCallExpr}
+   */
+  protected abstract R visitMethodCall(MethodCall methodCallExpr, P p);
+
+  /**
+   * Visit a {@link ThisReference}.
+   *
+   * @param thisExpr the JavaExpression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the {@code thisExpr}
+   */
+  protected abstract R visitThisReference(ThisReference thisExpr, P p);
+
+  /**
+   * Visit an {@link UnaryOperation}.
+   *
+   * @param unaryOpExpr the JavaExpression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the {@code unaryOpExpr}
+   */
+  protected abstract R visitUnaryOperation(UnaryOperation unaryOpExpr, P p);
+
+  /**
+   * Visit an {@link Unknown}.
+   *
+   * @param unknownExpr the JavaExpression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the {@code unknownExpr}
+   */
+  protected abstract R visitUnknown(Unknown unknownExpr, P p);
+
+  /**
+   * Visit a {@link ValueLiteral}.
+   *
+   * @param literalExpr the JavaExpression to visit
+   * @param p the parameter to pass to the visit method
+   * @return the result of visiting the {@code literalExpr}
+   */
+  protected abstract R visitValueLiteral(ValueLiteral literalExpr, P p);
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/LocalVariable.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/LocalVariable.java
new file mode 100644
index 0000000..1bedeb0
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/LocalVariable.java
@@ -0,0 +1,138 @@
+package org.checkerframework.dataflow.expression;
+
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import java.util.Objects;
+import javax.lang.model.element.Element;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TypeAnnotationUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * A local variable.
+ *
+ * <p>This class also represents a formal parameter expressed using its name. Class {@link
+ * FormalParameter} represents a formal parameter expressed using the "#2" notation.
+ */
+public class LocalVariable extends JavaExpression {
+  /** The element for this local variable. */
+  protected final Element element;
+
+  /**
+   * Creates a new LocalVariable.
+   *
+   * @param localVar a CFG local variable
+   */
+  public LocalVariable(LocalVariableNode localVar) {
+    super(localVar.getType());
+    this.element = localVar.getElement();
+  }
+
+  /**
+   * Creates a LocalVariable
+   *
+   * @param element the element for the local variable
+   */
+  public LocalVariable(Element element) {
+    super(ElementUtils.getType(element));
+    this.element = element;
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof LocalVariable)) {
+      return false;
+    }
+    LocalVariable other = (LocalVariable) obj;
+
+    return sameElement(element, other.element);
+  }
+
+  /**
+   * Returns true if the two elements are the same.
+   *
+   * @param element1 the first element to compare
+   * @param element2 the second element to compare
+   * @return true if the two elements are the same
+   */
+  protected static boolean sameElement(Element element1, Element element2) {
+    VarSymbol vs1 = (VarSymbol) element1;
+    VarSymbol vs2 = (VarSymbol) element2;
+    // The code below isn't just return vs1.equals(vs2) because an element might be
+    // different between subcheckers.  The owner of a lambda parameter is the enclosing
+    // method, so a local variable and a lambda parameter might have the same name and the
+    // same owner.  pos is used to differentiate this case.
+    return vs1.pos == vs2.pos
+        && vs1.name.contentEquals(vs2.name)
+        && vs1.owner.toString().equals(vs2.owner.toString());
+  }
+
+  /**
+   * Returns the element for this variable.
+   *
+   * @return the element for this variable
+   */
+  public Element getElement() {
+    return element;
+  }
+
+  @Override
+  public int hashCode() {
+    VarSymbol vs = (VarSymbol) element;
+    return Objects.hash(
+        vs.name.toString(),
+        TypeAnnotationUtils.unannotatedType(vs.type).toString(),
+        vs.owner.toString());
+  }
+
+  @Override
+  public String toString() {
+    return element.toString();
+  }
+
+  @Override
+  public String toStringDebug() {
+    return super.toStringDebug() + " [owner=" + ((VarSymbol) element).owner + "]";
+  }
+
+  @Override
+  public boolean containsOfClass(Class<? extends JavaExpression> clazz) {
+    return getClass() == clazz;
+  }
+
+  @Override
+  public boolean isDeterministic(AnnotationProvider provider) {
+    return true;
+  }
+
+  @Override
+  public boolean syntacticEquals(JavaExpression je) {
+    if (!(je instanceof LocalVariable)) {
+      return false;
+    }
+    LocalVariable other = (LocalVariable) je;
+    return this.equals(other);
+  }
+
+  @Override
+  public boolean containsSyntacticEqualJavaExpression(JavaExpression other) {
+    return syntacticEquals(other);
+  }
+
+  @Override
+  public boolean isUnassignableByOtherCode() {
+    return true;
+  }
+
+  @Override
+  public boolean isUnmodifiableByOtherCode() {
+    return TypesUtils.isImmutableTypeInJdk(((VarSymbol) element).type);
+  }
+
+  @Override
+  public <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p) {
+    return visitor.visitLocalVariable(this, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/MethodCall.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/MethodCall.java
new file mode 100644
index 0000000..b16544e
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/MethodCall.java
@@ -0,0 +1,185 @@
+package org.checkerframework.dataflow.expression;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringJoiner;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.util.PurityUtils;
+import org.checkerframework.javacutil.AnnotationProvider;
+
+/** A call to a @Deterministic method. */
+public class MethodCall extends JavaExpression {
+
+  /** The method being called. */
+  protected final ExecutableElement method;
+  /** The receiver argument. */
+  protected final JavaExpression receiver;
+  /** The arguments. */
+  protected final List<JavaExpression> arguments;
+
+  /**
+   * Creates a new MethodCall.
+   *
+   * @param type the type of the method call
+   * @param method the method being called
+   * @param receiver the receiver argument
+   * @param arguments the arguments
+   */
+  public MethodCall(
+      TypeMirror type,
+      ExecutableElement method,
+      JavaExpression receiver,
+      List<JavaExpression> arguments) {
+    super(type);
+    this.receiver = receiver;
+    this.arguments = arguments;
+    this.method = method;
+  }
+
+  /**
+   * Returns the ExecutableElement for the method call.
+   *
+   * @return the ExecutableElement for the method call
+   */
+  public ExecutableElement getElement() {
+    return method;
+  }
+
+  /**
+   * Returns the method call receiver (for inspection only - do not modify).
+   *
+   * @return the method call receiver (for inspection only - do not modify)
+   */
+  public JavaExpression getReceiver() {
+    return receiver;
+  }
+
+  /**
+   * Returns the method call arguments (for inspection only - do not modify any of the arguments).
+   *
+   * @return the method call arguments (for inspection only - do not modify any of the arguments)
+   */
+  public List<JavaExpression> getArguments() {
+    return Collections.unmodifiableList(arguments);
+  }
+
+  @Override
+  public boolean containsOfClass(Class<? extends JavaExpression> clazz) {
+    if (getClass() == clazz) {
+      return true;
+    }
+    if (receiver.containsOfClass(clazz)) {
+      return true;
+    }
+    for (JavaExpression p : arguments) {
+      if (p.containsOfClass(clazz)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public boolean isDeterministic(AnnotationProvider provider) {
+    return PurityUtils.isDeterministic(provider, method)
+        && listIsDeterministic(arguments, provider);
+  }
+
+  @Override
+  public boolean isUnassignableByOtherCode() {
+    // There is no need to check that the method is deterministic, because a MethodCall is
+    // only created for deterministic methods.
+    return receiver.isUnmodifiableByOtherCode()
+        && arguments.stream().allMatch(JavaExpression::isUnmodifiableByOtherCode);
+  }
+
+  @Override
+  public boolean isUnmodifiableByOtherCode() {
+    return isUnassignableByOtherCode();
+  }
+
+  @Override
+  public boolean syntacticEquals(JavaExpression je) {
+    if (!(je instanceof MethodCall)) {
+      return false;
+    }
+    MethodCall other = (MethodCall) je;
+    return method.equals(other.method)
+        && this.receiver.syntacticEquals(other.receiver)
+        && JavaExpression.syntacticEqualsList(this.arguments, other.arguments);
+  }
+
+  @Override
+  public boolean containsSyntacticEqualJavaExpression(JavaExpression other) {
+    return syntacticEquals(other)
+        || receiver.containsSyntacticEqualJavaExpression(other)
+        || JavaExpression.listContainsSyntacticEqualJavaExpression(arguments, other);
+  }
+
+  @Override
+  public boolean containsModifiableAliasOf(Store<?> store, JavaExpression other) {
+    if (receiver.containsModifiableAliasOf(store, other)) {
+      return true;
+    }
+    for (JavaExpression p : arguments) {
+      if (p.containsModifiableAliasOf(store, other)) {
+        return true;
+      }
+    }
+    return false; // the method call itself is not modifiable
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof MethodCall)) {
+      return false;
+    }
+    if (method.getKind() == ElementKind.CONSTRUCTOR) {
+      return false;
+    }
+    MethodCall other = (MethodCall) obj;
+    return method.equals(other.method)
+        && receiver.equals(other.receiver)
+        && arguments.equals(other.arguments);
+  }
+
+  @Override
+  public int hashCode() {
+    if (method.getKind() == ElementKind.CONSTRUCTOR) {
+      return super.hashCode();
+    }
+    return Objects.hash(method, receiver, arguments);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder preParen = new StringBuilder();
+    if (receiver instanceof ClassName) {
+      preParen.append(receiver.getType());
+    } else {
+      preParen.append(receiver);
+    }
+    preParen.append(".");
+    String methodName = method.getSimpleName().toString();
+    preParen.append(methodName);
+    preParen.append("(");
+    StringJoiner result = new StringJoiner(", ", preParen, ")");
+    for (JavaExpression argument : arguments) {
+      result.add(argument.toString());
+    }
+    return result.toString();
+  }
+
+  @Override
+  public <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p) {
+    return visitor.visitMethodCall(this, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ThisReference.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ThisReference.java
new file mode 100644
index 0000000..7b80b30
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ThisReference.java
@@ -0,0 +1,68 @@
+package org.checkerframework.dataflow.expression;
+
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.TypesUtils;
+
+public class ThisReference extends JavaExpression {
+  public ThisReference(TypeMirror type) {
+    super(type);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    return obj instanceof ThisReference;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  @Override
+  public String toString() {
+    return "this";
+  }
+
+  @Override
+  public boolean containsOfClass(Class<? extends JavaExpression> clazz) {
+    return getClass() == clazz;
+  }
+
+  @Override
+  public boolean isDeterministic(AnnotationProvider provider) {
+    return true;
+  }
+
+  @Override
+  public boolean isUnassignableByOtherCode() {
+    return true;
+  }
+
+  @Override
+  public boolean isUnmodifiableByOtherCode() {
+    return TypesUtils.isImmutableTypeInJdk(type);
+  }
+
+  @Override
+  public boolean syntacticEquals(JavaExpression je) {
+    return je instanceof ThisReference;
+  }
+
+  @Override
+  public boolean containsSyntacticEqualJavaExpression(JavaExpression other) {
+    return this.syntacticEquals(other);
+  }
+
+  @Override
+  public boolean containsModifiableAliasOf(Store<?> store, JavaExpression other) {
+    return false; // 'this' is not modifiable
+  }
+
+  @Override
+  public <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p) {
+    return visitor.visitThisReference(this, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/UnaryOperation.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/UnaryOperation.java
new file mode 100644
index 0000000..6499ed6
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/UnaryOperation.java
@@ -0,0 +1,145 @@
+package org.checkerframework.dataflow.expression;
+
+import com.sun.source.tree.Tree;
+import java.util.Objects;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.cfg.node.UnaryOperationNode;
+import org.checkerframework.javacutil.AnnotationProvider;
+
+/** JavaExpression for unary operations. */
+public class UnaryOperation extends JavaExpression {
+
+  /** The unary operation kind. */
+  protected final Tree.Kind operationKind;
+  /** The operand. */
+  protected final JavaExpression operand;
+
+  /**
+   * Create a unary operation.
+   *
+   * @param type the type of the result
+   * @param operationKind the operator
+   * @param operand the operand
+   */
+  public UnaryOperation(TypeMirror type, Tree.Kind operationKind, JavaExpression operand) {
+    super(operand.type);
+    this.operationKind = operationKind;
+    this.operand = operand;
+  }
+
+  /**
+   * Create a unary operation.
+   *
+   * @param node the unary operation node
+   * @param operand the operand
+   */
+  public UnaryOperation(UnaryOperationNode node, JavaExpression operand) {
+    this(node.getType(), node.getTree().getKind(), operand);
+  }
+
+  /**
+   * Returns the operator of this unary operation.
+   *
+   * @return the unary operation kind
+   */
+  public Tree.Kind getOperationKind() {
+    return operationKind;
+  }
+
+  /**
+   * Returns the operand of this unary operation.
+   *
+   * @return the operand
+   */
+  public JavaExpression getOperand() {
+    return operand;
+  }
+
+  @Override
+  public boolean containsOfClass(Class<? extends JavaExpression> clazz) {
+    if (getClass() == clazz) {
+      return true;
+    }
+    return operand.containsOfClass(clazz);
+  }
+
+  @Override
+  public boolean isDeterministic(AnnotationProvider provider) {
+    return operand.isDeterministic(provider);
+  }
+
+  @Override
+  public boolean isUnassignableByOtherCode() {
+    return operand.isUnassignableByOtherCode();
+  }
+
+  @Override
+  public boolean isUnmodifiableByOtherCode() {
+    return operand.isUnmodifiableByOtherCode();
+  }
+
+  @Override
+  public boolean syntacticEquals(JavaExpression je) {
+    if (!(je instanceof UnaryOperation)) {
+      return false;
+    }
+    UnaryOperation other = (UnaryOperation) je;
+    return operationKind == other.getOperationKind() && operand.syntacticEquals(other.operand);
+  }
+
+  @Override
+  public boolean containsSyntacticEqualJavaExpression(JavaExpression other) {
+    return this.syntacticEquals(other) || operand.containsSyntacticEqualJavaExpression(other);
+  }
+
+  @Override
+  public boolean containsModifiableAliasOf(Store<?> store, JavaExpression other) {
+    return operand.containsModifiableAliasOf(store, other);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(operationKind, operand);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object other) {
+    if (!(other instanceof UnaryOperation)) {
+      return false;
+    }
+    UnaryOperation unOp = (UnaryOperation) other;
+    return operationKind == unOp.getOperationKind() && operand.equals(unOp.operand);
+  }
+
+  @Override
+  public String toString() {
+    String operandString = operand.toString();
+    switch (operationKind) {
+      case BITWISE_COMPLEMENT:
+        return "~" + operandString;
+      case LOGICAL_COMPLEMENT:
+        return "!" + operandString;
+      case POSTFIX_DECREMENT:
+        return operandString + "--";
+      case POSTFIX_INCREMENT:
+        return operandString + "++";
+      case PREFIX_DECREMENT:
+        return "--" + operandString;
+      case PREFIX_INCREMENT:
+        return "++" + operandString;
+      case UNARY_MINUS:
+        return "-" + operandString;
+      case UNARY_PLUS:
+        return "+" + operandString;
+      default:
+        throw new Error("Unrecognized unary operation kind " + operationKind);
+    }
+  }
+
+  @Override
+  public <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p) {
+    return visitor.visitUnaryOperation(this, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/Unknown.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/Unknown.java
new file mode 100644
index 0000000..a89c80a
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/Unknown.java
@@ -0,0 +1,112 @@
+package org.checkerframework.dataflow.expression;
+
+import com.sun.source.tree.Tree;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.interning.qual.UsesObjectEquals;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.TreeUtils;
+
+/** Stands for any expression that the Dataflow Framework lacks explicit support for. */
+@UsesObjectEquals
+public class Unknown extends JavaExpression {
+
+  /** String representation of the expression that has no corresponding {@code JavaExpression}. */
+  private final String originalExpression;
+  /**
+   * Create a new Unknown JavaExpression.
+   *
+   * @param type the Java type of this
+   */
+  public Unknown(TypeMirror type) {
+    this(type, "?");
+  }
+
+  /**
+   * Create a new Unknown JavaExpression.
+   *
+   * @param type the Java type of this
+   * @param originalExpression String representation of the expression that has no corresponding
+   *     {@code JavaExpression}
+   */
+  public Unknown(TypeMirror type, String originalExpression) {
+    super(type);
+    this.originalExpression = originalExpression;
+  }
+
+  /**
+   * Create a new Unknown JavaExpression.
+   *
+   * @param tree a tree that does not have a corresponding {@code JavaExpression}
+   */
+  public Unknown(Tree tree) {
+    this(TreeUtils.typeOf(tree), TreeUtils.toStringTruncated(tree, 40));
+  }
+
+  /**
+   * Create a new Unknown JavaExpression.
+   *
+   * @param node a node that does not have a corresponding {@code JavaExpression}
+   */
+  public Unknown(Node node) {
+    this(node.getType(), node.toString());
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    return obj == this;
+  }
+
+  // Overridden to avoid an error "overrides equals, but does not override hashCode"
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+
+  @Override
+  public String toString() {
+    return originalExpression;
+  }
+
+  @Override
+  public boolean containsOfClass(Class<? extends JavaExpression> clazz) {
+    return getClass() == clazz;
+  }
+
+  @Override
+  public boolean isDeterministic(AnnotationProvider provider) {
+    return false;
+  }
+
+  @Override
+  public boolean isUnassignableByOtherCode() {
+    return false;
+  }
+
+  @Override
+  public boolean isUnmodifiableByOtherCode() {
+    return false;
+  }
+
+  @Override
+  public boolean syntacticEquals(JavaExpression je) {
+    return this == je;
+  }
+
+  @Override
+  public boolean containsSyntacticEqualJavaExpression(JavaExpression other) {
+    return this.syntacticEquals(other);
+  }
+
+  @Override
+  public boolean containsModifiableAliasOf(Store<?> store, JavaExpression other) {
+    return true;
+  }
+
+  @Override
+  public <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p) {
+    return visitor.visitUnknown(this, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java
new file mode 100644
index 0000000..11a83ba
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java
@@ -0,0 +1,171 @@
+package org.checkerframework.dataflow.expression;
+
+import java.math.BigInteger;
+import java.util.Objects;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.cfg.node.ValueLiteralNode;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** JavaExpression for literals. */
+public class ValueLiteral extends JavaExpression {
+
+  /** The value of the literal. */
+  protected final @Nullable Object value;
+
+  /** The negative of Long.MIN_VALUE, which does not fit in a long. */
+  private final BigInteger NEGATIVE_LONG_MIN_VALUE = new BigInteger("9223372036854775808");
+
+  /**
+   * Creates a ValueLiteral from the node with the given type.
+   *
+   * @param type type of the literal
+   * @param node the literal represents by this {@link
+   *     org.checkerframework.dataflow.expression.ValueLiteral}
+   */
+  public ValueLiteral(TypeMirror type, ValueLiteralNode node) {
+    super(type);
+    value = node.getValue();
+  }
+
+  /**
+   * Creates a ValueLiteral where the value is {@code value} that has the given type.
+   *
+   * @param type type of the literal
+   * @param value the literal value
+   */
+  public ValueLiteral(TypeMirror type, Object value) {
+    super(type);
+    this.value = value;
+  }
+
+  /**
+   * Returns the negation of this literal. Throws an exception if negation is not possible.
+   *
+   * @return the negation of this literal
+   */
+  public ValueLiteral negate() {
+    if (TypesUtils.isIntegralPrimitive(type)) {
+      if (value == null) {
+        throw new Error("null value of integral type " + type);
+      }
+      return new ValueLiteral(type, negateBoxedPrimitive(value));
+    }
+    throw new Error(String.format("cannot negate: %s type=%s", this, type));
+  }
+
+  /**
+   * Negate a boxed primitive.
+   *
+   * @param o a boxed primitive
+   * @return a boxed primitive that is the negation of the argument
+   */
+  private Object negateBoxedPrimitive(Object o) {
+    if (value instanceof Byte) {
+      return (byte) -(Byte) value;
+    }
+    if (value instanceof Short) {
+      return (short) -(Short) value;
+    }
+    if (value instanceof Integer) {
+      return -(Integer) value;
+    }
+    if (value instanceof Long) {
+      return -(Long) value;
+    }
+    if (value instanceof Float) {
+      return -(Float) value;
+    }
+    if (value instanceof Double) {
+      return -(Double) value;
+    }
+    if (value instanceof BigInteger) {
+      assert value.equals(NEGATIVE_LONG_MIN_VALUE);
+      return Long.MIN_VALUE;
+    }
+    throw new Error("Cannot be negated: " + o + " " + o.getClass());
+  }
+
+  /**
+   * Returns the value of this literal.
+   *
+   * @return the value of this literal
+   */
+  public @Nullable Object getValue() {
+    return value;
+  }
+
+  @Override
+  public boolean containsOfClass(Class<? extends JavaExpression> clazz) {
+    return getClass() == clazz;
+  }
+
+  @Override
+  public boolean isDeterministic(AnnotationProvider provider) {
+    return true;
+  }
+
+  @Override
+  public boolean isUnassignableByOtherCode() {
+    return true;
+  }
+
+  @Override
+  public boolean isUnmodifiableByOtherCode() {
+    return true;
+  }
+
+  @Override
+  public boolean syntacticEquals(JavaExpression je) {
+    return this.equals(je);
+  }
+
+  @Override
+  public boolean containsSyntacticEqualJavaExpression(JavaExpression other) {
+    return this.syntacticEquals(other);
+  }
+
+  @Override
+  public boolean containsModifiableAliasOf(Store<?> store, JavaExpression other) {
+    return false; // not modifiable
+  }
+
+  /// java.lang.Object methods
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof ValueLiteral)) {
+      return false;
+    }
+    ValueLiteral other = (ValueLiteral) obj;
+    // TODO:  Can this string comparison be cleaned up?
+    // Cannot use Types.isSameType(type, other.type) because we don't have a Types object.
+    return type.toString().equals(other.type.toString()) && Objects.equals(value, other.value);
+  }
+
+  @Override
+  public String toString() {
+    if (TypesUtils.isString(type)) {
+      return "\"" + value + "\"";
+    } else if (type.getKind() == TypeKind.LONG) {
+      assert value != null : "@AssumeAssertion(nullness): invariant";
+      return value.toString() + "L";
+    } else if (type.getKind() == TypeKind.CHAR) {
+      return "\'" + value + "\'";
+    }
+    return value == null ? "null" : value.toString();
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(value, type.toString());
+  }
+
+  @Override
+  public <R, P> R accept(JavaExpressionVisitor<R, P> visitor, P p) {
+    return visitor.visitValueLiteral(this, p);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ViewpointAdaptJavaExpression.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ViewpointAdaptJavaExpression.java
new file mode 100644
index 0000000..70aeef3
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ViewpointAdaptJavaExpression.java
@@ -0,0 +1,102 @@
+package org.checkerframework.dataflow.expression;
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * This class has methods to viewpoint-adapt {@link JavaExpression} by replacing {@link
+ * ThisReference} and {@link FormalParameter} expressions with the given {@link JavaExpression}s.
+ */
+public class ViewpointAdaptJavaExpression extends JavaExpressionConverter {
+
+  // Public static methods
+
+  /**
+   * Replace {@link FormalParameter}s by {@code args} in {@code javaExpr}. ({@link ThisReference}s
+   * are not converted.)
+   *
+   * @param javaExpr the expression to viewpoint-adapt
+   * @param args the expressions that replace {@link FormalParameter}s; if null, {@link
+   *     FormalParameter}s are not replaced
+   * @return the viewpoint-adapted expression
+   */
+  public static JavaExpression viewpointAdapt(
+      JavaExpression javaExpr, @Nullable List<JavaExpression> args) {
+    return viewpointAdapt(javaExpr, null, args);
+  }
+  /**
+   * Replace {@link ThisReference} with {@code thisReference} in {@code javaExpr}. ({@link
+   * FormalParameter} are not replaced.
+   *
+   * @param javaExpr the expression to viewpoint-adapt
+   * @param thisReference the expression that replaces occurrences of {@link ThisReference}; if
+   *     null, {@link ThisReference}s are not replaced
+   * @return the viewpoint-adapted expression
+   */
+  public static JavaExpression viewpointAdapt(
+      JavaExpression javaExpr, @Nullable JavaExpression thisReference) {
+    return viewpointAdapt(javaExpr, thisReference, null);
+  }
+
+  /**
+   * Replace {@link FormalParameter}s with {@code args} and {@link ThisReference} with {@code
+   * thisReference} in {@code javaExpr}.
+   *
+   * @param javaExpr the expression to viewpoint-adapt
+   * @param thisReference the expression that replaces occurrences of {@link ThisReference}; if
+   *     null, {@link ThisReference}s are not replaced
+   * @param args the expressions that replaces {@link FormalParameter}s; if null, {@link
+   *     FormalParameter}s are not replaced
+   * @return the viewpoint-adapted expression
+   */
+  public static JavaExpression viewpointAdapt(
+      JavaExpression javaExpr,
+      @Nullable JavaExpression thisReference,
+      @Nullable List<JavaExpression> args) {
+    return new ViewpointAdaptJavaExpression(thisReference, args).convert(javaExpr);
+  }
+
+  // Fields
+
+  /** List of arguments used to replace occurrences {@link FormalParameter}s. */
+  private final @Nullable List<JavaExpression> args;
+
+  /** The expression to replace occurrences of {@link ThisReference}s. */
+  private final @Nullable JavaExpression thisReference;
+
+  // Instance methods
+
+  /**
+   * Creates a {@link JavaExpressionConverter} that viewpoint-adapts using the given {@code
+   * thisReference} and {@code args}.
+   *
+   * @param thisReference the expression that replaces occurrences of {@link ThisReference}; {@code
+   *     null} means don't replace
+   * @param args list of arguments that replaces occurrences {@link FormalParameter}s; {@code null}
+   *     means don't replace
+   */
+  private ViewpointAdaptJavaExpression(
+      @Nullable JavaExpression thisReference, @Nullable List<JavaExpression> args) {
+    this.args = args;
+    this.thisReference = thisReference;
+  }
+
+  @Override
+  protected JavaExpression visitThisReference(ThisReference thisExpr, Void unused) {
+    if (thisReference != null) {
+      return thisReference;
+    }
+    return super.visitThisReference(thisExpr, unused);
+  }
+
+  @Override
+  protected JavaExpression visitFormalParameter(FormalParameter parameterExpr, Void unused) {
+    if (args != null) {
+      int index = parameterExpr.getIndex() - 1;
+      if (index < args.size()) {
+        return args.get(index);
+      }
+    }
+    return super.visitFormalParameter(parameterExpr, unused);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarStore.java b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarStore.java
new file mode 100644
index 0000000..0abe452
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarStore.java
@@ -0,0 +1,146 @@
+package org.checkerframework.dataflow.livevariable;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.cfg.node.BinaryOperationNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.InstanceOfNode;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
+import org.checkerframework.dataflow.cfg.node.TypeCastNode;
+import org.checkerframework.dataflow.cfg.node.UnaryOperationNode;
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.javacutil.BugInCF;
+
+/** A live variable store contains a set of live variables represented by nodes. */
+public class LiveVarStore implements Store<LiveVarStore> {
+
+  /** A set of live variable abstract values. */
+  private final Set<LiveVarValue> liveVarValueSet;
+
+  /** Create a new LiveVarStore. */
+  public LiveVarStore() {
+    liveVarValueSet = new LinkedHashSet<>();
+  }
+
+  /**
+   * Create a new LiveVarStore.
+   *
+   * @param liveVarValueSet a set of live variable abstract values
+   */
+  public LiveVarStore(Set<LiveVarValue> liveVarValueSet) {
+    this.liveVarValueSet = liveVarValueSet;
+  }
+
+  /**
+   * Add the information of a live variable into the live variable set.
+   *
+   * @param variable a live variable
+   */
+  public void putLiveVar(LiveVarValue variable) {
+    liveVarValueSet.add(variable);
+  }
+
+  /**
+   * Remove the information of a live variable from the live variable set.
+   *
+   * @param variable a live variable
+   */
+  public void killLiveVar(LiveVarValue variable) {
+    liveVarValueSet.remove(variable);
+  }
+
+  /**
+   * Add the information of live variables in an expression to the live variable set.
+   *
+   * @param expression a node
+   */
+  public void addUseInExpression(Node expression) {
+    // TODO Do we need a AbstractNodeScanner to do the following job?
+    if (expression instanceof LocalVariableNode || expression instanceof FieldAccessNode) {
+      LiveVarValue liveVarValue = new LiveVarValue(expression);
+      putLiveVar(liveVarValue);
+    } else if (expression instanceof UnaryOperationNode) {
+      UnaryOperationNode unaryNode = (UnaryOperationNode) expression;
+      addUseInExpression(unaryNode.getOperand());
+    } else if (expression instanceof TernaryExpressionNode) {
+      TernaryExpressionNode ternaryNode = (TernaryExpressionNode) expression;
+      addUseInExpression(ternaryNode.getConditionOperand());
+      addUseInExpression(ternaryNode.getThenOperand());
+      addUseInExpression(ternaryNode.getElseOperand());
+    } else if (expression instanceof TypeCastNode) {
+      TypeCastNode typeCastNode = (TypeCastNode) expression;
+      addUseInExpression(typeCastNode.getOperand());
+    } else if (expression instanceof InstanceOfNode) {
+      InstanceOfNode instanceOfNode = (InstanceOfNode) expression;
+      addUseInExpression(instanceOfNode.getOperand());
+    } else if (expression instanceof BinaryOperationNode) {
+      BinaryOperationNode binaryNode = (BinaryOperationNode) expression;
+      addUseInExpression(binaryNode.getLeftOperand());
+      addUseInExpression(binaryNode.getRightOperand());
+    }
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof LiveVarStore)) {
+      return false;
+    }
+    LiveVarStore other = (LiveVarStore) obj;
+    return other.liveVarValueSet.equals(this.liveVarValueSet);
+  }
+
+  @Override
+  public int hashCode() {
+    return this.liveVarValueSet.hashCode();
+  }
+
+  @Override
+  public LiveVarStore copy() {
+    return new LiveVarStore(new HashSet<>(liveVarValueSet));
+  }
+
+  @Override
+  public LiveVarStore leastUpperBound(LiveVarStore other) {
+    Set<LiveVarValue> liveVarValueSetLub =
+        new HashSet<>(this.liveVarValueSet.size() + other.liveVarValueSet.size());
+    liveVarValueSetLub.addAll(this.liveVarValueSet);
+    liveVarValueSetLub.addAll(other.liveVarValueSet);
+    return new LiveVarStore(liveVarValueSetLub);
+  }
+
+  /** It should not be called since it is not used by the backward analysis. */
+  @Override
+  public LiveVarStore widenedUpperBound(LiveVarStore previous) {
+    throw new BugInCF("wub of LiveVarStore get called!");
+  }
+
+  @Override
+  public boolean canAlias(JavaExpression a, JavaExpression b) {
+    return true;
+  }
+
+  @Override
+  public String visualize(CFGVisualizer<?, LiveVarStore, ?> viz) {
+    String key = "live variables";
+    if (liveVarValueSet.isEmpty()) {
+      return viz.visualizeStoreKeyVal(key, "none");
+    }
+    StringJoiner sjStoreVal = new StringJoiner(", ");
+    for (LiveVarValue liveVarValue : liveVarValueSet) {
+      sjStoreVal.add(liveVarValue.toString());
+    }
+    return viz.visualizeStoreKeyVal(key, sjStoreVal.toString());
+  }
+
+  @Override
+  public String toString() {
+    return liveVarValueSet.toString();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarTransfer.java b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarTransfer.java
new file mode 100644
index 0000000..c5692e3
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarTransfer.java
@@ -0,0 +1,96 @@
+package org.checkerframework.dataflow.livevariable;
+
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.BackwardTransferFunction;
+import org.checkerframework.dataflow.analysis.RegularTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.node.AbstractNodeVisitor;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+import org.checkerframework.dataflow.cfg.node.StringConcatenateAssignmentNode;
+
+/** A live variable transfer function. */
+public class LiveVarTransfer
+    extends AbstractNodeVisitor<
+        TransferResult<LiveVarValue, LiveVarStore>, TransferInput<LiveVarValue, LiveVarStore>>
+    implements BackwardTransferFunction<LiveVarValue, LiveVarStore> {
+
+  @Override
+  public LiveVarStore initialNormalExitStore(
+      UnderlyingAST underlyingAST, @Nullable List<ReturnNode> returnNodes) {
+    return new LiveVarStore();
+  }
+
+  @Override
+  public LiveVarStore initialExceptionalExitStore(UnderlyingAST underlyingAST) {
+    return new LiveVarStore();
+  }
+
+  @Override
+  public RegularTransferResult<LiveVarValue, LiveVarStore> visitNode(
+      Node n, TransferInput<LiveVarValue, LiveVarStore> p) {
+    return new RegularTransferResult<>(null, p.getRegularStore());
+  }
+
+  @Override
+  public RegularTransferResult<LiveVarValue, LiveVarStore> visitAssignment(
+      AssignmentNode n, TransferInput<LiveVarValue, LiveVarStore> p) {
+    RegularTransferResult<LiveVarValue, LiveVarStore> transferResult =
+        (RegularTransferResult<LiveVarValue, LiveVarStore>) super.visitAssignment(n, p);
+    processLiveVarInAssignment(n.getTarget(), n.getExpression(), transferResult.getRegularStore());
+    return transferResult;
+  }
+
+  @Override
+  public RegularTransferResult<LiveVarValue, LiveVarStore> visitStringConcatenateAssignment(
+      StringConcatenateAssignmentNode n, TransferInput<LiveVarValue, LiveVarStore> p) {
+    RegularTransferResult<LiveVarValue, LiveVarStore> transferResult =
+        (RegularTransferResult<LiveVarValue, LiveVarStore>)
+            super.visitStringConcatenateAssignment(n, p);
+    processLiveVarInAssignment(
+        n.getLeftOperand(), n.getRightOperand(), transferResult.getRegularStore());
+    return transferResult;
+  }
+
+  @Override
+  public RegularTransferResult<LiveVarValue, LiveVarStore> visitMethodInvocation(
+      MethodInvocationNode n, TransferInput<LiveVarValue, LiveVarStore> p) {
+    RegularTransferResult<LiveVarValue, LiveVarStore> transferResult =
+        (RegularTransferResult<LiveVarValue, LiveVarStore>) super.visitMethodInvocation(n, p);
+    LiveVarStore store = transferResult.getRegularStore();
+    for (Node arg : n.getArguments()) {
+      store.addUseInExpression(arg);
+    }
+    return transferResult;
+  }
+
+  @Override
+  public RegularTransferResult<LiveVarValue, LiveVarStore> visitObjectCreation(
+      ObjectCreationNode n, TransferInput<LiveVarValue, LiveVarStore> p) {
+    RegularTransferResult<LiveVarValue, LiveVarStore> transferResult =
+        (RegularTransferResult<LiveVarValue, LiveVarStore>) super.visitObjectCreation(n, p);
+    LiveVarStore store = transferResult.getRegularStore();
+    for (Node arg : n.getArguments()) {
+      store.addUseInExpression(arg);
+    }
+    return transferResult;
+  }
+
+  /**
+   * Update the information of live variables from an assignment statement.
+   *
+   * @param variable the variable that should be killed
+   * @param expression the expression in which the variables should be added
+   * @param store the live variable store
+   */
+  private void processLiveVarInAssignment(Node variable, Node expression, LiveVarStore store) {
+    store.killLiveVar(new LiveVarValue(variable));
+    store.addUseInExpression(expression);
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarValue.java b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarValue.java
new file mode 100644
index 0000000..1b50e73
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarValue.java
@@ -0,0 +1,50 @@
+package org.checkerframework.dataflow.livevariable;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.AbstractValue;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.javacutil.BugInCF;
+
+/** A live variable (which is represented by a node) wrapper turning node into abstract value. */
+public class LiveVarValue implements AbstractValue<LiveVarValue> {
+
+  /**
+   * A live variable is represented by a node, which can be a {@link
+   * org.checkerframework.dataflow.cfg.node.LocalVariableNode} or {@link
+   * org.checkerframework.dataflow.cfg.node.FieldAccessNode}.
+   */
+  protected final Node liveVariable;
+
+  @Override
+  public LiveVarValue leastUpperBound(LiveVarValue other) {
+    throw new BugInCF("lub of LiveVar get called!");
+  }
+
+  /**
+   * Create a new live variable.
+   *
+   * @param n a node
+   */
+  public LiveVarValue(Node n) {
+    this.liveVariable = n;
+  }
+
+  @Override
+  public int hashCode() {
+    return this.liveVariable.hashCode();
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof LiveVarValue)) {
+      return false;
+    }
+    LiveVarValue other = (LiveVarValue) obj;
+    return this.liveVariable.equals(other.liveVariable);
+  }
+
+  @Override
+  public String toString() {
+    return this.liveVariable.toString();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/package-info.java b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/package-info.java
new file mode 100644
index 0000000..238ae8e
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/package-info.java
@@ -0,0 +1,9 @@
+/**
+ * Classes using for live variable analysis. Live variable analysis is a backward analysis to
+ * calculate the variables that are live at each point in the program. To run live variable
+ * analysis, see {@link org.checkerframework.dataflow.cfg.playground.LiveVariablePlayground}.
+ *
+ * @see <a
+ *     href="https://en.wikipedia.org/wiki/Live_variable_analysis">https://en.wikipedia.org/wiki/Live_variable_analysis</a>
+ */
+package org.checkerframework.dataflow.livevariable;
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/AbstractMostlySingleton.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/AbstractMostlySingleton.java
new file mode 100644
index 0000000..4d400a9
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/AbstractMostlySingleton.java
@@ -0,0 +1,168 @@
+package org.checkerframework.dataflow.util;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.PolyNull;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Base class for arbitrary-size sets that very efficient (more efficient than HashSet) for 0 and 1
+ * elements.
+ *
+ * <p>Does not support storing {@code null}.
+ *
+ * <p>This class exists because it has multiple subclasses (currently {@link MostlySingleton} and
+ * {@link IdentityMostlySingleton}).
+ */
+public abstract class AbstractMostlySingleton<T extends Object> implements Set<T> {
+
+  /** The possible states of this set. */
+  public enum State {
+    /** An empty set. */
+    EMPTY,
+    /** A singleton set. */
+    SINGLETON,
+    /** A set of arbitrary size. */
+    ANY
+  }
+
+  /** The current state. */
+  protected State state;
+  /** The current value, non-null when the state is SINGLETON. */
+  protected @Nullable T value;
+  /** The wrapped set, non-null when the state is ANY. */
+  protected @Nullable Set<T> set;
+
+  /** Create an AbstractMostlySingleton. */
+  protected AbstractMostlySingleton(State s) {
+    this.state = s;
+    this.value = null;
+  }
+
+  /** Create an AbstractMostlySingleton. */
+  protected AbstractMostlySingleton(State s, T v) {
+    this.state = s;
+    this.value = v;
+  }
+
+  @Override
+  public int size() {
+    switch (state) {
+      case EMPTY:
+        return 0;
+      case SINGLETON:
+        return 1;
+      case ANY:
+        assert set != null : "@AssumeAssertion(nullness): set initialized before";
+        return set.size();
+      default:
+        throw new BugInCF("Unhandled state " + state);
+    }
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return size() == 0;
+  }
+
+  @Override
+  public Iterator<T> iterator() {
+    switch (state) {
+      case EMPTY:
+        return Collections.emptyIterator();
+      case SINGLETON:
+        return new Iterator<T>() {
+          private boolean hasNext = true;
+
+          @Override
+          public boolean hasNext() {
+            return hasNext;
+          }
+
+          @Override
+          public T next() {
+            if (hasNext) {
+              hasNext = false;
+              assert value != null : "@AssumeAssertion(nullness): previous add is non-null";
+              return value;
+            }
+            throw new NoSuchElementException();
+          }
+
+          @Override
+          public void remove() {
+            state = State.EMPTY;
+            value = null;
+          }
+        };
+      case ANY:
+        assert set != null : "@AssumeAssertion(nullness): set initialized before";
+        return set.iterator();
+      default:
+        throw new BugInCF("Unhandled state " + state);
+    }
+  }
+
+  @Override
+  public String toString() {
+    switch (state) {
+      case EMPTY:
+        return "[]";
+      case SINGLETON:
+        return "[" + value + "]";
+      case ANY:
+        assert set != null : "@AssumeAssertion(nullness): set initialized before";
+        return set.toString();
+      default:
+        throw new BugInCF("Unhandled state " + state);
+    }
+  }
+
+  @Override
+  public boolean addAll(Collection<? extends T> c) {
+    boolean res = false;
+    for (T elem : c) {
+      res |= add(elem);
+    }
+    return res;
+  }
+
+  @Override
+  public Object[] toArray() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public <S> @Nullable S[] toArray(@PolyNull S[] a) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean remove(@Nullable Object o) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean containsAll(Collection<?> c) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean retainAll(Collection<?> c) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean removeAll(Collection<?> c) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void clear() {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/IdentityMostlySingleton.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/IdentityMostlySingleton.java
new file mode 100644
index 0000000..5987b89
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/IdentityMostlySingleton.java
@@ -0,0 +1,69 @@
+package org.checkerframework.dataflow.util;
+
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * An arbitrary-size set that is very efficient (more efficient than HashSet) for 0 and 1 elements.
+ * Uses object identity for object comparison.
+ */
+public final class IdentityMostlySingleton<T extends Object> extends AbstractMostlySingleton<T> {
+
+  /** Create an IdentityMostlySingleton. */
+  public IdentityMostlySingleton() {
+    super(State.EMPTY);
+  }
+
+  /** Create an IdentityMostlySingleton. */
+  public IdentityMostlySingleton(T value) {
+    super(State.SINGLETON, value);
+  }
+
+  @Override
+  public boolean add(@FindDistinct T e) {
+    switch (state) {
+      case EMPTY:
+        state = State.SINGLETON;
+        value = e;
+        return true;
+      case SINGLETON:
+        if (value == e) {
+          return false;
+        }
+        makeNonSingleton();
+        // fall through
+      case ANY:
+        assert set != null : "@AssumeAssertion(nullness): set initialized before";
+        return set.add(e);
+      default:
+        throw new BugInCF("Unhandled state " + state);
+    }
+  }
+
+  /** Switch the representation of this from SINGLETON to ANY. */
+  private void makeNonSingleton() {
+    state = State.ANY;
+    set = Collections.newSetFromMap(new IdentityHashMap<>(4));
+    assert value != null : "@AssumeAssertion(nullness): previous add is non-null";
+    set.add(value);
+    value = null;
+  }
+
+  @SuppressWarnings("interning:not.interned") // this class uses object identity
+  @Override
+  public boolean contains(Object o) {
+    switch (state) {
+      case EMPTY:
+        return false;
+      case SINGLETON:
+        return o == value;
+      case ANY:
+        assert set != null : "@AssumeAssertion(nullness): set initialized before";
+        return set.contains(o);
+      default:
+        throw new BugInCF("Unhandled state " + state);
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/MostlySingleton.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/MostlySingleton.java
new file mode 100644
index 0000000..3b20535
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/MostlySingleton.java
@@ -0,0 +1,68 @@
+package org.checkerframework.dataflow.util;
+
+import java.util.LinkedHashSet;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * A set that is more efficient than HashSet for 0 and 1 elements. Uses {@code Objects.equals} for
+ * object comparison and a {@link LinkedHashSet} for backing storage.
+ */
+public final class MostlySingleton<T extends Object> extends AbstractMostlySingleton<T> {
+
+  /** Create a MostlySingleton. */
+  public MostlySingleton() {
+    super(State.EMPTY);
+  }
+
+  /** Create a MostlySingleton. */
+  public MostlySingleton(T value) {
+    super(State.SINGLETON, value);
+  }
+
+  @Override
+  public boolean add(T e) {
+    switch (state) {
+      case EMPTY:
+        state = State.SINGLETON;
+        value = e;
+        return true;
+      case SINGLETON:
+        assert value != null : "@AssumeAssertion(nullness): SINGLETON => value != null";
+        if (value.equals(e)) {
+          return false;
+        }
+        makeNonSingleton();
+        // fall through
+      case ANY:
+        assert set != null : "@AssumeAssertion(nullness): ANY => value != null";
+        return set.add(e);
+      default:
+        throw new BugInCF("Unhandled state " + state);
+    }
+  }
+
+  /** Switch the representation of this from SINGLETON to ANY. */
+  private void makeNonSingleton() {
+    state = State.ANY;
+    set = new LinkedHashSet<>();
+    assert value != null : "@AssumeAssertion(nullness): SINGLETON => value != null";
+    set.add(value);
+    value = null;
+  }
+
+  @Override
+  public boolean contains(Object o) {
+    switch (state) {
+      case EMPTY:
+        return false;
+      case SINGLETON:
+        assert value != null : "@AssumeAssertion(nullness): SINGLETON => value != null";
+        return value.equals(o);
+      case ANY:
+        assert set != null : "@AssumeAssertion(nullness): set initialized before";
+        return set.contains(o);
+      default:
+        throw new BugInCF("Unhandled state " + state);
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/NodeUtils.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/NodeUtils.java
new file mode 100644
index 0000000..9bc577c
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/NodeUtils.java
@@ -0,0 +1,90 @@
+package org.checkerframework.dataflow.util;
+
+import com.sun.source.tree.Tree;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.tree.JCTree;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.dataflow.cfg.node.BooleanLiteralNode;
+import org.checkerframework.dataflow.cfg.node.ConditionalNotNode;
+import org.checkerframework.dataflow.cfg.node.ConditionalOrNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** A utility class to operate on a given {@link Node}. */
+public class NodeUtils {
+
+  /**
+   * Returns true iff {@code node} corresponds to a boolean typed expression (either the primitive
+   * type {@code boolean}, or class type {@link java.lang.Boolean}).
+   *
+   * @return true iff {@code node} corresponds to a boolean typed expression (either the primitive
+   *     type {@code boolean}, or class type {@link java.lang.Boolean})
+   */
+  public static boolean isBooleanTypeNode(Node node) {
+
+    if (node instanceof ConditionalOrNode) {
+      return true;
+    }
+
+    // not all nodes have an associated tree, but those are all not of a boolean type.
+    Tree tree = node.getTree();
+    if (tree == null) {
+      return false;
+    }
+
+    Type type = ((JCTree) tree).type;
+    if (TypesUtils.isBooleanType(type)) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Returns true iff {@code node} is a {@link FieldAccessNode} that is an access to an array's
+   * length.
+   *
+   * @return true iff {@code node} is a {@link FieldAccessNode} that is an access to an array's
+   *     length
+   */
+  public static boolean isArrayLengthFieldAccess(Node node) {
+    if (!(node instanceof FieldAccessNode)) {
+      return false;
+    }
+    FieldAccessNode fieldAccess = (FieldAccessNode) node;
+    return fieldAccess.getFieldName().equals("length")
+        && fieldAccess.getReceiver().getType().getKind() == TypeKind.ARRAY;
+  }
+
+  /** Returns true iff {@code node} is an invocation of the given method. */
+  public static boolean isMethodInvocation(
+      Node node, ExecutableElement method, ProcessingEnvironment env) {
+    if (!(node instanceof MethodInvocationNode)) {
+      return false;
+    }
+    ExecutableElement invoked = ((MethodInvocationNode) node).getTarget().getMethod();
+    return ElementUtils.isMethod(invoked, method, env);
+  }
+
+  /**
+   * Returns true if the given node statically evaluates to {@code value} and has no side effects.
+   *
+   * @param n a node
+   * @param value the boolean value that the node is tested against
+   * @return true if the node is equivalent to a literal with value {@code value}
+   */
+  public static boolean isConstantBoolean(Node n, boolean value) {
+    if (n instanceof BooleanLiteralNode) {
+      return ((BooleanLiteralNode) n).getValue() == value;
+    } else if (n instanceof ConditionalNotNode) {
+      return isConstantBoolean(((ConditionalNotNode) n).getOperand(), !value);
+    } else {
+      return false;
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityChecker.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityChecker.java
new file mode 100644
index 0000000..8469a77
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityChecker.java
@@ -0,0 +1,342 @@
+package org.checkerframework.dataflow.util;
+
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.CatchTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import javax.lang.model.element.Element;
+import org.checkerframework.dataflow.qual.Deterministic;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.Pure.Kind;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A visitor that determines the purity (as defined by {@link
+ * org.checkerframework.dataflow.qual.SideEffectFree}, {@link
+ * org.checkerframework.dataflow.qual.Deterministic}, and {@link
+ * org.checkerframework.dataflow.qual.Pure}) of a statement or expression. The entry point is method
+ * {@link #checkPurity}.
+ *
+ * @see SideEffectFree
+ * @see Deterministic
+ * @see Pure
+ */
+public class PurityChecker {
+
+  /**
+   * Compute whether the given statement is side-effect-free, deterministic, or both. Returns a
+   * result that can be queried.
+   *
+   * @param statement the statement to check
+   * @param annoProvider the annotation provider
+   * @param assumeSideEffectFree true if all methods should be assumed to be @SideEffectFree
+   * @param assumeDeterministic true if all methods should be assumed to be @Deterministic
+   * @return information about whether the given statement is side-effect-free, deterministic, or
+   *     both
+   */
+  public static PurityResult checkPurity(
+      TreePath statement,
+      AnnotationProvider annoProvider,
+      boolean assumeSideEffectFree,
+      boolean assumeDeterministic) {
+    PurityCheckerHelper helper =
+        new PurityCheckerHelper(annoProvider, assumeSideEffectFree, assumeDeterministic);
+    helper.scan(statement, null);
+    return helper.purityResult;
+  }
+
+  /**
+   * Result of the {@link PurityChecker}. Can be queried regarding whether a given tree was
+   * side-effect-free, deterministic, or both; also gives reasons if the answer is "no".
+   */
+  public static class PurityResult {
+
+    /** Reasons that the referenced method is not side-effect-free. */
+    protected final List<Pair<Tree, String>> notSEFreeReasons = new ArrayList<>(1);
+
+    /** Reasons that the referenced method is not deterministic. */
+    protected final List<Pair<Tree, String>> notDetReasons = new ArrayList<>(1);
+
+    /** Reasons that the referenced method is not side-effect-free and deterministic. */
+    protected final List<Pair<Tree, String>> notBothReasons = new ArrayList<>(1);
+
+    /**
+     * Contains all the varieties of purity that the expression has. Starts out with all varieties,
+     * and elements are removed from it as violations are found.
+     */
+    protected EnumSet<Pure.Kind> kinds = EnumSet.allOf(Pure.Kind.class);
+
+    /**
+     * Return the kinds of purity that the method has.
+     *
+     * @return the kinds of purity that the method has
+     */
+    public EnumSet<Pure.Kind> getKinds() {
+      return kinds;
+    }
+
+    /**
+     * Is the method pure w.r.t. a given set of kinds?
+     *
+     * @param otherKinds the varieties of purity to check
+     * @return true if the method is pure with respect to all the given kinds
+     */
+    public boolean isPure(EnumSet<Pure.Kind> otherKinds) {
+      return kinds.containsAll(otherKinds);
+    }
+
+    /**
+     * Get the reasons why the method is not side-effect-free.
+     *
+     * @return the reasons why the method is not side-effect-free
+     */
+    public List<Pair<Tree, String>> getNotSEFreeReasons() {
+      return notSEFreeReasons;
+    }
+
+    /**
+     * Add a reason why the method is not side-effect-free.
+     *
+     * @param t a tree
+     * @param msgId why the tree is not side-effect-free
+     */
+    public void addNotSEFreeReason(Tree t, String msgId) {
+      notSEFreeReasons.add(Pair.of(t, msgId));
+      kinds.remove(Kind.SIDE_EFFECT_FREE);
+    }
+
+    /**
+     * Get the reasons why the method is not deterministic.
+     *
+     * @return the reasons why the method is not deterministic
+     */
+    public List<Pair<Tree, String>> getNotDetReasons() {
+      return notDetReasons;
+    }
+
+    /**
+     * Add a reason why the method is not deterministic.
+     *
+     * @param t a tree
+     * @param msgId why the tree is not deterministic
+     */
+    public void addNotDetReason(Tree t, String msgId) {
+      notDetReasons.add(Pair.of(t, msgId));
+      kinds.remove(Kind.DETERMINISTIC);
+    }
+
+    /**
+     * Get the reasons why the method is not both side-effect-free and deterministic.
+     *
+     * @return the reasons why the method is not both side-effect-free and deterministic
+     */
+    public List<Pair<Tree, String>> getNotBothReasons() {
+      return notBothReasons;
+    }
+
+    /**
+     * Add a reason why the method is not both side-effect-free and deterministic.
+     *
+     * @param t tree
+     * @param msgId why the tree is not deterministic and side-effect-free
+     */
+    public void addNotBothReason(Tree t, String msgId) {
+      notBothReasons.add(Pair.of(t, msgId));
+      kinds.remove(Kind.DETERMINISTIC);
+      kinds.remove(Kind.SIDE_EFFECT_FREE);
+    }
+  }
+
+  // TODO: It would be possible to improve efficiency by visiting fewer nodes.  This would require
+  // overriding more visit* methods.  I'm not sure whether such an optimization would be worth it.
+
+  /** Helper class to keep {@link PurityChecker}'s interface clean. */
+  protected static class PurityCheckerHelper extends TreePathScanner<Void, Void> {
+
+    PurityResult purityResult = new PurityResult();
+
+    protected final AnnotationProvider annoProvider;
+
+    /**
+     * True if all methods should be assumed to be @SideEffectFree, for the purposes of
+     * org.checkerframework.dataflow analysis.
+     */
+    private final boolean assumeSideEffectFree;
+
+    /**
+     * True if all methods should be assumed to be @Deterministic, for the purposes of
+     * org.checkerframework.dataflow analysis.
+     */
+    private final boolean assumeDeterministic;
+
+    /**
+     * Create a PurityCheckerHelper.
+     *
+     * @param annoProvider the annotation provider
+     * @param assumeSideEffectFree true if all methods should be assumed to be @SideEffectFree
+     * @param assumeDeterministic true if all methods should be assumed to be @Deterministic
+     */
+    public PurityCheckerHelper(
+        AnnotationProvider annoProvider,
+        boolean assumeSideEffectFree,
+        boolean assumeDeterministic) {
+      this.annoProvider = annoProvider;
+      this.assumeSideEffectFree = assumeSideEffectFree;
+      this.assumeDeterministic = assumeDeterministic;
+    }
+
+    @Override
+    public Void visitCatch(CatchTree node, Void ignore) {
+      purityResult.addNotDetReason(node, "catch");
+      return super.visitCatch(node, ignore);
+    }
+
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree node, Void ignore) {
+      Element elt = TreeUtils.elementFromUse(node);
+      if (!PurityUtils.hasPurityAnnotation(annoProvider, elt)) {
+        purityResult.addNotBothReason(node, "call");
+      } else {
+        EnumSet<Pure.Kind> purityKinds =
+            (assumeDeterministic && assumeSideEffectFree)
+                // Avoid computation if not necessary
+                ? EnumSet.of(Kind.DETERMINISTIC, Kind.SIDE_EFFECT_FREE)
+                : PurityUtils.getPurityKinds(annoProvider, elt);
+        boolean det = assumeDeterministic || purityKinds.contains(Kind.DETERMINISTIC);
+        boolean seFree = assumeSideEffectFree || purityKinds.contains(Kind.SIDE_EFFECT_FREE);
+        if (!det && !seFree) {
+          purityResult.addNotBothReason(node, "call");
+        } else if (!det) {
+          purityResult.addNotDetReason(node, "call");
+        } else if (!seFree) {
+          purityResult.addNotSEFreeReason(node, "call");
+        }
+      }
+      return super.visitMethodInvocation(node, ignore);
+    }
+
+    @Override
+    public Void visitNewClass(NewClassTree node, Void ignore) {
+      // Ordinarily, "new MyClass()" is forbidden.  It is permitted, however, when it is the
+      // expression in "throw EXPR;".  (In the future, more expressions could be permitted.)
+      //
+      // The expression in "throw EXPR;" is allowed to be non-@Deterministic, so long as it is not
+      // within a catch block that could catch an exception that the statement throws.  For example,
+      // EXPR can be object creation (a "new" expression) or can call a non-deterministic method.
+      //
+      // Coarse rule (currently implemented):
+      //  * permit only "throw new SomeExpression(args)", where the constructor is
+      //    @SideEffectFree and the args are pure, and forbid all enclosing try statements
+      //    that have a catch clause.
+      // More precise rule:
+      //  * permit other non-deterministic expresssions within throw (at which time move this
+      //    logic to visitThrow()).
+      //  * the only bad try statements are those with a catch block that is:
+      //     * unchecked exceptions
+      //        * checked = Exception or lower, but excluding RuntimeException and its subclasses
+      //     * super- or sub-classes of the type of _expr_
+      //        * if _expr_ is exactly "new SomeException", this can be changed to just
+      //          "superclasses of SomeException".
+      //     * super- or sub-classes of exceptions declared to be thrown by any component of _expr_.
+      //     * need to check every containing try statement, not just the nearest enclosing one.
+
+      // Object creation is usually prohibited, but permit "throw new SomeException();" if it is not
+      // contained within any try statement that has a catch clause.  (There is no need to check the
+      // latter condition, because the Purity Checker forbids all catch statements.)
+      Tree parent = getCurrentPath().getParentPath().getLeaf();
+      boolean okThrowDeterministic = parent.getKind() == Tree.Kind.THROW;
+
+      Element ctorElement = TreeUtils.elementFromUse(node);
+      boolean deterministic = assumeDeterministic || okThrowDeterministic;
+      boolean sideEffectFree =
+          assumeSideEffectFree || PurityUtils.isSideEffectFree(annoProvider, ctorElement);
+      // This does not use "addNotBothReason" because the reasons are different:  one is because the
+      // constructor is called at all, and the other is because the constuctor is not
+      // side-effect-free.
+      if (!deterministic) {
+        purityResult.addNotDetReason(node, "object.creation");
+      }
+      if (!sideEffectFree) {
+        purityResult.addNotSEFreeReason(node, "call");
+      }
+
+      // TODO: if okThrowDeterministic, permit arguments to the newClass to be
+      // non-deterministic (don't add those to purityResult), but still don't permit them to
+      // have side effects.  This should probably wait until a rewrite of the Purity Checker.
+      return super.visitNewClass(node, ignore);
+    }
+
+    @Override
+    public Void visitAssignment(AssignmentTree node, Void ignore) {
+      ExpressionTree variable = node.getVariable();
+      assignmentCheck(variable);
+      return super.visitAssignment(node, ignore);
+    }
+
+    @Override
+    public Void visitUnary(UnaryTree node, Void ignore) {
+      switch (node.getKind()) {
+        case POSTFIX_DECREMENT:
+        case POSTFIX_INCREMENT:
+        case PREFIX_DECREMENT:
+        case PREFIX_INCREMENT:
+          ExpressionTree expression = node.getExpression();
+          assignmentCheck(expression);
+          break;
+        default:
+          // Nothing to do
+          break;
+      }
+      return super.visitUnary(node, ignore);
+    }
+
+    /**
+     * Check whether {@code variable} is permitted on the left-hand-side of an assignment.
+     *
+     * @param variable the lhs to check
+     */
+    protected void assignmentCheck(ExpressionTree variable) {
+      if (TreeUtils.isFieldAccess(variable)) {
+        // lhs is a field access
+        purityResult.addNotBothReason(variable, "assign.field");
+      } else if (variable instanceof ArrayAccessTree) {
+        // lhs is array access
+        purityResult.addNotBothReason(variable, "assign.array");
+      } else {
+        // lhs is a local variable
+        assert isLocalVariable(variable);
+      }
+    }
+
+    /**
+     * Checks if the argument is a local variable.
+     *
+     * @param variable the tree to check
+     * @return true if the argument is a local variable
+     */
+    protected boolean isLocalVariable(ExpressionTree variable) {
+      return variable instanceof IdentifierTree && !TreeUtils.isFieldAccess(variable);
+    }
+
+    @Override
+    public Void visitCompoundAssignment(CompoundAssignmentTree node, Void ignore) {
+      ExpressionTree variable = node.getVariable();
+      assignmentCheck(variable);
+      return super.visitCompoundAssignment(node, ignore);
+    }
+  }
+}
diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java
new file mode 100644
index 0000000..51ce3da
--- /dev/null
+++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java
@@ -0,0 +1,144 @@
+package org.checkerframework.dataflow.util;
+
+import com.sun.source.tree.MethodTree;
+import java.util.EnumSet;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import org.checkerframework.dataflow.qual.Deterministic;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.Pure.Kind;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A utility class for working with the {@link SideEffectFree}, {@link Deterministic}, and {@link
+ * Pure} annotations.
+ *
+ * @see SideEffectFree
+ * @see Deterministic
+ * @see Pure
+ */
+public class PurityUtils {
+
+  /**
+   * Does the method {@code methodTree} have any purity annotation?
+   *
+   * @param provider how to get annotations
+   * @param methodTree a method to test
+   * @return whether the method has any purity annotations
+   */
+  public static boolean hasPurityAnnotation(AnnotationProvider provider, MethodTree methodTree) {
+    return !getPurityKinds(provider, methodTree).isEmpty();
+  }
+
+  /**
+   * Does the method {@code methodElement} have any purity annotation?
+   *
+   * @param provider how to get annotations
+   * @param methodElement a method to test
+   * @return whether the method has any purity annotations
+   */
+  public static boolean hasPurityAnnotation(AnnotationProvider provider, Element methodElement) {
+    return !getPurityKinds(provider, methodElement).isEmpty();
+  }
+
+  /**
+   * Is the method {@code methodTree} deterministic?
+   *
+   * @param provider how to get annotations
+   * @param methodTree a method to test
+   * @return whether the method is deterministic
+   */
+  public static boolean isDeterministic(AnnotationProvider provider, MethodTree methodTree) {
+    Element methodElement = TreeUtils.elementFromTree(methodTree);
+    if (methodElement == null) {
+      throw new BugInCF("Could not find element for tree: " + methodTree);
+    }
+    return isDeterministic(provider, methodElement);
+  }
+
+  /**
+   * Is the method {@code methodElement} deterministic?
+   *
+   * @param provider how to get annotations
+   * @param methodElement a method to test
+   * @return whether the method is deterministic
+   */
+  public static boolean isDeterministic(AnnotationProvider provider, Element methodElement) {
+    EnumSet<Pure.Kind> kinds = getPurityKinds(provider, methodElement);
+    return kinds.contains(Kind.DETERMINISTIC);
+  }
+
+  /**
+   * Is the method {@code methodTree} side-effect-free?
+   *
+   * @param provider how to get annotations
+   * @param methodTree a method to test
+   * @return whether the method is side-effect-free
+   */
+  public static boolean isSideEffectFree(AnnotationProvider provider, MethodTree methodTree) {
+    Element methodElement = TreeUtils.elementFromTree(methodTree);
+    if (methodElement == null) {
+      throw new BugInCF("Could not find element for tree: " + methodTree);
+    }
+    return isSideEffectFree(provider, methodElement);
+  }
+
+  /**
+   * Is the method {@code methodElement} side-effect-free?
+   *
+   * @param provider how to get annotations
+   * @param methodElement a method to test
+   * @return whether the method is side-effect-free
+   */
+  public static boolean isSideEffectFree(AnnotationProvider provider, Element methodElement) {
+    EnumSet<Pure.Kind> kinds = getPurityKinds(provider, methodElement);
+    return kinds.contains(Kind.SIDE_EFFECT_FREE);
+  }
+
+  /**
+   * Returns the types of purity of the method {@code methodTree}.
+   *
+   * @param provider how to get annotations
+   * @param methodTree a method to test
+   * @return the types of purity of the method {@code methodTree}
+   */
+  public static EnumSet<Pure.Kind> getPurityKinds(
+      AnnotationProvider provider, MethodTree methodTree) {
+    Element methodElement = TreeUtils.elementFromTree(methodTree);
+    if (methodElement == null) {
+      throw new BugInCF("Could not find element for tree: " + methodTree);
+    }
+    return getPurityKinds(provider, methodElement);
+  }
+
+  /**
+   * Returns the types of purity of the method {@code methodElement}.
+   *
+   * @param provider how to get annotations
+   * @param methodElement a method to test
+   * @return the types of purity of the method {@code methodElement}
+   */
+  // TODO: should the return type be an EnumSet?
+  public static EnumSet<Pure.Kind> getPurityKinds(
+      AnnotationProvider provider, Element methodElement) {
+    AnnotationMirror pureAnnotation = provider.getDeclAnnotation(methodElement, Pure.class);
+    AnnotationMirror sefAnnotation =
+        provider.getDeclAnnotation(methodElement, SideEffectFree.class);
+    AnnotationMirror detAnnotation = provider.getDeclAnnotation(methodElement, Deterministic.class);
+
+    if (pureAnnotation != null) {
+      return EnumSet.of(Kind.DETERMINISTIC, Kind.SIDE_EFFECT_FREE);
+    }
+    EnumSet<Pure.Kind> result = EnumSet.noneOf(Pure.Kind.class);
+    if (sefAnnotation != null) {
+      result.add(Kind.SIDE_EFFECT_FREE);
+    }
+    if (detAnnotation != null) {
+      result.add(Kind.DETERMINISTIC);
+    }
+    return result;
+  }
+}
diff --git a/dataflow/src/test/java/livevar/LiveVariable.java b/dataflow/src/test/java/livevar/LiveVariable.java
new file mode 100644
index 0000000..63bf6b1
--- /dev/null
+++ b/dataflow/src/test/java/livevar/LiveVariable.java
@@ -0,0 +1,41 @@
+package livevar;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Map;
+import org.checkerframework.dataflow.analysis.BackwardAnalysis;
+import org.checkerframework.dataflow.analysis.BackwardAnalysisImpl;
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizeLauncher;
+import org.checkerframework.dataflow.livevariable.LiveVarStore;
+import org.checkerframework.dataflow.livevariable.LiveVarTransfer;
+import org.checkerframework.dataflow.livevariable.LiveVarValue;
+
+/** Used in liveVariableTest Gradle task to test the LiveVariable analysis. */
+public class LiveVariable {
+
+  /**
+   * The main method expects to be run in dataflow/tests/live-variable directory.
+   *
+   * @param args not used
+   */
+  public static void main(String[] args) {
+
+    String inputFile = "Test.java";
+    String method = "test";
+    String clazz = "Test";
+    String outputFile = "Out.txt";
+
+    LiveVarTransfer transfer = new LiveVarTransfer();
+    BackwardAnalysis<LiveVarValue, LiveVarStore, LiveVarTransfer> backwardAnalysis =
+        new BackwardAnalysisImpl<>(transfer);
+    CFGVisualizeLauncher cfgVisualizeLauncher = new CFGVisualizeLauncher();
+    Map<String, Object> res =
+        cfgVisualizeLauncher.generateStringOfCFG(inputFile, method, clazz, true, backwardAnalysis);
+    try (FileWriter out = new FileWriter(outputFile)) {
+      out.write(res.get("stringGraph").toString());
+      out.write("\n");
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/dataflow/tests/issue3447/Test.java b/dataflow/tests/issue3447/Test.java
new file mode 100644
index 0000000..2ff93c9
--- /dev/null
+++ b/dataflow/tests/issue3447/Test.java
@@ -0,0 +1,12 @@
+// Test case for Issue 3447:
+// https://github.com/typetools/checker-framework/issues/3447
+
+public class Test {
+  public void test() throws Exception {
+    try {
+      int[] myNumbers = {1};
+      System.out.println(myNumbers[1]);
+    } catch (Exception e) {
+    }
+  }
+}
diff --git a/dataflow/tests/live-variable/Expected.txt b/dataflow/tests/live-variable/Expected.txt
new file mode 100644
index 0000000..1bcbdfc
--- /dev/null
+++ b/dataflow/tests/live-variable/Expected.txt
@@ -0,0 +1,76 @@
+2 -> 3 EACH_TO_EACH
+3 -> 4 EACH_TO_EACH
+4 -> 8 THEN_TO_BOTH
+4 -> 10 ELSE_TO_BOTH
+8 -> 0 EACH_TO_EACH
+10 -> 0 EACH_TO_EACH
+
+2:
+Process order: 1
+AnalysisResult#1
+Before:   live variables = none
+~~~~~~~~~
+<entry>
+
+3:
+Process order: 2
+AnalysisResult#3
+Before:   live variables = none
+~~~~~~~~~
+a   [ VariableDeclaration ]
+1   [ IntegerLiteral ]
+a = 1   [ Assignment ]
+b   [ VariableDeclaration ]
+2   [ IntegerLiteral ]
+b = 2   [ Assignment ]
+c   [ VariableDeclaration ]
+3   [ IntegerLiteral ]
+c = 3   [ Assignment ]
+a   [ LocalVariable ]
+0   [ IntegerLiteral ]
+(a > 0)   [ GreaterThan ]
+~~~~~~~~~
+TransferInput#18
+After:   live variables = a, b, c
+
+4:
+Process order: 3
+AnalysisResult#5
+Before:   live variables = a, b, c
+~~~~~~~~~
+ConditionalBlock: then: 8, else: 10
+
+8:
+Process order: 4
+AnalysisResult#7
+Before:   live variables = a, c
+~~~~~~~~~
+d   [ VariableDeclaration ]
+a   [ LocalVariable ]
+c   [ LocalVariable ]
+(a + c)   [ NumericalAddition ]
+d = (a + c)   [ Assignment ]
+~~~~~~~~~
+TransferInput#2
+After:   live variables = none
+
+10:
+Process order: 5
+AnalysisResult#9
+Before:   live variables = a, b
+~~~~~~~~~
+e   [ VariableDeclaration ]
+a   [ LocalVariable ]
+b   [ LocalVariable ]
+(a + b)   [ NumericalAddition ]
+e = (a + b)   [ Assignment ]
+~~~~~~~~~
+TransferInput#1
+After:   live variables = none
+
+0:
+Process order: 6
+AnalysisResult#11
+Before:   live variables = none
+~~~~~~~~~
+<exit>
diff --git a/dataflow/tests/live-variable/Test.java b/dataflow/tests/live-variable/Test.java
new file mode 100644
index 0000000..641ac2e
--- /dev/null
+++ b/dataflow/tests/live-variable/Test.java
@@ -0,0 +1,10 @@
+public class Test {
+  public void test() {
+    int a = 1, b = 2, c = 3;
+    if (a > 0) {
+      int d = a + c;
+    } else {
+      int e = a + b;
+    }
+  }
+}
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
new file mode 100644
index 0000000..512ddf6
--- /dev/null
+++ b/docs/CHANGELOG.md
@@ -0,0 +1,4094 @@
+Version 3.14.0 (June 1, 2021)
+----------------------------
+
+**User-visible changes:**
+
+The Units Checker supports new qualifiers (thanks to Rene Kraneis):
+ * `@Volume`, `@m3`, `@mm3`, `@km3`
+ * `@Force`, `@N`, `@kN`
+ * `@t` (metric ton, a unit of mass)
+
+Stub files can now override declaration annotations in the annotated JDK.
+Previously, stub files only overrode type annotations in the annotated JDK.
+
+Command-line argument `-AstubWarnIfNotFound` is treated as true for stub
+files provided on the command line.
+
+**Implementation details:**
+
+Method `SourceChecker.getProperties` takes a third formal parameter `permitNonExisting`.
+
+Method `TreeUtils.getMethodName()` returns a `String` rather than a `Name`.
+
+Removed CheckerDevelMain.
+
+**Closed issues:**
+
+
+Version 3.13.0 (May 3, 2021)
+----------------------------
+
+**Survey:**
+
+If you use the Checker Framework, please answer a 3-question survey about what
+version of Java you use.  It will take less than 1 minute to complete.  Please
+answer it at
+https://docs.google.com/forms/d/1Bbt34c_3nDItHsBnmEfumoyrR-Zxhvo3VTHucXwfMcQ .
+Thanks!
+
+**User-visible changes:**
+
+Command-line argument -AassumeKeyFor makes the Nullness Checker and Map Key
+Checker unsoundly assume that the argument to `Map.get` is a key for the
+receiver map.
+
+Warning message keys are shorter.  This reduces clutter in error messages and in
+`@SuppressWarnings` annotations.  Most ".type.invalid", ".type.incompatible",
+".invalid", and ".not.satisfied" suffixes and "type.invalid." prefixes have been
+removed, and most ".invalid." substrings have been changed to ".".
+
+The Checker Framework no longer crashes on code that contains binding
+variables (introduced in Java 14 for `instanceof` pattern matching), and
+such variables are reflected in the control flow graph (CFG).  Thanks to
+Chris Day for this change.  However, note that the Checker Framework only
+has full support for Java 8 and Java 11.
+
+New command-line argument `-AstubWarnNote` makes stub file warnings notes
+rather than warnings.
+
+Removed the StubGenerator section from the manual, because changes in JDK 11
+have broken the StubGenerator program.
+
+**Implementation details:**
+
+Method renamings:
+ * `DependentTypesHelper.atReturnType` => `atMethodBody`
+
+**Closed issues:**
+#1268, #3039, #4410, #4550, #4558, #4563, #4566, #4567, #4571, #4584, #4591,
+#4594, #4600.
+
+
+Version 3.12.0 (April 1, 2021)
+------------------------------
+
+**User-visible changes:**
+
+New FAQ item "How should I annotate code that uses generics?" gives
+examples of annotations on type variables, together with their meaning.
+
+`-Ainfer=ajava` uses ajava files (rather than jaif files or stub files)
+internally during whole-program inference.
+
+The Optional Checker supports a new annotation `@OptionalBottom` that
+stands for (only) the `null` value.
+
+The `value` element/argument to `@EnumVal` is now required.  Previously it
+defaulted to an empty array.
+
+**Implementation details:**
+
+A precondition or normal postcondition annotation's `value` element must have
+type `String[]`, not `String`.  A conditional postcondition annotation's
+`expression` element must have type `String[]`, not `String`.  These changes
+will not affect users (any programmer-written annotation that was legal before
+will still be legal), but it may affect checker implementations.
+
+`JavaExpressionParseUtil`:
+`JavaExpressionParseUtil#parse` no longer viewpoint-adapts Java expressions. It
+just converts the expression `String` to a `JavaExpression`.  To that end,
+`JavaExpressionParseUtil.JavaExpressionContext` has been removed and
+`JavaExpressionParseUtil#parse` no longer takes a context object.  Most calls to
+`JavaExpressionParseUtil#parse` should be replaced with a call to one of the
+methods in `StringToJavaExpressions`.
+
+Renamed `AnnotatedTypeComparer` to `DoubleAnnotatedTypeScanner`. In the new
+class, the method `compare` was renamed `defaultAction`. The method `combineRs`
+was replaced by `reduce`.
+
+Removed methods:
+ * `AnnotationUtils.getElementValueArrayOrSingleton`
+ * `DependentTypesHelper.standardizeNewClassTree`: use `atExpression` instead
+ * `DependentTypesHelper.standardizeString`: override one of the methods
+   explained in the Javadoc of `convertAnnotationMirror`
+
+Method renamings:
+ * `DefaultQualifierForUseTypeAnnotator.getSupportAnnosFromDefaultQualifierForUses` => `getDefaultQualifierForUses`
+ * In `DependentTypesHelper`:
+    * `check*` => `check*ForErrorExpressions`
+    * `viewpointAdaptConstructor` => `atConstructorInvocation`
+    * `viewpointAdaptMethod` => `atMethodInvocation`
+    * `viewpointAdaptTypeVariableBounds` => `atParameterizedTypeUse`
+    * `standardizeClass` =>  `atTypeDecl`
+    * `standardizeExpression` => `atExpression`
+    * `standardizeFieldAccess` => `atFieldAccess`
+    * `standardizeReturnType` => `atReturnType`
+    * `standardizeVariable` => `atVariableDeclaration`
+
+Deprecated some overloads in `AnnotationUtils` that take a `CharSequence`
+(use an overload that takes an `ExecutablElement`):
+ * `getElementValueArray`
+ * `getElementValueClassName`
+ * `getElementValueClassNames`
+ * `getElementValueEnumArray`
+ * `getElementValueEnum`
+ * `getElementValue`
+ * `getElementValuesWithDefaults`
+
+Deprecated methods in `AnnotationUtils`:
+ * `areSameByClass`: use `areSameByName`
+ * `getElementValuesWithDefaults`: use a `getElementValue*` method
+
+Removed deprecated `PluginUtil` class.
+
+**Closed issues:**
+#1376, #3740, #3970, #4041, #4254, #4346, #4355, #4358, #4372, #4381, #4384,
+#4417, #4449, #4452, #4480.
+
+
+Version 3.11.0 (March 1, 2021)
+------------------------------
+
+**User-visible changes:**
+
+In a stub file for a class C, you may write a declaration for a method that is
+inherited by C but not defined by it.  Previously, such stub declarations were
+ignored.  For more information, see the manual's documentation of "fake
+overrides".
+
+Nullness Checker error message key changes:
+ * `known.nonnull` => `nulltest.redundant`
+ * `initialization.static.fields.uninitialized` => `initialization.static.field.uninitialized`,
+   and it is now issued on the field rather than on the class
+ * new `initialization.field.uninitialized` is issued on the field instead of
+   `initialization.fields.uninitialized` on the class, if there is no
+   explicitly-written constructor.
+
+Signature Checker supports two new type qualifiers:
+ * `@CanonicalNameAndBinaryName`
+ * `@CanonicalNameOrPrimitiveType`
+
+**Implementation details:**
+
+You can make a variable's default type depend on its name, or a method
+return type default depend on the method's name.  To support this feature,
+`@DefaultFor` has new elements `names` and `namesExceptions`.
+
+Changes to protected fields in `OverrideChecker`:
+ * Removed `overriderMeth`, `overriderTyp`, `overriddenMeth`, `overriddenTyp`
+ * Renamed `methodReference` => `isMethodReference`
+ * Renamed `overridingType` => `overriderType`
+ * Renamed `overridingReturnType` => `overriderReturnType`
+
+Changes to JavaExpression parsing:
+ * The signatures of these methods changed; see Javadoc.
+    * `JavaExpressionParseUtil#parse`
+    * `DependentTypesHelper#standardizeString`
+ * These methods moved:
+    * `GenericAnnotatedTypeFactory#standardizeAnnotationFromContract` => `DependentTypesHelper`
+    * `JavaExpressionParseUtil#fromVariableTree` => `JavaExpression`
+
+Changes to JavaExpressionContext:
+ * New method JavaExpressionContext#buildContextForMethodDeclaration(MethodTree, SourceChecker)
+   replaces all overloads of buildContextForMethodDeclaration.
+
+Parsing a Java expression no longer requires the formal parameters
+`AnnotationProvider provider` or `boolean allowNonDeterministic`.  Methods
+in `JavaExpression` with simplified signatures include
+ * `fromArrayAccess`
+ * `fromNodeFieldAccess`
+ * `fromNode`
+ * `fromTree`
+ * `getParametersOfEnclosingMethod`
+ * `getReceiver`
+
+`CFAbstractStore.insertValue` does nothing if passed a nondeterministic
+expression.  Use new method `CFAbstractStore.insertValuePermitNondeterministic`
+to map a nondeterministic expression to a value.
+
+**Closed issues:**
+#862, #3631, #3991, #4031, #4206, #4207, #4226, #4231, #4248, #4263, #4265,
+#4279, #4286, #4289.
+
+
+Version 3.10.0 (February 1, 2021)
+---------------------------------
+
+**User-visible changes:**
+
+Moved utility classes from `checker-qual.jar` to the new `checker-util.jar`.
+Also, added `util` to the end of all the packages of the utility classes.
+
+In Maven Central, `checker.jar` no longer contains duplicates of qualifiers in
+`checker-qual.jar`, but rather uses a Maven dependency. A fat jar file with all
+the dependencies (like the old `checker.jar`) is available in Maven Central with
+the classifier "all".
+
+When supplying the `-Ainfer=...` command-line argument, you must also supply `-Awarns`.
+
+Replaced several error message keys:
+ * `contracts.precondition.expression.parameter.name`
+ * `contracts.postcondition.expression.parameter.name`
+ * `contracts.conditional.postcondition.expression.parameter.name`
+ * `method.declaration.expression.parameter.name`
+by new message keys:
+ * `expression.parameter.name`
+ * `expression.parameter.name.shadows.field`
+
+**Implementation details:**
+
+Deprecated `ElementUtils.enclosingClass`; use `ElementUtils.enclosingTypeElement`.
+
+Removed classes (use `SourceChecker` instead):
+ * `BaseTypeContext`
+ * `CFContext`
+ * `BaseContext`
+
+Removed methods:
+ * `SourceChecker.getContext()`: it returned the receiver
+ * `SourceChecker.getChecker()`: it returned the receiver
+ * `AnnotatedTypeFactory.getContext()`: use `getChecker()`
+ * methods on `TreePath`s from class 'TreeUtils`; use the versions in `TreePathUtil`.
+
+Moved class:
+ * org.checkerframework.framework.util.PurityUnqualified to
+   org.checkerframework.framework.qual.PurityUnqualified
+
+Renamed methods:
+ * `AnnotatedTypeMirror.directSuperTypes` => `directSupertypes` (note
+   capitalization) for consistency with `javax.lang.model.util.Types`
+ * `AnnotatedTypeMirror.removeAnnotation(Class)` => `removeAnnotationByClass`
+ * `MethodCall.getParameters` => `getArguments`
+ * `MethodCall.containsSyntacticEqualParameter` => `containsSyntacticEqualArgument`
+ * `ArrayAccess.getReceiver` => `getArray`
+
+**Closed issues:**
+#3325 , #3474.
+
+
+Version 3.9.1 (January 13, 2021)
+--------------------------------
+
+**Implementation details:**
+
+Copied methods on `TreePath`s from class 'TreeUtils` to new class `TreePathUtil`.
+(The methods in TreePath will be deleted in the next release.)
+ * `TreeUtils.enclosingClass` => `TreePathUtil.enclosingClass`
+ * `TreeUtils.enclosingMethod` => `TreePathUtil.enclosingMethod`
+ * `TreeUtils.enclosingMethodOrLambda` => `TreePathUtil.enclosingMethodOrLambda`
+ * `TreeUtils.enclosingNonParen` => `TreePathUtil.enclosingNonParen`
+ * `TreeUtils.enclosingOfClass` => `TreePathUtil.enclosingOfClass`
+ * `TreeUtils.enclosingOfKind` => `TreePathUtil.enclosingOfKind`
+ * `TreeUtils.enclosingTopLevelBlock` => `TreePathUtil.enclosingTopLevelBlock`
+ * `TreeUtils.enclosingVariable` => `TreePathUtil.enclosingVariable`
+ * `TreeUtils.getAssignmentContext` => `TreePathUtil.getAssignmentContext`
+ * `TreeUtils.inConstructor` => `TreePathUtil.inConstructor`
+ * `TreeUtils.isTreeInStaticScope` => `TreePathUtil.isTreeInStaticScope`
+ * `TreeUtils.pathTillClass` => `TreePathUtil.pathTillClass`
+ * `TreeUtils.pathTillOfKind` => `TreePathUtil.pathTillOfKind`
+
+**Closed issues:**
+#789, #3202, #4071, #4083, #4114, #4115.
+
+
+Version 3.9.0 (January 4, 2021)
+-------------------------------
+
+**User-visible changes:**
+
+New scripts `checker/bin/wpi.sh` and `checker/bin/wpi-many.sh` run whole-program
+inference, without modifying the source code of the target programs.
+
+The `-Ainfer` command-line argument now infers
+ * method preconditions (`@RequiresQualifiers`, `@RequiresNonNull`)
+ * method postconditions (`@EnsuresQualifiers`, `@EnsuresNonNull`)
+ * `@MonotonicNonNull`
+
+The Called Methods Checker supports the -AdisableReturnsReceiver command-line option.
+
+The Format String Checker recognizes Error Prone's `@FormatMethod` annotation.
+
+Use of `@SuppressWarnings("fbc")` to suppress initialization warnings is deprecated.
+
+**Implementation details:**
+
+Class renamings:
+ * `StubParser` => `AnnotationFileParser`
+ * `Receiver` => `JavaExpression`
+   Also related class and method renamings, such as
+    * `FlowExpressions.internalReprOf` => `JavaExpressions.fromNode`
+ * In the Dataflow Framework:
+    * `ThisLiteralNode` => `ThisNode`
+    * `ExplicitThisLiteralNode` => `ExplicitThisNode`
+    * `ImplicitThisLiteralNode` => `ImplicitThisNode`
+
+Method deprecations:
+ * Deprecated `AnnotatedTypeFactory.addAliasedAnnotation`; use `addAliasedTypeAnnotation`
+
+**Closed issues:**
+#765, #2452, #2953, #3377, #3496, #3499, #3826, #3956, #3971, #3974, #3994,
+#4004, #4005, #4018, #4032, #4068, #4070.
+
+
+Version 3.8.0 (December 1, 2020)
+--------------------------------
+
+**User-visible changes:**
+
+The Initialized Fields Checker warns when a field is not initialized by a
+constructor.  This is more general than the Initialization Checker, which
+only checks that `@NonNull` fields are initialized.
+
+The manual describes how to modify an sbt build file to run the Checker
+Framework.
+
+The -AwarnUnneededSuppressions command-line option warns only about
+suppression strings that contain a checker name.
+
+The -AwarnUnneededSuppressionsExceptions=REGEX command-line option
+partially disables -AwarnUnneededSuppressions.  Most users don't need this.
+
+**Implementation details:**
+
+Added classes `SubtypeIsSubsetQualifierHierarchy` and
+`SubtypeIsSupersetQualifierHierarchy`.
+
+Moved the `contractsUtils` field from the visitor to the type factory.
+
+Class renamings:
+ * `ContractsUtils` => `ContractsFromMethod`
+
+Method renamings:
+ * `ElementUtils.getVerboseName` => `ElementUtils.getQualifiedName`
+ * `ElementUtils.getSimpleName` => `ElementUtils.getSimpleSignature`
+
+Field renamings:
+ * `AnnotatedTypeMirror.actualType` => `AnnotatedTypeMirror.underlyingType`
+
+Added a formal parameter to methods in `MostlyNoElementQualifierHierarchy`:
+ * `leastUpperBoundWithElements`
+ * `greatestLowerBoundWithElements`
+
+Removed a formal parameter from methods in `BaseTypeVisitor`:
+ * `checkPostcondition`
+ * `checkConditionalPostcondition`
+
+In `Analysis.runAnalysisFor()`, changed `boolean` parameter to enum `BeforeOrAfter`.
+
+Removed `org.checkerframework.framework.util.AnnotatedTypes#getIteratedType`; use
+`AnnotatedTypeFactory#getIterableElementType(ExpressionTree)` instead.
+
+**Closed issues:**
+#3287, #3390, #3681, #3839, #3850, #3851, #3862, #3871, #3884, #3888, #3908,
+#3929, #3932, #3935.
+
+
+Version 3.7.1 (November 2, 2020)
+--------------------------------
+
+**User-visible changes:**
+
+The Constant Value Checker supports two new annotations: @EnumVal and @MatchesRegex.
+
+The Nullness Checker supports annotation org.jspecify.annotations.NullnessUnspecified.
+
+**Implementation details:**
+
+AnnotatedIntersectionType#directSuperTypes now returns
+List<? extends AnnotatedTypeMirror>.
+
+The @RelevantJavaTypes annotation is now enforced:  a checker issues a warning
+if the programmer writes a type annotation on a type that is not listed.
+
+Deprecated CFAbstractTransfer.getValueWithSameAnnotations(), which is no
+longer used.  Added new methods getWidenedValue() and getNarrowedValue().
+
+Renamed TestUtilities.assertResultsAreValid() to TestUtilities.assertTestDidNotFail().
+
+Renamed BaseTypeValidator.isValidType() to BaseTypeValidator.isValidStructurally().
+
+New method BaseTypeVisitor#visitAnnotatedType(List, Tree) centralizes checking
+of user-written type annotations, even when parsed in declaration locations.
+
+**Closed issues:**
+#868, #1908, #2075, #3349, #3362, #3569, #3614, #3637, #3709, #3710, #3711,
+#3720, #3730, #3742, #3760, #3770, #3775, #3776, #3792, #3793, #3794, #3819,
+#3831.
+
+
+Version 3.7.0 (October 1, 2020)
+-------------------------------
+
+**User-visible changes:**
+
+The new Called Methods Checker tracks methods that have definitely been
+called on an object. It automatically supports detecting mis-uses of the
+builder pattern in code that uses Lombok or AutoValue.
+
+Accumulation analysis is now supported via a generic Accumulation Checker.
+An accumulation analysis is a restricted form of typestate analysis that does
+not require a precise alias analysis for soundness. The Called Methods Checker
+is an accumulation analysis.
+
+The Nullness Checker supports annotations
+org.codehaus.commons.nullanalysis.NotNull,
+org.codehaus.commons.nullanalysis.Nullable, and
+org.jspecify.annotations.Nullable.
+
+The Signature Checker supports annotations @CanonicalName and @CanonicalNameOrEmpty.
+The Signature Checker treats jdk.jfr.Unsigned as an alias for its own @Unsigned annotation.
+
+The shorthand syntax for the -processor command-line argument applies to
+utility checkers, such as the Constant Value Checker.
+
+**Implementation details:**
+
+A checker implementation may override AnnotatedTypeFactory.getWidenedAnnotations
+to provide special behavior for primitive widening conversions.
+
+Deprecated org.checkerframework.framework.util.MultiGraphQualifierHierarchy and
+org.checkerframework.framework.util.GraphQualifierHierarchy.  Removed
+AnnotatedTypeFactory#createQualifierHierarchy(MultiGraphFactory) and
+AnnotatedTypeFactory#createQualifierHierarchyFactory.  See Javadoc of
+MultiGraphQualifierHierarchy for instructions on how to use the new classes and
+methods.
+
+Renamed methods:
+ * NumberUtils.isFloatingPoint => TypesUtils.isFloatingPoint
+ * NumberUtils.isIntegral => TypesUtils.isIntegralPrimitiveOrBoxed
+ * NumberUtils.isPrimitiveFloatingPoint => TypeKindUtils.isFloatingPoint
+ * NumberUtils.isPrimitiveIntegral => TypeKindUtils.isIntegral
+ * NumberUtils.unboxPrimitive => TypeKindUtils.primitiveOrBoxedToTypeKind
+ * TypeKindUtils.widenedNumericType => TypeKindUtils.widenedNumericType
+ * TypesUtils.isFloating => TypesUtils.isFloatingPrimitive
+ * TypesUtils.isIntegral => TypesUtils.isIntegralPrimitive
+
+The CFStore copy constructor now takes only one argument.
+
+**Closed issues:**
+#352, #354, #553, #722, #762, #2208, #2239, #3033, #3105, #3266, #3275, #3408,
+#3561, #3616, #3619, #3622, #3625, #3630, #3632, #3648, #3650, #3667, #3668,
+#3669, #3700, #3701.
+
+
+Version 3.6.1 (September 2, 2020)
+---------------------------------
+
+Documented that the Checker Framework can issue false positive warnings in
+dead code.
+
+Documented when the Signedness Checker permits right shift operations.
+
+**Closed issues:**
+#3484, #3562, #3565, #3566, #3570, #3584, #3594, #3597, #3598.
+
+
+Version 3.6.0 (August 3, 2020)
+------------------------------
+
+**User-visible changes:**
+
+The Interning Checker supports method annotations @EqualsMethod and
+@CompareToMethod.  Place them on methods like equals(), compareTo(), and
+compare() to permit certain uses of == on non-interned values.
+
+Added an overloaded version of NullnessUtil.castNonNull that takes an error message.
+
+Added a new option `-Aversion` to print the version of the Checker Framework.
+
+New CFGVisualizeLauncher command-line arguments:
+ * `--outputdir`: directory in which to write output files
+ * `--string`: print the control flow graph in the terminal
+All CFGVisualizeLauncher command-line arguments now start with `--` instead of `-`.
+
+**Implementation details:**
+
+commonAssignmentCheck() now takes an additional argument.  Type system
+authors must update their overriding implementations.
+
+Renamed methods:
+ * GenericAnnotatedTypeFactory#addAnnotationsFromDefaultQualifierForUse => #addAnnotationsFromDefaultForType and
+ * BaseTypeValidator#shouldCheckTopLevelDeclaredType => #shouldCheckTopLevelDeclaredOrPrimitiveType
+
+Removed org.checkerframework.framework.test.FrameworkPer(Directory/File)Test classes.
+Use CheckerFrameworkPer(Directory/File)Test instead.
+
+**Closed issues:**
+
+#1395, #2483, #3207, #3223, #3224, #3313, #3381, #3422, #3424, #3428, #3429,
+#3438, #3442, #3443, #3447, #3449, #3461, #3482, #3485, #3495, #3500, #3528.
+
+
+Version 3.5.0 (July 1, 2020)
+----------------------------
+
+**User-visible changes:**
+
+Use "allcheckers:" instead of "all:" as a prefix in a warning suppression string.
+Writing `@SuppressWarnings("allcheckers")` means the same thing as
+`@SuppressWarnings("all")`, unless the `-ArequirePrefixInWarningSuppressions`
+command-line argument is supplied.  See the manual for details.
+
+It is no longer necessary to pass -Astubs=checker.jar/javadoc.astub when
+compiling a program that uses Javadoc classes.
+
+Renamed command-line arguments:
+ * -AshowSuppressWarningKeys to -AshowSuppressWarningsStrings
+
+The Signature Checker no longer considers Java keywords to be identifiers.
+Renamed Signature Checker annotations:
+ * @BinaryNameInUnnamedPackage => @BinaryNameWithoutPackage
+ * @FieldDescriptorForPrimitiveOrArrayInUnnamedPackage => @FieldDescriptorWithoutPackage
+ * @IdentifierOrArray => @ArrayWithoutPackage
+Added new Signature Checker annotations:
+ * @BinaryNameOrPrimitiveType
+ * @DotSeparatedIdentifiersOrPrimitiveType
+ * @IdentifierOrPrimitiveType
+
+The Nullness Checker now treats `System.getProperty()` soundly.  Use
+`-Alint=permitClearProperty` to disable special treatment of
+`System.getProperty()` and to permit undefining built-in system properties.
+
+Class qualifier parameters:  When a generic class represents a collection,
+a user can write a type qualifier on the type argument, as in
+`List<@Tainted Character>` versus `List<@Untainted Character>`.  When a
+non-generic class represents a collection with a hard-coded type (as
+`StringBuffer` hard-codes `Character`), you can use the new class qualifier
+parameter feature to distinguish `StringBuffer`s that contain different
+types of characters.
+
+The Dataflow Framework supports backward analysis.  See its manual.
+
+**Implementation details:**
+
+Changed the types of some fields and methods from array to List:
+ * QualifierDefaults.validLocationsForUncheckedCodeDefaults()
+ * QualifierDefaults.STANDARD_CLIMB_DEFAULTS_TOP
+ * QualifierDefaults.STANDARD_CLIMB_DEFAULTS_BOTTOM
+ * QualifierDefaults.STANDARD_UNCHECKED_DEFAULTS_TOP
+ * QualifierDefaults.STANDARD_UNCHECKED_DEFAULTS_BOTTOM
+
+Dataflow Framework: Analysis is now an interface.  Added AbstractAnalysis,
+ForwardAnalysis, ForwardTransferFunction, ForwardAnalysisImpl,
+BackwardAnalysis, BackwardTransferFunction, and BackwardAnalysisImpl.
+To adapt existing code:
+ * `extends Analysis<V, S, T>` => `extends ForwardAnalysisImpl<V, S, T>`
+ * `implements TransferFunction<V, S>` => `implements ForwardTransferFunction<V, S>`
+
+In AbstractQualifierPolymorphism, use AnnotationMirrors instead of sets of
+annotation mirrors.
+
+Renamed meta-annotation SuppressWarningsKeys to SuppressWarningsPrefix.
+Renamed SourceChecker#getSuppressWarningsKeys(...) to getSuppressWarningsPrefixes.
+Renamed SubtypingChecker#getSuppressWarningsKeys to getSuppressWarningsPrefixes.
+
+Added GenericAnnotatedTypeFactory#postAnalyze, changed signature of
+GenericAnnotatedTypeFactory#handleCFGViz, and removed CFAbstractAnalysis#visualizeCFG.
+
+Removed methods and classes marked deprecated in release 3.3.0 or earlier.
+
+**Closed issues:**
+#1362, #1727, #2632, #3249, #3296, #3300, #3356, #3357, #3358, #3359, #3380.
+
+
+Version 3.4.1 (June 1, 2020)
+----------------------------
+
+-Ainfer now takes an argument:
+ * -Ainfer=jaifs uses .jaif files to store the results of whole-program inference.
+ * -Ainfer=stubs uses .astub files to store the results of whole-program inference.
+ * -Ainfer is deprecated but is the same as -Ainfer=jaifs, for backwards compatibility.
+
+New command-line option:
+  -AmergeStubsWithSource If both a stub file and a source file are available, use both.
+
+**Closed issues:**
+#2893, #3021, #3128, #3160, #3232, #3277, #3285, #3289, #3295, #3302, #3305,
+#3307, #3310, #3316, #3318, #3329.
+
+
+Version 3.4.0 (May 3, 2020)
+---------------------------
+
+The annotated jdk8.jar is no longer used.  You should remove any occurrence of
+  -Xbootclasspath/p:.../jdk8.jar
+from your build scripts.  Annotations for JDK 8 are included in checker.jar.
+
+The Returns Receiver Checker enables documenting and checking that a method
+returns its receiver (i.e., the `this` parameter).
+
+**Closed issues:**
+#3267, #3263, #3217, #3212, #3201, #3111, #3010, #2943, #2930.
+
+
+Version 3.3.0 (April 1, 2020)
+-----------------------------
+
+**User-visible changes:**
+
+New command-line options:
+  -Alint=trustArrayLenZero trust @ArrayLen(0) annotations when determining
+  the type of Collections.toArray.
+
+Renamings:
+  -AuseDefaultsForUncheckedCode to -AuseConservativeDefaultsForUncheckedCode
+    The old name works temporarily but will be removed in a future release.
+
+For collection methods with `Object` formal parameter type, such as
+contains, indexOf, and remove, the annotated JDK now forbids null as an
+argument.  To make the Nullness Checker permit null, pass
+`-Astubs=collection-object-parameters-may-be-null.astub`.
+
+The argument to @SuppressWarnings can be a substring of a message key that
+extends at each end to a period or an end of the key.  (Previously, any
+substring worked, including the empty string which suppressed all warnings.
+Use "all" to suppress all warnings.)
+
+All postcondition annotations are repeatable (e.g., `@EnsuresNonNull`,
+`@EnsuresNonNullIf`, ...).
+
+Renamed wrapper annotations (which users should not write):
+ * `@DefaultQualifiers` => `@DefaultQualifier.List`
+ * `@EnsuresQualifiersIf` => `@EnsuresQualifierIf.List`
+ * `@EnsuresQualifiers` => `@EnsuresQualifier.List`
+ * `@RequiresQualifiers` => `@RequiresQualifier.List`
+
+**Implementation details:**
+
+Removed `@DefaultInUncheckedCodeFor` and
+`@DefaultQualifierInHierarchyInUncheckedCode`.
+
+Renamings:
+ * applyUncheckedCodeDefaults() to applyConservativeDefaults()
+ * useUncheckedCodeDefault() to useConservativeDefault()
+ * AnnotatedTypeReplacer to AnnotatedTypeCopierWithReplacement
+ * AnnotatedTypeMerger to AnnotatedTypeReplacer
+
+Deprecated the `framework.source.Result` class; use `DiagMessage` or
+`List<DiagMessage>` instead.  If you were creating a `Result` just to
+pass it to `report`, then call new methods `reportError` and
+`reportWarning` instead.
+
+AbstractTypeProcessor#typeProcessingOver() always gets called.
+
+**Closed issues:**
+#1307, #1881, #1929, #2432, #2793, #3040, #3046, #3050, #3056, #3083, #3124,
+#3126, #3129, #3132, #3139, #3149, #3150, #3167, #3189.
+
+
+Version 3.2.0 (March 2, 2020)
+-----------------------------
+
+@SuppressWarnings("initialization") suppresses only warnings whose key
+contains "initialization".  Previously, it suppressed all warnings issued
+by the Nullness Checker or the Initialization Checker.
+
+**Closed issues:**
+#2719, #3001, #3020, #3069, #3093, #3120.
+
+
+Version 3.1.1 (February 3, 2020)
+--------------------------------
+
+New command-line options:
+  -AassumeDeterministic Unsoundly assume that every method is deterministic
+  -AassumePure Unsoundly assume that every method is pure
+
+Renamed -Anocheckjdk to -ApermitMissingJdk.
+The old version still works, for backward compatibility.
+
+Renamed -Alint=forbidnonnullarraycomponents to
+-Alint=soundArrayCreationNullness.  The old version still works, for
+backward compatibility.
+
+Implementation details:
+ * Deprecated QualifierHierarchy#getTypeQualifiers.
+ * Deprecated Analysis#Analysis(ProcessingEnvironment) and Analysis#Analysis(T,
+   int, ProcessingEnvironment); use Analysis#Analysis(), Analysis#Analysis(int),
+   Analysis#Analysis(T), and Analysis#Analysis(T, int) instead.
+ * Renamed SourceChecker#getMessages to getMessagesProperties.
+ * Renamed one overload of SourceChecker.printMessages to printOrStoreMessage.
+
+**Closed issues:**
+#2181, #2975, #3018, #3022, #3032, #3036, #3037, #3038, #3041, #3049, #3055,
+#3076.
+
+
+Version 3.1.0 (January 3, 2020)
+-------------------------------
+
+Command-line option -AprintGitProperties prints information about the git
+repository from which the Checker Framework was compiled.
+
+**Implementation details:**
+ * Removed static cache in AnnotationUtils#areSameByClass and added
+   AnnotatedTypeFactory#areSameByClass that uses an instance cache.
+ * Removed static cache in AnnotationBuilder#fromName and #fromClass.
+ * ContractsUtils#getPreconditions takes an ExecutableElement as an argument.
+ * ContractsUtils#getContracts returns a Set.
+ * Moved ContractUtils.Contract to outer level.
+ * Renamed ConditionalPostcondition#annoResult to ConditionalPostcondition#resultValue.
+
+**Closed issues:**
+#2867, #2897, #2972.
+
+
+Version 3.0.1 (December 2, 2019)
+--------------------------------
+
+New command-line option for the Constant Value Checker
+`-AnoNullStringsConcatenation` unsoundly assumes that every operand of a String
+concatenation is non-null.
+
+**Implementation details:**
+ * Moved AnnotatedTypes#hasTypeQualifierElementTypes to AnnotationUtils.
+ * Deprecated AnnotatedTypes#isTypeAnnotation and AnnotatedTypes#hasTypeQualifierElementTypes.
+
+**Closed issues:**
+#945, #1224, #2024, #2744, #2809, #2815, #2818, #2830, #2840, #2853, #2854,
+#2865, #2873, #2874, #2878, #2880, #2886, #2888, #2900, #2905, #2919, #2923.
+
+
+Version 3.0.0 (November 1, 2019)
+--------------------------------
+
+The Checker Framework works on both JDK 8 and JDK 11.
+ * Type annotations for JDK 8 remain in jdk8.jar.
+ * Type annotations for JDK 11 appear in stub files in checker.jar.
+
+Removed the @PolyAll annotation.
+
+**Implementation details:**
+ * Removed all previously deprecated methods.
+ * AnnotatedTypeFactory#getFnInterfaceFromTree now returns an AnnotatedExecutableType.
+ * AnnotationUtils#areSame and #areSameByName now only accept non-null
+   AnnotationMirrors
+
+**Closed issues:**
+#1169, #1654, #2081, #2703, #2739, #2749, #2779, #2781, #2798, #2820, #2824,
+#2829, #2842, #2845, #2848.
+
+
+Version 2.11.1 (October 1, 2019)
+--------------------------------
+
+The manual links to the Object Construction Checker.
+
+**Closed issues:**
+#1635, #2718, #2767.
+
+
+Version 2.11.0 (August 30, 2019)
+--------------------------------
+
+The Checker Framework now uses the Java 9 javac API. The manual describes
+how to satisfy this dependency, in a way that works on a Java 8 JVM.
+Running the Checker Framework on a Java 9 JVM is not yet supported.
+
+
+Version 2.10.1 (August 22, 2019)
+--------------------------------
+
+**Closed issues:**
+#1152, #1614, #2031, #2482, #2543, #2587, #2678, #2686, #2690, #2712, #2717,
+#2713, #2721, #2725, #2729.
+
+
+Version 2.10.0 (August 1, 2019)
+-------------------------------
+
+Removed the NullnessRawnessChecker.  Use the NullnessChecker instead.
+
+**Closed issues:**
+#435, #939, #1430, #1687, #1771, #1902, #2173, #2345, #2470, #2534, #2606,
+#2613, #2619, #2633, #2638.
+
+
+Version 2.9.0 (July 3, 2019)
+----------------------------
+
+Renamed the Signedness Checker's @Constant annotation to @SignednessGlb.
+Introduced an alias, @SignedPositive, for use by programmers.
+
+Annotated the first argument of Opt.get and Opt.orElseThrow as @NonNull.
+
+Removed meta-annotation @ImplicitFor:
+ * Use the new meta-annotation @QualifierForLiteral to replace
+   @ImplicitFor(literals, stringpatterns).
+ * Use the meta-annotation @DefaultFor to replace @ImplicitFor(typeKinds,
+   types).
+ * Use the new meta-annotation @UpperBoundFor to specify a qualifier upper
+   bound for certain types.
+ * You can completely remove
+     @ImplicitFor(typeNames = Void.class, literals = LiteralKind.NULL)
+   on bottom qualifiers.
+     @DefaultFor(types = Void.class)
+   and
+     @QualifierForLiterals(literals = LiteralKind.NULL)
+   are added to the bottom qualifier by default.
+
+Add @DefaultQualifierOnUse and @NoDefaultQualifierOnUse type declaration annotations
+
+New/changed error message keys:
+ * initialization.static.fields.uninitialized for uninitialized static fields
+ * unary.increment and unary.decrement
+   replace some occurrences of compound.assignment
+
+**Implementation details:**
+ * Renamed QualifierPolymorphism#annotate methods to resolve
+ * Renamed ImplicitsTreeAnnotator to LiteralTreeAnnotator
+ * Renamed ImplicitsTypeAnnotator to DefaultForTypeAnnotator
+ * Removed TypeUseLocation.TYPE_DECLARATION
+ * Removed InheritedFromClassAnnotator, replace with DefaultQualifierForUseTypeAnnotator
+ * Rename TreeUtils.isSuperCall and TreeUtils.isThisCall to
+ isSuperConstructorCall and isThisConstructorCall
+
+**Closed issues:**
+#2247, #2391, #2409, #2434, #2451, #2457, #2468, #2484, #2485, #2493, #2505,
+#2536, #2537, #2540, #2541, #2564, #2565, #2585.
+
+
+Version 2.8.2 (June 3, 2019)
+----------------------------
+
+The Signature Checker supports a new type, @FqBinaryName.
+
+Added a template for a repository that you can use to write a custom checker.
+
+Linked to the Checker Framework Gradle plugin, which makes it easy to run
+a checker on a project that is built using the Gradle build tool.
+
+Implementation detail: deprecated TreeUtils.skipParens in favor of
+TreeUtils.withoutParens which has the same specification.
+
+**Closed issues:**
+#2291, #2406, #2469, #2477, #2479, #2480, #2494, #2499.
+
+
+Version 2.8.1 (May 1, 2019)
+---------------------------
+
+Moved text about the Purity Checker into its own chapter in the manual.
+
+**Closed issues:**
+#660, #2030, #2223, #2240, #2244, #2375, #2407, #2410, #2415, #2420, #2421,
+#2446, #2447, #2460, #2462.
+
+
+Version 2.8.0 (April 3, 2019)
+-----------------------------
+
+Support `androidx.annotation.RecentlyNonNull` and `RecentlyNullable` (as of
+2.6.0, but not previously documented).
+
+The following qualifiers are now repeatable:  `@DefaultQualifier`
+`@EnsuresQualifierIf` `@EnsuresQualifier` `@RequiresQualifier`.  Therefore,
+users generally do not need to write the following wrapper annotations:
+`@DefaultQualifiers` `@EnsuresQualifiersIf` `@EnsuresQualifiers`
+`@RequiresQualifiers`.
+
+New command-line option `-ArequirePrefixInWarningSuppressions` makes
+`@SuppressWarnings` recognize warning keys of the form
+"checkername:key.about.problem" but ignore warning keys of the form
+"key.about.problem" without the checker name as a prefix.
+
+New CONSTRUCTOR_RESULT enum constant in TypeUseLocation makes it possible to
+set default annotations for constructor results.
+
+Clarified the semantics of annotations on class and constructor declarations.
+See Section 25.5 "Annotations on classes and constructors" in the manual.
+
+Interface changes:
+ * Added protected methods to BaseTypeVisitor so that checkers can change the
+   checks for annotations on classes, constructor declarations, and constructor
+   invocations.
+ * Removed BaseTypeVisitor#checkAssignability and BaseTypeVisitor#isAssignable
+   methods.
+ * Renamed AnnotatedTypeFactory#getEnclosingMethod to
+   AnnotatedTypeFactory#getEnclosingElementForArtificialTree
+
+**Closed issues:**
+#2159, #2230, #2318, #2324, #2330, #2334, #2343, #2344, #2353, #2366, #2367,
+#2370, #2371, #2385.
+
+
+Version 2.7.0 (March 1, 2019)
+-----------------------------
+
+The manual links to the AWS crypto policy compliance checker, which enforces
+that no weak cipher algorithms are used with the Java crypto API.
+
+The Nullness Checker supports RxJava annotations
+io.reactivex.annotations.NonNull and io.reactivex.annotations.Nullable.
+
+The checker-qual artifact (jar file) contains an OSGi manifest.
+
+New TYPE_DECLARATION enum constant in TypeUseLocation makes it possible to
+(for example) set defaults annotations for class/interface definitions.
+
+Interface changes:
+ * Renamed the "value" element of the @HasSubsequence annotation to
+   "subsequence".
+ * Renamed @PolySignedness to @PolySigned.
+ * Renamed AnnotatedTypeFactory.ParameterizedMethodType to
+   ParameterizedExecutableType.
+
+Added missing checks regarding annotations on classes, constructor
+declarations, and constructor invocations.  You may see new warnings.
+
+**Closed issues:**
+#788, #1751, #2147, #2163, #2186, #2235, #2243, #2263, #2264, #2286, #2302,
+#2326, #2327.
+
+
+Version 2.6.0 (February 3, 2019)
+--------------------------------
+
+The manual includes a section about how to use Lombok and the Checker
+Framework simultaneously.
+
+Commons CSV has been added to the annotated libraries on Maven Central.
+
+Some error messages have been changed to improve comprehensibility,
+such as by adjusting wording or adding additional information.
+
+Relevant to type system implementers:
+Renamed method areSameIgnoringValues to areSameByName.
+
+**Closed issues:**
+#2008, #2166, #2185, #2187, #2221, #2224, #2229, #2234, #2248.
+Also fixed false negatives in handling of Map.get().
+
+
+Version 2.5.8 (December 5, 2018)
+--------------------------------
+
+The manual now links to the AWS KMS compliance checker, which enforces
+that calls to AWS KMS only generate 256-bit keys.
+
+**Closed issues:**
+#372, #1678, #2207, #2212, #2217.
+
+
+Version 2.5.7 (November 4, 2018)
+--------------------------------
+
+New @EnsuresKeyFor and @EnsuresKeyForIf method annotations permit
+specifying the postcondition that a method gives some value a @KeyFor type.
+
+The manual links to the Rx Thread & Effect Checker, which enforces
+UI Thread safety properties for stream-based Android applications.
+
+**Closed issues:**
+#1014, #2151, #2178, #2180, #2183, #2188, #2190, #2195, #2196, #2198, #2199.
+
+
+Version 2.5.6 (October 3, 2018)
+-------------------------------
+
+Introduce checker-qual-android artifact that is just like the checker-qual
+artifact, but the qualifiers have classfile retention.  This is useful for
+Android projects.
+
+Removed the code for the checker-compat-qual artifact.  It was only useful
+for Java 7, which the Checker Framework no longer supports.  The
+checker-compat-qual artifact remains available on Maven Central, with
+versions 2.5.5 and earlier.
+
+**Closed issues:**
+#2135, #2157, #2158, #2164, #2171.
+
+
+Version 2.5.5 (August 30, 2018)
+-------------------------------
+
+Implicit imports (deprecated in November 2014) are no longer supported.
+
+Renamed the testlib Maven artifact to framework-test.
+
+Removed command-line option -AprintErrorStack, which is now the default.
+Added -AnoPrintErrorStack to disable it (which should be rare).
+
+Replaced ErrorReporter class with BugInCF and UserError exceptions.
+
+**Closed issues:**
+#1999, #2008, #2023, #2029, #2074, #2088, #2098, #2099, #2102, #2107.
+
+
+Version 2.5.4 (August 1, 2018)
+------------------------------
+
+**Closed issues:**
+#2030, #2048, #2052, #2059, #2065, #2067, #2073, #2082.
+
+
+Version 2.5.3 (July 2, 2018)
+----------------------------
+
+**Closed issues:**
+#266, #1248, #1678, #2010, #2011, #2018, #2020, #2046, #2047, #2054.
+
+
+Version 2.5.2 (June 1, 2018)
+----------------------------
+
+In the Map Key Checker, null is now @UnknownKeyFor.  See the "Map Key Checker"
+chapter in the manual for more details.
+
+**Closed issues:**
+#370, #469, #1701, #1916, #1922, #1959, #1976, #1978, #1981, #1983, #1984, #1991, #1992.
+
+
+Version 2.5.1 (May 1, 2018)
+---------------------------
+
+Added a Maven artifact of the Checker Framework testing library, testlib.
+
+**Closed issues:**
+#849, #1739, #1838, #1847, #1890, #1901, #1911, #1912, #1913, #1934, #1936,
+#1941, #1942, #1945, #1946, #1948, #1949, #1952, #1953, #1956, #1958.
+
+
+Version 2.5.0 (April 2, 2018)
+-----------------------------
+
+Declaration annotations that are aliases for type annotations are now treated
+as if they apply to the top-level type.  See "Declaration annotations" section
+in the "Warnings" chapter in the manual for more details.
+
+Ended support for annotations in comments.  See "Migrating away from
+annotations in comments" section in the "Handling legacy code" chapter in the
+manual for instructions on how to remove annotations from comments.
+
+**Closed issues:**
+#515, #1667, #1739, #1776, #1819, #1863, #1864, #1865, #1866, #1867, #1870,
+#1876, #1879, #1882, #1898, #1903, #1905, #1906, #1910, #1914, #1915, #1920.
+
+
+Version 2.4.0 (March 1, 2018)
+-----------------------------
+
+Added the Index Checker, which eliminates ArrayIndexOutOfBoundsException.
+
+Added the Optional Checker, which verifies uses of Java 8's Optional class.
+
+Removed the Linear Checker, whose implementation was inconsistent with its
+documentation.
+
+Added a @QualifierArgument annotation to be used on pre- and postcondition
+  annotations created by @PreconditionAnnotation, @PostconditionAnnotation,
+  and @ConditionalPostconditionAnnotation. This allows qualifiers with
+  arguments to be used in pre- and postconditions.
+
+Added new type @InternalFormForNonArray to the Signature Checker
+
+Moved annotated libraries from checker/lib/*.jar to the Maven Central Repository:
+https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.checkerframework.annotatedlib%22
+
+Moved the Javadoc stub file from checker/lib/javadoc.astub to
+checker/resources/javadoc.astub.
+
+Simplified the instructions for running the Checker Framework with Gradle.
+
+The Checker Framework Eclipse plugin is no longer released nor supported.
+
+**Closed issues:**
+#65, #66, #100, #108, #175, #184, #190, #194, #209, #239, #260, #270, #274,
+#293, #302, #303, #306, #321, #325, #341, #356, #360, #361, #371, #383, #385,
+#391, #397, #398, #410, #423, #424, #431, #430, #432, #548, #1131, #1148,
+#1213, #1455, #1504, #1642, #1685, #1770, #1796, #1797, #1801, #1809, #1810,
+#1815, #1817, #1818, #1823, #1831, #1837, #1839, #1850, #1851, #1852, #1861.
+
+
+Version 2.3.2 (February 1, 2018)
+--------------------------------
+
+**Closed issues:**
+#946, #1133, #1232, #1319, #1625, #1633, #1696, #1709, #1712, #1734, #1738,
+#1749, #1754, #1760, #1761, #1768, #1769, #1781.
+
+
+Version 2.3.1 (January 2, 2018)
+-------------------------------
+
+**Closed issues:**
+#1695, #1696, #1697, #1698, #1705, #1708, #1711, #1714, #1715, #1724.
+
+
+Version 2.3.0 (December 1, 2017)
+--------------------------------
+
+Removed the deprecated @LazyNonNull type qualifier.
+Deprecated most methods in InternalUtils and moved them to either
+TreeUtils or TypesUtils. Adapted a few method names and parameter
+orders for consistency.
+
+**Closed issues:**
+#951, #1356, #1495, #1602, #1605, #1623, #1628, #1636, #1641, #1653, #1655,
+#1664, #1665, #1681, #1684, #1688, #1690.
+
+
+Version 2.2.2 (November 2, 2017)
+--------------------------------
+
+The Interning Checker supports a new annotation, @InternedDistinct, which
+indicates that the value is not equals() to any other value.
+
+An annotated version of the Commons IO library appears in checker/lib/ .
+
+Closed issue #1586, which required re-opening issues 293 and 341 until
+proper fixes for those are implemented.
+
+**Closed issues:**
+#1386, #1389, #1423, #1520, #1529, #1530, #1531, #1546, #1553, #1555, #1565,
+#1570, #1579, #1580, #1582, #1585, #1586, #1587, #1598, #1609, #1615, #1617.
+
+
+Version 2.2.1 (September 29, 2017)
+----------------------------------
+
+Deprecated some methods in AnnotatedTypeMirror and AnnotationUtils, to
+be removed after the 2.2.1 release.
+
+The qualifiers and utility classes in checker-qual.jar are compiled to Java 8
+byte code. A new jar, checker-qual7.jar, includes the qualifiers and utility
+classes compiled to Java 7 byte code.
+
+**Closed issues:**
+#724, #1431, #1442, #1459, #1464, #1482, #1496, #1499, #1500, #1506, #1507,
+#1510, #1512, #1522, #1526, #1528, #1532, #1535, #1542, #1543.
+
+
+Version 2.2.0 (September 5, 2017)
+---------------------------------
+
+A Java 8 JVM is required to run the Checker Framework.
+You can still typecheck and compile Java 7 (or earlier) code.
+With the "-target 7" flag, the resulting .class files still run with JDK 7.
+
+The stub file format has changed to be more similar to regular Java syntax.
+Most notably, receiver annotations are written using standard Java 8 syntax
+(a special first formal paramter named "this") and inner classes are written
+using standard Java syntax (rather than at the top level using a name that
+contains "$". You need to update your stub files to conform to the new syntax.
+
+**Closed issues:**
+#220, #293, #297, #341, #375, #407, #536, #571, #798, #867, #1180, #1214, #1218,
+#1371, #1411, #1427, #1428, #1435, #1438, #1450, #1456, #1460, #1466, #1473,
+#1474.
+
+
+Version 2.1.14 (3 August 2017)
+------------------------------
+
+Nullness Checker change to annotated JDK:  The type argument to the Class,
+Constructor, and Optional classes may now be annotated as @Nullable or
+@NonNull.  The nullness of the type argument doesn't matter, but this
+enables easier integration with generic clients.
+
+Many crashes and false positives associated with uninferred method type
+arguments have been correct. By default, uninferred method type arguments,
+which can happen with Java 8 style target type contexts, are silently ignored.
+Use the option -AconservativeUninferredTypeArguments to see warnings about
+method calls where the Checker Framework fails to infer type arguments.
+
+**Closed issues:**
+#753, #804, #961, #1032, #1062, #1066, #1098, #1209, #1280, #1316, #1329, #1355,
+#1365, #1366, #1367, #1377, #1379, #1382, #1384, #1397, #1398, #1399, #1402,
+#1404, #1406, #1407.
+
+
+Version 2.1.13 (3 July 2017)
+----------------------------
+
+Verified that the Checker Framework builds from source on Windows Subsystem
+for Linux, on Windows 10 Creators Edition.
+
+The manual explains how to configure Android projects that use Android Studio
+3.0 and Android Gradle Plugin 3.0.0, which support type annotations.
+
+**Closed issues:**
+#146, #1264, #1275, #1290, #1303, #1308, #1310, #1312, #1313, #1315, #1323,
+#1324, #1331, #1332, #1333, #1334, #1347, #1357, #1372.
+
+
+Version 2.1.12 (1 June 2017)
+----------------------------
+
+The manual links to Glacier, a class immutability checker.
+
+The stubparser license has been updated.  You can now use stubparser under
+either the LGPL or the Apache license, whichever you prefer.
+
+**Closed issues:**
+#254, #1201, #1229, #1236, #1239, #1240, #1257, #1265, #1270, #1271, #1272,
+#1274, #1288, #1291, #1299, #1304, #1305.
+
+
+Version 2.1.11 (1 May 2017)
+---------------------------
+
+The manual contains new FAQ (frequently asked questions) sections about
+false positive warnings and about inference for field types.
+
+**Closed issues:**
+#989, #1096, #1136, #1228.
+
+
+Version 2.1.10 (3 April 2017)
+-----------------------------
+
+The Constant Value Checker, which performs constant propagation, has been
+extended to perform interval analysis -- that is, it determines, for each
+expression, a statically-known lower and upper bound.  Use the new
+@IntRange annotation to express this.  Thanks to Jiasen (Jason) Xu for this
+feature.
+
+**Closed issues:**
+#134, #216, #227, #307, #334, #437, #445, #718, #1044, #1045, #1051, #1052,
+#1054, #1055, #1059, #1077, #1087, #1102, #1108, #1110, #1111, #1120, #1124,
+#1127, #1132.
+
+
+Version 2.1.9 (1 March 2017)
+----------------------------
+
+By default, uninferred method type arguments, which can happen with Java 8
+style target type contexts, are silently ignored, removing many false
+positives.  The new option -AconservativeUninferredTypeArguments can be used to
+get the conservative behavior.
+
+**Closed issues:**
+#1006, #1011, #1015, #1027, #1035, #1036, #1037, #1039, #1043, #1046, #1049,
+#1053, #1072, #1084.
+
+
+Version 2.1.8 (20 January 2017)
+-------------------------------
+
+The Checker Framework webpage has moved to https://checkerframework.org/.
+Old URLs should redirect to the new one, but please update your links
+and let us know if any old links are broken rather than redirecting.
+
+The documentation has been reorganized in the Checker Framework repository.
+The manual, tutorial, and webpages now appear under checker-framework/docs/.
+
+**Closed issues:**
+#770, #1003, #1012.
+
+
+Version 2.1.7 (3 January 2017)
+------------------------------
+
+Manual improvements:
+ * Added a link to jOOQ's SQL checker.
+ * Documented the `-AprintVerboseGenerics` command-line option.
+ * Better explanation of relationship between Fake Enum and Subtyping Checkers.
+
+**Closed issues:**
+#154, #322, #402, #404, #433, #531, #578, #720, #795, #916, #953, #973, #974,
+#975, #976, #980, #988, #1000.
+
+
+Version 2.1.6 (1 December 2016)
+-------------------------------
+
+**Closed issues:**
+#412, #475.
+
+
+Version 2.1.5 (2 November 2016)
+-------------------------------
+
+The new class org.checkerframework.checker.nullness.Opt provides every
+method in Java 8's java.util.Optional class, but written for possibly-null
+references rather than for the Optional type.  This can shorten code that
+manipulates possibly-null references.
+
+In bytecode, type variable upper bounds of type Object may or may not have
+been explicitly written.  The Checker Framework now assumes they were not
+written explicitly in source code and defaults them as implicit upper bounds.
+
+The manual describes how to run a checker within the NetBeans IDE.
+
+The manual describes two approaches to creating a type alias or typedef.
+
+**Closed issues:**
+#643, #775, #887, #906, #941.
+
+
+Version 2.1.4 (3 October 2016)
+------------------------------
+
+**Closed issues:**
+#885, #886, #919.
+
+
+Version 2.1.3 (16 September 2016)
+---------------------------------
+
+**Closed issues:**
+#122, #488, #495, #580, #618, #647, #713, #764, #818, #872, #893, #894, #901,
+#902, #903, #905, #913.
+
+
+Version 2.1.2 (1 September 2016)
+--------------------------------
+
+**Closed issues:**
+#182, #367, #712, #811, #846, #857, #858, #863, #870, #871, #878, #883, #888.
+
+
+Version 2.1.1 (1 August 2016)
+-----------------------------
+
+The codebase conforms to a consistent coding style, which is enforced by
+a git pre-commit hook.
+
+AnnotatedTypeFactory#createSupportedTypeQualifiers() must now return a mutable
+list.  Checkers that override this method will have to be changed.
+
+**Closed issues:**
+#384, #590, #681, #790, #805, #809, #810, #820, #824, #826, #829, #838, #845,
+#850, #856.
+
+
+Version 2.1.0 (1 July 2016)
+---------------------------
+
+The new Signedness Checker prevents mixing of unsigned and signed
+values and prevents meaningless operations on unsigned values.
+
+The Lock Checker expresses the annotated variable as `<self>`;
+previously it used `itself`, which may conflict with an identifier.
+
+**Closed issues:**
+#166, #273, #358, #408, #471, #484, #594, #625, #692, #700, #701, #711, #717,
+#752, #756, #759, #763, #767, #779, #783, #794, #807, #808.
+
+
+Version 2.0.1 (1 June 2016)
+---------------------------
+
+We renamed method annotateImplicit to addComputedTypeAnnotations.  If you
+have implemented a checker, you need to change occurrences of
+annotateImplicit to addComputedTypeAnnotations.
+
+The Checker Framework (checker.jar) is now placed on the processorpath
+during compilation.  Previously, it was placed on the classpath.  The
+qualifiers (checker-qual.jar) remain on the classpath.  This change should
+reduce conflicts between your code and the Checker Framework.  If your code
+depends on classes in the Checker Framework, then you should add those
+classes to the classpath when you run the compiler.
+
+**Closed issues:**
+#171, #250, #291, #523, #577, #672, #680, #688, #689, #690, #691, #695, #696,
+#698, #702, #704, #705, #706, #707, #720, #721, #723, #728, #736, #738, #740.
+
+
+Version 2.0.0 (2 May 2016)
+--------------------------
+
+Inference:
+
+ * The infer-and-annotate.sh script infers annotations and inserts them in
+   your source code.  This can reduce the burden of writing annotations and
+   let you get started using a type system more quickly.  See the
+   "Whole-program inference" section in the manual for details.
+
+Type systems:
+
+ * The Lock Checker has been replaced by a new implementation that provides
+   a stronger guarantee.  The old Lock Checker prevented two threads from
+   simultaneously using a given variable, but race conditions were still
+   possible due to aliases.  The new Lock Checker prevents two threads from
+   simultaneously dereferencing a given value, and thus prevents race
+   conditions.  For details, see the "Lock Checker" chapter in the manual,
+   which has been rewritten to describe the new semantics.
+
+ * The top type qualifier for the Signature String type system has been
+   renamed from @UnannotatedString to @SignatureUnknown.  You shouldn't
+   ever write this annotation, but if you perform separate compilation (for
+   instance, if you do type-checking with the Signature String Checker
+   against a library that is annotated with Signature String annotations),
+   then you need to re-compile the library.
+
+ * The IGJ, OIGJ, and Javari Checkers are no longer distributed with the
+   Checker Framework.  If you wish to use them, install version 1.9.13 of
+   the Checker Framework.  The implementations have been removed because
+   they were not being maintained.  The type systems are valuable, but the
+   type-checkers should be rewritten from scratch.
+
+Documentation improvements:
+
+ * New manual section "Tips for creating a checker" shows how to break down
+   the implementation of a type system into small, manageable pieces.
+
+ * Improved instructions for using Maven and Gradle, including for Android
+   code.
+
+Tool changes:
+
+ * The Checker Framework Live Demo webpage lets you try the Checker
+   Framework without installing it:  http://eisop.uwaterloo.ca/live/
+
+ * New command-line arguments -Acfgviz and -Averbosecfg enable better
+   debugging of the control-flow-graph generation step of type-checking.
+
+ * New command-line argument -Ainfer is used by the infer-and-annotate.sh
+   script that performs type inference.
+
+**Closed issues:**
+#69, #86, #199, #299, #329, #421, #428, #557, #564, #573, #579, #665, #668, #669,
+#670, #671.
+
+
+Version 1.9.13 (1 April 2016)
+-----------------------------
+
+Documentation:
+ * Clarified Maven documentation about use of annotations in comments.
+ * Added FAQ about annotating fully-qualified type names.
+
+**Closed issues:**
+#438, #572, #579, #607, #624, #631.
+
+
+Version 1.9.12 (1 March 2016)
+-----------------------------
+
+The Checker Framework distribution contains annotated versions
+of libraries in directory checker-framework/checker/lib/.
+During type-checking, you should put these versions first on your classpath,
+to obtain more precise type-checking with fewer false positive warnings.
+
+tools.jar is no longer required to be on the classpath when using
+checker-qual.jar
+
+The Signature String Checker supports two new string representations of a
+Java type: @InternalForm and @ClassGetSimpleName.
+
+The manual documents how to run a pluggable type-checker in IntelliJ IDEA.
+
+The instructions on how to run a type-checker in Gradle have been updated to
+use the artifacts in Maven Central. Examples using the instructions have been
+added under checker-framework/docs/examples/GradleExamples/.
+
+Renamed enum DefaultLocation to TypeUseLocation.
+
+**Closed issues:**
+#130, #263, #345, #458, #559, #559, #574, #582, #596.
+
+
+Version 1.9.11 (1 February 2016)
+--------------------------------
+
+Renamed and merged -AuseSafeDefaultsForUnannotatedSourceCode and
+-AsafeDefaultsForUnannotatedBytecode command-line options to
+-AuseDefaultsForUncheckedCode that takes arguments source and bytecode.
+
+For type-system developers:
+
+* The previously deprecated
+  org.checkerframework.framework.qual.TypeQualifier{s} annotations
+  were removed.
+* Every type system uses the CLIMB-to-top defaulting scheme, unless it
+  explicitly specifies a different one.  Previously a type system needed
+  to explicitly request CLIMB-to-top, but now it is the default.
+
+**Closed issues:**
+#524, #563, #568.
+
+
+Version 1.9.10 (4 January 2016)
+-------------------------------
+
+The Checker Framework distribution files now contain a version number:
+for example, checker-framework-1.9.9.zip rather than checker-framework.zip.
+
+The Nullness Checker supports the org.eclipse.jgit.annotations.Nullable and
+NonNull annotations.
+
+Buildfiles do less unnecessary recomputation.
+
+Documentation:
+ * Documented how to initialize circular data structures in the
+   Initialization type system.
+ * Linked to David Bürgin's Nullness Checker tutorial at
+   https://github.com/glts/safer-spring-petclinic/wiki
+ * Acknowledged more contributors in the manual.
+
+For type-system developers:
+ * The org.checkerframework.framework.qual.TypeQualifier{s} annotations are
+   now deprecated.  To indicate which annotations a checker supports, see
+   https://checkerframework.org/manual/#creating-indicating-supported-annotations .
+   Support for TypeQualifier{s} will be removed in the next release.
+ * Renamed
+   org.checkerframework.framework.qual.Default{,Qualifier}ForUnannotatedCode to
+   DefaultInUncheckedCodeFor and DefaultQualifierInHierarchyInUncheckedCode.
+
+**Closed issues:**
+#169, #363, #448, #478, #496, #516, #529.
+
+
+Version 1.9.9 (1 December 2015)
+-------------------------------
+
+Fixed issues:  #511, #513, #514, #455, #527.
+
+Removed the javac_maven script and batch file,
+which had been previously deprecated.
+
+
+Version 1.9.8 (9 November 2015)
+-------------------------------
+
+Field initialization warnings can now be suppressed for a single field at a
+time, by placing @SuppressWarnings("initialization") on the field declaration.
+
+Updated Maven instructions to no longer require a script.
+Added an example of how to use the instructions under
+docs/examples/MavenExample.
+
+The javac_maven script (and batch file) are deprecated and will be
+removed as of December 2015.
+
+Fixed issues:  #487, #500, #502.
+
+
+Version 1.9.7 (24 October 2015)
+-------------------------------
+
+Fixed issues:  #291, #474.
+
+
+Version 1.9.6 (8 October 2015)
+------------------------------
+
+Fixed issue:  #460.
+
+
+Version 1.9.5 (1 September 2015)
+--------------------------------
+
+Test Framework Updates:
+  * The test framework has been refactored to improve extensibility.
+  * Tests that previously extended ParameterizedCheckerTest or
+    CheckerTest should extend either CheckerFrameworkTest or nothing.
+  * If a test used methods that were previously found on
+    CheckerTest, you may find them in TestUtilities.
+
+Fixed issues:  #438, #457, #459.
+
+
+Version 1.9.4 (4 August 2015)
+-----------------------------
+
+Documented the notion of a compound checker, which depends on other checkers
+  and automatically runs them.
+
+Renamed -AuseConservativeDefaultsForUnannotatedSourceCode command-line
+  option to -AuseSafeDefaultsForUnannotatedSourceCode
+
+Moved the Checker Framework version control repository from Google Code to
+GitHub, and from the Mercurial version control system to Git.  If you have
+cloned the old repository, then discard your old clone and create a new one
+using this command:
+  git clone https://github.com/typetools/checker-framework.git
+
+Fixed issues:  #427, #429, #434, #442, #450.
+
+
+Version 1.9.3 (1 July 2015)
+---------------------------
+
+New command-line options:
+ * -AsafeDefaultsForUnannotatedBytecode causes a checker to use conservative
+   defaults for .class files that were compiled without running the given
+   checker.  Without this option, type-checking is unsound (that is, there
+   might be errors at run time even though the checker issues no warnings).
+ * -AuseConservativeDefaultsForUnannotatedSourceCode uses conservative
+   annotations for unannotated type uses.  Use this when compiling a library in
+   which some but not all classes are annotated.
+
+Various bug fixes and documentation improvements.
+
+Fixed issues: #436.
+
+
+Version 1.9.2 (1 June 2015)
+---------------------------
+
+Internationalization Format String Checker:
+This new type-checker prevents use of incorrect internationalization
+format strings.
+
+Fixed issues: #434.
+
+
+Version 1.9.1 (1 May 2015)
+--------------------------
+
+New FAQ entry:
+  "How does the Checker Framework compare with Eclipse's null analysis?"
+
+
+Version 1.9.0 (17 April 2015)
+-----------------------------
+
+Bug fixes for generics, especially type parameters:
+   * Manual chapter 21 "Generics and polymorphism" has been expanded,
+     and it gives more information on annotating type parameters.
+   * The qualifier on a type parameter (e.g. <@HERE T> ) only applies
+     to the lower bound of that type parameter.  Previously it also
+     applied to the upper bound.
+   * Unannotated, unbounded wildcards are now qualified with the
+     annotations of the type parameter to which they are an argument.
+     See the new manual section 23.3.4 for more details.
+   * Warning "bound" is issued if the lower bound of
+     a type parameter or wildcard is a supertype of its upper bound,
+     e.g.  <@Nullable T extends @NonNull Object>
+   * Method type argument inference has been improved. Fewer warnings
+     should be issued when method invocations omit type arguments.
+   * Added command-line option -AprintVerboseGenerics to print more
+     information about type parameters and wildcards when they appear
+     in warning messages.
+
+Reflection resolution:
+If you supply the -AresolveReflection command-line option, the Checker
+Framework attempts to resolve reflection.  This reduces the number of
+false positive warnings caused by reflection.
+
+The documentation for the Map Key Checker has been moved into its own
+chapter in the manual.
+
+Fixed issues: #221, #241, #313, #314, #328, #335, #337, #338, #339, #355, #369,
+              #376, #378, #386, #388, #389, #393, #403, #404, #413, #414, #415,
+              #417, #418, #420, #421, #422, #426.
+
+
+Version 1.8.11 (2 March 2015)
+-----------------------------
+
+Fixed issues: #396, #400, #401.
+
+
+Version 1.8.10 (30 January 2015)
+--------------------------------
+
+Fixed issues: #37, #127, #350, #364, #365, #387, #392, #395.
+
+
+Version 1.8.9 (19 December 2014)
+--------------------------------
+
+Aliasing Checker:
+This new type-checker ensures that an expression has no aliases.
+
+Fixed issues: #362, #380, #382.
+
+
+Version 1.8.8 (26 November 2014)
+--------------------------------
+
+@SuppressWarnings("all") suppresses all Checker Framework warnings.
+
+Implicit imports are deprecated, including the jsr308_imports environment
+variable and the -jsr308_imports ... and -Djsr308.imports=... command-line
+options.
+
+For checkers bundled with the Checker Framework, package names may now
+be omitted when running from the command line.
+E.g.
+    javac -processor NullnessChecker MyFile.java
+
+The Nullness checker supports Android annotations
+android.support.annotation.NonNull and android.support.annotation.Nullable.
+
+Fixed issues: #366, #379.
+
+
+Version 1.8.7 (30 October 2014)
+-------------------------------
+
+Fix performance regression introduced in release 1.8.6.
+
+Nullness Checker:
+  * Updated Nullness annotations in the annotated JDK.
+    See issues: #336, #340, #374.
+  * String concatenations with null literals are now @NonNull
+    rather than @Nullable.  See issue #357.
+
+Fixed issues:  #200, #300, #332, #336, #340, #357, #359, #373, #374.
+
+
+Version 1.8.6 (25 September 2014)
+---------------------------------
+
+Method Reference and Lambda Expression Support:
+The Checker Framework now supports type-checking method references
+and lambda expressions to ensure they are congruent with the
+functional interface they are assigned to. The bodies of lambda expressions
+are also now type-checked similarly to regular method bodies.
+
+Dataflow:
+ * Handling of the following language features has been improved:
+   boxed Booleans, finally blocks, switch statements, type casts, enhanced
+   for loops
+ * Performance improvements
+
+Annotations:
+The checker-compat-qual.jar is now included with the Checker Framework
+release.  It can also be found in Maven Central at the coordinates:
+org.checkerframework:checker-compat-qual
+Annotations in checker-compat-qual.jar do not require Java 8 but
+can only be placed in annotation locations valid in Java 7.
+
+
+Version 1.8.5 (29 August 2014)
+------------------------------
+
+Eclipse Plugin:
+All checkers in the Checker Framework manual now appear in the
+Eclipse plugin by default.  Users no longer have to include
+checker.jar on their classpath to run any of the built-in checkers.
+
+Improved Java 7 compatibility and introduced Java 7 compliant
+annotations for the Nullness Checker.  Please see the section on
+"Class-file compatibility with Java 7" in the manual for more details.
+
+Fixed issue #347.
+
+
+Version 1.8.4 (1 August 2014)
+-----------------------------
+
+The new Constant Value Checker is a constant propagation analysis:  it
+determines which variable values can be known at compile time.
+
+Overriding methods now inherit declaration annotations from methods they
+override, if the declaration annotation is meta-annotate with
+@InheritedAnnotation.  In particular, the purity annotations @SideEffectFree,
+@Deterministic, and @Pure are inherited.
+
+Command-line options:
+ * Renamed the -AenablePurity command-line flag to -AcheckPurityAnnotations.
+ * Added a command-line option -AoutputArgsToFile to output all command-line
+   options passed to the compiler to a file.  This is especially useful when
+   debugging Maven compilation.
+
+Annotations:
+These changes are relevant only to people who wish to use pluggable
+type-checking with a standard Java 7 toolset.  (If you are not having
+trouble with your Java 7 JVM, then you don't care about them.)
+ * Made clean-room reimplementations of nullness-related annotations
+   compatible with Java 7 JVMs, by removing TYPE_USE as a target.
+ * Added a new set of Java 7 compatibility annotations for the Nullness Checker
+   in the org.checkerframework.checker.nullness.compatqual package. These
+   annotations do not require Java 8 but can only be placed in annotation
+   locations valid in Java 7.
+
+Java 8 support:
+The Checker Framework no longer crashes when type-checking code with lambda
+expressions, but it does issue a lambda.unsupported warning when
+type-checking code containing lambda expressions.  Full support for
+type-checking lambda expressions will appear in a future release.
+
+Fixed issue #343.
+
+
+Version 1.8.3 (1 July 2014)
+---------------------------
+
+Updated the Initialization Checker section in the manual with
+a new introduction paragraph.
+
+Removed the Maven plugin section from the manual as the plugin is
+no longer maintained and the final release was on June 2, #2014.
+The javac_maven script (and batch file) are available to use
+the Checker Framework from Maven.
+
+Fixed issue #331.
+
+
+Version 1.8.2 (2 Jun 2014)
+--------------------------
+
+Converted from using rt.jar to ct.sym for creating the annotated jdk.
+Using the annotated jdk on the bootclasspath of a VM will cause the
+vm to crash immediately.
+
+The Lock Checker has been rewritten to support dataflow analysis.
+It can now understand conditional expressions, for example, and
+knows that "lock" is held in the body of statements like
+"if (lock.tryLock()) { ... }"
+The Lock Checker chapter in the manual has been updated accordingly
+and describes the new Lock Checker features in detail.
+
+Provided a javac_maven script (and batch file) to make it simpler
+to use the Checker Framework from Maven.  The Maven plug-in is deprecated
+and will be removed as of July 1, 2014. Added an explanation of how
+to use the script in the Maven section of the manual.
+
+The Checker Framework installation instructions in the manual have
+been updated.
+
+Fixed issues: #312, #315, #316, #318, #319, #324, #326, #327.
+
+
+Version 1.8.1 (1 May 2014)
+--------------------------
+
+Support to directly use the Java 8 javac in addition to jsr308-langtools.
+Added docs/examples directory to checker-framework.zip.
+New section in the manual describing the contents of checker-framework.zip.
+
+Fixed issues: #204, #304, #320.
+
+
+Version 1.8.0 (2 April 2014)
+----------------------------
+
+Added the GUI Effect Checker, which prevents "invalid thread access" errors
+when a background thread in a GUI attempts to access the UI.
+
+Changed the Java package of all type-checkers and qualifiers.  The package
+"checkers" has been renamed to "org.checkerframeork.checker".  This
+requires you to change your import statements, such as from
+  import checkers.nullness.quals.*;
+to
+  import org.checkerframework.checker.nullness.qual.*;
+It also requires you to change command-line invocations of javac, such as from
+  javac -processor checkers.nullness.NullnessChecker ...
+to
+  javac -processor org.checkerframework.checker.nullness.NullnessChecker ...
+
+Restructured the Checker Framework project and package layout,
+using the org.checkerframework prefix.
+
+
+Version 1.7.5 (5 March 2014)
+----------------------------
+
+Minor improvements to documentation and demos.
+Support a few new units in the UnitsChecker.
+
+
+Version 1.7.4 (19 February 2014)
+--------------------------------
+
+Error messages now display the error key that can be used in
+SuppressWarnings annotations. Use -AshowSuppressWarningKeys to
+show additional keys.
+
+Defaulted type qualifiers are now stored in the Element and written
+to the final bytecode.
+
+Reduce special treatment of checkers.quals.Unqualified.
+
+Fixed issues: #170, #240, #265, #281.
+
+
+Version 1.7.3 (4 February 2014)
+-------------------------------
+
+Fixes for Issues #210, #253, #280, #288.
+
+Manual:
+   Improved discussion of checker guarantees.
+
+Maven Plugin:
+   Added option useJavacOutput to display exact compiler output.
+
+Eclipse Plugin:
+   Added the Format String Checker to the list of built-in checkers.
+
+
+Version 1.7.2 (2 January 2014)
+------------------------------
+
+Fixed issues: #289, #292, #295, #296, #298.
+
+
+Version 1.7.1 (9 December 2013)
+-------------------------------
+
+Fixes for Issues #141, #145, #257, #261, #269, #267, #275, #278, #282, #283, #284, #285.
+
+**Implementation details:**
+
+Renamed AbstractBasicAnnotatedTypeFactory to GenericAnnotatedTypeFactory
+
+
+Version 1.7.0 (23 October 2013)
+-------------------------------
+
+Format String Checker:
+  This new type-checker ensures that format methods, such as
+  System.out.printf, are invoked with correct arguments.
+
+Renamed the Basic Checker to the Subtyping Checker.
+
+Reimplemented the dataflow analysis that performs flow-sensitive type
+  refinement.  This fixes many bugs, improves precision, and adds features.
+  Many more Java expressions can be written as annotation arguments.
+
+Initialization Checker:
+  This new abstract type-checker verifies initialization properties.  It
+  needs to be combined with another type system whose proper initialization
+  should be checked.  This is the new default initialzation checker for the
+  Nullness Checker.  It is based on the "Freedom Before Commitment" approach.
+
+Renamed method annotations used by the Nullness Checker:
+  @AssertNonNullAfter => @EnsuresNonNull
+  @NonNullOnEntry => @RequiresNonNull
+  @AssertNonNullIfTrue(...) => @IfMethodReturnsFalseEnsuresNonNull
+  @AssertNonNullIfFalse(...) => @IfMethodReturnsFalseEnsuresNonNull
+  @LazyNonNull => @MonotonicNonNull
+  @AssertParametersNonNull => [no replacement]
+Removed annotations used by the Nullness Checker:
+  @AssertParametersNonNull
+Renamed type annotations used by the Initialization Checker:
+  @NonRaw => @Initialized
+  @Raw => @UnknownInitialization
+  new annotation @UnderInitialization
+The old Initialization Checker (that uses @Raw and @NonRaw) can be invoked
+  by invoking the NullnessRawnessChecker rather than the NullnessChecker.
+
+Purity (side effect) analysis uses new annotations @SideEffectFree,
+  @Deterministic, and @TerminatesExecution; @Pure means both @SideEffectFree
+  and @Deterministic.
+
+Pre- and postconditions about type qualifiers are available for any type system
+  through @RequiresQualifier, @EnsuresQualifier and @EnsuresQualifierIf.  The
+  contract annotations for the Nullness Checker (e.g. @EnsuresNonNull) are now
+  only a special case of these general purpose annotations.
+  The meta-annotations @PreconditionAnnotation, @PostconditionAnnotation, and
+  @ConditionalPostconditionAnnotation can be used to create more special-case
+  annotations for other type systems.
+
+Renamed assertion comment string used by all checkers:
+  @SuppressWarnings => @AssumeAssertion
+
+To use an assert statement to suppress warnings, the assertion message must
+  include the string "@AssumeAssertion(warningkey)".  Previously, just the
+  warning key sufficed, but the string @SuppressWarnings(warningkey) was
+  recommended.
+
+New command-line options:
+  -AonlyDefs and -AonlyUses complement existing -AskipDefs and -AskipUses
+  -AsuppressWarnings Suppress warnings matching the given key
+  -AassumeSideEffectFree Unsoundly assume that every method is side-effect-free
+  -AignoreRawTypeArguments Ignore subtype tests for type arguments that
+    were inferred for a raw type
+  -AenablePurity Check the bodies of methods marked as pure
+    (@SideEffectFree or @Deterministic)
+  -AsuggestPureMethods Suggest methods that could be marked as pure
+  -AassumeAssertionsAreEnabled, -AassumeAssertionsAreDisabled Whether to
+    assume that assertions are enabled or disabled
+  -AconcurrentSemantics Whether to assume concurrent semantics
+  -Anocheckjdk Don't err if no annotated JDK can be found
+  -Aflowdotdir Create an image of the control flow graph
+  -AinvariantArrays replaces -Alint=arrays:invariant
+  -AcheckCastElementType replaces -Alint=cast:strict
+
+Manual:
+  New manual section about array types.
+  New FAQ entries:  "Which checker should I start with?", "How can I handle
+    typestate, or phases of my program with different data properties?",
+    "What is the meaning of a type qualifier at a class declaration?"
+  Reorganized FAQ chapter into sections.
+  Many other improvements.
+
+
+Version 1.6.7 (28 August 2013)
+------------------------------
+
+User-visible framework improvements:
+  Improve the error message produced by -Adetailedmsgtext
+
+Bug fixes:
+  Fix issue #245: anonymous classes were skipped by default
+
+
+Version 1.6.6 (01 August 2013)
+------------------------------
+
+Documentation:
+  The Checker Framework manual has been improved.  Changes include:
+more troubleshooting tips to the Checker Framework manual, an improved
+discussion on qualifier bounds, more examples, improved formatting, and more.
+  An FAQ entry has been added to discuss JSR305.
+  Minor clarifications have been added to the Checker Framework tutorial.
+
+
+Version 1.6.5 (01 July 2013)
+----------------------------
+
+User-visible framework improvements:
+  Stub files now support static imports.
+
+Maven plugin:
+  Maven plugin will now issue a warning rather than quit when zero checkers are specified in a project's pom.xml.
+
+Documentation:
+  Improved the Maven plugin instructions in the Checker Framework manual.
+  Added documentation for the -XDTA:noannotationsincomments compiler flag.
+
+Internal framework improvements:
+  Improved Maven-plugin developer documentation.
+
+
+Version 1.6.4 (01 June 2013)
+----------------------------
+
+User-visible framework improvements:
+    StubGenerator now generates stubs that can be read by the StubParser.
+
+Maven plugin:
+    The Maven plugin no longer requires the Maven project's output directory to exist in order to run the Checker Framework.  However, if you ask the Checker Framework to generate class files then the output directory will be created.
+
+Documentation:
+  Improved the Maven plugin instructions in the Checker Framework manual.
+  Improved the discussion of why to define both a bottom and a top qualifier in the Checker Framework manual.
+  Update FAQ to discuss that some other tools incorrectly interpret array declarations.
+
+
+Version 1.6.3 (01 May 2013)
+---------------------------
+
+Eclipse plugin bug fixes:
+  The javac argument files used by the Eclipse plugin now properly escape file paths.  Windows users should no longer encounter errors about missing built-in checkers.
+
+Documentation:
+  Add FAQ "What is the meaning of an annotation after a type?"
+
+
+Version 1.6.2 (04 Apr 2013)
+---------------------------
+
+Eclipse plugin:
+  The "Additional compiler parameters" text field has now been replaced by a list.  Parameters in this list may be activated/deactivated via checkbox.
+
+Eclipse plugin bug fixes:
+   Classpaths and source files should now be correctly quoted when they contain spaces.
+
+Internal framework improvements:
+  Update pom files to use the same update-version code as the Checker Framework "web" ant task.  Remove pom specific update-version code.
+  Update build ant tasks to avoid re-running targets when executing tests from the release script.
+
+
+Version 1.6.1 (01 Mar 2013)
+---------------------------
+
+User-visible framework improvements:
+  A number of error messages have been clarified.
+  Stub file now supports type annotations in front and after method type variable declarations.
+  You may now specify custom paths to javac.jar and jdk7.jar on the command line for non-standard installations.
+
+Internal framework improvements:
+  Add shouldBeApplied method to avoid unnecessary scans in DefaultApplier and avoid annotating void types.
+  Add createQualifierDefaults and createQualifierPolymorphism factory methods.
+
+Maven plugin:
+  Put Checker Framework jars at the beginning of classpath.
+  Added option to compile code in order to support checking for multi-module projects.
+  The plugin no longer copies the various Checker Framework maven artifacts to one location but instead takes advantage of the new custom path options for javac.jar and jdk7.jar.
+  The maven plugin no longer attempts to resolve jdk6.jar
+
+Eclipse plugin:
+  Put Checker Framework jars at the beginning of classpath.
+  All files selected from a single project can now be checked.  The previous behavior only checked the entire project or one file depending on the type of the first file selected.
+
+Documentation:
+  Fixed broken links and incomplete URLs in the Checker Framework Manual.
+  Update FAQ to discuss that some other tools incorrectly interpret array declarations.
+
+Bug fixes
+
+
+Version 1.6.0 (1 Feb 2013)
+--------------------------
+
+User-visible framework improvements:
+  It is possible to use enum constants in stub files without requiring the fully qualified name, as was previously necessary.
+  Support build on a stock Java 8 OpenJDK.
+
+Adapt to underlying jsr308-langtools changes.
+  The most visible change is syntax for fully-qualified types, from @A java.lang.Object to java.lang.@A Object.
+  JDK 7 is now required.  The Checker Framework does not build or run on JDK 6.
+
+Documentation:
+  A new tutorial is available at https://checkerframework.org/tutorial/
+
+
+Version 1.5.0 (14 Jan 2013)
+---------------------------
+
+User-visible framework improvements:
+  To invoke the Checker Framework, call the main method of class
+    CheckerMain, which is a drop-in replacement for javac.  This replaces
+    all previous techniques for invoking the Checker Framework.  Users
+    should no longer provide any Checker Framework jars on the classpath or
+    bootclasspath.  jsr308-all.jar has been removed.
+  The Checker Framework now works with both JDK 6 and JDK 7, without need
+    for user customization.  The Checker Framework determines the
+    appropriate annotated JDK to use.
+  All jar files now reside in checker-framework/checkers/binary/.
+
+Maven plugin:
+  Individual pom files (and artifacts in the Maven repository) for all
+    Checker Framework jar files.
+  Avoid too-long command lines on Windows.
+  See the Maven section of the manual for more details.
+
+Eclipse plugin:
+  Avoid too-long command lines on Windows.
+  Other bug fixes and interface improvements.
+
+Other framework improvements:
+  New -Adetailedmsgtext command-line option, intended for use by IDE plugins.
+
+
+Version 1.4.4 (1 Dec 2012)
+--------------------------
+
+Internal framework improvements:
+  Add shutdown hook mechanism and use it for -AresourceStats resource
+    statistics flag.
+  Add -AstubWarnIfNotFound and -AstubDebug options to improve
+    warnings and debug information from the stub file parsing.
+  Ignore case when comparing error suppression keys.
+  Support the bottom type as subtype of any wildcard type.
+
+Tool Integration Changes
+  The Maven plugin id has been changed to reflect standard Maven
+    naming conventions.
+  Eclipse and Maven plugin version numbers will now
+    track the Checker Framework version numbers.
+
+Bug fixes.
+
+
+Version 1.4.3 (1 Nov 2012)
+--------------------------
+
+Clarify license:
+  The Checker Framework is licensed under the GPL2.  More permissive
+    licenses apply to annotations, tool plugins (Maven, Eclipse),
+    external libraries included with the Checker Framework, and examples in
+    the Checker Framework Manual.
+  Replaced all third-party annotations by cleanroom implementations, to
+    avoid any potential problems or confusion with licensing.
+
+Aliased annotations:
+  Clarified that there is no need to rewrite your program.  The Checker
+    Framework recognizes dozens of annotations used by other tools.
+
+Improved documentation of Units Checker and Gradle Integration.
+Improved developer documentation of Eclipse and Maven plugins.
+
+Bug fixes.
+
+
+Version 1.4.2 (16 Oct 2012)
+---------------------------
+
+External tool support:
+  Eclipse plug-in now works properly, due to many fixes
+
+Regex Checker:
+  New CheckedPatternSyntaxException added to RegexUtil
+
+Support new foreign annotations:
+  org.eclipse.jdt.annotation.Nullable
+  org.eclipse.jdt.annotation.NonNull
+
+New FAQ: "What is a receiver?"
+
+Make annotations use 1-based numbering for formal parameters:
+  Previously, due to a bug the annotations used 0-based numbering.
+  This change means that you need to rewrite annotations in the following ways:
+    @KeyFor("#3")  =>  @KeyFor("#4")
+    @AssertNonNullIfTrue("#0")  =>  @AssertNonNullIfTrue("#1")
+    @AssertNonNullIfTrue({"#0", "#1"})  =>  @AssertNonNullIfTrue({"#1", "#2"})
+    @AssertNonNullAfter("get(#2)")  =>  @AssertNonNullAfter("get(#3)")
+  This command:
+    find . -type f -print | xargs perl -pi -e 's/("#)([0-9])(")/$1.($2+1).$3/eg'
+  handles the first two cases, which account for most uses.  You would need
+  to handle any annotations like the last two cases in a different way,
+  such as by running
+    grep -r -n -E '\("[^"]+#[0-9][^A-Za-z]|\("#[0-9][^"]' .
+  and making manual changes to the matching lines.  (It is possible to
+  provide a command that handles all cases, but it would be more likely to
+  make undesired changes.)
+  Whenever making automated changes, it is wise to save a copy of your
+  codebase, then compare it to the modified version so you can undo any
+  undesired changes.  Also, avoid running the automated command over version
+  control files such as your .hg, .git, .svn, or CVS directory.
+
+
+Version 1.4.1 (29 Sep 2012)
+---------------------------
+
+User-visible framework improvements:
+  Support stub files contained in .jar files.
+  Support aliasing for declaration annotations.
+  Updated the Maven plugin.
+
+Code refactoring:
+  Make AnnotationUtils and AnnotatedTypes into stateless utility classes.
+    Instead, provide the necessary parameters for particular methods.
+  Make class AnnotationBuilder independent of AnnotationUtils.
+  Remove the ProcessingEnvironment from AnnotatedTypeMirror, which was
+    hardly used and can be replaced easily.
+  Used more consistent naming for a few more fields.
+  Moved AnnotatedTypes from package checkers.types to checkers.utils.
+    this required making a few methods in AnnotatedTypeFactory public,
+    which might require changes in downstream code.
+
+Internal framework improvements:
+  Fixed Issues #136, #139, #142, #156.
+  Bug fixes and documentation improvements.
+
+
+Version 1.4.0 (11 Sep 2012)
+---------------------------
+
+User-visible framework improvements:
+  Defaulting:
+    @DefaultQualifier annotations now use a Class instead of a String,
+      preventing simple typo errors.
+    @DefaultLocation extended with more constants.
+    TreeAnnotator propagates the least-upper-bound of the operands of
+      binary/compound operations, instead of taking the default qualifier.
+  Stub files now ignore the return type, allowing for files automatically
+    generated from other formats.
+  Type factories and type hierarchies:
+    Simplify AnnotatedTypeFactory constructors.
+    Add a GeneralAnnotatedTypeFactory that supports multiple type systems.
+    Improvements to QualifierHierarchy construction.
+  Type-checking improvements:
+    Propagate annotations from the sub-expression of a cast to its result.
+    Better handling of assignment context and improved inference of
+      array creation expressions.
+  Optional stricter checking of casts to array and generic types using
+    the new -Alint=cast:strict flag.
+    This will become the default in the future.
+  Code reorganization:
+    SourceChecker.initChecker no longer has a ProcessingEnvironment
+      parameter. The environment can now be accessed using the standard
+      processingEnv field (instead of the previous env field).
+    Classes com.sun.source.util.AbstractTypeProcessor and
+      checkers.util.AggregateChecker are now in package checkers.source.
+    Move isAssignable from the BaseTypeChecker to the BaseTypeVisitor; now
+      the Checker only consists of factories and logic is contained in the
+      Visitor.
+  Warning and error messages:
+    Issue a warning if an unsupported -Alint option is provided.
+    Improved error messages.
+  Maven plugin now works.
+
+Nullness Checker:
+  Only allow creation of (implicitly) non-null objects.
+  Optionally forbid creation of arrays with @NonNull component type,
+    when flag -Alint=arrays:forbidnonnullcomponents is supplied.
+    This will become the default in the future.
+
+Internal framework improvements:
+  Enable assertion checking.
+  Improve handling of annotated type variables.
+  Assignment context is now a type, not a tree.
+  Fix all compiler warnings.
+
+
+Version 1.3.1 (21 Jul 2012)
+---------------------------
+
+Installation:
+  Clarify installation instructions for Windows.  Remove javac.bat, which
+  worked for running distributed checkers but not for creating new checkers.
+
+User-visible framework improvements:
+  Implement @PolyAll qualifier to vary over multiple type systems.
+  The Checker Framework is unsound due to Java's covariant array subtyping.
+    You can enable invariant array subtyping (for qualifiers only, not for
+    base Java types) with the command-line option -Alint=arrays:invariant.
+    This will become the default in the future.
+
+Internal framework improvements:
+  Improve defaulting for multiple qualifier hierarchies.
+  Big refactoring of how qualifier hierarchies are built up.
+  Improvements to error handling output for unexpected exceptions.
+  Bug fixes and documentation improvements.
+
+
+Version 1.3.0 (3 Jul 2012)
+--------------------------
+
+Annotation syntax changes, as mandated by the latest Type Annotations
+(JSR 308) specification.  The most important ones are:
+- New receiver syntax, using "this" as a formal parameter name:
+    ReturnType methodname(@ReceiverAnnotation MyClass this, ...) { ... }
+- Changed @Target default to be the Java 1.5 values
+- UW extension: in addition to annotations in comments, support
+    special /*>>> */ comments to hide multiple tokens.
+    This is useful for the new receiver syntax and for import statements.
+
+Framework improvements:
+  Adapt to annotation storage changes in jsr308-langtools 1.3.0.
+  Move type validation methods from the BaseTypeChecker to BaseTypeVisitor.
+
+
+Version 1.2.7 (14 May 2012)
+---------------------------
+
+Regex Checker:
+  Add basic support for the concatenation of two non-regular expressions
+    that produce a valid regular expression.
+  Support "isRegex" in flow inference.
+
+Framework improvements:
+  New @StubFiles annotation declaratively adds stub files to a checker.
+
+Internal bug fixes:
+  Respect skipDefs and skipUses in NullnessFlow.
+  Support package annotations in stub files.
+  Better support for enums in annotation attributes.
+  Cleanups to how implicit receivers are determined.
+
+
+Version 1.2.6 (18 Mar 2012)
+---------------------------
+
+Nullness Checker:
+  Correctly handle unboxing in more contexts (if, switch (Issue 129),
+    while loops, ...)
+
+Regex Checker:
+  Add capturing groups parameter to Regex qualifier.
+    Count groups in String literals and String concatenation.
+    Verify group number to method calls that take a capturing group
+      number.
+    Update RegexUtil methods to take optional groups parameter.
+    Modify regex qualifier hierarchy to support groups parameter.
+  Add special case for Pattern.compile when called with Pattern.LITERAL flag.
+
+Internal bug fixes:
+  Improve flow's support of annotations with parameters.
+  Fix generics corner cases (Issues #131, #132, #133, #135).
+  Support type annotations in annotations and type-check annotations.
+  Improve reflective look-up of visitors and factories.
+  Small cleanups.
+
+
+Version 1.2.5.1 (06 Feb 2012)
+-----------------------------
+
+Nullness Checker:
+  Correct the annotations on ThreadLocal and InheritableThreadLocal.
+
+Internal bug fixes:
+  Expand release tests.
+  Compile release with JDK 6 to work on both JDK 6 and JDK 7.
+
+
+Version 1.2.5 (3 Feb 2012)
+--------------------------
+
+Don't put classpath on the bootclasspath when invoking javac.  This
+prevents problems if, for example, android.jar is on the classpath.
+
+New -jsr308_imports ... and -Djsr308.imports=... command-line options, for
+specifying implicit imports from the command line.  This is needed by Maven.
+
+New -Aignorejdkastub option makes the checker not load the jdk.astub
+file. Files from the "stubs" option are still loaded.
+
+Regex Checker:
+  Support concatenation of PolyRegex strings.
+  Improve examples of use of RegexUtil methods.
+
+Signature Checker:
+  Add new @ClassGetName annotation, for a 4th string representation of a
+    class that is used by the JDK.  Add supporting annotations to make the
+    type hierarchy a complete lattice.
+  Add PolySignature annotation.
+
+Internal bug fixes:
+  Improve method type argument inference.
+  Handle type variables whose upper bound is a type variable.
+  Fix bug in least upper bound computation for anonymous classes.
+  Improve handling of annotations inherited from superclasses.
+  Fix design problem with Nullness Checker and primitive types.
+  Ensure that overriding methods respect pre- and postconditions.
+  Correctly resolve references to an enclosing this.
+  Improve handling of Java source that contains compilation errors.
+
+
+Version 1.2.4 (15 Dec 2011)
+---------------------------
+
+All checkers:
+- @Target(TYPE_USE) meta-annotation is properly handled.
+
+Nullness Checker:
+- Do not allow nullness annotations on primitive types.
+- Improvements to rawness (initialization) checks.
+- Special-case known keys for System.getProperty.
+- The -Alint=uninitialized command-line option now defaults to off, and
+  applies only to initialization of primitive and @Nullable fields.  It is
+  not possible to disable, from the command line, the check that all
+  @NonNull fields are initialized.  Such warnings must be suppressed
+  explicitly, for example by using @SuppressWarnings.
+
+Regex Checker:
+- Improved RegexUtil class.
+
+Manual:
+- Add FAQ item "Is the Checker Framework an official part of Java?"
+- Trim down README.txt; users should read the manual instead.
+- Improvements throughout, especially to Nullness and Regex Checker sections.
+
+**Implementation details:**
+- Add a new @InvisibleQualifier meta-annotation for type qualifiers.
+  Instead of special-casing @Unqualified in the AnnotatedTypeMirror it
+  now looks for this meta-annotation. This also allows type systems to
+  hide type qualifiers it doesn't want visible, which we now use in the
+  Nullness Checker to hide the @Primitive annotation.
+- Nullness Checker:  Introduce a new internal qualifier @Primitive that is
+  used for primitive types.
+- Be stricter about qualifiers being present on all types. If you get
+  errors about missing qualifiers, check your defaulting rules.
+  This helped in fixing small bugs in corner cases of the type
+  hierarchy and type factory.
+- Unify decoding type annotations from trees and elements.
+- Improve handling of annotations on type variables and upper bounds.
+- Support checkers that use multiple, disjoint qualifier hierarchies.
+- Many bug fixes.
+
+
+Version 1.2.3 (1 Nov 2011)
+--------------------------
+
+Regex Checker:
+- Add @PolyRegex polymorphic annotation
+- Add more stub library annotations
+
+**Implementation details:**
+- Do not use "null" for unqualified types. Explicitly use @Unqualified
+  and be strict about correct usage. If this causes trouble for you,
+  check your @ImplicitFor and @DefaultQualifierInHierarchy
+  meta-annotations and ensure correct defaulting in your
+  AnnotatedTypeFactory.
+
+Bug fixes:
+- Correctly handle f-bounded polymorphism. AnnotatedTypeMirror now has
+  methods to query the "effective" annotations on a type, which
+  handles type variable and wildcard bounds correctly. Also, terminate
+  recursions by not doing lazy-initialization of bounds during defaulting.
+- Many other small bug fixes and documentation updates.
+
+
+Version 1.2.2 (1 Oct 2011)
+--------------------------
+
+Be less restrictive about when to start type processing when errors
+already exist.
+Add -AskipDefs command-line option to not type-check some class
+definitions.
+Documentation improvements.
+
+
+Version 1.2.1 (20 Sep 2011)
+---------------------------
+
+Fix issues #109, #110, #111 and various other cleanups.
+Improvements to the release process.
+Documentation improvements.
+
+
+Version 1.2.0.1 (4 Sep 2011)
+----------------------------
+
+New version number to stay in sync with JSR 308 compiler bugfix.
+No significant changes.
+
+
+Version 1.2.0 (2 Sep 2011)
+--------------------------
+
+Updated to JDK 8. Use -source 8 (the new default) for type annotations.
+Documentation improvements
+Bug fixes all over
+
+Nullness Checker:
+- Correct the upper bounds of all Collection subtypes
+
+
+Version 1.1.5 (22 Jul 2011)
+---------------------------
+
+**User-visible changes:**
+
+Units Checker:
+  Instead of conversion routines, provide unit constants, with which
+  to multiply unqualified values. This is easier to type and the
+  multiplication gets optimized away by the compiler.
+
+Fenum Checker:
+  Ensure that the switch statement expression is a supertype of all
+  the case expressions.
+
+**Implementation details:**
+
+- Parse declaration annotations in stub files
+
+- Output error messages instead of raising exceptions. This change
+  required us to introduce method "initChecker" in class
+  SourceChecker, which should be used instead of "init". This allows
+  us to handle the calls to initChecker within the framework.
+  Use method "errorAbort" to output an error message and abort
+  processing.
+
+
+Version 1.1.4 (8 Jul 2011)
+--------------------------
+
+**User-visible changes:**
+
+Units Checker (new):
+  Ensures operations are performed on variables of correct units of
+  measurement (e.g., miles vs. kilometers vs. kilograms).
+
+Changed -AskipClasses command-line option to -AskipUses
+
+**Implementation details:**
+
+- Improve support for type qualifiers with enum attributes
+
+
+Version 1.1.3 (17 Jun 2011)
+---------------------------
+
+**User-visible changes:**
+
+Interning:
+- Add @UsesObjectEquals annotation
+
+Manual:
+- Signature Checker is now documented
+- Fenum Checker documentation improved
+- Small improvements to other sections
+
+**Implementation details:**
+
+- Updates to the web-site build process
+
+- The BaseTypeVisitor used to provide the same two type parameters as
+  class SourceVisitor. However, all subtypes of BaseTypeVisitor were
+  instantiated as <Void, Void>. We decided to directly instantiate the
+  SourceVisitor as <Void, Void> and removed this complexity.
+  Instead, the BaseTypeVisitor is now parameterized by the subtype of
+  BaseTypeChecker that should be used. This gives a more concrete type
+  to field "checker" and is similar to BasicAnnotatedTypeFactory.
+
+- Added method AnnotatedTypeFactory.typeVariablesFromUse to allow
+  type-checkers to adapt the upper bounds of a type variable depending on
+  the type instantiation.
+
+- Method type argument inference:
+  Changed AnnotatedTypeFactory.methodFromUse to return a Pair consisting
+  of the method and the inferred or explicit method type arguments.
+  If you override this method, you will need to update your version.
+  See this change set for a simple example:
+  https://github.com/typetools/checker-framework/source/detail?r=8381a213a4
+
+- Testing framework:
+  Support for multiple expected errors using the "// :: A :: B :: C" syntax.
+
+Many small updates and fixes.
+
+
+Version 1.1.2 (12 Jan 2011)
+---------------------------
+
+Fake Enum Checker (new):
+  A "fake enumeration" is a set of integers rather than a proper Java enum.
+  They are used in legacy code and for efficiency (e.g., in Android).  The
+  Fake Enum Checker gives them the same safety guarantees as a proper Java
+  enum.
+
+Property File Checker (new):
+  Ensures that valid keys are used for property files and resource bundles.
+  Also includes a checker that code is properly internationalized and a
+  checker for compiler message keys as used in the Checker Framework.
+
+Signature Checker (new):
+  Ensures that different string representations of a Java type (e.g.,
+  "pakkage.Outer.Inner" vs. "pakkage.Outer$Inner" vs. "Lpakkage/Outer$Inner;")
+  are not misused.
+
+Interning Checker enhancements:
+  Issues fewer false positives for code like "a==b || a.equals(b)"
+
+Foreign annotations:
+  The Checker Framework supports more non-Checker-Framework annotations.
+  This means that it can check already-annotated code without requiring you
+  to rewrite your annotations.
+    Add as an alias for checkers.interning.quals.Interned:
+      com.sun.istack.Interned
+    Add as aliases for checkers.nullness.quals.NonNull:
+      com.sun.istack.NotNull
+      org.netbeans.api.annotations.common.NonNull
+    Add as aliases for checkers.nullness.quals.Nullable:
+      com.sun.istack.Nullable
+      javax.validation.constraints.NotNull
+      org.netbeans.api.annotations.common.CheckForNull
+      org.netbeans.api.annotations.common.NullAllowed
+      org.netbeans.api.annotations.common.NullUnknown
+
+Manual improvements:
+  Improve installation instructions
+  Rewrite section on generics (thanks to Bert Fernandez and David Cok)
+    Also refactor the generics section into its own chapter
+  Rewrite section on @Unused and @Dependent
+  New manual section: Writing Java expressions as annotation arguments
+  Better explanation of warning suppression
+  JSR 308 is planned for Java 8, not Java 7
+
+Stub files:
+  Support nested classes by expressing them at top level in binary form: A$B
+  Improved error reporting when parsing stub files
+
+Annotated JDK:
+  New way of generating annotated JDK
+  jdk.jar file no longer appears in repository
+  Warning if you are not using the annotated JDK.
+
+Miscellaneous:
+  Warn if -source command-line argument does not support type annotations
+
+Many bug fixes
+  There are too many to list, but some notable ones are to local type
+  inference, generics, pre- and post-conditions (e.g., @NonNullOnEntry,
+  @AssertNonNull*), and map keys (@KeyFor).  In particular, preconditions
+  and map key annotations are now checked, and if they cannot be verified,
+  an error is raised; previously, they were not verified, just unsoundly
+  trusted.
+
+
+Version 1.1.1 (18 Sep 2010)
+---------------------------
+
+Eclipse support:
+  Removed the obsolete Eclipse plug-in from repository.  The new one uses a
+  different repository
+  (http://code.google.com/a/eclipselabs.org/p/checker-plugin/) but a user
+  obtains it from the same URL as before:
+  https://checkerframework.org/eclipse/
+
+Property Key Checker:
+  The property key checker allows multiple resource bundles and the
+  simultaneous use of both resource bundles and property files.
+
+Javari Checker:
+  Added Javari stub classes for more JDK classes.
+
+Distribution:
+  Changed directory structure (top level is "checker-framework"; "checkers"
+  is a under that) for consistency with version control repository.
+
+Many documentation improvements and minor bugfixes.
+
+
+Version 1.1.0b, 16 Jun 2010
+---------------------------
+
+Fixed a bug related to running binary release in JDK 6
+
+
+Version 1.1.0 (13 Jun 2010)
+---------------------------
+
+Checkers
+  Introduced a new simple mechanism for running a checker
+  Added one annotated JDK for all checkers
+
+Nullness Checker
+  Fixed bugs related to map.get() and KeyFor annotation
+  Fixed bugs related to AssertNonNull* and parameters
+  Minor updates to the annotated JDK, especially to java.io.File
+
+Manual
+  Updated installation instructions
+  Clarified section regarding fields and type inference
+
+
+Version 1.0.9 (25 May 2010)
+---------------------------
+
+Nullness Checker:
+  Improved Javadocs and manual documentation
+  Added two new annotations: AssertNonNullAfter, KeyFor
+  Fixed a bug related to AssertNonNullIfFalse and assert statements
+  Renamed NonNullVariable to NonNullOnEntry
+
+Checkers:
+  Interning: Skipping equality check, if either operands should be skipped
+  Fixed a bug related to annotations targeting array fields found in classfile
+  Fixed a bug related to method invocation generic type inference
+    in static methods
+
+Manual
+  Added a section on nullness method annotations
+  Revised the Nullness Checker section
+  Updated Ant usage instructions
+
+
+Version 1.0.8 (15 May 2010)
+---------------------------
+
+Checkers
+  Changed behavior of flow type refinement when annotation is explicit
+  Handle array initializer trees (without explicit type)
+  Handle the case of Vector.copyInto
+  Include javax classes in the distributed jdk jar files
+
+Interning Checker
+  Handle interning inference of string concatenation
+  Add 20+ @Interned annotations to the JDK
+  Add an option, checkclass, to validate the interning
+    of specific classes only
+
+Bug fixes
+  Fix a bug related to array implicit types
+  Lock Checker: Treat null as a bottom type
+
+Manual
+  Added a new section about Flow inference and fields
+
+
+Version 1.0.7 (12 Apr 2010)
+---------------------------
+
+Checkers
+  Distributed a Maven repository
+  Updated stub parser project to latest version (javaparser 1.0.8)
+  Fixed bugs related to iterable wildcards and type parameter types
+
+
+Version 1.0.6 (24 Feb 2009)
+---------------------------
+
+Nullness Checker
+  Added support for new annotations:
+    Pure - indicates that the method, given the same parameters, return the
+            same values
+    AssertNonNullIfFalse - indicates that a field is NonNull if the method
+            returns false
+  Renamed AssertNonNull to AssertParametersNonNull
+  Updated the annotated jdk
+
+Javari Checker
+  Fixed many bugs:
+    handle implicit dereferencing of this (e.g. `field` in place of
+      `this.field`)
+    apply default annotations to method parameters
+
+
+Version 1.0.5 (12 Jan 2009)
+---------------------------
+
+Checkers
+  Added support for annotated jdk jars
+  Improved readability of some failure messages
+  Added AssertNonNullIfTrue support for method parameter references
+  Fixed a bug related to LazyNonNull and array fields
+  Fixed a bug related to inference and compound assignments (e.g. +=)
+  nullness: permit the type of @NonNull Void
+
+Manual
+  Updated annotating-libraries chapter regarding annotated jdk
+
+
+Version 1.0.4 (19 Dec 2009)
+---------------------------
+
+Bug Fixes
+  wildcards not recognized as subtypes of type variables
+    e.g. '? extends A' and 'A'
+  PolyNull methods not accepting null literal value arguments
+  spurious unexpected Raw warnings
+
+Manual
+  Clarified FAQ item regarding why List's type parameter is
+    "extends @NonNull Object"
+
+
+Version 1.0.3 (5 Dec 2009)
+--------------------------
+
+Checkers
+  New location UPPER_BOUND for DefaultQualifier permits setting the default
+    for upper bounds, such as Object in "? extends Object".
+  @DefaultQualifier accepts simple names, like @DefaultQualifier("Nullable"),
+    rather than requiring @DefaultQualifier("checkers.nullness.quals.Nullable").
+  Local variable type inference has improved support for array accesses.
+  The repository contains Eclipse project and launch configuration files.
+    This is helpful too people who want to build a checker, not to people
+    who merely want to run a checker.
+  Many bug fixes, including:
+    handling wildcard subtyping rules
+    stub files and vararg methods being ignored
+    nullness and spurious rawness errors
+    uses of array clone method (e.g. String[].clone())
+    multibound type parameters (e.g. <T extends @A Number & @B Cloneable>)
+
+Manual
+  Documented the behavior of annotations on type parameter declarations.
+  New FAQ item:
+    How to collect warnings from multiple files
+    Why a qualifier shouldn't apply to both types and declarations
+
+
+Version 1.0.2 (16 Nov 2009)
+---------------------------
+
+Checkers
+  Renamed Regex Checker's @ValidRegex annotation to @Regex
+  Improved Collection.toArray() heuristics to be more sound
+
+Bug fixes
+  Fixed the annotated JDK to match OpenJDK 6
+    - Added missing methods and corrected class hierarchy
+  Fixed a crash related to intersection types
+
+
+Version 1.0.1 (1 Nov 2009)
+--------------------------
+
+Checkers
+  Added new checkers:
+    RegEx checker to detect invalid regular expression use
+    Internationalization (I18n) checker to detect internationalization errors
+
+Functionality
+  Added more performance optimizations
+  nullness: Added support for netbeans nullness annotations
+  nullness: better semantics for redundant nullness tests
+    related to redundant tests in assertions
+  lock: Added support for JCIP annotation in the Lock Checker
+  tainting: Added support for polymorphism
+  Lock Checker supports the JCIP GuardedBy annotation
+
+Bug fixes
+  Fixed a crashing bug related to interaction between
+    generic types and wildcards
+  Fixed a bug in stub file parser related to vararg annotations
+  Fixed few bugs in skeleton file generators
+
+Manual
+  Tweak installation instructions
+  Reference Units Checker
+  Added new sections for new checkers
+    RegEx checker (S 10)
+    Internationalization Checker (S 11)
+
+
+Version 1.0.0 (30 Sep 2009)
+---------------------------
+
+Functionality
+  Added Linear Checker to restrict aliasing
+
+Bug fixes
+  Fixed flow erros related to loop controls and break/continue
+
+Manual
+  Adopt new term, "Declaration Annotation" instead of non-type annotations
+  Added new sections:
+    Linear Checker (S 9)
+    Inexpressible types (S 14.3)
+    How to get started annotating legacy code (S 2.4.4)
+  Expanded Tainting Checker section
+
+
+Version 0.9.9 (4 Sep 2009)
+--------------------------
+
+Functionality
+  Added more optional lint checks (cast:unsafe, all)
+  Nullness Checker supports @SuppressWarnings("nullness:generic.argument"),
+    for suppressing warnings related to misuse of generic type arguments.
+    This was already supported and documented, but had not been mentioned
+    in the changelog.
+
+Bug fixes
+  Fixed many bugs related to Stub files causing parser to ignore
+    bodiless constructors
+    annotated arrays annotations
+    type parameter and wildcard bounds annotations
+
+Manual
+  Rewrote 'javac implementation survival guide' (S 13.9)
+  Restructured 'Using a checker' (S 2)
+  Added 'Integration with external tools' (S 14)
+  Added new questions to the FAQ (S 15)
+
+
+Version 0.9.8 (21 Aug 2009)
+---------------------------
+
+Functionality
+  Added a Tainting Checker
+  Added support for conditional nonnull checking
+  Added optional check for redundant nullness tests
+  Updated stub parser to latest libraries
+
+Bug fixes
+  Fixed a bug related to int[] treated as Object when passed to vararg T...
+  Fixed a crash related to intersection types
+  Fixed a bug related to -AskipClasses not being honored
+  Fixed a bug related to flow
+
+Manual
+  Added new sections
+    8 Tainting Checker
+    3.2.3 Conditional nullness
+
+
+Version 0.9.7 (12 Aug 2009)
+---------------------------
+
+Functionality
+  Changed swNonNull to castNonNull
+  nullness: Improved flow to infer nullness based on method invocations
+  locking: Permitted @Holding to appear on constructors
+
+Bug fixes
+  Fixed a bug related to typevar and wildcard extends clauses
+
+
+Version 0.9.6 (29 Jul 2009)
+---------------------------
+
+Functionality
+  Changed 'jsr308.skipClasses' property with '-AskipClasses' option
+  Locking checker
+    - Add subtype checking for Holding
+    - Treat constructors as synchronized methods
+
+Bug fixes
+  Added some missing nullness annotations in the jdk
+  Fixed some bugs related to reading stub files
+
+Manual
+  Added a new section
+    2.10  Tips about writing annotations
+  Updated sections of
+    2.6   Unused fields and dependent types
+    3.1.1 Rawness annotation hierarchy
+
+
+Version 0.9.5 (13 Jul 2009)
+---------------------------
+
+Functionality
+  Added support for Findbugs, JSR305, and IntelliJ nullness annotations
+  Added an Aggregate Checker base-class
+  Added support for a form of field access control
+
+Bug fixes
+  Added check for arguments in super() calls in constructors
+
+Manual
+  Added new sections:
+    Fields access control
+    Other tools for nullness checking
+    Bundling multiple checkers
+
+
+Version 0.9.4 (30 Jun 2009)
+---------------------------
+
+Functionality
+  Added Lock Checker
+
+Bug fixes
+  Handle more patterns for determining Map.get() return type
+
+Manual Documentations
+  Improved installation instructions
+  Added the following sections
+    2.6 Dependent types
+    3.1 subsection for LazyNonNull
+    10.9 When to use (and not to use) type qualifiers
+
+
+Version 0.9.3 (23 Jun 2009)
+---------------------------
+
+Functionality
+  Added support DefaultQualifier on packages
+  Added support for Dependent qualifier types
+    see checkers.quals.Dependent
+  Added an option to treat checker errors as warnings
+  Improved flow handling of boolean logic
+
+Manual Documentations
+  Improved installation instructions
+  Improved discussion of effective and implicit qualifiers and defaults
+  Added a discussion about the need for bottom qualifiers
+  Added sections for how-to
+    . suppress Basic Checker warnings
+    . troubleshoot skeleton files
+
+
+Version 0.9.2 (2 Jun 2009)
+--------------------------
+
+Functionality
+  Added pre-liminary support for lazy initialization in nullness
+    see LazyNonNull
+
+Bug fixes
+  Corrected method declarations in JDK skeleton files
+    - bug resulted in a runtime error
+
+Documentations
+  Updated qualifier javadoc documentations
+  Corrected a reference on passing qualifiers to javac
+
+
+Version 0.9.1 (19 May 2009)
+---------------------------
+
+Bug fixes
+  Eliminated unexpected compiler errors when using checkers
+  Fixed bug related to reading annotations in skeleton files
+
+API Changes
+  Renamed SourceChecker.process() to .typeProcess()
+
+Manual
+  Updated troubleshooting info
+    info for annotations in skeleton files
+
+
+Version 0.9b, 22 Apr 2009
+-------------------------
+
+No visible changes
+
+
+Version 0.9 (16 Apr 2009)
+-------------------------
+
+Framework
+  More space and performance optimizations
+  Handle raw type with multiple type var level
+    e.g. class Pair<X, Y extends X> { ... }
+
+Manual
+  Improve installation instructions
+  Update references to command line arguments
+
+
+Version 0.8.9 (28 Mar 2009)
+---------------------------
+
+Framework
+  Introduce Space (and minor performance) optimizations
+  Type-check constructor invocation receiver type
+  Fixed bug related to try-catch flow sensitivity analysis
+  Fixed bugs when type-checking annotations and enums
+    - bug results in null-pointer exception
+
+
+Version 0.8.8 (13 Mar 2009)
+---------------------------
+
+Nullness Checker
+  Support for custom nullness assertion via @AssertNonNull
+  Support for meta-annotation AssertNonNull
+  Support for Collection.toArray() method
+    Infer the nullness of the returned type
+  Corrected some JDK Collection API annotations
+
+Framework
+  Fixed bugs related to assignments expressions in Flow
+  Fixed bugs related to enum and annotation type hierarchy
+  Fixed bugs related to default annotations on wildcard bounds
+
+
+Version 0.8.7 (27 Feb 2009)
+---------------------------
+
+Framework
+  Support annotations on type parameters
+  Fixed bugs related to polymorphic types/annotations
+  Fixed bugs related to stub fixes
+
+Manual
+  Specify annotation defaults settings for IGJ
+  Update Known Problems section
+
+Version 0.8.6 (3 Feb 2009)
+--------------------------
+
+Framework
+  Fixed bugs related to flow sensitivity analysis related to
+    . for loop and do while loops
+    . multiple iterations of a loop
+    . complement of logical conditions
+  Declarative syntax for string literal type introduction rules
+  Support for specifying stub file directories
+
+
+Version 0.8.5 (17 Jan 2009)
+---------------------------
+
+Framework
+  Fixed bugs related to flow sensitivity analysis
+  Fixed bugs related to annotations on type parameters
+
+
+Version 0.8.4 (17 Dec 2008)
+---------------------------
+
+Distribution
+  Included checkers-quals.jar which contains the qualifiers only
+
+Framework
+  Fixed bugs related to inner classes
+  Fixed a bug related to resolving polymorphic qualifiers
+    within static methods
+
+Manual
+  Added 'Distributing your annotated project'
+
+
+Version 0.8.3 (7 Dec 2008)
+--------------------------
+
+Framework
+  Fixed bugs related to inner classes
+  Changed cast semantics
+    Unqualified casts don't change cast away (or in) any qualifiers
+  Refactored AnnotationBuilder to ease building annotations
+  Added support for Object += String new behavior
+  Added a type validation check for method return types
+
+Nullness
+  Added inference of field initialization
+    Suppress false warnings due to method invocations within constructors
+
+IGJ
+  Added proper support for AssignsFields and inner classes interactions
+
+Manual
+  Updated 'Known Problems' section
+
+
+Version 0.8.2 (14 Nov 2008)
+---------------------------
+
+Framework
+  Included a binary distribution in the releases
+  Added support for annotations on type parameters
+  Fixed bugs related to casts
+
+Nullness
+  Improved error messages readability
+  Added partial support for Map.get() detection
+
+Manual
+  Improved installation instructions
+
+
+Version 0.8.1 (1 Nov 2008)
+--------------------------
+
+Framework
+  Added support for array initializers
+  Fixed many bugs related to generics and generic type inference
+
+Documentations
+  Added 'Getting Started' guide
+
+
+Version 0.8 (27 Sep 2008)
+-------------------------
+
+Framework
+  Added support for newly specified array syntax
+  Refactored code for annotating supertypes
+  Fixed AnnotationBuilder AnnotationMirror string representation
+  Fixed AnnotatedTypeMirror hashCode
+
+Manual
+  Reorganized 'Annotating Libraries' section
+
+
+Version 0.7.9 (19 Sep 2008)
+---------------------------
+
+Framework
+  Added support for stub files/classes
+  Fixed bugs related to anonymous classes
+  Fixed bugs related to qualifier polymorphism
+
+Manual
+  Updated 'Annotating Libraries' section to describe stub files
+
+Tests
+  Added support for Windows
+  Fixed a bug causing IGJ tests to fail on Windows
+
+
+Version 0.7.8 (12 Sep 2008)
+---------------------------
+
+Framework
+  Improved support for anonymous classes
+  Included refactorings to ease extensibility
+  Fixed some minor bugs
+
+Nullness
+  Fix some errors in annotated JDK
+
+
+Version 0.7.7 (29 Aug 2008)
+---------------------------
+
+Framework
+  Fixed bugs related to polymorphic qualifiers
+  Fixed bugs related to elements array convention
+  Add implicit type arguments to raw types
+
+Interning
+  Suppress cast warnings for interned classes
+
+Manual
+  Removed discussion of non-standard array syntax alternatives
+
+
+Version 0.7.6 (12 Aug 2008)
+---------------------------
+
+Framework
+  Changed default array syntax to ARRAYS-PRE, per the JSR 308 specification
+  Added an optional check for qualifier unsafe casts
+  Added support for running multiple checkers at once
+  Fixed bugs related array syntax
+  Fixed bugs related to accessing outer classes with-in inner classes
+
+Manual
+  Added a new subsection about Checker Auto-Discovery
+    2.2.1 Checker Auto-discovery
+
+
+Version 0.7.5 (2 Aug 2008)
+--------------------------
+
+Framework
+  Added support for ARRAYS-PRE and ELTS-PRE array syntax
+  Added a check for unsafe casts
+  Some improvements to the AnnotationBuilder API
+
+Nullness Checker
+  Added a check for synchronized objects
+  Added a check for (un)boxing conversions
+
+Javari Checker
+  Fixed some JDK annotated classes
+
+
+Version 0.7.4 (11 July 2008)
+----------------------------
+
+Framework
+  Added support for annotations found in classfiles
+  Added support for the ARRAY-IN array syntax
+  Added AnnotationBuilder, to create AnotationMirrors with values
+  Improved the readability of recursive types string representation
+
+Nullness Checker
+  Added a check for thrown Throwable nullability
+
+IGJ Checker
+  Treat enums as mutable by default, like regular classes
+
+Manual
+  Added a new subsection about array syntax proposals:
+    2.1.2 Annotating Arrays
+
+
+Version 0.7.3 ( 4 July 2008)
+----------------------------
+
+Javari Checker
+  Converted JDK files into stubs
+
+Nullness Checker
+  Fixed java.lang.Number declaration in the annotated jdk
+
+Framework
+  Fixed a bug causing crashes related to primitive type boxing
+  Renamed DAGQualifierHierarchy to GraphQualifierHierarchy
+
+
+Version 0.7.2 (26 June 2008)
+----------------------------
+
+IGJ Checker
+  Supports flow-sensitive type refinement
+
+Framework
+  Renamed Default annotation to DefaultQualifier
+  Added DefaultQualifiers annotation
+  Fixed bugs related to flow-sensitive type refinement
+  Fixed an error in the build script in Windows
+
+Manual
+  Added a new section
+    9.2  javac implementation survival guide
+  Added hyperlinks to Javadocs of the referenced classes
+
+
+Version 0.7.1 (20 June 2008)
+----------------------------
+
+Nullness Checker
+  Made NNEL the default qualifier scheme
+
+Basic Checker
+  Moved to its own checkers.basic package
+
+Framework
+  Enhanced type-checking within qualifier-polymorphic method bodies
+  Fixed a bug causing StackOverflowError when type-checking wildcards
+  Fixed a bug causing a NullPointerException when type-checking
+    compound assignments, in the form of +=
+
+Class Skeleton Generator
+  Distributed in compiled form (no more special installation instructions)
+  Added required asmx.jar library to lib/
+
+Manual
+  Added new sections
+    2.2.1 Ant tasks
+    2.2.2 Eclipse plugin
+    2.6   The effective qualifier on a type
+  Rewrote section 8 on annotating libraries
+    Added reference to the new Eclipse plug-in
+    Deleted installation instructions
+
+Javari Checker
+  Fixed bugs causing a NullPointerException when type-checking
+    primitive arrays
+
+IGJ Checker
+  Fixed bugs related to uses of raw types
+
+API Changes
+  Moved AnnotationFactory functionality to AnnotationUtils
+  Removed .root and .inConflict from DAGQualifierHierarchy
+
+
+Version 0.7 (14 June 2008)
+--------------------------
+
+Installation
+  New, very simple installation instructions for Linux.  For other
+    operating systems, you should continue to use the old instructions.
+
+Nullness Checker
+  Renamed from "NonNull Checker" to "Nullness Checker".
+    Renamed package from checkers.nonnull to checkers.nullness.
+    The annotation names remain the same.
+  Added PolyNull, a polymorphic type qualifier for nullness.
+
+Interning Checker
+  Renamed from "Interned Checker" to "Interning Checker".
+    Renamed package from checkers.interned to checkers.interning.
+    The annotation names remain the same.
+  Added PolyInterned, a polymorphic type qualifier for Interning.
+  Added support for @Default annotation.
+
+Framework
+  Qualifiers
+    @PolymorphicQualifier was not previously documented in the manual.
+    Moved meta-qualifiers from checkers.metaquals package to checkers.quals.
+    Removed @VariableQualifier and @RootQualifier meta-qualifiers.
+  Added BasicAnnotatedTypeFactory, a factory that handles implicitFor,
+    defaults, flow-sensitive type inference.
+  Deprecated GraphQualifierHierarchy; DAGQualifierHierarchy replaces it.
+  Renamed methods in QualifierHierarchy.
+
+Manual
+  Rewrote several manual sections, most notably:
+    2.1.1  Writing annotations in comments for backward compatibility
+      (note new -Xspacesincomments argument to javac)
+    2.3  Checking partially-annotated programs: handling unannotated code
+    2.6  Default qualifier for unannotated types
+    2.7  Implicitly refined types (flow-sensitive type qualifier inference)
+    8  Annotating libraries
+    9  How to create a new checker plugin
+  Javadoc for the Checker Framework is included in its distribution and is
+    available online at https://checkerframework.org/api/ .
+
+
+Version 0.6.4 (9 June 2008)
+---------------------------
+
+All Framework
+  Updated the distributed JDK and examples to the new location of qualifiers
+
+Javari Checker
+  Improved documentation on polymorphism resolution
+  Removed redundant code now added to the framework from JavariVisitor,
+    JavariChecker and JavariAnnotatedTypeFactory
+  Refactored method polymorphism into JavariAnnotatedTypeFactory
+  Fixed bug on obtaining type from NewClassTree, annotations at constructor
+    invocation are not ignored now
+  Refactored polymorphism resolution, now all annotations on parameters and
+    receivers are replaced, not only on the return type
+  Refactored and renamed internal annotator classes in
+    JavariAnnotatedTypeFactory
+  Added more constructor tests
+  Moved Javari annotations to checkers.javari.quals package
+
+
+Version 0.6.3 (6 June 2008)
+---------------------------
+
+Checker Framework
+  Improved documentation and manual
+  Treat qualifiers on extends clauses of type variables and wildcard types as
+    if present on type variable itself
+  Renamed AnnotationRelations to QualifierHierarchy
+  Renamed GraphAnnotationRelations to GraphQualifierHierarchy
+  Renamed TypeRelations to TypeHierarchy
+  Added flow as a supported lint option for all checkers
+  Determined the suppress warning key reflectively
+
+Interned Checker
+  Moved @Interned annotation to checkers.interned.quals package
+
+NonNull Checker
+  Moved nonnull annotations to checkers.nonnull.quals package
+
+Miscellaneous
+  Included Javadocs in the release
+  Improved documentation for all checkers
+
+
+Version 0.6.2 (30 May 2008)
+---------------------------
+
+Checker Framework API
+  Added support for @Default annotation via TreeAnnotator
+  Added support for PolymorphicQualifier meta-annotation
+  Disallow the use of @SupportedAnnotationTypes on checkers
+  Fixed bugs related to wildcards with super clauses
+  Improved flow-sensitive analysis for fields
+
+Javari Checker
+  Moved Javari qualifiers from checkers.quals to checkers.javari.quals
+  Fixed bugs causing null pointer exceptions
+
+NonNull Checker
+  Fixed bugs related to nonnull flow
+  Added new tests to test suite
+
+Basic Checker
+  Renamed Custom Checker to Basic Checker
+
+
+Version 0.6.1 (26 Apr 2008)
+---------------------------
+
+Checker Framework API
+  Added support for @ImplicitFor meta-annotations via the new TypeAnnotator
+    and TreeAnnotator classes
+  Improved documentation and specifications
+  Fixed a bug related to getting supertypes of wildcards
+  Fixed a crash on class literals of primitive and array types
+  Framework ignores annotations that are not part of a type system
+  Fixed several minor bugs in the flow-sensitive inference implementation.
+
+IGJ Checker
+  Updated the checker to use AnnotationRelations and TypeRelations
+
+Javari Checker
+  Changing RoMaybe annotation to PolyRead
+  Updated checker to use AnnotationRelations and TypeRelations
+  Updated the JDK
+  Fixed bugs related to QReadOnly and type argument subtyping
+  Fixed bugs related to this-mutable fields in methods with @ReadOnly receiver
+  Fixed bugs related to primitive type casts
+  Added new tests to test suit
+
+NonNull Checker
+  Updated the annotated JDK
+  Fixed bugs in which default annotations were not correctly applied
+  Added @Raw types to handle partial object initialization.
+  Fixed several minor bugs in the checker implementation.
+
+Custom Checker
+  Updated checker to use hierarchy meta-annotations, via -Aquals argument
+
+
+Version 0.6 (11 Apr 2008)
+-------------------------
+
+Checker Framework API
+  Introduced AnnotationRelations and TypeRelations, more robust classes to
+    represent type and annotation hierarchies, and deprecated
+    SimpleSubtypeRelation
+  Add support for meta-annotations to declare type qualifiers subtype relations
+  Re-factored AnnotatedTypes and AnnotatedTypeFactory
+  Added a default implementation of SourceChecker.getSuppressWarningsKey()
+    that reads the @SuppressWarningsKey class annotation
+  Improved support for multidimensional arrays and new array expressions
+  Fixed a bug in which implicit annotations were not being applied to
+    parenthesized expressions
+  Framework ignores annotations on a type that do not have @TypeQualifier
+  Moved error/warning messages into "messages.properties" files in each
+    checker package
+  Fixed a bug in which annotations were inferred to liberally by
+    checkers.flow.Flow
+
+Interned Checker
+  Added heuristics that suppress warnings for certain comparisons (namely in
+    methods that override Comparator.compareTo and Object.equals)
+  The Interned checker uses flow-sensitive inference by default
+
+IGJ Checker
+  Fixed bugs related to resolving immutability variable in method invocation
+  Fixed a bug related to reassignability of fields
+  Add more tests
+
+Javari Checker
+  Added placeholder annotation for ThisMutable mutability
+  Re-factored JavariAnnotatedTypeFactory
+  Fixed self-type resolution for method receivers for readonly classes
+  Fixed annotations on parameters of readonly methods
+  Fixed type validation for arrays of primitives
+  Added more tests
+  Renamed @RoMaybe annotation to @PolyRead
+
+NonNull Checker
+  Removed deprecated checkers.nonnull.flow package
+  Fixed a bug in which default annotations were not applied correctly
+
+Miscellaneous
+  Improved Javadocs
+  Added FactoryTestChecker, a more modular tester for the annotated type
+    factory
+  Simplify error output for some types by stripping package names
+
+
+Version 0.5.1 (21 Mar 2008)
+---------------------------
+
+Checker Framework API
+  Added support for conditional expression
+  Added checks for type validity and assignability
+  Added support for per-checker customization of asMemberOf
+  Added support for type parameters in method invocation,
+    including type inference
+  Enhanced performance of AnnotatedTypeFactory
+  Checkers run only when no errors are found by Javac
+  Fixed bugs related AnnotationUtils.deepCopy()
+  Fixed support for annotated class type parameters
+  Fixed some support for annotated type variable bounds
+  Added enhancements to flow-sensitive qualifier inference
+  Added checks for type parameter bounds
+
+Interned Checker
+  Fixed some failing test cases
+  Fixed a bug related to autoboxing/unboxing
+  Added experimental flow-sensitive qualifier inference (use
+    "-Alint=flow" to enable)
+  Improved subtype testing, removing some spurious errors
+
+IGJ Checker
+  Deleted IGJVisitor!
+  Fixed some bugs related to immutability type variable resolution
+
+Javari Checker
+  Removed redundant methods from JavariVisitor in the new framework
+  Added support to constructor receivers
+  Added support to parenthesized expressions
+  Fixed a bug related to resolving RoMaybe constructors
+  Fixed a bug related to parsing conditional expressions
+  Added parsing of parenthesized expressions
+  Replaced checkers.javari.VisitorState with
+    checkers.types.VisitorState, present in BaseTypeVisitor
+  Modified JavariVisitor type parameters (it now extends
+    BaseTypeVisitor<Void, Void>, not BaseTypeVisitor<Void,
+    checkers.javari.VisitorState>)
+  Modified JavariAnnotatedTypeFactory TreePreAnnotator to mutate a
+    AnnotatedTypeMirror parameter instead of returning a
+    List<AnnotationMirror>, in accordance with other parts of the
+    framework design
+  Modified test output format
+  Added tests to test suite
+
+NonNull Checker
+  Fixed a bug related to errors produced on package declarations
+  Exception parameters are now treated as NonNull by default
+  Added better support for complex conditionals in NonNull-specific
+    flow-sensitive inference
+  Fixed some failing test cases
+  Improved subtype testing, removing some spurious errors
+
+Custom Checker
+  Added a new type-checker for type systems with no special semantics, for
+    which annotations can be provided via the command line
+
+Miscellaneous
+  Made corrections and added more links to Javadocs
+  A platform-independent binary version of the checkers and framework
+    (checkers.jar) is now included in this release
+
+
+Version 0.5 (7 Mar 2008)
+------------------------
+
+Checker Framework API
+  Enhanced the supertype finder to take annotations on extends and
+    implements clauses of a class type
+  Fixed a bug related to checking an empty array initializer ("{}")
+  Fixed a bug related to missing type information when multiple
+    top-level classes are defined in a single file
+  Fixed infinite recursion when checking expressions like "Enum<E
+    extends Enum<E>>"
+  Fixed a crash in checkers.flow.Flow related to multiple top-level
+    classes in a single file
+  Added better support for annotated wildcard type bounds
+  Added AnnotatedTypeFactory.annotateImplicit() methods to replace
+    overriding the getAnnotatedType() methods directly
+  Fixed a bug in which constructor arguments were not checked
+
+Interned Checker
+  Fixed a bug related to auto-unboxing of classes for primitives
+  Added checks for calling methods with an @Interned receiver
+
+IGJ Checker
+  Implemented the immutability inference for self-type (type of
+    'this') properly
+  Enhanced the implicit annotations to make an un-annotated code
+    type-check
+  Fixed bugs related to invoking methods based on a method's receiver
+    annotations
+
+Javari Checker
+  Restored in this version, after porting to the new framework
+
+NonNull Checker
+  Fixed a bug in which primitive types were considered possibly null
+  Improvements to support for @Default annotations
+
+Miscellaneous
+  Improved error message display for all checkers
+
+
+Version 0.4.1 (22 Feb 2008)
+---------------------------
+
+Checker Framework API
+  Introduced AnnotatedTypeFactory.directSupertypes() which finds the
+    supertypes as annotated types, which can be used by the framework.
+  Introduced default error messages analogous to javac's error messages.
+  Fixed bugs related to handling array access and enhanced-for-loop type
+    testing.
+  Fixed several bugs that are due AnnotationMirror not overriding .equals()
+    and .hashCode().
+  Improved Javadocs for various classes and methods.
+  Fixed several bugs that caused crashes in the checkers.
+  Fixed a bug where varargs annotations were not handled correctly.
+
+IGJ Checker
+  Restored in this version, after porting the checker to the new framework.
+
+NonNull Checker
+  Fixed a bug where static field accesses were not handled correctly.
+  Improved error messages for the NonNull checker.
+  Added the NNEL (NonNull Except Locals) annotation default.
+
+Interned Checker
+  Fixed a bug where annotations on type parameter bounds were not handled
+    correctly.
+  Improved error messages for the Interned checker.
+
+
+Version 0.4 (11 Feb 2008)
+-------------------------
+
+Checker Framework API
+  Added checkers.flow, an improved and generalized flow-sensitive type
+    qualifier inference, and removed redundant parts from
+    checkers.nonnull.flow.
+  Fixed a bug that prevented AnnotatedTypeMirror.removeAnnotation from working
+    correctly.
+  Fixed incorrect behavior in checkers.util.SimpleSubtypeRelation.
+
+NonNull Checker
+  Adopted the new checkers.flow.Flow type qualifier inference.
+  Clarifications and improvements to Javadocs.
+
+
+Version 0.3.99 (20 Nov 2007)
+----------------------------
+
+Checker Framework API
+  Deprecated AnnotatedClassType, AnnotatedMethodType, and AnnotationLocation
+    in favor of AnnotatedTypeMirror (a new representation of annotated types
+    based on the javax.lang.model.type hierarchy).
+  Added checkers.basetype, which provides simple assignment and
+    pseudo-assignment checking.
+  Deprecated checkers.subtype in favor of checkers.basetype.
+  Added options for debugging output from checkers: -Afilenames, -Ashowchecks
+
+Interned Checker
+  Adopted the new Checker Framework API.
+  Fixed a bug in which "new" expressions had an incorrect type.
+
+NonNull Checker
+  Adopted the new Checker Framework API.
+
+Javari Checker
+IGJ Checker
+  Removed in this version, to be restored in a future version pending
+    completion of updates to these checkers with respect to the new framework
+    API.
+
+
+Version 0.3 (1 Oct 2007)
+------------------------
+
+Miscellaneous Changes
+  Consolidated HTML documentation into a single user manual (see the "manual"
+    directory in the distribution).
+
+IGJ Checker
+  New features:
+    Added a test suite.
+    Added annotations (skeleton files) for parts of java.util and java.lang.
+
+NonNull Checker
+  New features:
+    @SuppressWarnings("nonnull") annotation suppresses checker warnings.
+    @Default annotation can make NonNull (not Nullable) the default.
+    Added annotations (skeleton classes) for parts of java.util and java.lang.
+    NonNull checker skips no classes by default (previously skipped JDK).
+    Improved error messages: checker reports expected and found types.
+
+  Bug fixes:
+    Fixed a null-pointer exception when checking certain array accesses.
+    Improved checking for field dereferences.
+
+Interned Checker
+  New features:
+    @SuppressWarnings("interned") annotation suppresses checker warnings.
+    The checker warns when two @Interned objects are compared with .equals
+
+  Bug fixes:
+    The checker honors @Interned annotations on method receivers.
+    java.lang.Class types are treated as @Interned.
+
+Checker Framework API
+  New features:
+    Added support for default annotations and warning suppression in checkers
+
+
+Version 0.2.3 (30 Aug 2007)
+---------------------------
+
+IGJ Checker
+  New features:
+    changed @W(int) annotation to @I(String) to improve readability
+    improved readability of error messages
+    added a test for validity of types (testing @Mutable String)
+
+  Bug fixes:
+    fixed resolving of @I on fields on receiver type
+    fixed assignment checking assignment validity for enhanced for loop
+    added check for constructor invocation parameters
+
+Interned Checker
+  added the Interned checker, for verifying the absence of equality testing
+    errors; see "interned-checker.html" for more information
+
+Javari Checker
+  New features:
+    added skeleton classes for parts of java.util and java.lang with Javari
+      annotations
+
+  Bug fixes:
+    fixed readonly inner class bug on Javari Checker
+
+NonNull Checker
+  New features:
+    flow-sensitive analysis for assignments from a known @NonNull type (e.g.,
+      when the right-hand of an assignment is @NonNull, the left-hand is
+      considered @NonNull from the assignment to the next possible
+      reassignment)
+    flow-sensitive analysis within conditional checks
+
+  Bug fixes:
+    fixed several sources of null-pointer errors in the NonNull checker
+    fixed a bug in the flow-sensitive analysis when a variable was used on
+      both sides of the "=" operator
+
+Checker Framework API
+  New features:
+    added the TypesUtils.toString() method for pretty-printing annotated types
+    added AnnotationUtils, a utility class for working with annotations and
+      their values
+    added SourceChecker.getDefaultSkipPattern(), so that checkers can
+      individually specify which classes to skip by default
+    added preliminary support for suppressing checker warnings via
+      the @SuppressWarnings annotation
+
+  Bug fixes:
+    fixed handling of annotations of field values
+    InternalAnnotation now correctly uses defaults for annotation values
+    improved support for annotations on class type parameter bounds
+    fixed an assertion violation when compiling certain uses of arrays
+
+
+Version 0.2.2 (16 Aug 2007)
+---------------------------
+
+
+Code Changes
+
+* checkers.igj
+    some bug fixes and improved documentation
+
+* checkers.javari
+    fixed standard return value to be @Mutable
+    fixed generic and array handling of @ReadOnly
+    fixed @RoMaybe resolution of receivers at method invocation
+    fixed parsing of parenthesized trees and conditional trees
+    added initial support for for-enhanced loop
+    fixed constructor behavior on @ReadOnly classes
+    added checks for annotations on primitive types inside arrays
+
+* checkers.nonnull
+    flow sensitive analysis supports System.exit, new class/array creation
+
+* checkers.subtype
+    fixes for method overriding and other generics-related bugs
+
+* checkers.types
+    added AnnotatedTypeMirror, a new representation for annotated types that
+      might be moved to the compiler in later version
+    added AnnotatedTypeScanner and AnnotatedTypeVisitor, visitors for types
+    AnnotatedTypeFactory uses GenericsUtils for improved handing of annotated
+      generic types
+
+* checkers.util
+    added AnnotatedTypes, a utility class for AnnotatedTypeMirror
+    added GenericsUtils, a utility class for working with generic types
+
+* tests
+    modified output to print only missing and unexpected diagnostics
+    added new test cases for the Javari Checker
+
+
+Documentation Changes
+
+* checkers/igj-checker.html
+    improvements to page
+
+* checkers/javari-checker.html
+    examples now point to test suit files
+
+Miscellaneous Changes
+
+* checkers/build.xml
+    Ant script fails if it doesn't find the correct JSR 308 javac version
+
+
+Version 0.2.1 (1 Aug 2007)
+--------------------------
+
+
+Code Changes
+
+* checkers.igj & checkers.igj.quals
+    added an initial implementation for the IGJ language
+
+* checkers.javari
+    added a state parameter to the visitor methods
+    added tests and restructured the test suite
+    restructured and implemented RoMaybe
+    modified return type to be mutable by default
+    fixed mutability type handling for type casts and field access
+    fixed bug, ensuring no primitives can be ReadOnly
+    a method receiver type is now based on the correct annotation
+    fixed parameter type-checking for overriden methods
+    fixed bug on readonly field initialization
+    added handling for unary trees
+
+* checkers.nonnull
+    added a tests for the flow-senstive analysis and varargs methods
+    improved flow-sensitive analysis: else statements, asserts,
+      return/throw statements, instanceof checks, complex conditionals with &&
+    fixed a bug in the flow-sensitive analysis that incorrectly inferred
+      @NonNull for some elements
+    removed NonnullAnnotatedClassType, moving its functionality into
+      NonnullAnnotatedTypeFactory
+
+* checkers.source
+    SourceChecker.getSupportedAnnotationTypes() returns ["*"], overriding
+      AbstractProcessor.getSupportedAnnotationTypes(). This enables all
+      checkers to run on unannotated code
+
+* checkers.subtypes
+    fixed a bug pertaining to method parameter checks for overriding methods
+    fixed a bug that caused crashes when checking varargs methods
+
+* checkers.types
+    AnnotatedTypeFactory.getClass(Element) and getMethod(Element) use the
+      tree of the passed Element if one exists
+    AnnotatedClassType.includeAt, .execludeAt, .getAnnotationData were
+      added and are public
+    added constructor() and skipParens() methods to InternalUtils
+    renamed getTypeArgumentLocations() to getAnnotatedTypeArgumentLocations()
+      in AnnotatedClassType
+    added AnnotationData to represent annotations instead of Class instances;
+      primarily allows querying annotation arguments
+    added switch for whether or not to use includes/excludes in
+      AnnotatedClassType.hasAnnotationAt()
+
+* checkers.util
+    added utility classes
+    added skeleton class generator utility for annotating external libraries
+
+
+Documentation Changes
+
+* checkers/nonnull-checker.html
+    added a note about JML
+    added a caveat about variable initialization
+
+* checkers/README-checkers.html
+    improvements to instructions
+
+
+Version 0.2 (2 Jul 2007)
+------------------------
+
+
+Code Changes
+
+* checkers.subtype
+    subtype checker warns for annotated and redundant typecasts
+    SubtypeVisitor checks for invalid return and parameter types in overriding
+      methods
+    added checks for compound assignments (like '+=')
+
+* checkers.source
+    SourceChecker honors the "checkers.skipClasses" property as a regex for
+      suppressing warnings from unannotated code (property is "java.*" by
+      default)
+    SourceVisitor extends TreePathScanner<R,P> instead of
+      TreeScanner<Void,Void>
+
+* checkers.types
+    AnnotatedClassType.isAnnotatedWith removed
+    AnnotatedClassType.getInnerLocations renamed to getTypeArgumentLocations
+    AnnotatedClassType.include now removes from the exclude list (and
+      vice-versa)
+    AnnotatedClassType.setElement and setTree methods are now public
+
+* checkers.nonnull
+    added a flow-sensitive analysis for inferring @NonNull in "if (var !=
+      null)"-style checks
+    added checks for prefix and postfix increment and decrement operations
+
+* checkers.javari
+    added initial implementation of a type-checker for the Javari language
+
+
+Version 0.1.1 (7 Jun 2007)
+--------------------------
+
+
+Documentation Changes
+
+* checkers/nonnull-checker.html
+    created "Tiny examples" subsection
+    created "Annotated library" subsection
+    noted where to read @NonNull-annotated source
+    moved instructions for unannotated code to README-checkers.html
+    various minor corrections and clarifications
+
+* checkers/README-checkers.html
+    added cross-references to other Checker Framework documents
+    removed redundant text
+    moved instructions for unannotated code from nonnull-checker.html
+    various minor corrections and clarifications
+
+* checkers/creating-a-checker.html
+    added note about getSupportedSourceVersion
+    removed line numbers from @Interned example
+    added section on SubtypeChecker/SubtypeVisitor
+    various minor corrections and clarifications
+
+
+Code Changes
+
+* checkers.subtype
+    removed deprecated getCheckedAnnotation() mechanism
+    added missing package Javadocs
+    package Javadocs reference relevant HTML documentation
+    various improvements to Javadocs
+    SubtypeVisitor and SubtypeChecker are now abstract classes
+    updated with respect to preferred usages of
+      AnnotatedClassType.hasAnnotationAt and AnnotatedClassType.annotateAt
+
+* checkers.source
+    added missing package Javadocs
+    package Javadocs reference relevant HTML documentation
+
+* checkers.types
+    added missing package Javadocs
+    package Javadocs reference relevant HTML documentation
+    AnnotatedClassType.annotateAt now correctly handles
+      AnnotationLocation.RAW argument
+    AnnotatedClassType.annotate deprecated in favor of
+      AnnotatedClassType.annotateAt with AnnotationLocation.RAW as an argument
+    AnnotatedClassType.isAnnotatedWith deprecated in favor of
+      AnnotatedClassType.hasAnnotationAt with AnnotationLocation.RAW as an
+      argument
+    Added fromArray and fromList methods to AnnotationLocation and made
+      corresponding constructors private.
+
+* checkers.quals
+    added Javadocs and meta-annotations on annotation declarations where
+      missing
+    package Javadocs reference relevant HTML documentation
+
+* checkers.nonnull
+    various improvements to Javadocs
+    package Javadocs reference relevant HTML documentation
+
+
+Miscellaneous Changes
+
+    improved documentation of ch examples
+    Checker Framework build file now only attempts to compile .java files
+
+
+Version 0.1.0 (1 May 2007)
+--------------------------
+
+Initial release.
diff --git a/docs/checker-framework-quick-start.html b/docs/checker-framework-quick-start.html
new file mode 100644
index 0000000..347c79c
--- /dev/null
+++ b/docs/checker-framework-quick-start.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Checker Framework quick start guide</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<h1>Checker Framework quick start guide</h1>
+
+<p>
+This document gives some important information for getting started with
+pluggable type-checking.  More resources are given at the end.
+</p>
+
+<p>
+A pluggable type-checker, or &ldquo;checker&rdquo; for short, prevents certain run-time errors.  For example, it can prove that your code never suffers a
+NullPointerException.  Choose which checker you want to run from the <a href="https://checkerframework.org/manual/#introduction">list
+of checkers</a>.
+</p>
+
+<p>
+To <a href="https://checkerframework.org/manual/#installation">install the Checker Framework</a>, download and unzip
+the Checker Framework distribution:
+<a href="checker-framework-2.1.7.zip"><!-- checker-framework-zip-version -->checker-framework-2.1.7.zip<!-- /checker-framework-zip-version --></a>.<br/>
+Or, the
+<a href="http://eisop.uwaterloo.ca/live/">Checker Framework Live Demo</a>
+webpage lets you try the Checker Framework without installing it.
+</p>
+
+<p>
+To <a href="https://checkerframework.org/manual/#running">run a checker</a>, supply the <code>-processor</code> command-line argument:
+</p>
+
+<pre>
+  $CHECKERFRAMEWORK/bin/checker/javac -processor nullness MyFile1.java MyFile2.java
+</pre>
+
+<p>
+You write annotations in your code.  Then, the checker
+<a href="https://checkerframework.org/manual/#checker-guarantees">verifies
+two facts:</a> (1) the annotations you wrote are correct (they are consistent with your source code), and (2) your program will not suffer certain exceptions or errors at run time.
+</p>
+
+<p>
+Java has two types of annotations:  type annotations and declaration annotations.
+</p>
+
+<ul>
+<li>
+A type annotation, also known as a type qualifier, creates a new type.  The
+qualified type represents a different set of values.  For example, the
+ordinary Java type <code>String</code> includes <code>"hello"</code>,
+<code>""</code>, <code>null</code>, and other values.  The
+<code>@NonNull String</code> type includes <code>"hello"</code> and
+<code>""</code>, but does not include <code>null</code>.
+<br/>
+You write a type annotation right before a use of a type (on the same
+line), <a href="https://checkerframework.org/manual/#writing-annotations">as in</a>:
+<pre>
+  @NonNull String s;
+  List&lt;@Positive Integer&gt; l;
+  class Folder&lt;F extends @Existing File&gt; { ... }
+</pre>
+</li>
+<li>
+A declaration annotation gives information about a method or a variable (as
+opposed to information about the method's return type or the variable's
+type).
+<br/>
+Write a declaration annotation on a different line than the declaration it
+annotates.  For example, to indicate that a method has no side effects:
+
+<pre>
+  @SideEffectFree
+  public String toString() { ... }
+</pre>
+
+</li>
+</ul>
+
+<p>
+For the most part, you only need to write annotations on method signatures and fields.
+Most annotations within method bodies are <a href="https://checkerframework.org/manual/#type-refinement">inferred for you automatically</a>.
+Furthermore, each checker applies certain defaults to further reduce your
+annotation burden; for example, using the
+<a href="https://checkerframework.org/manual/#nullness-checker">Nullness
+  Checker</a>, you do not need to write <code>@NonNull</code>, only
+  <code>@Nullable</code> which appears less often.
+See the manual for more
+<a href="https://checkerframework.org/manual/#tips-about-writing-annotations">tips
+  about writing annotations</a>.
+</p>
+
+<p>
+To learn more, you can read:
+</p>
+<ul>
+  <li><a href="https://checkerframework.org/tutorial/">Checker Framework tutorial</a>
+  </li>
+  <li><a href="https://github.com/glts/safer-spring-petclinic/wiki">Nullness Checker tutorial</a> (external site, setup information is out of date)</li>
+  <li><a href="https://checkerframework.org/manual/#faq">Checker Framework frequently asked questions (FAQs)</a>
+  </li>
+  <li><a href="https://checkerframework.org/manual/">Checker Framework manual</a>
+  </li>
+</ul>
+
+</body>
+</html>
+
+<!--  LocalWords:  CHECKERFRAMEWORK MyFile1 MyFile2
+ -->
diff --git a/docs/checker-framework-webpage.html b/docs/checker-framework-webpage.html
new file mode 100644
index 0000000..8f9ec55
--- /dev/null
+++ b/docs/checker-framework-webpage.html
@@ -0,0 +1,257 @@
+<!DOCTYPE html>
+<html>
+<head><link rel="icon" type="image/png" href="favicon-checkerframework.png" />
+  <title>The Checker Framework</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+</head>
+<body>
+
+<img src="CFLogo.png" alt="Checker Framework logo" />
+
+<h1>The Checker Framework</h1>
+
+<p>
+Are you tired of null pointer exceptions, unintended side effects, SQL
+injections, concurrency errors, mistaken equality tests, and other run-time
+errors that appear during testing or in the field?
+</p>
+
+<p>
+The Checker Framework enhances Java's type system to make it more powerful
+and useful. This lets software developers detect and prevent errors in
+their Java programs.  The Checker Framework includes compiler plug-ins
+("checkers") that find bugs or verify their absence.  It also permits you
+to write your own compiler plug-ins.
+</p>
+
+<ul>
+  <li>
+    Quick start:  see the
+    <a href="manual/#installation"><b>Installation instructions and tutorial</b></a>.
+  </li>
+  <li>
+    Download: <a href="checker-framework-3.13.0.zip"><!-- checker-framework-zip-version -->checker-framework-3.13.0.zip<!-- /checker-framework-zip-version --></a>
+    (<!-- checker-framework-date -->3 May 2021<!-- /checker-framework-date -->);
+    includes source, platform-independent binary, tests, and documentation.<br/>
+    Then, see the <a
+    href="manual/#installation"><b>installation
+    instructions and tutorial</b></a>.
+  </li>
+
+  <li>
+    Documentation:
+    <!-- Keep this in sync with the list below. -->
+    <ul>
+      <li>
+        <a href="manual/">Checker Framework Manual (HTML)</a>
+      </li>
+      <li>
+        <a href="manual/checker-framework-manual.pdf">Checker Framework Manual (PDF)</a>
+      </li>
+      <li>
+        <a href="manual/#installation">Installation instructions</a><br/>
+        or, try it without installation at the
+        <a href="http://eisop.uwaterloo.ca/live/">Checker Framework Live Demo</a>
+        webpage
+      </li>
+      <li>
+        <a href="tutorial/">Tutorial</a> with Nullness Checker, Regex Checker, and Tainting checker<br/>
+        (There is also an older external <a href="https://github.com/glts/safer-spring-petclinic/wiki">Nullness Checker tutorial</a> whose setup information is out of date.)
+      </li>
+      <li>
+        <a href="manual/#faq">FAQ (Frequently Asked Questions with answers)</a>
+      </li>
+      <li>
+        <a href="api/">Javadoc</a> API documentation
+      </li>
+      <li>
+        <a href="CHANGELOG.md">Changelog</a>
+      </li>
+    </ul>
+  </li>
+
+  <li>
+    Source code repository (at GitHub):  <a href="https://github.com/typetools/checker-framework/">https://github.com/typetools/checker-framework/</a><br/>
+    The Checker Framework Manual contains <a href="manual/#build-source">instructions on building from source</a>.<br/>
+    Also see the <a href="https://rawgit.com/typetools/checker-framework/master/docs/developer/developer-manual.html">Developer manual</a>.
+  </li>
+
+  <!-- This paragraph appears identically at jsr308-langtools/doc/openjdk-webpage.html -->
+  <li>
+    Inference tools automatically add annotations to your code,
+    making it even easier to start using the checkers.  The Checker Framework manual contains <a
+href="manual/#type-inference-tools">a list of inference tools</a>.
+  </li>
+
+  <li>Optional related tools:
+    <ul>
+      <li>The <a href="annotation-file-utilities/"><b>Annotation File Utilities</b></a>
+          extract annotations from, and write annotations to,
+          <code>.java</code> and <code>.class</code> files.
+          It also provides a representation (called
+          an &ldquo;annotation file&rdquo;) for annotations that is outside the source code or
+          the <code>.class</code> file.  The tools support both Java 5
+          declaration annotations and Java 8 type annotations.
+        <ul>
+          <li><a href="annotation-file-utilities/annotation-tools-3.13.0.zip"><!-- annotation-tools-zip-version -->annotation-tools-3.13.0.zip<!-- /annotation-tools-zip-version --></a> (<!-- afu-date -->03 May 2021<!-- /afu-date -->)
+          </li>
+          <li><a href="https://github.com/typetools/annotation-tools/">source code repository</a>
+          </li>
+          <li><a href="annotation-file-utilities/">Documentation</a>
+              is included in the zip archive and in the repository.
+          </li>
+        </ul>
+      </li>
+    </ul>
+  </li>
+
+  <li>
+    <a href="releases/">Archive of previous releases</a> of the Checker Framework
+  </li>
+
+  <li>
+    Research papers:  See the <a
+href="manual/#publications">Checker Framework manual</a>
+  </li>
+
+</ul>
+
+
+
+<hr />
+<h2 id="Support">Support and community</h2>
+
+<p>
+If you <b>have a question</b>, then first see whether your question is
+answered in one of the manuals listed under
+<a href="#documentation">Documentation</a> below.
+If none of those documents answers your question, then use one of the
+<a href="#mailing-lists">mailing lists</a>.
+</p>
+
+
+<h3 id="documentation">Documentation</h3>
+
+    <!-- Keep this in sync with the list above. -->
+    <ul>
+      <li>
+        Checker Framework Manual (<a href="manual/checker-framework-manual.pdf">PDF</a>, <a href="manual/">HTML</a>)
+      </li>
+      <li>
+        <a href="manual/#installation">Installation instructions</a>
+      <br/>
+        or, try it without installation at the
+        <a href="http://eisop.uwaterloo.ca/live/">Checker Framework Live Demo</a>
+        webpage
+      </li>
+      <li>
+        <a href="tutorial/">Tutorial</a>
+        Other tutorials:<ul>
+          <li>
+            <a href="https://github.com/glts/safer-spring-petclinic/wiki">Nullness Checker tutorial</a>
+            (external site, setup information is out of date)
+          </li>
+        </ul>
+      </li>
+      <li>
+        <a href="manual/#faq">FAQ (Frequently Asked Questions with answers)</a>
+      </li>
+      <li>
+        <a href="api/">Javadoc</a> API documentation
+      </li>
+      <li>
+        <a href="CHANGELOG.md">Changelog</a>
+      </li>
+    </ul>
+
+
+<h3 id="bugs">Bug reports</h3>
+
+<p>
+If you encounter a problem, please submit a bug report so that we can fix it.
+To submit a bug report, read these
+<a href="manual/#reporting-bugs">instructions</a>, and then use the <a href="https://github.com/typetools/checker-framework/issues">Checker Framework issue tracker</a>.
+</p>
+
+
+<h3 id="mailing-lists">Mailing lists</h3>
+
+<p>
+We welcome questions, suggestions, patches, reports about case
+studies,
+and other contributions.
+Please let us know how we can improve the Checker Framework!
+</p>
+
+<ul>
+  <li>
+    <a href="https://groups.google.com/forum/#!forum/checker-framework-discuss">checker-framework-discuss</a>:
+    for general discussion about the Checker Framework for building
+    pluggable type systems
+    (<a href="https://groups.google.com/forum/#!forum/checker-framework-discuss/topics">view archives</a>,
+    <a href="https://types.cs.washington.edu/list-archives/jsr308/">view old archives</a>)
+  </li>
+  <li>
+    <a href="https://groups.google.com/forum/#!forum/checker-framework-dev">checker-framework-dev</a>:
+    to reach the developers who maintain and extend the Checker Framework
+    (<a href="https://groups.google.com/forum/#!forum/checker-framework-dev/topics">view archives</a>,
+    <a href="https://types.cs.washington.edu/list-archives/checkers/">view old archives</a>)
+  </li>
+</ul>
+
+<p>
+You can also use the mailing lists to <b>give help</b>.  Here are just a
+few examples:
+</p>
+<ul>
+  <li>Respond to questions.</li>
+  <li>Report problems (in the implementation or the documentation) or request features.</li>
+  <li>Write code, then share your bug fixes, new features, compiler plug-ins,
+      or other improvements.</li>
+  <li>Make suggestions regarding the specification.</li>
+</ul>
+
+
+<p>
+Another way to help is to tell your friends and colleagues about the
+usefulness and practicality of type annotations, or to report your
+successes to the mailing lists.
+</p>
+
+
+
+<hr />
+
+<p>
+Last updated: <!-- checker-framework-date -->3 May 2021<!-- /checker-framework-date -->
+</p>
+
+</body>
+</html>
+<!--
+IGNORE Local Variables:
+time-stamp-start: "^Last updated: "
+time-stamp-end: "\\.?$"
+time-stamp-format: "%:b %:d, %:y"
+time-stamp-line-limit: -50
+End:
+-->
+
+<!--  LocalWords:  JCP wiki classfile OpenJDK javac var NonNull subcategory SCA Sep JastAdd Regex ReIm ReImInfer JavaUI
+ -->
+<!--  LocalWords:  classfiles const changelog JLS Metadata getSize sql BNF jsr
+ -->
+<!--  LocalWords:  openjdk Inv isible isibleTypeAnnotations TypeArguments
+ -->
+<!--  LocalWords:  VariableDeclaratorRest MethodOrFieldRest TypeArgument Alast
+ -->
+<!--  LocalWords:  TypeArgumentsAnnotationsLast TypeArgumentsAnnotationsFirst
+ -->
+<!--  LocalWords:  BasicType RawBasicType NonEmpty Afirst UnmodifiableList int
+ -->
+<!--  LocalWords:  monitorTemperature TemperatureException myString myObject EE
+ -->
+<!--  LocalWords:  isNonNull instanceof myNonEmptyStringSet MyObject langtools
+ -->
+<!--  LocalWords:  rc desugar txt dev Nullable codename hg
+ -->
diff --git a/docs/developer/developer-manual.html b/docs/developer/developer-manual.html
new file mode 100644
index 0000000..758cf90
--- /dev/null
+++ b/docs/developer/developer-manual.html
@@ -0,0 +1,459 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Checker Framework developer manual</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../manual/favicon-checkerframework.png" type="image/png"/>
+</head>
+<body>
+<h1 id="Checker_Framework_developer_manual">Checker Framework developer manual</h1>
+
+<p>
+  If you wish to use the Checker Framework, see its user manual
+  (<a href="https://checkerframework.org/manual/">HTML</a>,
+  <a href="https://checkerframework.org/manual/checker-framework-manual.pdf">PDF</a>).
+</p>
+
+<p>
+This document contains information for Checker Framework developers,
+including people who wish to edit its source code or make pull requests.
+</p>
+
+<p>Contents:</p>
+<!-- start toc.  do not edit; run html-update-toc instead -->
+    <ul>
+      <li><a href="#Directory_structure">Directory structure</a>
+        <ul>
+          <li><a href="#Related_repositories">Related repositories</a></li>
+        </ul></li>
+      <li><a href="#Build_tasks">Build tasks</a></li>
+      <li><a href="#tests">Testing the Checker Framework</a>
+        <ul>
+          <li><a href="#testing-optimizations">Testing optimizations</a></li>
+        </ul></li>
+      <li><a href="#Code_style">Code style</a></li>
+      <li><a href="#IDE_configuration">IDE configuration</a></li>
+      <li><a href="#pull-requests">Pull requests</a>
+        <ul>
+          <li><a href="#pull-requests-branches">Branches</a></li>
+          <li><a href="#pull-requests-maintainers">Pull request and commit notes for maintainers</a></li>
+        </ul></li>
+      <li><a href="#github-configuration">GitHub configuration</a></li>
+      <li><a href="#ci">Continuous Integration</a>
+        <ul>
+          <li><a href="#ci-failure">What to do if a Continuous Integration build fails</a></li>
+        </ul></li>
+      <li><a href="#Documenting_refactoring_ideas">Documenting refactoring ideas</a></li>
+      <li><a href="#annotated-library-version-numbers">Version numbers for annotated libraries</a></li>
+      <li><a href="#Making_a_Checker_Framework_release">Making a Checker Framework release</a></li>
+    </ul>
+<!-- end toc -->
+
+
+<h2 id="Directory_structure">Directory structure</h2>
+
+<p>
+The <a href="https://github.com/typetools/checker-framework">checker-framework
+repository</a> contains several related projects:
+</p>
+
+<dl>
+  <dt><code>framework</code></dt>
+  <dd>the framework that enables building pluggable type checkers</dd>
+
+  <dt><code>checker</code></dt>
+  <dd>the type checkers provided with the Checker Framework</dd>
+
+  <dt><code>javacutil</code></dt>
+  <dd>utilities for integrating with javac</dd>
+
+  <dt><code>dataflow</code></dt>
+  <dd>a dataflow framework that is used by the Checker Framework, <a href="https://errorprone.info">Error Prone</a>, <a href="https://github.com/uber/NullAway">NullAway</a>, and other tools</dd>
+</dl>
+
+<p>
+The repository also contains the following directories:
+</p>
+<dl>
+  <dt><code>docs</code></dt>
+  <dd>documentation: manual, tutorial, examples, developer docs</dd>
+</dl>
+
+
+<h3 id="Related_repositories">Related repositories</h3>
+
+<p>
+  The <a href="https://github.com/typetools/checker-framework"><code>checker-framework</code></a> project
+  depends on
+  the <a href="https://github.com/typetools/jdk"><code>typetools/jdk</code></a> project,
+  the <a href="https://github.com/typetools/annotation-tools"><code>annotation-tools</code></a> project,
+  and the <a href="https://github.com/typetools/stubparser"><code>stubparser</code></a> project.
+When making changes to one of these projects, you may also need to make changes to one or more of the others.
+</p>
+
+<p>
+If you make related changes in the <code>checker-framework</code> and <code>jdk</code>
+repositories, use the <em>same
+branch name</em> for each.  The continuous integration framework will find
+and use that branch when running tests.
+For example, when continuous integration runs for branch <em>B</em> of fork <em>F</em> of <code>checker-framework</code>, it will use branch <em>B</em> of fork <em>F</em> of the other repositories (if they exist).
+The same is true for the other projects.
+</p>
+
+<p>
+If a change spans multiple projects, make pull requests for all of them.  Each pull request description's should link to all the others.
+</p>
+
+<p>
+Furthermore, whenever you make a pull request from
+a <code>checker-framework</code> branch A into branch B, if B has a
+corresponding <code>jdk</code>
+branch, then A also needs one (even if identical to B's version).
+</p>
+
+
+<h2 id="Build_tasks">Build tasks</h2>
+
+<p>
+Full instructions for building the Checker Framework from sources appear in
+the <a href="https://checkerframework.org/manual/#build-source">Checker
+Framework manual</a>. This section describes the build system (the Gradle build tasks).
+</p>
+
+<p>
+Don't run the <code>gradle</code> command, which would use whatever version
+of Gradle is installed on your computer.  Instead, use
+the <code>gradlew</code> script in the <code>checker-framework</code>
+directory, also known as
+the <a href="https://docs.gradle.org/current/userguide/gradle_wrapper.html">Gradle
+wrapper</a>.
+</p>
+
+<p>
+Frequently-used tasks:
+</p>
+<ul>
+  <li> <code>assemble</code>: builds all jars.
+  <li> <code>build</code>: <code>assemble</code>, plus runs all JUnit tests.
+  <li> <code>allTests</code>: runs all tests.
+  <li> <code>reformat</code>: reformats Java files.
+  <li> <code>NameOfJUnitTest</code>: runs the JUnit test with that name; for example, <code>NullnessTest</code>.
+  <li> <code>task</code>: lists tasks; use <code>--all</code> to see all tasks.
+</ul>
+
+<p>
+If you run a task from the main directory, then it will run that task in all
+subprojects with a task by that name. So, if you run <code>./gradlew
+allTests</code> that runs all tests everywhere. But <code>(cd
+framework &amp;&amp; ../gradlew allTests)</code> only runs tests in
+the <code>framework</code> project.
+Alternatively, running <code>:framework:allTests</code> from the
+main directory or any subproject runs the <code>allTests</code> task only in the <code>framework</code> project.
+</p>
+
+
+<h2 id="tests">Testing the Checker Framework</h2>
+
+<p>
+For writing new test cases, see file <a href="https://raw.githubusercontent.com/typetools/checker-framework/master/checker/tests/README"><code>checker/tests/README</code></a>.
+</p>
+
+
+<h3 id="testing-optimizations">Testing optimizations</h3>
+
+<p>
+To test an optimization that should speed up type-checking, see
+the <code>test-daikon.sh</code> stage of the <code>daikon_jdk8</code> job
+of the Azure Pipelines CI job.  Compare the run time of this stage (or of
+the entire <code>daikon_jdk8</code> job) between the master branch and a
+branch with your improvements.
+</p>
+
+<p>
+You can also compare run times of the Checker Framework test suite.
+</p>
+
+
+<h2 id="Code_style">Code style</h2>
+
+<p>
+Code in this project follows the
+<a href="https://google.github.io/styleguide/javaguide.html">Google Java Style
+  Guide</a> (except 4 spaces are used for indentation),
+<a href="https://homes.cs.washington.edu/~mernst/advice/coding-style.html">Michael
+Ernst's coding style guidelines</a>, and <a href="http://www.oracle.com/technetwork/java/javase/documentation/codeconventions-136091.html#248">Oracle's
+Java code conventions</a>.
+</p>
+
+<p>
+From the command line, you can format your code by running <code>./gradlew reformat</code>.
+You
+can <a href="https://github.com/google/google-java-format#using-the-formatter">configure
+your IDE</a> (Eclipse or IntelliJ) to use the formatting (don't forget the
+non-standard <code>-a</code> flag).
+</p>
+
+<p>
+We don't use <code>@author</code> Javadoc tags in code files.
+Doing so clutters the code, and is misleading once that individual
+is no longer maintaining the code.
+Authorship (and who has made changes recently) can be obtained from the
+version control system, such as by running <code>git annotate <em>filename</em></code>.
+</p>
+
+<p>
+  Every class, method, and field (even private ones) must have a
+  descriptive Javadoc comment.
+</p>
+
+
+<h2 id="IDE_configuration">IDE configuration</h2>
+
+<p>
+First clone and build all projects from their sources, from the command line,
+using the instructions
+at <a href="https://checkerframework.org/manual/#build-source">https://checkerframework.org/manual/#build-source</a>.
+After that succeeds, import the projects into your IDE as Gradle projects.
+</p>
+
+<p>
+If your IDE cannot find <code>com.sun.source.*</code> packages, try changing the project JDK to JDK 11.
+</p>
+
+
+<h2 id="pull-requests">Pull requests</h2>
+
+<p>
+Each pull request should address a single concern, rather than (say)
+addressing multiple concerns such as fixing a bug, adding a feature, <em>and</em>
+changing formatting.  Focusing each pull request on a single concern makes
+the commit easier to understand and review.  It also makes the commit
+history (after the pull request is merged) easier to understand and (if
+necessary) revert.
+</p>
+
+<p>
+The pull request title should clearly explain the change.  It will be used
+as the commit message for your change.  If the pull request fixes an issue
+in the issue tracker, its title should end with "; fixes #NNN" where NNN is
+the fixed issue.
+</p>
+
+<p>
+  Your pull request (whether it is a bug fix or a new feature) should
+  include tests that fail before your change and pass afterward.
+</p>
+
+<p>
+If you make a user-visible change, update the manual (or add a new section)
+and add a brief description at the top of the changelog
+(file <code>docs/CHANGELOG.md</code>).
+</p>
+
+<p>
+To reduce iterations of code review, please follow
+the <a href="#code-style">coding conventions</a>.
+Also enable <a href="#ci">continuous integration</a> on your fork of the Checker
+Framework and ensure that it passes before you open a pull request.
+</p>
+
+<p>
+A pull request marked as "draft" means it should not be reviewed.  To use a
+"draft" pull request for discussions, make it in a fork.  If it were in
+the <code>typetools</code> GitHub organization, it would use CI resources
+and thus slow down CI feedback for other pull requests.
+</p>
+
+<p>
+Also
+see <a href="https://homes.cs.washington.edu/~mernst/advice/github-pull-request.html">Michael
+Ernst's advice about creating GitHub pull requests</a>.
+</p>
+
+
+<h3 id="pull-requests-branches">Branches</h3>
+
+<p>
+  It is good style to create a branch (in your fork of the Checker
+  Framework GitHub repository) for each independent change.
+  Do not make changes to your <code>master</code> branch.
+  If you have write access to the <code>typetools</code> repository, don't
+  work in a branch of it, because such a branch competes for CI resources
+  with all pull requests.
+</p>
+
+<p>
+Azure Pipelines has a bug:  whenever two CI jobs would run code with the same
+commit hash, it re-uses a previous execution result.  This is a bug because the
+CI job's behavior may depend on the branch name and other factors that are
+independent of the commit hash.  This means that you may see spurious successes
+or failures when your branch of (say) the <code>jdk</code> repository has the
+identical commit hash to some other branch that Azure Pipelines previous ran a
+job for.
+</p>
+
+
+<h3 id="pull-requests-maintainers">Pull request and commit notes for maintainers</h3>
+
+<p>
+It is acceptable to commit small, noncontroversial changes directly to
+master.  (This policy differs from some projects, which require an issue
+tracker issue and a pull request for every change, however minor.)
+As with pull requests, each commit should address a single concern.
+For any change where you want feedback, or where others might have
+useful comments or might disagree, please submit a pull request.  Be
+conservative in your judgment; others might consider something
+controversial that you do not.
+</p>
+
+<p>
+Try to review pull requests promptly, to avoid stalling others while
+waiting for your feedback.  If you have been waiting for more than a week
+after the pull request was assigned with no feedback, then ping the
+assignee, wait at least another business day, and then go ahead and push
+your changes.  It's great if reviewers can give feedback, but if they are
+too busy to do so, you should recognize that and move on.
+</p>
+
+
+<h2 id="github-configuration">GitHub configuration</h2>
+
+<p>
+When you installed Git, you should have set your name and email address.  If you have not yet done so, do it now:
+</p>
+<pre>
+git config --global user.name "<em>FIRST_NAME LAST_NAME</em>"
+git config --global user.email "<em>USERNAME</em>@<em>SOMEDOMAIN.COM</em>"
+</pre>
+
+<p>
+Before you make any commits (even to your own fork),
+update your GitHub account profile so it contains your complete name.
+This is necessary to include you in
+the <a href="https://checkerframework.org/manual/#credits">list of
+contributors</a>.
+</p>
+
+<p>
+You will want your own GitHub fork of any project you plan to modify.  We
+recommend that, for each fork, you configure GitHub
+to <a href="https://help.github.com/en/github/administering-a-repository/managing-the-automatic-deletion-of-branches">delete
+branches after they are merged</a>.
+</p>
+
+
+<h2 id="ci">Continuous Integration</h2>
+
+<p>
+The Checker Framework has continuous integration jobs that run in Azure
+Pipelines, CircleCI, and/or Travis CI on each push to GitHub.
+We recommend Azure Pipelines.
+</p>
+
+<p>
+  To enable Azure Pipelines continuous integration for your fork:
+  (This is a summary of the <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/create-first-pipeline?view=azure-devops">Azure
+  Pipelines getting started directions</a>.)
+</p>
+<ul>
+  <li>Browse to <a href="https://dev.azure.com/">dev.azure.com</a>.
+      (You might need to create a (free) account.)</li>
+  <li>Click "Create a project to get started" and enter an appropriate name</li>
+  <li>Click "public".</li>
+  <li>Click "create project"</li>
+  <li>At the left, click the blue rocket labeled "pipelines"</li>
+  <li>Click "new pipeline"</li>
+  <li>Click "GitHub"
+      (You might need to authorize the app on GitHub.)</li>
+  <li>Click "Select a repository"</li>
+  <li>Choose <em>MYUSERID</em>/checker-framework</li>
+  <li>Choose the default radio button, "only selected repositories".  <b>Do not</b> choose "all repositories".</li>
+  <li>Choose "Existing Azure Pipelines YAML file"</li>
+  <li>Select "/azure-pipelines.yml" in the dropdown menu</li>
+  <li>Approve and install.</li>
+  <li>Click "run".</li>
+</ul>
+
+<p>
+  To enable Travis CI continuous integration for your fork:
+</p>
+<ul>
+  <li>Browse to <a href="https://travis-ci.com/">travis-ci.com</a></li>
+  <li>Click the downarrow for the dropdown, near your face in the upper right corner</li>
+  <li>Click settings</li>
+  <li>Find "checker-framework" in the list, and click the slider to enable it.</li>
+</ul>
+
+
+<h3 id="ci-failure">What to do if a Continuous Integration build fails</h3>
+
+<p>
+Sometimes, CI tests for your pull request may fail even though your local build passed.
+This is usually because the CI service performed more tests than you ran locally.
+</p>
+
+<p>
+First, examine the CI service's logs, which contain diagnostic output from the
+failing command.  You can determine which command was run from the logs, or
+from the CI configuration file (such as <code>azure-pipelines.yml</code>).
+</p>
+
+
+<h2 id="Documenting_refactoring_ideas">Documenting refactoring ideas</h2>
+
+<p>
+Don't open issues for code improvement ideas (such as potential refactorings).
+If it can be described concisely and is unlikely to be rediscovered by other
+people, write a TODO comment in the code.  The code comment is more likely to be
+noticed by someone
+working with the code, and it is equally easy to search for.  Furthermore,
+it doesn't clutter the issue tracker.  Clutter in the issue tracker reduces
+morale, makes it harder to search, and makes the project appear
+lower-quality than it actually is.
+</p>
+
+
+<h2 id="annotated-library-version-numbers">Version numbers for annotated libraries</h2>
+
+<p>
+  We maintain annotated versions of some third-party libraries.  The source
+  code appears in a fork in
+  the <a href="https://github.com/typetools">GitHub <code>typetools</code>
+  organization</a>.  Binaries are hosted
+  at <a href="https://search.maven.org/search?q=annotatedlib">Maven Central
+  in the <code>org.checkerframework.annotatedlib</code> group</a>.
+</p>
+
+<p>
+Annotated libraries should be based on a released version of the upstream
+library, not an arbitrary commit in the upstream library's version control
+system.  The library's version number is the same as the upstream version
+number.
+</p>
+
+<p>
+When making a new version of an annotated library, between upstream
+releases, add ".0.1" to the end of the version number.  For example, if we
+already uploaded version 6.2 to Maven Central, the next version we upload
+would be 6.2.0.1.  This accommodates the possibility that the upstream
+maintainers release 6.2.1.  Our further releases increment the last number,
+for example to 6.2.0.2.
+</p>
+
+
+<h2 id="Making_a_Checker_Framework_release">Making a Checker Framework release</h2>
+
+<p>
+See a separate document about the
+<a href="release/README-release-process.html">Checker Framework release process</a>.
+</p>
+
+
+</body>
+</html>
+
+<!--  LocalWords:  TODO javacutil gradle javadoc reformats subprojects pre NullAway CircleCI travis ci downarrow dropdown MYUSERID NNN doesn
+ -->
+<!--  LocalWords:  subproject personalblog changelog config SOMEDOMAIN YAML
+ -->
diff --git a/docs/developer/gsoc-ideas-old-html b/docs/developer/gsoc-ideas-old-html
new file mode 100644
index 0000000..165ae43
--- /dev/null
+++ b/docs/developer/gsoc-ideas-old-html
@@ -0,0 +1,629 @@
+<h2 id="Avoiding_exponential_blowup_when_processing_DAGs">Avoiding exponential blowup when processing DAGs</h2>
+
+<!-- John Field of Google is interested in this. -->
+
+<p>
+Google's <a href="https://bazel.build/">Bazel</a> open-source project is a
+publicly-released version of their build system, Blaze.  Blaze builds every
+line of source code that is written by any Google programmer &mdash; all of
+that source code appears in a single repository!  Therefore, Bazel/Blaze needs to
+be fast.  Bazel represents all of the source code and its dependencies as a
+large DAG (a
+<a href="https://en.wikipedia.org/wiki/Directed_acyclic_graph">directed
+acyclic graph</a>).  It needs to manipulate these DAGs efficiently.
+</p>
+
+<p>
+One of the biggest problems that the Bazel developers face is exponential
+blowup of DAG sizes and therefore of run time.  Periodically, one of the
+Bazel developers makes such a mistake, and Bazel becomes unusable until
+they can diagnose and fix the problem.
+</p>
+
+<p>
+Here are two different ways to view the problem.
+</p>
+
+<ol>
+  <li>
+    In a DAG, multiple nodes may have the same child.  Traversing the DAG
+    naively would visit the child multiple times &mdash; in the worst case,
+    exponentially many times.  It is necessary to avoid doing so.
+  </li>
+  <li>
+    Bazel contains a function that takes a DAG as input and generates a DAG
+    as output (the output is an object graph).  The Bazel developers want to
+    ensure that the size of the output DAG is O(|input DAG|). The input DAG is
+    processed bottom-up (it ensures that each input node is visited once) and
+    Bazel stores the results of intermediate computations that construct the
+    output DAG with nodes of the input DAG. The key thing the Bazel developers
+    want to avoid is copying intermediate subgraphs that have unbounded size.
+  </li>
+</ol>
+
+<p>
+More concretely, there is only one Java type for all DAGs, and there is a
+method <code>flatten()</code>.  It's a mistake to call
+<code>flatten()</code> on certain DAGs, because doing so may cause
+exponential blowup.
+</p>
+
+<!--
+</p>
+
+<p>
+A pluggable type system can subdivide the Java type, using this type hierarchy:
+</p>
+
+<p>
+  @PossbilyPropagatable
+          |
+  @NotPropagatable
+</p>
+
+<p>
+A call to flatten() is permitted only if the receiver is known to be
+@NotPropagatable.
+</p>
+
+<p>
+This should be very easy to code up and evaluate, once we have more precise
+definitions.
+-->
+
+<p>
+The goal of this project would be to better understand the problem with
+Bazel, to formalize it, and to create a program analysis that solves
+the problem.  You would evaluate your work by running it on the Bazel
+codebase to discover latent problems, or by providing it to the Bazel
+developers to run each time they propose a code change.  The Bazel
+team is interested in collaborating by evaluating a tool.
+</p>
+
+
+<h2 id="case-study-index-out-of-bounds">Array indexing (index-out-of-bounds errors)</h2>
+
+<p>
+An index-out-of-bounds error occurs when a programmer provides an illegal
+index to an array or list, as in <code>a[i]</code> or <code>a.get(i)</code>
+where <code>i</code> is less than 0 or greater than the length of
+<code>a</code>.  In languages like C, this is disastrous:  buffers
+overflows lead to about 1/6 of all security vulnerabilities.  In languages
+like Java, the result is &ldquo;merely&rdquo; that the program crashes.  In
+both languages, it is desirable to prevent programmers from making this
+error and to prevent users from suffering the bad consequences.
+</p>
+
+<p>
+This project will be a substantial case study with
+the <a href="https://checkerframework.org/manual/#index-checker">Index
+Checker</a>.  The first goal is to identify its merits and limitations.
+Does it scale up to big,
+interesting programs?  Are there common, important code patterns that it
+fails to handle?  Does it produce too many false positive warnings?  Does
+it place too heavy a burden on the user, either in terms of annotations or
+in terms of complexity of the error messages?  Worst of all, are there
+unknown unsoundnesses in the tool?
+The second goal is to improve its precision enough to make it usable by
+real-world programmers.
+</p>
+
+<p>
+A <a href="#index-checker-mutable-length">related project</a> is to extend
+the Index Checker to handle
+mutable collections such as
+<code>List</code>s, where the <code>remove()</code> method makes sound,
+precise analysis very tricky.
+</p>
+
+
+<h3 id="case-study-nullness-bazel">Bazel tool</h3>
+
+<!-- John Field of Google is interested in this. -->
+
+<p>
+This project is related to the
+<a href="https://bazel.build/">Bazel</a> build system, and was
+proposed by its development manager.
+</p>
+
+<p>
+The Bazel codebase contains 1586 occurrences of the <code>@Nullable</code>
+annotation.  This annotation indicates that a variable may hold a null
+value.  This is valuable documentation and helps programmers avoid null
+pointer exceptions that would crash Bazel.  However, these annotations are
+not checked by any tool.  Instead, programmers have to do their best to
+obey the <code>@Nullable</code> specifications in the source code.  This is
+a lost opportunity, since documentation is most useful when it is
+automatically processed and verified.  (For several years, Google tried
+using <a href="http://findbugs.sourceforge.net/">FindBugs</a>, but they
+eventually abandoned it:  its analysis is too weak, suffering too many
+false positives and false negatives.)
+</p>
+
+<p>
+Despite the programmers' best efforts, null pointer exceptions do still
+creep into the code, impacting users.  The Bazel developers would like to
+prevent these.  They want a guarantee, at compile time, that no null
+pointer exceptions will occur at run time.
+</p>
+
+<p>
+Such a tool already exists:  the
+<a href="https://checkerframework.org/manual/#nullness-checker">Nullness
+Checker</a> of the <a href="https://checkerframework.org/">Checker
+Framework</a>.  It runs as a compiler plug-in, and it issues a warning at
+every possible null pointer dereference.  If it issues no warnings, the
+code is guaranteed not to throw a <code>NullPointerException</code> at run time.
+</p>
+
+<p>
+The goal of this project is to do a large-scale case study of the Nullness
+Checker on Bazel.  The main goal is to understand how the Nullness Checker
+can be used on a large-scale industrial codebase.  How many lurking bugs
+does it find?  What
+<a href="https://checkerframework.org/releases/1.9.13/api/org/checkerframework/checker/nullness/qual/Nullable.html"><code>@Nullable</code></a>
+annotations are missing from the codebase because the developers failed to
+write them?  What are its limitations, such as code patterns that it cannot
+recognize as safe?  (You might create new analyses and incorporating them
+into the Nullness Checker, or you might just reporting bugs to the Nullness
+Checker developers for fixing.)  What burdens does it place on users?  Is
+the cost-benefit tradeoff worth the effort &mdash; that is, should Google
+adopt this tool more broadly?  How should it be improved?  Are the most
+needed improvements in the precision of the analysis, or in the UI of the
+tooling?
+</p>
+
+
+<h3 id="case-study-nullness-bcel">BCEL library</h3>
+
+<p>
+  Annotate the BCEL library to express its contracts with respect to nullness.
+  Show that the BCEL library has no null pointer exceptions (or find bugs
+  in BCEL).  There are
+  already <a href="https://github.com/apache/commons-bcel/compare/trunk...typetools:trunk?expand=1">some
+  annotations</a> in BCEL, but they have not been verified as correct by
+  running the Nullness Checker on BCEL.  (Currently, those annotations are
+  trusted when type-checking clients of BCEL.)
+</p>
+
+<p>
+  To get started:
+</p>
+
+<ul>
+  <li>Fork https://github.com/typetools/commons-bcel.git</li>
+  <li>Clone your new fork</li>
+  <li><code>git checkout typecheck-nullness</code></li>
+  <li>mvn verify</li>
+</ul>
+
+<p>
+   Some challenging aspects of this case study are:
+</p>
+
+<ul>
+  <li>
+    There is some poor design that needs to be resolved in discussions with
+    the BCEL maintainers.  For example, consider the <code>copy()</code>
+    method.  Some implementations of <code>copy()</code> return null, but
+    are not documented to do so.  In addition, some implementations
+    of <code>copy()</code> catch and ignore exceptions.  I think it would
+    be nicest to change the methods to never return null, but to throw an
+    exception instead.  (This is no more burdensome to users, who currently
+    have to check for null.)  Alternately, the methods could all be
+    documented to return null.
+  </li>
+</ul>
+
+
+
+<h2 id="your-own-new-type-system">Invent your own new type system</h2>
+
+<p>
+We also welcome your ideas for new type systems.  For example, any run-time
+failure can probably be prevented at compile time with the right
+analysis.  Can you come up with a way to fix your pet peeve?
+</p>
+
+<p>
+It is easiest, but not required, to choose an existing type system from the
+literature, since that means you can skip the design stage and go right to
+implementation.
+</p>
+
+<p>
+This task can be simple or very
+challenging, depending on how ambitious the type system is.  Remember to
+focus on what helps a software developer most!
+</p>
+
+
+
+<h2 id="Bounded-size_strings">Bounded-size strings</h2>
+
+<!-- John Field of Google is interested in this. -->
+
+<p>
+Windows cannot run command lines longer than 8191 characters.  Creating a
+too-long command line causes failures when the program is run on Windows.
+These failures are irritating when discovered during testing, and
+embarrassing or worse when discovered during deployment.  The same command
+line would work on Unix, which has longer command-line limits, and as a
+result developers may not realize that their change to a command can cause
+such a problem.
+</p>
+
+<p>
+Programmers would like to enforce that they don't accidentally pass a
+too-long string to the <code>exec()</code> routine.  The goal of this
+project is to give a compile-time tool that provides such a guarantee.
+</p>
+
+<p>
+Here are two possible solutions.
+</p>
+
+<p>
+<b>Simple solution:</b>
+For each array and list, determine whether its length is known at compile
+time.  The routines that build a command line are only allowed to take such
+constant-length lists, on the assumption that if the length is constant,
+its concatenation is probably short enough.
+</p>
+
+<p>
+<b>More complex solution:</b>
+For each String, have a compile-time estimate of its maximum length.  Only
+permit <code>exec()</code> to be called on strings whose estimate is no more than 8191.
+String concatenation would return a string whose estimated size is the sum
+of the maximums of its arguments, and likewise for concatenating an array
+or list of strings.
+</p>
+
+
+<h2 id="lock-ordering">Lock ordering</h2>
+
+<p>
+The <a href="https://checkerframework.org/manual/#lock-checker">Lock
+Checker</a> prevents race conditions by ensuring that locks are held when
+they need to be.  It does not prevent deadlocks that can result from locks
+being acquired in the wrong order.  This project would extend the Lock
+Checker to address deadlocks, or create a new checker to do so.
+</p>
+
+<p>
+Suppose that a program contains two different locks.  Suppose that one
+thread tries to acquire lockA then lockB, and another thread tries to
+acquire lockB then lockA, and each thread acquires its first lock.  Then
+both locks will wait forever for the other lock to become available.  The
+program will not make any more progress and is said to
+be <a href="https://en.wikipedia.org/wiki/Deadlock">deadlocked</a>.
+</p>
+
+<p>
+If all threads acquire locks in the same order &mdash; in our example, say
+lockA then lockB &mdash; then deadlocks do not happen.  You will extend the
+Lock Checker to verify this property.
+</p>
+
+
+<h2 id="asm">Upgrade to a newer version of ASM</h2>
+
+<p>
+  The
+  <a href="https://checkerframework.org/annotation-file-utilities/">Annotation
+  File Utilities</a>, or AFU, insert annotations into, and extract
+  annotations from, <code>.java</code> files, <code>.class</code> files,
+  and text files.  These programs were written before the
+  <a href="https://asm.ow2.org/">ASM</a> bytecode library supported Java 8's
+  type annotations.  Therefore, the AFU has its own custom version of ASM
+  that supports type annotations.  Now that ASM 6 has been released and it
+  supports type annotations, the AFU needs to be slightly changed to use
+  the official ASM 6 library instead of its own custom ASM variant.
+</p>
+
+<p>
+  This project is a good way to learn about <code>.class</code> files and
+  Java bytecodes:  how they are stored, and how to manipulate them.
+</p>
+
+<p>
+  (Kush Gupta is working on this project.)
+</p>
+
+
+<h2 id="typestate">Stateful type systems</h2>
+
+<p>
+This project is to improve support for
+<a href="https://checkerframework.org/manual/#typestate-checker">typestate checking</a>
+</p>
+
+<p>
+Ordinarily, a program variable has
+the same type throughout its lifetime from when the variable is declared
+until it goes out of scope. "Typestate"
+permits the type of an object or variable to <em>change</em> in a controlled way.
+Essentially, it is a combination of standard type systems with dataflow
+analysis. For instance, a file object changes from unopened, to opened, to
+closed; certain operations such as writing to the file are only permitted
+when the file is in the opened typestate. Another way of saying this is
+that <code>write</code> is permitted after <code>open</code>, but not after <code>close</code>.
+Typestate
+is applicable to many other types of software properties as well.
+</p>
+
+<p>
+Two <a href="https://checkerframework.org/manual/">typestate checking frameworks</a>
+exist for the Checker Framework.  Neither is being maintained; a new one
+needs to be written.
+</p>
+
+
+<h2 id="analysis_diffs">Tool for analysis diffs</h2>
+<!--
+  This project idea is duplicated at
+  ~mernst/public_html/uw-only/research/potential-research-projects.html .
+-->
+
+<p>
+   Many program analyses are too verbose for a person to read their entire
+   output.  However, after a program change, the analysis results may
+   change only slightly.  An "analysis diff" tool could show the
+   difference between the analysis run on the old code and the analysis run
+   on the new code.
+</p>
+<ul>
+  <li>
+    The analysis diffs may help the programmer to better understand the
+    changes.
+  </li>
+  <li>
+    Bug detection tools. such
+    as <a href="http://findbugs.sourceforge.net/">FindBugs</a> or
+    the <a href="https://checkerframework.org/">Checker Framework</a>, have
+    extremely verbose output when first run on a program.  Programmers
+    could examine and fix only the warnings about code they have changed
+    (and that they are currently thinking about).
+  </li>
+  <li>
+    Tools that always have large output, such as inference tools, could
+    become manageable to users if output is shown in small doses.
+  </li>
+  <li>
+    You can probably think of other uses.
+  </li>
+</ul>
+
+<p>
+  The analysis diff tool would take as input two analysis results (the
+  previous and the current one).  It would output only the new parts of its
+  second input.  (It could optionally output a complete diff between two
+  analysis results.)
+</p>
+
+<p>
+  One challenge is dealing with changed line numbers and other analysis
+  output differences between runs.
+</p>
+
+<p>
+It would be nice to integrate the tool with git pre-commit hooks or GitHub
+pull requests, to enable either of the following functionality (for either
+commits to master or for pull requests):
+</p>
+<ul>
+  <li>
+    Permit only those commits/pulls that do not add any new analysis warnings.
+  </li>
+  <li>
+    Permit only those commits/pulls that are "clean" &mdash; the analysis
+    issues no warnings for any changed line.
+  </li>
+</ul>
+
+<p>
+  A concrete example of an analysis diff tool
+  is <a href="https://github.com/plume-lib/checklink/blob/master/checklink-persistent-errors">checklink-persistent-errors</a>;
+  see the documentation at the top of the file.  That tool only works for
+  one particular analysis, the W3C Link Checker.
+  An analysis diff tool also appears to be built into FindBugs.
+  The goal of this project is to build a general-purpose tool that is easy
+  to apply to new analyses.
+</p>
+
+
+<h2 id="performance">Performance improvements</h2>
+
+<p>
+  The Checker Framework runs much slower than the standard javac compiler
+  &mdash; often 20 times slower!  This is not acceptable as part of a
+  developer's regular process, so we need to speed up the Checker
+  Framework.  This project involves determining the cause of slowness in
+  the Checker Framework, and correcting those problems.
+</p>
+
+<p>
+This is a good way to learn about performance tuning for Java applications.
+</p>
+
+<p>
+  Some concrete tasks include:
+</p>
+
+<ul>
+  <li>Profile the Checker Framework.  Run a profiler such as
+  <a href="https://www.yourkit.com/java/profiler/">YourKit</a> to determine
+  which parts of the Checker Framework consume the most CPU time and memory.
+  </li>
+
+  <li>Perhaps compare a profile of the Checker Framework against a profile
+  of regular javac.  This probably is not necessary because the Checker
+    Framework is so much slower than regular javac.
+  </li>
+
+  <li>Consider interning string values.  The Checker Framework does a fair
+  amount of string manipulation, in part because it reads from resources
+  such as stub files that do not produce <code>Element</code>s.  Interning
+  could save time when doing comparisons.  You can verify the correctness
+  of the optimization by running the
+  <a href="https://checkerframework.org/manual/#interning-checker">Interning
+    Checker</a> on the Checker Framework code.  Compare the run time of the
+  Checker Framework before and after this optimization.
+  </li>
+
+  <li> Based on profiling results, devise other optimizations, implement
+    them, and evaluate them.
+  </li>
+</ul>
+
+
+<h2 id="run-time-checking">Run-time checking</h2>
+
+<p>
+Implement run-time checking to complement compile-time checking.  This will
+let users combine the power of static checking with that of dynamic
+checking.
+</p>
+
+<p>
+Every type system is too strict: it rejects some programs that never go
+wrong at run time. A human must insert a type loophole to make such a
+program type-check.  For example, Java takes this approach with its
+cast operation (and in some other places).
+</p>
+
+<p>
+When doing type-checking, it is desirable to automatically insert run-time
+checks at each operation that the static checker was unable to verify.
+(Again, Java takes exactly this approach.)  This guards against mistakes by
+the human who inserted the type loopholes.  A nice property of this
+approach is that it enables you to prevent errors in a program
+with no type annotations:  whenever the static checker is unable
+to verify an operation, it would insert a dynamic check.  Run-time checking
+would also be useful in verifying whether the suppressed warnings are
+correct &mdash; whether the programmer made a mistake when writing them.
+</p>
+
+<p>
+The annotation processor (the pluggable type-checker) should automatically
+insert the checks, as part of the compilation process.
+</p>
+
+<p>
+There should be various modes for the run-time checks:
+</p>
+<ul>
+  <li>fail immediately.</li>
+  <li>logging, to permit post-mortem debugging without crashing the program.</li>
+</ul>
+
+<p>
+The run-time penalty should be small:  a run-time check is necessary only
+at the location of each cast or suppressed warning.  Everywhere that the
+compile-time checker reports no possible error, there is no need to insert a
+check.  But, it will be an interesting project to determine how to minimize
+the run-time cost.
+</p>
+
+<p>
+Another interesting, and more challenging, design question is whether you need to add and maintain
+a run-time representation of the property being tested.  It's easy to test
+whether a particular value is null, but how do you test whether it is
+tainted, or should be treated as immutable?  For a more concrete example,
+see the discussion of the (not yet implemented)
+[Javari run-time checker](http://pag.csail.mit.edu/pubs/ref-immutability-oopsla2005-abstract.html).
+Adding this run-time support would be an interesting and challenging project.
+</p>
+
+<p>
+We developed a prototype for the
+<a href="https://ece.uwaterloo.ca/~wdietl/publications/pubs/EnerJ11-abstract.html">EnerJ runtime system</a>.
+That code could be used as starting point, or you could start afresh.
+</p>
+
+<p>
+In the short term, this could be prototyped as a source- or
+bytecode-rewriting approach; but integrating it into the type checker is a
+better long-term implementation strategy.
+</p>
+
+
+
+<h2 id="ide-support">IDE and build system support</h2>
+
+<p>
+The Checker Framework comes with support for
+<a href="https://checkerframework.org/manual/#external-tools">external tools</a>,
+including both IDEs (such as an Eclipse plug-in) and build tools
+(instructions for Maven, etc.).
+</p>
+
+<p>
+  These plug-ins and other integration should be improved.
+  We have a number of concrete ideas, but you will also probably come up
+  with some after a few minutes of using the existing IDE plugins!
+</p>
+
+<p>
+  This is only a task for someone who is already an expert, such as someone
+  who has built IDE plugins before or is very familiar with the build system.
+  One reason is that these tools tend to be complex, which can lead to
+  subtle problems.
+  Another reason is that we don't want to be stuck maintaining code written by
+  someone who is just learning how to write an IDE plugin.
+</p>
+
+<p>
+  Rather than modifying the Checker Framework's existing support or
+  building new support from scratch, it may be better to adapt some other
+  project's support for build systems and IDEs.  For instance, you might
+  make <a href="https://github.com/coala/coala">coala</a> support the
+  Checker Framework, or you might adapt the tool integration provided
+  by <a href="http://errorprone.info/">Error Prone</a>.
+</p>
+
+
+<!--
+Integrate the Checker Framework with an IDE such as Eclipse, IntelliJ, IDEA or NetBeans,
+which will make pluggable type-checking easier to use and more attractive to developers.
+
+Some specific projects include:
+  * Create an IDE plug-in that invokes the checker and reports any errors to the developer, just as the IDE currently does for type errors.
+  * Improve the existing Eclipse plug-in.
+  * Add a button to the IDE that hides/shows all annotations of a given variety, to reduce clutter.  This would be useful beyond type annotations.
+  * Implement quick fixes for common errors.
+  * Integrate type inference with the IDE, to make it easier for a developer to add annotations to code.
+  * Improve an IDE's existing refactoring support so that it is aware of, and properly retains, type annotations.
+  * Highlight defaulted types and flow-sensitive type refinements, making error messages easier to understand.
+  * Highlight the parts of the code that influenced flow-sensitive type refinements, again to make errors easier to comprehend.
+
+IDEs that build on the OpenJDK Java compiler could benefit from even tighter integration with the Checker Framework.
+
+This project may entail the following:
+
+  * familiarity with the IDE plug-in development environment, Eclipse PDT or NetBeans plug-in support:  By the end of the project, the developer should be familiar with building reasonably complex IDE plug-ins.
+
+  * UI/UX design:  The developer would design a UI experience that is appealing to developers and integrates well with the developer's workflow!
+-->
+
+
+<h2 id="exhaustive-testing">Model checking of a type system</h2>
+
+<p>
+Design and implement an algorithm to check type soundness of a type system
+by exhaustively verifying the type checker on all programs up to a certain
+size. The challenge lies in efficient enumeration of all programs and
+avoiding redundant checks, and in knowing the expected outcome of the
+tests.  This approach is related to bounded exhaustive
+testing and model checking; for a reference, see
+[Efficient Software Model Checking of Soundness of Type Systems](http://www.eecs.umich.edu/~bchandra/publications/oopsla08.pdf).
+</p>
+
+
+<p>
+A valuable project all by itself would be to compare heavy-weight and
+light-weight type inference this whole-program inference vs. Checker
+Framework Inference vs. Julia, to understand when each one is worth using.
+</p>
diff --git a/docs/developer/gsoc-ideas.html b/docs/developer/gsoc-ideas.html
new file mode 100644
index 0000000..09f8fc7
--- /dev/null
+++ b/docs/developer/gsoc-ideas.html
@@ -0,0 +1,1532 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Checker Framework ideas for new contributors (GSoC ideas)</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" type="image/png" href=
+  "../logo/Checkmark/CFCheckmark_favicon.png">
+</head>
+<body>
+
+<img src="../logo/Logo/CFLogo.png" alt="Checker Framework logo" />
+
+<h1>New contributor ideas</h1> <!-- omit from toc -->
+
+<p>Contents:</p>
+<!-- start toc.  do not edit; run html-update-toc instead -->
+<ul>
+  <li><a href="#introduction">Introduction</a>
+    <ul>
+      <li><a href="#get-started">How to get started: do a case study</a></li>
+      <li><a href="#ask-questions">How to get help and ask questions</a></li>
+      <li><a href="#types-of-projects">Types of projects</a></li>
+      <li><a href="#apply">How to apply</a></li>
+    </ul></li>
+  <li><a href="#evaluate-type-system">Evaluate a type system or a Checker Framework feature</a>
+    <ul>
+      <li><a href="#case-study-signature">Signature strings</a></li>
+      <li><a href="#case-study-signedness">Signed and unsigned numbers</a></li>
+      <li><a href="#optional-case-study">Java's Optional class</a></li>
+      <li><a href="#Whole-program_type_inference">Whole-program type inference</a></li>
+      <li><a href="#determinism">Determinism</a></li>
+      <li><a href="#sound-by-default">Sound checking by default</a></li>
+      <li><a href="#compare-other-tools">Comparison to other tools</a></li>
+      <li><a href="#case-study-android-support">Android support annotations</a></li>
+    </ul></li>
+  <li><a href="#annotate-library">Annotate a library</a>
+    <ul>
+      <li><a href="#choose-a-library">Choosing a library to annotate</a></li>
+      <li><a href="#case-study-nullness-guava">Guava library</a></li>
+    </ul></li>
+  <li><a href="#create-new-type-system">Create a new type system</a>
+    <ul>
+      <li><a href="#non-empty-checker">Non-Empty Checker for precise handling of Queue.peek() and poll()</a></li>
+      <li><a href="#custom-tainting-checking">Custom tainting checking</a></li>
+      <li><a href="#track-unsupported-operations">Track unsupported operations</a></li>
+      <li><a href="#overflow">Overflow checking</a></li>
+      <li><a href="#index-checker-mutable-length">Index checking for mutable length data structures</a></li>
+      <li><a href="#nullness-bug-detector">Nullness bug detector</a></li>
+    </ul></li>
+  <li><a href="#cf-other">Enhance the toolset</a>
+    <ul>
+      <li><a href="#index-errors">Improving error messages</a></li>
+      <li><a href="#java-expression-parser">Java expression parser</a></li>
+      <li><a href="#dataflow">Dataflow enhancements</a></li>
+      <li><a href="#Purity_analysis">Purity (side effect) analysis</a></li>
+      <li><a href="#javadoc">Javadoc support</a></li>
+    </ul></li>
+</ul>
+<!-- end toc -->
+
+<h1 id="introduction">Introduction</h1>
+
+<p>
+  The <a href="https://checkerframework.org/">Checker Framework</a> is an
+  innovative programming tool that prevents bugs at development
+  time, before they escape to production.
+</p>
+
+<p>
+  Java's type system prevents some bugs, such as <code>int count =
+  "hello";</code>.  However, it does not prevent other bugs, such as null
+  pointer dereferences, concurrency errors, disclosure of private
+  information, incorrect internationalization, out-of-bounds indices, etc.
+  <em>Pluggable type-checking</em> replaces a
+  programming language's built-in type system with a more powerful,
+  expressive one.
+</p>
+
+<p>
+  We have created around 20
+  <a href="https://checkerframework.org/manual/#introduction">new type
+  systems</a>, and other people have created
+  <a href="https://checkerframework.org/manual/#third-party-checkers">many
+  more</a>.
+  The more powerful type system is not just a
+  bug-finding tool:  it is a verification tool that gives a guarantee that
+  no errors (of certain types) exist in your program.  Even though it is
+  powerful, it is easy to use.  It follows the standard typing rules
+  that programmers already know, and it fits into their workflow.
+</p>
+
+<p>
+  The Checker Framework is popular:  it is used daily at Google, Amazon,
+  Uber, on Wall Street, and in other companies from big to small.  It is
+  attractive to programmers who care about their craft and the quality of
+  their code.  The Checker Framework is the motivation for Java's type
+  annotations feature.  It has received multiple awards.
+  <!-- at conferences such as JavaOne. -->
+  With this widespread use, there is a need for people to help with the
+  project:  everything from bug fixes, to new features, to case studies, to
+  integration with other tools.  We welcome your contribution!
+</p>
+
+<p>
+  Why should you join this project?  It's popular, so you will have an
+  impact.  It makes code more robust and secure, which is a socially
+  important purpose.  Past GSOC students have had great success.
+  (David Lazar became a graduate student at MIT; multiple students
+  have published papers in scientific conferences, such
+  as <a href="https://homes.cs.washington.edu/~mernst/pubs/array-indexing-issta2018-abstract.html">Vlastimil
+  Dort at ISSTA 2018</a>.)  You will
+  get to scratch your own itch by creating tools that solve problems that
+  frustrate you.  And, we have a lot of fun on this project!
+</p>
+
+<p>
+  <b>Prerequisites:</b> You should be very comfortable with the Java
+  programming language and its type system.  You should know how a type
+  system helps you and where it can hinder you.  You should be willing to
+  dive into a moderately-sized codebase.
+  You should understand fundamental object-oriented programming concepts,
+  such as
+  <a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle">behavioral
+  subtyping</a>:  subtyping theory
+  permits argument types to change contravariantly (even though Java forbids it
+  for reasons related to overloading), whereas return types may change
+  <a href="https://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29">covariantly</a>
+  both in theory and in Java.
+</p>
+
+
+<p>
+<b>Potential projects:</b>
+Most of this document lists potential summer projects.  The projects are
+grouped roughly from easiest to most challenging.  Many of the projects are
+applicable beyond Google Summer of Code.
+<!--
+You can find more potential projects in the
+<a href="https://github.com/typetools/checker-framework/issues">issue
+  tracker</a>.
+-->
+</p>
+
+
+
+<h2 id="get-started">How to get started: do a case study</h2>
+
+<p>
+  To <b>get started</b>, first do a case study of using the Checker
+  Framework:  that is, run the Checker Framework on some program.
+  A case study gives you experience in using the Checker
+  Framework, and it may reveal bugs in either the Checker Framework or in
+  the program it is analyzing.
+</p>
+
+<p>
+  Do the case study before submitting your proposal.
+  You might want to start with a small program such as from your
+  coursework, then repeat the process with an open-source program or library.
+</p>
+<ol>
+  <li>
+    <a href="https://checkerframework.org/manual/#installation">Install</a>
+    the Checker Framework.
+  <li>
+    <a href="https://checkerframework.org/manual/#how-to-read-this-manual">Review
+    the Checker Framework documentation.</a>
+  <li>
+    Choose an existing library or program to type-check.
+    Choose a program that is at least 1000 lines long.
+    The library or program should be under active maintenance; don't choose one
+    that has not had a commit in several years.
+    You will find the case study easier if you are already familiar with
+    the program, or if it is written in good style.
+  <li>
+    Choose one type system, from
+    among <a href="https://checkerframework.org/manual/#introduction">those
+    distributed with the Checker Framework</a>, that is appropriate for the
+    program.
+  <li>
+    If the program is hosted on GitHub, fork it and create a branch for
+    your work.  (Leave the master branch unchanged
+    from upstream.)
+  <li>
+    Annotate the program, based on its documentation.
+    <br/>
+    Please do <em>not</em> make changes unrelated to annotating the
+    program, such as inserting/removing whitespace or sorting
+    the <code>import</code> statements.  Doing so bloats the size of the
+    diffs and makes it hard to understand the essential changes.
+  <li>
+    Change the build system so that building the annotated branch runs the type-checker.
+  <li>
+    Run the type-checker.  If it issues
+    warnings, <a href="https://checkerframework.org/manual/#handling-warnings">correct them</a>.
+    This might require adding more annotations,
+    fixing bugs in the program, or suppressing warnings.
+    Be sure that the program's test suite continues to pass.
+    Repeat until
+    the type-checker passes on the program.
+    <ul>
+      <li>Don't add an <code>if</code> statement that always succeeds, just
+        to suppress a warning.  Convince yourself that both branches can
+        execute, or else don't add the <code>if</code> statement.
+      <li>If you add a <code>@SuppressWarnings</code> annotation,
+        <a href="https://checkerframework.org/manual/#suppresswarnings-best-practices-smallest-scope">write
+        it on the smallest possible scope</a> and
+        <a href="https://checkerframework.org/manual/#suppresswarnings-best-practices-justification">explain
+        why</a> the checker warning is a false positive and you are certain
+        the code is safe.
+    </ul>
+  <li>
+    Share it with us so that
+    we can evaluate it and give you feedback.
+
+    <p>
+    Share the case study as soon
+    as you finish it or as soon as you have a question that is not answered
+    in the <a href="https://checkerframework.org/manual/">manual</a>;
+    don't wait until you submit your proposal.
+    The subject line should be descriptive (not just "Case study", but
+    "Nullness case study of Apache Commons Exec library").
+    You should give us access to
+    <ul>
+      <li>the original (unannotated) version of the program,
+      <li>the annotated version of the program, and
+      <li>the exact command that runs the type-checker from the command
+        line.
+    </ul>
+    The best way to give all this information is a pointer to your GitHub
+    fork of the library.
+</ol>
+
+<p>
+The primary result of your case study is that you will discover bugs in the
+subject program, or you will verify that it has no bugs (of some particular
+type).  If you find bugs in open-source code, report them to the program's
+maintainer, and let us know when they are resolved.
+</p>
+
+<p>
+Another outcome of your case study is that you may discover bugs, limitations,
+or usability problems in the Checker Framework.  Please
+<a href="https://checkerframework.org/manual/#reporting-bugs">report them</a>.
+We'll try to fix them, or they might give you inspiration for
+improvements you would like to make to the Checker Framework this summer.
+</p>
+
+<p>
+You can also try to fix problems yourself and submit a
+<a href="https://github.com/typetools/checker-framework/pulls">pull
+  request</a>, but that is <em>not</em> a requirement; most successful GSOC
+  applicants had not submitted a successful pull request before being selected.
+(Some GSOC projects have a requirement to fix an issue in the issue tracker.
+We do not, because it is unproductive.
+Don't try to start fixing issues before you
+understand the Checker Framework from the user point of view, which will
+not happen until you have completed a case study on an open-source program.)
+You may discuss your ideas with us by sending mail
+to <a href="https://groups.google.com/forum/#!forum/checker-framework-gsoc">checker-framework-gsoc@googlegroups.com</a>.
+</p>
+
+<p>
+  Why should you start with a case study?
+  <!-- , instead of diving right into fixing
+bugs, designing a new type system, or making other changes to the Checker
+Framework?
+-->
+Before you can contribute to any project, you must
+understand the tool from a user point of view, including its strengths,
+weaknesses, and how to use it.
+A case study is the best way to
+learn about the Checker Framework, determine whether you would enjoy
+joining the project during the summer, and show your aptitude so that you
+will be chosen for the summer.
+</p>
+
+
+<h2 id="ask-questions">How to get help and ask questions</h2>
+
+<p>
+We are very happy to answer your questions, and we are eager to interact
+with you!  It's OK to have questions, and your questions can lead to
+improvements in the documentation and the tool.
+</p>
+
+<p>
+Before you ask a question, read this file and the
+<a href="https://checkerframework.org/manual/#troubleshooting">"Troubleshooting"
+  section</a> of the Checker Framework manual
+  (including <a href="https://checkerframework.org/manual/#reporting-bugs">"How
+  to report problems"</a>),
+and also search in the
+<a href="https://checkerframework.org/manual/">Checker Framework manual</a>
+for the answer.
+Don't send us a message
+that says nothing but &ldquo;please guide me&rdquo;
+or &ldquo;tell me how to fix this bug&rdquo;.  Such a message disqualifies
+you from participating in GSOC, because it shows that you do not read
+instructions, and you haven't thought about the problem nor
+tried to solve it.
+</p>
+
+<p>
+Your questions should show that you will be a productive colleague over the
+summer:  tell us what you have tried, tell us what went wrong or
+where you got stuck, and ask a concrete technical question that will
+help you get past your problem.  If you can do that, then definitely ask
+your question, because we don't want you to be stuck or frustrated.
+</p>
+
+<p>
+Whenever you send email (related to GSoC or not),
+please use standard email etiquette, such as:  avoid all-caps; use a
+descriptive subject line; don't put multiple different topics in a single
+email message; start a new thread with a new subject line
+when you change the topic; don't clutter discussions with irrelevant
+remarks; don't use screenshots (unless there is a problem with a GUI), but
+instead cut-and-paste the output or code into your message;
+if you are making a guess, clearly indicate that it is a guess and
+your grounds for it.  If you violate these basic rules, you will
+demonstrate that you don't read instructions and you don't act
+professionally.  Bug
+reports should be
+<a href="https://checkerframework.org/manual/#reporting-bugs">complete</a>
+and should usually be
+<a href="https://checkerframework.org/manual/#reporting-bugs">reported</a>
+to the issue tracker.
+</p>
+
+
+<h2 id="types-of-projects">Types of projects</h2>
+
+<p>
+  Here are some possible focuses for a project:
+</p>
+<ul>
+  <li>
+    <a href="#evaluate-type-system">Evaluate</a> a recently-written type
+    system, or a feature used by multiple type systems.
+  </li>
+  <li>
+    <a href="#annotate-library">Annotate</a> a popular library, so that it
+    is easier to type-check clients of the library.
+  </li>
+  <li>
+    <a href="#create-new-type-system">Create</a> a new type system, to
+    prevent some Java programming error.
+  </li>
+  <li>
+    <a href="#cf-other">Enhance</a> a type system or the Checker Framework
+    itself.
+  </li>
+</ul>
+
+<p>
+This document gives a few suggestions in each category.
+</p>
+
+
+<h2 id="apply">How to apply</h2>
+
+<p>
+  To <b>apply</b>, you will submit a single PDF through the Google Summer
+  of Code website.  This PDF should contain two main parts.  We suggest
+  that you number the parts and subparts to ensure that you don't forget anything, and
+  to ensure that we don't overlook anything when reading your application.  You might find it
+  easiest to create multiple PDFs for the different parts, then concatenate
+  them before uploading to the website, but how you create your proposal is
+  entirely up to you.
+</p>
+
+<ol>
+  <li>The proposal itself:  what project you want to work on during the
+    summer.  You might propose to do a project listed on this webpage, or
+    you might propose a different project.
+
+    <p>The proposal should have a descriptive title, both in the PDF and in
+      the GSoC submission system.  Don't use a title like "Checker
+      Proposal" or "Proposal for GSoC".  Don't distract from content with
+      gratuitous graphics.
+
+    <p>List the tasks or subparts that are required to complete your
+    project.  This will help you discover a part that you had forgotten.
+    We do not require a detailed timeline, because you don't yet
+    know enough to create one.
+    </p>
+
+    <p>
+    If you want to do a
+    case study, say what program you will do your case study on.
+
+    <p>If you want to create a new type system (whether one proposed on
+    this webpage or one of your own devising), then your proposal should include
+    the type system's user manual.  You don't have to integrate it in the Checker
+    Framework repository (in other words, use any word processor or text
+    editor you want to create a PDF file you will submit), but you should describe
+    your proposed checker's <a href="https://checkerframework.org/manual/#creating-parts-of-a-checker">parts</a>
+    in precise English or simple formalisms, and you should follow the
+    suggested <a href="https://checkerframework.org/manual/#creating-documenting-a-checker">structure</a>.
+
+    <p>
+    If you want to do exactly what is already listed on this page, then
+    just say that (but be specific about which one!), and it will not hurt
+    your chances of being selected.  However, show us what progress you
+    have made so far.  You might also give specific ideas
+    about extensions, about details that are not mentioned on this webpage,
+    about implementation strategies, and so forth.
+    </p>
+
+    <p>Never literally cut-and-paste text that was not written by you, because
+    that would be plagiarism.  If you quote from text written by someone
+    else, give proper credit.
+    Don't
+    submit a proposal that is just a rearrangement of text that already
+    appears on this page or in the Checker Framework manual, because it does
+    not help us to assess your likelihood of being successful.
+    </p>
+
+  </li>
+
+  <li>Your qualifications.  Please convince us that you
+    are likely to be successful in your proposed summer project.
+
+    <ol>
+      <li>A URL that points to a code sample.
+        Don't write any new code, but provide code you wrote in the
+        past, such as for a class assignment
+        or a project you have worked on outside class.
+        It does not need to have anything to do with
+        the Checker Framework project.  It should be your own personal work.
+        The purpose is to assess your programming skills so we can assign you
+        to an appropriate project.
+        A common problem is to submit undocumented code; we expect every
+        programmer to write documentation when working on the Checker
+        Framework.
+        Don't put a lot of different files in Google Drive and share that
+        URL; it's better to upload a single <code>.zip</code> file or
+        provide a GitHub URL.
+      </li>
+      <li>
+        What you have done to prepare yourself
+        for working with the Checker Framework during the summer.
+        You may wish to structure this as a list.
+    Examples of items in the list include:
+    <ul>
+      <li>A URL for code you have annotated as a case study.  Please indicate the
+    original unannotated code, the annotated code, and the exact command to
+    run the type-checker from the command line.  Ensure that the GSoC
+    mentors can compile your code.
+    (It is acceptable to use the same code, or different code, for this
+      item and the code sample above.)
+      </li>
+      <li>URLs for bugs or pull requests that you have filed.</li>
+      <li>Information about other projects you have done, or classes you
+        have taken, that prepare you for your proposed summer task.  This
+        is optional, because it might already appear in your resume.</li>
+    </ul>
+  </li>
+  <li>A resume.
+    A <a href="https://en.wikipedia.org/wiki/R%C3%A9sum%C3%A9">resume</a>
+    contains a brief description of your skills and your job or project
+    experience.  It will often list classes you have taken so far and your
+    GPA.  It should not be longer than one page.</li>
+  <li>An unofficial transcript or grade report (don't spend
+    money for an official one).</li>
+  </ol>
+  </li>
+</ol>
+
+<p>
+The <b>best way</b> to impress us is by doing a thoughtful job in the case
+study.  The case study is even more important than the proposal text,
+because it shows us your abilities.
+The case study may result in you submitting issues against the issue tracker of the
+program you are annotating or of the Checker Framework.
+Pull requests against our GitHub project are a plus but are not required:
+good submitted bugs are just as valuable as bug fixes!
+You can also make a good impression by correctly answering questions from
+other students on the GSOC mailing list.
+</p>
+
+<p>
+Get feedback!  Feel free to <a href="#ask-questions">ask questions</a>
+to make your application more
+competitive.  We want you to succeed.  Historically, students who start
+early and get feedback are most successful.  You can submit a draft
+proposal via the Google Summer of Code website, and we will review it.  We
+do <em>not</em> receive any notification when you submit a draft
+proposal, so if you want feedback, please tell us that.
+Also, we can only see draft proposals; we cannot see final proposals until
+after the application deadline has passed.
+</p>
+
+
+<h1 id="evaluate-type-system">Evaluate a type system or a Checker Framework feature</h1>
+
+<p>
+  These projects evaluate a recently-written type system or a feature used
+  by multiple type systems.
+  Using the type systems on real code is our most important source of new ideas and improvements.
+  Many people have started out &ldquo;just&rdquo; doing a case
+  study but have ended up making deep, fundamental contributions and even
+  publishing scientific papers about their discoveries.
+</p>
+
+<p>
+One possible outcome is to identify
+  weaknesses in the type-checker so that we can improve it.  Another
+  possible outcome is to provide evidence that the type-checker is
+  effective and convince more users to adopt it.  You will probably
+  also discover defects (bugs) in the codebase being type-checked.
+</p>
+
+
+
+<h2 id="case-study-signature">Signature strings</h2>
+
+<p>
+Determine whether the <a href="https://asm.ow2.org/">ASM library</a>, or
+some other library, properly handles signature strings.
+</p>
+
+<p>
+   Some challenging aspects of this case study are:
+</p>
+<ul>
+  <li>
+    Some libraries define their own new signature string formats (!), which
+    you need to define in the Signature String Checker.
+  </li>
+  <li>
+    Sometimes the library's documentation is incorrect, and in other cases the
+    string format is not defined.
+  </li>
+</ul>
+
+
+<h2 id="case-study-signedness">Signed and unsigned numbers</h2>
+
+<p>
+  The <a href="https://checkerframework.org/manual/#signedness-checker">Signedness
+  Checker</a> ensures that you do not misuse unsigned values, such as
+  by mixing signed and unsigned values in a computation or by performing a
+  meaningless operation.
+</p>
+
+<p>
+  Perform a case study of the Signedness Checker, in order to detect errors
+  or guarantee that code is correct.
+</p>
+
+<p>
+  A good way to find projects that use unsigned arithmetic is to find a library that supports unsigned
+  arithmetic, then search on GitHub for projects that use that library.
+</p>
+
+<p>
+  Here are some relevant libraries.
+</p>
+<ul>
+  <li>In the JDK's <code>Integer</code>
+  and <code>Long</code>, these include
+   <code>compareUnsigned</code>,
+   <code>divideUnsigned</code>,
+   <code>parseUnsignedInt</code>,
+   <code>remainderUnsigned</code>, and
+   <code>toUnsignedLong</code>.
+   <br/>
+   Classes like <code>DataInputStream</code>, <code>ObjectInputStream</code>,
+   and <code>RandomAccessFile</code> have <code>readUnsignedByte</code>.
+   <br/>
+   <code>Arrays</code> has <code>compareUnsigned</code>.
+   The JDK is already annotated; search for <code>@Unsigned</code> within
+   <a href="https://github.com/typetools/jdk">https://github.com/typetools/jdk</a>.
+  </li>
+  <li>
+    In Guava, see
+  its <a href="https://github.com/google/guava/wiki/PrimitivesExplained#unsigned-support">unsigned
+  support</a>, such
+  as <a href="https://google.github.io/guava/releases/snapshot-jre/api/docs/com/google/common/primitives/UnsignedBytes.html">UnsignedBytes</a>,
+  <a href="https://google.github.io/guava/releases/snapshot-jre/api/docs/com/google/common/primitives/UnsignedLong.html">UnsignedLong</a>,
+  <a href="https://google.github.io/guava/releases/snapshot-jre/api/docs/com/google/common/primitives/UnsignedLongs.html">UnsignedLongs</a>,
+  etc.
+   Guava is already annotated; search for <code>@Unsigned</code> within
+   <a href="https://github.com/typetools/guava">https://github.com/typetools/guava</a>.
+  </li>
+  <li>The <a href="https://github.com/jOOQ/jOOU">jOOU</a> library consists of support for unsigned
+      integers.</li>
+</ul>
+
+<p>
+  Another possibility is to find Java projects that <em>could</em> use an
+  unsigned arithmetic library but do not.  For
+  example, <a href="https://github.com/bcgit/bc-java">bc-java</a> defines
+  its own unsigned libraries, and some other programs might do direct bit
+  manipulation.
+</p>
+  <!-- Not a good choice because this is just example code, not an actively-maintained project.
+  <li>Project
+  Nayuki: <a href="https://www.nayuki.io/page/forcing-a-files-crc-to-any-value">CRC</a>, <a href="https://www.nayuki.io/page/notepadcrypt-format-decryptor-java">crypt</a>, <a href="https://www.nayuki.io/page/native-hash-functions-for-java">hash</a>.</li>
+  -->
+  <!-- Not a good choice because it is not under active development:
+  <li><a href="https://bytonic.de/html/jake2.html">Jake2</a></li>
+   -->
+
+<p>
+  Your case studies will show the need for enhancements to the Signedness
+  Checker.  For example, the Signedness Checker does not currently handle
+  boxed integers and BigInteger; these haven't yet come up in case studies
+  but could be worthwhile enhancements.  You may also need to
+  write more annotations for libraries such as the JDK.
+</p>
+
+
+<h2 id="optional-case-study">Java's Optional class</h2>
+
+<p>
+Java 8 introduced the
+<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html"><code>Optional</code></a>
+class, a container that is either empty or contains a non-null value.
+It is intended to solve the problem of null
+pointer exceptions.  However, <code>Optional</code> has <a href="https://homes.cs.washington.edu/~mernst/advice/nothing-is-better-than-optional.html">its own problems</a>.
+</p>
+
+<p>
+Because of <code>Optional</code>'s problems, many commentators advise programmers to use
+<code>Optional</code> only in limited ways.
+</p>
+
+<p>
+The goal of this project is to evaluate
+the <a href="https://checkerframework.org/manual/#optional-checker">Optional
+    Checker</a>, which warns programmers who
+have misused <code>Optional</code>.
+Another goal is to extend the Optional Checker to make it more precise or
+to detect other misuses of <code>Optional</code>.
+</p>
+
+
+<h2 id="Whole-program_type_inference">Whole-program type inference</h2>
+
+<p>
+A type system is useful because it prevents certain errors.  The downside
+of a type system is the effort required to write the types.  Type inference
+is the process of writing the types for a program.
+</p>
+
+<p>
+The Checker Framework includes
+a <a href="https://checkerframework.org/manual/#whole-program-inference">whole-program
+    inference</a> that inserts type qualifiers in the user's program.
+It works well on some programs, but needs more enhancements to work well on
+all programs.
+</p>
+
+
+<h2 id="determinism">Determinism</h2>
+
+<p>
+Programs are easier to use and debug if their output is deterministic.  For
+example, it is easier to test a deterministic program, because
+nondeterminism can lead to flaky tests that sometimes succeed and sometimes
+fail.  As another example, it is easier for a user or programmer to compare
+two deterministic executions than two nondeterministic executions.
+</p>
+
+<p>
+  We have created a prototype Determinism Checker.  It is documented in
+  this <a href="https://checkerframework.org/determinism-checker-manual/manual.html#determinism-checker">draft
+  manual chapter</a>, and its implementation is in
+<a href="https://github.com/t-rasmud/checker-framework/tree/nondet-checker">t-rasmud/nondet-checker</a>.
+You will do a case study of this type system.
+</p>
+
+
+<h2 id="sound-by-default">Sound checking by default</h2>
+
+<p>
+By default, the Checker Framework is
+<a href="https://checkerframework.org/manual/#unsound-by-default">unsound
+  in</a> <a href="https://checkerframework.org/manual/#nullness-lint">several</a>
+<a href="https://github.com/typetools/checker-framework/issues/986">circumstances</a>.
+&ldquo;Unsound&rdquo; means that the Checker Framework
+may report no warning even though the program can misbehave at run time.
+</p>
+
+<p>
+The reason that the Checker Framework is unsound is that we believe that
+enabling these checks would cause too many false positive warnings:
+warnings that the Checker Framework issues because it cannot prove that the
+code is safe (even though a human can see that the code is safe).  Having
+too many false positive warnings would irritate users and lead them not to
+use the checker at all, or would force them to simply disable those checks.
+</p>
+
+<p>
+We would like to do studies of these command-line options to see whether
+our concern is justified.  Is it prohibitive to enable sound checking?  Or can we
+think of enhancements that would let us turn on those checks that are
+currently disabled by default?
+</p>
+
+<p>
+There is no need to annotate new code for this project.  Just use existing
+annotated codebases, such as those that are type-checked as part of the
+Checker
+Framework's <a href="https://github.com/typetools/checker-framework/blob/master/azure-pipelines.yml">Azure
+    Pipeline</a>.  In other words, you can start by enabling Azure
+Pipelines for your fork and then changing the default behavior in a
+branch.  The Azure Pipelines job will show you what new warnings appear.
+</p>
+
+
+<h2 id="compare-other-tools">Comparison to other tools</h2>
+
+<p>
+  Many other tools exist for prevention of programming errors, such as
+  Error Prone, NullAway, FindBugs, JLint, PMD, and IDEs such as Eclipse and
+  IntelliJ.  These tools
+  are not as powerful as the Checker Framework (some are bug finders rather
+  than verification tools, and some perform a shallower analysis), but they
+  may be easier to use.
+  Programmers who use these tools wonder, "Is it worth my time to switch to
+  using the Checker Framework?"
+</p>
+
+<p>
+  The goal of this project is to perform a head-to-head comparison of as
+  many different tools as possible.  You will quantify:
+</p>
+
+<ul>
+  <li>the number of annotations that need to be written</li>
+  <li>the number of bugs detected</li>
+  <li>the number of bugs missed</li>
+  <li>the number of false positive warnings</li>
+</ul>
+
+<p>
+  This project will help programmers to choose among the different tools
+  &mdash; it will show when a programmer should or should not use the
+  Checker Framework.
+  This project will also indicate how each tool should be improved.
+</p>
+
+<p>
+  One place to start would be with an old version of a program that is
+  known to contain bugs.  Or, start with the latest version of the program
+  and re-introduce fixed bugs.  (Either of these is more realistic than
+  introducing artificial bugs into the program.)  A possibility would be to
+  use the Lookup program that has been used in previous case studies.
+</p>
+
+
+<h2 id="case-study-android-support">Android support annotations</h2>
+
+<p>
+Android uses its own annotations that are similar to some in the Checker
+Framework.  Examples include the
+<a href="https://tips.seebrock3r.me/annotations-to-support-your-contracts-609ff259d5df">Android
+  Studio support annotations</a>,
+  including <code>@NonNull</code>, <code>@IntRange</code>, <code>@IntDef</code>,
+  and others.
+</p>
+
+<p>
+The goal of this project is to implement support for these annotations.
+That is probably as simple as creating aliased annotations
+by calling method <code>addAliasedTypeAnnotation()</code>
+in <a href="https://checkerframework.org/api/org/checkerframework/framework/type/AnnotatedTypeFactory.html">AnnotatedTypeFactory</a>.
+</p>
+
+<p>
+  Then, do a case study to show the utility (or not) of
+  pluggable type-checking, by comparison with how Android Studio currently
+  checks the annotations.
+</p>
+
+
+<h1 id="annotate-library">Annotate a library</h1>
+
+<p>
+  These projects annotate a library, so that it is easier to
+  type-check clients of the library.  Another benefit is that this may find
+  bugs in the library.  It can also give evidence for the usefulness of
+  pluggable type-checking, or point out ways to improve the Checker
+  Framework.
+</p>
+
+
+<p>
+When type-checking a method call, the Checker Framework uses the method
+declaration's annotations.
+This means that in order to type-check code that uses a library, the
+Checker Framework needs an annotated version of the library.
+</p>
+
+<p>
+The Checker Framework comes with a
+few <a href="https://search.maven.org/search?q=annotatedlib">annotated
+libraries</a>.  Increasing this number will make the Checker Framework even
+more useful, and easier to use.
+</p>
+
+<p>
+After you have <a href="#choose-a-library">chosen a library</a>,
+fork the library's source code, adjust
+its <a href="https://checkerframework.org/manual/#external-tools">build
+system</a> to run the Checker Framework, and add annotations to it until
+the type-checker issues no warnings.
+</p>
+
+<p>
+Before you get started, be sure to read
+<a href="https://checkerframework.org/manual/#get-started-with-legacy-code">How
+to get started annotating legacy code</a>.  More generally, read the
+<a href="https://checkerframework.org/manual/#how-to-read-this-manual">relevant
+sections of the Checker Framework manual</a>.
+</p>
+
+
+<h2 id="choose-a-library">Choosing a library to annotate</h2>
+
+<p>
+There are several ways to <b>choose a library</b> to annotate:
+</p>
+<ul>
+  <li>
+The best way to choose a library is to try to annotate a program and notice
+that library annotations are needed in order to type-check the program.
+</li>
+<li>
+Alternately, you can
+    choose a <a href="https://docs.google.com/spreadsheets/d/17x_jKkGquEFq7LBQhS9HGXiG7iIl2AlXoPGfB6N5_bw">popular
+      Java library</a>.
+</li>
+</ul>
+
+<p>
+  When annotating a library, it is important to type-check both the library
+  and at least one client that uses it.  Type-checking the client will
+  ensure that the library annotations are accurate.
+</p>
+
+<p>
+  Whatever library you choose, you will need to deeply understand its
+  source code.  You will find it easier to work with a library that is
+  well-designed and well-documented.
+</p>
+
+<p>
+  You should choose a library that is
+  not <a href="https://search.maven.org/search?q=org.checkerframework.annotatedlib">already
+    annotated</a>.  There are two exceptions to this.
+</p>
+<ul>
+  <li>
+    A library might be annotated for one type system, but you add
+    annotations for a different type system.  One advantage of this is that
+    the library's build system is already set up to run the Checker
+    Framework.  You can tell which type systems a library is annotated for
+    by examining its source code.
+  </li>
+  <li>
+    A library might be annotated, but the annotations have not been
+    verified by running the type-checker on the library source code.  You
+    would verify that the annotations in the library are correct.
+  </li>
+</ul>
+
+
+<h2 id="case-study-nullness-guava">Guava library</h2>
+
+<p>
+  Guava is already partially annotated with nullness annotations &mdash; in
+  part by Guava's developers, and in part by the Checker Framework team.
+  However, Guava does not yet type-check without errors.  Doing so could
+  find more errors (the Checker Framework has found nullness and indexing
+  errors in Guava in the past) and would be a good case study to learn the
+  limitations of the Nullness Checker.
+</p>
+
+
+<h1 id="create-new-type-system">Create a new type system</h1>
+
+<p>
+The Checker Framework is shipped with <a href="https://checkerframework.org/manual/#introduction">about 20 type-checkers</a>.  Users can
+<a href="https://checkerframework.org/manual/#creating-a-checker">create a
+  new checker</a> of their own.  However, some users don't want to go to
+that trouble.  They would like to have more type-checkers packaged with the
+Checker Framework for easy use.
+</p>
+
+<p>
+Each of these projects requires you to design a <a href="https://checkerframework.org/manual/#creating-a-checker">new type system</a>,
+implement it, and perform case studies to demonstrate that it is both
+usable and effective in finding/preventing bugs.
+</p>
+
+
+<h2 id="non-empty-checker">Non-Empty Checker for precise handling of Queue.peek() and poll()</h2>
+
+<p>
+The Nullness Checker issues a false positive warning for this code:
+</p>
+
+<pre>
+import java.util.PriorityQueue;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+public class MyClass {
+    public static void usePriorityQueue(PriorityQueue&lt;@NonNull Object&gt; active) {
+        while (!(active.isEmpty())) {
+            @NonNull Object queueMinPathNode = active.peek();
+        }
+    }
+}
+</pre>
+
+<p>
+The Checker Framework does not determine that <code>active.peek()</code> returns a non-null value in this context.
+</p>
+
+<p>
+The contract of <code>peek()</code> is that it returns a non-null value if the queue is not empty and the queue contains no null values.
+</p>
+
+<p>
+To handle this code precisely, the Nullness Checker needs to know, for each queue, whether it is empty.
+This is analogous to how the Nullness Checker tracks whether a particular
+value <a href="https://checkerframework.org/manual/#map-key-checker">is a key in a map</a>.
+</p>
+
+<p>
+It should be handled the same way:  by adding a new subchecker, called the
+  Nonempty Checker, to the Nullness Checker.  Its types are:
+</p>
+<ul>
+<li><code>@UnknownNonEmpty</code> &mdash; the queue might or might not be empty
+<li><code>@NonEmpty</code> &mdash; the queue is definitely non-empty
+</ul>
+
+<p>
+There is a start at this type-checker in branch <code>nonempty-checker</code>.  It:
+</p>
+<ul>
+  <li>defines the annotations
+  </li>
+  <li>creates the integration into the Nullness Checker
+  </li>
+</ul>
+<p>
+However, it is not done.  (In fact, it doesn't even compile.)
+For information about what needs to be done, see <a href="https://github.com/typetools/checker-framework/issues/399">issue #399</a>.
+</p>
+
+<p>
+When you are done, the Nullness Checker should issue only the <code>// ::</code> diagnostics from <code>checker/tests/nullness/IsEmptyPoll.java</code> &mdash; no more and no fewer.
+You can test that by running the Nullness Checker on the file, and when you are done you should delete the <code>// @skip-test</code> line so that the file is run as part of the Checker Framework test suite.
+</p>
+
+
+<h2 id="custom-tainting-checking">Custom tainting checking</h2>
+
+<p>
+The Checker Framework comes with
+a <a href="https://checkerframework.org/manual/#tainting-checker">Tainting
+  Checker</a> that is so general that it is not good for much of anything.
+In order to be useful in a particular domain, a user must customize it:
+</p>
+<ul>
+  <li>
+    rename the <code>@Tainted</code> and <code>@Untainted</code> qualifiers
+    to something more specific (such as <code>@Private</code>
+    or <code>@PaymentDetails</code> or <code>@HtmlQuoted</code>), and
+  <li>
+    annotate libraries.
+</ul>
+
+<p>
+The first part of this project is to make this customization easier to do
+&mdash; preferably, a user will not have to change any code in the Checker
+Framework (the
+<a href="https://checkerframework.org/manual/#subtyping-checker">Subtyping
+Checker</a> already works this way).
+As part of making customization easier, a user should be able to specify
+multiple levels of taint &mdash; many information classification hierarchies
+have more than two levels (for example, the US government separates classified
+information into three categories: Confidential, Secret, and Top Secret).
+</p>
+
+<p>
+The second part of this project is to provide several examples, and do case
+studies showing the utility of compile-time taint checking.
+</p>
+
+<p>
+  Possible examples include:
+</p>
+<ul>
+  <li>SQL injection
+  <li>OS command injection
+  <li>the <code>@PrivacySource</code> and <code>@PrivacySink</code>
+    annotations used by the Facebook <a href="http://fbinfer.com/">Infer
+    static analyzer</a>.
+  <li>information flow
+  <li>many of the <a href="http://cwe.mitre.org/top25/">CWE/SANS most
+  dangerous software programming errors</a> (and the "on the cusp" ones too)
+  <!-- More details appear in these files:
+      ~/research/games/notes/notes
+      ~/prof/grants/2012-02-darpa-verigames/proposal/top25-as-types.pdf
+  -->
+</ul>
+
+<p>
+For some microbenchmarks, see the Juliette test suite for Java from CWE.
+</p>
+
+
+<h2 id="track-unsupported-operations">Track unsupported operations</h2>
+
+<p>
+Some objects do not fully implement their interface; they
+throw <code>UnsupportedOperationException</code> for some operations.  One
+example is unmodifiable collections.  They throw the exception when a
+mutating operation is called, such
+as <code>add</code>, <code>addAll</code>, <code>put</code>, <code>remove</code>,
+etc.
+</p>
+
+<p>
+Design a type system to track which operations might not be supported.
+The checker would issue a warning whenever
+an <code>UnsupportedOperationException</code> might occur at run time.
+</p>
+
+<p>
+  Here is a possible type hierarchy:
+</p>
+
+<pre>
+  @MightSupportNothing     &larr; every hierarchy must have a top type
+         |
+  @MightNotSupport({"add", remove"})
+         |
+  @MightNotSupport("add")
+         |
+  @SupportsAllOperations   &larr; this is the default type qualifier
+</pre>
+
+<p>
+<code>@SupportsAllOperations</code>
+indicates that an object that supports all its documented operations:
+it never throws <code>UnsupportedOperationException</code>.
+<code>@MightNotSupport(operations)</code> indicates which methods might throw
+<code>UnsupportedOperationException</code>.
+Methods such
+as <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Arrays.html#asList(T...)">Arrays.asList</a>
+and <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Collections.html#emptyList()">Collections.emptyList</a>
+must be annotated to return one of these less-capable supertypes.
+</p>
+
+<p>
+(An alternative would be to have annotations that are specific to certain
+types, such as <code>@MutableList</code> for lists.)
+</p>
+
+<p>
+If a program uses no objects that
+throw <code>UnsupportedOperationException</code>, no annotations are
+required.  Experiments must be done to determine how high the annotation
+burden is, in a program that extensively uses such objects.  If the whole
+program is available, then type inference can infer the needed types.
+</p>
+
+
+<h2 id="overflow">Overflow checking</h2>
+
+<p>
+Overflow is when 32-bit arithmetic differs from ideal arithmetic.  For
+example, in Java the <code>int</code> computation 2,147,483,647 + 1 yields
+a negative number, -2,147,483,648.  The goal of this project is to detect
+and prevent problems such as these.
+</p>
+
+<p>
+One way to write this is as an extension of the Constant Value Checker,
+which already keeps track of integer ranges.  It even already
+<a href="https://checkerframework.org/manual/#value-checker-overflow">checks
+  for overflow</a>, but it never issues a warning when it discovers
+  possible overflow.  Your variant would do so.
+</p>
+
+<p>
+This problem is so challenging that there has been almost no previous
+research on static approaches to the problem.  (Two relevant papers are
+<a href="https://www.researchgate.net/publication/221655385_IntScope_Automatically_Detecting_Integer_Overflow_Vulnerability_in_X86_Binary_Using_Symbolic_Execution">IntScope:
+Automatically Detecting Integer Overflow Vulnerability in x86 Binary Using
+Symbolic Execution</a> and
+<a href="https://dl.acm.org/citation.cfm?id=3136872">Integer Overflow
+Vulnerabilities Detection in Software Binary Code</a>.)  Researchers are
+concerned that users will have to write a lot of annotations indicating the
+possible ranges of variables, and that even so there will be a lot of false
+positive warnings due to approximations in the conservative analysis.
+For example, will every loop that contains <code>i++</code> cause a warning that <code>i</code> might overflow?
+That would not be acceptable:  users would just disable the check.
+</p>
+
+<p>
+You can convince yourself of the difficulty by manually analyzing programs
+to see how clever the analysis has to be, or manually simulating your
+proposed analysis on a selection of real-world code to learn its
+weaknesses.  You might also try it
+on <a href="https://ai.googleblog.com/2006/06/extra-extra-read-all-about-it-nearly.html">good
+  and bad binary search code</a>.
+</p>
+
+<p>
+One way to make the problem tractable is to limit its scope:  instead of
+being concerned with all possible arithmetic overflow, focus on a specific
+use case.
+As one concrete application,
+the <a href="https://checkerframework.org/manual/#index-checker">Index
+Checker</a> is currently unsound in the presence of integer overflow.  If
+an integer <code>i</code> is known to be <code>@Positive</code>, and 1 is
+added to it, then the Index Checker believes that its type
+remains <code>@Positive</code>. If <code>i</code> was
+already <code>Integer.MAX_VALUE</code>, then the result is negative &mdash;
+that is, the Index Checker's approximation to it is unsound.
+</p>
+
+<p>
+This project involves removing this unsoundness by implementing a type system to track when an
+integer value might overflow &mdash; but this only matters for values that
+are used as an array index.
+That is, checking can be restricted to computations that involve an operand
+of type <code>@IntRange</code>).
+Implementing such an analysis would permit the Index Checker
+to extend its guarantees even to programs that might overflow.
+</p>
+
+<p>
+This analysis is important for some indexing bugs in practice.
+Using the Index Checker, we found 5 bugs in Google
+Guava related to overflow.  Google marked these as high priority and
+fixed them immediately.  In practice, there would be a run-time exception
+only for an array of size approximately <code>Integer.MAX_INT</code>.
+</p>
+
+<p>
+You could write an extension of the Constant Value Checker, which already
+keeps track of integer ranges and
+even <a href="https://checkerframework.org/manual/#value-checker-overflow">determines
+when overflow is possible</a>.  It doesn't issue a warning, but your
+checker could record whether overflow was possible (this could be a
+two-element type system) and then issue a warning, if the value is used as
+an array index.
+Other implementation strategies may be possible.
+</p>
+
+<p>
+Here are some ideas for how to avoid the specific problem
+of issuing a warning about potential overflow for every <code>i++</code> in
+a loop (but maybe other approaches are possible):
+</p>
+<ul>
+  <li>
+    The loop checks whether <code>i == Integer.MAX_VALUE</code> before
+    incrementing.  This wide-scale, disruptive code change is not
+    acceptable.
+  </li>
+  <li>
+    Make the default array size (the length of an unannotated array) be
+    <code>@ArrayLenRange(0, Integer.MAX_VALUE-1)</code> rather
+    than <code>@UnknownVal</code>, which is equivalent
+    to <code>@ArrayLenRange(0, Integer.MAX_VALUE-1)</code>.  Now, every
+    array construction requires the client to establish that the length is
+    not <code>Integer.MAX_VALUE</code>.  I don't have a feel for whether
+    this would be unduly burdensome to users.
+  </li>
+</ul>
+
+
+<h2 id="index-checker-mutable-length">Index checking for mutable length data structures</h2>
+
+<p>
+The <a href="https://checkerframework.org/manual/#index-checker">Index
+Checker</a> is currently restricted to fixed-size data structures.  A
+fixed-size data structure is one whose length cannot be changed once it is
+created, such as arrays and Strings.  This limitation prevents the Index
+Checker from verifying indexing operations on mutable-size data structures,
+like Lists, that have <code>add</code> or <code>remove</code>
+methods. Since these kind of collections are common in practice, this is a
+severe limitation for the Index Checker.
+</p>
+
+<p>
+The limitation is caused by the Index Checker's use of types that are dependent on the length of data structures,
+like <code>@LTLengthOf("data_structure")</code>. If <code>data_structure</code>'s length could change,
+then the correctness of this type might change.
+</p>
+
+<p>
+A naive solution would be to invalidate these types any time a method is called on <code>data_structure</code>.
+Unfortunately, aliasing makes this still unsound. Even more, a great solution to this problem would keep
+the information in the type when a method like add or remove is called on <code>data_structure</code>.
+A more complete solution might involve some special annotations on List that permit the information to be persisted.
+</p>
+
+<p>
+This project would involve designing and implementing a solution to this problem.
+</p>
+
+
+<h2 id="nullness-bug-detector">Nullness bug detector</h2>
+
+<p>
+Verifying a program to be free of errors can be a daunting task.  When
+starting out, a user may be more interested in
+<a href="https://checkerframework.org/manual/#other-tools">bug-finding</a>
+than verification.  The goal of this project is to create a nullness bug
+detector that uses the powerful analysis of the Checker Framework and its
+Nullness Checker, but omits some of its more confusing or expensive
+features.  The goal is to create a fast, easy-to-use bug detector.  It
+would enable users to start small and advance to full verification in the
+future, rather than having to start out doing full verification.
+</p>
+
+<p>
+This could be structured as a new NullnessLight Checker, or as a
+command-line argument to the current Nullness Checker.  Here are some
+differences from the real Nullness checker:
+</p>
+<ul>
+  <li>No initialization analysis; the checker assumes that every value is
+  initialized.</li>
+  <li>No map key analysis; assume that, at every call to
+    <code>Map.get</code>, the given key appears in the map.</li>
+  <li>No invalidation of dataflow facts.  Assume all method calls are pure,
+  so method calls do not invalidate dataflow facts.  Assume there is no
+    aliasing, so field updates do not invalidate dataflow facts.
+    </li>
+  <li>Assume that boxing of primitives is <code>@Pure</code>: it returns
+  the same value on every call.</li>
+  <li>If the Checker Framework cannot infer a type argument, assume that
+  the type argument is <code>@NonNull</code>.</li>
+</ul>
+<p>
+  Each of these behaviors should be controlled by its own command-line
+  argument, as well as being enabled in the NullnessLight Checker.
+</p>
+
+<p>
+  The implementation may be relatively straightforward, since in most cases
+  the behavior is just to disable some functionality of existing checkers.
+</p>
+
+<p>Tools such as FindBugs, NullAway, NullnessLight, and the Nullness
+  Checker form a spectrum from easy-to-use bug detectors to sound
+  verification.  NullnessLight represents a new point in the design space.
+  It will be interesting to compare these checkers:
+</p>
+
+<ul>
+    <li>How much easier is it to use?  For example, how many fewer
+      annotations need to be written?</li>
+    <li>
+      How many more fewer true positives does it report &mdash; in other
+      words, how many more false negatives does it suffer?
+    </li>
+    <li>
+      How many fewer false positives does it report?
+    </li>
+</ul>
+
+<p>
+  Uber's <a href="https://github.com/uber/NullAway">NullAway</a> tool is also
+  an implementation of this idea (that is, a fast, but incomplete and
+  unsound, nullness checker).  NullAway doesn't let the user specify Java
+  Generics:  it assumes that every type parameter is <code>@NonNull</code>.
+  Does Uber's tool provide users a good
+  introduction to the ideas that a user can use to transition to a nullness
+  type system later?
+</p>
+
+
+
+<h1 id="cf-other">Enhance the toolset</h1>
+
+
+<h2 id="index-errors">Improving error messages</h2>
+
+<p>
+Compiler writers have come to realize that clarity of error
+messages is as important as the speed of the executable
+(<a href="http://www.brettbecker.com/wp-content/uploads/2016/06/Becker-Effective-2016-SIGCSE.pdf">1</a>, <a href="https://www.mville.edu/sites/default/files/p53-munson_1.pdf">2</a>,
+<a href="http://se.ethz.ch/~meyer/publications/teaching/compiler-errors.pdf">3</a>,
+<a href="http://static.barik.net/barik/publications/icse2017/PID4655707.pdf">4</a>).  This is especially true when the language or type system has rich features.
+</p>
+
+<p>
+The goal of this project is to improve a compiler's error messages.  Here are
+some distinct challenges:
+</p>
+<ul>
+  <li>
+    Some type errors can be more concisely or clearly expressed than the
+    standard "found type A, expected type B" message.
+  </li>
+  <li>
+    Some types are complex.  The error message could explain them, or link
+    to the manual, or give suggested fixes.
+  </li>
+  <li>
+    Compiler messages currently show
+    the <a href="https://checkerframework.org/manual/#effective-qualifier">effective
+    type</a>, which may be different than what the user wrote due to
+    defaulting, inference, and syntactic sugar.  For example, a user-written
+    <code>@IndexFor("a")</code> annotation is syntactic sugar for
+    <code>@NonNegative @LTLengthOf("a")</code>, and those types are
+    the ones that currently appear in error messages.
+    It might be good to show simpler types or ones that the user wrote.
+  </li>
+  <li>
+    Some checkers combine multiple cooperating type systems;
+    the <a href="https://checkerframework.org/manual/#nullness-checker">Nullness
+    Checker</a> and
+    the <a href="https://checkerframework.org/manual/#index-checker">Index
+    Checker</a> are examples.  If there is a problem with a variable's
+    lower bound type, then its upper bound type should not be shown in the
+    error message.  This will make the message shorter and more specific,
+    and avoid distracting the user with irrelevant information.
+  </li>
+  <li>
+    When a checker has multiple type systems, a type error or the lack of one may depend on facts from multiple type systems, and this should be expressed to the user.
+  </li>
+</ul>
+
+
+<h2 id="java-expression-parser">Java expression parser</h2>
+
+<p>
+A number of type annotations take, as an
+argument, <a href="https://checkerframework.org/manual/#java-expressions-as-arguments">a
+Java expression</a>.  The representation for these
+(the <a href="https://checkerframework.org/api/org.checkerframework.dataflow.expression.JavaExpression.html"><code>JavaExpression</code></a>
+class) is a hack.  The goal of this
+project is to remove it.
+</p>
+
+<p>
+The <code>JavaExpression</code> class
+represents an AST.  There is no need for the Checker Framework to
+define its own AST when the JavaParser AST already exists and is
+maintained.  In fact, <code>JavaExpressionParseUtil</code> uses JavaParser,
+but needlessly converts a
+JavaParser <code>Expression</code> into
+a <code>JavaExpression</code>.
+</p>
+
+<p>
+  The goals for the project include:
+</p>
+<ul>
+  <li>
+    Replace every use
+    of <a href="https://checkerframework.org/api/org.checkerframework.dataflow.expression.JavaExpression.html"><code>JavaExpression</code></a>
+    by a use of the JavaParser
+    class <a href="https://www.javadoc.io/static/com.github.javaparser/javaparser-core/3.15.22/com/github/javaparser/ast/expr/Expression.html"><code>com.github.javaparser.ast.expr.Expression</code></a>.
+  </li>
+  <li>
+    Replace every use of a subclass of <code>JavaExpression</code> (listed in the
+    "Direct Known Subclasses" section of
+    the <a href="https://checkerframework.org/api/org.checkerframework.dataflow.expression.JavaExpression.html"><code>JavaExpression</code>
+    API documentation)</a> by a use of a
+    <a href="https://www.javadoc.io/static/com.github.javaparser/javaparser-core/3.15.22/com/github/javaparser/ast/expr/Expression.html">subclass of <code>Expression</code></a>.  For example, replace every use
+    of <a href="https://checkerframework.org/api/org/checkerframework/dataflow/expression/MethodCall.html"><code>MethodCall</code></a> by <a href="https://www.javadoc.io/static/com.github.javaparser/javaparser-core/3.15.22/com/github/javaparser/ast/expr/MethodCallExpr.html"><code>MethodCallExpr</code></a>.
+  </li>
+  <li>
+    The <a href="https://checkerframework.org/api/org/checkerframework/framework/util/JavaExpressionParseUtil.html"><code>JavaExpressionParseUtil</code></a>
+    class already uses JavaParser, but it uses <code>ExpressionToReceiverVisitor</code> it to construct
+    a <code>JavaExpression</code>.  Have it return a
+    JavaParser <code>Expression</code> instead, and delete <code>ExpressionToReceiverVisitor</code>.
+  </li>
+</ul>
+
+<p>
+Direct replacement of the classes is not possible, or we would have done it
+already.  For example, <code>JavaExpression</code> contains some methods that
+JavaParser lacks, such as <code>isUnassignableByOtherCode</code>.  As a
+first step before doing the tasks listed above, you may want to convert
+these methods from instance methods of <code>JavaExpression</code> into static
+methods in <code>JavaExpressions</code>, making <code>JavaExpression</code> more
+like a standard AST that can be replaced by JavaParser classes.
+You also need to decide how to store the <code>type</code> field
+of <code>JavaExpression</code>, when <code>JavaExpression</code> is eliminated.
+An
+alternate design (or a partial step in the refactoring process) would be to
+retain the <code>JavaExpression</code> class, but make it a thin wrapper around
+JavaParser classes that do most of the real work.
+</p>
+
+<p>
+Another aspect of this project is
+fixing <a href="https://github.com/typetools/checker-framework/issues?q=is%3Aopen+is%3Aissue+label%3AJavaExpressions">the
+    issues that are labeled "JavaExpression".</a>
+</p>
+
+
+<h2 id="dataflow">Dataflow enhancements</h2>
+
+<p>
+The Checker
+Framework's <a href="https://checkerframework.org/manual/#creating-dataflow">dataflow
+    framework</a>
+(<a href="https://checkerframework.org/manual/checker-framework-dataflow-manual.pdf">manual
+    here</a>) implements flow-sensitive type refinement (local type
+inference) and other features.  It is used in the Checker
+Framework and also in <a href="http://errorprone.info/">Error Prone</a>,
+<a href="https://github.com/uber/NullAway">NullAway</a>, and elsewhere.
+</p>
+
+<p>
+There are a number
+of <a href="https://github.com/typetools/checker-framework/issues?q=is%3Aopen+is%3Aissue+label%3ADataflow">open
+issues</a> &mdash; both bugs and feature requests &mdash; related to the
+dataflow framework.  The goal of this project is to address as many of
+those issues as possible, which will directly improve all the tools that
+use it.
+</p>
+
+
+<h2 id="Purity_analysis">Purity (side effect) analysis</h2>
+
+<p>
+A program analysis technique makes estimates about the current values of
+expressions.  When a method call occurs, the analysis has to throw away
+most of its estimates, because the method call might change any variable.
+If the method is known to have no side effects, then the analysis doesn't
+need to throw away its estimates, and the analysis is more precise.
+</p>
+
+<p>
+For example, the Checker Framework unsoundly trusts but does not check
+<a href="https://checkerframework.org/manual/#type-refinement-purity">purity annotations</a>.  This makes
+the system vulnerable to programmer mistakes when writing annotations.  The
+Checker Framework contains a sound checker for immutability annotations,
+but it suffers too many false positive warnings and thus is not usable.  A
+better checker is necessary.  It will also incorporate aspects of an escape
+analysis.
+</p>
+
+<p>
+Choosing an algorithm from the literature is the best choice, but there
+still might be research work to do:  in the past, when implementing
+algorithms from research papers, we have sometimes found that they did not
+work as well as claimed, and we have had to enhance them.  One challenge is
+that any technique used by pluggable type-checking to verify immutability
+must be modular, but many side effect analyses require examining the whole
+program.  The system should require few or no method annotations within
+method bodies.  I'm not sure whether such a system already exists or we
+need to design a new one.
+</p>
+
+<p>
+Perhaps one of these existing side effect analyses could be used:
+<a href="https://github.com/Sable/soot/wiki/Using-Side-Effect-Attributes">Soot</a>,
+<a href="https://pdfs.semanticscholar.org/d2bd/29e20c99a10ab9d0c5ecf3acaf15606407d1.pdf">Geffken</a>.
+</p>
+
+<!--
+For design and implementation ideas, see:
+ * $qn/notes-purity
+ * ~/prof/grants/2011-09-darpa-apac/phase3-extension-proposal
+-->
+
+
+<h2 id="javadoc">Javadoc support</h2>
+
+<p>
+Currently, type annotations are only displayed in Javadoc if they are
+explicitly written by the programmer.  However, the Checker Framework
+provides flexible defaulting mechanisms, reducing the annotation overhead.
+This project will integrate the Checker Framework defaulting phase with
+Javadoc, showing the signatures after applying defaulting rules.
+</p>
+
+<p>
+There are other type-annotation-related improvements to Javadoc that can be
+explored, e.g. using JavaScript to show or hide only the type annotations
+currently of interest.
+</p>
+
+
+
+</body>
+</html>
+
+<!--  LocalWords:  GSoC uwplse codespecs randoop typetools blogosphere Shi
+ -->
+<!--  LocalWords:  Marks's Bikesheds ICST Gyori Legunsen Marinov NonDex bc
+ -->
+<!--  LocalWords:  PossiblyNonDeterministic PossiblyNonDeterministicOrder
+ -->
+<!--  LocalWords:  DeterministicOrder Bazel PossbilyPropagatable Lazar tex
+ -->
+<!--  LocalWords:  NotPropagatable JavaParser contravariantly mortem EnerJ
+ -->
+<!--  LocalWords:  unsoundnesses nondeterministic nondeterministically ASM
+ -->
+<!--  LocalWords:  deterministically prototyped Stateful plugins plugin
+ -->
+<!--  LocalWords:  PDFs Stubparser JLint NullnessLight bytecodes YourKit
+ -->
+<!--  LocalWords:  timeline Uber's NullAway Uber coala BCEL mvn BCEL's AFU
+ -->
+<!--  LocalWords:  Signedness Jake2 README Lookup ASM's Nayuki CRC mis CWE
+ -->
+<!--  LocalWords:  BigInteger lockB lockA signedness microbenchmarks pre
+ -->
+<!--  LocalWords:  checklink subparts UnsignedBytes runtime MyClass jOOU
+ -->
+<!--  LocalWords:  usePriorityQueue PriorityQueue queueMinPathNode didn
+ -->
+<!--  LocalWords:  subchecker IntScope UnsignedLong UnsignedLongs doesn
+ -->
+<!--  LocalWords:  rasmud nondet Geffken Dort unmodifiable MightNotSupport
+ -->
+<!--  LocalWords:  SupportsAllOperations asList emptyList JavaExpression
+ -->
diff --git a/docs/developer/release/.flake8 b/docs/developer/release/.flake8
new file mode 100644
index 0000000..5376b8a
--- /dev/null
+++ b/docs/developer/release/.flake8
@@ -0,0 +1,4 @@
+[flake8]
+max-line-length = 80
+select = C,E,F,W,B,B950
+ignore = E203, E501, W503
diff --git a/docs/developer/release/Makefile b/docs/developer/release/Makefile
new file mode 100644
index 0000000..46fa717
--- /dev/null
+++ b/docs/developer/release/Makefile
@@ -0,0 +1,3 @@
+python-style:
+	black .
+	flake8
diff --git a/docs/developer/release/README-release-process.html b/docs/developer/release/README-release-process.html
new file mode 100644
index 0000000..1570f99
--- /dev/null
+++ b/docs/developer/release/README-release-process.html
@@ -0,0 +1,682 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="icon" type="image/png" href="../../manual/favicon-checkerframework.png" />
+  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
+  <title>Checker Framework release process</title>
+  <script type="text/javascript" src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
+  <link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet"/>
+  <script type="text/javascript" src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
+  <style type="text/css">
+    .page-header {
+    padding-left: 10px;
+    margin-left: 10px;
+    }
+
+    .container {
+    padding-left: 10px;
+    margin-left: 10px;
+    }
+
+    .file_layout_table th {
+    background: #EFEFEF;
+    }
+
+    ul.step_list li {
+    margin-top: 10px;
+    }
+
+    ul#backing_out_steps li {
+    padding-bottom: 10px;
+    }
+  </style>
+</head>
+
+<body>
+<div class="page-header">
+  <h1>Release Process: <small>Annotation File Utilities, Checker Framework</small></h1> <!-- omit from toc -->
+</div>
+
+<div class="container">
+<div class="row" style="width: 100%">
+<div id="content">
+<p>
+  This document explains how to make a release of the Checker Framework
+  and the Annotation File Utilities.
+The process takes around 2 hours (including manual steps) when all goes according to plan.
+  However, the lengthiest steps are automatic so you will be able to work on other tasks.
+</p>
+
+<div class="alert alert-success">
+  <strong>Note:</strong> If you have never read this document before, please read
+  the section <a href="#pre_release_checklist">Pre-release Checklist</a> first.
+  It is also recommended that you read sections  <a href="#release_process_overview">Release Process Overview</a>
+  and <a href="#changelog_guide">Changelog Guidelines</a> to familiarize yourself
+  with the process.
+</div>
+
+<p>Contents:</p>
+<!-- start toc.  do not edit; run html-update-toc instead -->
+    <ul>
+      <li><a href="#step_by_step">Step by Step</a></li>
+      <li><a href="#continuous-integration">Continuous integration tests</a></li>
+      <li><a href="#pre_release_checklist">Pre-release Checklist</a></li>
+      <li><a href="#release_process_overview">Release Process Overview</a>
+        <ul>
+          <li><a href="#file_layout">File Layout</a></li>
+          <li><a href="#release_scripts">Release Scripts</a></li>
+        </ul></li>
+      <li><a href="#changelog_guide">Changelog Guidelines</a>
+        <ul>
+          <li><a href="#content_guidelines">Content Guidelines</a></li>
+          <li><a href="#style_guidelines">Style Guidelines</a></li>
+        </ul></li>
+      <li><a href="#backing_out">Backing Out an Uncommitted Release</a>
+        <ul>
+          <li><a href="#backing_out_steps">Manual Steps to Back Out a Release</a></li>
+        </ul></li>
+      <li><a href="#future_improvements">Future Improvements</a></li>
+    </ul>
+<!-- end toc -->
+
+
+<h2 id="step_by_step">Step by Step</h2>
+
+<p>
+  More information about the steps is
+  provided in the code comments of the <code>main()</code> functions of the <code>release_build</code>
+  and <code>release_push</code> scripts. Please read those comments if you have never done so before.
+</p>
+
+<p>
+Answering 'no' to a prompt does not exit the script unless otherwise indicated.
+</p>
+
+<p>
+Be sure to carefully read all instructions on the command-line before answering 'yes' to a prompt.
+This is because the scripts do not ask you to say 'yes' after each step, so you may
+miss a step if you only read the paragraph asking you to say 'yes'.
+</p>
+
+<ol>
+  <li>If you have never made a release before, follow the instructions in the
+    <a href="#pre_release_checklist">Pre-release Checklist</a>.
+  </li>
+  <li>
+    <b>Update stubparser.</b> If there has been
+    a <a href="https://github.com/javaparser/javaparser/releases">JavaParser
+    release</a> since the last Checker Framework
+    release, <a href="https://github.com/typetools/stubparser#updating-from-upstream-javaparser">update
+    Stubparser from JavaParser</a>.
+  </li>
+  <li>Update the Checker Framework source code:
+    <ul>
+      <li>
+        <b>Update the list of contributors</b>.
+        <pre><code>cd docs/manual
+make contributors.tex
+git diff `git rev-list -1 --before 1.month.ago origin/master` HEAD -- contributors.tex
+</code></pre>
+        Update the <a href="https://github.com/plume-lib/plume-scripts/blob/master/git-authors.sed"><code>plume-scripts/git-authors.sed</code></a> script if
+        any customizations are necessary.
+      </li>
+      <li>
+        <b>Update the AWS Java SDK BOM dependency.</b> In file <code>checker/build.gradle</code>,
+        edit the <code>com.amazonaws:aws-java-sdk-bom</code> dependency to be the latest version
+        number at
+        <a href="https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk">https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk</a>.
+        (The next time Dependabot opens a pull request for that dependency, you
+        might need to respond <code>@dependabot ignore this dependency</code> to
+        prevent such pull requests for the next month.)
+      </li>
+      <li>
+        <b>Update AFU and Checker Framework change logs</b> by following the instructions
+        at <a href="#content_guidelines">content guidelines</a>.
+      </li>
+      <li> <b>Update the Checker Framework version number</b>
+        in <code>checker-framework/build.gradle</code>,
+        in the <code>allprojects</code> block.
+        (The AFU version is updated as part of the release scripts.)
+
+        <p>
+        Update the minor version (second number) if there are any incompatibilities
+        with the previous version.
+        This includes incompatibilities for people who are maintaining a checker,
+        which happens if the signature changes for any method in these classes:
+        </p>
+<!-- Add other classes to this list if relevant. -->
+<pre>
+AnnotatedTypeFactory
+BaseAnnotatedTypeFactory
+GenericAnnotatedTypeFactory
+CFTransfer
+CFAbstractTransfer
+CFAbstractAnalysis
+CFAnalysis
+CFAbstractStore
+CFStore
+CFAbstractValue
+CFValue
+BaseTypeVisitor
+BaseTypeChecker
+SourceChecker
+MultigraphQualifierHierarchy
+AbstractQualifierPolymorphism
+AnnotationUtils
+TreeAnnotator
+</pre>
+        <p>
+        (TODO: Write a command that diffs these classes between the previous and current release.)
+        </p>
+        <p>
+        A rule of thumb is that any framework change that requires changes to more than one type-checker should be at least a minor version (second number in the version number) bump.
+        </p>
+      </li>
+      <li>
+        <b>Commit and push</b> these changes to typetools/master.
+      </li>
+    </ul>
+  <li>
+      <strong>Log into Buffalo</strong><br/>
+      <code>ssh $USER@buffalo.cs.washington.edu<br/></code>
+  </li>
+  <li>
+      <strong>In a user-specific temporary directory, clone/update the Checker Framework repository (it contains the release scripts).</strong><br/>
+      <code>mkdir -p /scratch/$USER/cf-release<br/></code>
+      <code>cd /scratch/$USER/cf-release<br/></code>
+      <code>test -d checker-framework && (cd checker-framework && git pull --quiet) || git clone --quiet https://github.com/typetools/checker-framework.git<br/></code>
+      <code>cd checker-framework/docs/developer/release<br/></code>
+    (The release scripts will checkout and build all dependencies.)
+  </li>
+  <li><strong>Run release_build</strong> to create the release artifacts and place them in the development website<br/>
+      <code>git pull && python3 release_build.py all</code><br/><br/>
+      For verbose debugging output, use the <code>--debug</code> option.<br/><br/>
+      <strong>Note:</strong> The &quot;all&quot; argument specifies that all projects should be built.
+      There has been some work done in order to select only specific projects (AFU and Checker Framework)
+      to be built but more work still needs to be done.  Just pass &quot;all&quot; for now.
+  </li>
+  <li>
+      <strong>Run release_push</strong> to move release artifacts from the development website to the live site and to Maven Central <br/>
+      <code>python3 release_push.py release</code><br/><br/>
+      <div class="alert alert-danger">
+        <strong>Note:</strong> The &quot;<strong>release</strong>&quot; argument states that you intend
+        to do an actual release.  If you just want to test the script, leave out &quot;release&quot;
+        and the script will run but not update anything.
+      </div>
+
+      If you get an obscure error about permissions, try running the release_push script several times
+      in a row. This will sometimes update the repository permissions such that the script can proceed
+      further each time.
+  </li>
+  <li> <strong>Update list of qualifiers in Project Lombok.</strong>
+    Following the instructions in <a href="https://github.com/rzwitserloot/lombok/blob/master/src/core/lombok/core/handlers/HandlerUtil.java#L108">HandlerUtil.java</a>.
+    If the release did not add, remove, or rename any type qualifiers, no changes are required.
+    Those instructions do not work on macOS, so you may need to use buffalo to make the changes.
+  </li>
+</ol>
+
+  <h2 id="continuous-integration">Continuous integration tests</h2>
+
+  <p>
+    The following continuous integration tests should all pass before you make changes that you need to test.  If not, notify the
+    relevant team members.
+  </p>
+
+<ol>
+  <li>The following Continuous Integration builds should be passing (refresh this page to see the latest status):
+  <ul>
+    <li>Annotation File Utilities: <a href="https://travis-ci.org/typetools/annotation-tools/branches"><img src="https://travis-ci.org/typetools/annotation-tools.svg?branch=master" alt="Travis typetools/annotation-tools status"/></a></li>
+    <li>Checker Framework: <a href="https://dev.azure.com/typetools/checker-framework/_build/latest?definitionId=2&branchName=master"><img src="https://dev.azure.com/typetools/checker-framework/_apis/build/status/typetools.checker-framework?branchName=master" alt="Azure Pipelines typetools/checker-framework status"/></a></li>
+    <li>checker-framework.demos: <a href="https://travis-ci.org/typetools/checker-framework.demos/branches"><img src="https://travis-ci.org/typetools/checker-framework.demos.svg?branch=master" alt="Travis typetools/checker-framework.demos status"/></a></li>
+    <li>Daikon: <a href="https://dev.azure.com/codespecs/daikon/_build/latest?definitionId=1&branchName=master"><img src="https://dev.azure.com/codespecs/daikon/_apis/build/status/codespecs.daikon?branchName=master" alt="codespecs/daikon Azure Pipelines status"></a></li>
+    <li>guava-typecheck: <a href="https://travis-ci.org/typetests/guava-typecheck/branches"><img src="https://travis-ci.org/typetests/guava-typecheck.svg?branch=master" alt="Travis typetests/guava-typecheck status"/></a></li>
+  </ul>
+  </li>
+  <li> Check that <a href="https://travis-ci.org/typetests/daikon-typecheck/builds">a daikon-typecheck Travis job</a> was triggered by the last commit of the Checker Framework before the code freeze.
+  </li>
+</ol>
+
+  <h2 id="pre_release_checklist">Pre-release Checklist</h2>
+  <p>If you have not performed a release before you must follow these steps.</p>
+  <table class="table">
+    <tr>
+      <td>1.</td>
+      <td><strong>Ensure you are a member of the types_www and pl_gang groups</strong><br/>
+        Run the command "groups" on the file system (perhaps on Buffalo).
+        If the group types_www or pl_gang do not appear in the list, email the appropriate
+        person to be added (currently Michael Ernst).
+      </td>
+    </tr>
+    <tr>
+      <td>2.</td>
+      <td><strong>Import the Checker Framework signing key for PGP</strong><br/>
+        SSH into Buffalo and run the following command:<br/>
+        <code>gpg --allow-secret-key-import --import /projects/swlab1/checker-framework/hosting-info/release-private.key<br/><br/></code>
+        Note:  The password for this key is located in the file<br/>
+        <code>/projects/swlab1/checker-framework/hosting-info/release-private.password<br/></code>
+        and is used by the release_push script to sign Maven artifacts.<br/>
+      </td>
+    <tr>
+      <td>3.</td>
+      <td><strong>Sign up for a Sonatype Account</strong><br/>
+        You will likely want to do this a few days in advance.
+        Directions can be found <a href="http://central.sonatype.org/pages/ossrh-guide.html">here</a>.
+        Remember to be asked to be added to the org.checkerframework repository by creating
+        a ticket (see the note
+        <a href="http://central.sonatype.org/pages/ossrh-guide.html#create-a-ticket-with-sonatype">here</a>).
+        If after signing up for a Sonatype JIRA account you are not able to sign in to
+        <a href="https://issues.sonatype.org">https://issues.sonatype.org</a>
+        to create a ticket, there may be a configuration problem with your account. In that case, sign up for a
+        second Sonatype account, and use that account to file a ticket indicating that you cannot sign in
+        with your first account.
+      </td>
+    </tr>
+    <tr>
+      <td>4.</td>
+      <td><strong>Add your account information to gradle/build.properties in your home directory.</strong><br/>
+        Create a <code>~/.gradle/gradle.properties</code> file with the following:
+        <pre>
+SONATYPE_NEXUS_USERNAME=your_user_name
+SONATYPE_NEXUS_PASSWORD=your_password</pre>
+        using the information you just created for your Sonatype Account on Buffalo or other
+        network host.  Since the file contains your password, make it non-readable:  <code>chmod og-rw ~/.gradle/gradle.properties</code>
+      </td>
+    </tr>
+    <tr>
+      <td>5.</td>
+      <td><strong>Get edit privileges for Checker Framework, Annotation Tools</strong><br/>
+        Once a release has been completed, you will be prompted to update issues in the
+        issue tracker for each project that has been released.
+        You will need to be a "developer" for each project so that you have update privileges for
+        the issue trackers.
+        You should be listed as a member of typetools on a committer on <a href="https://github.com/orgs/typetools/teams/committers">GitHub</a>.
+      </td>
+    </tr>
+    <tr>
+      <td>6.</td>
+      <td><strong>Install html5validator</strong><br/>
+       If you are going to perform the release on buffalo, you may need to install
+       html5validator.  If <code>html5validator --version</code> issues any errors, try running
+       <code>pip install --user html5validator</code>.  You may need to add <code>.local/bin</code>
+       to your path.
+      </td>
+    </tr>
+    <tr>
+      <td>7.</td>
+      <td><strong>Add GitHub SSH keys to buffalo</strong><br/>
+      See <a href="https://help.github.com/articles/connecting-to-github-with-ssh/">GitHub docs</a>.
+      </td>
+    </tr>
+    <tr>
+      <td>8.</td>
+      <td><strong>Configure git</strong><br/>
+        <pre>
+git config --global user.name "Your Name"
+git config --global user.email you@example.com</pre>
+        or copy over your configuration file.</td>
+    </tr>
+    <tr>
+      <td>9.</td>
+      <td><strong>Run release_build once</strong><br/>
+        To save time on release day addressing potential configuration issues, before your first
+        release day, ensure that <code>release_build</code> is working correctly
+        by following the initial steps of the <a href="#step_by_step">Step by Step</a> section.
+        <code>release_build</code> still needs to be re-run on release day (even if no changes
+        were pushed to any repositories since the last run of <code>release_build</code>) in
+        order for the release date to be correct in all documents.
+      </td>
+    </tr>
+  </table>
+
+  <h2 id="release_process_overview">Release Process Overview</h2>
+  <p>This section first explains the structure of the projects on disk on Buffalo, then lists scripts used during the release process.</p>
+
+    <h3 id="file_layout">File Layout</h3>
+    <table class="table table-bordered file_layout_table">
+      <tr><th colspan="2">Release Directory <span class="glyphicon glyphicon-folder-open" style="float:right;"></span></th></tr>
+      <tr>
+        <td colspan="2" class="top_file">/scratch/$USER/cf-release</td>
+        <td>Contains repositories and scripts to perform a release</td>
+      </tr>
+      <tr>
+        <td></td>
+        <td colspan="2">build</td>
+        <td>Contains repositories for:
+          annotation-tools, checker-framework, plume-bib, plume-scripts, stubparser<br/>
+          These repositories are used to build the Checker Framework and its dependencies.
+        </td>
+      </tr>
+      <tr>
+        <td></td>
+        <td colspan="2">interm</td>
+        <td>Contains repositories for:
+          annotation-tools, checker-framework, plume-bib, plume-scripts, stubparser<br/>
+          The repositories in build are clones of repositories in interm.  The repositories
+          in interm are clones of the GitHub repositories.  This is so that
+          we can test and push the release to the interm repositories then check the
+          release before we have made any irreversible changes.  Then, when the release
+          is validated, all we do is run &quot;git push&quot; or &quot;hg push&quot; on all of the repos in interm.
+        </td>
+      </tr>
+      <tr>
+        <td></td>
+        <td colspan="2">sanity</td>
+        <td>Directory used by the release_push script to do sanity checks.
+        </td>
+      </tr>
+      <tr>
+        <td></td>
+        <td colspan="2">checker-framework/docs/developer/release</td>
+        <td>The directory where the release scripts are run from.  Any changes made under this directory
+          won't be automatically committed when the release is committed.
+        </td>
+      </tr>
+    </table>
+
+    <table class="table table-bordered file_layout_table">
+      <tr><th colspan="2">Live Website Directory <span class="glyphicon glyphicon-folder-open" style="float:right;"></span></th></tr>
+
+      <tr>
+        <td colspan="2" class="top_file">/cse/www2/types</td>
+        <td>The file system location of the website: <br/><a href="https://checkerframework.org/">https://checkerframework.org/</a>.</td>
+      </tr>
+      <tr>
+        <td></td>
+        <td colspan="2">m2-repo</td>
+        <td>The location of the in-house Maven repository.  It is accessed through URL: <br/>
+          <a href="https://checkerframework.org/m2-repo">https://checkerframework.org/m2-repo</a>
+        </td>
+      </tr>
+    </table>
+
+    <table class="table table-bordered file_layout_table">
+      <tr><th colspan="2">Staging (Development) Website Directory <span class="glyphicon glyphicon-folder-open" style="float:right;"></span></th></tr>
+
+      <tr>
+        <td colspan="2" class="top_file">/cse/www2/types/dev</td>
+        <td>The file system location of the development staging website: <br/><a href="https://checkerframework.org/dev/">https://checkerframework.org/dev</a>.</td>
+      </tr>
+      <tr>
+        <td></td>
+        <td colspan="2">&lt;project&gt;/current</td>
+        <td>The staging analogue to /cse/www2/types/&lt;project&gt;/current directory.  The latest release is copied from this directory
+          to the /cse/www2/types/&lt;project&gt;/current by the release_push script after a prompt.
+        </td>
+      </tr>
+      <tr>
+        <td></td>
+        <td colspan="2">m2-repo</td>
+        <td>The location of the in-house staging version of the Maven repository.  It is accessed through URL: <br/>
+          <a href="https://checkerframework.org/dev/m2-repo">https://checkerframework.org/dev/m2-repo</a>
+        </td>
+      </tr>
+    </table>
+
+    <h3 id="release_scripts">Release Scripts</h3>
+    <p>As mentioned above, in order to release the Checker Framework you must run two scripts,
+      release_build.py and release_push.py but there are supporting scripts and files in the
+      release directory.  Some of these files are described below.
+    </p>
+
+    <table class="table table-bordered">
+      <tr>
+        <td>release_build.py</td>
+        <td>Reverts the build/interm repositories to the state of their master repositories in GitHub.
+          It then builds the projects and all their artifacts and then stages a development
+          version of all websites to
+          <a href="https://checkerframework.org/dev/">https://checkerframework.org/dev/</a>
+          This script is thoroughly documented in code comments located in its <code>main()</code> function.
+        </td>
+      </tr>
+      <tr>
+        <td>release_push.py</td>
+        <td>Verifies the release at
+          <a href="https://checkerframework.org/dev/">https://checkerframework.org/dev/</a>
+          is correct through scripts and manual steps.  When the user is satisfied the website
+          is correct it deploys the website to the live website:
+          <a href="https://checkerframework.org/">https://checkerframework.org/</a>.
+          It also pushes Maven artifacts to Maven central.
+          This script is thoroughly documented in code comments located in its <code>main()</code> function.
+        </td>
+      </tr>
+      <tr>
+        <td>release_utils.py</td>
+        <td>Utility methods used in both release_push and release_build.</td>
+      </tr>
+      <tr>
+        <td>sanity_checks.py</td>
+        <td>Contains methods to run various sanity checks.  These methods are called from release_push.py
+        </td>
+      </tr>
+      <tr>
+        <td>release_vars.py</td>
+        <td>Global variables used in release_push, release_build, and sanity_checks.  These should NOT
+          be used in release_utils as release_utils is supposed to consist of self-contained
+          reusable methods.  These variables are tailored for running the scripts on
+          buffalo.cs.washington.edu
+        </td>
+      </tr>
+      <tr>
+        <td>release.xml</td>
+        <td>The previous release script used Ant to do a number of tasks.  Rather than
+          reimplement them all, we still use the targets from this script.
+          They are called from release_push and release_build.
+        </td>
+      </tr>
+    </table>
+
+  <h2 id="changelog_guide">Changelog Guidelines</h2>
+  <p> Each developer is responsible for updating project changelogs to reflect changes they
+    have made each month.  The changelogs should be updated by "feature freeze" though at the
+    latest this should be done before "code freeze".  Before releasing, the person performing
+    the release will be asked to verify the changelogs.  Please check that
+    there aren't typos (i.e. missing release date/version information, spelling errors, etc...).
+    Also check that the changelog obeys the guidelines below.
+  </p>
+
+    <h3 id="content_guidelines">Content Guidelines</h3>
+    <ul>
+      <li>Only (and all) changes that affect users go into the changelog.
+        If a change is breaking, no matter how small, mention it.</li>
+      <li>
+            Even if some code has been committed to the repository, don't
+            announce the feature until it has been documented in the manual.
+      </li>
+      <li>Checker Framework:
+        <ul>
+          <li>List all issues (in issue trackers) resolved since the previous release:
+            <a href="https://checkerframework.org/CHANGELOG.md">previous release</a>,
+            <a href="https://github.com/typetools/checker-framework/issues?q=is%3Aissue+is%3Aclosed+sort%3Aupdated-desc">issues query</a>,
+            <a href="https://github.com/typetools/checker-framework/blob/master/docs/changelog.txt">current changelog</a>.
+          </li>
+          <li>
+            Ensure the changelogs reflect version control commits since the last release, via the
+            command line:<br/>
+            <code>cd $CHECKERFRAMEWORK && git log --name-status `git describe --abbrev=0 --tags`..</code><br/>
+            or via <a href="https://github.com/typetools/checker-framework/commits/master">GitHub Checker Framework commit logs</a>.
+          </li>
+          <li>
+            Ensure the changelogs reflect changes to the manual since the last release, via the
+            command line:<br/>
+            <code>cd $CHECKERFRAMEWORK && git diff -w `git describe --abbrev=0 --tags` docs/manual</code>
+          </li>
+        </ul>
+      </li>
+
+      <li>Annotation File Utilities:
+        <ul>
+          <li>List all issues (in issue trackers) resolved since the previous release:
+            <a href="https://checkerframework.org/annotation-file-utilities/changelog.html">previous release</a>,
+            <a href="https://github.com/typetools/annotation-tools/issues?q=is%3Aissue+is%3Aclosed+sort%3Aupdated-desc">issues query</a>,
+            <a href="https://github.com/typetools/annotation-tools/blob/master/annotation-file-utilities/changelog.html">current changelog</a>
+          </li>
+          <li>
+            Ensure the changelogs reflect version control commits since the last release, via the
+            command line:<br/>
+            <code>cd $CHECKERFRAMEWORK/../annotation-tools && git log --name-status `git describe --abbrev=0 --tags`..</code><br/>
+            or via <a href="https://github.com/typetools/annotation-tools/commits/master">GitHub Annotation File Utilities commit logs</a>.
+          </li>
+          <li>
+            Ensure the changelogs reflect changes to the manual since the last release, via the
+            command line:<br/>
+            <code>cd $CHECKERFRAMEWORK/../annotation-tools && git diff -w `git describe --abbrev=0 --tags` annotation-file-utilities/annotation-file-utilities.html</code>
+          </li>
+        </ul>
+      </li>
+    </ul>
+    <h3 id="style_guidelines">Style Guidelines</h3>
+    <ul>
+      <li>Changes are written from the user's perspective.  Don't include implementation details that users
+        don't care about.
+      </li>
+      <li>To be consistent, write in the past tense (e.g. &quot;Added option for verbose error messages&quot;).</li>
+      <li>Lines should not exceed 80 characters wide.</li>
+      <li>Break the different programs into their own sections.  See notes on release 1.7.0 and 1.5.0 for an
+        example.  Tools should have their own section within the
+        Checker Framework release notes (excluding issue fixes from the issue tracker).
+      </li>
+      <li>Be specific.  Don't write something like "added a few options to Eclipse plugin".  Do write "added
+        options to the Eclipse plugin to include stub files, ignore warnings, and show verbose output."
+      </li>
+    </ul>
+
+  <h2 id="backing_out">Backing Out an Uncommitted Release</h2>
+  <p>At the time of this writing, there are 2 steps that cannot be reverted.</p>
+  <ol>
+    <li>The push from the interm repositories to the GitHub (release) repositories</li>
+    <li>The release of the staged Maven artifacts</li>
+  </ol>
+  <p> If you have executed either of these steps and then realized there is a breaking error, you should do another
+    release.  The release script will allow you to do a point release like &quot;1.8.0.1&quot; when a version
+    &quot;1.8.0&quot; already exists.<br/>
+    <br/>
+    If you have NOT committed an irreversible step then you can
+    follow the steps below to point the live release to a previous release.  You can then redo the original
+    release.  Make sure to say &quot;yes&quot; when the release script asks you to delete the old directories.
+  </p>
+  <h3 id="backing_out_steps">Manual Steps to Back Out a Release</h3>
+  <ul>
+    <li><strong>Drop the artifacts from Central</strong><br/>
+      You may have staged and then closed the artifacts in the Sonatype Central repository.  Drop (do NOT release)
+      these artifacts.  See
+      <a href="https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide#SonatypeOSSMavenRepositoryUsageGuide-DroppingaStagingRepository%28i.e.WhattoDoifYourStagingRepositoryHasErrors%29">
+        the Sonatype OSS Repository Usage Guidelines.
+      </a>
+    </li>
+    <!-- <li><strong>Drop the Maven Artifacts from our Maven repo *** currently unneeded ***</strong>
+      <p class="alert alert-warning"><strong>Note:</strong> The procedure below is NOT recommended
+        by the Maven developers guide.  However, if done carefully and quickly no harm will likely
+        come.  If people end up with out-of-date artifacts, recommend to them that they use the -U option
+        in Maven 3.  Never follow these instructions if the plugin release has been publicized or
+        existed for a while.
+      </p>
+
+      At the moment, we have decided to drop the user of our local Maven repository in favor of using
+      the Central repository exclusively.  This may not be the case in the future.  If we do have a
+      local repository, you need to go into the repositories directory (usually an m2-repo directory) and
+      manually delete the versions of the Maven artifacts.  For instance, for the artifacts checker and compiler
+      using version 0.9.8, I would delete the directories:
+      <ul>
+        <li>.../m2-repo/org/checkerframework/checker/0.9.8</li>
+        <li>.../m2-repo/org/checkerframework/compiler/0.9.8</li>
+      </ul>
+      Furthermore, I would grep the files in the parents of these directories and ensure the version number
+      does not appear in any metadata.  If so, fix up the metadata.
+    </li> -->
+  </ul>
+
+  <p class="alert alert-danger"><strong>Note:</strong> You may find yourself copying release directories for some
+    reason or other.  It is important to remember that the symlinks may be absolute.  You should check any
+    symlinks that may be affected by the move and ensure they point to the new location and not the old one.
+  </p>
+
+  <h2 id="future_improvements">Future Improvements</h2>
+  <p>Below is a roughly priority-ordered list of future improvements.  In a perfect world we would do
+    all of these.  At the moment, I believe only the first 2 (Open JDK Javadoc Fixes,
+    and More Sanity Checks) should have any appreciable priority.
+  </p>
+
+    <h3 id="future_more_sanity_checks">More Sanity Checks</h3> <!-- omit from toc -->
+    There are likely more sanity checks we might want to run during the release process.  One such example
+    would be running the tutorial from the command line.
+    <h4>Tasks:</h4> <!-- omit from toc -->
+    <ul>
+      <li>Implement one of the sanity checks mentioned in this section.</li>
+      <li>Add your sanity check to the appropriate location in the release_push script.</li>
+      <li>Update this document to reflect the new steps.</li>
+    </ul>
+    <h4 id="sanity-checks">Sanity Checks:</h4> <!-- omit from toc -->
+    <ul>
+      <li>Run tutorial sanity check automatically via command line.</li>
+    </ul>
+    <h3 id="future_continuous_integration">Release in Continuous Integration</h3> <!-- omit from toc -->
+    If we could run the release_build script nightly, we could head off broken links and other
+    release related errors.
+    <h4>Tasks:</h4> <!-- omit from toc -->
+    <ul>
+      <li>Create a Buffalo task to run the release_build script without prompting.</li>
+      <li>Observe the output of this task and identify possible errors.</li>
+      <li>Fix up all observed errors until the release_build script runs without error.</li>
+      <li>We probably want to move the link checking of the development site to the release_build script and
+        write it out to disk.  This will allow the developer releasing the framework to consult pre-made
+        link-checking results rather than waiting for the link checker to execute in the push script.
+      </li>
+    </ul>
+    <h3 id="future_auto-copy_release_scripts">Auto-copy Release Scripts</h3> <!-- omit from toc -->
+    The first step in running the release process, after the pre-release checklist, is to copy the
+    release scripts into the <code>cf-release</code> directory.  We could automate this.
+    <h4>Tasks:</h4> <!-- omit from toc -->
+    <ul>
+      <li>Create a read-only script in cf-release/scripts.</li>
+      <li>Edit the script so that it downloads all release scripts into the cf-release directory.</li>
+      <li>Update this document to reflect this new process.</li>
+    </ul>
+    <h3 id="future_option_parsing">Option Parsing</h3> <!-- omit from toc -->
+    There is currently some primitive option parsing written into the release scripts.  We should use
+    Python's built-in option parsing library.  There are multiple built-in libraries.  Please use the latest one.
+    <h4>Tasks:</h4> <!-- omit from toc -->
+    <ul>
+      <li>Look up the latest Python parsing library.</li>
+      <li>Replace the option parsing in the release_build.py and release_push.py scripts with library calls.</li>
+    </ul>
+    <h3 id="future_option_projects">Optional Projects</h3> <!-- omit from toc -->
+    A user might want to test the release_build script only on a specific project.  Some work has gone into
+    the ability to select a project to build.  However, this currently doesn't take into consideration of dependencies.
+    It might be more hassle than it's worth to implement this feature.  We should either complete the implementation
+    or remove the partial implementation that already exists.  See <a href="#future_repl_mode">REPL Mode</a>.
+    <h4>Tasks:</h4> <!-- omit from toc -->
+    <ul>
+      <li>Improve option parsing to allow the user to select only certain projects to build.</li>
+      <li>Improve scripts to resolve dependencies between projects and only build the selected projects and
+        their dependencies.
+      </li>
+    </ul>
+    <h3 id="future_repl_mode">REPL Mode</h3> <!-- omit from toc -->
+    The current script is built in an imperative style with little use of object orientation.  This improvement
+    would create methods or objects that further encapsulate the current set of steps.  We could then provide
+    an easy way to execute these steps from the Python REPL.  This would allow the user to execute individual steps
+    in whatever order they saw fit from the Python REPL.  This could also be used to
+    automatically back out the current release or the ability to edit/debug the release steps more easily.
+    <h4>Tasks:</h4> <!-- omit from toc -->
+    <ul>
+      <li>Further separate the steps of the release process.</li>
+      <li>Provide a default parameterization for these steps to make them easily runnable from the
+        command line. </li>
+      <li>Provide a script to launch Python with these commands on its path.</li>
+      <li>Update this README to document the new functionality.</li>
+    </ul>
+</div>
+</div>
+</div>
+
+</body>
+</html>
+
+<!--  LocalWords:  serif px pre CCC JDK AFS PAG mkdir cd svn co EFEFEF ul li Changelog stubparser JavaParser jdk8 GenericAnnotatedTypeFactory CFTransfer CFAbstractTransfer CFAbstractAnalysis CFAnalysis CFAbstractStore CFStore CFAbstractValue CFValue BaseTypeVisitor BaseTypeChecker SourceChecker MultigraphQualifierHierarchy AbstractQualifierPolymorphism AnnotationUtils TreeAnnotator TODO typetools
+ -->
+<!--  LocalWords:  xml ver dev yyyyMMDD URL url diff hg buildfile kelloggm checkerframework gradle
+ -->
+<!--  LocalWords:  HandlerUtil macOS typecheck www Sonatype typetests html5validator config interm
+ -->
+<!--  LocalWords:  Hevea TEXINPUTS m2 repo py aren changelog changelogs utils OSS symlinks doesn
+ -->
diff --git a/docs/developer/release/checkLinks.sh b/docs/developer/release/checkLinks.sh
new file mode 100755
index 0000000..5769ab4
--- /dev/null
+++ b/docs/developer/release/checkLinks.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# TODO: Remove all dependencies on /homes/gws
+export PERL5LIB=/homes/gws/mernst/bin/install/ActivePerl-5.28/bin:/homes/gws/mernst/bin/install/ActivePerl-5.28/share/perl5:/homes/gws/mernst/bin/install/ActivePerl-5.28/lib64/perl5:
+
+export PATH=/homes/gws/mernst/bin/install/ActivePerl-5.28/bin:${PATH}
+
+# Argument #1 is extra command-line arguments.
+# Argument #2 is URL to check
+
+# shellcheck disable=SC2086
+${CHECKLINK}/checklink -q -r -e `grep -v '^#' ${CHECKLINK}/checklink-args.txt` $1 $2
diff --git a/docs/developer/release/checker-excludes b/docs/developer/release/checker-excludes
new file mode 100644
index 0000000..79621e1
--- /dev/null
+++ b/docs/developer/release/checker-excludes
@@ -0,0 +1,9 @@
+api/**
+checker/bin/README
+checker/bin/javac
+checker/dist/*.asc
+docs/tutorial/tests/**
+docs/tutorial/src/**
+docs/tutorial/test/**
+docs/tutorial/Makefile
+docs/tutorial/README
diff --git a/docs/developer/release/checker-includes b/docs/developer/release/checker-includes
new file mode 100644
index 0000000..405d876
--- /dev/null
+++ b/docs/developer/release/checker-includes
@@ -0,0 +1,28 @@
+README.html
+LICENSE.txt
+checker/bin/**
+checker/dist/**
+checker/resources/**
+docs/CHANGELOG.md
+docs/manual/manual.html
+docs/manual/manual.pdf
+docs/manual/*.svg
+docs/manual/manual001.png
+docs/manual/manual001.svg
+docs/examples/InterningExample.java
+docs/examples/InterningExampleWithWarnings.java
+docs/examples/NullnessExample.java
+docs/examples/NullnessExampleWithWarnings.java
+docs/examples/NullnessReleaseTests.java
+docs/examples/units-extension/Demo.java
+docs/examples/units-extension/Expected.txt
+docs/examples/units-extension/Frequency.java
+docs/examples/units-extension/FrequencyRelations.java
+docs/examples/units-extension/Hz.java
+docs/examples/units-extension/kHz.java
+docs/examples/units-extension/Makefile
+docs/examples/units-extension/README
+docs/examples/MavenExample/pom.xml
+docs/examples/MavenExample/README
+docs/examples/MavenExample/src/main/java/org/checkerframework/example/MavenExample.java
+docs/tutorial/**
diff --git a/docs/developer/release/example-release-schedule.png b/docs/developer/release/example-release-schedule.png
new file mode 100644
index 0000000..078d727
--- /dev/null
+++ b/docs/developer/release/example-release-schedule.png
Binary files differ
diff --git a/docs/developer/release/langtools-excludes b/docs/developer/release/langtools-excludes
new file mode 100644
index 0000000..e8dddde
--- /dev/null
+++ b/docs/developer/release/langtools-excludes
@@ -0,0 +1,10 @@
+old-src/**
+build/**
+dist/bin/**
+.git/**
+.gitignore
+.hg/**
+.hgignore
+.hgtags
+.jcheck
+**/*.orig
diff --git a/docs/developer/release/release.properties b/docs/developer/release/release.properties
new file mode 100644
index 0000000..9d56b22
--- /dev/null
+++ b/docs/developer/release/release.properties
@@ -0,0 +1,19 @@
+# Text of version/date "tags"
+checkers.date.0=<!-- checker-framework-date -->
+checkers.date.1=<!-- /checker-framework-date -->
+checkers.zip.ver.0=<!-- checker-framework-zip-version -->
+checkers.zip.ver.1=<!-- /checker-framework-zip-version -->
+afu.zip.ver.0=<!-- annotation-tools-zip-version -->
+afu.zip.ver.1=<!-- /annotation-tools-zip-version -->
+checkers.ver.0=<!-- checker-framework-version -->
+checkers.ver.1=<!-- /checker-framework-version -->
+compiler.ver.0=<!-- compiler-version -->
+compiler.ver.1=<!-- /compiler-version -->
+h4.ver.0=<h4 id="version">
+h4.ver.1=</h4>
+release.info.0=ReleaseInfo}\\{
+release.info.1=\\}
+release.ver.0=ReleaseVersion}\\{
+release.ver.1=\\}
+gradle.ver.0=ext.checkerFrameworkVersion = '
+gradle.ver.1='
diff --git a/docs/developer/release/release.xml b/docs/developer/release/release.xml
new file mode 100644
index 0000000..7df05c9
--- /dev/null
+++ b/docs/developer/release/release.xml
@@ -0,0 +1,307 @@
+<project name="jsr-308" basedir="../../..">
+
+    <description>
+        Ant utilities for releasing the Checker-Framework.  All targets in this file
+        are intended to be run much like method calls (i.e. you must specify a certain set of properties
+        as parameters and they do not depend on any other target.
+    </description>
+
+    <property file="docs/developer/release/release.properties"/>
+    <!-- Todays date -->
+    <tstamp>
+        <format property="release.date" pattern="d MMM yyyy"/>
+    </tstamp>
+
+    <!-- For updating version numbers in files -->
+    <macrodef name="update">
+        <attribute name="file"/>
+        <attribute name="start"/>
+        <attribute name="end" default=""/>
+        <attribute name="with"/>
+        <sequential>
+            <echo level="info" message="updating @{file}"/>
+            <replaceregexp file="@{file}" byline="true"
+                           match="@{start}.*@{end}" replace="@{start}@{with}@{end}"/>
+        </sequential>
+    </macrodef>
+
+    <target name="update-checker-framework-versions" description="updates version info in Checker Framework documents and build configs">
+
+        <fail unless="checker"            message="checker property is not set!"   />
+        <fail unless="release.ver"         message="release.ver property is not set!"/>
+        <fail unless="afu.properties"      message="afu.properties property is not set!"/>
+        <fail unless="afu.release.date"    message="afu.release.date property is not set!"/>
+        <fail unless="afu.version"    message="afu.version not set"/>
+
+        <property name="cf.docs"             value="${checker}/../docs"                      />
+        <property name="checker.manual"      value="${cf.docs}/manual"                       />
+        <property name="cf.docs.examples"    value="${cf.docs}/examples"                     />
+        <property name="checker.tutorial"    value="${cf.docs}/tutorial"                     />
+        <property name="checker.release"     value="${checker}/../release"                   />
+        <property name="checkerWebPage"      value="${cf.docs}/checker-framework-webpage.html" />
+        <property name="checkerQuickStartPage" value="${checker.manual}/checker-framework-quick-start.html" />
+        <property name="release.version.regexp" value="\d\.\d+\.\d+(?:\.\d)" />
+
+        <!-- for afu.date.0 and afu.date.1 -->
+        <loadproperties srcFile="${afu.properties}"/>
+
+        <replaceregexp file="${checkerWebPage}" byline="true"
+                       match="checker-framework-${release.version.regexp}{0,1}" replace="checker-framework-${release.ver}"/>
+
+        <replaceregexp file="${checkerWebPage}" byline="true"
+                       match="annotation-tools-${release.version.regexp}{0,1}.zip" replace="annotation-tools-${afu.version}.zip"/>
+
+        <update file="${checkerWebPage}"
+                start="${checkers.zip.ver.0}" end="${checkers.zip.ver.1}"
+                with="checker-framework-${release.ver}.zip"/>
+
+        <update file="${checkerWebPage}"
+                start="${afu.zip.ver.0}" end="${afu.zip.ver.1}"
+                with="annotation-tools-${afu.version}.zip"/>
+
+        <update file="${checkerWebPage}"
+                start="${checkers.ver.0}" end="${checkers.ver.1}"
+                with="${release.ver}, ${release.date}"/>
+
+        <update file="${cf.docs}/manual/checkerframework.gradle"
+                start="${gradle.ver.0}" end="${gradle.ver.1}"
+                with="${release.ver}"/>
+
+        <update file="${checkerWebPage}"
+                start="${compiler.ver.0}" end="${compiler.ver.1}"
+                with="${release.ver}, ${release.date}"/>
+
+        <update file="${checkerWebPage}"
+                start="${checkers.date.0}" end="${checkers.date.1}"
+                with="${release.date}"/>
+
+        <update file="${checkerWebPage}"
+                start="${afu.date.0}" end="${afu.date.1}"
+                with="${afu.release.date}"/>
+
+        <replaceregexp file="${checkerQuickStartPage}" byline="true"
+                       match="checker-framework-${release.version.regexp}{0,1}" replace="checker-framework-${release.ver}"/>
+
+        <update file="${checkerQuickStartPage}"
+                start="${checkers.zip.ver.0}" end="${checkers.zip.ver.1}"
+                with="checker-framework-${release.ver}.zip"/>
+
+        <replaceregexp file="${checker.manual}/introduction.tex" byline="true"
+                       match="checker-framework-${release.version.regexp}{0,1}" replace="checker-framework-${release.ver}"/>
+
+        <replaceregexp file="${checker.manual}/external-tools.tex" byline="true"
+                       match="checker-framework-${release.version.regexp}{0,1}" replace="checker-framework-${release.ver}"/>
+
+        <replaceregexp file="${checker.manual}/external-tools.tex" byline="true"
+                       match="checker-${release.version.regexp}{0,1}" replace="checker-${release.ver}"/>
+
+        <replaceregexp file="${checker.manual}/external-tools.tex" byline="true"
+                       match="checker-qual-${release.version.regexp}{0,1}" replace="checker-qual-${release.ver}"/>
+
+        <replaceregexp file="${checker.manual}/external-tools.tex" byline="true"
+                       match="checker-util-${release.version.regexp}{0,1}" replace="checker-util-${release.ver}"/>
+        <replaceregexp file="${checker.manual}/external-tools.tex" byline="true"
+                       match="checker/${release.version.regexp}{0,1}" replace="checker/${release.ver}"/>
+
+        <replaceregexp file="${checker.manual}/external-tools.tex" byline="true"
+                       match="checker-qual/${release.version.regexp}{0,1}" replace="checker-qual/${release.ver}"/>
+
+        <replaceregexp file="${checker.manual}/external-tools.tex" byline="true"
+                       match="checker-util/${release.version.regexp}{0,1}" replace="checker-util/${release.ver}"/>
+
+        <replaceregexp file="${checker.manual}/external-tools.tex" byline="true"
+                       match="/${release.version.regexp}{0,1}/jar" replace="/${release.ver}/jar"/>
+
+        <replaceregexp file="${checker.manual}/external-tools.tex" byline="true"
+                       match="ext.checkerFrameworkVersion = '${release.version.regexp}{0,1}'"
+                       replace="ext.checkerFrameworkVersion = '${release.ver}'"/>
+
+        <replaceregexp file="${checker}/../build-common.properties" byline="true"
+                       match="build.version = ${release.version.regexp}{0,1}" replace="build.version = ${release.ver}"/>
+
+        <update file="${checker}/build.properties"
+                start="build.version = " with="${release.ver}"/>
+
+        <update file="${checker.manual}/manual.tex"
+                start="${release.info.0}" end="${release.info.1}"
+                with="${release.ver} (${release.date})"/>
+
+        <update file="${checker.manual}/manual.tex"
+                start="${release.ver.0}" end="${release.ver.1}"
+                with="${release.ver}"/>
+
+        <!-- examples -->
+        <update file="${cf.docs.examples}/MavenExample/pom.xml"
+                start="${checkers.ver.0}" end="${checkers.ver.1}"
+                with="${release.ver}"/>
+
+    </target>
+
+    <target name="zip-checker-framework" description="Creates a zip archive for the Checker Framework.">
+
+        <fail unless="checker"     message="checker property is not set!" />
+        <fail unless="dest.dir"    message="dest.dir property is not set!"  />
+        <fail unless="file.name"   message="file.name property is not set!" />
+        <fail unless="version"     message="version number is not set!"     />
+
+        <property name="dest.file" value="${dest.dir}/${file.name}"/>
+        <property name="checkerframework" value="${checker}/.."/>
+
+        <property name="checker.includes" value="${checkerframework}/docs/developer/release/checker-includes"/>
+        <property name="checker.excludes" value="${checkerframework}/docs/developer/release/checker-excludes"/>
+
+        <echo message="${checker.includes}" />
+        <available file="${checker.includes}"  property="checker.includes.available"/>
+        <available file="${checker.excludes}"  property="checker.excludes.available"/>
+
+        <fail unless="${checker.includes.available}" message="${checker.includes} includes file not available"/>
+        <fail unless="${checker.excludes.available}" message="${checker.excludes} excludes file not available"/>
+
+        <chmod file="${checker}/bin/javac" perm="+x"/>
+
+        <delete file="${dest.file}"/>
+
+        <zip destfile="${dest.file}" update="true">
+            <zipfileset dir="${checkerframework}" prefix="checker-framework-${version}"
+                        includesfile="${checker.includes}" excludesfile="${checker.excludes}"/>
+
+            <zipfileset dir="${checkerframework}" prefix="checker-framework-${version}"
+                        includes="checker/bin/javac" filemode="755"/>
+
+            <zipfileset dir="${checkerframework}"
+                        includes="docs/logo/Logo/CFLogo.png" fullpath="checker-framework-${version}/docs/manual/CFLogo.png"/>
+
+            <zipfileset dir="${checkerframework}"
+                        includes="docs/logo/Logo/CFLogo.png" fullpath="checker-framework-${version}/docs/tutorial/CFLogo.png"/>
+
+            <zipfileset dir="${checkerframework}"
+                        includes="docs/logo/Logo/CFLogo.png" fullpath="checker-framework-${version}/docs/tutorial/webpages/CFLogo.png"/>
+        </zip>
+
+    </target>
+
+    <target name="zip-tutorial" description="Create a zip archive for the directory tutorial.">
+        <fail unless="checker"    message="checker property is not set!" />
+        <fail unless="dest.dir"   message="dest.dir property is not set!"  />
+
+        <zip destfile="${dest.dir}/$tutorial.zip">
+            <zipfileset dir="${checkerframework}" prefix="tutorial"
+                        includes="docs/tutorial/**"
+                        excludes="docs/tutorial/test/** docs/tutorial/src/** docs/tutorial/Makefile docs/tutorial/README"/>
+            <zipfileset dir="${checkerframework}" includes="docs/logo/Logo/CFLogo.png" fullpath="docs/tutorial/CFLogo.png"/>
+            <zipfileset dir="${checkerframework}" includes="docs/logo/Logo/CFLogo.png" fullpath="docs/tutorial/webpages/CFLogo.png"/>
+        </zip>
+    </target>
+
+    <target name="zip-maven-examples" description="Create a zip archive for the directory checker/examples/MavenExample.">
+        <fail unless="checker"    message="checker property is not set!" />
+        <fail unless="dest.dir"    message="dest.dir property is not set!"  />
+        <fail unless="file.name"   message="file.name property is not set!" />
+        <fail unless="version"     message="version is not set!" />
+
+        <property name="cf.docs"             value="${checker}/../docs"  />
+        <property name="cf.docs.examples"    value="${cf.docs}/examples" />
+
+        <zip destfile="${dest.dir}/${file.name}">
+            <zipfileset dir="${cf.docs.examples}/MavenExample" filemode="755" prefix="maven-examples-${version}"/>
+        </zip>
+    </target>
+
+    <!-- TODO: Document the params -->
+    <target name="checker-framework-website-docs" description="Copies the relevant Checker Framework files to the given directory">
+        <!-- This assumes all files have been built; it just does the copying -->
+
+        <!-- The directory in which the Checker Framework has been built -->
+        <fail unless="checker"   message="checker property is not set!" />
+
+        <!-- The properties set by release_build.py -->
+        <fail unless="dest.dir"   message="dest.dir property is not set!" />
+        <fail unless="manual.name"        message="manual.name property is not set!" />
+        <fail unless="dataflow.manual.name" message="dataflow.manual.name property is not set!" />
+        <fail unless="checker.webpage"   message="checker.webpage property is not set!" />
+
+        <!-- //TODO: PERHAPS CHECK THAT THESE FILES EXIST -->
+        <property name="cf.docs"          value="${checker}/../docs" />
+        <property name="checker.manual"   value="${cf.docs}/manual" />
+        <property name="checker.logo"     value="${cf.docs}/logo" />
+        <property name="dataflow.manual"  value="${checker}/../dataflow/manual" />
+        <property name="checker.tutorial" value="${cf.docs}/tutorial" />
+        <property name="dest.dir.manual"         value="${dest.dir}/manual" />
+        <property name="web.root"         value="${dest.dir}/../.." />
+
+        <copy file="${cf.docs}/${checker.webpage}" tofile="${dest.dir}/${checker.webpage}" />
+
+        <mkdir dir="${dest.dir.manual}" />
+
+        <copy file="${checker.manual}/manual.html" tofile="${dest.dir.manual}/${manual.name}.html"/>
+        <symlink link="${dest.dir.manual}/index.html" resource="${manual.name}.html" />
+        <copy file="${checker.manual}/manual.pdf"  tofile="${dest.dir.manual}/${manual.name}.pdf"/>
+
+        <copy file="${dataflow.manual}/dataflow.pdf"  tofile="${dest.dir.manual}/${dataflow.manual.name}.pdf" failonerror="false"/>
+
+        <copy todir="${dest.dir.manual}" flatten="true">
+            <fileset dir="${checker.manual}">
+                <include name="*.svg"/>
+                <include name="manual001.png"/>
+                <include name="manual001.svg"/>
+            </fileset>
+        </copy>
+
+        <copy file="${checker.logo}/Logo/CFLogo.png" tofile="${web.root}/CFLogo.png" />
+        <copy file="${checker.logo}/Logo/CFLogo.png" tofile="${checker.manual}/CFLogo.png" />
+        <copy file="${checker.logo}/Logo/CFLogo.png" tofile="${dest.dir.manual}/CFLogo.png" />
+        <copy file="${checker.logo}/Logo/CFLogo.png" tofile="${dest.dir}/docs/tutorial/CFLogo.png" />
+        <copy file="${checker.logo}/Logo/CFLogo.png" tofile="${dest.dir}/docs/tutorial/webpages/CFLogo.png" />
+
+        <copy file="${checker.logo}/Checkmark/CFCheckmark_favicon.png" tofile="${dest.dir}/favicon-checkerframework.png" />
+        <copy file="${checker.logo}/Checkmark/CFCheckmark_favicon.png" tofile="${dest.dir.manual}/favicon-checkerframework.png" />
+        <copy file="${checker.logo}/Checkmark/CFCheckmark_favicon.png" tofile="${web.root}/favicon-checkerframework.png" />
+
+        <copy todir="${dest.dir}/tutorial" >
+            <fileset dir="${checker.tutorial}" includes="**"/>
+        </copy>
+
+        <copy todir="${dest.dir}" file="${checker}/../docs/CHANGELOG.md" />
+
+        <copy todir="${dest.dir}/api" flatten="false">
+            <fileset dir="${checker}/../docs/api">
+                <include name="**" />
+            </fileset>
+        </copy>
+
+        <symlink overwrite="true" link="${dest.dir}/index.html" resource="${checker.webpage}" />
+
+    </target>
+
+    <target name="update-and-copy-maven-example" description="Update version/repo information of the Maven example and copy it to dest.dir">
+
+        <fail unless="dest.dir"   message="dest.dir property is not set!" />
+        <fail unless="checker"    message="checker property is not set!" />
+        <fail unless="version"    message="version is not set!" />
+
+        <property name="cf.docs"             value="${checker}/../docs"  />
+        <property name="cf.docs.examples"    value="${cf.docs}/examples" />
+
+        <copy todir="${dest.dir}/MavenExample" flatten="false">
+            <fileset dir="${cf.docs.examples}/MavenExample">
+                <include name="**" />
+            </fileset>
+        </copy>
+
+        <!-- examples -->
+        <update file="${dest.dir}/MavenExample/pom.xml"
+                start="${checkers.ver.0}" end="${checkers.ver.1}"
+                with="${version}"/>
+    </target>
+
+    <!-- Run from parent directory as: ant -e -file release/release.xml pylint -->
+    <target name="pylint" description="Run pylint on Python code">
+        <apply executable="pylint">
+            <arg value="--output-format=parseable"/>
+            <arg value="--rcfile=docs/developer/release/.pylintrc"/>
+            <srcfile/>
+            <fileset dir="." includes="docs/developer/release/*.py"/>
+        </apply>
+    </target>
+
+</project>
diff --git a/docs/developer/release/release_build.py b/docs/developer/release/release_build.py
new file mode 100755
index 0000000..a921d99
--- /dev/null
+++ b/docs/developer/release/release_build.py
@@ -0,0 +1,502 @@
+#!/usr/bin/env python3
+# encoding: utf-8
+"""
+release_build.py
+
+Created by Jonathan Burke on 2013-08-01.
+
+Copyright (c) 2015 University of Washington. All rights reserved.
+"""
+
+# See README-release-process.html for more information
+
+from release_vars import ANNO_FILE_UTILITIES
+from release_vars import ANNO_TOOLS
+from release_vars import BUILD_REPOS
+from release_vars import CF_VERSION
+from release_vars import CHECKER_FRAMEWORK
+from release_vars import CHECKER_FRAMEWORK_RELEASE
+from release_vars import CHECKLINK
+from release_vars import CHECKLINK_REPO
+from release_vars import DEV_SITE_DIR
+from release_vars import INTERM_REPOS
+from release_vars import INTERM_TO_BUILD_REPOS
+from release_vars import LIVE_SITE_URL
+from release_vars import LIVE_TO_INTERM_REPOS
+from release_vars import PLUME_BIB
+from release_vars import PLUME_BIB_REPO
+from release_vars import PLUME_SCRIPTS
+from release_vars import PLUME_SCRIPTS_REPO
+from release_vars import RELEASE_BUILD_COMPLETED_FLAG_FILE
+from release_vars import STUBPARSER
+from release_vars import STUBPARSER_REPO
+from release_vars import TOOLS
+
+from release_vars import execute
+
+from release_utils import check_repos
+from release_utils import check_tools
+from release_utils import clone_from_scratch_or_update
+from release_utils import commit_tag_and_push
+from release_utils import continue_or_exit
+from release_utils import create_empty_file
+from release_utils import current_distribution_by_website
+from release_utils import delete_if_exists
+from release_utils import delete_path_if_exists
+from release_utils import ensure_group_access
+from release_utils import increment_version
+from release_utils import os
+from release_utils import print_step
+from release_utils import prompt_to_continue
+from release_utils import prompt_w_default
+from release_utils import prompt_yes_no
+from release_utils import read_command_line_option
+from release_utils import set_umask
+
+from distutils.dir_util import copy_tree
+import datetime
+import sys
+
+# Turned on by the --debug command-line option.
+debug = False
+ant_debug = ""
+
+# Currently only affects the Checker Framework tests, which run the longest
+notest = False
+
+
+def print_usage():
+    """Print usage information."""
+    print("Usage:    python3 release_build.py [options]")
+    print("\n  --debug  turns on debugging mode which produces verbose output")
+    print("\n  --notest  disables tests to speed up scripts; for debugging only")
+
+
+def clone_or_update_repos():
+    """Clone the relevant repos from scratch or update them if they exist and
+    if directed to do so by the user."""
+    message = """Before building the release, we clone or update the release repositories.
+However, if you have had to run the script multiple times today and no files
+have changed since the last attempt, you may skip this step.
+WARNING: IF THIS IS YOUR FIRST RUN OF THE RELEASE ON RELEASE DAY, DO NOT SKIP THIS STEP.
+The following repositories will be cloned or updated from their origins:
+"""
+    for live_to_interm in LIVE_TO_INTERM_REPOS:
+        message += live_to_interm[1] + "\n"
+
+    for interm_to_build in INTERM_TO_BUILD_REPOS:
+        message += interm_to_build[1] + "\n"
+
+    message += PLUME_SCRIPTS + "\n"
+    message += CHECKLINK + "\n"
+    message += PLUME_BIB + "\n"
+    message += STUBPARSER + "\n\n"
+
+    message += "Clone repositories from scratch (answer no to be given a chance to update them instead)?"
+
+    clone_from_scratch = True
+
+    if not prompt_yes_no(message, True):
+        clone_from_scratch = False
+        if not prompt_yes_no(
+            "Update the repositories without cloning them from scratch?", True
+        ):
+            print("WARNING: Continuing without refreshing repositories.\n")
+            return
+
+    for live_to_interm in LIVE_TO_INTERM_REPOS:
+        clone_from_scratch_or_update(
+            live_to_interm[0], live_to_interm[1], clone_from_scratch, True
+        )
+
+    for interm_to_build in INTERM_TO_BUILD_REPOS:
+        clone_from_scratch_or_update(
+            interm_to_build[0], interm_to_build[1], clone_from_scratch, False
+        )
+
+    clone_from_scratch_or_update(
+        PLUME_SCRIPTS_REPO, PLUME_SCRIPTS, clone_from_scratch, False
+    )
+    clone_from_scratch_or_update(CHECKLINK_REPO, CHECKLINK, clone_from_scratch, False)
+    clone_from_scratch_or_update(PLUME_BIB_REPO, PLUME_BIB, clone_from_scratch, False)
+    clone_from_scratch_or_update(STUBPARSER_REPO, STUBPARSER, clone_from_scratch, False)
+    # clone_from_scratch_or_update(LIVE_ANNO_REPO, ANNO_TOOLS, clone_from_scratch, False)
+
+
+def get_afu_date():
+    """If the AFU is being built, return the current date, otherwise return the
+    date of the last AFU release as indicated in the AFU home page."""
+    return get_current_date()
+
+
+def get_new_version(project_name, curr_version):
+    "Queries the user for the new version number; returns old and new version numbers."
+
+    print("Current " + project_name + " version: " + curr_version)
+    suggested_version = increment_version(curr_version)
+
+    new_version = prompt_w_default(
+        "Enter new version", suggested_version, "^\\d+\\.\\d+(?:\\.\\d+){0,2}$"
+    )
+
+    print("New version: " + new_version)
+
+    if curr_version == new_version:
+        curr_version = prompt_w_default(
+            "Enter current version", suggested_version, "^\\d+\\.\\d+(?:\\.\\d+){0,2}$"
+        )
+        print("Current version: " + curr_version)
+
+    return (curr_version, new_version)
+
+
+def create_dev_website_release_version_dir(project_name, version):
+    """Create the directory for the given version of the given project under
+    the releases directory of the dev web site."""
+    if project_name in (None, "checker-framework"):
+        interm_dir = os.path.join(DEV_SITE_DIR, "releases", version)
+    else:
+        interm_dir = os.path.join(DEV_SITE_DIR, project_name, "releases", version)
+    delete_path_if_exists(interm_dir)
+
+    execute("mkdir -p %s" % interm_dir, True, False)
+    return interm_dir
+
+
+def create_dirs_for_dev_website_release_versions(cf_version):
+    """Create directories for the given versions of the CF, and AFU
+    projects under the releases directory of the dev web site.
+    For example,
+    /cse/www2/types/dev/checker-framework/<project_name>/releases/<version> ."""
+    afu_interm_dir = create_dev_website_release_version_dir(
+        "annotation-file-utilities", cf_version
+    )
+    checker_framework_interm_dir = create_dev_website_release_version_dir(
+        None, cf_version
+    )
+
+    return (afu_interm_dir, checker_framework_interm_dir)
+
+
+# def update_project_dev_website_symlink(project_name, release_version):
+#     """Update the \"current\" symlink in the dev web site for the given project
+#     to point to the given release of the project on the dev web site."""
+#     project_dev_site = os.path.join(DEV_SITE_DIR, project_name)
+#     link_path = os.path.join(project_dev_site, "current")
+#
+#     dev_website_relative_dir = os.path.join("releases", release_version)
+#
+#     print "Writing symlink: " + link_path + "\nto point to relative directory: " + dev_website_relative_dir
+#     force_symlink(dev_website_relative_dir, link_path)
+
+
+def update_project_dev_website(project_name, release_version):
+    """Update the dev web site for the given project
+    according to the given release of the project on the dev web site."""
+    if project_name == "checker-framework":
+        project_dev_site = DEV_SITE_DIR
+    else:
+        project_dev_site = os.path.join(DEV_SITE_DIR, project_name)
+    dev_website_relative_dir = os.path.join(
+        project_dev_site, "releases", release_version
+    )
+
+    print("Copying from : " + dev_website_relative_dir + "\nto: " + project_dev_site)
+    copy_tree(dev_website_relative_dir, project_dev_site)
+
+
+def get_current_date():
+    "Return today's date in a string format similar to: 02 May 2016"
+    return datetime.date.today().strftime("%d %b %Y")
+
+
+def build_annotation_tools_release(version, afu_interm_dir):
+    """Build the Annotation File Utilities project's artifacts and place them
+    in the development web site."""
+    execute("java -version", True)
+
+    date = get_current_date()
+
+    build = os.path.join(ANNO_FILE_UTILITIES, "build.xml")
+    ant_cmd = (
+        'ant %s -buildfile %s -e update-versions -Drelease.ver="%s" -Drelease.date="%s"'
+        % (ant_debug, build, version, date)
+    )
+    execute(ant_cmd)
+
+    # Deploy to intermediate site
+    gradle_cmd = "./gradlew releaseBuild -Pafu.version=%s -Pdeploy-dir=%s" % (
+        version,
+        afu_interm_dir,
+    )
+    execute(gradle_cmd, True, False, ANNO_FILE_UTILITIES)
+
+    update_project_dev_website("annotation-file-utilities", version)
+
+
+def build_and_locally_deploy_maven(version):
+    execute("./gradlew publishToMavenLocal", working_dir=CHECKER_FRAMEWORK)
+
+
+def build_checker_framework_release(
+    version, old_cf_version, afu_release_date, checker_framework_interm_dir
+):
+    """Build the release files for the Checker Framework project, including the
+    manual and the zip file, and run tests on the build."""
+    checker_dir = os.path.join(CHECKER_FRAMEWORK, "checker")
+
+    afu_build_properties = os.path.join(ANNO_FILE_UTILITIES, "build.properties")
+
+    # build stubparser
+    execute("mvn package -Dmaven.test.skip=true", True, False, STUBPARSER)
+
+    # build annotation-tools
+    execute("./gradlew assemble -Prelease=true", True, False, ANNO_FILE_UTILITIES)
+
+    # update versions
+    ant_props = (
+        '-Dchecker=%s -Drelease.ver=%s -Dafu.version=%s -Dafu.properties=%s -Dafu.release.date="%s"'
+        % (checker_dir, version, version, afu_build_properties, afu_release_date)
+    )
+    # IMPORTANT: The release.xml in the directory where the Checker Framework is being built is used. Not the release.xml in the directory you ran release_build.py from.
+    ant_cmd = "ant %s -f release.xml %s update-checker-framework-versions " % (
+        ant_debug,
+        ant_props,
+    )
+    execute(ant_cmd, True, False, CHECKER_FRAMEWORK_RELEASE)
+
+    # Check that updating versions didn't overlook anything.
+    print("Here are occurrences of the old version number, " + old_cf_version)
+    grep_cmd = "grep -r --exclude-dir=build --exclude-dir=.git -F %s" % old_cf_version
+    execute(grep_cmd, False, False, CHECKER_FRAMEWORK)
+    continue_or_exit(
+        'If any occurrence is not acceptable, then stop the release, update target "update-checker-framework-versions" in file release.xml, and start over.'
+    )
+
+    # build the checker framework binaries and documents, run checker framework tests
+    if notest:
+        ant_cmd = "./gradlew releaseBuild"
+    else:
+        ant_cmd = "./gradlew releaseAndTest"
+    execute(ant_cmd, True, False, CHECKER_FRAMEWORK)
+
+    # make the Checker Framework Manual
+    checker_manual_dir = os.path.join(CHECKER_FRAMEWORK, "docs", "manual")
+    execute("make manual.pdf manual.html", True, False, checker_manual_dir)
+
+    # make the dataflow manual
+    dataflow_manual_dir = os.path.join(CHECKER_FRAMEWORK, "dataflow", "manual")
+    execute("make", True, False, dataflow_manual_dir)
+
+    # make the checker framework tutorial
+    checker_tutorial_dir = os.path.join(CHECKER_FRAMEWORK, "docs", "tutorial")
+    execute("make", True, False, checker_tutorial_dir)
+
+    cfZipName = "checker-framework-%s.zip" % version
+
+    # Create checker-framework-X.Y.Z.zip and put it in checker_framework_interm_dir
+    ant_props = "-Dchecker=%s -Ddest.dir=%s -Dfile.name=%s -Dversion=%s" % (
+        checker_dir,
+        checker_framework_interm_dir,
+        cfZipName,
+        version,
+    )
+    # IMPORTANT: The release.xml in the directory where the Checker Framework is being built is used. Not the release.xml in the directory you ran release_build.py from.
+    ant_cmd = "ant %s -f release.xml %s zip-checker-framework " % (ant_debug, ant_props)
+    execute(ant_cmd, True, False, CHECKER_FRAMEWORK_RELEASE)
+
+    ant_props = "-Dchecker=%s -Ddest.dir=%s -Dfile.name=%s -Dversion=%s" % (
+        checker_dir,
+        checker_framework_interm_dir,
+        "mvn-examples.zip",
+        version,
+    )
+    # IMPORTANT: The release.xml in the directory where the Checker Framework is being built is used. Not the release.xml in the directory you ran release_build.py from.
+    ant_cmd = "ant %s -f release.xml %s zip-maven-examples " % (ant_debug, ant_props)
+    execute(ant_cmd, True, False, CHECKER_FRAMEWORK_RELEASE)
+
+    # copy the remaining checker-framework website files to checker_framework_interm_dir
+    ant_props = "-Dchecker=%s -Ddest.dir=%s -Dmanual.name=%s -Ddataflow.manual.name=%s -Dchecker.webpage=%s" % (
+        checker_dir,
+        checker_framework_interm_dir,
+        "checker-framework-manual",
+        "checker-framework-dataflow-manual",
+        "checker-framework-webpage.html",
+    )
+
+    # IMPORTANT: The release.xml in the directory where the Checker Framework is being built is used. Not the release.xml in the directory you ran release_build.py from.
+    ant_cmd = "ant %s -f release.xml %s checker-framework-website-docs " % (
+        ant_debug,
+        ant_props,
+    )
+    execute(ant_cmd, True, False, CHECKER_FRAMEWORK_RELEASE)
+
+    # clean no longer necessary files left over from building the checker framework tutorial
+    checker_tutorial_dir = os.path.join(CHECKER_FRAMEWORK, "docs", "tutorial")
+    execute("make clean", True, False, checker_tutorial_dir)
+
+    build_and_locally_deploy_maven(version)
+
+    update_project_dev_website("checker-framework", version)
+
+    return
+
+
+def commit_to_interm_projects(cf_version):
+    """Commit the changes for each project from its build repo to its
+    corresponding intermediate repo in preparation for running the release_push
+    script, which does not read the build repos."""
+    # Use project definition instead, see find project location find_project_locations
+
+    commit_tag_and_push(cf_version, ANNO_TOOLS, "")
+
+    commit_tag_and_push(cf_version, CHECKER_FRAMEWORK, "checker-framework-")
+
+
+def main(argv):
+    """The release_build script is responsible for building the release
+    artifacts for the AFU and the Checker Framework projects
+    and placing them in the development web site. It can also be used to review
+    the documentation and changelogs for the three projects."""
+    # MANUAL Indicates a manual step
+    # AUTO Indicates the step is fully automated.
+
+    delete_if_exists(RELEASE_BUILD_COMPLETED_FLAG_FILE)
+
+    set_umask()
+
+    global debug
+    global ant_debug
+    debug = read_command_line_option(argv, "--debug")
+    if debug:
+        ant_debug = "-debug"
+    global notest
+    notest = read_command_line_option(argv, "--notest")
+
+    afu_date = get_afu_date()
+
+    # For each project, build what is necessary but don't push
+
+    print("Building a new release of Annotation Tools and the Checker Framework!")
+
+    print("\nPATH:\n" + os.environ["PATH"] + "\n")
+
+    print_step("Build Step 1: Clone the build and intermediate repositories.")  # MANUAL
+
+    # Recall that there are 3 relevant sets of repositories for the release:
+    # * build repository - repository where the project is built for release
+    # * intermediate repository - repository to which release related changes are pushed after the project is built
+    # * release repository - GitHub repositories, the central repository.
+
+    # Every time we run release_build, changes are committed to the intermediate repository from build but NOT to
+    # the release repositories. If we are running the build script multiple times without actually committing the
+    # release then these changes need to be cleaned before we run the release_build script again.
+    # The "Clone/update repositories" step updates the repositories with respect to the live repositories on
+    # GitHub, but it is the "Verify repositories" step that ensures that they are clean,
+    # i.e. indistinguishable from a freshly cloned repository.
+
+    # check we are cloning LIVE -> INTERM, INTERM -> RELEASE
+    print_step("\n1a: Clone/update repositories.")  # MANUAL
+    clone_or_update_repos()
+
+    # This step ensures the previous step worked. It checks to see if we have any modified files, untracked files,
+    # or outgoing changesets. If so, it fails.
+
+    print_step("1b: Verify repositories.")  # MANUAL
+    check_repos(INTERM_REPOS, True, True)
+    check_repos(BUILD_REPOS, True, False)
+
+    # The release script requires a number of common tools (Ant, Maven, make, etc...). This step checks
+    # to make sure all tools are available on the command line in order to avoid wasting time in the
+    # event a tool is missing late in execution.
+
+    print_step("Build Step 2: Check tools.")  # AUTO
+    check_tools(TOOLS)
+
+    # Usually we increment the release by 0.0.1 per release unless there is a major change.
+    # The release script will read the current version of the Checker Framework/Annotation File Utilities
+    # from the release website and then suggest the next release version 0.0.1 higher than the current
+    # version. You can also manually specify a version higher than the current version. Lower or equivalent
+    # versions are not possible and will be rejected when you try to push the release.
+
+    print_step("Build Step 3: Determine release versions.")  # MANUAL
+
+    old_cf_version = current_distribution_by_website(LIVE_SITE_URL)
+    cf_version = CF_VERSION
+    print("Version: " + cf_version + "\n")
+
+    if old_cf_version == cf_version:
+        print(
+            (
+                "It is *strongly discouraged* to not update the release version numbers for the Checker Framework "
+                + "even if no changes were made to these in a month. This would break so much "
+                + "in the release scripts that they would become unusable. Update the version number in checker-framework/build.gradle\n"
+            )
+        )
+        prompt_to_continue()
+
+    print_step(
+        "Build Step 4: Create directories for the current release on the dev site."
+    )  # AUTO
+
+    (
+        afu_interm_dir,
+        checker_framework_interm_dir,
+    ) = create_dirs_for_dev_website_release_versions(cf_version)
+
+    # The projects are built in the following order:
+    # Annotation File Utilities and Checker Framework. Furthermore, their
+    # manuals and websites are also built and placed in their relevant locations
+    # at https://checkerframework.org/dev/ .  This is the most time-consuming
+    # piece of the release. There are no prompts from this step forward; you
+    # might want to get a cup of coffee and do something else until it is done.
+
+    print_step("Build Step 5: Build projects and websites.")  # AUTO
+
+    print_step("5a: Build Annotation File Utilities.")
+    build_annotation_tools_release(cf_version, afu_interm_dir)
+
+    print_step("5b: Build Checker Framework.")
+    build_checker_framework_release(
+        cf_version,
+        old_cf_version,
+        afu_date,
+        checker_framework_interm_dir,
+    )
+
+    print_step("Build Step 6: Overwrite .htaccess and CFLogo.png .")  # AUTO
+
+    # Not "cp -p" because that does not work across filesystems whereas rsync does
+    CFLOGO = os.path.join(CHECKER_FRAMEWORK, "docs", "logo", "Logo", "CFLogo.png")
+    execute("rsync --times %s %s" % (CFLOGO, checker_framework_interm_dir))
+
+    # Each project has a set of files that are updated for release. Usually these updates include new
+    # release date and version information. All changed files are committed and pushed to the intermediate
+    # repositories. Keep this in mind if you have any changed files from steps 1d, 4, or 5. Edits to the
+    # scripts in the cf-release/scripts directory will never be checked in.
+
+    print_step("Build Step 7: Commit projects to intermediate repos.")  # AUTO
+    commit_to_interm_projects(cf_version)
+
+    # Adds read/write/execute group permissions to all of the new dev website directories
+    # under https://checkerframework.org/dev/ These directories need group read/execute
+    # permissions in order for them to be served.
+
+    print_step("\n\nBuild Step 8: Add group permissions to repos.")
+    for build in BUILD_REPOS:
+        ensure_group_access(build)
+
+    for interm in INTERM_REPOS:
+        ensure_group_access(interm)
+
+    # At the moment, this will lead to output error messages because some metadata in some of the
+    # dirs I think is owned by Mike or Werner.  We should identify these and have them fix it.
+    # But as long as the processes return a zero exit status, we should be ok.
+    print_step("\n\nBuild Step 9: Add group permissions to websites.")  # AUTO
+    ensure_group_access(DEV_SITE_DIR)
+
+    create_empty_file(RELEASE_BUILD_COMPLETED_FLAG_FILE)
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))
diff --git a/docs/developer/release/release_push.py b/docs/developer/release/release_push.py
new file mode 100755
index 0000000..0669b92
--- /dev/null
+++ b/docs/developer/release/release_push.py
@@ -0,0 +1,595 @@
+#!/usr/bin/env python3
+# encoding: utf-8
+"""
+release_push.py
+
+Created by Jonathan Burke on 2013-12-30.
+
+Copyright (c) 2013-2016 University of Washington. All rights reserved.
+"""
+
+# See README-release-process.html for more information
+
+import os
+from os.path import expanduser
+
+from release_vars import AFU_LIVE_RELEASES_DIR
+from release_vars import CF_VERSION
+from release_vars import CHECKER_FRAMEWORK
+from release_vars import CHECKER_LIVE_RELEASES_DIR
+from release_vars import CHECKLINK
+from release_vars import DEV_SITE_DIR
+from release_vars import DEV_SITE_URL
+from release_vars import INTERM_ANNO_REPO
+from release_vars import INTERM_CHECKER_REPO
+from release_vars import LIVE_SITE_DIR
+from release_vars import LIVE_SITE_URL
+from release_vars import RELEASE_BUILD_COMPLETED_FLAG_FILE
+from release_vars import SANITY_DIR
+from release_vars import SCRIPTS_DIR
+from release_vars import TMP_DIR
+
+from release_vars import execute
+
+from release_utils import continue_or_exit
+from release_utils import current_distribution_by_website
+from release_utils import delete_if_exists
+from release_utils import delete_path
+from release_utils import ensure_group_access
+from release_utils import get_announcement_email
+from release_utils import print_step
+from release_utils import prompt_to_continue
+from release_utils import prompt_yes_no
+from release_utils import push_changes_prompt_if_fail
+from release_utils import read_command_line_option
+from release_utils import read_first_line
+from release_utils import set_umask
+from release_utils import subprocess
+from release_utils import version_number_to_array
+from sanity_checks import javac_sanity_check, maven_sanity_check
+
+import sys
+
+
+def check_release_version(previous_release, new_release):
+    """Ensure that the given new release version is greater than the given
+    previous one."""
+    if version_number_to_array(previous_release) >= version_number_to_array(
+        new_release
+    ):
+        raise Exception(
+            "Previous release version ("
+            + previous_release
+            + ") should be less than "
+            + "the new release version ("
+            + new_release
+            + ")"
+        )
+
+
+def copy_release_dir(path_to_dev_releases, path_to_live_releases, release_version):
+    """Copy a release directory with the given release version from the dev
+    site to the live site. For example,
+    /cse/www2/types/dev/checker-framework/releases/2.0.0 ->
+    /cse/www2/types/checker-framework/releases/2.0.0"""
+    source_location = os.path.join(path_to_dev_releases, release_version)
+    dest_location = os.path.join(path_to_live_releases, release_version)
+
+    if os.path.exists(dest_location):
+        delete_path(dest_location)
+
+    if os.path.exists(dest_location):
+        raise Exception("Destination location exists: " + dest_location)
+
+    # The / at the end of the source location is necessary so that
+    # rsync copies the files in the source directory to the destination directory
+    # rather than a subdirectory of the destination directory.
+    cmd = "rsync --omit-dir-times --recursive --links --quiet %s/ %s" % (
+        source_location,
+        dest_location,
+    )
+    execute(cmd)
+
+    return dest_location
+
+
+def promote_release(path_to_releases, release_version):
+    """Copy a release directory to the top level. For example,
+    /cse/www2/types/checker-framework/releases/2.0.0/* ->
+    /cse/www2/types/checker-framework/*"""
+    from_dir = os.path.join(path_to_releases, release_version)
+    to_dir = os.path.join(path_to_releases, "..")
+    # Trailing slash is crucial.
+    cmd = "rsync -aJ --omit-dir-times %s/ %s" % (from_dir, to_dir)
+    execute(cmd)
+
+
+def copy_htaccess():
+    "Copy the .htaccess file from the dev site to the live site."
+    LIVE_HTACCESS = os.path.join(LIVE_SITE_DIR, ".htaccess")
+    execute(
+        "rsync --times %s %s" % (os.path.join(DEV_SITE_DIR, ".htaccess"), LIVE_HTACCESS)
+    )
+    ensure_group_access(LIVE_HTACCESS)
+
+
+def copy_releases_to_live_site(cf_version):
+    """Copy the new releases of the AFU and the Checker
+    Framework from the dev site to the live site."""
+    CHECKER_INTERM_RELEASES_DIR = os.path.join(DEV_SITE_DIR, "releases")
+    copy_release_dir(CHECKER_INTERM_RELEASES_DIR, CHECKER_LIVE_RELEASES_DIR, cf_version)
+    promote_release(CHECKER_LIVE_RELEASES_DIR, cf_version)
+    AFU_INTERM_RELEASES_DIR = os.path.join(
+        DEV_SITE_DIR, "annotation-file-utilities", "releases"
+    )
+    copy_release_dir(AFU_INTERM_RELEASES_DIR, AFU_LIVE_RELEASES_DIR, cf_version)
+    promote_release(AFU_LIVE_RELEASES_DIR, cf_version)
+
+
+def ensure_group_access_to_releases():
+    """Gives group access to all files and directories in the \"releases\"
+    subdirectories on the live web site for the AFU and the
+    Checker Framework."""
+    ensure_group_access(AFU_LIVE_RELEASES_DIR)
+    ensure_group_access(CHECKER_LIVE_RELEASES_DIR)
+
+
+def stage_maven_artifacts_in_maven_central(new_cf_version):
+    """Stages the Checker Framework artifacts on Maven Central. After the
+    artifacts are staged, the user can then close them, which makes them
+    available for testing purposes but does not yet release them on Maven
+    Central. This is a reversible step, since artifacts that have not been
+    released can be dropped, which for our purposes is equivalent to never
+    having staged them."""
+    gnupgPassphrase = read_first_line(
+        "/projects/swlab1/checker-framework/hosting-info/release-private.password"
+    )
+    # When bufalo uses gpg2 version 2.2+, then remove signing.gnupg.useLegacyGpg=true
+    execute(
+        "./gradlew publish -Prelease=true --no-parallel -Psigning.gnupg.useLegacyGpg=true -Psigning.gnupg.keyName=checker-framework-dev@googlegroups.com -Psigning.gnupg.passphrase=%s"
+        % gnupgPassphrase,
+        working_dir=CHECKER_FRAMEWORK,
+    )
+
+
+def is_file_empty(filename):
+    "Returns true if the given file has size 0."
+    return os.path.getsize(filename) == 0
+
+
+def run_link_checker(site, output, additional_param=""):
+    """Runs the link checker on the given web site and saves the output to the
+    given file. Additional parameters (if given) are passed directly to the
+    link checker script."""
+    delete_if_exists(output)
+    check_links_script = os.path.join(SCRIPTS_DIR, "checkLinks.sh")
+    if additional_param == "":
+        cmd = ["sh", check_links_script, site]
+    else:
+        cmd = ["sh", check_links_script, additional_param, site]
+    env = {"CHECKLINK": CHECKLINK}
+
+    out_file = open(output, "w+")
+
+    print(
+        (
+            "Executing: "
+            + " ".join("%s=%r" % (key2, val2) for (key2, val2) in list(env.items()))
+            + " "
+            + " ".join(cmd)
+        )
+    )
+    process = subprocess.Popen(cmd, env=env, stdout=out_file, stderr=out_file)
+    process.communicate()
+    process.wait()
+    out_file.close()
+
+    if process.returncode != 0:
+        raise Exception(
+            "Non-zero return code (%s; see output in %s) while executing %s"
+            % (process.returncode, output, cmd)
+        )
+
+    return output
+
+
+def check_all_links(
+    afu_website,
+    checker_website,
+    suffix,
+    test_mode,
+    cf_version_of_broken_link_to_suppress="",
+):
+    """Checks all links on the given web sites for the AFU
+    and the Checker Framework. The suffix parameter should be \"dev\" for the
+    dev web site and \"live\" for the live web site. test_mode indicates
+    whether this script is being run in release or in test mode. The
+    cf_version_of_broken_link_to_suppress parameter should be set to the
+    new Checker Framework version and should only be passed when checking links
+    for the dev web site (to prevent reporting of a broken link to the
+    not-yet-live zip file for the new release)."""
+    afuCheck = run_link_checker(afu_website, TMP_DIR + "/afu." + suffix + ".check")
+    additional_param = ""
+    if cf_version_of_broken_link_to_suppress != "":
+        additional_param = (
+            "--suppress-broken 404:https://checkerframework.org/checker-framework-"
+            + cf_version_of_broken_link_to_suppress
+            + ".zip"
+        )
+    checkerCheck = run_link_checker(
+        checker_website,
+        TMP_DIR + "/checker-framework." + suffix + ".check",
+        additional_param,
+    )
+
+    is_afuCheck_empty = is_file_empty(afuCheck)
+    is_checkerCheck_empty = is_file_empty(checkerCheck)
+
+    errors_reported = not (is_afuCheck_empty and is_checkerCheck_empty)
+    if errors_reported:
+        print("Link checker results can be found at:\n")
+    if not is_afuCheck_empty:
+        print("\t" + afuCheck + "\n")
+    if not is_checkerCheck_empty:
+        print("\t" + checkerCheck + "\n")
+    if errors_reported:
+        release_option = ""
+        if not test_mode:
+            release_option = " release"
+        raise Exception(
+            "The link checker reported errors.  Please fix them by committing changes to the mainline\n"
+            + 'repository and pushing them to GitHub, running "python release_build.py all" again\n'
+            + '(in order to update the development site), and running "python release_push'
+            + release_option
+            + '" again.'
+        )
+
+
+def push_interm_to_release_repos():
+    """Push the release to the GitHub repositories for
+    the AFU and the Checker Framework. This is an
+    irreversible step."""
+    push_changes_prompt_if_fail(INTERM_ANNO_REPO)
+    push_changes_prompt_if_fail(INTERM_CHECKER_REPO)
+
+
+def validate_args(argv):
+    """Validate the command-line arguments to ensure that they meet the
+    criteria issued in print_usage."""
+    if len(argv) > 3:
+        print_usage()
+        raise Exception("Invalid arguments. " + ",".join(argv))
+    for i in range(1, len(argv)):
+        if argv[i] != "release":
+            print_usage()
+            raise Exception("Invalid arguments. " + ",".join(argv))
+
+
+def print_usage():
+    """Print instructions on how to use this script, and in particular how to
+    set test or release mode."""
+    print(
+        (
+            "Usage: python3 release_build.py [release]\n"
+            + 'If the "release" argument is '
+            + "NOT specified then the script will execute all steps that checking and prompting "
+            + "steps but will NOT actually perform a release.  This is for testing the script."
+        )
+    )
+
+
+def main(argv):
+    """The release_push script is mainly responsible for copying the artifacts
+    (for the AFU and the Checker Framework) from the
+    development web site to Maven Central and to
+    the live site. It also performs link checking on the live site, pushes
+    the release to GitHub repositories, and guides the user to
+    perform manual steps such as sending the
+    release announcement e-mail."""
+    # MANUAL Indicates a manual step
+    # AUTO Indicates the step is fully automated.
+
+    set_umask()
+
+    validate_args(argv)
+    test_mode = not read_command_line_option(argv, "release")
+
+    m2_settings = expanduser("~") + "/.m2/settings.xml"
+    if not os.path.exists(m2_settings):
+        raise Exception("File does not exist: " + m2_settings)
+
+    if test_mode:
+        msg = (
+            "You have chosen test_mode.\n"
+            + "This means that this script will execute all build steps that "
+            + "do not have side effects.  That is, this is a test run of the script.  All checks and user prompts "
+            + "will be shown but no steps will be executed that will cause the release to be deployed or partially "
+            + "deployed.\n"
+            + 'If you meant to do an actual release, re-run this script with one argument, "release".'
+        )
+    else:
+        msg = "You have chosen release_mode.  Please follow the prompts to run a full Checker Framework release."
+
+    continue_or_exit(msg + "\n")
+    if test_mode:
+        print("Continuing in test mode.")
+    else:
+        print("Continuing in release mode.")
+
+    if not os.path.exists(RELEASE_BUILD_COMPLETED_FLAG_FILE):
+        continue_or_exit(
+            "It appears that release_build.py has not been run since the last push to "
+            + "the AFU or Checker Framework repositories.  Please ensure it has "
+            + "been run."
+        )
+
+    # The release script checks that the new release version is greater than the previous release version.
+
+    print_step("Push Step 1: Checking release versions")  # SEMIAUTO
+    dev_afu_website = os.path.join(DEV_SITE_URL, "annotation-file-utilities")
+    live_afu_website = os.path.join(LIVE_SITE_URL, "annotation-file-utilities")
+
+    dev_checker_website = DEV_SITE_URL
+    live_checker_website = LIVE_SITE_URL
+    current_cf_version = current_distribution_by_website(live_checker_website)
+    new_cf_version = CF_VERSION
+    check_release_version(current_cf_version, new_cf_version)
+
+    print(
+        "Checker Framework and AFU:  current-version=%s    new-version=%s"
+        % (current_cf_version, new_cf_version)
+    )
+
+    # Runs the link the checker on all websites at:
+    # https://checkerframework.org/dev/
+    # The output of the link checker is written to files in the /scratch/$USER/cf-release directory
+    # whose locations will be output at the command prompt if the link checker reported errors.
+
+    # In rare instances (such as when a link is correct but the link checker is
+    # unable to check it), you may add a suppression to the checklink-args.txt file.
+    # In extremely rare instances (such as when a website happens to be down at the
+    # time you ran the link checker), you may ignore an error.
+
+    print_step("Push Step 2: Check links on development site")  # SEMIAUTO
+
+    if prompt_yes_no("Run link checker on DEV site?", True):
+        check_all_links(
+            dev_afu_website, dev_checker_website, "dev", test_mode, new_cf_version
+        )
+
+    # Runs sanity tests on the development release. Later, we will run a smaller set of sanity
+    # tests on the live release to ensure no errors occurred when promoting the release.
+
+    print_step("Push Step 3: Run development sanity tests")  # SEMIAUTO
+    if prompt_yes_no("Perform this step?", True):
+
+        print_step("3a: Run javac sanity test on development release.")
+        if prompt_yes_no("Run javac sanity test on development release?", True):
+            javac_sanity_check(dev_checker_website, new_cf_version)
+
+        print_step("3b: Run Maven sanity test on development release.")
+        if prompt_yes_no("Run Maven sanity test on development repo?", True):
+            maven_sanity_check("maven-dev", "", new_cf_version)
+
+    # The Central repository is a repository of build artifacts for build programs like Maven and Ivy.
+    # This step stages (but doesn't release) the Checker Framework's Maven artifacts in the Sonatypes
+    # Central Repository.
+
+    # Once staging is complete, there are manual steps to log into Sonatypes Central and "close" the
+    # staging repository. Closing allows us to test the artifacts.
+
+    # This step deploys the artifacts to the Central repository and prompts the user to close the
+    # artifacts. Later, you will be prompted to release the staged artifacts after we push the
+    # release to our GitHub repositories.
+
+    # For more information on deploying to the Central Repository see:
+    # https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide
+
+    print_step("Push Step 4: Stage Maven artifacts in Central")  # SEMIAUTO
+
+    print_step("4a: Stage the artifacts at Maven central.")
+    if (not test_mode) or prompt_yes_no(
+        "Stage Maven artifacts in Maven Central?", not test_mode
+    ):
+        stage_maven_artifacts_in_maven_central(new_cf_version)
+
+        print_step("4b: Close staged artifacts at Maven central.")
+        continue_or_exit(
+            "Maven artifacts have been staged!  Please 'close' (but don't release) the artifacts.\n"
+            + " * Browse to https://oss.sonatype.org/#stagingRepositories\n"
+            + " * Log in using your Sonatype credentials\n"
+            + ' * In the search box at upper right, type "checker"\n'
+            + " * In the top pane, click on orgcheckerframework-XXXX\n"
+            + ' * Click "close" at the top\n'
+            + " * For the close message, enter:  Checker Framework release "
+            + new_cf_version
+            + "\n"
+            + " * Click the Refresh button near the top of the page until the bottom pane has:\n"
+            + '   "Activity   Last operation completed successfully".\n'
+            + " * Copy the URL of the closed artifacts (in the bottom pane) for use in the next step\n"
+            "(You can also see the instructions at: http://central.sonatype.org/pages/releasing-the-deployment.html)\n"
+        )
+
+        print_step("4c: Run Maven sanity test on Maven central artifacts.")
+        if prompt_yes_no("Run Maven sanity test on Maven central artifacts?", True):
+            repo_url = input("Please enter the repo URL of the closed artifacts:\n")
+
+            maven_sanity_check("maven-staging", repo_url, new_cf_version)
+
+    # This step copies the development release directories to the live release directories.
+    # It then adds the appropriate permissions to the release. Symlinks need to be updated to point
+    # to the live website rather than the development website. A straight copy of the directory
+    # will NOT update the symlinks.
+
+    print_step(
+        "Push Step 5. Copy dev current release website to live website"
+    )  # SEMIAUTO
+    if not test_mode:
+        if prompt_yes_no("Copy release to the live website?"):
+            print("Copying to live site")
+            copy_releases_to_live_site(new_cf_version)
+            copy_htaccess()
+            ensure_group_access_to_releases()
+    else:
+        print("Test mode: Skipping copy to live site!")
+
+    # This step downloads the checker-framework-X.Y.Z.zip file of the newly live release and ensures we
+    # can run the Nullness Checker. If this step fails, you should backout the release.
+
+    print_step("Push Step 6: Run javac sanity tests on the live release.")  # SEMIAUTO
+    if not test_mode:
+        if prompt_yes_no("Run javac sanity test on live release?", True):
+            javac_sanity_check(live_checker_website, new_cf_version)
+            SANITY_TEST_CHECKER_FRAMEWORK_DIR = SANITY_DIR + "/test-checker-framework"
+            if not os.path.isdir(SANITY_TEST_CHECKER_FRAMEWORK_DIR):
+                execute("mkdir -p " + SANITY_TEST_CHECKER_FRAMEWORK_DIR)
+            sanity_test_script = os.path.join(SCRIPTS_DIR, "test-checker-framework.sh")
+            execute(
+                "sh " + sanity_test_script + " " + new_cf_version,
+                True,
+                False,
+                SANITY_TEST_CHECKER_FRAMEWORK_DIR,
+            )
+    else:
+        print("Test mode: Skipping javac sanity tests on the live release.")
+
+    # Runs the link the checker on all websites at:
+    # https://checkerframework.org/
+    # The output of the link checker is written to files in the /scratch/$USER/cf-release directory whose locations
+    # will be output at the command prompt. Review the link checker output.
+
+    # The set of broken links that is displayed by this check will differ from those in push
+    # step 2 because the Checker Framework manual and website uses a mix of absolute and
+    # relative links. Therefore, some links from the development site actually point to the
+    # live site (the previous release). After step 5, these links point to the current
+    # release and may be broken.
+
+    print_step("Push Step 7. Check live site links")  # SEMIAUTO
+    if not test_mode:
+        if prompt_yes_no("Run link checker on LIVE site?", True):
+            check_all_links(live_afu_website, live_checker_website, "live", test_mode)
+    else:
+        print("Test mode: Skipping checking of live site links.")
+
+    # This step pushes the changes committed to the interm repositories to the GitHub
+    # repositories. This is the first irreversible change. After this point, you can no longer
+    # backout changes and should do another release in case of critical errors.
+
+    print_step("Push Step 8. Push changes to repositories")  # SEMIAUTO
+    # This step could be performed without asking for user input but I think we should err on the side of caution.
+    if not test_mode:
+        if prompt_yes_no(
+            "Push the release to GitHub repositories?  This is irreversible.", True
+        ):
+            push_interm_to_release_repos()
+            print("Pushed to repos")
+    else:
+        print("Test mode: Skipping push to GitHub!")
+
+    # This is a manual step that releases the staged Maven artifacts to the actual Central repository.
+    # This is also an irreversible step. Once you have released these artifacts they will be forever
+    # available to the Java community through the Central repository. Follow the prompts. The Maven
+    # artifacts (such as checker-qual.jar) are still needed, but the Maven plug-in is no longer maintained.
+
+    print_step("Push Step 9. Release staged artifacts in Central repository.")  # MANUAL
+    if test_mode:
+        msg = (
+            "Test Mode: You are in test_mode.  Please 'DROP' the artifacts. "
+            + "To drop, log into https://oss.sonatype.org using your "
+            + "Sonatype credentials and follow the 'DROP' instructions at: "
+            + "http://central.sonatype.org/pages/releasing-the-deployment.html"
+        )
+    else:
+        msg = (
+            "Please 'release' the artifacts.\n"
+            + "First log into https://oss.sonatype.org using your Sonatype credentials. Go to Staging Repositories and "
+            + "locate the orgcheckerframework repository and click on it.\n"
+            + "If you have a permissions problem, try logging out and back in.\n"
+            + "Finally, click on the Release button at the top of the page. In the dialog box that pops up, "
+            + 'leave the "Automatically drop" box checked. For the description, write '
+            + "Checker Framework release "
+            + new_cf_version
+            + "\n\n"
+        )
+
+    print(msg)
+    prompt_to_continue()
+
+    if test_mode:
+        print("Test complete")
+    else:
+        # A prompt describes the email you should send to all relevant mailing lists.
+        # Please fill out the email and announce the release.
+
+        print_step(
+            "Push Step 10. Post the Checker Framework and Annotation File Utilities releases on GitHub."
+        )  # MANUAL
+
+        msg = (
+            "\n"
+            + "* Download the following files to your local machine."
+            + "\n"
+            + "https://checkerframework.org/checker-framework-"
+            + new_cf_version
+            + ".zip\n"
+            + "https://checkerframework.org/annotation-file-utilities/annotation-tools-"
+            + new_cf_version
+            + ".zip\n"
+            + "\n"
+            + "To post the Checker Framework release on GitHub:\n"
+            + "\n"
+            + "* Go to https://github.com/typetools/checker-framework/releases/new?tag=checker-framework-"
+            + new_cf_version
+            + "\n"
+            + "* For the release title, enter: Checker Framework "
+            + new_cf_version
+            + "\n"
+            + "* For the description, insert the latest Checker Framework changelog entry (available at https://checkerframework.org/CHANGELOG.md). Please include the first line with the release version and date.\n"
+            + '* Find the link below "Attach binaries by dropping them here or selecting them." Click on "selecting them" and upload checker-framework-'
+            + new_cf_version
+            + ".zip from your machine.\n"
+            + '* Click on the green "Publish release" button.\n'
+            + "\n"
+            + "To post the Annotation File Utilities release on GitHub:\n"
+            + "\n"
+            + "* Go to https://github.com/typetools/annotation-tools/releases/new?tag="
+            + new_cf_version
+            + "\n"
+            + "* For the release title, enter: Annotation File Utilities "
+            + new_cf_version
+            + "\n"
+            + "* For the description, insert the latest Annotation File Utilities changelog entry (available at https://checkerframework.org/annotation-file-utilities/changelog.html). Please include the first line with the release version and date. For bullet points, use the * Markdown character.\n"
+            + '* Find the link below "Attach binaries by dropping them here or selecting them." Click on "selecting them" and upload annotation-tools-'
+            + new_cf_version
+            + ".zip from your machine.\n"
+            + '* Click on the green "Publish release" button.\n'
+        )
+
+        print(msg)
+
+        print_step("Push Step 11. Announce the release.")  # MANUAL
+        continue_or_exit(
+            "Please announce the release using the email structure below.\n"
+            + get_announcement_email(new_cf_version)
+        )
+
+        print_step(
+            "Push Step 12. Update the Checker Framework Gradle plugin."
+        )  # MANUAL
+        continue_or_exit(
+            "Please update the Checker Framework Gradle plugin:\n"
+            + "https://github.com/kelloggm/checkerframework-gradle-plugin/blob/master/RELEASE.md#updating-the-checker-framework-version\n"
+        )
+
+        print_step("Push Step 13. Prep for next Checker Framework release.")  # MANUAL
+        continue_or_exit(
+            "Change the patch level (last number) of the Checker Framework version\nin build.gradle:  increment it and add -SNAPSHOT\n"
+        )
+
+    delete_if_exists(RELEASE_BUILD_COMPLETED_FLAG_FILE)
+
+    print("Done with release_push.py")
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))
diff --git a/docs/developer/release/release_utils.py b/docs/developer/release/release_utils.py
new file mode 100755
index 0000000..6765159
--- /dev/null
+++ b/docs/developer/release/release_utils.py
@@ -0,0 +1,574 @@
+#!/usr/bin/env python3
+# encoding: utf-8
+"""
+releaseutils.py
+
+Python Utils for releasing the Checker Framework
+This contains no main method only utility functions
+Created by Jonathan Burke 11/21/2012
+
+Copyright (c) 2012 University of Washington
+"""
+
+import urllib.request
+import urllib.error
+import urllib.parse
+import re
+import subprocess
+import os
+import os.path
+import shutil
+from release_vars import execute
+
+# =========================================================================================
+# Parse Args Utils # TODO: Perhaps use argparse module
+
+
+def read_command_line_option(argv, argument):
+    """Returns True if the given command line arguments contain the specified
+    argument, False otherwise."""
+    for index in range(1, len(argv)):
+        if argv[index] == argument:
+            return True
+    return False
+
+
+# =========================================================================================
+# Command utils
+
+
+def execute_write_to_file(
+    command_args, output_file_path, halt_if_fail=True, working_dir=None
+):
+    """Execute the given command, capturing the output to the given file."""
+    print("Executing: %s" % (command_args))
+    import shlex
+
+    args = shlex.split(command_args) if isinstance(command_args, str) else command_args
+
+    output_file = open(output_file_path, "w+")
+    process = subprocess.Popen(
+        args, stdout=output_file, stderr=output_file, cwd=working_dir
+    )
+    process.communicate()
+    process.wait()
+    output_file.close()
+
+    if process.returncode != 0 and halt_if_fail:
+        raise Exception(
+            "Error %s while executing %s" % (process.returncode, command_args)
+        )
+
+
+def check_command(command):
+    """Executes the UNIX \"which\" command to determine whether the given command
+    is installed and on the PATH."""
+    p = execute(["which", command], False)
+    if p:
+        raise AssertionError("command not found: %s" % command)
+    print("")
+
+
+def prompt_yes_no(msg, default=False):
+    """Prints the given message and continually prompts the user until they
+    answer yes or no. Returns true if the answer was yes, false otherwise."""
+    default_str = "no"
+    if default:
+        default_str = "yes"
+
+    result = prompt_w_default(msg, default_str, "^(Yes|yes|No|no)$")
+
+    if result == "yes" or result == "Yes":
+        return True
+    return False
+
+
+def prompt_yn(msg):
+    """Prints the given message and continually prompts the user until they
+    answer y or n. Returns true if the answer was y, false otherwise."""
+    y_or_n = "z"
+    while y_or_n != "y" and y_or_n != "n":
+        print(msg + " [y|n]")
+        y_or_n = input().lower()
+
+    return y_or_n == "y"
+
+
+def prompt_to_continue():
+    "Prompts the user to continue, until they enter yes."
+    while not prompt_yes_no("Continue?"):
+        pass
+
+
+def prompt_w_default(msg, default, valid_regex=None):
+    """Only accepts answers that match valid_regex.
+    If default is None, requires an answer."""
+    answer = None
+    while answer is None:
+        answer = input(msg + " (%s): " % default)
+
+        if answer is None or answer == "":
+            answer = default
+        else:
+            answer = answer.strip()
+
+            if valid_regex is not None:
+                m = re.match(valid_regex, answer)
+                if m is None:
+                    answer = None
+                    print("Invalid answer.  Validating regex: " + valid_regex)
+            else:
+                answer = default
+
+    return answer
+
+
+def check_tools(tools):
+    """Given an array specifying a set of tools, verify that the tools are
+    installed and on the PATH."""
+    print("\nChecking to make sure the following programs are installed:")
+    print(", ".join(tools))
+    print(
+        (
+            "Note: If you are NOT working on buffalo.cs.washington.edu then you "
+            + "likely need to change the variables that are set in release.py\n"
+            + 'Search for "Set environment variables".'
+        )
+    )
+    list(map(check_command, tools))
+    print("")
+
+
+def continue_or_exit(msg):
+    "Prompts the user whether to continue executing the script."
+    continue_script = prompt_w_default(
+        msg + " Continue ('no' will exit the script)?", "yes", "^(Yes|yes|No|no)$"
+    )
+    if continue_script == "no" or continue_script == "No":
+        raise Exception("User elected NOT to continue at prompt: " + msg)
+
+
+# =========================================================================================
+# Version Utils
+
+# From http://stackoverflow.com/a/1714190/173852, but doesn't strip trailing zeroes
+def version_number_to_array(version_num):
+    """Given a version number, return an array of the elements, as integers."""
+    return [int(x) for x in version_num.split(".")]
+
+
+def increment_version(version_num):
+    """
+    Returns the next incremental version after the argument.
+    """
+    # Drop the fourth and subsequent parts if present
+    version_array = version_number_to_array(version_num)[:3]
+    version_array[-1] = version_array[-1] + 1
+    return ".".join(str(x) for x in version_array)
+
+
+def test_increment_version():
+    """Run test cases to ensure that increment_version works correctly."""
+    assert increment_version("1.0.3") == "1.0.4"
+    assert increment_version("1.0.9") == "1.0.10"
+    assert increment_version("1.1.9") == "1.1.10"
+    assert increment_version("1.3.0") == "1.3.1"
+    assert increment_version("1.3.1") == "1.3.2"
+    assert increment_version("1.9.9") == "1.9.10"
+    assert increment_version("3.6.22") == "3.6.23"
+    assert increment_version("3.22.6") == "3.22.7"
+    assert increment_version("1.0.3.1") == "1.0.4"
+    assert increment_version("1.0.9.1") == "1.0.10"
+    assert increment_version("1.1.9.1") == "1.1.10"
+    assert increment_version("1.3.0.1") == "1.3.1"
+    assert increment_version("1.3.1.1") == "1.3.2"
+    assert increment_version("1.9.9.1") == "1.9.10"
+    assert increment_version("3.6.22.1") == "3.6.23"
+    assert increment_version("3.22.6.1") == "3.22.7"
+
+
+def current_distribution_by_website(site):
+    """
+    Reads the checker framework version from the checker framework website and
+    returns the version of the current release.
+    """
+    print("Looking up checker-framework-version from %s\n" % site)
+    ver_re = re.compile(
+        r"<!-- checker-framework-zip-version -->checker-framework-(.*)\.zip"
+    )
+    text = urllib.request.urlopen(url=site).read().decode("utf-8")
+    result = ver_re.search(text)
+    return result.group(1)
+
+
+# =========================================================================================
+# Git Utils
+
+
+def git_bare_repo_exists_at_path(
+    repo_root,
+):  # Bare git repos have no .git directory but they have a refs directory
+    "Returns whether a bare git repository exists at the given filesystem path."
+    if os.path.isdir(repo_root + "/refs"):
+        return True
+    return False
+
+
+def git_repo_exists_at_path(repo_root):
+    """Returns whether a (bare or non-bare) git repository exists at the given
+    filesystem path."""
+    return os.path.isdir(repo_root + "/.git") or git_bare_repo_exists_at_path(repo_root)
+
+
+def push_changes_prompt_if_fail(repo_root):
+    """Attempt to push changes, including tags, that were committed to the
+    repository at the given filesystem path. In case of failure, ask the user
+    if they would like to try again. Loop until pushing changes succeeds or the
+    user answers opts to not try again."""
+    while True:
+        cmd = "(cd %s && git push --tags)" % repo_root
+        result = os.system(cmd)
+        cmd = "(cd %s && git push origin master)" % repo_root
+        result = os.system(cmd)
+        if result == 0:
+            break
+        else:
+            print(
+                "Could not push from: "
+                + repo_root
+                + "; result="
+                + str(result)
+                + " for command: "
+                + cmd
+                + "` in "
+                + os.getcwd()
+            )
+            if not prompt_yn(
+                "Try again (responding 'n' will skip this push command but will not exit the script) ?"
+            ):
+                break
+
+
+def push_changes(repo_root):
+    """Pushes changes, including tags, that were committed to the repository at
+    the given filesystem path."""
+    execute("git push --tags", working_dir=repo_root)
+    execute("git push origin master", working_dir=repo_root)
+
+
+def update_repo(path, bareflag):
+    """Pull the latest changes to the given repo and update. The bareflag
+    parameter indicates whether the updated repo must be a bare git repo."""
+    if bareflag:
+        execute("git fetch origin master:master", working_dir=path)
+    else:
+        execute("git pull", working_dir=path)
+
+
+def commit_tag_and_push(version, path, tag_prefix):
+    """Commit the changes made for this release, add a tag for this release, and
+    push these changes."""
+    execute('git commit -a -m "new release %s"' % (version), working_dir=path)
+    execute("git tag %s%s" % (tag_prefix, version), working_dir=path)
+    push_changes(path)
+
+
+def clone_from_scratch_or_update(src_repo, dst_repo, clone_from_scratch, bareflag):
+    """If the clone_from_scratch flag is True, clone the given git or
+    Mercurial repo from scratch into the filesystem path specified by dst_repo,
+    deleting it first if the repo is present on the filesystem.
+    Otherwise, if a repo exists at the filesystem path given by dst_repo, pull
+    the latest changes to it and update it. If the repo does not exist, clone it
+    from scratch. The bareflag parameter indicates whether the cloned/updated
+    repo must be a bare git repo."""
+    if clone_from_scratch:
+        delete_and_clone(src_repo, dst_repo, bareflag)
+    else:
+        if os.path.exists(dst_repo):
+            update_repo(dst_repo, bareflag)
+        else:
+            clone(src_repo, dst_repo, bareflag)
+
+
+def delete_and_clone(src_repo, dst_repo, bareflag):
+    """Clone the given git or Mercurial repo from scratch into the filesystem
+    path specified by dst_repo. If a repo exists at the filesystem path given
+    by dst_repo, delete it first. The bareflag parameter indicates whether
+    the cloned repo must be a bare git repo."""
+    delete_path_if_exists(dst_repo)
+    clone(src_repo, dst_repo, bareflag)
+
+
+def clone(src_repo, dst_repo, bareflag):
+    """Clone the given git or Mercurial repo from scratch into the filesystem
+    path specified by dst_repo. The bareflag parameter indicates whether the
+    cloned repo must be a bare git repo."""
+    flags = ""
+    if bareflag:
+        flags = "--bare"
+    execute("git clone --quiet %s %s %s" % (flags, src_repo, dst_repo))
+
+
+def is_repo_cleaned_and_updated(repo):
+    """IMPORTANT: this function is not known to be fully reliable in ensuring
+    that a repo is fully clean of all changes, such as committed tags. To be
+    certain of success throughout the release_build and release_push process,
+    the best option is to clone repositories from scratch.
+    Returns whether the repository at the given filesystem path is clean (i.e.
+    there are no committed changes and no untracked files in the working tree)
+    and up-to-date with respect to the repository it was cloned from."""
+    # The idiom "not execute(..., capture_output=True)" evaluates to True when the captured output is empty.
+    if git_bare_repo_exists_at_path(repo):
+        execute("git fetch origin", working_dir=repo)
+        is_updated = not execute(
+            "git diff master..FETCH_HEAD", working_dir=repo, capture_output=True
+        )
+        return is_updated
+    else:
+        # Could add "--untracked-files=no" to this command
+        is_clean = not execute(
+            "git status --porcelain", working_dir=repo, capture_output=True
+        )
+        execute("git fetch origin", working_dir=repo)
+        is_updated = not execute(
+            "git diff origin/master..master", working_dir=repo, capture_output=True
+        )
+        return is_clean and is_updated
+
+
+def check_repos(repos, fail_on_error, is_intermediate_repo_list):
+    """Fail if the repository is not clean and up to date."""
+    for repo in repos:
+        if git_repo_exists_at_path(repo):
+            if not is_repo_cleaned_and_updated(repo):
+                if is_intermediate_repo_list:
+                    print(
+                        (
+                            "\nWARNING: Intermediate repository "
+                            + repo
+                            + " is not up to date with respect to the live repository.\n"
+                            + "A separate warning will not be issued for a build repository that is cloned off of the intermediate repository."
+                        )
+                    )
+                if fail_on_error:
+                    raise Exception("repo %s is not cleaned and updated!" % repo)
+                else:
+                    if not prompt_yn(
+                        "%s is not clean and up to date! Continue (answering 'n' will exit the script)?"
+                        % repo
+                    ):
+                        raise Exception(
+                            "%s is not clean and up to date! Halting!" % repo
+                        )
+
+
+def get_tag_line(lines, revision, tag_prefixes):
+    """Get the revision hash for the tag matching the given project revision in
+    the given lines containing revision hashes. Uses the given array of tag
+    prefix strings if provided. For example, given an array of tag prefixes
+    [\"checker-framework-\", \"checkers-\"] and project revision \"2.0.0\", the
+    tags named \"checker-framework-2.0.0\" and \"checkers-2.0.0\" are sought."""
+    for line in lines:
+        for prefix in tag_prefixes:
+            full_tag = prefix + revision
+            if line.startswith(full_tag):
+                return line
+    return None
+
+
+def get_commit_for_tag(revision, repo_file_path, tag_prefixes):
+    """Get the commit hash for the tag matching the given project revision of
+    the Git repository at the given filesystem path. Uses the given array of
+    tag prefix strings if provided. For example, given an array of tag prefixes
+    [\"checker-framework-\", \"checkers-\"] and project revision \"2.0.0\", the
+    tags named \"checker-framework-2.0.0\" and \"checkers-2.0.0\" are sought."""
+
+    # assume the first is the most recent
+    tags = execute(
+        "git rev-list " + tag_prefixes[0] + revision,
+        True,
+        True,
+        working_dir=repo_file_path,
+    )
+    lines = tags.splitlines()
+
+    commit = lines[0]
+    if commit is None:
+        msg = "Could not find revision %s in repo %s using tags %s " % (
+            revision,
+            repo_file_path,
+            ",".join(tag_prefixes),
+        )
+        raise Exception(msg)
+
+    return commit
+
+
+# =========================================================================================
+# File Utils
+
+
+def wget_file(source_url, destination_dir):
+    """Download a file from the source URL to the given destination directory.
+    Useful since download_binary does not seem to work on source files."""
+    print("DEST DIR: " + destination_dir)
+    execute("wget %s" % source_url, True, False, destination_dir)
+
+
+def download_binary(source_url, destination):
+    """Download a file from the given URL and save its contents to the
+    destination filename."""
+    http_response = urllib.request.urlopen(url=source_url)
+    content_length = http_response.headers["content-length"]
+
+    if content_length is None:
+        raise Exception("No content-length when downloading: " + source_url)
+
+    dest_file = open(destination, "wb")
+    dest_file.write(http_response.read())
+    dest_file.close()
+
+
+def read_first_line(file_path):
+    "Return the first line in the given file. Assumes the file exists."
+    infile = open(file_path, "r")
+    first_line = infile.readline()
+    infile.close()
+    return first_line
+
+
+def ensure_group_access(path):
+    "Give group access to all files and directories under the specified path"
+    # Errs for any file not owned by this user.
+    # But, the point is to set group writeability of any *new* files.
+    execute("chmod -f -R g+rw %s" % path, halt_if_fail=False)
+
+
+def ensure_user_access(path):
+    "Give the user access to all files and directories under the specified path"
+    execute("chmod -f -R u+rwx %s" % path, halt_if_fail=True)
+
+
+def set_umask():
+    'Equivalent to executing "umask g+rw" from the command line.'
+    os.umask(os.umask(0) & 0b001111)
+
+
+def delete(file_to_delete):
+    "Delete the specified file."
+    os.remove(file_to_delete)
+
+
+def delete_if_exists(file_to_delete):
+    "Check if the specified file exists, and if so, delete it."
+    if os.path.exists(file_to_delete):
+        delete(file_to_delete)
+
+
+def delete_path(path):
+    "Delete all files and directories under the specified path."
+    ensure_group_access(path)
+    shutil.rmtree(path)
+
+
+def delete_path_if_exists(path):
+    """Check if the specified path exists, and if so, delete all files and
+    directories under it."""
+    if os.path.exists(path):
+        delete_path(path)
+
+
+def are_in_file(file_path, strs_to_find):
+    """Returns true if every string in the given strs_to_find array is found in
+    at least one line in the given file. In particular, returns true if
+    strs_to_find is empty. Note that the strs_to_find parameter is mutated."""
+    infile = open(file_path)
+
+    for line in infile:
+        if len(strs_to_find) == 0:
+            return True
+
+        index = 0
+        while index < len(strs_to_find):
+            if strs_to_find[index] in line:
+                del strs_to_find[index]
+            else:
+                index = index + 1
+
+    return len(strs_to_find) == 0
+
+
+def insert_before_line(to_insert, file_path, line):
+    """Insert the given line to the given file before the given 0-indexed line
+    number."""
+    mid_line = line - 1
+
+    with open(file_path) as infile:
+        content = infile.readlines()
+
+    output = open(file_path, "w")
+    for i in range(0, mid_line):
+        output.write(content[i])
+
+    output.write(to_insert)
+
+    for i in range(mid_line, len(content)):
+        output.write(content[i])
+
+    output.close()
+
+
+def create_empty_file(file_path):
+    "Creates an empty file with the given filename."
+    dest_file = open(file_path, "wb")
+    dest_file.close()
+
+
+# =========================================================================================
+# Misc. Utils
+
+
+def print_step(step):
+    "Print a step in the release_build or release_push script."
+    print("\n")
+    print(step)
+
+    dashStr = ""
+    for dummy in range(0, len(step)):
+        dashStr += "-"
+    print(dashStr)
+
+
+def get_announcement_email(version):
+    """Return the template for the e-mail announcing a new release of the
+    Checker Framework."""
+    return """
+To:  checker-framework-discuss@googlegroups.com
+Subject: Release %s of the Checker Framework
+
+We have released a new version of the Checker Framework.
+The Checker Framework lets you create and/or run pluggable type checkers, in order to detect and prevent bugs in your code.
+
+You can find documentation and download links at:
+http://CheckerFramework.org/
+
+Changes for Checker Framework version %s:
+
+<<Insert latest Checker Framework changelog entry, omitting the first line with the release version and date, and with hard line breaks removed>>
+""" % (
+        version,
+        version,
+    )
+
+
+# =========================================================================================
+# Testing
+
+
+def test_release_utils():
+    "Test that critical methods in this file work as expected."
+    test_increment_version()
+
+
+# Tests run every time this file is loaded
+test_release_utils()
diff --git a/docs/developer/release/release_vars.py b/docs/developer/release/release_vars.py
new file mode 100755
index 0000000..908773c
--- /dev/null
+++ b/docs/developer/release/release_vars.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+# encoding: utf-8
+"""
+release_vars.py
+
+Created by Jonathan Burke on 2013-02-05.
+
+Copyright (c) 2014 University of Washington. All rights reserved.
+"""
+
+# See release_development.html for an explanation of how the release process works
+# it will be invaluable when trying to understand the scripts that drive the
+# release process
+
+import os
+import pwd
+import subprocess
+import shlex
+
+
+# ---------------------------------------------------------------------------------
+# The only methods that should go here are methods that help define global release
+# variables.  All other methods that aid in release should go in release_utils.py
+
+
+def getAndAppend(name, append):
+    """Retrieves the given environment variable and appends the given string to
+    its value and returns the new value. The environment variable is not
+    modified. Returns an empty string if the environment variable does not
+    exist."""
+    if name in os.environ:
+        return os.environ[name] + append
+
+    else:
+        return ""
+
+
+def execute(command_args, halt_if_fail=True, capture_output=False, working_dir=None):
+    """Execute the given command.
+    If capture_output is true, then return the output (and ignore the halt_if_fail argument).
+    If capture_output is not true, return the return code of the subprocess call."""
+
+    if working_dir is not None:
+        print("Executing in %s: %s" % (working_dir, command_args))
+    else:
+        print("Executing: %s" % (command_args))
+    args = shlex.split(command_args) if isinstance(command_args, str) else command_args
+
+    if capture_output:
+        process = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=working_dir)
+        out = process.communicate()[0]
+        process.wait()
+        return out
+
+    else:
+        result = subprocess.call(args, cwd=working_dir)
+        if halt_if_fail and result:
+            raise Exception("Error %s while executing %s" % (result, args))
+        return result
+
+
+# ---------------------------------------------------------------------------------
+
+# The location the test site is built in
+DEV_SITE_URL = "https://checkerframework.org/dev"
+DEV_SITE_DIR = "/cse/www2/types/checker-framework/dev"
+
+# The location the test site is pushed to when it is ready
+LIVE_SITE_URL = "https://checkerframework.org"
+LIVE_SITE_DIR = "/cse/www2/types/checker-framework"
+
+# Per-user directory for the temporary files created by the release process
+# ("USER = os.getlogin()" does not work; see http://bugs.python.org/issue584566.
+# Another alternative is: USER = os.getenv('USER').)
+TMP_DIR = "/scratch/" + pwd.getpwuid(os.geteuid())[0] + "/cf-release"
+
+# Location this and other release scripts are contained in
+SCRIPTS_DIR = TMP_DIR + "/checker-framework/docs/developer/release"
+
+# Location in which we will download files to run sanity checks
+SANITY_DIR = TMP_DIR + "/sanity"
+
+# The existence of this file indicates that release_build completed.
+# It is deleted at the beginning of a release_build run, and at the
+# end of a release_push run.
+RELEASE_BUILD_COMPLETED_FLAG_FILE = TMP_DIR + "/release-build-completed"
+
+# Every time a release is built the changes/tags are pushed here
+INTERM_REPO_ROOT = TMP_DIR + "/interm"
+INTERM_CHECKER_REPO = os.path.join(INTERM_REPO_ROOT, "checker-framework")
+INTERM_ANNO_REPO = os.path.join(INTERM_REPO_ROOT, "annotation-tools")
+
+# The central repositories for Checker Framework related projects
+LIVE_ANNO_REPO = "git@github.com:typetools/annotation-tools.git"
+LIVE_CHECKER_REPO = "git@github.com:typetools/checker-framework.git"
+PLUME_SCRIPTS_REPO = "https://github.com/plume-lib/plume-scripts"
+CHECKLINK_REPO = "https://github.com/plume-lib/checklink"
+PLUME_BIB_REPO = "https://github.com/mernst/plume-bib"
+STUBPARSER_REPO = "https://github.com/typetools/stubparser"
+
+# Location of the project directories in which we will build the actual projects.
+# When we build these projects are pushed to the INTERM repositories.
+BUILD_DIR = TMP_DIR + "/build/"
+CHECKER_FRAMEWORK = os.path.join(BUILD_DIR, "checker-framework")
+CHECKER_FRAMEWORK_RELEASE = os.path.join(CHECKER_FRAMEWORK, "docs/developer/release")
+
+# If a new Gradle wrapper was recently installed, the first ./gradlew command may output:
+#   Downloading https://services.gradle.org/distributions/gradle-6.6.1-bin.zip
+# This first call might output Gradle diagnostics, such as "downloading".
+execute("./gradlew version -q", True, True, TMP_DIR + "/checker-framework")
+CF_VERSION = (
+    execute("./gradlew version -q", True, True, TMP_DIR + "/checker-framework")
+    .strip()
+    .decode("utf-8")
+)
+
+ANNO_TOOLS = os.path.join(BUILD_DIR, "annotation-tools")
+ANNO_FILE_UTILITIES = os.path.join(ANNO_TOOLS, "annotation-file-utilities")
+
+PLUME_SCRIPTS = os.path.join(BUILD_DIR, "plume-scripts")
+CHECKLINK = os.path.join(BUILD_DIR, "checklink")
+PLUME_BIB = os.path.join(BUILD_DIR, "plume-bib")
+STUBPARSER = os.path.join(BUILD_DIR, "stubparser")
+
+BUILD_REPOS = (CHECKER_FRAMEWORK, ANNO_TOOLS)
+INTERM_REPOS = (INTERM_CHECKER_REPO, INTERM_ANNO_REPO)
+
+INTERM_TO_BUILD_REPOS = (
+    (INTERM_CHECKER_REPO, CHECKER_FRAMEWORK),
+    (INTERM_ANNO_REPO, ANNO_TOOLS),
+)
+
+LIVE_TO_INTERM_REPOS = (
+    (LIVE_CHECKER_REPO, INTERM_CHECKER_REPO),
+    (LIVE_ANNO_REPO, INTERM_ANNO_REPO),
+)
+
+AFU_LIVE_SITE = os.path.join(LIVE_SITE_DIR, "annotation-file-utilities")
+AFU_LIVE_RELEASES_DIR = os.path.join(AFU_LIVE_SITE, "releases")
+
+CHECKER_LIVE_RELEASES_DIR = os.path.join(LIVE_SITE_DIR, "releases")
+
+os.environ["PARENT_DIR"] = BUILD_DIR
+os.environ["CHECKERFRAMEWORK"] = CHECKER_FRAMEWORK
+perl_libs = TMP_DIR + "/homes/gws/mernst/bin/src/perl:/usr/share/perl5/"
+# Environment variables for tools needed during the build
+os.environ["PLUME_SCRIPTS"] = PLUME_SCRIPTS
+os.environ["CHECKLINK"] = CHECKLINK
+os.environ["BIBINPUTS"] = ".:" + PLUME_BIB
+os.environ["TEXINPUTS"] = ".:/homes/gws/mernst/tex/sty:/homes/gws/mernst/tex:..:"
+os.environ["PERLLIB"] = getAndAppend("PERLLIB", ":") + perl_libs
+os.environ["PERL5LIB"] = getAndAppend("PERL5LIB", ":") + perl_libs
+# Still needed for santiy checks
+os.environ["JAVA_8_HOME"] = "/usr/lib/jvm/java-1.8.0-openjdk/"
+os.environ["JAVA_HOME"] = os.environ["JAVA_8_HOME"]
+
+EDITOR = os.getenv("EDITOR")
+if EDITOR is None:
+    EDITOR = "emacs"
+
+PATH = os.environ["JAVA_HOME"] + "/bin:" + os.environ["PATH"]
+PATH = PATH + ":/usr/bin"
+PATH = PATH + ":" + PLUME_SCRIPTS
+PATH = PATH + ":" + CHECKLINK
+PATH = PATH + ":/homes/gws/mernst/.local/bin"  # for html5validator
+PATH = PATH + ":."
+os.environ["PATH"] = PATH
+
+# Tools that must be on your PATH (besides common Unix ones like grep)
+TOOLS = ["hevea", "perl", "java", "latex", "mvn", "hg", "git", "html5validator", EDITOR]
diff --git a/docs/developer/release/sanity_checks.py b/docs/developer/release/sanity_checks.py
new file mode 100755
index 0000000..535694e
--- /dev/null
+++ b/docs/developer/release/sanity_checks.py
@@ -0,0 +1,216 @@
+#!/usr/bin/env python3
+# encoding: utf-8
+"""
+releaseutils.py
+
+This contains no main method only utility functions to
+run sanity checks on the Checker Framework.
+Created by Jonathan Burke 11/21/2012
+
+Copyright (c) 2012 University of Washington
+"""
+
+import zipfile
+
+from release_vars import CHECKER_FRAMEWORK
+from release_vars import CHECKER_FRAMEWORK_RELEASE
+from release_vars import SANITY_DIR
+
+from release_vars import execute
+
+from release_utils import are_in_file
+from release_utils import delete
+from release_utils import delete_path
+from release_utils import download_binary
+from release_utils import ensure_user_access
+from release_utils import execute_write_to_file
+from release_utils import insert_before_line
+from release_utils import os
+from release_utils import wget_file
+
+
+def javac_sanity_check(checker_framework_website, release_version):
+    """
+    Download the release of the Checker Framework from the development website
+    and NullnessExampleWithWarnings.java from the GitHub repository.
+    Run the Nullness Checker on NullnessExampleWithWarnings and verify the output
+    Fails if the expected errors are not found in the output.
+    """
+
+    new_checkers_release_zip = os.path.join(
+        checker_framework_website,
+        "releases",
+        release_version,
+        "checker-framework-" + release_version + ".zip",
+    )
+
+    javac_sanity_dir = os.path.join(SANITY_DIR, "javac")
+
+    if os.path.isdir(javac_sanity_dir):
+        delete_path(javac_sanity_dir)
+    execute("mkdir -p " + javac_sanity_dir)
+
+    javac_sanity_zip = os.path.join(
+        javac_sanity_dir, "checker-framework-%s.zip" % release_version
+    )
+
+    print(
+        "Attempting to download %s to %s" % (new_checkers_release_zip, javac_sanity_zip)
+    )
+    download_binary(new_checkers_release_zip, javac_sanity_zip)
+
+    nullness_example_url = "https://raw.githubusercontent.com/typetools/checker-framework/master/docs/examples/NullnessExampleWithWarnings.java"
+    nullness_example = os.path.join(
+        javac_sanity_dir, "NullnessExampleWithWarnings.java"
+    )
+
+    if os.path.isfile(nullness_example):
+        delete(nullness_example)
+
+    wget_file(nullness_example_url, javac_sanity_dir)
+
+    deploy_dir = os.path.join(javac_sanity_dir, "checker-framework-" + release_version)
+
+    if os.path.exists(deploy_dir):
+        print("Deleting existing path: " + deploy_dir)
+        delete_path(deploy_dir)
+
+    with zipfile.ZipFile(javac_sanity_zip, "r") as z:
+        z.extractall(javac_sanity_dir)
+
+    ensure_user_access(deploy_dir)
+
+    sanity_javac = os.path.join(deploy_dir, "checker", "bin", "javac")
+    nullness_output = os.path.join(deploy_dir, "output.log")
+
+    cmd = (
+        sanity_javac
+        + " -processor org.checkerframework.checker.nullness.NullnessChecker "
+        + nullness_example
+        + " -Anomsgtext"
+    )
+    execute_write_to_file(cmd, nullness_output, False)
+    check_results(
+        "Javac sanity check",
+        nullness_output,
+        [
+            "NullnessExampleWithWarnings.java:23: error: (assignment)",
+            "NullnessExampleWithWarnings.java:33: error: (argument)",
+        ],
+    )
+
+    # this is a smoke test for the built-in checker shorthand feature
+    # https://checkerframework.org/manual/#shorthand-for-checkers
+    nullness_shorthand_output = os.path.join(deploy_dir, "output_shorthand.log")
+    cmd = (
+        sanity_javac
+        + " -processor NullnessChecker "
+        + nullness_example
+        + " -Anomsgtext"
+    )
+    execute_write_to_file(cmd, nullness_shorthand_output, False)
+    check_results(
+        "Javac Shorthand Sanity Check",
+        nullness_shorthand_output,
+        [
+            "NullnessExampleWithWarnings.java:23: error: (assignment)",
+            "NullnessExampleWithWarnings.java:33: error: (argument)",
+        ],
+    )
+
+
+def maven_sanity_check(sub_sanity_dir_name, repo_url, release_version):
+    """
+    Run the Maven sanity check with the local artifacts or from the repo at
+    repo_url.
+    """
+    checker_dir = os.path.join(CHECKER_FRAMEWORK, "checker")
+    maven_sanity_dir = os.path.join(SANITY_DIR, sub_sanity_dir_name)
+    if os.path.isdir(maven_sanity_dir):
+        delete_path(maven_sanity_dir)
+
+    execute("mkdir -p " + maven_sanity_dir)
+
+    maven_example_dir = os.path.join(maven_sanity_dir, "MavenExample")
+    output_log = os.path.join(maven_example_dir, "output.log")
+
+    ant_release_script = os.path.join(CHECKER_FRAMEWORK_RELEASE, "release.xml")
+    get_example_dir_cmd = (
+        "ant -f %s update-and-copy-maven-example -Dchecker=%s -Dversion=%s -Ddest.dir=%s"
+        % (ant_release_script, checker_dir, release_version, maven_sanity_dir)
+    )
+
+    execute(get_example_dir_cmd)
+    path_to_artifacts = os.path.join(
+        os.path.expanduser("~"), ".m2", "repository", "org", "checkerframework"
+    )
+    if repo_url != "":
+        print(
+            (
+                "This script will now delete your Maven Checker Framework artifacts.\n"
+                + "See README-release-process.html#Maven-Plugin dependencies.  These artifacts "
+                + "will need to be re-downloaded the next time you need them.  This will be "
+                + "done automatically by Maven next time you use the plugin."
+            )
+        )
+
+        if os.path.isdir(path_to_artifacts):
+            delete_path(path_to_artifacts)
+        maven_example_pom = os.path.join(maven_example_dir, "pom.xml")
+        add_repo_information(maven_example_pom, repo_url)
+
+    os.environ["JAVA_HOME"] = os.environ["JAVA_8_HOME"]
+    execute_write_to_file("mvn compile", output_log, False, maven_example_dir)
+    if repo_url != "":
+        delete_path(path_to_artifacts)
+
+
+def check_results(title, output_log, expected_errors):
+    """Verify the given actual output of a sanity check against the given
+    expected output. If the sanity check passed, print the given title of the
+    sanity check and a success message. If the sanity check failed, raise an
+    exception whose text contains the given title of the sanity check and the
+    actual and expected output."""
+    found_errors = are_in_file(output_log, expected_errors)
+
+    if not found_errors:
+        raise Exception(
+            title
+            + " did not work!\n"
+            + "File: "
+            + output_log
+            + "\n"
+            + "should contain the following errors: [ "
+            + ", ".join(expected_errors)
+        )
+    else:
+        print("%s check: passed!\n" % title)
+
+
+def add_repo_information(pom, repo_url):
+    """Adds development maven repo to pom file so that the artifacts used are
+    the development artifacts"""
+    to_insert = """
+        <repositories>
+              <repository>
+                  <id>checker-framework-repo</id>
+                  <url>%s</url>
+              </repository>
+        </repositories>
+
+        <pluginRepositories>
+              <pluginRepository>
+                    <id>checker-framework-repo</id>
+                    <url>%s</url>
+              </pluginRepository>
+        </pluginRepositories>
+        """ % (
+        repo_url,
+        repo_url,
+    )
+
+    result_str = execute('grep -nm 1 "<build>" %s' % pom, True, True).decode()
+    line_no_str = result_str.split(":")[0]
+    line_no = int(line_no_str)
+    print(" LINE_NO: " + line_no_str)
+    insert_before_line(to_insert, pom, line_no)
diff --git a/docs/developer/release/site-copy-excludes b/docs/developer/release/site-copy-excludes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docs/developer/release/site-copy-excludes
diff --git a/docs/developer/release/site-copy-includes b/docs/developer/release/site-copy-includes
new file mode 100644
index 0000000..1da10c5
--- /dev/null
+++ b/docs/developer/release/site-copy-includes
@@ -0,0 +1,2 @@
+annotation-file-utilities/**
+jsr308/specification/java-annotation-design-*.html
diff --git a/docs/developer/release/test-checker-framework.sh b/docs/developer/release/test-checker-framework.sh
new file mode 100755
index 0000000..8fc77f8
--- /dev/null
+++ b/docs/developer/release/test-checker-framework.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+
+# This script tests that the Checker Framework release can be downloaded
+# and that a simple sanity test works.
+# It assumes that environment variable JAVA8_HOME is defined.
+# It takes an argument specifying the current Checker Framework version, e.g. "1.9.11".
+
+# This script is used by the release_push script in the "Run javac sanity tests on the live release" step
+
+set -x
+
+if [ $# -eq 0 ]; then
+    echo "Usage: test-checker-framework.sh <current version of Checker Framework on live web site>"
+    exit 6
+fi
+
+rm -f "checker-framework-$1.zip"
+rm -rf "checker-framework-$1/"
+
+wget "https://checkerframework.org/checker-framework-$1.zip"
+unzip -q "checker-framework-$1.zip"
+
+export CHECKERFRAMEWORK=checker-framework-$1
+export ORIG_PATH=$PATH
+
+
+function cfruntest() {
+  # shellcheck disable=SC2230
+  which java
+  command -v java
+  java -version
+
+  chmod +x "$CHECKERFRAMEWORK"/checker/bin/javac
+  if ! "$CHECKERFRAMEWORK"/checker/bin/javac -version ; then
+    exit 6
+  fi
+
+  if ! java -jar "$CHECKERFRAMEWORK/checker/dist/checker.jar" -version ; then
+    exit 6
+  fi
+
+  if ! "$CHECKERFRAMEWORK/checker/bin/javac" -processor org.checkerframework.checker.nullness.NullnessChecker \
+      "$CHECKERFRAMEWORK/docs/examples/NullnessReleaseTests.java" ; then
+    exit 6
+  fi
+
+  if ! java -jar "$CHECKERFRAMEWORK/checker/dist/checker.jar" \
+      -processor org.checkerframework.checker.nullness.NullnessChecker \
+      "$CHECKERFRAMEWORK/docs/examples/NullnessReleaseTests.java" ; then
+    exit 6
+  fi
+}
+
+echo "Testing with Java 8:"
+
+# shellcheck disable=SC2153
+export JAVA_HOME=$JAVA8_HOME
+export PATH=$JAVA_HOME/bin:$ORIG_PATH
+
+cfruntest
+
+
+# echo "Testing with latest type-annotations build:"
+
+# export JAVA_HOME=$WORKSPACE/../../type-annotations/lastSuccessful/archive/build/linux-x86_64-normal-server-release/images/j2sdk-image
+# export PATH=$JAVA_HOME/bin:$ORIG_PATH
+
+# cfruntest
diff --git a/docs/examples/InterningExample.java b/docs/examples/InterningExample.java
new file mode 100644
index 0000000..5122db8
--- /dev/null
+++ b/docs/examples/InterningExample.java
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.interning.qual.*;
+
+/**
+ * This class illustrates a correct use of the @{@link Interned} type annotation. The class doesn't
+ * do anything -- it is merely meant to be compiled. Compilation will produce no warning messages.
+ *
+ * <p>Also see {@link InterningExampleWithWarnings}, an example of incorrect use of the Interned
+ * type annotation. See the Interning Checker documentation for larger examples of annotated code.
+ */
+public class InterningExample {
+
+    public void example() {
+
+        // These type annotations are redundant -- the Interning Checker will
+        // infer them, but they are written here in the example for emhpasis.
+        // In general, you do not have to annotate local variables.
+        @Interned String foo = "foo";
+        @Interned String bar = "bar";
+
+        if (foo == bar) {
+            System.out.println("foo == bar");
+        }
+    }
+}
diff --git a/docs/examples/InterningExampleWithWarnings.java b/docs/examples/InterningExampleWithWarnings.java
new file mode 100644
index 0000000..174f655
--- /dev/null
+++ b/docs/examples/InterningExampleWithWarnings.java
@@ -0,0 +1,25 @@
+import org.checkerframework.checker.interning.qual.*;
+
+/**
+ * This class illustrates an incorrect use of the @{@link Interned} type annotation. The class
+ * doesn't do anything -- it is merely meant to be compiled. Compilation will produce warning
+ * messages.
+ *
+ * <p>Also see {@link InterningExample}, an example of correct use of the @Interned type annotation.
+ * See the Interning Checker documentation for larger examples of annotated code.
+ */
+public class InterningExampleWithWarnings {
+
+    public void example() {
+
+        // This type annotation is redundant -- the Interning Checker will
+        // infer it, but it is written here in the example for emhpasis.
+        // In general, you do not have to annotate local variables.
+        @Interned String foo = "foo";
+        String bar = new String("bar");
+
+        if (foo == bar) {
+            System.out.println("foo == bar");
+        }
+    }
+}
diff --git a/docs/examples/LockExample.java b/docs/examples/LockExample.java
new file mode 100644
index 0000000..a5765e2
--- /dev/null
+++ b/docs/examples/LockExample.java
@@ -0,0 +1,70 @@
+import org.checkerframework.checker.lock.qual.*;
+
+class BankAccount {
+    int balance;
+
+    void withdraw(@GuardSatisfied BankAccount this, int amount) {
+        this.balance = this.balance - amount;
+    }
+
+    void deposit(@GuardedBy("<self>") BankAccount this, int amount) {
+        synchronized (this) {
+            this.balance = this.balance + amount;
+        }
+    }
+}
+
+public class LockExample {
+    final @GuardedBy("<self>") BankAccount myAccount;
+
+    LockExample(@GuardedBy("<self>") BankAccount in) {
+        this.myAccount = in;
+    }
+
+    void demo1() {
+        myAccount.withdraw(100); // error!
+
+        synchronized (myAccount) {
+            myAccount.withdraw(100); // OK
+        }
+    }
+
+    @Holding("myAccount")
+    void demo1b() {
+        myAccount.withdraw(100); // OK
+    }
+
+    void demo1c() {
+        demo1b(); // error!
+
+        synchronized (myAccount) {
+            demo1b();
+        }
+    }
+
+    void demo2() {
+        myAccount.deposit(500); // OK
+    }
+
+    void demo3(Object someotherlock, @GuardedBy("someotherlock") BankAccount otherAccount) {
+        otherAccount.deposit(500); // error!
+    }
+
+    void demo3b(Object someotherlock, @GuardedBy("#1") BankAccount otherAccount) {
+        synchronized (someotherlock) {
+            otherAccount.deposit(500); // error!
+        }
+    }
+
+    void demo4() {
+        BankAccount spouseAccount = myAccount; // OK
+        spouseAccount.deposit(500); // OK
+
+        synchronized (myAccount) {
+            spouseAccount.withdraw(100); // error!
+        }
+        synchronized (spouseAccount) {
+            spouseAccount.withdraw(200); // OK
+        }
+    }
+}
diff --git a/docs/examples/Makefile b/docs/examples/Makefile
new file mode 100644
index 0000000..b3a4bcc
--- /dev/null
+++ b/docs/examples/Makefile
@@ -0,0 +1,18 @@
+JAVAC ?= $(realpath ../../checker/bin/javac)
+
+.PHONY: all
+
+all: compile
+
+compile:
+	$(JAVAC) *.java
+	cd fenum-extension && $(MAKE)
+	cd subtyping-extension && $(MAKE)
+	cd units-extension && $(MAKE)
+# The Maven example downloads a lot.  Try twice in case of network lossage.
+	cd MavenExample && ($(MAKE) || (sleep 60 && echo "Trying again:" && $(MAKE)))
+	cd lombok && $(MAKE)
+	cd errorprone && $(MAKE)
+
+# TODO: type check the different files with the right checker;
+#   some tests expect errors, compare against expected errors.
diff --git a/docs/examples/MavenExample/Makefile b/docs/examples/MavenExample/Makefile
new file mode 100644
index 0000000..1d63345
--- /dev/null
+++ b/docs/examples/MavenExample/Makefile
@@ -0,0 +1,10 @@
+.PHONY: all
+
+all: clean
+	(mvn -B -fn compile > Out.txt 2>&1) \
+	  || (sleep 5m && mvn -B -fn compile > Out.txt 2>&1)
+	grep -qF "MavenExample.java:[29,29] error: [assignment.type.incompatible] incompatible types in assignment." Out.txt || (cat Out.txt && false)
+
+clean:
+	mvn -q clean
+	rm -f Out.txt
diff --git a/docs/examples/MavenExample/README b/docs/examples/MavenExample/README
new file mode 100644
index 0000000..b087f44
--- /dev/null
+++ b/docs/examples/MavenExample/README
@@ -0,0 +1,9 @@
+This directory contains an example Maven project that downloads the latest
+Checker Framework Maven artifacts and runs the Nullness Checker.
+
+To run the example, simply execute:
+mvn compile
+
+You should see output that includes:
+
+MavenExample.java:[29,29] error: [assignment] incompatible types in assignment.
diff --git a/docs/examples/MavenExample/pom.xml b/docs/examples/MavenExample/pom.xml
new file mode 100644
index 0000000..1a752c3
--- /dev/null
+++ b/docs/examples/MavenExample/pom.xml
@@ -0,0 +1,196 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.checkerframework</groupId>
+  <artifactId>MavenExample</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+
+  <name>MavenExample</name>
+  <url>http://maven.apache.org</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <!-- These properties will be set by the Maven Dependency plugin -->
+    <errorProneJavac>${com.google.errorprone:javac:jar}</errorProneJavac>
+    <checkerFrameworkVersion><!-- checker-framework-version -->3.13.0<!-- /checker-framework-version --></checkerFrameworkVersion>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.13.1</version>
+      <scope>test</scope>
+    </dependency>
+    <!-- Annotations from the Checker Framework: nullness, interning, locking, ... -->
+    <dependency>
+      <groupId>org.checkerframework</groupId>
+      <artifactId>checker-qual</artifactId>
+      <version>${checkerFrameworkVersion}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.errorprone</groupId>
+      <artifactId>javac</artifactId>
+      <version>9+181-r4173-1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+      <version>3.1</version>
+    </dependency>
+    <dependency>
+        <groupId>javax.servlet</groupId>
+        <artifactId>servlet-api</artifactId>
+        <version>2.4</version>
+        <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+          <!-- This plugin will set properties values using dependency information -->
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-dependency-plugin</artifactId>
+          <executions>
+              <execution>
+                  <goals>
+                      <goal>properties</goal>
+                  </goals>
+              </execution>
+          </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-site-plugin</artifactId>
+        <version>3.2</version>
+      </plugin>
+    </plugins>
+  </build>
+
+  <profiles>
+    <profile>
+      <id>checkerframework</id>
+      <activation>
+        <jdk>[1.8,13)</jdk>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <version>3.8.1</version>
+            <configuration>
+              <fork>true</fork> <!-- Must fork or else JVM arguments are ignored. -->
+              <compilerArguments>
+                <Xmaxerrs>10000</Xmaxerrs>
+                <Xmaxwarns>10000</Xmaxwarns>
+              </compilerArguments>
+              <annotationProcessorPaths>
+                <path>
+                  <groupId>org.checkerframework</groupId>
+                  <artifactId>checker</artifactId>
+                  <version>3.7.0</version>
+                </path>
+              </annotationProcessorPaths>
+              <annotationProcessors>
+                <!-- Add all the checkers you want to enable here -->
+                <annotationProcessor>org.checkerframework.checker.nullness.NullnessChecker</annotationProcessor>
+              </annotationProcessors>
+              <compilerArgs>
+                <!-- <arg>-Awarns</arg> --> <!-- -Awarns turns type-checking errors into warnings. -->
+              </compilerArgs>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+      <dependencies>
+        <dependency>
+          <groupId>org.checkerframework</groupId>
+          <artifactId>checker</artifactId>
+          <version>${checkerFrameworkVersion}</version>
+        </dependency>
+      </dependencies>
+    </profile>
+
+    <profile>
+      <id>checkerframework-jdk8</id>
+      <activation>
+        <jdk>1.8</jdk>
+      </activation>
+      <!-- using github.com/google/error-prone-javac is required when running on JDK 8 -->
+      <properties>
+        <javac.version>9+181-r4173-1</javac.version>
+      </properties>
+      <dependencies>
+        <dependency>
+          <groupId>com.google.errorprone</groupId>
+          <artifactId>javac</artifactId>
+          <version>9+181-r4173-1</version>
+        </dependency>
+      </dependencies>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <configuration>
+              <fork>true</fork> <!-- Must fork or else JVM arguments are ignored. -->
+              <source>1.8</source>
+              <target>1.8</target>
+              <compilerArgs combine.children="append">
+                <arg>-J-Xbootclasspath/p:${settings.localRepository}/com/google/errorprone/javac/${javac.version}/javac-${javac.version}.jar</arg>
+              </compilerArgs>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+
+    <profile>
+      <id>checkerframework-jdk11</id>
+      <activation>
+        <jdk>11</jdk>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <configuration>
+              <fork>true</fork>
+              <release>11</release>
+              <compilerArgs combine.children="append">
+                <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
+              </compilerArgs>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+
+  <reporting>
+    <excludeDefaults>true</excludeDefaults>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jxr-plugin</artifactId>
+        <version>2.1</version>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <version>3.0.3</version>
+        <configuration>
+          <findbugsXmlOutput>true</findbugsXmlOutput>
+          <findbugsXmlWithMessages>true</findbugsXmlWithMessages>
+          <includeTests>true</includeTests>
+          <xmlOutput>true</xmlOutput>
+        </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
+
+</project>
diff --git a/docs/examples/MavenExample/src/main/java/org/checkerframework/example/MavenExample.java b/docs/examples/MavenExample/src/main/java/org/checkerframework/example/MavenExample.java
new file mode 100644
index 0000000..5818683
--- /dev/null
+++ b/docs/examples/MavenExample/src/main/java/org/checkerframework/example/MavenExample.java
@@ -0,0 +1,35 @@
+package org.checkerframework.example;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.lang3.text.StrBuilder;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * If you run:
+ *
+ * <pre>mvn compile</pre>
+ *
+ * then the build for this project should fail with a warning for the line:
+ *
+ * <pre>@NonNull Object nn = nullable;</pre>
+ */
+public class MavenExample {
+
+    public static @Nullable Object nullable = null;
+    public Map<Object, Object> map = new HashMap<>();
+
+    public static void main(final String[] args) {
+        System.out.println("Hello World!");
+
+        StrBuilder stb = new StrBuilder();
+
+        @NonNull Object nn = nullable; // error on this line
+        System.out.println(nn);
+    }
+
+    // Test for -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED.
+    void mapTest(@KeyFor("map") Object k) {}
+}
diff --git a/docs/examples/NullnessExample.java b/docs/examples/NullnessExample.java
new file mode 100644
index 0000000..53386e7
--- /dev/null
+++ b/docs/examples/NullnessExample.java
@@ -0,0 +1,38 @@
+import java.util.LinkedList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+/**
+ * This class illustrates use of nullness type annotations. The class doesn't do anything -- it is
+ * merely meant to be compiled. Compilation will produce no warning messages.
+ *
+ * <p>There are two related files that differ only slightly: {@link NullnessExample}, an example of
+ * correct use, and {@link NullnessExampleWithWarnings}, an example of incorrect use. See the
+ * Nullness Checker documentation for larger examples of annotated code.
+ */
+public class NullnessExample {
+
+    public void example() {
+
+        // In general, you do not have to annotate local variables, because the
+        // Nullness Checker infers such annotations.  It is written here in the
+        // example for emhpasis.
+        @NonNull String foo = "foo";
+        @NonNull String bar = "bar";
+
+        foo = bar;
+        bar = foo;
+    }
+
+    public @NonNull String exampleGenerics() {
+
+        List<@NonNull String> foo = new LinkedList<@NonNull String>();
+        List<@NonNull String> bar = foo;
+
+        @NonNull String quux = "quux";
+        foo.add(quux);
+        foo.add("quux");
+        @NonNull String baz = foo.get(0);
+        return baz;
+    }
+}
diff --git a/docs/examples/NullnessExampleWithWarnings.java b/docs/examples/NullnessExampleWithWarnings.java
new file mode 100644
index 0000000..25400d2
--- /dev/null
+++ b/docs/examples/NullnessExampleWithWarnings.java
@@ -0,0 +1,38 @@
+import java.util.LinkedList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+/**
+ * This class illustrates use of nullness type annotations. The class doesn't do anything -- it is
+ * merely meant to be compiled. Compilation will produce warning messages.
+ *
+ * <p>There are two related files that differ only slightly: {@link NullnessExample}, an example of
+ * correct use, and {@link NullnessExampleWithWarnings}, an example of incorrect use. See the
+ * Nullness Checker documentation for larger examples of annotated code.
+ */
+public class NullnessExampleWithWarnings {
+
+    public void example() {
+
+        // In general, you do not have to annotate local variables, because the
+        // Nullness Checker infers such annotations.  It is written here in the
+        // example for emhpasis.
+        @NonNull String foo = "foo";
+        String bar = null;
+
+        foo = bar;
+        bar = foo;
+    }
+
+    public String exampleGenerics() {
+
+        List<@NonNull String> foo = new LinkedList<@NonNull String>();
+        List<String> bar = foo;
+
+        String quux = null;
+        foo.add(quux);
+        foo.add("quux");
+        @NonNull String baz = foo.get(0);
+        return baz;
+    }
+}
diff --git a/docs/examples/NullnessReleaseTests.java b/docs/examples/NullnessReleaseTests.java
new file mode 100644
index 0000000..b746bde
--- /dev/null
+++ b/docs/examples/NullnessReleaseTests.java
@@ -0,0 +1,33 @@
+import java.util.LinkedList;
+import java.util.List;
+import org.checkerframework.checker.nullness.qual.*;
+
+/**
+ * This class is based on NullnessExample. This version contains additional tests to ensure that a
+ * build works correctly.
+ */
+public class NullnessReleaseTests {
+
+    public void example() {
+        @NonNull String foo = "foo";
+        @NonNull String bar = "bar";
+
+        foo = bar;
+        bar = foo;
+    }
+
+    public @NonNull String exampleGenerics() {
+        List<@NonNull String> foo = new LinkedList<@NonNull String>();
+        List<@NonNull String> bar = foo;
+
+        @NonNull String quux = "quux";
+        foo.add(quux);
+        foo.add("quux");
+        @NonNull String baz = foo.get(0);
+        return baz;
+    }
+
+    // For some reason this class causes an exception if the Checker
+    // Framework is compiled with JDK 7 and then executed on JDK 6.
+    class TestException extends Exception {}
+}
diff --git a/docs/examples/errorprone/Makefile b/docs/examples/errorprone/Makefile
new file mode 100644
index 0000000..ec4dee4
--- /dev/null
+++ b/docs/examples/errorprone/Makefile
@@ -0,0 +1,10 @@
+.PHONY: all
+
+all: clean
+	- ../../../gradlew build > Out.txt 2>&1
+	grep -qF "Demo.java:5: warning: [CollectionIncompatibleType] Argument 'i - 1' should not be passed to this method; its type int is not compatible with its collection's type argument Short" Out.txt
+	grep -qF "Demo.java:6: error: [argument.type.incompatible] incompatible argument for parameter arg0 of add." Out.txt
+
+clean:
+	../../../gradlew clean
+	rm -f Out.txt
diff --git a/docs/examples/errorprone/build.gradle b/docs/examples/errorprone/build.gradle
new file mode 100644
index 0000000..d400803
--- /dev/null
+++ b/docs/examples/errorprone/build.gradle
@@ -0,0 +1,34 @@
+///////////////////////////////////////////////////////////////////////////
+/// Checker Framework pluggable type-checking and Error Prone example
+///
+
+plugins {
+    id 'java'
+    id 'net.ltgt.errorprone' version '2.0.1'
+    // Checker Framework pluggable type-checking
+    id 'org.checkerframework' version '0.5.17'
+}
+
+apply plugin: 'org.checkerframework'
+
+dependencies {
+    // Must use at least version 2.4.0 of Error Prone.
+    errorprone 'com.google.errorprone:error_prone_core:2.7.1'
+}
+
+repositories {
+    mavenCentral()
+}
+
+checkerFramework {
+    checkers = [
+        'org.checkerframework.checker.nullness.NullnessChecker',
+    ]
+}
+
+compileJava {
+    // A checker will only run if Error Prone does not issue any warnings.  So
+    // convert the expected error to a warning to test that both Error Prone
+    // and the Nullness Checker run.
+    options.errorprone.warn('CollectionIncompatibleType')
+}
diff --git a/docs/examples/errorprone/settings.gradle b/docs/examples/errorprone/settings.gradle
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docs/examples/errorprone/settings.gradle
diff --git a/docs/examples/errorprone/src/main/java/Demo.java b/docs/examples/errorprone/src/main/java/Demo.java
new file mode 100644
index 0000000..00f825d
--- /dev/null
+++ b/docs/examples/errorprone/src/main/java/Demo.java
@@ -0,0 +1,8 @@
+import java.util.Set;
+
+public class Demo {
+    void demo(Set<Short> s, short i) {
+        s.remove(i - 1); // Error Prone error
+        s.add(null); // Nullness Checker error
+    }
+}
diff --git a/docs/examples/fenum-extension/Expected.txt b/docs/examples/fenum-extension/Expected.txt
new file mode 100644
index 0000000..8bb60d0
--- /dev/null
+++ b/docs/examples/fenum-extension/Expected.txt
@@ -0,0 +1,94 @@
+FenumDemo.java:6: error: [assignment] incompatible types in assignment.
+  public static final @Fenum("A") int ACONST1 = 1;
+                                                ^
+  found   : @FenumUnqualified int
+  required: @Fenum("A") int
+FenumDemo.java:7: error: [assignment] incompatible types in assignment.
+  public static final @Fenum("A") int ACONST2 = 2;
+                                                ^
+  found   : @FenumUnqualified int
+  required: @Fenum("A") int
+FenumDemo.java:9: error: [assignment] incompatible types in assignment.
+  public static final @Fenum("B") int BCONST1 = 4;
+                                                ^
+  found   : @FenumUnqualified int
+  required: @Fenum("B") int
+FenumDemo.java:10: error: [assignment] incompatible types in assignment.
+  public static final @Fenum("B") int BCONST2 = 5;
+                                                ^
+  found   : @FenumUnqualified int
+  required: @Fenum("B") int
+FenumDemo.java:12: error: [assignment] incompatible types in assignment.
+  public static final @MyFenum int CCONST1 = 5;
+                                             ^
+  found   : @FenumUnqualified int
+  required: @MyFenum int
+FenumDemo.java:13: error: [assignment] incompatible types in assignment.
+  public static final @MyFenum int CCONST2 = 6;
+                                             ^
+  found   : @FenumUnqualified int
+  required: @MyFenum int
+FenumDemo.java:19: error: [assignment] incompatible types in assignment.
+  @Fenum("B") int state2 = TestStatic.ACONST1; // Incompatible fenums forbidden!
+                                     ^
+  found   : @Fenum("A") int
+  required: @Fenum("B") int
+FenumDemo.java:28: error: [assignment] incompatible types in assignment.
+    state1 = 4; // Direct use of value forbidden!
+             ^
+  found   : @FenumUnqualified int
+  required: @Fenum("A") int
+FenumDemo.java:29: error: [assignment] incompatible types in assignment.
+    state1 = TestStatic.BCONST1; // Incompatible fenums forbidden!
+                       ^
+  found   : @Fenum("B") int
+  required: @Fenum("A") int
+FenumDemo.java:32: error: [argument] incompatible argument for parameter p of fenumArg.
+    fenumArg(5); // Direct use of value forbidden!
+             ^
+  found   : @FenumUnqualified int
+  required: @Fenum("A") int
+FenumDemo.java:33: error: [argument] incompatible argument for parameter p of fenumArg.
+    fenumArg(TestStatic.BCONST1); // Incompatible fenums forbidden!
+                       ^
+  found   : @Fenum("B") int
+  required: @Fenum("A") int
+FenumDemo.java:36: error: [assignment] incompatible types in assignment.
+    state3 = 8;
+             ^
+  found   : @FenumUnqualified int
+  required: @MyFenum int
+FenumDemo.java:37: error: [assignment] incompatible types in assignment.
+    state3 = TestStatic.ACONST2; // Incompatible fenums forbidden!
+                       ^
+  found   : @Fenum("A") int
+  required: @MyFenum int
+FenumDemo.java:40: error: [argument] incompatible argument for parameter p of myFenumArg.
+    myFenumArg(8); // Direct use of value forbidden!
+               ^
+  found   : @FenumUnqualified int
+  required: @MyFenum int
+FenumDemo.java:41: error: [argument] incompatible argument for parameter p of myFenumArg.
+    myFenumArg(TestStatic.BCONST2); // Incompatible fenums forbidden!
+                         ^
+  found   : @Fenum("B") int
+  required: @MyFenum int
+FenumDemo.java:54: error: [binary] binary operation between incompatible fenums: @Fenum("A") int and @Fenum("B") int
+    if (TestStatic.ACONST1 < TestStatic.BCONST2) {}
+                           ^
+FenumDemo.java:56: error: [binary] binary operation between incompatible fenums: @Fenum("A") int and @Fenum("B") int
+    if (TestStatic.ACONST1 == TestStatic.BCONST2) {}
+                           ^
+FenumDemo.java:58: error: [binary] binary operation between incompatible fenums: @Fenum("A") int and @MyFenum int
+    if (TestStatic.ACONST1 >= TestStatic.CCONST2) {}
+                           ^
+FenumDemo.java:61: error: [binary] binary operation between incompatible fenums: @Fenum("A") int and @FenumUnqualified int
+    if (TestStatic.ACONST1 < 5) {}
+                           ^
+FenumDemo.java:63: error: [binary] binary operation between incompatible fenums: @Fenum("B") int and @FenumUnqualified int
+    if (TestStatic.BCONST1 > 5) {}
+                           ^
+FenumDemo.java:65: error: [binary] binary operation between incompatible fenums: @MyFenum int and @FenumUnqualified int
+    if (TestStatic.CCONST1 == 5) {}
+                           ^
+21 errors
diff --git a/docs/examples/fenum-extension/FenumDemo.java b/docs/examples/fenum-extension/FenumDemo.java
new file mode 100644
index 0000000..af8ba6e
--- /dev/null
+++ b/docs/examples/fenum-extension/FenumDemo.java
@@ -0,0 +1,67 @@
+import org.checkerframework.checker.fenum.qual.Fenum;
+import qual.MyFenum;
+
+@SuppressWarnings("fenum:assignmenTestStatic") // initialization of fake enums
+class TestStatic {
+  public static final @Fenum("A") int ACONST1 = 1;
+  public static final @Fenum("A") int ACONST2 = 2;
+
+  public static final @Fenum("B") int BCONST1 = 4;
+  public static final @Fenum("B") int BCONST2 = 5;
+
+  public static final @MyFenum int CCONST1 = 5;
+  public static final @MyFenum int CCONST2 = 6;
+}
+
+public class FenumDemo {
+  @Fenum("A") int state1 = TestStatic.ACONST1; // ok
+
+  @Fenum("B") int state2 = TestStatic.ACONST1; // Incompatible fenums forbidden!
+
+  @MyFenum int state3 = TestStatic.CCONST1; // ok
+
+  void fenumArg(@Fenum("A") int p) {}
+
+  void myFenumArg(@MyFenum int p) {}
+
+  void foo() {
+    state1 = 4; // Direct use of value forbidden!
+    state1 = TestStatic.BCONST1; // Incompatible fenums forbidden!
+    state1 = TestStatic.ACONST2; // ok
+
+    fenumArg(5); // Direct use of value forbidden!
+    fenumArg(TestStatic.BCONST1); // Incompatible fenums forbidden!
+    fenumArg(TestStatic.ACONST1); // ok
+
+    state3 = 8;
+    state3 = TestStatic.ACONST2; // Incompatible fenums forbidden!
+    state3 = TestStatic.CCONST2; // ok
+
+    myFenumArg(8); // Direct use of value forbidden!
+    myFenumArg(TestStatic.BCONST2); // Incompatible fenums forbidden!
+    myFenumArg(TestStatic.CCONST1); // ok
+  }
+
+  void comparisons() {
+    if (TestStatic.ACONST1 < TestStatic.ACONST2) {
+      // ok
+    }
+    if (TestStatic.CCONST1 > TestStatic.CCONST2) {
+      // ok
+    }
+
+    // :: error: (binary)
+    if (TestStatic.ACONST1 < TestStatic.BCONST2) {}
+    // :: error: (binary)
+    if (TestStatic.ACONST1 == TestStatic.BCONST2) {}
+    // :: error: (binary)
+    if (TestStatic.ACONST1 >= TestStatic.CCONST2) {}
+
+    // :: error: (binary)
+    if (TestStatic.ACONST1 < 5) {}
+    // :: error: (binary)
+    if (TestStatic.BCONST1 > 5) {}
+    // :: error: (binary)
+    if (TestStatic.CCONST1 == 5) {}
+  }
+}
diff --git a/docs/examples/fenum-extension/Makefile b/docs/examples/fenum-extension/Makefile
new file mode 100644
index 0000000..b7ce040
--- /dev/null
+++ b/docs/examples/fenum-extension/Makefile
@@ -0,0 +1,36 @@
+FILES=qual/MyFenum.java
+
+JAVAOPTS=
+
+JAVAC?=../../../checker/bin/javac
+
+# gets the full path to the directory of the make file, which is also the root dir of the qual folder
+# for custom projects, it is best to encode the full root path as a variable
+PROJECTDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+
+all: compile-for-test named-quals-test qual-folder-test clean
+
+demo:
+	$(JAVAC) $(JAVAOPTS) $(FILES)
+	@echo "***** This command is expected to produce 21 errors:"
+	$(JAVAC) -classpath $(PROJECTDIR) -processor org.checkerframework.checker.fenum.FenumChecker -AqualDirs=$(PROJECTDIR) FenumDemo.java
+
+# compile qualifiers
+compile-for-test:
+	$(JAVAC) $(JAVAOPTS) $(FILES)
+
+# test case for using externally defined qualifiers by explicitly naming them using the -Aquals option
+named-quals-test:
+	$(JAVAC) -classpath $(PROJECTDIR) -processor org.checkerframework.checker.fenum.FenumChecker -Aquals=qual.MyFenum FenumDemo.java > Out.txt 2>&1 || true
+	diff -u Expected.txt Out.txt
+	rm -f Out.txt
+
+# test case for using externally defined qualifiers by loading them from a directory using the -AqualDirs option
+qual-folder-test:
+	$(JAVAC) -classpath $(PROJECTDIR) -processor org.checkerframework.checker.fenum.FenumChecker -AqualDirs=$(PROJECTDIR) FenumDemo.java > Out.txt 2>&1 || true
+	diff -u Expected.txt Out.txt
+	rm -f Out.txt
+
+# test clean up
+clean:
+	rm -f qual/*.class
diff --git a/docs/examples/fenum-extension/README b/docs/examples/fenum-extension/README
new file mode 100644
index 0000000..d4e95c4
--- /dev/null
+++ b/docs/examples/fenum-extension/README
@@ -0,0 +1,8 @@
+This directory shows how to create:
+ * a specialized fenum annotation, @MyFenum
+
+The make file shows how to invoke the Fenum Checker to utilize the specialized annotation.
+
+You can model your own new fenum annotations and fenum checkers after it.
+
+To see the demo, run: make demo
diff --git a/docs/examples/fenum-extension/qual/MyFenum.java b/docs/examples/fenum-extension/qual/MyFenum.java
new file mode 100644
index 0000000..92e1ec2
--- /dev/null
+++ b/docs/examples/fenum-extension/qual/MyFenum.java
@@ -0,0 +1,15 @@
+package qual;
+
+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;
+import org.checkerframework.checker.fenum.qual.FenumTop;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(FenumTop.class)
+public @interface MyFenum {}
diff --git a/docs/examples/lombok/Makefile b/docs/examples/lombok/Makefile
new file mode 100644
index 0000000..edd0af1
--- /dev/null
+++ b/docs/examples/lombok/Makefile
@@ -0,0 +1,14 @@
+.PHONY: all
+
+# Delomboking seems to mess up line numbers. The actual error is on line 13, but the error appears on line 12.
+# So check for both the error message and make sure it is for the right assignment.
+all: clean
+	- ../../../gradlew build > Out.txt 2>&1
+	(grep -qF "User.java:9: error: [argument] incompatible argument for parameter y of y." Out.txt \
+	  && grep -qF "Foo.java:12: error: [assignment] incompatible types in assignment." Out.txt \
+	  && grep -qF "y = null; // error" Out.txt) \
+	 || (echo "===== start of Out.txt =====" && cat Out.txt && echo "===== end of Out.txt =====" && false)
+
+clean:
+	../../../gradlew clean
+	rm -f Out.txt
diff --git a/docs/examples/lombok/build.gradle b/docs/examples/lombok/build.gradle
new file mode 100644
index 0000000..b6bb292
--- /dev/null
+++ b/docs/examples/lombok/build.gradle
@@ -0,0 +1,29 @@
+///////////////////////////////////////////////////////////////////////////
+/// Checker Framework pluggable type-checking and Lombok example
+///
+
+plugins {
+    id 'java'
+    id "io.freefair.lombok" version "5.1.0"
+    // Checker Framework pluggable type-checking
+    id 'org.checkerframework' version '0.5.17'
+}
+
+apply plugin: 'org.checkerframework'
+
+def cfHome = "${projectDir}/../../.."
+dependencies {
+    compileOnly files(cfHome + "/checker/dist/checker-qual.jar")
+    testCompileOnly files(cfHome + "/checker/dist/checker-qual.jar")
+    checkerFramework files(cfHome + "/checker/dist/checker.jar")
+}
+
+repositories {
+    mavenCentral()
+}
+
+checkerFramework {
+    checkers = [
+        'org.checkerframework.checker.nullness.NullnessChecker',
+    ]
+}
diff --git a/docs/examples/lombok/settings.gradle b/docs/examples/lombok/settings.gradle
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docs/examples/lombok/settings.gradle
diff --git a/docs/examples/lombok/src/main/java/lib/Foo.java b/docs/examples/lombok/src/main/java/lib/Foo.java
new file mode 100644
index 0000000..bac1257
--- /dev/null
+++ b/docs/examples/lombok/src/main/java/lib/Foo.java
@@ -0,0 +1,15 @@
+package lib;
+
+import lombok.Builder;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+@Builder
+public class Foo {
+    private @Nullable Integer x;
+    private Integer y;
+
+    void demo() {
+        x = null; // ok
+        y = null; // error
+    }
+}
diff --git a/docs/examples/lombok/src/main/java/lombok.config b/docs/examples/lombok/src/main/java/lombok.config
new file mode 100644
index 0000000..684957e
--- /dev/null
+++ b/docs/examples/lombok/src/main/java/lombok.config
@@ -0,0 +1 @@
+lombok.copyableAnnotations += org.checkerframework.checker.nullness.qual.Nullable
diff --git a/docs/examples/lombok/src/main/java/use/User.java b/docs/examples/lombok/src/main/java/use/User.java
new file mode 100644
index 0000000..64f218d
--- /dev/null
+++ b/docs/examples/lombok/src/main/java/use/User.java
@@ -0,0 +1,12 @@
+package use;
+
+import lib.Foo;
+
+public class User {
+    Foo demo() {
+        return Foo.builder()
+                .x(null) // ok
+                .y(null) // error
+                .build();
+    }
+}
diff --git a/docs/examples/subtyping-extension/Demo.java b/docs/examples/subtyping-extension/Demo.java
new file mode 100644
index 0000000..f1b30b1
--- /dev/null
+++ b/docs/examples/subtyping-extension/Demo.java
@@ -0,0 +1,38 @@
+import java.util.LinkedList;
+import java.util.List;
+import qual.Encrypted;
+
+abstract class EncryptionDemo {
+
+    public @Encrypted String encrypt(String text) {
+        byte[] b = text.getBytes();
+        for (int i = 0; i < b.length; b[i++]++) {
+            // side effect is in increment expression of for loop
+        }
+        // :: warning: (cast.unsafe)
+        return (@Encrypted String) new String(b);
+    }
+
+    // Only send encrypted data!
+    abstract void sendOverTheInternet(@Encrypted String msg);
+
+    void sendText() {
+        @Encrypted String s = encrypt("foo"); // valid
+        sendOverTheInternet(s); // valid
+
+        String t = encrypt("bar"); // valid (subtype)
+        sendOverTheInternet(t); // valid (flow)
+
+        List<@Encrypted String> lst = new LinkedList<@Encrypted String>();
+        lst.add(s);
+        lst.add(t);
+
+        for (String str : lst) // valid
+        sendOverTheInternet(str);
+    }
+
+    void sendPassword() {
+        String password = "unencrypted";
+        sendOverTheInternet(password); // invalid
+    }
+}
diff --git a/docs/examples/subtyping-extension/Expected.txt b/docs/examples/subtyping-extension/Expected.txt
new file mode 100644
index 0000000..8d6b7fe
--- /dev/null
+++ b/docs/examples/subtyping-extension/Expected.txt
@@ -0,0 +1,10 @@
+Demo.java:13: warning: [cast.unsafe] cast from "@PossiblyUnencrypted String" to "@Encrypted String" cannot be statically verified
+        return (@Encrypted String) new String(b);
+               ^
+Demo.java:36: error: [argument] incompatible argument for parameter msg of sendOverTheInternet.
+        sendOverTheInternet(password); // invalid
+                            ^
+  found   : @PossiblyUnencrypted String
+  required: @Encrypted String
+1 error
+1 warning
diff --git a/docs/examples/subtyping-extension/Makefile b/docs/examples/subtyping-extension/Makefile
new file mode 100644
index 0000000..58f1d9c
--- /dev/null
+++ b/docs/examples/subtyping-extension/Makefile
@@ -0,0 +1,37 @@
+FILES=qual/Encrypted.java \
+  qual/PossiblyUnencrypted.java
+
+JAVAOPTS=
+
+JAVAC?=../../../checker/bin/javac
+
+# gets the full path to the directory of the make file, which is also the root dir of the qual folder
+# for custom projects, it is best to encode the full root path as a variable
+PROJECTDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+
+all: compile-for-test named-quals-test qual-folder-test clean
+
+demo:
+	$(JAVAC) $(JAVAOPTS) $(FILES)
+	@echo "***** This command is expected to produce a warning on line 12 and an error on line 35:"
+	$(JAVAC) -classpath $(PROJECTDIR) -processor org.checkerframework.common.subtyping.SubtypingChecker -AqualDirs=$(PROJECTDIR) Demo.java
+
+# compile qualifiers
+compile-for-test:
+	$(JAVAC) $(JAVAOPTS) $(FILES)
+
+# test case for using externally defined qualifiers by explicitly naming them using the -Aquals option
+named-quals-test:
+	$(JAVAC) -classpath $(PROJECTDIR) -processor org.checkerframework.common.subtyping.SubtypingChecker -Aquals=qual.Encrypted,qual.PossiblyUnencrypted Demo.java > Out.txt 2>&1 || true
+	diff -u Expected.txt Out.txt
+	rm -f Out.txt
+
+# test case for using externally defined qualifiers by loading them from a directory using the -AqualDirs option
+qual-folder-test:
+	$(JAVAC) -classpath $(PROJECTDIR) -processor org.checkerframework.common.subtyping.SubtypingChecker -AqualDirs=$(PROJECTDIR) Demo.java > Out.txt 2>&1 || true
+	diff -u Expected.txt Out.txt
+	rm -f Out.txt
+
+# test clean up
+clean:
+	rm -f qual/*.class
diff --git a/docs/examples/subtyping-extension/README b/docs/examples/subtyping-extension/README
new file mode 100644
index 0000000..d263faf
--- /dev/null
+++ b/docs/examples/subtyping-extension/README
@@ -0,0 +1,9 @@
+This directory shows how to create:
+ * a qualifier which denotes that the representation of an object (such as a String, CharSequence, or byte[]) is encrypted, @Encrypted
+ * a qualifier which denotes that the representation of an object may be unencrypted, @PossiblyUnencrypted
+
+The make file shows how to invoke the Subtyping Checker to utilize the specialized annotation.
+
+You can model your own new units and unit checkers after it.
+
+To see the demo, run: make demo
diff --git a/docs/examples/subtyping-extension/qual/Encrypted.java b/docs/examples/subtyping-extension/qual/Encrypted.java
new file mode 100644
index 0000000..5f781b6
--- /dev/null
+++ b/docs/examples/subtyping-extension/qual/Encrypted.java
@@ -0,0 +1,18 @@
+package qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/** Denotes that the representation of an object is encrypted. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(PossiblyUnencrypted.class)
+@DefaultFor({TypeUseLocation.LOWER_BOUND})
+public @interface Encrypted {}
diff --git a/docs/examples/subtyping-extension/qual/PossiblyUnencrypted.java b/docs/examples/subtyping-extension/qual/PossiblyUnencrypted.java
new file mode 100644
index 0000000..b5ce5cf
--- /dev/null
+++ b/docs/examples/subtyping-extension/qual/PossiblyUnencrypted.java
@@ -0,0 +1,17 @@
+package qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** Denotes that the representation of an object might not be encrypted. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@DefaultQualifierInHierarchy
+@SubtypeOf({})
+public @interface PossiblyUnencrypted {}
diff --git a/docs/examples/units-extension/Expected.txt b/docs/examples/units-extension/Expected.txt
new file mode 100644
index 0000000..3fd95ce
--- /dev/null
+++ b/docs/examples/units-extension/Expected.txt
@@ -0,0 +1,11 @@
+UnitsExtensionDemo.java:14: error: [assignment] incompatible types in assignment.
+    frq = 5;
+          ^
+  found   : @UnknownUnits int
+  required: @Hz int
+UnitsExtensionDemo.java:67: error: [assignment] incompatible types in assignment.
+    @Hz int badTernaryAssign = seconds > 10 ? hertz : kilohertz;
+                                            ^
+  found   : @Frequency int
+  required: @Hz int
+2 errors
diff --git a/docs/examples/units-extension/Makefile b/docs/examples/units-extension/Makefile
new file mode 100644
index 0000000..165be31
--- /dev/null
+++ b/docs/examples/units-extension/Makefile
@@ -0,0 +1,39 @@
+FILES=qual/Frequency.java \
+  qual/FrequencyRelations.java \
+  qual/Hz.java \
+  qual/kHz.java
+
+JAVAOPTS= -classpath .:../../../checker/dist/checker.jar
+
+JAVAC?=../../../checker/bin/javac
+
+# gets the full path to the directory of the make file, which is also the root dir of the qual folder
+# for custom projects, it is best to encode the full root path as a variable
+PROJECTDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+
+all: compile-for-test named-quals-test qual-folder-test clean
+
+demo:
+	$(JAVAC) $(JAVAOPTS) $(FILES)
+	@echo "***** This command is expected to produce errors on line 14 & 67:"
+	$(JAVAC) -classpath $(PROJECTDIR) -processor org.checkerframework.checker.units.UnitsChecker -AunitsDirs=$(PROJECTDIR) UnitsExtensionDemo.java
+
+# compile qualifiers
+compile-for-test:
+	$(JAVAC) $(JAVAOPTS) $(FILES)
+
+# test case for using externally defined units by explicitly naming them using the -Aunits option
+named-quals-test:
+	$(JAVAC) -classpath $(PROJECTDIR) -processor org.checkerframework.checker.units.UnitsChecker -Aunits=qual.Hz,qual.kHz,qual.Frequency UnitsExtensionDemo.java > Out.txt 2>&1 || true
+	diff -u Expected.txt Out.txt
+	rm -f Out.txt
+
+# test case for using externally defined units by loading them from a directory using the -AunitsDirs option
+qual-folder-test:
+	$(JAVAC) -classpath $(PROJECTDIR) -processor org.checkerframework.checker.units.UnitsChecker -AunitsDirs=$(PROJECTDIR) UnitsExtensionDemo.java > Out.txt 2>&1 || true
+	diff -u Expected.txt Out.txt
+	rm -f Out.txt
+
+# test clean up
+clean:
+	rm -f qual/*.class
diff --git a/docs/examples/units-extension/README b/docs/examples/units-extension/README
new file mode 100644
index 0000000..bdd2043
--- /dev/null
+++ b/docs/examples/units-extension/README
@@ -0,0 +1,13 @@
+This directory shows how to create:
+ * a new kind, @Frequency
+ * a new unit, Hertz (@Hz)
+ * a unit Kilohertz (@kHz) which is an alias annotation of @Hz(Prefix.kilo):
+    using @kHz has the same effect as using @Hz(Prefix.kilo) in source code
+ * relations that enforce that Hertz is computed as scalar / second,
+    and that Kilohertz is computed as scalar / millisecond
+
+The make file shows how to invoke the Units Checker to utilize these kinds and units.
+
+You can model your own new units and unit checkers after it.
+
+To see the demo, run: make demo
diff --git a/docs/examples/units-extension/UnitsExtensionDemo.java b/docs/examples/units-extension/UnitsExtensionDemo.java
new file mode 100644
index 0000000..e46c1b8
--- /dev/null
+++ b/docs/examples/units-extension/UnitsExtensionDemo.java
@@ -0,0 +1,69 @@
+import org.checkerframework.checker.units.qual.Prefix;
+import org.checkerframework.checker.units.qual.s;
+import org.checkerframework.checker.units.util.UnitsTools;
+import qual.Frequency;
+import qual.Hz;
+import qual.kHz;
+
+public class UnitsExtensionDemo {
+  @Hz int frq;
+
+  void bad() {
+    // Error! Unqualified value assigned to a @Hz value.
+    // :: error: (assignment)
+    frq = 5;
+
+    // suppress all warnings issued by the units checker for the d1 assignment statement
+    @SuppressWarnings("units")
+    @Hz int d1 = 9;
+
+    // specifically suppress warnings related to any frequency units for the d2 assigment
+    // statement
+    @SuppressWarnings("frequency")
+    @Hz int d2 = 10;
+  }
+
+  // specifically suppresses warnings for the hz annotation for the toHz method
+  @SuppressWarnings("hz")
+  static @Hz int toHz(int hz) {
+    return hz;
+  }
+
+  void good() {
+    frq = toHz(9);
+
+    @s double time = 5 * UnitsTools.s;
+    @Hz double freq2 = 20 / time;
+  }
+
+  void auto(@s int time) {
+    // The @Hz annotation is automatically added to the result
+    // of the division, because we provide class FrequencyRelations.
+    frq = 99 / time;
+  }
+
+  public static void main(String[] args) {
+    @Hz int hertz = toHz(20);
+    @s int seconds = 5 * UnitsTools.s;
+
+    @SuppressWarnings("units")
+    @s(Prefix.milli) int millisec = 10;
+
+    @SuppressWarnings("hz")
+    @kHz int kilohertz = 30;
+
+    @Hz int resultHz = hertz + 20 / seconds;
+    System.out.println(resultHz);
+
+    @kHz int resultkHz = kilohertz + 50 / millisec;
+    System.out.println(resultkHz);
+
+    // this demonstrates the type hierarchy resolution: the common supertype of Hz and kHz is
+    // Frequency, so this statement will pass
+    @Frequency int okTernaryAssign = seconds > 10 ? hertz : kilohertz;
+
+    // on the other hand, this statement expects the right hand side to be a Hz, so it will fail
+    // :: error: (assignment)
+    @Hz int badTernaryAssign = seconds > 10 ? hertz : kilohertz;
+  }
+}
diff --git a/docs/examples/units-extension/qual/Frequency.java b/docs/examples/units-extension/qual/Frequency.java
new file mode 100644
index 0000000..793acd1
--- /dev/null
+++ b/docs/examples/units-extension/qual/Frequency.java
@@ -0,0 +1,22 @@
+package qual;
+
+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;
+import org.checkerframework.checker.units.qual.UnitsRelations;
+import org.checkerframework.checker.units.qual.UnknownUnits;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Units of frequency, such as hertz (@{@link Hz}).
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({UnknownUnits.class})
+@UnitsRelations(FrequencyRelations.class)
+public @interface Frequency {}
diff --git a/docs/examples/units-extension/qual/FrequencyRelations.java b/docs/examples/units-extension/qual/FrequencyRelations.java
new file mode 100644
index 0000000..c94836c
--- /dev/null
+++ b/docs/examples/units-extension/qual/FrequencyRelations.java
@@ -0,0 +1,58 @@
+package qual;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.units.UnitsRelations;
+import org.checkerframework.checker.units.UnitsRelationsTools;
+import org.checkerframework.checker.units.qual.Prefix;
+import org.checkerframework.checker.units.qual.s;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/** Relations among units of frequency. */
+public class FrequencyRelations implements UnitsRelations {
+
+    protected AnnotationMirror hertz, kilohertz, second, millisecond;
+    protected Elements elements;
+
+    public UnitsRelations init(ProcessingEnvironment env) {
+        elements = env.getElementUtils();
+
+        // create Annotation Mirrors, each representing a particular Unit's Annotation
+        hertz = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, Hz.class);
+        kilohertz =
+                UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, Hz.class, Prefix.kilo);
+        second = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, s.class);
+        millisecond =
+                UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, s.class, Prefix.milli);
+
+        return this;
+    }
+
+    /** No multiplications yield Hertz. */
+    public @Nullable AnnotationMirror multiplication(
+            AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) {
+        // return null so the default units relations can process multiplcations of other units
+        return null;
+    }
+
+    /**
+     * Division of a scalar by seconds yields Hertz. Division of a scalar by milliseconds yields
+     * Kilohertz. Other divisions yield an unannotated value.
+     */
+    public @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) {
+        if (UnitsRelationsTools.hasNoUnits(lht)) {
+            // scalar / millisecond => kilohertz
+            if (UnitsRelationsTools.hasSpecificUnit(rht, millisecond)) {
+                return kilohertz;
+            }
+            // scalar / second => hertz
+            else if (UnitsRelationsTools.hasSpecificUnit(rht, second)) {
+                return hertz;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/docs/examples/units-extension/qual/Hz.java b/docs/examples/units-extension/qual/Hz.java
new file mode 100644
index 0000000..89fa0f9
--- /dev/null
+++ b/docs/examples/units-extension/qual/Hz.java
@@ -0,0 +1,24 @@
+package qual;
+
+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;
+import org.checkerframework.checker.units.qual.Prefix;
+import org.checkerframework.checker.units.qual.UnitsRelations;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Hertz (Hz), a unit of frequency.
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Frequency.class)
+@UnitsRelations(FrequencyRelations.class)
+public @interface Hz {
+    Prefix value() default Prefix.one;
+}
diff --git a/docs/examples/units-extension/qual/kHz.java b/docs/examples/units-extension/qual/kHz.java
new file mode 100644
index 0000000..48ddab5
--- /dev/null
+++ b/docs/examples/units-extension/qual/kHz.java
@@ -0,0 +1,24 @@
+package qual;
+
+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;
+import org.checkerframework.checker.units.qual.Prefix;
+import org.checkerframework.checker.units.qual.UnitsMultiple;
+import org.checkerframework.checker.units.qual.UnitsRelations;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Kilohertz (kHz), a unit of frequency, and an alias of @Hz(Prefix.kilo).
+ *
+ * @checker_framework.manual #units-checker Units Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Frequency.class)
+@UnitsRelations(FrequencyRelations.class)
+@UnitsMultiple(quantity = Hz.class, prefix = Prefix.kilo) // alias of @Hz(Prefix.kilo)
+public @interface kHz {} // No prefix defined in the annotation itself
diff --git a/docs/examples/wpi-many/README b/docs/examples/wpi-many/README
new file mode 100644
index 0000000..df5fec2
--- /dev/null
+++ b/docs/examples/wpi-many/README
@@ -0,0 +1,3 @@
+This directory contains examples of how to use the wpi-many.sh script
+with a custom checker.
+For an explanation, see https://checkerframework.org/manual/#wpi-many .
diff --git a/docs/examples/wpi-many/securerandom.list b/docs/examples/wpi-many/securerandom.list
new file mode 100644
index 0000000..c58f026
--- /dev/null
+++ b/docs/examples/wpi-many/securerandom.list
@@ -0,0 +1,298 @@
+https://github.com/1-Family/external	06a0bfee7abba6e30ee25974839f369a0fc9d551
+https://github.com/10114395/android-5.0.0_r5	d023f08717af9bb61c2a273a040d3986a87422e1
+https://github.com/Abhishekh-TEL/pdroid	58d2d0991bbdd8344b377bac19310594deb327c1
+https://github.com/AdoptOpenJDK/openjdk-jdk14	5ed4b9f2c0a1572da09fef84aaf0ce899689c4c2
+https://github.com/Akhiljs/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/AngelinaChebotareva/corda	4dd34149b83b935ba7d7e9dcb91570fe8807be8b
+https://github.com/ArminMa/SocialMediaBackEnd	3f73e53963c84dca1b21ec009938f1811891e363
+https://github.com/Arrowyi/Android_framework_23	6f0518f764080013e27b8836e41b32c180e06783
+https://github.com/Bbpzz/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/Borzen/AndroidOS	33ec68844d7332bca742a6e3a961221a4048a59f
+https://github.com/BvbKoala/AndroidSourceCode	0ba44c97f0b6c21621cc531600f9b912acee9c07
+https://github.com/CarlosDiogo01/NUMA_Aware_Thesis	ab427203f00e44bb9f9bb86022191f1506160c97
+https://github.com/ChrisP-Android/BananaPi-Android-4.2.2-Liab	b74ba79ea3060f1756c931596e672b746c606e51
+https://github.com/CunningLogic/gt-p3113-kernel	4eee2a15c2794ea9c8790e9a9cb66330b84aa338
+https://github.com/DmitrySkiba/ARTPart	1c16aa6bb6519a37286d6c72d23ab7d4787f5b6b
+https://github.com/Dr-Emann/haxe-java-externs	a64eda0c84ddba90ed0627f70b0ed118c1ee8faf
+https://github.com/Dxh7788/openjdk	986a51503a7a4a938c5ef31cd999787eac2d4759
+https://github.com/F0rth/Izly	89af45cedfc38e370a64c9fa341815070cdf49d6
+https://github.com/FFYzz/jdk14	aa017c8fa926dad7b26a37d772123304d54e8361
+https://github.com/FlexoVM/flexovm	b90d63cd1df87c3e44e2945908c18bf689c6d2d1
+https://github.com/GPNex/diordna_erocbil	9f66e3dc9794ec8da6fbd140ae5c09cfa6670a48
+https://github.com/Gebreyesus/from-google--error-detect-java	e0197b50c86d266f5c1f733d2dfbeadd0fe9ea99
+https://github.com/Guangxingtianxia/android-source-code	749ba361e873d0fbdd49bdd90ac24b9332ae6ab7
+https://github.com/Halliburton-Landmark/openjdk-jdk11u	5ab8feeda030ea9d71290c84379c901cec4f3325
+https://github.com/Iscle/OrangePi_4G-IOT_Android_8.1_BSP	58548740b6e9afe99a55b77582588c37609d2bca
+https://github.com/IzZzI/AOSPRead	d25b2934fb45bed28e87e2d5dfb6eec65e370b42
+https://github.com/JRGGRoberto/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/JSDemos/android-sdk-20	f07e3d226070be615385a974f57447f05f87a34f
+https://github.com/JamesBeaton/manciniAutomation	dd8b351d57a9bfc0cd28a7ec641efec61b02ce02
+https://github.com/JeffCarpenter/openjdk-sandbox-8200758	4994cdb78c5e0fbf7fc665fbcd1d531959cda10d
+https://github.com/JeffreyLau/JJWD-K8_icsCream	a9790f6edf973d9e6b102f9be89c7b7f883f1cb2
+https://github.com/JetBrains/jdk8u_tests	263c74f1842954bae0b34ec3703ad35668b3ffa2
+https://github.com/JoeLiu2015/MyProjects	95fcdeb7d295b0e90fb02ff77b8b30f6ee7434a4
+https://github.com/Jonasrems/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/Jorgerunza/Parcial2-PruebasAutomaticas	2fcaad5f3845376cdbebf26f7d7d8f28db45c6ff
+https://github.com/Katarzynasrom/patch-hosting-for-android-x86-support	cfd927fa04ffd208bd4370373f84e94f28f1d5ca
+https://github.com/Keneral/ae	d5b2c518599bca9e802706f7a3f56a4247f73870
+https://github.com/Kingson4Wu/openjdk9-clion	f1b008bdf88da0d488f2068f91a7b6be1934fe88
+https://github.com/Koty97/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/LIFECorp/root_source	d9b2a02e5fc2303ddab5682b3bde0cab7e292e79
+https://github.com/LeMaker/android-actions	91fd670a2a9f13dcd5c1a10c3413cbad6b386d80
+https://github.com/LeeMinWoo/android_dalvik	51ab0adb191273f9e28441724f1384c31779c125
+https://github.com/Longcnttbkhn/ChatSecurity	cd00c387ce5e9b7d17edef280bbc895b9104ba9d
+https://github.com/MCApollo/OpenJDK-iOS-jailbroken	d546eeb4f10836505185ad9b96eb8848cb2587e7
+https://github.com/MT6797/external	758b5b1c1d039ee315908358019e15f54b905f3c
+https://github.com/MatrixYe23/openjdk	c56baae38f019c51ab113bb18e464418bba313ee
+https://github.com/MauroelBrizola/patch-hosting-for-android-x86-support	cfd927fa04ffd208bd4370373f84e94f28f1d5ca
+https://github.com/Michael-Frank/jmh-Introduction	757ea2702088d02d3ccb6c6111f1bb5df748734d
+https://github.com/Minjep/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/MobiVM/robovm	e0847ce527ac8b323b03e395a66d148788bf15a9
+https://github.com/MomirSarac/Java-SecurityRandom-Example	40dc9c2e3cf10eb1e87505686a1502eeae269d14
+https://github.com/MonsieurCode/udoo-quad-kitkat	61a4f8eae394887490364f5c2a1f9f393427678e
+https://github.com/MxJ3lany/ExtractionFiles	18eafb494421b0f225b4b6017e9e17da65843517
+https://github.com/Nekbakht/in-the-box	820f1b45f9e84d9b2d06b8f973d5016acdda0590
+https://github.com/OpenEtna/android_dalvik	93dda3428d68c4e1b3ac32532cddf16b3cceca6d
+https://github.com/OpenSource-Infinix/android_external	d5b2c518599bca9e802706f7a3f56a4247f73870
+https://github.com/PerfectCarl/robovm-android	955efafe67f22e5d930e17e492c2fb7681c61a51
+https://github.com/Phantomwriter/myGeoApp	943ae212e8962a896b1209883e92e7b533e4993b
+https://github.com/ProjectMAXS/maxs	e8e02c46a9ea26af15b07469663b0af99ea96af7
+https://github.com/ProjectRetroScope/RetroScope	276b5b03d63f49235db74f2c501057abb9e79d89
+https://github.com/RepoBackups/android_build_tass_tmo	7f4f03d91a3fcd1fbdc57d444ad6c108d12f0e9c
+https://github.com/RetronixTechInc/android-retronix	605744953141141631b174a35301d587fb35eaba
+https://github.com/RichardHappy/android-19	3e9bce82d7d4d96835ee74f845a1c1938757143d
+https://github.com/STAMP-project/dspot-experiments	121487e65cdce6988081b67f21bbc6731354a47f
+https://github.com/Scorpio92/OLD_HAL_KITKAT	7eea4c238825e1bb42505b79537daff04c08ba18
+https://github.com/Sikandarkhan/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/SirIndubitable/android	5f0868089ca096a21627d02a0d06301ee371f218
+https://github.com/Sjith/android-source-read	0d575e563feaa58d370516ec38c9aac2de2bfce8
+https://github.com/StefanSchneidel/patch-hosting-for-android-x86-support	cfd927fa04ffd208bd4370373f84e94f28f1d5ca
+https://github.com/StormGens/Android-23-cn	f56a3749135657a5b2c914659e655fa933a039bb
+https://github.com/StormGens/Android_SDK_SourceCode	1793227de5f37befd4fc8388fb2b6f005b486ef3
+https://github.com/TO21Consortium/SGSWPlatform	116caf7b2cfdb28bb1b5d4aac6822293e9c6d264
+https://github.com/Tangjiahui26/SoftwareTestingAndVerification	750697f0ec2bc07884708773e4738ac5d41079b1
+https://github.com/TheSecurityVault/random	78adf23f32e5e695dd72afb0f050c37a56fe5d0c
+https://github.com/ThoreTechnics/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/TonyQi/bcc-admin	013d801344aef0390cf68e667529bce027f87d5d
+https://github.com/WenbinHou/PKUAutoGateway.Android	1170dea169d8a91a919f006fcda29f1187d6e3d9
+https://github.com/WillGluck/JavaAlgorithmsAndDataStructures	53a8a334cbd2bba07850e9e41545d192ddee9d9c
+https://github.com/abazad/android_cm	10c17b729892334092588ce1479426422f4d64fe
+https://github.com/abgoyal-archive/OT_997D	9df73ab7dd2ee4dae9264978f96b097f9d187a37
+https://github.com/achabdo/coyote	eb0378b78dd88ea1af94ba03fd34c67d91f64b1d
+https://github.com/adeelmunir/x210_ics_rtm_v13	9b57c96068d28d55600fa76d7d73455f69664902
+https://github.com/adobe-flash/crossbridge	f8c6eff42a2936d66c5a7aa1d5fbc35379985fb5
+https://github.com/andr3jx/MTK6577	095200ac563060d63991b084474fcda4808ab5d6
+https://github.com/andrew-m-leonard/test-openjdk-jdk11u	d5bd69696eac1462d004c80613d4a9cee0ccace2
+https://github.com/android-nostalgic/platform_dalvik	2ad60cfc28e14ee8f0bb038720836a4696c478ad
+https://github.com/anthonytyk/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/aohanyao/PlatformLibCore	6698a87474bf8c8a43e82e1a297eda08a3f4725f
+https://github.com/apache/harmony	02970cb7227a335edd2c8457ebdde0195a735733
+https://github.com/apache/harmony-classlib	ee663b95e84093405985d9557a925b651a0f69b3
+https://github.com/archermind/MTK_X20_BASE_AOSP	977e6ec44363be5ce1eebf9c47339fe5f4ca0947
+https://github.com/arjunroy/cinder_dalvik	efa51ec316e49abf49013de3d08c81dec7fbb8f2
+https://github.com/arjwan/patch-hosting-for-android-x86-support	cfd927fa04ffd208bd4370373f84e94f28f1d5ca
+https://github.com/asherkhanov7/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/asnowfix/android_dalvik	a24c6ffaedf339ed1a2f090c79ce66235833a2b4
+https://github.com/avitkus/HTTPServer	5abc90a3f474a233add539259d82cf361940b47c
+https://github.com/ayo-janet/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/b2gdev/Android-JB-4.1.2	e66aea986bbf29ff70e5ec4440504ca24f8104e1
+https://github.com/benjaminvm/vm	de2fc73cc9115f5b7259b07553810ad32413475a
+https://github.com/bigbrother82/android-test	51ab0adb191273f9e28441724f1384c31779c125
+https://github.com/bitlogo/androidsource	47f9bbfb032b3118907e709b1099d3f1983ae346
+https://github.com/blois/AndroidSDKCloneMin	918d8e22ea50eb9c871f5ae22bc483e57406dddb
+https://github.com/bluespark80/example	bab348bd44303c97203ea65e169045754cede3dc
+https://github.com/bnbinod/in-the-box	820f1b45f9e84d9b2d06b8f973d5016acdda0590
+https://github.com/bourne015/android-ics-s5pv210	b0d84c2af5eb01109e572f19d9e7c0f07790f8ee
+https://github.com/brianwoo/cm11_grouper	b23d9dd09915bf2c1a744113e507d3d7fc32f4bd
+https://github.com/caizp2008/patch-hosting-for-android-x86-support	cfd927fa04ffd208bd4370373f84e94f28f1d5ca
+https://github.com/canmeizhexue/Android2.3SourceAndDocs	9d08850bcd24371f6534228e7e0b90c61a6cd16c
+https://github.com/carterwirtz/in-the-box	820f1b45f9e84d9b2d06b8f973d5016acdda0590
+https://github.com/chaoyangnz/openjdk9	a42e8241762da495b0f286de7838fd8eb84f5c0c
+https://github.com/chenxianjing/cloudintegrate	577dedfe3edfbe9960da9d76b9757830e1f9c81e
+https://github.com/cjrequena/crypto	784e67a58328f0df3e011403beffda1eabcfbf98
+https://github.com/codediving/Android19_SourceNote	1c5d3a737b9973357afe2801023cf1e6dd612a69
+https://github.com/corda/corda	8faf72f7b50ba087617ba79ff27a596c85a703cf
+https://github.com/craignelson/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/cuishangg/friendly-arm-android-4.0	c807214fd36a86952ff0b545dd752289dfe59edc
+https://github.com/danny-source/SDK_Android_Source_03-14	323ad23e16f598d5589485b467bb9fba7403c811
+https://github.com/debian-pkg-android-tools/android-framework-23	738983d8c636efdfef17cd8b5b4d10e903b0db45
+https://github.com/deepakkathayat/android	86718bc4f27509e16e408ccd9e2d9c100c59881f
+https://github.com/degbug/source-code	251310879c9a63affe1802faf6da2296c6613feb
+https://github.com/destiny-creates/become-muslim	466077d8110332802d1160cc8934e0a8328aba96
+https://github.com/dmlloyd/openjdk-modules	117a1cd6a8c1c4471506880d1890bd3e6f78d70e
+https://github.com/dmurimi14/ALPS-MP-N1.MP1-V1_FIH6737M_65_N1_INHOUSE	cb8e0843db07005be54b67d3c10f5c4a06979c05
+https://github.com/edwinwijaya94/kripto_k9	96e5e740ee033728c12f65666b3983d95f89fa6a
+https://github.com/elsaxo/patch-hosting-for-android-x86-support	cfd927fa04ffd208bd4370373f84e94f28f1d5ca
+https://github.com/ericmckean/nacl-llvm-branches.llvm-gcc	6af8a94420a576c859aec5cf129fe0a51c33647d
+https://github.com/esotericnomen/Android_SDK_Repo	40ed7e1f2aced2f79cd4731daeea75b77110b32d
+https://github.com/faridazhar/panda-a4	ff88df36a11e3af5c85d565351ad72ce62406e5e
+https://github.com/federivas/s6500d_official_platform	f94641150d9821c962bef8f3945fa3524cab09d0
+https://github.com/flyskywhy/android-sdk	a6f2482e1c43f5d28882a41b5360baea6f1a5b82
+https://github.com/freeVM/freeVM	9caa0256b4089d74186f84b8fb2afc95a0afc7bc
+https://github.com/g-petracca/AWare	a1824c4fb3bdf01d783b5846cbb5d07d328cc4c6
+https://github.com/galilio/patch-hosting-for-android-x86-support	cfd927fa04ffd208bd4370373f84e94f28f1d5ca
+https://github.com/gaoxu122/sysmanage	3feb2d12186d8b1bc549235e76882943a6d9c840
+https://github.com/gnehcgnaw/ojdk	ab1a738928b91dea075149ec696eb61f4c8074b9
+https://github.com/gonzo0605/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/google/error-prone	ebd99dcbbcc115254c4384cd2d1802219a658d79
+https://github.com/gopicsw/pdn-slatedroid-froyo	9a33664a6f0bf7ead8688d13b6a517674cdee08d
+https://github.com/gramarye/Android	51ab0adb191273f9e28441724f1384c31779c125
+https://github.com/hacking-android/frameworks	943f0b4d46f72532a419fb6171e40d1c93984c8e
+https://github.com/hackthinking/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/haneul/appfence_dalvik	2c3266cde13b264bc638661aca6d5fa654f1d25f
+https://github.com/hankching/venee-ext	758b5b1c1d039ee315908358019e15f54b905f3c
+https://github.com/hankching/venee-lc	31d5b6e635fd5e2ed6c34e47fffee3509e98c11d
+https://github.com/heliumfire/androux_donut	d2dc30eb834ab020e9f39f4ec2619e9254e14620
+https://github.com/henry821/android_aosp_8.0	d4a08b5696e4f404c242ee87eb84eefd5defe419
+https://github.com/hronik1/my_fitsby_copy	67d3af3ee9b0a93bd6da9c11dac4239fbd48a450
+https://github.com/huanzheng/FlashVPN	0e214d8506c3f64e4cee36b3d194d8d1cc008c5b
+https://github.com/hugocm93/seguranca_g2	a6692d602874ba94fe49adfc453f8231367fd0dd
+https://github.com/hupeiice/OHA-Android-4.0.3_r1.0	f7044748eee0210f65c252ece2df3962bd9ebd43
+https://github.com/huqiucheng5/Android4.0.3	b65e143e586a2bb0118f9a1787eccd709a5fd992
+https://github.com/hustwei/android_tools	e7c89d51ca2121ceead0616b8ddf4b3673be05fe
+https://github.com/hyperdriveguy/androidx86_remix	a78e3614ca8d57c6157943c34e222a9ebf63da9a
+https://github.com/hzio/OpenJDK9	dfe9c5dfdb7ad0c3f86105585b232e8f713836c3
+https://github.com/ibmruntimes/openj9-openjdk-jdk14	7a36a3fd456d7da1763d3ebc98ba96304661ee24
+https://github.com/ichtrojan/in-the-box	820f1b45f9e84d9b2d06b8f973d5016acdda0590
+https://github.com/im-0/e3372h-kmods	e5f2da610abd78946786fdcd7ec6c744c9feb697
+https://github.com/imoseyon/leanKernel-d2usc-deprecated	a8bb00677bf1f75a41e680edb79bc8e2a7f2c4ef
+https://github.com/indashnet/InDashNet.Open.UN2000	d18b1395224e2afb004a5e6ff6c26561baa70abe
+https://github.com/infoud/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/iofisher/Android_SDK_Source	9f0a9d5fb449508559eb2dafff043e717447f3d6
+https://github.com/iroot/Wi-Fi-zvonki	d33c431bf4c26be8a3ba6c0bc2f2ad875cb589e5
+https://github.com/ismacasti/nokia6.1-source	f151bd04170b89e4756c69479ad8112c3a0f0cda
+https://github.com/itdog2019/sources	01d0cec9801852c7ed36e7684133be28cb2b4189
+https://github.com/ivanyshenko/graal	c8fed2dd49a2cf55898ce8950b257b728f57fefb
+https://github.com/j8unit-team/j8unit	2a662c5b2c4a40b6ac42d2b9f601cc4d586ce41c
+https://github.com/jackyh/qt210_ics_external_apache_harmony	d5737aab9de2aded7b5585c746bffb06ec7e888a
+https://github.com/jamesyan84/mt5507_android_4.4	880d4424989cf91f690ca187d6f0343df047da4f
+https://github.com/javafxports/openjdk-mobile	efbeadbafecd9b5c562f308c56837385eb3d85af
+https://github.com/javafxports/openjdk-mobile11	19fb8f93c59dfd791f62d41f332db9e306bc1422
+https://github.com/jefflovejapan/j2objc	b0499de1d2b0d997f6b55c8fbe876c6d3e4a7503
+https://github.com/jiankunking/openjdk11	eb5df9feeb1dd69548292bba7c74bd3f8714ac8d
+https://github.com/jinyuttt/DBRemote	96ffd7836258ff8565108b9f03cbe2be6beb7fa4
+https://github.com/johnwpoliver/Samsung-GT-P3113-AOSP-CM-Kernel-and-Ramdisk	977eac4315212869beacd7b78ab27945c4990584
+https://github.com/jpatel75/in-the-box	820f1b45f9e84d9b2d06b8f973d5016acdda0590
+https://github.com/juliodomingosalvador/in-the-box	2fa82dc22a6a29073cb5406e2b8b731267c014e0
+https://github.com/k0059/android422	da654b16dfe11db3317d6f240c3d8f812acbb7f4
+https://github.com/kant2016/Rosemary	57cdb30bb553fb29c28f49b8dfded0a896c505ab
+https://github.com/kapw001/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/kares/jruby-openssl	c862febc6a77d6aae708bce1564fba21810cbb44
+https://github.com/karunmatharu/Android-4.4-Pay-by-Data	fcb778e92d4aad525ef7a995660580f948d40bc9
+https://github.com/kateviolet/in-the-box	820f1b45f9e84d9b2d06b8f973d5016acdda0590
+https://github.com/katzenpost/katzenpost-android-mail	2f5ad451859b60acf405184deb5ef7b8e41747ab
+https://github.com/knewbury01/SnipPipeServe	f4b4c2417d0606f22d94b4aa2bf1e075ea89958b
+https://github.com/lamjar/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/lanyuwen/openweave-core	fbed1743a7b62657f5d310b98909c59474a6404d
+https://github.com/lems111/Intercept-CM6-Kernel	06b9428d41b0baff5ca2bb96d084b9a88d939e2b
+https://github.com/leotfrancisco/patch-hosting-for-android-x86-support	e932645af3ff9515bd152b124bb55479758c2344
+https://github.com/lexyan/shield-tv-kernel	c5900ea94732f550a0018029ecb7831183a0eb13
+https://github.com/likexi/hello	65510d84ed18652bec320bce2c1237238eae0f6d
+https://github.com/lingzhuzi/android-sources	26f46c6c8ababfef1cfc4ad5e72af440978066d0
+https://github.com/linuxandroidmcu/android	6ebfac58721e49cb0a6e143b32cfc3ad84801999
+https://github.com/liuhaozzu/openjdk10	4bddc1ceab92656329681385d88e389f317599d5
+https://github.com/longlinht/android_source	6a8b5d982137d02b6e181483e13971c1c7ed818a
+https://github.com/lorensoo/chromecast-mirrored-source.toolchain	4691ad865e0fced632358cbe02ec6b669447f3b8
+https://github.com/lucas847/AUTOdroid-Rockchip	c03058042bd1d1b8602125ba58750edee67488b3
+https://github.com/luchsh/bridgedCHeap	9201c0b9f97671338d3c942bfd22729820f0c152
+https://github.com/luis69fernando/Trabalho-Mobile-ECT-2019-	ecf361559ea0de9e660b9341ec30bfcbaa50ca66
+https://github.com/mainjavagmail/ecc	602fa86ecc29442597272b17836805a07b2f6764
+https://github.com/marcosandre98/Android	71365a606509107c8c37e29275ed266821600d16
+https://github.com/matcdac/jdk	2305df71d1b7710266ae0956d73927a225132c0f
+https://github.com/mateor/PDroidHistory	d10d6abc83ca0698a963fafcc1bf3bd534dafcda
+https://github.com/mateor/pdroid	e11dabd8cea2c923d1459588a2f9256ccb5540f3
+https://github.com/maxwellxy/firefly-rk3288_android_5.1_sdk	29b5dea38cb1c82ad40a995b7209f705fef2a281
+https://github.com/mdsalman729/droidtrack	2e535f81468da8d96c22303fc4f2592eb712b71a
+https://github.com/microvibe/vibe	4b3e2d5b5a6f3b3e021276b7e496d6eaa1b7b665
+https://github.com/minsoo/test_dalvik	0772546ae9e326e559c0bdd17063eb589b6542ac
+https://github.com/mirek190/x86-android-5.0	eb1029956682072bb7404192a80214189f0dc73b
+https://github.com/morrisvanegas/sdk	ce28e79f951621e510ffadabe6f07aba56f69144
+https://github.com/mythoi/openjdk	a5552ce78a16d7db22a3d10d0ce4c013fd2414a3
+https://github.com/mythwei/openjdk	bcc3cbd468c80160739230151d16df4dd98811ee
+https://github.com/nDroidProject/android_dalvik	32f8982116e5155bc3c75c86e3c861cab1a848be
+https://github.com/naosim/android-15	aa896e2e8ca7e839af48c4028c0d770655d0d974
+https://github.com/napile/napile.classpath	1599cba24d751a75dceefdabda0cce384e672ed8
+https://github.com/oknepal/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/openjdk/jdk11u	3428ac2f70b4ac979a9bba1b4d058bbc3dd4155c
+https://github.com/openjdk/panama-vector	d37985cd708f3c265c4095ae1acf97bb3d3c42bd
+https://github.com/openjdk/tsan	aeb549dc8e94985c411958d21f3648ecefe6d42e
+https://github.com/openweave/openweave-core	70a44a72ea67f84dd4b9f94856b7d6c9f46a6682
+https://github.com/oppo-source/Reno2_Z_9.0_kernel-source	92cdd9270096d62a19168b40d3db500de25d34cc
+https://github.com/ops-class/gcc	77b63a368989fab5b177eda47ce98340c41e653b
+https://github.com/oyhf521/cloud	b3116f2eeed3b14e97c3942ad120b6a8fa84bb8a
+https://github.com/pachpage/in-the-box	c4ef397135bdbea95537ebedbdb2e9bf2a754ad3
+https://github.com/pandalion98/SPay-inner-workings	6dd83a6ea0916c272423ea0dc1fa3757baa632e7
+https://github.com/panfudonan/mx535	1ffc16ea2d332c8ff08063c54f59c46a4f23f6ed
+https://github.com/pengaoao/pdroid	71a72bfbf8700c5f2abdd47caa024567df6964a8
+https://github.com/pmarches/bitcoop	5c70f4d7f2460db08d9d288ab8fbc190f43a870a
+https://github.com/pnhpostea/postea-kernels	3892b2c5ef0b4f718e9f0b85029ee13685005ce2
+https://github.com/qihao27/JDKBugRepo	f64a255a3a163d87e4cbf5c0b69011943cf3bcea
+https://github.com/qiyei2015/android_5.1_sdk	29a803102e4742713118f714423ee2119ed60902
+https://github.com/qqedfr/kitkat-2	51b1e812a87d8bf22a135df4f054a14d9c65f87b
+https://github.com/rayandrews/SCK-OpenJDK11	40935ab58a782ee788cdd9c09e7ad43490365b5f
+https://github.com/realthunder/hm_sun7i_android	a5a71080b3ff3dec917f9179184057a4c051543e
+https://github.com/rex-xxx/mt6572_x201	f87ef7407576b4fd190c76287e92b2e9886ca484
+https://github.com/ritesh96310/dexandroid	6bd3c2ac38887f76cca390ad8329cd619ded3ae7
+https://github.com/rmcc/android_dalvik	990e9d58f383a2636926b1c1a2d6c0957723ecb8
+https://github.com/rockduan/androidN-android-7.1.1_r28	10bab435cd61ffa2e93a20c082624954c757999d
+https://github.com/rrashbull/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/runtimeverification/property-db	ccbe0137ba949d474303a7ba48c9360dc37be888
+https://github.com/saadvarg/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/samm-git/e3372h-vendor-src	536b1180fd3262521a85c783ebaf37e7986b6e05
+https://github.com/sckalman/FBSDK	4c5df1e667567082e038ef47cd6c374dab38e4d4
+https://github.com/sdklite/Dalvik_CAR	c807deed520953d90c55b3ad6426d785a0388f80
+https://github.com/secure-device-onboard/cri	d4f28570040132cb33f0f2bc2e50375b7418cb73
+https://github.com/secure-device-onboard/iot-platform-sdk	cf7dd598961ff6bd1e0549a4a68ee6997f5c79dd
+https://github.com/sgs3/GT-I9305_Platform	9361398a70434dedae7130838516e1c8d89f6e1e
+https://github.com/shandanjay/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/silionXi/android-7.1.0_r1	e81b6cd496a285130b12d3d4266fc71db1cc3cbf
+https://github.com/simmel/cryptobice	ad8e874b7df1e29360a2b08dd36cfcfa052ec4e7
+https://github.com/simonxu14/workshop_Android	57fd722eb9b7f84197882c31165904361cf16948
+https://github.com/singzinc/sing-encrytion-showcase	22601844bb697826f63b0fa24f7773bc56ee9ac5
+https://github.com/sirinath/Harmony	724deb045a85b722c961d8b5a83ac7a697319441
+https://github.com/siyoutech/j2objc	63bb6b995f5b6adc858f999bcd93e413c1e62fd4
+https://github.com/sktechno/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/skyHALud/codenameone	f34440f80f64701080e3ca8f33e75d2c5a3e6c66
+https://github.com/snajdan0525/android-dalvik-sourcecode	a866eaf01ec9408ff142f555273cc64bbe548557
+https://github.com/songhaonangit/patch-hosting-for-android-x86-support	cfd927fa04ffd208bd4370373f84e94f28f1d5ca
+https://github.com/spartan263/vizio_oss-toolchain	e0d306a97f73e84cc868d10221837b8a6ebe4ff5
+https://github.com/stayboogy/ics_android	3bba954637d0c648fbc9a3f02f6bccc67520d0ed
+https://github.com/sujeesh1989/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/sunilyadav050585/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/syslover33/ctank	0148ba6a49a0f1e674fa23aaac9a6e87f5c51d6f
+https://github.com/taylorcfh/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/truedat101/fiskidagur	48dea2cc7378f7b1a3d06fad07835e052dc15748
+https://github.com/typelead/eta-java-interop	a9a8d857e6cec9094439da38b143832848fd431b
+https://github.com/uiabhishek15/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/unitedroad/harmony-for-haiku	c91603bb4677aeba623dd3727587a439802495b5
+https://github.com/unofficial-openjdk/openjdk	2b52c740a5422306136d48f83b942699367912b4
+https://github.com/vdjeudjam/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/viewsonic-vsi/ViewPad-7-Kernel	2c40be49f7ddb959cb5067271bad806e554ec9c4
+https://github.com/vinod-morya/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/vinothtimes/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/visht/in-the-box	899689e74493c965bfee888787a990f8cad443cc
+https://github.com/vitalyster/InTheBox	f415809864ae21d7c78ce7f86f1b992bfa4a1074
+https://github.com/wawawak/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/weimingtom/dalvik_cygwin_port	1d8cbb4525dd0ef41d840c1673f29688e3d1ba8c
+https://github.com/weissreto/what-is-new-in-java-9	958810b4837e7d4a267ab1894c6b1c846697fb74
+https://github.com/wqh0109663/openjdk-9-build-source	2a26e61759d748efe917abaa9917e117d83e0f85
+https://github.com/xdanushka/Fosmis.v.1.0.0	07cc7a15faf2d44ddca96e4def50ae3c320b937d
+https://github.com/xdtianyu/android-4.2_r1	bbf7345911f49df00a4d95964fac0d644afc7151
+https://github.com/xdtianyu/android-4.3	d035e0af4a5927f615a24780a547cf08a9f6f61e
+https://github.com/xdtianyu/android-4.4.4	ba5f52193d50b465d2bfb9a410c85041febe42f7
+https://github.com/xdtianyu/android-5.0.0_r5	d023f08717af9bb61c2a273a040d3986a87422e1
+https://github.com/xdtianyu/android-7.0.0_r1	4bd4cb29853dd0335dc1b04800bc5224f9232e31
+https://github.com/xdtianyu/android-8.0.0_r4	7b2a348b53815c068a960fe7243b9dc9ba144fa6
+https://github.com/xdtianyu/android_04.01.01_msm7627a	7eb0fe9ae86ca8ec0da2a2e34cb3fb1865d6e5f9
+https://github.com/xdtianyu/android_2.3.5	9174053315321d0907059f5474038d0b23c879f1
+https://github.com/xrd-lcd/A83T_soft	9649964ce948c2cdb17b08e07d8d961ddd36da86
+https://github.com/xuwakao/aosp_debug_host	5d5e16a00df97d45e267e33387f20b80fe705356
+https://github.com/yesimxev/android_device_nokia_ES2_sprout	b30731e2f1731250cdceb3da52e15b851d213d6b
+https://github.com/yooocen/openJDK9	2826d9eebf8056c82299d739c6e5ade94d2adb40
+https://github.com/yu-hfl/Android_SDK_Source	960a50901cf5258a7a1cf4682ba1cc713e9317be
+https://github.com/yuanhy0055/OyO	c353b7ace1b6274e7cf7eecdd23aee6764d756f2
+https://github.com/yugandhar56/in-the-box	e0e7a7e6e54e8e41749d61aee1d204457aa7bb51
+https://github.com/zcf9916/udax_wallet	f971679e9debc85908ed715ec6485c211137257b
+https://github.com/zhangfenglin/android-source	15f45552f4b14e3961fac5adb2cab77206d820f3
+https://github.com/zhangquanit/android6.0_source	e404732201d6daf82097151567fb461e3a8e345e
+https://github.com/zhxzhxustc/AFrame	e79bcf659660746b937fda182189cf928f84657a
diff --git a/docs/examples/wpi-many/securerandom.query b/docs/examples/wpi-many/securerandom.query
new file mode 100644
index 0000000..862986c
--- /dev/null
+++ b/docs/examples/wpi-many/securerandom.query
@@ -0,0 +1 @@
+"import java.security.SecureRandom;" -filename:*Test.java language:Java -user:kelloggm -user:AndroidSDKSources
\ No newline at end of file
diff --git a/docs/examples/wpi-many/wpi-many-custom-checker-example.sh b/docs/examples/wpi-many/wpi-many-custom-checker-example.sh
new file mode 100755
index 0000000..412f184
--- /dev/null
+++ b/docs/examples/wpi-many/wpi-many-custom-checker-example.sh
@@ -0,0 +1,99 @@
+#!/bin/sh
+
+## This is an executable example that runs wpi-many.sh with appropriate
+## arguments and environment variables, using a custom typechecker.
+## It exercises most of the features of the wpi-many.sh script.
+##
+## Rather than directly using this script, you should copy it and
+## then modify and run your copy. Every line of the script before
+## the line that says "There is no need to make changes below this point"
+## defines a variable (either an environment variable or a variable
+## used as input to wpi-many.sh). You should consider changing each
+## of these, to suit your use case.
+
+## Change these to match your system.
+
+export JAVA11_HOME=/usr/lib/jvm/java-11-openjdk/
+export JAVA8_HOME=/usr/lib/jvm/java-1.8.0-openjdk
+
+export ANDROID_HOME=${HOME}/compliance-experiments/fse20/android_home
+
+# This directory must contain a copy of the Checker Framework that has
+# been built from source.
+export CHECKERFRAMEWORK=${HOME}/jsr308/checker-framework
+
+## Change these to match your experimental setup.
+
+export PARENTDIR=${HOME}/compliance-experiments/fse20
+checker=org.checkerframework.checker.noliteral.NoLiteralChecker
+checkername=no-literal
+repolist=securerandom.list
+workingdir=$(pwd)
+timeout=3600 # 60 minutes
+
+# The stub files for the checker being used.
+custom_stubs=${PARENTDIR}/no-literal-checker/no-literal-checker/stubs
+
+# The checker classpath.  Paste in the result of running ./gradlew -q
+# printClasspath in the subproject of your custom checker with the
+# checker implementation.  If your custom checker does not define such
+# a task, you can define it:
+#
+# task printClasspath {
+#     doLast {
+#         println sourceSets.main.runtimeClasspath.asPath
+#     }
+# }
+#
+# If you are not using a custom typechecker (i.e. you are using a typechecker built into
+# the Checker Framework, such as the Nullness Checker), set this variable to the empty string
+# or comment out this line.
+checker_classpath='/homes/gws/kelloggm/compliance-experiments/fse20/no-literal-checker/no-literal-checker/build/classes/java/main:/homes/gws/kelloggm/compliance-experiments/fse20/no-literal-checker/no-literal-checker/build/resources/main:/homes/gws/kelloggm/compliance-experiments/fse20/checker-framework/checker/dist/checker.jar:/homes/gws/kelloggm/compliance-experiments/fse20/no-literal-checker/no-literal-qual/build/libs/no-literal-qual.jar:/homes/gws/kelloggm/.gradle/caches/modules-2/files-2.1/com.google.errorprone/javac/9+181-r4173-1/bdf4c0aa7d540ee1f7bf14d47447aea4bbf450c5/javac-9+181-r4173-1.jar:/homes/gws/kelloggm/.gradle/caches/modules-2/files-2.1/org.checkerframework/checker-qual/3.1.1/361404eff7f971a296020d47c928905b3b9c5b5f/checker-qual-3.1.1.jar'
+
+# The qualifier classpath. Usually, this is a subset of
+# checker_classpath that contains just two elements:
+#  * the qual jar for your checker, and
+#  * the version of checker-qual.jar that your qualifiers depend on.
+#
+# Like checker_classpath, this is usually generated using the printClasspath
+# task in the qualifier subproject of your custom checker, if it has one.
+#
+# If you are not using a custom typechecker (i.e. you are using a typechecker built into
+# the Checker Framework, such as the Nullness Checker), set this variable to the empty string
+## or comment out this line.
+qual_classpath='/homes/gws/kelloggm/compliance-experiments/fse20/no-literal-checker/no-literal-qual/build/libs/no-literal-qual.jar:/homes/gws/kelloggm/.gradle/caches/modules-2/files-2.1/org.checkerframework/checker-qual/3.1.1/361404eff7f971a296020d47c928905b3b9c5b5f/checker-qual-3.1.1.jar'
+
+## There is no need to make changes below this point.
+
+export JAVA_HOME=${JAVA11_HOME}
+repolistbase=$(basename "$repolist")
+
+# DLJC will fail if these arguments are passed to it with empty values.
+if [ ! "x${qual_classpath}" = "x" ]; then
+  quals_arg='yes'
+else
+  quals_arg=
+fi
+
+if [ ! "x${checker_classpath}" = "x" ]; then
+  lib_arg='yes'
+else
+  lib_arg=
+fi
+
+if [ ! "x${custom_stubs}" = "x" ]; then
+  stubs_arg='yes'
+fi
+
+## Code starts here.
+
+rm -rf "${checkername}-${repolistbase}-results"
+
+bash wpi-many.sh -o "${workingdir}/${checkername}-${repolistbase}" \
+     -i "${PARENTDIR}/${repolist}" \
+     -t ${timeout} \
+     -- \
+     --checker "${checker}" \
+     ${quals_arg:+--quals "${qual_classpath}"} \
+     ${lib_arg:+--lib "${checker_classpath}"} \
+     ${stubs_arg:+--stubs "${custom_stubs}"}
diff --git a/docs/logo/CFLogo.ai b/docs/logo/CFLogo.ai
new file mode 100644
index 0000000..a4d5f0e
--- /dev/null
+++ b/docs/logo/CFLogo.ai
Binary files differ
diff --git a/docs/logo/Checkmark/CFCheckmark.pdf b/docs/logo/Checkmark/CFCheckmark.pdf
new file mode 100644
index 0000000..12002e8
--- /dev/null
+++ b/docs/logo/Checkmark/CFCheckmark.pdf
Binary files differ
diff --git a/docs/logo/Checkmark/CFCheckmark.png b/docs/logo/Checkmark/CFCheckmark.png
new file mode 100644
index 0000000..5a3be6a
--- /dev/null
+++ b/docs/logo/Checkmark/CFCheckmark.png
Binary files differ
diff --git a/docs/logo/Checkmark/CFCheckmarkGrayscale.pdf b/docs/logo/Checkmark/CFCheckmarkGrayscale.pdf
new file mode 100644
index 0000000..fe4f7cd
--- /dev/null
+++ b/docs/logo/Checkmark/CFCheckmarkGrayscale.pdf
Binary files differ
diff --git a/docs/logo/Checkmark/CFCheckmarkHighRes.png b/docs/logo/Checkmark/CFCheckmarkHighRes.png
new file mode 100644
index 0000000..8931478
--- /dev/null
+++ b/docs/logo/Checkmark/CFCheckmarkHighRes.png
Binary files differ
diff --git a/docs/logo/Checkmark/CFCheckmark_favicon.png b/docs/logo/Checkmark/CFCheckmark_favicon.png
new file mode 100644
index 0000000..774a7fa
--- /dev/null
+++ b/docs/logo/Checkmark/CFCheckmark_favicon.png
Binary files differ
diff --git a/docs/logo/Checkmark/CFCheckmark_small.png b/docs/logo/Checkmark/CFCheckmark_small.png
new file mode 100644
index 0000000..1cd9ac3
--- /dev/null
+++ b/docs/logo/Checkmark/CFCheckmark_small.png
Binary files differ
diff --git a/docs/logo/Logo/CFLogo.pdf b/docs/logo/Logo/CFLogo.pdf
new file mode 100644
index 0000000..bb50120
--- /dev/null
+++ b/docs/logo/Logo/CFLogo.pdf
Binary files differ
diff --git a/docs/logo/Logo/CFLogo.png b/docs/logo/Logo/CFLogo.png
new file mode 100644
index 0000000..ddea37e
--- /dev/null
+++ b/docs/logo/Logo/CFLogo.png
Binary files differ
diff --git a/docs/logo/Logo/CFLogoGrayscale.pdf b/docs/logo/Logo/CFLogoGrayscale.pdf
new file mode 100644
index 0000000..c0687f9
--- /dev/null
+++ b/docs/logo/Logo/CFLogoGrayscale.pdf
Binary files differ
diff --git a/docs/logo/Logo/CFLogoHighRes.png b/docs/logo/Logo/CFLogoHighRes.png
new file mode 100644
index 0000000..918d9d3
--- /dev/null
+++ b/docs/logo/Logo/CFLogoHighRes.png
Binary files differ
diff --git a/docs/logo/Logo/CFLogoSmall.png b/docs/logo/Logo/CFLogoSmall.png
new file mode 100644
index 0000000..f019124
--- /dev/null
+++ b/docs/logo/Logo/CFLogoSmall.png
Binary files differ
diff --git a/docs/logo/Logo_on_dark/CFLogoGrayscale_on_dark.pdf b/docs/logo/Logo_on_dark/CFLogoGrayscale_on_dark.pdf
new file mode 100644
index 0000000..ea49123
--- /dev/null
+++ b/docs/logo/Logo_on_dark/CFLogoGrayscale_on_dark.pdf
Binary files differ
diff --git a/docs/logo/Logo_on_dark/CFLogoHighRes_on_dark.png b/docs/logo/Logo_on_dark/CFLogoHighRes_on_dark.png
new file mode 100644
index 0000000..cb00bf4
--- /dev/null
+++ b/docs/logo/Logo_on_dark/CFLogoHighRes_on_dark.png
Binary files differ
diff --git a/docs/logo/Logo_on_dark/CFLogoSmall_on_dark.png b/docs/logo/Logo_on_dark/CFLogoSmall_on_dark.png
new file mode 100644
index 0000000..bd2b3b2
--- /dev/null
+++ b/docs/logo/Logo_on_dark/CFLogoSmall_on_dark.png
Binary files differ
diff --git a/docs/logo/Logo_on_dark/CFLogo_on_dark.pdf b/docs/logo/Logo_on_dark/CFLogo_on_dark.pdf
new file mode 100644
index 0000000..bbec3b2
--- /dev/null
+++ b/docs/logo/Logo_on_dark/CFLogo_on_dark.pdf
Binary files differ
diff --git a/docs/logo/Logo_on_dark/CFLogo_on_dark.png b/docs/logo/Logo_on_dark/CFLogo_on_dark.png
new file mode 100644
index 0000000..a38c2c4
--- /dev/null
+++ b/docs/logo/Logo_on_dark/CFLogo_on_dark.png
Binary files differ
diff --git a/docs/logo/atT80-transparent.png b/docs/logo/atT80-transparent.png
new file mode 100644
index 0000000..d67ed69
--- /dev/null
+++ b/docs/logo/atT80-transparent.png
Binary files differ
diff --git a/docs/logo/github-typetools-logo.png b/docs/logo/github-typetools-logo.png
new file mode 100644
index 0000000..e5732bd
--- /dev/null
+++ b/docs/logo/github-typetools-logo.png
Binary files differ
diff --git a/docs/logo/github-typetools-logo.xcf b/docs/logo/github-typetools-logo.xcf
new file mode 100644
index 0000000..bae492b
--- /dev/null
+++ b/docs/logo/github-typetools-logo.xcf
Binary files differ
diff --git a/docs/manual/Makefile b/docs/manual/Makefile
new file mode 100644
index 0000000..4c38a17
--- /dev/null
+++ b/docs/manual/Makefile
@@ -0,0 +1,136 @@
+# Put user-specific changes in your own Makefile.user.
+# Make will silently continue if that file does not exist.
+-include Makefile.user
+
+# To regenerate this list, run:
+# ../../checker/bin-devel/.plume-scripts/latex-process-inputs -makefilelist manual.tex
+TEX_FILES = \
+manual.tex \
+manual-style.tex \
+introduction.tex \
+nullness-checker.tex \
+map-key-checker.tex \
+optional-checker.tex \
+interning-checker.tex \
+lock-checker.tex \
+index-checker.tex \
+called-methods-checker.tex \
+fenum-checker.tex \
+tainting-checker.tex \
+regex-checker.tex \
+formatter-checker.tex \
+i18n-format-checker.tex \
+propkey-checker.tex \
+signature-checker.tex \
+guieffect-checker.tex \
+units-checker.tex \
+signedness-checker.tex \
+purity-checker.tex \
+constant-value-checker.tex \
+returns-receiver-checker.tex \
+reflection-checker.tex \
+reflection-inference-rules.tex \
+initialized-fields-checker.tex \
+aliasing-checker.tex \
+subtyping-checker.tex \
+external-checkers.tex \
+typestate-checker.tex \
+generics.tex \
+advanced-features.tex \
+warnings.tex \
+inference.tex \
+annotating-libraries.tex \
+creating-a-checker.tex \
+accumulation-checker.tex \
+external-tools.tex \
+faq.tex \
+troubleshooting.tex \
+contributors.tex
+
+all: manual.pdf manual.html
+
+.PHONY: figures-all
+figures-all:
+	${MAKE} -C figures all
+
+manual.pdf: ${TEX_FILES} plume-bib-update figures-all check-labels
+	latexmk -silent -pdf -interaction=nonstopmode manual.tex || latexmk -gg -pdf -interaction=nonstopmode manual.tex
+
+html: manual.html
+manual.html: manual.pdf CFLogo.png favicon-checkerframework.png ../api
+	hevea -fix svg.hva -exec xxdate.exe manual.tex
+# I'm not sure why this is necessary; "hevea -fix" should run it automatically.
+# Also, you need ImageMagick 6.8.0-2 Beta or later to avoid a bug.
+	imagen manual
+	./add-favicon-to-manual
+# The following three lines are only necessary when using Hevea before version 2.04.
+# With version 2.04 they have no effect but do no harm.
+	\mv -f manual.html manual.html-with-htoc
+	./hevea-retarget-crossrefs < manual.html-with-htoc > manual.html
+	\rm -f manual.html-with-htoc
+# Add CSS styling, since \newstyle doesn't work for me.
+	sed -i -e "s%<style type=\"text/css\">%<style type=\"text/css\">\nimg { max-width: 100\%; max-height: 100\%; }%" manual.html
+# Add CSS styling for some links, since \ahrefloc doesn't permit styling
+	sed -i -e 's%\(<a href="#[^"]*"\)\(><span style="font-size:small">&\#X1F517;</span></a>\)%\1 style="color:inherit; text-decoration:none"\2%g' manual.html
+
+../../checker/bin-devel/.plume-scripts:
+	cd ../.. && ./gradlew getPlumeScripts -q
+
+.PHONY: contributors.tex
+contributors.tex:
+# Update plume-scripts even if it is already cloned
+	cd ../.. && ./gradlew getPlumeScripts -q
+	../../checker/bin-devel/.plume-scripts/git-authors --latex --punctuation > contributors.tex
+
+../api:
+	cd ../.. && ./gradlew allJavadoc
+
+CFLogo.png: ../logo/Logo/CFLogo.png
+	cp -p $< $@
+
+favicon-checkerframework.png: ../logo/Checkmark/CFCheckmark_favicon.png
+	cp -p $< $@
+
+# Don't use \section; use \sectionAndLabel instead
+.PHONY: check-labels
+check-labels:
+	if ( grep -n '^\\\(chapter\|\(sub\)*section\|paragraph\){' *.tex ) ; then false ; else true ; fi
+
+export BIBINPUTS = $BIBINPUTS:.:plume-bib
+plume-bib:
+	(git clone --depth 1 -q https://github.com/mernst/plume-bib.git || git clone --depth 1 -q https://github.com/mernst/plume-bib.git)
+.PHONY: plume-bib-update
+plume-bib-update: plume-bib
+# Even if this command fails, it does not terminate the make job.
+# However, to skip it, invoke make as:  make NOGIT=1 ...
+ifndef NOGIT
+	-(cd plume-bib && git pull && make)
+endif
+
+# Leaves manual.html, and .svg files that it references.
+clean:
+	@\rm -f *.aux *.blg *.dvi *.haux *.htoc *.idx *.ilg *.ind *.log *.out *.pdf *.ps *.toc manual.image.tex
+
+very_clean: clean
+	@\rm -f manual.html CFLogo.png
+
+.PHONY: checklink
+checklink:
+	checklink-via-http
+
+.PHONY: checklink-via-file
+checklink-via-file:
+	${CHECKLINK}/checklink -q -e `grep -v '^#' ${CHECKLINK}/checklink-args.txt` manual.html
+
+# Example invocation:
+#   CHECKLINK=$HOME/bin/src/checklink make checklink-via-http
+.PHONY: checklink-via-http
+checklink-via-http: manual.html
+	rm -rf ${HOME}/public_html/tmp-cf-manual
+	(cd .. && cp -prf manual ${HOME}/public_html/tmp-cf-manual)
+	${CHECKLINK}/checklink -q -e `grep -v '^#' ${CHECKLINK}/checklink-args.txt` https://homes.cs.washington.edu/~mernst/tmp-cf-manual/manual.html
+
+.PHONY: tags
+tags: TAGS
+TAGS: ${TEX_FILES}
+	etags ${TEX_FILES}
diff --git a/docs/manual/README b/docs/manual/README
new file mode 100644
index 0000000..9c001ca
--- /dev/null
+++ b/docs/manual/README
@@ -0,0 +1,3 @@
+On a standard Ubuntu 14.10 system, you will need to install some
+additional packages to build the manual.  See the "Building from source"
+section of the manual for that list.
diff --git a/docs/manual/accumulation-checker.tex b/docs/manual/accumulation-checker.tex
new file mode 100644
index 0000000..a9cc767
--- /dev/null
+++ b/docs/manual/accumulation-checker.tex
@@ -0,0 +1,114 @@
+\htmlhr
+\chapterAndLabel{Building an accumulation checker}{accumulation-checker}
+
+%% This chapter should appear after the "creating a checker" chapter, or perhaps as part of it,
+%% once accumulation support is complete.
+
+This chapter describes how to build a checker for an accumulation analysis.
+If you want to \emph{use} an existing checker, you do not need to read this chapter.
+
+An \emph{accumulation analysis} is a program analysis where the
+analysis abstraction is a monotonically increasing set --- that is, the
+analysis learns new facts, and facts are never retracted.
+Typically, some operation in code is legal
+only when the set is large enough --- that is, the estimate has accumulated
+sufficiently many facts.
+
+The Called Methods Checker (\chapterpageref{called-methods-checker})
+is an accumulation analysis.
+The
+\href{https://github.com/typetools/checker-framework/blob/master/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationChecker.java}{Test Accumulation Checker}
+is a simplified version of the Called Methods Checker that you can also use as a model.
+
+Accumulation analysis is a special case of typestate analysis in which
+(1) the order in which operations are performed does not affect what is subsequently legal,
+and (2) the accumulation does not add restrictions; that is, as
+more operations are performed, more operations become legal.
+Unlike a traditional typestate analysis, an accumulation analysis does
+not require an alias analysis for soundness. It can therefore be implemented
+as a flow-sensitive type system.
+
+The rest of this chapter assumes you have read how to create a checker
+(\chapterpageref{creating-a-checker}).
+
+
+\paragraphAndLabel{Defining type qualifiers}{accumulation-qualifiers}
+Define 2 or 3 type qualifiers.
+
+\begin{itemize}
+\item
+The ``accumulator'' type qualifier has a single argument: a \<String[]> named
+\<value> that defaults to the an empty array.  Note that the Checker Framework's
+support for accumulation analysis requires you to accumulate a string representation
+of whatever you are accumulating. For example, when accumulating which methods have
+been called, you might choose to accumulate method names.
+
+The accumulator
+qualifier should have no supertypes (\<@SubtypeOf({})>) and should
+be the default qualifier in the hierarchy (\<@DefaultQualifierInHierarchy>).
+
+An example of such a qualifier can be found in the Checker Framework's tests:
+\href{https://github.com/typetools/checker-framework/blob/master/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulation.java}{TestAccumulation.java}.
+
+\item
+Define a bottom type, analogous to
+\href{https://github.com/typetools/checker-framework/blob/master/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationBottom.java}{TestAccumulationBottom.java}.
+It should take no arguments, and should be a subtype of the accumulator type you defined earlier.
+
+\item
+Optionally, define a predicate annotation, analogous to
+\href{https://github.com/typetools/checker-framework/blob/master/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationPredicate.java}{TestAccumulationPredicate.java}.
+It must have a single argument named \<value> of type \<String>.
+The predicate syntax supports
+\begin{itemize}
+\item \<||> disjunctions
+\item \<\&\&> conjunctions
+\item \<!> logical complement.  \<"!x"> means
+``it is not true that \<x> was definitely accumulated'' or, equivalently, ``there is no path on which \<x> was accumulated''.
+Note that this does \textbf{not} mean ``\<x> was not accumulated'' --- it is not a violation of the specification \<"!x"> if \<x> is accumulated
+on some paths, but not others.
+\item \<(...)> parentheses for precedence
+\end{itemize}
+
+\end{itemize}
+
+\paragraphAndLabel{Setting up the checker}{accumulation-setup}
+
+Define a new class that extends \refclass{common/accumulation}{AccumulationChecker}.
+It does not need any content.
+
+Define a new class that extends \refclass{common/accumulation}{AccumulationAnnotatedTypeFactory}.
+You must create a new constructor whose only argument is a \refclass{common/basetype}{BaseTypeChecker}.
+Your constructor should call one of the \<super> constructors defined in
+\refclass{common/accumulation}{AccumulationAnnotatedTypeFactory} (which one depends on whether or not
+you defined a predicate annotation).
+
+
+\paragraphAndLabel{Adding accumulation logic}{accumulation-accumulating}
+
+Define a class that extends \refclass{common/accumulation}{AccumulationTransfer}.
+To update the estimate of what has been accumulated, override some method in
+\refclass{framework/flow}{CFAbstractTransfer} to call
+\refmethodterse{common/accumulation}{AccumulationTransfer}{accumulate}{-org.checkerframework.dataflow.cfg.node.Node-org.checkerframework.dataflow.analysis.TransferResult-java.lang.String...-}.
+
+For example, to accumulate the names of methods called, a checker would override
+\refmethod{framework/flow}{CFAbstractTransfer}{visitMethodInvocation}{-org.checkerframework.dataflow.cfg.node.MethodInvocationNode-org.checkerframework.dataflow.analysis.TransferInput-} to:
+call \<super> to get a \<TransferResult>, compute the method name from the \<MethodInvocationNode>,
+and then call \<accumulate>.
+
+
+\paragraphAndLabel{Enforcing program properties}{accumulation-enforcing}
+
+At this point, your checker ensures that all annotations are consistent
+with one another and the souce code, and it flow-sensitively refines the
+annotations.
+Te enforce properties in the code being type-checked, write type rules
+(Section~\ref{creating-extending-visitor}) that are specific to your type
+system.
+
+
+% LocalWords:  SubtypeOf TestAccumulation TestAccumulationBottom
+% LocalWords:  TestAccumulationPredicate AccumulationChecker
+% LocalWords:  AccumulationAnnotatedTypeFactory BaseTypeChecker
+% LocalWords:  AccumulationTransfer CFAbstractTransfer TransferResult
+% LocalWords:  visitMethodInvocation MethodInvocationNode
diff --git a/docs/manual/add-favicon-to-manual b/docs/manual/add-favicon-to-manual
new file mode 100755
index 0000000..bb6c592
--- /dev/null
+++ b/docs/manual/add-favicon-to-manual
@@ -0,0 +1,7 @@
+# Similar to ../logo/html-add-favicon, but works only on a single file: manual.html
+# The reason we don't just fix ../logo/html-add-favicon to work on a single file instead
+# of creating this special-purpose script is that any changes we make to ../logo/html-add-favicon
+# would promptly be overwritten the next time it is updated from the plume-lib repository.
+# Likewise any changes made to html-add-favicon in our clone of the plume-lib repository would
+# be overwritten once we refreshed it from its upstream repository.
+sed -i -e " s%<head>\$%<head><link rel=\"icon\" href=\"favicon-checkerframework.png\" type=\"image/png\"/>%" manual.html
diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex
new file mode 100644
index 0000000..a25f391
--- /dev/null
+++ b/docs/manual/advanced-features.tex
@@ -0,0 +1,1578 @@
+\htmlhr
+\chapterAndLabel{Advanced type system features}{advanced-type-system-features}
+
+This chapter describes features that are automatically supported by every
+checker written with the Checker Framework.
+You may wish to skim or skip this chapter on first reading.  After you have
+used a checker for a little while and want to be able to express more
+sophisticated and useful types, or to understand more about how the Checker
+Framework works, you can return to it.
+
+
+\sectionAndLabel{Invariant array types}{invariant-arrays}
+
+Java's type system is unsound with respect to arrays.  That is, the Java
+type-checker approves code that is unsafe and will cause a run-time crash.
+Technically, the problem is that Java has ``covariant array types'', such
+as treating \<String[]> as a subtype of \<Object[]>.  Consider the
+following example:
+
+\begin{Verbatim}
+  String[] strings = new String[] {"hello"};
+  Object[] objects = strings;
+  objects[0] = new Object();
+  String myString = strs[0];
+\end{Verbatim}
+
+\noindent
+The above code puts an \<Object> in the array \<strings> and thence in
+\<myString>, even though \<myString = new Object()> should be, and is,
+rejected by the Java type system.  Java prevents corruption of the JVM by
+doing a costly run-time check at every array assignment; nonetheless, it is
+undesirable to learn about a type error only via a run-time crash rather
+than at compile time.
+
+When you pass the \<-AinvariantArrays> command-line option,
+the Checker Framework is stricter than Java, in the sense that it treats
+arrays invariantly rather than covariantly.  This means that a type system
+built upon the Checker Framework is sound:  you get a compile-time
+guarantee without the need for any run-time checks.  But it also means that
+the Checker Framework rejects code that is similar to what Java unsoundly
+accepts.  The guarantee and the compile-time checks are about your
+extended type system.  The Checker Framework does not reject the example
+code above, which contains no type annotations.
+
+Java's covariant array typing is sound if the array is used in a read-only
+fashion:  that is, if the array's elements are accessed but the array is
+not modified.  However, facts about read-only usage are not built into any of
+the type-checkers.  Therefore, when using type systems
+along with \<-AinvariantArrays>, you will need to suppress any warnings that
+are false positives because the array is treated in a read-only way.
+
+
+\sectionAndLabel{Context-sensitive type inference for array constructors}{array-context-sensitive}
+
+When you write an expression, the Checker Framework gives it the most
+precise possible type, depending on the particular expression or value.
+For example, when using the Regex Checker (\chapterpageref{regex-checker}),
+the string \<"hello"> is given type \<@Regex String> because it is a legal
+regular expression (whether it is meant to be used as one or not) and the
+string \<"(foo"> is given the type \<@RegexBottom String> because it is not
+a legal regular expression.
+
+Array constructors work differently.  When you create an array with the
+array constructor syntax, such as the right-hand side of this assignment:
+
+\begin{Verbatim}
+String[] myStrings = {"hello"};
+\end{Verbatim}
+
+\noindent
+then the expression does not get the most precise possible type, because
+doing so could cause inconvenience.  Rather, its type is determined by the
+context in which it is used:  the left-hand side if it is in an assignment,
+the declared formal parameter type if it is in a method call, etc.
+
+In particular, if the expression \verb|{"hello"}| were given the type
+\<@Regex String[]>, then the assignment would be illegal!  But the Checker
+Framework gives the type \<String[]> based on the assignment context, so the code
+type-checks.
+
+If you prefer a specific type for a constructed array, you can indicate
+that either in the context (change the declaration of \<myStrings>) or in a
+\<new> construct (change the expression to \<new @Regex String[] >\verb|{"hello"}|).
+
+
+\sectionAndLabel{Upper bound of qualifiers on uses of a given type (annotations on a class declaration)}{upper-bound-for-use}
+
+The examples in this section use the type qualifier hierarchy \code{@A :> @B :> @C}.
+
+A qualifier on a use of a certain type must be a subtype or equal to the upper bound for that type.
+The upper bound of qualifiers used on a given type is specified by annotating the type declaration
+with some qualifier --- that is, by writing an annotation on a class declaration.
+\begin{Verbatim}
+  @C class MyClass {}
+\end{Verbatim}
+
+This means that \<@B MyClass> is an invalid type.  (Annotations on class declarations may also specify
+default annotations for uses of the type; see Section~\ref{default-for-use})
+
+If it is not possible to annotate the class's definition (e.g., for
+primitives and some library classes),
+the type-system designer can specify an upper bound by using the meta-annotation
+\refqualclass{framework/qual}{UpperBoundFor}.
+
+If no annotation is present on a type declaration and if no \<@UpperBoundFor> mentions the type, then
+the bound is top. This can be changed by overriding
+\refmethodanchortext{framework/type}{AnnotatedTypeFactory}{getTypeDeclarationBounds}{-javax.lang.model.type.TypeMirror-}{AnnotatedTypeFactory\#getTypeDeclarationBounds}.
+
+There are two exceptions.
+\begin{itemize}
+  \item
+  An expression can have a supertype of the upper bound; that is, some expression could
+  have type \<@B MyClass>.  This type is not written explicitly, but results from viewpoint adaptation.
+  \item
+  Using usual CLIMB-to-top rules, local variables of type MyClass default to \<@A MyClass>.
+  It is legal for \<@A MyClass> to be the type of a local variable.
+  For consistency, users are allowed to write such a type on a local variable declaration.
+\end{itemize}
+
+Due to existing type rules, an expression of type \<@A MyClass> can only be used in limited ways.
+\begin{itemize}
+  \item
+  Since every field, formal parameter, and return type of type MyClass (or lower) is annotated as
+  \<@B> (or lower), it cannot be assigned to a field, passed to a method, or returned from a method.
+  \item
+  It can be used in a context that requires \<@A Object> (or whatever the least supertype is of MyClass
+  for which the \<@A> qualifier is permitted).  Examples include being tested against \<null> or
+  (for most type systems) being passed to polymorphic routines such as \<System.out.println> or \<System.identityHashCode>.
+\end{itemize}
+
+These operations might refine its type.  If a user wishes to annotate a method that does type refinement,
+its formal parameter must be of illegal type \<@A MyClass>, which requires a warning suppression.
+
+If the framework were to forbid expressions and local variables from having types inconsistent with the class annotation,
+then important APIs and common coding paradigms would no longer type-check.
+
+Consider the annotation
+\begin{Verbatim}
+  @NonNull class Optional { ... }
+\end{Verbatim}
+and the client code
+
+\begin{Verbatim}
+  Map<String, Optional> m;
+  String key = ...;
+  Optional value = m.get(key);
+  if (value != null) {
+    ...
+  }
+\end{Verbatim}
+
+The type of \<m.get(key)> is \<@Nullable Optional>, which is an illegal type.
+However, this is a very common paradigm.  Programmers should not need to rewrite the code to test
+\<m.containsKey(key)> nor suppress a warning in this safe code.
+
+
+\sectionAndLabel{The effective qualifier on a type (defaults and inference)}{effective-qualifier}
+
+A checker sometimes treats a type as having a slightly different qualifier
+than what is written on the type --- especially if the programmer wrote no
+qualifier at all.
+Most readers can skip this section on first reading, because you will
+probably find the system simply ``does what you mean'', without forcing
+you to write too many qualifiers in your program.
+particular, programmers rarely write qualifiers in method bodies (except occasionally on
+type arguments and array component types).
+
+The following steps determine the effective
+qualifier on a type --- the qualifier that the checkers treat as being present.
+
+\begin{enumerate}
+\item
+  If a type qualifier is present in the source code, that qualifier is used.
+
+\item
+  If there is no explicit qualifier on a type, then a default
+  qualifier
+  % except for type parameters, but don't clutter this text with that detail
+  is applied; see Section~\ref{defaults}. Defaulted qualifiers are treated by checkers
+  exactly as if the programmer had written them explicitly.
+
+\item
+  The type system may refine a qualified type on a local variable --- that
+  is, treat it as a subtype of how it was declared or defaulted.  This
+  refinement is always sound and has the effect of eliminating false
+  positive error messages.  See Section~\ref{type-refinement}.
+
+\end{enumerate}
+
+%TODO: Where does @QualifierForLiterals go?
+
+\sectionAndLabel{Default qualifier for unannotated types}{defaults}
+
+An unannotated
+Java type is treated as if it had a default annotation.
+Both the type system designer and an end-user programmer can control the defaulting.
+Defaulting never applies to uses of type variables, even if they do not
+have an explicit type annotation.
+% TODO: If the type of a local variable is a use of a type parameter, then it is defaulted to top,
+% so it can be refined. I don't know that's worth mentioning here.  Maybe just change never to does not?
+Most of this section is about defaults for source code that is read by
+the compiler.  When the compiler reads a \<.class> file, different
+defaulting rules apply.
+See Section~\ref{defaults-classfile} for these rules.
+
+There are several defaulting mechanisms, for convenience and flexibility.
+When determining the default qualifier for a use of an unannotated type, \<MyClass>, the following
+rules are used in order, until one applies.
+%TODO: I don't think this is true.(Some of these currently do not work in stub files.)
+
+\begin{enumerate}
+\item
+  The qualifier specified via \<@DefaultQualifierForUse> on the declaration of \<MyClass>.
+  (Section~\ref{default-for-use})
+\item
+  If no \<@NoDefaultQualifierForUse> is written on the declaration of \<MyClass>, the qualifier
+  explicitly written on the declaration of \<MyClass>. (Section~\ref{default-for-use})
+\item
+  The qualifier with a meta-annotation \<@DefaultFor(types = MyClass.class)>. (Section~\ref{default-for-use})
+\item
+  The qualifier with a meta-annotation \<@DefaultFor(typeKinds = KIND)>, where \<KIND> is the
+  \<TypeKind> of \<MyClass>. (Section~\ref{default-for-use})
+\item
+  The qualifier with a meta-annotation \<@DefaultFor(names = REGEX)>, where \<REGEX>
+  matches the name of the variable being defined (if any).  For return types, the  the name of
+  the method is used. (Section~\ref{default-for-use})
+\item
+  The qualifier in the innermost user-written \<@DefaultQualifier> for the location of the use of \<MyClass>.
+  (Section~\ref{default-qualifier})
+\item
+  The qualifier in the meta-annotation \<@DefaultFor> for the location of the use of \<MyClass>.
+  These are defaults specified by the type system designer (Section~\ref{creating-typesystem-defaults});
+  this is usually CLIMB-to-top (Section~\ref{climb-to-top}).
+\item
+  The qualifier with the meta-annotation \refqualclass{framework/qual}{DefaultQualifierInHierarchy}.
+\end{enumerate}
+
+If the unannotated type is the type of a local variable, then the first 5 rules are skipped and only
+rules 6 and 7 apply. If rule 6 applies, it makes the type of local variables top so they can be refined.
+
+% (Implementation detail:  setting defaults is implemented by the
+% \refclass{framework/util}{QualifierDefaults} class.)
+
+\subsectionAndLabel{Default for use of a type}{default-for-use}
+The type declaration annotation \refqualclass{framework/qual}{DefaultQualifierForUse}
+indicates that the specified qualifier should be added to all unannotated uses of the type.
+
+For example:
+\begin{Verbatim}
+@DefaultQualifierForUse(B.class)
+class MyClass {}
+\end{Verbatim}
+
+This means any unannotated use of \<MyClass> is treated as \<@B MyClass> by the checker.
+(Except for locals, which can be refined.)
+
+Similarly, the meta-annotation \refqualclass{framework/qual}{DefaultFor} can be used to specify defaults
+for uses of of types, using the \<types> element, or type kinds, using the \<typeKinds> elements.
+
+Interaction between qualifier bounds and \<DefaultQualifierForUse>:
+\begin{itemize}
+\item
+  If a type declaration is annotated with a qualifier bound, but not a \<@DefaultQualifierForUse>,
+  then the qualifier bound is added to all unannotated uses of that type (except locals).
+  For example, \<@C class MyClass {}> is equivalent to
+\begin{Verbatim}
+@DefaultQualifierForUse(C.class)
+@C class MyClass {}
+\end{Verbatim}
+
+\item
+  If the qualifier bound should not be added to all unannotated uses, then
+  \refqualclass{framework/qual}{NoDefaultQualifierForUse} should be written on the declaration:
+\begin{Verbatim}
+@NoDefaultQualifierForUse
+@C class MyClass {}
+\end{Verbatim}
+  This means that unannotated uses of MyClass are defaulted normally.
+\item
+  If neither \<@DefaultQualifierForUse> nor a qualifier bound is present on a type declaration, that
+  is equivalent to writing \<@NoDefaultQualifierForUse>.
+
+\end{itemize}
+
+\subsectionAndLabel{Controlling defaults in source code}{default-qualifier}
+The end-user programmer specifies a default qualifier by writing the
+\refqualclass{framework/qual}{DefaultQualifier}\code{(\emph{ClassName}, }[\emph{locations}]\<)>
+annotation on a package, class, method, or variable declaration.  The
+argument to \refqualclass{framework/qual}{DefaultQualifier} is the \code{Class}
+name of an annotation.
+The optional second argument indicates where the default
+applies.  If the second argument is omitted, the specified annotation is
+the default in all locations.  See the Javadoc of \refclass{framework/qual}{DefaultQualifier} for details.
+
+For example, using the Nullness type system (Chapter~\ref{nullness-checker}):
+
+\begin{Verbatim}
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+@DefaultQualifier(NonNull.class)
+class MyClass {
+
+  public boolean compile(File myFile) { // myFile has type "@NonNull File"
+    if (!myFile.exists())          // no warning: myFile is non-null
+      ...
+    @Nullable File srcPath = ...;  // must annotate to specify "@Nullable File"
+    if (srcPath.exists())          // warning: srcPath might be null
+      ...
+  }
+
+  @DefaultQualifier(Tainted.class)
+  public boolean isJavaFile(File myfile) {  // myFile has type "@Tainted File"
+    ...
+  }
+}
+\end{Verbatim}
+
+You may write multiple
+\refqualclass{framework/qual}{DefaultQualifier} annotations at a single location.
+
+If \code{@DefaultQualifier}[\code{s}] is placed on a package (via the
+\<package-info.java> file), then it applies to the given package \emph{and}
+all subpackages.
+% This is slightly at odds with Java's treatment of packages of different
+% names as essentially unrelated, but is more intuitive and useful.
+
+
+%% Don't even bother to bring this up; it will just sow confusion without
+%% being helpful.
+% For some type systems, a user may not specify a default qualifier, or doing
+% so prevents giving any other qualifier to any reference.  This is a
+% consequence of the design of the type system; see
+% Section~\ref{bottom-and-top-qualifier}.
+
+
+%When a programmer omits an \<extends> clause at a declaration of a type
+%parameter, the implicit upper bound is defaulted to the top qualifier; see
+%Section~\ref{climb-to-top}.
+
+
+\subsectionAndLabel{Defaulting rules and CLIMB-to-top}{climb-to-top}
+
+Each type system defines a default qualifier (see
+Section~\ref{creating-typesystem-defaults}).  For example, the default
+qualifier for the Nullness Checker is
+\refqualclass{checker/nullness/qual}{NonNull}.  When a user
+writes an unqualified type such as \<Date>, the Nullness Checker interprets it as
+\<@NonNull Date>.
+
+The type system applies that default qualifier to most but
+not all type uses.  In particular, unless otherwise stated, every type system
+uses the CLIMB-to-top rule.  This
+rule states that the \emph{top} qualifier in the hierarchy is the default for
+the CLIMB locations:  \textbf{C}asts, \textbf{L}ocals,
+and (some) \textbf{Im}plicit \textbf{B}ounds.
+For example, when the user writes an unqualified type such as \<Date> in such a
+location, the Nullness Checker interprets it as \<@Nullable Date> (because
+\refqualclass{checker/nullness/qual}{Nullable} is the top qualifier in the
+hierarchy, see Figure~\ref{fig-nullness-hierarchy}).
+(Casts are treated a bit specially; see below.)
+
+% TODO:  Add wildcard bounds to the set of type-refined locations?
+% Especially in light of
+% https://github.com/typetools/checker-framework/issues/260
+
+The CLIMB-to-top rule is used only for unannotated source code that is
+being processed by a checker.  For unannotated libraries (code read by the
+compiler in \<.class> or \<.jar> form), see Section~\ref{defaults-classfile}.
+
+The rest of this section explains the rationale and implementation of
+CLIMB-to-top.
+
+Here is the rationale for CLIMB-to-top:
+
+\begin{itemize}
+\item
+Local variables are defaulted to top because type refinement
+(Section~\ref{type-refinement}) is applied to local variables.  If a local
+variable starts as the top type, then the Checker Framework refines it to
+the best (most specific) possible type based on assignments to it.  As a
+result, a programmer rarely writes an explicit annotation on any of those
+locations.
+
+Variables defaulted to top include local variables, resource variables in the
+try-with-resources construct, variables in \<for> statements, and \<catch>
+arguments (known as exception parameters in the Java Language Specification).
+
+Exception parameters default to the top type because they might catch an
+exception thrown anywhere in the program.
+
+An alternate design for exception parameters would be to default exception
+parameters some other type T (instead of the top type); then the Checker
+Framework would need to issue a warning at every \<throw> statement whose
+argument might not be a subtype of T\@.  A checker can implement this
+alternate design by overriding a few methods.  The alternative is not
+appropriate for all type systems.  The alternative is unsound for deep type
+systems because the JDK's annotations are trusted rather than checked.  A
+deep type system is one where the type of a field can determine the type of
+its containing instance, such as tainting, Example: a user passes a secret
+regex to the JDK, and the JDK throws a format exception that includes the
+regex.  This could be caught by a \<catch> clause in the program whose
+exception parameter is not annotated as secret.  As another example, the
+user passes a secret integer and the JDK throws a DivideByZeroException
+that reveals the value.
+
+% (An even more precise analysis would be for the Checker Framework to
+% compute the feasibility of every pair of $\langle$throw statement, catch
+% clauses$\rangle$ in the program, but such an analysis might be expensive
+% or non-modular, and it still does not address the issue of unchecked
+% library methods.)
+
+\item
+Cast types are defaulted to the same type as their argument expression.
+This has the same effect as if they were given the
+top type and then flow-sensitively refined to the type of their argument.
+However, note that programmer-written type qualifiers are \emph{not}
+refined, so writing the top annotation is not the same as writing no annotation.
+% Programmer-written type qualifiers could be refined, but it has never
+% been a priority.  The code that defaults casts in this manner predates
+% the dataflow framework and we never reimplemented casts in the dataflow
+% framework.
+
+\item
+Implicit upper bounds are defaulted to top to allow them to be instantiated
+in any way.  If a user declared \code{class C<T> \ttlcb\ ...\ \ttrcb}, then
+the Checker Framework assumes that the user intended to allow any instantiation of the class,
+and the declaration is interpreted as \code{class C<T extends @Nullable
+  Object> \ttlcb\ ...\ \ttrcb} rather than as \code{class C<T extends
+  @NonNull Object> \ttlcb\ ...\ \ttrcb}.  The latter would forbid
+instantiations such as \code{C<@Nullable String>}, or would require
+rewriting of code.  On the other hand, if a user writes an explicit bound
+such as \code{class C<T extends D> \ttlcb\ ...\ \ttrcb}, then the user
+intends some restriction on instantiation and can write a qualifier on the
+upper bound as desired.
+
+This rule means that the upper bound of \code{class C<T>} is defaulted
+differently than the upper bound of \code{class C<T extends Object>}.
+This is a bit unfortunate, but it is the least bad option.  The
+more confusing alternative would be for ``\code{Object}'' to be defaulted
+differently in \code{class C<T extends Object>} and in an
+instantiation \code{C<Object>}, and for the upper bounds to be defaulted
+differently in \code{class C<T extends Object>}
+and \code{class C<T extends Date>}.
+
+\item
+Implicit \emph{lower} bounds are defaulted to the bottom type, again to allow
+maximal instantiation.  Note that Java does not allow a programmer to
+express both the upper and lower bounds of a type, but the Checker
+Framework allows the programmer to specify either or both;
+see Section~\ref{generics-defaults}.
+
+\end{itemize}
+
+A \refqualclass{framework/qual}{DefaultQualifier} that specifies a
+CLIMB-to-top location takes precedence over the CLIMB-to-top rule.
+
+Here is how the Nullness Checker overrides part of the CLIMB-to-top rule:
+
+\begin{Verbatim}
+@DefaultQualifierInHierarchy
+@DefaultFor({ TypeUseLocation.EXCEPTION_PARAMETER })
+public @interface NonNull {}
+
+public @interface Nullable {}
+\end{Verbatim}
+
+\noindent
+ As mentioned above, the exception parameters are always non-null, so
+\<@DefaultFor(\{ TypeUseLocation.EXCEPTION\_PARAMETER \})> on \<@NonNull> overrides
+the CLIMB-to-top rule.
+
+
+\subsectionAndLabel{Inherited defaults}{inherited-defaults}
+
+When overriding a method, programmers must fully specify types in the overridding
+method, which duplicates information on the overridden method.
+By contrast, declaration annotations that are meta-annotated with
+\<@InheritedAnnotation> are inherited by overriding methods.
+
+An example for type annotations is that when definining an \<equals()>
+method, programmers must write the type annotation \<@Nullable>:
+
+\begin{Verbatim}
+  public boolean equals(@Nullable Object obj) {
+      ...
+  }
+\end{Verbatim}
+
+An alternate design would be for every annotation on a superclass member to
+to be automatically inherited by subclasses that override it.
+
+The alternate design would reduce annotation effort.
+
+The alternate design would reduce program comprehensibility.
+Currently, a user can determine the annotation on a parameter or return
+value by looking at a single file.  If annotations could be inherited from
+supertypes, then a user would have to examine all supertypes, and do
+computations over them, to understand the meaning of an unannotated type in
+a given file.
+For declaration annotations, no computation is necessary; that is why they
+may be inherited.
+Computation is necessary for type annotations because different annotations might be inherited
+from a supertype and an interface, or from two interfaces.  For return
+types, the inherited type should be the least upper bound of all
+annotations on overridden implementations in supertypes.  For method
+parameters, the inherited type should be the greatest lower bound of all
+annotations on overridden implementations in supertypes.  In each case, the
+Checker Framework would need to issue an error if no such annotations existed.
+
+Because a program is read more often than it is edited/annotated, the
+Checker Framework does not currently support the alternate design.  In the
+future, this feature may be added.
+
+
+\subsectionAndLabel{Inherited wildcard annotations}{inherited-wildcard-annotations}
+
+If a wildcard is unbounded and has no annotation (e.g. \code{List<?>}),
+the annotations on the wildcard's bounds are copied from the type parameter
+to which the wildcard is an argument.
+
+For example, the two wildcards in
+the declarations below are equivalent.
+
+\begin{Verbatim}
+class MyList<@Nullable T extends @Nullable Object> {}
+
+MyList<?> listOfNullables;
+MyList<@Nullable ? extends @Nullable Object> listOfNullables;
+\end{Verbatim}
+
+The Checker Framework copies
+these annotations because wildcards must be within the bounds of their
+corresponding type parameter.
+By contrast, if the bounds of a wildcard
+were defaulted differently from the bounds of its corresponding type
+parameter, then there would be many false positive
+\code{type.argument} warnings.
+
+Here is another example of two equivalent wildcard declarations:
+
+\begin{Verbatim}
+class MyList<@Regex(5) T extends @Regex(1) Object> {}
+
+MyList<?> listOfRegexes;
+MyList<@Regex(5) ? extends @Regex(1) Object> listOfRegexes;
+\end{Verbatim}
+
+Note, this copying of annotations for a wildcard's bounds applies only to
+unbounded wildcards.  The two wildcards in the
+following example are equivalent.
+
+\begin{Verbatim}
+class MyList<@NonNull T extends @Nullable Object> {}
+
+MyList<? extends Object> listOfNonNulls;
+MyList<@NonNull ? extends @NonNull Object> listOfNonNulls2;
+\end{Verbatim}
+
+Note, the upper bound of the wildcard \code{?~extends Object} is defaulted to
+\code{@NonNull} using the CLIMB-to-top rule (see Section~\ref{climb-to-top}).
+Also note that the \code{MyList} class declaration could have been more succinctly
+written as: \code{class MyList<T extends @Nullable Object>} where the lower bound
+is implicitly the bottom annotation: \code {@NonNull}.
+
+\subsectionAndLabel{Default qualifiers for \<.class> files (library defaults)}{defaults-classfile}
+
+(\emph{Note:} Currently, the conservative library defaults presented in this section
+are off by default and can be turned on by supplying the \<-AuseConservativeDefaultsForUncheckedCode=bytecode>
+command-line option.  In a future release, they will be turned on
+by default and it will be possible to turn them off by supplying a
+\<-AuseConservativeDefaultsForUncheckedCode=-bytecode> command-line option.)
+
+The defaulting rules presented so far apply to source code that is read by
+the compiler.  When the compiler reads a \<.class> file, different
+defaulting rules apply.
+
+If the checker was run during the compiler execution that created the
+\<.class> file,
+%% This caveat is true, but it's a distraction at this point in the manual.
+%% Also, not having a top and a bottom qualifier is an uncommon case.
+% (and the qualifier hierarchy has both a top and a bottom
+% qualifier, see Section~\ref{bottom-and-top-qualifier}),
+then there is no need for
+defaults:  the \<.class> file has an explicit qualifier at each type use.
+(Furthermore, unless warnings were suppressed, those qualifiers are
+guaranteed to be correct.)
+When you are performing pluggable type-checking,
+it is best to ensure that the compiler only reads such \<.class> files.
+Section~\ref{compiling-libraries} discusses how to create annotated
+libraries.
+%% True, but not relevant to the point of this paragraph.
+% , even if the library source code is only partially annotated.
+
+If the checker was not run during the compiler execution that created the
+\<.class> file, then the \<.class> file contains only the type qualifiers
+that the programmer wrote explicitly.  (Furthermore, there is no guarantee
+that these qualifiers are correct, since they have not been checked.)
+In this case, each checker decides what qualifier to use for the
+locations where the programmer did not write an annotation.  Unless otherwise noted, the
+choice is:
+
+\begin{itemize}
+\item
+  For method parameters and lower bounds, use the bottom qualifier (see
+  Section~\ref{creating-bottom-qualifier}).
+\item
+  For method return values, fields, and upper bounds, use the top qualifier (see
+  Section~\ref{creating-top-qualifier}).
+\end{itemize}
+
+These choices are conservative.  They are likely to cause many
+false-positive type-checking errors, which will help you to know which
+library methods need annotations.  You can then write those library
+annotations (see Chapter~\ref{annotating-libraries}) or alternately
+suppress the warnings (see Chapter~\ref{suppressing-warnings}).
+
+For example, an unannotated method
+
+\begin{Verbatim}
+  String concatenate(String p1, String p2)
+\end{Verbatim}
+
+\noindent
+in a classfile would be interpreted as
+
+\begin{Verbatim}
+  @Top String concatenate(@Bottom String p1, @Bottom String p2)
+\end{Verbatim}
+
+There is no single possible default that is sound for fields.  In the rare
+circumstance that there is a mutable public field in an unannotated
+library, the Checker Framework may fail to warn about code that can
+misbehave at run time.
+
+%% TODO: The following rule is preferable to the current safe behavior.
+%% However, we have not yet figured out how to implement separate handling
+%% for field read vs. write.
+% For fields, use the bottom qualifier when writing to the field and the
+% top qualifier when reading from the field.
+
+%% TODO: should give rules for other locations, such as type parameters
+%% (which should behave like fields), bounds, etc.
+
+% If you supply the command-line option
+% \<-AunsafeDefaultsForUnannotatedBytecode>,
+% then the checker does defaulting for unannotated bytecode like it does for
+% annotated source code.  In other words, when a type use in a \<.class> file
+% has no explicit annotation, it is defaulted using the same rules as for the
+% corresponding source code location.  You should only use this command-line
+% option as temporary measure, because it is unsafe:  the checker might issue
+% no warnings even though the code could violate the type guarantee at run
+% time.  However, it can be useful when you are first annotating a codebase,
+% to help you focus on errors within the codebase before you have annotated
+% external libraries.
+
+
+\sectionAndLabel{Annotations on constructors}{annotations-on-constructors}
+
+\subsectionAndLabel{Annotations on constructor declarations}{annotations-on-constructor-declarations}
+
+An annotation on the ``return type'' of a constructor declaration indicates
+what the constructor creates.  For example,
+
+\begin{Verbatim}
+@B class MyClass {
+  @C MyClass() {}
+}
+\end{Verbatim}
+
+\noindent
+means that invoking that constructor creates a \<@C MyClass>.
+
+The Checker Framework cannot verify that the constructor really creates
+such an object, because the Checker Framework does not know the
+type-system-specific semantics of the \<@C> annotation.
+Therefore, if the constructor result type is different than the top annotation in the
+hierarchy, the Checker Framework will issue a warning.
+The programmer should check the annotation manually, then
+suppress the warning.
+
+
+\subsubsectionAndLabel{Defaults}{constructor-declaration-defaults}
+
+If a constructor declaration is unannotated, it defaults to the same type as that of
+its enclosing class (rather than the default qualifier in the hierarchy).
+For example, the Tainting Checker (Chapter~\ref{tainting-checker}) has \code{@Tainted}
+as its default qualifier. Consider the following class:
+
+\begin{Verbatim}
+  @Untainted class MyClass {
+    MyClass() {}
+  }
+\end{Verbatim}
+
+\noindent
+The constructor declaration is equivalent to \<@Untainted MyClass() \ttlcb\ttrcb>.
+
+The Checker Framework produces the same error messages for
+explicitly-written and defaulted annotations.
+
+
+\subsectionAndLabel{Annotations on constructor invocations}{annotations-on-constructor-invocations}
+
+The type of a method call expression \<x.myMethod(y, z)> is determined by
+the return type of the declaration of \<myMethod>.  There is no way to
+write an annotation on the call to change its type.  However, it is
+possible to write a cast:  \<(@Anno SomeType) x.myMethod(y, z)>.  The Checker
+Framework will issue a warning that it cannot verify that the downcast is
+correct.  The programmer should manually determine that the annotation is
+correct and then suppress the warning.
+
+A constructor invocation \<new MyClass()> is also a call, so its semantics
+are similar.  The type of the expression is determined by the annotation on
+the result type of the constructor declaration.  It is possible to write a cast
+\<(@Anno MyClass) new MyClass()>.  The syntax \<new @Anno MyClass()> is shorthand
+for the cast.  For either syntax, the Checker Framework will issue a
+warning that it cannot verify that the cast is correct.  The programmer may
+suppress the warning if the code is correct.
+
+
+\sectionAndLabel{Type refinement (flow-sensitive type qualifier inference)}{type-refinement}
+
+A checker can sometimes deduce that an expression's type is more specific
+than --- that is, a subtype of --- its declared or defaulted (Section~\ref{defaults}).
+This is called ``flow-sensitive type refinement'' or ``local type inference''.
+
+Due to local type refinement, a programmer typically
+does not have to write any qualifiers on local variables within a method body
+(except occasionally on type arguments).
+However, the programmer must write type annotations for method
+signatures (arguments and return values) and fields, unless the default
+annotations are correct.
+Local type refinement does not change the source code; it re-runs every time
+you run a checker.
+
+
+\subsectionAndLabel{Type refinement examples}{type-refinement-examples}
+
+Here is an example for the Nullness Checker
+(\chapterpageref{nullness-checker}).
+\<myVar> is declared as \<@Nullable String>, but
+it is treated as \<@NonNull String> within the body of the \<if> test.
+
+\begin{Verbatim}
+  @Nullable String myVar;
+  ...                   // myVar has type @Nullable String here.
+  myVar.hashCode();     // warning: possible dereference of null.
+  ...
+  if (myVar != null) {
+    ...                 // myVar has type @NonNull String here.
+    myVar.hashCode();   // no warning.
+  }
+\end{Verbatim}
+
+Here is another example.
+Note that the same expression may yield a
+warning or not depending on its context (that is, depending on the current
+type refinement).
+
+\begin{Verbatim}
+  @Nullable String myVar;
+  ...                   // myVar has type @Nullable String
+  myVar = "hello";
+  ...                   // myVar has type @NonNull String
+  myVar.hashCode();     // no warning
+  ...
+  myVar = myMap.get(someKey);
+  ...                   // myVar has type @Nullable String
+  myVar.hashCode();     // warning: posible dereference of null
+\end{Verbatim}
+
+Type refinement applies to every checker, including new
+checkers that you write.  Here is an example for the Regex Checker
+(\chapterpageref{regex-checker}):
+
+\begin{Verbatim}
+  void m2(@Unannotated String s) {
+    s = RegexUtil.asRegex(s, 2);  // asRegex throws an exception if its argument is not
+                                  // a regex with the given number of capturing groups
+    ...   // s now has type "@Regex(2) String"
+  }
+\end{Verbatim}
+
+
+\subsectionAndLabel{Type refinement behavior}{type-refinement-behavior}
+
+The checker treats a variable or expression as a subtype of its declared type:
+\begin{itemize}
+\item
+  starting at the time that
+  it is assigned a value,
+  a method establishes a postcondition (e.g., as
+  expressed by \refqualclass{checker/nullness/qual}{EnsuresNonNull} or
+  \refqualclass{framework/qual}{EnsuresQualifierIf}), or
+  a run-time check is performed (e.g., via an assertion or
+  \code{if} statement).
+\item
+until its value might change (e.g.,
+via an assignment, or because a method call might have a side effect).
+\end{itemize}
+
+The checker never treats a variable as
+a supertype of its declared type.  For example, an expression with declared type \refqualclass{checker/nullness/qual}{NonNull}
+type is never treated as possibly-null, and such an assignment is always illegal.
+
+The functionality has a variety of names:  type refinement,
+flow-sensitive type qualifier inference, local type inference, and
+sometimes just ``flow''.
+
+
+\subsectionAndLabel{Which types are refined}{type-refinement-which-types}
+
+You generally do not need to annotate the top-level type of a local variable.
+You do need to annotate its type arguments or array element types.
+(Type refinement does not change them, because doing so would not produce a
+subtype, as explained in see Section~\ref{covariant-type-parameters} and
+Section~\ref{invariant-arrays}.)
+Type refinement works within a method, so you still need to
+annotate method signatures (parameter and return type) and field types.
+
+If you find examples where you think a value should be inferred to have
+(or not have) a
+given annotation, but the checker does not do so, please submit a bug
+report (see Section~\ref{reporting-bugs}) that includes a small piece of
+Java code that reproduces the problem.
+
+
+\subsubsectionAndLabel{Fields and type refinement}{type-refinement-fields}
+
+Type refinement infers the type of fields in some restricted cases:
+
+\begin{itemize}
+
+\item
+A final initialized field:
+Type inference is performed for final fields that are initialized to a
+compile-time constant at the declaration site; so the type of \code{protocol}
+is \code{@NonNull String} in the following declaration:
+
+\begin{Verbatim}
+    public final String protocol = "https";
+\end{Verbatim}
+
+Such an inferred type may leak to the public interface of the class.
+If you wish to override such behavior, you can explicitly insert the desired
+annotation, e.g.,
+
+\begin{Verbatim}
+    public final @Nullable String protocol = "https";
+\end{Verbatim}
+
+\item
+Within method bodies:
+Type inference is performed for fields in the context of method bodies,
+like local variables or any other expression.
+Consider the following example, where \code{updatedAt} is a nullable
+field:
+
+\begin{Verbatim}
+class DBObject {
+  @Nullable Date updatedAt;
+
+  void m() {
+    // updatedAt is @Nullable, so warning about .getTime()
+    ... updatedAt.getTime() ... // warning about possible NullPointerException
+
+    if (updatedAt == null) {
+      updatedAt = new Date();
+    }
+
+    // updatedAt is now @NonNull, so .getTime() call is OK
+    ... updatedAt.getTime() ...
+  }
+}
+\end{Verbatim}
+
+A method call may invalidate inferences about field types; see
+Section~\ref{type-refinement-purity}.
+
+\end{itemize}
+
+
+\subsectionAndLabel{Run-time tests and type refinement}{type-refinement-runtime-tests}
+
+Some type systems support a run-time test that the Checker Framework can
+use to refine types within the scope of a conditional such as \<if>, after
+an \<assert> statement, etc.
+
+Whether a type system supports such a run-time test depends on whether the
+type system is computing properties of data itself, or properties of
+provenance (the source of the data).  An example of a property about data is
+whether a string is a regular expression.  An example of a property about
+provenance is units of measure:  there is no way to look at the
+representation of a number and determine whether it is intended to
+represent kilometers or miles.
+
+% Keep these lists in sync with the list in introduction.tex
+
+Type systems that support a run-time test are:
+\begin{itemize}
+\item
+  \ahrefloc{nullness-checker}{Nullness Checker} for null pointer errors
+  (see \chapterpageref{nullness-checker})
+\item
+  \ahrefloc{map-key-checker}{Map Key Checker} to track which values are
+  keys in a map (see \chapterpageref{map-key-checker})
+\item
+  \ahrefloc{optional-checker}{Optional Checker} for errors in using the
+  \sunjavadoc{java.base/java/util/Optional.html}{Optional} type (see
+  \chapterpageref{optional-checker})
+\item
+  \ahrefloc{lock-checker}{Lock Checker} for concurrency and lock errors
+  (see \chapterpageref{lock-checker})
+\item
+  \ahrefloc{index-checker}{Index Checker} for array accesses
+  (see \chapterpageref{index-checker})
+\item
+  \ahrefloc{regex-checker}{Regex Checker} to prevent use of syntactically
+  invalid regular expressions (see \chapterpageref{regex-checker})
+\item
+  \ahrefloc{formatter-checker}{Format String Checker} to ensure that format
+  strings have the right number and type of \<\%> directives (see
+  \chapterpageref{formatter-checker})
+\item
+  \ahrefloc{i18n-formatter-checker}{Internationalization Format String Checker}
+  to ensure that i18n format strings have the right number and type of
+  \<\{\}> directives (see \chapterpageref{i18n-formatter-checker})
+\end{itemize}
+
+
+Type systems that do not currently support a run-time test, but could do so with some
+additional implementation work, are
+
+\begin{itemize}
+\item
+  \ahrefloc{interning-checker}{Interning Checker} for errors in equality
+  testing and interning (see \chapterpageref{interning-checker})
+\item
+  \ahrefloc{propkey-checker}{Property File Checker} to ensure that valid
+  keys are used for property files and resource bundles (see
+  \chapterpageref{propkey-checker})
+\item
+  \ahrefloc{i18n-checker}{Internationalization Checker} to
+  ensure that code is properly internationalized (see
+  \chapterpageref{i18n-checker})
+% The Compiler Message Key Checker is neither here nor in the introduction
+% chapter because it is really meant for Checker Framework developers and
+% as sample code, and is not meant for Checker Framework users at large.
+\item
+  \ahrefloc{signature-checker}{Signature String Checker} to ensure that the
+  string representation of a type is properly used, for example in
+  \<Class.forName> (see \chapterpageref{signature-checker}).
+\item
+  \ahrefloc{constant-value-checker}{Constant Value Checker} to determine
+  whether an expression's value can be known at compile time
+  (see \chapterpageref{constant-value-checker})
+\end{itemize}
+
+
+Type systems that cannot support a run-time test are:
+
+\begin{itemize}
+\item
+  \ahrefloc{initialization-checker}{Initialization Checker} to ensure all
+  fields are set in the constructor (see
+  \chapterpageref{initialization-checker})
+\item
+  \ahrefloc{fenum-checker}{Fake Enum Checker} to allow type-safe fake enum
+  patterns and type aliases or typedefs (see \chapterpageref{fenum-checker})
+\item
+  \ahrefloc{tainting-checker}{Tainting Checker} for trust and security errors
+  (see \chapterpageref{tainting-checker})
+\item
+  \ahrefloc{guieffect-checker}{GUI Effect Checker} to ensure that non-GUI
+  threads do not access the UI, which would crash the application
+  (see \chapterpageref{guieffect-checker})
+\item
+  \ahrefloc{units-checker}{Units Checker} to ensure operations are
+  performed on correct units of measurement
+  (see \chapterpageref{units-checker})
+\item
+  \ahrefloc{signedness-checker}{Signedness Checker} to
+  ensure unsigned and signed values are not mixed
+  (see \chapterpageref{signedness-checker})
+\item
+  \ahrefloc{purity-checker}{Purity Checker} to identify whether
+  methods have side effects (see \chapterpageref{purity-checker})
+\item
+  \ahrefloc{initialized-fields-checker}{Initialized Fields Checker} to ensure all
+  fields are set in the constructor (see
+  \chapterpageref{initialization-checker})
+\item
+  \ahrefloc{aliasing-checker}{Aliasing Checker} to identify whether
+  expressions have aliases (see \chapterpageref{aliasing-checker})
+\item
+  \ahrefloc{subtyping-checker}{Subtyping Checker} for customized checking without
+  writing any code (see \chapterpageref{subtyping-checker})
+\end{itemize}
+
+
+
+\subsectionAndLabel{Side effects, determinism, purity, and type refinement}{type-refinement-purity}
+
+Calling a method typically causes the checker to discard its knowledge of
+the refined type, because the method might assign a field.
+The \refqualclass{dataflow/qual}{SideEffectFree} annotation indicates that
+the method has no side effects, so calling it does not invalidate any
+dataflow facts.
+
+Calling a method twice might have different results, so facts known about
+one call cannot be relied upon at another call.
+The \refqualclass{dataflow/qual}{Deterministic} annotation indicates that
+the method returns the same result every time it is called on the same
+arguments.
+
+\refqualclass{dataflow/qual}{Pure} means both
+\refqualclass{dataflow/qual}{SideEffectFree} and
+\refqualclass{dataflow/qual}{Deterministic}.
+The \refqualclass{dataflow/qual}{TerminatesExecution} annotation
+indicates that a given method never returns.  This can enable the
+type refinement to be more precise.
+
+Chapter~\ref{purity-checker} gives more information about these annotations.
+This section explains how to use them to improve type refinement.
+
+
+\subsubsectionAndLabel{Side effects}{type-refinement-side-effects}
+
+Consider the following declarations and uses:
+
+\begin{Verbatim}
+  @Nullable Object myField;
+
+  int computeValue() { ... }
+
+  void m() {
+    ...
+    if (myField != null) {
+                        // The type of myField is now "@NonNull Object".
+      int result = computeValue();
+                        // The type of myField is now "@Nullable Object",
+                        // because computeValue might have set myField to null.
+      myField.toString(); // Warning: possible null pointer exception.
+    }
+  }
+\end{Verbatim}
+
+There are three ways to express that \<computeValue> does not set
+\<myField> to \<null>, and thus to prevent the Nullness Checker from
+issuing a warning about the call \<myField.toString()>.
+
+\begin{enumerate}
+\item
+  If \<computeValue> has no side effects, declare it as
+
+\begin{Verbatim}
+  @SideEffectFree
+  int computeValue() { ... }
+\end{Verbatim}
+
+\noindent
+The Nullness Checker issues no warnings, because it can reason that
+the second occurrence of \code{myField} has the same (non-null) value as
+the one in the test.
+
+\item
+  If no method resets \<myField> to \<null> after it has been initialized
+  to a non-null value (even if a method has some other side effect),
+  declare \<myField> as \refqualclass{checker/nullness/qual}{MonotonicNonNull}.
+
+\begin{Verbatim}
+  @MonotonicNonNull Object myField;
+\end{Verbatim}
+
+\item
+  If \<computeValue> sets \<myField> to a non-null value, declare it as
+
+\begin{Verbatim}
+  @EnsuresNonNull("myField")
+  int computeValue() { ... }
+\end{Verbatim}
+
+  If \<computeValue> maintains \<myField> as a non-null value, even if it
+  might have other side effects and even if other methods might set
+  \<myField> to \<null>, declare it as
+
+\begin{Verbatim}
+  @RequiresNonNull("myField")
+  @EnsuresNonNull("myField")
+  int computeValue() { ... }
+\end{Verbatim}
+\end{enumerate}
+
+
+\subsubsectionAndLabel{Deterministic methods}{type-refinement-determinism}
+
+Consider the following declaration and uses:
+
+\begin{Verbatim}
+  @Nullable Object getField(Object arg) { ... }
+
+  void m() {
+    ...
+    if (x.getField(y) != null) {
+      x.getField(y).toString(); // warning: possible null pointer exception
+    }
+  }
+\end{Verbatim}
+
+The Nullness Checker issues a warning regarding the
+\code{toString()} call, because its receiver \code{x.getField(y)} might
+be \code{null}, according to the \code{@Nullable} return type in the
+declaration of \code{getField}.  The Nullness Checker cannot assume that
+\<getField> returns non-null on the second call, just based on the fact
+that it returned non-null on the first call.
+
+To indicate that a method returns the same value each time it is called on
+the same arguments, use the \refqualclass{dataflow/qual}{Deterministic} annotation.
+Actually, it is necessary to use \refqualclass{dataflow/qual}{Pure} which
+means both \<@Deterministic> and \<@SideEffectFree>, because otherwise the
+first call might change a value that the method depends on.
+
+If you change the declaration of \code{getField} to
+
+\begin{Verbatim}
+  @Pure
+  @Nullable Object getField(Object arg) { ... }
+\end{Verbatim}
+
+\noindent
+then the Nullness Checker issues no warnings.
+Because \<getField> is \<@SideEffectFree>, the values of \<x> and \<y> are the
+same at both invocations.
+Because \<getField> is \<@Deterministic>, the two invocations of
+\code{x.getField(y)} have the same value.
+Therefore, \code{x.getField(y)} is non-null within the \<then> branch
+of the \<if> statement.
+
+
+
+% This is a slightly funny place for this section, but I cannot decide on a
+% better one.
+\subsectionAndLabel{Assertions}{type-refinement-assertions}
+
+If your code contains an \<assert> statement, then your code could behave
+in two different ways at run time, depending on whether assertions are
+enabled or disabled
+via the \<-ea> or \<-da> command-line options to java.
+
+By default, the Checker Framework outputs warnings about any error that
+could happen at run time, whether assertions are enabled or disabled.
+
+If you supply the \<-AassumeAssertionsAreEnabled> command-line option, then
+the Checker Framework assumes assertions are enabled.  If you supply the
+\<-AassumeAssertionsAreDisabled> command-line option, then the Checker
+Framework assumes assertions are disabled.  You may not supply both
+command-line options.  It is uncommon to supply either one.
+
+These command-line arguments have no effect on processing of \<assert>
+statements whose message contains the text \<@AssumeAssertion>; see
+Section~\ref{assumeassertion}.
+
+
+% If you add a Javadoc link to this location, also add the qualifier to the
+% list below.
+\sectionAndLabel{Writing Java expressions as annotation arguments}{java-expressions-as-arguments}
+
+Sometimes, it is necessary to write a Java expression as the argument to an
+annotation.  The annotations that take a Java
+expression as an argument include:
+
+% TODO: Need to periodically check/update this list.
+\begin{itemize}
+\item \refqualclass{framework/qual}{RequiresQualifier}
+\item \refqualclass{framework/qual}{EnsuresQualifier}
+\item \refqualclass{framework/qual}{EnsuresQualifierIf}
+\item \refqualclass{checker/nullness/qual}{RequiresNonNull}
+\item \refqualclass{checker/nullness/qual}{EnsuresNonNull}
+\item \refqualclass{checker/nullness/qual}{EnsuresNonNullIf}
+% Not implemented: \refqualclass{checker/nullness/qual}{AssertNonNullIfNonNull}
+\item \refqualclass{checker/nullness/qual}{KeyFor}
+\item \refqualclass{checker/nullness/qual}{EnsuresKeyFor}
+\item \refqualclass{checker/nullness/qual}{EnsuresKeyForIf}
+\item \refqualclass{checker/i18nformatter/qual}{I18nFormatFor}
+\item \refqualclass{checker/lock/qual}{EnsuresLockHeld}
+\item \refqualclass{checker/lock/qual}{EnsuresLockHeldIf}
+\item \refqualclass{checker/lock/qual}{GuardedBy}
+\item \refqualclass{checker/lock/qual}{Holding}
+\end{itemize}
+
+The set of permitted expressions is a subset of all Java expressions,
+with a few extensions.
+The extensions are formal parameters like \<\#1> and (for some type
+systems) \code{<self>}.
+
+\begin{itemize}
+\item
+  \<this>, the receiver object.  You can write \<this> to annotate any
+  variable or declaration where you could write \<this> in code.
+  Notably, it cannot be used in annotations on declarations of
+  static fields or methods.  For a field, \<this> is the field's
+  receiver (sometimes called its container).  For a local variable, it is the
+  method's receiver.
+
+\item
+  \<super>, the receiver object as seen from the superclass.  This can be used
+  to refer to fields shadowed in the subclass (although shadowing fields is
+  discouraged in Java).
+
+\item
+  \code{<self>}, the value of the annotated reference (non-primitive) variable.
+  Currently only defined for the \<@GuardedBy> type system.
+  For example, \code{@GuardedBy("<self>") Object o} indicates that the value
+  referenced by \<o> is guarded by the intrinsic (monitor) lock of the value
+  referenced by \<o>.
+
+\item
+  a formal parameter, e.g., \<\#2>.  It is represented as \<\#> followed by the \textbf{one-based} parameter
+  index.  For example: \<\#1>, \<\#3>.  It is not permitted to write \<\#0> to
+  refer to the receiver object; use \<this> instead.
+
+  The formal parameter syntax \<\#1> is less natural in source code
+  than writing the formal parameter name.  This syntax is necessary for
+  separate compilation, because no formal parameter name information is
+  available in a \<.class> file.  Suppose an annotated method \<m> has
+  already been compiled into a \<.class> file, perhaps by a compilation
+  that did not use the Checker Framework.  When a client of \<m> is later
+  compiled, it cannot interpret a formal parameter name, but it can
+  interpret a number.
+
+  Within a method \emph{body}, you may use the formal parameter name.  The
+  formal parameter name never works within a method \emph{signature} or for
+  a contract (pre- or post-condition) annotation; in those locations, an
+  identifier is interpreted as a field name (not a formal parameter).
+
+\item
+  a local variable, e.g., \<myLocalVar>.
+  The variable must be in scope; for example, a method annotation on method
+  \<m> cannot mention a local variable that is declared inside \<m>.
+
+\item
+  a static variable, e.g., \<System.out>.
+  Write the class name and the variable.
+
+\item
+  a field of any expression.  For example:  \<next>,
+  \<this.next>, \<\#1.next>. %, \<myLocalVar.next>.
+  You may optionally omit a leading ``\<this.>'', just as in Java.  Thus,
+  \<this.next> and \<next> are equivalent.
+
+\item
+  an array access.  For example:  \<this.myArray[i]>, \<vals[\#1]>.
+
+\item
+  an array creation. For example: \<new int[10]>, \<new String[] {"a", "b"}>.
+
+\item literals: string, integer, char, long, float, double, null, class literals.
+
+\item a method invocation on any expression.
+  This even works for overloaded methods and methods with type parameters.
+  For example:
+  \<m1(x, y.z, \#2)>, \<a.m2("hello")>.
+
+  Currently, the Checker Framework cannot prove all contracts about method
+  calls, so you may need to suppress some warnings.
+
+  One unusual feature of the Checker Framework's Java expressions is that a
+  method call is allowed to have side effects.  Other tools forbid methods
+  with side effects (and doing so is necessary if a specification is going
+  to be checked at run time via assertions).  The Checker Framework enables
+  you to state more facts.  For example, consider the annotation on
+  \<java.io.BufferedReader.ready()>:
+
+\begin{Verbatim}
+  @EnsuresNonNullIf(expression="readLine()", result=true)
+  @Pure public boolean ready() throws IOException { ... }
+\end{Verbatim}
+
+  This states that if \<readLine()> is called immediately after \<ready()>
+  returns true, then \<readLine()> returns a non-null value.
+
+\item a binary expression, e.g., \<x + y> or <\#1 - 1>.  These are used by
+  the Index Checker, for example.
+
+\item a class name expression within another expression, e.g., ``String''
+  in \<String.class> or ``pkg.MyClass'' in \<pkg.MyClass.staticField>.  The
+  class name must be fully-qualified unless it can be referenced by its
+  simple name without an \<import> statement at the location where the
+  annotation appears.  For example, an annotation in class \<C> can use the
+  simple name of a class in \<java.lang> or in the same package as \<C>.
+
+\end{itemize}
+
+% We want this in the future:
+% The expression may refer to private fields and method calls.  The client
+% cannot directly make use of these private fields and method calls, but they
+% may be useful in establishing a precondition that itself refers to private
+% fields or methods.  This exposure of implementation details is unfortunate,
+% but it enables more expressive and useful specifications to be written, it does not jeopardize soundness nor let the client manipulate the
+% representation, and it is simple
+% simpler than defining specification fields or ghost fields
+%% TODO: we may eventually have an @SpecField annotation
+
+
+\textbf{Limitations:}
+It is not possible to write a
+quantification over all array components (e.g., to express that all
+  array elements are non-null).  There is no such Java expression, but it
+  would be useful when writing specifications.
+
+
+\sectionAndLabel{Field invariants}{field-invariants}
+
+Sometimes a field declared in a superclass has a more precise type in a
+subclass.  To express this fact, write
+\refqualclass{framework/qual}{FieldInvariant} on the subclass. It specifies
+the field's type in the class on which this annotation is written.
+The field must be declared in a superclass and
+must be final.
+
+% Other possible examples:
+% Vehicles have some number of wheels; motorcycles have exactly 2 wheels.
+% Sentence: subject (missing for imperative, which would let us use
+%   @Unused, but that's not the point here), verb, object (optional,
+%   missing for some types of sentences?).
+% Book might have an index
+% DOM tree might have parent or not.
+% Computer might have a sound card or not.
+
+For example,
+\begin{Verbatim}
+class Person {
+  final @Nullable String nickname;
+  public Person(@Nullable String nickname) {
+    this.nickname = nickname;
+  }
+}
+
+// A rapper always has a nickname.
+@FieldInvariant(qualifier = NonNull.class, field = "nickname")
+class Rapper extends Person {
+  public Rapper(String nickname) {
+    super(nickname);
+  }
+  void method() {
+    ... nickname.length() ...   // legal, nickname is non-null in this class.
+  }
+}
+\end{Verbatim}
+ A field invariant annotation can refer to more than one field. For example,
+\<@FieldInvariant(qualifier = NonNull.class, field = \{fieldA, fieldB\})> means
+that \<fieldA> and \<fieldB> are both non-null in the class upon which the
+annotation is written.  A field invariant annotation
+can also apply different qualifiers to different fields. For example,
+\<@FieldInvariant(qualifier = \{NonNull.class, Untainted.class\}, field =
+\{fieldA, fieldB\})> means that \<fieldA> is non-null and \<fieldB> is untainted.
+
+This annotation is inherited:  if a superclass is annotated with
+\<@FieldInvariant>, its subclasses have the same annotation. If a subclass has its
+own \<@FieldInvariant>, then it must include the fields in the superclass
+annotation and those fields' annotations must be a subtype (or equal) to the
+annotations for those fields in the superclass \<@FieldInvariant>.
+
+Currently, the \<@FieldInvariant> annotation is trusted rather than
+checked.  In other words, the \<@FieldInvariant> annotation introduces a
+loophole in the type system, which requires verification by other means
+such as manual examination.
+
+
+% \sectionAndLabel{Unused fields and dependent types}{unused-fields-and-dependent-types}
+\sectionAndLabel{Unused fields}{unused-fields}
+
+In an inheritance hierarchy, subclasses often introduce new methods and
+fields.  For example, a \<Marsupial> (and its subclasses such as
+\<Kangaroo>) might have a variable \<pouchSize> indicating the size of the animal's
+pouch.  The field does not exist in superclasses such as
+\<Mammal> and \<Animal>, so Java issues a compile-time
+error if a program tries to access \<myMammal.pouchSize>.
+
+If you cannot use subtypes in your program, you can enforce similar
+requirements using type qualifiers.
+For fields, use the \<@Unused> annotation (Section~\ref{unused-annotation}), which enforces that a field or method may only
+be accessed from a receiver expression with a given annotation (or one of
+its subtypes).
+For methods, annotate the receiver parameter \<this>; then a method call
+type-checks only if the actual receiver is of the specified type.
+
+% For example, consider the declaration
+%   void getChars(@Untainted String this, int srcBegin, int srcEnd, char[] dst,
+%   int dstBegin) { ... }
+% Now, the invocation
+%   myString.getChars(a, b, c, d)
+% type-checks only if myString has type @Untainted String.  It does not
+% type-check if myString has type @Tainted String.
+
+
+% Then,
+% Section~\ref{dependent-types} describes an even more powerful mechanism, by
+% which the qualifier of a field depends on the qualifier of the expression
+% from which the field was accessed.
+
+Also see the discussion of typestate checkers, in
+Chapter~\ref{typestate-checker}.
+
+
+% \subsectionAndLabel{Unused fields}{unused-fields}
+\subsectionAndLabel{\<@Unused> annotation}{unused-annotation}
+
+A Java subtype can have more fields than its supertype.  For example:
+
+\begin{Verbatim}
+class Animal {}
+class Mammal extends Animal { ... }
+class Marsupial extends Mammal {
+  int pouchSize;  // pouch capacity, in cubic centimeters
+  ...
+}
+\end{Verbatim}
+
+You can simulate
+the same effect for type qualifiers:
+the \refqualclass{framework/qual}{Unused} annotation
+on a field declares that the field may \emph{not} be accessed via a receiver of
+the given qualified type (or any \emph{super}type).
+% a given field \emph{may not} be accessed via
+% a reference with a supertype qualifier, but \emph{may} be accessed via a reference
+% with a subtype qualifier.
+%% The following is true, but there's no need to distract readers with this detail.
+% (It would probably be clearer to replace \<@Unused> by an annotation that
+% indicates when the field \emph{may} be used.)
+For example:
+
+\begin{Verbatim}
+class Animal {
+  @Unused(when=Mammal.class)
+  int pouchSize;  // pouch capacity, in cubic centimeters
+  ...
+}
+@interface Mammal {}
+@interface Marsupial {}
+
+@Marsupial Animal joey = ...;
+... joey.pouchSize ...    // OK
+@Mammal Animal mae = ...;
+... mae.pouchSize ...    // compile-time error
+\end{Verbatim}
+
+The above class declaration is like writing
+
+\begin{Verbatim}
+class @Mammal-Animal { ... }
+class @Marsupial-Animal {
+  int pouchSize;  // pouch capacity, in cubic centimeters
+  ...
+}
+\end{Verbatim}
+
+
+% \subsectionAndLabel{Dependent types}{dependent-types}
+%
+% A variable has a \emph{dependent type} if its type depends on some other
+% value or type.
+% %  --- the type is dynamically, not statically, determined.
+% % (Type-safety can still be statically determined, though.)
+%
+% The Checker Framework supports a form of dependent types, via the
+% \refqualclass{framework/qual}{Dependent} annotation.
+% This annotation changes the type of a reference, based on the
+% qualified type of the receiver (\code{this}).  This can be viewed as a more
+% expressive form of polymorphism (see Section~\ref{polymorphism}).  It can
+% also be seen as a way of linking the meanings of two type qualifier
+% hierarchies.
+%
+% When the \refqualclass{framework/qual}{Unused} annotation is sufficient, you
+% should use it instead of \code{@Dependent}.
+%
+% Here is a restatement of the example of Section~\ref{unused-fields}, using
+% \refqualclass{framework/qual}{Dependent}:
+%
+% \begin{Verbatim}
+% @interface Mammal {}
+% @interface Marsupial {}
+% class Animal {
+%   // pouch capacity, in cubic centimeters
+%   // (non-null if this animal is a marsupial)
+%   @Nullable @Dependent(result=NonNull.class, when=Marsupial.class) Integer getPouchSize;
+%   ...
+% }
+%
+% @Marsupial Animal joey = ...;
+% ... joey.getPouchSize().intValue() ...    // OK
+% @Mammal Animal mae = ...;
+% ... mae.getPouchSize().intValue() ...    // compile-time error:
+%                                          //   dereference of possibly-null mae.getPouchSize()
+% \end{Verbatim}
+%
+% Just as writing \<@Unused> is similar to writing multiple classes (but when
+% it is not possible to write real subclasses), \<@Dependent> mimics the
+% effect of multiple classes with overriding definitions of some method or
+% field.
+% The above class declaration is like writing
+%
+% \begin{Verbatim}
+% class @Mammal-Animal {
+%   // pouch capacity, in cubic centimeters
+%   // (non-null if this animal is a marsupial)
+%   @Nullable Integer getPouchSize();
+% }
+% class @Marsupial-Animal {
+%   // pouch capacity, in cubic centimeters
+%   @NonNull Integer getPouchSize();
+%   ...
+% }
+% \end{Verbatim}
+%
+%
+% \subsubsectionAndLabel{Limitations of \<@Dependent>}{dependent-types-limitations}
+%
+% It is unsound to write \<@Dependent> on a non-\<final> field.  Consider the
+% following:
+%
+% \begin{Verbatim}
+% class MyClass {
+%   @Nullable @Dependent(result=NonNull.class, when=Marsupial.class) Integer pouchSize;
+% }
+% \end{Verbatim}
+%
+% Then it would be possible to write
+%
+% \begin{Verbatim}
+% @Marsupial Animal a = new @Marsupial Animal();
+% @Mammal Animal b = a;
+% b.pouchSize = null;
+% a.pouchSize.intValue();
+% \end{Verbatim}
+%
+% \noindent
+% In the last line of the example, \<a.pouchSize> is null, contradicting its
+% declaration and leading to a null pointer exception that would not be
+% caught by the type-checker.
+%
+% In certain circumstances, it may be desirable to write such an annotation
+% on a \<private> field anyway, manually check all uses, and then suppress
+% the warning.
+%
+% It is sound to write \<@Dependent> on a \<final> field.  Such a field
+% behaves like a getter method, such as \<getPouchSize()> above.
+
+
+
+% TO DO:  give an example where @Dependent is actually needed
+
+
+% LocalWords:  MyClass qual PolymorphicQualifier DefaultQualifier subpackages
+% LocalWords:  actuals toArray CollectionToArrayHeuristics nn RegexBottom
+% LocalWords:  MyList Nullness DefaultLocation nullness PolyNull util java TODO
+% LocalWords:  QualifierDefaults nullable lub persistData updatedAt nble KeyFor
+% LocalWords:  subtype's RequiresNonNull EnsuresNonNull EnsuresNonNullIf
+% LocalWords:  myLocalVar myClass getPackage getSuperclass myString Regex
+% LocalWords:  getComponentType enum implementers dereferenced superclasses
+% LocalWords:  regex myStrings myVar pouchSize myMammal getter foo MyAnno
+% LocalWords:  getPouchSize TerminatesExecution myvar myField getField m1
+% LocalWords:  computeValue AsuggestPureMethods arg myInt Anno Im
+% LocalWords:  AcheckPurityAnnotations AassumeSideEffectFree iMplicit m2
+% LocalWords:  AassumeAssertionsAreEnabled myArray vals propkey forName
+% LocalWords:  fenum i18n RequiresQualifier EnsuresQualifier ClassName
+% LocalWords:  EnsuresQualifierIf AsuppressWarnings AinvariantArrays asts
+% LocalWords:  formatter ocals nstanceof plicit ounds wildcard's da
+% LocalWords:  wildcards guieffect AassumeAssertionsAreDisabled readLine
+% LocalWords:  AssumeAssertion AsafeDefaultsForUnannotatedBytecode
+% LocalWords:  I18nFormatFor AunsafeDefaultsForUnannotatedBytecode
+% LocalWords:  AuseConservativeDefaultsForUncheckedCode DefaultFor TypeUseLocation
+% LocalWords:  EnsuresKeyFor EnsuresKeyForIf DivideByZeroException
+% LocalWords:  EnsuresLockHeld EnsuresLockHeldIf FieldInvariant fieldA
+% LocalWords:  fieldB myMethod SomeType runtime typedefs signedness
+% LocalWords:  Signedness MonotonicNonNull UpperBoundFor
+% LocalWords:  getTypeDeclarationBounds identityHashCode
diff --git a/docs/manual/aliasing-checker.tex b/docs/manual/aliasing-checker.tex
new file mode 100644
index 0000000..6fb5663
--- /dev/null
+++ b/docs/manual/aliasing-checker.tex
@@ -0,0 +1,318 @@
+\htmlhr
+\chapterAndLabel{Aliasing Checker}{aliasing-checker}
+
+The Aliasing Checker identifies expressions that definitely have no
+aliases.
+
+Two expressions are aliased when they have the same non-primitive value;
+that is, they are references to the identical Java object
+in the heap. Another way of saying this is that two expressions,
+$\mathit{exprA}$ and $\mathit{exprB}$, are aliases of each other when
+$\mathit{exprA} \<==> \mathit{exprB}$ at the same program point.
+
+Assigning to a variable or field typically creates an alias.  For example,
+after the statement \<a = b;>, the variables \<a> and \<b> are aliased.
+
+Knowing that an expression is not aliased permits more accurate reasoning
+about how side effects modify the expression's value.
+
+To run the Aliasing Checker, supply the
+\code{-processor org.checkerframework.common.aliasing.AliasingChecker}
+command-line option to javac.
+However, a user rarely runs the Aliasing Checker directly.
+This type system is mainly intended to be used together with other type systems.
+For example, the SPARTA information flow type-checker
+(Section~\ref{sparta-checker}) uses the Aliasing Checker to improve its
+type refinement --- if an expression has no aliases, a more refined type
+can often be inferred, otherwise the type-checker makes conservative
+assumptions.
+
+\sectionAndLabel{Aliasing annotations}{aliasing-annotations}
+
+\begin{figure}
+\includeimage{aliasing}{2cm}
+\caption{Type hierarchy for the Aliasing type system.
+  These qualifiers are applicable to any reference (non-primitive) type.}
+\label{fig-aliasing-hierarchy}
+\end{figure}
+
+There are two possible types for an expression:
+
+\begin{description}
+
+\item[\refqualclass{common/aliasing/qual}{MaybeAliased}]
+is the type of an expression that might have an alias.
+This is the default, so every unannotated type is
+\code{@MaybeAliased}. (This includes the type of \code{null}.)
+
+\item[\refqualclass{common/aliasing/qual}{Unique}]
+is the type of an expression that has no aliases.
+
+The \code{@Unique} annotation is only allowed at local variables, method
+parameters, constructor results, and method returns.
+A constructor's result should be annotated with \code{@Unique} only if the
+constructor's body does not creates an alias to the constructed object.
+
+\end{description}
+
+There are also two annotations, which are currently trusted instead of verified,
+that can be used on formal parameters (including
+the receiver parameter, \<this>):
+
+\begin{description}
+
+\item[\refqualclass{common/aliasing/qual}{NonLeaked}]
+identifies a formal parameter that is not leaked nor
+returned by the method body.
+For example, the formal parameter of the String copy constructor,
+\code{String(String s)}, is \code{@NonLeaked} because the body of the method
+only makes a copy of the parameter.
+
+\item[\refqualclass{common/aliasing/qual}{LeakedToResult}]
+is used when the parameter may be returned, but it is not
+otherwise leaked.
+For example, the receiver parameter of \code{StringBuffer.append(StringBuffer
+this, String s)} is
+\code{@LeakedToResult}, because the method returns the updated receiver.
+
+\end{description}
+
+\sectionAndLabel{Leaking contexts}{aliasing-leaking-contexts}
+This section lists the expressions that create aliases.  These are also
+called ``leaking contexts''.
+
+\begin{description}
+\item[Assignments]
+After an assignment, the left-hand side and the right-hand side are
+typically aliased.  (The only counterexample is when the right-hand side is
+a fresh expression; see Section~\ref{aliasing-refinement}.)
+
+\begin{Verbatim}
+  @Unique Object u = ...;
+  Object o = u;                    // (not.unique) type-checking error!
+\end{Verbatim}
+
+If this example type-checked, then \<u> and \<o> would be aliased.
+For this example to type-check, either the \<@Unique> annotation on the
+type of \<u>, or the \<o = u;> assignment, must be removed.
+
+\item[Method calls and returns (pseudo-assignments)]
+Passing an argument to a method is a ``pseudo-assignment'' because it effectively
+assigns the argument to the formal parameter.  Return statements are also
+pseudo-assignments.
+As with assignments, the left-hand side and right-hand side of
+pseudo-assignments are typically aliased.
+
+Here is an example for argument-passing:
+
+\begin{Verbatim}
+  void mightDoAnything(Object o) { ... }
+
+  @Unique Object u = ...;
+  mightDoAnything(u);  // type-checking error, because the callee may create an alias of the passed argument
+\end{Verbatim}
+
+Passing a non-aliased
+reference to a method does not necessarily create an alias.
+However, the body of the method might create an alias or leak the
+reference.  Thus, the Aliasing Checker always treats a method call as
+creating aliases for each argument unless the corresponding formal
+parameter is marked as
+@\refqualclass{common/aliasing/qual}{NonLeaked} or
+@\refqualclass{common/aliasing/qual}{LeakedToResult}.
+
+Here is an example for a return statement:
+
+\begin{Verbatim}
+Object id(@Unique Object p) {
+    return p;     // (not.unique) type-checking error!
+}
+\end{Verbatim}
+
+If this code type-checked, then it would be possible for clients to write
+code like this:
+
+\begin{Verbatim}
+@Unique Object u = ...;
+Object o = id(u);
+\end{Verbatim}
+
+\noindent
+after which there is an alias to \<u> even though it is declared as \<@Unique>.
+
+However, it is permitted to write
+
+\begin{Verbatim}
+Object id(@LeakedToResult Object p) {
+    return p;
+}
+\end{Verbatim}
+
+\noindent
+after which the following code type-checks:
+
+\begin{Verbatim}
+@Unique Object u = ...;
+id(u);                   // method call result is not used
+Object o1 = ...;
+Object o2 = id(o1);      // argument is not @Unique
+\end{Verbatim}
+
+
+
+\item[Throws]
+A thrown exception can be captured by a catch block, which creates an
+alias of the thrown exception.
+
+\begin{Verbatim}
+void aliasInCatchBlock() {
+    @Unique Exception uex = new Exception();
+    try {
+        throw uex;    // (not.unique) type-checking error!
+    } catch (Exception ex) {
+        // uex and ex refer to the same object here.
+    }
+}
+\end{Verbatim}
+
+\item[Array initializers]
+
+Array initializers assign the elements in the initializers to corresponding
+indexes in the array, therefore expressions in an array initializer are leaked.
+
+\begin{Verbatim}
+void aliasInArrayInitializer() {
+    @Unique Object o = new Object();
+    Object[] ar = new Object[] { o };  // (not.unique) type-checking error!
+    // The expressions o and ar[0] are now aliased.
+}
+\end{Verbatim}
+
+%Remember to add enhanced for statement if support to type variables is added.
+
+\end{description}
+
+
+\sectionAndLabel{Restrictions on where \<@Unique> may be written}{aliasing-unique-restrictions}
+
+The \<@Unique> qualifier may not be written on locations such as fields,
+array elements, and type parameters.
+
+As an example of why \<@Unique> may not be written on a field's type,
+consider the following code:
+
+\begin{Verbatim}
+class MyClass {
+    @Unique Object field;
+    void makesAlias() {
+        MyClass myClass2 = this;
+        // this.field is now an alias of myClass2.field
+    }
+}
+\end{Verbatim}
+
+That code must not type-check, because \<field> is declared as \<@Unique>
+but has an alias.  The Aliasing Checker solves the problem by forbidding
+the \<@Unique> qualifier on subcomponents of a structure, such as fields.
+Other solutions might be possible; they would be more complicated but would
+permit more code to type-check.
+
+\<@Unique> may not be written on a type parameter for similar reasons.
+The assignment
+
+\begin{Verbatim}
+List<@Unique Object> l1 = ...;
+List<@Unique Object> l2 = l1;
+\end{Verbatim}
+
+\noindent
+must be forbidden because it would alias \<l1.get(0)> with \<l2.get(0)>
+even though both have type \<@Unique>.  The Aliasing Checker forbids this
+code by rejecting the type \code{List<@Unique Object>}.
+
+
+\sectionAndLabel{Aliasing type refinement}{aliasing-refinement}
+
+Type refinement enables a type checker to treat an expression as a subtype
+of its declared type.  For example, even if you declare a local variable as
+\<@MaybeAliased> (or don't write anything, since \<@MaybeAliased> is the
+default), sometimes the Aliasing Checker can determine that it is actually
+\<@Unique>.
+% This prevents the type checker from issuing false positive warnings.
+For more details, see Section~\ref{type-refinement}.
+
+The Aliasing Checker treats type refinement in the usual way,
+except that at (pseudo-)assignments
+the right-hand-side (RHS) may lose its type refinement, before the
+left-hand-side (LHS) is type-refined.
+The RHS always loses its type refinement (it is widened to
+\code{@MaybeAliased}, and its declared type must have been
+\code{@MaybeAliased}) except in the following cases:
+
+\begin{itemize}
+\item
+The RHS is a fresh expression --- an expression that returns a different value
+each time it is evaluated. In practice, this is only method/constructor calls
+with \code{@Unique} return type. A variable/field is not fresh because it can
+return the same value when evaluated twice.
+\item
+The LHS is a \code{@NonLeaked} formal parameter and the RHS is an argument in a
+method call or constructor invocation.
+\item
+The LHS is a \code{@LeakedToResult} formal parameter, the RHS is an argument in
+a method call or constructor invocation, and the method's return value is
+discarded --- that is, the method call or constructor invocation is written
+syntactically as a statement rather than as a part of a larger expression or
+statement.
+\end{itemize}
+%(Notice that the last two rules above are restricted to pseudo-assignments.)
+
+A consequence of the above rules is that most method calls are treated conservatively.
+If a variable with declared type \code{@MaybeAliased} has been refined
+to \code{@Unique} and is used as an argument of a method call, it usually loses its
+\code{@Unique} refined type.
+
+
+Figure~\ref{fig-aliasing-refinement-example} gives an example of the Aliasing Checker's
+type refinement rules.
+
+\begin{figure}
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+// Annotations on the StringBuffer class, used in the examples below.
+// class StringBuffer {
+//  @Unique StringBuffer();
+//  StringBuffer append(@LeakedToResult StringBuffer this, @NonLeaked String s);
+// }
+
+void foo() {
+    StringBuffer sb = new StringBuffer();    // sb is refined to @Unique.
+
+    StringBuffer sb2 = sb;                   // sb loses its refinement.
+    // Both sb and sb2 have aliases and because of that have type @MaybeAliased.
+}
+
+void bar() {
+    StringBuffer sb = new StringBuffer();     // sb is refined to @Unique.
+
+    sb.append("someString");
+    // sb stays @Unique, as no aliases are created.
+
+    StringBuffer sb2 = sb.append("someString");
+    // sb is leaked and becomes @MaybeAliased.
+
+    // Both sb and sb2 have aliases and because of that have type @MaybeAliased.
+}
+
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+\caption{Example of the Aliasing Checker's type refinement rules.}
+\label{fig-aliasing-refinement-example}
+\end{figure}
+
+%%  LocalWords:  MaybeAliased NonLeaked LeakedToResult l1 l2 RHS LHS
+%%  LocalWords:  subcomponents
diff --git a/docs/manual/annotating-libraries.tex b/docs/manual/annotating-libraries.tex
new file mode 100644
index 0000000..385fbeb
--- /dev/null
+++ b/docs/manual/annotating-libraries.tex
@@ -0,0 +1,1021 @@
+\htmlhr
+\chapterAndLabel{Annotating libraries}{annotating-libraries}
+
+When your code uses a library that is not currently being compiled,
+the Checker Framework looks up the library's annotations in its class files.
+Section~\ref{annotated-libraries-using} tells you how to find and use a
+version of a library that contains type annotations.
+
+If your code uses a library that does \emph{not} contain type annotations,
+then the type-checker has no way to know the library's behavior.
+The type-checker
+makes conservative assumptions about unannotated bytecode.
+% :  it assumes that every method parameter has the bottom type annotation
+% and that every method return type has the top type annotation
+(See
+Section~\ref{defaults-classfile} for details, an example, and how to
+override this conservative behavior.)
+These conservative library
+annotations invariably lead to checker warnings.
+
+This chapter describes how to eliminate
+the warnings by adding annotations to the library.
+(Alternately, you can instead
+suppress all warnings related to an unannotated library, or to part of your
+codebase, by use of the
+\code{-AskipUses} or \code{-AonlyUses} command-line option; see
+Sections~\ref{askipuses}--\ref{askipdefs}.)
+
+You can write annotations for a library, and make them known to a checker, in two ways.
+
+\begin{enumerate}
+\item
+  Write annotations in a copy of the library's source code (for instance,
+  in a fork of the library's GitHub project).  In addition to writing
+  annotations, adjust the build system to run pluggable-type-checking when
+  compiling (see \chapterpageref{external-tools}).  Now, when you compile
+  the library, the resulting \<.jar> file contains type annotations.
+
+  When checking a client of the library,
+  put the annotated library's \<.jar> file on the classpath, as explained in
+  Section~\ref{annotated-libraries-using}.
+  %% You only need to do this if for some reason you don't trust the
+  %% artifacts on Maven Central; but don't cater to that level of paranoia
+  %% by mentioning it.
+  % When \emph{running} your code, you can use either version of the library:  the
+  % one you created or the original distributed version.
+
+  %% There is no point to advertising this deprecated workflow.
+  % You can insert annotations in the compiled \code{.class} files of the
+  % library.  You would express the annotations textually, typically as an
+  % annotation index file, and
+  % then insert them in the library by using the Annotation File Utilities
+  % (\myurl{https://checkerframework.org/annotation-file-utilities/}).
+  % See the Annotation File Utilities documentation for full details.
+
+  With this compilation approach, the syntax of the library annotations is
+  validated ahead of time.  Thus, this compilation approach is less
+  error-prone, and the type-checker runs faster.  You get
+  correctness guarantees about the library in addition to your code.
+
+  For instructions, see Section~\ref{annotating-libraries-create}.
+
+\item
+  Write annotations in a ``stub file'', if you do not have access to the
+  source code.
+  %% Leave out this complication.
+  % This approach is possible with annotated source code.
+
+  Then, when check a client of the library,
+  supply the ``stub file'' textually to the Checker Framework.
+
+  This approach does not require you to compile the library source
+  code.
+  A stub file is applicable to multiple versions of a library, so
+  the stub file does not need to be updated when a new version of the
+  library is released, unless the API has changed (such as defining a new
+  class or method).
+
+  For instructions, see Section~\ref{stub}.
+
+\end{enumerate}
+
+
+If you annotate a new library (either in its source code or in a stub
+file), please inform the Checker Framework
+developers so that your annotated library can be advertised to users of the
+Checker Framework.
+Sharing your annotations is useful even if the library is only partially
+annotated.
+However, as noted in Sections~\ref{get-started-with-legacy-code} and~\ref{library-tips-fully-annotate}, you
+should annotate an entire class at a time.
+You may find type inference tools (\chapterpageref{type-inference}) helpful
+when getting started, but you should always examine their results.
+
+
+\sectionAndLabel{Tips for annotating a library}{library-tips}
+
+Section~\ref{tips-about-writing-annotations} gives general tips for writing
+annotations.  This section gives tips that are specific to annotating a
+third-party library.
+
+
+\subsectionAndLabel{Don't change the code}{library-tips-dont-change-the-code}
+
+If you annotate a library that you maintain, you can refactor it to improve
+its design.
+
+When you annotate a library that you do not maintain, you should only add
+annotations and, when necessary, documentation of those annotations.  You
+can place the documentation in a Java comment (\<//> or \</*...*/>) or in a
+\refqualclass{framework/qual}{CFComment} annotation.
+
+Do not change the library's code, which will change its behavior and make
+the annotated version inconsistent with the unannotated version.
+(Sometimes it is acceptable to make a refactoring, such as extracting an
+expression into a new local variable in order to annotate its type or
+suppress a warning.  Perform refactoring only when you cannot use
+\<@AssumeAssertion>.)
+% (The only time it is acceptable to comment out existing code in the library
+% is if the regular Java compiler cannot compile the code; but this should be
+% extremely rare.)
+
+Do not change publicly-visible documentation, such as Javadoc comments.
+That also makes the annotated version inconsistent with the unannotated
+version.
+
+Do not change formatting and whitespace.
+
+Any of these changes would increase the difference between upstream (the original
+version) and your annotated version.  Unnecessary differences make it harder for others to
+understand what you have done, and they make it harder to pull changes from
+upstream into the annotated library.
+
+
+\subsectionAndLabel{Library annotations should reflect the specification, not the implementation}{library-tips-specification}
+
+Publicly-visible annotations (including those on public method formal
+parameters and return types) should be based on the
+documentation, typically the method's Javadoc.
+In other words, your annotations should re-state facts that are in the Javadoc
+documentation.
+
+Do not add requirements or guarantees beyond what the library author has
+already committed to.  If a project's Javadoc says nothing about nullness,
+then you should not assume that the specification forbids or permits null.
+(If the project's Javadoc explicitly mentions null everywhere it is permitted,
+then you can assume it is forbidden elsewhere, where the author omitted those
+statements.)
+
+If a fact is not mentioned in the documentation, then it is usually an
+implementation detail.  Clients should not depend on implementation
+details, which are prone to change without notice.
+(In some cases, you can infer some facts from the
+implementation, such as that null is permitted for a method's parameter or
+return type if the implementation explicitly passes null to the method or
+the method implementation returns null.)
+
+If there is a fact that you think should be in the library's documentation
+but was unintentionally omitted by its authors, then please submit a bug
+report asking them to update the documentation to reflect this fact.  After
+they do, you can also express the fact as an annotation.
+
+% If you wish to depend on your assumption that the behavior will never
+% change, then you can create a local stub file as a workaround while you
+% wait for the library documentation to be updated.
+
+
+\subsectionAndLabel{Report bugs upstream}{library-tips-report-bugs}
+
+While annotating the library, you may discover bugs or missing/wrong
+documentation.  If you have a documentation improvement or a bug fix, then
+open a pull request against the upstream version of the library.  This will
+benefit all users of the library.  And, once the documentation is updated,
+you will be able to add annotations that are consistent with the
+documentation.
+
+
+\subsectionAndLabel{Fully annotate the library, or indicate which parts you did not}{library-tips-fully-annotate}
+
+If you do not annotate all the files in the library, then use
+\refqualclass{framework/qual}{AnnotatedFor} to indicate what files you have
+annotated.
+
+Whenever you annotate any part of a file, fully annotate the file!  That
+is, write annotations for all the methods and fields, based on their
+documentation.  Here are reasons for this rule:
+
+\begin{itemize}
+\item
+  If you annotate just part of the file, then users may be surprised that
+  calls to some methods type-check as expected whereas other methods do not
+  (because they have not been annotated).
+\item
+  Annotating one method or field at a time may lead to inconsistencies
+  between different parts of the file.  Different people may make different
+  assumptions, might write annotations in a way that is locally convenient
+  but globally inconsistent, or might not read all the documentation of the
+  class to understand how it works.
+\item
+  It is not much more effort to annotate an entire class versus one method
+  or field.  In either case it is usually necessary to understand the entire
+  class's design and implementation.  Once you have done that, you might as
+  well annotate the whole thing.
+\item
+  If you fully annotate the file, it is possible to type-check the library to
+  verify the annotations.  (Even if you do not do this right now, it eases
+  the task in the future.)
+\end{itemize}
+
+
+\subsectionAndLabel{Verify your annotations}{library-tips-verify}
+
+Ideally, after you annotate a file, you should type-check the file to verify
+the correctness and completeness of your annotations.
+
+An alternative is to
+only annotate method signatures.  The alternative is quicker but more
+error-prone.  There is no difference from the point of view of clients,
+who can only see annotations on public methods and fields.  When you
+compile the library, the type-checker will probably issue warnings; you can
+supply \<-Awarns> so that the
+compiler will still produce \<.class> files.
+
+
+\sectionAndLabel{Creating an annotated library}{annotating-libraries-create}
+
+This section describes how to create an annotated library.
+
+\begin{enumerate}
+\item See the
+  \ahref{https://search.maven.org/search?q=org.checkerframework.annotatedlib}{the
+  \<org.checkerframework.annotatedlib> group in the Maven Central Repository}
+  to find out whether an annotated version of the library already exists.
+%% This adds clutter to the source code, so omit the step from these instructions.
+% \item Optionally, run a script that adds
+%     \<\refqualclass{framework/qual}{AnnotatedFor}(\ttlcb\ttrcb)>
+%     to each class.  [[TODO: say where to find this script.]]
+%     This step has no semantic effect.  It only saves you the trouble of
+%     typing those 17 characters later.
+
+  \begin{itemize}
+  \item
+    If it exists, but you want to add annotations for a different checker:
+
+    Clone its repository from \url{https://github.com/typetools/}, and tweak
+    its buildfile to run an additional checker.
+
+  \item
+    If it does not already exist:
+
+    Fork the project.  (First, verify that its license permits forking.)
+
+    Add a line in its main README to indicate that this is an annotated
+    version of the library.  That line should also indicate how to obtain
+    the corresponding upstream version (typically a git tag
+    \ahref{https://rawgit.com/typetools/checker-framework/master/docs/developer/developer-manual.html#annotated-library-version-numbers}{corresponding
+      to a release}), so that others can see exactly what edits you have
+    made.
+
+  Adjust the library's
+  build process, such as an Ant, Gradle, or Maven buildfile (for guidance,
+  see \chapterpageref{external-tools}).
+    Every time the build system runs the compiler, it should:
+    \begin{itemize}
+    \item
+      pass the \<-AuseConservativeDefaultsForUncheckedCode=source,bytecode>
+      command-line option and
+    \item
+      run every pluggable type-checker for which any
+      annotations exist, using \<-processor
+      \emph{TypeSystem1},\emph{TypeSystem2},\emph{TypeSystem3}>
+    \end{itemize}
+
+  You are not adding new build targets, but modifying existing targets.
+  The reason to run every type-checker is to verify
+  the annotations you wrote, and to use appropriate defaults for all
+  unannotated type uses.
+  \end{itemize}
+
+\item Annotate some files.
+%  You can determine which files need to be annotated by using the
+%  \<-AprintUnannotatedMethods> command-line argument while type-checking a
+%  client of the library.
+
+  % This is a strong recomendation, but not a requirement.  @AnnotatedFor
+  % can be written on a method as well.
+  When you annotate a file, annotate the whole thing, not just a few of its
+  methods.  Add an
+  \<\refqualclass{framework/qual}{AnnotatedFor}(\ttlcb"\emph{checkername}"\ttrcb)>
+  annotation to each class declaration, or augment an existing \<@AnnotatedFor>
+  annotation.
+
+\item
+  Build the library.
+
+  Because of the changes that you made in step 1, this will run pluggable
+  type-checkers.  If there are any compiler warnings, fix them and re-compile.
+
+  Now you have a \<.jar> file that you can use while type-checking and at
+  run time.
+
+\item
+  Tell other people about your work so that they can benefit from it.
+
+  \begin{itemize}
+  \item
+    Please inform the Checker Framework developers
+    about your new annotated library by opening a pull request or an issue.
+    This will let us add your annotations to a repository in
+    \url{https://github.com/typetools/} and upload a compiled artifact to
+    the Maven Central Repository.
+
+  \item
+    Optionally, encourage the library's maintainers to accept your annotations into its
+    main version control repository.  This suggestion will be most
+    compelling if you have already reported bugs in the library that were
+    revealed by pluggable type-checking.  Once the annotations are in the
+    main version control repository, they will be easier
+    to maintain, the library will obtain the correctness guarantees of
+    pluggable type-checking, and there will be no need
+    to distribute an annotated version of the library.
+
+    If the library maintainers do not accept the annotations, then
+    periodically, such as when a new version of the library is released,
+    pull changes from upstream (the library's main version control system)
+    into your fork, add annotations to any newly-added methods in classes
+    that are annotated with \<@AnnotatedFor>, rebuild to create an updated
+    \<.jar> file, and inform the Checker Framework developers by opening an
+    issue or issuing a pull request.
+  \end{itemize}
+
+\end{enumerate}
+
+
+\sectionAndLabel{Creating an annotated JDK}{annotating-jdk}
+
+When you create a new checker, you need to also supply annotations for
+parts of the JDK\@.  You can do so either as stub files or in a copy of the
+JDK source code, as described in Section~\ref{creating-a-checker-annotated-jdk}.
+Section~\ref{reporting-bugs-annotated-libraries} tells how to improve
+JDK annotations for an existing type system.
+
+
+\sectionAndLabel{Compiling partially-annotated libraries}{compiling-libraries}
+
+If you \emph{completely} annotate a library, then you can compile it using a
+pluggable type-checker.  You get a guarantee that the library contains no
+errors (that is, it is consistent with the specifications you wrote).
+
+The rest of this section tells you how to compile a library if you
+\emph{partially} annotate it:  that is, you write annotations for some of its
+classes but not others.
+(There is another type of partial annotation, which is when you annotate
+method signatures but do not type-check the bodies.
+See Section~\ref{library-tips}.)
+
+Here are two concerns you may have when partially annotating a library:
+
+\begin{itemize}
+\item
+  Ignoring type-checking errors in unannotated parts of the library.
+  Use the \<-AskipDefs> or \<-AonlyDefs> command-line arguments; see
+  Section~\ref{askipdefs}.
+
+\item
+  Putting conservative annotations in unannotated parts of the library.
+  The checker needs to use normal defaulting rules
+  (Section~\ref{climb-to-top}) for code you have annotated and conservative
+  defaulting rules (Section~\ref{defaults-classfile}) for code you have not
+  yet annotated.  This section describes how to do this.  You use
+  \<@AnnotatedFor> to indicate which classes you have annotated.
+\end{itemize}
+
+
+
+\subsectionAndLabel{The \<-AuseConservativeDefaultsForUncheckedCode=source,bytecode> command-line argument}{AuseConservativeDefaultsForUncheckedCodesource}
+
+\begin{sloppypar}
+When compiling a library that is not fully annotated, use command-line
+argument \<-AuseConservativeDefaultsForUncheckedCode=source,bytecode>.  This causes
+the checker to behave normally for classes with a relevant \<@AnnotatedFor>
+annotation.  For classes without \<@AnnotatedFor>, the checker uses
+conservative defaults
+(see Section~\ref{defaults-classfile}) for any type use with no explicit
+user-written annotation, \emph{and} the checker issues no warnings.
+\end{sloppypar}
+
+The \refqualclass{framework/qual}{AnnotatedFor} annotation, written on a
+class, indicates that the class has been annotated for certain type
+systems.  For example, \<@AnnotatedFor(\ttlcb"nullness", "regex"\ttrcb)> means that
+the programmer has written annotations for the Nullness and Regular
+Expression type systems.  If one of those two type-checkers is run,
+the \<-AuseConservativeDefaultsForUncheckedCode=source,bytecode> command-line argument
+has no effect and this class is treated normally:
+unannotated types are defaulted using normal source-code
+defaults and type-checking warnings are issued.
+\refqualclass{framework/qual}{AnnotatedFor}'s arguments are any string that
+may be passed to the \<-processor> command-line argument:  the
+fully-qualified class name for the checker, or a shorthand for built-in
+checkers (see Section~\ref{shorthand-for-checkers}).
+Writing \<@AnnotatedFor> on a class doesn't necessarily mean that you wrote
+any annotations, but that the user examined the source code and verified
+that all appropriate annotations are present.
+
+\begin{sloppypar}
+Whenever you compile a class using the Checker Framework, including when
+using the \<-AuseConservativeDefaultsForUncheckedCode=source,bytecode> command-line
+argument, the resulting \<.class> files are fully-annotated; each type use
+in the \<.class> file has an explicit type qualifier for any checker that
+is run.
+\end{sloppypar}
+
+
+\sectionAndLabel{Using stub classes}{stub}
+
+You use a ``stub file'' to write annotations
+for a library, when you cannot edit and recompile the library.  A
+checker uses the annotated signatures at compile time, instead of or in
+addition to annotations that appear in the library's \<.class> files.
+
+A stub file cannot override the types of a package-private class, method, or field.
+
+A stub class is Java source code that is allowed to omit certain parts,
+such as method bodies.  Section~\ref{stub-format} describes
+the stub file format.
+
+Section~\ref{annotating-jdk} explains how you should choose between
+creating stub classes or creating an annotated library.
+Section~\ref{stub-creating} describes how to create stub classes.
+Section~\ref{stub-using} describes how to use stub classes.
+These sections illustrate stub classes via the example of creating a \refqualclass{checker/interning/qual}{Interned}-annotated
+version of \code{java.lang.String}.  You don't need to repeat these steps
+to handle \code{java.lang.String} for the Interning Checker,
+but you might do something similar for a different class and/or checker.
+
+
+\subsectionAndLabel{Using a stub file}{stub-using}
+
+The \code{-Astubs} argument causes the Checker Framework to read
+annotations from annotated stub classes in preference to the unannotated
+original library classes.  For example:
+
+\begin{myxsmall}
+\begin{Verbatim}
+  javac -processor org.checkerframework.checker.interning.InterningChecker \
+    -Astubs=path/to/String.astub:stubs MyFile.java MyOtherFile.java ...
+\end{Verbatim}
+\end{myxsmall}
+
+Each stub path entry is a stub file (ending with \<.astub>), directory, or
+\<.jar> file; specifying a directory or \<.jar> file is
+equivalent to specifying every file in it whose name ends with
+\code{.astub}.  The stub path entries are delimited by
+\<File.pathSeparator> (`\<:>' for Linux and Mac, `\<;>' for Windows).
+
+A checker automatically reads some of its own stub files, even without a
+\<-Astubs> command-line argument; see
+Section~\ref{creating-a-checker-annotated-jdk}.
+
+User-supplied stub files override a checker's built-in stub files and the
+annotated JDK\@.
+
+
+\subsectionAndLabel{Multiple specifications for a method}{stub-multiple-specifications}
+
+There are three possible sources of information for a given
+element: source files, stub files, and bytecode files.
+Usually source files take precedence over the other two,
+and stub files take precedence over bytecode.
+In other words:
+\begin{itemize}
+\item
+  If file \<A.java> is being compiled, then by default any stub for class
+  \<A> is ignored.  See below for how to change this behavior and respect
+  both types of annotations.
+\item
+  An un-annotated type variable in a stub file is used instead of
+  annotations on a type variable in bytecode.
+  \\
+  Use the \<-AstubWarnIfRedundantWithBytecode> command-line option to get a
+  warning whenever a stub file specification is redundant with bytecode
+  annotations.
+  %% Uncomment when https://tinyurl.com/cfissue/2759 is fixed.
+  % Use the \<-AstubWarnIfOverwritesBytecode> command-line option to get a
+  % warning whenever a stub file overwrites bytecode annotations.
+\item
+  If a stub file does not mention a method or field (and no source is
+  available), its annotations are taken from bytecode.
+\end{itemize}
+
+If a method appears in more than one stub file (or twice in the same
+stub file), then, for each type hierarchy, the last annotation read is used.
+
+The annotated JDK is read as a stub file.
+You can override JDK annotations by providing your own stub file.
+If your stub file contains a JDK method \<m>, then no type annotations from
+the JDK's \<m> are used.  If the JDK's \<m> contains a declaration
+annotation \<@D>, it is used unless your stub file contains a different
+\<@D> annotation for \<m>.
+
+The command-line option \<-AmergeStubsWithSource> tells the checker to use
+both source files and stub files.  The checker permits only values that are
+permitted by \emph{both} the source and stub annotations.  (This is called
+the greatest lower bound, or GLB, of the types.)
+
+As an example of GLB, suppose the source annotation says the value is in
+the range [1..20] and the stub file says the value is in the range
+[11..30].  Since both sources of information are trusted, the value must be
+in the range [11..20].  Equivalently, the GLB of
+\refqualclasswithparams{common/value/qual}{IntRange}{from=1, to=20} and
+\refqualclasswithparams{common/value/qual}{IntRange}{from=11, to=30} is
+\refqualclasswithparams{common/value/qual}{IntRange}{from=11, to=20}.
+As another example, the GLB of
+\refqualclasswithparams{checker/nullness/qual}{KeyFor}{\{"map1", "map2"\}} and
+\refqualclasswithparams{checker/nullness/qual}{KeyFor}{\{"map2", "map3"\}} is
+\refqualclasswithparams{checker/nullness/qual}{KeyFor}{\{"map1", "map2", "map3"\}}
+since the value is known to be a key for all three maps.
+
+
+\subsectionAndLabel{Stub methods in subclasses of the declaring class}{stub-fake-override}
+
+Sometimes, a method's return type is different in a subclass than in a
+superclass, even though the subclass inherits (does not define) the method.
+An example, \<SecureRandom.nextInt()>, is shown below.
+
+To express that method \<m> has a different type in subclass \<Sub> than
+where \<m> is defined, write a ``fake override'':  write the method in in
+class \<Sub> in a stub file.  The Checker Framework will use that type for
+\<m> at calls where the receiver's declared type is \<Sub> or lower.
+
+In a fake override of \<m>, the formal parameter Java types of the method
+must be identical to those where \<m> is defined, but you can change its
+annotations.  (The return type and the receiver type can be different.)
+If a fake override has no annotations on a given type use, that type use is
+defaulted according to the usual defaulting rules.
+
+This feature currently only works for return types.  That is, a fake
+override changes the return type at certain calls, but formal parameter
+type annotations on fake overrides have no effect.  Type annotations on
+fake overrides of fields also have no effect.  Fake overrides will be able
+to change formal parameter and field type annotations in a future release
+of the Checker Framework.
+
+
+\paragraphAndLabel{Example}{stub-fake-override-example}
+
+As an example, consider a type system that tracks whether a value is
+cryptographically secure.  \<@Secure> is a subtype of \<@Insecure>.
+Although \<SecureRandom> does not override \<Random.nextInt> (it overrides
+the source of randomness instead), you are allowed to write the following
+stub file:
+
+\begin{Verbatim}
+package java.util;
+class Random {
+  @Insecure int nextInt();
+}
+
+package java.security;
+class SecureRandom extends Random {
+  @Secure int nextInt();
+}
+\end{Verbatim}
+
+Client code behaves as follows:
+
+\begin{Verbatim}
+Random r = ...;
+SecureRandom sr = ...;
+
+@Secure int i1 = r.nextInt(); // error
+@Secure int i2 = sr.nextInt(); // OK
+\end{Verbatim}
+
+
+\subsectionAndLabel{Stub file format}{stub-format}
+
+Every Java file is a valid stub file.  However, you can omit information
+that is not relevant to pluggable type-checking; this makes the stub file
+smaller and easier for people to read and write.
+Also note that the stub file's extension must be \<.astub>, not \<.java>.
+
+As an illustration, a stub file for the Interning type system
+(Chapter~\ref{interning-checker}) could be:
+
+\begin{Verbatim}
+  import org.checkerframework.checker.interning.qual.Interned;
+  package java.lang;
+  @Interned class Class<T> {}
+  class String {
+    @Interned String intern();
+  }
+\end{Verbatim}
+
+The stub file format is allowed to differ from Java source code in the
+following ways:
+\begin{description}
+
+\item{\textbf{Method bodies:}}
+  The stub class does not require method bodies for classes; any method
+  body may be replaced by a semicolon (\code{;}), as in an interface or
+  abstract method declaration.
+
+\item{\textbf{Method declarations:}}
+  You only have to specify the methods that you need to annotate.
+  Any method declaration may be omitted, in which case the checker reads
+  its annotations from library's \<.class> files.  (If you are using a stub class, then
+  typically the library is unannotated.)
+
+\item{\textbf{Declaration specifiers:}}
+  Declaration specifiers (e.g., \<public>, \<final>, \<volatile>)
+  may be omitted.
+
+\item{\textbf{Return types:}}
+  The return type of a method does not need to match the real method.
+  In particular, it is valid to use \<java.lang.Object> for every method.
+  This simplifies the creation of stub files.
+
+\item{\textbf{Import statements:}}
+  Imports may appear at the beginning of the file or after any package declaration.
+  The only required import statements are the ones to import type
+  annotations.  Import statements for types are optional.
+
+\item{\textbf{Multiple classes and packages:}}
+  The stub file format permits having multiple classes and packages.
+  The packages are separated by a package statement:
+  \<package my.package;>.  Each package declaration may occur only once; in
+  other words, all classes from a package must appear together.
+
+\end{description}
+
+
+
+\subsectionAndLabel{Creating a stub file}{stub-creating}
+
+
+\subsubsectionAndLabel{If you have access to the Java source code}{stub-creating-with-source}
+
+Every Java file is a stub file.  If you have access to the Java file,
+copy file \<A.java> to \<A.astub>.  You can add
+annotations to the signatures, leaving the method bodies unchanged.
+The stub file parser silently ignores any annotations that it cannot
+resolve to a type, so don't forget the \<import> statement.
+
+Optionally (but highly recommended!), run the type-checker to verify that
+your annotations are correct.  When you run the type-checker on your
+annotations, there should not be any stub file that also contains
+annotations for the class.  In particular, if you are type-checking the JDK
+itself, then you should use the \<-Aignorejdkastub> command-line option.
+
+This approach retains the original
+documentation and source code, making it easier for a programmer to
+double-check the annotations.  It also enables creation of diffs, easing
+the process of upgrading when a library adds new methods.  And, the
+annotations are in a format that the library maintainers can even
+incorporate.
+
+The downside of this approach is that the stub files are larger.  This can
+slow down the Checker Framework, because it parses the stub files each time
+it runs.
+% Furthermore, a programmer must search the stub file
+% for a given method rather than just skimming a few pages of method signatures.
+
+Alternatively, you can minimize source files to make them more suitable as stub files.
+Use the \<JavaStubifier> to convert, in-place, all \<.java> files in given directories into
+minimal stub files.
+
+\begin{Verbatim}
+  mkdir project-stubs
+  cp -R project/src project-stubs
+  java -cp $CHECKERFRAMEWORK/checker/dist/checker.jar org.checkerframework.framework.stub.JavaStubifier project-stubs
+  find project-stubs -type f -name "*.java" -exec rename 's/.java$/.astub/' {} \;
+\end{Verbatim}
+
+Supply it a list of directories to process. Replacement happens in-place, so make sure to
+process a copy of your sources.
+
+You can now provide \<project-stubs> as a stub directory using \<-Astubs=project-stubs> as
+an additional command-line option.
+
+If you wish to use a single stub file, rather than a stub directory,
+you can concatenate all the minimized \<.java> files into a single \<.astub> file:
+
+\begin{Verbatim}
+find project-stubs/ -name "*.java" -type f | xargs cat > project.astub
+\end{Verbatim}
+
+% That file will contain many non-unique import statements, but that shouldn't be harmful.
+
+
+%% Changes in JDK 11 have broken StubGenerator.
+% \subsubsectionAndLabel{If you do not have access to the Java source code}{stub-creating-without-source}
+%
+% If you do not have access to the library source code, then you can create a
+% stub file from the class file (Section~\ref{stub-creating}),
+% and then annotate it.  The rest of this section describes this approach.
+%
+%
+% \begin{enumerate}
+%
+% \item
+%   Create a stub file by running the stub class generator.  (\<checker.jar> must be on your classpath.)
+%
+% \begin{Verbatim}
+%   cd nullness-stub
+%   java -cp $CHECKERFRAMEWORK/checker/dist/checker.jar \
+%     org.checkerframework.framework.stub.StubGenerator java.lang.String > String.astub
+% \end{Verbatim}
+%
+%   Supply it with the fully-qualified name of the class for which you wish to
+%   generate a stub class.  The stub class generator prints the
+%   stub class to standard out, so you may wish to redirect its output to a
+%   file.
+%
+% \item
+%   Add import statements for the annotations.  So you would need to
+% add the following import statement at the beginning of the file:
+%
+% \begin{Verbatim}
+%   import org.checkerframework.checker.interning.qual.*;
+% \end{Verbatim}
+%
+% \noindent
+% The stub file parser silently ignores any annotations that it cannot
+% resolve to a type, so don't forget the import statement.
+% Use the \<-AstubWarnIfNotFound> command-line option to see warnings
+% if an entry could not be found.
+%
+% \item
+%   Add annotations to the stub class.  For example, you might annotate
+%   the \sunjavadoc{java.base/java/lang/String.html\#intern()}{String.intern()} method as follows:
+%
+% \begin{Verbatim}
+%   @Interned String intern();
+% \end{Verbatim}
+%
+%   You may also remove irrelevant parts of the stub file; see
+%   Section~\ref{stub-format}.
+%
+% \end{enumerate}
+
+
+\subsectionAndLabel{Distributing stub files}{stub-distributing}
+
+If you are writing stub files but are not writing a checker, you can place
+the stub files anywhere that is convenient.  However, please consider
+providing your stub files to the checker author, so that other users can
+also benefit from the stub files you wrote.
+
+Stub files distributed with a checker appear in the same directory as the
+checker implementation (the \<*Checker.java> file, see
+Section~\ref{creating-parts-of-a-checker}).  For example, in the Checker
+Framework they appear in files such as
+\begin{Verbatim}
+checker/src/main/java/org/checkerframework/checker/regex/apache-xerces.astub
+checker/src/main/java/org/checkerframework/checker/signature/javaparser.astub
+\end{Verbatim}
+
+If you are distributing stub files with a checker implementation, you must
+configure your build system to copy the stub files into the distributed jar for
+your checker.  Here are instructions if you are using the Checker Framework
+Gradle plugin:
+\begin{itemize}
+\item
+  If the stub files appear in the same directory as the checker class, which is
+  a subtype of \<BaseTypeChecker>, use the following build configuration:
+\begin{Verbatim}
+  sourceSets {
+    main {
+        resources {
+            srcDirs += ['src/main/java']
+        }
+    }
+  }
+\end{Verbatim}
+\item
+  If the stub files appear under the \<src/main/resources/>
+  directory in a sub-directory matching the package of your checker class,
+  the default Gradle build configuration is sufficient.
+\end{itemize}
+In either case, recall that a \refqualclass{framework/qual}{StubFiles}
+annotation on the checker class lists stub files that are always used.  For
+stub files whose use is optional (for example, because the behavior is
+unsound, or unsound except in certain circumstances), users must supply the
+\<-Astubs=...> command-line option.
+
+
+If a stub file contains annotations that are used by the framework rather
+than by any specific checker (such as purity annotations), and you wish to
+distribute it with the Checker Framework, put the stub file in directory
+\<checker/resources/>.  You can also do this if the stub file has
+annotations for multiple checkers.  To use a stub file in directory
+\<checker/resources/>, users must supply the
+\<-Astubs=checker.jar/stub-file-name.astub> command-line option.
+
+
+\subsectionAndLabel{Troubleshooting stub libraries}{stub-troubleshooting}
+
+
+\subsubsectionAndLabel{Type-checking does not yield the expected results}{stub-troubleshooting-type-checking-results}
+
+By default, the stub parser silently ignores
+annotations on unknown classes and methods.
+The stub parser also silently ignores unknown annotations, so don't forget to
+\<import> any annotations.
+Some command-line options make the stub parser issue more warnings:
+
+\begin{description}
+\item[\<-AstubWarnIfNotFound>]
+  Warn whenever some element of a stub file cannot be found.
+  This is the default for stub files provided on the command line;
+  supplying the command-line option enables it for built-in stub files as well.
+  The \<@NoStubParserWarning> annotation on a package or type in a stub file
+  overrides the \<-AstubWarnIfNotFound> command-line option, and no warning
+  will be issued.
+
+  % These warnings are not enabled by default because they would produce too
+  % much output.  An example of an innocuous warning is when a stub file is
+  % written for library version B but library version A is on the classpath.  A
+  % warning is issued for every method that is in the stub file but not in
+  % version A which is on the classpath.
+
+\item[\<-AstubWarnIfNotFoundIgnoresClasses>]
+  Modifies the behavior of \<-AstubWarnIfNotFound>
+  to report only missing methods/fields, but ignore missing classes, even if
+  other classes from the same package are present.
+  Useful if a package spans more than one jar.
+
+\item[\<-AstubWarnIfRedundantWithBytecode>]
+  Warn if a stub file entry is redundant with bytecode information.  The
+  warning means that the stub file's type is the same as the bytecode type,
+  so that entry in the stub file has no effect.  You could remove the
+  entry in the stub file to make it shorter, or you could add an annotation
+  to the stub file to make the entry have an effect.
+
+%% Uncomment when https://tinyurl.com/cfissue/2759 is fixed.
+% \item[\<-AstubWarnIfOverwritesBytecode>]
+%   Warn whenever some element of a
+%   stub file overwrites annotations contained in bytecode.
+
+\item[\<-AstubWarnNote>]
+  Issue a ``note'', not a warning, for the \<-AstubWarn*> command-line
+  arguments.  A warning prevents further compilation, but a note permits
+  compilation to proceed.
+
+\end{description}
+
+Finally,
+use command-line option {\bf\<-AstubDebug>} to output debugging messages while
+parsing stub files, including about unknown classes, methods, and
+annotations.  This overrides the \<@NoAnnotationFileParserWarning> annotation.
+
+
+
+\subsubsectionAndLabel{Problems parsing stub libraries}{stub-troubleshooting-parsing}
+
+When using command-line option \<-AstubWarnIfNotFound>,
+an error is issued if a stub file has a typo or the API method does not
+exist.
+
+Fix an error of the form
+\begin{Verbatim}
+AnnotationFileParser: Method isLLowerCase(char) not found in type java.lang.Character
+\end{Verbatim}
+
+\noindent
+by removing the extra ``L'' in the method name.
+
+Fix an error of the form
+\begin{Verbatim}
+AnnotationFileParser: Method enableForegroundNdefPush(Activity,NdefPushCallback)
+      not found in type android.nfc.NfcAdapter
+\end{Verbatim}
+
+\noindent
+by removing the method \<enableForgroundNdefPush(...)> from
+the stub file, because it is not defined in class \<android.nfc.NfcAdapter>
+in the version of the library you are using.
+
+
+\sectionAndLabel{Ajava files}{ajava-files}
+
+There are two types of annotation files, which are files containing annotations that
+can be read by the Checker Framework.
+Section~\ref{stub} describes stub files, which are used by programmers and
+type system designers.
+This section dscribes ajava files.
+
+Ajava files are simply valid Java
+source files whose annotations the Checker Framework can read.
+The Checker Framework can read their annotations that are written
+on anonymous and local classes (annotations in such locations are ignored
+in stub files).
+
+Ajava files are typically read and written by tools, such as whole-program
+inference (see Section~\ref{whole-program-inference}).  This section is
+primarily of interest to the maintainers of those tools.
+
+
+\subsectionAndLabel{Using an Ajava file}{ajava-using}
+
+The \code{-Aajava} command-line argument works like \code{-Astubs} (see
+Section~\ref{stub-using}). It takes a colon-separated list of
+files and directories containing ajava files, which end in the \code{.ajava}
+extension.
+
+For example:
+
+\begin{myxsmall}
+\begin{Verbatim}
+  javac -processor org.checkerframework.checker.interning.InterningChecker \
+    -Aajava=path/to/String.ajava:ajavadir MyFile.java MyOtherFile.java ...
+\end{Verbatim}
+\end{myxsmall}
+
+If there's an annotation in both a source file file and the corresponding a
+source file, the Checker Framework uses the greatest lower bound, as with
+the \code{-AmergeStubsWithSource} option.
+
+
+\subsectionAndLabel{Corresponding source files and ajava files}{ajava-corresponding}
+
+
+If the Checker Framework reads both a source file and an ajava file that
+corresponds to it, then the source file and the ajava file must have a
+specific relationship to one another.
+
+
+\subsubsectionAndLabel{Names of ajava files}{ajava-file-name}
+
+The ajava file must be found below one of the arguments to
+\code{-Aajava}, in a subdirectory matching its package name.
+
+The ajava file must be named
+\code{ClassName-checker.qualified.name.ajava} where
+\code{checker.qualified.name} is the fully qualified name of the checker that
+the ajava file's annotations belong to.
+
+For example, an ajava file with
+tainting annotations for a class \code{outerpackage.innerpackage.MyClass} would
+be located in a subdirectory \code{outerpackage/innerpackage} and it would be
+named
+\code{MyClass-org.checkerframework.checker.tainting.TaintingChecker.ajava}.
+
+
+\subsubsectionAndLabel{Contents of an ajava file}{ajava-contents}
+
+The
+ajava file must contain the same source code as the source file with the following exceptions:
+\begin{itemize}
+  \item The two may differ in annotations.
+  \item The two may have different import statements.
+  \item The ajava file may have explicit receivers where the source file
+    doesn't. If a source file has a method declaration \code{void
+      myMethod()}, the ajava file might contain \code{void myMethod(MyClass
+      this)} or \code{void myMethod(@Interned MyClass this)}.
+  \item The ajava file may have explicit type bounds that are implicit in the source file.
+    If a source file has a type argument \<?>,
+    the ajava file might contain \code{? extends Object} or \code{? extends
+      @Nullable Object}.
+\end{itemize}
+
+
+\subsubsectionAndLabel{Inserting annotations from ajava files}{ajava-insert-annotations}
+
+The \code{InsertAjavaAnnotations.java} program inserts annotations
+from ajava files into Java files.  This can be used to
+insert the annotations inferred by whole program inference into the source code
+of a program, similarly to the
+\ahref{https://checkerframework.org/annotation-file-utilities/\#insert-annotations-to-source}{\code{insert-annotations-to-source}
+  script} from the
+\ahref{https://checkerframework.org/annotation-file-utilities/}{Annotation
+  File Utilities project}.
+
+Its arguments are a directory containing ajava files and a directory
+containing Java files.  The \code{checker.jar} file must be in the classpath.
+Here is an example invocation:
+\begin{Verbatim}
+  java -cp $CHECKERFRAMEWORK/checker/dist/checker.jar \
+    org.checkerframework.framework.ajava.InsertAjavaAnnoations \
+    <path-to-ajava-files> <path-to-java-files>
+\end{Verbatim}
+
+
+\sectionAndLabel{Troubleshooting/debugging annotated libraries}{libraries-troubleshooting}
+
+Sometimes, it may seem that a checker is treating a library as unannotated
+even though the library has annotations.  The compiler has a flags that
+may help you in determining which library files are read.
+
+\begin{description}
+\item \<-verbose>
+  Outputs info about compile phases --- when the compiler
+  reads/parses/attributes/writes any file.  Also outputs the classpath and
+  sourcepath paths.
+\end{description}
+
+A syntax error in a stub file or the annotated JDK can lead to the file
+being silently ignored.  A typo in an annotation name, or a missing
+\<import> statement, can lead to annotations being silently ignored.
+
+
+% LocalWords: plugin utils util dist RuntimeException NonNull TODO AFU enum
+% LocalWords: sourcepath Nullness javac classpath src quals pathSeparator JDKs
+% LocalWords: jdk Astubs skipUses astub AskipUses toArray JDK6 xvzf javax
+% LocalWords: CollectionToArrayHeuristics BaseTypeVisitor Xbootclasspath
+% LocalWords: Interning's UsesObjectEquals ApermitMissingJdk AonlyUses java pre
+% LocalWords: Aignorejdkastub AstubWarnIfNotFound AstubDebug dont local'
+% LocalWords: enableForgroundNdefPush BCEL getopt jdk8
+% LocalWords: NoAnnotationFileParserWarning CHECKERFRAMEWORK AnnotatedFor regex
+% LocalWords: AuseConservativeDefaultsForUnannotatedCode buildfile qual
+% LocalWords: AprintUnannotatedMethods checkername AskipDefs bcel mkdir
+% LocalWords: AuseSafeDefaultsForUnannotatedSourceCode TypeSystem1 cd map1
+% LocalWords: TypeSystem2 TypeSystem3 AuseConservativeDefaultsForUncheckedCode ln
+% LocalWords: mychecker DIRS README TypeSystem un debugJSR org boolean
+% LocalWords: AstubWarnIfOverwritesBytecode Awarns AssumeAssertion map2
+% LocalWords: Makefile PuseLocalJdk jdkShaHash AonlyDefs map3 SecureRandom
+% LocalWords: AuseConservativeDefaultsForUncheckedCodesource nextInt
+% LocalWords: AstubWarnIfNotFoundIgnoresClasses AmergeStubsWithSource
+% LocalWords:  AstubWarnIfRedundantWithBytecode JavaStubifier StubFiles
+% LocalWords:  BaseTypeChecker
diff --git a/docs/manual/called-methods-checker.tex b/docs/manual/called-methods-checker.tex
new file mode 100644
index 0000000..7b28f38
--- /dev/null
+++ b/docs/manual/called-methods-checker.tex
@@ -0,0 +1,249 @@
+\htmlhr
+\chapterAndLabel{Called Methods Checker}{called-methods-checker}
+
+The Called Methods Checker tracks the names of methods that have definitely
+been called on an object. This checker is useful for checking any property
+of the form ``call method A before method B''.
+
+The Called Methods Checker provides built-in support for one such property:
+that clients of the builder pattern for object
+construction always provide all required arguments before calling
+\<build()>.  The builder pattern is a flexible and readable way to
+construct objects, but it is error-prone.  Failing to provide
+a required argument causes a run-time error that manifests during testing
+or in the field, instead of at compile time as for regular Java
+constructors.  The Called Methods Checker verifies at compile time that
+your code correctly uses the builder pattern, never omitting a required
+argument. The Called Methods Checker has built-in support for
+\href{https://projectlombok.org/}{Lombok} (see the caveats about Lombok in
+Section~\ref{called-methods-lombok}) and
+\href{https://github.com/google/auto/blob/master/value/userguide/index.md}{AutoValue}.
+
+You can verify other builders, or verify other properties of the form
+``foo() must be called before bar()'', by writing method specifications.
+Section~\ref{called-methods-other} describes another example related to a
+security property.
+
+If the checker issues no warnings, then you have a guarantee that your code
+supplies all the required information to the builder.  The checker might
+yield a false positive warning when your code is too tricky for it to
+verify.  Please submit an
+\href{https://github.com/typetools/checker-framework/issues}{issue} if you
+discover this.
+
+
+\sectionAndLabel{How to run the Called Methods Checker}{called-methods-run-checker}
+
+\begin{Verbatim}
+javac -processor CalledMethods MyFile.java ...
+\end{Verbatim}
+
+The Called Methods Checker supports the following optional command-line arguments:
+\begin{itemize}
+\item The \<-ACalledMethodsChecker\_disableBuilderFrameworkSupports> option disables automatic
+ annotation inference for builder frameworks.
+ Section~\ref{called-methods-framework-details} describes its syntax.
+ Supply this if you are uninterested in errors in the use of builders, but
+ are using the Called Methods Checker to detect errors in other types of
+ code.
+\item The \<-ACalledMethodsChecker\_disableReturnsReceiver> option disables
+  the Returns Receiver Checker (\chapterpageref{returns-receiver-checker}),
+  which ordinarily runs as a subchecker of the Called Methods Checker.  If
+  the code being checked does not use fluent APIs, then you can supply this
+  option and the Called Methods Checker will run much faster.
+\item The \<-ACalledMethodsChecker\_useValueChecker> option improves precision when analyzing
+ code that uses the AWS SDK's \<DescribeImageRequest> API\@.  See
+ Section~\ref{called-methods-other}.
+\end{itemize}
+
+\sectionAndLabel{For Lombok users}{called-methods-lombok}
+
+The Called Methods Checker supports projects that use Lombok via
+the \href{https://plugins.gradle.org/plugin/io.freefair.lombok}{io.freefair.lombok} Gradle plugin automatically.
+However, note that the checker's error messages refer to Lombok's output, which is a variant of your source code
+that appears in a \<delombok> directory.
+To fix issues, you should edit your original source code, \textbf{not} the files in the checker's error messages.
+
+If you use Lombok with a build system other than Gradle, you must configure it to do two tasks.
+If either of these is not done, the checker will not issue any errors on Lombok code.
+\begin{itemize}
+\item set Lombok configuration option \<lombok.addLombokGeneratedAnnotation = true>
+\item delombok the code before passing it to the checker
+\end{itemize}
+
+
+\sectionAndLabel{Specifying your code}{called-methods-spec}
+
+The Called Methods Checker reads method specifications (contracts) that
+state what a method requires when it is called.  It warns if method
+arguments do not satisfy the method's specification.
+
+If you use AutoValue or Lombok, most specifications are automatically
+inferred by the Called Methods Checker, from field annotations such as
+\<@Nullable> and field types such as
+\<Optional>. Section~\ref{called-methods-framework-details} gives
+defaulting rules for Lombok and AutoValue.
+
+\begin{figure}
+\begin{center}
+  \hfill
+  \includeimage{calledmethods}{5cm}
+  \hfill
+\end{center}
+  \caption{The type hierarchy for the Called Methods type system, for an object with two methods: \<a()> and \<b()>.
+  Types displayed in gray should rarely be written by the programmer.}
+  \label{fig-called-methods-types}
+\end{figure}
+
+In some cases, you may need to specify your code. You do so by writing one of the following type
+annotations (Figure~\ref{fig-called-methods-types}):
+\begin{description}
+\item[\refqualclasswithparams{checker/calledmethods/qual}{CalledMethods}{String[] methodNames}]
+  The annotated type represents values on which all the given method were definitely called.
+  (Other methods might also have been called.) \<@CalledMethods()>, with no
+  arguments, is the default annotation.
+
+  Suppose that the method \<build> is annotated as
+
+  \begin{Verbatim}
+  class MyObjectBuilder {
+    MyObject build(@CalledMethods({"setX", "setY"}) MyObjectBuilder this) { ... }
+  }
+  \end{Verbatim}
+
+  Then the receiver for any call to \<build()> must have had \<setX()> and \<setY()> called on it.
+
+\item[\refqualclasswithparams{checker/calledmethods/qual}{CalledMethodsPredicate}{String expression}]
+  The boolean expression specifies the required method calls.  The string
+  is a boolean expression composed of method names, disjunction (\<||>),
+  conjunction (\<\&\&>), not (\<!>), and parentheses.
+
+  For example, the annotation \<@CalledMethodsPredicate("x \&\& y || z")> on a type represents
+  objects such that either both the  \<x()> and \<y()> methods have been called on the object, \textbf{or}
+  the \<z()> method has been called on the object.
+
+  A note on the not operator (\<!>): the annotation
+  \<@CalledMethodsPredicate("!m")> means ``it is not true \<m> was
+  definitely called''; equivalently ``there is some path on which \<m> was
+  not called''.  The annotation \<@CalledMethodsPredicate("!m")> does
+  \emph{not} mean ``\<m> was not called''.
+
+  The Called Methods Checker does not have a way of expressing that a
+  method must never be called.  You can do unsound bug-finding for such a
+  property by using the \<!> operator.  The Called Methods Checker will
+  detect if the method was always called, but will silently approve the code
+  if the method is called on some but not all paths.
+
+\item[\refqualclass{common/returnsreceiver/qual}{This}]
+  \<@This> may only be written on a method return type, and means that the method returns its receiver.
+  This is helpful when type-checking fluent APIs. This annotation is defined by the
+  Returns Receiver Checker (\chapterpageref{returns-receiver-checker}), but is particularly useful
+  for the Called Methods Checker because many builders are fluent APIs.
+
+\item[\refqualclass{checker/calledmethods/qual}{CalledMethodsBottom}]
+  The bottom type for the Called Methods hierarchy. Conceptually, this annotation
+  means that all possible methods have been called on the object. Programmers should rarely,
+  if ever, need to write this annotation---write an appropriate \<@CalledMethods> annotation
+  instead.
+
+\end{description}
+
+There are also method annotations:
+
+\begin{description}
+\item[\refqualclass{checker/calledmethods/qual}{EnsuresCalledMethods}]
+  This declaration annotation specifies a post-condition on a method, indicating the methods it
+  guarantees to be called.
+
+  For example, this specification:
+
+  \begin{Verbatim}
+    @EnsuresCalledMethods(value = "#1", methods = {"x", "y"})
+    void m(Param p) { ... }
+  \end{Verbatim}
+
+  guarantees that \<p.x()> and \<p.y()> will always be called before \<m> returns.
+  The body of \<m> must satisfy that property, and clients of \<m> can depend on the property.
+
+\item[\refqualclass{checker/calledmethods/qual}{EnsuresCalledMethodsIf}]
+  This declaration annotation specifies a post-condition on a method, indicating the methods it
+  guarantees to be called if it returns a given result.
+
+  For example, this specification:
+
+  \begin{Verbatim}
+    @EnsuresCalledMethodsIf(expression = "#1", methods = {"x", "y"}, result=true)
+    boolean m(Param p) { ... }
+  \end{Verbatim}
+
+  guarantees that \<p.x()> and \<p.y()> will always be called if \<m> returns \<true>.
+  The body of \<m> must satisfy that property, and clients of \<m> can depend on the property.
+\end{description}
+
+\sectionAndLabel{Default handling for Lombok and AutoValue}{called-methods-framework-details}
+
+This section explains how the Called Methods Checker infers types for code
+that uses the Lombok and AutoValue frameworks. Most readers can skip these
+details.
+
+You can disable the builder framework support by specifying them in a
+comma-separated lowercase list to the command-line flag
+\<disableBuilderFrameworkSupports>.  For example, to disable both Lombok
+and AutoValue support, use: \\
+\<-ACalledMethodsChecker\_disableBuilderFrameworkSupports=autovalue,lombok>
+
+The Called Methods Checker automatically assumes default annotations for code that uses builders generated
+by Lombok and AutoValue. There are three places annotations are usually assumed:
+\begin{itemize}
+\item A \<@CalledMethods> annotation is placed on the receiver of the
+  \<build()> method, indicating the setter methods that must be invoked on
+  the builder before calling \<build()>. For Lombok, this annotation's
+  argument is the set of \<@lombok.NonNull> fields that do not have default
+  values.  For AutoValue, it is the set of fields that are not
+  \<@Nullable>, \<Optional>, or a Guava Immutable Collection.
+\item The return type of a \<toBuilder()> method (for example, if the
+  \<toBuilder = true> option is passed to Lombok's \<@Builder> annotation)
+  is annotated with the same \<@CalledMethods> annotation as the receiver
+  of \<build()>, using the same rules as above.
+\item A \<@This> annotation is placed on the return type of each setter in
+  the builder's implementation.
+\end{itemize}
+
+If your program directly defines any of these methods (for example, by adding your own setters to
+a Lombok builder), you may need to write the annotations manually.
+
+Minor notes/caveats on these rules:
+\begin{itemize}
+\item Lombok fields annotated with \<@Singular> will be treated as defaulted (i.e., not required), because
+Lombok will set them to empty collections if the corresponding setter is not called.
+\item If you manually provide defaults to a Lombok builder (for example, by defining the builder yourself
+and assigning a default value to the builder's field), the checker will treat that field as defaulted
+\emph{most} of the time. In particular, it will not treat it as defaulted if it is defined in bytecode rather
+than in source code.
+\end{itemize}
+
+
+\sectionAndLabel{Using the Called Methods Checker for properties unrelated to builders}{called-methods-other}
+
+The Called Methods Checker can be used to verify any property of the form
+``always call A before B'', even if the property is unrelated to builders.
+
+For example, consider the AWS EC2 \<describeImages> API, which
+clients use during the process of initializing a new cloud instance.
+\href{https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-15869}{CVE-2018-15869}
+describes how an improperly-configured request to this API can make the
+requesting client vulnerable to a ``machine-image sniping'' attack that
+would allow a malicious third-party to control the operating system image
+used to initialize the machine. To prevent this attack, clients must
+specify some trusted source for the image by calling the \<withOwners> or
+\<withImageIds> methods on the request prior to sending it to AWS\@. Using
+a stub file for the \<describeImages> API
+(\href{https://github.com/typetools/checker-framework/blob/master/checker/src/main/java/org/checkerframework/checker/calledmethods/DescribeImages.astub}{DescribeImages.astub}),
+the Called Methods Checker can prove that a client is not vulnerable to
+such an attack.
+
+To improve precision, you can specify the
+\<-ACalledMethodsChecker\_useValueChecker> command-line option, which
+instructs the checker to treat provably-safe calls to the \<withFilters>
+method of a \<DescribeImagesRequest> as equivalent to the \<withOwners> or
+\<withImageIds> methods.
diff --git a/docs/manual/constant-value-checker.tex b/docs/manual/constant-value-checker.tex
new file mode 100644
index 0000000..6775c88
--- /dev/null
+++ b/docs/manual/constant-value-checker.tex
@@ -0,0 +1,402 @@
+\htmlhr
+\chapterAndLabel{Constant Value Checker}{constant-value-checker}
+
+The Constant Value Checker is a constant propagation analysis: for
+each variable, it determines whether that variable's value can be
+known at compile time.
+
+There are two ways to run the Constant Value Checker.
+\begin{itemize}
+\item
+Typically, it is automatically run by another type checker.
+\item
+Alternately, you can run just the Constant Value Checker, by
+supplying the following command-line options to javac:
+\code{-processor org.checkerframework.common.value.ValueChecker}
+\end{itemize}
+
+
+\sectionAndLabel{Annotations}{constant-value-checker-annotations}
+
+The Constant Value Checker uses type annotations to indicate the value of
+an expression (Section~\ref{constant-value-checker-type-annotations}), and
+it uses method annotations to indicate methods that the Constant Value
+Checker can execute at compile time
+(Section~\ref{constant-value-staticallyexecutable-annotation}).
+
+
+\subsectionAndLabel{Type Annotations}{constant-value-checker-type-annotations}
+
+Typically, the programmer does not write any type annotations.  Rather, the
+type annotations are inferred by the Constant Value Checker.
+The programmer is also permitted to write type annotations.  This is only necessary in
+locations where the Constant Value Checker does not infer annotations:  on fields
+and method signatures.
+
+The main type annotations are
+\refqualclass{common/value/qual}{BoolVal},
+\refqualclass{common/value/qual}{IntVal},
+\refqualclass{common/value/qual}{IntRange},
+\refqualclass{common/value/qual}{DoubleVal},
+\refqualclass{common/value/qual}{StringVal},
+\refqualclass{common/value/qual}{MatchesRegex},
+and \refqualclass{common/value/qual}{EnumVal}.
+Additional type annotations for arrays and strings are
+\refqualclass{common/value/qual}{ArrayLen},
+\refqualclass{common/value/qual}{ArrayLenRange},
+and \refqualclass{common/value/qual}{MinLen}.
+A polymorphic qualifier (\refqualclass{common/value/qual}{PolyValue})
+is also supported (see Section~\ref{method-qualifier-polymorphism}).
+In addition, there are separate checkers for
+\refqualclass{common/reflection/qual}{ClassVal} and
+\refqualclass{common/reflection/qual}{MethodVal} annotations
+(see Section~\ref{methodval-and-classval-checkers}).
+
+Each \<*Val> type annotation takes as an argument a set of values, and its
+meaning is that at run time, the expression evaluates to one of the values.  For
+example, an expression of type
+\<\refqualclass{common/value/qual}{StringVal}("a", "b")> evaluates to
+one of the values \<"a">, \<"b">, or \<null>.
+The set is limited to 10 entries; if a variable
+could be more than 10 different values, the Constant Value
+Checker gives up and its type becomes
+\refqualclass{common/value/qual}{IntRange} for integral types,
+\refqualclass{common/value/qual}{ArrayLenRange} for array types,
+\refqualclass{common/value/qual}{MatchesRegex},
+\refqualclass{common/value/qual}{ArrayLen}, or
+\refqualclass{common/value/qual}{ArrayLenRange} for \<String>, and
+\refqualclass{common/value/qual}{UnknownVal} for all other types.
+The \<@ArrayLen> annotation means that at run time, the expression
+evaluates to an array or a string whose length is one of the annotation's arguments.
+
+In the case of too many strings in \<@StringVal>, the values are forgotten
+and just the lengths are used in \<@ArrayLen>.
+If this would result in too many lengths,
+only the minimum and maximum lengths are used in \<@ArrayLenRange>,
+giving a range of possible lengths of the string.
+
+The \<@StringVal> and \<@MatchesRegex> annotations may be applied to char arrays.  Although byte
+arrays are often converted to/from strings, these annotations may
+not be applied to them.  This is because the conversion depends on the
+platform's character set.
+
+The \<@MatchesRegex> annotation uses the standard Java regular expression syntax.
+\<@MatchesRegex(A)> is only a subtype of \<@MatchesRegex(B)> if the set of regular
+expressions \<A> is a subset of the set of regular expressions \<B>. An
+\<@StringVal> annotation is a subtype of an \<@MatchesRegex> annotation if
+each string matches at least one of the regular expressions.  Matching is done
+via the
+\href{https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html\#matches(java.lang.String)}{java.lang.String\#matches}
+method, which matches against the entire string (it does not look for a
+matching substring).
+
+The \<@EnumVal> annotation's argument is the names of the enum constants
+that the type might evaluate to.  (Java syntax does not allow the enum
+constants themselves to be arguments.)  \<@EnumVal>
+is treated identically to \<@StringVal> by the checker internally, so
+\<@StringVal> may appear in error messages related to enums.
+
+% \refqualclass{checker/value/qual}{BottomVal}, meaning that the expression
+% is dead or always has the value \<null>.
+
+\refqualclass{common/value/qual}{IntRange} takes two arguments --- a lower
+bound and an upper bound.  Its meaning is that at run time, the expression
+evaluates to a value between the bounds (inclusive).  For example, an
+expression of type \<@IntRange(from=0, to=255)> evaluates to
+0, 1, 2, \ldots, 254, or 255.
+An \refqualclass{common/value/qual}{IntVal} and
+\refqualclass{common/value/qual}{IntRange} annotation that represent the
+same set of values are semantically identical and interchangeable:  they
+have exactly the same meaning, and using either one has the same effect.
+\refqualclass{common/value/qual}{ArrayLenRange} has the same relationship
+to \refqualclass{common/value/qual}{ArrayLen} that
+\refqualclass{common/value/qual}{IntRange} has to
+\refqualclass{common/value/qual}{IntVal}.
+The \<@MinLen> annotation is an alias for \<@ArrayLenRange> (meaning that every \<@MinLen> annotation
+ is automatically converted to an \<@ArrayLenRange> annotation) that only takes
+one argument, which is the lower bound of the range. The upper bound of the
+range is the maximum integer value.
+
+Figure~\ref{fig-value-hierarchy} shows the
+subtyping relationship among the type annotations.
+For two annotations of the same type, subtypes have a smaller set of
+possible values, as also shown in the figure.
+Because \<int> can be casted to \<double>, an \<@IntVal> annotation is a
+subtype of a \<@DoubleVal> annotation with the same values.
+
+\begin{figure}
+\includeimage{value-subtyping}{7.9cm}
+\caption{At the top, the type qualifier hierarchy of the Constant Value Checker
+  annotations.
+  The first four qualifiers are applicable to primitives and their
+  wrappers; the next to \<String>s (it can also be written as \<@EnumVal> for
+  enumeration constants), and the final two to arrays.
+Qualifiers in gray are used
+internally by the type system but should never be written by a
+programmer.  At the bottom are examples of additional subtyping
+relationships that depend on the annotations' arguments.}
+\label{fig-value-hierarchy}
+\end{figure}
+
+Figure~\ref{fig-value-multivalue} illustrates how the Constant Value Checker
+infers type annotations (using flow-sensitive type qualifier refinement, Section~\ref{type-refinement}).
+
+\begin{figure}
+\begin{Verbatim}
+public void flowSensitivityExample(boolean b) {
+    int i = 1;     // i has type:  @IntVal({1}) int
+    if (b) {
+        i = 2;     // i now has type:  @IntVal({2}) int
+    }
+                   // i now has type:  @IntVal({1,2}) int
+    i = i + 1;     // i now has type:  @IntVal({2,3}) int
+}
+\end{Verbatim}
+\caption{The Constant Value Checker infers different types
+  for a variable on different lines of the program.}
+\label{fig-value-multivalue}
+\end{figure}
+
+
+\sectionAndLabel{Other constant value annotations}{other-constant-value-annotations}
+
+The Checker Framework's constant value annotations are similar to annotations used
+elsewhere.
+
+If your code is already annotated with a different constant value or range
+annotation, the Checker Framework can type-check your code.
+It treats annotations from other tools
+as if you had written the corresponding annotation from the
+Constant Value Checker, as described in Figure~\ref{fig-constant-value-refactoring}.
+If the other annotation is a declaration annotation, it may be moved; see
+Section~\ref{declaration-annotations-moved}.
+
+
+% These lists should be kept in sync with ValueAnnotatedTypeFactory.java .
+\begin{figure}
+\begin{center}
+% The ~ around the text makes things look better in Hevea (and not terrible
+% in LaTeX).
+\begin{tabular}{ll}
+\begin{tabular}{|l|}
+\hline
+ ~android.support.annotation.IntRange~ \\ \hline
+\end{tabular}
+&
+$\Rightarrow$
+~org.checkerframework.checker.common.value.qual.IntRange~
+\end{tabular}
+\end{center}
+%BEGIN LATEX
+\vspace{-1.5\baselineskip}
+%END LATEX
+\caption{Correspondence between other constant value and range annotations
+  and the Checker Framework's annotations.}
+\label{fig-constant-value-refactoring}
+\end{figure}
+
+The Constant Value Checker trusts the
+\refqualclass{checker/index/qual}{Positive},
+\refqualclass{checker/index/qual}{NonNegative},
+and \refqualclass{checker/index/qual}{GTENegativeOne} annotations.  If your code
+contains any of these annotations, then
+in order to guarantee soundness, you must run the Index Checker whenever
+you run the Constant Value Checker.
+
+
+\subsectionAndLabel{Compile-time execution of expressions}{constant-value-compile-time-execution}
+
+Whenever all the operands of an expression are compile-time constants (that
+is, their types have constant-value type annotations), the Constant Value
+Checker attempts to execute the expression.  This is independent of any
+optimizations performed by the compiler and does not affect the code that
+is generated.
+
+The Constant Value Checker statically executes (at compile time) operators that do
+not throw exceptions (e.g., \<+>, \<->, \code{<}\code{<}, \<!=>).
+
+
+\subsectionAndLabel{\<@StaticallyExecutable> methods and the classpath}{constant-value-staticallyexecutable-annotation}
+
+The Constant Value Checker statically executes (at compile time) methods annotated with
+\refqualclass{common/value/qual}{StaticallyExecutable}.
+
+\begin{figure}
+\begin{Verbatim}
+@StaticallyExecutable @Pure
+public static int myAdd(int a, int b) {
+    return a + b;
+}
+
+public void bar() {
+    int a = 5;            // a has type:  @IntVal({5}) int
+    int b = 4;            // b has type:  @IntVal({4}) int
+    int c = myAdd(a, b);  // c has type:  @IntVal({9}) int
+}
+\end{Verbatim}
+\caption{The
+  \refqualclass{common/value/qual}{StaticallyExecutable} annotation enables
+  constant propagation through method calls.}
+\label{fig-staticallyexecutable}
+\end{figure}
+
+The static execution feature has some requirements:
+
+\begin{itemize}
+\item
+  A \<@StaticallyExecutable> method must be
+  \refqualclass{dataflow/qual}{Pure} (side-effect-free and deterministic).
+
+\item
+  The Constant Value Checker must have an estimate for all the arguments at
+  a call site.
+
+  This means that \<@StaticallyExecutable> is not applicable
+  to user-written instance methods.  It is only applicable to instance
+  methods whose receiver is a compile-time constant, such as a primitive
+  wrapper or an array.
+
+\item
+  The \<@StaticallyExecutable> method and any method it calls must be on
+  the same path (the classpath or the processorpath) as the Checker
+  Framework.  This is because the Constant Value Checker reflectively calls
+  these methods at compile time.
+
+To use \<@StaticallyExecutable> on methods in your own code, you should
+first compile the code without the Constant Value Checker and then add
+the location of the resulting \code{.class} files to the
+classpath or processorpath, whichever is appropriate. For example, the
+command-line arguments to the Checker Framework
+might include:
+\begin{Verbatim}
+  -processor org.checkerframework.common.value.ValueChecker
+  -classpath $CLASSPATH:MY_PROJECT/build/
+\end{Verbatim}
+or
+\begin{Verbatim}
+  -processor org.checkerframework.common.value.ValueChecker
+  -processorpath ${CHECKERFRAMEWORK}/checker/build/libs/checker-3.6.1-SNAPSHOT.jar:MY_PROJECT/build/
+\end{Verbatim}
+
+\end{itemize}
+
+
+\sectionAndLabel{Warnings}{value-checker-warnings}
+
+If the option \code{-AreportEvalWarns} options is used, the Constant Value Checker issues a warning if it cannot load and run, at
+compile time, a method marked as \<@StaticallyExecutable>.  If it issues
+such a warning, then the return value of the method will be \<@UnknownVal>
+instead of being able to be resolved to a specific value annotation.
+Some examples of these:
+% This section describes potentially-confusing messages, not every message.
+
+\begin{sloppypar}
+\begin{itemize}
+\item \code{[class.find.failed] Failed to find class named Test.}
+
+  The checker could not find the class
+  specified for resolving a \<@StaticallyExecutable> method. Typically
+  this means that the path that contains the Checker Framework (the
+  classpath or the processorpath) lacks the given classfile.
+
+\item \code{[method.find.failed] Failed to find a method named foo with argument types [@IntVal(3) int].}
+
+  The checker could not find the method \code{foo(int)} specified for
+  resolving a \<@StaticallyExecutable> method, but could find the
+  class. This is usually due to providing an outdated version of the
+  classfile that does not contain the
+  method that was annotated as \<@StaticallyExecutable>.
+
+\item \code{[method.evaluation.exception] Failed to evaluate method public static int Test.foo(int) because it threw an exception: java.lang.ArithmeticException: / by zero.}
+
+  An exception was thrown when trying to statically execute (at compile time) the
+  method. In this case it was a divide-by-zero exception. If the
+  arguments to the method each only had one value in their annotations
+  then this exception will always occur when the program is actually
+  run as well. If there are multiple possible values then the exception
+  might not be thrown on every execution, depending on the run-time values.
+
+\end{itemize}
+\end{sloppypar}
+
+There are some other situations in which the Constant Value Checker produces a
+warning message:
+
+\begin{sloppypar}
+\begin{itemize}
+\item \code{[too.many.values.given] The maximum number of arguments permitted is 10.}
+
+  The Constant Value Checker only tracks up to 10 possible values for an
+  expression.  If you write an annotation with more values than will be
+  tracked, the annotation is replaced with \<@IntRange>, \<@ArrayLen>, \<@ArrayLenRange>, or \<@UnknownVal>.
+
+\end{itemize}
+\end{sloppypar}
+
+
+\sectionAndLabel{Unsoundly ignoring overflow}{value-checker-overflow}
+
+The Constant Value Checker takes Java's overflow rules into account when
+computing the possible values of expressions.
+%
+The \code{-AignoreRangeOverflow} command-line option makes it ignore the
+possibility of overflow for range annotations
+\refqualclass{common/value/qual}{IntRange} and
+\refqualclass{common/value/qual}{ArrayLenRange}.
+%
+Figure~\ref{fig-value-ignore-overflow} gives an example of behavior with
+and without the \code{-AignoreRangeOverflow} command-line option.
+
+\begin{figure}
+\begin{Verbatim}
+  ...
+  if (i > 5) {
+    // i now has type:  @IntRange(from=5, to=Integer.MAX_VALUE)
+    i = i + 1;
+    // If i started out as Integer.MAX_VALUE, then i is now Integer.MIN_VALUE.
+    // i's type is now @IntRange(from=Integer.MIN_VALUE, to=Integer.MAX_VALUE).
+    // When ignoring overflow, i's type is now @IntRange(from=6, to=Integer.MAX_VALUE).
+  }
+\end{Verbatim}
+\caption{With the \code{-AignoreRangeOverflow} command-line option,
+the Constant Value Checker ignores overflow
+for range types, which gives smaller ranges to range types.}
+\label{fig-value-ignore-overflow}
+\end{figure}
+
+As with any unsound behavior in the Checker Framework, this option reduces
+the number of warnings and errors produced, and may reduce the number of
+\<@IntRange> qualifiers that you need to write in the source code.
+However, it is possible that at run time, an expression might evaluate to a
+value that is not in its \<@IntRange> qualifier.  You should either accept
+that possibility, or verify the lack of overflow using some other tool or
+manual analysis.
+
+
+\sectionAndLabel{Strings can be null in concatenations}{non-null-strings-concats}
+
+By default, the Constant Value Checker is sound with respect to string
+concatenation and nullness.  It assumes that, in a string concatenation,
+every non-primitive argument might be null, except for String literals
+and compile-time constants. It ignores Nullness Checker annotations.
+(This behavior is conservative but sound.)
+
+Consider a variable declared as
+\<\refqualclass{common/value/qual}{StringVal}("a", "b") String x;>.
+At run time, \<x> evaluates to one of the values \<"a">, \<"b">, or
+\<null>.
+Therefore, the type of ``\<x + "c">'' is
+\<@StringVal("ac", "bc", "nullc") String>.
+
+The \code{-AnonNullStringsConcatenation} command-line option makes the
+Constant Value Checker unsoundly assume that no arguments in a string
+concatenation are null.
+With the command-line argument, the type of ``\<x + "c">'' is
+\<@StringVal("ac", "bc") String>.
+
+%%  LocalWords:  UnknownVal StringValue BottomVal astub Astubs IntRange bc
+%%  LocalWords:  StaticallyExecutable BoolVal IntVal DoubleVal StringVal
+%%  LocalWords:  classpath AreportEvalWarns ArrayLen ArrayLenRange casted
+%%  LocalWords:  qual AignoreRangeOverflow MinLen PolyValue GTENegativeOne
+%%  LocalWords:  staticallyexecutable concats AnonNullStringsConcatenation
+%%  LocalWords:  ClassVal MethodVal processorpath nullc
diff --git a/docs/manual/contributors.tex b/docs/manual/contributors.tex
new file mode 100644
index 0000000..6120ecd
--- /dev/null
+++ b/docs/manual/contributors.tex
@@ -0,0 +1,120 @@
+Abraham Lin,
+Adian Qian,
+Aditya Singh,
+Akash Srivastava,
+Alvin Abdagic,
+Anant Jain,
+Anatoly Kupriyanov,
+Andy Turner,
+Arie van Deursen,
+Artem Pyanykh,
+Arthur Baars,
+Ashish Rana,
+Asumu Takikawa,
+Atul Dada,
+Ayush Agarwal,
+Baorui Zhou,
+Basil Peace,
+Benno Stein,
+Bohdan Sharipov,
+Brian Corcoran,
+Calvin Loncaric,
+Charles Chen,
+Charlie Garrett,
+Chris Povirk,
+Chris Toxiadis,
+Christopher Mackie,
+Colin S. Gordon,
+Craig Day,
+Dan Brotherston,
+Dan Brown,
+David Lazar,
+David McArthur,
+Di Wang,
+Dilraj Singh,
+Dmitriy Shepelev,
+Eric Spishak,
+Felipe R. Monteiro,
+Gautam Korlam,
+Google Inc.\ (via @wmdietlGC),
+Haaris Ahmed,
+Heath Borders,
+Jakub Vr\'ana,
+Jason Waataja,
+Javier Thaine,
+Jeff Luo,
+Jenny Xiang,
+Jianchu Li,
+Jiangqi Zhang,
+Jiasen (Jason) Xu,
+Joe Schafer,
+John Vandenberg,
+Jonathan Burke,
+Jonathan Nieder,
+Jugal Mistry,
+Kartikeya Goswami,
+Kivanc Muslu,
+Konstantin Weitz,
+Leo Liu,
+Liam Miller-Cushon,
+Lian Sun,
+Luqman Aden,
+L\'azaro Clapp,
+Mahmood Ali,
+Manu Sridharan,
+Mark Roberts,
+Martin Kellogg,
+Matt Mullen,
+Maximilian Gama,
+Michael Bayne,
+Michael Coblenz,
+Michael Ernst,
+Michael Hixson,
+Michael Sloan,
+Michal Stehlik,
+Mier Ta,
+Mrigank Arora,
+Narges Shadab,
+Nhat Dinh,
+Nhat Nguyen,
+Nikhil Shinde,
+Nima Karimipour,
+Nitin Kumar Das,
+Oleg Shchelykalnov,
+Olek Wojnar,
+Pascal Wittmann,
+Patrick Meiring,
+Paul Vines,
+Paulo Barros,
+Philip Lai,
+Prionti Nasir,
+Priti Chattopadhyay,
+Rashmi Mudduluru,
+Ravi Roshan,
+Renato Athaydes,
+Ren\'e Just,
+Ren\'e Kraneis,
+Ruturaj Mohanty,
+Ryan Oblak,
+Sadaf Tajik,
+Sagar Tewari,
+Sean C. Sullivan,
+Sean McLaughlin,
+Shinya Yoshida,
+Shubham Raj,
+Sidney Monteiro,
+Stefan Heule,
+Steph Dietzel,
+Stuart Pernsteiner,
+Suzanne Millstein,
+Thomas Wei\ss schuh,
+Tony Wang,
+Trask Stalnaker,
+Travis Haagen,
+Utsav Oza,
+Vatsal Sura,
+Vladimir Sitnikov,
+Vlastimil Dort,
+Weitian Xing,
+Werner Dietl,
+Zhiping Cai.
diff --git a/docs/manual/creating-a-checker.tex b/docs/manual/creating-a-checker.tex
new file mode 100644
index 0000000..6265a41
--- /dev/null
+++ b/docs/manual/creating-a-checker.tex
@@ -0,0 +1,2483 @@
+\htmlhr
+\chapterAndLabel{How to create a new checker}{creating-a-checker}
+\label{writing-a-checker} % for old links; don't use any more!
+
+\newcommand{\TreeAPIBase}{https://docs.oracle.com/en/java/javase/11/docs/api/jdk.compiler/com/sun/source}
+\newcommand{\refTreeclass}[2]{\href{\TreeAPIBase{}/#1/#2.html?is-external=true}{\<#2>}}
+\newcommand{\ModelAPIBase}{https://docs.oracle.com/en/java/javase/11/docs/api/java.compiler/javax/lang/model}
+\newcommand{\refModelclass}[2]{\href{\ModelAPIBase{}/#1/#2.html?is-external=true}{\<#2>}}
+
+This chapter describes how to create a checker
+--- a type-checking compiler plugin that detects bugs or verifies their
+absence.  After a programmer annotates a program,
+the checker verifies that the code is consistent
+with the annotations.
+If you only want to \emph{use} a checker, you do not need to read this
+chapter.
+People who wish to edit the Checker Framework source code or
+make pull requests should read the
+\ahref{https://rawgit.com/typetools/checker-framework/master/docs/developer/developer-manual.html}{Checker
+  Framework Developer Manual}.
+
+
+Writing a simple checker is easy!  For example, here is a complete, useful
+type-checker:
+
+\begin{Verbatim}
+import java.lang.annotation.Documented;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import org.checkerframework.common.subtyping.qual.Unqualified;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(Unqualified.class)
+public @interface Encrypted {}
+\end{Verbatim}
+
+This checker is so short because it builds on the Subtyping Checker
+(Chapter~\ref{subtyping-checker}).
+See Section~\ref{subtyping-example} for more details about this particular checker.
+When you wish to create a new checker, it is often easiest to begin by
+building it declaratively on top of the Subtyping Checker, and then return to
+this chapter when you need more expressiveness or power than the Subtyping
+Checker affords.
+
+Three choices for creating your own checker are:
+\begin{itemize}
+\item
+  Customize an existing checker.
+  Checkers that are designed for extension include
+  the Subtyping Checker (\chapterpageref{subtyping-checker}),
+  the Accumulation Checker (\chapterpageref{accumulation-checker}),
+  the Fake Enumeration Checker (\chapterpageref{fenum-checker}),
+  and the Units Checker (\chapterpageref{units-checker}).
+\item
+  Follow the instructions in this chapter to create a checker from scratch.
+  This enables creation of checkers that are more powerful than customizing
+  an existing checker.
+\item
+  Copy and then modify a different existing checker --- whether
+  one distributed with the Checker Framework or a third-party one.
+  You can get tangled up if you don't fully understand
+  the subtleties of the existing checker that you are modifying.
+  Usually, it is easier to follow the instructions in this chapter.
+  (If you are going to copy a checker, one good choice to copy and modify
+  is the Regex Checker (\chapterpageref{regex-checker}).  A bad choice is
+  the Nullness Checker (\chapterpageref{nullness-checker}),
+  which is more sophisticated than anything you want to start out building.)
+\end{itemize}
+
+You do not need all of the details in this chapter, at least at first.
+In addition to reading this chapter of the manual, you may find it helpful
+to examine the implementations of the checkers that are distributed with
+the Checker Framework.
+The Javadoc documentation of the framework and the checkers is in the
+distribution and is also available online at
+\myurl{https://checkerframework.org/api/}.
+
+If you write a new checker and wish to advertise it to the world, let us
+know so we can mention it in \chapterpageref{third-party-checkers}
+or even include it in the Checker Framework distribution.
+
+
+\sectionAndLabel{How checkers build on the Checker Framework}{creating-tool-relationships}
+
+This table shows the relationship among tools that the Checker Framework
+builds on or that are built on the Checker Framework.
+You use the Checker Framework to build pluggable type systems, and the
+Annotation File Utilities to manipulate \code{.java} and \code{.class} files.
+
+\newlength{\bw}
+\setlength{\bw}{.5in}
+
+%% Strictly speaking, "Subtyping Checker" should sit on top of Checker
+%% Framework and below all the specific checkers.  But omit it for simplicity.
+
+% Unfortunately, Hevea inserts a horizontal line between every pair of rows
+% regardless of whether there is a \hline or \cline.  So, make paragraphs.
+\begin{center}
+\begin{tabular}{|p{\bw}|p{\bw}|p{\bw}|p{\bw}|p{.4\bw}|p{\bw}|p{1.5\bw}|p{1\bw}|}
+\cline{1-4} \cline{6-6}
+\centering Subtyping \par Checker &
+\centering Nullness \par Checker &
+\centering Index \par Checker &
+\centering Tainting \par Checker &
+\centering \ldots &
+\centering Your \par Checker &
+\multicolumn{2}{c}{}
+\\ \hline
+\multicolumn{6}{|p{6\bw}|}{\centering Base Checker \par (enforces subtyping rules)} &
+\centering Type \par inference &
+% Adding "\centering" here causes a LaTeX alignment error
+Other \par tools
+\\ \hline
+\multicolumn{6}{|p{6\bw}|}{\centering Checker Framework \par (enables creation of pluggable type-checkers)} &
+\multicolumn{2}{p{3\bw}|}{\centering \href{https://checkerframework.org/annotation-file-utilities/}{Annotation File Utilities} \par (\code{.java} $\leftrightarrow$ \code{.class} files)}
+\\ \hline
+\multicolumn{8}{|p{8.5\bw}|}{\centering
+  \href{https://checkerframework.org/jsr308/}{Java type annotations} syntax
+  and classfile format \par \centering (no built-in semantics)} \\ \hline
+\end{tabular}
+\end{center}
+
+
+The Base Checker
+(more precisely, the \refclass{common/basetype}{BaseTypeChecker})
+enforces the standard subtyping rules.
+The Subtyping Checker is a simple use of the Base Checker that supports
+providing type qualifiers on the command line.
+You usually want to build your checker on the Base Checker.
+
+
+\sectionAndLabel{The parts of a checker}{creating-parts-of-a-checker}
+
+The Checker Framework provides abstract base classes (default
+implementations), and a specific checker overrides as little or as much of
+the default implementations as necessary.
+To simplify checker implementations, by default the Checker Framework
+automatically discovers the parts of a checker by looking for specific files.
+Thus, checker implementations follow a very formulaic structure.
+To illustrate, a checker for MyProp must be laid out as follows:
+%
+\begin{Verbatim}
+myPackage/
+  | qual/                               type qualifiers
+  | MyPropChecker.java                  interface to the compiler
+  | MyPropVisitor.java                  [optional] type rules
+  | MyPropAnnotatedTypeFactory.java     [optional] type introduction and dataflow rules
+\end{Verbatim}
+%
+\<MyPropChecker.java> is occasionally optional, such as if you are
+building on the Subtyping Checker.  If you want to create an artifact
+containing just the qualifiers (similar to the Checker Framework's
+\<checker-qual> artifact), you should put the \<qual/> directory in a
+separate Maven module or Gradle subproject.
+
+Sections~\ref{creating-typequals}--\ref{creating-dataflow} describe
+the individual components of a type system as written using the Checker
+Framework:
+
+\begin{description}
+
+\item{\ref{creating-typequals}}
+  \textbf{Type qualifiers and hierarchy.}  You define the annotations for
+  the type system and the subtyping relationships among qualified types
+  (for instance, \<@NonNull Object> is a subtype of \<@Nullable
+  Object>).  This is also where you specify the default annotation that
+  applies whenever the programmer wrote no annotation and no other defaulting
+  rule applies.
+
+\item{\ref{creating-compiler-interface}}
+  \textbf{Interface to the compiler.}  The compiler interface indicates
+  which annotations are part of the type system, which command-line options
+  and \<@SuppressWarnings> annotations the checker recognizes, etc.
+
+\item{\ref{creating-extending-visitor}}
+  \textbf{Type rules.}  You specify the type system semantics (type
+  rules), violation of which yields a type error.  A type system has two types of
+  rules.
+\begin{itemize}
+\item
+  Subtyping rules related to the type hierarchy, such as that in every
+  assignment,
+  % and pseudo-assignment
+  the type of the right-hand-side is a subtype of the type of the left-hand-side.
+  Your checker automatically inherits these subtyping rules from the Base
+  Checker (Chapter~\ref{subtyping-checker}), so there is nothing for you to do.
+\item
+  Additional rules that are specific to your particular checker.  For
+  example, in the Nullness type system, only references whose type is
+  \refqualclass{checker/nullness/qual}{NonNull} may be dereferenced.  You
+  write these additional rules yourself.
+\end{itemize}
+
+\item{\ref{creating-type-introduction}}
+  \textbf{Type introduction rules.}  You specify the type of some expressions where
+  the rules differ from the built-in framework rules.
+
+\item{\ref{creating-dataflow}}
+  \textbf{Dataflow rules.}  These optional rules enhance flow-sensitive
+  type qualifier inference (also sometimes called ``local variable type inference'').
+\end{description}
+
+
+
+
+\sectionAndLabel{Compiling and using a custom checker}{creating-compiling}
+
+You can place your checker's source files wherever you like.
+One choice is to write your checker in a fork of the Checker Framework
+repository \url{https://github.com/typetools/checker-framework}.
+Another choice is to write it in a stand-alone repository.  Here is a
+template for a stand-alone repository:
+\url{https://github.com/typetools/templatefora-checker}; at that URL,
+click the ``Use this template'' button.
+
+% You may also wish to consult Section~\ref{creating-testing-framework} for
+% information on testing a checker and
+% Section~\ref{creating-debugging-options} for information on debugging a
+% checker.
+
+Once your custom checker is written, using it is very similar to using a
+built-in checker (Section~\ref{running}):
+simply pass the fully-qualified name of your \<BaseTypeChecker>
+subclass to the \<-processor> command-line option:
+\begin{alltt}
+  javac -processor \textit{mypackage.MyPropChecker} SourceFile.java
+\end{alltt}
+Note that your custom checker's
+\<.class> files must be on the same path (the classpath or processorpath)
+as the Checker Framework.
+Invoking a custom checker that builds on
+the Subtyping Checker is slightly different (Section~\ref{subtyping-using}).
+
+
+
+\sectionAndLabel{Tips for creating a checker}{creating-tips}
+
+To make your job easier, we recommend that you build your type-checker
+incrementally, testing at each phase rather than trying to build the whole
+thing at once.
+
+Here is a good way to proceed.
+
+\begin{enumerate}
+\item
+\label{creating-tips-write-manual}
+  Write the user manual.  Do this before you start coding.  The manual
+  explains the type system, what it guarantees, how to use it, etc., from
+  the point of view of a user.  Writing the manual will help you flesh out
+  your goals and the concepts, which are easier to understand and change in
+  text than in an implementation.
+  Section~\ref{creating-documenting-a-checker} gives a suggested structure
+  for the manual chapter, which will help you avoid omitting any parts.
+  Get feedback from someone else at this point to ensure that your manual
+  is comprehensible.
+
+  Once you have designed and documented the parts of your type system, you
+  should ``play computer'', manually
+  type-checking some code according to the rules you defined.
+  During manual checking, ask
+  yourself what reasoning you applied, what information you needed, and
+  whether your written-down rules were sufficient.
+  It is more efficient to find problems now rather than after coding up
+  your design.
+
+\item
+\label{creating-tips-implement-qualifiers}
+  Implement the type qualifiers and hierarchy
+  (Section~\ref{creating-typequals}).
+
+  Write simple test cases that consist of only assignments,
+  to test your type hierarchy.  For instance, if
+  your type hierarchy consists of a supertype \<@UnknownSign> and a subtype
+  \<@NonNegative>, then you could write a test case such as:
+
+\begin{Verbatim}
+  void testHierarchy(@UnknownSign int us, @NonNegative int nn) {
+    @UnknownSign int a = us;
+    @UnknownSign int b = nn;
+    // :: error: assignment
+    @NonNegative int c = us;  // expected error on this line
+    @NonNegative int d = nn;
+  }
+\end{Verbatim}
+
+  Type-check your test files using the Subtyping Checker
+  (\chapterpageref{subtyping-checker}).
+
+\item
+\label{creating-tips-implement-checker}
+  Write the checker class itself
+  (Section~\ref{creating-compiler-interface}).
+
+  Ensure that you can still type-check your test files and that the results
+  are the same.  You will not use the Subtyping Checker any more; you will
+  call the checker directly, as in
+
+\begin{Verbatim}
+  javac -processor mypackage.MyChecker File1.java File2.java ...
+\end{Verbatim}
+
+\item
+\label{creating-tips-test-infrastructure}
+  Test infrastructure.
+  If your checker source code is in a clone of the Checker Framework
+  repository, integrate your checker with the Checker Framework's Gradle
+  targets for testing (Section~\ref{creating-testing-framework}).  This
+  will make it much more convenient to run tests, and to ensure that they
+  are passing, as your work proceeds.
+
+\item
+  Annotate parts of the JDK, if relevant
+  (Section~\ref{creating-a-checker-annotated-jdk}).
+
+  Write test cases for a few of the annotated JDK methods to ensure
+  that the annotations are being properly read by your checker.
+
+\item
+  Implement type rules, if any (Section~\ref{creating-extending-visitor}).
+  (Some type systems need JDK annotations but don't have any additional
+  type rules.)
+
+  Before implementing type rules (or any other code in your type-checker),
+  read the Javadoc to familiarize yourself with the utility routines in the
+  \<org.checkerframework.javacutil> package, especially
+  \refclass{javacutil}{AnnotationBuilder},
+  \refclass{javacutil}{AnnotationUtils},
+  \refclass{javacutil}{ElementUtils},
+  \refclass{javacutil}{TreeUtils},
+  \refclass{javacutil}{TypeAnnotationUtils}, and
+  \refclass{javacutil}{TypesUtils}.
+  You will learn how to access needed information and avoid
+  reimplementing existing functionality.
+
+  Write simple test cases to test the type rules, and ensure that the
+  type-checker behaves as expected on those test files.
+  For example, if your type system forbids indexing an array by a
+  possibly-negative value, then you would write a test case such as:
+
+\begin{Verbatim}
+  void testArrayIndexing(String[] myArray, @UnknownSign int us, @NonNegative int nn) {
+    myArray[us];  // expected error on this line
+    myArray[nn];
+  }
+\end{Verbatim}
+
+\item
+  Implement type introduction rules, if any (Section~\ref{creating-type-introduction}).
+
+  Test your type introduction rules.
+  For example, if your type system sets the qualifier for manifest literal
+  integers and for array lengths, you would write a test case like the following:
+
+\begin{Verbatim}
+  void testTypeIntroduction(String[] myArray) {
+    @NonNegative nn1 = -1;  // expected error on this line
+    @NonNegative nn2 = 0;
+    @NonNegative nn3 = 1;
+    @NonNegative nn4 = myArray.length;
+  }
+\end{Verbatim}
+
+\item
+  Optionally, implement dataflow refinement rules
+  (Section~\ref{creating-dataflow}).
+
+  Test them if you wrote any.
+  For instance, if after an arithmetic comparison, your type system infers
+  which expressions are now known to be non-negative, you could write a
+  test case such as:
+
+\begin{Verbatim}
+  void testDataflow(@UnknownSign int us, @NonNegative int nn) {
+    @NonNegative nn2;
+    nn2 = us;  // expected error on this line
+    if (us > j) {
+      nn2 = us;
+    }
+    if (us >= j) {
+      nn2 = us;
+    }
+    if (j < us) {
+      nn2 = us;
+    }
+    if (j <= us) {
+      nn2 = us;
+    }
+    nn = us;  // expected error on this line
+  }
+\end{Verbatim}
+
+\end{enumerate}
+
+
+
+
+\sectionAndLabel{Annotations: Type qualifiers and hierarchy}{creating-typequals}
+
+A type system designer specifies the qualifiers in the type system (Section~\ref{creating-define-type-qualifiers})
+and
+the type hierarchy that relates them.
+The type hierarchy --- the subtyping relationships among the qualifiers ---
+can be defined either
+declaratively via meta-annotations (Section~\ref{creating-declarative-hierarchy}), or procedurally through
+subclassing \refclass{framework/type}{QualifierHierarchy} or
+\refclass{framework/type}{TypeHierarchy} (Section~\ref{creating-procedural-hierarchy}).
+
+
+\subsectionAndLabel{Defining the type qualifiers}{creating-define-type-qualifiers}
+
+%% True, but seems irrelevant here, so it detracts from the message.
+% Each qualifier restricts the values that
+% a type can represent.  For example \<@NonNull String> type can only
+% represent non-null values, indicating that the variable may not hold
+% \<null> values.
+
+Type qualifiers are defined as Java annotations.  In Java, an
+annotation is defined using the Java \code{@interface} keyword.
+Here is how to define a two-qualifier hierarchy:
+
+\begin{Verbatim}
+package mypackage.qual;
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+/**
+ * The run-time value of the integer is unknown.
+ *
+ * @checker_framework.manual #nonnegative-checker Non-Negative Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface UnknownSign {}
+
+
+package mypackage.qual;
+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;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.SubtypeOf;
+/**
+ * Indicates that the value is greater than or equal to zero.
+ *
+ * @checker_framework.manual #nonnegative-checker Non-Negative Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({UnknownSign.class})
+public @interface NonNegative {}
+\end{Verbatim}
+
+The \refqualclass{framework/qual}{SubtypeOf} meta-annotation
+indicates the parent in the type hierarchy.
+
+The \sunjavadocanno{java.base/java/lang/annotation/Target.html}{Target}
+meta-annotation indicates where the annotation
+may be written. All type qualifiers that users can write in source code should
+have the value \<ElementType.TYPE\_USE> and optionally with the additional value
+of \<ElementType.TYPE\_PARAMETER>, but no other \<ElementType> values.
+%% This feels like clutter that distracts from the main point of the section.
+% (Terminological note:  a \emph{meta-annotation} is an annotation that
+% is written on an annotation definition, such as
+% \refqualclass{framework/qual}{SubtypeOf} and
+% \sunjavadocanno{java.base/java/lang/annotation/Target.html}{Target}.)
+
+The annotations should be placed within a directory called \<qual>, and
+\<qual> should be placed in the same directory as your checker's source file.
+The Checker Framework automatically treats any annotation that
+is declared in the \<qual> package as a type qualifier.
+(See Section \ref{creating-indicating-supported-annotations} for more details.)
+For example, the Nullness Checker's source file is located at
+\<.../nullness/NullnessChecker.java>. The \<@NonNull> qualifier is defined in
+file \<.../nullness/qual/NonNull.java>.
+
+% \noindent
+% The \<@Target({ElementType.TYPE\_USE})> meta-annotation
+% distinguishes it from an ordinary
+% annotation that applies to a declaration (e.g., \<@Deprecated> or
+% \<@Override>).
+% The framework ignores any annotation whose
+% declaration does not bear the \<@Target({ElementType.TYPE\_USE})>
+% meta-annotation (with minor
+% exceptions, such as \<@SuppressWarnings>).
+
+Your type system should include a top qualifier and a bottom qualifier
+(Section~\ref{creating-bottom-and-top-qualifier}).
+The top qualifier is conventionally named \<\emph{CheckerName}Unknown>.
+Most type systems should also include a
+polymorphic qualifier \<@Poly\emph{MyTypeSystem}>
+(Section~\ref{method-qualifier-polymorphism}).
+
+Choose good names for the qualifiers, because users will write these in
+their source code.
+The Javadoc of every type qualifier should include a precise English
+description and an example use of the qualifier.
+
+
+\subsectionAndLabel{Declaratively defining the qualifier hierarchy}{creating-declarative-hierarchy}
+
+Declaratively, the type system designer uses two meta-annotations (written
+on the declaration of qualifier annotations) to specify the qualifier
+hierarchy.
+
+\begin{itemize}
+
+\item \refqualclass{framework/qual}{SubtypeOf} denotes that a qualifier is a subtype of
+  another qualifier or qualifiers, specified as an array of class
+  literals.  For example, for any type $T$,
+  \refqualclass{checker/nullness/qual}{NonNull} $T$ is a subtype of \refqualclass{checker/nullness/qual}{Nullable} $T$:
+
+  \begin{Verbatim}
+    @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+    @SubtypeOf( { Nullable.class } )
+    public @interface NonNull {}
+  \end{Verbatim}
+
+  % (The actual definition of \refclass{checker/nullness/qual}{NonNull} is slightly more complex.)
+
+
+  %% True, but a distraction.  Move to Javadoc?
+  % (It would be more natural to use Java subtyping among the qualifier
+  % annotations, but Java forbids annotations from subtyping one another.)
+  %
+  \refqualclass{framework/qual}{SubtypeOf} accepts multiple annotation classes as an argument,
+  permitting the type hierarchy to be an arbitrary DAG\@.
+
+% TODO: describe multiple type hierarchies
+% TODO: describe multiple polymorphic qualifiers
+% TODO: the code consistently uses "top" for type qualifiers and
+%       "root" for ASTs, in particular for CompilationUnitTrees.
+
+  All type qualifiers, except for polymorphic qualifiers (see below and
+  also Section~\ref{method-qualifier-polymorphism}), need to be
+  properly annotated with \refclass{framework/qual}{SubtypeOf}.
+
+  The top qualifier is annotated with
+  \<@SubtypeOf( \{ \} )>.  The top qualifier is the qualifier that is
+  a supertype of all other qualifiers.  For example, \refqualclass{checker/nullness/qual}{Nullable}
+  is the top qualifier of the Nullness type system, hence is defined as:
+
+  \begin{Verbatim}
+    @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+    @SubtypeOf( {} )
+    public @interface Nullable {}
+  \end{Verbatim}
+
+  \begin{sloppypar}
+  If the top qualifier of the hierarchy is the generic unqualified type
+  (this is not recommended!), then its children
+  will use \code{@SubtypeOf(Unqualified.class)}, but no
+  \code{@SubtypeOf(\{\})} annotation on the top qualifier \<Unqualified> is
+  necessary.  For an example, see the
+  \<Encrypted> type system of Section~\ref{encrypted-example}.
+  \end{sloppypar}
+
+\item \refqualclass{framework/qual}{PolymorphicQualifier} denotes that a qualifier is a
+  polymorphic qualifier.  For example:
+
+  \begin{Verbatim}
+    @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+    @PolymorphicQualifier
+    public @interface PolyNull {}
+  \end{Verbatim}
+
+  For a description of polymorphic qualifiers, see
+  Section~\ref{method-qualifier-polymorphism}.  A polymorphic qualifier must not have
+  a \refqualclass{framework/qual}{SubtypeOf} meta-annotation nor be
+  mentioned in any other \refqualclass{framework/qual}{SubtypeOf}
+  meta-annotation.
+
+\end{itemize}
+
+The declarative and procedural mechanisms for specifying the hierarchy can
+be used together. If any of the annotations representing type qualifiers have elements, then
+the relationships between those qualifiers must be defined procedurally.
+
+
+\subsectionAndLabel{Procedurally defining the qualifier hierarchy}{creating-procedural-hierarchy}
+
+The declarative syntax suffices for most cases.  More complex type
+hierarchies can be expressed by overriding, in your subclass of
+\refclass{common/basetype}{BaseAnnotatedTypeFactory}, either
+\refmethodterse{framework/type}{AnnotatedTypeFactory}{createQualifierHierarchy}{--}
+or \refmethodterse{framework/type}{AnnotatedTypeFactory}{createTypeHierarchy}{--}
+(typically only one of these needs to be overridden).
+
+For more details, see the Javadoc of those methods and of the classes
+\refclass{framework/type}{QualifierHierarchy} and \refclass{framework/type}{TypeHierarchy}.
+
+The \refclass{framework/type}{QualifierHierarchy} class represents the qualifier hierarchy (not the
+type hierarchy).  A type-system designer may subclass
+\refclass{framework/type}{QualifierHierarchy} to express customized qualifier
+relationships (e.g., relationships based on annotation
+arguments).
+
+The \refclass{framework/type}{TypeHierarchy} class represents the type hierarchy ---
+that is, relationships between
+annotated types, rather than merely type qualifiers, e.g., \<@NonNull
+Date> is a subtype of \<@Nullable Date>.  The default \refclass{framework/type}{TypeHierarchy} uses
+\refclass{framework/type}{QualifierHierarchy} to determine all subtyping relationships.
+The default \refclass{framework/type}{TypeHierarchy} handles
+generic type arguments, array components, type variables, and
+wildcards in a similar manner to the Java standard subtype
+relationship but with taking qualifiers into consideration.  Some type
+systems may need to override that behavior.  For instance, the Java
+Language Specification specifies that two generic types are subtypes only
+if their type arguments are identical:  for example,
+\code{List<Date>} is not a subtype of \code{List<Object>}, or of any other
+generic \code{List}.
+(In the technical jargon, the generic arguments are ``invariant'' or ``novariant''.)
+
+
+\subsectionAndLabel{Defining the default annotation}{creating-typesystem-defaults}
+
+A type system applies the default qualifier where the user has not written a
+qualifier (and no other default qualifier is applicable), as explained in
+Section~\ref{defaults}.
+
+The type system designer must specify the default annotation. The designer can specify the default annotation declaratively,
+using the \refqualclass{framework/qual}{DefaultQualifierInHierarchy}
+meta-annotation.
+Note that the default will apply to any source code that the checker reads,
+including stub libraries, but will not apply to compiled \code{.class}
+files that the checker reads.
+
+\begin{sloppypar}
+Alternately, the type system designer may specify a default procedurally,
+by overriding the
+\refmethod{framework/type}{GenericAnnotatedTypeFactory}{addCheckedCodeDefaults}{-org.checkerframework.framework.util.defaults.QualifierDefaults-}
+method.  You may do this even if you have declaratively defined the
+qualifier hierarchy.
+\end{sloppypar}
+
+If the default qualifier in the type hierarchy requires a value, there are
+ways for the type system designer to specify a default value both
+declaratively and procedurally, as well.  To do so declaratively, append
+the string \<default \emph{value}> where \emph{value} is the actual value
+you want to be the default, after the declaration of the value in the
+qualifier file.  For instance, \code{int value() default 0;} would make
+\code{value} default to zero. Alternatively, the procedural method
+described above can be used.
+
+The default qualifier applies to most, but not all, unannotated types, Section~\ref{climb-to-top}
+other defaulting rules are automatically added to every checker. Also, Section~\ref{defaults}
+describes other meta-annotations used to specify default annotations.
+
+\subsectionAndLabel{Relevant Java types}{creating-relevant-java-types}
+
+Sometimes, a checker only processes certain Java types.  For example, the
+\ahrefloc{formatter-checker}{Format String Checker} is relevant only to
+\<CharSequence> and its subtypes such as \<String>.
+The \refqualclass{framework/qual}{RelevantJavaTypes}
+annotation on the checker class indicates that its qualifiers may only be
+written on those types and no others.  All irrelevant types are defaulted to
+the top annotation.
+
+
+\subsectionAndLabel{Do not re-use type qualifiers}{creating-do-not-re-use-type-qualifiers}
+
+Every annotation should belong to only one type system.  No annotation
+should be used by multiple type systems.  This is true even of annotations
+that are internal to the type system and are not intended to be written by
+the programmer.
+
+Suppose that you have two type systems that both use the same type
+qualifier \<@Bottom>.  In a client program, a use of type \<T> may require type
+qualifier \<@Bottom> for one type system but a different qualifier for the other
+type system.  There is no annotation that a programmer can write to make
+the program type-check under both type systems.
+
+This also applies to type qualifiers that a programmer does not write,
+because the compiler outputs \<.class> files that contain an explicit type
+qualifier on every type --- a defaulted or inferred type qualifier if the
+programmer didn't write a type qualifier explicitly.
+
+
+\subsectionAndLabel{Completeness of the type hierarchy}{creating-bottom-and-top-qualifier}
+
+When you define a type system, its type hierarchy must be a
+lattice:  every set of types has a unique least upper bound and a unique
+greatest lower bound.  This implies that there must be a top type that is a
+supertype of all other types, and there must be a bottom type that is a
+subtype of all other types.
+Furthermore, the top type and bottom type should be defined
+specifically for the type system.  Don't reuse an existing qualifier from the
+Checker Framework such as \<@Unqualified>.
+
+It is possible that a single type-checker checks multiple type hierarchies.
+An example is the Nullness Checker, which has three separate type
+hierarchies, one each for
+nullness, initialization, and map keys.  In this case, each type hierarchy
+would have its own top qualifier and its own bottom qualifier; they don't
+all have to share a single top qualifier or a single bottom qualifier.
+
+
+\paragraphAndLabel{Bottom qualifier}{creating-bottom-qualifier}
+Your type hierarchy must have a bottom qualifier
+--- a qualifier that is a (direct or indirect) subtype of every other
+qualifier.
+
+\<null> is the bottom type. Because the only value with type \<Void> is
+\<null>, uses of the type \<Void> are also bottom.
+(The only exception
+is if the type system has special treatment for \<null> values, as the
+Nullness Checker does. In that case, add the meta-annotation
+\refqualclasswithparams{framework/qual}{QualifierForLiterals}{LiteralKind.NULL}
+to the correct qualifier.)
+This legal code
+will not type-check unless \<null> has the bottom type:
+\begin{Verbatim}
+<T> T f() {
+    return null;
+}
+\end{Verbatim}
+
+% \begin{sloppypar}
+% You don't necessarily have to define a new bottom qualifier.  You can
+% use \<org.checkerframework.common.subtyping.qual.Bottom> if your type system does not already have an
+% appropriate bottom qualifier.
+% \end{sloppypar}
+
+Some type systems have a special bottom type that is used \emph{only} for
+the \code{null} value, and for dead code and other erroneous situations.
+In this case, users should only write the bottom qualifier on explicit
+bounds.  In this case, the definition of the bottom qualifier should be
+meta-annotated with:
+
+% import java.lang.annotation.ElementType;
+% import java.lang.annotation.Target;
+% import org.checkerframework.framework.qual.TargetLocations;
+% import org.checkerframework.framework.qual.TypeUseLocation;
+%
+\begin{Verbatim}
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+\end{Verbatim}
+
+Furthermore, by convention the name of such a qualifier ends with ``\<Bottom>''.
+
+The hierarchy shown in Figure~\ref{fig-initialization-hierarchy} lacks
+a bottom qualifier, but the actual implementation does contain a (non-user-visible) bottom qualifier.
+
+
+\paragraphAndLabel{Top qualifier}{creating-top-qualifier}
+Your type hierarchy must have a top qualifier
+--- a qualifier that is a (direct or indirect) supertype of every other
+qualifier.
+Here is one reason.
+The default type for local variables is the top
+qualifier (that type is then flow-sensitively
+refined depending on what values are stored in the local variable).
+If there is no single top qualifier, then there is no
+unambiguous choice to make for local variables.
+
+
+\subsectionAndLabel{Annotations whose argument is a Java expression (dependent type annotations)\label{expression-annotations}}{dependent-types}
+
+Sometimes, an annotation needs to refer to a Java expression.
+Section~\ref{java-expressions-as-arguments} gives examples of such
+annotations and also explains what Java expressions can and cannot be
+referred to.
+
+This section explains how to implement a dependent type annotation.
+
+A ``dependent type annotation''
+must have one attribute, \<value>, that is an
+array of strings.  The Checker Framework verifies that the annotation's
+arguments are valid expressions according to the rules of
+Section~\ref{java-expressions-as-arguments}.  If
+the expression is not valid, an error is issued and the string in the
+annotation is changed to indicate that the expression is not valid.
+
+The Checker Framework standardizes the expression strings.  For example, a
+field \<f> can be referred to as either ``\<f>'' or ``\<this.f>''.  If the
+programmer writes ``\<f>'', the Checker Framework treats it
+as if the programmer had written ``\<this.f>''.
+An advantage of this canonicalization is
+that comparisons, such as \<isSubtype>, can be implemented as string comparisons.
+
+The Checker Framework viewpoint-adapts type annotations on method, constructor,
+and field declarations at uses for those methods.  For example, given the
+following class
+
+\begin{Verbatim}
+class MyClass {
+   Object field = ...;
+   @Anno("this.field") Object field2 = ...;
+}
+\end{Verbatim}
+and assuming the variable \<myClass> is of type \<MyClass>, then the type of
+\<myClass.field> is viewpoint-adapted to \<@Anno("myClass.field")>.
+
+To use this built-in functionality, add a \refqualclass{framework/qual}{JavaExpression} annotation
+to any annotation element that should be interpreted as a Java expression.  The type of the
+element must be an array of Strings.  If your checker requires special handling of Java expressions,
+your checker implementation should override
+\refmethod{framework/type}{GenericAnnotatedTypeFactory}{createDependentTypesHelper}{--}
+to return a subclass of \<DependentTypesHelper>.
+
+Given a specific expression in the program (of type Tree or Node), a
+checker may need to obtain its canonical string representation.  This
+enables the checker to create an dependent type annotation that refers to
+it, or to compare to the string expression of an existing expression
+annotation.
+To obtain the string, first create a
+\refclass{dataflow/expression}{JavaExpression} object by calling
+\refmethodanchortext{dataflow/expression}{JavaExpression}{fromTree}{-com.sun.source.tree.ExpressionTree-}{fromTree(AnnotationProvider,
+ExpressionTree)} or
+\refmethodanchortext{dataflow/expression}{JavaExpression}{fromNode}{-org.checkerframework.dataflow.cfg.node.Node-}{fromNode(AnnotationProvider,
+Node)}.
+Then, call \<toString()> on the \<JavaExpression> object.
+
+
+\subsectionAndLabel{Repeatable annotations}{repeatable-annotations}
+
+Some pre- and post-condition annotations that have multiple elements (that
+is, annotations that take multiple arguments) should be repeatable, so that
+programmers can specify them more than once.  An example is
+\refqualclass{checker/nullness/qual}{EnsuresNonNullIf}; it could not be
+defined with each of its elements being a list, as (for example)
+\refqualclass{checker/nullness/qual}{KeyFor} is.
+
+Make an annotation \emph{A} repeatable by defining a nested annotation (within
+\emph{A}'s definition) named \<List>, and writing
+\<@Repeatable(\emph{A}.List.class)> on \emph{A}'s definition.
+% This convention is encoded in
+% AnnotatedTypeFactory.isListForRepeatedAnnotationImplementation .
+
+
+\sectionAndLabel{The checker class:  Compiler interface}{creating-compiler-interface}
+
+A checker's entry point is a subclass of
+\refclass{framework/source}{SourceChecker}, and is usually a direct subclass
+of either \refclass{common/basetype}{BaseTypeChecker} or
+\refclass{framework/source}{AggregateChecker}.
+This entry
+point, which we call the checker class, serves two
+roles:  an interface to the compiler and a factory for constructing
+type-system classes.
+
+Because the Checker Framework provides reasonable defaults, oftentimes the
+checker class has no work to do.  Here are the complete definitions of the
+checker classes for the Interning Checker and the Nullness Checker:
+
+\begin{Verbatim}
+  package my.package;
+  import org.checkerframework.common.basetype.BaseTypeChecker;
+  @SupportedLintOptions({"dotequals"})
+  public final class InterningChecker extends BaseTypeChecker {}
+
+  package my.package;
+  import org.checkerframework.common.basetype.BaseTypeChecker;
+  @SupportedLintOptions({"flow", "cast", "cast:redundant"})
+  public class NullnessChecker extends BaseTypeChecker {}
+\end{Verbatim}
+
+(The \refqualclass{framework/source}{SupportedLintOptions} annotation is
+optional, and many checker classes do not have one.)
+
+The checker class bridges between the Java compiler and the checker.  It
+invokes the type-rule check visitor on every Java source file being
+compiled.  The checker uses
+\refmethod{framework/source}{SourceChecker}{reportError}{-java.lang.Object-java.lang.String-java.lang.Object...-}
+and
+\refmethod{framework/source}{SourceChecker}{reportWarning}{-java.lang.Object-java.lang.String-java.lang.Object...-}
+to issue errors.
+
+Also, the checker class follows the factory method pattern to
+construct the concrete classes (e.g., visitor, factory) and annotation
+hierarchy representation.  It is a convention that, for
+a type system named Foo, the compiler
+interface (checker), the visitor, and the annotated type factory are
+named as \<FooChecker>, \<FooVisitor>, and \<FooAnnotatedTypeFactory>.
+\refclass{common/basetype}{BaseTypeChecker} uses the convention to
+reflectively construct the components.  Otherwise, the checker writer
+must specify the component classes for construction.
+
+\begin{sloppypar}
+A checker can customize the default error messages through a
+\sunjavadoc{java.base/java/util/Properties.html}{Properties}-loadable text file named
+\<messages.properties> that appears in the same directory as the checker class.
+The property file keys are the strings passed to \refmethodterse{framework/source}{SourceChecker}{reportError}{-java.lang.Object-java.lang.String-java.lang.Object...-}
+and
+\refmethodterse{framework/source}{SourceChecker}{reportWarning}{-java.lang.Object-java.lang.String-java.lang.Object...-}
+(like \code{"type.incompatible"}) and the values are the strings to be
+printed (\code{"cannot assign ..."}).
+The \<messages.properties> file only need to mention the new messages that
+the checker defines.
+It is also allowed to override messages defined in superclasses, but this
+is rarely needed.
+Section~\refwithpageparen{compiler-message-keys} discusses best practices
+when using a message key in a \<@SuppressWarnings> annotation.
+\end{sloppypar}
+
+\subsectionAndLabel{Indicating supported annotations}{creating-indicating-supported-annotations}
+
+A checker must indicate the annotations that it supports (that make up its type
+hierarchy).
+
+By default, a checker supports all type annotations located in a
+subdirectory called \<qual> that's located in the same directory as the checker.
+A type annotation is meta-annotated with either
+\<@Target(ElementType.TYPE\_USE)>
+or
+\<@Target({ElementType.TYPE\_USE, ElementType.TYPE\_PARAMETER})>.
+
+To indicate support for annotations that are located outside of the \<qual>
+subdirectory, annotations that have other \<ElementType> values,
+checker writers can override the
+\refmethodterse{framework/type}{AnnotatedTypeFactory}{createSupportedTypeQualifiers}{--}
+method (see its Javadoc for details).
+It is required to define \<createSupportedTypeQualifiers> if you are mixing
+qualifiers from multiple directories (including when extending an existing
+checker that has its own qualifiers) and when using the Buck build tool,
+whose classloader cannot find the qualifier directory.
+
+An aggregate checker (which extends
+\refclass{framework/source}{AggregateChecker}) does not need to specify its
+type qualifiers, but each of its component checkers should do so.
+
+
+\subsectionAndLabel{Bundling multiple checkers}{creating-bundling-multiple-checkers}
+
+Sometimes, multiple checkers work together and should always be run
+together.  There are two different ways to bundle multiple checkers
+together, by creating either an ``aggregate checker'' or a ``compound checker''.
+
+
+\begin{enumerate}
+\item
+An aggregate checker runs multiple independent, unrelated checkers.  There
+is no communication or cooperation among them.
+
+The effect is the same as if a user passes
+multiple processors to the \<-processor> command-line option.
+
+For example, instead of a user having to run
+
+\begin{Verbatim}
+  javac -processor DistanceUnitChecker,VelocityUnitChecker,MassUnitChecker MyFile.java
+\end{Verbatim}
+
+\noindent
+the user can write
+
+\begin{Verbatim}
+  javac -processor MyUnitCheckers MyFile.java
+\end{Verbatim}
+
+\noindent
+if you define an aggregate checker class.  Extend \refclass{framework/source}{AggregateChecker} and override
+the \<getSupportedTypeCheckers> method, like the following:
+
+\begin{Verbatim}
+  public class MyUnitCheckers extends AggregateChecker {
+    protected Collection<Class<? extends SourceChecker>> getSupportedCheckers() {
+      return Arrays.asList(DistanceUnitChecker.class,
+                           VelocityUnitChecker.class,
+                           MassUnitChecker.class);
+    }
+  }
+\end{Verbatim}
+
+% This is the *only* example, as of July 2015.
+An example of an aggregate checker is \refclass{checker/i18n}{I18nChecker}
+(see \chapterpageref{i18n-checker}), which consists of
+\refclass{checker/i18n}{I18nSubchecker} and
+\refclass{checker/i18n}{LocalizableKeyChecker}.
+
+\item
+Use a compound checker to express dependencies among checkers.  Suppose it
+only makes sense to run MyChecker if MyHelperChecker has already been run;
+that might be the case if MyHelperChecker computes some information that
+MyChecker needs to use.
+
+Override
+\<MyChecker.\refmethodterse{common/basetype}{BaseTypeChecker}{getImmediateSubcheckerClasses}{--}>
+to return a list of the checkers that MyChecker depends on.  Every one of
+them will be run before MyChecker is run.  One of MyChecker's subcheckers
+may itself be a compound checker, and multiple checkers may declare a
+dependence on the same subchecker.  The Checker Framework will run each
+checker once, and in an order consistent with all the dependences.
+
+A checker obtains information from its subcheckers (those that ran before
+it) by querying their \refclass{framework/type}{AnnotatedTypeFactory} to
+determine the types of variables.  Obtain the \<AnnotatedTypeFactory> by
+calling
+\refmethodterse{common/basetype}{BaseTypeChecker}{getTypeFactoryOfSubchecker}{-java.lang.Class-}.
+
+\end{enumerate}
+
+
+
+\subsectionAndLabel{Providing command-line options}{creating-providing-command-line-options}
+
+A checker can provide two kinds of command-line options:
+boolean flags and
+named string values (the standard annotation processor
+options).
+
+\subsubsectionAndLabel{Boolean flags}{creating-providing-command-line-options-boolean-flags}
+
+To specify a simple boolean flag, add:
+
+\begin{alltt}
+  \refqualclass{framework/source}{SupportedLintOptions}(\{"myflag"\})
+\end{alltt}
+
+\noindent
+to your checker subclass.
+The value of the flag can be queried using
+
+\begin{Verbatim}
+  checker.getLintOption("myflag", false)
+\end{Verbatim}
+
+The second argument sets the default value that should be returned.
+
+To pass a flag on the command line, call javac as follows:
+
+\begin{Verbatim}
+  javac -processor MyChecker -Alint=myflag
+\end{Verbatim}
+
+
+\subsubsectionAndLabel{Named string values}{creating-providing-command-line-options-named-string-values}
+
+For more complicated options, one can use the standard
+\code{@SupportedOptions} annotation on the checker, as in:
+
+\begin{alltt}
+  \refqualclass{framework/source}{SupportedOptions}(\{"myoption"\})
+\end{alltt}
+
+The value of the option can be queried using
+
+\begin{Verbatim}
+  checker.getOption("myoption")
+\end{Verbatim}
+
+To pass an option on the command line, call javac as follows:
+
+\begin{Verbatim}
+  javac -processor MyChecker -Amyoption=p1,p2
+\end{Verbatim}
+
+The value is returned as a single string and you have to perform the
+required parsing of the option.
+
+
+% TODO: describe -ANullnessChecker_option=value mechanism.
+
+
+\sectionAndLabel{Visitor: Type rules}{creating-extending-visitor}
+
+A type system's rules define which operations on values of a
+particular type are forbidden.
+These rules must be defined procedurally, not declaratively.
+Put them in a file \<\emph{MyChecker}Visitor.java> that extends
+\refclass{common/basetype}{BaseTypeVisitor}.
+
+BaseTypeVisitor performs type-checking at each node of a
+source file's AST\@.  It uses the visitor design pattern to traverse
+Java syntax trees as provided by Oracle's
+\href{https://docs.oracle.com/en/java/javase/11/docs/api/jdk.compiler/module-summary.html}{jdk.compiler
+API},
+and it issues a warning (by calling
+\refmethodterse{framework/source}{SourceChecker}{reportError}{-java.lang.Object-java.lang.String-java.lang.Object...-}
+or
+\refmethodterse{framework/source}{SourceChecker}{reportWarning}{-java.lang.Object-java.lang.String-java.lang.Object...-})
+whenever the type system is violated.
+
+Most type-checkers
+override only a few methods in \refclass{common/basetype}{BaseTypeVisitor}.
+A checker's visitor overrides one method in the base visitor for each special
+rule in the type qualifier system.
+The last line of the overridden version is
+``\<return super.visit\emph{TreeType}(node, p);>''.
+If the method didn't raise any error,
+the superclass implementation can perform standard checks.
+
+
+By default, \refclass{common/basetype}{BaseTypeVisitor} performs subtyping checks that are
+similar to Java subtype rules, but taking the type qualifiers into account.
+\refclass{common/basetype}{BaseTypeVisitor} issues these errors:
+
+\begin{itemize}
+
+\item invalid assignment (type.incompatible) for an assignment from
+  an expression type to an incompatible type.  The assignment may be a
+  simple assignment, or pseudo-assignment like return expressions or
+  argument passing in a method invocation
+
+  In particular, in every assignment and pseudo-assignment, the
+  left-hand side of the assignment is a supertype of (or the same type
+  as) the right-hand side.  For example, this assignment is not
+  permitted:
+
+  \begin{Verbatim}
+    @Nullable Object myObject;
+    @NonNull Object myNonNullObject;
+    ...
+    myNonNullObject = myObject;  // invalid assignment
+  \end{Verbatim}
+
+\item invalid generic argument (type.argument) when a type
+  is bound to an incompatible generic type variable
+
+\item invalid method invocation (method.invocation) when a
+  method is invoked on an object whose type is incompatible with the
+  method receiver type
+
+\item invalid overriding parameter type (override.param)
+  when a parameter in a method declaration is incompatible with that
+  parameter in the overridden method's declaration
+
+\item invalid overriding return type (override.return) when the
+  return type in a method declaration is incompatible with the
+  return type in the overridden method's declaration
+
+\item invalid overriding receiver type (override.receiver)
+  when a receiver in a method declaration is incompatible with that
+  receiver in the overridden method's declaration
+
+\end{itemize}
+
+
+\subsectionAndLabel{AST traversal}{creating-ast-traversal}
+
+The Checker Framework needs to do its own traversal of the AST even though
+it operates as an ordinary annotation processor~\cite{JSR269}.  Java
+provides a visitor for Java code that is intended to be used by annotation
+processors, but that visitor only
+visits the public elements of Java code, such as classes, fields, methods,
+and method arguments --- it does not visit code bodies or various other
+locations.  The Checker Framework hardly uses the built-in visitor --- as
+soon as the built-in visitor starts to visit a class, then the Checker
+Framework's visitor takes over and visits all of the class's source code.
+
+Because there is no standard API for the AST of Java
+code\footnote{Actually, there is a standard API for Java ASTs --- JSR 198
+  (Extension API for Integrated Development Environments)~\cite{JSR198}.
+  If tools were to implement it (which would just require writing wrappers
+  or adapters), then the Checker Framework and similar tools could be
+  portable among different compilers and IDEs.}, the Checker Framework uses
+the javac implementation.  This is why the Checker Framework is not deeply
+integrated with Eclipse or IntelliJ IDEA, but runs as an external tool (see
+Section~\ref{eclipse}).
+
+
+\subsectionAndLabel{Avoid hardcoding}{creating-avoid-hardcoding}
+
+If a method's contract is expressible in the type system's annotation
+syntax, then you should write annotations, in a stub file or annotated JDK
+(Chapter~\ref{annotating-libraries}).
+
+Only if the contract is not expressible should you write a type-checking
+rule for method invocation, where your rule checks the name of the method
+being called and then treats the method in a special way.
+
+
+\sectionAndLabel{Type factory: Type introduction rules}{creating-type-introduction}
+
+The annotated type of expressions and types are defined via type introduction rules in the
+type factory.  For most expressions and types, these rules are the same regardless of the type system.
+For example, the type of a method invocation expression is the return type of the invoked method,
+viewpoint-adapted for the call site.  The framework implements these rules so that all type systems
+automatically use them.  For other expressions, such as string literals, their (annotated) types depend
+on the type system, so the framework provides way to specify what qualifiers should apply to these expressions.
+
+Defaulting rules are type introduction rules for computing the annotated type for an unannotated type;
+these rules are explained in Section~\ref{creating-typesystem-defaults}. The meta-annotation \refqualclass{framework/qual}{QualifierForLiterals} can be written on an annotation
+declaration to specify that that annotation should be applied to the type of literals listed in the
+meta-annotation.
+
+\subsectionAndLabel{Procedurally specifying type introduction rules}{creating-procedurally-specifying-implicit-annotations}
+
+If the meta-annotations are not sufficiently expressive, then you
+can write your own type introduction rules.  There are three ways to do so.
+Each makes changes to an \refclass{framework/type}{AnnotatedTypeMirror},
+which is the Checker Framework's representation of an annotated type.
+
+
+\begin{enumerate}
+\item
+  Define a subclass of
+  \refclass{framework/type/treeannotator}{TreeAnnotator},
+  typically as a private inner class of your \<AnnotatedTypeFactory>.
+  There is a method of \<TreeAnnotator> for every AST node, and the visitor
+  has access to both the tree (the AST node) and its type.  In your
+  subclass of \<AnnotatedTypeFactory>, override \<createTreeAnnotator> to
+  return a \<ListTreeAnnotator> containing that annotator, as in
+
+\begin{Verbatim}
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+      return new ListTreeAnnotator(super.createTreeAnnotator(), new MyTreeAnnotator(this));
+  }
+\end{Verbatim}
+
+  \noindent
+  (or put your TreeAnnotator first; several tree annotators are run by
+  default, and among them, \<PropagationTreeAnnotator>
+  adds annotations to \<AnnotatedTypeMirror>s that do not have an annotation,
+  but has no effect on those that have an annotation).
+
+\item
+  Define a subclass of a
+  \refclass{framework/type/typeannotator}{TypeAnnotator},
+  typically as a private inner class of your \<AnnotatedTypeFactory>.
+  There is a method of \<TypeAnnotator> for every kind of type, and the
+  visitor has access to only the type.  In your subclass of
+  \<AnnotatedTypeFactory>, override \<createTypeAnnotator> to return a
+  \<ListTypeAnnotator> containing that annotator, as in
+
+\begin{Verbatim}
+  @Override
+  protected TypeAnnotator createTypeAnnotator() {
+      return new ListTypeAnnotator(new MyTypeAnnotator(this), super.createTypeAnnotator());
+  }
+\end{Verbatim}
+
+  \noindent
+  (or put your TypeAnnotator last).
+
+\item
+  Create a subclass of \refclass{framework/type}{AnnotatedTypeFactory} and
+  override two \<addComputedTypeAnnotations> methods:
+  \refmethodanchortext{framework/type}{AnnotatedTypeFactory}{addComputedTypeAnnotations}{-com.sun.source.tree.Tree-org.checkerframework.framework.type.AnnotatedTypeMirror-}{addComputedTypeAnnotations(Tree,AnnotatedTypeMirror)}
+  (or
+  \refmethodanchortext{framework/type}{GenericAnnotatedTypeFactory}{addComputedTypeAnnotations}{-com.sun.source.tree.Tree-org.checkerframework.framework.type.AnnotatedTypeMirror-boolean-}{addComputedTypeAnnotations(Tree,AnnotatedTypeMirror,boolean)}
+  if extending \code{GenericAnnotatedTypeFactory})
+  and
+  \refmethodanchortext{framework/type}{AnnotatedTypeFactory}{addComputedTypeAnnotations}{-javax.lang.model.element.Element-org.checkerframework.framework.type.AnnotatedTypeMirror-}{addComputedTypeAnnotations(Element,AnnotatedTypeMirror)}.
+  The methods can make arbitrary changes to the annotations on a type.
+
+  Recall that \<AnnotatedTypeFactory>, when given a program
+  expression, returns the expression's type.  This should include not only
+  the qualifiers that the programmer explicitly wrote in the source code, but
+  also default annotations and type
+  refinement (see Section~\ref{effective-qualifier} for explanations of these
+  concepts).
+
+  The approach of overriding \<addComputedTypeAnnotations> is a last
+  resort, if your logic cannot be implemented using a TreeAnnotator or a
+  TypeAnnotator.  The implementation of \<addComputedTypeAnnotations> in
+  \<GenericAnnotatedTypeFactory> calls the tree annotator and the type
+  annotator (in that order), but by overriding the method you can cause
+  your logic to be run even earlier or even later.
+
+\end{enumerate}
+
+
+\sectionAndLabel{Dataflow: enhancing flow-sensitive type refinement}{creating-dataflow}
+
+By default, every checker performs flow-sensitive type refinement, as described
+in Section~\ref{type-refinement}.
+
+This section of the manual explains how to enhance the Checker Framework's
+built-in type refinement.
+Most commonly, you will inform the Checker Framework about a run-time test
+that gives information about the type qualifiers in your type system.
+Section~\refwithpageparen{type-refinement-runtime-tests} gives examples of
+type systems with and without run-time tests.
+
+The steps to customizing type refinement are:
+\begin{enumerate}
+\item{\S\ref{creating-dataflow-determine-expressions}}
+  Determine which expressions will be refined.
+\item{\S\ref{creating-dataflow-create-classes}}
+  Create required class and configure its use.
+\item{\S\ref{creating-dataflow-override-methods}}
+  Override methods that handle \refclass{dataflow/cfg/node}{Node}s of interest.
+\item{\S\ref{creating-dataflow-implement-refinement}}
+  Implement the refinement.
+\end{enumerate}
+
+The Regex Checker's dataflow customization for the
+\refmethod{checker/regex/util}{RegexUtil}{asRegex}{-java.lang.String-}
+run-time check is used as a running example.
+
+If needed, you can find more details about the implementation of
+type refinement, and the control flow graph (CFG) data
+structure that it uses, in the
+\href{https://checkerframework.org/manual/checker-framework-dataflow-manual.pdf}{Dataflow
+  Manual}.
+
+
+\subsectionAndLabel{Determine expressions to refine the types of}{creating-dataflow-determine-expressions}
+
+A run-time check or run-time
+operation involves multiple expressions (arguments, results).
+Determine which expression the customization will refine.  This is
+usually specific to the type system and run-time test.
+There is no code to write in this step; you are merely determining
+the design of your type refinement.
+
+For the program operation \code{op(a,b)}, you can refine
+the types in either or both of the following ways:
+\begin{enumerate}
+\item Change the result type of the entire expression \code{op(a,b)}.
+
+As an example (and as the running example of implementing a dataflow
+refinement), the \code{RegexUtil.asRegex} method is declared as:
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  @Regex(0) String asRegex(String s, int groups) { ... }
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+\noindent
+This annotation is sound and conservative:  it says that an expression such
+as \code{RegexUtil.asRegex(myString, myInt)} has type \code{@Regex(0)
+  String}.  However, this annotation is imprecise.  When the \code{group}
+argument is known at compile time, a better estimate can be given.  For
+example, \code{RegexUtil.asRegex(myString, 2)} has type \code{@Regex(2)
+  String}.
+
+\item Change the type of some other expression, such as \code{a} or \code{b}.
+
+As an example, consider an equality test in the Nullness type system:
+
+\begin{Verbatim}
+  @Nullable String s;
+    if (s != null) {
+      ...
+    } else {
+      ...
+    }
+\end{Verbatim}
+
+The type of \<s != null> is always \<boolean>.  However, in the
+true branch, the type of \<s> can be refined to \<@NonNull String>.
+
+\end{enumerate}
+
+If you are refining the types of arguments or the result of a method call,
+then you may be able to implement your flow-sensitive refinement rules by
+just writing \refqualclass{framework/qual}{EnsuresQualifier} and/or
+\refqualclass{framework/qual}{EnsuresQualifierIf} annotations.
+When this is possible, it is the best approach.
+
+Sections~\ref{creating-dataflow-create-classes}--\ref{creating-dataflow-implement-refinement}
+explain how to create a transfer class when the
+\refqualclass{framework/qual}{EnsuresQualifier} and
+\refqualclass{framework/qual}{EnsuresQualifierIf} annotations are insufficient.
+
+
+\subsectionAndLabel{Create required class}{creating-dataflow-create-classes}
+
+In the same directory as \<\emph{MyChecker}Checker.java>, create a class
+named \<\emph{MyChecker}Transfer> that extends
+\refclass{framework/flow}{CFTransfer}.
+
+Leave the class body empty for now.  Your class will add functionality by
+overriding methods of \<CFTransfer>, which performs the default Checker
+Framework type refinement.
+
+As an example, the Regex Checker's extended
+\refclass{framework/flow}{CFTransfer} is
+\refclass{checker/regex}{RegexTransfer}.
+
+(If you disregard the instructions above and choose a different name or a
+different directory for your \<\emph{MyChecker}Transfer> class, you will
+also need to override the \<createFlowTransferFunction> method in your type
+factory to return a new instance of the class.)
+
+(As a reminder, use of \refqualclass{framework/qual}{EnsuresQualifier} and
+\refqualclass{framework/qual}{EnsuresQualifierIf} may obviate the need for
+a transfer class.)
+
+%% More extended directions about what do to if the name is non-standard.
+% If the checker's extended \refclass{framework/flow}{CFTransfer}
+% starts with the name of the type system, then the type factory will use the
+% transfer class without further configuration. For example, if the checker
+% class is \<FooChecker>, then if the transfer class is \<FooTransfer>, then it is
+% not necessary to configure the type factory
+% to use \<FooTransfer>.  If some other naming convention is used, then
+% to configure your checker's type factory to use the new extended
+% \refclass{framework/flow}{CFTransfer}, override the
+% \code{createFlowTransferFunction} method in your type factory to return a new instance
+% of the extended \refclass{framework/flow}{CFTransfer}.
+%
+% %BEGIN LATEX
+% \begin{smaller}
+% %END LATEX
+% \begin{Verbatim}
+%   @Override
+%   public CFTransfer createFlowTransferFunction(
+%           CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
+%       return new RegexTransfer((CFAnalysis) analysis);
+%   }
+% \end{Verbatim}
+% %BEGIN LATEX
+% \end{smaller}
+% %END LATEX
+
+%% The text below is true, but not required.
+%\item \textbf{Create a class that extends
+%    \refclass{framework/flow}{CFAbstractAnalysis} and uses the extended
+%    \refclass{framework/flow}{CFAbstractTransfer}}
+%
+%  \begin{sloppypar}
+%  \refclass{framework/flow}{CFAbstractTransfer} and its superclass,
+%  \refclass{dataflow/analysis}{Analysis}, are the central coordinating classes
+%  in the Checker Framework's dataflow algorithm. The
+%  \code{createTransferFunction} method must be overridden in an extended
+%  \refclass{framework/flow}{CFAbstractTransfer} to return a new instance of the
+%  extended \refclass{framework/flow}{CFAbstractTransfer}.
+%  \end{sloppypar}
+%
+%  \begin{sloppypar}
+%  The Regex Checker's extended \refclass{framework/flow}{CFAbstractAnalysis} is
+%  \refclass{checker/regex/classic}{RegexAnalysis}, which overrides the
+%  \code{createTransferFunction} to return a new
+%  \refclass{checker/regex/classic}{RegexTransfer} instance:
+%  \end{sloppypar}
+%
+%%BEGIN LATEX
+%\begin{smaller}
+%%END LATEX
+%\begin{Verbatim}
+%  @Override
+%  public RegexTransfer createTransferFunction() {
+%      return new RegexTransfer(this);
+%  }
+%\end{Verbatim}
+%%BEGIN LATEX
+%\end{smaller}
+%%END LATEX
+%
+%\item \textbf{Configure the checker's type factory to use the extended
+%    \refclass{framework/flow}{CFAbstractAnalysis}}
+%
+%\begin{sloppypar}
+%To configure your checker's type factory to use the new extended
+%\refclass{framework/flow}{CFAbstractAnalysis}, override the
+%\code{createFlowAnalysis} method in your type factory to return a new instance
+%of the extended \refclass{framework/flow}{CFAbstractAnalysis}.
+%\end{sloppypar}
+%
+%%BEGIN LATEX
+%\begin{smaller}
+%%END LATEX
+%\begin{Verbatim}
+%  @Override
+%  protected RegexAnalysis createFlowAnalysis(
+%          List<Pair<VariableElement, CFValue>> fieldValues) {
+%
+%      return new RegexAnalysis(checker, this, fieldValues);
+%  }
+%\end{Verbatim}
+%%BEGIN LATEX
+%\end{smaller}
+%%END LATEX
+
+
+\subsectionAndLabel{Override methods that handle Nodes of interest}{creating-dataflow-override-methods}
+
+Decide what source code syntax is relevant to the run-time checks or
+run-time operations you are trying to support.  The CFG (control flow
+graph) represents source code as \refclass{dataflow/cfg/node}{Node}, a
+node in the abstract syntax tree of the program being checked (see
+\href{#creating-dataflow-representation}{``Program representation''} below).
+
+In your extended \refclass{framework/flow}{CFTransfer}
+override the visitor method that handles the \refclass{dataflow/cfg/node}{Node}s
+relevant to your run-time check or run-time operation.
+Leave the body of the overriding method empty for now.
+
+For example, the Regex Checker refines the type of a run-time test method
+call.  A method call is represented by a
+\refclass{dataflow/cfg/node}{MethodInvocationNode}.  Therefore,
+\refclass{checker/regex}{RegexTransfer} overrides the
+\code{visitMethodInvocation} method:
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  public TransferResult<CFValue, CFStore> visitMethodInvocation(
+    MethodInvocationNode n, TransferInput<CFValue, CFStore> in)  { ... }
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+
+\subsubsectionAndLabel{Program representation}{creating-dataflow-representation}
+
+% A \refclass{dataflow/cfg/node}{Node} generally maps one-to-one with a
+% \refTreeclass{tree}{Tree}. When dataflow processes a method, it translates
+% \refTreeclass{tree}{Tree}s into \refclass{dataflow/cfg/node}{Node}s and then
+% calls the appropriate visit method on
+% \refclass{framework/flow}{CFAbstractTransfer} which then performs the dataflow
+% analysis for the passed in \refclass{dataflow/cfg/node}{Node}.
+
+The \refclass{dataflow/cfg/node}{Node} subclasses can be found in the
+\code{org.checkerframework.dataflow.cfg.node} package.  Some examples are
+\refclass{dataflow/cfg/node}{EqualToNode},
+\refclass{dataflow/cfg/node}{LeftShiftNode},
+\refclass{dataflow/cfg/node}{VariableDeclarationNode}.
+
+A \refclass{dataflow/cfg/node}{Node}
+is basically equivalent to a javac compiler \refTreeclass{tree}{Tree}.
+
+See Section~\ref{creating-javac-tips} for more information about \refTreeclass{tree}{Tree}s.
+As an example, the statement \<String a = "";> is represented as this
+abstract syntax tree:
+\begin{Verbatim}
+VariableTree:
+  name: "a"
+  type:
+    IdentifierTree
+      name: String
+  initializer:
+    LiteralTree
+      value: ""
+\end{Verbatim}
+
+
+
+\subsectionAndLabel{Implement the refinement}{creating-dataflow-implement-refinement}
+
+\begin{sloppypar}
+Each visitor method in \refclass{framework/flow}{CFAbstractTransfer}
+returns a \refclass{dataflow/analysis}{TransferResult}.  A
+\refclass{dataflow/analysis}{TransferResult} represents the
+refined information that is known after an operation.  It has two
+components:  the result type for the \refclass{dataflow/cfg/node}{Node}
+being evaluated, and a map from expressions in scope to estimates of their
+types (a \refclass{dataflow/analysis}{Store}).  Each of these components is
+relevant to one of the two cases in
+Section~\ref{creating-dataflow-determine-expressions}:
+\end{sloppypar}
+
+\begin{enumerate}
+\item
+\begin{sloppypar}
+Changing the \refclass{dataflow/analysis}{TransferResult}'s result type changes
+the type that is returned by the \refclass{framework/type}{AnnotatedTypeFactory}
+for the tree corresponding to the \refclass{dataflow/cfg/node}{Node} that was
+visited.  (Remember that \refclass{common/basetype}{BaseTypeVisitor} uses the
+\refclass{framework/type}{AnnotatedTypeFactory} to look up the type of a
+\refTreeclass{tree}{Tree}, and then performs checks on types of one or more
+\refTreeclass{tree}{Tree}s.)
+\end{sloppypar}
+
+For example, When \refclass{checker/regex}{RegexTransfer} evaluates a
+\code{RegexUtils.asRegex} invocation, it updates the
+\refclass{dataflow/analysis}{TransferResult}'s result type. This changes the
+type of the \code{RegexUtils.asRegex} invocation when its
+\refTreeclass{tree}{Tree} is looked up by the
+\refclass{framework/type}{AnnotatedTypeFactory}.  See below for details.
+
+\item
+Updating the \refclass{dataflow/analysis}{Store} treats an expression as
+having a refined type for the remainder of the method or conditional block. For
+example, when the Nullness Checker's dataflow evaluates \code{myvar != null}, it
+updates the \refclass{dataflow/analysis}{Store} to specify that the variable
+\code{myvar} should be treated as having type \code{@NonNull} for the rest of the
+then conditional block.  Not all kinds of expressions can be refined; currently
+method return values, local variables, fields, and array values can be stored in
+the \refclass{dataflow/analysis}{Store}.  Other kinds of expressions, like
+binary expressions or casts, cannot be stored in the
+\refclass{dataflow/analysis}{Store}.
+
+\end{enumerate}
+
+
+\begin{sloppypar}
+The rest of this section details implementing the visitor method
+\code{RegexTransfer.visitMethodInvocation} for the \code{RegexUtil.asRegex}
+run-time test.  You can find other examples of visitor methods in
+\refclass{checker/lock}{LockTransfer} and
+\refclass{checker/formatter}{FormatterTransfer}.
+\end{sloppypar}
+
+
+
+\begin{enumerate}
+\item \textbf{Determine if the visited \refclass{dataflow/cfg/node}{Node} is of
+    interest}
+
+A visitor method is invoked for all
+instances of a given \refclass{dataflow/cfg/node}{Node} kind in the
+program.
+The visitor must inspect the
+\refclass{dataflow/cfg/node}{Node} to determine if it is an
+instance of the desired run-time test or operation.  For example,
+\code{visitMethodInvocation} is called when dataflow processes any method
+invocation, but the \refclass{checker/regex}{RegexTransfer} should only refine
+the result of \code{RegexUtils.asRegex} invocations:
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  @Override
+  public TransferResult<CFValue, CFStore> visitMethodInvocation(...)
+    ...
+    MethodAccessNode target = n.getTarget();
+    ExecutableElement method = target.getMethod();
+    Node receiver = target.getReceiver();
+    if (receiver instanceof ClassNameNode) {
+      String receiverName = ((ClassNameNode) receiver).getElement().toString();
+
+      // Is this a call to static method isRegex(s, groups) in a class named RegexUtil?
+      if (receiverName.equals("RegexUtil")
+          && ElementUtils.matchesElement(method,
+                 "isRegex", String.class, int.class)) {
+            ...
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+\item \textbf{Determine the refined type}
+
+Sometimes the refined type is dependent on the parts of the operation,
+such as arguments passed to it.
+
+For example, the refined type of \code{RegexUtils.asRegex} is dependent on the
+integer argument to the method call. The \refclass{checker/regex}{RegexTransfer}
+uses this argument to build the resulting type \code{@Regex(\emph{i})}, where \code{\emph{i}}
+is the value of the integer argument.  For simplicity the below code only uses
+the value of the integer argument if the argument was an integer literal.  It
+could be extended to use the value of the argument if it was any compile-time
+constant or was inferred at compile time by another analysis, such as the
+Constant Value Checker (\chapterpageref{constant-value-checker}).
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  AnnotationMirror regexAnnotation;
+  Node count = n.getArgument(1);
+  if (count instanceof IntegerLiteralNode) {
+    // argument is a literal integer
+    IntegerLiteralNode iln = (IntegerLiteralNode) count;
+    Integer groupCount = iln.getValue();
+    regexAnnotation = factory.createRegexAnnotation(groupCount);
+  } else {
+    // argument is not a literal integer; fall back to @Regex(), which is the same as @Regex(0)
+    regexAnnotation = AnnotationBuilder.fromClass(factory.getElementUtils(), Regex.class);
+  }
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+
+\item \textbf{Return a \refclass{dataflow/analysis}{TransferResult} with the
+    refined types}
+
+Recall that the type of an expression is refined by modifying the
+\refclass{dataflow/analysis}{TransferResult} returned by a visitor method.
+Since the \refclass{checker/regex}{RegexTransfer} is updating the type of
+the run-time test itself, it will update the result type and not the
+\refclass{dataflow/analysis}{Store}.
+
+A \refclass{framework/flow}{CFValue} is created to hold the type inferred.
+\refclass{framework/flow}{CFValue} is a wrapper class for values being inferred
+by dataflow:
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  CFValue newResultValue = analysis.createSingleAnnotationValue(regexAnnotation,
+      result.getResultValue().getType().getUnderlyingType());
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+Then, RegexTransfer's \code{visitMethodInvocation} creates and returns a
+\refclass{dataflow/analysis}{TransferResult} using \code{newResultValue} as the
+result type.
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  return new RegularTransferResult<>(newResultValue, result.getRegularStore());
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+As a result of this code, when the Regex Checker encounters a
+\code{RegexUtils.asRegex} method call, the checker will refine the return
+type of the method if it can determine the value of the integer parameter
+at compile time.
+
+\end{enumerate}
+
+
+\subsectionAndLabel{Disabling flow-sensitive inference}{creating-dataflow-disable}
+
+In the uncommon case that you wish to disable the Checker Framework's
+built-in flow inference in your checker (this is different than choosing
+not to extend it as described in Section~\ref{creating-dataflow}), put the
+following two lines at the beginning of the constructor for your subtype of
+\refclass{common/basetype}{BaseAnnotatedTypeFactory}:
+
+\begin{Verbatim}
+        // disable flow inference
+        super(checker, /*useFlow=*/ false);
+\end{Verbatim}
+
+
+\sectionAndLabel{Annotated JDK and other annotated libraries}{creating-a-checker-annotated-jdk}
+
+You will need to supply annotations for relevant parts of the JDK;
+otherwise, your type-checker may produce spurious warnings for code that
+uses the JDK\@.  You have two options:
+
+\begin{itemize}
+\item
+  Write JDK annotations in a fork of
+  \url{https://github.com/typetools/jdk}.
+
+  If your checker is written in a fork of
+  \url{https://github.com/typetools/jdk},
+  then use the same fork name (GitHub organization) and branch name;
+  this is necessary so that the CI jobs use the right annotated JDK.
+
+  Clone the JDK and the Checker Framework in the same place; that is, your
+  working copy of the JDK (a \<jdk> directory) should be a sibling of your
+  working copy of the Checker Framework (a \<checker-framework> directory).
+
+  Here are some tips:
+  \begin{itemize}
+  \item
+    Add
+    an \refqualclass{framework/qual}{AnnotatedFor} annotation to each file you annotate.
+  \item
+    Whenever you add a file, fully annotate it, as described in
+    Section~\ref{library-tips}.
+  \item
+    If you are only annotating fields and method signatures (but not
+    ensuring that method bodies type-check), then you don't need to suppress
+    warnings, because the JDK is not type-checked.
+  \end{itemize}
+
+\item
+  Write JDK annotations as stub files (partial Java source files).
+
+  Create a file \<jdk.astub> in
+the checker's main source directory.  You can also create \<jdk\emph{N}.astub> files that contain methods
+or classes that only exist in certain JDK versions.
+The JDK stub files will be automatically used by the
+checker, unless the user supplies the command-line option \<-Aignorejdkastub>.
+
+You can also supply \<.astub> files in that directory for other libraries.
+You should list those other libraries in a
+\refqualclass{framework/qual}{StubFiles} annotation on the checker's main
+class, so that they will also be automatically used.
+
+When a stub file should be used by multiple checkers (for example, if it
+contains purity annotations that are needed by multiple distinct checkers),
+the stub file should appear in directory \<checker/src/main/resources/>.
+In the distribution, it will appear at the top level of the \<checker.jar> file.
+It will not be used automatically; a user must pass the
+\<-Astubs=checker.jar/\emph{stubfilename.astub}> command-line argument
+(see Section~\ref{annotated-libraries-using})
+
+While creating a stub file, you may find the debugging options described in
+Section~\ref{stub-troubleshooting} useful.
+
+\end{itemize}
+
+
+\sectionAndLabel{Testing framework}{creating-testing-framework}
+
+The Checker Framework provides a convenient way to write tests for your
+checker.  Each test case is a Java file, with inline indications of what
+errors and warnings (if any) a checker should emit.  An example is
+
+\begin{Verbatim}
+class MyNullnessTest {
+  void method() {
+    Object nullable = null;
+    // :: error: (dereference.of.nullable)
+    nullable.toString();
+  }
+}
+\end{Verbatim}
+
+\noindent
+When the Nullness Checker is run on the above code, it should produce
+exactly one error, whose message key is \<dereference.of.nullable>, on
+the line following the ``// ::'' comment.
+
+% Don't repeat the information here, to prevent them from getting out of sync.
+The testing infrastructure is extensively documented in file \ahref{https://github.com/typetools/checker-framework/blob/master/checker/tests/README}{\<checker-framework/checker/tests/README>}.
+
+If your checker's source code is within a fork of the Checker Framework
+repository, then you can copy the testing infrastructure used by some
+existing type system.
+
+
+\sectionAndLabel{Debugging options}{creating-debugging-options}
+
+The Checker Framework provides debugging options that can be helpful when
+implementing a checker. These are provided via the standard \code{javac} ``\code{-A}''
+switch, which is used to pass options to an annotation processor.
+
+
+\subsectionAndLabel{Amount of detail in messages}{creating-debugging-options-detail}
+
+\begin{itemize}
+\item \code{-AprintAllQualifiers}: print all type qualifiers, including
+qualifiers meta-annotated with \code{@InvisibleQualifier}, which are
+usually not shown.
+
+\item \code{-AprintVerboseGenerics}: print more information about type
+  parameters and wildcards when they appear in warning messages.  Supplying
+  this also implies \code{-AprintAllQualifiers}.
+
+\item \code{-Anomsgtext}: use message keys (such as ``\code{type.invalid}'')
+rather than full message text when reporting errors or warnings.  This is
+used by the Checker Framework's own tests, so they do not need to be
+changed if the English message is updated.
+
+\item \code{-AnoPrintErrorStack}: don't print a stack trace when an
+internal Checker Framework error occurs.  Setting this option is rare.  You
+should only do it if you have discovered a bug in a checker, you have
+already reported the bug, and you want to continue using the checker on a
+large codebase without being inundated in stack traces.
+
+\item \code{-AdumpOnErrors}: Outputs a stack trace when reporting errors or warnings.
+
+\end{itemize}
+
+
+\subsectionAndLabel{Format of output}{creating-debugging-options-format}
+
+\begin{itemize}
+
+\item \code{-Adetailedmsgtext}: Output error/warning messages in a
+  stylized format that is easy for tools to parse.  This is useful for
+  tools that run the Checker Framework and parse its output, such as IDE
+  plugins.  See the source code of \<SourceChecker.java> for details about
+  the format.
+
+\end{itemize}
+
+The
+\ahref{https://github.com/eisopux/javac-diagnostics-wrapper}{javac-diagnostic-wrapper}
+tool can transform javac's textual output into other formats, such as JSON
+in LSP (Language Server Protocol) format.
+
+
+\subsectionAndLabel{Stub and JDK libraries}{creating-debugging-options-libraries}
+
+\begin{itemize}
+
+\item \code{-Aignorejdkastub}:
+  ignore the \<jdk.astub> and \<jdk\emph{N}.astub> files in the checker directory. Files passed
+  through the \code{-Astubs} option are still processed. This is useful
+  when experimenting with an alternative stub file.
+
+\item \code{-ApermitMissingJdk}:
+  don't issue an error if no annotated JDK can be found.
+
+\item \code{-AparseAllJdk}:
+  parse all JDK files at startup rather than as needed.
+
+\item \code{-AstubDebug}:
+  Print debugging messages while processing stub files.
+  Section~\ref{stub-troubleshooting} describes more diagnostic command-line
+  arguments.
+
+\end{itemize}
+
+\subsectionAndLabel{Progress tracing}{creating-debugging-options-progress}
+
+\begin{itemize}
+
+\item \code{-Afilenames}: print the name of each file before type-checking it.
+This can be useful for determining that a long compilation job is making
+progress.
+
+This option can also help to keep a Travis CI job alive (since Travis CI
+terminates any job that does not produce output for 10 minutes).
+This does not work if you are using Maven, because with forked compilation,
+the maven-compiler-plugin queues up all the output and then prints it at the end.
+(Also, the maven-compiler-plugin is buggy and sometimes doesn't print any
+output if it cannot parse it.)
+% Example of not producing output:
+% https://github.com/codehaus-plexus/plexus-compiler/issues/66
+
+\item \code{-Ashowchecks}: print debugging information for each
+pseudo-assignment check (\<commonAssignmentCheck>) performed by
+\refclass{common/basetype}{BaseTypeVisitor}; see
+Section~\ref{creating-extending-visitor}.
+
+\item \code{-AshowInferenceSteps}: print debugging information
+about intermediate steps in method type argument inference
+(as performed by \refclass{framework/util/typeinference}{DefaultTypeArgumentInference}).
+
+\end{itemize}
+
+\subsectionAndLabel{Saving the command-line arguments to a file}{creating-debugging-options-output-args}
+
+\begin{itemize}
+
+\item \code{-AoutputArgsToFile}:
+  This saves the final command-line parameters as passed to the compiler in a file.
+  The file can be used as a script to re-execute the same compilation command.
+  (The script file must be marked as executable on Unix, or
+  must have a \code{.bat} extension on Windows.)
+  Example usage: \code{-AoutputArgsToFile=\$HOME/scriptfile}
+
+  The \code{-AoutputArgsToFile} command-line argument is processed by
+  CheckerMain, not by the annotation processor.  That means that it can be
+  supplied only when you use the Checker Framework compiler (the ``Checker
+  Framework javac wrapper''), and it cannot be written in a file containing
+  command-line arguments passed to the compiler using the @argfile syntax.
+
+\end{itemize}
+
+\subsectionAndLabel{Visualizing the dataflow graph}{creating-debugging-dataflow-graph}
+
+To understand control flow in your program and the resulting type
+refinement, you can create a graphical representation of the CFG.
+
+Typical use is:
+
+\begin{Verbatim}
+javac -processor myProcessor -Aflowdotdir=. MyClass.java
+for dotfile in *.dot; do dot -Tpdf -o $dotfile.pdf $dotfile; done
+\end{Verbatim}
+
+\noindent
+where the first command creates file \<someDirectory/MyClass.dot> that
+represents the CFG, and the last command draws the CFG in a PDF file.
+The \<dot> program is part of \ahref{http://www.graphviz.org}{Graphviz}.
+
+In the output, conditional basic blocks are represented as octagons with
+two successors.  Special basic blocks are represented as ovals (e.g., the
+entry and exit point of the method).
+
+
+\subsubsectionAndLabel{Creating a CFG while running a type-checker}{creating-debugging-dataflow-graph-with-typechecker}
+
+To create a CFG while running a type-checker, use the following
+command-line options.
+
+\begin{itemize}
+
+\item \code{-Aflowdotdir=\emph{somedir}}:
+  Specify directory for \<.dot> files visualizing the CFG\@.
+  Shorthand for\\
+  \<-Acfgviz=org.checkerframework.dataflow.cfg.DOTCFGVisualizer,outdir=\emph{somedir}>.
+  % TODO: create the directory if it doesn't exist.
+  The directory must already exist.
+
+\item \code{-Averbosecfg}:
+  Enable additional output in the CFG visualization.
+  Equivalent to passing \<verbose> to \<cfgviz>, e.g. as in
+  \<-Acfgviz=MyVisualizer,verbose>
+
+\item \code{-Acfgviz=\emph{VizClassName}[,\emph{opts},...]}:
+  Mechanism to visualize the control flow graph (CFG) of
+  all the methods and code fragments
+  analyzed by the dataflow analysis (Section~\ref{creating-dataflow}).
+  The graph also contains information about flow-sensitively refined
+  types of various expressions at many program points.
+
+  The argument is a comma-separated sequence of keys or key--value pairs.
+  The first argument is the fully-qualified name of the
+  \<org.checkerframework.dataflow.cfg.CFGVisualizer> implementation
+  that should be used. The remaining keys or key--value pairs are
+  passed to \<CFGVisualizer.init>.  Supported keys include
+  \begin{itemize}
+  \item \<verbose>
+  \item \<outdir> directory into which to write files
+  \item \<checkerName>
+  \end{itemize}
+
+\end{itemize}
+
+
+\subsubsectionAndLabel{Creating a CFG by running CFGVisualizeLauncher}{creating-debugging-dataflow-graph-with-cfgvisualizelauncher}
+
+You can also use \refclass{dataflow/cfg}{CFGVisualizeLauncher} to generate a DOT
+or String representation of the control flow graph of a given method in a given class.
+The CFG is generated and output, but no dataflow analysis is performed.
+
+\begin{itemize}
+
+\item With JDK 8:
+
+\begin{smaller}
+\begin{Verbatim}
+java -Xbootclasspath/p:$CHECKERFRAMEWORK/checker/dist/javac.jar \
+  -cp $CHECKERFRAMEWORK/checker/dist/checker.jar \
+  org.checkerframework.dataflow.cfg.CFGVisualizeLauncher \
+  MyClass.java --class MyClass --method test --pdf
+\end{Verbatim}
+\end{smaller}
+
+
+\item With JDK 11:
+
+\begin{smaller}
+\begin{Verbatim}
+java -cp $CHECKERFRAMEWORK/checker/dist/checker.jar \
+  org.checkerframework.dataflow.cfg.CFGVisualizeLauncher \
+  MyClass.java --class MyClass --method test --pdf
+\end{Verbatim}
+\end{smaller}
+
+\end{itemize}
+
+\noindent
+The above command will generate the corresponding \<.dot> and \<.pdf> files for the
+method \code{test} in the class \code{MyClass} in the project directory.
+To generate a string representation of the graph to standard output,
+remove \<--pdf> but add \<--string>. For example (with JDK 8):
+
+\begin{smaller}
+\begin{Verbatim}
+java -Xbootclasspath/p:$CHECKERFRAMEWORK/checker/dist/javac.jar \
+  -cp $CHECKERFRAMEWORK/checker/dist/checker.jar \
+  org.checkerframework.dataflow.cfg.CFGVisualizeLauncher \
+  MyClass.java --class MyClass --method test --string
+\end{Verbatim}
+\end{smaller}
+
+For more details about invoking
+\refclass{dataflow/cfg}{CFGVisualizeLauncher}, run it with
+no arguments.
+
+
+\subsectionAndLabel{Miscellaneous debugging options}{creating-debugging-options-misc}
+
+\begin{itemize}
+
+\item \code{-AresourceStats}:
+  Whether to output resource statistics at JVM shutdown.
+
+\item \<-AatfDoNotCache>:
+  If provided, the Checker Framework will not cache results but will
+  recompute them.  This makes the Checker Framework run slower.  If the
+  Checker Framework behaves differently with and without this flag, then
+  there is a bug in its caching code.  Please report that bug.
+
+\item \<-AatfCacheSize>:
+  The size of the Checker Framework's internal caches.
+  Ignored if \<-AatfDoNotCache> is provided.
+  Most users have no need to set this.
+
+\end{itemize}
+
+
+\subsectionAndLabel{Examples}{creating-debugging-options-examples}
+
+The following example demonstrates how these options are used:
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+$ javac -processor org.checkerframework.checker.interning.InterningChecker \
+    docs/examples/InternedExampleWithWarnings.java -Ashowchecks -Anomsgtext -Afilenames
+
+[InterningChecker] InterningExampleWithWarnings.java
+ success (line  18): STRING_LITERAL "foo"
+     actual: DECLARED @org.checkerframework.checker.interning.qual.Interned java.lang.String
+   expected: DECLARED @org.checkerframework.checker.interning.qual.Interned java.lang.String
+ success (line  19): NEW_CLASS new String("bar")
+     actual: DECLARED java.lang.String
+   expected: DECLARED java.lang.String
+docs/examples/InterningExampleWithWarnings.java:21: (not.interned)
+    if (foo == bar) {
+            ^
+ success (line  22): STRING_LITERAL "foo == bar"
+     actual: DECLARED @org.checkerframework.checker.interning.qual.Interned java.lang.String
+   expected: DECLARED java.lang.String
+1 error
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+\subsectionAndLabel{Using an external debugger}{creating-debugging-options-external}
+
+You can use any standard debugger to observe the execution of your checker.
+
+You can also set up remote (or local) debugging using the following command as a template:
+
+\begin{Verbatim}
+java -jar "$CHECKERFRAMEWORK/checker/dist/checker.jar" \
+    -J-Xdebug -J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 \
+    -processor org.checkerframework.checker.nullness.NullnessChecker \
+    src/sandbox/FileToCheck.java
+
+\end{Verbatim}
+
+
+\sectionAndLabel{Documenting the checker}{creating-documenting-a-checker}
+
+This section describes how to write a chapter for this manual that
+describes a new type-checker.  This is a prerequisite to having your
+type-checker distributed with the Checker Framework, which is the best way
+for users to find it and for it to be kept up to date with Checker
+Framework changes.  Even if you do not want your checker distributed with
+the Checker Framework, these guidelines may help you write better
+documentation.
+
+When writing a chapter about a new type-checker, see the existing chapters
+for inspiration.  (But recognize that the existing chapters aren't perfect:
+maybe they can be improved too.)
+
+A chapter in the Checker Framework manual should generally have the
+following sections:
+
+\begin{description}
+
+\item[Chapter: Belly Rub Checker]
+  The text before the first section in the chapter should state the
+  guarantee that the checker provides and why it is important.  It should
+  give an overview of the concepts.  It should state how to run the checker.
+
+\item[Section: Belly Rub annotations]
+  This section includes descriptions of the annotations with links to the
+  Javadoc.  Separate type annotations from declaration annotations, and put
+  any type annotations that a programmer may not write (they are only used
+  internally by the implementation) last within variety of annotation.
+
+  Draw a diagram of the type hierarchy.  A textual description of
+  the hierarchy is not sufficient; the diagram really helps readers to
+  understand the system.
+  The diagram will appear in directory \<docs/manual/figures/>;
+  see its \<README> file for tips.
+
+  The Javadoc for the annotations deserves the same care as the manual
+  chapter.  Each annotation's Javadoc comment should use the
+  \<@checker\_framework.manual> Javadoc taglet to refer to the chapter that
+  describes the checker, and the polymorphic qualifier's Javadoc should
+  also refer to the \<qualifier-polymorphism> section.  For example, in
+  \<PolyPresent.java>:
+
+  \begin{Verbatim}
+ * @checker_framework.manual #optional-checker Optional Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+  \end{Verbatim}
+
+  \noindent
+  For more details, see \refclass{javacutil/dist}{ManualTaglet}.
+
+\item[Section: What the Belly Rub Checker checks]
+  This section gives more details about when an error is issued, with examples.
+  This section may be omitted if the checker does not contain special
+  type-checking rules --- that is, if the checker only enforces the usual
+  Java subtyping rules.
+
+\item[Section: Examples]
+  Code examples.
+\end{description}
+
+Sometimes you can omit some of the above sections.  Sometimes there are
+additional sections, such as tips on suppressing warnings, comparisons to
+other tools, and run-time support.
+
+You will create a new \<belly-rub-checker.tex> file,
+then \verb|\input| it at a logical place in \<manual.tex> (not
+necessarily as the last checker-related chapter).  Also add two references
+to the checker's chapter:  one at the beginning of
+chapter~\ref{introduction}, and identical text in the appropriate part of
+Section~\ref{type-refinement-runtime-tests}.  Add the new file to
+\<docs/manual/Makefile>.  Keep the lists in
+the same order as the manual chapters, to help us notice if anything is
+missing.
+
+For a chapter or (sub)*section, use \verb|\sectionAndLabel{Section title}{section-label}|.
+Section labels should start with the checker
+name (as in \verb|bellyrub-examples|) and not with ``\<sec:>''.
+Figure labels should start with ``fig-\emph{checkername}'' and not with ``fig:''.
+These conventions are for the benefit of the Hevea program that produces
+the HTML version of the manual.
+Use \verb|\begin{figure}| for all figures, including those whose
+content is a table, in order to have a single consistent numbering for all
+figures.
+
+Don't forget to write Javadoc for any annotations that the checker uses.
+That is part of the documentation and is the first thing that many users
+may see.  The documentation for any annotation should include an example
+use of the annotation.
+Also ensure that the Javadoc links back to the manual, using the
+\<@checker\_framework.manual> custom Javadoc tag.
+
+
+\sectionAndLabel{javac implementation survival guide}{creating-javac-tips}
+
+Since this section of the manual was written, the useful ``The Hitchhiker's
+Guide to javac'' has become available at
+\url{http://openjdk.java.net/groups/compiler/doc/hhgtjavac/index.html}.
+See it first, and then refer to this section.  (This section of the manual
+should be revised, or parts eliminated, in light of that document.)
+
+
+A checker built using the Checker Framework makes use of a few interfaces
+from the underlying compiler (Oracle's OpenJDK javac).
+This section describes those interfaces.
+
+
+
+
+\subsectionAndLabel{Checker access to compiler information}{creating-compiler-information}
+
+The compiler uses and exposes three hierarchies to model the Java
+source code and classfiles.
+
+
+\subsubsectionAndLabel{Types --- Java Language Model API}{creating-javac-types}
+
+A \refModelclass{type}{TypeMirror} represents a Java type.
+% Java declaration, statement, or expression.
+
+\begin{sloppypar}
+There is a \code{TypeMirror} interface to represent each type kind,
+e.g., \code{PrimitiveType} for primitive types, \code{ExecutableType}
+for method types, and \code{NullType} for the type of the \code{null} literal.
+\end{sloppypar}
+
+\code{TypeMirror} does not represent annotated types though.  A checker
+should use the Checker Framework types API,
+\refclass{framework/type}{AnnotatedTypeMirror}, instead.  \code{AnnotatedTypeMirror}
+parallels the \code{TypeMirror} API, but also presents the type annotations
+associated with the type.
+
+The Checker Framework and the checkers use the types API extensively.
+
+
+\subsubsectionAndLabel{Elements --- Java Language Model API}{creating-javac-elements}
+
+An \refModelclass{element}{Element} represents a potentially-public
+declaration that can be accessed from elsewhere:  classes, interfaces, methods, constructors, and
+fields.  \<Element> represents elements found in both source
+code and bytecode.
+
+There is an \code{Element} interface to represent each construct, e.g.,
+\code{TypeElement} for classes/interfaces, \code{ExecutableElement} for
+methods/constructors, and \code{VariableElement} for local variables and
+method parameters.
+
+If you need to operate on the declaration level, always use elements rather
+than trees
+% in same subsection, which is the limit of the numbering.
+% (Section~\ref{javac-trees})
+(see below).  This allows the code to work on
+both source and bytecode elements.
+
+Example: retrieve declaration annotations, check variable
+modifiers (e.g., \code{strictfp}, \code{synchronized})
+
+
+\subsubsectionAndLabel{Trees --- Compiler Tree API}{creating-javac-trees}
+
+A \refTreeclass{tree}{Tree} represents a syntactic unit in the source code,
+such as a method declaration, statement, block, \<for> loop, etc. Trees only
+represent source code to be compiled (or found in \code{-sourcepath});
+no tree is available for classes read from bytecode.
+
+There is a Tree interface for each Java source structure, e.g.,
+\code{ClassTree} for class declaration, \code{MethodInvocationTree}
+for a method invocation, and \code{ForEachTree} for an enhanced-for-loop
+statement.
+
+You should limit your use of trees. A checker uses Trees mainly to
+traverse the source code and retrieve the types/elements corresponding to
+them.  Then, the checker performs any needed checks on the types/elements instead.
+
+
+\subsubsectionAndLabel{Using the APIs}{creating-using-the-apis}
+
+The three APIs use some common idioms and conventions; knowing them will
+help you to create your checker.
+
+\emph{Type-checking}:
+Do not use \code{instanceof Subinterface} to determine which kind of \<TypeMirror> or \<Element> an expression is,
+because some of the classes that implement the TypeMirror and Element subinterfaces implement multiple
+subinterfaces. For example, \<type instanceof DeclaredType> and \<type instanceof UnionType>
+both return true if \<type> is an \<com.sun.tools.javac.code.Type.UnionClassType> object.
+
+Instead, use the
+\sunjavadoc{java.compiler/javax/lang/model/type/TypeMirror.html\#getKind()}{TypeMirror.getKind()}
+or
+\sunjavadoc{java.compiler/javax/lang/model/element/Element.html\#getKind()}{Element.getKind()}
+method.  For example, if \<type> is an \<com.sun.tools.javac.code.Type.UnionClassType> object, then
+\<expr.getKind() == TypeKind.DECLARED> is false and \<expr.getKind() == TypeKind.UNION> is true.
+
+
+For \<Tree>s, you can use either \<Tree.getKind()> or \<instanceof>.
+
+\emph{Visitors and Scanners}:
+The compiler and the Checker Framework use the visitor pattern
+extensively. For example, visitors are used to traverse the source tree
+(\refclass{common/basetype}{BaseTypeVisitor} extends
+\refTreeclass{util}{TreePathScanner}) and for type
+checking (\refclass{framework/type/treeannotator}{TreeAnnotator} implements
+\refTreeclass{tree}{TreeVisitor}).
+
+\emph{Utility classes}:
+Some useful methods appear in a utility class.  The OpenJDK convention is that
+the utility class for a \code{Foo} hierarchy is \code{Foos} (e.g.,
+\refModelclass{util}{Types}, \refModelclass{util}{Elements}, and
+\refTreeclass{util}{Trees}).  The Checker Framework uses a common
+\code{Utils} suffix to distinguish the class names (e.g., \refclass{javacutil}{TypesUtils},
+\refclass{javacutil}{TreeUtils}, \refclass{javacutil}{ElementUtils}), with one
+notable exception: \refclass{framework/util}{AnnotatedTypes}.
+
+
+\subsubsectionAndLabel{Equality for annotations}{equality-for-annotations}
+
+\<AnnotationMirror> is an interface that is implemented both by javac and
+the Checker Framework. The documentation of
+\refModelclass{element}{AnnotationMirror} says, ``Annotations should be
+compared using the equals method. There is no guarantee that any particular
+annotation will always be represented by the same object.''  The second
+sentence is true, but the first sentence is wrong.  You should never
+compare \<AnnotationMirror>s using \<equals()>, which (for some
+implementations) is reference equality.
+\refclass{javacutil}{AnnotationUtils} has various
+methods that should be used instead. Also,
+\refclass{framework/util}{AnnotationMirrorMap} and
+\refclass{framework/util}{AnnotationMirrorSet} can be used.
+
+
+\subsectionAndLabel{How a checker fits in the compiler as an annotation processor}{creating-checker-as-annotation-processor}
+
+The Checker Framework builds on the Annotation Processing API
+introduced in Java 6.  A type-checking annotation processor is one that extends
+\refclass{javacutil}{AbstractTypeProcessor}; it gets run on each class
+source file after the compiler confirms that the class is valid Java code.
+
+The most important methods of \refclass{javacutil}{AbstractTypeProcessor}
+are \code{typeProcess} and \code{getSupportedSourceVersion}. The former
+class is where you would insert any sort of method call to walk the AST\@,
+and the latter just returns a constant indicating that we are targeting
+version 8 of the compiler. Implementing these two methods should be enough
+for a basic plugin; see the Javadoc for the class for other methods that
+you may find useful later on.
+
+The Checker Framework uses Oracle's Tree API to access a program's AST\@.
+The Tree API is specific to the Oracle OpenJDK, so the Checker Framework only
+works with the OpenJDK javac, not with Eclipse's compiler ecj.
+This also limits the tightness of
+the integration of the Checker Framework into other IDEs such as \href{https://www.jetbrains.com/idea/}{IntelliJ IDEA}\@.
+An implementation-neutral API would be preferable.
+In the future, the Checker Framework
+can be migrated to use the Java Model AST of JSR 198 (Extension API for
+Integrated Development Environments)~\cite{JSR198}, which gives access to
+the source code of a method.  But, at present no tools
+implement JSR~198.  Also see Section~\ref{creating-ast-traversal}.
+
+
+
+\subsubsectionAndLabel{Learning more about javac}{creating-learning-more-about-javac}
+
+Sun's javac compiler interfaces can be daunting to a
+newcomer, and its documentation is a bit sparse. The Checker Framework
+aims to abstract a lot of these complexities.
+You do not have to understand the implementation of javac to
+build powerful and useful checkers.
+Beyond this document,
+other useful resources include the Java Infrastructure
+Developer's guide at
+\url{https://netbeans.apache.org/wiki/Java_DevelopersGuide.asciidoc} and the compiler
+mailing list archives at
+\url{http://mail.openjdk.java.net/pipermail/compiler-dev/}
+(subscribe at
+\url{http://mail.openjdk.java.net/mailman/listinfo/compiler-dev}).
+
+
+\sectionAndLabel{Integrating a checker with the Checker Framework}{creating-integrating-a-checker}
+
+% First version of how to integrate a new checker into the release.
+% TODO: what steps are missing?
+
+To integrate a new checker with the Checker Framework release, perform
+the following:
+
+\begin{itemize}
+
+\item Make sure \code{check-compilermsgs} and \code{check-purity} run
+without warnings or errors.
+
+\end{itemize}
+
+
+% LocalWords:  plugin javac's SourceChecker AbstractProcessor getMessages quals
+% LocalWords:  getSourceVisitor SourceVisitor getFactory AnnotatedTypeFactory
+% LocalWords:  SupportedAnnotationTypes SupportedSourceVersion TreePathScanner
+% LocalWords:  TreeScanner visitAssignment AssignmentTree AnnotatedClassTypes
+% LocalWords:  SubtypeChecker SubtypeVisitor NonNull isSubtype getClass nonnull
+% LocalWords:  AnnotatedClassType isAnnotatedWith hasAnnotationAt TODO src jdk
+% LocalWords:  processor NullnessChecker InterningChecker Nullness Nullable
+% LocalWords:  AnnotatedTypeMirrors BaseTypeChecker BaseTypeVisitor basetype
+% LocalWords:  Aqual Anqual java CharSequence getAnnotatedType UseLovely
+% LocalWords:  AnnotatedTypeMirror LovelyChecker Anomsgtext Ashowchecks enums
+% LocalWords:  Afilenames dereferenced SuppressWarnings declaratively SubtypeOf
+% LocalWords:  TypeHierarchy GraphQualifierHierarchy Foo qual UnknownSign
+% LocalWords:  QualifierHierarchy QualifierRoot createQualifierHierarchy util
+% LocalWords:  createTypeHierarchy ImplicitFor treeClasses TypeMirror Anno
+% LocalWords:  LiteralTree ExpressionTree typeClasses addComputedTypeAnnotations nullable
+% LocalWords:  createSupportedTypeQualifiers FooChecker nullness KeyFor
+% LocalWords:  FooVisitor FooAnnotatedTypeFactory basicstyle InterningVisitor
+% LocalWords:  InterningAnnotatedTypeFactory QualifierDefaults TypeKind getKind
+% LocalWords:  setAbsoluteDefaults PolymorphicQualifier TreeVisitor subnodes
+% LocalWords:  SimpleTreeVisitor TreePath instanceof subinterfaces TypeElement
+% LocalWords:  ExecutableElement PackageElement DeclaredType VariableElement
+% LocalWords:  TypeParameterElement ElementVisitor javax getElementUtils NoType
+% LocalWords:  ProcessingEnvironment ExecutableType MethodTree ArrayType Warski
+% LocalWords:  MethodInvocationTree PrimitiveType BlockTree TypeVisitor blog
+% LocalWords:  AnnotatedTypeVisitor SimpleAnnotatedTypeVisitor html langtools
+% LocalWords:  AnnotatedTypeScanner bootclasspath asType stringPatterns Foos
+% LocalWords:  DefaultQualifierInHierarchy invocable wildcards novariant Utils
+% LocalWords:  AggregateChecker getSupportedTypeCheckers Uninterned sourcepath
+% LocalWords:  DefaultQualifier bytecode NullType strictfp ClassTree TypesUtils
+% LocalWords:  ForEachTree ElementKind TreeAnnotator TreeUtils ElementUtils ecj
+% LocalWords:  AnnotatedTypes AbstractTypeProcessor gcj hardcoding jsr api
+% LocalWords:  typeProcess getSupportedSourceVersion fenum classpath astub
+%%  LocalWords:  addAbsoluteDefault BaseAnnotatedTypeFactory superclasses
+%%  LocalWords:  SupportedOptions AprintAllQualifiers InvisibleQualifier
+%%  LocalWords:  Adetailedmsgtext AnoPrintErrorStack Aignorejdkastub Astubs
+%%  LocalWords:  ApermitMissingJdk AstubDebug Aflowdotdir AresourceStats Regex
+%%  LocalWords:  classfiles CHECKERFRAMEWORK RegexUtil asRegex myString
+%%  LocalWords:  myInt CFAbstractTransfer RegexTransfer CFAbstractAnalysis
+%%  LocalWords:  createTransferFunction RegexAnalysis createFlowAnalysis
+%%  LocalWords:  EqualToNode LeftShiftNode VariableDeclarationNode myvar
+%%  LocalWords:  MethodInvocationNode visitMethodInvocation TransferResult
+%%  LocalWords:  RegexUtils LockTransfer FormatterTransfer CFValue argfile
+%%  LocalWords:  RegexTransfer's newResultValue subcheckers taglet tex XXX
+%%  LocalWords:  ParameterizedCheckerTest AoutputArgsToFile ManualTaglet
+%%  LocalWords:  Hevea Hitchhiker's compilermsgs args Poly MyTypeSystem
+%%  LocalWords:  I18nChecker i18n I18nSubchecker LocalizableKeyChecker ast
+%%  LocalWords:  MyChecker MyHelperChecker getImmediateSubcheckerClasses
+%%  LocalWords:  MyChecker's subchecker plugins ElementType myClass myflag
+%%  LocalWords:  CheckerFrameworkTest GenericAnnotatedTypeFactory MyClass
+%%  LocalWords:  addCheckedCodeDefaults RelevantJavaTypes TargetLocations
+%%  LocalWords:  TypeUseLocation createExpressionAnnoHelper fromNode
+%%  LocalWords:  ExpressionAnnotationHelper JavaExpressions CFTransfer LSP
+%%  LocalWords:  AnnotationProvider FooTransfer createFlowTransferFunction
+%%  LocalWords:  SupportedLintOptions myoption StubFiles scriptfile outdir
+%%  LocalWords:  somedir Acfgviz Averbosecfg cfgviz MyVisualizer init apis
+%%  LocalWords:  VizClassName CFGVisualizer MyProp MyPropChecker mypackage
+%  LocalWords:  SourceFile NonNegative JavaExpression DependentTypesHelper
+%  LocalWords:  createDependentTypesHelper boolean regex subclasses README
+%  LocalWords:  formatter nChecker nSubchecker AprintVerboseGenerics pdf
+%  LocalWords:  AshowInferenceSteps DefaultTypeArgumentInference Graphviz
+%  LocalWords:  javacutil LiteralKind EnsuresQualifier EnsuresQualifierIf
+%%  LocalWords:  mychecker AnnotationBuilder AnnotationUtils typequals
+%%  LocalWords:  TypeAnnotationUtils reimplementing typesystem TreeType
+%%  LocalWords:  getTypeFactoryOfSubchecker someDirectory checkername
+%%  LocalWords:  AnnotationMirror AnnotationMirrorMap AnnotationMirrorSet
+%%  LocalWords:  processorpath CheckerName EnsuresNonNullIf reportError
+%%  LocalWords:  reportWarning AnnotatedFor AdumpOnErrors AparseAllJdk
+% LocalWords:  createTreeAnnotator ListTreeAnnotator TypeAnnotator
+% LocalWords:  createTypeAnnotator ListTypeAnnotator CFGVisualizeLauncher
+% LocalWords:  PropagationTreeAnnotator checkerName cfgvisualizelauncher
diff --git a/docs/manual/external-checkers.tex b/docs/manual/external-checkers.tex
new file mode 100644
index 0000000..e0c50ff
--- /dev/null
+++ b/docs/manual/external-checkers.tex
@@ -0,0 +1,243 @@
+\htmlhr
+\chapterAndLabel{Third-party checkers\label{external-checkers}}{third-party-checkers}
+
+The Checker Framework has been used to build other checkers that are not
+distributed together with the framework.  This chapter mentions just a few
+of them.  They are listed in reverse chronological order; newer ones appear
+first and older ones appear last.
+
+They are externally-maintained, so if you have problems or questions, you
+should contact their maintainers rather than the Checker Framework
+maintainers.
+
+If you want this chapter to reference your checker,
+please send us a link and a short description.
+
+
+% Note to maintainers:
+% Sections are added to this chapter in reverse chronological order, at the top.
+
+
+\sectionAndLabel{Determinism checker}{deteriminism-checker}
+
+The
+\href{https://github.com/t-rasmud/checker-framework/tree/nondet-checker}{Determinism
+  Checker} ensures that a program is deterministic across executions.  A
+determinismic program is easier to test, and it is easier to debug (such as
+comparing executions).
+
+The Determinism Checker focuses on sequential programs.  It detects
+determinism due to the iteration order of a hash table (or on any other
+property of hash codes), default formatting (such as Java's
+\<Object.toString()>, which includes a memory address), \<random>,
+date-and-time functions, and accessing system properties such as the file
+system or environment variables.
+
+
+\sectionAndLabel{AWS crypto policy compliance checker}{crypto-policy-compliance-checker}
+
+The
+\href{https://github.com/awslabs/aws-crypto-policy-compliance-checker}{AWS
+  crypto policy compliance checker} checks that no weak cipher algorithms
+are used with the Java crypto API\@.
+
+
+\sectionAndLabel{AWS KMS compliance checker}{kms-compliance-checker}
+
+The \href{https://github.com/awslabs/aws-kms-compliance-checker}{AWS KMS
+  compliance checker} extends the Constant Value Checker (see
+\chapterpageref{constant-value-checker}) to enforce that calls to Amazon
+Web Services' Key Management System only request 256-bit (or longer) data
+keys.  This checker can be used to help enforce a compliance requirement
+(such as from SOC or PCI-DSS) that customer data is always encrypted with
+256-bit keys.
+
+The KMS compliance checker is available in Maven Central.  To use it in
+\<build.gradle>, add the following dependency:
+
+\begin{Verbatim}
+compile group: 'software.amazon.checkerframework', name: 'aws-kms-compliance-checker', version: '1.0.2'
+\end{Verbatim}
+
+\ahref{https://mvnrepository.com/artifact/software.amazon.checkerframework/aws-kms-compliance-checker/1.0.2}{Other build systems} are similar.
+
+
+\sectionAndLabel{UI Thread Checker for ReactiveX}{rx-thread-checker}
+
+The \href{https://www.bennostein.org/ase18.pdf}{Rx Thread \& Effect Checker}~\cite{SteinCSC2018} enforces
+UI Thread safety properties for stream-based Android applications and is available at
+\url{https://github.com/uber-research/RxThreadEffectChecker}.
+
+
+\sectionAndLabel{Glacier:  Class immutability}{glacier-immutability-checker}
+
+\href{http://mcoblenz.github.io/Glacier/}{Glacier}~\cite{CoblenzNAMS2017}
+enforces transitive class immutability in Java.  According to its webpage:
+
+\begin{itemize}
+\item
+  Transitive: if a class is immutable, then every field must be
+  immutable. This means that all reachable state from an immutable object's
+  fields is immutable.
+\item
+  Class: the immutability of an object depends only on its class's
+  immutability declaration.
+\item
+  Immutability: state in an object is not changable through any reference to
+  the object.
+\end{itemize}
+
+
+\sectionAndLabel{SQL checker that supports multiple dialects}{sql-schecker}
+
+\href{http://www.jooq.org/}{jOOQ} is a database API that lets you build
+typesafe SQL queries.  jOOQ version 3.0.9 and later ships with a SQL
+checker that provides even more safety:  it ensures that you don't
+use SQL features that are not supported by your database
+implementation.  You can learn about the SQL checker at
+\url{https://blog.jooq.org/2016/05/09/jsr-308-and-the-checker-framework-add-even-more-typesafety-to-jooq-3-9/}.
+
+
+\sectionAndLabel{Read Checker for CERT FIO08-J}{read-checker}
+
+CERT
+rule \href{https://www.securecoding.cert.org/confluence/display/java/FIO08-J.+Distinguish+between+characters+or+bytes+read+from+a+stream+and+-1}{FIO08-J}
+describes a rule for the correct handling of characters/bytes read
+from a stream.
+
+The Read Checker enforces this rule.
+It is available from
+\url{https://github.com/opprop/ReadChecker}.
+
+
+\sectionAndLabel{Nullness Rawness Checker}{initialization-rawness-checker}
+
+The Nullness Rawness Checker is a nullness checker that uses a different type system for initialization.
+It was distributed with the Checker Framework through release 2.9.0 (dated 3 July 2019). If you wish
+to use them, install \href{https://checkerframework.org/releases/2.9.0/}{Checker Framework version 2.9.0}.
+
+
+\sectionAndLabel{Immutability checkers:  IGJ, OIGJ, and Javari\label{javari-checker}}{igj-checker}
+
+Javari~\cite{TschantzE2005}, IGJ~\cite{ZibinPAAKE2007}, and
+OIGJ~\cite{ZibinPLAE2010} are type systems that enforce immutability
+constraints.  Type-checkers for all three type systems were distributed
+with the Checker Framework through release 1.9.13 (dated 1 April 2016).
+If you wish to use them, install
+\href{https://checkerframework.org/releases/1.9.13/}{Checker
+  Framework version 1.9.13}.
+
+They were removed from the main distribution on June 1, 2016 because the
+implementations were not being maintained as the Checker Framework evolved.
+The type systems are valuable, and some people found the type-checkers
+useful.  However,
+% the type-checkers should be rewritten from scratch and
+we wanted
+to focus on distributing checkers that are currently being maintained.
+
+
+\sectionAndLabel{SPARTA information flow type-checker for Android}{sparta-checker}
+
+SPARTA is a security toolset aimed at preventing malware from appearing in
+an app store.  SPARTA provides an information-flow type-checker that is
+customized to Android but can also be applied to other domains.
+The SPARTA toolset is available from
+\url{https://checkerframework.org/sparta/}.
+The paper
+\href{https://homes.cs.washington.edu/~mernst/pubs/infoflow-ccs2014.pdf}{``Collaborative
+    verification of information flow for a high-assurance app store''}
+  appeared in CCS 2014.
+
+
+\sectionAndLabel{CheckLT taint checker}{checklt-checker}
+
+CheckLT uses taint tracking to detect illegal information flows, such as
+unsanitized data that could result in a SQL injection attack.
+CheckLT is available from \url{http://checklt.github.io/}.
+
+
+\sectionAndLabel{EnerJ checker}{enerj-checker}
+
+A checker for EnerJ~\cite{SampsonDFGCG2011}, an extension to Java that exposes hardware faults
+in a safe, principled manner to save energy with only
+slight sacrifices to the quality of service, is available from
+\url{http://sampa.cs.washington.edu/research/approximation/enerj.html}.
+
+
+\sectionAndLabel{Generic Universe Types checker}{gut-checker}
+
+A checker for Generic Universe Types~\cite{DietlEM2011}, a lightweight ownership type
+system, is available from
+\url{https://ece.uwaterloo.ca/~wdietl/ownership/}.
+
+
+\sectionAndLabel{Safety-Critical Java checker}{safety-critical-java-checker}
+
+A checker for Safety-Critical Java (SCJ, JSR 302)~\cite{TangPJ2010} is available at
+\url{https://www.cs.purdue.edu/sss/projects/oscj/checker/checker.html}.
+Developer resources are available at the project page
+\url{https://code.google.com/archive/p/scj-jsr302/}.
+
+
+% In a mail from Aleš Plšek <aplsek@gmail.com> on 29.03.2011:
+
+% Name: SCJ Checker
+% WWW: http://sss.cs.purdue.edu/projects/oscj/checker/checker.html
+% Source-Code Repository: http://code.google.com/p/scj-jsr302/
+
+% Description: The SCJ Checker implements verification of a set of
+% annotations defined by the Safety-Critical Java standard (JSR-302).
+% The checker mainly focuses on proving memory safety of Java programs
+% that use a region-based memory management.
+
+% Publications: Static checking of safety critical Java annotations:
+% http://portal.acm.org/citation.cfm?doid=1850771.1850792
+
+
+\sectionAndLabel{Thread locality checker}{loci-thread-locality-checker}
+
+Loci~\cite{WrigstadPMZV2009}, a checker for thread locality, is available at
+\url{http://www.it.uu.se/research/upmarc/loci/}.
+%% This URL is broken as of
+% Developer resources are available at the project page
+% \url{http://java.net/projects/loci/}.
+
+% A paper was publishd in ECOOP 2009, release 0.1 was made in March 2011,
+% but as of October 2013 and June 2017 the manual is still listed as "forthcoming".
+
+
+% In a mail from Amanj Mahmud <amanjpro@gmail.com> on 28.03.2011:
+
+% The plugin name:
+% ``Loci: A Pluggable Type Checker for Expressing Thread Locality in
+% Java''
+
+% Project homepage: http://www.it.uu.se/research/upmarc/loci
+
+% Project's developer's page: http://java.net/projects/loci
+
+
+\sectionAndLabel{Units and dimensions checker}{units-and-dimensions-checker}
+
+A checker for units and dimensions is available at
+\url{https://www.lexspoon.org/expannots/}.
+
+Unlike the Units Checker that is distributed with the Checker Framework
+(see Section~\ref{units-checker}), this checker includes dynamic checks and
+permits annotation arguments that are Java expressions.  This added
+flexibility, however, requires that you use a special version both of the
+Checker Framework and of the javac compiler.
+
+
+\input{typestate-checker}
+
+
+%%% *****
+%%% DO NOT EDIT HERE!
+%%% New sections go at the top of the file, not the bottom.
+%%% *****
+
+
+
+% LocalWords:  SCJ EnerJ CheckLT unsanitized JavaUI CCS IGJ OIGJ FIO08
+%%  LocalWords:  jOOQ typesafe
diff --git a/docs/manual/external-tools.tex b/docs/manual/external-tools.tex
new file mode 100644
index 0000000..d338db4
--- /dev/null
+++ b/docs/manual/external-tools.tex
@@ -0,0 +1,1360 @@
+\htmlhr
+\chapterAndLabel{Integration with external tools}{external-tools}
+
+This chapter discusses how to run a checker from the command line, from a
+build system, or from an IDE\@.  You can skip to the appropriate section:
+
+% Keep this list up to date with the sections of this chapter and with a
+% copy of the list in file introduction.tex .
+\begin{itemize}
+\item Android (Section~\ref{android})
+\item Android Gradle Plugin (Section~\ref{android-gradle})
+\item Ant (Section~\ref{ant-task})
+\item Buck (Section~\ref{buck})
+\item Command line, via Checker Framework javac wrapper (Section~\ref{javac-wrapper})
+\item Command line, via JDK javac (Section~\ref{javac})
+\item Eclipse (Section~\ref{eclipse})
+\item Gradle (Section~\ref{gradle})
+\item IntelliJ IDEA (Section~\ref{intellij})
+\item javac Diagnostics Wrapper (Section~\ref{javac-diagnostics-wrapper})
+\item Lombok (Section~\ref{lombok})
+\item Maven (Section~\ref{maven})
+\item NetBeans (Section~\ref{netbeans})
+\item sbt (Section~\ref{sbt})
+\item tIDE (Section~\ref{tide})
+\end{itemize}
+
+If your build system or IDE is not listed above, you should customize how
+it runs the javac command on your behalf.  See your build system or IDE
+documentation to learn how to
+customize it, adapting the instructions for javac in Section~\ref{javac}.
+If you make another tool support running a checker, please
+inform us via the
+\href{https://groups.google.com/forum/#!forum/checker-framework-discuss}{mailing
+  list} or
+\href{https://github.com/typetools/checker-framework/issues}{issue tracker} so
+we can add it to this manual.
+
+All examples in this chapter are in the public domain, with no copyright nor
+licensing restrictions.
+
+
+\sectionAndLabel{Android}{android}
+
+When creating an Android app, you may wish to use \<checker-qual-android>
+whenever this document mentions \<checker-qual>.  This can lead to smaller
+dex files (smaller distributed apps).
+
+The \<checker-qual-android> artifact is identical to the \<checker-qual>
+artifact, except that in \<checker-qual-android> annotations have classfile
+retention.  The default Android Gradle plugin retains types annotated with
+runtime-retention annotations in the main dex, but strips out class-retention
+annotations.
+
+
+\sectionAndLabel{Android Studio and the Android Gradle Plugin}{android-gradle}
+
+Android Studio 3.0 and later, and Android Gradle Plugin 3.0.0 and later, support type
+annotations.  (See
+\url{https://developer.android.com/studio/write/java8-support}
+for more details.)  This section explains how to configure your Android
+project to use the Checker Framework.  All the changes should be made to
+the module's \<build.gradle> file --- not the project's \<build.gradle> file.
+
+Different changes are required for JDK 8
+(Section~\ref{android-jdk8}) and for JDK 9+ (Section~\ref{android-jdk11}).
+
+\subsectionAndLabel{JDK 8}{android-jdk8}
+This section shows what changes to make if you are using JDK 8.
+
+\begin{enumerate}
+
+\item In your module's \<build.gradle> file, set the source and target
+  compatibility to \<JavaVersion.VERSION\_1\_8>:
+
+\begin{Verbatim}
+android {
+    ...
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+}
+\end{Verbatim}
+
+\item Add a build variant for running checkers:
+
+ \begin{Verbatim}
+ android {
+    ...
+      buildTypes {
+      ...
+        checkTypes {
+            javaCompileOptions.annotationProcessorOptions.
+                    classNames.add("org.checkerframework.checker.nullness.NullnessChecker")
+            // You can pass options like so:
+            // javaCompileOptions.annotationProcessorOptions.arguments.put("warns", "")
+        }
+    }
+}
+\end{Verbatim}
+
+\item Add a dependency configuration for the Java 9 compiler:
+
+\begin{mysmall}
+\begin{Verbatim}
+configurations {
+    errorproneJavac {
+        description = 'Java 9 compiler; required to run the Checker Framework under JDK 8.'
+    }
+}
+
+\end{Verbatim}
+\end{mysmall}
+
+\item Declare the Checker Framework dependencies:
+
+\begin{mysmall}
+\begin{Verbatim}
+dependencies {
+    ... existing dependencies...
+    ext.checkerFrameworkVersion = '3.13.0'
+    implementation "org.checkerframework:checker-qual-android:${checkerFrameworkVersion}"
+    // or if you use no annotations in source code the above line could be
+    // compileOnly "org.checkerframework:checker-qual-android:${checkerFrameworkVersion}"
+    annotationProcessor "org.checkerframework:checker:${checkerFrameworkVersion}"
+    errorproneJavac 'com.google.errorprone:javac:9+181-r4173-1'
+}
+\end{Verbatim}
+\end{mysmall}
+
+\item Direct all tasks of type \<JavaCompile> used by the \<checkTypes>
+  build variant to use the Error Prone compiler:
+\begin{mysmall}
+\begin{Verbatim}
+gradle.projectsEvaluated {
+    tasks.withType(JavaCompile).all { compile ->
+        if (compile.name.contains("CheckTypes")) {
+            options.fork = true
+            options.forkOptions.jvmArgs += ["-Xbootclasspath/p:${configurations.errorproneJavac.asPath}".toString()]
+        }
+    }
+}
+\end{Verbatim}
+\end{mysmall}
+
+\item To run the checkers, build using the \<checkTypes> variant:
+\begin{Verbatim}
+gradlew assembleCheckTypes
+\end{Verbatim}
+\end{enumerate}
+
+\subsectionAndLabel{JDK 11}{android-jdk11}
+This section shows what changes to make if you are using JDK 11.  They should work for JDK 9+, but
+we have only tested them with JDK 11.
+
+\begin{enumerate}
+
+\item In your module's \<build.gradle> file, set the source and target
+  compatibility to \<JavaVersion.VERSION\_1\_8>:
+
+\begin{Verbatim}
+android {
+    ...
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+}
+\end{Verbatim}
+
+\item Add a build variant for running checkers:
+
+ \begin{Verbatim}
+ android {
+    ...
+      buildTypes {
+      ...
+        checkTypes {
+            javaCompileOptions.annotationProcessorOptions.
+                    classNames.add("org.checkerframework.checker.nullness.NullnessChecker")
+            // You can pass options like so:
+            // javaCompileOptions.annotationProcessorOptions.arguments.put("warns", "")
+        }
+    }
+}
+\end{Verbatim}
+
+\item Declare the Checker Framework dependencies:
+
+\begin{mysmall}
+\begin{Verbatim}
+dependencies {
+    ... existing dependencies...
+    ext.checkerFrameworkVersion = '3.13.0'
+    implementation "org.checkerframework:checker-qual-android:${checkerFrameworkVersion}"
+    // or if you use no annotations in source code the above line could be
+    // compileOnly "org.checkerframework:checker-qual-android:${checkerFrameworkVersion}"
+    annotationProcessor "org.checkerframework:checker:${checkerFrameworkVersion}"
+}
+\end{Verbatim}
+\end{mysmall}
+
+\item To run the checkers, build using the \<checkTypes> variant:
+\begin{Verbatim}
+gradlew assembleCheckTypes
+\end{Verbatim}
+
+\end{enumerate}
+
+
+\sectionAndLabel{Ant task}{ant-task}
+
+If you use the \href{http://ant.apache.org/}{Ant} build tool to compile
+your software, then you can add an Ant task that runs a checker.  We assume
+that your Ant file already contains a compilation target that uses the
+\code{javac} task, and that the \<CHECKERFRAMEWORK> environment variable is set.
+
+\begin{enumerate}
+\item
+Set the \code{cfJavac} property:
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  <property environment="env"/>
+  <property name="checkerframework" value="${env.CHECKERFRAMEWORK}" />
+  <condition property="cfJavac" value="javac.bat" else="javac">
+    <os family="windows" />
+  </condition>
+  <presetdef name="cf.javac">
+    <javac fork="yes" executable="${checkerframework}/checker/bin/${cfJavac}" >
+      <compilerarg value="-version"/>
+      <compilerarg value="-implicit:class"/>
+    </javac>
+  </presetdef>
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+\item \textbf{Duplicate} the compilation target, then \textbf{modify} it slightly as
+indicated in this example:
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  <target name="check-nullness"
+          description="Check for null pointer dereferences"
+          depends="clean,...">
+    <!-- use cf.javac instead of javac -->
+    <cf.javac ... >
+      <compilerarg line="-processor org.checkerframework.checker.nullness.NullnessChecker"/>
+      <!-- optional, to not check uses of library methods:
+        <compilerarg value="-AskipUses=^(java\.awt\.|javax\.swing\.)"/>
+      -->
+      <compilerarg line="-Xmaxerrs 10000"/>
+      ...
+    </cf.javac>
+  </target>
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+Fill in each ellipsis (\ldots) from the original compilation target.
+However, do not copy any \<fork=> setting from the original \code{<javac>}
+task invocation.
+
+If your original compilation target set the classpath or bootclasspath,
+then you will need to adjust it; see Section~\ref{javac-jdk8}.
+
+In the example, the target is named \code{check-nullness}, but you can
+name it whatever you like.
+\end{enumerate}
+
+\subsectionAndLabel{Explanation}{ant-task-explanation}
+
+This section explains each part of the Ant task.
+
+\begin{enumerate}
+\item Definition of \code{cf.javac}:
+
+The \code{fork} field of the \code{javac} task
+ensures that an external \code{javac} program is called.  Otherwise, Ant will run
+\code{javac} via a Java method call, and there is no guarantee that it will get
+correct version of \code{javac}.
+
+The \code{-version} compiler argument is just for debugging; you may omit
+it.
+
+The \code{-implicit:class} compiler argument causes annotation processing
+to be performed on implicitly compiled files.  (An implicitly compiled file
+is one that was not specified on the command line, but for which the source
+code is newer than the \code{.class} file.)  This is the default, but
+supplying the argument explicitly suppresses a compiler warning.
+
+%% -Awarns was removed above without removing it here.
+% The \code{-Awarns} compiler argument is optional, and causes the checker to
+% treat errors as warnings so that compilation does not fail even if
+% pluggable type-checking fails; see Section~\ref{checker-options}.
+
+\item The \code{check-nullness} target:
+
+The target assumes the existence of a \code{clean} target that removes all
+\code{.class} files.  That is necessary because Ant's \code{javac} target
+doesn't re-compile \code{.java} files for which a \code{.class} file
+already exists.
+
+The \code{-processor ...} compiler argument indicates which checker to
+run.  You can supply additional arguments to the checker as well.
+
+\end{enumerate}
+
+
+\sectionAndLabel{Buck}{buck}
+
+Buck is a build system maintained by Facebook.
+
+Buck has support for annotation processors, but that support is
+undocumented because the Buck maintainers may change the syntax in the
+future and they don't wish to ever change anything that is documented.
+
+You can learn more about Buck and annotation processors at these URLs:
+{\codesize\url{https://stackoverflow.com/questions/32915721/documentation-for-annotation-processors-buck}},
+{\codesize\url{https://github.com/facebook/buck/issues/85}}.
+
+In order to use Checker Framework with Buck on JDK 8, you first need
+to place the Error Prone JDK 9 compiler in Buck's bootclasspath
+(further explanation in Section~\ref{javac-jdk8}).  To do so,
+follow the instructions on this page:
+
+{\codesize\url{https://github.com/uber/okbuck/wiki/Using-Error-Prone-with-Buck-and-OkBuck#using-error-prone-javac-on-jdk-8}}
+
+You only need to follow the instructions to use Error Prone javac on
+that page, not the instructions to enable Error Prone.
+
+Once you have completed those steps, here is an example \<BUCK> build
+file showing how to enable Checker Framework:
+
+\begin{Verbatim}
+prebuilt_jar(
+    name = 'checker-framework',
+    binary_jar = 'checker-3.13.0.jar',
+    visibility = [ 'PUBLIC' ]
+)
+
+prebuilt_jar(
+    name = 'checker-qual',
+    binary_jar = 'checker-qual-3.13.0.jar',
+    visibility = [ 'PUBLIC' ]
+)
+
+java_library (
+    name = 'hello',
+    srcs = glob(['src/main/java/**/*.java']),
+    java_version = '1.8',
+    provided_deps = [ ':checker-framework', ':checker-qual' ],
+# To add annotation processing
+    annotation_processors = [ 'org.checkerframework.checker.units.UnitsChecker' ],
+    annotation_processor_deps = [ ':checker-framework', ':checker-qual' ],
+)
+\end{Verbatim}
+
+
+
+\sectionAndLabel{Command line, via Checker Framework javac wrapper}{javac-wrapper}
+\label{javac-installation}      % for backward compatibility, added 10/2/2019
+
+To perform pluggable type-checking from the command line, run the \<javac>
+command that ships with the Checker Framework.  This is called the
+``Checker Framework compiler''.  It is exactly the same as the OpenJDK
+\<javac> compiler, with one small difference:  it includes the Checker
+Framework jar file on its classpath.
+
+There are three ways to use the Checker Framework compiler from the command
+line.  You can use any
+one of them.  However, if you are using the Windows command shell, you must
+use the last one.
+% Is the last one required for Cygwin, as well as for the Windows command shell?
+Adjust the pathnames if you have installed the Checker Framework somewhere
+other than \<\${HOME}/checker-framework-3.13.0/>.
+
+
+\begin{itemize}
+  \item
+    Option 1:
+    Add directory
+    \code{.../checker-framework-3.13.0/checker/bin} to your path, \emph{before} any other
+    directory that contains a \<javac> executable.
+
+    If you are
+    using the bash shell, a way to do this is to add the following to your
+    \verb|~/.profile| (or alternately \verb|~/.bash_profile| or \verb|~/.bashrc|) file:
+\begin{Verbatim}
+  export CHECKERFRAMEWORK=${HOME}/checker-framework-3.13.0
+  export PATH=${CHECKERFRAMEWORK}/checker/bin:${PATH}
+\end{Verbatim}
+
+   After editing the file, log out and back in to ensure that the environment variable
+   setting takes effect.
+
+  \item
+    \begin{sloppypar}
+    Option 2:
+    Whenever this document tells you to run \code{javac},
+    instead run \code{\$CHECKERFRAMEWORK/checker/bin/javac}.
+    \end{sloppypar}
+
+    You can simplify this by introducing an alias \<javacheck>.  Then,
+    whenever this document tells you to run \code{javac}, instead run
+    \<javacheck>.  Here is the syntax for your
+    \verb|~/.bashrc|, \verb|~/.profile|, or \verb|~/.bash_profile|
+    file:
+% No Windows example because this doesn't work under Windows.
+\begin{Verbatim}
+  export CHECKERFRAMEWORK=${HOME}/checker-framework-3.13.0
+  alias javacheck='$CHECKERFRAMEWORK/checker/bin/javac'
+\end{Verbatim}
+
+   After editing the file, log out and back in to ensure that the environment variable
+   setting and alias take effect.
+
+   \item
+   Option 3:
+   Whenever this document tells you to run \code{javac}, instead
+   run \<checker.jar> via \<java> (not \<javac>) as in:
+
+\begin{Verbatim}
+  java -jar "$CHECKERFRAMEWORK/checker/dist/checker.jar" -cp "myclasspath" -processor nullness MyFile.java
+\end{Verbatim}
+
+    You can simplify the above command by introducing an alias
+    \<javacheck>.  Then, whenever this document tells you to run
+    \code{javac}, instead run \<javacheck>.  For example:
+
+\begin{Verbatim}
+  # Unix
+  export CHECKERFRAMEWORK=${HOME}/checker-framework-3.13.0
+  alias javacheck='java -jar "$CHECKERFRAMEWORK/checker/dist/checker.jar"'
+
+  # Windows
+  set CHECKERFRAMEWORK = C:\Program Files\checker-framework-3.13.0\
+  doskey javacheck=java -jar "%CHECKERFRAMEWORK%\checker\dist\checker.jar" $*
+\end{Verbatim}
+
+  (Explanation for advanced users:
+  More generally, anywhere that you would use \<javac.jar>, you can substitute
+  \<\$CHECKERFRAMEWORK/checker/dist/checker.jar>;
+  the result is to use the Checker
+  Framework compiler instead of the regular \<javac>.)
+
+\end{itemize}
+
+
+
+%% Does this work?  Text elsewhere in the manual imples that it does not.
+% \item
+% \begin{sloppypar}
+%   In order to use the updated compiler when you type \code{javac}, add the
+%   directory \<C:\ttbs{}Program Files\ttbs{}checker-framework\ttbs{}checkers\ttbs{}binary> to the
+%   beginning of your path variable.  Also set a \code{CHECKERFRAMEWORK} variable.
+% \end{sloppypar}
+%
+% % Instructions stolen from http://www.webreference.com/js/tips/020429.html
+%
+% To set an environment variable, you have two options:  make the change
+% temporarily or permanently.
+% \begin{itemize}
+% \item
+% To make the change \textbf{temporarily}, type at the command shell prompt:
+%
+% \begin{alltt}
+% path = \emph{newdir};%PATH%
+% \end{alltt}
+%
+% For example:
+%
+% \begin{Verbatim}
+% set CHECKERFRAMEWORK = C:\Program Files\checker-framework
+% path = %CHECKERFRAMEWORK%\checker\bin;%PATH%
+% \end{Verbatim}
+%
+% This is a temporary change that endures until the window is closed, and you
+% must re-do it every time you start a new command shell.
+%
+% \item
+% To make the change \textbf{permanently},
+% Right-click the \<My Computer> icon and
+% select \<Properties>. Select the \<Advanced> tab and click the
+% \<Environment Variables> button. You can set the variable as a ``System
+% Variable'' (visible to all users) or as a ``User Variable'' (visible to
+% just this user).  Both work; the instructions below show how to set as a
+% ``System Variable''.
+% In the \<System Variables> pane, select
+% \<Path> from the list and click \<Edit>. In the \<Edit System Variable>
+% dialog box, move the cursor to the beginning of the string in the
+% \<Variable Value> field and type the full directory name (not using the
+% \verb|%CHECKERFRAMEWORK%| environment variable) followed by a
+% semicolon (\<;>).
+%
+% % This is for the benefit of the Ant task.
+% Similarly, set the \code{CHECKERFRAMEWORK} variable.
+%
+% This is a permanent change that only needs to be done once ever.
+% \end{itemize}
+
+
+\sectionAndLabel{Command line, via JDK javac}{javac}
+
+This section explains how to use the Checker Framework with the OpenJDK or
+OracleJDK \<javac>, rather than with the \<javac> wrapper script described in
+Section~\ref{javac-wrapper}.
+
+This
+section assumes you have downloaded the Checker Framework release zip and set
+the environment variable \<CHECKERFRAMEWORK> to point to the unzipped directory.
+Alternately, you can get all of the jars mentioned in this section from Maven Central:
+
+\begin{itemize}
+\item \<javac.jar>: \url{https://search.maven.org/artifact/com.google.errorprone/javac/9%2B181-r4173-1/jar}
+\item \<checker-qual.jar>: \url{https://repo1.maven.org/maven2/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0.jar}
+\item \<checker-util.jar>: \url{https://repo1.maven.org/maven2/org/checkerframework/checker-util/3.13.0/checker-util-3.13.0.jar}
+\item \<checker.jar>: \url{https://repo1.maven.org/maven2/org/checkerframework/checker/3.13.0/checker-3.13.0-all.jar}
+\end{itemize}
+
+Different arguments to \<javac> are required for JDK 8
+(Section~\ref{javac-jdk8}) and for JDK 9+ (Section~\ref{javac-jdk11}).
+
+
+\subsectionAndLabel{JDK 8}{javac-jdk8}
+
+This section shows what arguments to pass to javac to run the Checker
+Framework, if you are using JDK 8.
+
+\begin{Verbatim}
+javac \
+-J-Xbootclasspath/p:$CHECKERFRAMEWORK/checker/dist/javac.jar \
+-cp $CHECKERFRAMEWORK/checker/dist/checker-qual.jar \
+-processorpath $CHECKERFRAMEWORK/checker/dist/checker.jar \
+-processor org.checkerframework.checker.nullness.NullnessChecker \
+-source 8 -target 8
+\end{Verbatim}
+
+Below is an explanation of each argument.
+\begin{enumerate}
+\item \<-J-Xbootclasspath/p:\$CHECKERFRAMEWORK/checker/dist/javac.jar>:
+even when running under JDK 8, a Java 9+ compiler (either the Error Prone
+compiler or the OpenJDK Java compiler (version 9 or later) must be on the JVM bootclasspath.
+The \<-J> indicates that this is a JVM argument rather than a compiler
+argument.
+
+The following exception is thrown if this argument is missing:
+\begin{Verbatim}
+error: SourceChecker.typeProcessingStart: unexpected Throwable (NoSuchMethodError);
+message: com.sun.tools.javac.code.Type.stripMetadata()Lcom/sun/tools/javac/code/Type;
+\end{Verbatim}
+
+If you are compiling your own checker, the following exception is thrown if this argument is missing:
+\begin{Verbatim}
+java.lang.NoSuchFieldError: ANNOTATION_PROCESSOR_MODULE_PATH
+\end{Verbatim}
+
+\item \<-cp \$CHECKERFRAMEWORK/checker/dist/checker-qual.jar>: \<checker-qual.jar>
+must be on the compilation classpath.
+
+\item \<-processorpath \$CHECKERFRAMEWORK/checker/dist/checker.jar>:
+\<checker.jar> must be on the processor path. Using this option means that the processor path, not
+the classpath, will be searched for annotation processors
+and for classes that they load.
+
+\item \<-processor org.checkerframework.checker.nullness.NullnessChecker>:
+Choose which checker to run by passing its fully qualified name as a processor.
+(Note, using this option means that javac will not search for annotation
+processors, but rather will run only those specified here.)
+
+\item \<-source 8 -target 8>: Because the build is using
+a Java 9 compiler, these options ensure that the
+source code is Java 8 and that Java 8 bytecode is created.
+
+\end{enumerate}
+
+
+\subsectionAndLabel{JDK 11}{javac-jdk11}
+
+This section shows what arguments to pass to javac to run the Checker Framework using JDK 11.  These
+instructions should work on JDK 9 or later, but we only test with JDK 11.
+
+
+\subsubsectionAndLabel{Non-modularized code}{javac-jdk11-non-modularized}
+
+To compile non-modularized code:
+
+\begin{Verbatim}
+javac \
+-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \
+-processorpath $CHECKERFRAMEWORK/checker/dist/checker.jar \
+-cp $CHECKERFRAMEWORK/checker/dist/checker-qual.jar \
+-processor org.checkerframework.checker.nullness.NullnessChecker
+\end{Verbatim}
+
+The arguments are explained above, except for
+\<-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED> which
+opens the \<jdk.compiler/com.sun.tools.java.comp> package.  This is
+required because the Checker Framework reflectively accesses private members of this package.
+
+If this option is missing, \<javac> may issue the following warning:
+\begin{Verbatim}
+WARNING: An illegal reflective access operation has occurred
+WARNING: Illegal reflective access by org.checkerframework.javacutil.Resolver
+  (file:$CHECKERFRAMEWORK/checker/dist/checker.jar) to method com.sun.tools.javac.comp.Resolve.findMethod(...)
+WARNING: Please consider reporting this to the maintainers of org.checkerframework.javacutil.Resolver
+WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
+WARNING: All illegal access operations will be denied in a future release
+\end{Verbatim}
+
+
+\subsubsectionAndLabel{Modularized code}{javac-jdk11-modularized}
+
+To compile a module, first add \<requires
+org.checkerframework.checker.qual;> to your \<module-info.java>.  The Checker
+Framework inserts inferred annotations into bytecode even if none appear in source code,
+so you must do this even if you write no annotations in your code.
+
+\begin{Verbatim}
+javac \
+  -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \
+  -processorpath $CHECKERFRAMEWORK/checker/dist/checker.jar \
+  --module-path $CHECKERFRAMEWORK/checker/dist/checker-qual.jar \
+  -processor org.checkerframework.checker.nullness.NullnessChecker
+\end{Verbatim}
+
+\<checker-qual.jar> must be on the module path rather than the class path, but
+do not put \<checker.jar> on the processor module path as it is not
+modularized.
+
+
+\sectionAndLabel{Eclipse}{eclipse}
+
+% Eclipse supports type annotations.
+% Eclipse does not directly support running the Checker Framework,
+% nor is Eclipse necessary for running the Checker Framework.
+
+You
+need to run the Checker Framework via a build tool (Ant, Gradle, Maven, etc.), rather
+than by supplying the \<-processor> command-line option to the \<ejc>
+compiler, which is also known as \<eclipsec>.
+The reason is that the Checker Framework is built upon \<javac>,
+and \<ejc> represents the Java program differently.  (If both \<javac> and \<ejc>
+implemented JSR 198~\cite{JSR198}, then it would be possible to build
+an annotation processor that works with both compilers.)
+
+
+There is no dedicated Eclipse plug-in for running the Checker Framework,
+but it's still easy to run the Checker Framework.  First, create a
+target/task in your build system to run the Checker Framework.  Then, run
+the target/task from Eclipse.  Section~\ref{eclipse-ant} gives details for
+Ant, but other build systems are similar.
+
+
+\subsectionAndLabel{Using an Ant task}{eclipse-ant}
+
+Add an Ant target as described in Section~\ref{ant-task}.  You can
+run the Ant target by executing the following steps
+(instructions copied from
+{\codesize\url{http://help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.platform.doc.user%2FgettingStarted%2Fqs-84_run_ant.htm}}):
+
+\begin{enumerate}
+
+\item
+  Select \code{build.xml} in one of the navigation views and choose
+  {\bf Run As $>$ Ant Build...} from its context menu.
+
+\item
+  A launch configuration dialog is opened on a launch configuration
+  for this Ant buildfile.
+
+\item
+  In the {\bf Targets} tab, select the new ant task (e.g., check-interning).
+
+\item
+  Click {\bf Run}.
+
+\item
+  The Ant buildfile is run, and the output is sent to the Console view.
+
+\end{enumerate}
+
+
+\subsectionAndLabel{Troubleshooting Eclipse}{eclipse-troubleshooting}
+
+Eclipse issues an ``Unhandled Token in @SuppressWarnings'' warning if you
+write a \<@SuppressWarnings> annotation containing a string that Eclipse does not
+know about.  Unfortunately, Eclipse
+\href{https://bugs.eclipse.org/bugs/show_bug.cgi?id=122475}{hard-codes}
+this list.
+
+To eliminate the warnings:
+disable all ``Unhandled Token in @SuppressWarnings'' warnings in Eclipse.
+Look under the menu headings
+``Java $\rightarrow$ Compiler $\rightarrow$ Errors/Warnings $\rightarrow$
+Annotations $\rightarrow$ Unhandled Token in '@SuppressWarnings',''
+and set it to ignore.
+
+
+\sectionAndLabel{Gradle}{gradle}
+
+To run a checker
+on a project that uses the \href{https://gradle.org/}{Gradle} build system,
+use the
+\ahreforurl{https://github.com/kelloggm/checkerframework-gradle-plugin}{Checker
+  Framework Gradle plugin}.  Its documentation explains how to use it.
+
+
+\sectionAndLabel{IntelliJ IDEA}{intellij}
+
+% The following method has been tested with IntelliJ IDEA 2019.1.1.
+
+This section tells you how to make IntelliJ IDEA automatically run a
+checker on every compile for Java projects and/or modules.
+
+If your project uses a build tool (Ant, Gradle, Maven, etc.), \emph{do not}
+use the instructions in this section.
+Follow the instructions for that build tool instead (they are in
+a different section of this chapter).  To compile your project, run the
+build tool (possibly from within IntelliJ IDEA, possibly not).
+
+
+\subsectionAndLabel{Running a checker on every IntelliJ compilation}{intellij-every-compilation}
+
+If your project does not use a build tool, use the following instructions
+to run a checker within IntelliJ IDEA on every compile:
+
+\begin{enumerate}
+
+\item Change the project SDK to 11, as explained at
+  \url{https://www.jetbrains.com/help/idea/sdk.html#change-project-sdk}.
+
+\item Make sure the language level for your project is 8 or higher, as explained at
+\url{https://www.jetbrains.com/help/idea/project-page.html}.
+
+\item Create and configure an annotation profile, following the instructions at \url{https://www.jetbrains.com/help/idea/annotation-processors-support.html}.
+When configuring the profile:
+\begin{enumerate}
+\item Add \<.../checker-framework/checker/dist/checker.jar> to the processor path.
+\item Add checkers to be run during compilation by writing the
+  fully-qualified name of the checker in the ``Processor FQ Name''
+  section.
+\end{enumerate}
+
+\item Add \<.../checker-framework/checker/dist/checker-qual.jar>, as a dependency to all
+modules you wish to type check. (They should all have been associated with
+the annotation profile above.)
+Instructions appear at
+\url{https://www.jetbrains.com/help/idea/creating-and-managing-projects.html}.
+
+\end{enumerate}
+
+Now, when you compile your code, the checker will be run.
+
+It is necessary to manually inform the IDE via a plugin if an annotation
+system adds any dependencies beyond those that normally exist in Java.
+For information about the extension points, see
+\url{https://youtrack.jetbrains.com/issue/IDEA-159286}.
+
+
+\subsectionAndLabel{Running a checker on every IntelliJ change or save}{intellij-every-save}
+
+To make IntelliJ compile on every change or save,
+follow the instructions at
+\url{https://www.jetbrains.com/help/idea/compiling-applications.html#auto-build}.
+
+You can also configure IntelliJ to automatically save (and thus
+automatically compile) your work periodically. Instructions appear at
+\url{https://www.jetbrains.com/help/idea/system-settings.html#sync}.
+
+\sectionAndLabel{javac diagnostics wrapper}{javac-diagnostics-wrapper}
+
+The \href{https://github.com/eisopux/javac-diagnostics-wrapper}{javac
+diagnostics wrapper} project can post-process the javac
+diagnostics output into other formats, such as
+the LSP (Language Server Protocol) JSON style.
+
+
+\sectionAndLabel{Lombok}{lombok}
+
+Project Lombok (\url{https://projectlombok.org/}) is a library that
+generates getter, setter, and builder methods, among other features.
+For example, if you declare
+a field:
+
+\begin{Verbatim}
+  @Getter @Setter
+  private @Regex String role;
+\end{Verbatim}
+
+\noindent
+then Lombok will generate getter and setter methods:
+
+\begin{Verbatim}
+  public @Regex String getRole() { return role; }
+  public void setRole(@Regex String role) { this.role = role; }
+\end{Verbatim}
+
+
+\subsectionAndLabel{Annotations on generated code}{lombok-copying-annotations}
+
+As illustrated in the example above, Lombok copies type annotations from fields
+to generated methods, when the user writes Lombok's \<@Getter>, \<@Setter>,
+and \<@Builder> annotations.  Lombok does so only for certain type
+annotations (including all annotations in the Checker Framework
+distribution); see variable \<BASE\_COPYABLE\_ANNOTATIONS> in file
+\href{https://github.com/rzwitserloot/lombok/blob/master/src/core/lombok/core/handlers/HandlerUtil.java}{\<HandlerUtil.java>}.
+
+To make Lombok copy other type annotations from fields to generated code,
+add those type annotations to the \<lombok.copyableAnnotations>
+configuration key in your \<lombok.config> file.  For example:
+
+\begin{Verbatim}
+  lombok.copyableAnnotations += my.checker.qual.MyTypeAnnotation
+\end{Verbatim}
+
+Directory \<docs/examples/lombok> contains an example Gradle project that
+augments the configuration key.
+
+% Alternatives:
+%  * It would be better if Lombok automatically copied all type annotations.
+%    Unfortunately, there is no way for Lombok to know whether an annotation
+%    is a type annotation, at the (early) point in the javac pipeline where
+%    Lombok runs.
+
+% TODO: write a simple tool that generates a lombok.config file for a particular
+% project, with all type annotations used in the project.
+
+
+\subsectionAndLabel{Type-checking code with Lombok annotations}{lombok-typechecking}
+
+If you run the Checker Framework and Lombok in the same \<javac>
+invocation, the Checker Framework cannot type-check a class that contains
+Lombok annotations.  The way that Lombok changes the class prevents the
+Checker Framework from seeing any of the class.  (The Checker Framework
+works fine on classes that do not contain Lombok annotations, including if
+they call Lombok-generated code.)
+
+Therefore, you must run the Checker Framework in a postpass after the
+\<javac> that runs Lombok has completed.  Use the
+\href{https://projectlombok.org/features/delombok}{Delombok} tool
+(distributed with Lombok) to generate Java source code, then run the
+Checker Framework on that.  The
+\href{https://github.com/kelloggm/checkerframework-gradle-plugin}{Checker
+  Framework Gradle plugin} does this for you automatically.
+
+
+\sectionAndLabel{Maven}{maven}
+
+If you use the \href{http://maven.apache.org/}{Maven} tool,
+then you can enable Checker Framework checkers by following the
+instructions below.
+
+See the directory \code{docs/examples/MavenExample/} for examples of the use of
+Maven build files.
+This example can be used to verify that
+Maven is correctly downloading the Checker Framework from the
+\href{https://search.maven.org/search?q=org.checkerframework}{Maven
+  Central Repository} and executing it.
+
+Please note that the \<-AoutputArgsToFile> command-line option
+(see Section~\ref{creating-debugging-options-output-args}) and shorthands for built-in checkers
+(see Section~\ref{shorthand-for-checkers}) are not available when
+following these instructions.  Both these features are available only when a checker is
+launched via \<checker.jar> such as when \code{\$CHECKERFRAMEWORK/checker/bin/javac}
+is run.  The instructions in this section
+bypass \<checker.jar> and cause the compiler to run a
+checker as an annotation processor directly.
+
+\begin{enumerate}
+
+\item Declare a dependency on the Checker Framework artifacts, either from
+  Maven Central or from a local directory.  Find the
+  existing \code{<dependencies>} section and add the following new
+  \code{<dependency>} items:
+
+\begin{enumerate}
+\item
+  To obtain artifacts from Maven Central:
+
+\begin{alltt}
+  <dependencies>
+    ... existing <dependency> items ...
+
+    <!-- Annotations from the Checker Framework: nullness, interning, locking, ... -->
+    <dependency>
+      <groupId>org.checkerframework</groupId>
+      <artifactId>checker-qual</artifactId>
+      <version>\ReleaseVersion{}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.errorprone</groupId>
+      <artifactId>javac</artifactId>
+      <version>9+181-r4173-1</version>
+    </dependency>
+  </dependencies>
+\end{alltt}
+
+Java 11 and later do not need the \<com.google.errorprone.javac>
+dependency.  If you will never use Java 8, you can omit it.
+
+Periodically update to the most recent version, to obtain the
+latest bug fixes and new features:
+\begin{Verbatim}
+  mvn versions:use-latest-versions -Dincludes="org.checkerframework:*"
+\end{Verbatim}
+
+The need for the \<com.google.errorprone:javac> artifact when running under
+JDK 8 is explained in Section~\ref{javac-jdk8}.
+
+\end{enumerate}
+
+\item If using JDK 8, use a Maven property to hold the location of the
+  Error Prone \<javac.jar>.  They were declared as Maven dependencies above.
+To set the value of these properties automatically, you will use the Maven Dependency plugin.
+
+First, create the property in the \code{properties} section of the POM:
+
+\begin{alltt}
+<properties>
+  <!-- These properties will be set by the Maven Dependency plugin -->
+  <errorProneJavac>$\{com.google.errorprone:javac:jar\}</errorProneJavac>
+</properties>
+\end{alltt}
+
+Change the reference to the \code{maven-dependency-plugin} within the \code{<plugins>}
+section, or add it if it is not present.
+
+\begin{alltt}
+  <plugin>
+    <!-- This plugin will set properties values using dependency information -->
+    <groupId>org.apache.maven.plugins</groupId>
+    <artifactId>maven-dependency-plugin</artifactId>
+    <executions>
+      <execution>
+        <goals>
+          <goal>properties</goal>
+        </goals>
+      </execution>
+    </executions>
+  </plugin>
+\end{alltt}
+
+\item Direct the Maven compiler plugin to use the desired checkers by
+  creating three new profiles as shown below (the example uses the Nullness
+  Checker).  If your POM file does not already use the
+  \code{maven-compiler-plugin} plugin, you are done.  If your POM file
+  already uses the plugin, copy edit the first profile to incorporate the
+  existing configuration.
+
+%% TODO: Is this comment still accurate?
+%              <!-- Without showWarnings and verbose, maven-compiler-plugin may not show output. -->
+%              <showWarnings>true</showWarnings>
+%              <verbose>true</verbose>
+
+\begin{mysmall}
+\begin{alltt}
+  <profiles>
+    <profile>
+      <id>checkerframework</id>
+      <activation>
+        <jdk>[1.8,13)</jdk>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <version>3.8.1</version>
+            <configuration>
+              <fork>true</fork> <!-- Must fork or else JVM arguments are ignored. -->
+              <compilerArguments>
+                <Xmaxerrs>10000</Xmaxerrs>
+                <Xmaxwarns>10000</Xmaxwarns>
+              </compilerArguments>
+              <annotationProcessorPaths>
+                <path>
+                  <groupId>org.checkerframework</groupId>
+                  <artifactId>checker</artifactId>
+                  <version>\ReleaseVersion{}</version>
+                </path>
+              </annotationProcessorPaths>
+              <annotationProcessors>
+                <!-- Add all the checkers you want to enable here -->
+                <annotationProcessor>org.checkerframework.checker.nullness.NullnessChecker</annotationProcessor>
+              </annotationProcessors>
+              <compilerArgs>
+                <!-- <arg>-Awarns</arg> --> <!-- -Awarns turns type-checking errors into warnings. -->
+              </compilerArgs>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+      <dependencies>
+        <dependency>
+          <groupId>org.checkerframework</groupId>
+          <artifactId>checker</artifactId>
+          <version>$\{checkerFrameworkVersion\}</version>
+        </dependency>
+      </dependencies>
+    </profile>
+
+    <profile>
+      <id>checkerframework-jdk8</id>
+      <activation>
+        <jdk>1.8</jdk>
+      </activation>
+      <!-- using github.com/google/error-prone-javac is required when running on JDK 8 -->
+      <properties>
+        <javac.version>9+181-r4173-1</javac.version>
+      </properties>
+      <dependencies>
+        <dependency>
+          <groupId>com.google.errorprone</groupId>
+          <artifactId>javac</artifactId>
+          <version>9+181-r4173-1</version>
+        </dependency>
+      </dependencies>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <configuration>
+              <fork>true</fork>
+              <compilerArgs combine.children="append">
+                <arg>-Xbootclasspath/p:$\{annotatedJdk\}</arg>
+                <arg>-J-Xbootclasspath/p:$\{settings.localRepository\}/com/google/errorprone/javac/$\{javac.version\}/javac-$\{javac.version\}.jar</arg>
+              </compilerArgs>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+
+    <profile>
+      <id>checkerframework-jdk11</id>
+      <activation>
+        <jdk>11</jdk>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <configuration>
+              <fork>true</fork>
+              <compilerArgs combine.children="append">
+                <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
+              </compilerArgs>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+      <properties>
+        <!-- Needed for animal-sniffer-maven-plugin version 1.19 (version 1.20 is fixed). -->
+        <animal.sniffer.skip>true</animal.sniffer.skip>
+      </properties>
+    </profile>
+  </profiles>
+\end{alltt}
+\end{mysmall}
+
+Now, building with Maven will run the checkers during every compilation
+that uses JDK 8 or JDK 11.
+
+If you wish to run checkers while compiling your source code but not your
+tests, wrap the \code{<configuration>...</configuration>} within
+
+\begin{Verbatim}
+<executions>
+  <execution>
+    <id>default-compile</id>
+    ...
+  </execution>
+</executions>
+\end{Verbatim}
+
+To compile without using the Checker Framework, pass
+\<-P '!checkerframework-java11'> (or \<-P '!checkerframework-java8'>)
+on the Maven command line.
+
+% TODO: Figure out why, and then remove this paragraph.
+Warning:  adding
+
+\begin{mysmall}
+\begin{alltt}
+<compilerArgs>
+  ...
+  <arg>-verbose</arg>
+\end{alltt}
+\end{mysmall}
+
+\noindent
+may suppress warnings from the stub parser.
+
+\end{enumerate}
+
+
+\subsectionAndLabel{Maven, with a locally-built version of the Checker Framework}{maven-locally-built}
+
+To use a locally-built version of the Checker Framework, first run:
+
+\begin{alltt}
+./gradlew publishToMavenLocal
+\end{alltt}
+
+\noindent
+Then use the Maven instructions, but modify the version number for the
+Checker Framework artifacts.  Instead of \<\ReleaseVersion{}>, use the
+version number that is output when you run \<./gradlew version>.
+
+
+\sectionAndLabel{NetBeans}{netbeans}
+
+There are two approaches to running a checker in NetBeans:  via modifying the
+project properties, or via a custom ant target.
+
+Note: The ``compile and save'' action in NetBeans 8.1 automatically
+runs the Checker Framework, but this functionality has not yet
+been incorporated into NetBeans 8.2. Additionally, JDK annotations
+are currently unavailable on NetBeans 8.1 and 8.2.
+
+% NetBeans 8.2 switched to a Java 9 javac, which breaks the Checker Framework as
+% annotation processor.
+% There is no way to set a bootclasspath for annotation processors, so we can't
+% add the JDK annotations.
+% Work on a NetBeans plug-in at
+% https://github.com/typetools/checker-framework/pull/1592
+% could not solve the bootclasspath issue, so we decided to not integrate it.
+
+
+\subsectionAndLabel{Adding a checker via the Project Properties window}{netbeans-project-properties}
+
+\begin{enumerate}
+\item
+  Add the Checker Framework libraries to your project's
+  library. First, right click on the project in the Projects panel,
+  and select ``Properties'' in the drop-down menu. Then, in the
+  ``Project Properties'' window, navigate to the ``Libraries'' tab.
+
+\item
+  Add \<checker-qual.jar> to the compile-time libraries. To do so,
+  select the ``Compile'' tab, click the ``Add JAR/Folder'' button on
+  the right and browse to
+  add \<\$CHECKERFRAMEWORK/checker/dist/checker-qual.jar>.
+
+\item
+  Add \<checker.jar> to the processor-path libraries. To do so, select
+  the ``Processor'' tab, click the ``Add JAR/Folder'' button on the
+  right and browse to
+  add \<\$CHECKERFRAMEWORK/checker/dist/checker.jar>.
+
+\item
+  Enable annotation processor underlining in the editor. Go to
+  ``Build>Compiling'' and check the box ``Enable Annotation
+  Processing,'' and under that, ``Enable Annotation Processing in
+  Editor.''
+
+\item
+  Add the checker to run, by clicking ``Add'' next to the box labeled
+  ``Annotation Processors'' and enter the fully qualified name of the
+  checker (for
+  example, \<org.checkerframework.checker.nullness.NullnessChecker>)
+  and click ``OK'' to add.
+\end{enumerate}
+
+The selected checker should be run on the project either on a save (if
+Compile on Save is enabled), or when the project is built, and
+annotation processor output will appear in the editor.
+
+
+\subsectionAndLabel{Adding a checker via an ant target}{netbeans-ant-target}
+
+\begin{enumerate}
+\item
+Set the \code{cfJavac} property:
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  <property environment="env"/>
+  <property name="checkerframework" value="${env.CHECKERFRAMEWORK}" />
+  <condition property="cfJavac" value="javac.bat" else="javac">
+    <os family="windows" />
+  </condition>
+  <presetdef name="cf.javac">
+    <javac fork="yes" executable="${checkerframework}/checker/bin/${cfJavac}" >
+      <compilerarg value="-version"/>
+      <compilerarg value="-implicit:class"/>
+    </javac>
+  </presetdef>
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+\item
+Override the \code{-init-macrodef-javac-with-processors} target to
+use \code{cf.javac} instead of \code{javac} and to run the checker.
+In this example, a nullness checker is run:
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  <target depends="-init-ap-cmdline-properties" if="ap.supported.internal"
+        name="-init-macrodef-javac-with-processors">
+    <echo message = "${checkerframework}"/>
+    <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
+        <attribute default="${src.dir}" name="srcdir"/>
+        <attribute default="${build.classes.dir}" name="destdir"/>
+        <attribute default="${javac.classpath}" name="classpath"/>
+        <attribute default="${javac.processorpath}" name="processorpath"/>
+        <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
+        <attribute default="${includes}" name="includes"/>
+        <attribute default="${excludes}" name="excludes"/>
+        <attribute default="${javac.debug}" name="debug"/>
+        <attribute default="${empty.dir}" name="sourcepath"/>
+        <attribute default="${empty.dir}" name="gensrcdir"/>
+        <element name="customize" optional="true"/>
+        <sequential>
+            <property location="${build.dir}/empty" name="empty.dir"/>
+            <mkdir dir="${empty.dir}"/>
+            <mkdir dir="@{apgeneratedsrcdir}"/>
+            <cf.javac debug="@{debug}" deprecation="${javac.deprecation}"
+                    destdir="@{destdir}" encoding="${source.encoding}"
+                    excludes="@{excludes}" fork="${javac.fork}"
+                    includeantruntime="false" includes="@{includes}"
+                    source="${javac.source}" sourcepath="@{sourcepath}"
+                    srcdir="@{srcdir}" target="${javac.target}"
+                    tempdir="${java.io.tmpdir}">
+                <src>
+                    <dirset dir="@{gensrcdir}" erroronmissingdir="false">
+                        <include name="*"/>
+                    </dirset>
+                </src>
+                <classpath>
+                    <path path="@{classpath}"/>
+                </classpath>
+                <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
+                <compilerarg line="${javac.profile.cmd.line.arg}"/>
+                <compilerarg line="${javac.compilerargs}"/>
+                <compilerarg value="-processorpath"/>
+                <compilerarg path="@{processorpath}:${empty.dir}"/>
+                <compilerarg line="${ap.processors.internal}"/>
+                <compilerarg line="${annotation.processing.processor.options}"/>
+                <compilerarg value="-s"/>
+                <compilerarg path="@{apgeneratedsrcdir}"/>
+                <compilerarg line="${ap.proc.none.internal}"/>
+                <compilerarg line="-processor org.checkerframework.checker.nullness.NullnessChecker"/>
+                <compilerarg line="-Xmaxerrs 10000"/>
+                <compilerarg line="-Xmaxwarns 10000"/>
+                <customize/>
+            </cf.javac>
+        </sequential>
+    </macrodef>
+  </target>
+  <target name="-post-jar">
+  </target>
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+When Build and Clean Project is used, the output of the checker will
+now appear in the build console. However, annotation processor output
+will not appear in the editor.
+
+\end{enumerate}
+
+
+\sectionAndLabel{sbt}{sbt}
+
+\ahref{https://www.scala-sbt.org/}{sbt} is a build tool for
+Scala, Java, and more.
+
+Adjust the \<-processor> command-line argument for the processor(s)
+you wish to run (see Section~\ref{running}).
+
+
+\subsectionAndLabel{JDK 8}{sbt-jdk8}
+
+\begin{Verbatim}
+javacOptions ++= Seq(
+    "-J-Xbootclasspath/p:$CHECKERFRAMEWORK/checker/dist/javac.jar",
+    "-cp $CHECKERFRAMEWORK/checker/dist/checker-qual.jar",
+    "-processorpath $CHECKERFRAMEWORK/checker/dist/checker.jar",
+    "-processor org.checkerframework.checker.nullness.NullnessChecker",
+    "-source 8", "-target 8"
+  )
+\end{Verbatim}
+
+
+\subsectionAndLabel{JDK 11, for non-modularized code}{sbt-jdk11-nonmodularized-code}
+
+\begin{Verbatim}
+javacOptions ++= Seq(
+    "-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
+    "-processorpath $CHECKERFRAMEWORK/checker/dist/checker.jar",
+    "-cp $CHECKERFRAMEWORK/checker/dist/checker-qual.jar",
+    "-processor org.checkerframework.checker.nullness.NullnessChecker"
+  )
+\end{Verbatim}
+
+
+\subsectionAndLabel{For modularized code}{sbt-modularized-code}
+
+\begin{Verbatim}
+For compiling modularized code:
+javacOptions ++= Seq(
+    "-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
+    "-processorpath $CHECKERFRAMEWORK/checker/dist/checker.jar",
+    "--module-path $CHECKERFRAMEWORK/checker/dist/checker-qual.jar",
+    "-processor org.checkerframework.checker.nullness.NullnessChecker"
+  )
+\end{Verbatim}
+
+
+\sectionAndLabel{tIDE}{tide}
+
+\begin{sloppypar}
+tIDE, an open-source Java IDE, supports the Checker Framework.
+You can download it from \myurl{https://sourceforge.net/projects/tide/}.
+\end{sloppypar}
+
+
+\sectionAndLabel{Type inference tools}{type-inference-varieties}
+
+A type inference tool infers type annotations for a program's method
+signatures and fields, so that the programmer does not need to manually
+annotate the program's source code.  Section~\ref{type-inference-tools}
+lists type inference tools.
+
+
+
+% LocalWords:  jsr plugin Warski xml buildfile tIDE java Awarns pom lifecycle
+% LocalWords:  IntelliJ Maia newdir classpath Unconfuse nullness Gradle cp
+% LocalWords:  compilerArgs Xbootclasspath mvn
+% LocalWords:  plugins proc procOnly DirectoryScanner setIncludes groupId
+% LocalWords:  setExcludes checkerFrameworkVersion javacParams javaParams
+% LocalWords:  artifactId quals failOnError ejc CHECKERFRAMEWORK env jdk
+% LocalWords:  javacheck checkerframework MavenExample org arg typecheck
+% LocalWords:  AoutputArgsToFile qual jdk8 annotatedJdk Unhandled dex SDK
+% LocalWords:  annotationProcessors annotationProcessor JavaCompile Ctrl
+% LocalWords:  targetJavaVersion GradleExamples gradle JavaVersion lombok
+% LocalWords:  systemPath artifactID MacOS eclipsec getter getRole setRole
+% LocalWords:  config copyableAnnotations COPYABLE localRepository init FQ
+% LocalWords:  compilerArguments Xmaxerrs Xmaxwarns netbeans macrodef LSP
+% LocalWords:  annotationProcessorPaths checkTypes OracleJDK java8 java11
+% LocalWords:  bootclasspath processorpath intellij typechecking postpass
+% LocalWords:  Delombok r4173 pathnames HandlerUtil errorProneJavac
+% LocalWords:  uncomment deployArtifactsToLocalRepo
diff --git a/docs/manual/faq.tex b/docs/manual/faq.tex
new file mode 100644
index 0000000..2205bf6
--- /dev/null
+++ b/docs/manual/faq.tex
@@ -0,0 +1,1995 @@
+\htmlhr
+\chapterAndLabel{Frequently Asked Questions (FAQs)}{faq}
+
+These are some common questions about the Checker Framework and about
+pluggable type-checking in general.  Feel free to suggest improvements to
+the answers, or other questions to include here.
+
+% Not supported by Hevea, so don't bother; instead do by hand:
+% \minitoc
+
+%BEGIN LATEX
+~
+%END LATEX
+
+%BEGIN LATEX
+\newcommand{\faqtocpara}[1]{\paragraph{#1} ~}
+%END LATEX
+%HEVEA \newcommand{\faqtocpara}[1]{\textbf{#1}}
+
+
+\noindent
+\textbf{Contents:}
+
+\faqtocpara{\ref{faq-motivation-section}: Motivation for pluggable type-checking}
+\\ \ref{faq-never-make-type-errors}: I don't make type errors, so would pluggable type-checking help me?
+\\ \ref{faq-typequals-vs-subtypes}: Should I use pluggable types (type qualifiers), or should I used Java subtypes?
+
+\faqtocpara{\ref{faq-getting-started-section}: Getting started}
+\\ \ref{faq-annotate-existing-program}: How do I get started annotating an existing program?
+\\ \ref{faq-first-checker}: Which checker should I start with?
+\\ \ref{faq-checker-framework-dev}: How can I join the checker-framework-dev mailing list?
+
+\faqtocpara{\ref{faq-usability-section}: Usability of pluggable type-checking}
+\\ \ref{faq-ease-of-use}: Are type annotations easy to read and write?
+\\ \ref{faq-code-clutter}: Will my code become cluttered with type annotations?
+\\ \ref{faq-slowdown}: Will using the Checker Framework slow down my program?  Will it slow down the compiler?
+\\ \ref{faq-shorten-command-line}: How do I shorten the command line when invoking a checker?
+\\ \ref{faq-pre-conditions}: Method pre-condition contracts, including formal parameter annotations, make no sense for public methods.
+
+\faqtocpara{\ref{faq-warnings-section}: How to handle warnings}
+\\ \ref{faq-handling-warnings}: What should I do if a checker issues a warning about my code?
+\\ \ref{faq-interpreting-warnings}: What does a certain Checker Framework warning message mean?
+\\ \ref{faq-warnings-square-brackets}: What do square brackets mean in a Checker Framework warning message?
+\\ \ref{faq-no-absolute-guarantee}: Can a pluggable type-checker guarantee that my code is correct?
+\\ \ref{faq-concurrency}: What guarantee does the Checker Framework give for concurrent code?
+\\ \ref{faq-awarns}: How do I make compilation succeed even if a checker issues errors?
+\\ \ref{faq-100-warnings}: Why does the checker always say there are 100 errors or warnings?
+\\ \ref{faq-type-i-did-not-write}: Why does the Checker Framework report an error regarding a type I have not written in my program?
+\\ \ref{faq-same-code-different-behavior}: Why does the Checker Framework accept code on one line but reject it on the next?
+\\ \ref{faq-run-time-checking}: How can I do run-time monitoring of properties that were not statically checked?
+
+\faqtocpara{\ref{faq-false-positives-section}: False positive warnings}
+\\ \ref{faq-false-positive}: What is a ``false positive'' warning?
+\\ \ref{faq-false-positive-extend-checker-framework}: How can I improve the Checker Framework to eliminate a false positive warning?
+\\ \ref{faq-infer-fields}: Why doesn't the Checker Framework infer types for fields and method return types?
+\\ \ref{faq-relationships-between-variables}: Why doesn't the Checker Framework track relationships between variables?
+\\ \ref{faq-path-sensitive}: Why isn't the Checker Framework path-sensitive?
+
+\faqtocpara{\ref{faq-syntax-section}: Syntax of type annotations}
+\\ \ref{faq-receiver}: What is a ``receiver''?
+\\ \ref{faq-annotation-after-type}: What is the meaning of an annotation after a type, such as \<@NonNull Object @Nullable>?
+\\ \ref{faq-array-syntax-meaning}: What is the meaning of array annotations such as \<@NonNull Object @Nullable []>?
+\\ \ref{faq-varargs-syntax-meaning}: What is the meaning of varargs annotations such as \<@English String @NonEmpty~...>?
+\\ \ref{faq-type-qualifier-on-class-declaration}: What is the meaning of a type qualifier at a class declaration?
+\\ \ref{faq-type-qualifier-on-bounds}: How are type qualifiers written on upper and lower bounds?
+\\ \ref{faq-no-annotation-on-types-and-declarations}: Why shouldn't a qualifier apply to both types and declarations?
+\\ \ref{faq-annotate-fully-qualified-name}: How do I annotate a
+fully-qualified type name?
+\\ \ref{faq-type-vs-declaration-annotations}: What is the difference between type annotations and declaration annotations?
+\\ \ref{faq-declaration-annotations-moved}: How does the Checker Framework handle obsolete declaration annotations?
+
+\faqtocpara{\ref{faq-semantics-section}: Semantics of type annotations}
+\\ \ref{faq-typestate}: How can I handle typestate, or phases of my program with different data properties?
+\\ \ref{faq-implicit-bounds}: Why are explicit and implicit bounds defaulted differently?
+\\ \ref{faq-writing-generics}: How should I annotate code that uses generics?
+\\ \ref{faq-runtime-retention}: Why are type annotations declared with \<@Retention(RetentionPolicy.RUNTIME)>?
+
+\faqtocpara{\ref{faq-create-a-checker-section}: Creating a new checker}
+\\ \ref{faq-create-a-checker}: How do I create a new checker?
+\\ \ref{faq-type-properties}: What properties can and cannot be handled by type-checking?
+\\ \ref{faq-declarative-syntax-for-type-rules}: Why is there no declarative syntax for writing type rules?
+
+\faqtocpara{\ref{faq-tool-section}: Tool questions}
+\\ \ref{faq-pluggable-type-checking}: How does pluggable type-checking work?
+\\ \ref{faq-classpath-to-use-annotated-library}: What classpath is needed to use an annotated library?
+\\ \ref{faq-classfile-annotations}: Why do \<.class> files contain more annotations than the source code?
+\\ \ref{faq-checked-exceptions}: Is there a type-checker for managing checked and unchecked exceptions?
+\\ \ref{faq-cf-is-slow}: The Checker Framework runs too slowly
+\\ \ref{faq-version-number}: What does the Checker Framework version number mean?
+
+\faqtocpara{\ref{faq-other-tools-section}: Relationship to other tools}
+\\ \ref{faq-type-checking-vs-bug-detectors}: Why not just use a bug detector (like SpotBugs or Error Prone)?
+\\ \ref{faq-eclipse}: How does the Checker Framework compare with Eclipse's Null Analysis?
+\\ \ref{faq-nullaway}: How does the Checker Framework compare with NullAway?
+\\ \ref{faq-optional}: How does the Checker Framework compare with the JDK's \<Optional> type?
+\\ \ref{faq-jml}: How does pluggable type-checking compare with JML?
+\\ \ref{faq-checker-framework-part-of-java}: Is the Checker Framework an official part of Java?
+\\ \ref{faq-jsr-305}: What is the relationship between the Checker Framework and JSR 305?
+\\ \ref{faq-jsr-308}: What is the relationship between the Checker Framework and JSR 308?
+
+
+\sectionAndLabel{Motivation for pluggable type-checking}{faq-motivation-section}
+
+\subsectionAndLabel{I don't make type errors, so would pluggable type-checking help me?}{faq-never-make-type-errors}
+
+Occasionally, a developer says that he makes no errors that type-checking
+could catch, or that any such errors are unimportant because they have low
+impact and are easy to fix.  When I investigate the claim, I invariably
+find that the developer is mistaken.
+
+Very frequently, the developer has underestimated what type-checking can
+discover.  Not every type error leads to an exception being thrown; and
+even if an exception is thrown, it may not seem related to classical types.
+Remember that a type system can discover
+null pointer dereferences,
+incorrect side effects,
+security errors such as information leakage or SQL injection,
+partially-initialized data,
+wrong units of measurement,
+and many other errors.
+Every programmer makes errors sometimes and works with other people
+who do.
+Even where type-checking does not discover a
+problem directly, it can indicate code with bad smells, thus revealing
+problems, improving documentation, and making future maintenance easier.
+
+There are other ways to discover errors, including extensive testing and
+debugging.  You should continue to use these.
+But type-checking is a good complement to these.  Type-checking is more
+effective for some problems, and less effective for other problems.  It can
+reduce (but not eliminate) the time and effort that you spend on other
+approaches.  There are many important errors that type-checking and other
+automated approaches cannot find; pluggable type-checking gives you more
+time to focus on those.
+
+
+\subsectionAndLabel{Should I use pluggable types (type qualifiers), or should I used Java subtypes?}{faq-typequals-vs-subtypes}
+
+% Old labels, for backward compatibility.  Don't use them any longer.
+\label{when-to-use-type-qualifiers}
+\label{faq-qualifiers-vs-subclasses}
+
+In brief, use subtypes when you can, and use type qualifiers when you cannot
+use subtypes.
+
+For some programming tasks, you can use either a Java subtype (interfaces
+or subclasses) or a type
+qualifier.  As an example, suppose that your code currently uses \code{String} to
+represent an address.  You could use Java subclasses by creating a new
+\code{Address} class and refactor your code to use it, or you could use
+type qualifiers by creating an \code{@Address} annotation and applying it
+to some uses of \code{String} in your code.  As another example, suppose
+that your code currently uses \code{MyClass} in two different ways that
+should not interact with one another.  You could use Java subclasses by
+changing MyClass into an interface or abstract class, defining two
+subclasses, and ensuring that neither subclass ever refers to the other
+subclass nor to the parent class.
+
+If  Java subclasses solve your problem, then that is probably better.
+We do not encourage you to use type qualifiers as a poor substitute for
+classes.  An advantage of using classes is that the Java type-checker
+runs every time you compile your code;
+by contrast, it is possible to forget to run the pluggable
+type-checker.  However, sometimes type qualifiers are a
+better choice; here are some reasons:
+
+\begin{description}
+
+\item[Backward compatibility]
+Using a new class may make your code incompatible with existing libraries or
+clients.  Brian Goetz expands on this issue in an article on the
+pseudo-typedef antipattern~\cite{Goetz2006:typedef}.  Even if compatibility
+is not a concern, a code change may introduce bugs, whereas adding
+annotations does not change the run-time behavior.  It is possible to add
+annotations to existing code, including code you do not maintain or cannot
+change.  For code that strictly cannot be changed, you
+can write library annotations (see Chapter~\ref{annotating-libraries}).
+
+\item[Broader applicability]
+Type annotations can be applied to primitives and to final classes such as
+\code{String}, which cannot be subclassed.
+
+\item[Richer semantics and new supertypes]
+Type qualifiers permit you to remove operations, with a compile-time
+guarantee.  More
+generally, type qualifiers permit creating a new supertype, not just a
+subtype, of an existing Java type.
+
+\item[More precise type-checking]
+The Checker Framework is able to verify the correctness of code that the
+Java type-checker would reject.  Here are a few examples.
+\begin{itemize}
+\item
+  It uses a dataflow analysis to determine a more precise type for
+  variables after conditional tests or assignments.
+\item
+  It treats certain Java constructs more precisely, such as
+  reflection (see Chapter~\ref{reflection-resolution}).
+\item
+  It includes special-case logic for type-checking specific methods, such
+  as the Nullness Checker's treatment of \code{Map.get}.
+\end{itemize}
+
+
+\item[Efficiency]
+  Type qualifiers have no run-time representation.  Therefore, there is no
+  space overhead for separate classes or for wrapper classes for
+  primitives.  There is no run-time overhead for due to extra dereferences
+  or dynamic dispatch for methods that could otherwise be statically
+  dispatched.
+
+\item[Less code clutter]
+  The programmer does not have to convert primitive types to wrappers,
+  which would make the code both uglier and slower.  Thanks to defaults and
+  type refinement (Section~\ref{defaults}),
+  you may be able to write and think in terms of the
+  original Java type, rather than having to explicitly write one of the
+  subtypes in all locations.
+
+\end{description}
+
+
+For more details, see Section~\ref{faq-typequals-vs-subtypes}.
+
+
+
+\sectionAndLabel{Getting started}{faq-getting-started-section}
+
+\subsectionAndLabel{How do I get started annotating an existing program?}{faq-annotate-existing-program}
+
+See Section~\ref{get-started-with-legacy-code}.
+
+
+\subsectionAndLabel{Which checker should I start with?}{faq-first-checker}
+
+You should start with a property that matters to you.  Think about what
+aspects of your code cause the most errors, or cost the most time during
+maintenance, or are the most common to be incorrectly-documented.  Focusing
+on what you care about will give you the best benefits.
+
+When you first start out with the Checker Framework, it's usually best to
+get experience with an existing type-checker before you write your own new
+checker.
+
+Many users are tempted to start with the
+\ahrefloc{nullness-checker}{Nullness Checker} (see
+\chapterpageref{nullness-checker}), since null pointer errors are common
+and familiar.  The Nullness Checker works very well, but be warned of three
+facts that make the absence of null pointer exceptions challenging to
+verify.
+
+\begin{enumerate}
+\item
+  Dereferences happen throughout your codebase, so there are a lot of
+  potential problems.  By contrast, fewer lines of code are related to
+  locking, regular expressions, etc., so those properties are easier to
+  check.
+\item
+  Programmers use \<null> for many different purposes.  More seriously,
+  programmers write run-time tests against \<null>, and those are difficult
+  for any static analysis to capture.
+\item
+  The Nullness Checker interacts with initialization and map keys.
+\end{enumerate}
+
+If null pointer exceptions are most important to you, then by all means use
+the Nullness Checker.  But if you just want to try \emph{some}
+type-checker, there are others that are easier to use.
+
+We do not recommend indiscriminately running all the checkers on your code.
+The reason is that each one has a cost --- not just at compile time, but
+also in terms of code clutter and human time to maintain the annotations.
+If the property is important to you, is difficult for people to reason
+about, or has caused problems in the past, then you should run that
+checker.  For other properties, the benefits may not repay the effort to
+use it.  You will be the best judge of this for your own code, of course.
+
+%You might want to avoid some type-checkers when you are first starting out.
+Some of the third-party checkers (see
+\chapterpageref{third-party-checkers})
+have known bugs that limit their
+usability.  (Report the ones that affect you, so the developers
+will prioritize fixing them.)
+
+
+\subsectionAndLabel{How can I join the checker-framework-dev mailing list?}{faq-checker-framework-dev}
+
+The \code{checker-framework-dev@googlegroups.com} mailing list is for
+Checker Framework developers.  Anyone is welcome to
+\href{https://groups.google.com/forum/#!forum/checker-framework-dev}{join
+  \code{checker-framework-dev}}, after they have had several pull requests
+accepted.
+
+Anyone is welcome to send mail to the
+\code{checker-framework-dev@googlegroups.com} mailing list --- for
+implementation details it is generally a better place for discussions than
+the general \code{checker-framework-discuss@googlegroups.com} mailing list,
+which is for user-focused discussions.
+
+Anyone is welcome to
+\href{https://groups.google.com/forum/#!forum/checker-framework-discuss}{join
+  \code{checker-framework-discuss@googlegroups.com}} and send mail to it.
+
+
+\sectionAndLabel{Usability of pluggable type-checking}{faq-usability-section}
+
+\subsectionAndLabel{Are type annotations easy to read and write?}{faq-ease-of-use}
+
+% This FAQ also appears in the JSR 308 FAQ.
+% When I update one, also update the other.
+
+The papers
+\href{https://homes.cs.washington.edu/~mernst/pubs/pluggable-checkers-issta2008-abstract.html}{``Practical
+  pluggable types for Java''}~\cite{PapiACPE2008}
+and
+\href{https://homes.cs.washington.edu/~mernst/pubs/pluggable-checkers-icse2011-abstract.html}{``Building
+  and using pluggable type-checkers''}~\cite{DietlDEMS2011}
+discuss case studies in
+which programmers
+found type annotations to be natural to read and write.  The code
+continued to feel like Java, and the type-checking errors were easy to
+comprehend and often led to real bugs.
+
+You don't have to take our word for it, though.  You can try the
+Checker Framework for yourself.
+
+The difficulty of adding and verifying annotations depends on your program.
+If your program is well-designed and -documented, then skimming the
+existing documentation and writing type annotations is extremely easy.
+Otherwise, you may find yourself spending a lot of time trying to
+understand, reverse-engineer, or fix bugs in your program, and then just a
+moment writing a type annotation that describes what you discovered.  This
+process inevitably improves your code.  You must decide whether it is a
+good use of your time.  For code that is not causing trouble now and is
+unlikely to do so in the future (the code is bug-free, and you do not
+anticipate changing it or using it in new contexts), then the
+effort of writing type annotations for it may not be justified.
+
+
+\subsectionAndLabel{Will my code become cluttered with type annotations?}{faq-code-clutter}
+
+% This FAQ also appears in the JSR 308 FAQ.
+% When I update one, also update the other.
+
+In summary:  annotations do not clutter code; they are used much
+less frequently than generic types, which Java programmers find acceptable;
+and they reduce the overall volume of documentation that a codebase needs.
+
+As with any language feature, it is possible to write ugly code that
+over-uses annotations.  However, in normal use, very few annotations need
+to be written.  Figure 1 of the paper
+\href{https://homes.cs.washington.edu/~mernst/pubs/pluggable-checkers-issta2008-abstract.html}{Practical
+  pluggable types for Java}~\cite{PapiACPE2008} reports data for over
+350,000 lines of type-annotated code:
+
+\begin{itemize}
+\item
+    1 annotation per 62 lines for nullness annotations (\<@NonNull>, \<@Nullable>, etc.)
+    % (/ (+ 4640 3961 10798) (+ 107 35 167))
+\item
+    1 annotation per 1736 lines for interning annotations (\<@Interned>)
+    % (/ 224048 129)
+\end{itemize}
+
+% ICSE 2011 paper says:
+% Signature String Checker: less than 1 annotation per 500 lines of code
+
+These numbers are for annotating existing code.  New code that
+is written with the type annotation system in mind is cleaner and more
+correct, so it requires even fewer annotations.
+
+Each annotation that a programmer writes replaces a sentence or phrase of
+English descriptive text that would otherwise have been written in the
+Javadoc.  So, use of annotations actually reduces the overall size of the
+documentation, at the same time as making it machine-processable
+and less ambiguous.
+
+
+\subsectionAndLabel{Will using the Checker Framework slow down my program?  Will it slow down the compiler?}{faq-slowdown}
+
+Using the Checker Framework has no impact on the execution of your program:
+the compiler emits the identical bytecodes as the javac
+compiler and so there is no run-time effect.  Because there is no run-time
+representation of type qualifiers, there is no way to use reflection to
+query the qualifier on a given object, though you can use reflection to
+examine a class/method/field declaration.
+
+Using the Checker Framework does increase compilation time.
+It can increase the compilation time by 2--10 times --- or more, if you run
+many pluggable type-checkers at once.  For workarounds, see
+Section~\ref{faq-cf-is-slow}.
+
+
+\subsectionAndLabel{How do I shorten the command line when invoking a checker?}{faq-shorten-command-line}
+
+\begin{sloppypar}
+The compile options to javac can be long to type; for example,
+\code{javac -processor org.checkerframework.checker.nullness.NullnessChecker ...}.
+You can use shorthand for built-in checkers, such as \code{javac -processor
+  nullness ...} (see Section~\ref{shorthand-for-checkers}), or you can use
+auto-discovery te eliminate the need for the \code{-processor} command-line
+option (see Section~\ref{checker-auto-discovery}).
+\end{sloppypar}
+
+
+\subsectionAndLabel{Method pre-condition contracts, including formal parameter annotations, make no sense for public methods}{faq-pre-conditions}
+
+Some people go further and say that pre-condition contracts make no sense
+for any method.  This objection is sometimes stated as, "A method parameter
+should never be annotated as \<@NonNull>.  A client could pass any value at
+all, so the method implementation cannot depend on the value being
+non-null.  Furthermore, if a client passes an illegal value, it is the
+method's responsibility to immediately tell the client about the illegal
+value."
+
+Here is an example that invalidates this general argument.  Consider a
+binary search routine.  Its specification requires that clients pass in a
+sorted array.
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  /** Return index of the search key, if it is contained it the sorted array a; otherwise ... */
+  int binarySearch(Object @Sorted [] a, Object key)
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+The \<binarySearch> routine is fast --- it runs in O(log n) time where n is
+the length of the array.  If the routine had to validate that its input
+array is sorted, then it would run in O(n) time, negating all benefit of
+binary search.  In other words, \<binarySearch> should \emph{not} validate
+its input!
+
+The nature of a contract is that if the \emph{caller} violates its
+responsibilities by passing bad values, then the \emph{callee} is absolved
+of its responsibilities.  It is polite for the callee to try to provide a
+useful diagnostic to the misbehaving caller (for example, by raising a
+particular exception quickly), but it is \emph{not} required.  In such a
+situation, the callee has the flexibility to do anything that is
+convenient.
+
+In some cases a routine has a \emph{complete} specification:  the contract
+permits the caller to pass any value, and the callee is required to throw
+particular exceptions for particular inputs.  This approach is common for
+public methods, but it is not required and is not always the right thing.
+As explained in section~\ref{annotate-normal-behavior}, even when a method
+has a complete specification, the annotations should indicate normal
+behavior:  behavior that will avoid exceptions.
+
+
+
+\sectionAndLabel{How to handle warnings and errors}{faq-warnings-section}
+
+\subsectionAndLabel{What should I do if a checker issues a warning about my code?}{faq-handling-warnings}
+
+For a discussion of this issue, see Section~\ref{handling-warnings}.
+
+
+\subsectionAndLabel{What does a certain Checker Framework warning message mean?}{faq-interpreting-warnings}
+
+Read the error message first; sometimes that is enough to clarify it.
+
+Search through this manual for the text of the warning message or for words
+that appear in it.
+
+If nothing else explains it, then ask on the
+\href{https://groups.google.com/forum/#!forum/checker-framework-discuss}{mailing
+  list}.  Be sure to say what you think it means or what specific part does
+not make sense to you, and what you have already done to try to understand it.
+
+
+\subsectionAndLabel{What do square brackets mean in a Checker Framework warning message?}{faq-warnings-square-brackets}
+
+In a message like this:
+
+\begin{Verbatim}
+  found   : ? extends T extends @UnknownKeyFor Object
+  required: [extends @UnknownKeyFor Object super @UnknownKeyFor null]
+\end{Verbatim}
+
+\noindent
+the square brackets enclose the upper and lower bounds that the type parameter needs to be within.
+
+
+\subsectionAndLabel{Can a pluggable type-checker guarantee that my code is correct?}{faq-no-absolute-guarantee}
+
+Each checker looks for certain errors.  You can use multiple checkers to
+detect more errors in your code, but you will never have a guarantee that
+your code is completely bug-free.
+
+If the type-checker issues no warning, then you have a guarantee that your
+code is free of some particular error.  There are some limitations to the
+guarantee.
+
+Most importantly, if you run a pluggable checker on only part of a program, then
+you only get a guarantee that those parts of the program are error-free.
+For example, if your code uses a library that has not been type-checked,
+then the library might be incorrect, or your code might misuse the library.
+As another example, suppose you have type-checked a framework that clients
+are intended to extend.  You should recommend that clients
+run the pluggable checker.  There is no way to force users to do so, so you
+may want to retain dynamic checks or use other mechanisms to detect errors.
+
+Section~\ref{checker-guarantees} states other limitations to a checker's
+guarantee, such as regarding concurrency.  Java's type system is also
+unsound in certain situations, such as for arrays and casts (however, the
+Checker Framework is sound for arrays and casts).  Java uses dynamic checks
+is some places it is unsound, so that errors are thrown at run time.  The
+pluggable type-checkers do not currently have built-in dynamic checkers to
+check for the places they are unsound.
+Writing dynamic checkers would be an interesting and valuable project.
+
+Other types of dynamism in a Java application do not jeopardize the
+guarantee, because the type-checker is conservative.  For example, at a
+method call, dynamic dispatch chooses some implementation of the method,
+but it is impossible to know at compile time which one it will be.  The
+type-checker gives a guarantee no matter what implementation of the method
+is invoked.
+
+% This paragraph is weak.
+
+Even if a pluggable checker cannot give an ironclad
+guarantee of correctness, it is still useful.  It can find errors,
+exclude certain types of possible problems (e.g., restricting the
+possible class of problems), improve documentation, and increase confidence
+in your software.
+
+
+\subsectionAndLabel{What guarantee does the Checker Framework give for concurrent code?}{faq-concurrency}
+
+The Lock Checker (see Chapter~\ref{lock-checker}) offers a way to detect
+and prevent certain concurrency errors.
+
+
+By default, the Checker Framework assumes that the code that it is checking
+is sequential:  that is, there are no concurrent accesses from another
+thread.  This means that the Checker Framework is unsound for concurrent
+code, in the sense that it may fail to issue a warning about errors that
+occur only when the code is running in a concurrent setting.
+For example, the Nullness Checker issues no warning for this
+code:
+
+\begin{Verbatim}
+  if (myobject.myfield != null) {
+    myobject.myfield.toString();
+  }
+\end{Verbatim}
+
+\noindent
+This code is safe when run on its own.
+However, in the presence of multithreading, the call to \<toString> may
+fail because another thread may set \<myobject.myfield> to \<null> after
+the nullness check in the \<if> condition, but before the \<if> body is
+executed.
+
+If you supply the \<-AconcurrentSemantics> command-line option, then the
+Checker Framework assumes that any field can be changed at any time.  This
+limits the amount of type refinement
+(Section~\ref{type-refinement}) that the Checker Framework can do.
+
+
+% If you are concerned about concurrency, then the ``fix''
+% of putting data in a local variable doesn't fix the problem,
+% just masks it from one particular checker.  This is bad style and may make
+% debugging harder rather than easier.
+%
+% For instance, suppose you have
+%
+% if (x.val != null) {
+%   x.val = x.val + 1;
+% }
+%
+% which can suffer a null pointer exception if another thread nulls out
+% x.val.  The underlying problem is the possible concurrency error:  the user
+% should have used locks or some other mechanism to protect access to x.val.
+%
+% Changing this to
+%
+% myval = x.val;
+% if (myval != null) {
+%   x.val = myval + 1;
+% }
+%
+% does not fix the concurrency error, because no locking has been introduced.
+% The code still has a data race that can lose updates or corrupt data
+% structures.  The code has been transformed so that the Nullness Checker
+% does not issue a warning, but this is scant comfort since the code is no
+% more correct than it was before.
+%
+% If you want to detect concurrency errors, then it is better to use a
+% correct checker that is concurrency-aware, rather than an unsound one that
+% encourages incorrect workarounds.  Another way to put this is that a static
+% checker should encourage better overall design, not just different bad
+% designs.
+
+
+\subsectionAndLabel{How do I make compilation succeed even if a checker issues errors?}{faq-awarns}
+
+Section~\ref{running} describes the \<-Awarns> command-line
+option that turns checker errors into warnings, so type-checking errors
+will not cause \<javac> to exit with a failure status.
+
+
+\subsectionAndLabel{Why does the checker always say there are 100 errors or warnings?}{faq-100-warnings}
+
+By default, javac only reports the first 100 errors or warnings.
+Furthermore, once javac encounters an error, it doesn't try compiling any
+more files (but does complete compilation of all the ones that it has
+started so far).
+
+To see more than 100 errors or warnings, use the javac options \<-Xmaxerrs>
+and \<-Xmaxwarns>.  To convert Checker Framework errors into warnings so
+that javac will process all your source files, use the option \<-Awarns>.
+See Section~\ref{running} for more details.
+
+
+\subsectionAndLabel{Why does the Checker Framework report an error regarding a type I have not written in my program?}{faq-type-i-did-not-write}
+
+Sometimes, a Checker Framework warning message will mention a type you have
+not written in your program.  This is typically because a default has been
+applied where you did not write a type; see Section~\ref{defaults}.  In
+other cases, this is because type refinement has given an
+expression a more specific type than you wrote or than was defaulted; see
+Section~\ref{type-refinement}.
+Note that an invocation of an impure method may cause the loss of all
+information that was determined via type refinement; see
+Section~\ref{type-refinement-purity}.
+
+
+\subsectionAndLabel{Why does the Checker Framework accept code on one line but reject it on the next?}{faq-same-code-different-behavior}
+
+Sometimes, the Checker Framework permits code on one line, but rejects the
+same code a few lines later:
+
+\begin{Verbatim}
+  if (myField != null) {
+    myField.hashCode();   // no warning
+    someMethod();
+    myField.hashCode();   // warning about potential NullPointerException
+  }
+\end{Verbatim}
+
+The reason is explained in the section on type refinement
+(Section~\ref{type-refinement}), which also tells how to specify the side
+effects of \<someMethod> (Section~\ref{type-refinement-purity}).
+
+
+\subsectionAndLabel{How can I do run-time monitoring of properties that were not statically checked?}{faq-run-time-checking}
+
+Some properties are not checked statically (see
+Chapter~\ref{suppressing-warnings} for reasons that code might not be
+statically checked).  In such cases, it would be desirable to check the
+property dynamically, at run time.
+Currently, the Checker Framework has no support for adding code to perform
+run-time checking.
+
+Adding such support would be an interesting and valuable project.
+An example would be an option that causes the Checker Framework to
+automatically insert a run-time check anywhere that static checking is
+suppressed.
+% such as casts
+If you
+are able to add run-time verification functionality, we would gladly
+welcome it as a contribution to the Checker Framework.
+
+Some checkers have library methods that you can explicitly insert in your
+source code.
+Examples include the Nullness Checker's
+\refmethod{checker/nullness/util}{NullnessUtil}{castNonNull}{-T-} method (see
+Section~\ref{suppressing-warnings-with-assertions}) and the Regex Checker's
+\<RegexUtil> class (see Section~\ref{regexutil-methods}).
+But, it would be better to have more general support that does not require
+the user to explicitly insert method calls.
+
+
+\sectionAndLabel{False positive warnings}{faq-false-positives-section}
+
+
+\subsectionAndLabel{What is a ``false positive'' warning?}{faq-false-positive}
+
+A ``false positive'' is when the tool reports a potential problem, but the
+code is actually correct and will never violate the given property at run
+time.
+
+The Checker Framework aims to be sound; that is, if the Checker Framework
+does not report any possible errors, then your code is correct.
+
+Every sound tool suffers false positive errors.
+Wherever the Checker Framework issues an error, you can think of it as
+saying, ``I can't prove this code is safe,'' but the code might be safe for
+some complex, tricky reason that is beyond the capabilities of its
+analysis.
+
+If you are sure that the warning is a false positive, you have several
+options.
+Perhaps you just need to write annotations, especially on method signatures
+but perhaps within method bodies as well.
+Sometimes you can rewrite the code in a clearer way that the Checker
+Framework can verify, and that might be easier for people to understand, too.
+If these don't work, then you can suppress the warning
+(Section~\ref{handling-warnings}).
+You also might want to report
+the false positive in the Checker Framework issue tracker
+(Section~\ref{reporting-bugs}), if it appears in real-world, well-written code.
+%
+Finally, you could improve the Checker Framework to make it more
+precise, so that it does not suffer that false positive (see
+Section~\ref{faq-false-positive-extend-checker-framework}).
+
+
+\subsectionAndLabel{How can I improve the Checker Framework to eliminate a false positive warning?}{faq-false-positive-extend-checker-framework}
+
+As noted in Section~\ref{faq-false-positive}, \emph{every} sound analysis
+tool suffers false positives.
+
+For any given false positive warning, it is theoretically possible to
+improve the Checker Framework to eliminate it.  (But, it's theoretically
+impossible to eliminate all false positives.  That is, there
+will always exist some programs that don't go wrong at run time but for
+which the Checker Framework issues a warning.)
+
+Some improvements affect the implementation of the type system; they do not
+add any new types.  Such an improvement is invisible to users, except that
+the users suffer fewer false positive warnings.  This type of improvement
+to the type checker's implementation is often worthwhile.
+
+Other improvements change the type system or add a new type system.
+Defining new types is a powerful way to improve precision, but it is costly
+too.  A simpler type system is easier for users to understand, less likely
+to contain bugs, and more efficient.
+
+By design, each type system in the Checker Framework has limited
+expressiveness.  Our goal is to implement enough functionality to handle
+common, well-written real-world code, not to cover every possible
+situation.
+
+When reporting bugs, please focus on realistic scenarios.  We are sure that
+you can make up artificial code that stymies the type-checker!  Those bugs
+aren't a good use of your time to report nor the maintainers' time to
+evaluate and fix.  When reporting a bug, it's very helpful to minimize it
+to give a tiny example that is easy to evaluate and fix, but please also
+indicate how it arises in real-world, well-written code.
+
+
+\subsectionAndLabel{Why doesn't the Checker Framework infer types for fields and method return types?}{faq-infer-fields}
+
+Consider the following code.  A programmer can tell that all three
+invocations of \<format> are safe --- they never suffer an
+\<IllegalFormatException> exception at run time:
+
+\begin{Verbatim}
+class MyClass {
+
+  final String field = "%d";
+
+  String method() {
+    return "%d";
+  }
+
+  void method m() {
+    String local = "%d";
+    String.format(local, 5);    // Format String Checker verifies the call is safe
+    String.format(field, 5);    // Format String Checker warns the call might not be safe
+    String.format(method(), 5); // Format String Checker warns the call might not be safe
+  }
+}
+\end{Verbatim}
+
+\noindent
+However, the Format String Checker can only verify the first call.  It issues a
+false positive warning about the second and third calls.
+
+The Checker Framework can verify all three calls, with no false positive
+warnings, if you annotate the type of \<field> and the return type of
+\<method> as \<@Format(INT)>.
+
+By default, the Checker Framework infers types for local variables
+(Section~\ref{type-refinement}), but not for fields and method return
+types.  (The Checker Framework includes a whole-program type inference tool
+that infers field and method return types; see
+Section~\ref{whole-program-inference}.)
+There are several reasons for this design choice.
+
+\begin{description}
+\item[Separation of specification from implementation]
+  The designer of an API makes certain promises to clients; these are
+  codified in the API's specification or contract.  The implementation
+  might return a more specific type today, but the designer does not want
+  clients to depend on that.  For example, a string might happen to be a
+  regular expression because it contains no characters with special meaning
+  in regexes, but that is not guaranteed to always be true.  It's better
+  for the programmer to explicitly write the intended specification.
+
+\item[Separate compilation]
+  To infer types for a non-final method, it is necessary to examine every
+  overriding implementation, so that the method's return type annotation is
+  compatible with all values that are returned by any overriding
+  implementation.  In general, examining all implementations is impossible,
+  because clients may override the method.  When possible, it is
+  inconvenient to provide all that source code, and it would slow down the
+  type-checker.
+
+  A related issue is that determining which values can be returned by a
+  method $m$ requires analyzing $m$'s body, which requires analyzing
+  all the methods called by $m$, and so forth.  This quickly devolves to
+  analyzing the whole program.
+  Determining all possible values assigned to a field is equally hard.
+
+  Type-checking is modular --- it works on one procedure at a time,
+  examining that procedure and the specifications (not implementations) of
+  methods that it calls.  Therefore, type-checking is fast and can work on
+  partial programs.  The Checker Framework performs modular type-checking,
+  not a whole-program analysis.
+
+\item[Order of compilation]
+  When the compiler is called on class \<Client> and class \<Library>, the
+  programmer has no control over which class is analyzed first.  When the
+  first class is compiled, it has access only to the signature of the other
+  class.  Therefore, a programmer would see inconsistent results depending
+  on whether \<Client> was compiled first and had access only to the
+  declared types of \<Library>, or the \<Library> was compiled first and
+  the compiler refined the types of its methods and fields before \<Client>
+  looked them up.
+
+\item[Consistent behavior with or without pluggable type-checking]
+  The \<.class> files produced with or without pluggable type-checking
+  should specify the same types for all public fields and methods.  If
+  pluggable type-checking changed the those types, then users would be
+  confused.  Depending on how a library was compiled, pluggable
+  type-checking of a client program would give different results.
+
+\end{description}
+
+% In the future, the Checker Framework may infer types for final fields; if
+% programmers see inconsistent results, they should write types explicitly.
+
+
+\subsectionAndLabel{Why doesn't the Checker Framework track relationships between variables?}{faq-relationships-between-variables}
+
+The Checker Framework estimates the possible run-time value of each
+variable, as expressed in a type system.  In general, the Checker Framework
+does estimate relationships between two variables, except for specific
+relationships listed in Section~\ref{java-expressions-as-arguments}.
+
+For example, the Checker Framework does not track which variables are equal
+to one another.  The Nullness Checker issues a warning, ``dereference of
+possibly-null reference y'', for expression \<y.toString()>:
+
+\begin{Verbatim}
+void nullnessExample1(@Nullable Object x) {
+    Object y = x;
+    if (x != null) {
+      System.out.println(y.toString());
+    }
+  }
+\end{Verbatim}
+
+\noindent
+Code that checks one variable and then uses a different variable is
+confusing and is often considered poor style.
+
+The Nullness Checker is able to verify the correctness of a small variant
+of the program, thanks to type refinement
+(Section~\ref{type-refinement}):
+
+\begin{Verbatim}
+  void nullnessExample12(@Nullable Object x) {
+    if (x != null) {
+      Object y = x;
+      System.out.println(y.toString());
+    }
+  }
+\end{Verbatim}
+
+The difference is that in the first example, nothing was known about \<x> at
+the time \<y> was set to it, and so the Nullness Checker recorded no facts
+about \<y>.  In the second example, the Nullness Checker knew that \<x>
+was non-null when \<y> was assigned to it.
+
+In the future, the Checker Framework could be enriched by tracking which
+variables are equal to one another, a technique called ``copy
+propagation''.
+
+This would handle the above examples, but wouldn't handle other examples.
+For example, the following code is safe:
+
+\begin{Verbatim}
+  void requiresPositive(@Positive int arg) {}
+
+  void intExample1(int x) {
+    int y = x*x;
+    if (x > 0) {
+      requiresPositive(y);
+    }
+  }
+
+  void intExample2(int x) {
+    int y = x*x;
+    if (y > 0) {
+      requiresPositive(x);
+    }
+  }
+\end{Verbatim}
+
+\noindent
+However, the Index Checker (\chapterpageref{index-checker}), which defines the
+\refqualclass{checker/index/qual}{Positive} type qualifier, issues warnings
+saying that it cannot prove that the arguments are \<@Positive>.
+
+A slight variant of \<intExample1> can be verified:
+
+\begin{Verbatim}
+  void intExample1a(int x) {
+    if (x > 0) {
+      int y = x*x;
+      requiresPositive(y);
+    }
+  }
+\end{Verbatim}
+
+\noindent
+No variant of \<intExample2> can be verified.  It is not worthwhile to make
+the Checker Framework more complex and slow by tracking rich properties
+such as arbitrary arithmetic.
+
+As another example of a false positive warning due to arbitrary arithmetic
+properties, consider the following code:
+
+\begin{Verbatim}
+  void falsePositive2(int arg) {
+    Object o;
+    if (arg * arg >= arg) { // always true!
+      o = new Object();
+    }
+    o.toString();  // Nullness Checker issues a false positive warning
+  }
+\end{Verbatim}
+
+
+\subsectionAndLabel{Why isn't the Checker Framework path-sensitive?}{faq-path-sensitive}
+
+The Checker Framework is not path-sensitive.  That is, it maintains one
+estimate for each variable, and it assumes at every branch (such as \<if>
+statement) that every choice could be taken.
+
+In the following code, there are two \<if> statements.
+
+\begin{Verbatim}
+  void falsePositive1(boolean b) {
+    Object o;
+    if (b) {
+      o = new Object();
+    }
+    if (b) {
+      o.toString();  // Nullness Checker issues a false positive warning
+    }
+  }
+\end{Verbatim}
+
+In general, if code has two \<if> statements in succession, then there are
+4 possible paths through the code:  [true, true], [true, false], [false,
+  true], and [false, false].  However, for this code only two of those
+paths are feasible:  namely, [true, true] and [false, false].
+
+The Checker Framework is not path-sensitive, so it issues a warning.
+
+The lack of path-sensitivity can be viewed as special case of the fact that
+the Checker Framework maintains a single estimate for each variable value,
+rather than tracking relationships between multiple variables
+(Section~\ref{faq-relationships-between-variables}).
+
+Making the Checker Framework path-sensitive would make it more powerful,
+but also much more complex and much slower.  We have not yet found this
+necessary.
+
+
+\sectionAndLabel{Syntax of type annotations}{faq-syntax-section}
+
+There is also a separate FAQ for the type annotations syntax
+(\url{https://checkerframework.org/jsr308/jsr308-faq.html}).
+
+
+\subsectionAndLabel{What is a ``receiver''?}{faq-receiver}
+
+The \emph{receiver} of a method is the \<this> formal parameter, sometimes
+also called the ``current object''.  Within the method declaration, \<this>
+is used to refer to the receiver formal parameter.  At a method call, the
+receiver actual argument is written before a period and the method name.
+
+The method \<compareTo> takes \emph{two} formal parameters.  At a call site
+like \<x.compareTo(y)>, the two arguments are \<x> and \<y>.  It is
+desirable to be able to annotate the types of both of the formal
+parameters, and doing so is supported by both Java's type annotations
+syntax and by the Checker Framework.
+
+A type annotation on the receiver is treated exactly like a type annotation
+on any other formal parameter.  At each call site, the type of the argument
+must be a consistent with (a subtype of or equal to) the declaration of the
+corresponding formal parameter.  If not, the type-checker issues a warning.
+
+Here is an example.  Suppose that \<@A Object> is a supertype of \<@B
+Object> in the following declaration:
+
+\begin{Verbatim}
+  class MyClass {
+    void requiresA(@A MyClass this) { ... }
+    void requiresB(@B MyClass this) { ... }
+  }
+\end{Verbatim}
+
+\noindent
+Then the behavior of four different invocations is as follows:
+
+\begin{Verbatim}
+  @A MyClass myA = ...;
+  @B MyClass myB = ...;
+
+  myA.requiresA()    // OK
+  myA.requiresB()    // compile-time error
+  myB.requiresA()    // OK
+  myB.requiresB()    // OK
+\end{Verbatim}
+
+The invocation \<myA.requiresB()> does not type-check because the actual
+argument's type is not a subtype of the formal parameter's type.
+
+A top-level constructor does not have a receiver.  An inner class
+constructor does have a receiver, whose type is the same as the containing
+outer class.  The receiver is distinct from the object being constructed.
+In a method of a top-level class, the receiver is named \<this>.  In a
+constructor of an inner class, the receiver is named \<Outer.this> and the
+result is named \<this>.
+
+A method in an anonymous class has a receiver, but it is not possible to
+write the receiver in the formal parameter list because there is no name
+for the type of \<this>.
+
+
+\subsectionAndLabel{What is the meaning of an annotation after a type, such as \<@NonNull Object @Nullable>?}{faq-annotation-after-type}
+
+In a type such as \<@NonNull Object @Nullable []>, it may appear that the
+\<@Nullable> annotation is written \emph{after} the type \<Object>.  In
+fact, \<@Nullable> modifies \<[]>.  See the next FAQ, about array
+annotations (Section~\ref{faq-array-syntax-meaning}).
+
+
+\subsectionAndLabel{What is the meaning of array annotations such as \<@NonNull Object @Nullable []>?}{faq-array-syntax-meaning}
+
+You should parse this as:
+(\textbf{\<@NonNull Object>}) (\textbf{\<@Nullable []>}).
+Each annotation precedes the component of the type that it qualifies.
+
+Thus,
+\<@NonNull Object @Nullable []> is a possibly-null array of non-null
+objects.  Note that the first token in the type,
+``\<@NonNull>'', applies to the element
+type \<Object>, not to the array type as a whole.  The annotation \<@Nullable> applies to the
+array (\<[]>).
+
+Similarly,
+\<@Nullable Object @NonNull []> is a non-null array of possibly-null
+objects.
+
+Some older tools have inconsistent semantics for annotations on array and
+varargs elements.  Their semantics is unfortunate and confusing; developers
+should convert their code to use type annotations instead.
+Section~\ref{declaration-annotations-moved} explains how the Checker
+Framework handles declaration annotations in the meanwhile.
+
+
+\subsectionAndLabel{What is the meaning of varargs annotations such as \<@English String @NonEmpty~...>?}{faq-varargs-syntax-meaning}
+
+Varargs annotations are treated similarly to array annotations.
+(A way to remember this is that
+when you write a varargs formal parameter such as
+\<void method(String... x) \ttlcb\ttrcb>, the Java compiler generates a
+method that takes an array of strings; whenever your source code calls the
+method with multiple arguments, the Java compiler packages them up into an
+array before calling the method.)
+
+Either of these annotations
+
+\begin{Verbatim}
+  void method(String @NonEmpty [] x) {}
+  void method(String @NonEmpty ... x) {}
+\end{Verbatim}
+
+\noindent
+applies to the array:  the method takes a non-empty array of strings, or
+the varargs list must not be empty.
+
+Either of these annotations
+
+\begin{Verbatim}
+  void method(@English String [] x) {}
+  void method(@English String ... x) {}
+\end{Verbatim}
+
+\noindent.
+applies to the element type. The annotation documents that the method takes an array of English strings.
+
+
+\subsectionAndLabel{What is the meaning of a type qualifier at a class declaration?}{faq-type-qualifier-on-class-declaration}
+
+See Section~\ref{upper-bound-for-use}.
+
+
+\subsectionAndLabel{How are type qualifiers written on upper and lower bounds?}{faq-type-qualifier-on-bounds}
+
+See Section~\ref{generics-instantiation}.
+
+
+\subsectionAndLabel{Why shouldn't a qualifier apply to both types and declarations?}{faq-no-annotation-on-types-and-declarations}
+
+It is bad style for an annotation to apply to both types and declarations.
+In other words, every annotation should have a \<@Target> meta-annotation,
+and the \<@Target> meta-annotation should list either only declaration
+locations or only type annotations.  (It's OK for an annotation to target
+both \<ElementType.TYPE\_PARAMETER> and \<ElementType.TYPE\_USE>, but no
+other declaration location along with \<ElementType.TYPE\_USE>.)
+
+Sometimes, it may seem tempting for an annotation to apply to both type
+uses and (say) method declarations.  Here is a hypothetical example:
+
+\begin{quote}
+  ``Each \<Widget> type may have a \<@Version> annotation.
+  I wish to prove that versions of widgets don't get assigned to
+  incompatible variables, and that older code does not call newer code (to
+  avoid problems when backporting).
+
+  A \<@Version> annotation could be written like so:
+
+\begin{Verbatim}
+  @Version("2.0") Widget createWidget(String value) { ... }
+\end{Verbatim}
+
+\<@Version("2.0")> on the method could mean that the \<createWidget> method
+only appears in the 2.0 version.  \<@Version("2.0")> on the return type
+could mean that the returned \<Widget> should only be used by code that
+uses the 2.0 API of \<Widget>.  It should be possible to specify these
+independently, such as a 2.0 method that returns a value that allows the
+1.0 API method invocations.''
+\end{quote}
+
+Both of these are type properties and should be specified with type
+annotations.  No method annotation is necessary or desirable.  The best way
+to require that the receiver has a certain property is to use a type
+annotation on the receiver of the method.  (Slightly more formally, the
+property being checked is compatibility between the annotation on the type
+of the formal parameter receiver and the annotation on the type of the
+actual receiver.)  If you do not know what ``receiver'' means, see
+Section~\ref{faq-receiver}.
+
+Another example of a type-and-declaration annotation that represents poor
+design is JCIP's \<@GuardedBy> annotation~\cite{Goetz2006}.  As discussed
+in Section~\ref{lock-jcip-annotations}, it means two different things when
+applied to a field or a method.  To reduce confusion and increase
+expressiveness, the Lock Checker (see Chapter~\ref{lock-checker}) uses the
+\<@Holding> annotation for one of these meanings, rather than overloading
+\<@GuardedBy> with two distinct meanings.
+
+
+A final example of a type-and-declaration annotation is some \<@Nullable>
+or \<@NonNull> annotations that are intended to work both with modern tools
+that process type annotations and with old tools that were written before
+Java had type annotations.  Such type-and-declaration annotations were a
+temporary measure, intended to be used until the tool supported Java 8
+(which was released in March 2014), and
+should not be necessary any longer.
+
+
+\subsectionAndLabel{How do I annotate a fully-qualified type name?}{faq-annotate-fully-qualified-name}
+
+If you write a fully-qualified type name in your program, then the Java
+language requires you to write a type annotation on the simple name part,
+such as
+\begin{Verbatim}
+  entity.hibernate. @Nullable User x;
+\end{Verbatim}
+
+If you try to write the type annotation before the entire fully-qualified
+name, such as
+\begin{Verbatim}
+  @Nullable entity.hibernate.User x;  // illegal Java syntax
+\end{Verbatim}
+\noindent
+then you will get an error like one of the following:
+\begin{Verbatim}
+error: scoping construct for static nested type cannot be annotated
+error: scoping construct cannot be annotated with type-use annotation
+\end{Verbatim}
+
+
+\subsectionAndLabel{What is the difference between type annotations and declaration annotations?}{faq-type-vs-declaration-annotations}
+
+Java has two distinct varieties of annotation:  type annotations and
+declaration annotations.
+
+A \textbf{type annotation} can be written on any use of a \textbf{type}.
+It conceptually creates a new, more specific type.
+That is, it describes what values the type represents.
+
+As an example, the \<int> type contains these values: ..., -2, -1, 0, 1, 2, ...  \\
+The \<@Positive int> type contains these values:  1, 2, ...   \\
+Therefore, \<@Positive int> is a subtype of \<int>.
+
+A \textbf{declaration annotation} can be written on any \textbf{declaration} (a class, method, or variable).  It describes the thing being declared, but does not describe run-time values.  Here are examples of declaration annotations:
+
+\begin{Verbatim}
+    @Deprecated    // programmers should not use this class
+    class MyClass { ... }
+
+    @Override      // this method overrides a method in a supertype
+    void myMethod() { ... }
+
+    @SuppressWarnings(...) // compiler should not warn about the initialization expr
+    int myField = INITIALIZATION-EXPRESSION;
+\end{Verbatim}
+
+Here are examples that use both a declaration annotation and a type annotation:
+
+\begin{Verbatim}
+    @Override
+    @Regex String getPattern() { ... }
+
+    @GuardedBy("myLock")
+    @NonNull String myField;
+\end{Verbatim}
+
+Note that the type annotation describes the value, and the declaration annotation says something about the method or use of the field.
+
+As a matter of style, declaration annotations are written on their own line, and type annotations are written directly before the type, on the same line.
+
+
+\label{declaration-annotations-moved} % temporary, for backward compatibility
+\subsectionAndLabel{How does the Checker Framework handle obsolete declaration annotations?}{faq-declaration-annotations-moved}
+
+When a declaration annotation is an alias for a type annotation, the
+Checker Framework may move the annotation before replacing it by the
+canonical version. (If the declaration annotation is in an \<org.checkerframework>
+package, it is not moved.)
+
+For example,
+
+\begin{Verbatim}
+  import android.support.annotation.NonNull;
+  ...
+  @NonNull Object [] returnsArray();
+\end{Verbatim}
+
+\noindent
+is treated as if the programmer had written
+
+\begin{Verbatim}
+  import org.checkerframework.checker.nullness.qual.NonNull;
+  ...
+  Object @NonNull [] returnsArray();
+\end{Verbatim}
+
+\noindent
+because Android's \<@NonNull> annotation is a declaration annotation, which
+is understood to apply to the top-level return type of the annotated method.
+
+When possible, you should use type annotations rather than declaration
+annotations.
+
+Users who are using old Java 5--7 declaration annotations (for instance,
+from the FindBugs tool, which has not been maintained since 2015, and from
+its successor SpotBugs, which has not yet adopted type annotations, even
+though type annotations were added to Java in 2014) can use annotations in
+package \<org.checkerframework.checker.nullness.compatqual> to avoid name
+conflicts.  These are available in package
+\href{https://search.maven.org/search?q=a:checker-compat-qual}{\<checker-compat-qual>}
+on Maven Central.  Once the users are ready to upgrade to Java 8+ type
+annotations, those compatibility annotations are no longer necessary.
+
+
+\sectionAndLabel{Semantics of type annotations}{faq-semantics-section}
+
+
+\subsectionAndLabel{How can I handle typestate, or phases of my program with different data properties?}{faq-typestate}
+
+Sometimes, your program works in phases that have different behavior.  For
+example, you might have a field that starts out null and becomes non-null
+at some point during execution, such as after a method is called.  You can
+express this property as follows:
+
+\begin{enumerate}
+\item
+Annotate the field type as \refqualclass{checker/nullness/qual}{MonotonicNonNull}.
+\item
+Annotate the method that sets the field as \refqualclass{checker/nullness/qual}{EnsuresNonNull}\<(">\emph{\<myFieldName>}\<")>.
+(If method \<m1> calls method \<m2>, which actually sets the field, then
+you would probably write this annotation on both \<m1> and \<m2>.)
+\item
+Annotate any method that depends on the field being non-null as
+\refqualclass{checker/nullness/qual}{RequiresNonNull}\<(">\emph{\<myFieldName>}\<")>.
+The type-checker will verify that such a method is only called when the
+field isn't null --- that is, the method is only called after the setting
+method.
+\end{enumerate}
+
+You can also use a typestate checker (see
+\chapterpageref{typestate-checker}), but they have not been as extensively
+tested.
+
+
+\subsectionAndLabel{Why are explicit and implicit bounds defaulted differently?}{faq-implicit-bounds}
+
+The following two bits of code have the same semantics under Java, but are
+treated differently by the Checker Framework's CLIMB-to-top defaulting
+rules (Section~\ref{climb-to-top}):
+
+\begin{Verbatim}
+class MyClass<T> { ... }
+class MyClass<T extends Object> { ... }
+\end{Verbatim}
+
+The difference is the annotation on the upper bound of the type argument
+\<T>.  They are treated in the following way.
+
+\begin{Verbatim}
+class MyClass<T>                 ==  class MyClass<T extends @TOPTYPEANNO Object>
+class MyClass<T extends Object>  ==  class MyClass<T extends @DEFAULTANNO Object>
+\end{Verbatim}
+
+\noindent
+\<@TOPTYPEANNO> is the top annotation in the type qualifier hierarchy.  For
+example, for the nullness type system, the top type annotation is
+\<@Nullable>, as shown in Figure~\ref{fig-nullness-hierarchy}.
+\<@DEFAULTANNO> is the default annotation for the type system.  For
+example, for the nullness type system, the default type annotation is
+\<@NonNull>.
+
+In some type systems, the top qualifier and the default are the same.  For
+such type systems, the two code snippets shown above are treated the same.
+An example is the regular expression type system; see
+Figure~\ref{fig-regex-hierarchy}.
+
+The CLIMB-to-top rule reduces the code edits required to annotate an
+existing program, and it treats types written in the program consistently.
+
+When a user writes no upper bound, as in
+\code{class C<T> \ttlcb\ ... \ttrcb},
+then Java permits the class to be instantiated with any type parameter.
+The Checker Framework behaves exactly the same, no matter what the default
+is for a particular type system --- and no matter whether the user has
+changed the default locally.
+
+When a user writes an upper bound, as in
+\code{class C<T extends OtherClass> \ttlcb\ ... \ttrcb},
+then the Checker Framework treats this occurrence of \<OtherClass> exactly
+like any other occurrence, and applies the usual defaulting rules.  Use of
+\<Object> is treated consistently with all other types in this location and
+all other occurrences of \<Object> in the program.
+
+Here are some style guidelines:
+\begin{itemize}
+\item
+  Omit the \<extends> clause when possible.  To indicate no constraints on
+  type qualifiers, write \code{class MyClass<T>} rather than \code{class
+    MyClass<T extends @TOPTYPEANNO Object>}.
+\item
+  When you write an \<Object> upper bound, give an explicit type annotation.
+  That is, write \code{class C<T extends @DEFAULTANNO Object>
+    \ttlcb\ ... \ttrcb} even though it is equivalent to writing
+  \code{class C<T extends Object> \ttlcb\ ... \ttrcb}.
+
+  If you write just \<extends Object>, then someone who is reading the code
+  might think that it is irrelevant (which it is in plain Java).  Also,
+  some IDEs will suggest removing it.
+\end{itemize}
+
+
+\subsectionAndLabel{How should I annotate code that uses generics?}{faq-writing-generics}
+
+Suppose unannotated code contains a type parameter \code{<T>}.  How should
+you annotate that?
+
+This is really a question about Java's generic types, so if you understand
+Java generics, this question is moot.  However, Java generics can be hard
+to understand, so here is a brief explanation of some concrete annotations,
+using nullness annotations for concreteness.
+
+\begin{description}
+\item[\code{<T>}]
+  Any type argument may be supplied for \code{T}.
+
+  It is equivalent to \code{<T extends @Nullable Object>}, because
+  \code{@Nullable Object} is the top type in the type hierarchy.
+
+\item[\code{<T extends @Nullable Object>}]
+  Any type argument may be supplied for \code{T}.
+
+  It is equivalent to \code{<T>}, as noted above.
+
+\item[\code{<T extends Object>}]
+  \code{T} can be instantiated by any type whose qualifier is \code{@NonNull}.
+
+  The default annotation is \code{@NonNull}, and annotation defaults apply
+  to type uses such as \code{Object} but not to type variables such as
+  \code{T}.  Therefore, \code{<T extends Object>} is equivalent to
+  \code{<T extends @NonNull Object>}.  It permits any type argument that is
+  a subtype of \code{@NonNull Object}, which is any type argument whose
+  qualifier is \code{@NonNull} since \code{@NonNull} is the bottom type
+  qualifier in the type qualifier hierarchy.
+
+\item[\code{<T extends @NonNull Object>}]
+  \code{T} can be instantiated by any type whose qualifier is \code{@NonNull}.
+
+  It is equivalent to \code{<T extends Object>}, as noted above.
+
+\item[\code{<@Nullable T>}]
+  \code{T} can be instantiated by any type whose qualifier is \code{@Nullable}.
+
+  The annotation \code{@Nullable} before \code{T} applies to \code{T}'s
+  implicit lower bound.  There is no explicit upper bound (that is, no
+  \code{extends}), so the upper bound is the top type, \code{@Nullable
+    Object}, just as for \code{<T>}, which was discussed above.  Therefore,
+  \code{<@Nullable T>} is the same as \code{<T super @Nullable void extends
+    @Nullable Object>}, except that the latter is not legal Java.
+
+\item[\code{<T super @Nullable String>}]
+  \code{T} can be instantiated by any supertype of \code{@Nullable String},
+  which is any supertype of \code{String} (\code{Object},
+  \code{Serializable}, \code{CharSequence}, etc.) so long as its type
+  qualifier is \code{@Nullable}.
+
+\item[\code{<T super @NonNull String>}]
+  \code{T} can be instantiated by any supertype of \code{@NonNull String}.
+  Since \<@NonNull> is the bottom type qualifier, the instantiating type
+  can have any type qualifier.
+
+\end{description}
+
+For more details about how the Checker Framework supports generics and
+polymorphism, see \chapterpageref{polymorphism}
+
+
+\subsectionAndLabel{Why are type annotations declared with \<@Retention(RetentionPolicy.RUNTIME)>?}{faq-runtime-retention}
+
+Annotations such as \refqualclass{checker/nullness/qual}{NonNull} are
+declared with
+\<\sunjavadocanno{java.base/java/lang/annotation/Retention.html}{Retention}(RetentionPolicy.\sunjavadoc{java.base/java/lang/annotation/RetentionPolicy.html\#RUNTIME}{RUNTIME})>.  In other words,
+these type annotations are available to tools at run time.  Such run-time
+tools could check the annotations (like an \<assert> statement), type-check
+dynamically-loaded code, check casts and \<instanceof> operations, resolve
+reflection more precisely, or other tasks that we have not yet thought of.
+Not many such tools exist today, but the annotation designers wanted to
+accommodate them in the future.
+
+\<RUNTIME> retention has negligible costs (no run-time dependency, minimal
+increase in heap size).
+
+For the purpose of static checking at compile time,
+\sunjavadoc{java.base/java/lang/annotation/RetentionPolicy.html\#CLASS}{CLASS}
+retention would be sufficient.  Note that
+\sunjavadoc{java.base/java/lang/annotation/RetentionPolicy.html\#SOURCE}{SOURCE}
+retention would not be sufficient, because of separate compilation: when
+type-checking a class, the compiler needs to read the annotations on
+libraries that it uses, and separately-compiled libraries are available to
+the compiler only as class files.
+
+
+\sectionAndLabel{Creating a new checker}{faq-create-a-checker-section}
+
+\subsectionAndLabel{How do I create a new checker?}{faq-create-a-checker}
+
+In addition to using the checkers that are distributed with the Checker
+Framework, you can write your own checker to check specific properties that
+you care about.  Thus, you can find and prevent the bugs that are most
+important to you.
+
+Chapter~\ref{creating-a-checker} gives
+complete details regarding how to write a checker.  It also suggests places
+to look for more help, such as the \href{../api/}{Checker Framework
+API documentation (Javadoc)} and the source code of the distributed
+checkers.
+
+To whet your interest and demonstrate how easy it is to get started, here
+is an example of a complete, useful type-checker.
+
+\begin{Verbatim}
+  @SubtypeOf(Unqualified.class)
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  public @interface Encrypted {}
+\end{Verbatim}
+
+Section~\ref{subtyping-example} explains this checker and tells
+you how to run it.
+
+
+\subsectionAndLabel{What properties can and cannot be handled by type-checking?}{faq-type-properties}
+
+In theory, any property about a program can be expressed and checked within
+a type system.  In practice, types are a good choice for some properties
+and a bad choice for others.
+
+A type expresses the set of possible values for an expression.  Therefore,
+types are a good choice for any property that is about variable values or
+provenance.
+
+Types are a poor choice for expressing properties about timing, such as
+that action B will happen within 10 milliseconds of action A.  Types are
+not good for verifying the results of calculations; for example, they could
+ensure that code always call an \<encrypt> routine in the appropriate
+places, but not that the \<encrypt> routine is correctly implemented.
+Types are not a good solution for preventing infinite loops, except perhaps
+in special cases.
+
+
+\subsectionAndLabel{Why is there no declarative syntax for writing type rules?}{faq-declarative-syntax-for-type-rules}
+
+A type system implementer can declaratively specify the type qualifier
+hierarchy (Section~\ref{creating-declarative-hierarchy}) and the type introduction rules
+(Section~\ref{creating-type-introduction}).  However, the Checker
+Framework uses a procedural syntax for specifying type-checking
+rules (Section~\ref{creating-extending-visitor}).
+A declarative syntax might be more concise, more readable, and more
+verifiable than a procedural syntax.
+
+We have not found the procedural syntax to be the most important impediment
+to writing a checker.
+
+Previous attempts to devise a declarative syntax
+for realistic type systems have failed; see a technical
+paper~\cite{PapiACPE2008} for a discussion.  When an
+adequate syntax exists, then the Checker Framework can be extended to
+support it.
+
+
+\sectionAndLabel{Tool questions}{faq-tool-section}
+
+
+\subsectionAndLabel{How does pluggable type-checking work?}{faq-pluggable-type-checking}
+
+The Checker Framework enables you to define a new type system.  It finds
+errors, or guarantees their absence, by performing type-checking that is
+similar to that already performed by the Java compiler.
+
+Type-checking examines each statement of your program in turn, one at a time.
+\begin{itemize}
+\item
+Expressions are processed bottom-up.  Given types for each sub-expression,
+the type-checker determines whether the types are legal for the
+expression's operator and determines the type of the expression.
+% For example, if \<x> has type \<@Positive> and \<y> has type \<@Positive>,
+% then the expression \<x + y> also has type @Positive.
+
+\item
+An assignment is legal if the type of the right-hand side is a subtype of
+the declared type of the left-hand side.
+
+\item
+ At a method call, the arguments are legal if they can be assigned to the
+ formal parameters (this is called a ``pseudo-assignment'' and it follows
+ the normal rules for assignment).  The type of the method call is the
+ declared type of the return type, where the method is declared.  If
+ the method declaration is not annotated, then a default annotation is
+ used.
+
+\item
+  Suppose that method \<Sub.m> overrides method \<Super.m>.
+  %
+  The return type of \<Sub.m> must be equal to or a subtype of the return
+  type of \<Super.m> (this is called ``covariance'').
+  %
+  The type of formal parameter $i$ of \<Sub.m> must be equal to or a
+  \emph{super}type of the type of formal parameter $i$ in \<Super.m> (this
+  is called ``contravariance'').
+
+\end{itemize}
+
+
+\subsectionAndLabel{What classpath is needed to use an annotated library?}{faq-classpath-to-use-annotated-library}
+
+Suppose that you distribute a library, which contains Checker Framework
+annotations such as \<@Nullable>.  This enables clients of the library to
+use the Checker Framework to type-check their programs.  To do so, they
+must have the Checker Framework annotations on their classpath, for
+instance by using the Checker Framework Compiler.
+
+Clients who do not wish to perform pluggable type-checking do not need to
+have the Checker Framework annotations
+(\<checker-qual.jar>) in their classpath, either when compiling or running
+their programs.
+
+The JVM does not issue a link error if an annotation is not found when a
+class is loaded.  (By contrast, the JVM does issue a link error if a
+superclass, or a parameter/return/field type, is not found.)
+Likewise, there is no problem compiling against a library even if the
+library's annotations are not on the classpath.
+These are properties of Java, and are not specific to the Checker
+Framework's annotations.
+
+
+\subsectionAndLabel{Why do \<.class> files contain more annotations than the source code?}{faq-classfile-annotations}
+
+A \<.class> file contains an annotation on every type, as computed by
+defaulting rules; see Section~\ref{effective-qualifier}.
+
+When an overridden method has a side effect annotation, the overriding
+method must have one too.  However, if the side effect annotation is
+declared with the \refqualclass{framework/qual}{InheritedAnnotation}
+meta-annotation, the Checker Framework automatically adds the missing
+annotation.  This is the case for most side effect annotations --- these
+annotations are propagated from annotated libraries, such as the JDK, to
+your code.
+
+
+\subsectionAndLabel{Is there a type-checker for managing checked and unchecked exceptions?}{faq-checked-exceptions}
+
+It is possible to annotate exception types, and any type-checker built on the
+Checker Framework enforces that type annotations are consistent between
+\<throw> statements and \<catch> clauses that might catch them.
+
+The Java compiler already enforces that all checked exceptions are caught
+or are declared to be passed through, so you would use annotations to
+express other properties about exceptions.
+
+Checked exceptions are an example of a ``type and effect'' system, which is
+like a type system but also accounts for actions/behaviors such as side
+effects.  The GUI Effect Checker (\chapterpageref{guieffect-checker}) is a
+type-and-effect system that is distributed with the Checker Framework.
+
+
+\subsectionAndLabel{The Checker Framework runs too slowly}{faq-cf-is-slow}
+
+Using a pluggable type-checker increases compile times by a factor of 2--10.
+Slow compilation speed is probably the worst thing about the Checker
+Framework.
+
+To improve performance:
+\begin{itemize}
+\item
+  Ensure that the Checker Framework has enough memory.  The Checker
+  Framework uses more memory than \<javac> does, and Java's default heap
+  limit is so small that the Checker Framework might spend most of its time
+  in garbage collection.  (If this is the case, the Checker Framework will
+  issue a warning ``Garbage collection consumed over 25\% of CPU during the
+  past minute.")  For example, you might pass an argument like
+  \<-Xmx3g>, or set an environment variable like \<export
+  \_JAVA\_OPTIONS=-Xmx3g>, to permit the Checker Framework to use up to 3GB
+  of memory.  If you use Java 8, consider upgrading to Java 11, which has
+  better memory management.
+\item
+  Set your build system to perform \emph{incremental compilation}.  When
+  compiling just a few source files (the size of a typical edit or commit),
+  you won't notice the slowdown even if the Checker Framework is very slow.
+  If you compile all files in a large project, you will definitely notice a
+  slowdown.  You should structure your build system to make compiling all
+  files rare, by declaring dependencies and using caching.
+  (Note: Maven lacks dependency-driven build and caching.  If your project
+  uses Maven, consider switching to a more capable build system such as Gradle.)
+  % (Note that some build systems have a bug, in that they unnecessarily always
+  % re-run compilation that uses annotation processors.)
+\end{itemize}
+
+If the Checker Framework is still too slow for you to run on every compilation,
+you can run it periodically, such as in a Git commit hook or in continuous
+integration.
+
+The Checker Framework team does not currently have the resources to fix
+performance problems, but we welcome community contributions.
+
+Here are some reasons that the Checker Framework is slow.
+\begin{itemize}
+\item
+  It has to do all the same work as the compiler does, such as resolving
+  overloading and overriding, inferring generics, and type-checking.
+\item
+  Its analysis is general; it interprets user-defined type systems whereas
+  \<javac> hard-codes one type system and integrates it with other processing.
+\item
+  Its analysis are much richer.  \<javac> can take certain shortcuts that
+  are not correct for all possible type systems.
+\item
+  It builds a control flow graph and performs a fixpoint analysis on it,
+  which \<javac> does not do.
+\item
+  It manipulates multiple representations of data, including \<javac>'s
+  internal representation, source code, control flow graph, and its own
+  internal representation.  These transformations and lookups take time.
+\item
+  It switches between dataflow analysis and type analysis, and this
+  sometimes causes it to redo work.
+\item
+  When running a compound or aggregate checker, it computes the control
+  flow graph multiple times, and it makes multiple passes over the program
+  rather than just one.
+\end{itemize}
+
+
+\subsectionAndLabel{What does the Checker Framework version number mean?}{faq-version-number}
+
+As explained in Section~\ref{version-number}, a change in the middle number
+of the Checker Framework version string indicates a possible
+incompatibility.
+
+Another policy is "semantic versioning" as defined at
+\url{https://semver.org/}.  It is also a fine version number policy, with
+its own advantages and disadvantages.  It is a proposal, not a standard nor
+an official definition.  Some projects follow it, and many projects do not
+follow it; even those that follow it often do so loosely.  It does not
+define key terms, such as "backwards compatible bug fixes".  It includes an
+escape hatch, ``Semantic Versioning is all about conveying meaning by how
+the version number changes.''
+
+% The API also includes the Annotation File Utilities.
+If the Checker Framework strictly used semver.org's difinition, every
+release would be a new major version, and the current version string would
+be
+% 70 releases as of the beginning of 2021.
+around version 100.0.0.
+Always incrementing the major version number would not convey meaning to
+its users --- for example, it would not indicate major new functionality or
+important behavior differences.
+
+% The Checker Framework has over 10,000 methods in its API, xand most users
+% depend on its external behavior which is also complex.  The Checker
+% Framework is released frequently.
+
+
+\sectionAndLabel{Relationship to other tools}{faq-other-tools-section}
+
+
+\subsectionAndLabel{Why not just use a bug detector (like SpotBugs or Error Prone)?}{faq-type-checking-vs-bug-detectors}
+
+A pluggable type-checker
+is a verification tool that prevents or detects all errors of a given
+variety.  If it issues no warnings, your code has no errors of a given
+variety (for details about the guarantee, see
+Section~\ref{checker-guarantees}).
+
+An alternate approach is to use a bug detector such as
+\href{https://errorprone.info/}{Error Prone},
+\href{http://findbugs.sourceforge.net/}{FindBugs}~\cite{HovemeyerP2004,HovemeyerSP2005},
+\href{https://github.com/spotbugs/spotbugs}{SpotBugs},
+\href{http://jlint.sourceforge.net/}{Jlint}~\cite{Artho2001},
+\href{https://pmd.github.io/}{PMD}~\cite{Copeland2005},
+or the
+tools built into Eclipse (see Section~\ref{faq-eclipse}) and IntelliJ\@.
+The \href{https://github.com/uber/NullAway}{NullAway} and
+\href{https://fbinfer.com/docs/next/checker-eradicate/}{Eradicate} tools are more
+like sound type-checking than bug detection, but all of those tools accept
+unsoundness --- that is, false negatives or missed warnings --- in exchange
+for analysis speed.
+
+A pluggable type-checker or verifier
+differs from a bug detector in several ways:
+\begin{itemize}
+\item
+  A type-checker reports \emph{all} errors in your code.  If a type-checker
+  reports no warnings, then you have a guarantee (a proof) of correctness
+  (Section~\ref{faq-no-absolute-guarantee}).
+
+  A bug detector aims to find \emph{some} of the most obvious errors.  Even
+  if a bug detector reports no warnings, there may still be errors in your
+  code.
+
+\item
+  A type-checker requires you to annotate your code with type qualifiers,
+  or to run an inference tool that does so for you.  That is, it requires
+  you to write a specification, then it verifies that your code meets its
+  specification.
+
+  Some bug detectors do not require annotations.  This means that it may be
+  easier to get started running a bug detector.  The tool makes guesses
+  about the intended behavior of your code, leading to false alarms or
+  missed alarms.
+
+\item
+  A verification tool may issue more warnings for a programmer to
+  investigate.  Some bug detectors internally generate many warnings, then
+  use heuristics to discard some of them.  The cost is missed alarms, when
+  the tool's heuristics classified the warnings as likely false positives
+  and discarded them.
+
+\item
+  A type-checker uses a more sophisticated and precise analysis.  For
+  example, it can take advantage of method annotations and annotations on
+  generic type parameters, such as \code{List<@NonNull String>}.  An
+  example specific to the Nullness Checker (Section~\ref{nullness-checker},
+  no other tool correctly handles map keys or initialization.
+
+  A bug detector does a more lightweight analysis.  This means that a bug
+  detector usually runs faster, giving feedback to the programmer more
+  rapidly and avoiding slowdowns.  Its analysis is often narrow, avoiding
+  properties that are tricky to reason about or that might lead to false
+  alarms.  The cost is missed alarms, when analysis is too weak to find the
+  errors.
+
+\item
+  Neither type checking nor bug detection subsumes the other.  A
+  type-checker finds problems that a bug detector cannot.  A bug detector
+  finds problems that a type-checker does not:  there is no need for the
+  type-checker to address style rules, when a bug detector is adequate.
+
+  If your code is important to you, it is advantageous to run both types of
+  tools.
+
+\end{itemize}
+
+For a case study that compared the nullness analysis of FindBugs
+(equivalent to SpotBugs), Jlint,
+PMD, and the Checker Framework, see section 6 of the paper
+\href{https://homes.cs.washington.edu/~mernst/pubs/pluggable-checkers-issta2008.pdf}{``Practical pluggable types for Java''}~\cite{PapiACPE2008}.
+The case study was on a well-tested program in daily use.  The Checker
+Framework tool found 8 nullness errors (that is, null pointer
+dereferences).  None of the other tools found any errors.  A follow-up 10
+years later found that Eclipse's nullness analysis found 0 of the errors,
+and  IntelliJ's nullness analyses found 3 of the errors, that the Nullness
+Checker found.
+
+The
+\href{https://checkerframework.org/jsr308/}{JSR 308}~\cite{JSR308-2008-09-12}
+documentation also contains a discussion of related work.
+
+
+\subsectionAndLabel{How does the Checker Framework compare with Eclipse's null analysis?}{faq-eclipse}
+
+Eclipse comes with a
+\href{http://help.eclipse.org/luna/index.jsp?topic=\%2Forg.eclipse.jdt.doc.user\%2Ftasks\%2Ftask-using_null_annotations.htm}{null analysis} that
+can detect potential null pointer errors in your code.  Eclipse's built-in
+analysis differs from the Checker Framework in several respects.
+
+The Checker Framework's Nullness Checker
+(see~\chapterpageref{nullness-checker}) is more precise:  it does a deeper
+semantic analysis, so it issues fewer false positives than Eclipse.
+Eclipse's nullness analysis is missing many features that the Checker
+Framework supports, such as handling of map keys, partially-initialized
+objects, method pre- and post-conditions, polymorphism, and a powerful
+dataflow analysis.  These are essential for practical verification of
+real-world code without poor precision.
+Furthermore, Eclipse by default ignores unannotated code (even unannotated
+parameters within a method that contains other annotations).
+As a result, Eclipse is more useful for bug-finding than for verification,
+and that is what the Eclipse documentation recommends.
+% See heading "Interpretation of null annotations" under
+% http://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-using_external_null_annotations.htm
+
+Eclipse assumes by default that all code is multi-threaded, which cripples its local
+type inference.  (This default can be turned off, however.)
+The Checker Framework allows the user to
+specify whether code will be run concurrently or not via the
+\<-AconcurrentSemantics> command-line option (see
+Section~\ref{faq-concurrency}).
+
+The Checker Framework builds on javac, so it is easier to run in
+integration scripts or in
+environments where not all developers have installed Eclipse.
+% It is possible to use ecj as one's compiler:
+% https://wiki.eclipse.org/JDT/FAQ#Can_I_use_JDT_outside_Eclipse_to_compile_Java_code.3F
+
+Eclipse handles only nullness properties and is not extensible, whereas the
+Checker Framework comes with over 20 type-checkers (for a list,
+see~\chapterpageref{introduction}) and is extensible to more properties.
+
+There are also some benefits to Eclipse's null analysis.
+It is faster than the Checker Framework, in part because it is less featureful.
+It is built into Eclipse, so you do not have to add it to your build scripts.
+Its IDE integration is tighter and slicker.
+
+In a case study, the Nullness Checker found 9 errors in a program, and
+Eclipse's analysis found 0.
+
+
+\subsectionAndLabel{How does the Checker Framework compare with NullAway?}{faq-nullaway}
+
+\href{https://github.com/uber/NullAway}{NullAway} is a lightweight, unsound
+type-checker whose aim is similar to that of the Nullness Checker
+(\chapterpageref{nullness-checker}).  For both tools, the user writes
+nullness annotations, and then the tool checks them.
+
+NullAway is faster than the Nullness Checker and requires fewer annotations.
+
+NullAway is unsound:  even if NullAway issues no warnings, your code might
+crash with a null pointer exception.  Two differences are that NullAway
+makes unchecked assumptions about getter methods and NullAway assumes all
+objects are always fully initialized.  NullAway forces all generic
+arguments to be non-null, which is not an unsoundness but is less flexible
+than the Nullness Checker.
+
+
+\subsectionAndLabel{How does the Checker Framework compare with the JDK's \<Optional> type?}{faq-optional}
+
+JDK 8 introduced the \sunjavadoc{java.base/java/util/Optional.html}{\code{Optional}}
+class, a container that is either empty or contains a non-null value.
+The Optional Checker (see Chapter~\ref{optional-checker}) guarantees that
+programmers use \<Optional> correctly.
+
+Section~\ref{nullness-vs-optional} explains the relationship between
+nullness and \<Optional> and the benefits of each.
+
+
+\subsectionAndLabel{How does pluggable type-checking compare with JML?}{faq-jml}
+
+\href{http://www.eecs.ucf.edu/~leavens/JML/index.shtml}{JML}, the Java Modeling
+Language~\cite{LeavensBR2006:JML}, is a language for writing formal
+specifications.
+
+\textbf{JML aims to be more expressive than pluggable type-checking.}
+A programmer can write a JML specification that
+describes arbitrary facts about program behavior.  Then, the programmer can
+use formal reasoning or a theorem-proving tool to verify that the code
+meets the specification.  Run-time checking is also possible.
+By contrast, pluggable type-checking can express a more limited set of
+properties about your program.  Pluggable type-checking annotations are
+more concise and easier to understand.
+
+\textbf{JML is not as practical as pluggable type-checking.}
+The JML toolset is less mature.  For instance, if your code uses
+generics or other features of Java 5, then you cannot use JML.
+However, JML has a run-time checker, which the Checker Framework currently
+lacks.
+
+
+\subsectionAndLabel{Is the Checker Framework an official part of Java?}{faq-checker-framework-part-of-java}
+
+The Checker Framework is not an official part of Java.
+The Checker Framework relies on type annotations, which became part of Java
+with Java 8 (released in March 2014).  For more about type annotations, see the
+\href{https://checkerframework.org/jsr308/jsr308-faq.html#pluggable-type-checking-in-java}{Type
+  Annotations (JSR 308) FAQ} for more details.
+
+
+\subsectionAndLabel{What is the relationship between the Checker Framework and JSR 305?}{faq-jsr-305}
+
+JSR 305 aimed to define official Java names for some annotations, such as
+\<@NonNull> and \<@Nullable>.  However, it did not aim to precisely define
+the semantics of those annotations nor to provide a reference
+implementation of an annotation processor that validated their use;
+as a result, JSR 305 was of limited utility as a specification.
+JSR 305 has been abandoned; there has been
+no activity by its expert group since
+% January
+2009.
+
+By contrast, the Checker Framework precisely defines the meaning of a set
+of annotations and provides powerful type-checkers that validate them.
+However, the Checker Framework is not an official part of the Java
+language; it chooses one set of names, but another tool might choose other
+names.
+
+In the future, the Java Community Process might revitalize JSR 305 or
+create a replacement JSR to standardize the names and
+meanings of specific annotations, after there is more experience with their
+use in practice.
+
+% JSR 305 didn't specify the semantics of its annotations, and where it did
+% they were idiosyncratic --- essentially mimicking the FindBugs tool, but
+% not useful for any other defect detection tool.  A revitalization of JSR
+% 305 would have to start over from scratch in order to clearly specify a
+% semantics that is general and useful for a whole range of tools.
+% The Java community does not yet understand all the subtleties well enough
+% to set the annotations in stone in the Java specification yet; it is
+% better for the community to experiment with different approaches, such as
+% those of FindBugs, IntelliJ, Eclipse, and the Checker Framework, so that
+% we can come to consensus before deciding on an official set.
+
+
+The Checker Framework defines annotations \<@NonNull> and \<@Nullable> that
+are compatible with annotations defined by JSR 305, SpotBugs, IntelliJ, and
+other tools; see Section~\ref{nullness-related-work}.
+
+
+\subsectionAndLabel{What is the relationship between the Checker Framework and JSR 308?}{faq-jsr-308}
+
+JSR 308, also known as the Type Annotations specification, dictates the
+syntax of type annotations in Java SE 8:  how they are expressed in the
+Java language.
+
+JSR 308 does not define any type annotations such as \<@NonNull>, and it does
+not specify the semantics of any annotations.  Those tasks are left to
+third-party tools.  The Checker Framework is one such tool.
+
+
+% LocalWords:  toolset AbstractCollection ConcurrentHashMap NullnessUtil
+% LocalWords:  castNonNull createWidget backporting JCIP's GuardedBy Awarns PMD
+% LocalWords:  ElementType nullness bytecodes Jlint Hashtable SuppressWarnings
+%  LocalWords:  RegexUtil compareTo myA requiresB nullable java myobject
+%  LocalWords:  multithreading myfield Regex NonEmpty Xmaxerrs Xmaxwarns
+%  LocalWords:  MonotonicNonNull EnsuresNonNull myFieldName pre dev API's
+%  LocalWords:  m1 m2 RequiresNonNull AconcurrentSemantics MyQual plugin
+%  LocalWords:  MyClass MyMoreRestrictiveQual TOPTYPEANNO DEFAULTANNO api
+%%  LocalWords:  OtherClass Goetz antipattern subclassed varargs regexes
+%%  LocalWords:  featureful RetentionPolicy instanceof contravariance qual
+%%  LocalWords:  NoSuchElementException binarySearch subclasses faq awarns
+%%  LocalWords:  intExample1 intExample2 requiresPositive RUNTIME NullAway
+%%  LocalWords:  IllegalFormatException typequals typedef runtime nullaway
+%%  LocalWords:  covariance InheritedAnnotation someMethod jml jsr Java''
+% LocalWords:  checkers'' covariance'' contravariance'' Xmx3g lookups
diff --git a/docs/manual/fenum-checker.tex b/docs/manual/fenum-checker.tex
new file mode 100644
index 0000000..34ea573
--- /dev/null
+++ b/docs/manual/fenum-checker.tex
@@ -0,0 +1,300 @@
+\htmlhr
+\chapterAndLabel{Fake Enum Checker for fake enumerations}{fenum-checker}
+
+The Fake Enum Checker, or Fenum Checker, enables you to define a type alias
+or typedef, in which two different sets of values have the same
+representation (the same Java type) but are not allowed to be used
+interchangeably.  It is also possible to create a typedef using the
+Subtyping Checker (\chapterpageref{subtyping-checker}), and that approach
+is sometimes more appropriate.
+
+One common use for the Fake Enum Checker is the
+\emph{fake enumeration pattern} (Section~\ref{fenum-pattern}).  For
+example, consider this code adapted from Android's
+\href{https://developer.android.com/reference/android/support/annotation/IntDef}{\code{IntDef}}
+documentation:
+
+\begin{Verbatim}
+@NavigationMode int NAVIGATION_MODE_STANDARD = 0;
+@NavigationMode int NAVIGATION_MODE_LIST = 1;
+@NavigationMode int NAVIGATION_MODE_TABS = 2;
+
+@NavigationMode int getNavigationMode();
+
+void setNavigationMode(@NavigationMode int mode);
+\end{Verbatim}
+
+The Fake Enum Checker can issue a compile-time warning if the programmer
+ever tries to call \<setNavigationMode> with an \<int> that is not a
+\<@NavigationMode int>.
+
+The Fake Enum Checker gives the same safety guarantees as a true enumeration
+type or typedef, but retaining backward-compatibility with interfaces that
+use existing Java types.  You can apply fenum annotations to any Java type,
+including all primitive types and also reference types.  Thus, you could
+use it (for example) to represent floating-point values between 0 and 1, or
+\<String>s with some particular characteristic.
+(Note that the Fake Enum Checker does not let you create a shorter alias
+for a long type name, as a real typedef would if Java supported it.)
+
+As explained in Section~\ref{fenum-annotations}, you can either define your
+own fenum annotations, such as \<@NavigationMode> above, or you can use the
+\refqualclass{checker/fenum/qual}{Fenum} type qualifier with a string argument.
+Figure~\ref{fig-fenum-hierarchy} shows part of the type hierarchy for the
+Fenum type system.
+
+\begin{figure}
+\includeimage{fenum}{3.2cm}
+\caption{Partial type hierarchy for the Fenum type system.
+There are two forms of fake enumeration annotations --- above, illustrated
+by \code{@Fenum("A")} and \code{@FenumC}.
+See Section~\ref{fenum-annotations} for descriptions of how to
+introduce both types of fenums. The type qualifiers in gray
+(\code{@FenumTop}, \code{@FenumUnqualified}, and \code{@FenumBottom})
+should never be written in
+source code; they are used internally by the type system.
+\<@FenumUnqualified> is the default qualifier for unannotated types, except
+for upper bounds which default to \<@FenumTop>.
+}
+\label{fig-fenum-hierarchy}
+\end{figure}
+
+\sectionAndLabel{Fake enum annotations}{fenum-annotations}
+
+The Fake Enum Checker supports two ways to introduce a new fake enum (fenum):
+
+\begin{enumerate}
+\item Introduce your own specialized fenum annotation with code like this in
+file \code{\emph{MyFenum}.java}:
+
+\begin{alltt}
+package \textit{myPackage}.qual;
+
+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;
+import org.checkerframework.checker.fenum.qual.FenumTop;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(\ttlcb{}ElementType.TYPE_USE, ElementType.TYPE_PARAMETER\ttrcb)
+@SubtypeOf(FenumTop.class)
+public @interface \textit{MyFenum} \ttlcb\ttrcb
+\end{alltt}
+
+You only need to adapt the italicized package, annotation, and file names in the example.
+
+Note that all custom annotations must have the
+\<@Target(\ttlcb ElementType.TYPE\_USE\ttrcb)> meta-annotation. See section
+\ref{creating-define-type-qualifiers}.
+
+\item Use the provided \refqualclass{checker/fenum/qual}{Fenum} annotation, which takes a
+\code{String} argument to distinguish different fenums or type aliases.
+For example, \code{@Fenum("A")} and \code{@Fenum("B")} are two distinct
+type qualifiers.
+\end{enumerate}
+
+
+The first approach allows you to define a short, meaningful name suitable for
+your project, whereas the second approach allows quick prototyping.
+
+
+
+\sectionAndLabel{What the Fenum Checker checks}{fenum-checks}
+
+The Fenum Checker ensures that unrelated types are not mixed.
+All types with a particular fenum annotation, or \code{@Fenum(...)} with a
+particular \code{String} argument, are
+disjoint from all unannotated types and from all types with a different fenum
+annotation or \code{String} argument.
+
+The checker ensures that
+only compatible fenum types are used in comparisons and arithmetic operations
+(if applicable to the annotated type).
+
+It is the programmer's responsibility to ensure that fields with a fenum type
+are properly initialized before use.  Otherwise, one might observe a \code{null}
+reference or zero value in the field of a fenum type.  (The Nullness Checker
+(\chapterpageref{nullness-checker}) can prevent failure to initialize a
+reference variable.)
+
+
+\sectionAndLabel{Running the Fenum Checker}{fenum-running}
+
+The Fenum Checker can be invoked by running the following commands.
+
+\begin{itemize}
+  \item
+If you define your own annotation(s), provide the name(s) of the annotation(s)
+through the \code{-Aquals} option, using a comma-no-space-separated notation:
+
+\begin{alltt}
+  javac -classpath \textit{/full/path/to/myProject/bin}:\textit{/full/path/to/myLibrary/bin} \ttbs
+        -processor org.checkerframework.checker.fenum.FenumChecker \ttbs
+        -Aquals=\textit{myPackage.qual.MyFenum} MyFile.java ...
+\end{alltt}
+
+The annotations listed in \code{-Aquals} must be accessible to
+the compiler during compilation.  Before you run the Fenum Checker with
+\code{javac}, they must be compiled and on the same path (the classpath or
+processorpath) as the Checker Framework.  It
+is not sufficient to supply their source files on the command line.
+
+You can also provide the fully-qualified paths to a set of directories
+that contain the annotations through the \code{-AqualDirs} option,
+using a colon-no-space-separated notation. For example,
+if the Checker Framework is on the classpath rather than the processorpath:
+
+\begin{alltt}
+  javac -classpath \textit{/full/path/to/myProject/bin}:\textit{/full/path/to/myLibrary/bin} \ttbs
+        -processor org.checkerframework.checker.fenum.FenumChecker \ttbs
+        -AqualDirs=\textit{/full/path/to/myProject/bin}:\textit{/full/path/to/myLibrary/bin} MyFile.java ...
+\end{alltt}
+
+Note that in these two examples, the compiled class file of the
+\<myPackage.qual.MyFenum> annotation must exist in either the \<myProject/bin>
+directory or the \<myLibrary/bin> directory. The following placement of
+the class file will work with the above commands:
+
+\begin{alltt}
+  .../myProject/bin/myPackage/qual/MyFenum.class
+\end{alltt}
+
+The two options can be used at the same time to provide groups of annotations
+from directories, and individually named annotations.
+
+\item
+If your code uses the \refqualclass{checker/fenum/qual}{Fenum} annotation, you do
+not need the \code{-Aquals} or \code{-AqualDirs} option:
+
+\begin{Verbatim}
+  javac -processor org.checkerframework.checker.fenum.FenumChecker MyFile.java ...
+\end{Verbatim}
+
+\end{itemize}
+
+For an example of running the Fake Enum Checker on Android code, see
+\url{https://github.com/karlicoss/checker-fenum-android-demo}.
+
+
+\sectionAndLabel{Suppressing warnings}{fenum-suppressing}
+
+One example of when you need to suppress warnings is when you initialize the
+fenum constants to literal values.
+To remove this warning message, add a \code{@SuppressWarnings} annotation to either
+the field or class declaration, for example:
+
+\begin{Verbatim}
+@SuppressWarnings("fenum:assignment") // initialization of fake enums
+class MyConsts {
+  public static final @Fenum("A") int ACONST1 = 1;
+  public static final @Fenum("A") int ACONST2 = 2;
+}
+\end{Verbatim}
+
+
+
+\sectionAndLabel{Example}{fenum-example}
+
+The following example introduces two fenums in class \code{TestStatic}
+and then performs a few typical operations.
+
+\begin{Verbatim}
+@SuppressWarnings("fenum:assignment")   // initialization of fake enums
+public class TestStatic {
+  public static final @Fenum("A") int ACONST1 = 1;
+  public static final @Fenum("A") int ACONST2 = 2;
+
+  public static final @Fenum("B") int BCONST1 = 4;
+  public static final @Fenum("B") int BCONST2 = 5;
+}
+
+class FenumUser {
+  @Fenum("A") int state1 = TestStatic.ACONST1;     // ok
+  @Fenum("B") int state2 = TestStatic.ACONST1;     // Incompatible fenums forbidden!
+
+  void fenumArg(@Fenum("A") int p) {}
+
+  void fenumTest() {
+    state1 = 4;                     // Direct use of value forbidden!
+    state1 = TestStatic.BCONST1;    // Incompatible fenums forbidden!
+    state1 = TestStatic.ACONST2;    // ok
+
+    fenumArg(5);                    // Direct use of value forbidden!
+    fenumArg(TestStatic.BCONST1);   // Incompatible fenums forbidden!
+    fenumArg(TestStatic.ACONST1);   // ok
+  }
+ }
+\end{Verbatim}
+
+Also, see the example project in the \<docs/examples/fenum-extension> directory.
+
+
+\sectionAndLabel{The fake enumeration pattern}{fenum-pattern}
+
+Java's
+\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.9}{\code{enum}}
+keyword lets you define an enumeration type: a finite set of distinct values
+that are related to one another but are disjoint from all other
+types, including other enumerations.
+Before enums were added to Java, there were two ways to encode an
+enumeration, both of which are error-prone:
+
+\begin{description}
+\item[the fake enum pattern]  a set of \code{int} or \code{String}
+  constants (as often found in older C code).
+
+\item[the typesafe enum pattern]  a class with private constructor.
+% This requires
+% \href{http://www.javaworld.com/javaworld/javatips/jw-javatip122.html}{careful development}.
+\end{description}
+
+Sometimes you need to use the fake enum pattern,
+rather than a real enum or the typesafe enum pattern.
+%
+One reason is backward-compatibility.  A public API that predates Java's
+enum keyword may use \code{int} constants; it cannot be changed, because
+doing so would break existing clients.  For example, Java's JDK still uses
+\code{int} constants in the AWT and Swing frameworks, and Android also uses
+\code{int} constants rather than Java enums.
+%
+Another reason is performance, especially in environments with limited
+resources.  Use of an int instead of an object can
+reduce code size, memory requirements, and run time.
+% Android no longer recommends use of ints instead of enums:  see
+% http://stackoverflow.com/questions/5143256/why-was-avoid-enums-where-you-only-need-ints-removed-from-androids-performanc
+
+In cases when code has to use the fake enum pattern, the Fake Enum Checker,
+or Fenum Checker, gives the same safety guarantees as a true enumeration type.
+The developer can introduce new types that are distinct from all values of the
+base type and from all other fake enums. Fenums can be introduced for
+primitive types as well as for reference types.
+
+
+\sectionAndLabel{References}{fenum-references}
+
+\begin{itemize}
+\item Case studies of the Fake Enum Checker:\\
+  ``Building and using pluggable type-checkers''~\cite{DietlDEMS2011}
+  (ICSE 2011, \url{https://homes.cs.washington.edu/~mernst/pubs/pluggable-checkers-icse2011.pdf#page=3})
+
+\item Java Language Specification on enums:\\
+  \url{https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.9}
+
+\item Tutorial trail on enums:\\
+  \url{https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html}
+
+\item Java Tip 122: Beware of Java typesafe enumerations:\\
+  \url{https://www.infoworld.com/article/2077487/java-tip-122--beware-of-java-typesafe-enumerations.html}
+
+\end{itemize}
+
+% LocalWords:  enums typesafe Fenum Fenums fenum MyFenum quals fenums Aquals
+% LocalWords:  TestStatic FenumC FenumD myPackage RetentionPolicy IntDef
+% LocalWords:  SubtypeOf FenumTop MyFile Enum enum AWT java jls qual
+%  LocalWords:  FenumUnqualified FenumBottom ElementType classpath typedef
+%%  LocalWords:  bootclasspath AqualDirs myProject myLibrary RUNTIME
+%  LocalWords:  setNavigationMode NavigationMode checkers'' processorpath
diff --git a/docs/manual/figures/Makefile b/docs/manual/figures/Makefile
new file mode 100644
index 0000000..0a2a231
--- /dev/null
+++ b/docs/manual/figures/Makefile
@@ -0,0 +1,28 @@
+# Put user-specific changes in your own Makefile.user.
+# Make will silently continue if that file does not exist.
+-include Makefile.user
+
+PDFFILES = $(patsubst %.svg,%.pdf,$(wildcard *.svg))
+
+RSVG_CONVERT_VERSION := $(shell rsvg-convert --version 2>/dev/null)
+
+all: ${PDFFILES} clean-obsolete-files svg-copy
+
+svg-copy:
+	-chmod -f +w ../*.svg
+	cp -pf *.svg ..
+	chmod -f -w ../*.svg
+
+%.pdf : %.svg
+ifdef RSVG_CONVERT_VERSION
+	rsvg-convert -f pdf -o $@ $<
+else
+	convert $< $@
+endif
+
+clean: clean-obsolete-files
+	@\rm -f ${PDFFILES}
+
+clean-obsolete-files:
+	@\rm -f *.png
+	@\rm -f *.eps
diff --git a/docs/manual/figures/README b/docs/manual/figures/README
new file mode 100644
index 0000000..a0011e4
--- /dev/null
+++ b/docs/manual/figures/README
@@ -0,0 +1,28 @@
+This directory contains the SVG files used for the diagrams in the Checker
+Framework manual.  The HTML manual uses the SVGs directly.  The PDF manual
+uses PDF conversions of each SVG.
+
+To edit an existing image (.svg file), use one of these tools, which are
+listed in order of preference:
+ * Inkscape
+ * Visio
+ * LibreOffice/OpenOffice
+Please don't use:
+ * https://www.draw.io (no tool installation required)
+    * The .svg files it exports don't convert to pdf with correct formatting
+      if they have multiline text
+    * The figures might require fine tuning with Inkscape afterwards, or
+      editing of the .svg files with a text editor.
+ * macSVG, which saves the .svg file as one line (!).
+    * Unless you manually fix the .svg file afterward.
+
+To create a new image:
+Copy an existing SVG and make modifications to it, so that its boxes and
+arrows have the same size and font as in other SVGs.
+
+To include an image in your chapter, use one of these:
+ \includeimage{formatter-hierarchy}{3.5cm}
+ \includeimagenocentering{value-subtyping}{2.75cm}
+Note the absence of the SVG extension. Note that the size parameter is the
+*vertical* size for the PDF only, not the HTML.  To affect the size of an
+image in HTML, please edit the image size in the SVG directly.
diff --git a/docs/manual/figures/aliasing.svg b/docs/manual/figures/aliasing.svg
new file mode 100644
index 0000000..e2baea0
--- /dev/null
+++ b/docs/manual/figures/aliasing.svg
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="200.03"
+   height="95.730003"
+   viewBox="358 100 177.42344 135.08567"
+   id="svg3577"
+   version="1.1"
+   inkscape:version="0.48.5 r10040"
+   sodipodi:docname="aliasing.svg">
+  <metadata
+     id="metadata3693">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs3691" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1707"
+     inkscape:window-height="1035"
+     id="namedview3689"
+     showgrid="false"
+     inkscape:zoom="2.3166763"
+     inkscape:cx="-59.013423"
+     inkscape:cy="35.503692"
+     inkscape:window-x="9"
+     inkscape:window-y="3"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg3577" />
+  <g
+     id="g3589"
+     transform="translate(-14.618644,-11.921761)">
+    <rect
+       style="fill:#ffffff"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3591" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3593" />
+    <text
+       font-size="15.8044"
+       style="font-size:15.80440044px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+       x="460.5"
+       y="146.772"
+       id="text3595">
+      <tspan
+         x="460.5"
+         y="146.772"
+         id="tspan3597">@MaybeAliased</tspan>
+    </text>
+  </g>
+  <g
+     id="g3609"
+     transform="translate(-14.618644,-11.921761)">
+    <rect
+       style="fill:#ffffff"
+       x="360"
+       y="200"
+       width="201"
+       height="44"
+       id="rect3611" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x="360"
+       y="200"
+       width="201"
+       height="44"
+       id="rect3613" />
+    <text
+       font-size="15.8044"
+       style="font-size:15.80440044px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+       x="460.5"
+       y="226.772"
+       id="text3615">
+      <tspan
+         x="460.5"
+         y="226.772"
+         id="tspan3617">@Unique</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new"
+     x="460.5"
+     y="77.085655"
+     id="text3619">
+    <tspan
+       x="460.5"
+       y="77.085655"
+       id="tspan3621" />
+  </text>
+  <g
+     id="g3657"
+     transform="translate(-14.618644,-11.921761)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3659" />
+    <polygon
+       style="fill:#000000"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       id="polygon3661" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       id="polygon3663" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new"
+     x="460.5"
+     y="77.085655"
+     id="text3673">
+    <tspan
+       x="460.5"
+       y="77.085655"
+       id="tspan3675" />
+  </text>
+</svg>
diff --git a/docs/manual/figures/calledmethods.svg b/docs/manual/figures/calledmethods.svg
new file mode 100644
index 0000000..4d275d8
--- /dev/null
+++ b/docs/manual/figures/calledmethods.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<svg width="12cm" height="5.8962cm" style="zoom:1" version="1.1" viewBox="358 100 248.21 294.81" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+    <metadata>
+        <rdf:RDF>
+            <cc:Work rdf:about="">
+                <dc:format>image/svg+xml</dc:format>
+                <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+                <dc:title/>
+            </cc:Work>
+        </rdf:RDF>
+    </metadata>
+    <g transform="translate(24.6 -19)">
+        <rect x="360" y="120" width="201" height="44" fill="#fff"/>
+        <rect x="340.92" y="120.09" width="240.37" height="43.817" fill="none" stroke="#7f7f7f" stroke-width="2.1826"/>
+        <text x="460.5" y="146.772" fill="#7f7f7f" font-family="'courier new'" font-size="14.323px" text-anchor="middle"><tspan x="460.5" y="146.772" fill="#7f7f7f" font-size="21.167px">@CalledMethods({})</tspan></text>
+    </g>
+    <text x="638.71808" y="257.31573" fill="#000000" font-family="'courier new'" font-size="12.8px"><tspan x="638.71808" y="257.31573"/></text>
+    <g transform="translate(27.142 149.8)">
+        <line x1="460.5" x2="460.5" y1="200" y2="173.74" fill="none" stroke="#000" stroke-width="2"/>
+        <polygon points="465.5 176.24 460.5 173.74 455.5 176.24 460.5 166.24"/>
+        <polygon points="465.5 176.24 460.5 173.74 455.5 176.24 460.5 166.24" fill="none" stroke="#000" stroke-width="2"/>
+    </g>
+    <text x="638.71808" y="257.31573" fill="#000000" font-family="'courier new'" font-size="12.8px"><tspan x="638.71808" y="257.31573"/></text>
+    <text x="489.33737" y="376.5813" fill="#7f7f7f" font-family="'courier new'" font-size="15.804px" text-anchor="middle"><tspan x="489.33737" y="376.5813" fill="#7f7f7f" font-size="21.167px">@CalledMethodsBottom</tspan></text>
+    <path d="m604.17 233.4" fill="none" stroke="#000" stroke-width="1.4111px"/>
+    <g transform="translate(28.636 155.35)">
+        <rect x="185.22px" y="31.009px" width="249.1px" height="43.36px" fill="none" stroke="#000" stroke-width="1.0249"/>
+        <text x="348.12827" y="55.567112" fill="#000000" font-family="'courier new'" font-size="21.167px" text-anchor="middle"><tspan x="310" y="58" font-size="21.167px">@CalledMethods("a")</tspan></text>
+    </g>
+    <g transform="translate(245.56 155.35)">
+        <rect x="257.39" y="31.009" width="246.7px" height="43.36" fill="none" stroke="#000" stroke-width="1.0249"/>
+        <text x="348.12827" y="55.567112" fill="#000000" font-family="'courier new'" font-size="21.167px" text-anchor="middle"><tspan x="380" y="58" font-size="21.167px">@CalledMethods("b")</tspan></text>
+    </g>
+    <path d="m501.1 181.86" fill="none" stroke="#000" stroke-width="1.4111px"/>
+    <g transform="translate(143.34 239.26)">
+        <rect x="176.38px" y="31.124px" width="339.33px" height="43.13px" fill="none" stroke="#000" stroke-width="1.2545"/>
+        <text x="348.12827" y="55.567112" fill="#000000" font-family="'courier new'" font-size="21.167px" text-anchor="middle"><tspan x="348.12827" y="55.567112" font-size="21.167px">@CalledMethods({"a", "b"})</tspan></text>
+    </g>
+    <g transform="matrix(.57938 .81506 -.81506 .57938 345.91 -324.02)">
+        <line x1="460.5" x2="460.5" y1="200" y2="173.74" fill="none" stroke="#000" stroke-width="2"/>
+        <polygon points="455.5 176.24 460.5 166.24 465.5 176.24 460.5 173.74"/>
+        <polygon points="455.5 176.24 460.5 166.24 465.5 176.24 460.5 173.74" fill="none" stroke="#000" stroke-width="2"/>
+    </g>
+    <path d="m452.18 165.76-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+    <path d="m450.68 166.06-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+    <g transform="matrix(-1 0 0 1 898.43 -83.701)">
+        <g transform="matrix(.57938 .81506 -.81506 .57938 347.02 -157.87)">
+            <line x1="460.5" x2="460.5" y1="200" y2="173.74" fill="none" stroke="#000" stroke-width="2"/>
+            <polygon points="465.5 176.24 460.5 173.74 455.5 176.24 460.5 166.24"/>
+            <polygon points="465.5 176.24 460.5 173.74 455.5 176.24 460.5 166.24" fill="none" stroke="#000" stroke-width="2"/>
+        </g>
+        <path d="m453.28 331.9-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+        <path d="m451.78 332.2-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+    </g>
+    <g transform="translate(78.376 -83.701)">
+        <g transform="matrix(.57938 .81506 -.81506 .57938 347.02 -157.87)">
+            <line x1="460.5" x2="460.5" y1="200" y2="173.74" fill="none" stroke="#000" stroke-width="2"/>
+            <polygon points="460.5 166.24 465.5 176.24 460.5 173.74 455.5 176.24"/>
+            <polygon points="460.5 166.24 465.5 176.24 460.5 173.74 455.5 176.24" fill="none" stroke="#000" stroke-width="2"/>
+        </g>
+        <path d="m453.28 331.9-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+        <path d="m451.78 332.2-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+    </g>
+    <rect x="358.75px" y="349.88px" width="265.91px" height="43.86px" fill="none" stroke="#7f7f7f" stroke-width="2.1401"/>
+    <g transform="matrix(-1 0 0 1 981.48 -166.75)">
+        <g transform="matrix(.57938 .81506 -.81506 .57938 347.02 -157.87)">
+            <line x1="460.5" x2="460.5" y1="200" y2="173.74" fill="none" stroke="#000" stroke-width="2"/>
+            <polygon points="460.5 173.74 455.5 176.24 460.5 166.24 465.5 176.24"/>
+            <polygon points="460.5 173.74 455.5 176.24 460.5 166.24 465.5 176.24" fill="none" stroke="#000" stroke-width="2"/>
+        </g>
+        <path d="m453.28 331.9-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+        <path d="m451.78 332.2-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+    </g>
+</svg>
diff --git a/docs/manual/figures/chainlink.svg b/docs/manual/figures/chainlink.svg
new file mode 100644
index 0000000..bf88e80
--- /dev/null
+++ b/docs/manual/figures/chainlink.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+   width="482.136px" height="482.135px" viewBox="0 0 482.136 482.135" style="enable-background:new 0 0 482.136 482.135;"
+   xml:space="preserve">
+<g>
+  <path d="M455.482,198.184L326.829,326.832c-35.535,35.54-93.108,35.54-128.646,0l-42.881-42.886l42.881-42.876l42.884,42.876
+    c11.845,11.822,31.064,11.846,42.886,0l128.644-128.643c11.816-11.831,11.816-31.066,0-42.9l-42.881-42.881
+    c-11.822-11.814-31.064-11.814-42.887,0l-45.928,45.936c-21.292-12.531-45.491-17.905-69.449-16.291l72.501-72.526
+    c35.535-35.521,93.136-35.521,128.644,0l42.886,42.881C491.018,105.045,491.018,162.663,455.482,198.184z M201.206,366.698
+    l-45.903,45.9c-11.845,11.846-31.064,11.817-42.881,0l-42.884-42.881c-11.845-11.821-11.845-31.041,0-42.886l128.646-128.648
+    c11.819-11.814,31.069-11.814,42.884,0l42.886,42.886l42.876-42.886l-42.876-42.881c-35.54-35.521-93.113-35.521-128.65,0
+    L26.655,283.946c-35.538,35.545-35.538,93.146,0,128.652l42.883,42.882c35.51,35.54,93.11,35.54,128.646,0l72.496-72.499
+    C246.724,384.578,222.588,379.197,201.206,366.698z"/>
+</g>
+</svg>
diff --git a/docs/manual/figures/classval.svg b/docs/manual/figures/classval.svg
new file mode 100644
index 0000000..60cfd67
--- /dev/null
+++ b/docs/manual/figures/classval.svg
@@ -0,0 +1,329 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="21.891211cm"
+   height="7.5999999cm"
+   viewBox="358 100 688.00947 379.99999"
+   id="svg3577"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="classval.svg">
+  <metadata
+     id="metadata3693">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs3691" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1596"
+     inkscape:window-height="1155"
+     id="namedview3689"
+     showgrid="false"
+     inkscape:zoom="1.1774114"
+     inkscape:cx="321.35175"
+     inkscape:cy="71.466199"
+     inkscape:window-x="4"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="g3557"
+     inkscape:connector-spacing="5"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <g
+     id="g3589"
+     transform="translate(249.16728,-13.432111)">
+    <rect
+       style="fill:#ffffff"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3591" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2;stroke-opacity:1"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3593" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:15.80440044px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1;"
+       x="460.5"
+       y="146.772"
+       id="text3595">
+      <tspan
+         x="460.5"
+         y="146.772"
+         id="tspan3597"
+         style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1;">@UnknownClass</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="817.27289"
+     y="299.38467"
+     id="text3619">
+    <tspan
+       x="817.27289"
+       y="299.38467"
+       id="tspan3621" />
+  </text>
+  <g
+     id="g3657"
+     transform="translate(249.16728,-12.431901)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3659" />
+    <polygon
+       style="fill:#000000"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       id="polygon3661" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       id="polygon3663" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="817.27289"
+     y="299.38467"
+     id="text3673">
+    <tspan
+       x="817.27289"
+       y="299.38467"
+       id="tspan3675" />
+  </text>
+  <g
+     id="g3557"
+     transform="translate(249.16728,234.99999)">
+    <rect
+       id="rect3559"
+       height="44"
+       width="201"
+       y="200"
+       x="360"
+       style="fill:#ffffff" />
+    <rect
+       id="rect3561"
+       height="44"
+       width="201"
+       y="200"
+       x="360"
+       style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2;stroke-opacity:1" />
+    <text
+       id="text3563"
+       y="226.772"
+       x="460.5"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1;"
+       font-size="15.8044">
+      <tspan
+         id="tspan3565"
+         y="226.772"
+         x="460.5"
+         style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1;">@ClassValBottom</tspan>
+    </text>
+  </g>
+  <rect
+     id="rect3575"
+     height="43.299995"
+     width="372.2453"
+     y="353.34964"
+     x="521.14764"
+     style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.97555554;stroke-miterlimit:4;stroke-dasharray:none" />
+  <text
+     id="text3577"
+     y="378.38217"
+     x="708.80579"
+     style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+     font-size="15.8044">
+    <tspan
+       style="font-size:21.16666603px"
+       id="tspan3579"
+       y="378.38217"
+       x="708.80579">@ClassVal(&quot;java.lang.String&quot;)</tspan>
+  </text>
+  <g
+     id="g3631"
+     transform="translate(242.50394,155.48248)">
+    <rect
+       id="rect3583"
+       height="42.330967"
+       width="703.12579"
+       y="33.920292"
+       x="116.46185"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.97555554;stroke-miterlimit:4;stroke-dasharray:none" />
+    <text
+       id="text3585"
+       y="56.765598"
+       x="469.17535"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       font-size="15.8044">
+      <tspan
+         style="font-size:21.16666603px"
+         id="tspan3587"
+         y="56.765598"
+         x="469.17535">@ClassBound({&quot;java.lang.String&quot;,&quot;com.example.MyClass&quot;})</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(249.16728,233.99985)"
+     id="g3590">
+    <line
+       id="line3592"
+       y2="173.73599"
+       x2="460.5"
+       y1="200"
+       x1="460.5"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    <polygon
+       id="polygon3594"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       style="fill:#000000" />
+    <polygon
+       id="polygon3596"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+  </g>
+  <polygon
+     transform="matrix(0.3613421,-0.93243331,0.93243331,0.3613421,176.60015,685.00991)"
+     inkscape:transform-center-x="-11.798166"
+     style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2"
+     points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+     id="polygon3604"
+     inkscape:transform-center-y="-26.307047" />
+  <path
+     inkscape:connector-curvature="2"
+     inkscape:connector-type="polyline"
+     id="path4207"
+     d="m 506.0449,318.83324 204.00523,34.32427"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.76400471;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+  <g
+     id="g3659"
+     transform="translate(254.31136,164.35666)">
+    <g
+       id="g4194">
+      <rect
+         id="rect3638"
+         height="43.204239"
+         width="400.00244"
+         y="107.88798"
+         x="593.76715"
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.97555554;stroke-miterlimit:4;stroke-dasharray:none" />
+      <text
+         id="text3640"
+         y="131.16992"
+         x="795.98877"
+         style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+         font-size="15.8044">
+        <tspan
+           id="tspan3642"
+           y="131.16992"
+           x="795.98877"
+           style="font-size:21.16666603px">@ClassBound(&quot;java.lang.String&quot;)</tspan>
+      </text>
+    </g>
+    <g
+       transform="translate(-172.24538,-16.460672)"
+       id="g3651">
+      <g
+         id="g4186"
+         transform="translate(-57.627117,0)">
+        <rect
+           style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.97555554;stroke-miterlimit:4;stroke-dasharray:none"
+           x="131.05716"
+           y="123.41464"
+           width="671.4068"
+           height="39.973568"
+           id="rect3653" />
+        <text
+           font-size="15.8044"
+           style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+           x="469.60605"
+           y="146.35243"
+           id="text3655">
+          <tspan
+             x="469.60605"
+             y="146.35243"
+             id="tspan3657"
+             style="font-size:21.16666603px">@ClassVal({&quot;java.lang.String&quot;,&quot;com.example.MyClass&quot;})</tspan>
+        </text>
+      </g>
+    </g>
+  </g>
+  <polygon
+     transform="matrix(0.36063736,0.93270611,-0.93270611,0.36063736,1062.049,-170.73459)"
+     id="polygon7624"
+     points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+     style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2" />
+  <path
+     inkscape:connector-curvature="2"
+     inkscape:connector-type="polyline"
+     id="path4199"
+     d="M 708.26873,353.3872 1065.8075,321.41052"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.39961195;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+  <path
+     inkscape:connection-end="#polygon7624"
+     inkscape:connection-start="#polygon7624"
+     inkscape:connector-curvature="2"
+     inkscape:connector-type="polyline"
+     id="path4201"
+     d="m 1067.1048,323.14027 0,0"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <line
+     id="line7628"
+     y2="238.5769"
+     x2="704.63818"
+     y1="271.28821"
+     x1="493.95413"
+     style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2.86678195;stroke-miterlimit:4;stroke-dasharray:none" />
+  <polygon
+     id="polygon7632"
+     points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+     style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2"
+     transform="matrix(0.3749201,0.92705713,-0.92705713,0.3749201,691.56047,-253.66045)"
+     inkscape:transform-center-x="4.3747709"
+     inkscape:transform-center-y="6.3525828" />
+  <line
+     style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:3.08052111;stroke-miterlimit:4;stroke-dasharray:none"
+     x1="1073.6409"
+     y1="270.60599"
+     x2="720.29547"
+     y2="238.67139"
+     id="line7636" />
+  <polygon
+     style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2"
+     points="455.5,176.236 460.5,166.236 465.5,176.236 460.5,173.736 "
+     id="polygon7640"
+     transform="matrix(0.41276874,-0.91083586,0.91083586,0.41276874,372.78503,586.29175)" />
+</svg>
diff --git a/docs/manual/figures/fenum.svg b/docs/manual/figures/fenum.svg
new file mode 100644
index 0000000..2604070
--- /dev/null
+++ b/docs/manual/figures/fenum.svg
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
+<svg width="25.5cm" height="5.5cm" viewBox="328 238 1013 205" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <g>
+    <rect style="fill: #ffffff" x="329.198" y="320.056" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="329.198" y="320.056" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="409.198" y="345.906">
+      <tspan x="409.198" y="345.906">@Fenum("A")</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="1149.67" y="320" width="189.8" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #7f7f7f" x="1149.67" y="320" width="189.8" height="42"/>
+    <text font-size="16" style="fill: #7f7f7f;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="1244.57" y="345.85">
+      <tspan x="1244.57" y="345.85">@FenumUnqualified</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="740" y="240" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #7f7f7f" x="740" y="240" width="160" height="42"/>
+    <text font-size="16" style="fill: #7f7f7f;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="820" y="265.85">
+      <tspan x="820" y="265.85">@FenumTop</tspan>
+    </text>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="409.198" y1="320.056" x2="724.153" y2="274.779"/>
+    <polygon style="fill: #000000" points="725.149,281.708 738.011,272.787 723.157,267.85 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="725.149,281.708 738.011,272.787 723.157,267.85 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1244.57" y1="320" x2="915.838" y2="274.318"/>
+    <polygon style="fill: #000000" points="916.801,267.385 901.971,272.391 914.874,281.251 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="916.801,267.385 901.971,272.391 914.874,281.251 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="820" y1="400" x2="424.134" y2="363.436"/>
+    <polygon style="fill: #000000" points="424.778,356.465 410.194,362.148 423.491,370.406 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="424.778,356.465 410.194,362.148 423.491,370.406 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="820" y1="400" x2="1229.63" y2="363.337"/>
+    <polygon style="fill: #000000" points="1230.26,370.309 1243.58,362.089 1229.01,356.365 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1230.26,370.309 1243.58,362.089 1229.01,356.365 "/>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="740" y="400" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #7f7f7f" x="740" y="400" width="160" height="42"/>
+    <text font-size="16" style="fill: #7f7f7f;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="820" y="425.85">
+      <tspan x="820" y="425.85">@FenumBottom</tspan>
+    </text>
+  </g>
+  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal" x="820" y="261">
+    <tspan x="820" y="261"></tspan>
+  </text>
+  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal" x="409.198" y="341.056">
+    <tspan x="409.198" y="341.056"></tspan>
+  </text>
+  <g>
+    <rect style="fill: #ffffff" x="499.97" y="320.092" width="160" height="41.7325"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="499.97" y="320.092" width="160" height="41.7325"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="579.97" y="345.808">
+      <tspan x="579.97" y="345.808">@Fenum("B")</tspan>
+    </text>
+  </g>
+  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal" x="579.97" y="340.958">
+    <tspan x="579.97" y="340.958"></tspan>
+  </text>
+  <g>
+    <rect style="fill: #ffffff" x="739.446" y="320.092" width="160" height="41.7325"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="739.446" y="320.092" width="160" height="41.7325"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="819.446" y="345.808">
+      <tspan x="819.446" y="345.808">@FenumC</tspan>
+    </text>
+  </g>
+  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal" x="819.446" y="340.958">
+    <tspan x="819.446" y="340.958"></tspan>
+  </text>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="579.97" y1="320.092" x2="729.545" y2="283.269"/>
+    <polygon style="fill: #000000" points="736.827,281.476 728.313,288.721 729.545,283.269 725.922,279.011 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="736.827,281.476 728.313,288.721 729.545,283.269 725.922,279.011 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="819.446" y1="320.092" x2="819.702" y2="292.736"/>
+    <polygon style="fill: #000000" points="819.773,285.237 824.679,295.283 819.702,292.736 814.679,295.189 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="819.773,285.237 824.679,295.283 819.702,292.736 814.679,295.189 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="820" y1="400" x2="589.585" y2="363.353"/>
+    <polygon style="fill: #000000" points="582.178,362.175 592.84,358.808 589.585,363.353 591.269,368.684 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="582.178,362.175 592.84,358.808 589.585,363.353 591.269,368.684 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="820" y1="400" x2="819.587" y2="371.559"/>
+    <polygon style="fill: #000000" points="819.478,364.06 824.623,373.986 819.587,371.559 814.624,374.131 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="819.478,364.06 824.623,373.986 819.587,371.559 814.624,374.131 "/>
+  </g>
+  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal" x="409.198" y="341.056">
+    <tspan x="409.198" y="341.056"></tspan>
+  </text>
+  <g>
+    <rect style="fill: #ffffff" x="1079.65" y="320" width="40" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1079.65" y="320" width="40" height="42"/>
+    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="1099.65" y="344.9">
+      <tspan x="1099.65" y="344.9">...</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="669.338" y="320.036" width="40" height="41.8095"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="669.338" y="320.036" width="40" height="41.8095"/>
+    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="689.338" y="344.841">
+      <tspan x="689.338" y="344.841">...</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="910.434" y="320.572" width="160" height="41.7325"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="910.434" y="320.572" width="160" height="41.7325"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="990.434" y="346.288">
+      <tspan x="990.434" y="346.288">@FenumD</tspan>
+    </text>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="990.434" y1="320.572" x2="892.147" y2="286.217"/>
+    <polygon style="fill: #000000" points="885.067,283.743 896.156,282.322 892.147,286.217 892.857,291.762 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="885.067,283.743 896.156,282.322 892.147,286.217 892.857,291.762 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="820" y1="400" x2="980.928" y2="364.407"/>
+    <polygon style="fill: #000000" points="988.251,362.787 979.566,369.828 980.928,364.407 977.407,360.064 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="988.251,362.787 979.566,369.828 980.928,364.407 977.407,360.064 "/>
+  </g>
+</svg>
diff --git a/docs/manual/figures/formatter-categories.svg b/docs/manual/figures/formatter-categories.svg
new file mode 100644
index 0000000..c7059e6
--- /dev/null
+++ b/docs/manual/figures/formatter-categories.svg
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
+<svg width="17cm" height="8cm" viewBox="-3 239 666 319" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <g>
+    <rect style="fill: #ffffff" x="250.274" y="304" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="250.274" y="304" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="330.274" y="329.85">
+      <tspan x="330.274" y="329.85">GENERAL</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="502.292" y="372.486" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="502.292" y="372.486" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="582.292" y="398.336">
+      <tspan x="582.292" y="398.336">FLOAT</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="250.274" y="240" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="250.274" y="240" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="330.274" y="265.85">
+      <tspan x="330.274" y="265.85">UNUSED</tspan>
+    </text>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="330.274" y1="304" x2="330.274" y2="297"/>
+    <polygon style="fill: #000000" points="337.274,297 330.274,283 323.274,297 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="337.274,297 330.274,283 323.274,297 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="246.292" y1="372.486" x2="278.322" y2="354.375"/>
+    <polygon style="fill: #000000" points="281.767,360.469 290.509,347.485 274.876,348.282 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="281.767,360.469 290.509,347.485 274.876,348.282 "/>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="166.292" y="372.486" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="166.292" y="372.486" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="246.292" y="398.336">
+      <tspan x="246.292" y="398.336">INT</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="334.292" y="372.486" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="334.292" y="372.486" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="414.292" y="398.336">
+      <tspan x="414.292" y="398.336">TIME</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="-1.70787" y="372.486" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-1.70787" y="372.486" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="78.2921" y="398.336">
+      <tspan x="78.2921" y="398.336">CHAR</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="78.4018" y="442.758" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="78.4018" y="442.758" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="158.402" y="468.608">
+      <tspan x="158.402" y="468.608">CHAR_AND_INT</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="250.402" y="442.758" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="250.402" y="442.758" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="330.402" y="468.608">
+      <tspan x="330.402" y="468.608">INT_AND_TIME</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="250.274" y="514.918" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="250.274" y="514.918" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="330.274" y="540.768">
+      <tspan x="330.274" y="540.768">NULL</tspan>
+    </text>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="501.444" y1="371.516" x2="425.597" y2="350.904"/>
+    <polygon style="fill: #000000" points="427.432,344.149 412.087,347.233 423.761,357.659 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="427.432,344.149 412.087,347.233 423.761,357.659 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="159.128" y1="371.516" x2="234.963" y2="350.904"/>
+    <polygon style="fill: #000000" points="236.799,357.659 248.473,347.233 233.127,344.15 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="236.799,357.659 248.473,347.233 233.127,344.15 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="387.339" y1="371.516" x2="368.854" y2="356.448"/>
+    <polygon style="fill: #000000" points="373.276,351.022 358.002,347.602 364.431,361.873 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="373.276,351.022 358.002,347.602 364.431,361.873 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="133.319" y1="441.755" x2="114.652" y2="425.38"/>
+    <polygon style="fill: #000000" points="119.268,420.118 104.127,416.148 110.036,430.643 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="119.268,420.118 104.127,416.148 110.036,430.643 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="185.921" y1="441.755" x2="207.057" y2="424.856"/>
+    <polygon style="fill: #000000" points="211.428,430.323 217.992,416.113 202.686,419.389 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="211.428,430.323 217.992,416.113 202.686,419.389 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="304.066" y1="441.755" x2="284.139" y2="425.106"/>
+    <polygon style="fill: #000000" points="288.627,419.734 273.395,416.13 279.651,430.478 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="288.627,419.734 273.395,416.13 279.651,430.478 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="356.669" y1="441.755" x2="376.526" y2="425.121"/>
+    <polygon style="fill: #000000" points="381.021,430.487 387.259,416.131 372.031,419.755 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="381.021,430.487 387.259,416.131 372.031,419.755 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="277.886" y1="513.923" x2="224.621" y2="491.56"/>
+    <polygon style="fill: #000000" points="227.33,485.106 211.712,486.14 221.911,498.014 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="227.33,485.106 211.712,486.14 221.911,498.014 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="330.313" y1="513.923" x2="330.336" y2="500.753"/>
+    <polygon style="fill: #000000" points="337.336,500.765 330.361,486.753 323.336,500.741 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="337.336,500.765 330.361,486.753 323.336,500.741 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="369.19" y1="513.924" x2="530.317" y2="422.861"/>
+    <polygon style="fill: #000000" points="533.761,428.955 542.505,415.972 526.873,416.766 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="533.761,428.955 542.505,415.972 526.873,416.766 "/>
+  </g>
+</svg>
diff --git a/docs/manual/figures/formatter-hierarchy.svg b/docs/manual/figures/formatter-hierarchy.svg
new file mode 100644
index 0000000..88694fb
--- /dev/null
+++ b/docs/manual/figures/formatter-hierarchy.svg
@@ -0,0 +1,305 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="9.5cm"
+   height="4.5cm"
+   viewBox="146 238 365 175"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.5 r10040"
+   sodipodi:docname="formatter-hierarchy.svg">
+  <metadata
+     id="metadata104">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs102" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="2560"
+     inkscape:window-height="1551"
+     id="namedview100"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="4.1115322"
+     inkscape:cx="127.68962"
+     inkscape:cy="62.182749"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="g40" />
+  <g
+     id="g4">
+    <rect
+       style="fill: #ffffff"
+       x="349.452"
+       y="304"
+       width="160"
+       height="42"
+       id="rect6" />
+    <rect
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x="349.452"
+       y="304"
+       width="160"
+       height="42"
+       id="rect8" />
+    <text
+       font-size="16"
+       style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal"
+       x="429.452"
+       y="329.85"
+       id="text10">
+      <tspan
+         x="429.452"
+         y="329.85"
+         id="tspan12">@InvalidFormat</tspan>
+    </text>
+  </g>
+  <g
+     id="g14">
+    <rect
+       style="fill: #ffffff"
+       x="250.274"
+       y="239.028"
+       width="160"
+       height="42"
+       id="rect16" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke-width:2;stroke:#a2a2a2;stroke-opacity:1"
+       x="250.274"
+       y="239.028"
+       width="160"
+       height="42"
+       id="rect18" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#a2a2a2;font-family:courier new;fill-opacity:1"
+       x="330.27399"
+       y="264.87799"
+       id="text20">
+      <tspan
+         x="330.27399"
+         y="264.87799"
+         id="tspan22"
+         style="fill:#a2a2a2;fill-opacity:1">@UnknownFormat</tspan>
+    </text>
+  </g>
+  <g
+     id="g24">
+    <line
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x1="429.452"
+       y1="304"
+       x2="393.6"
+       y2="288.104"
+       id="line26" />
+    <polygon
+       style="fill: #000000"
+       points="396.437,281.705 380.801,282.43 390.763,294.504 "
+       id="polygon28" />
+    <polygon
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       points="396.437,281.705 380.801,282.43 390.763,294.504 "
+       id="polygon30" />
+  </g>
+  <g
+     id="g32">
+    <line
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x1="227.446"
+       y1="300"
+       x2="259.858"
+       y2="287.4"
+       id="line34" />
+    <polygon
+       style="fill: #000000"
+       points="262.394,293.925 272.907,282.328 257.322,280.876 "
+       id="polygon36" />
+    <polygon
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       points="262.394,293.925 272.907,282.328 257.322,280.876 "
+       id="polygon38" />
+  </g>
+  <g
+     id="g40">
+    <rect
+       style="fill: #ffffff"
+       x="250"
+       y="369.95"
+       width="160"
+       height="42"
+       id="rect42" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke-width:2;stroke:#a2a2a2;stroke-opacity:1"
+       x="250"
+       y="369.95"
+       width="160"
+       height="42"
+       id="rect44" />
+    <text
+       font-size="16"
+       style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal"
+       x="330"
+       y="395.8"
+       id="text46">
+      <tspan
+         x="330"
+         y="395.8"
+         id="tspan48"
+         style="fill:#a2a2a2;fill-opacity:1">@FormatBottom</tspan>
+    </text>
+  </g>
+  <g
+     id="g50">
+    <rect
+       style="fill: #ffffff"
+       x="155.446"
+       y="308"
+       width="160"
+       height="42"
+       id="rect52" />
+    <rect
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x="155.446"
+       y="308"
+       width="160"
+       height="42"
+       id="rect54" />
+    <text
+       font-size="16"
+       style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal"
+       x="235.446"
+       y="333.85"
+       id="text56">
+      <tspan
+         x="235.446"
+         y="333.85"
+         id="tspan58" />
+    </text>
+  </g>
+  <g
+     id="g60">
+    <line
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x1="296.424"
+       y1="368.951"
+       x2="281.569"
+       y2="359.219"
+       id="line62" />
+    <polygon
+       style="fill: #000000"
+       points="285.406,353.364 269.859,351.547 277.733,365.074 "
+       id="polygon64" />
+    <polygon
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       points="285.406,353.364 269.859,351.547 277.733,365.074 "
+       id="polygon66" />
+  </g>
+  <g
+     id="g68">
+    <line
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x1="363.179"
+       y1="368.948"
+       x2="383.772"
+       y2="355.292"
+       id="line70" />
+    <polygon
+       style="fill: #000000"
+       points="387.641,361.126 395.44,347.555 379.903,349.458 "
+       id="polygon72" />
+    <polygon
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       points="387.641,361.126 395.44,347.555 379.903,349.458 "
+       id="polygon74" />
+  </g>
+  <g
+     id="g76">
+    <rect
+       style="fill: #ffffff"
+       x="151.446"
+       y="304"
+       width="160"
+       height="42"
+       id="rect78" />
+    <rect
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x="151.446"
+       y="304"
+       width="160"
+       height="42"
+       id="rect80" />
+    <text
+       font-size="16"
+       style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal"
+       x="231.446"
+       y="329.85"
+       id="text82">
+      <tspan
+         x="231.446"
+         y="329.85"
+         id="tspan84" />
+    </text>
+  </g>
+  <g
+     id="g86">
+    <rect
+       style="fill: #ffffff"
+       x="147.446"
+       y="300"
+       width="160"
+       height="42"
+       id="rect88" />
+    <rect
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x="147.446"
+       y="300"
+       width="160"
+       height="42"
+       id="rect90" />
+    <text
+       font-size="16"
+       style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal"
+       x="227.446"
+       y="325.85"
+       id="text92">
+      <tspan
+         x="227.446"
+         y="325.85"
+         id="tspan94">@Format(...,)</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal"
+     x="227.446"
+     y="321"
+     id="text96">
+    <tspan
+       x="227.446"
+       y="321"
+       id="tspan98" />
+  </text>
+</svg>
diff --git a/docs/manual/figures/i18n-format-category.svg b/docs/manual/figures/i18n-format-category.svg
new file mode 100644
index 0000000..1f354b3
--- /dev/null
+++ b/docs/manual/figures/i18n-format-category.svg
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="161.0625"
+   height="260.21875"
+   id="svg2">
+  <defs
+     id="defs4">
+    <marker
+       refX="0"
+       refY="0"
+       orient="auto"
+       id="Arrow2Sstart"
+       style="overflow:visible">
+      <path
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+         transform="matrix(0.3,0,0,0.3,-0.69,0)"
+         id="path4032"
+         style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+    </marker>
+    <marker
+       refX="0"
+       refY="0"
+       orient="auto"
+       id="Arrow2Mend"
+       style="overflow:visible">
+      <path
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+         transform="scale(-0.6,-0.6)"
+         id="path4029"
+         style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+    </marker>
+    <marker
+       refX="0"
+       refY="0"
+       orient="auto"
+       id="Arrow2Lstart"
+       style="overflow:visible">
+      <path
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+         transform="matrix(1.1,0,0,1.1,1.1,0)"
+         id="path4020"
+         style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+    </marker>
+    <marker
+       refX="0"
+       refY="0"
+       orient="auto"
+       id="Arrow1Lstart"
+       style="overflow:visible">
+      <path
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         transform="matrix(0.8,0,0,0.8,10,0)"
+         id="path4002"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" />
+    </marker>
+  </defs>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     transform="translate(-254.1875,-137.96916)"
+     id="layer1">
+    <rect
+       width="159.07629"
+       height="41.07629"
+       x="255.17612"
+       y="138.96678"
+       id="rect3769"
+       style="fill:#ffffff;fill-opacity:0.97682854;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <rect
+       width="159.07629"
+       height="41.07629"
+       x="255.17612"
+       y="211.34778"
+       id="rect3782"
+       style="fill:#ffffff;fill-opacity:0.97682854;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <rect
+       width="159.07629"
+       height="41.07629"
+       x="255.17612"
+       y="283.72876"
+       id="rect3795"
+       style="fill:#ffffff;fill-opacity:0.97682854;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <rect
+       width="159.07629"
+       height="41.07629"
+       x="255.17612"
+       y="356.10974"
+       id="rect3808"
+       style="fill:#ffffff;fill-opacity:0.97682854;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 334.74876,181.32105 0,30.02673"
+       id="path3812"
+       style="fill:none;stroke:#000000;stroke-width:1.95875084;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Lstart)" />
+    <path
+       d="m 334.74804,254.93582 0,28.79294"
+       id="path3814"
+       style="fill:none;stroke:#000000;stroke-width:1.91808677;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Lstart)" />
+    <path
+       d="m 334.74825,326.95833 0,29.15141"
+       id="path3816"
+       style="fill:none;stroke:#000000;stroke-width:1.92999017;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Lstart);marker-end:none" />
+    <text
+       x="308.60159"
+       y="149.79088"
+       id="text8896"
+       xml:space="preserve"
+       style="font-size:12.80000019px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Courier New;-inkscape-font-specification:Courier New"><tspan
+         x="308.60159"
+         y="149.79088"
+         id="tspan8898" /></text>
+    <text
+       x="305.94473"
+       y="164.0518"
+       id="text8900"
+       xml:space="preserve"
+       style="font-size:12.80000019px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Courier New;-inkscape-font-specification:Courier New"><tspan
+         x="305.94473"
+         y="164.0518"
+         id="tspan8902"
+         style="font-size:16px;font-weight:normal;-inkscape-font-specification:Courier New">UNUSED</tspan></text>
+    <text
+       x="301.08926"
+       y="236.4328"
+       id="text8904"
+       xml:space="preserve"
+       style="font-size:12.80000019px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Courier New;-inkscape-font-specification:Courier New"><tspan
+         x="301.08926"
+         y="236.4328"
+         id="tspan8906"
+         style="font-size:16px">GENERAL</tspan></text>
+    <text
+       x="315.52286"
+       y="308.83722"
+       id="text8908"
+       xml:space="preserve"
+       style="font-size:12.80000019px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Courier New;-inkscape-font-specification:Courier New"><tspan
+         x="315.52286"
+         y="308.83722"
+         id="tspan8910"
+         style="font-size:16px">DATE</tspan></text>
+    <text
+       x="305.80801"
+       y="381.08929"
+       id="text8912"
+       xml:space="preserve"
+       style="font-size:12.80000019px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Courier New;-inkscape-font-specification:Courier New"><tspan
+         x="305.80801"
+         y="381.08929"
+         id="tspan8914"
+         style="font-size:16px">NUMBER</tspan></text>
+  </g>
+</svg>
diff --git a/docs/manual/figures/i18n-format-type-hierarchy.svg b/docs/manual/figures/i18n-format-type-hierarchy.svg
new file mode 100644
index 0000000..960ad6c
--- /dev/null
+++ b/docs/manual/figures/i18n-format-type-hierarchy.svg
@@ -0,0 +1,354 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="653.03308"
+   height="169.46263"
+   viewBox="146 238 708.10173 185.99046"
+   id="svg3165">
+  <metadata
+     id="metadata3267">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs3265">
+    <marker
+       refX="0"
+       refY="0"
+       orient="auto"
+       id="Arrow2Mend"
+       style="overflow:visible">
+      <path
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+         transform="scale(-0.6,-0.6)"
+         id="path4711"
+         style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+    </marker>
+    <marker
+       refX="0"
+       refY="0"
+       orient="auto"
+       id="Arrow2Lstart"
+       style="overflow:visible">
+      <path
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+         transform="matrix(1.1,0,0,1.1,1.1,0)"
+         id="path4702"
+         style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+    </marker>
+    <marker
+       refX="0"
+       refY="0"
+       orient="auto"
+       id="Arrow2Lend"
+       style="overflow:visible">
+      <path
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+         transform="matrix(-1.1,0,0,-1.1,-1.1,0)"
+         id="path4705"
+         style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+    </marker>
+    <marker
+       refX="0"
+       refY="0"
+       orient="auto"
+       id="Arrow1Lend"
+       style="overflow:visible">
+      <path
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         transform="matrix(-0.8,0,0,-0.8,-10,0)"
+         id="path4687"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" />
+    </marker>
+    <marker
+       refX="0"
+       refY="0"
+       orient="auto"
+       id="Arrow1Lstart"
+       style="overflow:visible">
+      <path
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+         transform="matrix(0.8,0,0,0.8,10,0)"
+         id="path4684"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" />
+    </marker>
+  </defs>
+  <g
+     transform="translate(-71.255226,-76.322033)"
+     id="g3719">
+    <rect
+       width="162.61374"
+       height="41.984112"
+       x="469.6788"
+       y="457.30695"
+       id="rect3171"
+       style="fill:none;stroke:#7f7f7f;stroke-width:2.01588821;stroke-opacity:1" />
+    <text
+       x="550.77936"
+       y="481.73871"
+       id="text3173"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#7f7f7f;fill-opacity:1;font-family:courier new">
+      <tspan
+         x="550.77936"
+         y="481.73871"
+         id="tspan3175"
+         style="font-size:12.07283974px;fill:#7f7f7f;fill-opacity:1">@I18nFormatBottom</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(-199.09803,-230.40625)"
+     id="g3738">
+    <rect
+       width="162.61374"
+       height="41.984112"
+       x="597.52161"
+       y="469.41837"
+       id="rect3655"
+       style="fill:none;stroke:#7f7f7f;stroke-width:2.01588821;stroke-opacity:1" />
+    <text
+       x="678.23309"
+       y="493.85013"
+       id="text3209"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#7f7f7f;fill-opacity:1;font-family:courier new">
+      <tspan
+         x="678.23309"
+         y="493.85013"
+         id="tspan3211"
+         style="font-size:12.07283974px;fill:#7f7f7f;fill-opacity:1">@I18nUnknownFormat</tspan>
+    </text>
+  </g>
+  <text
+     x="218.76646"
+     y="478.2164"
+     id="text3259"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new">
+    <tspan
+       x="218.76646"
+       y="478.2164"
+       id="tspan3261" />
+  </text>
+  <g
+     transform="translate(-72.448842,-2.460269)"
+     id="g3705">
+    <rect
+       width="162.61374"
+       height="41.984112"
+       x="205.36841"
+       y="141.12973"
+       transform="translate(186.27587,171.66547)"
+       id="rect3657"
+       style="fill:none;stroke:#7f7f7f;stroke-width:2.01588821;stroke-opacity:1" />
+    <g
+       transform="translate(72.412041,83.033086)"
+       id="g3177">
+      <g
+         transform="translate(164.17706,1.3457136)"
+         id="g3645">
+        <text
+           x="236.15572"
+           y="252.84816"
+           id="text3183"
+           style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#7f7f7f;fill-opacity:1;font-family:courier new">
+          <tspan
+             x="236.15572"
+             y="252.84816"
+             id="tspan3185"
+             style="font-size:12.07283974px;fill:#7f7f7f;fill-opacity:1">@I18nInvalidFormat</tspan>
+        </text>
+      </g>
+    </g>
+  </g>
+  <g
+     transform="translate(-14.301925,-59.370974)"
+     id="g3616">
+    <g
+       transform="translate(9.4199955,69.697961)"
+       id="g3213">
+      <rect
+         width="160"
+         height="42"
+         x="155.446"
+         y="308"
+         id="rect3215"
+         style="fill:#ffffff" />
+      <rect
+         width="160"
+         height="42"
+         x="155.446"
+         y="308"
+         id="rect3217"
+         style="fill:none;stroke:#000000;stroke-width:2" />
+      <text
+         x="235.446"
+         y="333.85001"
+         id="text3219"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new">
+        <tspan
+           x="235.446"
+           y="333.85001"
+           id="tspan3221" />
+      </text>
+    </g>
+    <g
+       transform="translate(9.4199955,69.697961)"
+       id="g3239">
+      <rect
+         width="160"
+         height="42"
+         x="151.446"
+         y="304"
+         id="rect3241"
+         style="fill:#ffffff" />
+      <rect
+         width="160"
+         height="42"
+         x="151.446"
+         y="304"
+         id="rect3243"
+         style="fill:none;stroke:#000000;stroke-width:2" />
+      <text
+         x="231.446"
+         y="329.85001"
+         id="text3245"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new">
+        <tspan
+           x="231.446"
+           y="329.85001"
+           id="tspan3247" />
+      </text>
+    </g>
+    <g
+       transform="translate(9.4199955,69.697961)"
+       id="g3249">
+      <rect
+         width="160"
+         height="42"
+         x="147.446"
+         y="300"
+         id="rect3251"
+         style="fill:#ffffff" />
+      <rect
+         width="160"
+         height="42"
+         x="147.446"
+         y="300"
+         id="rect3253"
+         style="fill:none;stroke:#000000;stroke-width:2" />
+      <text
+         x="228.64856"
+         y="324.20578"
+         id="text3255"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new">
+        <tspan
+           x="228.64856"
+           y="324.20578"
+           id="tspan3257"
+           style="font-size:12.07283974px">@I18nFormat(...,)</tspan>
+      </text>
+    </g>
+  </g>
+  <g
+     transform="translate(-58.662892,-76.566131)"
+     id="g3724">
+    <rect
+       width="162.61374"
+       height="41.984112"
+       x="554.48175"
+       y="386.90106"
+       id="rect3663"
+       style="fill:none;stroke:#000000;stroke-width:2.01588821" />
+    <text
+       x="636.89099"
+       y="410.57828"
+       id="text3604"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new">
+      <tspan
+         x="636.89099"
+         y="410.57828"
+         id="tspan3606"
+         style="font-size:12.07283974px">@I18nFormatFor(&quot;#1&quot;)</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(-87.751761,-7.9347528)"
+     id="g3729">
+    <rect
+       width="162.61374"
+       height="41.984112"
+       x="782.39832"
+       y="318.26968"
+       id="rect3659"
+       style="fill:none;stroke:#000000;stroke-width:2.01588821" />
+    <g
+       transform="translate(339.57383,-14.936474)"
+       id="g3608">
+      <text
+         x="525.2337"
+         y="356.88336"
+         id="text3612"
+         style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new">
+        <tspan
+           x="525.2337"
+           y="356.88336"
+           id="tspan3614"
+           style="font-size:12.07283974px">@I18nFormatFor(&quot;#n&quot;)</tspan>
+      </text>
+    </g>
+  </g>
+  <text
+     x="665.4306"
+     y="332.00543"
+     id="text3743"
+     xml:space="preserve"
+     style="font-size:14.0483942px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Courier New;-inkscape-font-specification:Courier New"><tspan
+       x="665.4306"
+       y="332.00543"
+       id="tspan3745"
+       style="font-size:13.17036915px">...</tspan></text>
+  <path
+     d="M 302.56407,312.75491 409.30768,280.99623"
+     id="path3823"
+     style="fill:none;stroke:#000000;stroke-width:2.19506168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-mid:none;marker-end:url(#Arrow2Lend)" />
+  <path
+     d="m 423.95441,310.33493 32.59057,-29.3387"
+     id="path3825"
+     style="fill:none;stroke:#000000;stroke-width:2.19506168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow2Lend)" />
+  <path
+     d="m 548.59323,310.33493 -40.06364,-29.3387"
+     id="path3827"
+     style="fill:none;stroke:#000000;stroke-width:2.19506168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow2Lend)" />
+  <path
+     d="M 694.64655,311.7183 561.03732,279.54866"
+     id="path3829"
+     style="fill:none;stroke:#000000;stroke-width:2.19506168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow2Lend)" />
+  <path
+     d="M 400.12659,380.98491 310.56407,357.40619"
+     id="path3833"
+     style="fill:none;stroke:#000000;stroke-width:2.19506168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow2Lend)" />
+  <path
+     d="M 456.32291,380.98491 424.17649,352.31904"
+     id="path3835"
+     style="fill:none;stroke:#000000;stroke-width:2.19506168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow2Lend)" />
+  <path
+     d="m 508.80258,380.98491 39.51764,-28.66587"
+     id="path3839"
+     style="fill:none;stroke:#000000;stroke-width:2.19506168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow2Lend)" />
+  <path
+     d="M 561.03732,382.61685 694.64655,350.75069"
+     id="path3841"
+     style="fill:none;stroke:#000000;stroke-width:2.19506168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow2Lend)" />
+</svg>
diff --git a/docs/manual/figures/initialization.svg b/docs/manual/figures/initialization.svg
new file mode 100644
index 0000000..c87131d
--- /dev/null
+++ b/docs/manual/figures/initialization.svg
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
+<svg width="23.5cm" height="6.5cm" viewBox="439 25 932 258" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <g>
+    <rect style="fill: #ffffff" x="539.424" y="160" width="241.15" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="539.424" y="160" width="241.15" height="42"/>
+    <text font-size="13" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="659.999" y="185.85">
+      <tspan x="659.999" y="185.85">@UnknownInitialization Date</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="504.176" y="35" width="311.65" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="504.176" y="35" width="311.65" height="42"/>
+    <text font-size="13" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="660.001" y="60.85">
+      <tspan x="660.001" y="60.85">@UnknownInitialization Object</tspan>
+    </text>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="525" y1="100" x2="645.213" y2="79.5193"/>
+    <polygon style="fill: #000000" points="646.389,86.4198 659.014,77.168 644.037,72.6187 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="646.389,86.4198 659.014,77.168 644.037,72.6187 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="660" y1="160" x2="660" y2="92"/>
+    <polygon style="fill: #000000" points="667,92 660,78 653,92 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="667,92 660,78 653,92 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="797" y1="102" x2="674.756" y2="79.6928"/>
+    <polygon style="fill: #000000" points="676.013,72.8065 660.984,77.1795 673.5,86.579 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="676.013,72.8065 660.984,77.1795 673.5,86.579 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="525" y1="224.999" x2="525" y2="157"/>
+    <polygon style="fill: #000000" points="532,157 525,143 518,157 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="532,157 525,143 518,157 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="795.038" y1="224.036" x2="796.632" y2="158.995"/>
+    <polygon style="fill: #000000" points="803.63,159.167 796.975,145 789.635,158.824 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="803.63,159.167 796.975,145 789.635,158.824 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="525" y1="226" x2="645.232" y2="204.626"/>
+    <polygon style="fill: #000000" points="646.457,211.517 659.015,202.175 644.006,197.734 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="646.457,211.517 659.015,202.175 644.006,197.734 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="794.5" y1="225" x2="674.785" y2="204.528"/>
+    <polygon style="fill: #000000" points="675.965,197.629 660.986,202.169 673.605,211.428 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="675.965,197.629 660.986,202.169 673.605,211.428 "/>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="440.126" y="100" width="169.75" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="440.126" y="100" width="169.75" height="42"/>
+    <text font-size="13" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="525.001" y="125.85">
+      <tspan x="525.001" y="125.85">@Initialized Object</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="682.4" y="102" width="229.2" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="682.4" y="102" width="229.2" height="42"/>
+    <text font-size="13" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="797" y="127.85">
+      <tspan x="797" y="127.85">@UnderInitialization Object</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="445" y="226" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="445" y="226" width="160" height="42"/>
+    <text font-size="13" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="525" y="251.85">
+      <tspan x="525" y="251.85">@Initialized Date</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="680" y="225" width="229" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="680" y="225" width="229" height="42"/>
+    <text font-size="13" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="794.5" y="250.85">
+      <tspan x="794.5" y="250.85">@UnderInitialization Date</tspan>
+    </text>
+  </g>
+  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal" x="660" y="56">
+    <tspan x="660" y="56"></tspan>
+  </text>
+  <g>
+    <rect style="fill: #ffffff" x="995.5" y="26.1" width="374" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="995.5" y="26.1" width="374" height="42"/>
+    <text font-size="13" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="1182.5" y="51.95">
+      <tspan x="1182.5" y="51.95">@UnderInitialization(Object.class) Giraffe</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="996.05" y="97.7" width="373.5" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="996.05" y="97.7" width="373.5" height="42"/>
+    <text font-size="13" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="1182.8" y="123.55">
+      <tspan x="1182.8" y="123.55">@UnderInitialization(Vertebrate.class) Giraffe</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="995.5" y="167.3" width="374.052" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="995.5" y="167.3" width="374.052" height="42"/>
+    <text font-size="13" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="1182.53" y="193.15">
+      <tspan x="1182.53" y="193.15">@UnderInitialization(Mammal.class) Giraffe</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="996" y="239.9" width="373.552" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="996" y="239.9" width="373.552" height="42"/>
+    <text font-size="13" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="1182.78" y="265.75">
+      <tspan x="1182.78" y="265.75">@UnderInitialization(Giraffe.class) Giraffe</tspan>
+    </text>
+  </g>
+  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal" x="1182.5" y="47.1">
+    <tspan x="1182.5" y="47.1"></tspan>
+  </text>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1182.8" y1="97.7" x2="1182.65" y2="83.0992"/>
+    <polygon style="fill: #000000" points="1189.65,83.0283 1182.51,69.0999 1175.65,83.1702 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1189.65,83.0283 1182.51,69.0999 1175.65,83.1702 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1182.53" y1="167.3" x2="1182.65" y2="154.699"/>
+    <polygon style="fill: #000000" points="1189.65,154.769 1182.79,140.7 1175.65,154.63 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1189.65,154.769 1182.79,140.7 1175.65,154.63 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1182.78" y1="239.9" x2="1182.65" y2="224.299"/>
+    <polygon style="fill: #000000" points="1189.65,224.242 1182.53,210.3 1175.65,224.357 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1189.65,224.242 1182.53,210.3 1175.65,224.357 "/>
+  </g>
+</svg>
diff --git a/docs/manual/figures/initializedfields.svg b/docs/manual/figures/initializedfields.svg
new file mode 100644
index 0000000..23fac2a
--- /dev/null
+++ b/docs/manual/figures/initializedfields.svg
@@ -0,0 +1,343 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="12.425153cm"
+   height="5.9943547cm"
+   viewBox="358 100 342.66891 294.53207"
+   id="svg3577"
+   version="1.1"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
+   sodipodi:docname="initializedfields.svg">
+  <metadata
+     id="metadata3693">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs3691" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1446"
+     inkscape:window-height="1119"
+     id="namedview3689"
+     showgrid="false"
+     inkscape:zoom="1.6651112"
+     inkscape:cx="309.45215"
+     inkscape:cy="142.59829"
+     inkscape:window-x="1100"
+     inkscape:window-y="71"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg3577"
+     inkscape:connector-spacing="5"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0%;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="699.48737"
+     y="256.94293"
+     id="text3619">
+    <tspan
+       x="699.48737"
+       y="256.94293"
+       id="tspan3621" />
+  </text>
+  <g
+     id="g3657"
+     transform="translate(69.711358,149.42398)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3659" />
+    <polygon
+       style="fill:#000000"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       id="polygon3661" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       id="polygon3663" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0%;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="699.48737"
+     y="256.94293"
+     id="text3673">
+    <tspan
+       x="699.48737"
+       y="256.94293"
+       id="tspan3675" />
+  </text>
+  <text
+     font-size="15.8044"
+     style="font-style:normal;font-weight:normal;font-size:15.80440044px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1"
+     x="531.90643"
+     y="376.20847"
+     id="text3563">
+    <tspan
+       style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1"
+       x="531.90643"
+       y="376.20847"
+       id="tspan3565">@InitializedfieldsBottom</tspan>
+  </text>
+  <path
+     inkscape:connector-curvature="2"
+     inkscape:connector-type="polyline"
+     id="path4201"
+     d="m 612.42097,233.0249 v 0"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <g
+     transform="translate(27.004371,154.98008)"
+     id="g4844">
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.09047127;stroke-miterlimit:4;stroke-dasharray:none"
+       x="197.62096"
+       y="31.041571"
+       width="297.06793"
+       height="43.294464"
+       id="rect4846" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="348.12827"
+       y="55.567112"
+       id="text4848">
+      <tspan
+         x="348.12827"
+         y="55.567112"
+         id="tspan4850"
+         style="font-size:21.16666603px">@InitializedFields(&quot;a&quot;)</tspan>
+    </text>
+  </g>
+  <g
+     id="g4330"
+     transform="translate(334.93234,154.98008)">
+    <rect
+       id="rect4332"
+       height="43.396053"
+       width="299.95517"
+       y="30.990776"
+       x="199.15279"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.09704244;stroke-miterlimit:4;stroke-dasharray:none" />
+    <text
+       id="text4334"
+       y="55.567112"
+       x="348.12827"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       font-size="15.8044">
+      <tspan
+         style="font-size:21.16666603px"
+         id="tspan4336"
+         y="55.567112"
+         x="348.12827">@Initializedfields(&quot;b&quot;)</tspan>
+    </text>
+  </g>
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="m 509.35117,181.49 v 0"
+     id="path4342"
+     inkscape:connector-type="polyline"
+     inkscape:connector-curvature="2" />
+  <g
+     transform="translate(185.90891,238.88266)"
+     id="g4344">
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.51688433;stroke-miterlimit:4;stroke-dasharray:none"
+       x="152.46198"
+       y="31.188354"
+       width="390.81888"
+       height="43.000896"
+       id="rect4346" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="348.12827"
+       y="55.567112"
+       id="text4348">
+      <tspan
+         x="348.12827"
+         y="55.567112"
+         id="tspan4350"
+         style="font-size:21.16666603px">@Initializedfields({&quot;a&quot;, &quot;b&quot;})</tspan>
+    </text>
+  </g>
+  <g
+     id="g4375"
+     transform="rotate(54.593234,489.06143,173.04598)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line4377" />
+    <polygon
+       style="fill:#000000"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       id="polygon4379" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       id="polygon4381" />
+  </g>
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="m 450.54442,165.38325 -30.10954,20.41858 z"
+     id="path4397"
+     inkscape:connector-curvature="0" />
+  <g
+     id="g4427"
+     transform="matrix(-1,0,0,1,940.9986,-84.074335)">
+    <g
+       id="g4403"
+       transform="rotate(54.593234,326.46831,257.27778)">
+      <line
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+         x1="460.5"
+         y1="200"
+         x2="460.5"
+         y2="173.73599"
+         id="line4405" />
+      <polygon
+         style="fill:#000000"
+         points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+         id="polygon4407" />
+      <polygon
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+         points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+         id="polygon4409" />
+    </g>
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 453.28092,331.9049 -30.10954,20.41858 z"
+       id="path4423"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path4425"
+       d="M 451.78281,332.20452 421.67327,352.6231 Z"
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  </g>
+  <g
+     transform="translate(120.94486,-84.074335)"
+     id="g4443">
+    <g
+       transform="rotate(54.593234,326.46831,257.27778)"
+       id="g4445">
+      <line
+         id="line4447"
+         y2="173.73599"
+         x2="460.5"
+         y1="200"
+         x1="460.5"
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+      <polygon
+         id="polygon4449"
+         points="455.5,176.236 460.5,166.236 465.5,176.236 460.5,173.736 "
+         style="fill:#000000" />
+      <polygon
+         id="polygon4451"
+         points="455.5,176.236 460.5,166.236 465.5,176.236 460.5,173.736 "
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       id="path4453"
+       d="m 453.28092,331.9049 -30.10954,20.41858 z"
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 451.78281,332.20452 421.67327,352.6231 Z"
+       id="path4455"
+       inkscape:connector-curvature="0" />
+  </g>
+  <rect
+     style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2.50759959;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     x="374.14365"
+     y="349.59464"
+     width="318.18719"
+     height="43.68364"
+     id="rect3561" />
+  <g
+     transform="matrix(-1,0,0,1,1024.0492,-167.12518)"
+     id="g4457">
+    <g
+       transform="rotate(54.593234,326.46831,257.27778)"
+       id="g4459">
+      <line
+         id="line4461"
+         y2="173.73599"
+         x2="460.5"
+         y1="200"
+         x1="460.5"
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+      <polygon
+         id="polygon4463"
+         points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+         style="fill:#000000" />
+      <polygon
+         id="polygon4465"
+         points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       id="path4467"
+       d="m 453.28092,331.9049 -30.10954,20.41858 z"
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 451.78281,332.20452 421.67327,352.6231 Z"
+       id="path4469"
+       inkscape:connector-curvature="0" />
+  </g>
+  <g
+     transform="translate(185.47803,69.558943)"
+     id="g4844-3">
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.24451447;stroke-miterlimit:4;stroke-dasharray:none"
+       x="217.2141"
+       y="31.063314"
+       width="261.54846"
+       height="43.250977"
+       id="rect4846-6" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="348.12827"
+       y="55.567112"
+       id="text4848-7">
+      <tspan
+         x="348.12827"
+         y="55.567112"
+         id="tspan4850-5"
+         style="font-size:21.16666603px">@InitializedFields()</tspan>
+    </text>
+  </g>
+</svg>
diff --git a/docs/manual/figures/interning.svg b/docs/manual/figures/interning.svg
new file mode 100644
index 0000000..a625feb
--- /dev/null
+++ b/docs/manual/figures/interning.svg
@@ -0,0 +1,362 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="14.756812cm"
+   height="5.8732452cm"
+   viewBox="639 459 564.97509 215.35233"
+   version="1.1"
+   id="svg74"
+   sodipodi:docname="interning.svg"
+   inkscape:version="0.92.1 r15371">
+  <metadata
+     id="metadata80">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs78" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1176"
+     id="namedview76"
+     showgrid="false"
+     inkscape:zoom="2.5677183"
+     inkscape:cx="241.11424"
+     inkscape:cy="140.9272"
+     inkscape:window-x="0"
+     inkscape:window-y="24"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg74"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <g
+     id="g10"
+     transform="translate(162.97506,-3.6428527)">
+    <rect
+       style="fill:#ffffff"
+       x="880"
+       y="520"
+       width="160"
+       height="42"
+       id="rect2" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x="880"
+       y="520"
+       width="160"
+       height="42"
+       id="rect4" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="960"
+       y="545.84998"
+       id="text8">
+      <tspan
+         x="960"
+         y="545.84998"
+         id="tspan6">Date</tspan>
+    </text>
+  </g>
+  <g
+     id="g20"
+     transform="translate(162.97506,-3.6428527)">
+    <rect
+       style="fill:#ffffff"
+       x="640"
+       y="520"
+       width="160"
+       height="42"
+       id="rect12" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x="640"
+       y="520"
+       width="160"
+       height="42"
+       id="rect14" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="720"
+       y="545.84998"
+       id="text18">
+      <tspan
+         x="720"
+         y="545.84998"
+         id="tspan16">@Interned Object</tspan>
+    </text>
+  </g>
+  <g
+     id="g30"
+     transform="translate(162.97506,-3.6428527)">
+    <rect
+       style="fill:#ffffff"
+       x="760"
+       y="460"
+       width="160"
+       height="42"
+       id="rect22" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x="760"
+       y="460"
+       width="160"
+       height="42"
+       id="rect24" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="840"
+       y="485.85001"
+       id="text28">
+      <tspan
+         x="840"
+         y="485.85001"
+         id="tspan26">Object</tspan>
+    </text>
+  </g>
+  <g
+     id="g38"
+     transform="translate(162.97506,-3.6428527)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="720"
+       y1="520"
+       x2="825.16602"
+       y2="504.22501"
+       id="line32" />
+    <polygon
+       style="fill:#000000"
+       points="824.128,497.303 826.204,511.148 839.011,502.148 "
+       id="polygon34" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="824.128,497.303 826.204,511.148 839.011,502.148 "
+       id="polygon36" />
+  </g>
+  <g
+     id="g46"
+     transform="translate(162.97506,-3.6428527)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="960"
+       y1="520"
+       x2="854.83398"
+       y2="504.22501"
+       id="line40" />
+    <polygon
+       style="fill:#000000"
+       points="853.796,511.148 855.872,497.303 840.989,502.148 "
+       id="polygon42" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="853.796,511.148 855.872,497.303 840.989,502.148 "
+       id="polygon44" />
+  </g>
+  <g
+     id="g54"
+     transform="translate(162.97506,-3.6428527)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="840"
+       y1="580"
+       x2="734.83398"
+       y2="564.22498"
+       id="line48" />
+    <polygon
+       style="fill:#000000"
+       points="733.796,571.148 735.872,557.303 720.989,562.148 "
+       id="polygon50" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="733.796,571.148 735.872,557.303 720.989,562.148 "
+       id="polygon52" />
+  </g>
+  <g
+     id="g62"
+     transform="translate(162.97506,-3.6428527)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="840"
+       y1="580"
+       x2="945.16602"
+       y2="564.22498"
+       id="line56" />
+    <polygon
+       style="fill:#000000"
+       points="944.128,557.303 946.204,571.148 959.011,562.148 "
+       id="polygon58" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="944.128,557.303 946.204,571.148 959.011,562.148 "
+       id="polygon60" />
+  </g>
+  <g
+     id="g72"
+     transform="translate(162.97506,-3.6428527)">
+    <rect
+       style="fill:#ffffff"
+       x="760"
+       y="580"
+       width="160"
+       height="42"
+       id="rect64" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x="760"
+       y="580"
+       width="160"
+       height="42"
+       id="rect66" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="840"
+       y="605.84998"
+       id="text70">
+      <tspan
+         x="840"
+         y="605.84998"
+         id="tspan68">@Interned Date</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(39.789041,57.218563)"
+     id="g20-0">
+    <rect
+       style="fill:#ffffff"
+       x="640"
+       y="520"
+       width="160"
+       height="42"
+       id="rect12-5" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2.44124079"
+       x="600.43158"
+       y="520.11456"
+       width="239.69342"
+       height="41.770958"
+       id="rect14-5" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="720"
+       y="545.84998"
+       id="text18-2">
+      <tspan
+         x="720"
+         y="545.84998"
+         id="tspan16-9">@InternedDistinct Object</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(39.789041,57.218563)"
+     id="g38-0">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="720"
+       y1="520"
+       x2="825.16602"
+       y2="504.22501"
+       id="line32-2" />
+    <polygon
+       style="fill:#000000"
+       points="826.204,511.148 839.011,502.148 824.128,497.303 "
+       id="polygon34-8" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="826.204,511.148 839.011,502.148 824.128,497.303 "
+       id="polygon36-3" />
+  </g>
+  <g
+     transform="translate(39.789041,57.218563)"
+     id="g54-8">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="840"
+       y1="580"
+       x2="734.83398"
+       y2="564.22498"
+       id="line48-0" />
+    <polygon
+       style="fill:#000000"
+       points="735.872,557.303 720.989,562.148 733.796,571.148 "
+       id="polygon50-4" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="735.872,557.303 720.989,562.148 733.796,571.148 "
+       id="polygon52-0" />
+  </g>
+  <g
+     transform="translate(39.789041,57.218563)"
+     id="g62-9">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="840"
+       y1="580"
+       x2="945.16602"
+       y2="564.22498"
+       id="line56-1" />
+    <polygon
+       style="fill:#000000"
+       points="946.204,571.148 959.011,562.148 944.128,557.303 "
+       id="polygon58-9" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="946.204,571.148 959.011,562.148 944.128,557.303 "
+       id="polygon60-6" />
+  </g>
+  <g
+     transform="matrix(1.0523765,0,0,1,-1.6771392,57.218563)"
+     id="g72-2">
+    <rect
+       style="fill:#ffffff"
+       x="760"
+       y="580"
+       width="160"
+       height="42"
+       id="rect64-5" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2.32170033"
+       x="731.75653"
+       y="580.16083"
+       width="217.27594"
+       height="41.678299"
+       id="rect66-4" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="840"
+       y="605.84998"
+       id="text70-4">
+      <tspan
+         x="840"
+         y="605.84998"
+         id="tspan68-9">@InternedDistinct Date</tspan>
+    </text>
+  </g>
+</svg>
diff --git a/docs/manual/figures/lock-guardedby.svg b/docs/manual/figures/lock-guardedby.svg
new file mode 100644
index 0000000..c02cec7
--- /dev/null
+++ b/docs/manual/figures/lock-guardedby.svg
@@ -0,0 +1,446 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="682.53998"
+   height="194.88"
+   viewBox="328 238 765.22915 204.99801"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="lock-guardedby.svg">
+  <metadata
+     id="metadata198">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs196" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="978"
+     id="namedview194"
+     showgrid="false"
+     inkscape:zoom="1.7467478"
+     inkscape:cx="306.15895"
+     inkscape:cy="41.341013"
+     inkscape:window-x="0"
+     inkscape:window-y="31"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="g66-6" />
+  <g
+     id="g4"
+     transform="translate(2.2396156,-0.00294728)">
+    <rect
+       style="fill:#ffffff"
+       x="329.198"
+       y="320.056"
+       width="160"
+       height="42"
+       id="rect6" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x="329.198"
+       y="320.056"
+       width="160"
+       height="42"
+       id="rect8" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+       x="409.198"
+       y="345.90601"
+       id="text10">
+      <tspan
+         x="409.198"
+         y="345.90601"
+         id="tspan12">@GuardedBy({})</tspan>
+    </text>
+  </g>
+  <g
+     id="g58"
+     transform="matrix(0.61098909,0,0,1,170.58624,-0.00294728)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="1229.63"
+       y2="363.33701"
+       id="line60" />
+    <polygon
+       style="fill:#000000"
+       points="1243.58,362.089 1229.01,356.365 1230.26,370.309 "
+       id="polygon62" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="1243.58,362.089 1229.01,356.365 1230.26,370.309 "
+       id="polygon64" />
+  </g>
+  <g
+     id="g66"
+     transform="translate(-147.99294,-0.00294728)"
+     style="stroke:#000000;stroke-opacity:1">
+    <rect
+       style="fill:#ffffff;stroke:#000000;stroke-opacity:1"
+       x="740"
+       y="400"
+       width="160"
+       height="42"
+       id="rect68" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-opacity:1"
+       x="740"
+       y="400"
+       width="160"
+       height="42"
+       id="rect70" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new;stroke:none;stroke-opacity:1"
+       x="820"
+       y="425.85001"
+       id="text72">
+      <tspan
+         x="820"
+         y="425.85001"
+         id="tspan74"
+         style="fill:#000000;stroke:none;stroke-opacity:1">@GuardedByBottom</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new"
+     x="820"
+     y="260.99786"
+     id="text76">
+    <tspan
+       x="820"
+       y="260.99786"
+       id="tspan78" />
+  </text>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new"
+     x="409.198"
+     y="341.05386"
+     id="text80">
+    <tspan
+       x="409.198"
+       y="341.05386"
+       id="tspan82" />
+  </text>
+  <g
+     id="g84"
+     transform="translate(2.2396156,-0.00294728)">
+    <rect
+       style="fill:#ffffff"
+       x="499.97"
+       y="320.09201"
+       width="160"
+       height="41.732498"
+       id="rect86" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x="499.97"
+       y="320.09201"
+       width="160"
+       height="41.732498"
+       id="rect88" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+       x="579.96997"
+       y="345.80801"
+       id="text90">
+      <tspan
+         x="579.96997"
+         y="345.80801"
+         id="tspan92">@GuardedBy(&quot;a&quot;)</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new"
+     x="579.96997"
+     y="340.95587"
+     id="text94">
+    <tspan
+       x="579.96997"
+       y="340.95587"
+       id="tspan96" />
+  </text>
+  <g
+     id="g98"
+     transform="translate(-65.028862,-0.00294728)">
+    <rect
+       style="fill:#ffffff"
+       x="739.44598"
+       y="320.09201"
+       width="160"
+       height="41.732498"
+       id="rect100" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x="739.44598"
+       y="320.09201"
+       width="160"
+       height="41.732498"
+       id="rect102" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+       x="819.44598"
+       y="345.80801"
+       id="text104">
+      <tspan
+         x="819.44598"
+         y="345.80801"
+         id="tspan106">@GuardedBy(&quot;b&quot;)</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new"
+     x="819.44598"
+     y="340.95587"
+     id="text108">
+    <tspan
+       x="819.44598"
+       y="340.95587"
+       id="tspan110" />
+  </text>
+  <g
+     id="g128"
+     transform="matrix(0.92931178,0,0,1,-89.991164,-0.00294728)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="589.58502"
+       y2="363.353"
+       id="line130" />
+    <polygon
+       style="fill:#000000"
+       points="582.178,362.175 592.84,358.808 589.585,363.353 591.269,368.684 "
+       id="polygon132" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="582.178,362.175 592.84,358.808 589.585,363.353 591.269,368.684 "
+       id="polygon134" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new"
+     x="409.198"
+     y="341.05386"
+     id="text144">
+    <tspan
+       x="409.198"
+       y="341.05386"
+       id="tspan146" />
+  </text>
+  <g
+     id="g168"
+     transform="translate(-65.028862,-0.00294728)">
+    <rect
+       style="fill:#ffffff"
+       x="910.43402"
+       y="320.57199"
+       width="160"
+       height="41.732498"
+       id="rect170" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x="910.43402"
+       y="320.57199"
+       width="215.19875"
+       height="41.732513"
+       id="rect172" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+       x="1017.3417"
+       y="346.28799"
+       id="text174">
+      <tspan
+         x="1017.3417"
+         y="346.28799"
+         id="tspan176">@GuardedBy({&quot;a&quot;,&quot;b&quot;})</tspan>
+    </text>
+  </g>
+  <g
+     id="g186"
+     transform="matrix(0.55513772,0,0,1,216.34935,-0.00294728)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="980.92798"
+       y2="364.40701"
+       id="line188" />
+    <polygon
+       style="fill:#000000"
+       points="980.928,364.407 977.407,360.064 988.251,362.787 979.566,369.828 "
+       id="polygon190" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="980.928,364.407 977.407,360.064 988.251,362.787 979.566,369.828 "
+       id="polygon192" />
+  </g>
+  <g
+     transform="translate(-167.53712,-158.70237)"
+     id="g66-6">
+    <rect
+       style="fill:#ffffff"
+       x="740"
+       y="400"
+       width="160"
+       height="42"
+       id="rect68-0" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2.1243825;stroke-opacity:1"
+       x="749.69"
+       y="-441.93784"
+       width="181.05629"
+       height="41.875622"
+       id="rect70-3"
+       transform="scale(1,-1)" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+       x="840.18079"
+       y="425.85001"
+       id="text72-9">
+      <tspan
+         x="840.18079"
+         y="425.85001"
+         id="tspan74-0"
+         style="fill:#000000">@GuardedByUnknown</tspan>
+    </text>
+  </g>
+  <g
+     id="g58-5"
+     transform="matrix(-0.51494794,0,0,0.98010942,1345.6669,-72.390198)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="1229.63"
+       y2="363.33701"
+       id="line60-4" />
+    <polygon
+       style="fill:#000000"
+       points="1229.01,356.365 1230.26,370.309 1243.58,362.089 "
+       id="polygon62-0" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="1229.01,356.365 1230.26,370.309 1243.58,362.089 "
+       id="polygon64-7" />
+  </g>
+  <g
+     id="g186-4"
+     transform="matrix(-0.40806447,0,0,0.99800889,1093.9352,-78.824065)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="980.92798"
+       y2="364.40701"
+       id="line188-8" />
+    <polygon
+       style="fill:#000000"
+       points="977.407,360.064 988.251,362.787 979.566,369.828 980.928,364.407 "
+       id="polygon190-5" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="977.407,360.064 988.251,362.787 979.566,369.828 980.928,364.407 "
+       id="polygon192-7" />
+  </g>
+  <g
+     id="g128-9-4"
+     transform="matrix(-0.83627951,0,0,0.98528777,1137.4596,-74.271689)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="589.58502"
+       y2="363.353"
+       id="line130-2-6" />
+    <polygon
+       style="fill:#000000"
+       points="592.84,358.808 589.585,363.353 591.269,368.684 582.178,362.175 "
+       id="polygon132-6-3" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="592.84,358.808 589.585,363.353 591.269,368.684 582.178,362.175 "
+       id="polygon134-1-0" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+     x="1083.5023"
+     y="344.13104"
+     id="text154">
+    <tspan
+       x="1083.5023"
+       y="344.13104"
+       id="tspan156">...</tspan>
+  </text>
+  <g
+     id="g186-2"
+     transform="matrix(-0.55513772,0,0,1,1123.2398,-0.25815538)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="980.92798"
+       y2="364.40701"
+       id="line188-86" />
+    <polygon
+       style="fill:#000000"
+       points="977.407,360.064 988.251,362.787 979.566,369.828 980.928,364.407 "
+       id="polygon190-4" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="977.407,360.064 988.251,362.787 979.566,369.828 980.928,364.407 "
+       id="polygon192-2" />
+  </g>
+  <g
+     id="g186-4-0"
+     transform="matrix(0.40806447,0,0,0.99800889,244.18996,-78.440616)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="980.92798"
+       y2="364.40701"
+       id="line188-8-6" />
+    <polygon
+       style="fill:#000000"
+       points="980.928,364.407 977.407,360.064 988.251,362.787 979.566,369.828 "
+       id="polygon190-5-3" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="980.928,364.407 977.407,360.064 988.251,362.787 979.566,369.828 "
+       id="polygon192-7-3" />
+  </g>
+</svg>
diff --git a/docs/manual/figures/lowerbound.svg b/docs/manual/figures/lowerbound.svg
new file mode 100644
index 0000000..e2e1875
--- /dev/null
+++ b/docs/manual/figures/lowerbound.svg
@@ -0,0 +1,296 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="4.8510003cm"
+   height="7.4877024cm"
+   viewBox="358 100 152.46 374.38511"
+   id="svg3577"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="lowerbound.svg">
+  <metadata
+     id="metadata3693">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs3691" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1280"
+     inkscape:window-height="698"
+     id="namedview3689"
+     showgrid="false"
+     inkscape:zoom="1.1774114"
+     inkscape:cx="43.459633"
+     inkscape:cy="190.04784"
+     inkscape:window-x="0"
+     inkscape:window-y="1"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="g3375"
+     inkscape:connector-spacing="5"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <g
+     id="g3589"
+     transform="translate(-27.314388,-18.999996)">
+    <rect
+       style="fill:#ffffff"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3591" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2.18256378;stroke-opacity:1"
+       x="340.9155"
+       y="120.09128"
+       width="240.36746"
+       height="43.817436"
+       id="rect3593" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:14.32277775px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1"
+       x="460.5"
+       y="146.772"
+       id="text3595">
+      <tspan
+         x="460.5"
+         y="146.772"
+         id="tspan3597"
+         style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1">@LowerBoundUnknown</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="586.80353"
+     y="257.31573"
+     id="text3619">
+    <tspan
+       x="586.80353"
+       y="257.31573"
+       id="tspan3621" />
+  </text>
+  <g
+     id="g3657"
+     transform="translate(-27.314388,-17.999786)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3659" />
+    <polygon
+       style="fill:#000000"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       id="polygon3661" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       id="polygon3663" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="586.80353"
+     y="257.31573"
+     id="text3673">
+    <tspan
+       x="586.80353"
+       y="257.31573"
+       id="tspan3675" />
+  </text>
+  <rect
+     style="fill:#ffffff"
+     x="332.68564"
+     y="347.00104"
+     width="201"
+     height="44"
+     id="rect3559" />
+  <rect
+     style="fill:#000000;fill-opacity:0;stroke:#7f7f7f;stroke-width:1.65556252;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     x="364.87241"
+     y="346.82867"
+     width="136.62643"
+     height="44.344681"
+     id="rect3561" />
+  <text
+     font-size="15.8044"
+     style="font-style:normal;font-weight:normal;font-size:15.80440044px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1"
+     x="433.18561"
+     y="373.77304"
+     id="text3563">
+    <tspan
+       style="font-size:21.16666603px;fill:#000000;fill-opacity:1"
+       x="433.18561"
+       y="373.77304"
+       id="tspan3565">@Positive</tspan>
+  </text>
+  <g
+     id="g3631"
+     transform="translate(-7.1410873,149.91457)">
+    <rect
+       id="rect3583"
+       height="42.654793"
+       width="214.14209"
+       y="33.758381"
+       x="332.6564"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:0.91046369;stroke-miterlimit:4;stroke-dasharray:none" />
+    <text
+       id="text3585"
+       y="56.765598"
+       x="441.76974"
+       style="font-style:normal;font-weight:normal;font-size:15.80440044px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       font-size="15.8044">
+      <tspan
+         id="tspan3587"
+         y="56.765598"
+         x="441.76974"
+         style="font-size:21.16666603px">@GTENegativeOne</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(-27.314398,146.00089)"
+     id="g3590">
+    <line
+       id="line3592"
+       y2="173.73599"
+       x2="460.5"
+       y1="200"
+       x1="460.5"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    <polygon
+       id="polygon3594"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       style="fill:#000000" />
+    <polygon
+       id="polygon3596"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+  </g>
+  <g
+     transform="translate(-36.110367,231.91491)"
+     id="g4844">
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.05518711;stroke-miterlimit:4;stroke-dasharray:none"
+       x="374.85611"
+       y="33.420906"
+       width="187.6813"
+       height="43.32975"
+       id="rect4846" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="469.17535"
+       y="56.765598"
+       id="text4848">
+      <tspan
+         x="469.17535"
+         y="56.765598"
+         id="tspan4850"
+         style="font-size:21.16666603px">@NonNegative</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(-27.314388,64.000554)"
+     id="g4856">
+    <line
+       id="line4858"
+       y2="173.73599"
+       x2="460.5"
+       y1="200"
+       x1="460.5"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    <polygon
+       id="polygon4860"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       style="fill:#000000" />
+    <polygon
+       id="polygon4862"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+  </g>
+  <path
+     style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.19848609"
+     d=""
+     id="path5762"
+     inkscape:connector-curvature="0" />
+  <path
+     style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.29962152"
+     d="m 364.20294,368.94265 0,-22.92105 69.06275,0 69.06277,0 0,22.92105 0,22.92104 -69.06277,0 -69.06275,0 0,-22.92104 z m 136.62741,0 0,-21.42294 -67.56466,0 -67.56465,0 0,21.42294 0,21.42293 67.56465,0 67.56466,0 0,-21.42293 z"
+     id="path5764"
+     inkscape:connector-curvature="0" />
+  <g
+     transform="translate(-24.917416,308.18666)"
+     id="g3375">
+    <rect
+       id="rect3377"
+       height="44"
+       width="201"
+       y="120"
+       x="360"
+       style="fill:#ffffff" />
+    <rect
+       id="rect3379"
+       height="43.817436"
+       width="240.36746"
+       y="121.28976"
+       x="338.51852"
+       style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2.18256378;stroke-opacity:1" />
+    <text
+       id="text3381"
+       y="146.772"
+       x="458.10303"
+       style="font-style:normal;font-weight:normal;font-size:14.32277775px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1"
+       font-size="15.8044">
+      <tspan
+         style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1"
+         id="tspan3383"
+         y="146.772"
+         x="458.10303">@LowerBoundBottom</tspan>
+    </text>
+  </g>
+  <g
+     id="g3385"
+     transform="translate(-28.512884,226.29944)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3387" />
+    <polygon
+       style="fill:#000000"
+       points="455.5,176.236 460.5,166.236 465.5,176.236 460.5,173.736 "
+       id="polygon3389" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="455.5,176.236 460.5,166.236 465.5,176.236 460.5,173.736 "
+       id="polygon3391" />
+  </g>
+</svg>
diff --git a/docs/manual/figures/map-key-keyfor.svg b/docs/manual/figures/map-key-keyfor.svg
new file mode 100644
index 0000000..da4dd72
--- /dev/null
+++ b/docs/manual/figures/map-key-keyfor.svg
@@ -0,0 +1,468 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="486.06232"
+   height="327.95752"
+   viewBox="328 238 544.94467 344.98492"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="map-key-keyfor.svg">
+  <metadata
+     id="metadata198">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs196" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="978"
+     id="namedview194"
+     showgrid="false"
+     inkscape:zoom="1.2351372"
+     inkscape:cx="90.550785"
+     inkscape:cy="99.683257"
+     inkscape:window-x="0"
+     inkscape:window-y="31"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg2"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <g
+     id="g24"
+     transform="translate(-366.86329,-12.350945)">
+    <rect
+       style="fill:#ffffff"
+       x="740"
+       y="240"
+       width="160"
+       height="42"
+       id="rect26" />
+    <rect
+       style="fill:none;stroke:#7f7f7f;stroke-width:2"
+       x="740"
+       y="240"
+       width="160"
+       height="42"
+       id="rect28" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#7f7f7f;font-family:courier new"
+       x="820"
+       y="265.85001"
+       id="text30">
+      <tspan
+         x="820"
+         y="265.85001"
+         id="tspan32">@UnknownKeyFor</tspan>
+    </text>
+  </g>
+  <g
+     id="g66"
+     transform="translate(-366.86329,151.33586)">
+    <rect
+       style="fill:#ffffff"
+       x="740"
+       y="400"
+       width="160"
+       height="42"
+       id="rect68" />
+    <rect
+       style="fill:none;stroke:#7f7f7f;stroke-width:2"
+       x="740"
+       y="400"
+       width="160"
+       height="42"
+       id="rect70" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#7f7f7f;font-family:courier new"
+       x="820"
+       y="425.85001"
+       id="text72">
+      <tspan
+         x="820"
+         y="425.85001"
+         id="tspan74">@KeyForBottom</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new"
+     x="453.13672"
+     y="425.78732"
+     id="text76">
+    <tspan
+       x="453.13672"
+       y="425.78732"
+       id="tspan78" />
+  </text>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new"
+     x="42.334709"
+     y="505.84366"
+     id="text80">
+    <tspan
+       x="42.334709"
+       y="505.84366"
+       id="tspan82" />
+  </text>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new"
+     x="213.10667"
+     y="505.7457"
+     id="text94">
+    <tspan
+       x="213.10667"
+       y="505.7457"
+       id="tspan96" />
+  </text>
+  <g
+     id="g98"
+     transform="translate(-366.86329,-12.350945)">
+    <rect
+       style="fill:#ffffff"
+       x="739.44598"
+       y="320.09201"
+       width="160"
+       height="41.732498"
+       id="rect100" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x="739.44598"
+       y="320.09201"
+       width="160"
+       height="41.732498"
+       id="rect102" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+       x="819.44598"
+       y="345.80801"
+       id="text104">
+      <tspan
+         x="819.44598"
+         y="345.80801"
+         id="tspan106">@KeyFor(map1)</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new"
+     x="452.5827"
+     y="505.7457"
+     id="text108">
+    <tspan
+       x="452.5827"
+       y="505.7457"
+       id="tspan110" />
+  </text>
+  <g
+     id="g120"
+     transform="translate(-366.86329,-12.350945)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="819.44598"
+       y1="320.09201"
+       x2="819.70203"
+       y2="292.73599"
+       id="line122" />
+    <polygon
+       style="fill:#000000"
+       points="819.702,292.736 814.679,295.189 819.773,285.237 824.679,295.283 "
+       id="polygon124" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="819.702,292.736 814.679,295.189 819.773,285.237 824.679,295.283 "
+       id="polygon126" />
+  </g>
+  <g
+     id="g136"
+     transform="translate(-366.86329,-12.350945)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="819.58698"
+       y2="371.55899"
+       id="line138" />
+    <polygon
+       style="fill:#000000"
+       points="819.587,371.559 814.624,374.131 819.478,364.06 824.623,373.986 "
+       id="polygon140" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="819.587,371.559 814.624,374.131 819.478,364.06 824.623,373.986 "
+       id="polygon142" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-size:12.80000019px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:courier new"
+     x="42.334709"
+     y="505.84366"
+     id="text144">
+    <tspan
+       x="42.334709"
+       y="505.84366"
+       id="tspan146" />
+  </text>
+  <g
+     id="g168"
+     transform="translate(-366.86329,-12.350945)">
+    <rect
+       style="fill:#ffffff"
+       x="910.43402"
+       y="320.57199"
+       width="160"
+       height="41.732498"
+       id="rect170" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x="910.43402"
+       y="320.57199"
+       width="160"
+       height="41.732498"
+       id="rect172" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+       x="990.43402"
+       y="346.28799"
+       id="text174">
+      <tspan
+         x="990.43402"
+         y="346.28799"
+         id="tspan176">@KeyFor(map2)</tspan>
+    </text>
+  </g>
+  <g
+     id="g178"
+     transform="translate(-366.86329,-12.350945)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="990.43402"
+       y1="320.57199"
+       x2="892.14697"
+       y2="286.21701"
+       id="line180" />
+    <polygon
+       style="fill:#000000"
+       points="892.147,286.217 892.857,291.762 885.067,283.743 896.156,282.322 "
+       id="polygon182" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="892.147,286.217 892.857,291.762 885.067,283.743 896.156,282.322 "
+       id="polygon184" />
+  </g>
+  <g
+     id="g186"
+     transform="translate(-366.86329,-12.350945)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="980.92798"
+       y2="364.40701"
+       id="line188" />
+    <polygon
+       style="fill:#000000"
+       points="980.928,364.407 977.407,360.064 988.251,362.787 979.566,369.828 "
+       id="polygon190" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="980.928,364.407 977.407,360.064 988.251,362.787 979.566,369.828 "
+       id="polygon192" />
+  </g>
+  <flowRoot
+     xml:space="preserve"
+     id="flowRoot3179"
+     style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+     transform="matrix(1.1211416,0,0,1.1211416,-38.86329,396.0448)"><flowRegion
+       id="flowRegion3181"><rect
+         id="rect3183"
+         width="4.0481334"
+         height="59.102745"
+         x="289.84634"
+         y="196.50114" /></flowRegion><flowPara
+       id="flowPara3185" /></flowRoot>  <flowRoot
+     xml:space="preserve"
+     id="flowRoot3191"
+     style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+     transform="matrix(1.1211416,0,0,1.1211416,-38.86329,396.0448)"><flowRegion
+       id="flowRegion3193"><rect
+         id="rect3195"
+         width="8.9058933"
+         height="51.816105"
+         x="344.90094"
+         y="190.83377" /></flowRegion><flowPara
+       id="flowPara3197" /></flowRoot>  <g
+     id="g98-5"
+     transform="translate(-365.8469,69.315941)">
+    <rect
+       style="fill:#ffffff"
+       x="739.44598"
+       y="320.09201"
+       width="160"
+       height="41.732498"
+       id="rect100-1" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2.16861057"
+       x="724.09924"
+       y="320.17633"
+       width="188.87798"
+       height="41.563892"
+       id="rect102-9" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+       x="819.44598"
+       y="345.80801"
+       id="text104-8">
+      <tspan
+         x="819.44598"
+         y="345.80801"
+         id="tspan106-1">@KeyFor({map1,map2})</tspan>
+    </text>
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2.17365456"
+       x="1047.9241"
+       y="319.38879"
+       width="189.78065"
+       height="41.558849"
+       id="rect102-9-2" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+       x="1144.176"
+       y="345.01794"
+       id="text104-8-9">
+      <tspan
+         x="1144.176"
+         y="345.01794"
+         id="tspan106-1-6">@KeyFor({map2,map3})</tspan>
+    </text>
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2.46929455"
+       x="695.08154"
+       y="401.81403"
+       width="246.67049"
+       height="41.26321"
+       id="rect102-9-4" />
+    <text
+       font-size="16"
+       style="font-size:16px;font-style:normal;font-weight:normal;text-anchor:middle;fill:#000000;font-family:courier new"
+       x="819.32452"
+       y="427.29538"
+       id="text104-8-4">
+      <tspan
+         x="819.32452"
+         y="427.29538"
+         id="tspan106-1-8">@KeyFor({map1,...,mapn})</tspan>
+    </text>
+  </g>
+  <g
+     id="g186-1"
+     transform="matrix(-0.90403164,0,0,1,1516.6664,-11.183754)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="980.92798"
+       y2="364.40701"
+       id="line188-5" />
+    <polygon
+       style="fill:#000000"
+       points="979.566,369.828 980.928,364.407 977.407,360.064 988.251,362.787 "
+       id="polygon190-6" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="979.566,369.828 980.928,364.407 977.407,360.064 988.251,362.787 "
+       id="polygon192-1" />
+  </g>
+  <g
+     id="g136-0"
+     transform="translate(-367.31755,70.455944)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="819.58698"
+       y2="371.55899"
+       id="line138-2" />
+    <polygon
+       style="fill:#000000"
+       points="819.587,371.559 814.624,374.131 819.478,364.06 824.623,373.986 "
+       id="polygon140-0" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="819.587,371.559 814.624,374.131 819.478,364.06 824.623,373.986 "
+       id="polygon142-8" />
+  </g>
+  <g
+     id="g186-9"
+     transform="matrix(0.97043543,0,0,1.0399962,-343.08706,91.731906)">
+    <g
+       transform="matrix(0.79311177,0,0,1,170.11759,-35.829237)"
+       id="g58">
+      <line
+         style="fill:none;stroke:#000000;stroke-width:2"
+         x1="820"
+         y1="400"
+         x2="1229.63"
+         y2="363.33701"
+         id="line60" />
+      <polygon
+         style="fill:#000000"
+         points="1243.58,362.089 1229.01,356.365 1230.26,370.309 "
+         id="polygon62" />
+      <polygon
+         style="fill:none;stroke:#000000;stroke-width:2"
+         points="1243.58,362.089 1229.01,356.365 1230.26,370.309 "
+         id="polygon64" />
+    </g>
+  </g>
+  <g
+     id="g136-0-6"
+     transform="translate(-366.51009,151.74623)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="400"
+       x2="819.58698"
+       y2="371.55899"
+       id="line138-2-6" />
+    <polygon
+       style="fill:#000000"
+       points="824.623,373.986 819.587,371.559 814.624,374.131 819.478,364.06 "
+       id="polygon140-0-9" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="824.623,373.986 819.587,371.559 814.624,374.131 819.478,364.06 "
+       id="polygon142-8-4" />
+  </g>
+</svg>
diff --git a/docs/manual/figures/methodval.svg b/docs/manual/figures/methodval.svg
new file mode 100644
index 0000000..4e560c2
--- /dev/null
+++ b/docs/manual/figures/methodval.svg
@@ -0,0 +1,243 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="27.791376cm"
+   height="5.8400235cm"
+   viewBox="358 100 873.44324 292.00117"
+   id="svg3577"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="methodval.svg">
+  <metadata
+     id="metadata3693">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs3691" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1596"
+     inkscape:window-height="1155"
+     id="namedview3689"
+     showgrid="false"
+     inkscape:zoom="1.1774114"
+     inkscape:cx="492.98141"
+     inkscape:cy="-25.97894"
+     inkscape:window-x="4"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg3577"
+     inkscape:connector-spacing="5"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <g
+     id="g3589"
+     transform="translate(333.36558,-19)">
+    <rect
+       style="fill:#ffffff"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3591" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2;stroke-opacity:1"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3593" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:14.32277775px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1;"
+       x="460.5"
+       y="146.772"
+       id="text3595">
+      <tspan
+         x="460.5"
+         y="146.772"
+         id="tspan3597"
+         style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1;">@UnknownMethod</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="947.48358"
+     y="257.31573"
+     id="text3619">
+    <tspan
+       x="947.48358"
+       y="257.31573"
+       id="tspan3621" />
+  </text>
+  <g
+     id="g3657"
+     transform="translate(333.36558,-17.999791)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3659" />
+    <polygon
+       style="fill:#000000"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       id="polygon3661" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       id="polygon3663" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="947.48358"
+     y="257.31573"
+     id="text3673">
+    <tspan
+       x="947.48358"
+       y="257.31573"
+       id="tspan3675" />
+  </text>
+  <rect
+     style="fill:#ffffff"
+     x="693.3656"
+     y="347.00104"
+     width="201"
+     height="44"
+     id="rect3559" />
+  <rect
+     style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2.05175543;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     x="688.59741"
+     y="347.02679"
+     width="211.73486"
+     height="43.94849"
+     id="rect3561" />
+  <text
+     font-size="15.8044"
+     style="font-style:normal;font-weight:normal;font-size:15.80440044px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1;"
+     x="793.8656"
+     y="373.77304"
+     id="text3563">
+    <tspan
+       style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1;"
+       x="793.8656"
+       y="373.77304"
+       id="tspan3565">@MethodValBottom</tspan>
+  </text>
+  <g
+     id="g3631"
+     transform="translate(353.53888,149.91457)">
+    <rect
+       id="rect3583"
+       height="41.285389"
+       width="1387.2889"
+       y="34.443081"
+       x="-250.32158"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2.27986622;stroke-miterlimit:4;stroke-dasharray:none" />
+    <text
+       id="text3585"
+       y="56.765598"
+       x="441.76974"
+       style="font-style:normal;font-weight:normal;font-size:15.80440044px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       font-size="15.8044">
+      <tspan
+         id="tspan3587"
+         y="56.765598"
+         x="441.76974"
+         style="font-size:21.16666603px">@MethodVal(className={&quot;java.lang.String&quot;, &quot;java.lang.String&quot;},methodName={&quot;toString&quot;,&quot;equals&quot;},params={0,1})</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(333.36557,146.00089)"
+     id="g3590">
+    <line
+       id="line3592"
+       y2="173.73599"
+       x2="460.5"
+       y1="200"
+       x1="460.5"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    <polygon
+       id="polygon3594"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       style="fill:#000000" />
+    <polygon
+       id="polygon3596"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+  </g>
+  <path
+     inkscape:connector-curvature="2"
+     inkscape:connector-type="polyline"
+     id="path4201"
+     d="m 728.20565,313.69628 0,0"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <g
+     transform="translate(324.5696,231.91491)"
+     id="g4844">
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2.25654793;stroke-miterlimit:4;stroke-dasharray:none"
+       x="26.697365"
+       y="34.021584"
+       width="882.80029"
+       height="42.128387"
+       id="rect4846" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="469.17535"
+       y="56.765598"
+       id="text4848">
+      <tspan
+         x="469.17535"
+         y="56.765598"
+         id="tspan4850"
+         style="font-size:21.16666603px">@MethodVal(className=&quot;java.lang.String&quot;,methodName=&quot;equals&quot;,params=1)</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(333.36558,64.000552)"
+     id="g4856">
+    <line
+       id="line4858"
+       y2="173.73599"
+       x2="460.5"
+       y1="200"
+       x1="460.5"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    <polygon
+       id="polygon4860"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       style="fill:#000000" />
+    <polygon
+       id="polygon4862"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+  </g>
+</svg>
diff --git a/docs/manual/figures/mustcall.svg b/docs/manual/figures/mustcall.svg
new file mode 100644
index 0000000..20256dc
--- /dev/null
+++ b/docs/manual/figures/mustcall.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<svg width="12cm" height="5.8962cm" style="zoom:1" version="1.1" viewBox="358 100 248.21 294.81" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+    <metadata>
+        <rdf:RDF>
+            <cc:Work rdf:about="">
+                <dc:format>image/svg+xml</dc:format>
+                <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+                <dc:title/>
+            </cc:Work>
+        </rdf:RDF>
+    </metadata>
+    <g transform="translate(24.6 -19)">
+        <rect x="360" y="120" width="201" height="44" fill="#fff"/>
+        <rect x="340.92" y="120.09" width="240.37" height="43.817" fill="none" stroke="#7f7f7f" stroke-width="2.1826"/>
+        <text x="460.5" y="146.772" fill="#7f7f7f" font-family="'courier new'" font-size="14.323px" text-anchor="middle"><tspan x="460.5" y="146.772" fill="#7f7f7f" font-size="21.167px">@MustCallUnknown</tspan></text>
+    </g>
+    <text x="638.71808" y="257.31573" fill="#000000" font-family="'courier new'" font-size="12.8px"><tspan x="638.71808" y="257.31573"/></text>
+    <g transform="translate(27.142 -18.8)">
+        <line x1="460.5" x2="460.5" y1="200" y2="173.74" fill="none" stroke="#000" stroke-width="2"/>
+        <polygon points="465.5 176.24 460.5 173.74 455.5 176.24 460.5 166.24"/>
+        <polygon points="465.5 176.24 460.5 173.74 455.5 176.24 460.5 166.24" fill="none" stroke="#000" stroke-width="2"/>
+    </g>
+    <text x="638.71808" y="257.31573" fill="#000000" font-family="'courier new'" font-size="12.8px"><tspan x="638.71808" y="257.31573"/></text>
+    <text x="489.33737" y="376.5813" fill="#7f7f7f" font-family="'courier new'" font-size="15.804px" text-anchor="middle"><tspan x="489.33737" y="376.5813" fill="#000000" font-size="21.167px">@MustCall({})</tspan></text>
+    <path d="m604.17 233.4" fill="none" stroke="#000" stroke-width="1.4111px"/>
+    <g transform="translate(28.636 234.26)">
+        <rect x="185.22px" y="31.009px" width="249.1px" height="43.36px" fill="none" stroke="#000" stroke-width="1.0249"/>
+        <text x="348.12827" y="55.567112" fill="#000000" font-family="'courier new'" font-size="21.167px" text-anchor="middle"><tspan x="310" y="58" font-size="21.167px">@MustCall("foo")</tspan></text>
+    </g>
+    <g transform="translate(245.56 234.26)">
+        <rect x="257.39" y="31.009" width="246.7px" height="43.36" fill="none" stroke="#000" stroke-width="1.0249"/>
+        <text x="348.12827" y="55.567112" fill="#000000" font-family="'courier new'" font-size="21.167px" text-anchor="middle"><tspan x="380" y="58" font-size="21.167px">@MustCall("bar")</tspan></text>
+    </g>
+    <path d="m501.1 181.86" fill="none" stroke="#000" stroke-width="1.4111px"/>
+    <g transform="translate(143.34 150.35)">
+        <rect x="176.38px" y="31.124px" width="339.33px" height="43.13px" fill="none" stroke="#000" stroke-width="1.2545"/>
+        <text x="348.12827" y="55.567112" fill="#000000" font-family="'courier new'" font-size="21.167px" text-anchor="middle"><tspan x="348.12827" y="58" font-size="21.167px">@MustCall({"foo", "bar"})</tspan></text>
+    </g>
+    <g transform="matrix(.57938 .81506 -.81506 .57938 345.91 -247.02)">
+        <line x1="460.5" x2="460.5" y1="200" y2="173.74" fill="none" stroke="#000" stroke-width="2"/>
+        <polygon points="455.5 176.24 460.5 166.24 465.5 176.24 460.5 173.74"/>
+        <polygon points="455.5 176.24 460.5 166.24 465.5 176.24 460.5 173.74" fill="none" stroke="#000" stroke-width="2"/>
+    </g>
+    <path d="m452.18 242.76-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+    <path d="m450.68 243.06-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+    <g transform="matrix(-1 0 0 1 898.43 -3.701)">
+        <g transform="matrix(.57938 .81506 -.81506 .57938 347.02 -157.87)">
+            <line x1="460.5" x2="460.5" y1="200" y2="173.74" fill="none" stroke="#000" stroke-width="2"/>
+            <polygon points="465.5 176.24 460.5 173.74 455.5 176.24 460.5 166.24"/>
+            <polygon points="465.5 176.24 460.5 173.74 455.5 176.24 460.5 166.24" fill="none" stroke="#000" stroke-width="2"/>
+        </g>
+        <path d="m453.28 331.9-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+        <path d="m451.78 332.2-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+    </g>
+    <g transform="translate(78.376 -3.701)">
+        <g transform="matrix(.57938 .81506 -.81506 .57938 347.02 -157.87)">
+            <line x1="460.5" x2="460.5" y1="200" y2="173.74" fill="none" stroke="#000" stroke-width="2"/>
+            <polygon points="460.5 166.24 465.5 176.24 460.5 173.74 455.5 176.24"/>
+            <polygon points="460.5 166.24 465.5 176.24 460.5 173.74 455.5 176.24" fill="none" stroke="#000" stroke-width="2"/>
+        </g>
+        <path d="m453.28 331.9-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+        <path d="m451.78 332.2-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+    </g>
+    <rect x="358.75px" y="349.88px" width="265.91px" height="43.86px" fill="none" stroke="#000" stroke-width="1.0249"/>
+    <g transform="matrix(-1 0 0 1 981.48 -88.75)">
+        <g transform="matrix(.57938 .81506 -.81506 .57938 347.02 -157.87)">
+            <line x1="460.5" x2="460.5" y1="200" y2="173.74" fill="none" stroke="#000" stroke-width="2"/>
+            <polygon points="460.5 173.74 455.5 176.24 460.5 166.24 465.5 176.24"/>
+            <polygon points="460.5 173.74 455.5 176.24 460.5 166.24 465.5 176.24" fill="none" stroke="#000" stroke-width="2"/>
+        </g>
+        <path d="m453.28 331.9-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+        <path d="m451.78 332.2-30.11 20.419z" fill="none" stroke="#000" stroke-width="1.4746px"/>
+    </g>
+</svg>
diff --git a/docs/manual/figures/nullness.svg b/docs/manual/figures/nullness.svg
new file mode 100644
index 0000000..18e5daa
--- /dev/null
+++ b/docs/manual/figures/nullness.svg
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
+<svg width="10.5cm" height="4.5cm" viewBox="619 259 402 165" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <g>
+    <rect style="fill: #ffffff" x="620" y="320" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="620" y="320" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="700" y="345.85">
+      <tspan x="700" y="345.85">@NonNull Object</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="860" y="320" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="860" y="320" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="940" y="345.85">
+      <tspan x="940" y="345.85">@Nullable Date</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="740" y="260" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="740" y="260" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="820" y="285.85">
+      <tspan x="820" y="285.85">@Nullable Object</tspan>
+    </text>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="700" y1="320" x2="805.166" y2="304.225"/>
+    <polygon style="fill: #000000" points="806.204,311.148 819.011,302.148 804.128,297.303 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="806.204,311.148 819.011,302.148 804.128,297.303 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="940" y1="320" x2="834.834" y2="304.225"/>
+    <polygon style="fill: #000000" points="835.872,297.303 820.989,302.148 833.796,311.148 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="835.872,297.303 820.989,302.148 833.796,311.148 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="820" y1="380" x2="714.834" y2="364.225"/>
+    <polygon style="fill: #000000" points="715.872,357.303 700.989,362.148 713.796,371.148 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="715.872,357.303 700.989,362.148 713.796,371.148 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="820" y1="380" x2="925.166" y2="364.225"/>
+    <polygon style="fill: #000000" points="926.204,371.148 939.011,362.148 924.128,357.303 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="926.204,371.148 939.011,362.148 924.128,357.303 "/>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="740" y="380" width="160" height="42"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="740" y="380" width="160" height="42"/>
+    <text font-size="16" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="820" y="405.85">
+      <tspan x="820" y="405.85">@NonNull Date</tspan>
+    </text>
+  </g>
+</svg>
diff --git a/docs/manual/figures/optional-subtyping.svg b/docs/manual/figures/optional-subtyping.svg
new file mode 100644
index 0000000..2da84db
--- /dev/null
+++ b/docs/manual/figures/optional-subtyping.svg
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="144.88226"
+   height="145.84274"
+   viewBox="358 100 128.50827 205.80031"
+   id="svg3577"
+   version="1.1"
+   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
+   sodipodi:docname="optional-subtyping.svg">
+  <metadata
+     id="metadata3693">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs3691" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1707"
+     inkscape:window-height="1035"
+     id="namedview3689"
+     showgrid="false"
+     inkscape:zoom="2.3166763"
+     inkscape:cx="-46.627506"
+     inkscape:cy="30.487617"
+     inkscape:window-x="9"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="g3609-3"
+     inkscape:document-rotation="0"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <g
+     id="g3589"
+     transform="translate(-37.864476,-19)">
+    <rect
+       style="fill:#ffffff"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3591" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3593" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:15.8044px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="460.5"
+       y="146.772"
+       id="text3595"><tspan
+         x="460.5"
+         y="146.772"
+         id="tspan3597">@MaybePresent</tspan></text>
+  </g>
+  <g
+     id="g3609"
+     transform="translate(-37.864476,-19)">
+    <rect
+       style="fill:#ffffff"
+       x="360"
+       y="200"
+       width="201"
+       height="44"
+       id="rect3611" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x="360"
+       y="200"
+       width="201"
+       height="44"
+       id="rect3613" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:15.8044px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="460.5"
+       y="226.772"
+       id="text3615"><tspan
+         x="460.5"
+         y="226.772"
+         id="tspan3617">@Present</tspan></text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.8px;line-height:0%;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="423.14307"
+     y="70.007416"
+     id="text3619"><tspan
+       x="423.14307"
+       y="70.007416"
+       id="tspan3621" /></text>
+  <g
+     id="g3657"
+     transform="translate(-37.864476,-19)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3659" />
+    <polygon
+       style="fill:#000000"
+       points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+       id="polygon3661" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+       id="polygon3663" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.8px;line-height:0%;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="423.14307"
+     y="70.007416"
+     id="text3673"><tspan
+       x="423.14307"
+       y="70.007416"
+       id="tspan3675" /></text>
+  <g
+     id="g3609-3"
+     transform="translate(-39.309445,60.800322)">
+    <rect
+       style="fill:#ffffff"
+       x="360"
+       y="200"
+       width="201"
+       height="44"
+       id="rect3611-6" />
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x="360"
+       y="200"
+       width="201"
+       height="44"
+       id="rect3613-7" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:15.8044px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="460.5"
+       y="226.772"
+       id="text3615-5"><tspan
+         x="460.5"
+         y="226.772"
+         id="tspan3617-3">@OptionalBottom</tspan></text>
+  </g>
+  <g
+     id="g3657-5"
+     transform="translate(-39.309445,60.800322)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3659-6" />
+    <polygon
+       style="fill:#000000"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       id="polygon3661-2" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       id="polygon3663-9" />
+  </g>
+</svg>
diff --git a/docs/manual/figures/regex.svg b/docs/manual/figures/regex.svg
new file mode 100644
index 0000000..f9e9472
--- /dev/null
+++ b/docs/manual/figures/regex.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
+<svg width="5.5cm" height="11.5cm" viewBox="358 38 204 447" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <g>
+    <rect style="fill: #ffffff" x="360" y="40" width="201" height="44"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #7f7f7f" x="360" y="40" width="201" height="44"/>
+    <text font-size="15.8044" style="fill: #7f7f7f;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="460.5" y="66.7722">
+      <tspan x="460.5" y="66.7722">@UnknownRegex</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="360" y="120" width="201" height="44"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="360" y="120" width="201" height="44"/>
+    <text font-size="15.8044" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="460.5" y="146.772">
+      <tspan x="460.5" y="146.772">@Regex(0) = @Regex</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="360" y="280" width="201" height="44"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="360" y="280" width="201" height="44"/>
+    <text font-size="15.8044" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="460.5" y="306.772">
+      <tspan x="460.5" y="306.772">@Regex(2)</tspan>
+    </text>
+  </g>
+  <g>
+    <rect style="fill: #ffffff" x="360" y="200" width="201" height="44"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="360" y="200" width="201" height="44"/>
+    <text font-size="15.8044" style="fill: #000000;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="460.5" y="226.772">
+      <tspan x="460.5" y="226.772">@Regex(1)</tspan>
+    </text>
+  </g>
+  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal" x="460.5" y="142">
+    <tspan x="460.5" y="142"></tspan>
+  </text>
+  <g>
+    <rect style="fill: #ffffff" x="360" y="440" width="201" height="44"/>
+    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #7f7f7f" x="360" y="440" width="201" height="44"/>
+    <text font-size="15.8044" style="fill: #7f7f7f;text-anchor:middle;font-family:courier new;font-style:normal;font-weight:normal" x="460.5" y="466.772">
+      <tspan x="460.5" y="466.772">@RegexBottom</tspan>
+    </text>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="460.5" y1="120" x2="460.5" y2="93.7361"/>
+    <polygon style="fill: #000000" points="460.5,86.2361 465.5,96.2361 460.5,93.7361 455.5,96.2361 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="460.5,86.2361 465.5,96.2361 460.5,93.7361 455.5,96.2361 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="460" y1="360" x2="460.365" y2="333.735"/>
+    <polygon style="fill: #000000" points="460.469,326.236 465.33,336.304 460.365,333.735 455.331,336.165 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="460.469,326.236 465.33,336.304 460.365,333.735 455.331,336.165 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="460.5" y1="280" x2="460.5" y2="253.736"/>
+    <polygon style="fill: #000000" points="460.5,246.236 465.5,256.236 460.5,253.736 455.5,256.236 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="460.5,246.236 465.5,256.236 460.5,253.736 455.5,256.236 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="460.5" y1="200" x2="460.5" y2="173.736"/>
+    <polygon style="fill: #000000" points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "/>
+  </g>
+  <g>
+    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="460.5" y1="440" x2="460.127" y2="411.491"/>
+    <polygon style="fill: #000000" points="460.029,403.992 465.16,413.926 460.127,411.491 455.16,414.056 "/>
+    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="460.029,403.992 465.16,413.926 460.127,411.491 455.16,414.056 "/>
+  </g>
+  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal" x="460.5" y="142">
+    <tspan x="460.5" y="142"></tspan>
+  </text>
+  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal" x="458.11" y="372.72">
+    <tspan x="458.11" y="372.72">.</tspan>
+  </text>
+  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal" x="458.078" y="381.66">
+    <tspan x="458.078" y="381.66">.</tspan>
+  </text>
+  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:courier new;font-style:normal;font-weight:normal" x="458.134" y="389.92">
+    <tspan x="458.134" y="389.92">.</tspan>
+  </text>
+</svg>
diff --git a/docs/manual/figures/samelen.svg b/docs/manual/figures/samelen.svg
new file mode 100644
index 0000000..c89887c
--- /dev/null
+++ b/docs/manual/figures/samelen.svg
@@ -0,0 +1,355 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="7.8975224cm"
+   height="5.8961892cm"
+   viewBox="358 100 248.20783 294.80945"
+   id="svg3577"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="samelen.svg">
+  <metadata
+     id="metadata3693">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs3691" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1280"
+     inkscape:window-height="698"
+     id="namedview3689"
+     showgrid="false"
+     inkscape:zoom="1.6651112"
+     inkscape:cx="131.75286"
+     inkscape:cy="70.457598"
+     inkscape:window-x="0"
+     inkscape:window-y="1"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg3577"
+     inkscape:connector-spacing="5"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <g
+     id="g3589"
+     transform="translate(24.600084,-18.999985)">
+    <rect
+       style="fill:#ffffff"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3591" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2.18256378;stroke-opacity:1"
+       x="340.9155"
+       y="120.09128"
+       width="240.36746"
+       height="43.817436"
+       id="rect3593" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:14.32277775px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1"
+       x="460.5"
+       y="146.772"
+       id="text3595">
+      <tspan
+         x="460.5"
+         y="146.772"
+         id="tspan3597"
+         style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1">@SameLenUnknown</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="638.71808"
+     y="257.31573"
+     id="text3619">
+    <tspan
+       x="638.71808"
+       y="257.31573"
+       id="tspan3621" />
+  </text>
+  <g
+     id="g3657"
+     transform="translate(27.142456,149.79682)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3659" />
+    <polygon
+       style="fill:#000000"
+       points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+       id="polygon3661" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+       id="polygon3663" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="638.71808"
+     y="257.31573"
+     id="text3673">
+    <tspan
+       x="638.71808"
+       y="257.31573"
+       id="tspan3675" />
+  </text>
+  <text
+     font-size="15.8044"
+     style="font-style:normal;font-weight:normal;font-size:15.80440044px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1"
+     x="489.33737"
+     y="376.5813"
+     id="text3563">
+    <tspan
+       style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1"
+       x="489.33737"
+       y="376.5813"
+       id="tspan3565">@SameLenBottom</tspan>
+  </text>
+  <path
+     inkscape:connector-curvature="2"
+     inkscape:connector-type="polyline"
+     id="path4201"
+     d="m 604.16876,233.39773 0,0"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <g
+     transform="translate(28.636421,155.35292)"
+     id="g4844">
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.024863;stroke-miterlimit:4;stroke-dasharray:none"
+       x="257.38934"
+       y="31.008766"
+       width="176.92526"
+       height="43.360073"
+       id="rect4846" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="348.12827"
+       y="55.567112"
+       id="text4848">
+      <tspan
+         x="348.12827"
+         y="55.567112"
+         id="tspan4850"
+         style="font-size:21.16666603px">@SameLen(&quot;a&quot;)</tspan>
+    </text>
+  </g>
+  <g
+     id="g4330"
+     transform="translate(245.56238,155.35292)">
+    <rect
+       id="rect4332"
+       height="43.360073"
+       width="176.92526"
+       y="31.008766"
+       x="257.38934"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.024863;stroke-miterlimit:4;stroke-dasharray:none" />
+    <text
+       id="text4334"
+       y="55.567112"
+       x="348.12827"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       font-size="15.8044">
+      <tspan
+         style="font-size:21.16666603px"
+         id="tspan4336"
+         y="55.567112"
+         x="348.12827">@SameLen(&quot;b&quot;)</tspan>
+    </text>
+  </g>
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="m 501.09897,181.86283 0,0"
+     id="path4342"
+     inkscape:connector-type="polyline"
+     inkscape:connector-curvature="2" />
+  <g
+     transform="translate(143.34003,239.2555)"
+     id="g4344">
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.25453866;stroke-miterlimit:4;stroke-dasharray:none"
+       x="212.02193"
+       y="31.123606"
+       width="266.52182"
+       height="43.130394"
+       id="rect4346" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="348.12827"
+       y="55.567112"
+       id="text4348">
+      <tspan
+         x="348.12827"
+         y="55.567112"
+         id="tspan4350"
+         style="font-size:21.16666603px">@SameLen({&quot;a&quot;, &quot;b&quot;})</tspan>
+    </text>
+  </g>
+  <g
+     id="g4375"
+     transform="matrix(0.57937742,0.81505938,-0.81505938,0.57937742,345.91225,-324.02303)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line4377" />
+    <polygon
+       style="fill:#000000"
+       points="455.5,176.236 460.5,166.236 465.5,176.236 460.5,173.736 "
+       id="polygon4379" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="455.5,176.236 460.5,166.236 465.5,176.236 460.5,173.736 "
+       id="polygon4381" />
+  </g>
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="m 452.17656,165.75609 -30.10954,20.41858 z"
+     id="path4397"
+     inkscape:connector-curvature="0" />
+  <path
+     inkscape:connector-curvature="0"
+     id="path4401"
+     d="m 450.67845,166.05571 -30.10954,20.41858 z"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <g
+     id="g4427"
+     transform="matrix(-1,0,0,1,898.42972,-83.70148)">
+    <g
+       id="g4403"
+       transform="matrix(0.57937742,0.81505938,-0.81505938,0.57937742,347.01661,-157.87422)">
+      <line
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+         x1="460.5"
+         y1="200"
+         x2="460.5"
+         y2="173.73599"
+         id="line4405" />
+      <polygon
+         style="fill:#000000"
+         points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+         id="polygon4407" />
+      <polygon
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+         points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+         id="polygon4409" />
+    </g>
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 453.28092,331.9049 -30.10954,20.41858 z"
+       id="path4423"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path4425"
+       d="M 451.78281,332.20452 421.67327,352.6231 Z"
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  </g>
+  <g
+     transform="translate(78.375978,-83.70148)"
+     id="g4443">
+    <g
+       transform="matrix(0.57937742,0.81505938,-0.81505938,0.57937742,347.01661,-157.87422)"
+       id="g4445">
+      <line
+         id="line4447"
+         y2="173.73599"
+         x2="460.5"
+         y1="200"
+         x1="460.5"
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+      <polygon
+         id="polygon4449"
+         points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+         style="fill:#000000" />
+      <polygon
+         id="polygon4451"
+         points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       id="path4453"
+       d="m 453.28092,331.9049 -30.10954,20.41858 z"
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 451.78281,332.20452 421.67327,352.6231 Z"
+       id="path4455"
+       inkscape:connector-curvature="0" />
+  </g>
+  <rect
+     style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2.14008689;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     x="374.52548"
+     y="349.87921"
+     width="230.82231"
+     height="43.860157"
+     id="rect3561" />
+  <g
+     transform="matrix(-1,0,0,1,981.48056,-166.75232)"
+     id="g4457">
+    <g
+       transform="matrix(0.57937742,0.81505938,-0.81505938,0.57937742,347.01661,-157.87422)"
+       id="g4459">
+      <line
+         id="line4461"
+         y2="173.73599"
+         x2="460.5"
+         y1="200"
+         x1="460.5"
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+      <polygon
+         id="polygon4463"
+         points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+         style="fill:#000000" />
+      <polygon
+         id="polygon4465"
+         points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       id="path4467"
+       d="m 453.28092,331.9049 -30.10954,20.41858 z"
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.47461903px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 451.78281,332.20452 421.67327,352.6231 Z"
+       id="path4469"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/docs/manual/figures/searchindex.svg b/docs/manual/figures/searchindex.svg
new file mode 100644
index 0000000..d29385c
--- /dev/null
+++ b/docs/manual/figures/searchindex.svg
@@ -0,0 +1,402 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="13.027042cm"
+   height="7.5658431cm"
+   viewBox="358 100 409.42131 378.29214"
+   id="svg3577"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="searchindex.svg">
+  <metadata
+     id="metadata3693">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs3691" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1280"
+     inkscape:window-height="698"
+     id="namedview3689"
+     showgrid="false"
+     inkscape:zoom="1"
+     inkscape:cx="195.58274"
+     inkscape:cy="158.4843"
+     inkscape:window-x="0"
+     inkscape:window-y="1"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg3577"
+     inkscape:connector-spacing="5"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <g
+     id="g3589"
+     transform="translate(108.80225,-18.999998)">
+    <rect
+       style="fill:#ffffff"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3591" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2.27557755;stroke-opacity:1"
+       x="328.97714"
+       y="120.13779"
+       width="261.8472"
+       height="43.724422"
+       id="rect3593" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:14.32277775px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1"
+       x="460.5"
+       y="146.772"
+       id="text3595">
+      <tspan
+         x="460.5"
+         y="146.772"
+         id="tspan3597"
+         style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1">@SearchIndexUnknown</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="722.92023"
+     y="257.31573"
+     id="text3619">
+    <tspan
+       x="722.92023"
+       y="257.31573"
+       id="tspan3621" />
+  </text>
+  <g
+     id="g3657"
+     transform="translate(108.80225,-17.999789)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3659" />
+    <polygon
+       style="fill:#000000"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       id="polygon3661" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       id="polygon3663" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="722.92023"
+     y="257.31573"
+     id="text3673">
+    <tspan
+       x="722.92023"
+       y="257.31573"
+       id="tspan3675" />
+  </text>
+  <rect
+     style="fill:#ffffff"
+     x="472.39774"
+     y="433.29202"
+     width="201"
+     height="44"
+     id="rect3559" />
+  <rect
+     style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2.22468281;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     x="448.54025"
+     y="433.40424"
+     width="249.9135"
+     height="43.775558"
+     id="rect3561" />
+  <text
+     font-size="15.8044"
+     style="font-style:normal;font-weight:normal;font-size:15.80440044px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1"
+     x="572.89777"
+     y="460.06403"
+     id="text3563">
+    <tspan
+       style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1"
+       x="572.89777"
+       y="460.06403"
+       id="tspan3565">@SearchIndexBottom</tspan>
+  </text>
+  <path
+     inkscape:connector-curvature="2"
+     inkscape:connector-type="polyline"
+     id="path4201"
+     d="m 644.33511,399.98726 0,0"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <g
+     transform="translate(100.00627,151.61634)"
+     id="g4844">
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.19203472;stroke-miterlimit:4;stroke-dasharray:none"
+       x="346.16089"
+       y="33.489326"
+       width="240.27786"
+       height="43.192905"
+       id="rect4846" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="469.17535"
+       y="56.765598"
+       id="text4848">
+      <tspan
+         x="469.17535"
+         y="56.765598"
+         id="tspan4850"
+         style="font-size:21.16666603px">@SearchIndex(&quot;a&quot;)</tspan>
+    </text>
+  </g>
+  <g
+     id="g3370"
+     transform="translate(109.59416,234.31188)">
+    <rect
+       id="rect3372"
+       height="43.076015"
+       width="290.49741"
+       y="33.547771"
+       x="487.64069"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.30892479;stroke-miterlimit:4;stroke-dasharray:none" />
+    <text
+       id="text3374"
+       y="56.765598"
+       x="633.36792"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       font-size="15.8044">
+      <tspan
+         style="font-size:21.16666603px"
+         id="tspan3376"
+         y="56.765598"
+         x="633.36792">@NegativeIndexFor(&quot;a&quot;)</tspan>
+    </text>
+  </g>
+  <g
+     id="g3378"
+     transform="translate(107.60376,229.89491)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3380" />
+    <polygon
+       style="fill:#000000"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       id="polygon3382" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       id="polygon3384" />
+  </g>
+  <g
+     id="g3374"
+     transform="translate(-30.628692,234.31187)">
+    <rect
+       id="rect3376"
+       height="43.052357"
+       width="301.2601"
+       y="32.361115"
+       x="268.32959"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.33258557;stroke-miterlimit:4;stroke-dasharray:none" />
+    <text
+       id="text3378"
+       y="55.567112"
+       x="423.63287"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       font-size="15.8044">
+      <tspan
+         style="font-size:21.16666603px"
+         id="tspan3380"
+         y="55.567112"
+         x="423.63287">@SearchIndex({&quot;a&quot;, &quot;b})</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(-59.392362,315.80893)"
+     id="g3382">
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.48494232;stroke-miterlimit:4;stroke-dasharray:none"
+       x="443.38474"
+       y="33.63578"
+       width="375.41388"
+       height="42.899998"
+       id="rect3384" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="633.36792"
+       y="56.765598"
+       id="text3386">
+      <tspan
+         x="633.36792"
+         y="56.765598"
+         id="tspan3388"
+         style="font-size:21.16666603px">@NegativeIndexFor({&quot;a&quot;, &quot;b&quot;})</tspan>
+    </text>
+  </g>
+  <g
+     id="g3396"
+     transform="translate(123.78648,-1.8530272e-7)">
+    <g
+       id="g3590"
+       transform="matrix(0.52612488,0.85040733,-0.85040733,0.52612488,342.44621,-248.62679)">
+      <line
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+         x1="460.5"
+         y1="200"
+         x2="460.5"
+         y2="173.73599"
+         id="line3592" />
+      <polygon
+         style="fill:#000000"
+         points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+         id="polygon3594" />
+      <polygon
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+         points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+         id="polygon3596" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       id="path3390"
+       d="m 435.64085,234.99948 -51.83452,31.16063 z"
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 436.24009,235.59872 -51.83452,31.16063 z"
+       id="path3394"
+       inkscape:connector-curvature="0" />
+  </g>
+  <g
+     id="g3404"
+     transform="matrix(-1,0,0,1,1016.3086,0.29962135)">
+    <g
+       transform="matrix(0.52612488,0.85040733,-0.85040733,0.52612488,342.44621,-248.62679)"
+       id="g3406">
+      <line
+         id="line3408"
+         y2="173.73599"
+         x2="460.5"
+         y1="200"
+         x1="460.5"
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+      <polygon
+         id="polygon3410"
+         points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+         style="fill:#000000" />
+      <polygon
+         id="polygon3412"
+         points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    </g>
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 435.64085,234.99948 -51.83452,31.16063 z"
+       id="path3414"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path3416"
+       d="m 436.24009,235.59872 -51.83452,31.16063 z"
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  </g>
+  <g
+     transform="matrix(-1,0,0,1,954.21981,82.144063)"
+     id="g3418">
+    <g
+       id="g3420"
+       transform="matrix(0.52612488,0.85040733,-0.85040733,0.52612488,342.44621,-248.62679)">
+      <line
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+         x1="460.5"
+         y1="200"
+         x2="460.5"
+         y2="173.73599"
+         id="line3422" />
+      <polygon
+         style="fill:#000000"
+         points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+         id="polygon3424" />
+      <polygon
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+         points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+         id="polygon3426" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       id="path3428"
+       d="m 435.64085,234.99948 -51.83452,31.16063 z"
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 436.24009,235.59872 -51.83452,31.16063 z"
+       id="path3430"
+       inkscape:connector-curvature="0" />
+  </g>
+  <g
+     id="g3432"
+     transform="translate(194.34204,83.255553)">
+    <g
+       transform="matrix(0.52612488,0.85040733,-0.85040733,0.52612488,342.44621,-248.62679)"
+       id="g3434">
+      <line
+         id="line3436"
+         y2="173.73599"
+         x2="460.5"
+         y1="200"
+         x1="460.5"
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+      <polygon
+         id="polygon3438"
+         points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+         style="fill:#000000" />
+      <polygon
+         id="polygon3440"
+         points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+         style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    </g>
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 435.64085,234.99948 -51.83452,31.16063 z"
+       id="path3442"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path3444"
+       d="m 436.24009,235.59872 -51.83452,31.16063 z"
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  </g>
+</svg>
diff --git a/docs/manual/figures/signature-types.svg b/docs/manual/figures/signature-types.svg
new file mode 100644
index 0000000..1034b9c
--- /dev/null
+++ b/docs/manual/figures/signature-types.svg
@@ -0,0 +1,741 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:xhtml="http://www.w3.org/1999/xhtml"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="1061px"
+   height="467px"
+   version="1.1"
+   content="&lt;mxfile userAgent=&quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36&quot; version=&quot;9.0.4&quot; editor=&quot;www.draw.io&quot; type=&quot;device&quot;&gt;&lt;diagram id=&quot;2b81190c-a13e-ed42-a410-f27d97360f89&quot; name=&quot;Page-1&quot;&gt;3VvbcuI4EP0aHknZlozhcXLdqdpLtqip3X3UYAW8YyxKiAD79SvHki9tERSPDXbyEtS+6pxW91FLHqG79eGJk83qNxbSeOQ54WGE7kee5wUOkv9SyzGzTKbTzLDkUZiZ3MIwj/6jyugo6y4K6bZyomAsFtGmalywJKELUbERztm+etoLi6tP3ZAlrRnmCxLXrX9FoVhl1qnvFPZfaLRc6Se7jjrynSx+LDnbJep5Iw+9vP1lh9dE30udv12RkO1LJvQwQnecMZH9Wh/uaJxiq2HLrns8cTR/b04TYXMBzi54JfGO6jeexPLS2+/yxzL9McLOXUy22ycq5tF6E9PfyZrmZ3F92lzwKFlqu3xicYPauarv4qjxfkOMpu/kyMP7VSTofEMW6dG99DBpW4l1LFtufnW5j6rbr5QLeiiZVJ+fKFtTwY/yFHV0jJEiQDmoq6ndF3TntlWJam0jysOW+b0LlOUPBbQZdN8K9K+JoDwh8SPj66HD7XoWcHsdwT2xgvsxonF4T7cLHm0E4wNH3A2uCHjwoaDyCcKJTTDpCuypFdi3UUL48RNAnWdahXWeSS+B9cwukuzi+PjnjsTRS0TDT4D52Jtc0cG1Jiyjjp15tEyI2HH6LfmRsH1yDuI+wQmzoe9fDk2T4pNaI5TdSd2V/8G/cE6OQ4ITTUFImNXxzL2odTwNYq4mJqSEe+bROhLRK1UAf02+JYkMDeGznDGks5HhAI7c8yEYdea/hnxX9t8B4TieVXFEswtGVWSMqoVMGLB/1iSCKR50hSs2Te4AIDQJv6RFCtlapCI4WlQxoIdI/J3CdTNDSLX/ke2xc+Ng1X6mMqBQOUNUsJ7Ebct2fEEr+kUQvqT6LNVnGlYqInVwS+C9Ny/mNCZpmKu8gwlQ9YRnFskXLk3MgxODQt8j64+6zCtVNmp3AlkWYXCnDIband4YzjtuN8NslXS/RHlKrWSUH7NDOPOH1JAedW+cqW43cohp3SGCnvkDkJ7YaewPIG0hONxb9AdTyeGD/mDFXzCAAe0DAt3GBIK4jibdEWgqYXRB4GQABGJAoNeUQAQFT9AdgaaySBcE+gMgEJSn8vbHCQQzrbzdAYGmWktrKdWKWjwAaoHGwVDj2FN7Tne1R23QglqyIlD7eZnBab8IREDe+I3lDZyV4+7kTdCCvCmJWn/mAVGLT4naYkR7jYa0ySP8nnkE0Et+Y72EgF7C3emloAW9VPKIwK84hC27pmTcs+kMAmLKbyymPCCmcHdiKmhBTP1kLjZRO+kZtUBm+Y1llgdkFu5OZgXXl1mmIoSGrjfcwhWbxjrLAzoLd6ezprpy2gq3zRKuSUTjnnEL0qQP06Q9t+dSd4vcetcftwZqZ/2iFuZIH+ZIe2rP5e0WqUXXp9YQkvs2bGGS9GGStOf2XOJuj9uZISS/u1o8oGU3WIrQY+0Sq/AzQzjEzj0Tcyr7RAQNixXi7YAwHbsgpVx0b0O+kHrFUKSZ7XGWcWFVFGJvHYlcEIkQrAa1F4lcpwXlb8eg3sBfTib9KrroV/z5VKI7pr976G7N2TXti2tvcJbrc42KMUba+1V9dR3Ae+O9Bp4LeO+u+Oq6rU7r3uPd9yDzzXcamCSlju09cYexC1c6m+898SdVz+rSH9qoxuf+MMEYekTj3UaG1N2vADD2wfJLbdxaM+6D5ZdJh6lbT8XKBEtA56rJuFixJUtI/FBYb6vS9WwsgGs0MBL8S4U4qg/3yE4waSqe+ytjm4/kCsM2Ftu6vbUHnBC9sll8bZeRUXzSiB7+Bw==&lt;/diagram&gt;&lt;/mxfile&gt;"
+   style="background-color: rgb(255, 255, 255);"
+   id="svg190"
+   sodipodi:docname="signature-types.svg"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
+  <metadata
+     id="metadata194">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="2560"
+     inkscape:window-height="1536"
+     id="namedview192"
+     showgrid="false"
+     inkscape:zoom="2"
+     inkscape:cx="256.24737"
+     inkscape:cy="133.08719"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="g188" />
+  <defs
+     id="defs2" />
+  <g
+     transform="translate(0.5,0.5)"
+     id="g188">
+    <rect
+       x="44"
+       y="125"
+       width="150"
+       height="40"
+       pointer-events="none"
+       id="rect4"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(49.5,137.5)"
+       id="g10">
+      <switch
+         id="switch8"
+         transform="translate(0,-4)">
+        <text
+           x="68"
+           y="19"
+           font-size="12px"
+           id="text6"
+           style="font-size:12px;font-family:'Helvetica Bold','Helvetica Neue Bold','Arial','sans-serif';font-weight:700;text-anchor:middle;fill:#000000">@ClassGetSimpleName</text>
+      </switch>
+    </g>
+    <rect
+       x="325.68027"
+       y="124.9815"
+       width="120"
+       height="40"
+       pointer-events="none"
+       id="rect12"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(339.97409,136.29383)"
+       id="g18">
+      <switch
+         id="switch16"
+         transform="translate(0,-4)">
+        <text
+           x="43"
+           y="19"
+           font-size="12px"
+           id="text14"
+           style="font-size:12px;font-family:'Helvetica Bold','Helvetica Neue Bold','Arial','sans-serif';font-weight:700;text-anchor:middle;fill:#000000">@InternalForm</text>
+      </switch>
+    </g>
+    <rect
+       x="742"
+       y="125"
+       width="120"
+       height="40"
+       pointer-events="none"
+       id="rect20"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(750.5,137.5)"
+       id="g26">
+      <switch
+         id="switch24"
+         transform="translate(0,-4)">
+        <text
+           x="50"
+           y="19"
+           font-size="12px"
+           id="text22"
+           style="font-size:12px;font-family:'Helvetica Bold','Helvetica Neue Bold','Arial','sans-serif';font-weight:700;text-anchor:middle;fill:#000000">@FieldDescriptor</text>
+      </switch>
+    </g>
+    <rect
+       x="602"
+       y="125"
+       width="120"
+       height="40"
+       pointer-events="none"
+       id="rect28"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(612.5,137.5)"
+       id="g34">
+      <switch
+         id="switch32"
+         transform="translate(0,-4)">
+        <text
+           x="48"
+           y="19"
+           font-size="12px"
+           id="text30"
+           style="font-size:12px;font-family:'Helvetica Bold','Helvetica Neue Bold','Arial','sans-serif';font-weight:700;text-anchor:middle;fill:#000000">@ClassGetName</text>
+      </switch>
+    </g>
+    <rect
+       x="530"
+       y="191"
+       width="120"
+       height="40"
+       pointer-events="none"
+       id="rect36"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(547.5,197.5)"
+       id="g42">
+      <switch
+         id="switch40">
+        <text
+           x="41"
+           y="19"
+           font-size="12px"
+           id="text38"
+           style="font-size:12px;font-family:'Helvetica Bold','Helvetica Neue Bold','Arial','sans-serif';font-weight:700;text-anchor:middle;fill:#000000">@BinaryName</text>
+      </switch>
+    </g>
+    <rect
+       x="282.79657"
+       y="190.68338"
+       width="135"
+       height="40"
+       pointer-events="none"
+       id="rect44"
+       style="fill:#ffffff;stroke:#000000;stroke-width:1.06161761" />
+    <g
+       transform="translate(284.94597,199.18338)"
+       id="g50">
+      <switch
+         id="switch48"
+         transform="translate(0,-4)">
+        <text
+           x="62"
+           y="19"
+           font-size="12px"
+           id="text46"
+           style="font-size:12px;font-family:'Helvetica Bold','Helvetica Neue Bold','Arial','sans-serif';font-weight:700;text-anchor:middle;fill:#000000">@FullyQualifiedName</text>
+      </switch>
+    </g>
+    <rect
+       x="310"
+       y="40"
+       width="120"
+       height="40"
+       pointer-events="none"
+       id="rect52"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(311.5,48.5)"
+       id="g58">
+      <switch
+         id="switch56">
+        <text
+           x="57"
+           y="19"
+           font-size="12px"
+           id="text54"
+           style="font-size:12px;font-family:Helvetica;text-anchor:middle;fill:#000000">@SignatureUnknown</text>
+      </switch>
+    </g>
+    <rect
+       x="50"
+       y="260"
+       width="210"
+       height="40"
+       pointer-events="none"
+       id="rect60"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(103.5,268.5)"
+       id="g66">
+      <switch
+         id="switch64">
+        <text
+           x="50"
+           y="19"
+           font-size="12px"
+           id="text62"
+           style="font-size:12px;font-family:Helvetica;text-anchor:middle;fill:#000000">@ArrayWithoutPackage</text>
+      </switch>
+    </g>
+    <rect
+       x="710"
+       y="189"
+       width="320"
+       height="40"
+       pointer-events="none"
+       id="rect68"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(714.5,197.5)"
+       id="g74">
+      <switch
+         id="switch72">
+        <text
+           x="154"
+           y="19"
+           font-size="12px"
+           id="text70"
+           style="font-size:12px;font-family:Helvetica;text-anchor:middle;fill:#000000">@FieldDescriptorWithoutPackage</text>
+      </switch>
+    </g>
+    <rect
+       x="340"
+       y="371"
+       width="120"
+       height="40"
+       pointer-events="none"
+       id="rect76"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(369.5,377.5)"
+       id="g82">
+      <switch
+         id="switch80">
+        <text
+           x="29"
+           y="19"
+           font-size="12px"
+           id="text78"
+           style="font-size:12px;font-family:Helvetica;text-anchor:middle;fill:#000000">@Identifier</text>
+      </switch>
+    </g>
+    <rect
+       x="530"
+       y="260"
+       width="220"
+       height="40"
+       pointer-events="none"
+       id="rect84"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(545.5,268.5)"
+       id="g90">
+      <switch
+         id="switch88">
+        <text
+           x="93"
+           y="19"
+           font-size="12px"
+           id="text86"
+           style="font-size:12px;font-family:Helvetica;text-anchor:middle;fill:#000000">@BinaryNameWithoutPackage</text>
+      </switch>
+    </g>
+    <path
+       d="M 565.29711,190.25289 532.76289,165.56711"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path96"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:0.85214466;stroke-miterlimit:10" />
+    <path
+       d="M 480.53,125 414.85,83.41"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path100"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 410.42,80.6 7.78,0.79 -3.35,2.02 -0.39,3.89 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path102"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="M 612.51339,124.86513 436.03661,76.614874"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path104"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:0.98344642;stroke-miterlimit:10" />
+    <path
+       d="m 431.06,75 7.74,-1.14 -2.75,2.78 0.58,3.87 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path106"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="M 370,125 V 86.37"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path108"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 370,81.12 3.5,7 -3.5,-1.75 -3.5,1.75 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path110"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="M 119.82351,122.37344 303.81649,70.756556"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path112"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:0.87308133;stroke-miterlimit:10" />
+    <path
+       d="m 308.91,69.58 -5.99,5.03 0.89,-3.81 -2.52,-3 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path114"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="M 623.33,260 608.2,238.3"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path116"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:1.12663662;stroke-miterlimit:10" />
+    <path
+       d="m 605.29,233.93 6.79,3.88 -3.88,0.49 -1.94,3.4 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path118"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="M 541.85568,258.3775 423.60432,169.0925"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path120"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:0.76448315;stroke-miterlimit:10" />
+    <path
+       d="m 418.85,166.96 7.82,-0.28 -3.04,2.46 0.14,3.91 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path122"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 553.74,300.31 -5.73,5.33 0.69,-3.85 -2.66,-2.86 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path134"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="M 146,260.33 126,171.21"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path136"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 124.85,166.09 4.95,6.06 -3.8,-0.94 -3.03,2.48 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path138"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <rect
+       x="450"
+       y="425"
+       width="210"
+       height="40"
+       pointer-events="none"
+       id="rect148"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(475.5,435.5)"
+       id="g154">
+      <switch
+         id="switch152">
+        <text
+           x="78"
+           y="19"
+           font-size="12px"
+           id="text150"
+           style="font-size:12px;font-family:Helvetica;text-anchor:middle;fill:#000000">@FieldDescriptorForPrimitive</text>
+      </switch>
+    </g>
+    <rect
+       x="280"
+       y="260"
+       width="210"
+       height="40"
+       pointer-events="none"
+       id="rect156"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(314.5,268.5)"
+       id="g162">
+      <switch
+         id="switch160">
+        <text
+           x="69"
+           y="19"
+           font-size="12px"
+           id="text158"
+           style="font-size:12px;font-family:Helvetica;text-anchor:middle;fill:#000000">@DotSeparatedIdentifiers</text>
+      </switch>
+    </g>
+    <path
+       d="m 864.59,229.66 -3.61,6.94 -0.64,-3.86 -3.47,-1.81 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path170"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 401.43,411.55 7.54,-2.11 -2.38,3.1 1.05,3.77 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path174"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 395.46,301.44 4.25,6.58 -3.67,-1.36 -3.29,2.13 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path178"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="M 415.33,260.33 523.88,229.05"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path180"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 528.93,227.6 -5.76,5.3 0.71,-3.85 -2.65,-2.88 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path182"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 662.33,175.33 v 0"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path184"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="M 662.33,175.33 Z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path186"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <rect
+       x="464.28284"
+       y="125.33501"
+       width="120"
+       height="40"
+       pointer-events="none"
+       id="rect28-3"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(474.78284,137.835)"
+       id="g34-6">
+      <switch
+         id="switch32-7"
+         transform="translate(0,-4)">
+        <text
+           x="48"
+           y="19"
+           font-size="12px"
+           id="text30-5"
+           style="font-size:12px;font-family:'Helvetica Bold','Helvetica Neue Bold','Arial','sans-serif';font-weight:700,'Helvetica Neue Bold','Arial','sans-serif';font-weight:700;text-anchor:middle;fill:#000000">@FqBinaryName</text>
+      </switch>
+    </g>
+    <path
+       d="m 524.61284,175.665 v 0"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path184-5"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="M 524.61284,175.665 Z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path186-6"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="M 749.59143,187.30884 679.53799,168.62342"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path96-2"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:1.08789241;stroke-miterlimit:10" />
+    <path
+       d="m 674.37471,167.45613 7.58,-1.97 -2.44,3.06 0.98,3.79 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path98-7"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 632.31023,164.31053 -0.72,7.79 -2.05,-3.33 -3.9,-0.36 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path142-3"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 533.31134,166.67685 7.78,0.79 -3.35,2.02 -0.39,3.89 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path102-0"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 609.7725,190.33715 19.76234,-21.56088"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path96-6"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:0.62068641;stroke-miterlimit:10" />
+    <path
+       d="m 787.71994,165.57595 4.95,6.06 -3.8,-0.94 -3.03,2.48 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path138-2"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 800.565,187.85 -13.13,-19.7"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path116-8"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 431.24022,63.756698 7.74,-1.14 -2.75,2.78 0.58,3.87 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path106-6"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="M 759.15437,123.88873 434.84563,65.111272"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path120-7"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:1.02721262;stroke-miterlimit:10" />
+    <path
+       d="m 410.76479,190.5603 74.49782,-23.98394"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path132-3"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:0.52591407;stroke-miterlimit:10" />
+    <path
+       d="m 490.2387,165.31833 -5.73,5.33 0.69,-3.85 -2.66,-2.86 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path134-5"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="M 221.71539,260.27028 327.29877,232.89482"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path180-1"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:0.92263561;stroke-miterlimit:10" />
+    <path
+       d="m 332.33208,231.49255 -5.76,5.3 0.71,-3.85 -2.65,-2.88 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path182-2"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 380.01179,259.46545 -4.36042,-21.65414"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path176-0"
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:0.75442952;stroke-miterlimit:10" />
+    <path
+       d="m 375.10658,232.58338 4.25,6.58 -3.67,-1.36 -3.29,2.13 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path178-9"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <rect
+       x="73.5"
+       y="406.94159"
+       width="120"
+       height="40"
+       pointer-events="none"
+       id="rect76-3"
+       style="fill:#ffffff;stroke:#000000" />
+    <g
+       transform="translate(103,413.4416)"
+       id="g82-6">
+      <switch
+         id="switch80-7">
+        <text
+           x="29"
+           y="19"
+           font-size="12px"
+           id="text78-5"
+           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;font-family:Helvetica;-inkscape-font-specification:'Helvetica, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;fill:#000000">
+          <tspan
+             sodipodi:role="line"
+             id="tspan4658"
+             x="29"
+             y="19">@PrimitiveType</tspan>
+        </text>
+      </switch>
+    </g>
+    <rect
+       x="180.08162"
+       y="318.97141"
+       width="152.38857"
+       height="39.940365"
+       pointer-events="none"
+       id="rect76-5"
+       style="fill:#ffffff;stroke:#000000;stroke-width:1.12606013" />
+    <g
+       transform="translate(227,325.4416)"
+       id="g82-62">
+      <switch
+         id="switch80-9">
+        <text
+           x="29"
+           y="19"
+           font-size="12px"
+           id="text78-1"
+           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;font-family:Helvetica;-inkscape-font-specification:'Helvetica, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;fill:#000000">
+          <tspan
+             sodipodi:role="line"
+             id="tspan4656"
+             x="28.999996"
+             y="19">@IdentifierOrPrimitiveType</tspan>
+        </text>
+      </switch>
+    </g>
+    <path
+       d="m 258.93,359.4916 7.54,-2.11 -2.38,3.1 1.05,3.77 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path174-2"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 235.5,318 218,300.5"
+       id="path4660"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 395.46,301.44 399,371 553.74,300.31"
+       id="path4662"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 399,371 264.09,360.4816"
+       id="path4664"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 509,424.5 406.59,412.54"
+       id="path4666"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 629.5,424.5 864.59,229.66"
+       id="path4668"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 138,407 238,358.5"
+       id="path4670"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 235.00648,360.20995 -5.73,5.33 0.69,-3.85 -2.66,-2.86 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path134-5-3"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+    <path
+       d="m 214.81134,299.17685 7.78,0.79 -3.35,2.02 -0.39,3.89 z"
+       stroke-miterlimit="10"
+       pointer-events="none"
+       id="path102-0-8"
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke:#000000;stroke-miterlimit:10" />
+  </g>
+</svg>
diff --git a/docs/manual/figures/signedness.svg b/docs/manual/figures/signedness.svg
new file mode 100644
index 0000000..8f43edd
--- /dev/null
+++ b/docs/manual/figures/signedness.svg
@@ -0,0 +1,423 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="9.455224cm"
+   height="8cm"
+   viewBox="619 259 362.00001 293.33333"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
+   sodipodi:docname="signedness.svg">
+  <metadata
+     id="metadata80">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs78">
+    <marker
+       inkscape:stockid="TriangleOutL"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="marker4669"
+       style="overflow:visible"
+       inkscape:isstock="true">
+      <path
+         id="path4671"
+         d="M 5.77,0 -2.88,5 V -5 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+         transform="scale(0.8)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="TriangleOutL"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="TriangleOutL"
+       style="overflow:visible"
+       inkscape:isstock="true">
+      <path
+         id="path4387"
+         d="M 5.77,0 -2.88,5 V -5 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+         transform="scale(0.8)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Lend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Lend"
+       style="overflow:visible"
+       inkscape:isstock="true">
+      <path
+         id="path4248"
+         d="M 0,0 5,-5 -12.5,0 5,5 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+         transform="matrix(-0.8,0,0,-0.8,-10,0)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow2Lend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow2Lend"
+       style="overflow:visible"
+       inkscape:isstock="true">
+      <path
+         id="path4266"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+         transform="matrix(-1.1,0,0,-1.1,-1.1,0)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="TriangleOutL"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="marker4669-2"
+       style="overflow:visible"
+       inkscape:isstock="true">
+      <path
+         id="path4671-9"
+         d="M 5.77,0 -2.88,5 V -5 Z"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+         transform="scale(0.8)"
+         inkscape:connector-curvature="0" />
+    </marker>
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="2560"
+     inkscape:window-height="1534"
+     id="namedview76"
+     showgrid="false"
+     inkscape:snap-page="true"
+     inkscape:zoom="1.094736"
+     inkscape:cx="45.247074"
+     inkscape:cy="191.49037"
+     inkscape:window-x="0"
+     inkscape:window-y="29"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg2"
+     inkscape:snap-others="true"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:snap-global="false" />
+  <g
+     id="g4"
+     transform="translate(-20,4.6244988)">
+    <rect
+       style="fill:#ffffff"
+       x="640"
+       y="320"
+       width="120"
+       height="42"
+       id="rect6" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x="640"
+       y="320"
+       width="120"
+       height="42"
+       id="rect8" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="700"
+       y="345.85001"
+       id="text10">
+      <tspan
+         x="700"
+         y="345.85001"
+         id="tspan12">@Unsigned</tspan>
+    </text>
+  </g>
+  <g
+     id="g14"
+     style="opacity:0.5"
+     transform="translate(-20,4.6244988)">
+    <rect
+       style="fill:#ffffff"
+       x="880"
+       y="320"
+       width="120"
+       height="42"
+       id="rect16" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x="880"
+       y="320"
+       width="120"
+       height="42"
+       id="rect18" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="940"
+       y="345.85001"
+       id="text20">
+      <tspan
+         x="940"
+         y="345.85001"
+         id="tspan22">@Signed</tspan>
+    </text>
+  </g>
+  <g
+     id="g24"
+     style="opacity:0.5"
+     transform="translate(-20,4.6244988)">
+    <rect
+       style="fill:#ffffff"
+       x="730"
+       y="260"
+       width="180"
+       height="42"
+       id="rect26" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x="730"
+       y="260"
+       width="180"
+       height="42"
+       id="rect28" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="820"
+       y="285.85001"
+       id="text30">
+      <tspan
+         x="820"
+         y="285.85001"
+         id="tspan32">@UnknownSignedness</tspan>
+    </text>
+  </g>
+  <g
+     id="g34"
+     transform="translate(-20,4.6244988)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="700"
+       y1="320"
+       x2="805.16602"
+       y2="304.22501"
+       id="line36" />
+    <polygon
+       style="fill:#000000"
+       points="804.128,297.303 806.204,311.148 819.011,302.148 "
+       id="polygon38" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="804.128,297.303 806.204,311.148 819.011,302.148 "
+       id="polygon40" />
+  </g>
+  <g
+     id="g42"
+     transform="translate(-20,4.6244988)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="940"
+       y1="320"
+       x2="834.83398"
+       y2="304.22501"
+       id="line44" />
+    <polygon
+       style="fill:#000000"
+       points="833.796,311.148 835.872,297.303 820.989,302.148 "
+       id="polygon46" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="833.796,311.148 835.872,297.303 820.989,302.148 "
+       id="polygon48" />
+  </g>
+  <g
+     id="g50"
+     transform="translate(-20,4.6244988)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="380"
+       x2="714.83398"
+       y2="364.22501"
+       id="line52" />
+    <polygon
+       style="fill:#000000"
+       points="713.796,371.148 715.872,357.303 700.989,362.148 "
+       id="polygon54" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="713.796,371.148 715.872,357.303 700.989,362.148 "
+       id="polygon56" />
+  </g>
+  <g
+     id="g58"
+     transform="translate(-20,4.6244988)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="820"
+       y1="380"
+       x2="925.16602"
+       y2="364.22501"
+       id="line60" />
+    <polygon
+       style="fill:#000000"
+       points="924.128,357.303 926.204,371.148 939.011,362.148 "
+       id="polygon62" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="924.128,357.303 926.204,371.148 939.011,362.148 "
+       id="polygon64" />
+  </g>
+  <g
+     id="g66"
+     style="opacity:0.5"
+     transform="translate(-20,4.6244988)">
+    <rect
+       style="fill:#ffffff"
+       x="740"
+       y="380"
+       width="160"
+       height="42"
+       id="rect68" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x="740"
+       y="380"
+       width="160"
+       height="42"
+       id="rect70" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="820"
+       y="395"
+       id="text72">
+      <tspan
+         x="820"
+         y="395"
+         id="tspan74">@SignedPositive</tspan>
+    </text>
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="820"
+       y="415"
+       id="text50">
+      <tspan
+         x="820"
+         y="415"
+         id="tspan48">@SignednessGlb</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(-20.638354,136.6169)"
+     id="g66-5"
+     style="opacity:0.5">
+    <rect
+       style="fill:#ffffff"
+       x="700"
+       y="380"
+       width="240"
+       height="42"
+       id="rect68-5" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x="700"
+       y="380"
+       width="240"
+       height="42"
+       id="rect70-0" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="820"
+       y="405.85001"
+       id="text72-7">
+      <tspan
+         x="820"
+         y="405.85001"
+         id="tspan74-4">@SignednessBottom</tspan>
+    </text>
+  </g>
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.89582634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4669)"
+     d="m 799.62158,516.96072 v -13.0419"
+     id="path4239"
+     inkscape:connector-curvature="0" />
+  <g
+     id="g66-3"
+     style="opacity:0.5"
+     transform="translate(-20.436862,71.166399)">
+    <rect
+       style="fill:#ffffff"
+       x="740"
+       y="380"
+       width="160"
+       height="42"
+       id="rect68-6" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x="740"
+       y="380"
+       width="160"
+       height="42"
+       id="rect70-7" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15.99962139px;line-height:0%;font-family:'courier new';-inkscape-font-specification:'courier new, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;fill:#000000"
+       x="820"
+       y="395"
+       id="text72-5">
+      <tspan
+         sodipodi:role="line"
+         id="tspan3819"
+         x="820"
+         y="395">@SignedPositive-</tspan>
+    </text>
+    <text
+       font-size="16"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15.99962139px;line-height:0%;font-family:'courier new';-inkscape-font-specification:'courier new, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;fill:#000000"
+       x="820"
+       y="415"
+       id="text50-5">
+      <tspan
+         sodipodi:role="line"
+         id="tspan3817"
+         x="820"
+         y="415">FromUnsigned</tspan>
+    </text>
+  </g>
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.89582634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4669-2)"
+     d="m 800.01121,448.04446 v -13.0419"
+     id="path4239-1"
+     inkscape:connector-curvature="0" />
+</svg>
diff --git a/docs/manual/figures/substringindex.svg b/docs/manual/figures/substringindex.svg
new file mode 100644
index 0000000..725d6d1
--- /dev/null
+++ b/docs/manual/figures/substringindex.svg
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="620"
+   height="160"
+   viewBox="358 100 515.55952 211.66667"
+   id="svg3577"
+   version="1.1"
+   inkscape:version="0.92.1 r15371"
+   sodipodi:docname="substringindex.svg">
+  <metadata
+     id="metadata3693">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs3691" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview3689"
+     showgrid="false"
+     inkscape:zoom="1.6651112"
+     inkscape:cx="391.56665"
+     inkscape:cy="112.59904"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="g3704"
+     inkscape:connector-spacing="5"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <g
+     id="g3589"
+     transform="translate(153.6843,-18.841861)">
+    <rect
+       style="fill:#ffffff"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3591" />
+    <rect
+       style="fill:none;stroke:#7f7f7f;stroke-width:2.39919949;stroke-opacity:1"
+       x="316.28244"
+       y="120.14493"
+       width="291.1651"
+       height="43.71014"
+       id="rect3593" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:14.32277775px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1"
+       x="460.5"
+       y="146.772"
+       id="text3595">
+      <tspan
+         x="460.5"
+         y="146.772"
+         id="tspan3597"
+         style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1">@SubstringIndexUnknown</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0%;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="755.19824"
+     y="257.47388"
+     id="text3619">
+    <tspan
+       x="755.19824"
+       y="257.47388"
+       id="tspan3621" />
+  </text>
+  <g
+     id="g3657"
+     transform="translate(155.04929,-17.841651)">
+    <line
+       style="fill:none;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3659" />
+    <polygon
+       style="fill:#000000"
+       points="455.5,176.236 460.5,166.236 465.5,176.236 460.5,173.736 "
+       id="polygon3661" />
+    <polygon
+       style="fill:none;stroke:#000000;stroke-width:2"
+       points="455.5,176.236 460.5,166.236 465.5,176.236 460.5,173.736 "
+       id="polygon3663" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0%;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="755.19824"
+     y="257.47388"
+     id="text3673">
+    <tspan
+       x="755.19824"
+       y="257.47388"
+       id="tspan3675" />
+  </text>
+  <rect
+     style="fill:none;stroke:#7f7f7f;stroke-width:2.36985517;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     x="473.63843"
+     y="266.08267"
+     width="283.82175"
+     height="43.740437"
+     id="rect3561" />
+  <text
+     font-size="15.8044"
+     style="font-style:normal;font-weight:normal;font-size:15.80440044px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1"
+     x="615.85938"
+     y="294.41983"
+     id="text3563">
+    <tspan
+       style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1"
+       x="615.85938"
+       y="294.41983"
+       id="tspan3565">@SubstringIndexBottom</tspan>
+  </text>
+  <g
+     transform="translate(155.04929,64.158695)"
+     id="g4856">
+    <line
+       id="line4858"
+       y2="173.73599"
+       x2="460.5"
+       y1="200"
+       x1="460.5"
+       style="fill:none;stroke:#000000;stroke-width:2" />
+    <polygon
+       id="polygon4860"
+       points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+       style="fill:#000000" />
+    <polygon
+       id="polygon4862"
+       points="465.5,176.236 460.5,173.736 455.5,176.236 460.5,166.236 "
+       style="fill:none;stroke:#000000;stroke-width:2" />
+  </g>
+  <g
+     transform="translate(166.54547,150.92016)"
+     id="g3704">
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:1.37245405;stroke-miterlimit:4;stroke-dasharray:none"
+       x="41.784367"
+       y="30.592176"
+       width="814.4389"
+       height="45.597359"
+       id="rect3706" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:15.80440044px;line-height:0%;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="450.54892"
+       y="56.765598"
+       id="text3708">
+      <tspan
+         style="font-size:21.16666603px"
+         x="450.54892"
+         y="56.765598"
+         id="tspan3710">@SubstringIndexFor(value=&quot;myStr&quot;, offset=&quot;subStr.length()-1&quot;)</tspan>
+    </text>
+  </g>
+</svg>
diff --git a/docs/manual/figures/typedef.svg b/docs/manual/figures/typedef.svg
new file mode 100644
index 0000000..0747a1c
--- /dev/null
+++ b/docs/manual/figures/typedef.svg
@@ -0,0 +1,234 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="10.5cm"
+   height="4.5cm"
+   viewBox="619 259 402 165"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="subtyping.svg">
+  <metadata
+     id="metadata80">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs78" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1057"
+     inkscape:window-height="861"
+     id="namedview76"
+     showgrid="false"
+     inkscape:zoom="2.4808677"
+     inkscape:cx="186.02362"
+     inkscape:cy="79.724409"
+     inkscape:window-x="125"
+     inkscape:window-y="158"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg2" />
+  <g
+     id="g4">
+    <rect
+       style="fill: #ffffff"
+       x="620"
+       y="320"
+       width="160"
+       height="42"
+       id="rect6" />
+    <rect
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x="620"
+       y="320"
+       width="160"
+       height="42"
+       id="rect8" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="700"
+       y="345.85001"
+       id="text10">
+      <tspan
+         x="700"
+         y="345.85001"
+         id="tspan12">@MyType</tspan>
+    </text>
+  </g>
+  <g
+     id="g14">
+    <rect
+       style="fill: #ffffff"
+       x="860"
+       y="320"
+       width="160"
+       height="42"
+       id="rect16" />
+    <rect
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x="860"
+       y="320"
+       width="160"
+       height="42"
+       id="rect18" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="940"
+       y="345.85001"
+       id="text20">
+      <tspan
+         x="940"
+         y="345.85001"
+         id="tspan22">@NotMyType</tspan>
+    </text>
+  </g>
+  <g
+     id="g24">
+    <rect
+       style="fill: #ffffff"
+       x="740"
+       y="260"
+       width="160"
+       height="42"
+       id="rect26" />
+    <rect
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x="740"
+       y="260"
+       width="160"
+       height="42"
+       id="rect28" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="820"
+       y="285.85001"
+       id="text30">
+      <tspan
+         x="820"
+         y="285.85001"
+         id="tspan32">@MyTypeUnknown</tspan>
+    </text>
+  </g>
+  <g
+     id="g34">
+    <line
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x1="700"
+       y1="320"
+       x2="805.166"
+       y2="304.225"
+       id="line36" />
+    <polygon
+       style="fill: #000000"
+       points="806.204,311.148 819.011,302.148 804.128,297.303 "
+       id="polygon38" />
+    <polygon
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       points="806.204,311.148 819.011,302.148 804.128,297.303 "
+       id="polygon40" />
+  </g>
+  <g
+     id="g42">
+    <line
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x1="940"
+       y1="320"
+       x2="834.834"
+       y2="304.225"
+       id="line44" />
+    <polygon
+       style="fill: #000000"
+       points="835.872,297.303 820.989,302.148 833.796,311.148 "
+       id="polygon46" />
+    <polygon
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       points="835.872,297.303 820.989,302.148 833.796,311.148 "
+       id="polygon48" />
+  </g>
+  <g
+     id="g50">
+    <line
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x1="820"
+       y1="380"
+       x2="714.834"
+       y2="364.225"
+       id="line52" />
+    <polygon
+       style="fill: #000000"
+       points="715.872,357.303 700.989,362.148 713.796,371.148 "
+       id="polygon54" />
+    <polygon
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       points="715.872,357.303 700.989,362.148 713.796,371.148 "
+       id="polygon56" />
+  </g>
+  <g
+     id="g58">
+    <line
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x1="820"
+       y1="380"
+       x2="925.166"
+       y2="364.225"
+       id="line60" />
+    <polygon
+       style="fill: #000000"
+       points="926.204,371.148 939.011,362.148 924.128,357.303 "
+       id="polygon62" />
+    <polygon
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       points="926.204,371.148 939.011,362.148 924.128,357.303 "
+       id="polygon64" />
+  </g>
+  <g
+     id="g66">
+    <rect
+       style="fill: #ffffff"
+       x="740"
+       y="380"
+       width="160"
+       height="42"
+       id="rect68" />
+    <rect
+       style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000"
+       x="740"
+       y="380"
+       width="160"
+       height="42"
+       id="rect70" />
+    <text
+       font-size="16"
+       style="font-style:normal;font-weight:normal;font-size:16px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="820"
+       y="405.85001"
+       id="text72">
+      <tspan
+         x="820"
+         y="405.85001"
+         id="tspan74">@MyTypeBottom</tspan>
+    </text>
+  </g>
+</svg>
diff --git a/docs/manual/figures/upperbound.svg b/docs/manual/figures/upperbound.svg
new file mode 100644
index 0000000..5bbcf21
--- /dev/null
+++ b/docs/manual/figures/upperbound.svg
@@ -0,0 +1,285 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="9.1598835cm"
+   height="7.4332433cm"
+   viewBox="358 100 287.88204 371.66216"
+   id="svg3577"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="upperbound.svg">
+  <metadata
+     id="metadata3693">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs3691" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1280"
+     inkscape:window-height="698"
+     id="namedview3689"
+     showgrid="false"
+     inkscape:zoom="1.6651112"
+     inkscape:cx="226.93323"
+     inkscape:cy="139.0237"
+     inkscape:window-x="0"
+     inkscape:window-y="1"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg3577"
+     inkscape:connector-spacing="5"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <g
+     id="g3589"
+     transform="translate(43.662473,-18.999996)">
+    <rect
+       style="fill:#ffffff"
+       x="360"
+       y="120"
+       width="201"
+       height="44"
+       id="rect3591" />
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2.18256378;stroke-opacity:1"
+       x="340.9155"
+       y="120.09128"
+       width="240.36746"
+       height="43.817436"
+       id="rect3593" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:14.32277775px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1"
+       x="460.5"
+       y="146.772"
+       id="text3595">
+      <tspan
+         x="460.5"
+         y="146.772"
+         id="tspan3597"
+         style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1">@UpperBoundUnknown</tspan>
+    </text>
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="657.78046"
+     y="257.31573"
+     id="text3619">
+    <tspan
+       x="657.78046"
+       y="257.31573"
+       id="tspan3621" />
+  </text>
+  <g
+     id="g3657"
+     transform="translate(43.662473,-17.999787)">
+    <line
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       x1="460.5"
+       y1="200"
+       x2="460.5"
+       y2="173.73599"
+       id="line3659" />
+    <polygon
+       style="fill:#000000"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       id="polygon3661" />
+    <polygon
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
+       points="460.5,173.736 455.5,176.236 460.5,166.236 465.5,176.236 "
+       id="polygon3663" />
+  </g>
+  <text
+     font-size="12.8"
+     style="font-style:normal;font-weight:normal;font-size:12.80000019px;font-family:'courier new';text-anchor:start;fill:#000000"
+     x="657.78046"
+     y="257.31573"
+     id="text3673">
+    <tspan
+       x="657.78046"
+       y="257.31573"
+       id="tspan3675" />
+  </text>
+  <rect
+     style="fill:#ffffff"
+     x="403.66251"
+     y="347.00104"
+     width="201"
+     height="44"
+     id="rect3559" />
+  <rect
+     style="fill:none;fill-opacity:0;stroke:#7f7f7f;stroke-width:2.14008689;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     x="386.80826"
+     y="426.73199"
+     width="230.82231"
+     height="43.860157"
+     id="rect3561" />
+  <text
+     font-size="15.8044"
+     style="font-style:normal;font-weight:normal;font-size:15.80440044px;font-family:'courier new';text-anchor:middle;fill:#7f7f7f;fill-opacity:1"
+     x="501.62015"
+     y="455.129"
+     id="text3563">
+    <tspan
+       style="font-size:21.16666603px;fill:#7f7f7f;fill-opacity:1"
+       x="501.62015"
+       y="455.129"
+       id="tspan3565">@UpperBoundBottom</tspan>
+  </text>
+  <g
+     id="g3631"
+     transform="translate(63.835768,310.93151)">
+    <rect
+       id="rect3583"
+       height="42.243332"
+       width="455.82483"
+       y="33.964108"
+       x="210.61656"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.32192183;stroke-miterlimit:4;stroke-dasharray:none" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:15.80440044px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="441.76974"
+       y="56.765598"
+       id="text3676">
+      <tspan
+         style="font-size:21.16666603px"
+         x="441.76974"
+         y="56.765598"
+         id="tspan3678">@LTOMLengthOf(&quot;myArray&quot;)</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(42.815005,224.81445)"
+     id="g3590">
+    <line
+       id="line3592"
+       y2="173.73599"
+       x2="460.5"
+       y1="200"
+       x1="460.5"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    <polygon
+       id="polygon3594"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       style="fill:#000000" />
+    <polygon
+       id="polygon3596"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+  </g>
+  <path
+     inkscape:connector-curvature="2"
+     inkscape:connector-type="polyline"
+     id="path4201"
+     d="m 610.66182,395.89969 0,0"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41111112px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <g
+     transform="translate(34.866493,231.91491)"
+     id="g4844">
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.60108316;stroke-miterlimit:4;stroke-dasharray:none"
+       x="250.48653"
+       y="33.693851"
+       width="437.61899"
+       height="42.783855"
+       id="rect4846" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:21.16666603px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="469.17535"
+       y="56.765598"
+       id="text4848">
+      <tspan
+         x="469.17535"
+         y="56.765598"
+         id="tspan4850"
+         style="font-size:21.16666603px">@LTLengthOf(&quot;myArray&quot;)</tspan>
+    </text>
+  </g>
+  <g
+     transform="translate(43.662473,64.000556)"
+     id="g4856">
+    <line
+       id="line4858"
+       y2="173.73599"
+       x2="460.5"
+       y1="200"
+       x1="460.5"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    <polygon
+       id="polygon4860"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       style="fill:#000000" />
+    <polygon
+       id="polygon4862"
+       points="460.5,166.236 465.5,176.236 460.5,173.736 455.5,176.236 "
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+  </g>
+  <g
+     transform="translate(42.815015,143.86462)"
+     id="g3696">
+    <line
+       id="line3698"
+       y2="173.73599"
+       x2="460.5"
+       y1="200"
+       x1="460.5"
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+    <polygon
+       id="polygon3700"
+       points="455.5,176.236 460.5,166.236 465.5,176.236 460.5,173.736 "
+       style="fill:#000000" />
+    <polygon
+       id="polygon3702"
+       points="455.5,176.236 460.5,166.236 465.5,176.236 460.5,173.736 "
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
+  </g>
+  <g
+     transform="translate(63.835771,150.76204)"
+     id="g3704">
+    <rect
+       style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.32192183;stroke-miterlimit:4;stroke-dasharray:none"
+       x="210.61656"
+       y="33.964108"
+       width="455.82483"
+       height="42.243332"
+       id="rect3706" />
+    <text
+       font-size="15.8044"
+       style="font-style:normal;font-weight:normal;font-size:15.80440044px;font-family:'courier new';text-anchor:middle;fill:#000000"
+       x="441.76974"
+       y="56.765598"
+       id="text3708">
+      <tspan
+         style="font-size:21.16666603px"
+         x="441.76974"
+         y="56.765598"
+         id="tspan3710">@LTEqLengthOf(&quot;myArray&quot;)</tspan>
+    </text>
+  </g>
+</svg>
diff --git a/docs/manual/figures/value-subtyping.svg b/docs/manual/figures/value-subtyping.svg
new file mode 100644
index 0000000..3b9c2bc
--- /dev/null
+++ b/docs/manual/figures/value-subtyping.svg
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#"
+  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg"
+  xmlns="http://www.w3.org/2000/svg" width="860" height="410" viewBox="-2 -2 1205 565"
+  version="1.1">
+  <metadata id="metadata84">
+    <rdf:RDF>
+      <cc:Work rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <style type="text/css">
+    rect{
+    fill:#fff;
+    stroke:#000;
+    stroke-width:2.14;
+    }
+    text{
+    font-size:14.10753822px;
+    font-style:normal;
+    font-weight:normal;
+    font-family:courier new;
+    text-anchor:middle;
+    }
+    polyline{
+    fill:none;
+    stroke:#000;
+    stroke-width:3.2;
+    marker-start:url(#arrow);
+    stroke-dasharray:0, 5, 1000;
+    }
+    polyline.noarrow{
+    marker-start:none;
+    stroke-dasharray:none;
+    }
+    text.gray{
+    fill:#808080;
+    }
+    rect.gray{
+    stroke:#808080;
+    }
+  </style>
+  <defs>
+    <marker id="arrow" orient="auto" markerWidth="5"
+      markerHeight="5" refY="2.5" refX="0">
+      <path style="fill:#000;" d="M5,0 L0,2.5 L5,5" />
+    </marker>
+  </defs>
+  <g id="value-subtyping">
+    <g id="sub-top">
+      <rect x="510" y="0" width="130" height="40" class="gray" />
+      <text x="575" y="24.10753822px" class="gray">@UnknownVal</text>
+    </g>
+    <g id="sub-intval">
+      <rect x="350" y="90" width="130" height="40" />
+      <text x="415" y="114.10753822px">@IntVal(long[])</text>
+    </g>
+    <g id="sub-intrange">
+      <rect x="170" y="90" width="170" height="40" />
+      <text x="255" y="114.10753822px">@IntRange(long,long)</text>
+    </g>
+    <g id="sub-doubleval">
+      <rect x="490" y="90" width="170" height="40" />
+      <text x="575" y="114.10753822px">@DoubleVal(double[])</text>
+    </g>
+    <g id="sub-boolval">
+      <rect x="0" y="90" width="160" height="40" />
+      <text x="80" y="114.10753822px">@BoolVal(boolean[])</text>
+    </g>
+    <g id="sub-stringval">
+      <rect x="670" y="90" width="170" height="40" />
+      <text x="755" y="114.10753822px">@StringVal(String[])</text>
+    </g>
+    <g id="sub-arraylen">
+      <rect x="850" y="90" width="140" height="40" />
+      <text x="920" y="114.10753822px">@ArrayLen(int[])</text>
+    </g>
+    <g id="sub-arraylenrange">
+      <rect x="1000" y="90" width="200" height="40" />
+      <text x="1100" y="114.10753822px">@ArrayLenRange(int,int)</text>
+    </g>
+    <g id="sub-bot">
+      <rect x="510" y="180" width="130" height="40" class="gray" />
+      <text x="575" y="204.10753822px" class="gray">@BottomVal</text>
+    </g>
+    <polyline points="575,40 575,90" />
+    <polyline points="575,65 80,65 80,90" class="noarrow" />
+    <polyline points="575,65 1100,65 1100,90" class="noarrow" />
+    <polyline points="255,65 255,90" class="noarrow" />
+    <polyline points="415,65 415,90" class="noarrow" />
+    <polyline points="755,65 755,90" class="noarrow" />
+    <polyline points="920,65 920,90" class="noarrow" />
+
+    <polyline points="575,130 575,180" />
+    <polyline points="80,130 80,155 575,155" />
+    <polyline points="1100,130 1100,155 570,155" />
+    <polyline points="255,130 255,155" />
+    <polyline points="415,130 415,155" />
+    <polyline points="755,130 755,155" />
+    <polyline points="920,130 920,155" />
+  </g>
+  <g id="numeric-subtyping">
+    <g id="num-int-range-0-200">
+      <rect x="35" y="240" width="200" height="40" />
+      <text x="135" y="264.10753822px">@IntRange(from=0,to=200)</text>
+    </g>
+    <g id="num-int-1">
+      <rect x="310" y="400" width="130" height="40" />
+      <text x="375" y="424.10753822px">@IntVal(1)</text>
+    </g>
+    <g id="num-int-1-2">
+      <rect x="310" y="320" width="130" height="40" />
+      <text x="375" y="344.10753822px">@IntVal({1,2})</text>
+    </g>
+    <g id="num-int-range-0-1">
+      <rect x="35" y="320" width="200" height="40" />
+      <text x="135" y="344.10753822px">@IntRange(from=0,to=1)</text>
+    </g>
+    <g id="num-double-1">
+      <rect x="490" y="320" width="130" height="40" />
+      <text x="555" y="344.10753822px">@DoubleVal(1.0)</text>
+    </g>
+    <g id="num-double-0-2">
+      <rect x="270" y="240" width="210" height="40" />
+      <text x="375" y="264.10753822px">@DoubleVal({0.0,1.0,2.0})</text>
+    </g>
+
+    <polyline points="135,280 135,320" />
+    <polyline points="375,280 375,320" />
+    <polyline points="375,360 375,400" />
+
+    <polyline points="195,280 315,320" />
+    <polyline points="315,280 195,320" />
+    <polyline points="435,280 555,320" />
+    <polyline points="195,360 315,400" />
+    <polyline points="555,360 435,400" />
+
+  </g>
+  <g id="array-subtyping">
+    <g id="string-a">
+      <rect x="800" y="520" width="130" height="40" />
+      <text x="865" y="544.10753822px">@StringVal("a")</text>
+    </g>
+    <g id="string-a-aa">
+      <rect x="650" y="450" width="190" height="40" />
+      <text x="745" y="474.10753822px">@StringVal({"a","aa"})</text>
+    </g>
+    <g id="string-a-b">
+      <rect x="895" y="450" width="180" height="40" />
+      <text x="985" y="474.10753822px">@StringVal({"a","b"})</text>
+    </g>
+    <g id="string-a-aa-b">
+      <rect x="755" y="380" width="220" height="40" />
+      <text x="865" y="404.10753822px">@StringVal({"a","aa","b"})</text>
+    </g>
+    <g id="matches-regex-a-star">
+      <rect x="650" y="320" width="170" height="40" />
+      <text x="740" y="344.10753822px">@MatchesRegex("a*")</text>
+    </g>
+    <g id="matches-regex-a-star-b-star">
+      <rect x="630" y="240" width="230" height="40" />
+      <text x="745" y="264.10753822px">@MatchesRegex({"a*", "b*"})</text>
+    </g>
+
+    <g id="arraylen-1">
+      <rect x="1040" y="380" width="130" height="40" />
+      <text x="1105" y="404.10753822px">@ArrayLen(1)</text>
+    </g>
+    <g id="arraylen-1-2">
+      <rect x="915" y="310" width="140" height="40" />
+      <text x="985" y="334.10753822px">@ArrayLen({1,2})</text>
+    </g>
+    <g id="arraylenrange">
+      <rect x="895" y="240" width="180" height="40" />
+      <text x="985" y="264.10753822px">@ArrayLenRange(0,200)</text>
+    </g>
+
+    <polyline points="985,280 985,310" />
+    <polyline points="1015,350 1075,380" />
+    <polyline points="955,350 895,380" />
+    <polyline points="835,420 775,450" />
+    <polyline points="895,420 955,450" />
+    <polyline points="1075,420 1015,450" />
+    <polyline points="955,490 895,520" />
+    <polyline points="775,490 835,520" />
+    <polyline points="720,360 720,450" />
+    <polyline points="720,280 720,320" />
+    <polyline points="820,280 870,380" />
+
+  </g>
+</svg>
diff --git a/docs/manual/formatter-checker.tex b/docs/manual/formatter-checker.tex
new file mode 100644
index 0000000..36ce18c
--- /dev/null
+++ b/docs/manual/formatter-checker.tex
@@ -0,0 +1,591 @@
+\htmlhr
+\chapterAndLabel{Format String Checker}{formatter-checker}
+
+% VERIFICATION
+\begin{sloppypar}
+The Format String Checker
+prevents use of incorrect format strings
+in format methods such as
+\sunjavadoc{java.base/java/io/PrintStream.html\#printf(java.lang.String,java.lang.Object...)}{System.out.printf}
+and \sunjavadoc{java.base/java/lang/String.html\#format(java.lang.String,java.lang.Object...)}{String.format}.
+\end{sloppypar}
+
+The Format String Checker warns you if you write an invalid format string,
+and it warns you if the other arguments are not consistent with the format
+string (in number of arguments or in their types).
+Here are examples of errors that the
+Format String Checker
+detects at compile time.
+Section~\ref{formatter-guarantees} provides more details.
+
+% BUG FINDER
+% The Format String Checker helps to prevent bugs that are the result of an
+% incorrect use of format methods such as
+% \sunjavadoc{java.base/java/io/PrintStream.html\#printf(java.lang.String(java.lang.Object...)}{System.out.printf}.
+
+% VERIFICATION
+
+% BUG FINDER
+% The Format String Checker helps to prevent bugs in two ways:
+%
+% \begin{itemize}
+% \item An error is issued if a format method would fail to execute at
+%     runtime.  For example, if an invalid format string is passed.
+% \item A warning is issued for possibly legal but likely unintended uses of a
+%     format method. For example, if unused format arguments are passed.
+% \end{itemize}
+%
+% \noindent
+% Following are examples of common errors that the Format String Checker detects at
+% compile time, more details are provided in Section~\ref{formatter-guarantees}.
+
+\begin{Verbatim}
+  String.format("%y", 7);           // error: invalid format string
+
+  String.format("%d", "a string");  // error: invalid argument type for %d
+
+  String.format("%d %s", 7);        // error: missing argument for %s
+  String.format("%d", 7, 3);        // warning: unused argument 3
+  String.format("{0}", 7);          // warning: unused argument 7, because {0} is wrong syntax
+\end{Verbatim}
+
+
+\begin{sloppypar}
+To run the Format String Checker, supply the
+\code{-processor org.checkerframework.checker.formatter.FormatterChecker} command-line option to javac.
+\end{sloppypar}
+
+
+\sectionAndLabel{Formatting terminology}{formatter-terminology}
+
+Printf-style formatting takes as an argument a \emph{format string} and a
+list of arguments.  It produces a new string in which each \emph{format
+  specifier} has been replaced by the corresponding argument.
+The format specifier determines how the format argument is converted to a
+string.
+%% Redundant
+%  The Java standard library provides printf-style formatting in \emph{format methods} such as
+% \sunjavadoc{java.base/java/lang/String.html\#format(java.lang.String,java.lang.Object...)}{String.format}
+% and
+% \sunjavadoc{java.base/java/io/PrintStream.html\#printf(java.lang.String,java.lang.Object...)}{System.out.printf}.
+A format specifier is introduced by a \code{\%} character. For example,
+\code{String.format("The \%s is \%d.","answer",42)} yields
+\code{"The answer is 42."}.  \code{"The \%s is \%d."} is
+the format string, \code{"\%s"} and \code{"\%d"} are the format specifiers;
+\code{"answer"} and \code{42} are format arguments.
+
+
+\sectionAndLabel{Format String Checker annotations}{formatter-annotations}
+
+The \refqualclass{checker/formatter/qual}{Format} qualifier on a string type
+indicates a
+\sunjavadoc{java.base/java/util/Formatter.html\#syntax}{\textrm{valid format string}}.
+A programmer rarely writes the \<@Format> annotation, as it is inferred for
+string literals.  A programmer may need to write it on fields and on method
+signatures.
+
+%% This is premature; it is not discussed here, and it was already
+%% mentioned briefly to introduce readers to the idea, so that isn't
+%% necessary either.
+% Passing a valid format string to a format method does not guarantee that the
+% invocation will succeed. The format method invocation \code{String.format("\%d","hello")}
+% for example, will fail despite the fact that \code{"\%d"} is valid.
+
+The \refqualclass{checker/formatter/qual}{Format} qualifier is parameterized with
+a list of conversion categories that impose restrictions on the format arguments.
+Conversion categories are explained in more detail in
+Section~\ref{formatter-categories}.  The type qualifier for \code{"\%d \%f"} is
+for example \code{@Format(\{INT, FLOAT\})}.
+
+Consider the below \<printFloatAndInt> method.  Its parameter must be a
+format string that can be used in a format method, where the first format
+argument is ``float-like'' and the second format argument is
+``integer-like''.  The type of its parameter, \<@Format(\{FLOAT, INT\})
+String>, expresses that contract.
+
+\begin{Verbatim}
+    void printFloatAndInt(@Format({FLOAT, INT}) String fs) {
+        System.out.printf(fs, 3.1415, 42);
+    }
+
+    printFloatAndInt("Float %f, Number %d");  // OK
+    printFloatAndInt("Float %f");             // error
+\end{Verbatim}
+
+\begin{figure}
+\includeimage{formatter-hierarchy}{3.5cm}
+\caption{The
+  Format String Checker type qualifier hierarchy.
+  The type qualifiers are applicable to \<CharSequence> and its subtypes.
+  The figure does not show the subtyping rules among different
+  \code{@Format(...)}
+  qualifiers; see
+  Section~\ref{formatter-format-subtyping}.
+}
+\label{fig-formatter-hierarchy}
+\end{figure}
+
+Figure~\ref{fig-formatter-hierarchy} shows all the type qualifiers.
+The annotations other than \<@Format> are only used
+internally and cannot be written in your code.
+\refqualclass{checker/formatter/qual}{InvalidFormat} indicates an invalid format
+string --- that is, a string that cannot be used as a format string.  For
+example, the type of \code{"\%y"} is \<@InvalidFormat String>.
+\refqualclass{checker/formatter/qual}{FormatBottom} is the type of the
+\code{null} literal.
+\refqualclass{checker/formatter/qual}{UnknownFormat} is the default that is
+applied to strings that are not literals and on which the user has not
+written a \<@Format> annotation.
+
+There is also a \refqualclass{checker/formatter/qual}{FormatMethod}
+annotation; see Section~\ref{formatter-FormatMethod}.
+
+
+\subsectionAndLabel{Conversion Categories}{formatter-categories}
+
+Given a format specifier, only certain format arguments are compatible with
+it, depending on its ``conversion'' --- its last, or last two,
+characters.  For example, in the format specifier \code{"\%d"}, the
+conversion \code{d} restricts the corresponding format argument
+to be ``integer-like'':
+
+\begin{Verbatim}
+    String.format("%d", 5);         // OK
+    String.format("%d", "hello");   // error
+\end{Verbatim}
+
+\noindent Many conversions enforce the same restrictions.  A set of
+restrictions is represented as a \emph{conversion
+category}. The ``integer like'' restriction is for example the conversion
+category \refenum{checker/formatter/qual}{ConversionCategory}{INT}{}\@.  The following conversion categories are defined in the
+\code{\refclass{checker/formatter/qual}{ConversionCategory}} enumeration:
+
+\begin{description}
+\item{\refenum{checker/formatter/qual}{ConversionCategory}{GENERAL}{}} imposes no restrictions on a format argument's type. Applicable for
+    conversions b, B, h, H, s, S.
+
+\item{\refenum{checker/formatter/qual}{ConversionCategory}{CHAR}{}} requires that a format argument represents a Unicode character.
+    Specifically, \code{char}, \code{Character}, \code{byte},
+    \code{Byte}, \code{short}, and \code{Short} are allowed.
+    \code{int} or \code{Integer} are allowed if
+    \code{Character.isValidCodePoint(argument)} would return \code{true}
+    for the format argument. (The Format String Checker permits any \<int>
+    or \<Integer> without issuing a warning or error --- see
+    Section~\ref{formatter-missed-alarms}.)
+    Applicable for conversions c, C.
+
+\item{\refenum{checker/formatter/qual}{ConversionCategory}{INT}{}} requires that a format argument represents an integral type. Specifically,
+    \code{byte}, \code{Byte}, \code{short}, \code{Short},
+    \code{int} and \code{Integer}, \code{long},
+    \code{Long}, and \code{BigInteger} are allowed. Applicable for
+    conversions d, o, x, X.
+
+\item{\refenum{checker/formatter/qual}{ConversionCategory}{FLOAT}{}} requires that a format argument represents a floating-point type.  Specifically,
+    \code{float}, \code{Float}, \code{double},
+    \code{Double}, and \code{BigDecimal} are allowed. Surprisingly, integer
+    values are not allowed. Applicable for
+    conversions e, E, f, g, G, a, A.
+
+\item{\refenum{checker/formatter/qual}{ConversionCategory}{TIME}{}} requires that a format argument represents a date or time.
+    Specifically, \code{long}, \code{Long}, \code{Calendar}, and
+    \code{Date} are allowed.  Applicable for conversions t, T.
+
+\item{\refenum{checker/formatter/qual}{ConversionCategory}{UNUSED}{}} imposes no restrictions on a format argument. This is the case if a
+    format argument is not used as replacement for any format specifier.
+    \code{"\%2\$s"} for example ignores the first format argument.
+    % This conversion category is similar to GENERAL, but does allow objects that
+    % throw exceptions in \code{toString} and \code{formatTo}.
+\end{description}
+
+\noindent All conversion categories accept \code{null}.
+Furthermore, \<null> is always a legal argument array, because it is treated
+as supplying \<null> to each format specifer.  For example,
+\<String.format("\%d \%f \%s", (Object[]) null)> evaluates to \<"null null null">.
+
+The same format argument may serve as a replacement for multiple format specifiers.
+Until now, we have assumed that the format specifiers simply consume format arguments left to right.
+But there are two other ways for a format specifier to select a format argument:
+
+\begin{itemize}
+\item $n$\code{\$} specifies a one-based index $n$. In the
+    format string \code{"\%2\$s"}, the format specifier selects the
+    second format argument.
+\item The \code{<} \emph{flag} references the format argument
+    that was used by the previous format specifier. In the format string
+    \code{"\%d \%<d"} for example, both format specifiers select the first
+    format argument.
+\end{itemize}
+
+\noindent
+In the following example,
+the format argument must be compatible with both conversion
+categories, and can therefore be neither a \code{Character} nor a \code{long}.
+
+\begin{Verbatim}
+    format("Char %1$c, Int %1$d", (int)42);            // OK
+    format("Char %1$c, Int %1$d", new Character(42));  // error
+    format("Char %1$c, Int %1$d", (long)42);           // error
+\end{Verbatim}
+
+Only three additional conversion categories are needed represent all possible
+intersections of previously-mentioned conversion categories:
+
+\begin{description}
+\item{\refenum{checker/formatter/qual}{ConversionCategory}{NULL}{}} is used if no object of any type can be
+    passed as parameter. In this case, the only legal value is \code{null}.
+    For example, the format string \code{"\%1\$f \%1\$c"} requires that the first
+    format argument be \code{null}.  Passing either \code{4} or
+    \code{4.2} would lead to an exception.
+\item{\refenum{checker/formatter/qual}{ConversionCategory}{CHAR\_AND\_INT}{}} is used if a format argument is restricted by a \refenum{checker/formatter/qual}{ConversionCategory}{CHAR}{} and a \refenum{checker/formatter/qual}{ConversionCategory}{INT}{} conversion category (\code{CHAR} $\cap$ \code{INT}).
+\item{\refenum{checker/formatter/qual}{ConversionCategory}{INT\_AND\_TIME}{}} is used if a format argument is restricted by an \refenum{checker/formatter/qual}{ConversionCategory}{INT}{} and a \refenum{checker/formatter/qual}{ConversionCategory}{TIME}{} conversion category (\code{INT} $\cap$ \code{TIME}).
+\end{description}
+
+\noindent All other intersections lead to already existing conversion categories.
+For example, \code{GENERAL} $\cap$ \code{CHAR} $=$ \code{CHAR} and
+\code{UNUSED} $\cap$ \code{GENERAL} $=$ \code{GENERAL}.
+
+Figure~\ref{fig-formatter-cat} summarizes the subset
+relationship among all conversion categories.
+
+\begin{figure}[thbp]
+    \includeimage{formatter-categories}{7.8cm}
+    \caption{The subset relationship
+        among conversion categories.}
+    \label{fig-formatter-cat}
+\end{figure}
+
+
+\subsectionAndLabel{Subtyping rules for \<@Format>}{formatter-format-subtyping}
+
+Here are the subtyping rules among different
+\refqualclass{checker/formatter/qual}{Format}
+qualifiers.
+It is legal to:
+
+\begin{itemize}
+\item use a format string with a weaker (less restrictive) conversion category than required.
+\item use a format string with fewer format specifiers than required.
+  Although this is legal a warning is issued because most occurrences of
+  this are due to programmer error.
+\end{itemize}
+
+The following example shows the subtyping rules in action:
+
+\begin{Verbatim}
+    @Format({FLOAT, INT}) String f;
+
+    f = "%f %d";       // OK
+    f = "%s %d";       // OK, %s is weaker than %f
+    f = "%f";          // warning: last argument is ignored
+    f = "%f %d %s";    // error: too many arguments
+    f = "%d %d";       // error: %d is not weaker than %f
+
+    String.format(f, 0.8, 42);
+\end{Verbatim}
+
+\sectionAndLabel{What the Format String Checker checks}{formatter-guarantees}
+
+% VERIFICATION
+If the Format String Checker issues no errors, it provides the following guarantees:
+
+\begin{enumerate}
+\item
+The following guarantees hold for every format method invocation:
+
+\begin{enumerate}
+    \item The format method's first parameter (or second if a \sunjavadoc{java.base/java/util/Locale.html}{Locale} is provided) is a valid
+        format string (or \code{null}).
+
+    \item A warning is issued if one of the format string's conversion categories is \code{UNUSED}.
+        \label{formatter-unused-category-warning}
+    \item None of the format string's conversion categories is \code{NULL}.
+        \label{formatter-null-category-error}
+\end{enumerate}
+
+\item If the format arguments are passed to the format method as varargs, the
+Format String Checker guarantees the following additional properties:
+
+\begin{enumerate}
+\item No fewer format arguments are passed than required by the format string.
+\item A warning is issued if more format arguments are passed than required by the format string.
+\item Every format argument's type satisfies its conversion category's restrictions.
+\end{enumerate}
+
+\item If the format arguments are passed to the format method as an array,
+a warning is issued by the Format String Checker.
+        \label{formatter-array-warning}
+\end{enumerate}
+
+
+\noindent Following are examples for every guarantee:
+
+\begin{Verbatim}
+    String.format("%d", 42);                      // OK
+    String.format(Locale.GERMAN, "%d", 42);       // OK
+    String.format(new Object());                  // error (1a)
+    String.format("%y");                          // error (1a)
+    String.format("%2$s", "unused", "used");      // warning (1b)
+    String.format("%1$d %1$f", 5.5);              // error (1c)
+    String.format("%1$d %1$f %d", null, 6);       // error (1c)
+    String.format("%s");                          // error (2a)
+    String.format("%s", "used", "ignored");       // warning (2b)
+    String.format("%c",4.2);                      // error (2c)
+    String.format("%c", (String)null);            // error (2c)
+    String.format("%1$d %1$f", new Object[]{1});  // warning (3)
+    String.format("%s", new Object[]{"hello"});   // warning (3)
+\end{Verbatim}
+
+\subsectionAndLabel{Possible false alarms}{formatter-false-alarms}
+
+There are three cases in which the Format String Checker may issue a
+warning or error, even though the code cannot fail at run time.
+(These are in addition to the general conservatism of a type system:  code
+may be correct because of application invariants that are not captured by
+the type system.)
+In each of these cases, you can rewrite the code, or you can manually check
+it and write a \code{@SuppressWarnings} annotation if you can reason that
+the code is correct.
+
+
+Case \ref{formatter-unused-category-warning}:
+  Unused format arguments.  It is legal to provide more arguments than are
+  required by the format string; Java ignores the extras.  However, this is
+  an uncommon case.  In practice, a mismatch between the number of format
+  specifiers and the number of format arguments is usually an error.
+
+Case \ref{formatter-null-category-error}:
+  Format arguments that can only be \code{null}.
+  It is legal to write a format string that permits only null arguments and
+  throws an exception for any other argument.  An example is
+  \code{String.format("\%1\$d \%1\$f", null)}.
+  The Format String Checker forbids such a format string.
+  If you should ever need such a format string, simply replace the problematic
+  format specifier with \code{"null"}.  For example, you would replace the
+  call above by \code{String.format("null null")}.
+
+Case \ref{formatter-array-warning}:
+  Array format arguments.
+  The Format String Checker performs no analysis of
+  arrays, only of varargs invocations.  It is better style to use varargs
+  when possible.
+
+
+% BUG FINDER
+% Whenever a format method invocation is found in your code, the Format String
+% Checker performs certain checks. If it issues an \emph{error}, the invocation
+% will definitely fail at runtime. If a \emph{warning} is issued, the Format
+% String Checker detected a common source for errors, and it is very likely that
+% the invocation contains a bug.
+%
+% \noindent I.) The following checks are run for every format method invocation. The checks:
+%
+% \begin{enumerate}
+% \item Issue an \emph{error}, if the format method's first format argument (or
+%     second if a \sunjavadoc{java.base/java/util\Locale.html}{Locale} is provided) is not a
+%     \code{String} annotated with the \code{@Format} qualifier.
+% \item Issue a \emph{warning}, if any of the \code{@Format} string's
+%     conversion categories is \code{UNUSED}.
+% \end{enumerate}
+%
+% \noindent II.) If the format arguments are passed to the format method as varargs, the
+% Format String Checker performs the following additional checks, that:
+%
+% \begin{enumerate}
+% \item Issue an \emph{error}, if fewer format arguments are passed than required
+%     by the \code{@Format} qualifier.
+% \item Issue a \emph{warning}, if more format arguments are passed than required
+%     by the \code{@Format} qualifier.
+% \item The following checks are performed for every format argument (unless the
+%     format argument is the \code{null} literal) and the associated conversion category
+%     from the \code{@Format} qualifier. The checks:
+%     \begin{enumerate}
+%         \item Issue a \emph{warning}, if the conversion category is \code{NULL}.
+%         \item Issue a \emph{warning}, if the format argument's type does not satisfy
+%             the conversion category's restrictions.
+%     \end{enumerate}
+% \end{enumerate}
+%
+% \noindent III.) If the format arguments are passed to the format method as array, the
+% Format String Checker performs the following checks instead, that:
+%
+% \begin{enumerate}
+% \item Issue a \emph{warning}, if any of the \code{@Format} string's
+%     conversion categories is \code{NULL} (unless the array is the
+%     null-array literal \code{(Object[])null}).
+% \end{enumerate}
+%
+% \noindent Following are examples for every check:
+%
+% \begin{Verbatim}
+%     String.format("%d", 42);                     // ok
+%     String.format(Locale.GERMAN, "%d", 42);      // ok (I 1)
+%     String.format(new Object());                 // error (I 1)
+%     String.format("%y");                         // error (I 1)
+%     String.format("%2$s","unused","used");       // warning (I 2)
+%     String.format("%s");                         // error (II 1)
+%     String.format("%s","used","ignored");        // warning (II 2)
+%     String.format("%1$d %1$f", null);            // ok (II 3)
+%     String.format("%d", null);                   // ok (II 3)
+%     String.format("%1$d %1$f", 4);               // warning (II 3 a)
+%     String.format("%c",4.2);                     // warning (II 3 b)
+%     String.format("%1$d %1$f", new Object[]{1}); // warning (III 1)
+% \end{Verbatim}
+
+\subsectionAndLabel{Possible missed alarms}{formatter-missed-alarms}
+
+The Format String Checker helps prevent bugs by detecting, at compile time,
+which invocations of format methods will fail. While the Format String Checker
+finds most of these invocations, there are cases in which a format method call
+will fail even though the Format String Checker issued neither errors nor
+warnings. These cases are:
+
+\begin{enumerate}
+\item The format string is \code{null}. Use the \ahrefloc{nullness-checker}{Nullness Checker} to prevent this.
+\item A format argument's \code{toString} method throws an exception.
+\item A format argument implements the \code{Formattable} interface and throws an
+    exception in the \code{formatTo} method.
+\item A format argument's conversion category is \code{CHAR} or \code{CHAR\_AND\_INT},
+    and the passed value is an \code{int} or \code{Integer}, and
+    \code{Character.isValidCodePoint(argument)} returns \code{false}.
+% VERIFICATION
+% BUG FINDER
+% \item Illegal format arguments are passed as an array (instead of varargs).
+\end{enumerate}
+
+\noindent The following examples illustrate these limitations:
+
+% VERIFICATION
+\begin{Verbatim}
+    class A {
+        public String toString() {
+            throw new Error();
+        }
+    }
+
+    class B implements Formattable {
+        public void formatTo(Formatter fmt, int f,
+                int width, int precision) {
+            throw new Error();
+        }
+    }
+
+    // The checker issues no errors or warnings for the
+    // following illegal invocations of format methods.
+    String.format(null);          // NullPointerException (1)
+    String.format("%s", new A()); // Error (2)
+    String.format("%s", new B()); // Error (3)
+    String.format("%c", (int)-1); // IllegalFormatCodePointException (4)
+\end{Verbatim}
+
+% BUG FINDER
+% \begin{Verbatim}
+%     class A {
+%         public String toString() {
+%             throw new Error();
+%         }
+%     }
+%
+%     class B implements Formattable {
+%         public void formatTo(Formatter fmt, int f,
+%                 int width, int precision) {
+%             throw new Error();
+%         }
+%     }
+%
+%     // the checker issues no errors or warnings for the
+%     // following illegal invocations of format methods
+%     String.format(null);          // NullPointerException (1)
+%     String.format("%s", new A()); // Error (2)
+%     String.format("%s", new B()); // Error (3)
+%     String.format("%d", new Object[]{4.2}); // IllegalFormatConversionException (4)
+%     String.format("%c", (int)-1); // IllegalFormatCodePointException (5)
+% \end{Verbatim}
+
+
+\sectionAndLabel{Implicit qualifiers}{formatter-implicit}
+
+The Format String Checker adds implicit
+qualifiers, reducing the number of annotations that must appear in your code
+(see Section~\ref{effective-qualifier}).
+The checker implicitly adds the \code{@Format} qualifier with the appropriate
+conversion categories to any String literal that is a valid format string.
+
+
+\sectionAndLabel{@FormatMethod}{formatter-FormatMethod}
+
+Your project may contain methods that forward their arguments to a format method.
+Consider for example the following \code{log} method:
+
+\begin{Verbatim}
+@FormatMethod
+void log(String format, Object... args) {
+    if (enabled) {
+        logfile.print(indent_str);
+        logfile.printf(format , args);
+    }
+}
+\end{Verbatim}
+
+You should annotate such a method with the
+\refqualclass{checker/formatter/qual}{FormatMethod} annotation,
+which indicates that the \<String> argument is a format string for the
+remaining arguments.
+
+
+\sectionAndLabel{Testing whether a format string is valid}{formatter-run-time-tests}
+
+% Copied from the Regex Checker.
+% When one is improved, improve the other as well.
+
+The Format String Checker automatically determines whether each \<String>
+literal is a valid format string or not.  When a string is computed or is
+obtained from an external resource, then the string must be trusted or tested.
+
+One way to test a string is to call the
+\refmethod{checker/formatter/util}{FormatUtil}{asFormat}{-java.lang.String-org.checkerframework.checker.formatter.qual.ConversionCategory...-}
+method to check whether the format string is valid and its format
+specifiers match certain conversion categories.
+If this is not the case, \<asFormat> raises an exception.  Your code should
+catch this exception and handle it gracefully.
+
+The following code examples may fail at run time, and therefore they do not
+type check.  The type-checking errors are indicated by comments.
+
+\begin{Verbatim}
+Scanner s = new Scanner(System.in);
+String fs = s.next();
+System.out.printf(fs, "hello", 1337);          // error: fs is not known to be a format string
+\end{Verbatim}
+
+\begin{Verbatim}
+Scanner s = new Scanner(System.in);
+@Format({GENERAL, INT}) String fs = s.next();  // error: fs is not known to have the given type
+System.out.printf(fs, "hello", 1337);          // OK
+\end{Verbatim}
+
+\noindent The following variant does not throw a run-time error, and
+therefore passes the type-checker:
+
+\begin{Verbatim}
+Scanner s = new Scanner(System.in);
+String format = s.next()
+try {
+    format = FormatUtil.asFormat(format, GENERAL, INT);
+} catch (IllegalFormatException e) {
+    // Replace this by your own error handling.
+    System.err.println("The user entered the following invalid format string: " + format);
+    System.exit(2);
+}
+// fs is now known to be of type: @Format({GENERAL, INT}) String
+System.out.printf(format, "hello", 1337);
+\end{Verbatim}
+
+\noindent
+To use the \refclass{checker/formatter/util}{FormatUtil} class, the \<checker-util.jar> file
+must be on the classpath at run time.
+
+%  LocalWords:  printf InvalidFormat Formatter FormatBottom specifier's
+%  LocalWords:  ConversionCategory isValidCodePoint BigInteger BigDecimal
+%  LocalWords:  varargs TODO Formattable formatTo FormatUtil java qual
+%  LocalWords:  asFormat printFloatAndInt formatter CharSequence
+%%  LocalWords:  UnknownFormat FormatMethod
diff --git a/docs/manual/generics.tex b/docs/manual/generics.tex
new file mode 100644
index 0000000..3b61370
--- /dev/null
+++ b/docs/manual/generics.tex
@@ -0,0 +1,1106 @@
+\htmlhr
+\chapterAndLabel{Generics and polymorphism}{polymorphism}
+
+Section~\ref{generics} describes support for Java generics (also known as
+``parametric polymorphism'').
+Section~\ref{method-qualifier-polymorphism} describes polymorphism over
+type qualifiers for methods. Section~\ref{class-qualifier-polymorphism} describes
+polymorphism over type qualifiers for classes.
+
+
+\sectionAndLabel{Generics (parametric polymorphism or type polymorphism)}{generics}
+
+The Checker Framework fully supports
+type-qualified Java generic types and methods (also known as ``parametric
+polymorphism'').
+When instantiating a generic type,
+clients supply the qualifier along with the type argument, as in
+\code{List<@NonNull String>}.
+When using a type variable \code{T} within the implementation of a generic type,
+typically no type qualifier is written (see Section~\ref{type-variable-use});
+rather, the instantiation of the type parameter is restricted (see
+Section~\ref{generics-instantiation}).
+
+
+\subsectionAndLabel{Raw types}{generics-raw-types}
+
+Before running any pluggable type-checker, we recommend that you eliminate
+raw types from your code (e.g., your code should use \code{List<...>} as
+opposed to \code{List}).
+Your code should compile without warnings when using the standard Java
+compiler and the \<-Xlint:unchecked -Xlint:rawtypes> command-line options.
+Using generics helps prevent type errors just as using a pluggable
+type-checker does, and makes the Checker Framework's warnings easier to
+understand.
+
+If your code uses raw types, then the Checker Framework will do its best to
+infer the Java type arguments and the type qualifiers.  By default these
+inferred types are ignored in subtyping checks. If you supply the
+command-line option \<-AignoreRawTypeArguments=false> you will see errors
+from raw types.
+
+
+\subsectionAndLabel{Restricting instantiation of a generic class}{generics-instantiation}
+
+When you define a generic class in Java, the \<extends> clause
+of the generic type parameter (known as the ``upper bound'') requires that
+the corresponding type argument must be a subtype of the bound.
+For example, given the definition
+\verb|class G<T extends Number> {...}|,
+the upper bound is \<Number>
+and a client can instantiate it as \code{G<Number>} or \code{G<Integer>}
+but not \code{G<Date>}.
+
+You can write a type qualifier on the \<extends> clause to make the upper
+bound a qualified type.  For example, you can declare that a generic list class can hold only non-null values:
+% Similarly, a generic map
+% class might indicate it requires an immutable key type, but that it
+% supports both nullable and non-null value types.
+
+\begin{Verbatim}
+  class MyList<T extends @NonNull Object> {...}
+
+  MyList<@NonNull String> m1;       // OK
+  MyList<@Nullable String> m2;      // error
+\end{Verbatim}
+
+That is, in the above example, all
+arguments that replace \code{T} in \code{MyList<T>} must be subtypes of
+\code{@NonNull Object}.
+
+
+\subsubsectionAndLabel{Syntax for upper and lower bounds}{generics-bounds-syntax}
+
+Conceptually, each generic type parameter has two bounds --- a lower bound
+and an upper bound --- and at instantiation, the type argument must be
+within the bounds.  Java only allows you to specify the upper bound; the
+lower bound is implicitly the bottom type \<void>.  The Checker Framework
+gives you more power:  you can specify both an upper and lower bound for
+type parameters.
+Write the upper bound on the \<extends> clause, and
+write the lower bound on the type variable.
+
+\begin{Verbatim}
+  class MyList<@LowerBound T extends @UpperBound Object> { ... }
+\end{Verbatim}
+
+You may omit either the upper or the lower bound, and the Checker Framework
+will use a default.
+
+For a discussion of wildcards, see Section~\ref{annotations-on-wildcards}.
+
+For a concrete example, recall the type system of the Regex Checker (see
+Figure~\refwithpage{fig-regex-hierarchy}) in which
+ \<@Regex(0)> :>
+ \<@Regex(1)> :>
+ \<@Regex(2)> :>
+ \<@Regex(3)> :> \ldots.
+
+\begin{Verbatim}
+  class MyRegexes<@Regex(5) T extends @Regex(1) String> { ... }
+
+  MyRegexes<@Regex(0) String> mu;   // error - @Regex(0) is not a subtype of @Regex(1)
+  MyRegexes<@Regex(1) String> m1;   // OK
+  MyRegexes<@Regex(3) String> m3;   // OK
+  MyRegexes<@Regex(5) String> m5;   // OK
+  MyRegexes<@Regex(6) String> m6;   // error - @Regex(6) is not a supertype of @Regex(5)
+\end{Verbatim}
+
+The above declaration states that the upper bound of the type variable
+is \<@Regex(1) String> and the lower bound is \<@Regex(5) void>.  That is,
+arguments that replace \code{T} in \code{MyList<T>} must be subtypes of
+\code{@Regex(1) String} and supertypes of \code{@Regex(5) void}.
+Since \<void> cannot be used to instantiate a generic class, \<MyList> may
+be instantiated with \<@Regex(1) String> through \<@Regex(5) String>.
+
+
+To specify an exact bound, place the same annotation on both bounds.  For example:
+
+\begin{Verbatim}
+  class MyListOfNonNulls<@NonNull T extends @NonNull Object> { ... }
+  class MyListOfNullables<@Nullable T extends @Nullable Object> { ... }
+
+  MyListOfNonNulls<@NonNull Number> v1;      // OK
+  MyListOfNonNulls<@Nullable Number> v2;     // error
+  MyListOfNullables<@NonNull Number> v4;     // error
+  MyListOfNullables<@Nullable Number> v3;    // OK
+\end{Verbatim}
+
+It is an error if the lower bound is not a subtype of the upper bound.
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  class MyClass<@Nullable T extends @NonNull Object>  // error: @Nullable is not a subtype of @NonNull
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+
+\subsubsectionAndLabel{Defaults}{generics-defaults}
+
+A generic type parameter or wildcard is written as \code{class
+  MyClass<\emph{@LowerBound} T extends \emph{@UpperBound} JavaUpperBound>} or as
+\code{MyClass<\emph{@UpperBound} ? super \emph{@LowerBound} JavaLowerBound>}, where
+``\<\emph{@LowerBound}>'' and ``\<\emph{@UpperBound}>'' are type qualifiers.
+
+For lower bounds:
+If no type annotation is written in front of \<?>,
+then the lower bound defaults to \<@\emph{BottomType} void>.
+
+For upper bounds:
+\begin{itemize}
+\item
+If the \<extends> clause is omitted,
+then the upper bound defaults to \<@\emph{TopType} Object>.
+\item
+If the \<extends> clause is written but contains no type qualifier,
+then the normal defaulting rules apply to the type in the \<extends>
+clause (see Section~\ref{climb-to-top}).
+\end{itemize}
+
+The upper-bound rules mean that even though in Java the following two
+declarations are equivalent:
+
+\begin{Verbatim}
+  class MyClass<T>
+  class MyClass<T extends Object>
+\end{Verbatim}
+
+\noindent
+they specify different type qualifiers on the upper bound,
+if the type system's default annotation is not its top annotation.
+
+The Nullness type system is an example.
+
+\begin{Verbatim}
+  class MyClass<T>                 ==  class MyClass<T extends @Nullable Object>
+  class MyClass<T extends Object>  ==  class MyClass<T extends @NonNull Object>
+\end{Verbatim}
+
+The rationale for this choice is:
+\begin{itemize}
+\item
+  The ``\code{<T>}'' in \code{MyClass<T>} means ``fully unconstrained'',
+  and the rules maintain that, without the need for a programmer to
+  change existing code.
+\item
+  The ``\code{Object}'' in \code{MyClass<T extends Object>} is treated
+  exactly like every other occurrence of \code{Object} in the program ---
+  it would be confusing for different occurrences of \code{Object} to mean
+  different annotated types.
+\end{itemize}
+
+Because of these rules, the recommended style is:
+\begin{itemize}
+\item
+  Use ``\code{<T>}'' when there are no constraints on the type qualifiers.
+  This is short and is what already appears in source code.
+\item
+  Whenever you write an \<extends> clause, write an explicit type
+  annotation on it.  For example, for the Nullness Checker, write
+  \code{class MyClass<T>} rather than \code{class MyClass<T extends
+    @Nullable Object>}, and write \code{class MyClass<T extends @NonNull
+    Object>} rather than \code{class MyClass<T extends Object>}.
+\end{itemize}
+
+For further discussion, see Section~\ref{faq-implicit-bounds}.
+
+
+\subsectionAndLabel{Type annotations on a use of a generic type variable}{type-variable-use}
+
+A type annotation on a use of a generic type variable overrides/ignores any type
+qualifier (in the same type hierarchy) on the corresponding actual type
+argument.  For example, suppose that \code{T} is a formal type parameter.
+Then using \code{@Nullable T} within the scope of \code{T} applies the type
+qualifier \code{@Nullable} to the (unqualified) Java type of \code{T}\@.
+This feature is sometimes useful, but more often the implementation of a
+generic type just uses the type variable \code{T}, whose instantiation is
+restricted (see Section~\ref{generics-instantiation}).
+
+Here is an example of applying a type annotation to a generic type
+variable:
+
+\begin{Verbatim}
+  class MyClass2<T> {
+    ...
+    @Nullable T myField = null;
+    ...
+  }
+\end{Verbatim}
+
+\noindent
+The type annotation does not restrict how \code{MyClass2} may be
+instantiated.  In other words, both
+\code{MyClass2<@NonNull String>} and \code{MyClass2<@Nullable String>} are
+legal, and in both cases \code{@Nullable T} means \code{@Nullable String}.
+In \code{MyClass2<@Interned String>},
+\code{@Nullable T} means \code{@Nullable @Interned String}.
+
+% Note that a type annotation on a generic type variable does not act like
+% other type qualifiers.  In both cases the type annotation acts as a type
+% constructor, but as noted above they act slightly differently.
+
+
+% %% This isn't quite right because a type qualifier is itself a type
+% %% constructor.
+% More formally, a type annotation on a generic type variable acts as a type
+% constructor rather than a type qualifier.  Another example of a type
+% constructor is \code{[]}.  Just as \code{T[]} is not the same type as
+% \code{T}, \code{@Nullable T} is not (necessarily) the same type as
+% \code{T}.
+
+
+Defaulting never affects a use of a type variable, even if the type
+variable use has no explicit annotation.  Defaulting helps to choose a
+single type qualifier for a concrete Java class or interface.  By contrast,
+a type variable use represents a set of possible types.
+
+
+\subsectionAndLabel{Annotations on wildcards}{annotations-on-wildcards}
+
+At an instantiation of a generic type, a Java wildcard indicates that some
+constraints are known on the type argument, but the type argument is not known
+exactly.
+For example, you can indicate that the type parameter for variable \<ls> is
+some unknown subtype of \<CharSequence>:
+
+\begin{Verbatim}
+  List<? extends CharSequence> ls;
+  ls = new ArrayList<String>();      // OK
+  ls = new ArrayList<Integer>();     // error: Integer is not a subtype of CharSequence
+\end{Verbatim}
+
+For more details about wildcards, see the
+\href{https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html}{Java
+  tutorial on wildcards} or
+\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.5.1}{JLS
+  \S 4.5.1}.
+
+You can write a type annotation on the bound of a wildcard:
+
+\begin{Verbatim}
+  List<? extends @NonNull CharSequence> ls;
+  ls = new ArrayList<@NonNull String>();    // OK
+  ls = new ArrayList<@Nullable String>();   // error: @Nullable is not a subtype of @NonNull
+\end{Verbatim}
+
+Conceptually, every wildcard has two bounds --- an upper bound and a lower
+bound.  Java only permits you to write one bound.
+You can specify the upper bound with \code{<?\ extends SomeType>}, in which
+case the lower bound is implicitly the bottom type \<void>.
+You can specify the lower bound (with \code{<?\ super OtherType>}), in
+which case the upper bound is implicitly the top type \<Object>.
+The Checker Framework is more flexible:  it lets you similarly write
+annotations on both the upper and lower bound.
+
+To annotate the \emph{implicit} bound, write the type annotation
+before the \<?>.  For example:
+
+\begin{Verbatim}
+  List<@LowerBound ? extends @UpperBound CharSequence> lo;
+  List<@UpperBound ? super @NonNull Number> ls;
+\end{Verbatim}
+
+For an unbounded wildcard (\code{<?>}, with neither
+bound specified), the annotation in front of a wildcard applies
+to both bounds.  The following three declarations are equivalent (except
+that you cannot write the bottom type \<void>; note that
+\sunjavadoc{java.base/java/lang/Void.html}{Void} does not denote the bottom type):
+
+\begin{Verbatim}
+  List<@NonNull ?> lnn;
+  List<@NonNull ? extends @NonNull Object> lnn;
+  List<@NonNull ? super @NonNull void> lnn;
+\end{Verbatim}
+
+\noindent
+Note that the annotation in front of a type parameter always applies to its
+lower bound, because type parameters can only be written with \<extends>
+and never \<super>.
+
+
+% Defaults are as for type variables (see Section~\ref{generics-defaults}),
+% with one exception.
+
+The defaulting rules for
+wildcards also differ from those of type parameters (see
+Section~\ref{inherited-wildcard-annotations}).
+
+
+%% Mike isn't sure that this section pulls its weight, especially since it
+%% doesn't justify why it is desirable to be able to constrain both the
+%% upper and the lower bound of a type.  If readers believe that, they will
+%% be OK with the syntax.
+% \subsubsectionAndLabel{Type parameter declaration annotation rationale}{type-parameter-rationale}
+%
+% It is desirable to be able to constrain both the upper and the lower bound
+% of a type, as in
+%
+% \begin{Verbatim}
+%   class MyClass<T extends @C MyUpperBound super @D void> { ... }
+% \end{Verbatim}
+%
+% However, doing so is not possible due to two limitations of Java's syntax.
+% First, it is illegal to specify both the upper and the lower bound of a
+% type parameter or wildcard.
+% Second, it is impossible to specify a type annotation for a lower
+% bound without also specifying a type (use of \<void> is illegal).
+%
+% Thus, when you wish to specify both bounds, you write one of them
+% explicitly, and you write the other one in front of the type variable name
+% or \<?>.  When you wish to specify two identical bounds, you write a
+% single annotation in front of the type variable name or \<?>.
+
+
+\subsectionAndLabel{Examples of qualifiers on a type parameter}{type-parameter-qualifier-examples}
+
+Recall that \<@Nullable \emph{X}> is a supertype of \<@NonNull \emph{X}>,
+for any \emph{X}\@.
+Most of the following types mean different things:
+
+\begin{Verbatim}
+  class MyList1<@Nullable T> { ... }
+  class MyList1a<@Nullable T extends @Nullable Object> { ... } // same as MyList1
+  class MyList2<@NonNull T extends @NonNull Object> { ... }
+  class MyList2a<T extends @NonNull Object> { ... } // same as MyList2
+  class MyList3<T extends @Nullable Object> { ... }
+\end{Verbatim}
+
+\<MyList1> and \<MyList1a> must be instantiated with a nullable type.
+The implementation of \<MyList1> must be able to consume (store) a null
+value and produce (retrieve) a null value.
+
+\<MyList2> and \<MyList2a> must be instantiated with non-null type.
+The implementation of \<MyList2> has to account for only non-null values --- it
+does not have to account for consuming or producing null.
+
+\<MyList3> may be instantiated either way:
+with a nullable type or a non-null type.  The implementation of \<MyList3> must consider
+that it may be instantiated either way --- flexible enough to support either
+instantiation, yet rigorous enough to impose the correct constraints of the
+specific instantiation.  It must also itself comply with the constraints of
+the potential instantiations.
+
+One way to express the difference among \<MyList1>, \<MyList2>, and
+\<MyList3> is by comparing what expressions are legal in the implementation
+of the list --- that is, what expressions may appear in the ellipsis in the
+declarations above, such as inside a method's body.  Suppose each class
+has, in the ellipsis, these declarations:
+
+\begin{Verbatim}
+  T t;
+  @Nullable T nble;      // Section "Type annotations on a use of a generic type variable", below,
+  @NonNull T nn;         // further explains the meaning of "@Nullable T" and "@NonNull T".
+  void add(T arg) {}
+  T get(int i) {}
+\end{Verbatim}
+
+\noindent
+Then the following expressions would be legal, inside a given
+implementation --- that is, also within the ellipses.
+(Compilable source code appears as file
+\<checker-framework/checker/tests/nullness/generics/GenericsExample.java>.)
+
+\begin{tabular}{|l|c|c|c|c|c|} \hline
+                        & MyList1 & MyList2 & MyList3 \\ \hline
+  t = null;             & OK      & error   & error   \\ \hline
+  t = nble;             & OK      & error   & error   \\ \hline
+  nble = null;          & OK      & OK      & OK      \\ \hline
+  nn = null;            & error   & error   & error   \\ \hline
+  t = this.get(0);      & OK      & OK      & OK      \\ \hline
+  nble = this.get(0);   & OK      & OK      & OK      \\ \hline
+  nn = this.get(0);     & error   & OK      & error   \\ \hline
+  this.add(t);          & OK      & OK      & OK      \\ \hline
+  this.add(nble);       & OK      & error   & error   \\ \hline
+  this.add(nn);         & OK      & OK      & OK      \\ \hline
+\end{tabular}
+
+
+%% This text is not very helpful.
+% The
+% implementation of \code{MyList2} may only place non-null objects in the
+% list and may assume that retrieved elements are non-null.  The
+% implementation of \code{MyList3} is similar in that it may only place
+% non-null objects in the list, because it might be instantiated as, say,
+% \code{MyList3<@NonNull Date>}.  When retrieving elements from the list,
+% the implementation of \code{MyList3} must account for the fact that
+% elements of \code{MyList3} may be null, because it might be instantiated
+% as, say, \code{MyList3<@Nullable Date>}.
+The differences are more
+significant when the qualifier hierarchy is more complicated than just
+\<@Nullable> and \<@NonNull>.
+
+\subsectionAndLabel{Covariant type parameters}{covariant-type-parameters}
+
+Java types are \emph{invariant} in their type parameter.  This means that
+\code{A<X>} is a subtype of \code{B<Y>} only if \<X> is identical to \<Y>.  For
+example, \code{ArrayList<Number>} is a subtype of \code{List<Number>}, but
+neither \code{ArrayList<Integer>} nor \code{List<Integer>} is a subtype of
+\code{List<Number>}.  (If they were, there would be a loophole in the Java
+type system.)  For the same reason, type parameter annotations are treated
+invariantly.  For example, \code{List<@Nullable String>} is not a subtype
+of \code{List<String>}.
+
+When a type parameter is used in a read-only way --- that is, when clients
+read values of that type from the class but never pass values of that type
+to the class --- then it is safe for the
+type to be \emph{covariant} in the type parameter.  Use the
+\refqualclass{framework/qual}{Covariant} annotation to indicate this.
+When a type parameter is covariant, two instantiations of the class with
+different type arguments have the same subtyping relationship as the type
+arguments do.
+
+For example, consider \<Iterator>.  A client can read elements but not
+write them, so \code{Iterator<@Nullable String>} can be a subtype of
+\code{Iterator<String>} without introducing a hole in the type system.
+Therefore, its type parameter is annotated with
+\refqualclass{framework/qual}{Covariant}.
+The first type parameter of \<Map.Entry> is also covariant.
+Another example would be the type parameter of a hypothetical class
+\<ImmutableList>.
+
+The \<@Covariant> annotation is trusted but not checked.
+If you incorrectly specify as covariant a type parameter that can be
+written (say, the class supports a
+\<set> operation or some other mutation on an object of that type), then
+you have created an unsoundness in the type system.
+For example, it would be incorrect to annotate the type parameter of
+\<ListIterator> as covariant, because \<ListIterator> supports a \<set>
+operation.
+
+
+\subsectionAndLabel{Method type argument inference and type qualifiers}{infer-method-type-qualifiers}
+
+Sometimes method type argument inference does not interact well with
+type qualifiers. In such situations, you might need to provide
+explicit method type arguments, for which the syntax is as follows:
+
+\begin{alltt}
+    Collections.<@MyTypeAnnotation Object>sort(l, c);
+\end{alltt}
+
+\noindent
+This uses Java's existing syntax for specifying a method call's type arguments.
+
+
+\subsectionAndLabel{The Bottom type}{bottom-type}
+
+Many type systems have a \<*Bottom> type that is used only for the \<null>
+value, dead code, and some erroneous situations.  A programmer should
+rarely write the bottom type.
+
+One use is on a lower bound, to indicate that any type qualifier is
+permitted.  A lower-bounded wildcard indicates that a consumer method can
+accept a collection containing any Java type above some Java type, and you
+can add the bottom type qualifier as well:
+
+\begin{Verbatim}
+public static void addNumbers(List<? super @SignednessBottom Integer> list) { ... }
+\end{Verbatim}
+
+
+\sectionAndLabel{Qualifier polymorphism for methods\label{qualifier-polymorphism}}{method-qualifier-polymorphism}
+
+Type qualifier polymorphism permits a single method to have multiple different qualified
+type signatures.
+
+Here is where a polymorphic qualifier (e.g., \<@PolyNull>) can be used:
+\begin{itemize}
+\item Polymorphic qualifiers are most often used in method signatures.
+  See the examples below in Section~\ref{qualifier-polymorphism-examples}.
+\item Polymorphic qualifiers can also be written in method bodies
+  (implementations).
+\item If you can use generics, you typically do not need to use a
+  polymorphic qualifier.  Do not write a polymorphic qualifier on a type
+  variable declaration.
+\item Polymorphic qualifiers may not be used on a class declaration.
+  To apply qualifier polymorphism to classes, use a class qualifier
+  parameter; see Section~\ref{class-qualifier-polymorphism}.
+\item A polymorphic qualifier may be used on a field declaration only in a
+  class with a class qualifier parameter; see Section~\ref{class-qual-param-field}.
+\item If a class has a class qualifier parameter, then a polymorphic
+  qualifier written on a method in the class has a slightly different
+  meaning, see Section~\ref{class-qual-param-method}.
+\end{itemize}
+
+
+\subsectionAndLabel{Using polymorphic qualifiers in a method signature}{qualifier-polymorphism-examples}
+
+A method whose signature has a polymorphic qualifier (such as \<@PolyNull>) conceptually has multiple
+versions, somewhat like the generics feature of Java or a template in C++.
+In each version, each instance of the polymorphic qualifier has been
+replaced by the same other qualifier from the hierarchy.
+
+The method body must type-check with all signatures.  A method call is
+type-correct if it type-checks under any one of the signatures.  If a call
+matches multiple signatures, then the compiler uses the most specific
+matching signature for the purpose of type-checking.  This is the same as
+Java's rule for resolving overloaded methods.
+
+
+As an example of the use of \<@PolyNull>, method
+\sunjavadoc{java.base/java/lang/Class.html\#cast(java.lang.Object)}{Class.cast}
+returns null if and only if its argument is \<null>:
+
+\begin{Verbatim}
+  @PolyNull T cast(@PolyNull Object obj) { ... }
+\end{Verbatim}
+
+\noindent
+This is like writing:
+
+\begin{Verbatim}
+   @NonNull T cast( @NonNull Object obj) { ... }
+  @Nullable T cast(@Nullable Object obj) { ... }
+\end{Verbatim}
+
+\noindent
+except that the latter is not legal Java, since it defines two
+methods with the same Java signature.
+
+
+As another example, consider
+
+\begin{Verbatim}
+  // Returns null if either argument is null.
+  @PolyNull T max(@PolyNull T x, @PolyNull T y);
+\end{Verbatim}
+
+\noindent
+which is like writing
+
+\begin{Verbatim}
+   @NonNull T max( @NonNull T x,  @NonNull T y);
+  @Nullable T max(@Nullable T x, @Nullable T y);
+\end{Verbatim}
+
+\noindent
+At a call site, the most specific applicable signature is selected.
+
+Another way of thinking about which one of the two \code{max} variants is
+selected is that the nullness annotations of (the declared types of) both
+arguments are \emph{unified} to a type that is a supertype of both, also
+known as the \emph{least upper bound} or lub.  If both
+arguments are \code{@NonNull}, their unification (lub) is \<@NonNull>, and the
+method return type is \<@NonNull>.  But if even one of the arguments is \<@Nullable>,
+then the unification (lub) is \<@Nullable>, and so is the return type.
+
+
+
+\subsectionAndLabel{Relationship to subtyping and generics}{qualifier-polymorphism-vs-subtyping}
+
+Qualifier polymorphism has the same purpose and plays the same role as
+Java's generics.  You use them for the similar reasons, such as:
+\begin{itemize}
+\item
+  A method operates on collections with different types of
+  elements.
+\item
+  Two different arguments have the same type, without constraining them to
+  be one specific type.
+\item
+  A method returns a value of the same type as its argument.
+\end{itemize}
+
+
+If a method is written using Java generics, it usually does not need
+qualifier polymorphism.  If you can use Java's generics, then that is often
+better.  On the other hand, if you have legacy code that is not
+written generically, and you cannot change it to use generics, then you can
+use qualifier polymorphism to achieve a similar effect, with respect to
+type qualifiers only.  The Java compiler still treats the base Java types
+non-generically.
+
+In some cases, you don't need qualifier polymorphism because subtyping
+already provides the needed functionality.
+\<String> is a supertype of \<@Interned String>, so a method \<toUpperCase>
+that is declared to take a \<String> parameter can also be called on an
+\<@Interned String> argument.
+
+%% TODO: Polymorphic qualifiers do not yet take an optional argument.
+% PolyAll in the section below should be changed to any poly qualifier.
+%
+% \subsectionAndLabel{Multiple instances of polymorphic qualifiers (the index argument)}{qualifier-polymorphism-multiple-instances}
+%
+% Each polymorphic qualifier such as \refqualclass{framework/qual}{PolyAll}
+% takes an optional argument so that you can
+% specify multiple, independent polymorphic type qualifiers.  For example,
+% this signature is overly restrictive:
+%
+% \begin{Verbatim}
+%   /**
+%    * Returns true if the arrays are elementwise equal,
+%    * testing for equality using == (not the equals method).
+%    */
+%   public static int eltwiseEqualUsingEq(@PolyAll Object[] a, @PolyAll Object elt) {
+%     for (int i=0; i<a.length; i++) {
+%       if (elt != a[i]) {
+%         return false;
+%       }
+%     }
+%     return true;
+%   }
+% \end{Verbatim}
+%
+% \noindent
+% That signature requires the element type annotation to be identical for the
+% two arguments.  For example, it forbids this invocation:
+%
+% \begin{Verbatim}
+%   @Nullable Object[] x;
+%    @NonNull Object   y;
+%   ... indexOf(x, y) ...
+% \end{Verbatim}
+%
+% \noindent
+% A better signature lets the two arrays' element types vary independently:
+%
+% \begin{Verbatim}
+%   public static int eltwiseEqualUsingEq(@PolyAll(1) Object[] a, @PolyAll(2) Object elt)
+% \end{Verbatim}
+%
+% \noindent
+% Note that in this case, the \<@Nullable> annotation on \<elt>'s type is no
+% longer necessary, since it is subsumed by \<@PolyAll>.
+%
+% The \<@PolyAll> annotation at a location $l$ applies to every type
+% qualifier hierarchy for which no explicit qualifier is written at location
+% $l$.  For example, a declaration like
+% \<@PolyAll @NonNull Object elt> is polymorphic over every type system
+% \emph{except} the nullness type system, for which the type is fixed at
+% \<@NonNull>.  That would be the proper declaration for \<elt> if the body
+% had used \<elt.equals(a[i])> instead of \<elt == a[i]>.
+%
+%
+% % Suppose that some type system has two qualifiers, such as
+% % \<@Nullable> and \<@NonNull>.  When a polymorphic type qualifier such
+% % as \<@PolyNull> is used in a method, then the method conceptually
+% % has two different versions:  one in which every instance of
+% % \<@PolyNull> has been replaced by \<@NonNull> and one in
+% % which every instance of \<@PolyNull> has been replaced by
+% % \<@Nullable>.
+%
+% If a method signature contains only indexless versions of a polymorphic
+% qualifier such as \refqualclass{framework/qual}{PolyAll} or
+% \refqualclass{checker/nullness/qual}{PolyNull}, then all of them refer to
+% the same type as described in
+% Section~\ref{qualifier-polymorphism-multiple-qualifiers}.  If any indexed
+% version appears, then every occurrence of the polymorphic qualifier without
+% an index is considered to use a fresh index.  For example, the following
+% two declarations are equivalent (where \<@PA> means \<@PolyAll>, for brevity):
+%
+% \begin{smaller}
+% \begin{Verbatim}
+%   @PA(1) foo(@PA(1) Object a, @PA(2) Object b, @PA(2) Object c, @PA    Object d, @PA    Object e) {...}
+%
+%   @PA(1) foo(@PA(1) Object a, @PA(2) Object b, @PA(2) Object c, @PA(3) Object d, @PA(4) Object e) {...}
+% \end{Verbatim}
+% \end{smaller}
+%
+% As described in Section~\ref{qualifier-polymorphism-return-type}, the
+% qualifier on a return type must be the same as that on some formal parameter.
+% Therefore, the first of these declarations is legal because it is
+% equivalent to the second, but the third is illegal because it is
+% equivalent to the fourth.
+%
+% \begin{Verbatim}
+%   @PolyAll    m1(@PolyAll    Object a, @PolyAll    Object b) { ... } // OK
+%   @PolyAll(1) m2(@PolyAll(1) Object a, @PolyAll(1) Object b) { ... } // OK (same as m1)
+%
+%   @PolyAll    m3(@PolyAll    Object a, @PolyAll(1) Object b) { ... } // illegal
+%   @PolyAll(2) m4(@PolyAll(3) Object a, @PolyAll(1) Object b) { ... } // illegal (same as m3)
+% \end{Verbatim}
+
+
+\subsectionAndLabel{Using multiple polymorphic qualifiers in a method signature}{qualifier-polymorphism-multiple-qualifiers}
+
+%% I can't think of a non-clumsy way to say this.
+% Each method containing a polymorphic qualifier is (conceptually) expanded
+% into multiple versions completely independently.
+
+Usually, it does not make sense to write only a single instance of a polymorphic
+qualifier in a method definition:  if you write one instance of (say)
+\<@PolyNull>, then you should use at least two.
+The main benefit of polymorphic qualifiers comes when one is used multiple times
+in a method, since then each instance turns into the same type qualifier.
+(Section~\ref{qualifier-polymorphism-single-qualifier} describes some
+exceptions to this rule:  times when it makes sense to write a single
+polymorphic qualifier in a signature.)
+
+Most frequently, the polymorphic qualifier appears on at least one formal
+parameter and also on the return type.
+
+
+
+  It can also be useful to have
+polymorphic qualifiers on (only) multiple formal parameters, especially if
+the method side-effects one of its arguments.
+For example, consider
+
+\begin{Verbatim}
+void moveBetweenStacks(Stack<@PolyNull Object> s1, Stack<@PolyNull Object> s2) {
+  s1.push(s2.pop());
+}
+\end{Verbatim}
+
+\noindent
+In this particular example, it would be cleaner to rewrite your code to use
+Java generics, if you can do so:
+
+\begin{Verbatim}
+<T> void moveBetweenStacks(Stack<T> s1, Stack<T> s2) {
+  s1.push(s2.pop());
+}
+\end{Verbatim}
+
+
+\subsectionAndLabel{Using a single polymorphic qualifier in a method signature}{qualifier-polymorphism-single-qualifier}
+
+As explained in Section~\ref{qualifier-polymorphism-multiple-qualifiers},
+you will usually use a polymorphic qualifier
+multiple times in a signature.
+This section describes situations when it makes sense to write just one
+polymorphic qualifier in a method signature.
+Some of these situations can be avoided by writing a generic method,
+but in legacy code it may not be possible for you to change a method to be
+generic.
+
+
+\subsubsectionAndLabel{Using a single polymorphic qualifier on a return type}{qualifier-polymorphism-return-type}
+
+It is unusual, but permitted, to write just one polymorphic qualifier, on a return type.
+
+This is just like it is unusual, but permitted, to write just one
+occurrence of a generic type parameter, on a return type.  An example of
+such a method is
+\sunjavadoc{java.base/java/util/Collections.html\#emptyList()}{Collections.emptyList()}.
+
+
+\subsubsectionAndLabel{Using a single polymorphic qualifier on an element type}{qualifier-polymorphism-element-types}
+
+It can make sense to use a polymorphic qualifier just once, on an array or
+generic element type.
+
+For example, consider a routine that returns the index, in an array, of a
+given element:
+
+\begin{Verbatim}
+  public static int indexOf(@PolyNull Object[] a, @Nullable Object elt) { ... }
+\end{Verbatim}
+
+If \<@PolyNull> were replaced with either \<@Nullable> or \<@NonNull>, then
+one of these safe client calls would be rejected:
+
+\begin{Verbatim}
+  @Nullable Object[] a1;
+  @NonNull Object[] a2;
+
+  indexOf(a1, someObject);
+  indexOf(a2, someObject);
+\end{Verbatim}
+
+Of course, it would be better style to use a generic method, as in either
+of these signatures:
+
+\begin{Verbatim}
+ public static <T extends @Nullable Object> int indexOf(T[] a, @Nullable Object elt) { ... }
+ public static <T extends @Nullable Object> int indexOf(T[] a, T elt) { ... }
+\end{Verbatim}
+
+
+Another example is a method that writes bytes to a file.  It accepts an
+array of signed or unsigned bytes, and it behaves identically for both:
+
+\begin{Verbatim}
+  void write(@PolySigned byte[] b) { ... }
+\end{Verbatim}
+
+
+These examples use arrays, but there are similar examples that
+use collections.
+
+
+\subsubsectionAndLabel{Don't use a single polymorphic qualifier on a formal parameter type}{qualifier-polymorphism-formal-parameter}
+
+There is no point to writing
+
+\begin{Verbatim}
+  void m(@PolyNull Object obj)
+\end{Verbatim}
+
+\noindent
+which expands to
+
+\begin{Verbatim}
+  void m(@NonNull Object obj)
+  void m(@Nullable Object obj)
+\end{Verbatim}
+
+This is no different (in terms of which calls to the method will
+type-check) than writing just
+
+\begin{Verbatim}
+  void m(@Nullable Object obj)
+\end{Verbatim}
+
+
+\subsectionAndLabel{Defining a polymorphic qualifier}{qualifier-polymorphism-define}
+
+To define a polymorphic qualifier, meta-annotate the definition with
+\refqualclass{framework/qual}{PolymorphicQualifier}.  For example,
+\refqualclass{checker/nullness/qual}{PolyNull} is a polymorphic type
+qualifier for the Nullness type system and is defined as follows:
+
+\begin{Verbatim}
+  import java.lang.annotation.ElementType;
+  import java.lang.annotation.Target;
+  import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+  @PolymorphicQualifier
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  public @interface PolyNull {}
+\end{Verbatim}
+
+
+
+
+To \emph{define} a polymorphic qualifier, mark the definition with
+\refqualclass{framework/qual}{PolymorphicQualifier}.  For example,
+\refqualclass{checker/nullness/qual}{PolyNull} is a polymorphic type
+qualifier for the Nullness type system:
+
+\begin{Verbatim}
+  import java.lang.annotation.ElementType;
+  import java.lang.annotation.Target;
+  import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+  @PolymorphicQualifier
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  public @interface PolyNull {}
+\end{Verbatim}
+
+
+
+
+\sectionAndLabel{Class qualifier parameters}{class-qualifier-polymorphism}
+
+Class qualifier parameters permit you to supply a type qualifier (only,
+without a Java basetype) to any class (generic or not).
+
+When a generic class represents a collection, a user can write a type
+qualifier on the type argument, as in \code{List<@Tainted Character>}
+versus \code{List<@Untainted Character>}.  When a non-generic class
+represents a collection with a hard-coded type (as \<StringBuffer>
+hard-codes \<Character>), you can use a class qualifier parameter to
+distinguish \<StringBuffer>s that contain different types of characters.
+% (For a discussion of tainting, see \chapterpageref{tainting-checker}.)
+
+To add a qualifier parameter to a class, annotate its declaration with \refqualclass{framework/qual}{HasQualifierParameter}
+and write the class of the top qualifier as its element.
+
+\begin{Verbatim}
+@HasQualifierParameter(Tainted.class)
+class StringBuffer { ... }
+\end{Verbatim}
+
+A qualifier on a use of \<StringBuffer> is treated as
+appearing both on the \<StringBuffer> and on its conceptual type argument.  That
+is:\\
+\begin{tt}
+  @Tainted StringBuffer $\approx$ @Tainted Collection<@Tainted Character>\\
+  @Untainted StringBuffer $\approx$ @Untainted Collection<@Untainted Character>
+\end{tt}
+
+If two types have different qualifier arguments, they have no subtyping
+relationship.  (This is ``invariant subtyping'', also used by Java for
+generic classes.)  In particular, \<@Untainted StringBuffer> is not a
+subtype of \<@Tainted StringBuffer>; an attempt to cast between them, in
+either direction, will yield an \<invariant.cast.unsafe> error.
+
+If a subclass extends a \<@HasQualifierParameter> class (or implements a
+\<@HasQualifierParameter> interface), then the subclass must also be marked
+\<@HasQualifierParameter>.
+
+%% TODO: Inheritance of classes with a qualifier parameter below.
+
+Within a class with a qualifier parameter,
+the default qualifier for uses of that class is the polymorphic qualifier.
+
+
+\subsectionAndLabel{Resolving polymorphism when the receiver type has a polymorphic qualifier}{class-qual-param-method}
+
+Qualifier polymorphism changes the rules for instantiating polymorphic
+qualifiers (Section~\ref{method-qualifier-polymorphism}).
+If the receiver type has a qualifier parameter and is annotated with a polymorphic qualifier,
+then at a call site all polymorphic annotations are instantiated to
+the same qualifier as the type of the receiver expression of the method call.
+Otherwise, use the rules of Section~\ref{method-qualifier-polymorphism}
+
+For example, consider
+
+\begin{Verbatim}
+@HasQualifierParameter(Tainted.class)
+class Buffer {
+  void append(@PolyTainted Buffer this, @PolyTainted String s) { ... }
+}
+\end{Verbatim}
+
+\noindent
+Because \<@PolyTainted> applies to a type (\<Buffer>) with a qualifier parameter, all
+uses of \<@PolyTainted> are instantiated to the qualifiers on the type of
+the receiver expression at call sites to \<append>. For example,
+
+\begin{Verbatim}
+@Untainted Buffer untaintedBuffer = ...;
+@Tainted String taintedString = ...;
+untaintedBuffer.append(taintedString); // error: argument
+\end{Verbatim}
+The above \<append> call is illegal because the \<@PolyTainted> is instantiated to \<@Untainted> and
+the type of the argument is \<@Tainted> which is a supertype of \<@Untainted>.  If the type of
+\<untaintedBuffer> were \<@Tainted> then the call would be legal.
+
+\subsectionAndLabel{Using class qualifier parameters in the type of a field}{class-qual-param-field}
+
+To express that the type of a field
+should have the same qualifier as the class qualifier parameter,
+annotate the field type with the polymorphic qualifier for the type system.
+
+\begin{Verbatim}
+@HasQualifierParameter(Tainted.class)
+class Buffer {
+  @PolyTainted String field;
+}
+\end{Verbatim}
+
+At a field access where the declared type of the field has a polymorphic
+qualifier, that polymorphic qualifier is instantiated to the qualifier on the
+type of the receiver of the field access (or in the case of type variables, the
+qualifier on the upper bound).  That is, the qualifier on \<myBuffer.field>
+is that same as that on \<myBuffer>.
+
+\subsectionAndLabel{Local variable defaults for types with qualifier parameters}{local-vars-qual-param-defaults}
+
+Local variables default to the top type (see
+Section~\ref{climb-to-top}). Type refinement determines if a variable can be
+treated as a suitable subtype, and annotations on local variables are rarely
+needed as a result. However, since qualifier parameters add invariant subtyping,
+type refinement is no longer valid. For example, suppose in the following code
+that \<StringBuffer> is annotated with \<@HasQualifierParameter(Tainted.class)>.
+
+\begin{Verbatim}
+    void method(@Untainted StringBuffer buffer) {
+        StringBuffer local = buffer;
+        executeSql(local.toString());
+    }
+
+    void executeSql(@Untainted String code) {
+        // ...
+    }
+\end{Verbatim}
+
+Normally, the framework would determine that \<local> has type \<@Untainted
+StringBuffer> and the call to \<executeSql> would be valid. However, since by
+default \<local> has type \<@Tainted StringBuffer>, and
+\<@Untainted StringBuffer> is not a subtype, no type refinement would be
+performed, leading to an error. Fixing this would require manually annotating
+\<local> as an \<@Untainted StringBuffer>, increasing the annotation burden on
+programmers.
+
+For this reason, local variables with types that have a qualifier parameter use
+different defaulting rules. When a local variable has an initializer, the type
+of that initializer is used as the default type of that variable if no other
+annotations are written. For example, in the above code, the type of \<local>
+would be \<@Untainted StringBuffer>. This eliminates the need for type
+refinement.
+
+\subsectionAndLabel{Qualifier parameters by default}{default-has-qualifier-parameter}
+
+If many classes in a project should have \<@HasQualifierParameter>, it's
+possible to enable it on all classes in a package by default. Writing
+\<@HasQualifierParameter> on a package is equivalent to writing
+\<@HasQualifierParameter> on each class in that package and all subpackages with
+the same arguments.
+
+For example, writing this annotation enables \<@HasQualifierParameter>
+for all classes in \code{mypackage}.
+
+\begin{Verbatim}
+@HasQualifierParameter(Tainted.class)
+package mypackage;
+\end{Verbatim}
+
+When using \<@HasQualifierParameter> on a package, it's possible to disable it
+for a specific class using \refqualclass{framework/qual}{NoQualifierParameter}.
+Writing this on a class indicates it has no class qualifier parameter and
+\<@HasQualifierParameter> will not be enabled by default. Like
+\<@HasQualifierParameter>, it takes one or more top annotations. It is illegal
+to explicitly write both \<@HasQualifierParameter> and \<@NoQualifierParameter>
+on the same class for the same hierarchy.
+
+%% TODO: implement this.
+%% https://github.com/typetools/checker-framework/issues/3400
+%\subsectionAndLabel{Inheritance of classes with a qualifier parameter}{class-qual-param-inheritance}
+%
+%If a class extends a \<@HasQualifierParameter> class (or implements a
+%\<@HasQualifierParameter> interface), then that class must also be marked
+%\<@HasQualifierParameter>. A particular subclass can specify which qualifier
+%parameter should be used for the super class.  (This is similar to \<class
+%StringList implements List<String> \{...\}>.) For example,
+%
+%\begin{Verbatim}
+%  @HasQualifierParameter(Tainted.class)
+%  class Buffer {
+%  void append(@PolyTainted Buffer this, @PolyTainted String s) { ... }
+%  }
+%  @HasQualifierParameter(Tainted.class)
+%  @Tainted class TaintedBuffer extends @Tainted Buffer {
+%  @Override
+%  void append(@Tainted TaintedBuffer this, @Tainted String s) { ... } // legal override
+%  }
+%\end{Verbatim}
+%
+% \<@Untainted TaintedBuffer> is an invalid type.
+
+\subsectionAndLabel{Types with qualifier parameters as type arguments}{class-qual-param-type-arg}
+
+Types with qualifier parameters are only allowed as type arguments to type parameters whose upper bound
+have a qualifier parameter. If they were allowed for as type arguments for any type parameter, then
+unsound casts would be permitted. For example:
+
+\begin{Verbatim}
+    @HasQualifierParameter(Tainted.class)
+    interface Buffer {
+        void append(@PolyTainted String s);
+    }
+
+    public class ClassQPTypeVarTest {
+        <T> @Tainted T cast(T param) {
+            return param;
+        }
+
+        void bug(@Untainted Buffer b, @Tainted String s) {
+            cast(b).append(s);  // error
+        }
+    }
+
+
+\end{Verbatim}
+
+% LocalWords:  nullable MyList nble nn Nullness DefaultQualifier MyClass quals
+% LocalWords:  DefaultLocation subtype's ImmutableList ListIterator nullness
+% LocalWords:  PolymorphicQualifier PolyNull java lub invariantly supertype's
+% LocalWords:  MyList1 MyList2 MyList4 MyList3 MyClass2 toUpperCase elt
+% LocalWords:  PolyAll arrays' Xlint rawtypes AignoreRawTypeArguments s1
+% LocalWords:  call's Regex taintedHolder untaintedHolder taintedHolder2
+% LocalWords:  wildcards holderExtends holderSuper getTaintedString s2 JLS
+% LocalWords:  getUntaintedString Typesystem Param ClassTaintingParam h1
+% LocalWords:  arg param MethodTaintingParam Util meth value1 h2 TopType
+% LocalWords:  nestedHolder nestedHolder2 extendsHolder PolyTainted Poly
+%%  LocalWords:  BottomType CharSequence SomeType OtherType MyList1a
+%%  LocalWords:  MyList2a polymorphism'' bound'' LowerBound UpperBound
+% LocalWords:  JavaUpperBound JavaLowerBound MyTypeAnnotation myBuffer
+% LocalWords:  HasQualifierParameter myUntaintedBuffer myTaintedString
+% LocalWords:  myTaintedBuffer myUntaintedString
diff --git a/docs/manual/guieffect-checker.tex b/docs/manual/guieffect-checker.tex
new file mode 100644
index 0000000..a391bbd
--- /dev/null
+++ b/docs/manual/guieffect-checker.tex
@@ -0,0 +1,425 @@
+\htmlhr
+\chapterAndLabel{GUI Effect Checker}{guieffect-checker}
+
+One of the most prevalent GUI-related bugs is \emph{invalid UI update} or \emph{invalid thread access}:  accessing the UI directly from a
+background thread.
+
+
+% For performance reasons, most applications with a graphical user interface (GUI) use multiple
+% threads.
+
+Most GUI frameworks (including Android, AWT, Swing, and SWT) create
+a single distinguished thread --- the UI event thread
+--- that handles all GUI events and updates.
+% Every UI event (mouse click, key press,
+% etc.) is dispatched to that thread.
+To keep the interface responsive, any expensive computation should be
+offloaded to \emph{background threads} (also called \emph{worker threads}).
+If a background thread accesses a UI
+element such as a JPanel (by calling a JPanel method or reading/writing a
+field of JPanel),
+the GUI framework raises an exception that terminates the program.
+To fix the bug, the background thread should send a request to the
+UI thread to perform the access on its behalf.
+
+It is difficult for a programmer to remember which methods may be called on
+which thread(s).
+The GUI Effect Checker solves this problem.
+The programmer annotates each method to indicate whether:
+\begin{itemize}
+\item
+  It accesses no UI elements (and may run on any thread);
+  such a method is said to have the ``safe effect''.
+\item
+  It may access UI elements (and must run on the UI thread);
+  such a method is said to have the ``UI effect''.
+\end{itemize}
+
+
+%% True and interesting, but not relevant to people who are reading this
+%% section of the manual.
+% The GUI Effect Checker prevents this problem by using an \emph{effect
+% system}, also known as a type-and-effect system.
+% An \emph{effect} is a property of a \emph{computation}.
+% A well-known example of an effect system is Java's checked exception mechanism.  When Java's
+% type system says a method may throw exceptions \code{A} or \code{B} (the method type-checks with a
+% \code{throws A, B} clause), this makes two important statements about the behavior of the method at
+% run time.  First, running the method in question may result in one of the specified exceptions being
+% thrown.  Second, if an exception is thrown by the method, then the exception must be an \code{A} or
+% \code{B}.  So an effect for a method (or any other block of code) is an upper bound on a certain
+% class of behaviors.  In general, a block of code with a given effect (upper bound on behavior) may
+% call methods with the same, or weaker effects (e.g., no throws clause), because this will not
+% violate the assumed upper bound.  Calling a method with an incompatible effect (e.g., calling a
+% \code{throws C} method) would be a compile-time error.  For reasoning about effects to be sound, the
+% effects must interact with inheritance: method overrides must have the same (or lower) upper bound
+% on behavior as the method being overridden.  For checked exceptions, this means the method override
+% cannot throw any exceptions not permitted by the parent class implementation's \code{throws} clause.
+
+The GUI Effect Checker verifies these effects and statically enforces that UI methods are only
+called from the correct thread.  A method with the safe effect is prohibited from calling a method
+with the UI effect.
+% All UI element methods have the ``UI effect.''
+
+For example, the effect system can reason about when method calls must be dispatched to the UI
+thread via a message such as \<Display.syncExec>.
+\begin{alltt}
+@SafeEffect
+public void calledFromBackgroundThreads(JLabel l) \{
+    l.setText("Foo");       // Error: calling a @UIEffect method from a @SafeEffect method
+    Display.syncExec(new Runnable \{
+        @UIEffect // inferred by default
+        public void run() \{
+            l.setText("Bar");  // OK: accessing JLabel from code run on the UI thread
+        \}
+    \});
+
+\}
+\end{alltt}
+
+%% True, but why tip off readers that this is complex?  They might read it
+%% and just understand it...
+% The bulk of the GUI Effect Checker's complexity comes from handling interactions between effects and
+% inheritance, and from \emph{effect polymorphism} (code that can work as either safe or UI code).
+
+The GUI Effect Checker's annotations fall into three categories:
+
+\begin{itemize}
+\item
+  effect annotations on methods (Section~\ref{guieffect-annotations}),
+\item
+ class or package annotations controlling the default effect (Section~\ref{guieffect-defaults}), and
+\item
+  \emph{effect-polymorphism}:  code that works for both the safe effect and
+  the UI effect (Section~\ref{guieffect-polymorphism}).
+\end{itemize}
+
+
+\sectionAndLabel{GUI effect annotations}{guieffect-annotations}
+
+There are two primary GUI effect annotations:
+\begin{description}
+\item[\refqualclass{checker/guieffect/qual}{SafeEffect}]
+  is a method annotation marking code that must not
+  access UI objects.
+\item[\refqualclass{checker/guieffect/qual}{UIEffect}]
+  is a method annotation marking code that may access
+  UI objects.  Most UI object methods (e.g., methods of \<JPanel>) are
+  annotated as \code{@UIEffect}.
+\end{description}
+
+\code{@SafeEffect} is a sub-effect of \code{@UIEffect}, in that it is always safe to
+call a \code{@SafeEffect} method anywhere it is permitted to call a
+\code{@UIEffect} method.  We write this relationship as
+
+\centerline{\code{@SafeEffect} $\prec$ \code{@UIEffect}}
+
+
+\sectionAndLabel{What the GUI Effect Checker checks}{guieffect-checks}
+
+The GUI Effect Checker ensures that only the UI thread accesses UI objects.
+This prevents GUI errors such
+as invalid UI update and invalid thread access.
+
+The GUI Effect Checker issues errors in the following cases:
+
+%% TODO: Is this exhaustive?  Read the code to find out.
+
+\begin{itemize}
+\item
+  A \<@UIEffect> method is invoked by a \<@SafeEffect> method.
+
+\item
+  Method declarations violate subtyping restrictions:  a supertype declares
+  a \code{@SafeEffect} method, and a subtype annotates an overriding
+  version as \code{@UIEffect}.
+
+\end{itemize}
+
+Additionally, if a method implements or overrides a method in two
+supertypes (two interfaces, or an interface and parent class), and those
+supertypes give different effects for the methods, the GUI Effect Checker
+issues a warning (not an error).
+
+
+
+% It checks this by attaching to each method in the program an effect, which is an upper bound on
+% which objects it may touch: the \code{@UIEffect} effect (and method annotation) marks code that may
+% access UI elements, and therefore must execute only on the distinguished UI event thread; the
+% \code{@SafeEffect} effect and method annotation marks code that must not directly access UI
+% elements, and is therefore safe to call from any thread.
+%
+% Polymorphic effects are used for classes and interfaces which are used in both ways, for example the
+% \code{java.util.Runnable} interface, containing one method, which is used for tasks including
+% starting new background threads (in which case the code must have the \code{@SafeEffect}) and
+% dispatching code to run on the UI thread (in which case the code may have the \code{@UIEffect}).
+
+
+\sectionAndLabel{Running the GUI Effect Checker}{guieffect-running}
+
+The GUI Effect Checker can be invoked by running the following command:
+\begin{Verbatim}
+  javac -processor org.checkerframework.checker.guieffect.GuiEffectChecker MyFile.java ...
+\end{Verbatim}
+
+
+\sectionAndLabel{Annotation defaults}{guieffect-defaults}
+
+The default method annotation is \code{@SafeEffect}, since most code in most programs is not related
+to the UI\@.  This also means that typically, code that is unrelated to the UI need not be annotated
+at all.
+
+The GUI Effect Checker provides three primary ways to change the default method effect for a class
+or package:
+\begin{description}
+\item[\refqualclass{checker/guieffect/qual}{UIType}]
+  is a class annotation that makes the effect for unannotated methods in
+  that class default to \code{@UIEffect}.  (See also \code{@UI} in
+  Section~\ref{guieffect-polymorphism-using}.)
+\item[\refqualclass{checker/guieffect/qual}{UIPackage}]
+  is a \emph{package} annotation, that makes the effect for unannotated
+  methods in that package default to \code{@UIEffect}.  It is not
+  transitive; a package nested inside a package marked \code{@UIPackage}
+  does not inherit the changed default.
+\item[\refqualclass{checker/guieffect/qual}{SafeType}]
+  is a class annotation that makes the effect for unannotated methods in
+  that class default to \code{@SafeEffect}.  Because \code{@SafeEffect} is
+  already the default effect, \code{@SafeType} is only useful for class
+  types inside a package marked \code{@UIPackage}.
+\end{description}
+
+There is one other place where the default annotation is not automatically \code{@SafeEffect}:
+anonymous inner classes.  Since anonymous inner classes exist primarily for brevity, it would be
+unfortunate to spoil that brevity with extra annotations.  By default, an anonymous inner class
+method that overrides or implements a method of the parent type inherits that method's effect.
+For example, an anonymous inner class implementing an interface with method \code{@UIEffect void
+m()} need not explicitly annotate its implementation of \code{m()}; the implementation will inherit
+the parent's effect.  Methods of the anonymous inner class that are not inherited from a parent type
+follow the standard defaulting rules.
+
+
+\sectionAndLabel{Polymorphic effects}{guieffect-polymorphism}
+
+Sometimes a type is reused for both UI-specific and background-thread work.  A good example is the
+\code{Runnable} interface, which is used both for creating new background threads (in which case the
+\code{run()} method must have the \code{@SafeEffect}) and for sending code to the UI thread to
+execute (in which case the \code{run()} method may have the
+\code{@UIEffect}).  But the declaration of
+\code{Runnable.run()} may have only one effect annotation in the source code.
+How do we reconcile these
+conflicting use cases?
+
+\emph{Effect-polymorphism} permits a type to be used for both UI and non-UI
+purposes.  It is similar to Java's generics in that you define, then use, the
+effect-polymorphic type.
+Recall that to \emph{define} a generic type, you write a type parameter such as
+\code{<T>} and use it in the body of the type definition; for example,
+ \code{class List<T> \ttlcb\ ...\ \ T get() \ttlcb...\ttrcb\ \ ...\ \ \ttrcb}.
+To \emph{instantiate} a generic type, you write its name along with a type
+argument; for example, \code{List<Date> myDates;}.
+
+
+\subsectionAndLabel{Defining an effect-polymorphic type}{guieffect-polymorphism-defining}
+
+To declare that a class is effect-polymorphic, annotate its definition with
+\refqualclass{checker/guieffect/qual}{PolyUIType}.
+To use the effect variable in the class body, annotate a method with \refqualclass{checker/guieffect/qual}{PolyUIEffect}.
+It is an error to use \code{@PolyUIEffect} in a class that is not
+effect-polymorphic.
+
+
+Consider the following example:
+
+\begin{alltt}
+@PolyUIType
+public interface Runnable \{
+    @PolyUIEffect
+    void run();
+\}
+\end{alltt}
+
+\noindent
+This declares that class \<Runnable> is parameterized over one
+generic effect, and that when \<Runnable> is instantiated, the effect
+argument will be used as the effect for the \<run> method.
+
+
+\subsectionAndLabel{Using an effect-polymorphic type}{guieffect-polymorphism-using}
+
+To instantiate an effect-polymorphic type, write one of these three type
+qualifiers before a use of the type:
+\begin{description}
+\item[\refqualclass{checker/guieffect/qual}{AlwaysSafe}]
+  instantiates the type's effect to \code{@SafeEffect}.
+\item[\refqualclass{checker/guieffect/qual}{UI}]
+  instantiates the type's effect to \code{@UIEffect}.
+  \emph{Additionally}, it changes the
+  default method effect for the class to \code{@UIEffect}.
+\item[\refqualclass{checker/guieffect/qual}{PolyUI}]
+  instantiates the type's
+  effect to \code{@PolyUIEffect} for the same instantiation as the current
+  (containing) class.  For example, this is the qualifier of the receiver
+  \code{this} inside a method of a \code{@PolyUIType} class, which is how
+  one method of an effect-polymorphic class may call an effect-polymorphic
+  method of the same class.
+\end{description}
+
+As an example:
+
+\begin{alltt}
+@AlwaysSafe Runnable s = ...;    s.run();    // s.run() is @SafeEffect
+@PolyUI Runnable p = ...;        p.run();    // p.run() is @PolyUIEffect (context-dependent)
+@UI Runnable u = ...;            u.run();    // u.run() is @UIEffect
+\end{alltt}
+
+It is an error to apply an effect instantiation qualifier to a type that is not effect-polymorphic.
+
+Note that no annotation is required on the anonymous class declaration itself (e.g. \code{new Runnable()\{...\}}
+does not require a type use annotation, although the variable, field, or argument it ends up being assigned to might).
+Instead, the GUI Effect Checker will infer the effect qualifier based on the method being called from within the
+members of that specific anonymous class.
+
+\subsectionAndLabel{Subclassing a specific instantiation of an effect-polymorphic type}{guieffect-subclassing}
+
+Sometimes you may wish to subclass a specific instantiation of an effect-polymorphic type, just as
+you may extend \code{List<String>}.
+
+To do this, simply place the effect instantiation qualifier by the name of the type you are
+defining, e.g.:
+
+\begin{alltt}
+@UI
+public class UIRunnable extends Runnable \{...\}
+@AlwaysSafe
+public class SafeRunnable extends Runnable \{...\}
+\end{alltt}
+The GUI Effect Checker will automatically apply the qualifier to all classes and interfaces the class
+being defined extends or implements.  (This means you cannot write a class that is a subtype of a
+\code{@AlwaysSafe Foo} and a \code{@UI Bar}, but this has not been a problem in our experience.)
+
+
+\subsectionAndLabel{Subtyping with polymorphic effects}{guieffect-subtyping}
+
+With three effect annotations, we must extend the static sub-effecting relationship:
+
+\centerline{\code{@SafeEffect} $\prec$ \code{@PolyUIEffect} $\prec$ \code{@UIEffect}}
+
+\noindent
+This is the correct sub-effecting relation because it is always safe to
+call a \code{@SafeEffect}
+method (whether from an effect-polymorphic method or a UI method), and a \code{@UIEffect} method
+may safely call any other method.
+% (since the instantiation of \code{@PolyUIEffect} may be at most
+% \code{@UIEffect} itself).
+
+This induces a subtyping hierarchy on type qualifiers:
+
+\centerline{\code{@AlwaysSafe} $\prec$ \code{@PolyUI} $\prec$ \code{@UI}}
+
+\noindent
+This is sound because a method instantiated according to any qualifier will always be
+safe to call in place of a method instantiated according to one of its super-qualifiers.
+This allows clients to pass ``safer'' instances of some object type to a given method.
+
+
+\subsubsectionAndLabel{Effect polymorphism and arguments}{guieffect-overrides}
+
+Sometimes it is useful to have \code{@PolyUI} parameters on a method.  As a trivial example, this
+permits us to specify an identity method that works for both \code{@UI Runnable} and
+\code{@AlwaysSafe Runnable}:
+
+\begin{alltt}
+public @PolyUI Runnable id(@PolyUI Runnable r) \{
+    return r;
+\}
+\end{alltt}
+
+\noindent
+This use of \code{@PolyUI} will be handled as is standard for polymorphic qualifiers in the Checker
+Framework (see Section \ref{method-qualifier-polymorphism}).
+
+\code{@PolyUIEffect} methods should generally not use \code{@PolyUI} arguments: it is permitted by
+the framework, but its interaction with inheritance is subtle, and may not behave as you would
+hope.
+
+The shortest explanation is this: \code{@PolyUI} arguments may only be overridden by \code{@PolyUI}
+arguments, even though the implicitly \code{@PolyUI} \emph{receiver} may be overridden with a
+\code{@AlwaysSafe} receiver.
+
+As noted earlier (Section \ref{covariant-type-parameters}), Java's generics are invariant ---
+\code{A<X>} is a subtype of \code{B<Y>} only if \code{X} is identical to \code{Y}.
+Class-level use of \code{@PolyUI} behaves slightly differently.
+Marking a type declaration \code{@PolyUIType} is conceptually
+equivalent to parameterizing the type by some \code{E extends Effect}.  But in this view,
+\code{Runnable<SafeEffect>} (really \code{@AlwaysSafe Runnable}) would be considered a subtype of
+\code{Runnable<UIEffect>} (really \code{@UI Runnable}), as explained earlier in this section.  Java's
+generics do not permit this, which is called \emph{covariant} subtyping in the effect parameter.
+Permitting it for all generics leads to problems where a type system can miss errors.  Java solves
+this by making all generics invariant, which rejects more programs than strictly necessary, but
+leads to an easy-to-explain limitation.  For this checker, covariant subtyping of effect parameters
+is very important: being able to pass an \code{@AlwaysSafe
+Runnable} in place of a \code{@UI Runnable} is extremely useful.  Since we need to allow some
+cases for flexibility, but need to reject other cases to avoid missing errors, the distinction is a
+bit more subtle for this checker.
+
+Consider this small example (warning: the following is rejected by the GUI Effect Checker):
+
+\begin{alltt}
+@PolyUIType
+public interface Dispatcher \{
+    @PolyUIEffect
+    void dispatch(@PolyUI Runnable toRun);
+\}
+@SafeType
+public class SafeDispatcher implements Dispatcher \{
+    @Override
+    public void dispatch(@AlwaysSafe Runnable toRun) \{
+        runOnBackgroundThread(toRun);
+    \}
+\}
+\end{alltt}
+
+This may initially seem like harmless code to write, which simply specializes the implicit effect
+parameter from \code{Dispatcher} in the \code{SafeDispatcher} implementation.  However, the way
+method effect polymorphism is implemented is by implicitly making the receiver of a
+\code{@PolyUIEffect} method --- the object on which the method is invoked --- \code{@PolyUI}.  So
+if the definitions above were permitted, the following client code would be possible:
+
+\begin{alltt}
+@AlwaysSafe SafeDispatcher s = ...;
+@UI Runnable uitask = ...;
+s.dispatch(uitask);
+\end{alltt}
+
+At the call to \code{dispatch}, the Checker Framework is free to consider \code{s} as its
+supertype, \code{@UI SafeDispatcher}.  This permits the framework to choose the same qualifier for
+both the (implicit) receiver use of \code{@PolyUI} and the \code{toRun} argument to
+\code{Dispatcher.dispatch}, passing the checker.  But this code would then pass a UI-thread-only
+task to a method which should only accept background thread tasks --- exactly what the checker
+should prevent!
+
+To resolve this, the GUI Effect Checker rejects the definitions above, specifically the
+\code{@AlwaysSafe} on \code{SafeDispatcher.dispatch}'s parameter, which would need to remain
+\code{@PolyUI}.
+
+A subtlety of the code above is that the receiver for \code{SafeDispatcher.dispatch} is also
+overridden, switching from a \code{@PolyUI} receiver to a \code{@Safe} receiver.  That change is
+permissible.  But when that is done, the polymorphic arguments may no longer be interchangeable
+with the receiver.
+
+
+\sectionAndLabel{References}{guieffect-references}
+
+The ECOOP 2013 paper ``JavaUI: Effects for Controlling UI Object Access''
+    includes some case
+    studies on the checker's efficacy, including descriptions of the relatively few false warnings
+    we encountered.
+    It also contains a more formal description of the effect system.
+    You can obtain the paper at: \\
+  \myurl{https://homes.cs.washington.edu/~mernst/pubs/gui-thread-ecoop2013-abstract.html}
+
+
+%  LocalWords:  SafeEffect calledFromBackgroundThreads JLabel setText AWT
+%  LocalWords:  UIEffect syncExec Runnable UIType UIPackage SafeType SWT
+%  LocalWords:  PolyUIType PolyUIEffect PolyUI UIRunnable SafeRunnable Foo
+%  LocalWords:  JavaUI JPanel myDates AlwaysSafe toRun SafeDispatcher
+%  LocalWords:  runOnBackgroundThread uitask guieffect
diff --git a/docs/manual/hevea-retarget-crossrefs b/docs/manual/hevea-retarget-crossrefs
new file mode 100755
index 0000000..adce198
--- /dev/null
+++ b/docs/manual/hevea-retarget-crossrefs
@@ -0,0 +1,85 @@
+#!/usr/bin/perl -n
+# hevea-retarget-crossrefs
+# Michael Ernst
+# Last updated: May 20, 2012
+
+# To use:
+#   hevea-retarget-crossrefs < orig.html > new.html
+
+# This script replaces HTML cross-references of the form
+#    <a href="#htoc1">
+# by cross-refenences to named labels, such as
+#    <a href="#introduction">
+
+# It is required that the original .tex source file contained a \label
+# command at the end of each \chapter or \[sub]section command, like so:
+#   \chapter{Introduction\label{introduction}}
+# The given label that will replace the "htoc" one in the .html file.
+
+# Rationale:
+# In the table of contents, Hevea creates HTML cross-references that use
+# Hevea-generated labels of the form "htoc99", even when a \label already
+# exists.  This leads to users following a link from the table of contents,
+# then bookmarking or mentioning that link.  The "htoc99" link may point
+# to a completely different section if the manual is reordered or even if a
+# new section is added.  So, it is better for webpages not to contain the
+# easy-to-misuse "htoc99" cross-references.
+
+
+# This script does not work with in-place editing (perl's -i argument).
+
+# use strict;
+# use English;
+# $WARNING = 1;
+
+$debug = 0;
+# $debug = 1;
+
+# if (scalar(@ARGV) != 1) {
+#   die "Expected exactly 1 argument, got " . scalar(@ARGV);
+# }
+# my $filename = $ARGV[0];
+
+push @lines, $_;
+
+END {
+
+  for (my $i = 0; $i<scalar(@lines); $i++) {
+    # Handle lines *with* htoc, substituting it by the first other anchor and moving others forward.
+    if ($lines[$i] =~ s:<A NAME="(htoc[0-9]+)">(((Chapter&#XA0;)?([0-9]+|[A-Z]))(\.[0-9]+)*)(</A>)(.*?)(<A NAME="(.*?)">)</A>((<A NAME=".*"></A>)*)(</H[0-9]+>):$9$2$7$11$8$13:) {
+      $mapping{$1} = $10;
+      if ($debug) { print STDERR "$1 => $mapping{$1}\n"; }
+    }
+    # Move around the "<A NAME=" for sections *without* htoc (anything not in
+    # tocdepth, which is not in a table of contents).  If the anchor comes
+    # within but at the end of a header, then when going to that URL, some browsers
+    # will position the header off the top of the screen.  Putting the
+    # anchor at the beginning of the header fixes this problem.
+    $lines[$i] =~ s:(<(H[345]) CLASS="((sub)*section|paragraph)">)(.*?)(<A NAME=".*">(</A><A NAME=".*">)*)(</A></\2>):$1$6$5$8:;
+  }
+
+  foreach my $line (@lines) {
+    if ($line =~ /<A HREF="#(htoc[0-9]+)">/) {
+      my $htoc = $1;
+      my $replacement = $mapping{$htoc};
+      if (defined($replacement)) {
+        if ($debug) { print STDERR $line; }
+        # Also remove "Chapter" if present, for brevity
+        $line =~ s/$htoc(">)(Chapter&#XA0;)?/$replacement$1/;
+        if ($debug) { print STDERR $line; }
+      } else {
+        print STDERR "No symbolic name for section $htoc\n";
+      }
+    }
+    $line =~ s/(<IMG SRC="([^"]+\.[^".]+)")>/$1 ALT="$2">/g;
+    print $line;
+  }
+
+}
+
+# Local Variables:
+# time-stamp-start: "^# Last updated: "
+# time-stamp-end: "\\.?$"
+# time-stamp-format: "%:b %:d, %:y"
+# time-stamp-line-limit: 10
+# End:
diff --git a/docs/manual/i18n-format-checker.tex b/docs/manual/i18n-format-checker.tex
new file mode 100644
index 0000000..15185a3
--- /dev/null
+++ b/docs/manual/i18n-format-checker.tex
@@ -0,0 +1,438 @@
+\htmlhr
+\chapterAndLabel{Internationalization Format String Checker (I18n Format String Checker)}{i18n-formatter-checker}
+
+The Internationalization Format String Checker, or I18n Format String Checker,
+prevents use of incorrect i18n format strings.
+
+If the I18n Format String Checker issues no warnings or errors, then
+\sunjavadoc{java.base/java/text/MessageFormat.html\#format(java.lang.String,java.lang.Object...)}{MessageFormat.format}
+will raise no error at run time.
+``I18n'' is short for
+``internationalization'' because there are 18 characters between the ``i'' and
+the ``n''.
+
+Here are the examples of errors that the
+I18n Format Checker
+detects at compile time.
+
+\begin{Verbatim}
+  // Warning: the second argument is missing.
+  MessageFormat.format("{0} {1}", 3.1415);
+  // String argument cannot be formatted as Time type.
+  MessageFormat.format("{0, time}", "my string");
+  // Invalid format string: unknown format type: thyme.
+  MessageFormat.format("{0, thyme}", new Date());
+  // Invalid format string: missing the right brace.
+  MessageFormat.format("{0", new Date());
+  // Invalid format string: the argument index is not an integer.
+  MessageFormat.format("{0.2, time}", new Date());
+  // Invalid format string: "#.#.#" subformat is invalid.
+  MessageFormat.format("{0, number, #.#.#}", 3.1415);
+\end{Verbatim}
+
+For instructions on how to run the Internationalization Format String
+Checker, see Section~\ref{i18n-format-running}.
+
+The Internationalization Checker or I18n Checker (\chapterpageref{i18n-checker})
+has a different purpose.  It verifies that your code is properly
+internationalized: any user-visible text should be obtained from a
+localization resource and all keys exist in that resource.
+
+
+\sectionAndLabel{Internationalization Format String Checker annotations}{i18n-format-annotation}
+
+
+\begin{figure}
+\includeimage{i18n-format-type-hierarchy}{3cm}
+\caption{The
+  Internationalization
+  Format String Checker type qualifier hierarchy.
+  The type qualifiers are applicable to \<CharSequence> and its subtypes.
+  The figure does not show the subtyping rules among different
+  \refqualclass{checker/i18nformatter/qual}{I18nFormat}\code{(...)}
+  qualifiers; see
+  Section~\ref{i18n-format-conversion-categories}.
+  All \refqualclass{checker/i18nformatter/qual}{I18nFormatFor} annotations
+  are unrelated by subtyping, unless they are identical.
+  The qualifiers in gray are used internally by
+  the checker and should never be written by a programmer.
+}
+\label{i18n-format-type-hierarchy}
+\end{figure}
+
+The \sunjavadoc{java.base/java/text/MessageFormat.html}{MessageFormat} documentation
+specifies the syntax of the i18n format string.
+
+These are the qualifiers that make up the I18n Format String type system.
+Figure~\ref{i18n-format-type-hierarchy} shows their subtyping relationships.
+
+\begin{description}
+
+\item[\refqualclass{checker/i18nformatter/qual}{I18nFormat}]
+  represents a valid i18n format string. For example,
+  \code{@I18nFormat(\{GENERAL, NUMBER, UNUSED, DATE\})} is a legal type for
+  \code{"\{0\}\{1, number\} \{3, date\}"}, indicating that when the format
+  string is used,
+  the first argument should be of \code{GENERAL} conversion category,
+  the second argument should be of \code{NUMBER} conversion category, and so on.
+  Conversion categories such as \code{GENERAL} are described in
+  Section~\ref{i18n-format-conversion-categories}.
+
+\item[\refqualclass{checker/i18nformatter/qual}{I18nFormatFor}]
+  indicates that the qualified type is a valid i18n format string for use
+  with some array of values.  For example,
+  \code{@I18nFormatFor("\#2")} indicates that the string can be used to
+  format the contents of the second parameter array.
+  The argument is a Java expression whose syntax
+  is explained in Section~\ref{java-expressions-as-arguments}.
+  An example of its use is:
+
+\begin{Verbatim}
+  static void method(@I18nFormatFor("#2") String format, Object... args) {
+    // the body may use the parameters like this:
+    MessageFormat.format(format, args);
+  }
+
+  method("{0, number} {1}", 3.1415, "A string");  // OK
+  // error: The string "hello" cannot be formatted as a Number.
+  method("{0, number} {1}", "hello", "goodbye");
+\end{Verbatim}
+
+\item[\refqualclass{checker/i18nformatter/qual}{I18nInvalidFormat}]
+  represents an invalid i18n format string. Programmers are not allowed to
+  write this annotation. It is only used internally by the type checker.
+
+\item[\refqualclass{checker/i18nformatter/qual}{I18nUnknownFormat}]
+  represents any string.  The string might or might not be a valid i18n
+  format string.  Programmers are not allowed to write this annotation.
+
+\item[\refqualclass{checker/i18nformatter/qual}{I18nFormatBottom}]
+  indicates that the value is definitely \<null>. Programmers are not allowed
+  to write this annotation.
+\end{description}
+
+\sectionAndLabel{Conversion categories}{i18n-format-conversion-categories}
+
+In a message string, the optional second element within the curly braces is
+called a \emph{format type} and must be one of \<number>, \<date>,
+\<time>, and \<choice>. These four format types correspond to different
+conversion categories. \<date> and \<time> correspond to \emph{DATE} in the
+conversion categories figure. \<choice> corresponds to \emph{NUMBER}.
+The format type restricts what arguments are legal.
+For example, a date argument is not compatible with
+the \<number> format type, i.e., \code{MessageFormat.format("\{0, number\}",
+new Date())} will throw an exception.
+
+The I18n Checker represents the possible arguments via \emph{conversion
+  categories}.  A conversion category defines a set of restrictions or a
+subtyping rule.
+
+Figure~\ref{i18n-format-category} summarizes the subset
+relationship among all conversion categories.
+
+\begin{figure}
+    \includeimage{i18n-format-category}{5cm}
+    \caption{The subset relationship among
+        i18n
+        conversion categories.}
+    \label{i18n-format-category}
+\end{figure}
+
+
+\sectionAndLabel{Subtyping rules for \<@I18nFormat>}{i18n-formatter-format-subtyping}
+
+Here are the subtyping rules among different
+\code{@I18nFormat}
+qualifiers.
+It is legal to:
+
+\begin{itemize}
+\item use a format string with a weaker (less restrictive) conversion category than required.
+\item use a format string with fewer format specifiers than required.
+  Although this is legal a warning is issued because most occurrences of
+  this are due to programmer error.
+\end{itemize}
+
+The following example shows the subtyping rules in action:
+
+\begin{Verbatim}
+  @I18nFormat({NUMBER, DATE}) String f;
+
+  f = "{0, number, #.#} {1, date}"; // OK
+  f = "{0, number} {1}";            // OK, GENERAL is weaker (less restrictive) than DATE
+  f = "{0} {1, date}";              // OK, GENERAL is weaker (less restrictive) than NUMBER
+  f = "{0, number}";                // warning: last argument is ignored
+  f = "{0}";                        // warning: last argument is ignored
+  f = "{0, number} {1, number}";    // error: NUMBER is stronger (more restrictive) than DATE
+  f = "{0} {1} {2}";                // error: too many arguments
+\end{Verbatim}
+
+The conversion categories are:
+
+\begin{description}
+
+\item[\refenum{checker/i18nformatter/qual}{I18nConversionCategory}{UNUSED}{}]
+indicates an unused argument. For example, in
+\code{MessageFormat.format("\{0, number\} \{2, number\}", 3.14, "Hello", 2.718)}
+, the second argument \code{Hello} is unused. Thus, the conversion
+categories for the format, \code{{0, number} {2, number}}, is
+\code{(NUMBER, UNUSED, NUMBER)}.
+
+\item[\refenum{checker/i18nformatter/qual}{I18nConversionCategory}{GENERAL}{}]
+means that any value can be supplied as an argument.
+
+\item[\refenum{checker/i18nformatter/qual}{I18nConversionCategory}{DATE}{}]
+is applicable for date, time, and number types. An argument needs to be
+of \sunjavadoc{java.sql/java/sql/Date.html}{Date},
+\sunjavadoc{java.sql/java/sql/Time.html}{Time}, or
+\sunjavadoc{java.base/java/lang/Number.html}{Number} type or a subclass of them,
+including \sunjavadoc{java.sql/java/sql/Timestamp.html}{Timestamp} and the classes
+listed immediately below.
+
+\item[\refenum{checker/i18nformatter/qual}{I18nConversionCategory}{NUMBER}{}]
+means that the argument needs to be of \code{Number}
+type or a subclass:
+\sunjavadoc{java.base/java/lang/Number.html}{Number},
+\sunjavadoc{java.base/java/util/concurrent/atomic/AtomicInteger.html}{AtomicInteger},
+\sunjavadoc{java.base/java/util/concurrent/atomic/AtomicLong.html}{AtomicLong},
+\sunjavadoc{java.base/java/math/BigDecimal.html}{BigDecimal},
+\sunjavadoc{java.base/java/math/BigInteger.html}{BigInteger},
+\sunjavadoc{java.base/java/lang/Byte.html}{Byte},
+\sunjavadoc{java.base/java/lang/Double.html}{Double},
+\sunjavadoc{java.base/java/lang/Float.html}{Float},
+\sunjavadoc{java.base/java/lang/Integer.html}{Integer},
+\sunjavadoc{java.base/java/lang/Long.html}{Long},
+\sunjavadoc{java.base/java/lang/Short.html}{Short}.
+
+\end{description}
+
+\sectionAndLabel{What the Internationalization Format String Checker checks}{i18n-format-checks}
+
+The Internationalization Format String Checker checks calls to the i18n
+formatting method \sunjavadoc{java.base/java/text/MessageFormat.html\#format(java.lang.String,java.lang.Object...)}{MessageFormat.format}
+and guarantees the following:
+
+\begin{enumerate}
+  \item{The checker issues a warning for the following cases:}
+    \begin{enumerate}
+      \item There are missing arguments from what is required by the format string.
+
+      \code{MessageFormat.format("\{0, number\} \{1, number\}", 3.14); // Output: 3.14 \{1\}}
+
+      \item More arguments are passed than what is required by the format string.
+
+      \code{MessageFormat.format("\{0, number\}", 1, new Date());}
+
+      \code{MessageFormat.format("\{0, number\} \{0, number\}", 3.14, 3.14);}
+
+      This does not cause an error at run time, but it often indicates a
+      programmer mistake.  If it is intentional, then you should suppress
+      the warning (see Chapter~\ref{suppressing-warnings}).
+
+      \item Some argument is an array of objects.
+
+      \code{MessageFormat.format("\{0, number\} \{1\}", array);}
+
+      The checker cannot verify whether the format string is valid, so
+      the checker conservatively issues a warning.  This is a limitation of
+      the Internationalization Format String Checker.
+
+    \end{enumerate}
+  \item The checker issues an error for the following cases:
+    \begin{enumerate}
+      \item The format string is invalid.
+
+        \begin{itemize}
+          \item Unmatched braces.
+
+          \code{MessageFormat.format("\{0, time", new Date());}
+
+          \item The argument index is not an integer or is negative.
+
+          \code{MessageFormat.format("\{0.2, time\}", new Date());}
+
+          \code{MessageFormat.format("\{-1, time\}", new Date());}
+
+          \item Unknown format type.
+
+          \code{MessageFormat.format("\{0, foo\}", 3.14);}
+
+          \item Missing a format style required for \<choice> format.
+
+          \code{MessageFormat.format("\{0, choice\}", 3.14);}
+
+          \item Wrong format style.
+
+          \code{MessageFormat.format("\{0, time, number\}", 3.14);}
+
+          \item Invalid subformats.
+
+          \code{MessageFormat.format("\{0, number, \#.\#.\#\}", 3.14)}
+        \end{itemize}
+
+      \item Some argument's type doesn't satisfy its conversion category.
+
+      \code{MessageFormat.format("\{0, number\}", new Date());}
+    \end{enumerate}
+\end{enumerate}
+
+The Checker also detects illegal assignments: assigning a non-format-string
+or an incompatible format string to a variable declared as containing a
+specific type of format string. For example,
+
+\begin{Verbatim}
+  @I18nFormat({GENERAL, NUMBER}) String format;
+  // OK.
+  format = "{0} {1, number}";
+  // OK, GENERAL is weaker (less restrictive) than NUMBER.
+  format = "{0} {1}";
+  // OK, it is legal to have fewer arguments than required (less restrictive).
+  // But the warning will be issued instead.
+  format = "{0}";
+
+  // Error, the format string is stronger (more restrictive) than the specifiers.
+  format = "{0} {1} {2}";
+  // Error, the format string is more restrictive. NUMBER is a subtype of GENERAL.
+  format = "{0, number} {1, number}";
+\end{Verbatim}
+
+\sectionAndLabel{Resource files}{i18n-format-resource-files}
+
+A programmer rarely writes an i18n format string literally. (The examples
+in this chapter show that for simplicity.) Rather, the i18n format strings are
+read from a resource file.  The program chooses a resource file at run time
+depending on the locale (for example, different resource files for English
+and Spanish users).
+
+\noindent For example, suppose that the \<resource1.properties> file contains
+
+\begin{Verbatim}
+  key1 = The number is {0, number}.
+\end{Verbatim}
+
+\noindent Then code such as the following:
+
+\begin{Verbatim}
+  String formatPattern = ResourceBundle.getBundle("resource1").getString("key1");
+  System.out.println(MessageFormat.format(formatPattern, 2.2361));
+\end{Verbatim}
+
+\noindent will output ``The number is 2.2361.''  A different resource file would contain
+\code{key1 = El n\'{u}mero es \{0, number\}.}
+
+When you run the I18n Format String Checker, you need to indicate which resource file it
+should check. If you change the resource file or use a different resource
+file, you should re-run the checker
+to ensure that you did not make an error. The I18n Format String Checker supports two types of
+resource files: ResourceBundles and property files. The example above shows use of
+resource bundles.
+For more about checking property files, see \chapterpageref{propkey-checker}.
+
+
+\sectionAndLabel{Running the Internationalization Format Checker}{i18n-format-running}
+
+The checker can be invoked by running one of the following commands (with
+the whole command on one line).
+
+\begin{itemize}
+  \item Using ResourceBundles:
+
+    \begin{smaller}
+    \code{javac -processor
+      org.checkerframework.checker.i18nformatter.I18nFormatterChecker
+    -Abundlenames=MyResource MyFile.java}
+    \end{smaller}
+
+  \item Using property files:
+
+    \begin{smaller}
+    \code{javac -processor
+      org.checkerframework.checker.i18nformatter.I18nFormatterChecker
+    -Apropfiles=MyResource.properties MyFile.java}
+    \end{smaller}
+
+  \item Not using a property file.  Use this if the programmer hard-coded the
+  format patterns without loading them from a property file.
+
+    \begin{smaller}
+    \code{javac -processor
+      org.checkerframework.checker.i18nformatter.I18nFormatterChecker MyFile.java}
+    \end{smaller}
+\end{itemize}
+
+
+\sectionAndLabel{Testing whether a string has an i18n format type}{i18n-format-testing}
+
+In the case that the checker cannot infer the i18n format type of a string,
+you can use the \refmethod{checker/i18nformatter/util}{I18nFormatUtil}{hasFormat}{-java.lang.String-org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory...-}
+method to define the type of the string in the scope of a conditional statement.
+
+\begin{description}
+
+\item[\refmethod{checker/i18nformatter/util}{I18nFormatUtil}{hasFormat}{-java.lang.String-org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory...-}]
+  returns \<true> if the given string has the given i18n format type.
+
+\end{description}
+
+\noindent For an example, see Section~\ref{i18n-format-examples}.
+
+To use the \refclass{checker/i18nformatter/util}{I18nFormatUtil} class, the \<checker-util.jar> file
+must be on the classpath at run time.
+
+
+\sectionAndLabel{Examples of using the Internationalization Format Checker}{i18n-format-examples}
+
+\begin{itemize}
+  \item Using \sunjavadoc{java.base/java/text/MessageFormat.html\#format(java.lang.String,java.lang.Object...)}{MessageFormat.format}.
+\begin{Verbatim}
+      // suppose the bundle "MyResource" contains:  key1={0, number} {1, date}
+      String value = ResourceBundle.getBundle("MyResource").getString("key1");
+      MessageFormat.format(value, 3.14, new Date());  // OK
+      // error: incompatible types in argument; found String, expected number
+      MessageFormat.format(value, "Text", new Date());
+\end{Verbatim}
+  \item Using the
+    \refmethod{checker/i18nformatter/util}{I18nFormatUtil}{hasFormat}{-java.lang.String-org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory...-}
+    method to check whether a format
+    string has particular conversion categories.
+\begin{Verbatim}
+      void test1(String format) {
+        if (I18nFormatUtil.hasFormat(format, I18nConversionCategory.GENERAL,
+                                             I18nConversionCategory.NUMBER)) {
+          MessageFormat.format(format, "Hello", 3.14);  // OK
+          // error: incompatible types in argument; found String, expected number
+          MessageFormat.format(format, "Hello", "Bye");
+          // error: missing arguments; expected 2 but 1 given
+          MessageFormat.format(format, "Bye");
+          // error: too many arguments; expected 2 but 3 given
+          MessageFormat.format(format, "A String", 3.14, 3.14);
+        }
+      }
+\end{Verbatim}
+  \item Using \refqualclass{checker/i18nformatter/qual}{I18nFormatFor}
+    to ensure that an argument is a particular type of format string.
+\begin{Verbatim}
+      static void method(@I18nFormatFor("#2") String f, Object... args) {...}
+
+      // OK, MessageFormat.format(...) would return "3.14 Hello greater than one"
+      method("{0, number} {1} {2, choice,0#zero|1#one|1<greater than one}",
+             3.14, "Hello", 100);
+
+      // error: incompatible types in argument; found String, expected number
+      method("{0, number} {1}", "Bye", "Bye");
+\end{Verbatim}
+  \item Annotating a string with
+    \refqualclass{checker/i18nformatter/qual}{I18nFormat}.
+\begin{Verbatim}
+      @I18nFormat({I18nConversionCategory.DATE}) String;
+      s1 = "{0}";
+      s1 = "{0, number}";        // error: incompatible types in assignment
+\end{Verbatim}
+\end{itemize}
+
+%%  LocalWords:  I18n i18n java MessageFormat I18nFormat I18nFormatFor
+%%  LocalWords:  arg I18nInvalid I18nUnknownFormat I18nFormatBottom
+%%  LocalWords:  Timestamp AtomicInteger AtomicLong BigDecimal BigInteger
+%%  LocalWords:  number' foo subformats resource1 key1 mero Abundlenames
+%%  LocalWords:  ResourceBundles MyResource MyFile Apropfiles hasFormat
+%%  LocalWords:  I18nFormatUtil formatter CharSequence I18nInvalidFormat
diff --git a/docs/manual/index-checker.tex b/docs/manual/index-checker.tex
new file mode 100644
index 0000000..6c4e6a8
--- /dev/null
+++ b/docs/manual/index-checker.tex
@@ -0,0 +1,813 @@
+\htmlhr
+% Reinstate when lists are supported:
+% \chapterAndLabel{Index Checker for sequence bounds (arrays, strings, lists)}{index-checker}
+\chapterAndLabel{Index Checker for sequence bounds (arrays and strings)}{index-checker}
+
+The Index Checker warns about potentially out-of-bounds accesses to sequence
+data structures, such as arrays
+% , lists,
+and strings.
+
+The Index Checker prevents \<IndexOutOfBoundsException>s that result from
+an index expression that might be negative or might be equal to or larger
+than the sequence's length.
+It also prevents \<NegativeArraySizeException>s that result from a negative
+array dimension in an array creation expression.
+(A caveat: the Index Checker does not check for arithmetic overflow. If
+an expression overflows, the Index Checker might fail to warn about a
+possible exception.  This is unlikely to be a problem in practice unless
+you have an array whose length is \<Integer.MAX\_VALUE>.)
+
+% Here's a pathological example of overflow leading to unsoundness:
+%
+% public class IndexOverflow {
+%     public static void main(String[] args) {
+%         @Positive int x = 1073741825; // 2 ^ 30 + 1
+%         @Positive int x2 = x + x; // 2 ^ 31 + 2 == - 2 ^ 31 + 2
+%         int[] a = new int[0];
+%         if (x2 < a.length) {
+%             a[x2] = 42;
+%         }
+%     }
+% }
+
+The programmer can write annotations that indicate which expressions are
+indices for which sequences.  The Index Checker prohibits any operation that
+may violate these properties, and the Index Checker takes advantage of
+these properties when verifying indexing operations.
+%
+Typically, a programmer writes few annotations, because the Index Checker
+infers properties of indexes from
+the code around them. For example, it will infer that \<x> is positive
+within the \<then> block of an \code{if (x > 0)} statement.
+The programmer does need to write field types and method pre-conditions or post-conditions. For instance,
+if a method's formal parameter is used as an index for
+\<myArray>, the programmer might need to
+write an \refqualclasswithparams{checker/index/qual}{IndexFor}{"myArray"}
+annotation on the formal parameter's types.
+
+The Index Checker checks fixed-size data structures, whose size is never
+changed after creation.  A fixed-size data structure has no \<add> or
+\<remove> operation.  Examples are strings and arrays, and you can add
+support for other fixed-size data structures (see
+Section~\ref{index-annotating-fixed-size}).
+
+To run the Index Checker, run the command
+
+\begin{alltt}
+  javac -processor index \emph{MyJavaFile}.java
+\end{alltt}
+
+Recall that in Java, type annotations are written before the type;
+in particular,
+array annotations appear immediately before ``\<[]>''.
+Here is how to declare a length-9 array of positive integers:
+
+\begin{Verbatim}
+  @Positive int @ArrayLen(9) []
+\end{Verbatim}
+
+Multi-dimensional arrays are similar.
+Here is how to declare a length-2 array of length-4 arrays:
+
+\begin{Verbatim}
+  String @ArrayLen(2) [] @ArrayLen(4) []
+\end{Verbatim}
+
+
+\sectionAndLabel{Index Checker structure and annotations}{index-annotations}
+
+Internally, the Index Checker computes information about integers that
+might be indices:
+\begin{itemize}
+\item
+  the lower bound on an integer, such as whether it is known to be positive
+  (Section~\ref{index-lowerbound})
+\item
+  the upper bound on an integer, such as whether it is less than the length
+  of a given sequence (Section~\ref{index-upperbound})
+\item
+  whether an integer came from calling the JDK's binary search routine on
+  an array (Section~\ref{index-searchindex})
+\item
+  whether an integer came from calling a string search routine
+  (Section~\ref{index-substringindex})
+\end{itemize}
+
+\noindent
+and about sequence lengths:
+\begin{itemize}
+\item
+  the minimum length of a sequence, such ``\<myArray> contains at least 3
+  elements'' (Section~\ref{index-minlen})
+\item
+  whether two sequences have the same length (Section~\ref{index-samelen})
+\end{itemize}
+
+The Index Checker checks of all these properties at once, but
+this manual discusses each type system in a different section.
+There are some annotations that are shorthand for writing multiple
+annotations, each from a different type system:
+
+\begin{description}
+\item[\refqualclasswithparams{checker/index/qual}{IndexFor}{String[] names}]
+  The value is a valid index for the named sequences.  For example, the
+  \sunjavadoc{java.base/java/lang/String.html\#charAt(int)}{String.charAt(int)}
+  method is declared as
+
+  \begin{Verbatim}
+  class String {
+    char charAt(@IndexFor("this") index) { ... }
+  }
+  \end{Verbatim}
+
+  More generally, a variable
+  declared as \<@IndexFor("someArray") int i> has type
+  \<@IndexFor("someArray") int> and its run-time value is guaranteed to be
+  non-negative and less than the length of \<someArray>.  You could also
+  express this as
+  \<\refqualclass{checker/index/qual}{NonNegative}
+  \refqualclasswithparams{checker/index/qual}{LTLengthOf}{"someArray"}
+  int i>,
+  but \<@IndexFor("someArray") int i> is more concise.
+
+ \item[\refqualclasswithparams{checker/index/qual}{IndexOrHigh}{String[] names}]
+   The value is non-negative and is less than or equal to the length of
+   each named sequence.  This type combines
+  \refqualclass{checker/index/qual}{NonNegative} and
+  \refqualclass{checker/index/qual}{LTEqLengthOf}.
+
+  For example, the
+  \sunjavadoc{java.base/java/util/Arrays.html\#fill(java.lang.Object\%5B\%5D,int,int,java.lang.Object)}{Arrays.fill}
+   method is declared as
+
+  \begin{mysmall}
+  \begin{Verbatim}
+  class Arrays {
+    void fill(Object[] a, @IndexFor("#1") int fromIndex, @IndexOrHigh("#1") int toIndex, Object val)
+  }
+  \end{Verbatim}
+  \end{mysmall}
+
+ \item[\refqualclasswithparams{checker/index/qual}{LengthOf}{String[] names}]
+   The value is exactly equal to the length of the named
+   sequences. In the implementation, this type aliases
+   \refqualclass{checker/index/qual}{IndexOrHigh}, so writing it
+   only adds documentation (although future versions of the Index Checker
+   may use it to improve precision).
+
+ \item[\refqualclasswithparams{checker/index/qual}{IndexOrLow}{String[] names}]
+   The value is -1 or is a valid index for
+   each named sequence.  This type combines
+  \refqualclass{checker/index/qual}{GTENegativeOne} and
+  \refqualclass{checker/index/qual}{LTLengthOf}.
+
+%  Example commented out; IndexOrLow is not sound for indexOf, because "".indexOf("") returns 0
+%
+%  For example, the
+%  \sunjavadoc{java.base/java/lang/String.html\#indexOf(java.lang.String)}{String.indexOf(String)}
+%  method is declared as
+%
+%  \begin{Verbatim}
+%  class String {
+%    @IndexOrLow("this") int indexOf(String str) { ... }
+%  }
+%  \end{Verbatim}
+
+ \item[\refqualclass{checker/index/qual}{PolyIndex}]
+   indicates qualifier polymorphism.  This type combines
+   \refqualclass{checker/index/qual}{PolyLowerBound} and
+   \refqualclass{checker/index/qual}{PolyUpperBound}.
+   For a description of qualifier polymorphism, see
+   Section~\ref{method-qualifier-polymorphism}.
+
+ \item[\refqualclass{checker/index/qual}{PolyLength}]
+   is a special polymorphic qualifier that combines
+   \refqualclass{checker/index/qual}{PolySameLen} and
+   \refqualclass{common/value/qual}{PolyValue} from the
+   Constant Value Checker (see \chapterpageref{constant-value-checker}).
+   \refqualclass{checker/index/qual}{PolyLength} exists
+   as a shorthand for these two annotations, since
+   they often appear together.
+
+\end{description}
+
+\sectionAndLabel{Lower bounds}{index-lowerbound}
+
+The Index Checker issues an error when
+a sequence is indexed by an integer that might be negative.
+The Lower Bound Checker uses a type system (Figure~\ref{fig-index-int-types}) with the following
+qualifiers:
+
+\begin{description}
+\item[\refqualclass{checker/index/qual}{Positive}]
+  The value is 1 or greater, so it is not too low to be used as an index.
+  Note that this annotation is trusted by the Constant Value Checker,
+  so if the Constant Value Checker is run on code containing this annotation,
+  the Lower Bound Checker must be run on the same code in order to
+  guarantee soundness.
+\item[\refqualclass{checker/index/qual}{NonNegative}]
+  The value is 0 or greater, so it is not too low to be used as an index.
+\item[\refqualclass{checker/index/qual}{GTENegativeOne}]
+  The value is -1 or greater.
+  It may not be used as an index for a sequence, because it might be too low.
+  (``\<GTE>'' stands for ``Greater Than or Equal to''.)
+\item[\refqualclass{checker/index/qual}{PolyLowerBound}]
+  indicates qualifier polymorphism.
+  For a description of qualifier polymorphism, see
+  Section~\ref{method-qualifier-polymorphism}.
+\item[\refqualclass{checker/index/qual}{LowerBoundUnknown}]
+  There is no information about the value.
+  It may not be used as an index for a sequence, because it might be too low.
+\item[\refqualclass{checker/index/qual}{LowerBoundBottom}]
+    The value cannot take on any integral types. The bottom type, which
+    should not need to be written by the programmer.
+\end{description}
+
+\begin{figure}
+\begin{center}
+  \hfill
+  \includeimagenocentering{lowerbound}{5cm}
+  ~~~~\hfill~~~~
+  \includeimagenocentering{upperbound}{7cm}
+  \hfill
+\end{center}
+  \caption{The two type hierarchies for integer types used by the Index
+    Checker.  On the left is a type system for lower bounds.  On the right
+    is a type system for upper bounds.  Qualifiers written in gray should
+    never be written in source code; they are used internally by the type
+    system.
+    % Using "\\" works for some but not all installations of LaTeX.
+    \newline
+    In the Upper Bound type system, subtyping rules depend on both the
+    array name (\<"myArray">, in the figure) and on the offset (which is 0,
+    the default, in the figure).  Another qualifier is
+    \refqualclass{checker/index/qual}{UpperBoundLiteral}, whose subtyping
+    relationships depend on its argument and on offsets for other qualifiers.
+ }
+  \label{fig-index-int-types}
+\end{figure}
+
+
+\sectionAndLabel{Upper bounds}{index-upperbound}
+
+The Index Checker issues an error when a sequence index might be
+too high. To do this, it maintains information about which expressions are
+safe indices for which sequences.
+The length of a sequence is \code{arr.length} for arrays and
+\code{str.length()} for strings.
+It uses a type system (Figure~\ref{fig-index-int-types}) with the following
+qualifiers:
+
+It issues an error when a sequence \code{arr}
+is indexed by an integer that is not of type \code{@LTLengthOf("arr")}
+or \code{@LTOMLengthOf("arr")}.
+
+\begin{description}
+
+\item[\refqualclasswithparams{checker/index/qual}{LTLengthOf}{String[] names, String[] offset}]
+  An expression with this type
+  has value less than the length of each sequence listed in \<names>.
+  The expression may be used as an index into any of those sequences,
+  if it is non-negative.
+  For example, an expression of type \code{@LTLengthOf("a") int} might be
+  used as an index to \<a>.
+  The type \code{@LTLengthOf(\{"a", "b"\})} is a subtype of both
+  \code{@LTLengthOf("a")} and \code{@LTLengthOf("b")}.
+  (``\<LT>'' stands for ``Less Than''.)
+
+  \<@LTLengthOf> takes an optional \<offset> element, meaning that the
+  annotated expression plus the offset is less than the length of the given
+  sequence.  For example, suppose expression \<e> has type \<@LTLengthOf(value
+  = \{"a", "b"\}, offset = \{"-1", "x"\})>. Then \<e - 1> is less than
+  \<a.length>, and \<e + x> is less than \<b.length>.  This helps to make
+  the checker more precise.  Programmers rarely need to write the \<offset>
+  element.
+
+\item[\refqualclasswithparams{checker/index/qual}{LTEqLengthOf}{String[] names}]
+  An expression with this type
+  has value less than or equal to the length of each sequence listed in \<names>.
+  It may not be used as an index for these sequences, because it might be too high.
+  \code{@LTEqLengthOf(\{"a", "b"\})} is a subtype of both
+  \code{@LTEqLengthOf("a")} and \code{@LTEqLengthOf("b")}.
+  (``\<LTEq>'' stands for ``Less Than or Equal to''.)
+
+  \<@LTEqLengthOf(\{"a"\})> = \<@LTLengthOf(value=\{"a"\}, offset=-1)>, and \\
+  \<@LTEqLengthOf(value=\{"a"\}, offset=x)> = \<@LTLengthOf(value=\{"a"\},
+  offset=x-1)> for any x.
+
+\item[\refqualclasswithparams{checker/index/qual}{LTOMLengthOf}{String[] names}]
+  An expression with this type
+  has value at least 2 less than the length of each sequence listed in \<names>.
+  It may always used as an index for a sequence listed in \<names>, if it is
+  non-negative.
+
+  This type exists to allow the checker to infer the safety of loops of
+  the form:
+\begin{Verbatim}
+  for (int i = 0; i < array.length - 1; ++i) {
+    arr[i] = arr[i+1];
+  }
+\end{Verbatim}
+  This annotation should rarely (if ever) be written by the programmer; usually
+  \refqualclasswithparams{checker/index/qual}{LTLengthOf}{String[] names}
+  should be written instead.
+  \code{@LTOMLengthOf(\{"a", "b"\})} is a subtype of both
+  \code{@LTOMLengthOf("a")} and \code{@LTOMLengthOf("b")}.
+  (``\<LTOM>'' stands for ``Less Than One Minus'', because another way of
+  saying ``at least 2 less than \<a.length>'' is ``less than \<a.length-1>''.)
+
+  \<@LTOMLengthOf(\{"a"\})> = \<@LTLengthOf(value=\{"a"\}, offset=1)>, and \\
+  \<@LTOMLengthOf(value=\{"a"\}, offset=x)> = \<@LTLengthOf(value=\{"a"\},
+  offset=x+1)> for any x.
+
+\item[\refqualclass{checker/index/qual}{UpperBoundLiteral}]
+  repesents a constant value, typically a literal written in source code.
+  Its subtyping relationship is:
+  \<@UpperBoundLiteral(lit)> <: \<LTLengthOf(value="myArray", offset=off)>
+  if \<lit>+\<offset> $\le$ -1.
+
+\item[\refqualclass{checker/index/qual}{PolyUpperBound}]
+  indicates qualifier polymorphism.
+  For a description of qualifier polymorphism, see
+  Section~\ref{method-qualifier-polymorphism}.
+
+\item[\refqualclass{checker/index/qual}{UpperBoundUnknown}]
+  There is no information about the upper bound on the value of an expression with this type.
+  It may not be used as an index for a sequence, because it might be too high.
+  This type is the top type, and should never need to be written by the
+  programmer.
+
+\item[\refqualclass{checker/index/qual}{UpperBoundBottom}]
+  This is the bottom type for the upper bound type system. It should
+  never need to be written by the programmer.
+
+\end{description}
+
+The following method annotations can be used to establish a method postcondition
+that ensures that a certain expression is a valid index for a sequence:
+
+\begin{description}
+\item[\refqualclasswithparams{checker/index/qual}{EnsuresLTLengthOf}{String[] value, String[] targetValue, String[] offset}]
+  When the method with this annotation returns, the expression (or all the expressions) given in the \code{value} element
+  is less than the length of the given sequences with the given offsets. More precisely, the expression
+  has the \code{@LTLengthOf} qualifier with the \code{value} and \code{offset} arguments
+  taken from the \code{targetValue} and \code{offset} elements of this annotation.
+\item[\refqualclasswithparams{checker/index/qual}{EnsuresLTLengthOfIf}{String[] expression, boolean result, String[] targetValue, String[] offset}]
+  If the method with this annotation returns the given boolean value,
+  then the given expression (or all the given expressions)
+  is less than the length of the given sequences with the given offsets.
+\end{description}
+
+There is one declaration annotation that indicates the relationship between
+two sequences:
+
+\begin{description}
+\item[\refqualclasswithparams{checker/index/qual}{HasSubsequence}{String[]
+    value, String[] from, String[] to}]
+  indicates that a subsequence (from \code{from} to \code{to}) of the
+  annotated sequence is equal to some other sequence, named by
+  \code{value}).
+
+For example, to indicate that \<shorter> is a subsequence of \<longer>:
+
+\begin{Verbatim}
+  int startIndex;
+  int endIndex;
+  int[] shorter;
+  @HasSubsequence(value="shorter", from="this.start", to="this.end")
+  int[] longer;
+\end{Verbatim}
+
+Thus, a valid index into \<shorter> is also a valid index (between
+\code{start} and \code{end-1} inclusive) into \<longer>.  More generally,
+if \code{x} is \code{@IndexFor("shorter")} in the example above, then
+\code{start + x} is \code{@IndexFor("longer")}. If \code{y} is
+\code{@IndexFor("longer")} and \code{@LessThan("end")}, then \code{y -
+  start} is \code{@IndexFor("shorter")}. Finally, \code{end - start} is
+\code{@IndexOrHigh("shorter")}.
+
+This annotation is in part checked and in part trusted.  When an array is
+assigned to \code{longer}, three facts are checked: that \code{start} is
+non-negative, that \code{start} is less than or equal to \code{end}, and
+that \code{end} is less than or equal to the length of \code{longer}.  This
+ensures that the indices are valid. The programmer must manually verify
+that the value of \code{shorter} equals the subsequence that they describe.
+\end{description}
+
+
+\sectionAndLabel{Sequence minimum lengths}{index-minlen}
+
+The Index Checker estimates, for each sequence expression, how long its value
+might be at run time by computing a minimum length that
+the sequence is guaranteed to have.  This enables the Index Checker to
+verify indices that are compile-time constants.  For example, this code:
+
+\begin{Verbatim}
+  String getThirdElement(String[] arr) {
+    return arr[2];
+  }
+\end{Verbatim}
+
+\noindent
+is legal if \<arr> has at least three elements, which can be indicated
+in this way:
+
+\begin{Verbatim}
+  String getThirdElement(String @MinLen(3) [] arr) {
+    return arr[2];
+  }
+\end{Verbatim}
+
+When the index is not a compile-time constant, as in \<arr[i]>, then the
+Index Checker depends not on a \<@MinLen> annotation but on \<i> being
+annotated as
+\refqualclasswithparams{checker/index/qual}{LTLengthOf}{"arr"}.
+
+The MinLen type qualifier is implemented in practice by the Constant Value Checker,
+using \<@ArrayLenRange> annotations (see \chapterpageref{constant-value-checker}).
+This means that errors related to the minimum lengths of arrays must be suppressed using
+the "value" argument to \<@SuppressWarnings>.
+\refqualclass{common/value/qual}{ArrayLenRange} and \refqualclass{common/value/qual}{ArrayLen}
+annotations can also be used to establish the minimum length of a sequence, if a
+more precise estimate of length is known. For example,
+if \<arr> is known to have exactly three elements:
+
+\begin{Verbatim}
+  String getThirdElement(String @ArrayLen(3) [] arr) {
+    return arr[2];
+  }
+\end{Verbatim}
+
+The following type qualifiers (from \chapterpageref{constant-value-checker})
+can establish the minimum length of a sequence:
+
+\begin{description}
+\item[\refqualclasswithparams{common/value/qual}{MinLen}{int value}]
+  The value of an expression of this type is a sequence with at least
+  \code{value} elements.  The default annotation is
+  \code{@MinLen(0)}, and it may be applied to non-sequences.
+  \code{@MinLen($x$)} is a subtype of \code{@MinLen($x-1$)}.
+  An \code{@MinLen} annotation is treated internally as an
+  \refqualclass{common/value/qual}{ArrayLenRange} with only its
+  \code{from} field filled.
+\item[\refqualclasswithparams{common/value/qual}{ArrayLen}{int[] value}]
+  The value of an expression of this type is a sequence whose
+  length is exactly one of the integers listed in its argument.
+  The argument can contain at most ten integers; larger collections of
+  integers are converted to \refqualclass{common/value/qual}{ArrayLenRange}
+  annotations. The minimum length of a sequence with this annotation
+  is the smallest element of the argument.
+\item[\refqualclasswithparams{common/value/qual}{ArrayLenRange}{int from, int to}]
+  The value of an expression of this type is a sequence whose
+  length is bounded by its arguments, inclusive.
+  The minimum length of a sequence with this annotation is its \<from> argument.
+\end{description}
+
+\begin{figure}
+\begin{center}
+  \hfill
+  \includeimage{samelen}{5cm}
+  \hfill
+\end{center}
+  \caption{The type hierarchy for arrays of equal length ("a" and "b" are
+    assumed to be in-scope sequences).  Qualifiers
+    written in gray should never be written in source code; they are used
+    internally by the type system.}
+  \label{fig-index-array-types}
+\end{figure}
+
+The following method annotation can be used to establish a method postcondition
+that ensures that a certain sequence has a minimum length:
+
+\begin{description}
+\item[\refqualclasswithparams{common/value/qual}{EnsuresMinLenIf}{String[] expression, boolean result, int targetValue}]
+  If the method with this annotation returns the given boolean value,
+  then the given expression (or all the given expressions) is a sequence
+  with at least \code{targetValue} elements.
+\end{description}
+
+\sectionAndLabel{Sequences of the same length}{index-samelen}
+
+The Index Checker determines whether two or more sequences have the same length.
+This enables it to verify that all the indexing operations are safe in code
+like the following:
+
+\begin{Verbatim}
+  boolean lessThan(double[] arr1, double @SameLen("#1") [] arr2) {
+    for (int i = 0; i < arr1.length; i++) {
+      if (arr1[i] < arr2[i]) {
+        return true;
+      } else if (arr1[i] > arr2[i]) {
+        return false;
+      }
+    }
+    return false;
+  }
+\end{Verbatim}
+
+When needed, you can specify which sequences have the same length using the following type qualifiers (Figure~\ref{fig-index-array-types}):
+
+\begin{description}
+\item[\refqualclasswithparams{checker/index/qual}{SameLen}{String[] names}]
+  An expression with this type represents a sequence that has the
+  same length as the other sequences named in \<names>. In general,
+  \code{@SameLen} types that have non-intersecting sets of names
+  are \textit{not} subtypes of each other. However, if at least one
+  sequence is named by both types, the types are actually the same,
+  because all the named sequences must have the same length.
+\item[\refqualclass{checker/index/qual}{PolySameLen}]
+  indicates qualifier polymorphism.
+  For a description of qualifier polymorphism, see
+  Section~\ref{method-qualifier-polymorphism}.
+\item[\refqualclass{checker/index/qual}{SameLenUnknown}]
+  No information is known about which other sequences have the same length
+  as this one.
+  This is the top type, and programmers should never need to write it.
+\item[\refqualclass{checker/index/qual}{SameLenBottom}]
+  This is the bottom type, and programmers should rarely need to write it.
+  \code{null} has this type.
+\end{description}
+
+
+\sectionAndLabel{Binary search indices}{index-searchindex}
+
+The JDK's
+\sunjavadoc{java.base/java/util/Arrays.html\#binarySearch(java.lang.Object\%5B\%5D,java.lang.Object)}{Arrays.binarySearch}
+method returns either where the value was found, or a negative value
+indicating where the value could be inserted.  The Search Index Checker
+represents this concept.
+
+\begin{figure}
+\begin{center}
+  \hfill
+  \includeimage{searchindex}{7cm}
+  \hfill
+\end{center}
+  \caption{The type hierarchy for the Index Checker's internal type system
+  that captures information about the results of calls to
+  \sunjavadoc{java.base/java/util/Arrays.html\#binarySearch(java.lang.Object\%5B\%5D,java.lang.Object)}{Arrays.binarySearch}.}
+  \label{fig-index-searchindex}
+\end{figure}
+
+The Search Index Checker's type hierarchy (Figure~\ref{fig-index-searchindex}) has four type qualifiers:
+\begin{description}
+\item[\refqualclasswithparams{checker/index/qual}{SearchIndexFor}{String[] names}]
+  An expression with this type represents an integer that could have been
+  produced by calling
+  \sunjavadoc{java.base/java/util/Arrays.html\#binarySearch(java.lang.Object\%5B\%5D,java.lang.Object)}{Arrays.binarySearch}:
+  for each array \<a> specified in the annotation, the annotated integer is
+  between \<-a.length-1> and \<a.length-1>, inclusive
+\item[\refqualclasswithparams{checker/index/qual}{NegativeIndexFor}{String[] names}]
+  An expression with this type represents a ``negative index'' that is
+  between \<a.length-1> and \<-1>, inclusive; that is, a value that is both
+  a \<@SearchIndex> and is negative.  Applying the bitwise complement
+  operator (\verb|~|) to an expression of this type produces an expression
+  of type \refqualclass{checker/index/qual}{IndexOrHigh}.
+\item[\refqualclass{checker/index/qual}{SearchIndexBottom}]
+  This is the bottom type, and programmers should rarely need to write it.
+\item[\refqualclass{checker/index/qual}{SearchIndexUnknown}]
+  No information is known about whether this integer is a search index.
+  This is the top type, and programmers should rarely need to write it.
+\end{description}
+
+
+\sectionAndLabel{Substring indices}{index-substringindex}
+
+The methods
+\sunjavadoc{java.base/java/lang/String.html\#indexOf(java.lang.String)}{String.indexOf}
+and
+\sunjavadoc{java.base/java/lang/String.html\#lastIndexOf(java.lang.String)}{String.lastIndexOf}
+return an index of a given substring within a given string, or -1 if no
+such substring exists.  The index \<i> returned from
+\<receiver.indexOf(substring)> satisfies the following property, which is
+stated here in three equivalent ways:
+\begin{Verbatim}
+ i == -1 || ( i >= 0       && i <= receiver.length() - substring.length()                  )
+ i == -1 || ( @NonNegative && @LTLengthOf(value="receiver", offset="substring.length()-1") )
+ @SubstringIndexFor(value="receiver", offset="substring.length()-1")
+\end{Verbatim}
+
+% This new annotation is similar to \<@LTLengthOf\allowbreak(value =
+% "receiver", offset = "substring.length()-1")>, but explicitly allows the
+% index to be -1 even if the upper bound would not allow it because of the
+% offset.  The Upper Bound Checker can infer the corresponding
+% \<@LTLengthOf> annotation for expressions that have a
+% \<@SubstringIndexFor> annotation and at the same time are known to be
+% non-negative (according to the Lower Bound Checker).
+
+The return type of methods \sunjavadoc{java.base/java/lang/String.html\#indexOf(java.lang.String)}{String.indexOf}
+and \sunjavadoc{java.base/java/lang/String.html\#lastIndexOf(java.lang.String)}{String.lastIndexOf} has the annotation
+\refqualclasswithparams{checker/index/qual}{SubstringIndexFor}{value="this", offset="\#1.length()-1")}.
+This allows writing code such as the following with no warnings from the
+Index Checker:
+
+\begin{Verbatim}
+  public static String removeSubstring(String original, String removed) {
+    int i = original.indexOf(removed);
+    if (i != -1) {
+      return original.substring(0, i) + original.substring(i + removed.length());
+    }
+    return original;
+  }
+\end{Verbatim}
+
+% The code removes the first occurrence of \<removed> from
+% \<original>. After checking that \code{i != -1}, the value of \<i> must
+% be a valid index for \<original>. Because this index is the start of an
+% occurrence of \<removed>, \code{i + removed.length()} is the index of the
+% end of the occurrence.  Without the \<@SubstringIndexFor> annotation, the
+% Upper Bound Checker would not be able to verify that \code{i +
+% removed.length()} is a valid argument to \<substring>, which requires
+% both arguments to be \<@IndexOrHigh("original")>.
+
+\begin{figure}
+\begin{center}
+  \hfill
+  \includeimage{substringindex}{3.5cm}
+  \hfill
+\end{center}
+  \caption{The type hierarchy for the Substring Index Checker, which
+    captures information about the results of calls to
+    \sunjavadoc{java.base/java/lang/String.html\#indexOf(java.lang.String)}{String.indexOf}
+    and
+    \sunjavadoc{java.base/java/lang/String.html\#lastIndexOf(java.lang.String)}{String.lastIndexOf}.}
+  \label{fig-index-substringindex}
+\end{figure}
+
+The \<@SubstringIndexFor> annotation is implemented in a Substring Index
+Checker that runs together with the Index Checker and has its own type
+hierarchy (Figure~\ref{fig-index-substringindex}) with three type
+qualifiers:
+\begin{description}
+\item[\refqualclasswithparams{checker/index/qual}{SubstringIndexFor}{String[] value, String[] offset}]
+  An expression with this type represents an integer that could have been
+  produced by calling
+  \sunjavadoc{java.base/java/lang/String.html\#indexOf(java.lang.String)}{String.indexOf}:
+  the annotated integer is either -1, or it is non-negative and is less
+  than or equal to \<receiver.length - offset> (where the sequence
+  \<receiver> and the offset \<offset> are corresponding elements of the
+  annotation's arguments).
+\item[\refqualclass{checker/index/qual}{SubstringIndexBottom}]
+  This is the bottom type, and programmers should rarely need to write it.
+\item[\refqualclass{checker/index/qual}{SubstringIndexUnknown}]
+  No information is known about whether this integer is a substring index.
+  This is the top type, and programmers should rarely need to write it.
+\end{description}
+
+
+\subsectionAndLabel{The need for the \<@SubstringIndexFor> annotation}{index-substringindex-justification}
+
+No other annotation supported by the Index Checker precisely represents the
+possible return values of methods
+\sunjavadoc{java.base/java/lang/String.html\#indexOf(java.lang.String)}{String.indexOf}
+and
+\sunjavadoc{java.base/java/lang/String.html\#lastIndexOf(java.lang.String)}{String.lastIndexOf}.
+The reason is the methods' special cases for empty strings and for failed matches.
+
+Consider the result \<i> of \<receiver.indexOf(substring)>:
+
+\begin{itemize}
+\item
+  \<i> is \<@GTENegativeOne>, because \code{i >= -1}.
+\item
+  \<i> is \<@LTEqLengthOf("receiver")>, because \code{i <= receiver.length()}.
+\item
+  \<i> is not \<@IndexOrLow("receiver")>, because for
+  \code{receiver = "", substring = "", i = 0}, the property
+  \code{i >= -1 \&\& i < receiver.length()} does not hold.
+\item
+  \<i> is not \<@IndexOrHigh("receiver")>, because for
+  \code{receiver = "", substring = "b", i = -1}, the property
+  \code{i >= 0 \&\& i <= receiver.length()} does not hold.
+\item
+  \<i> is not
+  \<@LTLengthOf(value = "receiver", offset = "substring.length()-1")>,
+  because for \code{receiver = "", substring = "abc", i = -1}, the property
+  \code{i + substring.length() - 1 < receiver.length()} does not hold.
+\end{itemize}
+
+\noindent
+The last annotation in the list above,
+\<@LTLengthOf(value = "receiver", offset = "substring.length()-1")>,
+is the correct and precise upper bound for all values of \<i> except -1.
+The offset expresses the fact that we can add \<substring.length()> to this
+index and still get a valid index for \<receiver>.  That is useful for
+type-checking code that adds the length of the substring to the found
+index, in order to obtain the rest of the string.  However, the upper bound
+applies only after the index is explicitly checked not to be -1:
+
+\begin{Verbatim}
+  int i = receiver.indexOf(substring);
+  // i is @GTENegativeOne and @LTEqLengthOf("receiver")
+  // i is not @LTLengthOf(value = "receiver", offset = "substring.length()-1")
+  if (i != -1) {
+    // i is @NonNegative and @LTLengthOf(value = "receiver", offset = "substring.length()-1")
+    int j = i + substring.length();
+    // j is @IndexOrHigh("receiver")
+    return receiver.substring(j); // this call is safe
+  }
+\end{Verbatim}
+
+The property of the result of \<indexOf> cannot be expressed by any
+combination of lower-bound (Section~\ref{index-lowerbound}) and upper-bound
+(Section~\ref{index-upperbound}) annotations, because the upper-bound
+annotations apply independently of the lower-bound annotations, but in this
+case, the upper bound \code{i <= receiver.length() - substring.length()}
+holds only if \code{i >= 0}.  Therefore, to express this property and make
+the example type-check without false positives, a new annotation such as
+\<@SubstringIndexFor\allowbreak(value = "receiver", offset = "substring.length()-1")>
+is necessary.
+
+\sectionAndLabel{Inequalities}{index-inequalities}
+
+The Index Checker estimates which expression's values are less than other expressions' values.
+
+\begin{description}
+
+\item[\refqualclasswithparams{checker/index/qual}{LessThan}{String[] values}]
+  An expression with this type has a value that is less than the value of each
+  expression listed in \<values>. The expressions in values must be composed of
+  final or effectively final variables and constants.
+
+\item[\refqualclass{checker/index/qual}{LessThanUnknown}]
+  There is no information about the value of an expression this type relative to other expressions.
+  This is the top type, and should not be written by the programmer.
+
+ \item[\refqualclass{checker/index/qual}{LessThanBottom}]
+   This is the bottom type for the less than type system. It should
+   never need to be written by the programmer.
+
+\end{description}
+
+\sectionAndLabel{Annotating fixed-size data structures}{index-annotating-fixed-size}
+
+The Index Checker has built-in support for Strings and arrays.
+You can add support for additional fixed-size data structures by writing
+annotations.
+This allows the Index Checker to typecheck the data structure's
+implementation and to typecheck uses of the class.
+
+This section gives an example:  a fixed-length collection.
+
+%% The code that follows is copied from checker/tests/index/ArrayWrapper.java.
+%% If this code is updated, please update that file, too.
+
+\begin{Verbatim}
+/** ArrayWrapper is a fixed-size generic collection. */
+public class ArrayWrapper<T> {
+    private final Object @SameLen("this") [] delegate;
+
+    @SuppressWarnings("index") // constructor creates object of size @SameLen(this) by definition
+    ArrayWrapper(@NonNegative int size) {
+        delegate = new Object[size];
+    }
+
+    public @LengthOf("this") int size() {
+        return delegate.length;
+    }
+
+    public void set(@IndexFor("this") int index, T obj) {
+        delegate[index] = obj;
+    }
+
+    @SuppressWarnings("unchecked") // required for normal Java compilation due to unchecked cast
+    public T get(@IndexFor("this") int index) {
+        return (T) delegate[index];
+    }
+}
+\end{Verbatim}
+
+The Index Checker treats methods annotated with \code{@LengthOf("this")}  as
+the length of a sequence like \code{arr.length} for arrays and
+\code{str.length()} for strings.
+
+With these annotations, client code like the following typechecks with no
+warnings:
+\begin{Verbatim}
+    public static void clearIndex1(ArrayWrapper<? extends Object> a, @IndexFor("#1") int i) {
+        a.set(i, null);
+    }
+
+    public static void clearIndex2(ArrayWrapper<? extends Object> a, int i) {
+        if (0 <= i && i < a.size()) {
+            a.set(i, null);
+        }
+    }
+\end{Verbatim}
+
+%%  LocalWords:  NegativeArraySizeException pre myArray IndexFor someArray
+%%  LocalWords:  MyJavaFile LTLengthOf LTEqLengthOf GTENegativeOne GTE str
+%%  LocalWords:  LowerBoundUnknown LTOMLengthOf LTEq LTOM UpperBoundBottom
+%%  LocalWords:  UpperBoundUnknown MinLen MinLenBottom SameLen indexOf abc
+%%  LocalWords:  SameLenUnknown SameLenBottom lastIndexOf html lang charAt
+%%  LocalWords:  LengthOf IndexOrLow PolyIndex PolyLowerBound PolyLength
+%%  LocalWords:  PolyUpperBound PolySameLen PolyValue LowerBoundBottom
+%%  LocalWords:  lowerbound upperbound EnsuresLTLengthOf targetValue
+%%  LocalWords:  EnsuresLTLengthOfIf boolean HasSubsequence LessThan
+%%  LocalWords:  minlen ArrayLenRange ArrayLen EnsuresMinLenIf samelen
+%%  LocalWords:  searchindex binarySearch SearchIndexFor NegativeIndexFor
+%%  LocalWords:  SearchIndex bitwise SearchIndexBottom SearchIndexUnknown
+%%  LocalWords:  Substring substringindex substring SubstringIndexFor
+%%  LocalWords:  SubstringIndexBottom SubstringIndexUnknown LessThanBottom
+%%  LocalWords:  LessThanUnknown typecheck typechecks
diff --git a/docs/manual/inference.tex b/docs/manual/inference.tex
new file mode 100644
index 0000000..42816b6
--- /dev/null
+++ b/docs/manual/inference.tex
@@ -0,0 +1,576 @@
+\htmlhr
+\chapterAndLabel{Type inference}{type-inference}
+
+This chapter is about tools that infer annotations for your
+program's method signatures and fields, before you run a type-checker.
+To learn about local type inference within a method body,
+see Section~\ref{type-refinement}.
+
+A typical workflow (Section~\ref{tips-about-writing-annotations}) is for a
+programmer to first write annotations on method signatures and fields, then
+run a type-checker.  Type inference performs the first step automatically
+for you.  This saves time for programmers who would otherwise have to
+understand the code, then write annotations manually.
+
+Type inference outputs type qualifiers that are consistent with your
+program's source code.  Your program still might not type-check if your
+program contains a bug or contains tricky code that is beyond the
+capabilities of the type checker.
+
+The qualifiers are output into an annotation file.  They can be viewed and
+adjusted by the programmer, can be used by tools such as the type-checker,
+and can be inserted into the source code or the class file.
+
+Inserting the inferred annotations into the program source code creates documentation in the form of type
+qualifiers, which can aid programmer understanding and may make
+type-checking warnings more comprehensible.
+Storing annotations in side-files is more desirable if the program's source code cannot
+be modified for some reason, if the typechecking is "one-off" (typechecking will
+be done once and its results will be evaluated, but it will not be done
+repeatedly), or if the set of annotations is extremely voluminous and would
+clutter the code.
+
+
+Type inference is most effective when you run it on a program rather than
+on a library --- unless you also run it on an extensive test suite for the
+library.  See Section~\ref{whole-program-inference-non-representative-uses}
+for an explanation.
+
+
+Type inference is costly:  it takes several times longer than
+type-checking does.  However, it only needs to be run once, after which
+you can use and possibly modify the results.
+
+
+\sectionAndLabel{Type inference tools}{type-inference-tools}
+
+This section lists tools that take a program and output a set of
+annotations for it.
+It first lists tools that work only for a single type system (but may do a
+more accurate job for that type system)
+then lists general tools that work for any type system.
+
+\begin{description}
+  \item[For the Nullness Checker:]
+Section~\ref{nullness-inference} lists several tools that infer
+annotations for the Nullness Checker.
+
+\item[For the Purity Checker:]
+If you run the Checker Framework with the \<-AsuggestPureMethods>
+command-line option, it will suggest methods that can be marked as
+\<@SideEffectFree>, \<@Deterministic>, or \<@Pure>; see
+Section~\ref{type-refinement-purity}.
+
+\item[WPI, for any type system:]
+``Whole program inference'', or WPI, is distributed with the Checker
+  Framework.  See Section~\ref{whole-program-inference}.
+
+\item[CFI, for any type system:]
+\href{https://github.com/opprop/checker-framework-inference}{``Checker
+  Framework Inference''}, or CFI, is a type inference framework built on
+a variant of the Checker Framework.  You need to slightly rewrite your type system to
+work with CFI\@.  The
+\ahreforurl{https://github.com/opprop/checker-framework-inference}{CFI
+  repository} contains rewritten versions of some of
+the type systems that are distributed with the Checker Framework.
+
+\item[Cascade, for any type system:]
+\href{https://github.com/reprogrammer/cascade/}{Cascade}~\cite{VakilianPEJ2014}
+is an Eclipse plugin that implements interactive type qualifier inference.
+Cascade is interactive rather than fully-automated:  it makes it easier for
+a developer to insert annotations.
+Cascade starts with an unannotated program and runs a type-checker.  For each
+warning it suggests multiple fixes, the developer chooses a fix, and
+Cascade applies it.  Cascade works with any checker built on the Checker
+Framework.
+You can find installation instructions and a video tutorial at \url{https://github.com/reprogrammer/cascade}.
+% See last commit at https://github.com/reprogrammer/cascade/commits/master .
+Cascade was last updated in November 2014, so it might or might not work for you.
+
+\end{description}
+
+Except for one of the nullness inference tools, all these
+type inference tools are static analyses.  They analyze your program's
+source code, but they do not run your program.
+
+
+\sectionAndLabel{Whole-program inference}{whole-program-inference}
+
+Whole-program inference
+infers types for fields, method parameters, and method return types that do not
+have a user-written qualifier (for the given type system).
+The inferred type qualifiers are output into annotation files.
+The inferred type is the most specific type that is compatible with all the
+uses in the program.  For example, the inferred type for a field is the
+least upper bound of the types of all the expressions that are assigned
+into the field.
+
+There are three scripts that you can use to run whole-program inference.
+Each has advantages and disadvantages, discussed below:
+
+\begin{itemize}
+    \item
+    To run whole-program inference on a single project without modifying its source code,
+    use the \<wpi.sh> script (Section~\ref{wpi-one}). This script can automatically understand
+    many Ant, Maven, and Gradle build files, so it requires little manual configuration.
+
+    \item
+    To run whole-program inference on many projects without modifying their source code
+    (say, when running it on projects from GitHub), use the \<wpi-many.sh> script (Section~\ref{wpi-many}).
+    This script can understand the same build files as \<wpi.sh>.
+
+    \item
+    If you want to insert the inferred annotations directly into a single
+    project's source code, use the \<infer-and-annotate.sh> script (Section~\ref{wpi-insert}).
+\end{itemize}
+
+These type inference scripts appear in the \<checker/bin/> directory.
+The remainder of this chapter describes them
+(Sections~\ref{wpi-one}--\ref{wpi-insert}), then concludes with discussion
+that applies to all of them.
+
+
+\sectionAndLabel{Running whole-program inference on a single project}{wpi-one}
+
+A typical invocation of \<wpi.sh> is
+
+\begin{Verbatim}
+  wpi.sh -- --checker nullness
+\end{Verbatim}
+
+The result is a set of log files placed in the \<dljc-out/> folder of the
+target project. The results of type-checking with each candidate set of
+annotations will be concatenated into the file \<dljc-out/wpi.log>; the final
+results (i.e., those obtained using the most precise, consistent set of annotations)
+will appear at the end of this file.
+The inferred annotations appear in \<.stub> files in a
+temporary directory whose name appears in the \<dlcj-out/wpi.log> file.
+
+The full syntax for invoking \<wpi.sh> is
+
+\begin{Verbatim}
+  wpi.sh [-d PROJECTDIR] [-t TIMEOUT] [-b EXTRA_BUILD_ARGS] -- [DLJC-ARGS]
+\end{Verbatim}
+
+Arguments in square brackets are optional.
+Here is an explanation of the arguments:
+
+\begin{description}
+\item[-d PROJECTDIR]
+  The top-level directory of the project.  It must contain an Ant, Gradle,
+  or Maven buildfile. The default is the current working directory.
+
+\item[-t TIMEOUT]
+  The timeout for running the checker, in seconds
+
+\item[-b EXTRA\_BUILD\_ARGS]
+  Extra arguments to pass to the build script invocation. This argument
+  will be passed to the build commands, such as
+  \<ant compile>, \<gradle compileJava>, or \<mvn compile>.
+  It is also passed to other build system commands, such as
+  \<ant clean>, \<gradle clean>, or \<mvn clean>.
+  % An alternative would be to permit it to be passed multiple times, and
+  % not re-tokenize it.
+  The argument may contain spaces and is re-tokenized by the shell.
+
+\item[-g GRADLECACHEDIR]
+  The directory to use for the `-g` option to Gradle (the Gradle home
+  directory). This option is ignored if the target project does not
+  build with Gradle. The default is `.gradle` relative to the target
+  project (i.e., each target project has its own Gradle home). This default
+  is motivated by
+  \ahref{https://github.com/gradle/gradle/issues/1319}{Gradle issue \#1319}.
+
+\label{DLJC-ARGS}
+\item[DLJC-ARGS]
+  Arguments that are passed directly to
+  \ahref{https://github.com/kelloggm/do-like-javac}{do-like-javac}'s
+  \<dljc> program without
+  modification.  One argument is required: \<-\relax-checker>, which indicates
+  what type-checker(s) to run (in the format described in Section~\ref{shorthand-for-checkers}).
+
+  The \ahreforurl{https://github.com/kelloggm/do-like-javac}{documentation of do-like-javac}
+  describes the other commands that its WPI tool supports. Notably, to pass checker-specific
+  arguments to invocations of \<javac>,
+  use the \<-\relax-extraJavacArgs> argument to \<dljc>. For example, to use the \<-AignoreRangeOverflow>
+  option for the Constant Value Checker (\chapterpageref{constant-value-checker}) when running
+  inference, you would add \<-\relax-extraJavacArgs='-AignoreRangeOverflow'> anywhere after the \<-\relax->
+  argument to \<wpi.sh>.
+\end{description}
+
+You may need to wait a few minutes for the command to complete.
+
+
+\subsectionAndLabel{Requirements for whole-program inference scripts}{wpi-shared-requirements}
+
+The requirements to run \<wpi.sh> and \<wpi-many.sh> are the same:
+
+\begin{itemize}
+\item The project on which inference is run must contain an Ant, Gradle,
+  or Maven buildfile that compiles the project.
+\item At least one of the \verb|JAVA_HOME|, \verb|JAVA8_HOME|, or \verb|JAVA_HOME| environment variables
+must be set.
+\item If set, the \verb|JAVA_HOME| environment variable must point to a Java 8 or Java 11 JDK.
+\item If set, the \verb|JAVA8_HOME| environment variable must point to a Java 8 JDK.
+\item If set, the \verb|JAVA11_HOME| environment variable must point to a Java 11 JDK.
+\item \<CHECKERFRAMEWORK> environment variable must point to a built copy of the Checker Framework.
+\item If set, the \verb|DLJC| environment variable must point to a copy of the \<dljc> script
+from \ahref{https://github.com/kelloggm/do-like-javac}{do-like-javac}. (If this variable is not
+set, the WPI scripts will download this dependency automatically.)
+\item Other dependencies:
+  ant,
+  awk,
+  curl,
+  git,
+  gradle,
+  mvn,
+  python2.7 (for dljc),
+  wget.
+
+  Python2.7 modules:
+  subprocess32.
+\end{itemize}
+
+
+
+\sectionAndLabel{Running whole-program inference on many projects}{wpi-many}
+
+The requirements to run \<wpi.sh> and \<wpi-many.sh> are the same. See Section~\ref{wpi-shared-requirements}
+for the list of requirements.
+
+To run an experiment on many projects:
+\begin{enumerate}
+\item Use \<query-github.sh> to search GitHub for candidate repositories.
+File \<docs/examples/wpi-many/securerandom.query> is an example query, and file
+\<docs/manual/securerandom.list> is the standard output
+created by running \<query-github.sh securerandom.query 100>. If you do
+not want to use GitHub, construct a file yourself that matches the format of
+the file \<securerandom.list>.
+
+\item Use \<wpi-many.sh> to run whole-program inference on every
+Ant, Gradle, or Maven project in a list of (GitHub repository URL, git hash)
+pairs.
+\begin{itemize}
+\item If you are using a checker that is distributed with the Checker
+Framework, use \<wpi-many.sh> directly.
+\item If you are using a checker that is not distributed with the Checker
+Framework (also known as a "custom checker"), file
+\<docs/examples/wpi-many/wpi-many-custom-checker-example.sh> is a no-arguments
+script that serves as an example of how to use \<wpi-many.sh>.
+\end{itemize}
+
+Log files are copied into a results directory.
+For a failed run, the log file indicates the reason that WPI could not
+be run to completion on the project.
+For a successful run, the log file indicates whether the project was verified
+(i.e., no errors were reported), or whether the checker issued warnings
+(which might be true positive or false positive warnings).
+
+\item Use \<wpi-summary.sh> to summarize the logs in the output results directory.
+Use its output to guide your analysis of the results of running \<wpi-many.sh>:
+you should manually examine the log files for the projects that appear in the
+"results available" list it produces. This list is the list of every project
+that the script was able to successfully run WPI on.  (This does not mean
+that the project type-checks without errors afterward.)
+
+\item (Optional) Fork repositories and make changes (e.g., add annotations or fix bugs).
+Modify the input file for \<wpi-many.sh> to remove the line for the original repository,
+but add a new line that indicates the location of both your
+fork and the original repository.
+Then, re-run your experiments, supplying the \<-u "\$yourGithubId"> option to \<wpi-many.sh>.
+\<wpi-many.sh> will perform inference on your forked version rather than
+the original.
+
+\end{enumerate}
+
+A typical invocation is
+
+\begin{Verbatim}
+wpi-many.sh -o outdir -i /path/to/repo.list -t 7200 -- --checker optional
+\end{Verbatim}
+
+The \<wpi-many.sh> script takes the following command-line arguments.
+The \<-o> and \<-i> arguments are mandatory.
+An invocation should also include \<-- [\emph{DLJC-ARGS}]> at the end;
+\emph{DLJC-ARGS} is documented in Section~\ref{DLJC-ARGS}.
+
+
+\begin{description}
+\item[-o outdir]
+  run the experiment in the \<\emph{outdir}> directory, and place the results in
+  the \<\emph{outdir}-results> directory. Both will be created if they do not
+  exist.  The directory may be specified as an absolute or relative path.
+
+\item[-i infile]
+  Read the list of repositories to use from the file infile.
+  % The need to be an absolute pathname is a bug in wpi-many.sh that should be fixed.
+  The file must be specified as an absolute, not relative, path.
+  Each line
+  should have 2 elements, separated by whitespace:
+  \begin{enumerate}
+  \item
+    The URL of the git repository on GitHub. The URL must be of the form
+    https://github.com/username/repository .  The script is reliant on the
+    number of slashes, so excluding ``https://'' is an error.
+  \item The commit hash to use.
+  \end{enumerate}
+
+\item[-t timeout]
+  The timeout for running the checker on each project, in seconds.
+
+\item[-g GRADLECACHEDIR]
+  The directory to use for the `-g` option to Gradle (the Gradle home
+  directory). This option is ignored if the target project does not
+  build with Gradle. The default is `.gradle` relative to the target
+  project (i.e., each target project has its own Gradle home). This default
+  is motivated by
+  \ahref{https://github.com/gradle/gradle/issues/1319}{Gradle issue \#1319}.
+
+\item[-s]
+  If this flag is present, then projects which are not buildable --- for which
+  no supported build file is present or for which running the standard build
+  commands fail --- are skipped on future runs but are \emph{not} deleted immediately
+  (such projects are deleted immediately if this flag is not present). This flag is useful
+  if you intend to run \<wpi-many.sh> several times on the same set of repositories
+  (for example, during checker development), to avoid re-downloading unusable projects.
+
+\end{description}
+
+
+\sectionAndLabel{Whole-program inference that inserts annotations into source code}{wpi-insert}
+
+\begin{sloppypar}
+To use this version of whole-program inference, make sure that
+\<insert-annotations-to-source>, from the Annotation File Utilities project,
+is on your path (for example, its directory is in the \<\$PATH> environment variable).
+Then, run the script \<checker-framework/checker/bin/infer-and-annotate.sh>.
+Its command-line arguments are:
+\end{sloppypar}
+
+\begin{enumerate}
+\item Optional: Command-line arguments to
+  \href{https://checkerframework.org/annotation-file-utilities/#insert-annotations-to-source}{\<insert-annotations-to-source>}.
+\item Processor's name.
+\item Target program's classpath.  This argument is required; pass "" if it
+  is empty.
+\item Optional: Extra processor arguments which will be passed to the checker, if any.
+  You may supply any number of such arguments, or none.  Each such argument
+  must start with a hyphen.
+\item Optional: Paths to \<.jaif> files used as input in the inference
+    process.
+\item Paths to \<.java> files in the program.
+\end{enumerate}
+
+% TODO: Change the example project that is being annotated, since plume-lib is now deprecated.
+For example, to add annotations to the \<plume-lib> project:
+\begin{Verbatim}
+git clone https://github.com/mernst/plume-lib.git
+cd plume-lib
+make jar
+$CHECKERFRAMEWORK/checker/bin/infer-and-annotate.sh \
+    "LockChecker,NullnessChecker" java/plume.jar:java/lib/junit-4.12.jar:$JAVA_HOME/lib/tools.jar \
+    `find java/src/plume/ -name "*.java"`
+# View the results
+git diff
+\end{Verbatim}
+
+You may need to wait a few minutes for the command to complete.
+You can ignore warnings that the command outputs while it tries different
+annotations in your code.
+
+It is recommended that you run \<infer-and-annotate.sh> on a copy of your
+code, so that you can see what changes it made and so that it does not
+change your only copy.  One way to do this is to work in a clone of your
+repository that has no uncommitted changes.
+
+
+\sectionAndLabel{Inference results depend on uses in your program or test suite}{whole-program-inference-non-representative-uses}
+
+Type inference outputs the most specific type qualifiers that are
+consistent with all the source code it is given.
+(Section~\ref{whole-program-inference-ignores-some-code}
+explains when type inference ignores some code.)
+This may be different than the specification the programmer had in mind
+when writing tho code.
+If the program uses a method or field in a limited way, then the inferred
+annotations will be legal for the program as
+currently written but may not be as general as possible and may not
+accommodate future program changes.
+
+Here are some examples:
+
+\begin{itemize}
+\item
+Suppose that your program (or test suite) currently calls
+method \<m1> only with non-null
+arguments.  The tool will infer that \<m1>'s parameter has
+\<@NonNull> type.  If you had intended the method to be able to
+take \<null> as an argument and you later add such a call, the type-checker
+will issue a warning because the inferred \<@NonNull>
+annotation is inconsistent with the new call.
+
+\item
+If your program (or test suite) passes only \<null> as an argument, the
+inferred type will be the bottom type, such as \<@GuardedByBottom>.
+
+\item
+Suppose that method \<m2> has no body, because it is defined in an interface or
+abstract class.
+Type inference can still infer types for its signature, based on the
+overriding implementations.
+If all the methods that override \<m2> return a non-null value, type
+inference will infer that \<m2>'s return type has \<@NonNull> type, even if
+some other overriding method is allowed to return
+\<null>.
+
+\end{itemize}
+
+If the program contains erroneous calls, the
+inferred annotations may reflect those errors.
+Suppose you intend method \<m3> to be called with
+non-null arguments, but your program contains an error and one of the calls
+to \<m3> passes \<null> as the argument.  Then the tool will infer that
+\<m3>'s parameter has \<@Nullable> type.
+
+If you run whole-program inference on a library that contains mutually
+recursive routines, and there are no non-recursive calls to the routines,
+then whole-program inference may run a long time and eventually produce
+incorrect results.  In this case, write type annotations on the formal
+parameters of one of the routines.
+
+Whole-program inference is a ``forward analysis''.
+% This might change in the future.
+It determines a method parameter's type
+annotation based on what arguments are passed to the method but not on how the
+parameter is used within the method body.
+It determines a method's return type based on code in the method body but
+not on uses of the method return value in client code.
+
+
+\subsectionAndLabel{Whole-program inference ignores some code}{whole-program-inference-ignores-some-code}
+
+Whole-program inference ignores code within the scope of a
+\<@SuppressWarnings> annotation with an appropriate key
+(Section~\ref{suppresswarnings-annotation}).  In particular, uses within
+the scope do not contribute to the inferred type, and declarations within
+the scope are not changed.  You should remove \<@SuppressWarnings> annotations
+from the class declaration of any class you wish to infer types for.
+
+As noted above, whole-program inference generalizes from invocations of methods and
+assignments to fields.  If a field is set via
+reflection (such as via injection), there are no explicit assignments to it
+for type inference to generalize from, and type inference will produce
+an inaccurate result.  There are two ways to make whole-program inference
+ignore such a field.
+%
+(1)
+You probably have an annotation such as
+\javaeejavadocanno{javax/inject/Inject.html}{Inject}
+or
+\href{https://types.cs.washington.edu/plume-lib/api/plume/Option.html}{\<@Option>}
+that indicates such fields.  Meta-annotate the declaration of the \<Inject>
+or \<Option> annotation with
+\refqualclass{framework/qual}{IgnoreInWholeProgramInference}.
+%
+(2)
+Annotate the field to be ignored with
+\refqualclass{framework/qual}{IgnoreInWholeProgramInference}.
+
+Whole-program inference, for a type-checker other than the Nullness Checker,
+ignores assignments and pseudo-assignments where the right-hand-side is the \<null> literal.
+
+
+\subsectionAndLabel{Manually checking whole-program inference results}{whole-program-inference-manual-checking}
+
+With any type inference tool, it is a good idea to manually examine the
+results.  This can help you find bugs in your code or places where type
+inference inferred an overly-precise result.
+You can correct the inferred results manually, or you can
+add tests that pass additional values and then re-run inference.
+
+When arguments or assignments are literals, whole-program inference
+commonly infers overly precise type annotations, such as \<@Interned> and
+\<@Regex> annotations when the analyzed code only uses a constant string.
+
+When an annotation is inferred for a \emph{use} of a type variable,
+you may wish to move the annotation
+to the corresponding upper bounds of the type variable \emph{declaration}.
+
+
+\sectionAndLabel{How whole-program inference works}{how-whole-program-inference-works}
+
+This section explains how the \<wpi.sh> and \<infer-and-annotate.sh> scripts work.  If you
+merely want to run the scripts and you are not encountering trouble, you can
+skip this section.
+
+Each script repeatedly runs the checker with an \<-Ainfer=> command-line option to infer
+types for fields and method signatures.  The output of this step
+is a \<.jaif> (for \<infer-and-annotate.sh>) or stub (for \<wpi.sh>) file that records the inferred types.
+Each script adds the inferred annotation to the next run, so that the checker takes them into
+account (and checks them). \<wpi.sh> does this using the \<-AmergeStubsWithSource> command-line
+option to the Checker Framework; \<infer-and-annotate.sh> inserts the inferred annotations in the program using the
+\ahreforurl{https://checkerframework.org/annotation-file-utilities/}{Annotation File Utilities}.
+
+On each
+iteration through the process, there may be new annotations in the \<.jaif> or \<.astub>
+files, and some type-checking errors may be eliminated (though others might
+be introduced).
+The process halts when there are no more changes to the inference results,
+that is, the \<.jaif> or \<.astub> files are unchanged between two runs.
+
+When the type-checker is run on the program with the final annotations
+inserted, there might still be errors.  This may be because the tool did
+not infer enough annotations, or because your program cannot typecheck
+(either because contains a defect, or because it contains subtle code that
+is beyond the capabilities of the type system).
+However, each of the inferred annotations is sound, and this reduces your
+manual effort in annotating the program.
+
+The iterative process is required because type-checking is modular:  it
+processes each class and each method only once, independently.  Modularity
+enables you to run type-checking on only part of your program, and it makes
+type-checking fast.  However, it has some disadvantages:
+\begin{itemize}
+\item
+  The first run of the type-checker cannot take advantage of whole-program
+  inference results because whole-program inference is only complete at the
+  end of type-checking, and modular type-checking does not revisit any
+  already-processed classes.
+\item
+  Revisiting an
+  already-processed class may result in a better estimate.
+\end{itemize}
+
+
+\sectionAndLabel{Type inference compared to whole-program analyses}{type-inference-vs-whole-program-analysis}
+
+There exist monolithic whole-program analyses that run without requiring any
+annotations in the source code.  An advantage of such a tool is that the
+programmer never needs to write any type annotations.
+
+Running a whole-program inference tool, then running a type-checker, has
+some benefits:
+\begin{itemize}
+\item
+  The type qualifiers act as machine-checked documentation,
+  which can aid programmer understanding.
+\item
+  Error messages may be more comprehensible.  With a monolithic
+  whole-program analysis, error messages can be obscure, because the
+  analysis has already inferred (possibly incorrect) types for a number of
+  variables.
+\item
+  Errors are localized.  A change to one part of the program does not lead
+  to an error message in a far-removed part of the program.
+\item
+  Type-checking is modular, which can be faster than re-doing a
+  whole-program analysis every time the program changes.
+\end{itemize}
+
+
+
+%%  LocalWords:  Ainfer java jaif plugin classpath m2 m1 multi javax CFI
+%%  LocalWords:  AsuggestPureMethods CHECKERFRAMEWORK GuardedByBottom dljc
+%%  LocalWords:  IgnoreInWholeProgramInference typechecking Inference'' m3
+% LocalWords:  PROJECTDIR awk gradle mvn python2 wget subprocess32 github
+% LocalWords:  securerandom astub typecheck
diff --git a/docs/manual/initialized-fields-checker.tex b/docs/manual/initialized-fields-checker.tex
new file mode 100644
index 0000000..adbb6bd
--- /dev/null
+++ b/docs/manual/initialized-fields-checker.tex
@@ -0,0 +1,190 @@
+\htmlhr
+\chapterAndLabel{Initialized Fields Checker}{initialized-fields-checker}
+
+The Initialized Fields Checker warns if a constructor does not initialize a
+field.
+
+
+\sectionAndLabel{Running the Initialized Fields Checker}{initialized-fields-checker-running}
+
+An example invocation is
+
+\begin{Verbatim}
+javac -processor org.checkerframework.common.initializedfields.InitializedFieldsChecker MyFile.java
+\end{Verbatim}
+
+If you run it together with other checkers, then it issues warnings only if
+the default value assigned by Java (0, false, or null) is not consistent
+with the field's annotation, for the other checkers.
+% It's actually the checkers and their subcheckers if any.  Saying that
+% would be confusing to most users, who don't know what a "subchecker" is.
+% The term "subchecker" appears only in the "creating a checker" section of
+% the manual.
+An example invocation is
+
+\begin{Verbatim}
+javac -processor ValueChecker,InitializedFieldsChecker MyFile.java
+\end{Verbatim}
+
+
+\sectionAndLabel{Motivation:  uninitialized fields}{initialized-fields-motivation}
+
+Without the Initialized Fields Checker, every type system is
+unsound with respect to fields that are never set.  (Exception:  The
+Nullness Checker (\chapterpageref{nullness-checker}) is sound. Also, a type
+system is sound if every annotation is consistent with 0, false, and null.)
+Consider the following code:
+
+\begin{Verbatim}
+import org.checkerframework.checker.index.qual.Positive;
+
+class MyClass {
+  @Positive int x;
+  MyClass() {
+    // empty body
+  }
+
+  @Positive int getX() {
+    return x;
+  }
+}
+\end{Verbatim}
+
+\noindent
+Method \<getX> is incorrect because it returns 0, which is not positive.
+However, the code type-checks because there is never an assignment to \<x>
+whose right-hand side is not positive.
+If you run the Index Checker together with the Initialized Fields Checker,
+then the code correctly does not type-check.
+
+
+\subsubsectionAndLabel{Remaining unsoundness}{initialized-fields-remaining-unsoundness}
+
+Even with the Initialized Fields Checker, every type system (except the
+Nullness Checker, \chapterpageref{nullness-checker}) is unsound with
+respect to partially-initialized fields.  Consider the following code:
+
+\begin{Verbatim}
+import org.checkerframework.checker.index.qual.Positive;
+
+class MyClass {
+  @Positive int x;
+  MyClass() {
+    foo(this);
+    x = 1;
+  }
+
+  @Positive int foo() {
+    // ... use x, expecting it to be positive ...
+  }
+}
+\end{Verbatim}
+
+\noindent
+Within method \<foo>, \<x> can have the value 0 even though the type of
+\<x> is \<@Positive int>.
+
+
+\sectionAndLabel{Example}{initialized-fields-example}
+
+As an example, consider the following code:
+
+\begin{Verbatim}
+import org.checkerframework.checker.index.qual.Positive;
+
+class MyClass {
+
+  @Positive int x;
+  @Positive int y;
+  int z;
+
+  // Warning: field y is not initialized
+  MyClass() {
+    x = 1;
+  }
+}
+\end{Verbatim}
+
+When run by itself, the Initialized Fields Checker warns that fields \<y>
+and field \<z> are not set.
+
+When run together with the Index Checker, the Initialized Fields Checker
+warns that field \<y> is not set.  It does not warn about field \<z>,
+because its default value (0) is consistent with its annotations.
+
+
+\sectionAndLabel{Annotations}{initialized-fields-annotations}
+
+The Initialized Fields type system uses the following type annotations:
+\begin{description}
+\item[\refqualclass{common/initializedfields/qual}{InitializedFields}]
+  indicates which fields have definitely been initialized so far.
+\item[\refqualclass{common/initializedfields/qual}{InitializedFieldsBottom}]
+  is the type of \<null>.  Programmers rarely write this type.
+\item[\refqualclass{common/initializedfields/qual}{PolyInitializedFields}]
+  is a qualifier that is polymorphic over field initialization.
+  For a description of qualifier polymorphism, see
+  Section~\ref{method-qualifier-polymorphism}.
+\end{description}
+
+\begin{figure}
+\includeimage{initializedfields}{4.5cm}
+\caption{The type qualifier hierarchy of the Initialized Fields Checker.
+\<@InitializedFieldsBottom> is rarely written by a programmer.}
+\label{fig-initialized-fields-hierarchy}
+\end{figure}
+
+Figure~\ref{fig-initialized-fields-hierarchy} shows the the subtyping
+relationships among the type qualifiers.
+
+There is also a method declaration annotation:
+
+\begin{description}
+\item[\refqualclass{common/initializedfields/qual}{EnsuresInitializedFields}]
+  indicates which fields the method sets.  Use this for helper methods that
+  are called from a constructor.
+\end{description}
+
+
+\sectionAndLabel{Comparison to the Initialization Checker}{initialized-fields-vs-initialization}
+
+The Initialized Fields Checker is a lightweight version of the  Initialization Checker
+(Section~\ref{initialization-checker}).  Here is a comparison between them.
+
+\noindent
+\begin{small}
+\begin{tabular}{| l | l | l |}
+ \hline
+ & Initialization Checker & Initialized Fields Checker
+ \\ \hline
+ superclasses
+ & tracks initialization of supertype fields
+ & checks one class at a time
+ \\
+ partial initialization
+ & changes the types of fields that are not initialized
+ & unsound treatment of partially-initialized objects (*)
+ \\
+ type systems
+ & works only with the Nullness Checker (**)
+ & works for any type system
+ \\
+ disabling
+ & always runs with the Nullness Checker
+ & can be enabled/disabled per run
+ \\
+ \hline
+\end{tabular}
+
+\noindent
+* See Section~\ref{initialized-fields-remaining-unsoundness} for an example.
+\newline
+** The Initialization Checker could be made to work with any type system, but
+doing so would require changing the implementation of both the type system and
+the Initialization Checker.
+\end{small}
+
+
+% LocalWords:  InitializedFields getX
+% LocalWords:  InitializedFieldsBottom PolyInitializedFields
+% LocalWords:  EnsuresInitializedFields
diff --git a/docs/manual/interning-checker.tex b/docs/manual/interning-checker.tex
new file mode 100644
index 0000000..878fb07
--- /dev/null
+++ b/docs/manual/interning-checker.tex
@@ -0,0 +1,483 @@
+\htmlhr
+\chapterAndLabel{Interning Checker}{interning-checker}
+
+If the Interning Checker issues no errors for a given program, then all
+reference equality tests (i.e., all uses of ``\code{==}'') are proper;
+that is,
+\code{==} is not misused where \code{equals()} should have been used instead.
+
+Interning is a design pattern in which the same object is used whenever two
+different objects would be considered equal.  Interning is also known as
+canonicalization or hash-consing, and it is related to the flyweight design
+pattern.
+Interning has two benefits:  it can save memory, and it can speed up testing for
+equality by permitting use of \code{==}.
+
+The Interning Checker prevents two types of problems in your code.
+First, it prevents using \code{==} on
+non-interned values, which can result in subtle bugs.  For example:
+
+\begin{Verbatim}
+  Integer x = new Integer(22);
+  Integer y = new Integer(22);
+  System.out.println(x == y);  // prints false!
+\end{Verbatim}
+
+\noindent
+Second,
+the Interning Checker helps to prevent performance problems that result
+from failure to use interning.
+(See Section~\ref{checker-guarantees} for caveats to the checker's guarantees.)
+
+Interning is such an important design pattern that Java builds it in for
+these types: \<String>, \<Boolean>, \<Byte>, \<Character>, \<Integer>,
+\<Short>.  Every string literal in the program is guaranteed to be interned
+(\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-3.html#jls-3.10.5}{JLS
+  \S3.10.5}), and the
+\sunjavadoc{java.base/java/lang/String.html\#intern()}{String.intern()} method
+performs interning for strings that are computed at run time.
+The \<valueOf> methods in wrapper classes always (\<Boolean>, \<Byte>) or
+sometimes (\<Character>, \<Integer>, \<Short>) return an interned result
+(\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-5.html#jls-5.1.7}{JLS \S5.1.7}).
+Users can also write their own interning methods for other types.
+
+It is a proper optimization to use \code{==}, rather than \code{equals()},
+whenever the comparison is guaranteed to produce the same result --- that
+is, whenever the comparison is never provided with two different objects
+for which \code{equals()} would return true.  Here are three reasons that
+this property could hold:
+
+\begin{enumerate}
+\item
+  Interning.  A factory method ensures that, globally, no two different
+  interned objects are \code{equals()} to one another.
+  (For some classes, every instance is interned; however, in other cases it is
+  possible for two objects of the class to be
+  \code{equals()} to one another, even if one of them is interned.)
+  Interned objects should always be immutable.
+\item
+  Global control flow.  The program's control flow is such that the
+  constructor for class $C$ is called a limited number of times, and with
+  specific values that ensure the results are not \code{equals()} to one
+  another.  Objects of class $C$ can always be compared with \code{==}.
+  Such objects may be mutable or immutable.
+\item
+  Local control flow.  Even though not all objects of the given type may be
+  compared with \code{==}, the specific objects that can reach a given
+  comparison may be.
+  \begin{itemize}
+  \item
+    When searching for an element (say, in a collection), \code{==} may be
+    appropriate.
+  \item
+    Some routines return either their argument, or a modified version of
+    it.  Your code might compare \code{s == s.toLowerCase()} to see whether
+    a string contained any upper-case characters.
+  \end{itemize}
+\end{enumerate}
+
+To eliminate Interning Checker errors, you will need to annotate the
+declarations of any expression used as an argument to \code{==}.
+Thus, the Interning Checker
+could also have been called the Reference Equality Checker.
+% In the
+% future, the checker will include annotations that target the non-interning
+% cases above, but for now you need to use \<@Interned>, \<@UsesObjectEquals>
+% (which handles a surprising number of cases), and/or
+% \<@SuppressWarnings>.
+
+\begin{sloppypar}
+To run the Interning Checker, supply the
+\code{-processor org.checkerframework.checker.interning.InterningChecker}
+command-line option to javac.  For examples, see Section~\ref{interning-example}.
+\end{sloppypar}
+
+
+\sectionAndLabel{Interning annotations}{interning-annotations}
+
+\subsectionAndLabel{Interning qualifiers}{interning-qualifiers}
+
+These qualifiers are part of the Interning type system:
+
+\begin{description}
+
+\item[\refqualclass{checker/interning/qual}{Interned}]
+  indicates a type that includes only interned values (no non-interned
+  values).
+
+\item[\refqualclass{checker/interning/qual}{InternedDistinct}]
+  indicates a type such that each value is not \<equals()> to any other
+  Java value.  This is a stronger (more restrictive) property than
+  \<@Interned>, but is a weaker property than writing \<@Interned> on a
+  class declaration.  For
+  details, see Section~\ref{interning-distinct}.
+
+\item[\refqualclass{checker/interning/qual}{UnknownInterned}]
+  indicates a type whose values might or might not be interned.
+  It is used internally by the type system and is not written by programmers.
+
+\item[\refqualclass{checker/interning/qual}{PolyInterned}]
+  indicates qualifier polymorphism.
+  For a description of qualifier polymorphism, see
+  Section~\ref{method-qualifier-polymorphism}.
+
+\end{description}
+
+\subsectionAndLabel{Interning method and class annotations}{interning-declaration-annotations}
+
+\begin{description}
+
+\item[\refqualclass{checker/interning/qual}{UsesObjectEquals}]
+  is a class annotation (not a type annotation) that indicates that this class's
+  \<equals> method is the same as that of \<Object>.  Since
+  \<Object.equals> uses reference equality, this means that for such a
+  class, \<==> and \<equals> are equivalent, and so the Interning Checker
+  does not issue errors or warnings for either one.
+
+  Two ways to satisfy this annotation are:  (1) neither this class nor any
+  of its superclasses overrides the \<equals> method, or (2) this class
+  defines \<equals> with body \<return this == o;>.
+
+\item[\refqualclass{checker/interning/qual}{InternMethod}]
+  is a method declaration annotation that indicates that this method
+  returns an interned object and may be invoked
+  on an uninterned object. See Section~\ref{interning-intern-methods} for more details.
+
+\item[\refqualclass{checker/interning/qual}{EqualsMethod}]
+  is a method declaration annotation that indicates that this method
+  has a specification like \<equals()>.  The Interning Checker permits use
+  of \<this == arg> within the body.
+
+\item[\refqualclass{checker/interning/qual}{CompareToMethod}]
+  is a method declaration annotation that indicates that this method
+  has a specification like \<compareTo()>.  The Interning Checker permits use
+  of \<if (arg1 == arg2) \ttlcb\ return 0; \ttrcb> within the body.
+
+\item[\refqualclass{checker/interning/qual}{FindDistinct}]
+  is a formal parameter declaration annotation that indicates that this
+  method uses \<==> to perform comparisons against the annotated formal
+  parameter.  A common reason is that the method searches for the formal
+  parameter in some data structure, using \<==>.  Any value
+  may be passed to the method.
+\end{description}
+
+\sectionAndLabel{Annotating your code with \code{@Interned}}{interning-annotating}
+
+\begin{figure}
+\includeimage{interning}{2.5cm}
+\caption{Type hierarchy for the Interning type system.}
+\label{fig-interning-hierarchy}
+\end{figure}
+
+In order to perform checking, you must annotate your code with the \refqualclass{checker/interning/qual}{Interned}
+type annotation.  A type annotated with \<@Interned> contains the canonical
+representation of an
+object:
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+            String s1 = ...;  // type is (uninterned) "String"
+  @Interned String s2 = ...;  // type is "@Interned String"
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+The Interning Checker ensures that only interned
+values can be assigned to \code{s2}.
+
+\sectionAndLabel{Interned classes}{interning-interned-classes}
+
+An interned annotation on a class declaration indicates that all objects of a
+type are interned \textit{except for newly created objects}. That means that
+all uses of such types are \<@Interned> by default and the type \<@UnknownInterned
+MyClass> is an invalid type.
+
+An exception is \textit{constructor results}. Constructor results and \<this> within the
+body of the constructor are \<@UnknownInterned> by default. Although \<@UnknownInterned InternClass>
+is not a legal type, no ``type.invalid'' error is issued at constructor declarations.
+Instead, an ``interned.object.creation''
+error is issued at the invocation of the constructor. The user should inspect
+this location and suppress the warning if the newly created object is interned.
+
+For example:
+
+\begin{Verbatim}
+@Interned class InternedClass {
+  @UnknownInterned InternedClass() {
+    // error, "this" is @UnknownInterned.
+    @Interned InternedClass that = this;
+  }
+
+  @SuppressWarnings("intern") // Only creation of an InternedClass object.
+  static final InternedClass ONE = new InternedClass();
+}
+\end{Verbatim}
+
+\subsectionAndLabel{The intern() methods}{interning-intern-methods}
+Some interned classes use an \<intern()> method to look up the interned version of
+the object. These methods must be annotated with the declaration annotation
+\<@InternMethod>. This allows the checker to verify that a newly created object
+is immediately interned and therefore not issue an interned object creation
+error.
+
+\begin{Verbatim}
+new InternedClass().intern() // no error
+\end{Verbatim}
+
+Because an \<intern> method is expected to be called on uninterned objects, the
+type of \<this> in \<intern> is implicitly \<@UnknownInterned>. This will cause an
+error if \<this> is used someplace where an interned object is expected.  Some
+of these warnings will be false positives that should be suppressed by the
+user.
+
+\begin{Verbatim}
+@InternMethod
+public InternedClass intern() {
+  // Type of "this" inside an @InternMethod is @UnknownInterned
+  @Interned InternedClass that = this; // error
+
+  if (!pool.contains(this)) {
+    @SuppressWarnings("interning:assignment")
+    @Interned InternedClass internedThis = this;
+    pool.add(internedThis);
+  }
+  return pool.get(this);
+}
+\end{Verbatim}
+
+Some interned classes do not use an intern method to ensure that every object
+of that class is interned.  For these classes, the user will have to manually
+inspect every constructor invocation and suppress the ``interned.object.creation''
+error.
+
+If every invocation of a constructor is guaranteed to be interned, then the
+user should annotate the constructor result with \<@Interned> and suppress a
+warning at the constructor.
+
+\begin{Verbatim}
+@Interned class AnotherInternedClass {
+  // manually verified that all constructor invocations used such that all
+  // new objects are interned
+  @SuppressWarnings("super.invocation")
+  @Interned AnotherInternedClass() {}
+}
+\end{Verbatim}
+
+
+\subsectionAndLabel{Default qualifiers and qualifiers for literals}{interning-implicit-qualifiers}
+
+The Interning Checker
+adds qualifiers to unannotated types, reducing the number of annotations that must
+appear in your code (see Section~\ref{effective-qualifier}).
+
+For a complete description of all defaulting rules for interning qualifiers, see the
+Javadoc for \refclass{checker/interning}{InterningAnnotatedTypeFactory}.
+
+\subsectionAndLabel{InternedDistinct: values not equals() to any other value}{interning-distinct}
+
+The \refqualclass{checker/interning/qual}{InternedDistinct} annotation
+represents values that are not \<equals()> to any other value.  Suppose
+expression \<e> has type \<@InternedDistinct>.  Then \<e.equals(x) == (e ==
+x)>.  Therefore, it is legal to use \<==> whenever at least one of the
+operands has type \<@InternedDistinct>.
+
+\<@InternedDistinct> is stronger (more restrictive) than \<@Interned>.
+For example, consider these variables:
+
+\begin{Verbatim}
+@Interned String i = "22";
+          String s = new Integer(22).toString();
+\end{Verbatim}
+
+\noindent
+The variable \<i> is not \<@InternedDistinct> because \<i.equals(s)> is true.
+
+\<@InternedDistinct> is not as restrictive as stating that all objects of a
+given Java type are interned.
+
+The \<@InternedDistinct> annotation is rarely used, because it arises from
+coding paradigms that are tricky to reason about.
+%
+One use is on static fields
+that hold canonical values of a type.
+Given this declaration:
+
+\begin{Verbatim}
+class MyType {
+  final static @InternedDistinct MyType SPECIAL = new MyType(...);
+  ...
+}
+\end{Verbatim}
+
+\noindent
+it would be legal to write \<myValue == MyType.SPECIAL> rather than
+\<myValue.equals(MyType.SPECIAL)>.
+
+The \<@InternedDistinct> is trusted (not verified), because it would be too
+complex to analyze the \<equals()> method to ensure that no other value is
+\<equals()> to a \<@InternedDistinct> value.  You will need to manually
+verify that it is only written in locations where its contract is satisfied.
+For example, here is one set of guidelines that you could check manually:
+\begin{itemize}
+\item The constructor is private.
+\item The factory method (whose return type is annotated with
+  \<@InternedDistinct> returns the canonical version for certain values.
+\item The class is final, so that subclasses cannot violate these properties.
+\end{itemize}
+
+
+\sectionAndLabel{What the Interning Checker checks}{interning-checks}
+
+Objects of an \refqualclass{checker/interning/qual}{Interned} type may be safely compared using the ``\code{==}''
+operator.
+
+The checker issues an error in two cases:
+
+\begin{enumerate}
+
+\item
+  When a reference (in)equality operator (``\code{==}'' or ``\code{!=}'')
+  has an operand of non-\refqualclass{checker/interning/qual}{Interned} type.
+  As a special case, the operation is permitted if either argument is of
+  \refqualclass{checker/interning/qual}{InternedDistinct} type
+
+\item
+  When a non-\refqualclass{checker/interning/qual}{Interned} type is used
+  where an \refqualclass{checker/interning/qual}{Interned} type
+  is expected.
+
+\end{enumerate}
+
+This example shows both sorts of problems:
+
+\begin{Verbatim}
+                    Date  date;
+          @Interned Date idate;
+  @InternedDistinct Date ddate;
+  ...
+  if (date == idate) ...  // error: reference equality test is unsafe
+  idate = date;           // error: idate's referent might no longer be interned
+  ddate = idate;          // error: idate's referent might be equals() to some other value
+\end{Verbatim}
+
+\label{lint-dotequals}
+
+The checker also issues a warning when \code{.equals} is used where
+\code{==} could be safely used.  You can disable this behavior via the
+javac \code{-Alint=-dotequals} command-line option.
+
+For a complete description of all checks performed by
+  the checker, see the Javadoc for
+  \refclass{checker/interning}{InterningVisitor}.
+
+\label{checking-class}
+To restrict which types the checker should type-check, pass a canonical
+name (fully-qualified name) using the \code{-Acheckclass} option.
+For example, to find only the
+interning errors related to uses of \code{String}, you can pass
+\code{-Acheckclass=java.lang.String}.  The Interning Checker always checks all
+subclasses and superclasses of the given class.
+
+
+\subsectionAndLabel{Imprecision (false positive warnings) of the Interning Checker}{interning-limitations}
+
+% There is no point to linking to the Javadoc for the valueOf methods,
+% which don't discuss interning.
+
+The Interning Checker conservatively assumes that the \<Character>, \<Integer>,
+and \<Short> \<valueOf> methods return a non-interned value.  In fact, these
+methods sometimes return an interned value and sometimes a non-interned
+value, depending on the run-time argument (\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-5.html#jls-5.1.7}{JLS
+\S5.1.7}).  If you know that the run-time argument to \<valueOf> implies that
+the result is interned, then you will need to suppress an error.
+(The Interning Checker should make use of the Value Checker to estimate the upper
+and lower bounds on char, int, and short values so that it can more
+precisely determine whether the result of a given \<valueOf> call is
+interned.)
+
+
+
+\sectionAndLabel{Examples}{interning-example}
+
+To try the Interning Checker on a source file that uses the \refqualclass{checker/interning/qual}{Interned} qualifier,
+use the following command:
+
+\begin{mysmall}
+\begin{Verbatim}
+  javac -processor org.checkerframework.checker.interning.InterningChecker docs/examples/InterningExample.java
+\end{Verbatim}
+\end{mysmall}
+
+\noindent
+Compilation will complete without errors or warnings.
+
+To see the checker warn about incorrect usage of annotations, use the following
+command:
+
+\begin{mysmall}
+\begin{Verbatim}
+  javac -processor org.checkerframework.checker.interning.InterningChecker docs/examples/InterningExampleWithWarnings.java
+\end{Verbatim}
+\end{mysmall}
+
+\noindent
+The compiler will issue an error regarding violation of the semantics of
+\refqualclass{checker/interning/qual}{Interned}.
+% in the \code{InterningExampleWithWarnings.java} file.
+
+
+The Daikon invariant detector
+(\myurl{http://plse.cs.washington.edu/daikon/}) is also annotated with
+\refqualclass{checker/interning/qual}{Interned}.  From directory \code{java/},
+run \code{make check-interning}.
+
+
+
+\sectionAndLabel{Other interning annotations}{other-interning-annotations}
+
+The Checker Framework's interning annotations are similar to annotations used
+elsewhere.
+
+If your code is already annotated with a different interning
+annotation, the Checker Framework can type-check your code.
+It treats annotations from other tools
+as if you had written the corresponding annotation from the
+Interning Checker, as described in Figure~\ref{fig-interning-refactoring}.
+If the other annotation is a declaration annotation, it may be moved; see
+Section~\ref{declaration-annotations-moved}.
+
+
+% These lists should be kept in sync with InterningAnnotatedTypeFactory.java .
+\begin{figure}
+\begin{center}
+% The ~ around the text makes things look better in Hevea (and not terrible
+% in LaTeX).
+\begin{tabular}{ll}
+\begin{tabular}{|l|}
+\hline
+ ~com.sun.istack.internal.Interned~ \\ \hline
+\end{tabular}
+&
+$\Rightarrow$
+~org.checkerframework.checker.interning.qual.Interned~
+\end{tabular}
+\end{center}
+%BEGIN LATEX
+\vspace{-1.5\baselineskip}
+%END LATEX
+\caption{Correspondence between other interning annotations and the
+  Checker Framework's annotations.}
+\label{fig-interning-refactoring}
+\end{figure}
+
+
+
+% LocalWords:  plugin MyInternedClass enum InterningExampleWithWarnings java
+% LocalWords:  PolyInterned Alint dotequals quals InterningAnnotatedTypeFactory
+% LocalWords:  javac InterningVisitor JLS Acheckclass UsesObjectEquals
+%  LocalWords:  consing valueOf superclasses s2 cleanroom canonicalization
+%%  LocalWords:  InternedDistinct UnknownInterned myValue MyType MyClass
+% LocalWords:  toLowerCase InternMethod uninterned InternClass
diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex
new file mode 100644
index 0000000..644a388
--- /dev/null
+++ b/docs/manual/introduction.tex
@@ -0,0 +1,1654 @@
+\htmlhr
+\chapterAndLabel{Introduction}{introduction}
+
+The Checker Framework enhances Java's type system to make it more powerful
+and useful.
+This lets software developers detect and
+prevent errors in their Java programs.
+
+A ``checker'' is a tool that warns you about certain errors or gives you a
+guarantee that those errors do not occur.
+The Checker Framework comes with checkers for specific types of errors:
+
+\begin{enumerate}
+% If you update this list, also update the list in advanced-features.tex .
+
+\item
+  \ahrefloc{nullness-checker}{Nullness Checker} for null pointer errors
+  (see \chapterpageref{nullness-checker})
+\item
+  \ahrefloc{initialization-checker}{Initialization Checker} to ensure all
+  \<@NonNull> fields are set in the constructor (see
+  \chapterpageref{initialization-checker})
+\item
+  \ahrefloc{map-key-checker}{Map Key Checker} to track which values are
+  keys in a map (see \chapterpageref{map-key-checker})
+\item
+  \ahrefloc{optional-checker}{Optional Checker} for errors in using the
+  \sunjavadoc{java.base/java/util/Optional.html}{Optional} type (see
+  \chapterpageref{optional-checker})
+\item
+  \ahrefloc{interning-checker}{Interning Checker} for errors in equality
+  testing and interning (see \chapterpageref{interning-checker})
+\item
+  \ahrefloc{lock-checker}{Lock Checker} for concurrency and lock errors
+  (see \chapterpageref{lock-checker})
+\item
+  \ahrefloc{index-checker}{Index Checker} for array accesses
+  (see \chapterpageref{index-checker})
+\item
+  \ahrefloc{fenum-checker}{Fake Enum Checker} to allow type-safe fake enum
+  patterns and type aliases or typedefs (see \chapterpageref{fenum-checker})
+\item
+  \ahrefloc{tainting-checker}{Tainting Checker} for trust and security errors
+  (see \chapterpageref{tainting-checker})
+\item
+  \ahrefloc{regex-checker}{Regex Checker} to prevent use of syntactically
+  invalid regular expressions (see \chapterpageref{regex-checker})
+\item
+  \ahrefloc{formatter-checker}{Format String Checker} to ensure that format
+  strings have the right number and type of \<\%> directives (see
+  \chapterpageref{formatter-checker})
+\item
+  \ahrefloc{i18n-formatter-checker}{Internationalization Format String Checker}
+  to ensure that i18n format strings have the right number and type of
+  \<\{\}> directives (see \chapterpageref{i18n-formatter-checker})
+\item
+  \ahrefloc{propkey-checker}{Property File Checker} to ensure that valid
+  keys are used for property files and resource bundles (see
+  \chapterpageref{propkey-checker})
+\item
+  \ahrefloc{i18n-checker}{Internationalization Checker} to
+  ensure that code is properly internationalized (see
+  \chapterpageref{i18n-checker})
+% The Compiler Message Key Checker is neither here nor in the advanced
+% type system features chapter because it is really meant for
+% Checker Framework developers and as sample code, and is not meant
+% for Checker Framework users at large.
+\item
+  \ahrefloc{signature-checker}{Signature String Checker} to ensure that the
+  string representation of a type is properly used, for example in
+  \<Class.forName> (see \chapterpageref{signature-checker})
+\item
+  \ahrefloc{guieffect-checker}{GUI Effect Checker} to ensure that non-GUI
+  threads do not access the UI, which would crash the application
+  (see \chapterpageref{guieffect-checker})
+\item
+  \ahrefloc{units-checker}{Units Checker} to ensure operations are
+  performed on correct units of measurement
+  (see \chapterpageref{units-checker})
+\item
+  \ahrefloc{signedness-checker}{Signedness Checker} to
+  ensure unsigned and signed values are not mixed
+  (see \chapterpageref{signedness-checker})
+\item
+  \ahrefloc{purity-checker}{Purity Checker} to identify whether
+  methods have side effects (see \chapterpageref{purity-checker})
+\item
+  \ahrefloc{constant-value-checker}{Constant Value Checker} to determine
+  whether an expression's value can be known at compile time
+  (see \chapterpageref{constant-value-checker})
+\item
+  \ahrefloc{reflection-resolution}{Reflection Checker} to determine
+  whether an expression's value (of type \<Method> or \<Class>) can be known at compile time
+  (see \chapterpageref{reflection-resolution})
+\item
+  \ahrefloc{initialized-fields-checker}{Initialized Fields Checker} to ensure all
+  fields are set in the constructor (see
+  \chapterpageref{initialization-checker})
+\item
+  \ahrefloc{aliasing-checker}{Aliasing Checker} to identify whether
+  expressions have aliases (see \chapterpageref{aliasing-checker})
+\item
+  \ahrefloc{must-call-checker}{Must Call Checker} to over-approximate the
+  methods that should be called on an object before it is de-allocated (see \chapterpageref{must-call-checker})
+\item
+  \ahrefloc{subtyping-checker}{Subtyping Checker} for customized checking without
+  writing any code (see \chapterpageref{subtyping-checker})
+% \item
+%   \ahrefloc{typestate-checker}{Typestate checker} to ensure operations are
+%   performed on objects that are in the right state, such as only opened
+%   files being read (see \chapterpageref{typestate-checker})
+\item
+  \ahrefloc{third-party-checkers}{Third-party checkers} that are distributed
+  separately from the Checker Framework
+  (see \chapterpageref{third-party-checkers})
+
+\end{enumerate}
+
+\noindent
+These checkers are easy to use and are invoked as arguments to \<javac>.
+
+
+The Checker Framework also enables you to write new checkers of your
+own; see Chapters~\ref{subtyping-checker} and~\ref{creating-a-checker}.
+
+
+\sectionAndLabel{How to read this manual}{how-to-read-this-manual}
+
+If you wish to get started using some particular type system from the list
+above, then the most effective way to read this manual is:
+
+\begin{itemize}
+\item
+  Read all of the introductory material
+  (Chapters~\ref{introduction}--\ref{using-a-checker}).
+\item
+  Read just one of the descriptions of a particular type system and its
+  checker (Chapters~\ref{nullness-checker}--\ref{third-party-checkers}).
+\item
+  Skim the advanced material that will enable you to make more effective
+  use of a type system
+  (Chapters~\ref{polymorphism}--\ref{troubleshooting}), so that you will
+  know what is available and can find it later.  Skip
+  Chapter~\ref{creating-a-checker} on creating a new checker.
+\end{itemize}
+
+
+\sectionAndLabel{How it works:  Pluggable types}{pluggable-types}
+
+Java's built-in type-checker finds and prevents many errors --- but it
+doesn't find and prevent \emph{enough} errors.  The Checker Framework lets you
+define new type systems and run them as a plug-in to the javac compiler.  Your
+code stays completely backward-compatible:  your code compiles with any
+Java compiler, it runs on any JVM, and your coworkers don't have to use the
+enhanced type system if they don't want to.  You can check part of
+your program, or the whole thing.  Type inference tools exist to help you annotate your
+code; see Section~\ref{type-inference}.
+
+Most programmers will use type systems created by other people, such as
+those listed at the start of the introduction (\chapterpageref{introduction}).
+Some people, called ``type system designers'', create new type systems
+(\chapterpageref{creating-a-checker}).
+The Checker Framework is useful both to programmers who
+wish to write error-free code, and to type system designers who wish to
+evaluate and deploy their type systems.
+
+This document uses the terms ``checker'' and ``type-checking compiler
+plugin'' as synonyms.
+
+\sectionAndLabel{Installation}{installation}
+
+This section describes how to install the Checker Framework.
+\begin{itemize}
+\item
+If you use a build system that automatically downloads dependencies,
+such as Gradle or Maven, \textbf{no installation is necessary}; just see
+\chapterpageref{external-tools}.
+\item
+If you wish to try the Checker Framework without installing it, use the
+\href{http://eisop.uwaterloo.ca/live/}{Checker Framework Live Demo} webpage.
+\item
+This section describes how to install the Checker Framework from its
+distribution.  The Checker Framework release contains everything that you
+need, both to run checkers and to write your own checkers.
+\item
+Alternately, you can build the latest development version from source
+(Section~\refwithpage{build-source}).
+\end{itemize}
+
+
+\textbf{Requirement:}
+% Keep in sync with build.gradle and SourceChecker.init.
+You must have \textbf{JDK 8} or \textbf{JDK 11} installed.
+% Possible replacement for the above.
+% You must have a supported LTS JDK installed: \textbf{JDK 8} or \textbf{JDK 11}.
+The Checker Framework processes code written for either of those versions.
+
+The installation process has two required steps and one
+optional step.
+\begin{enumerate}
+\item
+  Download the Checker Framework distribution:
+  %BEGIN LATEX
+  \\
+  %END LATEX
+  \url{https://checkerframework.org/checker-framework-3.13.0.zip}
+
+\item
+  Unzip it to create a \code{checker-framework-\ReleaseVersion{}} directory.
+
+\item
+  \label{installation-configure-step}%
+  Configure your IDE, build system, or command shell to include the Checker
+  Framework on the classpath.  Choose the appropriate section of
+  Chapter~\ref{external-tools}.
+
+
+\end{enumerate}
+
+Now you are ready to start using the checkers.
+
+We recommend that you work through the
+\ahreforurl{https://checkerframework.org/tutorial/}{Checker
+Framework tutorial}, which demonstrates the Nullness, Regex, and Tainting Checkers.
+
+Section~\ref{example-use} walks you through a simple example.  More detailed
+instructions for using a checker appear in Chapter~\ref{using-a-checker}.
+
+\label{version-number}
+The Checker Framework is released on a monthly schedule.  The minor version
+(the middle number in the version number) is incremented if there are any
+incompatibilities
+% Sometimes, we permit trivial incompatibilities, or ones that are unlikely
+% to affect users, without changing the minor version.
+with the previous version, including in user-visible
+behavior or in methods that a checker implementation might call.
+
+
+\sectionAndLabel{Example use:  detecting a null pointer bug}{example-use}
+
+This section gives a very simple example of running the Checker Framework.
+There is also a \ahreforurl{https://checkerframework.org/tutorial/}{tutorial}
+that you can work along with.
+
+  Let's consider this very simple Java class.  The local variable \<ref>'s type is
+  annotated as \refqualclass{checker/nullness/qual}{NonNull}, indicating that \<ref> must be a reference to a
+  non-null object.  Save the file as \<GetStarted.java>.
+
+\begin{Verbatim}
+import org.checkerframework.checker.nullness.qual.*;
+
+public class GetStarted {
+    void sample() {
+        @NonNull Object ref = new Object();
+    }
+}
+\end{Verbatim}
+
+If you run the Nullness Checker (Chapter~\ref{nullness-checker}), the
+compilation completes without any errors.
+
+Now, introduce an error.  Modify \<ref>'s assignment to:
+\begin{alltt}
+  @NonNull Object ref = \textbf{null};
+\end{alltt}
+
+If you run the Nullness Checker again, it emits
+  the following error:
+\begin{Verbatim}
+GetStarted.java:5: incompatible types.
+found   : @Nullable <nulltype>
+required: @NonNull Object
+        @NonNull Object ref = null;
+                              ^
+1 error
+\end{Verbatim}
+
+This is a trivially simple example.  Even an unsound bug-finding tool like
+SpotBugs or Error Prone could have detected this bug.  The
+Checker Framework's analysis is more powerful than those tools and detects
+more code defects than they do.
+
+Type qualifiers such as \<@NonNull> are permitted anywhere
+that you can write a type, including generics and casts; see
+Section~\ref{writing-annotations}.  Here are some examples:
+
+\begin{alltt}
+  \underline{@Interned} String intern() \ttlcb{} ... \ttrcb{}             // return value
+  int compareTo(\underline{@NonNull} String other) \ttlcb{} ... \ttrcb{}  // parameter
+  \underline{@NonNull} List<\underline{@Interned} String> messages;     // non-null list of interned Strings
+\end{alltt}
+
+
+\htmlhr
+\chapterAndLabel{Using a checker}{using-a-checker}
+
+A pluggable type-checker enables you to detect certain bugs in your code,
+or to prove that they are not present.  The verification happens at compile
+time.
+
+
+Finding bugs, or verifying their absence, with a checker is a two-step process, whose steps are
+described in Sections~\ref{writing-annotations} and~\ref{running}.
+
+\begin{enumerate}
+
+\item The programmer writes annotations, such as \refqualclass{checker/nullness/qual}{NonNull} and
+  \refqualclass{checker/interning/qual}{Interned}, that specify additional information about Java types.
+  (Or, the programmer uses an inference tool to automatically infer
+  annotations that are consistent with their code:  see Section~\ref{type-inference}.)
+  It is possible to annotate only part of your code:  see
+  Section~\ref{unannotated-code}.
+
+\item The checker reports whether the program contains any erroneous code
+  --- that is, code that is inconsistent with the annotations.
+
+\end{enumerate}
+
+This chapter is structured as follows:
+\begin{itemize}
+\item Section~\ref{writing-annotations}: How to write annotations
+\item Section~\ref{running}:  How to run a checker
+\item Section~\ref{checker-guarantees}: What the checker guarantees
+\item Section~\ref{tips-about-writing-annotations}: Tips about writing annotations
+\end{itemize}
+
+Additional topics that apply to all checkers are covered later in the manual:
+\begin{itemize}
+\item Chapter~\ref{advanced-type-system-features}: Advanced type system features
+\item Chapter~\ref{suppressing-warnings}: Suppressing warnings
+\item Chapter~\ref{annotating-libraries}: Annotating libraries
+\item Chapter~\ref{creating-a-checker}: How to create a new checker
+\item Chapter~\ref{external-tools}: Integration with external tools
+\end{itemize}
+
+
+There is a
+\ahreforurl{https://checkerframework.org/tutorial/}{tutorial}
+that walks you through using the Checker Framework on the
+command line.
+
+% The annotations have to be on your classpath even when you are not using
+% the -processor, because of the existence of the import statement for
+% the annotations.
+
+
+\sectionAndLabel{Where to write type annotations}{writing-annotations}
+
+You may write a type annotation immediately before any
+use of a type, including in generics and casts.  Because array levels are
+types and receivers have types, you can also write type annotations on
+them.  Here are a few examples of type annotations:
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{alltt}
+  \underline{@Interned} String intern() \ttlcb{} ... \ttrcb{}               // return value
+  int compareTo(\underline{@NonNull} String other) \ttlcb{} ... \ttrcb{}    // parameter
+  String toString(\underline{@Tainted} MyClass this) \ttlcb{} ... \ttrcb{}  // receiver ("this" parameter)
+  \underline{@NonNull} List<\underline{@Interned} String> messages;       // generics:  non-null list of interned Strings
+  \underline{@Interned} String \underline{@NonNull} [] messages;          // arrays:  non-null array of interned Strings
+  myDate = (\underline{@Initialized} Date) beingConstructed;  // cast
+\end{alltt}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+You only need to write type annotations on method signatures, fields, and some type arguments.
+Most annotations within method bodies are inferred for you; for more details,
+see  Section~\ref{type-refinement}.
+
+The Java Language Specification also defines
+declaration annotations, such as \<@Deprecated> and \<@Override>, which apply
+to a class, method, or field but do not apply to the method's return type
+or the field's type.  They should be written on their own line in the
+source code, before the method's signature.
+
+
+\sectionAndLabel{Running a checker}{running}
+
+To run a checker, run the compiler \code{javac} as usual,
+but either pass the \code{-processor \emph{plugin\_class}} command-line
+option, or use auto-discovery as described in
+Section~\ref{checker-auto-discovery}.
+(If your project already uses auto-discovery for some annotation processor,
+such as AutoValue, then you should use auto-discovery.)
+A concrete example of using \code{-processor} to run the Nullness Checker is:
+
+\begin{Verbatim}
+  javac -processor nullness MyFile.java
+\end{Verbatim}
+
+\noindent
+where \<javac> is as specified in Section~\ref{javac-wrapper}.
+
+You can also run a checker from within your favorite IDE or build system.  See
+Chapter~\ref{external-tools} for details about build tools such as
+Ant (Section~\ref{ant-task}),
+Buck (Section~\ref{buck}),
+Gradle (Section~\ref{gradle}),
+Maven (Section~\ref{maven}), and
+sbt (Section~\ref{sbt});
+IDEs such as
+Eclipse (Section~\ref{eclipse}),
+IntelliJ IDEA (Section~\ref{intellij}),
+NetBeans (Section~\ref{netbeans}),
+and
+tIDE (Section~\ref{tide});
+and about customizing other IDEs and build tools.
+
+The checker is run on only the Java files that javac compiles.
+This includes all Java files specified on the command line and those
+created by another annotation processor.  It may also include other of
+your Java files, if they are more recent than the corresponding \code{.class} file.
+Even when the checker does not analyze a class (say, the class was
+already compiled, or source code is not available), it does check
+the \emph{uses} of those classes in the source code being compiled.
+Type-checking works modularly and intraprocedurally:  when verifying a
+method, it examines only the signature (including annotations) of other
+methods, not their implementations.  When analyzing a variable use, it
+relies on the type of the variable, not any dataflow outside the current
+method that produced the value.
+
+After you compile your code while running a checker, the resulting
+\<.class> and \<.jar> files can be used for pluggable type-checking of client code.
+
+If you compile code without the \code{-processor}
+command-line option, no checking of the type
+annotations is performed.  Furthermore, only explicitly-written annotations
+are written to the \<.class> file; defaulted annotations are not, and this
+will interfere with type-checking of clients that use your code.
+Therefore, to create
+\<.class> files that will be distributed or compiled against, you should run the
+type-checkers for all the annotations that you have written.
+
+
+\subsectionAndLabel{Using annotated libraries}{annotated-libraries-using}
+
+When your code uses a library that is not currently being compiled, the
+Checker Framework looks up the library's annotations in its class files or
+in a stub file.
+
+Some projects are already distributed with type annotations by their
+maintainers, so you do not need to do anything special.
+An example is all the libraries in \url{https://github.com/plume-lib/}.
+Over time, this should become more common.
+
+For some other libraries, the Checker Framework developers have provided an
+annotated version of the library, either as a stub file or as compiled class files.
+(If some library is not available in either of these forms,
+you can contribute by annotating it, which will
+help you and all other Checker Framework users; see
+\chapterpageref{annotating-libraries}.)
+
+Some stub files are used automatically by a checker, without any action on
+your part.  For others, you must pass the \<-Astubs=...> command-line argument.
+As a special case, if an \<.astub> file appears in
+\<checker/resources/>, then pass the command-line option
+use \<-Astubs=checker.jar/\emph{stubfilename.astub}>.
+The ``\<checker.jar>'' should be literal --- don't provide a path.
+This special syntax only works for ``\<checker.jar>''.
+%% There aren't any such libraries at the moment.
+% (Examples of such libraries are:
+% % This list appears here to make it searchable/discoverable.
+% ...
+% .)
+
+The annotated libraries that are provided as class files appear in
+\ahref{https://search.maven.org/search?q=org.checkerframework.annotatedlib}{the
+  \<org.checkerframework.annotatedlib> group in the Maven Central Repository}.
+The annotated library has \emph{identical} behavior to the upstream,
+unannotated version; the source code is identical other than added
+annotations.
+%
+(Some of the annotated libraries are
+% This list appears here to make it searchable/discoverable.
+bcel,
+commons-csv,
+commons-io,
+guava,
+and
+java-getopt.)
+
+To use an annotated library:
+
+\begin{itemize}
+\item
+If your project stores \<.jar> files locally, then
+\ahref{https://search.maven.org/search?q=org.checkerframework.annotatedlib}{download
+  the \<.jar> file from the Maven Central Repository}.
+
+\item
+If your project manages dependencies using a tool such as Gradle or Maven,
+then update your buildfile to use the \<org.checkerframework.annotatedlib>
+group.  For example, in \<build.gradle>, change
+
+\begin{Verbatim}
+  api group: 'org.apache.bcel', name: 'bcel', version: '6.3.1'
+  api group: 'commons-io', name: 'commans-io', version: '2.6'
+\end{Verbatim}
+
+\noindent
+to
+
+\begin{Verbatim}
+  api group: 'org.checkerframework.annotatedlib', name: 'bcel', version: '6.3.1'
+  api group: 'org.checkerframework.annotatedlib', name: 'commons-io', version: '2.6.0.1'
+\end{Verbatim}
+
+\noindent
+Usually use the same version number.  (Sometimes you will use a slightly larger
+number, if the Checker Framework developers have improved the type
+annotations since the last release by the upstream maintainers.)  If a
+newer version of the upstream library is available but that version is not
+available in \<org.checkerframework.annotatedlib>, then
+\ahrefloc{reporting-bugs}{open an issue} requesting that the
+\<org.checkerframework.annotatedlib> version be updated.
+\end{itemize}
+
+%% This is for paranoid users.
+% During type-checking, you should use the
+% annotated version of the library to improve type-checking results (to issue
+% fewer false positive warnings).  When doing ordinary compilation or while
+% running your code, you can use either the annotated library or the regular
+% distributed version of the library --- they behave identically.
+
+There is one special case.  If an \<.astub> file is shipped with the
+Checker Framework in \<checker/resources/>, then you can
+use \<-Astubs=checker.jar/\emph{stubfilename.astub}>.
+The ``\<checker.jar>'' should be literal --- don't provide a path.
+(This special syntax only works for ``\<checker.jar>''.)
+
+
+\subsectionAndLabel{Summary of command-line options}{checker-options}
+
+You can pass command-line arguments to a checker via \<javac>'s standard \<-A>
+option (``\<A>'' stands for ``annotation'').  All of the distributed
+checkers support the following command-line options.
+Each checker may support additional command-line options; see the checker's
+documentation.
+
+To pass an option to only a particular checker,
+prefix the option with the canonical or simple name
+of a checker, followed by an underscore ``\<\_>''.
+Such an option will apply only to a checker with that name or any subclass of that checker.
+For example, you can use
+\begin{Verbatim}
+    -ANullnessChecker_lint=redundantNullComparison
+    -Aorg.checkerframework.checker.guieffect.GuiEffectChecker_lint=debugSpew
+\end{Verbatim}
+
+\noindent
+to pass different lint options to the Nullness and GUI Effect Checkers.  A
+downside is that, in this example, the Nullness Checker will issue a
+``The following options were not recognized by any processor'' warning
+about the second option and the GUI Effect Checker will issue a
+``The following options were not recognized by any processor'' warning
+about the first option.
+
+% This list should be kept in sync with file
+% framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java
+
+Unsound checking: ignore some errors
+\begin{itemize}
+\item \<-AsuppressWarnings>
+  Suppress all errors and warnings matching the given key; see
+  Section~\ref{suppresswarnings-command-line}.
+\item \<-AskipUses>, \<-AonlyUses>
+  Suppress all errors and warnings at all uses of a given class --- or at all
+  uses except those of a given class.  See Section~\ref{askipuses}.
+\item \<-AskipDefs>, \<-AonlyDefs>
+  Suppress all errors and warnings within the definition of a given class
+  --- or everywhere except within the definition of a given class.  See
+  Section~\ref{askipdefs}.
+\item \<-AassumeSideEffectFree>, \<-AassumeDeterministic>, \<-AassumePure>
+  Unsoundly assume that every method is side-effect-free, deterministic, or
+  both; see
+  Section~\ref{type-refinement-purity}.
+\item \<-AassumeAssertionsAreEnabled>, \<-AassumeAssertionsAreDisabled>
+  Whether to assume that assertions are enabled or disabled; see Section~\ref{type-refinement-assertions}.
+\item \<-AignoreRangeOverflow>
+  Ignore the possibility of overflow for range annotations such as
+  \<@IntRange>; see Section~\ref{value-checker-overflow}.
+\item \<-Awarns>
+  Treat checker errors as warnings.  If you use this, you may wish to also
+  supply \code{-Xmaxwarns 10000}, because by default \<javac> prints at
+  most 100 warnings.  If you use this, don't supply \code{-Werror},
+  which is a javac argument to halt compilation if a warning is issued.
+\item \<-AignoreInvalidAnnotationLocations>
+  Ignore annotations in bytecode that have invalid annotation locations.
+\end{itemize}
+
+\label{unsound-by-default}
+More sound (strict) checking: enable errors that are disabled by default
+\begin{itemize}
+\item \<-AcheckPurityAnnotations>
+  Check the bodies of methods marked
+  \refqualclass{dataflow/qual}{SideEffectFree},
+  \refqualclass{dataflow/qual}{Deterministic},
+  and \refqualclass{dataflow/qual}{Pure}
+  to ensure the method satisfies the annotation.  By default,
+  the Checker Framework unsoundly trusts the method annotation.  See
+  Section~\ref{type-refinement-purity}.
+\item \<-AinvariantArrays>
+  Make array subtyping invariant; that is, two arrays are subtypes of one
+  another only if they have exactly the same element type.  By default,
+  the Checker Framework unsoundly permits covariant array subtyping, just
+  as Java does.  See Section~\ref{invariant-arrays}.
+\item \<-AcheckCastElementType>
+  In a cast, require that parameterized type arguments and array elements
+  are the same.  By default, the Checker Framework unsoundly permits them
+  to differ, just as Java does.  See Section~\ref{covariant-type-parameters}
+  and Section~\ref{invariant-arrays}.
+\item \<-AuseConservativeDefaultsForUncheckedCode>
+  Enables conservative defaults, and suppresses all type-checking warnings,
+  in unchecked code.  Takes arguments ``source,bytecode''.
+  ``-source,-bytecode'' is the (unsound) default setting.
+  \begin{itemize}
+  \item
+  ``bytecode'' specifies
+  whether the checker should apply conservative defaults to
+  bytecode (that is, to already-compiled libraries); see
+  Section~\ref{defaults-classfile}.
+  \item
+  Outside the scope of any relevant
+  \refqualclass{framework/qual}{AnnotatedFor} annotation, ``source'' specifies whether conservative
+  default annotations are applied to source code and suppress all type-checking warnings; see
+  Section~\ref{compiling-libraries}.
+  \end{itemize}
+\item \<-AconcurrentSemantics>
+  Whether to assume concurrent semantics (field values may change at any
+  time) or sequential semantics; see Section~\ref{faq-concurrency}.
+\item \<-AconservativeUninferredTypeArguments>
+  Whether an error should be issued if type arguments could not be inferred and
+  whether method type arguments that could not be inferred should use
+  conservative defaults.
+  By default, such type arguments are (largely) ignored in later
+  checks.
+  Passing this option uses a conservative value instead.
+  See \href{https://github.com/typetools/checker-framework/issues/979}{Issue
+  979}.
+\item \<-AignoreRawTypeArguments=false>
+  Do not ignore subtype tests for type arguments that were inferred for a
+  raw type. Must also use \<-AconservativeUninferredTypeArguments>.  See
+  Section~\ref{generics-raw-types}.
+\item \<-processor org.checkerframework.common.initializedfields.InitializedFieldsChecker,...>
+  Ensure that all fields are initialized by the constructor.  See
+  \chapterpageref{initialized-fields-checker}.
+\end{itemize}
+
+Type-checking modes:  enable/disable functionality
+\begin{itemize}
+\item \<-Alint>
+  Enable or disable optional checks; see Section~\ref{lint-options}.
+\item \<-AsuggestPureMethods>
+  Suggest methods that could be marked
+  \refqualclass{dataflow/qual}{SideEffectFree},
+  \refqualclass{dataflow/qual}{Deterministic},
+  or \refqualclass{dataflow/qual}{Pure}; see
+  Section~\ref{type-refinement-purity}.
+\item \<-AresolveReflection>
+  Determine the target of reflective calls, and perform more precise
+  type-checking based no that information; see
+  Chapter~\ref{reflection-resolution}.  \<-AresolveReflection=debug> causes
+  debugging information to be output.
+\item \<-Ainfer=\emph{outputformat}>
+  Output suggested annotations for method signatures and fields.
+  These annotations may reduce the number of type-checking
+  errors when running type-checking in the future; see
+  Section~\ref{whole-program-inference}.
+  Using \<-Ainfer=jaifs> produces \<.jaif> files.
+  Using \<-Ainfer=stubs> produces \<.astub> files.
+  Using \<-Ainfer=ajava> produces \<.ajava> files.
+  You must also supply \<-Awarns>, or the inference output may be incomplete.
+\item \<-AshowSuppressWarningsStrings>
+  With each warning, show all possible strings to suppress that warning.
+\item \<-AwarnUnneededSuppressions>
+  Issue a warning if a \<@SuppressWarnings> did not suppress a warning
+  issued by the checker.  This only warns about
+  \<@SuppressWarnings> strings that contain a checker name
+  (Section~\ref{suppresswarnings-annotation-syntax}).  The
+  \<-ArequirePrefixInWarningSuppressions> command-line argument ensures
+  that all \<@SuppressWarnings> strings contain a checker name.
+\item \<-AwarnUnneededSuppressionsExceptions=\emph{regex}> disables
+  \<-AwarnUnneededSuppressions> for \<@SuppressWarnings> strings that
+  contain a match for the regular expression.  Most users don't need this.
+\item \<-ArequirePrefixInWarningSuppressions>
+  Require that the string in a warning suppression annotation begin with a checker
+  name.  Otherwise, the suppress warning annotation does not
+  suppress any warnings.  For example, if this command-line option is
+  supplied, then \<@SuppressWarnings("assignment")> has no effect, but
+  \<@SuppressWarnings("nullness:assignment")> does.
+\end{itemize}
+
+Partially-annotated libraries
+\begin{itemize}
+
+% \item \<-AprintUnannotatedMethods>
+%   List library methods that need to be annotated; see
+%   Section~\ref{annotating-libraries}.
+
+\item \<-Astubs>
+  List of stub files or directories; see Section~\ref{stub-using}.
+
+\item
+  \<-AstubWarnIfNotFound>,
+  \<-AstubWarnIfNotFoundIgnoresClasses>,
+  %% Uncomment when https://tinyurl.com/cfissue/2759 is fixed.
+  %  \<-AstubWarnIfOverwritesBytecode>,
+  \<-AstubWarnIfRedundantWithBytecode>,
+  \<-AstubWarnNote>,
+  Warn about problems with stub files; see Section~\ref{stub-troubleshooting}.
+
+\item \<-AmergeStubsWithSource>
+  If both a stub file and a source file for a class are available, trust
+  both and use the greatest lower bound of their annotations. The default
+  behavior (without this flag) is to ignore types from the stub file if
+  source is available. See Section~\ref{stub-multiple-specifications}.
+  % note to maintainers: GLB of the two types was chosen to support
+  % using this flag in combination with \<-Ainfer=stubs>.
+
+% This item is repeated above:
+\item \<-AuseConservativeDefaultsForUncheckedCode=source>
+  Outside the scope of any relevant
+  \refqualclass{framework/qual}{AnnotatedFor} annotation, use conservative
+  default annotations and suppress all type-checking warnings; see
+  Section~\ref{compiling-libraries}.
+
+\end{itemize}
+
+Debugging
+\begin{itemize}
+\item
+ \<-AprintAllQualifiers>,
+ \<-AprintVerboseGenerics>,
+ \<-Anomsgtext>,
+ \<-AdumpOnErrors>
+Amount of detail in messages; see Section~\ref{creating-debugging-options-detail}.
+
+\item
+ \<-Adetailedmsgtext>
+Format of diagnostic messages; see Section~\ref{creating-debugging-options-format}.
+
+\item
+ \<-Aignorejdkastub>,
+ \<-ApermitMissingJdk>,
+ \<-AparseAllJdk>,
+ \<-AstubDebug>
+Stub and JDK libraries; see Section~\ref{creating-debugging-options-libraries}.
+
+\item
+ \<-Afilenames>,
+ \<-Ashowchecks>,
+ \<-AshowInferenceSteps>
+Progress tracing; see Section~\ref{creating-debugging-options-progress}.
+
+\item
+\<-AoutputArgsToFile>
+Output the compiler command-line arguments to a file.  Useful when the
+command line is generated and executed by a tool, such as a build system.
+This produces a standalone command line that can be executed independently
+of the tool that generated it (such as a build system).
+That command line makes it easier to reproduce, report, and debug issues.
+For example, the command line can be modified to enable attaching a debugger.
+See Section~\ref{creating-debugging-options-output-args}.
+
+\item
+ \<-Aflowdotdir>,
+ \<-Averbosecfg>,
+ \<-Acfgviz>
+ Draw a visualization of the CFG (control flow graph); see
+ Section~\ref{creating-debugging-dataflow-graph}.
+
+\item
+ \<-AresourceStats>,
+ \<-AatfDoNotCache>,
+ \<-AatfCacheSize>
+Miscellaneous debugging options; see Section~\ref{creating-debugging-options-misc}.
+
+\item
+ \<-Aversion>
+Print the Checker Framework version.
+
+\item
+ \<-AprintGitProperties>
+Print information about the git repository from which the Checker Framework
+was compiled.
+
+\end{itemize}
+
+
+\noindent
+Some checkers support additional options, which are described in that
+checker's manual section.
+% Search for "@SupportedOptions" in the implementation to find them all.
+For example, \<-Aquals> tells
+the Subtyping Checker (see Chapter~\ref{subtyping-checker}) and the Fenum Checker
+(see Chapter~\ref{fenum-checker}) which annotations to check.
+
+
+Here are some standard javac command-line options that you may find useful.
+Many of them contain the word ``processor'', because in javac jargon, a
+checker is an ``annotation processor''.
+
+\begin{itemize}
+\item \<-processor> Names the checker to be
+  run; see Sections~\ref{running} and~\ref{shorthand-for-checkers}.
+  May be a comma-separated list of multiple checkers.  Note that javac
+  stops processing an indeterminate time after detecting an error.  When
+  providing multiple checkers, if one checker detects any error, subsequent
+  checkers may not run.
+\item \<-processorpath> Indicates where to search for the
+  checker.  This should also contain any classes used by type-checkers,
+  such as qualifiers used by the Subtyping Checker (see
+  Section~\ref{subtyping-example}) and classes that define
+  statically-executable methods used by the Constant Value Checker (see
+  Section~\ref{constant-value-staticallyexecutable-annotation}).
+\item \<-proc:>\{\<none>,\<only>\} Controls whether checking
+  happens; \<-proc:none>
+  means to skip checking; \<-proc:only> means to do only
+  checking, without any subsequent compilation; see
+  Section~\ref{checker-auto-discovery}.
+\item \<-implicit:class> Suppresses warnings about implicitly compiled files
+  (not named on the command line); see Section~\ref{ant-task}
+\item \<-J> Supply an argument to the JVM that is running javac;
+  for example, \<-J-Xmx2500m> to increase its maximum heap size
+\item \<-doe> To ``dump on error'', that is, output a stack trace
+  whenever a compiler warning/error is produced. Useful when debugging
+  the compiler or a checker.
+\end{itemize}
+
+The Checker Framework does not support \<-source 1.7> or earlier.  You must
+supply \<-source 1.8> or later, or no \<-source> command-line argument,
+when running \<javac>.
+
+
+\subsectionAndLabel{Checker auto-discovery}{checker-auto-discovery}
+
+``Auto-discovery'' makes the \code{javac} compiler always run an
+annotation processor, such as a checker
+plugin without explicitly passing the \code{-processor}
+command-line option.  This can make your command line shorter, and it ensures
+that your code is checked even if you forget the command-line option.
+
+If the \<javac> command line specifies any \<-processor> command-line
+option, then auto-discovery is disabled.  This means that if your project
+currently uses auto-discovery, you should use auto-discovery for the
+Checker Framework too.
+
+\begin{sloppypar}
+To enable auto-discovery, place a configuration file named
+\code{META-INF/services/javax.annotation.processing.Processor}
+in your classpath.  The file contains the names of the checkers to
+be used, listed one per line.  For instance, to run the Nullness Checker and the
+Interning Checker automatically, the configuration file should contain:
+\end{sloppypar}
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  org.checkerframework.checker.nullness.NullnessChecker
+  org.checkerframework.checker.interning.InterningChecker
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+You can disable this auto-discovery mechanism by passing the
+\code{-proc:none} command-line option to \<javac>, which disables all
+annotation processing including all pluggable type-checking.
+
+%% Auto-discovering all the distributed checkers by default would be
+%% problematic:  the nullness and mutability checkers would issue lots of
+%% errors for unannotated code, and that would be irritating.  So, leave it
+%% up to the user to enable auto-discovery.
+
+
+\subsectionAndLabel{Shorthand for built-in checkers}{shorthand-for-checkers}
+
+% TODO: this feature only works for our javac script, not when using
+% the standard javac. Should this be explained?
+
+Ordinarily, javac's \code{-processor} flag requires fully-qualified class names.
+When using the Checker Framework javac wrapper (Section~\ref{javac-wrapper}), you may
+omit the package name and the \<Checker> suffix.
+The following three commands are equivalent:
+
+\begin{alltt}
+  javac -processor \textbf{org.checkerframework.checker.nullness.NullnessChecker} MyFile.java
+  javac -processor \textbf{NullnessChecker} MyFile.java
+  javac -processor \textbf{nullness} MyFile.java
+\end{alltt}
+
+This feature also works when multiple checkers are specified.
+Their names are separated by commas, with no surrounding space.
+For example:
+
+\begin{alltt}
+  javac -processor NullnessChecker,RegexChecker MyFile.java
+  javac -processor nullness,regex MyFile.java
+\end{alltt}
+
+This feature does not apply to javac \href{https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#commandlineargfile}{@argfiles}.
+
+
+\sectionAndLabel{What the checker guarantees}{checker-guarantees}
+
+A checker guarantees two things:  type annotations reflect facts about
+run-time values, and illegal operations are not performed.
+
+For example, the Nullness Checker (Chapter~\ref{nullness-checker})
+guarantees lack of null pointer exceptions (Java \<NullPointerException>).
+More precisely, it guarantees
+that expressions whose type is annotated with
+\refqualclass{checker/nullness/qual}{NonNull} never evaluate to null,
+and it forbids other expressions from being dereferenced.
+
+As another example, the Interning Checker (Chapter~\ref{interning-checker})
+guarantees that correct equality tests are performed.
+More precisely, it guarantees that
+every expression whose type is an \refqualclass{checker/interning/qual}{Interned} type
+evaluates to an interned value, and it forbids
+\<==>  on other expressions.
+
+The guarantee holds only if you run the checker on every part of your
+program and the checker issues no warnings anywhere in the code.
+You can also verify just part of your program.
+
+There are some limitations to the guarantee.
+
+
+\begin{itemize}
+
+\item
+  A compiler plugin can check only those parts of your program that you run
+  it on.  If you compile some parts of your program without running the
+  checker, then there is no guarantee that the entire program satisfies the
+  property being checked.  Some examples of un-checked code are:
+
+  \begin{itemize}
+  \item
+    Code compiled without the \code{-processor} switch.  This includes
+    external libraries supplied as a \code{.class} file and native methods
+    (because the implementation is not Java code, it cannot be checked).
+  \item
+    Code compiled with the \code{-AskipUses}, \code{-AonlyUses}, \code{-AskipDefs} or \code{-AonlyDefs}
+    command-line arguments (see Chapter~\ref{suppressing-warnings}).
+  \item
+    Dynamically generated code, such as generated by Spring or MyBatis.
+    Its bytecode is directly generated and run, not compiled by javac and
+    not visible to the Checker Framework.
+    % https://github.com/typetools/checker-framework/issues/3139
+  \end{itemize}
+
+  In each of these cases, any \emph{use} of the code is checked --- for
+  example, a call to a native method must be compatible with any
+  annotations on the native method's signature.
+  However, the annotations on the un-checked code are trusted; there is no
+  verification that the implementation of the native method satisfies the
+  annotations.
+
+\item
+  You can suppress warnings, such as via the \code{@SuppressWarnings}
+  annotation (\chapterpageref{suppressing-warnings}).  If you do so
+  incorrectly, the checker's guarantee no longer holds.
+
+\item
+  The Checker Framework is, by default, unsound in a few places where a
+  conservative analysis would issue too many false positive warnings.
+  These are listed in Section~\ref{unsound-by-default}.
+  You can supply a command-line argument to make the Checker Framework
+  sound for each of these cases.
+
+%% This isn't an unsoundness in the Checker Framework:  for any type system
+%% that does not include a conservative library annotation for
+%% Method.invoke, it is a bug in that particular type-checker.
+% \item
+%   Reflection can violate the Java type system, and
+%   the checkers are not sophisticated enough to reason about the possible
+%   effects of reflection.  Similarly, deserialization and cloning can
+%   create objects that could not result from normal constructor calls, and
+%   that therefore may violate the property being checked.
+
+\item
+  Specific checkers may have other limitations; see their documentation for
+  details.
+
+\end{itemize}
+
+In order to avoid a flood of unhelpful warnings, many of the checkers avoid
+issuing the same warning multiple times.  For example, consider this code:
+
+\begin{Verbatim}
+  @Nullable Object x = ...;
+  x.toString();                 // warning
+  x.toString();                 // no warning
+\end{Verbatim}
+
+\noindent
+The second call to \<toString> cannot possibly throw a null
+pointer warning --- \<x> is non-null if control flows to the second
+statement.
+In other cases, a checker avoids issuing later warnings with the same cause
+even when later code in a method might also fail.
+This does not
+affect the soundness guarantee, but a user may need to examine more
+warnings after fixing the first ones identified.  (Often,
+a single fix corrects all the warnings.)
+
+% It might be worthwhile to permit a user to see every warning --- though I
+% would not advocate this setting for daily use.
+
+If you find that a checker fails to issue a warning that it
+should, then please report a bug (see Section~\ref{reporting-bugs}).
+
+
+\sectionAndLabel{Tips about writing annotations}{tips-about-writing-annotations}
+
+Section~\ref{library-tips} gives additional tips that are
+specific to annotating a third-party library.
+
+
+\subsectionAndLabel{Write annotations before you run a checker}{annotate-before-checking}
+
+Before you run a checker, annotate the code, based on its documentation.
+Then, run the checker to uncover bugs in the code or the documentation.
+
+Don't do the opposite, which is to run the checker and then add annotations
+according to the warnings issued.  This approach is less systematic, so you
+may overlook some annotations.  It often leads to confusion and poor
+results.  It leads users to make changes not for any principled reason, but
+to ``make the type-checker happy'', even when the changes are in conflict
+with the documentation or the code.  Also see
+\ahref{#get-started-annotations-are-a-specification}{``Annotations are a
+  specification''}, below.
+
+
+\subsectionAndLabel{How to get started annotating legacy code}{get-started-with-legacy-code}
+
+Annotating an entire existing program may seem like a daunting task.  But,
+if you approach it systematically and do a little bit at a time, you will
+find that it is manageable.
+
+\subsubsectionAndLabel{Start small}{get-started-start-small}
+
+Start small.  Focus on one specific property that matters to you; in
+other words, run just one checker rather than multiple ones.  You may
+choose a different checker for different programs.
+Focus on
+the most mission-critical or error-prone part of your code; don't try to
+annotate your whole program at first.
+
+It is easiest to add annotations if you know the code or the
+code contains documentation.  While adding annotations, you will spend most of your time
+understanding the code, and less time actually writing annotations
+or running the checker.
+
+Don't annotate the whole program, but work module by module.
+Start annotating classes at the leaves of the call tree ---
+that is,
+start with classes/packages that have few dependencies on other
+code.
+Annotate supertypes before you
+annotate classes that extend or implement them.
+The reason for this rule is that it is
+easiest to annotate a class if the code it depends on has already been
+annotated.
+Sections~\ref{askipuses} and~\ref{askipdefs} give ways to skip
+checking of some files, directories, or packages.
+Section~\ref{unannotated-code} gives advice about handling calls from
+annotated code into unannotated code.
+
+When annotating, be systematic; we recommend
+annotating an entire class or module at a time (not just some of the methods)
+so that you don't lose track of your work or redo work.  For example,
+working class-by-class avoids confusion about whether an unannotated type use
+means you determined that the default is desirable, or it means you didn't
+yet examine that type use.
+
+Don't overuse pluggable type-checking.  If the regular Java type system can
+verify a property using Java subclasses, then that is a better choice than
+pluggable type-checking (see Section~\ref{faq-typequals-vs-subtypes}).
+
+
+\subsubsectionAndLabel{Annotations are a specification}{get-started-annotations-are-a-specification}
+
+When you write annotations, you are writing a specification, and you should
+think about them that way.  Start out by understanding the program so that
+you can write an accurate specification.
+Sections~\ref{annotate-normal-behavior}
+and~\ref{annotations-are-a-contract} give more tips about writing
+specifications.
+
+For each class, read its Javadoc.  For instance, if you are adding
+annotations for the Nullness Checker (Section~\ref{nullness-checker}), then
+you can search the documentation for ``null'' and then add \<@Nullable>
+anywhere appropriate.  Start by annotating signatures and fields, but not
+method bodies.  The only reason to even
+\emph{read} the method bodies yet is to determine signature annotations for
+undocumented methods ---
+for example, if the method returns null, you know its return type should be
+annotated \<@Nullable>, and a parameter that is compared against \<null>
+may need to be annotated \<@Nullable>.
+
+The specification should state all facts that are relevant to callees.
+When checking a method, the checker uses only the specification, not the
+implementation, of other methods.  (Equivalently, type-checking is
+``modular'' or ``intraprocedural''.)  When analyzing a variable use, the
+checker relies on the type of the variable, not any dataflow outside the
+current method that produced the value.
+
+After you have annotated all the signatures, run the checker.
+Then, fix bugs in code and add/modify annotations as necessary.
+% If signature annotations are necessary, then you may want
+% to fix the documentation that did not indicate the property; but this isn't
+% strictly necessary, since the annotations that you wrote provide that
+% documentation.
+Don't get discouraged if you see many type-checker warnings at first.
+Often, adding just a few missing annotations will eliminate many warnings,
+and you'll be surprised how fast the process goes overall (assuming that
+you understand the code, of course).
+
+It is usually not a good idea to experiment with adding and removing
+annotations in order to understand their effect.  It is better to reason
+about the desired design.  However, to avoid having to manually examine all
+callees, a more automated approach is to save the checker output before
+changing an annotation, then compare it to the checker output after
+changing the annotation.
+
+Chapter~\ref{annotating-libraries} tells you how to annotate libraries that
+your code uses.  Section~\ref{handling-warnings} and
+Chapter~\ref{suppressing-warnings} tell you what to do when you are unable
+to eliminate checker warnings by adding annotations.
+
+
+\subsubsectionAndLabel{Write good code}{get-started-write-good-code}
+
+Avoid complex code, which is more error-prone.  If you write your code to
+be simple and clear enough for the type-checker to verify, then it will
+also be easier for programmers to understand.  When you verify your code, a
+side benefit is improving your code's structure.
+
+Your code should compile cleanly under the regular Java compiler.  As a
+specific example, your code should not use raw types like \code{List}; use
+parameterized types like \code{List<String>} instead
+(Section~\ref{generics-raw-types}).  If you suppress Java compiler
+warnings, then the Checker Framework will issue more warnings, and its
+messages will be more confusing.  (Also, if you are not willing to write
+code that type-checks in Java, then you might not be willing to use an even
+more powerful type system.)
+
+Do not write unnecessary annotations.
+\begin{itemize}
+\item
+  Do not annotate local variables unless necessary.  The checker infers
+  annotations for local variables (see Section~\ref{type-refinement}).
+  Usually, you only need to annotate fields and method signatures.  You
+  should add annotations inside method bodies only if the checker is unable
+  to infer the correct annotation (usually on type arguments or array
+  element types, rather than
+  on top-level types).
+  % or if
+  % you need to suppress a warning (see Chapter~\ref{suppressing-warnings}).
+
+\item
+  Do not write annotations that are redundant with defaults.  For example,
+  when checking nullness (\chapterpageref{nullness-checker}), the default
+  annotation is \<@NonNull>, in most locations other than some type bounds
+  (Section~\ref{climb-to-top}).  When you are starting out, it might seem
+  helpful to write redundant annotations as a reminder, but that's like
+  when beginning programmers write a comment about every simple piece of
+  code:
+
+\begin{Verbatim}
+// The below code increments variable i by adding 1 to it.
+i++;
+\end{Verbatim}
+
+  As you become comfortable with pluggable type-checking, you will find
+  redundant annotations to be distracting clutter, so avoid putting them in
+  your code in the first place.
+
+\item
+  Avoid writing \<@SuppressWarnings> annotations unless there is no
+  alternative.  It is tempting to think that your code is right and the
+  checker's warnings are false positives.  Sometimes they are, but slow
+  down and convince yourself of that before you dismiss them.
+  Section~\ref{handling-warnings} discusses what to do when a checker
+  issues a warning about your code.
+
+\end{itemize}
+
+
+\subsectionAndLabel{Annotations indicate non-exceptional behavior}{annotate-normal-behavior}
+
+You should use annotations to specify \emph{normal} behavior.  The
+annotations indicate all the values that you \emph{want} to flow to a
+reference --- not every value that might possibly flow there if your
+program has a bug.
+
+
+\subsubsectionAndLabel{Methods that crash when passed certain values}{annotate-normal-behavior-always-crash}
+
+\paragraphAndLabel{Nullness example}{annotate-normal-behavior-nullness-example}
+As an example, consider the Nullness Checker.  Its goal is to guarantee that your
+program does not crash due to a null value.
+
+This method crashes if \<null> is passed to it:
+
+\begin{Verbatim}
+  /** @throws NullPointerException if arg is null */
+  void m1(Object arg) {
+    arg.toString();
+    ...
+  }
+\end{Verbatim}
+
+\noindent
+Therefore, the type of \<arg>
+should be \<@NonNull Object> (which you can write as just \<Object>, because
+\<@NonNull> is the default).  The Nullness Checker (\chapterpageref{nullness-checker})
+prevents null pointer exceptions by warning you whenever a client passes a
+value that might cause \<m1> to crash.
+
+Here is another method:
+
+\begin{Verbatim}
+  /** @throws NullPointerException if arg is null */
+  void m2(Object arg) {
+    Objects.requireNonNull(arg);
+    ...
+  }
+\end{Verbatim}
+
+Method \<m2> behaves just like \<m1> in that it throws
+\<NullPointerException> if a client passes \<null>.  Therefore, their
+specifications should be identical (the argument is \<@NonNull>), so the
+checker will issue the same warning if a client might pass \<null>.
+
+The same argument applies to any method that is guaranteed to throw an exception
+if it receives \code{null} as an argument.  Examples include:
+
+\begin{Verbatim}
+  com.google.common.base.Preconditions.checkNotNull(Object)
+  java.lang.Double.valueOf(String)
+  java.lang.Objects.requireNonNull(Object)
+  java.lang.String.contains(CharSequence)
+  org.junit.Assert.assertNotNull(Object)
+\end{Verbatim}
+
+Their formal parameter type is annotated as \<@NonNull>, because otherwise the
+program might crash.  Adding a call to a method like \<requireNonNull>
+never prevents a crash:  your code still crashes, but with a slightly
+different stack trace.  In order to prevent all exceptions in your program
+caused by null pointers, you need to prevent those thrown by methods including
+\<requireNonNull>.
+
+(One might argue that the formal parameter should be annotated as
+\refqualclass{checker/nullness/qual}{Nullable} because passing \code{null} has a
+well-defined semantics (throw an exception) and such an execution may be
+possible if your program has a bug.  However, it is never the programmer's
+intent for \<null> to flow there.  Preventing such bugs is the purpose of
+the Nullness Checker.)
+
+A method like \<requireNonNull> is useless for making your code correct,
+but it does have a benefit:  its stack trace may help developers to track
+down the bug.  (For users, the stack trace is scary, confusing, and usually
+non-actionable.)  But if you are using the Checker Framework, you can
+prevent errors rather than needing extra help in debugging the ones that
+occur at run time.
+
+
+\paragraphAndLabel{Optional example}{annotate-normal-behavior-optional-example}
+Another example is the Optional Checker (\chapterpageref{optional-checker})
+and the \sunjavadoc{java.base/java/util/Optional.html\#orElseThrow()}{orElseThrow} method.
+The goal of the Optional Checker is to ensure that the program does not
+crash due to use of a non-present Optional value.  Therefore, the receiver
+of
+\<orElseThrow> is annotated as
+\refqualclass{checker/optional/qual}{Present},
+and the Optional Checker issues a warning if the client calls
+\<orElseThrow> on a \refqualclass{checker/optional/qual}{MaybePresent} value.
+
+
+\paragraphAndLabel{Permitting crashes in called methods}{annotate-normal-behavior-skip-libraries}
+You can make a checker ignore crashes in library code (such as
+\<assertNotNull()>) that occur as a result of misuse by your code.
+This invalidates the checker's guarantee that your program will not crash.
+(Programmers and users typically care about all crashes, no matter which
+method is at the top of the call stack when the exception is thrown.)
+The checker will still warn you about crashes in your own code.
+
+\begin{itemize}
+\item
+  The \<-AskipUses> command-line argument (Section~\ref{askipuses}) skips
+  checking all method calls to one or more classes.
+\item
+  A stub file (Section~\ref{stub}) can override the library's annotations,
+  for one or more methods.
+\item
+  Don't type-check clients of the method.  For example, JUnit's
+  \<assertNotNull()> is typically called only in test code; its clients are
+  the tests.  If you type-check only your main program, then the annotation
+  on \<assertNotNull()> is irrelevant.
+\end{itemize}
+
+
+\subsubsectionAndLabel{Methods that sometimes crash when passed certain values}{annotate-normal-behavior-sometimes-crash}
+
+%% TODO: This text should be revised when @NullableWhen or @NonNullWhen is implemented.
+% This is only an issue for code with unchecked, trusted annotations such as
+% library methods; if the method is type-checked, then the type-checker
+% warnings will lead you to leave the formal parameter as the default, which
+% means \<@NonNull>.
+If a method can \emph{possibly} throw an exception because its parameter
+is \<null>, then that parameter's type should be \<@NonNull>, which
+guarantees that the type-checker will issue a warning for every client
+use that has the potential to cause an exception.  Don't write
+\<@Nullable> on the parameter just because there exist some executions that
+don't necessarily throw an exception.
+
+% (The note at
+% http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/base/Preconditions.html
+% argues that the parameter could be marked as @Nullable, since it is
+% possible for null to flow there at run time.  However, since that is an
+% erroneous case, the annotation would be counterproductive rather than
+% useful.)
+
+
+\subsectionAndLabel{Subclasses must respect superclass annotations}{annotations-are-a-contract}
+
+An annotation indicates a guarantee that a client can depend upon.  A subclass
+is not permitted to \emph{weaken} the contract; for example,
+if a method accepts \code{null} as an argument, then every overriding
+definition must also accept \code{null}.
+A subclass is permitted to \emph{strengthen} the contract; for example,
+if a method does \emph{not} accept \code{null} as an argument, then an
+overriding definition is permitted to accept \code{null}.
+
+%% TODO: Revise when @NullableWhen or @NonNullWhen is implemented
+\begin{sloppypar}
+As a bad example, consider an erroneous \code{@Nullable} annotation in
+\href{https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Multiset.java\#L129}{\code{com/google/common/collect/Multiset.java}}:
+\end{sloppypar}
+
+\begin{Verbatim}
+101  public interface Multiset<E> extends Collection<E> {
+...
+122    /**
+123     * Adds a number of occurrences of an element to this multiset.
+...
+129     * @param element the element to add occurrences of; may be {@code null} only
+130     *     if explicitly allowed by the implementation
+...
+137     * @throws NullPointerException if {@code element} is null and this
+138     *     implementation does not permit null elements. Note that if {@code
+139     *     occurrences} is zero, the implementation may opt to return normally.
+140     */
+141    int add(@Nullable E element, int occurrences);
+\end{Verbatim}
+
+There exist implementations of Multiset that permit \code{null} elements,
+and implementations of Multiset that do not permit \code{null} elements.  A
+client with a variable \code{Multiset ms} does not know which variety of
+Multiset \code{ms} refers to.  However, the \code{@Nullable} annotation
+promises that \code{ms.add(null, 1)} is permissible.  (Recall from
+Section~\ref{annotate-normal-behavior} that annotations should indicate
+normal behavior.)
+
+If parameter \code{element} on line 141 were to be annotated, the correct
+annotation would be \code{@NonNull}.  Suppose a client has a reference to
+same Multiset \code{ms}.  The only way the client can be sure not to throw an exception is to pass
+only non-\code{null} elements to \code{ms.add()}.  A particular class
+that implements Multiset could declare \code{add} to take a
+\code{@Nullable} parameter.  That still satisfies the original contract.
+It strengthens the contract by promising even more:  a client with such a
+reference can pass any non-\code{null} value to \code{add()}, and may also
+pass \code{null}.
+
+\textbf{However}, the best annotation for line 141 is no annotation at all.
+The reason is that each implementation of the Multiset interface should
+specify its own nullness properties when it specifies the type parameter
+for Multiset.  For example, two clients could be written as
+
+\begin{Verbatim}
+  class MyNullPermittingMultiset implements Multiset<@Nullable Object> { ... }
+  class MyNullProhibitingMultiset implements Multiset<@NonNull Object> { ... }
+\end{Verbatim}
+
+\noindent
+or, more generally, as
+
+\begin{Verbatim}
+  class MyNullPermittingMultiset<E extends @Nullable Object> implements Multiset<E> { ... }
+  class MyNullProhibitingMultiset<E extends @NonNull Object> implements Multiset<E> { ... }
+\end{Verbatim}
+
+Then, the specification is more informative, and the Checker Framework is
+able to do more precise checking, than if line 141 has an annotation.
+
+It is a pleasant feature of the Checker Framework that in many cases, no
+annotations at all are needed on type parameters such as \code{E} in \<MultiSet>.
+
+
+\subsectionAndLabel{What to do if a checker issues a warning about your code}{handling-warnings}
+
+When you run a type-checker on your code, it is likely to issue warnings or
+errors.  Don't panic!
+If you have trouble understanding a Checker Framework warning message, you
+can search for its text in this manual.
+There are three general causes for the warnings:
+
+\begin{description}
+\item[You found a bug]
+  There is a bug in your code, such as a possible null dereference.  Fix
+  your code to prevent that crash.
+
+\item[Wrong annotations]
+  The annotations are too strong (they are incorrect) or too weak (they
+  are imprecise).  Improve the
+  annotations, usually by writing more annotations in order to better
+  express the specification.
+  Only write annotations that accurately describe the intended behavior of
+  the software --- don't write inaccurate annotations just for the purpose
+  of eliminating type-checker warnings.
+
+  Usually you need to improve the annotations in your source code.
+  Sometimes you need to improve annotations in a library that your program
+  uses (see \chapterpageref{annotating-libraries}).
+
+\item[Type-checker weakness]
+  There is a weakness in the type-checker.  Your code is safe --- it never
+  suffers the error at run time --- but the checker cannot prove this fact.
+  (The checker is not omniscient, and it works modularly:  when type-checking a
+  method \<m>, it relies on the types, but not the code, of variables and
+  methods used by \<m>.)
+
+  If possible, rewrite your code to be simpler for the checker to analyze;
+  this is likely to make it easier for people to understand, too.
+  If that is not possible, suppress the warning (see
+  \chapterpageref{suppressing-warnings}); be sure to include a code
+  comment explaining how you know the code is correct even though the
+  type-checker cannot deduce that fact.
+
+  (Do not add an \<if> test that can never fail, just to suppress a
+  warning.  Adding a gratuitous \<if> clutters the code and confuses
+  readers.  A reader should assume that every \<if> condition can evaluate to true
+  or false.  There is one exception to this rule:  an \<if> test may have a
+  condition that you think will never evaluate to true, if its body just throws a
+  descriptive error message.)
+\end{description}
+
+For each warning issued by the checker, you need to determine which of the
+above categories it falls into.  Here is an effective methodology to do so.
+It relies mostly on manual code examination, but you may also find it
+useful to write test cases for your code or do other kinds of analysis, to
+verify your reasoning.
+(Also see
+Section~\ref{common-problems-typechecking} and
+Chapter~\ref{troubleshooting}, Troubleshooting.
+In particular, Section~\ref{common-problems-typechecking} explains this
+same methodology in different words.)
+
+
+\paragraphAndLabel{Step 1: Explain correctness:  write a proof}{handling-warnings-step1}
+Write an explanation of why your code is correct and it
+never suffers the error at run time.  In other words, this is an informal proof
+that the type-checker's warning is incorrect.  Write it in natural language
+(e.g., English).
+
+Don't skip any steps in your proof.
+(For example, don't write an unsubstantiated claim such as ``\<x> is
+non-null here''; instead, give a justification.)
+Don't let your reasoning rely on
+facts that you do not write down explicitly.  For example, remember that
+calling a method might change the values of object fields; your proof
+might need to state that certain methods have no side effects.
+
+If you cannot write a proof, then there is a bug
+in your code (you should fix the bug) or your code is too complex for you
+to understand (you should improve its documentation and/or design).
+
+
+\paragraphAndLabel{Step 2: Translate the proof into annotations.}{handling-warnings-step2}
+Here are some examples of the translation.
+
+\begin{itemize}
+\item
+  If your proof includes ``variable \<x> is never \<null>
+  at run time'', then annotate \<x>'s type with
+  \refqualclass{checker/nullness/qual}{NonNull}.
+\item
+  If your proof
+  includes ``method \<foo> always returns a legal regular expression'',
+  then annotate \<foo>'s return type with
+  \refqualclass{checker/regex/qual}{Regex}.
+\item
+  If your proof includes ``if method \<join>'s first argument is
+  non-null, then \<join> returns a non-null result'', then annotate
+  \<join>'s first parameter and return type with
+  \refqualclass{checker/nullness/qual}{PolyNull}.
+\item
+  If your proof includes ``method \<processOptions> has already been called and it
+  set field \<tz1>'', then annotate \<processOptions>'s declaration with
+  \refqualclasswithparams{checker/nullness/qual}{EnsuresNonNull}{"tz1"}.
+\item
+  If your proof includes ``method \<isEmpty> returned false, so its
+  argument must have been non-null'', then annotate \<isEmpty>'s
+  declaration with
+  \refqualclasswithparams{checker/nullness/qual}{EnsuresNonNullIf}{expression="\#1",result=false}.
+\item
+  If your proof includes ``method \<m> has no side effects'',
+  then annotate \<m>'s declaration with
+  \refqualclass{dataflow/qual}{SideEffectFree}.
+\item
+  If your proof includes ``each call to method \<m> returns the same value'',
+  then annotate \<m>'s declaration with
+  \refqualclass{dataflow/qual}{Deterministic}.
+\end{itemize}
+
+All of these are examples of correcting weaknesses in the annotations you wrote.
+The Checker Framework provides many other powerful annotations; you may
+be surprised how many proofs you can express in annotations.
+If you need to annotate a method that is defined in a
+library that your code uses, see \chapterpageref{annotating-libraries}.
+
+Don't omit any parts of your proof.  When the Checker Framework analyzes
+a method, it examines only the specifications (not the implementations)
+of other methods.
+
+If there are complex facts in your proof that cannot be expressed as
+annotations, then that is a weakness in the type-checker.  For example,
+the Nullness Checker cannot express ``in list \<lst>, elements stored at
+even indices are always non-\<null>, but elements stored at odd elements
+might be \<null>.''  In this case, you have two choices.
+%
+First, you can suppress the warning
+(\chapterpageref{suppressing-warnings}); be sure to write a comment
+explaining your reasoning for suppressing the warning.  You may wish to
+submit a feature request (Section~\ref{reporting-bugs}) asking for
+annotations that handle your use case.
+%
+Second, you can rewrite the code to make the proof simpler;
+in the above example, it might be better to use a list of pairs
+rather than a heterogeneous list.
+
+\paragraphAndLabel{Step 3: Re-run the checker}{handling-warnings-step3}
+At this point, all the steps in your proof have been formalized as
+annotations.  Re-run the checker and repeat the process for any new or
+remaining warnings.
+
+If every step of your proof can be expressed in annotations, but the
+checker cannot make one of the deductions (it cannot follow one of the
+steps), then that is a weakness in the type-checker.  First, double-check
+your reasoning.  Then, suppress the warning, along with a comment
+explaining your reasoning (\chapterpageref{suppressing-warnings}).
+The comment is an excerpt from your informal proof, and the proof guides
+you to the best place to suppress the warning.
+Please submit a bug report so that the checker can be improved
+in the future (Section~\ref{reporting-bugs}).
+
+
+
+
+\subsectionAndLabel{Calls to unannotated code (legacy libraries)}{unannotated-code}
+
+Sometimes, you wish to type-check only part of your program.
+You might focus on the most mission-critical or error-prone part of your
+code.  When you start to use a checker, you may not wish to annotate
+your entire program right away.
+% Not having source code is *not* a reason.
+You may not have
+enough knowledge to annotate poorly-documented libraries that your program uses.
+Or, the code you are annotating may call into unannotated libraries.
+
+If annotated code uses unannotated code, then the checker may issue
+warnings.  For example, the Nullness Checker (Chapter~\ref{nullness-checker}) will
+warn whenever an unannotated method result is used in a non-null context:
+
+\begin{Verbatim}
+  @NonNull myvar = unannotated_method();   // WARNING: unannotated_method may return null
+\end{Verbatim}
+
+If the call \emph{can} return null, you should fix the bug in your program by
+removing the \refqualclass{checker/nullness/qual}{NonNull} annotation in your own program.
+
+If the call \emph{never} returns null, you have two choices:  annotate the library
+or suppress warnings.
+\begin{enumerate}
+\item To annotate the library:
+  \begin{itemize}
+  \item
+    If the unannotated code is in your program, you can write annotations
+    but not type-check them yet.  Two ways to prevent the type-checking are
+    via a \code{@SuppressWarnings} annotation
+    (Section~\ref{suppresswarnings-annotation}) or by not running the
+    checker on that file, for example via the \<-AskipDefs> command-line
+    option (Section~\ref{askipdefs}).
+  \item
+    To annotate a library whose source code you do not have or cannot
+    change, see Chapter~\ref{annotating-libraries}.
+  \end{itemize}
+\item To suppress all warnings related to uses of
+  \code{unannotated\_method}, use the \code{-AskipUses} command-line option
+  (see Section~\ref{askipuses}).  Beware:  a carelessly-written regular
+  expression may suppress more warnings than you intend.
+\end{enumerate}
+
+
+% LocalWords:  NonNull zipfile processor classfiles annotationname javac htoc
+% LocalWords:  SuppressWarnings un skipUses java plugins plugin TODO cp io
+% LocalWords:  nonnull langtools sourcepath classpath OpenJDK pre jsr lst
+% LocalWords:  Djsr qual Alint javac's dotequals nullable supertype JLS Papi
+% LocalWords:  deserialization Mahmood Telmo Correa changelog txt nullness ESC
+% LocalWords:  Nullness unselect checkbox unsetting PolyNull typedefs arg
+% LocalWords:  bashrc IDE xml buildfile PolymorphicQualifier enum API elts INF
+% LocalWords:  type-checker proc discoverable Xlint util QualifierDefaults Foo
+% LocalWords:  DefaultQualifier SoyLatte GetStarted Formatter bcel csv sbt
+% LocalWords:  Dcheckers Warski MyClass ProcessorName compareTo toString myDate
+% LocalWords:  int XDTA newdir Awarns signedness urgin bytecodes gradle m1
+% LocalWords:  subpackages bak tIDE Multiset NullPointerException AskipUses
+% LocalWords:  html JCIP MultiSet Astubs Afilenames Anomsgtext Ashowchecks tex
+% LocalWords:  Aquals processorpath regex RegEx Xmaxwarns com astub jaifs
+% LocalWords:  IntelliJ assertNotNull checkNotNull Goetz antipattern subclassed
+% LocalWords:  callees Xmx unconfuse fenum propkey forName
+% LocalWords:  bootclasspath AonlyUses AskipDefs AonlyDefs AcheckPurityAnnotations
+% LocalWords:  AsuppressWarnings AassumeSideEffectFree Adetailedmsgtext m2
+% LocalWords:  AignoreRawTypeArguments AsuggestPureMethods ApermitMissingJdk
+% LocalWords:  AassumeAssertionsAreEnabled AassumeAssertionsAreDisabled
+% LocalWords:  AconcurrentSemantics AstubWarnIfNotFound AnoPrintErrorStack
+% LocalWords:  AprintAllQualifiers Aignorejdkastub AstubDebug Aflowdotdir
+% LocalWords:  AresourceStats jls r78 JDKs i18n AignoreRangeOverflow L129
+% LocalWords:  AinvariantArrays AcheckCastElementType formatter pathname
+% LocalWords:  typedef guieffect Gradle jdk8 javadoc MyFile argfiles tz1
+% LocalWords:  AshowSuppressWarningsStrings AoutputArgsToFile RegexChecker
+% LocalWords:  NullnessChecker commandlineargfile AnnotatedFor Xmx2500m
+% LocalWords:  AsafeDefaultsForUnannotatedBytecode Signedness Werror jaif
+% LocalWords:  AuseSafeDefaultsForUnannotatedSourceCode beingConstructed
+% LocalWords:  AuseConservativeDefaultsForUncheckedCode AresolveReflection Ainfer
+% LocalWords:  AconservativeUninferredTypeArguments Averbosecfg Acfgviz
+% LocalWords:  AstubWarnIfOverwritesBytecode AprintVerboseGenerics here''
+% LocalWords:  AatfDoNotCache AatfCacheSize IntRange AwarnIfNotFound ajava
+% LocalWords:  AwarnUnneededSuppressions AshowInferenceSteps BHCJEIBB
+% LocalWords:  AstubWarnIfNotFoundIgnoresClasses processOptions getopt
+% LocalWords:  EnsuresNonNull EnsuresNonNullIf checkername orElseThrow
+% LocalWords:  ArequirePrefixInWarningSuppressions MaybePresent checker''
+% LocalWords:  AignoreInvalidAnnotationLocations AprintGitProperties
+% LocalWords:  AstubWarnIfRedundantWithBytecode annotation'' AassumePure
+% LocalWords:  AassumeDeterministic stubfilename outputformat AparseAllJdk
+% LocalWords:  AmergeStubsWithSource MyBatis AdumpOnErrors AutoValue
+% LocalWords:  specification'' AwarnUnneededSuppressionsExceptions
+% LocalWords:  requireNonNull
diff --git a/docs/manual/lock-checker.tex b/docs/manual/lock-checker.tex
new file mode 100644
index 0000000..f53d33e
--- /dev/null
+++ b/docs/manual/lock-checker.tex
@@ -0,0 +1,996 @@
+\htmlhr
+\chapterAndLabel{Lock Checker}{lock-checker}
+
+The Lock Checker prevents certain concurrency errors by enforcing a
+locking discipline.  A locking discipline indicates which locks must be held
+when a given operation occurs.  You express the locking discipline by
+declaring a variable's type to have the qualifier
+\<\refqualclass{checker/lock/qual}{GuardedBy}{\small("\emph{lockexpr}")}>.
+This indicates that the variable's value may
+be dereferenced only if the given lock is held.
+
+
+To run the Lock Checker, supply the
+\code{-processor org.checkerframework.checker.lock.LockChecker}
+command-line option to javac.  The \<-AconcurrentSemantics>
+command-line option is always enabled for the Lock Checker (see Section~\ref{faq-concurrency}).
+
+
+\sectionAndLabel{What the Lock Checker guarantees}{lock-guarantees}
+
+The Lock Checker gives the following guarantee.
+Suppose that expression $e$ has type
+\<\refqualclass{checker/lock/qual}{GuardedBy}(\ttlcb"x", "y.z"\ttrcb)>.
+Then the value computed for $e$ is only dereferenced by a thread when the
+thread holds locks \<x> and \<y.z>.
+Dereferencing a value is reading or writing one of its fields.
+The guarantee about $e$'s value
+holds not only if the expression $e$ is dereferenced
+directly, but also if the value was first copied into a variable,
+returned as the
+result of a method call, etc.
+Copying a reference is always
+permitted by the Lock Checker, regardless of which locks are held.
+
+A lock is held if it has been acquired but not yet released.
+Java has two types of locks.
+A monitor lock is acquired upon entry to a \<synchronized> method or block,
+and is released on exit from that method or block.
+%  (More precisely,
+%  the current thread locks the monitor associated with the value of
+%  \emph{E}; see \href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.1}{JLS \S17.1}.)
+An explicit lock is acquired by a method call such as
+\sunjavadoc{java.base/java/util/concurrent/locks/Lock.html\#lock()}{Lock.lock()},
+and is released by another method call such as
+\sunjavadoc{java.base/java/util/concurrent/locks/Lock.html\#unlock()}{Lock.unlock()}.
+The Lock Checker enforces that any expression whose type implements
+\sunjavadoc{java.base/java/util/concurrent/locks/Lock.html}{Lock} is used as an
+explicit lock, and all other expressions are used as monitor locks.
+% The class that implements the Lock interface could itself use the
+% current object as a monitor lock.  This doesn't seem like it
+% needs to be mentioned here, though.
+
+Ensuring that your program obeys its locking discipline is an easy and
+effective way to eliminate a common and important class of errors.
+If the Lock Checker issues no warnings, then your program obeys its locking discipline.
+However, your program might still have other types of concurrency errors.
+%
+For example, you might have specified an inadequate locking discipline
+because you forgot some \refqualclass{checker/lock/qual}{GuardedBy}
+annotations.
+%
+Your program might release and
+re-acquire the lock, when correctness requires it to hold it throughout a
+computation.
+%
+And, there are other concurrency errors that cannot, or
+should not, be solved with locks.
+
+\sectionAndLabel{Lock annotations}{lock-annotations}
+
+This section describes the lock annotations you can write on types and methods.
+
+
+\subsectionAndLabel{Type qualifiers}{lock-type-qualifiers}
+
+\begin{description}
+
+\item[\refqualclass{checker/lock/qual}{GuardedBy}{\small(\emph{exprSet})}]
+  If a variable \<x> has type \<@GuardedBy("\emph{expr}")>, then a thread may
+  dereference the value referred to by \<x> only when the thread holds the
+  lock that \emph{expr} currently evaluates to.
+
+  The \<@GuardedBy> annotation can list multiple expressions, as in
+  \<@GuardedBy(\ttlcb"\emph{expr1}", "\emph{expr2}"\ttrcb)>, in which case
+  the dereference is
+  permitted only if the thread holds all the locks.
+
+  Section~\ref{java-expressions-as-arguments} explains which
+  expressions the Lock Checker is able to analyze as lock expressions.
+  These include \code{<self>}, the value of the annotated reference
+  (non-primitive) variable.  For example, \code{@GuardedBy("<self>") Object o}
+  indicates that the value referenced by \<o> is guarded by the intrinsic
+  (monitor) lock of the value referenced by \<o>.
+
+  \<@GuardedBy(\{\})>, which means the value is always allowed to be
+  dereferenced, is the default type qualifier that is used for all locations
+  where the programmer does not
+  write an explicit locking type qualifier (except all CLIMB-to-top locations
+  other than upper bounds and exception parameters --- see Section~\ref{climb-to-top}).
+  (Section~\ref{lock-checker-default-qualifier} discusses this choice.)
+  It is also the conservative
+  default type qualifier for method parameters in unannotated libraries
+  (see \chapterpageref{annotating-libraries}).
+
+\item[\refqualclass{checker/lock/qual}{GuardedByUnknown}]
+  If a variable \<x> has type \code{@GuardedByUnknown}, then
+  it is not known which locks protect \<x>'s value.  Those locks might
+  even be out of scope (inaccessible) and therefore unable to be written
+  in the annotation.
+  The practical consequence is that
+  the value referred to by \<x> can never be dereferenced.
+
+  Any value can be assigned to a variable of type
+  \code{@GuardedByUnknown}.  In particular, if it is written on a
+  formal parameter, then any value,
+  including one whose locks are not currently held,
+  may be passed as an argument.
+
+  \<@GuardedByUnknown> is the conservative
+  default type qualifier for method receivers in unannotated libraries
+  (see \chapterpageref{annotating-libraries}).
+
+\item[\refqualclass{checker/lock/qual}{GuardedByBottom}]
+  If a variable \<x> has type \code{@GuardedByBottom}, then
+  the value referred to by \<x> is \code{null} and can never
+  be dereferenced.
+
+\end{description}
+
+\begin{figure}
+\includeimage{lock-guardedby}{3cm}
+\caption{The subtyping relationship of the Lock Checker's qualifiers.
+\code{@GuardedBy(\{\})} is the default type qualifier for unannotated
+types (except all CLIMB-to-top locations other than upper bounds and exception
+parameters --- see Section~\ref{climb-to-top}).
+}
+\label{fig-lock-guardedby-hierarchy}
+\end{figure}
+
+Figure~\ref{fig-lock-guardedby-hierarchy} shows the type hierarchy of these
+qualifiers.
+All \code{@GuardedBy} annotations are incomparable:
+if \emph{exprSet1} $\neq$ \emph{exprSet2}, then \code{@GuardedBy(\emph{exprSet1})} and
+\code{@GuardedBy(\emph{exprSet2})} are siblings in the type hierarchy.
+You might expect that
+\<@GuardedBy(\{"x", "y"\}) T> is a subtype of \<@GuardedBy(\{"x"\}) T>.  The
+first type requires two locks to be held, and the second requires only one
+lock to be held and so could be used in any situation where both locks are
+held.  The type system conservatively prohibits this in order to prevent
+type-checking loopholes that would result from aliasing and side effects
+--- that is, from having two mutable references, of different types, to the
+same data. See
+Section~\ref{lock-guardedby-invariant-subtyping} for an example
+of a problem that would occur if this rule were relaxed.
+
+
+\paragraphAndLabel{Polymorphic type qualifiers}{lock-polymorphic-type-qualifiers}
+
+%\refqualclass{checker/lock/qual}{GuardSatisfied}{\small(\emph{index})}
+%and
+%\refqualclass{checker/interning/qual}{PolyGuardedBy}
+%indicates qualifier polymorphism.  For a description of qualifier
+%polymorphism, see Section~\ref{method-qualifier-polymorphism}.
+
+\begin{description}
+
+\item[\refqualclass{checker/lock/qual}{GuardSatisfied}{\small(\emph{index})}]
+  If a variable \<x> has type \code{@GuardSatisfied}, then all
+  lock expressions for \<x>'s value are held.
+
+  As with other qualifier-polymorphism annotations
+  (Section~\ref{method-qualifier-polymorphism}), the \emph{index} argument
+  indicates when two values are guarded by the same (unknown) set of locks.
+
+  \code{@GuardSatisfied} is only allowed in method signatures:  on
+  formal parameters (including the receiver) and return types.
+  It may not be written on fields.  Also, it is a limitation of the
+  current design that \code{@GuardSatisfied} may not be written on
+  array elements or on local variables.
+
+  A return type can only be annotated with \<@GuardSatisfied(index)>,
+  not \<@GuardSatisfied>.
+
+  See Section~\ref{lock-checker-polymorphism-example}
+  for an example of a use of \code{@GuardSatisfied}.
+
+%\item[\refqualclass{checker/interning/qual}{PolyGuardedBy}]
+%  It is unknown what the guards are or whether they are held.
+%  An expression whose type is \code{@PolyGuardedBy}
+%  cannot be dereferenced.
+
+\end{description}
+
+
+\subsectionAndLabel{Declaration annotations}{lock-declaration-annotations}
+
+The Lock Checker supports several annotations that specify method behavior.
+These are declaration annotations, not type annotations: they apply to the
+method itself rather than to some particular type.
+
+\paragraphAndLabel{Method pre-conditions and post-conditions}{lock-method-pre-post-conditions}
+
+\begin{sloppypar}
+\begin{description}
+\item[\refqualclass{checker/lock/qual}{Holding}\small{(String[] locks)}]
+  All the given lock expressions
+  are held at the method call site.
+
+\item[\refqualclass{checker/lock/qual}{EnsuresLockHeld}\small{(String[] locks)}]
+  The given lock
+  expressions are
+  locked upon method return if the method
+  terminates successfully.  This is useful for annotating a
+  method that acquires a lock such as
+  \sunjavadoc{java.base/java/util/concurrent/locks/ReentrantLock.html\#lock()}{ReentrantLock.lock()}.
+
+\item[\refqualclass{checker/lock/qual}{EnsuresLockHeldIf}\small{(String[] locks, boolean result)}]
+  If the annotated method returns the given
+  boolean value (true or false), the given lock
+  expressions are locked upon method return if the method
+  terminates successfully.
+  This is useful for annotating a
+  method that conditionally acquires a lock.
+  See Section~\ref{ensureslockheld-examples} for examples.
+
+\end{description}
+
+\paragraphAndLabel{Side effect specifications}{lock-side-effect-specifications}
+
+\begin{description}
+
+\item[\refqualclass{checker/lock/qual}{LockingFree}]
+  The method does not acquire or release locks,
+  directly or indirectly.  The method is not \<synchronized>, it contains
+  no \<synchronized> blocks, it contains no calls to \<lock> or \<unlock>
+  methods, and it contains no calls to methods that are not themselves \<@LockingFree>.
+
+  Since
+  \code{@SideEffectFree} implies \code{@LockingFree}, if both are applicable
+  then you only need to write \code{@SideEffectFree}.
+
+\item[\refqualclass{checker/lock/qual}{ReleasesNoLocks}]
+  The method maintains a strictly nondecreasing lock hold count on the
+  current thread for any locks that were held prior
+  to the method call.  The method might acquire locks but then release
+  them, or might acquire locks but not release them (in which case it should
+  also be annotated with
+  \refqualclass{checker/lock/qual}{EnsuresLockHeld} or
+  \refqualclass{checker/lock/qual}{EnsuresLockHeldIf}).
+
+  This is the default for methods being type-checked that have no \<@LockingFree>,
+  \<@MayReleaseLocks>, \code{@SideEffectFree}, or \code{@Pure}
+  annotation.
+
+\item[\refqualclass{checker/lock/qual}{MayReleaseLocks}]
+  The method may release locks that were held prior to the method being called.
+  You can write this when you are certain the method releases locks, or
+  when you don't know whether the method releases locks.
+  This is the conservative default for methods in unannotated libraries (see \chapterpageref{annotating-libraries}).
+
+\end{description}
+\end{sloppypar}
+
+
+\sectionAndLabel{Type-checking rules}{lock-type-checking-rules}
+
+In addition to the standard subtyping rules enforcing the subtyping relationship
+described in Figure~\ref{fig-lock-guardedby-hierarchy}, the Lock Checker enforces
+the following additional rules.
+
+
+\subsectionAndLabel{Polymorphic qualifiers}{lock-type-checking-rules-polymorphic-qualifiers}
+
+\begin{description}
+
+\item[\code{@GuardSatisfied}]
+
+  The overall rules for polymorphic qualifiers are given in
+  Section~\ref{method-qualifier-polymorphism}.
+
+  Here are additional constraints for (pseudo-)assignments:
+
+  \begin{itemize}
+  \item
+    If the left-hand side has type \<@GuardSatisfied> (with or without an index),
+    then all locks mentioned in the right-hand side's \<@GuardedBy> type
+    must be currently held.
+  \item
+    A formal parameter with type qualifier \<@GuardSatisfied> without an
+    index cannot be assigned to.
+  \item
+    If the left-hand side is a formal parameter with type
+    \<@GuardSatisfied(\emph{index})>, the right-hand-side must have
+    identical \<@GuardSatisfied(\emph{index})> type.
+  \end{itemize}
+
+  If a formal parameter type is
+  annotated with \<@GuardSatisfied> without an index, then that formal parameter
+  type is unrelated to every other type in the \<@GuardedBy> hierarchy,
+  including other occurrences of \<@GuardSatisfied> without an index.
+
+  \<@GuardSatisfied> may not be used on formal parameters, receivers, or
+  return types of a method annotated with \<@MayReleaseLocks>.
+\end{description}
+
+\subsectionAndLabel{Dereferences}{lock-type-checking-rules-dereferences}
+
+\begin{description}
+
+\item[\code{@GuardedBy}]
+  An expression of type \<@GuardedBy(\emph{eset})> may be dereferenced only
+  if all locks in \emph{eset} are held.
+
+\item[\code{@GuardSatisfied}]
+  An expression of type \<@GuardSatisfied> may be dereferenced.
+
+\item[Not \code{@GuardedBy} or \code{@GuardSatisfied}]
+  An expression whose type is not annotated with \code{@GuardedBy} or
+  \code{@GuardSatisfied} may not be dereferenced.
+%  In particular, an expression of type \code{@PolyGuardedBy} may not be dereferenced.
+
+\end{description}
+
+\subsectionAndLabel{Primitive types, boxed primitive types, and Strings}{lock-type-checking-rules-primitives}
+
+Primitive types, boxed primitive types (such as \<java.lang.Integer>), and type \<java.lang.String>
+are annotated with \<@GuardedBy(\{\})>.
+It is an error for the programmer to annotate any of these types with an annotation from
+the \<@GuardedBy> type hierarchy, including \<@GuardedBy(\{\})>.
+
+%  Primitive values are not guarded.  Instead, the variables that store them are.
+%  Therefore, for reads, writes and other operations on primitive values, the Lock Checker requires that
+%  the appropriate locks be held, but it does not enforce any other rules.
+%  In particular, it does not require the annotations
+%  in the types involved in the operation (including assignments and
+%  pseudo-assignments) to match.  For example, given:
+%  \begin{verbatim}
+%  ReentrantLock lock1, lock2;
+%  @GuardedBy("lock1") int a;
+%  @GuardedBy("lock2") int b;
+%  @GuardedBy({}) int c;
+%  ...
+%  lock1.lock();
+%  lock2.lock();
+%  a = b;
+%  a = c;
+%  a = b + c;
+%  \end{verbatim}
+%  The expressions \code{a = b}, \code{a = c}, and \code{a = b + c}
+%  all type check, whereas none of them would type check if \code{a},
+%  \code{b} and \code{c} were not primitives.
+
+\subsectionAndLabel{Overriding}{lock-type-checking-rules-overriding}
+
+\begin{description}
+
+\item[Overriding methods annotated with \code{@Holding}]
+  If class $B$ overrides method $m$ from class $A$, then the expressions in
+  $B$'s \<@Holding>
+  annotation must be a subset of or equal to that of $A$'s \<@Holding>
+  annotation.
+
+\item[Overriding methods annotated with side effect annotations]
+  If class $B$ overrides method $m$ from class $A$, then
+  the side effect annotation on $B$'s declaration of $m$
+  must be at least as strong as that in $A$'s declaration of $m$.
+  From weakest to strongest, the side effect annotations
+  processed by the Lock Checker are:
+\begin{verbatim}
+  @MayReleaseLocks
+  @ReleasesNoLocks
+  @LockingFree
+  @SideEffectFree
+  @Pure
+\end{verbatim}
+
+\end{description}
+
+\subsectionAndLabel{Side effects}{lock-type-checking-rules-polymorphic-side-effects}
+
+\begin{description}
+
+\item[Releasing explicit locks]
+  Any method that releases an explicit lock must be annotated
+  with \code{@MayReleaseLocks}.
+  The Lock Checker issues a warning if it encounters a method declaration
+  annotated with \code{@MayReleaseLocks} and having a formal parameter
+  or receiver annotated with \code{@GuardSatisfied}.  This is because
+  the Lock Checker cannot guarantee that the guard will be satisfied
+  throughout the body of a method if that method may release a lock.
+
+\item[No side effects on lock expressions]
+  If expression \emph{expr} is used to acquire a lock, then
+  \emph{expr} must evaluate to the same value, starting from when
+  \emph{expr} is used to acquire a lock until \emph{expr} is used to
+  release the lock.
+  An expression is used to acquire a lock if it is the receiver at a
+  call site of a \<synchronized> method, is the expression in a
+  \<synchronized> block, or is the argument to a \<lock> method.
+
+\item[Locks are released after possible side effects]
+% These are standard dataflow analysis rules, but are worth
+% repeating here due to how important they are for the day-to-day
+% use of the Lock Checker.  I believe this would be the single
+% largest source of confusion amongst Lock Checker users if this
+% were not stated explicitly.
+  After a call to a method annotated with \code{@LockingFree},
+  \code{@ReleasesNoLocks}, \code{@SideEffectFree}, or \code{@Pure},
+  the Lock Checker's estimate of held locks
+  after a method call is the same as that prior to the method call.
+  After a call to a method annotated with \code{@MayReleaseLocks},
+  the estimate of held locks is conservatively reset to the empty set,
+  except for those locks specified to be held after the call
+  by an \code{@EnsuresLockHeld} or \code{@EnsuresLockHeldIf}
+  annotation on the method.  Assignments to variables also
+  cause the estimate of held locks to be conservatively reduced
+  to a smaller set if the Checker Framework determines that the
+  assignment might have side-effected a lock expression.
+  For more information on side effects, please refer to
+  Section~\ref{type-refinement-purity}.
+
+\end{description}
+
+
+\sectionAndLabel{Examples}{lock-examples}
+
+The Lock Checker guarantees that a value that was computed from an expression of \code{@GuardedBy} type is
+dereferenced only when the current thread holds all the expressions in the
+\code{@GuardedBy} annotation.
+
+\subsectionAndLabel{Examples of @GuardedBy}{lock-examples-guardedby}
+
+The following example demonstrates the basic
+type-checking rules.
+
+\begin{Verbatim}
+class MyClass {
+  final ReentrantLock lock; // Initialized in the constructor
+
+  @GuardedBy("lock") Object x = new Object();
+  @GuardedBy("lock") Object y = x; // OK, since dereferences of y will require "lock" to be held.
+  @GuardedBy({}) Object z = x; // ILLEGAL since dereferences of z don't require "lock" to be held.
+  @GuardedBy("lock") Object myMethod() { // myMethod is implicitly annotated with @ReleasesNoLocks.
+     return x; // OK because the return type is annotated with @GuardedBy("lock")
+  }
+
+  [...]
+
+  void exampleMethod() {
+     x.toString(); // ILLEGAL because the lock is not known to be held
+     y.toString(); // ILLEGAL because the lock is not known to be held
+     myMethod().toString(); // ILLEGAL because the lock is not known to be held
+     lock.lock();
+     x.toString();  // OK: the lock is known to be held
+     y.toString();  // OK: the lock is known to be held, and toString() is annotated with @SideEffectFree.
+     myMethod().toString(); // OK: the lock is known to be held, since myMethod
+                            // is implicitly annotated with @ReleasesNoLocks.
+  }
+}
+\end{Verbatim}
+
+Note that the expression \code{new Object()} is inferred to have type \code{@GuardedBy("lock")}
+because it is immediately assigned to a newly-declared
+variable having type annotation \code{@GuardedBy("lock")}.  You could
+explicitly write \code{new @GuardedBy("lock") Object()} but it is not
+required.
+
+The following example demonstrates that using \code{<self>} as a lock expression
+allows a guarded value to be dereferenced even when the original
+variable name the value was originally assigned to falls out of scope.
+
+\begin{Verbatim}
+class MyClass {
+  private final @GuardedBy("<self>") Object x = new Object();
+  void method() {
+    x.toString(); // ILLEGAL because x is not known to be held.
+    synchronized(x) {
+      x.toString(); // OK: x is known to be held.
+    }
+  }
+
+  public @GuardedBy("<self>") Object get_x() {
+    return x; // OK, since the return type is @GuardedBy("<self>").
+  }
+}
+
+class MyOtherClass {
+  void method() {
+    MyClass m = new MyClass();
+    final @GuardedBy("<self>") Object o = m.get_x();
+    o.toString(); // ILLEGAL because o is not known to be held.
+    synchronized(o) {
+      o.toString(); // OK: o is known to be held.
+    }
+  }
+}
+\end{Verbatim}
+
+
+\subsectionAndLabel{@GuardedBy(\{``a'', ``b''\}) is not a subtype of @GuardedBy(\{``a''\})}{lock-guardedby-invariant-subtyping}
+
+
+\textbf{@GuardedBy(exprSet)}
+
+The following example demonstrates the reason the Lock Checker enforces the
+following rule:  if \emph{exprSet1} $\neq$ \emph{exprSet2}, then
+\code{@GuardedBy(\emph{exprSet1})} and \code{@GuardedBy(\emph{exprSet2})} are siblings in the type
+hierarchy.
+
+\begin{Verbatim}
+class MyClass {
+    final Object lockA = new Object();
+    final Object lockB = new Object();
+    @GuardedBy("lockA") Object x = new Object();
+    @GuardedBy({"lockA", "lockB"}) Object y = new Object();
+    void myMethod() {
+        y = x;      // ILLEGAL; if legal, later statement x.toString() would cause trouble
+        synchronized(lockA) {
+          x.toString();  // dereferences y's value without holding lock lockB
+        }
+    }
+}
+\end{Verbatim}
+
+
+If the Lock Checker permitted the assignment
+\code{y = x;}, then the undesired dereference would be possible.
+
+
+\subsectionAndLabel{Examples of @Holding}{lock-examples-holding}
+
+The following example shows the interaction between \<@GuardedBy> and
+\<@Holding>:
+
+\begin{Verbatim}
+  void helper1(@GuardedBy("myLock") Object a) {
+    a.toString(); // ILLEGAL: the lock is not held
+    synchronized(myLock) {
+      a.toString();  // OK: the lock is held
+    }
+  }
+  @Holding("myLock")
+  void helper2(@GuardedBy("myLock") Object b) {
+    b.toString(); // OK: the lock is held
+  }
+  void helper3(@GuardedBy("myLock") Object d) {
+    d.toString(); // ILLEGAL: the lock is not held
+  }
+  void myMethod2(@GuardedBy("myLock") Object e) {
+    helper1(e);  // OK to pass to another routine without holding the lock
+                 // (but helper1's body has an error)
+    e.toString(); // ILLEGAL: the lock is not held
+    synchronized (myLock) {
+      helper2(e); // OK: the lock is held
+      helper3(e); // OK, but helper3's body has an error
+    }
+  }
+\end{Verbatim}
+
+
+\subsectionAndLabel{Examples of @EnsuresLockHeld and @EnsuresLockHeldIf}{ensureslockheld-examples}
+
+\code{@EnsuresLockHeld} and \code{@EnsuresLockHeldIf} are primarily intended
+for annotating JDK locking methods, as in:
+
+\begin{Verbatim}
+package java.util.concurrent.locks;
+
+class ReentrantLock {
+
+    @EnsuresLockHeld("this")
+    public void lock();
+
+    @EnsuresLockHeldIf (expression="this", result=true)
+    public boolean tryLock();
+
+    ...
+}
+\end{Verbatim}
+
+They can also be used to annotate user methods, particularly for
+higher-level lock constructs such as a Monitor, as in this simplified example:
+
+\begin{Verbatim}
+public class Monitor {
+
+    private final ReentrantLock lock; // Initialized in the constructor
+
+    ...
+
+    @EnsuresLockHeld("lock")
+    public void enter() {
+       lock.lock();
+    }
+
+    ...
+}
+\end{Verbatim}
+
+\subsectionAndLabel{Example of @LockingFree, @ReleasesNoLocks, and @MayReleaseLocks}{lock-lockingfree-example}
+
+\code{@LockingFree} is useful when a method does not make any use of synchronization
+or locks but causes other side effects (hence \code{@SideEffectFree} is not appropriate).
+\code{@SideEffectFree} implies \code{@LockingFree}, therefore if both are applicable,
+you should only write \code{@SideEffectFree}. \code{@ReleasesNoLocks} has a weaker guarantee
+than \code{@LockingFree}, and \code{@MayReleaseLocks} provides no guarantees.
+
+\begin{verbatim}
+private Object myField;
+private final ReentrantLock lock; // Initialized in the constructor
+private @GuardedBy("lock") Object x; // Initialized in the constructor
+
+[...]
+
+// This method does not use locks or synchronization, but it cannot
+// be annotated as @SideEffectFree since it alters myField.
+@LockingFree
+void myMethod() {
+  myField = new Object();
+}
+
+@SideEffectFree
+int mySideEffectFreeMethod() {
+  return 0;
+}
+
+@MayReleaseLocks
+void myUnlockingMethod() {
+  lock.unlock();
+}
+
+@ReleasesNoLocks
+void myLockingMethod() {
+  lock.lock();
+}
+
+@MayReleaseLocks
+void clientMethod() {
+  if (lock.tryLock()) {
+    x.toString(); // OK: the lock is held
+    myMethod();
+    x.toString(); // OK: the lock is still held since myMethod is locking-free
+    mySideEffectFreeMethod();
+    x.toString(); // OK: the lock is still held since mySideEffectFreeMethod is side-effect-free
+    myUnlockingMethod();
+    x.toString(); // ILLEGAL: myUnlockingMethod may have released a lock
+  }
+  if (lock.tryLock()) {
+    x.toString(); // OK: the lock is held
+    myLockingMethod();
+    x.toString(); // OK: the lock is held
+  }
+  if (lock.isHeldByCurrentThread()) {
+    x.toString(); // OK: the lock is known to be held
+  }
+}
+\end{verbatim}
+
+
+\subsectionAndLabel{Polymorphism and method formal parameters with unknown guards}{lock-checker-polymorphism-example}
+
+The polymorphic \code{@GuardSatisfied} type annotation allows a method body
+to dereference the method's formal parameters even if the
+\code{@GuardedBy} annotations on the actual parameters are unknown at
+the method declaration site.
+
+The declaration of
+\sunjavadoc{java.base/java/lang/StringBuffer.html\#append(java.lang.String)}{StringBuffer.append(String str)}
+is annotated as:
+
+\begin{verbatim}
+@LockingFree
+public @GuardSatisfied(1) StringBuffer append(@GuardSatisfied(1) StringBuffer this,
+                                              @GuardSatisfied(2) String str)
+\end{verbatim}
+
+The method manipulates the values of its arguments, so all their locks must
+be held.  However, the declaration does not know what those are and they
+might not even be in scope at the declaration.  Therefore, the declaration
+cannot use \<@GuardedBy> and must use \<@GuardSatisfied>.  The arguments to
+\<@GuardSatisfied> indicate that the receiver and result (which are the
+same value) are guarded by the same (unknown, possibly empty) set of locks,
+and the \<str> parameter may be guarded by a different set of locks.
+
+The \code{@LockingFree} annotation indicates that
+this method makes no use of
+locks or synchronization.
+
+Given these annotations on \<append>, the following code type-checks:
+
+\begin{verbatim}
+final ReentrantLock lock1, lock2; // Initialized in the constructor
+@GuardedBy("lock1") StringBuffer filename;
+@GuardedBy("lock2") StringBuffer extension;
+...
+lock1.lock();
+lock2.lock();
+filename = filename.append(extension);
+\end{verbatim}
+% The 'filename = ' assignment is unnecessary in the example
+% and is not good Java style, but it illustrates the type-checking against the
+% return value of the call to append.
+
+
+
+
+\sectionAndLabel{More locking details}{lock-details}
+
+This section gives some details that are helpful for understanding how Java
+locking and the Lock Checker works.
+
+\subsectionAndLabel{Two types of locking:  monitor locks and explicit locks}{lock-two-types}
+
+Java provides two types of locking:  monitor locks and explicit locks.
+
+\begin{itemize}
+\item
+  A \<synchronized(\emph{E})> block acquires the lock on the value of
+  \emph{E}; similarly, a method declared using the \<synchronized> method
+  modifier acquires the lock on the method receiver when called.
+  (More precisely,
+  the current thread locks the monitor associated with the value of
+  \emph{E}; see \href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.1}{JLS \S17.1}.)
+  The lock is automatically released when execution exits the block or the
+  method body, respectively.
+  We use the term ``monitor lock'' for a lock acquired using a
+  \<synchronized>  block or \<synchronized> method modifier.
+\item A method call, such as
+  \sunjavadoc{java.base/java/util/concurrent/locks/Lock.html\#lock()}{Lock.lock()},
+  acquires a lock that implements the
+  \sunjavadoc{java.base/java/util/concurrent/locks/Lock.html}{Lock}
+  interface.
+  The lock is released by another method call, such as
+  \sunjavadoc{java.base/java/util/concurrent/locks/Lock.html\#unlock()}{Lock.unlock()}.
+  We use the term ``explicit lock'' for a lock expression acquired in this
+  way.
+\end{itemize}
+
+You should not mix the two varieties of locking, and the Lock Checker
+enforces this.  To prevent an object from being used both as a monitor and
+an explicit lock, the Lock Checker issues a warning if a
+\<synchronized(\emph{E})> block's expression \<\emph{E}> has a type that
+implements \sunjavadoc{java.base/java/util/concurrent/locks/Lock.html}{Lock}.
+% The Lock Checker does not keep track of which locks are monitors
+% and which are explicit, so this check is necessary for the Lock Checker to
+% function correctly, and it also alerts the programmer of a code smell.
+
+
+\subsectionAndLabel{Held locks and held expressions; aliasing}{lock-aliasing}
+
+Whereas Java locking is defined in terms of values, Java programs are
+written in terms of expressions.
+We say that a lock expression is held if the value to which the expression
+currently evaluates is held.
+
+The Lock Checker conservatively estimates the expressions that are held at
+each point in a program.
+The Lock Checker does not track aliasing
+(different expressions that evaluate to the same value); it only considers
+the exact expression used to acquire a lock to be held.  After any statement
+that might side-effect a held expression or a lock expression, the Lock
+Checker conservatively considers the expression to be no longer held.
+
+Section~\ref{java-expressions-as-arguments} explains which Java
+expressions the Lock Checker is able to analyze as lock expressions.
+
+
+The \code{@LockHeld} and \code{@LockPossiblyHeld} type qualifiers are used internally by the Lock Checker
+and should never be written by the programmer.
+If you
+see a warning mentioning \code{@LockHeld} or \code{@LockPossiblyHeld},
+please contact the Checker Framework developers as it is likely to
+indicate a bug in the Checker Framework.
+
+
+\subsectionAndLabel{Run-time checks for locking}{lock-runtime-checks}
+
+When you perform a run-time check for locking, such as
+\<if (explicitLock.isHeldByCurrentThread())\{...\}> or
+\<if (Thread.holdsLock(monitorLock))\{...\}>,
+then the Lock Checker considers the lock expression to be held
+within the scope of the test.  For more details, see
+Section~\ref{type-refinement}.
+% Note that the java.util.concurrent.locks.Lock interface does not include
+% a run-time test, but ReentrantLock does.
+
+
+\subsectionAndLabel{Discussion of default qualifier}{lock-checker-default-qualifier}
+
+The default qualifier for unannotated types is \<@GuardedBy(\{\})>.
+This default forces you to write explicit \<@GuardSatisfied> in method
+signatures in the common case that clients ensure that all locks are held.
+
+It might seem that \<@GuardSatisfied> would be a better default for
+method signatures, but such a default would require even more annotations.
+The reason is that \<@GuardSatisfied> cannot be used on fields.  If
+\<@GuardedBy(\{\})> is the default for fields but \<@GuardSatisfied> is the
+default for parameters and return types, then getters, setters, and many
+other types of methods do not type-check without explicit lock qualifiers.
+
+
+\subsectionAndLabel{Discussion of \<@Holding>}{lock-checker-holding}
+
+A programmer might choose to use the \code{@Holding} method annotation in
+two different ways:  to specify correctness constraints for a
+synchronization protocol, or to summarize intended usage.  Both of these
+approaches are useful, and the Lock Checker supports both.
+
+\paragraphAndLabel{Synchronization protocol}{lock-checker-holding-synchronization-protocol}
+
+  \code{@Holding} can specify a synchronization protocol that
+  is not expressible as locks over the parameters to a method.  For example, a global lock
+  or a lock on a different object might need to be held.  By requiring locks to be
+  held, you can create protocol primitives without giving up
+  the benefits of the annotations and checking of them.
+
+\paragraphAndLabel{Method summary that simplifies reasoning}{lock-checker-holding-method-summary}
+
+  \code{@Holding} can be a method summary that simplifies reasoning.  In
+  this case, the \code{@Holding} doesn't necessarily introduce a new
+  correctness constraint; the program might be correct even if the lock
+  were not already acquired.
+
+  Rather, here \code{@Holding} expresses a fact about execution:  when
+  execution reaches this point, the following locks are known to be already held.  This
+  fact enables people and tools to reason intra- rather than
+  inter-procedurally.
+
+  In Java, it is always legal to re-acquire a lock that is already held,
+  and the re-acquisition always works.  Thus, whenever you write
+
+\begin{Verbatim}
+  @Holding("myLock")
+  void myMethod() {
+    ...
+  }
+\end{Verbatim}
+
+\noindent
+it would be equivalent, from the point of view of which locks are held
+during the body, to write
+
+\begin{Verbatim}
+  void myMethod() {
+    synchronized (myLock) {   // no-op:  re-acquire a lock that is already held
+      ...
+    }
+  }
+\end{Verbatim}
+
+
+It is better to write a \code{@Holding} annotation rather than writing the
+extra synchronized block.  Here are reasons:
+
+\begin{itemize}
+\item
+  The annotation documents the fact that the lock is intended to already be
+  held;  that is, the method's contract requires that the lock be held when
+  the method is called.
+\item
+  The Lock Checker enforces that the lock is held when the method is
+  called, rather than masking a programmer error by silently re-acquiring
+  the lock.
+\item
+  The version with a synchronized statement can deadlock if, due to a programmer error,
+  the lock is not already held.  The Lock Checker prevents this type of
+  error.
+\item
+  The annotation has no run-time overhead.  The lock re-acquisition
+  consumes time, even if it succeeds.
+\end{itemize}
+
+
+\sectionAndLabel{Other lock annotations}{lock-other-annotations}
+
+The Checker Framework's lock annotations are similar to annotations used
+elsewhere.
+
+If your code is already annotated with a different lock
+annotation, the Checker Framework can type-check your code.
+It treats annotations from other tools
+as if you had written the corresponding annotation from the
+Lock Checker, as described in Figure~\ref{fig-lock-refactoring}.
+If the other annotation is a declaration annotation, it may be moved; see
+Section~\ref{declaration-annotations-moved}.
+
+
+% These lists should be kept in sync with LockAnnotatedTypeFactory.java .
+\begin{figure}
+\begin{center}
+% The ~ around the text makes things look better in Hevea (and not terrible
+% in LaTeX).
+
+\begin{tabular}{ll}
+\begin{tabular}{|l|}
+\hline
+ ~net.jcip.annotations.GuardedBy~ \\ \hline
+ ~javax.annotation.concurrent.GuardedBy~ \\ \hline
+\end{tabular}
+&
+$\Rightarrow$
+%HEVEA ~org.checkerframework.checker.lock.qual.GuardedBy (for fields) or \ldots Holding (for methods)~
+%BEGIN LATEX
+\begin{tabular}{l}
+ ~org.checkerframework.checker.lock.qual.GuardedBy (for fields), or~ \\
+ ~org.checkerframework.checker.lock.qual.Holding (for methods)~
+\end{tabular}
+%END LATEX
+\end{tabular}
+\end{center}
+%BEGIN LATEX
+\vspace{-1.5\baselineskip}
+%END LATEX
+\caption{Correspondence between other lock annotations and the
+  Checker Framework's annotations.}
+\label{fig-lock-refactoring}
+\end{figure}
+
+
+\subsectionAndLabel{Relationship to annotations in \emph{Java Concurrency in Practice}}{lock-jcip-annotations}
+
+The book \href{https://jcip.net/}{\emph{Java Concurrency in Practice}}~\cite{Goetz2006} defines a
+\href{https://jcip.net/annotations/doc/net/jcip/annotations/GuardedBy.html}{\code{@GuardedBy}} annotation that is the inspiration for ours.  The book's
+\code{@GuardedBy} serves two related but distinct purposes:
+
+\begin{itemize}
+\item
+  When applied to a field, it means that the given lock must be held when
+  accessing the field.  The lock acquisition and the field access may occur
+  arbitrarily far in the future.
+\item
+  When applied to a method, it means that the given lock must be held by
+  the caller at the time that the method is called --- in other words, at
+  the time that execution passes the \code{@GuardedBy} annotation.
+\end{itemize}
+
+The Lock Checker renames the method annotation to
+\refqualclass{checker/lock/qual}{Holding}, and it generalizes the
+\refqualclass{checker/lock/qual}{GuardedBy} annotation into a type annotation
+that can apply not just to a field but to an arbitrary type (including the
+type of a parameter, return value, local variable, generic type parameter,
+etc.).  Another important distinction is that the Lock Checker's
+annotations express and enforce a locking discipline over values, just like
+the JLS expresses Java's locking semantics; by contrast, JCIP's annotations
+express a locking discipline that protects variable names and does not
+prevent race conditions.
+  This makes the annotations more expressive and also more amenable
+to automated checking.  It also accommodates the distinct
+meanings of the two annotations, and resolves ambiguity when \<@GuardedBy>
+is written in a location that might apply to either the method or the
+return type.
+
+(The JCIP book gives some rationales for reusing the annotation name for
+two purposes.  One rationale is
+that there are fewer annotations to learn.  Another rationale is
+that both variables and methods are ``members'' that can be ``accessed''
+and \code{@GuardedBy} creates preconditions for doing so.
+Variables can be accessed by reading or writing them (putfield, getfield),
+and methods can be accessed by calling them (invokevirtual,
+invokeinterface).  This informal intuition is
+inappropriate for a tool that requires precise semantics.)
+
+% It would not work to retain the name \code{@GuardedBy} but put it on the
+% receiver; an annotation on the receiver indicates what lock must be held
+% when it is accessed in the future, not what must have already been held
+% when the method was called.
+
+
+\sectionAndLabel{Possible extensions}{lock-extensions}
+
+The Lock Checker validates some uses of locks, but not all.  It would be
+possible to enrich it with additional annotations.  This would increase the
+programmer annotation burden, but would provide additional guarantees.
+
+Lock ordering:  Specify that one lock must be acquired before or after
+another, or specify a global ordering for all locks.  This would prevent
+deadlock.
+
+Not-holding:  Specify that a method must not be called if any of the listed
+locks are held.
+
+These features are supported by
+\href{http://clang.llvm.org/docs/ThreadSafetyAnalysis.html}{Clang's
+  thread-safety analysis}.
+
+
+% LocalWords:  quals GuardedBy JCIP putfield getfield invokevirtual b''
+% LocalWords:  invokeinterface threadsafety Clang's GuardedByUnknown a''
+%  LocalWords:  api 5cm lockexpr Dereferencing exprSet expr expr1 expr2
+%  LocalWords:  GuardedByBottom exprSet1 exprSet2 GuardSatisfied 3cm pre
+%  LocalWords:  PolyGuardedBy EnsuresLockHeld ReentrantLock boolean eset
+%  LocalWords:  EnsuresLockHeldIf LockingFree ReleasesNoLocks str lock1
+%  LocalWords:  MayReleaseLocks GuardedByName lock2 jls JLS LockHeld intra
+%  LocalWords:  LockPossiblyHeld explicitLock isHeldByCurrentThread JCIP's
+%  LocalWords:  holdsLock monitorLock cleanroom AconcurrentSemantics jcip
+% LocalWords:  guardedby ensureslockheld lockingfree runtime nondecreasing
diff --git a/docs/manual/manual-style.tex b/docs/manual/manual-style.tex
new file mode 100644
index 0000000..7da4e63
--- /dev/null
+++ b/docs/manual/manual-style.tex
@@ -0,0 +1,211 @@
+\usepackage[T1]{fontenc}
+\usepackage{pslatex}
+\usepackage{microtype}
+
+\usepackage{fullpage}
+\usepackage{graphicx}
+\usepackage{hevea}
+\usepackage{url}
+\usepackage{color}
+\usepackage{hyperref}
+% Not supported by Hevea, so don't bother: \usepackage{minitoc}
+\usepackage{proof}
+
+\usepackage{relsize}
+% \def\codesize{\smaller}
+\def\codesize{\relax}           % for "pslatex"
+%HEVEA \def\codesize{\relax}
+
+%%BEGIN LATEX
+\newcommand{\code}[1]{\ifmmode{\mbox{\codesize\ttfamily{#1}}}\else{\codesize\ttfamily #1}\fi}
+%\DeclareRobustCommand{\code}[1]{\codesize{\path{#1}}}
+%%END LATEX
+%%HEVEA \newcommand{\code}[1]{\codesize{\texttt{#1}}}
+\def\<#1>{\code{#1}}
+
+% This can't handle a URL with an embedded "#" -- at least at UW CSE
+\newcommand{\myurl}[1]{{\codesize\url{#1}}}
+%HEVEA \def\myurl{\url}
+% A command that shows the URL in the printed manual.
+% make a URL visible in PDF the but just be attached to anchor text in HTML:
+%BEGIN LATEX
+\newcommand{\ahreforurl}[2]{#2 (\url{#1})}
+%END LATEX
+%HEVEA \newcommand{\ahreforurl}[2]{\href{#1}{#2}}
+
+\usepackage{listings}
+\usepackage{alltt}
+\usepackage{fancyvrb}
+%BEGIN LATEX
+\RecustomVerbatimEnvironment{Verbatim}{Verbatim}{fontsize=\codesize}
+%END LATEX
+
+\newenvironment{mysmall}
+  {\ifhevea\makeatletter\@open{DIV}{style="font-size:small;"}\makeatother
+   \else\begin{smaller}\fi}
+  {\ifhevea\makeatletter\@close{DIV}\makeatother
+   \else\end{smaller}\fi}
+\newenvironment{myxsmall}
+  {\ifhevea\makeatletter\@open{DIV}{style="font-size:x-small;"}\makeatother
+   \else\begin{smaller}\begin{smaller}\fi}
+  {\ifhevea\makeatletter\@close{DIV}\makeatother
+   \else\end{smaller}\end{smaller}\fi}
+\newenvironment{myxxsmall}
+  {\ifhevea\makeatletter\@open{DIV}{style="font-size:xx-small;"}\makeatother
+   \else\begin{smaller}\begin{smaller}\begin{smaller}\fi}
+  {\ifhevea\makeatletter\@close{DIV}\makeatother
+   \else\end{smaller}\end{smaller}\end{smaller}\fi}
+
+%HEVEA \footerfalse    % Disable hevea advertisement in footer
+
+\newcommand{\htmlhr}{\relax}
+%HEVEA \renewcommand{\htmlhr}{\@hr{}{}}
+
+% Problem with using "\newcommand" or "\renewcommand": Hevea writes this
+% into manual.image.tex, and invokes LaTeX on it.  Sometimes running "make"
+% leads to an error as a result, sometimes not.  I don't know the pattern
+% of the failures, though running "make clean" and then "make" seems to
+% work.  So maybe there's a problem with an auxiliary file.  Solve it by
+% always defining \discretionary, so that \renewcommand works.
+%HEVEA \def\discretionary{\relax}\renewcommand{\discretionary}[3]{\relax}
+
+%HEVEA \newstyle{.lstframe}{margin:auto;margin-bottom:2em}
+
+% Make images larger and thus less pixelated.  This is not really a solution.
+%HEVEA\@addimagenopt{-mag 2000}
+% This avoids Hevea's built-in conversion.
+% The argument is the HEIGHT, not the width.
+\newcommand{\includeimage}[2]{%
+\begin{center}
+\includeimagenocentering{#1}{#2}%
+%BEGIN LATEX
+\vspace{-1.5\baselineskip}
+%END LATEX
+\end{center}}
+
+% The argument is the HEIGHT, not the width.
+\newcommand{\includeimagenocentering}[2]{%
+\ifhevea\imgsrc{#1.svg}\else%
+\resizebox{!}{#2}{\includegraphics{figures/#1}}\fi}
+
+
+% At least 80% of every float page must be taken up by
+% floats; there will be no page with more than 20% white space.
+\def\topfraction{.8}
+\def\dbltopfraction{\topfraction}
+\def\floatpagefraction{\topfraction}     % default .5
+\def\dblfloatpagefraction{\topfraction}  % default .5
+\def\textfraction{.2}
+
+
+% Left and right curly braces and backslash, in tt font
+\newcommand{\ttlcb}{\texttt{\char "7B}}
+\newcommand{\ttrcb}{\texttt{\char "7D}}
+\newcommand{\ttbs}{\texttt{\char "5C}}
+
+
+%BEGIN LATEX
+  %% Bring items closer together in list environments
+  % Prevent infinite loops
+  \let\Itemize =\itemize
+  \let\Enumerate =\enumerate
+  \let\Description =\description
+  % Zero the vertical spacing parameters
+  \def\Nospacing{\itemsep=0pt\topsep=0pt\partopsep=0pt\parskip=0pt\parsep=0pt}
+  % Redefine the environments in terms of the original values
+  \renewenvironment{itemize}{\Itemize\Nospacing}{\endlist}
+  \renewenvironment{enumerate}{\Enumerate\Nospacing}{\endlist}
+  \renewenvironment{description}{\Description\Nospacing}{\endlist}
+
+  % Add line between figure and text
+  \makeatletter
+  \def\topfigrule{\kern3\p@ \hrule \kern -3.4\p@} % the \hrule is .4pt high
+  \def\botfigrule{\kern-3\p@ \hrule \kern 2.6\p@} % the \hrule is .4pt high
+  \def\dblfigrule{\kern3\p@ \hrule \kern -3.4\p@} % the \hrule is .4pt high
+  \makeatother
+%END LATEX
+
+
+% Reference to Checker Framework Javadoc for a class (not a method, etc.).
+% Arg 1: directory under org/checkerframework/, including internal "/", but
+% no leading or trailing "/".
+% Arg 2: class name.
+% In the printed version, only the base class name appears.
+% In the HTML version, it's a link to the Javadoc.
+\newcommand{\refclass}[2]{\href{../api/org/checkerframework/#1/#2.html}{\<#2>}}
+
+% Reference to Checker Framework Javadoc for a type qualifier/annotation class
+% (not a method, etc.).  Like \refclass, but prepends an @ to the qualifier name.
+\newcommand{\refqualclass}[2]{\href{../api/org/checkerframework/#1/#2.html}{\<@#2>}}
+
+% Reference to Checker Framework Javadoc for a type qualifier/annotation class
+% (not a method, etc.) that takes parameters.  This prepends an @ to the
+% qualifier name and includes parentheses around the parameters.
+\newcommand{\refqualclasswithparams}[3]{\href{../api/org/checkerframework/#1/#2.html}{\<@#2>}\<(\allowbreak #3)>}
+
+% Reference to Checker Framework Javadoc for a method or field.).
+% Arg 1: package name under org/checkerframework, using "/" as a separator,
+% with no leading or trailing "/". example: "checker/nullness/qual"
+% Arg 2: class name.
+% Arg 3: method name.
+% Arg 4: fully-qualified arguments.  Example: "(T)".  For Java 8, may need
+% to use "-T-" with dashes instead of parentheses.
+% In the printed version, only "class.method" appears.
+% In the HTML version, it's a link to the Javadoc.
+\newcommand{\refmethod}[4]{\href{../api/org/checkerframework/#1/#2.html\##3#4}{\<#2.#3>}}
+% Omits the class name in the visible text; is terser.  Good for mentioning
+% a method that needs to be overridden.
+\newcommand{\refmethodterse}[4]{\href{../api/org/checkerframework/#1/#2.html\##3#4}{\<#3>}}
+% Permits specification of the anchor text.  Good for mentioning the
+% arguments of an overloaded method that needs to be overridden.
+\newcommand{\refmethodanchortext}[5]{\href{../api/org/checkerframework/#1/#2.html\##3#4}{\<#5>}}
+% Like refmethod, but formatted more concisely as ``field'' instead of
+% ``Class.field''.  The last argument is usually empty {}.
+\newcommand{\refenum}[4]{\href{../api/org/checkerframework/#1/#2.html\##3#4}{\<#3>}}
+
+% Reference to Sun Javadoc.
+% Arg 1: .html reference, without the .../api/ prefix
+% Arg 2: What will appear in the formatted manual.
+% Do not use \url in the body of any macro
+\newcommand{\sunjavadoc}[2]{\href{https://docs.oracle.com/en/java/javase/11/docs/api/#1}{\<#2>}}
+% Like \sunjavadoc, but for annotations and prepends "@".
+\newcommand{\sunjavadocanno}[2]{\href{https://docs.oracle.com/en/java/javase/11/docs/api/#1}{\<@#2>}}
+% Now, for Java EE (version 7 is the last version):
+\newcommand{\javaeejavadoc}[2]{\href{https://docs.oracle.com/javaee/7/api/#1}{\<#2>}}
+\newcommand{\javaeejavadocanno}[2]{\href{https://docs.oracle.com/javaee/7/api/#1}{\<@#2>}}
+
+
+% These commands are for long-range references:  those not in the same chapter.
+\newcommand{\refwithpage}[1]{\ref{#1}, page~\pageref{#1}}
+%HEVEA \renewcommand{\refwithpage}[1]{\ref{#1}}
+\newcommand{\refwithpageparen}[1]{\ref{#1} (page~\pageref{#1})}
+%HEVEA \renewcommand{\refwithpageparen}[1]{\ref{#1}}
+% Use \chapterpageref to reference only chapters, not sections.
+\newcommand{\chapterpageref}[1]{Chapter~\refwithpage{#1}}
+
+
+\ifhevea
+% Using a Unicode character avoids needing to use an image.  Unfortunately,
+% this Unicode character is not installed on all computers, so use an image
+% anyway.
+% \newcommand{\linkicon}{{\small\@print@u{128279}}}
+\newcommand{\linkicon}{\imgsrc[width="10" height="10"]{chainlink.svg}}
+\newcommand{\selflink}[1]{\if@refs\ \ahrefloc{#1}{\linkicon}\fi}
+\newcommand{\mksectionAndLabel}[1]
+{\newcommand{\csname #1AndLabel\endcsname}[2]
+{\csname #1\endcsname{##1\label{##2}\selflink{##2}}}}
+\mksectionAndLabel{chapter}
+\mksectionAndLabel{section}
+\mksectionAndLabel{subsection}
+\mksectionAndLabel{subsubsection}
+\mksectionAndLabel{paragraph}
+\else
+\newcommand{\selflink}[1]{}
+\newcommand{\chapterAndLabel}[2]{\chapter{#1}\label{#2}}
+\newcommand{\sectionAndLabel}[2]{\section{#1}\label{#2}}
+\newcommand{\subsectionAndLabel}[2]{\subsection{#1}\label{#2}}
+\newcommand{\subsubsectionAndLabel}[2]{\subsubsection{#1}\label{#2}}
+\newcommand{\paragraphAndLabel}[2]{\paragraph{#1}\label{#2}}
+\fi
+
+%  LocalWords:  fontsize mysmall myxsmall myxxsmall subsubsection
diff --git a/docs/manual/manual.bbl b/docs/manual/manual.bbl
new file mode 100644
index 0000000..6d316de
--- /dev/null
+++ b/docs/manual/manual.bbl
@@ -0,0 +1,227 @@
+\newcommand{\etalchar}[1]{$^{#1}$}
+\begin{thebibliography}{WPM{\etalchar{+}}09}
+
+\bibitem[AQKE09]{ArtziQKE2009}
+Shay Artzi, Jaime Quinonez, Adam Kie{\.z}un, and Michael~D. Ernst.
+\newblock Parameter reference immutability: Formal definition, inference tool,
+  and comparison.
+\newblock {\em Automated Software Engineering}, 16(1):145--192, March 2009.
+
+\bibitem[Art01]{Artho2001}
+Cyrille Artho.
+\newblock Finding faults in multi-threaded programs.
+\newblock Master's thesis, Swiss Federal Institute of Technology, March~15,
+  2001.
+
+\bibitem[BJM{\etalchar{+}}15]{BarrosJMVDdAE2015}
+Paulo Barros, Ren\'e Just, Suzanne Millstein, Paul Vines, Werner Dietl, Marcelo
+  d'Amorim, and Michael~D. Ernst.
+\newblock Static analysis of implicit control flow: Resolving {Java} reflection
+  and {Android} intents.
+\newblock In {\em ASE 2015: Proceedings of the 30th Annual International
+  Conference on Automated Software Engineering}, pages 669--679, Lincoln, NE,
+  USA, November 2015.
+
+\bibitem[CNA{\etalchar{+}}17]{CoblenzNAMS2017}
+Michael Coblenz, Whitney Nelson, Jonathan Aldrich, Brad Myers, and Joshua
+  Sunshine.
+\newblock Glacier: Transitive class immutability for {Java}.
+\newblock In {\em ICSE 2017, Proceedings of the 39th International Conference
+  on Software Engineering}, pages 496--506, Buenos Aires, Argentina, May 2017.
+
+\bibitem[Cop05]{Copeland2005}
+Tom Copeland.
+\newblock {\em PMD Applied}.
+\newblock Centennial Books, November 2005.
+
+\bibitem[Cro06]{JSR198}
+Jose Cronembold.
+\newblock {JSR} 198: A standard extension {API} for {Integrated} {Development}
+  {Environments}.
+\newblock \url{http://jcp.org/en/jsr/detail?id=198}, May~8, 2006.
+
+\bibitem[Dar06]{JSR269}
+Joe Darcy.
+\newblock {JSR} 269: Pluggable annotation processing {API}.
+\newblock \url{https://jcp.org/en/jsr/detail?id=269}, May~17, 2006.
+\newblock Public review version.
+
+\bibitem[DDE{\etalchar{+}}11]{DietlDEMS2011}
+Werner Dietl, Stephanie Dietzel, Michael~D. Ernst, K{\i}van{\c{c}} Mu{\c{s}}lu,
+  and Todd Schiller.
+\newblock Building and using pluggable type-checkers.
+\newblock In {\em ICSE 2011, Proceedings of the 33rd International Conference
+  on Software Engineering}, pages 681--690, Waikiki, Hawaii, USA, May 2011.
+
+\bibitem[DEM11]{DietlEM2011}
+Werner Dietl, Michael~D. Ernst, and Peter M{\"u}ller.
+\newblock Tunable static inference for {Generic} {Universe} {Types}.
+\newblock In {\em ECOOP 2011 --- Object-Oriented Programming, 25th European
+  Conference}, pages 333--357, Lancaster, UK, July 2011.
+
+\bibitem[EJM{\etalchar{+}}14]{ErnstJMDPRKBBHVW2014}
+Michael~D. Ernst, Ren{\'e} Just, Suzanne Millstein, Werner Dietl, Stuart
+  Pernsteiner, Franziska Roesner, Karl Koscher, Paulo Barros, Ravi Bhoraskar,
+  Seungyeop Han, Paul Vines, and Edward~X. Wu.
+\newblock Collaborative verification of information flow for a high-assurance
+  app store.
+\newblock In {\em CCS 2014: Proceedings of the 21st ACM Conference on Computer
+  and Communications Security}, pages 1092--1104, Scottsdale, AZ, USA, November
+  2014.
+
+\bibitem[ELM{\etalchar{+}}15]{ErnstLMSS2015}
+Michael~D. Ernst, Alberto Lovato, Damiano Macedonio, Ciprian Spiridon, and
+  Fausto Spoto.
+\newblock Boolean formulas for the static identification of injection attacks
+  in {Java}.
+\newblock In {\em LPAR 2015: Proceedings of the 20th International Conference
+  on Logic for Programming, Artificial Intelligence, and Reasoning}, pages
+  130--145, Suva, Fiji, November 2015.
+
+\bibitem[Ern08]{JSR308-2008-09-12}
+Michael~D. Ernst.
+\newblock {Type Annotations} specification ({JSR} 308).
+\newblock \url{https://checkerframework.org/jsr308/}, September~12, 2008.
+
+\bibitem[GDEG13]{GordonDEG2013}
+Colin~S. Gordon, Werner Dietl, Michael~D. Ernst, and Dan Grossman.
+\newblock {JavaUI}: Effects for controlling {UI} object access.
+\newblock In {\em ECOOP 2013 --- Object-Oriented Programming, 27th European
+  Conference}, pages 179--204, Montpellier, France, July 2013.
+
+\bibitem[Goe06]{Goetz2006:typedef}
+Brian Goetz.
+\newblock The pseudo-typedef antipattern: Extension is not type definition.
+\newblock \url{https://www.ibm.com/developerworks/java/library/j-jtp02216/},
+  February~21, 2006.
+
+\bibitem[GPB{\etalchar{+}}06]{Goetz2006}
+Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, and Doug
+  Lea.
+\newblock {\em Java Concurrency in Practice}.
+\newblock Addison-Wesley, 2006.
+
+\bibitem[HDME12]{HuangDME2012}
+Wei Huang, Werner Dietl, Ana Milanova, and Michael~D. Ernst.
+\newblock Inference and checking of object ownership.
+\newblock In {\em ECOOP 2012 --- Object-Oriented Programming, 26th European
+  Conference}, pages 181--206, Beijing, China, June 2012.
+
+\bibitem[HMDE12]{HuangMDE2012}
+Wei Huang, Ana Milanova, Werner Dietl, and Michael~D. Ernst.
+\newblock {ReIm} \& {ReImInfer}: Checking and inference of reference
+  immutability and method purity.
+\newblock In {\em OOPSLA 2012, Object-Oriented Programming Systems, Languages,
+  and Applications}, pages 879--896, Tucson, AZ, USA, October 2012.
+
+\bibitem[HP04]{HovemeyerP2004}
+David Hovemeyer and William Pugh.
+\newblock Finding bugs is easy.
+\newblock In {\em OOPSLA Companion: Companion to Object-Oriented Programming
+  Systems, Languages, and Applications}, pages 132--136, Vancouver, BC, Canada,
+  October 2004.
+
+\bibitem[HSP05]{HovemeyerSP2005}
+David Hovemeyer, Jaime Spacco, and William Pugh.
+\newblock Evaluating and tuning a static analysis to find null pointer bugs.
+\newblock In {\em PASTE 2005: ACM SIGPLAN/SIGSOFT Workshop on Program Analysis
+  for Software Tools and Engineering (PASTE 2005)}, pages 13--19, Lisbon,
+  Portugal, September 2005.
+
+\bibitem[LBR06]{LeavensBR2006:JML}
+Gary~T. Leavens, Albert~L. Baker, and Clyde Ruby.
+\newblock Preliminary design of {JML}: A behavioral interface specification
+  language for {Java}.
+\newblock {\em ACM SIGSOFT Software Engineering Notes}, 31(3), March 2006.
+
+\bibitem[PAC{\etalchar{+}}08]{PapiACPE2008}
+Matthew~M. Papi, Mahmood Ali, Telmo~Luis {Correa~Jr.}, Jeff~H. Perkins, and
+  Michael~D. Ernst.
+\newblock Practical pluggable types for {Java}.
+\newblock In {\em ISSTA 2008, Proceedings of the 2008 International Symposium
+  on Software Testing and Analysis}, pages 201--212, Seattle, WA, USA, July
+  2008.
+
+\bibitem[QTE08]{QuinonezTE2008}
+Jaime Quinonez, Matthew~S. Tschantz, and Michael~D. Ernst.
+\newblock Inference of reference immutability.
+\newblock In {\em ECOOP 2008 --- Object-Oriented Programming, 22nd European
+  Conference}, pages 616--641, Paphos, Cyprus, July 2008.
+
+\bibitem[SCSC18]{SteinCSC2018}
+Benno Stein, Lazaro Clapp, Manu Sridharan, and Bor{-}Yuh~Evan Chang.
+\newblock Safe stream-based programming with refinement types.
+\newblock In {\em ASE 2018: Proceedings of the 33rd Annual International
+  Conference on Automated Software Engineering}, pages 565--576, Montpellier,
+  France, September 2018.
+
+\bibitem[SDE12]{SpishakDE2012}
+Eric Spishak, Werner Dietl, and Michael~D. Ernst.
+\newblock A type system for regular expressions.
+\newblock In {\em FTfJP: 14th Workshop on Formal Techniques for Java-like
+  Programs}, pages 20--26, Beijing, China, June 2012.
+
+\bibitem[SDF{\etalchar{+}}11]{SampsonDFGCG2011}
+Adrian Sampson, Werner Dietl, Emily Fortuna, Danushen Gnanapragasam, Luis Ceze,
+  and Dan Grossman.
+\newblock {EnerJ}: Approximate data types for safe and general low-power
+  computation.
+\newblock In {\em PLDI 2011: Proceedings of the {ACM} {SIGPLAN} 2011 Conference
+  on Programming Language Design and Implementation}, pages 164--174, San Jose,
+  CA, USA, June 2011.
+
+\bibitem[SM11]{SummersM2011}
+Alexander~J. Summers and Peter M{\"u}ller.
+\newblock Freedom before commitment: A lightweight type system for object
+  initialisation.
+\newblock In {\em OOPSLA 2011, Object-Oriented Programming Systems, Languages,
+  and Applications}, pages 1013--1032, Portland, OR, USA, October 2011.
+
+\bibitem[TE05]{TschantzE2005}
+Matthew~S. Tschantz and Michael~D. Ernst.
+\newblock Javari: Adding reference immutability to {Java}.
+\newblock In {\em OOPSLA 2005, Object-Oriented Programming Systems, Languages,
+  and Applications}, pages 211--230, San Diego, CA, USA, October 2005.
+
+\bibitem[TPV10]{TangPJ2010}
+Daniel Tang, Ales Plsek, and Jan Vitek.
+\newblock Static checking of safety critical {Java} annotations.
+\newblock In {\em JTRES 2010: 8th International Workshop on Java Technologies
+  for Real-time and Embedded Systems}, pages 148--154, Prague, Czech Republic,
+  August 2010.
+
+\bibitem[VPEJ14]{VakilianPEJ2014}
+Mohsen Vakilian, Amarin Phaosawasdi, Michael~D. Ernst, and Ralph~E. Johnson.
+\newblock Cascade: A universal type qualifier inference tool.
+\newblock Technical report, University of Illinois at Urbana-Champaign, Urbana,
+  IL, USA, September 2014.
+
+\bibitem[WKSE14]{WeitzKSE2014}
+Konstantin Weitz, Gene Kim, Siwakorn Srisakaokul, and Michael~D. Ernst.
+\newblock A type system for format strings.
+\newblock In {\em ISSTA 2014, Proceedings of the 2014 International Symposium
+  on Software Testing and Analysis}, pages 127--137, San Jose, CA, USA, July
+  2014.
+
+\bibitem[WPM{\etalchar{+}}09]{WrigstadPMZV2009}
+Tobias Wrigstad, Filip Pizlo, Fadi Meawad, Lei Zhao, and Jan Vitek.
+\newblock Loci: Simple thread-locality for {J}ava.
+\newblock In {\em ECOOP 2009 --- Object-Oriented Programming, 23rd European
+  Conference}, pages 445--469, Genova, Italy, July 2009.
+
+\bibitem[ZPA{\etalchar{+}}07]{ZibinPAAKE2007}
+Yoav Zibin, Alex Potanin, Mahmood Ali, Shay Artzi, Adam Kie{\.z}un, and
+  Michael~D. Ernst.
+\newblock Object and reference immutability using {Java} generics.
+\newblock In {\em ESEC/FSE 2007: Proceedings of the 11th European Software
+  Engineering Conference and the 15th {ACM} {SIGSOFT} Symposium on the
+  Foundations of Software Engineering}, pages 75--84, Dubrovnik, Croatia,
+  September 2007.
+
+\bibitem[ZPL{\etalchar{+}}10]{ZibinPLAE2010}
+Yoav Zibin, Alex Potanin, Paley Li, Mahmood Ali, and Michael~D. Ernst.
+\newblock Ownership and immutability in generic {Java}.
+\newblock In {\em OOPSLA 2010, Object-Oriented Programming Systems, Languages,
+  and Applications}, pages 598--617, Revo, NV, USA, October 2010.
+
+\end{thebibliography}
diff --git a/docs/manual/manual.tex b/docs/manual/manual.tex
new file mode 100644
index 0000000..c415978
--- /dev/null
+++ b/docs/manual/manual.tex
@@ -0,0 +1,106 @@
+\documentclass[10pt]{report}
+
+\input{manual-style.tex}
+
+\title{The Checker Framework Manual: \\ Custom pluggable types for Java}
+\author{\url{https://checkerframework.org/}}
+\newcommand{\ReleaseVersion}{3.13.0}
+\newcommand{\ReleaseInfo}{3.13.0 (3 May 2021)}
+\date{Version \ReleaseInfo{}}
+
+\begin{document}
+
+%%% TODO: This file no longer exists on buffalo; must find a new file to test.
+%% Releases are made on buffalo, so this macro is really saying
+%% "released version of the CF" as opposed to a development version.
+\newif\ifonbuffalo
+%HEVEA\makeatletter\@iffileexists{/scratch/secs-jenkins/java/jdk1.7.0/LICENSE}{\onbuffalotrue}{\onbuffalofalse}\makeatother
+%BEGIN LATEX
+\IfFileExists{/scratch/secs-jenkins/java/jdk1.7.0/LICENSE}{\onbuffalotrue}{\onbuffalofalse}
+%END LATEX
+
+\begin{center}
+%HEVEA\imgsrc{CFLogo.png}
+%BEGIN LATEX
+  \includegraphics{../logo/Logo/CFLogo.pdf}
+%END LATEX
+\end{center}
+
+% Permit text on title page by making \newpage a no-op while running \maketitle
+{\let\newpage\relax \maketitle}
+
+
+\noindent
+\textbf{For the impatient:}
+Section~\refwithpageparen{installation}
+describes how to \textbf{install and use} pluggable type-checkers.
+
+%HEVEA This manual is also available in \href{https://checkerframework.org/manual/checker-framework-manual.pdf}{PDF}.
+
+%HEVEA \setcounter{tocdepth}{1}
+% Not supported by Hevea, so don't bother: \dominitoc
+\tableofcontents
+\newpage
+
+\input{introduction.tex}
+
+%% Checkers
+% If you add a new checker, also update the lists in introduction.tex and
+% advanced-features.tex .
+
+\input{nullness-checker.tex}
+\input{map-key-checker.tex}
+\input{optional-checker.tex}
+\input{interning-checker.tex}
+\input{lock-checker.tex}
+\input{index-checker.tex}
+\input{called-methods-checker}
+\input{fenum-checker.tex}
+\input{tainting-checker.tex}
+
+% These are focused on strings:
+\input{regex-checker.tex}
+\input{formatter-checker.tex}
+\input{i18n-format-checker.tex}
+\input{propkey-checker.tex}
+\input{signature-checker.tex}
+\input{guieffect-checker.tex}
+\input{units-checker.tex}
+
+% These are focused on ints:
+\input{signedness-checker.tex}
+
+% Checkers that tend to be used as components of other checkers rather
+% than used for their own sake:
+\input{purity-checker.tex}
+\input{constant-value-checker.tex}
+\input{returns-receiver-checker.tex}
+\input{reflection-checker.tex}
+\input{initialized-fields-checker.tex}
+\input{aliasing-checker.tex}
+\input{must-call-checker.tex}
+
+% Special subtyping checker and external checkers:
+\input{subtyping-checker.tex}
+\input{external-checkers.tex}
+
+%% Advanced material
+
+\input{generics.tex}
+\input{advanced-features.tex}
+\input{warnings.tex}
+\input{inference.tex}
+\input{annotating-libraries.tex}
+\input{creating-a-checker.tex}
+\input{accumulation-checker.tex}
+\input{external-tools.tex}
+\input{faq.tex}
+\input{troubleshooting.tex}
+
+\htmlhr
+\bibliographystyle{alpha}
+\bibliography{bibstring-unabbrev,types,ernst,invariants,generals,alias,concurrency,crossrefs}
+
+\end{document}
+
+% LocalWords:  pt TODO JavaDocs Arg api HEVEA html ernst
diff --git a/docs/manual/map-key-checker.tex b/docs/manual/map-key-checker.tex
new file mode 100644
index 0000000..6f83688
--- /dev/null
+++ b/docs/manual/map-key-checker.tex
@@ -0,0 +1,317 @@
+\htmlhr
+\chapterAndLabel{Map Key Checker}{map-key-checker}
+
+The Map Key Checker tracks which values are keys for which maps.  If variable
+\code{v} has type \code{@KeyFor("m")...}, then the value of \code{v} is a key
+in Map \code{m}.  That is, the expression \code{m.containsKey(v)} evaluates to
+\code{true}.
+
+Section~\ref{map-key-qualifiers} describes how \code{@KeyFor} annotations
+enable the
+Nullness Checker (\chapterpageref{nullness-checker}) to treat calls to
+\sunjavadoc{java.base/java/util/Map.html\#get(java.lang.Object)}{\code{Map.get}}
+more precisely by refining its result to \<@NonNull> in some cases.
+
+
+\sectionAndLabel{Invoking the Map Key Checker}{map-key-invoking}
+
+You will not typically run the Map Key Checker.  It is automatically run by
+other checkers, in particular the Nullness Checker.
+
+You can unsoundly suppress warnings related to map keys with
+\<@SuppressWarnings("keyfor")>, or everywhere by using command-line option
+\<-AsuppressWarnings=keyfor>; see \chapterpageref{suppressing-warnings}.
+
+The command-line argument \<-AassumeKeyFor> makes the Map Key Checker
+unsoundly assume that the argument to \<Map.get> is a key for the receiver
+map.  This is like declaring the \<Map.get> method as \<V get(Object key)>
+rather than \<@Nullable V get(Object key)>.  (Just changing the JDK
+declaration would not work, however, because the Map Key Checker has
+special-case logic for \<Map.get>.  This is different than suppressing
+warnings, because it changes a method's return type.  This is not the same
+as assuming that the return value is \<@NonNull>, because the map's value
+type might be \<@Nullable>, as in \code{Map<String, @Nullable Integer>}.
+
+
+\sectionAndLabel{Map key annotations}{map-key-annotations}
+
+These qualifiers are part of the Map Key type system:
+
+\begin{description}
+
+\item[\refqualclasswithparams{checker/nullness/qual}{KeyFor}{String[] maps}]
+  indicates that the value assigned to the annotated variable is a key for at
+  least the given maps.
+
+\item[\refqualclass{checker/nullness/qual}{UnknownKeyFor}]
+  is used internally by the type system but should never be written by a
+  programmer.  It indicates that the value assigned to the annotated
+  variable is not known to be a key for any map.  It is the default type
+  qualifier.
+
+\item[\refqualclass{checker/nullness/qual}{KeyForBottom}]
+  is used internally by the type system but should never be written by a
+  programmer.  There are no values of this type (not even \<null>).
+
+\end{description}
+
+The following method annotations can be used to establish a method postcondition
+that ensures that a certain expression is a key for a map:
+
+\begin{description}
+\item[\refqualclasswithparams{checker/nullness/qual}{EnsuresKeyFor}{String[] value, String[] map}]
+  When the method with this annotation returns, the expression (or all the
+  expressions) given in the \code{value} element is a key for the given
+  maps. More precisely, the expression has the \code{@KeyFor} qualifier
+  with the \code{value} arguments taken from the \code{targetValue} element
+  of this annotation.
+\item[\refqualclasswithparams{checker/nullness/qual}{EnsuresKeyForIf}{String[] expression, boolean result, String[] map}]
+  If the method with this annotation returns the given boolean value,
+  then the given expression (or all the given expressions)
+  is a key for the given maps.
+\end{description}
+
+\begin{figure}
+\includeimage{map-key-keyfor}{5cm}
+\caption{The subtyping relationship of the Map Key Checker's qualifiers.
+\<@KeyFor(A)> is a supertype of \<@KeyFor(B)> if and only if \<A> is a subset of
+\<B>.  Qualifiers in gray are used internally by the type system but should
+never be written by a programmer.}
+\label{fig-map-key-keyfor-hierarchy}
+\end{figure}
+
+\sectionAndLabel{Default annotations}{map-key-defaults}
+
+The qualifier for the type of the \code{null} literal is \code{@UnknownKeyFor}.
+If \code{null} were \code{@KeyForBottom}, that would mean that
+\code{null} is guaranteed to be a key for every map (which is not
+necessarily true).
+
+\subsectionAndLabel{Default for lower bounds}{map-key-defaults-lowerbound}
+
+Lower bounds are defaulted to \code{@UnknownKeyFor}.
+However, in \<java.*> packages, the default for lower bounds is
+\<@KeyForBottom>.
+
+It is challenging to choose a default for lower bounds of type variables
+and wildcards.
+
+Here is a comparison of two choices for lower bounds:
+
+%BEGIN LATEX
+\medskip
+%END LATEX
+
+\noindent
+\begin{tabular}{ll}
+\<@KeyForBottom> default                   & \<@UnknownKeyFor> default (current choice) \\
+\hline
+\code{class MyClass1<@UnknownKeyFor T> \{}  & \code{class MyClass1<T> \{} \\
+\code{~~T var = null; // OK}               & \code{~~T var = null; // OK} \\
+\hline
+\code{class MyClass2<T> \{}   & \\
+\code{~~@UnknownKeyFor T var = null; // OK} & \\
+\hline
+\code{class MyClass3<T> \{}    &  \\
+\code{~~T var = null; // ERROR}             &  \\
+\hline
+& \code{class MySet1<T> implements Set<T> \{ \}} \\
+& \code{MySet1<@KeyFor("m") String> s1; // ERROR} \\
+\hline
+\code{class Set<E> \{ \}} &
+                \code{class Set<@KeyForBottom E> \{ \}} \\
+\code{class MySet2<T> implements Set<T> \{ \}} &
+                \code{class MySet2<@KeyForBottom T> implements Set<T> \{ \}} \\
+\code{MySet2<@KeyFor("m") String> s2; // OK}
+%HEVEA~~~
+ &
+                \code{MySet2<@KeyFor("m") String> s2; // OK} \\
+
+% List<@KeyFor("m")> // ERROR unless List's type argument has lower bound of @KeyForBottom.
+\end{tabular}
+
+%BEGIN LATEX
+\medskip
+%END LATEX
+
+
+If lower bounds are defaulted to \code{@KeyForBottom} (which is not
+currently the case), then whenever \<null> is assigned to a variable whose
+type is a type variable, programmers must write an \<@UnknownKeyFor>
+annotation on either the type variable declaration or on variable
+declarations, as shown in \<MyClass1> and
+\<MyClass2>.
+A disadvantage of this default is that the Map Key checker may issue
+warnings in code that has nothing to do with map keys, and in which no map
+key annotations are used.
+
+If lower bounds are defaulted to \code{@UnknownKeyFor} (which is currently
+the case), then whenever a client might use a \<@KeyFor> type argument,
+programmers must write a \<@KeyForBottom> annotation on the type parameter,
+as in \<MySet2> (and \<Set>).
+
+
+\subsectionAndLabel{Diagnosing the need for explicit @KeyFor on lower bounds}{map-key-lowerbound-explicit}
+
+Under the current defaulting (lower bounds default to
+\code{@UnknownKeyFor}), suppose you write this code:
+
+\begin{Verbatim}
+public class Graph<N> {
+  Map<N, Integer> nodes = new HashMap<>();
+}
+
+class Client {
+  @Nullable Graph<@KeyFor("g.nodes") String> g;
+}
+\end{Verbatim}
+
+\noindent
+The Nullness Checker issues this error message:
+
+\begin{Verbatim}
+Graph.java:14: error: [type.argument] incompatible types in type argument.
+  @Nullable Graph<@KeyFor("g.nodes") String> g;
+                  ^
+  found   : @KeyFor("this.g.nodes") String
+  required: [extends @UnknownKeyFor Object super @UnknownKeyFor null]
+\end{Verbatim}
+
+Note that the upper and lower bounds are both \<@UnknownKeyFor>.  You can
+make the code type-check by writing a lower bound, which is written before
+the type variable name (Section~\ref{generics-instantiation}):
+
+\begin{Verbatim}
+public class Graph<@KeyForBottom N> {
+...
+\end{Verbatim}
+
+
+\sectionAndLabel{Examples}{map-key-examples}
+
+The Map Key Checker keeps track of which variables reference keys to
+which maps.  A variable annotated with \<@KeyFor(\emph{mapSet})> can only
+contain a value that is a key for all the maps in \emph{mapSet}.  For example:
+
+\begin{verbatim}
+Map<String,Date> m, n;
+@KeyFor("m") String km;
+@KeyFor("n") String kn;
+@KeyFor({"m", "n"}) String kmn;
+km = kmn;   // OK - a key for maps m and n is also a key for map m
+km = kn;    // error: a key for map n is not necessarily a key for map m
+\end{verbatim}
+
+
+As with any annotation, use of the \<@KeyFor> annotation may force you to
+slightly refactor your code.  For example, this would be illegal:
+
+\begin{verbatim}
+Map<String,Object> m;
+Collection<@KeyFor("m") String> coll;
+coll.add(x);   // error: element type is @KeyFor("m") String, but x does not have that type
+m.put(x, ...);
+\end{verbatim}
+
+\noindent
+The example type-checks if you reorder the two calls:
+
+\begin{verbatim}
+Map<String,Object> m;
+Collection<@KeyFor("m") String> coll;
+m.put(x, ...);    // after this statement, x has type @KeyFor("m") String
+coll.add(x);      // OK
+\end{verbatim}
+
+
+
+\sectionAndLabel{Inference of @KeyFor annotations}{map-key-annotations-inference}
+
+Within a method body, you usually do not have to write \<@KeyFor>
+explicitly (except sometimes on type arguments),
+because the checker infers it based on usage patterns.  When the Map Key
+Checker encounters a run-time check for map keys, such as
+``\<if (m.containsKey(k)) ...>'', then the Map Key Checker refines the type of
+\<k> to \<@KeyFor("m")> within the scope of the test (or until \<k> is
+side-effected within that scope).  The Map Key Checker also infers \<@KeyFor>
+annotations based on iteration over a map's
+\sunjavadoc{java.base/java/util/Map.html\#keySet()}{\textrm{key set}} or calls to
+\sunjavadoc{java.base/java/util/Map.html\#put(K,V)}{put}
+or
+\sunjavadoc{java.base/java/util/Map.html\#containsKey(java.lang.Object)}{containsKey}.
+For more details about type refinement, see Section~\ref{type-refinement}.
+
+Suppose we have these declarations:
+
+\begin{verbatim}
+Map<String,Date> m = new Map<>();
+String k = "key";
+@KeyFor("m") String km;
+\end{verbatim}
+
+Ordinarily, the following assignment does not type-check:
+
+\begin{verbatim}
+km = k;   // Error since k is not known to be a key for map m.
+\end{verbatim}
+
+The following examples show cases where the Map Key Checker
+infers a \<@KeyFor> annotation for variable \<k> based on usage patterns,
+enabling the \<km = k> assignment to type-check.
+
+
+\begin{verbatim}
+m.put(k, ...);
+// At this point, the type of k is refined to @KeyFor("m") String.
+km = k;   // OK
+
+
+if (m.containsKey(k)) {
+    // At this point, the type of k is refined to @KeyFor("m") String.
+    km = k;   // OK
+    ...
+} else {
+    km = k;   // Error since k is not known to be a key for map m.
+    ...
+}
+\end{verbatim}
+
+
+The following example shows a case where the Map Key Checker resets its
+assumption about the type of a field used as a key because that field may have
+been side-effected.
+
+\begin{verbatim}
+class MyClass {
+    private Map<String,Object> m;
+    private String k;   // The type of k defaults to @UnknownKeyFor String
+    private @KeyFor("m") String km;
+
+    public void myMethod() {
+        if (m.containsKey(k)) {
+            km = k;   // OK: the type of k is refined to @KeyFor("m") String
+
+            sideEffectFreeMethod();
+            km = k;   // OK: the type of k is not affected by the method call
+                      // and remains @KeyFor("m") String
+
+            otherMethod();
+            km = k;   // error: At this point, the type of k is once again
+                      // @UnknownKeyFor String, because otherMethod might have
+                      // side-effected k such that it is no longer a key for map m.
+        }
+    }
+
+    @SideEffectFree
+    private void sideEffectFreeMethod() { ... }
+
+    private void otherMethod() { ... }
+}
+\end{verbatim}
+
+
+%% LocalWords:  KeyFor containsKey java keyfor UnknownKeyFor KeyForBottom
+%% LocalWords:  mapSet keySet km threeLetterWordSubset JT MyClass1 MySet1
+%% LocalWords:  MyClass2 MyClass3 s1 MySet2 s2 EnsuresKeyFor targetValue
+%% LocalWords:  EnsuresKeyForIf boolean lowerbound AsuppressWarnings
+%% LocalWords:  AassumeKeyFor
diff --git a/docs/manual/must-call-checker.tex b/docs/manual/must-call-checker.tex
new file mode 100644
index 0000000..39b9b2c
--- /dev/null
+++ b/docs/manual/must-call-checker.tex
@@ -0,0 +1,198 @@
+\htmlhr
+\chapterAndLabel{Must Call Checker}{must-call-checker}
+
+The Must Call Checker conservatively over-approximates
+the set of methods that an object should call before it is de-allocated.
+The checker does not enforce that the methods are called before objects are de-allocated,
+nor are its annotations refined after a method is called; rather,
+it is intended to be run as a subchecker of another checker. On its own, it does not
+enforce any rules other than subtyping. Instead, the types that the Must Call Checker
+computes can be used by later checkers that require an over-approximation
+of the methods that each object in the program should call.
+
+For example, consider a \<java.io.OutputStream>.  This \<OutputStream>
+might have an obligation to call the \<close()> method, to release an
+underlying file resource. The type of this \<OutputStream> is
+\<@MustCall(\{"close"\})>.  Or, the \<OutputStream> might not have such an
+obligation, if the underlying resource is a byte array (or if \<close()>
+has already been called). The type of this \<OutputStream> is
+\<@MustCall(\{\})>.  For an arbitrary \<OutputStream>, the Must Call
+Checker over-approximates the methods that it should call by assigning it
+the type \<@MustCall(\{"close"\}) OutputStream>, which can be read as ``an
+OutputStream that might need to call \<close()> (but no other methods)
+before it is de-allocated''.
+
+\sectionAndLabel{Must Call annotations}{must-call-annotations}
+
+The Must Call Checker supports these type qualifiers:
+
+\begin{description}
+
+\item[\refqualclasswithparams{checker/mustcall/qual}{MustCall}{String[] value}]
+  gives a superset of the methods that
+  must be called before the expression's value is de-allocated.
+  The default type qualifier for an unannotated type is \<@MustCall(\{\})>.
+
+\item[\refqualclass{checker/mustcall/qual}{MustCallUnknown}]
+  represents a value with an unknown or infinite set of must-call obligations.
+  It is used internally by the type system but should never be written by a
+  programmer.
+
+\item[\refqualclass{checker/mustcall/qual}{PolyMustCall}]
+  is the polymorphic annotation for the Must Call type system.
+  For a description of polymorphic qualifiers, see
+  Section~\ref{method-qualifier-polymorphism}.
+
+\item[\refqualclasswithparams{checker/mustcall/qual}{InheritableMustCall}{String[] value}]
+  is an alias of \<@MustCall> that can only be written on a class declaration.
+  It applies both to the class declaration on which it is written and also to all subclasses.
+  This frees the user of the need to write the \<@MustCall> annotation on every subclass.
+  See Section~\ref{must-call-on-class} for details.
+
+\end{description}
+
+\begin{figure}
+\includeimage{mustcall}{5cm}
+\caption{Part of the Must Call Checker's type
+qualifier hierarchy.  The full hierarchy contains one \<@MustCall> annotation
+for every combination of methods.
+Qualifiers in gray are used internally by the type system but should
+never be written by a programmer.}
+\label{fig-must-call-hierarchy}
+\end{figure}
+
+Here are some facts about the type qualifier hierarchy, which is shown in
+Figure~\ref{fig-must-call-hierarchy}.
+Any expression of type \<@MustCall(\{\}) Object> also has type
+\<@MustCall(\{"foo"\}) Object>.
+The type \<@MustCall(\{"foo"\}) Object>
+contains objects that need to call \<foo> and objects that need to call
+nothing, but the type does not contain an
+object that needs to call \<bar> (or both \<foo> and \<bar>).
+\<@MustCall(\{"foo", "bar"\}) Object> represents all objects that need to
+call \<foo>, \<bar>, both, or neither; it cannot not represent an object that needs
+to call some other method.
+
+
+\sectionAndLabel{Writing \<@MustCall>/\<@InheritableMustCall> on a class}{must-call-on-class}
+
+As explained in Section~\ref{upper-bound-for-use}, a type
+annotation on a type declaration means that every use of the type has that
+annotation by default. \<@MustCall> annotations on a class declaration should
+usually also be shared by subclasses. To do so, a programmer can either write
+a \<@MustCall> annotation on every subclass, or can write \<@InheritableMustCall>
+on only the class, which will cause the checker to treat every subclass as if
+it has an identical \<@MustCall> annotation.  The latter is the preferred style.
+
+For example, given the following class annotation:
+\begin{Verbatim}
+    package java.net;
+
+    import org.checkerframework.checker.mustcall.qual.InheritableMustCall;
+
+    @InheritableMustCall({"close"}) class Socket { }
+\end{Verbatim}
+any use of \<Socket> or a subclass of \<Socket> defaults to \<@MustCall(\{"close"\}) Socket>.
+
+The \<@InheritableMustCall> annotation is necessary because type
+annotations cannot be made inheritable.  \<@InheritableMustCall> is
+a declaration annotation.
+
+
+%% \sectionAndLabel{Lightweight ownership annotations}{must-call-ownership-annos}
+
+%% TODO: this explanation feels...lacking to me. It doesn't make that much sense IMO
+%% without explaining the must-call consistency checker's usage of Owning/NotOwning.
+%% It's therefore been commented out here. Some version of this needs to appear, either
+%% here or in the documentation of the consistency checker, when that is merged. For now,
+%% this manual page doesn't list @Owning or @NotOwning at all, because they can't be explained
+%% without context - so we've decided to treat them as if they don't exist, for now.
+
+%% In many programs, aliasing creates two or more program elements that represent the same
+%% underlying object on which some methods might need to be called (i.e. whose type has a non-empty
+%% \<@MustCall> qualifier). For example, an \<OutputStream> might be stored in both
+%% a local variable and a field. Typically, the Must Call Checker will over-approximate both
+%% the local variable and the field (and any other pointers to the object) as \<@MustCall(\{"close"\})>:
+%% that is, because all \<OutputStream> objects might need to be closed, each pointer to an
+%% \<OutputStream> might need to be closed, too. In practice, however, some of these pointers can be
+%% ignored: they are \emph{non-owning}. In the example of an \<OutputStream> that is stored in both
+%% a local variable and a field, for instance, it may be the case that the field is non-owning (such
+%% as a cache) and the local is owning; or, it might be the case that the field is owning (an open connection,
+%% for example, that will be closed later), and the local is non-owning.
+
+%% The Must Call Checker supplies two annotations to support this concept: \<@Owning> and \<@NotOwning>.
+%% The Must Call Checker's support for these annotations is limited: method parameters annotated as
+%% \<@NotOwning> are treated as bottom (i.e. \<@MustCall(\{\})>) within the method bodies in which they
+%% appear. A client analysis of the Must Call Checker can use these annotations as a guide for which
+%% pointer to a given object is intended by the programmer as the pointer through which the must-call obligations
+%% of the object are satisfied, but these annotations are only hints.
+
+%% This feature can be disabled by passing the \<-AnoLightweightOwnership> command-line parameter to the Must
+%% Call Checker.
+
+\sectionAndLabel{Assumptions about reflection}{must-call-reflection}
+
+The Must Call Checker is unsound with respect to reflection: it assumes
+that objects instantiated reflectively do not have must-call obligations (i.e., that their
+type is \<@MustCall(\{\})>. If the checker is run with \<-AresolveReflection>, then
+this assumption applies only to types that cannot be resolved.
+
+\sectionAndLabel{Type parameter bounds often need to be annotated}{must-call-type-params}
+
+In an unannotated program, there may be mismatches between defaulted type
+qualifiers that lead to type-checking errors.  That is, the defaulted
+annotations at two different locations may be different and in conflict.  A
+specific example is in code that contains a mix of explicit upper bounds
+with an \<extends> clause and implicit upper bounds without an \<extends>
+clause.
+
+For example, consider the following example (from
+\href{https://github.com/plume-lib/plume-util}{plume-lib/plume-util}) of an
+interface with explicit upper bounds and a client with implicit upper
+bounds:
+\begin{Verbatim}
+  interface Partition<ELEMENT extends @Nullable Object, CLASS extends @Nullable Object> {}
+
+  class MultiRandSelector<T> {
+    private Partition<T, T> eq;
+  }
+\end{Verbatim}
+
+The code contains no \<@MustCall> annotations.
+Running the Must Call Checker on this code produces an error at each use
+of \<T> in \<MultiRandSelector>:
+
+\begin{smaller}
+\begin{Verbatim}
+error: [type.argument] incompatible type argument for type parameter ELEMENT of Partitioner.
+        private Partitioner<T, T> eq;
+                            ^
+  found   : T extends @MustCallUnknown Object
+  required: @MustCall Object
+error: [type.argument] incompatible type argument for type parameter CLASS of Partitioner.
+        private Partitioner<T, T> eq;
+                               ^
+  found   : T extends @MustCallUnknown Object
+  required: @MustCall Object
+\end{Verbatim}
+\end{smaller}
+
+The defaulted Must Call annotations differ.
+\<Partitioner> has an explicit bound, which uses the usual default of \<@MustCall(\{\})>.
+\<MultiRandSelector> has an implicit bound, which defaults to top (that is,
+\<@MustCallUnknown>), as explained in Section~\ref{climb-to-top}.
+
+In many cases, including this one, you can eliminate the false positive
+warning without writing any \<@MustCall> annotations.
+You can either:
+\begin{itemize}
+\item
+  adding an explicit bound to \<MultiRandSelector>, changing its declaration to
+  \code{class MultiRandSelector<T extends Object>}, or
+\item removing the explicit bound from \<Partition>, changing its declaration to
+  \code{interface Partition<ELEMENT, CLASS>}.
+\end{itemize}
+
+% LocalWords:  de subchecker OutputStream MustCall MustCallUnknown
+% LocalWords:  PolyMustCall InheritableMustCall MultiRandSelector
+% LocalWords:  Partitioner
diff --git a/docs/manual/nullness-checker.tex b/docs/manual/nullness-checker.tex
new file mode 100644
index 0000000..51be09c
--- /dev/null
+++ b/docs/manual/nullness-checker.tex
@@ -0,0 +1,2111 @@
+\htmlhr
+\chapterAndLabel{Nullness Checker}{nullness-checker}
+
+If the Nullness Checker issues no warnings for a given program, then
+running that program will never throw a null pointer exception.  In
+other words, the Nullness Checker prevents all \<NullPointerException>s.
+See Section~\ref{nullness-checks} for more details about
+the guarantee and what is checked.
+
+The most important annotations supported by the Nullness Checker are
+\refqualclass{checker/nullness/qual}{NonNull} and
+\refqualclass{checker/nullness/qual}{Nullable}.
+\refqualclass{checker/nullness/qual}{NonNull} is rarely written, because it is
+the default.  All of the annotations are explained in
+Section~\ref{nullness-annotations}.
+
+To run the Nullness Checker, supply the
+\code{-processor org.checkerframework.checker.nullness.NullnessChecker}
+command-line option to javac.  For
+examples, see Section~\ref{nullness-example}.
+
+The NullnessChecker is actually an ensemble of three pluggable
+type-checkers that work together: the Nullness Checker proper (which is the
+main focus of this chapter), the Initialization Checker
+(Section~\ref{initialization-checker}), and the Map Key Checker
+(\chapterpageref{map-key-checker}).
+Their type hierarchies are completely independent, but they work together
+to provide precise nullness checking.
+
+
+\sectionAndLabel{What the Nullness Checker checks}{nullness-checks}
+
+The checker issues a warning in these cases:
+
+\begin{enumerate}
+
+\item
+  When an expression of non-\refqualclass{checker/nullness/qual}{NonNull} type
+  is dereferenced, because it might cause a null pointer exception.
+  Dereferences occur not only when a field is accessed, but when an array
+  is indexed, an exception is thrown, a lock is taken in a synchronized
+  block, and more.  For a complete description of all checks performed by
+  the Nullness Checker, see the Javadoc for
+  \refclass{checker/nullness}{NullnessVisitor}.
+
+\item
+  When an expression of \refqualclass{checker/nullness/qual}{NonNull} type
+  might become null, because it
+  is a misuse of the type:  the null value could flow to a dereference that
+  the checker does not warn about.
+
+  As a special case of an of \refqualclass{checker/nullness/qual}{NonNull}
+  type becoming null, the checker also warns whenever a field of
+  \refqualclass{checker/nullness/qual}{NonNull} type is not initialized in a
+  constructor.
+
+\end{enumerate}
+
+This example illustrates the programming errors that the checker detects:
+
+\begin{Verbatim}
+  @Nullable Object   obj;  // might be null
+  @NonNull  Object nnobj;  // never null
+  ...
+  obj.toString()         // checker warning:  dereference might cause null pointer exception
+  nnobj = obj;           // checker warning:  nnobj may become null
+  if (nnobj == null)     // checker warning:  redundant test
+\end{Verbatim}
+
+Parameter passing and return values are checked analogously to assignments.
+
+The Nullness Checker also checks the correctness, and correct use, of
+initialization (see
+Section~\ref{initialization-checker}) and of map key annotations (see
+\chapterpageref{map-key-checker}).
+
+
+The checker performs additional checks if certain \code{-Alint}
+command-line options are provided.  (See
+Section~\ref{alint} for more details about the \code{-Alint}
+command-line option.)
+
+Section~\ref{checker-guarantees} notes some limitations to guarantees made
+by the Checker Framework.
+
+
+\subsectionAndLabel{Nullness Checker optional warnings}{nullness-lint}
+
+\begin{enumerate}
+\item
+  Options that control soundness:
+
+\begin{itemize}
+\item
+  \label{nullness-lint-soundArrayCreationNullness}%
+  \label{nullness-lint-nonnullarraycomponents}% temporary, for backward compatibility
+  If you supply the \code{-Alint=soundArrayCreationNullness} command-line
+  option, then the checker warns if it encounters an array creation
+  with a non-null component type.
+  See Section~\ref{nullness-arrays} for a discussion.
+  % TODO: this option is temporary and the sound option
+  % should become the default.
+
+\item
+  \label{collection-object-parameters-may-be-null}
+  If you supply the
+  \code{-Astubs=collection-object-parameters-may-be-null.astub}
+  command-line option, then in JDK collection classes, the checker
+  unsoundly permits null as an argument for any key or value formal
+  parameter whose type is \<Object> (instead of the element type).
+  See Section~\ref{nullness-collection-arguments}.
+
+\item
+  \label{nullness-lint-trustarraylenzero}%
+  If you supply the \code{-Alint=trustArrayLenZero} command-line option, then
+  the checker will trust \refqualclasswithparams{common/value/qual}{ArrayLen}{0}
+  annotations. See Section~\ref{nullness-collection-toarray} for a discussion.
+
+\item
+  \label{nullness-assumeKeyFor}%
+  If you supply the \code{-AassumeKeyFor} command-line option, then the
+  checker will unsoundly assume that the argument to \<Map.get> is a key
+  for the receiver map.  It will not do any checking of
+  \refqualclass{checker/nullness/qual}{KeyFor} and related qualifiers.
+\end{itemize}
+
+\item
+  Options that warn about poor code style:
+
+\begin{itemize}
+\item
+  \label{nullness-lint-nulltest}%
+  If you supply the \code{-Alint=redundantNullComparison} command-line option, then the
+  checker warns when a null check is performed against a value that is
+  guaranteed to be non-null, as in \code{("m" == null)}.  Such a check is
+  unnecessary and might indicate a programmer error or misunderstanding.
+  The lint option is disabled by default because sometimes such checks are
+  part of ordinary defensive programming.
+\end{itemize}
+
+\item
+  Options that enable checking modes:
+
+\begin{itemize}
+\item
+  If you supply the \code{-Alint=permitClearProperty} command-line option,
+  then the checker permits calls to
+  \sunjavadoc{java.base/java/lang/System.html\#getProperties()}{System.setProperties()}
+  and calls to
+  \sunjavadoc{java.base/java/lang/System.html\#clearProperty(java.lang.String)}{System.clearProperty}
+  that might clear one of the built-in properties.
+
+  By default, the checker forbids calls to those methods, and also
+  special-cases type-checking of calls to
+  \sunjavadoc{java.base/java/lang/System.html\#getProperty(java.lang.String,java.lang.String)}{System.getProperty()}
+  and
+  \sunjavadoc{java.base/java/lang/System.html\#setProperties(java.util.Properties)}{System.setProperties()}.
+  A call to one of these methods
+  can return null in general, but by default the Nullness Checker treats it
+  as returning non-null if the argument is one of the literal strings
+  listed in the documentation of
+  \sunjavadoc{java.base/java/lang/System.html\#getProperties()}{System.getProperties()}.
+  To make this behavior sound, the Nullness Checker forbids calls that
+  might clear any built-in property, as described above.
+\end{itemize}
+
+\end{enumerate}
+
+
+\sectionAndLabel{Nullness annotations}{nullness-annotations}
+
+The Nullness Checker uses three separate type hierarchies:  one for nullness,
+one for initialization (Section~\ref{initialization-checker}),
+and one for map keys (\chapterpageref{map-key-checker})
+The Nullness Checker has four varieties of annotations:  nullness
+type qualifiers, nullness method annotations, initialization type qualifiers, and
+map key type
+qualifiers.
+
+\subsectionAndLabel{Nullness qualifiers}{nullness-qualifiers}
+
+The nullness hierarchy contains these qualifiers:
+
+\begin{description}
+
+\item[\refqualclass{checker/nullness/qual}{Nullable}]
+  indicates a type that includes the null value.  For example, the Java
+  type \code{Boolean}
+  is nullable:  a variable of type \code{Boolean} always has one of the
+  values \code{TRUE}, \code{FALSE}, or \code{null}.
+  (Since \<@NonNull> is the default type annotation, you would actually
+  write this type as \<@Nullable Boolean>.)
+
+\item[\refqualclass{checker/nullness/qual}{NonNull}]
+  indicates a type that does not include the null value.  The type
+  \code{boolean} is non-null; a variable of type \code{boolean} always has
+  one of the values \code{true} or \code{false}.  The type \code{@NonNull
+    Boolean} is also non-null:  a variable of type \code{@NonNull Boolean}
+  always has one of the values \code{TRUE} or \code{FALSE} --- never
+  \code{null}.  Dereferencing an expression of non-null type can never cause
+  a null pointer exception.
+
+  The \<@NonNull> annotation is rarely written in a program, because it is
+  the default (see Section~\ref{null-defaults}).
+
+\item[\refqualclass{checker/nullness/qual}{PolyNull}]
+  indicates qualifier polymorphism.
+  For a description of qualifier polymorphism, see
+  Section~\ref{method-qualifier-polymorphism}.
+
+\item[\refqualclass{checker/nullness/qual}{MonotonicNonNull}]
+  indicates a reference that may be \code{null}, but if it ever becomes
+  non-\code{null}, then it never becomes \code{null} again.  This is
+  appropriate for lazily-initialized fields, for field initialization that
+  occurs in a lifecycle method other than the constructor (e.g., an
+  override of \<android.app.Activity.onCreate>), and other uses.
+  \<@MonotonicNonNull> is typically written on field types, but not elsewhere.
+
+  \begin{sloppypar}
+  The benefit of \<@MonotonicNonNull> over \<@Nullable> is that after a
+  check of a \<@MonotonicNonNull> field, all subsequent accesses
+  \emph{within that method} can be assumed to be \<@NonNull>, even after
+  arbitrary external method calls that have access to the given field.
+  By contrast, for a \<@Nullable> field, the Nullness Checker assumes that
+  most method calls might set it to \<null>. (Exceptions are calls to
+  methods that are \refqualclass{dataflow/qual}{SideEffectFree} or that
+  have an \refqualclass{checker/nullness/qual}{EnsuresNonNull} or
+  \refqualclass{checker/nullness/qual}{EnsuresNonNullIf} annotation.)
+  \end{sloppypar}
+
+  A \<@MonotonicNonNull> field may be initialized to null, but the
+  field may not be assigned to null anywhere else in the program.  If you
+  supply the \<noInitForMonotonicNonNull> lint flag (for example, supply
+  \<-Alint=noInitForMonotonicNonNull> on the command line), then
+  \<@MonotonicNonNull> fields are not allowed to have initializers at their
+  declarations.
+
+  Use of \<@MonotonicNonNull> on a \emph{static} field is a code smell:  it may
+  indicate poor design.  You should consider whether it is possible to make
+  the field a member field that is set in the constructor.
+
+  In the type system, \<@MonotonicNonNull> is a supertype of \<@NonNull>
+  and a subtype of \<@Nullable>.
+
+\end{description}
+
+Figure~\ref{fig-nullness-hierarchy} shows part of the type hierarchy for the
+Nullness type system.
+(The annotations exist only at compile time; at run time, Java has no
+multiple inheritance.)
+
+\begin{figure}
+\includeimage{nullness}{3.5cm}
+\caption{Partial type hierarchy for the Nullness type system.
+Java's \<Object> is expressed as \<@Nullable Object>.  Programmers can omit
+most type qualifiers, because the default annotation
+(Section~\ref{null-defaults}) is usually correct.
+The Nullness Checker verifies three type hierarchies:  this one for
+nullness, one for initialization (Section~\ref{initialization-checker}),
+and one for map keys (\chapterpageref{map-key-checker}).}
+\label{fig-nullness-hierarchy}
+\end{figure}
+
+
+\subsectionAndLabel{Nullness method annotations}{nullness-method-annotations}
+
+The Nullness Checker supports several annotations that specify method
+behavior.  These are declaration annotations, not type annotations:  they
+apply to the method itself rather than to some particular type.
+
+\begin{description}
+
+\item[\refqualclass{checker/nullness/qual}{RequiresNonNull}]
+  indicates a method precondition:  The annotated method expects the
+  specified variables to be non-null when the
+  method is invoked.  Don't use this for formal parameters (just annotate
+  their type as \<@NonNull>).  \<@RequiresNonNull> is appropriate for
+  a field that is \<@Nullable> in general, but some method requires the
+  field to be non-null.
+
+\item[\refqualclass{checker/nullness/qual}{EnsuresNonNull}]
+\item[\refqualclass{checker/nullness/qual}{EnsuresNonNullIf}]
+  indicates a method postcondition.  With \<@EnsuresNonNull>, the given
+  expressions are non-null after the method returns; this is useful for a
+  method that initializes a field, for example.  With
+  \<@EnsuresNonNullIf>, if the annotated
+  method returns the given boolean value (true or false), then the given
+  expressions are non-null.  See Section~\ref{conditional-nullness} and the
+  Javadoc for examples of their use.
+
+\end{description}
+
+
+\subsectionAndLabel{Initialization qualifiers}{initialization-qualifiers-overview}
+
+The Nullness Checker invokes an Initialization Checker, whose annotations indicate whether
+an object is fully initialized --- that is, whether all of its fields have
+been assigned.
+
+\begin{description}
+\item[\refqualclass{checker/initialization/qual}{Initialized}]
+\item[\refqualclass{checker/initialization/qual}{UnknownInitialization}]
+\item[\refqualclass{checker/initialization/qual}{UnderInitialization}]
+\end{description}
+
+\noindent
+Use of these annotations can help you to type-check more
+code.  Figure~\ref{fig-initialization-hierarchy} shows its type hierarchy.  For
+details, see Section~\ref{initialization-checker}.
+
+
+\subsectionAndLabel{Map key qualifiers}{map-key-qualifiers}
+
+\begin{description}
+\item[\refqualclass{checker/nullness/qual}{KeyFor}]
+\end{description}
+    indicates that a value is a key for a given map --- that is, indicates
+    whether \code{map.containsKey(value)} would evaluate to \code{true}.
+
+This annotation is checked by a Map Key Checker
+(\chapterpageref{map-key-checker}) that the Nullness Checker
+invokes.  The \refqualclass{checker/nullness/qual}{KeyFor} annotation enables
+the Nullness Checker to treat calls to
+\sunjavadoc{java.base/java/util/Map.html\#get(java.lang.Object)}{\code{Map.get}}
+precisely rather than assuming it may always return null.  In particular,
+a call \<mymap.get(mykey)> returns a non-\<null> value if two conditions
+are satisfied:
+\begin{enumerate}
+\item \<mymap>'s values are all non-\<null>; that is, \<mymap> was
+  declared as \code{Map<\emph{KeyType}, @NonNull \emph{ValueType}>}.  Note
+  that \<@NonNull> is the default type, so it need not be written explicitly.
+\item \<mykey> is a key in \<mymap>; that is, \<mymap.containsKey(mykey)>
+  returns \<true>.  You express this fact to the Nullness Checker by
+  declaring \<mykey> as \<@KeyFor("mymap") \emph{KeyType} mykey>.  For a
+  local variable, you generally do not need to write the
+  \<@KeyFor("mymap")> type qualifier, because it can be inferred.
+\end{enumerate}
+\noindent
+If either of these two conditions is violated, then \<mymap.get(mykey)> has
+the possibility of returning \<null>.
+
+The command-line argument \<-AassumeKeyFor> makes the Nullness Checker not
+run the Map Key Checker.  The Nullness Checker will unsoundly assume that
+the argument to \<Map.get> is a key for the receiver map.  That is, the
+second condition above is always considered to be \<true>.
+
+
+\sectionAndLabel{Writing nullness annotations}{writing-nullness-annotations}
+
+\subsectionAndLabel{Implicit qualifiers}{nullness-implicit-qualifiers}
+
+The Nullness Checker
+adds implicit qualifiers, reducing the number of annotations that must
+appear in your code (see Section~\ref{effective-qualifier}).
+For example, enum types are implicitly non-null, so you never need to write
+\<@NonNull MyEnumType>.
+
+If you want details about implicitly-added nullness qualifiers, see the
+implementation of \refclass{checker/nullness}{NullnessAnnotatedTypeFactory}.
+
+
+
+\subsectionAndLabel{Default annotation}{null-defaults}
+
+Unannotated references are treated as if they had a default annotation.
+All types default to
+\<@NonNull>, except that \<@Nullable> is used for casts, locals,
+instanceof, and implicit bounds (see Section~\ref{climb-to-top}).
+A user can choose a different defaulting
+rule by writing a \refqualclass{framework/qual}{DefaultQualifier} annotation on a
+ package, class, or method.  In the example below, fields are defaulted to
+\<@Nullable> instead of \<@NonNull>.
+
+\begin{Verbatim}
+@DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.FIELD)
+class MyClass {
+    Object nullableField = null;
+    @NonNull Object nonNullField = new Object();
+}
+\end{Verbatim}
+
+
+%% Cut this to shorten the section.  Most users won't care about it.
+% %BEGIN LATEX
+% \begin{sloppy}
+% %END LATEX
+% Here are three possible default rules you may wish to use.  Other rules are
+% possible but are not as useful.
+% \begin{itemize}
+% \item
+%   \refqualclass{checker/nullness/qual}{Nullable}:  Unannotated types are regarded as possibly-null, or
+%   nullable.  This default is backward-compatible with Java, which permits
+%   any reference to be null.  You can activate this default by writing
+%   a \code{@DefaultQualifier(Nullable.class)} annotation on a
+%   % package/
+%   class or method
+%   % /variable
+%   declaration.
+% \item
+%   \refqualclass{checker/nullness/qual}{NonNull}:  Unannotated types are treated as non-null.
+%   % This may leads to fewer annotations written in your code.
+%   You can activate this
+%   default via the
+%   \code{@DefaultQualifier(NonNull.class)} annotation.
+% \item
+%   Non-null except locals (NNEL):  Unannotated types are treated as
+%   \refqualclass{checker/nullness/qual}{NonNull}, \emph{except} that the
+%   unannotated raw type of a local variable is treated as
+%   \refqualclass{checker/nullness/qual}{Nullable}.  (Any generic arguments to a
+%   local variable still default to
+%   \refqualclass{checker/nullness/qual}{NonNull}.)  This is the standard
+%   behavior.  You can explicitly activate this default via the
+%   \code{@DefaultQualifier(value=NonNull.class,
+%     locations=\discretionary{}{}{}\{DefaultLocation\discretionary{}{}{}.ALL\_EXCEPT\_LOCALS\})}
+%   annotation.
+%
+%   The NNEL default leads to the smallest number of explicit annotations in
+%   your code~\cite{PapiACPE2008}.  It is what we recommend.  If you do not
+%   explicitly specify a different default, then NNEL is the default.
+% \end{itemize}
+% %BEGIN LATEX
+% \end{sloppy}
+% %END LATEX
+
+
+\subsectionAndLabel{Conditional nullness}{conditional-nullness}
+
+The Nullness Checker supports a form of conditional nullness types, via the
+\refqualclass{checker/nullness/qual}{EnsuresNonNullIf} method annotations.
+The annotation on a method declares that some expressions are non-null, if
+the method returns true (false, respectively).
+
+Consider \sunjavadoc{java.base/java/lang/Class.html}{java.lang.Class}.
+Method
+\sunjavadoc{java.base/java/lang/Class.html\#getComponentType()}{Class.getComponentType()}
+may return null, but it is specified to return a non-null value if
+\sunjavadoc{java.base/java/lang/Class.html\#isArray()}{Class.isArray()} is
+true.
+You could declare this relationship in the following way (this particular
+example is already
+done for you in the annotated JDK that comes with the Checker Framework):
+
+\begin{Verbatim}
+  class Class<T> {
+    @EnsuresNonNullIf(expression="getComponentType()", result=true)
+    public native boolean isArray();
+
+    public native @Nullable Class<?> getComponentType();
+  }
+\end{Verbatim}
+
+A client that checks that a \code{Class} reference is indeed that of an array,
+can then de-reference the result of \code{Class.getComponentType} safely
+without any nullness check.  The Checker Framework source code itself
+uses such a pattern:
+
+\begin{Verbatim}
+    if (clazz.isArray()) {
+      // no possible null dereference on the following line
+      TypeMirror componentType = typeFromClass(clazz.getComponentType());
+      ...
+    }
+\end{Verbatim}
+
+Another example is \sunjavadoc{java.base/java/util/Queue.html\#peek()}{Queue.peek}
+and \sunjavadoc{java.base/java/util/Queue.html\#poll()}{Queue.poll}, which return
+non-null if \sunjavadoc{java.base/java/util/Collection.html\#isEmpty()}{isEmpty}
+returns false.
+
+The argument to \code{@EnsuresNonNullIf} is a Java expression, including method calls
+(as shown above), method formal parameters, fields, etc.; for details, see
+Section~\ref{java-expressions-as-arguments}.
+More examples of the use of these annotations appear in the Javadoc for
+\refqualclass{checker/nullness/qual}{EnsuresNonNullIf}.
+
+
+\subsectionAndLabel{Nullness and array initialization}{nullness-arrays}
+
+Suppose that you declare an array to contain non-null elements:
+
+\begin{Verbatim}
+  Object [] oa = new Object[10];
+\end{Verbatim}
+
+\noindent
+(recall that \<Object> means the same thing as \<@NonNull Object>).
+By default, the Nullness Checker unsoundly permits this code.
+
+To make the Nullness Checker conservatively reject code that may leave a
+non-null value in an array, use the command-line option
+\code{-Alint=soundArrayCreationNullness}.  The option is currently disabled
+because it makes the checker issue many false positive errors.
+% TODO: flip the default after collecting more data.
+
+With the option enabled, you can write your code to create a nullable or
+lazy-nonnull array, initialize
+each component, and then assign the result to a non-null array:
+
+\begin{Verbatim}
+  @MonotonicNonNull Object [] temp = new @MonotonicNonNull Object[10];
+  for (int i = 0; i < temp.length; ++i) {
+    temp[i] = new Object();
+  }
+  @SuppressWarnings("nullness") // temp array is now fully initialized
+  @NonNull Object [] oa = temp;
+\end{Verbatim}
+
+Note that the checker is currently not powerful enough to ensure that
+each array component was initialized. Therefore, the last assignment
+needs to be trusted:  that is, a programmer must verify that it is safe,
+then write a \<@SuppressWarnings> annotation.
+
+% TODO: explain more aspects, give more examples.
+
+
+\subsectionAndLabel{Nullness and conversions from collections to arrays}{nullness-collection-toarray}
+
+% Implemented in org.checkerframework.checker.nullness.CollectionToArrayHeuristics
+
+The semantics of
+\sunjavadoc{java.base/java/util/Collection.html\#toArray(T[])}{Collection.toArray(T[])}
+cannot be captured by the nullness type system syntax. The nullness type of the
+returned array depends on the size of the passed parameter. In particular, the
+returned array component is of type \<@NonNull> if the following conditions
+hold:
+
+\begin{itemize}
+  \item The receiver collection's type argument (that is, the element type) is \<@NonNull>, and
+  \item The passed array size is less than or equal to the collection size. The
+    Nullness Checker uses these heuristics to handle the most common cases:
+
+  \begin{itemize}
+    \item the argument has length 0:
+
+      \begin{itemize}
+        \item an empty array initializer, e.g. \<c.toArray(new String[] \{\})>, or
+
+        \item array creation tree of size 0, e.g. \<c.toArray(new String[0])>.
+      \end{itemize}
+
+    \item array creation tree with a collection \<size()> method invocation as argument
+      \<c.toArray(new String[c.size()])>.
+  \end{itemize}
+
+\end{itemize}
+
+Additionally, when you supply the \code{-Alint=trustArrayLenZero} command-line
+option, a call to \code{Collection.toArray} will be estimated to return an array
+with a non-null component type if the argument is a field access where the field
+declaration has a \refqualclasswithparams{common/value/qual}{ArrayLen}{0}
+annotation.
+This trusts the \<@ArrayLen(0)> annotation, but does not verify it. Run the
+Constant Value Checker (see \chapterpageref{constant-value-checker}) to verify
+that annotation.
+
+Note: The nullness of the returned array doesn't depend on the passed array nullness.
+This is a fact about
+\sunjavadoc{java.base/java/util/Collection.html\#toArray(T[])}{Collection.toArray(T[])},
+not a limitation of this heuristic.
+
+
+\subsectionAndLabel{Run-time checks for nullness}{nullness-runtime-checks}
+
+When you perform a run-time check for nullness, such as \<if (x != null)
+...>, then the Nullness Checker refines the type of \<x> to
+\<@NonNull>.  The refinement lasts until the end of the scope of the test
+or until \<x> may be side-effected.  For more details, see
+Section~\ref{type-refinement}.
+
+
+\subsectionAndLabel{Inference of \code{@NonNull} and \code{@Nullable} annotations}{nullness-inference}
+
+It can be tedious to write annotations in your code.  Tools exist that
+can automatically infer annotations and insert them in your source code.
+(This is different than type qualifier refinement for local variables
+(Section~\ref{type-refinement}), which infers a more specific type for
+local variables and uses them during type-checking but does not insert them
+in your source code.  Type qualifier refinement is always enabled, no
+matter how annotations on signatures got inserted in your source code.)
+
+Your choice of tool depends on what default annotation (see
+Section~\ref{null-defaults}) your code uses.  You only need one of these tools.
+
+\begin{itemize}
+
+\item
+  Inference of \refqualclass{checker/nullness/qual}{Nullable}:
+  %
+  If your code uses the standard CLIMB-to-top default (Section~\ref{climb-to-top}) or
+  the NonNull default, then use the
+  \href{http://plse.cs.washington.edu/daikon/download/doc/daikon.html#AnnotateNullable}{AnnotateNullable}
+  tool of the \href{http://plse.cs.washington.edu/daikon/}{Daikon invariant
+    detector}.
+
+\item
+  Inference of \refqualclass{checker/nullness/qual}{NonNull}:
+  %
+  If your code uses the Nullable default (this is unusual), use one of these tools:
+\begin{itemize}
+% Julia is no longer publicly available.
+% \item
+%   \href{https://juliasoft.com/solutions/}{Julia analyzer},
+\item
+  \href{http://nit.gforge.inria.fr}{Nit: Nullability Inference Tool},
+\item
+  \href{https://jastadd.cs.lth.se/jastadd-tutorial-examples/non-null-types-for-java/}{Non-null
+    checker and inferencer} of the \href{https://jastadd.cs.lth.se/}{JastAdd
+    Extensible Compiler}.
+\end{itemize}
+
+\end{itemize}
+
+
+
+\sectionAndLabel{Suppressing nullness warnings}{suppressing-warnings-nullness}
+
+When the Nullness Checker reports a warning, it's best to change the code
+or its annotations, to eliminate the warning.  Alternately, you can
+suppress the warning, which does not change the code but prevents the
+Nullness Checker from reporting this particular warning to you.
+
+\begin{sloppypar}
+The Checker Framework supplies several ways to suppress warnings, most
+notably the \<@SuppressWarnings("nullness")> annotation (see
+Chapter~\ref{suppressing-warnings}).  An example use is
+\end{sloppypar}
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+    // might return null
+    @Nullable Object getObject(...) { ... }
+
+    void myMethod() {
+      @SuppressWarnings("nullness") // with argument x, getObject always returns a non-null value
+      @NonNull Object o2 = getObject(x);
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+
+The Nullness Checker supports an additional warning suppression string,
+\<nullness:generic.argument>.
+Use of \<@SuppressWarnings("nullness:generic.argument")> causes the Nullness
+Checker to suppress warnings related to misuse of generic type
+arguments.  One use for this key is when a class is declared to take only
+\<@NonNull> type arguments, but you want to instantiate the class with a
+\<@Nullable> type argument, as in \code{List<@Nullable Object>}.
+
+The Nullness Checker also permits you to use assertions or method calls to
+suppress warnings; see below.
+
+% TODO: check whether the SuppressWarnings strings are correct.
+
+
+\subsectionAndLabel{Suppressing warnings with assertions and method calls}{suppressing-warnings-with-assertions}
+
+Occasionally, it is inconvenient or
+verbose to use the \<@SuppressWarnings> annotation.  For example, Java does
+not permit annotations such as \<@SuppressWarnings> to appear on statements.
+In such cases, you can use the \<@AssumeAssertion> string in
+an \<assert> message (see Section~\ref{assumeassertion}).
+
+If you need to suppress a warning within an expression, then
+sometimes writing an assertion is not convenient.  In such a case,
+you can suppress warnings by writing a call to the
+\refmethod{checker/nullness/util}{NullnessUtil}{castNonNull}{-T-} method.
+The rest of this section discusses the \<castNonNull> method.
+
+The Nullness Checker considers both the return value, and also the
+argument, to be non-null after the \<castNonNull> method call.
+The Nullness Checker issues no warnings in any of the following
+code:
+
+\begin{Verbatim}
+  // One way to use castNonNull as a cast:
+  @NonNull String s = castNonNull(possiblyNull1);
+
+  // Another way to use castNonNull as a cast:
+  castNonNull(possiblyNull2).toString();
+
+  // It is possible, but not recommmended, to use castNonNull as a statement:
+  // (It would be better to write an assert statement with @AssumeAssertion
+  // in its message, instead.)
+  castNonNull(possiblyNull3);
+  possiblyNull3.toString();
+\end{Verbatim}
+
+  The \<castNonNull> method throws \<AssertionError> if Java assertions are enabled and
+  the argument is \<null>.  However, it is not intended for general defensive
+  programming; see Section~\ref{defensive-programming}.
+
+  To use the \<castNonNull> method, the \<checker-util.jar> file
+  must be on the classpath at run time.
+
+\begin{sloppypar}
+The Nullness Checker introduces a new method, rather than re-using an
+existing method such as \<org.junit.Assert.assertNotNull(Object)> or
+\<com.google.common.base.Preconditions.checkNotNull(Object)>.  Those
+methods are commonly used for defensive programming, so it is impossible to
+know the programmer's intent when writing them.  Therefore, it is important to
+have a method call that is used only for warning suppression.  See
+Section~\ref{defensive-programming} for a discussion of
+the distinction between warning suppression and defensive programming.
+% Also, different checking tools issue different false warnings that
+% need to be suppressed, so warning suppression needs to be customized for
+% each tool.
+\end{sloppypar}
+
+
+\subsectionAndLabel{Null arguments to collection classes}{nullness-collection-arguments}
+
+For collection methods with \<Object> formal parameter type, such as
+\sunjavadoc{java.base/java/util/Collection.html\#contains(java.lang.Object)}{contains},
+\sunjavadoc{java.base/java/util/AbstractList.html\#indexOf(java.lang.Object)}{indexOf}, and
+\sunjavadoc{java.base/java/util/Collection.html\#remove(java.lang.Object)}{remove},
+the annotated JDK forbids null as an argument.
+
+The reason is that some implementations (like \<ConcurrentHashMap>) throw
+\<NullPointerException> if \<null> is passed.  It would be unsound to
+permit \<null>, because it could lead to a false negative:  the Checker
+Framework issuing no warning even though a NullPointerException can occur
+at run time.
+
+However, many other common implementations permit such calls, so some users
+may wish to sacrifice soundness for a reduced number of false positive
+warnings.  To permit \<null> as an argument to these methods, pass the
+command-line argument
+\<-Astubs=collection-object-parameters-may-be-null.astub>.
+
+
+\sectionAndLabel{Examples}{nullness-example}
+
+\subsectionAndLabel{Tiny examples}{nullness-tiny-examples}
+
+To try the Nullness Checker on a source file that uses the \refqualclass{checker/nullness/qual}{NonNull} qualifier,
+use the following command (where \code{javac} is the Checker Framework compiler that
+is distributed with the Checker Framework, see Section~\ref{javac-wrapper}
+for details):
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  javac -processor org.checkerframework.checker.nullness.NullnessChecker docs/examples/NullnessExample.java
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+\noindent
+Compilation will complete without warnings.
+
+To see the checker warn about incorrect usage of annotations (and therefore the
+possibility of a null pointer exception at run time), use the following command:
+
+\begin{mysmall}
+\begin{Verbatim}
+  javac -processor org.checkerframework.checker.nullness.NullnessChecker docs/examples/NullnessExampleWithWarnings.java
+\end{Verbatim}
+\end{mysmall}
+
+
+\noindent
+The compiler will issue two warnings regarding violation of the semantics of
+\refqualclass{checker/nullness/qual}{NonNull}.
+% in the \code{NonNullExampleWithWarnings.java} file.
+
+
+\subsectionAndLabel{Example annotated source code}{nullness-annotated-library}
+
+Some libraries that are annotated with nullness qualifiers are:
+
+\begin{itemize}
+\item
+The Nullness Checker itself.
+
+\item
+The Java projects in the \href{https://github.com/plume-lib/}
+{plume-lib GitHub organization}.
+Type-checking occurs on each build.
+
+\item
+The
+\href{http://plse.cs.washington.edu/daikon/}{Daikon invariant detector}.
+Run the command \code{make check-nullness}.
+
+\end{itemize}
+
+
+\sectionAndLabel{Tips for getting started}{nullness-getting-started}
+
+Here are some tips about getting started using the Nullness Checker on a
+legacy codebase.  For more generic advice (not specific to the Nullness
+Checker), see Section~\ref{get-started-with-legacy-code}.  Also see the
+\ahreforurl{https://checkerframework.org/tutorial/}{Checker
+Framework tutorial}, which includes an example of using the Nullness Checker.
+
+Your goal is to add \refqualclass{checker/nullness/qual}{Nullable} annotations
+to the types of any variables that can be null.  (The default is to assume
+that a variable is non-null unless it has a \code{@Nullable} annotation.)
+Then, you will run the Nullness Checker.  Each of its errors indicates
+either a possible null pointer exception, or a wrong/missing annotation.
+When there are no more warnings from the checker, you are done!
+
+We recommend that you start by searching the code for occurrences of
+\code{null} in the following locations; when you find one, write the
+corresponding annotation:
+
+\begin{itemize}
+\item
+  in Javadoc:  add \code{@Nullable} annotations to method signatures (parameters and return types).
+% Search for "\*.*\bnull\b"
+\item
+  \code{return null}:  add a \code{@Nullable} annotation to the return type
+  of the given method.
+% Search for "return null" and "return.*? null" and "return.*: null"
+\item
+  \code{\emph{param} == null}:  when a formal parameter is compared to
+  \code{null}, then in most cases you can add a \code{@Nullable} annotation
+  to the formal parameter's type
+\item
+  \code{\emph{TypeName} \emph{field} = null;}:  when a field is initialized to
+  \code{null} in its declaration, then it needs either a
+  \refqualclass{checker/nullness/qual}{Nullable} or a
+  \refqualclass{checker/nullness/qual}{MonotonicNonNull} annotation.  If the field
+  is always set to a non-null value in the constructor, then you can just
+  change the declaration to \code{\emph{Type} \emph{field};}, without an
+  initializer, and write no type annotation (because the default is
+  \<@NonNull>).
+\item
+  declarations of \<contains>, \<containsKey>, \<containsValue>, \<equals>,
+  \<get>, \<indexOf>, \<lastIndexOf>, and \<remove> (with \<Object> as the
+  argument type):
+  change the argument type to \<@Nullable Object>; for \<remove>, also change
+  the return type to \<@Nullable Object>.
+% Emacs code that adds annotations to the argument types:
+% ;;NOT: (tags-query-replace " apply(Object " " apply(@Nullable Object ")
+% (tags-query-replace " \\(get\\|equals\\|remove\\|contains\\|containsValue\\|containsKey\\|indexOf\\|lastIndexOf\\)(Object " " \\1(@Nullable Object ")
+
+\end{itemize}
+
+\noindent
+You should ignore all other occurrences of \code{null} within a method
+body.  In particular, you rarely need to annotate local variables
+(except their type arguments or array element types).
+
+Only after this step should you run
+the Nullness Checker.  The reason is that it is quicker to search for
+places to change than to repeatedly run the checker and fix the errors it
+tells you about, one at a time.
+
+Here are some other tips:
+\begin{itemize}
+\item
+    \begin{sloppypar}
+    In any file where you write an annotation such as \code{@Nullable},
+    don't forget to add \code{import org.checkerframework.checker.nullness.qual.*;}.
+    \end{sloppypar}
+\item
+    To indicate an array that can be null, write, for example: \code{int
+      @Nullable []}. \\
+    By contrast, \code{@Nullable Object []} means a non-null array that
+    contains possibly-null objects.
+\item
+    If you know that a particular variable is definitely not null, but the
+    Nullness Checker estimates that the variable might be null, then you can
+    make the Nullness Checker trust your judgment by writing
+    an assertion (see Section~\ref{assumeassertion}):
+\begin{Verbatim}
+assert var != null : "@AssumeAssertion(nullness)";
+\end{Verbatim}
+\item
+    To indicate that a routine returns the same value every time it is
+    called, use \refqualclass{dataflow/qual}{Pure} (see Section~\ref{type-refinement-purity}).
+\item
+    To indicate a method precondition (a contract stating the conditions
+    under which a client is allowed to call it), you can use annotations
+    such as \refqualclass{checker/nullness/qual}{RequiresNonNull} (see Section~\ref{nullness-method-annotations}).
+\end{itemize}
+
+
+
+\sectionAndLabel{Other tools for nullness checking}{nullness-related-work}
+
+\newcommand{\linktoNonNull}{\refclass{checker/nullness/qual}{NonNull}}
+\newcommand{\linktoNullable}{\refclass{checker/nullness/qual}{Nullable}}
+
+The Checker Framework's nullness annotations are similar to annotations used
+in other
+tools.
+You might prefer to use the Checker Framework because it has a more
+powerful analysis that can warn you about more null pointer errors in your
+code.
+Most of the other tools are bug-finding tools rather than verification tools, since they
+give up precision, soundness, or both in favor of being fast and
+easy to use.  Also
+see Section~\ref{faq-type-checking-vs-bug-detectors} for a comparison to other tools.
+
+If your code is already annotated with a different nullness
+annotation, the Checker Framework can type-check your code.
+It treats annotations from other tools
+as if you had written the corresponding annotation from the
+Nullness Checker, as described in Figure~\ref{fig-nullness-refactoring}.
+If the other annotation is a declaration annotation, it may be moved; see
+Section~\ref{declaration-annotations-moved}.
+
+
+% These lists should be kept in sync with
+% ../../checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnnotatedTypeFactory.java
+\begin{figure}
+\begin{center}
+% The ~ around the text makes things look better in Hevea (and not terrible
+% in LaTeX).
+\begin{tabular}{ll}
+\begin{tabular}{|l|}
+\hline
+ ~android.annotation.NonNull~ \\ \hline
+ ~android.support.annotation.NonNull~ \\ \hline
+ ~androidx.annotation.NonNull~ \\ \hline
+ ~androidx.annotation.RecentlyNonNull~ \\ \hline
+ ~com.sun.istack.internal.NotNull~ \\ \hline
+ ~edu.umd.cs.findbugs.annotations.NonNull~ \\ \hline
+ ~io.reactivex.annotations.NonNull~ \\ \hline
+ ~io.reactivex.rxjava3.annotations.NonNull~ \\ \hline
+ ~javax.annotation.Nonnull~ \\ \hline
+ ~javax.validation.constraints.NotNull~ \\ \hline
+ ~lombok.NonNull~ \\ \hline
+ ~org.checkerframework.checker.nullness.compatqual.NonNullDecl~ \\ \hline
+ ~org.checkerframework.checker.nullness.compatqual.NonNullType~ \\ \hline
+ ~org.codehaus.commons.nullanalysis.NotNull~ \\ \hline
+ ~org.eclipse.jdt.annotation.NonNull~ \\ \hline
+ ~org.eclipse.jgit.annotations.NonNull~ \\ \hline
+ ~org.jetbrains.annotations.NotNull~ \\ \hline
+ ~org.jmlspecs.annotation.NonNull~ \\ \hline
+ ~org.netbeans.api.annotations.common.NonNull~ \\ \hline
+ ~org.springframework.lang.NonNull~ \\ \hline
+\end{tabular}
+&
+$\Rightarrow$
+~org.checkerframework.checker.nullness.qual.NonNull~
+\\
+\
+\\
+\begin{tabular}{|l|l|}
+\hline
+ ~android.annotation.Nullable~ \\ \hline
+ ~android.support.annotation.Nullable~ \\ \hline
+ ~androidx.annotation.Nullable~ \\ \hline
+ ~androidx.annotation.RecentlyNullable~ \\ \hline
+ ~com.sun.istack.internal.Nullable~ \\ \hline
+ ~edu.umd.cs.findbugs.annotations.Nullable~ \\ \hline
+ ~edu.umd.cs.findbugs.annotations.CheckForNull~ \\ \hline
+ ~edu.umd.cs.findbugs.annotations.PossiblyNull~ \\ \hline
+ ~edu.umd.cs.findbugs.annotations.UnknownNullness~ \\ \hline
+ ~io.reactivex.annotations.Nullable~ \\ \hline
+ ~io.reactivex.rxjava3.annotations.Nullable~ \\ \hline
+ ~javax.annotation.Nullable~ \\ \hline
+ ~javax.annotation.CheckForNull~ \\ \hline
+ ~org.checkerframework.checker.nullness.compatqual.NullableDecl~ \\ \hline
+ ~org.checkerframework.checker.nullness.compatqual.NullableType~ \\ \hline
+ ~org.codehaus.commons.nullanalysis.Nullable~ \\ \hline
+ ~org.eclipse.jdt.annotation.Nullable~ \\ \hline
+ ~org.eclipse.jgit.annotations.Nullable~ \\ \hline
+ ~org.jetbrains.annotations.Nullable~ \\ \hline
+ ~org.jmlspecs.annotation.Nullable~ \\ \hline
+ ~org.jspecify.nullness.Nullable~ \\ \hline
+ ~org.jspecify.nullness.NullnessUnspecified~ \\ \hline
+ ~org.netbeans.api.annotations.common.NullAllowed~ \\ \hline
+ ~org.netbeans.api.annotations.common.CheckForNull~ \\ \hline
+ ~org.netbeans.api.annotations.common.NullUnknown~ \\ \hline
+ ~org.springframework.lang.Nullable~ \\ \hline
+\end{tabular}
+&
+$\Rightarrow$
+~org.checkerframework.checker.nullness.qual.Nullable~
+\end{tabular}
+\end{center}
+%BEGIN LATEX
+\vspace{-1.5\baselineskip}
+%END LATEX
+\caption{Correspondence between other nullness annotations and the
+  Checker Framework's annotations.}
+\label{fig-nullness-refactoring}
+\end{figure}
+
+%% Removed, because it's tedious and should be obvious to a decent programmer.
+% Your IDE may be able to do that for you.  Alternately, do the following:
+% \begin{enumerate}
+% \item
+%   replace \<@Nonnull> by \<@NonNull> (note capitalization difference)
+% \item
+%   replace \<@CheckForNull> by \<@Nullable>
+% \item
+%   replace \<@UnknownNullness> by \<@Nullable>
+% \item
+%   convert each single-type import statement (without a ``\<*>'' character)
+%    according to the table above.
+% \item
+%   convert each on-demand import statements, such as ``\<import
+%    edu.umd.cs.findbugs.annotations.*;>''.
+% \begin{itemize}
+%    \item
+%   One approach is to change it into a set of single-type imports,
+%       then convert the relevant ones.
+%    \item
+%   Another approach is to change it according to the table above, then
+%       try to compile and re-introduce the single-type imports as necessary.
+% \end{itemize}
+%    These approaches let you continue to use other annotations in the
+%    \<edu.umd.cs.findbugs.annotations> package, even though you are not using
+%    its nullness annotations.
+% \end{enumerate}
+
+
+The Checker Framework may issue more or fewer errors than another tool.
+This is expected, since each tool uses a different analysis.  Remember that
+the Checker Framework aims at soundness:  it aims to never miss a possible
+null dereference, while at the same time limiting false reports.  Also,
+note SpotBugs's non-standard meaning for \<@Nullable>
+(Section~\ref{findbugs-nullable}).
+
+Java permits you to import at most one annotation of a given name.  For
+example, if you use both \<android.annotation.NonNull> and
+\<lombok.NonNull> in your source code, then you must write at least one of
+them in fully-qualified form, as \<@android.annotation.NonNull> rather than
+as \<@NonNull>.
+
+
+\subsectionAndLabel{Which tool is right for you?}{choosing-nullness-tool}
+
+Different tools are appropriate in different circumstances.
+Section~\ref{faq-type-checking-vs-bug-detectors} compares verification
+tools like the Checker Framework with bug detectors like SpotBugs and Error
+Prone.  In brief, a bug detector is easier to use because it requires fewer
+annotations, but it misses lots of real bugs that a verifier finds.  You
+should use whichever tool is appropriate for the importance of your code.
+
+You may also choose to use multiple tools, especially since each tool
+focuses on different types of errors.  If you know that you will eventually
+want to do verification for some particular task (say, nullness checking),
+there is little point using the nullness analysis of bug detector such as
+SpotBugs first.  It is easier to go straight to using the
+Checker Framework.
+
+If some other tool discovers a nullness error that the Checker
+Framework does not, please report it to us (see
+Section~\ref{reporting-bugs}) so that we can enhance the Checker Framework.
+For example, SpotBugs might detect an error that the Nullness Checker does
+not, if you are using an unnannotated library (including an unannotated
+part of the JDK) and running the Checker Framework in an unsound mode (see
+Section~\ref{checker-options}).
+
+
+\subsectionAndLabel{Incompatibility note about FindBugs and SpotBugs \tt{@Nullable}}{findbugs-nullable}
+
+FindBugs and SpotBugs have a non-standard definition of
+\<@Nullable>.  This treatment is not documented in its own
+% \href{http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/Nullable.html}{Javadoc};
+\href{https://www.javadoc.io/doc/com.github.spotbugs/spotbugs-annotations/latest/edu/umd/cs/findbugs/annotations/Nullable.html}{Javadoc};
+it is different from the definition of \<@Nullable> in every other tool for
+nullness analysis; it means the same thing as \<@NonNull> when applied to a
+formal parameter; and it invariably surprises programmers.  Thus, SpotBugs's
+\<@Nullable> is detrimental rather than useful as documentation.
+In practice, your best bet is to not rely on SpotBugs for nullness analysis,
+even if you find SpotBugs useful for other purposes.
+
+You can skip the rest of this section unless you wish to learn more details.
+
+SpotBugs suppresses all warnings at uses of a \<@Nullable> variable.
+(You have to use \<@CheckForNull> to
+indicate a nullable variable that SpotBugs should check.)  For example:
+
+\begin{Verbatim}
+     // declare getObject() to possibly return null
+     @Nullable Object getObject() { ... }
+
+     void myMethod() {
+       @Nullable Object o = getObject();
+       // SpotBugs issues no warning about calling toString on a possibly-null reference!
+       o.toString();
+     }
+\end{Verbatim}
+
+\noindent
+The Checker Framework does not emulate this non-standard behavior of
+SpotBugs, even if the code uses FindBugs/SpotBugs annotations.
+
+With SpotBugs, you annotate a declaration, which suppresses checking at
+\emph{all} client uses, even the places that you want to check.
+It is better to suppress warnings at only the specific client uses
+where the value is known to be non-null; the Checker Framework supports
+this, if you write \<@SuppressWarnings> at the client uses.
+The Checker Framework also supports suppressing checking at all client uses,
+by writing a \<@SuppressWarnings> annotation at the declaration site.
+Thus, the Checker Framework supports both use cases, whereas SpotBugs
+supports only one and gives the programmer less flexibility.
+
+In general, the Checker Framework will issue more warnings than SpotBugs,
+and some of them may be about real bugs in your program.
+See Section~\ref{suppressing-warnings-nullness} for information about
+suppressing nullness warnings.
+
+FindBugs and SpotBugs made a poor choice of names.  The choice of names should make a
+clear distinction between annotations that specify whether a reference is
+null, and annotations that suppress false warnings.  The choice of names
+should also have been consistent for other tools, and intuitively clear to
+programmers.  The FindBugs/SpotBugs choices make the SpotBugs annotations less
+helpful to people, and much less useful for other tools.
+
+Another problem is that the SpotBugs \<@Nullable> annotation is a
+declaration annotation rather than a type annotation.  This means that it
+cannot be written in important locations such as type arguments, and it is
+misleading when written on a field of array type or a method that returns
+an array.
+
+Overall, it is best to stay away from the SpotBugs nullness annotations and
+analysis, and use a tool with a more principled design.
+
+
+% As background, here is an explanation of the (sometimes surprising)
+% semantics of the FindBugs nullness annotations.
+%
+%  * edu.umd.cs.findbugs.annotations.NonNull     javax.annotation.Nonnull
+%    These mean the obvious thing:   the reference is never null.
+%
+%  * edu.umd.cs.findbugs.annotations.Nullable     javax.annotation.Nullable
+%    This means that the value may be null, but that *all warnings should be
+%    suppressed* regarding its use.  In other words, the value is really
+%    nullable, but clients should treat it as non-null.  For example:
+%
+%      // declare getObject() to possibly return null
+%      @Nullable Object getObject() { ... }
+%
+%      // FindBugs issues no warning about calling toString on a possibly-null reference
+%      getObject().toString();
+%
+%    In the Checker Framework, this corresponds to declaring the method
+%    return value as @Nullable, then suppressing warnings at client uses
+%    that are known to be non-null.  An easy way to suppress the warning
+%    is by adding an assert statement about the return value.
+%
+%    (Alternately, you could declare the method return value as @NonNull, and
+%    suppress warnings within the method definition where it returns null,
+%    but this approach is not recommended because the @NonNull annotation on
+%    the return value would be misleading, and warnings should be suppressed
+%    at particular sites where they are known to be unnecessary, not at all
+%    call sites whatsoever.)
+%
+%  * edu.umd.cs.findbugs.annotations.CheckForNull      javax.annotation.CheckForNull
+%    This means that the value may be null.  To avoid a NullPointerException,
+%    every client should check nullness before dereferencing the value.
+%    In the Checker Framework, this corresponds to @Nullable.
+
+
+\subsectionAndLabel{Relationship to \tt{Optional<T>}}{nullness-vs-optional}
+
+Many null pointer exceptions occur because the programmer forgets to check
+whether a reference is null before dereferencing it.  Java 8's
+\sunjavadoc{java.base/java/util/Optional.html}{\code{Optional<T>}}
+class provides a partial solution:  a programmer must call the \<get> method to
+access the value, and the designers of \<Optional> hope that the syntactic
+occurrence of the \<get> method will remind programmers to first check that
+the value is present.  This is still easy to forget, however.
+
+The Checker Framework contains an Optional Checker (see
+Chapter~\ref{optional-checker}) that guarantees that programmers use
+\<Optional> correctly, such as calling \<isPresent> before calling \<get>.
+
+There are some limitations to the utility of \code{Optional}, which might
+lead to you choose to use regular Java references rather than \<Optional>.
+(For more details, see the article
+\href{https://homes.cs.washington.edu/~mernst/advice/nothing-is-better-than-optional.html}{``Nothing
+  is better than the \<Optional> type''}.)
+
+\begin{itemize}
+\item
+  It is still possible to call \<get> on a non-present \<Optional>, leading
+  to a \code{NoSuchElementException}.  In other words, \code{Optional}
+  doesn't solve the underlying problem --- it merely converts a
+  \code{NullPointerException} into a \code{NoSuchElementException}
+  exception, and in either case your code crashes.
+\item
+  \code{NullPointerException} is still possible in code that uses \code{Optional}.
+\item
+  \code{Optional} adds syntactic complexity, making your code longer and harder to
+  read.
+\item
+  \code{Optional} adds time and space overhead.
+\item
+  \code{Optional} does not address important sources of null pointer
+  exceptions, such as partially-initialized objects and calls to \<Map.get>.
+\end{itemize}
+
+The Nullness Checker does not suffer these limitations.  Furthermore, it
+works with existing code and types, it ensures that you check for null
+wherever necessary, and it infers when the check for null is not necessary
+based on previous statements in the method.
+
+Java's \<Optional> class provides utility routines to reduce clutter when
+using \<Optional>.  The Nullness Checker provides an
+\refclass{checker/nullness/util}{Opt} class that provides all the same methods,
+but written for regular possibly-null Java references.
+To use the \refclass{checker/nullness/util}{Opt} class, the
+\<checker-util.jar> file must be on the classpath at run time.
+
+
+\sectionAndLabel{Initialization Checker}{initialization-checker}
+
+The Initialization Checker determines whether an object is initialized or
+not.  For any object that is not fully initialized, the Nullness Checker
+treats its fields as possibly-null --- even fields annotated as
+\<@NonNull>.
+(The Initialization Checker focuses on \<@NonNull> fields, to detect null
+pointer exceptions when using them.  It does not
+currently ensure that primitives or \<@Nullable> fields are initialized.
+Use the Initialized Fields Checker
+(\chapterpageref{initialized-fields-checker}) to check initialization with
+respect to properties other than nullness.)
+
+Every object's fields start out as null.  By the time the constructor
+finishes executing, the \<@NonNull> fields have been set to a different
+value.  Your code can suffer a NullPointerException when using a
+\<@NonNull> field, if your code uses the field during initialization.
+The Nullness Checker prevents this problem by warning you anytime that you
+may be accessing an uninitialized field.  This check is useful because it
+prevents errors in your code.  However, the analysis can be confusing to
+understand.  If you wish to disable the initialization checks, see
+Section~\ref{initialization-checking-suppressing-warnings}.
+
+
+An object is partially initialized from the time that its constructor starts until its constructor
+finishes.  This is relevant to the Nullness Checker because while the
+constructor is executing --- that is, before initialization completes ---
+a \<@NonNull>
+field may be observed to be null, until that field is set.  In
+particular, the Nullness Checker issues a warning for code like this:
+
+\begin{Verbatim}
+  public class MyClass {
+    private @NonNull Object f;
+    public MyClass(int x) {
+      // Error because constructor contains no assignment to this.f.
+      // By the time the constructor exits, f must be initialized to a non-null value.
+    }
+    public MyClass(int x, int y) {
+      // Error because this.f is accessed before f is initialized.
+      // At the beginning of the constructor's execution, accessing this.f
+      // yields null, even though field f has a non-null type.
+      this.f.toString();
+      f = new Object();
+    }
+    public MyClass(int x, int y, int z) {
+      m();
+      f = new Object();
+    }
+    public void m() {
+      // Error because this.f is accessed before f is initialized,
+      // even though the access is not in a constructor.
+      // When m is called from the constructor, accessing f yields null,
+      // even though field f has a non-null type.
+      this.f.toString();
+    }
+\end{Verbatim}
+
+\noindent
+When a field \<f> is declared with a \refqualclass{checker/nullness/qual}{NonNull}
+type, then code can depend on the fact that the field is not \<null>.
+However, this guarantee does not hold for a partially-initialized object.
+
+The Initialization Checker uses three annotations to indicate whether an object
+is initialized (all its \<@NonNull> fields have been assigned), under
+initialization (its constructor is currently executing), or its
+initialization state is unknown.
+
+These distinctions are mostly relevant within the constructor, or for
+references to \code{this} that escape the constructor (say, by being stored
+in a field or passed to a method before initialization is complete).
+Use of initialization annotations is rare in most code.
+
+
+\subsectionAndLabel{Initialization qualifiers}{initialization-qualifiers}
+
+\begin{figure}
+\includeimage{initialization}{4cm}
+\caption{Partial type hierarchy for the Initialization type system.
+  \<@UnknownInitialization> and \<@UnderInitialization> each take an
+  optional parameter indicating how far initialization has proceeded, and
+  the right side of the figure illustrates its type hierarchy in more detail.}
+\label{fig-initialization-hierarchy}
+\end{figure}
+
+The initialization hierarchy is shown in Figure~\ref{fig-initialization-hierarchy}.
+The initialization hierarchy contains these qualifiers:
+
+\begin{description}
+
+\item[\refqualclass{checker/initialization/qual}{Initialized}]
+  indicates a type that contains a fully-initialized object.  \code{Initialized}
+  is the default, so there is little need for a programmer to write this
+  explicitly.
+
+\item[\refqualclass{checker/initialization/qual}{UnknownInitialization}]
+  indicates a type that may contain a partially-initialized object.  In a
+  partially-initialized object, fields that are annotated as
+  \refqualclass{checker/nullness/qual}{NonNull} may be null because the field
+  has not yet been assigned.
+
+  \<@UnknownInitialization> takes a parameter that is the class the object
+  is definitely initialized up to.  For instance, the type
+  \<@UnknownInitialization(Foo.class)> denotes an object in which every
+  fields declared in \<Foo> or its superclasses is initialized, but other
+  fields might not be.
+  Just \<@UnknownInitialization> is equivalent to
+  \<@UnknownInitialization(Object.class)>.
+
+\item[\refqualclass{checker/initialization/qual}{UnderInitialization}]
+  indicates a type that contains a partially-initialized object that is
+  under initialization --- that is, its constructor is currently executing.
+  It is otherwise the same as \<@UnknownInitialization>.  Within the
+  constructor, \code{this} has
+  \refqualclass{checker/initialization/qual}{UnderInitialization} type until
+  all the \code{@NonNull} fields have been assigned.
+
+\end{description}
+
+  A partially-initialized object (\code{this} in a constructor) may be
+  passed to a helper method or stored in a variable; if so, the method
+  receiver, or the field, would have to be annotated as
+  \<@UnknownInitialization> or as \<@UnderInitialization>.
+
+% However, if the constructor makes
+% a method call (passing \code{this} as a parameter or the receiver), then
+% the called method could observe the object in an illegal state.
+
+If a reference has
+\code{@UnknownInitialization} or \code{@UnderInitialization} type, then all of its \code{@NonNull} fields are treated as
+\refqualclass{checker/nullness/qual}{MonotonicNonNull}:  when read, they are
+treated as being \refqualclass{checker/nullness/qual}{Nullable}, but when
+written, they are treated as being
+\refqualclass{checker/nullness/qual}{NonNull}.
+
+\begin{sloppypar}
+The initialization hierarchy is orthogonal to the nullness hierarchy.  It
+is legal for a reference to be \<@NonNull @UnderInitialization>, \<@Nullable @UnderInitialization>,
+\<@NonNull @Initialized>, or \<@Nullable @Initialized>.  The nullness hierarchy tells
+you about the reference itself:  might the reference be null?  The initialization
+hierarchy tells you about the \<@NonNull> fields in the referred-to object:
+might those fields be temporarily null in contravention of their
+type annotation?
+% It's a figure rather than appearing inline so as not to span page breaks
+% in the printed manual.
+Figure~\ref{fig-initialization-examples} contains some examples.
+\end{sloppypar}
+
+\begin{figure}
+\begin{tabular}{l|l|l}
+Declarations & Expression & Expression's nullness type, or checker error \\ \hline
+\begin{minipage}{1.5in}
+\begin{Verbatim}
+class C {
+  @NonNull Object f;
+  @Nullable Object g;
+  ...
+}
+\end{Verbatim}
+\end{minipage} & & \\ \cline{2-3}
+\<@NonNull @Initialized C a;>
+& \<a> & \<@NonNull> \\
+& \<a.f> & \<@NonNull> \\
+& \<a.g> & \<@Nullable> \\ \cline{2-3}
+\<@NonNull @UnderInitialization C b;>
+& \<b> & \<@NonNull> \\
+& \<b.f> & \<@MonotonicNonNull> \\
+& \<b.g> & \<@Nullable> \\ \cline{2-3}
+\<@Nullable @Initialized C c;>
+& \<c> & \<@Nullable> \\
+& \<c.f> & error: deref of nullable \\
+& \<c.g> & error: deref of nullable \\ \cline{2-3}
+\<@Nullable @UnderInitialization C d;>
+& \<d> & \<@Nullable> \\
+& \<d.f> & error: deref of nullable \\
+& \<d.g> & error: deref of nullable \\
+\end{tabular}
+\caption{Examples of the interaction between nullness and initialization.
+  Declarations are shown at the left for reference, but the focus of the
+  table is the expressions and their nullness type or error.}
+\label{fig-initialization-examples}
+\end{figure}
+
+
+% Does our implementation handle static fields soundly?  NO!  See issue
+% #105.  Maybe document this?
+
+
+\subsectionAndLabel{How an object becomes initialized}{becoming-initialized}
+
+Within the constructor,
+\code{this} starts out with \refqualclass{checker/initialization/qual}{UnderInitialization} type.
+As soon as all of the \refqualclass{checker/nullness/qual}{NonNull} fields
+in class $C$ have been initialized, then \code{this} is treated as
+\refqualclass{checker/initialization/qual}{UnderInitialization}\code{(\emph{C})}.
+This means that \code{this} is still being initialized, but all
+initialization of $C$'s fields is complete, including all fields of supertypes.
+Eventually, when all constructors complete, the type is
+\refqualclass{checker/initialization/qual}{Initialized}.
+
+The Initialization Checker issues an error if the constructor fails to initialize
+any \code{@NonNull} field.  This ensures that the object is in a legal (initialized)
+state by the time that the constructor exits.
+This is different than Java's test for definite assignment (see
+\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-16.html}{JLS ch.16}),
+% , which requires that local
+% variables (and blank \code{final} fields) must be assigned.  Java does not
+% require that non-\code{final} fields be assigned, since
+which does not apply to fields (except blank final ones, defined in
+\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.12.4}{JLS \S 4.12.4}) because fields
+have a default value of null.
+
+
+All \code{@NonNull} fields must either have a
+default in the field declaration, or be assigned in the constructor or in a
+helper method that the constructor calls.  If
+your code initializes (some) fields in a helper method, you will need to
+annotate the helper method with an annotation such as
+\refqualclass{checker/nullness/qual}{EnsuresNonNull}\code{(\{"field1", "field2"\})}
+for all the fields that the helper method assigns.
+
+% TODO:
+% We need an
+%   @EnsuresInitialized("b")
+% that is analogous to
+%   @EnsuresNonNull("b")
+% when we are dealing with a field of primitive type.
+% But, for now just use the same annotation, @EnsuresNonNull, for both purposes.
+
+
+\subsectionAndLabel{\code{@UnderInitialization} examples}{underinitialization-examples}
+
+The most common use for the \<@UnderInitialization> annotation is for a
+helper routine that is called by constructor.  For example:
+
+\begin{Verbatim}
+  class MyClass {
+    Object field1;
+    Object field2;
+    Object field3;
+
+    public MyClass(String arg1) {
+      this.field1 = arg1;
+      init_other_fields();
+    }
+
+    // A helper routine that initializes all the fields other than field1.
+    @EnsuresNonNull({"field2", "field3"})
+    private void init_other_fields(@UnderInitialization(Object.class) MyClass this) {
+      field2 = new Object();
+      field3 = new Object();
+    }
+
+    public MyClass(String arg1, String arg2, String arg3) {
+      this.field1 = arg1;
+      this.field2 = arg2;
+      this.field3 = arg3;
+      checkRep();
+    }
+
+    // Verify that the representation invariants are satisfied.
+    // Works as long as the MyClass fields are initialized, even if the receiver's
+    // class is a subclass of MyClass and not all of the subclass fields are initialized.
+    private void checkRep(@UnderInitialization(MyClass.class) MyClass this) {
+      ...
+    }
+
+
+  }
+\end{Verbatim}
+
+% Most readers can
+% skip this section on first reading; you can return to it once you have
+% mastered the rest of the Nullness Checker.
+
+At the end of the constructor, \<this> is not fully initialized. Rather,
+it is \<@UnderInitialization(\emph{CurrentClass.class})>.
+The reason is that there might be subclasses with uninitialized fields.
+The following example illustrates this:
+
+\begin{Verbatim}
+class A {
+   @NonNull String a;
+   public A() {
+       a = "";
+       // Now, all fields of A are initialized.
+       // However, if this constructor is invoked as part of 'new B()', then
+       // the fields of B are not yet initialized.
+       // If we would type 'this' as @Initialized, then the following call is valid:
+       doSomething();
+   }
+   void doSomething() {}
+}
+
+class B extends A {
+   @NonNull String b;
+   @Override
+   void doSomething() {
+       // Dereferencing 'b' is ok, because 'this' is @Initialized and 'b' @NonNull.
+       // However, when executing 'new B()', this line throws a null-pointer exception.
+       b.toString();
+   }
+}
+\end{Verbatim}
+
+
+\subsectionAndLabel{Partial initialization}{partial-initialization}
+
+So far, we have discussed initialization as if it is an all-or-nothing property:
+an object is non-initialized until initialization completes, and then it is initialized.  The full truth is a bit more complex:  during the
+initialization process an object can be partially initialized, and as the
+object's superclass constructors complete, its initialization status is updated.  The
+Initialization Checker lets you express such properties when necessary.
+
+Consider a simple example:
+
+\begin{Verbatim}
+class A {
+  Object aField;
+  A() {
+    aField = new Object();
+  }
+}
+class B extends A {
+  Object bField;
+  B() {
+    super();
+    bField = new Object();
+  }
+}
+\end{Verbatim}
+
+Consider what happens during execution of \<new B()>.
+
+\begin{enumerate}
+\item \<B>'s constructor begins to execute.  At this point, neither the
+  fields of \<A> nor those of \<B> have been initialized yet.
+\item \<B>'s constructor calls \<A>'s constructor, which begins to execute.
+  No fields of \<A> nor of \<B> have been initialized yet.
+\item \<A>'s constructor completes.  Now, all the fields of \<A> have been
+  initialized, and their invariants (such as that field \<a> is non-null) can be
+  depended on.  However, because \<B>'s constructor has not yet completed
+  executing, the object being constructed is not yet fully initialized.
+\item \<B>'s constructor completes.  The fields declared in \<A> and \<B>
+  are initialized.  However, the type system cannot assume that the object
+  is fully initialized --- there might be a \<class C extends B \{...\}>,
+  and \<B>'s constructor might have been invoked from that.
+\end{enumerate}
+
+At any moment during initialization, the superclasses of a given class
+can be divided into those that have completed initialization and those that
+have not yet completed initialization.  More precisely, at any moment there
+is a point in the class hierarchy such that all the classes above that
+point are fully initialized, and all those below it are not yet
+initialized.  As initialization proceeds, this dividing line between the
+initialized and uninitialized classes moves down the type hierarchy.
+
+The Nullness Checker lets you indicate where the dividing line is between
+the initialized and non-initialized classes.
+The \<@UnderInitialization(\emph{classliteral})>
+indicates the first class that is known to be fully initialized.
+When you write \refqualclass{checker/initialization/qual}{UnderInitialization}\code{(OtherClass.class) MyClass x;}, that
+means that variable \<x> is initialized for \<OtherClass> and its
+superclasses, and \<x> is (possibly) uninitialized for subclasses of \<OtherClass>.
+
+The example above lists 4 moments during construction.  At those moments,
+the type of the object being constructed is:
+
+\begin{enumerate}
+\item
+  \<@UnderInitialization B>
+\item
+  \<@UnderInitialization A>
+\item
+  \<@UnderInitialization(A.class) A>
+  %% Not quite equivalent because the Java (non-qualified) type differs
+  % ; equivalently, \<@UnderInitialization B>
+\item
+  \<@UnderInitialization(B.class) B>
+\end{enumerate}
+
+Note that neither \<@UnderInitialization(A.class) A> nor
+\<@UnderInitialization(A.class) B> may be used where <@Initialized A> is
+required.  Permitting that would be unsound.  For example, consider this
+code, where all types are \<@NonNull> because \<@NonNull> is the default annotation:
+
+\begin{Verbatim}
+class A {
+  Object aField;
+  A() {
+    aField = new Object();
+  }
+  Object get() {
+    return aField;
+  }
+}
+class B extends A {
+  Object bField;
+  B() {
+    super();
+    bField = new Object();
+  }
+  @Override
+  Object get() {
+    return bField;
+  }
+}
+\end{Verbatim}
+
+Given these declarations:
+
+\begin{Verbatim}
+@Initialized A w;
+@Initialized B x;
+@UnderInitialization(A.class) A y;
+@UnderInitialization(A.class) B z;
+\end{Verbatim}
+
+\noindent
+the expressions \<w.get()> and \<x.get()> evaluate to a non-null value, but
+\<y.get()> and \<z.get()> might evaluate to \<null>.
+(\<y.get()> might evaluate to \<null> because the run-time type of \<y>
+might be \<B>.  That is, \<y> and \<z> might refer to the same value, just
+as \<w> and \<x> might refer to the same value.)
+
+
+\subsectionAndLabel{Method calls from the constructor}{initialization-constructor}
+
+Consider the following incorrect program.
+
+\begin{Verbatim}
+class A {
+  Object aField;
+  A() {
+    aField = new Object();
+    process(5);  // illegal call
+  }
+  public void process(int arg) { ... }
+}
+\end{Verbatim}
+
+The call to \<process()> is not legal.
+\<process()> is declared to be called on a fully-initialized receiver, which is
+the default if you do not write a different initialization annotation.
+At the call to \<process()>, all the fields of \<A> have been set,
+but \<this> is not fully initialized because fields in subclasses of \<A> have
+not yet been set.  The type of \<this> is \<@UnderInitialization(A.class)>,
+meaning that \<this> is partially-initialized, with the \<A> part of
+initialization done but the initialization of subclasses not yet complete.
+
+The Initialization Checker output indicates this problem:
+
+\begin{Verbatim}
+Client.java:7: error: [method.invocation] call to process(int) not allowed on the given receiver.
+    process(5);  // illegal call
+           ^
+  found   : @UnderInitialization(A.class) A
+  required: @Initialized A
+\end{Verbatim}
+
+Here is a subclass and client code that crashes with a \<NullPointerException>.
+
+\begin{Verbatim}
+class B extends A {
+  List<Integer> processed;
+  B() {
+    super();
+    processed = new ArrayList<Integer>();
+  }
+  @Override
+  public void process(int arg) {
+    super();
+    processed.add(arg);
+  }
+}
+class Client {
+  public static void main(String[] args) {
+    new B();
+  }
+}
+\end{Verbatim}
+
+You can correct the problem in multiple ways.
+
+One solution is to not call methods that can be overridden from the
+constructor:  move the call to \<process()> to after the constructor has
+completed.
+
+Another solution is to change the declaration of \<process()>:
+
+\begin{Verbatim}
+  public void process(@UnderInitialization(A.class) A this, int arg) { ... }
+\end{Verbatim}
+
+If you choose this solution, you will need to rewrite the definition of
+\<B.process()> so that it is consistent with the declared receiver type.
+
+A non-solution is to prevent subclasses from overriding \<process()> by
+using \<final> on the method.  This doesn't work because even if
+\<process()> is not overridden, it might call other methods that are
+overridden.
+
+As final classes cannot have subclasses, they can be handled more
+flexibly: once all fields of the final class have been
+initialized, \<this> is fully initialized.
+
+% TODO: One could imagine having an annotation indicating that the routine
+% is "truly final":  it is final and it never calls, directly, or
+% indirectly, any routine that might be overridden.  I'm not sure how you
+% would confirm that, given the existence of callbacks from other code.
+
+
+
+\subsectionAndLabel{Initialization of circular data structures}{circular-initialization}
+
+There is one final aspect of the initialization type system to be
+considered:  the rules governing reading and writing to objects that are
+currently under initialization (both reading from fields of objects under
+initialization, as well as writing objects under initialization to fields).
+By default, only fully-initialized objects can be stored in a field of
+another object.  If this was the only option, then it would not be possible
+to create circular data structures (such as a doubly-linked list) where
+fields have a \refqualclass{checker/nullness/qual}{NonNull} type.  However,
+the annotation
+\refqualclass{checker/initialization/qual}{NotOnlyInitialized} can be used to
+indicate that a field can store objects that are currently under initialization.
+In this case, the rules for reading and writing to that field become a little
+bit more interesting, to soundly support circular structures.
+
+The rules for reading from a
+\refqualclass{checker/initialization/qual}{NotOnlyInitialized} field
+are summarized in Figure~\ref{fig-init-read-field}.  Essentially, nothing is
+known about the initialization status of the value returned unless the receiver
+was \refqualclass{checker/initialization/qual}{Initialized}.
+
+\begin{figure}
+\centering
+\begin{tabular}{l|l|l}
+  \<x.f>&\<f> is \<@NonNull>& \<f> is \<@Nullable>\\ \hline
+  \<x> is \<@Initialized> & \<@Initialized @NonNull> & \<@Initialized @Nullable> \\
+  \<x> is \<@UnderInitialization> & \<@UnknownInitialization @Nullable> & \<@UnknownInitialization @Nullable> \\
+  \<x> is \<@UnknownInitialization> & \<@UnknownInitialization @Nullable> & \<@UnknownInitialization @Nullable> \\
+\end{tabular}
+\caption{Initialization rules for reading a \refqualclass{checker/initialization/qual}{NotOnlyInitialized} field \<f>.}
+\label{fig-init-read-field}
+\end{figure}
+
+Similarly, Figure~\ref{fig-init-write-field} shows under which conditions
+an assignment \<x.f = y> is allowed for a
+\refqualclass{checker/initialization/qual}{NotOnlyInitialized} field \<f>.
+If the receiver \<x> is
+\refqualclass{checker/initialization/qual}{UnderInitialization}, then any
+\<y> can be of any initialization state.  If \<y> is known to be
+fully initialized, then any receiver is allowed.  All other assignments
+are disallowed.
+
+\begin{figure}
+\centering
+\begin{tabular}{l|ccc}
+  \<x.f = y>&\<y> is \<@Initialized>& \<y> is \<@UnderInitialization>& \<y> is \<@UnknownInitialization>\\ \hline
+  \<x> is \<@Initialized> & yes & no & no \\
+  \<x> is \<@UnderInitialization> & yes & yes & yes \\
+  \<x> is \<@UnknownInitialization> & yes & no & no \\
+\end{tabular}
+\caption{Rules for deciding when an assignment \<x.f = y> is allowed for a
+\refqualclass{checker/initialization/qual}{NotOnlyInitialized} field \<f>.}
+\label{fig-init-write-field}
+\end{figure}
+
+These rules allow for the safe initialization of circular structures.  For instance,
+consider a doubly linked list:
+
+\begin{Verbatim}
+  class List<T> {
+    @NotOnlyInitialized
+    Node<T> sentinel;
+
+    public List() {
+      this.sentinel = new Node<>(this);
+    }
+
+    void insert(@Nullable T data) {
+      this.sentinel.insertAfter(data);
+    }
+
+    public static void main() {
+      List<Integer> l = new List<>();
+      l.insert(1);
+      l.insert(2);
+    }
+  }
+
+  class Node<T> {
+    @NotOnlyInitialized
+    Node<T> prev;
+
+    @NotOnlyInitialized
+    Node<T> next;
+
+    @NotOnlyInitialized
+    List parent;
+
+    @Nullable
+    T data;
+
+    // for sentinel construction
+    Node(@UnderInitialization List parent) {
+      this.parent = parent;
+      this.prev = this;
+      this.next = this;
+    }
+
+    // for data node construction
+    Node(Node<T> prev, Node<T> next, @Nullable T data) {
+      this.parent = prev.parent;
+      this.prev = prev;
+      this.next = next;
+      this.data = data;
+    }
+
+    void insertAfter(@Nullable T data) {
+      Node<T> n = new Node<>(this, this.next, data);
+      this.next.prev = n;
+      this.next = n;
+    }
+  }
+\end{Verbatim}
+
+% \paragraphAndLabel{Example}{initialization-example}
+%
+% As another example, consider the following 12 declarations, where class A
+% extends Object and class B extends A:
+%
+% \begin{Verbatim}
+%     @UnderInitialization(Object.class) Object uOo;
+%     @Initialized Object o;
+%
+%     @UnderInitialization(Object.class) A uOa;
+%     @UnderInitialization(A.class) A uAa;
+%     @Initialized A nraA;
+%
+%     @UnderInitialization(Object.class) B uOb;
+%     @UnderInitialization(A.class) B uAb;
+%     @UnderInitialization(B.class) B uBb;
+%     @Initialized B b;
+% \end{Verbatim}
+%
+% In the following table, the type in cell C1 is a supertype of the type in
+% cell C2 if:  C1 is at least as high and at least as far left in the table
+% as C2 is.  For example, \<nraA>'s type is a supertype of those of \<rB>,
+% \<nraB>, \<nrbB>, \<a>, and \<b>.  (The empty cells on the top row are real
+% types, but are not expressible.  The other empty cells are not interesting
+% types.)
+%
+% \noindent
+% \begin{tabular}{|c|c|c|}
+%
+% \hline
+%     \<@UnderInitialization Object rO;>
+% &
+% &
+% \\
+% \hline
+%
+%     \<@Initialized(Object.class) Object nroO;>
+% &
+% \begin{minipage}{2in}
+% \begin{Verbatim}
+% @UnderInitialization A rA;
+% @Initialized(Object.class) A nroA;
+% \end{Verbatim}
+% \end{minipage}
+% &
+%     \<@Initialized(Object.class) B nroB;>
+% \\
+% \hline
+%
+% &
+%     \<@Initialized(A.class) A nraA;>
+% &
+% \begin{minipage}{1.75in}
+% \begin{Verbatim}
+% @UnderInitialization B rB;
+% @Initialized(A.class) B nraB;
+% \end{Verbatim}
+% \end{minipage}
+% \\
+% \hline
+%
+% &
+% &
+%     \<@Initialized(B.class) B nrbB;>
+% \\
+% \hline
+%
+%     \<Object o;>
+% &
+%     \<A a;>
+% &
+%     \<B b;>
+% \\
+% \hline
+% \end{tabular}
+
+
+
+% \urldef{\jlsconstructorbodyurl}{\url}{https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.8.7}
+% (Recall that the superclass constructor is called on the first line, or is
+% inserted automatically by the compiler before the first line, see
+% \href{\jlsconstructorbodyurl}{JLS \S8.8.7}.)
+
+
+
+\subsectionAndLabel{How to handle warnings}{initialization-warnings}
+
+
+\subsubsectionAndLabel{``error:  the constructor does not initialize fields: \ldots''}{initialization-warnings-constructor}
+
+Like any warning, ``error:  the constructor does not initialize fields:
+\ldots'' indicates that either your annotations are incorrect or your code
+is buggy.  You can fix either the annotations or the code.
+
+\begin{itemize}
+\item
+  Declare the field as \refqualclass{checker/nullness/qual}{Nullable}.  Recall
+  that if you did not write an annotation, the field defaults to
+  \refqualclass{checker/nullness/qual}{NonNull}.
+\item
+  Declare the field as \refqualclass{checker/nullness/qual}{MonotonicNonNull}.
+  This is appropriate if the field starts out as \<null> but is later set
+  to a non-null value.  You may then wish to use the
+  \refqualclass{checker/nullness/qual}{EnsuresNonNull} annotation to indicate
+  which methods set the field, and the
+  \refqualclass{checker/nullness/qual}{RequiresNonNull} annotation to indicate
+  which methods require the field to be non-null.
+\item
+  Initialize the field in the constructor or in the field's initializer, if
+  the field should be initialized.  (In this case, the Initialization
+  Checker has found a bug!)
+
+  Do \emph{not} initialize the field to an arbitrary non-null value just to
+  eliminate the warning.  Doing so degrades your code:  it introduces a
+  value that will confuse other programmers, and it converts a clear
+  NullPointerException into a more obscure error.
+\end{itemize}
+
+\subsubsectionAndLabel{``call to \ldots\ is not allowed on the given receiver''}{initialization-warnings-receiver}
+
+If your code calls an instance method from a constructor, you may see a
+message such as the following:
+
+\begin{Verbatim}
+MyFile.java:123: error: call to initHelper() not allowed on the given receiver.
+    initHelper();
+              ^
+  found   : @UnderInitialization(com.google.Bar.class) @NonNull MyClass
+  required: @Initialized @NonNull MyClass
+\end{Verbatim}
+
+The problem is that the current object (\<this>) is under initialization,
+but the receiver formal parameter (Section~\ref{faq-receiver}) of method
+\<initHelper()> is implicitly annotated as
+\refqualclass{checker/initialization/qual}{Initialized}.  If
+\<initHelper()> doesn't depend on its receiver being initialized --- that
+is, it's OK to call \<x.initHelper> even if \<x> is not initialized ---
+then you can indicate that by using
+\refqualclass{checker/initialization/qual}{UnderInitialization} or
+\refqualclass{checker/initialization/qual}{UnknownInitialization}.
+
+\begin{Verbatim}
+class MyClass {
+  void initHelper(@UnknownInitialization MyClass this, String param1) { ... }
+}
+\end{Verbatim}
+
+\noindent
+You are likely to want to annotate \<initHelper()> with
+\refqualclass{checker/nullness/qual}{EnsuresNonNull} as well; see
+Section~\ref{nullness-method-annotations}.
+
+You may get the ``call to \ldots\ is not allowed on the given receiver''
+error even if your constructor has already initialized all the fields.
+For this code:
+
+\begin{Verbatim}
+public class MyClass {
+  @NonNull Object field;
+  public MyClass() {
+    field = new Object();
+    helperMethod();
+  }
+  private void helperMethod() {
+  }
+}
+\end{Verbatim}
+
+\noindent
+the Nullness Checker issues the following warning:
+
+\begin{Verbatim}
+MyClass.java:7: error: call to helperMethod() not allowed on the given receiver.
+    helperMethod();
+                ^
+  found   : @UnderInitialization(MyClass.class) @NonNull MyClass
+  required: @Initialized @NonNull MyClass
+1 error
+\end{Verbatim}
+
+\begin{sloppypar}
+The reason is that even though the object under construction has had all
+the fields declared in \<MyClass> initialized, there might be a subclass of
+\<MyClass>.  Thus, the receiver of \<helperMethod> should be declared as
+\<@UnderInitialization(MyClass.class)>, which says that initialization has
+completed for all the \<MyClass> fields but may not have been completed
+overall.  If \<helperMethod> had been a public method that could also be called after
+initialization was actually complete, then the receiver should have type
+\<@UnknownInitialization>, which is the supertype of
+\<@UnknownInitialization> and \<@UnderInitialization>.
+\end{sloppypar}
+
+
+\subsectionAndLabel{Suppressing warnings}{initialization-checking-suppressing-warnings}
+
+To suppress most warnings related to partially-initialized values, use the
+warning suppression string ``initialization''.
+You can write \<@SuppressWarnings("initialization")>
+on a field, constructor, or
+class, or pass the command-line argument
+\<-AsuppressWarnings=initialization> when
+running the Nullness Checker.
+(For more about suppressing warnings, see
+\chapterpageref{suppressing-warnings}).  You will no longer get a guarantee
+of no null pointer exceptions, but you can still use the Nullness Checker
+to find most of the null pointer problems in your code.
+
+This suppresses warnings that are specific to the Initialization Checker,
+but does not suppress warnings issued by the Checker Framework itself, such
+as about assignments or method overriding.
+
+It is not possible to completely disable initialization checking while
+retaining nullness checking.  That is because
+of an implementation detail of the Nullness and Initialization Checkers:
+they are actually the same checker, rather than being two separate checkers
+that are aggregated together.
+
+
+\subsectionAndLabel{More details about initialization checking}{initialization-checking}
+
+
+\paragraphAndLabel{Use of method annotations}{initialization-checking-method-annotations}
+
+A method with a non-initialized receiver may assume that a few fields (but not all
+of them) are non-null, and it sometimes sets some more fields to non-null
+values.  To express these concepts, use the
+\refqualclass{checker/nullness/qual}{RequiresNonNull},
+\refqualclass{checker/nullness/qual}{EnsuresNonNull}, and
+\refqualclass{checker/nullness/qual}{EnsuresNonNullIf} method annotations;
+see Section~\ref{nullness-method-annotations}.
+
+
+\paragraphAndLabel{Source of the type system}{initialization-checking-type-system}
+
+The type system enforced by the Initialization Checker is based on
+``Freedom Before Commitment''~\cite{SummersM2011}.  Our implementation
+changes its initialization modifiers (``committed'', ``free'', and
+``unclassified'') to ``initialized'', ``unknown initialization'', and
+``under initialization''.  Our implementation also has several
+enhancements.  For example, it supports partial initialization (the
+argument to the \<@UnknownInitialization> and \<@UnderInitialization>
+annotations).  The benefit (in terms of reduced false positive
+initialization warnings) from supporting partial initialization is
+greater than the benefit from adopting the Freedom Before Commitment system.
+
+
+
+% LocalWords:  NonNull plugin quals un NonNullExampleWithWarnings java ahndrich
+% LocalWords:  NotNull IntelliJ FindBugs Nullable TODO Alint nullable NNEL JSR
+% LocalWords:  DefaultLocation Nullness PolyNull nullness AnnotateNullable JLS
+% LocalWords:  Daikon JastAdd javac DefaultQualifier boolean MyEnumType NonRaw
+% LocalWords:  NullnessAnnotatedTypeFactory NullnessVisitor MonotonicNonNull
+% LocalWords:  inferencer Nonnull CheckForNull UnknownNullness rawtypes de ch
+% LocalWords:  castNonNull NullnessUtil assertNotNull codebases checkNotNull
+% LocalWords:  Nullability typeargs nulltest EnsuresNonNullIf listFiles faq
+% LocalWords:  isDirectory AssertionError intraprocedurally SuppressWarnings rB
+% LocalWords:  FindBugs's getObject RequiresNonNull EnsuresNonNull KeyFor
+% LocalWords:  nonnull EnsuresNonNull arg orElse isPresent bField qual ccc
+% LocalWords:  keySet getField keyfor param TypeName containsValue indexOf nraA
+% LocalWords:  lastIndexOf deref getProperty getProperties classliteral MyClass
+% LocalWords:  typeofthis nraB nrbB rO nroO nroB containsKey enum NullAway
+% LocalWords:  JUnit's field1 field2 superclasses Foo C1 C2 PolyRaw type''
+% LocalWords:  NullnessChecker redundantNullComparison instanceof runtime
+% LocalWords:  noInitForMonotonicNonNull UnknownInitialization isArray
+% LocalWords:  UnderInitialization getComponentType AsuppressWarnings util
+% LocalWords:  isEmpty AssumeAssertion cleanroom varargs OtherClass Astubs
+% LocalWords:  mymap mykey KeyType ValueType lineSeparator receiver''
+% LocalWords:  dereferenced dereference Dereferencing initializers astub
+% LocalWords:  initHelper helperMethod NoSuchElementException classpath
+% LocalWords:  soundArrayCreationNullness lombok findbugs CurrentClass
+% LocalWords:  NotOnlyInitialized underinitialization Commitment'' toarray
+% LocalWords:  unclassified'' trustArrayLenZero ArrayLen toArray
+% LocalWords:  ConcurrentHashMap permitClearProperty setProperties
+% LocalWords:  clearProperty AassumeKeyFor
diff --git a/docs/manual/optional-checker.tex b/docs/manual/optional-checker.tex
new file mode 100644
index 0000000..c573664
--- /dev/null
+++ b/docs/manual/optional-checker.tex
@@ -0,0 +1,124 @@
+\htmlhr
+\chapterAndLabel{Optional Checker for possibly-present data}{optional-checker}
+
+Java 8 introduced the \sunjavadoc{java.base/java/util/Optional.html}{Optional}
+class, a container that is either empty or contains a non-null value.
+
+Using \<Optional> is intended to help programmers remember to check whether
+data is present or not.  However, \<Optional> itself is prone to misuse.
+The article
+\href{https://homes.cs.washington.edu/~mernst/advice/nothing-is-better-than-optional.html}{Nothing
+  is better than the \<Optional> type} gives reasons to use
+regular nullable references rather than \<Optional>.  However, if you do use
+\<Optional>, then the Optional Checker will help you avoid
+\<Optional>'s pitfalls.
+
+Stuart Marks gave
+\href{https://stuartmarks.wordpress.com/2016/09/27/vjug24-session-on-optional/}{7
+  rules} to avoid problems with Optional:
+\begin{enumerate}
+\item
+  Never, ever, use \<null> for an \<Optional> variable or return value.
+\item
+  Never use \sunjavadoc{java.base/java/util/Optional.html\#get()}{Optional.get()} unless you can prove that the Optional is present.
+\item
+  Prefer alternative APIs over
+  \sunjavadoc{java.base/java/util/Optional.html\#isPresent()}{Optional.isPresent()}
+  and \sunjavadoc{java.base/java/util/Optional.html\#get()}{Optional.get()}.
+\item
+  It's generally a bad idea to create an \<Optional> for the specific
+  purpose of chaining methods from it to get a value.
+\item
+  If an Optional chain has a nested \<Optional> chain, or has an
+  intermediate result of \<Optional>, it's probably too complex.
+\item
+  Avoid using \<Optional> in fields, method parameters, and collections.
+\item
+  Don't use an \<Optional> to wrap any collection type (\<List>, \<Set>,
+  \<Map>).  Instead, use an empty collection to represent the absence of
+  values.
+\end{enumerate}
+
+Rule \#1 is guaranteed by the Nullness Checker
+(\chapterpageref{nullness-checker}).
+Rules \#2--\#7 are guaranteed by the Optional Checker, described in this chapter.
+(Exception:  Rule \#5 is not yet implemented and will be checked by the
+Optional Checker in the future.)
+% They are all AST checks that would be easy to add later.
+
+Use of the Optional Checker guarantees that your program will not suffer a
+\<NullPointerException> nor a \<NoSuchElementException> when calling
+methods on an expression of \<Optional> type.
+
+
+\sectionAndLabel{How to run the Optional Checker}{optional-run-checker}
+
+\begin{Verbatim}
+javac -processor optional MyFile.java ...
+\end{Verbatim}
+
+
+\sectionAndLabel{Optional annotations}{optional-annotations}
+
+These qualifiers make up the Optional type system:
+
+\begin{description}
+
+% alternate name: PossiblyAbsent.  But, the Optional Javadoc is careful
+% never to use the term "absent", and it's nice parallelism to have
+% "Present" in the names of all the annotations.
+\item[\refqualclass{checker/optional/qual}{MaybePresent}]
+  The annotated \<Optional> container may or may not contain a value.
+  This is the default type, so programmers do not have to write it.
+
+\item[\refqualclass{checker/optional/qual}{Present}]
+  The annotated \<Optional> container definitely contains a (non-null) value.
+
+\item[\refqualclass{checker/optional/qual}{OptionalBottom}]
+  The annotated expression evaluates to \<null> rather than to an \<Optional> container.
+  Programmers rarely write this annotation.
+
+\item[\refqualclass{checker/optional/qual}{PolyPresent}]
+  indicates qualifier polymorphism.
+  For a description of qualifier polymorphism, see
+  Section~\ref{method-qualifier-polymorphism}.
+
+\end{description}
+
+The subtyping hierarchy of the Optional Checker's qualifiers is shown in
+Figure~\ref{fig-optional-hierarchy}.
+
+\begin{figure}
+\includeimage{optional-subtyping}{3.5cm}
+\caption{The subtyping relationship of the Optional Checker's qualifiers.}
+\label{fig-optional-hierarchy}
+\end{figure}
+
+
+\sectionAndLabel{What the Optional Checker guarantees}{optional-guarantees}
+
+The Optional Checker guarantees that your code will not throw an exception
+due to use of an absent \<Optional> where a present \<Optional> is needed.
+More specifically, the Optional Checker will issue a warning if you call
+\sunjavadoc{java.base/java/util/Optional.html\#get()
+}{get}
+or
+\sunjavadoc{java.base/java/util/Optional.html\#orElseThrow(java.util.function.Supplier)}{orElseThrow}
+on a \<@MaybePresent Optional> receiver, because each of these methods
+throws an exception if the receiver is an absent
+\<Optional>.
+
+The Optional Checker does not check nullness properties, such as requiring
+that the argument to
+\sunjavadoc{java.base/java/util/Optional.html\#of(T)}{of}
+is non-null or guaranteeing that the result of
+\sunjavadoc{java.base/java/util/Optional.html\#get()}{get}
+is non-null.  To obtain such a guarantee, run both the Optional Checker and
+the Nullness Checker (\chapterpageref{nullness-checker}).
+
+As with any checker, the guarantee is subject to certain limitations (see
+Section~\ref{checker-guarantees}).
+
+
+%  LocalWords:  isPresent NoSuchElementException MaybePresent PolyPresent
+%%  LocalWords:  orElseThrow nullable
diff --git a/docs/manual/propkey-checker.tex b/docs/manual/propkey-checker.tex
new file mode 100644
index 0000000..311bc4a
--- /dev/null
+++ b/docs/manual/propkey-checker.tex
@@ -0,0 +1,215 @@
+\htmlhr
+\chapterAndLabel{Property File Checker}{propkey-checker}
+
+The Property File Checker ensures that a property file or resource bundle (both
+of which act like maps from keys to values) is only accessed with valid keys.
+Accesses without a valid key either return \code{null} or a default value, which
+can lead to a \code{NullPointerException} or hard-to-trace behavior.
+The Property File Checker (Section \refwithpage{genpropkey-checker}) ensures
+that the used keys are found in the corresponding property file or resource
+bundle.
+
+We also provide two specialized checkers.
+An Internationalization Checker (Section \refwithpage{i18n-checker})
+verifies that code is properly internationalized.
+A Compiler Message Key Checker (Section \refwithpage{compilermsgs-checker})
+verifies that compiler message keys used in the Checker Framework are
+declared in a property file.
+This is an example of a simple specialization of the property
+file checker, and the Checker Framework source code shows how it is used.
+
+It is easy to customize the property key checker for other related purposes.
+Take a look at the source code of the Compiler Message Key Checker and adapt it for
+your purposes.
+
+
+
+\sectionAndLabel{General Property File Checker}{genpropkey-checker}
+
+The general Property File Checker ensures that a resource key is located
+in a specified property file or resource bundle.
+
+
+The annotation \refqualclass{checker/propkey/qual}{PropertyKey}
+indicates that the qualified \code{CharSequence} is a valid key
+found in the property file or resource bundle.
+You do not need to annotate \code{String} literals.
+The checker looks up every \code{String} literal in the specified
+property file or resource bundle, and adds annotations as appropriate.
+
+If you pass a \code{CharSequence} (including \<String>) variable to be
+eventually used as a key, you
+also need to annotate all these variables with \code{@PropertyKey}.
+
+
+The checker can be invoked by running the following
+command:
+
+\begin{Verbatim}
+  javac -processor org.checkerframework.checker.propkey.PropertyKeyChecker
+        -Abundlenames=MyResource MyFile.java ...
+\end{Verbatim}
+
+You must specify the resources, which map keys to strings.
+The checker supports two types of resource:
+resource bundles and property files.  You can specify one or both of the
+following two command-line options:
+
+\begin{enumerate}
+
+\item \code{-Abundlenames=\emph{resource\_name}}
+
+  \emph{resource\_name} is the name of the resource to be used with
+  \sunjavadoc{java.base/java/util/ResourceBundle.html\#getBundle(java.lang.String,java.util.Locale,java.lang.ClassLoader)}{ResourceBundle.getBundle()}.
+  The checker uses the default \code{Locale} and \code{ClassLoader} in the
+  compilation system.
+  (For a tutorial about \code{ResourceBundle}s, see
+  \myurl{https://docs.oracle.com/javase/tutorial/i18n/resbundle/concept.html}.)
+  % WAS:  http://java.sun.com/developer/technicalArticles/Intl/ResourceBundles/
+  % https://docs.oracle.com/javase/tutorial/ui/features/i18n.html is not what
+  % I am looking for to replace it.
+  Multiple resource bundle names are separated by colons '\code{:}'.
+
+\item \code{-Apropfiles=\emph{prop\_file}}
+
+  \emph{prop\_file} is the name of a properties file that maps
+  keys to values.  The file format is described in
+  the Javadoc for
+  \sunjavadoc{java.base/java/util/Properties.html\#load(java.io.Reader)}{Properties.load()}.
+  Multiple files are separated by colons '\code{:}'.
+
+\end{enumerate}
+
+
+
+\sectionAndLabel{Internationalization Checker (I18n Checker)}{i18n-checker}
+
+The Internationalization Checker, or I18n Checker, verifies that your code is properly
+internationalized.  Internationalization is the process of designing software so that
+it can be adapted to different languages and locales without needing to change the code.
+Localization is the process of adapting internationalized software to specific languages
+and locales.
+
+Internationalization is sometimes called i18n, because the word starts with ``i'',
+ends with ``n'', and has 18 characters in between.  Localization is similarly
+sometimes abbreviated as l10n.
+
+The checker focuses on one aspect of internationalization:  user-visible text
+should be presented in the user's own language, such as English, French, or
+German.  This is achieved by looking up keys in a localization resource,
+which maps keys to user-visible text.  For instance, one version of a
+resource might map \code{"CANCEL\_STRING"} to
+\code{"Cancel"}, and another version of the same resource might map
+\code{"CANCEL\_STRING"} to \code{"Abbrechen"}.
+
+There are other aspects to localization, such as formatting of dates (3/5
+vs.~5/3 for March 5), that the checker does not check.
+
+The Internationalization Checker verifies these two properties:
+
+\begin{enumerate}
+
+\item
+  Any user-visible text should be obtained from a localization resource.
+  For example, \code{String} literals should not be output to the user.
+
+\item
+  When looking up keys in a localization resource, the key should exist in
+  that resource.  This check catches incorrect or misspelled localization
+  keys.
+
+\end{enumerate}
+
+If you use the Internationalization Checker, you may want to also use the
+Internationalization Format String Checker, or I18n Format String Checker
+(Chapter~\ref{i18n-formatter-checker}).
+It verifies that internationalization format strings are well-formed and
+used with arguments of the proper type, so that
+\sunjavadoc{java.base/java/text/MessageFormat.html\#format(java.lang.String,java.lang.Object...)}{MessageFormat.format}
+does not fail at run time.
+
+\subsectionAndLabel{Internationalization annotations}{i18n-annotations}
+
+The Internationalization Checker supports two annotations:
+
+\begin{description}
+\item[\refqualclass{checker/i18n/qual}{Localized}]
+indicates that the qualified
+\code{CharSequence} is a message that has been localized and/or formatted with
+respect to the used locale.
+
+\item[\refqualclass{checker/i18n/qual}{LocalizableKey}]
+indicates that the
+qualified \code{CharSequence} or \code{Object} is a valid key found in the
+localization resource.
+This annotation is a specialization of the \code{@PropertyKey} annotation, that
+gets checked by the general Property Key Checker.
+\end{description}
+
+You may need to add the \code{@Localized} annotation to more methods in the
+JDK or other libraries, or in your own code.
+
+
+\subsectionAndLabel{Running the Internationalization Checker}{i18n-running}
+
+The Internationalization Checker can be invoked by running the following
+command:
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  javac -processor org.checkerframework.checker.i18n.I18nChecker -Abundlenames=MyResource MyFile.java ...
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+You must specify the localization resource, which maps keys to user-visible
+text.  Like the general Property Key Checker, the Internationalization Checker
+supports two types of localization resource:
+\code{ResourceBundle}s using the
+\code{-Abundlenames=\emph{resource\_name}} option
+or property files using the
+\code{-Apropfiles=\emph{prop\_file}} option.
+
+
+
+\sectionAndLabel{Compiler Message Key Checker}{compilermsgs-checker}
+
+The Checker Framework uses compiler message keys to output error messages.
+These keys are substituted by localized strings for user-visible error messages.
+Using keys instead of the localized strings in the source code enables easier
+testing, as the expected message keys can stay unchanged while the localized
+strings can still be modified.
+We use the Compiler Message Key Checker to ensure that all internal
+keys are correctly localized.
+Instead of using the Property File Checker, we use a specialized checker,
+giving us more precise documentation of the intended use of \code{String}s.
+
+The single annotation used by this checker is
+\refqualclass{checker/compilermsgs/qual}{CompilerMessageKey}.
+The Checker Framework is completely annotated;
+for example, class \code{org.checkerframework.framework.source.Result}
+uses \code{@CompilerMessageKey} in methods \code{failure} and \code{warning}.
+For most users of the Checker Framework there will be no need to annotate any
+\code{String}s, as the checker looks up all \code{String} literals and adds
+annotations as appropriate.
+
+The Compiler Message Key Checker can be invoked by running the following
+command:
+
+\begin{Verbatim}
+  javac -processor org.checkerframework.checker.compilermsgs.CompilerMessagesChecker
+        -Apropfiles=messages.properties MyFile.java ...
+\end{Verbatim}
+
+You must specify the resource, which maps compiler message keys to user-visible
+text.  The checker supports the same options as the general property key checker.
+Within the Checker Framework we only use property files,
+so the \code{-Apropfiles=\emph{prop\_file}} option should be used.
+
+% LocalWords:  NullPointerException genpropkey compilermsgs propkey PropertyKey
+% LocalWords:  Abundlenames getBundle ResourceBundle ClassLoader Apropfiles
+% LocalWords:  Abbrechen LocalizableKey CompilerMessageKey java I18n i18n
+%  LocalWords:  l10n
diff --git a/docs/manual/purity-checker.tex b/docs/manual/purity-checker.tex
new file mode 100644
index 0000000..7cbf2a8
--- /dev/null
+++ b/docs/manual/purity-checker.tex
@@ -0,0 +1,116 @@
+\htmlhr
+\chapterAndLabel{Purity Checker}{purity-checker}
+
+The Purity Checker identifies methods that have no side effects, that
+return the same value each time they are called on the same argument, or both.
+
+Purity analysis aids type refinement (Section~\ref{type-refinement}).
+
+All checkers utilize purity annotations on called methods.
+You do not need to run the Purity Checker directly.
+However you may run just the Purity Checker by
+supplying the following command-line options to javac:
+\code{-processor org.checkerframework.framework.util.PurityChecker}.
+
+
+\sectionAndLabel{Purity annotations}{purity-annotations}
+
+\begin{description}
+
+\item[\refqualclass{dataflow/qual}{SideEffectFree}]
+  indicates that the method has no externally-visible side effects.
+
+\item[\refqualclass{dataflow/qual}{Deterministic}]
+  indicates that if the method is called multiple times with identical
+  arguments, then it returns the identical result according to \<==>
+  (not just according to \<equals()>).
+
+\item[\refqualclass{dataflow/qual}{Pure}]
+  indicates that the method is both \<@SideEffectFree> and \<@Deterministic>.
+
+\end{description}
+
+If you supply the command-line option \<-AsuggestPureMethods>, then the
+Checker Framework will suggest methods that can be marked as
+\<@SideEffectFree>, \<@Deterministic>, or \<@Pure>.
+
+
+\sectionAndLabel{Purity annotations are trusted}{purity-trusted}
+
+Currently, purity annotations are trusted.  Purity annotations on called
+methods affect type-checking of client code.  However, you can make a
+mistake by writing \<@SideEffectFree> on the declaration of a method that
+is not actually side-effect-free or by writing \<@Deterministic> on the
+declaration of a method that is not actually deterministic.
+
+To enable
+checking of the annotations, supply the command-line option
+\<-AcheckPurityAnnotations>.  It is not enabled by default because of a high false
+positive rate.  In the future, after a new purity-checking analysis is
+implemented, the Checker Framework will default to checking purity
+annotations.
+
+
+\sectionAndLabel{Overriding methods must respect specifications in superclasses}{purity-override}
+
+If a method in a superclass has a purity annotation, then every overriding
+definition must also have that purity annotation (or a stronger one).
+
+Here is an example error if this requirement is violated:
+
+\begin{mysmall}
+\begin{Verbatim}
+MyClass.java:1465: error: int hashCode() in MyClass cannot override int hashCode(Object this) in java.lang.Object;
+attempting to use an incompatible purity declaration
+    public int hashCode() {
+               ^
+  found   : []
+  required: [SIDE_EFFECT_FREE, DETERMINISTIC]
+\end{Verbatim}
+\end{mysmall}
+
+\noindent
+The reason for the error is that the \<Object> class is annotated as:
+
+\begin{Verbatim}
+class Object {
+  ...
+  @Pure int hashCode() { ... }
+}
+\end{Verbatim}
+
+\noindent
+(where \refqualclass{dataflow/qual}{Pure} means both
+\refqualclass{dataflow/qual}{SideEffectFree} and
+\refqualclass{dataflow/qual}{Deterministic}).  Every overriding
+definition, including those in your program, must use be at least as strong
+a specification.  For \<hashCode>, every overriding definition must be
+annotated as \<@Pure>.
+
+You can fix the definition by adding \<@Pure> to your method definition.
+Alternately, you can suppress the warning.
+You can suppress each such warning individually using
+\<@SuppressWarnings("purity.overriding")>,
+or you can use the \<-AsuppressWarnings=purity.overriding>
+command-line argument to suppress all such warnings.
+% The \<-AassumeSideEffectFree> command-line argument suppresses the
+% warning, but it does more too; don't mention it here.
+In the future, the Checker Framework will support inheriting annotations
+from superclass definitions.
+
+
+\sectionAndLabel{Suppressing warnings}{purity-suppress-warnings}
+
+The command-line options \<-AassumeSideEffectFree>,
+\<-AassumeDeterministic>, \<-AassumePure> make the Checker Framework unsoundly
+assume that \emph{every} called method is side-effect-free, is
+deterministic, or is both, respectively.  This can make
+flow-sensitive type refinement much more effective, since method calls will
+not cause the analysis to discard information that it has learned.
+However, this option can mask real errors.  It is most appropriate when you
+are starting out annotating a project, or if you are using the Checker
+Framework to find some bugs but not to give a guarantee that no more errors
+exist of the given type.
+
+%%  LocalWords:  AsuggestPureMethods AcheckPurityAnnotations
+%%  LocalWords:  AsuppressWarnings AassumeSideEffectFree
diff --git a/docs/manual/reflection-checker.tex b/docs/manual/reflection-checker.tex
new file mode 100644
index 0000000..6f66e03
--- /dev/null
+++ b/docs/manual/reflection-checker.tex
@@ -0,0 +1,291 @@
+\htmlhr
+
+% Arguments are: class name, formal parameter list
+\def\reflectionAnno#1#2{\refqualclass{common/reflection/qual}{#1}\code{#2}}
+
+
+\chapterAndLabel{Reflection resolution}{reflection-resolution}
+
+A call to
+\sunjavadoc{java.base/java/lang/reflect/Method.html\#invoke(java.lang.Object,java.lang.Object...)}{Method.invoke}
+might reflectively invoke any method.  That method might place requirements
+on its formal parameters, and it might return any value.  To reflect these
+facts, the annotated JDK contains
+conservative annotations for \<Method.invoke>.
+These conservative library annotations often cause a checker to issue false
+positive warnings when type-checking code that uses reflection.
+
+If you supply the \<-AresolveReflection> command-line option, the Checker
+Framework attempts to resolve reflection.  At each call to \<Method.invoke>
+or \<Constructor.newInstance>, the Checker Framework first soundly estimates
+which methods might be invoked at run time.  When type-checking the call, the
+Checker Framework uses the library annotations for the possibly-invoked
+methods, rather than the imprecise one for \<Method.invoke>.
+
+If the estimate of invoked methods is small,
+the checker issues fewer false positive warnings.
+If the estimate of invoked methods is large, these types may be no better than the
+conservative library annotations.
+
+Reflection resolution is disabled by default, because it increases the time
+to type-check a program.
+% TODO: By how much, and how and when was that computed?
+You should enable reflection resolution with the \<-AresolveReflection>
+command-line option if, for some call site of \<Method.invoke> or
+\<Constructor.newInstance> in your program:
+\begin{enumerate}
+\item
+  the conservative library annotations on \<Method.invoke> or
+  \<Constructor.newInstance> cause false positive warnings,
+\item
+  the set of possibly-invoked methods or constructors can be known at
+  compile time,
+  and
+\item
+  the reflectively invoked methods/constructors are on the class path at
+  compile time.
+\end{enumerate}
+
+Reflection resolution does not change your source code or generated code.
+In particular, it does not replace the \<Method.invoke> or
+\<Constructor.newInstance> calls.
+
+The command-line option \<-AresolveReflection=debug> outputs verbose
+information about the reflection resolution process, which may be useful
+for debugging.
+
+Section~\ref{reflection-examples} gives an example of reflection resolution.
+Then,
+Section~\ref{methodval-and-classval-checkers} describes the MethodVal
+and ClassVal Checkers, which reflection resolution uses internally.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\sectionAndLabel{Reflection resolution example}{reflection-examples}
+
+Consider the following example, in which the Nullness Checker employs
+reflection resolution to avoid issuing a false positive warning.
+
+\begin{Verbatim}
+public class LocationInfo {
+    @NonNull Location getCurrentLocation() {  ...  }
+}
+
+public class Example {
+    LocationInfo privateLocation = ... ;
+    String getCurrentCity() throws Exception {
+        Method getCurrentLocationObj = LocationInfo.class.getMethod("getCurrentLocation");
+        Location currentLocation = (Location) getCurrentLocationObj.invoke(privateLocation);
+        return currentLocation.nameOfCity();
+    }
+}
+\end{Verbatim}
+
+When reflection resolution is not enabled, the Nullness Checker uses conservative
+annotations on the \<Method.invoke> method signature:
+
+\quad \<{\bfseries @Nullable} Object invoke({\bfseries @NonNull} Object recv, {\bfseries @NonNull} Object ... args)>
+
+
+This causes the Nullness Checker to issue the following warning even though
+\<currentLocation> cannot be null.
+
+\begin{Verbatim}
+error: [dereference.of.nullable] dereference of possibly-null reference currentLocation
+        return currentLocation.nameOfCity();
+               ^
+1 error
+\end{Verbatim}
+
+\begin{sloppypar}
+When reflection resolution is enabled, the MethodVal Checker infers that the \<@MethodVal> annotation for \<getCurrentLocationObj>  is:
+\end{sloppypar}
+
+\quad \<@MethodVal(className="LocationInfo", methodName="getCurrentLocation", params=0)>
+
+Based on this \<@MethodVal> annotation, the reflection resolver determines that
+the reflective method call represents a call to \<getCurrentLocation> in class
+\<LocationInfo>.
+The reflection resolver uses this information to provide the
+following precise procedure summary to the Nullness Checker, for this
+call site only:
+
+\quad \<{\bfseries @NonNull} Object invoke({\bfseries @NonNull} Object recv, {\bfseries @Nullable} Object ... args)>
+
+Using this more precise signature, the Nullness Checker does not issue the false positive warning shown above.
+
+
+\sectionAndLabel{MethodVal and ClassVal Checkers}{methodval-and-classval-checkers}
+
+The implementation of reflection resolution internally uses the ClassVal
+Checker (Section~\ref{classval-checker}) and the MethodVal Checker
+(Section~\ref{methodval-checker}).  They are similar to the Constant
+Value Checker (Section~\ref{constant-value-checker}) in that their
+annotations estimate the run-time value of an expression.
+
+In some cases, you may need to write annotations such as
+\refqualclass{common/reflection/qual}{ClassVal},
+\refqualclass{common/reflection/qual}{MethodVal},
+\refqualclass{common/value/qual}{StringVal}, and
+\refqualclass{common/value/qual}{ArrayLen} to aid in reflection
+resolution.
+Often, though, these annotations can be inferred
+(Section~\ref{methodval-and-classval-inference}).
+
+
+\subsectionAndLabel{ClassVal Checker}{classval-checker}
+
+The ClassVal Checker defines the following annotations:
+
+\begin{description}
+\item[\reflectionAnno{ClassVal}{(String[] value)}]
+If an expression has \<@ClassVal> type with a single argument,
+then its exact run-time value is known at compile time.
+For example, \<@ClassVal("java.util.HashMap")>
+indicates that the \<Class> object represents the \<java.util.HashMap> class.
+
+If multiple arguments are given, then the expression's run-time value is
+known to be in that set.
+
+Each argument is a ``fully-qualified binary name''
+(\refqualclass{checker/signature/qual}{FqBinaryName}):  a primitive or binary name
+(\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1}{JLS
+  \S 13.1}), possibly followed by array brackets.
+
+\item[\reflectionAnno{ClassBound}{(String[] value)}]
+If an expression has \<@ClassBound> type, then its run-time value is known
+to be upper-bounded by that type.
+For example,
+\<@ClassBound("java.util.HashMap")> indicates that the \<Class> object
+represents \<java.util.HashMap> or a subclass of it.
+
+If multiple arguments are given, then the run-time value is equal to or a
+subclass of some class in that set.
+
+Each argument is a ``fully-qualified binary name''
+(\refqualclass{checker/signature/qual}{FqBinaryName}):  a primitive or binary name
+(\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1}{JLS
+  \S 13.1}), possibly followed by array brackets.
+
+\item[\reflectionAnno{UnknownClass}{}] Indicates that there is no
+  compile-time information about the run-time value of the class --- or
+  that the Java type is not \<Class>.
+  This is the default qualifier, and it may not be written in source code.
+
+\item[\reflectionAnno{ClassValBottom}{}] Type given to the \<null> literal.
+  It may not be written in source code.
+\end{description}
+\begin{figure}
+\includeimage{classval}{6cm}
+\caption{Partial type hierarchy for the ClassVal type system. The type qualifiers in gray (\<@UnknownClass>
+and \<@ClassValBottom>) should never be written in source code; they are used internally by the type system.}
+\label{fig-classval-hierarchy}
+\end{figure}
+
+\subsubsectionAndLabel{Subtyping rules}{classval-subtyping-rules}
+Figure~\ref{fig-classval-hierarchy} shows part of the type hierarchy of  the
+ClassVal type system.
+\<@ClassVal(A)> is a subtype of \<@ClassVal(B)> if A is a subset of B.
+\<@ClassBound(A)> is a subtype of \<@ClassBound(B)> if A is a subset of B.
+\<@ClassVal(A)> is a subtype of \<@ClassBound(B)> if A is a subset of B.
+
+
+\subsectionAndLabel{MethodVal Checker}{methodval-checker}
+
+The MethodVal Checker defines the following annotations:
+
+\begin{description}
+\item[\reflectionAnno{MethodVal}{(String[] className, String[] methodName, int[] params)}]
+Indicates that an expression of type \<Method> or \<Constructor> has a
+run-time value in a given set.  If the set has size $n$, then each of
+\<@MethodVal>'s arguments is an array of size $n$, and the $i$th method in the set is
+represented by \{ className[i], methodName[i], params[i] \}.
+For a constructor, the method name is ``\code{<init>}''.
+% to avoid an additional annotation for constructors.
+
+Consider the following example:
+
+\begin{Verbatim}
+@MethodVal(className={"java.util.HashMap", "java.util.HashMap"},
+           methodName={"containsKey", "containsValue"},
+           params={1, 1})
+\end{Verbatim}
+
+\noindent
+This \<@MethodVal> annotation indicates that the \<Method>
+is either \<HashMap.containsKey> with 1 formal parameter or
+\<HashMap.containsValue> with 1 formal parameter.
+
+The \<@MethodVal> type qualifier indicates the number of
+parameters that the method takes, but not their type.  This means that the
+Checker Framework's reflection resolution cannot distinguish among
+overloaded methods.
+
+\item[\reflectionAnno{UnknownMethod}{}] Indicates that there is no
+  compile-time information about the run-time value of the method --- or
+  that the Java type is not \<Method> or \<Constructor>.
+  This is the default qualifier, and it may not be written in source code.
+
+\item[\reflectionAnno{MethodValBottom}{}] Type given to the \<null> literal.
+  It may not be written in source code.
+\end{description}
+
+\begin{figure}
+\includeimage{methodval}{3.5cm}
+\caption{Partial type hierarchy for the MethodVal type system. The type qualifiers in gray (\<@UnknownMethod>
+and \<@MethodValBottom>) should never be written in source code; they are used internally by the type system.}
+\label{fig-methodval-hierarchy}
+\end{figure}
+
+\subsubsectionAndLabel{Subtyping rules}{methodval-subtyping-rules}
+Figure~\ref{fig-methodval-hierarchy} shows part of the type hierarchy of  the
+MethodVal type system.  \<@MethodVal(classname=CA, methodname=MA, params=PA)> is a subtype of
+\<@MethodVal(classname=CB, methodname=MB, params=PB)> if
+
+\[\forall \textrm{indexes} i \exists \textrm{an index} j:  CA[i] = CB[j], MA[i] = MA[j], and PA[i] = PB[j]\]
+
+\noindent
+where CA, MA, and PA are lists of equal size and CB, MB, and PB are lists of equal size.
+
+
+
+\subsectionAndLabel{MethodVal and ClassVal inference}{methodval-and-classval-inference}
+
+The developer rarely has to write \<@ClassVal> or \<@MethodVal>
+annotations, because the Checker Framework infers them according to
+Figure~\ref{fig:reflection-inference}.  Most readers can skip this
+section, which explains the inference rules.
+
+\input{reflection-inference-rules}
+
+The ClassVal Checker infers the exact class name (\<@ClassVal>) for a
+\<Class> literal (\<C.class>), and for a static method call (e.g.,
+\<Class.forName(arg)>, \<ClassLoader.loadClass(arg)>, ...) if the argument is a
+statically computable expression.  In contrast, it infers an upper bound
+(\<@ClassBound>) for instance method calls (e.g., \<obj.getClass()>).
+
+The MethodVal Checker infers \<@MethodVal> annotations for \<Method> and
+\<Constructor> types that have been created using a method call to Java's Reflection
+API\@:
+\begin{itemize}
+    \item \code{Class.getMethod(String name, Class<?>...~paramTypes)}
+    \item \code{Class.getConstructor(Class<?>...~paramTypes)}
+\end{itemize}
+
+Note that an exact class name is necessary to precisely resolve
+reflectively-invoked constructors since a constructor in a subclass does not
+override a constructor in its superclass. This means that the MethodVal Checker
+does not infer a \<@MethodVal> annotation for \<Class.getConstructor> if the
+type of that class is \<@ClassBound>. In contrast, either an exact class name or a bound
+is adequate to resolve reflectively-invoked methods because of the subtyping
+rules for overridden methods.
+
+
+%%  LocalWords:  java AresolveReflection newInstance MethodVal ClassVal
+%%  LocalWords:  StringVal ArrayLen jls ClassBound UnknownClass params
+%%  LocalWords:  ClassValBottom className methodName init containsKey arg
+%%  LocalWords:  containsValue UnknownMethod MethodValBottom classname
+%%  LocalWords:  methodname forName ClassLoader loadClass getClass recv
+%%  LocalWords:  getMethod paramTypes getConstructor args currentLocation
+%%  LocalWords:  getCurrentLocationObj LocationInfo getCurrentLocation
+%%  LocalWords:  methodval classval
diff --git a/docs/manual/reflection-inference-rules.tex b/docs/manual/reflection-inference-rules.tex
new file mode 100644
index 0000000..1da59b3
--- /dev/null
+++ b/docs/manual/reflection-inference-rules.tex
@@ -0,0 +1,56 @@
+\begin{figure}[t!]
+
+% Column definitions to control alignment and width
+%\newcolumntype{L}[1]{>{\raggedright\let\newline\\\arraybackslash\hspace{0pt}}m{#1}}
+%\newcolumntype{C}[1]{>{\centering\let\newline\\\arraybackslash\hspace{0pt}}m{#1}}
+
+\begin{center}
+\begin{small}
+%BEGIN IMAGE
+\def\ruleHeader#1{\framebox[.4\linewidth]{#1}}
+\def\text#1{\textrm{#1}}
+\def\sym#1{\textit{#1}}
+\newcommand{\inferred}[2][\relax]{\textbf{#2\<(>}#1\textbf{\<)>}}
+\newcommand{\breakabledot}{\discretionary{}{.}{.}}
+
+\newcommand{\const}{\text{ is a compile-time constant string }}
+\newcommand{\condspace}{\quad}
+\newcommand{\halfcondspace}{\ }
+
+\def\forName{\<Class.forName>}
+\def\loadClass{\<ClassLoader.loadClass>}
+
+\begin{tabular}{c}
+%\ruleHeader{Inference of \textbf{\<@ClassVal>}} \\[\gap]
+\infer{\<C.class> : \inferred[bn]{\<@ClassVal>}}
+      {\sym{bn}\text{ is the binary name of }\<C>} \\
+\\
+\infer{\<Class.forName(>s\<)>: \inferred[\nu]{\<@ClassVal>}}
+      {s: \<@StringVal(>\nu\<)>} \\
+\\[4pt]
+%\ruleHeader{Inference of \textbf{\<@ClassBound>}}\\[\gap]
+\infer{e\<.getClass()> : \inferred[bn]{\<@ClassBound>}}
+      {e: \tau \condspace \sym{bn}\text{ is the binary name of }\tau} \\
+\\[4pt]
+%\ruleHeader{Inference of \textbf{\<@MethodVal>}} \\[\gap]
+\infer{e\<.getMethod(>s\<,>p\<)> : \inferred[\<cn=>\nu\<,mn=>\mu\<,np=>\pi]{\<@MethodVal>}}
+      {(e: \<@ClassBound(>\nu\<)> \halfcondspace \vee \halfcondspace e: \<@ClassVal(>\nu\<)>)
+        \\ s:\<@StringVal(>\mu\<)> \condspace p: \<@ArrayLen(>\pi\<)>} \\
+\\
+\infer{e\<.getConstructor(>p\<)> : \inferred[\<cn=>\nu,mn=\code{"<init>"},np=\pi]{\<@MethodVal>}}
+      {e: \<@ClassVal(>\nu\<)> \condspace p: \<@ArrayLen(>\pi\<)>} \\
+
+\end{tabular}
+%END IMAGE
+%HEVEA\imageflush
+\end{small}
+\end{center}
+\caption{\label{fig:reflection-inference}%
+Example inference rules for @ClassVal, @ClassBound, and @MethodVal.
+Additional rules exist for expressions with similar semantics but that call
+methods with different names or signatures.
+%\todo{I added more examples and a more detailed description to the section, so
+%    this does not seem to be necessary here: (e.g., @ClassVal is also inferred for
+%calls to \<ClassLoader.loadClass>).}
+}
+\end{figure}
diff --git a/docs/manual/regex-checker.tex b/docs/manual/regex-checker.tex
new file mode 100644
index 0000000..72ae054
--- /dev/null
+++ b/docs/manual/regex-checker.tex
@@ -0,0 +1,221 @@
+\htmlhr
+\chapterAndLabel{Regex Checker for regular expression syntax}{regex-checker}
+
+The Regex Checker prevents, at compile-time, use of syntactically invalid
+regular expressions and access of invalid capturing groups.
+
+A regular expression, or regex, is a pattern for matching certain strings
+of text.  In Java, a programmer writes a regular expression as a string.
+The syntax of regular expressions is complex, so it is easy to make a
+mistake.  It is also easy to accidentally use a regex feature from another
+language that is not supported by Java (see section ``Comparison to Perl
+5'' in the \sunjavadoc{java.base/java/util/regex/Pattern.html}{Pattern} Javadoc).
+These problems cause run-time errors.
+
+Regular expressions in Java also have capturing groups, which
+are delimited by parentheses and allow for extraction from text.
+If a programmer uses an incorrect index (larger than the number of
+capturing groups), an \<IndexOutOfBoundsException> is thrown.
+
+The Regex Checker warns about these problems at compile time, guaranteeing
+that your program does not crash due to incorrect use of regular expressions.
+
+For further details, including case studies, see a paper about the Regex
+Checker~\cite{SpishakDE2012}.
+
+To run the Regex Checker, supply the
+\code{-processor org.checkerframework.checker.regex.RegexChecker}
+command-line option to javac.
+
+
+\sectionAndLabel{Regex annotations}{regex-annotations}
+
+These qualifiers make up the Regex type system:
+
+\begin{description}
+
+\item[\refqualclass{checker/regex/qual}{Regex}]
+  indicates that the run-time value is a valid regular expression
+  \code{String}.  If the optional parameter is supplied to the qualifier,
+  then the number of capturing groups in the regular expression is at least
+  that many. If not provided, the parameter defaults to 0.
+  For example, if an expression's type is \<@Regex(1) String>, then its
+  run-time value could be \<"colo(u?)r"> or \<"(brown|beige)"> but not
+  \<"colou?r"> nor a non-regex string such as \<"1) first point">.
+
+\item[\refqualclass{checker/regex/qual}{PolyRegex}]
+  indicates qualifier polymorphism.
+  For a description of qualifier polymorphism, see
+  Section~\ref{method-qualifier-polymorphism}.
+
+\end{description}
+
+The subtyping hierarchy of the Regex Checker's qualifiers is shown in
+Figure~\ref{fig-regex-hierarchy}.
+
+\begin{figure}
+\includeimage{regex}{9cm}
+\caption{The subtyping relationship of the Regex Checker's qualifiers.
+  The type qualifiers are applicable to \<CharSequence> and its subtypes.
+  Because the parameter to a \<@Regex> qualifier is at least the number of
+  capturing groups in a regular expression, a \<@Regex> qualifier with more
+  capturing groups is a subtype of a \<@Regex> qualifier with fewer capturing
+  groups. Qualifiers in gray are used internally by the type
+  system but should never be written by a programmer.}
+\label{fig-regex-hierarchy}
+\end{figure}
+
+\sectionAndLabel{Annotating your code with \code{@Regex}}{annotating-with-regex}
+
+
+\subsectionAndLabel{Implicit qualifiers}{regex-implicit-qualifiers}
+
+The Regex Checker adds
+implicit qualifiers, reducing the number of annotations that must appear
+in your code (see Section~\ref{effective-qualifier}).
+If a \code{String} literal is a valid regex,
+the checker implicitly adds the \code{@Regex} qualifier with
+the argument set to the correct number of capturing groups.
+The Regex Checker allows
+the \code{null} literal to be assigned to any type qualified with the
+\code{Regex} qualifier.
+
+
+\subsectionAndLabel{Capturing groups}{regex-capturing-groups}
+
+The Regex Checker validates that a legal capturing group number is passed
+to \sunjavadoc{java.base/java/util/regex/Matcher.html}{Matcher}'s
+\sunjavadoc{java.base/java/util/regex/Matcher.html\#group(int)}{group},
+\sunjavadoc{java.base/java/util/regex/Matcher.html\#start(int)}{start} and
+\sunjavadoc{java.base/java/util/regex/Matcher.html\#end(int)}{end} methods. To do this,
+the type of \<Matcher> must be qualified with a \<@Regex> annotation
+with the number of capturing groups in the regular expression. This is
+handled implicitly by the Regex Checker for local variables (see
+Section~\ref{type-refinement}), but you may need to add \<@Regex> annotations
+with a capturing group count to \<Pattern> and \<Matcher> fields and
+parameters.
+
+
+\subsectionAndLabel{Concatenation of partial regular expressions}{regex-partial-regex}
+
+\begin{figure}
+\begin{Verbatim}
+public @Regex String parenthesize(@Regex String regex) {
+    return "(" + regex + ")"; // Even though the parentheses are not @Regex Strings,
+                              // the whole expression is a @Regex String
+}
+\end{Verbatim}
+\caption{An example of the Regex Checker's support for concatenation
+of non-regular-expression Strings to produce valid regular expression Strings.}
+\label{fig-regex-partial}
+\end{figure}
+
+In general, concatenating a non-regular-expression String with any other
+string yields a non-regular-expression String.  The Regex Checker can
+sometimes determine that concatenation of non-regular-expression Strings
+will produce valid regular expression Strings. For an example see
+Figure~\ref{fig-regex-partial}.
+
+
+\subsectionAndLabel{Testing whether a string is a regular expression}{regexutil-methods}
+
+Sometimes, the Regex Checker cannot infer whether a particular expression
+is a regular expression --- and sometimes your code cannot either!  In
+these cases, you can use the \<isRegex> method to perform such a test, and
+other helper methods to provide useful error messages.  A
+common use is for user-provided regular expressions (such as ones passed
+on the command-line).
+Figure~\ref{fig-regex-util-example} gives an
+example of the intended use of the \code{RegexUtil} methods.
+
+\begin{description}
+
+\item[\refmethod{checker/regex/util}{RegexUtil}{isRegex}{-java.lang.String-}]
+  returns \<true> if its argument is a valid regular expression.
+
+\item[\refmethod{checker/regex/util}{RegexUtil}{regexError}{-java.lang.String-}]
+  returns a \<String> error message if its argument is not a valid regular
+  expression, or \<null> if its argument is a valid regular expression.
+
+\item[\refmethod{checker/regex/util}{RegexUtil}{regexException}{-java.lang.String-}]
+  returns the
+  \sunjavadoc{java.base/java/util/regex/PatternSyntaxException.html}{Pattern\-Syntax\-Exception}
+  that \sunjavadoc{java.base/java/util/regex/Pattern.html\#compile(java.lang.String)}{Pattern.compile(String)}
+  throws when compiling an invalid regular expression.  It returns \<null>
+  if its argument is a valid regular expression.
+
+\end{description}
+
+An additional version of each of these methods is also provided that takes
+an additional group count parameter. The
+\refmethod{checker/regex/util}{RegexUtil}{isRegex}{-java.lang.String-int-} method
+verifies that the argument has at least the given number of groups. The
+\refmethod{checker/regex/util}{RegexUtil}{regexError}{-java.lang.String-int-} and
+\refmethod{checker/regex/util}{RegexUtil}{regexException}{-java.lang.String-int-}
+methods return a \<String> error message and \<Pattern\-Syntax\-Exception>,
+respectively, detailing why the given String is not a syntactically valid
+regular expression with at least the given number of capturing groups.
+
+\begin{sloppypar}
+If you detect that a \<String> is not a valid regular expression but would like
+to report the error higher up the call stack (potentially where you can
+provide a more detailed error message) you can throw a
+\refclass{checker/regex/util}{RegexUtil.CheckedPatternSyntaxException}. This exception is
+functionally the same as a
+\sunjavadoc{java.base/java/util/regex/PatternSyntaxException.html}{Pattern\-Syntax\-Exception}
+except it is checked to guarantee that the error will be handled up the
+call stack.  For more details, see the Javadoc for
+\refclass{checker/regex/util}{RegexUtil.CheckedPatternSyntaxException}.
+\end{sloppypar}
+
+To use the \<RegexUtil> class, the \<checker-util.jar> file
+must be on the classpath at run time.
+
+\begin{figure}
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+String regex = getRegexFromUser();
+if (! RegexUtil.isRegex(regex)) {
+   throw new RuntimeException("Error parsing regex " + regex, RegexUtil.regexException(regex));
+}
+Pattern p = Pattern.compile(regex);
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+\caption{Example use of \code{RegexUtil} methods.}
+\label{fig-regex-util-example}
+\end{figure}
+
+\subsectionAndLabel{Suppressing warnings}{regex-suppressing-warnings}
+
+If you are positive that a particular string that is being used as a
+regular expression is syntactically valid, but the Regex Checker cannot
+conclude this and issues a warning about possible use of an invalid regular
+expression, then you can use the
+\refmethod{checker/regex/util}{RegexUtil}{asRegex}{-java.lang.String-} method to suppress the
+warning.
+
+You can think of this method
+as a cast:  it returns its argument unchanged, but with the type
+\code{@Regex String} if it is a valid regular expression.  It throws an
+error if its argument is not a valid regular expression, but you should
+only use it when you are sure it will not throw an error.
+
+There is an additional \refmethod{checker/regex/util}{RegexUtil}{asRegex}{-java.lang.String-int-}
+method that takes a capturing group parameter. This method works the same as
+described above, but returns a \code{@Regex String} with the parameter on the
+annotation set to the value of the capturing group parameter passed to the method.
+
+The use case shown in Figure~\ref{fig-regex-util-example} should support most cases
+so the \<asRegex> method should be used rarely.
+
+
+
+
+% LocalWords:  Regex regex quals PolyRegex isRegex RegexUtil regexError asRegex
+% LocalWords:  regexException PatternSyntaxException Matcher java qual
+%  LocalWords:  CheckedPatternSyntaxException colo colou CharSequence
+%%  LocalWords:  regexutil
diff --git a/docs/manual/returns-receiver-checker.tex b/docs/manual/returns-receiver-checker.tex
new file mode 100644
index 0000000..5cb1bd1
--- /dev/null
+++ b/docs/manual/returns-receiver-checker.tex
@@ -0,0 +1,122 @@
+\htmlhr
+\chapterAndLabel{Returns Receiver Checker}{returns-receiver-checker}
+
+The Returns Receiver Checker enables documenting and checking that a method
+returns its receiver (i.e., the \<this> parameter).
+
+There are two ways to run the Returns Receiver Checker.
+\begin{itemize}
+\item
+  Typically, it is automatically run by another checker.
+
+  If the code being checked does not use fluent APIs, you can pass the
+  \<-AdisableReturnsReceiver> command-line option.  This disables the
+  Returns Receiver Checker and makes the other checker run faster.
+\item
+Alternately, you can run just the Returns Receiver Checker, by
+supplying the following command-line options to javac:
+\code{-processor org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker}
+\end{itemize}
+
+
+\sectionAndLabel{Annotations}{returns-receiver-checker-annotations}
+
+The qualifier \refqualclass{common/returnsreceiver/qual}{This} on the return
+type of a method indicates that the method returns its receiver.  Methods
+that return their receiver are common in so-called ``fluent'' APIs.  Here
+is an example:
+
+\begin{Verbatim}
+class MyBuilder {
+  @This MyBuilder setName(String name) {
+    this.name = name;
+    return this;
+  }
+}
+\end{Verbatim}
+
+An \refqualclass{common/returnsreceiver/qual}{This} annotation can only be
+written on a return type, a receiver type, or in a downcast.
+
+As is standard, the Returns Receiver Checker has a top qualifier,
+\refqualclass{common/returnsreceiver/qual}{UnknownThis}, and a bottom qualifier,
+\refqualclass{common/returnsreceiver/qual}{BottomThis}.
+Programmers rarely need to write these annotations.
+
+Here are additional details.  \refqualclass{common/returnsreceiver/qual}{This}
+is a polymorphic qualifier rather than a regular type qualifier ((see
+Section~\ref{method-qualifier-polymorphism}). Conceptually, a receiver type always has
+an \refqualclass{common/returnsreceiver/qual}{This} qualifier. When a method
+return type also has an \refqualclass{common/returnsreceiver/qual}{This}
+qualifier, the presence of the polymorphic annotation on both the method's
+return and receiver type forces their type qualifiers to be \emph{equal}. Hence,
+the method will only pass the type checker if it returns its receiver argument,
+achieving the desired checking.
+
+
+\sectionAndLabel{AutoValue and Lombok Support}{returns-receiver-checker-autovalue-lombok-support}
+
+\begin{figure}
+\begin{Verbatim}
+@AutoValue
+abstract class Animal {
+  abstract String name();
+  abstract int numberOfLegs();
+  static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+  @AutoValue.Builder
+  abstract static class Builder {
+    abstract Builder setName(String value);       // @This is automatically added here
+    abstract Builder setNumberOfLegs(int value);  // @This is automatically added here
+    abstract Animal build();
+  }
+}
+\end{Verbatim}
+    \caption{User-written code that uses the \<@AutoValue.Builder> annotation.
+      Given this code,
+      (1) AutoValue automatically generates a concrete subclass of
+      \<Animal.Builder>, see Figure~\ref{fig-autovalue-builder-generated}, and
+      (2) the Returns Receiver Checker automatically adds \<@This> annotations
+      on setters in both user-written and automatically-generated code.}
+    \label{fig-autovalue-builder}
+\end{figure}
+
+\begin{figure}
+    \begin{Verbatim}
+    class AutoValue_Animal {
+      static final class Builder extends Animal.Builder {
+        private String name;
+        private Integer numberOfLegs;
+        @This Animal.Builder setName(String name) {
+          this.name = name;
+          return this;
+        }
+        @This Animal.Builder setNumberOfLegs(int numberOfLegs) {
+          this.numberOfLegs = numberOfLegs;
+          return this;
+        }
+        @Override
+        Animal build() {
+          return new AutoValue_Animal(this.name, this.numberOfLegs);
+        }
+      }
+    }
+    \end{Verbatim}
+    \caption{Code generated by AutoValue for the example of
+    Figure~\ref{fig-autovalue-builder}, including the \<@This> annotations added
+    by the Returns Receiver Checker.}
+    \label{fig-autovalue-builder-generated}
+\end{figure}
+
+The \href{https://github.com/google/auto/tree/master/value}{AutoValue} and
+\href{https://projectlombok.org/}{Lombok} projects both support automatic
+generation of builder classes, which enable flexible object construction.
+For code using these two frameworks, the Returns Receiver Checker
+automatically adds \<@This> annotations to setter methods in builder
+classes.  All the \<@This> annotations in
+Figures~\ref{fig-autovalue-builder}
+and~\ref{fig-autovalue-builder-generated} are automatically added by the
+Returns Receiver Checker.
+
+% LocalWords:  UnknownThis BottomThis AutoValue autovalue lombok
diff --git a/docs/manual/signature-checker.tex b/docs/manual/signature-checker.tex
new file mode 100644
index 0000000..cb86593
--- /dev/null
+++ b/docs/manual/signature-checker.tex
@@ -0,0 +1,195 @@
+\htmlhr
+\chapterAndLabel{Signature String Checker for string representations of types}{signature-checker}
+
+The Signature String Checker, or Signature Checker for short, verifies that
+string representations of types and signatures are used correctly.
+
+Java defines multiple different string representations for types (see
+Section~\ref{signature-annotations}), and it is easy to
+misuse them or to miss bugs during testing.  Using the wrong string format
+leads to a run-time exception or an incorrect result.  This is a particular
+problem for fully qualified and binary names, which are nearly the same ---
+they differ only for nested classes and arrays.
+
+
+\sectionAndLabel{Signature annotations}{signature-annotations}
+
+Java defines six formats for the string representation of a type.
+There is an annotation for each of these representations.
+Figure~\ref{fig-signature-hierarchy} shows how they are related;
+examples appear in a table below.
+
+\begin{figure}
+\includeimage{signature-types}{7cm}
+\caption{Partial type hierarchy for the Signature type system, showing
+  string representations of a Java type.
+  The type qualifiers are applicable to \<CharSequence> and its subtypes.
+  Programmers usually only need to write
+  the boldfaced qualifiers; other qualifiers are
+  included to improve the internal handling of String literals.}
+\label{fig-signature-hierarchy}
+\end{figure}
+
+\label{signature-annotations-descriptions}
+
+\begin{description}
+
+\item[\refqualclass{checker/signature/qual}{FullyQualifiedName}]
+  A \emph{fully qualified name} (\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.7}{JLS \S
+    6.7}), such as
+  \<mypackage.Outer.Inner>, is used in Java code and in messages to
+  the user.
+
+\item[\refqualclass{checker/signature/qual}{ClassGetName}]
+\begin{sloppypar}
+  The type representation used by the
+  \sunjavadoc{java.base/java/lang/Class.html\#getName()}{\code{Class.getName()}}, \<Class.forName(String)>,
+  and \<Class.forName(String, boolean, ClassLoader)> methods.  This format
+  is:  for any non-array type, the binary name; and for any array type, a
+  format like the
+  \href{https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.3.2}{FieldDescriptor
+    field descriptor}, but using
+  ``\<.>''~where the field descriptor uses ``\</>''.  See examples below.
+\end{sloppypar}
+
+\item[\refqualclass{checker/signature/qual}{FieldDescriptor}]
+  A \emph{field descriptor} (\href{https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.3.2}{JVMS \S 4.3.2}), such as
+  \<Lmypackage/Outer\$Inner;>, is used in a \<.class> file's constant pool,
+  for example to refer to other types.  It abbreviates primitive types and
+  array types.  It uses internal form (binary names, but with \</> instead of
+  \<.>; see
+  \href{https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.2.1}{JVMS
+    \S 4.2}) for class names.  See examples below.
+
+\item[\refqualclass{checker/signature/qual}{BinaryName}]
+  A \emph{binary name} (\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1}{JLS \S 13.1}), such as
+  \<mypackage.Outer\$Inner>, is
+  the conceptual name of a type in its own \<.class> file.
+
+\item[\refqualclass{checker/signature/qual}{InternalForm}]
+  The \emph{internal form}
+  (\href{https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.2}{JVMS
+    \S 4.2}), such as \<mypackage/Outer\$Inner>, is how a class name is
+  actually represented in its own \<.class> file.  It is also known as the
+  ``syntax of binary names that appear in class file structures''.  It is
+  the same as the binary name, but with periods (\<.>) replaced by slashes
+  (\</>).  Programmers more often use the binary name, leaving the internal
+  form as a JVM implementation detail.
+
+\item[\refqualclass{checker/signature/qual}{ClassGetSimpleName}]
+  The type representation returned by the
+  \sunjavadoc{java.base/java/lang/Class.html\#getSimpleName()}{\code{Class.getSimpleName()}}
+  method.  This format is not required by any method in the JDK, so you
+  will rarely write it in source code.  The string can be empty.  This
+  is not the same as the ``simple name'' defined in
+  (\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.2}{JLS
+    \S 6.2}), which is the same as
+  \refqualclass{checker/signature/qual}{Identifier}.
+
+\item[\refqualclass{checker/signature/qual}{FqBinaryName}]
+  An extension of binary name format to represent primitives and arrays.
+  It is like \refqualclass{checker/signature/qual}{FullyQualifiedName}, but using
+  ``\<\$>'' instead of ``\<.>'' to separate nested classes from their
+  containing classes.  For example, \<"pkg.Outer\$Inner"> or
+  \<"pkg.Outer\$Inner[][]"> or \<"int[]">.
+
+\item[\refqualclass{checker/signature/qual}{CanonicalName}]
+  Syntactically identical to
+  \refqualclass{checker/signature/qual}{FullyQualifiedName}, but some
+  classes have multiple fully-qualified names, only one of which is
+  canonical (see
+  \href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.7}{JLS
+    \S 6.7}).
+
+\end{description}
+
+Other type qualifiers are the intersection of two or more qualifiers listed
+above; for example, a
+\refqualclass{checker/signature/qual}{BinaryNameWithoutPackage} is a string
+that is a valid internal form \emph{and} a valid binary name.  A
+programmer should never or rarely use these qualifiers, and you can ignore
+them as implementation details of the Signature Checker, though you might
+occasionally see them in an error message.  These qualifiers exist to give
+literals sufficiently precise types that they can be used in any
+appropriate context.
+
+Java also defines other string formats for a type, notably qualified names
+(\href{https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.2}{JLS
+  \S 6.2}). The Signature Checker does not include annotations for these.
+
+\label{signature-annotations-table}
+
+Here are examples of the supported formats:\selflink{signature-annotations-examples}
+\label{signature-annotations-examples}
+
+
+\newcommand{\naforanon}{\emph{n/a {\smaller for anonymous class}}}
+\newcommand{\naforanonarray}{\emph{n/a {\smaller for array of anon.~class}}}
+\newcommand{\naforprim}{\emph{n/a {\smaller for primitive type}}}
+\newcommand{\naforarray}{\emph{n/a {\smaller for array type}}}
+\newcommand{\emptystring}{\emph{\smaller (empty string)}}
+
+\begin{small}
+\begin{center}
+\begin{tabular}{|l|l|l|l|l|l|}
+\hline
+\multicolumn{1}{|c|}{fully qualified name} & \multicolumn{1}{c|}{Class.getName} & \multicolumn{1}{c|}{field descriptor} & \multicolumn{1}{c|}{binary name} & \multicolumn{1}{c|}{internal form} & \multicolumn{1}{c|}{Class.getSimpleName} \\ \hline
+int                 & int                  & I                    & \naforprim          & \naforprim          & int            \\
+int[][]             & [[I                  & [[I                  & \naforarray         & \naforarray         & int[][]        \\
+MyClass             & MyClass              & LMyClass;            & MyClass             & MyClass             & MyClass        \\
+MyClass[]           & [LMyClass;           & [LMyClass;           & \naforarray         & \naforarray         & MyClass[]      \\
+\naforanon          & MyClass\$22          & LMyClass\$22;        & MyClass\$22         & MyClass\$22         & \emptystring \\
+\naforanonarray     & [LMyClass\$22;       & [LMyClass\$22;       & \naforarray         & \naforarray         & []             \\
+java.lang.Integer   & java.lang.Integer    & Ljava/lang/Integer;  & java.lang.Integer   & java/lang/Integer   & Integer        \\
+java.lang.Integer[] & [Ljava.lang.Integer; & [Ljava/lang/Integer; & \naforarray         & \naforarray         & Integer[]      \\
+pkg.Outer.Inner     & pkg.Outer\$Inner     & Lpkg/Outer\$Inner;   & pkg.Outer\$Inner    & pkg/Outer\$Inner    & Inner          \\
+pkg.Outer.Inner[]   & [Lpkg.Outer\$Inner;  & [Lpkg/Outer\$Inner;  & \naforarray         & \naforarray         & Inner[]        \\
+\naforanon          & pkg.Outer\$22        & Lpkg/Outer\$22;      & pkg.Outer\$22       & pkg/Outer\$22       & \emptystring \\
+\naforanonarray     & [Lpkg.Outer\$22;     & [Lpkg/Outer\$22;     & \naforarray         & \naforarray         & []             \\
+\hline
+\end{tabular}
+\end{center}
+\end{small}
+
+Java defines one format for the string representation of a method signature:
+
+\begin{description}
+
+\item[\refqualclass{checker/signature/qual}{MethodDescriptor}]
+  A \emph{method descriptor} (\href{https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.3.3}{JVMS \S
+    4.3.3}) identifies a method's signature (its parameter and return
+  types), just as a field descriptor identifies a
+  type.   The method descriptor for the method
+\begin{Verbatim}
+    Object mymethod(int i, double d, Thread t)
+\end{Verbatim}
+\noindent is
+\begin{Verbatim}
+    (IDLjava/lang/Thread;)Ljava/lang/Object;
+\end{Verbatim}
+
+\end{description}
+
+
+\sectionAndLabel{What the Signature Checker checks}{signature-checks}
+
+Certain methods in the JDK, such as \<Class.forName>, are annotated
+indicating the type they require.  The Signature Checker ensures that
+clients call them with the proper arguments.  The Signature Checker does
+not reason about string operations such as concatenation, substring,
+parsing, etc.
+
+\begin{sloppypar}
+To run the Signature Checker, supply the
+\code{-processor org.checkerframework.checker.signature.SignatureChecker}
+command-line option to javac.
+\end{sloppypar}
+
+
+% LocalWords:  Regex regex quals FullyQualifiedName BinaryName FieldDescriptor
+% LocalWords:  Lpackage MyClass MethodDescriptor forName substring boolean
+% LocalWords:  jls getName ClassGetName ClassLoader LMyClass Ljava jvms
+% LocalWords:  DotSeparatedIdentifiers CharSequence Lmypackage mypackage
+% LocalWords:  FieldDescriptorWithoutPackage InternalForm getSimpleName
+% LocalWords:  ClassGetSimpleName FqBinaryName CanonicalName
+% LocalWords:  BinaryNameWithoutPackage
diff --git a/docs/manual/signedness-checker.tex b/docs/manual/signedness-checker.tex
new file mode 100644
index 0000000..73a9e84
--- /dev/null
+++ b/docs/manual/signedness-checker.tex
@@ -0,0 +1,335 @@
+\htmlhr
+\chapterAndLabel{Signedness Checker}{signedness-checker}
+
+The Signedness Checker guarantees that signed and unsigned integral values are not mixed
+together in a computation. In addition, it prohibits meaningless operations, such
+as division on an unsigned value.
+
+Recall that a computer represents a number as a sequence of bits.
+Signedness indicates how to interpret the most significant bit.  For
+example, the bits \<10000010> ordinarily represent the value -126, but when
+interpreted as unsigned, those bits represent the value 130.  The bits
+\<01111110> represent the value 126 in signed and in unsigned interpretation.
+The range of signed byte values is -128 to 127.  The range of unsigned byte
+values is 0 to 255.
+
+Signedness is only applicable to the integral types \<byte>,
+\<short>, \<int>, and \<long> and their boxed variants \<Byte>,
+\<Short>, \<Integer>, and \<Long>.
+\<char> and \<Character> are always unsigned.
+Floating-point types \<float>, \<double>, \<Float>, and \<Double> are always signed.
+% , because they do not have operations that interpret the bits as unsigned.
+
+Signedness is primarily about how the bits of the representation are
+\emph{interpreted}, not about the values that it can represent.  An unsigned value
+is always positive, but just because a variable's value is positive does
+not mean that it should be marked as \<@Unsigned>.  If variable $v$ will be
+compared to a signed value, or used in arithmetic operations with a signed
+value, then $v$ should have signed type.
+To indicate the range of possible values for a variable, use the
+\refqualclass{checker/index/qual}{NonNegative} annotation of the Index
+Checker (see \chapterpageref{index-checker}) or the
+\refqualclass{common/value/qual}{IntRange} annotation of the Constant Value
+Checker (see \chapterpageref{constant-value-checker}).
+
+
+\sectionAndLabel{Annotations}{signedness-checker-annotations}
+
+The Signedness Checker uses type annotations to indicate the signedness that the programmer intends an expression to have.
+
+\begin{figure}
+\includeimage{signedness}{3.5cm}
+\caption{The type qualifier hierarchy of the signedness annotations.
+Qualifiers in gray are used internally by the type system but should never be written by a programmer.}
+\label{fig-signedness-hierarchy}
+\end{figure}
+
+These are the qualifiers in the signedness type system:
+
+\begin{description}
+
+\item[\refqualclass{checker/signedness/qual}{Unsigned}]
+    indicates that the programmer intends the value to be
+    interpreted as unsigned.
+    That is, if the most significant bit in the bitwise representation is
+    set, then the bits should be interpreted as a large positive value.
+
+\item[\refqualclass{checker/signedness/qual}{Signed}]
+    indicates that the programmer intends the value to be
+    interpreted as signed.
+    That is, if the most significant bit in the bitwise representation is
+    set, then the bits should be interpreted as a negative value.
+    This is the default annotation.
+
+\item[\refqualclass{checker/signedness/qual}{SignedPositive}]
+    indicates that a value is known at compile time to be in the positive
+    signed range, so it has the same interpretation as signed or unsigned
+    and may be used with either interpretation.  (Equivalently, the most
+    significant bit is guaranteed to be 0.)  Programmers should usually
+    write \refqualclass{checker/signedness/qual}{Signed} or
+    \refqualclass{checker/signedness/qual}{Unsigned} instead.
+
+\item[\refqualclass{checker/signedness/qual}{SignedPositiveFromUnsigned}]
+    indicates that a value is in the positive signed range, as with
+    \refqualclass{checker/signedness/qual}{SignedPositive}.  Furthermore,
+    the value was derived from values that can be interpreted as
+    \refqualclass{checker/signedness/qual}{Unsigned}.  Programmers should
+    rarely write this annotation.
+
+\item[\refqualclass{checker/signedness/qual}{SignednessGlb}]
+    indicates that a value may be interpreted as unsigned or signed.  It
+    covers the same cases as
+    \refqualclass{checker/signedness/qual}{SignedPositive}, plus manifest literals, to
+    prevent the programmer from having to annotate them all explicitly.
+    This annotation should almost never be written by the
+    programmer.
+
+ \item[\refqualclass{checker/signedness/qual}{PolySigned}]
+   indicates qualifier polymorphism.
+   For a description of qualifier polymorphism, see
+   Section~\ref{method-qualifier-polymorphism}.
+
+\item[\refqualclass{checker/signedness/qual}{UnknownSignedness}]
+    indicates that a value's type is not relevant or known to this checker.
+    This annotation is used internally, and should not be
+    written by the programmer.
+
+\item[\refqualclass{checker/signedness/qual}{SignednessBottom}]
+  indicates that the value is \<null>.
+    This annotation is used internally, and should not
+    be written by the programmer.
+
+\end{description}
+
+
+\subsectionAndLabel{Default qualifiers}{signedness-checker-annotations-default-qualifiers}
+
+The only type qualifier that the programmer should need to write is
+\refqualclass{checker/signedness/qual}{Unsigned}.
+When a programmer leaves an expression unannotated, the
+Signedness Checker treats it in one of the following ways:
+
+\begin{itemize}
+
+    \item
+    All \code{byte}, \code{short}, \code{int}, and \code{long} literals default
+    to \refqualclass{checker/signedness/qual}{SignednessGlb}.
+    \item
+    All \code{byte}, \code{short}, \code{int}, and \code{long} variables default
+    to \refqualclass{checker/signedness/qual}{Signed}.
+    \item
+    All other expressions default to \refqualclass{checker/signedness/qual}{UnknownSignedness}.
+
+\end{itemize}
+
+
+\subsectionAndLabel{Widening conversions}{signedness-checker-widening-conversions}
+
+Figure~\ref{fig-signedness-hierarchy} shows the type qualifier hierarchy.
+When upcasting among integral types, the expression has type
+\<@SignedPositive> because the value is guaranteed to be within the signed
+positive range.  For example:
+
+\begin{Verbatim}
+@Signed int sint;
+@Unsigned int uint;
+@SignedPositive long splong1 = sint;   // legal
+@SignedPositive long splong2 = uint;   // legal
+... (long) sint ...   // this expression has type @SignedPositive long
+... (long) uint ...   // this expression has type @SignedPositive long
+\end{Verbatim}
+
+
+\sectionAndLabel{Prohibited operations}{signedness-checker-prohibited-operations}
+
+The Signedness Checker prohibits the following uses of operators:
+
+\begin{itemize}
+
+    \item
+    Division (\code{/}) or modulus (\code{\%}) with an \code{@Unsigned}
+    operand.
+    \item
+    Signed right shift (\verb|>>|) with an \code{@Unsigned} left operand.
+    \item
+    Unsigned right shift (\verb|>>>|) with a \code{@Signed} left operand.
+    \item
+    Greater/less than (or equal) comparators
+    (\code{<}, \code{<=}, \code{>}, \code{>=}) with an \code{@Unsigned}
+    operand.
+    \item
+    Any other binary operator with one \code{@Unsigned} operand and one
+    \code{@Signed} operand, with the exception of left shift (\verb|<<|).
+
+\end{itemize}
+
+There are some special cases where these operations are permitted; see
+Section~\ref{signedness-checker-permitted-shifts}.
+
+Like every type-checker built with the Checker Framework, the Signedness
+Checker ensures that assignments and pseudo-assignments have consistent types.
+For example, it is not permitted to assign a \code{@Signed} expression to an
+\code{@Unsigned} variable or vice versa.
+
+
+\subsectionAndLabel{Rationale}{signedness-checker-rationale}
+
+The Signedness Checker prevents misuse of unsigned values in Java code.
+Most Java operations interpret operands as signed.  If applied to unsigned
+values, those operations would produce unexpected, incorrect results.
+
+Consider the following Java code:
+
+\begin{Verbatim}
+public class SignednessManualExample {
+
+    int s1 = -2;
+    int s2 = -1;
+
+    @Unsigned int u1 = 2147483646; // unsigned: 2^32 - 2, signed: -2
+    @Unsigned int u2 = 2147483647; // unsigned: 2^32 - 1, signed: -1
+
+    void m() {
+        int w = s1 / s2; // OK: result is 2, which is correct for -2 / -1
+        int x = u1 / u2; // ERROR: result is 2, which is incorrect for (2^32 - 2) / (2^32 - 1)
+    }
+
+    int s3 = -1;
+    int s4 = 5;
+
+    @Unsigned int u3 = 2147483647; // unsigned: 2^32 - 1, signed: -1
+    @Unsigned int u4 = 5;
+
+    void m2() {
+        int y = s3 % s4; // OK: result is -1, which is correct for -1 % 5
+        int z = u3 % u4; // ERROR: result is -1, which is incorrect for (2^32 - 1) % 5 = 2
+    }
+}
+\end{Verbatim}
+
+These examples illustrate why division and modulus with an unsigned operand
+are illegal.  Other uses of operators are prohibited for similar reasons.
+
+
+\subsectionAndLabel{Permitted shifts}{signedness-checker-permitted-shifts}
+
+As exceptions to the rules given above, the Signedness Checker permits
+certain right shifts which are immediately followed by a cast or
+masking operation.
+
+For example, right shift by 8 then mask by 0xFF evaluates to the same value
+whether the argument is interpreted as signed or unsigned.  Thus, the
+Signedness Checker permits both \verb|((myInt >> 8) & 0xFF)| and
+\verb|((myInt >>> 8) & 0xFF)|, regardless of the qualifier on the type of
+\<myInt>.
+
+Likewise, right shift by 8 then cast to byte evaluates to the
+same value whether the argument is interpreted as signed or unsigned, so
+the Signedness Checker permits both \verb|(byte) (myInt >> 8)| and
+\verb|(byte) (myInt >>> 8)|, regardless of the type of \<myInt>.
+
+%% TODO: This is not yet implemented.  Should it be?
+% These masked/casted shift expressions have type
+% \refqualclass{checker/signedness/qual}{SignednessGlb}.  They are bit
+% patterns that can be interpreted as either signed or unsigned values.
+
+
+% There are two distinct reasons to use \verb|>>>|, the unsigned right shift operator:
+% \begin{itemize}
+% \item
+%    To perform arithmetic:  dividing or multiplying by 2.
+%    The left-hand operand must be an unsigned value, and
+%    the result is an unsigned value.
+%  \item
+%    To extract bits.
+%    The left-hand operand may be arbitrary, and \verb|>>>| behaves the same
+%    as \verb|>>|.
+%    % TODO: The result should be @SignednessUnknown.
+%    % The result is a bit string: no arithmetic should be performed on it (@SignednessUnknown).
+% \end{itemize}
+
+
+\sectionAndLabel{Utility routines for manipulating unsigned values}{signedness-utilities}
+
+Class \refclass{checker/signedness/util}{SignednessUtil} provides static
+utility methods for working with unsigned values.  They are
+properly annotated with \refqualclass{checker/signedness/qual}{Unsigned}
+where appropriate, so using them may reduce the number of annotations that
+you need to write.
+To use the \refclass{checker/signedness/util}{SignednessUtil} class, the
+\<checker-util.jar> file must be on the classpath at run time.
+
+Class \refclass{checker/signedness/util}{SignednessUtilExtra} contains more utility
+methods that reference packages not included in Android.  This class is not
+included in \code{checker-util.jar}, so you may want to copy the methods to your code.
+
+
+\sectionAndLabel{Local type refinement}{signedness-refinement}
+
+Local type refinement/inference (Section~\ref{type-refinement}) may be
+surprising for the Signedness type system.  Ordinarily, an expression with
+unsigned type may not participate in a division, as shown in
+Sections~\ref{signedness-checker-prohibited-operations}
+and~\ref{signedness-checker-rationale}.  However, if a constant is assigned
+to a variable that was declared with \<@Unsigned> type, then --- just like
+the constant --- the variable may be treated as either signed or unsigned.
+For example, it can participate in division.  Since the result of the
+division is signed, you cannot accidentally assign the division result to
+an \<@Unsigned> variable.
+
+\begin{Verbatim}
+    void useLocalVariables() {
+
+        int s1 = -2;
+        int s2 = -1;
+
+        @Unsigned int u1 = 2147483646; // unsigned: 2^32 - 2, signed: -2
+        @Unsigned int u2 = 2147483647; // unsigned: 2^32 - 1, signed: -1
+
+        int w = s1 / s2; // OK: result is 2, which is correct for -2 / -1
+        int x = u1 / u2; // OK; computation over constants, interpreted as signed; result is signed
+    }
+\end{Verbatim}
+
+
+\sectionAndLabel{Other signedness annotations}{signedness-other-annotations}
+
+The Checker Framework's signedness annotations are similar to annotations used
+elsewhere.
+
+If your code is already annotated with a different
+annotation, the Checker Framework can type-check your code.
+It treats annotations from other tools
+as if you had written the corresponding annotation from the
+Signedness Checker, as described in Figure~\ref{fig-signedness-refactoring}.
+% If the other annotation is a declaration annotation, it may be moved; see
+% Section~\ref{declaration-annotations-moved}.
+
+
+% These lists should be kept in sync with SignednessAnnotatedTypeFactory.java .
+\begin{figure}
+\begin{center}
+% The ~ around the text makes things look better in Hevea (and not terrible
+% in LaTeX).
+\begin{tabular}{ll}
+\begin{tabular}{|l|}
+\hline
+ ~jdk.jfr.Unsigned~ \\ \hline
+\end{tabular}
+&
+$\Rightarrow$
+~org.checkerframework.checker.signedness.qual.Unsigned~
+\end{tabular}
+\end{center}
+%BEGIN LATEX
+\vspace{-1.5\baselineskip}
+%END LATEX
+\caption{Correspondence between other signedness annotations
+  and the Checker Framework's annotations.}
+\label{fig-signedness-refactoring}
+\end{figure}
+
+% LocalWords:  Signedness signedness IntRange bitwise SignedPositive myInt
+% LocalWords:  SignedPositiveFromUnsigned SignednessGlb PolySigned qual
+% LocalWords:  UnknownSignedness SignednessBottom upcasting comparators
+% LocalWords:  SignednessUtil SignednessUtilExtra
diff --git a/docs/manual/subtyping-checker.tex b/docs/manual/subtyping-checker.tex
new file mode 100644
index 0000000..c066dfd
--- /dev/null
+++ b/docs/manual/subtyping-checker.tex
@@ -0,0 +1,360 @@
+\htmlhr
+\chapterAndLabel{Subtyping Checker}{subtyping-checker}
+
+The Subtyping Checker enforces only subtyping rules.  It operates over
+annotations specified by a user on the command line.  Thus, users can
+create a simple type-checker without writing any code beyond definitions of
+the type qualifier annotations.
+
+The Subtyping Checker can accommodate all of the type system enhancements that
+can be declaratively specified (see Chapter~\ref{creating-a-checker}).
+This includes type introduction rules via the
+\refqualclass{framework/qual}{QualifierForLiterals} meta-annotation, and other features such as
+type refinement (Section~\ref{type-refinement}) and
+qualifier polymorphism (Section~\ref{method-qualifier-polymorphism}).
+
+The Subtyping Checker is also useful to type system designers who wish to
+experiment with a checker before writing code; the Subtyping Checker
+demonstrates the functionality that a checker inherits from the Checker
+Framework.
+
+If you need typestate analysis, then you can extend a typestate checker.
+For more details (including a definition of ``typestate''), see
+Chapter~\ref{typestate-checker}.
+See Section~\ref{faq-typestate} for a simpler alternative.
+
+For type systems that require special checks (e.g., warning about
+dereferences of possibly-null values), you will need to write code and
+extend the framework as discussed in Chapter~\ref{creating-a-checker}.
+
+
+\sectionAndLabel{Using the Subtyping Checker}{subtyping-using}
+
+\begin{sloppypar}
+The Subtyping Checker is used in the same way as other checkers (using the
+\code{-processor org.checkerframework.common.subtyping.SubtypingChecker} option; see Chapter~\ref{using-a-checker}), except that it
+requires an additional annotation processor argument via the standard
+``\code{-A}'' switch. You must provide one or both of the two following
+command-line arguments:
+\end{sloppypar}
+
+\begin{itemize}
+
+\item
+Provide the fully-qualified class name(s) of the annotation(s) in the custom
+type system through the \code{-Aquals} option, using a comma-no-space-separated
+notation:
+
+\begin{alltt}
+  javac -classpath \textit{/full/path/to/myProject/bin}:\textit{/full/path/to/myLibrary/bin} \ttbs
+        -processor org.checkerframework.common.subtyping.SubtypingChecker \ttbs
+        -Aquals=\textit{myPackage.qual.MyQual},\textit{myPackage.qual.OtherQual} MyFile.java ...
+\end{alltt}
+
+\item
+Provide the fully-qualified paths to a set of directories that contain the
+annotations in the custom type system through the \code{-AqualDirs} option,
+using a colon-no-space-separated notation. For example,
+if the Checker Framework is on the classpath rather than the processorpath:
+
+\begin{alltt}
+  javac -classpath \textit{/full/path/to/myProject/bin}:\textit{/full/path/to/myLibrary/bin} \ttbs
+        -processor org.checkerframework.common.subtyping.SubtypingChecker \ttbs
+        -AqualDirs=\textit{/full/path/to/myProject/bin}:\textit{/full/path/to/myLibrary/bin} MyFile.java
+\end{alltt}
+
+\end{itemize}
+
+If the Checker Framework is on the processorpath, place the annotations on
+the processorpath instead of on the classpath.
+
+
+\subsectionAndLabel{Compiling your qualifiers and your project}{subtyping-compiling}
+
+The annotations listed in \code{-Aquals} or \code{-AqualDirs} must be accessible to
+the compiler during compilation.  Before you run the Subtyping Checker with
+\code{javac}, they must be compiled and on the same path (the classpath or
+processorpath) as the Checker Framework.  It
+is not sufficient to supply their source files on the command line.
+
+
+\subsectionAndLabel{Suppressing warnings from the Subtyping Checker}{subtyping-suppressing}
+
+When suppressing a warning issued by the Subtyping Checker, as the
+``checker name'' you may use the unqualified, uncapitalized name of any of
+the annotations passed to \code{-Aquals}.  (See
+Section~\ref{suppresswarnings-annotation-syntax} for details about the
+\<@SuppressWarnings> syntax.)  As a matter of style, you should
+choose one of the annotations as the ``checker name'' part of the
+\code{@SuppressWarnings} string and use it consistently; this avoids
+confusion and makes it easier to search for uses.
+
+
+\sectionAndLabel{Subtyping Checker example\label{encrypted-example}}{subtyping-example}
+
+Consider a hypothetical \code{Encrypted} type qualifier, which denotes that the
+representation of an object (such as a \code{String}, \code{CharSequence}, or
+\code{byte[]}) is encrypted. To use the Subtyping Checker for the \code{Encrypted}
+type system, follow three steps.
+
+\begin{enumerate}
+\item
+Define two annotations for the \code{Encrypted} and \code{PossiblyUnencrypted} qualifiers:
+
+% alltt because it uses \textit
+\begin{alltt}
+package \textit{myPackage}.qual;
+
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the representation of an object is encrypted.
+ */
+@SubtypeOf(PossiblyUnencrypted.class)
+@DefaultFor(\{TypeUseLocation.LOWER_BOUND\})
+@Target(\{ElementType.TYPE_USE, ElementType.TYPE_PARAMETER\})
+public @interface Encrypted \{\}
+\end{alltt}
+
+~
+
+% alltt because it uses \textit
+\begin{alltt}
+package \textit{myPackage}.qual;
+
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the representation of an object might not be encrypted.
+ */
+@DefaultQualifierInHierarchy
+@SubtypeOf(\{\})
+@Target(\{ElementType.TYPE_USE, ElementType.TYPE_PARAMETER\})
+public @interface PossiblyUnencrypted \{\}
+\end{alltt}
+
+Note that all custom annotations must have the
+\<@Target(ElementType.TYPE\_USE)> meta-annotation.
+See Section~\ref{creating-define-type-qualifiers}.
+
+Don't forget to compile these classes:
+
+\begin{Verbatim}
+$ javac myPackage/qual/Encrypted.java myPackage/qual/PossiblyUnencrypted.java
+\end{Verbatim}
+
+The resulting \<.class> files should either be on the same path (the classpath or
+processor path) as the Checker Framework.
+
+\item
+  Write \code{@Encrypted} annotations in your program (say, in file
+  \code{YourProgram.java}):
+
+\begin{alltt}
+import \textit{myPackage}.qual.Encrypted;
+
+...
+
+public @Encrypted String encrypt(String text) \{
+    // ...
+\}
+
+// Only send encrypted data!
+public void sendOverInternet(@Encrypted String msg) \{
+    // ...
+\}
+
+void sendText() \{
+    // ...
+    @Encrypted String ciphertext = encrypt(plaintext);
+    sendOverInternet(ciphertext);
+    // ...
+\}
+
+void sendPassword() \{
+    String password = getUserPassword();
+    sendOverInternet(password);
+\}
+\end{alltt}
+
+You may also need to add \code{@SuppressWarnings} annotations to the
+\code{encrypt} and \code{decrypt} methods.  Analyzing them is beyond the
+capability of any realistic type system.
+
+\item
+  Invoke the compiler with the Subtyping Checker, specifying the
+  \code{@Encrypted} annotation using the \code{-Aquals} option.
+  You should add the \code{Encrypted} classfile to the processor classpath:
+
+\begin{alltt}
+  javac -processorpath \textit{myqualpath} -processor org.checkerframework.common.subtyping.SubtypingChecker \
+        -Aquals=\textit{myPackage.qual.Encrypted},\textit{myPackage.qual.PossiblyUnencrypted} YourProgram.java
+
+YourProgram.java:42: incompatible types.
+found   : @myPackage.qual.PossiblyUnencrypted java.lang.String
+required: @myPackage.qual.Encrypted java.lang.String
+    sendOverInternet(password);
+                     ^
+\end{alltt}
+
+\item
+You can also provide the fully-qualified paths to a set of directories
+that contain the qualifiers using the \code{-AqualDirs} option, and add
+the directories to the boot classpath, for example:
+
+\begin{alltt}
+  javac -classpath \textit{/full/path/to/myProject/bin}:\textit{/full/path/to/myLibrary/bin} \ttbs
+        -processor org.checkerframework.common.subtyping.SubtypingChecker \ttbs
+        -AqualDirs=\textit{/full/path/to/myProject/bin}:\textit{/full/path/to/myLibrary/bin} YourProgram.java
+\end{alltt}
+
+\begin{sloppypar}
+Note that in these two examples, the compiled class file of the
+\<myPackage.qual.Encrypted> and \<myPackage.qual.PossiblyUnencrypted> annotations
+must exist in either the \<myProject/bin> directory or the \<myLibrary/bin>
+directory. The following placement of the class files will work with the above
+commands:
+\end{sloppypar}
+
+\begin{alltt}
+  .../myProject/bin/myPackage/qual/Encrypted.class
+  .../myProject/bin/myPackage/qual/PossiblyUnencrypted.class
+\end{alltt}
+
+\end{enumerate}
+
+Also, see the example project in the \<docs/examples/subtyping-extension> directory.
+
+
+\sectionAndLabel{Type aliases and typedefs}{subtyping-type-alias}
+
+A type alias or typedef is a type that shares the same representation as
+another type but is conceptually distinct from it.  For example, some
+strings in your program may be street addresses; others may be passwords;
+and so on.  You wish to indicate, for each string, which one it is, and to
+avoid mixing up the different types of strings.  Likewise, you could
+distinguish integers that are offsets from those that are absolute values.
+
+Creating a new type makes your code easier to understand by conveying the
+intended use of each variable.  It also prevents errors that come from
+using the wrong type or from mixing incompatible types in an operation.
+
+If you want to create a type alias or typedef, you have multiple options:
+a regular Java subtype,
+the Units Checker (\chapterpageref{units-checker}),
+the Fake Enum Checker (\chapterpageref{fenum-checker}), or
+the Subtyping Checker.
+
+A Java subtype is easy to create and does not require a tool such as the
+Checker Framework; for instance, you would declare \<class Address extends
+String>.  There are a number of limitations to this ``pseudo-typedef'',
+however~\cite{Goetz2006:typedef}.
+Primitive types and final types (including \<String>) cannot be extended.
+Equality and identity tests can return incorrect results when a wrapper
+object is used.  Existing return types in code would need to be changed,
+which is easy with an annotation but disruptive to change the Java type.
+Therefore, it is best to avoid the pseudo-typedef antipattern.
+
+The Units Checker (\chapterpageref{units-checker}) is useful for the
+particular case of units of measurement, such as kilometers verses miles.
+
+The Fake Enum Checker (\chapterpageref{fenum-checker})
+builds in a set of assumptions.  If those fit your
+use case, then it's easiest to use the Fake Enum Checker (though you can
+achieve them using the Subtyping Checker).  The Fake Enum Checker forbids
+mixing of fenums of different types, or fenums and unannotated types.  For
+instance, binary operations other than string concatenations are forbidden,
+such as \<NORTH+1>, \<NORTH+MONDAY>, and \<NORTH==MONDAY>.  However,
+\<NORTH+SOUTH> is permitted.
+
+By default, the Subtyping Checker does not forbid any operations.
+
+If you choose to use the Subtyping Checker, then you have an additional
+design choice to make about the type system.  In the general case, your
+type system will look something like Figure~\ref{fig-typedef-hierarchy}.
+
+\begin{figure}
+\includeimage{typedef}{3.5cm}
+\caption{Type system for a type alias or typedef type system.
+  The type system designer may choose to omit some of these types, but
+  this is the general case.
+  The type system designer's choice of defaults affects the interpretation
+  of unannotated code, which affects the guarantees given for unannotated code.
+  \label{fig-typedef-hierarchy}}
+\end{figure}
+
+References whose type is \<@MyType> are known to store only values from
+your new type.  There is no such guarantee for \<@MyTypeUnknown> and
+\<@NotMyType>, but those types mean different things.  An expression of type
+\<@NotMyType> is guaranteed never to evaluate to a value of your new type.
+An expression of type \<@MyTypeUnknown> may evaluate to any value ---
+including values of your new type and values not of your new type.
+(\<@MyTypeBottom> is the type of \<null> and is also used for dead code and
+erroneous situations; it can be ignored for this
+discussion.)
+
+A key choice for the type system designer is which type is the default.
+That is, if a programmer does not write \<@MyType> on a given type use,
+should that type use be interpreted as \<@MyTypeUnknown> or as
+\<@NotMyType>?
+
+\begin{itemize}
+\item
+If unannotated types are interpreted as \<@NotMyType>, then the type system
+enforces very strong separation between your new type and all other types.
+Values of your type will never mix with values of other types.  If you
+don't see \<@MyType> written explicitly on a type, you will know that
+it does not contain values of your type.
+
+\item
+If unannotated types are interpreted as \<@MyTypeUnknown>, then
+a generic, unannotated type may contain a value of your new type.
+In this case, \<@NotMyType> does not need to exist, and \<@MyTypeBottom>
+may or may not exist in your type system.
+\end{itemize}
+
+A downside of the stronger guarantee that comes from using \<@NotMyType> as
+the default is the need to write additional annotations.
+For example, if \<@NotMyType> is the default, this code does not typecheck:
+
+\begin{Verbatim}
+void method(Object o) { ... }
+<U> void use(List<U> list) {
+  method(list.get(0));
+}
+\end{Verbatim}
+
+Because (implicit) upper bounds are interpreted as the top type (see
+Section~\ref{generics-defaults}), this is interpreted as
+
+\begin{Verbatim}
+void method(@NotMyType Object o) { ... }
+<@U extends @MyTypeUnknown Object> void use(List<U> list) {
+  // type error: list.get(0) has type @MyTypeUnknown, method expects @NotMyType
+  method(list.get(0));
+}
+\end{Verbatim}
+
+To make the code type-check, it is necessary to write an explicit
+annotation, either to restrict \<use>'s argument or to expand \<method>'s
+parameter type.
+
+
+
+
+% LocalWords:  TODO ImplicitFor Aquals sourcepath java NonNull AqualDirs
+% LocalWords:  CharSequence classpath nullness quals SuppressWarnings classfile
+% LocalWords:  uncapitalized processorpath Warski MyFile YourProgram qual
+%%  LocalWords:  bootclasspath PossiblyUnencrypted myProject myLibrary msg
+%%  LocalWords:  ElementType myqualpath sendOverInternet myPackage typedefs
+%%  LocalWords:  SubtypeOf LiteralKind DefaultFor TypeUseLocation fenum
+%%  LocalWords:  DefaultQualifierInHierarchy sendText sendPassword MyType
+%  LocalWords:  getUserPassword CompassDirection MyTypeUnknown NotMyType
+%  LocalWords:  MyTypeBottom typecheck ciphertext plaintext decrypt fenums
+%%  LocalWords:  typedef antipattern typestate''
diff --git a/docs/manual/tainting-checker.tex b/docs/manual/tainting-checker.tex
new file mode 100644
index 0000000..41950ef
--- /dev/null
+++ b/docs/manual/tainting-checker.tex
@@ -0,0 +1,219 @@
+\htmlhr
+\chapterAndLabel{Tainting Checker}{tainting-checker}
+
+The Tainting Checker prevents certain kinds of trust errors.
+A \emph{tainted}, or untrusted, value is one that comes from an arbitrary,
+possibly malicious source, such as user input or unvalidated data.
+In certain parts of your application, using a tainted value can compromise
+the application's integrity, causing it to crash, corrupt data, leak
+private data, etc.
+
+% Ought to have many more examples
+
+For example, a user-supplied pointer, handle, or map key should be
+validated before being dereferenced.
+As another example, a user-supplied string should not be concatenated into a
+SQL query, lest the program be subject to a
+\href{https://en.wikipedia.org/wiki/Sql_injection}{SQL injection} attack.
+A location in your program where malicious data could do damage is
+called a \emph{sensitive sink}.
+
+A program must ``sanitize'' or ``untaint'' an untrusted value before using
+it at a sensitive sink.  There are two general ways to untaint a value:
+by checking
+that it is innocuous/legal (e.g., it contains no characters that can be
+interpreted as SQL commands when pasted into a string context), or by
+transforming the value to be legal (e.g., quoting all the characters that
+can be interpreted as SQL commands).  A correct program must use one of
+these two techniques so that tainted values never flow to a sensitive sink.
+The Tainting Checker ensures that your program does so.
+
+If the Tainting Checker issues no warning for a given program, then no
+tainted value ever flows to a sensitive sink.  However, your program is not
+necessarily free from all trust errors.  As a simple example, you might
+have forgotten to annotate a sensitive sink as requiring an untainted type,
+or you might have forgotten to annotate untrusted data as having a tainted
+type.
+
+To run the Tainting Checker, supply the
+\code{-processor TaintingChecker}
+command-line option to javac.
+%TODO: For examples, see Section~\ref{tainting-examples}.
+
+
+\sectionAndLabel{Tainting annotations}{tainting-annotations}
+
+% TODO: add both qualifiers explicitly, and then describe their relationship.
+
+The Tainting type system uses the following annotations:
+\begin{description}
+\item[\refqualclass{checker/tainting/qual}{Untainted}]
+  indicates
+  a type that includes only untainted (trusted) values.
+\item[\refqualclass{checker/tainting/qual}{Tainted}]
+  indicates
+  a type that may include tainted (untrusted) or untainted (trusted) values.
+  \code{@Tainted} is a supertype of \code{@Untainted}.
+  It is the default qualifier.
+\item[\refqualclass{checker/tainting/qual}{PolyTainted}]
+  indicates qualifier polymorphism.
+  For a description of qualifier polymorphism, see
+  Section~\ref{method-qualifier-polymorphism}.
+\end{description}
+
+
+\sectionAndLabel{Tips on writing \code{@Untainted} annotations}{writing-untainted}
+
+Most programs are designed with a boundary that surrounds sensitive
+computations, separating them from untrusted values.  Outside this
+boundary, the program may manipulate malicious values, but no malicious
+values ever pass the boundary to be operated upon by sensitive
+computations.
+
+In some programs, the area outside the boundary is very small:  values are
+sanitized as soon as they are received from an external source.  In other
+programs, the area inside the boundary is very small:  values are sanitized
+only immediately before being used at a sensitive sink.  Either approach
+can work, so long as every possibly-tainted value is sanitized before it
+reaches a sensitive sink.
+
+Once you determine the boundary, annotating your program is easy:  put
+\code{@Tainted} outside the boundary, \code{@Untainted} inside, and
+\code{@SuppressWarnings("tainting")} at the validation or
+sanitization routines that are used at the boundary.
+% (Or, the Tainting Checker may indicate to you that the boundary
+% does not exist or has holes through which tainted values can pass.)
+
+The Tainting Checker's standard default qualifier is \code{@Tainted} (see
+Section~\ref{defaults} for overriding this default).  This is the safest
+default, and the one that should be used for all code outside the boundary
+(for example, code that reads user input).  You can set the default
+qualifier to \code{@Untainted} in code that may contain sensitive sinks.
+
+The Tainting Checker does not know the intended semantics of your program,
+so it cannot warn you if you mis-annotate a sensitive sink as taking
+\code{@Tainted} data, or if you mis-annotate external data as
+\code{@Untainted}.  So long as you correctly annotate the sensitive sinks
+and the places that untrusted data is read, the Tainting Checker will
+ensure that all your other annotations are correct and that no undesired
+information flows exist.
+
+As an example, suppose that you wish to prevent SQL injection attacks.  You
+would start by annotating the
+\sunjavadoc{java.sql/java/sql/Statement.html}{Statement} class to indicate that the
+\code{execute} operations may only operate on untainted queries
+(Chapter~\ref{annotating-libraries} describes how to annotate external
+libraries):
+
+\begin{Verbatim}
+  public boolean execute(@Untainted String sql) throws SQLException;
+  public boolean executeUpdate(@Untainted String sql) throws SQLException;
+\end{Verbatim}
+
+
+\sectionAndLabel{\code{@Tainted} and \code{@Untainted} can be used for many purposes}{tainting-many-uses}
+
+The \code{@Tainted} and \code{@Untainted} annotations have only minimal
+built-in semantics.  In fact, the Tainting Checker provides only a small
+amount of functionality beyond the Subtyping Checker
+(Chapter~\ref{subtyping-checker}).  This lack of hard-coded behavior has
+two consequences.  The first consequence is that
+the annotations can serve many different purposes, such as:
+
+\begin{itemize}
+\item
+  Prevent SQL injection attacks:  \code{@Tainted} is external input,
+  \code{@Untainted} has been checked for SQL syntax.
+\item
+  Prevent cross-site scripting attacks:  \code{@Tainted} is external input,
+  \code{@Untainted} has been checked for JavaScript syntax.
+\item
+  Prevent information leakage:  \code{@Tainted} is secret data,
+  \code{@Untainted} may be displayed to a user.
+\end{itemize}
+
+The second consequence is that the Tainting Checker is not useful unless
+you annotate the appropriate sources, sinks, and untainting/sanitization
+routines.
+% This is similar to the \code{@Encrypted} annotation
+% (Section~\ref{encrypted-example}), where the cryptographic functions are
+% beyond the reasoning abilities of the type system.  In each case, the type
+% system verifies most of your code, and the \code{@SuppressWarnings}
+% annotations indicate the few places where human attention is needed.
+
+
+If you want more specialized semantics, or you want to annotate multiple
+types of tainting (for example, HTML and SQL) in a single program,
+then you can copy the definition of
+the Tainting Checker to create a new annotation and checker with a more
+specific name and semantics.  You will change the copy to rename the
+annotations, and you will annotate libraries and/or your code to identify
+sources, sinks, and validation/sanitization routines.
+See Chapter~\ref{creating-a-checker} for more
+details.
+
+
+\sectionAndLabel{A caution about polymorphism and side effects}{tainting-polymorphism-caution}
+
+Misuse of polymorphism can lead to unsoundness with the Tainting Checker
+and other similar information flow checkers. To understand the potential
+problem, consider the \code{append} function in
+\code{java.lang.StringBuffer}:
+
+\begin{Verbatim}
+  public StringBuffer append(StringBuffer this, String toAppend);
+\end{Verbatim}
+
+Given these declarations:
+
+\begin{Verbatim}
+  @Tainted StringBuffer tsb;
+  @Tainted String ts;
+  @Untainted StringBuffer usb;
+  @Untainted String us;
+\end{Verbatim}
+
+\noindent
+both of these invocations should be legal:
+
+\begin{Verbatim}
+  tsb.append(ts);
+  usb.append(us);
+\end{Verbatim}
+
+That suggests that perhaps the function should be annotated as polymorphic:
+
+\begin{Verbatim}
+  // UNSOUND annotation -- do not do this!
+  public @PolyTainted StringBuffer append(@PolyTainted StringBuffer this, @PolyTainted String toAppend);
+\end{Verbatim}
+
+The problem with the above annotation is that it permits the undesirable
+invocation:
+
+\begin{Verbatim}
+  usb.append(ts); // illegal invocation
+\end{Verbatim}
+
+\noindent
+This invocation is permitted because, in the expression, all
+\<@PolyTainted> annotations on formal parameters are instantiated to
+\<@Tainted>, the top annotation, and each argument is a subtype of the
+corresponding formal parameter.
+
+Beware this problem both in code you write, and also in annotated libraries
+(such as stub files).  The correct way to annotate this class is to
+add a class qualifier parameter; see Section~\ref{class-qualifier-polymorphism}.
+
+(Side note:  if \code{append} were purely functional (had no side effects
+and returned a new \<StringBuffer>) the method call would be acceptable,
+because the return type is instantiated to \<@Tainted StringBuffer> for the
+expression \<usb.append(ts)>.  However, the \code{append} method works via
+side-effect, and only returns a reference to the buffer as a convenience
+for writing ``fluent'' client code.)
+
+% TODO: one could add a String[] value to @Untainted to distinguish different
+% values, eg @Untainted{``SQL''} versus @Untainted{``HTML''}.
+
+% LocalWords:  quals untaint PolyTainted mis untainting sanitization java
+%%  LocalWords:  TaintingChecker untrusted unvalidated usb PolyDet
diff --git a/docs/manual/to-do-refactoring.txt b/docs/manual/to-do-refactoring.txt
new file mode 100644
index 0000000..561a795
--- /dev/null
+++ b/docs/manual/to-do-refactoring.txt
@@ -0,0 +1,152 @@
+
+Checker Framework redesign/refactoring (in priority order):
+
+(Throughout the refactorings, incrementally update the manual.)
+
+* Document the difference between getAnnotation and getEffectiveAnnotation
+
+* rename commonAssignmentCheck to subtypeCheck
+
+* rename asMemberOf or postAsMemberOf to fieldFromUse
+
+* isSubtype should not return boolean.
+  (Sometimes it is used for control flow rather than error reporting?  Why?  And would that even be possible to incorporate into inference mode?)
+  It should be "requireSubtype", return void, and issue the proper error.
+  This is essentially commonAssignmentCheck, though.  What's the distinction?  Determine that and document it.
+  An alternative (but not as good?):  isSubtype could return information about the specific error (like 3rd type parameter is the one that doesn't match, for example).
+  Problem: type hierarchy doesn't have access to the Checker.
+   * could return an error object, or pass in something that will handle errors.
+  (Werner calls this a "declarative interface".)
+  [Jonathan should propose an interface.]
+  [Werner will write some code in the meanwhile to direct the discussion.]
+
+* CF cleanups based on langtools cleanups -- typeFromElement and typeFromTree hacks can be removed [Werner]
+
+* Some things need to happen only once ever, whereas others happen once per compilation method.
+  Need a setup and teardown method (rather than typeProcessingOver, which is not called if there is a Java error or typechkecing error (but is called if there are warnings)).
+  [Werner.]
+
+* AnnotatedTypeFactory changes (Stuart); see below
+
+* Eliminate use of reflection in composing a type-checker from
+  similarly-named classes; force this to be explicit.  This will make the
+  type-checkers a bit more verbose, but easier to understand and debug.
+  Examples throughout the manual may need to be updated.
+
+* change from AST visiting to CFG visiting, so that each type system doesn't
+  have to implement special logic to infer where boxing, unboxing, and
+  other desugarings occur -- that can be done once, correctly, by CFG
+  construction.
+  TypeVisitor or SourceVisitor should be over the CFG.
+  This can be independent of Stuart's changes.
+  Should the new interface be over the CFG too?
+  A nice side effect is that if the CFG duplicates code (such as that for
+  finally blocks), then we get path/context-sensitive treatment of that
+  code for free since we analyze it twice.  One challenge would be avoiding
+  producing two error messages for the same expression, but that feature
+  could be deferred because the system can be useful even before that
+  feature is implemented.
+Stefan says:
+I have three comments that might be useful to consider when performing this
+transformation:
+- We currently have a generic MarkerNode which we use (mostly for debugging)
+  to keep some of the structure of high-level constructs that we don't need
+  in the CFG, such as locking or try-catch blocks.  I'd except this structure
+  to be more important for type-checkers, so it might be worth having
+  explicit types for the different kinds.
+- If the CFG is used for type-checking, then somebody should go carefully
+  over the CFGBuilder to make sure it does not unsoundly approximate the
+  source program.  Some approximations might be sound for flow-sensitive
+  type-refinement (and only lead to missed opportunities for type
+  refinement), but are not sound when type-checking.  I don't have a concrete
+  example in mind, but the TODOs might be a good starting point.
+- A challenge will be how to deal with type-checking errors that occur for
+  CFG nodes without direct source-level counter-part (which were introduced
+  as part of desugaring), but I'm sure you are aware of this.
+
+* there are currently multiple ways to obtain an error reporter and other
+  global values.  Put these in a Checker Framework environment, and make all
+  the code use a single consistent way to obtain them.  This will make the
+  code easier to understand and will make mocking easier to do.
+
+* staging for compound checkers:  ability to run multiple checkers within a
+  single type system [Rene]
+
+* staging of steps within a checker:  separate conceptually distinct tasks,
+  such as implicits/defaults/flow/typevalidity/subtyping/customchecks.
+  This can be done in twwo spages:two tasks:
+   1.  Using the current AnnotatedTypeFactory's lazy approach to annotation,
+       separate the current logic into individual steps, where each step is run
+       successively on the current tree.
+   2.  Abandon the lazy approach.  Separate the current logic into steps that
+       are run over an entire compilation unit.  This is to avoid the visitor
+       feedback loop that makes debugging so difficult.
+  [Rene]
+
+* separate definition of a type-checker from performing type-checking using
+  that definition, to make "initialization" of a type system easier
+* eliminate postInit method
+
+* load javac via a classloader rather than by starting a new process -- this
+  may avoid some problems with the Maven plugin?
+
+===========================================================================
+
+Inference-related tasks:
+
+* Create unique ids for each annotated type location in source code, these IDs
+will replace inference's ids.  This can be a discrete task, though I would
+imagine there are some interactions with Stuart's code.
+
+* Add a callback for either generating constraints or enforcing checks in
+either commonAssignmentCheck or QualifierHierarchy.  If this is done through
+QualifierHierarchy, create methods isSubtype and mustBeSubtype to distinguish
+between the times when isSubtype is used in conditionals rather than to
+enforce a subtype relationship
+
+* Add a Solve step to the top-level control flow.  Perhaps, as part of the high
+level staging this step can optionally be replaced by a PrintConstraints step,
+or a GenerateGame step.
+
+===========================================================================
+
+Overview of Stuart's refactorings, in his own words:
+
+> The stuff I'm working on has three main components:
+>
+>  - User-defined qualifier support: Allow checkers to use custom objects to
+>    represent qualifiers internally, instead of using AnnotationMirrors
+>    directly.
+>  - Qualifier parameter support: Allow checkers to easily add support for
+>    generics-style qualifier parameters.  This uses user-defined qualifiers in
+>    its implementation.
+>  - SPARTA: A new implementation of the SPARTA FlowChecker, based on qualifier
+>    parameters.
+>
+> Here are my branches:
+>
+> ssh://tricycle/homes/gws/spernste/hg/checker-framework
+>   (branched from checker-framework-dff)
+>   This has changes to existing checker framework components that are necessary
+>   to make the other components work.
+>
+> ssh://tricycle/homes/gws/spernste/hg/checker-framework-qual
+>   (branched from ~spernste/hg/checker-framework)
+>   This has user-defined qualifier support, implemented as an abstract base
+>   checker in the checkers.userqual package.  I also adapted the TaintingChecker
+>   to use the new userqual base classes.
+>
+> ssh://tricycle/homes/gws/spernste/hg/checker-framework-qualparam
+>   (branched from ~spernste/hg/checker-framework-qual)
+>   This has qualifier parameter support, implemented as an abstract base checker
+>   in the checkers.qualparam package.  I also adapted the TaintingChecker to
+>   use the new qualparam base classes.
+>
+> ssh://tricycle/homes/gws/spernste/hg/checker-framework-sparta
+>   (branched from ~spernste/hg/checker-framework-qualparam)
+>   This has the qualparam-based implementation of the SPARTA FlowChecker.
+>
+> I think I have the permissions set so anyone can check out from these
+> repositories, but let me know if I need to fix them.
+
+===========================================================================
diff --git a/docs/manual/todo.txt b/docs/manual/todo.txt
new file mode 100644
index 0000000..6741eaf
--- /dev/null
+++ b/docs/manual/todo.txt
@@ -0,0 +1,28 @@
+Necessary changes to the manual.
+
+------------------------------------------------------
+The warning output now contains the most specific error key that
+can be used to suppress that warning.
+Update all example output in the manual to account for this.
+------------------------------------------------------
+
+New section on concepts and annotations that apply to all type-systems and checkers
+
+Where would a good place be for this?  At the beginning/end?  Split into multiple sections?
+
+- Pre-/postconditions from a user perspective:
+  - Explain the generic pre- and postcondition annotations: @EnsuresAnnotation/@EnsuresAnnotations, @EnsuresAnnotationIf/@EnsuresAnnotationsIf and @RequiresAnnotation/@RequiresAnnotations.
+  - Explain what kind of strings are supported (see text near the end of this documentat; use that to augment the existing Checker Framework documentation).
+
+- Pre-/postconditions from a developer perspective:
+  - Explain the meta-annotations @PreconditionAnnotation, @ConditionalPostconditionAnnotation and @PostconditionAnnotation.
+
+------------------------------------------------------
+
+Explain relationship between XXXChecker and XXXAnnotatedTypeFactory:  if
+the Checker is a subtype of BaseTypeChecker, the AnnotatedTypeFactory has
+to be a subtype of AbstractBasicAnnotatedTypeFactory, because
+BaseTypeVisitor assumes that.
+[Defer this because it is being rewritten.]
+
+------------------------------------------------------
diff --git a/docs/manual/troubleshooting.tex b/docs/manual/troubleshooting.tex
new file mode 100644
index 0000000..fad4663
--- /dev/null
+++ b/docs/manual/troubleshooting.tex
@@ -0,0 +1,1116 @@
+\htmlhr
+\chapterAndLabel{Troubleshooting, getting help, and contributing}{troubleshooting}
+
+\begin{sloppypar}
+The manual might already answer your question, so first please look for
+your answer in the manual,
+including this chapter and the FAQ (Chapter~\ref{faq}).
+If not, you can use the mailing list,
+\code{checker-framework-discuss@googlegroups.com}, to ask other users for
+help.  For archives and to subscribe, see \url{https://groups.google.com/forum/#!forum/checker-framework-discuss}.
+To report bugs, please see Section~\ref{reporting-bugs}.
+If you want to help out, you can give feedback (including on the
+documentation), choose a bug and fix it, or select a
+project from the ideas list at
+\url{https://rawgit.com/typetools/checker-framework/master/docs/developer/gsoc-ideas.html}.
+\end{sloppypar}
+
+
+\sectionAndLabel{Common problems and solutions}{common-problems}
+
+\begin{itemize}
+\item
+To verify that you are using the compiler you think you are, you can add
+\code{-version} to the command line.  For instance, instead of running
+\code{javac -g MyFile.java}, you can run \code{javac \underline{-version} -g
+  MyFile.java}.  Then, javac will print out its version number in addition
+to doing its normal processing.  Note that if you are running under Java 8,
+\<\$CHECKERFRAMEWORK/bin/javac -version> prints ``javac (version info
+not available)'' because it uses the Error Prone compiler which does not
+provide version information.
+
+\end{itemize}
+
+
+\subsectionAndLabel{Unable to compile the Checker Framework}{common-problems-compiling}
+
+If you get the following error while compiling the Checker Framework itself:
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+checker-framework/stubparser/dist/stubparser.jar(org/checkerframework/stubparser/ast/CompilationUnit.class):
+warning: [classfile] Signature attribute introduced in version 49.0 class files is ignored in version 46.0 class files
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+\noindent
+and you have used Eclipse to compile the Checker Framework, then probably
+you are using a very old version of Eclipse.  (If you
+install Eclipse from the Ubuntu 16.04 repository, you get Eclipse version
+3.8.  Ubuntu 16.04 was released in April 2016, and Eclipse 3.8 was released
+in June 2012, with subsequent major releases in June 2013, June 2014, and
+June 2015.)
+Install the latest version of Eclipse and use it instead.
+
+
+\subsectionAndLabel{Unable to run the checker, or checker crashes}{common-problems-running}
+
+If you are unable to run the checker, or if the checker or the compiler
+terminates with an error, then the problem may be a problem with your environment.
+(If the checker or the compiler crashes, that is a bug in the Checker
+Framework; please report it.  See Section~\ref{reporting-bugs}.)
+This section describes some possible problems and solutions.
+
+\begin{itemize}
+\item
+  \label{no-such-field-error-release}
+  An error that includes \<java.lang.NoSuchFieldError: RELEASE"> means that
+  you are not using the correct compiler (you are not using a Java 9+ compiler).
+
+\item
+If you get the error
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+com.sun.tools.javac.code.Symbol$CompletionFailure: class file for com.sun.source.tree.Tree not found
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+\noindent
+then you are using the source installation and file \code{tools.jar} is not
+on your classpath.  See the installation instructions
+(Section~\ref{installation}).
+
+\item
+If you get an error like one of the following,
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+...\build.xml:59: Error running ${env.CHECKERFRAMEWORK}\checker\bin\javac.bat compiler
+\end{Verbatim}
+
+\begin{Verbatim}
+.../bin/javac: Command not found
+\end{Verbatim}
+
+\begin{Verbatim}
+error: Annotation processor 'org.checkerframework.checker.signedness.SignednessChecker' not found
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+\noindent
+then the problem may be that you have not set the \code{CHECKERFRAMEWORK} environment
+variable, as described in Section~\ref{javac-wrapper}.  Or, maybe
+you made it a user variable instead of a system variable.
+
+\item
+If you get one of these errors:
+
+\begin{alltt}
+The hierarchy of the type \emph{ClassName} is inconsistent
+
+The type com.sun.source.util.AbstractTypeProcessor cannot be resolved.
+  It is indirectly referenced from required .class files
+\end{alltt}
+
+\begin{sloppypar}
+\noindent
+then you are likely \textbf{not} using the Checker Framework compiler.  Use
+either \code{\$CHECKERFRAMEWORK/checker/bin/javac} or one of the alternatives
+described in Section~\ref{javac-wrapper}.
+\end{sloppypar}
+
+\item
+If you get the error
+
+\begin{Verbatim}
+  java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
+\end{Verbatim}
+
+\noindent
+% I'm not 100% sure of the following explanation and solution.
+then an annotation is not present at run time that was present at compile
+time.  For example, maybe when you compiled the code, the \<@Nullable>
+annotation was available, but it was not available at run time.
+
+\item
+If you get an error that contains lines like these:
+
+\begin{Verbatim}
+Caused by: java.util.zip.ZipException: error in opening zip file
+    at java.util.zip.ZipFile.open(Native Method)
+    at java.util.zip.ZipFile.<init>(ZipFile.java:131)
+\end{Verbatim}
+
+\noindent
+then one possibility is that you have installed the Checker Framework in a
+directory that contains special characters that Java's ZipFile
+implementation cannot handle.  For instance, if the directory name contains
+``\<+>'', then Java 1.6 throws a ZipException, and Java 1.7 throws a
+FileNotFoundException and prints out the directory name with ``\<+>''
+replaced by blanks.
+
+\item
+The Checker Framework sometimes runs out of memory when processing very
+large Java files, or files with very large methods.
+
+If you get an error such as
+\begin{Verbatim}
+error: SourceChecker.typeProcess: unexpected Throwable (OutOfMemoryError) while processing ...
+  ; message: GC overhead limit exceeded
+\end{Verbatim}
+
+\noindent
+(all on one line),
+then either give the JVM more memory when running the Checker Framework, or
+split your files and methods into smaller ones, or both.
+
+\item
+Versions of \ahref{https://errorprone.info/}{Error Prone} before 2.4.0 are
+incompatible with the Checker Framework.  Those versions of Error Prone
+use an outdated version of the Checker Framework's dataflow analysis
+library, which conflicts with the Checker Framework itself.
+
+If you wish to use both Error Prone and the Checker Framework, then use
+Error Prone version 2.4.0 or later.
+If you cannot upgrade to Error Prone 2.4.0 or later, there is a way to
+still use both tools.  The
+\ahreforurl{https://github.com/kelloggm/checkerframework-gradle-plugin\#incompatibility-with-error-prone}{Gradle
+  plugin documentation} shows how to do so if you use the Gradle build
+system.
+
+\end{itemize}
+
+
+\subsectionAndLabel{Unexpected warnings not related to type-checking}{common-problems-non-typechecking}
+
+This section gives solutions for some warning messages that are not related
+to type errors in your code.
+
+\begin{itemize}
+
+\item
+If you get an error like the following
+
+\begin{Verbatim}
+error: scoping construct for static nested type cannot be annotated
+error: scoping construct cannot be annotated with type-use annotation
+\end{Verbatim}
+
+\noindent
+  then you have probably written something like one of the following:
+\begin{description}
+\item[\<@Nullable java.util.List>]
+The correct Java syntax to write an annotation on a fully-qualified type
+name is to put the annotation on the simple name part, as in
+\<java.util.@Nullable List>.  But, it's usually
+better to add \<import java.util.List> to your source file, so that you can
+just write \<@Nullable List>.
+\item[\<@Nullable Map.Entry>]  You must write \<Outer.@Nullable
+StaticNestedClass> rather than \<@Nullable Outer.StaticNestedClass>.
+Since a static nested class does not depend on its outer class, the
+annotation on the outer class would have no effect and is forbidden.
+\end{description}
+
+Java 8 requires that a type qualifier be written directly on the type that
+it qualifies, rather than on a scoping mechanism that assists in resolving
+the name.  Examples of scoping mechanisms are package names and outer
+classes of static nested classes.
+
+The reason for the Java 8 syntax is to avoid syntactic irregularity.  When
+writing a member nested class (also known as an inner class), it is
+possible to write annotations on both the outer and the inner class:  \<@A1
+Outer. @A2 Inner>.  Therefore, when writing a static nested class, the
+annotations should go on the same place:  \<Outer. @A3 StaticNested> (rather
+than \<@ConfusingAnnotation Outer.\ Nested> where
+\<@ConfusingAnnotation> applies to \<Outer> if \<Nested> is a member class
+and applies to \<Nested> if \<Nested> is a static class).  It's not legal
+to write an annotation on the outer class of a static nested class, because
+neither annotations nor instantiations of the outer class affect the static
+nested class.
+
+Similar arguments apply when annotating \<pakkage.Outer.Nested>.
+
+\item
+An ``error: package ... does not exist'' or ``error: cannot find symbol''
+about classes in your own project or its dependencies means that you have
+left your own project or its dependencies off the classpath when you
+invoked the compiler.
+
+\end{itemize}
+
+
+\subsectionAndLabel{Unexpected type-checking results}{common-problems-typechecking}
+
+This section describes possible problems that can lead the type-checker to
+give unexpected results.
+
+
+\begin{itemize}
+\item
+  If the Checker Framework is unable to verify a property that you know is
+  true, then you should formulate a proof about why the property is true.
+  Your proof depends on some set of facts about the program and its
+  operation.  State them completely; for example, don't write ``the field
+  \<f> cannot be null when execution reaches line 22'', but justify
+  \emph{why} the field cannot be null.
+
+  Once you have written down your proof, translate each fact into a Java
+  annotation.
+
+  If you are unable to express some aspect of your proof as an annotation,
+  then the type system is not capable of reproducing your proof.  You might
+  need to find a different proof, or extend the type system to be more
+  expressive, or suppress the warning.
+
+  If you are able to express your entire proof as annotations, then look at
+  the Checker Framework error messages.
+  \begin{itemize}
+  \item
+    Perhaps your proof was incomplete or incorrect.  Your proof might
+    depend on some fact you didn't write down, such as ``method
+    \<m> has no side effects.''  In this case, you should revise your proof
+    and add more annotations to your Java code.
+  \item
+    Perhaps your proof is incorrect, and the errors indicate where.
+  \item
+    Perhaps there is a bug in the type-checker.  In this case,
+    you should report it to the maintainers, giving your proof and explaining
+    why the type-checker should be able to make the same inferences that you did.
+  \end{itemize}
+
+  Recall that the Checker Framework does modular verification,
+  one procedure at a time; it observes the specifications, but not the
+  implementations, of other methods.
+
+  Also see Section~\ref{handling-warnings}, which explains this same
+  methodology in different words.
+
+\item
+If a checker seems to be ignoring the annotation on a method, then it is
+possible that the checker is reading the method's signature from its
+\code{.class} file, but the \code{.class} file was not created by a Java 8
+or later compiler.
+You can check whether the annotations actually appear in the
+\code{.class} file by using the \code{javap} tool.
+
+If the annotations do not appear in the \code{.class} file, here are two
+ways to solve the problem:
+\begin{itemize}
+\item
+  Re-compile the method's class with the Checker Framework compiler.  This will
+  ensure that the type annotations are written to the class file, even if
+  no type-checking happens during that execution.
+\item
+  Pass the method's file explicitly on the command line when type-checking,
+  so that the compiler reads its source code instead of its \code{.class}
+  file.
+\end{itemize}
+
+\item
+If a checker issues a warning about a property that it accepted (or that
+was checked) on a previous line, then probably there was a side-effecting
+method call in between that could invalidate the property.  For example, in
+this code:
+
+\begin{Verbatim}
+if (currentOutgoing != null && !message.isCompleted()) {
+    currentOutgoing.continueBuffering(message);
+}
+\end{Verbatim}
+
+\noindent
+the Nullness Checker will issue a warning on the second line:
+\begin{Verbatim}
+warning: [dereference.of.nullable] dereference of possibly-null reference currentOutgoing
+    currentOutgoing.continueBuffering(message);
+    ^
+\end{Verbatim}
+
+If \<currentOutgoing> is a field rather than a local variable, and
+\<isCompleted()> is not a pure method, then a null pointer
+dereference can occur at the given location, because \<isCompleted()> might set
+the field \<currentOutgoing> to \<null>.
+
+If you want to communicate that
+isCompleted() does not set the field \<currentOutgoing> to \<null>, you can use
+\<\refqualclass{dataflow/qual}{Pure}>,
+\<\refqualclass{dataflow/qual}{SideEffectFree}>,
+or \<\refqualclass{checker/nullness/qual}{EnsuresNonNull}> on the
+declaration of \<isCompleted()>; see Sections~\ref{type-refinement-purity}
+and~\ref{nullness-method-annotations}.
+
+
+\item
+If a checker issues a type-checking error for a call that the library's
+documentation states is correct, then maybe that library method has not yet
+been annotated, so default annotations are being used.
+
+To solve the problem, add the missing annotations to the library (see
+Chapter~\ref{annotating-libraries}).  The
+annotations might appear in stub files (which appear
+in an \code{.astub} file together with the checker's source code)
+or in the form of annotated libraries (which appear
+in \url{https://github.com/typetools/}).
+
+\item
+If the compiler reports that it cannot find a method from the JDK or
+another external library, then maybe the stub file for that class
+is incomplete.
+
+To solve the problem, add the missing annotations to the library, as
+described in the previous item.
+
+The error might take one of these forms:
+
+\begin{Verbatim}
+method sleep in class Thread cannot be applied to given types
+cannot find symbol: constructor StringBuffer(StringBuffer)
+\end{Verbatim}
+
+\item
+If you get an error related to a bounded type parameter and a literal such
+as \<null>, the problem may be missing defaulting.  Here is an example:
+
+\begin{Verbatim}
+mypackage/MyClass.java:2044: warning: incompatible types in assignment.
+      T retval = null;
+                 ^
+  found   : null
+  required: T extends @MyQualifier Object
+\end{Verbatim}
+
+\noindent
+A value that can be assigned to a variable of type \<T extends @MyQualifier
+Object> only if that value is of the bottom type, since the bottom type is
+the only one that is a subtype of every subtype of \<T extends @MyQualifier
+Object>.  The value \<null> satisfies this for the Java type system, and it
+must be made to satisfy it for the pluggable type system as well.  The
+typical way to address this is to call \<addStandardLiteralQualifiers> on the \<LiteralTreeAnnotator>.
+
+\item
+An error such as
+
+\begin{Verbatim}
+MyFile.java:123: error: incompatible types in argument.
+                        myModel.addElement("Scanning directories...");
+                                           ^
+  found   : String
+  required: ? extends Object
+\end{Verbatim}
+
+\noindent
+may stem from use of raw types.  (``\<String>'' might be a different type
+and might have type annotations.)  If your declaration was
+
+\begin{Verbatim}
+  DefaultListModel myModel;
+\end{Verbatim}
+
+\noindent
+then it should be
+\begin{Verbatim}
+  DefaultListModel<String> myModel;
+\end{Verbatim}
+
+Running the regular Java compiler with the \<-Xlint:unchecked> command-line
+option will help you to find and fix problems such as raw types.
+
+
+\item
+The error
+
+\begin{Verbatim}
+error: annotation type not applicable to this kind of declaration
+    ... List<@NonNull String> ...
+\end{Verbatim}
+
+\noindent
+indicates that you are using a definition of \<@NonNull> that is a
+declaration annotation, which cannot be used in that syntactic location.
+For example, many legacy annotations such as those listed in
+Figure~\ref{fig-nullness-refactoring} are declaration annotations.  You can
+fix the problem by instead using a definition of \<@NonNull> that is a type
+annotation, such as the Checker Framework's annotations; often this only
+requires changing an \<import> statement.
+
+
+\item
+If Eclipse gives the warning
+
+\begin{Verbatim}
+The annotation @NonNull is disallowed for this location
+\end{Verbatim}
+
+\noindent
+then you have the wrong version of the \<org.eclipse.jdt.annotation>
+classes.  Eclipse includes two incompatible versions of these annotations.
+You want the one with a name like
+\<org.eclipse.jdt.annotation\_2.0.0.....jar>, which you can find in the
+\<plugins> subdirectory under the Eclipse installation directory.
+Add this .jar file to your build path.
+
+
+\item
+When one formal parameter's annotation references another formal
+parameter's name, as in this constructor:
+
+\begin{smaller}
+\begin{Verbatim}
+public String(char value[], @IndexFor("value") int offset, @IndexOrHigh("value") int count) { ... }
+\end{Verbatim}
+\end{smaller}
+
+\noindent
+you will get an error such as
+
+\begin{smaller}
+\begin{Verbatim}[]
+[expression.unparsable] Expression in dependent type annotation invalid:
+Use "#1" rather than "value"
+\end{Verbatim}
+\end{smaller}
+
+Section~\ref{java-expressions-as-arguments} explains that you need to use
+a different syntax to refer to a formal parameter:
+
+\begin{smaller}
+\begin{Verbatim}
+public String(char value[], @IndexFor("#1") int offset, @IndexOrHigh("#1") int count) { ... }
+\end{Verbatim}
+\end{smaller}
+
+
+\item
+\label{false-positive-in-dead-code}
+  The Checker Framework can issue false positive warnings in dead code.  An
+  example is
+
+\begin{smaller}
+\begin{Verbatim}
+  void m() {
+    String t = "";
+    t.toString();
+    try {
+    } catch (Throwable e) {
+        t.toString(); // Nullness Checker issues spurious (dereference.of.nullable)
+    }
+\end{Verbatim}
+\end{smaller}
+
+\item
+  If the Checker Framework issues an error or warning that you cannot
+  reproduce using a smaller test case, then try running the Checker
+  Framework with the \<-AatfDoNotCache> command-line argument.  If the
+  Checker Framework behaves differently with and without that command-line
+  argument, then there is a bug related to its internal caches.  Please
+  report that bug at
+  \url{https://github.com/typetools/checker-framework/issues}.
+
+\end{itemize}
+
+
+\subsectionAndLabel{Unexpected compilation output when running javac without a pluggable type-checker}{common-problems-running-javac}
+
+A message of the form
+
+\begin{Verbatim}
+  error: annotation values must be of the form 'name=value'
+        @LTLengthOf("firstName", "lastName") int index;
+                    ^
+\end{Verbatim}
+
+\noindent
+is caused by incorrect Java syntax.  When you supply a set of multiple
+values as an annotation argument, you need to put curly braces around them:
+
+\begin{Verbatim}
+        @LTLengthOf({"firstName", "lastName"}) int index;
+\end{Verbatim}
+
+
+\sectionAndLabel{How to report problems (bug reporting)}{reporting-bugs}
+
+If you have a problem with any checker, or with the Checker Framework,
+please file a bug at
+\url{https://github.com/typetools/checker-framework/issues}.
+(First, check whether there is an existing bug report for that issue.)
+If the problem is with an incorrect or missing annotation on a library,
+including the JDK, see Section~\ref{reporting-bugs-annotated-libraries}.
+You can also use the issue tracker to make suggestions and feature
+requests.  We also welcome pull requests with annotated libraries, bug
+fixes, new features, new checkers, and other improvements.
+
+Please ensure that your bug report is clear and that it is complete.
+Otherwise, we may be unable to understand it or to reproduce it, either of
+which would prevent us from helping you.  Your bug report should include at
+least the following 4 parts: commands, inputs, outputs, and expectation.
+
+% If you update this, also update ../../.github/ISSUE_TEMPLATE
+\begin{description}
+\item[Commands]
+  Provide one or more commands that can be pasted into a command shell to
+  reproduce the problem.
+
+  In the simplest case, you will provide one command of
+  the form \<javac -processor ... MyFile.java>.  In more complex cases, it
+  might be a set of commands clone a repository, check out a branch, and
+  run a build command.  Include commands to install software and set
+  environment variables if necessary.
+
+  If you encountered the problem in an IDE, reproduce it from the command
+  line; if you cannot, report the bug to the IDE integration.
+
+  It can be helpful to add \\
+  \code{-version -Aversion -AprintGitProperties -verbose -AprintVerboseGenerics} \\
+  to the javac options.  This causes the
+  compiler to output debugging information, including its version number.
+
+\item[Inputs]
+  Include all files that are necessary to reproduce the problem.  This
+  includes every file that is used by any of the commands you reported, and
+  possibly other files as well.  This is not necessary if the commands you
+  provided obtain the code that is type-checked.
+
+  If you cannot share your code, create a new, small test case.  (A small
+  test case is helpful even if your code is not secret!)  If the Checker
+  Framework crashed, the progress tracing options
+  (Section~\ref{creating-debugging-options-progress}) can be helpful in
+  determining which file the Checker Framework was processing when it
+  crashed.
+
+  \emph{Minimization:}
+  If your command invokes a build system such as Gradle or Maven, it can be
+  helpful to the maintainers if you are able to reduce it to a single
+  \<javac> invocation on a single file.
+  This also rules out the possibility that the problem is with the build system
+  integration rather than the checker itself.
+  If you use an invasive
+  annotation processor such as Lombok, then try to reproduce the problem
+  without it --- this will indicate whether the problem is in the Checker
+  Framework proper or in its interaction with Lombok.
+
+\item[Output]
+  Indicate exactly what the result was by attaching a file or using
+  cut-and-paste from your command shell.  Don't merely describe it in
+  words, don't use a screenshot, and don't provide just part of the output.
+
+\item[Expectation]
+  If the problem is not a crash, then
+  indicate what you expected the result to be, since a bug is a difference
+  between desired and actual outcomes.  Also, please indicate \textbf{why}
+  you expected that result --- explaining your reasoning can reveal
+  how your reasoning is different than the checker's and which
+  one is wrong.  Remember that the checker reasons modularly and
+  intraprocedurally:  it examines one method at a time, using only the
+  method signatures of other methods.
+
+  It can also be helpful to indicate what you have already done to try to
+  understand the problem.  Did you do any additional experiments?  What
+  parts of the manual did you read, and what else did you search for in the
+  manual?  Without this information, the maintainers may give you redundant
+  suggestions or may waste time re-doing work you have already done.
+\end{description}
+
+% A particularly useful format for a test case is as a new file, or a diff to
+% an existing file, for the existing Checker Framework test suite.  For
+% instance, for the Nullness
+% Checker, see directory \<checker-framework/checker/tests/nullness/>.
+% But, please report your bug even if you do not report it in this format.
+
+When reporting bugs, please focus on realistic scenarios and well-written
+code.  We are sure that you can make up artificial code that stymies the
+type-checker!  Those aren't a good use of your time to report nor the
+maintainers' time to evaluate and fix.
+
+
+\subsectionAndLabel{Problems with annotated libraries}{reporting-bugs-annotated-libraries}
+
+If a checker reports a warning because a library contains \emph{incorrect} annotations,
+then please open a pull request that adds the annotations.  If the
+library's maintainers have added annotations, make a pull request to them.
+If the Checker Framework maintainers have added annotations,
+you can find the annotated source in \url{https://github.com/typetools/}.
+
+If a checker reports a warning because a library is \emph{not
+annotated}, please do not open a GitHub issue.
+Instead, we appreciate pull requests to help us improve the annotations.
+Alternatively or in the interim, you can use stub files as described in
+Section~\ref{stub}.
+
+For either approach, please see \chapterpageref{annotating-libraries} for
+tips about writing library annotations.
+
+Thanks for your contribution to the annotated libraries!
+
+
+\sectionAndLabel{Building from source}{build-source}
+
+The Checker Framework release (Section~\ref{installation}) contains
+everything that most users need, both to use the distributed checkers and
+to write your own checkers.  This section describes how to compile its
+binaries from source.  You will be using the latest development version of
+the Checker Framework, rather than an official release.
+
+% Doing
+% so permits you to examine and modify the implementation of the distributed
+% checkers and of the checker framework.  It may also help you to debug
+% problems more effectively.
+
+Note the JDK requirements (Section~\ref{installation}).
+
+
+\subsectionAndLabel{Install prerequisites}{building-prerequisites}
+
+You need to install several packages in order to build the Checker
+Framework.
+Follow the instructions for your operating system.
+If your OS is not listed, adapt the instructions for an existing OS;
+for example, other Linux distributions will be similar to Ubuntu.
+
+% Why is this necessary?  What goes wrong if it is not set?  Can I avoid
+% the need to set it?  It's used for:
+%  * the location of tools.jar, below.
+%  * the default location of CTSYM, in checker/jdk/Makefile.
+Put the setting of the \<JAVA\_HOME> environment variable in a startup file
+such as \<.bash\_profile> or \<.bashrc>, if \<JAVA\_HOME> is not already
+set appropriately.  It must be the location of your JDK 8 or JDK 11
+installation (not the JRE installation, and not JDK 7 or earlier).
+You may need to log out and log back in for the setting to take effect.
+
+\begin{description}
+\item[Ubuntu]
+  Run the following commands:
+
+% Keep this up to date with ../../checker/bin-devel/Dockerfile-ubuntu-jdk11-plus
+\begin{Verbatim}
+sudo apt-get -qqy update
+sudo apt-get -qqy install \
+ openjdk-11-jdk \
+ ant cpp git gradle jq libcurl3-gnutls \
+ make maven mercurial python3-pip python3-requests unzip wget \
+ dia hevea imagemagick latexmk librsvg2-bin maven rsync \
+ texlive-font-utils texlive-fonts-recommended \
+ texlive-latex-base texlive-latex-extra texlive-latex-recommended
+pip3 install html5validator
+\end{Verbatim}
+
+  You may have to answer questions about which time zone your computer is in.
+
+In a startup file, write:
+% Can someone give a simpler command?
+% Command taken from:
+%   https://github.com/typetools/checker-framework/blob/master/checker/bin-devel/build.sh#L19
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  export JAVA_HOME=${JAVA_HOME:-$(dirname "$(dirname "$(readlink -f "$(which javac)")")")}
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+\item[Mac OS]
+  If you employ \ahref{https://brew.sh}{homebrew} to install packages, run
+  the following commands:
+
+\begin{Verbatim}
+brew update
+brew install git ant hevea maven mercurial librsvg unzip make
+brew tap AdoptOpenJDK/openjdk
+brew install --cask adoptopenjdk11
+brew install --cask mactex
+\end{Verbatim}
+
+Make a \<latex> directory and copy \<hevea.sty> file into it (you may need to update the version number \<2.31>):
+
+\begin{Verbatim}
+mkdir -p $HOME/Library/texmf/tex/latex
+cp -p /usr/local/Cellar/hevea/2.31/lib/hevea/hevea.sty $HOME/Library/texmf/tex/latex/
+\end{Verbatim}
+
+Note: If copy permission is denied, try \<sudo>.
+
+In a startup file, write:
+
+\begin{Verbatim}
+export JAVA_HOME=$(/usr/libexec/java_home -v 1.11)
+\end{Verbatim}
+or
+\begin{Verbatim}
+export JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home/
+\end{Verbatim}
+
+
+\item[Windows]
+  To build on Windows 10,
+  run \<bash> to obtain a version of
+  Ubuntu (provided by the Windows Subsystem for Linux) and follow the Ubuntu
+  instructions.
+
+% To build on other versions of Windows,
+% here is an \emph{incomplete} list of needed prerequisites:
+% \begin{itemize}
+% \item
+%   Install MSYS to obtain the \<cp> and \<make> commands.
+% \end{itemize}
+
+\end{description}
+
+
+\subsectionAndLabel{Obtain the source}{building-obtain-source}
+
+Obtain the latest source code from the version control repository:
+
+\begin{Verbatim}
+git clone https://github.com/typetools/checker-framework.git checker-framework
+export CHECKERFRAMEWORK=`pwd`/checker-framework
+\end{Verbatim}
+% $ to unconfuse Emacs LaTeX mode
+
+You might want to add an \<export CHECKERFRAMEWORK=...> line to your
+\<.bashrc> file.
+
+
+\subsectionAndLabel{Build the Checker Framework}{building}
+
+% Building (compiling) the checkers and framework from source creates the
+% \code{checker.jar} file.  A pre-compiled \code{checker.jar} is included
+% in the distribution, so building it is optional.  It is mostly useful for
+% people who are developing compiler plug-ins (type-checkers).  If you only
+% want to \emph{use} the compiler and existing plug-ins, it is sufficient to
+% use the pre-compiled version.
+
+\begin{enumerate}
+
+\item
+Run \code{./gradlew assemble} to build the Checker Framework:
+
+\begin{Verbatim}
+  cd $CHECKERFRAMEWORK
+  ./gradlew assemble
+\end{Verbatim}
+% $ to unconfuse Emacs LaTeX mode
+
+\noindent
+Your antivirus program (e.g., Windows Security Virus \& threat protection)
+might make the build run very slowly, or might make it seem to stall.  Just
+give it time.
+
+\item
+Once it is built, you may wish to put the Checker Framework's \<javac>
+even earlier in your \<PATH>:
+
+\begin{Verbatim}
+export PATH=$CHECKERFRAMEWORK/checker/bin:${PATH}
+\end{Verbatim}
+
+The Checker Framework's \code{javac} ensures that all required
+libraries are on your classpath and boot classpath, but is otherwise
+identical to the javac Java compiler.
+
+Putting the Checker Framework's \<javac> earlier in your \<PATH> will
+ensure that the Checker Framework's version is used.
+
+\item Test that everything works:
+
+  \begin{itemize}
+
+  \item Run \code{./gradlew allTests}:
+\begin{Verbatim}
+  cd $CHECKERFRAMEWORK
+  ./gradlew allTests
+\end{Verbatim}
+% $ to unconfuse Emacs LaTeX mode
+
+  \item Run the Nullness Checker examples (see
+    Section~\refwithpage{nullness-example}).
+
+  \end{itemize}
+
+\end{enumerate}
+
+
+\subsectionAndLabel{Build the Checker Framework Manual (this document)}{building-manual}
+
+\begin{enumerate}
+\item
+Install needed packages; see Section~\ref{building-prerequisites} for
+instructions.
+
+\item
+Run \code{make} in the \code{docs/manual} directory to build both the PDF and HTML versions of the manual.
+\end{enumerate}
+
+
+\subsectionAndLabel{Code style, IDE configuration, pull requests, etc.}{building-developer-manual}
+
+Please see the
+\href{https://rawgit.com/typetools/checker-framework/master/docs/developer/developer-manual.html}{Checker Framework Developer Manual}.
+
+
+\subsectionAndLabel{Enable continuous integration builds}{building-ci}
+
+We strongly recommend that you enable continuous integration (CI) builds on
+your fork of the projects, so that you learn quickly about errors.  See the
+\ahref{https://rawgit.com/typetools/checker-framework/master/docs/developer/developer-manual.html\#ci}{CI
+  instructions} in the Checker Framework Developer Manual.
+
+
+\subsubsectionAndLabel{Debugging continuous integration builds}{building-ci-debug}
+
+If a continuous integration job is failing, you can reproduce the problem locally.
+The CI jobs all run within Docker containers.
+Install Docker on your local computer, then perform commands like the
+following to get a shell within Docker:
+
+\begin{Verbatim}
+docker pull mdernst/cf-ubuntu-jdk8
+docker run -it mdernst/cf-ubuntu-jdk8 /bin/bash
+\end{Verbatim}
+
+Then, you can run arbitrary commands, including those that appear in the
+CI configuration files.
+
+
+\sectionAndLabel{Contributing}{contributing}
+
+We welcome contributions and pull requests.  Section~\ref{build-source}
+tells you how to set up your development environment to compile the Checker
+Framework, and the
+\href{https://rawgit.com/typetools/checker-framework/master/docs/developer/developer-manual.html}{Checker
+  Framework Developer Manual} gives more information.
+
+One good way to contribute is to give feedback about your use of the tool.
+There is a list
+  of potential projects for new contributors at \url{https://rawgit.com/typetools/checker-framework/master/docs/developer/gsoc-ideas.html}.
+Or, you can fix a bug that
+you have encountered during your use of the Checker Framework.
+
+
+\subsectionAndLabel{Contributing fixes (creating a pull request)}{pull-request}
+
+Please see the
+\ahref{https://rawgit.com/typetools/checker-framework/master/docs/developer/developer-manual.html#pull-requests}{pull
+  requests} section of the Checker Framework Developer Manual.
+Thanks in advance for your contributions!
+
+The easiest bugs to fix for a newcomer are
+\ahref{https://github.com/typetools/checker-framework/issues?q=is\%3Aopen+is\%3Aissue+label\%3A\%22help+wanted\%22}{labeled
+  as ``help wanted''}.
+
+Please do not spam the issue tracker with requests to assign an issue to
+you.  If you want to contribute by fixing an issue, just open a pull
+request that fixes it.  You can do that even if the issue is already
+assigned to someone.
+
+Please do not spam the issue tracker asking how to get started fixing a
+bug.  If you have concrete questions, please ask them and we will be happy
+to assist you.  If you don't know how to get started fixing a particular
+bug, then you should contribute to the project in other ways.  Thanks!
+
+
+\sectionAndLabel{Publications}{publications}
+
+Here are two technical papers about the Checker Framework itself:
+
+\begin{itemize}
+\item
+``Practical pluggable types for Java''~\cite{PapiACPE2008}
+(ISSTA 2008, \myurl{https://homes.cs.washington.edu/~mernst/pubs/pluggable-checkers-issta2008.pdf})
+describes the design and implementation of the Checker Framework.
+The paper also describes case
+studies in which the Nullness, Interning, Javari, and IGJ Checkers found
+previously-unknown errors in real software.
+The case studies also yielded new insights about type systems.
+
+\item
+``Building and using pluggable
+type-checkers''~\cite{DietlDEMS2011}
+(ICSE 2011, \myurl{https://homes.cs.washington.edu/~mernst/pubs/pluggable-checkers-icse2011.pdf})
+discusses further experience with the Checker Framework, increasing the
+number of lines of verified code to 3 million.  The case studies are of the
+Fake Enum, Signature String, Interning, and Nullness Checkers.
+The paper also evaluates the ease
+of pluggable type-checking with the Checker Framework:  type-checkers
+were easy to write, easy for novices to use, and effective in finding
+errors.
+\end{itemize}
+
+Here are some papers about type systems that were implemented and evaluated
+using the Checker Framework:
+
+\begin{description}
+\item[Nullness (Chapter~\ref{nullness-checker})]
+See the two papers about the Checker Framework, described above.
+
+\item[Rawness initialization (Section~\ref{initialization-rawness-checker})]
+``Inference of field initialization'' (ICSE 2011, \myurl{https://homes.cs.washington.edu/~mernst/pubs/initialization-icse2011-abstract.html})
+describes inference for the Rawness Initialization Checker.
+
+\item[Interning (Chapter~\ref{interning-checker})]
+See the two papers about the Checker Framework, described above.
+
+\item[Locking (Chapter~\ref{lock-checker})]
+``Locking discipline inference and checking'' (ICSE 2016,
+\myurl{https://homes.cs.washington.edu/~mernst/pubs/locking-inference-checking-icse2016-abstract.html})
+describes the Lock Checker.
+
+\item[Fake enumerations, type aliases, and typedefs (Chapter~\ref{fenum-checker})]
+See the ICSE 2011 paper about the Checker Framework, described above.
+
+\item[Regular expressions (Chapter~\ref{regex-checker})]
+``A type system for regular expressions''~\cite{SpishakDE2012} (FTfJP 2012, \myurl{https://homes.cs.washington.edu/~mernst/pubs/regex-types-ftfjp2012-abstract.html})
+            describes the Regex Checker.
+
+\item[Format Strings (Chapter~\ref{formatter-checker})]
+``A type system for format strings''~\cite{WeitzKSE2014} (ISSTA 2014, \myurl{https://homes.cs.washington.edu/~mernst/pubs/format-string-issta2014-abstract.html})
+            describes the Format String Checker.
+
+\item[Signature strings (Chapter~\ref{signature-checker})]
+See the ICSE 2011 paper about the Checker Framework, described above.
+
+\item[GUI Effects (Chapter~\ref{guieffect-checker})]
+``JavaUI: Effects for controlling UI object access''~\cite{GordonDEG2013} (ECOOP 2013, \myurl{https://homes.cs.washington.edu/~mernst/pubs/gui-thread-ecoop2013-abstract.html})
+            describes the GUI Effect Checker.
+
+\item
+``Verification games: Making verification fun'' (FTfJP 2012, \myurl{https://homes.cs.washington.edu/~mernst/pubs/verigames-ftfjp2012-abstract.html})
+            describes a general inference approach that, at the time, had only been implemented for the Nullness Checker (Section~\ref{nullness-checker}).
+
+\item[Thread locality (Section~\ref{loci-thread-locality-checker})]
+``Loci: Simple thread-locality for Java''~\cite{WrigstadPMZV2009} (ECOOP 2009,
+\myurl{http://janvitek.org/pubs/ecoop09.pdf})
+
+\item[Security (Section~\ref{sparta-checker})]
+``Static analysis of implicit control flow: Resolving Java reflection and
+  Android intents''~\cite{BarrosJMVDdAE2015} (ASE 2015,
+  \myurl{https://homes.cs.washington.edu/~mernst/pubs/implicit-control-flow-ase2015-abstract.html})
+  describes the SPARTA toolset and information flow type-checker.
+
+``Boolean formulas for the static identification of injection attacks in
+  Java''~\cite{ErnstLMSS2015} (LPAR 2015, \myurl{https://homes.cs.washington.edu/~mernst/pubs/detect-injections-lpar2015-abstract.html})
+
+\item[\href{https://ece.uwaterloo.ca/~wdietl/ownership/}{Generic Universe
+    Types} (Section~\ref{gut-checker})]
+``Tunable static inference for Generic Universe Types'' (ECOOP 2011, \myurl{https://homes.cs.washington.edu/~mernst/pubs/tunable-typeinf-ecoop2011-abstract.html})
+            describes inference for the Generic Universe Types type system.
+
+Another implementation of Universe Types and \href{http://www.cs.rpi.edu/~huangw5/cf-inference/}{ownership types} is  described in
+``Inference and checking of object ownership''~\cite{HuangDME2012} (ECOOP 2012, \myurl{https://homes.cs.washington.edu/~mernst/pubs/infer-ownership-ecoop2012-abstract.html}).
+
+\item[Approximate data (Section~\ref{enerj-checker})]
+``EnerJ: Approximate Data Types for Safe and General Low-Power Computation''~\cite{SampsonDFGCG2011} (PLDI 2011, \myurl{https://homes.cs.washington.edu/~luisceze/publications/Enerj-pldi2011.pdf})
+
+\item[Information flow and tainting (Section~\ref{sparta-checker})]
+``Collaborative Verification of Information Flow
+for a High-Assurance App Store''~\cite{ErnstJMDPRKBBHVW2014} (CCS 2014, \myurl{https://homes.cs.washington.edu/~mernst/pubs/infoflow-ccs2014.pdf}) describes the SPARTA information flow type system.
+
+\item[IGJ and OIGJ immutability (Section~\ref{igj-checker})]
+``Object and reference immutability using Java generics''~\cite{ZibinPAAKE2007} (ESEC/FSE 2007, \myurl{https://homes.cs.washington.edu/~mernst/pubs/immutability-generics-fse2007-abstract.html})
+and
+``Ownership and immutability in generic Java''~\cite{ZibinPLAE2010} (OOPSLA 2010, \myurl{https://homes.cs.washington.edu/~mernst/pubs/ownership-immutability-oopsla2010-abstract.html})
+            describe the IGJ and OIGJ immutability type systems.
+For further case studies, also see the ISSTA 2008 paper about the Checker
+Framework, described above.
+
+\item[Javari immutability (Section~\ref{javari-checker})]
+``Javari: Adding reference immutability to Java''~\cite{TschantzE2005} (OOPSLA 2005, \myurl{https://homes.cs.washington.edu/~mernst/pubs/ref-immutability-oopsla2005-abstract.html})
+            describes the Javari type system.
+For inference, see
+``Inference of reference immutability''~\cite{QuinonezTE2008} (ECOOP 2008, \myurl{https://homes.cs.washington.edu/~mernst/pubs/infer-refimmutability-ecoop2008-abstract.html})
+and
+``Parameter reference immutability: Formal definition, inference tool, and comparison''~\cite{ArtziQKE2009} (J.ASE 2009, \myurl{https://homes.cs.washington.edu/~mernst/pubs/mutability-jase2009-abstract.html}).
+For further case studies, also see the ISSTA 2008 paper about the Checker
+Framework, described above.
+
+\item[ReIm immutability]
+% TODO:  (Section~\ref{reim-checker})
+``ReIm \& ReImInfer: Checking and inference of reference immutability and method purity''~\cite{HuangMDE2012} (OOPSLA 2012, \myurl{https://homes.cs.washington.edu/~mernst/pubs/infer-refimmutability-oopsla2012-abstract.html})
+            describes the ReIm immutability type system.
+
+\end{description}
+
+In addition to these papers that discuss use the Checker Framework
+directly, other academic papers use the Checker Framework in their
+implementation or evaluation.
+Most educational use of the Checker
+Framework is never published, and most commercial use of the Checker
+Framework is never discussed publicly.
+
+(If you know of a paper or other use that is not listed here, please inform
+the Checker Framework developers so we can add it.)
+
+
+\sectionAndLabel{Credits and changelog}{credits}
+
+Differences from previous versions of the checkers and framework can be found
+in the \code{docs/CHANGELOG.md} file.  This file is included in the
+Checker Framework distribution and is also available on the web at
+\myurl{https://checkerframework.org/CHANGELOG.md}.
+
+Developers who have contributed code to the Checker Framework include
+\input{contributors.tex}
+In addition, too many users to list have provided valuable feedback, which
+has improved the toolset's design and implementation.
+Thanks for your help!
+
+
+\sectionAndLabel{License}{license}
+
+Two different licenses apply to different parts of the Checker Framework.
+\begin{itemize}
+\item
+The Checker Framework itself is licensed under the GNU General Public License
+(GPL), version 2, with the classpath exception.
+The GPL is the same license that OpenJDK is licensed
+under.  Just as compiling your code with javac does not infect your code
+with the GPL, type-checking your code with the Checker Framework does not
+infect your code with the GPL\@.  Running the Checker Framework during
+development has no effect on your intellectual property or licensing.  If
+you want to ship the Checker Framework as part of your product, then your
+product must be licensed under the GPL\@.
+\item
+The more permissive MIT License applies
+to code that you might want to include in your own
+program, such as the annotations and run-time utility classes.
+\end{itemize}
+\noindent
+For details, see file
+\ahref{https://raw.githubusercontent.com/typetools/checker-framework/master/LICENSE.txt}{\<LICENSE.txt>}.
+
+
+
+% LocalWords:  jsr unsetting plugins langtools zipfile cp plugin Nullness txt
+% LocalWords:  nullness classpath NonNull MyObject javac uref changelog MyEnum
+% LocalWords:  subtyping containsKey proc classfiles SourceChecker javap jdk
+% LocalWords:  MyFile buildfiles ClassName JRE java bootclasspath
+%  LocalWords:  extJavac ZipFile AnoPrintErrorStack AprintAllQualifiers Jlint
+%  LocalWords:  Telmo Correa Papi NoSuchFieldError ZipException Xlint A1
+%  LocalWords:  FileNotFoundException MyQualifier ImplicitFor A2 A3 JDKs
+%  LocalWords:  StaticNestedClass StaticNested ConfusingAnnotation pre yml
+%%  LocalWords:  CHECKERFRAMEWORK currentOutgoing isCompleted ElementType
+%%  LocalWords:  EnsuresNonNull devel javacutil stubparser rsvg Regex IGJ
+%%  LocalWords:  JavaUI EnerJ App CCS ReIm ReImInfer Anatoly Kupriyanov ci
+%%  LocalWords:  Asumu Takikawa Brotherston McArthur Luo Bayne Coblenz Lai
+%%  LocalWords:  Barros Athaydes Ren Oblak Heule Steph Trask Stalnaker
+%%  LocalWords:  toolset's typechecking LiteralKind reifiable plaintext
+%%  LocalWords:  astub homebrew hevea allTests AatfDoNotCache Java''
+%%  LocalWords:  compileJava typedefs LPAR Tunable OIGJ sudo wanted''
+% LocalWords:  addStandardLiteralQualifiers AprintGitProperties checkers''
+% LocalWords:  expressions'' strings'' access'' intents'' ownership''
+% LocalWords:  Computation'' Store'' generics'' immutability'' purity''
+% LocalWords:  comparison''
diff --git a/docs/manual/typestate-checker.tex b/docs/manual/typestate-checker.tex
new file mode 100644
index 0000000..f524414
--- /dev/null
+++ b/docs/manual/typestate-checker.tex
@@ -0,0 +1,63 @@
+\sectionAndLabel{Typestate checkers}{typestate-checker}
+
+In a regular type system, a variable has the same type throughout its
+scope.
+In a typestate system, a variable's type can change as operations
+are performed on it.
+
+The most common example of typestate is for a \<File> object.  Assume a file
+can be in two states, \<@Open> and \<@Closed>.  Calling the \<close()> method
+changes the file's state.  Any subsequent attempt to read, write, or close
+the file will lead to a run-time error.  It would be better for the type
+system to warn about such problems, or guarantee their absence, at compile
+time.
+
+Accumulation analysis (\chapterpageref{accumulation-checker}) is a special
+case of typestate analysis.  One instantiation of it is the Called Methods
+Checker (\chapterpageref{called-methods-checker}), which can check any
+property of the form ``call method A before method B''.  It also ensures
+that builders are used correctly.
+
+The Java Typestate Checker
+(\url{https://github.com/jdmota/java-typestate-checker}) ensures that
+methods are called in the correct order. The sequences of method calls
+allowed are specified in a protocol file which is associated with a Java
+class by adding a \<@Typestate> annotation to the class.
+
+
+\subsectionAndLabel{Comparison to flow-sensitive type refinement}{typestate-vs-type-refinement}
+
+The Checker Framework's flow-sensitive type refinement
+(Section~\ref{type-refinement}) implements a form of typestate analysis.
+For example, after code that tests a variable against null, the Nullness
+Checker (Chapter~\ref{nullness-checker}) treats the variable's type as
+\<@NonNull \emph{T}>, for some \<\emph{T}>\@.
+
+For many type systems, flow-sensitive type refinement is sufficient.  But
+sometimes, you need full typestate analysis.  This section compares the
+two.
+% (Dependent types and unused variables
+(Unused variables
+% (Section~\ref{unused-fields-and-dependent-types})
+(Section~\ref{unused-fields})
+also have similarities
+with typestate analysis and can occasionally substitute for it.  For
+brevity, this discussion omits them.)
+
+A typestate analysis is easier for a user to create or extend.
+Flow-sensitive type refinement is built into the Checker Framework and is
+optionally extended by each checker.  Modifying the rules requires writing
+Java code in your checker.  By contrast, it is possible to write a simple
+typestate checker declaratively, by writing annotations on the methods
+(such as \<close()>) that change a reference's typestate.
+
+A typestate analysis can change a reference's type to something that is not
+consistent with its original definition.  For example, suppose that a
+programmer decides that the \<@Open> and \<@Closed> qualifiers are
+incomparable --- neither is a subtype of the other.  A typestate analysis
+can specify that the \<close()> operation converts an \<@Open File> into a
+\<@Closed File>.  By contrast, flow-sensitive type refinement can only give
+a new type that is a subtype of the declared type --- for flow-sensitive
+type refinement to be effective, \<@Closed> would need to be a child of
+\<@Open> in the qualifier hierarchy (and \<close()> would need to be
+treated specially by the checker).
diff --git a/docs/manual/units-checker.tex b/docs/manual/units-checker.tex
new file mode 100644
index 0000000..17ebfd6
--- /dev/null
+++ b/docs/manual/units-checker.tex
@@ -0,0 +1,350 @@
+\htmlhr
+\chapterAndLabel{Units Checker}{units-checker}
+
+For many applications, it is important to use the correct units of
+measurement for primitive types.  For example, NASA's Mars Climate Orbiter
+(cost: \$327 million) was lost because of a discrepancy between use
+of the metric unit Newtons and the imperial measure Pound-force.
+
+The \emph{Units Checker} ensures consistent usage of units.
+For example, consider the following code:
+
+\begin{alltt}
+@m int meters = 5 * UnitsTools.m;
+@s int secs = 2 * UnitsTools.s;
+@mPERs int speed = meters / secs;
+\end{alltt}
+
+Due to the annotations \<@m> and \<@s>, the variables \code{meters} and \code{secs} are guaranteed to contain
+only values with meters and seconds as units of measurement.
+The assignment of an unqualified value to \code{meters}, as in
+\code{meters = 99}, will be flagged as an error by the Units Checker.
+Utility class \refclass{checker/units/util}{UnitsTools} provides constants
+that you can multiply with unqualified integer are multiplied to get values
+of the corresponding unit; for example, \code{meters = 99 *
+  UnitsTools.m} is legal, or just \code{meters = 99 *
+  m} if the file contains
+\<import static org.checkerframework.checker.units.util.UnitsTools.*;>.
+To use the \refclass{checker/units/util}{UnitsTools} class, the
+\<checker-util.jar> file must be on the classpath at run time.
+
+The division \code{meters/secs} takes the types of the two operands
+into account and determines that the result is of type
+meters per second, signified by the \code{@mPERs} qualifier.
+We provide an extensible framework to define the result of operations
+on units.
+
+
+\sectionAndLabel{Units annotations}{units-annotations}
+
+The checker currently supports three varieties of units annotations:
+kind annotations (\refqualclass{checker/units/qual}{Length},
+\refqualclass{checker/units/qual}{Mass}, \dots),
+the SI units (\refqualclass{checker/units/qual}{m}, \refqualclass{checker/units/qual}{kg}, \dots), and polymorphic annotations
+(\refqualclass{checker/units/qual}{PolyUnit}).
+
+
+Kind annotations can be used to declare what the expected unit of
+measurement is, without fixing the particular unit used.
+For example, one could write a method taking a \code{@Length} value,
+without specifying whether it will take meters or kilometers.
+The following kind annotations are defined:
+
+\begin{description}
+\item[\refqualclass{checker/units/qual}{Acceleration}]
+
+\item[\refqualclass{checker/units/qual}{Angle}]
+
+\item[\refqualclass{checker/units/qual}{Area}]
+
+\item[\refqualclass{checker/units/qual}{Current}]
+
+\item[\refqualclass{checker/units/qual}{Force}]
+
+\item[\refqualclass{checker/units/qual}{Length}]
+
+\item[\refqualclass{checker/units/qual}{Luminance}]
+
+\item[\refqualclass{checker/units/qual}{Mass}]
+
+\item[\refqualclass{checker/units/qual}{Speed}]
+
+\item[\refqualclass{checker/units/qual}{Substance}]
+
+\item[\refqualclass{checker/units/qual}{Temperature}]
+
+\item[\refqualclass{checker/units/qual}{Time}]
+
+\item[\refqualclass{checker/units/qual}{Volume}]
+\end{description}
+
+% \medskip
+
+
+For each kind of unit, the corresponding SI unit of
+measurement is defined:
+
+\begin{enumerate}
+\item For \code{@Acceleration}:
+  Meter Per Second Square \refqualclass{checker/units/qual}{mPERs2}
+
+\item For \code{@Angle}:
+  Radians \refqualclass{checker/units/qual}{radians},
+  and the derived unit
+  Degrees \refqualclass{checker/units/qual}{degrees}
+
+\item For \code{@Area}:
+  the derived units
+  square millimeters \refqualclass{checker/units/qual}{mm2},
+  square meters \refqualclass{checker/units/qual}{m2}, and
+  square kilometers \refqualclass{checker/units/qual}{km2}
+
+\item For \code{@Current}:
+  Ampere \refqualclass{checker/units/qual}{A}
+
+\item For \code{@Force}:
+  Newton \refqualclass{checker/units/qual}{N}
+  and the derived unit
+  kilonewton \refqualclass{checker/units/qual}{kN}
+
+\item For \code{@Length}:
+  Meters \refqualclass{checker/units/qual}{m}
+  and the derived units
+  millimeters \refqualclass{checker/units/qual}{mm} and
+  kilometers \refqualclass{checker/units/qual}{km}
+
+\item For \code{@Luminance}:
+  Candela \refqualclass{checker/units/qual}{cd}
+
+\item For \code{@Mass}:
+  kilograms \refqualclass{checker/units/qual}{kg}
+  and the derived units
+  grams \refqualclass{checker/units/qual}{g} and
+  metric tons \refqualclass{checker/units/qual}{t}
+
+\item For \code{@Speed}:
+  meters per second \refqualclass{checker/units/qual}{mPERs} and
+  kilometers per hour \refqualclass{checker/units/qual}{kmPERh}
+
+\item For \code{@Substance}:
+  Mole \refqualclass{checker/units/qual}{mol}
+
+\item For \code{@Temperature}:
+  Kelvin \refqualclass{checker/units/qual}{K}
+  and the derived unit
+  Celsius \refqualclass{checker/units/qual}{C}
+
+\item For \code{@Time}:
+  seconds \refqualclass{checker/units/qual}{s}
+  and the derived units
+  minutes \refqualclass{checker/units/qual}{min} and
+  hours \refqualclass{checker/units/qual}{h}
+
+\item For \code{@Volume}:
+  the derived units
+  cubic millimeters \refqualclass{checker/units/qual}{mm3},
+  cubic meters \refqualclass{checker/units/qual}{m3}, and
+  cubic kilometers \refqualclass{checker/units/qual}{km3}
+\end{enumerate}
+
+
+You may specify SI unit prefixes, using enumeration \code{\refclass{checker/units/qual}{Prefix}}.
+The basic SI units
+(\code{@s}, \code{@m}, \code{@g}, \code{@A}, \code{@K},
+ \code{@mol}, \code{@cd})
+take an optional \code{Prefix} enum as argument.
+For example, to use nanoseconds as unit, you could use
+\code{@s(Prefix.nano)} as a unit type.
+You can sometimes use a different annotation instead of a prefix;
+for example, \<@mm> is equivalent to \<@m(Prefix.milli)>.
+
+Class \code{UnitsTools} contains a constant for each SI unit.
+To create a value of the particular unit, multiply an unqualified
+value with one of these constants.
+By using static imports, this allows very natural notation; for
+example, after statically importing \code{UnitsTools.m},
+the expression \code{5 * m} represents five meters.
+As all these unit constants are public, static, and final with value
+one, the compiler will optimize away these multiplications.
+To use the \refclass{checker/units/util}{UnitsTools} class, the
+\<checker-util.jar> file must be on the classpath at run time.
+
+The polymorphic annotation \refqualclass{checker/units/qual}{PolyUnit}
+enables you to write a method that takes an argument of any unit type and
+returns a result of that same type.  For more about polymorphic qualifiers,
+see Section~\ref{method-qualifier-polymorphism}.  For an example of its use, see
+the
+\href{../api/org/checkerframework/checker/units/qual/PolyUnit.html}{\<@PolyUnit>
+Javadoc}.
+
+
+\sectionAndLabel{Extending the Units Checker}{extending-units}
+
+You can create new kind annotations and unit annotations that are specific
+to the particular needs of your project.  An easy way to do this is by
+copying and adapting an existing annotation.  (In addition, search for all
+uses of the annotation's name throughout the Units Checker implementation,
+to find other code to adapt; read on for details.)
+
+Here is an example of a new unit annotation.
+
+\begin{alltt}
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(\{ElementType.TYPE_USE, ElementType.TYPE_PARAMETER\})
+@SubtypeOf(\ttlcb{}Time.class\ttrcb{})
+@UnitsMultiple(quantity=s.class, prefix=Prefix.nano)
+public @interface ns \ttlcb{}\ttrcb{}
+\end{alltt}
+
+The \code{@SubtypeOf} meta-annotation specifies that this annotation
+introduces an additional unit of time.
+The \code{@UnitsMultiple} meta-annotation specifies that this annotation
+should be a nano multiple of the basic unit \code{@s}:  \code{@ns} and
+\code{@s(Prefix.nano)}
+behave equivalently and interchangeably.
+Most annotation definitions do not have a \<@UnitsMultiple> meta-annotation.
+
+Note that all custom annotations must have the
+\<@Target(ElementType.TYPE\_USE)> meta-annotation. See section
+\ref{creating-define-type-qualifiers}.
+
+To take full advantage of the additional unit qualifier, you need to
+do two additional steps.
+(1)~Provide constants that convert from unqualified types to types that use
+the new unit.
+See class \code{UnitsTools} for examples (you will need to suppress a
+checker warning in just those few locations).
+(2)~Put the new unit in relation to existing units.
+Provide an
+implementation of the \code{UnitsRelations} interface as a
+meta-annotation to one of the units.
+
+See demonstration \code{docs/examples/units-extension/} for an example
+extension that defines Hertz (hz) as scalar per second, and defines an
+implementation of \code{UnitsRelations} to enforce it.
+
+
+
+\sectionAndLabel{What the Units Checker checks}{units-checks}
+
+The Units Checker ensures that unrelated types are not mixed.
+
+All types with a particular unit annotation are
+disjoint from all unannotated types, from all types with a different unit
+annotation, and from all types with the same unit annotation but a
+different prefix.
+
+Subtyping between the units and the unit kinds is taken into account,
+as is the \code{@UnitsMultiple} meta-annotation.
+
+Multiplying a scalar with a unit type results in the same unit type.
+
+The division of a unit type by the same unit type
+results in the unqualified type.
+
+Multiplying or dividing different unit types, for which no unit
+relation is known to the system, will result in a \code{MixedUnits}
+type, which is separate from all other units.
+If you encounter a \code{MixedUnits} annotation in an error message,
+ensure that your operations are performed on correct units or refine
+your \code{UnitsRelations} implementation.
+
+The Units Checker does \emph{not} change units based on multiplication; for
+example, if variable \<mass> has the type \<@kg double>, then \<mass *
+1000> has that same type rather than the type \<@g double>.  (The Units
+Checker has no way of knowing whether you intended a conversion, or you
+were computing the mass of 1000 items.  You need to make all conversions
+explicit in your code, and it's good style to minimize the number of
+conversions.)
+
+
+\sectionAndLabel{Running the Units Checker}{units-running}
+
+The Units Checker can be invoked by running the following commands.
+
+\begin{itemize}
+\item
+If your code uses only the SI units that are provided by the
+framework, simply invoke the checker:
+
+\begin{Verbatim}
+  javac -processor org.checkerframework.checker.units.UnitsChecker MyFile.java ...
+\end{Verbatim}
+
+\item
+If you define your own units, provide the fully-qualified class names of the
+annotations through the \code{-Aunits} option, using a comma-no-space-separated
+notation:
+
+\begin{alltt}
+  javac -classpath \textit{/full/path/to/myProject/bin}:\textit{/full/path/to/myLibrary/bin} \ttbs
+        -processor org.checkerframework.checker.units.UnitsChecker \ttbs
+        -Aunits=\textit{myPackage.qual.MyUnit},\textit{myPackage.qual.MyOtherUnit} MyFile.java ...
+\end{alltt}
+
+The annotations listed in \code{-Aunits} must be accessible to
+the compiler during compilation.  Before you run the Units Checker with
+\code{javac}, they must be compiled and on the same path (the classpath or
+processorpath) as the Checker Framework.  It
+is not sufficient to supply their source files on the command line.
+
+\item
+You can also provide the fully-qualified paths to a set of directories
+that contain units qualifiers through the \code{-AunitsDirs} option,
+using a colon-no-space-separated notation. For example,
+if the Checker Framework is on the classpath rather than the processorpath:
+
+\begin{alltt}
+  javac -classpath \textit{/full/path/to/myProject/bin}:\textit{/full/path/to/myLibrary/bin} \ttbs
+        -processor org.checkerframework.checker.units.UnitsChecker \ttbs
+        -AunitsDirs=\textit{/full/path/to/myProject/bin}:\textit{/full/path/to/myLibrary/bin} MyFile.java ...
+\end{alltt}
+
+Note that in these two examples, the compiled class file of the
+\<myPackage.qual.MyUnit> and \<myPackage.qual.MyOtherUnit> annotations
+must exist in either the \<myProject/bin> directory or the
+\<myLibrary/bin> directory. The following placement of the class files
+will work with the above commands:
+
+\begin{alltt}
+  .../myProject/bin/myPackage/qual/MyUnit.class
+  .../myProject/bin/myPackage/qual/MyOtherUnit.class
+\end{alltt}
+
+The two options can be used at the same time to provide groups of annotations
+from directories, and individually named annotations.
+
+\end{itemize}
+
+Also, see the example project in the \<docs/examples/units-extension> directory.
+
+
+
+\sectionAndLabel{Suppressing warnings}{units-suppressing}
+
+One example of when you need to suppress warnings is when you
+initialize a variable with a unit type by a literal value.
+To remove this warning message, it is best to introduce a
+constant that represents the unit and to
+add a \code{@SuppressWarnings}
+annotation to that constant.
+For examples, see class \code{UnitsTools}.
+
+
+\sectionAndLabel{References}{units-references}
+
+\begin{itemize}
+\item The GNU Units tool provides a comprehensive list of units:\\
+  \url{http://www.gnu.org/software/units/}
+
+\item The F\# units of measurement system inspired some of our syntax:\\
+  \url{https://en.wikibooks.org/wiki/F_Sharp_Programming/Units_of_Measure}
+
+\end{itemize}
+
+% LocalWords:  UnitsTools toMeter toSecond mPERs Candela cd kmPERh mol nano ns
+% LocalWords:  milli RetentionPolicy SubtypeOf UnitsMultiple hz PolyUnit
+% LocalWords:  UnitsRelations Aunits MyFile mm2 m2 km2 enum ElementType
+%  LocalWords:  MixedUnits java mPERs2 api classpath bootclasspath RUNTIME
+%%  LocalWords:  AunitsDirs myProject myLibrary Luminance processorpath
diff --git a/docs/manual/warnings.tex b/docs/manual/warnings.tex
new file mode 100644
index 0000000..eb2f231
--- /dev/null
+++ b/docs/manual/warnings.tex
@@ -0,0 +1,762 @@
+\htmlhr
+\chapterAndLabel{Suppressing warnings}{suppressing-warnings}
+
+%% This feels redundant.
+% The Checker Framework is sound:  whenever your code contains an error, the
+% Checker Framework will warn you about the error.  The Checker Framework is
+% conservative:  it may issue warnings when your code is safe and never
+% misbehaves at run time.
+
+When the Checker Framework reports a warning, it's best to fix the
+underlying problem, by changing the code or its annotations.  For each
+warning, follow the methodology in Section~\ref{handling-warnings} to
+correct the underlying problem.
+
+This section describes what to do if the methodology of
+Section~\ref{handling-warnings} indicates that you need to suppress the
+warning.  You won't change your code, but you will prevent the Checker
+Framework from reporting this particular warning to you.  (Changing the
+code to fix a bug is another way to prevent the Checker Framework from
+issuing a warning, but it is not what this chapter is about.)
+
+You may wish to suppress checker warnings because of unannotated libraries
+or un-annotated portions of your own code, because of application
+invariants that are beyond the capabilities of the type system, because of
+checker limitations, because you are interested in only some of the
+guarantees provided by a checker, or for other reasons.
+Suppressing a warning is similar to writing a cast in a Java
+program:  the programmer knows more about the type than the type system does
+and uses the warning suppression or cast to convey that information to the
+type system.
+
+You can suppress a warning message in a single variable initializer,
+method, or class by using the following mechanisms:
+
+\newcounter{lastsinglesuppression}
+\begin{itemize}
+\item
+  the \code{@SuppressWarnings} annotation
+  (Section~\ref{suppresswarnings-annotation}), or
+\item
+  the \code{@AssumeAssertion} string in an \<assert> message (Section~\ref{assumeassertion}).
+\end{itemize}
+
+You can suppress warnings throughout the codebase by using the following mechanisms:
+
+\begin{itemize}
+\item
+  the \code{-AsuppressWarnings} command-line option (Section~\ref{suppresswarnings-command-line}),
+\item
+  the \code{-AskipUses} and \code{-AonlyUses} command-line options (Section~\ref{askipuses}),
+\item
+  the \code{-AskipDefs} and \code{-AonlyDefs} command-line options (Section~\ref{askipdefs}),
+\item
+  the \code{-AuseConservativeDefaultsForUncheckedCode=source} command-line
+  option (Section~\ref{compiling-libraries}),
+\item
+  the \code{-Alint} command-line option enables/disables optional checks (Section~\ref{alint}),
+\item
+  changing the specification of a method (Section~\ref{suppressing-warnings-stub}), or
+\item
+  not running the annotation processor
+  (Section~\ref{no-processor}).
+\end{itemize}
+
+Some type checkers can suppress warnings via
+\begin{itemize}
+\item
+  checker-specific mechanisms (Section~\ref{checker-specific-suppression}).
+\end{itemize}
+
+\noindent
+The rest of this chapter explains these mechanisms in turn.
+
+You can use the \code{-AwarnUnneededSuppressions} command-line option to issue a
+warning if a \code{@SuppressWarnings} did not suppress any warnings issued by the current checker.
+
+
+\sectionAndLabel{\code{@SuppressWarnings} annotation}{suppresswarnings-annotation}
+
+\begin{sloppypar}
+You can suppress specific errors and warnings by use of the
+\code{@SuppressWarnings} annotation, for example
+\code{@SuppressWarnings("interning")} or \code{@SuppressWarnings("nullness")}.
+Section~\ref{suppresswarnings-annotation-syntax} explains the syntax of the
+argument string.
+\end{sloppypar}
+
+A \sunjavadocanno{java.base/java/lang/SuppressWarnings.html}{SuppressWarnings}
+annotation may be placed on program declarations such as a local
+variable declaration, a method, or a class.  It suppresses all warnings
+within that program element.
+Section~\ref{suppresswarnings-annotation-locations} discusses where the
+annotation may be written in source code.
+
+Section~\ref{suppresswarnings-best-practices} gives best practices for
+writing \<@SuppressWarnings> annotations.
+
+
+\subsectionAndLabel{\code{@SuppressWarnings} syntax}{suppresswarnings-annotation-syntax}
+
+The \code{@SuppressWarnings} annotation takes a string argument, in one of
+the following forms:
+\code{"\emph{checkername}:\emph{messagekey}"},
+\code{"\emph{checkername}"},
+or
+\code{"\emph{messagekey}"}.
+
+The argument \emph{checkername} is the checker name, without ``Checker''.
+It is lower case by default, though a checker can choose a different casing.
+For
+example, if you invoke a checker as
+\code{javac -processor MyNiftyChecker ...},
+then you would suppress its error messages with
+\code{@SuppressWarnings("mynifty")}.  (An exception is the Subtyping
+Checker, for which you use the annotation name; see
+Section~\ref{subtyping-using}.)
+Sometimes, a checker honors multiple \emph{checkername} arguments; use
+the \code{-AshowSuppressWarningsStrings} command-line option to see them.
+
+The argument \emph{messagekey} is the message key for the error.
+Each warning message from the compiler gives the most specific
+suppression string that can be used to suppress that warning.
+An example is ``\<dereference.of.nullable>'' in
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+MyFile.java:107: error: [dereference.of.nullable] dereference of possibly-null reference myList
+          myList.add(elt);
+          ^
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+\noindent
+You are allowed to use any substring of a message key, so long as the
+substring extends at each end to a period or an end of the key.  For
+example, to suppress a warning with message key
+\code{"assignment"}, you could use
+\<@SuppressWarnings("assignment")>,
+\<@SuppressWarnings("assignment.type")>,
+\<@SuppressWarnings("type.incompatible")>, or other variants.
+We recommend using
+the longest possible message key; a short message might suppress more
+warnings than you expect.
+
+The checkername \<"allcheckers"> means all checkers.  Using this is not
+recommended, except for messages common to all checkers such as
+purity-related messages when using \<-AcheckPurityAnnotations>.  If you use
+\<"allcheckers">, you run some checker that does not issue any warnings,
+and you suply the \<-AwarnUnneededSuppressions> command-line argument, then
+the Checker Framework will issue an unsuppressable \<unneeded.suppression>
+warning.
+
+The special messagekey ``\<all>'' means to suppress all warnings.
+
+If the \emph{checkername} part is omitted, the \<@SuppressWarnings> applies
+to all checkers.
+If the \emph{messagekey} part is omitted, the \<@SuppressWarnings> applies
+to all messages (it suppresses all warnings from the given checker).
+
+With the \code{-ArequirePrefixInWarningSuppressions} command-line
+option, the Checker Framework only suppresses warnings when the string is in
+the \code{"\emph{checkername}"} or \code{"\emph{checkername}:\emph{messagekey}"}
+format, as in
+\<@SuppressWarnings("nullness")> or
+\<@SuppressWarnings("nullness:assignment")>.
+For example, \<@SuppressWarnings("assignment")> and
+\<@SuppressWarnings("all")> have no effect (they are ignored) when
+\code{-ArequirePrefixInWarningSuppressions} is used.  You can use
+\<@SuppressWarnings("allcheckers")> to suppress all Checker Framework
+warnings.
+
+%% This is true, but relevant mostly to developers, not users.
+% For a list of all message keys for a given checker, see two files:
+% \begin{enumerate}
+% \item \code{checker-framework/checker/src/org/checkerframework/checker/\emph{checkername}/messages.properties}
+% \item \code{checker-framework/framework/src/org/checkerframework/common/basetype/messages.properties}
+% \end{enumerate}
+%
+% \noindent
+% You need to check the latter file because
+% each checker is built on the \code{basetype} checker and inherits its
+% properties.
+
+
+\subsectionAndLabel{Where \code{@SuppressWarnings} can be written}{suppresswarnings-annotation-locations}
+
+\<@SuppressWarnings> is a declaration annotation, so it may be placed on
+program declarations such as a local variable declaration, a method, or a
+class.  It cannot be used on statements, expressions, or types.
+(\<assert> plus \<@AssumeAssertion> can be used between statements and can
+affect arbitrary expressions; see Section~\ref{assumeassertion}.)
+
+Always write a \<@SuppressWarnings> annotation on the smallest possible
+scope.  To reduce the scope of a \<@SuppressWarnings> annotation, it is
+sometimes desirable to refactor the code.  You might extract an expression
+into a local variable, so that warnings can be suppressed just for that
+local variable's initializer expression.  Likewise, you might extract some
+code into a separate method, so that warnings can be suppressed just for
+its body.  Or, you can use \<@AssumeAssertion> on an \<assert> statement;
+see Section~\ref{assumeassertion}.
+
+As an example, consider suppressing a warning at an assignment that you know is
+safe.  Here is an example that uses the Tainting Checker
+(Section~\ref{tainting-checker}).  Assume that \<expr> has compile-time
+(declared) type \<@Tainted String>, but you know that the run-time value of
+\<expr> is untainted.
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  @SuppressWarnings("tainting:cast.unsafe") // expr is untainted because ... [explanation goes here]
+  @Untainted String myvar = expr;
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+\noindent
+Java does not permit annotations (such as \<@SuppressWarnings>) on
+assignments (or on other statements or expressions), so
+it would have been \emph{illegal} to write
+
+%BEGIN LATEX
+\begin{smaller}
+%END LATEX
+\begin{Verbatim}
+  @Untainted String myvar;
+  ...
+  @SuppressWarnings("tainting:cast.unsafe") // expr is untainted because ...
+  myvar = expr;
+\end{Verbatim}
+%BEGIN LATEX
+\end{smaller}
+%END LATEX
+
+
+\subsectionAndLabel{Good practices when suppressing warnings}{suppresswarnings-best-practices}
+
+\subsubsectionAndLabel{Suppress warnings in the smallest possible scope}{suppresswarnings-best-practices-smallest-scope}
+
+Prefer \<@SuppressWarnings> on a local variable declaration to one on a method, and
+prefer one on a method to one on a class.
+\<@SuppressWarnings> on a local variable declaration applies only to the
+declaration (including its initializer if any), not to all uses of the variable.
+
+You may be able to suppress a warning about a use of an expression by
+writing \<@AssumeAssertion> for the expression, before the use.  See
+Section~\ref{assumeassertion}.
+
+Another way to reduce the scope of a \<@SuppressWarnings> is to
+extract the expression into a new local variable
+and place a \code{@SuppressWarnings} annotation on the variable
+declaration.  See Section~\ref{suppresswarnings-annotation-locations}.
+
+%% I'm not sure how this is related to the smallest possible scope.
+% As another example, if you have annotated the signatures but not the bodies
+% of the methods in a class or package, put a \code{@SuppressWarnings}
+% annotation on the class declaration or on the package's
+% \code{package-info.java} file.
+
+
+\subsubsectionAndLabel{Use a specific argument to \code{@SuppressWarnings}}{suppresswarnings-best-practices-specific-argument}
+
+
+\label{compiler-message-keys}
+
+It is best to use the most specific possible message key to suppress just a
+specific error that you know to be a false positive.  The checker outputs
+this message key when it issues an error.  If you use a broader
+\<@SuppressWarnings> annotation, then it may mask other errors that you
+needed to know about.
+
+Any of the following would have suppressed the warning in
+Section~\ref{suppresswarnings-annotation-locations}:
+
+\begin{Verbatim}
+  @SuppressWarnings("tainting")              // suppresses all tainting-related warnings
+  @SuppressWarnings("cast")                  // suppresses warnings from all checkers about casts
+  @SuppressWarnings("unsafe")                // suppresses warnings from all checkers about unsafe code
+  @SuppressWarnings("cast.unsafe")           // suppresses warnings from all checkers about unsafe casts
+  @SuppressWarnings("tainting:cast")         // suppresses tainting warnings about casts
+  @SuppressWarnings("tainting:unsafe")       // suppresses tainting warnings about unsafe code
+  @SuppressWarnings("tainting:cast.unsafe")  // suppresses tainting warnings about unsafe casts
+\end{Verbatim}
+
+The last one is the most specific, and therefore is the best style.
+
+
+\subsubsectionAndLabel{Justify why the warning is a false positive}{suppresswarnings-best-practices-justification}
+
+A \<@SuppressWarnings> annotation asserts that the programmer knows that
+the code is actually correct or safe (that is, no undesired behavior will
+occur), even though the type system is unable to prove that the code is
+correct or safe.
+
+Whenever you write a \<@SuppressWarnings> annotation, you should also
+write, typically on the same line, a code comment
+explaining why the code is actually correct.  In some cases you might also
+justify why the code cannot be rewritten in a simpler way that would be
+amenable to type-checking.  Also make it clear what error is being
+suppressed.  (This is particularly important when the \<@SuppressWarnings> is
+on a method declaration and the suppressed warning might be anywhere in the
+method body.)
+
+This documentation will help you and others to understand the reason for
+the \<@SuppressWarnings> annotation.  It will also help you audit your code
+to verify all the warning suppressions.  (The code is correct only if the
+checker issues no warnings \emph{and} each \<@SuppressWarnings> is correct.)
+
+A suppression message like ``a.f is not null'' is not useful.  The fact
+that you are suppressing the warning means that you believe that \<a.f> is
+not null.  The message should explain \emph{why} you believe that; for
+example, ``a.f was checked above and no subsequent side effect can affect it''.
+
+Here are some terse examples from libraries in \href{https://github.com/plume-lib/}{plume-lib}:
+
+\begin{Verbatim}
+@SuppressWarnings("cast") // cast is redundant (except when checking nullness)
+@SuppressWarnings("interning") // FbType.FREE is interned but is not annotated
+@SuppressWarnings("interning") // equality testing optimization
+@SuppressWarnings("nullness") // used portion of array is non-null
+@SuppressWarnings("nullness") // oi.factory is a static method, so null first argument is OK
+@SuppressWarnings("purity") // side effect to local state of type BitSet
+\end{Verbatim}
+
+A particularly good (and concise) justification is to reference an issue in
+the issue tracker, as in these two from \href{https://plse.cs.washington.edu/daikon/}{Daikon}:
+
+\begin{Verbatim}
+@SuppressWarnings("flowexpr.parse.error") // https://tinyurl.com/cfissue/862
+@SuppressWarnings("keyfor") // https://tinyurl.com/cfissue/877
+\end{Verbatim}
+
+\noindent
+Please report false positive warnings, then reference them in your warning suppressions.
+This permits the Checker Framework maintainers to know about the
+problem, it helps them with prioritization (by knowing how often in your
+codebase a particular issue arises), and it enables you to know when an
+issue has been fixed (though the \<-AwarnUnneededSuppressions> command-line
+option also serves the latter purpose).
+
+
+\sectionAndLabel{\code{@AssumeAssertion} string in an \<assert> message}{assumeassertion}
+
+\begin{sloppypar}
+Sometimes, it is too disruptive to refactor your code to create a location
+where \<@SuppressWarnings> can be written.  You can instead suppress a
+warning by writing an assertion whose message contains the string
+\<@AssumeAssertion(\emph{checkername})>.
+\end{sloppypar}
+
+For example, in this code:
+
+\begin{Verbatim}
+while (c != Object.class) {
+  ...
+  c = c.getSuperclass();
+  assert c != null
+    : "@AssumeAssertion(nullness): c was not Object, so its superclass is not null";
+ }
+\end{Verbatim}
+
+\noindent
+the Nullness Checker assumes that \<c> is non-null from the \<assert>
+statement forward (including on the next iteration through the loop).
+
+The \<assert> expression must be an expression that would affect flow-sensitive
+type refinement (Section~\ref{type-refinement}), if the
+expression appeared in a conditional test.  Each type system has its own
+rules about what type refinement it performs.
+
+The value in parentheses is a checker name (typically lowercase),
+exactly as in the \<@SuppressWarnings> annotation
+(Section~\ref{suppresswarnings-annotation-syntax}).
+Any subcheckers will also assume that the
+assertion is true (e.g., the Map Key Checker will assume that
+the assertion in the example above cannot fail, when it runs
+as a subchecker of the Nullness Checker).
+
+The same good practices apply
+as for \<@SuppressWarnings> annotations, such as writing a comment
+justifying why the assumption is safe
+(Section~\ref{suppresswarnings-best-practices}).
+
+The \<-AassumeAssertionsAreEnabled> and \<-AassumeAssertionsAreDisabled>
+command-line options (Section~\ref{type-refinement-assertions}) do not
+affect processing of \<assert> statements that have \<@AssumeAssertion> in
+their message.  Writing \<@AssumeAssertion> means that the assertion would
+succeed if it were executed, and the Checker Framework makes use of that
+information regardless of the \<-AassumeAssertionsAreEnabled> and
+\<-AassumeAssertionsAreDisabled> command-line options.
+
+
+%% Redundant.
+% If the string \<@AssumeAssertion(\emph{checkername})> does not appear in the
+% assertion message, then the Checker Framework treats the assertion as
+% being used for defensive programming.  That is, the programmer believes
+% that the assertion might fail at run time, so the Checker Framework should
+% not make any inference, which would not be justified.
+
+%% Users should never see assertions anyway -- they are for programmers.
+% A downside of putting the string in the assertion message is that if the
+% assertion ever fails, then a user might see the string and be confused.
+% This should never be a problem, since
+% the programmer should write the string should only if the programmer has
+% reasoned that the
+% assertion can never fail.
+
+% (Another way of stating the Nullness Checker's use of assertions is as an
+% additional caveat to the guarantees provided by a checker
+% (Section~\ref{checker-guarantees}).  The Nullness Checker prevents null
+% pointer errors in your code under the assumption that assertions are
+% enabled, and it does not guarantee that all of your assertions succeed.)
+
+
+\subsectionAndLabel{Suppressing warnings and defensive programming}{defensive-programming}
+
+This section explains the distinction between two different uses for
+assertions:  debugging a program (also known as defensive programming)
+versus specifying a program.  The examples use nullness annotations, but
+the concepts apply to any checker.
+
+The purpose of assertions is to aid debugging by throwing an exception
+when a program does not work correctly.  Sometimes, programmers use assertions for a
+different  purpose:  documenting how
+the program works.  By default, the Checker Framework assumes that each assertion
+is used for its primary purpose of debugging:  the assertion might fail at run time, and the programmer
+wishes to be informed at compile time about such possible run-time errors.
+
+% Here is an example of an assertion used for debugging.
+Suppose that a
+programmer encounters a failing test, adds an assertion to aid debugging, and fixes the
+test.  The programmer leaves the assertion in the program if the programmer
+is worried that the program might fail in a similar way in the future.
+% The assertion indicates the potential for failure at this point in the code.
+The Checker Framework should not assume that the assertion succeeds ---
+doing so would defeat the very purpose of the Checker Framework, which is
+to detect errors at compile time and prevent them from occurring at run
+time.
+
+A non-standard use for annotations is to document facts that a programmer
+has independently verified to be true.  The Checker Framework can
+leverage these assertions in order to avoid issuing false positive
+warnings.  The programmer marks such assertions with the \<@AssumeAssertion>
+string in the \<assert> message (see Section~\ref{assumeassertion}.  Only
+do so if you are sure that the assertion always succeeds at run time.
+
+\label{assertion-methods}
+Methods such as
+\sunjavadoc{java.base/java/util/Objects.html\#requireNonNull(T)}{Objects.requireNonNull},
+JUnit's \<Assert.assertNotNull>, and
+Guava's \<verifyNotNull> and \<checkNotNull> are
+similar to assertions.  Just as for assertions, their intended use is as
+debugging aids, they might fail at run time, and the Checker Framework
+warns if that might happen.
+Some programmers may use assert methods as documentation of facts
+that the programmer has verified in some other manner.
+If you know that a particular codebase always uses
+an assertion method not for defensive programming but to indicate
+facts that are guaranteed to be true (that is, these assertions cannot
+fail at run time), then there are two approaches to avoid false positive
+warnings:  write specifications or suppress
+warnings; see below for an explanation of each approach.
+
+The method
+\refmethod{checker/nullness/util}{NullnessUtil}{castNonNull}{-T-} is not an
+assertion method.  It is a warning suppression method.
+
+Note that some libraries have an imprecise specification of their assertion
+methods.  For example, Guava's \<Verify.verifyNotNull> is imprecisely
+specified to have a \<@Nullable> formal parameter.  In a correct execution,
+\<null> never flows there, so its type can and should be annotated as
+\<@NonNull>.  That annotation allows the Nullness Checker to warn about
+programs that crash due to passing \<null> to \<verifyNotNull>.  You can
+use a version of Guava with a small change to your build file.  Where the
+build file refers to Maven Central's \<guava> artifact, change the group
+name from ``com.google.guava'' to ``org.checkerframework.annotatedlib''.
+(The code is identical; the only difference is annotations.)
+
+
+\paragraphAndLabel{Option 1: Write specifications based on uses of assertion methods}{specifications-based-on-assertion-methods}
+Suppose you are annotating a codebase that already contains precondition checks,
+such as:
+
+\begin{Verbatim}
+  public String myGet(String key, String def) {
+    checkNotNull(key, "key"); // NOI18N
+    ...
+  }
+\end{Verbatim}
+
+\noindent
+Because \<key> is non-null in every correct execution, its type should be
+\<@NonNull> in \<myGet>'s signature.  (\<@NonNull> is the default, so in
+this case there is nothing to write.)  The checker will not issue a warning
+about the \<checkNotNull> call, but will issue a warning at incorrect calls
+to \<myGet>.
+
+\paragraphAndLabel{Option 2: Suppress warnings at uses of assertion methods}{assert-method-suppress-warnings}
+
+This section explains how to suppress warnings at all uses of an assertion
+method.  As with any warning suppression, you will compromise the checker's
+guarantee that your code is correct and will not fail at run time.
+
+\begin{itemize}
+\item
+  If the method is defined in your source code, annotate its definition just as
+  \refmethod{checker/nullness/util}{NullnessUtil}{castNonNull}{-T-} is
+  annotated; see its Javadoc or the source code for the Checker Framework.
+  Also, be sure to document the intention in the method's Javadoc, so that
+  programmers do not
+  accidentally misuse it for defensive programming.
+\item
+  If the method is defined in an external library, write a stub file that
+  changes the method's annotations, or use \<-AskipUses> to make the
+  Checker Framework ignore all calls to an entire class.
+\end{itemize}
+
+
+\sectionAndLabel{\code{-AsuppressWarnings} command-line option}{suppresswarnings-command-line}
+
+Supplying the \<-AsuppressWarnings> command-line option is equivalent to
+writing a \<@SuppressWarnings> annotation on every class that the compiler
+type-checks.  The argument to \<-AsuppressWarnings> is a comma-separated
+list of warning suppression strings, as in
+\<-AsuppressWarnings=purity,uninitialized>.
+
+When possible, it is better to write a \<@SuppressWarnings> annotation with a
+smaller scope, rather than using the \<-AsuppressWarnings> command-line option.
+
+
+\sectionAndLabel{\code{-AskipUses} and \code{-AonlyUses} command-line options}{askipuses}
+
+You can suppress all errors and warnings at all \emph{uses} of a given
+class, or suppress all errors and warnings except those at uses of a given
+class.  (The class itself is still type-checked, unless you also use
+the \code{-AskipDefs} or \code{-AonlyDefs} command-line option, see~\ref{askipdefs}).
+You can also use these options to affect entire packages or directories/folders.
+
+Set the \code{-AskipUses} command-line option to a
+regular expression that matches class names (not file names) for which warnings and errors
+should be suppressed.
+Or, set the \code{-AonlyUses} command-line option to a
+regular expression that matches class names (not file names) for which warnings and errors
+should be emitted; warnings about uses of all other classes will be suppressed.
+
+For example, suppose that you use
+``{\codesize\verb|-AskipUses=^java\.|}'' on the command line
+(with appropriate quoting) when invoking
+\code{javac}.  Then the checkers will suppress all warnings related to
+classes whose fully-qualified name starts with \codesize\verb|java.|, such
+as all warnings relating to invalid arguments and all warnings relating to
+incorrect use of the return value.
+
+To suppress all errors and warnings related to multiple classes, you can use
+the regular expression alternative operator ``\code{|}'', as in
+``{\codesize\verb+-AskipUses="java\.lang\.|java\.util\."+}'' to suppress
+all warnings related to uses of classes that belong to the \code{java.lang} or
+\code{java.util} packages.  (Depending on your shell or other tool, you
+might need to change or remove the quoting.)
+
+You can supply both \code{-AskipUses} and \code{-AonlyUses}, in which case
+the \code{-AskipUses} argument takes precedence, and \code{-AonlyUses} does
+further filtering but does not add anything that \code{-AskipUses} removed.
+
+Warning:  Use the \code{-AonlyUses} command-line option with care,
+because it can have unexpected results.  For example, if the
+given regular expression does not match classes in the JDK, then the
+Checker Framework will suppress every warning that involves a JDK class
+such as \<Object> or \<String>.  The meaning of \code{-AonlyUses} may be
+refined in the future.  Oftentimes \code{-AskipUses} is more useful.
+
+% The desired meaning of -AonlyUses is tricky, because what is a "use"?
+% Maybe only check calls of methods on the class (though don't check
+% argument expressions) and field accesses, but nothing else (such as
+% extends clauses that happen to use the class).  But then we would also
+% want to suppress warnings related to assignments where a method use or
+% field access is the right-hand side.  I'm going to punt on this for now.
+
+
+\sectionAndLabel{\code{-AskipDefs} and \code{-AonlyDefs} command-line options}{askipdefs}
+
+You can suppress all errors and warnings in the \emph{definition} of a given
+class, or suppress all errors and warnings except those in the definition
+of a given class.  (Uses of the class are still type-checked, unless you also use
+the \code{-AskipUses} or \code{-AonlyUses} command-line option,
+see~\ref{askipuses}.)
+You can also use these options to affect entire packages or directories/folders.
+
+Set the \code{-AskipDefs} command-line option to a
+regular expression that matches class names (not file names) in whose definition warnings and errors
+should be suppressed.
+Or, set the \code{-AonlyDefs} command-line option to a
+regular expression that matches class names (not file names) whose
+definitions should be type-checked.
+(This is somewhat similar to NullAway's
+\<-XepOpt:NullAway:AnnotatedPackages> command-line argument.)
+
+For example, if you use
+``{\codesize\verb|-AskipDefs=^mypackage\.|}'' on the command line
+(with appropriate quoting) when invoking
+\code{javac}, then the definitions of
+classes whose fully-qualified name starts with \codesize\verb|mypackage.|
+will not be checked.
+
+If you supply both \code{-AskipDefs} and \code{-AonlyDefs}, then
+\code{-AskipDefs} takes precedence.
+
+Another way not to type-check a file is not to pass it on the compiler
+command-line:  the Checker Framework type-checks only files that are passed
+to the compiler on the command line, and does not type-check any file that
+is not passed to the compiler.  The \code{-AskipDefs} and \code{-AonlyDefs}
+command-line options
+are intended for situations in which the build system is hard to understand
+or change.  In such a situation, a programmer may find it easier to supply
+an extra command-line argument, than to change the set of files that is
+compiled.
+
+A common scenario for using the arguments is when you are starting out by
+type-checking only part of a legacy codebase.  After you have verified the
+most important parts, you can incrementally check more classes until you
+are type-checking the whole thing.
+
+
+\sectionAndLabel{\code{-Alint} command-line option\label{lint-options}}{alint}
+
+The \code{-Alint} option enables or disables optional checks, analogously to
+javac's \code{-Xlint} option.
+Each of the distributed checkers supports at least the following lint
+options (and possibly more, see the checker's documentation):
+
+% For the current list of lint options supported by all checkers, see
+% method BaseTypeChecker.getSupportedLintOptions().
+
+% For the per-checker list, search for "@SupportedLintOptions" in the
+% checker implementations.
+
+
+\begin{itemize}
+
+\item
+  \code{cast:unsafe} (default: on) warn about unsafe casts that are not
+  checked at run time, as in \code{((@NonNull String) myref)}.  Such casts
+  are generally not necessary because of type refinement
+  (Section~\ref{type-refinement}).
+
+\item
+  \code{cast:redundant} (default: on) warn about redundant
+  casts that are guaranteed to succeed at run time,
+  as in \code{((@NonNull String) "m")}.  Such casts are not necessary,
+  because the target expression of the cast already has the given type
+  qualifier.
+
+\item
+  \code{cast} Enable or disable all cast-related warnings.
+
+\item
+\begin{sloppypar}
+  \code{all} Enable or disable all lint warnings, including
+  checker-specific ones if any.  Examples include \code{redundantNullComparison} for the
+  Nullness Checker (see Section~\ref{nullness-lint-nulltest}) and \<dotequals> for
+  the Interning Checker (see Section~\ref{lint-dotequals}).  This option
+  does not enable/disable the checker's standard checks, just its optional
+  ones.
+\end{sloppypar}
+
+\item
+  \code{none} The inverse of \<all>:  disable or enable all lint warnings,
+  including checker-specific ones if any.
+
+\end{itemize}
+
+% This syntax is different from -Xlint that uses a colon instead of an
+% equals sign, because javac forces the use of the equals sign.
+
+\noindent
+To activate a lint option, write \code{-Alint=} followed by a
+comma-delimited list of check names.  If the option is preceded by a
+hyphen (\code{-}), the warning is disabled.  For example, to disable all
+lint options except redundant casts, you can pass
+\code{-Alint=-all,cast:redundant} on the command line.
+
+Only the last \code{-Alint} option is used; all previous \code{-Alint}
+options are silently ignored.  In particular, this means that \<-Alint=all
+-Alint=cast:redundant> is \emph{not} equivalent to
+\code{-Alint=-all,cast:redundant}.
+
+
+\sectionAndLabel{Change the specification of a method}{suppressing-warnings-stub}
+
+To prevent a checker from issuing a warning at calls to a specific method,
+you can change the annotations on that method by writing a stub file (see
+Section~\ref{stub}).
+
+Stub files are usually used to provide correct specifications for
+unspecified code.
+
+Stub files can also be used to provide \emph{incorrect} specifications, for
+the purpose of suppressing warnings.  For example, suppose that you are
+running the Nullness Checker to prevent null pointer exceptions.  Further
+suppose that for some reason you do not care if method
+\<Objects.requireNonNull> crashes with a \<NullPointerException>.  You can
+supply a stub file containing:
+
+\begin{Verbatim}
+package java.util;
+class Objects {
+  @EnsuresNonNull("#1")
+  public static <T> @NonNull T requireNonNull(@Nullable T obj);
+}
+\end{Verbatim}
+
+
+\sectionAndLabel{Don't run the processor}{no-processor}
+
+You can compile parts of your code without use of the
+\code{-processor} switch to \code{javac}.  No checking is done during
+such compilations, so no warnings are issued related to pluggable
+type-checking.
+
+You can direct your build system to avoid compiling certain parts of your
+code.  For example, the \<-Dmaven.test.skip=true> command-line argument
+tells Maven not to compile (or run) the tests.
+
+
+\sectionAndLabel{Checker-specific mechanisms}{checker-specific-suppression}
+
+Finally, some checkers have special rules.  For example, the Nullness
+checker (Chapter~\ref{nullness-checker}) uses
+the special \<castNonNull> method to suppress warnings
+(Section~\ref{suppressing-warnings-with-assertions}).
+This manual also explains special mechanisms for
+suppressing warnings issued by the Fenum Checker
+(Section~\ref{fenum-suppressing}) and the Units Checker
+(Section~\ref{units-suppressing}).
+
+
+% LocalWords:  quals skipUses un AskipUses Alint annotationname javac's Awarns
+% LocalWords:  Xlint dotequals castNonNull XDTA Formatter jsr subpart
+% LocalWords:  unselect checkbox classpath Djsr bak Nullness nullness java lang
+% LocalWords:  checkername util myref nulltest html ESC buildfile mynifty Fenum
+% LocalWords:  MyNiftyChecker messagekey basetype uncommenting Anomsgtext
+% LocalWords:  AskipDefs mypackage Makefile PLXCOMP expr
+%  LocalWords:  TODO AsuppressWarnings AssumeAssertion AonlyUses AonlyDefs
+%  LocalWords:  ing warningkey redundantNullComparison qual proc Decl
+%  LocalWords:  lastsinglesuppression classfiles AwarnUnneededSuppressions
+%%  LocalWords:  AshowSuppressWarningsStrings NullableType NonNullType JUnit's
+%%  LocalWords:  PolyNullType MonotonicNonNullType KeyForType NullableDecl
+%%  LocalWords:  NonNullDecl PolyNullDecl MonotonicNonNullDecl KeyForDecl
+%%  LocalWords:  refactored AassumeAssertionsAreEnabled assertNotNull
+%%  LocalWords:  AassumeAssertionsAreDisabled NullnessUtil checkNotNull
+%%  LocalWords:  ElementType AuseSafeDefaultsForUnannotatedSourceCode
+% LocalWords:  AuseConservativeDefaultsForUncheckedCode checkerframework askipuses
+%%  LocalWords:  suppresswarnings ArequirePrefixInWarningSuppressions
+%%  LocalWords:  assumeassertion askipdefs alint compat substring myGet
+% LocalWords:  AcheckPurityAnnotations allcheckers requireNonNull
+% LocalWords:  verifyNotNull subcheckers subchecker
diff --git a/docs/tutorial/Makefile b/docs/tutorial/Makefile
new file mode 100644
index 0000000..eb99780
--- /dev/null
+++ b/docs/tutorial/Makefile
@@ -0,0 +1,20 @@
+# Put user-specific changes in your own Makefile.user.
+# Make will silently continue if that file does not exist.
+-include Makefile.user
+
+CHECKER_QUAL_JAR=../../checker/dist/checker-qual.jar
+
+ZIPFILES = sourcefiles.zip
+
+all: ${ZIPFILES}
+
+sourcefiles.zip: clean-classes
+	rm -f src/README.txt
+	cp -pf README-sourcefiles.txt src/README.txt
+	zip -r sourcefiles.zip src/
+
+clean-classes:
+	find . -name '*.class' ! -name '.*' -exec rm {} +
+
+clean: clean-classes
+	@\rm -f ${ZIPFILES}
diff --git a/docs/tutorial/README b/docs/tutorial/README
new file mode 100644
index 0000000..9d8cff7
--- /dev/null
+++ b/docs/tutorial/README
@@ -0,0 +1,16 @@
+Run
+  make
+to create these files:
+  sourcefiles.zip
+
+TODO: explain how the website is created
+
+If you want to present the tutorial when Internet is not available, try this:
+(I have not yet tried an offline tutorial with these materials.)
+
+mkdir cf-tutorial
+cd cf-tutorial
+wget -r -k -np https://checkerframework.org/tutorial/
+wget -r -k -np https://checkerframework.org/manual/
+cd ..
+zip -r cf-tutorial cf-tutorial
diff --git a/docs/tutorial/README-sourcefiles.txt b/docs/tutorial/README-sourcefiles.txt
new file mode 100644
index 0000000..080d211
--- /dev/null
+++ b/docs/tutorial/README-sourcefiles.txt
@@ -0,0 +1,3 @@
+This zipfile contains source code corresponding to the
+Checker Framework tutorial:
+https://checkerframework.org/tutorial/
diff --git a/docs/tutorial/index.html b/docs/tutorial/index.html
new file mode 100644
index 0000000..d9e4814
--- /dev/null
+++ b/docs/tutorial/index.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta name="generator" content=
+  "HTML Tidy for Linux (vers 25 March 2009), see www.w3.org">
+
+  <title>Checker Framework Tutorial</title>
+  <link href="webpages/bootstrap/css/bootstrap.css" rel="stylesheet" type=
+  "text/css">
+  <script type="text/javascript" src=
+  "webpages/bootstrap/js/bootstrap.min.js">
+</script>
+  <link href="webpages/css/main.css" rel="stylesheet" type="text/css">
+  <link rel="icon" type="image/png" href=
+  "https://checkerframework.org/favicon-checkerframework.png">
+</head>
+
+<body>
+  <div class="top_liner"></div>
+
+  <div class="navbar navbar-inverse navbar-fixed-top" style=
+  "border-bottom: 1px solid #66d;">
+    <div class="navbar-inner">
+      <div class="contained">
+        <ul class="nav">
+          <li class="heading">Checker Framework:</li>
+
+          <li><a href="https://checkerframework.org/">Main Site</a></li>
+
+          <li><a href=
+          "https://checkerframework.org/manual/">
+          Manual</a></li>
+
+          <li><a href=
+          "https://groups.google.com/forum/#!forum/checker-framework-discuss">
+          Discussion List</a></li>
+
+          <li><a href=
+          "https://github.com/typetools/checker-framework/issues">Issue
+          Tracker</a></li>
+
+          <li><a href=
+          "https://github.com/typetools/checker-framework">Source
+          Code</a></li>
+
+          <li class="active"><a href=
+          "https://checkerframework.org/tutorial/">Tutorial</a></li>
+        </ul>
+      </div>
+    </div>
+  </div><img src="https://checkerframework.org/CFLogo.png" alt="Checker Framework logo">
+
+  <div class="page-header short" style=
+  "border-bottom: 1px solid #EEE; border-top: none;">
+    <h1>Checker Framework Tutorial</h1>
+  </div>
+
+  <div id="introduction">
+    <div class="page-header short" style="border-top: none;">
+      <h2>Introduction</h2>
+    </div>
+
+    <div class="section">
+      <p>The Checker Framework enhances Java's type system to make it more
+      powerful and useful. This lets software developers detect and
+      prevent errors in their Java programs. The Checker Framework
+      includes compiler plug-ins ("checkers") that find bugs or verify
+      their absence. It also permits software developers to write their
+      own compiler plug-ins.</p>
+
+      <p>In this tutorial, you will learn to use the Checker Framework to
+      prevent null pointer exceptions, to prevent SQL injection attacks,
+      and to improve code quality. In this tutorial, you will learn to use the Checker
+      Framework from the command line.
+      The Checker
+      Framework can also be used with other <a href=
+      "https://checkerframework.org/manual/#external-tools">tools</a>
+      such as Maven or InteliJ IDEA, and you could follow
+      the command-line version of the tutorial using one of those tools.</p>
+    </div>
+  </div>
+
+  <div id="commandlinetutorial">
+    <div class="page-header short">
+      <h2>Using the Checker Framework from the command line</h2>
+    </div>
+
+    <div class="section">
+      <ol>
+        <li><a href=
+        "https://checkerframework.org/manual/#installation">
+        Install the Checker Framework</a> and then return to this page.<br/>
+        As described in the installation instructions,
+        <a href="https://checkerframework.org/manual/#javac-installation">set
+        the alias
+        <code>javacheck</code></a> to the Checker Framework compiler.</li>
+
+        <li><a href="sourcefiles.zip">Download</a> and unzip the source
+        files for the tutorial, then enter the <code>src</code> directory:
+        <code>cd src</code></li>
+
+        <li><a href="webpages/get-started-cmd.html">Getting Started</a>, a
+        simple example use of the Nullness Checker</li>
+
+        <li><a href="webpages/user-input-cmd.html">Validating User
+        Input</a>, an example using the Regex Checker</li>
+
+        <li><a href="webpages/security-error-cmd.html">Finding a Security
+        Error</a>, a complex example using the Tainting Checker</li>
+
+        <li><a href="webpages/encryption-checker-cmd.html">Writing an
+        Encryption Checker</a>, an example of writing your own type
+        checker</li>
+      </ol>
+    </div>
+  </div>
+
+  <div id="resources">
+    <div class="page-header short">
+      <h2>Resources</h2>
+    </div>
+
+    <div class="section">
+      <ul>
+        <li><a href=
+        "https://checkerframework.org/manual/">
+        The Checker Framework Manual</a></li>
+
+        <li><a href="https://checkerframework.org/">The Checker Framework
+        homepage</a></li>
+
+        <li>There is also a
+        A <a href="https://github.com/glts/safer-spring-petclinic/wiki">Nullness
+        Checker tutorial</a> by David B&uuml;rgin, last updated in April 2016.
+        The tutorial doesn't work because the setup instructions are out of
+        date, but some people find it helpful to read through the steps.</li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="bottom_liner"></div>
+  <!--  LocalWords:  Plugin plugin VM SDK plugins quals classpath
+ -->
+  <!--  LocalWords:  NullnessChecker plugin's hg
+ -->
+</body>
+</html>
diff --git a/docs/tutorial/src/NullnessExample.java b/docs/tutorial/src/NullnessExample.java
new file mode 100644
index 0000000..d5a29a6
--- /dev/null
+++ b/docs/tutorial/src/NullnessExample.java
@@ -0,0 +1,10 @@
+public class NullnessExample {
+    public static void main(String[] args) {
+        Object myObject = null;
+
+        if (args.length > 2) {
+            myObject = new Object();
+        }
+        System.out.println(myObject.toString());
+    }
+}
diff --git a/docs/tutorial/src/README.txt b/docs/tutorial/src/README.txt
new file mode 100644
index 0000000..080d211
--- /dev/null
+++ b/docs/tutorial/src/README.txt
@@ -0,0 +1,3 @@
+This zipfile contains source code corresponding to the
+Checker Framework tutorial:
+https://checkerframework.org/tutorial/
diff --git a/docs/tutorial/src/RegexExample.java b/docs/tutorial/src/RegexExample.java
new file mode 100644
index 0000000..72c7a17
--- /dev/null
+++ b/docs/tutorial/src/RegexExample.java
@@ -0,0 +1,22 @@
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Call this program with two arguments; a regular expression and a string. The program prints the
+ * text, from the string, that matches the first capturing group in the regular expression.
+ */
+public class RegexExample {
+    public static void main(String[] args) {
+        String regex = args[0];
+        String content = args[1];
+
+        Pattern pat = Pattern.compile(regex);
+        Matcher mat = pat.matcher(content);
+
+        if (mat.matches()) {
+            System.out.println("Group 1: " + mat.group(1));
+        } else {
+            System.out.println("No match!");
+        }
+    }
+}
diff --git a/docs/tutorial/src/encrypted/EncryptionDemo.java b/docs/tutorial/src/encrypted/EncryptionDemo.java
new file mode 100644
index 0000000..78c6008
--- /dev/null
+++ b/docs/tutorial/src/encrypted/EncryptionDemo.java
@@ -0,0 +1,34 @@
+package encrypted;
+
+import myqual.Encrypted;
+
+public class EncryptionDemo {
+    private final int OFFSET = 13;
+
+    public @Encrypted String encrypt(String text) {
+        @Encrypted String encryptedText = new @Encrypted String();
+        for (char character : text.toCharArray()) {
+            encryptedText += encryptCharacter(character);
+        }
+        return encryptedText;
+    }
+
+    private @Encrypted char encryptCharacter(char character) {
+        @Encrypted int encryptInt = (character + OFFSET) % Character.MAX_VALUE;
+        return (@Encrypted char) encryptInt;
+    }
+
+    // Only send encrypted data!
+    public void sendOverInternet(@Encrypted String msg) {
+        // ...
+    }
+
+    public void sendPassword() {
+        String password = getUserPassword();
+        sendOverInternet(password);
+    }
+
+    private String getUserPassword() {
+        return "!@#$Really Good Password**";
+    }
+}
diff --git a/docs/tutorial/src/myqual/Encrypted.java b/docs/tutorial/src/myqual/Encrypted.java
new file mode 100644
index 0000000..be95359
--- /dev/null
+++ b/docs/tutorial/src/myqual/Encrypted.java
@@ -0,0 +1,12 @@
+package myqual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** Denotes that the representation of an object is encrypted. */
+@Documented
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(PossiblyUnencrypted.class)
+public @interface Encrypted {}
diff --git a/docs/tutorial/src/myqual/PolyEncrypted.java b/docs/tutorial/src/myqual/PolyEncrypted.java
new file mode 100644
index 0000000..5930cff
--- /dev/null
+++ b/docs/tutorial/src/myqual/PolyEncrypted.java
@@ -0,0 +1,12 @@
+package myqual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/** Denotes that the representation of an object is encrypted. */
+@Documented
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier
+public @interface PolyEncrypted {}
diff --git a/docs/tutorial/src/myqual/PossiblyUnencrypted.java b/docs/tutorial/src/myqual/PossiblyUnencrypted.java
new file mode 100644
index 0000000..33f1022
--- /dev/null
+++ b/docs/tutorial/src/myqual/PossiblyUnencrypted.java
@@ -0,0 +1,14 @@
+package myqual;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** Denotes that the representation of an object might not be encrypted. */
+@Documented
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface PossiblyUnencrypted {}
diff --git a/docs/tutorial/src/personalblog-demo/build.xml b/docs/tutorial/src/personalblog-demo/build.xml
new file mode 100644
index 0000000..e8fae4a
--- /dev/null
+++ b/docs/tutorial/src/personalblog-demo/build.xml
@@ -0,0 +1,54 @@
+<project name="personalblog-demo" default="check-tainting">
+
+  <property environment="env"/>
+
+  <property name="checker-framework" value="../../../.."/>
+
+  <condition property="cfJavac" value="javac.bat" else="javac">
+    <os family="windows" />
+  </condition>
+
+
+  <presetdef name="cf.javac">
+    <javac fork="yes" executable="${checker-framework}/checker/bin/${cfJavac}" encoding="UTF-8">
+      <compilerarg value="-version"/>
+      <compilerarg value="-implicit:class"/>
+
+      <classpath>
+        <pathelement location="${checker-framework}/checker/dist/checker.jar"/>
+        <pathelement location="lib/personalblog.jar"/>
+        <pathelement location="lib/hibernate2.jar"/>
+        <pathelement location="lib/commons-logging.jar"/>
+        <pathelement location="lib/struts.jar"/>
+        <pathelement location="lib/servlet-api.jar"/>
+        <pathelement location="lib/commons-lang.jar"/>
+      </classpath>
+    </javac>
+  </presetdef>
+
+  <target name="check-tainting"
+          description="Check for tainting errors as they come up"
+          depends="clean">
+    <mkdir dir="bin"/>
+    <cf.javac fork="true" srcdir="src" destdir="bin" includeantruntime="false">
+      <compilerarg value="-processor"/>
+      <compilerarg value="org.checkerframework.checker.tainting.TaintingChecker"/>
+    </cf.javac>
+  </target>
+
+  <target name="check-tainting-all"
+          description="Check for tainting errors in all files"
+          depends="clean">
+    <mkdir dir="bin"/>
+    <cf.javac fork="true" srcdir="src" destdir="bin" includeantruntime="false">
+      <compilerarg value="-processor"/>
+      <compilerarg value="org.checkerframework.checker.tainting.TaintingChecker"/>
+      <compilerarg value="-Awarns"/>
+    </cf.javac>
+  </target>
+
+  <target name="clean">
+    <delete dir="bin"/>
+  </target>
+
+</project>
diff --git a/docs/tutorial/src/personalblog-demo/lib/checker-qual.jar b/docs/tutorial/src/personalblog-demo/lib/checker-qual.jar
new file mode 100644
index 0000000..439229d
--- /dev/null
+++ b/docs/tutorial/src/personalblog-demo/lib/checker-qual.jar
Binary files differ
diff --git a/docs/tutorial/src/personalblog-demo/lib/commons-lang.jar b/docs/tutorial/src/personalblog-demo/lib/commons-lang.jar
new file mode 100644
index 0000000..37ddb9b
--- /dev/null
+++ b/docs/tutorial/src/personalblog-demo/lib/commons-lang.jar
Binary files differ
diff --git a/docs/tutorial/src/personalblog-demo/lib/commons-logging.jar b/docs/tutorial/src/personalblog-demo/lib/commons-logging.jar
new file mode 100644
index 0000000..b99c937
--- /dev/null
+++ b/docs/tutorial/src/personalblog-demo/lib/commons-logging.jar
Binary files differ
diff --git a/docs/tutorial/src/personalblog-demo/lib/hibernate2.jar b/docs/tutorial/src/personalblog-demo/lib/hibernate2.jar
new file mode 100644
index 0000000..33881af
--- /dev/null
+++ b/docs/tutorial/src/personalblog-demo/lib/hibernate2.jar
Binary files differ
diff --git a/docs/tutorial/src/personalblog-demo/lib/personalblog.jar b/docs/tutorial/src/personalblog-demo/lib/personalblog.jar
new file mode 100644
index 0000000..8c9774a
--- /dev/null
+++ b/docs/tutorial/src/personalblog-demo/lib/personalblog.jar
Binary files differ
diff --git a/docs/tutorial/src/personalblog-demo/lib/servlet-api.jar b/docs/tutorial/src/personalblog-demo/lib/servlet-api.jar
new file mode 100644
index 0000000..3363d5a
--- /dev/null
+++ b/docs/tutorial/src/personalblog-demo/lib/servlet-api.jar
Binary files differ
diff --git a/docs/tutorial/src/personalblog-demo/lib/struts.jar b/docs/tutorial/src/personalblog-demo/lib/struts.jar
new file mode 100644
index 0000000..db32f9f
--- /dev/null
+++ b/docs/tutorial/src/personalblog-demo/lib/struts.jar
Binary files differ
diff --git a/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java b/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java
new file mode 100644
index 0000000..fbb724c
--- /dev/null
+++ b/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java
@@ -0,0 +1,179 @@
+package net.eyde.personalblog.service;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import net.eyde.personalblog.beans.BlogProperty;
+import net.eyde.personalblog.beans.Comment;
+import net.eyde.personalblog.beans.Post;
+import net.eyde.personalblog.beans.Referrer;
+import net.sf.hibernate.Session;
+import net.sf.hibernate.SessionFactory;
+import net.sf.hibernate.cfg.Configuration;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+/**
+ * @author NEyde
+ *     <p>When the user selects a date, they will get the previous 25 posts from the date selected.
+ *     <p>When a user selects a specific post, they will see that post only.
+ *     <p>When a user selects a month, they will get all the posts for the month.
+ */
+public class PersonalBlogService {
+    // Installation State
+    public static final String INSTALLATION_STATE = "installation_state";
+    public static final String STATE_UNDEFINED = "undefined";
+    public static final String STATE_NO_HIBERNATE_FILE = "no_hibernate_file";
+    public static final String STATE_DATABASE_OFF = "database_off";
+    public static final String STATE_HIBERNATE_FILE_INVALID = "hibernate_file_invalid";
+    public static final String STATE_TABLES_NOT_CREATED = "tables_not_created_yet";
+    public static final String STATE_MISSING_PROPERTIES = "missing_properties";
+    public static final String STATE_OK = "ok";
+    private static Log log = LogFactory.getLog(PersonalBlogService.class);
+    private static PersonalBlogService service = null;
+
+    // Property Name Constants
+    public static final String WEBLOG_TITLE = "weblog.title";
+    public static final String WEBLOG_DESCRIPTION = "weblog.description";
+    public static final String WEBLOG_PICTURE = "weblog.ownerpicture";
+    public static final String WEBLOG_OWNER_NICK_NAME = "weblog.ownernickname";
+    public static final String WEBLOG_URL = "weblog.url";
+    public static final String WEBLOG_OWNER = "weblog.owner";
+    public static final String WEBLOG_EMAIL = "weblog.email";
+    public static final String LINK_POST = "links.post";
+    public static final String EMOTICON_VALUES = "emoticon.values";
+    public static final String EMOTICON_IMAGES = "emoticon.images";
+    public static final String LOGON_ID = "logon.id";
+    public static final String LOGON_PASSWORD = "logon.password";
+    public static final String EDITOR = "weblog.editor";
+    public static final String EMAIL_HOST = "mail.smtp.host";
+    public static final String EMAIL_TRANSPORT = "mail.transport";
+    public static final String EMAIL_USERNAME = "mail.username";
+    public static final String EMAIL_PASSWORD = "mail.password";
+    public static final String CATEGORY_TITLES = "category.titles";
+    public static final String CATEGORY_VALUES = "category.values";
+    public static final String CATEGORY_IMAGES = "category.images";
+    Configuration cfg;
+    SessionFactory sf;
+
+    int adjustHours;
+    PropertyManager pm;
+    CacheManager cache;
+
+    // is really necessary when you are going to format it?
+    Locale myLocale = Locale.US;
+    String dburl;
+    String dbuser;
+    String dbpassword;
+    SimpleDateFormat qf = new SimpleDateFormat("yyyy-MM-dd", myLocale);
+    SimpleDateFormat monthNav = new SimpleDateFormat("yyyyMM", myLocale);
+
+    /** Constructor for PersonalBlogService. */
+    protected PersonalBlogService(Properties conn) throws InitializationException {
+        log.debug("initialization - constructor");
+
+        try {
+            cfg =
+                    new Configuration()
+                            .addClass(Post.class)
+                            .addClass(Comment.class)
+                            .addClass(Referrer.class)
+                            .addClass(BlogProperty.class);
+
+            if (conn != null) {
+                cfg.setProperties(conn);
+                pm = new PropertyManager(conn);
+            } else {
+                pm = new PropertyManager();
+            }
+
+            // I want to take it out of here, for these
+            sf = cfg.buildSessionFactory();
+        } catch (Exception e) {
+            log.error("Error initializing PersonalBlog Service", e);
+
+            throw new InitializationException(e);
+        }
+    }
+
+    /** Singleton getInstance method */
+    public static PersonalBlogService getInstance() throws ServiceException {
+        if (service == null) {
+            try {
+                log.debug("Initializing PersonalBlog Service (WITHOUT CONNECTION PARMS)");
+                service = new PersonalBlogService(null);
+            } catch (ServiceException e) {
+                log.error("Error getting instance of PersonalBlog Service", e);
+
+                throw e;
+            }
+        }
+
+        return service;
+    }
+
+    public static PersonalBlogService getInstance(Properties conn) throws ServiceException {
+        if (service == null) {
+            try {
+                log.debug("Initializing PersonalBlog Service (WITH CONNECTION PARMS)");
+                service = new PersonalBlogService(conn);
+            } catch (Exception e) {
+                log.error("Error getting instance of PersonalBlog Service", e);
+            }
+        }
+
+        return service;
+    }
+
+    /*
+     * This method will return the most recent posts for today's date.  This method
+     * will return a maximum of 25 total posts or three days worth of posts.
+     *
+     */
+    public List<?> getPosts() throws ServiceException {
+        List<?> posts = null;
+
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.MONTH, -1);
+        @SuppressWarnings("tainting")
+        String startdate = (@Untainted String) qf.format(cal.getTime());
+
+        posts =
+                executeQuery(
+                        "from post in class net.eyde.personalblog.beans.Post "
+                                + "where post.created > '"
+                                + startdate
+                                + "' order by post.created desc");
+
+        return posts;
+    }
+
+    public List<?> getPostsByCategory(String category) throws ServiceException {
+        List<?> posts = null;
+
+        posts =
+                executeQuery(
+                        "from post in class net.eyde.personalblog.beans.Post "
+                                + "where post.category like '%"
+                                + category
+                                + "%' order by post.created desc");
+
+        return posts;
+    }
+
+    private <T> List<T> executeQuery(@Untainted String query) {
+        try {
+            Session session = sf.openSession();
+            @SuppressWarnings({"unchecked"})
+            List<T> lst = (List<T>) session.find(query);
+            session.close();
+            return lst;
+        } catch (Exception e) {
+            log.error("Error while importing data", e);
+            return null;
+        }
+    }
+}
diff --git a/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java b/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java
new file mode 100644
index 0000000..1bcd1f4
--- /dev/null
+++ b/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java
@@ -0,0 +1,99 @@
+package net.eyde.personalblog.struts.action;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import net.eyde.personalblog.service.PersonalBlogService;
+import net.eyde.personalblog.service.ServiceException;
+import org.apache.struts.action.ActionErrors;
+import org.apache.struts.action.ActionForm;
+import org.apache.struts.action.ActionForward;
+import org.apache.struts.action.ActionMapping;
+import org.apache.struts.action.ActionMessage;
+import org.apache.struts.action.ActionMessages;
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+/**
+ * Description of the Class
+ *
+ * @author NEyde
+ * @created September 17, 2002
+ */
+public final class ReadAction extends BlogGeneralAction {
+    /**
+     * Process the specified HTTP request, and create the corresponding HTTP response (or forward to
+     * another web component that will create it). Return an ActionForward instance describing where
+     * and how control should be forwarded, or null if the response has already been completed.
+     *
+     * @param mapping The ActionMapping used to select this instance
+     * @param request The HTTP request we are processing
+     * @param response The HTTP response we are creating
+     * @param form Description of the Parameter
+     * @return Description of the Return Value
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet exception occurs
+     */
+    @Override
+    public ActionForward executeSub(
+            ActionMapping mapping,
+            ActionForm form,
+            HttpServletRequest request,
+            HttpServletResponse response)
+            throws Exception {
+        ActionErrors errors = new ActionErrors();
+        String forward = "readposts";
+
+        // Get request parameters
+        String reqCategory = cleanNull(request.getParameter("cat"));
+
+        // Get instance of PersonalBlog Service
+        PersonalBlogService pblog = PersonalBlogService.getInstance();
+
+        // Set Request Parameters
+        // Depending on the parameters, call the appropriate method
+        try {
+            if (!reqCategory.equals("")) {
+                request.setAttribute("posts", pblog.getPostsByCategory(reqCategory));
+            } else {
+                request.setAttribute("posts", pblog.getPosts());
+            }
+
+        } catch (ServiceException e) {
+            ActionMessages messages = new ActionMessages();
+            ActionMessage message = new ActionMessage("exception.postdoesnotexist");
+            messages.add(ActionMessages.GLOBAL_MESSAGE, message);
+
+            errors.add(messages);
+            e.printStackTrace();
+        }
+
+        if (!errors.isEmpty()) {
+            saveErrors(request, errors);
+        }
+
+        return (mapping.findForward(forward));
+    }
+
+    /**
+     * Validates userInput: verifies that it cannot be used for an attack.
+     *
+     * <p>A string is valid if it contains only letters, digits, and whitespace.
+     *
+     * @param userInput user input to be validated
+     * @return the input if it is valid
+     * @throws IllegalArgumentException if userInput is not valid
+     */
+    @Untainted String validate(String userInput) {
+        for (int i = 0; i < userInput.length(); ++i) {
+            char ch = userInput.charAt(i);
+            if (!Character.isLetter(ch) && !Character.isDigit(ch) && !Character.isWhitespace(ch))
+                throw new IllegalArgumentException("Illegal user input");
+        }
+        @SuppressWarnings("tainting")
+        @Untainted String result = userInput;
+        return result;
+    }
+}
+
+/* To fix the bug, replace line 48 by:
+        String reqCategory = validate(cleanNull(request.getParameter("cat")));
+*/
diff --git a/docs/tutorial/tests/README b/docs/tutorial/tests/README
new file mode 100644
index 0000000..65f7983
--- /dev/null
+++ b/docs/tutorial/tests/README
@@ -0,0 +1 @@
+These files were copied from checker-framework.demo and modified.
diff --git a/docs/tutorial/tests/ant-contrib-1.0b3.jar b/docs/tutorial/tests/ant-contrib-1.0b3.jar
new file mode 100644
index 0000000..0625376
--- /dev/null
+++ b/docs/tutorial/tests/ant-contrib-1.0b3.jar
Binary files differ
diff --git a/docs/tutorial/tests/ant.xml b/docs/tutorial/tests/ant.xml
new file mode 100644
index 0000000..f6da9ab
--- /dev/null
+++ b/docs/tutorial/tests/ant.xml
@@ -0,0 +1,198 @@
+<project name="macro-imports">
+
+    <taskdef resource="net/sf/antcontrib/antcontrib.properties">
+        <classpath>
+            <pathelement location="../../tests/ant-contrib-1.0b3.jar"/>
+        </classpath>
+    </taskdef>
+
+    <property environment="env"/>
+
+  <!--
+       Macro for calling the JSR 308 Java compiler with a checker
+       for the demo code.
+  -->
+  <macrodef name="run-demo">
+    <attribute name="srcpath"/>
+    <attribute name="files"/>
+    <attribute name="checker"/>
+    <attribute name="lint" default="none"/>
+    <attribute name="custom.lib" default=""/>
+    <sequential>
+      <java fork="true" failonerror="true"
+            classpath="${checker.lib}"
+            classname="org.checkerframework.framework.util.CheckerMain"
+            taskname="javac-cf">
+        <!-- <arg value="-version"/> -->
+        <arg value="-g"/>
+        <arg value="-classpath"/>
+        <arg value="${checker.lib}:@{custom.lib}"/>
+        <arg value="-sourcepath"/>
+        <arg value="@{srcpath}"/>
+        <arg value="-processor"/>
+        <arg value="@{checker}"/>
+        <arg value="-implicit:none"/>
+        <arg value="-source"/>
+        <arg value="1.8"/>
+        <arg value="-encoding"/>
+        <arg value="utf-8"/>
+        <arg value="-Xprefer:newer"/>
+        <arg line="@{files}"/>
+        <arg value="-Alint=@{lint}"/>
+        <arg value="-ApermitMissingJdk"/>
+        <arg value="-AsuppressWarnings=purity"/>
+      </java>
+    </sequential>
+  </macrodef>
+
+  <macrodef name="run-demo-basic">
+    <attribute name="srcpath"/>
+    <attribute name="files"/>
+    <attribute name="checker" default="org.checkerframework.common.subtyping.SubtypingChecker"/>
+    <attribute name="lint" default="none"/>
+    <attribute name="quals" default=""/>
+    <attribute name="custom.lib" default=""/>
+    <sequential>
+      <java fork="true" failonerror="true"
+            classpath="${checker.lib}"
+            classname="org.checkerframework.framework.util.CheckerMain"
+            taskname="javac-cf">
+        <!-- <arg value="-version"/> -->
+        <arg value="-g"/>
+        <arg value="-classpath"/>
+        <arg value="@{custom.lib}"/>
+        <arg value="-sourcepath"/>
+        <arg value="@{srcpath}"/>
+        <arg value="-processor"/>
+        <arg value="@{checker}"/>
+        <arg value="-implicit:none"/>
+        <arg value="-source"/>
+        <arg value="1.8"/>
+        <arg value="-encoding"/>
+        <arg value="utf-8"/>
+        <arg value="-Xprefer:newer"/>
+        <arg line="@{files}"/>
+        <arg value="-Alint=@{lint}"/>
+        <arg value="-Aquals=@{quals}"/>
+      </java>
+    </sequential>
+  </macrodef>
+
+  <!-- Pass arguments in the order: expected, actual. -->
+  <macrodef name="my-diff">
+    <attribute name="file1"/>
+    <attribute name="file2"/>
+    <attribute name="ignore-matching-lines"/>
+    <sequential>
+      <exec executable="diff" outputproperty="diff-output" resultproperty="diff-result"> <!-- not  failonerror="true because that prevents user-friendly error message below -->
+        <!-- <arg value="-q"/> -->
+        <arg value="-I"/>
+        <arg value="@{ignore-matching-lines}"/>
+        <!-- ignore lint results -->
+        <arg value="-I"/>
+        <arg value="Note:"/>
+        <!-- ignore ant headers and empty lines -->
+        <arg value="-I"/>
+        <arg value="^ *$"/>
+        <arg value="-I"/>
+        <arg value="^[^ ]*:$"/>
+
+        <!-- ignore version of Checker Framework javac -->
+        <arg value="-I"/>
+        <arg value="^javac "/>
+
+        <!-- ignore paths -->
+        <arg value="-I"/>
+        <arg value=".java"/>
+        <arg value="-I"/>
+        <arg value=".xml"/>
+        <arg value="-I"/>
+        <arg value="Deleting directory"/>
+        <arg value="-I"/>
+        <arg value="Created dir:"/>
+        <arg value="-I"/>
+        <arg value="Compiling 2 source files to"/>
+
+        <!-- the files to compare -->
+        <arg line="@{file1}"/>
+        <arg line="@{file2}"/>
+      </exec>
+
+      <if>
+          <isfailure code="${diff-result}" />
+          <then>
+              <loadfile property="results.expected" srcFile="@{file1}"/>
+              <loadfile property="results.actual" srcFile="@{file2}"/>
+              <fail message="Error in the demo.${line.separator}
+${line.separator}Expected:
+${line.separator}=====================================================================
+${line.separator}${results.expected}
+${line.separator}=====================================================================
+${line.separator}
+${line.separator}but found:
+${line.separator}=====================================================================
+${line.separator}${results.actual}
+${line.separator}=====================================================================
+${line.separator}
+${line.separator}Diff output is:
+${line.separator}=====================================================================
+${line.separator}${diff-output}
+${line.separator}=====================================================================
+${line.separator}
+${line.separator}NOTE: Did you run 'ant touch-files'?"/>
+          </then>
+      </if>
+    </sequential>
+  </macrodef>
+
+  <macrodef name="my-ant">
+    <attribute name="task" default=""/>
+    <attribute name="logfile" default=""/>
+    <sequential>
+      <exec executable="ant" failonerror="false"
+          resultproperty="property.ignore">
+        <arg value="-e"/>
+        <arg value="-l"/>
+        <arg value="@{logfile}"/>
+        <arg value="@{task}"/>
+      </exec>
+      <sleep milliseconds="100"/> <!--sometimes output gets truncated -->
+    </sequential>
+  </macrodef>
+
+  <macrodef name="check-demo">
+    <attribute name="patchfile"/>
+    <attribute name="task"/>
+    <attribute name="expected"/>
+    <sequential>
+      <!-- Apply the patch -->
+      <patch patchfile="@{patchfile}" quiet="yes" strip="0" failonerror="true"/>
+      <my-ant task="@{task}" logfile="output.tmp"/>
+      <my-diff file1="@{expected}" file2="output.tmp"
+               ignore-matching-lines="Total time"/>
+      <delete file="output.tmp"/>
+    </sequential>
+  </macrodef>
+
+  <macrodef name="check-demo-common">
+    <attribute name="target"/>
+    <attribute name="step"/>
+    <attribute name="dir" default="testdemo"/>
+    <sequential>
+      <echo message="Checking demo target=@{target} step=@{step}"/>
+      <check-demo
+            task="@{target}"
+            patchfile="@{dir}/@{target}.@{step}.patch"
+            expected="@{dir}/@{target}.@{step}.expected"/>
+    </sequential>
+  </macrodef>
+
+  <macrodef name="revert-demo">
+      <attribute name="target"/>
+      <attribute name="step"/>
+      <attribute name="dir" default="testdemo"/>
+      <sequential>
+          <patch patchfile="@{dir}/@{target}.@{step}.patch" quiet="yes" reverse="yes" strip="0" failonerror="true"/>
+      </sequential>
+  </macrodef>
+</project>
diff --git a/docs/tutorial/tests/build.xml b/docs/tutorial/tests/build.xml
new file mode 100644
index 0000000..bb70e0e
--- /dev/null
+++ b/docs/tutorial/tests/build.xml
@@ -0,0 +1,22 @@
+<project name="personalblog" basedir="../src/personalblog-demo/" default="check-tutorial">
+
+    <property file="../../../build-common.properties"/>
+    <import file="ant.xml"/>
+
+    <target name="check-pblog">
+      <ant >
+         <target name="clean"/>
+      </ant>
+      <check-demo-common target="check-tainting" step="0" dir="../../tests/testdemo"/>
+      <check-demo-common target="check-tainting" step="1" dir="../../tests/testdemo"/>
+      <check-demo-common target="check-tainting" step="2" dir="../../tests/testdemo"/>
+      <echo message="pblog demo works as expected" />
+      <revert-demo target="check-tainting" step="2" dir="../../tests/testdemo"/>
+      <revert-demo target="check-tainting" step="1" dir="../../tests/testdemo"/>
+      <revert-demo target="check-tainting" step="0" dir="../../tests/testdemo"/>
+    </target>
+
+    <target name="check-tutorial" depends="check-pblog">
+    </target>
+
+</project>
diff --git a/docs/tutorial/tests/testdemo/check-tainting.0.expected b/docs/tutorial/tests/testdemo/check-tainting.0.expected
new file mode 100644
index 0000000..31e3103
--- /dev/null
+++ b/docs/tutorial/tests/testdemo/check-tainting.0.expected
@@ -0,0 +1,21 @@
+
+clean:
+Deleting directory /Users/smillst/src/jsr308/checker-framework/tutorial/eclipse-projects/personalblog-demo/bin
+
+check-tainting:
+Created dir: /Users/smillst/src/jsr308/checker-framework/tutorial/eclipse-projects/personalblog-demo/bin
+Compiling 2 source files to /Users/smillst/src/jsr308/checker-framework/tutorial/eclipse-projects/personalblog-demo/bin
+javac 1.8.0-jsr308-3.12.0
+/home/mernst/research/types/checker-framework/tutorial/eclipse-projects/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java:174: error: [argument] incompatible types in argument.
+                                + "%' order by post.created desc");
+                                  ^
+  found   : @Tainted String
+  required: @Untainted String
+1 error
+
+BUILD FAILED
+/Users/smillst/src/jsr308/checker-framework/tutorial/eclipse-projects/personalblog-demo/build.xml:35: Compile failed; see the compiler error output for details.
+
+Total time: 3 seconds
+
+
diff --git a/docs/tutorial/tests/testdemo/check-tainting.0.patch b/docs/tutorial/tests/testdemo/check-tainting.0.patch
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docs/tutorial/tests/testdemo/check-tainting.0.patch
diff --git a/docs/tutorial/tests/testdemo/check-tainting.1.expected b/docs/tutorial/tests/testdemo/check-tainting.1.expected
new file mode 100644
index 0000000..cbd74a3
--- /dev/null
+++ b/docs/tutorial/tests/testdemo/check-tainting.1.expected
@@ -0,0 +1,19 @@
+
+clean:
+Deleting directory /home/mernst/research/types/checker-framework-REAL/tutorial/eclipse-projects/personalblog-demo/bin
+
+check-tainting:
+Created dir: /home/mernst/research/types/checker-framework-REAL/tutorial/eclipse-projects/personalblog-demo/bin
+Compiling 2 source files to /home/mernst/research/types/checker-framework-REAL/tutorial/eclipse-projects/personalblog-demo/bin
+javac 1.8.0-jsr308-1.9.9
+/home/mernst/research/types/checker-framework-REAL/tutorial/eclipse-projects/personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java:58: error: [argument] incompatible types in argument.
+                        pblog.getPostsByCategory(reqCategory));
+                                                 ^
+  found   : @Tainted String
+  required: @Untainted String
+1 error
+
+BUILD FAILED
+/home/mernst/research/types/checker-framework-REAL/tutorial/eclipse-projects/personalblog-demo/build.xml:35: Compile failed; see the compiler error output for details.
+
+Total time: 2 seconds
diff --git a/docs/tutorial/tests/testdemo/check-tainting.1.patch b/docs/tutorial/tests/testdemo/check-tainting.1.patch
new file mode 100644
index 0000000..7b1d16f
--- /dev/null
+++ b/docs/tutorial/tests/testdemo/check-tainting.1.patch
@@ -0,0 +1,13 @@
+Index: src/net/eyde/personalblog/service/PersonalBlogService.java
+===================================================================
+--- src/net/eyde/personalblog/service/PersonalBlogService.java	(revision 3589)
++++ src/net/eyde/personalblog/service/PersonalBlogService.java	(working copy)
+@@ -165,7 +165,7 @@ public class PersonalBlogService {
+         return posts;
+     }
+
+-    public List<?> getPostsByCategory(String category) throws ServiceException {
++    public List<?> getPostsByCategory(@Untainted String category) throws ServiceException {
+         List<?> posts = null;
+
+         posts = executeQuery(
diff --git a/docs/tutorial/tests/testdemo/check-tainting.2.expected b/docs/tutorial/tests/testdemo/check-tainting.2.expected
new file mode 100644
index 0000000..1fd73fb
--- /dev/null
+++ b/docs/tutorial/tests/testdemo/check-tainting.2.expected
@@ -0,0 +1,11 @@
+
+clean:
+Deleting directory /home/mernst/research/types/checker-framework-REAL/tutorial/eclipse-projects/personalblog-demo/bin
+
+check-tainting:
+Created dir: /home/mernst/research/types/checker-framework-REAL/tutorial/eclipse-projects/personalblog-demo/bin
+Compiling 2 source files to /home/mernst/research/types/checker-framework-REAL/tutorial/eclipse-projects/personalblog-demo/bin
+javac 1.8.0-jsr308-1.9.9
+
+BUILD SUCCESSFUL
+Total time: 2 seconds
diff --git a/docs/tutorial/tests/testdemo/check-tainting.2.patch b/docs/tutorial/tests/testdemo/check-tainting.2.patch
new file mode 100644
index 0000000..0f6351e
--- /dev/null
+++ b/docs/tutorial/tests/testdemo/check-tainting.2.patch
@@ -0,0 +1,13 @@
+Index: src/net/eyde/personalblog/struts/action/ReadAction.java
+===================================================================
+--- src/net/eyde/personalblog/struts/action/ReadAction.java	(revision 3589)
++++ src/net/eyde/personalblog/struts/action/ReadAction.java	(working copy)
+@@ -45,7 +45,7 @@
+         String forward = "readposts";
+ 
+         // Get request parameters
+-        String reqCategory = cleanNull(request.getParameter("cat"));
++        String reqCategory = validate(cleanNull(request.getParameter("cat")));
+ 
+         // Get instance of PersonalBlog Service
+         PersonalBlogService pblog = PersonalBlogService.getInstance();
diff --git a/docs/tutorial/webpages/bootstrap/css/bootstrap-responsive.css b/docs/tutorial/webpages/bootstrap/css/bootstrap-responsive.css
new file mode 100644
index 0000000..a3352d7
--- /dev/null
+++ b/docs/tutorial/webpages/bootstrap/css/bootstrap-responsive.css
@@ -0,0 +1,1092 @@
+/*!
+ * Bootstrap Responsive v2.2.2
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+@-ms-viewport {
+  width: device-width;
+}
+
+.clearfix {
+  *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.hide-text {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+.input-block-level {
+  display: block;
+  width: 100%;
+  min-height: 30px;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+.hidden {
+  display: none;
+  visibility: hidden;
+}
+
+.visible-phone {
+  display: none !important;
+}
+
+.visible-tablet {
+  display: none !important;
+}
+
+.hidden-desktop {
+  display: none !important;
+}
+
+.visible-desktop {
+  display: inherit !important;
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+  .hidden-desktop {
+    display: inherit !important;
+  }
+  .visible-desktop {
+    display: none !important ;
+  }
+  .visible-tablet {
+    display: inherit !important;
+  }
+  .hidden-tablet {
+    display: none !important;
+  }
+}
+
+@media (max-width: 767px) {
+  .hidden-desktop {
+    display: inherit !important;
+  }
+  .visible-desktop {
+    display: none !important;
+  }
+  .visible-phone {
+    display: inherit !important;
+  }
+  .hidden-phone {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .row {
+    margin-left: -30px;
+    *zoom: 1;
+  }
+  .row:before,
+  .row:after {
+    display: table;
+    line-height: 0;
+    content: "";
+  }
+  .row:after {
+    clear: both;
+  }
+  [class*="span"] {
+    float: left;
+    min-height: 1px;
+    margin-left: 30px;
+  }
+  .container,
+  .navbar-static-top .container,
+  .navbar-fixed-top .container,
+  .navbar-fixed-bottom .container {
+    width: 1170px;
+  }
+  .span12 {
+    width: 1170px;
+  }
+  .span11 {
+    width: 1070px;
+  }
+  .span10 {
+    width: 970px;
+  }
+  .span9 {
+    width: 870px;
+  }
+  .span8 {
+    width: 770px;
+  }
+  .span7 {
+    width: 670px;
+  }
+  .span6 {
+    width: 570px;
+  }
+  .span5 {
+    width: 470px;
+  }
+  .span4 {
+    width: 370px;
+  }
+  .span3 {
+    width: 270px;
+  }
+  .span2 {
+    width: 170px;
+  }
+  .span1 {
+    width: 70px;
+  }
+  .offset12 {
+    margin-left: 1230px;
+  }
+  .offset11 {
+    margin-left: 1130px;
+  }
+  .offset10 {
+    margin-left: 1030px;
+  }
+  .offset9 {
+    margin-left: 930px;
+  }
+  .offset8 {
+    margin-left: 830px;
+  }
+  .offset7 {
+    margin-left: 730px;
+  }
+  .offset6 {
+    margin-left: 630px;
+  }
+  .offset5 {
+    margin-left: 530px;
+  }
+  .offset4 {
+    margin-left: 430px;
+  }
+  .offset3 {
+    margin-left: 330px;
+  }
+  .offset2 {
+    margin-left: 230px;
+  }
+  .offset1 {
+    margin-left: 130px;
+  }
+  .row-fluid {
+    width: 100%;
+    *zoom: 1;
+  }
+  .row-fluid:before,
+  .row-fluid:after {
+    display: table;
+    line-height: 0;
+    content: "";
+  }
+  .row-fluid:after {
+    clear: both;
+  }
+  .row-fluid [class*="span"] {
+    display: block;
+    float: left;
+    width: 100%;
+    min-height: 30px;
+    margin-left: 2.564102564102564%;
+    *margin-left: 2.5109110747408616%;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .row-fluid [class*="span"]:first-child {
+    margin-left: 0;
+  }
+  .row-fluid .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 2.564102564102564%;
+  }
+  .row-fluid .span12 {
+    width: 100%;
+    *width: 99.94680851063829%;
+  }
+  .row-fluid .span11 {
+    width: 91.45299145299145%;
+    *width: 91.39979996362975%;
+  }
+  .row-fluid .span10 {
+    width: 82.90598290598291%;
+    *width: 82.8527914166212%;
+  }
+  .row-fluid .span9 {
+    width: 74.35897435897436%;
+    *width: 74.30578286961266%;
+  }
+  .row-fluid .span8 {
+    width: 65.81196581196582%;
+    *width: 65.75877432260411%;
+  }
+  .row-fluid .span7 {
+    width: 57.26495726495726%;
+    *width: 57.21176577559556%;
+  }
+  .row-fluid .span6 {
+    width: 48.717948717948715%;
+    *width: 48.664757228587014%;
+  }
+  .row-fluid .span5 {
+    width: 40.17094017094017%;
+    *width: 40.11774868157847%;
+  }
+  .row-fluid .span4 {
+    width: 31.623931623931625%;
+    *width: 31.570740134569924%;
+  }
+  .row-fluid .span3 {
+    width: 23.076923076923077%;
+    *width: 23.023731587561375%;
+  }
+  .row-fluid .span2 {
+    width: 14.52991452991453%;
+    *width: 14.476723040552828%;
+  }
+  .row-fluid .span1 {
+    width: 5.982905982905983%;
+    *width: 5.929714493544281%;
+  }
+  .row-fluid .offset12 {
+    margin-left: 105.12820512820512%;
+    *margin-left: 105.02182214948171%;
+  }
+  .row-fluid .offset12:first-child {
+    margin-left: 102.56410256410257%;
+    *margin-left: 102.45771958537915%;
+  }
+  .row-fluid .offset11 {
+    margin-left: 96.58119658119658%;
+    *margin-left: 96.47481360247316%;
+  }
+  .row-fluid .offset11:first-child {
+    margin-left: 94.01709401709402%;
+    *margin-left: 93.91071103837061%;
+  }
+  .row-fluid .offset10 {
+    margin-left: 88.03418803418803%;
+    *margin-left: 87.92780505546462%;
+  }
+  .row-fluid .offset10:first-child {
+    margin-left: 85.47008547008548%;
+    *margin-left: 85.36370249136206%;
+  }
+  .row-fluid .offset9 {
+    margin-left: 79.48717948717949%;
+    *margin-left: 79.38079650845607%;
+  }
+  .row-fluid .offset9:first-child {
+    margin-left: 76.92307692307693%;
+    *margin-left: 76.81669394435352%;
+  }
+  .row-fluid .offset8 {
+    margin-left: 70.94017094017094%;
+    *margin-left: 70.83378796144753%;
+  }
+  .row-fluid .offset8:first-child {
+    margin-left: 68.37606837606839%;
+    *margin-left: 68.26968539734497%;
+  }
+  .row-fluid .offset7 {
+    margin-left: 62.393162393162385%;
+    *margin-left: 62.28677941443899%;
+  }
+  .row-fluid .offset7:first-child {
+    margin-left: 59.82905982905982%;
+    *margin-left: 59.72267685033642%;
+  }
+  .row-fluid .offset6 {
+    margin-left: 53.84615384615384%;
+    *margin-left: 53.739770867430444%;
+  }
+  .row-fluid .offset6:first-child {
+    margin-left: 51.28205128205128%;
+    *margin-left: 51.175668303327875%;
+  }
+  .row-fluid .offset5 {
+    margin-left: 45.299145299145295%;
+    *margin-left: 45.1927623204219%;
+  }
+  .row-fluid .offset5:first-child {
+    margin-left: 42.73504273504273%;
+    *margin-left: 42.62865975631933%;
+  }
+  .row-fluid .offset4 {
+    margin-left: 36.75213675213675%;
+    *margin-left: 36.645753773413354%;
+  }
+  .row-fluid .offset4:first-child {
+    margin-left: 34.18803418803419%;
+    *margin-left: 34.081651209310785%;
+  }
+  .row-fluid .offset3 {
+    margin-left: 28.205128205128204%;
+    *margin-left: 28.0987452264048%;
+  }
+  .row-fluid .offset3:first-child {
+    margin-left: 25.641025641025642%;
+    *margin-left: 25.53464266230224%;
+  }
+  .row-fluid .offset2 {
+    margin-left: 19.65811965811966%;
+    *margin-left: 19.551736679396257%;
+  }
+  .row-fluid .offset2:first-child {
+    margin-left: 17.094017094017094%;
+    *margin-left: 16.98763411529369%;
+  }
+  .row-fluid .offset1 {
+    margin-left: 11.11111111111111%;
+    *margin-left: 11.004728132387708%;
+  }
+  .row-fluid .offset1:first-child {
+    margin-left: 8.547008547008547%;
+    *margin-left: 8.440625568285142%;
+  }
+  input,
+  textarea,
+  .uneditable-input {
+    margin-left: 0;
+  }
+  .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 30px;
+  }
+  input.span12,
+  textarea.span12,
+  .uneditable-input.span12 {
+    width: 1156px;
+  }
+  input.span11,
+  textarea.span11,
+  .uneditable-input.span11 {
+    width: 1056px;
+  }
+  input.span10,
+  textarea.span10,
+  .uneditable-input.span10 {
+    width: 956px;
+  }
+  input.span9,
+  textarea.span9,
+  .uneditable-input.span9 {
+    width: 856px;
+  }
+  input.span8,
+  textarea.span8,
+  .uneditable-input.span8 {
+    width: 756px;
+  }
+  input.span7,
+  textarea.span7,
+  .uneditable-input.span7 {
+    width: 656px;
+  }
+  input.span6,
+  textarea.span6,
+  .uneditable-input.span6 {
+    width: 556px;
+  }
+  input.span5,
+  textarea.span5,
+  .uneditable-input.span5 {
+    width: 456px;
+  }
+  input.span4,
+  textarea.span4,
+  .uneditable-input.span4 {
+    width: 356px;
+  }
+  input.span3,
+  textarea.span3,
+  .uneditable-input.span3 {
+    width: 256px;
+  }
+  input.span2,
+  textarea.span2,
+  .uneditable-input.span2 {
+    width: 156px;
+  }
+  input.span1,
+  textarea.span1,
+  .uneditable-input.span1 {
+    width: 56px;
+  }
+  .thumbnails {
+    margin-left: -30px;
+  }
+  .thumbnails > li {
+    margin-left: 30px;
+  }
+  .row-fluid .thumbnails {
+    margin-left: 0;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+  .row {
+    margin-left: -20px;
+    *zoom: 1;
+  }
+  .row:before,
+  .row:after {
+    display: table;
+    line-height: 0;
+    content: "";
+  }
+  .row:after {
+    clear: both;
+  }
+  [class*="span"] {
+    float: left;
+    min-height: 1px;
+    margin-left: 20px;
+  }
+  .container,
+  .navbar-static-top .container,
+  .navbar-fixed-top .container,
+  .navbar-fixed-bottom .container {
+    width: 724px;
+  }
+  .span12 {
+    width: 724px;
+  }
+  .span11 {
+    width: 662px;
+  }
+  .span10 {
+    width: 600px;
+  }
+  .span9 {
+    width: 538px;
+  }
+  .span8 {
+    width: 476px;
+  }
+  .span7 {
+    width: 414px;
+  }
+  .span6 {
+    width: 352px;
+  }
+  .span5 {
+    width: 290px;
+  }
+  .span4 {
+    width: 228px;
+  }
+  .span3 {
+    width: 166px;
+  }
+  .span2 {
+    width: 104px;
+  }
+  .span1 {
+    width: 42px;
+  }
+  .offset12 {
+    margin-left: 764px;
+  }
+  .offset11 {
+    margin-left: 702px;
+  }
+  .offset10 {
+    margin-left: 640px;
+  }
+  .offset9 {
+    margin-left: 578px;
+  }
+  .offset8 {
+    margin-left: 516px;
+  }
+  .offset7 {
+    margin-left: 454px;
+  }
+  .offset6 {
+    margin-left: 392px;
+  }
+  .offset5 {
+    margin-left: 330px;
+  }
+  .offset4 {
+    margin-left: 268px;
+  }
+  .offset3 {
+    margin-left: 206px;
+  }
+  .offset2 {
+    margin-left: 144px;
+  }
+  .offset1 {
+    margin-left: 82px;
+  }
+  .row-fluid {
+    width: 100%;
+    *zoom: 1;
+  }
+  .row-fluid:before,
+  .row-fluid:after {
+    display: table;
+    line-height: 0;
+    content: "";
+  }
+  .row-fluid:after {
+    clear: both;
+  }
+  .row-fluid [class*="span"] {
+    display: block;
+    float: left;
+    width: 100%;
+    min-height: 30px;
+    margin-left: 2.7624309392265194%;
+    *margin-left: 2.709239449864817%;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .row-fluid [class*="span"]:first-child {
+    margin-left: 0;
+  }
+  .row-fluid .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 2.7624309392265194%;
+  }
+  .row-fluid .span12 {
+    width: 100%;
+    *width: 99.94680851063829%;
+  }
+  .row-fluid .span11 {
+    width: 91.43646408839778%;
+    *width: 91.38327259903608%;
+  }
+  .row-fluid .span10 {
+    width: 82.87292817679558%;
+    *width: 82.81973668743387%;
+  }
+  .row-fluid .span9 {
+    width: 74.30939226519337%;
+    *width: 74.25620077583166%;
+  }
+  .row-fluid .span8 {
+    width: 65.74585635359117%;
+    *width: 65.69266486422946%;
+  }
+  .row-fluid .span7 {
+    width: 57.18232044198895%;
+    *width: 57.12912895262725%;
+  }
+  .row-fluid .span6 {
+    width: 48.61878453038674%;
+    *width: 48.56559304102504%;
+  }
+  .row-fluid .span5 {
+    width: 40.05524861878453%;
+    *width: 40.00205712942283%;
+  }
+  .row-fluid .span4 {
+    width: 31.491712707182323%;
+    *width: 31.43852121782062%;
+  }
+  .row-fluid .span3 {
+    width: 22.92817679558011%;
+    *width: 22.87498530621841%;
+  }
+  .row-fluid .span2 {
+    width: 14.3646408839779%;
+    *width: 14.311449394616199%;
+  }
+  .row-fluid .span1 {
+    width: 5.801104972375691%;
+    *width: 5.747913483013988%;
+  }
+  .row-fluid .offset12 {
+    margin-left: 105.52486187845304%;
+    *margin-left: 105.41847889972962%;
+  }
+  .row-fluid .offset12:first-child {
+    margin-left: 102.76243093922652%;
+    *margin-left: 102.6560479605031%;
+  }
+  .row-fluid .offset11 {
+    margin-left: 96.96132596685082%;
+    *margin-left: 96.8549429881274%;
+  }
+  .row-fluid .offset11:first-child {
+    margin-left: 94.1988950276243%;
+    *margin-left: 94.09251204890089%;
+  }
+  .row-fluid .offset10 {
+    margin-left: 88.39779005524862%;
+    *margin-left: 88.2914070765252%;
+  }
+  .row-fluid .offset10:first-child {
+    margin-left: 85.6353591160221%;
+    *margin-left: 85.52897613729868%;
+  }
+  .row-fluid .offset9 {
+    margin-left: 79.8342541436464%;
+    *margin-left: 79.72787116492299%;
+  }
+  .row-fluid .offset9:first-child {
+    margin-left: 77.07182320441989%;
+    *margin-left: 76.96544022569647%;
+  }
+  .row-fluid .offset8 {
+    margin-left: 71.2707182320442%;
+    *margin-left: 71.16433525332079%;
+  }
+  .row-fluid .offset8:first-child {
+    margin-left: 68.50828729281768%;
+    *margin-left: 68.40190431409427%;
+  }
+  .row-fluid .offset7 {
+    margin-left: 62.70718232044199%;
+    *margin-left: 62.600799341718584%;
+  }
+  .row-fluid .offset7:first-child {
+    margin-left: 59.94475138121547%;
+    *margin-left: 59.838368402492065%;
+  }
+  .row-fluid .offset6 {
+    margin-left: 54.14364640883978%;
+    *margin-left: 54.037263430116376%;
+  }
+  .row-fluid .offset6:first-child {
+    margin-left: 51.38121546961326%;
+    *margin-left: 51.27483249088986%;
+  }
+  .row-fluid .offset5 {
+    margin-left: 45.58011049723757%;
+    *margin-left: 45.47372751851417%;
+  }
+  .row-fluid .offset5:first-child {
+    margin-left: 42.81767955801105%;
+    *margin-left: 42.71129657928765%;
+  }
+  .row-fluid .offset4 {
+    margin-left: 37.01657458563536%;
+    *margin-left: 36.91019160691196%;
+  }
+  .row-fluid .offset4:first-child {
+    margin-left: 34.25414364640884%;
+    *margin-left: 34.14776066768544%;
+  }
+  .row-fluid .offset3 {
+    margin-left: 28.45303867403315%;
+    *margin-left: 28.346655695309746%;
+  }
+  .row-fluid .offset3:first-child {
+    margin-left: 25.69060773480663%;
+    *margin-left: 25.584224756083227%;
+  }
+  .row-fluid .offset2 {
+    margin-left: 19.88950276243094%;
+    *margin-left: 19.783119783707537%;
+  }
+  .row-fluid .offset2:first-child {
+    margin-left: 17.12707182320442%;
+    *margin-left: 17.02068884448102%;
+  }
+  .row-fluid .offset1 {
+    margin-left: 11.32596685082873%;
+    *margin-left: 11.219583872105325%;
+  }
+  .row-fluid .offset1:first-child {
+    margin-left: 8.56353591160221%;
+    *margin-left: 8.457152932878806%;
+  }
+  input,
+  textarea,
+  .uneditable-input {
+    margin-left: 0;
+  }
+  .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 20px;
+  }
+  input.span12,
+  textarea.span12,
+  .uneditable-input.span12 {
+    width: 710px;
+  }
+  input.span11,
+  textarea.span11,
+  .uneditable-input.span11 {
+    width: 648px;
+  }
+  input.span10,
+  textarea.span10,
+  .uneditable-input.span10 {
+    width: 586px;
+  }
+  input.span9,
+  textarea.span9,
+  .uneditable-input.span9 {
+    width: 524px;
+  }
+  input.span8,
+  textarea.span8,
+  .uneditable-input.span8 {
+    width: 462px;
+  }
+  input.span7,
+  textarea.span7,
+  .uneditable-input.span7 {
+    width: 400px;
+  }
+  input.span6,
+  textarea.span6,
+  .uneditable-input.span6 {
+    width: 338px;
+  }
+  input.span5,
+  textarea.span5,
+  .uneditable-input.span5 {
+    width: 276px;
+  }
+  input.span4,
+  textarea.span4,
+  .uneditable-input.span4 {
+    width: 214px;
+  }
+  input.span3,
+  textarea.span3,
+  .uneditable-input.span3 {
+    width: 152px;
+  }
+  input.span2,
+  textarea.span2,
+  .uneditable-input.span2 {
+    width: 90px;
+  }
+  input.span1,
+  textarea.span1,
+  .uneditable-input.span1 {
+    width: 28px;
+  }
+}
+
+@media (max-width: 767px) {
+  body {
+    padding-right: 20px;
+    padding-left: 20px;
+  }
+  .navbar-fixed-top,
+  .navbar-fixed-bottom,
+  .navbar-static-top {
+    margin-right: -20px;
+    margin-left: -20px;
+  }
+  .container-fluid {
+    padding: 0;
+  }
+  .dl-horizontal dt {
+    float: none;
+    width: auto;
+    clear: none;
+    text-align: left;
+  }
+  .dl-horizontal dd {
+    margin-left: 0;
+  }
+  .container {
+    width: auto;
+  }
+  .row-fluid {
+    width: 100%;
+  }
+  .row,
+  .thumbnails {
+    margin-left: 0;
+  }
+  .thumbnails > li {
+    float: none;
+    margin-left: 0;
+  }
+  [class*="span"],
+  .uneditable-input[class*="span"],
+  .row-fluid [class*="span"] {
+    display: block;
+    float: none;
+    width: 100%;
+    margin-left: 0;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .span12,
+  .row-fluid .span12 {
+    width: 100%;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .row-fluid [class*="offset"]:first-child {
+    margin-left: 0;
+  }
+  .input-large,
+  .input-xlarge,
+  .input-xxlarge,
+  input[class*="span"],
+  select[class*="span"],
+  textarea[class*="span"],
+  .uneditable-input {
+    display: block;
+    width: 100%;
+    min-height: 30px;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .input-prepend input,
+  .input-append input,
+  .input-prepend input[class*="span"],
+  .input-append input[class*="span"] {
+    display: inline-block;
+    width: auto;
+  }
+  .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 0;
+  }
+  .modal {
+    position: fixed;
+    top: 20px;
+    right: 20px;
+    left: 20px;
+    width: auto;
+    margin: 0;
+  }
+  .modal.fade {
+    top: -100px;
+  }
+  .modal.fade.in {
+    top: 20px;
+  }
+}
+
+@media (max-width: 480px) {
+  .nav-collapse {
+    -webkit-transform: translate3d(0, 0, 0);
+  }
+  .page-header h1 small {
+    display: block;
+    line-height: 20px;
+  }
+  input[type="checkbox"],
+  input[type="radio"] {
+    border: 1px solid #ccc;
+  }
+  .form-horizontal .control-label {
+    float: none;
+    width: auto;
+    padding-top: 0;
+    text-align: left;
+  }
+  .form-horizontal .controls {
+    margin-left: 0;
+  }
+  .form-horizontal .control-list {
+    padding-top: 0;
+  }
+  .form-horizontal .form-actions {
+    padding-right: 10px;
+    padding-left: 10px;
+  }
+  .media .pull-left,
+  .media .pull-right {
+    display: block;
+    float: none;
+    margin-bottom: 10px;
+  }
+  .media-object {
+    margin-right: 0;
+    margin-left: 0;
+  }
+  .modal {
+    top: 10px;
+    right: 10px;
+    left: 10px;
+  }
+  .modal-header .close {
+    padding: 10px;
+    margin: -10px;
+  }
+  .carousel-caption {
+    position: static;
+  }
+}
+
+@media (max-width: 979px) {
+  body {
+    padding-top: 0;
+  }
+  .navbar-fixed-top,
+  .navbar-fixed-bottom {
+    position: static;
+  }
+  .navbar-fixed-top {
+    margin-bottom: 20px;
+  }
+  .navbar-fixed-bottom {
+    margin-top: 20px;
+  }
+  .navbar-fixed-top .navbar-inner,
+  .navbar-fixed-bottom .navbar-inner {
+    padding: 5px;
+  }
+  .navbar .container {
+    width: auto;
+    padding: 0;
+  }
+  .navbar .brand {
+    padding-right: 10px;
+    padding-left: 10px;
+    margin: 0 0 0 -5px;
+  }
+  .nav-collapse {
+    clear: both;
+  }
+  .nav-collapse .nav {
+    float: none;
+    margin: 0 0 10px;
+  }
+  .nav-collapse .nav > li {
+    float: none;
+  }
+  .nav-collapse .nav > li > a {
+    margin-bottom: 2px;
+  }
+  .nav-collapse .nav > .divider-vertical {
+    display: none;
+  }
+  .nav-collapse .nav .nav-header {
+    color: #777777;
+    text-shadow: none;
+  }
+  .nav-collapse .nav > li > a,
+  .nav-collapse .dropdown-menu a {
+    padding: 9px 15px;
+    font-weight: bold;
+    color: #777777;
+    -webkit-border-radius: 3px;
+       -moz-border-radius: 3px;
+            border-radius: 3px;
+  }
+  .nav-collapse .btn {
+    padding: 4px 10px 4px;
+    font-weight: normal;
+    -webkit-border-radius: 4px;
+       -moz-border-radius: 4px;
+            border-radius: 4px;
+  }
+  .nav-collapse .dropdown-menu li + li a {
+    margin-bottom: 2px;
+  }
+  .nav-collapse .nav > li > a:hover,
+  .nav-collapse .dropdown-menu a:hover {
+    background-color: #f2f2f2;
+  }
+  .navbar-inverse .nav-collapse .nav > li > a,
+  .navbar-inverse .nav-collapse .dropdown-menu a {
+    color: #999999;
+  }
+  .navbar-inverse .nav-collapse .nav > li > a:hover,
+  .navbar-inverse .nav-collapse .dropdown-menu a:hover {
+    background-color: #111111;
+  }
+  .nav-collapse.in .btn-group {
+    padding: 0;
+    margin-top: 5px;
+  }
+  .nav-collapse .dropdown-menu {
+    position: static;
+    top: auto;
+    left: auto;
+    display: none;
+    float: none;
+    max-width: none;
+    padding: 0;
+    margin: 0 15px;
+    background-color: transparent;
+    border: none;
+    -webkit-border-radius: 0;
+       -moz-border-radius: 0;
+            border-radius: 0;
+    -webkit-box-shadow: none;
+       -moz-box-shadow: none;
+            box-shadow: none;
+  }
+  .nav-collapse .open > .dropdown-menu {
+    display: block;
+  }
+  .nav-collapse .dropdown-menu:before,
+  .nav-collapse .dropdown-menu:after {
+    display: none;
+  }
+  .nav-collapse .dropdown-menu .divider {
+    display: none;
+  }
+  .nav-collapse .nav > li > .dropdown-menu:before,
+  .nav-collapse .nav > li > .dropdown-menu:after {
+    display: none;
+  }
+  .nav-collapse .navbar-form,
+  .nav-collapse .navbar-search {
+    float: none;
+    padding: 10px 15px;
+    margin: 10px 0;
+    border-top: 1px solid #f2f2f2;
+    border-bottom: 1px solid #f2f2f2;
+    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+       -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+  }
+  .navbar-inverse .nav-collapse .navbar-form,
+  .navbar-inverse .nav-collapse .navbar-search {
+    border-top-color: #111111;
+    border-bottom-color: #111111;
+  }
+  .navbar .nav-collapse .nav.pull-right {
+    float: none;
+    margin-left: 0;
+  }
+  .nav-collapse,
+  .nav-collapse.collapse {
+    height: 0;
+    overflow: hidden;
+  }
+  .navbar .btn-navbar {
+    display: block;
+  }
+  .navbar-static .navbar-inner {
+    padding-right: 10px;
+    padding-left: 10px;
+  }
+}
+
+@media (min-width: 980px) {
+  .nav-collapse.collapse {
+    height: auto !important;
+    overflow: visible !important;
+  }
+}
diff --git a/docs/tutorial/webpages/bootstrap/css/bootstrap-responsive.min.css b/docs/tutorial/webpages/bootstrap/css/bootstrap-responsive.min.css
new file mode 100644
index 0000000..5cb833f
--- /dev/null
+++ b/docs/tutorial/webpages/bootstrap/css/bootstrap-responsive.min.css
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap Responsive v2.2.2
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */@-ms-viewport{width:device-width}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
diff --git a/docs/tutorial/webpages/bootstrap/css/bootstrap.css b/docs/tutorial/webpages/bootstrap/css/bootstrap.css
new file mode 100644
index 0000000..aec78b7
--- /dev/null
+++ b/docs/tutorial/webpages/bootstrap/css/bootstrap.css
@@ -0,0 +1,6040 @@
+/*!
+ * Bootstrap v2.2.2
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section {
+  display: block;
+}
+
+audio,
+canvas,
+video {
+  display: inline-block;
+  *display: inline;
+  *zoom: 1;
+}
+
+audio:not([controls]) {
+  display: none;
+}
+
+html {
+  font-size: 100%;
+  -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+}
+
+a:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+a:hover,
+a:active {
+  outline: 0;
+}
+
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+
+sup {
+  top: -0.5em;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+img {
+  width: auto\9;
+  height: auto;
+  max-width: 100%;
+  vertical-align: middle;
+  border: 0;
+  -ms-interpolation-mode: bicubic;
+}
+
+#map_canvas img,
+.google-maps img {
+  max-width: none;
+}
+
+button,
+input,
+select,
+textarea {
+  margin: 0;
+  font-size: 100%;
+  vertical-align: middle;
+}
+
+button,
+input {
+  *overflow: visible;
+  line-height: normal;
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  cursor: pointer;
+  -webkit-appearance: button;
+}
+
+label,
+select,
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"],
+input[type="radio"],
+input[type="checkbox"] {
+  cursor: pointer;
+}
+
+input[type="search"] {
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+  -webkit-appearance: textfield;
+}
+
+input[type="search"]::-webkit-search-decoration,
+input[type="search"]::-webkit-search-cancel-button {
+  -webkit-appearance: none;
+}
+
+textarea {
+  overflow: auto;
+  vertical-align: top;
+}
+
+@media print {
+  * {
+    color: #000 !important;
+    text-shadow: none !important;
+    background: transparent !important;
+    box-shadow: none !important;
+  }
+  a,
+  a:visited {
+    text-decoration: underline;
+  }
+  a[href]:after {
+    content: " (" attr(href) ")";
+  }
+  abbr[title]:after {
+    content: " (" attr(title) ")";
+  }
+  .ir a:after,
+  a[href^="javascript:"]:after,
+  a[href^="#"]:after {
+    content: "";
+  }
+  pre,
+  blockquote {
+    border: 1px solid #999;
+    page-break-inside: avoid;
+  }
+  thead {
+    display: table-header-group;
+  }
+  tr,
+  img {
+    page-break-inside: avoid;
+  }
+  img {
+    max-width: 100% !important;
+  }
+  @page  {
+    margin: 0.5cm;
+  }
+  p,
+  h2,
+  h3 {
+    orphans: 3;
+    widows: 3;
+  }
+  h2,
+  h3 {
+    page-break-after: avoid;
+  }
+}
+
+.clearfix {
+  *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.hide-text {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+.input-block-level {
+  display: block;
+  width: 100%;
+  min-height: 30px;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+body {
+  margin: 0;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  line-height: 20px;
+  color: #333333;
+  background-color: #ffffff;
+}
+
+a {
+  color: #0088cc;
+  text-decoration: none;
+}
+
+a:hover {
+  color: #005580;
+  text-decoration: underline;
+}
+
+.img-rounded {
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+}
+
+.img-polaroid {
+  padding: 4px;
+  background-color: #fff;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+     -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+          box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.img-circle {
+  -webkit-border-radius: 500px;
+     -moz-border-radius: 500px;
+          border-radius: 500px;
+}
+
+.row {
+  margin-left: -20px;
+  *zoom: 1;
+}
+
+.row:before,
+.row:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.row:after {
+  clear: both;
+}
+
+[class*="span"] {
+  float: left;
+  min-height: 1px;
+  margin-left: 20px;
+}
+
+.container,
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+  width: 940px;
+}
+
+.span12 {
+  width: 940px;
+}
+
+.span11 {
+  width: 860px;
+}
+
+.span10 {
+  width: 780px;
+}
+
+.span9 {
+  width: 700px;
+}
+
+.span8 {
+  width: 620px;
+}
+
+.span7 {
+  width: 540px;
+}
+
+.span6 {
+  width: 460px;
+}
+
+.span5 {
+  width: 380px;
+}
+
+.span4 {
+  width: 300px;
+}
+
+.span3 {
+  width: 220px;
+}
+
+.span2 {
+  width: 140px;
+}
+
+.span1 {
+  width: 60px;
+}
+
+.offset12 {
+  margin-left: 980px;
+}
+
+.offset11 {
+  margin-left: 900px;
+}
+
+.offset10 {
+  margin-left: 820px;
+}
+
+.offset9 {
+  margin-left: 740px;
+}
+
+.offset8 {
+  margin-left: 660px;
+}
+
+.offset7 {
+  margin-left: 580px;
+}
+
+.offset6 {
+  margin-left: 500px;
+}
+
+.offset5 {
+  margin-left: 420px;
+}
+
+.offset4 {
+  margin-left: 340px;
+}
+
+.offset3 {
+  margin-left: 260px;
+}
+
+.offset2 {
+  margin-left: 180px;
+}
+
+.offset1 {
+  margin-left: 100px;
+}
+
+.row-fluid {
+  width: 100%;
+  *zoom: 1;
+}
+
+.row-fluid:before,
+.row-fluid:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.row-fluid:after {
+  clear: both;
+}
+
+.row-fluid [class*="span"] {
+  display: block;
+  float: left;
+  width: 100%;
+  min-height: 30px;
+  margin-left: 2.127659574468085%;
+  *margin-left: 2.074468085106383%;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+.row-fluid [class*="span"]:first-child {
+  margin-left: 0;
+}
+
+.row-fluid .controls-row [class*="span"] + [class*="span"] {
+  margin-left: 2.127659574468085%;
+}
+
+.row-fluid .span12 {
+  width: 100%;
+  *width: 99.94680851063829%;
+}
+
+.row-fluid .span11 {
+  width: 91.48936170212765%;
+  *width: 91.43617021276594%;
+}
+
+.row-fluid .span10 {
+  width: 82.97872340425532%;
+  *width: 82.92553191489361%;
+}
+
+.row-fluid .span9 {
+  width: 74.46808510638297%;
+  *width: 74.41489361702126%;
+}
+
+.row-fluid .span8 {
+  width: 65.95744680851064%;
+  *width: 65.90425531914893%;
+}
+
+.row-fluid .span7 {
+  width: 57.44680851063829%;
+  *width: 57.39361702127659%;
+}
+
+.row-fluid .span6 {
+  width: 48.93617021276595%;
+  *width: 48.88297872340425%;
+}
+
+.row-fluid .span5 {
+  width: 40.42553191489362%;
+  *width: 40.37234042553192%;
+}
+
+.row-fluid .span4 {
+  width: 31.914893617021278%;
+  *width: 31.861702127659576%;
+}
+
+.row-fluid .span3 {
+  width: 23.404255319148934%;
+  *width: 23.351063829787233%;
+}
+
+.row-fluid .span2 {
+  width: 14.893617021276595%;
+  *width: 14.840425531914894%;
+}
+
+.row-fluid .span1 {
+  width: 6.382978723404255%;
+  *width: 6.329787234042553%;
+}
+
+.row-fluid .offset12 {
+  margin-left: 104.25531914893617%;
+  *margin-left: 104.14893617021275%;
+}
+
+.row-fluid .offset12:first-child {
+  margin-left: 102.12765957446808%;
+  *margin-left: 102.02127659574467%;
+}
+
+.row-fluid .offset11 {
+  margin-left: 95.74468085106382%;
+  *margin-left: 95.6382978723404%;
+}
+
+.row-fluid .offset11:first-child {
+  margin-left: 93.61702127659574%;
+  *margin-left: 93.51063829787232%;
+}
+
+.row-fluid .offset10 {
+  margin-left: 87.23404255319149%;
+  *margin-left: 87.12765957446807%;
+}
+
+.row-fluid .offset10:first-child {
+  margin-left: 85.1063829787234%;
+  *margin-left: 84.99999999999999%;
+}
+
+.row-fluid .offset9 {
+  margin-left: 78.72340425531914%;
+  *margin-left: 78.61702127659572%;
+}
+
+.row-fluid .offset9:first-child {
+  margin-left: 76.59574468085106%;
+  *margin-left: 76.48936170212764%;
+}
+
+.row-fluid .offset8 {
+  margin-left: 70.2127659574468%;
+  *margin-left: 70.10638297872339%;
+}
+
+.row-fluid .offset8:first-child {
+  margin-left: 68.08510638297872%;
+  *margin-left: 67.9787234042553%;
+}
+
+.row-fluid .offset7 {
+  margin-left: 61.70212765957446%;
+  *margin-left: 61.59574468085106%;
+}
+
+.row-fluid .offset7:first-child {
+  margin-left: 59.574468085106375%;
+  *margin-left: 59.46808510638297%;
+}
+
+.row-fluid .offset6 {
+  margin-left: 53.191489361702125%;
+  *margin-left: 53.085106382978715%;
+}
+
+.row-fluid .offset6:first-child {
+  margin-left: 51.063829787234035%;
+  *margin-left: 50.95744680851063%;
+}
+
+.row-fluid .offset5 {
+  margin-left: 44.68085106382979%;
+  *margin-left: 44.57446808510638%;
+}
+
+.row-fluid .offset5:first-child {
+  margin-left: 42.5531914893617%;
+  *margin-left: 42.4468085106383%;
+}
+
+.row-fluid .offset4 {
+  margin-left: 36.170212765957444%;
+  *margin-left: 36.06382978723405%;
+}
+
+.row-fluid .offset4:first-child {
+  margin-left: 34.04255319148936%;
+  *margin-left: 33.93617021276596%;
+}
+
+.row-fluid .offset3 {
+  margin-left: 27.659574468085104%;
+  *margin-left: 27.5531914893617%;
+}
+
+.row-fluid .offset3:first-child {
+  margin-left: 25.53191489361702%;
+  *margin-left: 25.425531914893618%;
+}
+
+.row-fluid .offset2 {
+  margin-left: 19.148936170212764%;
+  *margin-left: 19.04255319148936%;
+}
+
+.row-fluid .offset2:first-child {
+  margin-left: 17.02127659574468%;
+  *margin-left: 16.914893617021278%;
+}
+
+.row-fluid .offset1 {
+  margin-left: 10.638297872340425%;
+  *margin-left: 10.53191489361702%;
+}
+
+.row-fluid .offset1:first-child {
+  margin-left: 8.51063829787234%;
+  *margin-left: 8.404255319148938%;
+}
+
+[class*="span"].hide,
+.row-fluid [class*="span"].hide {
+  display: none;
+}
+
+[class*="span"].pull-right,
+.row-fluid [class*="span"].pull-right {
+  float: right;
+}
+
+.container {
+  margin-right: auto;
+  margin-left: auto;
+  *zoom: 1;
+}
+
+.container:before,
+.container:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.container:after {
+  clear: both;
+}
+
+.container-fluid {
+  padding-right: 20px;
+  padding-left: 20px;
+  *zoom: 1;
+}
+
+.container-fluid:before,
+.container-fluid:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.container-fluid:after {
+  clear: both;
+}
+
+p {
+  margin: 0 0 10px;
+}
+
+.lead {
+  margin-bottom: 20px;
+  font-size: 21px;
+  font-weight: 200;
+  line-height: 30px;
+}
+
+small {
+  font-size: 85%;
+}
+
+strong {
+  font-weight: bold;
+}
+
+em {
+  font-style: italic;
+}
+
+cite {
+  font-style: normal;
+}
+
+.muted {
+  color: #999999;
+}
+
+a.muted:hover {
+  color: #808080;
+}
+
+.text-warning {
+  color: #c09853;
+}
+
+a.text-warning:hover {
+  color: #a47e3c;
+}
+
+.text-error {
+  color: #b94a48;
+}
+
+a.text-error:hover {
+  color: #953b39;
+}
+
+.text-info {
+  color: #3a87ad;
+}
+
+a.text-info:hover {
+  color: #2d6987;
+}
+
+.text-success {
+  color: #468847;
+}
+
+a.text-success:hover {
+  color: #356635;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  margin: 10px 0;
+  font-family: inherit;
+  font-weight: bold;
+  line-height: 20px;
+  color: inherit;
+  text-rendering: optimizelegibility;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small {
+  font-weight: normal;
+  line-height: 1;
+  color: #999999;
+}
+
+h1,
+h2,
+h3 {
+  line-height: 40px;
+}
+
+h1 {
+  font-size: 38.5px;
+}
+
+h2 {
+  font-size: 31.5px;
+}
+
+h3 {
+  font-size: 24.5px;
+}
+
+h4 {
+  font-size: 17.5px;
+}
+
+h5 {
+  font-size: 14px;
+}
+
+h6 {
+  font-size: 11.9px;
+}
+
+h1 small {
+  font-size: 24.5px;
+}
+
+h2 small {
+  font-size: 17.5px;
+}
+
+h3 small {
+  font-size: 14px;
+}
+
+h4 small {
+  font-size: 14px;
+}
+
+.page-header {
+  padding-bottom: 9px;
+  margin: 20px 0 30px;
+  border-bottom: 1px solid #eeeeee;
+}
+
+ul,
+ol {
+  padding: 0;
+  margin: 0 0 10px 25px;
+}
+
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+  margin-bottom: 0;
+}
+
+li {
+  line-height: 20px;
+}
+
+ul.unstyled,
+ol.unstyled {
+  margin-left: 0;
+  list-style: none;
+}
+
+ul.inline,
+ol.inline {
+  margin-left: 0;
+  list-style: none;
+}
+
+ul.inline > li,
+ol.inline > li {
+  display: inline-block;
+  padding-right: 5px;
+  padding-left: 5px;
+}
+
+dl {
+  margin-bottom: 20px;
+}
+
+dt,
+dd {
+  line-height: 20px;
+}
+
+dt {
+  font-weight: bold;
+}
+
+dd {
+  margin-left: 10px;
+}
+
+.dl-horizontal {
+  *zoom: 1;
+}
+
+.dl-horizontal:before,
+.dl-horizontal:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.dl-horizontal:after {
+  clear: both;
+}
+
+.dl-horizontal dt {
+  float: left;
+  width: 160px;
+  overflow: hidden;
+  clear: left;
+  text-align: right;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.dl-horizontal dd {
+  margin-left: 180px;
+}
+
+hr {
+  margin: 20px 0;
+  border: 0;
+  border-top: 1px solid #eeeeee;
+  border-bottom: 1px solid #ffffff;
+}
+
+abbr[title],
+abbr[data-original-title] {
+  cursor: help;
+  border-bottom: 1px dotted #999999;
+}
+
+abbr.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+
+blockquote {
+  padding: 0 0 0 15px;
+  margin: 0 0 20px;
+  border-left: 5px solid #eeeeee;
+}
+
+blockquote p {
+  margin-bottom: 0;
+  font-size: 16px;
+  font-weight: 300;
+  line-height: 25px;
+}
+
+blockquote small {
+  display: block;
+  line-height: 20px;
+  color: #999999;
+}
+
+blockquote small:before {
+  content: '\2014 \00A0';
+}
+
+blockquote.pull-right {
+  float: right;
+  padding-right: 15px;
+  padding-left: 0;
+  border-right: 5px solid #eeeeee;
+  border-left: 0;
+}
+
+blockquote.pull-right p,
+blockquote.pull-right small {
+  text-align: right;
+}
+
+blockquote.pull-right small:before {
+  content: '';
+}
+
+blockquote.pull-right small:after {
+  content: '\00A0 \2014';
+}
+
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+  content: "";
+}
+
+address {
+  display: block;
+  margin-bottom: 20px;
+  font-style: normal;
+  line-height: 20px;
+}
+
+code,
+pre {
+  padding: 0 3px 2px;
+  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+  font-size: 12px;
+  color: #333333;
+  -webkit-border-radius: 3px;
+     -moz-border-radius: 3px;
+          border-radius: 3px;
+}
+
+code {
+  padding: 2px 4px;
+  color: #d14;
+  white-space: nowrap;
+  background-color: #f7f7f9;
+  border: 1px solid #e1e1e8;
+}
+
+pre {
+  display: block;
+  padding: 9.5px;
+  margin: 0 0 10px;
+  font-size: 13px;
+  line-height: 20px;
+  word-break: break-all;
+  word-wrap: break-word;
+  white-space: pre;
+  white-space: pre-wrap;
+  background-color: #f5f5f5;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.15);
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+pre.prettyprint {
+  margin-bottom: 20px;
+}
+
+pre code {
+  padding: 0;
+  color: inherit;
+  white-space: pre;
+  white-space: pre-wrap;
+  background-color: transparent;
+  border: 0;
+}
+
+.pre-scrollable {
+  max-height: 340px;
+  overflow-y: scroll;
+}
+
+form {
+  margin: 0 0 20px;
+}
+
+fieldset {
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+
+legend {
+  display: block;
+  width: 100%;
+  padding: 0;
+  margin-bottom: 20px;
+  font-size: 21px;
+  line-height: 40px;
+  color: #333333;
+  border: 0;
+  border-bottom: 1px solid #e5e5e5;
+}
+
+legend small {
+  font-size: 15px;
+  color: #999999;
+}
+
+label,
+input,
+button,
+select,
+textarea {
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 20px;
+}
+
+input,
+button,
+select,
+textarea {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+label {
+  display: block;
+  margin-bottom: 5px;
+}
+
+select,
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+  display: inline-block;
+  height: 20px;
+  padding: 4px 6px;
+  margin-bottom: 10px;
+  font-size: 14px;
+  line-height: 20px;
+  color: #555555;
+  vertical-align: middle;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+input,
+textarea,
+.uneditable-input {
+  width: 206px;
+}
+
+textarea {
+  height: auto;
+}
+
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+     -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+       -o-transition: border linear 0.2s, box-shadow linear 0.2s;
+          transition: border linear 0.2s, box-shadow linear 0.2s;
+}
+
+textarea:focus,
+input[type="text"]:focus,
+input[type="password"]:focus,
+input[type="datetime"]:focus,
+input[type="datetime-local"]:focus,
+input[type="date"]:focus,
+input[type="month"]:focus,
+input[type="time"]:focus,
+input[type="week"]:focus,
+input[type="number"]:focus,
+input[type="email"]:focus,
+input[type="url"]:focus,
+input[type="search"]:focus,
+input[type="tel"]:focus,
+input[type="color"]:focus,
+.uneditable-input:focus {
+  border-color: rgba(82, 168, 236, 0.8);
+  outline: 0;
+  outline: thin dotted \9;
+  /* IE6-9 */
+
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+  margin: 4px 0 0;
+  margin-top: 1px \9;
+  *margin-top: 0;
+  line-height: normal;
+}
+
+input[type="file"],
+input[type="image"],
+input[type="submit"],
+input[type="reset"],
+input[type="button"],
+input[type="radio"],
+input[type="checkbox"] {
+  width: auto;
+}
+
+select,
+input[type="file"] {
+  height: 30px;
+  /* In IE7, the height of the select element cannot be changed by height, only font-size */
+
+  *margin-top: 4px;
+  /* For IE7, add top margin to align select with labels */
+
+  line-height: 30px;
+}
+
+select {
+  width: 220px;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+}
+
+select[multiple],
+select[size] {
+  height: auto;
+}
+
+select:focus,
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+.uneditable-input,
+.uneditable-textarea {
+  color: #999999;
+  cursor: not-allowed;
+  background-color: #fcfcfc;
+  border-color: #cccccc;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+     -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+}
+
+.uneditable-input {
+  overflow: hidden;
+  white-space: nowrap;
+}
+
+.uneditable-textarea {
+  width: auto;
+  height: auto;
+}
+
+input:-moz-placeholder,
+textarea:-moz-placeholder {
+  color: #999999;
+}
+
+input:-ms-input-placeholder,
+textarea:-ms-input-placeholder {
+  color: #999999;
+}
+
+input::-webkit-input-placeholder,
+textarea::-webkit-input-placeholder {
+  color: #999999;
+}
+
+.radio,
+.checkbox {
+  min-height: 20px;
+  padding-left: 20px;
+}
+
+.radio input[type="radio"],
+.checkbox input[type="checkbox"] {
+  float: left;
+  margin-left: -20px;
+}
+
+.controls > .radio:first-child,
+.controls > .checkbox:first-child {
+  padding-top: 5px;
+}
+
+.radio.inline,
+.checkbox.inline {
+  display: inline-block;
+  padding-top: 5px;
+  margin-bottom: 0;
+  vertical-align: middle;
+}
+
+.radio.inline + .radio.inline,
+.checkbox.inline + .checkbox.inline {
+  margin-left: 10px;
+}
+
+.input-mini {
+  width: 60px;
+}
+
+.input-small {
+  width: 90px;
+}
+
+.input-medium {
+  width: 150px;
+}
+
+.input-large {
+  width: 210px;
+}
+
+.input-xlarge {
+  width: 270px;
+}
+
+.input-xxlarge {
+  width: 530px;
+}
+
+input[class*="span"],
+select[class*="span"],
+textarea[class*="span"],
+.uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"] {
+  float: none;
+  margin-left: 0;
+}
+
+.input-append input[class*="span"],
+.input-append .uneditable-input[class*="span"],
+.input-prepend input[class*="span"],
+.input-prepend .uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"],
+.row-fluid .input-prepend [class*="span"],
+.row-fluid .input-append [class*="span"] {
+  display: inline-block;
+}
+
+input,
+textarea,
+.uneditable-input {
+  margin-left: 0;
+}
+
+.controls-row [class*="span"] + [class*="span"] {
+  margin-left: 20px;
+}
+
+input.span12,
+textarea.span12,
+.uneditable-input.span12 {
+  width: 926px;
+}
+
+input.span11,
+textarea.span11,
+.uneditable-input.span11 {
+  width: 846px;
+}
+
+input.span10,
+textarea.span10,
+.uneditable-input.span10 {
+  width: 766px;
+}
+
+input.span9,
+textarea.span9,
+.uneditable-input.span9 {
+  width: 686px;
+}
+
+input.span8,
+textarea.span8,
+.uneditable-input.span8 {
+  width: 606px;
+}
+
+input.span7,
+textarea.span7,
+.uneditable-input.span7 {
+  width: 526px;
+}
+
+input.span6,
+textarea.span6,
+.uneditable-input.span6 {
+  width: 446px;
+}
+
+input.span5,
+textarea.span5,
+.uneditable-input.span5 {
+  width: 366px;
+}
+
+input.span4,
+textarea.span4,
+.uneditable-input.span4 {
+  width: 286px;
+}
+
+input.span3,
+textarea.span3,
+.uneditable-input.span3 {
+  width: 206px;
+}
+
+input.span2,
+textarea.span2,
+.uneditable-input.span2 {
+  width: 126px;
+}
+
+input.span1,
+textarea.span1,
+.uneditable-input.span1 {
+  width: 46px;
+}
+
+.controls-row {
+  *zoom: 1;
+}
+
+.controls-row:before,
+.controls-row:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.controls-row:after {
+  clear: both;
+}
+
+.controls-row [class*="span"],
+.row-fluid .controls-row [class*="span"] {
+  float: left;
+}
+
+.controls-row .checkbox[class*="span"],
+.controls-row .radio[class*="span"] {
+  padding-top: 5px;
+}
+
+input[disabled],
+select[disabled],
+textarea[disabled],
+input[readonly],
+select[readonly],
+textarea[readonly] {
+  cursor: not-allowed;
+  background-color: #eeeeee;
+}
+
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"][readonly],
+input[type="checkbox"][readonly] {
+  background-color: transparent;
+}
+
+.control-group.warning .control-label,
+.control-group.warning .help-block,
+.control-group.warning .help-inline {
+  color: #c09853;
+}
+
+.control-group.warning .checkbox,
+.control-group.warning .radio,
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+  color: #c09853;
+}
+
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+  border-color: #c09853;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.warning input:focus,
+.control-group.warning select:focus,
+.control-group.warning textarea:focus {
+  border-color: #a47e3c;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+
+.control-group.warning .input-prepend .add-on,
+.control-group.warning .input-append .add-on {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #c09853;
+}
+
+.control-group.error .control-label,
+.control-group.error .help-block,
+.control-group.error .help-inline {
+  color: #b94a48;
+}
+
+.control-group.error .checkbox,
+.control-group.error .radio,
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+  color: #b94a48;
+}
+
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+  border-color: #b94a48;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.error input:focus,
+.control-group.error select:focus,
+.control-group.error textarea:focus {
+  border-color: #953b39;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+
+.control-group.error .input-prepend .add-on,
+.control-group.error .input-append .add-on {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #b94a48;
+}
+
+.control-group.success .control-label,
+.control-group.success .help-block,
+.control-group.success .help-inline {
+  color: #468847;
+}
+
+.control-group.success .checkbox,
+.control-group.success .radio,
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+  color: #468847;
+}
+
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+  border-color: #468847;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.success input:focus,
+.control-group.success select:focus,
+.control-group.success textarea:focus {
+  border-color: #356635;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+
+.control-group.success .input-prepend .add-on,
+.control-group.success .input-append .add-on {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #468847;
+}
+
+.control-group.info .control-label,
+.control-group.info .help-block,
+.control-group.info .help-inline {
+  color: #3a87ad;
+}
+
+.control-group.info .checkbox,
+.control-group.info .radio,
+.control-group.info input,
+.control-group.info select,
+.control-group.info textarea {
+  color: #3a87ad;
+}
+
+.control-group.info input,
+.control-group.info select,
+.control-group.info textarea {
+  border-color: #3a87ad;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.info input:focus,
+.control-group.info select:focus,
+.control-group.info textarea:focus {
+  border-color: #2d6987;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+}
+
+.control-group.info .input-prepend .add-on,
+.control-group.info .input-append .add-on {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #3a87ad;
+}
+
+input:focus:invalid,
+textarea:focus:invalid,
+select:focus:invalid {
+  color: #b94a48;
+  border-color: #ee5f5b;
+}
+
+input:focus:invalid:focus,
+textarea:focus:invalid:focus,
+select:focus:invalid:focus {
+  border-color: #e9322d;
+  -webkit-box-shadow: 0 0 6px #f8b9b7;
+     -moz-box-shadow: 0 0 6px #f8b9b7;
+          box-shadow: 0 0 6px #f8b9b7;
+}
+
+.form-actions {
+  padding: 19px 20px 20px;
+  margin-top: 20px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border-top: 1px solid #e5e5e5;
+  *zoom: 1;
+}
+
+.form-actions:before,
+.form-actions:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.form-actions:after {
+  clear: both;
+}
+
+.help-block,
+.help-inline {
+  color: #595959;
+}
+
+.help-block {
+  display: block;
+  margin-bottom: 10px;
+}
+
+.help-inline {
+  display: inline-block;
+  *display: inline;
+  padding-left: 5px;
+  vertical-align: middle;
+  *zoom: 1;
+}
+
+.input-append,
+.input-prepend {
+  margin-bottom: 5px;
+  font-size: 0;
+  white-space: nowrap;
+}
+
+.input-append input,
+.input-prepend input,
+.input-append select,
+.input-prepend select,
+.input-append .uneditable-input,
+.input-prepend .uneditable-input,
+.input-append .dropdown-menu,
+.input-prepend .dropdown-menu {
+  font-size: 14px;
+}
+
+.input-append input,
+.input-prepend input,
+.input-append select,
+.input-prepend select,
+.input-append .uneditable-input,
+.input-prepend .uneditable-input {
+  position: relative;
+  margin-bottom: 0;
+  *margin-left: 0;
+  vertical-align: top;
+  -webkit-border-radius: 0 4px 4px 0;
+     -moz-border-radius: 0 4px 4px 0;
+          border-radius: 0 4px 4px 0;
+}
+
+.input-append input:focus,
+.input-prepend input:focus,
+.input-append select:focus,
+.input-prepend select:focus,
+.input-append .uneditable-input:focus,
+.input-prepend .uneditable-input:focus {
+  z-index: 2;
+}
+
+.input-append .add-on,
+.input-prepend .add-on {
+  display: inline-block;
+  width: auto;
+  height: 20px;
+  min-width: 16px;
+  padding: 4px 5px;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 20px;
+  text-align: center;
+  text-shadow: 0 1px 0 #ffffff;
+  background-color: #eeeeee;
+  border: 1px solid #ccc;
+}
+
+.input-append .add-on,
+.input-prepend .add-on,
+.input-append .btn,
+.input-prepend .btn,
+.input-append .btn-group > .dropdown-toggle,
+.input-prepend .btn-group > .dropdown-toggle {
+  vertical-align: top;
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.input-append .active,
+.input-prepend .active {
+  background-color: #a9dba9;
+  border-color: #46a546;
+}
+
+.input-prepend .add-on,
+.input-prepend .btn {
+  margin-right: -1px;
+}
+
+.input-prepend .add-on:first-child,
+.input-prepend .btn:first-child {
+  -webkit-border-radius: 4px 0 0 4px;
+     -moz-border-radius: 4px 0 0 4px;
+          border-radius: 4px 0 0 4px;
+}
+
+.input-append input,
+.input-append select,
+.input-append .uneditable-input {
+  -webkit-border-radius: 4px 0 0 4px;
+     -moz-border-radius: 4px 0 0 4px;
+          border-radius: 4px 0 0 4px;
+}
+
+.input-append input + .btn-group .btn:last-child,
+.input-append select + .btn-group .btn:last-child,
+.input-append .uneditable-input + .btn-group .btn:last-child {
+  -webkit-border-radius: 0 4px 4px 0;
+     -moz-border-radius: 0 4px 4px 0;
+          border-radius: 0 4px 4px 0;
+}
+
+.input-append .add-on,
+.input-append .btn,
+.input-append .btn-group {
+  margin-left: -1px;
+}
+
+.input-append .add-on:last-child,
+.input-append .btn:last-child,
+.input-append .btn-group:last-child > .dropdown-toggle {
+  -webkit-border-radius: 0 4px 4px 0;
+     -moz-border-radius: 0 4px 4px 0;
+          border-radius: 0 4px 4px 0;
+}
+
+.input-prepend.input-append input,
+.input-prepend.input-append select,
+.input-prepend.input-append .uneditable-input {
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.input-prepend.input-append input + .btn-group .btn,
+.input-prepend.input-append select + .btn-group .btn,
+.input-prepend.input-append .uneditable-input + .btn-group .btn {
+  -webkit-border-radius: 0 4px 4px 0;
+     -moz-border-radius: 0 4px 4px 0;
+          border-radius: 0 4px 4px 0;
+}
+
+.input-prepend.input-append .add-on:first-child,
+.input-prepend.input-append .btn:first-child {
+  margin-right: -1px;
+  -webkit-border-radius: 4px 0 0 4px;
+     -moz-border-radius: 4px 0 0 4px;
+          border-radius: 4px 0 0 4px;
+}
+
+.input-prepend.input-append .add-on:last-child,
+.input-prepend.input-append .btn:last-child {
+  margin-left: -1px;
+  -webkit-border-radius: 0 4px 4px 0;
+     -moz-border-radius: 0 4px 4px 0;
+          border-radius: 0 4px 4px 0;
+}
+
+.input-prepend.input-append .btn-group:first-child {
+  margin-left: 0;
+}
+
+input.search-query {
+  padding-right: 14px;
+  padding-right: 4px \9;
+  padding-left: 14px;
+  padding-left: 4px \9;
+  /* IE7-8 doesn't have border-radius, so don't indent the padding */
+
+  margin-bottom: 0;
+  -webkit-border-radius: 15px;
+     -moz-border-radius: 15px;
+          border-radius: 15px;
+}
+
+/* Allow for input prepend/append in search forms */
+
+.form-search .input-append .search-query,
+.form-search .input-prepend .search-query {
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.form-search .input-append .search-query {
+  -webkit-border-radius: 14px 0 0 14px;
+     -moz-border-radius: 14px 0 0 14px;
+          border-radius: 14px 0 0 14px;
+}
+
+.form-search .input-append .btn {
+  -webkit-border-radius: 0 14px 14px 0;
+     -moz-border-radius: 0 14px 14px 0;
+          border-radius: 0 14px 14px 0;
+}
+
+.form-search .input-prepend .search-query {
+  -webkit-border-radius: 0 14px 14px 0;
+     -moz-border-radius: 0 14px 14px 0;
+          border-radius: 0 14px 14px 0;
+}
+
+.form-search .input-prepend .btn {
+  -webkit-border-radius: 14px 0 0 14px;
+     -moz-border-radius: 14px 0 0 14px;
+          border-radius: 14px 0 0 14px;
+}
+
+.form-search input,
+.form-inline input,
+.form-horizontal input,
+.form-search textarea,
+.form-inline textarea,
+.form-horizontal textarea,
+.form-search select,
+.form-inline select,
+.form-horizontal select,
+.form-search .help-inline,
+.form-inline .help-inline,
+.form-horizontal .help-inline,
+.form-search .uneditable-input,
+.form-inline .uneditable-input,
+.form-horizontal .uneditable-input,
+.form-search .input-prepend,
+.form-inline .input-prepend,
+.form-horizontal .input-prepend,
+.form-search .input-append,
+.form-inline .input-append,
+.form-horizontal .input-append {
+  display: inline-block;
+  *display: inline;
+  margin-bottom: 0;
+  vertical-align: middle;
+  *zoom: 1;
+}
+
+.form-search .hide,
+.form-inline .hide,
+.form-horizontal .hide {
+  display: none;
+}
+
+.form-search label,
+.form-inline label,
+.form-search .btn-group,
+.form-inline .btn-group {
+  display: inline-block;
+}
+
+.form-search .input-append,
+.form-inline .input-append,
+.form-search .input-prepend,
+.form-inline .input-prepend {
+  margin-bottom: 0;
+}
+
+.form-search .radio,
+.form-search .checkbox,
+.form-inline .radio,
+.form-inline .checkbox {
+  padding-left: 0;
+  margin-bottom: 0;
+  vertical-align: middle;
+}
+
+.form-search .radio input[type="radio"],
+.form-search .checkbox input[type="checkbox"],
+.form-inline .radio input[type="radio"],
+.form-inline .checkbox input[type="checkbox"] {
+  float: left;
+  margin-right: 3px;
+  margin-left: 0;
+}
+
+.control-group {
+  margin-bottom: 10px;
+}
+
+legend + .control-group {
+  margin-top: 20px;
+  -webkit-margin-top-collapse: separate;
+}
+
+.form-horizontal .control-group {
+  margin-bottom: 20px;
+  *zoom: 1;
+}
+
+.form-horizontal .control-group:before,
+.form-horizontal .control-group:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.form-horizontal .control-group:after {
+  clear: both;
+}
+
+.form-horizontal .control-label {
+  float: left;
+  width: 160px;
+  padding-top: 5px;
+  text-align: right;
+}
+
+.form-horizontal .controls {
+  *display: inline-block;
+  *padding-left: 20px;
+  margin-left: 180px;
+  *margin-left: 0;
+}
+
+.form-horizontal .controls:first-child {
+  *padding-left: 180px;
+}
+
+.form-horizontal .help-block {
+  margin-bottom: 0;
+}
+
+.form-horizontal input + .help-block,
+.form-horizontal select + .help-block,
+.form-horizontal textarea + .help-block,
+.form-horizontal .uneditable-input + .help-block,
+.form-horizontal .input-prepend + .help-block,
+.form-horizontal .input-append + .help-block {
+  margin-top: 10px;
+}
+
+.form-horizontal .form-actions {
+  padding-left: 180px;
+}
+
+table {
+  max-width: 100%;
+  background-color: transparent;
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+.table {
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.table th,
+.table td {
+  padding: 8px;
+  line-height: 20px;
+  text-align: left;
+  vertical-align: top;
+  border-top: 1px solid #dddddd;
+}
+
+.table th {
+  font-weight: bold;
+}
+
+.table thead th {
+  vertical-align: bottom;
+}
+
+.table caption + thead tr:first-child th,
+.table caption + thead tr:first-child td,
+.table colgroup + thead tr:first-child th,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child th,
+.table thead:first-child tr:first-child td {
+  border-top: 0;
+}
+
+.table tbody + tbody {
+  border-top: 2px solid #dddddd;
+}
+
+.table .table {
+  background-color: #ffffff;
+}
+
+.table-condensed th,
+.table-condensed td {
+  padding: 4px 5px;
+}
+
+.table-bordered {
+  border: 1px solid #dddddd;
+  border-collapse: separate;
+  *border-collapse: collapse;
+  border-left: 0;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+.table-bordered th,
+.table-bordered td {
+  border-left: 1px solid #dddddd;
+}
+
+.table-bordered caption + thead tr:first-child th,
+.table-bordered caption + tbody tr:first-child th,
+.table-bordered caption + tbody tr:first-child td,
+.table-bordered colgroup + thead tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child td,
+.table-bordered thead:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child td {
+  border-top: 0;
+}
+
+.table-bordered thead:first-child tr:first-child > th:first-child,
+.table-bordered tbody:first-child tr:first-child > td:first-child {
+  -webkit-border-top-left-radius: 4px;
+          border-top-left-radius: 4px;
+  -moz-border-radius-topleft: 4px;
+}
+
+.table-bordered thead:first-child tr:first-child > th:last-child,
+.table-bordered tbody:first-child tr:first-child > td:last-child {
+  -webkit-border-top-right-radius: 4px;
+          border-top-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+}
+
+.table-bordered thead:last-child tr:last-child > th:first-child,
+.table-bordered tbody:last-child tr:last-child > td:first-child,
+.table-bordered tfoot:last-child tr:last-child > td:first-child {
+  -webkit-border-bottom-left-radius: 4px;
+          border-bottom-left-radius: 4px;
+  -moz-border-radius-bottomleft: 4px;
+}
+
+.table-bordered thead:last-child tr:last-child > th:last-child,
+.table-bordered tbody:last-child tr:last-child > td:last-child,
+.table-bordered tfoot:last-child tr:last-child > td:last-child {
+  -webkit-border-bottom-right-radius: 4px;
+          border-bottom-right-radius: 4px;
+  -moz-border-radius-bottomright: 4px;
+}
+
+.table-bordered tfoot + tbody:last-child tr:last-child td:first-child {
+  -webkit-border-bottom-left-radius: 0;
+          border-bottom-left-radius: 0;
+  -moz-border-radius-bottomleft: 0;
+}
+
+.table-bordered tfoot + tbody:last-child tr:last-child td:last-child {
+  -webkit-border-bottom-right-radius: 0;
+          border-bottom-right-radius: 0;
+  -moz-border-radius-bottomright: 0;
+}
+
+.table-bordered caption + thead tr:first-child th:first-child,
+.table-bordered caption + tbody tr:first-child td:first-child,
+.table-bordered colgroup + thead tr:first-child th:first-child,
+.table-bordered colgroup + tbody tr:first-child td:first-child {
+  -webkit-border-top-left-radius: 4px;
+          border-top-left-radius: 4px;
+  -moz-border-radius-topleft: 4px;
+}
+
+.table-bordered caption + thead tr:first-child th:last-child,
+.table-bordered caption + tbody tr:first-child td:last-child,
+.table-bordered colgroup + thead tr:first-child th:last-child,
+.table-bordered colgroup + tbody tr:first-child td:last-child {
+  -webkit-border-top-right-radius: 4px;
+          border-top-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+}
+
+.table-striped tbody > tr:nth-child(odd) > td,
+.table-striped tbody > tr:nth-child(odd) > th {
+  background-color: #f9f9f9;
+}
+
+.table-hover tbody tr:hover td,
+.table-hover tbody tr:hover th {
+  background-color: #f5f5f5;
+}
+
+table td[class*="span"],
+table th[class*="span"],
+.row-fluid table td[class*="span"],
+.row-fluid table th[class*="span"] {
+  display: table-cell;
+  float: none;
+  margin-left: 0;
+}
+
+.table td.span1,
+.table th.span1 {
+  float: none;
+  width: 44px;
+  margin-left: 0;
+}
+
+.table td.span2,
+.table th.span2 {
+  float: none;
+  width: 124px;
+  margin-left: 0;
+}
+
+.table td.span3,
+.table th.span3 {
+  float: none;
+  width: 204px;
+  margin-left: 0;
+}
+
+.table td.span4,
+.table th.span4 {
+  float: none;
+  width: 284px;
+  margin-left: 0;
+}
+
+.table td.span5,
+.table th.span5 {
+  float: none;
+  width: 364px;
+  margin-left: 0;
+}
+
+.table td.span6,
+.table th.span6 {
+  float: none;
+  width: 444px;
+  margin-left: 0;
+}
+
+.table td.span7,
+.table th.span7 {
+  float: none;
+  width: 524px;
+  margin-left: 0;
+}
+
+.table td.span8,
+.table th.span8 {
+  float: none;
+  width: 604px;
+  margin-left: 0;
+}
+
+.table td.span9,
+.table th.span9 {
+  float: none;
+  width: 684px;
+  margin-left: 0;
+}
+
+.table td.span10,
+.table th.span10 {
+  float: none;
+  width: 764px;
+  margin-left: 0;
+}
+
+.table td.span11,
+.table th.span11 {
+  float: none;
+  width: 844px;
+  margin-left: 0;
+}
+
+.table td.span12,
+.table th.span12 {
+  float: none;
+  width: 924px;
+  margin-left: 0;
+}
+
+.table tbody tr.success td {
+  background-color: #dff0d8;
+}
+
+.table tbody tr.error td {
+  background-color: #f2dede;
+}
+
+.table tbody tr.warning td {
+  background-color: #fcf8e3;
+}
+
+.table tbody tr.info td {
+  background-color: #d9edf7;
+}
+
+.table-hover tbody tr.success:hover td {
+  background-color: #d0e9c6;
+}
+
+.table-hover tbody tr.error:hover td {
+  background-color: #ebcccc;
+}
+
+.table-hover tbody tr.warning:hover td {
+  background-color: #faf2cc;
+}
+
+.table-hover tbody tr.info:hover td {
+  background-color: #c4e3f3;
+}
+
+[class^="icon-"],
+[class*=" icon-"] {
+  display: inline-block;
+  width: 14px;
+  height: 14px;
+  margin-top: 1px;
+  *margin-right: .3em;
+  line-height: 14px;
+  vertical-align: text-top;
+  background-image: url("../img/glyphicons-halflings.png");
+  background-position: 14px 14px;
+  background-repeat: no-repeat;
+}
+
+/* White icons with optional class, or on hover/active states of certain elements */
+
+.icon-white,
+.nav-pills > .active > a > [class^="icon-"],
+.nav-pills > .active > a > [class*=" icon-"],
+.nav-list > .active > a > [class^="icon-"],
+.nav-list > .active > a > [class*=" icon-"],
+.navbar-inverse .nav > .active > a > [class^="icon-"],
+.navbar-inverse .nav > .active > a > [class*=" icon-"],
+.dropdown-menu > li > a:hover > [class^="icon-"],
+.dropdown-menu > li > a:hover > [class*=" icon-"],
+.dropdown-menu > .active > a > [class^="icon-"],
+.dropdown-menu > .active > a > [class*=" icon-"],
+.dropdown-submenu:hover > a > [class^="icon-"],
+.dropdown-submenu:hover > a > [class*=" icon-"] {
+  background-image: url("../img/glyphicons-halflings-white.png");
+}
+
+.icon-glass {
+  background-position: 0      0;
+}
+
+.icon-music {
+  background-position: -24px 0;
+}
+
+.icon-search {
+  background-position: -48px 0;
+}
+
+.icon-envelope {
+  background-position: -72px 0;
+}
+
+.icon-heart {
+  background-position: -96px 0;
+}
+
+.icon-star {
+  background-position: -120px 0;
+}
+
+.icon-star-empty {
+  background-position: -144px 0;
+}
+
+.icon-user {
+  background-position: -168px 0;
+}
+
+.icon-film {
+  background-position: -192px 0;
+}
+
+.icon-th-large {
+  background-position: -216px 0;
+}
+
+.icon-th {
+  background-position: -240px 0;
+}
+
+.icon-th-list {
+  background-position: -264px 0;
+}
+
+.icon-ok {
+  background-position: -288px 0;
+}
+
+.icon-remove {
+  background-position: -312px 0;
+}
+
+.icon-zoom-in {
+  background-position: -336px 0;
+}
+
+.icon-zoom-out {
+  background-position: -360px 0;
+}
+
+.icon-off {
+  background-position: -384px 0;
+}
+
+.icon-signal {
+  background-position: -408px 0;
+}
+
+.icon-cog {
+  background-position: -432px 0;
+}
+
+.icon-trash {
+  background-position: -456px 0;
+}
+
+.icon-home {
+  background-position: 0 -24px;
+}
+
+.icon-file {
+  background-position: -24px -24px;
+}
+
+.icon-time {
+  background-position: -48px -24px;
+}
+
+.icon-road {
+  background-position: -72px -24px;
+}
+
+.icon-download-alt {
+  background-position: -96px -24px;
+}
+
+.icon-download {
+  background-position: -120px -24px;
+}
+
+.icon-upload {
+  background-position: -144px -24px;
+}
+
+.icon-inbox {
+  background-position: -168px -24px;
+}
+
+.icon-play-circle {
+  background-position: -192px -24px;
+}
+
+.icon-repeat {
+  background-position: -216px -24px;
+}
+
+.icon-refresh {
+  background-position: -240px -24px;
+}
+
+.icon-list-alt {
+  background-position: -264px -24px;
+}
+
+.icon-lock {
+  background-position: -287px -24px;
+}
+
+.icon-flag {
+  background-position: -312px -24px;
+}
+
+.icon-headphones {
+  background-position: -336px -24px;
+}
+
+.icon-volume-off {
+  background-position: -360px -24px;
+}
+
+.icon-volume-down {
+  background-position: -384px -24px;
+}
+
+.icon-volume-up {
+  background-position: -408px -24px;
+}
+
+.icon-qrcode {
+  background-position: -432px -24px;
+}
+
+.icon-barcode {
+  background-position: -456px -24px;
+}
+
+.icon-tag {
+  background-position: 0 -48px;
+}
+
+.icon-tags {
+  background-position: -25px -48px;
+}
+
+.icon-book {
+  background-position: -48px -48px;
+}
+
+.icon-bookmark {
+  background-position: -72px -48px;
+}
+
+.icon-print {
+  background-position: -96px -48px;
+}
+
+.icon-camera {
+  background-position: -120px -48px;
+}
+
+.icon-font {
+  background-position: -144px -48px;
+}
+
+.icon-bold {
+  background-position: -167px -48px;
+}
+
+.icon-italic {
+  background-position: -192px -48px;
+}
+
+.icon-text-height {
+  background-position: -216px -48px;
+}
+
+.icon-text-width {
+  background-position: -240px -48px;
+}
+
+.icon-align-left {
+  background-position: -264px -48px;
+}
+
+.icon-align-center {
+  background-position: -288px -48px;
+}
+
+.icon-align-right {
+  background-position: -312px -48px;
+}
+
+.icon-align-justify {
+  background-position: -336px -48px;
+}
+
+.icon-list {
+  background-position: -360px -48px;
+}
+
+.icon-indent-left {
+  background-position: -384px -48px;
+}
+
+.icon-indent-right {
+  background-position: -408px -48px;
+}
+
+.icon-facetime-video {
+  background-position: -432px -48px;
+}
+
+.icon-picture {
+  background-position: -456px -48px;
+}
+
+.icon-pencil {
+  background-position: 0 -72px;
+}
+
+.icon-map-marker {
+  background-position: -24px -72px;
+}
+
+.icon-adjust {
+  background-position: -48px -72px;
+}
+
+.icon-tint {
+  background-position: -72px -72px;
+}
+
+.icon-edit {
+  background-position: -96px -72px;
+}
+
+.icon-share {
+  background-position: -120px -72px;
+}
+
+.icon-check {
+  background-position: -144px -72px;
+}
+
+.icon-move {
+  background-position: -168px -72px;
+}
+
+.icon-step-backward {
+  background-position: -192px -72px;
+}
+
+.icon-fast-backward {
+  background-position: -216px -72px;
+}
+
+.icon-backward {
+  background-position: -240px -72px;
+}
+
+.icon-play {
+  background-position: -264px -72px;
+}
+
+.icon-pause {
+  background-position: -288px -72px;
+}
+
+.icon-stop {
+  background-position: -312px -72px;
+}
+
+.icon-forward {
+  background-position: -336px -72px;
+}
+
+.icon-fast-forward {
+  background-position: -360px -72px;
+}
+
+.icon-step-forward {
+  background-position: -384px -72px;
+}
+
+.icon-eject {
+  background-position: -408px -72px;
+}
+
+.icon-chevron-left {
+  background-position: -432px -72px;
+}
+
+.icon-chevron-right {
+  background-position: -456px -72px;
+}
+
+.icon-plus-sign {
+  background-position: 0 -96px;
+}
+
+.icon-minus-sign {
+  background-position: -24px -96px;
+}
+
+.icon-remove-sign {
+  background-position: -48px -96px;
+}
+
+.icon-ok-sign {
+  background-position: -72px -96px;
+}
+
+.icon-question-sign {
+  background-position: -96px -96px;
+}
+
+.icon-info-sign {
+  background-position: -120px -96px;
+}
+
+.icon-screenshot {
+  background-position: -144px -96px;
+}
+
+.icon-remove-circle {
+  background-position: -168px -96px;
+}
+
+.icon-ok-circle {
+  background-position: -192px -96px;
+}
+
+.icon-ban-circle {
+  background-position: -216px -96px;
+}
+
+.icon-arrow-left {
+  background-position: -240px -96px;
+}
+
+.icon-arrow-right {
+  background-position: -264px -96px;
+}
+
+.icon-arrow-up {
+  background-position: -289px -96px;
+}
+
+.icon-arrow-down {
+  background-position: -312px -96px;
+}
+
+.icon-share-alt {
+  background-position: -336px -96px;
+}
+
+.icon-resize-full {
+  background-position: -360px -96px;
+}
+
+.icon-resize-small {
+  background-position: -384px -96px;
+}
+
+.icon-plus {
+  background-position: -408px -96px;
+}
+
+.icon-minus {
+  background-position: -433px -96px;
+}
+
+.icon-asterisk {
+  background-position: -456px -96px;
+}
+
+.icon-exclamation-sign {
+  background-position: 0 -120px;
+}
+
+.icon-gift {
+  background-position: -24px -120px;
+}
+
+.icon-leaf {
+  background-position: -48px -120px;
+}
+
+.icon-fire {
+  background-position: -72px -120px;
+}
+
+.icon-eye-open {
+  background-position: -96px -120px;
+}
+
+.icon-eye-close {
+  background-position: -120px -120px;
+}
+
+.icon-warning-sign {
+  background-position: -144px -120px;
+}
+
+.icon-plane {
+  background-position: -168px -120px;
+}
+
+.icon-calendar {
+  background-position: -192px -120px;
+}
+
+.icon-random {
+  width: 16px;
+  background-position: -216px -120px;
+}
+
+.icon-comment {
+  background-position: -240px -120px;
+}
+
+.icon-magnet {
+  background-position: -264px -120px;
+}
+
+.icon-chevron-up {
+  background-position: -288px -120px;
+}
+
+.icon-chevron-down {
+  background-position: -313px -119px;
+}
+
+.icon-retweet {
+  background-position: -336px -120px;
+}
+
+.icon-shopping-cart {
+  background-position: -360px -120px;
+}
+
+.icon-folder-close {
+  background-position: -384px -120px;
+}
+
+.icon-folder-open {
+  width: 16px;
+  background-position: -408px -120px;
+}
+
+.icon-resize-vertical {
+  background-position: -432px -119px;
+}
+
+.icon-resize-horizontal {
+  background-position: -456px -118px;
+}
+
+.icon-hdd {
+  background-position: 0 -144px;
+}
+
+.icon-bullhorn {
+  background-position: -24px -144px;
+}
+
+.icon-bell {
+  background-position: -48px -144px;
+}
+
+.icon-certificate {
+  background-position: -72px -144px;
+}
+
+.icon-thumbs-up {
+  background-position: -96px -144px;
+}
+
+.icon-thumbs-down {
+  background-position: -120px -144px;
+}
+
+.icon-hand-right {
+  background-position: -144px -144px;
+}
+
+.icon-hand-left {
+  background-position: -168px -144px;
+}
+
+.icon-hand-up {
+  background-position: -192px -144px;
+}
+
+.icon-hand-down {
+  background-position: -216px -144px;
+}
+
+.icon-circle-arrow-right {
+  background-position: -240px -144px;
+}
+
+.icon-circle-arrow-left {
+  background-position: -264px -144px;
+}
+
+.icon-circle-arrow-up {
+  background-position: -288px -144px;
+}
+
+.icon-circle-arrow-down {
+  background-position: -312px -144px;
+}
+
+.icon-globe {
+  background-position: -336px -144px;
+}
+
+.icon-wrench {
+  background-position: -360px -144px;
+}
+
+.icon-tasks {
+  background-position: -384px -144px;
+}
+
+.icon-filter {
+  background-position: -408px -144px;
+}
+
+.icon-briefcase {
+  background-position: -432px -144px;
+}
+
+.icon-fullscreen {
+  background-position: -456px -144px;
+}
+
+.dropup,
+.dropdown {
+  position: relative;
+}
+
+.dropdown-toggle {
+  *margin-bottom: -3px;
+}
+
+.dropdown-toggle:active,
+.open .dropdown-toggle {
+  outline: 0;
+}
+
+.caret {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  vertical-align: top;
+  border-top: 4px solid #000000;
+  border-right: 4px solid transparent;
+  border-left: 4px solid transparent;
+  content: "";
+}
+
+.dropdown .caret {
+  margin-top: 8px;
+  margin-left: 2px;
+}
+
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  display: none;
+  float: left;
+  min-width: 160px;
+  padding: 5px 0;
+  margin: 2px 0 0;
+  list-style: none;
+  background-color: #ffffff;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  *border-right-width: 2px;
+  *border-bottom-width: 2px;
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+     -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+          box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -webkit-background-clip: padding-box;
+     -moz-background-clip: padding;
+          background-clip: padding-box;
+}
+
+.dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+
+.dropdown-menu .divider {
+  *width: 100%;
+  height: 1px;
+  margin: 9px 1px;
+  *margin: -5px 0 5px;
+  overflow: hidden;
+  background-color: #e5e5e5;
+  border-bottom: 1px solid #ffffff;
+}
+
+.dropdown-menu li > a {
+  display: block;
+  padding: 3px 20px;
+  clear: both;
+  font-weight: normal;
+  line-height: 20px;
+  color: #333333;
+  white-space: nowrap;
+}
+
+.dropdown-menu li > a:hover,
+.dropdown-menu li > a:focus,
+.dropdown-submenu:hover > a {
+  color: #ffffff;
+  text-decoration: none;
+  background-color: #0081c2;
+  background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+  background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+  background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+
+.dropdown-menu .active > a,
+.dropdown-menu .active > a:hover {
+  color: #ffffff;
+  text-decoration: none;
+  background-color: #0081c2;
+  background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+  background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+  background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+  background-repeat: repeat-x;
+  outline: 0;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+
+.dropdown-menu .disabled > a,
+.dropdown-menu .disabled > a:hover {
+  color: #999999;
+}
+
+.dropdown-menu .disabled > a:hover {
+  text-decoration: none;
+  cursor: default;
+  background-color: transparent;
+  background-image: none;
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.open {
+  *z-index: 1000;
+}
+
+.open > .dropdown-menu {
+  display: block;
+}
+
+.pull-right > .dropdown-menu {
+  right: 0;
+  left: auto;
+}
+
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+  border-top: 0;
+  border-bottom: 4px solid #000000;
+  content: "";
+}
+
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+  top: auto;
+  bottom: 100%;
+  margin-bottom: 1px;
+}
+
+.dropdown-submenu {
+  position: relative;
+}
+
+.dropdown-submenu > .dropdown-menu {
+  top: 0;
+  left: 100%;
+  margin-top: -6px;
+  margin-left: -1px;
+  -webkit-border-radius: 0 6px 6px 6px;
+     -moz-border-radius: 0 6px 6px 6px;
+          border-radius: 0 6px 6px 6px;
+}
+
+.dropdown-submenu:hover > .dropdown-menu {
+  display: block;
+}
+
+.dropup .dropdown-submenu > .dropdown-menu {
+  top: auto;
+  bottom: 0;
+  margin-top: 0;
+  margin-bottom: -2px;
+  -webkit-border-radius: 5px 5px 5px 0;
+     -moz-border-radius: 5px 5px 5px 0;
+          border-radius: 5px 5px 5px 0;
+}
+
+.dropdown-submenu > a:after {
+  display: block;
+  float: right;
+  width: 0;
+  height: 0;
+  margin-top: 5px;
+  margin-right: -10px;
+  border-color: transparent;
+  border-left-color: #cccccc;
+  border-style: solid;
+  border-width: 5px 0 5px 5px;
+  content: " ";
+}
+
+.dropdown-submenu:hover > a:after {
+  border-left-color: #ffffff;
+}
+
+.dropdown-submenu.pull-left {
+  float: none;
+}
+
+.dropdown-submenu.pull-left > .dropdown-menu {
+  left: -100%;
+  margin-left: 10px;
+  -webkit-border-radius: 6px 0 6px 6px;
+     -moz-border-radius: 6px 0 6px 6px;
+          border-radius: 6px 0 6px 6px;
+}
+
+.dropdown .dropdown-menu .nav-header {
+  padding-right: 20px;
+  padding-left: 20px;
+}
+
+.typeahead {
+  z-index: 1051;
+  margin-top: 2px;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+.well {
+  max-width: 500px;
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.well blockquote {
+  border-color: #ddd;
+  border-color: rgba(0, 0, 0, 0.15);
+}
+
+.well-large {
+  padding: 24px;
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+}
+
+.well-small {
+  padding: 9px;
+  -webkit-border-radius: 3px;
+     -moz-border-radius: 3px;
+          border-radius: 3px;
+}
+
+.fade {
+  opacity: 0;
+  -webkit-transition: opacity 0.15s linear;
+     -moz-transition: opacity 0.15s linear;
+       -o-transition: opacity 0.15s linear;
+          transition: opacity 0.15s linear;
+}
+
+.fade.in {
+  opacity: 1;
+}
+
+.collapse {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  -webkit-transition: height 0.35s ease;
+     -moz-transition: height 0.35s ease;
+       -o-transition: height 0.35s ease;
+          transition: height 0.35s ease;
+}
+
+.collapse.in {
+  height: auto;
+}
+
+.close {
+  float: right;
+  font-size: 20px;
+  font-weight: bold;
+  line-height: 20px;
+  color: #000000;
+  text-shadow: 0 1px 0 #ffffff;
+  opacity: 0.2;
+  filter: alpha(opacity=20);
+}
+
+.close:hover {
+  color: #000000;
+  text-decoration: none;
+  cursor: pointer;
+  opacity: 0.4;
+  filter: alpha(opacity=40);
+}
+
+button.close {
+  padding: 0;
+  cursor: pointer;
+  background: transparent;
+  border: 0;
+  -webkit-appearance: none;
+}
+
+.btn {
+  display: inline-block;
+  *display: inline;
+  padding: 4px 12px;
+  margin-bottom: 0;
+  *margin-left: .3em;
+  font-size: 14px;
+  line-height: 20px;
+  color: #333333;
+  text-align: center;
+  text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+  vertical-align: middle;
+  cursor: pointer;
+  background-color: #f5f5f5;
+  *background-color: #e6e6e6;
+  background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+  background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
+  background-repeat: repeat-x;
+  border: 1px solid #bbbbbb;
+  *border: 0;
+  border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  border-bottom-color: #a2a2a2;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  *zoom: 1;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+     -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn:hover,
+.btn:active,
+.btn.active,
+.btn.disabled,
+.btn[disabled] {
+  color: #333333;
+  background-color: #e6e6e6;
+  *background-color: #d9d9d9;
+}
+
+.btn:active,
+.btn.active {
+  background-color: #cccccc \9;
+}
+
+.btn:first-child {
+  *margin-left: 0;
+}
+
+.btn:hover {
+  color: #333333;
+  text-decoration: none;
+  background-position: 0 -15px;
+  -webkit-transition: background-position 0.1s linear;
+     -moz-transition: background-position 0.1s linear;
+       -o-transition: background-position 0.1s linear;
+          transition: background-position 0.1s linear;
+}
+
+.btn:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+.btn.active,
+.btn:active {
+  background-image: none;
+  outline: 0;
+  -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+     -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn.disabled,
+.btn[disabled] {
+  cursor: default;
+  background-image: none;
+  opacity: 0.65;
+  filter: alpha(opacity=65);
+  -webkit-box-shadow: none;
+     -moz-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn-large {
+  padding: 11px 19px;
+  font-size: 17.5px;
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+}
+
+.btn-large [class^="icon-"],
+.btn-large [class*=" icon-"] {
+  margin-top: 4px;
+}
+
+.btn-small {
+  padding: 2px 10px;
+  font-size: 11.9px;
+  -webkit-border-radius: 3px;
+     -moz-border-radius: 3px;
+          border-radius: 3px;
+}
+
+.btn-small [class^="icon-"],
+.btn-small [class*=" icon-"] {
+  margin-top: 0;
+}
+
+.btn-mini [class^="icon-"],
+.btn-mini [class*=" icon-"] {
+  margin-top: -1px;
+}
+
+.btn-mini {
+  padding: 0 6px;
+  font-size: 10.5px;
+  -webkit-border-radius: 3px;
+     -moz-border-radius: 3px;
+          border-radius: 3px;
+}
+
+.btn-block {
+  display: block;
+  width: 100%;
+  padding-right: 0;
+  padding-left: 0;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+.btn-block + .btn-block {
+  margin-top: 5px;
+}
+
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+  width: 100%;
+}
+
+.btn-primary.active,
+.btn-warning.active,
+.btn-danger.active,
+.btn-success.active,
+.btn-info.active,
+.btn-inverse.active {
+  color: rgba(255, 255, 255, 0.75);
+}
+
+.btn {
+  border-color: #c5c5c5;
+  border-color: rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);
+}
+
+.btn-primary {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #006dcc;
+  *background-color: #0044cc;
+  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+  background-image: linear-gradient(to bottom, #0088cc, #0044cc);
+  background-repeat: repeat-x;
+  border-color: #0044cc #0044cc #002a80;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-primary:hover,
+.btn-primary:active,
+.btn-primary.active,
+.btn-primary.disabled,
+.btn-primary[disabled] {
+  color: #ffffff;
+  background-color: #0044cc;
+  *background-color: #003bb3;
+}
+
+.btn-primary:active,
+.btn-primary.active {
+  background-color: #003399 \9;
+}
+
+.btn-warning {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #faa732;
+  *background-color: #f89406;
+  background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+  background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+  background-image: -o-linear-gradient(top, #fbb450, #f89406);
+  background-image: linear-gradient(to bottom, #fbb450, #f89406);
+  background-repeat: repeat-x;
+  border-color: #f89406 #f89406 #ad6704;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-warning:hover,
+.btn-warning:active,
+.btn-warning.active,
+.btn-warning.disabled,
+.btn-warning[disabled] {
+  color: #ffffff;
+  background-color: #f89406;
+  *background-color: #df8505;
+}
+
+.btn-warning:active,
+.btn-warning.active {
+  background-color: #c67605 \9;
+}
+
+.btn-danger {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #da4f49;
+  *background-color: #bd362f;
+  background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));
+  background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: linear-gradient(to bottom, #ee5f5b, #bd362f);
+  background-repeat: repeat-x;
+  border-color: #bd362f #bd362f #802420;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-danger:hover,
+.btn-danger:active,
+.btn-danger.active,
+.btn-danger.disabled,
+.btn-danger[disabled] {
+  color: #ffffff;
+  background-color: #bd362f;
+  *background-color: #a9302a;
+}
+
+.btn-danger:active,
+.btn-danger.active {
+  background-color: #942a25 \9;
+}
+
+.btn-success {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #5bb75b;
+  *background-color: #51a351;
+  background-image: -moz-linear-gradient(top, #62c462, #51a351);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));
+  background-image: -webkit-linear-gradient(top, #62c462, #51a351);
+  background-image: -o-linear-gradient(top, #62c462, #51a351);
+  background-image: linear-gradient(to bottom, #62c462, #51a351);
+  background-repeat: repeat-x;
+  border-color: #51a351 #51a351 #387038;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-success:hover,
+.btn-success:active,
+.btn-success.active,
+.btn-success.disabled,
+.btn-success[disabled] {
+  color: #ffffff;
+  background-color: #51a351;
+  *background-color: #499249;
+}
+
+.btn-success:active,
+.btn-success.active {
+  background-color: #408140 \9;
+}
+
+.btn-info {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #49afcd;
+  *background-color: #2f96b4;
+  background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));
+  background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: linear-gradient(to bottom, #5bc0de, #2f96b4);
+  background-repeat: repeat-x;
+  border-color: #2f96b4 #2f96b4 #1f6377;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-info:hover,
+.btn-info:active,
+.btn-info.active,
+.btn-info.disabled,
+.btn-info[disabled] {
+  color: #ffffff;
+  background-color: #2f96b4;
+  *background-color: #2a85a0;
+}
+
+.btn-info:active,
+.btn-info.active {
+  background-color: #24748c \9;
+}
+
+.btn-inverse {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #363636;
+  *background-color: #222222;
+  background-image: -moz-linear-gradient(top, #444444, #222222);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));
+  background-image: -webkit-linear-gradient(top, #444444, #222222);
+  background-image: -o-linear-gradient(top, #444444, #222222);
+  background-image: linear-gradient(to bottom, #444444, #222222);
+  background-repeat: repeat-x;
+  border-color: #222222 #222222 #000000;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-inverse:hover,
+.btn-inverse:active,
+.btn-inverse.active,
+.btn-inverse.disabled,
+.btn-inverse[disabled] {
+  color: #ffffff;
+  background-color: #222222;
+  *background-color: #151515;
+}
+
+.btn-inverse:active,
+.btn-inverse.active {
+  background-color: #080808 \9;
+}
+
+button.btn,
+input[type="submit"].btn {
+  *padding-top: 3px;
+  *padding-bottom: 3px;
+}
+
+button.btn::-moz-focus-inner,
+input[type="submit"].btn::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+
+button.btn.btn-large,
+input[type="submit"].btn.btn-large {
+  *padding-top: 7px;
+  *padding-bottom: 7px;
+}
+
+button.btn.btn-small,
+input[type="submit"].btn.btn-small {
+  *padding-top: 3px;
+  *padding-bottom: 3px;
+}
+
+button.btn.btn-mini,
+input[type="submit"].btn.btn-mini {
+  *padding-top: 1px;
+  *padding-bottom: 1px;
+}
+
+.btn-link,
+.btn-link:active,
+.btn-link[disabled] {
+  background-color: transparent;
+  background-image: none;
+  -webkit-box-shadow: none;
+     -moz-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn-link {
+  color: #0088cc;
+  cursor: pointer;
+  border-color: transparent;
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.btn-link:hover {
+  color: #005580;
+  text-decoration: underline;
+  background-color: transparent;
+}
+
+.btn-link[disabled]:hover {
+  color: #333333;
+  text-decoration: none;
+}
+
+.btn-group {
+  position: relative;
+  display: inline-block;
+  *display: inline;
+  *margin-left: .3em;
+  font-size: 0;
+  white-space: nowrap;
+  vertical-align: middle;
+  *zoom: 1;
+}
+
+.btn-group:first-child {
+  *margin-left: 0;
+}
+
+.btn-group + .btn-group {
+  margin-left: 5px;
+}
+
+.btn-toolbar {
+  margin-top: 10px;
+  margin-bottom: 10px;
+  font-size: 0;
+}
+
+.btn-toolbar > .btn + .btn,
+.btn-toolbar > .btn-group + .btn,
+.btn-toolbar > .btn + .btn-group {
+  margin-left: 5px;
+}
+
+.btn-group > .btn {
+  position: relative;
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.btn-group > .btn + .btn {
+  margin-left: -1px;
+}
+
+.btn-group > .btn,
+.btn-group > .dropdown-menu,
+.btn-group > .popover {
+  font-size: 14px;
+}
+
+.btn-group > .btn-mini {
+  font-size: 10.5px;
+}
+
+.btn-group > .btn-small {
+  font-size: 11.9px;
+}
+
+.btn-group > .btn-large {
+  font-size: 17.5px;
+}
+
+.btn-group > .btn:first-child {
+  margin-left: 0;
+  -webkit-border-bottom-left-radius: 4px;
+          border-bottom-left-radius: 4px;
+  -webkit-border-top-left-radius: 4px;
+          border-top-left-radius: 4px;
+  -moz-border-radius-bottomleft: 4px;
+  -moz-border-radius-topleft: 4px;
+}
+
+.btn-group > .btn:last-child,
+.btn-group > .dropdown-toggle {
+  -webkit-border-top-right-radius: 4px;
+          border-top-right-radius: 4px;
+  -webkit-border-bottom-right-radius: 4px;
+          border-bottom-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+  -moz-border-radius-bottomright: 4px;
+}
+
+.btn-group > .btn.large:first-child {
+  margin-left: 0;
+  -webkit-border-bottom-left-radius: 6px;
+          border-bottom-left-radius: 6px;
+  -webkit-border-top-left-radius: 6px;
+          border-top-left-radius: 6px;
+  -moz-border-radius-bottomleft: 6px;
+  -moz-border-radius-topleft: 6px;
+}
+
+.btn-group > .btn.large:last-child,
+.btn-group > .large.dropdown-toggle {
+  -webkit-border-top-right-radius: 6px;
+          border-top-right-radius: 6px;
+  -webkit-border-bottom-right-radius: 6px;
+          border-bottom-right-radius: 6px;
+  -moz-border-radius-topright: 6px;
+  -moz-border-radius-bottomright: 6px;
+}
+
+.btn-group > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group > .btn:active,
+.btn-group > .btn.active {
+  z-index: 2;
+}
+
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+  outline: 0;
+}
+
+.btn-group > .btn + .dropdown-toggle {
+  *padding-top: 5px;
+  padding-right: 8px;
+  *padding-bottom: 5px;
+  padding-left: 8px;
+  -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+     -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn-group > .btn-mini + .dropdown-toggle {
+  *padding-top: 2px;
+  padding-right: 5px;
+  *padding-bottom: 2px;
+  padding-left: 5px;
+}
+
+.btn-group > .btn-small + .dropdown-toggle {
+  *padding-top: 5px;
+  *padding-bottom: 4px;
+}
+
+.btn-group > .btn-large + .dropdown-toggle {
+  *padding-top: 7px;
+  padding-right: 12px;
+  *padding-bottom: 7px;
+  padding-left: 12px;
+}
+
+.btn-group.open .dropdown-toggle {
+  background-image: none;
+  -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+     -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn-group.open .btn.dropdown-toggle {
+  background-color: #e6e6e6;
+}
+
+.btn-group.open .btn-primary.dropdown-toggle {
+  background-color: #0044cc;
+}
+
+.btn-group.open .btn-warning.dropdown-toggle {
+  background-color: #f89406;
+}
+
+.btn-group.open .btn-danger.dropdown-toggle {
+  background-color: #bd362f;
+}
+
+.btn-group.open .btn-success.dropdown-toggle {
+  background-color: #51a351;
+}
+
+.btn-group.open .btn-info.dropdown-toggle {
+  background-color: #2f96b4;
+}
+
+.btn-group.open .btn-inverse.dropdown-toggle {
+  background-color: #222222;
+}
+
+.btn .caret {
+  margin-top: 8px;
+  margin-left: 0;
+}
+
+.btn-mini .caret,
+.btn-small .caret,
+.btn-large .caret {
+  margin-top: 6px;
+}
+
+.btn-large .caret {
+  border-top-width: 5px;
+  border-right-width: 5px;
+  border-left-width: 5px;
+}
+
+.dropup .btn-large .caret {
+  border-bottom-width: 5px;
+}
+
+.btn-primary .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret,
+.btn-success .caret,
+.btn-inverse .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.btn-group-vertical {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+}
+
+.btn-group-vertical > .btn {
+  display: block;
+  float: none;
+  max-width: 100%;
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.btn-group-vertical > .btn + .btn {
+  margin-top: -1px;
+  margin-left: 0;
+}
+
+.btn-group-vertical > .btn:first-child {
+  -webkit-border-radius: 4px 4px 0 0;
+     -moz-border-radius: 4px 4px 0 0;
+          border-radius: 4px 4px 0 0;
+}
+
+.btn-group-vertical > .btn:last-child {
+  -webkit-border-radius: 0 0 4px 4px;
+     -moz-border-radius: 0 0 4px 4px;
+          border-radius: 0 0 4px 4px;
+}
+
+.btn-group-vertical > .btn-large:first-child {
+  -webkit-border-radius: 6px 6px 0 0;
+     -moz-border-radius: 6px 6px 0 0;
+          border-radius: 6px 6px 0 0;
+}
+
+.btn-group-vertical > .btn-large:last-child {
+  -webkit-border-radius: 0 0 6px 6px;
+     -moz-border-radius: 0 0 6px 6px;
+          border-radius: 0 0 6px 6px;
+}
+
+.alert {
+  padding: 8px 35px 8px 14px;
+  margin-bottom: 20px;
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+  background-color: #fcf8e3;
+  border: 1px solid #fbeed5;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+.alert,
+.alert h4 {
+  color: #c09853;
+}
+
+.alert h4 {
+  margin: 0;
+}
+
+.alert .close {
+  position: relative;
+  top: -2px;
+  right: -21px;
+  line-height: 20px;
+}
+
+.alert-success {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+
+.alert-success h4 {
+  color: #468847;
+}
+
+.alert-danger,
+.alert-error {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #eed3d7;
+}
+
+.alert-danger h4,
+.alert-error h4 {
+  color: #b94a48;
+}
+
+.alert-info {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+
+.alert-info h4 {
+  color: #3a87ad;
+}
+
+.alert-block {
+  padding-top: 14px;
+  padding-bottom: 14px;
+}
+
+.alert-block > p,
+.alert-block > ul {
+  margin-bottom: 0;
+}
+
+.alert-block p + p {
+  margin-top: 5px;
+}
+
+.nav {
+  margin-bottom: 20px;
+  margin-left: 0;
+  list-style: none;
+}
+
+.nav > li > a {
+  display: block;
+}
+
+.nav > li > a:hover {
+  text-decoration: none;
+  background-color: #eeeeee;
+}
+
+.nav > li > a > img {
+  max-width: none;
+}
+
+.nav > .pull-right {
+  float: right;
+}
+
+.nav-header {
+  display: block;
+  padding: 3px 15px;
+  font-size: 11px;
+  font-weight: bold;
+  line-height: 20px;
+  color: #999999;
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+  text-transform: uppercase;
+}
+
+.nav li + .nav-header {
+  margin-top: 9px;
+}
+
+.nav-list {
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-bottom: 0;
+}
+
+.nav-list > li > a,
+.nav-list .nav-header {
+  margin-right: -15px;
+  margin-left: -15px;
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+}
+
+.nav-list > li > a {
+  padding: 3px 15px;
+}
+
+.nav-list > .active > a,
+.nav-list > .active > a:hover {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+  background-color: #0088cc;
+}
+
+.nav-list [class^="icon-"],
+.nav-list [class*=" icon-"] {
+  margin-right: 2px;
+}
+
+.nav-list .divider {
+  *width: 100%;
+  height: 1px;
+  margin: 9px 1px;
+  *margin: -5px 0 5px;
+  overflow: hidden;
+  background-color: #e5e5e5;
+  border-bottom: 1px solid #ffffff;
+}
+
+.nav-tabs,
+.nav-pills {
+  *zoom: 1;
+}
+
+.nav-tabs:before,
+.nav-pills:before,
+.nav-tabs:after,
+.nav-pills:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.nav-tabs:after,
+.nav-pills:after {
+  clear: both;
+}
+
+.nav-tabs > li,
+.nav-pills > li {
+  float: left;
+}
+
+.nav-tabs > li > a,
+.nav-pills > li > a {
+  padding-right: 12px;
+  padding-left: 12px;
+  margin-right: 2px;
+  line-height: 14px;
+}
+
+.nav-tabs {
+  border-bottom: 1px solid #ddd;
+}
+
+.nav-tabs > li {
+  margin-bottom: -1px;
+}
+
+.nav-tabs > li > a {
+  padding-top: 8px;
+  padding-bottom: 8px;
+  line-height: 20px;
+  border: 1px solid transparent;
+  -webkit-border-radius: 4px 4px 0 0;
+     -moz-border-radius: 4px 4px 0 0;
+          border-radius: 4px 4px 0 0;
+}
+
+.nav-tabs > li > a:hover {
+  border-color: #eeeeee #eeeeee #dddddd;
+}
+
+.nav-tabs > .active > a,
+.nav-tabs > .active > a:hover {
+  color: #555555;
+  cursor: default;
+  background-color: #ffffff;
+  border: 1px solid #ddd;
+  border-bottom-color: transparent;
+}
+
+.nav-pills > li > a {
+  padding-top: 8px;
+  padding-bottom: 8px;
+  margin-top: 2px;
+  margin-bottom: 2px;
+  -webkit-border-radius: 5px;
+     -moz-border-radius: 5px;
+          border-radius: 5px;
+}
+
+.nav-pills > .active > a,
+.nav-pills > .active > a:hover {
+  color: #ffffff;
+  background-color: #0088cc;
+}
+
+.nav-stacked > li {
+  float: none;
+}
+
+.nav-stacked > li > a {
+  margin-right: 0;
+}
+
+.nav-tabs.nav-stacked {
+  border-bottom: 0;
+}
+
+.nav-tabs.nav-stacked > li > a {
+  border: 1px solid #ddd;
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.nav-tabs.nav-stacked > li:first-child > a {
+  -webkit-border-top-right-radius: 4px;
+          border-top-right-radius: 4px;
+  -webkit-border-top-left-radius: 4px;
+          border-top-left-radius: 4px;
+  -moz-border-radius-topright: 4px;
+  -moz-border-radius-topleft: 4px;
+}
+
+.nav-tabs.nav-stacked > li:last-child > a {
+  -webkit-border-bottom-right-radius: 4px;
+          border-bottom-right-radius: 4px;
+  -webkit-border-bottom-left-radius: 4px;
+          border-bottom-left-radius: 4px;
+  -moz-border-radius-bottomright: 4px;
+  -moz-border-radius-bottomleft: 4px;
+}
+
+.nav-tabs.nav-stacked > li > a:hover {
+  z-index: 2;
+  border-color: #ddd;
+}
+
+.nav-pills.nav-stacked > li > a {
+  margin-bottom: 3px;
+}
+
+.nav-pills.nav-stacked > li:last-child > a {
+  margin-bottom: 1px;
+}
+
+.nav-tabs .dropdown-menu {
+  -webkit-border-radius: 0 0 6px 6px;
+     -moz-border-radius: 0 0 6px 6px;
+          border-radius: 0 0 6px 6px;
+}
+
+.nav-pills .dropdown-menu {
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+}
+
+.nav .dropdown-toggle .caret {
+  margin-top: 6px;
+  border-top-color: #0088cc;
+  border-bottom-color: #0088cc;
+}
+
+.nav .dropdown-toggle:hover .caret {
+  border-top-color: #005580;
+  border-bottom-color: #005580;
+}
+
+/* move down carets for tabs */
+
+.nav-tabs .dropdown-toggle .caret {
+  margin-top: 8px;
+}
+
+.nav .active .dropdown-toggle .caret {
+  border-top-color: #fff;
+  border-bottom-color: #fff;
+}
+
+.nav-tabs .active .dropdown-toggle .caret {
+  border-top-color: #555555;
+  border-bottom-color: #555555;
+}
+
+.nav > .dropdown.active > a:hover {
+  cursor: pointer;
+}
+
+.nav-tabs .open .dropdown-toggle,
+.nav-pills .open .dropdown-toggle,
+.nav > li.dropdown.open.active > a:hover {
+  color: #ffffff;
+  background-color: #999999;
+  border-color: #999999;
+}
+
+.nav li.dropdown.open .caret,
+.nav li.dropdown.open.active .caret,
+.nav li.dropdown.open a:hover .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+  opacity: 1;
+  filter: alpha(opacity=100);
+}
+
+.tabs-stacked .open > a:hover {
+  border-color: #999999;
+}
+
+.tabbable {
+  *zoom: 1;
+}
+
+.tabbable:before,
+.tabbable:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.tabbable:after {
+  clear: both;
+}
+
+.tab-content {
+  overflow: auto;
+}
+
+.tabs-below > .nav-tabs,
+.tabs-right > .nav-tabs,
+.tabs-left > .nav-tabs {
+  border-bottom: 0;
+}
+
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+  display: none;
+}
+
+.tab-content > .active,
+.pill-content > .active {
+  display: block;
+}
+
+.tabs-below > .nav-tabs {
+  border-top: 1px solid #ddd;
+}
+
+.tabs-below > .nav-tabs > li {
+  margin-top: -1px;
+  margin-bottom: 0;
+}
+
+.tabs-below > .nav-tabs > li > a {
+  -webkit-border-radius: 0 0 4px 4px;
+     -moz-border-radius: 0 0 4px 4px;
+          border-radius: 0 0 4px 4px;
+}
+
+.tabs-below > .nav-tabs > li > a:hover {
+  border-top-color: #ddd;
+  border-bottom-color: transparent;
+}
+
+.tabs-below > .nav-tabs > .active > a,
+.tabs-below > .nav-tabs > .active > a:hover {
+  border-color: transparent #ddd #ddd #ddd;
+}
+
+.tabs-left > .nav-tabs > li,
+.tabs-right > .nav-tabs > li {
+  float: none;
+}
+
+.tabs-left > .nav-tabs > li > a,
+.tabs-right > .nav-tabs > li > a {
+  min-width: 74px;
+  margin-right: 0;
+  margin-bottom: 3px;
+}
+
+.tabs-left > .nav-tabs {
+  float: left;
+  margin-right: 19px;
+  border-right: 1px solid #ddd;
+}
+
+.tabs-left > .nav-tabs > li > a {
+  margin-right: -1px;
+  -webkit-border-radius: 4px 0 0 4px;
+     -moz-border-radius: 4px 0 0 4px;
+          border-radius: 4px 0 0 4px;
+}
+
+.tabs-left > .nav-tabs > li > a:hover {
+  border-color: #eeeeee #dddddd #eeeeee #eeeeee;
+}
+
+.tabs-left > .nav-tabs .active > a,
+.tabs-left > .nav-tabs .active > a:hover {
+  border-color: #ddd transparent #ddd #ddd;
+  *border-right-color: #ffffff;
+}
+
+.tabs-right > .nav-tabs {
+  float: right;
+  margin-left: 19px;
+  border-left: 1px solid #ddd;
+}
+
+.tabs-right > .nav-tabs > li > a {
+  margin-left: -1px;
+  -webkit-border-radius: 0 4px 4px 0;
+     -moz-border-radius: 0 4px 4px 0;
+          border-radius: 0 4px 4px 0;
+}
+
+.tabs-right > .nav-tabs > li > a:hover {
+  border-color: #eeeeee #eeeeee #eeeeee #dddddd;
+}
+
+.tabs-right > .nav-tabs .active > a,
+.tabs-right > .nav-tabs .active > a:hover {
+  border-color: #ddd #ddd #ddd transparent;
+  *border-left-color: #ffffff;
+}
+
+.nav > .disabled > a {
+  color: #999999;
+}
+
+.nav > .disabled > a:hover {
+  text-decoration: none;
+  cursor: default;
+  background-color: transparent;
+}
+
+.navbar {
+  *position: relative;
+  *z-index: 2;
+  margin-bottom: 20px;
+  overflow: visible;
+}
+
+.navbar-inner {
+  min-height: 40px;
+  padding-right: 20px;
+  padding-left: 20px;
+  background-color: #fafafa;
+  background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));
+  background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);
+  background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);
+  background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);
+  background-repeat: repeat-x;
+  border: 1px solid #d4d4d4;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);
+  *zoom: 1;
+  -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+     -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+          box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+}
+
+.navbar-inner:before,
+.navbar-inner:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.navbar-inner:after {
+  clear: both;
+}
+
+.navbar .container {
+  width: auto;
+}
+
+.nav-collapse.collapse {
+  height: auto;
+  overflow: visible;
+}
+
+.navbar .brand {
+  display: block;
+  float: left;
+  padding: 10px 20px 10px;
+  margin-left: -20px;
+  font-size: 20px;
+  font-weight: 200;
+  color: #777777;
+  text-shadow: 0 1px 0 #ffffff;
+}
+
+.navbar .brand:hover {
+  text-decoration: none;
+}
+
+.navbar-text {
+  margin-bottom: 0;
+  line-height: 40px;
+  color: #777777;
+}
+
+.navbar-link {
+  color: #777777;
+}
+
+.navbar-link:hover {
+  color: #333333;
+}
+
+.navbar .divider-vertical {
+  height: 40px;
+  margin: 0 9px;
+  border-right: 1px solid #ffffff;
+  border-left: 1px solid #f2f2f2;
+}
+
+.navbar .btn,
+.navbar .btn-group {
+  margin-top: 5px;
+}
+
+.navbar .btn-group .btn,
+.navbar .input-prepend .btn,
+.navbar .input-append .btn {
+  margin-top: 0;
+}
+
+.navbar-form {
+  margin-bottom: 0;
+  *zoom: 1;
+}
+
+.navbar-form:before,
+.navbar-form:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.navbar-form:after {
+  clear: both;
+}
+
+.navbar-form input,
+.navbar-form select,
+.navbar-form .radio,
+.navbar-form .checkbox {
+  margin-top: 5px;
+}
+
+.navbar-form input,
+.navbar-form select,
+.navbar-form .btn {
+  display: inline-block;
+  margin-bottom: 0;
+}
+
+.navbar-form input[type="image"],
+.navbar-form input[type="checkbox"],
+.navbar-form input[type="radio"] {
+  margin-top: 3px;
+}
+
+.navbar-form .input-append,
+.navbar-form .input-prepend {
+  margin-top: 5px;
+  white-space: nowrap;
+}
+
+.navbar-form .input-append input,
+.navbar-form .input-prepend input {
+  margin-top: 0;
+}
+
+.navbar-search {
+  position: relative;
+  float: left;
+  margin-top: 5px;
+  margin-bottom: 0;
+}
+
+.navbar-search .search-query {
+  padding: 4px 14px;
+  margin-bottom: 0;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 13px;
+  font-weight: normal;
+  line-height: 1;
+  -webkit-border-radius: 15px;
+     -moz-border-radius: 15px;
+          border-radius: 15px;
+}
+
+.navbar-static-top {
+  position: static;
+  margin-bottom: 0;
+}
+
+.navbar-static-top .navbar-inner {
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  position: fixed;
+  right: 0;
+  left: 0;
+  z-index: 1030;
+  margin-bottom: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+  border-width: 0 0 1px;
+}
+
+.navbar-fixed-bottom .navbar-inner {
+  border-width: 1px 0 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-fixed-bottom .navbar-inner {
+  padding-right: 0;
+  padding-left: 0;
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+  width: 940px;
+}
+
+.navbar-fixed-top {
+  top: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+  -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
+     -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
+          box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
+}
+
+.navbar-fixed-bottom {
+  bottom: 0;
+}
+
+.navbar-fixed-bottom .navbar-inner {
+  -webkit-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
+     -moz-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
+          box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
+}
+
+.navbar .nav {
+  position: relative;
+  left: 0;
+  display: block;
+  float: left;
+  margin: 0 10px 0 0;
+}
+
+.navbar .nav.pull-right {
+  float: right;
+  margin-right: 0;
+}
+
+.navbar .nav > li {
+  float: left;
+}
+
+.navbar .nav > li > a {
+  float: none;
+  padding: 10px 15px 10px;
+  color: #777777;
+  text-decoration: none;
+  text-shadow: 0 1px 0 #ffffff;
+}
+
+.navbar .nav .dropdown-toggle .caret {
+  margin-top: 8px;
+}
+
+.navbar .nav > li > a:focus,
+.navbar .nav > li > a:hover {
+  color: #333333;
+  text-decoration: none;
+  background-color: transparent;
+}
+
+.navbar .nav > .active > a,
+.navbar .nav > .active > a:hover,
+.navbar .nav > .active > a:focus {
+  color: #555555;
+  text-decoration: none;
+  background-color: #e5e5e5;
+  -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+     -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+}
+
+.navbar .btn-navbar {
+  display: none;
+  float: right;
+  padding: 7px 10px;
+  margin-right: 5px;
+  margin-left: 5px;
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #ededed;
+  *background-color: #e5e5e5;
+  background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));
+  background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5);
+  background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5);
+  background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5);
+  background-repeat: repeat-x;
+  border-color: #e5e5e5 #e5e5e5 #bfbfbf;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+     -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+}
+
+.navbar .btn-navbar:hover,
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active,
+.navbar .btn-navbar.disabled,
+.navbar .btn-navbar[disabled] {
+  color: #ffffff;
+  background-color: #e5e5e5;
+  *background-color: #d9d9d9;
+}
+
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active {
+  background-color: #cccccc \9;
+}
+
+.navbar .btn-navbar .icon-bar {
+  display: block;
+  width: 18px;
+  height: 2px;
+  background-color: #f5f5f5;
+  -webkit-border-radius: 1px;
+     -moz-border-radius: 1px;
+          border-radius: 1px;
+  -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+     -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+          box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.btn-navbar .icon-bar + .icon-bar {
+  margin-top: 3px;
+}
+
+.navbar .nav > li > .dropdown-menu:before {
+  position: absolute;
+  top: -7px;
+  left: 9px;
+  display: inline-block;
+  border-right: 7px solid transparent;
+  border-bottom: 7px solid #ccc;
+  border-left: 7px solid transparent;
+  border-bottom-color: rgba(0, 0, 0, 0.2);
+  content: '';
+}
+
+.navbar .nav > li > .dropdown-menu:after {
+  position: absolute;
+  top: -6px;
+  left: 10px;
+  display: inline-block;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid #ffffff;
+  border-left: 6px solid transparent;
+  content: '';
+}
+
+.navbar-fixed-bottom .nav > li > .dropdown-menu:before {
+  top: auto;
+  bottom: -7px;
+  border-top: 7px solid #ccc;
+  border-bottom: 0;
+  border-top-color: rgba(0, 0, 0, 0.2);
+}
+
+.navbar-fixed-bottom .nav > li > .dropdown-menu:after {
+  top: auto;
+  bottom: -6px;
+  border-top: 6px solid #ffffff;
+  border-bottom: 0;
+}
+
+.navbar .nav li.dropdown > a:hover .caret {
+  border-top-color: #555555;
+  border-bottom-color: #555555;
+}
+
+.navbar .nav li.dropdown.open > .dropdown-toggle,
+.navbar .nav li.dropdown.active > .dropdown-toggle,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle {
+  color: #555555;
+  background-color: #e5e5e5;
+}
+
+.navbar .nav li.dropdown > .dropdown-toggle .caret {
+  border-top-color: #777777;
+  border-bottom-color: #777777;
+}
+
+.navbar .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret {
+  border-top-color: #555555;
+  border-bottom-color: #555555;
+}
+
+.navbar .pull-right > li > .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu:before,
+.navbar .nav > li > .dropdown-menu.pull-right:before {
+  right: 12px;
+  left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu:after,
+.navbar .nav > li > .dropdown-menu.pull-right:after {
+  right: 13px;
+  left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu {
+  right: 100%;
+  left: auto;
+  margin-right: -1px;
+  margin-left: 0;
+  -webkit-border-radius: 6px 0 6px 6px;
+     -moz-border-radius: 6px 0 6px 6px;
+          border-radius: 6px 0 6px 6px;
+}
+
+.navbar-inverse .navbar-inner {
+  background-color: #1b1b1b;
+  background-image: -moz-linear-gradient(top, #222222, #111111);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));
+  background-image: -webkit-linear-gradient(top, #222222, #111111);
+  background-image: -o-linear-gradient(top, #222222, #111111);
+  background-image: linear-gradient(to bottom, #222222, #111111);
+  background-repeat: repeat-x;
+  border-color: #252525;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);
+}
+
+.navbar-inverse .brand,
+.navbar-inverse .nav > li > a {
+  color: #999999;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.navbar-inverse .brand:hover,
+.navbar-inverse .nav > li > a:hover {
+  color: #ffffff;
+}
+
+.navbar-inverse .brand {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-text {
+  color: #999999;
+}
+
+.navbar-inverse .nav > li > a:focus,
+.navbar-inverse .nav > li > a:hover {
+  color: #ffffff;
+  background-color: transparent;
+}
+
+.navbar-inverse .nav .active > a,
+.navbar-inverse .nav .active > a:hover,
+.navbar-inverse .nav .active > a:focus {
+  color: #ffffff;
+  background-color: #111111;
+}
+
+.navbar-inverse .navbar-link {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-link:hover {
+  color: #ffffff;
+}
+
+.navbar-inverse .divider-vertical {
+  border-right-color: #222222;
+  border-left-color: #111111;
+}
+
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle {
+  color: #ffffff;
+  background-color: #111111;
+}
+
+.navbar-inverse .nav li.dropdown > a:hover .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret {
+  border-top-color: #999999;
+  border-bottom-color: #999999;
+}
+
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .navbar-search .search-query {
+  color: #ffffff;
+  background-color: #515151;
+  border-color: #111111;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+     -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+  -webkit-transition: none;
+     -moz-transition: none;
+       -o-transition: none;
+          transition: none;
+}
+
+.navbar-inverse .navbar-search .search-query:-moz-placeholder {
+  color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query:-ms-input-placeholder {
+  color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder {
+  color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query:focus,
+.navbar-inverse .navbar-search .search-query.focused {
+  padding: 5px 15px;
+  color: #333333;
+  text-shadow: 0 1px 0 #ffffff;
+  background-color: #ffffff;
+  border: 0;
+  outline: 0;
+  -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+     -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+          box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+}
+
+.navbar-inverse .btn-navbar {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #0e0e0e;
+  *background-color: #040404;
+  background-image: -moz-linear-gradient(top, #151515, #040404);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));
+  background-image: -webkit-linear-gradient(top, #151515, #040404);
+  background-image: -o-linear-gradient(top, #151515, #040404);
+  background-image: linear-gradient(to bottom, #151515, #040404);
+  background-repeat: repeat-x;
+  border-color: #040404 #040404 #000000;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.navbar-inverse .btn-navbar:hover,
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active,
+.navbar-inverse .btn-navbar.disabled,
+.navbar-inverse .btn-navbar[disabled] {
+  color: #ffffff;
+  background-color: #040404;
+  *background-color: #000000;
+}
+
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active {
+  background-color: #000000 \9;
+}
+
+.breadcrumb {
+  padding: 8px 15px;
+  margin: 0 0 20px;
+  list-style: none;
+  background-color: #f5f5f5;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+.breadcrumb > li {
+  display: inline-block;
+  *display: inline;
+  text-shadow: 0 1px 0 #ffffff;
+  *zoom: 1;
+}
+
+.breadcrumb > li > .divider {
+  padding: 0 5px;
+  color: #ccc;
+}
+
+.breadcrumb > .active {
+  color: #999999;
+}
+
+.pagination {
+  margin: 20px 0;
+}
+
+.pagination ul {
+  display: inline-block;
+  *display: inline;
+  margin-bottom: 0;
+  margin-left: 0;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  *zoom: 1;
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+     -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.pagination ul > li {
+  display: inline;
+}
+
+.pagination ul > li > a,
+.pagination ul > li > span {
+  float: left;
+  padding: 4px 12px;
+  line-height: 20px;
+  text-decoration: none;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-left-width: 0;
+}
+
+.pagination ul > li > a:hover,
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+  background-color: #f5f5f5;
+}
+
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+  color: #999999;
+  cursor: default;
+}
+
+.pagination ul > .disabled > span,
+.pagination ul > .disabled > a,
+.pagination ul > .disabled > a:hover {
+  color: #999999;
+  cursor: default;
+  background-color: transparent;
+}
+
+.pagination ul > li:first-child > a,
+.pagination ul > li:first-child > span {
+  border-left-width: 1px;
+  -webkit-border-bottom-left-radius: 4px;
+          border-bottom-left-radius: 4px;
+  -webkit-border-top-left-radius: 4px;
+          border-top-left-radius: 4px;
+  -moz-border-radius-bottomleft: 4px;
+  -moz-border-radius-topleft: 4px;
+}
+
+.pagination ul > li:last-child > a,
+.pagination ul > li:last-child > span {
+  -webkit-border-top-right-radius: 4px;
+          border-top-right-radius: 4px;
+  -webkit-border-bottom-right-radius: 4px;
+          border-bottom-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+  -moz-border-radius-bottomright: 4px;
+}
+
+.pagination-centered {
+  text-align: center;
+}
+
+.pagination-right {
+  text-align: right;
+}
+
+.pagination-large ul > li > a,
+.pagination-large ul > li > span {
+  padding: 11px 19px;
+  font-size: 17.5px;
+}
+
+.pagination-large ul > li:first-child > a,
+.pagination-large ul > li:first-child > span {
+  -webkit-border-bottom-left-radius: 6px;
+          border-bottom-left-radius: 6px;
+  -webkit-border-top-left-radius: 6px;
+          border-top-left-radius: 6px;
+  -moz-border-radius-bottomleft: 6px;
+  -moz-border-radius-topleft: 6px;
+}
+
+.pagination-large ul > li:last-child > a,
+.pagination-large ul > li:last-child > span {
+  -webkit-border-top-right-radius: 6px;
+          border-top-right-radius: 6px;
+  -webkit-border-bottom-right-radius: 6px;
+          border-bottom-right-radius: 6px;
+  -moz-border-radius-topright: 6px;
+  -moz-border-radius-bottomright: 6px;
+}
+
+.pagination-mini ul > li:first-child > a,
+.pagination-small ul > li:first-child > a,
+.pagination-mini ul > li:first-child > span,
+.pagination-small ul > li:first-child > span {
+  -webkit-border-bottom-left-radius: 3px;
+          border-bottom-left-radius: 3px;
+  -webkit-border-top-left-radius: 3px;
+          border-top-left-radius: 3px;
+  -moz-border-radius-bottomleft: 3px;
+  -moz-border-radius-topleft: 3px;
+}
+
+.pagination-mini ul > li:last-child > a,
+.pagination-small ul > li:last-child > a,
+.pagination-mini ul > li:last-child > span,
+.pagination-small ul > li:last-child > span {
+  -webkit-border-top-right-radius: 3px;
+          border-top-right-radius: 3px;
+  -webkit-border-bottom-right-radius: 3px;
+          border-bottom-right-radius: 3px;
+  -moz-border-radius-topright: 3px;
+  -moz-border-radius-bottomright: 3px;
+}
+
+.pagination-small ul > li > a,
+.pagination-small ul > li > span {
+  padding: 2px 10px;
+  font-size: 11.9px;
+}
+
+.pagination-mini ul > li > a,
+.pagination-mini ul > li > span {
+  padding: 0 6px;
+  font-size: 10.5px;
+}
+
+.pager {
+  margin: 20px 0;
+  text-align: center;
+  list-style: none;
+  *zoom: 1;
+}
+
+.pager:before,
+.pager:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.pager:after {
+  clear: both;
+}
+
+.pager li {
+  display: inline;
+}
+
+.pager li > a,
+.pager li > span {
+  display: inline-block;
+  padding: 5px 14px;
+  background-color: #fff;
+  border: 1px solid #ddd;
+  -webkit-border-radius: 15px;
+     -moz-border-radius: 15px;
+          border-radius: 15px;
+}
+
+.pager li > a:hover {
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+
+.pager .next > a,
+.pager .next > span {
+  float: right;
+}
+
+.pager .previous > a,
+.pager .previous > span {
+  float: left;
+}
+
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > span {
+  color: #999999;
+  cursor: default;
+  background-color: #fff;
+}
+
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1040;
+  background-color: #000000;
+}
+
+.modal-backdrop.fade {
+  opacity: 0;
+}
+
+.modal-backdrop,
+.modal-backdrop.fade.in {
+  opacity: 0.8;
+  filter: alpha(opacity=80);
+}
+
+.modal {
+  position: fixed;
+  top: 10%;
+  left: 50%;
+  z-index: 1050;
+  width: 560px;
+  margin-left: -280px;
+  background-color: #ffffff;
+  border: 1px solid #999;
+  border: 1px solid rgba(0, 0, 0, 0.3);
+  *border: 1px solid #999;
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+  outline: none;
+  -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+     -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+          box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+  -webkit-background-clip: padding-box;
+     -moz-background-clip: padding-box;
+          background-clip: padding-box;
+}
+
+.modal.fade {
+  top: -25%;
+  -webkit-transition: opacity 0.3s linear, top 0.3s ease-out;
+     -moz-transition: opacity 0.3s linear, top 0.3s ease-out;
+       -o-transition: opacity 0.3s linear, top 0.3s ease-out;
+          transition: opacity 0.3s linear, top 0.3s ease-out;
+}
+
+.modal.fade.in {
+  top: 10%;
+}
+
+.modal-header {
+  padding: 9px 15px;
+  border-bottom: 1px solid #eee;
+}
+
+.modal-header .close {
+  margin-top: 2px;
+}
+
+.modal-header h3 {
+  margin: 0;
+  line-height: 30px;
+}
+
+.modal-body {
+  position: relative;
+  max-height: 400px;
+  padding: 15px;
+  overflow-y: auto;
+}
+
+.modal-form {
+  margin-bottom: 0;
+}
+
+.modal-footer {
+  padding: 14px 15px 15px;
+  margin-bottom: 0;
+  text-align: right;
+  background-color: #f5f5f5;
+  border-top: 1px solid #ddd;
+  -webkit-border-radius: 0 0 6px 6px;
+     -moz-border-radius: 0 0 6px 6px;
+          border-radius: 0 0 6px 6px;
+  *zoom: 1;
+  -webkit-box-shadow: inset 0 1px 0 #ffffff;
+     -moz-box-shadow: inset 0 1px 0 #ffffff;
+          box-shadow: inset 0 1px 0 #ffffff;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.modal-footer:after {
+  clear: both;
+}
+
+.modal-footer .btn + .btn {
+  margin-bottom: 0;
+  margin-left: 5px;
+}
+
+.modal-footer .btn-group .btn + .btn {
+  margin-left: -1px;
+}
+
+.modal-footer .btn-block + .btn-block {
+  margin-left: 0;
+}
+
+.tooltip {
+  position: absolute;
+  z-index: 1030;
+  display: block;
+  padding: 5px;
+  font-size: 11px;
+  opacity: 0;
+  filter: alpha(opacity=0);
+  visibility: visible;
+}
+
+.tooltip.in {
+  opacity: 0.8;
+  filter: alpha(opacity=80);
+}
+
+.tooltip.top {
+  margin-top: -3px;
+}
+
+.tooltip.right {
+  margin-left: 3px;
+}
+
+.tooltip.bottom {
+  margin-top: 3px;
+}
+
+.tooltip.left {
+  margin-left: -3px;
+}
+
+.tooltip-inner {
+  max-width: 200px;
+  padding: 3px 8px;
+  color: #ffffff;
+  text-align: center;
+  text-decoration: none;
+  background-color: #000000;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+.tooltip-arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+
+.tooltip.top .tooltip-arrow {
+  bottom: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-top-color: #000000;
+  border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+  top: 50%;
+  left: 0;
+  margin-top: -5px;
+  border-right-color: #000000;
+  border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+  top: 50%;
+  right: 0;
+  margin-top: -5px;
+  border-left-color: #000000;
+  border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+  top: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-bottom-color: #000000;
+  border-width: 0 5px 5px;
+}
+
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1010;
+  display: none;
+  width: 236px;
+  padding: 1px;
+  text-align: left;
+  white-space: normal;
+  background-color: #ffffff;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+     -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+          box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -webkit-background-clip: padding-box;
+     -moz-background-clip: padding;
+          background-clip: padding-box;
+}
+
+.popover.top {
+  margin-top: -10px;
+}
+
+.popover.right {
+  margin-left: 10px;
+}
+
+.popover.bottom {
+  margin-top: 10px;
+}
+
+.popover.left {
+  margin-left: -10px;
+}
+
+.popover-title {
+  padding: 8px 14px;
+  margin: 0;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 18px;
+  background-color: #f7f7f7;
+  border-bottom: 1px solid #ebebeb;
+  -webkit-border-radius: 5px 5px 0 0;
+     -moz-border-radius: 5px 5px 0 0;
+          border-radius: 5px 5px 0 0;
+}
+
+.popover-content {
+  padding: 9px 14px;
+}
+
+.popover .arrow,
+.popover .arrow:after {
+  position: absolute;
+  display: block;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+
+.popover .arrow {
+  border-width: 11px;
+}
+
+.popover .arrow:after {
+  border-width: 10px;
+  content: "";
+}
+
+.popover.top .arrow {
+  bottom: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-top-color: #999;
+  border-top-color: rgba(0, 0, 0, 0.25);
+  border-bottom-width: 0;
+}
+
+.popover.top .arrow:after {
+  bottom: 1px;
+  margin-left: -10px;
+  border-top-color: #ffffff;
+  border-bottom-width: 0;
+}
+
+.popover.right .arrow {
+  top: 50%;
+  left: -11px;
+  margin-top: -11px;
+  border-right-color: #999;
+  border-right-color: rgba(0, 0, 0, 0.25);
+  border-left-width: 0;
+}
+
+.popover.right .arrow:after {
+  bottom: -10px;
+  left: 1px;
+  border-right-color: #ffffff;
+  border-left-width: 0;
+}
+
+.popover.bottom .arrow {
+  top: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-bottom-color: #999;
+  border-bottom-color: rgba(0, 0, 0, 0.25);
+  border-top-width: 0;
+}
+
+.popover.bottom .arrow:after {
+  top: 1px;
+  margin-left: -10px;
+  border-bottom-color: #ffffff;
+  border-top-width: 0;
+}
+
+.popover.left .arrow {
+  top: 50%;
+  right: -11px;
+  margin-top: -11px;
+  border-left-color: #999;
+  border-left-color: rgba(0, 0, 0, 0.25);
+  border-right-width: 0;
+}
+
+.popover.left .arrow:after {
+  right: 1px;
+  bottom: -10px;
+  border-left-color: #ffffff;
+  border-right-width: 0;
+}
+
+.thumbnails {
+  margin-left: -20px;
+  list-style: none;
+  *zoom: 1;
+}
+
+.thumbnails:before,
+.thumbnails:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.thumbnails:after {
+  clear: both;
+}
+
+.row-fluid .thumbnails {
+  margin-left: 0;
+}
+
+.thumbnails > li {
+  float: left;
+  margin-bottom: 20px;
+  margin-left: 20px;
+}
+
+.thumbnail {
+  display: block;
+  padding: 4px;
+  line-height: 20px;
+  border: 1px solid #ddd;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+     -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+          box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+  -webkit-transition: all 0.2s ease-in-out;
+     -moz-transition: all 0.2s ease-in-out;
+       -o-transition: all 0.2s ease-in-out;
+          transition: all 0.2s ease-in-out;
+}
+
+a.thumbnail:hover {
+  border-color: #0088cc;
+  -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+     -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+          box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+}
+
+.thumbnail > img {
+  display: block;
+  max-width: 100%;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.thumbnail .caption {
+  padding: 9px;
+  color: #555555;
+}
+
+.media,
+.media-body {
+  overflow: hidden;
+  *overflow: visible;
+  zoom: 1;
+}
+
+.media,
+.media .media {
+  margin-top: 15px;
+}
+
+.media:first-child {
+  margin-top: 0;
+}
+
+.media-object {
+  display: block;
+}
+
+.media-heading {
+  margin: 0 0 5px;
+}
+
+.media .pull-left {
+  margin-right: 10px;
+}
+
+.media .pull-right {
+  margin-left: 10px;
+}
+
+.media-list {
+  margin-left: 0;
+  list-style: none;
+}
+
+.label,
+.badge {
+  display: inline-block;
+  padding: 2px 4px;
+  font-size: 11.844px;
+  font-weight: bold;
+  line-height: 14px;
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  white-space: nowrap;
+  vertical-align: baseline;
+  background-color: #999999;
+}
+
+.label {
+  -webkit-border-radius: 3px;
+     -moz-border-radius: 3px;
+          border-radius: 3px;
+}
+
+.badge {
+  padding-right: 9px;
+  padding-left: 9px;
+  -webkit-border-radius: 9px;
+     -moz-border-radius: 9px;
+          border-radius: 9px;
+}
+
+.label:empty,
+.badge:empty {
+  display: none;
+}
+
+a.label:hover,
+a.badge:hover {
+  color: #ffffff;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.label-important,
+.badge-important {
+  background-color: #b94a48;
+}
+
+.label-important[href],
+.badge-important[href] {
+  background-color: #953b39;
+}
+
+.label-warning,
+.badge-warning {
+  background-color: #f89406;
+}
+
+.label-warning[href],
+.badge-warning[href] {
+  background-color: #c67605;
+}
+
+.label-success,
+.badge-success {
+  background-color: #468847;
+}
+
+.label-success[href],
+.badge-success[href] {
+  background-color: #356635;
+}
+
+.label-info,
+.badge-info {
+  background-color: #3a87ad;
+}
+
+.label-info[href],
+.badge-info[href] {
+  background-color: #2d6987;
+}
+
+.label-inverse,
+.badge-inverse {
+  background-color: #333333;
+}
+
+.label-inverse[href],
+.badge-inverse[href] {
+  background-color: #1a1a1a;
+}
+
+.btn .label,
+.btn .badge {
+  position: relative;
+  top: -1px;
+}
+
+.btn-mini .label,
+.btn-mini .badge {
+  top: 0;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-moz-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-ms-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-o-keyframes progress-bar-stripes {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: 40px 0;
+  }
+}
+
+@keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+.progress {
+  height: 20px;
+  margin-bottom: 20px;
+  overflow: hidden;
+  background-color: #f7f7f7;
+  background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
+  background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
+  background-repeat: repeat-x;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+     -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.progress .bar {
+  float: left;
+  width: 0;
+  height: 100%;
+  font-size: 12px;
+  color: #ffffff;
+  text-align: center;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #0e90d2;
+  background-image: -moz-linear-gradient(top, #149bdf, #0480be);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
+  background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
+  background-image: -o-linear-gradient(top, #149bdf, #0480be);
+  background-image: linear-gradient(to bottom, #149bdf, #0480be);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+     -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+  -webkit-transition: width 0.6s ease;
+     -moz-transition: width 0.6s ease;
+       -o-transition: width 0.6s ease;
+          transition: width 0.6s ease;
+}
+
+.progress .bar + .bar {
+  -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+     -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+          box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+}
+
+.progress-striped .bar {
+  background-color: #149bdf;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  -webkit-background-size: 40px 40px;
+     -moz-background-size: 40px 40px;
+       -o-background-size: 40px 40px;
+          background-size: 40px 40px;
+}
+
+.progress.active .bar {
+  -webkit-animation: progress-bar-stripes 2s linear infinite;
+     -moz-animation: progress-bar-stripes 2s linear infinite;
+      -ms-animation: progress-bar-stripes 2s linear infinite;
+       -o-animation: progress-bar-stripes 2s linear infinite;
+          animation: progress-bar-stripes 2s linear infinite;
+}
+
+.progress-danger .bar,
+.progress .bar-danger {
+  background-color: #dd514c;
+  background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));
+  background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);
+}
+
+.progress-danger.progress-striped .bar,
+.progress-striped .bar-danger {
+  background-color: #ee5f5b;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-success .bar,
+.progress .bar-success {
+  background-color: #5eb95e;
+  background-image: -moz-linear-gradient(top, #62c462, #57a957);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));
+  background-image: -webkit-linear-gradient(top, #62c462, #57a957);
+  background-image: -o-linear-gradient(top, #62c462, #57a957);
+  background-image: linear-gradient(to bottom, #62c462, #57a957);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);
+}
+
+.progress-success.progress-striped .bar,
+.progress-striped .bar-success {
+  background-color: #62c462;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-info .bar,
+.progress .bar-info {
+  background-color: #4bb1cf;
+  background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));
+  background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);
+}
+
+.progress-info.progress-striped .bar,
+.progress-striped .bar-info {
+  background-color: #5bc0de;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-warning .bar,
+.progress .bar-warning {
+  background-color: #faa732;
+  background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+  background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+  background-image: -o-linear-gradient(top, #fbb450, #f89406);
+  background-image: linear-gradient(to bottom, #fbb450, #f89406);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+}
+
+.progress-warning.progress-striped .bar,
+.progress-striped .bar-warning {
+  background-color: #fbb450;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.accordion {
+  margin-bottom: 20px;
+}
+
+.accordion-group {
+  margin-bottom: 2px;
+  border: 1px solid #e5e5e5;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+.accordion-heading {
+  border-bottom: 0;
+}
+
+.accordion-heading .accordion-toggle {
+  display: block;
+  padding: 8px 15px;
+}
+
+.accordion-toggle {
+  cursor: pointer;
+}
+
+.accordion-inner {
+  padding: 9px 15px;
+  border-top: 1px solid #e5e5e5;
+}
+
+.carousel {
+  position: relative;
+  margin-bottom: 20px;
+  line-height: 1;
+}
+
+.carousel-inner {
+  position: relative;
+  width: 100%;
+  overflow: hidden;
+}
+
+.carousel-inner > .item {
+  position: relative;
+  display: none;
+  -webkit-transition: 0.6s ease-in-out left;
+     -moz-transition: 0.6s ease-in-out left;
+       -o-transition: 0.6s ease-in-out left;
+          transition: 0.6s ease-in-out left;
+}
+
+.carousel-inner > .item > img {
+  display: block;
+  line-height: 1;
+}
+
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  display: block;
+}
+
+.carousel-inner > .active {
+  left: 0;
+}
+
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+
+.carousel-inner > .next {
+  left: 100%;
+}
+
+.carousel-inner > .prev {
+  left: -100%;
+}
+
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+  left: 0;
+}
+
+.carousel-inner > .active.left {
+  left: -100%;
+}
+
+.carousel-inner > .active.right {
+  left: 100%;
+}
+
+.carousel-control {
+  position: absolute;
+  top: 40%;
+  left: 15px;
+  width: 40px;
+  height: 40px;
+  margin-top: -20px;
+  font-size: 60px;
+  font-weight: 100;
+  line-height: 30px;
+  color: #ffffff;
+  text-align: center;
+  background: #222222;
+  border: 3px solid #ffffff;
+  -webkit-border-radius: 23px;
+     -moz-border-radius: 23px;
+          border-radius: 23px;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+.carousel-control.right {
+  right: 15px;
+  left: auto;
+}
+
+.carousel-control:hover {
+  color: #ffffff;
+  text-decoration: none;
+  opacity: 0.9;
+  filter: alpha(opacity=90);
+}
+
+.carousel-caption {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  padding: 15px;
+  background: #333333;
+  background: rgba(0, 0, 0, 0.75);
+}
+
+.carousel-caption h4,
+.carousel-caption p {
+  line-height: 20px;
+  color: #ffffff;
+}
+
+.carousel-caption h4 {
+  margin: 0 0 5px;
+}
+
+.carousel-caption p {
+  margin-bottom: 0;
+}
+
+.hero-unit {
+  padding: 60px;
+  margin-bottom: 30px;
+  font-size: 18px;
+  font-weight: 200;
+  line-height: 30px;
+  color: inherit;
+  background-color: #eeeeee;
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+}
+
+.hero-unit h1 {
+  margin-bottom: 0;
+  font-size: 60px;
+  line-height: 1;
+  letter-spacing: -1px;
+  color: inherit;
+}
+
+.hero-unit li {
+  line-height: 30px;
+}
+
+.pull-right {
+  float: right;
+}
+
+.pull-left {
+  float: left;
+}
+
+.hide {
+  display: none;
+}
+
+.show {
+  display: block;
+}
+
+.invisible {
+  visibility: hidden;
+}
+
+.affix {
+  position: fixed;
+}
diff --git a/docs/tutorial/webpages/bootstrap/css/bootstrap.min.css b/docs/tutorial/webpages/bootstrap/css/bootstrap.min.css
new file mode 100644
index 0000000..140f731
--- /dev/null
+++ b/docs/tutorial/webpages/bootstrap/css/bootstrap.min.css
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap v2.2.2
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover{color:#808080}.text-warning{color:#c09853}a.text-warning:hover{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover{color:#2d6987}.text-success{color:#468847}a.text-success:hover{color:#356635}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:25px}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{margin-bottom:5px;font-size:0;white-space:nowrap}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover td,.table-hover tbody tr:hover th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success td{background-color:#dff0d8}.table tbody tr.error td{background-color:#f2dede}.table tbody tr.warning td{background-color:#fcf8e3}.table tbody tr.info td{background-color:#d9edf7}.table-hover tbody tr.success:hover td{background-color:#d0e9c6}.table-hover tbody tr.error:hover td{background-color:#ebcccc}.table-hover tbody tr.warning:hover td{background-color:#faf2cc}.table-hover tbody tr.info:hover td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu li>a:hover,.dropdown-menu li>a:focus,.dropdown-submenu:hover>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .disabled>a,.dropdown-menu .disabled>a:hover{color:#999}.dropdown-menu .disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #bbb;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn{border-color:#c5c5c5;border-color:rgba(0,0,0,0.15) rgba(0,0,0,0.15) rgba(0,0,0,0.25)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-mini .caret,.btn-small .caret,.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret{border-top-color:#555;border-bottom-color:#555}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{margin-top:-3px}.tooltip.right{margin-left:3px}.tooltip.bottom{margin-top:3px}.tooltip.left{margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;width:236px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media .pull-left{margin-right:10px}.media .pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed}
diff --git a/docs/tutorial/webpages/bootstrap/img/glyphicons-halflings-white.png b/docs/tutorial/webpages/bootstrap/img/glyphicons-halflings-white.png
new file mode 100644
index 0000000..3bf6484
--- /dev/null
+++ b/docs/tutorial/webpages/bootstrap/img/glyphicons-halflings-white.png
Binary files differ
diff --git a/docs/tutorial/webpages/bootstrap/img/glyphicons-halflings.png b/docs/tutorial/webpages/bootstrap/img/glyphicons-halflings.png
new file mode 100644
index 0000000..a996999
--- /dev/null
+++ b/docs/tutorial/webpages/bootstrap/img/glyphicons-halflings.png
Binary files differ
diff --git a/docs/tutorial/webpages/bootstrap/js/bootstrap.js b/docs/tutorial/webpages/bootstrap/js/bootstrap.js
new file mode 100644
index 0000000..6c15a58
--- /dev/null
+++ b/docs/tutorial/webpages/bootstrap/js/bootstrap.js
@@ -0,0 +1,2159 @@
+/* ===================================================
+ * bootstrap-transition.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#transitions
+ * ===================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+  /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
+   * ======================================================= */
+
+  $(function () {
+
+    $.support.transition = (function () {
+
+      var transitionEnd = (function () {
+
+        var el = document.createElement('bootstrap')
+          , transEndEventNames = {
+               'WebkitTransition' : 'webkitTransitionEnd'
+            ,  'MozTransition'    : 'transitionend'
+            ,  'OTransition'      : 'oTransitionEnd otransitionend'
+            ,  'transition'       : 'transitionend'
+            }
+          , name
+
+        for (name in transEndEventNames){
+          if (el.style[name] !== undefined) {
+            return transEndEventNames[name]
+          }
+        }
+
+      }())
+
+      return transitionEnd && {
+        end: transitionEnd
+      }
+
+    })()
+
+  })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-alert.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* ALERT CLASS DEFINITION
+  * ====================== */
+
+  var dismiss = '[data-dismiss="alert"]'
+    , Alert = function (el) {
+        $(el).on('click', dismiss, this.close)
+      }
+
+  Alert.prototype.close = function (e) {
+    var $this = $(this)
+      , selector = $this.attr('data-target')
+      , $parent
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    $parent = $(selector)
+
+    e && e.preventDefault()
+
+    $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
+
+    $parent.trigger(e = $.Event('close'))
+
+    if (e.isDefaultPrevented()) return
+
+    $parent.removeClass('in')
+
+    function removeElement() {
+      $parent
+        .trigger('closed')
+        .remove()
+    }
+
+    $.support.transition && $parent.hasClass('fade') ?
+      $parent.on($.support.transition.end, removeElement) :
+      removeElement()
+  }
+
+
+ /* ALERT PLUGIN DEFINITION
+  * ======================= */
+
+  var old = $.fn.alert
+
+  $.fn.alert = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('alert')
+      if (!data) $this.data('alert', (data = new Alert(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.alert.Constructor = Alert
+
+
+ /* ALERT NO CONFLICT
+  * ================= */
+
+  $.fn.alert.noConflict = function () {
+    $.fn.alert = old
+    return this
+  }
+
+
+ /* ALERT DATA-API
+  * ============== */
+
+  $(document).on('click.alert.data-api', dismiss, Alert.prototype.close)
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-button.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#buttons
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* BUTTON PUBLIC CLASS DEFINITION
+  * ============================== */
+
+  var Button = function (element, options) {
+    this.$element = $(element)
+    this.options = $.extend({}, $.fn.button.defaults, options)
+  }
+
+  Button.prototype.setState = function (state) {
+    var d = 'disabled'
+      , $el = this.$element
+      , data = $el.data()
+      , val = $el.is('input') ? 'val' : 'html'
+
+    state = state + 'Text'
+    data.resetText || $el.data('resetText', $el[val]())
+
+    $el[val](data[state] || this.options[state])
+
+    // push to event loop to allow forms to submit
+    setTimeout(function () {
+      state == 'loadingText' ?
+        $el.addClass(d).attr(d, d) :
+        $el.removeClass(d).removeAttr(d)
+    }, 0)
+  }
+
+  Button.prototype.toggle = function () {
+    var $parent = this.$element.closest('[data-toggle="buttons-radio"]')
+
+    $parent && $parent
+      .find('.active')
+      .removeClass('active')
+
+    this.$element.toggleClass('active')
+  }
+
+
+ /* BUTTON PLUGIN DEFINITION
+  * ======================== */
+
+  var old = $.fn.button
+
+  $.fn.button = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('button')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('button', (data = new Button(this, options)))
+      if (option == 'toggle') data.toggle()
+      else if (option) data.setState(option)
+    })
+  }
+
+  $.fn.button.defaults = {
+    loadingText: 'loading...'
+  }
+
+  $.fn.button.Constructor = Button
+
+
+ /* BUTTON NO CONFLICT
+  * ================== */
+
+  $.fn.button.noConflict = function () {
+    $.fn.button = old
+    return this
+  }
+
+
+ /* BUTTON DATA-API
+  * =============== */
+
+  $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) {
+    var $btn = $(e.target)
+    if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+    $btn.button('toggle')
+  })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-carousel.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#carousel
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* CAROUSEL CLASS DEFINITION
+  * ========================= */
+
+  var Carousel = function (element, options) {
+    this.$element = $(element)
+    this.options = options
+    this.options.pause == 'hover' && this.$element
+      .on('mouseenter', $.proxy(this.pause, this))
+      .on('mouseleave', $.proxy(this.cycle, this))
+  }
+
+  Carousel.prototype = {
+
+    cycle: function (e) {
+      if (!e) this.paused = false
+      this.options.interval
+        && !this.paused
+        && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+      return this
+    }
+
+  , to: function (pos) {
+      var $active = this.$element.find('.item.active')
+        , children = $active.parent().children()
+        , activePos = children.index($active)
+        , that = this
+
+      if (pos > (children.length - 1) || pos < 0) return
+
+      if (this.sliding) {
+        return this.$element.one('slid', function () {
+          that.to(pos)
+        })
+      }
+
+      if (activePos == pos) {
+        return this.pause().cycle()
+      }
+
+      return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
+    }
+
+  , pause: function (e) {
+      if (!e) this.paused = true
+      if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+        this.$element.trigger($.support.transition.end)
+        this.cycle()
+      }
+      clearInterval(this.interval)
+      this.interval = null
+      return this
+    }
+
+  , next: function () {
+      if (this.sliding) return
+      return this.slide('next')
+    }
+
+  , prev: function () {
+      if (this.sliding) return
+      return this.slide('prev')
+    }
+
+  , slide: function (type, next) {
+      var $active = this.$element.find('.item.active')
+        , $next = next || $active[type]()
+        , isCycling = this.interval
+        , direction = type == 'next' ? 'left' : 'right'
+        , fallback  = type == 'next' ? 'first' : 'last'
+        , that = this
+        , e
+
+      this.sliding = true
+
+      isCycling && this.pause()
+
+      $next = $next.length ? $next : this.$element.find('.item')[fallback]()
+
+      e = $.Event('slide', {
+        relatedTarget: $next[0]
+      })
+
+      if ($next.hasClass('active')) return
+
+      if ($.support.transition && this.$element.hasClass('slide')) {
+        this.$element.trigger(e)
+        if (e.isDefaultPrevented()) return
+        $next.addClass(type)
+        $next[0].offsetWidth // force reflow
+        $active.addClass(direction)
+        $next.addClass(direction)
+        this.$element.one($.support.transition.end, function () {
+          $next.removeClass([type, direction].join(' ')).addClass('active')
+          $active.removeClass(['active', direction].join(' '))
+          that.sliding = false
+          setTimeout(function () { that.$element.trigger('slid') }, 0)
+        })
+      } else {
+        this.$element.trigger(e)
+        if (e.isDefaultPrevented()) return
+        $active.removeClass('active')
+        $next.addClass('active')
+        this.sliding = false
+        this.$element.trigger('slid')
+      }
+
+      isCycling && this.cycle()
+
+      return this
+    }
+
+  }
+
+
+ /* CAROUSEL PLUGIN DEFINITION
+  * ========================== */
+
+  var old = $.fn.carousel
+
+  $.fn.carousel = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('carousel')
+        , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
+        , action = typeof option == 'string' ? option : options.slide
+      if (!data) $this.data('carousel', (data = new Carousel(this, options)))
+      if (typeof option == 'number') data.to(option)
+      else if (action) data[action]()
+      else if (options.interval) data.cycle()
+    })
+  }
+
+  $.fn.carousel.defaults = {
+    interval: 5000
+  , pause: 'hover'
+  }
+
+  $.fn.carousel.Constructor = Carousel
+
+
+ /* CAROUSEL NO CONFLICT
+  * ==================== */
+
+  $.fn.carousel.noConflict = function () {
+    $.fn.carousel = old
+    return this
+  }
+
+ /* CAROUSEL DATA-API
+  * ================= */
+
+  $(document).on('click.carousel.data-api', '[data-slide]', function (e) {
+    var $this = $(this), href
+      , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+      , options = $.extend({}, $target.data(), $this.data())
+    $target.carousel(options)
+    e.preventDefault()
+  })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-collapse.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#collapse
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* COLLAPSE PUBLIC CLASS DEFINITION
+  * ================================ */
+
+  var Collapse = function (element, options) {
+    this.$element = $(element)
+    this.options = $.extend({}, $.fn.collapse.defaults, options)
+
+    if (this.options.parent) {
+      this.$parent = $(this.options.parent)
+    }
+
+    this.options.toggle && this.toggle()
+  }
+
+  Collapse.prototype = {
+
+    constructor: Collapse
+
+  , dimension: function () {
+      var hasWidth = this.$element.hasClass('width')
+      return hasWidth ? 'width' : 'height'
+    }
+
+  , show: function () {
+      var dimension
+        , scroll
+        , actives
+        , hasData
+
+      if (this.transitioning) return
+
+      dimension = this.dimension()
+      scroll = $.camelCase(['scroll', dimension].join('-'))
+      actives = this.$parent && this.$parent.find('> .accordion-group > .in')
+
+      if (actives && actives.length) {
+        hasData = actives.data('collapse')
+        if (hasData && hasData.transitioning) return
+        actives.collapse('hide')
+        hasData || actives.data('collapse', null)
+      }
+
+      this.$element[dimension](0)
+      this.transition('addClass', $.Event('show'), 'shown')
+      $.support.transition && this.$element[dimension](this.$element[0][scroll])
+    }
+
+  , hide: function () {
+      var dimension
+      if (this.transitioning) return
+      dimension = this.dimension()
+      this.reset(this.$element[dimension]())
+      this.transition('removeClass', $.Event('hide'), 'hidden')
+      this.$element[dimension](0)
+    }
+
+  , reset: function (size) {
+      var dimension = this.dimension()
+
+      this.$element
+        .removeClass('collapse')
+        [dimension](size || 'auto')
+        [0].offsetWidth
+
+      this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
+
+      return this
+    }
+
+  , transition: function (method, startEvent, completeEvent) {
+      var that = this
+        , complete = function () {
+            if (startEvent.type == 'show') that.reset()
+            that.transitioning = 0
+            that.$element.trigger(completeEvent)
+          }
+
+      this.$element.trigger(startEvent)
+
+      if (startEvent.isDefaultPrevented()) return
+
+      this.transitioning = 1
+
+      this.$element[method]('in')
+
+      $.support.transition && this.$element.hasClass('collapse') ?
+        this.$element.one($.support.transition.end, complete) :
+        complete()
+    }
+
+  , toggle: function () {
+      this[this.$element.hasClass('in') ? 'hide' : 'show']()
+    }
+
+  }
+
+
+ /* COLLAPSE PLUGIN DEFINITION
+  * ========================== */
+
+  var old = $.fn.collapse
+
+  $.fn.collapse = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('collapse')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('collapse', (data = new Collapse(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.collapse.defaults = {
+    toggle: true
+  }
+
+  $.fn.collapse.Constructor = Collapse
+
+
+ /* COLLAPSE NO CONFLICT
+  * ==================== */
+
+  $.fn.collapse.noConflict = function () {
+    $.fn.collapse = old
+    return this
+  }
+
+
+ /* COLLAPSE DATA-API
+  * ================= */
+
+  $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
+    var $this = $(this), href
+      , target = $this.attr('data-target')
+        || e.preventDefault()
+        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+      , option = $(target).data('collapse') ? 'toggle' : $this.data()
+    $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+    $(target).collapse(option)
+  })
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-dropdown.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#dropdowns
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* DROPDOWN CLASS DEFINITION
+  * ========================= */
+
+  var toggle = '[data-toggle=dropdown]'
+    , Dropdown = function (element) {
+        var $el = $(element).on('click.dropdown.data-api', this.toggle)
+        $('html').on('click.dropdown.data-api', function () {
+          $el.parent().removeClass('open')
+        })
+      }
+
+  Dropdown.prototype = {
+
+    constructor: Dropdown
+
+  , toggle: function (e) {
+      var $this = $(this)
+        , $parent
+        , isActive
+
+      if ($this.is('.disabled, :disabled')) return
+
+      $parent = getParent($this)
+
+      isActive = $parent.hasClass('open')
+
+      clearMenus()
+
+      if (!isActive) {
+        $parent.toggleClass('open')
+      }
+
+      $this.focus()
+
+      return false
+    }
+
+  , keydown: function (e) {
+      var $this
+        , $items
+        , $active
+        , $parent
+        , isActive
+        , index
+
+      if (!/(38|40|27)/.test(e.keyCode)) return
+
+      $this = $(this)
+
+      e.preventDefault()
+      e.stopPropagation()
+
+      if ($this.is('.disabled, :disabled')) return
+
+      $parent = getParent($this)
+
+      isActive = $parent.hasClass('open')
+
+      if (!isActive || (isActive && e.keyCode == 27)) return $this.click()
+
+      $items = $('[role=menu] li:not(.divider):visible a', $parent)
+
+      if (!$items.length) return
+
+      index = $items.index($items.filter(':focus'))
+
+      if (e.keyCode == 38 && index > 0) index--                                        // up
+      if (e.keyCode == 40 && index < $items.length - 1) index++                        // down
+      if (!~index) index = 0
+
+      $items
+        .eq(index)
+        .focus()
+    }
+
+  }
+
+  function clearMenus() {
+    $(toggle).each(function () {
+      getParent($(this)).removeClass('open')
+    })
+  }
+
+  function getParent($this) {
+    var selector = $this.attr('data-target')
+      , $parent
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    $parent = $(selector)
+    $parent.length || ($parent = $this.parent())
+
+    return $parent
+  }
+
+
+  /* DROPDOWN PLUGIN DEFINITION
+   * ========================== */
+
+  var old = $.fn.dropdown
+
+  $.fn.dropdown = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('dropdown')
+      if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.dropdown.Constructor = Dropdown
+
+
+ /* DROPDOWN NO CONFLICT
+  * ==================== */
+
+  $.fn.dropdown.noConflict = function () {
+    $.fn.dropdown = old
+    return this
+  }
+
+
+  /* APPLY TO STANDARD DROPDOWN ELEMENTS
+   * =================================== */
+
+  $(document)
+    .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus)
+    .on('click.dropdown touchstart.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+    .on('touchstart.dropdown.data-api', '.dropdown-menu', function (e) { e.stopPropagation() })
+    .on('click.dropdown.data-api touchstart.dropdown.data-api'  , toggle, Dropdown.prototype.toggle)
+    .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+
+}(window.jQuery);/* =========================================================
+ * bootstrap-modal.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#modals
+ * =========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* MODAL CLASS DEFINITION
+  * ====================== */
+
+  var Modal = function (element, options) {
+    this.options = options
+    this.$element = $(element)
+      .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
+    this.options.remote && this.$element.find('.modal-body').load(this.options.remote)
+  }
+
+  Modal.prototype = {
+
+      constructor: Modal
+
+    , toggle: function () {
+        return this[!this.isShown ? 'show' : 'hide']()
+      }
+
+    , show: function () {
+        var that = this
+          , e = $.Event('show')
+
+        this.$element.trigger(e)
+
+        if (this.isShown || e.isDefaultPrevented()) return
+
+        this.isShown = true
+
+        this.escape()
+
+        this.backdrop(function () {
+          var transition = $.support.transition && that.$element.hasClass('fade')
+
+          if (!that.$element.parent().length) {
+            that.$element.appendTo(document.body) //don't move modals dom position
+          }
+
+          that.$element
+            .show()
+
+          if (transition) {
+            that.$element[0].offsetWidth // force reflow
+          }
+
+          that.$element
+            .addClass('in')
+            .attr('aria-hidden', false)
+
+          that.enforceFocus()
+
+          transition ?
+            that.$element.one($.support.transition.end, function () { that.$element.focus().trigger('shown') }) :
+            that.$element.focus().trigger('shown')
+
+        })
+      }
+
+    , hide: function (e) {
+        e && e.preventDefault()
+
+        var that = this
+
+        e = $.Event('hide')
+
+        this.$element.trigger(e)
+
+        if (!this.isShown || e.isDefaultPrevented()) return
+
+        this.isShown = false
+
+        this.escape()
+
+        $(document).off('focusin.modal')
+
+        this.$element
+          .removeClass('in')
+          .attr('aria-hidden', true)
+
+        $.support.transition && this.$element.hasClass('fade') ?
+          this.hideWithTransition() :
+          this.hideModal()
+      }
+
+    , enforceFocus: function () {
+        var that = this
+        $(document).on('focusin.modal', function (e) {
+          if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
+            that.$element.focus()
+          }
+        })
+      }
+
+    , escape: function () {
+        var that = this
+        if (this.isShown && this.options.keyboard) {
+          this.$element.on('keyup.dismiss.modal', function ( e ) {
+            e.which == 27 && that.hide()
+          })
+        } else if (!this.isShown) {
+          this.$element.off('keyup.dismiss.modal')
+        }
+      }
+
+    , hideWithTransition: function () {
+        var that = this
+          , timeout = setTimeout(function () {
+              that.$element.off($.support.transition.end)
+              that.hideModal()
+            }, 500)
+
+        this.$element.one($.support.transition.end, function () {
+          clearTimeout(timeout)
+          that.hideModal()
+        })
+      }
+
+    , hideModal: function (that) {
+        this.$element
+          .hide()
+          .trigger('hidden')
+
+        this.backdrop()
+      }
+
+    , removeBackdrop: function () {
+        this.$backdrop.remove()
+        this.$backdrop = null
+      }
+
+    , backdrop: function (callback) {
+        var that = this
+          , animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+        if (this.isShown && this.options.backdrop) {
+          var doAnimate = $.support.transition && animate
+
+          this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+            .appendTo(document.body)
+
+          this.$backdrop.click(
+            this.options.backdrop == 'static' ?
+              $.proxy(this.$element[0].focus, this.$element[0])
+            : $.proxy(this.hide, this)
+          )
+
+          if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+          this.$backdrop.addClass('in')
+
+          doAnimate ?
+            this.$backdrop.one($.support.transition.end, callback) :
+            callback()
+
+        } else if (!this.isShown && this.$backdrop) {
+          this.$backdrop.removeClass('in')
+
+          $.support.transition && this.$element.hasClass('fade')?
+            this.$backdrop.one($.support.transition.end, $.proxy(this.removeBackdrop, this)) :
+            this.removeBackdrop()
+
+        } else if (callback) {
+          callback()
+        }
+      }
+  }
+
+
+ /* MODAL PLUGIN DEFINITION
+  * ======================= */
+
+  var old = $.fn.modal
+
+  $.fn.modal = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('modal')
+        , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
+      if (!data) $this.data('modal', (data = new Modal(this, options)))
+      if (typeof option == 'string') data[option]()
+      else if (options.show) data.show()
+    })
+  }
+
+  $.fn.modal.defaults = {
+      backdrop: true
+    , keyboard: true
+    , show: true
+  }
+
+  $.fn.modal.Constructor = Modal
+
+
+ /* MODAL NO CONFLICT
+  * ================= */
+
+  $.fn.modal.noConflict = function () {
+    $.fn.modal = old
+    return this
+  }
+
+
+ /* MODAL DATA-API
+  * ============== */
+
+  $(document).on('click.modal.data-api', '[data-toggle="modal"]', function (e) {
+    var $this = $(this)
+      , href = $this.attr('href')
+      , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+      , option = $target.data('modal') ? 'toggle' : $.extend({ remote:!/#/.test(href) && href }, $target.data(), $this.data())
+
+    e.preventDefault()
+
+    $target
+      .modal(option)
+      .one('hide', function () {
+        $this.focus()
+      })
+  })
+
+}(window.jQuery);
+/* ===========================================================
+ * bootstrap-tooltip.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#tooltips
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* TOOLTIP PUBLIC CLASS DEFINITION
+  * =============================== */
+
+  var Tooltip = function (element, options) {
+    this.init('tooltip', element, options)
+  }
+
+  Tooltip.prototype = {
+
+    constructor: Tooltip
+
+  , init: function (type, element, options) {
+      var eventIn
+        , eventOut
+
+      this.type = type
+      this.$element = $(element)
+      this.options = this.getOptions(options)
+      this.enabled = true
+
+      if (this.options.trigger == 'click') {
+        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+      } else if (this.options.trigger != 'manual') {
+        eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
+        eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
+        this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+      }
+
+      this.options.selector ?
+        (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+        this.fixTitle()
+    }
+
+  , getOptions: function (options) {
+      options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
+
+      if (options.delay && typeof options.delay == 'number') {
+        options.delay = {
+          show: options.delay
+        , hide: options.delay
+        }
+      }
+
+      return options
+    }
+
+  , enter: function (e) {
+      var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+      if (!self.options.delay || !self.options.delay.show) return self.show()
+
+      clearTimeout(this.timeout)
+      self.hoverState = 'in'
+      this.timeout = setTimeout(function() {
+        if (self.hoverState == 'in') self.show()
+      }, self.options.delay.show)
+    }
+
+  , leave: function (e) {
+      var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+      if (this.timeout) clearTimeout(this.timeout)
+      if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+      self.hoverState = 'out'
+      this.timeout = setTimeout(function() {
+        if (self.hoverState == 'out') self.hide()
+      }, self.options.delay.hide)
+    }
+
+  , show: function () {
+      var $tip
+        , inside
+        , pos
+        , actualWidth
+        , actualHeight
+        , placement
+        , tp
+
+      if (this.hasContent() && this.enabled) {
+        $tip = this.tip()
+        this.setContent()
+
+        if (this.options.animation) {
+          $tip.addClass('fade')
+        }
+
+        placement = typeof this.options.placement == 'function' ?
+          this.options.placement.call(this, $tip[0], this.$element[0]) :
+          this.options.placement
+
+        inside = /in/.test(placement)
+
+        $tip
+          .detach()
+          .css({ top: 0, left: 0, display: 'block' })
+          .insertAfter(this.$element)
+
+        pos = this.getPosition(inside)
+
+        actualWidth = $tip[0].offsetWidth
+        actualHeight = $tip[0].offsetHeight
+
+        switch (inside ? placement.split(' ')[1] : placement) {
+          case 'bottom':
+            tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
+            break
+          case 'top':
+            tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
+            break
+          case 'left':
+            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
+            break
+          case 'right':
+            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
+            break
+        }
+
+        $tip
+          .offset(tp)
+          .addClass(placement)
+          .addClass('in')
+      }
+    }
+
+  , setContent: function () {
+      var $tip = this.tip()
+        , title = this.getTitle()
+
+      $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+      $tip.removeClass('fade in top bottom left right')
+    }
+
+  , hide: function () {
+      var that = this
+        , $tip = this.tip()
+
+      $tip.removeClass('in')
+
+      function removeWithAnimation() {
+        var timeout = setTimeout(function () {
+          $tip.off($.support.transition.end).detach()
+        }, 500)
+
+        $tip.one($.support.transition.end, function () {
+          clearTimeout(timeout)
+          $tip.detach()
+        })
+      }
+
+      $.support.transition && this.$tip.hasClass('fade') ?
+        removeWithAnimation() :
+        $tip.detach()
+
+      return this
+    }
+
+  , fixTitle: function () {
+      var $e = this.$element
+      if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+        $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
+      }
+    }
+
+  , hasContent: function () {
+      return this.getTitle()
+    }
+
+  , getPosition: function (inside) {
+      return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
+        width: this.$element[0].offsetWidth
+      , height: this.$element[0].offsetHeight
+      })
+    }
+
+  , getTitle: function () {
+      var title
+        , $e = this.$element
+        , o = this.options
+
+      title = $e.attr('data-original-title')
+        || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
+
+      return title
+    }
+
+  , tip: function () {
+      return this.$tip = this.$tip || $(this.options.template)
+    }
+
+  , validate: function () {
+      if (!this.$element[0].parentNode) {
+        this.hide()
+        this.$element = null
+        this.options = null
+      }
+    }
+
+  , enable: function () {
+      this.enabled = true
+    }
+
+  , disable: function () {
+      this.enabled = false
+    }
+
+  , toggleEnabled: function () {
+      this.enabled = !this.enabled
+    }
+
+  , toggle: function (e) {
+      var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+      self[self.tip().hasClass('in') ? 'hide' : 'show']()
+    }
+
+  , destroy: function () {
+      this.hide().$element.off('.' + this.type).removeData(this.type)
+    }
+
+  }
+
+
+ /* TOOLTIP PLUGIN DEFINITION
+  * ========================= */
+
+  var old = $.fn.tooltip
+
+  $.fn.tooltip = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('tooltip')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tooltip.Constructor = Tooltip
+
+  $.fn.tooltip.defaults = {
+    animation: true
+  , placement: 'top'
+  , selector: false
+  , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+  , trigger: 'hover'
+  , title: ''
+  , delay: 0
+  , html: false
+  }
+
+
+ /* TOOLTIP NO CONFLICT
+  * =================== */
+
+  $.fn.tooltip.noConflict = function () {
+    $.fn.tooltip = old
+    return this
+  }
+
+}(window.jQuery);/* ===========================================================
+ * bootstrap-popover.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#popovers
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* POPOVER PUBLIC CLASS DEFINITION
+  * =============================== */
+
+  var Popover = function (element, options) {
+    this.init('popover', element, options)
+  }
+
+
+  /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
+     ========================================== */
+
+  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
+
+    constructor: Popover
+
+  , setContent: function () {
+      var $tip = this.tip()
+        , title = this.getTitle()
+        , content = this.getContent()
+
+      $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+      $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
+
+      $tip.removeClass('fade top bottom left right in')
+    }
+
+  , hasContent: function () {
+      return this.getTitle() || this.getContent()
+    }
+
+  , getContent: function () {
+      var content
+        , $e = this.$element
+        , o = this.options
+
+      content = $e.attr('data-content')
+        || (typeof o.content == 'function' ? o.content.call($e[0]) :  o.content)
+
+      return content
+    }
+
+  , tip: function () {
+      if (!this.$tip) {
+        this.$tip = $(this.options.template)
+      }
+      return this.$tip
+    }
+
+  , destroy: function () {
+      this.hide().$element.off('.' + this.type).removeData(this.type)
+    }
+
+  })
+
+
+ /* POPOVER PLUGIN DEFINITION
+  * ======================= */
+
+  var old = $.fn.popover
+
+  $.fn.popover = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('popover')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('popover', (data = new Popover(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.popover.Constructor = Popover
+
+  $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
+    placement: 'right'
+  , trigger: 'click'
+  , content: ''
+  , template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"></div></div></div>'
+  })
+
+
+ /* POPOVER NO CONFLICT
+  * =================== */
+
+  $.fn.popover.noConflict = function () {
+    $.fn.popover = old
+    return this
+  }
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-scrollspy.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#scrollspy
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* SCROLLSPY CLASS DEFINITION
+  * ========================== */
+
+  function ScrollSpy(element, options) {
+    var process = $.proxy(this.process, this)
+      , $element = $(element).is('body') ? $(window) : $(element)
+      , href
+    this.options = $.extend({}, $.fn.scrollspy.defaults, options)
+    this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)
+    this.selector = (this.options.target
+      || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+      || '') + ' .nav li > a'
+    this.$body = $('body')
+    this.refresh()
+    this.process()
+  }
+
+  ScrollSpy.prototype = {
+
+      constructor: ScrollSpy
+
+    , refresh: function () {
+        var self = this
+          , $targets
+
+        this.offsets = $([])
+        this.targets = $([])
+
+        $targets = this.$body
+          .find(this.selector)
+          .map(function () {
+            var $el = $(this)
+              , href = $el.data('target') || $el.attr('href')
+              , $href = /^#\w/.test(href) && $(href)
+            return ( $href
+              && $href.length
+              && [[ $href.position().top + self.$scrollElement.scrollTop(), href ]] ) || null
+          })
+          .sort(function (a, b) { return a[0] - b[0] })
+          .each(function () {
+            self.offsets.push(this[0])
+            self.targets.push(this[1])
+          })
+      }
+
+    , process: function () {
+        var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+          , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+          , maxScroll = scrollHeight - this.$scrollElement.height()
+          , offsets = this.offsets
+          , targets = this.targets
+          , activeTarget = this.activeTarget
+          , i
+
+        if (scrollTop >= maxScroll) {
+          return activeTarget != (i = targets.last()[0])
+            && this.activate ( i )
+        }
+
+        for (i = offsets.length; i--;) {
+          activeTarget != targets[i]
+            && scrollTop >= offsets[i]
+            && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+            && this.activate( targets[i] )
+        }
+      }
+
+    , activate: function (target) {
+        var active
+          , selector
+
+        this.activeTarget = target
+
+        $(this.selector)
+          .parent('.active')
+          .removeClass('active')
+
+        selector = this.selector
+          + '[data-target="' + target + '"],'
+          + this.selector + '[href="' + target + '"]'
+
+        active = $(selector)
+          .parent('li')
+          .addClass('active')
+
+        if (active.parent('.dropdown-menu').length)  {
+          active = active.closest('li.dropdown').addClass('active')
+        }
+
+        active.trigger('activate')
+      }
+
+  }
+
+
+ /* SCROLLSPY PLUGIN DEFINITION
+  * =========================== */
+
+  var old = $.fn.scrollspy
+
+  $.fn.scrollspy = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('scrollspy')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.scrollspy.Constructor = ScrollSpy
+
+  $.fn.scrollspy.defaults = {
+    offset: 10
+  }
+
+
+ /* SCROLLSPY NO CONFLICT
+  * ===================== */
+
+  $.fn.scrollspy.noConflict = function () {
+    $.fn.scrollspy = old
+    return this
+  }
+
+
+ /* SCROLLSPY DATA-API
+  * ================== */
+
+  $(window).on('load', function () {
+    $('[data-spy="scroll"]').each(function () {
+      var $spy = $(this)
+      $spy.scrollspy($spy.data())
+    })
+  })
+
+}(window.jQuery);/* ========================================================
+ * bootstrap-tab.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#tabs
+ * ========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* TAB CLASS DEFINITION
+  * ==================== */
+
+  var Tab = function (element) {
+    this.element = $(element)
+  }
+
+  Tab.prototype = {
+
+    constructor: Tab
+
+  , show: function () {
+      var $this = this.element
+        , $ul = $this.closest('ul:not(.dropdown-menu)')
+        , selector = $this.attr('data-target')
+        , previous
+        , $target
+        , e
+
+      if (!selector) {
+        selector = $this.attr('href')
+        selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+      }
+
+      if ( $this.parent('li').hasClass('active') ) return
+
+      previous = $ul.find('.active:last a')[0]
+
+      e = $.Event('show', {
+        relatedTarget: previous
+      })
+
+      $this.trigger(e)
+
+      if (e.isDefaultPrevented()) return
+
+      $target = $(selector)
+
+      this.activate($this.parent('li'), $ul)
+      this.activate($target, $target.parent(), function () {
+        $this.trigger({
+          type: 'shown'
+        , relatedTarget: previous
+        })
+      })
+    }
+
+  , activate: function ( element, container, callback) {
+      var $active = container.find('> .active')
+        , transition = callback
+            && $.support.transition
+            && $active.hasClass('fade')
+
+      function next() {
+        $active
+          .removeClass('active')
+          .find('> .dropdown-menu > .active')
+          .removeClass('active')
+
+        element.addClass('active')
+
+        if (transition) {
+          element[0].offsetWidth // reflow for transition
+          element.addClass('in')
+        } else {
+          element.removeClass('fade')
+        }
+
+        if ( element.parent('.dropdown-menu') ) {
+          element.closest('li.dropdown').addClass('active')
+        }
+
+        callback && callback()
+      }
+
+      transition ?
+        $active.one($.support.transition.end, next) :
+        next()
+
+      $active.removeClass('in')
+    }
+  }
+
+
+ /* TAB PLUGIN DEFINITION
+  * ===================== */
+
+  var old = $.fn.tab
+
+  $.fn.tab = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('tab')
+      if (!data) $this.data('tab', (data = new Tab(this)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tab.Constructor = Tab
+
+
+ /* TAB NO CONFLICT
+  * =============== */
+
+  $.fn.tab.noConflict = function () {
+    $.fn.tab = old
+    return this
+  }
+
+
+ /* TAB DATA-API
+  * ============ */
+
+  $(document).on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+    e.preventDefault()
+    $(this).tab('show')
+  })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-typeahead.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#typeahead
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function($){
+
+  "use strict"; // jshint ;_;
+
+
+ /* TYPEAHEAD PUBLIC CLASS DEFINITION
+  * ================================= */
+
+  var Typeahead = function (element, options) {
+    this.$element = $(element)
+    this.options = $.extend({}, $.fn.typeahead.defaults, options)
+    this.matcher = this.options.matcher || this.matcher
+    this.sorter = this.options.sorter || this.sorter
+    this.highlighter = this.options.highlighter || this.highlighter
+    this.updater = this.options.updater || this.updater
+    this.source = this.options.source
+    this.$menu = $(this.options.menu)
+    this.shown = false
+    this.listen()
+  }
+
+  Typeahead.prototype = {
+
+    constructor: Typeahead
+
+  , select: function () {
+      var val = this.$menu.find('.active').attr('data-value')
+      this.$element
+        .val(this.updater(val))
+        .change()
+      return this.hide()
+    }
+
+  , updater: function (item) {
+      return item
+    }
+
+  , show: function () {
+      var pos = $.extend({}, this.$element.position(), {
+        height: this.$element[0].offsetHeight
+      })
+
+      this.$menu
+        .insertAfter(this.$element)
+        .css({
+          top: pos.top + pos.height
+        , left: pos.left
+        })
+        .show()
+
+      this.shown = true
+      return this
+    }
+
+  , hide: function () {
+      this.$menu.hide()
+      this.shown = false
+      return this
+    }
+
+  , lookup: function (event) {
+      var items
+
+      this.query = this.$element.val()
+
+      if (!this.query || this.query.length < this.options.minLength) {
+        return this.shown ? this.hide() : this
+      }
+
+      items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
+
+      return items ? this.process(items) : this
+    }
+
+  , process: function (items) {
+      var that = this
+
+      items = $.grep(items, function (item) {
+        return that.matcher(item)
+      })
+
+      items = this.sorter(items)
+
+      if (!items.length) {
+        return this.shown ? this.hide() : this
+      }
+
+      return this.render(items.slice(0, this.options.items)).show()
+    }
+
+  , matcher: function (item) {
+      return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+    }
+
+  , sorter: function (items) {
+      var beginswith = []
+        , caseSensitive = []
+        , caseInsensitive = []
+        , item
+
+      while (item = items.shift()) {
+        if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+        else if (~item.indexOf(this.query)) caseSensitive.push(item)
+        else caseInsensitive.push(item)
+      }
+
+      return beginswith.concat(caseSensitive, caseInsensitive)
+    }
+
+  , highlighter: function (item) {
+      var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
+      return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+        return '<strong>' + match + '</strong>'
+      })
+    }
+
+  , render: function (items) {
+      var that = this
+
+      items = $(items).map(function (i, item) {
+        i = $(that.options.item).attr('data-value', item)
+        i.find('a').html(that.highlighter(item))
+        return i[0]
+      })
+
+      items.first().addClass('active')
+      this.$menu.html(items)
+      return this
+    }
+
+  , next: function (event) {
+      var active = this.$menu.find('.active').removeClass('active')
+        , next = active.next()
+
+      if (!next.length) {
+        next = $(this.$menu.find('li')[0])
+      }
+
+      next.addClass('active')
+    }
+
+  , prev: function (event) {
+      var active = this.$menu.find('.active').removeClass('active')
+        , prev = active.prev()
+
+      if (!prev.length) {
+        prev = this.$menu.find('li').last()
+      }
+
+      prev.addClass('active')
+    }
+
+  , listen: function () {
+      this.$element
+        .on('blur',     $.proxy(this.blur, this))
+        .on('keypress', $.proxy(this.keypress, this))
+        .on('keyup',    $.proxy(this.keyup, this))
+
+      if (this.eventSupported('keydown')) {
+        this.$element.on('keydown', $.proxy(this.keydown, this))
+      }
+
+      this.$menu
+        .on('click', $.proxy(this.click, this))
+        .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+    }
+
+  , eventSupported: function(eventName) {
+      var isSupported = eventName in this.$element
+      if (!isSupported) {
+        this.$element.setAttribute(eventName, 'return;')
+        isSupported = typeof this.$element[eventName] === 'function'
+      }
+      return isSupported
+    }
+
+  , move: function (e) {
+      if (!this.shown) return
+
+      switch(e.keyCode) {
+        case 9: // tab
+        case 13: // enter
+        case 27: // escape
+          e.preventDefault()
+          break
+
+        case 38: // up arrow
+          e.preventDefault()
+          this.prev()
+          break
+
+        case 40: // down arrow
+          e.preventDefault()
+          this.next()
+          break
+      }
+
+      e.stopPropagation()
+    }
+
+  , keydown: function (e) {
+      this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
+      this.move(e)
+    }
+
+  , keypress: function (e) {
+      if (this.suppressKeyPressRepeat) return
+      this.move(e)
+    }
+
+  , keyup: function (e) {
+      switch(e.keyCode) {
+        case 40: // down arrow
+        case 38: // up arrow
+        case 16: // shift
+        case 17: // ctrl
+        case 18: // alt
+          break
+
+        case 9: // tab
+        case 13: // enter
+          if (!this.shown) return
+          this.select()
+          break
+
+        case 27: // escape
+          if (!this.shown) return
+          this.hide()
+          break
+
+        default:
+          this.lookup()
+      }
+
+      e.stopPropagation()
+      e.preventDefault()
+  }
+
+  , blur: function (e) {
+      var that = this
+      setTimeout(function () { that.hide() }, 150)
+    }
+
+  , click: function (e) {
+      e.stopPropagation()
+      e.preventDefault()
+      this.select()
+    }
+
+  , mouseenter: function (e) {
+      this.$menu.find('.active').removeClass('active')
+      $(e.currentTarget).addClass('active')
+    }
+
+  }
+
+
+  /* TYPEAHEAD PLUGIN DEFINITION
+   * =========================== */
+
+  var old = $.fn.typeahead
+
+  $.fn.typeahead = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('typeahead')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.typeahead.defaults = {
+    source: []
+  , items: 8
+  , menu: '<ul class="typeahead dropdown-menu"></ul>'
+  , item: '<li><a href="#"></a></li>'
+  , minLength: 1
+  }
+
+  $.fn.typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD NO CONFLICT
+  * =================== */
+
+  $.fn.typeahead.noConflict = function () {
+    $.fn.typeahead = old
+    return this
+  }
+
+
+ /* TYPEAHEAD DATA-API
+  * ================== */
+
+  $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+    var $this = $(this)
+    if ($this.data('typeahead')) return
+    e.preventDefault()
+    $this.typeahead($this.data())
+  })
+
+}(window.jQuery);
+/* ==========================================================
+ * bootstrap-affix.js v2.2.2
+ * http://twitter.github.com/bootstrap/javascript.html#affix
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* AFFIX CLASS DEFINITION
+  * ====================== */
+
+  var Affix = function (element, options) {
+    this.options = $.extend({}, $.fn.affix.defaults, options)
+    this.$window = $(window)
+      .on('scroll.affix.data-api', $.proxy(this.checkPosition, this))
+      .on('click.affix.data-api',  $.proxy(function () { setTimeout($.proxy(this.checkPosition, this), 1) }, this))
+    this.$element = $(element)
+    this.checkPosition()
+  }
+
+  Affix.prototype.checkPosition = function () {
+    if (!this.$element.is(':visible')) return
+
+    var scrollHeight = $(document).height()
+      , scrollTop = this.$window.scrollTop()
+      , position = this.$element.offset()
+      , offset = this.options.offset
+      , offsetBottom = offset.bottom
+      , offsetTop = offset.top
+      , reset = 'affix affix-top affix-bottom'
+      , affix
+
+    if (typeof offset != 'object') offsetBottom = offsetTop = offset
+    if (typeof offsetTop == 'function') offsetTop = offset.top()
+    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+    affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?
+      false    : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?
+      'bottom' : offsetTop != null && scrollTop <= offsetTop ?
+      'top'    : false
+
+    if (this.affixed === affix) return
+
+    this.affixed = affix
+    this.unpin = affix == 'bottom' ? position.top - scrollTop : null
+
+    this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))
+  }
+
+
+ /* AFFIX PLUGIN DEFINITION
+  * ======================= */
+
+  var old = $.fn.affix
+
+  $.fn.affix = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('affix')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('affix', (data = new Affix(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.affix.Constructor = Affix
+
+  $.fn.affix.defaults = {
+    offset: 0
+  }
+
+
+ /* AFFIX NO CONFLICT
+  * ================= */
+
+  $.fn.affix.noConflict = function () {
+    $.fn.affix = old
+    return this
+  }
+
+
+ /* AFFIX DATA-API
+  * ============== */
+
+  $(window).on('load', function () {
+    $('[data-spy="affix"]').each(function () {
+      var $spy = $(this)
+        , data = $spy.data()
+
+      data.offset = data.offset || {}
+
+      data.offsetBottom && (data.offset.bottom = data.offsetBottom)
+      data.offsetTop && (data.offset.top = data.offsetTop)
+
+      $spy.affix(data)
+    })
+  })
+
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/docs/tutorial/webpages/bootstrap/js/bootstrap.min.js b/docs/tutorial/webpages/bootstrap/js/bootstrap.min.js
new file mode 100644
index 0000000..6eeb15c
--- /dev/null
+++ b/docs/tutorial/webpages/bootstrap/js/bootstrap.min.js
@@ -0,0 +1,6 @@
+/*!
+* Bootstrap.js by @fat & @mdo
+* Copyright 2012 Twitter, Inc.
+* http://www.apache.org/licenses/LICENSE-2.0.txt
+*/
+!function($){"use strict";$(function(){$.support.transition=function(){var transitionEnd=function(){var name,el=document.createElement("bootstrap"),transEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(name in transEndEventNames)if(void 0!==el.style[name])return transEndEventNames[name]}();return transitionEnd&&{end:transitionEnd}}()})}(window.jQuery),!function($){"use strict";var dismiss='[data-dismiss="alert"]',Alert=function(el){$(el).on("click",dismiss,this.close)};Alert.prototype.close=function(e){function removeElement(){$parent.trigger("closed").remove()}var $parent,$this=$(this),selector=$this.attr("data-target");selector||(selector=$this.attr("href"),selector=selector&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),e&&e.preventDefault(),$parent.length||($parent=$this.hasClass("alert")?$this:$this.parent()),$parent.trigger(e=$.Event("close")),e.isDefaultPrevented()||($parent.removeClass("in"),$.support.transition&&$parent.hasClass("fade")?$parent.on($.support.transition.end,removeElement):removeElement())};var old=$.fn.alert;$.fn.alert=function(option){return this.each(function(){var $this=$(this),data=$this.data("alert");data||$this.data("alert",data=new Alert(this)),"string"==typeof option&&data[option].call($this)})},$.fn.alert.Constructor=Alert,$.fn.alert.noConflict=function(){return $.fn.alert=old,this},$(document).on("click.alert.data-api",dismiss,Alert.prototype.close)}(window.jQuery),!function($){"use strict";var Button=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.button.defaults,options)};Button.prototype.setState=function(state){var d="disabled",$el=this.$element,data=$el.data(),val=$el.is("input")?"val":"html";state+="Text",data.resetText||$el.data("resetText",$el[val]()),$el[val](data[state]||this.options[state]),setTimeout(function(){"loadingText"==state?$el.addClass(d).attr(d,d):$el.removeClass(d).removeAttr(d)},0)},Button.prototype.toggle=function(){var $parent=this.$element.closest('[data-toggle="buttons-radio"]');$parent&&$parent.find(".active").removeClass("active"),this.$element.toggleClass("active")};var old=$.fn.button;$.fn.button=function(option){return this.each(function(){var $this=$(this),data=$this.data("button"),options="object"==typeof option&&option;data||$this.data("button",data=new Button(this,options)),"toggle"==option?data.toggle():option&&data.setState(option)})},$.fn.button.defaults={loadingText:"loading..."},$.fn.button.Constructor=Button,$.fn.button.noConflict=function(){return $.fn.button=old,this},$(document).on("click.button.data-api","[data-toggle^=button]",function(e){var $btn=$(e.target);$btn.hasClass("btn")||($btn=$btn.closest(".btn")),$btn.button("toggle")})}(window.jQuery),!function($){"use strict";var Carousel=function(element,options){this.$element=$(element),this.options=options,"hover"==this.options.pause&&this.$element.on("mouseenter",$.proxy(this.pause,this)).on("mouseleave",$.proxy(this.cycle,this))};Carousel.prototype={cycle:function(e){return e||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval($.proxy(this.next,this),this.options.interval)),this},to:function(pos){var $active=this.$element.find(".item.active"),children=$active.parent().children(),activePos=children.index($active),that=this;if(!(pos>children.length-1||0>pos))return this.sliding?this.$element.one("slid",function(){that.to(pos)}):activePos==pos?this.pause().cycle():this.slide(pos>activePos?"next":"prev",$(children[pos]))},pause:function(e){return e||(this.paused=!0),this.$element.find(".next, .prev").length&&$.support.transition.end&&(this.$element.trigger($.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){return this.sliding?void 0:this.slide("next")},prev:function(){return this.sliding?void 0:this.slide("prev")},slide:function(type,next){var e,$active=this.$element.find(".item.active"),$next=next||$active[type](),isCycling=this.interval,direction="next"==type?"left":"right",fallback="next"==type?"first":"last",that=this;if(this.sliding=!0,isCycling&&this.pause(),$next=$next.length?$next:this.$element.find(".item")[fallback](),e=$.Event("slide",{relatedTarget:$next[0]}),!$next.hasClass("active")){if($.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(e),e.isDefaultPrevented())return;$next.addClass(type),$next[0].offsetWidth,$active.addClass(direction),$next.addClass(direction),this.$element.one($.support.transition.end,function(){$next.removeClass([type,direction].join(" ")).addClass("active"),$active.removeClass(["active",direction].join(" ")),that.sliding=!1,setTimeout(function(){that.$element.trigger("slid")},0)})}else{if(this.$element.trigger(e),e.isDefaultPrevented())return;$active.removeClass("active"),$next.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return isCycling&&this.cycle(),this}}};var old=$.fn.carousel;$.fn.carousel=function(option){return this.each(function(){var $this=$(this),data=$this.data("carousel"),options=$.extend({},$.fn.carousel.defaults,"object"==typeof option&&option),action="string"==typeof option?option:options.slide;data||$this.data("carousel",data=new Carousel(this,options)),"number"==typeof option?data.to(option):action?data[action]():options.interval&&data.cycle()})},$.fn.carousel.defaults={interval:5e3,pause:"hover"},$.fn.carousel.Constructor=Carousel,$.fn.carousel.noConflict=function(){return $.fn.carousel=old,this},$(document).on("click.carousel.data-api","[data-slide]",function(e){var href,$this=$(this),$target=$($this.attr("data-target")||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,"")),options=$.extend({},$target.data(),$this.data());$target.carousel(options),e.preventDefault()})}(window.jQuery),!function($){"use strict";var Collapse=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.collapse.defaults,options),this.options.parent&&(this.$parent=$(this.options.parent)),this.options.toggle&&this.toggle()};Collapse.prototype={constructor:Collapse,dimension:function(){var hasWidth=this.$element.hasClass("width");return hasWidth?"width":"height"},show:function(){var dimension,scroll,actives,hasData;if(!this.transitioning){if(dimension=this.dimension(),scroll=$.camelCase(["scroll",dimension].join("-")),actives=this.$parent&&this.$parent.find("> .accordion-group > .in"),actives&&actives.length){if(hasData=actives.data("collapse"),hasData&&hasData.transitioning)return;actives.collapse("hide"),hasData||actives.data("collapse",null)}this.$element[dimension](0),this.transition("addClass",$.Event("show"),"shown"),$.support.transition&&this.$element[dimension](this.$element[0][scroll])}},hide:function(){var dimension;this.transitioning||(dimension=this.dimension(),this.reset(this.$element[dimension]()),this.transition("removeClass",$.Event("hide"),"hidden"),this.$element[dimension](0))},reset:function(size){var dimension=this.dimension();return this.$element.removeClass("collapse")[dimension](size||"auto")[0].offsetWidth,this.$element[null!==size?"addClass":"removeClass"]("collapse"),this},transition:function(method,startEvent,completeEvent){var that=this,complete=function(){"show"==startEvent.type&&that.reset(),that.transitioning=0,that.$element.trigger(completeEvent)};this.$element.trigger(startEvent),startEvent.isDefaultPrevented()||(this.transitioning=1,this.$element[method]("in"),$.support.transition&&this.$element.hasClass("collapse")?this.$element.one($.support.transition.end,complete):complete())},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var old=$.fn.collapse;$.fn.collapse=function(option){return this.each(function(){var $this=$(this),data=$this.data("collapse"),options="object"==typeof option&&option;data||$this.data("collapse",data=new Collapse(this,options)),"string"==typeof option&&data[option]()})},$.fn.collapse.defaults={toggle:!0},$.fn.collapse.Constructor=Collapse,$.fn.collapse.noConflict=function(){return $.fn.collapse=old,this},$(document).on("click.collapse.data-api","[data-toggle=collapse]",function(e){var href,$this=$(this),target=$this.attr("data-target")||e.preventDefault()||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,""),option=$(target).data("collapse")?"toggle":$this.data();$this[$(target).hasClass("in")?"addClass":"removeClass"]("collapsed"),$(target).collapse(option)})}(window.jQuery),!function($){"use strict";function clearMenus(){$(toggle).each(function(){getParent($(this)).removeClass("open")})}function getParent($this){var $parent,selector=$this.attr("data-target");return selector||(selector=$this.attr("href"),selector=selector&&/#/.test(selector)&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),$parent.length||($parent=$this.parent()),$parent}var toggle="[data-toggle=dropdown]",Dropdown=function(element){var $el=$(element).on("click.dropdown.data-api",this.toggle);$("html").on("click.dropdown.data-api",function(){$el.parent().removeClass("open")})};Dropdown.prototype={constructor:Dropdown,toggle:function(){var $parent,isActive,$this=$(this);if(!$this.is(".disabled, :disabled"))return $parent=getParent($this),isActive=$parent.hasClass("open"),clearMenus(),isActive||$parent.toggleClass("open"),$this.focus(),!1},keydown:function(e){var $this,$items,$parent,isActive,index;if(/(38|40|27)/.test(e.keyCode)&&($this=$(this),e.preventDefault(),e.stopPropagation(),!$this.is(".disabled, :disabled"))){if($parent=getParent($this),isActive=$parent.hasClass("open"),!isActive||isActive&&27==e.keyCode)return $this.click();$items=$("[role=menu] li:not(.divider):visible a",$parent),$items.length&&(index=$items.index($items.filter(":focus")),38==e.keyCode&&index>0&&index--,40==e.keyCode&&$items.length-1>index&&index++,~index||(index=0),$items.eq(index).focus())}}};var old=$.fn.dropdown;$.fn.dropdown=function(option){return this.each(function(){var $this=$(this),data=$this.data("dropdown");data||$this.data("dropdown",data=new Dropdown(this)),"string"==typeof option&&data[option].call($this)})},$.fn.dropdown.Constructor=Dropdown,$.fn.dropdown.noConflict=function(){return $.fn.dropdown=old,this},$(document).on("click.dropdown.data-api touchstart.dropdown.data-api",clearMenus).on("click.dropdown touchstart.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("touchstart.dropdown.data-api",".dropdown-menu",function(e){e.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",toggle,Dropdown.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",toggle+", [role=menu]",Dropdown.prototype.keydown)}(window.jQuery),!function($){"use strict";var Modal=function(element,options){this.options=options,this.$element=$(element).delegate('[data-dismiss="modal"]',"click.dismiss.modal",$.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};Modal.prototype={constructor:Modal,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var that=this,e=$.Event("show");this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.backdrop(function(){var transition=$.support.transition&&that.$element.hasClass("fade");that.$element.parent().length||that.$element.appendTo(document.body),that.$element.show(),transition&&that.$element[0].offsetWidth,that.$element.addClass("in").attr("aria-hidden",!1),that.enforceFocus(),transition?that.$element.one($.support.transition.end,function(){that.$element.focus().trigger("shown")}):that.$element.focus().trigger("shown")}))},hide:function(e){e&&e.preventDefault(),e=$.Event("hide"),this.$element.trigger(e),this.isShown&&!e.isDefaultPrevented()&&(this.isShown=!1,this.escape(),$(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),$.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal())},enforceFocus:function(){var that=this;$(document).on("focusin.modal",function(e){that.$element[0]===e.target||that.$element.has(e.target).length||that.$element.focus()})},escape:function(){var that=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(e){27==e.which&&that.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var that=this,timeout=setTimeout(function(){that.$element.off($.support.transition.end),that.hideModal()},500);this.$element.one($.support.transition.end,function(){clearTimeout(timeout),that.hideModal()})},hideModal:function(){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(callback){var animate=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var doAnimate=$.support.transition&&animate;this.$backdrop=$('<div class="modal-backdrop '+animate+'" />').appendTo(document.body),this.$backdrop.click("static"==this.options.backdrop?$.proxy(this.$element[0].focus,this.$element[0]):$.proxy(this.hide,this)),doAnimate&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),doAnimate?this.$backdrop.one($.support.transition.end,callback):callback()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),$.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one($.support.transition.end,$.proxy(this.removeBackdrop,this)):this.removeBackdrop()):callback&&callback()}};var old=$.fn.modal;$.fn.modal=function(option){return this.each(function(){var $this=$(this),data=$this.data("modal"),options=$.extend({},$.fn.modal.defaults,$this.data(),"object"==typeof option&&option);data||$this.data("modal",data=new Modal(this,options)),"string"==typeof option?data[option]():options.show&&data.show()})},$.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},$.fn.modal.Constructor=Modal,$.fn.modal.noConflict=function(){return $.fn.modal=old,this},$(document).on("click.modal.data-api",'[data-toggle="modal"]',function(e){var $this=$(this),href=$this.attr("href"),$target=$($this.attr("data-target")||href&&href.replace(/.*(?=#[^\s]+$)/,"")),option=$target.data("modal")?"toggle":$.extend({remote:!/#/.test(href)&&href},$target.data(),$this.data());e.preventDefault(),$target.modal(option).one("hide",function(){$this.focus()})})}(window.jQuery),!function($){"use strict";var Tooltip=function(element,options){this.init("tooltip",element,options)};Tooltip.prototype={constructor:Tooltip,init:function(type,element,options){var eventIn,eventOut;this.type=type,this.$element=$(element),this.options=this.getOptions(options),this.enabled=!0,"click"==this.options.trigger?this.$element.on("click."+this.type,this.options.selector,$.proxy(this.toggle,this)):"manual"!=this.options.trigger&&(eventIn="hover"==this.options.trigger?"mouseenter":"focus",eventOut="hover"==this.options.trigger?"mouseleave":"blur",this.$element.on(eventIn+"."+this.type,this.options.selector,$.proxy(this.enter,this)),this.$element.on(eventOut+"."+this.type,this.options.selector,$.proxy(this.leave,this))),this.options.selector?this._options=$.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(options){return options=$.extend({},$.fn[this.type].defaults,options,this.$element.data()),options.delay&&"number"==typeof options.delay&&(options.delay={show:options.delay,hide:options.delay}),options},enter:function(e){var self=$(e.currentTarget)[this.type](this._options).data(this.type);return self.options.delay&&self.options.delay.show?(clearTimeout(this.timeout),self.hoverState="in",this.timeout=setTimeout(function(){"in"==self.hoverState&&self.show()},self.options.delay.show),void 0):self.show()},leave:function(e){var self=$(e.currentTarget)[this.type](this._options).data(this.type);return this.timeout&&clearTimeout(this.timeout),self.options.delay&&self.options.delay.hide?(self.hoverState="out",this.timeout=setTimeout(function(){"out"==self.hoverState&&self.hide()},self.options.delay.hide),void 0):self.hide()},show:function(){var $tip,inside,pos,actualWidth,actualHeight,placement,tp;if(this.hasContent()&&this.enabled){switch($tip=this.tip(),this.setContent(),this.options.animation&&$tip.addClass("fade"),placement="function"==typeof this.options.placement?this.options.placement.call(this,$tip[0],this.$element[0]):this.options.placement,inside=/in/.test(placement),$tip.detach().css({top:0,left:0,display:"block"}).insertAfter(this.$element),pos=this.getPosition(inside),actualWidth=$tip[0].offsetWidth,actualHeight=$tip[0].offsetHeight,inside?placement.split(" ")[1]:placement){case"bottom":tp={top:pos.top+pos.height,left:pos.left+pos.width/2-actualWidth/2};break;case"top":tp={top:pos.top-actualHeight,left:pos.left+pos.width/2-actualWidth/2};break;case"left":tp={top:pos.top+pos.height/2-actualHeight/2,left:pos.left-actualWidth};break;case"right":tp={top:pos.top+pos.height/2-actualHeight/2,left:pos.left+pos.width}}$tip.offset(tp).addClass(placement).addClass("in")}},setContent:function(){var $tip=this.tip(),title=this.getTitle();$tip.find(".tooltip-inner")[this.options.html?"html":"text"](title),$tip.removeClass("fade in top bottom left right")},hide:function(){function removeWithAnimation(){var timeout=setTimeout(function(){$tip.off($.support.transition.end).detach()},500);$tip.one($.support.transition.end,function(){clearTimeout(timeout),$tip.detach()})}var $tip=this.tip();return $tip.removeClass("in"),$.support.transition&&this.$tip.hasClass("fade")?removeWithAnimation():$tip.detach(),this},fixTitle:function(){var $e=this.$element;($e.attr("title")||"string"!=typeof $e.attr("data-original-title"))&&$e.attr("data-original-title",$e.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(inside){return $.extend({},inside?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var title,$e=this.$element,o=this.options;return title=$e.attr("data-original-title")||("function"==typeof o.title?o.title.call($e[0]):o.title)},tip:function(){return this.$tip=this.$tip||$(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(e){var self=$(e.currentTarget)[this.type](this._options).data(this.type);self[self.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var old=$.fn.tooltip;$.fn.tooltip=function(option){return this.each(function(){var $this=$(this),data=$this.data("tooltip"),options="object"==typeof option&&option;data||$this.data("tooltip",data=new Tooltip(this,options)),"string"==typeof option&&data[option]()})},$.fn.tooltip.Constructor=Tooltip,$.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover",title:"",delay:0,html:!1},$.fn.tooltip.noConflict=function(){return $.fn.tooltip=old,this}}(window.jQuery),!function($){"use strict";var Popover=function(element,options){this.init("popover",element,options)};Popover.prototype=$.extend({},$.fn.tooltip.Constructor.prototype,{constructor:Popover,setContent:function(){var $tip=this.tip(),title=this.getTitle(),content=this.getContent();$tip.find(".popover-title")[this.options.html?"html":"text"](title),$tip.find(".popover-content")[this.options.html?"html":"text"](content),$tip.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var content,$e=this.$element,o=this.options;return content=$e.attr("data-content")||("function"==typeof o.content?o.content.call($e[0]):o.content)},tip:function(){return this.$tip||(this.$tip=$(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var old=$.fn.popover;$.fn.popover=function(option){return this.each(function(){var $this=$(this),data=$this.data("popover"),options="object"==typeof option&&option;data||$this.data("popover",data=new Popover(this,options)),"string"==typeof option&&data[option]()})},$.fn.popover.Constructor=Popover,$.fn.popover.defaults=$.extend({},$.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"></div></div></div>'}),$.fn.popover.noConflict=function(){return $.fn.popover=old,this}}(window.jQuery),!function($){"use strict";function ScrollSpy(element,options){var href,process=$.proxy(this.process,this),$element=$(element).is("body")?$(window):$(element);this.options=$.extend({},$.fn.scrollspy.defaults,options),this.$scrollElement=$element.on("scroll.scroll-spy.data-api",process),this.selector=(this.options.target||(href=$(element).attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=$("body"),this.refresh(),this.process()}ScrollSpy.prototype={constructor:ScrollSpy,refresh:function(){var $targets,self=this;this.offsets=$([]),this.targets=$([]),$targets=this.$body.find(this.selector).map(function(){var $el=$(this),href=$el.data("target")||$el.attr("href"),$href=/^#\w/.test(href)&&$(href);return $href&&$href.length&&[[$href.position().top+self.$scrollElement.scrollTop(),href]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){self.offsets.push(this[0]),self.targets.push(this[1])})},process:function(){var i,scrollTop=this.$scrollElement.scrollTop()+this.options.offset,scrollHeight=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,maxScroll=scrollHeight-this.$scrollElement.height(),offsets=this.offsets,targets=this.targets,activeTarget=this.activeTarget;if(scrollTop>=maxScroll)return activeTarget!=(i=targets.last()[0])&&this.activate(i);for(i=offsets.length;i--;)activeTarget!=targets[i]&&scrollTop>=offsets[i]&&(!offsets[i+1]||offsets[i+1]>=scrollTop)&&this.activate(targets[i])},activate:function(target){var active,selector;this.activeTarget=target,$(this.selector).parent(".active").removeClass("active"),selector=this.selector+'[data-target="'+target+'"],'+this.selector+'[href="'+target+'"]',active=$(selector).parent("li").addClass("active"),active.parent(".dropdown-menu").length&&(active=active.closest("li.dropdown").addClass("active")),active.trigger("activate")}};var old=$.fn.scrollspy;$.fn.scrollspy=function(option){return this.each(function(){var $this=$(this),data=$this.data("scrollspy"),options="object"==typeof option&&option;data||$this.data("scrollspy",data=new ScrollSpy(this,options)),"string"==typeof option&&data[option]()})},$.fn.scrollspy.Constructor=ScrollSpy,$.fn.scrollspy.defaults={offset:10},$.fn.scrollspy.noConflict=function(){return $.fn.scrollspy=old,this},$(window).on("load",function(){$('[data-spy="scroll"]').each(function(){var $spy=$(this);$spy.scrollspy($spy.data())})})}(window.jQuery),!function($){"use strict";var Tab=function(element){this.element=$(element)};Tab.prototype={constructor:Tab,show:function(){var previous,$target,e,$this=this.element,$ul=$this.closest("ul:not(.dropdown-menu)"),selector=$this.attr("data-target");selector||(selector=$this.attr("href"),selector=selector&&selector.replace(/.*(?=#[^\s]*$)/,"")),$this.parent("li").hasClass("active")||(previous=$ul.find(".active:last a")[0],e=$.Event("show",{relatedTarget:previous}),$this.trigger(e),e.isDefaultPrevented()||($target=$(selector),this.activate($this.parent("li"),$ul),this.activate($target,$target.parent(),function(){$this.trigger({type:"shown",relatedTarget:previous})})))},activate:function(element,container,callback){function next(){$active.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),element.addClass("active"),transition?(element[0].offsetWidth,element.addClass("in")):element.removeClass("fade"),element.parent(".dropdown-menu")&&element.closest("li.dropdown").addClass("active"),callback&&callback()}var $active=container.find("> .active"),transition=callback&&$.support.transition&&$active.hasClass("fade");transition?$active.one($.support.transition.end,next):next(),$active.removeClass("in")}};var old=$.fn.tab;$.fn.tab=function(option){return this.each(function(){var $this=$(this),data=$this.data("tab");data||$this.data("tab",data=new Tab(this)),"string"==typeof option&&data[option]()})},$.fn.tab.Constructor=Tab,$.fn.tab.noConflict=function(){return $.fn.tab=old,this},$(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(e){e.preventDefault(),$(this).tab("show")})}(window.jQuery),!function($){"use strict";var Typeahead=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.typeahead.defaults,options),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=$(this.options.menu),this.shown=!1,this.listen()};Typeahead.prototype={constructor:Typeahead,select:function(){var val=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(val)).change(),this.hide()},updater:function(item){return item},show:function(){var pos=$.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:pos.top+pos.height,left:pos.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(){var items;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(items=$.isFunction(this.source)?this.source(this.query,$.proxy(this.process,this)):this.source,items?this.process(items):this)},process:function(items){var that=this;return items=$.grep(items,function(item){return that.matcher(item)}),items=this.sorter(items),items.length?this.render(items.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(item){return~item.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(items){for(var item,beginswith=[],caseSensitive=[],caseInsensitive=[];item=items.shift();)item.toLowerCase().indexOf(this.query.toLowerCase())?~item.indexOf(this.query)?caseSensitive.push(item):caseInsensitive.push(item):beginswith.push(item);return beginswith.concat(caseSensitive,caseInsensitive)},highlighter:function(item){var query=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return item.replace(RegExp("("+query+")","ig"),function($1,match){return"<strong>"+match+"</strong>"})},render:function(items){var that=this;return items=$(items).map(function(i,item){return i=$(that.options.item).attr("data-value",item),i.find("a").html(that.highlighter(item)),i[0]}),items.first().addClass("active"),this.$menu.html(items),this},next:function(){var active=this.$menu.find(".active").removeClass("active"),next=active.next();next.length||(next=$(this.$menu.find("li")[0])),next.addClass("active")},prev:function(){var active=this.$menu.find(".active").removeClass("active"),prev=active.prev();prev.length||(prev=this.$menu.find("li").last()),prev.addClass("active")},listen:function(){this.$element.on("blur",$.proxy(this.blur,this)).on("keypress",$.proxy(this.keypress,this)).on("keyup",$.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",$.proxy(this.keydown,this)),this.$menu.on("click",$.proxy(this.click,this)).on("mouseenter","li",$.proxy(this.mouseenter,this))},eventSupported:function(eventName){var isSupported=eventName in this.$element;return isSupported||(this.$element.setAttribute(eventName,"return;"),isSupported="function"==typeof this.$element[eventName]),isSupported},move:function(e){if(this.shown){switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()}},keydown:function(e){this.suppressKeyPressRepeat=~$.inArray(e.keyCode,[40,38,9,13,27]),this.move(e)},keypress:function(e){this.suppressKeyPressRepeat||this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},blur:function(){var that=this;setTimeout(function(){that.hide()},150)},click:function(e){e.stopPropagation(),e.preventDefault(),this.select()},mouseenter:function(e){this.$menu.find(".active").removeClass("active"),$(e.currentTarget).addClass("active")}};var old=$.fn.typeahead;$.fn.typeahead=function(option){return this.each(function(){var $this=$(this),data=$this.data("typeahead"),options="object"==typeof option&&option;data||$this.data("typeahead",data=new Typeahead(this,options)),"string"==typeof option&&data[option]()})},$.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},$.fn.typeahead.Constructor=Typeahead,$.fn.typeahead.noConflict=function(){return $.fn.typeahead=old,this},$(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(e){var $this=$(this);$this.data("typeahead")||(e.preventDefault(),$this.typeahead($this.data()))})}(window.jQuery),!function($){"use strict";var Affix=function(element,options){this.options=$.extend({},$.fn.affix.defaults,options),this.$window=$(window).on("scroll.affix.data-api",$.proxy(this.checkPosition,this)).on("click.affix.data-api",$.proxy(function(){setTimeout($.proxy(this.checkPosition,this),1)},this)),this.$element=$(element),this.checkPosition()};Affix.prototype.checkPosition=function(){if(this.$element.is(":visible")){var affix,scrollHeight=$(document).height(),scrollTop=this.$window.scrollTop(),position=this.$element.offset(),offset=this.options.offset,offsetBottom=offset.bottom,offsetTop=offset.top,reset="affix affix-top affix-bottom";"object"!=typeof offset&&(offsetBottom=offsetTop=offset),"function"==typeof offsetTop&&(offsetTop=offset.top()),"function"==typeof offsetBottom&&(offsetBottom=offset.bottom()),affix=null!=this.unpin&&scrollTop+this.unpin<=position.top?!1:null!=offsetBottom&&position.top+this.$element.height()>=scrollHeight-offsetBottom?"bottom":null!=offsetTop&&offsetTop>=scrollTop?"top":!1,this.affixed!==affix&&(this.affixed=affix,this.unpin="bottom"==affix?position.top-scrollTop:null,this.$element.removeClass(reset).addClass("affix"+(affix?"-"+affix:"")))}};var old=$.fn.affix;$.fn.affix=function(option){return this.each(function(){var $this=$(this),data=$this.data("affix"),options="object"==typeof option&&option;data||$this.data("affix",data=new Affix(this,options)),"string"==typeof option&&data[option]()})},$.fn.affix.Constructor=Affix,$.fn.affix.defaults={offset:0},$.fn.affix.noConflict=function(){return $.fn.affix=old,this},$(window).on("load",function(){$('[data-spy="affix"]').each(function(){var $spy=$(this),data=$spy.data();data.offset=data.offset||{},data.offsetBottom&&(data.offset.bottom=data.offsetBottom),data.offsetTop&&(data.offset.top=data.offsetTop),$spy.affix(data)})})}(window.jQuery);
\ No newline at end of file
diff --git a/docs/tutorial/webpages/css/main.css b/docs/tutorial/webpages/css/main.css
new file mode 100644
index 0000000..cf7862f
--- /dev/null
+++ b/docs/tutorial/webpages/css/main.css
@@ -0,0 +1,38 @@
+body {
+  margin-left: 5px;
+  margin-right: 5px;
+  margin-bottom: 10px;
+}
+
+div.section {
+    margin-left: 10px;
+}
+
+div.short {
+    border-top: 1px solid #EEE;
+    border-bottom: none;
+    margin-bottom: 10px;
+}
+
+div.page_header {
+    border-bottom: none;
+    border-top: 1px solid #EEE;
+}
+
+div.top_liner {
+    height: 42px;
+    background: #888;
+}
+div.bottom_liner {
+    height: 12pt;
+    padding-left: 5px;
+    padding-bottom:5px;
+    padding-top:5px;
+}
+
+li.heading {
+    margin-left: 5px;
+    margin-top: 8px;
+    font-size: 16pt;
+    color: #66D;
+}
diff --git a/docs/tutorial/webpages/encryption-checker-cmd.html b/docs/tutorial/webpages/encryption-checker-cmd.html
new file mode 100644
index 0000000..0dd66a2
--- /dev/null
+++ b/docs/tutorial/webpages/encryption-checker-cmd.html
@@ -0,0 +1,241 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta name="generator" content=
+  "HTML Tidy for Linux (vers 25 March 2009), see www.w3.org">
+
+  <title>Checker Framework Tutorial - Writing an Encryption Checker -
+  Command Line</title>
+  <link href="bootstrap/css/bootstrap.css" rel="stylesheet" type=
+  "text/css">
+  <script type="text/javascript" src="bootstrap/js/bootstrap.min.js">
+</script>
+  <link href="css/main.css" rel="stylesheet" type="text/css">
+  <link rel="icon" type="image/png" href=
+  "https://checkerframework.org/favicon-checkerframework.png">
+  </head>
+
+<body>
+  <div class="top_liner"></div>
+
+  <div class="navbar navbar-inverse navbar-fixed-top" style=
+  "border-bottom: 1px solid #66d;">
+    <div class="navbar-inner">
+      <div class="contained">
+        <ul class="nav">
+          <li class="heading">Checker Framework:</li>
+
+          <li><a href="https://checkerframework.org/">Main Site</a></li>
+
+          <li><a href=
+          "https://checkerframework.org/manual/">
+          Manual</a></li>
+
+          <li><a href=
+          "https://groups.google.com/forum/#!forum/checker-framework-discuss">
+          Discussion List</a></li>
+
+          <li><a href=
+          "https://github.com/typetools/checker-framework/issues">Issue
+          Tracker</a></li>
+
+          <li><a href=
+          "https://github.com/typetools/checker-framework">Source
+          Code</a></li>
+
+          <li class="active"><a href=
+          "https://checkerframework.org/tutorial/">Tutorial</a></li>
+        </ul>
+      </div>
+    </div>
+  </div><img src="https://checkerframework.org/CFLogo.png" alt="Checker Framework logo">
+
+  <div class="page-header short" style=
+  "border-bottom: 1px solid #EEE; border-top: none;">
+    <h1>Checker Framework Tutorial</h1>
+
+    <h2><small>Previous <a href="security-error-cmd.html">Finding a
+    Security Error</a>, Download <a href="../sourcefiles.zip">Example
+    Sources</a></small></h2>
+  </div>
+
+  <div id="top">
+    <div class="page-header short" style="border-top: none;">
+      <h2>Writing an Encryption Checker
+      <small><em>Optional</em></small></h2>
+    </div>
+  </div>
+
+  <div id="introduction">
+    <p>This section of the tutorial is only for those who are interested
+    in writing their own type-checkers. Others may skip this
+    section.</p><!--Copied from the manual-->
+
+    <p>Although the Checker Framework ships with <a href=
+    "https://checkerframework.org/manual/#introduction">
+    many checkers</a>, you may wish to write your own checker because
+    there are other run-time problems you wish to prevent.  If you do
+    not wish to write a new type-checker, feel free to skip this section
+    of the tutorial.</p>
+  </div>
+
+  <div id="outline">
+    <div class="well">
+      <h5>Outline</h5>
+
+      <ol>
+        <li><a href="#setup">An Encryption Checker</a>
+        </li>
+
+        <li><a href="#annotation">Write type annotation
+        definitions</a></li>
+
+        <li><a href="#run1">Run the Encryption Checker &mdash; 2 errors</a></li>
+
+        <li><a href="#error1">Suppress the first error</a></li>
+
+        <li><a href="#run2">Re-run the Encryption Checker &mdash; 1 error</a></li>
+
+        <li><a href="#error2">Correct the second error</a></li>
+
+        <li><a href="#run3">Re-run the Encryption Checker &mdash; no errors</a></li>
+
+        <li><a href="#learnmore">Learn more about writing your own checker</a></li>
+
+      </ol>
+    </div>
+  </div>
+
+  <div id="setup">
+    <h4>1. An Encryption Checker</h4>
+
+    <p>As an example, suppose that you wish to only allow encrypted
+    information to be sent over the internet. To do so, you can write an
+    Encryption Checker.</p>
+  </div>
+
+  <div id="annotation">
+    <h4>2. Write the type annotation definitions</h4>
+
+    <p>For this example, the annotation definitions have already been
+    written for you and appear in files <code>Encrypted.java</code>,
+    <code>PossiblyUnencrypted.java</code>, and
+    <code>PolyEncrypted.java</code>.</p>
+
+    <p><b>Compile the annotation definitions.</b></p>
+    <pre>
+$ <strong>javacheck myqual/Encrypted.java myqual/PossiblyUnencrypted.java myqual/PolyEncrypted.java</strong>
+</pre>
+  </div>
+
+  <div id="run1">
+    <h4>3. Run the Encryption Checker</h4>
+
+    <p>The <code>@Encrypted</code> annotations have already been written
+    in file EncryptionDemo.java. The default for types without annotations is
+    <code>@PossiblyUnencrypted</code>.</p>
+
+    <p><b>Invoke the compiler</b> with the Subtyping Checker.
+    Specify the @Encrypted, @PossiblyUnencrypted, and @PolyEncrypted annotations using the -Aquals
+    command-line option, and add the Encrypted, PossiblyUnencrypted, and PolyEncrypted
+    classfiles to the processor classpath:</p>
+    <pre>
+$ <strong>javacheck -processor org.checkerframework.common.subtyping.SubtypingChecker -Aquals=myqual.Encrypted,myqual.PossiblyUnencrypted,myqual.PolyEncrypted encrypted/EncryptionDemo.java</strong>
+encrypted/EncryptionDemo.java:21: error: [assignment] incompatible types in assignment.
+        @Encrypted int encryptInt = (character + OFFSET) % Character.MAX_VALUE ;
+                                                         ^
+  found   : @PossiblyUnencrypted int
+  required: @Encrypted int
+encrypted/EncryptionDemo.java:32: error: [argument] incompatible types in argument.
+        sendOverInternet(password);
+                         ^
+  found   : @PossiblyUnencrypted String
+  required: @Encrypted String
+2 errors
+</pre>
+  </div>
+
+  <div id="error1">
+    <h4>4. Suppress the First Error</h4>
+
+    <p>The first error needs to be suppressed, because the string on the
+    left is considered "encrypted" in this encryption scheme. All
+    <code>@SuppressWarnings</code> should have a comment explaining why
+    suppressing the warning is the correct action. See the correction
+    below.</p>
+    <pre>
+<b>@SuppressWarnings("encrypted")  // this is the encryption algorithm</b>
+private @Encrypted char encryptCharacter(char character) {
+</pre>
+  </div>
+
+  <div id="run2">
+    <h4>5. Re-run the Encryption Checker</h4>
+
+    <p>You will see the following error:</p>
+    <pre>
+$ <strong>javacheck -processor org.checkerframework.common.subtyping.SubtypingChecker -Aquals=myqual.Encrypted,myqual.PossiblyUnencrypted,myqual.PolyEncrypted encrypted/EncryptionDemo.java</strong>
+encrypted/EncryptionDemo.java:34: error: [argument] incompatible types in argument.
+        sendOverInternet(password);
+                         ^
+  found   : @PossiblyUnencrypted String
+  required: @Encrypted String
+1 error
+</pre>
+
+    <p>This is a real error, because the programmer is trying to send a
+    password over the Internet without encrypting it first.</p>
+  </div>
+
+  <div id="error2">
+    <h4>6. Correct the Second Error</h4>
+
+    <p>The password should be encrypted before it is sent over the
+    Internet. The correction is below.</p>
+    <pre>
+void sendPassword() {
+    String password = getUserPassword();
+    sendOverInternet(<b>encrypt(</b>password<b>)</b>);
+}
+</pre>
+  </div>
+
+  <div id="run3">
+    <h4>7. Re-run the Encryption Checker</h4>
+
+    <pre>
+$ <strong>javacheck -processor org.checkerframework.common.subtyping.SubtypingChecker -Aquals=myqual.Encrypted,myqual.PossiblyUnencrypted,myqual.PolyEncrypted encrypted/EncryptionDemo.java</strong>
+</pre>
+
+    <p>There should be no errors.</p>
+
+  </div>
+
+  <div id="learnmore">
+    <h4>8. Learn more about writing your own checker</h4>
+
+    <p>For further explanations, see the Checker Framework manual, chapter
+    <a href=
+    "https://checkerframework.org/manual/#creating-a-checker">How
+    to create a new checker</a>.</p>
+  </div>
+
+  <div id="end">
+    <div class="page-header short">
+      <h2>The end of the Checker Framework Tutorial <small><br/>
+      <strong>Return
+      to the <a href="../index.html">main page</a> of the
+      Tutorial.</strong></small></h2>
+    </div>
+  </div>
+<!--
+<div class="bottom_liner well">
+    <a href="#">Top</a>
+</div>
+-->
+  <!--  LocalWords:  Plugin plugin VM SDK plugins quals classpath
+ -->
+  <!--  LocalWords:  NullnessChecker plugin's hg
+ -->
+</body>
+</html>
diff --git a/docs/tutorial/webpages/get-started-cmd.html b/docs/tutorial/webpages/get-started-cmd.html
new file mode 100644
index 0000000..97f7a21
--- /dev/null
+++ b/docs/tutorial/webpages/get-started-cmd.html
@@ -0,0 +1,182 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta name="generator" content=
+  "HTML Tidy for Linux (vers 25 March 2009), see www.w3.org">
+
+  <title>Checker Framework Tutorial - Getting Started - Command
+  Line</title>
+  <link href="bootstrap/css/bootstrap.css" rel="stylesheet" type=
+  "text/css">
+  <script type="text/javascript" src="bootstrap/js/bootstrap.min.js">
+</script>
+  <link href="css/main.css" rel="stylesheet" type="text/css">
+  <link rel="icon" type="image/png" href=
+  "https://checkerframework.org/favicon-checkerframework.png">
+</head>
+
+<body>
+  <div class="top_liner"></div>
+
+  <div class="navbar navbar-inverse navbar-fixed-top" style=
+  "border-bottom: 1px solid #66d;">
+    <div class="navbar-inner">
+      <div class="contained">
+        <ul class="nav">
+          <li class="heading">Checker Framework:</li>
+
+          <li><a href="https://checkerframework.org/">Main Site</a></li>
+
+          <li><a href=
+          "https://checkerframework.org/manual/">
+          Manual</a></li>
+
+          <li><a href=
+          "https://groups.google.com/forum/#!forum/checker-framework-discuss">
+          Discussion List</a></li>
+
+          <li><a href=
+          "https://github.com/typetools/checker-framework/issues">Issue
+          Tracker</a></li>
+
+          <li><a href=
+          "https://github.com/typetools/checker-framework">Source
+          Code</a></li>
+
+          <li class="active"><a href=
+          "https://checkerframework.org/tutorial/">Tutorial</a></li>
+        </ul>
+      </div>
+    </div>
+  </div><img src="https://checkerframework.org/CFLogo.png" alt="Checker Framework logo">
+
+  <div class="page-header short" style=
+  "border-bottom: 1px solid #EEE; border-top: none;">
+    <h1>Checker Framework Tutorial</h1>
+  </div>
+
+  <div id="gettingstarteed">
+    <div class="page-header short" style="border-top: none;">
+      <h2>Getting Started</h2>
+    </div>
+
+    <div class="section">
+      <p>This page walks you through a simple example to show how to use a
+      type-checker
+      from the command line.
+      It shows how the Nullness
+      Checker can be used to prevent null pointer exceptions.</p>
+    </div>
+
+    <div class="section">
+      <h4>Outline</h4>
+
+      <div class="well">
+        <ol>
+          <li><a href="#view">Manually spot the null pointer
+          exception</a></li>
+
+          <li><a href="#run1">Run the Nullness Checker to see how it can
+          catch this error</a></li>
+
+          <li><a href="#error">Correct the error</a></li>
+
+          <li><a href="#run2">Run the Nullness Checker to verify that
+          there are no more errors</a></li>
+        </ol>
+      </div>
+
+      <div id="view">
+        <h4>1. Spot the null pointer exception</h4>Begin by <b>viewing
+        <a href=
+        "../src/NullnessExample.java">NullnessExample.java</a></b>. (If
+        you have not already, download <a href="../sourcefiles.zip">the
+        source files</a> for the tutorial.)
+        It is a simple Java program with an obvious null pointer exception.
+        <pre>
+public class NullnessExample {
+    public static void main(String[] args) {
+        Object myObject = null;
+        System.out.println(myObject.toString());
+    }
+}
+</pre>
+      </div>
+
+      <div id="run1">
+        <h4>2. Run the Nullness Checker</h4>
+
+        <p><b>Run the Nullness Checker</b> to see how it can warn you
+        about this error at compile time.</p>
+
+        <p>To run the Nullness Checker, run javac with command-line
+        arguments <code>-processor
+        org.checkerframework.checker.nullness.NullnessChecker</code>, as
+        follows.</p>
+
+        <p>(Note:  In this tutorial, the commands that you cut-and-paste
+        to run on the command line appear in bold after a <code>$</code> prompt.)
+        <br/>
+        (Note: You should have already made
+        <code>javacheck</code> <a href=
+        "https://checkerframework.org/manual/#installation">
+        an alias</a> to the Checker Framework compiler.)</p>
+        <pre>
+$ <strong>javacheck -processor org.checkerframework.checker.nullness.NullnessChecker NullnessExample.java</strong>
+</pre>
+
+        <p>The following error will be produced.</p>
+        <pre>
+NullnessExample.java:9: error: [dereference.of.nullable] dereference of possibly-null reference myObject
+        System.out.println(myObject.toString());
+                           ^
+1 error
+</pre>
+      </div>
+
+      <div id="error">
+        <h4>3. Correct the error</h4>
+
+        <p>Edit the code to <b>initialize the <code>myObject</code> variable</b>
+        to some non-null value.</p>
+        <pre>
+public class NullnessExample {
+    public static void main(String[] args) {
+        Object myObject = <b>new Object()</b>;
+        System.out.println(myObject.toString());
+    }
+}
+</pre>
+      </div>
+
+      <div id="run2">
+        <h4>4. Re-run the Nullness Checker</h4>
+        <pre>
+$ <b>javacheck -processor org.checkerframework.checker.nullness.NullnessChecker NullnessExample.java</b>
+</pre>
+
+        <p>No errors should be produced.</p>
+      </div>
+
+      <p>This was a very simple example to show how to use the Checker Framework
+      from the command line.
+      The next example is a little more complex.</p>
+    </div>
+  </div>
+
+  <div id="end">
+    <div class="page-header short">
+      <h2><small>Next, try <a href="user-input-cmd.html">Validating
+      User Input</a>, an example using the Regex Checker.</small></h2>
+    </div>
+  </div><!--
+<div class="bottom_liner well">
+    <a href="#">Top</a>
+</div>
+-->
+  <!--  LocalWords:  Plugin plugin VM SDK plugins quals classpath
+ -->
+  <!--  LocalWords:  NullnessChecker plugin's hg
+ -->
+</body>
+</html>
diff --git a/docs/tutorial/webpages/images/checkers_box.png b/docs/tutorial/webpages/images/checkers_box.png
new file mode 100644
index 0000000..c6c58fa
--- /dev/null
+++ b/docs/tutorial/webpages/images/checkers_box.png
Binary files differ
diff --git a/docs/tutorial/webpages/images/other_preferences.png b/docs/tutorial/webpages/images/other_preferences.png
new file mode 100644
index 0000000..5b3fb0a
--- /dev/null
+++ b/docs/tutorial/webpages/images/other_preferences.png
Binary files differ
diff --git a/docs/tutorial/webpages/images/selected_checkers.png b/docs/tutorial/webpages/images/selected_checkers.png
new file mode 100644
index 0000000..35bd10d
--- /dev/null
+++ b/docs/tutorial/webpages/images/selected_checkers.png
Binary files differ
diff --git a/docs/tutorial/webpages/security-error-cmd.html b/docs/tutorial/webpages/security-error-cmd.html
new file mode 100644
index 0000000..3d44a29
--- /dev/null
+++ b/docs/tutorial/webpages/security-error-cmd.html
@@ -0,0 +1,235 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta name="generator" content=
+  "HTML Tidy for Linux (vers 25 March 2009), see www.w3.org">
+
+  <title>Checker Framework Tutorial - Security Error - Command
+  Line</title>
+  <link href="bootstrap/css/bootstrap.css" rel="stylesheet" type=
+  "text/css">
+  <script type="text/javascript" src="bootstrap/js/bootstrap.min.js">
+</script>
+  <link href="css/main.css" rel="stylesheet" type="text/css">
+  <link rel="icon" type="image/png" href=
+  "https://checkerframework.org/favicon-checkerframework.png">
+  </head>
+
+<body>
+  <div class="top_liner"></div>
+
+  <div class="navbar navbar-inverse navbar-fixed-top" style=
+  "border-bottom: 1px solid #66d;">
+    <div class="navbar-inner">
+      <div class="contained">
+        <ul class="nav">
+          <li class="heading">Checker Framework:</li>
+
+          <li><a href="https://checkerframework.org/">Main Site</a></li>
+
+          <li><a href=
+          "https://checkerframework.org/manual/">
+          Manual</a></li>
+
+          <li><a href=
+          "https://groups.google.com/forum/#!forum/checker-framework-discuss">
+          Discussion List</a></li>
+
+          <li><a href=
+          "https://github.com/typetools/checker-framework/issues">Issue
+          Tracker</a></li>
+
+          <li><a href=
+          "https://github.com/typetools/checker-framework">Source
+          Code</a></li>
+
+          <li class="active"><a href=
+          "https://checkerframework.org/tutorial/">Tutorial</a></li>
+        </ul>
+      </div>
+    </div>
+  </div><img src="https://checkerframework.org/CFLogo.png" alt="Checker Framework logo">
+
+  <div class="page-header short" style=
+  "border-bottom: 1px solid #EEE; border-top: none;">
+    <h1>Checker Framework Tutorial</h1>
+
+    <h2><small>Previous <a href="user-input-cmd.html">Validating User
+    Input</a></small></h2>
+  </div>
+
+  <div id="introduction">
+    <div class="page-header short" style="border-top: none;">
+      <h2>Finding a Security Error</h2>
+    </div>
+
+    <p>This example uses the Tainting Checker to verify that user input
+    does not contain SQL statements, thus preventing SQL injection. (If
+    you have not already done so, download <a href=
+    "../sourcefiles.zip">the tutorial sourcefiles</a>.)</p>
+
+    <div class="well">
+      <h5>Outline</h5>
+
+      <ol>
+        <li><a href="#run1">Run the Tainting Checker &mdash; 1 error
+        found</a></li>
+
+        <li><a href="#error1">Correct the error</a></li>
+
+        <li><a href="#run2">Re-run the Tainting Checker &mdash; a new error is
+        found</a></li>
+
+        <li><a href="#error2">Correct the new error</a></li>
+
+        <li><a href="#run3">Re-run the Tainting Checker &mdash; no errors</a></li>
+      </ol>
+    </div>
+
+    <div id="run1">
+      <h4>1. Run the Tainting Checker &mdash; 1 error found</h4>
+
+      <p>Run the buildfile:
+      <br/> (The Ant buildfile
+      makes use of the <a href=
+      "https://checkerframework.org/manual/#ant-task">
+      Checker Framework support for Ant</a>.)</p>
+      <pre>
+$ <b>cd personalblog-demo</b>
+$ <b>ant</b>
+Buildfile: .../personalblog-demo/build.xml
+
+clean:
+
+check-tainting:
+    [mkdir] Created dir: .../personalblog-demo/bin
+[cf.javac] Compiling 2 source files to .../personalblog-demo/bin
+[cf.javac] javac 1.8.0-jsr308-3.12.0
+[cf.javac] .../personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java:175: error: incompatible types in argument.
+[cf.javac]                     "where post.category like '%", category,
+[cf.javac]                                                    ^
+[cf.javac]   found   : @Tainted String
+[cf.javac]   required: @Untainted String
+[cf.javac] 1 error
+
+BUILD FAILED
+.../personalblog-demo/build.xml:35: Compile failed; see the compiler error output for details.
+
+Total time: 2 seconds
+
+</pre>
+
+      <p>The checker issues an error for <code>getPostsByCategory()</code>
+      because the possibly-tainted string <code>category</code> is used in
+      the query construction. This string could contain SQL statements
+      that could taint the database. The programmer must ensure that
+      <code>category</code> does not contain malicious SQL code.</p>
+    </div>
+
+    <div id="error1">
+      <h4>2. Correct the Error</h4>
+
+      <p>To correct this error, <b>add <code>@Untainted</code></b> to the
+      type of the <code>category</code> parameter.</p>
+      <pre>
+     public List&lt;?&gt; getPostsByCategory(<b>@Untainted</b> String category) throws ServiceException {
+</pre>This forces clients to pass an <code>@Untainted</code> value, which
+was the intention of the designer of the <code>getPostsByCategory</code>
+method.
+    </div>
+
+    <div id="run2">
+      <h4>3. Re-run the Tainting Checker &mdash; a new error is found</h4>
+
+      <p>Run the Tainting Checker again.</p>
+
+      <pre>
+$ <strong>ant</strong>
+Buildfile: .../personalblog-demo/build.xml
+
+clean:
+   [delete] Deleting directory .../personalblog-demo/bin
+
+check-tainting:
+    [mkdir] Created dir: .../personalblog-demo/bin
+[cf.javac] Compiling 2 source files to .../personalblog-demo/bin
+[cf.javac] javac 1.8.0-jsr308-3.12.0
+[cf.javac] .../personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java:58: error: incompatible types in argument.
+[cf.javac]                          pblog.getPostsByCategory(reqCategory));
+[cf.javac]                                                   ^
+[cf.javac]   found   : @Tainted String
+[cf.javac]   required: @Untainted String
+[cf.javac] 1 error
+
+BUILD FAILED
+.../personalblog-demo/build.xml:35: Compile failed; see the compiler error output for details.
+
+Total time: 2 seconds
+
+</pre>
+
+      <p>There is an error in <code>ReadAction.executeSub()</code>, which
+      is a client of <code>getPostsByCategory</code>. The
+      <code>reqCategory</code> is accepted from the user (from request
+      object) without validation.</p>
+    </div>
+
+    <div id="error2">
+      <h4>4. Correct the New Error</h4>To correct, <b>use the
+      <code>validate</code> method</b> as shown below.
+      <pre>
+    String reqCategory = <b>validate(</b>cleanNull(request.getParameter("cat"))<b>)</b>;
+</pre>
+    </div>
+
+    <div id="run3">
+      <h4>5. Re-run the Tainting Checker &mdash; no errors</h4>
+
+      <p>There should be no errors.</p>
+      <pre>
+$ <strong>ant</strong>
+Buildfile: .../personalblog-demo/build.xml
+
+clean:
+   [delete] Deleting directory .../personalblog-demo/bin
+
+check-tainting:
+    [mkdir] Created dir: .../personalblog-demo/bin
+[cf.javac] Compiling 2 source files to .../personalblog-demo/bin
+[cf.javac] javac 1.8.0-jsr308-3.12.0
+
+BUILD SUCCESSFUL
+Total time: 2 seconds
+</pre>
+
+      <p>You are done with the personalblog-demo project,
+      so return to the parent directory</p>
+
+      <pre>
+$ <strong>cd ..</strong>
+</pre>
+
+      <p>For a complete discussion of how to use the Tainting Checker,
+      please read the <a href=
+      "https://checkerframework.org/manual/#tainting-checker">
+      Tainting Checker chapter</a> in the Checker Framework manual.</p>
+    </div>
+  </div>
+
+  <div id="installation">
+    <div class="page-header short">
+      <h2><small>Next, try <a href="encryption-checker-cmd.html">Writing
+      an Encryption Checker</a> or return to the <a href=
+      "../index.html">main page</a> of the Tutorial.</small></h2>
+    </div>
+  </div><!--
+<div class="bottom_liner well">
+    <a href="#">Top</a>
+</div>
+-->
+  <!--  LocalWords:  Plugin plugin VM SDK plugins quals classpath
+ -->
+  <!--  LocalWords:  NullnessChecker plugin's hg
+ -->
+</body>
+</html>
diff --git a/docs/tutorial/webpages/user-input-cmd.html b/docs/tutorial/webpages/user-input-cmd.html
new file mode 100644
index 0000000..9de53e3
--- /dev/null
+++ b/docs/tutorial/webpages/user-input-cmd.html
@@ -0,0 +1,247 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta name="generator" content=
+  "HTML Tidy for Linux (vers 25 March 2009), see www.w3.org">
+
+  <title>Checker Framework Tutorial - Validating User Input - Command
+  Line</title>
+  <link href="bootstrap/css/bootstrap.css" rel="stylesheet" type=
+  "text/css">
+  <script type="text/javascript" src="bootstrap/js/bootstrap.min.js">
+</script>
+  <link href="css/main.css" rel="stylesheet" type="text/css">
+  <link rel="icon" type="image/png" href=
+  "https://checkerframework.org/favicon-checkerframework.png">
+  </head>
+
+<body>
+  <div class="top_liner"></div>
+
+  <div class="navbar navbar-inverse navbar-fixed-top" style=
+  "border-bottom: 1px solid #66d;">
+    <div class="navbar-inner">
+      <div class="contained">
+        <ul class="nav">
+          <li class="heading">Checker Framework:</li>
+
+          <li><a href="https://checkerframework.org/">Main Site</a></li>
+
+          <li><a href=
+          "https://checkerframework.org/manual/">
+          Manual</a></li>
+
+          <li><a href=
+          "https://groups.google.com/forum/#!forum/checker-framework-discuss">
+          Discussion List</a></li>
+
+          <li><a href=
+          "https://github.com/typetools/checker-framework/issues">Issue
+          Tracker</a></li>
+
+          <li><a href=
+          "https://github.com/typetools/checker-framework">Source
+          Code</a></li>
+
+          <li class="active"><a href=
+          "https://checkerframework.org/tutorial/">Tutorial</a></li>
+        </ul>
+      </div>
+    </div>
+  </div><img src="https://checkerframework.org/CFLogo.png" alt="Checker Framework logo">
+
+  <div class="page-header short" style=
+  "border-bottom: 1px solid #EEE; border-top: none;">
+    <h1>Checker Framework Tutorial</h1>
+
+    <h2><small>Previous <a href="get-started-cmd.html">Getting
+    Started</a></small></h2>
+  </div>
+
+  <div id="introduction">
+    <div class="page-header short" style="border-top: none;">
+      <h2>Validating User Input</h2>
+    </div>
+
+    <p>This part of the tutorial shows how the Checker Framework can
+    detect and help correct missing input validation.</p>
+
+    <div class="section">
+      <div class="well">
+        <h5>Outline</h5>
+
+        <ol>
+          <li><a href="#regexexample">The RegexExample program</a></li>
+
+          <li><a href="#runex1">Run the example with an invalid regular
+          expression</a></li>
+
+          <li><a href="#run1">Run the Regex Checker to see how it could
+          have prevented the runtime error</a></li>
+
+          <li><a href="#validate">Validate the user input</a></li>
+
+          <li><a href="#run2">Run the Regex Checker to verify that the
+          error is corrected</a></li>
+
+          <li><a href="#runex2">Run the example with an invalid regular
+          expression to see the warning</a></li>
+        </ol>
+      </div>
+    </div>
+
+    <div class="section">
+      <div id="regexexample">
+        <h4>1. The RegexExample program</h4>
+
+        <p>If you have not already done so, download
+          <a href="../sourcefiles.zip">the source files</a> for the tutorial.
+        </p>
+
+        <p>
+        The <a href="../src/RegexExample.java">RegexExample.java</a>
+        program is called with two arguments: a regular expression and a
+        string. The program prints the text from the string that matches
+        the first capturing group in the regular expression.
+        <br/><b>Compile the program</b>:
+        </p>
+        <pre>
+$ <strong>javac RegexExample.java</strong>
+</pre>
+      </div>
+
+      <div id="runex1">
+        <h4>2. Run the RegexExample program</h4>
+
+        <p><b>Run the program</b> with a <strong class=
+        "text-success">valid</strong> regular expression and a matching
+        string:
+        </p>
+        <pre>
+$ <strong>java RegexExample '[01]?\d-([0123]?\d)-\d{4}+' '01-24-2013'</strong>
+Group 1: 24
+</pre>
+
+        <p><b>Run the program</b> with an <strong class=
+        "text-error">invalid</strong> regular expression and any
+        string:</p>
+        <pre>
+$ <strong>java RegexExample '[01]?[\d-[0123]?\d-\d{4}+' '01-24-2013'</strong>
+Exception in thread "main" java.util.regex.PatternSyntaxException: Unclosed character class near index 24
+[01]?[d-[0123]?d-d{4}+
+                     ^
+        at java.util.regex.Pattern.error(Pattern.java:1924)
+        at java.util.regex.Pattern.clazz(Pattern.java:2493)
+        at java.util.regex.Pattern.sequence(Pattern.java:2030)
+        at java.util.regex.Pattern.expr(Pattern.java:1964)
+        at java.util.regex.Pattern.compile(Pattern.java:1665)
+        at java.util.regex.Pattern.&lt;init&gt;(Pattern.java:1337)
+        at java.util.regex.Pattern.compile(Pattern.java:1022)
+        at RegexExample.main(RegexExample.java:13)
+</pre>
+
+        <p>Good programming style dictates that the user should not see a
+        stack trace, even if the user supplies invalid output.</p>
+      </div>
+
+      <div id="run1">
+        <h4>3. Run the Regex Checker</h4>
+
+        <p>The Regex Checker prevents, at compile time, use of
+        syntactically invalid regular expressions and access of invalid
+        capturing groups. In other words, it prevents you from writing code
+        that would throw certain exceptions at run time. Next <b>run the Regex
+        Checker</b> to see how it could have spotted this issue at compile
+        time.
+        </p>
+        <pre>
+$ <strong>javacheck -processor org.checkerframework.checker.regex.RegexChecker RegexExample.java</strong>
+RegexExample.java:13: error: [argument] incompatible types in argument.
+        Pattern pat = Pattern.compile(regex);
+                                      ^
+  found   : String
+  required: @Regex String
+1 errors
+</pre>
+
+        <p>The "incompatible types" warning indicates that variable
+        <code>regex</code> is not of type <code>@Regex String</code> which
+        is required for strings passed to <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Pattern.html#compile(java.lang.String)"><code>Pattern.compile()</code></a>.</p>
+      </div>
+
+      <div id="validate">
+        <h4>4. Fix the Code</h4>
+
+        <p>The right way to fix the problems is for the code to issue a
+        user-friendly message at run time.  It should <b>verify the user
+        input</b> using the
+        <a href="https://checkerframework.org/api/org/checkerframework/checker/regex/RegexUtil.html#isRegex-java.lang.String-"><code>RegexUtil.isRegex(String, int)</code></a> method.
+        If the string is not a valid regular
+        expression, it should <b>print an error message and halt</b>.
+        If it <em>is</em> a valid regular expression, perform as before.
+        </p>
+
+        <p>
+        You need to make two changes to <code>RegexExample.java</code>
+        to correctly handle invalid user input.  At the top of the file, add</p>
+        <pre>
+import org.checkerframework.checker.regex.RegexUtil;
+</pre>
+        <p>
+        After variable <code>regex</code> is defined but before it is
+        used (that is, before <code>Pattern pat = Pattern.compile(regex);</code>), add</p>
+<pre>
+    if (!RegexUtil.isRegex(regex, 1)) {
+        System.out.println("Input is not a regular expression \"" + regex
+            + "\": " + RegexUtil.regexException(regex).getMessage());
+        System.exit(1);
+    }
+</pre>
+      </div>
+
+      <div id="run2">
+        <h4>5. Re-run the Regex Checker</h4>
+
+        <pre>
+$ <strong>javacheck -processor org.checkerframework.checker.regex.RegexChecker RegexExample.java</strong>
+</pre>
+
+        <p>There should be no warnings.  This shows that the code will not
+        throw a PatternSyntaxException at compile time.</p>
+      </div>
+
+      <div id="runex2">
+        <h4>6. Run the Example</h4>
+
+        <p>Run the program as before (but adding <code>checker-qual.jar</code> to the classpath)
+        to verify that the program
+        prints a user-friendly warning.</p>
+        <pre>
+$ <strong>java -cp ".:$CHECKERFRAMEWORK/checker/dist/checker-qual.jar" RegexExample '[01]?[\d-\([0123]?\d\)-\d{4}+' '01-24-2013'</strong>
+Input is not a regular expression "[01]?[d-([0123]?d)-d{4}+": Illegal character range near index 24
+</pre>
+
+        <p>For a full discussion of the Regex Checker, please see the <a href=
+        "https://checkerframework.org/manual/#regex-checker">
+        Regex Checker chapter</a> of the Checker Framework manual.</p>
+      </div>
+    </div>
+  </div>
+
+  <div id="next">
+    <div class="page-header short">
+      <h2><small>Next, try <a href="security-error-cmd.html">Finding a
+      Security Error</a>, a complex example using the Tainting
+      Checker.</small></h2>
+    </div>
+  </div><!--
+<div class="bottom_liner well">
+    <a href="#">Top</a>
+</div>
+-->
+  <!--  LocalWords:  Plugin plugin VM SDK plugins quals classpath
+ -->
+  <!--  LocalWords:  NullnessChecker plugin's hg
+ -->
+</body>
+</html>
diff --git a/framework-test/build.gradle b/framework-test/build.gradle
new file mode 100644
index 0000000..5a4cefb
--- /dev/null
+++ b/framework-test/build.gradle
@@ -0,0 +1,50 @@
+import org.gradle.internal.jvm.Jvm
+
+sourceSets {
+    taglet
+    tagletJdk11
+}
+
+dependencies {
+    implementation  group: 'junit', name: 'junit', version: '4.13.2'
+    implementation project(':javacutil')
+    implementation project(':checker-qual')
+
+    implementation 'org.plumelib:plume-util:1.5.3'
+
+    if (Jvm.current().toolsJar) {
+        tagletImplementation files(Jvm.current().toolsJar)
+    }
+}
+
+jar.archiveBaseName = 'framework-test'
+
+apply from: rootProject.file("gradle-mvn-push.gradle")
+
+/** Adds information to the publication for uploading to Maven repositories. */
+final frameworkTest(publication) {
+    sharedPublicationConfiguration(publication)
+    publication.from components.java
+    publication.pom {
+        name = 'Checker Framework Testing Library'
+        description = 'framework-test contains utility classes for testing type-checkers\n' +
+                'that are built on the Checker Framework.'
+        licenses {
+            license {
+                name = 'GNU General Public License, version 2 (GPL2), with the classpath exception'
+                url = 'http://www.gnu.org/software/classpath/license.html'
+                distribution = 'repo'
+            }
+        }
+    }
+}
+publishing {
+    publications {
+        frameworkTest(MavenPublication) {
+            frameworkTest it
+        }
+    }
+}
+signing {
+    sign publishing.publications.frameworkTest
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java
new file mode 100644
index 0000000..cb4fdab
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java
@@ -0,0 +1,180 @@
+package org.checkerframework.framework.test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.AbstractProcessor;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Compiles all test files in a test directory together. Use {@link CheckerFrameworkPerFileTest} to
+ * compile each test file in a test directory individually. A {@link
+ * CheckerFrameworkPerDirectoryTest} is faster than an equivalent {@link
+ * CheckerFrameworkPerFileTest}, but can only test that processor errors or warnings are issued.
+ *
+ * <p>To create a {@link CheckerFrameworkPerDirectoryTest}, create a new class that extends this
+ * class. The new class must do the following:
+ *
+ * <ol>
+ *   <li>Declare a constructor taking 1 parameter of type {@code java.util.List<java.io.File>}. This
+ *       is a list of the files that will be compiled.
+ *   <li>Declare the following method:
+ *       <pre>{@code @Parameters public static String [] getTestDirs()}</pre>
+ *       <p>getTestDir must return an array of directories that exist in the test folder. The
+ *       directories can contain more path information (e.g., "myTestDir/moreTests") but note, the
+ *       test suite will find all of the Java test files that exists below the listed directories.
+ *       It is unnecessary to list child directories of a directory you have already listed.
+ * </ol>
+ *
+ * <pre><code>
+ * public class MyTest extends CheckerFrameworkPerDirectoryTest {
+ *   /** {@literal @}param testFiles the files containing test code, which will be type-checked *{@literal /}
+ *   public MyTest(List{@literal <}File{@literal >} testFiles) {
+ *     super(testFiles, MyChecker.class, "", "Anomsgtext");
+ *   }
+ *  {@literal @}Parameters
+ *   public static String [] getTestDirs() {
+ *     return new String[]{"all-systems"};
+ *   }
+ * }
+ * </code></pre>
+ */
+@RunWith(PerDirectorySuite.class)
+public abstract class CheckerFrameworkPerDirectoryTest {
+
+  /** The files containing test code, which will be type-checked. */
+  protected final List<File> testFiles;
+
+  /** The binary names of the checkers to run. */
+  protected final List<@BinaryName String> checkerNames;
+
+  /** The path, relative to currentDir/test to the directory containing test inputs. */
+  protected final String testDir;
+
+  /** Extra options to pass to javac when running the checker. */
+  protected final List<String> checkerOptions;
+
+  /** Extra entries for the classpath. */
+  protected final List<String> classpathExtra;
+
+  /**
+   * Creates a new checker test.
+   *
+   * <p>{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String,
+   * Iterable, Iterable, List, boolean)} adds additional checker options.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   * @param checker the class for the checker to use
+   * @param testDir the path to the directory of test inputs
+   * @param checkerOptions options to pass to the compiler when running tests
+   */
+  protected CheckerFrameworkPerDirectoryTest(
+      List<File> testFiles,
+      Class<? extends AbstractProcessor> checker,
+      String testDir,
+      String... checkerOptions) {
+    this(testFiles, checker, testDir, Collections.emptyList(), checkerOptions);
+  }
+
+  /**
+   * Creates a new checker test.
+   *
+   * <p>{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String,
+   * Iterable, Iterable, List, boolean)} adds additional checker options.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   * @param checker the class for the checker to use
+   * @param testDir the path to the directory of test inputs
+   * @param classpathExtra extra entries for the classpath
+   * @param checkerOptions options to pass to the compiler when running tests
+   */
+  @SuppressWarnings(
+      "signature:argument" // for non-array non-primitive class, getName(): @BinaryName
+  )
+  protected CheckerFrameworkPerDirectoryTest(
+      List<File> testFiles,
+      Class<? extends AbstractProcessor> checker,
+      String testDir,
+      List<String> classpathExtra,
+      String... checkerOptions) {
+    this(
+        testFiles,
+        Collections.singletonList(checker.getName()),
+        testDir,
+        classpathExtra,
+        checkerOptions);
+  }
+
+  /**
+   * Creates a new checker test.
+   *
+   * <p>{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String,
+   * Iterable, Iterable, List, boolean)} adds additional checker options.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   * @param checkerNames the binary names of the checkers to run
+   * @param testDir the path to the directory of test inputs
+   * @param classpathExtra extra entries for the classpath
+   * @param checkerOptions options to pass to the compiler when running tests
+   */
+  protected CheckerFrameworkPerDirectoryTest(
+      List<File> testFiles,
+      List<@BinaryName String> checkerNames,
+      String testDir,
+      List<String> classpathExtra,
+      String... checkerOptions) {
+    this.testFiles = testFiles;
+    this.checkerNames = checkerNames;
+    this.testDir = "tests" + File.separator + testDir;
+    this.classpathExtra = classpathExtra;
+    this.checkerOptions = new ArrayList<>(Arrays.asList(checkerOptions));
+    this.checkerOptions.add("-AajavaChecks");
+  }
+
+  @Test
+  public void run() {
+    boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo();
+    List<String> customizedOptions = customizeOptions(Collections.unmodifiableList(checkerOptions));
+    TestConfiguration config =
+        TestConfigurationBuilder.buildDefaultConfiguration(
+            testDir,
+            testFiles,
+            classpathExtra,
+            checkerNames,
+            customizedOptions,
+            shouldEmitDebugInfo);
+    TypecheckResult testResult = new TypecheckExecutor().runTest(config);
+    TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult);
+    TestUtilities.assertTestDidNotFail(adjustedTestResult);
+  }
+
+  /**
+   * This method is called before issuing assertions about a TypecheckResult. Subclasses can
+   * override it to customize behavior.
+   *
+   * @param testResult a test result to possibly change
+   * @return a TypecheckResult to use instead, which may be the unmodified argument
+   */
+  public TypecheckResult adjustTypecheckResult(TypecheckResult testResult) {
+    return testResult;
+  }
+
+  /**
+   * Override this method if you would like to supply a checker command-line option that depends on
+   * the Java files passed to the test. Those files are available in field {@link #testFiles}.
+   *
+   * <p>If you want to specify the same command-line option for all tests of a particular checker,
+   * then pass it to the {@link #CheckerFrameworkPerDirectoryTest} constructor.
+   *
+   * @param previousOptions the options specified in the constructor of the test previousOptions is
+   *     unmodifiable
+   * @return a new list of options or the original passed through
+   */
+  public List<String> customizeOptions(List<String> previousOptions) {
+    return previousOptions;
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerFileTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerFileTest.java
new file mode 100644
index 0000000..f9b10b6
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerFileTest.java
@@ -0,0 +1,109 @@
+package org.checkerframework.framework.test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.AbstractProcessor;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Compiles all test files individually. Use {@link CheckerFrameworkPerDirectoryTest} to compile all
+ * files in a test directory together.
+ *
+ * <p>To use this class you must do two things:
+ *
+ * <ol>
+ *   <li>Create exactly 1 constructor in the subclass with exactly 1 argument of type java.io.File.
+ *       This File will be the Java file that is compiled and whose output is verified.
+ *   <li>Create one of the following 2 public static methods with the annotation
+ *       org.junit.runners.Parameterized.Parameters. The method name and signature must match
+ *       exactly.
+ *       <ul>
+ *         <li>{@code @Parameters public static String [] getTestDirs()}
+ *             <p>getTestDir must return an array of directories that exist in the test folder, e.g.
+ *             <pre>  @Parameters
+ *   public static String [] getTestDirs() {
+ *      return new String[]{"all-systems", "flow"};
+ *   }</pre>
+ *             The directories can contain more path information (e.g., "myTestDir/moreTests") but
+ *             note, the test suite will find all of the Java test files that exists below the
+ *             listed directories. It is unnecessary to list child directories of a directory you
+ *             have already listed.
+ *         <li>{@code @Parameters public static List<File> getTestFiles() }
+ *             <p>The method returns a List of Java files. There are methods like {@link
+ *             TestUtilities#findNestedJavaTestFiles} to help you construct this List. The
+ *             PerDirectorySuite will then instantiate the subclass once for each file returned by
+ *             getTestFiles and execute the run method. An example of this method is:
+ *             <pre>  @Parameters
+ *   public static List&lt;File&gt; getTestFiles() {
+ *     return TestUtilities.findNestedJavaTestFiles("aggregate");
+ *   }</pre>
+ *       </ul>
+ * </ol>
+ */
+@RunWith(PerFileSuite.class)
+public abstract class CheckerFrameworkPerFileTest {
+
+  /** The file containing test code, which will be type-checked. */
+  protected final File testFile;
+
+  /** The checker to use for tests. */
+  protected final Class<?> checker;
+
+  /** The path, relative to currentDir/test to the directory containing test inputs. */
+  protected final String testDir;
+
+  /** Extra options to pass to javac when running the checker. */
+  protected final List<String> checkerOptions;
+
+  /**
+   * Creates a new checker test.
+   *
+   * <p>{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String,
+   * Iterable, Iterable, List, boolean)} adds additional checker options.
+   *
+   * @param testFile the file containing test code, which will be type-checked
+   * @param checker the class for the checker to use
+   * @param testDir the path to the directory of test inputs
+   * @param checkerOptions options to pass to the compiler when running tests
+   */
+  protected CheckerFrameworkPerFileTest(
+      File testFile,
+      Class<? extends AbstractProcessor> checker,
+      String testDir,
+      String... checkerOptions) {
+    this.testFile = testFile;
+    this.checker = checker;
+    this.testDir = "tests" + File.separator + testDir;
+    this.checkerOptions = new ArrayList<>(Arrays.asList(checkerOptions));
+  }
+
+  @Test
+  public void run() {
+    boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo();
+    List<String> customizedOptions = customizeOptions(Collections.unmodifiableList(checkerOptions));
+    TestConfiguration config =
+        TestConfigurationBuilder.buildDefaultConfiguration(
+            testDir, testFile, checker, customizedOptions, shouldEmitDebugInfo);
+    TypecheckResult testResult = new TypecheckExecutor().runTest(config);
+    TestUtilities.assertTestDidNotFail(testResult);
+  }
+
+  /**
+   * Override this method if you would like to supply a checker command-line option that depends on
+   * the Java file passed to the test. That file name is available in field {@link #testFile}.
+   *
+   * <p>If you want to specify the same command-line option for all tests of a particular checker,
+   * then pass it to the {@link CheckerFrameworkPerFileTest} constructor.
+   *
+   * @param previousOptions the options specified in the constructor of the test previousOptions is
+   *     unmodifiable
+   * @return a new list of options or the original passed through
+   */
+  public List<String> customizeOptions(List<String> previousOptions) {
+    return previousOptions;
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CompilationResult.java b/framework-test/src/main/java/org/checkerframework/framework/test/CompilationResult.java
new file mode 100644
index 0000000..dd01819
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/CompilationResult.java
@@ -0,0 +1,61 @@
+package org.checkerframework.framework.test;
+
+import java.util.Collections;
+import java.util.List;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+
+/** CompilationResult represents the output of the compiler after it is run. */
+public class CompilationResult {
+  private final boolean compiledWithoutError;
+  private final String javacOutput;
+  private final Iterable<? extends JavaFileObject> javaFileObjects;
+  private final List<Diagnostic<? extends JavaFileObject>> diagnostics;
+
+  CompilationResult(
+      boolean compiledWithoutError,
+      String javacOutput,
+      Iterable<? extends JavaFileObject> javaFileObjects,
+      List<Diagnostic<? extends JavaFileObject>> diagnostics) {
+    this.compiledWithoutError = compiledWithoutError;
+    this.javacOutput = javacOutput;
+    this.javaFileObjects = javaFileObjects;
+    this.diagnostics = Collections.unmodifiableList(diagnostics);
+  }
+
+  /**
+   * Returns whether or not compilation succeeded without errors or exceptions.
+   *
+   * @return whether or not compilation succeeded without errors or exceptions
+   */
+  public boolean compiledWithoutError() {
+    return compiledWithoutError;
+  }
+
+  /**
+   * Returns all of the output from the compiler.
+   *
+   * @return all of the output from the compiler
+   */
+  public String getJavacOutput() {
+    return javacOutput;
+  }
+
+  /**
+   * Returns the list of Java files passed to the compiler.
+   *
+   * @return the list of Java files passed to the compiler
+   */
+  public Iterable<? extends JavaFileObject> getJavaFileObjects() {
+    return javaFileObjects;
+  }
+
+  /**
+   * Returns the diagnostics reported by the compiler.
+   *
+   * @return the diagnostics reported by the compiler
+   */
+  public List<Diagnostic<? extends JavaFileObject>> getDiagnostics() {
+    return diagnostics;
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java b/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java
new file mode 100644
index 0000000..f860e61
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java
@@ -0,0 +1,112 @@
+package org.checkerframework.framework.test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Represents all of the information needed to execute the Javac compiler for a given set of test
+ * files.
+ */
+public class ImmutableTestConfiguration implements TestConfiguration {
+
+  /**
+   * Options that should be passed to the compiler. This a {@code Map(optionName =>
+   * optionArgumentIfAny)}. E.g.,
+   *
+   * <pre>{@code
+   * Map(
+   *   "-AprintAllQualifiers" => null
+   *    "-classpath" => "myDir1:myDir2"
+   * )
+   * }</pre>
+   */
+  private final Map<String, @Nullable String> options;
+  /**
+   * These files contain diagnostics that should be returned by Javac. If this list is empty, the
+   * diagnostics are instead read from comments in the Java file itself
+   */
+  private final List<File> diagnosticFiles;
+
+  /**
+   * The source files to compile. If the file is expected to emit errors on compilation, the file
+   * should contain expected error diagnostics OR should have a companion file with the same
+   * path/name but with the extension .out instead of .java if they
+   */
+  private final List<File> testSourceFiles;
+
+  /** A list of AnnotationProcessors (usually checkers) to pass to the compiler for this test. */
+  private final List<@BinaryName String> processors;
+
+  /** The value of system property "emit.test.debug". */
+  private final boolean shouldEmitDebugInfo;
+
+  /**
+   * Create a new ImmutableTestConfiguration.
+   *
+   * @param diagnosticFiles files containing diagnostics that should be returned by javac
+   * @param testSourceFiles the source files to compile
+   * @param processors the annotation processors (usually checkers) to run
+   * @param options options that should be passed to the compiler
+   * @param shouldEmitDebugInfo the value of system property "emit.test.debug"
+   */
+  public ImmutableTestConfiguration(
+      List<File> diagnosticFiles,
+      List<File> testSourceFiles,
+      List<@BinaryName String> processors,
+      Map<String, @Nullable String> options,
+      boolean shouldEmitDebugInfo) {
+    this.diagnosticFiles = Collections.unmodifiableList(diagnosticFiles);
+    this.testSourceFiles = Collections.unmodifiableList(new ArrayList<>(testSourceFiles));
+    this.processors = new ArrayList<>(processors);
+    this.options =
+        Collections.unmodifiableMap(new LinkedHashMap<String, @Nullable String>(options));
+    this.shouldEmitDebugInfo = shouldEmitDebugInfo;
+  }
+
+  @Override
+  public List<File> getTestSourceFiles() {
+    return testSourceFiles;
+  }
+
+  @Override
+  public List<File> getDiagnosticFiles() {
+    return diagnosticFiles;
+  }
+
+  @Override
+  public List<@BinaryName String> getProcessors() {
+    return processors;
+  }
+
+  @Override
+  public Map<String, @Nullable String> getOptions() {
+    return options;
+  }
+
+  @Override
+  public List<String> getFlatOptions() {
+    return TestUtilities.optionMapToList(options);
+  }
+
+  @Override
+  public boolean shouldEmitDebugInfo() {
+    return shouldEmitDebugInfo;
+  }
+
+  @Override
+  public String toString() {
+    return StringsPlume.joinLines(
+        "TestConfigurationBuilder:",
+        "testSourceFiles=" + StringsPlume.join(" ", testSourceFiles),
+        "processors=" + String.join(", ", processors),
+        "options=" + String.join(", ", getFlatOptions()),
+        "shouldEmitDebugInfo=" + shouldEmitDebugInfo);
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java b/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java
new file mode 100644
index 0000000..4354967
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java
@@ -0,0 +1,185 @@
+package org.checkerframework.framework.test;
+
+import java.io.File;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringJoiner;
+import org.checkerframework.javacutil.BugInCF;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Suite;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+// TODO: large parts of this file are the same as PerFileSuite.java.
+// Reduce duplication by moving common parts to an abstract class.
+/**
+ * PerDirectorySuite runs a test class once for each set of javaFiles returned by its method marked
+ * with {@code @Parameters}
+ *
+ * <p>To use:<br>
+ * Annotated your test class with {@code @RunWith(PerDirectorySuite.class)}<br>
+ * Create a javaFiles method by annotating a public static method with {@code @Parameters}. This
+ * method must return either a {@code List<File>} where each element of the list is a Java file to
+ * test against OR a {@code String []} where each String in the array is a directory in the tests
+ * directory.
+ */
+public class PerDirectorySuite extends Suite {
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  public @interface Name {}
+
+  private final ArrayList<Runner> runners = new ArrayList<>();
+
+  @Override
+  protected List<Runner> getChildren() {
+    return runners;
+  }
+
+  /**
+   * Only called reflectively. Do not use programmatically.
+   *
+   * @param klass the class whose tests to run
+   */
+  @SuppressWarnings("nullness") // JUnit needs to be annotated
+  public PerDirectorySuite(Class<?> klass) throws Throwable {
+    super(klass, Collections.emptyList());
+    final TestClass testClass = getTestClass();
+    final Class<?> javaTestClass = testClass.getJavaClass();
+    final List<List<File>> parametersList = getParametersList(testClass);
+
+    for (List<File> parameters : parametersList) {
+      runners.add(new PerParameterSetTestRunner(javaTestClass, parameters));
+    }
+  }
+
+  /** Returns a list of one-element arrays, each containing a Java File. */
+  @SuppressWarnings("nullness") // JUnit needs to be annotated
+  private List<List<File>> getParametersList(TestClass klass) throws Throwable {
+    FrameworkMethod method = getParametersMethod(klass);
+
+    // We must have a method getTestDirs which returns String[],
+    // or getParametersMethod would fail.
+    if (!method.getReturnType().isArray()) {
+      return Collections.emptyList();
+    }
+    String[] dirs = (String[]) method.invokeExplosively(null);
+    return TestUtilities.findJavaFilesPerDirectory(new File("tests"), dirs);
+  }
+
+  /** Returns method annotated @Parameters, typically the getTestDirs or getTestFiles method. */
+  private FrameworkMethod getParametersMethod(TestClass testClass) {
+    final List<FrameworkMethod> parameterMethods = testClass.getAnnotatedMethods(Parameters.class);
+    if (parameterMethods.size() != 1) {
+      // Construct error message
+
+      String methods;
+      if (parameterMethods.isEmpty()) {
+        methods = "[No methods specified]";
+      } else {
+        StringJoiner sj = new StringJoiner(", ");
+        for (FrameworkMethod method : parameterMethods) {
+          sj.add(method.getName());
+        }
+        methods = sj.toString();
+      }
+
+      throw new BugInCF(
+          "Exactly one of the following methods should be declared:%n%s%n"
+              + "testClass=%s%n"
+              + "parameterMethods=%s",
+          requiredFormsMessage, testClass.getName(), methods);
+    }
+
+    FrameworkMethod method = parameterMethods.get(0);
+
+    Class<?> returnType = method.getReturnType();
+    String methodName = method.getName();
+    switch (methodName) {
+      case "getTestDirs":
+        if (!(returnType.isArray() && returnType.getComponentType() == String.class)) {
+          throw new RuntimeException("getTestDirs should return String[], found " + returnType);
+        }
+        break;
+
+      default:
+        throw new RuntimeException(
+            requiredFormsMessage
+                + "%n"
+                + "testClass="
+                + testClass.getName()
+                + "%n"
+                + "parameterMethods="
+                + method);
+    }
+
+    int modifiers = method.getMethod().getModifiers();
+    if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
+      throw new RuntimeException(
+          "Parameter method (" + method.getName() + ") must be public and static");
+    }
+
+    return method;
+  }
+
+  /** The message about the required getTestDirs method. */
+  private static final String requiredFormsMessage =
+      "Parameter method must have the following form:"
+          + System.lineSeparator()
+          + "@Parameters String[] getTestDirs()";
+
+  /** Runs the test class for the set of javaFiles passed in the constructor. */
+  private static class PerParameterSetTestRunner extends BlockJUnit4ClassRunner {
+    private final List<File> javaFiles;
+
+    PerParameterSetTestRunner(Class<?> type, List<File> javaFiles) throws InitializationError {
+      super(type);
+      this.javaFiles = javaFiles;
+    }
+
+    @Override
+    public Object createTest() throws Exception {
+      Object[] arguments = Collections.singleton(javaFiles).toArray();
+      return getTestClass().getOnlyConstructor().newInstance(arguments);
+    }
+
+    String testCaseName() {
+      File file = javaFiles.get(0).getParentFile();
+      if (file == null) {
+        throw new Error("root was passed? " + javaFiles.get(0));
+      }
+      return file.getPath().replace("tests" + System.getProperty("file.separator"), "");
+    }
+
+    @Override
+    protected String getName() {
+      return String.format("[%s]", testCaseName());
+    }
+
+    @Override
+    protected String testName(final FrameworkMethod method) {
+      return String.format("%s[%s]", method.getName(), testCaseName());
+    }
+
+    @Override
+    protected void validateZeroArgConstructor(List<Throwable> errors) {
+      // constructor should have args.
+    }
+
+    @Override
+    protected Statement classBlock(RunNotifier notifier) {
+      return childrenInvoker(notifier);
+    }
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java b/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java
new file mode 100644
index 0000000..ef8a037
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java
@@ -0,0 +1,200 @@
+package org.checkerframework.framework.test;
+
+import java.io.File;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringJoiner;
+import org.checkerframework.javacutil.BugInCF;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Suite;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+import org.plumelib.util.CollectionsPlume;
+
+// TODO: large parts of this file are the same as PerDirectorySuite.java.
+// Reduce duplication by moving common parts to an abstract class.
+/**
+ * PerDirectorySuite runs a test class once for each set of parameters returned by its method marked
+ * with {@code @Parameters}
+ *
+ * <p>To use:<br>
+ * Annotated your test class with {@code @RunWith(PerDirectorySuite.class)}<br>
+ * Create a parameters method by annotating a public static method with {@code @Parameters}. This
+ * method must return either a {@code List<File>} where each element of the list is a Java file to
+ * test against OR a {@code String []} where each String in the array is a directory in the tests
+ * directory.
+ */
+public class PerFileSuite extends Suite {
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  public @interface Name {}
+
+  private final ArrayList<Runner> runners = new ArrayList<>();
+
+  @Override
+  protected List<Runner> getChildren() {
+    return runners;
+  }
+
+  /**
+   * Only called reflectively. Do not use programmatically.
+   *
+   * @param klass the class whose tests to run
+   */
+  @SuppressWarnings("nullness") // JUnit needs to be annotated
+  public PerFileSuite(Class<?> klass) throws Throwable {
+    super(klass, Collections.emptyList());
+    final TestClass testClass = getTestClass();
+    final Class<?> javaTestClass = testClass.getJavaClass();
+    final List<Object[]> parametersList = getParametersList(testClass);
+
+    for (Object[] parameters : parametersList) {
+      runners.add(new PerParameterSetTestRunner(javaTestClass, parameters));
+    }
+  }
+
+  /** Returns a list of one-element arrays, each containing a Java File. */
+  @SuppressWarnings({
+    "unchecked",
+    "nullness" // JUnit needs to be annotated
+  })
+  private List<Object[]> getParametersList(TestClass klass) throws Throwable {
+    FrameworkMethod method = getParametersMethod(klass);
+
+    List<File> javaFiles;
+    // We will have either a method getTestDirs which returns String [] or getTestFiles
+    // which returns List<Object []> or getParametersMethod would fail
+    if (method.getReturnType().isArray()) {
+      String[] dirs = (String[]) method.invokeExplosively(null);
+      javaFiles = TestUtilities.findNestedJavaTestFiles(dirs);
+
+    } else {
+      javaFiles = (List<File>) method.invokeExplosively(null);
+    }
+
+    List<Object[]> argumentLists =
+        CollectionsPlume.mapList((File javaFile) -> new Object[] {javaFile}, javaFiles);
+
+    return argumentLists;
+  }
+
+  /** Returns method annotated @Parameters, typically the getTestDirs or getTestFiles method. */
+  private FrameworkMethod getParametersMethod(TestClass testClass) {
+    final List<FrameworkMethod> parameterMethods = testClass.getAnnotatedMethods(Parameters.class);
+    if (parameterMethods.size() != 1) {
+      // Construct error message
+
+      String methods;
+      if (parameterMethods.isEmpty()) {
+        methods = "[No methods specified]";
+      } else {
+        StringJoiner sj = new StringJoiner(", ");
+        for (FrameworkMethod method : parameterMethods) {
+          sj.add(method.getName());
+        }
+        methods = sj.toString();
+      }
+
+      throw new BugInCF(requiredFormsMessage, testClass.getName(), methods);
+    } // else
+
+    FrameworkMethod method = parameterMethods.get(0);
+
+    Class<?> returnType = method.getReturnType();
+    String methodName = method.getName();
+    switch (methodName) {
+      case "getTestDirs":
+        if (returnType.isArray()) {
+          if (returnType.getComponentType() != String.class) {
+            throw new RuntimeException(
+                "Component type of getTestDirs must be java.lang.String, found "
+                    + returnType.getComponentType().getCanonicalName());
+          }
+        }
+        break;
+
+      case "getTestFiles":
+        // We'll force people to return a List for now but enforcing exactly List<File> or a
+        // subtype thereof is not easy.
+        if (!List.class.getCanonicalName().equals(returnType.getCanonicalName())) {
+          throw new RuntimeException("getTestFiles must return a List<File>, found " + returnType);
+        }
+        break;
+
+      default:
+        throw new BugInCF(requiredFormsMessage, testClass.getName(), method);
+    }
+
+    int modifiers = method.getMethod().getModifiers();
+    if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
+      throw new RuntimeException(
+          "Parameter method (" + method.getName() + ") must be public and static");
+    }
+
+    return method;
+  }
+
+  /** The message about the required getTestDirs or getTestFiles method. */
+  private static final String requiredFormsMessage =
+      "Parameter method must have one of the following two forms:%n"
+          + "@Parameters String [] getTestDirs()%n"
+          + "@Parameters List<File> getTestFiles()%n"
+          + "testClass=%s%n"
+          + "parameterMethods=%s";
+
+  /** Runs the test class for the set of parameters passed in the constructor. */
+  private static class PerParameterSetTestRunner extends BlockJUnit4ClassRunner {
+    private final Object[] parameters;
+
+    PerParameterSetTestRunner(Class<?> type, Object[] parameters) throws InitializationError {
+      super(type);
+      this.parameters = parameters;
+    }
+
+    @Override
+    public Object createTest() throws Exception {
+      return getTestClass().getOnlyConstructor().newInstance(parameters);
+    }
+
+    String testCaseName() {
+      File file = (File) parameters[0];
+      String name =
+          file.getPath()
+              .replace(".java", "")
+              .replace("tests" + System.getProperty("file.separator"), "");
+      return name;
+    }
+
+    @Override
+    protected String getName() {
+      return String.format("[%s]", testCaseName());
+    }
+
+    @Override
+    protected String testName(final FrameworkMethod method) {
+      return String.format("%s[%s]", method.getName(), testCaseName());
+    }
+
+    @Override
+    protected void validateZeroArgConstructor(List<Throwable> errors) {
+      // constructor should have args.
+    }
+
+    @Override
+    protected Statement classBlock(RunNotifier notifier) {
+      return childrenInvoker(notifier);
+    }
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java b/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java
new file mode 100644
index 0000000..ff9f669
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java
@@ -0,0 +1,149 @@
+package org.checkerframework.framework.test;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * SimpleOptionMap is a very basic Option container. The keys of the Option container are the set of
+ * Options and the values are the arguments to those options if they exists: e.g.,
+ *
+ * <pre>{@code
+ * Map(
+ *    "-AprintAllQualifiers" => null
+ *    "-classpath" => "myDir1:myDir2"
+ * )
+ * }</pre>
+ *
+ * This class is mainly used by TestConfigurationBuilder to make working with existing options
+ * simpler and less error prone. It is not intended for a general Option container because users
+ * creating tests via source code can more easily manipulate the map whereas a lot of sugar would be
+ * needed to make this class usable from the command line.
+ */
+public class SimpleOptionMap {
+  /** A Map from optionName to arg, where arg is null if the option doesn't require any args. */
+  private final Map<String, @Nullable String> options = new LinkedHashMap<>();
+
+  /**
+   * Clears the current set of options and copies the input options to this map.
+   *
+   * @param options the new options to use for this object
+   */
+  public void setOptions(Map<String, @Nullable String> options) {
+    this.options.clear();
+    this.options.putAll(options);
+  }
+
+  /**
+   * A method to easily add Strings to an option that takes a filepath as an argument.
+   *
+   * @param key an option with an argument of the form "arg1[path-separator]arg2..." e.g., "-cp
+   *     myDir:myDir2:myDir3"
+   * @param toAppend a string to append onto the path or, if the path is null/empty, the argument to
+   *     the option indicated by key
+   */
+  public void addToPathOption(String key, String toAppend) {
+    if (toAppend == null) {
+      throw new IllegalArgumentException("Null string appended to sourcePath.");
+    }
+
+    String path = options.get(key);
+
+    if (toAppend.startsWith(File.pathSeparator)) {
+      if (path == null || path.isEmpty()) {
+        path = toAppend.substring(1, toAppend.length());
+      } else {
+        path += toAppend;
+      }
+    } else {
+      if (path == null || path.isEmpty()) {
+        path = toAppend;
+      } else {
+        path += File.pathSeparator + toAppend;
+      }
+    }
+
+    addOption(key, path);
+  }
+
+  /**
+   * Adds an option that takes no argument.
+   *
+   * @param option the no-argument option to add to this object
+   */
+  public void addOption(String option) {
+    this.options.put(option, null);
+  }
+
+  /**
+   * Adds an option that takes an argument.
+   *
+   * @param option the option to add to this object
+   * @param value the argument to the option
+   */
+  public void addOption(String option, String value) {
+    this.options.put(option, value);
+  }
+
+  /**
+   * Adds the option only if value is a non-null, non-empty String.
+   *
+   * @param option the option to add to this object
+   * @param value the argument to the option (or null)
+   */
+  public void addOptionIfValueNonEmpty(String option, String value) {
+    if (value != null && !value.isEmpty()) {
+      addOption(option, value);
+    }
+  }
+
+  /**
+   * Adds all of the options in the given map to this one.
+   *
+   * @param options the options to add to this object
+   */
+  public void addOptions(Map<String, @Nullable String> options) {
+    this.options.putAll(options);
+  }
+
+  public void addOptions(Iterable<String> newOptions) {
+    Iterator<String> optIter = newOptions.iterator();
+    while (optIter.hasNext()) {
+      String opt = optIter.next();
+      if (this.options.get(opt) != null) {
+        if (!optIter.hasNext()) {
+          throw new RuntimeException(
+              "Expected a value for option: "
+                  + opt
+                  + " in option list: "
+                  + String.join(", ", newOptions));
+        }
+        this.options.put(opt, optIter.next());
+
+      } else {
+        this.options.put(opt, null);
+      }
+    }
+  }
+
+  /**
+   * Returns the map that backs this SimpleOptionMap.
+   *
+   * @return the options in this object
+   */
+  public Map<String, @Nullable String> getOptions() {
+    return options;
+  }
+
+  /**
+   * Creates a "flat" list representation of these options.
+   *
+   * @return a list of the string representations of the options in this object
+   */
+  public List<String> getOptionsAsList() {
+    return TestUtilities.optionMapToList(options);
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java
new file mode 100644
index 0000000..9f71014
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java
@@ -0,0 +1,84 @@
+package org.checkerframework.framework.test;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.BinaryName;
+
+/** A configuration for running CheckerFrameworkTests or running the TypecheckExecutor. */
+public interface TestConfiguration {
+  /**
+   * Returns a list of source files a CheckerFrameworkPerDirectoryTest should be run over. These
+   * source files will be passed to Javac when the test is run. These are NOT JUnit tests.
+   *
+   * @return a list of source files a CheckerFrameworkPerDirectoryTest should be run over
+   */
+  List<File> getTestSourceFiles();
+
+  /**
+   * Diagnostic files consist of a set of lines that enumerate expected error/warning diagnostics.
+   * The lines are of the form:
+   *
+   * <pre>fileName:lineNumber: diagnostKind: (messageKey)</pre>
+   *
+   * e.g.,
+   *
+   * <pre>MethodInvocation.java:17: error: (method.invocation)</pre>
+   *
+   * If getDiagnosticFiles does NOT return an empty list, then the only diagnostics expected by the
+   * TestExecutor will be the ones found in these files. If it does return an empty list, then the
+   * only diagnostics expected will be the ones found in comments in the input test files.
+   *
+   * <p>It is preferred that users write the errors in the test files and not in diagnostic files.
+   *
+   * @return a List of diagnostic files containing the error/warning messages expected to be output
+   *     when Javac is run on the files returned by getTestSourceFiles. Return an empty list if
+   *     these messages were specified within the source files.
+   */
+  List<File> getDiagnosticFiles();
+
+  /**
+   * Returns a list of annotation processors (Checkers) passed to the Javac compiler.
+   *
+   * @return a list of annotation processors (Checkers) passed to the Javac compiler
+   */
+  List<@BinaryName String> getProcessors();
+
+  /**
+   * Some Javac command line arguments require arguments themselves (e.g. {@code -classpath} takes a
+   * path) getOptions returns a {@code Map(optionName => optionArgumentIfAny)}. If an option does
+   * not take an argument, pass null as the value.
+   *
+   * <p>E.g.,
+   *
+   * <pre>{@code
+   * Map(
+   *   "-AprintAllQualifiers" => null
+   *    "-classpath" => "myDir1:myDir2"
+   * )
+   * }</pre>
+   *
+   * @return a Map representing all command-line options to Javac other than source files and
+   *     processors
+   */
+  Map<String, @Nullable String> getOptions();
+
+  /**
+   * Returns the map returned by {@link #getOptions}, flattened into a list. The entries will be
+   * added as followed: List(key1, value1, key2, value2, ..., keyN, valueN). If a value is NULL,
+   * then it will not appear in the list.
+   *
+   * @return the map returned {@link #getOptions}, but flattened into a list
+   */
+  List<String> getFlatOptions();
+
+  /**
+   * Returns true if the TypecheckExecutor should emit debug information on system out, false
+   * otherwise.
+   *
+   * @return true if the TypecheckExecutor should emit debug information on system out, false
+   *     otherwise
+   */
+  boolean shouldEmitDebugInfo();
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java
new file mode 100644
index 0000000..ffc3476
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java
@@ -0,0 +1,479 @@
+package org.checkerframework.framework.test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.SystemUtil;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Used to create an instance of TestConfiguration. TestConfigurationBuilder is fluent: it returns
+ * itself after every call so you can string together configuration methods as follows:
+ *
+ * <p>{@code new TestConfigurationBuilder() .addOption("-Awarns") .addSourceFile("src1.java")
+ * .addDiagnosticFile("src1.out") }
+ *
+ * @see TestConfiguration
+ */
+public class TestConfigurationBuilder {
+
+  // Presented first are static helper methods that reduce configuration building to a method call
+  // However, if you need more complex configuration or custom configuration, use the
+  // constructors provided below
+
+  /**
+   * This creates a builder for the default configuration used by Checker Framework JUnit tests.
+   *
+   * @param testSourcePath the path to the Checker test file sources, usually this is the directory
+   *     of Checker's tests
+   * @param outputClassDirectory the directory to place classes compiled for testing
+   * @param classPath the classpath to use for compilation
+   * @param testSourceFiles the Java files that compose the test
+   * @param processors the checkers or other annotation processors to run over the testSourceFiles
+   * @param options the options to the compiler/processors
+   * @param shouldEmitDebugInfo whether or not debug information should be emitted
+   * @return the builder that will create an immutable test configuration
+   */
+  public static TestConfigurationBuilder getDefaultConfigurationBuilder(
+      String testSourcePath,
+      File outputClassDirectory,
+      String classPath,
+      Iterable<File> testSourceFiles,
+      Iterable<@BinaryName String> processors,
+      List<String> options,
+      boolean shouldEmitDebugInfo) {
+
+    TestConfigurationBuilder configBuilder =
+        new TestConfigurationBuilder()
+            .setShouldEmitDebugInfo(shouldEmitDebugInfo)
+            .addProcessors(processors)
+            .addOption("-Xmaxerrs", "9999")
+            .addOption("-g")
+            .addOption("-Xlint:unchecked")
+            .addOption("-XDrawDiagnostics") // use short javac diagnostics
+            .addSourceFiles(testSourceFiles);
+
+    if (outputClassDirectory != null) {
+      configBuilder.addOption("-d", outputClassDirectory.getAbsolutePath());
+    }
+
+    if (SystemUtil.getJreVersion() == 8) {
+      configBuilder.addOption("-source", "8").addOption("-target", "8");
+    }
+
+    configBuilder
+        .addOptionIfValueNonEmpty("-sourcepath", testSourcePath)
+        .addOption("-implicit:class")
+        .addOption("-classpath", classPath);
+
+    configBuilder.addOptions(options);
+    return configBuilder;
+  }
+
+  /**
+   * This is the default configuration used by Checker Framework JUnit tests.
+   *
+   * @param testSourcePath the path to the Checker test file sources, usually this is the directory
+   *     of Checker's tests
+   * @param testFile a single test java file to compile
+   * @param processor a single checker to include in the processors field
+   * @param options the options to the compiler/processors
+   * @param shouldEmitDebugInfo whether or not debug information should be emitted
+   * @return a TestConfiguration with input parameters added plus the normal default options,
+   *     compiler, and file manager used by Checker Framework tests
+   */
+  @SuppressWarnings(
+      "signature:argument" // for non-array non-primitive class, getName(): @BinaryName
+  )
+  public static TestConfiguration buildDefaultConfiguration(
+      String testSourcePath,
+      File testFile,
+      Class<?> processor,
+      List<String> options,
+      boolean shouldEmitDebugInfo) {
+    return buildDefaultConfiguration(
+        testSourcePath,
+        Arrays.asList(testFile),
+        Collections.emptyList(),
+        Arrays.asList(processor.getName()),
+        options,
+        shouldEmitDebugInfo);
+  }
+
+  /**
+   * This is the default configuration used by Checker Framework JUnit tests.
+   *
+   * @param testSourcePath the path to the Checker test file sources, usually this is the directory
+   *     of Checker's tests
+   * @param testSourceFiles the Java files that compose the test
+   * @param processors the checkers or other annotation processors to run over the testSourceFiles
+   * @param options the options to the compiler/processors
+   * @param shouldEmitDebugInfo whether or not debug information should be emitted
+   * @return a TestConfiguration with input parameters added plus the normal default options,
+   *     compiler, and file manager used by Checker Framework tests
+   */
+  public static TestConfiguration buildDefaultConfiguration(
+      String testSourcePath,
+      Iterable<File> testSourceFiles,
+      Iterable<@BinaryName String> processors,
+      List<String> options,
+      boolean shouldEmitDebugInfo) {
+    return buildDefaultConfiguration(
+        testSourcePath,
+        testSourceFiles,
+        Collections.emptyList(),
+        processors,
+        options,
+        shouldEmitDebugInfo);
+  }
+
+  /**
+   * This is the default configuration used by Checker Framework JUnit tests.
+   *
+   * @param testSourcePath the path to the Checker test file sources, usually this is the directory
+   *     of Checker's tests
+   * @param testSourceFiles the Java files that compose the test
+   * @param classpathExtra extra entries for the classpath, needed to compile the source files
+   * @param processors the checkers or other annotation processors to run over the testSourceFiles
+   * @param options the options to the compiler/processors
+   * @param shouldEmitDebugInfo whether or not debug information should be emitted
+   * @return a TestConfiguration with input parameters added plus the normal default options,
+   *     compiler, and file manager used by Checker Framework tests
+   */
+  public static TestConfiguration buildDefaultConfiguration(
+      String testSourcePath,
+      Iterable<File> testSourceFiles,
+      Collection<String> classpathExtra,
+      Iterable<@BinaryName String> processors,
+      List<String> options,
+      boolean shouldEmitDebugInfo) {
+
+    String classPath = getDefaultClassPath();
+    if (!classpathExtra.isEmpty()) {
+      classPath +=
+          System.getProperty("path.separator")
+              + String.join(System.getProperty("path.separator"), classpathExtra);
+    }
+
+    File outputDir = getOutputDirFromProperty();
+
+    TestConfigurationBuilder builder =
+        getDefaultConfigurationBuilder(
+            testSourcePath,
+            outputDir,
+            classPath,
+            testSourceFiles,
+            processors,
+            options,
+            shouldEmitDebugInfo);
+    return builder.validateThenBuild(true);
+  }
+
+  /** The list of files that contain Java diagnostics to compare against. */
+  private List<File> diagnosticFiles;
+
+  /** The set of Java files to test against. */
+  private List<File> testSourceFiles;
+
+  /** The set of Checker Framework processors to test with. */
+  private Set<@BinaryName String> processors;
+
+  /** The set of options to the Javac command line used to run the test. */
+  private SimpleOptionMap options;
+
+  /** Should the Javac options be output before running the test. */
+  private boolean shouldEmitDebugInfo;
+
+  /**
+   * Note: There are static helper methods named buildConfiguration and buildConfigurationBuilder
+   * that can be used to create the most common types of configurations
+   */
+  public TestConfigurationBuilder() {
+    diagnosticFiles = new ArrayList<>();
+    testSourceFiles = new ArrayList<>();
+    processors = new LinkedHashSet<>();
+    options = new SimpleOptionMap();
+    shouldEmitDebugInfo = false;
+  }
+
+  /**
+   * Create a builder that has all of the options in initialConfig.
+   *
+   * @param initialConfig initial configuration for the newly-created builder
+   */
+  public TestConfigurationBuilder(TestConfiguration initialConfig) {
+    this.diagnosticFiles = new ArrayList<>(initialConfig.getDiagnosticFiles());
+    this.testSourceFiles = new ArrayList<>(initialConfig.getTestSourceFiles());
+    this.processors = new LinkedHashSet<>(initialConfig.getProcessors());
+    this.options = new SimpleOptionMap();
+    this.addOptions(initialConfig.getOptions());
+
+    this.shouldEmitDebugInfo = initialConfig.shouldEmitDebugInfo();
+  }
+
+  /**
+   * Ensures that the minimum requirements for running a test are met. These requirements are:
+   *
+   * <ul>
+   *   <li>There is at least one source file
+   *   <li>There is at least one processor (if requireProcessors has been set to true)
+   *   <li>There is an output directory specified for class files
+   *   <li>There is no {@code -processor} option in the optionMap (it should be added by
+   *       addProcessor instead)
+   * </ul>
+   *
+   * @param requireProcessors whether or not to require that there is at least one processor
+   * @return a list of errors found while validating this configuration
+   */
+  public List<String> validate(boolean requireProcessors) {
+    List<String> errors = new ArrayList<>();
+    if (testSourceFiles == null || !testSourceFiles.iterator().hasNext()) {
+      errors.add("No source files specified!");
+    }
+
+    if (requireProcessors && !processors.iterator().hasNext()) {
+      errors.add("No processors were specified!");
+    }
+
+    final Map<String, @Nullable String> optionMap = options.getOptions();
+    if (!optionMap.containsKey("-d") || optionMap.get("-d") == null) {
+      errors.add("No output directory was specified.");
+    }
+
+    if (optionMap.containsKey("-processor")) {
+      errors.add("Processors should not be added to the options list");
+    }
+
+    return errors;
+  }
+
+  public TestConfigurationBuilder adddToPathOption(String key, String toAppend) {
+    options.addToPathOption(key, toAppend);
+    return this;
+  }
+
+  public TestConfigurationBuilder addDiagnosticFile(File diagnostics) {
+    this.diagnosticFiles.add(diagnostics);
+    return this;
+  }
+
+  public TestConfigurationBuilder addDiagnosticFiles(Iterable<File> diagnostics) {
+    this.diagnosticFiles = catListAndIterable(diagnosticFiles, diagnostics);
+    return this;
+  }
+
+  public TestConfigurationBuilder setDiagnosticFiles(List<File> diagnosticFiles) {
+    this.diagnosticFiles = new ArrayList<>(diagnosticFiles);
+    return this;
+  }
+
+  public TestConfigurationBuilder addSourceFile(File sourceFile) {
+    testSourceFiles.add(sourceFile);
+    return this;
+  }
+
+  public TestConfigurationBuilder addSourceFiles(Iterable<File> sourceFiles) {
+    testSourceFiles = catListAndIterable(testSourceFiles, sourceFiles);
+    return this;
+  }
+
+  public TestConfigurationBuilder setSourceFiles(List<File> sourceFiles) {
+    this.testSourceFiles = new ArrayList<>(sourceFiles);
+    return this;
+  }
+
+  public TestConfigurationBuilder setOptions(Map<String, @Nullable String> options) {
+    this.options.setOptions(options);
+    return this;
+  }
+
+  public TestConfigurationBuilder addOption(String option) {
+    this.options.addOption(option);
+    return this;
+  }
+
+  public TestConfigurationBuilder addOption(String option, String value) {
+    this.options.addOption(option, value);
+    return this;
+  }
+
+  public TestConfigurationBuilder addOptionIfValueNonEmpty(String option, String value) {
+    if (value != null && !value.isEmpty()) {
+      return addOption(option, value);
+    }
+
+    return this;
+  }
+
+  /**
+   * Adds the given options to this.
+   *
+   * @param options options to add to this
+   * @return this
+   */
+  @SuppressWarnings("nullness:return") // need @PolyInitialized annotation
+  @RequiresNonNull("this.options")
+  public TestConfigurationBuilder addOptions(
+      @UnknownInitialization(TestConfigurationBuilder.class) TestConfigurationBuilder this,
+      Map<String, @Nullable String> options) {
+    this.options.addOptions(options);
+    return this;
+  }
+
+  public TestConfigurationBuilder addOptions(Iterable<String> newOptions) {
+    this.options.addOptions(newOptions);
+    return this;
+  }
+
+  /**
+   * Set the processors.
+   *
+   * @param processors the processors to run
+   * @return this
+   */
+  public TestConfigurationBuilder setProcessors(Iterable<@BinaryName String> processors) {
+    this.processors.clear();
+    for (String proc : processors) {
+      this.processors.add(proc);
+    }
+    return this;
+  }
+
+  /**
+   * Add a processor.
+   *
+   * @param processor a processor to run
+   * @return this
+   */
+  public TestConfigurationBuilder addProcessor(@BinaryName String processor) {
+    this.processors.add(processor);
+    return this;
+  }
+
+  /**
+   * Add processors.
+   *
+   * @param processors processors to run
+   * @return this
+   */
+  public TestConfigurationBuilder addProcessors(Iterable<@BinaryName String> processors) {
+    for (String processor : processors) {
+      this.processors.add(processor);
+    }
+
+    return this;
+  }
+
+  public TestConfigurationBuilder emitDebugInfo() {
+    this.shouldEmitDebugInfo = true;
+    return this;
+  }
+
+  public TestConfigurationBuilder dontEmitDebugInfo() {
+    this.shouldEmitDebugInfo = false;
+    return this;
+  }
+
+  public TestConfigurationBuilder setShouldEmitDebugInfo(boolean shouldEmitDebugInfo) {
+    this.shouldEmitDebugInfo = shouldEmitDebugInfo;
+    return this;
+  }
+
+  /**
+   * Creates a TestConfiguration using the settings in this builder. The settings are NOT validated
+   * first.
+   *
+   * @return a TestConfiguration using the settings in this builder
+   */
+  public TestConfiguration build() {
+    return new ImmutableTestConfiguration(
+        diagnosticFiles,
+        testSourceFiles,
+        new ArrayList<>(processors),
+        options.getOptions(),
+        shouldEmitDebugInfo);
+  }
+
+  /**
+   * Creates a TestConfiguration using the settings in this builder. The settings are first
+   * validated and a runtime exception is thrown if any errors are found
+   *
+   * @param requireProcessors whether or not there should be at least 1 processor specified, see
+   *     method validate
+   * @return a TestConfiguration using the settings in this builder
+   */
+  public TestConfiguration validateThenBuild(boolean requireProcessors) {
+    List<String> errors = validate(requireProcessors);
+    if (errors.isEmpty()) {
+      return build();
+    }
+
+    throw new BugInCF(
+        "Attempted to build invalid test configuration:%n" + "Errors:%n%s%n%s%n",
+        String.join("%n", errors), this);
+  }
+
+  /**
+   * Returns the set of Javac options as a flat list.
+   *
+   * @return the set of Javac options as a flat list
+   */
+  public List<String> flatOptions() {
+    return options.getOptionsAsList();
+  }
+
+  @Override
+  public String toString() {
+    return StringsPlume.joinLines(
+        "TestConfigurationBuilder:",
+        "testSourceFiles=" + StringsPlume.join(" ", testSourceFiles),
+        "processors=" + String.join(", ", processors),
+        "options=" + String.join(", ", options.getOptionsAsList()),
+        "shouldEmitDebugInfo=" + shouldEmitDebugInfo);
+  }
+
+  /**
+   * Returns a list that first has the items from parameter list then the items from iterable.
+   *
+   * @param <T> the type of the elements in the resulting list
+   * @param list a list
+   * @param iterable an iterable
+   * @return a list that first has the items from parameter list then the items from iterable
+   */
+  private static <T> List<T> catListAndIterable(
+      final List<? extends T> list, final Iterable<? extends T> iterable) {
+    final List<T> newList = new ArrayList<>(list);
+
+    for (T iterObject : iterable) {
+      newList.add(iterObject);
+    }
+
+    return newList;
+  }
+
+  public static final String TESTS_OUTPUTDIR = "tests.outputDir";
+
+  public static File getOutputDirFromProperty() {
+    return new File(
+        System.getProperty(
+            "tests.outputDir",
+            "tests" + File.separator + "build" + File.separator + "testclasses"));
+  }
+
+  public static String getDefaultClassPath() {
+    String classpath = System.getProperty("tests.classpath", "tests" + File.separator + "build");
+    String globalclasspath = System.getProperty("java.class.path", "");
+    return classpath + File.pathSeparator + globalclasspath;
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java
new file mode 100644
index 0000000..01e738a
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java
@@ -0,0 +1,493 @@
+package org.checkerframework.framework.test;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.tools.Diagnostic;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.ToolProvider;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.SystemUtil;
+import org.junit.Assert;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.StringsPlume;
+import org.plumelib.util.SystemPlume;
+
+/** Utilities for testing. */
+public class TestUtilities {
+
+  public static final boolean IS_AT_LEAST_9_JVM = SystemUtil.getJreVersion() >= 9;
+  public static final boolean IS_AT_LEAST_11_JVM = SystemUtil.getJreVersion() >= 11;
+
+  static {
+    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+    OutputStream err = new ByteArrayOutputStream();
+    compiler.run(null, null, err, "-version");
+  }
+
+  public static List<File> findNestedJavaTestFiles(String... dirNames) {
+    return findRelativeNestedJavaFiles(new File("tests"), dirNames);
+  }
+
+  public static List<File> findRelativeNestedJavaFiles(String parent, String... dirNames) {
+    return findRelativeNestedJavaFiles(new File(parent), dirNames);
+  }
+
+  public static List<File> findRelativeNestedJavaFiles(File parent, String... dirNames) {
+    File[] dirs = new File[dirNames.length];
+
+    int i = 0;
+    for (String dirName : dirNames) {
+      dirs[i] = new File(parent, dirName);
+      i += 1;
+    }
+
+    return getJavaFilesAsArgumentList(dirs);
+  }
+
+  /**
+   * Returns a list where each item is a list of Java files, excluding any skip tests, for each
+   * directory given by dirName and also a list for any subdirectory.
+   *
+   * @param parent parent directory of the dirNames directories
+   * @param dirNames names of directories to search
+   * @return list where each item is a list of Java test files grouped by directory
+   */
+  public static List<List<File>> findJavaFilesPerDirectory(File parent, String... dirNames) {
+    if (!parent.exists()) {
+      throw new BugInCF(
+          "test parent directory does not exist: %s %s", parent, parent.getAbsoluteFile());
+    }
+    if (!parent.isDirectory()) {
+      throw new BugInCF(
+          "test parent directory is not a directory: %s %s", parent, parent.getAbsoluteFile());
+    }
+
+    List<List<File>> filesPerDirectory = new ArrayList<>();
+
+    for (String dirName : dirNames) {
+      File dir = new File(parent, dirName).toPath().toAbsolutePath().normalize().toFile();
+      // This fails for the whole-program-inference tests:  their sources do not necessarily
+      // exist yet but will be created by a test that runs earlier than they do.
+      // if (!dir.isDirectory()) {
+      //     throw new BugInCF("test directory does not exist: %s", dir);
+      // }
+      if (dir.isDirectory()) {
+        filesPerDirectory.addAll(findJavaTestFilesInDirectory(dir));
+      }
+    }
+
+    return filesPerDirectory;
+  }
+
+  /**
+   * Returns a list where each item is a list of Java files, excluding any skip tests. There is one
+   * list for {@code dir}, and one list for each subdirectory of {@code dir}.
+   *
+   * @param dir directory in which to search for Java files
+   * @return a list of list of Java test files
+   */
+  private static List<List<File>> findJavaTestFilesInDirectory(File dir) {
+    List<List<File>> fileGroupedByDirectory = new ArrayList<>();
+    List<File> filesInDir = new ArrayList<>();
+
+    fileGroupedByDirectory.add(filesInDir);
+    String[] dirContents = dir.list();
+    if (dirContents == null) {
+      throw new Error("Not a directory: " + dir);
+    }
+    Arrays.sort(dirContents);
+    for (String fileName : dirContents) {
+      File file = new File(dir, fileName);
+      if (file.isDirectory()) {
+        fileGroupedByDirectory.addAll(findJavaTestFilesInDirectory(file));
+      } else if (isJavaTestFile(file)) {
+        filesInDir.add(file);
+      }
+    }
+    if (filesInDir.isEmpty()) {
+      fileGroupedByDirectory.remove(filesInDir);
+    }
+    return fileGroupedByDirectory;
+  }
+
+  /**
+   * Prepends a file to the beginning of each filename.
+   *
+   * @param parent a file to prepend to each filename
+   * @param fileNames file names
+   * @return the file names, each with {@code parent} prepended
+   */
+  public static List<Object[]> findFilesInParent(File parent, String... fileNames) {
+    return CollectionsPlume.mapList(
+        (String fileName) -> new Object[] {new File(parent, fileName)}, fileNames);
+  }
+
+  /**
+   * Traverses the directories listed looking for Java test files.
+   *
+   * @param dirs directories in which to search for Java test files
+   * @return a list of Java test files found in the directories
+   */
+  public static List<File> getJavaFilesAsArgumentList(File... dirs) {
+    List<File> arguments = new ArrayList<>();
+    for (File dir : dirs) {
+      arguments.addAll(deeplyEnclosedJavaTestFiles(dir));
+    }
+    return arguments;
+  }
+
+  /** Returns all the java files that are descendants of the given directory. */
+  public static List<File> deeplyEnclosedJavaTestFiles(File directory) {
+    if (!directory.exists()) {
+      throw new IllegalArgumentException(
+          "directory does not exist: " + directory + " " + directory.getAbsolutePath());
+    }
+    if (!directory.isDirectory()) {
+      throw new IllegalArgumentException("found file instead of directory: " + directory);
+    }
+
+    List<File> javaFiles = new ArrayList<>();
+
+    @SuppressWarnings("nullness") // checked above that it's a directory
+    File @NonNull [] in = directory.listFiles();
+    Arrays.sort(
+        in,
+        new Comparator<File>() {
+          @Override
+          public int compare(File o1, File o2) {
+            return o1.getName().compareTo(o2.getName());
+          }
+        });
+    for (File file : in) {
+      if (file.isDirectory()) {
+        javaFiles.addAll(deeplyEnclosedJavaTestFiles(file));
+      } else if (isJavaTestFile(file)) {
+        javaFiles.add(file);
+      }
+    }
+
+    return javaFiles;
+  }
+
+  public static boolean isJavaFile(File file) {
+    return file.isFile() && file.getName().endsWith(".java");
+  }
+
+  public static boolean isJavaTestFile(File file) {
+    if (!isJavaFile(file)) {
+      return false;
+    }
+
+    // We could implement special filtering based on directory names,
+    // but I prefer using @below-java9-jdk-skip-test
+    // if (!IS_AT_LEAST_9_JVM && file.getAbsolutePath().contains("java9")) {
+    //     return false;
+    // }
+
+    Scanner in = null;
+    try {
+      in = new Scanner(file);
+    } catch (FileNotFoundException e) {
+      throw new RuntimeException(e);
+    }
+
+    while (in.hasNext()) {
+      String nextLine = in.nextLine();
+      if (nextLine.contains("@skip-test")
+          || (!IS_AT_LEAST_9_JVM && nextLine.contains("@below-java9-jdk-skip-test"))
+          || (!IS_AT_LEAST_11_JVM && nextLine.contains("@below-java11-jdk-skip-test"))) {
+        in.close();
+        return false;
+      }
+    }
+
+    in.close();
+    return true;
+  }
+
+  public static @Nullable String diagnosticToString(
+      final Diagnostic<? extends JavaFileObject> diagnostic, boolean usingAnomsgtxt) {
+
+    String result = diagnostic.toString().trim();
+
+    // suppress Xlint warnings
+    if (result.contains("uses unchecked or unsafe operations.")
+        || result.contains("Recompile with -Xlint:unchecked for details.")
+        || result.endsWith(" declares unsafe vararg methods.")
+        || result.contains("Recompile with -Xlint:varargs for details.")) {
+      return null;
+    }
+
+    if (usingAnomsgtxt) {
+      // Lines with "unexpected Throwable" are stack traces
+      // and should be printed in full.
+      if (!result.contains("unexpected Throwable")) {
+        String firstLine;
+        int lineSepPos = result.indexOf(System.lineSeparator());
+        if (lineSepPos != -1) {
+          firstLine = result.substring(0, lineSepPos);
+        } else {
+          firstLine = result;
+        }
+        int javaPos = firstLine.indexOf(".java:");
+        if (javaPos != -1) {
+          firstLine = firstLine.substring(javaPos + 5).trim();
+        }
+        result = firstLine;
+      }
+    }
+
+    return result;
+  }
+
+  public static Set<String> diagnosticsToStrings(
+      final Iterable<Diagnostic<? extends JavaFileObject>> actualDiagnostics,
+      boolean usingAnomsgtxt) {
+    Set<String> actualDiagnosticsStr = new LinkedHashSet<>();
+    for (Diagnostic<? extends JavaFileObject> diagnostic : actualDiagnostics) {
+      String diagnosticStr = TestUtilities.diagnosticToString(diagnostic, usingAnomsgtxt);
+      if (diagnosticStr != null) {
+        actualDiagnosticsStr.add(diagnosticStr);
+      }
+    }
+
+    return actualDiagnosticsStr;
+  }
+
+  /**
+   * Return the file absolute pathnames, separated by commas.
+   *
+   * @param javaFiles a list of Java files
+   * @return the file absolute pathnames, separated by commas
+   */
+  public static String summarizeSourceFiles(List<File> javaFiles) {
+    StringJoiner sj = new StringJoiner(", ");
+    for (File file : javaFiles) {
+      sj.add(file.getAbsolutePath());
+    }
+    return sj.toString();
+  }
+
+  public static File getTestFile(String fileRelativeToTestsDir) {
+    return new File("tests", fileRelativeToTestsDir);
+  }
+
+  public static File findComparisonFile(File testFile) {
+    final File comparisonFile =
+        new File(testFile.getParent(), testFile.getName().replace(".java", ".out"));
+    return comparisonFile;
+  }
+
+  public static List<String> optionMapToList(Map<String, @Nullable String> options) {
+    List<String> optionList = new ArrayList<>(options.size() * 2);
+
+    for (Map.Entry<String, @Nullable String> opt : options.entrySet()) {
+      optionList.add(opt.getKey());
+
+      if (opt.getValue() != null) {
+        optionList.add(opt.getValue());
+      }
+    }
+
+    return optionList;
+  }
+
+  public static void writeLines(File file, Iterable<?> lines) {
+    try {
+      final BufferedWriter bw = new BufferedWriter(new FileWriter(file, true));
+      Iterator<?> iter = lines.iterator();
+      while (iter.hasNext()) {
+        Object next = iter.next();
+        if (next == null) {
+          bw.write("<null>");
+        } else {
+          bw.write(next.toString());
+        }
+        bw.newLine();
+      }
+      bw.flush();
+      bw.close();
+
+    } catch (IOException io) {
+      throw new RuntimeException(io);
+    }
+  }
+
+  public static void writeDiagnostics(
+      File file,
+      File testFile,
+      List<String> expected,
+      List<String> actual,
+      List<String> unexpected,
+      List<String> missing,
+      boolean usingNoMsgText,
+      boolean testFailed) {
+    try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file, true)))) {
+      pw.println("File: " + testFile.getAbsolutePath());
+      pw.println("TestFailed: " + testFailed);
+      pw.println("Using nomsgtxt: " + usingNoMsgText);
+      pw.println("#Missing: " + missing.size() + "      #Unexpected: " + unexpected.size());
+
+      pw.println("Expected:");
+      pw.println(StringsPlume.joinLines(expected));
+      pw.println();
+
+      pw.println("Actual:");
+      pw.println(StringsPlume.joinLines(actual));
+      pw.println();
+
+      pw.println("Missing:");
+      pw.println(StringsPlume.joinLines(missing));
+      pw.println();
+
+      pw.println("Unexpected:");
+      pw.println(StringsPlume.joinLines(unexpected));
+      pw.println();
+
+      pw.println();
+      pw.println();
+      pw.flush();
+
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static void writeTestConfiguration(File file, TestConfiguration config) {
+    try {
+      final BufferedWriter bw = new BufferedWriter(new FileWriter(file, true));
+      bw.write(config.toString());
+      bw.newLine();
+      bw.newLine();
+      bw.flush();
+      bw.close();
+
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static void writeJavacArguments(
+      File file,
+      Iterable<? extends JavaFileObject> files,
+      Iterable<String> options,
+      Iterable<String> processors) {
+    try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file, true)))) {
+      pw.println("Files:");
+      for (JavaFileObject f : files) {
+        pw.println("    " + f.getName());
+      }
+      pw.println();
+
+      pw.println("Options:");
+      for (String o : options) {
+        pw.println("    " + o);
+      }
+      pw.println();
+
+      pw.println("Processors:");
+      for (String p : processors) {
+        pw.println("    " + p);
+      }
+      pw.println();
+      pw.println();
+
+      pw.flush();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * If the given TypecheckResult has unexpected or missing diagnostics, fail the running JUnit
+   * test.
+   *
+   * @param testResult the result of type-checking
+   */
+  public static void assertTestDidNotFail(TypecheckResult testResult) {
+    if (testResult.didTestFail()) {
+      if (getShouldEmitDebugInfo()) {
+        System.out.println("---------------- start of javac ouput ----------------");
+        System.out.println(testResult.getCompilationResult().getJavacOutput());
+        System.out.println("---------------- end of javac ouput ----------------");
+      } else {
+        System.out.println("To see the javac command line and output, run with: -Pemit.test.debug");
+      }
+      Assert.fail(testResult.summarize());
+    }
+  }
+
+  /**
+   * Create the directory (and its parents) if it does not exist.
+   *
+   * @param dir the directory to create
+   */
+  public static void ensureDirectoryExists(String dir) {
+    try {
+      Files.createDirectories(Paths.get(dir));
+    } catch (FileAlreadyExistsException e) {
+      // directory already exists
+    } catch (IOException e) {
+      throw new RuntimeException("Could not make directory: " + dir + ": " + e.getMessage());
+    }
+  }
+
+  /**
+   * Return true if the system property is set to "true". Return false if the system property is not
+   * set or is set to "false". Otherwise, errs.
+   *
+   * @param key system property to check
+   * @return true if the system property is set to "true". Return false if the system property is
+   *     not set or is set to "false". Otherwise, errs.
+   * @deprecated Use {@link SystemUtil#getBooleanSystemProperty(String)} instead.
+   */
+  @Deprecated // 2020-04-30
+  public static boolean testBooleanProperty(String key) {
+    return testBooleanProperty(key, false);
+  }
+
+  /**
+   * If the system property is set, return its boolean value; otherwise return {@code defaultValue}.
+   * Errs if the system property is set to a non-boolean value.
+   *
+   * @param key system property to check
+   * @param defaultValue value to use if the property is not set
+   * @return the boolean value of {@code key} or {@code defaultValue} if {@code key} is not set
+   * @deprecated Use {@link SystemUtil#getBooleanSystemProperty(String, boolean)} instead.
+   */
+  @Deprecated
+  public static boolean testBooleanProperty(String key, boolean defaultValue) {
+    return SystemUtil.getBooleanSystemProperty(key, defaultValue);
+  }
+
+  /**
+   * Returns the value of system property "emit.test.debug".
+   *
+   * @return the value of system property "emit.test.debug"
+   */
+  public static boolean getShouldEmitDebugInfo() {
+    return SystemPlume.getBooleanSystemProperty("emit.test.debug");
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java
new file mode 100644
index 0000000..a07d1aa
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java
@@ -0,0 +1,148 @@
+package org.checkerframework.framework.test;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+import org.checkerframework.framework.test.diagnostics.JavaDiagnosticReader;
+import org.checkerframework.framework.test.diagnostics.TestDiagnostic;
+import org.checkerframework.javacutil.SystemUtil;
+import org.plumelib.util.StringsPlume;
+
+/** Used by the Checker Framework test suite to run the framework and generate a test result. */
+public class TypecheckExecutor {
+
+  /** Creates a new TypecheckExecutor. */
+  public TypecheckExecutor() {}
+
+  /**
+   * Runs a typechecking test using the given configuration and returns the test result.
+   *
+   * @param configuration the test configuration
+   * @return the test result
+   */
+  public TypecheckResult runTest(TestConfiguration configuration) {
+    try {
+      CompilationResult result = compile(configuration);
+      return interpretResults(configuration, result);
+    } catch (OutOfMemoryError e) {
+      String message =
+          String.format(
+              "Max memory = %d, total memory = %d, free memory = %d.",
+              Runtime.getRuntime().maxMemory(),
+              Runtime.getRuntime().totalMemory(),
+              Runtime.getRuntime().freeMemory());
+      System.out.println(message);
+      System.err.println(message);
+      throw new Error(message, e);
+    }
+  }
+
+  /**
+   * Using the settings from the input configuration, compile all source files in the configuration,
+   * and return the result in a CompilationResult.
+   */
+  public CompilationResult compile(TestConfiguration configuration) {
+    String dOption = configuration.getOptions().get("-d");
+    if (dOption == null) {
+      throw new Error("-d not supplied");
+    }
+    TestUtilities.ensureDirectoryExists(dOption);
+
+    final StringWriter javacOutput = new StringWriter();
+    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
+
+    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
+    Iterable<? extends JavaFileObject> javaFiles =
+        fileManager.getJavaFileObjects(configuration.getTestSourceFiles().toArray(new File[] {}));
+
+    // Even though the method compiler.getTask takes a list of processors, it fails if
+    // processors are passed this way with the message:
+    //   error: Class names, 'org.checkerframework.checker.interning.InterningChecker', are only
+    //   accepted if annotation processing is explicitly requested
+    // Therefore, we now add them to the beginning of the options list.
+    final List<String> options = new ArrayList<>();
+    options.add("-processor");
+    options.add(String.join(",", configuration.getProcessors()));
+    if (SystemUtil.getJreVersion() == 8) {
+      options.add("-source");
+      options.add("8");
+      options.add("-target");
+      options.add("8");
+    }
+
+    List<String> nonJvmOptions = new ArrayList<>();
+    for (String option : configuration.getFlatOptions()) {
+      if (!option.startsWith("-J-")) {
+        nonJvmOptions.add(option);
+      }
+    }
+    nonJvmOptions.add("-Xmaxerrs");
+    nonJvmOptions.add("100000");
+    nonJvmOptions.add("-Xmaxwarns");
+    nonJvmOptions.add("100000");
+    nonJvmOptions.add("-Xlint:deprecation");
+
+    nonJvmOptions.add("-ApermitMissingJdk");
+    nonJvmOptions.add("-Anocheckjdk"); // temporary, for backward compatibility
+
+    options.addAll(nonJvmOptions);
+
+    if (configuration.shouldEmitDebugInfo()) {
+      System.out.println("Running test using the following invocation:");
+      System.out.println(
+          "javac "
+              + String.join(" ", options)
+              + " "
+              + StringsPlume.join(" ", configuration.getTestSourceFiles()));
+    }
+
+    JavaCompiler.CompilationTask task =
+        compiler.getTask(
+            javacOutput, fileManager, diagnostics, options, new ArrayList<String>(), javaFiles);
+
+    /*
+     * In Eclipse, std out and std err for multiple tests appear as one
+     * long stream. When selecting a specific failed test, one sees the
+     * expected/unexpected messages, but not the std out/err messages from
+     * that particular test. Can we improve this somehow?
+     */
+    final Boolean compiledWithoutError = task.call();
+    javacOutput.flush();
+    return new CompilationResult(
+        compiledWithoutError, javacOutput.toString(), javaFiles, diagnostics.getDiagnostics());
+  }
+
+  /**
+   * Reads the expected diagnostics for the given configuration and creates a TypecheckResult which
+   * contains all of the missing and expected diagnostics.
+   */
+  public TypecheckResult interpretResults(
+      TestConfiguration config, CompilationResult compilationResult) {
+    List<TestDiagnostic> expectedDiagnostics = readDiagnostics(config, compilationResult);
+    return TypecheckResult.fromCompilationResults(config, compilationResult, expectedDiagnostics);
+  }
+
+  /**
+   * A subclass can override this to filter out errors or add new expected errors. This method is
+   * called immediately before results are checked.
+   */
+  protected List<TestDiagnostic> readDiagnostics(
+      TestConfiguration config, CompilationResult compilationResult) {
+    List<TestDiagnostic> expectedDiagnostics;
+    if (config.getDiagnosticFiles() == null || config.getDiagnosticFiles().isEmpty()) {
+      expectedDiagnostics =
+          JavaDiagnosticReader.readJavaSourceFiles(compilationResult.getJavaFileObjects());
+    } else {
+      expectedDiagnostics = JavaDiagnosticReader.readDiagnosticFiles(config.getDiagnosticFiles());
+    }
+
+    return expectedDiagnostics;
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java
new file mode 100644
index 0000000..f82288c
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java
@@ -0,0 +1,162 @@
+package org.checkerframework.framework.test;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+import org.checkerframework.framework.test.diagnostics.TestDiagnostic;
+import org.checkerframework.framework.test.diagnostics.TestDiagnosticUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Represents the test results from typechecking one or more Java files using the given
+ * TestConfiguration.
+ */
+public class TypecheckResult {
+  private final TestConfiguration configuration;
+  private final CompilationResult compilationResult;
+  private final List<TestDiagnostic> expectedDiagnostics;
+
+  private final List<TestDiagnostic> missingDiagnostics;
+  private final List<TestDiagnostic> unexpectedDiagnostics;
+
+  protected TypecheckResult(
+      TestConfiguration configuration,
+      CompilationResult compilationResult,
+      List<TestDiagnostic> expectedDiagnostics,
+      List<TestDiagnostic> missingDiagnostics,
+      List<TestDiagnostic> unexpectedDiagnostics) {
+    this.configuration = configuration;
+    this.compilationResult = compilationResult;
+    this.expectedDiagnostics = expectedDiagnostics;
+    this.missingDiagnostics = missingDiagnostics;
+    this.unexpectedDiagnostics = unexpectedDiagnostics;
+  }
+
+  public TestConfiguration getConfiguration() {
+    return configuration;
+  }
+
+  public CompilationResult getCompilationResult() {
+    return compilationResult;
+  }
+
+  public List<Diagnostic<? extends JavaFileObject>> getActualDiagnostics() {
+    return compilationResult.getDiagnostics();
+  }
+
+  public List<TestDiagnostic> getExpectedDiagnostics() {
+    return expectedDiagnostics;
+  }
+
+  public boolean didTestFail() {
+    return !unexpectedDiagnostics.isEmpty() || !missingDiagnostics.isEmpty();
+  }
+
+  public List<TestDiagnostic> getMissingDiagnostics() {
+    return missingDiagnostics;
+  }
+
+  public List<TestDiagnostic> getUnexpectedDiagnostics() {
+    return unexpectedDiagnostics;
+  }
+
+  public List<String> getErrorHeaders() {
+    List<String> errorHeaders = new ArrayList<>();
+
+    // none of these should be true if the test didn't fail
+    if (didTestFail()) {
+      if (compilationResult.compiledWithoutError() && !expectedDiagnostics.isEmpty()) {
+        errorHeaders.add("The test run was expected to issue errors/warnings, but it did not.");
+
+      } else if (!compilationResult.compiledWithoutError() && expectedDiagnostics.isEmpty()) {
+        errorHeaders.add("The test run was not expected to issue errors/warnings, but it did.");
+      }
+
+      if (!unexpectedDiagnostics.isEmpty() || !missingDiagnostics.isEmpty()) {
+        int numExpected = expectedDiagnostics.size();
+        int numFound = numExpected - missingDiagnostics.size();
+        errorHeaders.add(
+            numFound
+                + " out of "
+                + StringsPlume.nplural(numExpected, "expected diagnostic")
+                + " "
+                + (numFound == 1 ? "was" : "were")
+                + " found.");
+      }
+    }
+
+    return errorHeaders;
+  }
+
+  /**
+   * Summarize unexpected and missing diagnostics.
+   *
+   * @return summary of failures
+   */
+  public String summarize() {
+    if (!didTestFail()) {
+      return "";
+    }
+    StringJoiner summaryBuilder = new StringJoiner(System.lineSeparator());
+    summaryBuilder.add(StringsPlume.joinLines(getErrorHeaders()));
+
+    if (!unexpectedDiagnostics.isEmpty()) {
+      int numUnexpected = unexpectedDiagnostics.size();
+      if (numUnexpected == 1) {
+        summaryBuilder.add("1 unexpected diagnostic was found");
+      } else {
+        summaryBuilder.add(numUnexpected + " unexpected diagnostics were found");
+      }
+
+      for (TestDiagnostic unexpected : unexpectedDiagnostics) {
+        summaryBuilder.add(unexpected.toString());
+      }
+    }
+
+    if (!missingDiagnostics.isEmpty()) {
+      int numMissing = missingDiagnostics.size();
+      summaryBuilder.add(
+          StringsPlume.nplural(numMissing, "expected diagnostic")
+              + " "
+              + (numMissing == 1 ? "was" : "were")
+              + " not found:");
+
+      for (TestDiagnostic missing : missingDiagnostics) {
+        summaryBuilder.add(missing.toString());
+      }
+    }
+
+    summaryBuilder.add(
+        "While type-checking "
+            + TestUtilities.summarizeSourceFiles(configuration.getTestSourceFiles()));
+    return summaryBuilder.toString();
+  }
+
+  public static TypecheckResult fromCompilationResults(
+      TestConfiguration configuration,
+      CompilationResult result,
+      List<TestDiagnostic> expectedDiagnostics) {
+
+    boolean usingAnomsgtxt = configuration.getOptions().containsKey("-Anomsgtext");
+    final Set<TestDiagnostic> actualDiagnostics =
+        TestDiagnosticUtils.fromJavaxDiagnosticList(result.getDiagnostics(), usingAnomsgtxt);
+
+    final Set<TestDiagnostic> unexpectedDiagnostics = new LinkedHashSet<>();
+    unexpectedDiagnostics.addAll(actualDiagnostics);
+    unexpectedDiagnostics.removeAll(expectedDiagnostics);
+
+    final List<TestDiagnostic> missingDiagnostics = new ArrayList<>(expectedDiagnostics);
+    missingDiagnostics.removeAll(actualDiagnostics);
+
+    return new TypecheckResult(
+        configuration,
+        result,
+        expectedDiagnostics,
+        missingDiagnostics,
+        new ArrayList<>(unexpectedDiagnostics));
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java
new file mode 100644
index 0000000..422e7e8
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java
@@ -0,0 +1,37 @@
+package org.checkerframework.framework.test.diagnostics;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/** The kinds of errors that can be encountered during typechecking. */
+public enum DiagnosticKind {
+  /** A warning. */
+  Warning("warning"),
+  /** An error. */
+  Error("error"),
+  /** A JSpecify diagnostic. */
+  JSpecify("jspecify"),
+  /** Something else. */
+  Other("other");
+
+  /** How this DiagnosticKind appears in error messages or source code. */
+  public final String parseString;
+
+  DiagnosticKind(String parseString) {
+    this.parseString = parseString;
+  }
+
+  private static final Map<String, DiagnosticKind> stringToCategory = new LinkedHashMap<>();
+
+  static {
+    for (DiagnosticKind cat : values()) {
+      stringToCategory.put(cat.parseString, cat);
+    }
+  }
+
+  /** Convert a string as it would appear in error messages or source code into a DiagnosticKind. */
+  public static @Nullable DiagnosticKind fromParseString(String parseStr) {
+    return stringToCategory.get(parseStr);
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java
new file mode 100644
index 0000000..388991b
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java
@@ -0,0 +1,236 @@
+package org.checkerframework.framework.test.diagnostics;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import javax.tools.JavaFileObject;
+import org.checkerframework.checker.index.qual.GTENegativeOne;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+import org.checkerframework.dataflow.qual.Pure;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * This class reads expected javac diagnostics from a single file. Its implementation is as an
+ * iterator over {@link TestDiagnosticLine}. However, clients should call the static methods: {@link
+ * #readJavaSourceFiles} reads diagnostics from multiple Java source files, and {@link
+ * #readDiagnosticFiles} reads diagnostics from multiple "diagnostic files".
+ */
+public class JavaDiagnosticReader implements Iterator<TestDiagnosticLine> {
+
+  ///
+  /// This class begins with the public static methods that clients use to read diagnostics.
+  ///
+
+  /**
+   * Returns all the diagnostics in any of the Java source files.
+   *
+   * @param files the Java files to read; each is a File or a JavaFileObject
+   * @return the TestDiagnostics from the input file
+   */
+  // The argument has type Iterable<? extends Object> because Java cannot resolve the overload
+  // of two versions that take Iterable<? extends File> and Iterable<? extends JavaFileObject>.
+  public static List<TestDiagnostic> readJavaSourceFiles(Iterable<? extends Object> files) {
+    List<JavaDiagnosticReader> readers = new ArrayList<>();
+    for (Object file : files) {
+      if (file instanceof JavaFileObject) {
+        readers.add(
+            new JavaDiagnosticReader(
+                (JavaFileObject) file, TestDiagnosticUtils::fromJavaSourceLine));
+      } else if (file instanceof File) {
+        readers.add(new JavaDiagnosticReader((File) file, TestDiagnosticUtils::fromJavaSourceLine));
+      } else {
+        throw new IllegalArgumentException(
+            String.format(
+                "Elements of argument should be File or JavaFileObject, not %s: %s",
+                file.getClass(), file));
+      }
+    }
+    return readDiagnostics(readers);
+  }
+
+  /**
+   * Reads diagnostics line-by-line from the input diagnostic files.
+   *
+   * @param files a set of diagnostic files
+   * @return the TestDiagnosticLines from the input files
+   */
+  public static List<TestDiagnostic> readDiagnosticFiles(Iterable<? extends File> files) {
+    List<JavaDiagnosticReader> readers =
+        CollectionsPlume.mapList(
+            (File file) ->
+                new JavaDiagnosticReader(
+                    file,
+                    (filename, line, lineNumber) ->
+                        TestDiagnosticUtils.fromDiagnosticFileLine(line)),
+            files);
+    return readDiagnostics(readers);
+  }
+
+  ///
+  /// End of public static methods, start of private static methods.
+  ///
+
+  /**
+   * Returns all the diagnostics in any of the files.
+   *
+   * @param readers the files (Java or Diagnostics format) to read
+   * @return the List of TestDiagnosticLines from the input file
+   */
+  private static List<TestDiagnostic> readDiagnostics(Iterable<JavaDiagnosticReader> readers) {
+    return diagnosticLinesToDiagnostics(readDiagnosticLines(readers));
+  }
+
+  /**
+   * Reads the entire input file using the given codec and returns the resulting line.
+   *
+   * @param readers the files (Java or Diagnostics format) to read
+   * @return the List of TestDiagnosticLines from the input file
+   */
+  private static List<TestDiagnosticLine> readDiagnosticLines(
+      Iterable<JavaDiagnosticReader> readers) {
+    List<TestDiagnosticLine> result = new ArrayList<>();
+    for (JavaDiagnosticReader reader : readers) {
+      result.addAll(readDiagnosticLines(reader));
+    }
+    return result;
+  }
+
+  /**
+   * Reads the entire input file using the given codec and returns the resulting lines, filtering
+   * out empty ones produced by JavaDiagnosticReader.
+   *
+   * @param reader the file (Java or Diagnostics format) to read
+   * @return the List of TestDiagnosticLines from the input file
+   */
+  private static List<TestDiagnosticLine> readDiagnosticLines(JavaDiagnosticReader reader) {
+    List<TestDiagnosticLine> diagnosticLines = new ArrayList<>();
+    while (reader.hasNext()) {
+      TestDiagnosticLine line = reader.next();
+      // A JavaDiagnosticReader can return a lot of empty diagnostics.  Filter them out.
+      if (line.hasDiagnostics()) {
+        diagnosticLines.add(line);
+      }
+    }
+    return diagnosticLines;
+  }
+
+  /** Converts a list of TestDiagnosticLine into a list of TestDiagnostic. */
+  private static List<TestDiagnostic> diagnosticLinesToDiagnostics(List<TestDiagnosticLine> lines) {
+    List<TestDiagnostic> result = new ArrayList<>((int) (lines.size() * 1.1));
+    for (TestDiagnosticLine line : lines) {
+      result.addAll(line.getDiagnostics());
+    }
+    return result;
+  }
+
+  /**
+   * StringToTestDiagnosticLine converts a line of a file into a TestDiagnosticLine. There are
+   * currently two possible formats: one for Java source code, and one for Diagnostic files.
+   *
+   * <p>No classes implement this interface. The methods TestDiagnosticUtils.fromJavaSourceLine and
+   * TestDiagnosticUtils.fromDiagnosticFileLine instantiate the method.
+   */
+  private interface StringToTestDiagnosticLine {
+
+    /**
+     * Converts the specified line of the file into a {@link TestDiagnosticLine}.
+     *
+     * @param filename name of the file
+     * @param line the text of the line to convert to a TestDiagnosticLine
+     * @param lineNumber the line number of the line
+     * @return TestDiagnosticLine corresponding to {@code line}
+     */
+    TestDiagnosticLine createTestDiagnosticLine(String filename, String line, long lineNumber);
+  }
+
+  ///
+  /// End of static methods, start of per-instance state.
+  ///
+
+  private final StringToTestDiagnosticLine codec;
+
+  private final String filename;
+
+  private LineNumberReader reader;
+
+  private @Nullable String nextLine = null;
+  private @GTENegativeOne int nextLineNumber = -1;
+
+  private JavaDiagnosticReader(File toRead, StringToTestDiagnosticLine codec) {
+    this.codec = codec;
+    this.filename = toRead.getName();
+    try {
+      reader = new LineNumberReader(new FileReader(toRead));
+      advance();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private JavaDiagnosticReader(JavaFileObject toReadFileObject, StringToTestDiagnosticLine codec) {
+    this.codec = codec;
+    this.filename = new File(toReadFileObject.getName()).getName();
+    try {
+      reader = new LineNumberReader(toReadFileObject.openReader(true));
+      advance();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  @Pure
+  public boolean hasNext() {
+    return nextLine != null;
+  }
+
+  @Override
+  public void remove() {
+    throw new UnsupportedOperationException(
+        "Cannot remove elements using JavaDiagnosticFileReader.");
+  }
+
+  @Override
+  public TestDiagnosticLine next() {
+    if (nextLine == null) {
+      throw new NoSuchElementException();
+    }
+
+    String currentLine = nextLine;
+    int currentLineNumber = nextLineNumber;
+
+    try {
+      advance();
+
+      currentLine = TestDiagnosticUtils.handleEndOfLineJavaDiagnostic(currentLine);
+
+      if (TestDiagnosticUtils.isJavaDiagnosticLineStart(currentLine)) {
+        while (TestDiagnosticUtils.isJavaDiagnosticLineContinuation(nextLine)) {
+          currentLine = currentLine.trim() + " " + TestDiagnosticUtils.continuationPart(nextLine);
+          currentLineNumber = nextLineNumber;
+          advance();
+        }
+      }
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+
+    return codec.createTestDiagnosticLine(filename, currentLine, currentLineNumber);
+  }
+
+  @RequiresNonNull("reader")
+  protected void advance(@UnknownInitialization JavaDiagnosticReader this) throws IOException {
+    nextLine = reader.readLine();
+    nextLineNumber = reader.getLineNumber();
+    if (nextLine == null) {
+      reader.close();
+    }
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java
new file mode 100644
index 0000000..4c71ca2
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java
@@ -0,0 +1,115 @@
+package org.checkerframework.framework.test.diagnostics;
+
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Represents an expected error/warning message in a Java test file or an error/warning reported by
+ * the Javac compiler. By contrast, {@link TestDiagnosticLine} represents a set of TestDiagnostics,
+ * all of which were read from the same line of a file.
+ *
+ * @see JavaDiagnosticReader
+ */
+public class TestDiagnostic {
+
+  private final String filename;
+  private final long lineNumber;
+  private final DiagnosticKind kind;
+
+  /**
+   * An error key or full error message that usually appears between parentheses in diagnostic
+   * messages.
+   */
+  private final String message;
+
+  /** Whether this diagnostic should no longer be reported after whole program inference. */
+  private final boolean isFixable;
+
+  /** Whether or not the toString representation should omit the parentheses around the message. */
+  private final boolean omitParentheses;
+
+  /** Basic constructor that sets the immutable fields of this diagnostic. */
+  public TestDiagnostic(
+      String filename,
+      long lineNumber,
+      DiagnosticKind kind,
+      String message,
+      boolean isFixable,
+      boolean omitParentheses) {
+    this.filename = filename;
+    this.lineNumber = lineNumber;
+    this.kind = kind;
+    this.message = message;
+    this.isFixable = isFixable;
+    this.omitParentheses = omitParentheses;
+  }
+
+  public String getFilename() {
+    return filename;
+  }
+
+  public long getLineNumber() {
+    return lineNumber;
+  }
+
+  public DiagnosticKind getKind() {
+    return kind;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+
+  public boolean isFixable() {
+    return isFixable;
+  }
+
+  /**
+   * Returns whether or not the printed representation should omit parentheses around the message.
+   *
+   * @return whether or not the printed representation should omit parentheses around the message
+   */
+  public boolean shouldOmitParentheses() {
+    return omitParentheses;
+  }
+
+  /**
+   * Equality is compared without isFixable/omitParentheses.
+   *
+   * @return true if this and otherObj are equal according to filename, lineNumber, kind, and
+   *     message
+   */
+  @Override
+  public boolean equals(@Nullable Object otherObj) {
+    if (otherObj == null || otherObj.getClass() != TestDiagnostic.class) {
+      return false;
+    }
+
+    final TestDiagnostic other = (TestDiagnostic) otherObj;
+    return other.filename.equals(this.filename)
+        && other.lineNumber == lineNumber
+        && other.kind == this.kind
+        && other.message.equals(this.message);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(filename, lineNumber, kind, message);
+  }
+
+  /**
+   * Returns a representation of this diagnostic as if it appeared in a diagnostics file.
+   *
+   * @return a representation of this diagnostic as if it appeared in a diagnostics file
+   */
+  @Override
+  public String toString() {
+    if (kind == DiagnosticKind.JSpecify) {
+      return filename + ":" + lineNumber + ": " + message;
+    }
+    if (omitParentheses) {
+      return filename + ":" + lineNumber + ": " + kind.parseString + ": " + message;
+    }
+    return filename + ":" + lineNumber + ": " + kind.parseString + ": (" + message + ")";
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticLine.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticLine.java
new file mode 100644
index 0000000..7c12461
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticLine.java
@@ -0,0 +1,39 @@
+package org.checkerframework.framework.test.diagnostics;
+
+import java.util.List;
+
+/** Represents a list of TestDiagnostics, which was read from a one line of a file. */
+public class TestDiagnosticLine {
+  private final String filename;
+  private final long lineNumber;
+  private final String originalLine;
+  private final List<TestDiagnostic> diagnostics;
+
+  public TestDiagnosticLine(
+      String filename, long lineNumber, String originalLine, List<TestDiagnostic> diagnostics) {
+    this.filename = filename;
+    this.lineNumber = lineNumber;
+    this.originalLine = originalLine;
+    this.diagnostics = diagnostics;
+  }
+
+  public String getFilename() {
+    return filename;
+  }
+
+  public boolean hasDiagnostics() {
+    return !diagnostics.isEmpty();
+  }
+
+  public long getLineNumber() {
+    return lineNumber;
+  }
+
+  public String getOriginalLine() {
+    return originalLine;
+  }
+
+  public List<TestDiagnostic> getDiagnostics() {
+    return diagnostics;
+  }
+}
diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java
new file mode 100644
index 0000000..aa536ed
--- /dev/null
+++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java
@@ -0,0 +1,421 @@
+package org.checkerframework.framework.test.diagnostics;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.Pair;
+import org.plumelib.util.CollectionsPlume;
+
+/** A set of utilities and factory methods useful for working with TestDiagnostics. */
+public class TestDiagnosticUtils {
+
+  /** How the diagnostics appear in Java source files. */
+  public static final String DIAGNOSTIC_IN_JAVA_REGEX =
+      "\\s*(error|fixable-error|warning|fixable-warning|other):\\s*(\\(?.*\\)?)\\s*";
+  /** How the diagnostics appear in Java source files. */
+  public static final Pattern DIAGNOSTIC_IN_JAVA_PATTERN =
+      Pattern.compile(DIAGNOSTIC_IN_JAVA_REGEX);
+
+  public static final String DIAGNOSTIC_WARNING_IN_JAVA_REGEX = "\\s*warning:\\s*(.*\\s*.*)\\s*";
+  public static final Pattern DIAGNOSTIC_WARNING_IN_JAVA_PATTERN =
+      Pattern.compile(DIAGNOSTIC_WARNING_IN_JAVA_REGEX);
+
+  // How the diagnostics appear in javax tools diagnostics from the compiler.
+  public static final String DIAGNOSTIC_REGEX = ":(\\d+):" + DIAGNOSTIC_IN_JAVA_REGEX;
+  public static final Pattern DIAGNOSTIC_PATTERN = Pattern.compile(DIAGNOSTIC_REGEX);
+
+  public static final String DIAGNOSTIC_WARNING_REGEX =
+      ":(\\d+):" + DIAGNOSTIC_WARNING_IN_JAVA_REGEX;
+  public static final Pattern DIAGNOSTIC_WARNING_PATTERN =
+      Pattern.compile(DIAGNOSTIC_WARNING_REGEX);
+
+  // How the diagnostics appear in diagnostic files (.out).
+  public static final String DIAGNOSTIC_FILE_REGEX = ".+\\.java" + DIAGNOSTIC_REGEX;
+  public static final Pattern DIAGNOSTIC_FILE_PATTERN = Pattern.compile(DIAGNOSTIC_FILE_REGEX);
+
+  public static final String DIAGNOSTIC_FILE_WARNING_REGEX = ".+\\.java" + DIAGNOSTIC_WARNING_REGEX;
+  public static final Pattern DIAGNOSTIC_FILE_WARNING_PATTERN =
+      Pattern.compile(DIAGNOSTIC_FILE_WARNING_REGEX);
+
+  /**
+   * Instantiate the diagnostic based on a string that would appear in diagnostic files (i.e. files
+   * that only contain line after line of expected diagnostics).
+   *
+   * @param stringFromDiagnosticFile a single diagnostic string to parse
+   * @return a new TestDiagnostic
+   */
+  public static TestDiagnostic fromDiagnosticFileString(String stringFromDiagnosticFile) {
+    return fromPatternMatching(
+        DIAGNOSTIC_FILE_PATTERN,
+        DIAGNOSTIC_WARNING_IN_JAVA_PATTERN,
+        "",
+        null,
+        stringFromDiagnosticFile);
+  }
+
+  /**
+   * Instantiate the diagnostic from a string that would appear in a Java file, e.g.: "error:
+   * (message)"
+   *
+   * @param filename the file containing the diagnostic (and the error)
+   * @param lineNumber the line number of the line immediately below the diagnostic comment in the
+   *     Java file
+   * @param stringFromJavaFile the string containing the diagnostic
+   * @return a new TestDiagnostic
+   */
+  public static TestDiagnostic fromJavaFileComment(
+      String filename, long lineNumber, String stringFromJavaFile) {
+    return fromPatternMatching(
+        DIAGNOSTIC_IN_JAVA_PATTERN,
+        DIAGNOSTIC_WARNING_IN_JAVA_PATTERN,
+        filename,
+        lineNumber,
+        stringFromJavaFile);
+  }
+  /**
+   * Instantiate a diagnostic from output produced by the Java compiler. The resulting diagnostic is
+   * never fixable and always has parentheses.
+   */
+  public static TestDiagnostic fromJavaxToolsDiagnostic(
+      String diagnosticString, boolean noMsgText) {
+    // It would be nice not to parse this from the diagnostic string.
+    // However, diagnostic.toString() may contain "[unchecked]" even though getMessage() does not.
+    // Since we want to match the error messages reported by javac exactly, we must parse.
+    Pair<String, String> trimmed = formatJavaxToolString(diagnosticString, noMsgText);
+    return fromPatternMatching(
+        DIAGNOSTIC_PATTERN, DIAGNOSTIC_WARNING_PATTERN, trimmed.second, null, trimmed.first);
+  }
+
+  /**
+   * Instantiate the diagnostic from a JSpecify string that would appear in a Java file, e.g.:
+   * "jspecify_some_category".
+   *
+   * @param filename the file containing the diagnostic (and the error)
+   * @param lineNumber the line number of the line immediately below the diagnostic comment in the
+   *     Java file
+   * @param stringFromJavaFile the string containing the diagnostic
+   * @return a new TestDiagnostic
+   */
+  public static TestDiagnostic fromJSpecifyFileComment(
+      String filename, long lineNumber, String stringFromJavaFile) {
+    return new TestDiagnostic(
+        filename,
+        lineNumber,
+        DiagnosticKind.JSpecify,
+        stringFromJavaFile,
+        /*isFixable=*/ false,
+        /*omitParentheses=*/ true);
+  }
+
+  /**
+   * Instantiate the diagnostic via pattern-matching against patterns.
+   *
+   * @param diagnosticPattern a pattern that matches any diagnostic
+   * @param warningPattern a pattern that matches a warning diagnostic
+   * @param filename the file name
+   * @param lineNumber the line number
+   * @param diagnosticString the string to parse
+   * @return a diagnostic parsed from the given string
+   */
+  @SuppressWarnings("nullness") // TODO: regular expression group access
+  protected static TestDiagnostic fromPatternMatching(
+      Pattern diagnosticPattern,
+      Pattern warningPattern,
+      String filename,
+      @Nullable Long lineNumber,
+      String diagnosticString) {
+    final DiagnosticKind kind;
+    final String message;
+    final boolean isFixable;
+    final boolean noParentheses;
+    long lineNo = -1;
+    int capturingGroupOffset = 1;
+
+    if (lineNumber != null) {
+      lineNo = lineNumber;
+      capturingGroupOffset = 0;
+    }
+
+    Matcher diagnosticMatcher = diagnosticPattern.matcher(diagnosticString);
+    if (diagnosticMatcher.matches()) {
+      Pair<DiagnosticKind, Boolean> categoryToFixable =
+          parseCategoryString(diagnosticMatcher.group(1 + capturingGroupOffset));
+      kind = categoryToFixable.first;
+      isFixable = categoryToFixable.second;
+      String msg = diagnosticMatcher.group(2 + capturingGroupOffset).trim();
+      noParentheses = msg.equals("") || msg.charAt(0) != '(' || msg.charAt(msg.length() - 1) != ')';
+      message = noParentheses ? msg : msg.substring(1, msg.length() - 1);
+
+      if (lineNumber == null) {
+        lineNo = Long.parseLong(diagnosticMatcher.group(1));
+      }
+
+    } else {
+      Matcher warningMatcher = warningPattern.matcher(diagnosticString);
+      if (warningMatcher.matches()) {
+        kind = DiagnosticKind.Warning;
+        isFixable = false;
+        message = warningMatcher.group(1 + capturingGroupOffset);
+        noParentheses = true;
+
+        if (lineNumber == null) {
+          lineNo = Long.parseLong(diagnosticMatcher.group(1));
+        }
+
+      } else if (diagnosticString.startsWith("warning:")) {
+        kind = DiagnosticKind.Warning;
+        isFixable = false;
+        message = diagnosticString.substring("warning:".length()).trim();
+        noParentheses = true;
+        if (lineNumber != null) {
+          lineNo = lineNumber;
+        } else {
+          lineNo = 0;
+        }
+
+      } else {
+        kind = DiagnosticKind.Other;
+        isFixable = false;
+        message = diagnosticString;
+        noParentheses = true;
+
+        // this should only happen if we are parsing a Java Diagnostic from the compiler
+        // that we did do not handle
+        if (lineNumber == null) {
+          lineNo = -1;
+        }
+      }
+    }
+    return new TestDiagnostic(filename, lineNo, kind, message, isFixable, noParentheses);
+  }
+
+  /**
+   * Given a javax diagnostic, return a pair of (trimmed, fileame), where "trimmed" is the first
+   * line of the message, without the leading filename.
+   *
+   * @param original a javax diagnostic
+   * @param noMsgText whether to do work; if false, this returns a pair of (argument, "").
+   * @return the diagnostic, split into message and filename
+   */
+  public static Pair<String, String> formatJavaxToolString(String original, boolean noMsgText) {
+    String trimmed = original;
+    String filename = "";
+    if (noMsgText) {
+      if (!retainAllLines(trimmed)) {
+        int lineSepPos = trimmed.indexOf(System.lineSeparator());
+        if (lineSepPos != -1) {
+          trimmed = trimmed.substring(0, lineSepPos);
+        }
+
+        int extensionPos = trimmed.indexOf(".java:");
+        if (extensionPos != -1) {
+          int basenameStart = trimmed.lastIndexOf(File.separator);
+          filename = trimmed.substring(basenameStart + 1, extensionPos + 5).trim();
+          trimmed = trimmed.substring(extensionPos + 5).trim();
+        }
+      }
+    }
+
+    return Pair.of(trimmed, filename);
+  }
+
+  /**
+   * Returns true if all lines of the message should be shown, false if only the first line should
+   * be shown.
+   *
+   * @param message a diagnostic message
+   * @return true if all lines of the message should be shown
+   */
+  private static boolean retainAllLines(String message) {
+    // Retain all if it is a thrown exception "unexpected Throwable" or it is a Checker Framework
+    // Error (contains "Compilation unit") or is OutOfMemoryError.
+    return message.contains("unexpected Throwable")
+        || message.contains("Compilation unit")
+        || message.contains("OutOfMemoryError");
+  }
+
+  /**
+   * Given a category string that may be prepended with "fixable-", return the category enum that
+   * corresponds with the category and whether or not it is a isFixable error
+   */
+  private static Pair<DiagnosticKind, Boolean> parseCategoryString(String category) {
+    final String fixable = "fixable-";
+    final boolean isFixable = category.startsWith(fixable);
+    if (isFixable) {
+      category = category.substring(fixable.length());
+    }
+    DiagnosticKind categoryEnum = DiagnosticKind.fromParseString(category);
+    if (categoryEnum == null) {
+      throw new Error("Unparsable category: " + category);
+    }
+
+    return Pair.of(categoryEnum, isFixable);
+  }
+
+  /**
+   * Return true if this line in a Java file indicates an expected diagnostic that might be
+   * continued on the next line.
+   */
+  public static boolean isJavaDiagnosticLineStart(String originalLine) {
+    final String trimmedLine = originalLine.trim();
+    return trimmedLine.startsWith("// ::") || trimmedLine.startsWith("// warning:");
+  }
+
+  /**
+   * Convert an end-of-line diagnostic message to a beginning-of-line one. Returns the argument
+   * unchanged if it does not contain an end-of-line diagnostic message.
+   *
+   * <p>Most diagnostics in Java files start at the beginning of a line. Occasionally, javac issues
+   * a warning about implicit code, such as an implicit constructor, on the line <em>immediately
+   * after</em> a curly brace. The only place to put the expected diagnostic message is on the line
+   * with the curly brace.
+   *
+   * <p>This implementation replaces "{ // ::" by "// ::", converting the end-of-line diagnostic
+   * message to a beginning-of-line one that the rest of the code can handle. It is rather specific
+   * (to avoid false positive matches, such as when "// ::" is commented out in source code). It
+   * could be extended in the future if such an extension is necessary.
+   */
+  public static String handleEndOfLineJavaDiagnostic(String originalLine) {
+    int curlyIndex = originalLine.indexOf("{ // ::");
+    if (curlyIndex == -1) {
+      return originalLine;
+    } else {
+      return originalLine.substring(curlyIndex + 2);
+    }
+  }
+
+  /** Return true if this line in a Java file continues an expected diagnostic. */
+  @EnsuresNonNullIf(result = true, expression = "#1")
+  public static boolean isJavaDiagnosticLineContinuation(@Nullable String originalLine) {
+    if (originalLine == null) {
+      return false;
+    }
+    final String trimmedLine = originalLine.trim();
+    // Unlike with errors, there is no logic elsewhere for splitting multiple "warning:"s.  So,
+    // avoid concatenating them.  Also, each one must begin a line.  They are allowed to wrap to
+    // the next line, though.
+    return trimmedLine.startsWith("// ") && !trimmedLine.startsWith("// warning:");
+  }
+
+  /**
+   * Return the continuation part. The argument is such that {@link
+   * #isJavaDiagnosticLineContinuation} returns true.
+   */
+  public static String continuationPart(String originalLine) {
+    return originalLine.trim().substring(2).trim();
+  }
+
+  /**
+   * Convert a line in a Java source file to a TestDiagnosticLine.
+   *
+   * <p>The input {@code line} is possibly the concatenation of multiple source lines, if the
+   * diagnostic was split across lines in the source code.
+   */
+  public static TestDiagnosticLine fromJavaSourceLine(
+      String filename, String line, long lineNumber) {
+    final String trimmedLine = line.trim();
+    long errorLine = lineNumber + 1;
+
+    if (trimmedLine.startsWith("// ::")) {
+      String restOfLine = trimmedLine.substring(5); // drop the "// ::"
+      String[] diagnosticStrs = restOfLine.split("::");
+      List<TestDiagnostic> diagnostics =
+          CollectionsPlume.mapList(
+              (String diagnostic) -> fromJavaFileComment(filename, errorLine, diagnostic),
+              diagnosticStrs);
+      return new TestDiagnosticLine(
+          filename, errorLine, line, Collections.unmodifiableList(diagnostics));
+
+    } else if (trimmedLine.startsWith("// warning:")) {
+      // This special diagnostic does not expect a line number nor a file name
+      String diagnosticString = trimmedLine.substring(2);
+      TestDiagnostic diagnostic = fromJavaFileComment("", 0, diagnosticString);
+      return new TestDiagnosticLine("", 0, line, Collections.singletonList(diagnostic));
+    } else if (trimmedLine.startsWith("//::")) {
+      TestDiagnostic diagnostic =
+          new TestDiagnostic(
+              filename,
+              lineNumber,
+              DiagnosticKind.Error,
+              "Use \"// ::\", not \"//::\"",
+              false,
+              true);
+      return new TestDiagnosticLine(
+          filename, lineNumber, line, Collections.singletonList(diagnostic));
+    } else if (trimmedLine.startsWith("// jspecify_")) {
+      TestDiagnostic diagnostic =
+          fromJSpecifyFileComment(filename, errorLine, trimmedLine.substring(3));
+      return new TestDiagnosticLine(
+          filename, errorLine, line, Collections.singletonList(diagnostic));
+    } else {
+      // It's a bit gross to create empty diagnostics (returning null might be more
+      // efficient), but they will be filtered out later.
+      return new TestDiagnosticLine(filename, errorLine, line, Collections.emptyList());
+    }
+  }
+
+  /** Convert a line in a DiagnosticFile to a TestDiagnosticLine. */
+  public static TestDiagnosticLine fromDiagnosticFileLine(String diagnosticLine) {
+    final String trimmedLine = diagnosticLine.trim();
+    if (trimmedLine.startsWith("#") || trimmedLine.isEmpty()) {
+      return new TestDiagnosticLine("", -1, diagnosticLine, Collections.emptyList());
+    }
+
+    TestDiagnostic diagnostic = fromDiagnosticFileString(diagnosticLine);
+    return new TestDiagnosticLine(
+        "", diagnostic.getLineNumber(), diagnosticLine, Arrays.asList(diagnostic));
+  }
+
+  public static Set<TestDiagnostic> fromJavaxDiagnosticList(
+      List<Diagnostic<? extends JavaFileObject>> javaxDiagnostics, boolean noMsgText) {
+    Set<TestDiagnostic> diagnostics = new LinkedHashSet<>(javaxDiagnostics.size());
+
+    for (Diagnostic<? extends JavaFileObject> diagnostic : javaxDiagnostics) {
+      // See TestDiagnosticUtils as to why we use diagnostic.toString rather
+      // than convert from the diagnostic itself
+      final String diagnosticString = diagnostic.toString();
+
+      // suppress Xlint warnings
+      if (diagnosticString.contains("uses unchecked or unsafe operations.")
+          || diagnosticString.contains("Recompile with -Xlint:unchecked for details.")
+          || diagnosticString.endsWith(" declares unsafe vararg methods.")
+          || diagnosticString.contains("Recompile with -Xlint:varargs for details.")) {
+        continue;
+      }
+
+      diagnostics.add(TestDiagnosticUtils.fromJavaxToolsDiagnostic(diagnosticString, noMsgText));
+    }
+
+    return diagnostics;
+  }
+
+  /**
+   * Converts the given diagnostics to strings (as they would appear in a source file individually).
+   *
+   * @param diagnostics a list of diagnostics
+   * @return a list of the diagnastics as they would appear in a source file
+   */
+  public static List<String> diagnosticsToString(List<TestDiagnostic> diagnostics) {
+    return CollectionsPlume.mapList(TestDiagnostic::toString, diagnostics);
+  }
+
+  public static void removeDiagnosticsOfKind(
+      DiagnosticKind kind, List<TestDiagnostic> expectedDiagnostics) {
+    for (int i = 0; i < expectedDiagnostics.size(); /*no-increment*/ ) {
+      if (expectedDiagnostics.get(i).getKind() == kind) {
+        expectedDiagnostics.remove(i);
+      } else {
+        ++i;
+      }
+    }
+  }
+}
diff --git a/framework-test/src/taglet/java/org/checkerframework/taglet/ManualTaglet.java b/framework-test/src/taglet/java/org/checkerframework/taglet/ManualTaglet.java
new file mode 100644
index 0000000..0535fb7
--- /dev/null
+++ b/framework-test/src/taglet/java/org/checkerframework/taglet/ManualTaglet.java
@@ -0,0 +1,125 @@
+// Keep this file in sync with
+// ../../../../../tagletJdk11/java/org/checkerframework/taglet/ManualTaglet.java .
+
+package org.checkerframework.taglet;
+
+import com.sun.javadoc.Tag;
+import com.sun.tools.doclets.Taglet;
+import java.util.Map;
+import java.util.StringJoiner;
+
+/**
+ * A taglet for processing the {@code @checker_framework.manual} javadoc block tag, which inserts
+ * references to the Checker Framework manual into javadoc.
+ *
+ * <p>The {@code @checker_framework.manual} tag is used as follows:
+ *
+ * <ul>
+ *   <li>{@code @checker_framework.manual #} expands to a top-level link to the Checker Framework
+ *       manual
+ *   <li>{@code @checker_framework.manual #anchor text} expands to a link with some text to a
+ *       particular part of the manual
+ * </ul>
+ */
+public class ManualTaglet implements Taglet {
+
+  @Override
+  public String getName() {
+    return "checker_framework.manual";
+  }
+
+  @Override
+  public boolean inConstructor() {
+    return true;
+  }
+
+  @Override
+  public boolean inField() {
+    return true;
+  }
+
+  @Override
+  public boolean inMethod() {
+    return true;
+  }
+
+  @Override
+  public boolean inOverview() {
+    return true;
+  }
+
+  @Override
+  public boolean inPackage() {
+    return true;
+  }
+
+  @Override
+  public boolean inType() {
+    return true;
+  }
+
+  @Override
+  public boolean isInlineTag() {
+    return false;
+  }
+
+  /**
+   * Formats a link, given an array of tokens.
+   *
+   * @param parts the array of tokens
+   * @return a link to the manual top-level if the array size is one, or a link to a part of the
+   *     manual if it's larger than one
+   */
+  private String formatLink(String[] parts) {
+    String anchor, text;
+    if (parts.length < 2) {
+      anchor = "";
+      text = "Checker Framework";
+    } else {
+      anchor = parts[0];
+      text = parts[1];
+    }
+    return String.format("<A HREF=\"https://checkerframework.org/manual/%s\">%s</A>", anchor, text);
+  }
+
+  /**
+   * Formats the {@code @checker_framework.manual} tag, prepending the tag header to the tag
+   * content.
+   *
+   * @param text the tag content
+   * @return the formatted tag
+   */
+  private String formatHeader(String text) {
+    return String.format("<DT><B>See the Checker Framework Manual:</B><DD>%s<BR>", text);
+  }
+
+  @Override
+  public String toString(Tag tag) {
+    String[] split = tag.text().split(" ", 2);
+    return formatHeader(formatLink(split));
+  }
+
+  @Override
+  public String toString(Tag[] tags) {
+    if (tags.length == 0) {
+      return "";
+    }
+    StringJoiner sb = new StringJoiner(", ");
+    for (Tag t : tags) {
+      String text = t.text();
+      String[] split = text.split(" ", 2);
+      sb.add(formatLink(split));
+    }
+    return formatHeader(sb.toString());
+  }
+
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  public static void register(Map tagletMap) {
+    ManualTaglet tag = new ManualTaglet();
+    Taglet t = (Taglet) tagletMap.get(tag.getName());
+    if (t != null) {
+      tagletMap.remove(tag.getName());
+    }
+    tagletMap.put(tag.getName(), tag);
+  }
+}
diff --git a/framework-test/src/tagletJdk11/java/org/checkerframework/taglet/ManualTaglet.java b/framework-test/src/tagletJdk11/java/org/checkerframework/taglet/ManualTaglet.java
new file mode 100644
index 0000000..2de85b9
--- /dev/null
+++ b/framework-test/src/tagletJdk11/java/org/checkerframework/taglet/ManualTaglet.java
@@ -0,0 +1,125 @@
+// Keep this file in sync with
+// ../../../../../taglet/java/org/checkerframework/taglet/ManualTaglet.java .
+
+package org.checkerframework.taglet;
+
+import com.sun.source.doctree.DocTree;
+import com.sun.source.doctree.TextTree;
+import com.sun.source.doctree.UnknownBlockTagTree;
+import com.sun.source.doctree.UnknownInlineTagTree;
+import com.sun.source.util.SimpleDocTreeVisitor;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.lang.model.element.Element;
+import jdk.javadoc.doclet.Taglet;
+
+/**
+ * A taglet for processing the {@code @checker_framework.manual} javadoc block tag, which inserts
+ * references to the Checker Framework manual into javadoc.
+ *
+ * <p>The {@code @checker_framework.manual} tag is used as follows:
+ *
+ * <ul>
+ *   <li>{@code @checker_framework.manual #} expands to a top-level link to the Checker Framework
+ *       manual
+ *   <li>{@code @checker_framework.manual #anchor text} expands to a link with some text to a
+ *       particular part of the manual
+ * </ul>
+ */
+public class ManualTaglet implements Taglet {
+
+  private static final String NAME = "checker_framework.manual";
+
+  @Override
+  public String getName() {
+    return NAME;
+  }
+
+  private final EnumSet<Location> allowedSet = EnumSet.allOf(Location.class);
+
+  @Override
+  public Set<Taglet.Location> getAllowedLocations() {
+    return allowedSet;
+  }
+
+  @Override
+  public boolean isInlineTag() {
+    return false;
+  }
+
+  /**
+   * Formats a link, given an array of tokens.
+   *
+   * @param parts the array of tokens
+   * @return a link to the manual top-level if the array size is one, or a link to a part of the
+   *     manual if it's larger than one
+   */
+  private String formatLink(String[] parts) {
+    String anchor, text;
+    if (parts.length < 2) {
+      anchor = "";
+      text = "Checker Framework";
+    } else {
+      anchor = parts[0];
+      text = parts[1];
+    }
+    return String.format("<A HREF=\"https://checkerframework.org/manual/%s\">%s</A>", anchor, text);
+  }
+
+  /**
+   * Formats the {@code @checker_framework.manual} tag, prepending the tag header to the tag
+   * content.
+   *
+   * @param text the tag content
+   * @return the formatted tag
+   */
+  private String formatHeader(String text) {
+    return String.format("<DT><B>See the Checker Framework Manual:</B><DD>%s<BR>", text);
+  }
+
+  @Override
+  public String toString(List<? extends DocTree> tags, Element element) {
+    if (tags.isEmpty()) {
+      return "";
+    }
+    StringJoiner sb = new StringJoiner(", ");
+    for (DocTree t : tags) {
+      String text = getText(t);
+      String[] split = text.split(" ", 2);
+      sb.add(formatLink(split));
+    }
+    return formatHeader(sb.toString());
+  }
+
+  static String getText(DocTree dt) {
+    return new SimpleDocTreeVisitor<String, Void>() {
+      @Override
+      public String visitUnknownBlockTag(UnknownBlockTagTree node, Void p) {
+        for (DocTree dt : node.getContent()) {
+          return dt.accept(this, null);
+        }
+        return "";
+      }
+
+      @Override
+      public String visitUnknownInlineTag(UnknownInlineTagTree node, Void p) {
+        for (DocTree dt : node.getContent()) {
+          return dt.accept(this, null);
+        }
+        return "";
+      }
+
+      @Override
+      public String visitText(TextTree node, Void p) {
+        return node.getBody();
+      }
+
+      @Override
+      protected String defaultAction(DocTree node, Void p) {
+        return "";
+      }
+    }.visit(dt, null);
+  }
+}
diff --git a/framework/build.gradle b/framework/build.gradle
new file mode 100644
index 0000000..33ae053
--- /dev/null
+++ b/framework/build.gradle
@@ -0,0 +1,223 @@
+plugins {
+    id 'java-library'
+}
+
+ext {
+    annotatedJdkHome = '../../jdk'
+}
+sourceSets {
+    main {
+        resources {
+            // Stub files, message.properties, etc.
+            srcDirs += ['src/main/java', "${buildDir}/generated/resources"]
+        }
+    }
+    testannotations
+}
+
+sourcesJar {
+    // The resources duplicate content from the src directory.
+    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+}
+
+def versions = [
+        autoValue       : "1.7.4",
+        lombok          : "1.18.20",
+]
+
+
+dependencies {
+    api project(':javacutil')
+    api project(':dataflow')
+    api files("${stubparserJar}")
+    // AFU is an "includedBuild" imported in checker-framework/settings.gradle, so the version number doesn't matter.
+    // https://docs.gradle.org/current/userguide/composite_builds.html#settings_defined_composite
+    api('org.checkerframework:annotation-file-utilities:*') {
+        exclude group: 'com.google.errorprone', module: 'javac'
+    }
+    api project(':checker-qual')
+
+    // External dependencies:
+    // If you add an external dependency, you must shadow its packages.
+    // See the comment in ../build.gradle in the shadowJar block.
+    implementation 'org.plumelib:plume-util:1.5.3'
+    implementation 'org.plumelib:reflection-util:1.0.3'
+    implementation 'io.github.classgraph:classgraph:4.8.105'
+
+    // TODO: it's a bug that annotatedlib:guava requires the error_prone_annotations dependency.
+    // Update the next two version numbers in tandem.  Get the Error Prone version from the "compile
+    // dependencies" section of https://mvnrepository.com/artifact/com.google.guava/guava/28.2-jre .
+    // (It isn't at https://mvnrepository.com/artifact/org.checkerframework.annotatedlib/guava/28.2-jre, which is the bug.)
+    implementation 'com.google.errorprone:error_prone_annotations:2.7.1'
+    implementation ('org.checkerframework.annotatedlib:guava:30.1.1-jre') {
+        // So long as Guava only uses annotations from checker-qual, excluding it should not cause problems.
+        exclude group: 'org.checkerframework'
+    }
+
+    testImplementation group: 'junit', name: 'junit', version: '4.13.2'
+    testImplementation project(':framework-test')
+    testImplementation sourceSets.testannotations.output
+
+    // AutoValue support in Returns Receiver Checker
+    testImplementation "com.google.auto.value:auto-value-annotations:${versions.autoValue}"
+    testImplementation "com.google.auto.value:auto-value:${versions.autoValue}"
+
+    // Lombok support in Returns Receiver Checker
+    testImplementation "org.projectlombok:lombok:${versions.lombok}"
+}
+
+task cloneTypetoolsJdk() {
+    description 'Obtain or update the annotated JDK.'
+    doLast {
+        if (file(annotatedJdkHome).exists()) {
+            exec {
+                workingDir annotatedJdkHome
+                executable 'git'
+                args = ['pull', '-q']
+                ignoreExitValue = true
+            }
+        } else {
+            println 'Cloning annotated JDK repository.'
+            exec {
+                workingDir "${annotatedJdkHome}/../"
+                executable 'git'
+                args = ['clone', '-q', '--depth', '1', 'https://github.com/typetools/jdk.git', 'jdk']
+            }
+        }
+    }
+}
+
+
+task copyAndMinimizeAnnotatedJdkFiles(dependsOn: cloneTypetoolsJdk, group: 'Build') {
+    dependsOn ':framework:compileJava'
+    def inputDir = "${annotatedJdkHome}/src"
+    def outputDir = "${buildDir}/generated/resources/annotated-jdk/"
+
+    description "Copy annotated JDK files to ${outputDir}. Removes private and package-private methods, method bodies, comments, etc. from the annotated JDK"
+
+    inputs.dir file(inputDir)
+    outputs.dir file(outputDir)
+
+    doLast {
+        FileTree tree = fileTree(dir: inputDir)
+        SortedSet<String> annotatedForFiles = new TreeSet<>();
+        tree.visit { FileVisitDetails fvd ->
+            if (!fvd.file.isDirectory() && fvd.file.name.matches(".*\\.java")
+                    && !fvd.file.path.contains("org/checkerframework")) {
+                fvd.getFile().readLines().any { line ->
+                    if (line.contains("@AnnotatedFor") || line.contains("org.checkerframework")) {
+                        annotatedForFiles.add(fvd.file.absolutePath)
+                        return true;
+                    }
+                }
+            }
+        }
+        String absolutejdkHome = file(annotatedJdkHome).absolutePath
+        int jdkDirStringSize = absolutejdkHome.size()
+        copy {
+            from(annotatedJdkHome)
+            into(outputDir)
+            for (String filename : annotatedForFiles) {
+                include filename.substring(jdkDirStringSize)
+            }
+        }
+        javaexec {
+            classpath = sourceSets.main.runtimeClasspath
+
+            main = 'org.checkerframework.framework.stub.JavaStubifier'
+            args outputDir
+        }
+    }
+}
+
+processResources.dependsOn(copyAndMinimizeAnnotatedJdkFiles)
+
+task checkDependencies(dependsOn: ':maybeCloneAndBuildDependencies') {
+    doLast {
+        if (!file(stubparserJar).exists()) {
+            throw new GradleException("${stubparserJar} does not exist. Try running './gradlew cloneAndBuildDependencies'")
+        }
+    }
+}
+
+compileJava.dependsOn(checkDependencies)
+
+task allSourcesJar(type: Jar, group: 'Build') {
+    description 'Creates a sources jar that includes sources for all Checker Framework classes in framework.jar'
+    destinationDirectory = file("${projectDir}/dist")
+    archiveFileName = "framework-source.jar"
+    from (project(':framework').sourceSets.main.java,
+            project(':dataflow').sourceSets.main.allJava,
+            project(':javacutil').sourceSets.main.allJava)
+}
+
+task allJavadocJar(type: Jar, group: 'Build') {
+    description 'Creates javadoc jar include Javadoc for all of the framework'
+    dependsOn rootProject.tasks.allJavadoc
+    destinationDirectory = file("${projectDir}/dist")
+    archiveFileName = "framework-javadoc.jar"
+    from (project(':framework').tasks.javadoc.destinationDir,
+            project(':dataflow').tasks.javadoc.destinationDir,
+            project(':javacutil').tasks.javadoc.destinationDir)
+}
+
+shadowJar {
+    description 'Creates the "fat" framework.jar in dist'
+    destinationDirectory = file("${projectDir}/dist")
+    archiveFileName = "framework.jar"
+    manifest {
+        attributes('Automatic-Module-Name': "org.checkerframework.framework")
+    }
+}
+
+createCheckTypeTask(project.name, "CompilerMessages",
+    'org.checkerframework.checker.compilermsgs.CompilerMessagesChecker')
+checkCompilerMessages {
+    options.compilerArgs += [
+            '-Apropfiles=' + sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath
+    ]
+}
+
+task loaderTests(dependsOn: 'shadowJar', group: 'Verification') {
+    description 'Run tests for the annotation class loader'
+    dependsOn(compileTestJava)
+    // TODO: this dependency on checker is a bit ugly.
+    dependsOn project(':checker-qual').tasks.jar
+    dependsOn project(':checker').tasks.assemble
+    doLast {
+        exec {
+            executable 'make'
+            args = ['-C', "tests/annotationclassloader/", "all"]
+        }
+    }
+}
+
+clean {
+    delete("tests/returnsreceiverdelomboked")
+    delete('dist')
+}
+
+
+task delombok {
+    description 'Delomboks the source code tree in tests/returnsreceiverlombok'
+
+    def srcDelomboked = 'tests/returnsreceiverdelomboked'
+    def srcJava = 'tests/returnsreceiverlombok'
+
+    inputs.files file(srcJava)
+    outputs.dir file(srcDelomboked)
+
+    // This dependency is required to ensure the checker-qual jar exists,
+    // to prevent lombok from emitting "cannot find symbol" errors for @This
+    // annotations in the test input code.
+    dependsOn project(':checker-qual').tasks.jar
+
+    doLast {
+        def collection = files(configurations.testCompileClasspath)
+        ant.taskdef(name: 'delombok', classname: 'lombok.delombok.ant.Tasks$Delombok',
+                classpath: collection.asPath)
+        ant.delombok(from: srcJava, to: srcDelomboked, classpath: collection.asPath)
+    }
+}
+
+tasks.test.dependsOn("delombok")
diff --git a/framework/jtreg/BinaryDefaultTestBinary.java b/framework/jtreg/BinaryDefaultTestBinary.java
new file mode 100644
index 0000000..35b333a
--- /dev/null
+++ b/framework/jtreg/BinaryDefaultTestBinary.java
@@ -0,0 +1,5 @@
+public class BinaryDefaultTestBinary {
+  public static BinaryDefaultTestBinary foo(BinaryDefaultTestInterface foo) {
+    return new BinaryDefaultTestBinary();
+  }
+}
diff --git a/framework/jtreg/BinaryDefaultTestInterface.java b/framework/jtreg/BinaryDefaultTestInterface.java
new file mode 100644
index 0000000..18827eb
--- /dev/null
+++ b/framework/jtreg/BinaryDefaultTestInterface.java
@@ -0,0 +1 @@
+public interface BinaryDefaultTestInterface {}
diff --git a/framework/jtreg/DOTCFGVisualizerForVarargsTest.java b/framework/jtreg/DOTCFGVisualizerForVarargsTest.java
new file mode 100644
index 0000000..845284f
--- /dev/null
+++ b/framework/jtreg/DOTCFGVisualizerForVarargsTest.java
@@ -0,0 +1,21 @@
+/*
+ * @test
+ * @summary Test the DOTCFGVisualizer doesn't crash for varargs method invocation.
+ *
+ * @compile -Aflowdotdir=. -Averbosecfg  -processor org.checkerframework.common.value.ValueChecker DOTCFGVisualizerForVarargsTest.java
+ */
+
+public class DOTCFGVisualizerForVarargsTest {
+
+  public DOTCFGVisualizerForVarargsTest(Object... objs) {}
+
+  public static void method(Object... objs) {}
+
+  public void call() {
+    new DOTCFGVisualizerForVarargsTest();
+    new DOTCFGVisualizerForVarargsTest(1, 2);
+
+    method();
+    method("", null);
+  }
+}
diff --git a/framework/jtreg/StubParserEnum/AnnotationFileParserEnum.astub b/framework/jtreg/StubParserEnum/AnnotationFileParserEnum.astub
new file mode 100644
index 0000000..8f239de
--- /dev/null
+++ b/framework/jtreg/StubParserEnum/AnnotationFileParserEnum.astub
@@ -0,0 +1,15 @@
+import org.checkerframework.common.util.report.qual.*;
+
+package java.util.concurrent;
+
+// test annotations placed on plain enum constants
+enum TimeUnit {
+    NANOSECONDS,
+    MICROSECONDS,
+
+    @ReportReadWrite MILLISECONDS,
+    @ReportReadWrite SECONDS;
+
+    @ReportCall
+    long toMicros(long d);
+}
diff --git a/framework/jtreg/StubParserEnum/AnnotationFileParserEnumTest.java b/framework/jtreg/StubParserEnum/AnnotationFileParserEnumTest.java
new file mode 100644
index 0000000..01c980e
--- /dev/null
+++ b/framework/jtreg/StubParserEnum/AnnotationFileParserEnumTest.java
@@ -0,0 +1,51 @@
+/*
+ * @test
+ * @summary Adapted test case for Issue 2147
+ * https://github.com/typetools/checker-framework/issues/2147 using framework package quals
+ *
+ * @compile/fail/ref=WithoutStub.out -XDrawDiagnostics -processor org.checkerframework.common.util.report.ReportChecker -AstubWarnIfNotFound AnnotationFileParserEnumTest.java
+ * @compile/fail/ref=WithStub.out -XDrawDiagnostics -processor org.checkerframework.common.util.report.ReportChecker -AstubWarnIfNotFound -Astubs=AnnotationFileParserEnum.astub AnnotationFileParserEnumTest.java
+ */
+
+import static java.util.concurrent.TimeUnit.*;
+
+import java.util.concurrent.TimeUnit;
+import org.checkerframework.common.util.report.qual.*;
+
+public class AnnotationFileParserEnumTest {
+
+  @SuppressWarnings("report")
+  enum MyTimeUnit {
+    NANOSECONDS,
+    MICROSECONDS,
+    @ReportReadWrite
+    MILLISECONDS,
+    @ReportReadWrite
+    SECONDS;
+
+    @ReportCall
+    long toMicros(long d) {
+      return d;
+    }
+  }
+
+  void readFromEnumInSource() {
+    MyTimeUnit u1 = MyTimeUnit.SECONDS;
+    MyTimeUnit u2 = MyTimeUnit.MILLISECONDS;
+    MyTimeUnit u3 = MyTimeUnit.MICROSECONDS;
+    MyTimeUnit u4 = MyTimeUnit.NANOSECONDS;
+    long sUS = MyTimeUnit.SECONDS.toMicros(10);
+  }
+
+  void readFromEnumInStub() {
+    TimeUnit u1 = TimeUnit.SECONDS;
+    TimeUnit u2 = MILLISECONDS;
+    TimeUnit u3 = TimeUnit.MICROSECONDS;
+    TimeUnit u4 = NANOSECONDS;
+    long sUS = TimeUnit.SECONDS.toMicros(10);
+    long sNS = SECONDS.toNanos(10);
+    long msMS = TimeUnit.MILLISECONDS.toMillis(10);
+    long msUS = TimeUnit.MILLISECONDS.toMicros(10);
+    long msNS = MILLISECONDS.toNanos(10);
+  }
+}
diff --git a/framework/jtreg/StubParserEnum/WithStub.out b/framework/jtreg/StubParserEnum/WithStub.out
new file mode 100644
index 0000000..c0df47a
--- /dev/null
+++ b/framework/jtreg/StubParserEnum/WithStub.out
@@ -0,0 +1,14 @@
+AnnotationFileParserEnumTest.java:33:31: compiler.err.proc.messager: [fieldreadwrite] Field read/write of AnnotationFileParserEnumTest.MyTimeUnit.SECONDS
+AnnotationFileParserEnumTest.java:34:31: compiler.err.proc.messager: [fieldreadwrite] Field read/write of AnnotationFileParserEnumTest.MyTimeUnit.MILLISECONDS
+AnnotationFileParserEnumTest.java:37:43: compiler.err.proc.messager: [methodcall] Method call of AnnotationFileParserEnumTest.MyTimeUnit.toMicros(long)
+AnnotationFileParserEnumTest.java:37:26: compiler.err.proc.messager: [fieldreadwrite] Field read/write of AnnotationFileParserEnumTest.MyTimeUnit.SECONDS
+AnnotationFileParserEnumTest.java:41:27: compiler.err.proc.messager: [fieldreadwrite] Field read/write of java.util.concurrent.TimeUnit.SECONDS
+AnnotationFileParserEnumTest.java:42:19: compiler.err.proc.messager: [fieldreadwrite] Field read/write of java.util.concurrent.TimeUnit.MILLISECONDS
+AnnotationFileParserEnumTest.java:45:41: compiler.err.proc.messager: [methodcall] Method call of java.util.concurrent.TimeUnit.toMicros(long)
+AnnotationFileParserEnumTest.java:45:24: compiler.err.proc.messager: [fieldreadwrite] Field read/write of java.util.concurrent.TimeUnit.SECONDS
+AnnotationFileParserEnumTest.java:46:16: compiler.err.proc.messager: [fieldreadwrite] Field read/write of java.util.concurrent.TimeUnit.SECONDS
+AnnotationFileParserEnumTest.java:47:25: compiler.err.proc.messager: [fieldreadwrite] Field read/write of java.util.concurrent.TimeUnit.MILLISECONDS
+AnnotationFileParserEnumTest.java:48:47: compiler.err.proc.messager: [methodcall] Method call of java.util.concurrent.TimeUnit.toMicros(long)
+AnnotationFileParserEnumTest.java:48:25: compiler.err.proc.messager: [fieldreadwrite] Field read/write of java.util.concurrent.TimeUnit.MILLISECONDS
+AnnotationFileParserEnumTest.java:49:17: compiler.err.proc.messager: [fieldreadwrite] Field read/write of java.util.concurrent.TimeUnit.MILLISECONDS
+13 errors
diff --git a/framework/jtreg/StubParserEnum/WithoutStub.out b/framework/jtreg/StubParserEnum/WithoutStub.out
new file mode 100644
index 0000000..7e7decd
--- /dev/null
+++ b/framework/jtreg/StubParserEnum/WithoutStub.out
@@ -0,0 +1,5 @@
+AnnotationFileParserEnumTest.java:33:31: compiler.err.proc.messager: [fieldreadwrite] Field read/write of AnnotationFileParserEnumTest.MyTimeUnit.SECONDS
+AnnotationFileParserEnumTest.java:34:31: compiler.err.proc.messager: [fieldreadwrite] Field read/write of AnnotationFileParserEnumTest.MyTimeUnit.MILLISECONDS
+AnnotationFileParserEnumTest.java:37:43: compiler.err.proc.messager: [methodcall] Method call of AnnotationFileParserEnumTest.MyTimeUnit.toMicros(long)
+AnnotationFileParserEnumTest.java:37:26: compiler.err.proc.messager: [fieldreadwrite] Field read/write of AnnotationFileParserEnumTest.MyTimeUnit.SECONDS
+4 errors
diff --git a/framework/jtreg/TEST.ROOT b/framework/jtreg/TEST.ROOT
new file mode 100644
index 0000000..e59c1a5
--- /dev/null
+++ b/framework/jtreg/TEST.ROOT
@@ -0,0 +1,5 @@
+# This file identifies the root of the test-suite hierarchy.
+# It also contains test-suite configuration information.
+
+# The list of keywords supported in the entire test suite
+keys=lubglb defaults
diff --git a/framework/jtreg/issue845/checker/NotInPackageChecker.java b/framework/jtreg/issue845/checker/NotInPackageChecker.java
new file mode 100644
index 0000000..46cad0b
--- /dev/null
+++ b/framework/jtreg/issue845/checker/NotInPackageChecker.java
@@ -0,0 +1,7 @@
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+/**
+ * Checker that is in the default package. This tests for Issue 845:
+ * https://github.com/typetools/checker-framework/issues/845
+ */
+public class NotInPackageChecker extends BaseTypeChecker {}
diff --git a/framework/jtreg/issue845/checker/qual/NotInPackageTop.java b/framework/jtreg/issue845/checker/qual/NotInPackageTop.java
new file mode 100644
index 0000000..5425499
--- /dev/null
+++ b/framework/jtreg/issue845/checker/qual/NotInPackageTop.java
@@ -0,0 +1,11 @@
+package qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface NotInPackageTop {}
diff --git a/framework/jtreg/issue845/test/Test.java b/framework/jtreg/issue845/test/Test.java
new file mode 100644
index 0000000..575c57a
--- /dev/null
+++ b/framework/jtreg/issue845/test/Test.java
@@ -0,0 +1,8 @@
+/*
+ * @test
+ * @summary Testing that a checker isn't required to be in a package. (Issue 845, https://github.com/typetools/checker-framework/issues/845)  The checker java files have to be in a separate directory otherwise $CHECKERFRAMEWORK/framework/jtreg/checker/qual directory is on the classpath before the qual directory with the compiled classes.
+ *
+ * @compile -XDrawDiagnostics -Xlint:unchecked ../checker/NotInPackageChecker.java ../checker/qual/NotInPackageTop.java
+ * @compile -XDrawDiagnostics -Xlint:unchecked -processor NotInPackageChecker Test.java -proc:only
+ */
+public class Test {}
diff --git a/framework/jtreg/variablenamedefault/Driver.java b/framework/jtreg/variablenamedefault/Driver.java
new file mode 100644
index 0000000..1a7c760
--- /dev/null
+++ b/framework/jtreg/variablenamedefault/Driver.java
@@ -0,0 +1,20 @@
+/*
+ * @test
+ * @summary Test variable name defaults.
+ *
+ * @clean lib.Test use.UseTest
+ * @compile lib/Test.java
+ * @compile -processor org.checkerframework.framework.testchecker.variablenamedefault.VariableNameDefaultChecker use/UseTest.java -XDrawDiagnostics
+ *
+ * @clean lib.Test use.UseTest
+ * @compile -g lib/Test.java
+ * @compile/fail/ref=UseTest1.out -processor org.checkerframework.framework.testchecker.variablenamedefault.VariableNameDefaultChecker use/UseTest.java -XDrawDiagnostics
+ *
+ * @clean lib.Test use.UseTest
+ * @compile -processor org.checkerframework.framework.testchecker.variablenamedefault.VariableNameDefaultChecker lib/Test.java
+ * @compile/fail/ref=UseTest2.out -processor org.checkerframework.framework.testchecker.variablenamedefault.VariableNameDefaultChecker use/UseTest.java -XDrawDiagnostics
+ *
+ * @clean lib.Test use.UseTest
+ * @compile/fail/ref=UseTest1.out -processor org.checkerframework.framework.testchecker.variablenamedefault.VariableNameDefaultChecker use/UseTest.java lib/Test.java -XDrawDiagnostics
+ */
+class Driver {}
diff --git a/framework/jtreg/variablenamedefault/UseTest1.out b/framework/jtreg/variablenamedefault/UseTest1.out
new file mode 100644
index 0000000..76d721b
--- /dev/null
+++ b/framework/jtreg/variablenamedefault/UseTest1.out
@@ -0,0 +1,4 @@
+UseTest.java:8:17: compiler.err.proc.messager: [argument] incompatible argument for parameter middle of method.
+found   : @VariableNameDefaultTop int
+required: @VariableNameDefaultMiddle int
+1 error
diff --git a/framework/jtreg/variablenamedefault/UseTest2.out b/framework/jtreg/variablenamedefault/UseTest2.out
new file mode 100644
index 0000000..2a563df
--- /dev/null
+++ b/framework/jtreg/variablenamedefault/UseTest2.out
@@ -0,0 +1,4 @@
+UseTest.java:8:17: compiler.err.proc.messager: [argument] incompatible argument for parameter arg0 of method.
+found   : @VariableNameDefaultTop int
+required: @VariableNameDefaultMiddle int
+1 error
diff --git a/framework/jtreg/variablenamedefault/lib/Test.java b/framework/jtreg/variablenamedefault/lib/Test.java
new file mode 100644
index 0000000..6814368
--- /dev/null
+++ b/framework/jtreg/variablenamedefault/lib/Test.java
@@ -0,0 +1,5 @@
+package lib;
+
+public class Test {
+  public static void method(int middle, int notmiddle) {}
+}
diff --git a/framework/jtreg/variablenamedefault/use/UseTest.java b/framework/jtreg/variablenamedefault/use/UseTest.java
new file mode 100644
index 0000000..da5ccca
--- /dev/null
+++ b/framework/jtreg/variablenamedefault/use/UseTest.java
@@ -0,0 +1,10 @@
+package use;
+
+import lib.Test;
+import org.checkerframework.framework.testchecker.variablenamedefault.quals.*;
+
+public class UseTest {
+  void testParamters(@VariableNameDefaultTop int t) {
+    Test.method(t, t);
+  }
+}
diff --git a/framework/src/main/java/com/google/errorprone/annotations/FormatMethod.java b/framework/src/main/java/com/google/errorprone/annotations/FormatMethod.java
new file mode 100644
index 0000000..b481a83
--- /dev/null
+++ b/framework/src/main/java/com/google/errorprone/annotations/FormatMethod.java
@@ -0,0 +1,16 @@
+package com.google.errorprone.annotations;
+
+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;
+
+/**
+ * Annotation for a method that takes a printf-style format string as an argument followed by
+ * arguments for that format string.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+public @interface FormatMethod {}
diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java
new file mode 100644
index 0000000..bd2e1c2
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java
@@ -0,0 +1,655 @@
+package org.checkerframework.common.accumulation;
+
+import com.github.javaparser.ParseProblemException;
+import com.github.javaparser.StaticJavaParser;
+import com.github.javaparser.ast.expr.BinaryExpr;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.UnaryExpr;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringJoiner;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.accumulation.AccumulationChecker.AliasAnalysis;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.returnsreceiver.ReturnsReceiverAnnotatedTypeFactory;
+import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker;
+import org.checkerframework.common.returnsreceiver.qual.This;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.ElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypeSystemError;
+import org.checkerframework.javacutil.UserError;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * An annotated type factory for an accumulation checker.
+ *
+ * <p>New accumulation checkers should extend this class and implement a constructor, which should
+ * take a {@link BaseTypeChecker} and call both the constructor defined in this class and {@link
+ * #postInit()}.
+ */
+public abstract class AccumulationAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** The typechecker associated with this factory. */
+  public final AccumulationChecker accumulationChecker;
+
+  /**
+   * The canonical top annotation for this accumulation checker: an instance of the accumulator
+   * annotation with no arguments.
+   */
+  public final AnnotationMirror top;
+
+  /** The canonical bottom annotation for this accumulation checker. */
+  public final AnnotationMirror bottom;
+
+  /**
+   * The annotation that accumulates things in this accumulation checker. Must be an annotation with
+   * exactly one field named "value" whose type is a String array.
+   */
+  private final Class<? extends Annotation> accumulator;
+
+  /**
+   * The predicate annotation for this accumulation analysis, or null if predicates are not
+   * supported. A predicate annotation must have a single element named "value" of type String.
+   */
+  private final @MonotonicNonNull Class<? extends Annotation> predicate;
+
+  /**
+   * Create an annotated type factory for an accumulation checker.
+   *
+   * @param checker the checker
+   * @param accumulator the accumulator type in the hierarchy. Must be an annotation with a single
+   *     argument named "value" whose type is a String array.
+   * @param bottom the bottom type in the hierarchy, which must be a subtype of {@code accumulator}.
+   *     The bottom type should be an annotation with no arguments.
+   * @param predicate the predicate annotation. Either null (if predicates are not supported), or an
+   *     annotation with a single element named "value" whose type is a String.
+   */
+  protected AccumulationAnnotatedTypeFactory(
+      BaseTypeChecker checker,
+      Class<? extends Annotation> accumulator,
+      Class<? extends Annotation> bottom,
+      @Nullable Class<? extends Annotation> predicate) {
+    super(checker);
+    if (!(checker instanceof AccumulationChecker)) {
+      throw new TypeSystemError(
+          "AccumulationAnnotatedTypeFactory cannot be used with a checker "
+              + "class that is not a subtype of AccumulationChecker. Found class: "
+              + checker.getClass());
+    }
+    this.accumulationChecker = (AccumulationChecker) checker;
+
+    this.accumulator = accumulator;
+    // Check that the requirements of the accumulator are met.
+    Method[] accDeclaredMethods = accumulator.getDeclaredMethods();
+    if (accDeclaredMethods.length != 1) {
+      rejectMalformedAccumulator("have exactly one element");
+    }
+
+    Method accValue = accDeclaredMethods[0];
+    if (accValue.getName() != "value") { // interned
+      rejectMalformedAccumulator("name its element \"value\"");
+    }
+    if (!accValue.getReturnType().isInstance(new String[0])) {
+      rejectMalformedAccumulator("have an element of type String[]");
+    }
+    if (accValue.getDefaultValue() == null || ((String[]) accValue.getDefaultValue()).length != 0) {
+      rejectMalformedAccumulator("have the empty String array {} as its default value");
+    }
+
+    this.predicate = predicate;
+    // If there is a predicate annotation, check that its requirements are met.
+    if (predicate != null) {
+      Method[] predDeclaredMethods = predicate.getDeclaredMethods();
+      if (predDeclaredMethods.length != 1) {
+        rejectMalformedPredicate("have exactly one element");
+      }
+      Method predValue = predDeclaredMethods[0];
+      if (predValue.getName() != "value") { // interned
+        rejectMalformedPredicate("name its element \"value\"");
+      }
+      if (!predValue.getReturnType().isInstance("")) {
+        rejectMalformedPredicate("have an element of type String");
+      }
+    }
+
+    this.bottom = AnnotationBuilder.fromClass(elements, bottom);
+    this.top = createAccumulatorAnnotation(Collections.emptyList());
+
+    // Every subclass must call postInit!  This does not do so.
+  }
+
+  /**
+   * Create an annotated type factory for an accumulation checker.
+   *
+   * @param checker the checker
+   * @param accumulator the accumulator type in the hierarchy. Must be an annotation with a single
+   *     argument named "value" whose type is a String array.
+   * @param bottom the bottom type in the hierarchy, which must be a subtype of {@code accumulator}.
+   *     The bottom type should be an annotation with no arguments.
+   */
+  protected AccumulationAnnotatedTypeFactory(
+      BaseTypeChecker checker,
+      Class<? extends Annotation> accumulator,
+      Class<? extends Annotation> bottom) {
+    this(checker, accumulator, bottom, null);
+  }
+
+  /**
+   * Common error message for malformed accumulator annotation.
+   *
+   * @param missing what is missing from the accumulator, suitable for use in this string to replace
+   *     $MISSING$: "The accumulator annotation Foo must $MISSING$."
+   */
+  private void rejectMalformedAccumulator(String missing) {
+    rejectMalformedAnno("accumulator", accumulator, missing);
+  }
+
+  /**
+   * Common error message for malformed predicate annotation.
+   *
+   * @param missing what is missing from the predicate, suitable for use in this string to replace
+   *     $MISSING$: "The predicate annotation Foo must $MISSING$."
+   */
+  private void rejectMalformedPredicate(String missing) {
+    rejectMalformedAnno("predicate", predicate, missing);
+  }
+
+  /**
+   * Common error message implementation. Call rejectMalformedAccumulator or
+   * rejectMalformedPredicate as appropriate, rather than this method directly.
+   *
+   * @param annoTypeName the display name for the type of malformed annotation, such as
+   *     "accumulator"
+   * @param anno the malformed annotation
+   * @param missing what is missing from the annotation, suitable for use in this string to replace
+   *     $MISSING$: "The accumulator annotation Foo must $MISSING$."
+   */
+  private void rejectMalformedAnno(
+      String annoTypeName, Class<? extends Annotation> anno, String missing) {
+    throw new BugInCF("The " + annoTypeName + " annotation " + anno + " must " + missing + ".");
+  }
+
+  /**
+   * Creates a new instance of the accumulator annotation that contains the elements of {@code
+   * values}.
+   *
+   * @param values the arguments to the annotation. The values can contain duplicates and can be in
+   *     any order.
+   * @return an annotation mirror representing the accumulator annotation with {@code values}'s
+   *     arguments; this is top if {@code values} is empty
+   */
+  public AnnotationMirror createAccumulatorAnnotation(List<String> values) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, accumulator);
+    builder.setValue("value", CollectionsPlume.withoutDuplicates(values));
+    return builder.build();
+  }
+
+  /**
+   * Creates a new instance of the accumulator annotation that contains exactly one value.
+   *
+   * @param value the argument to the annotation
+   * @return an annotation mirror representing the accumulator annotation with {@code value} as its
+   *     argument
+   */
+  public AnnotationMirror createAccumulatorAnnotation(String value) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, accumulator);
+    builder.setValue("value", Collections.singletonList(value));
+    return builder.build();
+  }
+
+  /**
+   * Returns true if the return type of the given method invocation tree has an @This annotation
+   * from the Returns Receiver Checker.
+   *
+   * @param tree a method invocation tree
+   * @return true if the method being invoked returns its receiver
+   */
+  public boolean returnsThis(final MethodInvocationTree tree) {
+    if (!accumulationChecker.isEnabled(AliasAnalysis.RETURNS_RECEIVER)) {
+      return false;
+    }
+    // Must call `getTypeFactoryOfSubchecker` each time, not store and reuse.
+    ReturnsReceiverAnnotatedTypeFactory rrATF =
+        getTypeFactoryOfSubchecker(ReturnsReceiverChecker.class);
+    ExecutableElement methodEle = TreeUtils.elementFromUse(tree);
+    AnnotatedExecutableType methodAtm = rrATF.getAnnotatedType(methodEle);
+    AnnotatedTypeMirror rrType = methodAtm.getReturnType();
+    return rrType != null && rrType.hasAnnotation(This.class);
+  }
+
+  /**
+   * Is the given annotation an accumulator annotation? Returns false if the argument is {@link
+   * #bottom}.
+   *
+   * @param anm an annotation mirror
+   * @return true if the annotation mirror is an instance of this factory's accumulator annotation
+   */
+  public boolean isAccumulatorAnnotation(AnnotationMirror anm) {
+    return areSameByClass(anm, accumulator);
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(super.createTreeAnnotator(), new AccumulationTreeAnnotator(this));
+  }
+
+  /**
+   * This tree annotator implements the following rule(s):
+   *
+   * <dl>
+   *   <dt>RRA
+   *   <dd>If a method returns its receiver, and the receiver has an accumulation type, then the
+   *       default type of the method's return value is the type of the receiver.
+   * </dl>
+   */
+  protected class AccumulationTreeAnnotator extends TreeAnnotator {
+
+    /**
+     * Creates an instance of this tree annotator for the given type factory.
+     *
+     * @param factory the type factory
+     */
+    public AccumulationTreeAnnotator(AccumulationAnnotatedTypeFactory factory) {
+      super(factory);
+    }
+
+    /**
+     * Implements rule RRA.
+     *
+     * <p>This implementation propagates types from the receiver to the return value, without
+     * change. Subclasses may override this method to also add additional properties, as
+     * appropriate.
+     *
+     * @param tree a method invocation tree
+     * @param type the type of {@code tree} (i.e. the return type of the invoked method). Is
+     *     (possibly) side-effected by this method.
+     * @return nothing, works by side-effect on {@code type}
+     */
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
+      if (returnsThis(tree)) {
+        // There is a @This annotation on the return type of the invoked method.
+        ExpressionTree receiverTree = TreeUtils.getReceiverTree(tree.getMethodSelect());
+        AnnotatedTypeMirror receiverType =
+            receiverTree == null ? null : getAnnotatedType(receiverTree);
+        // The current type of the receiver, or top if none exists.
+        AnnotationMirror receiverAnno =
+            receiverType == null ? top : receiverType.getAnnotationInHierarchy(top);
+
+        AnnotationMirror returnAnno = type.getAnnotationInHierarchy(top);
+        type.replaceAnnotation(qualHierarchy.greatestLowerBound(returnAnno, receiverAnno));
+      }
+      return super.visitMethodInvocation(tree, type);
+    }
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new AccumulationQualifierHierarchy(this.getSupportedTypeQualifiers(), this.elements);
+  }
+
+  /**
+   * Returns all the values that anno has accumulated.
+   *
+   * @param anno an accumulator annotation; must not be bottom
+   * @return the list of values the annotation has accumulated; it is a new list, so it is safe for
+   *     clients to side-effect
+   */
+  public List<String> getAccumulatedValues(AnnotationMirror anno) {
+    if (!isAccumulatorAnnotation(anno)) {
+      throw new BugInCF(anno + " isn't an accumulator annotation");
+    }
+    List<String> values =
+        AnnotationUtils.getElementValueArrayOrNull(anno, "value", String.class, false);
+    if (values == null) {
+      return Collections.emptyList();
+    } else {
+      return values;
+    }
+  }
+
+  /**
+   * All accumulation analyses share a similar type hierarchy. This class implements the subtyping,
+   * LUB, and GLB for that hierarchy. The lattice looks like:
+   *
+   * <pre>
+   *       acc()
+   *      /   \
+   * acc(x)   acc(y) ...
+   *      \   /
+   *     acc(x,y) ...
+   *        |
+   *      bottom
+   * </pre>
+   *
+   * Predicate subtyping is defined as follows:
+   *
+   * <ul>
+   *   <li>An accumulator is a subtype of a predicate if substitution from the accumulator to the
+   *       predicate makes the predicate true. For example, {@code Acc(A)} is a subtype of {@code
+   *       AccPred("A || B")}, because when A is replaced with {@code true} and B is replaced with
+   *       {@code false}, the resulting boolean formula evaluates to true.
+   *   <li>A predicate P is a subtype of an accumulator iff after converting the accumulator into a
+   *       predicate representing the conjunction of its elements, P is a subtype of that predicate
+   *       according to the rule for subtyping between two predicates defined below.
+   *   <li>A predicate P is a subtype of another predicate Q iff P and Q are equal. An extension
+   *       point ({@link #isPredicateSubtype(String, String)}) is provided to allow more complex
+   *       subtyping behavior between predicates. (The "correct" subtyping rule is that P is a
+   *       subtype of Q iff P implies Q. That rule would require an SMT solver in the general case,
+   *       which is undesirable because it would require an external dependency. A user can override
+   *       {@link #isPredicateSubtype(String, String)} if they require more precise subtyping; the
+   *       check described here is overly conservative (and therefore sound), but not very precise.)
+   * </ul>
+   */
+  protected class AccumulationQualifierHierarchy extends ElementQualifierHierarchy {
+
+    /**
+     * Creates a ElementQualifierHierarchy from the given classes.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+     * @param elements element utils
+     */
+    protected AccumulationQualifierHierarchy(
+        Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+    }
+
+    /**
+     * GLB in this type system is set union of the arguments of the two annotations, unless one of
+     * them is bottom, in which case the result is also bottom.
+     */
+    @Override
+    public AnnotationMirror greatestLowerBound(
+        final AnnotationMirror a1, final AnnotationMirror a2) {
+      if (AnnotationUtils.areSame(a1, bottom) || AnnotationUtils.areSame(a2, bottom)) {
+        return bottom;
+      }
+
+      if (isPolymorphicQualifier(a1) && isPolymorphicQualifier(a2)) {
+        return a1;
+      } else if (isPolymorphicQualifier(a1) || isPolymorphicQualifier(a2)) {
+        return bottom;
+      }
+
+      // If either is a predicate, then both should be converted to predicates and and-ed.
+      if (isPredicate(a1) || isPredicate(a2)) {
+        String a1Pred = convertToPredicate(a1);
+        String a2Pred = convertToPredicate(a2);
+        // check for top
+        if (a1Pred.isEmpty()) {
+          return a2;
+        } else if (a2Pred.isEmpty()) {
+          return a1;
+        } else {
+          return createPredicateAnnotation("(" + a1Pred + ") && (" + a2Pred + ")");
+        }
+      }
+
+      List<String> a1Val = getAccumulatedValues(a1);
+      List<String> a2Val = getAccumulatedValues(a2);
+      // Avoid creating new annotation objects in the common case.
+      if (a1Val.containsAll(a2Val)) {
+        return a1;
+      }
+      if (a2Val.containsAll(a1Val)) {
+        return a2;
+      }
+      a1Val.addAll(a2Val); // union
+      return createAccumulatorAnnotation(a1Val);
+    }
+
+    /**
+     * LUB in this type system is set intersection of the arguments of the two annotations, unless
+     * one of them is bottom, in which case the result is the other annotation.
+     */
+    @Override
+    public AnnotationMirror leastUpperBound(final AnnotationMirror a1, final AnnotationMirror a2) {
+      if (AnnotationUtils.areSame(a1, bottom)) {
+        return a2;
+      } else if (AnnotationUtils.areSame(a2, bottom)) {
+        return a1;
+      }
+
+      if (isPolymorphicQualifier(a1) && isPolymorphicQualifier(a2)) {
+        return a1;
+      } else if (isPolymorphicQualifier(a1) || isPolymorphicQualifier(a2)) {
+        return top;
+      }
+
+      // If either is a predicate, then both should be converted to predicates and or-ed.
+      if (isPredicate(a1) || isPredicate(a2)) {
+        String a1Pred = convertToPredicate(a1);
+        String a2Pred = convertToPredicate(a2);
+        // check for top
+        if (a1Pred.isEmpty()) {
+          return a1;
+        } else if (a2Pred.isEmpty()) {
+          return a2;
+        } else {
+          return createPredicateAnnotation("(" + a1Pred + ") || (" + a2Pred + ")");
+        }
+      }
+
+      List<String> a1Val = getAccumulatedValues(a1);
+      List<String> a2Val = getAccumulatedValues(a2);
+      // Avoid creating new annotation objects in the common case.
+      if (a1Val.containsAll(a2Val)) {
+        return a2;
+      }
+      if (a2Val.containsAll(a1Val)) {
+        return a1;
+      }
+      a1Val.retainAll(a2Val); // intersection
+      return createAccumulatorAnnotation(a1Val);
+    }
+
+    /** isSubtype in this type system is subset. */
+    @Override
+    public boolean isSubtype(final AnnotationMirror subAnno, final AnnotationMirror superAnno) {
+      if (AnnotationUtils.areSame(subAnno, bottom)) {
+        return true;
+      } else if (AnnotationUtils.areSame(superAnno, bottom)) {
+        return false;
+      }
+
+      if (isPolymorphicQualifier(subAnno)) {
+        if (isPolymorphicQualifier(superAnno)) {
+          return true;
+        } else {
+          // Use this slightly more expensive conversion here because this is a rare code path and
+          // it's simpler to read than checking for both predicate and non-predicate forms of top.
+          return "".equals(convertToPredicate(superAnno));
+        }
+      } else if (isPolymorphicQualifier(superAnno)) {
+        // Polymorphic annotations are only a supertype of other polymorphic annotations and
+        // the bottom type, both of which have already been checked above.
+        return false;
+      }
+
+      if (isPredicate(subAnno)) {
+        return isPredicateSubtype(convertToPredicate(subAnno), convertToPredicate(superAnno));
+      } else if (isPredicate(superAnno)) {
+        return evaluatePredicate(subAnno, convertToPredicate(superAnno));
+      }
+
+      List<String> subVal = getAccumulatedValues(subAnno);
+      List<String> superVal = getAccumulatedValues(superAnno);
+      return subVal.containsAll(superVal);
+    }
+  }
+
+  /**
+   * Extension point for subtyping behavior between predicates. This implementation conservatively
+   * returns true only if the predicates are equal, or if the prospective supertype (q) is
+   * equivalent to top (that is, the empty string).
+   *
+   * @param p a predicate
+   * @param q another predicate
+   * @return true if p is a subtype of q
+   */
+  protected boolean isPredicateSubtype(String p, String q) {
+    return "".equals(q) || p.equals(q);
+  }
+
+  /**
+   * Evaluates whether the accumulator annotation {@code subAnno} makes the predicate {@code pred}
+   * true.
+   *
+   * @param subAnno an accumulator annotation
+   * @param pred a predicate
+   * @return whether the accumulator annotation satisfies the predicate
+   */
+  protected boolean evaluatePredicate(AnnotationMirror subAnno, String pred) {
+    if (!isAccumulatorAnnotation(subAnno)) {
+      throw new BugInCF(
+          "tried to evaluate a predicate using an annotation that wasn't an accumulator: "
+              + subAnno);
+    }
+    List<String> trueVariables = getAccumulatedValues(subAnno);
+    return evaluatePredicate(trueVariables, pred);
+  }
+
+  /**
+   * Checks that the given annotation either:
+   *
+   * <ul>
+   *   <li>does not contain a predicate, or
+   *   <li>contains a parse-able predicate
+   * </ul>
+   *
+   * Used by the visitor to throw "predicate" errors; thus must be package-private.
+   *
+   * @param anm any annotation supported by this checker
+   * @return null if there is nothing wrong with the annotation, or an error message indicating the
+   *     problem if it has an invalid predicate
+   */
+  /* package-private */
+  @Nullable String isValidPredicate(AnnotationMirror anm) {
+    String pred = convertToPredicate(anm);
+    try {
+      evaluatePredicate(Collections.emptyList(), pred);
+    } catch (UserError ue) {
+      return ue.getLocalizedMessage();
+    }
+    return null;
+  }
+
+  /**
+   * Evaluates whether treating the variables in {@code trueVariables} as {@code true} literals (and
+   * all other names as {@code false} literals) makes the predicate {@code pred} evaluate to true.
+   *
+   * @param trueVariables a list of names that should be replaced with {@code true}
+   * @param pred a predicate
+   * @return whether the true variables satisfy the predicate
+   */
+  protected boolean evaluatePredicate(List<String> trueVariables, String pred) {
+    Expression expression;
+    try {
+      expression = StaticJavaParser.parseExpression(pred);
+    } catch (ParseProblemException p) {
+      throw new UserError("unparsable predicate: " + pred + ". Parse exception: " + p);
+    }
+    return evaluateBooleanExpression(expression, trueVariables);
+  }
+
+  /**
+   * Evaluates a boolean expression, in JavaParser format, that contains only and, or, parentheses,
+   * logical complement, and boolean literal nodes.
+   *
+   * @param expression a JavaParser boolean expression
+   * @param trueVariables the names of the variables that should be considered "true"; all other
+   *     literals are considered "false"
+   * @return the result of evaluating the expression
+   */
+  private boolean evaluateBooleanExpression(Expression expression, List<String> trueVariables) {
+    if (expression.isNameExpr()) {
+      return trueVariables.contains(expression.asNameExpr().getNameAsString());
+    } else if (expression.isBinaryExpr()) {
+      if (expression.asBinaryExpr().getOperator() == BinaryExpr.Operator.OR) {
+        return evaluateBooleanExpression(expression.asBinaryExpr().getLeft(), trueVariables)
+            || evaluateBooleanExpression(expression.asBinaryExpr().getRight(), trueVariables);
+      } else if (expression.asBinaryExpr().getOperator() == BinaryExpr.Operator.AND) {
+        return evaluateBooleanExpression(expression.asBinaryExpr().getLeft(), trueVariables)
+            && evaluateBooleanExpression(expression.asBinaryExpr().getRight(), trueVariables);
+      }
+    } else if (expression.isEnclosedExpr()) {
+      return evaluateBooleanExpression(expression.asEnclosedExpr().getInner(), trueVariables);
+    } else if (expression.isUnaryExpr()) {
+      if (expression.asUnaryExpr().getOperator() == UnaryExpr.Operator.LOGICAL_COMPLEMENT) {
+        return !evaluateBooleanExpression(expression.asUnaryExpr().getExpression(), trueVariables);
+      }
+    }
+    // This could be a BugInCF if there is a bug in the code above.
+    throw new UserError(
+        "encountered an unexpected type of expression in a "
+            + "predicate expression: "
+            + expression
+            + " was of type "
+            + expression.getClass());
+  }
+
+  /**
+   * Creates a new predicate annotation from the given string.
+   *
+   * @param p a valid predicate
+   * @return an annotation representing that predicate
+   */
+  protected AnnotationMirror createPredicateAnnotation(String p) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, predicate);
+    builder.setValue("value", p);
+    return builder.build();
+  }
+
+  /**
+   * Converts the given annotation mirror to a predicate.
+   *
+   * @param anno an annotation
+   * @return the predicate, as a String, that is equivalent to that annotation. May return the empty
+   *     string.
+   */
+  protected String convertToPredicate(AnnotationMirror anno) {
+    if (AnnotationUtils.areSame(anno, bottom)) {
+      return "false";
+    } else if (isPredicate(anno)) {
+      String result = AnnotationUtils.getElementValueOrNull(anno, "value", String.class, false);
+      return result == null ? "" : result;
+    } else if (isAccumulatorAnnotation(anno)) {
+      List<String> values = getAccumulatedValues(anno);
+      StringJoiner sj = new StringJoiner(" && ");
+      for (String value : values) {
+        sj.add(value);
+      }
+      return sj.toString();
+    } else {
+      throw new BugInCF("annotation is not bottom, a predicate, or an accumulator: " + anno);
+    }
+  }
+
+  /**
+   * Returns true if anno is a predicate annotation.
+   *
+   * @param anno an annotation
+   * @return true if anno is a predicate annotation
+   */
+  protected boolean isPredicate(AnnotationMirror anno) {
+    return predicate != null && areSameByClass(anno, predicate);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java
new file mode 100644
index 0000000..dfd77b8
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java
@@ -0,0 +1,77 @@
+package org.checkerframework.common.accumulation;
+
+import java.util.EnumSet;
+import java.util.LinkedHashSet;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker;
+
+// TODO: This Javadoc comment should reference the Checker Framework manual, once the Accumulation
+// Checker chapter is uncommented in the manual's LaTeX source.
+/**
+ * An accumulation checker is one that accumulates some property: method calls, map keys, etc.
+ *
+ * <p>This class provides a basic accumulation analysis that can be extended to implement an
+ * accumulation type system. This accumulation analysis represents all facts as Strings.
+ *
+ * <p>This class supports modular alias analyses. To choose the alias analyses that your
+ * accumulation checker uses, override the {@link #createAliasAnalyses()} method. By default, the
+ * only alias analysis used is Returns Receiver.
+ *
+ * <p>The primary extension point is the constructor of {@link AccumulationAnnotatedTypeFactory},
+ * which every subclass should override to provide custom annotations.
+ */
+public abstract class AccumulationChecker extends BaseTypeChecker {
+
+  /** Set of alias analyses that are enabled in this particular accumulation checker. */
+  private EnumSet<AliasAnalysis> aliasAnalyses;
+
+  /** Constructs a new AccumulationChecker. */
+  protected AccumulationChecker() {
+    super();
+    this.aliasAnalyses = createAliasAnalyses();
+  }
+
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    LinkedHashSet<Class<? extends BaseTypeChecker>> checkers =
+        super.getImmediateSubcheckerClasses();
+    if (isEnabled(AliasAnalysis.RETURNS_RECEIVER)) {
+      checkers.add(ReturnsReceiverChecker.class);
+    }
+    return checkers;
+  }
+
+  /**
+   * The alias analyses that an accumulation checker can support. To add support for a new alias
+   * analysis, add a new item to this enum and then implement any functionality of the checker
+   * behind a call to {@link #isEnabled(AliasAnalysis)}.
+   */
+  public enum AliasAnalysis {
+    /**
+     * An alias analysis that detects methods that always return their own receiver (i.e. whose
+     * return value and receiver are aliases).
+     */
+    RETURNS_RECEIVER
+  }
+
+  /**
+   * Get the alias analyses that this checker should employ.
+   *
+   * @return the alias analyses
+   */
+  protected EnumSet<AliasAnalysis> createAliasAnalyses(
+      @UnderInitialization AccumulationChecker this) {
+    return EnumSet.of(AliasAnalysis.RETURNS_RECEIVER);
+  }
+
+  /**
+   * Check whether the given alias analysis is enabled by this particular accumulation checker.
+   *
+   * @param aliasAnalysis the analysis to check
+   * @return true iff the analysis is enabled
+   */
+  public boolean isEnabled(AliasAnalysis aliasAnalysis) {
+    return aliasAnalyses.contains(aliasAnalysis);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java
new file mode 100644
index 0000000..6cddb58
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java
@@ -0,0 +1,107 @@
+package org.checkerframework.common.accumulation;
+
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+
+/**
+ * The default transfer function for an accumulation checker.
+ *
+ * <p>Subclasses should call the {@link #accumulate(Node, TransferResult, String...)} accumulate}
+ * method to add a string to the estimate at a particular program point.
+ */
+public class AccumulationTransfer extends CFTransfer {
+
+  /** The type factory. */
+  protected final AccumulationAnnotatedTypeFactory atypeFactory;
+
+  /**
+   * Build a new AccumulationTransfer for the given analysis.
+   *
+   * @param analysis the analysis
+   */
+  public AccumulationTransfer(CFAnalysis analysis) {
+    super(analysis);
+    atypeFactory = (AccumulationAnnotatedTypeFactory) analysis.getTypeFactory();
+  }
+
+  /**
+   * Updates the estimate of how many things {@code node} has accumulated.
+   *
+   * <p>If the node is an invocation of a method that returns its receiver, then its receiver's type
+   * will also be updated. In a chain of method calls, this process will continue backward as long
+   * as each receiver is itself a receiver-returning method invocation.
+   *
+   * <p>For example, suppose {@code node} is the expression {@code a.b().c()}, the new value (added
+   * by the accumulation analysis because of the {@code .c()} call) is "foo", and b and c return
+   * their receiver. This method will directly update the estimate of {@code a.b().c()} to include
+   * "foo". In addition, the estimates for the expressions {@code a.b()} and {@code a} would have
+   * their estimates updated to include "foo", because c and b (respectively) return their
+   * receivers. Note that due to what kind of values can be held in the store, this information is
+   * lost outside the method chain. That is, the returns-receiver propagated information is lost
+   * outside the expression in which the returns-receiver method invocations are nested.
+   *
+   * <p>As a concrete example, consider the Called Methods accumulation checker: if {@code build}
+   * requires a, b, and c to be called, then {@code foo.a().b().c().build();} will typecheck (they
+   * are in one fluent method chain), but {@code foo.a().b().c(); foo.build();} will not -- the
+   * store does not keep the information that a, b, and c have been called outside the chain. {@code
+   * foo}'s type will be {@code CalledMethods("a")}, because only {@code a()} was called directly on
+   * {@code foo}. For such code to typecheck, the Called Methods accumulation checker uses an
+   * additional rule: the return type of a receiver-returning method {@code rr()} is {@code
+   * CalledMethods("rr")}. This rule is implemented directly in the {@link
+   * org.checkerframework.framework.type.treeannotator.TreeAnnotator} subclass defined in the Called
+   * Methods type factory.
+   *
+   * @param node the node whose estimate should be expanded
+   * @param result the transfer result containing the store to be modified
+   * @param values the new accumulation values
+   */
+  public void accumulate(Node node, TransferResult<CFValue, CFStore> result, String... values) {
+    List<String> valuesAsList = Arrays.asList(values);
+    // If dataflow has already recorded information about the target, fetch it and integrate
+    // it into the list of values in the new annotation.
+    JavaExpression target = JavaExpression.fromNode(node);
+    if (CFAbstractStore.canInsertJavaExpression(target)) {
+      CFValue flowValue = result.getRegularStore().getValue(target);
+      if (flowValue != null) {
+        Set<AnnotationMirror> flowAnnos = flowValue.getAnnotations();
+        assert flowAnnos.size() <= 1;
+        for (AnnotationMirror anno : flowAnnos) {
+          if (atypeFactory.isAccumulatorAnnotation(anno)) {
+            List<String> oldFlowValues = atypeFactory.getAccumulatedValues(anno);
+            if (!oldFlowValues.isEmpty()) {
+              // valuesAsList cannot have its length changed -- it is backed by an
+              // array -- but if oldFlowValues is not empty, it is a new, modifiable list.
+              oldFlowValues.addAll(valuesAsList);
+              valuesAsList = oldFlowValues;
+            }
+          }
+        }
+      }
+    }
+
+    AnnotationMirror newAnno = atypeFactory.createAccumulatorAnnotation(valuesAsList);
+    insertIntoStores(result, target, newAnno);
+
+    Tree tree = node.getTree();
+    if (tree != null && tree.getKind() == Kind.METHOD_INVOCATION) {
+      Node receiver = ((MethodInvocationNode) node).getTarget().getReceiver();
+      if (receiver != null && atypeFactory.returnsThis((MethodInvocationTree) tree)) {
+        accumulate(receiver, result, values);
+      }
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java
new file mode 100644
index 0000000..6b880fa
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java
@@ -0,0 +1,36 @@
+package org.checkerframework.common.accumulation;
+
+import com.sun.source.tree.AnnotationTree;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * The visitor for an accumulation checker. Issues predicate errors if the user writes an invalid
+ * predicate.
+ */
+public class AccumulationVisitor extends BaseTypeVisitor<AccumulationAnnotatedTypeFactory> {
+
+  /**
+   * Constructor matching super.
+   *
+   * @param checker the checker
+   */
+  public AccumulationVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  /** Checks each predicate annotation to make sure the predicate is well-formed. */
+  @Override
+  public Void visitAnnotation(final AnnotationTree node, final Void p) {
+    AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(node);
+    if (atypeFactory.isPredicate(anno)) {
+      String errorMessage = atypeFactory.isValidPredicate(anno);
+      if (errorMessage != null) {
+        checker.reportError(node, "predicate", errorMessage);
+      }
+    }
+    return super.visitAnnotation(node, p);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/messages.properties b/framework/src/main/java/org/checkerframework/common/accumulation/messages.properties
new file mode 100644
index 0000000..bd91405
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/accumulation/messages.properties
@@ -0,0 +1 @@
+predicate=Unparsable predicate. Predicates must be produced by this grammar: S --> method name | (S) | S && S | S || S. The message from the evaluator was: %s
diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingAnnotatedTypeFactory.java
new file mode 100644
index 0000000..0b84a0a
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingAnnotatedTypeFactory.java
@@ -0,0 +1,127 @@
+package org.checkerframework.common.aliasing;
+
+import com.sun.source.tree.NewArrayTree;
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.common.aliasing.qual.LeakedToResult;
+import org.checkerframework.common.aliasing.qual.MaybeAliased;
+import org.checkerframework.common.aliasing.qual.MaybeLeaked;
+import org.checkerframework.common.aliasing.qual.NonLeaked;
+import org.checkerframework.common.aliasing.qual.Unique;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.NoElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+
+/** Annotated type factory for the Aliasing Checker. */
+public class AliasingAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** Aliasing annotations. */
+  /** The @{@link MaybeAliased} annotation. */
+  protected final AnnotationMirror MAYBE_ALIASED =
+      AnnotationBuilder.fromClass(elements, MaybeAliased.class);
+  /** The @{@link NonLeaked} annotation. */
+  protected final AnnotationMirror NON_LEAKED =
+      AnnotationBuilder.fromClass(elements, NonLeaked.class);
+  /** The @{@link Unique} annotation. */
+  protected final AnnotationMirror UNIQUE = AnnotationBuilder.fromClass(elements, Unique.class);
+  /** The @{@link MaybeLeaked} annotation. */
+  protected final AnnotationMirror MAYBE_LEAKED =
+      AnnotationBuilder.fromClass(elements, MaybeLeaked.class);
+
+  /** Create the type factory. */
+  public AliasingAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    if (this.getClass() == AliasingAnnotatedTypeFactory.class) {
+      this.postInit();
+    }
+  }
+
+  // @NonLeaked and @LeakedToResult are type qualifiers because of a checker framework limitation
+  // (Issue 383). Once the stub parser gets updated to read non-type-qualifiers annotations on stub
+  // files, this annotation won't be a type qualifier anymore.
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return getBundledTypeQualifiers(MaybeLeaked.class);
+  }
+
+  @Override
+  public CFTransfer createFlowTransferFunction(
+      CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
+    CFTransfer ret = new AliasingTransfer(analysis);
+    return ret;
+  }
+
+  protected class AliasingTreeAnnotator extends TreeAnnotator {
+
+    public AliasingTreeAnnotator(AliasingAnnotatedTypeFactory atypeFactory) {
+      super(atypeFactory);
+    }
+
+    @Override
+    public Void visitNewArray(NewArrayTree node, AnnotatedTypeMirror type) {
+      type.replaceAnnotation(UNIQUE);
+      return super.visitNewArray(node, type);
+    }
+  }
+
+  @Override
+  protected ListTreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(new AliasingTreeAnnotator(this), super.createTreeAnnotator());
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new AliasingQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
+  }
+
+  /** AliasingQualifierHierarchy. */
+  protected class AliasingQualifierHierarchy extends NoElementQualifierHierarchy {
+
+    /**
+     * Create AliasingQualifierHierarchy.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers
+     * @param elements element utils
+     */
+    protected AliasingQualifierHierarchy(
+        Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+    }
+
+    /**
+     * Returns true is {@code anno} is annotation in the Leaked hierarchy.
+     *
+     * @param anno an annotation
+     * @return true is {@code anno} is annotation in the Leaked hierarchy
+     */
+    private boolean isLeakedQualifier(AnnotationMirror anno) {
+      return areSameByClass(anno, MaybeLeaked.class)
+          || areSameByClass(anno, NonLeaked.class)
+          || areSameByClass(anno, LeakedToResult.class);
+    }
+
+    @Override
+    public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+      if (isLeakedQualifier(superAnno) && isLeakedQualifier(subAnno)) {
+        // @LeakedToResult and @NonLeaked were supposed to be non-type-qualifiers annotations.
+        // Currently the stub parser does not support non-type-qualifier annotations on receiver
+        // parameters (Issue 383), therefore these annotations are implemented as type qualifiers
+        // but the warnings related to the hierarchy are ignored.
+        return true;
+      }
+      return super.isSubtype(subAnno, superAnno);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingChecker.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingChecker.java
new file mode 100644
index 0000000..81d11d3
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingChecker.java
@@ -0,0 +1,12 @@
+package org.checkerframework.common.aliasing;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.StubFiles;
+
+/**
+ * Aliasing type system -- used to identify expressions that definitely have no aliases.
+ *
+ * @checker_framework.manual #aliasing-checker Aliasing Checker
+ */
+@StubFiles({"android.astub"})
+public class AliasingChecker extends BaseTypeChecker {}
diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingTransfer.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingTransfer.java
new file mode 100644
index 0000000..c62512d
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingTransfer.java
@@ -0,0 +1,167 @@
+package org.checkerframework.common.aliasing;
+
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.List;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.common.aliasing.qual.LeakedToResult;
+import org.checkerframework.common.aliasing.qual.NonLeaked;
+import org.checkerframework.common.aliasing.qual.Unique;
+import org.checkerframework.dataflow.analysis.RegularTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Type refinement is treated in the usual way, except that at (pseudo-)assignments the RHS may lose
+ * its type refinement, before the LHS is type-refined.
+ *
+ * <p>The RHS always loses its type refinement (it is widened to {@literal @}MaybeAliased, and its
+ * declared type must have been {@literal @}MaybeAliased) except in the following cases:
+ *
+ * <ol>
+ *   <li>The RHS is a fresh expression.
+ *   <li>The LHS is a {@literal @}NonLeaked formal parameter and the RHS is an argument in a method
+ *       call or constructor invocation.
+ *   <li>The LHS is a {@literal @}LeakedToResult formal parameter, the RHS is an argument in a
+ *       method call or constructor invocation, and the method's return value is discarded.
+ * </ol>
+ */
+public class AliasingTransfer extends CFTransfer {
+
+  private AnnotatedTypeFactory factory;
+
+  public AliasingTransfer(CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
+    super(analysis);
+    factory = analysis.getTypeFactory();
+  }
+
+  /**
+   * Case 1: For every assignment, the LHS is refined if the RHS has type {@literal @}Unique and is
+   * a method invocation or a new class instance.
+   */
+  @Override
+  public TransferResult<CFValue, CFStore> visitAssignment(
+      AssignmentNode n, TransferInput<CFValue, CFStore> in) {
+    Node rhs = n.getExpression();
+    Tree treeRhs = rhs.getTree();
+    AnnotatedTypeMirror rhsType = factory.getAnnotatedType(treeRhs);
+
+    if (rhsType.hasAnnotation(Unique.class)
+        && (rhs instanceof MethodInvocationNode || rhs instanceof ObjectCreationNode)) {
+      return super.visitAssignment(n, in); // Do normal refinement.
+    }
+    // Widen the type of the rhs if the RHS's declared type wasn't @Unique.
+    JavaExpression rhsExpr = JavaExpression.fromNode(rhs);
+    in.getRegularStore().clearValue(rhsExpr);
+    return new RegularTransferResult<>(null, in.getRegularStore());
+  }
+
+  /**
+   * Handling pseudo-assignments. Called by {@code CFAbstractTransfer.visitMethodInvocation()}.
+   *
+   * <p>Case 2: Given a method call, traverses all formal parameters of the method declaration, and
+   * if it doesn't have the {@literal @}NonLeaked or {@literal @}LeakedToResult annotations, we
+   * remove the node of the respective argument in the method call from the store. If parameter has
+   * {@literal @}LeakedToResult, {@code visitMethodInvocation()} handles it.
+   */
+  @Override
+  protected void processPostconditions(
+      MethodInvocationNode n, CFStore store, ExecutableElement methodElement, Tree tree) {
+    super.processPostconditions(n, store, methodElement, tree);
+    if (TreeUtils.isEnumSuper(n.getTree())) {
+      // Skipping the init() method for enums.
+      return;
+    }
+    List<Node> args = n.getArguments();
+    List<? extends VariableElement> params = methodElement.getParameters();
+    assert (args.size() == params.size())
+        : "Number of arguments in "
+            + "the method call "
+            + n
+            + " is different from the"
+            + " number of parameters for the method declaration: "
+            + methodElement.getSimpleName();
+
+    AnnotatedExecutableType annotatedType = factory.getAnnotatedType(methodElement);
+    List<AnnotatedTypeMirror> paramTypes = annotatedType.getParameterTypes();
+    for (int i = 0; i < args.size(); i++) {
+      Node arg = args.get(i);
+      AnnotatedTypeMirror paramType = paramTypes.get(i);
+      if (!paramType.hasAnnotation(NonLeaked.class)
+          && !paramType.hasAnnotation(LeakedToResult.class)) {
+        store.clearValue(JavaExpression.fromNode(arg));
+      }
+    }
+
+    // Now, doing the same as above for the receiver parameter
+    Node receiver = n.getTarget().getReceiver();
+    AnnotatedDeclaredType receiverType = annotatedType.getReceiverType();
+    if (receiverType != null
+        && !receiverType.hasAnnotation(LeakedToResult.class)
+        && !receiverType.hasAnnotation(NonLeaked.class)) {
+      store.clearValue(JavaExpression.fromNode(receiver));
+    }
+  }
+
+  /**
+   * Case 3: Given a method invocation expression, if the parent of the expression is not a
+   * statement, check if there are any arguments of the method call annotated as
+   * {@literal @}LeakedToResult and remove it from the store, since it might be leaked.
+   */
+  @Override
+  public TransferResult<CFValue, CFStore> visitMethodInvocation(
+      MethodInvocationNode n, TransferInput<CFValue, CFStore> in) {
+    Tree parent = n.getTreePath().getParentPath().getLeaf();
+    boolean parentIsStatement = parent.getKind() == Kind.EXPRESSION_STATEMENT;
+
+    if (!parentIsStatement) {
+
+      ExecutableElement methodElement = TreeUtils.elementFromUse(n.getTree());
+      List<Node> args = n.getArguments();
+      List<? extends VariableElement> params = methodElement.getParameters();
+      assert (args.size() == params.size())
+          : "Number of arguments in "
+              + "the method call "
+              + n
+              + " is different from the"
+              + " number of parameters for the method declaration: "
+              + methodElement.getSimpleName();
+      CFStore store = in.getRegularStore();
+
+      for (int i = 0; i < args.size(); i++) {
+        Node arg = args.get(i);
+        VariableElement param = params.get(i);
+        if (factory.getAnnotatedType(param).hasAnnotation(LeakedToResult.class)) {
+          // If argument can leak to result, and parent is not a
+          // single statement, remove that node from store.
+          store.clearValue(JavaExpression.fromNode(arg));
+        }
+      }
+
+      // Now, doing the same as above for the receiver parameter
+      Node receiver = n.getTarget().getReceiver();
+      AnnotatedExecutableType annotatedType = factory.getAnnotatedType(methodElement);
+      AnnotatedDeclaredType receiverType = annotatedType.getReceiverType();
+      if (receiverType != null && receiverType.hasAnnotation(LeakedToResult.class)) {
+        store.clearValue(JavaExpression.fromNode(receiver));
+      }
+    }
+    // If parent is a statement, processPostconditions will handle the pseudo-assignments.
+    return super.visitMethodInvocation(n, in);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java
new file mode 100644
index 0000000..77800bb
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java
@@ -0,0 +1,322 @@
+package org.checkerframework.common.aliasing;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.ThrowTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+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.VariableElement;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.common.aliasing.qual.LeakedToResult;
+import org.checkerframework.common.aliasing.qual.NonLeaked;
+import org.checkerframework.common.aliasing.qual.Unique;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * This visitor ensures that every constructor whose result is annotated as {@literal @}Unique does
+ * not leak aliases.
+ *
+ * <p>TODO: Implement {@literal @}NonLeaked and {@literal @}LeakedToResult verifications:
+ *
+ * <p>{@literal @}NonLeaked: When a method declaration has a parameter annotated as
+ * {@literal @}NonLeaked, the method body must not leak a reference to that parameter.
+ *
+ * <p>{@literal @}LeakedToResult: When a method declaration has a parameter annotated as
+ * {@literal @}LeakedToResult, the method body must not leak a reference to that parameter, except
+ * at the method return statements.
+ *
+ * <p>Both of the checks above are similar to the @Unique check that is implemented in this visitor.
+ */
+public class AliasingVisitor extends BaseTypeVisitor<AliasingAnnotatedTypeFactory> {
+
+  public AliasingVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  /**
+   * Checks that if a method call is being invoked inside a constructor with result type
+   * {@literal @}Unique, it must not leak the "this" reference. There are 3 ways to make sure that
+   * this is not happening:
+   *
+   * <ol>
+   *   <li>{@code this} is not an argument of the method call.
+   *   <li>{@code this} is an argument of the method call, but the respective parameter is annotated
+   *       as {@literal @}NonLeaked.
+   *   <li>{@code this} is an argument of the method call, but the respective parameter is annotated
+   *       as {@literal @}LeakedToResult AND the result of the method call is not being stored (the
+   *       method call is a statement).
+   * </ol>
+   *
+   * The private method {@code isUniqueCheck} handles cases 2 and 3.
+   */
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+    // The check only needs to be done for constructors with result type
+    // @Unique. We also want to avoid visiting the <init> method.
+    if (isInUniqueConstructor()) {
+      if (TreeUtils.isSuperConstructorCall(node)) {
+        // Check if a call to super() might create an alias: that
+        // happens when the parent's respective constructor is not @Unique.
+        AnnotatedTypeMirror superResult = atypeFactory.getAnnotatedType(node);
+        if (!superResult.hasAnnotation(Unique.class)) {
+          checker.reportError(node, "unique.leaked");
+        }
+      } else {
+        // TODO: Currently the type of "this" doesn't always return the type of the constructor
+        // result, therefore we need this "else" block. Once constructors are implemented correctly
+        // we could remove that code below, since the type of "this" in a @Unique constructor will
+        // be @Unique.
+        Tree parent = getCurrentPath().getParentPath().getLeaf();
+        boolean parentIsStatement = parent.getKind() == Kind.EXPRESSION_STATEMENT;
+        ExecutableElement methodElement = TreeUtils.elementFromUse(node);
+        List<? extends VariableElement> params = methodElement.getParameters();
+        List<? extends ExpressionTree> args = node.getArguments();
+        assert (args.size() == params.size())
+            : "Number of arguments in"
+                + " the method call "
+                + node
+                + " is different from the "
+                + "number of parameters for the method declaration: "
+                + methodElement.getSimpleName();
+        for (int i = 0; i < args.size(); i++) {
+          // Here we are traversing the arguments of the method call.
+          // For every argument we check if it is a reference to "this".
+          if (TreeUtils.isExplicitThisDereference(args.get(i))) {
+            // If it is a reference to "this", there is still hope that
+            // it is not being leaked (2. and 3. from the javadoc).
+            VariableElement param = params.get(i);
+            boolean hasNonLeaked =
+                atypeFactory.getAnnotatedType(param).hasAnnotation(NonLeaked.class);
+            boolean hasLeakedToResult =
+                atypeFactory.getAnnotatedType(param).hasAnnotation(LeakedToResult.class);
+            isUniqueCheck(node, parentIsStatement, hasNonLeaked, hasLeakedToResult);
+          } else {
+            // Not possible to leak reference here (case 1. from the javadoc).
+          }
+        }
+
+        // Now, doing the same as above for the receiver parameter
+        AnnotatedExecutableType annotatedType = atypeFactory.getAnnotatedType(methodElement);
+        AnnotatedDeclaredType receiverType = annotatedType.getReceiverType();
+        if (receiverType != null) {
+          boolean hasNonLeaked = receiverType.hasAnnotation(NonLeaked.class);
+          boolean hasLeakedToResult = receiverType.hasAnnotation(LeakedToResult.class);
+          isUniqueCheck(node, parentIsStatement, hasNonLeaked, hasLeakedToResult);
+        }
+      }
+    }
+    return super.visitMethodInvocation(node, p);
+  }
+
+  private void isUniqueCheck(
+      MethodInvocationTree node,
+      boolean parentIsStatement,
+      boolean hasNonLeaked,
+      boolean hasLeakedToResult) {
+    if (hasNonLeaked || (hasLeakedToResult && parentIsStatement)) {
+      // Not leaked according to cases 2. and 3. from the javadoc of
+      // visitMethodInvocation.
+    } else {
+      // May be leaked, raise warning.
+      checker.reportError(node, "unique.leaked");
+    }
+  }
+
+  // TODO: Merge that code in commonAssignmentCheck(AnnotatedTypeMirror varType, ExpressionTree
+  // valueExp, String errorKey, boolean isLocalVariableAssignement), because the method below isn't
+  // called for pseudo-assignments, but the mentioned one is. The issue of copy-pasting the code
+  // from this method to the other one is that a declaration such as: List<@Unique Object> will
+  // raise a unique.leaked warning, as there is a pseudo-assignment from @Unique to a @MaybeAliased
+  // object, if the @Unique annotation is not in the stubfile.  TODO: Change the documentation in
+  // BaseTypeVisitor to point out that this isn't called for pseudo-assignments.
+  @Override
+  protected void commonAssignmentCheck(
+      Tree varTree,
+      ExpressionTree valueExp,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs);
+    if (isInUniqueConstructor() && TreeUtils.isExplicitThisDereference(valueExp)) {
+      // If an assignment occurs inside a constructor with result type @Unique, it will invalidate
+      // the @Unique property by using the "this" reference.
+      checker.reportError(valueExp, "unique.leaked");
+    } else if (canBeLeaked(valueExp)) {
+      checker.reportError(valueExp, "unique.leaked");
+    }
+  }
+
+  @Override
+  @FormatMethod
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      AnnotatedTypeMirror valueType,
+      Tree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
+
+    // If we are visiting a pseudo-assignment, visitorLeafKind is either
+    // Kind.NEW_CLASS or Kind.METHOD_INVOCATION.
+    TreePath path = visitorState.getPath();
+    if (path == null) {
+      return;
+    }
+    Kind visitorLeafKind = path.getLeaf().getKind();
+
+    if (visitorLeafKind == Kind.NEW_CLASS || visitorLeafKind == Kind.METHOD_INVOCATION) {
+      // Handling pseudo-assignments
+      if (canBeLeaked(valueTree)) {
+        Kind parentKind = visitorState.getPath().getParentPath().getLeaf().getKind();
+
+        if (!varType.hasAnnotation(NonLeaked.class)
+            && !(varType.hasAnnotation(LeakedToResult.class)
+                && parentKind == Kind.EXPRESSION_STATEMENT)) {
+          checker.reportError(valueTree, "unique.leaked");
+        }
+      }
+    }
+  }
+
+  @Override
+  public Void visitThrow(ThrowTree node, Void p) {
+    // throw is also an escape mechanism. If an expression of type
+    // @Unique is thrown, it is not @Unique anymore.
+    ExpressionTree exp = node.getExpression();
+    if (canBeLeaked(exp)) {
+      checker.reportError(exp, "unique.leaked");
+    }
+    return super.visitThrow(node, p);
+  }
+
+  @Override
+  public Void visitVariable(VariableTree node, Void p) {
+    // Component types are not allowed to have the @Unique annotation.
+    AnnotatedTypeMirror varType = atypeFactory.getAnnotatedType(node);
+    VariableElement elt = TreeUtils.elementFromDeclaration(node);
+    if (elt.getKind().isField() && varType.hasExplicitAnnotation(Unique.class)) {
+      checker.reportError(node, "unique.location.forbidden");
+    } else if (node.getType().getKind() == Kind.ARRAY_TYPE) {
+      AnnotatedArrayType arrayType = (AnnotatedArrayType) varType;
+      if (arrayType.getComponentType().hasAnnotation(Unique.class)) {
+        checker.reportError(node, "unique.location.forbidden");
+      }
+    } else if (node.getType().getKind() == Kind.PARAMETERIZED_TYPE) {
+      AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) varType;
+      for (AnnotatedTypeMirror atm : declaredType.getTypeArguments()) {
+        if (atm.hasAnnotation(Unique.class)) {
+          checker.reportError(node, "unique.location.forbidden");
+        }
+      }
+    }
+    return super.visitVariable(node, p);
+  }
+
+  @Override
+  public Void visitNewArray(NewArrayTree node, Void p) {
+    List<? extends ExpressionTree> initializers = node.getInitializers();
+    if (initializers != null && !initializers.isEmpty()) {
+      for (ExpressionTree exp : initializers) {
+        if (canBeLeaked(exp)) {
+          checker.reportError(exp, "unique.leaked");
+        }
+      }
+    }
+    return super.visitNewArray(node, p);
+  }
+
+  @Override
+  protected void checkConstructorResult(
+      AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {
+    // @Unique is verified, so don't check this.
+    if (!constructorType.getReturnType().hasAnnotation(atypeFactory.UNIQUE)) {
+      super.checkConstructorResult(constructorType, constructorElement);
+    }
+  }
+
+  @Override
+  protected void checkThisOrSuperConstructorCall(
+      MethodInvocationTree superCall, @CompilerMessageKey String errorKey) {
+    if (isInUniqueConstructor()) {
+      // Check if a call to super() might create an alias: that
+      // happens when the parent's respective constructor is not @Unique.
+      AnnotatedTypeMirror superResult = atypeFactory.getAnnotatedType(superCall);
+      if (!superResult.hasAnnotation(Unique.class)) {
+        checker.reportError(superCall, "unique.leaked");
+      }
+    }
+  }
+
+  /**
+   * Returns true if {@code exp} has type {@code @Unique} and is not a method invocation nor a new
+   * class expression. It checks whether the tree expression is unique by either checking for an
+   * explicit annotation or checking whether the class of the tree expression {@code exp} has type
+   * {@code @Unique}
+   *
+   * @param exp the Tree to check
+   */
+  private boolean canBeLeaked(Tree exp) {
+    AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(exp);
+    boolean isMethodInvocation = exp.getKind() == Kind.METHOD_INVOCATION;
+    boolean isNewClass = exp.getKind() == Kind.NEW_CLASS;
+    boolean isUniqueType = isUniqueClass(type) || type.hasExplicitAnnotation(Unique.class);
+    return isUniqueType && !isMethodInvocation && !isNewClass;
+  }
+
+  /**
+   * Return true if the class declaration for annotated type {@code type} has annotation
+   * {@code @Unique}.
+   *
+   * @param type the annotated type whose class must be checked
+   * @return boolean true if class is unique and false otherwise
+   */
+  private boolean isUniqueClass(AnnotatedTypeMirror type) {
+    Element el = types.asElement(type.getUnderlyingType());
+    if (el == null) {
+      return false;
+    }
+    Set<AnnotationMirror> annoMirrors = atypeFactory.getDeclAnnotations(el);
+    if (annoMirrors == null) {
+      return false;
+    }
+    if (atypeFactory.containsSameByClass(annoMirrors, Unique.class)) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if the enclosing method is a constructor whose return type is annotated as
+   * {@code @Unique}.
+   *
+   * @return true if the enclosing method is a constructor whose return type is annotated as
+   *     {@code @Unique}
+   */
+  private boolean isInUniqueConstructor() {
+    MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath());
+    if (enclosingMethod == null) {
+      return false; // No enclosing method.
+    }
+    return TreeUtils.isConstructor(enclosingMethod)
+        && atypeFactory
+            .getAnnotatedType(enclosingMethod)
+            .getReturnType()
+            .hasAnnotation(Unique.class);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/android.astub b/framework/src/main/java/org/checkerframework/common/aliasing/android.astub
new file mode 100644
index 0000000..d76e732
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/aliasing/android.astub
@@ -0,0 +1,211 @@
+// Aliasing types for Android API methods.
+
+import org.checkerframework.common.aliasing.qual.*;
+
+package android.content;
+
+class Intent implements Parcelable, Cloneable {
+  @Unique Intent();
+  // Copy constructor.
+  @Unique Intent(@NonLeaked Intent arg0);
+  @Unique Intent(String arg0);
+  @Unique Intent(String arg0, Uri arg1);
+  @Unique Intent(Context arg0, Class<?> arg1);
+  @Unique Intent(String arg0, Uri arg1, Context arg2, Class<?> arg3);
+
+  Intent addCategory(@LeakedToResult Intent this, String category);
+  Intent addFlags(@LeakedToResult Intent this, int flags);
+  Intent setType(@LeakedToResult Intent this, String type);
+  Intent setData(@LeakedToResult Intent this, Uri data);
+  Intent setComponent(@LeakedToResult Intent this, ComponentName cName);
+  Intent setClass(@LeakedToResult Intent this, Context arg0, Class<?> arg1);
+  Intent setAction(@LeakedToResult Intent this, String action);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, boolean arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, byte arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, char arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, short arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, int arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, long arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, float arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, double arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, String arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, CharSequence arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, Parcelable arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, Parcelable[] arg1);
+  Intent putParcelableArrayListExtra(@LeakedToResult Intent this, String arg0, ArrayList<? extends Parcelable> arg1);
+  Intent putIntegerArrayListExtra(@LeakedToResult Intent this, String arg0, ArrayList< Integer> arg1);
+  Intent putStringArrayListExtra(@LeakedToResult Intent this, String arg0, ArrayList< String> arg1);
+  Intent putCharSequenceArrayListExtra(@LeakedToResult Intent this, String arg0, ArrayList< CharSequence> arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, Serializable arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, boolean [] arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, byte [] arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, short [] arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, char [] arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, int [] arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, long [] arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, float [] arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, double [] arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, String [] arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, CharSequence [] arg1);
+  Intent putExtra(@LeakedToResult Intent this, String arg0, Bundle arg1);
+}
+
+
+package android.app;
+
+
+class BroadcastReceiver{
+    void startActivity(@NonLeaked Intent arg0);
+    void startActivities(@NonLeaked Intent[] arg0);
+    void sendBroadcast(@NonLeaked Intent arg0);
+    void sendBroadcast(@NonLeaked Intent arg0, String arg1);
+    void sendOrderedBroadcast(@NonLeaked Intent arg0, String arg1);
+    void sendOrderedBroadcast(@NonLeaked Intent arg0, String arg1, BroadcastReceiver arg2, Handler arg3, int arg4, String arg5, Bundle arg6);
+    void sendStickyBroadcast(@NonLeaked Intent arg0);
+    void sendStickyOrderedBroadcast(@NonLeaked Intent arg0, BroadcastReceiver arg1, Handler arg2, int arg3, String arg4, Bundle arg5);
+    void removeStickyBroadcast(@NonLeaked Intent arg0);
+    ComponentName startService(@NonLeaked Intent arg0);
+    boolean bindService(@NonLeaked Intent arg0, ServiceConnection arg1, int arg2);
+}
+
+
+class Context {
+    void startActivity(@NonLeaked Intent arg0);
+    void startActivities(@NonLeaked Intent[] arg0);
+    void sendBroadcast(@NonLeaked Intent arg0);
+    void sendBroadcast(@NonLeaked Intent arg0, String arg1);
+    void sendOrderedBroadcast(@NonLeaked Intent arg0, String arg1);
+    void sendOrderedBroadcast(@NonLeaked Intent arg0, String arg1, BroadcastReceiver arg2, Handler arg3, int arg4, String arg5, Bundle arg6);
+    void sendStickyBroadcast(@NonLeaked Intent arg0);
+    void sendStickyOrderedBroadcast(@NonLeaked Intent arg0, BroadcastReceiver arg1, Handler arg2, int arg3, String arg4, Bundle arg5);
+    void removeStickyBroadcast(@NonLeaked Intent arg0);
+    ComponentName startService(@NonLeaked Intent arg0);
+    boolean bindService(@NonLeaked Intent arg0, ServiceConnection arg1, int arg2);
+}
+
+class ContextWrapper extends Context {
+    void startActivity(@NonLeaked Intent arg0);
+    void startActivities(@NonLeaked Intent[] arg0);
+    void sendBroadcast(@NonLeaked Intent arg0);
+    void sendBroadcast(@NonLeaked Intent arg0, String arg1);
+    void sendOrderedBroadcast(@NonLeaked Intent arg0, String arg1);
+    void sendOrderedBroadcast(@NonLeaked Intent arg0, String arg1, BroadcastReceiver arg2, Handler arg3, int arg4, String arg5, Bundle arg6);
+    void sendStickyBroadcast(@NonLeaked Intent arg0);
+    void sendStickyOrderedBroadcast(@NonLeaked Intent arg0, BroadcastReceiver arg1, Handler arg2, int arg3, String arg4, Bundle arg5);
+    void removeStickyBroadcast(@NonLeaked Intent arg0);
+    ComponentName startService(@NonLeaked Intent arg0);
+    boolean bindService(@NonLeaked Intent arg0, ServiceConnection arg1, int arg2);
+}
+
+
+class Service {
+    void startActivity(@NonLeaked Intent arg0);
+    void startActivities(@NonLeaked Intent[] arg0);
+    void sendBroadcast(@NonLeaked Intent arg0);
+    void sendBroadcast(@NonLeaked Intent arg0, String arg1);
+    void sendOrderedBroadcast(@NonLeaked Intent arg0, String arg1);
+    void sendOrderedBroadcast(@NonLeaked Intent arg0, String arg1, BroadcastReceiver arg2, Handler arg3, int arg4, String arg5, Bundle arg6);
+    void sendStickyBroadcast(@NonLeaked Intent arg0);
+    void sendStickyOrderedBroadcast(@NonLeaked Intent arg0, BroadcastReceiver arg1, Handler arg2, int arg3, String arg4, Bundle arg5);
+    void removeStickyBroadcast(@NonLeaked Intent arg0);
+    ComponentName startService(@NonLeaked Intent arg0);
+    boolean bindService(@NonLeaked Intent arg0, ServiceConnection arg1, int arg2);
+}
+
+class Activity {
+    void startActivityForResult(@NonLeaked Intent arg0, int arg1);
+    boolean startActivityIfNeeded(@NonLeaked Intent arg0, int arg1);
+    boolean startNextMatchingActivity(@NonLeaked Intent arg0);
+    void startActivityFromChild(Activity arg0, @NonLeaked Intent arg1, int arg2);
+    void startActivityFromFragment(Fragment arg0, @NonLeaked Intent arg1, int arg2);
+    void startActivity(@NonLeaked Intent arg0);
+    void startActivities(@NonLeaked Intent[] arg0);
+    void sendBroadcast(@NonLeaked Intent arg0);
+    void sendBroadcast(@NonLeaked Intent arg0, String arg1);
+    void sendOrderedBroadcast(@NonLeaked Intent arg0, String arg1);
+    void sendOrderedBroadcast(@NonLeaked Intent arg0, String arg1, BroadcastReceiver arg2, Handler arg3, int arg4, String arg5, Bundle arg6);
+    void sendStickyBroadcast(@NonLeaked Intent arg0);
+    void sendStickyOrderedBroadcast(@NonLeaked Intent arg0, BroadcastReceiver arg1, Handler arg2, int arg3, String arg4, Bundle arg5);
+    void removeStickyBroadcast(@NonLeaked Intent arg0);
+    ComponentName startService(@NonLeaked Intent arg0);
+    boolean bindService(@NonLeaked Intent arg0, ServiceConnection arg1, int arg2);
+}
+
+package android.os;
+
+class Bundle {
+    @Unique Bundle();
+    void putAll(@NonLeaked Bundle this, Bundle arg0);
+    void putBoolean(@NonLeaked Bundle this, String arg0, boolean arg1);
+    void putByte(@NonLeaked Bundle this, String arg0, byte arg1);
+    void putChar(@NonLeaked Bundle this, String arg0, char arg1);
+    void putShort(@NonLeaked Bundle this, String arg0, short arg1);
+    void putInt(@NonLeaked Bundle this, String arg0, int arg1);
+    void putLong(@NonLeaked Bundle this, String arg0, long arg1);
+    void putFloat(@NonLeaked Bundle this, String arg0, float arg1);
+    void putDouble(@NonLeaked Bundle this, String arg0, double arg1);
+    void putString(@NonLeaked Bundle this, String arg0, String arg1);
+    void putCharSequence(@NonLeaked Bundle this, String arg0, CharSequence arg1);
+    void putParcelable(@NonLeaked Bundle this, String arg0, Parcelable arg1);
+    void putParcelableArray(@NonLeaked Bundle this, String arg0, Parcelable[] arg1);
+    void putParcelableArrayList(@NonLeaked Bundle this, String arg0, ArrayList<? extends Parcelable> arg1);
+    void putSparseParcelableArray(@NonLeaked Bundle this, String arg0, SparseArray<? extends Parcelable> arg1);
+    void putIntegerArrayList(@NonLeaked Bundle this, String arg0, ArrayList<Integer> arg1);
+    void putStringArrayList(@NonLeaked Bundle this, String arg0, ArrayList<String> arg1);
+    void putCharSequenceArrayList(@NonLeaked Bundle this, String arg0, ArrayList<CharSequence> arg1);
+    void putSerializable(@NonLeaked Bundle this, String arg0, Serializable arg1);
+    void putBooleanArray(@NonLeaked Bundle this, String arg0, boolean[] arg1);
+    void putByteArray(@NonLeaked Bundle this, String arg0, byte[] arg1);
+    void putShortArray(@NonLeaked Bundle this, String arg0, short[] arg1);
+    void putCharArray(@NonLeaked Bundle this, String arg0, char[] arg1);
+    void putIntArray(@NonLeaked Bundle this, String arg0, int[] arg1);
+    void putLongArray(@NonLeaked Bundle this, String arg0, long[] arg1);
+    void putFloatArray(@NonLeaked Bundle this, String arg0, float[] arg1);
+    void putDoubleArray(@NonLeaked Bundle this, String arg0, double[] arg1);
+    void putStringArray(@NonLeaked Bundle this, String arg0, String[] arg1);
+    void putCharSequenceArray(@NonLeaked Bundle this, String arg0, CharSequence[] arg1);
+    void putBundle(@NonLeaked Bundle this, String arg0, Bundle arg1);
+    void putBinder(@NonLeaked Bundle this, String arg0, IBinder arg1);
+    /**
+    boolean getBoolean(String arg0);
+    boolean getBoolean(String arg0, boolean arg1);
+    byte getByte(String arg0);
+    Byte getByte(String arg0, byte arg1);
+    char getChar(String arg0);
+    char getChar(String arg0, char arg1);
+    short getShort(String arg0);
+    short getShort(String arg0, short arg1);
+    int getInt(String arg0);
+    int getInt(String arg0, int arg1);
+    long getLong(String arg0);
+    long getLong(String arg0, long arg1);
+    float getFloat(String arg0);
+    float getFloat(String arg0, float arg1);
+    double getDouble(String arg0);
+    double getDouble(String arg0, double arg1);
+    String getString(String arg0);
+    String getString(String arg0, String arg1);
+    CharSequence getCharSequence(String arg0);
+    CharSequence getCharSequence(String arg0, CharSequence arg1);
+    Bundle getBundle(String arg0);
+    <T> T getParcelable(String arg0);
+    Parcelable[] getParcelableArray(String arg0);
+    <T> ArrayList<T> getParcelableArrayList(String arg0);
+    <T> SparseArray<T> getSparseParcelableArray(String arg0);
+    Serializable getSerializable(String arg0);
+    ArrayList<Integer> getIntegerArrayList(String arg0);
+    ArrayList<String> getStringArrayList(String arg0);
+    ArrayList<CharSequence> getCharSequenceArrayList(String arg0);
+    boolean[] getBooleanArray(String arg0);
+    byte[] getByteArray(String arg0);
+    short[] getShortArray(String arg0);
+    char[] getCharArray(String arg0);
+    int[] getIntArray(String arg0);
+    long[] getLongArray(String arg0);
+    float[] getFloatArray(String arg0);
+    double[] getDoubleArray(String arg0);
+    String[] getStringArray(String arg0);
+    CharSequence[] getCharSequenceArray(String arg0);
+    IBinder getBinder(String arg0);
+    **/
+}
diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/messages.properties b/framework/src/main/java/org/checkerframework/common/aliasing/messages.properties
new file mode 100644
index 0000000..c48f3b7
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/aliasing/messages.properties
@@ -0,0 +1,3 @@
+### Error messages for the AliasingChecker
+unique.leaked=Reference annotated as @Unique is leaked.
+unique.location.forbidden=The type @Unique cannot be used at this location.
diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseAnnotatedTypeFactory.java
new file mode 100644
index 0000000..c8e1d18
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseAnnotatedTypeFactory.java
@@ -0,0 +1,36 @@
+package org.checkerframework.common.basetype;
+
+import java.util.List;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * A factory that extends {@link GenericAnnotatedTypeFactory} to use the default flow-sensitive
+ * analysis as provided by {@link CFAnalysis}.
+ */
+public class BaseAnnotatedTypeFactory
+    extends GenericAnnotatedTypeFactory<CFValue, CFStore, CFTransfer, CFAnalysis> {
+
+  public BaseAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) {
+    super(checker, useFlow);
+
+    // Every subclass must call postInit!
+    if (this.getClass() == BaseAnnotatedTypeFactory.class) {
+      this.postInit();
+    }
+  }
+
+  public BaseAnnotatedTypeFactory(BaseTypeChecker checker) {
+    this(checker, flowByDefault);
+  }
+
+  @Override
+  protected CFAnalysis createFlowAnalysis(List<Pair<VariableElement, CFValue>> fieldValues) {
+    return new CFAnalysis(checker, this, fieldValues);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java
new file mode 100644
index 0000000..475beeb
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java
@@ -0,0 +1,905 @@
+package org.checkerframework.common.basetype;
+
+import com.google.common.collect.ImmutableSet;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Log;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.ClassGetName;
+import org.checkerframework.common.reflection.MethodValChecker;
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.TypeHierarchy;
+import org.checkerframework.framework.util.TreePathCacher;
+import org.checkerframework.javacutil.AbstractTypeProcessor;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.InternalUtils;
+import org.checkerframework.javacutil.TypeSystemError;
+import org.checkerframework.javacutil.UserError;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * An abstract {@link SourceChecker} that provides a simple {@link
+ * org.checkerframework.framework.source.SourceVisitor} implementation that type-checks assignments,
+ * pseudo-assignments such as parameter passing and method invocation, and method overriding.
+ *
+ * <p>Most type-checker annotation processor should extend this class, instead of {@link
+ * SourceChecker}. Checkers that require annotated types but not subtype checking (e.g. for testing
+ * purposes) should extend {@link SourceChecker}. Non-type checkers (e.g. checkers to enforce coding
+ * styles) can extend {@link SourceChecker} or {@link AbstractTypeProcessor}; the Checker Framework
+ * is not designed for such checkers.
+ *
+ * <p>It is a convention that, for a type system Foo, the checker, the visitor, and the annotated
+ * type factory are named as <i>FooChecker</i>, <i>FooVisitor</i>, and
+ * <i>FooAnnotatedTypeFactory</i>. Some factory methods use this convention to construct the
+ * appropriate classes reflectively.
+ *
+ * <p>{@code BaseTypeChecker} encapsulates a group for factories for various representations/classes
+ * related the type system, mainly:
+ *
+ * <ul>
+ *   <li>{@link QualifierHierarchy}: to represent the supported qualifiers in addition to their
+ *       hierarchy, mainly, subtyping rules
+ *   <li>{@link TypeHierarchy}: to check subtyping rules between <b>annotated types</b> rather than
+ *       qualifiers
+ *   <li>{@link AnnotatedTypeFactory}: to construct qualified types enriched with default qualifiers
+ *       according to the type system rules
+ *   <li>{@link BaseTypeVisitor}: to visit the compiled Java files and check for violations of the
+ *       type system rules
+ * </ul>
+ *
+ * <p>Subclasses must specify the set of type qualifiers they support. See {@link
+ * AnnotatedTypeFactory#createSupportedTypeQualifiers()}.
+ *
+ * <p>If the specified type qualifiers are meta-annotated with {@link SubtypeOf}, this
+ * implementation will automatically construct the type qualifier hierarchy. Otherwise, or if this
+ * behavior must be overridden, the subclass may override the {@link
+ * BaseAnnotatedTypeFactory#createQualifierHierarchy()} method.
+ *
+ * @checker_framework.manual #creating-compiler-interface The checker class
+ */
+public abstract class BaseTypeChecker extends SourceChecker {
+
+  @Override
+  public void initChecker() {
+    // initialize all checkers and share options as necessary
+    for (BaseTypeChecker checker : getSubcheckers()) {
+      // We need to add all options that are activated for the set of subcheckers to
+      // the individual checkers.
+      checker.addOptions(super.getOptions());
+      // Each checker should "support" all possible lint options - otherwise
+      // subchecker A would complain about a lint option for subchecker B.
+      checker.setSupportedLintOptions(this.getSupportedLintOptions());
+
+      // initChecker validates the passed options, so call it after setting supported options and
+      // lints.
+      checker.initChecker();
+    }
+
+    if (!getSubcheckers().isEmpty()) {
+      messageStore = new TreeSet<>(this::compareCheckerMessages);
+    }
+
+    super.initChecker();
+  }
+
+  /**
+   * The full list of subcheckers that need to be run prior to this one, in the order they need to
+   * be run in. This list will only be non-empty for the one checker that runs all other
+   * subcheckers. Do not read this field directly. Instead, retrieve it via {@link #getSubcheckers}.
+   *
+   * <p>If the list still null when {@link #getSubcheckers} is called, then getSubcheckers() will
+   * call {@link #instantiateSubcheckers}. However, if the current object was itself instantiated by
+   * a prior call to instantiateSubcheckers, this field will have been initialized to an empty list
+   * before getSubcheckers() is called, thereby ensuring that this list is non-empty only for one
+   * checker.
+   */
+  private @MonotonicNonNull List<BaseTypeChecker> subcheckers = null;
+
+  /**
+   * The list of subcheckers that are direct dependencies of this checker. This list will be
+   * non-empty for any checker that has at least one subchecker.
+   *
+   * <p>Does not need to be initialized to null or an empty list because it is always initialized
+   * via calls to instantiateSubcheckers.
+   */
+  // Set to non-null when subcheckers is.
+  private @MonotonicNonNull List<BaseTypeChecker> immediateSubcheckers = null;
+
+  /** Supported options for this checker. */
+  private @MonotonicNonNull Set<String> supportedOptions = null;
+
+  /** Options passed to this checker. */
+  private @MonotonicNonNull Map<String, String> options = null;
+
+  /**
+   * TreePathCacher to share between instances. Initialized either in getTreePathCacher (which is
+   * also called from instantiateSubcheckers).
+   */
+  private TreePathCacher treePathCacher = null;
+
+  /**
+   * The list of suppress warnings prefixes supported by this checker or any of its subcheckers
+   * (including indirect subcheckers). Do not access this field directly; instead, use {@link
+   * #getSuppressWarningsPrefixesOfSubcheckers}.
+   */
+  private @MonotonicNonNull Collection<String> suppressWarningsPrefixesOfSubcheckers = null;
+
+  @Override
+  protected void setRoot(CompilationUnitTree newRoot) {
+    super.setRoot(newRoot);
+    if (parentChecker == null) {
+      // Only clear the path cache if this is the main checker.
+      treePathCacher.clear();
+    }
+  }
+
+  /**
+   * Returns the set of subchecker classes on which this checker depends. Returns an empty set if
+   * this checker does not depend on any others.
+   *
+   * <p>Subclasses should override this method to specify subcheckers. If they do so, they should
+   * call the super implementation of this method and add dependencies to the returned set so that
+   * checkers required for reflection resolution are included if reflection resolution is requested.
+   *
+   * <p>Each subchecker of this checker may also depend on other checkers. If this checker and one
+   * of its subcheckers both depend on a third checker, that checker will only be instantiated once.
+   *
+   * <p>Though each checker is run on a whole compilation unit before the next checker is run, error
+   * and warning messages are collected and sorted based on the location in the source file before
+   * being printed. (See {@link #printOrStoreMessage(Diagnostic.Kind, String, Tree,
+   * CompilationUnitTree)}.)
+   *
+   * <p>WARNING: Circular dependencies are not supported nor do checkers verify that their
+   * dependencies are not circular. Make sure no circular dependencies are created when overriding
+   * this method. (In other words, if checker A depends on checker B, checker B cannot depend on
+   * checker A.)
+   *
+   * <p>This method is protected so it can be overridden, but it should only be called internally by
+   * the BaseTypeChecker.
+   *
+   * <p>The BaseTypeChecker will not modify the list returned by this method, but other clients do
+   * modify the list.
+   *
+   * @return the subchecker classes on which this checker depends
+   */
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    if (shouldResolveReflection()) {
+      return new LinkedHashSet<>(Collections.singleton(MethodValChecker.class));
+    }
+    // The returned set will be modified by callees.
+    return new LinkedHashSet<>();
+  }
+
+  /**
+   * Returns whether or not reflection should be resolved.
+   *
+   * @return true if reflection should be resolved
+   */
+  public boolean shouldResolveReflection() {
+    return hasOptionNoSubcheckers("resolveReflection");
+  }
+
+  /**
+   * Returns the appropriate visitor that type-checks the compilation unit according to the type
+   * system rules.
+   *
+   * <p>This implementation uses the checker naming convention to create the appropriate visitor. If
+   * no visitor is found, it returns an instance of {@link BaseTypeVisitor}. It reflectively invokes
+   * the constructor that accepts this checker and the compilation unit tree (in that order) as
+   * arguments.
+   *
+   * <p>Subclasses have to override this method to create the appropriate visitor if they do not
+   * follow the checker naming convention.
+   *
+   * @return the type-checking visitor
+   */
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    // Try to reflectively load the visitor.
+    Class<?> checkerClass = this.getClass();
+
+    while (checkerClass != BaseTypeChecker.class) {
+      BaseTypeVisitor<?> result =
+          invokeConstructorFor(
+              BaseTypeChecker.getRelatedClassName(checkerClass, "Visitor"),
+              new Class<?>[] {BaseTypeChecker.class},
+              new Object[] {this});
+      if (result != null) {
+        return result;
+      }
+      checkerClass = checkerClass.getSuperclass();
+    }
+
+    // If a visitor couldn't be loaded reflectively, return the default.
+    return new BaseTypeVisitor<BaseAnnotatedTypeFactory>(this);
+  }
+
+  /**
+   * A public variant of {@link #createSourceVisitor}. Only use this if you know what you are doing.
+   *
+   * @return the type-checking visitor
+   */
+  public BaseTypeVisitor<?> createSourceVisitorPublic() {
+    return createSourceVisitor();
+  }
+
+  /**
+   * Returns the name of a class related to a given one, by replacing "Checker" or "Subchecker" by
+   * {@code replacement}.
+   *
+   * @param checkerClass the checker class
+   * @param replacement the string to replace "Checker" or "Subchecker" by
+   * @return the name of the related class
+   */
+  @SuppressWarnings("signature") // string manipulation of @ClassGetName string
+  public static @ClassGetName String getRelatedClassName(
+      Class<?> checkerClass, String replacement) {
+    return checkerClass
+        .getName()
+        .replace("Checker", replacement)
+        .replace("Subchecker", replacement);
+  }
+
+  // **********************************************************************
+  // Misc. methods
+  // **********************************************************************
+
+  /** Specify supported lint options for all type-checkers. */
+  @Override
+  public Set<String> getSupportedLintOptions() {
+    Set<String> lintSet = new HashSet<>(super.getSupportedLintOptions());
+    lintSet.add("cast");
+    lintSet.add("cast:redundant");
+    lintSet.add("cast:unsafe");
+
+    for (BaseTypeChecker checker : getSubcheckers()) {
+      lintSet.addAll(checker.getSupportedLintOptions());
+    }
+
+    return Collections.unmodifiableSet(lintSet);
+  }
+
+  /**
+   * Invokes the constructor belonging to the class named by {@code name} having the given parameter
+   * types on the given arguments. Returns {@code null} if the class cannot be found. Otherwise,
+   * throws an exception if there is trouble with the constructor invocation.
+   *
+   * @param <T> the type to which the constructor belongs
+   * @param name the name of the class to which the constructor belongs
+   * @param paramTypes the types of the constructor's parameters
+   * @param args the arguments on which to invoke the constructor
+   * @return the result of the constructor invocation on {@code args}, or null if the class does not
+   *     exist
+   */
+  @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) // Intentional abuse
+  public static <T> T invokeConstructorFor(
+      @ClassGetName String name, Class<?>[] paramTypes, Object[] args) {
+
+    // Load the class.
+    Class<T> cls = null;
+    try {
+      cls = (Class<T>) Class.forName(name);
+    } catch (Exception e) {
+      // no class is found, simply return null
+      return null;
+    }
+
+    assert cls != null : "reflectively loading " + name + " failed";
+
+    // Invoke the constructor.
+    try {
+      Constructor<T> ctor = cls.getConstructor(paramTypes);
+      return ctor.newInstance(args);
+    } catch (Throwable t) {
+      if (t instanceof InvocationTargetException) {
+        Throwable err = t.getCause();
+        if (err instanceof UserError || err instanceof TypeSystemError) {
+          // Don't add more information about the constructor invocation.
+          throw (RuntimeException) err;
+        }
+      }
+      Throwable cause;
+      String causeMessage;
+      if (t instanceof InvocationTargetException) {
+        cause = t.getCause();
+        if (cause == null || cause.getMessage() == null) {
+          causeMessage = t.getMessage();
+        } else if (t.getMessage() == null) {
+          causeMessage = cause.getMessage();
+        } else {
+          causeMessage = t.getMessage() + ": " + cause.getMessage();
+        }
+      } else {
+        cause = t;
+        causeMessage = (cause == null) ? "null" : cause.getMessage();
+      }
+      throw new BugInCF(
+          cause,
+          "Error when invoking constructor %s(%s) on args %s; cause: %s",
+          name,
+          StringsPlume.join(", ", paramTypes),
+          Arrays.toString(args),
+          causeMessage);
+    }
+  }
+
+  @Override
+  public BaseTypeVisitor<?> getVisitor() {
+    return (BaseTypeVisitor<?>) super.getVisitor();
+  }
+
+  /**
+   * Return the type factory associated with this checker.
+   *
+   * @return the type factory associated with this checker
+   */
+  public GenericAnnotatedTypeFactory<?, ?, ?, ?> getTypeFactory() {
+    BaseTypeVisitor<?> visitor = getVisitor();
+    // Avoid NPE if this method is called during initialization.
+    if (visitor == null) {
+      return null;
+    }
+    return visitor.getTypeFactory();
+  }
+
+  @Override
+  public AnnotationProvider getAnnotationProvider() {
+    return getTypeFactory();
+  }
+
+  /**
+   * Returns the requested subchecker. A checker of a given class can only be run once, so this
+   * returns the only such checker, or null if none was found. The caller must know the exact
+   * checker class to request.
+   *
+   * @param checkerClass the class of the subchecker
+   * @return the requested subchecker or null if not found
+   */
+  @SuppressWarnings("unchecked")
+  public <T extends BaseTypeChecker> T getSubchecker(Class<T> checkerClass) {
+    for (BaseTypeChecker checker : immediateSubcheckers) {
+      if (checker.getClass() == checkerClass) {
+        return (T) checker;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Returns the type factory used by a subchecker. Returns null if no matching subchecker was found
+   * or if the type factory is null. The caller must know the exact checker class to request.
+   *
+   * @param checkerClass the class of the subchecker
+   * @return the type factory of the requested subchecker or null if not found
+   */
+  @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) // Intentional abuse
+  public <T extends GenericAnnotatedTypeFactory<?, ?, ?, ?>, U extends BaseTypeChecker>
+      T getTypeFactoryOfSubchecker(Class<U> checkerClass) {
+    BaseTypeChecker checker = getSubchecker(checkerClass);
+    if (checker != null) {
+      return (T) checker.getTypeFactory();
+    }
+
+    return null;
+  }
+
+  /*
+   * Performs a depth first search for all checkers this checker depends on.
+   * The depth first search ensures that the collection has the correct order the checkers need to be run in.
+   *
+   * Modifies the alreadyInitializedSubcheckerMap map by adding all recursively newly instantiated subcheckers' class objects and instances.
+   * A LinkedHashMap is used because, unlike HashMap, it preserves the order in which entries were inserted.
+   *
+   * Returns the unmodifiable list of immediate subcheckers of this checker.
+   */
+  private List<BaseTypeChecker> instantiateSubcheckers(
+      LinkedHashMap<Class<? extends BaseTypeChecker>, BaseTypeChecker>
+          alreadyInitializedSubcheckerMap) {
+    LinkedHashSet<Class<? extends BaseTypeChecker>> classesOfImmediateSubcheckers =
+        getImmediateSubcheckerClasses();
+    if (classesOfImmediateSubcheckers.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    ArrayList<BaseTypeChecker> immediateSubcheckers =
+        new ArrayList<>(classesOfImmediateSubcheckers.size());
+
+    for (Class<? extends BaseTypeChecker> subcheckerClass : classesOfImmediateSubcheckers) {
+      BaseTypeChecker subchecker = alreadyInitializedSubcheckerMap.get(subcheckerClass);
+      if (subchecker != null) {
+        // Add the already initialized subchecker to the list of immediate subcheckers so
+        // that this checker can refer to it.
+        immediateSubcheckers.add(subchecker);
+        continue;
+      }
+
+      BaseTypeChecker instance;
+      try {
+        instance = subcheckerClass.getDeclaredConstructor().newInstance();
+      } catch (Exception e) {
+        throw new BugInCF("Could not create an instance of " + subcheckerClass);
+      }
+
+      instance.setProcessingEnvironment(this.processingEnv);
+      instance.treePathCacher = this.getTreePathCacher();
+      // Prevent the new checker from storing non-immediate subcheckers
+      instance.subcheckers = Collections.emptyList();
+      immediateSubcheckers.add(instance);
+      instance.immediateSubcheckers =
+          instance.instantiateSubcheckers(alreadyInitializedSubcheckerMap);
+      instance.setParentChecker(this);
+      alreadyInitializedSubcheckerMap.put(subcheckerClass, instance);
+    }
+
+    return Collections.unmodifiableList(immediateSubcheckers);
+  }
+
+  /**
+   * Get the list of all subcheckers (if any). via the instantiateSubcheckers method. This list is
+   * only non-empty for the one checker that runs all other subcheckers. These are recursively
+   * instantiated via instantiateSubcheckers the first time the method is called if subcheckers is
+   * null. Assumes all checkers run on the same thread.
+   *
+   * @return the list of all subcheckers (if any)
+   */
+  public List<BaseTypeChecker> getSubcheckers() {
+    if (subcheckers == null) {
+      // Instantiate the checkers this one depends on, if any.
+      LinkedHashMap<Class<? extends BaseTypeChecker>, BaseTypeChecker> checkerMap =
+          new LinkedHashMap<>(1);
+
+      immediateSubcheckers = instantiateSubcheckers(checkerMap);
+
+      subcheckers = Collections.unmodifiableList(new ArrayList<>(checkerMap.values()));
+    }
+
+    return subcheckers;
+  }
+
+  /** Get the shared TreePathCacher instance. */
+  public TreePathCacher getTreePathCacher() {
+    if (treePathCacher == null) {
+      // In case it wasn't already set in instantiateSubcheckers.
+      treePathCacher = new TreePathCacher();
+    }
+    return treePathCacher;
+  }
+
+  @Override
+  protected void reportJavacError(TreePath p) {
+    if (parentChecker == null) {
+      // Only the parent checker should report the "type.checking.not.run" error.
+      super.reportJavacError(p);
+    }
+  }
+
+  // AbstractTypeProcessor delegation
+  @Override
+  public void typeProcess(TypeElement element, TreePath tree) {
+    if (!getSubcheckers().isEmpty()) {
+      // TODO: I expected this to only be necessary if (parentChecker == null).
+      // However, the NestedAggregateChecker fails otherwise.
+      messageStore.clear();
+    }
+
+    // Errors (or other messages) issued via
+    //   SourceChecker#message(Diagnostic.Kind, Object, String, Object...)
+    // are stored in messageStore until all checkers have processed this compilation unit.
+    // All other messages are printed immediately.  This includes errors issued because the
+    // checker threw an exception.
+
+    // In order to run the next checker on this compilation unit even if the previous issued errors,
+    // the next checker's errsOnLastExit needs to include all errors issued by previous checkers.
+
+    Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
+    Log log = Log.instance(context);
+
+    int nerrorsOfAllPreviousCheckers = this.errsOnLastExit;
+    for (BaseTypeChecker subchecker : getSubcheckers()) {
+      subchecker.errsOnLastExit = nerrorsOfAllPreviousCheckers;
+      subchecker.messageStore = messageStore;
+      int errorsBeforeTypeChecking = log.nerrors;
+
+      subchecker.typeProcess(element, tree);
+
+      int errorsAfterTypeChecking = log.nerrors;
+      nerrorsOfAllPreviousCheckers += errorsAfterTypeChecking - errorsBeforeTypeChecking;
+    }
+
+    this.errsOnLastExit = nerrorsOfAllPreviousCheckers;
+    super.typeProcess(element, tree);
+
+    if (!getSubcheckers().isEmpty()) {
+      printStoredMessages(tree.getCompilationUnit());
+      // Update errsOnLastExit to reflect the errors issued.
+      this.errsOnLastExit = log.nerrors;
+    }
+  }
+
+  /**
+   * Like {@link SourceChecker#getSuppressWarningsPrefixes()}, but includes all prefixes supported
+   * by this checker or any of its subcheckers. Does not guarantee that the result is in any
+   * particular order. The result is immutable.
+   *
+   * @return the suppress warnings prefixes supported by this checker or any of its subcheckers
+   */
+  public Collection<String> getSuppressWarningsPrefixesOfSubcheckers() {
+    if (this.suppressWarningsPrefixesOfSubcheckers == null) {
+      Collection<String> prefixes = getSuppressWarningsPrefixes();
+      for (BaseTypeChecker subchecker : getSubcheckers()) {
+        prefixes.addAll(subchecker.getSuppressWarningsPrefixes());
+      }
+      this.suppressWarningsPrefixesOfSubcheckers = ImmutableSet.copyOf(prefixes);
+    }
+    return this.suppressWarningsPrefixesOfSubcheckers;
+  }
+
+  /** A cache for {@link #getUltimateParentChecker}. */
+  @MonotonicNonNull BaseTypeChecker ultimateParentChecker;
+
+  /**
+   * Finds the ultimate parent checker of this checker. The ultimate parent checker is the checker
+   * that the user actually requested, i.e. the one with no parent. The ultimate parent might be
+   * this checker itself.
+   *
+   * @return the first checker in the parent checker chain with no parent checker of its own, i.e.
+   *     the ultimate parent checker
+   */
+  public BaseTypeChecker getUltimateParentChecker() {
+    if (ultimateParentChecker == null) {
+      ultimateParentChecker = this;
+      while (ultimateParentChecker.getParentChecker() instanceof BaseTypeChecker) {
+        ultimateParentChecker = (BaseTypeChecker) ultimateParentChecker.getParentChecker();
+      }
+    }
+
+    return ultimateParentChecker;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>This implementation collects needed warning suppressions for all subcheckers.
+   */
+  @Override
+  protected void warnUnneededSuppressions() {
+    if (parentChecker != null) {
+      return;
+    }
+
+    if (!hasOption("warnUnneededSuppressions")) {
+      return;
+    }
+    Set<Element> elementsWithSuppressedWarnings =
+        new HashSet<>(this.elementsWithSuppressedWarnings);
+    this.elementsWithSuppressedWarnings.clear();
+    Set<String> prefixes = new HashSet<>(getSuppressWarningsPrefixes());
+    Set<String> errorKeys = new HashSet<>(messagesProperties.stringPropertyNames());
+    for (BaseTypeChecker subChecker : subcheckers) {
+      elementsWithSuppressedWarnings.addAll(subChecker.elementsWithSuppressedWarnings);
+      subChecker.elementsWithSuppressedWarnings.clear();
+      prefixes.addAll(subChecker.getSuppressWarningsPrefixes());
+      errorKeys.addAll(subChecker.messagesProperties.stringPropertyNames());
+      subChecker.getVisitor().treesWithSuppressWarnings.clear();
+    }
+    warnUnneededSuppressions(elementsWithSuppressedWarnings, prefixes, errorKeys);
+
+    getVisitor().treesWithSuppressWarnings.clear();
+  }
+
+  /**
+   * Stores all messages issued by this checker and its subcheckers for the current compilation
+   * unit. The messages are printed after all checkers have processed the current compilation unit.
+   * The purpose is to sort messages, grouping together all messages about a particular line of
+   * code.
+   *
+   * <p>If this checker has no subcheckers and is not a subchecker for any other checker, then
+   * messageStore is null and messages will be printed as they are issued by this checker.
+   */
+  private TreeSet<CheckerMessage> messageStore = null;
+
+  /**
+   * If this is a compound checker or a subchecker of a compound checker, then the message is stored
+   * until all messages from all checkers for the compilation unit are issued.
+   *
+   * <p>Otherwise, it prints the message.
+   */
+  @Override
+  protected void printOrStoreMessage(
+      Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) {
+    assert this.currentRoot == root;
+    StackTraceElement[] trace = Thread.currentThread().getStackTrace();
+    if (messageStore == null) {
+      super.printOrStoreMessage(kind, message, source, root, trace);
+    } else {
+      CheckerMessage checkerMessage = new CheckerMessage(kind, message, source, this, trace);
+      messageStore.add(checkerMessage);
+    }
+  }
+
+  /**
+   * Prints error messages for this checker and all subcheckers such that the errors are ordered by
+   * line and column number and then by checker. (See {@link #compareCheckerMessages} for more
+   * precise order.)
+   *
+   * @param unit current compilation unit
+   */
+  private void printStoredMessages(CompilationUnitTree unit) {
+    for (CheckerMessage msg : messageStore) {
+      super.printOrStoreMessage(msg.kind, msg.message, msg.source, unit, msg.trace);
+    }
+  }
+
+  /** Represents a message (e.g., an error message) issued by a checker. */
+  private static class CheckerMessage {
+    /** The severity of the message. */
+    final Diagnostic.Kind kind;
+    /** The message itself. */
+    final String message;
+    /** The source code that the message is about. */
+    final @InternedDistinct Tree source;
+    /** Stores the stack trace when the message is created. */
+    final StackTraceElement[] trace;
+
+    /**
+     * The checker that issued this message. The compound checker that depends on this checker uses
+     * this to sort the messages.
+     */
+    final @InternedDistinct BaseTypeChecker checker;
+
+    /**
+     * Create a new CheckerMessage.
+     *
+     * @param kind kind of diagnostic, for example, error or warning
+     * @param message error message that needs to be printed
+     * @param source tree element causing the error
+     * @param checker the type-checker in use
+     * @param trace the stack trace when the message is created
+     */
+    private CheckerMessage(
+        Diagnostic.Kind kind,
+        String message,
+        @FindDistinct Tree source,
+        @FindDistinct BaseTypeChecker checker,
+        StackTraceElement[] trace) {
+      this.kind = kind;
+      this.message = message;
+      this.source = source;
+      this.checker = checker;
+      this.trace = trace;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+
+      CheckerMessage that = (CheckerMessage) o;
+      return this.kind == that.kind
+          && this.message.equals(that.message)
+          && this.source == that.source
+          && this.checker == that.checker;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(kind, message, source, checker);
+    }
+
+    @Override
+    public String toString() {
+      return "CheckerMessage{"
+          + "kind="
+          + kind
+          + ", checker="
+          + checker.getClass().getSimpleName()
+          + ", message='"
+          + message
+          + '\''
+          + ", source="
+          + source
+          + '}';
+    }
+  }
+
+  /**
+   * Compares two {@link CheckerMessage}s. Compares first by position at which the error will be
+   * printed, then by kind of message, then by the message string, and finally by the order in which
+   * the checkers run.
+   *
+   * @param o1 the first CheckerMessage
+   * @param o2 the second CheckerMessage
+   * @return a negative integer, zero, or a positive integer if the first CheckerMessage is less
+   *     than, equal to, or greater than the second
+   */
+  private int compareCheckerMessages(CheckerMessage o1, CheckerMessage o2) {
+    int byPos = InternalUtils.compareDiagnosticPosition(o1.source, o2.source);
+    if (byPos != 0) {
+      return byPos;
+    }
+
+    int kind = o1.kind.compareTo(o2.kind);
+    if (kind != 0) {
+      return kind;
+    }
+
+    int msgcmp = o1.message.compareTo(o2.message);
+    if (msgcmp == 0) {
+      // If the two messages are identical so far, it doesn't matter
+      // from which checker they came.
+      return 0;
+    }
+
+    // Sort by order in which the checkers are run. (All the subcheckers,
+    // followed by the checker.)
+    List<BaseTypeChecker> subcheckers = BaseTypeChecker.this.getSubcheckers();
+    int o1Index = subcheckers.indexOf(o1.checker);
+    int o2Index = subcheckers.indexOf(o2.checker);
+    if (o1Index == -1) {
+      o1Index = subcheckers.size();
+    }
+    if (o2Index == -1) {
+      o2Index = subcheckers.size();
+    }
+    int checkercmp = Integer.compare(o1Index, o2Index);
+    if (checkercmp == 0) {
+      // If the two messages are from the same checker, sort by message.
+      return msgcmp;
+    } else {
+      return checkercmp;
+    }
+  }
+
+  @Override
+  public void typeProcessingOver() {
+    for (BaseTypeChecker checker : getSubcheckers()) {
+      checker.typeProcessingOver();
+    }
+
+    super.typeProcessingOver();
+  }
+
+  @Override
+  public Set<String> getSupportedOptions() {
+    if (supportedOptions == null) {
+      Set<String> options = new HashSet<>();
+      options.addAll(super.getSupportedOptions());
+
+      for (BaseTypeChecker checker : getSubcheckers()) {
+        options.addAll(checker.getSupportedOptions());
+      }
+
+      options.addAll(
+          expandCFOptions(Arrays.asList(this.getClass()), options.toArray(new String[0])));
+
+      supportedOptions = Collections.unmodifiableSet(options);
+    }
+    return supportedOptions;
+  }
+
+  @Override
+  public Map<String, String> getOptions() {
+    if (this.options == null) {
+      Map<String, String> options = new HashMap<>(super.getOptions());
+
+      for (BaseTypeChecker checker : getSubcheckers()) {
+        options.putAll(checker.getOptions());
+      }
+      this.options = Collections.unmodifiableMap(options);
+    }
+
+    return this.options;
+  }
+
+  /**
+   * Like {@link #getOptions}, but only includes options provided to this checker. Does not include
+   * those passed to subcheckers.
+   *
+   * @return the the active options for this checker, not including those passed to subcheckers
+   */
+  public Map<String, String> getOptionsNoSubcheckers() {
+    return super.getOptions();
+  }
+
+  /**
+   * Like {@link #hasOption}, but checks whether the given option is provided to this checker. Does
+   * not consider those passed to subcheckers.
+   *
+   * @param name the name of the option to check
+   * @return true if the option name was provided to this checker, false otherwise
+   */
+  public final boolean hasOptionNoSubcheckers(String name) {
+    return getOptionsNoSubcheckers().containsKey(name);
+  }
+
+  /**
+   * Return a list of additional stub files to be treated as if they had been written in a
+   * {@code @StubFiles} annotation.
+   *
+   * @return stub files to be treated as if they had been written in a {@code @StubFiles} annotation
+   */
+  public List<String> getExtraStubFiles() {
+    return new ArrayList<>();
+  }
+
+  @Override
+  protected Object processArg(Object arg) {
+    if (arg instanceof Collection) {
+      Collection<?> carg = (Collection<?>) arg;
+      return CollectionsPlume.mapList(this::processArg, carg);
+    } else if (arg instanceof AnnotationMirror && getTypeFactory() != null) {
+      return getTypeFactory()
+          .getAnnotationFormatter()
+          .formatAnnotationMirror((AnnotationMirror) arg);
+    } else {
+      return super.processArg(arg);
+    }
+  }
+
+  @Override
+  protected boolean shouldAddShutdownHook() {
+    if (super.shouldAddShutdownHook() || getTypeFactory().getCFGVisualizer() != null) {
+      return true;
+    }
+    for (BaseTypeChecker checker : getSubcheckers()) {
+      if (checker.getTypeFactory().getCFGVisualizer() != null) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  protected void shutdownHook() {
+    super.shutdownHook();
+
+    CFGVisualizer<?, ?, ?> viz = getTypeFactory().getCFGVisualizer();
+    if (viz != null) {
+      viz.shutdown();
+    }
+
+    for (BaseTypeChecker checker : getSubcheckers()) {
+      viz = checker.getTypeFactory().getCFGVisualizer();
+      if (viz != null) {
+        viz.shutdown();
+      }
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java
new file mode 100644
index 0000000..9d8ea28
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java
@@ -0,0 +1,540 @@
+package org.checkerframework.common.basetype;
+
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.tools.Diagnostic.Kind;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.framework.source.DiagMessage;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.AnnotatedTypeParameterBounds;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
+import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypeAnnotationUtils;
+
+/**
+ * A visitor to validate the types in a tree.
+ *
+ * <p>Note: A TypeValidator (this class and its subclasses) cannot tell whether an annotation was
+ * written by a programmer or defaulted/inferred/computed by the Checker Framework, because the
+ * AnnotatedTypeMirror does not make distinctions about which annotations in an AnnotatedTypeMirror
+ * were explicitly written and which were added by a checker. To issue a warning/error only when a
+ * programmer writes an annotation, override {@link BaseTypeVisitor#visitAnnotatedType} and {@link
+ * BaseTypeVisitor#visitVariable}.
+ */
+public class BaseTypeValidator extends AnnotatedTypeScanner<Void, Tree> implements TypeValidator {
+  /** Is the type valid? This is side-effected by the visitor, and read at the end of visiting. */
+  protected boolean isValid = true;
+
+  /** Should the primary annotation on the top level type be checked? */
+  protected boolean checkTopLevelDeclaredOrPrimitiveType = true;
+
+  /** BaseTypeChecker. */
+  protected final BaseTypeChecker checker;
+  /** BaseTypeVisitor. */
+  protected final BaseTypeVisitor<?> visitor;
+  /** AnnotatedTypeFactory. */
+  protected final AnnotatedTypeFactory atypeFactory;
+
+  // TODO: clean up coupling between components
+  public BaseTypeValidator(
+      BaseTypeChecker checker, BaseTypeVisitor<?> visitor, AnnotatedTypeFactory atypeFactory) {
+    this.checker = checker;
+    this.visitor = visitor;
+    this.atypeFactory = atypeFactory;
+  }
+
+  /**
+   * Validate the type against the given tree. This method both issues error messages and also
+   * returns a boolean value.
+   *
+   * <p>This is the entry point to the type validator. Neither this method nor visit should be
+   * called directly by a visitor, only use {@link BaseTypeVisitor#validateTypeOf(Tree)}.
+   *
+   * <p>This method is only called on top-level types, but it validates the entire type including
+   * components of a compound type. Subclasses should override this only if there is special-case
+   * behavior that should be performed only on top-level types.
+   *
+   * @param type the type to validate
+   * @param tree the tree from which the type originated. If the tree is a method tree, {@code type}
+   *     is its return type. If the tree is a variable tree, {@code type} is the variable's type.
+   * @return true if the type is valid
+   */
+  @Override
+  public boolean isValid(AnnotatedTypeMirror type, Tree tree) {
+    List<DiagMessage> diagMessages =
+        isValidStructurally(atypeFactory.getQualifierHierarchy(), type);
+    if (!diagMessages.isEmpty()) {
+      for (DiagMessage d : diagMessages) {
+        checker.report(tree, d);
+      }
+      return false;
+    }
+    this.isValid = true;
+    this.checkTopLevelDeclaredOrPrimitiveType =
+        shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree);
+    visit(type, tree);
+    return this.isValid;
+  }
+
+  /**
+   * Should the top-level declared or primitive type be checked?
+   *
+   * <p>If {@code type} is not a declared or primitive type, then this method returns true.
+   *
+   * <p>Top-level type is not checked if tree is a local variable or an expression tree.
+   *
+   * @param type AnnotatedTypeMirror being validated
+   * @param tree a Tree whose type is {@code type}
+   * @return whether or not the top-level type should be checked, if {@code type} is a declared or
+   *     primitive type.
+   */
+  protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType(
+      AnnotatedTypeMirror type, Tree tree) {
+    if (type.getKind() != TypeKind.DECLARED && !type.getKind().isPrimitive()) {
+      return true;
+    }
+    return !TreeUtils.isLocalVariable(tree)
+        && (!TreeUtils.isExpressionTree(tree) || TreeUtils.isTypeTree(tree));
+  }
+
+  /**
+   * Performs some well-formedness checks on the given {@link AnnotatedTypeMirror}. Returns a list
+   * of failures. If successful, returns an empty list. The method will never return failures for a
+   * valid type, but might not catch all invalid types.
+   *
+   * <p>This method ensures that the type is structurally or lexically well-formed, but it does not
+   * check whether the annotations are semantically sensible. Subclasses should generally override
+   * visit methods such as {@link #visitDeclared} rather than this method.
+   *
+   * <p>Currently, this implementation checks the following (subclasses can extend this behavior):
+   *
+   * <ol>
+   *   <li>There should not be multiple annotations from the same qualifier hierarchy.
+   *   <li>There should not be more annotations than the width of the QualifierHierarchy.
+   *   <li>If the type is not a type variable, then the number of annotations should be the same as
+   *       the width of the QualifierHierarchy.
+   *   <li>These properties should also hold recursively for component types of arrays and for
+   *       bounds of type variables and wildcards.
+   * </ol>
+   *
+   * @param qualifierHierarchy the QualifierHierarchy
+   * @param type the type to test
+   * @return list of reasons the type is invalid, or empty list if the type is valid
+   */
+  protected List<DiagMessage> isValidStructurally(
+      QualifierHierarchy qualifierHierarchy, AnnotatedTypeMirror type) {
+    SimpleAnnotatedTypeScanner<List<DiagMessage>, QualifierHierarchy> scanner =
+        new SimpleAnnotatedTypeScanner<>(
+            (atm, q) -> isTopLevelValidType(q, atm),
+            DiagMessage::mergeLists,
+            Collections.emptyList());
+    return scanner.visit(type, qualifierHierarchy);
+  }
+
+  /**
+   * Checks every property listed in {@link #isValidStructurally}, but only for the top level type.
+   * If successful, returns an empty list. If not successful, returns diagnostics.
+   *
+   * @param qualifierHierarchy the QualifierHierarchy
+   * @param type the type to be checked
+   * @return the diagnostics indicating failure, or an empty list if successful
+   */
+  // This method returns a singleton or empyty list.  Its return type is List rather than
+  // DiagMessage (with null indicting success) because its caller, isValidStructurally(), expects
+  // a list.
+  protected List<DiagMessage> isTopLevelValidType(
+      QualifierHierarchy qualifierHierarchy, AnnotatedTypeMirror type) {
+    // multiple annotations from the same hierarchy
+    Set<AnnotationMirror> annotations = type.getAnnotations();
+    Set<AnnotationMirror> seenTops = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror anno : annotations) {
+      AnnotationMirror top = qualifierHierarchy.getTopAnnotation(anno);
+      if (AnnotationUtils.containsSame(seenTops, top)) {
+        return Collections.singletonList(
+            new DiagMessage(Kind.ERROR, "conflicting.annos", annotations, type));
+      }
+      seenTops.add(top);
+    }
+
+    boolean canHaveEmptyAnnotationSet = QualifierHierarchy.canHaveEmptyAnnotationSet(type);
+
+    // wrong number of annotations
+    if (!canHaveEmptyAnnotationSet && seenTops.size() < qualifierHierarchy.getWidth()) {
+      return Collections.singletonList(
+          new DiagMessage(Kind.ERROR, "too.few.annotations", annotations, type));
+    }
+
+    // success
+    return Collections.emptyList();
+  }
+
+  protected void reportValidityResult(
+      final @CompilerMessageKey String errorType, final AnnotatedTypeMirror type, final Tree p) {
+    checker.reportError(p, errorType, type.getAnnotations(), type.toString());
+    isValid = false;
+  }
+
+  /**
+   * Like {@link #reportValidityResult}, but the type is printed in the error message without
+   * annotations. This method would print "annotation @NonNull is not permitted on type int",
+   * whereas {@link #reportValidityResult} would print "annotation @NonNull is not permitted on
+   * type @NonNull int". In addition, when the underlying type is a compound type such as
+   * {@code @Bad List<String>}, the erased type will be used, i.e., "{@code List}" will print
+   * instead of "{@code @Bad List<String>}".
+   */
+  protected void reportValidityResultOnUnannotatedType(
+      final @CompilerMessageKey String errorType, final AnnotatedTypeMirror type, final Tree p) {
+    TypeMirror underlying =
+        TypeAnnotationUtils.unannotatedType(type.getErased().getUnderlyingType());
+    checker.reportError(p, errorType, type.getAnnotations(), underlying.toString());
+    isValid = false;
+  }
+
+  /**
+   * Most errors reported by this class are of the form type.invalid. This method reports when the
+   * bounds of a wildcard or type variable don't make sense. Bounds make sense when the effective
+   * annotations on the upper bound are supertypes of those on the lower bounds for all hierarchies.
+   * To ensure that this subtlety is not lost on users, we report "bound" and print the bounds along
+   * with the invalid type rather than a "type.invalid".
+   *
+   * @param type the type with invalid bounds
+   * @param tree where to report the error
+   */
+  protected void reportInvalidBounds(final AnnotatedTypeMirror type, final Tree tree) {
+    final String label;
+    final AnnotatedTypeMirror upperBound;
+    final AnnotatedTypeMirror lowerBound;
+
+    switch (type.getKind()) {
+      case TYPEVAR:
+        label = "type parameter";
+        upperBound = ((AnnotatedTypeVariable) type).getUpperBound();
+        lowerBound = ((AnnotatedTypeVariable) type).getLowerBound();
+        break;
+
+      case WILDCARD:
+        label = "wildcard";
+        upperBound = ((AnnotatedWildcardType) type).getExtendsBound();
+        lowerBound = ((AnnotatedWildcardType) type).getSuperBound();
+        break;
+
+      default:
+        throw new BugInCF("Type is not bounded.%ntype=%s%ntree=%s", type, tree);
+    }
+
+    checker.reportError(
+        tree,
+        "bound",
+        label,
+        type.toString(),
+        upperBound.toString(true),
+        lowerBound.toString(true));
+    isValid = false;
+  }
+
+  protected void reportInvalidType(final AnnotatedTypeMirror type, final Tree p) {
+    reportValidityResult("type.invalid", type, p);
+  }
+
+  /**
+   * Report an "annotations.on.use" error for the given type and tree.
+   *
+   * @param type the type with invalid annotations
+   * @param p the tree where to report the error
+   */
+  protected void reportInvalidAnnotationsOnUse(final AnnotatedTypeMirror type, final Tree p) {
+    reportValidityResultOnUnannotatedType("annotations.on.use", type, p);
+  }
+
+  @Override
+  public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) {
+    if (visitedNodes.containsKey(type)) {
+      return visitedNodes.get(type);
+    }
+
+    final boolean skipChecks = checker.shouldSkipUses(type.getUnderlyingType().asElement());
+
+    if (checkTopLevelDeclaredOrPrimitiveType && !skipChecks) {
+      // Ensure that type use is a subtype of the element type
+      // isValidUse determines the erasure of the types.
+
+      Set<AnnotationMirror> bounds =
+          atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType());
+
+      AnnotatedDeclaredType elemType = type.deepCopy();
+      elemType.clearAnnotations();
+      elemType.addAnnotations(bounds);
+
+      if (!visitor.isValidUse(elemType, type, tree)) {
+        reportInvalidAnnotationsOnUse(type, tree);
+      }
+    }
+    // Set checkTopLevelDeclaredType to true, because the next time visitDeclared is called,
+    // the type isn't the top level, so always do the check.
+    checkTopLevelDeclaredOrPrimitiveType = true;
+
+    /*
+     * Try to reconstruct the ParameterizedTypeTree from the given tree.
+     * TODO: there has to be a nicer way to do this...
+     */
+    Pair<ParameterizedTypeTree, AnnotatedDeclaredType> p = extractParameterizedTypeTree(tree, type);
+    ParameterizedTypeTree typeArgTree = p.first;
+    type = p.second;
+
+    if (typeArgTree == null) {
+      return super.visitDeclared(type, tree);
+    } // else
+
+    // We put this here because we don't want to put it in visitedNodes before calling
+    // super (in the else branch) because that would cause the super implementation
+    // to detect that we've already visited type and to immediately return.
+    visitedNodes.put(type, null);
+
+    // We have a ParameterizedTypeTree -> visit it.
+
+    visitParameterizedType(type, typeArgTree);
+
+    /*
+     * Instead of calling super with the unchanged "tree", adapt the
+     * second argument to be the corresponding type argument tree. This
+     * ensures that the first and second parameter to this method always
+     * correspond. visitDeclared is the only method that had this
+     * problem.
+     */
+    List<? extends AnnotatedTypeMirror> tatypes = type.getTypeArguments();
+
+    if (tatypes == null) {
+      return null;
+    }
+
+    // May be zero for a "diamond" (inferred type args in constructor invocation).
+    int numTypeArgs = typeArgTree.getTypeArguments().size();
+    if (numTypeArgs != 0) {
+      // TODO: this should be an equality, but in
+      // http://buffalo.cs.washington.edu:8080/job/jdk6-daikon-typecheck/2061/console
+      // it failed with:
+      // daikon/Debug.java; message: size mismatch for type arguments:
+      // @NonNull Object and Class<?>
+      // but I didn't manage to reduce it to a test case.
+      assert tatypes.size() <= numTypeArgs || skipChecks
+          : "size mismatch for type arguments: " + type + " and " + typeArgTree;
+
+      for (int i = 0; i < tatypes.size(); ++i) {
+        scan(tatypes.get(i), typeArgTree.getTypeArguments().get(i));
+      }
+    }
+
+    // Don't call the super version, because it creates a mismatch
+    // between the first and second parameters.
+    // return super.visitDeclared(type, tree);
+
+    return null;
+  }
+
+  private Pair<ParameterizedTypeTree, AnnotatedDeclaredType> extractParameterizedTypeTree(
+      Tree tree, AnnotatedDeclaredType type) {
+    ParameterizedTypeTree typeargtree = null;
+
+    switch (tree.getKind()) {
+      case VARIABLE:
+        Tree lt = ((VariableTree) tree).getType();
+        if (lt instanceof ParameterizedTypeTree) {
+          typeargtree = (ParameterizedTypeTree) lt;
+        } else {
+          // System.out.println("Found a: " + lt);
+        }
+        break;
+      case PARAMETERIZED_TYPE:
+        typeargtree = (ParameterizedTypeTree) tree;
+        break;
+      case NEW_CLASS:
+        NewClassTree nct = (NewClassTree) tree;
+        ExpressionTree nctid = nct.getIdentifier();
+        if (nctid.getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
+          typeargtree = (ParameterizedTypeTree) nctid;
+          /*
+           * This is quite tricky... for anonymous class instantiations,
+           * the type at this point has no type arguments. By doing the
+           * following, we get the type arguments again.
+           */
+          type = (AnnotatedDeclaredType) atypeFactory.getAnnotatedType(typeargtree);
+        }
+        break;
+      case ANNOTATED_TYPE:
+        AnnotatedTypeTree tr = (AnnotatedTypeTree) tree;
+        ExpressionTree undtr = tr.getUnderlyingType();
+        if (undtr instanceof ParameterizedTypeTree) {
+          typeargtree = (ParameterizedTypeTree) undtr;
+        } else if (undtr instanceof IdentifierTree) {
+          // @Something D -> Nothing to do
+        } else {
+          // TODO: add more test cases to ensure that nested types are
+          // handled correctly,
+          // e.g. @Nullable() List<@Nullable Object>[][]
+          Pair<ParameterizedTypeTree, AnnotatedDeclaredType> p =
+              extractParameterizedTypeTree(undtr, type);
+          typeargtree = p.first;
+          type = p.second;
+        }
+        break;
+      case IDENTIFIER:
+      case ARRAY_TYPE:
+      case NEW_ARRAY:
+      case MEMBER_SELECT:
+      case UNBOUNDED_WILDCARD:
+      case EXTENDS_WILDCARD:
+      case SUPER_WILDCARD:
+      case TYPE_PARAMETER:
+        // Nothing to do.
+        break;
+      default:
+        // The parameterized type is the result of some expression tree.
+        // No need to do anything further.
+        break;
+    }
+
+    return Pair.of(typeargtree, type);
+  }
+
+  @Override
+  @SuppressWarnings("signature:argument") // PrimitiveType.toString(): @PrimitiveType
+  public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) {
+    if (!checkTopLevelDeclaredOrPrimitiveType
+        || checker.shouldSkipUses(type.getUnderlyingType().toString())) {
+      return super.visitPrimitive(type, tree);
+    }
+
+    if (!visitor.isValidUse(type, tree)) {
+      reportInvalidAnnotationsOnUse(type, tree);
+    }
+
+    return super.visitPrimitive(type, tree);
+  }
+
+  @Override
+  public Void visitArray(AnnotatedArrayType type, Tree tree) {
+    // TODO: is there already or add a helper method
+    // to determine the non-array component type
+    AnnotatedTypeMirror comp = type;
+    do {
+      comp = ((AnnotatedArrayType) comp).getComponentType();
+    } while (comp.getKind() == TypeKind.ARRAY);
+
+    if (comp.getKind() == TypeKind.DECLARED
+        && checker.shouldSkipUses(((AnnotatedDeclaredType) comp).getUnderlyingType().asElement())) {
+      return super.visitArray(type, tree);
+    }
+
+    if (!visitor.isValidUse(type, tree)) {
+      reportInvalidAnnotationsOnUse(type, tree);
+    }
+
+    return super.visitArray(type, tree);
+  }
+
+  /**
+   * Checks that the annotations on the type arguments supplied to a type or a method invocation are
+   * within the bounds of the type variables as declared, and issues the "type.argument" error if
+   * they are not.
+   *
+   * @param type the type to check
+   * @param tree the type's tree
+   */
+  protected Void visitParameterizedType(AnnotatedDeclaredType type, ParameterizedTypeTree tree) {
+    // System.out.printf("TypeValidator.visitParameterizedType: type: %s, tree: %s%n", type, tree);
+
+    if (TreeUtils.isDiamondTree(tree)) {
+      return null;
+    }
+
+    final TypeElement element = (TypeElement) type.getUnderlyingType().asElement();
+    if (checker.shouldSkipUses(element)) {
+      return null;
+    }
+
+    List<AnnotatedTypeParameterBounds> bounds = atypeFactory.typeVariablesFromUse(type, element);
+
+    visitor.checkTypeArguments(
+        tree,
+        bounds,
+        type.getTypeArguments(),
+        tree.getTypeArguments(),
+        element.getSimpleName(),
+        element.getTypeParameters());
+
+    return null;
+  }
+
+  @Override
+  public Void visitTypeVariable(AnnotatedTypeVariable type, Tree tree) {
+    if (visitedNodes.containsKey(type)) {
+      return visitedNodes.get(type);
+    }
+
+    if (type.isDeclaration() && !areBoundsValid(type.getUpperBound(), type.getLowerBound())) {
+      reportInvalidBounds(type, tree);
+    }
+
+    return super.visitTypeVariable(type, tree);
+  }
+
+  @Override
+  public Void visitWildcard(AnnotatedWildcardType type, Tree tree) {
+    if (visitedNodes.containsKey(type)) {
+      return visitedNodes.get(type);
+    }
+
+    if (!areBoundsValid(type.getExtendsBound(), type.getSuperBound())) {
+      reportInvalidBounds(type, tree);
+    }
+
+    return super.visitWildcard(type, tree);
+  }
+
+  /**
+   * Returns true if the effective annotations on the upperBound are above those on the lowerBound.
+   *
+   * @return true if the effective annotations on the upperBound are above those on the lowerBound
+   */
+  public boolean areBoundsValid(
+      final AnnotatedTypeMirror upperBound, final AnnotatedTypeMirror lowerBound) {
+    final QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
+    final Set<AnnotationMirror> upperBoundAnnos =
+        AnnotatedTypes.findEffectiveAnnotations(qualifierHierarchy, upperBound);
+    final Set<AnnotationMirror> lowerBoundAnnos =
+        AnnotatedTypes.findEffectiveAnnotations(qualifierHierarchy, lowerBound);
+
+    if (upperBoundAnnos.size() == lowerBoundAnnos.size()) {
+      return qualifierHierarchy.isSubtype(lowerBoundAnnos, upperBoundAnnos);
+    } // else
+    //  When upperBoundAnnos.size() != lowerBoundAnnos.size() one of the two bound types will
+    //  be reported as invalid.  Therefore, we do not do any other comparisons nor do we report
+    //  a bound
+
+    return true;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java
new file mode 100644
index 0000000..57869f2
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java
@@ -0,0 +1,4529 @@
+package org.checkerframework.common.basetype;
+
+import com.github.javaparser.ParseProblemException;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.printer.DefaultPrettyPrinter;
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.CatchTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.EnhancedForLoopTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.IntersectionTypeTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModifiersTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.ThrowTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.TypeParameterTree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.SourcePositions;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreeScanner;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCMemberReference;
+import com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind;
+import com.sun.tools.javac.tree.TreeInfo;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.Vector;
+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.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.Diagnostic.Kind;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.BooleanLiteralNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.JavaExpressionScanner;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.util.PurityChecker;
+import org.checkerframework.dataflow.util.PurityChecker.PurityResult;
+import org.checkerframework.dataflow.util.PurityUtils;
+import org.checkerframework.framework.ajava.AnnotationEqualityVisitor;
+import org.checkerframework.framework.ajava.ExpectedTreesVisitor;
+import org.checkerframework.framework.ajava.InsertAjavaAnnotations;
+import org.checkerframework.framework.ajava.JointVisitorWithDefaultAction;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.checkerframework.framework.flow.CFAbstractValue;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.framework.qual.Unused;
+import org.checkerframework.framework.source.DiagMessage;
+import org.checkerframework.framework.source.SourceVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.AnnotatedTypeParameterBounds;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.TypeHierarchy;
+import org.checkerframework.framework.type.VisitorState;
+import org.checkerframework.framework.type.poly.QualifierPolymorphism;
+import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.Contract;
+import org.checkerframework.framework.util.Contract.ConditionalPostcondition;
+import org.checkerframework.framework.util.Contract.Postcondition;
+import org.checkerframework.framework.util.Contract.Precondition;
+import org.checkerframework.framework.util.ContractsFromMethod;
+import org.checkerframework.framework.util.FieldInvariants;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.framework.util.JavaParserUtil;
+import org.checkerframework.framework.util.StringToJavaExpression;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.ArraysPlume;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * A {@link SourceVisitor} that performs assignment and pseudo-assignment checking, method
+ * invocation checking, and assignability checking.
+ *
+ * <p>This implementation uses the {@link AnnotatedTypeFactory} implementation provided by an
+ * associated {@link BaseTypeChecker}; its visitor methods will invoke this factory on parts of the
+ * AST to determine the "annotated type" of an expression. Then, the visitor methods will check the
+ * types in assignments and pseudo-assignments using {@link #commonAssignmentCheck}, which
+ * ultimately calls the {@link TypeHierarchy#isSubtype} method and reports errors that violate
+ * Java's rules of assignment.
+ *
+ * <p>Note that since this implementation only performs assignment and pseudo-assignment checking,
+ * other rules for custom type systems must be added in subclasses (e.g., dereference checking in
+ * the {@link org.checkerframework.checker.nullness.NullnessChecker} is implemented in the {@link
+ * org.checkerframework.checker.nullness.NullnessChecker}'s {@link TreeScanner#visitMemberSelect}
+ * method).
+ *
+ * <p>This implementation does the following checks:
+ *
+ * <ol>
+ *   <li><b>Assignment and Pseudo-Assignment Check</b>: It verifies that any assignment type-checks,
+ *       using {@code TypeHierarchy.isSubtype} method. This includes method invocation and method
+ *       overriding checks.
+ *   <li><b>Type Validity Check</b>: It verifies that any user-supplied type is a valid type, using
+ *       one of the {@code isValidUse} methods.
+ *   <li><b>(Re-)Assignability Check</b>: It verifies that any assignment is valid, using {@code
+ *       Checker.isAssignable} method.
+ * </ol>
+ *
+ * @see "JLS $4"
+ * @see TypeHierarchy#isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)
+ * @see AnnotatedTypeFactory
+ */
+/*
+ * Note how the handling of VisitorState is duplicated in AbstractFlow. In
+ * particular, the handling of the assignment context has to be done correctly
+ * in both classes. This is a pain and we should see how to handle this in the
+ * DFF version.
+ *
+ * TODO: missing assignment context: array initializer
+ * expressions should have the component type as context
+ */
+public class BaseTypeVisitor<Factory extends GenericAnnotatedTypeFactory<?, ?, ?, ?>>
+    extends SourceVisitor<Void, Void> {
+
+  /** The {@link BaseTypeChecker} for error reporting. */
+  protected final BaseTypeChecker checker;
+
+  /** The factory to use for obtaining "parsed" version of annotations. */
+  protected final Factory atypeFactory;
+
+  /** For obtaining line numbers in -Ashowchecks debugging output. */
+  protected final SourcePositions positions;
+
+  /** For storing visitor state. */
+  protected final VisitorState visitorState;
+
+  /** The element for java.util.Vector#copyInto. */
+  private final ExecutableElement vectorCopyInto;
+
+  /** The element for java.util.function.Function#apply. */
+  private final ExecutableElement functionApply;
+
+  /** The type of java.util.Vector. */
+  private final AnnotatedDeclaredType vectorType;
+
+  /** The @java.lang.annotation.Target annotation. */
+  protected final AnnotationMirror TARGET =
+      AnnotationBuilder.fromClass(
+          elements,
+          java.lang.annotation.Target.class,
+          AnnotationBuilder.elementNamesValues("value", new ElementType[0]));
+
+  /** The {@code value} element/field of the @java.lang.annotation.Target annotation. */
+  protected final ExecutableElement targetValueElement;
+  /** The {@code when} element/field of the @Unused annotation. */
+  protected final ExecutableElement unusedWhenElement;
+
+  /** True if "-Ashowchecks" was passed on the command line. */
+  private final boolean showchecks;
+
+  /**
+   * @param checker the type-checker associated with this visitor (for callbacks to {@link
+   *     TypeHierarchy#isSubtype})
+   */
+  public BaseTypeVisitor(BaseTypeChecker checker) {
+    this(checker, null);
+  }
+
+  /**
+   * @param checker the type-checker associated with this visitor
+   * @param typeFactory the type factory, or null. If null, this calls {@link #createTypeFactory}.
+   */
+  protected BaseTypeVisitor(BaseTypeChecker checker, Factory typeFactory) {
+    super(checker);
+
+    this.checker = checker;
+    this.atypeFactory = typeFactory == null ? createTypeFactory() : typeFactory;
+    this.positions = trees.getSourcePositions();
+    this.visitorState = atypeFactory.getVisitorState();
+    this.typeValidator = createTypeValidator();
+    ProcessingEnvironment env = checker.getProcessingEnvironment();
+    this.vectorCopyInto = TreeUtils.getMethod("java.util.Vector", "copyInto", 1, env);
+    this.functionApply = TreeUtils.getMethod("java.util.function.Function", "apply", 1, env);
+    this.vectorType =
+        atypeFactory.fromElement(elements.getTypeElement(Vector.class.getCanonicalName()));
+    targetValueElement = TreeUtils.getMethod(Target.class, "value", 0, env);
+    unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, env);
+    showchecks = checker.hasOption("showchecks");
+  }
+
+  /**
+   * Constructs an instance of the appropriate type factory for the implemented type system.
+   *
+   * <p>The default implementation uses the checker naming convention to create the appropriate type
+   * factory. If no factory is found, it returns {@link BaseAnnotatedTypeFactory}. It reflectively
+   * invokes the constructor that accepts this checker and compilation unit tree (in that order) as
+   * arguments.
+   *
+   * <p>Subclasses have to override this method to create the appropriate visitor if they do not
+   * follow the checker naming convention.
+   *
+   * @return the appropriate type factory
+   */
+  @SuppressWarnings("unchecked") // unchecked cast to type variable
+  protected Factory createTypeFactory() {
+    // Try to reflectively load the type factory.
+    Class<?> checkerClass = checker.getClass();
+    while (checkerClass != BaseTypeChecker.class) {
+      AnnotatedTypeFactory result =
+          BaseTypeChecker.invokeConstructorFor(
+              BaseTypeChecker.getRelatedClassName(checkerClass, "AnnotatedTypeFactory"),
+              new Class<?>[] {BaseTypeChecker.class},
+              new Object[] {checker});
+      if (result != null) {
+        return (Factory) result;
+      }
+      checkerClass = checkerClass.getSuperclass();
+    }
+    try {
+      return (Factory) new BaseAnnotatedTypeFactory(checker);
+    } catch (Throwable t) {
+      throw new BugInCF(
+          "Unexpected "
+              + t.getClass().getSimpleName()
+              + " when invoking BaseAnnotatedTypeFactory for checker "
+              + checker.getClass().getSimpleName(),
+          t);
+    }
+  }
+
+  public final Factory getTypeFactory() {
+    return atypeFactory;
+  }
+
+  /**
+   * A public variant of {@link #createTypeFactory}. Only use this if you know what you are doing.
+   *
+   * @return the appropriate type factory
+   */
+  public Factory createTypeFactoryPublic() {
+    return createTypeFactory();
+  }
+
+  // **********************************************************************
+  // Responsible for updating the factory for the location (for performance)
+  // **********************************************************************
+
+  @Override
+  public void setRoot(CompilationUnitTree root) {
+    atypeFactory.setRoot(root);
+    super.setRoot(root);
+    testJointJavacJavaParserVisitor();
+    testAnnotationInsertion();
+  }
+
+  @Override
+  public Void scan(@Nullable Tree tree, Void p) {
+    if (tree != null && getCurrentPath() != null) {
+      this.visitorState.setPath(new TreePath(getCurrentPath(), tree));
+    }
+    return super.scan(tree, p);
+  }
+
+  /**
+   * Test {@link org.checkerframework.framework.ajava.JointJavacJavaParserVisitor} if the checker
+   * has the "ajavaChecks" option.
+   *
+   * <p>Parse the current source file with JavaParser and check that the AST can be matched with the
+   * Tree prodoced by javac. Crash if not.
+   *
+   * <p>Subclasses may override this method to disable the test if even the option is provided.
+   */
+  protected void testJointJavacJavaParserVisitor() {
+    if (root == null || !checker.hasOption("ajavaChecks")) {
+      return;
+    }
+
+    Map<Tree, com.github.javaparser.ast.Node> treePairs = new HashMap<>();
+    try (InputStream reader = root.getSourceFile().openInputStream()) {
+      CompilationUnit javaParserRoot = JavaParserUtil.parseCompilationUnit(reader);
+      JavaParserUtil.concatenateAddedStringLiterals(javaParserRoot);
+      new JointVisitorWithDefaultAction() {
+        @Override
+        public void defaultAction(Tree javacTree, com.github.javaparser.ast.Node javaParserNode) {
+          treePairs.put(javacTree, javaParserNode);
+        }
+      }.visitCompilationUnit(root, javaParserRoot);
+      ExpectedTreesVisitor expectedTreesVisitor = new ExpectedTreesVisitor();
+      expectedTreesVisitor.visitCompilationUnit(root, null);
+      for (Tree expected : expectedTreesVisitor.getTrees()) {
+        if (!treePairs.containsKey(expected)) {
+          throw new BugInCF(
+              "Javac tree not matched to JavaParser node: %s, in file: %s",
+              expected, root.getSourceFile().getName());
+        }
+      }
+    } catch (IOException e) {
+      throw new BugInCF("Error reading Java source file", e);
+    }
+  }
+
+  /**
+   * Tests {@link org.checkerframework.framework.ajava.InsertAjavaAnnotations} if the checker has
+   * the "ajavaChecks" option.
+   *
+   * <ol>
+   *   <li>Parses the current file with JavaParser.
+   *   <li>Removes all annotations.
+   *   <li>Reinserts the annotations.
+   *   <li>Throws an exception if the ASTs are not the same.
+   * </ol>
+   *
+   * <p>Subclasses may override this method to disable the test even if the option is provided.
+   */
+  protected void testAnnotationInsertion() {
+    if (root == null || !checker.hasOption("ajavaChecks")) {
+      return;
+    }
+
+    CompilationUnit originalAst;
+    try (InputStream originalInputStream = root.getSourceFile().openInputStream()) {
+      originalAst = JavaParserUtil.parseCompilationUnit(originalInputStream);
+    } catch (IOException e) {
+      throw new BugInCF("Error while reading Java file: " + root.getSourceFile().toUri(), e);
+    }
+
+    CompilationUnit astWithoutAnnotations = originalAst.clone();
+    JavaParserUtil.clearAnnotations(astWithoutAnnotations);
+    String withoutAnnotations = new DefaultPrettyPrinter().print(astWithoutAnnotations);
+
+    String withAnnotations;
+    try (InputStream annotationInputStream = root.getSourceFile().openInputStream()) {
+      // This check only runs on files from the Checker Framework test suite, which should all use
+      // UNIX line separators. Using System.lineSeparator instead of "\n" could cause the test to
+      // fail on Mac or Windows.
+      withAnnotations =
+          new InsertAjavaAnnotations(elements)
+              .insertAnnotations(annotationInputStream, withoutAnnotations, "\n");
+    } catch (IOException e) {
+      throw new BugInCF("Error while reading Java file: " + root.getSourceFile().toUri(), e);
+    }
+
+    CompilationUnit modifiedAst = null;
+    try {
+      modifiedAst = JavaParserUtil.parseCompilationUnit(withAnnotations);
+    } catch (ParseProblemException e) {
+      throw new BugInCF("Failed to parse annotation insertion:\n" + withAnnotations, e);
+    }
+
+    AnnotationEqualityVisitor visitor = new AnnotationEqualityVisitor();
+    originalAst.accept(visitor, modifiedAst);
+    if (!visitor.getAnnotationsMatch()) {
+      throw new BugInCF(
+          "Reinserting annotations produced different AST.\n"
+              + "Original node: "
+              + visitor.getMismatchedNode1()
+              + "\n"
+              + "Node with annotations re-inserted: "
+              + visitor.getMismatchedNode2()
+              + "\n"
+              + "Original annotations: "
+              + visitor.getMismatchedNode1().getAnnotations()
+              + "\n"
+              + "Re-inserted annotations: "
+              + visitor.getMismatchedNode2().getAnnotations()
+              + "\n"
+              + "Original AST:\n"
+              + originalAst
+              + "\n"
+              + "Ast with annotations re-inserted: "
+              + modifiedAst);
+    }
+  }
+
+  /**
+   * Type-check classTree and skips classes specified by the skipDef option. Subclasses should
+   * override {@link #processClassTree(ClassTree)} instead of this method.
+   *
+   * @param classTree class to check
+   * @param p null
+   * @return null
+   */
+  @Override
+  public final Void visitClass(ClassTree classTree, Void p) {
+    if (checker.shouldSkipDefs(classTree)) {
+      // Not "return super.visitClass(classTree, p);" because that would recursively call visitors
+      // on subtrees; we want to skip the class entirely.
+      return null;
+    }
+    atypeFactory.preProcessClassTree(classTree);
+
+    TreePath preTreePath = visitorState.getPath();
+    AnnotatedDeclaredType preACT = visitorState.getClassType();
+    ClassTree preCT = visitorState.getClassTree();
+    AnnotatedDeclaredType preAMT = visitorState.getMethodReceiver();
+    MethodTree preMT = visitorState.getMethodTree();
+    Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = visitorState.getAssignmentContext();
+
+    // Don't use atypeFactory.getPath, because that depends on the visitorState path.
+    visitorState.setPath(TreePath.getPath(root, classTree));
+    visitorState.setClassType(
+        atypeFactory.getAnnotatedType(TreeUtils.elementFromDeclaration(classTree)));
+    visitorState.setClassTree(classTree);
+    visitorState.setMethodReceiver(null);
+    visitorState.setMethodTree(null);
+    visitorState.setAssignmentContext(null);
+
+    try {
+      processClassTree(classTree);
+      atypeFactory.postProcessClassTree(classTree);
+    } finally {
+      visitorState.setPath(preTreePath);
+      visitorState.setClassType(preACT);
+      visitorState.setClassTree(preCT);
+      visitorState.setMethodReceiver(preAMT);
+      visitorState.setMethodTree(preMT);
+      visitorState.setAssignmentContext(preAssignmentContext);
+    }
+    return null;
+  }
+
+  /**
+   * Type-check classTree. Subclasses should override this method instead of {@link
+   * #visitClass(ClassTree, Void)}.
+   *
+   * @param classTree class to check
+   */
+  public void processClassTree(ClassTree classTree) {
+    checkFieldInvariantDeclarations(classTree);
+    if (!TreeUtils.hasExplicitConstructor(classTree)) {
+      checkDefaultConstructor(classTree);
+    }
+
+    AnnotatedDeclaredType classType = atypeFactory.getAnnotatedType(classTree);
+    atypeFactory.getDependentTypesHelper().checkClassForErrorExpressions(classTree, classType);
+    validateType(classTree, classType);
+
+    Tree ext = classTree.getExtendsClause();
+    if (ext != null) {
+      validateTypeOf(ext);
+    }
+
+    List<? extends Tree> impls = classTree.getImplementsClause();
+    if (impls != null) {
+      for (Tree im : impls) {
+        validateTypeOf(im);
+      }
+    }
+
+    checkForPolymorphicQualifiers(classTree);
+
+    checkExtendsImplements(classTree);
+
+    checkQualifierParameter(classTree);
+
+    super.visitClass(classTree, null);
+  }
+
+  /**
+   * A TreeScanner that issues an "invalid.polymorphic.qualifier" error for each {@link
+   * AnnotationTree} that is a polymorphic qualifier. The second parameter is added to the error
+   * message and should explain the location.
+   */
+  private final TreeScanner<Void, String> polyTreeScanner =
+      new TreeScanner<Void, String>() {
+        @Override
+        public Void visitAnnotation(AnnotationTree annoTree, String location) {
+          QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
+          AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(annoTree);
+          if (atypeFactory.isSupportedQualifier(anno)
+              && qualifierHierarchy.isPolymorphicQualifier(anno)) {
+            checker.reportError(annoTree, "invalid.polymorphic.qualifier", anno, location);
+          }
+          return super.visitAnnotation(annoTree, location);
+        }
+      };
+
+  /**
+   * Issues an "invalid.polymorphic.qualifier" error for all polymorphic annotations written on the
+   * class declaration.
+   *
+   * @param classTree the class to check
+   */
+  protected void checkForPolymorphicQualifiers(ClassTree classTree) {
+    if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) {
+      // Anonymous class can have polymorphic annotations, so don't check them.
+      return;
+    }
+    classTree.getModifiers().accept(polyTreeScanner, "in a class declaration");
+    if (classTree.getExtendsClause() != null) {
+      classTree.getExtendsClause().accept(polyTreeScanner, "in a class declaration");
+    }
+    for (Tree tree : classTree.getImplementsClause()) {
+      tree.accept(polyTreeScanner, "in a class declaration");
+    }
+    for (Tree tree : classTree.getTypeParameters()) {
+      tree.accept(polyTreeScanner, "in a class declaration");
+    }
+  }
+
+  /**
+   * Issues an "invalid.polymorphic.qualifier" error for all polymorphic annotations written on the
+   * type parameters declaration.
+   *
+   * @param typeParameterTrees the type parameters to check
+   */
+  protected void checkForPolymorphicQualifiers(
+      List<? extends TypeParameterTree> typeParameterTrees) {
+    for (Tree tree : typeParameterTrees) {
+      tree.accept(polyTreeScanner, "in a type parameter");
+    }
+  }
+
+  /**
+   * Issues an error if {@code classTree} has polymorphic fields but is not annotated with
+   * {@code @HasQualifierParameter}. Always issue a warning if the type of a static field is
+   * annotated with a polymorphic qualifier.
+   *
+   * <p>Issues an error if {@code classTree} extends or implements a class/interface that has a
+   * qualifier parameter, but this class does not.
+   *
+   * @param classTree the ClassTree to check for polymorphic fields
+   */
+  protected void checkQualifierParameter(ClassTree classTree) {
+    // Set of polymorphic qualifiers for hierarchies that do not have a qualifier parameter and
+    // therefor cannot appear on a field.
+    Set<AnnotationMirror> illegalOnFieldsPolyQual = AnnotationUtils.createAnnotationSet();
+    // Set of polymorphic annotations for all hierarchies
+    Set<AnnotationMirror> polys = AnnotationUtils.createAnnotationSet();
+    TypeElement classElement = TreeUtils.elementFromDeclaration(classTree);
+    for (AnnotationMirror top : atypeFactory.getQualifierHierarchy().getTopAnnotations()) {
+      AnnotationMirror poly = atypeFactory.getQualifierHierarchy().getPolymorphicAnnotation(top);
+      if (poly != null) {
+        polys.add(poly);
+      }
+      // else {
+      // If there is no polymorphic qualifier in the hierarchy, it could still have a
+      // @HasQualifierParameter that must be checked.
+      // }
+
+      if (atypeFactory.hasExplicitQualifierParameterInHierarchy(classElement, top)
+          && atypeFactory.hasExplicitNoQualifierParameterInHierarchy(classElement, top)) {
+        checker.reportError(classTree, "conflicting.qual.param", top);
+      }
+
+      if (atypeFactory.hasQualifierParameterInHierarchy(classElement, top)) {
+        continue;
+      }
+
+      if (poly != null) {
+        illegalOnFieldsPolyQual.add(poly);
+      }
+      Element extendsEle = TypesUtils.getTypeElement(classElement.getSuperclass());
+      if (extendsEle != null && atypeFactory.hasQualifierParameterInHierarchy(extendsEle, top)) {
+        checker.reportError(classTree, "missing.has.qual.param", top);
+      } else {
+        for (TypeMirror interfaceType : classElement.getInterfaces()) {
+          Element interfaceEle = TypesUtils.getTypeElement(interfaceType);
+          if (atypeFactory.hasQualifierParameterInHierarchy(interfaceEle, top)) {
+            checker.reportError(classTree, "missing.has.qual.param", top);
+            break; // only issue error once
+          }
+        }
+      }
+    }
+
+    for (Tree mem : classTree.getMembers()) {
+      if (mem.getKind() == Tree.Kind.VARIABLE) {
+        AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(mem);
+        List<DiagMessage> hasIllegalPoly;
+        if (ElementUtils.isStatic(TreeUtils.elementFromDeclaration((VariableTree) mem))) {
+          // A polymorphic qualifier is not allowed on a static field even if the class
+          // has a qualifier parameter.
+          hasIllegalPoly = polyScanner.visit(fieldType, polys);
+        } else {
+          hasIllegalPoly = polyScanner.visit(fieldType, illegalOnFieldsPolyQual);
+        }
+        for (DiagMessage dm : hasIllegalPoly) {
+          checker.report(mem, dm);
+        }
+      }
+    }
+  }
+
+  /**
+   * A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a use
+   * of one of the polymorphic qualifiers.
+   */
+  private final PolyTypeScanner polyScanner = new PolyTypeScanner();
+
+  /**
+   * A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a use
+   * of one of the polymorphic qualifiers.
+   */
+  static class PolyTypeScanner
+      extends SimpleAnnotatedTypeScanner<List<DiagMessage>, Set<AnnotationMirror>> {
+
+    /** Create PolyTypeScanner. */
+    private PolyTypeScanner() {
+      super(DiagMessage::mergeLists, Collections.emptyList());
+    }
+
+    @Override
+    protected List<DiagMessage> defaultAction(
+        AnnotatedTypeMirror type, Set<AnnotationMirror> polys) {
+      if (type == null) {
+        return Collections.emptyList();
+      }
+
+      for (AnnotationMirror poly : polys) {
+        if (type.hasAnnotationRelaxed(poly)) {
+          return Collections.singletonList(
+              new DiagMessage(Kind.ERROR, "invalid.polymorphic.qualifier.use", poly));
+        }
+      }
+      return Collections.emptyList();
+    }
+  }
+
+  /**
+   * If "@B class Y extends @A X {}", then enforce that @B must be a subtype of @A.
+   *
+   * <p>Also validate the types of the extends and implements clauses.
+   *
+   * @param classTree class tree to check
+   */
+  protected void checkExtendsImplements(ClassTree classTree) {
+    if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) {
+      // Don't check extends clause on anonymous classes.
+      return;
+    }
+    Set<AnnotationMirror> classBounds =
+        atypeFactory.getTypeDeclarationBounds(TreeUtils.typeOf(classTree));
+    QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
+    // If "@B class Y extends @A X {}", then enforce that @B must be a subtype of @A.
+    // classTree.getExtendsClause() is null when there is no explicitly-written extends clause,
+    // as in "class X {}". This is equivalent to writing "class X extends @Top Object {}", so
+    // there is no need to do any subtype checking.
+    if (classTree.getExtendsClause() != null) {
+      Set<AnnotationMirror> extendsAnnos =
+          atypeFactory.getTypeOfExtendsImplements(classTree.getExtendsClause()).getAnnotations();
+      for (AnnotationMirror classAnno : classBounds) {
+        AnnotationMirror extendsAnno =
+            qualifierHierarchy.findAnnotationInSameHierarchy(extendsAnnos, classAnno);
+        if (!qualifierHierarchy.isSubtype(classAnno, extendsAnno)) {
+          checker.reportError(
+              classTree.getExtendsClause(),
+              "declaration.inconsistent.with.extends.clause",
+              classAnno,
+              extendsAnno);
+        }
+      }
+    }
+    // Do the same check as above for implements clauses.
+    for (Tree implementsClause : classTree.getImplementsClause()) {
+      Set<AnnotationMirror> implementsClauseAnnos =
+          atypeFactory.getTypeOfExtendsImplements(implementsClause).getAnnotations();
+
+      for (AnnotationMirror classAnno : classBounds) {
+        AnnotationMirror implementsAnno =
+            qualifierHierarchy.findAnnotationInSameHierarchy(implementsClauseAnnos, classAnno);
+        if (!qualifierHierarchy.isSubtype(classAnno, implementsAnno)) {
+          checker.reportError(
+              implementsClause,
+              "declaration.inconsistent.with.implements.clause",
+              classAnno,
+              implementsAnno);
+        }
+      }
+    }
+  }
+
+  /**
+   * Check that the field invariant declaration annotations meet the following requirements:
+   *
+   * <ol>
+   *   <!-- The item numbering is referred to in the body of the method.-->
+   *   <li value="1">If the superclass of {@code classTree} has a field invariant, then the field
+   *       invariant for {@code classTree} must include all the fields in the superclass invariant
+   *       and those fields' annotations must be a subtype (or equal) to the annotations for those
+   *       fields in the superclass.
+   *   <li value="2">The fields in the invariant must be a.) final and b.) declared in a superclass
+   *       of {@code classTree}.
+   *   <li value="3">The qualifier for each field must be a subtype of the annotation on the
+   *       declaration of that field.
+   *   <li value="4">The field invariant has an equal number of fields and qualifiers, or it has one
+   *       qualifier and at least one field.
+   * </ol>
+   *
+   * @param classTree class that might have a field invariant
+   * @checker_framework.manual #field-invariants Field invariants
+   */
+  protected void checkFieldInvariantDeclarations(ClassTree classTree) {
+    TypeElement elt = TreeUtils.elementFromDeclaration(classTree);
+    FieldInvariants invariants = atypeFactory.getFieldInvariants(elt);
+    if (invariants == null) {
+      // No invariants to check
+      return;
+    }
+
+    // Where to issue an error, if any.
+    Tree errorTree =
+        atypeFactory.getFieldInvariantAnnotationTree(classTree.getModifiers().getAnnotations());
+    if (errorTree == null) {
+      // If the annotation was inherited, then there is no annotation tree, so issue the
+      // error on the class.
+      errorTree = classTree;
+    }
+
+    // Checks #4 (see method Javadoc)
+    if (!invariants.isWellFormed()) {
+      checker.reportError(errorTree, "field.invariant.not.wellformed");
+      return;
+    }
+
+    TypeMirror superClass = elt.getSuperclass();
+    List<String> fieldsNotFound = new ArrayList<>(invariants.getFields());
+    Set<VariableElement> fieldElts =
+        ElementUtils.findFieldsInTypeOrSuperType(superClass, fieldsNotFound);
+
+    // Checks that fields are declared in super class. (#2b)
+    if (!fieldsNotFound.isEmpty()) {
+      String notFoundString = String.join(", ", fieldsNotFound);
+      checker.reportError(errorTree, "field.invariant.not.found", notFoundString);
+    }
+
+    FieldInvariants superInvar =
+        atypeFactory.getFieldInvariants(TypesUtils.getTypeElement(superClass));
+    if (superInvar != null) {
+      // Checks #3 (see method Javadoc)
+      DiagMessage superError = invariants.isSuperInvariant(superInvar, atypeFactory);
+      if (superError != null) {
+        checker.report(errorTree, superError);
+      }
+    }
+
+    List<String> notFinal = new ArrayList<>();
+    for (VariableElement field : fieldElts) {
+      String fieldName = field.getSimpleName().toString();
+      if (!ElementUtils.isFinal(field)) {
+        notFinal.add(fieldName);
+      }
+      AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(field);
+
+      List<AnnotationMirror> annos = invariants.getQualifiersFor(field.getSimpleName());
+      for (AnnotationMirror invariantAnno : annos) {
+        AnnotationMirror declaredAnno = type.getEffectiveAnnotationInHierarchy(invariantAnno);
+        if (declaredAnno == null) {
+          // invariant anno isn't in this hierarchy
+          continue;
+        }
+
+        if (!atypeFactory.getQualifierHierarchy().isSubtype(invariantAnno, declaredAnno)) {
+          // Checks #3
+          checker.reportError(
+              errorTree, "field.invariant.not.subtype", fieldName, invariantAnno, declaredAnno);
+        }
+      }
+    }
+
+    // Checks #2a
+    if (!notFinal.isEmpty()) {
+      String notFinalString = String.join(", ", notFinal);
+      checker.reportError(errorTree, "field.invariant.not.final", notFinalString);
+    }
+  }
+
+  protected void checkDefaultConstructor(ClassTree node) {}
+
+  /**
+   * Performs pseudo-assignment check: checks that the method obeys override and subtype rules to
+   * all overridden methods.
+   *
+   * <p>The override rule specifies that a method, m1, may override a method m2 only if:
+   *
+   * <ul>
+   *   <li>m1 return type is a subtype of m2
+   *   <li>m1 receiver type is a supertype of m2
+   *   <li>m1 parameters are supertypes of corresponding m2 parameters
+   * </ul>
+   *
+   * Also, it issues a "missing.this" error for static method annotated receivers.
+   */
+  @Override
+  public Void visitMethod(MethodTree node, Void p) {
+    // We copy the result from getAnnotatedType to ensure that circular types (e.g. K extends
+    // Comparable<K>) are represented by circular AnnotatedTypeMirrors, which avoids problems with
+    // later checks.
+    // TODO: Find a cleaner way to ensure circular AnnotatedTypeMirrors.
+    AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(node).deepCopy();
+    AnnotatedDeclaredType preMRT = visitorState.getMethodReceiver();
+    MethodTree preMT = visitorState.getMethodTree();
+    visitorState.setMethodReceiver(methodType.getReceiverType());
+    visitorState.setMethodTree(node);
+    ExecutableElement methodElement = TreeUtils.elementFromDeclaration(node);
+
+    warnAboutTypeAnnotationsTooEarly(node, node.getModifiers());
+
+    if (node.getReturnType() != null) {
+      visitAnnotatedType(node.getModifiers().getAnnotations(), node.getReturnType());
+    }
+
+    try {
+      if (TreeUtils.isAnonymousConstructor(node)) {
+        // We shouldn't dig deeper
+        return null;
+      }
+
+      if (TreeUtils.isConstructor(node)) {
+        checkConstructorResult(methodType, methodElement);
+      }
+
+      checkPurity(node);
+
+      // Passing the whole method/constructor validates the return type
+      validateTypeOf(node);
+
+      // Validate types in throws clauses
+      for (ExpressionTree thr : node.getThrows()) {
+        validateTypeOf(thr);
+      }
+
+      atypeFactory.getDependentTypesHelper().checkMethodForErrorExpressions(node, methodType);
+
+      // Check method overrides
+      AnnotatedDeclaredType enclosingType =
+          (AnnotatedDeclaredType)
+              atypeFactory.getAnnotatedType(methodElement.getEnclosingElement());
+
+      // Find which methods this method overrides
+      Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
+          AnnotatedTypes.overriddenMethods(elements, atypeFactory, methodElement);
+      for (Map.Entry<AnnotatedDeclaredType, ExecutableElement> pair :
+          overriddenMethods.entrySet()) {
+        AnnotatedDeclaredType overriddenType = pair.getKey();
+        ExecutableElement overriddenMethodElt = pair.getValue();
+        AnnotatedExecutableType overriddenMethodType =
+            AnnotatedTypes.asMemberOf(types, atypeFactory, overriddenType, overriddenMethodElt);
+        if (!checkOverride(node, enclosingType, overriddenMethodType, overriddenType)) {
+          // Stop at the first mismatch; this makes a difference only if
+          // -Awarns is passed, in which case multiple warnings might be raised on
+          // the same method, not adding any value. See Issue 373.
+          break;
+        }
+      }
+
+      // Check well-formedness of pre/postcondition
+      boolean abstractMethod =
+          methodElement.getModifiers().contains(Modifier.ABSTRACT)
+              || methodElement.getModifiers().contains(Modifier.NATIVE);
+
+      List<String> formalParamNames =
+          CollectionsPlume.mapList(
+              (VariableTree param) -> param.getName().toString(), node.getParameters());
+      checkContractsAtMethodDeclaration(node, methodElement, formalParamNames, abstractMethod);
+
+      // Infer postconditions
+      if (atypeFactory.getWholeProgramInference() != null) {
+        assert ElementUtils.isElementFromSourceCode(methodElement);
+
+        // TODO: Infer conditional postconditions too.
+        CFAbstractStore<?, ?> store = atypeFactory.getRegularExitStore(node);
+        // The store is null if the method has no normal exit, for example if its body is a
+        // throw statement.
+        if (store != null) {
+          atypeFactory
+              .getWholeProgramInference()
+              .updateContracts(Analysis.BeforeOrAfter.AFTER, methodElement, store);
+        }
+      }
+
+      checkForPolymorphicQualifiers(node.getTypeParameters());
+
+      return super.visitMethod(node, p);
+    } finally {
+      visitorState.setMethodReceiver(preMRT);
+      visitorState.setMethodTree(preMT);
+    }
+  }
+
+  /**
+   * Check method purity if needed. Note that overriding rules are checked as part of {@link
+   * #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedExecutableType,
+   * AnnotatedTypeMirror.AnnotatedDeclaredType, AnnotatedTypeMirror.AnnotatedExecutableType,
+   * AnnotatedTypeMirror.AnnotatedDeclaredType)}.
+   *
+   * @param node the method tree to check
+   */
+  protected void checkPurity(MethodTree node) {
+    if (!checker.hasOption("checkPurityAnnotations")) {
+      return;
+    }
+
+    boolean anyPurityAnnotation = PurityUtils.hasPurityAnnotation(atypeFactory, node);
+    boolean suggestPureMethods = checker.hasOption("suggestPureMethods");
+    if (!anyPurityAnnotation && !suggestPureMethods) {
+      return;
+    }
+
+    // check "no" purity
+    EnumSet<Pure.Kind> kinds = PurityUtils.getPurityKinds(atypeFactory, node);
+    // @Deterministic makes no sense for a void method or constructor
+    boolean isDeterministic = kinds.contains(Pure.Kind.DETERMINISTIC);
+    if (isDeterministic) {
+      if (TreeUtils.isConstructor(node)) {
+        checker.reportWarning(node, "purity.deterministic.constructor");
+      } else if (TreeUtils.typeOf(node.getReturnType()).getKind() == TypeKind.VOID) {
+        checker.reportWarning(node, "purity.deterministic.void.method");
+      }
+    }
+
+    TreePath body = atypeFactory.getPath(node.getBody());
+    PurityResult r;
+    if (body == null) {
+      r = new PurityResult();
+    } else {
+      r =
+          PurityChecker.checkPurity(
+              body,
+              atypeFactory,
+              checker.hasOption("assumeSideEffectFree") || checker.hasOption("assumePure"),
+              checker.hasOption("assumeDeterministic") || checker.hasOption("assumePure"));
+    }
+    if (!r.isPure(kinds)) {
+      reportPurityErrors(r, node, kinds);
+    }
+
+    if (suggestPureMethods && !TreeUtils.isSynthetic(node)) {
+      // Issue a warning if the method is pure, but not annotated as such.
+      EnumSet<Pure.Kind> additionalKinds = r.getKinds().clone();
+      additionalKinds.removeAll(kinds);
+      if (TreeUtils.isConstructor(node)) {
+        additionalKinds.remove(Pure.Kind.DETERMINISTIC);
+      }
+      if (!additionalKinds.isEmpty()) {
+        if (additionalKinds.size() == 2) {
+          checker.reportWarning(node, "purity.more.pure", node.getName());
+        } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) {
+          checker.reportWarning(node, "purity.more.sideeffectfree", node.getName());
+        } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) {
+          checker.reportWarning(node, "purity.more.deterministic", node.getName());
+        } else {
+          assert false : "BaseTypeVisitor reached undesirable state";
+        }
+      }
+    }
+  }
+
+  /**
+   * Issue a warning if the result type of the constructor is not top. If it is a supertype of the
+   * class, then a conflicting.annos error will also be issued by {@link
+   * #isValidUse(AnnotatedTypeMirror.AnnotatedDeclaredType,AnnotatedTypeMirror.AnnotatedDeclaredType,Tree)}.
+   *
+   * @param constructorType AnnotatedExecutableType for the constructor
+   * @param constructorElement element that declares the constructor
+   */
+  protected void checkConstructorResult(
+      AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {
+    QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
+    Set<AnnotationMirror> constructorAnnotations = constructorType.getReturnType().getAnnotations();
+    Set<? extends AnnotationMirror> tops = qualifierHierarchy.getTopAnnotations();
+
+    for (AnnotationMirror top : tops) {
+      AnnotationMirror constructorAnno =
+          qualifierHierarchy.findAnnotationInHierarchy(constructorAnnotations, top);
+      if (!qualifierHierarchy.isSubtype(top, constructorAnno)) {
+        checker.reportWarning(
+            constructorElement, "inconsistent.constructor.type", constructorAnno, top);
+      }
+    }
+  }
+
+  /**
+   * Reports errors found during purity checking.
+   *
+   * @param result whether the method is deterministic and/or side-effect-free
+   * @param node the method
+   * @param expectedKinds the expected purity for the method
+   */
+  protected void reportPurityErrors(
+      PurityResult result, MethodTree node, EnumSet<Pure.Kind> expectedKinds) {
+    assert !result.isPure(expectedKinds);
+    EnumSet<Pure.Kind> violations = EnumSet.copyOf(expectedKinds);
+    violations.removeAll(result.getKinds());
+    if (violations.contains(Pure.Kind.DETERMINISTIC)
+        || violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) {
+      String msgKeyPrefix;
+      if (!violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) {
+        msgKeyPrefix = "purity.not.deterministic.";
+      } else if (!violations.contains(Pure.Kind.DETERMINISTIC)) {
+        msgKeyPrefix = "purity.not.sideeffectfree.";
+      } else {
+        msgKeyPrefix = "purity.not.deterministic.not.sideeffectfree.";
+      }
+      for (Pair<Tree, String> r : result.getNotBothReasons()) {
+        reportPurityError(msgKeyPrefix, r);
+      }
+      if (violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) {
+        for (Pair<Tree, String> r : result.getNotSEFreeReasons()) {
+          reportPurityError("purity.not.sideeffectfree.", r);
+        }
+      }
+      if (violations.contains(Pure.Kind.DETERMINISTIC)) {
+        for (Pair<Tree, String> r : result.getNotDetReasons()) {
+          reportPurityError("purity.not.deterministic.", r);
+        }
+      }
+    }
+  }
+
+  /**
+   * Reports a single purity error.
+   *
+   * @param msgKeyPrefix the prefix of the message key to use when reporting
+   * @param r the result to report
+   */
+  private void reportPurityError(String msgKeyPrefix, Pair<Tree, String> r) {
+    String reason = r.second;
+    @SuppressWarnings("compilermessages")
+    @CompilerMessageKey String msgKey = msgKeyPrefix + reason;
+    if (reason.equals("call")) {
+      if (r.first.getKind() == Tree.Kind.METHOD_INVOCATION) {
+        MethodInvocationTree mitree = (MethodInvocationTree) r.first;
+        checker.reportError(r.first, msgKey, mitree.getMethodSelect());
+      } else {
+        NewClassTree nctree = (NewClassTree) r.first;
+        checker.reportError(r.first, msgKey, nctree.getIdentifier());
+      }
+    } else {
+      checker.reportError(r.first, msgKey);
+    }
+  }
+
+  /**
+   * Check the contracts written on a method declaration. Ensures that the postconditions hold on
+   * exit, and that the contracts are well-formed.
+   *
+   * @param methodTree the method declaration
+   * @param methodElement the method element
+   * @param formalParamNames the formal parameter names
+   * @param abstractMethod whether the method is abstract
+   */
+  private void checkContractsAtMethodDeclaration(
+      MethodTree methodTree,
+      ExecutableElement methodElement,
+      List<String> formalParamNames,
+      boolean abstractMethod) {
+    Set<Contract> contracts = atypeFactory.getContractsFromMethod().getContracts(methodElement);
+
+    if (contracts.isEmpty()) {
+      return;
+    }
+    StringToJavaExpression stringToJavaExpr =
+        stringExpr -> StringToJavaExpression.atMethodBody(stringExpr, methodTree, checker);
+    for (Contract contract : contracts) {
+      String expressionString = contract.expressionString;
+      AnnotationMirror annotation =
+          contract.viewpointAdaptDependentTypeAnnotation(
+              atypeFactory, stringToJavaExpr, methodTree);
+
+      JavaExpression exprJe;
+      try {
+        exprJe = StringToJavaExpression.atMethodBody(expressionString, methodTree, checker);
+      } catch (JavaExpressionParseException e) {
+        DiagMessage diagMessage = e.getDiagMessage();
+        if (diagMessage.getMessageKey().equals("flowexpr.parse.error")) {
+          String s =
+              String.format(
+                  "'%s' in the %s %s on the declaration of method '%s': ",
+                  expressionString,
+                  contract.kind.errorKey,
+                  contract.contractAnnotation.getAnnotationType().asElement().getSimpleName(),
+                  methodTree.getName().toString());
+          checker.reportError(methodTree, "flowexpr.parse.error", s + diagMessage.getArgs()[0]);
+        } else {
+          checker.report(methodTree, e.getDiagMessage());
+        }
+        continue;
+      }
+      if (!CFAbstractStore.canInsertJavaExpression(exprJe)) {
+        checker.reportError(methodTree, "flowexpr.parse.error", expressionString);
+        continue;
+      }
+      if (!abstractMethod && contract.kind != Contract.Kind.PRECONDITION) {
+        // Check the contract, which is a postcondition.
+        // Preconditions are checked at method invocations, not declarations.
+
+        switch (contract.kind) {
+          case POSTCONDITION:
+            checkPostcondition(methodTree, annotation, exprJe);
+            break;
+          case CONDITIONALPOSTCONDITION:
+            checkConditionalPostcondition(
+                methodTree, annotation, exprJe, ((ConditionalPostcondition) contract).resultValue);
+            break;
+          default:
+            throw new BugInCF("Impossible: " + contract.kind);
+        }
+      }
+
+      if (formalParamNames != null && formalParamNames.contains(expressionString)) {
+        String locationOfExpression =
+            contract.kind.errorKey
+                + " "
+                + contract.contractAnnotation.getAnnotationType().asElement().getSimpleName()
+                + " on the declaration";
+        checker.reportWarning(
+            methodTree,
+            "expression.parameter.name.shadows.field",
+            locationOfExpression,
+            methodTree.getName().toString(),
+            expressionString,
+            expressionString,
+            formalParamNames.indexOf(expressionString) + 1);
+      }
+
+      checkParametersAreEffectivelyFinal(methodTree, exprJe);
+    }
+  }
+
+  /**
+   * Scans a {@link JavaExpression} and adds all the parameters in the {@code JavaExpression} to the
+   * passed set.
+   */
+  private final JavaExpressionScanner<Set<Element>> findParameters =
+      new JavaExpressionScanner<Set<Element>>() {
+        @Override
+        protected Void visitLocalVariable(LocalVariable localVarExpr, Set<Element> parameters) {
+          if (localVarExpr.getElement().getKind() == ElementKind.PARAMETER) {
+            parameters.add(localVarExpr.getElement());
+          }
+          return super.visitLocalVariable(localVarExpr, parameters);
+        }
+      };
+  /**
+   * Check that the parameters used in {@code javaExpression} are effectively final for method
+   * {@code method}.
+   *
+   * @param methodDeclTree a method declaration
+   * @param javaExpression a Java expression
+   */
+  private void checkParametersAreEffectivelyFinal(
+      MethodTree methodDeclTree, JavaExpression javaExpression) {
+    // check that all parameters used in the expression are
+    // effectively final, so that they cannot be modified
+    Set<Element> parameters = new HashSet<>(1);
+    findParameters.scan(javaExpression, parameters);
+    for (Element parameter : parameters) {
+      if (!ElementUtils.isEffectivelyFinal(parameter)) {
+        checker.reportError(
+            methodDeclTree,
+            "flowexpr.parameter.not.final",
+            parameter.getSimpleName(),
+            javaExpression);
+      }
+    }
+  }
+
+  /**
+   * Check that the expression's type is annotated with {@code annotation} at the regular exit
+   * store.
+   *
+   * @param methodTree declaration of the method
+   * @param annotation expression's type must have this annotation
+   * @param expression the expression that must have an annotation
+   */
+  protected void checkPostcondition(
+      MethodTree methodTree, AnnotationMirror annotation, JavaExpression expression) {
+    CFAbstractStore<?, ?> exitStore = atypeFactory.getRegularExitStore(methodTree);
+    if (exitStore == null) {
+      // If there is no regular exitStore, then the method cannot reach the regular exit and there
+      // is no need to check anything.
+    } else {
+      CFAbstractValue<?> value = exitStore.getValue(expression);
+      AnnotationMirror inferredAnno = null;
+      if (value != null) {
+        QualifierHierarchy hierarchy = atypeFactory.getQualifierHierarchy();
+        Set<AnnotationMirror> annos = value.getAnnotations();
+        inferredAnno = hierarchy.findAnnotationInSameHierarchy(annos, annotation);
+      }
+      if (!checkContract(expression, annotation, inferredAnno, exitStore)) {
+        checker.reportError(
+            methodTree,
+            "contracts.postcondition",
+            methodTree.getName(),
+            contractExpressionAndType(expression.toString(), inferredAnno),
+            contractExpressionAndType(expression.toString(), annotation));
+      }
+    }
+  }
+
+  /**
+   * Returns a string representation of an expression and type qualifier.
+   *
+   * @param expression a Java expression
+   * @param qualifier the expression's type, or null if no information is available
+   * @return a string representation of the expression and type qualifier
+   */
+  private String contractExpressionAndType(
+      String expression, @Nullable AnnotationMirror qualifier) {
+    if (qualifier == null) {
+      return "no information about " + expression;
+    } else {
+      return expression
+          + " is "
+          + atypeFactory.getAnnotationFormatter().formatAnnotationMirror(qualifier);
+    }
+  }
+
+  /**
+   * Check that the expression's type is annotated with {@code annotation} at every regular exit
+   * that returns {@code result}.
+   *
+   * @param methodTree tree of method with the postcondition
+   * @param annotation expression's type must have this annotation
+   * @param expression the expression that the postcondition concerns
+   * @param result result for which the postcondition is valid
+   */
+  protected void checkConditionalPostcondition(
+      MethodTree methodTree,
+      AnnotationMirror annotation,
+      JavaExpression expression,
+      boolean result) {
+    boolean booleanReturnType =
+        TypesUtils.isBooleanType(TreeUtils.typeOf(methodTree.getReturnType()));
+    if (!booleanReturnType) {
+      checker.reportError(methodTree, "contracts.conditional.postcondition.returntype");
+      // No reason to go ahead with further checking. The
+      // annotation is invalid.
+      return;
+    }
+
+    for (Pair<ReturnNode, ?> pair : atypeFactory.getReturnStatementStores(methodTree)) {
+      ReturnNode returnStmt = pair.first;
+
+      Node retValNode = returnStmt.getResult();
+      Boolean retVal =
+          retValNode instanceof BooleanLiteralNode
+              ? ((BooleanLiteralNode) retValNode).getValue()
+              : null;
+
+      TransferResult<?, ?> transferResult = (TransferResult<?, ?>) pair.second;
+      if (transferResult == null) {
+        // Unreachable return statements have no stores, but there is no need to check them.
+        continue;
+      }
+      CFAbstractStore<?, ?> exitStore =
+          (CFAbstractStore<?, ?>)
+              (result ? transferResult.getThenStore() : transferResult.getElseStore());
+      CFAbstractValue<?> value = exitStore.getValue(expression);
+
+      // don't check if return statement certainly does not match 'result'. at the moment,
+      // this means the result is a boolean literal
+      if (!(retVal == null || retVal == result)) {
+        continue;
+      }
+      AnnotationMirror inferredAnno = null;
+      if (value != null) {
+        QualifierHierarchy hierarchy = atypeFactory.getQualifierHierarchy();
+        Set<AnnotationMirror> annos = value.getAnnotations();
+        inferredAnno = hierarchy.findAnnotationInSameHierarchy(annos, annotation);
+      }
+
+      if (!checkContract(expression, annotation, inferredAnno, exitStore)) {
+        checker.reportError(
+            returnStmt.getTree(),
+            "contracts.conditional.postcondition",
+            methodTree.getName(),
+            result,
+            contractExpressionAndType(expression.toString(), inferredAnno),
+            contractExpressionAndType(expression.toString(), annotation));
+      }
+    }
+  }
+
+  @Override
+  public Void visitTypeParameter(TypeParameterTree node, Void p) {
+    validateTypeOf(node);
+    // Check the bounds here and not with every TypeParameterTree.
+    // For the latter, we only need to check annotations on the type variable itself.
+    // Why isn't this covered by the super call?
+    for (Tree tpb : node.getBounds()) {
+      validateTypeOf(tpb);
+    }
+
+    if (node.getBounds().size() > 1) {
+      // The upper bound of the type parameter is an intersection
+      AnnotatedTypeVariable type =
+          (AnnotatedTypeVariable) atypeFactory.getAnnotatedTypeFromTypeTree(node);
+      AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) type.getUpperBound();
+      checkExplicitAnnotationsOnIntersectionBounds(intersection, node.getBounds());
+    }
+
+    return super.visitTypeParameter(node, p);
+  }
+
+  /**
+   * Issues "explicit.annotation.ignored" warning if any explicit annotation on an intersection
+   * bound is not the same as the primary annotation of the given intersection type.
+   *
+   * @param intersection type to use
+   * @param boundTrees trees of {@code intersection} bounds
+   */
+  protected void checkExplicitAnnotationsOnIntersectionBounds(
+      AnnotatedIntersectionType intersection, List<? extends Tree> boundTrees) {
+    for (Tree boundTree : boundTrees) {
+      if (boundTree.getKind() != Tree.Kind.ANNOTATED_TYPE) {
+        continue;
+      }
+      List<? extends AnnotationMirror> explictAnnos =
+          TreeUtils.annotationsFromTree((AnnotatedTypeTree) boundTree);
+      for (AnnotationMirror explictAnno : explictAnnos) {
+        if (atypeFactory.isSupportedQualifier(explictAnno)) {
+          AnnotationMirror anno = intersection.getAnnotationInHierarchy(explictAnno);
+          if (!AnnotationUtils.areSame(anno, explictAnno)) {
+            checker.reportWarning(
+                boundTree, "explicit.annotation.ignored", explictAnno, anno, explictAnno, anno);
+          }
+        }
+      }
+    }
+  }
+
+  // **********************************************************************
+  // Assignment checkers and pseudo-assignments
+  // **********************************************************************
+
+  @Override
+  public Void visitVariable(VariableTree node, Void p) {
+    warnAboutTypeAnnotationsTooEarly(node, node.getModifiers());
+
+    visitAnnotatedType(node.getModifiers().getAnnotations(), node.getType());
+
+    Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = visitorState.getAssignmentContext();
+    AnnotatedTypeMirror variableType;
+    if (getCurrentPath().getParentPath() != null
+        && getCurrentPath().getParentPath().getLeaf().getKind() == Tree.Kind.LAMBDA_EXPRESSION) {
+      // Calling getAnnotatedTypeLhs on a lambda parameter node is possibly expensive
+      // because caching is turned off.  This should be fixed by #979.
+      // See https://github.com/typetools/checker-framework/issues/2853 for an example.
+      variableType = atypeFactory.getAnnotatedType(node);
+    } else {
+      variableType = atypeFactory.getAnnotatedTypeLhs(node);
+    }
+    visitorState.setAssignmentContext(Pair.of(node, variableType));
+
+    try {
+      atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(variableType, node);
+      // If there's no assignment in this variable declaration, skip it.
+      if (node.getInitializer() != null) {
+        commonAssignmentCheck(node, node.getInitializer(), "assignment");
+      } else {
+        // commonAssignmentCheck validates the type of node,
+        // so only validate if commonAssignmentCheck wasn't called
+        validateTypeOf(node);
+      }
+      return super.visitVariable(node, p);
+    } finally {
+      visitorState.setAssignmentContext(preAssignmentContext);
+    }
+  }
+
+  /**
+   * Warn if a type annotation is written before a modifier such as "public" or before a declaration
+   * annotation.
+   *
+   * @param node a VariableTree or a MethodTree
+   * @param modifiersTree the modifiers sub-tree of node
+   */
+  private void warnAboutTypeAnnotationsTooEarly(Tree node, ModifiersTree modifiersTree) {
+
+    // Don't issue warnings about compiler-inserted modifiers.
+    // This simple code completely igonores enum constants and try-with-resources declarations.
+    // It could be made to catch some user errors in those locations, but it doesn't seem worth
+    // the effort to do so.
+    if (node.getKind() == Tree.Kind.VARIABLE) {
+      ElementKind varKind = TreeUtils.elementFromDeclaration((VariableTree) node).getKind();
+      switch (varKind) {
+        case ENUM_CONSTANT:
+          // Enum constants are "public static final" by default, so the annotation always
+          // appears to be before "public".
+          return;
+        case RESOURCE_VARIABLE:
+          // Try-with-resources variables are "final" by default, so the annotation always
+          // appears to be before "final".
+          return;
+        default:
+          // Nothing to do
+      }
+    }
+
+    Set<Modifier> modifierSet = modifiersTree.getFlags();
+    List<? extends AnnotationTree> annotations = modifiersTree.getAnnotations();
+
+    if (annotations.isEmpty()) {
+      return;
+    }
+
+    // Warn about type annotations written before modifiers such as "public".  javac retains no
+    // information about modifier locations.  So, this is a very partial check:  Issue a warning if
+    // a type annotation is at the very beginning of the VariableTree, and a modifier follows it.
+
+    // Check if a type annotation precedes a declaration annotation.
+    int lastDeclAnnoIndex = -1;
+    for (int i = annotations.size() - 1; i > 0; i--) { // no need to check index 0
+      if (!isTypeAnnotation(annotations.get(i))) {
+        lastDeclAnnoIndex = i;
+        break;
+      }
+    }
+    if (lastDeclAnnoIndex != -1) {
+      List<AnnotationTree> badTypeAnnos = new ArrayList<>();
+      for (int i = 0; i < lastDeclAnnoIndex; i++) {
+        AnnotationTree anno = annotations.get(i);
+        if (isTypeAnnotation(anno)) {
+          badTypeAnnos.add(anno);
+        }
+      }
+      if (!badTypeAnnos.isEmpty()) {
+        checker.reportWarning(
+            node, "type.anno.before.decl.anno", badTypeAnnos, annotations.get(lastDeclAnnoIndex));
+      }
+    }
+
+    // Determine the length of the text that ought to precede the first type annotation.
+    // If the type annotation appears before that text could appear, then warn that a
+    // modifier appears after the type annotation.
+    // TODO: in the future, account for the lengths of declaration annotations.  Length of toString
+    // of the annotation isn't useful, as it might be different length than original input.  Can use
+    // JCTree.getEndPosition(EndPosTable) and com.sun.tools.javac.tree.EndPosTable, but it requires
+    // -Xjcov.
+    AnnotationTree firstAnno = annotations.get(0);
+    if (!modifierSet.isEmpty() && isTypeAnnotation(firstAnno)) {
+      int precedingTextLength = 0;
+      for (Modifier m : modifierSet) {
+        precedingTextLength += m.toString().length() + 1; // +1 for the space
+      }
+      int annoStartPos = ((JCTree) firstAnno).getStartPosition();
+      int varStartPos = ((JCTree) node).getStartPosition();
+      if (annoStartPos < varStartPos + precedingTextLength) {
+        checker.reportWarning(node, "type.anno.before.modifier", firstAnno, modifierSet);
+      }
+    }
+  }
+
+  /**
+   * Return true if the given annotation is a type annotation: that is, its definition is
+   * meta-annotated with {@code @Target({TYPE_USE,....})}.
+   */
+  private boolean isTypeAnnotation(AnnotationTree anno) {
+    Tree annoType = anno.getAnnotationType();
+    ClassSymbol annoSymbol;
+    switch (annoType.getKind()) {
+      case IDENTIFIER:
+        annoSymbol = (ClassSymbol) ((JCIdent) annoType).sym;
+        break;
+      case MEMBER_SELECT:
+        annoSymbol = (ClassSymbol) ((JCFieldAccess) annoType).sym;
+        break;
+      default:
+        throw new Error("Unhandled kind: " + annoType.getKind() + " for " + anno);
+    }
+    for (AnnotationMirror metaAnno : annoSymbol.getAnnotationMirrors()) {
+      if (AnnotationUtils.areSameByName(metaAnno, TARGET)) {
+        AnnotationValue av = metaAnno.getElementValues().get(targetValueElement);
+        return AnnotationUtils.annotationValueContainsToString(av, "TYPE_USE");
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Performs two checks: subtyping and assignability checks, using {@link
+   * #commonAssignmentCheck(Tree, ExpressionTree, String, Object[])}.
+   *
+   * <p>If the subtype check fails, it issues a "assignment" error.
+   */
+  @Override
+  public Void visitAssignment(AssignmentTree node, Void p) {
+    Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = visitorState.getAssignmentContext();
+    visitorState.setAssignmentContext(
+        Pair.of((Tree) node.getVariable(), atypeFactory.getAnnotatedType(node.getVariable())));
+    try {
+      commonAssignmentCheck(node.getVariable(), node.getExpression(), "assignment");
+      return super.visitAssignment(node, p);
+    } finally {
+      visitorState.setAssignmentContext(preAssignmentContext);
+    }
+  }
+
+  /**
+   * Performs a subtype check, to test whether the node expression iterable type is a subtype of the
+   * variable type in the enhanced for loop.
+   *
+   * <p>If the subtype check fails, it issues a "enhancedfor" error.
+   */
+  @Override
+  public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void p) {
+    AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(node.getVariable());
+    AnnotatedTypeMirror iteratedType = atypeFactory.getIterableElementType(node.getExpression());
+    boolean valid = validateTypeOf(node.getVariable());
+    if (valid) {
+      commonAssignmentCheck(var, iteratedType, node.getExpression(), "enhancedfor");
+    }
+    return super.visitEnhancedForLoop(node, p);
+  }
+
+  /**
+   * Performs a method invocation check.
+   *
+   * <p>An invocation of a method, m, on the receiver, r is valid only if:
+   *
+   * <ul>
+   *   <li>passed arguments are subtypes of corresponding m parameters
+   *   <li>r is a subtype of m receiver type
+   *   <li>if m is generic, passed type arguments are subtypes of m type variables
+   * </ul>
+   */
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+
+    // Skip calls to the Enum constructor (they're generated by javac and
+    // hard to check), also see CFGBuilder.visitMethodInvocation.
+    if (TreeUtils.elementFromUse(node) == null || TreeUtils.isEnumSuper(node)) {
+      return super.visitMethodInvocation(node, p);
+    }
+
+    if (shouldSkipUses(node)) {
+      return super.visitMethodInvocation(node, p);
+    }
+
+    ParameterizedExecutableType mType = atypeFactory.methodFromUse(node);
+    AnnotatedExecutableType invokedMethod = mType.executableType;
+    List<AnnotatedTypeMirror> typeargs = mType.typeArgs;
+
+    if (!atypeFactory.ignoreUninferredTypeArguments) {
+      for (AnnotatedTypeMirror typearg : typeargs) {
+        if (typearg.getKind() == TypeKind.WILDCARD
+            && ((AnnotatedWildcardType) typearg).isUninferredTypeArgument()) {
+          checker.reportError(
+              node, "type.arguments.not.inferred", invokedMethod.getElement().getSimpleName());
+          break; // only issue error once per method
+        }
+      }
+    }
+
+    List<AnnotatedTypeParameterBounds> paramBounds =
+        CollectionsPlume.mapList(
+            AnnotatedTypeVariable::getBounds, invokedMethod.getTypeVariables());
+
+    ExecutableElement method = invokedMethod.getElement();
+    CharSequence methodName = ElementUtils.getSimpleNameOrDescription(method);
+    try {
+      checkTypeArguments(
+          node,
+          paramBounds,
+          typeargs,
+          node.getTypeArguments(),
+          methodName,
+          invokedMethod.getTypeVariables());
+      List<AnnotatedTypeMirror> params =
+          AnnotatedTypes.expandVarArgs(atypeFactory, invokedMethod, node.getArguments());
+      checkArguments(params, node.getArguments(), methodName, method.getParameters());
+      checkVarargs(invokedMethod, node);
+
+      if (ElementUtils.isMethod(
+          invokedMethod.getElement(), vectorCopyInto, atypeFactory.getProcessingEnv())) {
+        typeCheckVectorCopyIntoArgument(node, params);
+      }
+
+      ExecutableElement invokedMethodElement = invokedMethod.getElement();
+      if (!ElementUtils.isStatic(invokedMethodElement) && !TreeUtils.isSuperConstructorCall(node)) {
+        checkMethodInvocability(invokedMethod, node);
+      }
+
+      // check precondition annotations
+      checkPreconditions(
+          node, atypeFactory.getContractsFromMethod().getPreconditions(invokedMethodElement));
+
+      if (TreeUtils.isSuperConstructorCall(node)) {
+        checkSuperConstructorCall(node);
+      } else if (TreeUtils.isThisConstructorCall(node)) {
+        checkThisConstructorCall(node);
+      }
+    } catch (RuntimeException t) {
+      // Sometimes the type arguments are inferred incorrect which causes crashes. Once #979
+      // is fixed this should be removed and crashes should be reported normally.
+      if (node.getTypeArguments().size() == typeargs.size()) {
+        // They type arguments were explicitly written.
+        throw t;
+      }
+      if (!atypeFactory.ignoreUninferredTypeArguments) {
+        checker.reportError(
+            node, "type.arguments.not.inferred", invokedMethod.getElement().getSimpleName());
+      } // else ignore the crash.
+    }
+
+    // Do not call super, as that would observe the arguments without
+    // a set assignment context.
+    scan(node.getMethodSelect(), p);
+    return null; // super.visitMethodInvocation(node, p);
+  }
+
+  /**
+   * Checks that the following rule is satisfied: The type on a constructor declaration must be a
+   * supertype of the return type of "this()" invocation within that constructor.
+   *
+   * <p>Subclasses can override this method to change the behavior for just "this" constructor
+   * class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to
+   * change the behavior for "this" and "super" constructor calls.
+   *
+   * @param thisCall the AST node for the constructor call
+   */
+  protected void checkThisConstructorCall(MethodInvocationTree thisCall) {
+    checkThisOrSuperConstructorCall(thisCall, "this.invocation");
+  }
+
+  /**
+   * Checks that the following rule is satisfied: The type on a constructor declaration must be a
+   * supertype of the return type of "super()" invocation within that constructor.
+   *
+   * <p>Subclasses can override this method to change the behavior for just "super" constructor
+   * class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to
+   * change the behavior for "this" and "super" constructor calls.
+   *
+   * @param superCall the AST node for the super constructor call
+   */
+  protected void checkSuperConstructorCall(MethodInvocationTree superCall) {
+    checkThisOrSuperConstructorCall(superCall, "super.invocation");
+  }
+
+  /**
+   * Checks that the following rule is satisfied: The type on a constructor declaration must be a
+   * supertype of the return type of "this()" or "super()" invocation within that constructor.
+   *
+   * @param call the AST node for the constructor call
+   * @param errorKey the error message key to use if the check fails
+   */
+  protected void checkThisOrSuperConstructorCall(
+      MethodInvocationTree call, @CompilerMessageKey String errorKey) {
+    TreePath path = atypeFactory.getPath(call);
+    MethodTree enclosingMethod = TreePathUtil.enclosingMethod(path);
+    AnnotatedTypeMirror superType = atypeFactory.getAnnotatedType(call);
+    AnnotatedExecutableType constructorType = atypeFactory.getAnnotatedType(enclosingMethod);
+    Set<? extends AnnotationMirror> topAnnotations =
+        atypeFactory.getQualifierHierarchy().getTopAnnotations();
+    for (AnnotationMirror topAnno : topAnnotations) {
+      AnnotationMirror superTypeMirror = superType.getAnnotationInHierarchy(topAnno);
+      AnnotationMirror constructorTypeMirror =
+          constructorType.getReturnType().getAnnotationInHierarchy(topAnno);
+
+      if (!atypeFactory.getQualifierHierarchy().isSubtype(superTypeMirror, constructorTypeMirror)) {
+        checker.reportError(call, errorKey, constructorTypeMirror, call, superTypeMirror);
+      }
+    }
+  }
+
+  /**
+   * A helper method to check that the array type of actual varargs is a subtype of the
+   * corresponding required varargs, and issues "argument" error if it's not a subtype of the
+   * required one.
+   *
+   * <p>Note it's required that type checking for each element in varargs is executed by the caller
+   * before or after calling this method.
+   *
+   * @see #checkArguments
+   * @param invokedMethod the method type to be invoked
+   * @param tree method or constructor invocation tree
+   */
+  protected void checkVarargs(AnnotatedExecutableType invokedMethod, Tree tree) {
+    if (!invokedMethod.isVarArgs()) {
+      return;
+    }
+
+    List<AnnotatedTypeMirror> formals = invokedMethod.getParameterTypes();
+    int numFormals = formals.size();
+    int lastArgIndex = numFormals - 1;
+    AnnotatedArrayType lastParamAnnotatedType = (AnnotatedArrayType) formals.get(lastArgIndex);
+
+    // We will skip type checking so that we avoid duplicating error message if the last argument is
+    // same depth with the depth of formal varargs because type checking is already done in
+    // checkArguments.
+    List<? extends ExpressionTree> args;
+    switch (tree.getKind()) {
+      case METHOD_INVOCATION:
+        args = ((MethodInvocationTree) tree).getArguments();
+        break;
+      case NEW_CLASS:
+        args = ((NewClassTree) tree).getArguments();
+        break;
+      default:
+        throw new BugInCF("Unexpected kind of tree: " + tree);
+    }
+    if (numFormals == args.size()) {
+      AnnotatedTypeMirror lastArgType = atypeFactory.getAnnotatedType(args.get(args.size() - 1));
+      if (lastArgType.getKind() == TypeKind.ARRAY
+          && AnnotatedTypes.getArrayDepth(lastParamAnnotatedType)
+              == AnnotatedTypes.getArrayDepth((AnnotatedArrayType) lastArgType)) {
+        return;
+      }
+    }
+
+    AnnotatedTypeMirror wrappedVarargsType = atypeFactory.getAnnotatedTypeVarargsArray(tree);
+
+    // When dataflow analysis is not enabled, it will be null and we can suppose there is no
+    // annotation to be checked for generated varargs array.
+    if (wrappedVarargsType == null) {
+      return;
+    }
+
+    // The component type of wrappedVarargsType might not be a subtype of the component type of
+    // lastParamAnnotatedType due to the difference of type inference between for an expression
+    // and an invoked method element. We can consider that the component type of actual is same
+    // with formal one because type checking for elements will be done in checkArguments. This
+    // is also needed to avoid duplicating error message caused by elements in varargs.
+    if (wrappedVarargsType.getKind() == TypeKind.ARRAY) {
+      ((AnnotatedArrayType) wrappedVarargsType)
+          .setComponentType(lastParamAnnotatedType.getComponentType());
+    }
+
+    commonAssignmentCheck(lastParamAnnotatedType, wrappedVarargsType, tree, "varargs");
+  }
+
+  /**
+   * Checks that all the given {@code preconditions} hold true immediately prior to the method
+   * invocation or variable access at {@code tree}.
+   *
+   * @param tree the method invocation; immediately prior to it, the preconditions must hold true
+   * @param preconditions the preconditions to be checked
+   */
+  protected void checkPreconditions(MethodInvocationTree tree, Set<Precondition> preconditions) {
+    // This check is needed for the GUI effects and Units Checkers tests to pass.
+    // TODO: Remove this check and investigate the root cause.
+    if (preconditions.isEmpty()) {
+      return;
+    }
+
+    StringToJavaExpression stringToJavaExpr =
+        stringExpr -> StringToJavaExpression.atMethodInvocation(stringExpr, tree, checker);
+    for (Contract c : preconditions) {
+      Precondition p = (Precondition) c;
+      String expressionString = p.expressionString;
+      AnnotationMirror anno =
+          c.viewpointAdaptDependentTypeAnnotation(atypeFactory, stringToJavaExpr, tree);
+      JavaExpression exprJe;
+      try {
+        exprJe = StringToJavaExpression.atMethodInvocation(expressionString, tree, checker);
+      } catch (JavaExpressionParseException e) {
+        // report errors here
+        checker.report(tree, e.getDiagMessage());
+        return;
+      }
+
+      CFAbstractStore<?, ?> store = atypeFactory.getStoreBefore(tree);
+      CFAbstractValue<?> value = null;
+      if (CFAbstractStore.canInsertJavaExpression(exprJe)) {
+        value = store.getValue(exprJe);
+      }
+      AnnotationMirror inferredAnno = null;
+      if (value != null) {
+        QualifierHierarchy hierarchy = atypeFactory.getQualifierHierarchy();
+        Set<AnnotationMirror> annos = value.getAnnotations();
+        inferredAnno = hierarchy.findAnnotationInSameHierarchy(annos, anno);
+      }
+      if (!checkContract(exprJe, anno, inferredAnno, store)) {
+        if (exprJe != null) {
+          expressionString = exprJe.toString();
+        }
+        checker.reportError(
+            tree,
+            "contracts.precondition",
+            tree.getMethodSelect().toString(),
+            contractExpressionAndType(expressionString, inferredAnno),
+            contractExpressionAndType(expressionString, anno));
+      }
+    }
+  }
+
+  /**
+   * Returns true if and only if {@code inferredAnnotation} is valid for a given expression to match
+   * the {@code necessaryAnnotation}.
+   *
+   * <p>By default, {@code inferredAnnotation} must be a subtype of {@code necessaryAnnotation}, but
+   * subclasses might override this behavior.
+   */
+  protected boolean checkContract(
+      JavaExpression expr,
+      AnnotationMirror necessaryAnnotation,
+      AnnotationMirror inferredAnnotation,
+      CFAbstractStore<?, ?> store) {
+    return inferredAnnotation != null
+        && atypeFactory.getQualifierHierarchy().isSubtype(inferredAnnotation, necessaryAnnotation);
+  }
+
+  /**
+   * Type checks the method arguments of {@code Vector.copyInto()}.
+   *
+   * <p>The Checker Framework special-cases the method invocation, as its type safety cannot be
+   * expressed by Java's type system.
+   *
+   * <p>For a Vector {@code v} of type {@code Vector<E>}, the method invocation {@code
+   * v.copyInto(arr)} is type-safe iff {@code arr} is an array of type {@code T[]}, where {@code T}
+   * is a subtype of {@code E}.
+   *
+   * <p>In other words, this method checks that the type argument of the receiver method is a
+   * subtype of the component type of the passed array argument.
+   *
+   * @param node a method invocation of {@code Vector.copyInto()}
+   * @param params the types of the parameters of {@code Vectory.copyInto()}
+   */
+  protected void typeCheckVectorCopyIntoArgument(
+      MethodInvocationTree node, List<? extends AnnotatedTypeMirror> params) {
+    assert params.size() == 1
+        : "invalid no. of parameters " + params + " found for method invocation " + node;
+    assert node.getArguments().size() == 1
+        : "invalid no. of arguments in method invocation " + node;
+
+    AnnotatedTypeMirror passed = atypeFactory.getAnnotatedType(node.getArguments().get(0));
+    AnnotatedArrayType passedAsArray = (AnnotatedArrayType) passed;
+
+    AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(node);
+    AnnotatedDeclaredType receiverAsVector =
+        AnnotatedTypes.asSuper(atypeFactory, receiver, vectorType);
+    if (receiverAsVector.getTypeArguments().isEmpty()) {
+      return;
+    }
+
+    AnnotatedTypeMirror argComponent = passedAsArray.getComponentType();
+    AnnotatedTypeMirror vectorTypeArg = receiverAsVector.getTypeArguments().get(0);
+    Tree errorLocation = node.getArguments().get(0);
+    if (TypesUtils.isErasedSubtype(
+        vectorTypeArg.getUnderlyingType(), argComponent.getUnderlyingType(), types)) {
+      commonAssignmentCheck(argComponent, vectorTypeArg, errorLocation, "vector.copyinto");
+    } else {
+      checker.reportError(errorLocation, "vector.copyinto", vectorTypeArg, argComponent);
+    }
+  }
+
+  /**
+   * Performs a new class invocation check.
+   *
+   * <p>An invocation of a constructor, c, is valid only if:
+   *
+   * <ul>
+   *   <li>passed arguments are subtypes of corresponding c parameters
+   *   <li>if c is generic, passed type arguments are subtypes of c type variables
+   * </ul>
+   */
+  @Override
+  public Void visitNewClass(NewClassTree node, Void p) {
+    if (checker.shouldSkipUses(TreeUtils.constructor(node))) {
+      return super.visitNewClass(node, p);
+    }
+
+    ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(node);
+    AnnotatedExecutableType constructorType = fromUse.executableType;
+    List<AnnotatedTypeMirror> typeargs = fromUse.typeArgs;
+
+    List<? extends ExpressionTree> passedArguments = node.getArguments();
+    List<AnnotatedTypeMirror> params =
+        AnnotatedTypes.expandVarArgs(atypeFactory, constructorType, passedArguments);
+
+    ExecutableElement constructor = constructorType.getElement();
+    CharSequence constructorName = ElementUtils.getSimpleNameOrDescription(constructor);
+
+    checkArguments(params, passedArguments, constructorName, constructor.getParameters());
+    checkVarargs(constructorType, node);
+
+    List<AnnotatedTypeParameterBounds> paramBounds =
+        CollectionsPlume.mapList(
+            AnnotatedTypeVariable::getBounds, constructorType.getTypeVariables());
+
+    checkTypeArguments(
+        node,
+        paramBounds,
+        typeargs,
+        node.getTypeArguments(),
+        constructorName,
+        constructor.getTypeParameters());
+
+    boolean valid = validateTypeOf(node);
+
+    if (valid) {
+      AnnotatedDeclaredType dt = atypeFactory.getAnnotatedType(node);
+      atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(dt, node);
+      checkConstructorInvocation(dt, constructorType, node);
+    }
+    // Do not call super, as that would observe the arguments without
+    // a set assignment context.
+    scan(node.getEnclosingExpression(), p);
+    scan(node.getIdentifier(), p);
+    scan(node.getClassBody(), p);
+
+    return null;
+  }
+
+  @Override
+  public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
+
+    AnnotatedExecutableType functionType = atypeFactory.getFunctionTypeFromTree(node);
+
+    if (node.getBody().getKind() != Tree.Kind.BLOCK) {
+      // Check return type for single statement returns here.
+      AnnotatedTypeMirror ret = functionType.getReturnType();
+      if (ret.getKind() != TypeKind.VOID) {
+        visitorState.setAssignmentContext(Pair.of((Tree) node, ret));
+        commonAssignmentCheck(ret, (ExpressionTree) node.getBody(), "return");
+      }
+    }
+
+    // Check parameters
+    for (int i = 0; i < functionType.getParameterTypes().size(); ++i) {
+      AnnotatedTypeMirror lambdaParameter =
+          atypeFactory.getAnnotatedType(node.getParameters().get(i));
+      commonAssignmentCheck(
+          lambdaParameter,
+          functionType.getParameterTypes().get(i),
+          node.getParameters().get(i),
+          "lambda.param",
+          i);
+    }
+
+    // TODO: Postconditions?
+    // https://github.com/typetools/checker-framework/issues/801
+
+    return super.visitLambdaExpression(node, p);
+  }
+
+  @Override
+  public Void visitMemberReference(MemberReferenceTree node, Void p) {
+    this.checkMethodReferenceAsOverride(node, p);
+    return super.visitMemberReference(node, p);
+  }
+
+  /**
+   * Checks that the type of the return expression is a subtype of the enclosing method required
+   * return type. If not, it issues a "return" error.
+   */
+  @Override
+  public Void visitReturn(ReturnTree node, Void p) {
+    // Don't try to check return expressions for void methods.
+    if (node.getExpression() == null) {
+      return super.visitReturn(node, p);
+    }
+
+    Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = visitorState.getAssignmentContext();
+    try {
+
+      Tree enclosing =
+          TreePathUtil.enclosingOfKind(
+              getCurrentPath(),
+              new HashSet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)));
+
+      AnnotatedTypeMirror ret = null;
+      if (enclosing.getKind() == Tree.Kind.METHOD) {
+
+        MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath());
+        boolean valid = validateTypeOf(enclosing);
+        if (valid) {
+          ret = atypeFactory.getMethodReturnType(enclosingMethod, node);
+        }
+      } else {
+        AnnotatedExecutableType result =
+            atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing);
+        ret = result.getReturnType();
+      }
+
+      if (ret != null) {
+        visitorState.setAssignmentContext(Pair.of((Tree) node, ret));
+
+        commonAssignmentCheck(ret, node.getExpression(), "return");
+      }
+      return super.visitReturn(node, p);
+    } finally {
+      visitorState.setAssignmentContext(preAssignmentContext);
+    }
+  }
+
+  /**
+   * Ensure that the annotation arguments comply to their declarations. This needs some special
+   * casing, as annotation arguments form special trees.
+   */
+  @Override
+  public Void visitAnnotation(AnnotationTree node, Void p) {
+    List<? extends ExpressionTree> args = node.getArguments();
+    if (args.isEmpty()) {
+      // Nothing to do if there are no annotation arguments.
+      return null;
+    }
+
+    TypeElement anno = (TypeElement) TreeInfo.symbol((JCTree) node.getAnnotationType());
+
+    Name annoName = anno.getQualifiedName();
+    if (annoName.contentEquals(DefaultQualifier.class.getName())
+        || annoName.contentEquals(SuppressWarnings.class.getName())) {
+      // Skip these two annotations, as we don't care about the arguments to them.
+      return null;
+    }
+
+    List<ExecutableElement> methods = ElementFilter.methodsIn(anno.getEnclosedElements());
+    // Mapping from argument simple name to its annotated type.
+    Map<String, AnnotatedTypeMirror> annoTypes = new HashMap<>(methods.size());
+    for (ExecutableElement meth : methods) {
+      AnnotatedExecutableType exeatm = atypeFactory.getAnnotatedType(meth);
+      AnnotatedTypeMirror retty = exeatm.getReturnType();
+      annoTypes.put(meth.getSimpleName().toString(), retty);
+    }
+
+    for (ExpressionTree arg : args) {
+      if (!(arg instanceof AssignmentTree)) {
+        // TODO: when can this happen?
+        continue;
+      }
+
+      AssignmentTree at = (AssignmentTree) arg;
+      // Ensure that we never ask for the annotated type of an annotation, because
+      // we don't have a type for annotations.
+      if (at.getExpression().getKind() == Tree.Kind.ANNOTATION) {
+        visitAnnotation((AnnotationTree) at.getExpression(), p);
+        continue;
+      }
+      if (at.getExpression().getKind() == Tree.Kind.NEW_ARRAY) {
+        NewArrayTree nat = (NewArrayTree) at.getExpression();
+        boolean isAnno = false;
+        for (ExpressionTree init : nat.getInitializers()) {
+          if (init.getKind() == Tree.Kind.ANNOTATION) {
+            visitAnnotation((AnnotationTree) init, p);
+            isAnno = true;
+          }
+        }
+        if (isAnno) {
+          continue;
+        }
+      }
+
+      AnnotatedTypeMirror expected = annoTypes.get(at.getVariable().toString());
+      Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = visitorState.getAssignmentContext();
+
+      {
+        // Determine and set the new assignment context.
+        ExpressionTree var = at.getVariable();
+        assert var instanceof IdentifierTree : "Expected IdentifierTree as context. Found: " + var;
+        AnnotatedTypeMirror meth = atypeFactory.getAnnotatedType(var);
+        assert meth instanceof AnnotatedExecutableType
+            : "Expected AnnotatedExecutableType as context. Found: " + meth;
+        AnnotatedTypeMirror newctx = ((AnnotatedExecutableType) meth).getReturnType();
+        visitorState.setAssignmentContext(Pair.of((Tree) null, newctx));
+      }
+
+      try {
+        AnnotatedTypeMirror actual = atypeFactory.getAnnotatedType(at.getExpression());
+        if (expected.getKind() != TypeKind.ARRAY) {
+          // Expected is not an array -> direct comparison.
+          commonAssignmentCheck(expected, actual, at.getExpression(), "annotation");
+        } else {
+          if (actual.getKind() == TypeKind.ARRAY) {
+            // Both actual and expected are arrays.
+            commonAssignmentCheck(expected, actual, at.getExpression(), "annotation");
+          } else {
+            // The declaration is an array type, but just a single
+            // element is given.
+            commonAssignmentCheck(
+                ((AnnotatedArrayType) expected).getComponentType(),
+                actual,
+                at.getExpression(),
+                "annotation");
+          }
+        }
+      } finally {
+        visitorState.setAssignmentContext(preAssignmentContext);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * If the computation of the type of the ConditionalExpressionTree in
+   * org.checkerframework.framework.type.TypeFromTree.TypeFromExpression.visitConditionalExpression(ConditionalExpressionTree,
+   * AnnotatedTypeFactory) is correct, the following checks are redundant. However, let's add
+   * another failsafe guard and do the checks.
+   */
+  @Override
+  public Void visitConditionalExpression(ConditionalExpressionTree node, Void p) {
+    AnnotatedTypeMirror cond = atypeFactory.getAnnotatedType(node);
+    this.commonAssignmentCheck(cond, node.getTrueExpression(), "conditional");
+    this.commonAssignmentCheck(cond, node.getFalseExpression(), "conditional");
+    return super.visitConditionalExpression(node, p);
+  }
+
+  // **********************************************************************
+  // Check for illegal re-assignment
+  // **********************************************************************
+
+  /** Performs assignability check. */
+  @Override
+  public Void visitUnary(UnaryTree node, Void p) {
+    Tree.Kind nodeKind = node.getKind();
+    if ((nodeKind == Tree.Kind.PREFIX_DECREMENT)
+        || (nodeKind == Tree.Kind.PREFIX_INCREMENT)
+        || (nodeKind == Tree.Kind.POSTFIX_DECREMENT)
+        || (nodeKind == Tree.Kind.POSTFIX_INCREMENT)) {
+      AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(node.getExpression());
+      AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedTypeRhsUnaryAssign(node);
+      String errorKey =
+          (nodeKind == Tree.Kind.PREFIX_INCREMENT || nodeKind == Tree.Kind.POSTFIX_INCREMENT)
+              ? "unary.increment"
+              : "unary.decrement";
+      commonAssignmentCheck(varType, valueType, node, errorKey);
+    }
+    return super.visitUnary(node, p);
+  }
+
+  /** Performs assignability check. */
+  @Override
+  public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
+    // If node is the tree representing the compounds assignment s += expr,
+    // Then this method should check whether s + expr can be assigned to s,
+    // but the "s + expr" tree does not exist.  So instead, check that
+    // s += expr can be assigned to s.
+    commonAssignmentCheck(node.getVariable(), node, "compound.assignment");
+    return super.visitCompoundAssignment(node, p);
+  }
+
+  // **********************************************************************
+  // Check for invalid types inserted by the user
+  // **********************************************************************
+
+  @Override
+  public Void visitNewArray(NewArrayTree node, Void p) {
+    boolean valid = validateTypeOf(node);
+
+    if (valid && node.getType() != null) {
+      AnnotatedArrayType arrayType = atypeFactory.getAnnotatedType(node);
+      atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(arrayType, node);
+      if (node.getInitializers() != null) {
+        checkArrayInitialization(arrayType.getComponentType(), node.getInitializers());
+      }
+    }
+
+    return super.visitNewArray(node, p);
+  }
+
+  /**
+   * If the lint option "cast:redundant" is set, this methods issues a warning if the cast is
+   * redundant.
+   */
+  protected void checkTypecastRedundancy(TypeCastTree typeCastTree) {
+    if (!checker.getLintOption("cast:redundant", false)) {
+      return;
+    }
+
+    AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(typeCastTree);
+    AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(typeCastTree.getExpression());
+
+    if (castType.equals(exprType)) {
+      checker.reportWarning(typeCastTree, "cast.redundant", castType);
+    }
+  }
+
+  /**
+   * Issues a warning if the given explicitly-written typecast is unsafe. Does nothing if the lint
+   * option "cast:unsafe" is not set. Only primary qualifiers are checked unless the command line
+   * option "checkCastElementType" is supplied.
+   *
+   * @param typeCastTree an explicitly-written typecast
+   */
+  protected void checkTypecastSafety(TypeCastTree typeCastTree) {
+    if (!checker.getLintOption("cast:unsafe", true)) {
+      return;
+    }
+    AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(typeCastTree);
+    AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(typeCastTree.getExpression());
+    boolean calledOnce = false;
+    for (AnnotationMirror top : atypeFactory.getQualifierParameterHierarchies(castType)) {
+      if (!isInvariantTypeCastSafe(castType, exprType, top)) {
+        checker.reportError(
+            typeCastTree,
+            "invariant.cast.unsafe",
+            exprType.toString(true),
+            castType.toString(true));
+      }
+      calledOnce = true; // don't issue cast unsafe warning.
+    }
+    // We cannot do a simple test of casting, as isSubtypeOf requires
+    // the input types to be subtypes according to Java.
+    if (!calledOnce && !isTypeCastSafe(castType, exprType)) {
+      checker.reportWarning(
+          typeCastTree, "cast.unsafe", exprType.toString(true), castType.toString(true));
+    }
+  }
+
+  /**
+   * Returns true if the cast is safe.
+   *
+   * <p>Only primary qualifiers are checked unless the command line option "checkCastElementType" is
+   * supplied.
+   *
+   * @param castType annotated type of the cast
+   * @param exprType annotated type of the casted expression
+   * @return true if the type cast is safe, false otherwise
+   */
+  protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) {
+
+    final TypeKind castTypeKind = castType.getKind();
+    if (castTypeKind == TypeKind.DECLARED) {
+      // Don't issue an error if the annotations are equivalent to the qualifier upper bound of the
+      // type.
+      AnnotatedDeclaredType castDeclared = (AnnotatedDeclaredType) castType;
+      Set<AnnotationMirror> bounds =
+          atypeFactory.getTypeDeclarationBounds(castDeclared.getUnderlyingType());
+
+      if (AnnotationUtils.areSame(castDeclared.getAnnotations(), bounds)) {
+        return true;
+      }
+    }
+
+    QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
+
+    Set<AnnotationMirror> castAnnos;
+    if (!checker.hasOption("checkCastElementType")) {
+      // checkCastElementType option wasn't specified, so only check effective annotations.
+      castAnnos = castType.getEffectiveAnnotations();
+    } else {
+      AnnotatedTypeMirror newCastType;
+      if (castTypeKind == TypeKind.TYPEVAR) {
+        newCastType = ((AnnotatedTypeVariable) castType).getUpperBound();
+      } else {
+        newCastType = castType;
+      }
+      AnnotatedTypeMirror newExprType;
+      if (exprType.getKind() == TypeKind.TYPEVAR) {
+        newExprType = ((AnnotatedTypeVariable) exprType).getUpperBound();
+      } else {
+        newExprType = exprType;
+      }
+
+      if (!atypeFactory.getTypeHierarchy().isSubtype(newExprType, newCastType)) {
+        return false;
+      }
+      if (newCastType.getKind() == TypeKind.ARRAY && newExprType.getKind() != TypeKind.ARRAY) {
+        // Always warn if the cast contains an array, but the expression
+        // doesn't, as in "(Object[]) o" where o is of type Object
+        return false;
+      } else if (newCastType.getKind() == TypeKind.DECLARED
+          && newExprType.getKind() == TypeKind.DECLARED) {
+        int castSize = ((AnnotatedDeclaredType) newCastType).getTypeArguments().size();
+        int exprSize = ((AnnotatedDeclaredType) newExprType).getTypeArguments().size();
+
+        if (castSize != exprSize) {
+          // Always warn if the cast and expression contain a different number of type arguments,
+          // e.g. to catch a cast from "Object" to "List<@NonNull Object>".
+          // TODO: the same number of arguments actually doesn't guarantee anything.
+          return false;
+        }
+      } else if (castTypeKind == TypeKind.TYPEVAR && exprType.getKind() == TypeKind.TYPEVAR) {
+        // If both the cast type and the casted expression are type variables, then check the
+        // bounds.
+        Set<AnnotationMirror> lowerBoundAnnotationsCast =
+            AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualifierHierarchy, castType);
+        Set<AnnotationMirror> lowerBoundAnnotationsExpr =
+            AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualifierHierarchy, exprType);
+        return qualifierHierarchy.isSubtype(lowerBoundAnnotationsExpr, lowerBoundAnnotationsCast)
+            && qualifierHierarchy.isSubtype(
+                exprType.getEffectiveAnnotations(), castType.getEffectiveAnnotations());
+      }
+      if (castTypeKind == TypeKind.TYPEVAR) {
+        // If the cast type is a type var, but the expression is not, then check that the
+        // type of the expression is a subtype of the lower bound.
+        castAnnos = AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualifierHierarchy, castType);
+      } else {
+        castAnnos = castType.getAnnotations();
+      }
+    }
+
+    AnnotatedTypeMirror exprTypeWidened = atypeFactory.getWidenedType(exprType, castType);
+    return qualifierHierarchy.isSubtype(exprTypeWidened.getEffectiveAnnotations(), castAnnos);
+  }
+
+  /**
+   * Return whether or not casting the exprType to castType is legal.
+   *
+   * @param castType an invariant type
+   * @param exprType type of the expressions that is cast which may or may not be invariant
+   * @param top the top qualifier of the hierarchy to check
+   * @return whether or not casting the exprType to castType is legal
+   */
+  private boolean isInvariantTypeCastSafe(
+      AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType, AnnotationMirror top) {
+    if (!isTypeCastSafe(castType, exprType)) {
+      return false;
+    }
+    AnnotationMirror castTypeAnno = castType.getEffectiveAnnotationInHierarchy(top);
+    AnnotationMirror exprTypeAnno = exprType.getEffectiveAnnotationInHierarchy(top);
+
+    if (atypeFactory.hasQualifierParameterInHierarchy(exprType, top)) {
+      // The isTypeCastSafe call above checked that the exprType is a subtype of castType,
+      // so just check the reverse to check that the qualifiers are equivalent.
+      return atypeFactory.getQualifierHierarchy().isSubtype(castTypeAnno, exprTypeAnno);
+    }
+    // Otherwise the cast is unsafe, unless the qualifiers on both cast and expr are bottom.
+    AnnotationMirror bottom = atypeFactory.getQualifierHierarchy().getBottomAnnotation(top);
+    return AnnotationUtils.areSame(castTypeAnno, bottom)
+        && AnnotationUtils.areSame(exprTypeAnno, bottom);
+  }
+
+  @Override
+  public Void visitTypeCast(TypeCastTree node, Void p) {
+    // validate "node" instead of "node.getType()" to prevent duplicate errors.
+    boolean valid = validateTypeOf(node) && validateTypeOf(node.getExpression());
+    if (valid) {
+      checkTypecastSafety(node);
+      checkTypecastRedundancy(node);
+    }
+    if (atypeFactory.getDependentTypesHelper().hasDependentAnnotations()) {
+      AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node);
+      atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(type, node.getType());
+    }
+
+    if (node.getType().getKind() == Tree.Kind.INTERSECTION_TYPE) {
+      AnnotatedIntersectionType intersection =
+          (AnnotatedIntersectionType) atypeFactory.getAnnotatedType(node);
+      checkExplicitAnnotationsOnIntersectionBounds(
+          intersection, ((IntersectionTypeTree) node.getType()).getBounds());
+    }
+    return super.visitTypeCast(node, p);
+    // return scan(node.getExpression(), p);
+  }
+
+  @Override
+  public Void visitInstanceOf(InstanceOfTree node, Void p) {
+    // The "reference type" is the type after "instanceof".
+    Tree refTypeTree = node.getType();
+    validateTypeOf(refTypeTree);
+    if (refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) {
+      AnnotatedTypeMirror refType = atypeFactory.getAnnotatedType(refTypeTree);
+      AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(node.getExpression());
+      if (!refType.hasAnnotation(NonNull.class)
+          && atypeFactory.getTypeHierarchy().isSubtype(refType, expType)
+          && !refType.getAnnotations().equals(expType.getAnnotations())) {
+        checker.reportWarning(node, "instanceof.unsafe", expType, refType);
+      }
+    }
+    return super.visitInstanceOf(node, p);
+  }
+
+  @Override
+  public Void visitArrayAccess(ArrayAccessTree node, Void p) {
+    Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = visitorState.getAssignmentContext();
+    try {
+      visitorState.setAssignmentContext(null);
+      scan(node.getExpression(), p);
+      scan(node.getIndex(), p);
+    } finally {
+      visitorState.setAssignmentContext(preAssignmentContext);
+    }
+    return null;
+  }
+
+  /**
+   * Checks the type of the exception parameter. Subclasses should override {@link
+   * #checkExceptionParameter} rather than this method to change the behavior of this check.
+   */
+  @Override
+  public Void visitCatch(CatchTree node, Void p) {
+    checkExceptionParameter(node);
+    return super.visitCatch(node, p);
+  }
+
+  /**
+   * Checks the type of a thrown exception. Subclasses should override
+   * checkThrownExpression(ThrowTree node) rather than this method to change the behavior of this
+   * check.
+   */
+  @Override
+  public Void visitThrow(ThrowTree node, Void p) {
+    checkThrownExpression(node);
+    return super.visitThrow(node, p);
+  }
+
+  /**
+   * Rather than overriding this method, clients should often override {@link
+   * #visitAnnotatedType(List,Tree)}. That method also handles the case of annotations at the
+   * beginning of a variable or method declaration. javac parses all those annotations as being on
+   * the variable or method declaration, even though the ones that are type annotations logically
+   * belong to the variable type or method return type.
+   */
+  @Override
+  public Void visitAnnotatedType(AnnotatedTypeTree node, Void p) {
+    visitAnnotatedType(null, node);
+    return super.visitAnnotatedType(node, p);
+  }
+
+  /**
+   * Checks an annotated type. Invoked by {@link #visitAnnotatedType(AnnotatedTypeTree, Void)},
+   * {@link #visitVariable}, and {@link #visitMethod}. Exists to prevent code duplication among the
+   * three. Checking in {@code visitVariable} and {@code visitMethod} is needed because there isn't
+   * an AnnotatedTypeTree within a variable declaration or for a method return type -- all the
+   * annotations are attached to the VariableTree or MethodTree, respectively.
+   *
+   * @param annoTrees annotations written before a variable/method declaration, if this type is from
+   *     one; null otherwise. This might contain type annotations that the Java parser attached to
+   *     the declaration rather than to the type.
+   * @param typeTree the type that any type annotations in annoTrees apply to
+   */
+  public void visitAnnotatedType(
+      @Nullable List<? extends AnnotationTree> annoTrees, Tree typeTree) {
+    warnAboutIrrelevantJavaTypes(annoTrees, typeTree);
+  }
+
+  /**
+   * Warns if a type annotation is written on a Java type that is not listed in
+   * the @RelevantJavaTypes annotation.
+   *
+   * @param annoTrees annotations written before a variable/method declaration, if this type is from
+   *     one; null otherwise. This might contain type annotations that the Java parser attached to
+   *     the declaration rather than to the type.
+   * @param typeTree the type that any type annotations in annoTrees apply to
+   */
+  public void warnAboutIrrelevantJavaTypes(
+      @Nullable List<? extends AnnotationTree> annoTrees, Tree typeTree) {
+    if (atypeFactory.relevantJavaTypes == null) {
+      return;
+    }
+
+    Tree t = typeTree;
+    while (true) {
+      switch (t.getKind()) {
+
+          // Recurse for compound types whose top level is not at the far left.
+        case ARRAY_TYPE:
+          t = ((ArrayTypeTree) t).getType();
+          continue;
+        case MEMBER_SELECT:
+          t = ((MemberSelectTree) t).getExpression();
+          continue;
+        case PARAMETERIZED_TYPE:
+          t = ((ParameterizedTypeTree) t).getType();
+          continue;
+
+          // Base cases
+        case PRIMITIVE_TYPE:
+        case IDENTIFIER:
+          List<AnnotationTree> supportedAnnoTrees = supportedAnnoTrees(annoTrees);
+          if (!supportedAnnoTrees.isEmpty() && !atypeFactory.isRelevant(TreeUtils.typeOf(t))) {
+            checker.reportError(t, "anno.on.irrelevant", supportedAnnoTrees, t);
+          }
+          return;
+        case ANNOTATED_TYPE:
+          AnnotatedTypeTree at = (AnnotatedTypeTree) t;
+          ExpressionTree underlying = at.getUnderlyingType();
+          List<AnnotationTree> annos = supportedAnnoTrees(at.getAnnotations());
+          if (!annos.isEmpty() && !atypeFactory.isRelevant(TreeUtils.typeOf(underlying))) {
+            checker.reportError(t, "anno.on.irrelevant", annos, underlying);
+          }
+          return;
+
+        default:
+          return;
+      }
+    }
+  }
+
+  /**
+   * Returns a new list containing only the supported annotations from its argument -- that is,
+   * those that are part of the current type system.
+   *
+   * <p>This method ignores aliases of supported annotations that are declaration annotations,
+   * because they may apply to inner types.
+   *
+   * @param annoTrees annotation trees
+   * @return a new list containing only the supported annotations from its argument
+   */
+  private List<AnnotationTree> supportedAnnoTrees(List<? extends AnnotationTree> annoTrees) {
+    List<AnnotationTree> result = new ArrayList<>(1);
+    for (AnnotationTree at : annoTrees) {
+      AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(at);
+      if (!AnnotationUtils.isDeclarationAnnotation(anno)
+          && atypeFactory.isSupportedQualifier(anno)) {
+        result.add(at);
+      }
+    }
+    return result;
+  }
+
+  // **********************************************************************
+  // Helper methods to provide a single overriding point
+  // **********************************************************************
+
+  /** Cache to avoid calling {@link #getExceptionParameterLowerBoundAnnotations} more than once. */
+  private Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotationsCache = null;
+  /** The same as {@link #getExceptionParameterLowerBoundAnnotations}, but uses a cache. */
+  private Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotationsCached() {
+    if (getExceptionParameterLowerBoundAnnotationsCache == null) {
+      getExceptionParameterLowerBoundAnnotationsCache =
+          getExceptionParameterLowerBoundAnnotations();
+    }
+    return getExceptionParameterLowerBoundAnnotationsCache;
+  }
+
+  /**
+   * Issue error if the exception parameter is not a supertype of the annotation specified by {@link
+   * #getExceptionParameterLowerBoundAnnotations()}, which is top by default.
+   *
+   * <p>Subclasses may override this method to change the behavior of this check. Subclasses wishing
+   * to enforce that exception parameter be annotated with other annotations can just override
+   * {@link #getExceptionParameterLowerBoundAnnotations()}.
+   *
+   * @param node CatchTree to check
+   */
+  protected void checkExceptionParameter(CatchTree node) {
+
+    Set<? extends AnnotationMirror> requiredAnnotations =
+        getExceptionParameterLowerBoundAnnotationsCached();
+    AnnotatedTypeMirror exPar = atypeFactory.getAnnotatedType(node.getParameter());
+
+    for (AnnotationMirror required : requiredAnnotations) {
+      AnnotationMirror found = exPar.getAnnotationInHierarchy(required);
+      assert found != null;
+      if (!atypeFactory.getQualifierHierarchy().isSubtype(required, found)) {
+        checker.reportError(node.getParameter(), "exception.parameter", found, required);
+      }
+
+      if (exPar.getKind() == TypeKind.UNION) {
+        AnnotatedUnionType aut = (AnnotatedUnionType) exPar;
+        for (AnnotatedTypeMirror alterntive : aut.getAlternatives()) {
+          AnnotationMirror foundAltern = alterntive.getAnnotationInHierarchy(required);
+          if (!atypeFactory.getQualifierHierarchy().isSubtype(required, foundAltern)) {
+            checker.reportError(node.getParameter(), "exception.parameter", foundAltern, required);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns a set of AnnotationMirrors that is a lower bound for exception parameters.
+   *
+   * <p>This implementation returns top; subclasses can change this behavior.
+   *
+   * <p>Note: by default this method is called by {@link #getThrowUpperBoundAnnotations()}, so that
+   * this annotation is enforced.
+   *
+   * @return set of annotation mirrors, one per hierarchy, that form a lower bound of annotations
+   *     that can be written on an exception parameter
+   */
+  protected Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotations() {
+    return atypeFactory.getQualifierHierarchy().getTopAnnotations();
+  }
+
+  /**
+   * Checks the type of the thrown expression.
+   *
+   * <p>By default, this method checks that the thrown expression is a subtype of top.
+   *
+   * <p>Issue error if the thrown expression is not a sub type of the annotation given by {@link
+   * #getThrowUpperBoundAnnotations()}, the same as {@link
+   * #getExceptionParameterLowerBoundAnnotations()} by default.
+   *
+   * <p>Subclasses may override this method to change the behavior of this check. Subclasses wishing
+   * to enforce that the thrown expression be a subtype of a type besides {@link
+   * #getExceptionParameterLowerBoundAnnotations}, should override {@link
+   * #getThrowUpperBoundAnnotations()}.
+   *
+   * @param node ThrowTree to check
+   */
+  protected void checkThrownExpression(ThrowTree node) {
+    AnnotatedTypeMirror throwType = atypeFactory.getAnnotatedType(node.getExpression());
+    Set<? extends AnnotationMirror> required = getThrowUpperBoundAnnotations();
+    switch (throwType.getKind()) {
+      case NULL:
+      case DECLARED:
+        Set<AnnotationMirror> found = throwType.getAnnotations();
+        if (!atypeFactory.getQualifierHierarchy().isSubtype(found, required)) {
+          checker.reportError(node.getExpression(), "throw", found, required);
+        }
+        break;
+      case TYPEVAR:
+      case WILDCARD:
+        // TODO: this code might change after the type var changes.
+        Set<AnnotationMirror> foundEffective = throwType.getEffectiveAnnotations();
+        if (!atypeFactory.getQualifierHierarchy().isSubtype(foundEffective, required)) {
+          checker.reportError(node.getExpression(), "throw", foundEffective, required);
+        }
+        break;
+      case UNION:
+        AnnotatedUnionType unionType = (AnnotatedUnionType) throwType;
+        Set<AnnotationMirror> foundPrimary = unionType.getAnnotations();
+        if (!atypeFactory.getQualifierHierarchy().isSubtype(foundPrimary, required)) {
+          checker.reportError(node.getExpression(), "throw", foundPrimary, required);
+        }
+        for (AnnotatedTypeMirror altern : unionType.getAlternatives()) {
+          if (!atypeFactory.getQualifierHierarchy().isSubtype(altern.getAnnotations(), required)) {
+            checker.reportError(node.getExpression(), "throw", altern.getAnnotations(), required);
+          }
+        }
+        break;
+      default:
+        throw new BugInCF("Unexpected throw expression type: " + throwType.getKind());
+    }
+  }
+
+  /**
+   * Returns a set of AnnotationMirrors that is a upper bound for thrown exceptions.
+   *
+   * <p>Note: by default this method is returns by getExceptionParameterLowerBoundAnnotations(), so
+   * that this annotation is enforced.
+   *
+   * <p>(Default is top)
+   *
+   * @return set of annotation mirrors, one per hierarchy, that form an upper bound of thrown
+   *     expressions
+   */
+  protected Set<? extends AnnotationMirror> getThrowUpperBoundAnnotations() {
+    return getExceptionParameterLowerBoundAnnotations();
+  }
+
+  /**
+   * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and
+   * emits an error message (through the compiler's messaging interface) if it is not valid.
+   *
+   * @param varTree the AST node for the lvalue (usually a variable)
+   * @param valueExp the AST node for the rvalue (the new value)
+   * @param errorKey the error message key to use if the check fails
+   * @param extraArgs arguments to the error message key, before "found" and "expected" types
+   */
+  protected void commonAssignmentCheck(
+      Tree varTree,
+      ExpressionTree valueExp,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(varTree);
+    assert varType != null : "no variable found for tree: " + varTree;
+
+    if (!validateType(varTree, varType)) {
+      return;
+    }
+
+    commonAssignmentCheck(varType, valueExp, errorKey, extraArgs);
+  }
+
+  /**
+   * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and
+   * emits an error message (through the compiler's messaging interface) if it is not valid.
+   *
+   * @param varType the annotated type for the lvalue (usually a variable)
+   * @param valueExp the AST node for the rvalue (the new value)
+   * @param errorKey the error message key to use if the check fails
+   * @param extraArgs arguments to the error message key, before "found" and "expected" types
+   */
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      ExpressionTree valueExp,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+    if (shouldSkipUses(valueExp)) {
+      return;
+    }
+    if (valueExp.getKind() == Tree.Kind.MEMBER_REFERENCE
+        || valueExp.getKind() == Tree.Kind.LAMBDA_EXPRESSION) {
+      // Member references and lambda expressions are type checked separately
+      // and do not need to be checked again as arguments.
+      return;
+    }
+    if (varType.getKind() == TypeKind.ARRAY
+        && valueExp instanceof NewArrayTree
+        && ((NewArrayTree) valueExp).getType() == null) {
+      AnnotatedTypeMirror compType = ((AnnotatedArrayType) varType).getComponentType();
+      NewArrayTree arrayTree = (NewArrayTree) valueExp;
+      assert arrayTree.getInitializers() != null
+          : "array initializers are not expected to be null in: " + valueExp;
+      checkArrayInitialization(compType, arrayTree.getInitializers());
+    }
+    if (!validateTypeOf(valueExp)) {
+      return;
+    }
+    AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueExp);
+    assert valueType != null : "null type for expression: " + valueExp;
+    commonAssignmentCheck(varType, valueType, valueExp, errorKey, extraArgs);
+  }
+
+  /**
+   * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and
+   * emits an error message (through the compiler's messaging interface) if it is not valid.
+   *
+   * @param varType the annotated type of the variable
+   * @param valueType the annotated type of the value
+   * @param valueTree the location to use when reporting the error message
+   * @param errorKey the error message key to use if the check fails
+   * @param extraArgs arguments to the error message key, before "found" and "expected" types
+   */
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      AnnotatedTypeMirror valueType,
+      Tree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+
+    commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree);
+
+    AnnotatedTypeMirror widenedValueType = atypeFactory.getWidenedType(valueType, varType);
+    boolean success = atypeFactory.getTypeHierarchy().isSubtype(widenedValueType, varType);
+
+    // TODO: integrate with subtype test.
+    if (success) {
+      for (Class<? extends Annotation> mono : atypeFactory.getSupportedMonotonicTypeQualifiers()) {
+        if (valueType.hasAnnotation(mono) && varType.hasAnnotation(mono)) {
+          checker.reportError(
+              valueTree,
+              "monotonic",
+              mono.getSimpleName(),
+              mono.getSimpleName(),
+              valueType.toString());
+          return;
+        }
+      }
+    }
+
+    commonAssignmentCheckEndDiagnostic(success, null, varType, valueType, valueTree);
+
+    // Use an error key only if it's overridden by a checker.
+    if (!success) {
+      FoundRequired pair = FoundRequired.of(valueType, varType);
+      String valueTypeString = pair.found;
+      String varTypeString = pair.required;
+      checker.reportError(
+          valueTree, errorKey, ArraysPlume.concatenate(extraArgs, valueTypeString, varTypeString));
+    }
+  }
+
+  /**
+   * Prints a diagnostic about entering commonAssignmentCheck, if the showchecks option was set.
+   *
+   * @param varType the annotated type of the variable
+   * @param valueType the annotated type of the value
+   * @param valueTree the location to use when reporting the error message
+   */
+  protected final void commonAssignmentCheckStartDiagnostic(
+      AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree) {
+    if (showchecks) {
+      long valuePos = positions.getStartPosition(root, valueTree);
+      System.out.printf(
+          "%s %s (line %3d): actual tree = %s %s%n     actual: %s %s%n   expected: %s %s%n",
+          this.getClass().getSimpleName(),
+          "about to test whether actual is a subtype of expected",
+          (root.getLineMap() != null ? root.getLineMap().getLineNumber(valuePos) : -1),
+          valueTree.getKind(),
+          valueTree,
+          valueType.getKind(),
+          valueType.toString(),
+          varType.getKind(),
+          varType.toString());
+    }
+  }
+
+  /**
+   * Prints a diagnostic about exiting commonAssignmentCheck, if the showchecks option was set.
+   *
+   * @param success whether the check succeeded or failed
+   * @param extraMessage information about why the result is what it is; may be null
+   * @param varType the annotated type of the variable
+   * @param valueType the annotated type of the value
+   * @param valueTree the location to use when reporting the error message
+   */
+  protected final void commonAssignmentCheckEndDiagnostic(
+      boolean success,
+      String extraMessage,
+      AnnotatedTypeMirror varType,
+      AnnotatedTypeMirror valueType,
+      Tree valueTree) {
+    if (showchecks) {
+      commonAssignmentCheckEndDiagnostic(
+          (success
+                  ? "success: actual is subtype of expected"
+                  : "FAILURE: actual is not subtype of expected")
+              + (extraMessage == null ? "" : " because " + extraMessage),
+          varType,
+          valueType,
+          valueTree);
+    }
+  }
+
+  /**
+   * Prints a diagnostic about exiting commonAssignmentCheck, if the showchecks option was set.
+   *
+   * <p>Most clients should call {@link #commonAssignmentCheckEndDiagnostic(boolean, String,
+   * AnnotatedTypeMirror, AnnotatedTypeMirror, Tree)}. The purpose of this method is to permit
+   * customizing the message that is printed.
+   *
+   * @param message the result, plus information about why the result is what it is; may be null
+   * @param varType the annotated type of the variable
+   * @param valueType the annotated type of the value
+   * @param valueTree the location to use when reporting the error message
+   */
+  protected final void commonAssignmentCheckEndDiagnostic(
+      String message, AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree) {
+    if (showchecks) {
+      long valuePos = positions.getStartPosition(root, valueTree);
+      System.out.printf(
+          " %s (line %3d): actual tree = %s %s%n     actual: %s %s%n   expected: %s %s%n",
+          message,
+          (root.getLineMap() != null ? root.getLineMap().getLineNumber(valuePos) : -1),
+          valueTree.getKind(),
+          valueTree,
+          valueType.getKind(),
+          valueType.toString(),
+          varType.getKind(),
+          varType.toString());
+    }
+  }
+
+  /**
+   * Class that creates string representations of {@link AnnotatedTypeMirror}s which are only
+   * verbose if required to differentiate the two types.
+   */
+  private static class FoundRequired {
+    public final String found;
+    public final String required;
+
+    private FoundRequired(AnnotatedTypeMirror found, AnnotatedTypeMirror required) {
+      if (shouldPrintVerbose(found, required)) {
+        this.found = found.toString(true);
+        this.required = required.toString(true);
+      } else {
+        this.found = found.toString();
+        this.required = required.toString();
+      }
+    }
+
+    /** Create a FoundRequired for a type and bounds. */
+    private FoundRequired(AnnotatedTypeMirror found, AnnotatedTypeParameterBounds required) {
+      if (shouldPrintVerbose(found, required)) {
+        this.found = found.toString(true);
+        this.required = required.toString(true);
+      } else {
+        this.found = found.toString();
+        this.required = required.toString();
+      }
+    }
+
+    /**
+     * Creates string representations of {@link AnnotatedTypeMirror}s which are only verbose if
+     * required to differentiate the two types.
+     */
+    static FoundRequired of(AnnotatedTypeMirror found, AnnotatedTypeMirror required) {
+      return new FoundRequired(found, required);
+    }
+
+    /**
+     * Creates string representations of {@link AnnotatedTypeMirror} and {@link
+     * AnnotatedTypeParameterBounds}s which are only verbose if required to differentiate the two
+     * types.
+     */
+    static FoundRequired of(AnnotatedTypeMirror found, AnnotatedTypeParameterBounds required) {
+      return new FoundRequired(found, required);
+    }
+  }
+
+  /**
+   * Return whether or not the verbose toString should be used when printing the two annotated
+   * types.
+   *
+   * @param atm1 the first AnnotatedTypeMirror
+   * @param atm2 the second AnnotatedTypeMirror
+   * @return true iff neither argumentc contains "@", or there are two annotated types (in either
+   *     ATM) such that their toStrings are the same but their verbose toStrings differ
+   */
+  private static boolean shouldPrintVerbose(AnnotatedTypeMirror atm1, AnnotatedTypeMirror atm2) {
+    if (!atm1.toString().contains("@") && !atm2.toString().contains("@")) {
+      return true;
+    }
+    return containsSameToString(atm1, atm2);
+  }
+
+  /**
+   * Return whether or not the verbose toString should be used when printing the annotated type and
+   * the bounds it is not within.
+   *
+   * @param atm the type
+   * @param bounds the bounds
+   * @return true iff bounds does not contain "@", or there are two annotated types (in either
+   *     argument) such that their toStrings are the same but their verbose toStrings differ
+   */
+  private static boolean shouldPrintVerbose(
+      AnnotatedTypeMirror atm, AnnotatedTypeParameterBounds bounds) {
+    if (!atm.toString().contains("@") && !bounds.toString().contains("@")) {
+      return true;
+    }
+    return containsSameToString(atm, bounds.getUpperBound(), bounds.getLowerBound());
+  }
+
+  /**
+   * A scanner that indicates whether any (sub-)types have the same toString but different verbose
+   * toString. If so, the Checker Framework prints types verbosely.
+   */
+  private static SimpleAnnotatedTypeScanner<Boolean, Map<String, String>>
+      checkContainsSameToString =
+          new SimpleAnnotatedTypeScanner<>(
+              (AnnotatedTypeMirror type, Map<String, String> map) -> {
+                if (type == null) {
+                  return false;
+                }
+                String simple = type.toString();
+                String verbose = map.get(simple);
+                if (verbose == null) {
+                  map.put(simple, type.toString(true));
+                  return false;
+                } else {
+                  return !verbose.equals(type.toString(true));
+                }
+              },
+              Boolean::logicalOr,
+              false);
+
+  /**
+   * Return true iff there are two annotated types (anywhere in any ATM) such that their toStrings
+   * are the same but their verbose toStrings differ. If so, the Checker Framework prints types
+   * verbosely.
+   *
+   * @param atms annotated type mirrors to compare
+   * @return true iff there are two annotated types (anywhere in any ATM) such that their toStrings
+   *     are the same but their verbose toStrings differ
+   */
+  private static boolean containsSameToString(AnnotatedTypeMirror... atms) {
+    Map<String, String> simpleToVerbose = new HashMap<>();
+    for (AnnotatedTypeMirror atm : atms) {
+      if (checkContainsSameToString.visit(atm, simpleToVerbose)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  protected void checkArrayInitialization(
+      AnnotatedTypeMirror type, List<? extends ExpressionTree> initializers) {
+    // TODO: set assignment context like for method arguments?
+    // Also in AbstractFlow.
+    for (ExpressionTree init : initializers) {
+      commonAssignmentCheck(type, init, "array.initializer");
+    }
+  }
+
+  /**
+   * Checks that the annotations on the type arguments supplied to a type or a method invocation are
+   * within the bounds of the type variables as declared, and issues the "type.argument" error if
+   * they are not.
+   *
+   * @param toptree the tree for error reporting, only used for inferred type arguments
+   * @param paramBounds the bounds of the type parameters from a class or method declaration
+   * @param typeargs the type arguments from the type or method invocation
+   * @param typeargTrees the type arguments as trees, used for error reporting
+   */
+  protected void checkTypeArguments(
+      Tree toptree,
+      List<? extends AnnotatedTypeParameterBounds> paramBounds,
+      List<? extends AnnotatedTypeMirror> typeargs,
+      List<? extends Tree> typeargTrees,
+      CharSequence typeOrMethodName,
+      List<?> paramNames) {
+
+    // System.out.printf("BaseTypeVisitor.checkTypeArguments: %s, TVs: %s, TAs: %s, TATs: %s%n",
+    //         toptree, paramBounds, typeargs, typeargTrees);
+
+    // If there are no type variables, do nothing.
+    if (paramBounds.isEmpty()) {
+      return;
+    }
+
+    int size = paramBounds.size();
+    assert size == typeargs.size()
+        : "BaseTypeVisitor.checkTypeArguments: mismatch between type arguments: "
+            + typeargs
+            + " and type parameter bounds"
+            + paramBounds;
+
+    for (int i = 0; i < size; i++) {
+
+      AnnotatedTypeParameterBounds bounds = paramBounds.get(i);
+      AnnotatedTypeMirror typeArg = typeargs.get(i);
+
+      if (isIgnoredUninferredWildcard(bounds.getUpperBound())
+          || isIgnoredUninferredWildcard(typeArg)) {
+        continue;
+      }
+
+      if (shouldBeCaptureConverted(typeArg, bounds)) {
+        continue;
+      }
+
+      AnnotatedTypeMirror paramUpperBound = bounds.getUpperBound();
+      if (typeArg.getKind() == TypeKind.WILDCARD) {
+        paramUpperBound =
+            atypeFactory.widenToUpperBound(paramUpperBound, (AnnotatedWildcardType) typeArg);
+      }
+
+      Tree reportErrorToTree;
+      if (typeargTrees == null || typeargTrees.isEmpty()) {
+        // The type arguments were inferred, report the error on the method invocation.
+        reportErrorToTree = toptree;
+      } else {
+        reportErrorToTree = typeargTrees.get(i);
+      }
+
+      checkHasQualifierParameterAsTypeArgument(typeArg, paramUpperBound, toptree);
+      commonAssignmentCheck(
+          paramUpperBound,
+          typeArg,
+          reportErrorToTree,
+          "type.argument",
+          paramNames.get(i),
+          typeOrMethodName);
+
+      if (!atypeFactory.getTypeHierarchy().isSubtype(bounds.getLowerBound(), typeArg)) {
+        FoundRequired fr = FoundRequired.of(typeArg, bounds);
+        checker.reportError(
+            reportErrorToTree,
+            "type.argument",
+            paramNames.get(i),
+            typeOrMethodName,
+            fr.found,
+            fr.required);
+      }
+    }
+  }
+
+  /**
+   * Reports an error if the type argument has a qualifier parameter and the type parameter upper
+   * bound does not have a qualifier parameter.
+   *
+   * @param typeArgument type argument
+   * @param typeParameterUpperBound upper bound of the type parameter
+   * @param reportError where to report the error
+   */
+  private void checkHasQualifierParameterAsTypeArgument(
+      AnnotatedTypeMirror typeArgument,
+      AnnotatedTypeMirror typeParameterUpperBound,
+      Tree reportError) {
+    for (AnnotationMirror top : atypeFactory.getQualifierHierarchy().getTopAnnotations()) {
+      if (atypeFactory.hasQualifierParameterInHierarchy(typeArgument, top)
+          && !getTypeFactory().hasQualifierParameterInHierarchy(typeParameterUpperBound, top)) {
+        checker.reportError(reportError, "type.argument.hasqualparam", top);
+      }
+    }
+  }
+
+  private boolean isIgnoredUninferredWildcard(AnnotatedTypeMirror type) {
+    return atypeFactory.ignoreUninferredTypeArguments
+        && type.getKind() == TypeKind.WILDCARD
+        && ((AnnotatedWildcardType) type).isUninferredTypeArgument();
+  }
+
+  // TODO: REMOVE WHEN CAPTURE CONVERSION IS IMPLEMENTED
+  // TODO: This may not occur only in places where capture conversion occurs but in those cases
+  // TODO: The containment check provided by this method should be enough
+  /**
+   * Identifies cases that would not happen if capture conversion were implemented. These special
+   * cases should be removed when capture conversion is implemented.
+   */
+  private boolean shouldBeCaptureConverted(
+      final AnnotatedTypeMirror typeArg, final AnnotatedTypeParameterBounds bounds) {
+    return typeArg.getKind() == TypeKind.WILDCARD
+        && bounds.getUpperBound().getKind() == TypeKind.WILDCARD;
+  }
+
+  /**
+   * Indicates whether to skip subtype checks on the receiver when checking method invocability. A
+   * visitor may, for example, allow a method to be invoked even if the receivers are siblings in a
+   * hierarchy, provided that some other condition (implemented by the visitor) is satisfied.
+   *
+   * @param node the method invocation node
+   * @param methodDefinitionReceiver the ATM of the receiver of the method definition
+   * @param methodCallReceiver the ATM of the receiver of the method call
+   * @return whether to skip subtype checks on the receiver
+   */
+  protected boolean skipReceiverSubtypeCheck(
+      MethodInvocationTree node,
+      AnnotatedTypeMirror methodDefinitionReceiver,
+      AnnotatedTypeMirror methodCallReceiver) {
+    return false;
+  }
+
+  /**
+   * Tests whether the method can be invoked using the receiver of the 'node' method invocation, and
+   * issues a "method.invocation" if the invocation is invalid.
+   *
+   * <p>This implementation tests whether the receiver in the method invocation is a subtype of the
+   * method receiver type. This behavior can be specialized by overriding skipReceiverSubtypeCheck.
+   *
+   * @param method the type of the invoked method
+   * @param node the method invocation node
+   */
+  protected void checkMethodInvocability(
+      AnnotatedExecutableType method, MethodInvocationTree node) {
+    if (method.getReceiverType() == null) {
+      // Static methods don't have a receiver.
+      return;
+    }
+    if (method.getElement().getKind() == ElementKind.CONSTRUCTOR) {
+      // TODO: Explicit "this()" calls of constructors have an implicit passed
+      // from the enclosing constructor. We must not use the self type, but
+      // instead should find a way to determine the receiver of the enclosing constructor.
+      // rcv =
+      // ((AnnotatedExecutableType)atypeFactory.getAnnotatedType(atypeFactory.getEnclosingMethod(node))).getReceiverType();
+      return;
+    }
+
+    AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased();
+    AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false);
+    AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(node);
+
+    treeReceiver.addAnnotations(rcv.getEffectiveAnnotations());
+
+    if (!skipReceiverSubtypeCheck(node, methodReceiver, rcv)) {
+      commonAssignmentCheckStartDiagnostic(methodReceiver, treeReceiver, node);
+      boolean success = atypeFactory.getTypeHierarchy().isSubtype(treeReceiver, methodReceiver);
+      commonAssignmentCheckEndDiagnostic(success, null, methodReceiver, treeReceiver, node);
+      if (!success) {
+        reportMethodInvocabilityError(node, treeReceiver, methodReceiver);
+      }
+    }
+  }
+
+  /**
+   * Report a method invocability error. Allows checkers to change how the message is output.
+   *
+   * @param node the AST node at which to report the error
+   * @param found the actual type of the receiver
+   * @param expected the expected type of the receiver
+   */
+  protected void reportMethodInvocabilityError(
+      MethodInvocationTree node, AnnotatedTypeMirror found, AnnotatedTypeMirror expected) {
+    checker.reportError(
+        node,
+        "method.invocation",
+        TreeUtils.elementFromUse(node),
+        found.toString(),
+        expected.toString());
+  }
+
+  /**
+   * Check that the (explicit) annotations on a new class tree are comparable to the result type of
+   * the constructor. Issue an error if not.
+   *
+   * <p>Issue a warning if the annotations on the constructor invocation is a subtype of the
+   * constructor result type. This is equivalent to down-casting.
+   */
+  protected void checkConstructorInvocation(
+      AnnotatedDeclaredType invocation,
+      AnnotatedExecutableType constructor,
+      NewClassTree newClassTree) {
+    // Only check the primary annotations, the type arguments are checked elsewhere.
+    Set<AnnotationMirror> explicitAnnos = atypeFactory.fromNewClass(newClassTree).getAnnotations();
+    if (explicitAnnos.isEmpty()) {
+      return;
+    }
+    Set<AnnotationMirror> resultAnnos = constructor.getReturnType().getAnnotations();
+    for (AnnotationMirror explicit : explicitAnnos) {
+      AnnotationMirror resultAnno =
+          atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(resultAnnos, explicit);
+      // The return type of the constructor (resultAnnos) must be comparable to the
+      // annotations on the constructor invocation (explicitAnnos).
+      if (!(atypeFactory.getQualifierHierarchy().isSubtype(explicit, resultAnno)
+          || atypeFactory.getQualifierHierarchy().isSubtype(resultAnno, explicit))) {
+        checker.reportError(
+            newClassTree, "constructor.invocation", constructor.toString(), explicit, resultAnno);
+        return;
+      } else if (!atypeFactory.getQualifierHierarchy().isSubtype(resultAnno, explicit)) {
+        // Issue a warning if the annotations on the constructor invocation is a subtype of
+        // the constructor result type. This is equivalent to down-casting.
+        checker.reportWarning(
+            newClassTree, "cast.unsafe.constructor.invocation", resultAnno, explicit);
+        return;
+      }
+    }
+
+    // TODO: what properties should hold for constructor receivers for
+    // inner type instantiations?
+  }
+
+  /**
+   * A helper method to check that each passed argument is a subtype of the corresponding required
+   * argument, and issues "argument" error for each passed argument that not a subtype of the
+   * required one.
+   *
+   * <p>Note this method requires the lists to have the same length, as it does not handle cases
+   * like var args.
+   *
+   * @see #checkVarargs(AnnotatedTypeMirror.AnnotatedExecutableType, Tree)
+   * @param requiredArgs the required types. This may differ from the formal parameter types,
+   *     because it replaces a varargs parameter by multiple parameters with the vararg's element
+   *     type.
+   * @param passedArgs the expressions passed to the corresponding types
+   * @param executableName the name of the method or constructor being called
+   * @param paramNames the names of the callee's formal parameters
+   */
+  protected void checkArguments(
+      List<? extends AnnotatedTypeMirror> requiredArgs,
+      List<? extends ExpressionTree> passedArgs,
+      CharSequence executableName,
+      List<?> paramNames) {
+    int size = requiredArgs.size();
+    assert size == passedArgs.size()
+        : "mismatch between required args ("
+            + requiredArgs
+            + ") and passed args ("
+            + passedArgs
+            + ")";
+    int maxParamNamesIndex = paramNames.size() - 1;
+    // Rather weak assertion, due to how varargs parameters are treated.
+    assert size >= maxParamNamesIndex
+        : String.format(
+            "mismatched lengths %d %d %d checkArguments(%s, %s, %s, %s)",
+            size,
+            passedArgs.size(),
+            paramNames.size(),
+            listToString(requiredArgs),
+            listToString(passedArgs),
+            executableName,
+            listToString(paramNames));
+
+    Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = visitorState.getAssignmentContext();
+    try {
+      for (int i = 0; i < size; ++i) {
+        visitorState.setAssignmentContext(
+            Pair.of((Tree) null, (AnnotatedTypeMirror) requiredArgs.get(i)));
+        commonAssignmentCheck(
+            requiredArgs.get(i),
+            passedArgs.get(i),
+            "argument",
+            // TODO: for expanded varargs parameters, maybe adjust the name
+            paramNames.get(Math.min(i, maxParamNamesIndex)),
+            executableName);
+        // Also descend into the argument within the correct assignment context.
+        scan(passedArgs.get(i), null);
+      }
+    } finally {
+      visitorState.setAssignmentContext(preAssignmentContext);
+    }
+  }
+
+  // com.sun.tools.javac.util.List has a toString that does not include surrounding "[...]",
+  // making it hard to interpret in messages.
+  /**
+   * Produce a printed representation of a list, in the standard format with surrounding "[...]".
+   *
+   * @param lst a list to format
+   * @return the printed representation of the list
+   */
+  private String listToString(List<?> lst) {
+    StringJoiner result = new StringJoiner(",", "[", "]");
+    for (Object elt : lst) {
+      result.add(elt.toString());
+    }
+    return result.toString();
+  }
+
+  /**
+   * Returns true if both types are type variables and outer contains inner. Outer contains inner
+   * implies: {@literal inner.upperBound <: outer.upperBound outer.lowerBound <: inner.lowerBound}.
+   *
+   * @return true if both types are type variables and outer contains inner
+   */
+  protected boolean testTypevarContainment(
+      final AnnotatedTypeMirror inner, final AnnotatedTypeMirror outer) {
+    if (inner.getKind() == TypeKind.TYPEVAR && outer.getKind() == TypeKind.TYPEVAR) {
+
+      final AnnotatedTypeVariable innerAtv = (AnnotatedTypeVariable) inner;
+      final AnnotatedTypeVariable outerAtv = (AnnotatedTypeVariable) outer;
+
+      if (AnnotatedTypes.areCorrespondingTypeVariables(elements, innerAtv, outerAtv)) {
+        final TypeHierarchy typeHierarchy = atypeFactory.getTypeHierarchy();
+        return typeHierarchy.isSubtype(innerAtv.getUpperBound(), outerAtv.getUpperBound())
+            && typeHierarchy.isSubtype(outerAtv.getLowerBound(), innerAtv.getLowerBound());
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Create an OverrideChecker.
+   *
+   * <p>This exists so that subclasses can subclass OverrideChecker and use their subclass instead
+   * of using OverrideChecker itself.
+   *
+   * @param overriderTree the AST node of the overriding method or method reference
+   * @param overriderMethodType the type of the overriding method
+   * @param overriderType the type enclosing the overrider method, usually an AnnotatedDeclaredType;
+   *     for Method References may be something else
+   * @param overriderReturnType the return type of the overriding method
+   * @param overriddenMethodType the type of the overridden method
+   * @param overriddenType the declared type enclosing the overridden method
+   * @param overriddenReturnType the return type of the overridden method
+   * @return an OverrideChecker
+   */
+  protected OverrideChecker createOverrideChecker(
+      Tree overriderTree,
+      AnnotatedExecutableType overriderMethodType,
+      AnnotatedTypeMirror overriderType,
+      AnnotatedTypeMirror overriderReturnType,
+      AnnotatedExecutableType overriddenMethodType,
+      AnnotatedDeclaredType overriddenType,
+      AnnotatedTypeMirror overriddenReturnType) {
+    return new OverrideChecker(
+        overriderTree,
+        overriderMethodType,
+        overriderType,
+        overriderReturnType,
+        overriddenMethodType,
+        overriddenType,
+        overriddenReturnType);
+  }
+
+  /**
+   * Type checks that a method may override another method. Uses an OverrideChecker subclass as
+   * created by {@link #createOverrideChecker}. This version of the method uses the annotated type
+   * factory to get the annotated type of the overriding method, and does NOT expose that type.
+   *
+   * @see #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedExecutableType,
+   *     AnnotatedTypeMirror.AnnotatedDeclaredType, AnnotatedTypeMirror.AnnotatedExecutableType,
+   *     AnnotatedTypeMirror.AnnotatedDeclaredType)
+   * @param overriderTree declaration tree of overriding method
+   * @param overriderType type of overriding class
+   * @param overriddenMethodType type of overridden method
+   * @param overriddenType type of overridden class
+   * @return true if the override is allowed
+   */
+  protected boolean checkOverride(
+      MethodTree overriderTree,
+      AnnotatedDeclaredType overriderType,
+      AnnotatedExecutableType overriddenMethodType,
+      AnnotatedDeclaredType overriddenType) {
+
+    // Get the type of the overriding method.
+    AnnotatedExecutableType overriderMethodType = atypeFactory.getAnnotatedType(overriderTree);
+
+    // Call the other version of the method, which takes overriderMethodType. Both versions
+    // exist to allow checkers to override one or the other depending on their needs.
+    return checkOverride(
+        overriderTree, overriderMethodType, overriderType, overriddenMethodType, overriddenType);
+  }
+
+  /**
+   * Type checks that a method may override another method. Uses an OverrideChecker subclass as
+   * created by {@link #createOverrideChecker}. This version of the method exposes
+   * AnnotatedExecutableType of the overriding method. Override this version of the method if you
+   * need to access that type.
+   *
+   * @see #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedDeclaredType,
+   *     AnnotatedTypeMirror.AnnotatedExecutableType, AnnotatedTypeMirror.AnnotatedDeclaredType)
+   * @param overriderTree declaration tree of overriding method
+   * @param overriderMethodType type of the overriding method
+   * @param overriderType type of overriding class
+   * @param overriddenMethodType type of overridden method
+   * @param overriddenType type of overridden class
+   * @return true if the override is allowed
+   */
+  protected boolean checkOverride(
+      MethodTree overriderTree,
+      AnnotatedExecutableType overriderMethodType,
+      AnnotatedDeclaredType overriderType,
+      AnnotatedExecutableType overriddenMethodType,
+      AnnotatedDeclaredType overriddenType) {
+
+    // This needs to be done before overriderMethodType.getReturnType() and
+    // overriddenMethodType.getReturnType()
+    if (overriderMethodType.getTypeVariables().isEmpty()
+        && !overriddenMethodType.getTypeVariables().isEmpty()) {
+      overriddenMethodType = overriddenMethodType.getErased();
+    }
+
+    OverrideChecker overrideChecker =
+        createOverrideChecker(
+            overriderTree,
+            overriderMethodType,
+            overriderType,
+            overriderMethodType.getReturnType(),
+            overriddenMethodType,
+            overriddenType,
+            overriddenMethodType.getReturnType());
+
+    return overrideChecker.checkOverride();
+  }
+
+  /**
+   * Check that a method reference is allowed. Using the OverrideChecker class.
+   *
+   * @param memberReferenceTree the tree for the method reference
+   * @return true if the method reference is allowed
+   */
+  protected boolean checkMethodReferenceAsOverride(
+      MemberReferenceTree memberReferenceTree, Void p) {
+
+    Pair<AnnotatedTypeMirror, AnnotatedExecutableType> result =
+        atypeFactory.getFnInterfaceFromTree(memberReferenceTree);
+    // The type to which the member reference is assigned -- also known as the target type of the
+    // reference.
+    AnnotatedTypeMirror functionalInterface = result.first;
+    // The type of the single method that is declared by the functional interface.
+    AnnotatedExecutableType functionType = result.second;
+
+    // ========= Overriding Type =========
+    // This doesn't get the correct type for a "MyOuter.super" based on the receiver of the
+    // enclosing method.
+    // That is handled separately in method receiver check.
+
+    // The type of the expression or type use, <expression>::method or <type use>::method.
+    final ExpressionTree qualifierExpression = memberReferenceTree.getQualifierExpression();
+    final ReferenceKind memRefKind = ((JCMemberReference) memberReferenceTree).kind;
+    AnnotatedTypeMirror enclosingType;
+    if (memberReferenceTree.getMode() == ReferenceMode.NEW
+        || memRefKind == ReferenceKind.UNBOUND
+        || memRefKind == ReferenceKind.STATIC) {
+      // The "qualifier expression" is a type tree.
+      enclosingType = atypeFactory.getAnnotatedTypeFromTypeTree(qualifierExpression);
+    } else {
+      // The "qualifier expression" is an expression.
+      enclosingType = atypeFactory.getAnnotatedType(qualifierExpression);
+    }
+
+    // ========= Overriding Executable =========
+    // The ::method element, see JLS 15.13.1 Compile-Time Declaration of a Method Reference
+    ExecutableElement compileTimeDeclaration =
+        (ExecutableElement) TreeUtils.elementFromTree(memberReferenceTree);
+
+    if (enclosingType.getKind() == TypeKind.DECLARED
+        && ((AnnotatedDeclaredType) enclosingType).wasRaw()) {
+      if (memRefKind == ReferenceKind.UNBOUND) {
+        // The method reference is of the form: Type # instMethod and Type is a raw type.
+        // If the first parameter of the function type, p1, is a subtype of type, then type should
+        // be p1.  This has the effect of "inferring" the class type parameter.
+        AnnotatedTypeMirror p1 = functionType.getParameterTypes().get(0);
+        TypeMirror asSuper =
+            TypesUtils.asSuper(
+                p1.getUnderlyingType(),
+                enclosingType.getUnderlyingType(),
+                atypeFactory.getProcessingEnv());
+        if (asSuper != null) {
+          enclosingType = AnnotatedTypes.asSuper(atypeFactory, p1, enclosingType);
+        }
+      }
+      // else method reference is something like ArrayList::new
+      // TODO: Use diamond, <>, inference to infer the class type arguments.
+      // For now this case is skipped below in checkMethodReferenceInference.
+    }
+
+    // The type of the compileTimeDeclaration if it were invoked with a receiver expression
+    // of type {@code type}
+    AnnotatedExecutableType invocationType =
+        atypeFactory.methodFromUse(memberReferenceTree, compileTimeDeclaration, enclosingType)
+            .executableType;
+
+    if (checkMethodReferenceInference(memberReferenceTree, invocationType, enclosingType)) {
+      // Type argument inference is required, skip check.
+      // #checkMethodReferenceInference issued a warning.
+      return true;
+    }
+
+    // This needs to be done before invocationType.getReturnType() and
+    // functionType.getReturnType()
+    if (invocationType.getTypeVariables().isEmpty() && !functionType.getTypeVariables().isEmpty()) {
+      functionType = functionType.getErased();
+    }
+
+    // Use the function type's parameters to resolve polymorphic qualifiers.
+    QualifierPolymorphism poly = atypeFactory.getQualifierPolymorphism();
+    poly.resolve(functionType, invocationType);
+
+    AnnotatedTypeMirror invocationReturnType;
+    if (compileTimeDeclaration.getKind() == ElementKind.CONSTRUCTOR) {
+      if (enclosingType.getKind() == TypeKind.ARRAY) {
+        // Special casing for the return of array constructor
+        invocationReturnType = enclosingType;
+      } else {
+        invocationReturnType =
+            atypeFactory.getResultingTypeOfConstructorMemberReference(
+                memberReferenceTree, invocationType);
+      }
+    } else {
+      invocationReturnType = invocationType.getReturnType();
+    }
+
+    AnnotatedTypeMirror functionTypeReturnType = functionType.getReturnType();
+    if (functionTypeReturnType.getKind() == TypeKind.VOID) {
+      // If the functional interface return type is void, the overriding return type doesn't matter.
+      functionTypeReturnType = invocationReturnType;
+    }
+
+    if (functionalInterface.getKind() == TypeKind.DECLARED) {
+      // Check the member reference as if invocationType overrides functionType.
+      OverrideChecker overrideChecker =
+          createOverrideChecker(
+              memberReferenceTree,
+              invocationType,
+              enclosingType,
+              invocationReturnType,
+              functionType,
+              (AnnotatedDeclaredType) functionalInterface,
+              functionTypeReturnType);
+      return overrideChecker.checkOverride();
+    } else {
+      // If the functionalInterface is not a declared type, it must be an uninferred wildcard.
+      // In that case, only return false if uninferred type arguments should not be ignored.
+      return !atypeFactory.ignoreUninferredTypeArguments;
+    }
+  }
+
+  /** Check if method reference type argument inference is required. Issue an error if it is. */
+  private boolean checkMethodReferenceInference(
+      MemberReferenceTree memberReferenceTree,
+      AnnotatedExecutableType invocationType,
+      AnnotatedTypeMirror type) {
+    // TODO: Issue #802
+    // TODO: https://github.com/typetools/checker-framework/issues/802
+    // TODO: Method type argument inference
+    // TODO: Enable checks for method reference with inferred type arguments.
+    // For now, error on mismatch of class or method type arguments.
+    boolean requiresInference = false;
+    // If the function to which the member reference refers is generic, but the member
+    // reference does not provide method type arguments, then Java 8 inference is required.
+    // Issue 979.
+    if (!invocationType.getTypeVariables().isEmpty()
+        && (memberReferenceTree.getTypeArguments() == null
+            || memberReferenceTree.getTypeArguments().isEmpty())) {
+      // Method type args
+      requiresInference = true;
+    } else if (memberReferenceTree.getMode() == ReferenceMode.NEW) {
+      if (type.getKind() == TypeKind.DECLARED && ((AnnotatedDeclaredType) type).wasRaw()) {
+        // Class type args
+        requiresInference = true;
+      }
+    }
+    if (requiresInference) {
+      if (checker.hasOption("conservativeUninferredTypeArguments")) {
+        checker.reportWarning(memberReferenceTree, "methodref.inference.unimplemented");
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Class to perform method override and method reference checks.
+   *
+   * <p>Method references are checked similarly to method overrides, with the method reference
+   * viewed as overriding the functional interface's method.
+   *
+   * <p>Checks that an overriding method's return type, parameter types, and receiver type are
+   * correct with respect to the annotations on the overridden method's return type, parameter
+   * types, and receiver type.
+   *
+   * <p>Furthermore, any contracts on the method must satisfy behavioral subtyping, that is,
+   * postconditions must be at least as strong as the postcondition on the superclass, and
+   * preconditions must be at most as strong as the condition on the superclass.
+   *
+   * <p>This method returns the result of the check, but also emits error messages as a side effect.
+   */
+  public class OverrideChecker {
+
+    /** The declaration of an overriding method. */
+    protected final Tree overriderTree;
+    /** True if {@link #overriderTree} is a MEMBER_REFERENCE. */
+    protected final boolean isMethodReference;
+
+    /** The type of the overriding method. */
+    protected final AnnotatedExecutableType overrider;
+    /** The subtype that declares the overriding method. */
+    protected final AnnotatedTypeMirror overriderType;
+    /** The type of the overridden method. */
+    protected final AnnotatedExecutableType overridden;
+    /** The supertype that declares the overridden method. */
+    protected final AnnotatedDeclaredType overriddenType;
+    /** The teturn type of the overridden method. */
+    protected final AnnotatedTypeMirror overriddenReturnType;
+    /** The return type of the overriding method. */
+    protected final AnnotatedTypeMirror overriderReturnType;
+
+    /**
+     * Create an OverrideChecker.
+     *
+     * <p>Notice that the return types are passed in separately. This is to support some types of
+     * method references where the overrider's return type is not the appropriate type to check.
+     *
+     * @param overriderTree the AST node of the overriding method or method reference
+     * @param overrider the type of the overriding method
+     * @param overriderType the type enclosing the overrider method, usually an
+     *     AnnotatedDeclaredType; for Method References may be something else
+     * @param overriderReturnType the return type of the overriding method
+     * @param overridden the type of the overridden method
+     * @param overriddenType the declared type enclosing the overridden method
+     * @param overriddenReturnType the return type of the overridden method
+     */
+    public OverrideChecker(
+        Tree overriderTree,
+        AnnotatedExecutableType overrider,
+        AnnotatedTypeMirror overriderType,
+        AnnotatedTypeMirror overriderReturnType,
+        AnnotatedExecutableType overridden,
+        AnnotatedDeclaredType overriddenType,
+        AnnotatedTypeMirror overriddenReturnType) {
+
+      this.overriderTree = overriderTree;
+      this.overrider = overrider;
+      this.overriderType = overriderType;
+      this.overridden = overridden;
+      this.overriddenType = overriddenType;
+      this.overriddenReturnType = overriddenReturnType;
+      this.overriderReturnType = overriderReturnType;
+
+      this.isMethodReference = overriderTree.getKind() == Tree.Kind.MEMBER_REFERENCE;
+    }
+
+    /**
+     * Perform the check.
+     *
+     * @return true if the override is allowed
+     */
+    public boolean checkOverride() {
+      if (checker.shouldSkipUses(overriddenType.getUnderlyingType().asElement())) {
+        return true;
+      }
+
+      boolean result = checkReturn();
+      result &= checkParameters();
+      if (isMethodReference) {
+        result &= checkMemberReferenceReceivers();
+      } else {
+        result &= checkReceiverOverride();
+      }
+      checkPreAndPostConditions();
+      checkPurity();
+
+      return result;
+    }
+
+    /** Check that an override respects purity. */
+    private void checkPurity() {
+      String msgKey = isMethodReference ? "purity.methodref" : "purity.overriding";
+
+      // check purity annotations
+      EnumSet<Pure.Kind> superPurity =
+          PurityUtils.getPurityKinds(atypeFactory, overridden.getElement());
+      EnumSet<Pure.Kind> subPurity =
+          PurityUtils.getPurityKinds(atypeFactory, overrider.getElement());
+      if (!subPurity.containsAll(superPurity)) {
+        checker.reportError(
+            overriderTree,
+            msgKey,
+            overriderType,
+            overrider,
+            overriddenType,
+            overridden,
+            subPurity,
+            superPurity);
+      }
+    }
+
+    /** Checks that overrides obey behavioral subtyping. */
+    private void checkPreAndPostConditions() {
+      String msgKey = isMethodReference ? "methodref" : "override";
+      if (isMethodReference) {
+        // TODO: Support postconditions and method references.
+        // The parse context always expects instance methods, but method references can be static.
+        return;
+      }
+
+      ContractsFromMethod contractsUtils = atypeFactory.getContractsFromMethod();
+
+      // Check preconditions
+      Set<Precondition> superPre = contractsUtils.getPreconditions(overridden.getElement());
+      Set<Precondition> subPre = contractsUtils.getPreconditions(overrider.getElement());
+      Set<Pair<JavaExpression, AnnotationMirror>> superPre2 =
+          parseAndLocalizeContracts(superPre, overridden);
+      Set<Pair<JavaExpression, AnnotationMirror>> subPre2 =
+          parseAndLocalizeContracts(subPre, overrider);
+      @SuppressWarnings("compilermessages")
+      @CompilerMessageKey String premsg = "contracts.precondition." + msgKey;
+      checkContractsSubset(overriderType, overriddenType, subPre2, superPre2, premsg);
+
+      // Check postconditions
+      Set<Postcondition> superPost = contractsUtils.getPostconditions(overridden.getElement());
+      Set<Postcondition> subPost = contractsUtils.getPostconditions(overrider.getElement());
+      Set<Pair<JavaExpression, AnnotationMirror>> superPost2 =
+          parseAndLocalizeContracts(superPost, overridden);
+      Set<Pair<JavaExpression, AnnotationMirror>> subPost2 =
+          parseAndLocalizeContracts(subPost, overrider);
+      @SuppressWarnings("compilermessages")
+      @CompilerMessageKey String postmsg = "contracts.postcondition." + msgKey;
+      checkContractsSubset(overriderType, overriddenType, superPost2, subPost2, postmsg);
+
+      // Check conditional postconditions
+      Set<ConditionalPostcondition> superCPost =
+          contractsUtils.getConditionalPostconditions(overridden.getElement());
+      Set<ConditionalPostcondition> subCPost =
+          contractsUtils.getConditionalPostconditions(overrider.getElement());
+      // consider only 'true' postconditions
+      Set<Postcondition> superCPostTrue = filterConditionalPostconditions(superCPost, true);
+      Set<Postcondition> subCPostTrue = filterConditionalPostconditions(subCPost, true);
+      Set<Pair<JavaExpression, AnnotationMirror>> superCPostTrue2 =
+          parseAndLocalizeContracts(superCPostTrue, overridden);
+      Set<Pair<JavaExpression, AnnotationMirror>> subCPostTrue2 =
+          parseAndLocalizeContracts(subCPostTrue, overrider);
+      @SuppressWarnings("compilermessages")
+      @CompilerMessageKey String posttruemsg = "contracts.conditional.postcondition.true." + msgKey;
+      checkContractsSubset(
+          overriderType, overriddenType, superCPostTrue2, subCPostTrue2, posttruemsg);
+
+      // consider only 'false' postconditions
+      Set<Postcondition> superCPostFalse = filterConditionalPostconditions(superCPost, false);
+      Set<Postcondition> subCPostFalse = filterConditionalPostconditions(subCPost, false);
+      Set<Pair<JavaExpression, AnnotationMirror>> superCPostFalse2 =
+          parseAndLocalizeContracts(superCPostFalse, overridden);
+      Set<Pair<JavaExpression, AnnotationMirror>> subCPostFalse2 =
+          parseAndLocalizeContracts(subCPostFalse, overrider);
+      @SuppressWarnings("compilermessages")
+      @CompilerMessageKey String postfalsemsg = "contracts.conditional.postcondition.false." + msgKey;
+      checkContractsSubset(
+          overriderType, overriddenType, superCPostFalse2, subCPostFalse2, postfalsemsg);
+    }
+
+    private boolean checkMemberReferenceReceivers() {
+      JCTree.JCMemberReference memberTree = (JCTree.JCMemberReference) overriderTree;
+
+      if (overriderType.getKind() == TypeKind.ARRAY) {
+        // Assume the receiver for all method on arrays are @Top
+        // This simplifies some logic because an AnnotatedExecutableType for an array method
+        // (ie String[]::clone) has a receiver of "Array." The UNBOUND check would then
+        // have to compare "Array" to "String[]".
+        return true;
+      }
+
+      // These act like a traditional override
+      if (memberTree.kind == JCTree.JCMemberReference.ReferenceKind.UNBOUND) {
+        AnnotatedTypeMirror overriderReceiver = overrider.getReceiverType();
+        AnnotatedTypeMirror overriddenReceiver = overridden.getParameterTypes().get(0);
+        boolean success =
+            atypeFactory.getTypeHierarchy().isSubtype(overriddenReceiver, overriderReceiver);
+        if (!success) {
+          checker.reportError(
+              overriderTree,
+              "methodref.receiver",
+              overriderReceiver,
+              overriddenReceiver,
+              overriderType,
+              overrider,
+              overriddenType,
+              overridden);
+        }
+        return success;
+      }
+
+      // The rest act like method invocations
+      AnnotatedTypeMirror receiverDecl;
+      AnnotatedTypeMirror receiverArg;
+      switch (memberTree.kind) {
+        case UNBOUND:
+          throw new BugInCF("Case UNBOUND should already be handled.");
+        case SUPER:
+          receiverDecl = overrider.getReceiverType();
+          receiverArg = atypeFactory.getAnnotatedType(memberTree.getQualifierExpression());
+
+          final AnnotatedTypeMirror selfType = atypeFactory.getSelfType(memberTree);
+          receiverArg.replaceAnnotations(selfType.getAnnotations());
+          break;
+        case BOUND:
+          receiverDecl = overrider.getReceiverType();
+          receiverArg = overriderType;
+          break;
+        case IMPLICIT_INNER:
+          // JLS 15.13.1 "It is a compile-time error if the method reference expression is
+          // of the form ClassType :: [TypeArguments] new and a compile-time error would
+          // occur when determining an enclosing instance for ClassType as specified in
+          // 15.9.2 (treating the method reference expression as if it were an unqualified
+          // class instance creation expression)."
+
+          // So a member reference can only refer to an inner class constructor if a type
+          // that encloses the inner class can be found. So either "this" is that
+          // enclosing type or "this" has an enclosing type that is that type.
+          receiverDecl = overrider.getReceiverType();
+          receiverArg = atypeFactory.getSelfType(memberTree);
+          while (!TypesUtils.isErasedSubtype(
+              receiverArg.getUnderlyingType(), receiverDecl.getUnderlyingType(), types)) {
+            receiverArg = ((AnnotatedDeclaredType) receiverArg).getEnclosingType();
+          }
+
+          break;
+        case TOPLEVEL:
+        case STATIC:
+        case ARRAY_CTOR:
+        default:
+          // Intentional fallthrough
+          // These don't have receivers
+          return true;
+      }
+
+      boolean success = atypeFactory.getTypeHierarchy().isSubtype(receiverArg, receiverDecl);
+      if (!success) {
+        checker.reportError(
+            overriderTree,
+            "methodref.receiver.bound",
+            receiverArg,
+            receiverDecl,
+            receiverArg,
+            overriderType,
+            overrider);
+      }
+
+      return success;
+    }
+
+    /**
+     * Issue an "override.receiver" error if the receiver override is not valid.
+     *
+     * @return true if the override is legal
+     */
+    protected boolean checkReceiverOverride() {
+      AnnotatedDeclaredType overriderReceiver = overrider.getReceiverType();
+      AnnotatedDeclaredType overriddenReceiver = overridden.getReceiverType();
+      QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
+      // Check the receiver type.
+      // isSubtype() requires its arguments to be actual subtypes with respect to JLS, but overrider
+      // receiver is not a subtype of the overridden receiver.  So, just check primary annotations.
+      // TODO: this will need to be improved for generic receivers.
+      Set<AnnotationMirror> overriderAnnos = overriderReceiver.getAnnotations();
+      Set<AnnotationMirror> overriddenAnnos = overriddenReceiver.getAnnotations();
+      if (!qualifierHierarchy.isSubtype(overriddenAnnos, overriderAnnos)) {
+        Set<AnnotationMirror> declaredAnnos =
+            atypeFactory.getTypeDeclarationBounds(overriderType.getUnderlyingType());
+        if (qualifierHierarchy.isSubtype(overriderAnnos, declaredAnnos)
+            && qualifierHierarchy.isSubtype(declaredAnnos, overriderAnnos)) {
+          // All the type of an object must be no higher than its upper bound. So if the receiver is
+          // annotated with the upper bound qualifiers, then the override is safe.
+          return true;
+        }
+        FoundRequired pair = FoundRequired.of(overriderReceiver, overriddenReceiver);
+        checker.reportError(
+            overriderTree,
+            "override.receiver",
+            pair.found,
+            pair.required,
+            overriderType,
+            overrider,
+            overriddenType,
+            overridden);
+        return false;
+      }
+      return true;
+    }
+
+    private boolean checkParameters() {
+      List<AnnotatedTypeMirror> overriderParams = overrider.getParameterTypes();
+      List<AnnotatedTypeMirror> overriddenParams = overridden.getParameterTypes();
+
+      // Fix up method reference parameters.
+      // See https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.13.1
+      if (isMethodReference) {
+        // The functional interface of an unbound member reference has an extra parameter
+        // (the receiver).
+        if (((JCTree.JCMemberReference) overriderTree)
+            .hasKind(JCTree.JCMemberReference.ReferenceKind.UNBOUND)) {
+          overriddenParams = new ArrayList<>(overriddenParams);
+          overriddenParams.remove(0);
+        }
+        // Deal with varargs
+        if (overrider.isVarArgs() && !overridden.isVarArgs()) {
+          overriderParams = AnnotatedTypes.expandVarArgsFromTypes(overrider, overriddenParams);
+        }
+      }
+
+      boolean result = true;
+      for (int i = 0; i < overriderParams.size(); ++i) {
+        boolean success =
+            atypeFactory
+                .getTypeHierarchy()
+                .isSubtype(overriddenParams.get(i), overriderParams.get(i));
+        if (!success) {
+          success = testTypevarContainment(overriddenParams.get(i), overriderParams.get(i));
+        }
+
+        checkParametersMsg(success, i, overriderParams, overriddenParams);
+        result &= success;
+      }
+      return result;
+    }
+
+    private void checkParametersMsg(
+        boolean success,
+        int index,
+        List<AnnotatedTypeMirror> overriderParams,
+        List<AnnotatedTypeMirror> overriddenParams) {
+      if (success && !showchecks) {
+        return;
+      }
+
+      String msgKey = isMethodReference ? "methodref.param" : "override.param";
+      long valuePos =
+          overriderTree instanceof MethodTree
+              ? positions.getStartPosition(
+                  root, ((MethodTree) overriderTree).getParameters().get(index))
+              : positions.getStartPosition(root, overriderTree);
+      Tree posTree =
+          overriderTree instanceof MethodTree
+              ? ((MethodTree) overriderTree).getParameters().get(index)
+              : overriderTree;
+
+      if (showchecks) {
+        System.out.printf(
+            " %s (line %3d):%n"
+                + "     overrider: %s %s (parameter %d type %s)%n"
+                + "   overridden: %s %s"
+                + " (parameter %d type %s)%n",
+            (success
+                ? "success: overridden parameter type is subtype of overriding"
+                : "FAILURE: overridden parameter type is not subtype of overriding"),
+            (root.getLineMap() != null ? root.getLineMap().getLineNumber(valuePos) : -1),
+            overrider,
+            overriderType,
+            index,
+            overriderParams.get(index).toString(),
+            overridden,
+            overriddenType,
+            index,
+            overriddenParams.get(index).toString());
+      }
+      if (!success) {
+        FoundRequired pair =
+            FoundRequired.of(overriderParams.get(index), overriddenParams.get(index));
+        checker.reportError(
+            posTree,
+            msgKey,
+            overrider.getElement().getParameters().get(index).toString(),
+            pair.found,
+            pair.required,
+            overriderType,
+            overrider,
+            overriddenType,
+            overridden);
+      }
+    }
+
+    /**
+     * Returns true if the return type of the overridden method is a subtype of the return type of
+     * the overriding method.
+     *
+     * @return true if the return type is correct
+     */
+    private boolean checkReturn() {
+      if ((overriderReturnType.getKind() == TypeKind.VOID)) {
+        // Nothing to check.
+        return true;
+      }
+      final TypeHierarchy typeHierarchy = atypeFactory.getTypeHierarchy();
+      boolean success = typeHierarchy.isSubtype(overriderReturnType, overriddenReturnType);
+      if (!success) {
+        // If both the overridden method have type variables as return types and both
+        // types were defined in their respective methods then, they can be covariant or
+        // invariant use super/subtypes for the overrides locations
+        success = testTypevarContainment(overriderReturnType, overriddenReturnType);
+      }
+
+      // Sometimes the overridden return type of a method reference becomes a captured
+      // type.  This leads to defaulting that often makes the overriding return type
+      // invalid.  We ignore these.  This happens in Issue403/Issue404.
+      if (!success
+          && isMethodReference
+          && TypesUtils.isCaptured(overriddenReturnType.getUnderlyingType())) {
+        if (ElementUtils.isMethod(
+            overridden.getElement(), functionApply, atypeFactory.getProcessingEnv())) {
+          success =
+              typeHierarchy.isSubtype(
+                  overriderReturnType,
+                  ((AnnotatedTypeVariable) overriddenReturnType).getUpperBound());
+        }
+      }
+
+      checkReturnMsg(success);
+      return success;
+    }
+
+    /**
+     * Issue an error message or log message about checking an overriding return type.
+     *
+     * @param success whether the check succeeded or failed
+     */
+    private void checkReturnMsg(boolean success) {
+      if (success && !showchecks) {
+        return;
+      }
+
+      String msgKey = isMethodReference ? "methodref.return" : "override.return";
+      long valuePos =
+          overriderTree instanceof MethodTree
+              ? positions.getStartPosition(root, ((MethodTree) overriderTree).getReturnType())
+              : positions.getStartPosition(root, overriderTree);
+      Tree posTree =
+          overriderTree instanceof MethodTree
+              ? ((MethodTree) overriderTree).getReturnType()
+              : overriderTree;
+      // The return type of a MethodTree is null for a constructor.
+      if (posTree == null) {
+        posTree = overriderTree;
+      }
+
+      if (showchecks) {
+        System.out.printf(
+            " %s (line %3d):%n"
+                + "     overrider: %s %s (return type %s)%n"
+                + "   overridden: %s %s (return type %s)%n",
+            (success
+                ? "success: overriding return type is subtype of overridden"
+                : "FAILURE: overriding return type is not subtype of overridden"),
+            (root.getLineMap() != null ? root.getLineMap().getLineNumber(valuePos) : -1),
+            overrider,
+            overriderType,
+            overrider.getReturnType().toString(),
+            overridden,
+            overriddenType,
+            overridden.getReturnType().toString());
+      }
+      if (!success) {
+        FoundRequired pair = FoundRequired.of(overriderReturnType, overriddenReturnType);
+        checker.reportError(
+            posTree,
+            msgKey,
+            pair.found,
+            pair.required,
+            overriderType,
+            overrider,
+            overriddenType,
+            overridden);
+      }
+    }
+  }
+
+  /**
+   * Filters the set of conditional postconditions to return only those whose annotation result
+   * value matches the value of the given boolean {@code b}. For example, if {@code b == true}, then
+   * the following {@code @EnsuresNonNullIf} conditional postcondition would match:<br>
+   * {@code @EnsuresNonNullIf(expression="#1", result=true)}<br>
+   * {@code boolean equals(@Nullable Object o)}
+   *
+   * @param conditionalPostconditions each is a ConditionalPostcondition
+   * @param b the value required for the {@code result} element
+   * @return all the given conditional postconditions whose {@code result} is {@code b}
+   */
+  private Set<Postcondition> filterConditionalPostconditions(
+      Set<ConditionalPostcondition> conditionalPostconditions, boolean b) {
+    if (conditionalPostconditions.isEmpty()) {
+      return Collections.emptySet();
+    }
+
+    Set<Postcondition> result = new LinkedHashSet<>(conditionalPostconditions.size());
+    for (Contract c : conditionalPostconditions) {
+      ConditionalPostcondition p = (ConditionalPostcondition) c;
+      if (p.resultValue == b) {
+        result.add(new Postcondition(p.expressionString, p.annotation, p.contractAnnotation));
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Checks that {@code mustSubset} is a subset of {@code set} in the following sense: For every
+   * expression in {@code mustSubset} there must be the same expression in {@code set}, with the
+   * same (or a stronger) annotation.
+   *
+   * <p>This uses field {@link #visitorState} to determine where to issue an error message.
+   *
+   * @param overriderType the subtype
+   * @param overriddenType the supertype
+   * @param mustSubset annotations that should be weaker
+   * @param set anontations that should be stronger
+   * @param messageKey message key for error messages
+   */
+  private void checkContractsSubset(
+      AnnotatedTypeMirror overriderType,
+      AnnotatedDeclaredType overriddenType,
+      Set<Pair<JavaExpression, AnnotationMirror>> mustSubset,
+      Set<Pair<JavaExpression, AnnotationMirror>> set,
+      @CompilerMessageKey String messageKey) {
+    for (Pair<JavaExpression, AnnotationMirror> weak : mustSubset) {
+      boolean found = false;
+
+      for (Pair<JavaExpression, AnnotationMirror> strong : set) {
+        // are we looking at a contract of the same receiver?
+        if (weak.first.equals(strong.first)) {
+          // check subtyping relationship of annotations
+          QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
+          if (qualifierHierarchy.isSubtype(strong.second, weak.second)) {
+            found = true;
+            break;
+          }
+        }
+      }
+
+      if (!found) {
+        MethodTree methodDeclTree = visitorState.getMethodTree();
+
+        String overriddenTypeString = overriddenType.getUnderlyingType().asElement().toString();
+        String overriderTypeString;
+        if (overriderType.getKind() == TypeKind.DECLARED) {
+          DeclaredType overriderTypeMirror =
+              ((AnnotatedDeclaredType) overriderType).getUnderlyingType();
+          overriderTypeString = overriderTypeMirror.asElement().toString();
+        } else {
+          overriderTypeString = overriderType.toString();
+        }
+
+        // weak.second is the AnnotationMirror that is too strong.  It might be from the
+        // precondition or the postcondition.
+
+        // These are the annotations that are too weak.
+        StringJoiner strongRelevantAnnos = new StringJoiner(" ").setEmptyValue("no information");
+        for (Pair<JavaExpression, AnnotationMirror> strong : set) {
+          if (weak.first.equals(strong.first)) {
+            strongRelevantAnnos.add(strong.second.toString());
+          }
+        }
+
+        Object overriddenAnno;
+        Object overriderAnno;
+        if (messageKey.contains(".precondition.")) {
+          overriddenAnno = strongRelevantAnnos;
+          overriderAnno = weak.second;
+        } else {
+          overriddenAnno = weak.second;
+          overriderAnno = strongRelevantAnnos;
+        }
+
+        checker.reportError(
+            methodDeclTree,
+            messageKey,
+            weak.first,
+            methodDeclTree.getName(),
+            overriddenTypeString,
+            overriddenAnno,
+            overriderTypeString,
+            overriderAnno);
+      }
+    }
+  }
+
+  /**
+   * Localizes some contracts -- that is, viewpoint-adapts them to some method body, according to
+   * the value of {@link #visitorState}.
+   *
+   * <p>The input is a set of {@link Contract}s, each of which contains an expression string and an
+   * annotation. In a {@link Contract}, Java expressions are exactly as written in source code, not
+   * standardized or viewpoint-adapted.
+   *
+   * <p>The output is a set of pairs of {@link JavaExpression} (parsed expression string) and
+   * standardized annotation (with respect to the path of {@link #visitorState}. This method
+   * discards any contract whose expression cannot be parsed into a JavaExpression.
+   *
+   * @param contractSet a set of contracts
+   * @param methodType the type of the method that the contracts are for
+   * @return pairs of (expression, AnnotationMirror), which are localized contracts
+   */
+  private Set<Pair<JavaExpression, AnnotationMirror>> parseAndLocalizeContracts(
+      Set<? extends Contract> contractSet, AnnotatedExecutableType methodType) {
+    if (contractSet.isEmpty()) {
+      return Collections.emptySet();
+    }
+
+    // This is the path to a place where the contract is being used, which might or might not be
+    // where the contract was defined.  For example, methodTree might be an overriding
+    // definition, and the contract might be for a superclass.
+    MethodTree methodTree = visitorState.getMethodTree();
+
+    StringToJavaExpression stringToJavaExpr =
+        expression -> {
+          JavaExpression javaExpr =
+              StringToJavaExpression.atMethodDecl(expression, methodType.getElement(), checker);
+          // methodType.getElement() is not necessarily the same method as methodTree, so
+          // viewpoint-adapt it to methodTree.
+          return javaExpr.atMethodBody(methodTree);
+        };
+
+    Set<Pair<JavaExpression, AnnotationMirror>> result = new HashSet<>(contractSet.size());
+    for (Contract p : contractSet) {
+      String expressionString = p.expressionString;
+      AnnotationMirror annotation =
+          p.viewpointAdaptDependentTypeAnnotation(atypeFactory, stringToJavaExpr, methodTree);
+      JavaExpression exprJe;
+      try {
+        // TODO: currently, these expressions are parsed many times.
+        // This could be optimized to store the result the first time.
+        // (same for other annotations)
+        exprJe = stringToJavaExpr.toJavaExpression(expressionString);
+      } catch (JavaExpressionParseException e) {
+        // report errors here
+        checker.report(methodTree, e.getDiagMessage());
+        continue;
+      }
+      result.add(Pair.of(exprJe, annotation));
+    }
+    return result;
+  }
+
+  /**
+   * Call this only when the current path is an identifier.
+   *
+   * @return the enclosing member select, or null if the identifier is not the field in a member
+   *     selection
+   */
+  protected MemberSelectTree enclosingMemberSelect() {
+    TreePath path = this.getCurrentPath();
+    assert path.getLeaf().getKind() == Tree.Kind.IDENTIFIER
+        : "expected identifier, found: " + path.getLeaf();
+    if (path.getParentPath().getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) {
+      return (MemberSelectTree) path.getParentPath().getLeaf();
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Returns the statement that encloses the given one.
+   *
+   * @param tree an AST node that is on the current path
+   * @return the statement that encloses the given one
+   */
+  protected Tree enclosingStatement(@FindDistinct Tree tree) {
+    TreePath path = this.getCurrentPath();
+    while (path != null && path.getLeaf() != tree) {
+      path = path.getParentPath();
+    }
+
+    if (path != null) {
+      return path.getParentPath().getLeaf();
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  public Void visitIdentifier(IdentifierTree node, Void p) {
+    checkAccess(node, p);
+    return super.visitIdentifier(node, p);
+  }
+
+  protected void checkAccess(IdentifierTree node, Void p) {
+    MemberSelectTree memberSel = enclosingMemberSelect();
+    ExpressionTree tree;
+    Element elem;
+
+    if (memberSel == null) {
+      tree = node;
+      elem = TreeUtils.elementFromUse(node);
+    } else {
+      tree = memberSel;
+      elem = TreeUtils.elementFromUse(memberSel);
+    }
+
+    if (elem == null || !elem.getKind().isField()) {
+      return;
+    }
+
+    AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree);
+
+    checkAccessAllowed(elem, receiver, tree);
+  }
+
+  /**
+   * Issues an error if access not allowed, based on an @Unused annotation.
+   *
+   * @param field the field to be accessed, whose declaration might be annotated by @Unused. It can
+   *     also be (for example) {@code this}, in which case {@code receiverType} is null.
+   * @param receiverType the type of the expression whose field is accessed; null if the field is
+   *     static
+   * @param accessTree the access expression
+   */
+  protected void checkAccessAllowed(
+      Element field, AnnotatedTypeMirror receiverType, @FindDistinct ExpressionTree accessTree) {
+    AnnotationMirror unused = atypeFactory.getDeclAnnotation(field, Unused.class);
+    if (unused == null) {
+      return;
+    }
+
+    String when = AnnotationUtils.getElementValueClassName(unused, unusedWhenElement).toString();
+
+    // TODO: Don't just look at the receiver type, but at the declaration annotations on the
+    // receiver.  (That will enable handling type annotations that are not part of the type
+    // system being checked.)
+
+    // TODO: This requires exactly the same type qualifier, but it should permit subqualifiers.
+    if (!AnnotationUtils.containsSameByName(receiverType.getAnnotations(), when)) {
+      return;
+    }
+
+    Tree tree = this.enclosingStatement(accessTree);
+
+    if (tree != null
+        && tree.getKind() == Tree.Kind.ASSIGNMENT
+        && ((AssignmentTree) tree).getVariable() == accessTree
+        && ((AssignmentTree) tree).getExpression().getKind() == Tree.Kind.NULL_LITERAL) {
+      // Assigning unused to null is OK.
+      return;
+    }
+
+    checker.reportError(accessTree, "unallowed.access", field, receiverType);
+  }
+
+  /**
+   * Tests that the qualifiers present on {@code useType} are valid qualifiers, given the qualifiers
+   * on the declaration of the type, {@code declarationType}.
+   *
+   * <p>The check is shallow, as it does not descend into generic or array types (i.e. only
+   * performing the validity check on the raw type or outermost array dimension). {@link
+   * BaseTypeVisitor#validateTypeOf(Tree)} would call this for each type argument or array dimension
+   * separately.
+   *
+   * <p>In most cases, {@code useType} simply needs to be a subtype of {@code declarationType}. If a
+   * type system makes exceptions to this rule, its implementation should override this method.
+   *
+   * <p>This method is not called if {@link
+   * BaseTypeValidator#shouldCheckTopLevelDeclaredOrPrimitiveType(AnnotatedTypeMirror, Tree)}
+   * returns false -- by default, it is not called on the top level for locals and expressions. To
+   * enforce a type validity property everywhere, override methods such as {@link
+   * BaseTypeValidator#visitDeclared} rather than this method.
+   *
+   * @param declarationType the type of the class (TypeElement)
+   * @param useType the use of the class (instance type)
+   * @param tree the tree where the type is used
+   * @return true if the useType is a valid use of elemType
+   */
+  public boolean isValidUse(
+      AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) {
+    // Don't use isSubtype(ATM, ATM) because it will return false if the types have qualifier
+    // parameters.
+    Set<? extends AnnotationMirror> tops = atypeFactory.getQualifierHierarchy().getTopAnnotations();
+    Set<AnnotationMirror> upperBounds =
+        atypeFactory
+            .getQualifierUpperBounds()
+            .getBoundQualifiers(declarationType.getUnderlyingType());
+    for (AnnotationMirror top : tops) {
+      AnnotationMirror upperBound =
+          atypeFactory.getQualifierHierarchy().findAnnotationInHierarchy(upperBounds, top);
+      AnnotationMirror qualifier = useType.getAnnotationInHierarchy(top);
+      if (!atypeFactory.getQualifierHierarchy().isSubtype(qualifier, upperBound)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Tests that the qualifiers present on the primitive type are valid.
+   *
+   * @param type the use of the primitive type
+   * @param tree the tree where the type is used
+   * @return true if the type is a valid use of the primitive type
+   */
+  public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) {
+    Set<AnnotationMirror> bounds = atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType());
+    return atypeFactory.getQualifierHierarchy().isSubtype(type.getAnnotations(), bounds);
+  }
+
+  /**
+   * Tests that the qualifiers present on the array type are valid. This method will be invoked for
+   * each array level independently, i.e. this method only needs to check the top-level qualifiers
+   * of an array.
+   *
+   * @param type the array type use
+   * @param tree the tree where the type is used
+   * @return true if the type is a valid array type
+   */
+  public boolean isValidUse(AnnotatedArrayType type, Tree tree) {
+    Set<AnnotationMirror> bounds = atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType());
+    return atypeFactory.getQualifierHierarchy().isSubtype(type.getAnnotations(), bounds);
+  }
+
+  /**
+   * Tests whether the tree expressed by the passed type tree is a valid type, and emits an error if
+   * that is not the case (e.g. '@Mutable String'). If the tree is a method or constructor, check
+   * the return type.
+   *
+   * @param tree the AST type supplied by the user
+   */
+  public boolean validateTypeOf(Tree tree) {
+    AnnotatedTypeMirror type;
+    // It's quite annoying that there is no TypeTree.
+    switch (tree.getKind()) {
+      case PRIMITIVE_TYPE:
+      case PARAMETERIZED_TYPE:
+      case TYPE_PARAMETER:
+      case ARRAY_TYPE:
+      case UNBOUNDED_WILDCARD:
+      case EXTENDS_WILDCARD:
+      case SUPER_WILDCARD:
+      case ANNOTATED_TYPE:
+        type = atypeFactory.getAnnotatedTypeFromTypeTree(tree);
+        break;
+      case METHOD:
+        type = atypeFactory.getMethodReturnType((MethodTree) tree);
+        if (type == null || type.getKind() == TypeKind.VOID) {
+          // Nothing to do for void methods.
+          // Note that for a constructor the AnnotatedExecutableType does
+          // not use void as return type.
+          return true;
+        }
+        break;
+      default:
+        type = atypeFactory.getAnnotatedType(tree);
+    }
+    return validateType(tree, type);
+  }
+
+  /**
+   * Tests whether the type and corresponding type tree is a valid type, and emits an error if that
+   * is not the case (e.g. '@Mutable String'). If the tree is a method or constructor, check the
+   * return type.
+   *
+   * @param tree the type tree supplied by the user
+   * @param type the type corresponding to tree
+   * @return true if the type is valid
+   */
+  protected boolean validateType(Tree tree, AnnotatedTypeMirror type) {
+    return typeValidator.isValid(type, tree);
+  }
+
+  // This is a test to ensure that all types are valid
+  protected final TypeValidator typeValidator;
+
+  protected TypeValidator createTypeValidator() {
+    return new BaseTypeValidator(checker, this, atypeFactory);
+  }
+
+  // **********************************************************************
+  // Random helper methods
+  // **********************************************************************
+
+  /**
+   * Tests whether the expression should not be checked because of the tree referring to unannotated
+   * classes, as specified in the {@code checker.skipUses} property.
+   *
+   * <p>It returns true if exprTree is a method invocation or a field access to a class whose
+   * qualified name matches @{link checker.skipUses} expression.
+   *
+   * @param exprTree any expression tree
+   * @return true if checker should not test exprTree
+   */
+  protected final boolean shouldSkipUses(ExpressionTree exprTree) {
+    // System.out.printf("shouldSkipUses: %s: %s%n", exprTree.getClass(), exprTree);
+
+    Element elm = TreeUtils.elementFromTree(exprTree);
+    return checker.shouldSkipUses(elm);
+  }
+
+  // **********************************************************************
+  // Overriding to avoid visit part of the tree
+  // **********************************************************************
+
+  /** Override Compilation Unit so we won't visit package names or imports. */
+  @Override
+  public Void visitCompilationUnit(CompilationUnitTree node, Void p) {
+    Void r = scan(node.getPackageAnnotations(), p);
+    // r = reduce(scan(node.getPackageName(), p), r);
+    // r = reduce(scan(node.getImports(), p), r);
+    r = reduce(scan(node.getTypeDecls(), p), r);
+    return r;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/basetype/TypeValidator.java b/framework/src/main/java/org/checkerframework/common/basetype/TypeValidator.java
new file mode 100644
index 0000000..db522cd
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/basetype/TypeValidator.java
@@ -0,0 +1,21 @@
+package org.checkerframework.common.basetype;
+
+import com.sun.source.tree.Tree;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/**
+ * TypeValidator ensures that a type for a given tree is valid both for the tree and the type system
+ * that is being used to check the tree.
+ */
+public interface TypeValidator {
+
+  /**
+   * The entry point to the type validator. Validate the type against the given tree.
+   *
+   * @param type the type to validate
+   * @param tree the tree from which the type originated. If the tree is a method tree, then
+   *     validate its return type. If the tree is a variable tree, then validate its field type.
+   * @return true, iff the type is valid
+   */
+  public boolean isValid(AnnotatedTypeMirror type, Tree tree);
+}
diff --git a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties
new file mode 100644
index 0000000..8c726b6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties
@@ -0,0 +1,117 @@
+### Error messages for BaseTypeChecker
+receiver=incompatible types.%nfound   : %s%nrequired: %s
+type.anno.before.modifier=write type annotation %s immediately before type, after modifiers %s
+type.anno.before.decl.anno=write type annotations %s immediately before type, after declaration annotation %s
+
+array.initializer=incompatible types in array initializer.%nfound   : %s%nrequired: %s
+assignment=incompatible types in assignment.%nfound   : %s%nrequired: %s
+compound.assignment=expression type incompatible with left-hand side in compound assignment.%nfound   : %s%nrequired: %s
+unary.increment=increment result incompatible with variable declared type.%nfound   : %s%nrequired: %s
+unary.decrement=decrement result incompatible with variable declared type.%nfound   : %s%nrequired: %s
+enhancedfor=incompatible types in enhanced for loop.%nfound   : %s%nrequired: %s
+vector.copyinto=incompatible component type in Vector.copyinto.%nfound   : %s%nrequired: %s
+return=incompatible types in return.%ntype of expression: %s%nmethod return type: %s
+annotation=incompatible types in annotation.%nfound   : %s%nrequired: %s
+conditional=incompatible types in conditional expression.%nfound   : %s%nrequired: %s
+type.argument=incompatible type argument for type parameter %s of %s.%nfound   : %s%nrequired: %s
+argument=incompatible argument for parameter %s of %s.%nfound   : %s%nrequired: %s
+varargs=incompatible types in varargs.%nfound   : %s%nrequired: %s
+type.incompatible=incompatible types.%nfound   : %s%nrequired: %s
+bound=incompatible bounds in %s%ntype: %s%nupper bound: %s%nlower bound: %s
+monotonic=cannot assign %s to %s (monotonic type).%ntype of right-hand-side: %s
+type.invalid=invalid type: annotations %s in type "%s"
+conflicting.annos=invalid type: conflicting annotations %s in type "%s"
+too.few.annotations=invalid type: missing annotations %s in type "%s"
+annotations.on.use=invalid type: annotations %s conflict with declaration of type %s
+cast.unsafe=cast from "%s" to "%s" cannot be statically verified
+invariant.cast.unsafe=cannot cast from "%s" to "%s"
+cast.unsafe.constructor.invocation=constructor invocation cast from "%s" to "%s" cannot be statically verified
+exception.parameter=invalid type in catch argument.%nfound   : %s%nrequired: %s
+throw=invalid type thrown.%nfound   : %s%nrequired: %s
+expression.unparsable=Expression invalid in dependent type annotation: %s
+explicit.annotation.ignored=The qualifier %s is ignored in favor of %s. Either delete %s or change it to %s.
+
+override.return=Incompatible return type.%nfound   : %s%nrequired: %s%nConsequence: method in %s%n  %s%ncannot override method in %s%n  %s
+override.param=Incompatible parameter type for %s.%nfound   : %s%nrequired: %s%nConsequence: method in %s%n  %s%ncannot override method in %s%n  %s
+override.receiver=Incompatible receiver type%nfound   : %s%nrequired: %s%nConsequence: method in %s%n  %s%ncannot override method in %s%n  %s
+methodref.return=Incompatible return type%nfound   : %s%nrequired: %s%nConsequence: method in %s%n  %s%nis not a valid method reference for method in %s%n  %s
+methodref.param=Incompatible parameter type for %s%nfound   : %s%nrequired: %s%nConsequence: method in %s%n  %s%nis not a valid method reference for method in %s%n  %s
+methodref.receiver=Incompatible receiver type%nfound   : %s%nrequired: %s%nConsequence: method in %s%n  %s%nis not a valid method reference for method in %s%n  %s
+methodref.receiver.bound=Incompatible receiver type%nfound   : %s%nrequired: %s%nConsequence: method%n  %s%nis not a valid method reference for method in %s%n  %s
+lambda.param=incompatible parameter types for parameter %s in lambda expression.%nfound   : %s%nrequired: %s
+
+# The first argument is a string like "annotation @KeyFor on parameter 'param1'"
+# or "postcondition @EnsuresKeyFor on the declaration".
+expression.parameter.name=The %s of method '%s' contains invalid identifier '%s'. Use "#%d" for the formal parameter.
+expression.parameter.name.shadows.field=The %s of method '%s' contains ambiguous identifier '%s'. Use "this.%s" for the field, or "#%d" for the formal parameter.
+
+inconsistent.constructor.type=Constructor type (%s) is a subtype of the top type (%s), therefore it cannot be statically verified.
+super.invocation=Constructor of type %s cannot call %s of type %s.
+this.invocation=Constructor of type %s cannot call %s of type %s.
+method.invocation=call to %s not allowed on the given receiver.%nfound   : %s%nrequired: %s
+constructor.invocation=creation of %s not allowed with given receiver.%nfound   : %s%nrequired: %s
+type.arguments.not.inferred=Could not infer type arguments for %s.
+type.argument.hasqualparam=Types with qualifier parameters are not allowed as type arguments.%nfound qualifier parameter of %s hierarchy.
+declaration.inconsistent.with.extends.clause=Class %s cannot extend %s
+declaration.inconsistent.with.implements.clause=Class %s cannot implement %s
+
+unallowed.access=access of the field (%s) is not permitted on receiver of type (%s)
+cast.redundant=Redundant cast;%ntype   : %s
+
+# TODO: The call.{constructor,method} messages should take an argument indicating which method or constructor is the problem
+purity.deterministic.constructor=a constructor cannot be deterministic
+purity.deterministic.void.method=a method without return value cannot be deterministic
+purity.methodref=Incompatible purity declaration%nMethod in %s%n  %s%n  is not a valid method reference for method in %s%n  %s%nfound   : %s%nrequired: %s
+purity.overriding=Incompatible purity declaration%nMethod in %s%n  %s%n  cannot override method in %s%n  %s%nfound   : %s%nrequired: %s
+purity.not.deterministic.assign.array=array assignment not allowed in deterministic method
+purity.not.deterministic.assign.field=field assignment not allowed in deterministic method
+purity.not.deterministic.call=call to non-deterministic method %s not allowed in deterministic method
+purity.not.deterministic.catch=catch block not allowed in deterministic method
+purity.not.deterministic.object.creation=object creation not allowed in deterministic method
+purity.not.deterministic.not.sideeffectfree.assign.array=array assignment not allowed in deterministic side-effect-free method
+purity.not.deterministic.not.sideeffectfree.assign.field=field assignment not allowed in deterministic side-effect-free method
+purity.not.deterministic.not.sideeffectfree.call=call to non-deterministic side-effecting %s not allowed in deterministic side-effect-free method
+purity.not.sideeffectfree.call=call to side-effecting %s not allowed in side-effect-free method
+purity.more.deterministic=the method %s could be declared as @Deterministic
+purity.more.pure=the method %s could be declared as @Pure
+purity.more.sideeffectfree=the method %s could be declared as @SideEffectFree
+
+flowexpr.parse.index.too.big=the method does not have a parameter %s
+flowexpr.parse.error=cannot parse the expression %s
+flowexpr.parse.error.postcondition=error parsing the postcondition expression for %s%ncannot parse the expression %s
+flowexpr.parse.context.not.determined=could not determine the context at '%s' with which to parse expressions
+flowexpr.parameter.not.final=parameter %s in '%s' is not effectively final (i.e., it gets re-assigned)
+contracts.precondition=precondition of %s is not satisfied.%nfound   : %s%nrequired: %s
+contracts.postcondition=postcondition of %s is not satisfied.%nfound   : %s%nrequired: %s
+contracts.conditional.postcondition=conditional postcondition is not satisfied when %s returns %s.%nfound   : %s%nrequired: %s
+contracts.conditional.postcondition.returntype=this annotation is only valid for methods with return type 'boolean'
+# Same text for "override" and "methodref", but different key.
+contracts.precondition.override=Subclass precondition is stronger for '%s' in %s.%n  In superclass %s: %s%n  In subclass %s: %s
+contracts.postcondition.override=Subclass postcondition is weaker for '%s' in %s.%n  In superclass %s: %s%n  In subclass %s: %s
+contracts.conditional.postcondition.true.override=Subclass postcondition with result=true is weaker for '%s' in %s.%n  In superclass %s: %s%n  In subclass %s: %s
+contracts.conditional.postcondition.false.override=Subclass postcondition with result=false is weaker for '%s' in %s.%n  In superclass %s: %s%n  In subclass %s: %s
+contracts.precondition.methodref=Subclass precondition is stronger for '%s' in %s.%n  In superclass %s: %s%n  In subclass %s: %s
+contracts.postcondition.methodref=Subclass postcondition is weaker for '%s' in %s.%n  In superclass %s: %s%n  In subclass %s: %s
+contracts.conditional.postcondition.true.methodref=Subclass postcondition with result=true is weaker for '%s' in %s.%n  In superclass %s: %s%n  In subclass %s: %s
+contracts.conditional.postcondition.false.methodref=Subclass postcondition with result=false is weaker for '%s' in %s.%n  In superclass %s: %s%n  In subclass %s: %s
+
+lambda.unimplemented=This version of the Checker Framework does not type-check lambda expressions.
+methodref.inference.unimplemented=This version of the Checker Framework does not type-check method references with implicit type arguments.
+
+invalid.polymorphic.qualifier=Cannot use polymorphic qualifier %s %s
+invalid.polymorphic.qualifier.use=Cannot use polymorphic qualifier %s on a field unless the class is annotated with @HasQualifierParameter
+missing.has.qual.param=Missing @HasQualifierParameter for hierarchy %s.%nClass extends or implements a type that has a qualifier parameter.
+conflicting.qual.param=Conflicting @HasQualifierParameter.%nClass has both @HasQualifierParameter and @NoQualifierParameter for %s.
+
+
+field.invariant.not.found=the field invariant annotation refers to fields not found in a superclass%nfields not found: %s
+field.invariant.not.final=the field invariant annotation refers to fields that are not final%nfields not final: %s
+field.invariant.not.subtype=the qualifier for field %s is not a subtype of the declared type%nfound: %s%ndeclared type: %s
+field.invariant.not.wellformed=the field invariant annotation does not have equal numbers of fields and qualifiers.
+field.invariant.not.found.superclass=the field invariant annotation is missing fields that are listed in the superclass field invariant.%nfields not found: %s
+field.invariant.not.subtype.superclass=the qualifier for field %s is not a subtype of the qualifier in the superclass field invariant%nfound: %s%nsuperclass type: %s
+
+invalid.annotation.location.bytecode=found annotation in unexpected location in bytecode on element: %s %nUse -AignoreInvalidAnnotationLocations to suppress this warning
+instanceof.unsafe='%s' instanceof '%s' cannot be statically verified.
+
+anno.on.irrelevant=Annotation %s is not applicable to %s
diff --git a/framework/src/main/java/org/checkerframework/common/basetype/package-info.java b/framework/src/main/java/org/checkerframework/common/basetype/package-info.java
new file mode 100644
index 0000000..6be8169
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/basetype/package-info.java
@@ -0,0 +1,10 @@
+/**
+ * Contains a simple type-checker plug-in that performs assignment and pseudo-assignment checks on
+ * annotated types.
+ *
+ * <p>Most type-checker implementations will want to use the classes in this package as the base of
+ * their own type-checkers.
+ *
+ * @checker_framework.manual #creating-a-checker Writing a checker
+ */
+package org.checkerframework.common.basetype;
diff --git a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java
new file mode 100644
index 0000000..8396e68
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java
@@ -0,0 +1,264 @@
+package org.checkerframework.common.initializedfields;
+
+import com.sun.source.tree.VariableTree;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Options;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.common.accumulation.AccumulationAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.initializedfields.qual.EnsuresInitializedFields;
+import org.checkerframework.common.initializedfields.qual.InitializedFields;
+import org.checkerframework.common.initializedfields.qual.InitializedFieldsBottom;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.util.Contract;
+import org.checkerframework.framework.util.ContractsFromMethod;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.UserError;
+
+/** The annotated type factory for the Initialized Fields Checker. */
+public class InitializedFieldsAnnotatedTypeFactory extends AccumulationAnnotatedTypeFactory {
+
+  /**
+   * The type factories that determine whether the default value is consistent with the annotated
+   * type. If empty, warn about all uninitialized fields.
+   */
+  List<GenericAnnotatedTypeFactory<?, ?, ?, ?>> defaultValueAtypeFactories;
+
+  /**
+   * Creates a new InitializedFieldsAnnotatedTypeFactory.
+   *
+   * @param checker the checker
+   */
+  public InitializedFieldsAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker, InitializedFields.class, InitializedFieldsBottom.class);
+
+    String[] checkerNames = getCheckerNames();
+
+    defaultValueAtypeFactories = new ArrayList<>();
+    for (String checkerName : checkerNames) {
+      if (checkerName.equals(InitializedFieldsChecker.class.getCanonicalName())) {
+        continue;
+      }
+      @SuppressWarnings("signature:argument") // -processor is a binary name
+      GenericAnnotatedTypeFactory<?, ?, ?, ?> atf = getTypeFactory(checkerName);
+      if (atf != null) {
+        defaultValueAtypeFactories.add(atf);
+      }
+    }
+
+    this.postInit();
+  }
+
+  /**
+   * Returns the names of the annotation processors that are being run.
+   *
+   * @return the names of the annotation processors that are being run
+   */
+  @SuppressWarnings("JdkObsolete") // ClassLoader.getResources returns an Enumeration
+  private String[] getCheckerNames() {
+    Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
+    String processorArg = Options.instance(context).get("-processor");
+    if (processorArg != null) {
+      return processorArg.split(",");
+    }
+    try {
+      String filename = "META-INF/services/javax.annotation.processing.Processor";
+      List<String> lines = new ArrayList<>();
+      Enumeration<URL> urls = getClass().getClassLoader().getResources(filename);
+      while (urls.hasMoreElements()) {
+        URL url = urls.nextElement();
+        BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
+        lines.addAll(in.lines().collect(Collectors.toList()));
+      }
+      String[] result = lines.toArray(new String[0]);
+      return result;
+    } catch (IOException e) {
+      throw new BugInCF(e);
+    }
+  }
+
+  /**
+   * Returns the type factory for the given annotation processor, if it is type-checker.
+   *
+   * @param processorName the fully-qualified class name of an annotation processor
+   * @return the type factory for the given annotation processor, or null if it's not a checker
+   */
+  GenericAnnotatedTypeFactory<?, ?, ?, ?> getTypeFactory(@BinaryName String processorName) {
+    try {
+      Class<?> checkerClass = Class.forName(processorName);
+      if (!BaseTypeChecker.class.isAssignableFrom(checkerClass)) {
+        return null;
+      }
+      @SuppressWarnings("unchecked")
+      BaseTypeChecker c =
+          ((Class<? extends BaseTypeChecker>) checkerClass).getDeclaredConstructor().newInstance();
+      c.init(processingEnv);
+      c.initChecker();
+      BaseTypeVisitor<?> v = c.createSourceVisitorPublic();
+      GenericAnnotatedTypeFactory<?, ?, ?, ?> atf = v.createTypeFactoryPublic();
+      if (atf == null) {
+        throw new UserError("Cannot find %s; check the classpath or processorpath", processorName);
+      }
+      return atf;
+    } catch (ClassNotFoundException
+        | InstantiationException
+        | InvocationTargetException
+        | IllegalAccessException
+        | NoSuchMethodException e) {
+      throw new UserError("Problem instantiating " + processorName, e);
+    }
+  }
+
+  @Override
+  public InitializedFieldsContractsFromMethod getContractsFromMethod() {
+    return new InitializedFieldsContractsFromMethod(this);
+  }
+
+  /**
+   * A subclass of ContractsFromMethod that adds a postcondition contract to each constructor,
+   * requiring that it initializes all fields.
+   */
+  private class InitializedFieldsContractsFromMethod extends ContractsFromMethod {
+    /**
+     * Creates an InitializedFieldsContractsFromMethod for the given factory.
+     *
+     * @param factory the type factory associated with the newly-created ContractsFromMethod
+     */
+    public InitializedFieldsContractsFromMethod(GenericAnnotatedTypeFactory<?, ?, ?, ?> factory) {
+      super(factory);
+    }
+
+    @Override
+    public Set<Contract.Postcondition> getPostconditions(ExecutableElement executableElement) {
+      Set<Contract.Postcondition> result = super.getPostconditions(executableElement);
+
+      // Only process methods defined in source code being type-checked.
+      if (declarationFromElement(executableElement) != null) {
+
+        if (executableElement.getKind() == ElementKind.CONSTRUCTOR) {
+          // It's a constructor
+
+          String[] fieldsToInitialize =
+              fieldsToInitialize((TypeElement) executableElement.getEnclosingElement());
+          if (fieldsToInitialize.length != 0) {
+
+            AnnotationMirror initializedFieldsAnno;
+            {
+              AnnotationBuilder builder =
+                  new AnnotationBuilder(processingEnv, InitializedFields.class);
+              builder.setValue("value", fieldsToInitialize);
+              initializedFieldsAnno = builder.build();
+            }
+            AnnotationMirror ensuresAnno;
+            {
+              AnnotationBuilder builder =
+                  new AnnotationBuilder(processingEnv, EnsuresInitializedFields.class);
+              builder.setValue("value", new String[] {"this"});
+              builder.setValue("fields", fieldsToInitialize);
+              ensuresAnno = builder.build();
+            }
+            Contract.Postcondition ensuresContract =
+                new Contract.Postcondition("this", initializedFieldsAnno, ensuresAnno);
+
+            result.add(ensuresContract);
+          }
+        }
+      }
+
+      return result;
+    }
+  }
+
+  /**
+   * Returns the fields that the constructor must initialize. These are the fields F declared in
+   * this class that satisfy all of the following conditions:
+   *
+   * <ul>
+   *   <li>F is a non-final field (if final, Java will issue a warning, so we don't need to).
+   *   <li>F's declaration has no initializer.
+   *   <li>No initialization block or static initialization block sets the field. (This is handled
+   *       automatically because dataflow visits (static) initialization blocks as part of the
+   *       constructor.)
+   *   <li>F's annotated type is not consistent with the default value (0, 0.0, false, or null)
+   * </ul>
+   *
+   * @param type the type whose fields to list
+   * @return the fields whose type is not consistent with the default value, so the constructor must
+   *     initialize them
+   */
+  // It is a bit wasteful that this is recomputed for each constructor.
+  private String[] fieldsToInitialize(TypeElement type) {
+    List<String> result = new ArrayList<String>();
+
+    for (Element member : type.getEnclosedElements()) {
+
+      if (member.getKind() != ElementKind.FIELD) {
+        continue;
+      }
+
+      VariableElement field = (VariableElement) member;
+      if (ElementUtils.isFinal(field)) {
+        continue;
+      }
+
+      VariableTree fieldTree = (VariableTree) declarationFromElement(field);
+      if (fieldTree.getInitializer() != null) {
+        continue;
+      }
+
+      if (!defaultValueIsOK(field)) {
+        result.add(field.getSimpleName().toString());
+      }
+    }
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns true if the default field value (0, false, or null) is consistent with the field's
+   * declared type.
+   *
+   * @param field a field
+   * @return true if the default field value is consistent with the field's declared type
+   */
+  private boolean defaultValueIsOK(VariableElement field) {
+    if (defaultValueAtypeFactories.isEmpty()) {
+      return false;
+    }
+
+    for (GenericAnnotatedTypeFactory<?, ?, ?, ?> defaultValueAtypeFactory :
+        defaultValueAtypeFactories) {
+      defaultValueAtypeFactory.setRoot(root);
+
+      AnnotatedTypeMirror fieldType = defaultValueAtypeFactory.getAnnotatedType(field);
+      AnnotatedTypeMirror defaultValueType =
+          defaultValueAtypeFactory.getDefaultValueAnnotatedType(fieldType.getUnderlyingType());
+      if (!defaultValueAtypeFactory.getTypeHierarchy().isSubtype(defaultValueType, fieldType)) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsChecker.java b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsChecker.java
new file mode 100644
index 0000000..7120b00
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsChecker.java
@@ -0,0 +1,10 @@
+package org.checkerframework.common.initializedfields;
+
+import org.checkerframework.common.accumulation.AccumulationChecker;
+
+/**
+ * The Initialized Fields Checker.
+ *
+ * @checker_framework.manual #initialized-fields-checker Initialized Fields Checker
+ */
+public class InitializedFieldsChecker extends AccumulationChecker {}
diff --git a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsTransfer.java b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsTransfer.java
new file mode 100644
index 0000000..1a12ce5
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsTransfer.java
@@ -0,0 +1,36 @@
+package org.checkerframework.common.initializedfields;
+
+import org.checkerframework.common.accumulation.AccumulationTransfer;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFValue;
+
+/** Accumulates the names of fields that are initialized. */
+public class InitializedFieldsTransfer extends AccumulationTransfer {
+
+  /**
+   * Create an InitializedFieldsTransfer.
+   *
+   * @param analysis the analysis
+   */
+  public InitializedFieldsTransfer(final CFAnalysis analysis) {
+    super(analysis);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitAssignment(
+      final AssignmentNode node, final TransferInput<CFValue, CFStore> input) {
+    TransferResult<CFValue, CFStore> result = super.visitAssignment(node, input);
+    Node lhs = node.getTarget();
+    if (lhs instanceof FieldAccessNode) {
+      FieldAccessNode fieldAccess = (FieldAccessNode) lhs;
+      accumulate(fieldAccess.getReceiver(), result, fieldAccess.getFieldName());
+    }
+    return result;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ClassValAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/reflection/ClassValAnnotatedTypeFactory.java
new file mode 100644
index 0000000..a33355f
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/reflection/ClassValAnnotatedTypeFactory.java
@@ -0,0 +1,375 @@
+package org.checkerframework.common.reflection;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.ArrayType;
+import com.sun.tools.javac.code.Type.UnionClassType;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Elements;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.reflection.qual.ClassBound;
+import org.checkerframework.common.reflection.qual.ClassVal;
+import org.checkerframework.common.reflection.qual.ClassValBottom;
+import org.checkerframework.common.reflection.qual.ForName;
+import org.checkerframework.common.reflection.qual.GetClass;
+import org.checkerframework.common.reflection.qual.UnknownClass;
+import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.common.value.qual.StringVal;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.ElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+public class ClassValAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  protected final AnnotationMirror CLASSVAL_TOP =
+      AnnotationBuilder.fromClass(elements, UnknownClass.class);
+
+  /** The ClassBound.value argument/element. */
+  private final ExecutableElement classBoundValueElement =
+      TreeUtils.getMethod(ClassBound.class, "value", 0, processingEnv);
+  /** The ClassVal.value argument/element. */
+  private final ExecutableElement classValValueElement =
+      TreeUtils.getMethod(ClassVal.class, "value", 0, processingEnv);
+
+  /**
+   * Create a new ClassValAnnotatedTypeFactory.
+   *
+   * @param checker the type-checker associated with this factory
+   */
+  public ClassValAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    if (this.getClass() == ClassValAnnotatedTypeFactory.class) {
+      this.postInit();
+    }
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<>(
+        Arrays.asList(UnknownClass.class, ClassVal.class, ClassBound.class, ClassValBottom.class));
+  }
+
+  /**
+   * Create a {@code @ClassVal} annotation with the given values.
+   *
+   * @param values the "value" field of the resulting {@code @ClassVal} annotation
+   * @return a {@code @ClassVal} annotation with the given values
+   */
+  private AnnotationMirror createClassVal(List<String> values) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ClassVal.class);
+    builder.setValue("value", values);
+    return builder.build();
+  }
+
+  /**
+   * Create a {@code @ClassBound} annotation with the given values.
+   *
+   * @param values the "value" field of the resulting {@code @ClassBound} annotation
+   * @return a {@code @ClassBound} annotation with the given values
+   */
+  private AnnotationMirror createClassBound(List<String> values) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ClassBound.class);
+    builder.setValue("value", values);
+    return builder.build();
+  }
+
+  /**
+   * Returns the list of classnames from {@code @ClassBound} or {@code @ClassVal} if anno is
+   * {@code @ClassBound} or {@code @ClassVal}, otherwise returns an empty list.
+   *
+   * @param anno any AnnotationMirror
+   * @return list of classnames in anno
+   */
+  public List<String> getClassNamesFromAnnotation(AnnotationMirror anno) {
+    if (areSameByClass(anno, ClassBound.class)) {
+      return AnnotationUtils.getElementValueArray(anno, classBoundValueElement, String.class);
+    } else if (areSameByClass(anno, ClassVal.class)) {
+      return AnnotationUtils.getElementValueArray(anno, classValValueElement, String.class);
+    } else {
+      return Collections.emptyList();
+    }
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new ClassValQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
+  }
+
+  /** The qualifier hierarchy for the ClassVal type system. */
+  protected class ClassValQualifierHierarchy extends ElementQualifierHierarchy {
+
+    /**
+     * Creates a ClassValQualifierHierarchy from the given classes.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+     * @param elements element utils
+     */
+    public ClassValQualifierHierarchy(
+        Set<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+    }
+
+    /*
+     * Determines the least upper bound of a1 and a2. If both are ClassVal
+     * annotations, then the least upper bound is the set of elements
+     * obtained by combining the values of both annotations.
+     */
+    @Override
+    public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
+      if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) {
+        return null;
+      } else if (isSubtype(a1, a2)) {
+        return a2;
+      } else if (isSubtype(a2, a1)) {
+        return a1;
+      } else {
+        List<String> a1ClassNames = getClassNamesFromAnnotation(a1);
+        List<String> a2ClassNames = getClassNamesFromAnnotation(a2);
+        Set<String> lubClassNames = new TreeSet<>();
+        lubClassNames.addAll(a1ClassNames);
+        lubClassNames.addAll(a2ClassNames);
+
+        // If either annotation is a ClassBound, the lub must also be a class bound.
+        if (areSameByClass(a1, ClassBound.class) || areSameByClass(a2, ClassBound.class)) {
+          return createClassBound(new ArrayList<>(lubClassNames));
+        } else {
+          return createClassVal(new ArrayList<>(lubClassNames));
+        }
+      }
+    }
+
+    @Override
+    public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
+      if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) {
+        return null;
+      } else if (isSubtype(a1, a2)) {
+        return a1;
+      } else if (isSubtype(a2, a1)) {
+        return a2;
+      } else {
+        List<String> a1ClassNames = getClassNamesFromAnnotation(a1);
+        List<String> a2ClassNames = getClassNamesFromAnnotation(a2);
+        Set<String> glbClassNames = new TreeSet<>();
+        glbClassNames.addAll(a1ClassNames);
+        glbClassNames.retainAll(a2ClassNames);
+
+        // If either annotation is a ClassVal, the glb must also be a ClassVal.
+        // For example:
+        // GLB( @ClassVal(a,b), @ClassBound(a,c)) is @ClassVal(a)
+        // because @ClassBound(a) is not a subtype of @ClassVal(a,b)
+        if (areSameByClass(a1, ClassVal.class) || areSameByClass(a2, ClassVal.class)) {
+          return createClassVal(new ArrayList<>(glbClassNames));
+        } else {
+          return createClassBound(new ArrayList<>(glbClassNames));
+        }
+      }
+    }
+
+    /*
+     * Computes subtyping as per the subtyping in the qualifier hierarchy
+     * structure unless both annotations are ClassVal. In this case, rhs is
+     * a subtype of lhs iff lhs contains  every element of rhs.
+     */
+    @Override
+    public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+      if (AnnotationUtils.areSame(subAnno, superAnno)
+          || areSameByClass(superAnno, UnknownClass.class)
+          || areSameByClass(subAnno, ClassValBottom.class)) {
+        return true;
+      }
+      if (areSameByClass(subAnno, UnknownClass.class)
+          || areSameByClass(superAnno, ClassValBottom.class)) {
+        return false;
+      }
+      if (areSameByClass(superAnno, ClassVal.class) && areSameByClass(subAnno, ClassBound.class)) {
+        return false;
+      }
+
+      // if super: ClassVal && sub is ClassVal
+      // if super: ClassBound && (sub is ClassBound or ClassVal)
+
+      List<String> supValues = getClassNamesFromAnnotation(superAnno);
+      List<String> subValues = getClassNamesFromAnnotation(subAnno);
+
+      return supValues.containsAll(subValues);
+    }
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(new ClassValTreeAnnotator(this), super.createTreeAnnotator());
+  }
+
+  /**
+   * Implements the following type inference rules.
+   *
+   * <pre>
+   * C.class:             @ClassVal(fully qualified name of C)
+   * Class.forName(name): @ClassVal("name")
+   * exp.getClass():      @ClassBound(fully qualified classname of exp)
+   * </pre>
+   */
+  protected class ClassValTreeAnnotator extends TreeAnnotator {
+
+    protected ClassValTreeAnnotator(ClassValAnnotatedTypeFactory factory) {
+      super(factory);
+    }
+
+    @Override
+    public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) {
+      if (TreeUtils.isClassLiteral(tree)) {
+        // Create annotations for Class literals
+        // C.class: @ClassVal(fully qualified name of C)
+        ExpressionTree etree = tree.getExpression();
+        Type classType = (Type) TreeUtils.typeOf(etree);
+        String name = getClassNameFromType(classType);
+        if (name != null && !name.equals("void")) {
+          AnnotationMirror newQual = createClassVal(Arrays.asList(name));
+          type.replaceAnnotation(newQual);
+        }
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
+
+      if (isForNameMethodInvocation(tree)) {
+        // Class.forName(name): @ClassVal("name")
+        ExpressionTree arg = tree.getArguments().get(0);
+        List<String> classNames = getStringValues(arg);
+        if (classNames != null) {
+          AnnotationMirror newQual = createClassVal(classNames);
+          type.replaceAnnotation(newQual);
+        }
+      } else if (isGetClassMethodInvocation(tree)) {
+        // exp.getClass(): @ClassBound(fully qualified class name of exp)
+        Type clType;
+        if (TreeUtils.getReceiverTree(tree) != null) {
+          clType = (Type) TreeUtils.typeOf(TreeUtils.getReceiverTree(tree));
+        } else { // receiver is null, so it is implicitly "this"
+          ClassTree classTree = TreePathUtil.enclosingClass(getPath(tree));
+          clType = (Type) TreeUtils.typeOf(classTree);
+        }
+        String className = getClassNameFromType(clType);
+        AnnotationMirror newQual = createClassBound(Arrays.asList(className));
+        type.replaceAnnotation(newQual);
+      }
+      return null;
+    }
+
+    /**
+     * Return true if this is an invocation of a method annotated with @ForName. An example of such
+     * a method is Class.forName.
+     */
+    private boolean isForNameMethodInvocation(MethodInvocationTree tree) {
+      return getDeclAnnotation(TreeUtils.elementFromTree(tree), ForName.class) != null;
+    }
+
+    /**
+     * Return true if this is an invocation of a method annotated with @GetClass. An example of such
+     * a method is Object.getClassName.
+     */
+    private boolean isGetClassMethodInvocation(MethodInvocationTree tree) {
+      return getDeclAnnotation(TreeUtils.elementFromTree(tree), GetClass.class) != null;
+    }
+
+    private List<String> getStringValues(ExpressionTree arg) {
+      ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class);
+      AnnotationMirror annotation = valueATF.getAnnotationMirror(arg, StringVal.class);
+      if (annotation == null) {
+        return null;
+      }
+      return AnnotationUtils.getElementValueArray(
+          annotation, valueATF.stringValValueElement, String.class);
+    }
+
+    // TODO: This looks like it returns a @BinaryName. Verify that fact and add a type qualifier.
+    /**
+     * Return String representation of class name. This will not return the correct name for
+     * anonymous classes.
+     */
+    private String getClassNameFromType(Type classType) {
+      switch (classType.getKind()) {
+        case ARRAY:
+          String array = "";
+          while (classType.getKind() == TypeKind.ARRAY) {
+            classType = ((ArrayType) classType).getComponentType();
+            array += "[]";
+          }
+          return getClassNameFromType(classType) + array;
+        case DECLARED:
+          StringBuilder className =
+              new StringBuilder(TypesUtils.getQualifiedName((DeclaredType) classType));
+          if (classType.getEnclosingType() != null) {
+            while (classType.getEnclosingType().getKind() != TypeKind.NONE) {
+              classType = classType.getEnclosingType();
+              int last = className.lastIndexOf(".");
+              if (last > -1) {
+                className.replace(last, last + 1, "$");
+              }
+            }
+          }
+          return className.toString();
+        case INTERSECTION:
+          // This could be more precise
+          return "java.lang.Object";
+        case NULL:
+          return "java.lang.Object";
+        case UNION:
+          classType = ((UnionClassType) classType).getLub();
+          return getClassNameFromType(classType);
+        case TYPEVAR:
+        case WILDCARD:
+          classType = classType.getUpperBound();
+          return getClassNameFromType(classType);
+        case INT:
+          return int.class.getCanonicalName();
+        case LONG:
+          return long.class.getCanonicalName();
+        case SHORT:
+          return short.class.getCanonicalName();
+        case BYTE:
+          return byte.class.getCanonicalName();
+        case CHAR:
+          return char.class.getCanonicalName();
+        case DOUBLE:
+          return double.class.getCanonicalName();
+        case FLOAT:
+          return float.class.getCanonicalName();
+        case BOOLEAN:
+          return boolean.class.getCanonicalName();
+        case VOID:
+          return "void";
+        default:
+          throw new BugInCF(
+              "ClassValAnnotatedTypeFactory.getClassname: did not expect " + classType.getKind());
+      }
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ClassValChecker.java b/framework/src/main/java/org/checkerframework/common/reflection/ClassValChecker.java
new file mode 100644
index 0000000..f296fd9
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/reflection/ClassValChecker.java
@@ -0,0 +1,35 @@
+package org.checkerframework.common.reflection;
+
+import java.util.LinkedHashSet;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.value.ValueChecker;
+
+/**
+ * The ClassVal Checker provides a sound estimate of the binary name of Class objects.
+ *
+ * @checker_framework.manual #methodval-and-classval-checkers ClassVal Checker
+ */
+public class ClassValChecker extends BaseTypeChecker {
+
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new ClassValVisitor(this);
+  }
+
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    // Don't call super otherwise MethodVal will be added as a subChecker
+    // which creates a circular dependency.
+    LinkedHashSet<Class<? extends BaseTypeChecker>> subCheckers = new LinkedHashSet<>();
+    subCheckers.add(ValueChecker.class);
+    return subCheckers;
+  }
+
+  @Override
+  public boolean shouldResolveReflection() {
+    // Because this checker is a subchecker of MethodVal,
+    // reflection can't be resolved.
+    return false;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java b/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java
new file mode 100644
index 0000000..ab13897
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java
@@ -0,0 +1,63 @@
+package org.checkerframework.common.reflection;
+
+import com.sun.source.tree.Tree;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeValidator;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.reflection.qual.ClassBound;
+import org.checkerframework.common.reflection.qual.ClassVal;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.plumelib.reflection.Signatures;
+
+/** A visitor to verify validity of {@code @}{@link ClassVal} annotations. */
+public class ClassValVisitor extends BaseTypeVisitor<ClassValAnnotatedTypeFactory> {
+  /**
+   * Create a new ClassValVisitor.
+   *
+   * @param checker the associated type-checker
+   */
+  public ClassValVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  protected ClassValAnnotatedTypeFactory createTypeFactory() {
+    return new ClassValAnnotatedTypeFactory(checker);
+  }
+
+  @Override
+  protected BaseTypeValidator createTypeValidator() {
+    return new ClassNameValidator(checker, this, atypeFactory);
+  }
+}
+
+class ClassNameValidator extends BaseTypeValidator {
+
+  public ClassNameValidator(
+      BaseTypeChecker checker, BaseTypeVisitor<?> visitor, AnnotatedTypeFactory atypeFactory) {
+    super(checker, visitor, atypeFactory);
+  }
+
+  /**
+   * This implementation reports an "illegal.classname" error if the type contains a @ClassVal
+   * annotation with a string that is not a valid class name.
+   */
+  @Override
+  public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) {
+    AnnotationMirror classVal = type.getAnnotation(ClassVal.class);
+    classVal = classVal == null ? type.getAnnotation(ClassBound.class) : classVal;
+    if (classVal != null) {
+      List<String> classNames =
+          ((ClassValAnnotatedTypeFactory) atypeFactory).getClassNamesFromAnnotation(classVal);
+      for (String className : classNames) {
+        if (!Signatures.isFqBinaryName(className)) {
+          checker.reportError(tree, "illegal.classname", className, type);
+        }
+      }
+    }
+    return super.visitDeclared(type, tree);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java b/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java
new file mode 100644
index 0000000..95fdcd6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java
@@ -0,0 +1,638 @@
+package org.checkerframework.common.reflection;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.api.JavacScope;
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.TypeTag;
+import com.sun.tools.javac.comp.AttrContext;
+import com.sun.tools.javac.comp.Env;
+import com.sun.tools.javac.comp.Resolve;
+import com.sun.tools.javac.comp.Resolve.RecoveryLoadClass;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+import com.sun.tools.javac.tree.JCTree.JCNewClass;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Name;
+import com.sun.tools.javac.util.Names;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.reflection.qual.Invoke;
+import org.checkerframework.common.reflection.qual.MethodVal;
+import org.checkerframework.common.reflection.qual.NewInstance;
+import org.checkerframework.common.reflection.qual.UnknownMethod;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Default implementation of {@link ReflectionResolver}. It resolves calls to:
+ *
+ * <ul>
+ *   <li>{@link Method#invoke(Object, Object...)}
+ *   <li>{@link Constructor#newInstance(Object...)}
+ * </ul>
+ *
+ * @checker_framework.manual #reflection-resolution Reflection resolution
+ */
+public class DefaultReflectionResolver implements ReflectionResolver {
+  // Message prefix added to verbose reflection messages
+  public static final String MSG_PREFEX_REFLECTION = "[Reflection] ";
+
+  private final BaseTypeChecker checker;
+  private final AnnotationProvider provider;
+  private final ProcessingEnvironment processingEnv;
+  private final Trees trees;
+  private final boolean debug;
+
+  public DefaultReflectionResolver(
+      BaseTypeChecker checker, MethodValAnnotatedTypeFactory methodValProvider, boolean debug) {
+    this.checker = checker;
+    this.provider = methodValProvider;
+    this.processingEnv = checker.getProcessingEnvironment();
+    this.trees = Trees.instance(processingEnv);
+    this.debug = debug;
+  }
+
+  @Override
+  public boolean isReflectiveMethodInvocation(MethodInvocationTree tree) {
+    return ((provider.getDeclAnnotation(TreeUtils.elementFromTree(tree), Invoke.class) != null
+        || provider.getDeclAnnotation(TreeUtils.elementFromTree(tree), NewInstance.class) != null));
+  }
+
+  @Override
+  public ParameterizedExecutableType resolveReflectiveCall(
+      AnnotatedTypeFactory factory,
+      MethodInvocationTree tree,
+      ParameterizedExecutableType origResult) {
+    assert isReflectiveMethodInvocation(tree);
+    if (provider.getDeclAnnotation(TreeUtils.elementFromTree(tree), NewInstance.class) != null) {
+      return resolveConstructorCall(factory, tree, origResult);
+    } else {
+      return resolveMethodCall(factory, tree, origResult);
+    }
+  }
+
+  /**
+   * Resolves a call to {@link Method#invoke(Object, Object...)}.
+   *
+   * @param factory the {@link AnnotatedTypeFactory} of the underlying type system
+   * @param tree the method invocation tree that has to be resolved
+   * @param origResult the original result from {@code factory.methodFromUse}
+   * @return the resolved type of the call
+   */
+  private ParameterizedExecutableType resolveMethodCall(
+      AnnotatedTypeFactory factory,
+      MethodInvocationTree tree,
+      ParameterizedExecutableType origResult) {
+    debugReflection("Try to resolve reflective method call: " + tree);
+    List<MethodInvocationTree> possibleMethods = resolveReflectiveMethod(tree, factory);
+
+    // Reflective method could not be resolved
+    if (possibleMethods.isEmpty()) {
+      return origResult;
+    }
+
+    Set<? extends AnnotationMirror> returnLub = null;
+    Set<? extends AnnotationMirror> receiverGlb = null;
+    Set<? extends AnnotationMirror> paramsGlb = null;
+
+    // Iterate over all possible methods: lub return types, and glb receiver and parameter types
+    for (MethodInvocationTree resolvedTree : possibleMethods) {
+      debugReflection("Resolved method invocation: " + resolvedTree);
+      if (!checkMethodArguments(resolvedTree)) {
+        debugReflection("Spoofed tree's arguments did not match declaration" + resolvedTree);
+        // Calling methodFromUse on these sorts of trees will cause an assertion to fail in
+        // QualifierPolymorphism.PolyCollector.visitArray(...)
+        continue;
+      }
+      ParameterizedExecutableType resolvedResult = factory.methodFromUse(resolvedTree);
+
+      // Lub return types
+      returnLub =
+          lub(returnLub, resolvedResult.executableType.getReturnType().getAnnotations(), factory);
+
+      // Glb receiver types (actual method receiver is passed as first
+      // argument to invoke(Object, Object[]))
+      // Check for static methods whose receiver is null
+      if (resolvedResult.executableType.getReceiverType() == null) {
+        // If the method is static the first argument to Method.invoke isn't used, so assume top.
+        receiverGlb =
+            glb(receiverGlb, factory.getQualifierHierarchy().getTopAnnotations(), factory);
+      } else {
+        receiverGlb =
+            glb(
+                receiverGlb,
+                resolvedResult.executableType.getReceiverType().getAnnotations(),
+                factory);
+      }
+
+      // Glb parameter types.  All formal parameter types get combined together because
+      // Method#invoke takes as argument an array of parameter types, so there is no way to
+      // distinguish the types of different formal parameters.
+      for (AnnotatedTypeMirror mirror : resolvedResult.executableType.getParameterTypes()) {
+        paramsGlb = glb(paramsGlb, mirror.getAnnotations(), factory);
+      }
+    }
+
+    if (returnLub == null) {
+      // None of the spoofed tree's arguments matched the declared method
+      return origResult;
+    }
+
+    /*
+     * Clear all original (return, receiver, parameter type) annotations and
+     * set lub/glb annotations from resolved method(s)
+     */
+
+    // return value
+    origResult.executableType.getReturnType().clearAnnotations();
+    origResult.executableType.getReturnType().addAnnotations(returnLub);
+
+    // receiver type
+    origResult.executableType.getParameterTypes().get(0).clearAnnotations();
+    origResult.executableType.getParameterTypes().get(0).addAnnotations(receiverGlb);
+
+    // parameter types
+    if (paramsGlb != null) {
+      AnnotatedArrayType origArrayType =
+          (AnnotatedArrayType) origResult.executableType.getParameterTypes().get(1);
+      origArrayType.getComponentType().clearAnnotations();
+      origArrayType.getComponentType().addAnnotations(paramsGlb);
+    }
+
+    debugReflection("Resolved annotations: " + origResult.executableType);
+    return origResult;
+  }
+
+  /**
+   * Checks that arguments of a method invocation are consistent with their corresponding
+   * parameters.
+   *
+   * @param resolvedTree a method invocation
+   * @return true if arguments are consistent with parameters
+   */
+  private boolean checkMethodArguments(MethodInvocationTree resolvedTree) {
+    // type.getKind() == actualType.getKind()
+    ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree);
+    return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments());
+  }
+
+  /**
+   * Checks that arguments of a constructor invocation are consistent with their corresponding
+   * parameters.
+   *
+   * @param resolvedTree a constructor invocation
+   * @return true if arguments are consistent with parameters
+   */
+  private boolean checkNewClassArguments(NewClassTree resolvedTree) {
+    ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree);
+    return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments());
+  }
+
+  /**
+   * Checks that argument are consistent with their corresponding parameter types. Common code used
+   * by {@link #checkMethodArguments} and {@link #checkNewClassArguments}.
+   *
+   * @param parameters formal parameters
+   * @param arguments actual arguments
+   * @return true if argument are consistent with their corresponding parameter types
+   */
+  private boolean checkArguments(
+      List<? extends VariableElement> parameters, List<? extends ExpressionTree> arguments) {
+    if (parameters.size() != arguments.size()) {
+      return false;
+    }
+
+    for (int i = 0; i < parameters.size(); i++) {
+      VariableElement param = parameters.get(i);
+      ExpressionTree arg = arguments.get(i);
+      TypeMirror argType = TreeUtils.typeOf(arg);
+      TypeMirror paramType = param.asType();
+      if (argType.getKind() == TypeKind.ARRAY && paramType.getKind() != argType.getKind()) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Resolves a call to {@link Constructor#newInstance(Object...)}.
+   *
+   * @param factory the {@link AnnotatedTypeFactory} of the underlying type system
+   * @param tree the method invocation tree (representing a constructor call) that has to be
+   *     resolved
+   * @param origResult the original result from {@code factory.methodFromUse}
+   * @return the resolved type of the call
+   */
+  private ParameterizedExecutableType resolveConstructorCall(
+      AnnotatedTypeFactory factory,
+      MethodInvocationTree tree,
+      ParameterizedExecutableType origResult) {
+    debugReflection("Try to resolve reflective constructor call: " + tree);
+    List<JCNewClass> possibleConstructors = resolveReflectiveConstructor(tree, factory);
+
+    // Reflective constructor could not be resolved
+    if (possibleConstructors.isEmpty()) {
+      return origResult;
+    }
+
+    Set<? extends AnnotationMirror> returnLub = null;
+    Set<? extends AnnotationMirror> paramsGlb = null;
+
+    // Iterate over all possible constructors: lub return types and glb parameter types
+    for (JCNewClass resolvedTree : possibleConstructors) {
+      debugReflection("Resolved constructor invocation: " + resolvedTree);
+      if (!checkNewClassArguments(resolvedTree)) {
+        debugReflection("Spoofed tree's arguments did not match declaration" + resolvedTree);
+        // Calling methodFromUse on these sorts of trees will cause an assertion to fail in
+        // QualifierPolymorphism.PolyCollector.visitArray(...)
+        continue;
+      }
+      ParameterizedExecutableType resolvedResult = factory.constructorFromUse(resolvedTree);
+
+      // Lub return types
+      returnLub =
+          lub(returnLub, resolvedResult.executableType.getReturnType().getAnnotations(), factory);
+
+      // Glb parameter types
+      for (AnnotatedTypeMirror mirror : resolvedResult.executableType.getParameterTypes()) {
+        paramsGlb = glb(paramsGlb, mirror.getAnnotations(), factory);
+      }
+    }
+    if (returnLub == null) {
+      // None of the spoofed tree's arguments matched the declared method
+      return origResult;
+    }
+    /*
+     * Clear all original (return, parameter type) annotations and set
+     * lub/glb annotations from resolved constructors.
+     */
+
+    // return value
+    origResult.executableType.getReturnType().clearAnnotations();
+    origResult.executableType.getReturnType().addAnnotations(returnLub);
+
+    // parameter types
+    if (paramsGlb != null) {
+      AnnotatedArrayType origArrayType =
+          (AnnotatedArrayType) origResult.executableType.getParameterTypes().get(0);
+      origArrayType.getComponentType().clearAnnotations();
+      origArrayType.getComponentType().addAnnotations(paramsGlb);
+    }
+
+    debugReflection("Resolved annotations: " + origResult.executableType);
+    return origResult;
+  }
+
+  /**
+   * Resolves a reflective method call and returns all possible corresponding method calls.
+   *
+   * @param tree the MethodInvocationTree node that is to be resolved (Method.invoke)
+   * @return a (potentially empty) list of all resolved MethodInvocationTrees
+   */
+  private List<MethodInvocationTree> resolveReflectiveMethod(
+      MethodInvocationTree tree, AnnotatedTypeFactory reflectionFactory) {
+    assert isReflectiveMethodInvocation(tree);
+    JCMethodInvocation methodInvocation = (JCMethodInvocation) tree;
+
+    Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
+    TreeMaker make = TreeMaker.instance(context);
+    TreePath path = reflectionFactory.getPath(tree);
+    JavacScope scope = (JavacScope) trees.getScope(path);
+    Env<AttrContext> env = scope.getEnv();
+
+    boolean unknown = isUnknownMethod(tree);
+
+    AnnotationMirror estimate = getMethodVal(tree);
+
+    if (estimate == null) {
+      debugReflection("MethodVal is unknown for: " + tree);
+      debugReflection("UnknownMethod annotation: " + unknown);
+      return Collections.emptyList();
+    }
+
+    debugReflection("MethodVal type system annotations: " + estimate);
+
+    List<String> listClassNames =
+        AnnotationUtils.getElementValueArray(
+            estimate, reflectionFactory.methodValClassNameElement, String.class);
+    List<String> listMethodNames =
+        AnnotationUtils.getElementValueArray(
+            estimate, reflectionFactory.methodValMethodNameElement, String.class);
+    List<Integer> listParamLengths =
+        AnnotationUtils.getElementValueArray(
+            estimate, reflectionFactory.methodValParamsElement, Integer.class);
+    assert listClassNames.size() == listMethodNames.size()
+        && listClassNames.size() == listParamLengths.size();
+
+    List<MethodInvocationTree> methods = new ArrayList<>();
+    for (int i = 0; i < listClassNames.size(); ++i) {
+      String className = listClassNames.get(i);
+      String methodName = listMethodNames.get(i);
+      int paramLength = listParamLengths.get(i);
+
+      // Get receiver, which is always the first argument of the invoke method
+      JCExpression receiver = methodInvocation.args.head;
+      // The remaining list contains the arguments
+      com.sun.tools.javac.util.List<JCExpression> args = methodInvocation.args.tail;
+
+      // Resolve the Symbol(s) for the current method
+      for (Symbol symbol : getMethodSymbolsfor(className, methodName, paramLength, env)) {
+        if (!processingEnv.getTypeUtils().isSubtype(receiver.type, symbol.owner.type)) {
+          continue;
+        }
+        if ((symbol.flags() & Flags.PUBLIC) > 0) {
+          debugReflection("Resolved public method: " + symbol.owner + "." + symbol);
+        } else {
+          debugReflection("Resolved non-public method: " + symbol.owner + "." + symbol);
+        }
+
+        JCExpression method = make.Select(receiver, symbol);
+        args = getCorrectedArgs(symbol, args);
+        // Build method invocation tree depending on the number of
+        // parameters
+        JCMethodInvocation syntTree = paramLength > 0 ? make.App(method, args) : make.App(method);
+
+        // add method invocation tree to the list of possible methods
+        methods.add(syntTree);
+      }
+    }
+    return methods;
+  }
+
+  private com.sun.tools.javac.util.List<JCExpression> getCorrectedArgs(
+      Symbol symbol, com.sun.tools.javac.util.List<JCExpression> args) {
+    if (symbol.getKind() == ElementKind.METHOD) {
+      MethodSymbol method = ((MethodSymbol) symbol);
+      // neg means too many arg,
+      // pos means to few args
+      int diff = method.getParameters().size() - args.size();
+      if (diff > 0) {
+        // means too few args
+        int origArgSize = args.size();
+        for (int i = 0; i < diff; i++) {
+          args = args.append(args.get(i % origArgSize));
+        }
+      } else if (diff < 0) {
+        // means too many args
+        com.sun.tools.javac.util.List<JCExpression> tmp = com.sun.tools.javac.util.List.nil();
+        for (int i = 0; i < method.getParameters().size(); i++) {
+          tmp = tmp.append(args.get(i));
+        }
+        args = tmp;
+      }
+    }
+    return args;
+  }
+
+  /**
+   * Resolves a reflective constructor call and returns all possible corresponding constructor
+   * calls.
+   *
+   * @param tree the MethodInvocationTree node that is to be resolved (Constructor.newInstance)
+   * @return a (potentially empty) list of all resolved MethodInvocationTrees
+   */
+  private List<JCNewClass> resolveReflectiveConstructor(
+      MethodInvocationTree tree, AnnotatedTypeFactory reflectionFactory) {
+    assert isReflectiveMethodInvocation(tree);
+    JCMethodInvocation methodInvocation = (JCMethodInvocation) tree;
+
+    Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
+    TreeMaker make = TreeMaker.instance(context);
+    TreePath path = reflectionFactory.getPath(tree);
+    JavacScope scope = (JavacScope) trees.getScope(path);
+    Env<AttrContext> env = scope.getEnv();
+
+    AnnotationMirror estimate = getMethodVal(tree);
+
+    if (estimate == null) {
+      debugReflection("MethodVal is unknown for: " + tree);
+      debugReflection("UnknownMethod annotation: " + isUnknownMethod(tree));
+      return Collections.emptyList();
+    }
+
+    debugReflection("MethodVal type system annotations: " + estimate);
+
+    List<String> listClassNames =
+        AnnotationUtils.getElementValueArray(
+            estimate, reflectionFactory.methodValClassNameElement, String.class);
+    List<Integer> listParamLengths =
+        AnnotationUtils.getElementValueArray(
+            estimate, reflectionFactory.methodValParamsElement, Integer.class);
+    assert listClassNames.size() == listParamLengths.size();
+
+    List<JCNewClass> constructors = new ArrayList<>();
+    for (int i = 0; i < listClassNames.size(); ++i) {
+      String className = listClassNames.get(i);
+      int paramLength = listParamLengths.get(i);
+
+      // Resolve the Symbol for the current constructor
+      for (Symbol symbol : getConstructorSymbolsfor(className, paramLength, env)) {
+        debugReflection("Resolved constructor: " + symbol.owner + "." + symbol);
+
+        JCNewClass syntTree = (JCNewClass) make.Create(symbol, methodInvocation.args);
+
+        // add constructor invocation tree to the list of possible
+        // constructors
+        constructors.add(syntTree);
+      }
+    }
+    return constructors;
+  }
+
+  private AnnotationMirror getMethodVal(MethodInvocationTree tree) {
+    return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), MethodVal.class);
+  }
+
+  /** Returns true if the receiver's type is @UnknownMethod. */
+  private boolean isUnknownMethod(MethodInvocationTree tree) {
+    return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), UnknownMethod.class)
+        != null;
+  }
+
+  /**
+   * Get set of MethodSymbols based on class name, method name, and parameter length.
+   *
+   * @param className the class that contains the method
+   * @param methodName the method's name
+   * @param paramLength the number of parameters
+   * @param env the environment
+   * @return the (potentially empty) set of corresponding method Symbol(s)
+   */
+  private List<Symbol> getMethodSymbolsfor(
+      String className, String methodName, int paramLength, Env<AttrContext> env) {
+    Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
+    Resolve resolve = Resolve.instance(context);
+    Names names = Names.instance(context);
+
+    Symbol sym = getSymbol(className, env, names, resolve);
+    if (!sym.exists()) {
+      debugReflection("Unable to resolve class: " + className);
+      return Collections.emptyList();
+    }
+
+    // The common case is probably that `result` is a singleton at method exit.
+    List<Symbol> result = new ArrayList<>();
+    ClassSymbol classSym = (ClassSymbol) sym;
+    while (classSym != null) {
+      for (Symbol s : classSym.getEnclosedElements()) {
+        // check all member methods
+        if (s.getKind() == ElementKind.METHOD) {
+          // Check for method name and number of arguments
+          if (names.fromString(methodName) == s.name
+              && ((MethodSymbol) s).getParameters().size() == paramLength) {
+            result.add(s);
+          }
+        }
+      }
+      if (!result.isEmpty()) {
+        break;
+      }
+      Type t = classSym.getSuperclass();
+      if (!t.hasTag(TypeTag.CLASS) || t.isErroneous()) {
+        break;
+      }
+      classSym = (ClassSymbol) t.tsym;
+    }
+    if (result.isEmpty()) {
+      debugReflection("Unable to resolve method: " + className + "@" + methodName);
+    }
+    return result;
+  }
+
+  /**
+   * Get set of Symbols for constructors based on class name and parameter length.
+   *
+   * @return the (potentially empty) set of corresponding constructor Symbol(s)
+   */
+  private List<Symbol> getConstructorSymbolsfor(
+      String className, int paramLength, Env<AttrContext> env) {
+    Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
+    Resolve resolve = Resolve.instance(context);
+    Names names = Names.instance(context);
+
+    Symbol symClass = getSymbol(className, env, names, resolve);
+    if (!symClass.exists()) {
+      debugReflection("Unable to resolve class: " + className);
+      return Collections.emptyList();
+    }
+
+    // TODO: Should this be used instead of the below??
+    ElementFilter.constructorsIn(symClass.getEnclosedElements());
+
+    // The common case is probably that `result` is a singleton at method exit.
+    List<Symbol> result = new ArrayList<>();
+    for (Symbol s : symClass.getEnclosedElements()) {
+      // Check all constructors
+      if (s.getKind() == ElementKind.CONSTRUCTOR) {
+        // Check for number of parameters
+        if (((MethodSymbol) s).getParameters().size() == paramLength) {
+          result.add(s);
+        }
+      }
+    }
+    if (result.isEmpty()) {
+      debugReflection("Unable to resolve constructor!");
+    }
+    return result;
+  }
+
+  private Symbol getSymbol(String className, Env<AttrContext> env, Names names, Resolve resolve) {
+    Method loadClass;
+    try {
+      loadClass =
+          Resolve.class.getDeclaredMethod(
+              "loadClass", Env.class, Name.class, RecoveryLoadClass.class);
+      loadClass.setAccessible(true);
+    } catch (SecurityException | NoSuchMethodException | IllegalArgumentException e) {
+      // A problem with javac is serious and must be reported.
+      throw new BugInCF("Error in obtaining reflective method.", e);
+    }
+    try {
+      RecoveryLoadClass noRecovery = (e, n) -> null;
+      return (Symbol) loadClass.invoke(resolve, env, names.fromString(className), noRecovery);
+    } catch (SecurityException
+        | IllegalAccessException
+        | IllegalArgumentException
+        | InvocationTargetException e) {
+      // A problem with javac is serious and must be reported.
+      throw new BugInCF("Error in invoking reflective method.", e);
+    }
+  }
+
+  /**
+   * Build lub of the two types (represented by sets {@code set1} and {@code set2}) using the
+   * provided AnnotatedTypeFactory.
+   *
+   * <p>If {@code set1} is {@code null} or empty, {@code set2} is returned.
+   */
+  private Set<? extends AnnotationMirror> lub(
+      Set<? extends AnnotationMirror> set1,
+      Set<? extends AnnotationMirror> set2,
+      AnnotatedTypeFactory factory) {
+    if (set1 == null || set1.isEmpty()) {
+      return set2;
+    } else {
+      return factory.getQualifierHierarchy().leastUpperBounds(set1, set2);
+    }
+  }
+
+  /**
+   * Build glb of the two types (represented by sets {@code set1} and {@code set2}) using the
+   * provided AnnotatedTypeFactory.
+   *
+   * <p>If {@code set1} is {@code null} or empty, {@code set2} is returned.
+   */
+  private Set<? extends AnnotationMirror> glb(
+      Set<? extends AnnotationMirror> set1,
+      Set<? extends AnnotationMirror> set2,
+      AnnotatedTypeFactory factory) {
+    if (set1 == null || set1.isEmpty()) {
+      return set2;
+    } else {
+      return factory.getQualifierHierarchy().greatestLowerBounds(set1, set2);
+    }
+  }
+
+  /**
+   * Reports debug information about the reflection resolution iff the corresponding debug flag is
+   * set.
+   *
+   * @param msg the debug message
+   */
+  private void debugReflection(String msg) {
+    if (debug) {
+      checker.message(javax.tools.Diagnostic.Kind.NOTE, MSG_PREFEX_REFLECTION + msg);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java
new file mode 100644
index 0000000..850bb0d
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java
@@ -0,0 +1,497 @@
+package org.checkerframework.common.reflection;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.reflection.qual.ClassBound;
+import org.checkerframework.common.reflection.qual.ClassVal;
+import org.checkerframework.common.reflection.qual.GetConstructor;
+import org.checkerframework.common.reflection.qual.GetMethod;
+import org.checkerframework.common.reflection.qual.MethodVal;
+import org.checkerframework.common.reflection.qual.MethodValBottom;
+import org.checkerframework.common.reflection.qual.UnknownMethod;
+import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.BottomVal;
+import org.checkerframework.common.value.qual.StringVal;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.ElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/** AnnotatedTypeFactory for the MethodVal Checker. */
+public class MethodValAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /** {@link UnknownMethod} annotation mirror. */
+  private final AnnotationMirror UNKNOWN_METHOD =
+      AnnotationBuilder.fromClass(elements, UnknownMethod.class);
+
+  /** An arary length that represents that the length is unknown. */
+  private static final int UNKNOWN_PARAM_LENGTH = -1;
+
+  /** A list containing just {@link #UNKNOWN_PARAM_LENGTH}. */
+  private static final List<Integer> UNKNOWN_PARAM_LENGTH_LIST =
+      Collections.singletonList(UNKNOWN_PARAM_LENGTH);
+
+  /** A list containing just 0. */
+  private static List<Integer> ZERO_LIST = Collections.singletonList(0);
+
+  /** A list containing just 1. */
+  private static List<Integer> ONE_LIST = Collections.singletonList(1);
+
+  /** An empty String list. */
+  private static List<String> EMPTY_STRING_LIST = Collections.emptyList();
+
+  /** The ArrayLen.value argument/element. */
+  public final ExecutableElement arrayLenValueElement =
+      TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv);
+  /** The ClassBound.value argument/element. */
+  public final ExecutableElement classBoundValueElement =
+      TreeUtils.getMethod(ClassBound.class, "value", 0, processingEnv);
+  /** The ClassVal.value argument/element. */
+  public final ExecutableElement classValValueElement =
+      TreeUtils.getMethod(ClassVal.class, "value", 0, processingEnv);
+  /** The StringVal.value argument/element. */
+  public final ExecutableElement stringValValueElement =
+      TreeUtils.getMethod(StringVal.class, "value", 0, processingEnv);
+
+  /**
+   * Create a new MethodValAnnotatedTypeFactory.
+   *
+   * @param checker the type-checker associated with this factory
+   */
+  public MethodValAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    if (this.getClass() == MethodValAnnotatedTypeFactory.class) {
+      this.postInit();
+    }
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<>(
+        Arrays.asList(MethodVal.class, MethodValBottom.class, UnknownMethod.class));
+  }
+
+  @Override
+  protected void initializeReflectionResolution() {
+    boolean debug = "debug".equals(checker.getOption("resolveReflection"));
+    reflectionResolver = new DefaultReflectionResolver(checker, this, debug);
+  }
+
+  /**
+   * Returns the methods that a {@code @MethodVal} represents.
+   *
+   * @param methodValAnno a {@code @MethodVal} annotation
+   * @return the methods that the given {@code @MethodVal} represents
+   */
+  List<MethodSignature> getListOfMethodSignatures(AnnotationMirror methodValAnno) {
+    List<String> methodNames =
+        AnnotationUtils.getElementValueArray(
+            methodValAnno, methodValMethodNameElement, String.class);
+    List<String> classNames =
+        AnnotationUtils.getElementValueArray(
+            methodValAnno, methodValClassNameElement, String.class);
+    List<Integer> params =
+        AnnotationUtils.getElementValueArray(methodValAnno, methodValParamsElement, Integer.class);
+    List<MethodSignature> list = new ArrayList<>(methodNames.size());
+    for (int i = 0; i < methodNames.size(); i++) {
+      list.add(new MethodSignature(classNames.get(i), methodNames.get(i), params.get(i)));
+    }
+    return list;
+  }
+
+  /**
+   * Creates a {@code @MethodVal} annotation.
+   *
+   * @param sigs the method signatures that the result should represent
+   * @return a {@code @MethodVal} annotation that represents {@code sigs}
+   */
+  private AnnotationMirror createMethodVal(Set<MethodSignature> sigs) {
+    int size = sigs.size();
+    List<String> classNames = new ArrayList<>(size);
+    List<String> methodNames = new ArrayList<>(size);
+    List<Integer> params = new ArrayList<>(size);
+    for (MethodSignature sig : sigs) {
+      classNames.add(sig.className);
+      methodNames.add(sig.methodName);
+      params.add(sig.params);
+    }
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MethodVal.class);
+    builder.setValue("className", classNames);
+    builder.setValue("methodName", methodNames);
+    builder.setValue("params", params);
+    return builder.build();
+  }
+
+  /**
+   * Returns a list of class names for the given tree using the Class Val Checker.
+   *
+   * @param tree ExpressionTree whose class names are requested
+   * @param mustBeExact whether @ClassBound may be read to produce the result; if false,
+   *     only @ClassVal may be read
+   * @return list of class names or the empty list if no class names were found
+   */
+  private List<String> getClassNamesFromClassValChecker(ExpressionTree tree, boolean mustBeExact) {
+    ClassValAnnotatedTypeFactory classValATF = getTypeFactoryOfSubchecker(ClassValChecker.class);
+    AnnotatedTypeMirror classAnno = classValATF.getAnnotatedType(tree);
+
+    AnnotationMirror classValAnno = classAnno.getAnnotation(ClassVal.class);
+    if (classValAnno != null) {
+      return AnnotationUtils.getElementValueArray(classValAnno, classValValueElement, String.class);
+    } else if (mustBeExact) {
+      return Collections.emptyList();
+    }
+
+    AnnotationMirror classBoundAnno = classAnno.getAnnotation(ClassBound.class);
+    if (classBoundAnno != null) {
+      return AnnotationUtils.getElementValueArray(
+          classBoundAnno, classBoundValueElement, String.class);
+    } else {
+      return Collections.emptyList();
+    }
+  }
+  /**
+   * Returns the string values for the argument passed. The String Values are estimated using the
+   * Value Checker.
+   *
+   * @param arg ExpressionTree whose string values are sought
+   * @return string values of arg or the empty list if no values were found
+   */
+  private List<String> getMethodNamesFromStringArg(ExpressionTree arg) {
+    ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class);
+    AnnotatedTypeMirror valueAnno = valueATF.getAnnotatedType(arg);
+    AnnotationMirror annotation = valueAnno.getAnnotation(StringVal.class);
+    if (annotation != null) {
+      return AnnotationUtils.getElementValueArray(annotation, stringValValueElement, String.class);
+    } else {
+      return EMPTY_STRING_LIST;
+    }
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new MethodValQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
+  }
+
+  /** MethodValQualifierHierarchy. */
+  protected class MethodValQualifierHierarchy extends ElementQualifierHierarchy {
+
+    /**
+     * Creates a MethodValQualifierHierarchy from the given classes.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+     * @param elements element utils
+     */
+    protected MethodValQualifierHierarchy(
+        Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+    }
+
+    /*
+     * Determines the least upper bound of a1 and a2. If both are MethodVal
+     * annotations, then the least upper bound is the result of
+     * concatenating all value lists of a1 and a2.
+     */
+    @Override
+    public @Nullable AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
+      if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) {
+        return null;
+      } else if (isSubtype(a1, a2)) {
+        return a2;
+      } else if (isSubtype(a2, a1)) {
+        return a1;
+      } else if (AnnotationUtils.areSameByName(a1, a2)) {
+        List<MethodSignature> a1Sigs = getListOfMethodSignatures(a1);
+        List<MethodSignature> a2Sigs = getListOfMethodSignatures(a2);
+
+        Set<MethodSignature> lubSigs = new HashSet<>(a1Sigs);
+        lubSigs.addAll(a2Sigs); // union
+
+        AnnotationMirror result = createMethodVal(lubSigs);
+        return result;
+      }
+      return null;
+    }
+
+    @Override
+    public @Nullable AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
+      if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) {
+        return null;
+      } else if (isSubtype(a1, a2)) {
+        return a1;
+      } else if (isSubtype(a2, a1)) {
+        return a2;
+      } else if (AnnotationUtils.areSameByName(a1, a2)) {
+        List<MethodSignature> a1Sigs = getListOfMethodSignatures(a1);
+        List<MethodSignature> a2Sigs = getListOfMethodSignatures(a2);
+
+        Set<MethodSignature> lubSigs = new HashSet<>(a1Sigs);
+        lubSigs.retainAll(a2Sigs); // intersection
+
+        AnnotationMirror result = createMethodVal(lubSigs);
+        return result;
+      }
+      return null;
+    }
+
+    @Override
+    public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+      if (AnnotationUtils.areSame(subAnno, superAnno)
+          || areSameByClass(superAnno, UnknownMethod.class)
+          || areSameByClass(subAnno, MethodValBottom.class)) {
+        return true;
+      }
+      if (areSameByClass(subAnno, UnknownMethod.class)
+          || areSameByClass(superAnno, MethodValBottom.class)) {
+        return false;
+      }
+      assert areSameByClass(subAnno, MethodVal.class) && areSameByClass(superAnno, MethodVal.class)
+          : "Unexpected annotation in MethodVal";
+      List<MethodSignature> subSignatures = getListOfMethodSignatures(subAnno);
+      List<MethodSignature> superSignatures = getListOfMethodSignatures(superAnno);
+      return superSignatures.containsAll(subSignatures);
+    }
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(new MethodValTreeAnnotator(this), super.createTreeAnnotator());
+  }
+
+  /** TreeAnnotator with the visitMethodInvocation method overridden. */
+  protected class MethodValTreeAnnotator extends TreeAnnotator {
+
+    protected MethodValTreeAnnotator(MethodValAnnotatedTypeFactory factory) {
+      super(factory);
+    }
+
+    /*
+     * Special handling of getMethod and getDeclaredMethod calls. Attempts
+     * to get the annotation on the Class object receiver to get the
+     * className, the annotation on the String argument to get the
+     * methodName, and the parameters argument to get the param, and then
+     * builds a new MethodVal annotation from these
+     */
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
+
+      List<String> methodNames;
+      List<Integer> params;
+      List<String> classNames;
+      if (isGetConstructorMethodInvocation(tree)) {
+        // method name for constructors is always <init>
+        methodNames = ReflectionResolver.INIT_LIST;
+        params = getConstructorParamsLen(tree.getArguments());
+        classNames = getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree), true);
+
+      } else if (isGetMethodMethodInvocation(tree)) {
+        ExpressionTree methodNameArg = tree.getArguments().get(0);
+        methodNames = getMethodNamesFromStringArg(methodNameArg);
+        params = getMethodParamsLen(tree.getArguments());
+        classNames = getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree), false);
+      } else {
+        // Not a covered method invocation
+        return null;
+      }
+
+      // Create MethodVal
+      if (methodNames.isEmpty() || classNames.isEmpty()) {
+        // No method name or classname is found, it could be any
+        // class or method, so, return @UnknownMethod.
+        type.replaceAnnotation(UNKNOWN_METHOD);
+        return null;
+      }
+
+      Set<MethodSignature> methodSigs = new HashSet<>();
+      // The possible method signatures are the Cartesian product of all
+      // found class, method, and parameter lengths
+      for (String methodName : methodNames) {
+        for (String className : classNames) {
+          for (Integer param : params) {
+            methodSigs.add(new MethodSignature(className, methodName, param));
+          }
+        }
+      }
+
+      AnnotationMirror newQual = createMethodVal(methodSigs);
+      type.replaceAnnotation(newQual);
+      return null;
+    }
+
+    /**
+     * Returns true if the method being invoked is annotated with @GetConstructor. An example of
+     * such a method is Class.getConstructor.
+     */
+    private boolean isGetConstructorMethodInvocation(MethodInvocationTree tree) {
+      return getDeclAnnotation(TreeUtils.elementFromTree(tree), GetConstructor.class) != null;
+    }
+
+    /**
+     * Returns true if the method being invoked is annotated with @GetMethod. An example of such a
+     * method is Class.getMethod.
+     */
+    private boolean isGetMethodMethodInvocation(MethodInvocationTree tree) {
+      return getDeclAnnotation(TreeUtils.elementFromTree(tree), GetMethod.class) != null;
+    }
+
+    /**
+     * Returns a singleton list containing the number of parameters for a call to a method annotated
+     * with {@code @}{@link GetMethod}.
+     *
+     * @param args arguments to a call to a method such as {@code getMethod}
+     * @return the number of parameters
+     */
+    private List<Integer> getMethodParamsLen(List<? extends ExpressionTree> args) {
+      assert !args.isEmpty() : "getMethod must have at least one parameter";
+
+      // Number of parameters in the created method object
+      int numParams = args.size() - 1;
+      if (numParams == 1) {
+        return getNumberOfParameterOneArg(args.get(1));
+      }
+      return Collections.singletonList(numParams);
+    }
+
+    /**
+     * Returns a singleton list containing the number of parameters for a call to a method annotated
+     * with {@code @}{@link GetConstructor}.
+     *
+     * @param args arguments to a call to a method such as {@code getConstructor}
+     * @return the number of parameters
+     */
+    private List<Integer> getConstructorParamsLen(List<? extends ExpressionTree> args) {
+      // Number of parameters in the created method object
+      int numParams = args.size();
+      if (numParams == 1) {
+        return getNumberOfParameterOneArg(args.get(0));
+      }
+      return Collections.singletonList(numParams);
+    }
+
+    /**
+     * if getMethod(Object receiver, Object... params) or getConstrutor(Object... params) have one
+     * argument for params, then the number of parameters in the underlying method or constructor
+     * must be:
+     *
+     * <ul>
+     *   <li>0: if the argument is null
+     *   <li>x: if the argument is an array with @ArrayLen(x); note that x might be a set rather
+     *       than a single value
+     *   <li>UNKNOWN_PARAM_LENGTH: if the argument is an array with @UnknownVal
+     *   <li>1: otherwise
+     * </ul>
+     *
+     * @param argument the single argument in a call to {@code getMethod} or {@code getConstrutor}
+     * @return a list, each of whose elementts is a possible the number of parameters; it is often,
+     *     but not always, a singleton list
+     */
+    private List<Integer> getNumberOfParameterOneArg(ExpressionTree argument) {
+      AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(argument);
+      switch (atm.getKind()) {
+        case ARRAY:
+          ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class);
+          AnnotatedTypeMirror valueType = valueATF.getAnnotatedType(argument);
+          AnnotationMirror arrayLenAnno = valueType.getAnnotation(ArrayLen.class);
+          if (arrayLenAnno != null) {
+            return AnnotationUtils.getElementValueArray(
+                arrayLenAnno, arrayLenValueElement, Integer.class);
+          } else if (valueType.getAnnotation(BottomVal.class) != null) {
+            // happens in this case: (Class[]) null
+            return ZERO_LIST;
+          }
+          // the argument is an array with unknown array length
+          return UNKNOWN_PARAM_LENGTH_LIST;
+        case NULL:
+          // null is treated as the empty list of parameters, so size is 0.
+          return ZERO_LIST;
+        default:
+          // The argument is not an array or null, so it must be a class.
+          return ONE_LIST;
+      }
+    }
+  }
+}
+
+/**
+ * An object that represents a the tuple that identifies a method signature: (fully qualified class
+ * name, method name, number of parameters).
+ */
+class MethodSignature {
+  String className;
+  String methodName;
+  int params;
+
+  public MethodSignature(String className, String methodName, int params) {
+    this.className = className;
+    this.methodName = methodName;
+    this.params = params;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(className, methodName, params);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null) {
+      return false;
+    }
+    if (getClass() != obj.getClass()) {
+      return false;
+    }
+    MethodSignature other = (MethodSignature) obj;
+    if (className == null) {
+      if (other.className != null) {
+        return false;
+      }
+    } else if (!className.equals(other.className)) {
+      return false;
+    }
+    if (methodName == null) {
+      if (other.methodName != null) {
+        return false;
+      }
+    } else if (!methodName.equals(other.methodName)) {
+      return false;
+    }
+    if (params != other.params) {
+      return false;
+    }
+    return true;
+  }
+
+  @Override
+  public String toString() {
+    return "MethodSignature [className="
+        + className
+        + ", methodName="
+        + methodName
+        + ", params="
+        + params
+        + "]";
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/reflection/MethodValChecker.java b/framework/src/main/java/org/checkerframework/common/reflection/MethodValChecker.java
new file mode 100644
index 0000000..0a1d197
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/reflection/MethodValChecker.java
@@ -0,0 +1,28 @@
+package org.checkerframework.common.reflection;
+
+import java.util.LinkedHashSet;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.value.ValueChecker;
+
+/**
+ * The MethodVal Checker provides a sound estimate of the signature of Method objects.
+ *
+ * @checker_framework.manual #methodval-and-classval-checkers MethodVal Checker
+ */
+public class MethodValChecker extends BaseTypeChecker {
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new MethodValVisitor(this);
+  }
+
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    // Don't call super otherwise MethodVal will be added as a subChecker
+    // which creates a circular dependency.
+    LinkedHashSet<Class<? extends BaseTypeChecker>> subCheckers = new LinkedHashSet<>();
+    subCheckers.add(ValueChecker.class);
+    subCheckers.add(ClassValChecker.class);
+    return subCheckers;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/reflection/MethodValVisitor.java b/framework/src/main/java/org/checkerframework/common/reflection/MethodValVisitor.java
new file mode 100644
index 0000000..971a728
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/reflection/MethodValVisitor.java
@@ -0,0 +1,83 @@
+package org.checkerframework.common.reflection;
+
+import com.sun.source.tree.Tree;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeValidator;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.reflection.qual.MethodVal;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+public class MethodValVisitor extends BaseTypeVisitor<MethodValAnnotatedTypeFactory> {
+
+  public MethodValVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  protected MethodValAnnotatedTypeFactory createTypeFactory() {
+    return new MethodValAnnotatedTypeFactory(checker);
+  }
+
+  @Override
+  protected BaseTypeValidator createTypeValidator() {
+    return new MethodNameValidator(checker, this, atypeFactory);
+  }
+}
+
+class MethodNameValidator extends BaseTypeValidator {
+
+  public MethodNameValidator(
+      BaseTypeChecker checker, BaseTypeVisitor<?> visitor, AnnotatedTypeFactory atypeFactory) {
+    super(checker, visitor, atypeFactory);
+  }
+
+  @Override
+  public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) {
+    AnnotationMirror methodVal = type.getAnnotation(MethodVal.class);
+    if (methodVal != null) {
+      AnnotatedTypeFactory atypeFactory = checker.getTypeFactory();
+      List<String> classNames =
+          AnnotationUtils.getElementValueArray(
+              methodVal, atypeFactory.methodValClassNameElement, String.class);
+      List<String> methodNames =
+          AnnotationUtils.getElementValueArray(
+              methodVal, atypeFactory.methodValMethodNameElement, String.class);
+      List<Integer> params =
+          AnnotationUtils.getElementValueArray(
+              methodVal, atypeFactory.methodValParamsElement, Integer.class);
+      if (!(classNames.size() == methodNames.size() && classNames.size() == params.size())) {
+        checker.reportError(tree, "invalid.methodval", methodVal);
+      }
+
+      for (String methodName : methodNames) {
+        if (!legalMethodName(methodName)) {
+          checker.reportError(tree, "illegal.methodname", methodName, type);
+        }
+      }
+    }
+    return super.visitDeclared(type, tree);
+  }
+
+  private boolean legalMethodName(String methodName) {
+    if (methodName.equals(ReflectionResolver.INIT)) {
+      return true;
+    }
+    if (methodName.length() < 1) {
+      return false;
+    }
+    char[] methodNameChars = methodName.toCharArray();
+    if (!Character.isJavaIdentifierStart(methodNameChars[0])) {
+      return false;
+    }
+    for (int i = 1; i < methodNameChars.length; i++) {
+      if (!Character.isJavaIdentifierPart(methodNameChars[i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ReflectionResolver.java b/framework/src/main/java/org/checkerframework/common/reflection/ReflectionResolver.java
new file mode 100644
index 0000000..1de0398
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/reflection/ReflectionResolver.java
@@ -0,0 +1,46 @@
+package org.checkerframework.common.reflection;
+
+import com.sun.source.tree.MethodInvocationTree;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType;
+
+/**
+ * Interface for reflection resolvers that handle reflective method calls such as {@link
+ * Method#invoke(Object, Object...)} or {@link Constructor#newInstance(Object...)}.
+ *
+ * @checker_framework.manual #reflection-resolution Reflection resolution
+ */
+public interface ReflectionResolver {
+  /** The "method name" of constructors. */
+  public static final String INIT = "<init>";
+
+  /**
+   * A list containing just the "method name" of constructors. Clients must not modify this list.
+   */
+  public static final List<String> INIT_LIST = Collections.singletonList(INIT);
+
+  /**
+   * Determines whether the given tree represents a reflective method or constructor call.
+   *
+   * @return {@code true} iff tree is a reflective method invocation, {@code false} otherwise
+   */
+  public boolean isReflectiveMethodInvocation(MethodInvocationTree tree);
+
+  /**
+   * Resolve reflection and return the result of {@code factory.methodFromUse} for the actual,
+   * resolved method or constructor call. If the reflective method cannot be resolved the original
+   * result ({@code origResult}) is returned.
+   *
+   * @param factory the currently used AnnotatedTypeFactory
+   * @param tree the reflective invocation tree (m.invoke or c.newInstance)
+   * @param origResult the original result for the unresolved, reflective method call
+   */
+  public ParameterizedExecutableType resolveReflectiveCall(
+      AnnotatedTypeFactory factory,
+      MethodInvocationTree tree,
+      ParameterizedExecutableType origResult);
+}
diff --git a/framework/src/main/java/org/checkerframework/common/reflection/messages.properties b/framework/src/main/java/org/checkerframework/common/reflection/messages.properties
new file mode 100644
index 0000000..7f7ba1d
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/reflection/messages.properties
@@ -0,0 +1,4 @@
+### Error messages for MethodVal and ClassVal Checkers
+illegal.classname=illegal class name in annotation: %s%nfound: %s
+illegal.methodname=illegal method name in annotation: %s%nfound: %s
+invalid.methodval=@MethodVal annotation does not have equal number of class names, method names, and params%nfound: %s
diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/DescribeImages.astub b/framework/src/main/java/org/checkerframework/common/returnsreceiver/DescribeImages.astub
new file mode 100644
index 0000000..7e6cbcd
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/DescribeImages.astub
@@ -0,0 +1,24 @@
+// Support for the EC2 classes that are automatically handled by the Called Methods Checker.
+
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+package com.amazonaws.services.ec2.model;
+
+class DescribeImagesRequest {
+
+    @This DescribeImagesRequest withFilters(Filter... f);
+
+    @This DescribeImagesRequest withOwners(String... o);
+
+    @This DescribeImagesRequest withImageIds(String... i);
+
+    @This DescribeImagesRequest withFilters(Collection<Filter> f);
+
+    @This DescribeImagesRequest withOwners(Collection<String> o);
+
+    @This DescribeImagesRequest withImageIds(Collection<String> i);
+
+    @This DescribeImagesRequest withExecutableUsers(String... i);
+
+    @This DescribeImagesRequest withExecutableUsers(Collection<String> f);
+}
diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/FluentAPIGenerator.java b/framework/src/main/java/org/checkerframework/common/returnsreceiver/FluentAPIGenerator.java
new file mode 100644
index 0000000..d6ad633
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/FluentAPIGenerator.java
@@ -0,0 +1,134 @@
+package org.checkerframework.common.returnsreceiver;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * A utility class to support fluent API generators so the checker can add {@code @This} annotations
+ * on method return types when a generator has been used. To check whether a method is created by
+ * any of the generators and returns {@code this}, simply call the {@link FluentAPIGenerator#check}
+ * on the annotated type of the method signature.
+ */
+public class FluentAPIGenerator {
+
+  /**
+   * Check if a method was generated by a known fluent API generator and returns its receiver.
+   *
+   * @param t the annotated type of the method signature
+   * @return {@code true} if the method was created by a generator and returns {@code this}
+   */
+  public static boolean check(AnnotatedExecutableType t) {
+    for (FluentAPIGenerators fluentAPIGenerator : FluentAPIGenerators.values()) {
+      if (fluentAPIGenerator.returnsThis(t)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Enum of supported fluent API generators. For such generators, the checker can automatically
+   * add @This annotations on method return types in the generated code.
+   */
+  private enum FluentAPIGenerators {
+    /**
+     * The <a
+     * href="https://github.com/google/auto/blob/master/value/userguide/builders.md">AutoValue</a>
+     * framework.
+     */
+    AUTO_VALUE {
+
+      /**
+       * The qualified name of the AutoValue Builder annotation. This needs to be constructed
+       * dynamically due to a side effect of the shadow plugin. See {@link
+       * FluentAPIGenerators#AUTO_VALUE#getAutoValueBuilderCanonicalName()} for more information.
+       */
+      private final String AUTO_VALUE_BUILDER = getAutoValueBuilderCanonicalName();
+
+      @Override
+      public boolean returnsThis(AnnotatedExecutableType t) {
+        ExecutableElement element = t.getElement();
+        Element enclosingElement = element.getEnclosingElement();
+        boolean inAutoValueBuilder =
+            AnnotationUtils.containsSameByName(
+                enclosingElement.getAnnotationMirrors(), AUTO_VALUE_BUILDER);
+
+        if (!inAutoValueBuilder) {
+          // see if superclass is an AutoValue Builder, to handle generated code
+          TypeMirror superclass = ((TypeElement) enclosingElement).getSuperclass();
+          // if enclosingElement is an interface, the superclass has TypeKind NONE
+          if (superclass.getKind() != TypeKind.NONE) {
+            // update enclosingElement to be for the superclass for this case
+            enclosingElement = TypesUtils.getTypeElement(superclass);
+            inAutoValueBuilder =
+                AnnotationUtils.containsSameByName(
+                    enclosingElement.getAnnotationMirrors(), AUTO_VALUE_BUILDER);
+          }
+        }
+
+        if (inAutoValueBuilder) {
+          AnnotatedTypeMirror returnType = t.getReturnType();
+          if (returnType == null) {
+            throw new BugInCF("Return type cannot be null: " + t);
+          }
+          return enclosingElement.equals(TypesUtils.getTypeElement(returnType.getUnderlyingType()));
+        }
+        return false;
+      }
+
+      /**
+       * Get the qualified name of the AutoValue Builder annotation. This method constructs the
+       * String dynamically, to ensure it does not get rewritten due to relocation of the {@code
+       * "com.google"} package during the build process.
+       *
+       * @return {@code "com.google.auto.value.AutoValue.Builder"}
+       */
+      private @CanonicalName String getAutoValueBuilderCanonicalName() {
+        String com = "com";
+        @SuppressWarnings("signature:assignment") // string concatenation
+        @CanonicalName String result = com + "." + "google.auto.value.AutoValue.Builder";
+        return result;
+      }
+    },
+    /** <a href="https://projectlombok.org/features/Builder">Project Lombok</a>. */
+    LOMBOK {
+      @Override
+      public boolean returnsThis(AnnotatedExecutableType t) {
+        ExecutableElement element = t.getElement();
+        Element enclosingElement = element.getEnclosingElement();
+        boolean inLombokBuilder =
+            (AnnotationUtils.containsSameByName(
+                        enclosingElement.getAnnotationMirrors(), "lombok.Generated")
+                    || AnnotationUtils.containsSameByName(
+                        element.getAnnotationMirrors(), "lombok.Generated"))
+                && enclosingElement.getSimpleName().toString().endsWith("Builder");
+
+        if (inLombokBuilder) {
+          AnnotatedTypeMirror returnType = t.getReturnType();
+          if (returnType == null) {
+            throw new BugInCF("Return type cannot be null: " + t);
+          }
+          return enclosingElement.equals(TypesUtils.getTypeElement(returnType.getUnderlyingType()));
+        }
+        return false;
+      }
+    };
+
+    /**
+     * Returns {@code true} if the method was created by this generator and returns {@code this}.
+     *
+     * @param t the annotated type of the method signature
+     * @return {@code true} if the method was created by this generator and returns {@code this}
+     */
+    protected abstract boolean returnsThis(AnnotatedExecutableType t);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/GenerateDataKey.astub b/framework/src/main/java/org/checkerframework/common/returnsreceiver/GenerateDataKey.astub
new file mode 100644
index 0000000..407dc7f
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/GenerateDataKey.astub
@@ -0,0 +1,15 @@
+// Support for EC2 classes automatically supported by the Called Methods Checker.
+
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+package com.amazonaws.services.kms.model;
+
+class GenerateDataKeyRequest {
+    @This GenerateDataKeyRequest withEncryptionContext(Map<String,String> encryptionContext);
+    @This GenerateDataKeyRequest withGrantTokens(Collection<String> grantTokens);
+    @This GenerateDataKeyRequest withGrantTokens(String... grantTokens);
+    @This GenerateDataKeyRequest withKeyId(String keyId);
+    @This GenerateDataKeyRequest withKeySpec(DataKeySpec keySpec);
+    @This GenerateDataKeyRequest withKeySpec(String keySpec);
+    @This GenerateDataKeyRequest withNumberOfBytes(Integer numberOfBytes);
+}
diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverAnnotatedTypeFactory.java
new file mode 100644
index 0000000..bc497e2
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverAnnotatedTypeFactory.java
@@ -0,0 +1,94 @@
+package org.checkerframework.common.returnsreceiver;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ElementKind;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.returnsreceiver.qual.BottomThis;
+import org.checkerframework.common.returnsreceiver.qual.This;
+import org.checkerframework.common.returnsreceiver.qual.UnknownThis;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/** The type factory for the Returns Receiver Checker. */
+public class ReturnsReceiverAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  /**
+   * The {@code @}{@link This} annotation. The field is package visible due to a use in {@link
+   * ReturnsReceiverVisitor}
+   */
+  final AnnotationMirror THIS_ANNOTATION;
+
+  /**
+   * Create a new {@code ReturnsReceiverAnnotatedTypeFactory}.
+   *
+   * @param checker the type-checker associated with this factory
+   */
+  public ReturnsReceiverAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    THIS_ANNOTATION = AnnotationBuilder.fromClass(elements, This.class);
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return getBundledTypeQualifiers(BottomThis.class, UnknownThis.class, This.class);
+  }
+
+  @Override
+  protected TypeAnnotator createTypeAnnotator() {
+    return new ListTypeAnnotator(
+        new ReturnsReceiverTypeAnnotator(this), super.createTypeAnnotator());
+  }
+
+  /** A TypeAnnotator to add the {@code @}{@link This} annotation. */
+  private class ReturnsReceiverTypeAnnotator extends TypeAnnotator {
+
+    /**
+     * Create a new ReturnsReceiverTypeAnnotator.
+     *
+     * @param typeFactory the {@link AnnotatedTypeFactory} associated with this {@link
+     *     TypeAnnotator}
+     */
+    public ReturnsReceiverTypeAnnotator(AnnotatedTypeFactory typeFactory) {
+      super(typeFactory);
+    }
+
+    @Override
+    public Void visitExecutable(AnnotatedTypeMirror.AnnotatedExecutableType t, Void p) {
+
+      // skip constructors, as we never need to add annotations to them
+      if (t.getElement().getKind() == ElementKind.CONSTRUCTOR) {
+        return super.visitExecutable(t, p);
+      }
+
+      AnnotatedTypeMirror returnType = t.getReturnType();
+
+      // If any FluentAPIGenerator indicates the method returns this,
+      // add an @This annotation on the return type.
+      if (FluentAPIGenerator.check(t)) {
+        if (!returnType.isAnnotatedInHierarchy(THIS_ANNOTATION)) {
+          returnType.addAnnotation(THIS_ANNOTATION);
+        }
+      }
+
+      // If return type is annotated with @This, add @This annotation to the receiver type.  We
+      // cannot yet default all receivers to be @This due to
+      // https://github.com/typetools/checker-framework/issues/2931
+      AnnotationMirror retAnnotation = returnType.getAnnotationInHierarchy(THIS_ANNOTATION);
+      if (retAnnotation != null && AnnotationUtils.areSame(retAnnotation, THIS_ANNOTATION)) {
+        AnnotatedTypeMirror.AnnotatedDeclaredType receiverType = t.getReceiverType();
+        if (receiverType != null && !receiverType.isAnnotatedInHierarchy(THIS_ANNOTATION)) {
+          receiverType.addAnnotation(THIS_ANNOTATION);
+        }
+      }
+      return super.visitExecutable(t, p);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverChecker.java b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverChecker.java
new file mode 100644
index 0000000..37bf673
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverChecker.java
@@ -0,0 +1,8 @@
+package org.checkerframework.common.returnsreceiver;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.StubFiles;
+
+/** Entry point for the Returns Receiver Checker. */
+@StubFiles({"DescribeImages.astub", "GenerateDataKey.astub"})
+public class ReturnsReceiverChecker extends BaseTypeChecker {}
diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverVisitor.java b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverVisitor.java
new file mode 100644
index 0000000..1a60db3
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverVisitor.java
@@ -0,0 +1,59 @@
+package org.checkerframework.common.returnsreceiver;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModifiersTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/** The visitor for the Returns Receiver Checker. */
+public class ReturnsReceiverVisitor extends BaseTypeVisitor<ReturnsReceiverAnnotatedTypeFactory> {
+
+  /**
+   * Create a new {@code ReturnsReceiverVisitor}.
+   *
+   * @param checker the type-checker associated with this visitor
+   */
+  public ReturnsReceiverVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  public Void visitAnnotation(AnnotationTree node, Void p) {
+    AnnotationMirror annot = TreeUtils.annotationFromAnnotationTree(node);
+    // Warn if a @This annotation is in an illegal location.
+    if (AnnotationUtils.areSame(annot, getTypeFactory().THIS_ANNOTATION)) {
+      TreePath parentPath = getCurrentPath().getParentPath();
+      Tree parent = parentPath.getLeaf();
+      Tree grandparent = parentPath.getParentPath().getLeaf();
+      Tree greatGrandparent = parentPath.getParentPath().getParentPath().getLeaf();
+      boolean isReturnAnnot =
+          grandparent instanceof MethodTree
+              && (parent.equals(((MethodTree) grandparent).getReturnType())
+                  || parent instanceof ModifiersTree);
+      boolean isReceiverAnnot =
+          greatGrandparent instanceof MethodTree
+              && grandparent.equals(((MethodTree) greatGrandparent).getReceiverParameter())
+              && parent.equals(((VariableTree) grandparent).getModifiers());
+      boolean isCastAnnot =
+          grandparent instanceof TypeCastTree
+              && parent.equals(((TypeCastTree) grandparent).getType());
+      if (!(isReturnAnnot || isReceiverAnnot || isCastAnnot)) {
+        checker.reportError(node, "this.location");
+      }
+      if (isReturnAnnot
+          && ElementUtils.isStatic(TreeUtils.elementFromDeclaration((MethodTree) grandparent))) {
+        checker.reportError(node, "this.location");
+      }
+    }
+    return super.visitAnnotation(node, p);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/messages.properties b/framework/src/main/java/org/checkerframework/common/returnsreceiver/messages.properties
new file mode 100644
index 0000000..0f0aa6f
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/messages.properties
@@ -0,0 +1 @@
+this.location=invalid location for @This annotation, only usable on method returns and receivers
diff --git a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotatedTypeFactory.java
new file mode 100644
index 0000000..91aa743
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotatedTypeFactory.java
@@ -0,0 +1,131 @@
+package org.checkerframework.common.subtyping;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.subtyping.qual.Unqualified;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.type.AnnotationClassLoader;
+import org.checkerframework.framework.util.defaults.QualifierDefaults;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.UserError;
+import org.plumelib.reflection.Signatures;
+
+/** Defines {@link #createSupportedTypeQualifiers}. */
+public class SubtypingAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  public SubtypingAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    postInit();
+  }
+
+  @Override
+  protected AnnotationClassLoader createAnnotationClassLoader() {
+    return new SubtypingAnnotationClassLoader(checker);
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    // Subtyping Checker doesn't have a qual directory, so we instantiate the loader here to
+    // load externally declared annotations
+    loader = createAnnotationClassLoader();
+
+    String qualNames = checker.getOption("quals");
+    String qualDirectories = checker.getOption("qualDirs");
+
+    if (qualNames == null && qualDirectories == null) {
+      throw new UserError("SubtypingChecker: missing required option. Use -Aquals or -AqualDirs.");
+    }
+
+    Set<Class<? extends Annotation>> qualSet = new LinkedHashSet<>();
+
+    // load individually named qualifiers
+    if (qualNames != null) {
+      for (String qualName : qualNames.split(",")) {
+        if (!Signatures.isBinaryName(qualName)) {
+          throw new UserError("Malformed qualifier \"%s\" in -Aquals=%s", qualName, qualNames);
+        }
+        Class<? extends Annotation> anno = loader.loadExternalAnnotationClass(qualName);
+        if (anno == null) {
+          throw new UserError("Qualifier specified in -Aquals not found: " + qualName);
+        }
+        qualSet.add(anno);
+      }
+    }
+
+    // load directories of qualifiers
+    if (qualDirectories != null) {
+      for (String dirName : qualDirectories.split(":")) {
+        Set<Class<? extends Annotation>> annos =
+            loader.loadExternalAnnotationClassesFromDirectory(dirName);
+        if (annos.isEmpty()) {
+          throw new UserError(
+              "Directory specified in -AqualsDir contains no qualifiers: " + dirName);
+        }
+        qualSet.addAll(annos);
+      }
+    }
+
+    if (qualSet.isEmpty()) {
+      throw new UserError("SubtypingChecker: no qualifiers specified via -Aquals or -AqualDirs");
+    }
+
+    // check for subtype meta-annotation
+    for (Class<? extends Annotation> qual : qualSet) {
+      Annotation subtypeOfAnnotation = qual.getAnnotation(SubtypeOf.class);
+      if (subtypeOfAnnotation != null) {
+        for (Class<? extends Annotation> superqual : qual.getAnnotation(SubtypeOf.class).value()) {
+          if (!qualSet.contains(superqual)) {
+            throw new UserError(
+                "SubtypingChecker: qualifier "
+                    + qual
+                    + " was specified via -Aquals but its super-qualifier "
+                    + superqual
+                    + " was not");
+          }
+        }
+      }
+    }
+
+    return qualSet;
+  }
+
+  /**
+   * If necessary, make Unqualified the default qualifier. Keep most logic in sync with super.
+   *
+   * @see
+   *     org.checkerframework.framework.type.GenericAnnotatedTypeFactory#addCheckedCodeDefaults(org.checkerframework.framework.util.defaults.QualifierDefaults)
+   */
+  @Override
+  protected void addCheckedCodeDefaults(QualifierDefaults defs) {
+    boolean foundOtherwise = false;
+    // Add defaults from @DefaultFor and @DefaultQualifierInHierarchy
+    for (Class<? extends Annotation> qual : getSupportedTypeQualifiers()) {
+      DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class);
+      if (defaultFor != null) {
+        final TypeUseLocation[] locations = defaultFor.value();
+        defs.addCheckedCodeDefaults(AnnotationBuilder.fromClass(elements, qual), locations);
+        foundOtherwise =
+            foundOtherwise || Arrays.asList(locations).contains(TypeUseLocation.OTHERWISE);
+      }
+
+      if (qual.getAnnotation(DefaultQualifierInHierarchy.class) != null) {
+        defs.addCheckedCodeDefault(
+            AnnotationBuilder.fromClass(elements, qual), TypeUseLocation.OTHERWISE);
+        foundOtherwise = true;
+      }
+    }
+    // If Unqualified is a supported qualifier, make it the default.
+    AnnotationMirror unqualified = AnnotationBuilder.fromClass(elements, Unqualified.class);
+    if (!foundOtherwise && this.isSupportedQualifier(unqualified)) {
+      defs.addCheckedCodeDefault(unqualified, TypeUseLocation.OTHERWISE);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotationClassLoader.java b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotationClassLoader.java
new file mode 100644
index 0000000..9939ed8
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotationClassLoader.java
@@ -0,0 +1,21 @@
+package org.checkerframework.common.subtyping;
+
+import java.lang.annotation.Annotation;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.subtyping.qual.Unqualified;
+import org.checkerframework.framework.type.AnnotationClassLoader;
+
+public class SubtypingAnnotationClassLoader extends AnnotationClassLoader {
+
+  public SubtypingAnnotationClassLoader(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  // Unqualified is a supported annotation for the Subtyping Checker, and is loaded only if listed
+  // in -Aquals. It intentionally has an empty @Target meta-annotation. All other annotations used
+  // with the subtyping checker must have a well-defined @Target meta-annotation.
+  @Override
+  protected boolean hasWellDefinedTargetMetaAnnotation(Class<? extends Annotation> annoClass) {
+    return super.hasWellDefinedTargetMetaAnnotation(annoClass) || annoClass == Unqualified.class;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java
new file mode 100644
index 0000000..d2cbae4
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java
@@ -0,0 +1,64 @@
+package org.checkerframework.common.subtyping;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import javax.annotation.processing.SupportedOptions;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.source.SourceVisitor;
+
+/**
+ * A checker for type qualifier systems that only checks subtyping relationships.
+ *
+ * <p>The annotation(s) are specified on the command line, using an annotation processor argument:
+ *
+ * <ul>
+ *   <li>{@code -Aquals}: specifies the annotations in the qualifier hierarchy (as a comma-separated
+ *       list of fully-qualified annotation names with no spaces in between). Only the annotations
+ *       for one qualified subtype hierarchy can be passed.
+ * </ul>
+ *
+ * @checker_framework.manual #subtyping-checker Subtying Checker
+ */
+@SupportedOptions({"quals", "qualDirs"})
+public final class SubtypingChecker extends BaseTypeChecker {
+
+  /**
+   * Compute SuppressWarnings prefixes, based on the names of all the qualifiers.
+   *
+   * <p>Provided for the convenience of checkers that do not subclass {@code SubtypingChecker}
+   * (because it is final). Clients should call it like:
+   *
+   * <pre>{@code
+   * SubtypingChecker.getSuppressWarningsPrefixes(this.visitor, super.getSuppressWarningsPrefixes());
+   * }</pre>
+   *
+   * @param visitor the visitor
+   * @param superSupportedTypeQualifiers the result of super.getSuppressWarningsPrefixes(), as
+   *     executed by checker
+   * @return SuppressWarnings prefixes, based on the names of all the qualifiers
+   */
+  public static SortedSet<String> getSuppressWarningsPrefixes(
+      SourceVisitor<?, ?> visitor, SortedSet<String> superSupportedTypeQualifiers) {
+    TreeSet<String> result = new TreeSet<>(superSupportedTypeQualifiers);
+
+    // visitor can be null if there was an error when calling the visitor class's constructor --
+    // that is, when there is a bug in a checker implementation.
+    if (visitor != null) {
+      Set<Class<? extends Annotation>> annos =
+          ((BaseTypeVisitor<?>) visitor).getTypeFactory().getSupportedTypeQualifiers();
+      for (Class<? extends Annotation> anno : annos) {
+        result.add(anno.getSimpleName().toLowerCase());
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public SortedSet<String> getSuppressWarningsPrefixes() {
+    return getSuppressWarningsPrefixes(this.visitor, super.getSuppressWarningsPrefixes());
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java b/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java
new file mode 100644
index 0000000..5febc22
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java
@@ -0,0 +1,609 @@
+package org.checkerframework.common.util;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor;
+import org.checkerframework.framework.util.DefaultAnnotationFormatter;
+import org.checkerframework.framework.util.ExecUtil;
+import org.checkerframework.javacutil.BugInCF;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * TypeVisualizer prints AnnotatedTypeMirrors as a directed graph where each node is a type and an
+ * arrow is a reference. Arrows are labeled with the relationship that reference represents (e.g. an
+ * arrow marked "extends" starts from a type variable or wildcard type and points to the upper bound
+ * of that type).
+ *
+ * <p>Currently, to use TypeVisualizer just insert an if statement somewhere that targets the type
+ * you would like to print: e.g.
+ *
+ * <pre>{@code
+ * if (type.getKind() == TypeKind.EXECUTABLE && type.toString().contains("methodToPrint")) {
+ *     TypeVisualizer.drawToPng("/Users/jburke/Documents/tmp/method.png", type);
+ * }
+ * }</pre>
+ *
+ * Be sure to remove such statements before committing your changes.
+ */
+public class TypeVisualizer {
+
+  /**
+   * Creates a dot file at dest that contains a digraph for the structure of {@code type}.
+   *
+   * @param dest the destination dot file
+   * @param type the type to be written
+   */
+  public static void drawToDot(final File dest, final AnnotatedTypeMirror type) {
+    final Drawing drawer = new Drawing("Type", type);
+    drawer.draw(dest);
+  }
+
+  /**
+   * Creates a dot file at dest that contains a digraph for the structure of {@code type}.
+   *
+   * @param dest the destination dot file, this string will be directly passed to new File(dest)
+   * @param type the type to be written
+   */
+  public static void drawToDot(final String dest, final AnnotatedTypeMirror type) {
+    drawToDot(new File(dest), type);
+  }
+
+  /**
+   * Draws a dot file for type in a temporary directory then calls the "dot" program to convert that
+   * file into a png at the location dest. This method will fail if a temp file can't be created.
+   *
+   * @param dest the destination png file
+   * @param type the type to be written
+   */
+  public static void drawToPng(final File dest, final AnnotatedTypeMirror type) {
+    try {
+      final File dotFile = File.createTempFile(dest.getName(), ".dot");
+      drawToDot(dotFile, type);
+      execDotToPng(dotFile, dest);
+
+    } catch (Exception exc) {
+      throw new RuntimeException(exc);
+    }
+  }
+
+  /**
+   * Draws a dot file for type in a temporary directory then calls the "dot" program to convert that
+   * file into a png at the location dest. This method will fail if a temp file can't be created.
+   *
+   * @param dest the destination png file, this string will be directly passed to new File(dest)
+   * @param type the type to be written
+   */
+  public static void drawToPng(final String dest, final AnnotatedTypeMirror type) {
+    drawToPng(new File(dest), type);
+  }
+
+  /**
+   * Converts the given dot file to a png file at the specified location. This method calls the
+   * program "dot" from Runtime.exec and will fail if "dot" is not on your class path.
+   *
+   * @param dotFile the dot file to convert
+   * @param pngFile the destination of the resultant png file
+   */
+  public static void execDotToPng(final File dotFile, final File pngFile) {
+    String[] cmd =
+        new String[] {"dot", "-Tpng", dotFile.getAbsolutePath(), "-o", pngFile.getAbsolutePath()};
+    System.out.println("Printing dotFile: " + dotFile + " to loc: " + pngFile);
+    System.out.flush();
+    ExecUtil.execute(cmd, System.out, System.err);
+  }
+
+  /**
+   * If the name of typeVariable matches one in the list of typeVarNames, then print typeVariable to
+   * a dot file at {@code directory/varName}.
+   *
+   * @return true if the type variable was printed, otherwise false
+   */
+  public static boolean printTypevarToDotIfMatches(
+      final AnnotatedTypeVariable typeVariable,
+      final List<String> typeVarNames,
+      final String directory) {
+    return printTypevarIfMatches(typeVariable, typeVarNames, directory, false);
+  }
+
+  /**
+   * If the name of typeVariable matches one in the list of typeVarNames, then print typeVariable to
+   * a png file at {@code directory/varName.png}.
+   *
+   * @return true if the type variable was printed, otherwise false
+   */
+  public static boolean printTypevarToPngIfMatches(
+      final AnnotatedTypeVariable typeVariable,
+      final List<String> typeVarNames,
+      final String directory) {
+    return printTypevarIfMatches(typeVariable, typeVarNames, directory, true);
+  }
+
+  private static boolean printTypevarIfMatches(
+      final AnnotatedTypeVariable typeVariable,
+      final List<String> typeVarNames,
+      final String directory,
+      final boolean png) {
+    final String dirPath =
+        directory.endsWith(File.separator) ? directory : directory + File.separator;
+    String varName = typeVariable.getUnderlyingType().asElement().toString();
+
+    if (typeVarNames.contains(varName)) {
+      if (png) {
+        TypeVisualizer.drawToPng(dirPath + varName + ".png", typeVariable);
+      } else {
+        TypeVisualizer.drawToDot(dirPath + varName + ".dot", typeVariable);
+      }
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * This class exists because there is no LinkedIdentityHashMap.
+   *
+   * <p>Node is just a wrapper around type mirror that replaces .equals with referential equality.
+   * This is done to preserve the order types were traversed so that printing will occur in a
+   * hierarchical order. However, since there is no LinkedIdentityHashMap, it was easiest to just
+   * create a wrapper that performed referential equality on types and use a LinkedHashMap.
+   */
+  private static class Node {
+    /** The delegate; that is, the wrapped value. */
+    private final @InternedDistinct AnnotatedTypeMirror type;
+
+    /**
+     * Create a new Node that wraps the given type.
+     *
+     * @param type the type that the newly-constructed Node represents
+     */
+    private Node(final @FindDistinct AnnotatedTypeMirror type) {
+      this.type = type;
+    }
+
+    @Override
+    public int hashCode() {
+      return type.hashCode();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+      if (obj == null) {
+        return false;
+      }
+      if (obj instanceof Node) {
+        return ((Node) obj).type == this.type;
+      }
+
+      return false;
+    }
+  }
+
+  /**
+   * Drawing visits a type and writes a dot file to the location specified. It contains data
+   * structures to hold the intermediate dot information before printing.
+   */
+  private static class Drawing {
+    /** A map from Node (type) to a dot string declaring that node. */
+    private final Map<Node, String> nodes = new LinkedHashMap<>();
+
+    /** List of connections between nodes. Lines refer to identifiers in nodes.values(). */
+    private final List<String> lines = new ArrayList<>();
+
+    private final String graphName;
+
+    /** The type being drawn. */
+    private final AnnotatedTypeMirror type;
+
+    /** Used to identify nodes uniquely. This field is monotonically increasing. */
+    private int nextId = 0;
+
+    public Drawing(final String graphName, final AnnotatedTypeMirror type) {
+      this.graphName = graphName;
+      this.type = type;
+    }
+
+    public void draw(final File file) {
+      addNodes(type);
+      addConnections();
+      write(file);
+    }
+
+    private void addNodes(final AnnotatedTypeMirror type) {
+      new NodeDrawer().visit(type);
+    }
+
+    private void addConnections() {
+      final ConnectionDrawer connectionDrawer = new ConnectionDrawer();
+      for (final Node node : nodes.keySet()) {
+        connectionDrawer.visit(node.type);
+      }
+    }
+
+    private void write(final File file) {
+      try {
+        BufferedWriter writer = null;
+        try {
+          writer = new BufferedWriter(new FileWriter(file));
+          writer.write("digraph " + graphName + "{");
+          writer.newLine();
+          for (final String line : lines) {
+            writer.write(line + ";");
+            writer.newLine();
+          }
+
+          writer.write("}");
+          writer.flush();
+        } catch (IOException e) {
+          throw new BugInCF(e, "Exception visualizing type:%nfile=%s%ntype=%s", file, type);
+        } finally {
+          if (writer != null) {
+            writer.close();
+          }
+        }
+      } catch (IOException exc) {
+        throw new BugInCF(exc, "Exception visualizing type:%nfile=%s%ntype=%s", file, type);
+      }
+    }
+
+    /**
+     * Connection drawer is used to add the connections between all the nodes created by the
+     * NodeDrawer. It is not a scanner and is called on every node in the nodes map.
+     */
+    private class ConnectionDrawer implements AnnotatedTypeVisitor<Void, Void> {
+
+      @Override
+      public Void visit(AnnotatedTypeMirror type) {
+        type.accept(this, null);
+        return null;
+      }
+
+      @Override
+      public Void visit(AnnotatedTypeMirror type, Void aVoid) {
+        return visit(type);
+      }
+
+      @Override
+      public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) {
+        final List<AnnotatedTypeMirror> typeArgs = type.getTypeArguments();
+        for (int i = 0; i < typeArgs.size(); i++) {
+          lines.add(connect(type, typeArgs.get(i)) + " " + makeTypeArgLabel(i));
+        }
+        return null;
+      }
+
+      @Override
+      public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) {
+        final List<AnnotatedTypeMirror> bounds = type.getBounds();
+        for (int i = 0; i < bounds.size(); i++) {
+          lines.add(connect(type, bounds.get(i)) + " " + makeLabel("&"));
+        }
+        return null;
+      }
+
+      @Override
+      public Void visitUnion(AnnotatedUnionType type, Void aVoid) {
+        final List<AnnotatedDeclaredType> alternatives = type.getAlternatives();
+        for (int i = 0; i < alternatives.size(); i++) {
+          lines.add(connect(type, alternatives.get(i)) + " " + makeLabel("|"));
+        }
+        return null;
+      }
+
+      @Override
+      public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) {
+
+        ExecutableElement methodElem = type.getElement();
+        lines.add(connect(type, type.getReturnType()) + " " + makeLabel("returns"));
+
+        final List<? extends TypeParameterElement> typeVarElems = methodElem.getTypeParameters();
+        final List<AnnotatedTypeVariable> typeVars = type.getTypeVariables();
+        for (int i = 0; i < typeVars.size(); i++) {
+          final String typeVarName = typeVarElems.get(i).getSimpleName().toString();
+          lines.add(connect(type, typeVars.get(i)) + " " + makeMethodTypeArgLabel(typeVarName));
+        }
+
+        if (type.getReceiverType() != null) {
+          lines.add(connect(type, type.getReceiverType()) + " " + makeLabel("receiver"));
+        }
+
+        final List<? extends VariableElement> paramElems = methodElem.getParameters();
+        final List<AnnotatedTypeMirror> params = type.getParameterTypes();
+        for (int i = 0; i < params.size(); i++) {
+          final String paramName = paramElems.get(i).getSimpleName().toString();
+          lines.add(connect(type, params.get(i)) + " " + makeParamLabel(paramName));
+        }
+
+        final List<AnnotatedTypeMirror> thrown = type.getThrownTypes();
+        for (int i = 0; i < thrown.size(); i++) {
+          lines.add(connect(type, thrown.get(i)) + " " + makeThrownLabel(i));
+        }
+
+        return null;
+      }
+
+      @Override
+      public Void visitArray(AnnotatedArrayType type, Void aVoid) {
+        lines.add(connect(type, type.getComponentType()));
+        return null;
+      }
+
+      @Override
+      public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) {
+        lines.add(connect(type, type.getUpperBound()) + " " + makeLabel("extends"));
+        lines.add(connect(type, type.getLowerBound()) + " " + makeLabel("super"));
+        return null;
+      }
+
+      @Override
+      public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) {
+        return null;
+      }
+
+      @Override
+      public Void visitNoType(AnnotatedNoType type, Void aVoid) {
+        return null;
+      }
+
+      @Override
+      public Void visitNull(AnnotatedNullType type, Void aVoid) {
+        return null;
+      }
+
+      @Override
+      public Void visitWildcard(AnnotatedWildcardType type, Void aVoid) {
+        lines.add(connect(type, type.getExtendsBound()) + " " + makeLabel("extends"));
+        lines.add(connect(type, type.getSuperBound()) + " " + makeLabel("super"));
+        return null;
+      }
+
+      private String connect(final AnnotatedTypeMirror from, final AnnotatedTypeMirror to) {
+        return nodes.get(new Node(from)) + " -> " + nodes.get(new Node(to));
+      }
+
+      private String makeLabel(final String text) {
+        return "[label=\"" + text + "\"]";
+      }
+
+      private String makeTypeArgLabel(final int argIndex) {
+        return makeLabel("<" + argIndex + ">");
+      }
+
+      private String makeMethodTypeArgLabel(final String paramName) {
+        return makeLabel("<" + paramName + ">");
+      }
+
+      private String makeParamLabel(final String paramName) {
+        return makeLabel(paramName);
+      }
+
+      private String makeThrownLabel(final int index) {
+        return makeLabel("throws: " + index);
+      }
+    }
+
+    /**
+     * Scans types and adds a mapping from type to dot node declaration representing that type in
+     * the enclosing drawing.
+     */
+    private class NodeDrawer implements AnnotatedTypeVisitor<Void, Void> {
+      private final DefaultAnnotationFormatter annoFormatter = new DefaultAnnotationFormatter();
+
+      public NodeDrawer() {}
+
+      private void visitAll(final List<? extends AnnotatedTypeMirror> types) {
+        for (final AnnotatedTypeMirror type : types) {
+          visit(type);
+        }
+      }
+
+      @Override
+      public Void visit(AnnotatedTypeMirror type) {
+        if (type != null) {
+          type.accept(this, null);
+        }
+
+        return null;
+      }
+
+      @Override
+      public Void visit(AnnotatedTypeMirror type, Void aVoid) {
+        return visit(type);
+      }
+
+      @Override
+      public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) {
+        if (checkOrAdd(type)) {
+          addLabeledNode(
+              type,
+              getAnnoStr(type)
+                  + " "
+                  + type.getUnderlyingType().asElement().getSimpleName()
+                  + (type.getTypeArguments().isEmpty() ? "" : "<...>"),
+              "shape=box");
+          visitAll(type.getTypeArguments());
+        }
+        return null;
+      }
+
+      @Override
+      public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) {
+        if (checkOrAdd(type)) {
+          addLabeledNode(type, getAnnoStr(type) + " Intersection", "shape=octagon");
+          visitAll(type.getBounds());
+        }
+
+        return null;
+      }
+
+      @Override
+      public Void visitUnion(AnnotatedUnionType type, Void aVoid) {
+        if (checkOrAdd(type)) {
+          addLabeledNode(type, getAnnoStr(type) + " Union", "shape=doubleoctagon");
+          visitAll(type.getAlternatives());
+        }
+        return null;
+      }
+
+      @Override
+      public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) {
+        if (checkOrAdd(type)) {
+          addLabeledNode(type, makeMethodLabel(type), "shape=box,peripheries=2");
+
+          visit(type.getReturnType());
+          visitAll(type.getTypeVariables());
+
+          visit(type.getReceiverType());
+          visitAll(type.getParameterTypes());
+
+          visitAll(type.getThrownTypes());
+
+        } else {
+          throw new BugInCF("Executable types should never be recursive%ntype=%s", type);
+        }
+        return null;
+      }
+
+      @Override
+      public Void visitArray(AnnotatedArrayType type, Void aVoid) {
+        if (checkOrAdd(type)) {
+          addLabeledNode(type, getAnnoStr(type) + "[]");
+          visit(type.getComponentType());
+        }
+        return null;
+      }
+
+      @Override
+      public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) {
+        if (checkOrAdd(type)) {
+          addLabeledNode(
+              type,
+              getAnnoStr(type) + " " + type.getUnderlyingType().asElement().getSimpleName(),
+              "shape=invtrapezium");
+          visit(type.getUpperBound());
+          visit(type.getLowerBound());
+        }
+        return null;
+      }
+
+      @Override
+      public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) {
+        if (checkOrAdd(type)) {
+          addLabeledNode(type, getAnnoStr(type) + " " + type.getKind());
+        }
+        return null;
+      }
+
+      @Override
+      public Void visitNoType(AnnotatedNoType type, Void aVoid) {
+        if (checkOrAdd(type)) {
+          addLabeledNode(type, getAnnoStr(type) + " None");
+        }
+        return null;
+      }
+
+      @Override
+      public Void visitNull(AnnotatedNullType type, Void aVoid) {
+        if (checkOrAdd(type)) {
+          addLabeledNode(type, getAnnoStr(type) + " NullType");
+        }
+        return null;
+      }
+
+      @Override
+      public Void visitWildcard(AnnotatedWildcardType type, Void aVoid) {
+        if (checkOrAdd(type)) {
+          addLabeledNode(type, getAnnoStr(type) + "?", "shape=trapezium");
+          visit(type.getExtendsBound());
+          visit(type.getSuperBound());
+        }
+        return null;
+      }
+
+      /**
+       * Returns a string representation of the annotations on a type.
+       *
+       * @param atm an annotated type
+       * @return a string representation of the annotations on {@code atm}
+       */
+      public String getAnnoStr(final AnnotatedTypeMirror atm) {
+        StringJoiner sj = new StringJoiner(" ");
+        for (final AnnotationMirror anno : atm.getAnnotations()) {
+          // TODO: More comprehensive escaping
+          sj.add(annoFormatter.formatAnnotationMirror(anno).replace("\"", "\\"));
+        }
+        return sj.toString();
+      }
+
+      public boolean checkOrAdd(final AnnotatedTypeMirror atm) {
+        final Node node = new Node(atm);
+        if (nodes.containsKey(node)) {
+          return false;
+        }
+        nodes.put(node, String.valueOf(nextId++));
+        return true;
+      }
+
+      public String makeLabeledNode(final AnnotatedTypeMirror type, final String label) {
+        return makeLabeledNode(type, label, null);
+      }
+
+      public String makeLabeledNode(
+          final AnnotatedTypeMirror type, final String label, final String attributes) {
+        final String attr = (attributes != null) ? ", " + attributes : "";
+        return nodes.get(new Node(type)) + " [label=\"" + label + "\"" + attr + "]";
+      }
+
+      public void addLabeledNode(final AnnotatedTypeMirror type, final String label) {
+        lines.add(makeLabeledNode(type, label));
+      }
+
+      public void addLabeledNode(
+          final AnnotatedTypeMirror type, final String label, final String attributes) {
+        lines.add(makeLabeledNode(type, label, attributes));
+      }
+
+      public String makeMethodLabel(final AnnotatedExecutableType methodType) {
+        final ExecutableElement methodElem = methodType.getElement();
+
+        final StringBuilder builder = new StringBuilder();
+        builder.append(methodElem.getReturnType().toString());
+        builder.append(" <");
+
+        builder.append(StringsPlume.join(", ", methodElem.getTypeParameters()));
+        builder.append("> ");
+
+        builder.append(methodElem.getSimpleName().toString());
+
+        builder.append("(");
+        builder.append(StringsPlume.join(",", methodElem.getParameters()));
+        builder.append(")");
+        return builder.toString();
+      }
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java
new file mode 100644
index 0000000..8649a7a
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java
@@ -0,0 +1,303 @@
+package org.checkerframework.common.util.count;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.TypeParameterTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.tree.WildcardTree;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.util.Log;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeSet;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Name;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.source.SourceVisitor;
+import org.checkerframework.framework.source.SupportedOptions;
+import org.checkerframework.javacutil.AnnotationProvider;
+
+/**
+ * An annotation processor for listing the potential locations of annotations. To invoke it, use
+ *
+ * <pre>
+ * javac -proc:only -processor org.checkerframework.common.util.count.AnnotationStatistics <em>MyFile.java ...</em>
+ * </pre>
+ *
+ * <p>You probably want to pipe the output through another program:
+ *
+ * <ul>
+ *   <li>Total annotation count: {@code ... | wc}.
+ *   <li>Breakdown by location type: {@code ... | sort | uniq -c}
+ *   <li>Count for only certain location types: use {@code grep}
+ * </ul>
+ *
+ * <p>By default, this utility displays annotation locations only. The following two options may be
+ * used to adjust the output:
+ *
+ * <ul>
+ *   <li>{@code -Aannotations}: prints information about the annotations
+ *   <li>{@code -Anolocations}: suppresses location output; only makes sense in conjunction with
+ *       {@code -Aannotations}
+ *   <li>{@code -Aannotationsummaryonly}: with both of the obove, only outputs a summary
+ * </ul>
+ *
+ * @see JavaCodeStatistics
+ */
+/*
+ * TODO: add an option to only list declaration or type annotations.
+ * This e.g. influences the output of "method return", which is only valid
+ * for type annotations for non-void methods.
+ */
+@SupportedOptions({"nolocations", "annotations", "annotationsummaryonly"})
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
+public class AnnotationStatistics extends SourceChecker {
+
+  /**
+   * Map from annotation name (as the toString() of its Name representation) to number of times the
+   * annotation was written in source code.
+   */
+  final Map<String, Integer> annotationCount = new HashMap<>();
+
+  /** Creates an AnnotationStatistics. */
+  public AnnotationStatistics() {
+    // This checker never issues any warnings, so don't warn about
+    // @SuppressWarnings("allcheckers:...").
+    this.useAllcheckersPrefix = false;
+  }
+
+  @Override
+  public void typeProcessingOver() {
+    Log log = getCompilerLog();
+    if (log.nerrors != 0) {
+      System.out.println("Not counting annotations, because compilation issued an error.");
+    } else if (annotationCount.isEmpty()) {
+      System.out.println("No annotations found.");
+    } else {
+      System.out.println("Found annotations: ");
+      for (String key : new TreeSet<>(annotationCount.keySet())) {
+        System.out.println(key + "\t" + annotationCount.get(key));
+      }
+    }
+    super.typeProcessingOver();
+  }
+
+  /** Increment the number of times annotation with name {@code annoName} has appeared. */
+  protected void incrementCount(Name annoName) {
+    String annoString = annoName.toString();
+    if (!annotationCount.containsKey(annoString)) {
+      annotationCount.put(annoString, 1);
+    } else {
+      annotationCount.put(annoString, annotationCount.get(annoString) + 1);
+    }
+  }
+
+  @Override
+  protected SourceVisitor<?, ?> createSourceVisitor() {
+    return new Visitor(this);
+  }
+
+  class Visitor extends SourceVisitor<Void, Void> {
+
+    /** Whether annotation locations should be printed. */
+    private final boolean locations;
+
+    /** Whether annotation details should be printed. */
+    private final boolean annotations;
+
+    /** Whether only a summary should be printed. */
+    private final boolean annotationsummaryonly;
+
+    /**
+     * Create a new Visitor.
+     *
+     * @param l the AnnotationStatistics object, used for obtaining command-line arguments
+     */
+    public Visitor(AnnotationStatistics l) {
+      super(l);
+
+      locations = !l.hasOption("nolocations");
+      annotations = l.hasOption("annotations");
+      annotationsummaryonly = l.hasOption("annotationsummaryonly");
+    }
+
+    @Override
+    public Void visitAnnotation(AnnotationTree tree, Void p) {
+      if (annotations) {
+        Name annoName = ((JCAnnotation) tree).annotationType.type.tsym.getQualifiedName();
+        incrementCount(annoName);
+
+        // An annotation is a body annotation if, while ascending the AST from the annotation to the
+        // root, we find a block immediately enclosed by a method.
+        //
+        // If an annotation is not a body annotation, it's a signature (declaration) annotation.
+
+        boolean isBodyAnnotation = false;
+        TreePath path = getCurrentPath();
+        Tree prev = null;
+        for (Tree t : path) {
+          if (prev != null
+              && prev.getKind() == Tree.Kind.BLOCK
+              && t.getKind() == Tree.Kind.METHOD) {
+            isBodyAnnotation = true;
+            break;
+          }
+          prev = t;
+        }
+
+        if (!annotationsummaryonly) {
+          System.out.printf(
+              ":annotation %s %s %s %s%n",
+              tree.getAnnotationType(),
+              tree,
+              root.getSourceFile().getName(),
+              (isBodyAnnotation ? "body" : "sig"));
+        }
+      }
+      return super.visitAnnotation(tree, p);
+    }
+
+    @Override
+    public Void visitArrayType(ArrayTypeTree tree, Void p) {
+      if (locations) {
+        System.out.println("array type");
+      }
+      return super.visitArrayType(tree, p);
+    }
+
+    @Override
+    public Void visitClass(ClassTree tree, Void p) {
+      if (shouldSkipDefs(tree)) {
+        // Not "return super.visitClass(classTree, p);" because that would recursively call visitors
+        // on subtrees; we want to skip the class entirely.
+        return null;
+      }
+      if (locations) {
+        System.out.println("class");
+        if (tree.getExtendsClause() != null) {
+          System.out.println("class extends");
+        }
+        for (@SuppressWarnings("unused") Tree t : tree.getImplementsClause()) {
+          System.out.println("class implements");
+        }
+      }
+      return super.visitClass(tree, p);
+    }
+
+    @Override
+    public Void visitMethod(MethodTree tree, Void p) {
+      if (locations) {
+        System.out.println("method return");
+        System.out.println("method receiver");
+        for (@SuppressWarnings("unused") Tree t : tree.getThrows()) {
+          System.out.println("method throws");
+        }
+        for (@SuppressWarnings("unused") Tree t : tree.getParameters()) {
+          System.out.println("method param");
+        }
+      }
+      return super.visitMethod(tree, p);
+    }
+
+    @Override
+    public Void visitVariable(VariableTree tree, Void p) {
+      if (locations) {
+        System.out.println("variable");
+      }
+      return super.visitVariable(tree, p);
+    }
+
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree tree, Void p) {
+      if (locations) {
+        for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) {
+          System.out.println("method invocation type argument");
+        }
+      }
+      return super.visitMethodInvocation(tree, p);
+    }
+
+    @Override
+    public Void visitNewClass(NewClassTree tree, Void p) {
+      if (locations) {
+        System.out.println("new class");
+        for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) {
+          System.out.println("new class type argument");
+        }
+      }
+      return super.visitNewClass(tree, p);
+    }
+
+    @Override
+    public Void visitNewArray(NewArrayTree tree, Void p) {
+      if (locations) {
+        System.out.println("new array");
+        for (@SuppressWarnings("unused") Tree t : tree.getDimensions()) {
+          System.out.println("new array dimension");
+        }
+      }
+      return super.visitNewArray(tree, p);
+    }
+
+    @Override
+    public Void visitTypeCast(TypeCastTree tree, Void p) {
+      if (locations) {
+        System.out.println("typecast");
+      }
+      return super.visitTypeCast(tree, p);
+    }
+
+    @Override
+    public Void visitInstanceOf(InstanceOfTree tree, Void p) {
+      if (locations) {
+        System.out.println("instanceof");
+      }
+      return super.visitInstanceOf(tree, p);
+    }
+
+    @Override
+    public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) {
+      if (locations) {
+        for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) {
+          System.out.println("parameterized type");
+        }
+      }
+      return super.visitParameterizedType(tree, p);
+    }
+
+    @Override
+    public Void visitTypeParameter(TypeParameterTree tree, Void p) {
+      if (locations) {
+        for (@SuppressWarnings("unused") Tree t : tree.getBounds()) {
+          System.out.println("type parameter bound");
+        }
+      }
+      return super.visitTypeParameter(tree, p);
+    }
+
+    @Override
+    public Void visitWildcard(WildcardTree tree, Void p) {
+      if (locations) {
+        System.out.println("wildcard");
+      }
+      return super.visitWildcard(tree, p);
+    }
+  }
+
+  @Override
+  public AnnotationProvider getAnnotationProvider() {
+    throw new UnsupportedOperationException(
+        "getAnnotationProvider is not implemented for this class.");
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java
new file mode 100644
index 0000000..a90ee6f
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java
@@ -0,0 +1,199 @@
+package org.checkerframework.common.util.count;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.AssertTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.tools.javac.util.Log;
+import java.util.List;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.source.SourceVisitor;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * An annotation processor for counting the size of Java code:
+ *
+ * <ul>
+ *   <li>The number of type parameter declarations and uses.
+ *   <li>The number of array accesses and dimensions in array creations.
+ *   <li>The number of type casts.
+ * </ul>
+ *
+ * <p>To invoke it, use
+ *
+ * <pre>
+ * javac -proc:only -processor org.checkerframework.common.util.count.JavaCodeStatistics <em>MyFile.java ...</em>
+ * </pre>
+ *
+ * @see AnnotationStatistics
+ */
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
+public class JavaCodeStatistics extends SourceChecker {
+
+  /** The number of type parameter declarations and uses. */
+  int generics = 0;
+  /** The number of array accesses and dimensions in array creations. */
+  int arrayAccesses = 0;
+  /** The number of type casts. */
+  int typecasts = 0;
+
+  String[] warningKeys = {
+    "index", "lowerbound", "samelen", "searchindex", "substringindex", "upperbound"
+  };
+  /**
+   * The number of warning suppressions with at least one key that matches one of the Index Checker
+   * subcheckers.
+   */
+  int numberOfIndexWarningSuppressions = 0;
+
+  /** The SuppressWarnings.value field/element. */
+  final ExecutableElement suppressWarningsValueElement =
+      TreeUtils.getMethod(SuppressWarnings.class, "value", 0, processingEnv);
+
+  /** Creates a JavaCodeStatistics. */
+  public JavaCodeStatistics() {
+    // This checker never issues any warnings, so don't warn about
+    // @SuppressWarnings("allcheckers:...").
+    this.useAllcheckersPrefix = false;
+  }
+
+  @Override
+  public void typeProcessingOver() {
+    Log log = getCompilerLog();
+    if (log.nerrors != 0) {
+      System.out.printf("Not outputting statistics, because compilation issued an error.%n");
+    } else {
+      System.out.printf("Found %d generic type uses.%n", generics);
+      System.out.printf("Found %d array accesses and creations.%n", arrayAccesses);
+      System.out.printf("Found %d typecasts.%n", typecasts);
+      System.out.printf(
+          "Found %d warning suppression annotations for the Index Checker.%n",
+          numberOfIndexWarningSuppressions);
+    }
+    super.typeProcessingOver();
+  }
+
+  @Override
+  protected SourceVisitor<?, ?> createSourceVisitor() {
+    return new Visitor(this);
+  }
+
+  class Visitor extends SourceVisitor<Void, Void> {
+
+    public Visitor(JavaCodeStatistics l) {
+      super(l);
+    }
+
+    @Override
+    public Void visitAnnotation(AnnotationTree node, Void aVoid) {
+      AnnotationMirror annotationMirror = TreeUtils.annotationFromAnnotationTree(node);
+      if (AnnotationUtils.annotationName(annotationMirror)
+          .equals(SuppressWarnings.class.getCanonicalName())) {
+        List<String> keys =
+            AnnotationUtils.getElementValueArray(
+                annotationMirror, suppressWarningsValueElement, String.class);
+        for (String foundKey : keys) {
+          for (String indexKey : warningKeys) {
+            if (foundKey.startsWith(indexKey)) {
+              numberOfIndexWarningSuppressions++;
+              return super.visitAnnotation(node, aVoid);
+            }
+          }
+        }
+      }
+      return super.visitAnnotation(node, aVoid);
+    }
+
+    @Override
+    public Void visitAssert(AssertTree tree, Void aVoid) {
+      ExpressionTree detail = tree.getDetail();
+      if (detail != null) {
+        String msg = detail.toString();
+        for (String indexKey : warningKeys) {
+          String key = "@AssumeAssertion(" + indexKey;
+          if (msg.contains(key)) {
+            numberOfIndexWarningSuppressions++;
+            return super.visitAssert(tree, aVoid);
+          }
+        }
+      }
+      return super.visitAssert(tree, aVoid);
+    }
+
+    @Override
+    public Void visitClass(ClassTree tree, Void p) {
+      if (shouldSkipDefs(tree)) {
+        // Not "return super.visitClass(classTree, p);" because that would recursively call visitors
+        // on subtrees; we want to skip the class entirely.
+        return null;
+      }
+      generics += tree.getTypeParameters().size();
+      return super.visitClass(tree, p);
+    }
+
+    @Override
+    public Void visitNewArray(NewArrayTree node, Void aVoid) {
+      arrayAccesses += node.getDimensions().size();
+
+      return super.visitNewArray(node, aVoid);
+    }
+
+    @Override
+    public Void visitNewClass(NewClassTree node, Void aVoid) {
+      if (TreeUtils.isDiamondTree(node)) {
+        generics++;
+      }
+      generics += node.getTypeArguments().size();
+      return super.visitNewClass(node, aVoid);
+    }
+
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) {
+      generics += node.getTypeArguments().size();
+      return super.visitMethodInvocation(node, aVoid);
+    }
+
+    @Override
+    public Void visitMethod(MethodTree node, Void aVoid) {
+      generics += node.getTypeParameters().size();
+      return super.visitMethod(node, aVoid);
+    }
+
+    @Override
+    public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) {
+      generics += tree.getTypeArguments().size();
+      return super.visitParameterizedType(tree, p);
+    }
+
+    @Override
+    public Void visitArrayAccess(ArrayAccessTree node, Void aVoid) {
+      arrayAccesses++;
+      return super.visitArrayAccess(node, aVoid);
+    }
+
+    @Override
+    public Void visitTypeCast(TypeCastTree node, Void aVoid) {
+      typecasts++;
+      return super.visitTypeCast(node, aVoid);
+    }
+  }
+
+  @Override
+  public AnnotationProvider getAnnotationProvider() {
+    throw new UnsupportedOperationException(
+        "getAnnotationProvider is not implemented for this class.");
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/EmptyProcessor.java b/framework/src/main/java/org/checkerframework/common/util/debug/EmptyProcessor.java
new file mode 100644
index 0000000..b3550b9
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/util/debug/EmptyProcessor.java
@@ -0,0 +1,28 @@
+package org.checkerframework.common.util.debug;
+
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+
+/**
+ * Empty simple processor.
+ *
+ * <p>It is useful in debugging compiler behavior with an annotation processor present.
+ */
+@SupportedAnnotationTypes("*")
+public class EmptyProcessor extends AbstractProcessor {
+
+  @Override
+  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    System.out.println("Empty Processor run!");
+    return false;
+  }
+
+  @Override
+  public SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latest();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java b/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java
new file mode 100644
index 0000000..d928d82
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java
@@ -0,0 +1,354 @@
+package org.checkerframework.common.util.debug;
+
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import java.io.PrintStream;
+import java.lang.reflect.Constructor;
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+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.ExecutableElement;
+import javax.lang.model.element.Name;
+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.util.AbstractElementVisitor7;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.source.SourceVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.javacutil.AbstractTypeProcessor;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.UserError;
+import org.plumelib.reflection.Signatures;
+
+/**
+ * Outputs the method signatures of a class with fully annotated types.
+ *
+ * <p>The class determines the effective annotations for a checker in source or the classfile.
+ * Finding the effective annotations is useful for the following purposes:
+ *
+ * <ol>
+ *   <li value="1">Debugging annotations in classfile
+ *   <li value="2">Debugging the default annotations that are implicitly added by the checker
+ * </ol>
+ *
+ * <p>The class can be used in two possible ways, depending on the type file:
+ *
+ * <ol>
+ *   <li id="a">From source: the class is to be used as an annotation processor when reading
+ *       annotations from source. It can be invoked via the command:
+ *       <p>{@code javac -processor SignaturePrinter <java files> ...}
+ *   <li id="b">From classfile: the class is to be used as an independent app when reading
+ *       annotations from classfile. It can be invoked via the command:
+ *       <p>{@code java SignaturePrinter <class name>}
+ * </ol>
+ *
+ * By default, only the annotations explicitly written by the user are emitted. To view the default
+ * and effective annotations in a class that are associated with a checker, the fully qualified name
+ * of the checker needs to be passed as {@code -Achecker=} argument, e.g.
+ *
+ * <pre>{@code
+ * javac -processor SignaturePrinter
+ *       -Achecker=org.checkerframework.checker.nullness.NullnessChecker JavaFile.java
+ * }</pre>
+ */
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
+@SupportedAnnotationTypes("*")
+@SupportedOptions("checker")
+public class SignaturePrinter extends AbstractTypeProcessor {
+
+  private SourceChecker checker;
+
+  ///////// Initialization /////////////
+  /**
+   * Initialization.
+   *
+   * @param env the ProcessingEnvironment
+   * @param checkerName the name of the checker
+   */
+  private void init(ProcessingEnvironment env, @Nullable @BinaryName String checkerName) {
+    if (checkerName != null) {
+      try {
+        Class<?> checkerClass = Class.forName(checkerName);
+        Constructor<?> cons = checkerClass.getConstructor();
+        checker = (SourceChecker) cons.newInstance();
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    } else {
+      checker =
+          new SourceChecker() {
+
+            @Override
+            protected SourceVisitor<?, ?> createSourceVisitor() {
+              return null;
+            }
+
+            @Override
+            public AnnotationProvider getAnnotationProvider() {
+              throw new UnsupportedOperationException(
+                  "getAnnotationProvider is not implemented for this class.");
+            }
+          };
+    }
+    checker.init(env);
+  }
+
+  @Override
+  public void typeProcessingStart() {
+    super.typeProcessingStart();
+    String checkerName = processingEnv.getOptions().get("checker");
+    if (!Signatures.isBinaryName(checkerName)) {
+      throw new UserError("Malformed checker name \"%s\"", checkerName);
+    }
+    init(processingEnv, checkerName);
+  }
+
+  @Override
+  public void typeProcess(TypeElement element, TreePath p) {
+    // TODO: fix this mess
+    // checker.currentPath = p;
+    // CompilationUnitTree root = p != null ? p.getCompilationUnit() : null;
+    // ElementPrinter printer = new ElementPrinter(checker.createTypeFactory(), System.out);
+    // printer.visit(element);
+  }
+
+  ////////// Printer //////////
+  static class ElementPrinter extends AbstractElementVisitor7<Void, Void> {
+    private static final String INDENTION = "    ";
+
+    private final PrintStream out;
+    private String indent = "";
+    private final AnnotatedTypeFactory factory;
+
+    public ElementPrinter(AnnotatedTypeFactory factory, PrintStream out) {
+      this.factory = factory;
+      this.out = out;
+    }
+
+    public void printTypeParams(List<? extends AnnotatedTypeVariable> params) {
+      if (params.isEmpty()) {
+        return;
+      }
+
+      out.print("<");
+      boolean isntFirst = false;
+      for (AnnotatedTypeMirror param : params) {
+        if (isntFirst) {
+          out.print(", ");
+        }
+        isntFirst = true;
+        out.print(param);
+      }
+      out.print("> ");
+    }
+
+    public void printParameters(AnnotatedExecutableType type) {
+      ExecutableElement elem = type.getElement();
+
+      out.print("(");
+      for (int i = 0; i < type.getParameterTypes().size(); ++i) {
+        if (i != 0) {
+          out.print(", ");
+        }
+        printVariable(type.getParameterTypes().get(i), elem.getParameters().get(i).getSimpleName());
+      }
+      out.print(")");
+    }
+
+    public void printThrows(AnnotatedExecutableType type) {
+      if (type.getThrownTypes().isEmpty()) {
+        return;
+      }
+
+      out.print(" throws ");
+
+      boolean isntFirst = false;
+      for (AnnotatedTypeMirror thrown : type.getThrownTypes()) {
+        if (isntFirst) {
+          out.print(", ");
+        }
+        isntFirst = true;
+        out.print(thrown);
+      }
+    }
+
+    public void printVariable(AnnotatedTypeMirror type, Name name, boolean isVarArg) {
+      out.print(type);
+      if (isVarArg) {
+        out.println("...");
+      }
+      out.print(' ');
+      out.print(name);
+    }
+
+    public void printVariable(AnnotatedTypeMirror type, Name name) {
+      printVariable(type, name, false);
+    }
+
+    public void printType(AnnotatedTypeMirror type) {
+      out.print(type);
+      out.print(' ');
+    }
+
+    public void printName(CharSequence name) {
+      out.print(name);
+    }
+
+    @Override
+    public Void visitExecutable(ExecutableElement e, Void p) {
+      out.print(indent);
+
+      AnnotatedExecutableType type = factory.getAnnotatedType(e);
+      printTypeParams(type.getTypeVariables());
+      if (e.getKind() != ElementKind.CONSTRUCTOR) {
+        printType(type.getReturnType());
+      }
+      printName(e.getSimpleName());
+      printParameters(type);
+      printThrows(type);
+      out.println(';');
+
+      return null;
+    }
+
+    @Override
+    public Void visitPackage(PackageElement e, Void p) {
+      throw new IllegalArgumentException("Cannot process packages");
+    }
+
+    private String typeIdentifier(TypeElement e) {
+      switch (e.getKind()) {
+        case INTERFACE:
+          return "interface";
+        case CLASS:
+          return "class";
+        case ANNOTATION_TYPE:
+          return "@interface";
+        case ENUM:
+          return "enum";
+        default:
+          throw new IllegalArgumentException("Not a type element: " + e.getKind());
+      }
+    }
+
+    @Override
+    public Void visitType(TypeElement e, Void p) {
+      String prevIndent = indent;
+
+      out.print(indent);
+      out.print(typeIdentifier(e));
+      out.print(' ');
+      out.print(e.getSimpleName());
+      out.print(' ');
+      AnnotatedDeclaredType dt = factory.getAnnotatedType(e);
+      printSupers(dt);
+      out.println("{");
+
+      indent += INDENTION;
+
+      for (Element enclosed : e.getEnclosedElements()) {
+        this.visit(enclosed);
+      }
+
+      indent = prevIndent;
+      out.print(indent);
+      out.println("}");
+
+      return null;
+    }
+
+    /**
+     * Print the supertypes.
+     *
+     * @param dt the type whos supertypes to print
+     */
+    private void printSupers(AnnotatedDeclaredType dt) {
+      if (dt.directSupertypes().isEmpty()) {
+        return;
+      }
+
+      out.print("extends ");
+
+      boolean isntFirst = false;
+      for (AnnotatedDeclaredType st : dt.directSupertypes()) {
+        if (isntFirst) {
+          out.print(", ");
+        }
+        isntFirst = true;
+        out.print(st);
+      }
+      out.print(' ');
+    }
+
+    @Override
+    public Void visitTypeParameter(TypeParameterElement e, Void p) {
+      throw new IllegalStateException("Shouldn't visit any type parameters");
+    }
+
+    @Override
+    public Void visitVariable(VariableElement e, Void p) {
+      if (!e.getKind().isField()) {
+        throw new IllegalStateException("can only process fields, received " + e.getKind());
+      }
+
+      out.print(indent);
+      AnnotatedTypeMirror type = factory.getAnnotatedType(e);
+      this.printVariable(type, e.getSimpleName());
+      out.println(';');
+
+      return null;
+    }
+  }
+
+  public static void printUsage() {
+    System.out.println("   Usage: java SignaturePrinter [-Achecker=<checkerName>] classname");
+  }
+
+  private static final String CHECKER_ARG = "-Achecker=";
+
+  public static void main(String[] args) {
+    if (!(args.length == 1 && !args[0].startsWith(CHECKER_ARG))
+        && !(args.length == 2 && args[0].startsWith(CHECKER_ARG))) {
+      printUsage();
+      return;
+    }
+
+    // process arguments
+    String checkerName = null;
+    if (args[0].startsWith(CHECKER_ARG)) {
+      checkerName = args[0].substring(CHECKER_ARG.length());
+      if (!Signatures.isBinaryName(checkerName)) {
+        throw new UserError("Bad checker name \"%s\"", checkerName);
+      }
+    }
+
+    // Setup compiler environment
+    Context context = new Context();
+    JavacProcessingEnvironment env = JavacProcessingEnvironment.instance(context);
+    SignaturePrinter printer = new SignaturePrinter();
+    printer.init(env, checkerName);
+
+    String className = args[args.length - 1];
+    TypeElement elem = env.getElementUtils().getTypeElement(className);
+    if (elem == null) {
+      System.err.println("Couldn't find class: " + className);
+      return;
+    }
+
+    printer.typeProcess(elem, null);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/TreeDebug.java b/framework/src/main/java/org/checkerframework/common/util/debug/TreeDebug.java
new file mode 100644
index 0000000..77d8493
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/util/debug/TreeDebug.java
@@ -0,0 +1,129 @@
+package org.checkerframework.common.util.debug;
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.tree.JCTree.JCNewArray;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementFilter;
+
+/**
+ * A utility class for displaying the structure of the AST of a program.
+ *
+ * <p>The class is actually an annotation processor; in order to use it, invoke the compiler on the
+ * source file(s) for which you wish to view the structure of the program. You may also wish to use
+ * the {@code -proc:only} javac option to stop compilation after annotation processing. (But, in
+ * general {@code -proc:only} causes type annotation processors not to be run.)
+ *
+ * <p>The utility will display the {@link Kind} of each node it encounters while scanning the AST,
+ * indented according to its depth in the tree. Additionally, the names of identifiers and member
+ * selection trees are displayed (since these names are not tree nodes and therefore not directly
+ * visited during AST traversal).
+ *
+ * @see org.checkerframework.common.util.debug.TreePrinter
+ */
+@SupportedAnnotationTypes("*")
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
+public class TreeDebug extends AbstractProcessor {
+
+  protected Visitor createSourceVisitor(CompilationUnitTree root) {
+    return new Visitor();
+  }
+
+  private static final String LINE_SEPARATOR = System.lineSeparator();
+
+  public static class Visitor extends TreePathScanner<Void, Void> {
+
+    private final StringBuilder buf;
+
+    public Visitor() {
+      buf = new StringBuilder();
+    }
+
+    @Override
+    public Void scan(Tree node, Void p) {
+
+      // Indent according to subtrees.
+      if (getCurrentPath() != null) {
+        for (TreePath tp = getCurrentPath(); tp != null; tp = tp.getParentPath()) {
+          buf.append("  ");
+        }
+      }
+
+      // Add node kind to the buffer.
+      if (node == null) {
+        buf.append("null");
+      } else {
+        buf.append(node.getKind());
+      }
+      buf.append(LINE_SEPARATOR);
+
+      // Visit subtrees.
+      super.scan(node, p);
+
+      // Display and clear the buffer.
+      System.out.print(buf.toString());
+      buf.setLength(0);
+
+      return null;
+    }
+
+    /**
+     * Splices additional information for a node into the buffer.
+     *
+     * @param text additional information for the node
+     */
+    private final void insert(Object text) {
+      buf.insert(buf.length() - 1, " ");
+      buf.insert(buf.length() - 1, text);
+    }
+
+    @Override
+    public Void visitIdentifier(IdentifierTree node, Void p) {
+      insert(node);
+      return super.visitIdentifier(node, p);
+    }
+
+    @Override
+    public Void visitMemberSelect(MemberSelectTree node, Void p) {
+      insert(node.getExpression() + "." + node.getIdentifier());
+      return super.visitMemberSelect(node, p);
+    }
+
+    @Override
+    public Void visitNewArray(NewArrayTree node, Void p) {
+      insert(((JCNewArray) node).annotations);
+      insert("|");
+      insert(((JCNewArray) node).dimAnnotations);
+      return super.visitNewArray(node, p);
+    }
+
+    @Override
+    public Void visitLiteral(LiteralTree node, Void p) {
+      insert(node.getValue());
+      return super.visitLiteral(node, p);
+    }
+  }
+
+  @Override
+  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    for (TypeElement element : ElementFilter.typesIn(roundEnv.getRootElements())) {
+      TreePath path = Trees.instance(processingEnv).getPath(element);
+      new Visitor().scan(path, null);
+    }
+    return false;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java b/framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java
new file mode 100644
index 0000000..72e73f9
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java
@@ -0,0 +1,62 @@
+package org.checkerframework.common.util.debug;
+
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.tree.Pretty;
+import java.io.IOException;
+import java.io.StringWriter;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import org.checkerframework.javacutil.AbstractTypeProcessor;
+
+/**
+ * A utility class for pretty-printing the AST of a program.
+ *
+ * <p>The class is actually an annotation processor; in order to use it, invoke the compiler on the
+ * source file(s) for which you wish to view the AST of the program. You may also wish to use the
+ * {@code -proc:only} javac option to stop compilation after annotation processing. (But, in general
+ * {@code -proc:only} causes type annotation processors not to be run.)
+ *
+ * <p>A simple main method is also provided. You can invoke this tool as:
+ *
+ * <p>java org.checkerframework.common.util.debug.TreePrinter *.java
+ *
+ * <p>The visitor simply uses the javac Pretty visitor to output a nicely formatted version of the
+ * AST.
+ *
+ * <p>TODO: I couldn't find a way to display the result of Pretty, therefore I wrote this simple
+ * class. If there already was a way, please let me know.
+ *
+ * <p>TODO: what I really want is something like SignaturePrinter, but for the whole source of the
+ * program, that is, for each type in the program use the factory to determine the defaulted
+ * annotations on the type.
+ *
+ * @see org.checkerframework.common.util.debug.TreeDebug
+ */
+@SupportedAnnotationTypes("*")
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
+public class TreePrinter extends AbstractTypeProcessor {
+  @Override
+  public void typeProcess(TypeElement element, TreePath tree) {
+    final StringWriter out = new StringWriter();
+    final Pretty pretty = new Pretty(out, true);
+
+    try {
+      pretty.printUnit((JCCompilationUnit) tree.getCompilationUnit(), null);
+    } catch (IOException e) {
+      throw new Error(e);
+    }
+    System.out.println(out.toString());
+  }
+
+  public static void main(String[] args) throws Exception {
+    String[] newArgs = new String[args.length + 3];
+    newArgs[0] = "-processor";
+    newArgs[1] = "org.checkerframework.common.util.debug.TreePrinter";
+    newArgs[2] = "-proc:only";
+    System.arraycopy(args, 0, newArgs, 3, args.length);
+    com.sun.tools.javac.Main.compile(newArgs);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java b/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java
new file mode 100644
index 0000000..b6895f6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java
@@ -0,0 +1,266 @@
+package org.checkerframework.common.util.debug;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import java.util.Collection;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+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.VariableElement;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A testing class that can be used to test {@link TypeElement}. In particular it tests that the
+ * types read from classfiles are the same to the ones from java files.
+ *
+ * <p>For testing, you need to do the following:
+ *
+ * <ol>
+ *   <li>Run the Checker on the source file like any checker:
+ *       <pre>{@code
+ * java -processor org.checkerframework.common.util.debug.TypeOutputtingChecker [source-file]
+ *
+ * }</pre>
+ *   <li>Run the Checker on the bytecode, by simply running the main and passing the qualified name,
+ *       e.g.
+ *       <pre>{@code
+ * java org.checkerframework.common.util.debug.TypeOutputtingChecker [qualified-name]
+ *
+ * }</pre>
+ *   <li>Apply a simple diff on the two outputs
+ * </ol>
+ */
+public class TypeOutputtingChecker extends BaseTypeChecker {
+
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new Visitor(this);
+  }
+
+  /** Prints the types of the class and all of its enclosing fields, methods, and inner classes. */
+  public static class Visitor extends BaseTypeVisitor<GenericAnnotatedTypeFactory<?, ?, ?, ?>> {
+    String currentClass;
+
+    public Visitor(BaseTypeChecker checker) {
+      super(checker);
+    }
+
+    // Print types of classes, methods, and fields
+    @Override
+    public void processClassTree(ClassTree node) {
+      TypeElement element = TreeUtils.elementFromDeclaration(node);
+      currentClass = element.getSimpleName().toString();
+
+      AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(node);
+      System.out.println(node.getSimpleName() + "\t" + type + "\t" + type.directSupertypes());
+
+      super.processClassTree(node);
+    }
+
+    @Override
+    public Void visitMethod(MethodTree node, Void p) {
+      ExecutableElement elem = TreeUtils.elementFromDeclaration(node);
+
+      AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node);
+      System.out.println(currentClass + "." + elem + "\t\t" + type);
+      // Don't dig deeper
+      return null;
+    }
+
+    @Override
+    public Void visitVariable(VariableTree node, Void p) {
+      VariableElement elem = TreeUtils.elementFromDeclaration(node);
+      if (elem.getKind().isField()) {
+        AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node);
+        System.out.println(currentClass + "." + elem + "\t\t" + type);
+      }
+
+      // Don't dig deeper
+      return null;
+    }
+  }
+
+  /**
+   * Main entry point.
+   *
+   * @param args command-line arguments
+   */
+  @SuppressWarnings("signature:argument") // user-supplied input, uncheckable
+  public static void main(String[] args) {
+    new TypeOutputtingChecker().run(args);
+  }
+
+  /**
+   * Run the test.
+   *
+   * @param args command-line arguments
+   */
+  public void run(@CanonicalName String[] args) {
+    ProcessingEnvironment env = JavacProcessingEnvironment.instance(new Context());
+    Elements elements = env.getElementUtils();
+
+    // TODO: Instead of using a GeneralAnnotatedTypeFactory, just use standard javac classes
+    // to print explicit annotations.
+    AnnotatedTypeFactory atypeFactory = new GeneralAnnotatedTypeFactory(this);
+
+    for (String className : args) {
+      TypeElement typeElt = elements.getTypeElement(className);
+      printClassType(typeElt, atypeFactory);
+    }
+  }
+
+  /** Prints the types of the class and all of its enclosing fields, methods, and inner classes. */
+  protected static void printClassType(TypeElement typeElt, AnnotatedTypeFactory atypeFactory) {
+    assert typeElt != null;
+
+    String simpleName = typeElt.getSimpleName().toString();
+    // Output class info
+    AnnotatedDeclaredType type = atypeFactory.fromElement(typeElt);
+    System.out.println(simpleName + "\t" + type + "\t" + type.directSupertypes());
+
+    // output fields and methods
+    for (Element enclosedElt : typeElt.getEnclosedElements()) {
+      if (enclosedElt instanceof TypeElement) {
+        printClassType((TypeElement) enclosedElt, atypeFactory);
+      }
+      if (!enclosedElt.getKind().isField() && !(enclosedElt instanceof ExecutableElement)) {
+        continue;
+      }
+      AnnotatedTypeMirror memberType = atypeFactory.fromElement(enclosedElt);
+      System.out.println(simpleName + "." + enclosedElt + "\t\t" + memberType);
+    }
+  }
+
+  /**
+   * Stores any explicit annotation in AnnotatedTypeMirrors. It doesn't have a qualifier hierarchy,
+   * so it violates most of the specifications for AnnotatedTypeMirrors and AnnotatedTypeFactorys,
+   * which may cause crashes and other unexpected behaviors.
+   */
+  public static class GeneralAnnotatedTypeFactory extends AnnotatedTypeFactory {
+
+    public GeneralAnnotatedTypeFactory(BaseTypeChecker checker) {
+      super(checker);
+      postInit();
+    }
+
+    @Override
+    public void postProcessClassTree(ClassTree tree) {
+      // Do not store the qualifiers determined by this factory.  This factory adds declaration
+      // annotations as type annotations, because TypeFromElement needs to read declaration
+      // annotations and this factory blindly supports all annotations.
+      // When storing those annotation to bytecode, the compiler chokes.  See testcase
+      // tests/nullness/GeneralATFStore.java
+    }
+
+    /** Return true to support any qualifier. No handling of aliases. */
+    @Override
+    public boolean isSupportedQualifier(AnnotationMirror a) {
+      return true;
+    }
+
+    @Override
+    protected QualifierHierarchy createQualifierHierarchy() {
+      return new GeneralQualifierHierarchy();
+    }
+
+    /**
+     * A very limited QualifierHierarchy that is used for access to qualifiers from different type
+     * systems.
+     */
+    static class GeneralQualifierHierarchy implements QualifierHierarchy {
+
+      // Always return true
+      @Override
+      public boolean isValid() {
+        return true;
+      }
+
+      // Return the qualifier itself instead of the top.
+      @Override
+      public AnnotationMirror getTopAnnotation(AnnotationMirror start) {
+        return start;
+      }
+
+      // Return the qualifier itself instead of the bottom.
+      @Override
+      public AnnotationMirror getBottomAnnotation(AnnotationMirror start) {
+        return start;
+      }
+
+      // Never find a corresponding qualifier.
+      @Override
+      public AnnotationMirror findAnnotationInSameHierarchy(
+          Collection<? extends AnnotationMirror> annotations, AnnotationMirror annotationMirror) {
+        return null;
+      }
+
+      // Not needed - raises error.
+      @Override
+      public Set<AnnotationMirror> getTopAnnotations() {
+        throw new BugInCF("GeneralQualifierHierarchy:getTopAnnotations() shouldn't be called");
+      }
+
+      // Not needed - should raise error. Unfortunately, in inference we ask for bottom annotations.
+      // Return a dummy value that does no harm.
+      @Override
+      public Set<AnnotationMirror> getBottomAnnotations() {
+        // throw new BugInCF("GeneralQualifierHierarchy.getBottomAnnotations()
+        // shouldn't be called");
+        return AnnotationUtils.createAnnotationSet();
+      }
+
+      // Not needed - raises error.
+      @Override
+      public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+        throw new BugInCF("GeneralQualifierHierarchy.isSubtype() shouldn't be called.");
+      }
+
+      // Not needed - raises error.
+      @Override
+      public boolean isSubtype(
+          Collection<? extends AnnotationMirror> rhs, Collection<? extends AnnotationMirror> lhs) {
+        throw new BugInCF("GeneralQualifierHierarchy.isSubtype() shouldn't be called.");
+      }
+
+      // Not needed - raises error.
+      @Override
+      public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
+        throw new BugInCF("GeneralQualifierHierarchy.leastUpperBound() shouldn't be called.");
+      }
+
+      // Not needed - raises error.
+      @Override
+      public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
+        throw new BugInCF("GeneralQualifierHierarchy.greatestLowerBound() shouldn't be called.");
+      }
+
+      @Override
+      public AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) {
+        throw new BugInCF(
+            "GeneralQualifierHierarchy.getPolymorphicAnnotation() shouldn't be called.");
+      }
+
+      @Override
+      public boolean isPolymorphicQualifier(AnnotationMirror qualifier) {
+        return false;
+      }
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/util/report/README b/framework/src/main/java/org/checkerframework/common/util/report/README
new file mode 100644
index 0000000..fa2d009
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/util/report/README
@@ -0,0 +1,21 @@
+Report Checker - Semantic Search
+================================
+
+The Report Checker provides mechanisms to implement semantic
+searches over a program, for example, to find all methods that override
+a specific method, all classes that inherit from a specific class, or
+all uses of do-while-loops (and not also while loops!).
+
+For most uses, see the qualifiers in
+org.checkerframework.common.util.report.qual.*
+The search is specified by writing a stub specification file using
+these annotations.
+
+Additionally, the reportTreeKinds option can be used to search for
+specific tree kinds.
+
+I have a few more ideas for additional searches.
+Let me know if there's something missing you think is useful!
+
+Some similar features are available from IDEs (e.g. show references),
+but this tool provides much more flexibility and a command-line tool.
diff --git a/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java b/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java
new file mode 100644
index 0000000..4dac576
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java
@@ -0,0 +1,14 @@
+package org.checkerframework.common.util.report;
+
+import javax.annotation.processing.SupportedOptions;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+/**
+ * The Report Checker for semantic searches.
+ *
+ * <p>See the qualifiers for documentation.
+ *
+ * <p>Options: reportTreeKinds: comma-separated list of Tree.Kinds that should be reported.
+ */
+@SupportedOptions({"reportTreeKinds", "reportModifiers"})
+public class ReportChecker extends BaseTypeChecker {}
diff --git a/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java b/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java
new file mode 100644
index 0000000..5f5ccc7
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java
@@ -0,0 +1,314 @@
+package org.checkerframework.common.util.report;
+
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModifiersTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeCastTree;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeValidator;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.util.report.qual.ReportCall;
+import org.checkerframework.common.util.report.qual.ReportCreation;
+import org.checkerframework.common.util.report.qual.ReportInherit;
+import org.checkerframework.common.util.report.qual.ReportOverride;
+import org.checkerframework.common.util.report.qual.ReportReadWrite;
+import org.checkerframework.common.util.report.qual.ReportUse;
+import org.checkerframework.common.util.report.qual.ReportWrite;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+public class ReportVisitor extends BaseTypeVisitor<BaseAnnotatedTypeFactory> {
+
+  /** The tree kinds that should be reported; may be null. */
+  private final EnumSet<Tree.Kind> treeKinds;
+
+  /** The modifiers that should be reported; may be null. */
+  private final EnumSet<Modifier> modifiers;
+
+  public ReportVisitor(BaseTypeChecker checker) {
+    super(checker);
+
+    if (checker.hasOption("reportTreeKinds")) {
+      String trees = checker.getOption("reportTreeKinds");
+      treeKinds = EnumSet.noneOf(Tree.Kind.class);
+      for (String treeKind : trees.split(",")) {
+        treeKinds.add(Tree.Kind.valueOf(treeKind.toUpperCase()));
+      }
+    } else {
+      treeKinds = null;
+    }
+
+    if (checker.hasOption("reportModifiers")) {
+      String mods = checker.getOption("reportModifiers");
+      modifiers = EnumSet.noneOf(Modifier.class);
+      for (String modifier : mods.split(",")) {
+        modifiers.add(Modifier.valueOf(modifier.toUpperCase()));
+      }
+    } else {
+      modifiers = null;
+    }
+  }
+
+  @SuppressWarnings("compilermessages") // These warnings are not translated.
+  @Override
+  public Void scan(Tree tree, Void p) {
+    if ((tree != null) && (treeKinds != null) && treeKinds.contains(tree.getKind())) {
+      checker.reportError(tree, "Tree.Kind." + tree.getKind());
+    }
+    return super.scan(tree, p);
+  }
+
+  /**
+   * Check for uses of the {@link ReportUse} annotation. This method has to be called for every
+   * explicit or implicit use of a type, most cases are simply covered by the type validator.
+   *
+   * @param node the tree for error reporting only
+   * @param member the element from which to start looking
+   */
+  private void checkReportUse(Tree node, Element member) {
+    Element loop = member;
+    while (loop != null) {
+      boolean report = this.atypeFactory.getDeclAnnotation(loop, ReportUse.class) != null;
+      if (report) {
+        checker.reportError(
+            node,
+            "usage",
+            node,
+            ElementUtils.getQualifiedName(loop),
+            loop.getKind(),
+            ElementUtils.getQualifiedName(member),
+            member.getKind());
+        break;
+      } else {
+        if (loop.getKind() == ElementKind.PACKAGE) {
+          loop = ElementUtils.parentPackage((PackageElement) loop, elements);
+          continue;
+        }
+      }
+      // Package will always be the last iteration.
+      loop = loop.getEnclosingElement();
+    }
+  }
+
+  /* Would we want this? Seems redundant, as all uses of the imported
+   * package should already be reported.
+   * Also, how do we get an element for the import?
+  public Void visitImport(ImportTree node, Void p) {
+      checkReportUse(node, elem);
+  }
+  */
+
+  @Override
+  public void processClassTree(ClassTree node) {
+    TypeElement member = TreeUtils.elementFromDeclaration(node);
+    boolean report = false;
+    // No need to check on the declaring class itself
+    // this.atypeFactory.getDeclAnnotation(member, ReportInherit.class) != null;
+
+    // Check whether any superclass/interface had the ReportInherit annotation.
+    List<TypeElement> suptypes = ElementUtils.getSuperTypes(member, elements);
+    for (TypeElement sup : suptypes) {
+      report = this.atypeFactory.getDeclAnnotation(sup, ReportInherit.class) != null;
+      if (report) {
+        checker.reportError(node, "inherit", node, ElementUtils.getQualifiedName(sup));
+      }
+    }
+    super.processClassTree(node);
+  }
+
+  @Override
+  public Void visitMethod(MethodTree node, Void p) {
+    ExecutableElement method = TreeUtils.elementFromDeclaration(node);
+    boolean report = false;
+
+    // Check all overridden methods.
+    Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
+        AnnotatedTypes.overriddenMethods(elements, atypeFactory, method);
+    for (Map.Entry<AnnotatedDeclaredType, ExecutableElement> pair : overriddenMethods.entrySet()) {
+      // AnnotatedDeclaredType overriddenType = pair.getKey();
+      ExecutableElement exe = pair.getValue();
+      report = this.atypeFactory.getDeclAnnotation(exe, ReportOverride.class) != null;
+      if (report) {
+        // Set method to report the right method, if found.
+        method = exe;
+        break;
+      }
+    }
+
+    if (report) {
+      checker.reportError(node, "override", node, ElementUtils.getQualifiedName(method));
+    }
+    return super.visitMethod(node, p);
+  }
+
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+    ExecutableElement method = TreeUtils.elementFromUse(node);
+    checkReportUse(node, method);
+    boolean report = this.atypeFactory.getDeclAnnotation(method, ReportCall.class) != null;
+
+    if (!report) {
+      // Find all methods that are overridden by the called method
+      Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
+          AnnotatedTypes.overriddenMethods(elements, atypeFactory, method);
+      for (Map.Entry<AnnotatedDeclaredType, ExecutableElement> pair :
+          overriddenMethods.entrySet()) {
+        // AnnotatedDeclaredType overriddenType = pair.getKey();
+        ExecutableElement exe = pair.getValue();
+        report = this.atypeFactory.getDeclAnnotation(exe, ReportCall.class) != null;
+        if (report) {
+          // Always report the element that has the annotation.
+          // Alternative would be to always report the initial element.
+          method = exe;
+          break;
+        }
+      }
+    }
+
+    if (report) {
+      checker.reportError(node, "methodcall", node, ElementUtils.getQualifiedName(method));
+    }
+    return super.visitMethodInvocation(node, p);
+  }
+
+  @Override
+  public Void visitMemberSelect(MemberSelectTree node, Void p) {
+    Element member = TreeUtils.elementFromUse(node);
+    checkReportUse(node, member);
+    boolean report = this.atypeFactory.getDeclAnnotation(member, ReportReadWrite.class) != null;
+
+    if (report) {
+      checker.reportError(node, "fieldreadwrite", node, ElementUtils.getQualifiedName(member));
+    }
+    return super.visitMemberSelect(node, p);
+  }
+
+  @Override
+  public Void visitIdentifier(IdentifierTree node, Void p) {
+    Element member = TreeUtils.elementFromUse(node);
+    boolean report = this.atypeFactory.getDeclAnnotation(member, ReportReadWrite.class) != null;
+
+    if (report) {
+      checker.reportError(node, "fieldreadwrite", node, ElementUtils.getQualifiedName(member));
+    }
+    return super.visitIdentifier(node, p);
+  }
+
+  @Override
+  public Void visitAssignment(AssignmentTree node, Void p) {
+    Element member = TreeUtils.elementFromUse(node.getVariable());
+    boolean report = this.atypeFactory.getDeclAnnotation(member, ReportWrite.class) != null;
+
+    if (report) {
+      checker.reportError(node, "fieldwrite", node, ElementUtils.getQualifiedName(member));
+    }
+    return super.visitAssignment(node, p);
+  }
+
+  @Override
+  public Void visitArrayAccess(ArrayAccessTree node, Void p) {
+    // TODO: should we introduce an annotation for this?
+    return super.visitArrayAccess(node, p);
+  }
+
+  @Override
+  public Void visitNewClass(NewClassTree node, Void p) {
+    Element member = TreeUtils.elementFromUse(node);
+    boolean report = this.atypeFactory.getDeclAnnotation(member, ReportCreation.class) != null;
+    if (!report) {
+      // If the constructor is not annotated, check whether the class is.
+      member = member.getEnclosingElement();
+      report = this.atypeFactory.getDeclAnnotation(member, ReportCreation.class) != null;
+    }
+    if (!report) {
+      // Check whether any superclass/interface had the ReportCreation annotation.
+      List<TypeElement> suptypes = ElementUtils.getSuperTypes((TypeElement) member, elements);
+      for (TypeElement sup : suptypes) {
+        report = this.atypeFactory.getDeclAnnotation(sup, ReportCreation.class) != null;
+        if (report) {
+          // Set member to report the right member if found
+          member = sup;
+          break;
+        }
+      }
+    }
+
+    if (report) {
+      checker.reportError(node, "creation", node, ElementUtils.getQualifiedName(member));
+    }
+    return super.visitNewClass(node, p);
+  }
+
+  @Override
+  public Void visitNewArray(NewArrayTree node, Void p) {
+    // TODO Should we report this if the array type is @ReportCreation?
+    return super.visitNewArray(node, p);
+  }
+
+  @Override
+  public Void visitTypeCast(TypeCastTree node, Void p) {
+    // TODO Is it worth adding a separate annotation for this?
+    return super.visitTypeCast(node, p);
+  }
+
+  @Override
+  public Void visitInstanceOf(InstanceOfTree node, Void p) {
+    // TODO Is it worth adding a separate annotation for this?
+    return super.visitInstanceOf(node, p);
+  }
+
+  @SuppressWarnings("compilermessages") // These warnings are not translated.
+  @Override
+  public Void visitModifiers(ModifiersTree node, Void p) {
+    if (node != null && modifiers != null) {
+      for (Modifier mod : node.getFlags()) {
+        if (modifiers.contains(mod)) {
+          checker.reportError(node, "Modifier." + mod);
+        }
+      }
+    }
+    return super.visitModifiers(node, p);
+  }
+
+  @Override
+  protected BaseTypeValidator createTypeValidator() {
+    return new ReportTypeValidator(checker, this, atypeFactory);
+  }
+
+  protected class ReportTypeValidator extends BaseTypeValidator {
+    public ReportTypeValidator(
+        BaseTypeChecker checker, BaseTypeVisitor<?> visitor, AnnotatedTypeFactory atypeFactory) {
+      super(checker, visitor, atypeFactory);
+    }
+
+    @Override
+    public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) {
+      Element member = type.getUnderlyingType().asElement();
+      checkReportUse(tree, member);
+
+      return super.visitDeclared(type, tree);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/util/report/messages.properties b/framework/src/main/java/org/checkerframework/common/util/report/messages.properties
new file mode 100644
index 0000000..cd5bbf9
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/util/report/messages.properties
@@ -0,0 +1,12 @@
+# First argument is the tree of usage
+# Second argument is qualified element string
+fieldreadwrite=Field read/write of %2$s
+fieldwrite=Field write of %2$s
+methodcall=Method call of %2$s
+creation=Instantiation of %2$s
+inherit=Inherit from %2$s
+override=Override of %2$s
+# First argument is the tree of usage
+# Second & third are the element to report and its kind
+# Fourth & fifth are the matching element and its kind
+usage=Usage of %2$s [%3$s] by %4$s [%5$s]
diff --git a/framework/src/main/java/org/checkerframework/common/value/JavaExpressionOptimizer.java b/framework/src/main/java/org/checkerframework/common/value/JavaExpressionOptimizer.java
new file mode 100644
index 0000000..9965284
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/JavaExpressionOptimizer.java
@@ -0,0 +1,78 @@
+package org.checkerframework.common.value;
+
+import java.util.List;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.JavaExpressionConverter;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.MethodCall;
+import org.checkerframework.dataflow.expression.ValueLiteral;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+
+/**
+ * Optimize the given JavaExpression. If the supplied factory is a {@code
+ * ValueAnnotatedTypeFactory}, this implementation replaces any expression that the factory has an
+ * exact value for, and does a small (not exhaustive) amount of constant-folding as well. If the
+ * factory is some other factory, less optimization occurs.
+ */
+public class JavaExpressionOptimizer extends JavaExpressionConverter {
+
+  /**
+   * Annotated type factory. If it is a {@code ValueAnnotatedTypeFactory}, then more optimizations
+   * are possible.
+   */
+  private final AnnotatedTypeFactory factory;
+
+  /**
+   * Creates a JavaExpressionOptimizer.
+   *
+   * @param factory an annotated type factory
+   */
+  public JavaExpressionOptimizer(AnnotatedTypeFactory factory) {
+    this.factory = factory;
+  }
+
+  @Override
+  protected JavaExpression visitFieldAccess(FieldAccess fieldAccessExpr, Void unused) {
+    // Replace references to compile-time constant fields by the constant itself.
+    if (fieldAccessExpr.isFinal()) {
+      Object constant = fieldAccessExpr.getField().getConstantValue();
+      if (constant != null && !(constant instanceof String)) {
+        return new ValueLiteral(fieldAccessExpr.getType(), constant);
+      }
+    }
+    return super.visitFieldAccess(fieldAccessExpr, unused);
+  }
+
+  @Override
+  protected JavaExpression visitLocalVariable(LocalVariable localVarExpr, Void unused) {
+    if (factory instanceof ValueAnnotatedTypeFactory) {
+      Element element = localVarExpr.getElement();
+      Long exactValue =
+          ValueCheckerUtils.getExactValue(element, (ValueAnnotatedTypeFactory) factory);
+      if (exactValue != null) {
+        return new ValueLiteral(localVarExpr.getType(), exactValue.intValue());
+      }
+    }
+    return super.visitLocalVariable(localVarExpr, unused);
+  }
+
+  @Override
+  protected JavaExpression visitMethodCall(MethodCall methodCallExpr, Void unused) {
+    JavaExpression optReceiver = convert(methodCallExpr.getReceiver());
+    List<JavaExpression> optArguments = convert(methodCallExpr.getArguments());
+    // Length of string literal: convert it to an integer literal.
+    if (methodCallExpr.getElement().getSimpleName().contentEquals("length")
+        && optReceiver instanceof ValueLiteral) {
+      Object value = ((ValueLiteral) optReceiver).getValue();
+      if (value instanceof String) {
+        return new ValueLiteral(
+            factory.types.getPrimitiveType(TypeKind.INT), ((String) value).length());
+      }
+    }
+    return new MethodCall(
+        methodCallExpr.getType(), methodCallExpr.getElement(), optReceiver, optArguments);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/RangeOrListOfValues.java b/framework/src/main/java/org/checkerframework/common/value/RangeOrListOfValues.java
new file mode 100644
index 0000000..b989c2a
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/RangeOrListOfValues.java
@@ -0,0 +1,141 @@
+package org.checkerframework.common.value;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.ArrayLenRange;
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.common.value.util.Range;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * An abstraction that can be either a range or a list of values that could come from an {@link
+ * ArrayLen} or {@link IntVal}. This abstraction reduces the number of cases that {@link
+ * ValueTreeAnnotator#handleInitializers(List, AnnotatedTypeMirror.AnnotatedArrayType)} and {@link
+ * ValueTreeAnnotator#handleDimensions(List, AnnotatedTypeMirror.AnnotatedArrayType)} must handle.
+ *
+ * <p>Tracks Ints in the list, and creates ArrayLen or ArrayLenRange annotations, because it's meant
+ * to be used to reason about ArrayLen and ArrayLenRange values.
+ */
+class RangeOrListOfValues {
+  private Range range;
+  private List<Integer> values;
+  private boolean isRange;
+
+  public RangeOrListOfValues(List<Integer> values) {
+    this.values = new ArrayList<>();
+    isRange = false;
+    addAll(values);
+  }
+
+  public RangeOrListOfValues(Range range) {
+    this.range = range;
+    isRange = true;
+  }
+
+  public void add(Range otherRange) {
+    if (isRange) {
+      range = range.union(otherRange);
+    } else {
+      convertToRange();
+      add(otherRange);
+    }
+  }
+
+  /**
+   * If this is not a range, adds all members of newValues to the list. Otherwise, extends the range
+   * as appropriate based on the max and min of newValues. If adding newValues to a non-range would
+   * cause the list to become too large, converts this into a range.
+   *
+   * <p>If reading from an {@link org.checkerframework.common.value.qual.IntRange} annotation,
+   * {@link #convertLongsToInts(List)} should be called before calling this method.
+   *
+   * @param newValues values to add
+   */
+  public void addAll(List<Integer> newValues) {
+    if (isRange) {
+      range = range.union(Range.create(newValues));
+    } else {
+      for (Integer i : newValues) {
+        if (!values.contains(i)) {
+          values.add(i);
+        }
+      }
+      if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) {
+        convertToRange();
+      }
+    }
+  }
+
+  /**
+   * Produces the most precise annotation that captures the information stored in this
+   * RangeOrListofValues. The result is either a {@link ArrayLen} or a {@link ArrayLenRange}.
+   *
+   * @param atypeFactory the type factory
+   * @return an annotation correspending to this RangeOrListofValues
+   */
+  public AnnotationMirror createAnnotation(ValueAnnotatedTypeFactory atypeFactory) {
+    if (isRange) {
+      return atypeFactory.createArrayLenRangeAnnotation(range);
+    } else {
+      return atypeFactory.createArrayLenAnnotation(values);
+    }
+  }
+
+  /**
+   * Converts a Long to an Integer by clipping it to the int range.
+   *
+   * @param l a Long integer
+   * @return the value clipped to the Integer range
+   */
+  private static Integer convertLongToInt(Long l) {
+    if (l > Integer.MAX_VALUE) {
+      return Integer.MAX_VALUE;
+    } else if (l < Integer.MIN_VALUE) {
+      return Integer.MIN_VALUE;
+    } else {
+      return l.intValue();
+    }
+  }
+
+  /**
+   * To be called before addAll. Converts Longs to Integers by clipping them to the int range; meant
+   * to be used with ArrayLenRange (which only handles Ints).
+   *
+   * @param newValues a list of Long integers
+   * @return a list of Integers
+   */
+  public static List<Integer> convertLongsToInts(List<Long> newValues) {
+    return CollectionsPlume.mapList(RangeOrListOfValues::convertLongToInt, newValues);
+  }
+
+  /**
+   * Transforms this into a range. Fails if there are no values in the list. Has no effect if this
+   * is already a range.
+   */
+  public void convertToRange() {
+    if (!isRange) {
+      isRange = true;
+      range = Range.create(values);
+      values = null;
+    }
+  }
+
+  @Override
+  public String toString() {
+    if (isRange) {
+      return range.toString();
+    } else {
+      if (values.isEmpty()) {
+        return "[]";
+      }
+      String res = "[";
+      res += StringsPlume.join(", ", values);
+      res += "]";
+      return res;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java b/framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java
new file mode 100644
index 0000000..dd8cf05
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java
@@ -0,0 +1,364 @@
+package org.checkerframework.common.value;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewClassTree;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty;
+import org.checkerframework.checker.signature.qual.ClassGetName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Evaluates expressions (such as method calls and field accesses) at compile time, to determine
+ * whether they have compile-time constant values.
+ */
+public class ReflectiveEvaluator {
+
+  /** The checker that is using this ReflectiveEvaluator. */
+  private BaseTypeChecker checker;
+
+  /**
+   * Whether to report warnings about problems with evaluation. Controlled by the -AreportEvalWarns
+   * command-line option.
+   */
+  private boolean reportWarnings;
+
+  public ReflectiveEvaluator(
+      BaseTypeChecker checker, ValueAnnotatedTypeFactory factory, boolean reportWarnings) {
+    this.checker = checker;
+    this.reportWarnings = reportWarnings;
+  }
+
+  /**
+   * Returns all possible values that the method may return, or null if the method could not be
+   * evaluated.
+   *
+   * @param allArgValues a list of list where the first list corresponds to all possible values for
+   *     the first argument. Pass null to indicate that the method has no arguments.
+   * @param receiverValues a list of possible receiver values. null indicates that the method has no
+   *     receiver.
+   * @param tree location to report any errors
+   * @return all possible values that the method may return, or null if the method could not be
+   *     evaluated
+   */
+  public List<?> evaluateMethodCall(
+      List<List<?>> allArgValues, List<?> receiverValues, MethodInvocationTree tree) {
+    Method method = getMethodObject(tree);
+    if (method == null) {
+      return null;
+    }
+
+    if (receiverValues == null) {
+      // Method does not have a receiver
+      // the first parameter of Method.invoke should be null
+      receiverValues = Collections.singletonList(null);
+    }
+
+    List<Object[]> listOfArguments;
+    if (allArgValues == null) {
+      // Method does not have arguments
+      listOfArguments = Collections.singletonList(null);
+    } else {
+      // Find all possible argument sets
+      listOfArguments = cartesianProduct(allArgValues, allArgValues.size() - 1);
+    }
+
+    if (method.isVarArgs()) {
+      int numberOfParameters = method.getParameterTypes().length;
+      listOfArguments =
+          CollectionsPlume.mapList(
+              (Object[] args) -> normalizeVararg(args, numberOfParameters), listOfArguments);
+    }
+
+    List<Object> results = new ArrayList<>(listOfArguments.size());
+    for (Object[] arguments : listOfArguments) {
+      for (Object receiver : receiverValues) {
+        try {
+          results.add(method.invoke(receiver, arguments));
+        } catch (InvocationTargetException e) {
+          if (reportWarnings) {
+            checker.reportWarning(
+                tree, "method.evaluation.exception", method, e.getTargetException().toString());
+          }
+          // Method evaluation will always fail, so don't bother
+          // trying again
+          return null;
+        } catch (ExceptionInInitializerError e) {
+          if (reportWarnings) {
+            checker.reportWarning(
+                tree, "method.evaluation.exception", method, e.getCause().toString());
+          }
+          return null;
+        } catch (IllegalArgumentException e) {
+          if (reportWarnings) {
+            String args = StringsPlume.join(", ", arguments);
+            checker.reportWarning(
+                tree, "method.evaluation.exception", method, e.getLocalizedMessage() + ": " + args);
+          }
+          return null;
+        } catch (Throwable e) {
+          // Catch any exception thrown because they shouldn't crash the type checker.
+          if (reportWarnings) {
+            checker.reportWarning(tree, "method.evaluation.failed", method);
+          }
+          return null;
+        }
+      }
+    }
+    return results;
+  }
+
+  /**
+   * This method normalizes an array of arguments to a varargs method by changing the arguments
+   * associated with the varargs parameter into an array.
+   *
+   * @param arguments an array of arguments for {@code method}. The length is at least {@code
+   *     numberOfParameters - 1}.
+   * @param numberOfParameters number of parameters of the vararg method
+   * @return the length of the array is exactly {@code numberOfParameters}
+   */
+  private Object[] normalizeVararg(Object[] arguments, int numberOfParameters) {
+
+    if (arguments == null) {
+      // null means no arguments.  For varargs no arguments is an empty array.
+      arguments = new Object[] {};
+    }
+    Object[] newArgs = new Object[numberOfParameters];
+    Object[] varArgsArray;
+    int numOfVarArgs = arguments.length - numberOfParameters + 1;
+    if (numOfVarArgs > 0) {
+      System.arraycopy(arguments, 0, newArgs, 0, numberOfParameters - 1);
+      varArgsArray = new Object[numOfVarArgs];
+      System.arraycopy(arguments, numberOfParameters - 1, varArgsArray, 0, numOfVarArgs);
+    } else {
+      System.arraycopy(arguments, 0, newArgs, 0, numberOfParameters - 1);
+      varArgsArray = new Object[] {};
+    }
+    newArgs[numberOfParameters - 1] = varArgsArray;
+    return newArgs;
+  }
+
+  /**
+   * Method for reflectively obtaining a method object so it can (potentially) be statically
+   * executed by the checker for constant propagation.
+   *
+   * @param tree a method invocation tree
+   * @return the Method object corresponding to the method invocation tree
+   */
+  private Method getMethodObject(MethodInvocationTree tree) {
+    final ExecutableElement ele = TreeUtils.elementFromUse(tree);
+    List<Class<?>> paramClasses = null;
+    try {
+      @CanonicalNameOrEmpty String className =
+          TypesUtils.getQualifiedName((DeclaredType) ele.getEnclosingElement().asType());
+      paramClasses = getParameterClasses(ele);
+      @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Class.toString
+      Class<?> clazz = Class.forName(className.toString());
+      Method method =
+          clazz.getMethod(ele.getSimpleName().toString(), paramClasses.toArray(new Class<?>[0]));
+      @SuppressWarnings("deprecation") // TODO: find alternative
+      boolean acc = method.isAccessible();
+      if (!acc) {
+        method.setAccessible(true);
+      }
+      return method;
+    } catch (ClassNotFoundException | UnsupportedClassVersionError | NoClassDefFoundError e) {
+      if (reportWarnings) {
+        checker.reportWarning(tree, "class.find.failed", ele.getEnclosingElement());
+      }
+      return null;
+
+    } catch (Throwable e) {
+      // The class we attempted to getMethod from inside the
+      // call to getMethodObject.
+      Element classElem = ele.getEnclosingElement();
+
+      if (classElem == null) {
+        if (reportWarnings) {
+          checker.reportWarning(tree, "method.find.failed", ele.getSimpleName(), paramClasses);
+        }
+      } else {
+        if (reportWarnings) {
+          checker.reportWarning(
+              tree, "method.find.failed.in.class", ele.getSimpleName(), paramClasses, classElem);
+        }
+      }
+      return null;
+    }
+  }
+
+  /**
+   * Returns the classes of the given method's formal parameters.
+   *
+   * @param ele a method or constructor
+   * @return the classes of the given method's formal parameters
+   * @throws ClassNotFoundException if the class cannot be found
+   */
+  private List<Class<?>> getParameterClasses(ExecutableElement ele) throws ClassNotFoundException {
+    return CollectionsPlume.mapList(
+        (Element e) -> TypesUtils.getClassFromType(ElementUtils.getType(e)), ele.getParameters());
+  }
+
+  private List<Object[]> cartesianProduct(List<List<?>> allArgValues, int whichArg) {
+    List<?> argValues = allArgValues.get(whichArg);
+    List<Object[]> tuples = new ArrayList<>();
+
+    for (Object value : argValues) {
+      if (whichArg == 0) {
+        Object[] objects = new Object[allArgValues.size()];
+        objects[0] = value;
+        tuples.add(objects);
+      } else {
+        List<Object[]> lastTuples = cartesianProduct(allArgValues, whichArg - 1);
+        List<Object[]> copies = copy(lastTuples);
+        for (Object[] copy : copies) {
+          copy[whichArg] = value;
+        }
+        tuples.addAll(copies);
+      }
+    }
+    return tuples;
+  }
+
+  /**
+   * Returns a depth-2 copy of the given list. In the returned value, the list and the arrays in it
+   * are new, but the elements of the arrays are shared with the argument.
+   *
+   * @param lastTuples a list of arrays
+   * @return a depth-2 copy of the given list
+   */
+  private List<Object[]> copy(List<Object[]> lastTuples) {
+    return CollectionsPlume.mapList(
+        (Object[] list) -> Arrays.copyOf(list, list.length), lastTuples);
+  }
+
+  /**
+   * Return the value of a static field access. Return null if accessing the field reflectively
+   * fails.
+   *
+   * @param classname the class containing the field
+   * @param fieldName the name of the field
+   * @param tree the static field access in the program; a MemberSelectTree or an IdentifierTree;
+   *     used for diagnostics
+   * @return the value of the static field access, or null if it cannot be determined
+   */
+  public Object evaluateStaticFieldAccess(
+      @ClassGetName String classname, String fieldName, ExpressionTree tree) {
+    try {
+      Class<?> recClass = Class.forName(classname);
+      Field field = recClass.getField(fieldName);
+      return field.get(recClass);
+
+    } catch (ClassNotFoundException | UnsupportedClassVersionError | NoClassDefFoundError e) {
+      if (reportWarnings) {
+        checker.reportWarning(
+            tree, "class.find.failed", classname, e.getClass() + ": " + e.getMessage());
+      }
+      return null;
+    } catch (Throwable e) {
+      // Catch all exception so that the checker doesn't crash
+      if (reportWarnings) {
+        checker.reportWarning(
+            tree,
+            "field.access.failed",
+            fieldName,
+            classname,
+            e.getClass() + ": " + e.getMessage());
+      }
+      return null;
+    }
+  }
+
+  public List<?> evaluteConstructorCall(
+      ArrayList<List<?>> argValues, NewClassTree tree, TypeMirror typeToCreate) {
+    Constructor<?> constructor;
+    try {
+      // get the constructor
+      constructor = getConstructorObject(tree, typeToCreate);
+    } catch (Throwable e) {
+      // Catch all exception so that the checker doesn't crash
+      if (reportWarnings) {
+        checker.reportWarning(tree, "constructor.invocation.failed");
+      }
+      return null;
+    }
+    if (constructor == null) {
+      return null;
+    }
+
+    List<Object[]> listOfArguments;
+    if (argValues == null) {
+      // Method does not have arguments
+      listOfArguments = Collections.singletonList(null);
+    } else {
+      // Find all possible argument sets
+      listOfArguments = cartesianProduct(argValues, argValues.size() - 1);
+    }
+
+    List<Object> results = new ArrayList<>(listOfArguments.size());
+    for (Object[] arguments : listOfArguments) {
+      try {
+        results.add(constructor.newInstance(arguments));
+      } catch (Throwable e) {
+        if (reportWarnings) {
+          checker.reportWarning(
+              tree,
+              "constructor.evaluation.failed",
+              typeToCreate,
+              StringsPlume.join(", ", arguments));
+        }
+        return null;
+      }
+    }
+    return results;
+  }
+
+  private Constructor<?> getConstructorObject(NewClassTree tree, TypeMirror typeToCreate)
+      throws ClassNotFoundException, NoSuchMethodException {
+    ExecutableElement ele = TreeUtils.elementFromUse(tree);
+    List<Class<?>> paramClasses = getParameterClasses(ele);
+    Class<?> recClass = boxPrimitives(TypesUtils.getClassFromType(typeToCreate));
+    Constructor<?> constructor = recClass.getConstructor(paramClasses.toArray(new Class<?>[0]));
+    return constructor;
+  }
+  /**
+   * Returns the box primitive type if the passed type is an (unboxed) primitive. Otherwise it
+   * returns the passed type
+   */
+  private static Class<?> boxPrimitives(Class<?> type) {
+    if (type == byte.class) {
+      return Byte.class;
+    } else if (type == short.class) {
+      return Short.class;
+    } else if (type == int.class) {
+      return Integer.class;
+    } else if (type == long.class) {
+      return Long.class;
+    } else if (type == float.class) {
+      return Float.class;
+    } else if (type == double.class) {
+      return Double.class;
+    } else if (type == char.class) {
+      return Character.class;
+    } else if (type == boolean.class) {
+      return Boolean.class;
+    }
+    return type;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java
new file mode 100644
index 0000000..6257e34
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java
@@ -0,0 +1,1520 @@
+package org.checkerframework.common.value;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.regex.qual.Regex;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.ArrayLenRange;
+import org.checkerframework.common.value.qual.BoolVal;
+import org.checkerframework.common.value.qual.BottomVal;
+import org.checkerframework.common.value.qual.DoubleVal;
+import org.checkerframework.common.value.qual.EnumVal;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.IntRangeFromGTENegativeOne;
+import org.checkerframework.common.value.qual.IntRangeFromNonNegative;
+import org.checkerframework.common.value.qual.IntRangeFromPositive;
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.common.value.qual.MatchesRegex;
+import org.checkerframework.common.value.qual.MinLen;
+import org.checkerframework.common.value.qual.MinLenFieldInvariant;
+import org.checkerframework.common.value.qual.PolyValue;
+import org.checkerframework.common.value.qual.StringVal;
+import org.checkerframework.common.value.qual.UnknownVal;
+import org.checkerframework.common.value.util.Range;
+import org.checkerframework.dataflow.expression.ArrayAccess;
+import org.checkerframework.dataflow.expression.ArrayCreation;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.ValueLiteral;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.DefaultTypeHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.StructuralEqualityComparer;
+import org.checkerframework.framework.type.TypeHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
+import org.checkerframework.framework.util.FieldInvariants;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypeKindUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+
+/** AnnotatedTypeFactory for the Value type system. */
+public class ValueAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+  /** Fully-qualified class name of {@link UnknownVal}. */
+  public static final String UNKNOWN_NAME = "org.checkerframework.common.value.qual.UnknownVal";
+  /** Fully-qualified class name of {@link BottomVal}. */
+  public static final String BOTTOMVAL_NAME = "org.checkerframework.common.value.qual.BottomVal";
+  /** Fully-qualified class name of {@link PolyValue}. */
+  public static final String POLY_NAME = "org.checkerframework.common.value.qual.PolyValue";
+  /** Fully-qualified class name of {@link ArrayLen}. */
+  public static final String ARRAYLEN_NAME = "org.checkerframework.common.value.qual.ArrayLen";
+  /** Fully-qualified class name of {@link BoolVal}. */
+  public static final String BOOLVAL_NAME = "org.checkerframework.common.value.qual.BoolVal";
+  /** Fully-qualified class name of {@link DoubleVal}. */
+  public static final String DOUBLEVAL_NAME = "org.checkerframework.common.value.qual.DoubleVal";
+  /** Fully-qualified class name of {@link IntVal}. */
+  public static final String INTVAL_NAME = "org.checkerframework.common.value.qual.IntVal";
+  /** Fully-qualified class name of {@link StringVal}. */
+  public static final String STRINGVAL_NAME = "org.checkerframework.common.value.qual.StringVal";
+  /** Fully-qualified class name of {@link ArrayLenRange}. */
+  public static final String ARRAYLENRANGE_NAME =
+      "org.checkerframework.common.value.qual.ArrayLenRange";
+  /** Fully-qualified class name of {@link IntRange}. */
+  public static final String INTRANGE_NAME = "org.checkerframework.common.value.qual.IntRange";
+
+  /** Fully-qualified class name of {@link IntRangeFromGTENegativeOne}. */
+  public static final String INTRANGE_FROMGTENEGONE_NAME =
+      "org.checkerframework.common.value.qual.IntRangeFromGTENegativeOne";
+  /** Fully-qualified class name of {@link IntRangeFromNonNegative}. */
+  public static final String INTRANGE_FROMNONNEG_NAME =
+      "org.checkerframework.common.value.qual.IntRangeFromNonNegative";
+  /** Fully-qualified class name of {@link IntRangeFromPositive}. */
+  public static final String INTRANGE_FROMPOS_NAME =
+      "org.checkerframework.common.value.qual.IntRangeFromPositive";
+  /** Fully-qualified class name of {@link MinLen}. */
+  public static final String MINLEN_NAME = "org.checkerframework.common.value.qual.MinLen";
+  /** Fully-qualified class name of {@link MatchesRegex}. */
+  public static final String MATCHES_REGEX_NAME =
+      "org.checkerframework.common.value.qual.MatchesRegex";
+
+  /** The maximum number of values allowed in an annotation's array. */
+  protected static final int MAX_VALUES = 10;
+
+  /** The top type for this hierarchy. */
+  protected final AnnotationMirror UNKNOWNVAL =
+      AnnotationBuilder.fromClass(elements, UnknownVal.class);
+
+  /** The bottom type for this hierarchy. */
+  protected final AnnotationMirror BOTTOMVAL =
+      AnnotationBuilder.fromClass(elements, BottomVal.class);
+
+  /** The canonical @{@link PolyValue} annotation. */
+  public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyValue.class);
+
+  /** The canonical @{@link BoolVal}(true) annotation. */
+  public final AnnotationMirror BOOLEAN_TRUE =
+      createBooleanAnnotation(Collections.singletonList(true));
+
+  /** The canonical @{@link BoolVal}(false) annotation. */
+  public final AnnotationMirror BOOLEAN_FALSE =
+      createBooleanAnnotation(Collections.singletonList(false));
+
+  /** The value() element/field of an @ArrayLen annotation. */
+  protected final ExecutableElement arrayLenValueElement =
+      TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv);
+  /** The from() element/field of an @ArrayLenRange annotation. */
+  protected final ExecutableElement arrayLenRangeFromElement =
+      TreeUtils.getMethod(ArrayLenRange.class, "from", 0, processingEnv);
+  /** The to() element/field of an @ArrayLenRange annotation. */
+  protected final ExecutableElement arrayLenRangeToElement =
+      TreeUtils.getMethod(ArrayLenRange.class, "to", 0, processingEnv);
+  /** The value() element/field of a @BoolVal annotation. */
+  protected final ExecutableElement boolValValueElement =
+      TreeUtils.getMethod(BoolVal.class, "value", 0, processingEnv);
+  /** The value() element/field of a @DoubleVal annotation. */
+  protected final ExecutableElement doubleValValueElement =
+      TreeUtils.getMethod(DoubleVal.class, "value", 0, processingEnv);
+  /** The from() element/field of an @IntRange annotation. */
+  protected final ExecutableElement intRangeFromElement =
+      TreeUtils.getMethod(IntRange.class, "from", 0, processingEnv);
+  /** The to() element/field of an @IntRange annotation. */
+  protected final ExecutableElement intRangeToElement =
+      TreeUtils.getMethod(IntRange.class, "to", 0, processingEnv);
+  /** The value() element/field of a @IntVal annotation. */
+  protected final ExecutableElement intValValueElement =
+      TreeUtils.getMethod(IntVal.class, "value", 0, processingEnv);
+  /** The value() element/field of a @MatchesRegex annotation. */
+  public final ExecutableElement matchesRegexValueElement =
+      TreeUtils.getMethod(MatchesRegex.class, "value", 0, processingEnv);
+  /** The value() element/field of a @MinLen annotation. */
+  protected final ExecutableElement minLenValueElement =
+      TreeUtils.getMethod(MinLen.class, "value", 0, processingEnv);
+  /** The field() element/field of a @MinLenFieldInvariant annotation. */
+  protected final ExecutableElement minLenFieldInvariantFieldElement =
+      TreeUtils.getMethod(MinLenFieldInvariant.class, "field", 0, processingEnv);
+  /** The minLen() element/field of a @MinLenFieldInvariant annotation. */
+  protected final ExecutableElement minLenFieldInvariantMinLenElement =
+      TreeUtils.getMethod(MinLenFieldInvariant.class, "minLen", 0, processingEnv);
+  /** The value() element/field of a @StringVal annotation. */
+  public final ExecutableElement stringValValueElement =
+      TreeUtils.getMethod(StringVal.class, "value", 0, processingEnv);
+
+  /** Should this type factory report warnings? */
+  private final boolean reportEvalWarnings;
+
+  /** Helper class that evaluates statically executable methods, constructors, and fields. */
+  // TODO: only used in ValueTreeAnnotator. Should it move there?
+  protected final ReflectiveEvaluator evaluator;
+
+  /** Helper class that holds references to special methods. */
+  private final ValueMethodIdentifier methods;
+
+  @SuppressWarnings("StaticAssignmentInConstructor") // static Range.ignoreOverflow is gross
+  public ValueAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+
+    reportEvalWarnings = checker.hasOption(ValueChecker.REPORT_EVAL_WARNS);
+    Range.ignoreOverflow = checker.hasOption(ValueChecker.IGNORE_RANGE_OVERFLOW);
+    evaluator = new ReflectiveEvaluator(checker, this, reportEvalWarnings);
+
+    addAliasedTypeAnnotation("android.support.annotation.IntRange", IntRange.class, true);
+
+    // The actual ArrayLenRange is created by
+    // {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)};
+    // this line just registers the alias. The BottomVal is never used.
+    addAliasedTypeAnnotation(MinLen.class, BOTTOMVAL);
+
+    // @Positive is aliased here because @Positive provides useful
+    // information about @MinLen annotations.
+    // @NonNegative and @GTENegativeOne are aliased similarly so
+    // that it's possible to overwrite a function annotated to return
+    // @NonNegative with, for instance, a function that returns an @IntVal(0).
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.index.qual.Positive", createIntRangeFromPositive());
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.index.qual.NonNegative", createIntRangeFromNonNegative());
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.index.qual.GTENegativeOne",
+        createIntRangeFromGTENegativeOne());
+    // Must also alias any alias of three annotations above:
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.index.qual.LengthOf", createIntRangeFromNonNegative());
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.index.qual.IndexFor", createIntRangeFromNonNegative());
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.index.qual.IndexOrHigh", createIntRangeFromNonNegative());
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.index.qual.IndexOrLow", createIntRangeFromGTENegativeOne());
+    addAliasedTypeAnnotation(
+        "org.checkerframework.checker.index.qual.SubstringIndexFor",
+        createIntRangeFromGTENegativeOne());
+
+    // PolyLength is syntactic sugar for both @PolySameLen and @PolyValue
+    addAliasedTypeAnnotation("org.checkerframework.checker.index.qual.PolyLength", POLY);
+
+    // EnumVal is treated as StringVal internally by the checker.
+    addAliasedTypeAnnotation(EnumVal.class, StringVal.class, true);
+
+    methods = new ValueMethodIdentifier(processingEnv);
+
+    if (this.getClass() == ValueAnnotatedTypeFactory.class) {
+      this.postInit();
+    }
+  }
+
+  /** Gets a helper object that holds references to methods with special handling. */
+  ValueMethodIdentifier getMethodIdentifier() {
+    return methods;
+  }
+
+  @Override
+  public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) {
+    if (AnnotationUtils.areSameByName(anno, MINLEN_NAME)) {
+      int from = getMinLenValue(anno);
+      return createArrayLenRangeAnnotation(from, Integer.MAX_VALUE);
+    }
+
+    return super.canonicalAnnotation(anno);
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    // Because the Value Checker includes its own alias annotations,
+    // the qualifiers have to be explicitly defined.
+    return new LinkedHashSet<>(
+        Arrays.asList(
+            ArrayLen.class,
+            ArrayLenRange.class,
+            IntVal.class,
+            IntRange.class,
+            BoolVal.class,
+            StringVal.class,
+            MatchesRegex.class,
+            DoubleVal.class,
+            BottomVal.class,
+            UnknownVal.class,
+            IntRangeFromPositive.class,
+            IntRangeFromNonNegative.class,
+            IntRangeFromGTENegativeOne.class,
+            PolyValue.class));
+  }
+
+  @Override
+  public CFTransfer createFlowTransferFunction(
+      CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
+    return new ValueTransfer(analysis);
+  }
+
+  @Override
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new ValueQualifierHierarchy(this, this.getSupportedTypeQualifiers());
+  }
+
+  @Override
+  protected TypeHierarchy createTypeHierarchy() {
+    // This is a lot of code to replace annotations so that annotations that are equivalent
+    // qualifiers are the same annotation.
+    return new DefaultTypeHierarchy(
+        checker,
+        getQualifierHierarchy(),
+        checker.getBooleanOption("ignoreRawTypeArguments", true),
+        checker.hasOption("invariantArrays")) {
+      @Override
+      public StructuralEqualityComparer createEqualityComparer() {
+        return new StructuralEqualityComparer(areEqualVisitHistory) {
+          @Override
+          protected boolean arePrimeAnnosEqual(
+              AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) {
+            type1.replaceAnnotation(
+                convertToUnknown(
+                    convertSpecialIntRangeToStandardIntRange(
+                        type1.getAnnotationInHierarchy(UNKNOWNVAL))));
+            type2.replaceAnnotation(
+                convertToUnknown(
+                    convertSpecialIntRangeToStandardIntRange(
+                        type2.getAnnotationInHierarchy(UNKNOWNVAL))));
+
+            return super.arePrimeAnnosEqual(type1, type2);
+          }
+        };
+      }
+    };
+  }
+
+  @Override
+  protected TypeAnnotator createTypeAnnotator() {
+    return new ListTypeAnnotator(new ValueTypeAnnotator(this), super.createTypeAnnotator());
+  }
+
+  @Override
+  public FieldInvariants getFieldInvariants(TypeElement element) {
+    AnnotationMirror fieldInvarAnno = getDeclAnnotation(element, MinLenFieldInvariant.class);
+    if (fieldInvarAnno == null) {
+      return null;
+    }
+    List<String> fields =
+        AnnotationUtils.getElementValueArray(
+            fieldInvarAnno, minLenFieldInvariantFieldElement, String.class);
+    List<Integer> minlens =
+        AnnotationUtils.getElementValueArray(
+            fieldInvarAnno, minLenFieldInvariantMinLenElement, Integer.class);
+    List<AnnotationMirror> qualifiers =
+        CollectionsPlume.mapList(
+            (Integer minlen) -> createArrayLenRangeAnnotation(minlen, Integer.MAX_VALUE), minlens);
+
+    FieldInvariants superInvariants = super.getFieldInvariants(element);
+    return new FieldInvariants(superInvariants, fields, qualifiers);
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> getFieldInvariantDeclarationAnnotations() {
+    // include FieldInvariant so that @MinLenBottom can be used.
+    Set<Class<? extends Annotation>> set =
+        new HashSet<>(super.getFieldInvariantDeclarationAnnotations());
+    set.add(MinLenFieldInvariant.class);
+    return set;
+  }
+
+  /**
+   * Creates array length annotations for the result of the Enum.values() method, which is the
+   * number of possible values of the enum.
+   */
+  @Override
+  public ParameterizedExecutableType methodFromUse(
+      ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) {
+
+    ParameterizedExecutableType superPair = super.methodFromUse(tree, methodElt, receiverType);
+    if (ElementUtils.matchesElement(methodElt, "values")
+        && methodElt.getEnclosingElement().getKind() == ElementKind.ENUM
+        && ElementUtils.isStatic(methodElt)) {
+      int count = 0;
+      List<? extends Element> l = methodElt.getEnclosingElement().getEnclosedElements();
+      for (Element el : l) {
+        if (el.getKind() == ElementKind.ENUM_CONSTANT) {
+          count++;
+        }
+      }
+      AnnotationMirror am = createArrayLenAnnotation(Collections.singletonList(count));
+      superPair.executableType.getReturnType().replaceAnnotation(am);
+    }
+    return superPair;
+  }
+
+  /**
+   * Finds the appropriate value for the {@code from} value of an annotated type mirror containing
+   * an {@code IntRange} annotation.
+   *
+   * @param atm an annotated type mirror that contains an {@code IntRange} annotation
+   * @return either the from value from the passed int range annotation, or the minimum value of the
+   *     domain of the underlying type (i.e. Integer.MIN_VALUE if the underlying type is int)
+   */
+  public long getFromValueFromIntRange(AnnotatedTypeMirror atm) {
+    TypeMirror type = atm.getUnderlyingType();
+    long defaultValue = TypeKindUtils.minValue(toPrimitiveIntegralTypeKind(type));
+
+    AnnotationMirror intRangeAnno = atm.getAnnotation(IntRange.class);
+    return getIntRangeFromValue(intRangeAnno, defaultValue);
+  }
+
+  /**
+   * Finds the appropriate value for the {@code to} value of an annotated type mirror containing an
+   * {@code IntRange} annotation.
+   *
+   * @param atm an annotated type mirror that contains an {@code IntRange} annotation
+   * @return either the to value from the passed int range annotation, or the maximum value of the
+   *     domain of the underlying type (i.e. Integer.MAX_VALUE if the underlying type is int)
+   */
+  public long getToValueFromIntRange(AnnotatedTypeMirror atm) {
+    TypeMirror type = atm.getUnderlyingType();
+    long defaultValue = TypeKindUtils.maxValue(toPrimitiveIntegralTypeKind(type));
+
+    AnnotationMirror intRangeAnno = atm.getAnnotation(IntRange.class);
+    return getIntRangeToValue(intRangeAnno, defaultValue);
+  }
+
+  /**
+   * Gets the from() element/field out of an IntRange annotation. The from() element/field must
+   * exist. Clients should call {@link #getFromValueFromIntRange} if it might not exist.
+   *
+   * @param intRangeAnno an IntRange annotation
+   * @return its from() element/field
+   */
+  protected long getIntRangeFromValue(AnnotationMirror intRangeAnno) {
+    return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeFromElement, Long.MIN_VALUE);
+  }
+
+  /**
+   * Gets the from() element/field out of an IntRange annotation. The from() element/field must
+   * exist. Clients should call {@link #getFromValueFromIntRange} if it might not exist.
+   *
+   * @param intRangeAnno an IntRange annotation
+   * @param defaultValue the value to return if there is no from() element/field
+   * @return its from() element/field
+   */
+  protected long getIntRangeFromValue(AnnotationMirror intRangeAnno, long defaultValue) {
+    return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeFromElement, defaultValue);
+  }
+
+  /**
+   * Gets the to() element/field out of an IntRange annotation. The to() element/field must exist.
+   * Clients should call {@link #getToValueFromIntRange} if it might not exist.
+   *
+   * @param intRangeAnno an IntRange annotation
+   * @param defaultValue the value to retur if there is no to() element/field
+   * @return its to() element/field
+   */
+  protected long getIntRangeToValue(AnnotationMirror intRangeAnno, long defaultValue) {
+    return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeToElement, defaultValue);
+  }
+
+  /**
+   * Gets the to() element/field out of an IntRange annotation. The to() element/field must exist.
+   * Clients should call {@link #getToValueFromIntRange} if it might not exist.
+   *
+   * @param intRangeAnno an IntRange annotation
+   * @return its to() element/field
+   */
+  protected long getIntRangeToValue(AnnotationMirror intRangeAnno) {
+    return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeToElement, Long.MAX_VALUE);
+  }
+
+  /**
+   * Gets the from() element/field out of an ArrayLenRange annotation.
+   *
+   * @param anno an ArrayLenRange annotation
+   * @return its from() element/field
+   */
+  protected int getArrayLenRangeFromValue(AnnotationMirror anno) {
+    return AnnotationUtils.getElementValueInt(anno, arrayLenRangeFromElement, 0);
+  }
+
+  /**
+   * Gets the to() element/field out of an ArrayLenRange annotation.
+   *
+   * @param anno an ArrayLenRange annotation
+   * @return its to() element/field
+   */
+  protected int getArrayLenRangeToValue(AnnotationMirror anno) {
+    return AnnotationUtils.getElementValueInt(anno, arrayLenRangeToElement, Integer.MAX_VALUE);
+  }
+
+  /**
+   * Gets the value() element/field out of a MinLen annotation.
+   *
+   * @param anno a MinLen annotation
+   * @return its value() element/field
+   */
+  protected int getMinLenValueValue(AnnotationMirror anno) {
+    return AnnotationUtils.getElementValueInt(anno, minLenValueElement, 0);
+  }
+
+  /**
+   * Determine the primitive integral TypeKind for the given integral type.
+   *
+   * @param type the type to convert, must be an integral type, boxed or primitive
+   * @return one of INT, SHORT, BYTE, CHAR, or LONG
+   */
+  private static TypeKind toPrimitiveIntegralTypeKind(TypeMirror type) {
+    TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type);
+    if (typeKind != null && TypeKindUtils.isIntegral(typeKind)) {
+      return typeKind;
+    }
+    throw new BugInCF(type.toString() + " expected to be an integral type.");
+  }
+
+  /**
+   * Gets the values stored in either an ArrayLen annotation (ints) or an IntVal/DoubleVal/etc.
+   * annotation (longs), and casts the result to a long.
+   *
+   * @param anno annotation mirror from which to get values
+   * @return the values in {@code anno} casted to longs
+   */
+  /* package-private*/ List<Long> getArrayLenOrIntValue(AnnotationMirror anno) {
+    if (AnnotationUtils.areSameByName(anno, ARRAYLEN_NAME)) {
+      return CollectionsPlume.mapList(Integer::longValue, getArrayLength(anno));
+    } else {
+      return getIntValues(anno);
+    }
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    // Don't call super.createTreeAnnotator because it includes the PropagationTreeAnnotator.
+    // Only use the PropagationTreeAnnotator for typing new arrays.  The Value Checker
+    // computes types differently for all other trees normally typed by the
+    // PropagationTreeAnnotator.
+    TreeAnnotator arrayCreation =
+        new TreeAnnotator(this) {
+          PropagationTreeAnnotator propagationTreeAnnotator =
+              new PropagationTreeAnnotator(atypeFactory);
+
+          @Override
+          public Void visitNewArray(NewArrayTree node, AnnotatedTypeMirror mirror) {
+            return propagationTreeAnnotator.visitNewArray(node, mirror);
+          }
+        };
+    return new ListTreeAnnotator(
+        new ValueTreeAnnotator(this),
+        new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(),
+        arrayCreation);
+  }
+
+  /**
+   * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link
+   * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just return.
+   *
+   * @param anm any annotation mirror
+   * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one
+   *     doesn't exist
+   */
+  /* package-private */ AnnotationMirror convertSpecialIntRangeToStandardIntRange(
+      AnnotationMirror anm) {
+    if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMPOS_NAME)) {
+      return createIntRangeAnnotation(1, Integer.MAX_VALUE);
+    }
+
+    if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMNONNEG_NAME)) {
+      return createIntRangeAnnotation(0, Integer.MAX_VALUE);
+    }
+
+    if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMGTENEGONE_NAME)) {
+      return createIntRangeAnnotation(-1, Integer.MAX_VALUE);
+    }
+    return anm;
+  }
+
+  /**
+   * If {@code anno} is equivalent to UnknownVal, return UnknownVal; otherwise, return {@code anno}.
+   *
+   * @param anno any annotation mirror
+   * @return UnknownVal if {@code anno} is equivalent to it; otherwise, return {@code anno}
+   */
+  /* package-private */ AnnotationMirror convertToUnknown(AnnotationMirror anno) {
+    if (AnnotationUtils.areSameByName(anno, ARRAYLENRANGE_NAME)) {
+      Range range = getRange(anno);
+      if (range.from == 0 && range.to >= Integer.MAX_VALUE) {
+        return UNKNOWNVAL;
+      }
+    } else if (AnnotationUtils.areSameByName(anno, INTRANGE_NAME)) {
+      Range range = getRange(anno);
+      if (range.isLongEverything()) {
+        return UNKNOWNVAL;
+      }
+    }
+    return anno;
+  }
+
+  /**
+   * Returns the estimate for the length of a string or array with whose annotated type is {@code
+   * type}.
+   *
+   * @param type annotated typed
+   * @return the estimate for the length of a string or array with whose annotated type is {@code
+   *     type}.
+   */
+  /* package-private */ AnnotationMirror createArrayLengthResultAnnotation(
+      AnnotatedTypeMirror type) {
+    AnnotationMirror arrayAnno = type.getAnnotationInHierarchy(UNKNOWNVAL);
+    switch (AnnotationUtils.annotationName(arrayAnno)) {
+      case ARRAYLEN_NAME:
+        // array.length, where array : @ArrayLen(x)
+        List<Integer> lengths = getArrayLength(arrayAnno);
+        return createNumberAnnotationMirror(new ArrayList<>(lengths));
+      case ARRAYLENRANGE_NAME:
+        // array.length, where array : @ArrayLenRange(x)
+        Range range = getRange(arrayAnno);
+        return createIntRangeAnnotation(range);
+      case STRINGVAL_NAME:
+        List<String> strings = getStringValues(arrayAnno);
+        List<Integer> lengthsS = ValueCheckerUtils.getLengthsForStringValues(strings);
+        return createNumberAnnotationMirror(new ArrayList<>(lengthsS));
+      default:
+        return createIntRangeAnnotation(0, Integer.MAX_VALUE);
+    }
+  }
+
+  /**
+   * Returns a constant value annotation with the {@code value}. The class of the annotation
+   * reflects the {@code resultType} given.
+   *
+   * @param resultType used to select which kind of value annotation is returned
+   * @param value value to use
+   * @return a constant value annotation with the {@code value}
+   */
+  /* package-private */ AnnotationMirror createResultingAnnotation(
+      TypeMirror resultType, Object value) {
+    return createResultingAnnotation(resultType, Collections.singletonList(value));
+  }
+
+  /**
+   * Returns a constant value annotation with the {@code values}. The class of the annotation
+   * reflects the {@code resultType} given.
+   *
+   * @param resultType used to select which kind of value annotation is returned
+   * @param values must be a homogeneous list: every element of it has the same class
+   * @return a constant value annotation with the {@code values}
+   */
+  /* package-private */ AnnotationMirror createResultingAnnotation(
+      TypeMirror resultType, List<?> values) {
+    if (values == null) {
+      return UNKNOWNVAL;
+    }
+    // For some reason null is included in the list of values,
+    // so remove it so that it does not cause a NPE elsewhere.
+    values.remove(null);
+    if (values.isEmpty()) {
+      return BOTTOMVAL;
+    }
+
+    if (TypesUtils.isString(resultType)) {
+      List<String> stringVals = CollectionsPlume.mapList((Object o) -> (String) o, values);
+      return createStringAnnotation(stringVals);
+    } else if (TypesUtils.getClassFromType(resultType) == char[].class) {
+      List<String> stringVals =
+          CollectionsPlume.mapList(
+              (Object o) -> {
+                if (o instanceof char[]) {
+                  return new String((char[]) o);
+                } else {
+                  return o.toString();
+                }
+              },
+              values);
+      return createStringAnnotation(stringVals);
+    }
+
+    TypeKind primitiveKind;
+    if (TypesUtils.isPrimitive(resultType)) {
+      primitiveKind = resultType.getKind();
+    } else if (TypesUtils.isBoxedPrimitive(resultType)) {
+      primitiveKind = types.unboxedType(resultType).getKind();
+    } else {
+      return UNKNOWNVAL;
+    }
+
+    switch (primitiveKind) {
+      case BOOLEAN:
+        List<Boolean> boolVals = CollectionsPlume.mapList((Object o) -> (Boolean) o, values);
+        return createBooleanAnnotation(boolVals);
+      case DOUBLE:
+      case FLOAT:
+      case INT:
+      case LONG:
+      case SHORT:
+      case BYTE:
+        List<Number> numberVals = new ArrayList<>(values.size());
+        List<Character> characterVals = new ArrayList<>(values.size());
+        for (Object o : values) {
+          if (o instanceof Character) {
+            characterVals.add((Character) o);
+          } else {
+            numberVals.add((Number) o);
+          }
+        }
+        if (numberVals.isEmpty()) {
+          return createCharAnnotation(characterVals);
+        }
+        return createNumberAnnotationMirror(new ArrayList<>(numberVals));
+      case CHAR:
+        List<Character> charVals = new ArrayList<>(values.size());
+        for (Object o : values) {
+          if (o instanceof Number) {
+            charVals.add((char) ((Number) o).intValue());
+          } else {
+            charVals.add((char) o);
+          }
+        }
+        return createCharAnnotation(charVals);
+      default:
+        throw new UnsupportedOperationException("Unexpected kind:" + resultType);
+    }
+  }
+
+  /**
+   * Returns a {@link IntVal} or {@link IntRange} annotation using the values. If {@code values} is
+   * null, then UnknownVal is returned; if {@code values} is empty, then bottom is returned. If the
+   * number of {@code values} is greater than MAX_VALUES, return an {@link IntRange}. In other
+   * cases, the values are sorted and duplicates are removed before an {@link IntVal} is created.
+   *
+   * @param values list of longs; duplicates are allowed and the values may be in any order
+   * @return an annotation depends on the values
+   */
+  public AnnotationMirror createIntValAnnotation(List<Long> values) {
+    if (values == null) {
+      return UNKNOWNVAL;
+    }
+    if (values.isEmpty()) {
+      return BOTTOMVAL;
+    }
+    values = CollectionsPlume.withoutDuplicates(values);
+    if (values.size() > MAX_VALUES) {
+      long valMin = Collections.min(values);
+      long valMax = Collections.max(values);
+      return createIntRangeAnnotation(valMin, valMax);
+    } else {
+      AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntVal.class);
+      builder.setValue("value", values);
+      return builder.build();
+    }
+  }
+
+  /**
+   * Convert an {@code @IntRange} annotation to an {@code @IntVal} annotation, or to UNKNOWNVAL if
+   * the input is too wide to be represented as an {@code @IntVal}.
+   *
+   * @param intRangeAnno an {@code @IntRange} annotation
+   * @return an {@code @IntVal} annotation corresponding to the argument
+   */
+  public AnnotationMirror convertIntRangeToIntVal(AnnotationMirror intRangeAnno) {
+    Range range = getRange(intRangeAnno);
+    List<Long> values = ValueCheckerUtils.getValuesFromRange(range, Long.class);
+    return createIntValAnnotation(values);
+  }
+
+  /**
+   * Returns a {@link DoubleVal} annotation using the values. If {@code values} is null, then
+   * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are
+   * sorted and duplicates are removed before the annotation is created.
+   *
+   * @param values list of doubles; duplicates are allowed and the values may be in any order
+   * @return a {@link DoubleVal} annotation using the values
+   */
+  public AnnotationMirror createDoubleValAnnotation(List<Double> values) {
+    if (values == null) {
+      return UNKNOWNVAL;
+    }
+    if (values.isEmpty()) {
+      return BOTTOMVAL;
+    }
+    values = CollectionsPlume.withoutDuplicates(values);
+    if (values.size() > MAX_VALUES) {
+      return UNKNOWNVAL;
+    } else {
+      Collections.sort(values);
+      AnnotationBuilder builder = new AnnotationBuilder(processingEnv, DoubleVal.class);
+      builder.setValue("value", values);
+      return builder.build();
+    }
+  }
+
+  /** Convert an {@code @IntVal} annotation to a {@code @DoubleVal} annotation. */
+  /* package-private */ AnnotationMirror convertIntValToDoubleVal(AnnotationMirror intValAnno) {
+    List<Long> intValues = getIntValues(intValAnno);
+    return createDoubleValAnnotation(convertLongListToDoubleList(intValues));
+  }
+
+  /**
+   * Convert a {@code List<Long>} to a {@code List<Double>}.
+   *
+   * @param intValues a list of long integers
+   * @return a list of double floating-point values
+   */
+  /* package-private */ List<Double> convertLongListToDoubleList(List<Long> intValues) {
+    return CollectionsPlume.mapList(Long::doubleValue, intValues);
+  }
+
+  /**
+   * Returns a {@link StringVal} annotation using the values. If {@code values} is null, then
+   * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are
+   * sorted and duplicates are removed before the annotation is created. If values is larger than
+   * the max number of values allowed (10 by default), then an {@link ArrayLen} or an {@link
+   * ArrayLenRange} annotation is returned.
+   *
+   * @param values list of strings; duplicates are allowed and the values may be in any order
+   * @return a {@link StringVal} annotation using the values
+   */
+  public AnnotationMirror createStringAnnotation(List<String> values) {
+    if (values == null) {
+      return UNKNOWNVAL;
+    }
+    if (values.isEmpty()) {
+      return BOTTOMVAL;
+    }
+    values = CollectionsPlume.withoutDuplicates(values);
+    if (values.size() > MAX_VALUES) {
+      // Too many strings are replaced by their lengths
+      List<Integer> lengths = ValueCheckerUtils.getLengthsForStringValues(values);
+      return createArrayLenAnnotation(lengths);
+    } else {
+      AnnotationBuilder builder = new AnnotationBuilder(processingEnv, StringVal.class);
+      builder.setValue("value", values);
+      return builder.build();
+    }
+  }
+
+  /**
+   * Returns a {@link ArrayLen} annotation using the values. If {@code values} is null, then
+   * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are
+   * sorted and duplicates are removed before the annotation is created. If values is larger than
+   * the max number of values allowed (10 by default), then an {@link ArrayLenRange} annotation is
+   * returned.
+   *
+   * @param values list of integers; duplicates are allowed and the values may be in any order
+   * @return a {@link ArrayLen} annotation using the values
+   */
+  public AnnotationMirror createArrayLenAnnotation(List<Integer> values) {
+    if (values == null) {
+      return UNKNOWNVAL;
+    }
+    if (values.isEmpty()) {
+      return BOTTOMVAL;
+    }
+    values = CollectionsPlume.withoutDuplicates(values);
+    if (values.isEmpty() || Collections.min(values) < 0) {
+      return BOTTOMVAL;
+    } else if (values.size() > MAX_VALUES) {
+      return createArrayLenRangeAnnotation(Collections.min(values), Collections.max(values));
+    } else {
+      AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ArrayLen.class);
+      builder.setValue("value", values);
+      return builder.build();
+    }
+  }
+
+  /**
+   * Returns a {@link BoolVal} annotation using the values. If {@code values} is null, then
+   * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are
+   * sorted and duplicates are removed before the annotation is created.
+   *
+   * @param values list of booleans; duplicates are allowed and the values may be in any order
+   * @return a {@link BoolVal} annotation using the values
+   */
+  public AnnotationMirror createBooleanAnnotation(List<Boolean> values) {
+    if (values == null) {
+      return UNKNOWNVAL;
+    }
+    if (values.isEmpty()) {
+      return BOTTOMVAL;
+    }
+    values = CollectionsPlume.withoutDuplicates(values);
+    if (values.size() > MAX_VALUES) {
+      return UNKNOWNVAL;
+    } else {
+      // TODO: This seems wasteful.  Why not create the 3 interesting AnnotationMirrors (with
+      // arguments {true}, {false}, and {true, false}, respectively) in advance and return one
+      // of them?  (Maybe an advantage of this implementation is that it is identical to
+      // some other implementations and therefore might be less error-prone.)
+      AnnotationBuilder builder = new AnnotationBuilder(processingEnv, BoolVal.class);
+      builder.setValue("value", values);
+      return builder.build();
+    }
+  }
+
+  /**
+   * Returns a {@link IntVal} annotation using the values. If {@code values} is null, then
+   * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are
+   * sorted and duplicates are removed before the annotation is created.
+   *
+   * @param values list of characters; duplicates are allowed and the values may be in any order
+   * @return a {@link IntVal} annotation using the values
+   */
+  public AnnotationMirror createCharAnnotation(List<Character> values) {
+    if (values == null) {
+      return UNKNOWNVAL;
+    }
+    if (values.isEmpty()) {
+      return BOTTOMVAL;
+    }
+    values = CollectionsPlume.withoutDuplicates(values);
+    if (values.size() > MAX_VALUES) {
+      return UNKNOWNVAL;
+    } else {
+      List<Long> longValues = CollectionsPlume.mapList((Character value) -> (long) value, values);
+      return createIntValAnnotation(longValues);
+    }
+  }
+
+  /**
+   * Returns a {@link DoubleVal} annotation using the values. If {@code values} is null, then
+   * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are
+   * sorted and duplicates are removed before the annotation is created.
+   *
+   * @param values list of doubleacters; duplicates are allowed and the values may be in any order
+   * @return a {@link IntVal} annotation using the values
+   */
+  public AnnotationMirror createDoubleAnnotation(List<Double> values) {
+    if (values == null) {
+      return UNKNOWNVAL;
+    }
+    if (values.isEmpty()) {
+      return BOTTOMVAL;
+    }
+    values = CollectionsPlume.withoutDuplicates(values);
+    if (values.size() > MAX_VALUES) {
+      return UNKNOWNVAL;
+    } else {
+      return createDoubleValAnnotation(values);
+    }
+  }
+
+  /**
+   * Returns an annotation that represents the given set of values.
+   *
+   * @param values a homogeneous list: every element of it has the same class
+   * @return an annotation that represents the given set of values
+   */
+  public AnnotationMirror createNumberAnnotationMirror(List<Number> values) {
+    if (values == null) {
+      return UNKNOWNVAL;
+    } else if (values.isEmpty()) {
+      return BOTTOMVAL;
+    }
+    Number first = values.get(0);
+    if (first instanceof Integer
+        || first instanceof Short
+        || first instanceof Long
+        || first instanceof Byte) {
+      List<Long> intValues = CollectionsPlume.mapList(Number::longValue, values);
+      return createIntValAnnotation(intValues);
+    } else if (first instanceof Double || first instanceof Float) {
+      List<Double> intValues = CollectionsPlume.mapList(Number::doubleValue, values);
+      return createDoubleValAnnotation(intValues);
+    }
+    throw new UnsupportedOperationException(
+        "ValueAnnotatedTypeFactory: unexpected class: " + first.getClass());
+  }
+
+  /**
+   * Create an {@code @IntRange} annotation from the two (inclusive) bounds. Does not return
+   * BOTTOMVAL or UNKNOWNVAL.
+   */
+  /* package-private */ AnnotationMirror createIntRangeAnnotation(long from, long to) {
+    assert from <= to;
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRange.class);
+    builder.setValue("from", from);
+    builder.setValue("to", to);
+    return builder.build();
+  }
+
+  /**
+   * Create an {@code @IntRange} or {@code @IntVal} annotation from the range. May return BOTTOMVAL
+   * or UNKNOWNVAL.
+   */
+  public AnnotationMirror createIntRangeAnnotation(Range range) {
+    if (range.isNothing()) {
+      return BOTTOMVAL;
+    } else if (range.isLongEverything()) {
+      return UNKNOWNVAL;
+    } else if (range.isWiderThan(MAX_VALUES)) {
+      return createIntRangeAnnotation(range.from, range.to);
+    } else {
+      List<Long> newValues = ValueCheckerUtils.getValuesFromRange(range, Long.class);
+      return createIntValAnnotation(newValues);
+    }
+  }
+
+  /**
+   * Creates the special {@link IntRangeFromPositive} annotation, which is only used as an alias for
+   * the Index Checker's {@link org.checkerframework.checker.index.qual.Positive} annotation. It is
+   * treated everywhere as an IntRange annotation, but is not checked when it appears as the left
+   * hand side of an assignment (because the Lower Bound Checker will check it).
+   */
+  private AnnotationMirror createIntRangeFromPositive() {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRangeFromPositive.class);
+    return builder.build();
+  }
+
+  /**
+   * Creates the special {@link IntRangeFromNonNegative} annotation, which is only used as an alias
+   * for the Index Checker's {@link org.checkerframework.checker.index.qual.NonNegative} annotation.
+   * It is treated everywhere as an IntRange annotation, but is not checked when it appears as the
+   * left hand side of an assignment (because the Lower Bound Checker will check it).
+   */
+  private AnnotationMirror createIntRangeFromNonNegative() {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRangeFromNonNegative.class);
+    return builder.build();
+  }
+
+  /**
+   * Creates the special {@link IntRangeFromGTENegativeOne} annotation, which is only used as an
+   * alias for the Index Checker's {@link org.checkerframework.checker.index.qual.GTENegativeOne}
+   * annotation. It is treated everywhere as an IntRange annotation, but is not checked when it
+   * appears as the left hand side of an assignment (because the Lower Bound Checker will check it).
+   */
+  private AnnotationMirror createIntRangeFromGTENegativeOne() {
+    AnnotationBuilder builder =
+        new AnnotationBuilder(processingEnv, IntRangeFromGTENegativeOne.class);
+    return builder.build();
+  }
+
+  /**
+   * Create an {@code @ArrayLenRange} annotation from the two (inclusive) bounds. Does not return
+   * BOTTOMVAL or UNKNOWNVAL.
+   */
+  public AnnotationMirror createArrayLenRangeAnnotation(int from, int to) {
+    assert from <= to;
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ArrayLenRange.class);
+    builder.setValue("from", from);
+    builder.setValue("to", to);
+    return builder.build();
+  }
+
+  /**
+   * Create an {@code @ArrayLenRange} annotation from the range. May return BOTTOMVAL or UNKNOWNVAL.
+   */
+  public AnnotationMirror createArrayLenRangeAnnotation(Range range) {
+    if (range.isNothing()) {
+      return BOTTOMVAL;
+    } else if (range.isLongEverything() || !range.isWithinInteger()) {
+      return UNKNOWNVAL;
+    } else {
+      return createArrayLenRangeAnnotation(
+          Long.valueOf(range.from).intValue(), Long.valueOf(range.to).intValue());
+    }
+  }
+
+  /**
+   * Creates an {@code MatchesRegex} annotation for the given regular expressions.
+   *
+   * @param regexes a list of Java regular expressions
+   * @return a MatchesRegex annotation with those values
+   */
+  public AnnotationMirror createMatchesRegexAnnotation(@Nullable List<@Regex String> regexes) {
+    if (regexes == null) {
+      return UNKNOWNVAL;
+    }
+    if (regexes.isEmpty()) {
+      return BOTTOMVAL;
+    }
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MatchesRegex.class);
+    builder.setValue("value", regexes.toArray(new String[0]));
+    return builder.build();
+  }
+
+  /**
+   * Converts an {@code @StringVal} annotation to an {@code @ArrayLenRange} annotation.
+   *
+   * @param stringValAnno a StringVal annotation
+   * @return an ArrayLenRange annotation representing the possible lengths of the values of the
+   *     given StringVal annotation
+   */
+  /* package-private */ AnnotationMirror convertStringValToArrayLenRange(
+      AnnotationMirror stringValAnno) {
+    List<String> values = getStringValues(stringValAnno);
+    List<Integer> lengths = ValueCheckerUtils.getLengthsForStringValues(values);
+    return createArrayLenRangeAnnotation(Collections.min(lengths), Collections.max(lengths));
+  }
+
+  /**
+   * Converts an {@code @StringVal} annotation to an {@code @ArrayLen} annotation. If the
+   * {@code @StringVal} annotation contains string values of more than MAX_VALUES distinct lengths,
+   * {@code @ArrayLenRange} annotation is returned instead.
+   */
+  /* package-private */ AnnotationMirror convertStringValToArrayLen(
+      AnnotationMirror stringValAnno) {
+    List<String> values = getStringValues(stringValAnno);
+    return createArrayLenAnnotation(ValueCheckerUtils.getLengthsForStringValues(values));
+  }
+
+  /**
+   * Converts an {@code StringVal} annotation to an {@code MatchesRegex} annotation that matches
+   * exactly the string values listed in the {@code StringVal}.
+   *
+   * @param stringValAnno a StringVal annotation
+   * @return an equivalent MatchesReges annotation
+   */
+  /* package-private */ AnnotationMirror convertStringValToMatchesRegex(
+      AnnotationMirror stringValAnno) {
+    List<String> values = getStringValues(stringValAnno);
+    List<@Regex String> valuesAsRegexes = CollectionsPlume.mapList(Pattern::quote, values);
+    return createMatchesRegexAnnotation(valuesAsRegexes);
+  }
+
+  /**
+   * Converts an {@code @ArrayLen} annotation to an {@code @ArrayLenRange} annotation.
+   *
+   * @param arrayLenAnno an ArrayLen annotation
+   * @return an ArrayLenRange annotation representing the bounds of the given ArrayLen annotation
+   */
+  public AnnotationMirror convertArrayLenToArrayLenRange(AnnotationMirror arrayLenAnno) {
+    List<Integer> values = getArrayLength(arrayLenAnno);
+    return createArrayLenRangeAnnotation(Collections.min(values), Collections.max(values));
+  }
+
+  /** Converts an {@code @IntVal} annotation to an {@code @IntRange} annotation. */
+  public AnnotationMirror convertIntValToIntRange(AnnotationMirror intValAnno) {
+    List<Long> intValues = getIntValues(intValAnno);
+    return createIntRangeAnnotation(Collections.min(intValues), Collections.max(intValues));
+  }
+
+  /**
+   * Returns a {@link Range} bounded by the values specified in the given {@code @Range} annotation.
+   * Also returns an appropriate range if an {@code @IntVal} annotation is passed. Returns {@code
+   * null} if the annotation is null or if the annotation is not an {@code IntRange}, {@code
+   * IntRangeFromPositive}, {@code IntVal}, or {@code ArrayLenRange}.
+   *
+   * @param rangeAnno a {@code @Range} annotation
+   * @return the {@link Range} that the annotation represents
+   */
+  public Range getRange(AnnotationMirror rangeAnno) {
+    if (rangeAnno == null) {
+      return null;
+    }
+    switch (AnnotationUtils.annotationName(rangeAnno)) {
+      case INTRANGE_FROMPOS_NAME:
+        return Range.create(1, Integer.MAX_VALUE);
+      case INTRANGE_FROMNONNEG_NAME:
+        return Range.create(0, Integer.MAX_VALUE);
+      case INTRANGE_FROMGTENEGONE_NAME:
+        return Range.create(-1, Integer.MAX_VALUE);
+      case INTVAL_NAME:
+        return ValueCheckerUtils.getRangeFromValues(getIntValues(rangeAnno));
+      case INTRANGE_NAME:
+        // Assume rangeAnno is well-formed, i.e., 'from' is less than or equal to 'to'.
+        return Range.create(getIntRangeFromValue(rangeAnno), getIntRangeToValue(rangeAnno));
+      case ARRAYLENRANGE_NAME:
+        return Range.create(
+            getArrayLenRangeFromValue(rangeAnno), getArrayLenRangeToValue(rangeAnno));
+      default:
+        return null;
+    }
+  }
+
+  /**
+   * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty
+   * list if no values are possible (for dead code). Returns null if any value is possible -- that
+   * is, if no estimate can be made -- and this includes when there is no constant-value annotation
+   * so the argument is null.
+   *
+   * <p>The method returns a list of {@code Long} but is named {@code getIntValues} because it
+   * supports the {@code @IntVal} annotation.
+   *
+   * @param intAnno an {@code @IntVal} annotation, or null
+   * @return the possible values, deduplicated and sorted
+   */
+  public List<Long> getIntValues(AnnotationMirror intAnno) {
+    if (intAnno == null) {
+      return null;
+    }
+    List<Long> list = AnnotationUtils.getElementValueArray(intAnno, intValValueElement, Long.class);
+    list = CollectionsPlume.withoutDuplicates(list);
+    return list;
+  }
+
+  /**
+   * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty
+   * list if no values are possible (for dead code). Returns null if any value is possible -- that
+   * is, if no estimate can be made -- and this includes when there is no constant-value annotation
+   * so the argument is null.
+   *
+   * @param doubleAnno a {@code @DoubleVal} annotation, or null
+   * @return the possible values, deduplicated and sorted
+   */
+  public List<Double> getDoubleValues(AnnotationMirror doubleAnno) {
+    if (doubleAnno == null) {
+      return null;
+    }
+    List<Double> list =
+        AnnotationUtils.getElementValueArray(doubleAnno, doubleValValueElement, Double.class);
+    list = CollectionsPlume.withoutDuplicates(list);
+    return list;
+  }
+
+  /**
+   * Returns the set of possible array lengths as a sorted list with no duplicate values. Returns
+   * the empty list if no values are possible (for dead code). Returns null if any value is possible
+   * -- that is, if no estimate can be made -- and this includes when there is no constant-value
+   * annotation so the argument is null.
+   *
+   * @param arrayAnno an {@code @ArrayLen} annotation, or null
+   * @return the possible array lengths, deduplicated and sorted
+   */
+  public List<Integer> getArrayLength(AnnotationMirror arrayAnno) {
+    if (arrayAnno == null) {
+      return null;
+    }
+    List<Integer> list =
+        AnnotationUtils.getElementValueArray(arrayAnno, arrayLenValueElement, Integer.class);
+    list = CollectionsPlume.withoutDuplicates(list);
+    return list;
+  }
+
+  /**
+   * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty
+   * list if no values are possible (for dead code). Returns null if any value is possible -- that
+   * is, if no estimate can be made -- and this includes when there is no constant-value annotation
+   * so the argument is null.
+   *
+   * @param intAnno an {@code @IntVal} annotation, or null
+   * @return the values represented by the given {@code @IntVal} annotation
+   */
+  public List<Character> getCharValues(AnnotationMirror intAnno) {
+    if (intAnno == null) {
+      return Collections.emptyList();
+    }
+    List<Long> intValues =
+        AnnotationUtils.getElementValueArray(intAnno, intValValueElement, Long.class);
+    List<Character> charValues =
+        CollectionsPlume.mapList((Long i) -> (char) i.intValue(), intValues);
+    Collections.sort(charValues);
+    // TODO: Should this be an unmodifiable list?
+    return new ArrayList<>(charValues);
+  }
+
+  /**
+   * Returns the single possible boolean value, or null if there is not exactly one possible value.
+   *
+   * @see #getBooleanValues
+   * @param boolAnno a {@code @BoolVal} annotation, or null
+   * @return the single possible boolean value, on null if that is not the case
+   */
+  public Boolean getBooleanValue(AnnotationMirror boolAnno) {
+    if (boolAnno == null) {
+      return null;
+    }
+    List<Boolean> boolValues =
+        AnnotationUtils.getElementValueArray(boolAnno, boolValValueElement, Boolean.class);
+    Set<Boolean> boolSet = new TreeSet<>(boolValues);
+    if (boolSet.size() == 1) {
+      return boolSet.iterator().next();
+    }
+    return null;
+  }
+
+  /**
+   * Returns the set of possible boolean values as a sorted list with no duplicate values. Returns
+   * the empty list if no values are possible (for dead code). Returns null if any value is possible
+   * -- that is, if no estimate can be made -- and this includes when there is no constant-value
+   * annotation so the argument is null.
+   *
+   * @param boolAnno a {@code @BoolVal} annotation, or null
+   * @return a singleton or empty list of possible boolean values, or null
+   */
+  public @Nullable List<Boolean> getBooleanValues(AnnotationMirror boolAnno) {
+    if (boolAnno == null) {
+      return Collections.emptyList();
+    }
+    List<Boolean> boolValues =
+        AnnotationUtils.getElementValueArray(boolAnno, boolValValueElement, Boolean.class);
+    if (boolValues.size() < 2) {
+      return boolValues;
+    }
+    // Remove duplicates.
+    Set<Boolean> boolSet = new TreeSet<>(boolValues);
+    if (boolSet.size() > 1) {
+      // boolSet={true,false};
+      return null;
+    }
+    return new ArrayList<>(boolSet);
+  }
+
+  /**
+   * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty
+   * list if no values are possible (for dead code). Returns null if any value is possible -- that
+   * is, if no estimate can be made -- and this includes when there is no constant-value annotation
+   * so the argument is null.
+   *
+   * @param stringAnno a {@code @StringVal} annotation, or null
+   * @return the possible values, deduplicated and sorted
+   */
+  public List<String> getStringValues(AnnotationMirror stringAnno) {
+    if (stringAnno == null) {
+      return null;
+    }
+    List<String> list =
+        AnnotationUtils.getElementValueArray(stringAnno, stringValValueElement, String.class);
+    list = CollectionsPlume.withoutDuplicates(list);
+    return list;
+  }
+
+  /**
+   * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty
+   * list if no values are possible (for dead code). Returns null if any value is possible -- that
+   * is, if no estimate can be made -- and this includes when there is no constant-value annotation
+   * so the argument is null.
+   *
+   * @param matchesRegexAnno a {@code @MatchesRegex} annotation, or null
+   * @return the possible values, deduplicated and sorted
+   */
+  public List<String> getMatchesRegexValues(AnnotationMirror matchesRegexAnno) {
+    if (matchesRegexAnno == null) {
+      return null;
+    }
+    List<String> list =
+        AnnotationUtils.getElementValueArray(
+            matchesRegexAnno, matchesRegexValueElement, String.class);
+    list = CollectionsPlume.withoutDuplicates(list);
+    return list;
+  }
+
+  public boolean isIntRange(Set<AnnotationMirror> anmSet) {
+    for (AnnotationMirror anm : anmSet) {
+      if (isIntRange(anm)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if {@code anno} is an {@link IntRange}, {@link IntRangeFromPositive}, {@link
+   * IntRangeFromNonNegative}, or {@link IntRangeFromGTENegativeOne}.
+   *
+   * @param anno annotation mirror
+   * @return true if {@code anno} is an {@link IntRange}, {@link IntRangeFromPositive}, {@link
+   *     IntRangeFromNonNegative}, or {@link IntRangeFromGTENegativeOne}
+   */
+  public boolean isIntRange(AnnotationMirror anno) {
+    String name = AnnotationUtils.annotationName(anno);
+    return name.equals(INTRANGE_NAME)
+        || name.equals(INTRANGE_FROMPOS_NAME)
+        || name.equals(INTRANGE_FROMNONNEG_NAME)
+        || name.equals(INTRANGE_FROMGTENEGONE_NAME);
+  }
+
+  public int getMinLenValue(AnnotatedTypeMirror atm) {
+    return getMinLenValue(atm.getAnnotationInHierarchy(UNKNOWNVAL));
+  }
+
+  /**
+   * Used to find the maximum length of an array. Returns null if there is no minimum length known,
+   * or if the passed annotation is null.
+   */
+  public Integer getMaxLenValue(AnnotationMirror annotation) {
+    if (annotation == null) {
+      return null;
+    }
+    switch (AnnotationUtils.annotationName(annotation)) {
+      case ARRAYLENRANGE_NAME:
+        return Long.valueOf(getRange(annotation).to).intValue();
+      case ARRAYLEN_NAME:
+        return Collections.max(getArrayLength(annotation));
+      case STRINGVAL_NAME:
+        return Collections.max(
+            ValueCheckerUtils.getLengthsForStringValues(getStringValues(annotation)));
+      default:
+        return null;
+    }
+  }
+
+  /**
+   * Finds a minimum length of an array specified by the provided annotation. Returns null if there
+   * is no minimum length known, or if the passed annotation is null.
+   *
+   * <p>Note that this routine handles actual {@link MinLen} annotations, because it is called by
+   * {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}, which transforms
+   * {@link MinLen} annotations into {@link ArrayLenRange} annotations.
+   */
+  private Integer getSpecifiedMinLenValue(AnnotationMirror annotation) {
+    if (annotation == null) {
+      return null;
+    }
+    switch (AnnotationUtils.annotationName(annotation)) {
+      case MINLEN_NAME:
+        return getMinLenValueValue(annotation);
+      case ARRAYLENRANGE_NAME:
+        return Long.valueOf(getRange(annotation).from).intValue();
+      case ARRAYLEN_NAME:
+        return Collections.min(getArrayLength(annotation));
+      case STRINGVAL_NAME:
+        return Collections.min(
+            ValueCheckerUtils.getLengthsForStringValues(getStringValues(annotation)));
+      default:
+        return null;
+    }
+  }
+
+  /**
+   * Used to find the minimum length of an array, which is useful for array bounds checking. Returns
+   * 0 if there is no minimum length known, or if the passed annotation is null.
+   *
+   * <p>Note that this routine handles actual {@link MinLen} annotations, because it is called by
+   * {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}, which transforms
+   * {@link MinLen} annotations into {@link ArrayLenRange} annotations.
+   */
+  public int getMinLenValue(AnnotationMirror annotation) {
+    Integer minLen = getSpecifiedMinLenValue(annotation);
+    if (minLen == null || minLen < 0) {
+      return 0;
+    } else {
+      return minLen;
+    }
+  }
+
+  /**
+   * Returns the minimum length of an array.
+   *
+   * @param annotations the annotations on the array expression
+   * @return the minimum length of an array
+   */
+  public int getMinLenValue(Set<AnnotationMirror> annotations) {
+    int result = 0;
+    for (AnnotationMirror annotation : annotations) {
+      Integer minLen = getSpecifiedMinLenValue(annotation);
+      if (minLen != null) {
+        result = Integer.min(result, minLen);
+      }
+    }
+    if (result < 0) {
+      return 0;
+    } else {
+      return result;
+    }
+  }
+
+  /**
+   * Returns the smallest possible value that an integral annotation might take on. The passed
+   * {@code AnnotatedTypeMirror} should contain either an {@code @IntRange} annotation or an
+   * {@code @IntVal} annotation. Returns null if it does not.
+   *
+   * @param atm annotated type
+   * @return the smallest possible integral for which the {@code atm} could be the type
+   */
+  public Long getMinimumIntegralValue(AnnotatedTypeMirror atm) {
+    AnnotationMirror anm = atm.getAnnotationInHierarchy(UNKNOWNVAL);
+    if (AnnotationUtils.areSameByName(anm, INTVAL_NAME)) {
+      List<Long> possibleValues = getIntValues(anm);
+      return Collections.min(possibleValues);
+    } else if (isIntRange(anm)) {
+      Range range = getRange(anm);
+      return range.from;
+    }
+    return null;
+  }
+
+  /**
+   * Returns the minimum length of an array expression or 0 if the min length is unknown.
+   *
+   * @param sequenceExpression Java expression
+   * @param tree expression tree or variable declaration
+   * @param currentPath path to local scope
+   * @return min length of sequenceExpression or 0
+   */
+  public int getMinLenFromString(String sequenceExpression, Tree tree, TreePath currentPath) {
+    AnnotationMirror lengthAnno;
+    JavaExpression expressionObj;
+    try {
+      expressionObj = parseJavaExpressionString(sequenceExpression, currentPath);
+    } catch (JavaExpressionParseException e) {
+      // ignore parse errors and return 0.
+      return 0;
+    }
+
+    if (expressionObj instanceof ValueLiteral) {
+      ValueLiteral sequenceLiteral = (ValueLiteral) expressionObj;
+      Object sequenceLiteralValue = sequenceLiteral.getValue();
+      if (sequenceLiteralValue instanceof String) {
+        return ((String) sequenceLiteralValue).length();
+      }
+    } else if (expressionObj instanceof ArrayCreation) {
+      ArrayCreation arrayCreation = (ArrayCreation) expressionObj;
+      // This is only expected to support array creations in varargs methods
+      return arrayCreation.getInitializers().size();
+    } else if (expressionObj instanceof ArrayAccess) {
+      List<? extends AnnotationMirror> annoList = expressionObj.getType().getAnnotationMirrors();
+      for (AnnotationMirror anno : annoList) {
+        String ANNO_NAME = AnnotationUtils.annotationName(anno);
+        if (ANNO_NAME.equals(MINLEN_NAME)) {
+          return getMinLenValue(canonicalAnnotation(anno));
+        } else if (ANNO_NAME.equals(ARRAYLEN_NAME) || ANNO_NAME.equals(ARRAYLENRANGE_NAME)) {
+          return getMinLenValue(anno);
+        }
+      }
+    }
+
+    lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, ArrayLenRange.class);
+    if (lengthAnno == null) {
+      lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, ArrayLen.class);
+    }
+    if (lengthAnno == null) {
+      lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, StringVal.class);
+    }
+
+    if (lengthAnno == null) {
+      // Could not find a more precise type, so return 0;
+      return 0;
+    }
+
+    return getMinLenValue(lengthAnno);
+  }
+
+  /**
+   * Returns the annotation type mirror for the type of {@code expressionTree} with default
+   * annotations applied.
+   */
+  @Override
+  public AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) {
+    TypeMirror type = TreeUtils.typeOf(expressionTree);
+    if (type.getKind() != TypeKind.VOID) {
+      AnnotatedTypeMirror atm = type(expressionTree);
+      addDefaultAnnotations(atm);
+      return atm;
+    }
+    return null;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java b/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java
new file mode 100644
index 0000000..664d6fc
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java
@@ -0,0 +1,61 @@
+package org.checkerframework.common.value;
+
+import java.util.LinkedHashSet;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.value.util.Range;
+import org.checkerframework.framework.source.SupportedOptions;
+
+/**
+ * The Constant Value Checker is a constant propagation analysis: for each variable, it determines
+ * whether that variable's value can be known at compile time.
+ *
+ * <p>The Constant Value Checker has no dependencies, but it does trust {@code
+ * org.checkerframework.checker.index.qual.Positive} annotations from the Index Checker. This means
+ * that if the Value Checker is run on code containing {@code Positive} annotations, then the Index
+ * Checker also needs to be run to guarantee soundness.
+ *
+ * @checker_framework.manual #constant-value-checker Constant Value Checker
+ */
+@SupportedOptions({
+  ValueChecker.REPORT_EVAL_WARNS,
+  ValueChecker.IGNORE_RANGE_OVERFLOW,
+  ValueChecker.NON_NULL_STRINGS_CONCATENATION
+})
+public class ValueChecker extends BaseTypeChecker {
+  /**
+   * Command-line option to warn the user if a @StaticallyExecutable method can't load and run at
+   * compile time.
+   */
+  public static final String REPORT_EVAL_WARNS = "reportEvalWarns";
+  /** Command-line option to ignore the possibility of overflow for range annotations. */
+  public static final String IGNORE_RANGE_OVERFLOW = "ignoreRangeOverflow";
+  /** Command-line option that assumes most expressions in String concatenations can be null. */
+  public static final String NON_NULL_STRINGS_CONCATENATION = "nonNullStringsConcatenation";
+
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new ValueVisitor(this);
+  }
+
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    // Don't call super otherwise MethodVal will be added as a subChecker
+    // which creates a circular dependency.
+    return new LinkedHashSet<>();
+  }
+
+  @Override
+  public boolean shouldResolveReflection() {
+    // Because this checker is a subchecker of MethodVal,
+    // reflection can't be resolved.
+    return false;
+  }
+
+  @Override
+  public void typeProcessingOver() {
+    // Reset ignore overflow.
+    Range.ignoreOverflow = false;
+    super.typeProcessingOver();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java b/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java
new file mode 100644
index 0000000..476353c
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java
@@ -0,0 +1,388 @@
+package org.checkerframework.common.value;
+
+import com.sun.source.tree.Tree;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.common.value.qual.StringVal;
+import org.checkerframework.common.value.util.NumberUtils;
+import org.checkerframework.common.value.util.Range;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+
+/** Utility methods for the Value Checker. */
+public class ValueCheckerUtils {
+
+  /** Do not instantiate. */
+  private ValueCheckerUtils() {
+    throw new BugInCF("do not instantiate");
+  }
+
+  /**
+   * Get a list of values of annotation, and then cast them to a given type.
+   *
+   * @param anno the annotation that contains values
+   * @param castTo the type that is casted to
+   * @param atypeFactory the type factory
+   * @return a list of values after the casting
+   */
+  public static List<?> getValuesCastedToType(
+      AnnotationMirror anno, TypeMirror castTo, ValueAnnotatedTypeFactory atypeFactory) {
+    Class<?> castType = TypesUtils.getClassFromType(castTo);
+    List<?> values;
+    switch (AnnotationUtils.annotationName(anno)) {
+      case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME:
+        values = convertDoubleVal(anno, castType, castTo, atypeFactory);
+        break;
+      case ValueAnnotatedTypeFactory.INTVAL_NAME:
+        List<Long> longs = atypeFactory.getIntValues(anno);
+        values = convertIntVal(longs, castType, castTo);
+        break;
+      case ValueAnnotatedTypeFactory.INTRANGE_NAME:
+        Range range = atypeFactory.getRange(anno);
+        List<Long> rangeValues = getValuesFromRange(range, Long.class);
+        values = convertIntVal(rangeValues, castType, castTo);
+        break;
+      case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
+        values = convertStringVal(anno, castType, atypeFactory);
+        break;
+      case ValueAnnotatedTypeFactory.BOOLVAL_NAME:
+        values = convertBoolVal(anno, castType, atypeFactory);
+        break;
+      case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME:
+      case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
+        values = Collections.emptyList();
+        break;
+      default:
+        values = null;
+    }
+    return values;
+  }
+
+  /** Get the minimum and maximum of a list and return a range bounded by them. */
+  public static Range getRangeFromValues(List<? extends Number> values) {
+    if (values == null) {
+      return null;
+    } else if (values.isEmpty()) {
+      return Range.NOTHING;
+    }
+    return Range.create(values);
+  }
+
+  /**
+   * Converts a long value to a boxed numeric type.
+   *
+   * @param value a long value
+   * @param expectedType the boxed numeric type of the result
+   * @return {@code value} converted to {@code expectedType} using standard conversion rules
+   */
+  private static <T> T convertLongToType(long value, Class<T> expectedType) {
+    Object convertedValue;
+    if (expectedType == Integer.class) {
+      convertedValue = (int) value;
+    } else if (expectedType == Short.class) {
+      convertedValue = (short) value;
+    } else if (expectedType == Byte.class) {
+      convertedValue = (byte) value;
+    } else if (expectedType == Long.class) {
+      convertedValue = value;
+    } else if (expectedType == Double.class) {
+      convertedValue = (double) value;
+    } else if (expectedType == Float.class) {
+      convertedValue = (float) value;
+    } else if (expectedType == Character.class) {
+      convertedValue = (char) value;
+    } else {
+      throw new UnsupportedOperationException(
+          "ValueCheckerUtils: unexpected class: " + expectedType);
+    }
+    return expectedType.cast(convertedValue);
+  }
+
+  /**
+   * Get all possible values from the given type and cast them into a boxed primitive type.
+   *
+   * <p>{@code expectedType} must be a boxed type, not a primitive type, because primitive types
+   * cannot be stored in a list.
+   *
+   * @param range the given range
+   * @param expectedType the expected type
+   * @return a list of all the values in the range
+   */
+  public static <T> List<T> getValuesFromRange(Range range, Class<T> expectedType) {
+    if (range == null || range.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) {
+      return null;
+    }
+    if (range.isNothing()) {
+      return Collections.emptyList();
+    }
+
+    // The subtraction does not overflow, because the width has already been checked, so the
+    // bound difference is less than ValueAnnotatedTypeFactory.MAX_VALUES.
+    long boundDifference = range.to - range.from;
+
+    // Each value is computed as a sum of the first value and an offset within the range,
+    // to avoid having range.to as an upper bound of the loop. range.to can be Long.MAX_VALUE,
+    // in which case a comparison value <= range.to would be always true.
+    // boundDifference is always much smaller than Long.MAX_VALUE
+    List<T> values = new ArrayList<>((int) boundDifference + 1);
+    for (long offset = 0; offset <= boundDifference; offset++) {
+      long value = range.from + offset;
+      values.add(convertLongToType(value, expectedType));
+    }
+    return values;
+  }
+
+  private static List<?> convertToStringVal(List<?> origValues) {
+    if (origValues == null) {
+      return null;
+    }
+    return CollectionsPlume.mapList(Object::toString, origValues);
+  }
+
+  /**
+   * Convert the {@code value} argument/element of a @BoolVal annotation into a list.
+   *
+   * @param anno a @BoolVal annotation
+   * @param newClass if String.class, the returned list is a {@code List<String>}
+   * @param atypeFactory the type factory, used for obtaining fields/elements from annotations
+   * @return the {@code value} of a @BoolVal annotation, as a {@code List<Boolean>} or a {@code
+   *     List<String>}
+   */
+  private static List<?> convertBoolVal(
+      AnnotationMirror anno, Class<?> newClass, ValueAnnotatedTypeFactory atypeFactory) {
+    List<Boolean> bools =
+        AnnotationUtils.getElementValueArray(anno, atypeFactory.boolValValueElement, Boolean.class);
+
+    if (newClass == String.class) {
+      return convertToStringVal(bools);
+    }
+    return bools;
+  }
+
+  /**
+   * Convert the {@code value} argument/element of a {@code @StringVal} annotation into a list.
+   *
+   * @param anno a {@code @StringVal} annotation
+   * @param newClass if char[].class, the returned list is a {@code List<char[]>}
+   * @param atypeFactory the type factory, used for obtaining fields/elements from annotations
+   * @return the {@code value} of a {@code @StringVal} annotation, as a {@code List<String>} or a
+   *     {@code List<char[]>}
+   */
+  private static List<?> convertStringVal(
+      AnnotationMirror anno, Class<?> newClass, ValueAnnotatedTypeFactory atypeFactory) {
+    List<String> strings = atypeFactory.getStringValues(anno);
+    if (newClass == char[].class) {
+      return CollectionsPlume.mapList(String::toCharArray, strings);
+    }
+    return strings;
+  }
+
+  private static List<?> convertIntVal(List<Long> longs, Class<?> newClass, TypeMirror newType) {
+    if (longs == null) {
+      return null;
+    }
+    if (newClass == String.class) {
+      return convertToStringVal(longs);
+    } else if (newClass == Character.class || newClass == char.class) {
+      return CollectionsPlume.mapList((Long l) -> (char) l.longValue(), longs);
+    } else if (newClass == Boolean.class) {
+      throw new UnsupportedOperationException(
+          "ValueAnnotatedTypeFactory: can't convert int to boolean");
+    }
+    return NumberUtils.castNumbers(newType, longs);
+  }
+
+  /**
+   * Convert the {@code value} argument/element of a @StringVal annotation into a list.
+   *
+   * @param anno a {@code @DoubleVal} annotation
+   * @param newClass the component type for the returned list
+   * @param newType the component type for the returned list
+   * @param atypeFactory the type factory, used for obtaining fields/elements from annotations
+   * @return the {@code value} of a {@code @DoubleVal} annotation
+   */
+  private static List<?> convertDoubleVal(
+      AnnotationMirror anno,
+      Class<?> newClass,
+      TypeMirror newType,
+      ValueAnnotatedTypeFactory atypeFactory) {
+    List<Double> doubles = atypeFactory.getDoubleValues(anno);
+    if (doubles == null) {
+      return null;
+    }
+    if (newClass == String.class) {
+      return convertToStringVal(doubles);
+    } else if (newClass == Character.class || newClass == char.class) {
+      return CollectionsPlume.mapList((Double l) -> (char) l.doubleValue(), doubles);
+    } else if (newClass == Boolean.class) {
+      throw new UnsupportedOperationException(
+          "ValueAnnotatedTypeFactory: can't convert double to boolean");
+    }
+    return NumberUtils.castNumbers(newType, doubles);
+  }
+
+  /**
+   * Gets a list of lengths for a list of string values.
+   *
+   * @param values list of string values
+   * @return list of unique lengths of strings in {@code values}
+   */
+  public static List<Integer> getLengthsForStringValues(List<String> values) {
+    List<Integer> lengths = CollectionsPlume.mapList(String::length, values);
+    return CollectionsPlume.withoutDuplicates(lengths);
+  }
+
+  /**
+   * Returns a range representing the possible integral values represented by the passed {@code
+   * AnnotatedTypeMirror}. If the passed {@code AnnotatedTypeMirror} does not contain an {@code
+   * IntRange} annotation or an {@code IntVal} annotation, returns null.
+   */
+  public static Range getPossibleValues(
+      AnnotatedTypeMirror valueType, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) {
+    if (valueAnnotatedTypeFactory.isIntRange(valueType.getAnnotations())) {
+      return valueAnnotatedTypeFactory.getRange(valueType.getAnnotation(IntRange.class));
+    } else {
+      List<Long> values =
+          valueAnnotatedTypeFactory.getIntValues(valueType.getAnnotation(IntVal.class));
+      if (values != null) {
+        return Range.create(values);
+      } else {
+        return null;
+      }
+    }
+  }
+
+  /**
+   * Either returns the exact value of the given tree according to the Constant Value Checker, or
+   * null if the exact value is not known. This method should only be used by clients who need
+   * exactly one value -- such as the LBC's binary operator rules -- and not by those that need to
+   * know whether a valueType belongs to a particular qualifier.
+   */
+  public static Long getExactValue(Tree tree, ValueAnnotatedTypeFactory factory) {
+    AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree);
+    Range possibleValues = getPossibleValues(valueType, factory);
+    if (possibleValues != null && possibleValues.from == possibleValues.to) {
+      return possibleValues.from;
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Returns the exact value of an annotated element according to the Constant Value Checker, or
+   * null if the exact value is not known.
+   *
+   * @param element the element to get the exact value from
+   * @param factory ValueAnnotatedTypeFactory used for annotation accessing
+   * @return the exact value of the element if it is constant, or null otherwise
+   */
+  public static Long getExactValue(Element element, ValueAnnotatedTypeFactory factory) {
+    AnnotatedTypeMirror valueType = factory.getAnnotatedType(element);
+    Range possibleValues = getPossibleValues(valueType, factory);
+    if (possibleValues != null && possibleValues.from == possibleValues.to) {
+      return possibleValues.from;
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Either returns the exact string value of the given tree according to the Constant Value
+   * Checker, or null if the exact value is not known. This method should only be used by clients
+   * who need exactly one value and not by those that need to know whether a valueType belongs to a
+   * particular qualifier.
+   */
+  public static String getExactStringValue(Tree tree, ValueAnnotatedTypeFactory factory) {
+    AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree);
+    if (valueType.hasAnnotation(StringVal.class)) {
+      AnnotationMirror valueAnno = valueType.getAnnotation(StringVal.class);
+      List<String> possibleValues =
+          AnnotationUtils.getElementValueArray(
+              valueAnno, factory.stringValValueElement, String.class);
+      if (possibleValues.size() == 1) {
+        return possibleValues.get(0);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Finds the minimum value in a Value Checker type. If there is no information (such as when the
+   * list of possible values is empty or null), returns null. Otherwise, returns the smallest value
+   * in the list of possible values.
+   */
+  public static Long getMinValue(Tree tree, ValueAnnotatedTypeFactory factory) {
+    AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree);
+    Range possibleValues = getPossibleValues(valueType, factory);
+    if (possibleValues != null) {
+      return possibleValues.from;
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Finds the maximum value in a Value Checker type. If there is no information (such as when the
+   * list of possible values is empty or null), returns null. Otherwise, returns the smallest value
+   * in the list of possible values.
+   */
+  public static Long getMaxValue(Tree tree, ValueAnnotatedTypeFactory factory) {
+    AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree);
+    Range possibleValues = getPossibleValues(valueType, factory);
+    if (possibleValues != null) {
+      return possibleValues.to;
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Looks up the minlen of a member select tree. The tree must be an access to a sequence length.
+   */
+  public static Integer getMinLenFromTree(Tree tree, ValueAnnotatedTypeFactory valueATF) {
+    AnnotatedTypeMirror minLenType = valueATF.getAnnotatedType(tree);
+    Long min = valueATF.getMinimumIntegralValue(minLenType);
+    if (min == null) {
+      return null;
+    }
+    if (min < 0 || min > Integer.MAX_VALUE) {
+      min = 0L;
+    }
+    return min.intValue();
+  }
+
+  /**
+   * Queries the Value Checker to determine if there is a known minimum length for the array
+   * represented by {@code tree}. If not, returns 0.
+   */
+  public static int getMinLen(Tree tree, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) {
+    AnnotatedTypeMirror minLenType = valueAnnotatedTypeFactory.getAnnotatedType(tree);
+    return valueAnnotatedTypeFactory.getMinLenValue(minLenType);
+  }
+
+  /**
+   * Optimize the given JavaExpression. See {@link JavaExpressionOptimizer} for more details.
+   *
+   * @param je the expression to optimize
+   * @param factory the annotated type factory
+   * @return an optimized version of the argument
+   */
+  public static JavaExpression optimize(JavaExpression je, AnnotatedTypeFactory factory) {
+    ValueAnnotatedTypeFactory vatf =
+        ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) factory)
+            .getTypeFactoryOfSubchecker(ValueChecker.class);
+    return new JavaExpressionOptimizer(vatf == null ? factory : vatf).convert(je);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java b/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java
new file mode 100644
index 0000000..1db8998
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java
@@ -0,0 +1,129 @@
+package org.checkerframework.common.value;
+
+import com.sun.source.tree.Tree;
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.javacutil.TreeUtils;
+
+/** Stores methods that have special handling in the value checker. */
+class ValueMethodIdentifier {
+
+  /** String.length() method. */
+  private final ExecutableElement lengthMethod;
+  /** Array.getLength() method. */
+  private final ExecutableElement getLengthMethod;
+  /** String.startsWith(String) method. */
+  private final ExecutableElement startsWithMethod;
+  /** String.endsWith(String) method. */
+  private final ExecutableElement endsWithMethod;
+  /** The {@code java.lang.Math#min()} methods. */
+  private final List<ExecutableElement> mathMinMethods;
+  /** The {@code java.lang.Math#max()} methods. */
+  private final List<ExecutableElement> mathMaxMethods;
+  /** Arrays.copyOf() methods. */
+  private final List<ExecutableElement> copyOfMethods;
+
+  /**
+   * Initialize elements with methods that have special handling in the value checker.
+   *
+   * @param processingEnv the processing environment
+   */
+  public ValueMethodIdentifier(ProcessingEnvironment processingEnv) {
+    lengthMethod = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv);
+    getLengthMethod = TreeUtils.getMethod("java.lang.reflect.Array", "getLength", 1, processingEnv);
+    startsWithMethod = TreeUtils.getMethod("java.lang.String", "startsWith", 1, processingEnv);
+    endsWithMethod = TreeUtils.getMethod("java.lang.String", "endsWith", 1, processingEnv);
+    mathMinMethods = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv);
+    mathMaxMethods = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv);
+    copyOfMethods = TreeUtils.getMethods("java.util.Arrays", "copyOf", 2, processingEnv);
+    copyOfMethods.add(TreeUtils.getMethod("java.util.Arrays", "copyOf", 3, processingEnv));
+  }
+
+  /**
+   * Returns true iff the argument is an invocation of Math.min.
+   *
+   * @param methodTree a tree
+   * @param processingEnv the processing environment
+   * @return true iff the argument is an invocation of Math.min
+   */
+  public boolean isMathMin(Tree methodTree, ProcessingEnvironment processingEnv) {
+    return TreeUtils.isMethodInvocation(methodTree, mathMinMethods, processingEnv);
+  }
+
+  /**
+   * Returns true iff the argument is an invocation of Math.max.
+   *
+   * @param methodTree a tree
+   * @param processingEnv the processing environment
+   * @return true iff the argument is an invocation of Math.max
+   */
+  public boolean isMathMax(Tree methodTree, ProcessingEnvironment processingEnv) {
+    return TreeUtils.isMethodInvocation(methodTree, mathMaxMethods, processingEnv);
+  }
+
+  /** Determines whether a tree is an invocation of the {@code String.length()} method. */
+  public boolean isStringLengthInvocation(Tree tree, ProcessingEnvironment processingEnv) {
+    return TreeUtils.isMethodInvocation(tree, lengthMethod, processingEnv);
+  }
+
+  /**
+   * Determines whether a tree is an invocation of the {@code Array.getLength()} method.
+   *
+   * @param tree tree to check
+   * @param processingEnv the processing environment
+   * @return true iff the argument is an invocation of {@code Array.getLength()} method
+   */
+  public boolean isArrayGetLengthInvocation(Tree tree, ProcessingEnvironment processingEnv) {
+    return TreeUtils.isMethodInvocation(tree, getLengthMethod, processingEnv);
+  }
+
+  /**
+   * Determines whether a method is the {@code String.length()} method.
+   *
+   * @param method the element to check
+   * @return true iff the argument methid is {@code String.length()} method
+   */
+  public boolean isStringLengthMethod(ExecutableElement method) {
+    // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden
+    return method.equals(lengthMethod);
+  }
+
+  /**
+   * Determines whether a method is the {@code Array.getLength()} method.
+   *
+   * @param method the element to check
+   * @return true iff the argument method is {@code Array.getLength()} method
+   */
+  public boolean isArrayGetLengthMethod(ExecutableElement method) {
+    // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden
+    return method.equals(getLengthMethod);
+  }
+
+  /**
+   * Determines whether a method is the {@code String.startsWith(String)} method.
+   *
+   * @param method the element to check
+   * @return true iff the argument method is {@code String.startsWith(String)} method
+   */
+  public boolean isStartsWithMethod(ExecutableElement method) {
+    // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden
+    return method.equals(startsWithMethod);
+  }
+  /** Determines whether a method is the {@code String.endsWith(String)} method. */
+  public boolean isEndsWithMethod(ExecutableElement method) {
+    // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden
+    return method.equals(endsWithMethod);
+  }
+
+  /**
+   * Determines whether a tree is an invocation of the {@code Arrays.copyOf()} method.
+   *
+   * @param tree tree to check
+   * @param processingEnv the processing environment
+   * @return true iff the argument is an invocation of {@code Arrays.copyOf()} method
+   */
+  public boolean isArraysCopyOfInvocation(Tree tree, ProcessingEnvironment processingEnv) {
+    return TreeUtils.isMethodInvocation(tree, copyOfMethods, processingEnv);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java
new file mode 100644
index 0000000..d4007c2
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java
@@ -0,0 +1,481 @@
+package org.checkerframework.common.value;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.regex.qual.Regex;
+import org.checkerframework.common.value.util.Range;
+import org.checkerframework.framework.type.ElementQualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.SystemUtil;
+
+/** The qualifier hierarchy for the Value type system. */
+final class ValueQualifierHierarchy extends ElementQualifierHierarchy {
+
+  /** The type factory to use. */
+  private final ValueAnnotatedTypeFactory atypeFactory;
+
+  /**
+   * Creates a ValueQualifierHierarchy from the given classes.
+   *
+   * @param atypeFactory ValueAnnotatedTypeFactory
+   * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+   */
+  ValueQualifierHierarchy(
+      ValueAnnotatedTypeFactory atypeFactory,
+      Collection<Class<? extends Annotation>> qualifierClasses) {
+    super(qualifierClasses, atypeFactory.getElementUtils());
+    this.atypeFactory = atypeFactory;
+  }
+
+  /**
+   * Computes greatest lower bound of a @StringVal annotation with another Value Checker annotation.
+   *
+   * @param stringValAnno annotation of type @StringVal
+   * @param otherAnno annotation from the value checker hierarchy
+   * @return greatest lower bound of {@code stringValAnno} and {@code otherAnno}
+   */
+  private AnnotationMirror glbOfStringVal(
+      AnnotationMirror stringValAnno, AnnotationMirror otherAnno) {
+    List<String> values = atypeFactory.getStringValues(stringValAnno);
+    switch (AnnotationUtils.annotationName(otherAnno)) {
+      case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
+        // Intersection of value lists
+        List<String> otherValues = atypeFactory.getStringValues(otherAnno);
+        values.retainAll(otherValues);
+        break;
+      case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
+        // Retain strings of correct lengths
+        List<Integer> otherLengths = atypeFactory.getArrayLength(otherAnno);
+        ArrayList<String> result = new ArrayList<>();
+        for (String s : values) {
+          if (otherLengths.contains(s.length())) {
+            result.add(s);
+          }
+        }
+        values = result;
+        break;
+      case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME:
+        // Retain strings of lengths from a range
+        Range otherRange = atypeFactory.getRange(otherAnno);
+        ArrayList<String> range = new ArrayList<>();
+        for (String s : values) {
+          if (otherRange.contains(s.length())) {
+            range.add(s);
+          }
+        }
+        values = range;
+        break;
+      case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME:
+        List<@Regex String> regexes =
+            AnnotationUtils.getElementValueArray(
+                otherAnno, atypeFactory.matchesRegexValueElement, String.class);
+        values =
+            values.stream()
+                .filter(value -> regexes.stream().anyMatch(value::matches))
+                .collect(Collectors.toList());
+        break;
+      default:
+        return atypeFactory.BOTTOMVAL;
+    }
+
+    return atypeFactory.createStringAnnotation(values);
+  }
+
+  @Override
+  public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
+    if (isSubtype(a1, a2)) {
+      return a1;
+    } else if (isSubtype(a2, a1)) {
+      return a2;
+    } else {
+
+      // Implementation of GLB where one of the annotations is StringVal is needed for
+      // length-based refinement of constant string values. Other cases of length-based
+      // refinement are handled by subtype check.
+      if (AnnotationUtils.areSameByName(a1, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) {
+        return glbOfStringVal(a1, a2);
+      } else if (AnnotationUtils.areSameByName(a2, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) {
+        return glbOfStringVal(a2, a1);
+      }
+
+      // Simply return BOTTOMVAL in other cases. Refine this if we discover use cases
+      // that need a more precise GLB.
+      return atypeFactory.BOTTOMVAL;
+    }
+  }
+
+  @Override
+  public int numberOfIterationsBeforeWidening() {
+    return ValueAnnotatedTypeFactory.MAX_VALUES + 1;
+  }
+
+  @Override
+  public AnnotationMirror widenedUpperBound(
+      AnnotationMirror newQualifier, AnnotationMirror previousQualifier) {
+    AnnotationMirror lub = leastUpperBound(newQualifier, previousQualifier);
+    if (AnnotationUtils.areSameByName(lub, ValueAnnotatedTypeFactory.INTRANGE_NAME)) {
+      Range lubRange = atypeFactory.getRange(lub);
+      Range newRange = atypeFactory.getRange(newQualifier);
+      Range oldRange = atypeFactory.getRange(previousQualifier);
+      Range wubRange = widenedRange(newRange, oldRange, lubRange);
+      return atypeFactory.createIntRangeAnnotation(wubRange);
+    } else if (AnnotationUtils.areSameByName(lub, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) {
+      Range lubRange = atypeFactory.getRange(lub);
+      Range newRange = atypeFactory.getRange(newQualifier);
+      Range oldRange = atypeFactory.getRange(previousQualifier);
+      Range wubRange = widenedRange(newRange, oldRange, lubRange);
+      return atypeFactory.createArrayLenRangeAnnotation(wubRange);
+    } else {
+      return lub;
+    }
+  }
+
+  /**
+   * Determine the widened range from other ranges.
+   *
+   * @param newRange the new range
+   * @param oldRange the old range
+   * @param lubRange the LUB range
+   * @return the widened range
+   */
+  private Range widenedRange(Range newRange, Range oldRange, Range lubRange) {
+    if (newRange == null || oldRange == null || lubRange.equals(oldRange)) {
+      return lubRange;
+    }
+    // If both bounds of the new range are bigger than the old range, then returned range
+    // should use the lower bound of the new range and a MAX_VALUE.
+    if ((newRange.from >= oldRange.from && newRange.to >= oldRange.to)) {
+      long max = lubRange.to;
+      if (max < Byte.MAX_VALUE) {
+        max = Byte.MAX_VALUE;
+      } else if (max < Short.MAX_VALUE) {
+        max = Short.MAX_VALUE;
+      } else if (max < Integer.MAX_VALUE) {
+        max = Integer.MAX_VALUE;
+      } else {
+        max = Long.MAX_VALUE;
+      }
+      return Range.create(newRange.from, max);
+    }
+
+    // If both bounds of the old range are bigger than the new range, then returned range
+    // should use a MIN_VALUE and the upper bound of the new range.
+    if ((newRange.from <= oldRange.from && newRange.to <= oldRange.to)) {
+      long min = lubRange.from;
+      if (min > Byte.MIN_VALUE) {
+        min = Byte.MIN_VALUE;
+      } else if (min > Short.MIN_VALUE) {
+        min = Short.MIN_VALUE;
+      } else if (min > Integer.MIN_VALUE) {
+        min = Integer.MIN_VALUE;
+      } else {
+        min = Long.MIN_VALUE;
+      }
+      return Range.create(min, newRange.to);
+    }
+
+    if (lubRange.isWithin(Byte.MIN_VALUE + 1, Byte.MAX_VALUE)
+        || lubRange.isWithin(Byte.MIN_VALUE, Byte.MAX_VALUE - 1)) {
+      return Range.BYTE_EVERYTHING;
+    } else if (lubRange.isWithin(Short.MIN_VALUE + 1, Short.MAX_VALUE)
+        || lubRange.isWithin(Short.MIN_VALUE, Short.MAX_VALUE - 1)) {
+      return Range.SHORT_EVERYTHING;
+    } else if (lubRange.isWithin(Long.MIN_VALUE + 1, Long.MAX_VALUE)
+        || lubRange.isWithin(Long.MIN_VALUE, Long.MAX_VALUE - 1)) {
+      return Range.INT_EVERYTHING;
+    } else {
+      return Range.EVERYTHING;
+    }
+  }
+
+  /**
+   * Determines the least upper bound of a1 and a2, which contains the union of their sets of
+   * possible values.
+   *
+   * @return the least upper bound of a1 and a2
+   */
+  @Override
+  public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
+    if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) {
+      // The annotations are in different hierarchies
+      return null;
+    }
+
+    a1 = atypeFactory.convertSpecialIntRangeToStandardIntRange(a1);
+    a2 = atypeFactory.convertSpecialIntRangeToStandardIntRange(a2);
+
+    if (isSubtype(a1, a2)) {
+      return a2;
+    } else if (isSubtype(a2, a1)) {
+      return a1;
+    }
+    String qual1 = AnnotationUtils.annotationName(a1);
+    String qual2 = AnnotationUtils.annotationName(a2);
+
+    if (qual1.equals(qual2)) {
+      // If both are the same type, determine the type and merge
+      switch (qual1) {
+        case ValueAnnotatedTypeFactory.INTRANGE_NAME:
+          // special handling for IntRange
+          Range intrange1 = atypeFactory.getRange(a1);
+          Range intrange2 = atypeFactory.getRange(a2);
+          return atypeFactory.createIntRangeAnnotation(intrange1.union(intrange2));
+        case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME:
+          // special handling for ArrayLenRange
+          Range range1 = atypeFactory.getRange(a1);
+          Range range2 = atypeFactory.getRange(a2);
+          return atypeFactory.createArrayLenRangeAnnotation(range1.union(range2));
+        case ValueAnnotatedTypeFactory.INTVAL_NAME:
+          List<Long> longs = atypeFactory.getIntValues(a1);
+          SystemUtil.addWithoutDuplicates(longs, atypeFactory.getIntValues(a2));
+          return atypeFactory.createIntValAnnotation(longs);
+        case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
+          List<Integer> arrayLens = atypeFactory.getArrayLength(a1);
+          SystemUtil.addWithoutDuplicates(arrayLens, atypeFactory.getArrayLength(a2));
+          return atypeFactory.createArrayLenAnnotation(arrayLens);
+        case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
+          List<String> strings = atypeFactory.getStringValues(a1);
+          SystemUtil.addWithoutDuplicates(strings, atypeFactory.getStringValues(a2));
+          return atypeFactory.createStringAnnotation(strings);
+        case ValueAnnotatedTypeFactory.BOOLVAL_NAME:
+          List<Boolean> bools = atypeFactory.getBooleanValues(a1);
+          SystemUtil.addWithoutDuplicates(bools, atypeFactory.getBooleanValues(a2));
+          return atypeFactory.createBooleanAnnotation(bools);
+        case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME:
+          List<Double> doubles = atypeFactory.getDoubleValues(a1);
+          SystemUtil.addWithoutDuplicates(doubles, atypeFactory.getDoubleValues(a2));
+          return atypeFactory.createDoubleAnnotation(doubles);
+        case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME:
+          List<@Regex String> regexes = atypeFactory.getMatchesRegexValues(a1);
+          SystemUtil.addWithoutDuplicates(regexes, atypeFactory.getMatchesRegexValues(a2));
+          return atypeFactory.createMatchesRegexAnnotation(regexes);
+        default:
+          throw new BugInCF("default case: %s %s %s%n", qual1, a1, a2);
+      }
+    }
+
+    // Special handling for dealing with the lub of two annotations that are distinct but
+    // convertible (e.g. a StringVal and a MatchesRegex, or an IntVal and an IntRange).
+    // Each of these variables is an annotation of the given type, or is null if neither of
+    // the arguments to leastUpperBound is of the given types.
+    AnnotationMirror arrayLenAnno = null;
+    AnnotationMirror arrayLenRangeAnno = null;
+    AnnotationMirror stringValAnno = null;
+    AnnotationMirror matchesRegexAnno = null;
+    AnnotationMirror intValAnno = null;
+    AnnotationMirror intRangeAnno = null;
+    AnnotationMirror doubleValAnno = null;
+
+    switch (qual1) {
+      case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
+        arrayLenAnno = a1;
+        break;
+      case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME:
+        arrayLenRangeAnno = a1;
+        break;
+      case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
+        stringValAnno = a1;
+        break;
+      case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME:
+        matchesRegexAnno = a1;
+        break;
+      case ValueAnnotatedTypeFactory.INTVAL_NAME:
+        intValAnno = a1;
+        break;
+      case ValueAnnotatedTypeFactory.INTRANGE_NAME:
+        intRangeAnno = a1;
+        break;
+      case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME:
+        doubleValAnno = a1;
+        break;
+      default:
+        // Do nothing
+    }
+
+    switch (qual2) {
+      case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
+        arrayLenAnno = a2;
+        break;
+      case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME:
+        arrayLenRangeAnno = a2;
+        break;
+      case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
+        stringValAnno = a2;
+        break;
+      case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME:
+        matchesRegexAnno = a2;
+        break;
+      case ValueAnnotatedTypeFactory.INTVAL_NAME:
+        intValAnno = a2;
+        break;
+      case ValueAnnotatedTypeFactory.INTRANGE_NAME:
+        intRangeAnno = a2;
+        break;
+      case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME:
+        doubleValAnno = a2;
+        break;
+      default:
+        // Do nothing
+    }
+
+    // Special handling for dealing with the lub of an ArrayLenRange and an ArrayLen,
+    // a StringVal with one of them, or a StringVal and a MatchesRegex.
+    if (arrayLenAnno != null && arrayLenRangeAnno != null) {
+      return leastUpperBound(
+          arrayLenRangeAnno, atypeFactory.convertArrayLenToArrayLenRange(arrayLenAnno));
+    } else if (stringValAnno != null && arrayLenAnno != null) {
+      return leastUpperBound(arrayLenAnno, atypeFactory.convertStringValToArrayLen(stringValAnno));
+    } else if (stringValAnno != null && arrayLenRangeAnno != null) {
+      return leastUpperBound(
+          arrayLenRangeAnno, atypeFactory.convertStringValToArrayLenRange(stringValAnno));
+    } else if (stringValAnno != null && matchesRegexAnno != null) {
+      return leastUpperBound(
+          matchesRegexAnno, atypeFactory.convertStringValToMatchesRegex(stringValAnno));
+    }
+
+    // Annotations are both in the same hierarchy, but they are not the same.
+    // If a1 and a2 are not the same type of *Value annotation, they may still be mergeable
+    // because some values can be implicitly cast as others. For example, if a1 and a2 are
+    // both in {DoubleVal, IntVal} then they will be converted upwards: IntVal -> DoubleVal
+    // to arrive at a common annotation type.
+
+    if (doubleValAnno != null) {
+      if (intRangeAnno != null) {
+        intValAnno = atypeFactory.convertIntRangeToIntVal(intRangeAnno);
+        if (AnnotationUtils.areSameByName(intValAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) {
+          intValAnno = null;
+        }
+      }
+      if (intValAnno != null) {
+        // Convert intValAnno to a @DoubleVal AnnotationMirror
+        AnnotationMirror doubleValAnno2 = atypeFactory.convertIntValToDoubleVal(intValAnno);
+        return leastUpperBound(doubleValAnno, doubleValAnno2);
+      }
+      return atypeFactory.UNKNOWNVAL;
+    }
+    if (intRangeAnno != null && intValAnno != null) {
+      // Convert intValAnno to an @IntRange AnnotationMirror
+      AnnotationMirror intRangeAnno2 = atypeFactory.convertIntValToIntRange(intValAnno);
+      return leastUpperBound(intRangeAnno, intRangeAnno2);
+    }
+
+    // In all other cases, the LUB is UnknownVal.
+    return atypeFactory.UNKNOWNVAL;
+  }
+
+  /**
+   * Computes subtyping as per the subtyping in the qualifier hierarchy structure unless both
+   * annotations are Value. In this case, subAnno is a subtype of superAnno iff superAnno contains
+   * at least every element of subAnno.
+   *
+   * @return true if subAnno is a subtype of superAnno, false otherwise
+   */
+  @Override
+  public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+    subAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(subAnno);
+    superAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(superAnno);
+    String subQual = AnnotationUtils.annotationName(subAnno);
+    if (subQual.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) {
+      superAnno = atypeFactory.convertToUnknown(superAnno);
+    }
+    String superQual = AnnotationUtils.annotationName(superAnno);
+    if (superQual.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)
+        || subQual.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
+      return true;
+    } else if (superQual.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)
+        || subQual.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) {
+      return false;
+    } else if (superQual.equals(ValueAnnotatedTypeFactory.POLY_NAME)) {
+      return subQual.equals(ValueAnnotatedTypeFactory.POLY_NAME);
+    } else if (subQual.equals(ValueAnnotatedTypeFactory.POLY_NAME)) {
+      return false;
+    } else if (superQual.equals(subQual)) {
+      // Same type, so might be subtype
+      if (subQual.equals(ValueAnnotatedTypeFactory.INTRANGE_NAME)
+          || subQual.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) {
+        // Special case for range-based annotations
+        Range superRange = atypeFactory.getRange(superAnno);
+        Range subRange = atypeFactory.getRange(subAnno);
+        return superRange.contains(subRange);
+      } else {
+        // The annotations are one of: ArrayLen, BoolVal, DoubleVal, EnumVal, StringVal.
+        @SuppressWarnings("deprecation") // concrete annotation class is not known
+        List<Object> superValues =
+            AnnotationUtils.getElementValueArray(superAnno, "value", Object.class, false);
+        @SuppressWarnings("deprecation") // concrete annotation class is not known
+        List<Object> subValues =
+            AnnotationUtils.getElementValueArray(subAnno, "value", Object.class, false);
+        return superValues.containsAll(subValues);
+      }
+    }
+    switch (superQual + subQual) {
+      case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME + ValueAnnotatedTypeFactory.INTVAL_NAME:
+        List<Double> superValues = atypeFactory.getDoubleValues(superAnno);
+        List<Double> subValues =
+            atypeFactory.convertLongListToDoubleList(atypeFactory.getIntValues(subAnno));
+        return superValues.containsAll(subValues);
+      case ValueAnnotatedTypeFactory.INTRANGE_NAME + ValueAnnotatedTypeFactory.INTVAL_NAME:
+      case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME + ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
+        Range superRange = atypeFactory.getRange(superAnno);
+        List<Long> subLongValues = atypeFactory.getArrayLenOrIntValue(subAnno);
+        Range subLongRange = Range.create(subLongValues);
+        return superRange.contains(subLongRange);
+      case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME + ValueAnnotatedTypeFactory.INTRANGE_NAME:
+        Range subRange = atypeFactory.getRange(subAnno);
+        if (subRange.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) {
+          return false;
+        }
+        List<Double> superDoubleValues = atypeFactory.getDoubleValues(superAnno);
+        List<Double> subDoubleValues = ValueCheckerUtils.getValuesFromRange(subRange, Double.class);
+        return superDoubleValues.containsAll(subDoubleValues);
+      case ValueAnnotatedTypeFactory.INTVAL_NAME + ValueAnnotatedTypeFactory.INTRANGE_NAME:
+      case ValueAnnotatedTypeFactory.ARRAYLEN_NAME + ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME:
+        Range subRange2 = atypeFactory.getRange(subAnno);
+        if (subRange2.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) {
+          return false;
+        }
+        List<Long> superValues2 = atypeFactory.getArrayLenOrIntValue(superAnno);
+        List<Long> subValues2 = ValueCheckerUtils.getValuesFromRange(subRange2, Long.class);
+        return superValues2.containsAll(subValues2);
+      case ValueAnnotatedTypeFactory.STRINGVAL_NAME + ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME:
+      case ValueAnnotatedTypeFactory.STRINGVAL_NAME + ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
+
+        // Allow @ArrayLen(0) to be converted to @StringVal("")
+        List<String> superStringValues = atypeFactory.getStringValues(superAnno);
+        return superStringValues.contains("") && atypeFactory.getMaxLenValue(subAnno) == 0;
+      case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME + ValueAnnotatedTypeFactory.STRINGVAL_NAME:
+        List<String> strings = atypeFactory.getStringValues(subAnno);
+        List<String> regexes =
+            AnnotationUtils.getElementValueArray(
+                superAnno, atypeFactory.matchesRegexValueElement, String.class);
+        return strings.stream().allMatch(string -> regexes.stream().anyMatch(string::matches));
+      case ValueAnnotatedTypeFactory.ARRAYLEN_NAME + ValueAnnotatedTypeFactory.STRINGVAL_NAME:
+        // StringVal is a subtype of ArrayLen, if all the strings have one of the correct lengths.
+        List<Integer> superIntValues = atypeFactory.getArrayLength(superAnno);
+        List<String> subStringValues = atypeFactory.getStringValues(subAnno);
+        for (String value : subStringValues) {
+          if (!superIntValues.contains(value.length())) {
+            return false;
+          }
+        }
+        return true;
+      case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME + ValueAnnotatedTypeFactory.STRINGVAL_NAME:
+        // StringVal is a subtype of ArrayLenRange, if all the strings have a length in the range.
+        Range superRange2 = atypeFactory.getRange(superAnno);
+        List<String> subValues3 = atypeFactory.getStringValues(subAnno);
+        for (String value : subValues3) {
+          if (!superRange2.contains(value.length())) {
+            return false;
+          }
+        }
+        return true;
+      default:
+        return false;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java b/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java
new file mode 100644
index 0000000..6e0a275
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java
@@ -0,0 +1,1671 @@
+package org.checkerframework.common.value;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.Tree;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.ArrayLenRange;
+import org.checkerframework.common.value.qual.StringVal;
+import org.checkerframework.common.value.util.NumberMath;
+import org.checkerframework.common.value.util.NumberUtils;
+import org.checkerframework.common.value.util.Range;
+import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
+import org.checkerframework.dataflow.analysis.RegularTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.BitwiseAndNode;
+import org.checkerframework.dataflow.cfg.node.BitwiseComplementNode;
+import org.checkerframework.dataflow.cfg.node.BitwiseOrNode;
+import org.checkerframework.dataflow.cfg.node.BitwiseXorNode;
+import org.checkerframework.dataflow.cfg.node.ConditionalAndNode;
+import org.checkerframework.dataflow.cfg.node.ConditionalNotNode;
+import org.checkerframework.dataflow.cfg.node.ConditionalOrNode;
+import org.checkerframework.dataflow.cfg.node.EqualToNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.FloatingDivisionNode;
+import org.checkerframework.dataflow.cfg.node.FloatingRemainderNode;
+import org.checkerframework.dataflow.cfg.node.GreaterThanNode;
+import org.checkerframework.dataflow.cfg.node.GreaterThanOrEqualNode;
+import org.checkerframework.dataflow.cfg.node.IntegerDivisionNode;
+import org.checkerframework.dataflow.cfg.node.IntegerRemainderNode;
+import org.checkerframework.dataflow.cfg.node.LeftShiftNode;
+import org.checkerframework.dataflow.cfg.node.LessThanNode;
+import org.checkerframework.dataflow.cfg.node.LessThanOrEqualNode;
+import org.checkerframework.dataflow.cfg.node.MethodAccessNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.NotEqualNode;
+import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode;
+import org.checkerframework.dataflow.cfg.node.NumericalMinusNode;
+import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode;
+import org.checkerframework.dataflow.cfg.node.NumericalPlusNode;
+import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
+import org.checkerframework.dataflow.cfg.node.SignedRightShiftNode;
+import org.checkerframework.dataflow.cfg.node.StringConcatenateAssignmentNode;
+import org.checkerframework.dataflow.cfg.node.StringConcatenateNode;
+import org.checkerframework.dataflow.cfg.node.StringConversionNode;
+import org.checkerframework.dataflow.cfg.node.StringLiteralNode;
+import org.checkerframework.dataflow.cfg.node.UnsignedRightShiftNode;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.Unknown;
+import org.checkerframework.dataflow.util.NodeUtils;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+
+/** The transfer class for the Value Checker. */
+public class ValueTransfer extends CFTransfer {
+  /** The Value type factory. */
+  protected final ValueAnnotatedTypeFactory atypeFactory;
+  /** The Value qualifier hierarchy. */
+  protected final QualifierHierarchy hierarchy;
+
+  /**
+   * Create a new ValueTransfer.
+   *
+   * @param analysis the corresponding analysis
+   */
+  public ValueTransfer(CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
+    super(analysis);
+    atypeFactory = (ValueAnnotatedTypeFactory) analysis.getTypeFactory();
+    hierarchy = atypeFactory.getQualifierHierarchy();
+  }
+
+  /** Returns a range of possible lengths for an integer from a range, as casted to a String. */
+  private Range getIntRangeStringLengthRange(Node subNode, TransferInput<CFValue, CFStore> p) {
+    Range valueRange = getIntRange(subNode, p);
+
+    // Get lengths of the bounds
+    int fromLength = Long.toString(valueRange.from).length();
+    int toLength = Long.toString(valueRange.to).length();
+
+    int lowerLength = Math.min(fromLength, toLength);
+    // In case the range contains 0, the minimum length is 1 even if both bounds are longer
+    if (valueRange.contains(0)) {
+      lowerLength = 1;
+    }
+
+    int upperLength = Math.max(fromLength, toLength);
+
+    return Range.create(lowerLength, upperLength);
+  }
+
+  /**
+   * Returns a range of possible lengths for {@code subNode}, as casted to a String.
+   *
+   * @param subNode some subnode of {@code p}
+   * @param p TransferInput
+   * @return a range of possible lengths for {@code subNode}, as casted to a String
+   */
+  private Range getStringLengthRange(Node subNode, TransferInput<CFValue, CFStore> p) {
+    CFValue value = p.getValueOfSubNode(subNode);
+
+    AnnotationMirror anno = getValueAnnotation(value);
+    if (anno == null) {
+      return null;
+    }
+    String annoName = AnnotationUtils.annotationName(anno);
+    if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) {
+      return atypeFactory.getRange(anno);
+    } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
+      return Range.NOTHING;
+    }
+
+    TypeKind subNodeTypeKind = subNode.getType().getKind();
+
+    // handle values converted to string (ints, longs, longs with @IntRange)
+    if (subNode instanceof StringConversionNode) {
+      return getStringLengthRange(((StringConversionNode) subNode).getOperand(), p);
+    } else if (isIntRange(subNode, p)) {
+      return getIntRangeStringLengthRange(subNode, p);
+    } else if (subNodeTypeKind == TypeKind.INT) {
+      // ints are between 1 and 11 characters long
+      return Range.create(1, 11);
+    } else if (subNodeTypeKind == TypeKind.LONG) {
+      // longs are between 1 and 20 characters long
+      return Range.create(1, 20);
+    }
+
+    return Range.create(0, Integer.MAX_VALUE);
+  }
+
+  /**
+   * Returns a list of possible lengths for {@code subNode}, as casted to a String. Returns null if
+   * {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is
+   * bottom.
+   */
+  private List<Integer> getStringLengths(Node subNode, TransferInput<CFValue, CFStore> p) {
+
+    CFValue value = p.getValueOfSubNode(subNode);
+    AnnotationMirror anno = getValueAnnotation(value);
+    if (anno == null) {
+      return null;
+    }
+    String annoName = AnnotationUtils.annotationName(anno);
+    if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) {
+      return atypeFactory.getArrayLength(anno);
+    } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
+      return Collections.emptyList();
+    }
+
+    TypeKind subNodeTypeKind = subNode.getType().getKind();
+
+    // handle values converted to string (characters, bytes, shorts, ints with @IntRange)
+    if (subNode instanceof StringConversionNode) {
+      return getStringLengths(((StringConversionNode) subNode).getOperand(), p);
+    } else if (subNodeTypeKind == TypeKind.CHAR) {
+      // characters always have length 1
+      return Collections.singletonList(1);
+    } else if (isIntRange(subNode, p)) {
+      // Try to get a list of lengths from a range of integer values converted to string @IntVal is
+      // not checked for, because if it is present, we would already have the actual string values
+      Range lengthRange = getIntRangeStringLengthRange(subNode, p);
+      return ValueCheckerUtils.getValuesFromRange(lengthRange, Integer.class);
+    } else if (subNodeTypeKind == TypeKind.BYTE) {
+      // bytes are between 1 and 4 characters long
+      return ValueCheckerUtils.getValuesFromRange(Range.create(1, 4), Integer.class);
+    } else if (subNodeTypeKind == TypeKind.SHORT) {
+      // shorts are between 1 and 6 characters long
+      return ValueCheckerUtils.getValuesFromRange(Range.create(1, 6), Integer.class);
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Returns a list of possible values for {@code subNode}, as casted to a String. Returns null if
+   * {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is
+   * bottom.
+   *
+   * @param subNode a subNode of p
+   * @param p TransferInput
+   * @return a list of possible values for {@code subNode} or null
+   */
+  private List<String> getStringValues(Node subNode, TransferInput<CFValue, CFStore> p) {
+    CFValue value = p.getValueOfSubNode(subNode);
+    AnnotationMirror anno = getValueAnnotation(value);
+    if (anno == null) {
+      return null;
+    }
+    String annoName = AnnotationUtils.annotationName(anno);
+    switch (annoName) {
+      case ValueAnnotatedTypeFactory.UNKNOWN_NAME:
+        return null;
+      case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME:
+        return Collections.emptyList();
+      case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
+        return atypeFactory.getStringValues(anno);
+      default:
+        // Do nothing.
+    }
+
+    // @IntVal, @IntRange, @DoubleVal, @BoolVal (have to be converted to string)
+    List<? extends Object> values;
+    if (annoName.equals(ValueAnnotatedTypeFactory.BOOLVAL_NAME)) {
+      values = getBooleanValues(subNode, p);
+    } else if (subNode.getType().getKind() == TypeKind.CHAR) {
+      values = getCharValues(subNode, p);
+    } else if (subNode instanceof StringConversionNode) {
+      return getStringValues(((StringConversionNode) subNode).getOperand(), p);
+    } else if (isIntRange(subNode, p)) {
+      Range range = getIntRange(subNode, p);
+      List<Long> longValues = ValueCheckerUtils.getValuesFromRange(range, Long.class);
+      values = NumberUtils.castNumbers(subNode.getType(), longValues);
+    } else {
+      values = getNumericalValues(subNode, p);
+    }
+    if (values == null) {
+      return null;
+    }
+    List<String> stringValues = CollectionsPlume.mapList(Object::toString, values);
+    // Empty list means bottom value
+    return stringValues.isEmpty() ? Collections.singletonList("null") : stringValues;
+  }
+
+  /**
+   * Create a @BoolVal CFValue for the given boolean value.
+   *
+   * @param value the value for the @BoolVal annotation
+   * @return a @BoolVal CFValue for the given boolean value
+   */
+  private CFValue createBooleanCFValue(boolean value) {
+    return analysis.createSingleAnnotationValue(
+        value ? atypeFactory.BOOLEAN_TRUE : atypeFactory.BOOLEAN_FALSE,
+        atypeFactory.types.getPrimitiveType(TypeKind.BOOLEAN));
+  }
+
+  /**
+   * Get the unique possible boolean value from @BoolVal. Returns null if that is not the case
+   * (including if the CFValue is not @BoolVal).
+   *
+   * @param value a CFValue
+   * @return theboolean if {@code value} represents a single boolean value; otherwise null
+   */
+  private Boolean getBooleanValue(CFValue value) {
+    AnnotationMirror boolAnno =
+        AnnotationUtils.getAnnotationByName(
+            value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME);
+    return atypeFactory.getBooleanValue(boolAnno);
+  }
+
+  /**
+   * Get possible boolean values for a node. Returns null if there is no estimate, because the
+   * node's value is not @BoolVal.
+   *
+   * @param subNode the node whose value to obtain
+   * @param p the transfer input in which to look up values
+   * @return the possible boolean values for the node
+   */
+  private List<Boolean> getBooleanValues(Node subNode, TransferInput<CFValue, CFStore> p) {
+    CFValue value = p.getValueOfSubNode(subNode);
+    AnnotationMirror intAnno =
+        AnnotationUtils.getAnnotationByName(
+            value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME);
+    return atypeFactory.getBooleanValues(intAnno);
+  }
+
+  /** Get possible char values from annotation @IntRange or @IntVal. */
+  private List<Character> getCharValues(Node subNode, TransferInput<CFValue, CFStore> p) {
+    CFValue value = p.getValueOfSubNode(subNode);
+    AnnotationMirror intAnno;
+
+    intAnno =
+        AnnotationUtils.getAnnotationByName(
+            value.getAnnotations(), ValueAnnotatedTypeFactory.INTVAL_NAME);
+    if (intAnno != null) {
+      return atypeFactory.getCharValues(intAnno);
+    }
+
+    if (atypeFactory.isIntRange(value.getAnnotations())) {
+      intAnno =
+          hierarchy.findAnnotationInHierarchy(value.getAnnotations(), atypeFactory.UNKNOWNVAL);
+      Range range = atypeFactory.getRange(intAnno);
+      return ValueCheckerUtils.getValuesFromRange(range, Character.class);
+    }
+
+    return Collections.emptyList();
+  }
+
+  private AnnotationMirror getValueAnnotation(Node subNode, TransferInput<CFValue, CFStore> p) {
+    CFValue value = p.getValueOfSubNode(subNode);
+    return getValueAnnotation(value);
+  }
+
+  /**
+   * Extract the Value Checker annotation from a CFValue object.
+   *
+   * @param cfValue a CFValue object
+   * @return the Value Checker annotation within cfValue
+   */
+  private AnnotationMirror getValueAnnotation(CFValue cfValue) {
+    return hierarchy.findAnnotationInHierarchy(cfValue.getAnnotations(), atypeFactory.UNKNOWNVAL);
+  }
+
+  /**
+   * Returns a list of possible values, or null if no estimate is available and any value is
+   * possible.
+   */
+  private List<? extends Number> getNumericalValues(
+      Node subNode, TransferInput<CFValue, CFStore> p) {
+    AnnotationMirror valueAnno = getValueAnnotation(subNode, p);
+    return getNumericalValues(subNode, valueAnno);
+  }
+
+  /**
+   * Returns the numerical values in valueAnno casted to the type of subNode.
+   *
+   * @param subNode node
+   * @param valueAnno annotation mirror
+   * @return the numerical values in valueAnno casted to the type of subNode
+   */
+  private List<? extends Number> getNumericalValues(Node subNode, AnnotationMirror valueAnno) {
+
+    if (valueAnno == null
+        || AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) {
+      return null;
+    } else if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
+      return Collections.emptyList();
+    }
+    List<? extends Number> values;
+    if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) {
+      values = atypeFactory.getIntValues(valueAnno);
+    } else if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) {
+      values = atypeFactory.getDoubleValues(valueAnno);
+    } else {
+      return null;
+    }
+    return NumberUtils.castNumbers(subNode.getType(), values);
+  }
+
+  /** Get possible integer range from annotation. */
+  private Range getIntRange(Node subNode, TransferInput<CFValue, CFStore> p) {
+    AnnotationMirror val = getValueAnnotation(subNode, p);
+    return getIntRangeFromAnnotation(subNode, val);
+  }
+
+  /**
+   * Returns the {@link Range} object corresponding to the annotation {@code val} casted to the type
+   * of {@code node}.
+   *
+   * @param node a node
+   * @param val annotation mirror
+   * @return the {@link Range} object corresponding to the annotation {@code val} casted to the type
+   *     of {@code node}.
+   */
+  private Range getIntRangeFromAnnotation(Node node, AnnotationMirror val) {
+    Range range;
+    if (val == null || AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) {
+      range = Range.EVERYTHING;
+    } else if (atypeFactory.isIntRange(val)) {
+      range = atypeFactory.getRange(val);
+    } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.INTVAL_NAME)) {
+      List<Long> values = atypeFactory.getIntValues(val);
+      range = ValueCheckerUtils.getRangeFromValues(values);
+    } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) {
+      List<Double> values = atypeFactory.getDoubleValues(val);
+      range = ValueCheckerUtils.getRangeFromValues(values);
+    } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
+      return Range.NOTHING;
+    } else {
+      range = Range.EVERYTHING;
+    }
+    return NumberUtils.castRange(node.getType(), range);
+  }
+
+  /**
+   * Returns true if subNode is annotated with {@code @IntRange}.
+   *
+   * @param subNode subNode of {@code p}
+   * @param p TransferInput
+   * @return true if this subNode is annotated with {@code @IntRange}
+   */
+  private boolean isIntRange(Node subNode, TransferInput<CFValue, CFStore> p) {
+    CFValue value = p.getValueOfSubNode(subNode);
+    return atypeFactory.isIntRange(value.getAnnotations());
+  }
+
+  /**
+   * Returns true if {@code node} an integral type and is {@code anno} is {@code @UnknownVal}.
+   *
+   * @param node a node
+   * @param anno annotation mirror
+   * @return true if node is annotated with {@code @UnknownVal} and it is an integral type
+   */
+  private boolean isIntegralUnknownVal(Node node, AnnotationMirror anno) {
+    return AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)
+        && TypesUtils.isIntegralPrimitive(node.getType());
+  }
+
+  /**
+   * Returns true if this node is annotated with {@code @IntRange} or {@code @UnknownVal}.
+   *
+   * @param node the node to inspect
+   * @param p storage
+   * @return true if this node is annotated with {@code @IntRange} or {@code @UnknownVal}
+   */
+  private boolean isIntRangeOrIntegralUnknownVal(Node node, TransferInput<CFValue, CFStore> p) {
+    if (isIntRange(node, p)) {
+      return true;
+    }
+    return isIntegralUnknownVal(node, getValueAnnotation(p.getValueOfSubNode(node)));
+  }
+
+  /**
+   * Create a new transfer result based on the original result and the new annotation.
+   *
+   * @param result the original result
+   * @param resultAnno the new annotation
+   * @return the new transfer result
+   */
+  private TransferResult<CFValue, CFStore> createNewResult(
+      TransferResult<CFValue, CFStore> result, AnnotationMirror resultAnno) {
+    CFValue newResultValue =
+        analysis.createSingleAnnotationValue(
+            resultAnno, result.getResultValue().getUnderlyingType());
+    return new RegularTransferResult<>(newResultValue, result.getRegularStore());
+  }
+
+  /** Create a boolean transfer result. */
+  private TransferResult<CFValue, CFStore> createNewResultBoolean(
+      CFStore thenStore, CFStore elseStore, List<Boolean> resultValues, TypeMirror underlyingType) {
+    AnnotationMirror boolVal = atypeFactory.createBooleanAnnotation(resultValues);
+    CFValue newResultValue = analysis.createSingleAnnotationValue(boolVal, underlyingType);
+    if (elseStore != null) {
+      return new ConditionalTransferResult<>(newResultValue, thenStore, elseStore);
+    } else {
+      return new RegularTransferResult<>(newResultValue, thenStore);
+    }
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitFieldAccess(
+      FieldAccessNode node, TransferInput<CFValue, CFStore> in) {
+
+    TransferResult<CFValue, CFStore> result = super.visitFieldAccess(node, in);
+    refineArrayAtLengthAccess(node, result.getRegularStore());
+    return result;
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitMethodInvocation(
+      MethodInvocationNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(n, p);
+    refineAtLengthInvocation(n, result.getRegularStore());
+    return result;
+  }
+
+  /**
+   * If array.length is encountered, transform its @IntVal annotation into an @ArrayLen annotation
+   * for array.
+   */
+  private void refineArrayAtLengthAccess(FieldAccessNode arrayLengthNode, CFStore store) {
+    if (!NodeUtils.isArrayLengthFieldAccess(arrayLengthNode)) {
+      return;
+    }
+
+    refineAtLengthAccess(arrayLengthNode, arrayLengthNode.getReceiver(), store);
+  }
+
+  /**
+   * If length method is invoked for a sequence, transform its @IntVal annotation into an @ArrayLen
+   * annotation.
+   *
+   * @param lengthNode the length method invocation node
+   * @param store the Checker Framework store
+   */
+  private void refineAtLengthInvocation(MethodInvocationNode lengthNode, CFStore store) {
+    if (atypeFactory
+        .getMethodIdentifier()
+        .isStringLengthMethod(lengthNode.getTarget().getMethod())) {
+      MethodAccessNode methodAccessNode = lengthNode.getTarget();
+      refineAtLengthAccess(lengthNode, methodAccessNode.getReceiver(), store);
+    } else if (atypeFactory
+        .getMethodIdentifier()
+        .isArrayGetLengthMethod(lengthNode.getTarget().getMethod())) {
+      Node node = lengthNode.getArguments().get(0);
+      refineAtLengthAccess(lengthNode, node, store);
+    }
+  }
+
+  /**
+   * Gets a value checker annotation relevant for an array or a string.
+   *
+   * @param arrayOrStringNode the node whose annotation to return
+   * @return the value checker annotation for the array or a string
+   */
+  private AnnotationMirror getArrayOrStringAnnotation(Node arrayOrStringNode) {
+    AnnotationMirror arrayOrStringAnno =
+        atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), StringVal.class);
+    if (arrayOrStringAnno == null) {
+      arrayOrStringAnno =
+          atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), ArrayLen.class);
+    }
+    if (arrayOrStringAnno == null) {
+      arrayOrStringAnno =
+          atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), ArrayLenRange.class);
+    }
+
+    return arrayOrStringAnno;
+  }
+
+  /**
+   * Transform @IntVal or @IntRange annotations of a array or string length into an @ArrayLen
+   * or @ArrayLenRange annotation for the array or string.
+   *
+   * @param lengthNode an invocation of method {@code length} or an access of the {@code length}
+   *     field
+   * @param receiverNode the receiver of {@code lengthNode}
+   * @param store the store to update
+   */
+  private void refineAtLengthAccess(Node lengthNode, Node receiverNode, CFStore store) {
+    JavaExpression lengthExpr = JavaExpression.fromNode(lengthNode);
+
+    // If the expression is not representable (for example if String.length() for some reason is
+    // not marked @Pure, then do not refine.
+    if (lengthExpr instanceof Unknown) {
+      return;
+    }
+
+    CFValue value = store.getValue(lengthExpr);
+    if (value == null) {
+      return;
+    }
+
+    AnnotationMirror lengthAnno = getValueAnnotation(value);
+    if (lengthAnno == null) {
+      return;
+    }
+    if (AnnotationUtils.areSameByName(lengthAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
+      // If the length is bottom, then this is dead code, so the receiver type
+      // should also be bottom.
+      JavaExpression receiver = JavaExpression.fromNode(receiverNode);
+      store.insertValue(receiver, lengthAnno);
+      return;
+    }
+
+    RangeOrListOfValues rolv;
+    if (atypeFactory.isIntRange(lengthAnno)) {
+      rolv = new RangeOrListOfValues(atypeFactory.getRange(lengthAnno));
+    } else if (AnnotationUtils.areSameByName(lengthAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) {
+      List<Long> lengthValues = atypeFactory.getIntValues(lengthAnno);
+      rolv = new RangeOrListOfValues(RangeOrListOfValues.convertLongsToInts(lengthValues));
+    } else {
+      return;
+    }
+    AnnotationMirror newRecAnno = rolv.createAnnotation(atypeFactory);
+    AnnotationMirror oldRecAnno = getArrayOrStringAnnotation(receiverNode);
+
+    AnnotationMirror combinedRecAnno;
+    // If the receiver doesn't have an @ArrayLen annotation, use the new annotation.
+    // If it does have an annotation, combine the facts known about the receiver
+    // with the facts known about its length using GLB.
+    if (oldRecAnno == null) {
+      combinedRecAnno = newRecAnno;
+    } else {
+      combinedRecAnno = hierarchy.greatestLowerBound(oldRecAnno, newRecAnno);
+    }
+    JavaExpression receiver = JavaExpression.fromNode(receiverNode);
+    store.insertValue(receiver, combinedRecAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitStringConcatenateAssignment(
+      StringConcatenateAssignmentNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> result = super.visitStringConcatenateAssignment(n, p);
+    return stringConcatenation(n.getLeftOperand(), n.getRightOperand(), p, result);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitStringConcatenate(
+      StringConcatenateNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> result = super.visitStringConcatenate(n, p);
+    return stringConcatenation(n.getLeftOperand(), n.getRightOperand(), p, result);
+  }
+
+  /**
+   * Calculates possible lengths of a result of string concatenation of strings with known lengths.
+   */
+  private List<Integer> calculateLengthAddition(
+      List<Integer> leftLengths, List<Integer> rightLengths) {
+    List<Integer> result = new ArrayList<>(leftLengths.size() * rightLengths.size());
+
+    for (int left : leftLengths) {
+      for (int right : rightLengths) {
+        long resultLength = (long) left + right;
+        // Lengths not fitting into int are not allowed
+        if (resultLength <= Integer.MAX_VALUE) {
+          result.add((int) resultLength);
+        }
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Calculates a range of possible lengths of a result of string concatenation of strings with
+   * known ranges of lengths.
+   */
+  private Range calculateLengthRangeAddition(Range leftLengths, Range rightLengths) {
+    return leftLengths.plus(rightLengths).intersect(Range.INT_EVERYTHING);
+  }
+
+  /**
+   * Checks whether or not the passed node is nullable. This superficial check assumes that every
+   * node is nullable unless it is a primitive, String literal, or compile-time constant.
+   *
+   * @return false if the node's run-time can't be null; true if the node's run-time value may be
+   *     null, or if this method is not precise enough
+   */
+  private boolean isNullable(Node node) {
+    if (node instanceof StringConversionNode) {
+      if (((StringConversionNode) node).getOperand().getType().getKind().isPrimitive()) {
+        return false;
+      }
+    } else if (node instanceof StringLiteralNode) {
+      return false;
+    } else if (node instanceof StringConcatenateNode) {
+      return false;
+    }
+
+    Element element = TreeUtils.elementFromUse((ExpressionTree) node.getTree());
+    return !ElementUtils.isCompileTimeConstant(element);
+  }
+
+  /** Creates an annotation for a result of string concatenation. */
+  private AnnotationMirror createAnnotationForStringConcatenation(
+      Node leftOperand, Node rightOperand, TransferInput<CFValue, CFStore> p) {
+
+    // Try using sets of string values
+    List<String> leftValues = getStringValues(leftOperand, p);
+    List<String> rightValues = getStringValues(rightOperand, p);
+
+    boolean nonNullStringConcat =
+        atypeFactory.getChecker().hasOption("nonNullStringsConcatenation");
+
+    if (leftValues != null && rightValues != null) {
+      // Both operands have known string values, compute set of results
+      if (!nonNullStringConcat) {
+        if (isNullable(leftOperand)) {
+          leftValues = CollectionsPlume.append(leftValues, "null");
+        }
+        if (isNullable(rightOperand)) {
+          rightValues = CollectionsPlume.append(rightValues, "null");
+        }
+      } else {
+        if (leftOperand instanceof StringConversionNode) {
+          if (((StringConversionNode) leftOperand).getOperand().getType().getKind()
+              == TypeKind.NULL) {
+            leftValues = CollectionsPlume.append(leftValues, "null");
+          }
+        }
+        if (rightOperand instanceof StringConversionNode) {
+          if (((StringConversionNode) rightOperand).getOperand().getType().getKind()
+              == TypeKind.NULL) {
+            rightValues = CollectionsPlume.append(rightValues, "null");
+          }
+        }
+      }
+
+      List<String> concatValues = new ArrayList<>(leftValues.size() * rightValues.size());
+      for (String left : leftValues) {
+        for (String right : rightValues) {
+          concatValues.add(left + right);
+        }
+      }
+      return atypeFactory.createStringAnnotation(concatValues);
+    }
+
+    // Try using sets of lengths
+    List<Integer> leftLengths =
+        leftValues != null
+            ? ValueCheckerUtils.getLengthsForStringValues(leftValues)
+            : getStringLengths(leftOperand, p);
+    List<Integer> rightLengths =
+        rightValues != null
+            ? ValueCheckerUtils.getLengthsForStringValues(rightValues)
+            : getStringLengths(rightOperand, p);
+
+    if (leftLengths != null && rightLengths != null) {
+      // Both operands have known lengths, compute set of result lengths
+      if (!nonNullStringConcat) {
+        if (isNullable(leftOperand)) {
+          leftLengths = new ArrayList<>(leftLengths);
+          leftLengths.add(4); // "null"
+        }
+        if (isNullable(rightOperand)) {
+          rightLengths = new ArrayList<>(rightLengths);
+          rightLengths.add(4); // "null"
+        }
+      }
+      List<Integer> concatLengths = calculateLengthAddition(leftLengths, rightLengths);
+      return atypeFactory.createArrayLenAnnotation(concatLengths);
+    }
+
+    // Try using ranges of lengths
+    Range leftLengthRange =
+        leftLengths != null
+            ? ValueCheckerUtils.getRangeFromValues(leftLengths)
+            : getStringLengthRange(leftOperand, p);
+    Range rightLengthRange =
+        rightLengths != null
+            ? ValueCheckerUtils.getRangeFromValues(rightLengths)
+            : getStringLengthRange(rightOperand, p);
+
+    if (leftLengthRange != null && rightLengthRange != null) {
+      // Both operands have a length from a known range, compute a range of result lengths
+      if (!nonNullStringConcat) {
+        if (isNullable(leftOperand)) {
+          leftLengthRange.union(Range.create(4, 4)); // "null"
+        }
+        if (isNullable(rightOperand)) {
+          rightLengthRange.union(Range.create(4, 4)); // "null"
+        }
+      }
+      Range concatLengthRange = calculateLengthRangeAddition(leftLengthRange, rightLengthRange);
+      return atypeFactory.createArrayLenRangeAnnotation(concatLengthRange);
+    }
+
+    return atypeFactory.UNKNOWNVAL;
+  }
+
+  public TransferResult<CFValue, CFStore> stringConcatenation(
+      Node leftOperand,
+      Node rightOperand,
+      TransferInput<CFValue, CFStore> p,
+      TransferResult<CFValue, CFStore> result) {
+
+    AnnotationMirror resultAnno =
+        createAnnotationForStringConcatenation(leftOperand, rightOperand, p);
+
+    TypeMirror underlyingType = result.getResultValue().getUnderlyingType();
+    CFValue newResultValue = analysis.createSingleAnnotationValue(resultAnno, underlyingType);
+    return new RegularTransferResult<>(newResultValue, result.getRegularStore());
+  }
+
+  /** Binary operations that are analyzed by the value checker. */
+  enum NumericalBinaryOps {
+    ADDITION,
+    SUBTRACTION,
+    DIVISION,
+    REMAINDER,
+    MULTIPLICATION,
+    SHIFT_LEFT,
+    SIGNED_SHIFT_RIGHT,
+    UNSIGNED_SHIFT_RIGHT,
+    BITWISE_AND,
+    BITWISE_OR,
+    BITWISE_XOR;
+  }
+
+  /**
+   * Get the refined annotation after a numerical binary operation.
+   *
+   * @param leftNode the node that represents the left operand
+   * @param rightNode the node that represents the right operand
+   * @param op the operator type
+   * @param p the transfer input
+   * @return the result annotation mirror
+   */
+  private AnnotationMirror calculateNumericalBinaryOp(
+      Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput<CFValue, CFStore> p) {
+    if (!isIntRangeOrIntegralUnknownVal(leftNode, p)
+        && !isIntRangeOrIntegralUnknownVal(rightNode, p)) {
+      List<Number> resultValues = calculateValuesBinaryOp(leftNode, rightNode, op, p);
+      return atypeFactory.createNumberAnnotationMirror(resultValues);
+    } else {
+      Range resultRange = calculateRangeBinaryOp(leftNode, rightNode, op, p);
+      return atypeFactory.createIntRangeAnnotation(resultRange);
+    }
+  }
+
+  /** Calculate the result range after a binary operation between two numerical type nodes. */
+  private Range calculateRangeBinaryOp(
+      Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput<CFValue, CFStore> p) {
+    if (TypesUtils.isIntegralPrimitive(leftNode.getType())
+        && TypesUtils.isIntegralPrimitive(rightNode.getType())) {
+      Range leftRange = getIntRange(leftNode, p);
+      Range rightRange = getIntRange(rightNode, p);
+      Range resultRange;
+      switch (op) {
+        case ADDITION:
+          resultRange = leftRange.plus(rightRange);
+          break;
+        case SUBTRACTION:
+          resultRange = leftRange.minus(rightRange);
+          break;
+        case MULTIPLICATION:
+          resultRange = leftRange.times(rightRange);
+          break;
+        case DIVISION:
+          resultRange = leftRange.divide(rightRange);
+          break;
+        case REMAINDER:
+          resultRange = leftRange.remainder(rightRange);
+          break;
+        case SHIFT_LEFT:
+          resultRange = leftRange.shiftLeft(rightRange);
+          break;
+        case SIGNED_SHIFT_RIGHT:
+          resultRange = leftRange.signedShiftRight(rightRange);
+          break;
+        case UNSIGNED_SHIFT_RIGHT:
+          resultRange = leftRange.unsignedShiftRight(rightRange);
+          break;
+        case BITWISE_AND:
+          resultRange = leftRange.bitwiseAnd(rightRange);
+          break;
+        case BITWISE_OR:
+          resultRange = leftRange.bitwiseOr(rightRange);
+          break;
+        case BITWISE_XOR:
+          resultRange = leftRange.bitwiseXor(rightRange);
+          break;
+        default:
+          throw new BugInCF("ValueTransfer: unsupported operation: " + op);
+      }
+      // Any integral type with less than 32 bits would be promoted to 32-bit int type during
+      // operations.
+      return leftNode.getType().getKind() == TypeKind.LONG
+              || rightNode.getType().getKind() == TypeKind.LONG
+          ? resultRange
+          : resultRange.intRange();
+    } else {
+      return Range.EVERYTHING;
+    }
+  }
+
+  /** Calculate the possible values after a binary operation between two numerical type nodes. */
+  private List<Number> calculateValuesBinaryOp(
+      Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput<CFValue, CFStore> p) {
+    List<? extends Number> lefts = getNumericalValues(leftNode, p);
+    List<? extends Number> rights = getNumericalValues(rightNode, p);
+    if (lefts == null || rights == null) {
+      return null;
+    }
+    List<Number> resultValues = new ArrayList<>();
+    for (Number left : lefts) {
+      NumberMath<?> nmLeft = NumberMath.getNumberMath(left);
+      for (Number right : rights) {
+        switch (op) {
+          case ADDITION:
+            resultValues.add(nmLeft.plus(right));
+            break;
+          case DIVISION:
+            Number result = nmLeft.divide(right);
+            if (result != null) {
+              resultValues.add(result);
+            }
+            break;
+          case MULTIPLICATION:
+            resultValues.add(nmLeft.times(right));
+            break;
+          case REMAINDER:
+            Number resultR = nmLeft.remainder(right);
+            if (resultR != null) {
+              resultValues.add(resultR);
+            }
+            break;
+          case SUBTRACTION:
+            resultValues.add(nmLeft.minus(right));
+            break;
+          case SHIFT_LEFT:
+            resultValues.add(nmLeft.shiftLeft(right));
+            break;
+          case SIGNED_SHIFT_RIGHT:
+            resultValues.add(nmLeft.signedShiftRight(right));
+            break;
+          case UNSIGNED_SHIFT_RIGHT:
+            resultValues.add(nmLeft.unsignedShiftRight(right));
+            break;
+          case BITWISE_AND:
+            resultValues.add(nmLeft.bitwiseAnd(right));
+            break;
+          case BITWISE_OR:
+            resultValues.add(nmLeft.bitwiseOr(right));
+            break;
+          case BITWISE_XOR:
+            resultValues.add(nmLeft.bitwiseXor(right));
+            break;
+          default:
+            throw new BugInCF("ValueTransfer: unsupported operation: " + op);
+        }
+      }
+    }
+    return resultValues;
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitNumericalAddition(
+      NumericalAdditionNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitNumericalAddition(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.ADDITION, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitNumericalSubtraction(
+      NumericalSubtractionNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitNumericalSubtraction(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SUBTRACTION, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitNumericalMultiplication(
+      NumericalMultiplicationNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitNumericalMultiplication(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.MULTIPLICATION, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitIntegerDivision(
+      IntegerDivisionNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitIntegerDivision(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitFloatingDivision(
+      FloatingDivisionNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitFloatingDivision(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitIntegerRemainder(
+      IntegerRemainderNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitIntegerRemainder(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitFloatingRemainder(
+      FloatingRemainderNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitFloatingRemainder(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitLeftShift(
+      LeftShiftNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitLeftShift(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SHIFT_LEFT, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitSignedRightShift(
+      SignedRightShiftNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitSignedRightShift(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SIGNED_SHIFT_RIGHT, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitUnsignedRightShift(
+      UnsignedRightShiftNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitUnsignedRightShift(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.UNSIGNED_SHIFT_RIGHT, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitBitwiseAnd(
+      BitwiseAndNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitBitwiseAnd(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_AND, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitBitwiseOr(
+      BitwiseOrNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitBitwiseOr(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_OR, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitBitwiseXor(
+      BitwiseXorNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitBitwiseXor(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalBinaryOp(
+            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_XOR, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  /** Unary operations that are analyzed by the value checker. */
+  enum NumericalUnaryOps {
+    PLUS,
+    MINUS,
+    BITWISE_COMPLEMENT;
+  }
+
+  /**
+   * Get the refined annotation after a numerical unary operation.
+   *
+   * @param operand the node that represents the operand
+   * @param op the operator type
+   * @param p the transfer input
+   * @return the result annotation mirror
+   */
+  private AnnotationMirror calculateNumericalUnaryOp(
+      Node operand, NumericalUnaryOps op, TransferInput<CFValue, CFStore> p) {
+    if (!isIntRange(operand, p)) {
+      List<Number> resultValues = calculateValuesUnaryOp(operand, op, p);
+      return atypeFactory.createNumberAnnotationMirror(resultValues);
+    } else {
+      Range resultRange = calculateRangeUnaryOp(operand, op, p);
+      return atypeFactory.createIntRangeAnnotation(resultRange);
+    }
+  }
+
+  /**
+   * Calculate the result range after a unary operation of a numerical type node.
+   *
+   * @param operand the node that represents the operand
+   * @param op the operator type
+   * @param p the transfer input
+   * @return the result annotation mirror
+   */
+  private Range calculateRangeUnaryOp(
+      Node operand, NumericalUnaryOps op, TransferInput<CFValue, CFStore> p) {
+    if (TypesUtils.isIntegralPrimitive(operand.getType())) {
+      Range range = getIntRange(operand, p);
+      Range resultRange;
+      switch (op) {
+        case PLUS:
+          resultRange = range.unaryPlus();
+          break;
+        case MINUS:
+          resultRange = range.unaryMinus();
+          break;
+        case BITWISE_COMPLEMENT:
+          resultRange = range.bitwiseComplement();
+          break;
+        default:
+          throw new BugInCF("ValueTransfer: unsupported operation: " + op);
+      }
+      // Any integral type with less than 32 bits would be promoted to 32-bit int type during
+      // operations.
+      return operand.getType().getKind() == TypeKind.LONG ? resultRange : resultRange.intRange();
+    } else {
+      return Range.EVERYTHING;
+    }
+  }
+
+  /** Calculate the possible values after a unary operation of a numerical type node. */
+  private List<Number> calculateValuesUnaryOp(
+      Node operand, NumericalUnaryOps op, TransferInput<CFValue, CFStore> p) {
+    List<? extends Number> lefts = getNumericalValues(operand, p);
+    if (lefts == null) {
+      return null;
+    }
+    List<Number> resultValues = new ArrayList<>(lefts.size());
+    for (Number left : lefts) {
+      NumberMath<?> nmLeft = NumberMath.getNumberMath(left);
+      switch (op) {
+        case PLUS:
+          resultValues.add(nmLeft.unaryPlus());
+          break;
+        case MINUS:
+          resultValues.add(nmLeft.unaryMinus());
+          break;
+        case BITWISE_COMPLEMENT:
+          resultValues.add(nmLeft.bitwiseComplement());
+          break;
+        default:
+          throw new BugInCF("ValueTransfer: unsupported operation: " + op);
+      }
+    }
+    return resultValues;
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitNumericalMinus(
+      NumericalMinusNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitNumericalMinus(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.MINUS, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitNumericalPlus(
+      NumericalPlusNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitNumericalPlus(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.PLUS, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitBitwiseComplement(
+      BitwiseComplementNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitBitwiseComplement(n, p);
+    AnnotationMirror resultAnno =
+        calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.BITWISE_COMPLEMENT, p);
+    return createNewResult(transferResult, resultAnno);
+  }
+
+  enum ComparisonOperators {
+    EQUAL,
+    NOT_EQUAL,
+    GREATER_THAN,
+    GREATER_THAN_EQ,
+    LESS_THAN,
+    LESS_THAN_EQ;
+  }
+
+  private List<Boolean> calculateBinaryComparison(
+      Node leftNode,
+      CFValue leftValue,
+      Node rightNode,
+      CFValue rightValue,
+      ComparisonOperators op,
+      CFStore thenStore,
+      CFStore elseStore) {
+    AnnotationMirror leftAnno = getValueAnnotation(leftValue);
+    AnnotationMirror rightAnno = getValueAnnotation(rightValue);
+
+    if (atypeFactory.isIntRange(leftAnno)
+        || atypeFactory.isIntRange(rightAnno)
+        || isIntegralUnknownVal(rightNode, rightAnno)
+        || isIntegralUnknownVal(leftNode, leftAnno)) {
+      // If either is @UnknownVal, then refineIntRanges will treat it as the max range and thus
+      // refine it if possible.  Also, if either is an @IntVal, then it will be converted to a
+      // range.  This is less precise in some cases, but avoids the complexity of comparing a list
+      // of values to a range. (This could be implemented in the future.)
+      return refineIntRanges(leftNode, leftAnno, rightNode, rightAnno, op, thenStore, elseStore);
+    }
+
+    List<? extends Number> lefts = getNumericalValues(leftNode, leftAnno);
+    List<? extends Number> rights = getNumericalValues(rightNode, rightAnno);
+
+    if (lefts == null || rights == null) {
+      // Appropriately handle bottom when something is compared to bottom.
+      if (AnnotationUtils.areSame(leftAnno, atypeFactory.BOTTOMVAL)
+          || AnnotationUtils.areSame(rightAnno, atypeFactory.BOTTOMVAL)) {
+        return Collections.emptyList();
+      }
+      return null;
+    }
+
+    // This is a list of all the values that the expression can evaluate to.
+    List<Boolean> resultValues = new ArrayList<>();
+
+    // These lists are used to refine the values in the store based on the results of the
+    // comparison.
+    List<Number> thenLeftVals = new ArrayList<>();
+    List<Number> elseLeftVals = new ArrayList<>();
+    List<Number> thenRightVals = new ArrayList<>();
+    List<Number> elseRightVals = new ArrayList<>();
+
+    for (Number left : lefts) {
+      NumberMath<?> nmLeft = NumberMath.getNumberMath(left);
+      for (Number right : rights) {
+        Boolean result;
+        switch (op) {
+          case EQUAL:
+            result = nmLeft.equalTo(right);
+            break;
+          case GREATER_THAN:
+            result = nmLeft.greaterThan(right);
+            break;
+          case GREATER_THAN_EQ:
+            result = nmLeft.greaterThanEq(right);
+            break;
+          case LESS_THAN:
+            result = nmLeft.lessThan(right);
+            break;
+          case LESS_THAN_EQ:
+            result = nmLeft.lessThanEq(right);
+            break;
+          case NOT_EQUAL:
+            result = nmLeft.notEqualTo(right);
+            break;
+          default:
+            throw new BugInCF("ValueTransfer: unsupported operation: " + op);
+        }
+        resultValues.add(result);
+        if (result) {
+          thenLeftVals.add(left);
+          thenRightVals.add(right);
+        } else {
+          elseLeftVals.add(left);
+          elseRightVals.add(right);
+        }
+      }
+    }
+
+    createAnnotationFromResultsAndAddToStore(thenStore, thenLeftVals, leftNode);
+    createAnnotationFromResultsAndAddToStore(elseStore, elseLeftVals, leftNode);
+    createAnnotationFromResultsAndAddToStore(thenStore, thenRightVals, rightNode);
+    createAnnotationFromResultsAndAddToStore(elseStore, elseRightVals, rightNode);
+
+    return resultValues;
+  }
+
+  /**
+   * Calculates the result of a binary comparison on a pair of intRange annotations, and refines
+   * annotations appropriately.
+   */
+  private List<Boolean> refineIntRanges(
+      Node leftNode,
+      AnnotationMirror leftAnno,
+      Node rightNode,
+      AnnotationMirror rightAnno,
+      ComparisonOperators op,
+      CFStore thenStore,
+      CFStore elseStore) {
+
+    Range leftRange = getIntRangeFromAnnotation(leftNode, leftAnno);
+    Range rightRange = getIntRangeFromAnnotation(rightNode, rightAnno);
+
+    final Range thenRightRange;
+    final Range thenLeftRange;
+    final Range elseRightRange;
+    final Range elseLeftRange;
+
+    switch (op) {
+      case EQUAL:
+        thenRightRange = rightRange.refineEqualTo(leftRange);
+        thenLeftRange = thenRightRange; // Only needs to be computed once.
+        elseRightRange = rightRange.refineNotEqualTo(leftRange);
+        elseLeftRange = leftRange.refineNotEqualTo(rightRange);
+        break;
+      case GREATER_THAN:
+        thenLeftRange = leftRange.refineGreaterThan(rightRange);
+        thenRightRange = rightRange.refineLessThan(leftRange);
+        elseRightRange = rightRange.refineGreaterThanEq(leftRange);
+        elseLeftRange = leftRange.refineLessThanEq(rightRange);
+        break;
+      case GREATER_THAN_EQ:
+        thenRightRange = rightRange.refineLessThanEq(leftRange);
+        thenLeftRange = leftRange.refineGreaterThanEq(rightRange);
+        elseLeftRange = leftRange.refineLessThan(rightRange);
+        elseRightRange = rightRange.refineGreaterThan(leftRange);
+        break;
+      case LESS_THAN:
+        thenLeftRange = leftRange.refineLessThan(rightRange);
+        thenRightRange = rightRange.refineGreaterThan(leftRange);
+        elseRightRange = rightRange.refineLessThanEq(leftRange);
+        elseLeftRange = leftRange.refineGreaterThanEq(rightRange);
+        break;
+      case LESS_THAN_EQ:
+        thenRightRange = rightRange.refineGreaterThanEq(leftRange);
+        thenLeftRange = leftRange.refineLessThanEq(rightRange);
+        elseLeftRange = leftRange.refineGreaterThan(rightRange);
+        elseRightRange = rightRange.refineLessThan(leftRange);
+        break;
+      case NOT_EQUAL:
+        thenRightRange = rightRange.refineNotEqualTo(leftRange);
+        thenLeftRange = leftRange.refineNotEqualTo(rightRange);
+        elseRightRange = rightRange.refineEqualTo(leftRange);
+        elseLeftRange = elseRightRange; // Equality only needs to be computed once.
+        break;
+      default:
+        throw new BugInCF("ValueTransfer: unsupported operation: " + op);
+    }
+
+    createAnnotationFromRangeAndAddToStore(thenStore, thenRightRange, rightNode);
+    createAnnotationFromRangeAndAddToStore(thenStore, thenLeftRange, leftNode);
+    createAnnotationFromRangeAndAddToStore(elseStore, elseRightRange, rightNode);
+    createAnnotationFromRangeAndAddToStore(elseStore, elseLeftRange, leftNode);
+
+    // TODO: Refine the type of the comparison.
+    return null;
+  }
+
+  /**
+   * Takes a list of result values (i.e. the values possible after the comparison) and creates the
+   * appropriate annotation from them, then combines that annotation with the existing annotation on
+   * the node. The resulting annotation is inserted into the store.
+   *
+   * @param store the store
+   * @param results the result values
+   * @param node the node whose existing annotation to refine
+   */
+  private void createAnnotationFromResultsAndAddToStore(CFStore store, List<?> results, Node node) {
+    AnnotationMirror anno = atypeFactory.createResultingAnnotation(node.getType(), results);
+    addAnnotationToStore(store, anno, node);
+  }
+
+  /**
+   * Takes a range and creates the appropriate annotation from it, then combines that annotation
+   * with the existing annotation on the node. The resulting annotation is inserted into the store.
+   *
+   * @param store the store
+   * @param range the range to create an annotation for
+   * @param node the node whose existing annotation to refine
+   */
+  private void createAnnotationFromRangeAndAddToStore(CFStore store, Range range, Node node) {
+    AnnotationMirror anno = atypeFactory.createIntRangeAnnotation(range);
+    addAnnotationToStore(store, anno, node);
+  }
+
+  private void addAnnotationToStore(CFStore store, AnnotationMirror anno, Node node) {
+    // If node is assignment, iterate over lhs and rhs; otherwise, iterator contains just node.
+    for (Node internal : splitAssignments(node)) {
+      JavaExpression je = JavaExpression.fromNode(internal);
+      CFValue currentValueFromStore;
+      if (CFAbstractStore.canInsertJavaExpression(je)) {
+        currentValueFromStore = store.getValue(je);
+      } else {
+        // Don't just `continue;` which would skip the calls to refine{Array,String}...
+        currentValueFromStore = null;
+      }
+      AnnotationMirror currentAnno =
+          (currentValueFromStore == null
+              ? atypeFactory.UNKNOWNVAL
+              : getValueAnnotation(currentValueFromStore));
+      // Combine the new annotations based on the results of the comparison with the existing type.
+      AnnotationMirror newAnno = hierarchy.greatestLowerBound(anno, currentAnno);
+      store.insertValue(je, newAnno);
+
+      if (node instanceof FieldAccessNode) {
+        refineArrayAtLengthAccess((FieldAccessNode) internal, store);
+      } else if (node instanceof MethodInvocationNode) {
+        MethodInvocationNode miNode = (MethodInvocationNode) node;
+        refineAtLengthInvocation(miNode, store);
+      }
+    }
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitLessThan(
+      LessThanNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitLessThan(n, p);
+    CFStore thenStore = transferResult.getThenStore();
+    CFStore elseStore = transferResult.getElseStore();
+    List<Boolean> resultValues =
+        calculateBinaryComparison(
+            n.getLeftOperand(),
+            p.getValueOfSubNode(n.getLeftOperand()),
+            n.getRightOperand(),
+            p.getValueOfSubNode(n.getRightOperand()),
+            ComparisonOperators.LESS_THAN,
+            thenStore,
+            elseStore);
+    TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType();
+    return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitLessThanOrEqual(
+      LessThanOrEqualNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitLessThanOrEqual(n, p);
+    CFStore thenStore = transferResult.getThenStore();
+    CFStore elseStore = transferResult.getElseStore();
+    List<Boolean> resultValues =
+        calculateBinaryComparison(
+            n.getLeftOperand(),
+            p.getValueOfSubNode(n.getLeftOperand()),
+            n.getRightOperand(),
+            p.getValueOfSubNode(n.getRightOperand()),
+            ComparisonOperators.LESS_THAN_EQ,
+            thenStore,
+            elseStore);
+    TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType();
+    return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitGreaterThan(
+      GreaterThanNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitGreaterThan(n, p);
+    CFStore thenStore = transferResult.getThenStore();
+    CFStore elseStore = transferResult.getElseStore();
+    List<Boolean> resultValues =
+        calculateBinaryComparison(
+            n.getLeftOperand(),
+            p.getValueOfSubNode(n.getLeftOperand()),
+            n.getRightOperand(),
+            p.getValueOfSubNode(n.getRightOperand()),
+            ComparisonOperators.GREATER_THAN,
+            thenStore,
+            elseStore);
+    TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType();
+    return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitGreaterThanOrEqual(
+      GreaterThanOrEqualNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitGreaterThanOrEqual(n, p);
+    CFStore thenStore = transferResult.getThenStore();
+    CFStore elseStore = transferResult.getElseStore();
+    List<Boolean> resultValues =
+        calculateBinaryComparison(
+            n.getLeftOperand(),
+            p.getValueOfSubNode(n.getLeftOperand()),
+            n.getRightOperand(),
+            p.getValueOfSubNode(n.getRightOperand()),
+            ComparisonOperators.GREATER_THAN_EQ,
+            thenStore,
+            elseStore);
+    TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType();
+    return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType);
+  }
+
+  @Override
+  protected TransferResult<CFValue, CFStore> strengthenAnnotationOfEqualTo(
+      TransferResult<CFValue, CFStore> transferResult,
+      Node firstNode,
+      Node secondNode,
+      CFValue firstValue,
+      CFValue secondValue,
+      boolean notEqualTo) {
+    if (firstValue == null) {
+      return transferResult;
+    }
+    if (TypesUtils.isNumeric(firstNode.getType()) || TypesUtils.isNumeric(secondNode.getType())) {
+      CFStore thenStore = transferResult.getThenStore();
+      CFStore elseStore = transferResult.getElseStore();
+      // At least one must be a primitive otherwise reference equality is used.
+      List<Boolean> resultValues =
+          calculateBinaryComparison(
+              firstNode,
+              firstValue,
+              secondNode,
+              secondValue,
+              notEqualTo ? ComparisonOperators.NOT_EQUAL : ComparisonOperators.EQUAL,
+              thenStore,
+              elseStore);
+      if (transferResult.getResultValue() == null) {
+        // Happens for case labels
+        return transferResult;
+      }
+      TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType();
+      return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType);
+    }
+    return super.strengthenAnnotationOfEqualTo(
+        transferResult, firstNode, secondNode, firstValue, secondValue, notEqualTo);
+  }
+
+  @Override
+  protected void processConditionalPostconditions(
+      MethodInvocationNode n,
+      ExecutableElement methodElement,
+      Tree tree,
+      CFStore thenStore,
+      CFStore elseStore) {
+    // For String.startsWith(String) and String.endsWith(String), refine the minimum length
+    // of the receiver to the minimum length of the argument.
+
+    ValueMethodIdentifier methodIdentifier = atypeFactory.getMethodIdentifier();
+    if (methodIdentifier.isStartsWithMethod(methodElement)
+        || methodIdentifier.isEndsWithMethod(methodElement)) {
+
+      Node argumentNode = n.getArgument(0);
+      AnnotationMirror argumentAnno = getArrayOrStringAnnotation(argumentNode);
+      int minLength = atypeFactory.getMinLenValue(argumentAnno);
+      // Update the annotation of the receiver
+      if (minLength != 0) {
+        JavaExpression receiver = JavaExpression.fromNode(n.getTarget().getReceiver());
+
+        AnnotationMirror minLenAnno =
+            atypeFactory.createArrayLenRangeAnnotation(minLength, Integer.MAX_VALUE);
+        thenStore.insertValuePermitNondeterministic(receiver, minLenAnno);
+      }
+    }
+
+    super.processConditionalPostconditions(n, methodElement, tree, thenStore, elseStore);
+  }
+
+  enum ConditionalOperators {
+    NOT,
+    OR,
+    AND;
+  }
+
+  private static final List<Boolean> ALL_BOOLEANS =
+      Arrays.asList(new Boolean[] {Boolean.TRUE, Boolean.FALSE});
+
+  private List<Boolean> calculateConditionalOperator(
+      Node leftNode, Node rightNode, ConditionalOperators op, TransferInput<CFValue, CFStore> p) {
+    List<Boolean> lefts = getBooleanValues(leftNode, p);
+    if (lefts == null) {
+      lefts = ALL_BOOLEANS;
+    }
+    List<Boolean> rights = null;
+    if (rightNode != null) {
+      rights = getBooleanValues(rightNode, p);
+      if (rights == null) {
+        rights = ALL_BOOLEANS;
+      }
+    }
+    // This list can contain duplicates.  It is deduplicated later by createBooleanAnnotation.
+    List<Boolean> resultValues = new ArrayList<>(2);
+    switch (op) {
+      case NOT:
+        return CollectionsPlume.mapList((Boolean left) -> !left, lefts);
+      case OR:
+        for (Boolean left : lefts) {
+          for (Boolean right : rights) {
+            resultValues.add(left || right);
+          }
+        }
+        return resultValues;
+      case AND:
+        for (Boolean left : lefts) {
+          for (Boolean right : rights) {
+            resultValues.add(left && right);
+          }
+        }
+        return resultValues;
+    }
+    throw new BugInCF("ValueTransfer: unsupported operation: " + op);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitEqualTo(
+      EqualToNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> res = super.visitEqualTo(n, p);
+
+    Node leftN = n.getLeftOperand();
+    Node rightN = n.getRightOperand();
+    CFValue leftV = p.getValueOfSubNode(leftN);
+    CFValue rightV = p.getValueOfSubNode(rightN);
+
+    // if annotations differ, use the one that is more precise for both
+    // sides (and add it to the store if possible)
+    res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false);
+    res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false);
+
+    Boolean leftBoolean = getBooleanValue(leftV);
+    if (leftBoolean != null) {
+      CFValue notLeftV = createBooleanCFValue(!leftBoolean);
+      res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, true);
+      res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, true);
+    }
+    Boolean rightBoolean = getBooleanValue(rightV);
+    if (rightBoolean != null) {
+      CFValue notRightV = createBooleanCFValue(!rightBoolean);
+      res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, true);
+      res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, true);
+    }
+
+    return res;
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitNotEqual(
+      NotEqualNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> res = super.visitNotEqual(n, p);
+
+    Node leftN = n.getLeftOperand();
+    Node rightN = n.getRightOperand();
+    CFValue leftV = p.getValueOfSubNode(leftN);
+    CFValue rightV = p.getValueOfSubNode(rightN);
+
+    // if annotations differ, use the one that is more precise for both
+    // sides (and add it to the store if possible)
+    res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true);
+    res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true);
+
+    Boolean leftBoolean = getBooleanValue(leftV);
+    if (leftBoolean != null) {
+      CFValue notLeftV = createBooleanCFValue(!leftBoolean);
+      res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, false);
+      res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, false);
+    }
+    Boolean rightBoolean = getBooleanValue(rightV);
+    if (rightBoolean != null) {
+      CFValue notRightV = createBooleanCFValue(!rightBoolean);
+      res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, false);
+      res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, false);
+    }
+
+    return res;
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitConditionalNot(
+      ConditionalNotNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitConditionalNot(n, p);
+    List<Boolean> resultValues =
+        calculateConditionalOperator(n.getOperand(), null, ConditionalOperators.NOT, p);
+    return createNewResultBoolean(
+        transferResult.getThenStore(),
+        transferResult.getElseStore(),
+        resultValues,
+        transferResult.getResultValue().getUnderlyingType());
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitConditionalAnd(
+      ConditionalAndNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitConditionalAnd(n, p);
+    List<Boolean> resultValues =
+        calculateConditionalOperator(
+            n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.AND, p);
+    return createNewResultBoolean(
+        transferResult.getThenStore(),
+        transferResult.getElseStore(),
+        resultValues,
+        transferResult.getResultValue().getUnderlyingType());
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitConditionalOr(
+      ConditionalOrNode n, TransferInput<CFValue, CFStore> p) {
+    TransferResult<CFValue, CFStore> transferResult = super.visitConditionalOr(n, p);
+    List<Boolean> resultValues =
+        calculateConditionalOperator(
+            n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.OR, p);
+    return createNewResultBoolean(
+        transferResult.getThenStore(),
+        transferResult.getElseStore(),
+        resultValues,
+        transferResult.getResultValue().getUnderlyingType());
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java b/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java
new file mode 100644
index 0000000..384b56a
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java
@@ -0,0 +1,637 @@
+package org.checkerframework.common.value;
+
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeCastTree;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+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.ElementKind;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.checker.signature.qual.Identifier;
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.ArrayLenRange;
+import org.checkerframework.common.value.qual.StaticallyExecutable;
+import org.checkerframework.common.value.util.NumberUtils;
+import org.checkerframework.common.value.util.Range;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** The TreeAnnotator for this AnnotatedTypeFactory. It adds/replaces annotations. */
+class ValueTreeAnnotator extends TreeAnnotator {
+
+  /** The type factory to use. Shadows the field from the superclass with a more specific type. */
+  @SuppressWarnings("HidingField")
+  protected final ValueAnnotatedTypeFactory atypeFactory;
+
+  /** The domain of the Constant Value Checker: the types for which it estimates possible values. */
+  protected static final Set<String> COVERED_CLASS_STRINGS =
+      Collections.unmodifiableSet(
+          new HashSet<>(
+              Arrays.asList(
+                  "int",
+                  "java.lang.Integer",
+                  "double",
+                  "java.lang.Double",
+                  "byte",
+                  "java.lang.Byte",
+                  "java.lang.String",
+                  "char",
+                  "java.lang.Character",
+                  "float",
+                  "java.lang.Float",
+                  "boolean",
+                  "java.lang.Boolean",
+                  "long",
+                  "java.lang.Long",
+                  "short",
+                  "java.lang.Short",
+                  "char[]")));
+
+  /**
+   * Create a ValueTreeAnnotator.
+   *
+   * @param atypeFactory the ValueAnnotatedTypeFactory to use
+   */
+  public ValueTreeAnnotator(ValueAnnotatedTypeFactory atypeFactory) {
+    super(atypeFactory);
+    this.atypeFactory = atypeFactory;
+  }
+
+  @Override
+  public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) {
+
+    List<? extends ExpressionTree> dimensions = tree.getDimensions();
+    List<? extends ExpressionTree> initializers = tree.getInitializers();
+
+    // Array construction can provide dimensions or use an initializer.
+
+    // Dimensions provided
+    if (!dimensions.isEmpty()) {
+      handleDimensions(dimensions, (AnnotatedTypeMirror.AnnotatedArrayType) type);
+    } else {
+      // Initializer used
+      handleInitializers(initializers, (AnnotatedTypeMirror.AnnotatedArrayType) type);
+
+      AnnotationMirror newQual;
+      Class<?> clazz = TypesUtils.getClassFromType(type.getUnderlyingType());
+      String stringVal = null;
+      if (clazz == char[].class) {
+        stringVal = getCharArrayStringVal(initializers);
+      }
+
+      if (stringVal != null) {
+        newQual = atypeFactory.createStringAnnotation(Collections.singletonList(stringVal));
+        type.replaceAnnotation(newQual);
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Recursive method to handle array initializations. Recursively descends the initializer to find
+   * each dimension's size and create the appropriate annotation for it.
+   *
+   * <p>If the annotation of the dimension is {@code @IntVal}, create an {@code @ArrayLen} with the
+   * same set of possible values. If the annotation is {@code @IntRange}, create an
+   * {@code @ArrayLenRange}. If the annotation is {@code @BottomVal}, create an {@code @BottomVal}
+   * instead. In other cases, no annotations are created.
+   *
+   * @param dimensions a list of ExpressionTrees where each ExpressionTree is a specifier of the
+   *     size of that dimension
+   * @param type the AnnotatedTypeMirror of the array
+   */
+  private void handleDimensions(
+      List<? extends ExpressionTree> dimensions, AnnotatedTypeMirror.AnnotatedArrayType type) {
+    if (dimensions.size() > 1) {
+      handleDimensions(
+          dimensions.subList(1, dimensions.size()),
+          (AnnotatedTypeMirror.AnnotatedArrayType) type.getComponentType());
+    }
+    AnnotationMirror dimType =
+        atypeFactory
+            .getAnnotatedType(dimensions.get(0))
+            .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL);
+
+    if (AnnotationUtils.areSameByName(dimType, atypeFactory.BOTTOMVAL)) {
+      type.replaceAnnotation(atypeFactory.BOTTOMVAL);
+    } else {
+      RangeOrListOfValues rolv = null;
+      if (atypeFactory.isIntRange(dimType)) {
+        rolv = new RangeOrListOfValues(atypeFactory.getRange(dimType));
+      } else if (AnnotationUtils.areSameByName(dimType, ValueAnnotatedTypeFactory.INTVAL_NAME)) {
+        rolv =
+            new RangeOrListOfValues(
+                RangeOrListOfValues.convertLongsToInts(atypeFactory.getIntValues(dimType)));
+      }
+      if (rolv != null) {
+        AnnotationMirror newQual = rolv.createAnnotation(atypeFactory);
+        type.replaceAnnotation(newQual);
+      }
+    }
+  }
+
+  /**
+   * Adds the ArrayLen/ArrayLenRange annotation from the array initializers to {@code type}.
+   *
+   * <p>If type is a multi-dimensional array, the initializers might also contain arrays, so this
+   * method adds the annotations for those initializers, too.
+   *
+   * @param initializers initializer trees
+   * @param type array type to which annotations are added
+   */
+  private void handleInitializers(
+      List<? extends ExpressionTree> initializers, AnnotatedTypeMirror.AnnotatedArrayType type) {
+
+    type.replaceAnnotation(
+        atypeFactory.createArrayLenAnnotation(Collections.singletonList(initializers.size())));
+
+    if (type.getComponentType().getKind() != TypeKind.ARRAY) {
+      return;
+    }
+
+    // A list of arrayLens.  arrayLenOfDimensions.get(i) is the array lengths for the ith dimension.
+    List<RangeOrListOfValues> arrayLenOfDimensions = new ArrayList<>();
+    for (ExpressionTree init : initializers) {
+      AnnotatedTypeMirror componentType = atypeFactory.getAnnotatedType(init);
+      int dimension = 0;
+      while (componentType.getKind() == TypeKind.ARRAY) {
+        RangeOrListOfValues rolv = null;
+        if (dimension < arrayLenOfDimensions.size()) {
+          rolv = arrayLenOfDimensions.get(dimension);
+        }
+        AnnotationMirror arrayLen = componentType.getAnnotation(ArrayLen.class);
+        if (arrayLen != null) {
+          List<Integer> currentLengths = atypeFactory.getArrayLength(arrayLen);
+          if (rolv != null) {
+            rolv.addAll(currentLengths);
+          } else {
+            arrayLenOfDimensions.add(new RangeOrListOfValues(currentLengths));
+          }
+        } else {
+          // Check for an arrayLenRange annotation
+          AnnotationMirror arrayLenRangeAnno = componentType.getAnnotation(ArrayLenRange.class);
+          Range range;
+          if (arrayLenRangeAnno != null) {
+            range = atypeFactory.getRange(arrayLenRangeAnno);
+          } else {
+            range = Range.EVERYTHING;
+          }
+          if (rolv != null) {
+            rolv.add(range);
+          } else {
+            arrayLenOfDimensions.add(new RangeOrListOfValues(range));
+          }
+        }
+
+        dimension++;
+        componentType = ((AnnotatedTypeMirror.AnnotatedArrayType) componentType).getComponentType();
+      }
+    }
+
+    AnnotatedTypeMirror componentType = type.getComponentType();
+    int i = 0;
+    while (componentType.getKind() == TypeKind.ARRAY && i < arrayLenOfDimensions.size()) {
+      RangeOrListOfValues rolv = arrayLenOfDimensions.get(i);
+      componentType.addAnnotation(rolv.createAnnotation(atypeFactory));
+      componentType = ((AnnotatedTypeMirror.AnnotatedArrayType) componentType).getComponentType();
+      i++;
+    }
+  }
+
+  /** Convert a char array to a String. Return null if unable to convert. */
+  private String getCharArrayStringVal(List<? extends ExpressionTree> initializers) {
+    boolean allLiterals = true;
+    StringBuilder stringVal = new StringBuilder();
+    for (ExpressionTree e : initializers) {
+      Range range =
+          atypeFactory.getRange(
+              atypeFactory.getAnnotatedType(e).getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL));
+      if (range != null && range.from == range.to) {
+        char charVal = (char) range.from;
+        stringVal.append(charVal);
+      } else {
+        allLiterals = false;
+        break;
+      }
+    }
+    if (allLiterals) {
+      return stringVal.toString();
+    }
+    // If any part of the initializer isn't known,
+    // the stringval isn't known.
+    return null;
+  }
+
+  @Override
+  public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror atm) {
+    if (handledByValueChecker(atm)) {
+      AnnotationMirror oldAnno =
+          atypeFactory
+              .getAnnotatedType(tree.getExpression())
+              .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL);
+      if (oldAnno == null) {
+        return null;
+      }
+      TypeMirror newType = atm.getUnderlyingType();
+      AnnotationMirror newAnno;
+      Range range;
+
+      if (TypesUtils.isString(newType) || newType.getKind() == TypeKind.ARRAY) {
+        // Strings and arrays do not allow conversions
+        newAnno = oldAnno;
+      } else if (atypeFactory.isIntRange(oldAnno)
+          && (range = atypeFactory.getRange(oldAnno))
+              .isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) {
+        Class<?> newClass = TypesUtils.getClassFromType(newType);
+        if (newClass == String.class) {
+          newAnno = atypeFactory.UNKNOWNVAL;
+        } else if (newClass == Boolean.class || newClass == boolean.class) {
+          throw new UnsupportedOperationException(
+              "ValueAnnotatedTypeFactory: can't convert int to boolean");
+        } else {
+          newAnno = atypeFactory.createIntRangeAnnotation(NumberUtils.castRange(newType, range));
+        }
+      } else {
+        List<?> values = ValueCheckerUtils.getValuesCastedToType(oldAnno, newType, atypeFactory);
+        newAnno = atypeFactory.createResultingAnnotation(atm.getUnderlyingType(), values);
+      }
+      atm.addMissingAnnotations(Collections.singleton(newAnno));
+    } else if (atm.getKind() == TypeKind.ARRAY) {
+      if (tree.getExpression().getKind() == Tree.Kind.NULL_LITERAL) {
+        atm.addMissingAnnotations(Collections.singleton(atypeFactory.BOTTOMVAL));
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Get the "value" field of the given annotation, casted to the given type. Empty list means no
+   * value is possible (dead code). Null means no information is known -- any value is possible.
+   */
+  private List<?> getValues(AnnotatedTypeMirror type, TypeMirror castTo) {
+    AnnotationMirror anno = type.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL);
+    if (anno == null) {
+      // If type is an AnnotatedTypeVariable (or other type without a primary annotation)
+      // then anno will be null. It would be safe to use the annotation on the upper
+      // bound; however, unless the upper bound was explicitly annotated, it will be
+      // unknown.  AnnotatedTypes.findEffectiveAnnotationInHierarchy(, toSearch, top)
+      return null;
+    }
+    return ValueCheckerUtils.getValuesCastedToType(anno, castTo, atypeFactory);
+  }
+
+  @Override
+  public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
+    if (!handledByValueChecker(type)) {
+      return null;
+    }
+    Object value = tree.getValue();
+    switch (tree.getKind()) {
+      case BOOLEAN_LITERAL:
+        AnnotationMirror boolAnno =
+            atypeFactory.createBooleanAnnotation(Collections.singletonList((Boolean) value));
+        type.replaceAnnotation(boolAnno);
+        return null;
+
+      case CHAR_LITERAL:
+        AnnotationMirror charAnno =
+            atypeFactory.createCharAnnotation(Collections.singletonList((Character) value));
+        type.replaceAnnotation(charAnno);
+        return null;
+
+      case DOUBLE_LITERAL:
+      case FLOAT_LITERAL:
+      case INT_LITERAL:
+      case LONG_LITERAL:
+        AnnotationMirror numberAnno =
+            atypeFactory.createNumberAnnotationMirror(Collections.singletonList((Number) value));
+        type.replaceAnnotation(numberAnno);
+        return null;
+      case STRING_LITERAL:
+        AnnotationMirror stringAnno =
+            atypeFactory.createStringAnnotation(Collections.singletonList((String) value));
+        type.replaceAnnotation(stringAnno);
+        return null;
+      default:
+        return null;
+    }
+  }
+
+  /**
+   * Given a MemberSelectTree representing a method call, return true if the method's declaration is
+   * annotated with {@code @StaticallyExecutable}.
+   */
+  private boolean methodIsStaticallyExecutable(Element method) {
+    return atypeFactory.getDeclAnnotation(method, StaticallyExecutable.class) != null;
+  }
+
+  /**
+   * Returns the Range of the Math.min or Math.max method, or null if the argument is none of these
+   * methods or their arguments are not annotated in ValueChecker hierarchy.
+   *
+   * @return the Range of the Math.min or Math.max method, or null if the argument is none of these
+   *     methods or their arguments are not annotated in ValueChecker hierarchy
+   */
+  private Range getRangeForMathMinMax(MethodInvocationTree tree) {
+    if (atypeFactory.getMethodIdentifier().isMathMin(tree, atypeFactory.getProcessingEnv())) {
+      AnnotatedTypeMirror arg1 = atypeFactory.getAnnotatedType(tree.getArguments().get(0));
+      AnnotatedTypeMirror arg2 = atypeFactory.getAnnotatedType(tree.getArguments().get(1));
+      Range rangeArg1 =
+          atypeFactory.getRange(arg1.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL));
+      Range rangeArg2 =
+          atypeFactory.getRange(arg2.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL));
+      if (rangeArg1 != null && rangeArg2 != null) {
+        return rangeArg1.min(rangeArg2);
+      }
+    } else if (atypeFactory
+        .getMethodIdentifier()
+        .isMathMax(tree, atypeFactory.getProcessingEnv())) {
+      AnnotatedTypeMirror arg1 = atypeFactory.getAnnotatedType(tree.getArguments().get(0));
+      AnnotatedTypeMirror arg2 = atypeFactory.getAnnotatedType(tree.getArguments().get(1));
+      Range rangeArg1 =
+          atypeFactory.getRange(arg1.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL));
+      Range rangeArg2 =
+          atypeFactory.getRange(arg2.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL));
+      if (rangeArg1 != null && rangeArg2 != null) {
+        return rangeArg1.max(rangeArg2);
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
+    if (type.hasAnnotation(atypeFactory.UNKNOWNVAL)) {
+      Range range = getRangeForMathMinMax(tree);
+      if (range != null) {
+        type.replaceAnnotation(atypeFactory.createIntRangeAnnotation(range));
+      }
+    }
+
+    if (atypeFactory
+        .getMethodIdentifier()
+        .isArraysCopyOfInvocation(tree, atypeFactory.getProcessingEnv())) {
+      List<? extends ExpressionTree> args = tree.getArguments();
+      Range range =
+          ValueCheckerUtils.getPossibleValues(
+              atypeFactory.getAnnotatedType(args.get(1)), atypeFactory);
+      if (range != null) {
+        type.replaceAnnotation(atypeFactory.createArrayLenRangeAnnotation(range));
+      }
+    }
+
+    if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree))
+        || !handledByValueChecker(type)) {
+      return null;
+    }
+
+    if (atypeFactory
+        .getMethodIdentifier()
+        .isStringLengthInvocation(tree, atypeFactory.getProcessingEnv())) {
+      AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(tree);
+      AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(receiverType);
+      if (resultAnno != null) {
+        type.replaceAnnotation(resultAnno);
+      }
+      return null;
+    }
+
+    if (atypeFactory
+        .getMethodIdentifier()
+        .isArrayGetLengthInvocation(tree, atypeFactory.getProcessingEnv())) {
+      List<? extends ExpressionTree> args = tree.getArguments();
+      AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(args.get(0));
+      AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(argType);
+      if (resultAnno != null) {
+        type.replaceAnnotation(resultAnno);
+      }
+      return null;
+    }
+
+    // Get argument values
+    List<? extends ExpressionTree> arguments = tree.getArguments();
+    ArrayList<List<?>> argValues;
+    if (arguments.isEmpty()) {
+      argValues = null;
+    } else {
+      argValues = new ArrayList<>(arguments.size());
+      for (ExpressionTree argument : arguments) {
+        AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argument);
+        List<?> values = getValues(argType, argType.getUnderlyingType());
+        if (values == null || values.isEmpty()) {
+          // Values aren't known, so don't try to evaluate the method.
+          return null;
+        }
+        argValues.add(values);
+      }
+    }
+
+    // Get receiver values
+    AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree);
+    List<?> receiverValues;
+
+    if (receiver != null && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) {
+      receiverValues = getValues(receiver, receiver.getUnderlyingType());
+      if (receiverValues == null || receiverValues.isEmpty()) {
+        // Values aren't known, so don't try to evaluate the method.
+        return null;
+      }
+    } else {
+      receiverValues = null;
+    }
+
+    // Evaluate method
+    List<?> returnValues =
+        atypeFactory.evaluator.evaluateMethodCall(argValues, receiverValues, tree);
+    if (returnValues == null) {
+      return null;
+    }
+    AnnotationMirror returnType =
+        atypeFactory.createResultingAnnotation(type.getUnderlyingType(), returnValues);
+    type.replaceAnnotation(returnType);
+
+    return null;
+  }
+
+  @Override
+  public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) {
+    if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree))
+        || !handledByValueChecker(type)) {
+      return null;
+    }
+
+    // get argument values
+    List<? extends ExpressionTree> arguments = tree.getArguments();
+    ArrayList<List<?>> argValues;
+    if (arguments.isEmpty()) {
+      argValues = null;
+    } else {
+      argValues = new ArrayList<>(arguments.size());
+      for (ExpressionTree argument : arguments) {
+        AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argument);
+        List<?> values = getValues(argType, argType.getUnderlyingType());
+        if (values == null || values.isEmpty()) {
+          // Values aren't known, so don't try to evaluate the method.
+          return null;
+        }
+        argValues.add(values);
+      }
+    }
+
+    // Evaluate method
+    List<?> returnValues =
+        atypeFactory.evaluator.evaluteConstructorCall(argValues, tree, type.getUnderlyingType());
+    if (returnValues == null) {
+      return null;
+    }
+    AnnotationMirror returnType =
+        atypeFactory.createResultingAnnotation(type.getUnderlyingType(), returnValues);
+    type.replaceAnnotation(returnType);
+
+    return null;
+  }
+
+  @Override
+  public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) {
+    visitFieldAccess(tree, type);
+    visitEnumConstant(tree, type);
+
+    if (TreeUtils.isArrayLengthAccess(tree)) {
+      // The field access is to the length field, as in "someArrayExpression.length"
+      AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(tree.getExpression());
+      if (receiverType.getKind() == TypeKind.ARRAY) {
+        AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(receiverType);
+        if (resultAnno != null) {
+          type.replaceAnnotation(resultAnno);
+        }
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Visit a tree that might be a field access.
+   *
+   * @param tree a tree that might be a field access. It is either a MemberSelectTree or an
+   *     IdentifierTree (if the programmer omitted the leading `this.`).
+   * @param type its type
+   */
+  private void visitFieldAccess(ExpressionTree tree, AnnotatedTypeMirror type) {
+    if (!TreeUtils.isFieldAccess(tree) || !handledByValueChecker(type)) {
+      return;
+    }
+
+    VariableElement fieldElement = (VariableElement) TreeUtils.elementFromTree(tree);
+    Object value = fieldElement.getConstantValue();
+    if (value != null) {
+      // The field is a compile-time constant.
+      type.replaceAnnotation(
+          atypeFactory.createResultingAnnotation(type.getUnderlyingType(), value));
+      return;
+    }
+    if (ElementUtils.isStatic(fieldElement) && ElementUtils.isFinal(fieldElement)) {
+      // The field is static and final, but its declaration does not initialize it to a
+      // compile-time constant.  Obtain its value reflectively.
+      Element classElement = fieldElement.getEnclosingElement();
+      if (classElement != null) {
+        @SuppressWarnings("signature" // TODO: bug in ValueAnnotatedTypeFactory.
+        // evaluateStaticFieldAccess requires a @ClassGetName but this passes a
+        // @FullyQualifiedName.  They differ for inner classes.
+        )
+        @BinaryName String classname = ElementUtils.getQualifiedClassName(classElement).toString();
+        @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Name.toString()
+        @Identifier String fieldName = fieldElement.getSimpleName().toString();
+        value = atypeFactory.evaluator.evaluateStaticFieldAccess(classname, fieldName, tree);
+        if (value != null) {
+          type.replaceAnnotation(
+              atypeFactory.createResultingAnnotation(type.getUnderlyingType(), value));
+        }
+        return;
+      }
+    }
+
+    return;
+  }
+
+  /** Returns true iff the given type is in the domain of the Constant Value Checker. */
+  private boolean handledByValueChecker(AnnotatedTypeMirror type) {
+    TypeMirror tm = type.getUnderlyingType();
+    /* TODO: compare performance to the more readable.
+    return TypesUtils.isPrimitive(tm)
+            || TypesUtils.isBoxedPrimitive(tm)
+            || TypesUtils.isString(tm)
+            || tm.toString().equals("char[]"); // Why?
+    */
+    return COVERED_CLASS_STRINGS.contains(tm.toString());
+  }
+
+  @Override
+  public Void visitConditionalExpression(
+      ConditionalExpressionTree node, AnnotatedTypeMirror annotatedTypeMirror) {
+    // Work around for https://github.com/typetools/checker-framework/issues/602.
+    annotatedTypeMirror.replaceAnnotation(atypeFactory.UNKNOWNVAL);
+    return null;
+  }
+
+  // An IdentifierTree can be a local variable (including formals, exception parameters, etc.) or
+  // an implicit field access (where `this.` is omitted).
+  // A field access is always an IdentifierTree or MemberSelectTree.
+  @Override
+  public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) {
+    visitFieldAccess(tree, type);
+    visitEnumConstant(tree, type);
+    return null;
+  }
+
+  /**
+   * Default the type of an enum constant {@code E.V} to {@code @StringVal("V")}. Does nothing if
+   * the argument is not an enum constant.
+   *
+   * @param tree an Identifier or MemberSelect tree that might be an enum
+   * @param type the type of that tree
+   */
+  private void visitEnumConstant(ExpressionTree tree, AnnotatedTypeMirror type) {
+    Element decl = TreeUtils.elementFromTree(tree);
+    if (decl.getKind() != ElementKind.ENUM_CONSTANT) {
+      return;
+    }
+
+    Name id;
+    switch (tree.getKind()) {
+      case MEMBER_SELECT:
+        id = ((MemberSelectTree) tree).getIdentifier();
+        break;
+      case IDENTIFIER:
+        id = ((IdentifierTree) tree).getName();
+        break;
+      default:
+        throw new BugInCF("unexpected kind of enum constant use tree: " + tree.getKind());
+    }
+    AnnotationMirror stringVal =
+        atypeFactory.createStringAnnotation(Collections.singletonList(id.toString()));
+    type.replaceAnnotation(stringVal);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTypeAnnotator.java b/framework/src/main/java/org/checkerframework/common/value/ValueTypeAnnotator.java
new file mode 100644
index 0000000..e52b702
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/ValueTypeAnnotator.java
@@ -0,0 +1,160 @@
+package org.checkerframework.common.value;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.common.value.util.Range;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TypeKindUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Performs pre-processing on annotations written by users, replacing illegal annotations by legal
+ * ones.
+ */
+class ValueTypeAnnotator extends TypeAnnotator {
+
+  /** The type factory to use. Shadows the field from the superclass with a more specific type. */
+  @SuppressWarnings("HidingField")
+  protected final ValueAnnotatedTypeFactory typeFactory;
+
+  /**
+   * Construct a new ValueTypeAnnotator.
+   *
+   * @param typeFactory the type factory to use
+   */
+  protected ValueTypeAnnotator(ValueAnnotatedTypeFactory typeFactory) {
+    super(typeFactory);
+    this.typeFactory = typeFactory;
+  }
+
+  @Override
+  protected Void scan(AnnotatedTypeMirror type, Void aVoid) {
+    replaceWithNewAnnoInSpecialCases(type);
+    return super.scan(type, aVoid);
+  }
+
+  /**
+   * This method performs pre-processing on annotations written by users.
+   *
+   * <p>If any *Val annotation has &gt; MAX_VALUES number of values provided, replaces the
+   * annotation by @IntRange for integral types, @ArrayLenRange for arrays, @ArrayLen
+   * or @ArrayLenRange for strings, and @UnknownVal for all other types. Works together with {@link
+   * ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} which issues warnings
+   * to users in these cases.
+   *
+   * <p>If any @IntRange or @ArrayLenRange annotation has incorrect parameters, e.g. the value
+   * "from" is greater than the value "to", replaces the annotation by {@code @BottomVal}. The
+   * {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} raises an error
+   * to users if the annotation was user-written.
+   *
+   * <p>If any @ArrayLen annotation has a negative number, replaces the annotation by {@code
+   * BottomVal}. The {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)}
+   * raises an error to users if the annotation was user-written.
+   *
+   * <p>If a user only writes one side of an {@code IntRange} annotation, this method also computes
+   * an appropriate default based on the underlying type for the other side of the range. For
+   * instance, if the user writes {@code @IntRange(from = 1) short x;} then this method will
+   * translate the annotation to {@code @IntRange(from = 1, to = Short.MAX_VALUE}.
+   */
+  private void replaceWithNewAnnoInSpecialCases(AnnotatedTypeMirror atm) {
+    AnnotationMirror anno = atm.getAnnotationInHierarchy(typeFactory.UNKNOWNVAL);
+    if (anno == null || anno.getElementValues().isEmpty()) {
+      return;
+    }
+
+    if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTVAL_NAME)) {
+      List<Long> values = typeFactory.getIntValues(anno);
+      if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) {
+        atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(Range.create(values)));
+      }
+    } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) {
+      List<Integer> values = typeFactory.getArrayLength(anno);
+      if (values.isEmpty()) {
+        atm.replaceAnnotation(typeFactory.BOTTOMVAL);
+      } else if (Collections.min(values) < 0) {
+        atm.replaceAnnotation(typeFactory.BOTTOMVAL);
+      } else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) {
+        atm.replaceAnnotation(typeFactory.createArrayLenRangeAnnotation(Range.create(values)));
+      }
+    } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) {
+      TypeMirror underlyingType = atm.getUnderlyingType();
+      // If the underlying type is neither a primitive integral type nor boxed integral type,
+      // return without making changes. TypesUtils.isIntegralPrimitiveOrBoxed fails if passed
+      // a non-primitive type that is not a declared type, so it cannot be called directly.
+      if (!TypeKindUtils.isIntegral(underlyingType.getKind())
+          && (underlyingType.getKind() != TypeKind.DECLARED
+              || !TypesUtils.isIntegralPrimitiveOrBoxed(underlyingType))) {
+        return;
+      }
+
+      // Compute appropriate defaults for integral ranges.
+      long from = typeFactory.getFromValueFromIntRange(atm);
+      long to = typeFactory.getToValueFromIntRange(atm);
+
+      if (from > to) {
+        // `from > to` either indicates a user error when writing an annotation or an error in the
+        // checker's implementation. `-from` should always be <= to. ValueVisitor#validateType will
+        // issue an error.
+        atm.replaceAnnotation(typeFactory.BOTTOMVAL);
+      } else {
+        // Always do a replacement of the annotation here so that the defaults calculated above are
+        // correctly added to the annotation (assuming the annotation is well-formed).
+        atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(from, to));
+      }
+    } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) {
+      int from = typeFactory.getArrayLenRangeFromValue(anno);
+      int to = typeFactory.getArrayLenRangeToValue(anno);
+      if (from > to) {
+        // `from > to` either indicates a user error when writing an annotation or an error in the
+        // checker's implementation `-from` should always be <= to. ValueVisitor#validateType will
+        // issue an error.
+        atm.replaceAnnotation(typeFactory.BOTTOMVAL);
+      } else if (from < 0) {
+        // No array can have a length less than 0. Any time the type includes a from
+        // less than zero, it must indicate imprecision in the checker.
+        atm.replaceAnnotation(typeFactory.createArrayLenRangeAnnotation(0, to));
+      }
+    } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) {
+      // The annotation is StringVal. If there are too many elements,
+      // ArrayLen or ArrayLenRange is used.
+      List<String> values = typeFactory.getStringValues(anno);
+
+      if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) {
+        List<Integer> lengths = ValueCheckerUtils.getLengthsForStringValues(values);
+        atm.replaceAnnotation(typeFactory.createArrayLenAnnotation(lengths));
+      }
+
+    } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME)) {
+      // If the annotation contains an invalid regex, replace it with bottom. ValueVisitor
+      // will issue a warning where the annotation was written.
+      List<String> regexes =
+          AnnotationUtils.getElementValueArray(
+              anno, typeFactory.matchesRegexValueElement, String.class);
+      for (String regex : regexes) {
+        try {
+          Pattern.compile(regex);
+        } catch (PatternSyntaxException pse) {
+          atm.replaceAnnotation(typeFactory.BOTTOMVAL);
+          break;
+        }
+      }
+    } else {
+      // In here the annotation is @*Val where (*) is not Int, String but other types
+      // (Bool, Double, or Enum).
+      // Therefore we extract its values in a generic way to check its size.
+      @SuppressWarnings("deprecation") // concrete annotation class is not known
+      List<Object> values =
+          AnnotationUtils.getElementValueArray(anno, "value", Object.class, false);
+      if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) {
+        atm.replaceAnnotation(typeFactory.UNKNOWNVAL);
+      }
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java
new file mode 100644
index 0000000..7760042
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java
@@ -0,0 +1,431 @@
+package org.checkerframework.common.value;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.TypeCastTree;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.value.qual.IntRangeFromGTENegativeOne;
+import org.checkerframework.common.value.qual.IntRangeFromNonNegative;
+import org.checkerframework.common.value.qual.IntRangeFromPositive;
+import org.checkerframework.common.value.qual.StaticallyExecutable;
+import org.checkerframework.common.value.util.NumberUtils;
+import org.checkerframework.common.value.util.Range;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** Visitor for the Constant Value type system. */
+public class ValueVisitor extends BaseTypeVisitor<ValueAnnotatedTypeFactory> {
+
+  public ValueVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  /**
+   * ValueVisitor overrides this method so that it does not have to check variables annotated with
+   * the {@link IntRangeFromPositive} annotation, the {@link IntRangeFromNonNegative} annotation, or
+   * the {@link IntRangeFromGTENegativeOne} annotation. This annotation is only introduced by the
+   * Index Checker's lower bound annotations. It is safe to defer checking of these values to the
+   * Index Checker because this is only introduced for explicitly-written {@code
+   * org.checkerframework.checker.index.qual.Positive}, explicitly-written {@code
+   * org.checkerframework.checker.index.qual.NonNegative}, and explicitly-written {@code
+   * org.checkerframework.checker.index.qual.GTENegativeOne} annotations, which must be checked by
+   * the Lower Bound Checker.
+   *
+   * @param varType the annotated type of the lvalue (usually a variable)
+   * @param valueExp the AST node for the rvalue (the new value)
+   * @param errorKey the error message key to use if the check fails
+   * @param extraArgs arguments to the error message key, before "found" and "expected" types
+   */
+  @Override
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      ExpressionTree valueExp,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+
+    replaceSpecialIntRangeAnnotations(varType);
+    super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs);
+  }
+
+  @Override
+  @FormatMethod
+  protected void commonAssignmentCheck(
+      AnnotatedTypeMirror varType,
+      AnnotatedTypeMirror valueType,
+      Tree valueTree,
+      @CompilerMessageKey String errorKey,
+      Object... extraArgs) {
+
+    replaceSpecialIntRangeAnnotations(varType);
+
+    if (valueType.getKind() == TypeKind.CHAR
+        && valueType.hasAnnotation(getTypeFactory().UNKNOWNVAL)) {
+      valueType.addAnnotation(getTypeFactory().createIntRangeAnnotation(Range.CHAR_EVERYTHING));
+    }
+
+    super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
+  }
+
+  /**
+   * Return types for methods that are annotated with {@code @IntRangeFromX} annotations need to be
+   * replaced with {@code @UnknownVal}. See the documentation on {@link
+   * #commonAssignmentCheck(AnnotatedTypeMirror, ExpressionTree, String, Object[])
+   * commonAssignmentCheck}.
+   *
+   * <p>A separate override is necessary because checkOverride doesn't actually use the
+   * commonAssignmentCheck.
+   */
+  @Override
+  protected boolean checkOverride(
+      MethodTree overriderTree,
+      AnnotatedTypeMirror.AnnotatedExecutableType overrider,
+      AnnotatedTypeMirror.AnnotatedDeclaredType overridingType,
+      AnnotatedTypeMirror.AnnotatedExecutableType overridden,
+      AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType) {
+
+    replaceSpecialIntRangeAnnotations(overrider);
+    replaceSpecialIntRangeAnnotations(overridden);
+
+    return super.checkOverride(
+        overriderTree, overrider, overridingType, overridden, overriddenType);
+  }
+
+  /**
+   * Replaces any {@code IntRangeFromX} annotations with {@code @UnknownVal}. This is used to
+   * prevent these annotations from being required on the left hand side of assignments.
+   *
+   * @param varType an annotated type mirror that may contain IntRangeFromX annotations, which will
+   *     be used on the lhs of an assignment or pseudo-assignment
+   */
+  private void replaceSpecialIntRangeAnnotations(AnnotatedTypeMirror varType) {
+    AnnotatedTypeScanner<Void, Void> replaceSpecialIntRangeAnnotations =
+        new AnnotatedTypeScanner<Void, Void>() {
+          @Override
+          protected Void scan(AnnotatedTypeMirror type, Void p) {
+            if (type.hasAnnotation(IntRangeFromPositive.class)
+                || type.hasAnnotation(IntRangeFromNonNegative.class)
+                || type.hasAnnotation(IntRangeFromGTENegativeOne.class)) {
+              type.replaceAnnotation(atypeFactory.UNKNOWNVAL);
+            }
+            return super.scan(type, p);
+          }
+
+          @Override
+          public Void visitDeclared(AnnotatedDeclaredType type, Void p) {
+            // Don't call super so that the type arguments are not visited.
+            if (type.getEnclosingType() != null) {
+              scan(type.getEnclosingType(), p);
+            }
+
+            return null;
+          }
+        };
+    replaceSpecialIntRangeAnnotations.visit(varType);
+  }
+
+  @Override
+  protected ValueAnnotatedTypeFactory createTypeFactory() {
+    return new ValueAnnotatedTypeFactory(checker);
+  }
+
+  /**
+   * Warns about malformed constant-value annotations.
+   *
+   * <p>Issues an error if any @IntRange annotation has its 'from' value greater than 'to' value.
+   *
+   * <p>Issues an error if any constant-value annotation has no arguments.
+   *
+   * <p>Issues a warning if any constant-value annotation has &gt; MAX_VALUES arguments.
+   *
+   * <p>Issues a warning if any @ArrayLen/@ArrayLenRange annotations contain a negative array
+   * length.
+   *
+   * <p>Issues a warning if any {@literal @}MatchesRegex annotation contains an invalid regular
+   * expression.
+   */
+  /* Implementation note: the ValueTypeAnnotator replaces such invalid annotations with valid ones.
+   * Therefore, the usual validation in #validateType cannot perform this validation.
+   * These warnings cannot be issued in the ValueAnnotatedTypeFactory, because the conversions
+   * might happen multiple times.
+   * On the other hand, not all validations can happen here, because only the annotations are
+   * available, not the full types.
+   * Therefore, some validation is still done in #validateType below.
+   */
+  @Override
+  public Void visitAnnotation(AnnotationTree node, Void p) {
+    List<? extends ExpressionTree> args = node.getArguments();
+
+    if (args.isEmpty()) {
+      // Nothing to do if there are no annotation arguments.
+      return super.visitAnnotation(node, p);
+    }
+
+    AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(node);
+    switch (AnnotationUtils.annotationName(anno)) {
+      case ValueAnnotatedTypeFactory.INTRANGE_NAME:
+        // If there are 2 arguments, issue an error if from.greater.than.to.
+        // If there are fewer than 2 arguments, we needn't worry about this problem because the
+        // other argument will be defaulted to Long.MIN_VALUE or Long.MAX_VALUE accordingly.
+        if (args.size() == 2) {
+          long from = getTypeFactory().getIntRangeFromValue(anno);
+          long to = getTypeFactory().getIntRangeToValue(anno);
+          if (from > to) {
+            checker.reportError(node, "from.greater.than.to");
+            return null;
+          }
+        }
+        break;
+      case ValueAnnotatedTypeFactory.ARRAYLEN_NAME:
+      case ValueAnnotatedTypeFactory.BOOLVAL_NAME:
+      case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME:
+      case ValueAnnotatedTypeFactory.INTVAL_NAME:
+      case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
+        @SuppressWarnings("deprecation") // concrete annotation class is not known
+        List<Object> values =
+            AnnotationUtils.getElementValueArray(anno, "value", Object.class, false);
+
+        if (values.isEmpty()) {
+          checker.reportWarning(node, "no.values.given");
+          return null;
+        } else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) {
+          checker.reportWarning(
+              node,
+              (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTVAL_NAME)
+                  ? "too.many.values.given.int"
+                  : "too.many.values.given"),
+              ValueAnnotatedTypeFactory.MAX_VALUES);
+          return null;
+        } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) {
+          List<Integer> arrayLens = getTypeFactory().getArrayLength(anno);
+          if (Collections.min(arrayLens) < 0) {
+            checker.reportWarning(node, "negative.arraylen", Collections.min(arrayLens));
+            return null;
+          }
+        }
+        break;
+      case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME:
+        long from = getTypeFactory().getArrayLenRangeFromValue(anno);
+        long to = getTypeFactory().getArrayLenRangeToValue(anno);
+        if (from > to) {
+          checker.reportError(node, "from.greater.than.to");
+          return null;
+        } else if (from < 0) {
+          checker.reportWarning(node, "negative.arraylen", from);
+          return null;
+        }
+        break;
+      case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME:
+        List<String> regexes =
+            AnnotationUtils.getElementValueArray(
+                anno, atypeFactory.matchesRegexValueElement, String.class);
+        for (String regex : regexes) {
+          try {
+            Pattern.compile(regex);
+          } catch (PatternSyntaxException pse) {
+            checker.reportWarning(node, "invalid.matches.regex", pse.getMessage());
+          }
+        }
+        break;
+      default:
+        // Do nothing.
+    }
+
+    return super.visitAnnotation(node, p);
+  }
+
+  @Override
+  public Void visitTypeCast(TypeCastTree node, Void p) {
+    if (node.getExpression().getKind() == Kind.NULL_LITERAL) {
+      return null;
+    }
+
+    AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(node);
+    AnnotationMirror castAnno = castType.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL);
+    AnnotationMirror exprAnno =
+        atypeFactory
+            .getAnnotatedType(node.getExpression())
+            .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL);
+
+    // It is always legal to cast to an IntRange type that includes all values
+    // of the underlying type. Do not warn about such casts.
+    // I.e. do not warn if an @IntRange(...) int is casted
+    // to a @IntRange(from = Byte.MIN_VALUE, to = Byte.MAX_VALUE byte).
+    if (castAnno != null
+        && exprAnno != null
+        && atypeFactory.isIntRange(castAnno)
+        && atypeFactory.isIntRange(exprAnno)) {
+      final Range castRange = atypeFactory.getRange(castAnno);
+      final TypeKind castTypeKind = castType.getKind();
+      if (castTypeKind == TypeKind.BYTE && castRange.isByteEverything()) {
+        return p;
+      }
+      if (castTypeKind == TypeKind.CHAR && castRange.isCharEverything()) {
+        return p;
+      }
+      if (castTypeKind == TypeKind.SHORT && castRange.isShortEverything()) {
+        return p;
+      }
+      if (castTypeKind == TypeKind.INT && castRange.isIntEverything()) {
+        return p;
+      }
+      if (castTypeKind == TypeKind.LONG && castRange.isLongEverything()) {
+        return p;
+      }
+      if (Range.ignoreOverflow) {
+        // Range.ignoreOverflow is only set if this checker is ignoring overflow.
+        // In that case, do not warn if the range of the expression encompasses
+        // the whole type being casted to (i.e. the warning is actually about overflow).
+        Range exprRange = atypeFactory.getRange(exprAnno);
+        if (castTypeKind == TypeKind.BYTE
+            || castTypeKind == TypeKind.CHAR
+            || castTypeKind == TypeKind.SHORT
+            || castTypeKind == TypeKind.INT) {
+          exprRange = NumberUtils.castRange(castType.getUnderlyingType(), exprRange);
+        }
+        if (castRange.equals(exprRange)) {
+          return p;
+        }
+      }
+    }
+    return super.visitTypeCast(node, p);
+  }
+
+  /**
+   * Overridden to issue errors at the appropriate place if an {@code IntRange} or {@code
+   * ArrayLenRange} annotation has {@code from > to}. {@code from > to} either indicates a user
+   * error when writing an annotation or an error in the checker's implementation, as {@code from}
+   * should always be {@code <= to}. Note that additional checks are performed in {@link
+   * #visitAnnotation(AnnotationTree, Void)}.
+   *
+   * @see #visitAnnotation(AnnotationTree, Void)
+   */
+  @Override
+  public boolean validateType(Tree tree, AnnotatedTypeMirror type) {
+    replaceSpecialIntRangeAnnotations(type);
+    if (!super.validateType(tree, type)) {
+      return false;
+    }
+
+    AnnotationMirror anno = type.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL);
+    if (anno == null) {
+      return false;
+    }
+
+    if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) {
+      if (TypesUtils.isIntegralPrimitiveOrBoxed(type.getUnderlyingType())) {
+        long from = atypeFactory.getFromValueFromIntRange(type);
+        long to = atypeFactory.getToValueFromIntRange(type);
+        if (from > to) {
+          checker.reportError(tree, "from.greater.than.to");
+          return false;
+        }
+      } else {
+        TypeMirror utype = type.getUnderlyingType();
+        if (!TypesUtils.isObject(utype)
+            && !TypesUtils.isDeclaredOfName(utype, "java.lang.Number")
+            && !TypesUtils.isFloatingPoint(utype)) {
+          checker.reportError(tree, "annotation.intrange.on.noninteger");
+          return false;
+        }
+      }
+    } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) {
+      long from = getTypeFactory().getArrayLenRangeFromValue(anno);
+      long to = getTypeFactory().getArrayLenRangeToValue(anno);
+      if (from > to) {
+        checker.reportError(tree, "from.greater.than.to");
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Returns true if an expression of the given type can be a compile-time constant value.
+   *
+   * @param tm a type
+   * @return true if an expression of the given type can be a compile-time constant value
+   */
+  private boolean canBeConstant(TypeMirror tm) {
+    return TypesUtils.isPrimitive(tm)
+        || TypesUtils.isBoxedPrimitive(tm)
+        || TypesUtils.isString(tm)
+        || (tm.getKind() == TypeKind.ARRAY && canBeConstant(((ArrayType) tm).getComponentType()));
+  }
+
+  @Override
+  public Void visitMethod(MethodTree node, Void p) {
+    super.visitMethod(node, p);
+
+    ExecutableElement method = TreeUtils.elementFromDeclaration(node);
+    if (atypeFactory.getDeclAnnotation(method, StaticallyExecutable.class) != null) {
+      // The method is annotated as @StaticallyExecutable.
+      if (atypeFactory.getDeclAnnotation(method, Pure.class) == null) {
+        checker.reportWarning(node, "statically.executable.not.pure");
+      }
+      TypeMirror returnType = method.getReturnType();
+      if (returnType.getKind() != TypeKind.VOID && !canBeConstant(returnType)) {
+        checker.reportError(node, "statically.executable.nonconstant.return.type", returnType);
+      }
+
+      // Ways to determine the receiver type.
+      // 1. This definition of receiverType is null when receiver is implicit and method has
+      //    class com.sun.tools.javac.code.Symbol$MethodSymbol.  WHY?
+      //        TypeMirror receiverType = method.getReceiverType();
+      //    The same is true of TreeUtils.elementFromDeclaration(node).getReceiverType()
+      //    which seems to conflict with ExecutableType's documentation.
+      // 2. Can't use the tree, because the receiver might not be explicit.
+      // 3. Check whether method is static and use the declaring class.  Doesn't handle all
+      //    cases, but handles the most common ones.
+      TypeMirror receiverType = method.getReceiverType();
+      // If the method is static, issue no warning.  This is incorrect in the case of a
+      // constructor or a static method in an inner class.
+      if (!ElementUtils.isStatic(method)) {
+        receiverType = ElementUtils.getType(ElementUtils.enclosingTypeElement(method));
+      }
+      if (receiverType != null
+          && receiverType.getKind() != TypeKind.NONE
+          && !canBeConstant(receiverType)) {
+        checker.reportError(
+            node,
+            "statically.executable.nonconstant.parameter.type",
+            "this (the receiver)",
+            returnType);
+      }
+
+      for (VariableElement param : method.getParameters()) {
+        TypeMirror paramType = param.asType();
+        if (paramType.getKind() != TypeKind.NONE && !canBeConstant(paramType)) {
+          checker.reportError(
+              node,
+              "statically.executable.nonconstant.parameter.type",
+              param.getSimpleName().toString(),
+              returnType);
+        }
+      }
+    }
+    return null;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/messages.properties b/framework/src/main/java/org/checkerframework/common/value/messages.properties
new file mode 100644
index 0000000..519013c
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/messages.properties
@@ -0,0 +1,22 @@
+### Error messages for ValueChecker
+method.find.failed.in.class=Failed to find a method named %s with argument types %s in class %s.
+method.find.failed=Failed to find a method named %s with argument types %s.
+method.evaluation.failed=Failed to invoke method %s for evaluation.
+method.evaluation.exception=Failed to evaluate method %s because it threw an exception: %s.
+class.find.failed=Failed to find class named %s: %s
+constructor.evaluation.failed=Failed to evaluate constructor for class %s with arguments %s.
+constructor.invocation.failed=Failed to invoke constructor for class.
+operator.unary.evaluation.failed=Failed to find unary operator %s with arguments %s.
+operator.binary.evaluation.failed=Failed to find binary operator %s with arguments %s.
+field.access.failed=Failed to access field %s in class %s: %s
+too.many.values.given=The maximum number of arguments permitted is %s.
+too.many.values.given.int=The maximum number of arguments permitted is %s.  Use @IntRange instead.
+no.values.given=No values specified.
+from.greater.than.to=The "from" value must be less than or equal to the "to" value.
+negative.arraylen=Negative array lengths are not allowed.%nfound: %s
+class.convert.failed=Cannot convert annotation %s to class %s"
+annotation.intrange.on.noninteger=@IntRange can only be used on integral types.
+statically.executable.not.pure=method is @StaticallyExecutable but is not @Pure
+statically.executable.nonconstant.parameter.type=parameter %s has type %s which cannot be constant
+statically.executable.nonconstant.return.type=return type %s cannot be constant
+invalid.matches.regex=Invalid regex in @MatchesRegex; Pattern.compile() reported: %s
diff --git a/framework/src/main/java/org/checkerframework/common/value/util/ByteMath.java b/framework/src/main/java/org/checkerframework/common/value/util/ByteMath.java
new file mode 100644
index 0000000..b4568ed
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/util/ByteMath.java
@@ -0,0 +1,385 @@
+package org.checkerframework.common.value.util;
+
+public class ByteMath extends NumberMath<Byte> {
+  byte number;
+
+  public ByteMath(byte i) {
+    number = i;
+  }
+
+  @Override
+  public Number plus(Number right) {
+    if (right instanceof Byte) {
+      return number + right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number + right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number + right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number + right.intValue();
+    }
+    if (right instanceof Long) {
+      return number + right.longValue();
+    }
+    if (right instanceof Short) {
+      return number + right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number minus(Number right) {
+    if (right instanceof Byte) {
+      return number - right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number - right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number - right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number - right.intValue();
+    }
+    if (right instanceof Long) {
+      return number - right.longValue();
+    }
+    if (right instanceof Short) {
+      return number - right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number times(Number right) {
+    if (right instanceof Byte) {
+      return number * right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number * right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number * right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number * right.intValue();
+    }
+    if (right instanceof Long) {
+      return number * right.longValue();
+    }
+    if (right instanceof Short) {
+      return number * right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number divide(Number right) {
+    if (isIntegralZero(right)) {
+      return null;
+    }
+    if (right instanceof Byte) {
+      return number / right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number / right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number / right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number / right.intValue();
+    }
+    if (right instanceof Long) {
+      return number / right.longValue();
+    }
+    if (right instanceof Short) {
+      return number / right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number remainder(Number right) {
+    if (isIntegralZero(right)) {
+      return null;
+    }
+    if (right instanceof Byte) {
+      return number % right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number % right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number % right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number % right.intValue();
+    }
+    if (right instanceof Long) {
+      return number % right.longValue();
+    }
+    if (right instanceof Short) {
+      return number % right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number shiftLeft(Number right) {
+    if (right instanceof Byte) {
+      return number << right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number << right.intValue();
+    }
+    if (right instanceof Long) {
+      return number << right.longValue();
+    }
+    if (right instanceof Short) {
+      return number << right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number signedShiftRight(Number right) {
+    if (right instanceof Byte) {
+      return number >> right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number >> right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >> right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >> right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number unsignedShiftRight(Number right) {
+    if (right instanceof Byte) {
+      return number >>> right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number >>> right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >>> right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >>> right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseAnd(Number right) {
+    if (right instanceof Byte) {
+      return number & right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number & right.intValue();
+    }
+    if (right instanceof Long) {
+      return number & right.longValue();
+    }
+    if (right instanceof Short) {
+      return number & right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseXor(Number right) {
+    if (right instanceof Byte) {
+      return number ^ right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number ^ right.intValue();
+    }
+    if (right instanceof Long) {
+      return number ^ right.longValue();
+    }
+    if (right instanceof Short) {
+      return number ^ right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseOr(Number right) {
+    if (right instanceof Byte) {
+      return number | right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number | right.intValue();
+    }
+    if (right instanceof Long) {
+      return number | right.longValue();
+    }
+    if (right instanceof Short) {
+      return number | right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number unaryPlus() {
+    return +number;
+  }
+
+  @Override
+  public Number unaryMinus() {
+    return -number;
+  }
+
+  @Override
+  public Number bitwiseComplement() {
+    return ~number;
+  }
+
+  @Override
+  public Boolean equalTo(Number right) {
+    if (right instanceof Byte) {
+      return number == right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number == right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number == right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number == right.intValue();
+    }
+    if (right instanceof Long) {
+      return number == right.longValue();
+    }
+    if (right instanceof Short) {
+      return number == right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean notEqualTo(Number right) {
+    if (right instanceof Byte) {
+      return number != right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number != right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number != right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number != right.intValue();
+    }
+    if (right instanceof Long) {
+      return number != right.longValue();
+    }
+    if (right instanceof Short) {
+      return number != right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean greaterThan(Number right) {
+    if (right instanceof Byte) {
+      return number > right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number > right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number > right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number > right.intValue();
+    }
+    if (right instanceof Long) {
+      return number > right.longValue();
+    }
+    if (right instanceof Short) {
+      return number > right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean greaterThanEq(Number right) {
+    if (right instanceof Byte) {
+      return number >= right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number >= right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number >= right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number >= right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >= right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >= right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean lessThan(Number right) {
+    if (right instanceof Byte) {
+      return number < right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number < right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number < right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number < right.intValue();
+    }
+    if (right instanceof Long) {
+      return number < right.longValue();
+    }
+    if (right instanceof Short) {
+      return number < right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean lessThanEq(Number right) {
+    if (right instanceof Byte) {
+      return number <= right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number <= right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number <= right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number <= right.intValue();
+    }
+    if (right instanceof Long) {
+      return number <= right.longValue();
+    }
+    if (right instanceof Short) {
+      return number <= right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/util/DoubleMath.java b/framework/src/main/java/org/checkerframework/common/value/util/DoubleMath.java
new file mode 100644
index 0000000..f4dbd76
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/util/DoubleMath.java
@@ -0,0 +1,307 @@
+package org.checkerframework.common.value.util;
+
+public class DoubleMath extends NumberMath<Double> {
+  double number;
+
+  public DoubleMath(double i) {
+    number = i;
+  }
+
+  @Override
+  public Number plus(Number right) {
+    if (right instanceof Byte) {
+      return number + right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number + right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number + right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number + right.intValue();
+    }
+    if (right instanceof Long) {
+      return number + right.longValue();
+    }
+    if (right instanceof Short) {
+      return number + right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number minus(Number right) {
+    if (right instanceof Byte) {
+      return number - right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number - right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number - right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number - right.intValue();
+    }
+    if (right instanceof Long) {
+      return number - right.longValue();
+    }
+    if (right instanceof Short) {
+      return number - right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number times(Number right) {
+    if (right instanceof Byte) {
+      return number * right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number * right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number * right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number * right.intValue();
+    }
+    if (right instanceof Long) {
+      return number * right.longValue();
+    }
+    if (right instanceof Short) {
+      return number * right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number divide(Number right) {
+    if (right instanceof Byte) {
+      return number / right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number / right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number / right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number / right.intValue();
+    }
+    if (right instanceof Long) {
+      return number / right.longValue();
+    }
+    if (right instanceof Short) {
+      return number / right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number remainder(Number right) {
+    if (right instanceof Byte) {
+      return number % right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number % right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number % right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number % right.intValue();
+    }
+    if (right instanceof Long) {
+      return number % right.longValue();
+    }
+    if (right instanceof Short) {
+      return number % right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number shiftLeft(Number right) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number signedShiftRight(Number right) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number unsignedShiftRight(Number right) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseAnd(Number right) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseXor(Number right) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseOr(Number right) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number unaryPlus() {
+    return +number;
+  }
+
+  @Override
+  public Number unaryMinus() {
+    return -number;
+  }
+
+  @Override
+  public Number bitwiseComplement() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean equalTo(Number right) {
+    if (right instanceof Byte) {
+      return number == right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number == right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number == right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number == right.intValue();
+    }
+    if (right instanceof Long) {
+      return number == right.longValue();
+    }
+    if (right instanceof Short) {
+      return number == right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean notEqualTo(Number right) {
+    if (right instanceof Byte) {
+      return number != right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number != right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number != right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number != right.intValue();
+    }
+    if (right instanceof Long) {
+      return number != right.longValue();
+    }
+    if (right instanceof Short) {
+      return number != right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean greaterThan(Number right) {
+    if (right instanceof Byte) {
+      return number > right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number > right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number > right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number > right.intValue();
+    }
+    if (right instanceof Long) {
+      return number > right.longValue();
+    }
+    if (right instanceof Short) {
+      return number > right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean greaterThanEq(Number right) {
+    if (right instanceof Byte) {
+      return number >= right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number >= right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number >= right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number >= right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >= right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >= right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean lessThan(Number right) {
+    if (right instanceof Byte) {
+      return number < right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number < right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number < right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number < right.intValue();
+    }
+    if (right instanceof Long) {
+      return number < right.longValue();
+    }
+    if (right instanceof Short) {
+      return number < right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean lessThanEq(Number right) {
+    if (right instanceof Byte) {
+      return number <= right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number <= right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number <= right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number <= right.intValue();
+    }
+    if (right instanceof Long) {
+      return number <= right.longValue();
+    }
+    if (right instanceof Short) {
+      return number <= right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/util/FloatMath.java b/framework/src/main/java/org/checkerframework/common/value/util/FloatMath.java
new file mode 100644
index 0000000..7e13213
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/util/FloatMath.java
@@ -0,0 +1,307 @@
+package org.checkerframework.common.value.util;
+
+public class FloatMath extends NumberMath<Float> {
+  float number;
+
+  public FloatMath(float i) {
+    number = i;
+  }
+
+  @Override
+  public Number plus(Number right) {
+    if (right instanceof Byte) {
+      return number + right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number + right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number + right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number + right.intValue();
+    }
+    if (right instanceof Long) {
+      return number + right.longValue();
+    }
+    if (right instanceof Short) {
+      return number + right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number minus(Number right) {
+    if (right instanceof Byte) {
+      return number - right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number - right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number - right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number - right.intValue();
+    }
+    if (right instanceof Long) {
+      return number - right.longValue();
+    }
+    if (right instanceof Short) {
+      return number - right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number times(Number right) {
+    if (right instanceof Byte) {
+      return number * right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number * right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number * right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number * right.intValue();
+    }
+    if (right instanceof Long) {
+      return number * right.longValue();
+    }
+    if (right instanceof Short) {
+      return number * right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number divide(Number right) {
+    if (right instanceof Byte) {
+      return number / right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number / right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number / right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number / right.intValue();
+    }
+    if (right instanceof Long) {
+      return number / right.longValue();
+    }
+    if (right instanceof Short) {
+      return number / right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number remainder(Number right) {
+    if (right instanceof Byte) {
+      return number % right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number % right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number % right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number % right.intValue();
+    }
+    if (right instanceof Long) {
+      return number % right.longValue();
+    }
+    if (right instanceof Short) {
+      return number % right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number shiftLeft(Number right) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number signedShiftRight(Number right) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number unsignedShiftRight(Number right) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseAnd(Number right) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseXor(Number right) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseOr(Number right) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number unaryPlus() {
+    return +number;
+  }
+
+  @Override
+  public Number unaryMinus() {
+    return -number;
+  }
+
+  @Override
+  public Number bitwiseComplement() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean equalTo(Number right) {
+    if (right instanceof Byte) {
+      return number == right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number == right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number == right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number == right.intValue();
+    }
+    if (right instanceof Long) {
+      return number == right.longValue();
+    }
+    if (right instanceof Short) {
+      return number == right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean notEqualTo(Number right) {
+    if (right instanceof Byte) {
+      return number != right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number != right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number != right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number != right.intValue();
+    }
+    if (right instanceof Long) {
+      return number != right.longValue();
+    }
+    if (right instanceof Short) {
+      return number != right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean greaterThan(Number right) {
+    if (right instanceof Byte) {
+      return number > right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number > right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number > right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number > right.intValue();
+    }
+    if (right instanceof Long) {
+      return number > right.longValue();
+    }
+    if (right instanceof Short) {
+      return number > right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean greaterThanEq(Number right) {
+    if (right instanceof Byte) {
+      return number >= right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number >= right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number >= right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number >= right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >= right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >= right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean lessThan(Number right) {
+    if (right instanceof Byte) {
+      return number < right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number < right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number < right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number < right.intValue();
+    }
+    if (right instanceof Long) {
+      return number < right.longValue();
+    }
+    if (right instanceof Short) {
+      return number < right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean lessThanEq(Number right) {
+    if (right instanceof Byte) {
+      return number <= right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number <= right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number <= right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number <= right.intValue();
+    }
+    if (right instanceof Long) {
+      return number <= right.longValue();
+    }
+    if (right instanceof Short) {
+      return number <= right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/util/IntegerMath.java b/framework/src/main/java/org/checkerframework/common/value/util/IntegerMath.java
new file mode 100644
index 0000000..b090d76
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/util/IntegerMath.java
@@ -0,0 +1,385 @@
+package org.checkerframework.common.value.util;
+
+public class IntegerMath extends NumberMath<Integer> {
+  int number;
+
+  public IntegerMath(int i) {
+    number = i;
+  }
+
+  @Override
+  public Number plus(Number right) {
+    if (right instanceof Byte) {
+      return number + right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number + right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number + right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number + right.intValue();
+    }
+    if (right instanceof Long) {
+      return number + right.longValue();
+    }
+    if (right instanceof Short) {
+      return number + right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number minus(Number right) {
+    if (right instanceof Byte) {
+      return number - right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number - right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number - right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number - right.intValue();
+    }
+    if (right instanceof Long) {
+      return number - right.longValue();
+    }
+    if (right instanceof Short) {
+      return number - right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number times(Number right) {
+    if (right instanceof Byte) {
+      return number * right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number * right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number * right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number * right.intValue();
+    }
+    if (right instanceof Long) {
+      return number * right.longValue();
+    }
+    if (right instanceof Short) {
+      return number * right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number divide(Number right) {
+    if (isIntegralZero(right)) {
+      return null;
+    }
+    if (right instanceof Byte) {
+      return number / right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number / right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number / right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number / right.intValue();
+    }
+    if (right instanceof Long) {
+      return number / right.longValue();
+    }
+    if (right instanceof Short) {
+      return number / right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number remainder(Number right) {
+    if (isIntegralZero(right)) {
+      return null;
+    }
+    if (right instanceof Byte) {
+      return number % right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number % right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number % right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number % right.intValue();
+    }
+    if (right instanceof Long) {
+      return number % right.longValue();
+    }
+    if (right instanceof Short) {
+      return number % right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number shiftLeft(Number right) {
+    if (right instanceof Byte) {
+      return number << right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number << right.intValue();
+    }
+    if (right instanceof Long) {
+      return number << right.longValue();
+    }
+    if (right instanceof Short) {
+      return number << right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number signedShiftRight(Number right) {
+    if (right instanceof Byte) {
+      return number >> right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number >> right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >> right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >> right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number unsignedShiftRight(Number right) {
+    if (right instanceof Byte) {
+      return number >>> right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number >>> right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >>> right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >>> right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseAnd(Number right) {
+    if (right instanceof Byte) {
+      return number & right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number & right.intValue();
+    }
+    if (right instanceof Long) {
+      return number & right.longValue();
+    }
+    if (right instanceof Short) {
+      return number & right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseXor(Number right) {
+    if (right instanceof Byte) {
+      return number ^ right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number ^ right.intValue();
+    }
+    if (right instanceof Long) {
+      return number ^ right.longValue();
+    }
+    if (right instanceof Short) {
+      return number ^ right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseOr(Number right) {
+    if (right instanceof Byte) {
+      return number | right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number | right.intValue();
+    }
+    if (right instanceof Long) {
+      return number | right.longValue();
+    }
+    if (right instanceof Short) {
+      return number | right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number unaryPlus() {
+    return +number;
+  }
+
+  @Override
+  public Number unaryMinus() {
+    return -number;
+  }
+
+  @Override
+  public Number bitwiseComplement() {
+    return ~number;
+  }
+
+  @Override
+  public Boolean equalTo(Number right) {
+    if (right instanceof Byte) {
+      return number == right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number == right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number == right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number == right.intValue();
+    }
+    if (right instanceof Long) {
+      return number == right.longValue();
+    }
+    if (right instanceof Short) {
+      return number == right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean notEqualTo(Number right) {
+    if (right instanceof Byte) {
+      return number != right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number != right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number != right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number != right.intValue();
+    }
+    if (right instanceof Long) {
+      return number != right.longValue();
+    }
+    if (right instanceof Short) {
+      return number != right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean greaterThan(Number right) {
+    if (right instanceof Byte) {
+      return number > right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number > right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number > right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number > right.intValue();
+    }
+    if (right instanceof Long) {
+      return number > right.longValue();
+    }
+    if (right instanceof Short) {
+      return number > right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean greaterThanEq(Number right) {
+    if (right instanceof Byte) {
+      return number >= right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number >= right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number >= right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number >= right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >= right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >= right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean lessThan(Number right) {
+    if (right instanceof Byte) {
+      return number < right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number < right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number < right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number < right.intValue();
+    }
+    if (right instanceof Long) {
+      return number < right.longValue();
+    }
+    if (right instanceof Short) {
+      return number < right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean lessThanEq(Number right) {
+    if (right instanceof Byte) {
+      return number <= right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number <= right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number <= right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number <= right.intValue();
+    }
+    if (right instanceof Long) {
+      return number <= right.longValue();
+    }
+    if (right instanceof Short) {
+      return number <= right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/util/LongMath.java b/framework/src/main/java/org/checkerframework/common/value/util/LongMath.java
new file mode 100644
index 0000000..5505ad2
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/util/LongMath.java
@@ -0,0 +1,385 @@
+package org.checkerframework.common.value.util;
+
+public class LongMath extends NumberMath<Long> {
+  long number;
+
+  public LongMath(long i) {
+    number = i;
+  }
+
+  @Override
+  public Number plus(Number right) {
+    if (right instanceof Byte) {
+      return number + right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number + right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number + right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number + right.intValue();
+    }
+    if (right instanceof Long) {
+      return number + right.longValue();
+    }
+    if (right instanceof Short) {
+      return number + right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number minus(Number right) {
+    if (right instanceof Byte) {
+      return number - right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number - right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number - right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number - right.intValue();
+    }
+    if (right instanceof Long) {
+      return number - right.longValue();
+    }
+    if (right instanceof Short) {
+      return number - right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number times(Number right) {
+    if (right instanceof Byte) {
+      return number * right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number * right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number * right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number * right.intValue();
+    }
+    if (right instanceof Long) {
+      return number * right.longValue();
+    }
+    if (right instanceof Short) {
+      return number * right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number divide(Number right) {
+    if (isIntegralZero(right)) {
+      return null;
+    }
+    if (right instanceof Byte) {
+      return number / right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number / right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number / right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number / right.intValue();
+    }
+    if (right instanceof Long) {
+      return number / right.longValue();
+    }
+    if (right instanceof Short) {
+      return number / right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number remainder(Number right) {
+    if (isIntegralZero(right)) {
+      return null;
+    }
+    if (right instanceof Byte) {
+      return number % right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number % right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number % right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number % right.intValue();
+    }
+    if (right instanceof Long) {
+      return number % right.longValue();
+    }
+    if (right instanceof Short) {
+      return number % right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number shiftLeft(Number right) {
+    if (right instanceof Byte) {
+      return number << right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number << right.intValue();
+    }
+    if (right instanceof Long) {
+      return number << right.longValue();
+    }
+    if (right instanceof Short) {
+      return number << right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number signedShiftRight(Number right) {
+    if (right instanceof Byte) {
+      return number >> right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number >> right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >> right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >> right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number unsignedShiftRight(Number right) {
+    if (right instanceof Byte) {
+      return number >>> right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number >>> right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >>> right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >>> right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseAnd(Number right) {
+    if (right instanceof Byte) {
+      return number & right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number & right.intValue();
+    }
+    if (right instanceof Long) {
+      return number & right.longValue();
+    }
+    if (right instanceof Short) {
+      return number & right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseXor(Number right) {
+    if (right instanceof Byte) {
+      return number ^ right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number ^ right.intValue();
+    }
+    if (right instanceof Long) {
+      return number ^ right.longValue();
+    }
+    if (right instanceof Short) {
+      return number ^ right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseOr(Number right) {
+    if (right instanceof Byte) {
+      return number | right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number | right.intValue();
+    }
+    if (right instanceof Long) {
+      return number | right.longValue();
+    }
+    if (right instanceof Short) {
+      return number | right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number unaryPlus() {
+    return +number;
+  }
+
+  @Override
+  public Number unaryMinus() {
+    return -number;
+  }
+
+  @Override
+  public Number bitwiseComplement() {
+    return ~number;
+  }
+
+  @Override
+  public Boolean equalTo(Number right) {
+    if (right instanceof Byte) {
+      return number == right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number == right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number == right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number == right.intValue();
+    }
+    if (right instanceof Long) {
+      return number == right.longValue();
+    }
+    if (right instanceof Short) {
+      return number == right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean notEqualTo(Number right) {
+    if (right instanceof Byte) {
+      return number != right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number != right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number != right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number != right.intValue();
+    }
+    if (right instanceof Long) {
+      return number != right.longValue();
+    }
+    if (right instanceof Short) {
+      return number != right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean greaterThan(Number right) {
+    if (right instanceof Byte) {
+      return number > right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number > right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number > right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number > right.intValue();
+    }
+    if (right instanceof Long) {
+      return number > right.longValue();
+    }
+    if (right instanceof Short) {
+      return number > right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean greaterThanEq(Number right) {
+    if (right instanceof Byte) {
+      return number >= right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number >= right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number >= right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number >= right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >= right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >= right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean lessThan(Number right) {
+    if (right instanceof Byte) {
+      return number < right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number < right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number < right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number < right.intValue();
+    }
+    if (right instanceof Long) {
+      return number < right.longValue();
+    }
+    if (right instanceof Short) {
+      return number < right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean lessThanEq(Number right) {
+    if (right instanceof Byte) {
+      return number <= right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number <= right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number <= right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number <= right.intValue();
+    }
+    if (right instanceof Long) {
+      return number <= right.longValue();
+    }
+    if (right instanceof Short) {
+      return number <= right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/util/NumberMath.java b/framework/src/main/java/org/checkerframework/common/value/util/NumberMath.java
new file mode 100644
index 0000000..a3efe9a
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/util/NumberMath.java
@@ -0,0 +1,86 @@
+package org.checkerframework.common.value.util;
+
+public abstract class NumberMath<T extends Number> {
+  public static NumberMath<?> getNumberMath(Number number) {
+    if (number instanceof Byte) {
+      return new ByteMath(number.byteValue());
+    }
+    if (number instanceof Double) {
+      return new DoubleMath(number.doubleValue());
+    }
+    if (number instanceof Float) {
+      return new FloatMath(number.floatValue());
+    }
+    if (number instanceof Integer) {
+      return new IntegerMath(number.intValue());
+    }
+    if (number instanceof Long) {
+      return new LongMath(number.longValue());
+    }
+    if (number instanceof Short) {
+      return new ShortMath(number.shortValue());
+    }
+    return null;
+  }
+
+  public abstract Number plus(Number right);
+
+  public abstract Number minus(Number right);
+
+  public abstract Number times(Number right);
+
+  /**
+   * Returns the result of dividing the {@code this} by {@code right}. If {@code right} is zero and
+   * this is an integer division, {@code null} is returned.
+   */
+  public abstract Number divide(Number right);
+
+  /**
+   * Returns the result of {@code this % right}. If {@code right} is zero and this is an integer
+   * remainder, {@code null} is returned.
+   */
+  public abstract Number remainder(Number right);
+
+  public abstract Number shiftLeft(Number right);
+
+  public abstract Number signedShiftRight(Number right);
+
+  public abstract Number unsignedShiftRight(Number right);
+
+  public abstract Number bitwiseAnd(Number right);
+
+  public abstract Number bitwiseOr(Number right);
+
+  public abstract Number bitwiseXor(Number right);
+
+  public abstract Number unaryPlus();
+
+  public abstract Number unaryMinus();
+
+  public abstract Number bitwiseComplement();
+
+  public abstract Boolean equalTo(Number right);
+
+  public abstract Boolean notEqualTo(Number right);
+
+  public abstract Boolean greaterThan(Number right);
+
+  public abstract Boolean greaterThanEq(Number right);
+
+  public abstract Boolean lessThan(Number right);
+
+  public abstract Boolean lessThanEq(Number right);
+
+  public static boolean isIntegralZero(Number number) {
+    if (number instanceof Byte) {
+      return number.byteValue() == 0;
+    } else if (number instanceof Integer) {
+      return number.intValue() == 0;
+    } else if (number instanceof Long) {
+      return number.longValue() == 0;
+    } else if (number instanceof Short) {
+      return number.shortValue() == 0;
+    }
+    return false;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/util/NumberUtils.java b/framework/src/main/java/org/checkerframework/common/value/util/NumberUtils.java
new file mode 100644
index 0000000..25a71ae
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/util/NumberUtils.java
@@ -0,0 +1,72 @@
+package org.checkerframework.common.value.util;
+
+import java.util.List;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.javacutil.TypeKindUtils;
+import org.plumelib.util.CollectionsPlume;
+
+/** Utility routines for manipulating numbers. */
+public class NumberUtils {
+
+  /** Converts a {@code List<A>} to a {@code List<B>}, where A and B are numeric types. */
+  public static List<? extends Number> castNumbers(
+      TypeMirror type, List<? extends Number> numbers) {
+    if (numbers == null) {
+      return null;
+    }
+    TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type);
+    if (typeKind == null) {
+      throw new UnsupportedOperationException(type.toString());
+    }
+    switch (typeKind) {
+      case BYTE:
+        return CollectionsPlume.mapList(Number::byteValue, numbers);
+      case CHAR:
+        return CollectionsPlume.mapList(Number::intValue, numbers);
+      case DOUBLE:
+        return CollectionsPlume.mapList(Number::doubleValue, numbers);
+      case FLOAT:
+        return CollectionsPlume.mapList(Number::floatValue, numbers);
+      case INT:
+        return CollectionsPlume.mapList(Number::intValue, numbers);
+      case LONG:
+        return CollectionsPlume.mapList(Number::longValue, numbers);
+      case SHORT:
+        return CollectionsPlume.mapList(Number::shortValue, numbers);
+      default:
+        throw new UnsupportedOperationException(typeKind + ": " + type);
+    }
+  }
+
+  /**
+   * Return a range that restricts the given range to the given type. That is, return the range
+   * resulting from casting a value with the given range.
+   *
+   * @param type the type for the cast; the result will be within it
+   * @param range the original range; the result will be within it
+   * @return the intersection of the given range and the possible values of the given type
+   */
+  public static Range castRange(TypeMirror type, Range range) {
+    TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type);
+    if (typeKind == null) {
+      throw new UnsupportedOperationException(type.toString());
+    }
+    switch (typeKind) {
+      case BYTE:
+        return range.byteRange();
+      case CHAR:
+        return range.charRange();
+      case SHORT:
+        return range.shortRange();
+      case INT:
+        return range.intRange();
+      case LONG:
+      case FLOAT:
+      case DOUBLE:
+        return range;
+      default:
+        throw new UnsupportedOperationException(typeKind + ": " + type);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/util/Range.java b/framework/src/main/java/org/checkerframework/common/value/util/Range.java
new file mode 100644
index 0000000..1e120c6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/util/Range.java
@@ -0,0 +1,1235 @@
+package org.checkerframework.common.value.util;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * The Range class models a 64-bit two's-complement integral interval, such as all integers between
+ * 1 and 10, inclusive.
+ *
+ * <p>{@code Range} is immutable.
+ */
+public class Range {
+
+  /** The lower bound of the interval, inclusive. */
+  public final long from;
+
+  /** The upper bound of the interval, inclusive. */
+  public final long to;
+
+  /**
+   * Should ranges take overflow into account or ignore it?
+   *
+   * <ul>
+   *   <li>If {@code ignoreOverflow} is true, then operations that would result in more than the max
+   *       value are clipped to the max value (and similarly for the min).
+   *   <li>If {@code ignoreOverflow} is false, then operations that would result in more than the
+   *       max wrap around according to the rules of twos-complement arithmetic and produce a
+   *       smaller value (and similarly for the min).
+   * </ul>
+   *
+   * <p>Any checker that uses this library should set this field. By default, this field is set to
+   * false (meaning overflow is taken into account), but a previous checker might have set it to
+   * true.
+   *
+   * <p>A static field is used because passing an instance field throughout the class bloats the
+   * code.
+   */
+  public static boolean ignoreOverflow = false;
+
+  /** A range containing all possible 64-bit values. */
+  public static final Range LONG_EVERYTHING = create(Long.MIN_VALUE, Long.MAX_VALUE);
+
+  /** A range containing all possible 32-bit values. */
+  public static final Range INT_EVERYTHING = create(Integer.MIN_VALUE, Integer.MAX_VALUE);
+
+  /** A range containing all possible 16-bit values. */
+  public static final Range SHORT_EVERYTHING = create(Short.MIN_VALUE, Short.MAX_VALUE);
+
+  /** A range containing all possible char values. */
+  public static final Range CHAR_EVERYTHING = create(Character.MIN_VALUE, Character.MAX_VALUE);
+
+  /** A range containing all possible 8-bit values. */
+  public static final Range BYTE_EVERYTHING = create(Byte.MIN_VALUE, Byte.MAX_VALUE);
+
+  /** The empty range. This is the only Range object that contains nothing */
+  @SuppressWarnings("interning:assignment") // no other constructor call makes this
+  public static final @InternedDistinct Range NOTHING = new Range(Long.MAX_VALUE, Long.MIN_VALUE);
+
+  /** An alias to the range containing all possible 64-bit values. */
+  public static final Range EVERYTHING = LONG_EVERYTHING;
+
+  /**
+   * Constructs a range with its bounds specified by two parameters, {@code from} and {@code to}.
+   *
+   * <p>This is a private constructor that does no validation of arguments, so special instances
+   * (e.g., {@link #NOTHING}) can be created through it.
+   *
+   * @param from the lower bound (inclusive)
+   * @param to the upper bound (inclusive)
+   */
+  private Range(long from, long to) {
+    this.from = from;
+    this.to = to;
+  }
+
+  /**
+   * Constructs a range with its bounds specified by two parameters, {@code from} and {@code to}.
+   * Requires {@code from <= to}.
+   *
+   * @param from the lower bound (inclusive)
+   * @param to the upper bound (inclusive)
+   * @return the Range [from..to]
+   */
+  public static Range create(long from, long to) {
+    if (!(from <= to)) {
+      throw new IllegalArgumentException(String.format("Invalid Range: %s %s", from, to));
+    }
+    return new Range(from, to);
+  }
+
+  /**
+   * Create a Range from a collection of Numbers.
+   *
+   * @param values collection whose min and max values will be used as the range's from and to
+   *     values
+   * @return a range that encompasses all the argument's values ({@link #NOTHING} if the argument is
+   *     an empty collection)
+   */
+  public static Range create(Collection<? extends Number> values) {
+    if (values.isEmpty()) {
+      return NOTHING;
+    }
+    long min = values.iterator().next().longValue();
+    long max = min;
+    for (Number value : values) {
+      long current = value.longValue();
+      if (min > current) min = current;
+      if (max < current) max = current;
+    }
+    return create(min, max);
+  }
+
+  /**
+   * Returns a Range representing all possible values for the given primitive type.
+   *
+   * @param typeKind one of INT, SHORT, BYTE, CHAR, or LONG
+   * @return the range for the given primitive type
+   */
+  public static Range create(TypeKind typeKind) {
+    switch (typeKind) {
+      case INT:
+        return INT_EVERYTHING;
+      case SHORT:
+        return SHORT_EVERYTHING;
+      case BYTE:
+        return BYTE_EVERYTHING;
+      case CHAR:
+        return CHAR_EVERYTHING;
+      case LONG:
+        return LONG_EVERYTHING;
+      default:
+        throw new IllegalArgumentException(
+            "Invalid TypeKind for Range: expected INT, SHORT, BYTE, CHAR, or LONG, got "
+                + typeKind);
+    }
+  }
+
+  /** Long.MIN_VALUE, as a BigInteger. */
+  private static final BigInteger BIG_LONG_MIN_VALUE = BigInteger.valueOf(Long.MIN_VALUE);
+  /** Long.MAX_VALUE, as a BigInteger. */
+  private static final BigInteger BIG_LONG_MAX_VALUE = BigInteger.valueOf(Long.MAX_VALUE);
+  /** The number of Long values, as a BigInteger. */
+  private static final BigInteger BIG_LONG_WIDTH =
+      BIG_LONG_MAX_VALUE.subtract(BIG_LONG_MIN_VALUE).add(BigInteger.ONE);
+
+  /**
+   * Creates a range using BigInteger type bounds.
+   *
+   * <p>If the BigInteger range is wider than the full range of the Long class, return EVERYTHING.
+   *
+   * <p>If one of the BigInteger bounds is out of Long's range and {@link #ignoreOverflow} is false,
+   * convert the bounds to Long type in accordance with Java twos-complement overflow rules, e.g.,
+   * Long.MAX_VALUE + 1 is converted to Long.MIN_VALUE.
+   *
+   * <p>If one of the BigInteger bounds is out of Long's range and {@link #ignoreOverflow} is true,
+   * convert the bound that is outside Long's range to max/min value of a Long.
+   *
+   * @param bigFrom the lower bound of the BigInteger range
+   * @param bigTo the upper bound of the BigInteger range
+   * @return a range with Long type bounds converted from the BigInteger range
+   */
+  private static Range create(BigInteger bigFrom, BigInteger bigTo) {
+    if (ignoreOverflow) {
+      bigFrom = bigFrom.max(BIG_LONG_MIN_VALUE);
+      bigTo = bigTo.min(BIG_LONG_MAX_VALUE);
+    } else {
+      BigInteger bigWidth = bigTo.subtract(bigFrom).add(BigInteger.ONE);
+      if (bigWidth.compareTo(BIG_LONG_WIDTH) > 0) {
+        return EVERYTHING;
+      }
+    }
+    long longFrom = bigFrom.longValue();
+    long longTo = bigTo.longValue();
+    return createOrElse(longFrom, longTo, EVERYTHING);
+  }
+
+  /**
+   * Creates a Range if {@code from<=to}; otherwise returns the given Range value.
+   *
+   * @param from lower bound for the range
+   * @param to upper bound for the range
+   * @param alternate what to return if {@code from > to}
+   * @return a new Range [from..to], or {@code alternate}
+   */
+  private static Range createOrElse(long from, long to, Range alternate) {
+    if (from <= to) {
+      return new Range(from, to);
+    } else {
+      return alternate;
+    }
+  }
+
+  /**
+   * Returns a range with its bounds specified by two parameters, {@code from} and {@code to}. If
+   * {@code from} is greater than {@code to}, returns {@link #NOTHING}.
+   *
+   * @param from the lower bound (inclusive)
+   * @param to the upper bound (inclusive)
+   * @return newly-created Range or NOTHING
+   */
+  private static Range createOrNothing(long from, long to) {
+    return createOrElse(from, to, NOTHING);
+  }
+
+  /**
+   * Returns the number of values in this range.
+   *
+   * @return how many values are in the range
+   */
+  private long width() {
+    return to - from + 1;
+  }
+
+  @Override
+  public String toString() {
+    if (this.isNothing()) {
+      return "[]";
+    } else {
+      return String.format("[%s..%s]", from, to);
+    }
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj instanceof Range) {
+      return equalsRange((Range) obj);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(from, to);
+  }
+
+  /**
+   * Compare two ranges in a type safe manner for equality without incurring the cost of an
+   * instanceof check such as equals(Object) does.
+   *
+   * @param range to compare against
+   * @return true for ranges that match from and to respectively
+   */
+  private boolean equalsRange(Range range) {
+    return from == range.from && to == range.to;
+  }
+
+  /** Return true if this range contains every {@code long} value. */
+  public boolean isLongEverything() {
+    return equalsRange(LONG_EVERYTHING);
+  }
+
+  /** Return true if this range contains every {@code int} value. */
+  public boolean isIntEverything() {
+    return equalsRange(INT_EVERYTHING);
+  }
+
+  /** Return true if this range contains every {@code short} value. */
+  public boolean isShortEverything() {
+    return equalsRange(SHORT_EVERYTHING);
+  }
+
+  /** Return true if this range contains every {@code char} value. */
+  public boolean isCharEverything() {
+    return equalsRange(CHAR_EVERYTHING);
+  }
+
+  /** Return true if this range contains every {@code byte} value. */
+  public boolean isByteEverything() {
+    return equalsRange(BYTE_EVERYTHING);
+  }
+
+  /** Return true if this range contains no values. */
+  public boolean isNothing() {
+    return this == NOTHING;
+  }
+
+  /** The number of values representable in 32 bits: 2^32 or {@code 1<<32}. */
+  private static final long INT_WIDTH = INT_EVERYTHING.width();
+
+  /**
+   * Converts this range to a 32-bit integral range.
+   *
+   * <p>If {@link #ignoreOverflow} is true and one of the bounds is outside the Integer range, then
+   * that bound is set to the bound of the Integer range.
+   *
+   * <p>If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full
+   * range of the Integer class, return INT_EVERYTHING.
+   *
+   * <p>If {@link #ignoreOverflow} is false and the bounds of this range are not representable as
+   * 32-bit integers, convert the bounds to Integer type in accordance with Java twos-complement
+   * overflow rules, e.g., Integer.MAX_VALUE + 1 is converted to Integer.MIN_VALUE.
+   */
+  public Range intRange() {
+    if (this.isNothing()) {
+      return this;
+    }
+    if (INT_EVERYTHING.contains(this)) {
+      return this;
+    }
+    if (ignoreOverflow) {
+      return create(clipToRange(from, INT_EVERYTHING), clipToRange(to, INT_EVERYTHING));
+    }
+    if (this.isWiderThan(INT_WIDTH)) {
+      return INT_EVERYTHING;
+    }
+    return createOrElse((int) this.from, (int) this.to, INT_EVERYTHING);
+  }
+
+  /** The number of values representable in 16 bits: 2^16 or 1&lt;&lt;16. */
+  private static final long SHORT_WIDTH = SHORT_EVERYTHING.width();
+
+  /**
+   * Converts a this range to a 16-bit short range.
+   *
+   * <p>If {@link #ignoreOverflow} is true and one of the bounds is outside the Short range, then
+   * that bound is set to the bound of the Short range.
+   *
+   * <p>If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full
+   * range of the Short class, return SHORT_EVERYTHING.
+   *
+   * <p>If {@link #ignoreOverflow} is false and the bounds of this range are not representable as
+   * 16-bit integers, convert the bounds to Short type in accordance with Java twos-complement
+   * overflow rules, e.g., Short.MAX_VALUE + 1 is converted to Short.MIN_VALUE.
+   */
+  public Range shortRange() {
+    if (this.isNothing()) {
+      return this;
+    }
+    if (SHORT_EVERYTHING.contains(this)) {
+      return this;
+    }
+    if (ignoreOverflow) {
+      return create(clipToRange(from, SHORT_EVERYTHING), clipToRange(to, SHORT_EVERYTHING));
+    }
+    if (this.isWiderThan(SHORT_WIDTH)) {
+      // short is promoted to int before the operation so no need for explicit casting
+      return SHORT_EVERYTHING;
+    }
+    return createOrElse((short) this.from, (short) this.to, SHORT_EVERYTHING);
+  }
+
+  /** The number of values representable in char: */
+  private static final long CHAR_WIDTH = CHAR_EVERYTHING.width();
+
+  /**
+   * Converts this range to a char range.
+   *
+   * <p>If {@link #ignoreOverflow} is true and one of the bounds is outside the Character range,
+   * then that bound is set to the bound of the Character range.
+   *
+   * <p>If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full
+   * range of the Character class, return CHAR_EVERYTHING.
+   *
+   * <p>If {@link #ignoreOverflow} is false and the bounds of this range are not representable as
+   * 8-bit integers, convert the bounds to Character type in accordance with Java overflow rules
+   * (twos-complement), e.g., Character.MAX_VALUE + 1 is converted to Character.MIN_VALUE.
+   */
+  public Range charRange() {
+    if (this.isNothing()) {
+      return this;
+    }
+    if (CHAR_EVERYTHING.contains(this)) {
+      return this;
+    }
+    if (ignoreOverflow) {
+      return create(clipToRange(from, CHAR_EVERYTHING), clipToRange(to, CHAR_EVERYTHING));
+    }
+    if (this.isWiderThan(CHAR_WIDTH)) {
+      // char is promoted to int before the operation so no need for explicit casting
+      return CHAR_EVERYTHING;
+    }
+    return createOrElse((char) this.from, (char) this.to, CHAR_EVERYTHING);
+  }
+
+  /** The number of values representable in 8 bits: 2^8 or 1&lt;&lt;8. */
+  private static final long BYTE_WIDTH = BYTE_EVERYTHING.width();
+
+  /**
+   * Converts this range to a 8-bit byte range.
+   *
+   * <p>If {@link #ignoreOverflow} is true and one of the bounds is outside the Byte range, then
+   * that bound is set to the bound of the Byte range.
+   *
+   * <p>If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full
+   * range of the Byte class, return BYTE_EVERYTHING.
+   *
+   * <p>If {@link #ignoreOverflow} is false and the bounds of this range are not representable as
+   * 8-bit integers, convert the bounds to Byte type in accordance with Java twos-complement
+   * overflow rules, e.g., Byte.MAX_VALUE + 1 is converted to Byte.MIN_VALUE.
+   */
+  public Range byteRange() {
+    if (this.isNothing()) {
+      return this;
+    }
+    if (BYTE_EVERYTHING.contains(this)) {
+      return this;
+    }
+    if (ignoreOverflow) {
+      return create(clipToRange(from, BYTE_EVERYTHING), clipToRange(to, BYTE_EVERYTHING));
+    }
+    if (this.isWiderThan(BYTE_WIDTH)) {
+      // byte is promoted to int before the operation so no need for explicit casting
+      return BYTE_EVERYTHING;
+    }
+    return createOrElse((byte) this.from, (byte) this.to, BYTE_EVERYTHING);
+  }
+
+  /**
+   * Return x clipped to the given range; out-of-range values become extremal values. Appropriate
+   * only when {@link #ignoreOverflow} is true.
+   *
+   * @param x a value
+   * @param r a range
+   * @return a value within the range; if x is outside r, returns the min or max of r
+   */
+  private long clipToRange(long x, Range r) {
+    if (x < r.from) {
+      return r.from;
+    } else if (x > r.to) {
+      return r.to;
+    } else {
+      return x;
+    }
+  }
+
+  /**
+   * Returns true if the element is contained in this range.
+   *
+   * @param element the value to seek
+   * @return true if {@code element} is in this range
+   */
+  public boolean contains(long element) {
+    return from <= element && element <= to;
+  }
+
+  /**
+   * Returns true if the other range is contained in this range.
+   *
+   * @param other the range that might be within this one
+   * @return true if {@code other} is within this range
+   */
+  public boolean contains(Range other) {
+    return other.isWithin(from, to);
+  }
+
+  /**
+   * Returns the smallest range that includes all values contained in either of the two ranges. We
+   * call this the union of two ranges.
+   *
+   * @param right a range to union with this range
+   * @return a range resulting from the union of the specified range and this range
+   */
+  public Range union(Range right) {
+    if (this.isNothing()) {
+      return right;
+    } else if (right.isNothing()) {
+      return this;
+    }
+
+    long resultFrom = Math.min(from, right.from);
+    long resultTo = Math.max(to, right.to);
+    return create(resultFrom, resultTo);
+  }
+
+  /**
+   * Returns the smallest range that includes all values contained in both of the two ranges. We
+   * call this the intersection of two ranges. If there is no overlap between the two ranges,
+   * returns an empty range.
+   *
+   * @param right the range to intersect with this range
+   * @return a range resulting from the intersection of the specified range and this range
+   */
+  public Range intersect(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    long resultFrom = Math.max(from, right.from);
+    long resultTo = Math.min(to, right.to);
+    return createOrNothing(resultFrom, resultTo);
+  }
+
+  /**
+   * Returns the range with the lowest to and from values of this range and the passed range.
+   *
+   * @param other the range to compare
+   * @return the range with the lowest to and from values of this range and the passed range
+   */
+  public Range min(Range other) {
+    return create(Math.min(this.from, other.from), Math.min(this.to, other.to));
+  }
+
+  /**
+   * Returns the range with the highest to and from values of this range and the passed range.
+   *
+   * @param other the range to compare
+   * @return the range with the highest to and from values of this range and the passed range
+   */
+  public Range max(Range other) {
+    return create(Math.max(this.from, other.from), Math.max(this.to, other.to));
+  }
+
+  /**
+   * Returns the smallest range that includes all possible values resulting from adding an arbitrary
+   * value in the specified range to an arbitrary value in this range. We call this the addition of
+   * two ranges.
+   *
+   * @param right a range to be added to this range
+   * @return the range resulting from the addition of the specified range and this range
+   */
+  public Range plus(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    if (this.isWithinHalfLong() && right.isWithinHalfLong()) {
+      // This bound is adequate to guarantee no overflow when using long to evaluate
+      long resultFrom = from + right.from;
+      long resultTo = to + right.to;
+      if (from > to) {
+        return Range.EVERYTHING;
+      } else {
+        return create(resultFrom, resultTo);
+      }
+    } else {
+      BigInteger bigFrom = BigInteger.valueOf(from).add(BigInteger.valueOf(right.from));
+      BigInteger bigTo = BigInteger.valueOf(to).add(BigInteger.valueOf(right.to));
+      return create(bigFrom, bigTo);
+    }
+  }
+
+  /**
+   * Returns the smallest range that includes all possible values resulting from subtracting an
+   * arbitrary value in the specified range from an arbitrary value in this range. We call this the
+   * subtraction of two ranges.
+   *
+   * @param right the range to be subtracted from this range
+   * @return the range resulting from subtracting the specified range from this range
+   */
+  public Range minus(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    if (this.isWithinHalfLong() && right.isWithinHalfLong()) {
+      // This bound is adequate to guarantee no overflow when using long to evaluate
+      long resultFrom = from - right.to;
+      long resultTo = to - right.from;
+      return create(resultFrom, resultTo);
+    } else {
+      BigInteger bigFrom = BigInteger.valueOf(from).subtract(BigInteger.valueOf(right.to));
+      BigInteger bigTo = BigInteger.valueOf(to).subtract(BigInteger.valueOf(right.from));
+      return create(bigFrom, bigTo);
+    }
+  }
+
+  /**
+   * Returns the smallest range that includes all possible values resulting from multiplying an
+   * arbitrary value in the specified range by an arbitrary value in this range. We call this the
+   * multiplication of two ranges.
+   *
+   * @param right the specified range to be multiplied by this range
+   * @return the range resulting from multiplying the specified range by this range
+   */
+  public Range times(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    // These bounds are adequate:  Integer.MAX_VALUE^2 is still a bit less than Long.MAX_VALUE.
+    if (this.isWithinInteger() && right.isWithinInteger()) {
+      List<Long> possibleValues =
+          Arrays.asList(from * right.from, from * right.to, to * right.from, to * right.to);
+      return create(possibleValues);
+    } else {
+      final BigInteger bigLeftFrom = BigInteger.valueOf(from);
+      final BigInteger bigRightFrom = BigInteger.valueOf(right.from);
+      final BigInteger bigRightTo = BigInteger.valueOf(right.to);
+      final BigInteger bigLeftTo = BigInteger.valueOf(to);
+      List<BigInteger> bigPossibleValues =
+          Arrays.asList(
+              bigLeftFrom.multiply(bigRightFrom),
+              bigLeftFrom.multiply(bigRightTo),
+              bigLeftTo.multiply(bigRightFrom),
+              bigLeftTo.multiply(bigRightTo));
+      return create(Collections.min(bigPossibleValues), Collections.max(bigPossibleValues));
+    }
+  }
+
+  /**
+   * Returns the smallest range that includes all possible values resulting from dividing (integer
+   * division) an arbitrary value in this range by an arbitrary value in the specified range. We
+   * call this the division of two ranges.
+   *
+   * @param right the specified range by which this range is divided
+   * @return the range resulting from dividing this range by the specified range
+   */
+  public Range divide(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+    if (right.from == 0 && right.to == 0) {
+      return NOTHING;
+    }
+    // Special cases that involve overflow.
+    // The only overflow in integer division is Long.MIN_VALUE / -1 == Long.MIN_VALUE.
+    if (from == Long.MIN_VALUE && right.contains(-1)) {
+      // The values in the right range are all negative because right does not contain 0 but does
+      // contain 1.
+      if (from != to) {
+        // Special case 1:
+        // This range contains Long.MIN_VALUE and Long.MIN_VALUE + 1, which makes the
+        // result range EVERYTHING.
+        return EVERYTHING;
+      } else if (right.from != right.to) {
+        // Special case 2:
+        // This range contains only Long.MIN_VALUE, and the right range contains at least -1
+        // and -2. The result range is from Long.MIN_VALUE to Long.MIN_VALUE / -2.
+        return create(Long.MIN_VALUE, Long.MIN_VALUE / -2);
+      } else {
+        // Special case 3:
+        // This range contains only Long.MIN_VALUE, and right contains only -1.
+        return create(Long.MIN_VALUE, Long.MIN_VALUE);
+      }
+    }
+    // We needn't worry about the overflow issue starting from here.
+
+    // There are 9 different cases:
+    // (note: pos=positive, neg=negative, unk=unknown sign, np=non-positive, nn=non-negative)
+    long resultFrom;
+    long resultTo;
+    if (from > 0) { // this range is positive
+      if (right.from >= 0) {
+        // 1. right: nn
+        resultFrom = from / Math.max(right.to, 1);
+        resultTo = to / Math.max(right.from, 1);
+      } else if (right.to <= 0) {
+        // 2. right: np
+        resultFrom = to / Math.min(right.to, -1);
+        resultTo = from / Math.min(right.from, -1);
+      } else {
+        // 3. right: unk; values include -1 and 1
+        resultFrom = -to;
+        resultTo = to;
+      }
+    } else if (to < 0) { // this range is negative
+      if (right.from >= 0) {
+        // 4. right: nn
+        resultFrom = from / Math.max(right.from, 1);
+        resultTo = to / Math.max(right.to, 1);
+      } else if (right.to <= 0) {
+        // 5. right: np
+        resultFrom = to / Math.min(right.from, -1);
+        resultTo = from / Math.min(right.to, -1);
+      } else {
+        // 6. right: unk; values include -1 and 1
+        resultFrom = from;
+        resultTo = -from;
+      }
+    } else { // this range spans both signs
+      if (right.from >= 0) {
+        // 7. right: nn
+        resultFrom = from / Math.max(right.from, 1);
+        resultTo = to / Math.max(right.from, 1);
+      } else if (right.to <= 0) {
+        // 8. right: np
+        resultFrom = to / Math.min(right.to, -1);
+        resultTo = from / Math.min(right.to, -1);
+      } else {
+        // 9. right: unk; values include -1 and 1
+        resultFrom = Math.min(from, -to);
+        resultTo = Math.max(-from, to);
+      }
+    }
+    return create(resultFrom, resultTo);
+  }
+
+  /**
+   * Returns a range that includes all possible values of the remainder of dividing an arbitrary
+   * value in this range by an arbitrary value in the specified range.
+   *
+   * <p>In the current implementation, the result might not be the smallest range that includes all
+   * the possible values.
+   *
+   * @param right the specified range by which this range is divided
+   * @return the range of the remainder of dividing this range by the specified range
+   */
+  public Range remainder(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+    if (right.from == 0 && right.to == 0) {
+      return NOTHING;
+    }
+    // Special cases that would cause overflow if we use the general method below
+    if (right.from == Long.MIN_VALUE) {
+      Range range;
+      // The value Long.MIN_VALUE as a divisor needs special handling as follows:
+      if (from == Long.MIN_VALUE) {
+        if (to == Long.MIN_VALUE) {
+          // This range only contains Long.MIN_VALUE, so the result range is {0}.
+          range = create(0, 0);
+        } else { // (to > Long.MIN_VALUE)
+          // When this range contains Long.MIN_VALUE, which would have a remainder of 0 if
+          // divided by Long.MIN_VALUE, the result range is {0} unioned with [from + 1, to].
+          range = create(from + 1, to).union(create(0, 0));
+        }
+      } else { // (from > Long.MIN_VALUE)
+        // When this range doesn't contain Long.MIN_VALUE, the remainder of each value
+        // in this range divided by Long.MIN_VALUE is this value itself. Therefore the
+        // result range is this range itself.
+        range = this;
+      }
+      // If right.to > Long.MIN_VALUE, union the previous result with the result of range
+      // [right.from + 1, right.to] divided by this range, which can be calculated using
+      // the general method (see below)
+      if (right.to > Long.MIN_VALUE) {
+        Range rangeAdditional = this.remainder(create(right.from + 1, right.to));
+        range = range.union(rangeAdditional);
+      }
+      return range;
+    }
+    // General method:
+    // Calculate range1: the result range of this range divided by EVERYTHING. For example,
+    // if this range is [3, 5], then the result range would be [0, 5]. If this range is [-3, 4],
+    // then the result range would be [-3, 4]. In general, the result range is {0} union with
+    // this range excluding the value Long.MIN_VALUE.
+    Range range1 =
+        create(Math.max(Long.MIN_VALUE + 1, from), Math.max(Long.MIN_VALUE + 1, to))
+            .union(create(0, 0));
+    // Calculate range2: the result range of range EVERYTHING divided by the right range. For
+    // example, if the right range is [-5, 3], then the result range would be [-4, 4]. If the
+    // right range is [3, 6], then the result range would be [-5, 5]. In general, the result
+    // range is calculated as following:
+    long maxAbsolute = Math.max(Math.abs(right.from), Math.abs(right.to));
+    Range range2 = create(-maxAbsolute + 1, maxAbsolute - 1);
+    // Since range1 and range2 are both super sets of the minimal result range, we return the
+    // intersection of range1 and range2, which is correct (super set) and precise enough.
+    return range1.intersect(range2);
+  }
+
+  /**
+   * Returns a range that includes all possible values resulting from left shifting an arbitrary
+   * value in this range by an arbitrary number of bits in the specified range. We call this the
+   * left shift of a range.
+   *
+   * @param right the range of bits by which this range is left shifted
+   * @return the range resulting from left shifting this range by the specified range
+   */
+  public Range shiftLeft(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    // Shifting operations in Java are depending on the type of the left-hand operand:
+    // If the left-hand operand is int  type, only the 5 lowest-order bits of the right-hand
+    // operand are used.
+    // If the left-hand operand is long type, only the 6 lowest-order bits of the right-hand
+    // operand are used.
+    // For example, while 1 << -1== 1 << 31, 1L << -1 == 1L << 63.
+    // For ths reason, we restrict the shift-bits to analyze in [0. 31] and give up the analysis
+    // when out of this range.
+    //
+    // Other possible solutions:
+    // 1. create different methods for int type and long type and use them accordingly
+    // 2. add an additional boolean parameter to indicate the type of the left-hand operand
+    //
+    // see https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.19 for more
+    // detail.
+    if (right.isWithin(0, 31)) {
+      if (this.isWithinInteger()) {
+        // This bound is adequate to guarantee no overflow when using long to evaluate
+        long resultFrom = from << (from >= 0 ? right.from : right.to);
+        long resultTo = to << (to >= 0 ? right.to : right.from);
+        return create(resultFrom, resultTo);
+      } else {
+        BigInteger bigFrom =
+            BigInteger.valueOf(from).shiftLeft(from >= 0 ? (int) right.from : (int) right.to);
+        BigInteger bigTo =
+            BigInteger.valueOf(to).shiftLeft(to >= 0 ? (int) right.to : (int) right.from);
+        return create(bigFrom, bigTo);
+      }
+    } else {
+      // In other cases, we give up on the calculation and return EVERYTHING (rare in practice).
+      return EVERYTHING;
+    }
+  }
+
+  /**
+   * Returns a range that includes all possible values resulting from signed right shifting an
+   * arbitrary value in this range by an arbitrary number of bits in the specified range. We call
+   * this the signed right shift operation of a range.
+   *
+   * @param right the range of bits by which this range is signed right shifted
+   * @return the range resulting from signed right shifting this range by the specified range
+   */
+  public Range signedShiftRight(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    if (this.isWithinInteger() && right.isWithin(0, 31)) {
+      // This bound is adequate to guarantee no overflow when using long to evaluate
+      long resultFrom = from >> (from >= 0 ? right.to : right.from);
+      long resultTo = to >> (to >= 0 ? right.from : right.to);
+      return create(resultFrom, resultTo);
+    } else {
+      // Signed shift right operation for long type cannot be simulated with BigInteger.
+      // Give up on the calculation and return EVERYTHING instead.
+      return EVERYTHING;
+    }
+  }
+
+  /**
+   * When this range only contains non-negative values, the refined result should be the same as
+   * {@link #signedShiftRight(Range)}. We give up the analysis when this range contains negative
+   * value(s).
+   */
+  public Range unsignedShiftRight(Range right) {
+    if (this.from >= 0) {
+      return signedShiftRight(right);
+    }
+
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    return EVERYTHING;
+  }
+
+  /**
+   * Returns a range that includes all possible values resulting from performing the bitwise and
+   * operation on a value in this range by a mask in the specified range. We call this the bitwise
+   * and operation of a range.
+   *
+   * <p>The current implementation is conservative: it only refines the cases where the range of
+   * mask represents a constant. In other cases, it gives up on the refinement and returns {@code
+   * EVERYTHING} instead.
+   *
+   * @param right the range of mask of the bitwise and operation
+   * @return the range resulting from the bitwise and operation of this range and the specified
+   *     range of mask
+   */
+  public Range bitwiseAnd(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    // We only refine the cases where the range of mask represent a constant.
+    // Recall these two's-complement facts:
+    //   11111111  represents  -1
+    //   10000000  represents  MIN_VALUE
+
+    Range constant = null;
+    Range variable = null;
+    if (right.isConstant()) {
+      constant = right;
+      variable = this;
+    } else if (this.isConstant()) {
+      constant = this;
+      variable = right;
+    }
+
+    if (constant != null) {
+      long mask = constant.from;
+      if (mask >= 0) {
+        // Sign bit of mask is 0.  The elements in the result range must be positive, and
+        // the result range is upper-bounded by the mask.
+        if (variable.from >= 0) {
+          // Case 1.1: The result range is upper-bounded by the upper bound of this range.
+          return create(0, Math.min(mask, variable.to));
+        } else if (variable.to < 0) {
+          // Case 1.2: The result range is upper-bounded by the upper bound of this range
+          // after ignoring the sign bit. The upper bound of this range has the most bits
+          // (of the highest place values) set to 1.
+          return create(0, Math.min(mask, noSignBit(variable.to)));
+        } else {
+          // Case 1.3:  Since this range contains -1, the upper bound of this range after ignoring
+          // the sign bit is Long.MAX_VALUE and thus doesn't contribute to further refinement.
+          return create(0, mask);
+        }
+      } else {
+        // Sign bit of mask is 1.
+        if (variable.from >= 0) {
+          // Case 2.1: Similar to case 1.1 except that the sign bit of the mask can be ignored.
+          return create(0, Math.min(noSignBit(mask), variable.to));
+        } else if (variable.to < 0) {
+          // Case 2.2: The sign bit of the elements in the result range must be 1.  Therefore the
+          // lower bound of the result range is Long.MIN_VALUE (when all 1-bits are mismatched
+          // between the mask and the element in this range). The result range is also upper-bounded
+          // by this mask itself and the upper bound of this range.  (Because more set bits means a
+          // larger number -- still negative, but closer to 0.)
+          return create(Long.MIN_VALUE, Math.min(mask, variable.to));
+        } else {
+          // Case 2.3: Similar to case 2.2 except that the elements in this range could
+          // be positive, and thus the result range is upper-bounded by the upper bound
+          // of this range and the mask after ignoring the sign bit.
+          return create(Long.MIN_VALUE, Math.min(noSignBit(mask), variable.to));
+        }
+      }
+    }
+
+    return EVERYTHING;
+  }
+
+  /** Return the argument, with its sign bit zeroed out. */
+  private long noSignBit(Long mask) {
+    return mask & (-1L >>> 1);
+  }
+
+  /** We give up the analysis for bitwise OR operation. */
+  public Range bitwiseOr(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    return EVERYTHING;
+  }
+
+  /** We give up the analysis for bitwise XOR operation. */
+  public Range bitwiseXor(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    return EVERYTHING;
+  }
+
+  /**
+   * Returns the range of a variable that falls within this range after applying the unary plus
+   * operation (which is a no-op).
+   *
+   * @return this range
+   */
+  public Range unaryPlus() {
+    return this;
+  }
+
+  /**
+   * Returns the range of a variable that falls within this range after applying the unary minus
+   * operation.
+   *
+   * @return the resulted range of applying unary minus on an arbitrary value in this range
+   */
+  public Range unaryMinus() {
+    if (this.isNothing()) {
+      return NOTHING;
+    }
+
+    if (from == Long.MIN_VALUE && from != to) {
+      // the only case that needs special handling because of overflow
+      return EVERYTHING;
+    }
+
+    return create(-to, -from);
+  }
+
+  /**
+   * Returns the range of a variable that falls within this range after applying the bitwise
+   * complement operation.
+   *
+   * @return the resulting range of applying bitwise complement on an arbitrary value in this range
+   */
+  public Range bitwiseComplement() {
+    if (this.isNothing()) {
+      return NOTHING;
+    }
+
+    return create(~to, ~from);
+  }
+
+  /**
+   * Refines this range to reflect that some value in it can be less than a value in the given
+   * range. This is used for calculating the control-flow-refined result of the &lt; operator. For
+   * example:
+   *
+   * <pre>
+   * <code>
+   *    {@literal @}IntRange(from = 0, to = 10) int a;
+   *    {@literal @}IntRange(from = 3, to = 7) int b;
+   *     ...
+   *     if (a &lt; b) {
+   *         // range of <i>a</i> is now refined to [0, 6] because a value in range [7, 10]
+   *         // cannot be smaller than variable <i>b</i> with range [3, 7].
+   *         ...
+   *     }
+   * </code>
+   * </pre>
+   *
+   * Use the {@link #refineGreaterThanEq(Range)} method if you are also interested in refining the
+   * range of {@code b} in the code above.
+   *
+   * @param right the specified {@code Range} to compare with
+   * @return the refined {@code Range}
+   */
+  public Range refineLessThan(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    if (right.to == Long.MIN_VALUE) {
+      return NOTHING;
+    }
+
+    long resultTo = Math.min(to, right.to - 1);
+    return createOrNothing(from, resultTo);
+  }
+
+  /**
+   * Refines this range to reflect that some value in it can be less than or equal to a value in the
+   * given range. This is used for calculating the control-flow-refined result of the &lt;=
+   * operator. For example:
+   *
+   * <pre>
+   * <code>
+   *    {@literal @}IntRange(from = 0, to = 10) int a;
+   *    {@literal @}IntRange(from = 3, to = 7) int b;
+   *     ...
+   *     if (a &lt;= b) {
+   *         // range of <i>a</i> is now refined to [0, 7] because a value in range [8, 10]
+   *         // cannot be less than or equal to variable <i>b</i> with range [3, 7].
+   *         ...
+   *     }
+   * </code>
+   * </pre>
+   *
+   * Use the {@link #refineGreaterThan(Range)} method if you are also interested in refining the
+   * range of {@code b} in the code above.
+   *
+   * @param right the specified {@code Range} to compare with
+   * @return the refined {@code Range}
+   */
+  public Range refineLessThanEq(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    long resultTo = Math.min(to, right.to);
+    return createOrNothing(from, resultTo);
+  }
+
+  /**
+   * Refines this range to reflect that some value in it can be greater than a value in the given
+   * range. This is used for calculating the control-flow-refined result of the &gt; operator. For
+   * example:
+   *
+   * <pre>
+   * <code>
+   *    {@literal @}IntRange(from = 0, to = 10) int a;
+   *    {@literal @}IntRange(from = 3, to = 7) int b;
+   *     ...
+   *     if (a &gt; b) {
+   *         // range of <i>a</i> is now refined to [4, 10] because a value in range [0, 3]
+   *         // cannot be greater than variable <i>b</i> with range [3, 7].
+   *         ...
+   *     }
+   * </code>
+   * </pre>
+   *
+   * Use the {@link #refineLessThanEq(Range)} method if you are also interested in refining the
+   * range of {@code b} in the code above.
+   *
+   * @param right the specified {@code Range} to compare with
+   * @return the refined {@code Range}
+   */
+  public Range refineGreaterThan(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    if (right.from == Long.MAX_VALUE) {
+      return NOTHING;
+    }
+
+    long resultFrom = Math.max(from, right.from + 1);
+    return createOrNothing(resultFrom, to);
+  }
+
+  /**
+   * Refines this range to reflect that some value in it can be greater than or equal to a value in
+   * the given range. This is used for calculating the control-flow-refined result of the &gt;=
+   * operator. For example:
+   *
+   * <pre>
+   * <code>
+   *    {@literal @}IntRange(from = 0, to = 10) int a;
+   *    {@literal @}IntRange(from = 3, to = 7) int b;
+   *     ...
+   *     if (a &gt;= b) {
+   *         // range of <i>a</i> is now refined to [3, 10] because a value in range [0, 2]
+   *         // cannot be greater than or equal to variable <i>b</i> with range [3, 7].
+   *         ...
+   *     }
+   * </code>
+   * </pre>
+   *
+   * Use the {@link #refineLessThan(Range)} method if you are also interested in refining the range
+   * of {@code b} in the code above.
+   *
+   * @param right the specified {@code Range} to compare with
+   * @return the refined {@code Range}
+   */
+  public Range refineGreaterThanEq(Range right) {
+    if (this.isNothing() || right.isNothing()) {
+      return NOTHING;
+    }
+
+    long resultFrom = Math.max(from, right.from);
+    return createOrNothing(resultFrom, to);
+  }
+
+  /**
+   * Refines this range to reflect that some value in it can be equal to a value in the given range.
+   * This is used for calculating the control-flow-refined result of the == operator. For example:
+   *
+   * <pre>
+   * <code>
+   *    {@literal @}IntRange(from = 0, to = 10) int a;
+   *    {@literal @}IntRange(from = 3, to = 15) int b;
+   *     ...
+   *     if (a == b) {
+   *         // range of <i>a</i> is now refined to [3, 10] because a value in range [0, 2]
+   *         // cannot be equal to variable <i>b</i> with range [3, 15].
+   *         ...
+   *     }
+   * </code>
+   * </pre>
+   *
+   * @param right the specified {@code Range} to compare with
+   * @return the refined {@code Range}
+   */
+  public Range refineEqualTo(Range right) {
+    return this.intersect(right);
+  }
+
+  /**
+   * Refines this range to reflect that some value in it must not be equal to a value in the given
+   * range. This only changes the range if the given range (right) contains exactly one integer, and
+   * that integer is one of the bounds of this range. This is used for calculating the
+   * control-flow-refined result of the != operator. For example:
+   *
+   * <pre>
+   * <code>
+   *    {@literal @}IntRange(from = 0, to = 10) int a;
+   *    {@literal @}IntRange(from = 0, to = 0) int b;
+   *     ...
+   *     if (a != b) {
+   *         // range of <i>a</i> is now refined to [1, 10] because it cannot
+   *         // be zero.
+   *         ...
+   *     }
+   * </code>
+   * </pre>
+   *
+   * @param right the specified {@code Range} to compare with
+   * @return the refined {@code Range}
+   */
+  public Range refineNotEqualTo(Range right) {
+    if (right.isConstant()) {
+      if (this.to == right.to) {
+        return create(this.from, this.to - 1);
+      } else if (this.from == right.from) {
+        return create(this.from + 1, this.to);
+      }
+    }
+    return this;
+  }
+
+  /**
+   * Determines if the range is wider than a given value, i.e., if the number of possible values
+   * enclosed by this range is more than the given value.
+   *
+   * @param value the value to compare with
+   * @return true if wider than the given value
+   */
+  public boolean isWiderThan(long value) {
+    if (this.isWithin((Long.MIN_VALUE >> 1) + 1, Long.MAX_VALUE >> 1)) {
+      // This bound is adequate to guarantee no overflow when using long to evaluate.
+      // Long.MIN_VALUE >> 1 + 1 = -4611686018427387903
+      // Long.MAX_VALUE >> 1 = 4611686018427387903
+      return width() > value;
+    } else {
+      return BigInteger.valueOf(to)
+              .subtract(BigInteger.valueOf(from))
+              .add(BigInteger.ONE)
+              .compareTo(BigInteger.valueOf(value))
+          > 0;
+    }
+  }
+
+  /** Determines if this range represents a constant value. */
+  public boolean isConstant() {
+    return from == to;
+  }
+
+  /**
+   * Determines if this range is completely contained in the range specified by the given lower
+   * bound inclusive and upper bound inclusive.
+   *
+   * @param lb lower bound for the range that might contain this one
+   * @param ub upper bound for the range that might contain this one
+   * @return true if this range is within the given bounds
+   */
+  public boolean isWithin(long lb, long ub) {
+    assert lb <= ub;
+    return lb <= from && to <= ub;
+  }
+
+  /**
+   * Determines if this range is contained inclusively between Long.MIN_VALUE/2 and
+   * Long.MAX_VALUE/2. Note: Long.MIN_VALUE/2 != -Long.MAX_VALUE/2
+   */
+  private boolean isWithinHalfLong() {
+    return isWithin(Long.MIN_VALUE >> 1, Long.MAX_VALUE >> 1);
+  }
+
+  /**
+   * Determines if this range is completely contained in the scope of the Integer type.
+   *
+   * @return true if the range is contained within the Integer range inclusive
+   */
+  public boolean isWithinInteger() {
+    return isWithin(Integer.MIN_VALUE, Integer.MAX_VALUE);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/util/ShortMath.java b/framework/src/main/java/org/checkerframework/common/value/util/ShortMath.java
new file mode 100644
index 0000000..d1d4ec6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/value/util/ShortMath.java
@@ -0,0 +1,385 @@
+package org.checkerframework.common.value.util;
+
+public class ShortMath extends NumberMath<Integer> {
+  int number;
+
+  public ShortMath(int i) {
+    number = i;
+  }
+
+  @Override
+  public Number plus(Number right) {
+    if (right instanceof Byte) {
+      return number + right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number + right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number + right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number + right.intValue();
+    }
+    if (right instanceof Long) {
+      return number + right.longValue();
+    }
+    if (right instanceof Short) {
+      return number + right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number minus(Number right) {
+    if (right instanceof Byte) {
+      return number - right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number - right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number - right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number - right.intValue();
+    }
+    if (right instanceof Long) {
+      return number - right.longValue();
+    }
+    if (right instanceof Short) {
+      return number - right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number times(Number right) {
+    if (right instanceof Byte) {
+      return number * right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number * right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number * right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number * right.intValue();
+    }
+    if (right instanceof Long) {
+      return number * right.longValue();
+    }
+    if (right instanceof Short) {
+      return number * right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number divide(Number right) {
+    if (isIntegralZero(right)) {
+      return null;
+    }
+    if (right instanceof Byte) {
+      return number / right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number / right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number / right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number / right.intValue();
+    }
+    if (right instanceof Long) {
+      return number / right.longValue();
+    }
+    if (right instanceof Short) {
+      return number / right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number remainder(Number right) {
+    if (isIntegralZero(right)) {
+      return null;
+    }
+    if (right instanceof Byte) {
+      return number % right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number % right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number % right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number % right.intValue();
+    }
+    if (right instanceof Long) {
+      return number % right.longValue();
+    }
+    if (right instanceof Short) {
+      return number % right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number shiftLeft(Number right) {
+    if (right instanceof Byte) {
+      return number << right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number << right.intValue();
+    }
+    if (right instanceof Long) {
+      return number << right.longValue();
+    }
+    if (right instanceof Short) {
+      return number << right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number signedShiftRight(Number right) {
+    if (right instanceof Byte) {
+      return number >> right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number >> right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >> right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >> right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number unsignedShiftRight(Number right) {
+    if (right instanceof Byte) {
+      return number >>> right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number >>> right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >>> right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >>> right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseAnd(Number right) {
+    if (right instanceof Byte) {
+      return number & right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number & right.intValue();
+    }
+    if (right instanceof Long) {
+      return number & right.longValue();
+    }
+    if (right instanceof Short) {
+      return number & right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseXor(Number right) {
+    if (right instanceof Byte) {
+      return number ^ right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number ^ right.intValue();
+    }
+    if (right instanceof Long) {
+      return number ^ right.longValue();
+    }
+    if (right instanceof Short) {
+      return number ^ right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number bitwiseOr(Number right) {
+    if (right instanceof Byte) {
+      return number | right.byteValue();
+    }
+    if (right instanceof Integer) {
+      return number | right.intValue();
+    }
+    if (right instanceof Long) {
+      return number | right.longValue();
+    }
+    if (right instanceof Short) {
+      return number | right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Number unaryPlus() {
+    return +number;
+  }
+
+  @Override
+  public Number unaryMinus() {
+    return -number;
+  }
+
+  @Override
+  public Number bitwiseComplement() {
+    return ~number;
+  }
+
+  @Override
+  public Boolean equalTo(Number right) {
+    if (right instanceof Byte) {
+      return number == right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number == right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number == right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number == right.intValue();
+    }
+    if (right instanceof Long) {
+      return number == right.longValue();
+    }
+    if (right instanceof Short) {
+      return number == right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean notEqualTo(Number right) {
+    if (right instanceof Byte) {
+      return number != right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number != right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number != right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number != right.intValue();
+    }
+    if (right instanceof Long) {
+      return number != right.longValue();
+    }
+    if (right instanceof Short) {
+      return number != right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean greaterThan(Number right) {
+    if (right instanceof Byte) {
+      return number > right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number > right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number > right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number > right.intValue();
+    }
+    if (right instanceof Long) {
+      return number > right.longValue();
+    }
+    if (right instanceof Short) {
+      return number > right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean greaterThanEq(Number right) {
+    if (right instanceof Byte) {
+      return number >= right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number >= right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number >= right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number >= right.intValue();
+    }
+    if (right instanceof Long) {
+      return number >= right.longValue();
+    }
+    if (right instanceof Short) {
+      return number >= right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean lessThan(Number right) {
+    if (right instanceof Byte) {
+      return number < right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number < right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number < right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number < right.intValue();
+    }
+    if (right instanceof Long) {
+      return number < right.longValue();
+    }
+    if (right instanceof Short) {
+      return number < right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean lessThanEq(Number right) {
+    if (right instanceof Byte) {
+      return number <= right.byteValue();
+    }
+    if (right instanceof Double) {
+      return number <= right.doubleValue();
+    }
+    if (right instanceof Float) {
+      return number <= right.floatValue();
+    }
+    if (right instanceof Integer) {
+      return number <= right.intValue();
+    }
+    if (right instanceof Long) {
+      return number <= right.longValue();
+    }
+    if (right instanceof Short) {
+      return number <= right.shortValue();
+    }
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/AnnotationConverter.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/AnnotationConverter.java
new file mode 100644
index 0000000..cefc653
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/AnnotationConverter.java
@@ -0,0 +1,210 @@
+package org.checkerframework.common.wholeprograminference;
+
+import com.sun.tools.javac.code.Type.ArrayType;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.reflection.Signatures;
+import org.plumelib.util.CollectionsPlume;
+import scenelib.annotations.Annotation;
+import scenelib.annotations.el.AnnotationDef;
+import scenelib.annotations.field.AnnotationFieldType;
+import scenelib.annotations.field.ArrayAFT;
+import scenelib.annotations.field.BasicAFT;
+import scenelib.annotations.field.ClassTokenAFT;
+import scenelib.annotations.field.EnumAFT;
+import scenelib.annotations.field.ScalarAFT;
+
+/**
+ * This class contains static methods that convert between {@link scenelib.annotations.Annotation}
+ * and {@link javax.lang.model.element.AnnotationMirror}.
+ */
+public class AnnotationConverter {
+
+  /**
+   * Converts an {@link javax.lang.model.element.AnnotationMirror} into an {@link
+   * scenelib.annotations.Annotation}.
+   *
+   * @param am the AnnotationMirror
+   * @return the Annotation
+   */
+  public static Annotation annotationMirrorToAnnotation(AnnotationMirror am) {
+    @SuppressWarnings("signature:argument") // TODO: bug for inner classes
+    AnnotationDef def =
+        new AnnotationDef(
+            AnnotationUtils.annotationName(am),
+            String.format(
+                "annotationMirrorToAnnotation %s [%s] keyset=%s",
+                am, am.getClass(), am.getElementValues().keySet()));
+    Map<String, AnnotationFieldType> fieldTypes = new HashMap<>(am.getElementValues().size());
+    // Handling cases where there are fields in annotations.
+    for (ExecutableElement ee : am.getElementValues().keySet()) {
+      AnnotationFieldType aft = getAnnotationFieldType(ee);
+      fieldTypes.put(ee.getSimpleName().toString(), aft);
+    }
+    def.setFieldTypes(fieldTypes);
+
+    // Now, we handle the values of those types below
+    Map<? extends ExecutableElement, ? extends AnnotationValue> values = am.getElementValues();
+    Map<String, Object> newValues = new HashMap<>(values.size());
+    for (ExecutableElement ee : values.keySet()) {
+      Object value = values.get(ee).getValue();
+      if (value instanceof List) {
+        // If we have a List here, then it is a List of AnnotationValue.
+        // Convert each AnnotationValue to its respective Java type.
+        @SuppressWarnings("unchecked")
+        List<AnnotationValue> valueList = (List<AnnotationValue>) value;
+        value = CollectionsPlume.mapList(AnnotationValue::getValue, valueList);
+      } else if (value instanceof TypeMirror) {
+        try {
+          value = Class.forName(TypesUtils.binaryName((TypeMirror) value));
+        } catch (ClassNotFoundException e) {
+          throw new BugInCF(e, "value = %s [%s]", value, value.getClass());
+        }
+      }
+      newValues.put(ee.getSimpleName().toString(), value);
+    }
+    Annotation out = new Annotation(def, newValues);
+    return out;
+  }
+
+  /**
+   * Converts an {@link scenelib.annotations.Annotation} into an {@link
+   * javax.lang.model.element.AnnotationMirror}.
+   *
+   * @param anno the Annotation
+   * @param processingEnv the ProcessingEnvironment
+   * @return the AnnotationMirror
+   */
+  protected static AnnotationMirror annotationToAnnotationMirror(
+      Annotation anno, ProcessingEnvironment processingEnv) {
+    final AnnotationBuilder builder =
+        new AnnotationBuilder(
+            processingEnv, Signatures.binaryNameToFullyQualified(anno.def().name));
+    for (String fieldKey : anno.fieldValues.keySet()) {
+      addFieldToAnnotationBuilder(fieldKey, anno.fieldValues.get(fieldKey), builder);
+    }
+    return builder.build();
+  }
+
+  /**
+   * Returns the type of an element (that is, a field) of an annotation.
+   *
+   * @param ee an element (that is, a field) of an annotation
+   * @return the type of the given annotation field
+   */
+  protected static @Nullable AnnotationFieldType getAnnotationFieldType(ExecutableElement ee) {
+    return typeMirrorToAnnotationFieldType(ee.getReturnType());
+  }
+
+  /**
+   * Converts a TypeMirror to an AnnotationFieldType.
+   *
+   * @param tm a type for an annotation element/field: primitive, String, class, enum constant, or
+   *     array thereof
+   * @return an AnnotationFieldType corresponding to the argument
+   */
+  protected static AnnotationFieldType typeMirrorToAnnotationFieldType(TypeMirror tm) {
+    switch (tm.getKind()) {
+      case BOOLEAN:
+        return BasicAFT.forType(boolean.class);
+        // Primitves
+      case BYTE:
+        return BasicAFT.forType(byte.class);
+      case CHAR:
+        return BasicAFT.forType(char.class);
+      case DOUBLE:
+        return BasicAFT.forType(double.class);
+      case FLOAT:
+        return BasicAFT.forType(float.class);
+      case INT:
+        return BasicAFT.forType(int.class);
+      case LONG:
+        return BasicAFT.forType(long.class);
+      case SHORT:
+        return BasicAFT.forType(short.class);
+
+      case ARRAY:
+        TypeMirror componentType = (((ArrayType) tm).getComponentType());
+        AnnotationFieldType componentAFT = typeMirrorToAnnotationFieldType(componentType);
+        return new ArrayAFT((ScalarAFT) componentAFT);
+
+      case DECLARED:
+        String className = TypesUtils.getQualifiedName((DeclaredType) tm);
+        if (className.equals("java.lang.String")) {
+          return BasicAFT.forType(String.class);
+        } else if (className.equals("java.lang.Class")) {
+          return ClassTokenAFT.ctaft;
+        } else {
+          // This must be an enum constant.
+          return new EnumAFT(className);
+        }
+
+      default:
+        throw new BugInCF(
+            "typeMirrorToAnnotationFieldType: unexpected argument %s [%s %s]",
+            tm, tm.getKind(), tm.getClass());
+    }
+  }
+
+  /**
+   * Adds a field to an AnnotationBuilder.
+   *
+   * @param fieldKey is the name of the field
+   * @param obj is the value of the field
+   * @param builder is the AnnotationBuilder
+   */
+  @SuppressWarnings("unchecked") // This is actually checked in the first instanceOf call below.
+  protected static void addFieldToAnnotationBuilder(
+      String fieldKey, Object obj, AnnotationBuilder builder) {
+    if (obj instanceof List<?>) {
+      builder.setValue(fieldKey, (List<Object>) obj);
+    } else if (obj instanceof String) {
+      builder.setValue(fieldKey, (String) obj);
+    } else if (obj instanceof Integer) {
+      builder.setValue(fieldKey, (Integer) obj);
+    } else if (obj instanceof Float) {
+      builder.setValue(fieldKey, (Float) obj);
+    } else if (obj instanceof Long) {
+      builder.setValue(fieldKey, (Long) obj);
+    } else if (obj instanceof Boolean) {
+      builder.setValue(fieldKey, (Boolean) obj);
+    } else if (obj instanceof Character) {
+      builder.setValue(fieldKey, (Character) obj);
+    } else if (obj instanceof Class<?>) {
+      builder.setValue(fieldKey, (Class<?>) obj);
+    } else if (obj instanceof Double) {
+      builder.setValue(fieldKey, (Double) obj);
+    } else if (obj instanceof Enum<?>) {
+      builder.setValue(fieldKey, (Enum<?>) obj);
+    } else if (obj instanceof Enum<?>[]) {
+      builder.setValue(fieldKey, (Enum<?>[]) obj);
+    } else if (obj instanceof AnnotationMirror) {
+      builder.setValue(fieldKey, (AnnotationMirror) obj);
+    } else if (obj instanceof Object[]) {
+      builder.setValue(fieldKey, (Object[]) obj);
+    } else if (obj instanceof TypeMirror) {
+      builder.setValue(fieldKey, (TypeMirror) obj);
+    } else if (obj instanceof Short) {
+      builder.setValue(fieldKey, (Short) obj);
+    } else if (obj instanceof VariableElement) {
+      builder.setValue(fieldKey, (VariableElement) obj);
+    } else if (obj instanceof VariableElement[]) {
+      builder.setValue(fieldKey, (VariableElement[]) obj);
+    } else {
+      throw new BugInCF("Unrecognized type: " + obj.getClass());
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java
new file mode 100644
index 0000000..6890e34
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java
@@ -0,0 +1,784 @@
+package org.checkerframework.common.wholeprograminference;
+
+import com.google.common.collect.ComparisonChain;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.regex.Pattern;
+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.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.index.qual.SameLen;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.qual.MinLen;
+import org.checkerframework.common.wholeprograminference.scenelib.ASceneWrapper;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import scenelib.annotations.Annotation;
+import scenelib.annotations.el.AClass;
+import scenelib.annotations.el.AField;
+import scenelib.annotations.el.AMethod;
+import scenelib.annotations.el.AScene;
+import scenelib.annotations.el.ATypeElement;
+import scenelib.annotations.el.AnnotationDef;
+import scenelib.annotations.el.DefCollector;
+import scenelib.annotations.el.DefException;
+import scenelib.annotations.el.TypePathEntry;
+import scenelib.annotations.field.AnnotationFieldType;
+
+// In this file, "base name" means "type without its package part in binary name format".
+// For example, "Outer$Inner" is a base name.
+
+/**
+ * Static method {@link #write} writes an {@link AScene} to a file in stub file format. This class
+ * is the equivalent of {@code IndexFileWriter} from the Annotation File Utilities, but outputs the
+ * results in the stub file format instead of jaif format. This class is not part of the Annotation
+ * File Utilities, a library for manipulating .jaif files, because it has nothing to do with .jaif
+ * files.
+ *
+ * <p>This class works by taking as input a scene-lib representation of a type augmented with
+ * additional information, stored in javac's format (e.g. as TypeMirrors or Elements). {@link
+ * ASceneWrapper} stores this additional information. This class walks the scene-lib representation
+ * structurally and outputs the stub file as a string, by combining the information scene-lib stores
+ * with the information gathered elsewhere.
+ *
+ * <p>The additional information is necessary because the scene-lib representation of a type does
+ * not have enough information to print full types.
+ *
+ * <p>This writer is used instead of {@code IndexFileWriter} if the {@code -Ainfer=stubs}
+ * command-line argument is present.
+ */
+public final class SceneToStubWriter {
+
+  /**
+   * The entry point to this class is {@link #write}.
+   *
+   * <p>This is a utility class with only static methods. It is not instantiable.
+   */
+  private SceneToStubWriter() {
+    throw new Error("Do not instantiate");
+  }
+
+  /**
+   * A pattern matching the name of an anonymous inner class, a local class, or a class nested
+   * within one of these types of classes. An anonymous inner class has a basename like Outer$1 and
+   * a local class has a basename like Outer$1Inner. See <a
+   * href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1">Java Language
+   * Specification, section 13.1</a>.
+   */
+  private static final Pattern anonymousInnerClassOrLocalClassPattern = Pattern.compile("\\$\\d+");
+
+  /** How far to indent when writing members of a stub file. */
+  private static final String INDENT = "  ";
+
+  /**
+   * Writes the annotations in {@code scene} to {@code out} in stub file format.
+   *
+   * @param scene the scene to write out
+   * @param filename the name of the file to write (must end with .astub)
+   * @param checker the checker, for computing preconditions and postconditions
+   */
+  public static void write(ASceneWrapper scene, String filename, BaseTypeChecker checker) {
+    writeImpl(scene, filename, checker);
+  }
+
+  /**
+   * Returns the part of a binary name that specifies the package.
+   *
+   * @param className the binary name of a class
+   * @return the part of the name referring to the package, or null if there is no package name
+   */
+  @SuppressWarnings("signature") // a valid non-empty package name is a dot separated identifier
+  private static @Nullable @DotSeparatedIdentifiers String packagePart(
+      @BinaryName String className) {
+    int lastdot = className.lastIndexOf('.');
+    return (lastdot == -1) ? null : className.substring(0, lastdot);
+  }
+
+  /**
+   * Returns the part of a binary name that specifies the basename of the class.
+   *
+   * @param className a binary name
+   * @return the part of the name representing the class's name without its package
+   */
+  @SuppressWarnings("signature:return") // A binary name without its package is still a binary name
+  private static @BinaryName String basenamePart(@BinaryName String className) {
+    int lastdot = className.lastIndexOf('.');
+    return className.substring(lastdot + 1);
+  }
+
+  /**
+   * Returns the String representation of an annotation in Java source format.
+   *
+   * @param a the annotation to print
+   * @return the formatted annotation
+   */
+  public static String formatAnnotation(Annotation a) {
+    String fullAnnoName = a.def().name;
+    String simpleAnnoName = fullAnnoName.substring(fullAnnoName.lastIndexOf('.') + 1);
+    if (a.fieldValues.isEmpty()) {
+      return "@" + simpleAnnoName;
+    }
+    StringJoiner sj = new StringJoiner(", ", "@" + simpleAnnoName + "(", ")");
+    if (a.fieldValues.size() == 1 && a.fieldValues.containsKey("value")) {
+      AnnotationFieldType aft = a.def().fieldTypes.get("value");
+      sj.add(aft.format(a.fieldValues.get("value")));
+    } else {
+      for (Map.Entry<String, Object> f : a.fieldValues.entrySet()) {
+        AnnotationFieldType aft = a.def().fieldTypes.get(f.getKey());
+        sj.add(f.getKey() + "=" + aft.format(f.getValue()));
+      }
+    }
+    return sj.toString();
+  }
+
+  /**
+   * Returns all annotations in {@code annos} in a form suitable to be printed as Java source code.
+   *
+   * <p>Each annotation is followed by a space, to separate it from following Java code.
+   *
+   * @param annos the annotations to format
+   * @return all annotations in {@code annos}, each followed by a space, in a form suitable to be
+   *     printed as Java source code
+   */
+  private static String formatAnnotations(Collection<? extends Annotation> annos) {
+    StringBuilder sb = new StringBuilder();
+    for (Annotation tla : annos) {
+      if (!isInternalJDKAnnotation(tla.def.name)) {
+        sb.append(formatAnnotation(tla));
+        sb.append(" ");
+      }
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Formats the type of an array so that it is printable in Java source code, with the annotations
+   * from the scenelib representation added in appropriate places.
+   *
+   * @param scenelibRep the array's scenelib type element
+   * @param javacRep the representation of the array's type used by javac
+   * @return the type formatted to be written to Java source code, followed by a space character
+   */
+  private static String formatArrayType(ATypeElement scenelibRep, ArrayType javacRep) {
+    TypeMirror componentType = javacRep.getComponentType();
+    ATypeElement scenelibComponent = getNextArrayLevel(scenelibRep);
+    while (componentType.getKind() == TypeKind.ARRAY) {
+      componentType = ((ArrayType) componentType).getComponentType();
+      scenelibComponent = getNextArrayLevel(scenelibComponent);
+    }
+    return formatType(scenelibComponent, componentType)
+        + formatArrayTypeImpl(scenelibRep, javacRep);
+  }
+
+  /**
+   * Formats the type of an array to be printable in Java source code, with the annotations from the
+   * scenelib representation added. This method formats only the "array" parts of an array type; it
+   * does not format (or attempt to format) the ultimate component type (that is, the non-array part
+   * of the array type).
+   *
+   * @param scenelibRep the scene-lib representation
+   * @param javacRep the javac representation of the array type
+   * @return the type formatted to be written to Java source code, followed by a space character
+   */
+  private static String formatArrayTypeImpl(ATypeElement scenelibRep, ArrayType javacRep) {
+    TypeMirror javacComponent = javacRep.getComponentType();
+    ATypeElement scenelibComponent = getNextArrayLevel(scenelibRep);
+    String result = "";
+    List<? extends AnnotationMirror> explicitAnnos = javacRep.getAnnotationMirrors();
+    for (AnnotationMirror explicitAnno : explicitAnnos) {
+      result += explicitAnno.toString();
+      result += " ";
+    }
+    if (result.isEmpty() && scenelibRep != null) {
+      result += formatAnnotations(scenelibRep.tlAnnotationsHere);
+    }
+    result += "[] ";
+    if (javacComponent.getKind() == TypeKind.ARRAY) {
+      return result + formatArrayTypeImpl(scenelibComponent, (ArrayType) javacComponent);
+    } else {
+      return result;
+    }
+  }
+
+  /** Static variable to improve performance of getNextArrayLevel. */
+  private static List<TypePathEntry> location;
+
+  /**
+   * Gets the outermost array level (or the component if not an array) from the given type element,
+   * or null if scene-lib is not storing any more information about this array (for example, when
+   * the component type is unannotated).
+   *
+   * @param e the array type element; can be null
+   * @return the next level of the array, if scene-lib stores information on it. null if the input
+   *     is null or scene-lib is not storing more information.
+   */
+  private static @Nullable ATypeElement getNextArrayLevel(@Nullable ATypeElement e) {
+    if (e == null) {
+      return null;
+    }
+
+    for (Map.Entry<List<TypePathEntry>, ATypeElement> ite : e.innerTypes.entrySet()) {
+      location = ite.getKey();
+      if (location.contains(TypePathEntry.ARRAY_ELEMENT)) {
+        return ite.getValue();
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Formats a single formal parameter declaration.
+   *
+   * @param param the AField that represents the parameter
+   * @param parameterName the name of the parameter to display in the stub file. Stub files
+   *     disregard formal parameter names, so this is aesthetic in almost all cases. The exception
+   *     is the receiver parameter, whose name must be "this".
+   * @param basename the type name to use for the receiver parameter. Only used when the previous
+   *     argument is exactly the String "this".
+   * @return the formatted formal parameter, as if it were written in Java source code
+   */
+  private static String formatParameter(AField param, String parameterName, String basename) {
+    StringJoiner result = new StringJoiner(" ");
+    for (Annotation declAnno : param.tlAnnotationsHere) {
+      result.add(formatAnnotation(declAnno));
+    }
+    result.add(formatAFieldImpl(param, parameterName, basename));
+    return result.toString();
+  }
+
+  /**
+   * Formats a field declaration or formal parameter so that it can be printed in a stub.
+   *
+   * <p>This method does not add a trailing semicolon or comma.
+   *
+   * <p>Usually, {@link #formatParameter(AField, String, String)} should be called to format method
+   * parameters, and {@link #printField(AField, String, PrintWriter, String)} should be called to
+   * print field declarations. Both use this method as their underlying implementation.
+   *
+   * @param aField the field declaration or formal parameter declaration to format; should not
+   *     represent a local variable
+   * @param fieldName the name to use for the declaration in the stub file. This doesn't matter for
+   *     parameters (except the "this" receiver parameter), but must be correct for fields.
+   * @param className the simple name of the enclosing class. This is only used for printing the
+   *     type of an explicit receiver parameter (i.e., a parameter named "this").
+   * @return a String suitable to print in a stub file
+   */
+  private static String formatAFieldImpl(AField aField, String fieldName, String className) {
+    if ("this".equals(fieldName)) {
+      return formatType(aField.type, null, className) + fieldName;
+    } else {
+      return formatType(aField.type, aField.getTypeMirror()) + fieldName;
+    }
+  }
+
+  /**
+   * Formats the given type for printing in Java source code.
+   *
+   * @param aType the scene-lib representation of the type, or null if only the unannotated type is
+   *     to be printed
+   * @param javacType the javac representation of the type
+   * @return the type as it would appear in Java source code, followed by a trailing space
+   */
+  private static String formatType(final @Nullable ATypeElement aType, final TypeMirror javacType) {
+    // TypeMirror#toString prints multiple annotations on a single type
+    // separated by commas rather than by whitespace, as is required in source code.
+    String basetypeToPrint = javacType.toString().replaceAll(",@", " @");
+
+    // We must not print annotations in the default package that conflict with
+    // imported annotation names.
+    for (AnnotationMirror anm : javacType.getAnnotationMirrors()) {
+      String annotationName = AnnotationUtils.annotationName(anm);
+      String simpleName = annotationName.substring(annotationName.lastIndexOf('.') + 1);
+      // This checks if it is in the default package.
+      if (simpleName.equals(annotationName)) {
+        // In that case, do not print any annotations with the type, to
+        // avoid needing to parse an annotation string to remove it.
+        // TypeMirror does not provide any methods to remove annotations.
+        // This code relies on unannotated Java types not including spaces.
+        basetypeToPrint = basetypeToPrint.substring(basetypeToPrint.lastIndexOf(' ') + 1);
+      }
+    }
+    return formatType(aType, javacType, basetypeToPrint);
+  }
+
+  /**
+   * Formats the given type for printing in Java source code. This separate version of this method
+   * exists only for receiver parameters, which are printed using the name of the class as {@code
+   * basetypeToPrint} instead of the javac type. The other version of this method should be
+   * preferred in every other case.
+   *
+   * @param aType the scene-lib representation of the type, or null if only the unannotated type is
+   *     to be printed
+   * @param javacType the javac representation of the type, or null if this is a receiver parameter
+   * @param basetypeToPrint the string representation of the type
+   * @return the type as it would appear in Java source code, followed by a trailing space
+   */
+  private static String formatType(
+      final @Nullable ATypeElement aType, @Nullable TypeMirror javacType, String basetypeToPrint) {
+    // anonymous static classes shouldn't be printed with the "anonymous" tag that the AScene
+    // library uses
+    if (basetypeToPrint.startsWith("<anonymous ")) {
+      basetypeToPrint =
+          basetypeToPrint.substring("<anonymous ".length(), basetypeToPrint.length() - 1);
+    }
+
+    // fields don't need their generic types, and sometimes they are wrong. Just don't print them.
+    while (basetypeToPrint.contains("<")) {
+      basetypeToPrint =
+          basetypeToPrint.substring(0, basetypeToPrint.indexOf('<'))
+              + basetypeToPrint.substring(basetypeToPrint.lastIndexOf('>') + 1);
+    }
+
+    // An array is not a receiver, so using the javacType to check for arrays is safe.
+    if (javacType != null && javacType.getKind() == TypeKind.ARRAY) {
+      return formatArrayType(aType, (ArrayType) javacType);
+    }
+
+    if (aType == null) {
+      return basetypeToPrint + " ";
+    } else {
+      return formatAnnotations(aType.tlAnnotationsHere) + basetypeToPrint + " ";
+    }
+  }
+
+  /** Writes an import statement for each annotation used in an {@link AScene}. */
+  private static class ImportDefWriter extends DefCollector {
+
+    /** The writer onto which to write the import statements. */
+    private final PrintWriter printWriter;
+
+    /**
+     * Constructs a new ImportDefWriter, which will run on the given AScene when its {@code visit}
+     * method is called.
+     *
+     * @param scene the scene whose imported annotations should be printed
+     * @param printWriter the writer onto which to write the import statements
+     * @throws DefException if the DefCollector does not succeed
+     */
+    ImportDefWriter(ASceneWrapper scene, PrintWriter printWriter) throws DefException {
+      super(scene.getAScene());
+      this.printWriter = printWriter;
+    }
+
+    /**
+     * Write an import statement for a given AnnotationDef. This is only called once per annotation
+     * used in the scene.
+     *
+     * @param d the annotation definition to print an import for
+     */
+    @Override
+    protected void visitAnnotationDef(AnnotationDef d) {
+      if (!isInternalJDKAnnotation(d.name)) {
+        printWriter.println("import " + d.name + ";");
+      }
+    }
+  }
+
+  /**
+   * Return true if the given annotation is an internal JDK annotation, whose name includes '+'.
+   *
+   * @param annotationName the name of the annotation
+   * @return true iff this is an internal JDK annotation
+   */
+  private static boolean isInternalJDKAnnotation(String annotationName) {
+    return annotationName.contains("+");
+  }
+
+  /**
+   * Print the hierarchy of outer classes up to and including the given class, and return the number
+   * of curly braces to close with. The classes are printed with appropriate opening curly braces,
+   * in standard Java style.
+   *
+   * <p>In an AScene, an inner class name is a binary name like "Outer$Inner". In a stub file, inner
+   * classes must be nested, as in Java source code.
+   *
+   * @param basename the binary name of the class without the package part
+   * @param aClass the AClass for {@code basename}
+   * @param printWriter the writer where the class definition should be printed
+   * @param checker the type-checker whose annotations are being written
+   * @return the number of outer classes within which this class is nested
+   */
+  private static int printClassDefinitions(
+      String basename, AClass aClass, PrintWriter printWriter, BaseTypeChecker checker) {
+    String[] classNames = basename.split("\\$");
+    TypeElement innermostTypeElt = aClass.getTypeElement();
+    if (innermostTypeElt == null) {
+      throw new BugInCF("typeElement was unexpectedly null in this aClass: " + aClass);
+    }
+    TypeElement[] typeElements = getTypeElementsForClasses(innermostTypeElt, classNames);
+
+    for (int i = 0; i < classNames.length; i++) {
+      String nameToPrint = classNames[i];
+      if (i == classNames.length - 1) {
+        printWriter.print(indents(i));
+        printWriter.println("@AnnotatedFor(\"" + checker.getClass().getCanonicalName() + "\")");
+      }
+      printWriter.print(indents(i));
+      if (aClass.isEnum(nameToPrint)) {
+        printWriter.print("enum ");
+      } else {
+        printWriter.print("class ");
+      }
+      if (i == classNames.length - 1) {
+        // Only print class annotations on the innermost class, which corresponds to aClass.
+        // If there should be class annotations on another class, it will have its own stub
+        // file, which will eventually be merged with this one.
+        printWriter.print(formatAnnotations(aClass.getAnnotations()));
+      }
+      printWriter.print(nameToPrint);
+      printTypeParameters(typeElements[i], printWriter);
+      printWriter.println(" {");
+      if (aClass.isEnum(nameToPrint) && i != classNames.length - 1) {
+        // Print a blank set of enum constants if this is an outer enum.
+        printWriter.println(indents(i + 1) + "/* omitted enum constants */ ;");
+      }
+      printWriter.println();
+    }
+    return classNames.length;
+  }
+
+  /**
+   * Constructs an array of TypeElements corresponding to the list of classes.
+   *
+   * @param innermostTypeElt the innermost type element: either an inner class or an outer class
+   *     without any inner classes that should be printed
+   * @param classNames the names of the containing classes, from outer to inner
+   * @return an array of TypeElements whose entry at a given index represents the type named at that
+   *     index in {@code classNames}
+   */
+  private static TypeElement @SameLen("#2") [] getTypeElementsForClasses(
+      TypeElement innermostTypeElt, String @MinLen(1) [] classNames) {
+    TypeElement[] result = new TypeElement[classNames.length];
+    result[classNames.length - 1] = innermostTypeElt;
+    Element elt = innermostTypeElt;
+    for (int i = classNames.length - 2; i >= 0; i--) {
+      elt = elt.getEnclosingElement();
+      result[i] = (TypeElement) elt;
+    }
+    return result;
+  }
+
+  /**
+   * Prints all the fields of a given class.
+   *
+   * @param aClass the class whose fields should be printed
+   * @param printWriter the writer on which to print the fields
+   * @param indentLevel the indent string
+   */
+  private static void printFields(AClass aClass, PrintWriter printWriter, String indentLevel) {
+
+    if (aClass.getFields().isEmpty()) {
+      return;
+    }
+
+    printWriter.println(indentLevel + "// fields:");
+    printWriter.println();
+    for (Map.Entry<String, AField> fieldEntry : aClass.getFields().entrySet()) {
+      String fieldName = fieldEntry.getKey();
+      AField aField = fieldEntry.getValue();
+      printField(aField, fieldName, printWriter, indentLevel);
+    }
+  }
+
+  /**
+   * Prints a field declaration, including a trailing semicolon and a newline.
+   *
+   * @param aField the field declaration
+   * @param fieldName the name of the field
+   * @param printWriter the writer on which to print
+   * @param indentLevel the indent string
+   */
+  private static void printField(
+      AField aField, String fieldName, PrintWriter printWriter, String indentLevel) {
+    if (aField.getTypeMirror() == null) {
+      // aField has no type mirror, so there are no inferred annotations and the field need
+      // not be printed.
+      return;
+    }
+
+    for (Annotation declAnno : aField.tlAnnotationsHere) {
+      printWriter.print(indentLevel);
+      printWriter.println(formatAnnotation(declAnno));
+    }
+
+    printWriter.print(indentLevel);
+    printWriter.print(formatAFieldImpl(aField, fieldName, /*enclosing class=*/ null));
+    printWriter.println(";");
+    printWriter.println();
+  }
+
+  /**
+   * Prints a method declaration in stub file format (i.e., without a method body).
+   *
+   * @param aMethod the method to print
+   * @param simplename the simple name of the enclosing class, for receiver parameters and
+   *     constructor names
+   * @param printWriter where to print the method signature
+   * @param atf the type factory, for computing preconditions and postconditions
+   * @param indentLevel the indent string
+   */
+  private static void printMethodDeclaration(
+      AMethod aMethod,
+      String simplename,
+      PrintWriter printWriter,
+      String indentLevel,
+      GenericAnnotatedTypeFactory<?, ?, ?, ?> atf) {
+
+    if (aMethod.getTypeParameters() == null) {
+      // aMethod.setFieldsFromMethodElement has not been called
+      return;
+    }
+
+    for (Annotation declAnno : aMethod.tlAnnotationsHere) {
+      printWriter.print(indentLevel);
+      printWriter.println(formatAnnotation(declAnno));
+    }
+
+    for (AnnotationMirror contractAnno : atf.getContractAnnotations(aMethod)) {
+      printWriter.print(indentLevel);
+      printWriter.println(contractAnno);
+    }
+
+    printWriter.print(indentLevel);
+
+    printTypeParameters(aMethod.getTypeParameters(), printWriter);
+
+    String methodName = aMethod.getMethodName();
+    // Use Java syntax for constructors.
+    if ("<init>".equals(methodName)) {
+      // Set methodName, but don't output a return type.
+      methodName = simplename;
+    } else {
+      printWriter.print(formatType(aMethod.returnType, aMethod.getReturnTypeMirror()));
+    }
+    printWriter.print(methodName);
+    printWriter.print("(");
+
+    StringJoiner parameters = new StringJoiner(", ");
+    if (!aMethod.receiver.type.tlAnnotationsHere.isEmpty()) {
+      // Only output the receiver if it has an annotation.
+      parameters.add(formatParameter(aMethod.receiver, "this", simplename));
+    }
+    for (Integer index : aMethod.getParameters().keySet()) {
+      AField param = aMethod.getParameters().get(index);
+      parameters.add(formatParameter(param, param.getName(), simplename));
+    }
+    printWriter.print(parameters.toString());
+    printWriter.println(");");
+    printWriter.println();
+  }
+
+  /**
+   * The implementation of {@link #write}. Prints imports, classes, method signatures, and fields in
+   * stub file format, all with appropriate annotations.
+   *
+   * @param scene the scene to write
+   * @param filename the name of the file to write (must end in .astub)
+   * @param checker the checker, for computing preconditions
+   */
+  private static void writeImpl(ASceneWrapper scene, String filename, BaseTypeChecker checker) {
+    // Sort by package name first so that output is deterministic and default package
+    // comes first; within package sort by class name.
+    @SuppressWarnings("signature") // scene-lib bytecode lacks signature annotations
+    List<@BinaryName String> classes = new ArrayList<>(scene.getAScene().getClasses().keySet());
+    Collections.sort(
+        classes,
+        new Comparator<@BinaryName String>() {
+          @Override
+          public int compare(@BinaryName String o1, @BinaryName String o2) {
+            return ComparisonChain.start()
+                .compare(
+                    packagePart(o1),
+                    packagePart(o2),
+                    Comparator.nullsFirst(Comparator.naturalOrder()))
+                .compare(basenamePart(o1), basenamePart(o2))
+                .result();
+          }
+        });
+
+    boolean anyClassPrintable = false;
+
+    // The writer is not initialized until it is certain that at
+    // least one class can be written, to avoid empty stub files.
+    PrintWriter printWriter = null;
+
+    // For each class
+    for (String clazz : classes) {
+      if (isPrintable(clazz, scene.getAScene().getClasses().get(clazz))) {
+        if (!anyClassPrintable) {
+          try {
+            printWriter = new PrintWriter(new FileWriter(filename));
+          } catch (IOException e) {
+            throw new BugInCF("error writing file during WPI: " + filename);
+          }
+
+          // Write out all imports
+          ImportDefWriter importDefWriter;
+          try {
+            importDefWriter = new ImportDefWriter(scene, printWriter);
+          } catch (DefException e) {
+            throw new BugInCF(e);
+          }
+          importDefWriter.visit();
+          printWriter.println("import org.checkerframework.framework.qual.AnnotatedFor;");
+          printWriter.println();
+          anyClassPrintable = true;
+        }
+        printClass(clazz, scene.getAScene().getClasses().get(clazz), checker, printWriter);
+      }
+    }
+    if (printWriter != null) {
+      printWriter.flush();
+    }
+  }
+
+  /**
+   * Returns true if the class is printable in a stub file. A printable class is a class or enum
+   * (not a package or module) and is not anonymous.
+   *
+   * @param classname the class name
+   * @param aClass the representation of the class
+   * @return whether the class is printable, by the definition above
+   */
+  private static boolean isPrintable(@BinaryName String classname, AClass aClass) {
+    String basename = basenamePart(classname);
+
+    if ("package-info".equals(basename) || "module-info".equals(basename)) {
+      return false;
+    }
+
+    // Do not attempt to print stubs for anonymous inner classes, local classes, or their inner
+    // classes, because the stub parser cannot read them.
+    if (anonymousInnerClassOrLocalClassPattern.matcher(basename).find()) {
+      return false;
+    }
+
+    if (aClass.getTypeElement() == null) {
+      throw new BugInCF(
+          "Tried printing an unprintable class to a stub file during WPI: " + aClass.className);
+    }
+
+    return true;
+  }
+
+  /**
+   * Print the class body, or nothing if this is an anonymous inner class. Call {@link
+   * #isPrintable(String, AClass)} and check that it returns true before calling this method.
+   *
+   * @param classname the class name
+   * @param aClass the representation of the class
+   * @param checker the checker, for computing preconditions
+   * @param printWriter the writer on which to print
+   */
+  private static void printClass(
+      @BinaryName String classname,
+      AClass aClass,
+      BaseTypeChecker checker,
+      PrintWriter printWriter) {
+
+    String basename = basenamePart(classname);
+    String innermostClassname =
+        basename.contains("$") ? basename.substring(basename.lastIndexOf('$') + 1) : basename;
+    String pkg = packagePart(classname);
+
+    if (pkg != null) {
+      printWriter.println("package " + pkg + ";");
+    }
+
+    int curlyCount = printClassDefinitions(basename, aClass, printWriter, checker);
+
+    String indentLevel = indents(curlyCount);
+
+    List<VariableElement> enumConstants = aClass.getEnumConstants();
+    if (enumConstants != null) {
+      StringJoiner sj = new StringJoiner(", ");
+      for (VariableElement enumConstant : enumConstants) {
+        sj.add(enumConstant.getSimpleName());
+      }
+
+      printWriter.println(indentLevel + "// enum constants:");
+      printWriter.println();
+      printWriter.println(indentLevel + sj.toString() + ";");
+      printWriter.println();
+    }
+
+    printFields(aClass, printWriter, indentLevel);
+
+    if (!aClass.getMethods().isEmpty()) {
+      // print method signatures
+      printWriter.println(indentLevel + "// methods:");
+      printWriter.println();
+      for (Map.Entry<String, AMethod> methodEntry : aClass.getMethods().entrySet()) {
+        printMethodDeclaration(
+            methodEntry.getValue(),
+            innermostClassname,
+            printWriter,
+            indentLevel,
+            checker.getTypeFactory());
+      }
+    }
+    for (int i = 0; i < curlyCount; i++) {
+      printWriter.println(indents(curlyCount - i - 1) + "}");
+    }
+  }
+
+  /**
+   * Return a string containing n indents.
+   *
+   * @param n the number of indents
+   * @return a string containing that many indents
+   */
+  private static String indents(int n) {
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < n; i++) {
+      sb.append(INDENT);
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Prints the type parameters of the given class, enclosed in {@code <...>}.
+   *
+   * @param type the TypeElement representing the class whose type parameters should be printed
+   * @param printWriter where to print the type parameters
+   */
+  private static void printTypeParameters(TypeElement type, PrintWriter printWriter) {
+    List<? extends TypeParameterElement> typeParameters = type.getTypeParameters();
+    printTypeParameters(typeParameters, printWriter);
+  }
+
+  /**
+   * Prints the given type parameters.
+   *
+   * @param typeParameters the type element to print
+   * @param printWriter where to print the type parameters
+   */
+  private static void printTypeParameters(
+      List<? extends TypeParameterElement> typeParameters, PrintWriter printWriter) {
+    if (typeParameters.isEmpty()) {
+      return;
+    }
+    StringJoiner sj = new StringJoiner(", ", "<", ">");
+    for (TypeParameterElement t : typeParameters) {
+      sj.add(t.getSimpleName().toString());
+    }
+    printWriter.print(sj.toString());
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInference.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInference.java
new file mode 100644
index 0000000..30a8ae2
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInference.java
@@ -0,0 +1,229 @@
+package org.checkerframework.common.wholeprograminference;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+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.VariableElement;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.checkerframework.framework.qual.IgnoreInWholeProgramInference;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+
+/**
+ * Interface for recording facts at (pseudo-)assignments. It is used by the -Ainfer command-line
+ * argument. The -Ainfer command-line argument is used by the whole-program-inference loop, but this
+ * class does not implement that loop and its name {@code WholeProgramInference} is misleading.
+ *
+ * <p>This interface has update* methods that should be called at certain (pseudo-)assignments, and
+ * they may update the type of the LHS of the (pseudo-)assignment based on the type of the RHS. In
+ * case the element on the LHS already had an inferred type, its new type will be the LUB between
+ * the previous and new types.
+ *
+ * @checker_framework.manual #whole-program-inference Whole-program inference
+ */
+public interface WholeProgramInference {
+
+  /**
+   * Updates the parameter types of the constructor {@code constructorElt} based on the arguments in
+   * {@code objectCreationNode}.
+   *
+   * <p>For each parameter in constructorElt:
+   *
+   * <ul>
+   *   <li>If there is no stored annotated type for that parameter, then use the type of the
+   *       corresponding argument in the object creation call objectCreationNode.
+   *   <li>If there was a stored annotated type for that parameter, then its new type will be the
+   *       LUB between the previous type and the type of the corresponding argument in the object
+   *       creation call.
+   * </ul>
+   *
+   * @param objectCreationNode the Node that invokes the constructor
+   * @param constructorElt the Element of the constructor
+   * @param store the store just before the call
+   */
+  void updateFromObjectCreation(
+      ObjectCreationNode objectCreationNode,
+      ExecutableElement constructorElt,
+      CFAbstractStore<?, ?> store);
+
+  /**
+   * Updates the parameter types of the method {@code methodElt} based on the arguments in the
+   * method invocation {@code methodInvNode}.
+   *
+   * <p>For each formal parameter in methodElt (including the receiver):
+   *
+   * <ul>
+   *   <li>If there is no stored annotated type for that parameter, then use the type of the
+   *       corresponding argument in the method call methodInvNode.
+   *   <li>If there was a stored annotated type for that parameter, then its new type will be the
+   *       LUB between the previous type and the type of the corresponding argument in the method
+   *       call.
+   * </ul>
+   *
+   * @param methodInvNode the node representing a method invocation
+   * @param methodElt the element of the method being invoked
+   * @param store the store before the method call, used for inferring method preconditions
+   */
+  void updateFromMethodInvocation(
+      MethodInvocationNode methodInvNode, ExecutableElement methodElt, CFAbstractStore<?, ?> store);
+
+  /**
+   * Updates the parameter types (including the receiver) of the method {@code methodTree} based on
+   * the parameter types of the overridden method {@code overriddenMethod}.
+   *
+   * <p>For each formal parameter in methodElt:
+   *
+   * <ul>
+   *   <li>If there is no stored annotated type for that parameter, then use the type of the
+   *       corresponding parameter on the overridden method.
+   *   <li>If there is a stored annotated type for that parameter, then its new type will be the LUB
+   *       between the previous type and the type of the corresponding parameter on the overridden
+   *       method.
+   * </ul>
+   *
+   * @param methodTree the tree of the method that contains the parameter(s)
+   * @param methodElt the element of the method
+   * @param overriddenMethod the AnnotatedExecutableType of the overridden method
+   */
+  void updateFromOverride(
+      MethodTree methodTree, ExecutableElement methodElt, AnnotatedExecutableType overriddenMethod);
+
+  /**
+   * Updates the type of {@code lhs} based on an assignment of {@code rhs} to {@code lhs}.
+   *
+   * <ul>
+   *   <li>If there is no stored annotated type for lhs, then use the type of the corresponding
+   *       argument in the method call methodInvNode.
+   *   <li>If there is a stored annotated type for lhs, then its new type will be the LUB between
+   *       the previous type and the type of the corresponding argument in the method call.
+   * </ul>
+   *
+   * @param lhs the node representing the formal parameter
+   * @param rhs the node being assigned to the parameter in the method body
+   * @param paramElt the formal parameter
+   */
+  void updateFromFormalParameterAssignment(
+      LocalVariableNode lhs, Node rhs, VariableElement paramElt);
+
+  /**
+   * Updates the type of {@code field} based on an assignment of {@code rhs} to {@code field}. If
+   * the field has a declaration annotation with the {@link IgnoreInWholeProgramInference}
+   * meta-annotation, no type annotation will be inferred for that field.
+   *
+   * <p>If there is no stored entry for the field lhs, the entry will be created and its type will
+   * be the type of rhs. If there is a stored entry/type for lhs, its new type will be the LUB
+   * between the previous type and the type of rhs.
+   *
+   * @param field the field whose type will be refined. Must be either a FieldAccessNode or a
+   *     LocalVariableNode whose element kind is FIELD.
+   * @param rhs the expression being assigned to the field
+   * @param classTree the ClassTree for the enclosing class of the assignment
+   */
+  void updateFromFieldAssignment(Node field, Node rhs, ClassTree classTree);
+
+  /**
+   * Updates the type of {@code field} based on an assignment whose right-hand side has type {@code
+   * rhsATM}. See more details at {@link #updateFromFieldAssignment}.
+   *
+   * @param lhsTree the tree for the field whose type will be refined
+   * @param element the element for the field whose type will be refined
+   * @param fieldName the name of the field whose type will be refined
+   * @param rhsATM the type of the expression being assigned to the field
+   */
+  void updateFieldFromType(
+      Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM);
+
+  /**
+   * Updates the return type of the method {@code methodTree} based on {@code returnedExpression}.
+   * Also updates the return types of any methods that this method overrides that are available as
+   * source code.
+   *
+   * <p>If there is no stored annotated return type for the method methodTree, then the type of the
+   * return expression will be added to the return type of that method. If there is a stored
+   * annotated return type for the method methodTree, its new type will be the LUB between the
+   * previous type and the type of the return expression.
+   *
+   * @param retNode the node that contains the expression returned
+   * @param classSymbol the symbol of the class that contains the method
+   * @param methodTree the tree of the method whose return type may be updated
+   * @param overriddenMethods the methods that the given method return overrides, indexed by the
+   *     annotated type of the superclass in which each method is defined
+   */
+  void updateFromReturn(
+      ReturnNode retNode,
+      ClassSymbol classSymbol,
+      MethodTree methodTree,
+      Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods);
+
+  /**
+   * Updates the preconditions or postconditions of the current method, from a store.
+   *
+   * @param methodElement the method or constructor whose preconditions or postconditions to update
+   * @param preOrPost whether to update preconditions or postconditions
+   * @param store the store at the method's entry or normal exit, for reading types of expressions
+   */
+  void updateContracts(
+      Analysis.BeforeOrAfter preOrPost,
+      ExecutableElement methodElement,
+      CFAbstractStore<?, ?> store);
+
+  /**
+   * Updates a method to add a declaration annotation.
+   *
+   * @param methodElt the method to annotate
+   * @param anno the declaration annotation to add to the method
+   */
+  void addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno);
+
+  /**
+   * Writes the inferred results to a file. Ideally, it should be called at the end of the
+   * type-checking process. In practice, it is called after each class, because we don't know which
+   * class will be the last one in the type-checking process.
+   *
+   * @param format the file format in which to write the results
+   * @param checker the checker from which this method is called, for naming stub files
+   */
+  void writeResultsToFile(OutputFormat format, BaseTypeChecker checker);
+
+  /**
+   * Performs any preparation required for inference on Elements of a class. Should be called on
+   * each toplevel class declaration in a compilation unit before processing it.
+   *
+   * @param classTree the class to preprocess
+   */
+  void preprocessClassTree(ClassTree classTree);
+
+  /** The kinds of output that whole-program inference can produce. */
+  enum OutputFormat {
+    /**
+     * Output the results of whole-program inference as a stub file that can be parsed back into the
+     * Checker Framework by the Stub Parser.
+     */
+    STUB(),
+
+    /**
+     * Output the results of whole-program inference as a Java annotation index file. The Annotation
+     * File Utilities project contains code for reading and writing .jaif files.
+     */
+    JAIF(),
+
+    /**
+     * Output the results of whole-program inference as an ajava file that can be read in using the
+     * -Aajava option.
+     */
+    AJAVA(),
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java
new file mode 100644
index 0000000..80ddf30
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java
@@ -0,0 +1,660 @@
+package org.checkerframework.common.wholeprograminference;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+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.VariableElement;
+import javax.lang.model.util.ElementFilter;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.ThisReference;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.checkerframework.framework.flow.CFAbstractValue;
+import org.checkerframework.framework.qual.IgnoreInWholeProgramInference;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * This is the primary implementation of {@link
+ * org.checkerframework.common.wholeprograminference.WholeProgramInference}. It uses an instance of
+ * {@link WholeProgramInferenceStorage} to store annotations and to create output files.
+ *
+ * <p>This class does not perform inference for an element if the element has explicit annotations.
+ * That is, calling an {@code update*} method on an explicitly annotated field, method return, or
+ * method parameter has no effect.
+ *
+ * <p>In addition, whole program inference ignores inferred types in a few scenarios. When
+ * discovering a use, WPI ignores an inferred type if:
+ *
+ * <ol>
+ *   <li>The inferred type of an element that should be written into a file is a subtype of the
+ *       upper bounds of this element's written type in the source code.
+ *   <li>The annotation annotates a {@code null} literal, except when doing inference for the
+ *       NullnessChecker. (The rationale for this is that {@code null} is a frequently-used default
+ *       value, and it would be undesirable to infer the bottom type if {@code null} were the only
+ *       value passed as an argument.)
+ * </ol>
+ *
+ * When outputting a file, WPI ignores an inferred type if:
+ *
+ * <ol>
+ *   <li>The @Target annotation does not permit the annotation to be written at this location.
+ *   <li>The @RelevantJavaTypes annotation does not permit the annotation to be written at this
+ *       location.
+ *   <li>The inferred annotation has the @InvisibleQualifier meta-annotation.
+ *   <li>The inferred annotation would be the same annotation applied via defaulting &mdash; that
+ *       is, if omitting it has the same effect as writing it.
+ * </ol>
+ *
+ * @param <T> the type used by the storage to store annotations. See {@link
+ *     WholeProgramInferenceStorage}
+ */
+// TODO: We could add an option to update the type of explicitly annotated elements, but this
+// currently is not recommended since the insert-annotations-to-source tool, which adds annotations
+// from .jaif files into source code, adds annotations on top of existing annotations. See
+// https://github.com/typetools/annotation-tools/issues/105 .
+// TODO: Ensure that annotations are inserted deterministically into files. This is important for
+// debugging and comparison; otherwise running the whole-program inference on the same set of files
+// can yield different results (order of annotations).
+public class WholeProgramInferenceImplementation<T> implements WholeProgramInference {
+
+  /** The type factory associated with this. */
+  protected final AnnotatedTypeFactory atypeFactory;
+
+  /** The storage for the inferred annotations. */
+  private WholeProgramInferenceStorage<T> storage;
+
+  /** Whether to ignore assignments where the rhs is null. */
+  private final boolean ignoreNullAssignments;
+
+  /**
+   * Constructs a new {@code WholeProgramInferenceImplementation} that has not yet inferred any
+   * annotations.
+   *
+   * @param atypeFactory the associated type factory
+   * @param storage the storage used for inferred annotations and for writing output files
+   */
+  public WholeProgramInferenceImplementation(
+      AnnotatedTypeFactory atypeFactory, WholeProgramInferenceStorage<T> storage) {
+    this.atypeFactory = atypeFactory;
+    this.storage = storage;
+    boolean isNullness =
+        atypeFactory.getClass().getSimpleName().equals("NullnessAnnotatedTypeFactory");
+    this.ignoreNullAssignments = !isNullness;
+  }
+
+  /**
+   * Returns the storage for inferred annotations.
+   *
+   * @return the storage for the inferred annotations
+   */
+  public WholeProgramInferenceStorage<T> getStorage() {
+    return storage;
+  }
+
+  @Override
+  public void updateFromObjectCreation(
+      ObjectCreationNode objectCreationNode,
+      ExecutableElement constructorElt,
+      CFAbstractStore<?, ?> store) {
+    // Don't infer types for code that isn't presented as source.
+    if (!ElementUtils.isElementFromSourceCode(constructorElt)) {
+      return;
+    }
+
+    List<Node> arguments = objectCreationNode.getArguments();
+    updateInferredExecutableParameterTypes(constructorElt, arguments);
+    updateContracts(Analysis.BeforeOrAfter.BEFORE, constructorElt, store);
+  }
+
+  @Override
+  public void updateFromMethodInvocation(
+      MethodInvocationNode methodInvNode,
+      ExecutableElement methodElt,
+      CFAbstractStore<?, ?> store) {
+    // Don't infer types for code that isn't presented as source.
+    if (!ElementUtils.isElementFromSourceCode(methodElt)) {
+      return;
+    }
+
+    if (!storage.hasStorageLocationForMethod(methodElt)) {
+      return;
+    }
+
+    // Don't infer formal parameter types from recursive calls.
+    //
+    // When performing WPI on a library, if there are no external calls (only recursive calls), then
+    // each iteration of WPI would make the formal parameter types more restrictive, leading to an
+    // infinite (or very long) loop.
+    //
+    // Consider
+    //   void myMethod(int x) { ... myMethod(x-1) ... }`
+    // On one iteration, if x has type IntRange(to=100), the recursive call's argument has type
+    // IntRange(to=99).  If that is the only call to `MyMethod`, then the formal parameter type
+    // would be updated.  On the next iteration it would be refined again to @IntRange(to=98), and
+    // so forth.  A recursive call should never restrict a formal parameter type.
+    if (isRecursiveCall(methodInvNode)) {
+      return;
+    }
+
+    List<Node> arguments = methodInvNode.getArguments();
+    updateInferredExecutableParameterTypes(methodElt, arguments);
+    updateContracts(Analysis.BeforeOrAfter.BEFORE, methodElt, store);
+  }
+
+  /**
+   * Returns true if the given call is a recursive call.
+   *
+   * @param methodInvNode a method invocation
+   * @return true if the given call is a recursive call
+   */
+  private boolean isRecursiveCall(MethodInvocationNode methodInvNode) {
+    MethodTree enclosingMethod = TreePathUtil.enclosingMethod(methodInvNode.getTreePath());
+    if (enclosingMethod == null) {
+      return false;
+    }
+    ExecutableElement methodInvocEle = TreeUtils.elementFromUse(methodInvNode.getTree());
+    ExecutableElement methodDeclEle = TreeUtils.elementFromDeclaration(enclosingMethod);
+    return methodDeclEle.equals(methodInvocEle);
+  }
+
+  /**
+   * Updates inferred parameter types based on a call to a method or constructor.
+   *
+   * @param methodElt the element of the method or constructor being invoked
+   * @param arguments the arguments of the invocation
+   */
+  private void updateInferredExecutableParameterTypes(
+      ExecutableElement methodElt, List<Node> arguments) {
+
+    String file = storage.getFileForElement(methodElt);
+
+    for (int i = 0; i < arguments.size(); i++) {
+      Node arg = arguments.get(i);
+      Tree argTree = arg.getTree();
+
+      VariableElement ve = methodElt.getParameters().get(i);
+      AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(ve);
+      AnnotatedTypeMirror argATM = atypeFactory.getAnnotatedType(argTree);
+      atypeFactory.wpiAdjustForUpdateNonField(argATM);
+      T paramAnnotations =
+          storage.getParameterAnnotations(methodElt, i, paramATM, ve, atypeFactory);
+      updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file);
+    }
+  }
+
+  @Override
+  public void updateContracts(
+      Analysis.BeforeOrAfter preOrPost, ExecutableElement methodElt, CFAbstractStore<?, ?> store) {
+    // Don't infer types for code that isn't presented as source.
+    if (!ElementUtils.isElementFromSourceCode(methodElt)) {
+      return;
+    }
+
+    if (store == null) {
+      throw new BugInCF(
+          "updateContracts(%s, %s, null) for %s",
+          preOrPost, methodElt, atypeFactory.getClass().getSimpleName());
+    }
+
+    if (!storage.hasStorageLocationForMethod(methodElt)) {
+      return;
+    }
+
+    // TODO: Probably move some part of this into the AnnotatedTypeFactory.
+
+    // This code only handles fields of "this", for now.  In the future, extend it to other
+    // expressions.
+    TypeElement containingClass = (TypeElement) methodElt.getEnclosingElement();
+    ThisReference thisReference = new ThisReference(containingClass.asType());
+    ClassName classNameReceiver = new ClassName(containingClass.asType());
+    for (VariableElement fieldElement :
+        ElementFilter.fieldsIn(containingClass.getEnclosedElements())) {
+      FieldAccess fa =
+          new FieldAccess(
+              (ElementUtils.isStatic(fieldElement) ? classNameReceiver : thisReference),
+              fieldElement.asType(),
+              fieldElement);
+      CFAbstractValue<?> v = store.getFieldValue(fa);
+      AnnotatedTypeMirror fieldDeclType = atypeFactory.getAnnotatedType(fieldElement);
+      AnnotatedTypeMirror inferredType;
+      if (v != null) {
+        // This field is in the store.
+        inferredType = convertCFAbstractValueToAnnotatedTypeMirror(v, fieldDeclType);
+        atypeFactory.wpiAdjustForUpdateNonField(inferredType);
+      } else {
+        // This field is not in the store. Add its declared type.
+        inferredType = atypeFactory.getAnnotatedType(fieldElement);
+      }
+      T preOrPostConditionAnnos =
+          storage.getPreOrPostconditionsForField(preOrPost, methodElt, fieldElement, atypeFactory);
+      String file = storage.getFileForElement(methodElt);
+      updateAnnotationSet(
+          preOrPostConditionAnnos, TypeUseLocation.FIELD, inferredType, fieldDeclType, file, false);
+    }
+  }
+
+  /**
+   * Converts a CFAbstractValue to an AnnotatedTypeMirror.
+   *
+   * @param v a value to convert to an AnnotatedTypeMirror
+   * @param fieldType an {@code AnnotatedTypeMirror} with the same underlying type as {@code v} that
+   *     is copied, then the copy is updated to use {@code v}'s annotations
+   * @return a copy of {@code fieldType} with {@code v}'s annotations
+   */
+  private AnnotatedTypeMirror convertCFAbstractValueToAnnotatedTypeMirror(
+      CFAbstractValue<?> v, AnnotatedTypeMirror fieldType) {
+    AnnotatedTypeMirror result = fieldType.deepCopy();
+    result.replaceAnnotations(v.getAnnotations());
+    return result;
+  }
+
+  @Override
+  public void updateFromOverride(
+      MethodTree methodTree,
+      ExecutableElement methodElt,
+      AnnotatedExecutableType overriddenMethod) {
+    // Don't infer types for code that isn't presented as source.
+    if (!ElementUtils.isElementFromSourceCode(methodElt)) {
+      return;
+    }
+
+    String file = storage.getFileForElement(methodElt);
+
+    for (int i = 0; i < overriddenMethod.getParameterTypes().size(); i++) {
+      VariableElement ve = methodElt.getParameters().get(i);
+      AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(ve);
+      AnnotatedTypeMirror argATM = overriddenMethod.getParameterTypes().get(i);
+      atypeFactory.wpiAdjustForUpdateNonField(argATM);
+      T paramAnnotations =
+          storage.getParameterAnnotations(methodElt, i, paramATM, ve, atypeFactory);
+      updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file);
+    }
+
+    AnnotatedDeclaredType argADT = overriddenMethod.getReceiverType();
+    if (argADT != null) {
+      AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(methodTree).getReceiverType();
+      if (paramATM != null) {
+        T receiver = storage.getReceiverAnnotations(methodElt, paramATM, atypeFactory);
+        updateAnnotationSet(receiver, TypeUseLocation.RECEIVER, argADT, paramATM, file);
+      }
+    }
+  }
+
+  @Override
+  public void updateFromFormalParameterAssignment(
+      LocalVariableNode lhs, Node rhs, VariableElement paramElt) {
+    // Don't infer types for code that isn't presented as source.
+    if (!ElementUtils.isElementFromSourceCode(lhs.getElement())) {
+      return;
+    }
+
+    Tree rhsTree = rhs.getTree();
+    if (rhsTree == null) {
+      // TODO: Handle variable-length list as parameter.
+      // An ArrayCreationNode with a null tree is created when the
+      // parameter is a variable-length list. We are ignoring it for now.
+      // See Issue 682: https://github.com/typetools/checker-framework/issues/682
+      return;
+    }
+
+    ExecutableElement methodElt = (ExecutableElement) paramElt.getEnclosingElement();
+
+    AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(paramElt);
+    AnnotatedTypeMirror argATM = atypeFactory.getAnnotatedType(rhsTree);
+    atypeFactory.wpiAdjustForUpdateNonField(argATM);
+    int i = methodElt.getParameters().indexOf(paramElt);
+    assert i != -1;
+    T paramAnnotations =
+        storage.getParameterAnnotations(methodElt, i, paramATM, paramElt, atypeFactory);
+    String file = storage.getFileForElement(methodElt);
+    updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file);
+  }
+
+  @Override
+  public void updateFromFieldAssignment(Node lhs, Node rhs, ClassTree classTree) {
+
+    Element element;
+    String fieldName;
+    if (lhs instanceof FieldAccessNode) {
+      element = ((FieldAccessNode) lhs).getElement();
+      fieldName = ((FieldAccessNode) lhs).getFieldName();
+    } else if (lhs instanceof LocalVariableNode) {
+      element = ((LocalVariableNode) lhs).getElement();
+      fieldName = ((LocalVariableNode) lhs).getName();
+    } else {
+      throw new BugInCF(
+          "updateFromFieldAssignment received an unexpected node type: " + lhs.getClass());
+    }
+
+    // TODO: For a primitive such as long, this is yielding just @GuardedBy rather than
+    // @GuardedBy({}).
+    AnnotatedTypeMirror rhsATM = atypeFactory.getAnnotatedType(rhs.getTree());
+    atypeFactory.wpiAdjustForUpdateField(lhs.getTree(), element, fieldName, rhsATM);
+
+    updateFieldFromType(lhs.getTree(), element, fieldName, rhsATM);
+  }
+
+  @Override
+  public void updateFieldFromType(
+      Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) {
+
+    if (ignoreFieldInWPI(element, fieldName)) {
+      return;
+    }
+
+    // Don't infer types for code that isn't presented as source.
+    if (!ElementUtils.isElementFromSourceCode(element)) {
+      return;
+    }
+
+    String file = storage.getFileForElement(element);
+
+    AnnotatedTypeMirror lhsATM = atypeFactory.getAnnotatedType(lhsTree);
+    T fieldAnnotations = storage.getFieldAnnotations(element, fieldName, lhsATM, atypeFactory);
+
+    updateAnnotationSet(fieldAnnotations, TypeUseLocation.FIELD, rhsATM, lhsATM, file);
+  }
+
+  /**
+   * Returns true if an assignment to the given field should be ignored by WPI.
+   *
+   * @param element the field's element
+   * @param fieldName the field's name
+   * @return true if an assignment to the given field should be ignored by WPI
+   */
+  protected boolean ignoreFieldInWPI(Element element, String fieldName) {
+    // Do not attempt to infer types for fields that do not have valid names. For example,
+    // compiler-generated temporary variables will have invalid names. Recording facts about fields
+    // with invalid names causes jaif-based WPI to crash when reading the .jaif file, and stub-based
+    // WPI to generate unparsable stub files.  See
+    // https://github.com/typetools/checker-framework/issues/3442
+    if (!SourceVersion.isIdentifier(fieldName)) {
+      return true;
+    }
+
+    // Don't infer types if the inferred field has a declaration annotation with the
+    // @IgnoreInWholeProgramInference meta-annotation.
+    if (atypeFactory.getDeclAnnotation(element, IgnoreInWholeProgramInference.class) != null
+        || atypeFactory
+                .getDeclAnnotationWithMetaAnnotation(element, IgnoreInWholeProgramInference.class)
+                .size()
+            > 0) {
+      return true;
+    }
+
+    // Don't infer types for code that isn't presented as source.
+    if (!ElementUtils.isElementFromSourceCode(element)) {
+      return true;
+    }
+
+    return false;
+  }
+
+  @Override
+  public void updateFromReturn(
+      ReturnNode retNode,
+      ClassSymbol classSymbol,
+      MethodTree methodDeclTree,
+      Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods) {
+    // Don't infer types for code that isn't presented as source.
+    if (methodDeclTree == null
+        || !ElementUtils.isElementFromSourceCode(
+            TreeUtils.elementFromDeclaration(methodDeclTree))) {
+      return;
+    }
+
+    // Whole-program inference ignores some locations.  See Issue 682:
+    // https://github.com/typetools/checker-framework/issues/682
+    if (classSymbol == null) { // TODO: Handle anonymous classes.
+      return;
+    }
+
+    ExecutableElement methodElt = TreeUtils.elementFromDeclaration(methodDeclTree);
+    String file = storage.getFileForElement(methodElt);
+
+    AnnotatedTypeMirror lhsATM = atypeFactory.getAnnotatedType(methodDeclTree).getReturnType();
+    // Type of the expression returned
+    AnnotatedTypeMirror rhsATM = atypeFactory.getAnnotatedType(retNode.getTree().getExpression());
+    atypeFactory.wpiAdjustForUpdateNonField(rhsATM);
+    DependentTypesHelper dependentTypesHelper =
+        ((GenericAnnotatedTypeFactory) atypeFactory).getDependentTypesHelper();
+    dependentTypesHelper.delocalize(rhsATM, methodDeclTree);
+    T returnTypeAnnos = storage.getReturnAnnotations(methodElt, lhsATM, atypeFactory);
+    updateAnnotationSet(returnTypeAnnos, TypeUseLocation.RETURN, rhsATM, lhsATM, file);
+
+    // Now, update return types of overridden methods based on the implementation we just saw.
+    // This inference is similar to the inference procedure for method parameters: both are
+    // updated based only on the implementations (in this case) or call-sites (for method
+    // parameters) that are available to WPI.
+    //
+    // An alternative implementation would be to:
+    //  * update only the method (not overridden methods)
+    //  * when finished, propagate the final result to overridden methods
+    //
+    for (Map.Entry<AnnotatedDeclaredType, ExecutableElement> pair : overriddenMethods.entrySet()) {
+
+      AnnotatedDeclaredType superclassDecl = pair.getKey();
+      ExecutableElement overriddenMethodElement = pair.getValue();
+
+      // Don't infer types for code that isn't presented as source.
+      if (!ElementUtils.isElementFromSourceCode(overriddenMethodElement)) {
+        continue;
+      }
+
+      AnnotatedExecutableType overriddenMethod =
+          AnnotatedTypes.asMemberOf(
+              atypeFactory.getProcessingEnv().getTypeUtils(),
+              atypeFactory,
+              superclassDecl,
+              overriddenMethodElement);
+
+      String superClassFile = storage.getFileForElement(overriddenMethodElement);
+      AnnotatedTypeMirror overriddenMethodReturnType = overriddenMethod.getReturnType();
+      T storedOverriddenMethodReturnTypeAnnotations =
+          storage.getReturnAnnotations(
+              overriddenMethodElement, overriddenMethodReturnType, atypeFactory);
+
+      updateAnnotationSet(
+          storedOverriddenMethodReturnTypeAnnotations,
+          TypeUseLocation.RETURN,
+          rhsATM,
+          overriddenMethodReturnType,
+          superClassFile);
+    }
+  }
+
+  @Override
+  public void addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno) {
+
+    // Do not infer types for library code, only for type-checked source code.
+    if (!ElementUtils.isElementFromSourceCode(methodElt)) {
+      return;
+    }
+
+    String file = storage.getFileForElement(methodElt);
+    boolean isNewAnnotation = storage.addMethodDeclarationAnnotation(methodElt, anno);
+    if (isNewAnnotation) {
+      storage.setFileModified(file);
+    }
+  }
+
+  /**
+   * Updates the set of annotations in a location in a program.
+   *
+   * <ul>
+   *   <li>If there was no previous annotation for that location, then the updated set will be the
+   *       annotations in rhsATM.
+   *   <li>If there was a previous annotation, the updated set will be the LUB between the previous
+   *       annotation and rhsATM.
+   * </ul>
+   *
+   * <p>Subclasses can customize this behavior.
+   *
+   * @param annotationsToUpdate the type whose annotations are modified by this method
+   * @param defLoc the location where the annotation will be added
+   * @param rhsATM the RHS of the annotated type on the source code
+   * @param lhsATM the LHS of the annotated type on the source code
+   * @param file the annotation file containing the executable; used for marking the scene as
+   *     modified (needing to be written to disk)
+   */
+  protected void updateAnnotationSet(
+      T annotationsToUpdate,
+      TypeUseLocation defLoc,
+      AnnotatedTypeMirror rhsATM,
+      AnnotatedTypeMirror lhsATM,
+      String file) {
+    updateAnnotationSet(annotationsToUpdate, defLoc, rhsATM, lhsATM, file, true);
+  }
+
+  /**
+   * Updates the set of annotations in a location in a program.
+   *
+   * <ul>
+   *   <li>If there was no previous annotation for that location, then the updated set will be the
+   *       annotations in rhsATM.
+   *   <li>If there was a previous annotation, the updated set will be the LUB between the previous
+   *       annotation and rhsATM.
+   * </ul>
+   *
+   * <p>Subclasses can customize this behavior.
+   *
+   * @param annotationsToUpdate the type whose annotations are modified by this method
+   * @param defLoc the location where the annotation will be added
+   * @param rhsATM the RHS of the annotated type on the source code
+   * @param lhsATM the LHS of the annotated type on the source code
+   * @param file annotation file containing the executable; used for marking the scene as modified
+   *     (needing to be written to disk)
+   * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the
+   *     source code
+   */
+  protected void updateAnnotationSet(
+      T annotationsToUpdate,
+      TypeUseLocation defLoc,
+      AnnotatedTypeMirror rhsATM,
+      AnnotatedTypeMirror lhsATM,
+      String file,
+      boolean ignoreIfAnnotated) {
+    if (rhsATM instanceof AnnotatedNullType && ignoreNullAssignments) {
+      return;
+    }
+    AnnotatedTypeMirror atmFromStorage =
+        storage.atmFromStorageLocation(rhsATM.getUnderlyingType(), annotationsToUpdate);
+    updateAtmWithLub(rhsATM, atmFromStorage);
+    if (lhsATM instanceof AnnotatedTypeVariable) {
+      Set<AnnotationMirror> upperAnnos =
+          ((AnnotatedTypeVariable) lhsATM).getUpperBound().getEffectiveAnnotations();
+      // If the inferred type is a subtype of the upper bounds of the
+      // current type in the source code, do nothing.
+      if (upperAnnos.size() == rhsATM.getAnnotations().size()
+          && atypeFactory.getQualifierHierarchy().isSubtype(rhsATM.getAnnotations(), upperAnnos)) {
+        return;
+      }
+    }
+    storage.updateStorageLocationFromAtm(
+        rhsATM, lhsATM, annotationsToUpdate, defLoc, ignoreIfAnnotated);
+    storage.setFileModified(file);
+  }
+
+  /**
+   * Updates sourceCodeATM to contain the LUB between sourceCodeATM and ajavaATM, ignoring missing
+   * AnnotationMirrors from ajavaATM -- it considers the LUB between an AnnotationMirror am and a
+   * missing AnnotationMirror to be am. The results are stored in sourceCodeATM.
+   *
+   * @param sourceCodeATM the annotated type on the source code
+   * @param ajavaATM the annotated type on the ajava file
+   */
+  private void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror ajavaATM) {
+
+    switch (sourceCodeATM.getKind()) {
+      case TYPEVAR:
+        updateAtmWithLub(
+            ((AnnotatedTypeVariable) sourceCodeATM).getLowerBound(),
+            ((AnnotatedTypeVariable) ajavaATM).getLowerBound());
+        updateAtmWithLub(
+            ((AnnotatedTypeVariable) sourceCodeATM).getUpperBound(),
+            ((AnnotatedTypeVariable) ajavaATM).getUpperBound());
+        break;
+      case WILDCARD:
+        break;
+        // throw new BugInCF("This can't happen");
+        // TODO: This comment is wrong: the wildcard case does get entered.
+        // Because inferring type arguments is not supported, wildcards won't be encountered.
+        // updateATMWithLUB(
+        //         atf,
+        //         ((AnnotatedWildcardType) sourceCodeATM).getExtendsBound(),
+        //         ((AnnotatedWildcardType) ajavaATM).getExtendsBound());
+        // updateATMWithLUB(
+        //         atf,
+        //         ((AnnotatedWildcardType) sourceCodeATM).getSuperBound(),
+        //         ((AnnotatedWildcardType) ajavaATM).getSuperBound());
+        // break;
+      case ARRAY:
+        updateAtmWithLub(
+            ((AnnotatedArrayType) sourceCodeATM).getComponentType(),
+            ((AnnotatedArrayType) ajavaATM).getComponentType());
+        break;
+        // case DECLARED:
+        // Inferring annotations on type arguments is not supported, so no need to recur on generic
+        // types. If this was ever implemented, this method would need a VisitHistory object to
+        // prevent infinite recursion on types such as T extends List<T>.
+      default:
+        // ATM only has primary annotations
+        break;
+    }
+
+    // LUB primary annotations
+    Set<AnnotationMirror> annosToReplace = new HashSet<>(sourceCodeATM.getAnnotations().size());
+    for (AnnotationMirror amSource : sourceCodeATM.getAnnotations()) {
+      AnnotationMirror amAjava = ajavaATM.getAnnotationInHierarchy(amSource);
+      // amAjava only contains annotations from the ajava file, so it might be missing
+      // an annotation in the hierarchy.
+      if (amAjava != null) {
+        amSource = atypeFactory.getQualifierHierarchy().leastUpperBound(amSource, amAjava);
+      }
+      annosToReplace.add(amSource);
+    }
+    sourceCodeATM.replaceAnnotations(annosToReplace);
+  }
+
+  @Override
+  public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checker) {
+    storage.writeResultsToFile(outputFormat, checker);
+  }
+
+  @Override
+  public void preprocessClassTree(ClassTree classTree) {
+    storage.preprocessClassTree(classTree);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java
new file mode 100644
index 0000000..18e6754
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java
@@ -0,0 +1,1084 @@
+package org.checkerframework.common.wholeprograminference;
+
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.NodeList;
+import com.github.javaparser.ast.body.CallableDeclaration;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.ConstructorDeclaration;
+import com.github.javaparser.ast.body.EnumDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.ReceiverParameter;
+import com.github.javaparser.ast.body.TypeDeclaration;
+import com.github.javaparser.ast.body.VariableDeclarator;
+import com.github.javaparser.ast.expr.ObjectCreationExpr;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import com.github.javaparser.ast.type.Type;
+import com.github.javaparser.ast.type.TypeParameter;
+import com.github.javaparser.ast.visitor.CloneVisitor;
+import com.github.javaparser.printer.DefaultPrettyPrinter;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+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.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.wholeprograminference.WholeProgramInference.OutputFormat;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.framework.ajava.AnnotationMirrorToAnnotationExprConversion;
+import org.checkerframework.framework.ajava.AnnotationTransferVisitor;
+import org.checkerframework.framework.ajava.DefaultJointVisitor;
+import org.checkerframework.framework.ajava.JointJavacJavaParserVisitor;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.util.JavaParserUtil;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import scenelib.annotations.util.JVMNames;
+
+/**
+ * This is an implementation of {@link WholeProgramInferenceStorage} that stores annotations
+ * directly with the JavaParser node corresponding to the annotation's location. It outputs ajava
+ * files.
+ */
+public class WholeProgramInferenceJavaParserStorage
+    implements WholeProgramInferenceStorage<AnnotatedTypeMirror> {
+
+  /**
+   * Directory where .ajava files will be written to and read from. This directory is relative to
+   * where the javac command is executed.
+   */
+  public static final String AJAVA_FILES_PATH =
+      "build" + File.separator + "whole-program-inference" + File.separator;
+
+  /** The type factory associated with this. */
+  protected final AnnotatedTypeFactory atypeFactory;
+
+  /**
+   * Maps from binary class name to the wrapper containing the class. Contains all classes in Java
+   * source files containing an Element for which an annotation has been inferred.
+   */
+  private Map<@BinaryName String, ClassOrInterfaceAnnos> classToAnnos = new HashMap<>();
+
+  /**
+   * Files containing classes for which an annotation has been inferred since the last time files
+   * were written to disk.
+   */
+  private Set<String> modifiedFiles = new HashSet<>();
+
+  /** Mapping from source file to the wrapper for the compilation unit parsed from that file. */
+  private Map<String, CompilationUnitAnnos> sourceToAnnos = new HashMap<>();
+
+  /**
+   * Constructs a new {@code WholeProgramInferenceJavaParser} that has not yet inferred any
+   * annotations.
+   *
+   * @param atypeFactory the associated type factory
+   */
+  public WholeProgramInferenceJavaParserStorage(AnnotatedTypeFactory atypeFactory) {
+    this.atypeFactory = atypeFactory;
+  }
+
+  @Override
+  public String getFileForElement(Element elt) {
+    return addClassesForElement(elt);
+  }
+
+  @Override
+  public void setFileModified(String path) {
+    modifiedFiles.add(path);
+  }
+
+  ///
+  /// Reading stored annotations
+  ///
+
+  @Override
+  public boolean hasStorageLocationForMethod(ExecutableElement methodElt) {
+    return getMethodAnnos(methodElt) != null;
+  }
+
+  /**
+   * Get the annotations for a method or constructor.
+   *
+   * @param methodElt the method or constructor
+   * @return the annotations for a method or constructor
+   */
+  private CallableDeclarationAnnos getMethodAnnos(ExecutableElement methodElt) {
+    String className = ElementUtils.getEnclosingClassName(methodElt);
+    // Read in classes for the element.
+    getFileForElement(methodElt);
+    ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className);
+    CallableDeclarationAnnos methodAnnos =
+        classAnnos.callableDeclarations.get(JVMNames.getJVMMethodSignature(methodElt));
+    return methodAnnos;
+  }
+
+  @Override
+  public AnnotatedTypeMirror getParameterAnnotations(
+      ExecutableElement methodElt,
+      int i,
+      AnnotatedTypeMirror paramATM,
+      VariableElement ve,
+      AnnotatedTypeFactory atypeFactory) {
+    CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt);
+    return methodAnnos.getParameterTypeInitialized(paramATM, i, atypeFactory);
+  }
+
+  @Override
+  public AnnotatedTypeMirror getReceiverAnnotations(
+      ExecutableElement methodElt,
+      AnnotatedTypeMirror paramATM,
+      AnnotatedTypeFactory atypeFactory) {
+    CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt);
+    return methodAnnos.getReceiverType(paramATM, atypeFactory);
+  }
+
+  @Override
+  public AnnotatedTypeMirror getReturnAnnotations(
+      ExecutableElement methodElt, AnnotatedTypeMirror atm, AnnotatedTypeFactory atypeFactory) {
+    CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt);
+    return methodAnnos.getReturnType(atm, atypeFactory);
+  }
+
+  @Override
+  public AnnotatedTypeMirror getFieldAnnotations(
+      Element element,
+      String fieldName,
+      AnnotatedTypeMirror lhsATM,
+      AnnotatedTypeFactory atypeFactory) {
+    ClassSymbol enclosingClass = ((VarSymbol) element).enclClass();
+    // Read in classes for the element.
+    getFileForElement(element);
+    @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094
+    @BinaryName String className = enclosingClass.flatname.toString();
+    ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className);
+    return classAnnos.fields.get(fieldName).getType(lhsATM, atypeFactory);
+  }
+
+  @Override
+  public AnnotatedTypeMirror getPreOrPostconditionsForField(
+      Analysis.BeforeOrAfter preOrPost,
+      ExecutableElement methodElement,
+      VariableElement fieldElement,
+      AnnotatedTypeFactory atypeFactory) {
+    switch (preOrPost) {
+      case BEFORE:
+        return getPreconditionsForField(methodElement, fieldElement, atypeFactory);
+      case AFTER:
+        return getPostconditionsForField(methodElement, fieldElement, atypeFactory);
+      default:
+        throw new BugInCF("Unexpected " + preOrPost);
+    }
+  }
+
+  /**
+   * Returns the precondition annotations for a field.
+   *
+   * @param methodElement the method
+   * @param fieldElement the field
+   * @param atypeFactory the type factory
+   * @return the precondition annotations for a field
+   */
+  private AnnotatedTypeMirror getPreconditionsForField(
+      ExecutableElement methodElement,
+      VariableElement fieldElement,
+      AnnotatedTypeFactory atypeFactory) {
+    CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElement);
+    return methodAnnos.getPreconditionsForField(fieldElement, atypeFactory);
+  }
+
+  /**
+   * Returns the postcondition annotations for a field.
+   *
+   * @param methodElement the method
+   * @param fieldElement the field
+   * @param atypeFactory the type factory
+   * @return the postcondition annotations for a field
+   */
+  private AnnotatedTypeMirror getPostconditionsForField(
+      ExecutableElement methodElement,
+      VariableElement fieldElement,
+      AnnotatedTypeFactory atypeFactory) {
+    CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElement);
+    return methodAnnos.getPostconditionsForField(fieldElement, atypeFactory);
+  }
+
+  @Override
+  public boolean addMethodDeclarationAnnotation(
+      ExecutableElement methodElt, AnnotationMirror anno) {
+
+    CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt);
+    boolean isNewAnnotation = methodAnnos.addDeclarationAnnotation(anno);
+    if (isNewAnnotation) {
+      modifiedFiles.add(getFileForElement(methodElt));
+    }
+    return isNewAnnotation;
+  }
+
+  @Override
+  public AnnotatedTypeMirror atmFromStorageLocation(
+      TypeMirror typeMirror, AnnotatedTypeMirror storageLocation) {
+    return storageLocation;
+  }
+
+  @Override
+  public void updateStorageLocationFromAtm(
+      AnnotatedTypeMirror newATM,
+      AnnotatedTypeMirror curATM,
+      AnnotatedTypeMirror typeToUpdate,
+      TypeUseLocation defLoc,
+      boolean ignoreIfAnnotated) {
+    // Clears only the annotations that are supported by atypeFactory.
+    // The others stay intact.
+    Set<AnnotationMirror> annosToRemove = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror anno : typeToUpdate.getAnnotations()) {
+      if (atypeFactory.isSupportedQualifier(anno)) {
+        annosToRemove.add(anno);
+      }
+    }
+
+    // This method may be called consecutive times to modify the same AnnotatedTypeMirror.
+    // Each time it is called, the AnnotatedTypeMirror has a better type
+    // estimate for the modified AnnotatedTypeMirror. Therefore, it is not a problem to remove
+    // all annotations before inserting the new annotations.
+    typeToUpdate.removeAnnotations(annosToRemove);
+
+    // Only update the AnnotatedTypeMirror if there are no explicit annotations
+    if (curATM.getExplicitAnnotations().isEmpty() || !ignoreIfAnnotated) {
+      for (AnnotationMirror am : newATM.getAnnotations()) {
+        typeToUpdate.addAnnotation(am);
+      }
+    } else if (curATM.getKind() == TypeKind.TYPEVAR) {
+      // getExplicitAnnotations will be non-empty for type vars whose bounds are explicitly
+      // annotated.  So instead, only insert the annotation if there is not primary annotation
+      // of the same hierarchy.
+      for (AnnotationMirror am : newATM.getAnnotations()) {
+        if (curATM.getAnnotationInHierarchy(am) != null) {
+          // Don't insert if the type is already has a primary annotation
+          // in the same hierarchy.
+          break;
+        }
+
+        typeToUpdate.addAnnotation(am);
+      }
+    }
+
+    if (newATM.getKind() == TypeKind.ARRAY) {
+      AnnotatedArrayType newAAT = (AnnotatedArrayType) newATM;
+      AnnotatedArrayType oldAAT = (AnnotatedArrayType) curATM;
+      AnnotatedArrayType aatToUpdate = (AnnotatedArrayType) typeToUpdate;
+      updateStorageLocationFromAtm(
+          newAAT.getComponentType(),
+          oldAAT.getComponentType(),
+          aatToUpdate.getComponentType(),
+          defLoc,
+          ignoreIfAnnotated);
+    }
+  }
+
+  ///
+  /// Reading in files
+  ///
+
+  @Override
+  public void preprocessClassTree(ClassTree classTree) {
+    addClassTree(classTree);
+  }
+
+  /**
+   * Reads in the source file containing {@code tree} and creates wrappers around all classes in the
+   * file. Stores the wrapper for the compilation unit in {@link #sourceToAnnos} and stores the
+   * wrappers of all classes in the file in {@link #classToAnnos}.
+   *
+   * @param tree tree for class to add
+   */
+  private void addClassTree(ClassTree tree) {
+    TypeElement element = TreeUtils.elementFromDeclaration(tree);
+    String className = ElementUtils.getBinaryName(element);
+    if (classToAnnos.containsKey(className)) {
+      return;
+    }
+
+    TypeElement toplevelClass = ElementUtils.toplevelEnclosingTypeElement(element);
+    String path = ElementUtils.getSourceFilePath(toplevelClass);
+    addSourceFile(path);
+    CompilationUnitAnnos sourceAnnos = sourceToAnnos.get(path);
+    TypeDeclaration<?> javaParserNode =
+        sourceAnnos.getClassOrInterfaceDeclarationByName(toplevelClass.getSimpleName().toString());
+    ClassTree toplevelClassTree = atypeFactory.getTreeUtils().getTree(toplevelClass);
+    createWrappersForClass(toplevelClassTree, javaParserNode, sourceAnnos);
+  }
+
+  /**
+   * Reads in the file at {@code path} and creates a wrapper around its compilation unit. Stores the
+   * wrapper in {@link #sourceToAnnos}, but doesn't create wrappers around any classes in the file.
+   *
+   * @param path path to source file to read
+   */
+  private void addSourceFile(String path) {
+    if (sourceToAnnos.containsKey(path)) {
+      return;
+    }
+
+    CompilationUnit root;
+    try {
+      root = JavaParserUtil.parseCompilationUnit(new File(path));
+    } catch (FileNotFoundException e) {
+      throw new BugInCF("Failed to read Java file " + path, e);
+    }
+    JavaParserUtil.concatenateAddedStringLiterals(root);
+    CompilationUnitAnnos sourceAnnos = new CompilationUnitAnnos(root);
+    sourceToAnnos.put(path, sourceAnnos);
+  }
+
+  /**
+   * The first two arugments are a javac tree and a JavaParser node representing the same class.
+   * This method creates wrappers around all the classes, fields, and methods in that class, and
+   * stores those wrappers in {@code sourceAnnos}.
+   *
+   * @param javacClass javac tree for class
+   * @param javaParserClass JavaParser node corresponding to the same class as {@code javacClass}
+   * @param sourceAnnos compilation unit wrapper to add new wrappers to
+   */
+  private void createWrappersForClass(
+      ClassTree javacClass, TypeDeclaration<?> javaParserClass, CompilationUnitAnnos sourceAnnos) {
+    JointJavacJavaParserVisitor visitor =
+        new DefaultJointVisitor() {
+          @Override
+          public void processClass(
+              ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) {
+            addClass(javacTree);
+          }
+
+          @Override
+          public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) {
+            addClass(javacTree);
+          }
+
+          @Override
+          public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) {
+            if (javacTree.getClassBody() != null) {
+              addClass(javacTree.getClassBody());
+            }
+          }
+
+          /**
+           * Creates a wrapper around the class for {@code tree} and stores it in {@code
+           * sourceAnnos}.
+           *
+           * @param tree tree to add
+           */
+          private void addClass(ClassTree tree) {
+            TypeElement classElt = TreeUtils.elementFromDeclaration(tree);
+            String className = ElementUtils.getBinaryName(classElt);
+            ClassOrInterfaceAnnos typeWrapper = new ClassOrInterfaceAnnos();
+            if (!classToAnnos.containsKey(className)) {
+              classToAnnos.put(className, typeWrapper);
+            }
+
+            sourceAnnos.types.add(typeWrapper);
+          }
+
+          @Override
+          public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) {
+            addCallableDeclaration(javacTree, javaParserNode);
+          }
+
+          @Override
+          public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) {
+            addCallableDeclaration(javacTree, javaParserNode);
+          }
+
+          /**
+           * Creates a wrapper around {@code javacTree} with the corresponding declaration {@code
+           * javaParserNode} and stores it in {@code sourceAnnos}.
+           *
+           * @param javacTree javac tree for declaration to add
+           * @param javaParserNode JavaParser node for the same class as {@code javacTree}
+           */
+          private void addCallableDeclaration(
+              MethodTree javacTree, CallableDeclaration<?> javaParserNode) {
+            ExecutableElement elt = TreeUtils.elementFromDeclaration(javacTree);
+            String className = ElementUtils.getEnclosingClassName(elt);
+            ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(className);
+            String executableSignature = JVMNames.getJVMMethodSignature(javacTree);
+            if (!enclosingClass.callableDeclarations.containsKey(executableSignature)) {
+              enclosingClass.callableDeclarations.put(
+                  executableSignature, new CallableDeclarationAnnos(javaParserNode));
+            }
+          }
+
+          @Override
+          public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) {
+            // This seems to occur when javacTree is a local variable in the second
+            // class located in a source file. If this check returns false, then the
+            // below call to TreeUtils.elementFromDeclaration causes a crash.
+            if (TreeUtils.elementFromTree(javacTree) == null) {
+              return;
+            }
+
+            VariableElement elt = TreeUtils.elementFromDeclaration(javacTree);
+            if (!elt.getKind().isField()) {
+              return;
+            }
+
+            String enclosingClassName = ElementUtils.getEnclosingClassName(elt);
+            ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(enclosingClassName);
+            String fieldName = javacTree.getName().toString();
+            if (!enclosingClass.fields.containsKey(fieldName)) {
+              enclosingClass.fields.put(fieldName, new FieldAnnos(javaParserNode));
+            }
+          }
+        };
+    visitor.visitClass(javacClass, javaParserClass);
+  }
+
+  /**
+   * Calls {@link #addSourceFile(String)} for the file containing the given element.
+   *
+   * @param element the element for the source file to add
+   * @return path of the file containing {@code element}
+   */
+  private String addClassesForElement(Element element) {
+    if (!ElementUtils.isElementFromSourceCode(element)) {
+      throw new BugInCF("Called addClassesForElement for non-source element: " + element);
+    }
+
+    TypeElement toplevelClass = ElementUtils.toplevelEnclosingTypeElement(element);
+    String path = ElementUtils.getSourceFilePath(toplevelClass);
+    if (classToAnnos.containsKey(ElementUtils.getBinaryName(toplevelClass))) {
+      return path;
+    }
+
+    addSourceFile(path);
+    CompilationUnitAnnos sourceAnnos = sourceToAnnos.get(path);
+    ClassTree toplevelClassTree = (ClassTree) atypeFactory.declarationFromElement(toplevelClass);
+    TypeDeclaration<?> javaParserNode =
+        sourceAnnos.getClassOrInterfaceDeclarationByName(toplevelClass.getSimpleName().toString());
+    createWrappersForClass(toplevelClassTree, javaParserNode, sourceAnnos);
+    return path;
+  }
+
+  ///
+  /// Writing to a file
+  ///
+
+  // The prepare*ForWriting hooks are needed in addition to the postProcessClassTree hook because
+  // a scene may be modifed and written at any time, including before or after
+  // postProcessClassTree is called.
+
+  /**
+   * Side-effects the compilation unit annotations to make any desired changes before writing to a
+   * file.
+   *
+   * @param compilationUnitAnnos the compilation unit annotations to modify
+   */
+  public void prepareCompilationUnitForWriting(CompilationUnitAnnos compilationUnitAnnos) {
+    for (ClassOrInterfaceAnnos type : compilationUnitAnnos.types) {
+      prepareClassForWriting(type);
+    }
+  }
+
+  /**
+   * Side-effects the class annotations to make any desired changes before writing to a file.
+   *
+   * @param classAnnos the class annotations to modify
+   */
+  public void prepareClassForWriting(ClassOrInterfaceAnnos classAnnos) {
+    for (Map.Entry<String, CallableDeclarationAnnos> methodEntry :
+        classAnnos.callableDeclarations.entrySet()) {
+      prepareMethodForWriting(methodEntry.getValue());
+    }
+  }
+
+  /**
+   * Side-effects the method or constructor annotations to make any desired changes before writing
+   * to a file.
+   *
+   * @param methodAnnos the method or constructor annotations to modify
+   */
+  public void prepareMethodForWriting(CallableDeclarationAnnos methodAnnos) {
+    atypeFactory.prepareMethodForWriting(methodAnnos);
+  }
+
+  @Override
+  public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checker) {
+    if (outputFormat != OutputFormat.AJAVA) {
+      throw new BugInCF("WholeProgramInferenceJavaParser used with format " + outputFormat);
+    }
+
+    File outputDir = new File(AJAVA_FILES_PATH);
+    if (!outputDir.exists()) {
+      outputDir.mkdirs();
+    }
+
+    for (String path : modifiedFiles) {
+      CompilationUnitAnnos root = sourceToAnnos.get(path);
+      prepareCompilationUnitForWriting(root);
+      root.transferAnnotations();
+      String packageDir = AJAVA_FILES_PATH;
+      if (root.compilationUnit.getPackageDeclaration().isPresent()) {
+        packageDir +=
+            File.separator
+                + root.compilationUnit
+                    .getPackageDeclaration()
+                    .get()
+                    .getNameAsString()
+                    .replaceAll("\\.", File.separator);
+      }
+
+      File packageDirFile = new File(packageDir);
+      if (!packageDirFile.exists()) {
+        packageDirFile.mkdirs();
+      }
+
+      String name = new File(path).getName();
+      if (name.endsWith(".java")) {
+        name = name.substring(0, name.length() - ".java".length());
+      }
+
+      name += "-" + checker.getClass().getCanonicalName() + ".ajava";
+      String outputPath = packageDir + File.separator + name;
+      try {
+        FileWriter writer = new FileWriter(outputPath);
+
+        // JavaParser can output using lexical preserving printing, which writes the file such that
+        // its formatting is close to the original source file it was parsed from as
+        // possible. Currently, this feature is very buggy and crashes when adding annotations in
+        // certain locations. This implementation could be used instead if it's fixed in JavaParser.
+        // LexicalPreservingPrinter.print(root.declaration, writer);
+
+        DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
+        writer.write(prettyPrinter.print(root.compilationUnit));
+        writer.close();
+      } catch (IOException e) {
+        throw new BugInCF("Error while writing ajava file " + outputPath, e);
+      }
+    }
+
+    modifiedFiles.clear();
+  }
+
+  /**
+   * Adds an explicit receiver type to a JavaParser method declaration.
+   *
+   * @param methodDeclaration declaration to add a receiver to
+   */
+  private static void addExplicitReceiver(MethodDeclaration methodDeclaration) {
+    if (methodDeclaration.getReceiverParameter().isPresent()) {
+      return;
+    }
+
+    com.github.javaparser.ast.Node parent = methodDeclaration.getParentNode().get();
+    if (!(parent instanceof TypeDeclaration)) {
+      return;
+    }
+
+    TypeDeclaration<?> parentDecl = (TypeDeclaration<?>) parent;
+    ClassOrInterfaceType receiver = new ClassOrInterfaceType();
+    receiver.setName(parentDecl.getName());
+    if (parentDecl.isClassOrInterfaceDeclaration()) {
+      ClassOrInterfaceDeclaration parentClassDecl = parentDecl.asClassOrInterfaceDeclaration();
+      if (!parentClassDecl.getTypeParameters().isEmpty()) {
+        NodeList<Type> typeArgs = new NodeList<>();
+        for (TypeParameter typeParam : parentClassDecl.getTypeParameters()) {
+          ClassOrInterfaceType typeArg = new ClassOrInterfaceType();
+          typeArg.setName(typeParam.getNameAsString());
+          typeArgs.add(typeArg);
+        }
+
+        receiver.setTypeArguments(typeArgs);
+      }
+    }
+
+    methodDeclaration.setReceiverParameter(new ReceiverParameter(receiver, "this"));
+  }
+
+  /**
+   * Transfers all annotations for {@code annotatedType} and its nested types to {@code target},
+   * which is the JavaParser node representing the same type. Does nothing if {@code annotatedType}
+   * is null (this may occur if there's no inferred annotations for the type).
+   *
+   * @param annotatedType type to transfer annotations from
+   * @param target the JavaParser type to transfer annotation to; must represent the same type as
+   *     {@code annotatedType}
+   */
+  private static void transferAnnotations(
+      @Nullable AnnotatedTypeMirror annotatedType, Type target) {
+    if (annotatedType == null) {
+      return;
+    }
+
+    target.accept(new AnnotationTransferVisitor(), annotatedType);
+  }
+
+  ///
+  /// Storing annotations
+  ///
+
+  /**
+   * Stores the JavaParser node for a compilation unit and the list of wrappers for the classes and
+   * interfaces in that compilation unit.
+   */
+  private static class CompilationUnitAnnos {
+    /** Compilation unit being wrapped. */
+    public CompilationUnit compilationUnit;
+    /** Wrappers for classes and interfaces in {@code declaration} */
+    public List<ClassOrInterfaceAnnos> types;
+
+    /**
+     * Constructs a wrapper around the given compilation unit.
+     *
+     * @param compilationUnit compilation unit to wrap
+     */
+    public CompilationUnitAnnos(CompilationUnit compilationUnit) {
+      this.compilationUnit = compilationUnit;
+      types = new ArrayList<>();
+    }
+
+    /**
+     * Transfers all annotations inferred by whole program inference for the wrapped compilation
+     * unit to their corresponding JavaParser locations.
+     */
+    public void transferAnnotations() {
+      JavaParserUtil.clearAnnotations(compilationUnit);
+      for (ClassOrInterfaceAnnos typeAnnos : types) {
+        typeAnnos.transferAnnotations();
+      }
+    }
+
+    /**
+     * Returns the top-level type declaration named {@code name} in the compilation unit.
+     *
+     * @param name name of type declaration
+     * @return the type declaration named {@code name} in the wrapped compilation unit
+     */
+    public TypeDeclaration<?> getClassOrInterfaceDeclarationByName(String name) {
+      return JavaParserUtil.getTypeDeclarationByName(compilationUnit, name);
+    }
+  }
+
+  /**
+   * Stores wrappers for the locations where annotations may be inferred in a class or interface.
+   */
+  private static class ClassOrInterfaceAnnos {
+    /**
+     * Mapping from JVM method signatures to the wrapper containing the corresponding executable.
+     */
+    public Map<String, CallableDeclarationAnnos> callableDeclarations = new HashMap<>();
+    /** Mapping from field names to wrappers for those fields. */
+    public Map<String, FieldAnnos> fields = new HashMap<>(2);
+
+    /**
+     * Transfers all annotations inferred by whole program inference for the methods and fields in
+     * the wrapper class or interface to their corresponding JavaParser locations.
+     */
+    public void transferAnnotations() {
+      for (CallableDeclarationAnnos callableAnnos : callableDeclarations.values()) {
+        callableAnnos.transferAnnotations();
+      }
+
+      for (FieldAnnos field : fields.values()) {
+        field.transferAnnotations();
+      }
+    }
+
+    @Override
+    public String toString() {
+      return "ClassOrInterfaceAnnos [callableDeclarations="
+          + callableDeclarations
+          + ", fields="
+          + fields
+          + "]";
+    }
+  }
+
+  /**
+   * Stores the JavaParser node for a method or constructor and the annotations that have been
+   * inferred about its parameters and return type.
+   */
+  public class CallableDeclarationAnnos {
+    /** Wrapped method or constructor declaration. */
+    public CallableDeclaration<?> declaration;
+    /** Path to file containing the declaration. */
+    public String file;
+    /**
+     * Inferred annotations for the return type, if the declaration represents a method. Initialized
+     * on first usage.
+     */
+    private @MonotonicNonNull AnnotatedTypeMirror returnType = null;
+    /**
+     * Inferred annotations for the receiver type, if the declaration represents a method.
+     * Initialized on first usage.
+     */
+    private @MonotonicNonNull AnnotatedTypeMirror receiverType = null;
+    /**
+     * Inferred annotations for parameter types. Initialized the first time any parameter is
+     * accessed and each parameter is initialized the first time it's accessed.
+     */
+    private @MonotonicNonNull List<@Nullable AnnotatedTypeMirror> parameterTypes = null;
+    /** Annotations on the callable declaration. */
+    private @MonotonicNonNull Set<AnnotationMirror> declarationAnnotations = null;
+
+    /**
+     * Mapping from VariableElements for fields to an AnnotatedTypeMirror containing the inferred
+     * preconditions on that field.
+     */
+    private @MonotonicNonNull Map<VariableElement, AnnotatedTypeMirror> fieldToPreconditions = null;
+    /**
+     * Mapping from VariableElements for fields to an AnnotatedTypeMirror containing the inferred
+     * postconditions on that field.
+     */
+    private @MonotonicNonNull Map<VariableElement, AnnotatedTypeMirror> fieldToPostconditions =
+        null;
+    // /** Inferred contracts for the callable declaration. */
+    // private @MonotonicNonNull List<AnnotationMirror> contracts = null;
+
+    /**
+     * Creates a wrapper for the given method or constructor declaration.
+     *
+     * @param declaration method or constructor declaration to wrap
+     */
+    public CallableDeclarationAnnos(CallableDeclaration<?> declaration) {
+      this.declaration = declaration;
+    }
+
+    /**
+     * Returns the inferred type for the parameter at the given index. If necessary, initializes the
+     * {@code AnnotatedTypeMirror} for that location using {@code type} and {@code atf} to a wrapper
+     * around the base type for the parameter.
+     *
+     * @param type type for the parameter at {@code index}, used for initializing the returned
+     *     {@code AnnotatedTypeMirror} the first time it's accessed
+     * @param atf the annotated type factory of a given type system, whose type hierarchy will be
+     *     used
+     * @param index index of the parameter to return the inferred annotations of
+     * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the parameter
+     *     at the given index
+     */
+    public AnnotatedTypeMirror getParameterTypeInitialized(
+        AnnotatedTypeMirror type, int index, AnnotatedTypeFactory atf) {
+      if (parameterTypes == null) {
+        parameterTypes =
+            new ArrayList<>(Collections.nCopies(declaration.getParameters().size(), null));
+      }
+
+      if (parameterTypes.get(index) == null) {
+        parameterTypes.set(
+            index, AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false));
+      }
+
+      return parameterTypes.get(index);
+    }
+
+    /**
+     * Returns the inferred type for the parameter at the given index, or null if there's no
+     * parameter at the given index or there's no inferred type for that parameter.
+     *
+     * @param index index of the parameter to return the inferred annotations of
+     * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the parameter
+     *     at the given index, or null if there's no parameter at {@code index} or if there's not
+     *     inferred annotations for that parameter
+     */
+    public @Nullable AnnotatedTypeMirror getParameterType(int index) {
+      if (parameterTypes == null || index < 0 || index >= parameterTypes.size()) {
+        return null;
+      }
+
+      return parameterTypes.get(index);
+    }
+
+    /**
+     * Returns the inferred declaration annotations on this executable, or null if there are no
+     * annotations.
+     *
+     * @return the declaration annotations for this callable declaration
+     */
+    public Set<AnnotationMirror> getDeclarationAnnotations() {
+      if (declarationAnnotations == null) {
+        return Collections.emptySet();
+      }
+
+      return Collections.unmodifiableSet(declarationAnnotations);
+    }
+
+    /**
+     * Adds a declaration annotation to this callable declaration and returns whether it was a new
+     * annotation.
+     *
+     * @param annotation declaration annotation to add
+     * @return true if {@code annotation} wasn't previously stored for this callable declaration
+     */
+    public boolean addDeclarationAnnotation(AnnotationMirror annotation) {
+      if (declarationAnnotations == null) {
+        declarationAnnotations = new HashSet<>();
+      }
+
+      return declarationAnnotations.add(annotation);
+    }
+
+    /**
+     * If this wrapper holds a method, returns the inferred type of the receiver. If necessary,
+     * initializes the {@code AnnotatedTypeMirror} for that location using {@code type} and {@code
+     * atf} to a wrapper around the base type for the receiver type.
+     *
+     * @param type base type for the receiver type, used for initializing the returned {@code
+     *     AnnotatedTypeMirror} the first time it's accessed
+     * @param atf the annotated type factory of a given type system, whose type hierarchy will be
+     *     used
+     * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the receiver
+     *     type
+     */
+    public AnnotatedTypeMirror getReceiverType(AnnotatedTypeMirror type, AnnotatedTypeFactory atf) {
+      if (receiverType == null) {
+        receiverType = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false);
+      }
+
+      return receiverType;
+    }
+
+    /**
+     * If this wrapper holds a method, returns the inferred type of the return type. If necessary,
+     * initializes the {@code AnnotatedTypeMirror} for that location using {@code type} and {@code
+     * atf} to a wrapper around the base type for the return type.
+     *
+     * @param type base type for the return type, used for initializing the returned {@code
+     *     AnnotatedTypeMirror} the first time it's accessed
+     * @param atf the annotated type factory of a given type system, whose type hierarchy will be
+     *     used
+     * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the return
+     *     type
+     */
+    public AnnotatedTypeMirror getReturnType(AnnotatedTypeMirror type, AnnotatedTypeFactory atf) {
+      if (returnType == null) {
+        returnType = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false);
+      }
+
+      return returnType;
+    }
+
+    /**
+     * Returns the inferred preconditions for this callable declaration.
+     *
+     * @return a mapping from VariableElements for fields to AnnotatedTypeMirrors containing the
+     *     inferred preconditions for those fields.
+     */
+    public Map<VariableElement, AnnotatedTypeMirror> getFieldToPreconditions() {
+      if (fieldToPreconditions == null) {
+        return Collections.emptyMap();
+      }
+
+      return Collections.unmodifiableMap(fieldToPreconditions);
+    }
+
+    /**
+     * Returns the inferred postconditions for this callable declaration.
+     *
+     * @return a mapping from VariableElements for fields to AnnotatedTypeMirrors containing the
+     *     inferred postconditions for those fields.
+     */
+    public Map<VariableElement, AnnotatedTypeMirror> getFieldToPostconditions() {
+      if (fieldToPostconditions == null) {
+        return Collections.emptyMap();
+      }
+
+      return Collections.unmodifiableMap(fieldToPostconditions);
+    }
+
+    /**
+     * Returns an AnnotatedTypeMirror containing the preconditions for the given field.
+     *
+     * @param field VariableElement for a field in the enclosing class for this method
+     * @param atf the annotated type factory of a given type system, whose type hierarchy will be
+     *     used
+     * @return an {@code AnnotatedTypeMirror} containing the annotations for the inferred
+     *     preconditions for the given field
+     */
+    public AnnotatedTypeMirror getPreconditionsForField(
+        VariableElement field, AnnotatedTypeFactory atf) {
+      if (fieldToPreconditions == null) {
+        fieldToPreconditions = new HashMap<>(1);
+      }
+
+      if (!fieldToPreconditions.containsKey(field)) {
+        TypeMirror underlyingType = atf.getAnnotatedType(field).getUnderlyingType();
+        AnnotatedTypeMirror preconditionsType =
+            AnnotatedTypeMirror.createType(underlyingType, atf, false);
+        fieldToPreconditions.put(field, preconditionsType);
+      }
+
+      return fieldToPreconditions.get(field);
+    }
+
+    /**
+     * Returns an AnnotatedTypeMirror containing the postconditions for the given field.
+     *
+     * @param field VariableElement for a field in the enclosing class for this method
+     * @param atf the annotated type factory of a given type system, whose type hierarchy will be
+     *     used
+     * @return an {@code AnnotatedTypeMirror} containing the annotations for the inferred
+     *     postconditions for the given field
+     */
+    public AnnotatedTypeMirror getPostconditionsForField(
+        VariableElement field, AnnotatedTypeFactory atf) {
+      if (fieldToPostconditions == null) {
+        fieldToPostconditions = new HashMap<>(1);
+      }
+
+      if (!fieldToPostconditions.containsKey(field)) {
+        TypeMirror underlyingType = atf.getAnnotatedType(field).getUnderlyingType();
+        AnnotatedTypeMirror postconditionsType =
+            AnnotatedTypeMirror.createType(underlyingType, atf, false);
+        fieldToPostconditions.put(field, postconditionsType);
+      }
+
+      return fieldToPostconditions.get(field);
+    }
+
+    /**
+     * Transfers all annotations inferred by whole program inference for the return type, receiver
+     * type, and parameter types for the wrapped declaration to their corresponding JavaParser
+     * locations.
+     */
+    public void transferAnnotations() {
+      if (atypeFactory instanceof GenericAnnotatedTypeFactory<?, ?, ?, ?>) {
+        GenericAnnotatedTypeFactory<?, ?, ?, ?> genericAtf =
+            (GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory;
+        for (AnnotationMirror contractAnno : genericAtf.getContractAnnotations(this)) {
+          declaration.addAnnotation(
+              AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr(
+                  contractAnno));
+        }
+      }
+
+      if (declarationAnnotations != null) {
+        for (AnnotationMirror annotation : declarationAnnotations) {
+          declaration.addAnnotation(
+              AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr(
+                  annotation));
+        }
+      }
+
+      if (returnType != null) {
+        // If a return type exists, then the declaration must be a method, not a constructor.
+        WholeProgramInferenceJavaParserStorage.transferAnnotations(
+            returnType, declaration.asMethodDeclaration().getType());
+      }
+
+      if (receiverType != null) {
+        addExplicitReceiver(declaration.asMethodDeclaration());
+        // The receiver won't be present for an anonymous class.
+        if (declaration.getReceiverParameter().isPresent()) {
+          WholeProgramInferenceJavaParserStorage.transferAnnotations(
+              receiverType, declaration.getReceiverParameter().get().getType());
+        }
+      }
+
+      if (parameterTypes == null) {
+        return;
+      }
+
+      for (int i = 0; i < parameterTypes.size(); i++) {
+        WholeProgramInferenceJavaParserStorage.transferAnnotations(
+            parameterTypes.get(i), declaration.getParameter(i).getType());
+      }
+    }
+
+    @Override
+    public String toString() {
+      return "CallableDeclarationAnnos [declaration="
+          + declaration
+          + ", file="
+          + file
+          + ", parameterTypes="
+          + parameterTypes
+          + ", receiverType="
+          + receiverType
+          + ", returnType="
+          + returnType
+          + "]";
+    }
+  }
+
+  /** Stores the JavaParser node for a field and the annotations that have been inferred for it. */
+  private static class FieldAnnos {
+    /** Wrapped field declaration. */
+    public VariableDeclarator declaration;
+    /** Inferred type for field, initialized the first time it's accessed. */
+    private @MonotonicNonNull AnnotatedTypeMirror type = null;
+
+    /**
+     * Creates a wrapper for the given field declaration.
+     *
+     * @param declaration field declaration to wrap
+     */
+    public FieldAnnos(VariableDeclarator declaration) {
+      this.declaration = declaration;
+    }
+
+    /**
+     * Returns the inferred type of the field. If necessary, initializes the {@code
+     * AnnotatedTypeMirror} for that location using {@code type} and {@code atf} to a wrapper around
+     * the base type for the field.
+     *
+     * @param type base type for the field, used for initializing the returned {@code
+     *     AnnotatedTypeMirror} the first time it's accessed
+     * @param atf the annotated type factory of a given type system, whose type hierarchy will be
+     *     used
+     * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the field
+     */
+    public AnnotatedTypeMirror getType(AnnotatedTypeMirror type, AnnotatedTypeFactory atf) {
+      if (this.type == null) {
+        this.type = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false);
+      }
+
+      return this.type;
+    }
+
+    /**
+     * Transfers all annotations inferred by whole program inference on this field to the JavaParser
+     * nodes for that field.
+     */
+    public void transferAnnotations() {
+      if (type == null) {
+        return;
+      }
+
+      Type newType = (Type) declaration.getType().accept(new CloneVisitor(), null);
+      WholeProgramInferenceJavaParserStorage.transferAnnotations(type, newType);
+      declaration.setType(newType);
+    }
+
+    @Override
+    public String toString() {
+      return "FieldAnnos [declaration=" + declaration + ", type=" + type + "]";
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenesStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenesStorage.java
new file mode 100644
index 0000000..7ec2e22
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenesStorage.java
@@ -0,0 +1,836 @@
+package org.checkerframework.common.wholeprograminference;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Target;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+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.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.wholeprograminference.WholeProgramInference.OutputFormat;
+import org.checkerframework.common.wholeprograminference.scenelib.ASceneWrapper;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TypeAnnotationUtils;
+import org.checkerframework.javacutil.UserError;
+import scenelib.annotations.Annotation;
+import scenelib.annotations.el.AClass;
+import scenelib.annotations.el.AField;
+import scenelib.annotations.el.AMethod;
+import scenelib.annotations.el.AScene;
+import scenelib.annotations.el.ATypeElement;
+import scenelib.annotations.el.TypePathEntry;
+import scenelib.annotations.io.IndexFileParser;
+import scenelib.annotations.util.JVMNames;
+
+/**
+ * This class stores annotations using scenelib objects.
+ *
+ * <p>The set of annotations inferred for a certain class is stored in an {@link
+ * scenelib.annotations.el.AScene}, which {@code writeScenes()} can write into a file. For example,
+ * a class {@code my.pakkage.MyClass} will have its members' inferred types stored in a Scene, and
+ * later written into a file named {@code my.pakkage.MyClass.jaif} if using {@link
+ * OutputFormat#JAIF}, or {@code my.pakkage.MyClass.astub} if using {@link OutputFormat#STUB}.
+ *
+ * <p>This class populates the initial Scenes by reading existing .jaif files on the {@link
+ * #JAIF_FILES_PATH} directory (regardless of output format). Having more information in those
+ * initial .jaif files means that the precision achieved by the whole-program inference analysis
+ * will be better. {@link #writeScenes} rewrites the initial .jaif files and may create new ones.
+ */
+public class WholeProgramInferenceScenesStorage
+    implements WholeProgramInferenceStorage<ATypeElement> {
+
+  /**
+   * Directory where .jaif files will be written to and read from. This directory is relative to
+   * where the CF's javac command is executed.
+   */
+  public static final String JAIF_FILES_PATH =
+      "build" + File.separator + "whole-program-inference" + File.separator;
+
+  /** The type factory associated with this WholeProgramInferenceScenesStorage. */
+  protected final AnnotatedTypeFactory atypeFactory;
+
+  /** Annotations that should not be output to a .jaif or stub file. */
+  private final AnnotationsInContexts annosToIgnore = new AnnotationsInContexts();
+
+  /**
+   * If true, assignments where the rhs is null are be ignored.
+   *
+   * <p>If all assignments to a variable are null (because inference is being done with respect to a
+   * limited set of uses) then the variable is inferred to have bottom type. That inference is
+   * unlikely to be correct. To avoid that inference, set this variable to true. When the variable
+   * is true, if all assignments are null, then none are recorded, no inference is done, and the
+   * variable remains at its default type.
+   */
+  private final boolean ignoreNullAssignments;
+
+  /** Maps .jaif file paths (Strings) to Scenes. Relative to JAIF_FILES_PATH. */
+  public final Map<String, ASceneWrapper> scenes = new HashMap<>();
+
+  /**
+   * Scenes that were modified since the last time all Scenes were written into .jaif files. Each
+   * String element of this set is a path (relative to JAIF_FILES_PATH) to the .jaif file of the
+   * corresponding Scene in the set. It is obtained by passing a class name as argument to the
+   * {@link #getJaifPath} method.
+   *
+   * <p>Modifying a Scene means adding (or changing) a type annotation for a field, method return
+   * type, or method parameter type in the Scene. (Scenes are modified by the method {@link
+   * #updateAnnotationSetInScene}.)
+   */
+  public final Set<String> modifiedScenes = new HashSet<>();
+
+  /**
+   * Default constructor.
+   *
+   * @param atypeFactory the type factory associated with this WholeProgramInferenceScenesStorage
+   */
+  public WholeProgramInferenceScenesStorage(AnnotatedTypeFactory atypeFactory) {
+    this.atypeFactory = atypeFactory;
+    boolean isNullness =
+        atypeFactory.getClass().getSimpleName().equals("NullnessAnnotatedTypeFactory");
+    this.ignoreNullAssignments = !isNullness;
+  }
+
+  @Override
+  public String getFileForElement(Element elt) {
+    String className;
+    switch (elt.getKind()) {
+      case CONSTRUCTOR:
+      case METHOD:
+        className = ElementUtils.getEnclosingClassName((ExecutableElement) elt);
+        break;
+      case LOCAL_VARIABLE:
+        className = getEnclosingClassName((LocalVariableNode) elt);
+        break;
+      case FIELD:
+        ClassSymbol enclosingClass = ((VarSymbol) elt).enclClass();
+        className = enclosingClass.flatname.toString();
+        break;
+      default:
+        throw new BugInCF("What element? %s %s", elt.getKind(), elt);
+    }
+    String file = getJaifPath(className);
+    return file;
+  }
+
+  /**
+   * Get the annotations for a class.
+   *
+   * @param className the name of the class, in binary form
+   * @param file the path to the file that represents the class
+   * @param classSymbol optionally, the ClassSymbol representing the class
+   * @return the annotations for the class
+   */
+  private AClass getClassAnnos(
+      @BinaryName String className, String file, @Nullable ClassSymbol classSymbol) {
+    // Possibly reads .jaif file to obtain a Scene.
+    ASceneWrapper scene = getScene(file);
+    AClass aClass = scene.getAScene().classes.getVivify(className);
+    scene.updateSymbolInformation(aClass, classSymbol);
+    return aClass;
+  }
+
+  /**
+   * Get the annotations for a method or constructor.
+   *
+   * @param methodElt the method or constructor
+   * @return the annotations for a method or constructor
+   */
+  private AMethod getMethodAnnos(ExecutableElement methodElt) {
+    String className = ElementUtils.getEnclosingClassName(methodElt);
+    String file = getFileForElement(methodElt);
+    AClass classAnnos = getClassAnnos(className, file, ((MethodSymbol) methodElt).enclClass());
+    AMethod methodAnnos = classAnnos.methods.getVivify(JVMNames.getJVMMethodSignature(methodElt));
+    methodAnnos.setFieldsFromMethodElement(methodElt);
+    return methodAnnos;
+  }
+
+  @Override
+  public boolean hasStorageLocationForMethod(ExecutableElement methodElt) {
+    // The scenes implementation can always add annotations to a method.
+    return true;
+  }
+
+  @Override
+  public ATypeElement getParameterAnnotations(
+      ExecutableElement methodElt,
+      int i,
+      AnnotatedTypeMirror paramATM,
+      VariableElement ve,
+      AnnotatedTypeFactory atypeFactory) {
+    AMethod methodAnnos = getMethodAnnos(methodElt);
+    AField param =
+        methodAnnos.vivifyAndAddTypeMirrorToParameter(
+            i, paramATM.getUnderlyingType(), ve.getSimpleName());
+    return param.type;
+  }
+
+  @Override
+  public ATypeElement getReceiverAnnotations(
+      ExecutableElement methodElt,
+      AnnotatedTypeMirror paramATM,
+      AnnotatedTypeFactory atypeFactory) {
+    AMethod methodAnnos = getMethodAnnos(methodElt);
+    return methodAnnos.receiver.type;
+  }
+
+  @Override
+  public ATypeElement getReturnAnnotations(
+      ExecutableElement methodElt, AnnotatedTypeMirror atm, AnnotatedTypeFactory atypeFactory) {
+    AMethod methodAnnos = getMethodAnnos(methodElt);
+    return methodAnnos.returnType;
+  }
+
+  @Override
+  public ATypeElement getFieldAnnotations(
+      Element element,
+      String fieldName,
+      AnnotatedTypeMirror lhsATM,
+      AnnotatedTypeFactory atypeFactory) {
+    ClassSymbol enclosingClass = ((VarSymbol) element).enclClass();
+    String file = getFileForElement(element);
+    @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094
+    @BinaryName String className = enclosingClass.flatname.toString();
+    AClass classAnnos = getClassAnnos(className, file, enclosingClass);
+    AField field = classAnnos.fields.getVivify(fieldName);
+    field.setTypeMirror(lhsATM.getUnderlyingType());
+    return field.type;
+  }
+
+  @Override
+  public ATypeElement getPreOrPostconditionsForField(
+      Analysis.BeforeOrAfter preOrPost,
+      ExecutableElement methodElement,
+      VariableElement fieldElement,
+      AnnotatedTypeFactory atypeFactory) {
+    switch (preOrPost) {
+      case BEFORE:
+        return getPreconditionsForField(methodElement, fieldElement, atypeFactory);
+      case AFTER:
+        return getPostconditionsForField(methodElement, fieldElement, atypeFactory);
+      default:
+        throw new BugInCF("Unexpected " + preOrPost);
+    }
+  }
+
+  /**
+   * Returns the precondition annotations for a field.
+   *
+   * @param methodElement the method
+   * @param fieldElement the field
+   * @param atypeFactory the type factory
+   * @return the precondition annotations for a field
+   */
+  @SuppressWarnings("UnusedVariable")
+  private ATypeElement getPreconditionsForField(
+      ExecutableElement methodElement,
+      VariableElement fieldElement,
+      AnnotatedTypeFactory atypeFactory) {
+    AMethod methodAnnos = getMethodAnnos(methodElement);
+    TypeMirror typeMirror = TypeAnnotationUtils.unannotatedType(fieldElement.asType());
+    return methodAnnos.vivifyAndAddTypeMirrorToPrecondition(fieldElement, typeMirror).type;
+  }
+
+  /**
+   * Returns the postcondition annotations for a field.
+   *
+   * @param methodElement the method
+   * @param fieldElement the field
+   * @param atypeFactory the type factory
+   * @return the postcondition annotations for a field
+   */
+  @SuppressWarnings("UnusedVariable")
+  private ATypeElement getPostconditionsForField(
+      ExecutableElement methodElement,
+      VariableElement fieldElement,
+      AnnotatedTypeFactory atypeFactory) {
+    AMethod methodAnnos = getMethodAnnos(methodElement);
+    TypeMirror typeMirror = TypeAnnotationUtils.unannotatedType(fieldElement.asType());
+    return methodAnnos.vivifyAndAddTypeMirrorToPostcondition(fieldElement, typeMirror).type;
+  }
+
+  @Override
+  public boolean addMethodDeclarationAnnotation(
+      ExecutableElement methodElt, AnnotationMirror anno) {
+
+    // Do not infer types for library code, only for type-checked source code.
+    if (!ElementUtils.isElementFromSourceCode(methodElt)) {
+      return false;
+    }
+
+    AMethod methodAnnos = getMethodAnnos(methodElt);
+
+    scenelib.annotations.Annotation sceneAnno =
+        AnnotationConverter.annotationMirrorToAnnotation(anno);
+    boolean isNewAnnotation = methodAnnos.tlAnnotationsHere.add(sceneAnno);
+    return isNewAnnotation;
+  }
+
+  /**
+   * Write all modified scenes into files. (Scenes are modified by the method {@link
+   * #updateAnnotationSetInScene}.)
+   *
+   * @param outputFormat the output format to use when writing files
+   * @param checker the checker from which this method is called, for naming stub files
+   */
+  public void writeScenes(OutputFormat outputFormat, BaseTypeChecker checker) {
+    // Create WPI directory if it doesn't exist already.
+    File jaifDir = new File(JAIF_FILES_PATH);
+    if (!jaifDir.exists()) {
+      jaifDir.mkdirs();
+    }
+    // Write scenes into files.
+    for (String jaifPath : modifiedScenes) {
+      scenes.get(jaifPath).writeToFile(jaifPath, annosToIgnore, outputFormat, checker);
+    }
+    modifiedScenes.clear();
+  }
+
+  /**
+   * Returns the String representing the .jaif path of a class given its name.
+   *
+   * @param className the simple name of a class
+   * @return the path to the .jaif file
+   */
+  protected String getJaifPath(String className) {
+    String jaifPath = JAIF_FILES_PATH + className + ".jaif";
+    return jaifPath;
+  }
+
+  /**
+   * Reads a Scene from the given .jaif file, or returns an empty Scene if the file does not exist.
+   *
+   * @param jaifPath the .jaif file
+   * @return the Scene read from the file, or an empty Scene if the file does not exist
+   */
+  private ASceneWrapper getScene(String jaifPath) {
+    AScene scene;
+    if (!scenes.containsKey(jaifPath)) {
+      File jaifFile = new File(jaifPath);
+      scene = new AScene();
+      if (jaifFile.exists()) {
+        try {
+          IndexFileParser.parseFile(jaifPath, scene);
+        } catch (IOException e) {
+          throw new UserError("Problem while reading %s: %s", jaifPath, e.getMessage());
+        }
+      }
+      ASceneWrapper wrapper = new ASceneWrapper(scene);
+      scenes.put(jaifPath, wrapper);
+      return wrapper;
+    } else {
+      return scenes.get(jaifPath);
+    }
+  }
+
+  /**
+   * Returns the scene-lib representation of the given className in the scene identified by the
+   * given jaifPath.
+   *
+   * @param className the name of the class to get, in binary form
+   * @param jaifPath the path to the jaif file that would represent that class (must end in ".jaif")
+   * @param classSymbol optionally, the ClassSymbol representing the class. Used to set the symbol
+   *     information stored on an AClass.
+   * @return a version of the scene-lib representation of the class, augmented with symbol
+   *     information if {@code classSymbol} was non-null
+   */
+  protected AClass getAClass(
+      @BinaryName String className, String jaifPath, @Nullable ClassSymbol classSymbol) {
+    // Possibly reads .jaif file to obtain a Scene.
+    ASceneWrapper scene = getScene(jaifPath);
+    AClass aClass = scene.getAScene().classes.getVivify(className);
+    scene.updateSymbolInformation(aClass, classSymbol);
+    return aClass;
+  }
+
+  /**
+   * Returns the scene-lib representation of the given className in the scene identified by the
+   * given jaifPath.
+   *
+   * @param className the name of the class to get, in binary form
+   * @param jaifPath the path to the jaif file that would represent that class (must end in ".jaif")
+   * @return the scene-lib representation of the class, possibly augmented with symbol information
+   *     if {@link #getAClass(String, String, com.sun.tools.javac.code.Symbol.ClassSymbol)} has
+   *     already been called with a non-null third argument
+   */
+  protected AClass getAClass(@BinaryName String className, String jaifPath) {
+    return getAClass(className, jaifPath, null);
+  }
+
+  /**
+   * Updates the set of annotations in a location of a Scene, as the result of a pseudo-assignment.
+   *
+   * <ul>
+   *   <li>If there was no previous annotation for that location, then the updated set will be the
+   *       annotations in rhsATM.
+   *   <li>If there was a previous annotation, the updated set will be the LUB between the previous
+   *       annotation and rhsATM.
+   * </ul>
+   *
+   * @param type ATypeElement of the Scene which will be modified
+   * @param jaifPath path to a .jaif file for a Scene; used for marking the scene as modified
+   *     (needing to be written to disk)
+   * @param rhsATM the RHS of the annotated type on the source code
+   * @param lhsATM the LHS of the annotated type on the source code
+   * @param defLoc the location where the annotation will be added
+   * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the
+   *     source code
+   */
+  protected void updateAnnotationSetInScene(
+      ATypeElement type,
+      TypeUseLocation defLoc,
+      AnnotatedTypeMirror rhsATM,
+      AnnotatedTypeMirror lhsATM,
+      String jaifPath,
+      boolean ignoreIfAnnotated) {
+    if (rhsATM instanceof AnnotatedNullType && ignoreNullAssignments) {
+      return;
+    }
+    AnnotatedTypeMirror atmFromScene = atmFromStorageLocation(rhsATM.getUnderlyingType(), type);
+    updateAtmWithLub(rhsATM, atmFromScene);
+    if (lhsATM instanceof AnnotatedTypeVariable) {
+      Set<AnnotationMirror> upperAnnos =
+          ((AnnotatedTypeVariable) lhsATM).getUpperBound().getEffectiveAnnotations();
+      // If the inferred type is a subtype of the upper bounds of the
+      // current type on the source code, halt.
+      if (upperAnnos.size() == rhsATM.getAnnotations().size()
+          && atypeFactory.getQualifierHierarchy().isSubtype(rhsATM.getAnnotations(), upperAnnos)) {
+        return;
+      }
+    }
+    updateTypeElementFromATM(type, 1, defLoc, rhsATM, lhsATM, ignoreIfAnnotated);
+    modifiedScenes.add(jaifPath);
+  }
+
+  /**
+   * Updates sourceCodeATM to contain the LUB between sourceCodeATM and jaifATM, ignoring missing
+   * AnnotationMirrors from jaifATM -- it considers the LUB between an AnnotationMirror am and a
+   * missing AnnotationMirror to be am. The results are stored in sourceCodeATM.
+   *
+   * @param sourceCodeATM the annotated type on the source code
+   * @param jaifATM the annotated type on the .jaif file
+   */
+  private void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror jaifATM) {
+
+    switch (sourceCodeATM.getKind()) {
+      case TYPEVAR:
+        updateAtmWithLub(
+            ((AnnotatedTypeVariable) sourceCodeATM).getLowerBound(),
+            ((AnnotatedTypeVariable) jaifATM).getLowerBound());
+        updateAtmWithLub(
+            ((AnnotatedTypeVariable) sourceCodeATM).getUpperBound(),
+            ((AnnotatedTypeVariable) jaifATM).getUpperBound());
+        break;
+        //        case WILDCARD:
+        // Because inferring type arguments is not supported, wildcards won't be encoutered
+        //            updateAtmWithLub(((AnnotatedWildcardType)
+        // sourceCodeATM).getExtendsBound(),
+        //                              ((AnnotatedWildcardType)
+        // jaifATM).getExtendsBound());
+        //            updateAtmWithLub(((AnnotatedWildcardType)
+        // sourceCodeATM).getSuperBound(),
+        //                              ((AnnotatedWildcardType) jaifATM).getSuperBound());
+        //            break;
+      case ARRAY:
+        updateAtmWithLub(
+            ((AnnotatedArrayType) sourceCodeATM).getComponentType(),
+            ((AnnotatedArrayType) jaifATM).getComponentType());
+        break;
+        // case DECLARED:
+        // inferring annotations on type arguments is not supported, so no need to recur on
+        // generic types. If this was every implemented, this method would need VisitHistory
+        // object to prevent infinite recursion on types such as T extends List<T>.
+      default:
+        // ATM only has primary annotations
+        break;
+    }
+
+    // LUB primary annotations
+    Set<AnnotationMirror> annosToReplace = new HashSet<>(sourceCodeATM.getAnnotations().size());
+    for (AnnotationMirror amSource : sourceCodeATM.getAnnotations()) {
+      AnnotationMirror amJaif = jaifATM.getAnnotationInHierarchy(amSource);
+      // amJaif only contains  annotations from the jaif, so it might be missing
+      // an annotation in the hierarchy
+      if (amJaif != null) {
+        amSource = atypeFactory.getQualifierHierarchy().leastUpperBound(amSource, amJaif);
+      }
+      annosToReplace.add(amSource);
+    }
+    sourceCodeATM.replaceAnnotations(annosToReplace);
+  }
+
+  /**
+   * Returns true if {@code am} should not be inserted in source code, for example {@link
+   * org.checkerframework.common.value.qual.BottomVal}. This happens when {@code am} cannot be
+   * inserted in source code or is the default for the location passed as argument.
+   *
+   * <p>Invisible qualifiers, which are annotations that contain the {@link
+   * org.checkerframework.framework.qual.InvisibleQualifier} meta-annotation, also return true.
+   *
+   * <p>TODO: Merge functionality somewhere else with {@link
+   * org.checkerframework.framework.util.defaults.QualifierDefaults}. Look into the
+   * createQualifierDefaults method in {@link GenericAnnotatedTypeFactory} (which uses the
+   * QualifierDefaults class linked above) before changing anything here. See
+   * https://github.com/typetools/checker-framework/issues/683 .
+   *
+   * @param am an annotation to test for whether it should be inserted into source code
+   * @param location where the location would be inserted; used to determine if {@code am} is the
+   *     default for that location
+   * @param atm its kind is used to determine if {@code am} is the default for that kind
+   * @return true if am should not be inserted into source code, or if am is invisible
+   */
+  private boolean shouldIgnore(
+      AnnotationMirror am, TypeUseLocation location, AnnotatedTypeMirror atm) {
+    Element elt = am.getAnnotationType().asElement();
+    // Checks if am is an implementation detail (a type qualifier used
+    // internally by the type system and not meant to be seen by the user).
+    Target target = elt.getAnnotation(Target.class);
+    if (target != null && target.value().length == 0) {
+      return true;
+    }
+    if (elt.getAnnotation(InvisibleQualifier.class) != null) {
+      return true;
+    }
+
+    // Checks if am is default
+    if (elt.getAnnotation(DefaultQualifierInHierarchy.class) != null) {
+      return true;
+    }
+    DefaultQualifier defaultQual = elt.getAnnotation(DefaultQualifier.class);
+    if (defaultQual != null) {
+      for (TypeUseLocation loc : defaultQual.locations()) {
+        if (loc == TypeUseLocation.ALL || loc == location) {
+          return true;
+        }
+      }
+    }
+    DefaultFor defaultQualForLocation = elt.getAnnotation(DefaultFor.class);
+    if (defaultQualForLocation != null) {
+      for (TypeUseLocation loc : defaultQualForLocation.value()) {
+        if (loc == TypeUseLocation.ALL || loc == location) {
+          return true;
+        }
+      }
+    }
+
+    // Checks if am is a default annotation.
+    // This case checks if it is meta-annotated with @DefaultFor.
+    // TODO: Handle cases of annotations added via an
+    // org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator.
+    DefaultFor defaultFor = elt.getAnnotation(DefaultFor.class);
+    if (defaultFor != null) {
+      org.checkerframework.framework.qual.TypeKind[] types = defaultFor.typeKinds();
+      TypeKind atmKind = atm.getUnderlyingType().getKind();
+      if (hasMatchingTypeKind(atmKind, types)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /** Returns true, iff a matching TypeKind is found. */
+  private boolean hasMatchingTypeKind(
+      TypeKind atmKind, org.checkerframework.framework.qual.TypeKind[] types) {
+    for (org.checkerframework.framework.qual.TypeKind tk : types) {
+      if (tk.name().equals(atmKind.name())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns a subset of annosSet, consisting of the annotations supported by the type factory
+   * associated with this. These are not necessarily legal annotations: they have the right name,
+   * but they may lack elements (fields).
+   *
+   * @param annosSet a set of annotations
+   * @return the annoattions supported by this object's AnnotatedTypeFactory
+   */
+  private Set<Annotation> getSupportedAnnosInSet(Set<Annotation> annosSet) {
+    Set<Annotation> output = new HashSet<>(1);
+    Set<Class<? extends java.lang.annotation.Annotation>> supportedAnnos =
+        atypeFactory.getSupportedTypeQualifiers();
+    for (Annotation anno : annosSet) {
+      for (Class<? extends java.lang.annotation.Annotation> clazz : supportedAnnos) {
+        // TODO: Remove comparison by name, and make this routine more efficient.
+        if (clazz.getName().equals(anno.def.name)) {
+          output.add(anno);
+        }
+      }
+    }
+    return output;
+  }
+
+  @Override
+  public AnnotatedTypeMirror atmFromStorageLocation(
+      TypeMirror typeMirror, ATypeElement storageLocation) {
+    AnnotatedTypeMirror result = AnnotatedTypeMirror.createType(typeMirror, atypeFactory, false);
+    updateAtmFromATypeElement(result, storageLocation);
+    return result;
+  }
+
+  /**
+   * Updates an {@link org.checkerframework.framework.type.AnnotatedTypeMirror} to contain the
+   * {@link scenelib.annotations.Annotation}s of an {@link scenelib.annotations.el.ATypeElement}.
+   *
+   * @param result the AnnotatedTypeMirror to be modified
+   * @param storageLocation the {@link scenelib.annotations.el.ATypeElement} used
+   */
+  private void updateAtmFromATypeElement(AnnotatedTypeMirror result, ATypeElement storageLocation) {
+    Set<Annotation> annos = getSupportedAnnosInSet(storageLocation.tlAnnotationsHere);
+    for (Annotation anno : annos) {
+      AnnotationMirror am =
+          AnnotationConverter.annotationToAnnotationMirror(anno, atypeFactory.getProcessingEnv());
+      result.addAnnotation(am);
+    }
+    if (result.getKind() == TypeKind.ARRAY) {
+      AnnotatedArrayType aat = (AnnotatedArrayType) result;
+      for (ATypeElement innerType : storageLocation.innerTypes.values()) {
+        updateAtmFromATypeElement(aat.getComponentType(), innerType);
+      }
+    }
+    if (result.getKind() == TypeKind.TYPEVAR) {
+      AnnotatedTypeVariable atv = (AnnotatedTypeVariable) result;
+      for (ATypeElement innerType : storageLocation.innerTypes.values()) {
+        updateAtmFromATypeElement(atv.getUpperBound(), innerType);
+      }
+    }
+  }
+
+  @Override
+  public void updateStorageLocationFromAtm(
+      AnnotatedTypeMirror newATM,
+      AnnotatedTypeMirror curATM,
+      ATypeElement typeToUpdate,
+      TypeUseLocation defLoc,
+      boolean ignoreIfAnnotated) {
+    updateTypeElementFromATM(typeToUpdate, 1, defLoc, newATM, curATM, ignoreIfAnnotated);
+  }
+
+  ///
+  /// Writing to a file
+  ///
+
+  // The prepare*ForWriting hooks are needed in addition to the postProcessClassTree hook because
+  // a scene may be modifed and written at any time, including before or after
+  // postProcessClassTree is called.
+
+  /**
+   * Side-effects the compilation unit annotations to make any desired changes before writing to a
+   * file.
+   *
+   * @param compilationUnitAnnos the compilation unit annotations to modify
+   */
+  public void prepareSceneForWriting(AScene compilationUnitAnnos) {
+    for (Map.Entry<String, AClass> classEntry : compilationUnitAnnos.classes.entrySet()) {
+      prepareClassForWriting(classEntry.getValue());
+    }
+  }
+
+  /**
+   * Side-effects the class annotations to make any desired changes before writing to a file.
+   *
+   * @param classAnnos the class annotations to modify
+   */
+  public void prepareClassForWriting(AClass classAnnos) {
+    for (Map.Entry<String, AMethod> methodEntry : classAnnos.methods.entrySet()) {
+      prepareMethodForWriting(methodEntry.getValue());
+    }
+  }
+
+  /**
+   * Side-effects the method or constructor annotations to make any desired changes before writing
+   * to a file.
+   *
+   * @param methodAnnos the method or constructor annotations to modify
+   */
+  public void prepareMethodForWriting(AMethod methodAnnos) {
+    atypeFactory.prepareMethodForWriting(methodAnnos);
+  }
+
+  @Override
+  public void writeResultsToFile(
+      WholeProgramInference.OutputFormat outputFormat, BaseTypeChecker checker) {
+    if (outputFormat == OutputFormat.AJAVA) {
+      throw new BugInCF("WholeProgramInferenceScenes used with format " + outputFormat);
+    }
+
+    for (String file : modifiedScenes) {
+      ASceneWrapper scene = scenes.get(file);
+      prepareSceneForWriting(scene.getAScene());
+    }
+
+    writeScenes(outputFormat, checker);
+  }
+
+  @Override
+  public void setFileModified(String path) {
+    modifiedScenes.add(path);
+  }
+
+  @Override
+  public void preprocessClassTree(ClassTree classTree) {
+    // This implementation does nothing.
+  }
+
+  /**
+   * Updates an {@link scenelib.annotations.el.ATypeElement} to have the annotations of an {@link
+   * org.checkerframework.framework.type.AnnotatedTypeMirror} passed as argument. Annotations in the
+   * original set that should be ignored (see {@link #shouldIgnore}) are not added to the resulting
+   * set. This method also checks if the AnnotatedTypeMirror has explicit annotations in source
+   * code, and if that is the case no annotations are added for that location.
+   *
+   * <p>This method removes from the ATypeElement all annotations supported by this object's
+   * AnnotatedTypeFactory before inserting new ones. It is assumed that every time this method is
+   * called, the AnnotatedTypeMirror has a better type estimate for the ATypeElement. Therefore, it
+   * is not a problem to remove all annotations before inserting the new annotations.
+   *
+   * @param typeToUpdate the ATypeElement that will be updated
+   * @param idx used to write annotations on compound types of an ATypeElement
+   * @param defLoc the location where the annotation will be added
+   * @param newATM the AnnotatedTypeMirror whose annotations will be added to the ATypeElement
+   * @param curATM used to check if the element which will be updated has explicit annotations in
+   *     source code
+   * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the
+   *     source code
+   */
+  private void updateTypeElementFromATM(
+      ATypeElement typeToUpdate,
+      int idx,
+      TypeUseLocation defLoc,
+      AnnotatedTypeMirror newATM,
+      AnnotatedTypeMirror curATM,
+      boolean ignoreIfAnnotated) {
+    // Clears only the annotations that are supported by the relevant AnnotatedTypeFactory.
+    // The others stay intact.
+    if (idx == 1) {
+      // This if avoids clearing the annotations multiple times in cases
+      // of type variables and compound types.
+      Set<Annotation> annosToRemove = getSupportedAnnosInSet(typeToUpdate.tlAnnotationsHere);
+      // This method may be called consecutive times for the same ATypeElement.  Each time it is
+      // called, the AnnotatedTypeMirror has a better type estimate for the ATypeElement. Therefore,
+      // it is not a problem to remove all annotations before inserting the new annotations.
+      typeToUpdate.tlAnnotationsHere.removeAll(annosToRemove);
+    }
+
+    // Only update the ATypeElement if there are no explicit annotations.
+    if (curATM.getExplicitAnnotations().isEmpty() || !ignoreIfAnnotated) {
+      for (AnnotationMirror am : newATM.getAnnotations()) {
+        addAnnotationsToATypeElement(
+            newATM, typeToUpdate, defLoc, am, curATM.hasEffectiveAnnotation(am));
+      }
+    } else if (curATM.getKind() == TypeKind.TYPEVAR) {
+      // getExplicitAnnotations will be non-empty for type vars whose bounds are explicitly
+      // annotated.  So instead, only insert the annotation if there is not primary annotation
+      // of the same hierarchy.  #shouldIgnore prevent annotations that are subtypes of type
+      // vars upper bound from being inserted.
+      for (AnnotationMirror am : newATM.getAnnotations()) {
+        if (curATM.getAnnotationInHierarchy(am) != null) {
+          // Don't insert if the type is already has a primary annotation
+          // in the same hierarchy.
+          break;
+        }
+        addAnnotationsToATypeElement(
+            newATM, typeToUpdate, defLoc, am, curATM.hasEffectiveAnnotation(am));
+      }
+    }
+
+    // Recursively update compound type and type variable type if they exist.
+    if (newATM.getKind() == TypeKind.ARRAY && curATM.getKind() == TypeKind.ARRAY) {
+      AnnotatedArrayType newAAT = (AnnotatedArrayType) newATM;
+      AnnotatedArrayType oldAAT = (AnnotatedArrayType) curATM;
+      updateTypeElementFromATM(
+          typeToUpdate.innerTypes.getVivify(
+              TypePathEntry.getTypePathEntryListFromBinary(Collections.nCopies(2 * idx, 0))),
+          idx + 1,
+          defLoc,
+          newAAT.getComponentType(),
+          oldAAT.getComponentType(),
+          ignoreIfAnnotated);
+    }
+  }
+
+  private void addAnnotationsToATypeElement(
+      AnnotatedTypeMirror newATM,
+      ATypeElement typeToUpdate,
+      TypeUseLocation defLoc,
+      AnnotationMirror am,
+      boolean isEffectiveAnnotation) {
+    Annotation anno = AnnotationConverter.annotationMirrorToAnnotation(am);
+    typeToUpdate.tlAnnotationsHere.add(anno);
+    if (isEffectiveAnnotation || shouldIgnore(am, defLoc, newATM)) {
+      // firstKey works as a unique identifier for each annotation
+      // that should not be inserted in source code
+      String firstKey = aTypeElementToString(typeToUpdate);
+      Pair<String, TypeUseLocation> key = Pair.of(firstKey, defLoc);
+      Set<String> annosIgnored = annosToIgnore.get(key);
+      if (annosIgnored == null) {
+        annosIgnored = new HashSet<>();
+        annosToIgnore.put(key, annosIgnored);
+      }
+      annosIgnored.add(anno.def().toString());
+    }
+  }
+
+  /**
+   * Returns a string representation of an ATypeElement, for use as part of a key in {@link
+   * AnnotationsInContexts}.
+   *
+   * @param aType an ATypeElement to convert to a string representation
+   * @return a string representation of the argument
+   */
+  public static String aTypeElementToString(ATypeElement aType) {
+    // return aType.description.toString() + aType.tlAnnotationsHere;
+    return aType.description.toString();
+  }
+
+  /**
+   * Maps the {@link #aTypeElementToString} representation of an ATypeElement and its
+   * TypeUseLocation to a set of names of annotations.
+   */
+  public static class AnnotationsInContexts
+      extends HashMap<Pair<String, TypeUseLocation>, Set<String>> {
+    private static final long serialVersionUID = 20200321L;
+  }
+
+  /**
+   * Returns the "flatname" of the class enclosing {@code localVariableNode}.
+   *
+   * @param localVariableNode the {@link LocalVariableNode}
+   * @return the "flatname" of the class enclosing {@code localVariableNode}
+   */
+  private static @BinaryName String getEnclosingClassName(LocalVariableNode localVariableNode) {
+    return ElementUtils.getBinaryName(
+        ElementUtils.enclosingTypeElement(localVariableNode.getElement()));
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceStorage.java
new file mode 100644
index 0000000..7befc4c
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceStorage.java
@@ -0,0 +1,196 @@
+package org.checkerframework.common.wholeprograminference;
+
+import com.sun.source.tree.ClassTree;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/**
+ * Stores annotations from whole-program inference. For a given location such as a field or method,
+ * an object can be obtained containing the inferred annotations for that object.
+ *
+ * <p>Also writes stored annotations to storage files. The specific format depends on the
+ * implementation.
+ *
+ * @param <T> the type used by the storage to store annotations. The methods {@link
+ *     #atmFromStorageLocation} and {@link #updateStorageLocationFromAtm} can be used to manipulate
+ *     a storage location.
+ */
+public interface WholeProgramInferenceStorage<T> {
+  /**
+   * Returns the file corresponding to the given element. This may side-effect the storage to load
+   * the file if it hasn't been read yet.
+   *
+   * @param elt an element
+   * @return the path to the file where inference results for the element will be written
+   */
+  public String getFileForElement(Element elt);
+
+  /**
+   * Given an ExecutableElement in a compilation unit that has already been read into storage,
+   * returns whether there exists a stored method matching {@code elt}.
+   *
+   * <p>An implementation is permitted to return false if {@code elt} represents a method that was
+   * synthetically added by javac, such as zero-argument constructors or valueOf(String) methods for
+   * enum types.
+   *
+   * @param methodElt a method or constructor Element
+   * @return true if the storage has a method corresponding to {@code elt}
+   */
+  public boolean hasStorageLocationForMethod(ExecutableElement methodElt);
+
+  /**
+   * Get the annotations for a formal parameter type.
+   *
+   * @param methodElt the method or constructor Element
+   * @param i the parameter index (0-based)
+   * @param paramATM the parameter type
+   * @param ve the parameter variable
+   * @param atypeFactory the type factory
+   * @return the annotations for a formal parameter type
+   */
+  public T getParameterAnnotations(
+      ExecutableElement methodElt,
+      int i,
+      AnnotatedTypeMirror paramATM,
+      VariableElement ve,
+      AnnotatedTypeFactory atypeFactory);
+
+  /**
+   * Get the annotations for the receiver type.
+   *
+   * @param methodElt the method or constructor Element
+   * @param paramATM the receiver type
+   * @param atypeFactory the type factory
+   * @return the annotations for the receiver type
+   */
+  public T getReceiverAnnotations(
+      ExecutableElement methodElt, AnnotatedTypeMirror paramATM, AnnotatedTypeFactory atypeFactory);
+
+  /**
+   * Get the annotations for the return type.
+   *
+   * @param methodElt the method or constructor Element
+   * @param atm the return type
+   * @param atypeFactory the type factory
+   * @return the annotations for the return type
+   */
+  public T getReturnAnnotations(
+      ExecutableElement methodElt, AnnotatedTypeMirror atm, AnnotatedTypeFactory atypeFactory);
+
+  /**
+   * Get the annotations for a field type.
+   *
+   * @param element the element for the field
+   * @param fieldName the simple field name
+   * @param lhsATM the field type
+   * @param atypeFactory the annotated type factory
+   * @return the annotations for a field type
+   */
+  public T getFieldAnnotations(
+      Element element,
+      String fieldName,
+      AnnotatedTypeMirror lhsATM,
+      AnnotatedTypeFactory atypeFactory);
+
+  // Fields are special because currently WPI computes preconditions for fields only, not for
+  // other expressions.
+  /**
+   * Returns the pre- or postcondition annotations for a field.
+   *
+   * @param preOrPost whether to get the precondition or postcondition
+   * @param methodElement the method
+   * @param fieldElement the field
+   * @param atypeFactory the type factory
+   * @return the pre- or postcondition annotations for a field
+   */
+  public T getPreOrPostconditionsForField(
+      Analysis.BeforeOrAfter preOrPost,
+      ExecutableElement methodElement,
+      VariableElement fieldElement,
+      AnnotatedTypeFactory atypeFactory);
+
+  /**
+   * Updates a method to add a declaration annotation.
+   *
+   * @param methodElt the method to annotate
+   * @param anno the declaration annotation to add to the method
+   * @return true if {@code anno} is a new declaration annotation for {@code methodElt}, false
+   *     otherwise
+   */
+  public boolean addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno);
+
+  /**
+   * Obtain the type from a storage location.
+   *
+   * @param typeMirror the underlying type for the result
+   * @param storageLocation the storage location from which to obtain annotations
+   * @return an annotated type mirror with underlying type {@code typeMirror} and annotations from
+   *     {@code storageLocation}
+   */
+  public AnnotatedTypeMirror atmFromStorageLocation(TypeMirror typeMirror, T storageLocation);
+
+  /**
+   * Updates a storage location to have the annotations of the given {@code AnnotatedTypeMirror}.
+   * Annotations in the original set that should be ignored are not added to the resulting set. If
+   * {@code ignoreIfAnnotated} is true, doesn't add annotations for locations with explicit
+   * annotations in source code.
+   *
+   * <p>This method removes from the storage location all annotations supported by the
+   * AnnotatedTypeFactory before inserting new ones. It is assumed that every time this method is
+   * called, the new {@code AnnotatedTypeMirror} has a better type estimate for the given location.
+   * Therefore, it is not a problem to remove all annotations before inserting the new annotations.
+   *
+   * <p>The {@code update*} methods in {@link WholeProgramInference} perform LUB. This one just does
+   * replacement. (Thus, the naming may be a bit confusing.)
+   *
+   * @param newATM the type whose annotations will be added to the {@code AnnotatedTypeMirror}
+   * @param curATM the annotations currently stored at the location, used to check if the element
+   *     that will be updated has explicit annotations in source code
+   * @param storageLocationToUpdate the storage location that will be updated
+   * @param defLoc the location where the annotation will be added
+   * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the
+   *     source code
+   */
+  public void updateStorageLocationFromAtm(
+      AnnotatedTypeMirror newATM,
+      AnnotatedTypeMirror curATM,
+      T storageLocationToUpdate,
+      TypeUseLocation defLoc,
+      boolean ignoreIfAnnotated);
+
+  /**
+   * Writes the inferred results to a file. Ideally, it should be called at the end of the
+   * type-checking process. In practice, it is called after each class, because we don't know which
+   * class will be the last one in the type-checking process.
+   *
+   * @param outputFormat the file format in which to write the results
+   * @param checker the checker from which this method is called, for naming stub files
+   */
+  public void writeResultsToFile(
+      WholeProgramInference.OutputFormat outputFormat, BaseTypeChecker checker);
+
+  /**
+   * Indicates that inferred annotations for the file at {@code path} have changed since last
+   * written. This causes output files for {@code path} to be written out next time {@link
+   * #writeResultsToFile} is called.
+   *
+   * @param path path to the file with annotations that have been modified
+   */
+  public void setFileModified(String path);
+
+  /**
+   * Performs any preparation required for inference on the elements of a class. Should be called on
+   * each top-level class declaration in a compilation unit before processing it.
+   *
+   * @param classTree the class to preprocess
+   */
+  void preprocessClassTree(ClassTree classTree);
+}
diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/package-info.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/package-info.java
new file mode 100644
index 0000000..8badc19
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * This package implements whole-program inference, an interprocedural inference that infers types
+ * and inserts them into a program.
+ *
+ * @checker_framework.manual #whole-program-inference Whole-program inference
+ */
+package org.checkerframework.common.wholeprograminference;
diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java
new file mode 100644
index 0000000..efdccc2
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java
@@ -0,0 +1,250 @@
+package org.checkerframework.common.wholeprograminference.scenelib;
+
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.wholeprograminference.AnnotationConverter;
+import org.checkerframework.common.wholeprograminference.SceneToStubWriter;
+import org.checkerframework.common.wholeprograminference.WholeProgramInference.OutputFormat;
+import org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesStorage;
+import org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesStorage.AnnotationsInContexts;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.UserError;
+import org.plumelib.util.CollectionsPlume;
+import scenelib.annotations.Annotation;
+import scenelib.annotations.el.AClass;
+import scenelib.annotations.el.AField;
+import scenelib.annotations.el.AMethod;
+import scenelib.annotations.el.AScene;
+import scenelib.annotations.el.ATypeElement;
+import scenelib.annotations.el.DefException;
+import scenelib.annotations.io.IndexFileWriter;
+
+/**
+ * scene-lib (from the Annotation File Utilities) doesn't provide enough information to usefully
+ * print stub files: it lacks information about what is and is not an enum, about the base types of
+ * variables, and about formal parameter names.
+ *
+ * <p>This class wraps AScene but provides access to that missing information. This allows us to
+ * preserve the code that generates .jaif files, while allowing us to sanely and safely keep the
+ * information we need to generate stubs.
+ *
+ * <p>This class would be better as a subclass of AScene.
+ */
+public class ASceneWrapper {
+
+  /** The AScene being wrapped. */
+  private final AScene theScene;
+
+  /**
+   * Constructor. Pass the AScene to wrap.
+   *
+   * @param theScene the scene to wrap
+   */
+  public ASceneWrapper(AScene theScene) {
+    this.theScene = theScene;
+  }
+
+  /**
+   * Removes the specified annotations from an AScene.
+   *
+   * @param scene the scene from which to remove annotations
+   * @param annosToRemove annotations that should not be added to .jaif or stub files
+   */
+  private void removeAnnosFromScene(AScene scene, AnnotationsInContexts annosToRemove) {
+    for (AClass aclass : scene.classes.values()) {
+      for (AField field : aclass.fields.values()) {
+        removeAnnosFromATypeElement(field.type, TypeUseLocation.FIELD, annosToRemove);
+      }
+      for (AMethod method : aclass.methods.values()) {
+        removeAnnosFromATypeElement(method.returnType, TypeUseLocation.RETURN, annosToRemove);
+        removeAnnosFromATypeElement(method.receiver.type, TypeUseLocation.RECEIVER, annosToRemove);
+        for (AField param : method.parameters.values()) {
+          removeAnnosFromATypeElement(param.type, TypeUseLocation.PARAMETER, annosToRemove);
+        }
+      }
+    }
+  }
+
+  /**
+   * Removes the specified annotations from an ATypeElement.
+   *
+   * @param typeElt the type element from which to remove annotations
+   * @param loc the location where typeElt is used
+   * @param annosToRemove annotations that should not be added to .jaif or stub files
+   */
+  private void removeAnnosFromATypeElement(
+      ATypeElement typeElt, TypeUseLocation loc, AnnotationsInContexts annosToRemove) {
+    String annosToRemoveKey = WholeProgramInferenceScenesStorage.aTypeElementToString(typeElt);
+    Set<String> annosToRemoveForLocation = annosToRemove.get(Pair.of(annosToRemoveKey, loc));
+    if (annosToRemoveForLocation != null) {
+      Set<Annotation> annosToRemoveHere = new HashSet<>();
+      for (Annotation anno : typeElt.tlAnnotationsHere) {
+        if (annosToRemoveForLocation.contains(anno.def().toString())) {
+          annosToRemoveHere.add(anno);
+        }
+      }
+      typeElt.tlAnnotationsHere.removeAll(annosToRemoveHere);
+    }
+
+    // Recursively remove annotations from inner types
+    for (ATypeElement innerType : typeElt.innerTypes.values()) {
+      removeAnnosFromATypeElement(innerType, loc, annosToRemove);
+    }
+  }
+
+  /**
+   * Write the scene wrapped by this object to a file at the given path.
+   *
+   * @param jaifPath the path of the file to be written, but ending in ".jaif". If {@code
+   *     outputformat} is not {@code JAIF}, the path will be modified to match.
+   * @param annosToIgnore which annotations should be ignored in which contexts
+   * @param outputFormat the output format to use
+   * @param checker the checker from which this method is called, for naming stub files
+   */
+  public void writeToFile(
+      String jaifPath,
+      AnnotationsInContexts annosToIgnore,
+      OutputFormat outputFormat,
+      BaseTypeChecker checker) {
+    assert jaifPath.endsWith(".jaif");
+    AScene scene = theScene.clone();
+    removeAnnosFromScene(scene, annosToIgnore);
+    scene.prune();
+    String filepath;
+    switch (outputFormat) {
+      case JAIF:
+        filepath = jaifPath;
+        break;
+      case STUB:
+        String astubWithChecker = "-" + checker.getClass().getCanonicalName() + ".astub";
+        filepath = jaifPath.replace(".jaif", astubWithChecker);
+        break;
+      default:
+        throw new BugInCF("Unhandled outputFormat " + outputFormat);
+    }
+    new File(filepath).delete();
+    // Only write non-empty scenes into files.
+    if (!scene.isEmpty()) {
+      try {
+        switch (outputFormat) {
+          case STUB:
+            // For stub files, pass in the checker to compute contracts on the fly; precomputing
+            // yields incorrect annotations, most likely due to nested classes.
+            SceneToStubWriter.write(this, filepath, checker);
+            break;
+          case JAIF:
+            // For .jaif files, precompute contracts because the Annotation File Utilities knows
+            // nothing about (and cannot depend on) the Checker Framework.
+            for (Map.Entry<String, AClass> classEntry : scene.classes.entrySet()) {
+              AClass aClass = classEntry.getValue();
+              for (Map.Entry<String, AMethod> methodEntry : aClass.getMethods().entrySet()) {
+                AMethod aMethod = methodEntry.getValue();
+                List<AnnotationMirror> contractAnnotationMirrors =
+                    checker.getTypeFactory().getContractAnnotations(aMethod);
+                List<Annotation> contractAnnotations =
+                    CollectionsPlume.mapList(
+                        AnnotationConverter::annotationMirrorToAnnotation,
+                        contractAnnotationMirrors);
+                aMethod.contracts = contractAnnotations;
+              }
+            }
+            IndexFileWriter.write(scene, new FileWriter(filepath));
+            break;
+          default:
+            throw new BugInCF("Unhandled outputFormat " + outputFormat);
+        }
+      } catch (IOException e) {
+        throw new UserError("Problem while writing %s: %s", filepath, e.getMessage());
+      } catch (DefException e) {
+        throw new BugInCF(e);
+      }
+    }
+  }
+
+  /**
+   * Updates the symbol information stored in AClass for the given class. May be called multiple
+   * times (and needs to be if the second parameter was null the first time it was called; only some
+   * calls provide the symbol information).
+   *
+   * @param aClass the class representation in which the symbol information is to be updated
+   * @param classSymbol the source of the symbol information; may be null, in which case this method
+   *     does nothing
+   */
+  public void updateSymbolInformation(AClass aClass, @Nullable ClassSymbol classSymbol) {
+    if (classSymbol == null) {
+      return;
+    }
+    if (classSymbol.isEnum()) {
+      List<VariableElement> enumConstants = new ArrayList<>();
+      for (Element e : ((TypeElement) classSymbol).getEnclosedElements()) {
+        if (e.getKind() == ElementKind.ENUM_CONSTANT) {
+          enumConstants.add((VariableElement) e);
+        }
+      }
+      if (!aClass.isEnum(classSymbol.getSimpleName().toString())) {
+        aClass.setEnumConstants(enumConstants);
+      } else {
+        // Verify that the existing value is consistent.
+        List<VariableElement> existingEnumConstants = aClass.getEnumConstants();
+        if (existingEnumConstants.size() != enumConstants.size()) {
+          throw new BugInCF(
+              "inconsistent enum constants in WPI for class "
+                  + classSymbol.getQualifiedName().toString());
+        }
+        for (int i = 0; i < enumConstants.size(); i++) {
+          if (!existingEnumConstants.get(i).equals(enumConstants.get(i))) {
+            throw new BugInCF(
+                "inconsistent enum constants in WPI for class "
+                    + classSymbol.getQualifiedName().toString());
+          }
+        }
+      }
+    }
+
+    ClassSymbol outerClass = classSymbol;
+    ClassSymbol previous = classSymbol;
+    do {
+      if (outerClass.isEnum()) {
+        aClass.markAsEnum(outerClass.getSimpleName().toString());
+      }
+      Element element = classSymbol.getEnclosingElement();
+      if (element == null || element.getKind() == ElementKind.PACKAGE) {
+        break;
+      }
+      TypeElement t = ElementUtils.enclosingTypeElement(element);
+      previous = outerClass;
+      outerClass = (ClassSymbol) t;
+      // It is necessary to check that previous isn't equal to outer class because
+      // otherwise this loop will sometimes run forever.
+    } while (outerClass != null && !previous.equals(outerClass));
+
+    aClass.setTypeElement(classSymbol);
+  }
+
+  /**
+   * Avoid using this if possible; use the other methods of this class unless you absolutely need an
+   * AScene.
+   *
+   * @return the AScene representation of this
+   */
+  public AScene getAScene() {
+    return theScene;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationEqualityVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationEqualityVisitor.java
new file mode 100644
index 0000000..abccefa
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationEqualityVisitor.java
@@ -0,0 +1,89 @@
+package org.checkerframework.framework.ajava;
+
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.comments.Comment;
+import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Given two ASTs representing the same Java file that may differ in annotations, tests if they have
+ * the same annotations.
+ *
+ * <p>To use this class, have the first AST node accept the visitor and pass the second AST node as
+ * the second argument. Then, check {@link #getAnnotationsMatch}.
+ */
+public class AnnotationEqualityVisitor extends DoubleJavaParserVisitor {
+  /** Whether or not a node with mismatched annotations has been seen. */
+  private boolean annotationsMatch;
+  /** If a node with mismatched annotations has been seen, stores the node from the first AST. */
+  private @MonotonicNonNull NodeWithAnnotations<?> mismatchedNode1;
+  /** If a node with mismatched annotations has been seen, stores the node from the second AST. */
+  private @MonotonicNonNull NodeWithAnnotations<?> mismatchedNode2;
+
+  /** Constructs an {@code AnnotationEqualityVisitor}. */
+  public AnnotationEqualityVisitor() {
+    annotationsMatch = true;
+    mismatchedNode1 = null;
+    mismatchedNode2 = null;
+  }
+
+  /**
+   * Returns whether a visited pair of nodes differed in annotations.
+   *
+   * @return true if some visited pair of nodes differed in annotations
+   */
+  public boolean getAnnotationsMatch() {
+    return annotationsMatch;
+  }
+
+  /**
+   * If a visited pair of nodes has had mismatched annotations, returns the node from the first AST
+   * where annotations differed, or null otherwise.
+   *
+   * @return the node from the first AST with differing annotations or null
+   */
+  public @Nullable NodeWithAnnotations<?> getMismatchedNode1() {
+    return mismatchedNode1;
+  }
+
+  /**
+   * If a visited pair of nodes has had mismatched annotations, returns the node from the second AST
+   * where annotations differed, or null otherwise.
+   *
+   * @return the node from the second AST with differing annotations or null
+   */
+  public @Nullable NodeWithAnnotations<?> getMismatchedNode2() {
+    return mismatchedNode2;
+  }
+
+  @Override
+  public <T extends Node> void defaultAction(T node1, T node2) {
+    if (!(node1 instanceof NodeWithAnnotations<?>) || !(node2 instanceof NodeWithAnnotations<?>)) {
+      return;
+    }
+
+    // Comparing annotations with "equals" considers comments in the AST attached to the
+    // annotations. These should be ignored because two valid ASTs for the same file may differ in
+    // where comments appear, or whether they appear at all. So, to check if two nodes have the same
+    // annotations we create copies with all comments removed and compare their lists of annotations
+    // directly.
+    Node node1Copy = node1.clone();
+    Node node2Copy = node2.clone();
+
+    for (Comment comment : node1Copy.getAllContainedComments()) {
+      comment.remove();
+    }
+    for (Comment comment : node2Copy.getAllContainedComments()) {
+      comment.remove();
+    }
+
+    if (!((NodeWithAnnotations<?>) node1Copy)
+        .getAnnotations()
+        .equals(((NodeWithAnnotations<?>) node2Copy).getAnnotations())) {
+      annotationsMatch = false;
+      mismatchedNode1 = (NodeWithAnnotations<?>) node1;
+      mismatchedNode2 = (NodeWithAnnotations<?>) node2;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationFileStore.java b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationFileStore.java
new file mode 100644
index 0000000..cbe63c6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationFileStore.java
@@ -0,0 +1,78 @@
+package org.checkerframework.framework.ajava;
+
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.body.TypeDeclaration;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.framework.util.JavaParserUtil;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Stores a collection of annotation files. Given a type name, can return a list of paths to stored
+ * annotation files corresponding to that type name.
+ */
+public class AnnotationFileStore {
+  /**
+   * Mapping from a fully qualified class name to the paths to annotation files that contain that
+   * type.
+   */
+  private Map<String, List<String>> annotationFiles;
+
+  /** Constructs an {@code AnnotationFileStore}. */
+  public AnnotationFileStore() {
+    annotationFiles = new HashMap<>();
+  }
+
+  /**
+   * If {@code location} is a file, stores it in this as an annotation file. If {@code location} is
+   * a directory, stores all annotation files contained in it.
+   *
+   * @param location an annotation file or a directory containing annotation files
+   */
+  public void addFileOrDirectory(File location) {
+    if (location.isDirectory()) {
+      for (File child : location.listFiles()) {
+        addFileOrDirectory(child);
+      }
+
+      return;
+    }
+
+    if (location.isFile() && location.getName().endsWith(".ajava")) {
+      try {
+        CompilationUnit root = JavaParserUtil.parseCompilationUnit(location);
+        for (TypeDeclaration<?> type : root.getTypes()) {
+          String name = JavaParserUtil.getFullyQualifiedName(type, root);
+
+          if (!annotationFiles.containsKey(name)) {
+            annotationFiles.put(name, new ArrayList<>());
+          }
+
+          annotationFiles.get(name).add(location.getPath());
+        }
+      } catch (FileNotFoundException e) {
+        throw new BugInCF("Unable to open annotation file: " + location.getPath(), e);
+      }
+    }
+  }
+
+  /**
+   * Given a fully qualified type name, returns a List of paths to annotation files containing
+   * annotations for the type.
+   *
+   * @param typeName fully qualified name of a type
+   * @return a list of paths to annotation files with annotations for {@code typeName}
+   */
+  public List<String> getAnnotationFileForType(String typeName) {
+    if (!annotationFiles.containsKey(typeName)) {
+      return Collections.emptyList();
+    }
+
+    return Collections.unmodifiableList(annotationFiles.get(typeName));
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java
new file mode 100644
index 0000000..82edb68
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java
@@ -0,0 +1,243 @@
+package org.checkerframework.framework.ajava;
+
+import com.github.javaparser.ParseProblemException;
+import com.github.javaparser.StaticJavaParser;
+import com.github.javaparser.ast.NodeList;
+import com.github.javaparser.ast.expr.AnnotationExpr;
+import com.github.javaparser.ast.expr.ArrayInitializerExpr;
+import com.github.javaparser.ast.expr.BooleanLiteralExpr;
+import com.github.javaparser.ast.expr.CharLiteralExpr;
+import com.github.javaparser.ast.expr.ClassExpr;
+import com.github.javaparser.ast.expr.DoubleLiteralExpr;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.FieldAccessExpr;
+import com.github.javaparser.ast.expr.IntegerLiteralExpr;
+import com.github.javaparser.ast.expr.LongLiteralExpr;
+import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
+import com.github.javaparser.ast.expr.MemberValuePair;
+import com.github.javaparser.ast.expr.Name;
+import com.github.javaparser.ast.expr.NameExpr;
+import com.github.javaparser.ast.expr.NormalAnnotationExpr;
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
+import com.github.javaparser.ast.expr.StringLiteralExpr;
+import com.github.javaparser.ast.expr.UnaryExpr;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.AnnotationValueVisitor;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Methods for converting a {@code AnnotationMirror} into a JavaParser {@code AnnotationExpr},
+ * namely {@code annotationMirrorToAnnotationExpr}.
+ */
+public class AnnotationMirrorToAnnotationExprConversion {
+  /**
+   * Converts an AnnotationMirror into a JavaParser {@code AnnotationExpr}.
+   *
+   * @param annotation the annotation to convert
+   * @return a JavaParser {@code AnnotationExpr} representing the same annotation with the same
+   *     element values. The converted annotation will contain the annotation's fully qualified
+   *     name.
+   */
+  public static AnnotationExpr annotationMirrorToAnnotationExpr(AnnotationMirror annotation) {
+    Map<? extends ExecutableElement, ? extends AnnotationValue> values =
+        annotation.getElementValues();
+    Name name = createQualifiedName(AnnotationUtils.annotationName(annotation));
+    if (values.isEmpty()) {
+      return new MarkerAnnotationExpr(name);
+    }
+
+    NodeList<MemberValuePair> convertedValues = convertAnnotationValues(values);
+    if (convertedValues.size() == 1
+        && convertedValues.get(0).getName().asString().equals("value")) {
+      return new SingleMemberAnnotationExpr(name, convertedValues.get(0).getValue());
+    }
+
+    return new NormalAnnotationExpr(name, convertedValues);
+  }
+
+  /**
+   * Converts a mapping of (annotation element &rarr; value) into a list of key-value pairs
+   * containing the JavaParser representations of the same values.
+   *
+   * @param values mapping of element values from an {@code AnnotationMirror}
+   * @return a list of the key-value pairs in {@code values} converted to their JavaParser
+   *     representations
+   */
+  private static NodeList<MemberValuePair> convertAnnotationValues(
+      Map<? extends ExecutableElement, ? extends AnnotationValue> values) {
+    NodeList<MemberValuePair> convertedValues = new NodeList<>();
+    AnnotationValueConverterVisitor converter = new AnnotationValueConverterVisitor();
+    for (ExecutableElement valueName : values.keySet()) {
+      AnnotationValue value = values.get(valueName);
+      convertedValues.add(
+          new MemberValuePair(valueName.getSimpleName().toString(), value.accept(converter, null)));
+    }
+
+    return convertedValues;
+  }
+
+  /**
+   * Given a fully qualified name, creates a JavaParser {@code Name} structure representing the same
+   * name.
+   *
+   * @param name the fully qualified name to convert
+   * @return a JavaParser {@code Name} holding {@code name}
+   */
+  private static Name createQualifiedName(String name) {
+    String[] components = name.split("\\.");
+    Name result = new Name(components[0]);
+    for (int i = 1; i < components.length; i++) {
+      result = new Name(result, components[i]);
+    }
+
+    return result;
+  }
+
+  /**
+   * A visitor that converts an annotation value from an {@code AnnotationMirror} to a JavaParser
+   * node that can appear in an {@code AnnotationExpr}.
+   */
+  private static class AnnotationValueConverterVisitor
+      implements AnnotationValueVisitor<Expression, Void> {
+    @Override
+    public Expression visit(AnnotationValue value, Void p) {
+      // This is called only if the value couldn't be dispatched to any known type, which
+      // should never happen.
+      throw new BugInCF("Unknown annotation value type: " + value);
+    }
+
+    @Override
+    public Expression visitAnnotation(AnnotationMirror value, Void p) {
+      return AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr(value);
+    }
+
+    @Override
+    public Expression visitArray(List<? extends AnnotationValue> value, Void p) {
+      NodeList<Expression> valueExpressions = new NodeList<>();
+      for (AnnotationValue arrayValue : value) {
+        valueExpressions.add(arrayValue.accept(this, null));
+      }
+
+      return new ArrayInitializerExpr(valueExpressions);
+    }
+
+    @Override
+    public Expression visitBoolean(boolean value, Void p) {
+      return new BooleanLiteralExpr(value);
+    }
+
+    @Override
+    public Expression visitByte(byte value, Void p) {
+      // Annotation byte values are automatically cast to the correct type, so using an
+      // integer literal here works.
+      return toIntegerLiteralExpr(value);
+    }
+
+    @Override
+    public Expression visitChar(char value, Void p) {
+      return new CharLiteralExpr(value);
+    }
+
+    @Override
+    public Expression visitDouble(double value, Void p) {
+      return new DoubleLiteralExpr(value);
+    }
+
+    @Override
+    public Expression visitEnumConstant(VariableElement value, Void p) {
+      // The enclosing element of an enum constant is the enum type itself.
+      TypeElement enumElt = (TypeElement) value.getEnclosingElement();
+      String[] components = enumElt.getQualifiedName().toString().split("\\.");
+      Expression enumName = new NameExpr(components[0]);
+      for (int i = 1; i < components.length; i++) {
+        enumName = new FieldAccessExpr(enumName, components[i]);
+      }
+
+      return new FieldAccessExpr(enumName, value.getSimpleName().toString());
+    }
+
+    @Override
+    public Expression visitFloat(float value, Void p) {
+      return new DoubleLiteralExpr(value + "f");
+    }
+
+    @Override
+    public Expression visitInt(int value, Void p) {
+      return toIntegerLiteralExpr(value);
+    }
+
+    @Override
+    public Expression visitLong(long value, Void p) {
+      if (value < 0) {
+        return new UnaryExpr(new LongLiteralExpr(Long.toString(-value)), UnaryExpr.Operator.MINUS);
+      }
+
+      return new LongLiteralExpr(Long.toString(value));
+    }
+
+    @Override
+    public Expression visitShort(short value, Void p) {
+      // Annotation short values are automatically cast to the correct type, so using an
+      // integer literal here works.
+      return toIntegerLiteralExpr(value);
+    }
+
+    @Override
+    public Expression visitString(String value, Void p) {
+      return new StringLiteralExpr(value);
+    }
+
+    @Override
+    public Expression visitType(TypeMirror value, Void p) {
+      if (value.getKind() != TypeKind.DECLARED) {
+        throw new BugInCF("Unexpected type for class expression: " + value);
+      }
+
+      DeclaredType type = (DeclaredType) value;
+      ClassOrInterfaceType parsedType;
+      try {
+        parsedType = StaticJavaParser.parseClassOrInterfaceType(TypesUtils.getQualifiedName(type));
+      } catch (ParseProblemException e) {
+        throw new BugInCF("Invalid class or interface name: " + value, e);
+      }
+
+      return new ClassExpr(parsedType);
+    }
+
+    @Override
+    public @Nullable Expression visitUnknown(AnnotationValue value, Void p) {
+      return null;
+    }
+
+    /**
+     * Creates a JavaParser expression node representing a literal with the given value.
+     *
+     * <p>JavaParser represents a negative literal with a {@code UnaryExpr} containing a {@code
+     * IntegerLiteralExpr}, so this method won't necessarily return an {@code IntegerLiteralExpr}.
+     *
+     * @param value the value for the literal
+     * @return a JavaParser expression representing {@code value}
+     */
+    private Expression toIntegerLiteralExpr(int value) {
+      if (value < 0) {
+        return new UnaryExpr(
+            new IntegerLiteralExpr(Integer.toString(-value)), UnaryExpr.Operator.MINUS);
+      }
+
+      return new IntegerLiteralExpr(Integer.toString(value));
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationTransferVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationTransferVisitor.java
new file mode 100644
index 0000000..118697e
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationTransferVisitor.java
@@ -0,0 +1,81 @@
+package org.checkerframework.framework.ajava;
+
+import com.github.javaparser.ast.NodeList;
+import com.github.javaparser.ast.expr.AnnotationExpr;
+import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations;
+import com.github.javaparser.ast.type.ArrayType;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import com.github.javaparser.ast.type.PrimitiveType;
+import com.github.javaparser.ast.type.Type;
+import com.github.javaparser.ast.type.TypeParameter;
+import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+
+/**
+ * A visitor that adds all annotations from a {@code AnnotatedTypeMirror} to the corresponding
+ * JavaParser type, including nested types like array components.
+ *
+ * <p>The {@code AnnotatedTypeMirror} is passed as the secondary parameter to the visit methods.
+ */
+public class AnnotationTransferVisitor extends VoidVisitorAdapter<AnnotatedTypeMirror> {
+  @Override
+  public void visit(ArrayType target, AnnotatedTypeMirror type) {
+    target.getComponentType().accept(this, ((AnnotatedArrayType) type).getComponentType());
+    transferAnnotations(type, target);
+  }
+
+  @Override
+  public void visit(ClassOrInterfaceType target, AnnotatedTypeMirror type) {
+    if (type.getKind() == TypeKind.DECLARED) {
+      AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type;
+      if (target.getTypeArguments().isPresent()) {
+        NodeList<Type> types = target.getTypeArguments().get();
+        for (int i = 0; i < types.size(); i++) {
+          types.get(i).accept(this, declaredType.getTypeArguments().get(i));
+        }
+      }
+    }
+
+    transferAnnotations(type, target);
+  }
+
+  @Override
+  public void visit(PrimitiveType target, AnnotatedTypeMirror type) {
+    transferAnnotations(type, target);
+  }
+
+  @Override
+  public void visit(TypeParameter target, AnnotatedTypeMirror type) {
+    AnnotatedTypeVariable annotatedTypeVar = (AnnotatedTypeVariable) type;
+    NodeList<ClassOrInterfaceType> bounds = target.getTypeBound();
+    if (bounds.size() == 1) {
+      bounds.get(0).accept(this, annotatedTypeVar.getUpperBound());
+    }
+  }
+
+  /**
+   * Transfers annotations from {@code annotatedType} to {@code target}. Does nothing if {@code
+   * annotatedType} is null.
+   *
+   * @param annotatedType type with annotations to transfer
+   * @param target JavaParser node representing the type to transfer annotations to
+   */
+  private void transferAnnotations(
+      @Nullable AnnotatedTypeMirror annotatedType, NodeWithAnnotations<?> target) {
+    if (annotatedType == null) {
+      return;
+    }
+
+    for (AnnotationMirror annotation : annotatedType.getAnnotations()) {
+      AnnotationExpr convertedAnnotation =
+          AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr(annotation);
+      target.addAnnotation(convertedAnnotation);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/DefaultJointVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/DefaultJointVisitor.java
new file mode 100644
index 0000000..d66af21
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/ajava/DefaultJointVisitor.java
@@ -0,0 +1,387 @@
+package org.checkerframework.framework.ajava;
+
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.ImportDeclaration;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.PackageDeclaration;
+import com.github.javaparser.ast.body.AnnotationDeclaration;
+import com.github.javaparser.ast.body.AnnotationMemberDeclaration;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.ConstructorDeclaration;
+import com.github.javaparser.ast.body.EnumConstantDeclaration;
+import com.github.javaparser.ast.body.EnumDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.Parameter;
+import com.github.javaparser.ast.body.ReceiverParameter;
+import com.github.javaparser.ast.body.VariableDeclarator;
+import com.github.javaparser.ast.expr.ArrayAccessExpr;
+import com.github.javaparser.ast.expr.AssignExpr;
+import com.github.javaparser.ast.expr.BinaryExpr;
+import com.github.javaparser.ast.expr.CastExpr;
+import com.github.javaparser.ast.expr.ClassExpr;
+import com.github.javaparser.ast.expr.ConditionalExpr;
+import com.github.javaparser.ast.expr.EnclosedExpr;
+import com.github.javaparser.ast.expr.FieldAccessExpr;
+import com.github.javaparser.ast.expr.InstanceOfExpr;
+import com.github.javaparser.ast.expr.LambdaExpr;
+import com.github.javaparser.ast.expr.LiteralExpr;
+import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import com.github.javaparser.ast.expr.MethodReferenceExpr;
+import com.github.javaparser.ast.expr.Name;
+import com.github.javaparser.ast.expr.NameExpr;
+import com.github.javaparser.ast.expr.NormalAnnotationExpr;
+import com.github.javaparser.ast.expr.ObjectCreationExpr;
+import com.github.javaparser.ast.expr.SimpleName;
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
+import com.github.javaparser.ast.expr.SuperExpr;
+import com.github.javaparser.ast.expr.ThisExpr;
+import com.github.javaparser.ast.expr.UnaryExpr;
+import com.github.javaparser.ast.modules.ModuleDeclaration;
+import com.github.javaparser.ast.modules.ModuleExportsDirective;
+import com.github.javaparser.ast.modules.ModuleOpensDirective;
+import com.github.javaparser.ast.modules.ModuleProvidesDirective;
+import com.github.javaparser.ast.modules.ModuleRequiresDirective;
+import com.github.javaparser.ast.modules.ModuleUsesDirective;
+import com.github.javaparser.ast.stmt.AssertStmt;
+import com.github.javaparser.ast.stmt.BlockStmt;
+import com.github.javaparser.ast.stmt.BreakStmt;
+import com.github.javaparser.ast.stmt.CatchClause;
+import com.github.javaparser.ast.stmt.ContinueStmt;
+import com.github.javaparser.ast.stmt.DoStmt;
+import com.github.javaparser.ast.stmt.EmptyStmt;
+import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
+import com.github.javaparser.ast.stmt.ExpressionStmt;
+import com.github.javaparser.ast.stmt.ForEachStmt;
+import com.github.javaparser.ast.stmt.ForStmt;
+import com.github.javaparser.ast.stmt.IfStmt;
+import com.github.javaparser.ast.stmt.LabeledStmt;
+import com.github.javaparser.ast.stmt.ReturnStmt;
+import com.github.javaparser.ast.stmt.SwitchEntry;
+import com.github.javaparser.ast.stmt.SwitchStmt;
+import com.github.javaparser.ast.stmt.SynchronizedStmt;
+import com.github.javaparser.ast.stmt.ThrowStmt;
+import com.github.javaparser.ast.stmt.TryStmt;
+import com.github.javaparser.ast.stmt.WhileStmt;
+import com.github.javaparser.ast.type.ArrayType;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import com.github.javaparser.ast.type.IntersectionType;
+import com.github.javaparser.ast.type.PrimitiveType;
+import com.github.javaparser.ast.type.TypeParameter;
+import com.github.javaparser.ast.type.UnionType;
+import com.github.javaparser.ast.type.VoidType;
+import com.github.javaparser.ast.type.WildcardType;
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.AssertTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.BreakTree;
+import com.sun.source.tree.CaseTree;
+import com.sun.source.tree.CatchTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ContinueTree;
+import com.sun.source.tree.DoWhileLoopTree;
+import com.sun.source.tree.EmptyStatementTree;
+import com.sun.source.tree.EnhancedForLoopTree;
+import com.sun.source.tree.ExportsTree;
+import com.sun.source.tree.ExpressionStatementTree;
+import com.sun.source.tree.ForLoopTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.IntersectionTypeTree;
+import com.sun.source.tree.LabeledStatementTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModuleTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.OpensTree;
+import com.sun.source.tree.PackageTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.PrimitiveTypeTree;
+import com.sun.source.tree.ProvidesTree;
+import com.sun.source.tree.RequiresTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.SwitchTree;
+import com.sun.source.tree.SynchronizedTree;
+import com.sun.source.tree.ThrowTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TryTree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.TypeParameterTree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.UnionTypeTree;
+import com.sun.source.tree.UsesTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.tree.WhileLoopTree;
+import com.sun.source.tree.WildcardTree;
+
+/**
+ * An implementation of JointJavacJavaParserVisitor where {@code process} methods do nothing. To use
+ * this class, extend it and override a {@code process} method.
+ */
+public class DefaultJointVisitor extends JointJavacJavaParserVisitor {
+  @Override
+  public void processAnnotation(AnnotationTree javacTree, NormalAnnotationExpr javaParserNode) {}
+
+  @Override
+  public void processAnnotation(AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode) {}
+
+  @Override
+  public void processAnnotation(
+      AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode) {}
+
+  @Override
+  public void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) {}
+
+  @Override
+  public void processArrayAccess(ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode) {}
+
+  @Override
+  public void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode) {}
+
+  @Override
+  public void processAssert(AssertTree javacTree, AssertStmt javaParserNode) {}
+
+  @Override
+  public void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode) {}
+
+  @Override
+  public void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode) {}
+
+  @Override
+  public void processBlock(BlockTree javacTree, BlockStmt javaParserNode) {}
+
+  @Override
+  public void processBreak(BreakTree javacTree, BreakStmt javaParserNode) {}
+
+  @Override
+  public void processCase(CaseTree javacTree, SwitchEntry javaParserNode) {}
+
+  @Override
+  public void processCatch(CatchTree javacTree, CatchClause javaParserNode) {}
+
+  @Override
+  public void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode) {}
+
+  @Override
+  public void processClass(ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) {}
+
+  @Override
+  public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) {}
+
+  @Override
+  public void processCompilationUnit(
+      CompilationUnitTree javacTree, CompilationUnit javaParserNode) {}
+
+  @Override
+  public void processConditionalExpression(
+      ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode) {}
+
+  @Override
+  public void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode) {}
+
+  @Override
+  public void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode) {}
+
+  @Override
+  public void processEmptyStatement(EmptyStatementTree javacTree, EmptyStmt javaParserNode) {}
+
+  @Override
+  public void processEnhancedForLoop(EnhancedForLoopTree javacTree, ForEachStmt javaParserNode) {}
+
+  @Override
+  public void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode) {}
+
+  @Override
+  public void processExpressionStatemen(
+      ExpressionStatementTree javacTree, ExpressionStmt javaParserNode) {}
+
+  @Override
+  public void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode) {}
+
+  @Override
+  public void processIdentifier(IdentifierTree javacTree, ClassOrInterfaceType javaParserNode) {}
+
+  @Override
+  public void processIdentifier(IdentifierTree javacTree, Name javaParserNode) {}
+
+  @Override
+  public void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode) {}
+
+  @Override
+  public void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode) {}
+
+  @Override
+  public void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode) {}
+
+  @Override
+  public void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode) {}
+
+  @Override
+  public void processIf(IfTree javacTree, IfStmt javaParserNode) {}
+
+  @Override
+  public void processImport(ImportTree javacTree, ImportDeclaration javaParserNode) {}
+
+  @Override
+  public void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode) {}
+
+  @Override
+  public void processIntersectionType(
+      IntersectionTypeTree javacTree, IntersectionType javaParserNode) {}
+
+  @Override
+  public void processLabeledStatement(LabeledStatementTree javacTree, LabeledStmt javaParserNode) {}
+
+  @Override
+  public void processLambdaExpression(LambdaExpressionTree javacTree, LambdaExpr javaParserNode) {}
+
+  @Override
+  public void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode) {}
+
+  @Override
+  public void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode) {}
+
+  @Override
+  public void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode) {}
+
+  @Override
+  public void processMemberReference(
+      MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode) {}
+
+  @Override
+  public void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode) {}
+
+  @Override
+  public void processMemberSelect(
+      MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode) {}
+
+  @Override
+  public void processMemberSelect(MemberSelectTree javacTree, FieldAccessExpr javaParserNode) {}
+
+  @Override
+  public void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode) {}
+
+  @Override
+  public void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode) {}
+
+  @Override
+  public void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode) {}
+
+  @Override
+  public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) {}
+
+  @Override
+  public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) {}
+
+  @Override
+  public void processMethod(MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) {}
+
+  @Override
+  public void processMethodInvocation(
+      MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode) {}
+
+  @Override
+  public void processMethodInvocation(
+      MethodInvocationTree javacTree, MethodCallExpr javaParserNode) {}
+
+  @Override
+  public void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode) {}
+
+  @Override
+  public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) {}
+
+  @Override
+  public void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode) {}
+
+  @Override
+  public void processOther(Tree javacTree, Node javaParserNode) {}
+
+  @Override
+  public void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode) {}
+
+  @Override
+  public void processParameterizedType(
+      ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode) {}
+
+  @Override
+  public void processParenthesized(ParenthesizedTree javacTree, EnclosedExpr javaParserNode) {}
+
+  @Override
+  public void processPrimitiveType(PrimitiveTypeTree javacTree, PrimitiveType javaParserNode) {}
+
+  @Override
+  public void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode) {}
+
+  @Override
+  public void processProvides(ProvidesTree javacTree, ModuleProvidesDirective javaParserNode) {}
+
+  @Override
+  public void processRequires(RequiresTree javacTree, ModuleRequiresDirective javaParserNode) {}
+
+  @Override
+  public void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode) {}
+
+  @Override
+  public void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode) {}
+
+  @Override
+  public void processSynchronized(SynchronizedTree javacTree, SynchronizedStmt javaParserNode) {}
+
+  @Override
+  public void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode) {}
+
+  @Override
+  public void processTry(TryTree javacTree, TryStmt javaParserNode) {}
+
+  @Override
+  public void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode) {}
+
+  @Override
+  public void processTypeParameter(TypeParameterTree javacTree, TypeParameter javaParserNode) {}
+
+  @Override
+  public void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode) {}
+
+  @Override
+  public void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode) {}
+
+  @Override
+  public void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode) {}
+
+  @Override
+  public void processVariable(VariableTree javacTree, EnumConstantDeclaration javaParserNode) {}
+
+  @Override
+  public void processVariable(VariableTree javacTree, Parameter javaParserNode) {}
+
+  @Override
+  public void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode) {}
+
+  @Override
+  public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) {}
+
+  @Override
+  public void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode) {}
+
+  @Override
+  public void processWildcard(WildcardTree javacTree, WildcardType javaParserNode) {}
+
+  @Override
+  public void processCompoundAssignment(
+      CompoundAssignmentTree javacTree, AssignExpr javaParserNode) {}
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavaParserVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavaParserVisitor.java
new file mode 100644
index 0000000..e2492e6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavaParserVisitor.java
@@ -0,0 +1,870 @@
+package org.checkerframework.framework.ajava;
+
+import com.github.javaparser.ast.ArrayCreationLevel;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.ImportDeclaration;
+import com.github.javaparser.ast.Modifier;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.PackageDeclaration;
+import com.github.javaparser.ast.body.AnnotationDeclaration;
+import com.github.javaparser.ast.body.AnnotationMemberDeclaration;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.ConstructorDeclaration;
+import com.github.javaparser.ast.body.EnumConstantDeclaration;
+import com.github.javaparser.ast.body.EnumDeclaration;
+import com.github.javaparser.ast.body.FieldDeclaration;
+import com.github.javaparser.ast.body.InitializerDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.Parameter;
+import com.github.javaparser.ast.body.ReceiverParameter;
+import com.github.javaparser.ast.body.VariableDeclarator;
+import com.github.javaparser.ast.comments.BlockComment;
+import com.github.javaparser.ast.comments.JavadocComment;
+import com.github.javaparser.ast.comments.LineComment;
+import com.github.javaparser.ast.expr.ArrayAccessExpr;
+import com.github.javaparser.ast.expr.ArrayCreationExpr;
+import com.github.javaparser.ast.expr.ArrayInitializerExpr;
+import com.github.javaparser.ast.expr.AssignExpr;
+import com.github.javaparser.ast.expr.BinaryExpr;
+import com.github.javaparser.ast.expr.BooleanLiteralExpr;
+import com.github.javaparser.ast.expr.CastExpr;
+import com.github.javaparser.ast.expr.CharLiteralExpr;
+import com.github.javaparser.ast.expr.ClassExpr;
+import com.github.javaparser.ast.expr.ConditionalExpr;
+import com.github.javaparser.ast.expr.DoubleLiteralExpr;
+import com.github.javaparser.ast.expr.EnclosedExpr;
+import com.github.javaparser.ast.expr.FieldAccessExpr;
+import com.github.javaparser.ast.expr.InstanceOfExpr;
+import com.github.javaparser.ast.expr.IntegerLiteralExpr;
+import com.github.javaparser.ast.expr.LambdaExpr;
+import com.github.javaparser.ast.expr.LongLiteralExpr;
+import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
+import com.github.javaparser.ast.expr.MemberValuePair;
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import com.github.javaparser.ast.expr.MethodReferenceExpr;
+import com.github.javaparser.ast.expr.Name;
+import com.github.javaparser.ast.expr.NameExpr;
+import com.github.javaparser.ast.expr.NormalAnnotationExpr;
+import com.github.javaparser.ast.expr.NullLiteralExpr;
+import com.github.javaparser.ast.expr.ObjectCreationExpr;
+import com.github.javaparser.ast.expr.SimpleName;
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
+import com.github.javaparser.ast.expr.StringLiteralExpr;
+import com.github.javaparser.ast.expr.SuperExpr;
+import com.github.javaparser.ast.expr.SwitchExpr;
+import com.github.javaparser.ast.expr.TextBlockLiteralExpr;
+import com.github.javaparser.ast.expr.ThisExpr;
+import com.github.javaparser.ast.expr.TypeExpr;
+import com.github.javaparser.ast.expr.UnaryExpr;
+import com.github.javaparser.ast.expr.VariableDeclarationExpr;
+import com.github.javaparser.ast.modules.ModuleDeclaration;
+import com.github.javaparser.ast.modules.ModuleExportsDirective;
+import com.github.javaparser.ast.modules.ModuleOpensDirective;
+import com.github.javaparser.ast.modules.ModuleProvidesDirective;
+import com.github.javaparser.ast.modules.ModuleRequiresDirective;
+import com.github.javaparser.ast.modules.ModuleUsesDirective;
+import com.github.javaparser.ast.stmt.AssertStmt;
+import com.github.javaparser.ast.stmt.BlockStmt;
+import com.github.javaparser.ast.stmt.BreakStmt;
+import com.github.javaparser.ast.stmt.CatchClause;
+import com.github.javaparser.ast.stmt.ContinueStmt;
+import com.github.javaparser.ast.stmt.DoStmt;
+import com.github.javaparser.ast.stmt.EmptyStmt;
+import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
+import com.github.javaparser.ast.stmt.ExpressionStmt;
+import com.github.javaparser.ast.stmt.ForEachStmt;
+import com.github.javaparser.ast.stmt.ForStmt;
+import com.github.javaparser.ast.stmt.IfStmt;
+import com.github.javaparser.ast.stmt.LabeledStmt;
+import com.github.javaparser.ast.stmt.LocalClassDeclarationStmt;
+import com.github.javaparser.ast.stmt.ReturnStmt;
+import com.github.javaparser.ast.stmt.SwitchEntry;
+import com.github.javaparser.ast.stmt.SwitchStmt;
+import com.github.javaparser.ast.stmt.SynchronizedStmt;
+import com.github.javaparser.ast.stmt.ThrowStmt;
+import com.github.javaparser.ast.stmt.TryStmt;
+import com.github.javaparser.ast.stmt.UnparsableStmt;
+import com.github.javaparser.ast.stmt.WhileStmt;
+import com.github.javaparser.ast.stmt.YieldStmt;
+import com.github.javaparser.ast.type.ArrayType;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import com.github.javaparser.ast.type.IntersectionType;
+import com.github.javaparser.ast.type.PrimitiveType;
+import com.github.javaparser.ast.type.TypeParameter;
+import com.github.javaparser.ast.type.UnionType;
+import com.github.javaparser.ast.type.UnknownType;
+import com.github.javaparser.ast.type.VarType;
+import com.github.javaparser.ast.type.VoidType;
+import com.github.javaparser.ast.type.WildcardType;
+import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
+import java.util.List;
+
+/**
+ * A visitor that visits two JavaParser ASTs simultaneously that almost match. When calling a visit
+ * method, the secondary argument must be another JavaParser {@code Node} that represents an AST
+ * identical to the first argument except for the exceptions allowed between an ajava file and its
+ * corresponding Java file, see the linked section of the manual.
+ *
+ * <p>To use this class, extend it and override {@link #defaultAction(Node, Node)}. This method will
+ * be called on every pair of nodes in the two ASTs. This class does not visit annotations, since
+ * those may differ between the two ASTs.
+ *
+ * @checker_framework.manual #ajava-contents ways in which the two visited ASTs may differ
+ */
+public abstract class DoubleJavaParserVisitor extends VoidVisitorAdapter<Node> {
+  /**
+   * Default action performed on all pairs of nodes from matching ASTs.
+   *
+   * @param node1 first node in pair
+   * @param node2 second node in pair
+   * @param <T> the Node type of {@code node1} and {@code node2}
+   */
+  public abstract <T extends Node> void defaultAction(T node1, T node2);
+
+  /**
+   * Given two lists with the same size where corresponding elements represent nodes with
+   * almost-identical ASTs as specified in this class's description, visits each pair of
+   * corresponding elements in order.
+   *
+   * @param list1 first list of nodes
+   * @param list2 second list of nodes
+   */
+  private void visitLists(List<? extends Node> list1, List<? extends Node> list2) {
+    assert list1.size() == list2.size();
+    for (int i = 0; i < list1.size(); i++) {
+      list1.get(i).accept(this, list2.get(i));
+    }
+  }
+
+  @Override
+  public void visit(final AnnotationDeclaration node1, final Node other) {
+    AnnotationDeclaration node2 = (AnnotationDeclaration) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getMembers(), node2.getMembers());
+    visitLists(node1.getModifiers(), node2.getModifiers());
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final AnnotationMemberDeclaration node1, final Node other) {
+    AnnotationMemberDeclaration node2 = (AnnotationMemberDeclaration) other;
+    defaultAction(node1, node2);
+    node1.getDefaultValue().ifPresent(l -> l.accept(this, node2.getDefaultValue().get()));
+    visitLists(node1.getModifiers(), node2.getModifiers());
+    node1.getName().accept(this, node2.getName());
+    node1.getType().accept(this, node2.getType());
+  }
+
+  @Override
+  public void visit(final ArrayAccessExpr node1, final Node other) {
+    ArrayAccessExpr node2 = (ArrayAccessExpr) other;
+    defaultAction(node1, node2);
+    node1.getIndex().accept(this, node2.getIndex());
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final ArrayCreationExpr node1, final Node other) {
+    ArrayCreationExpr node2 = (ArrayCreationExpr) other;
+    defaultAction(node1, node2);
+    node1.getElementType().accept(this, node2.getElementType());
+    node1.getInitializer().ifPresent(l -> l.accept(this, node2.getInitializer().get()));
+    visitLists(node1.getLevels(), node2.getLevels());
+  }
+
+  @Override
+  public void visit(final ArrayInitializerExpr node1, final Node other) {
+    ArrayInitializerExpr node2 = (ArrayInitializerExpr) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getValues(), node2.getValues());
+  }
+
+  @Override
+  public void visit(final AssertStmt node1, final Node other) {
+    AssertStmt node2 = (AssertStmt) other;
+    defaultAction(node1, node2);
+    node1.getCheck().accept(this, node2.getCheck());
+    node1.getMessage().ifPresent(l -> l.accept(this, node2.getMessage().get()));
+  }
+
+  @Override
+  public void visit(final AssignExpr node1, final Node other) {
+    AssignExpr node2 = (AssignExpr) other;
+    defaultAction(node1, node2);
+    node1.getTarget().accept(this, node2.getTarget());
+    node1.getValue().accept(this, node2.getValue());
+  }
+
+  @Override
+  public void visit(final BinaryExpr node1, final Node other) {
+    BinaryExpr node2 = (BinaryExpr) other;
+    defaultAction(node1, node2);
+    node1.getLeft().accept(this, node2.getLeft());
+    node1.getRight().accept(this, node2.getRight());
+  }
+
+  @Override
+  public void visit(final BlockComment node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final BlockStmt node1, final Node other) {
+    BlockStmt node2 = (BlockStmt) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getStatements(), node2.getStatements());
+  }
+
+  @Override
+  public void visit(final BooleanLiteralExpr node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final BreakStmt node1, final Node other) {
+    BreakStmt node2 = (BreakStmt) other;
+    defaultAction(node1, node2);
+    node1.getLabel().ifPresent(l -> l.accept(this, node2.getLabel().get()));
+  }
+
+  @Override
+  public void visit(final CastExpr node1, final Node other) {
+    CastExpr node2 = (CastExpr) other;
+    defaultAction(node1, node2);
+    node1.getExpression().accept(this, node2.getExpression());
+    node1.getType().accept(this, node2.getType());
+  }
+
+  @Override
+  public void visit(final CatchClause node1, final Node other) {
+    CatchClause node2 = (CatchClause) other;
+    defaultAction(node1, node2);
+    node1.getBody().accept(this, node2.getBody());
+    node1.getParameter().accept(this, node2.getParameter());
+  }
+
+  @Override
+  public void visit(final CharLiteralExpr node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final ClassExpr node1, final Node other) {
+    ClassExpr node2 = (ClassExpr) other;
+    defaultAction(node1, node2);
+    node1.getType().accept(this, node2.getType());
+  }
+
+  @Override
+  public void visit(final ClassOrInterfaceDeclaration node1, final Node other) {
+    ClassOrInterfaceDeclaration node2 = (ClassOrInterfaceDeclaration) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getExtendedTypes(), node2.getExtendedTypes());
+    visitLists(node1.getImplementedTypes(), node2.getImplementedTypes());
+    visitLists(node1.getTypeParameters(), node2.getTypeParameters());
+    visitLists(node1.getMembers(), node2.getMembers());
+    visitLists(node1.getModifiers(), node2.getModifiers());
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final ClassOrInterfaceType node1, final Node other) {
+    ClassOrInterfaceType node2 = (ClassOrInterfaceType) other;
+    defaultAction(node1, node2);
+    node1.getName().accept(this, node2.getName());
+    node1.getScope().ifPresent(l -> l.accept(this, node2.getScope().get()));
+    node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get()));
+  }
+
+  @Override
+  public void visit(final CompilationUnit node1, final Node other) {
+    CompilationUnit node2 = (CompilationUnit) other;
+    defaultAction(node1, node2);
+    node1.getModule().ifPresent(l -> l.accept(this, node2.getModule().get()));
+    node1
+        .getPackageDeclaration()
+        .ifPresent(l -> l.accept(this, node2.getPackageDeclaration().get()));
+    visitLists(node1.getTypes(), node2.getTypes());
+  }
+
+  @Override
+  public void visit(final ConditionalExpr node1, final Node other) {
+    ConditionalExpr node2 = (ConditionalExpr) other;
+    defaultAction(node1, node2);
+    node1.getCondition().accept(this, node2.getCondition());
+    node1.getElseExpr().accept(this, node2.getElseExpr());
+    node1.getThenExpr().accept(this, node2.getThenExpr());
+  }
+
+  @Override
+  public void visit(final ConstructorDeclaration node1, final Node other) {
+    ConstructorDeclaration node2 = (ConstructorDeclaration) other;
+    defaultAction(node1, node2);
+    node1.getBody().accept(this, node2.getBody());
+    visitLists(node1.getModifiers(), node2.getModifiers());
+    node1.getName().accept(this, node2.getName());
+    visitLists(node1.getParameters(), node2.getParameters());
+    if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) {
+      node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get());
+    }
+
+    visitLists(node1.getThrownExceptions(), node2.getThrownExceptions());
+    visitLists(node1.getTypeParameters(), node2.getTypeParameters());
+  }
+
+  @Override
+  public void visit(final ContinueStmt node1, final Node other) {
+    ContinueStmt node2 = (ContinueStmt) other;
+    defaultAction(node1, node2);
+    node1.getLabel().ifPresent(l -> l.accept(this, node2.getLabel().get()));
+  }
+
+  @Override
+  public void visit(final DoStmt node1, final Node other) {
+    DoStmt node2 = (DoStmt) other;
+    defaultAction(node1, node2);
+    node1.getBody().accept(this, node2.getBody());
+    node1.getCondition().accept(this, node2.getCondition());
+  }
+
+  @Override
+  public void visit(final DoubleLiteralExpr node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final EmptyStmt node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final EnclosedExpr node1, final Node other) {
+    EnclosedExpr node2 = (EnclosedExpr) other;
+    defaultAction(node1, node2);
+    node1.getInner().accept(this, node2.getInner());
+  }
+
+  @Override
+  public void visit(final EnumConstantDeclaration node1, final Node other) {
+    EnumConstantDeclaration node2 = (EnumConstantDeclaration) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getArguments(), node2.getArguments());
+    visitLists(node1.getClassBody(), node2.getClassBody());
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final EnumDeclaration node1, final Node other) {
+    EnumDeclaration node2 = (EnumDeclaration) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getEntries(), node2.getEntries());
+    visitLists(node1.getImplementedTypes(), node2.getImplementedTypes());
+    visitLists(node1.getMembers(), node2.getMembers());
+    visitLists(node1.getModifiers(), node2.getModifiers());
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final ExplicitConstructorInvocationStmt node1, final Node other) {
+    ExplicitConstructorInvocationStmt node2 = (ExplicitConstructorInvocationStmt) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getArguments(), node2.getArguments());
+    node1.getExpression().ifPresent(l -> l.accept(this, node2.getExpression().get()));
+    node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get()));
+  }
+
+  @Override
+  public void visit(final ExpressionStmt node1, final Node other) {
+    ExpressionStmt node2 = (ExpressionStmt) other;
+    defaultAction(node1, node2);
+    node1.getExpression().accept(this, node2.getExpression());
+  }
+
+  @Override
+  public void visit(final FieldAccessExpr node1, final Node other) {
+    FieldAccessExpr node2 = (FieldAccessExpr) other;
+    defaultAction(node1, node2);
+    node1.getName().accept(this, node2.getName());
+    node1.getScope().accept(this, node2.getScope());
+    node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get()));
+  }
+
+  @Override
+  public void visit(final FieldDeclaration node1, final Node other) {
+    FieldDeclaration node2 = (FieldDeclaration) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getModifiers(), node2.getModifiers());
+    visitLists(node1.getVariables(), node2.getVariables());
+  }
+
+  @Override
+  public void visit(final ForEachStmt node1, final Node other) {
+    ForEachStmt node2 = (ForEachStmt) other;
+    defaultAction(node1, node2);
+    node1.getBody().accept(this, node2.getBody());
+    node1.getIterable().accept(this, node2.getIterable());
+    node1.getVariable().accept(this, node2.getVariable());
+  }
+
+  @Override
+  public void visit(final ForStmt node1, final Node other) {
+    ForStmt node2 = (ForStmt) other;
+    defaultAction(node1, node2);
+    node1.getBody().accept(this, node2.getBody());
+    node1.getCompare().ifPresent(l -> l.accept(this, node2.getCompare().get()));
+    visitLists(node1.getInitialization(), node2.getInitialization());
+    visitLists(node1.getUpdate(), node2.getUpdate());
+  }
+
+  @Override
+  public void visit(final IfStmt node1, final Node other) {
+    IfStmt node2 = (IfStmt) other;
+    defaultAction(node1, node2);
+    node1.getCondition().accept(this, node2.getCondition());
+    node1.getElseStmt().ifPresent(l -> l.accept(this, node2.getElseStmt().get()));
+    node1.getThenStmt().accept(this, node2.getThenStmt());
+  }
+
+  @Override
+  public void visit(final InitializerDeclaration node1, final Node other) {
+    InitializerDeclaration node2 = (InitializerDeclaration) other;
+    defaultAction(node1, node2);
+    node1.getBody().accept(this, node2.getBody());
+  }
+
+  @Override
+  public void visit(final InstanceOfExpr node1, final Node other) {
+    InstanceOfExpr node2 = (InstanceOfExpr) other;
+    defaultAction(node1, node2);
+    node1.getExpression().accept(this, node2.getExpression());
+    node1.getType().accept(this, node2.getType());
+  }
+
+  @Override
+  public void visit(final IntegerLiteralExpr node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final JavadocComment node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final LabeledStmt node1, final Node other) {
+    LabeledStmt node2 = (LabeledStmt) other;
+    defaultAction(node1, node2);
+    node1.getLabel().accept(this, node2.getLabel());
+    node1.getStatement().accept(this, node2.getStatement());
+  }
+
+  @Override
+  public void visit(final LineComment node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final LongLiteralExpr node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final MarkerAnnotationExpr node1, final Node other) {
+    MarkerAnnotationExpr node2 = (MarkerAnnotationExpr) other;
+    defaultAction(node1, node2);
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final MemberValuePair node1, final Node other) {
+    MemberValuePair node2 = (MemberValuePair) other;
+    defaultAction(node1, node2);
+    node1.getName().accept(this, node2.getName());
+    node1.getValue().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final MethodCallExpr node1, final Node other) {
+    MethodCallExpr node2 = (MethodCallExpr) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getArguments(), node2.getArguments());
+    node1.getName().accept(this, node2.getName());
+    node1.getScope().ifPresent(l -> l.accept(this, node2.getScope().get()));
+    node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get()));
+  }
+
+  @Override
+  public void visit(final MethodDeclaration node1, final Node other) {
+    MethodDeclaration node2 = (MethodDeclaration) other;
+    defaultAction(node1, node2);
+    node1.getBody().ifPresent(l -> l.accept(this, node2.getBody().get()));
+    node1.getType().accept(this, node2.getType());
+    visitLists(node1.getModifiers(), node2.getModifiers());
+    node1.getName().accept(this, node2.getName());
+    visitLists(node1.getParameters(), node2.getParameters());
+    if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) {
+      node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get());
+    }
+
+    visitLists(node1.getThrownExceptions(), node2.getThrownExceptions());
+    visitLists(node1.getTypeParameters(), node2.getTypeParameters());
+  }
+
+  @Override
+  public void visit(final NameExpr node1, final Node other) {
+    NameExpr node2 = (NameExpr) other;
+    defaultAction(node1, node2);
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final NormalAnnotationExpr node1, final Node other) {
+    NormalAnnotationExpr node2 = (NormalAnnotationExpr) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getPairs(), node2.getPairs());
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final NullLiteralExpr node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final ObjectCreationExpr node1, final Node other) {
+    ObjectCreationExpr node2 = (ObjectCreationExpr) other;
+    defaultAction(node1, node2);
+    node1
+        .getAnonymousClassBody()
+        .ifPresent(l -> visitLists(l, node2.getAnonymousClassBody().get()));
+    visitLists(node1.getArguments(), node2.getArguments());
+    node1.getScope().ifPresent(l -> l.accept(this, node2.getScope().get()));
+    node1.getType().accept(this, node2.getType());
+    node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get()));
+  }
+
+  @Override
+  public void visit(final PackageDeclaration node1, final Node other) {
+    PackageDeclaration node2 = (PackageDeclaration) other;
+    defaultAction(node1, node2);
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final Parameter node1, final Node other) {
+    Parameter node2 = (Parameter) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getModifiers(), node2.getModifiers());
+    node1.getName().accept(this, node2.getName());
+    node1.getType().accept(this, node2.getType());
+  }
+
+  @Override
+  public void visit(final PrimitiveType node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final Name node1, final Node other) {
+    Name node2 = (Name) other;
+    defaultAction(node1, node2);
+    node1.getQualifier().ifPresent(l -> l.accept(this, node2.getQualifier().get()));
+  }
+
+  @Override
+  public void visit(final SimpleName node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final ArrayType node1, final Node other) {
+    ArrayType node2 = (ArrayType) other;
+    defaultAction(node1, node2);
+    node1.getComponentType().accept(this, node2.getComponentType());
+  }
+
+  @Override
+  public void visit(final ArrayCreationLevel node1, final Node other) {
+    ArrayCreationLevel node2 = (ArrayCreationLevel) other;
+    defaultAction(node1, node2);
+    node1.getDimension().ifPresent(l -> l.accept(this, node2.getDimension().get()));
+  }
+
+  @Override
+  public void visit(final IntersectionType node1, final Node other) {
+    IntersectionType node2 = (IntersectionType) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getElements(), node2.getElements());
+  }
+
+  @Override
+  public void visit(final UnionType node1, final Node other) {
+    UnionType node2 = (UnionType) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getElements(), node2.getElements());
+  }
+
+  @Override
+  public void visit(final ReturnStmt node1, final Node other) {
+    ReturnStmt node2 = (ReturnStmt) other;
+    defaultAction(node1, node2);
+    node1.getExpression().ifPresent(l -> l.accept(this, node2.getExpression().get()));
+  }
+
+  @Override
+  public void visit(final SingleMemberAnnotationExpr node1, final Node other) {
+    SingleMemberAnnotationExpr node2 = (SingleMemberAnnotationExpr) other;
+    defaultAction(node1, node2);
+    node1.getMemberValue().accept(this, node2.getMemberValue());
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final StringLiteralExpr node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final SuperExpr node1, final Node other) {
+    SuperExpr node2 = (SuperExpr) other;
+    defaultAction(node1, node2);
+    node1.getTypeName().ifPresent(l -> l.accept(this, node2.getTypeName().get()));
+  }
+
+  @Override
+  public void visit(final SwitchEntry node1, final Node other) {
+    SwitchEntry node2 = (SwitchEntry) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getLabels(), node2.getLabels());
+    visitLists(node1.getStatements(), node2.getStatements());
+  }
+
+  @Override
+  public void visit(final SwitchStmt node1, final Node other) {
+    SwitchStmt node2 = (SwitchStmt) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getEntries(), node2.getEntries());
+    node1.getSelector().accept(this, node2.getSelector());
+  }
+
+  @Override
+  public void visit(final SynchronizedStmt node1, final Node other) {
+    SynchronizedStmt node2 = (SynchronizedStmt) other;
+    defaultAction(node1, node2);
+    node1.getBody().accept(this, node2.getBody());
+    node1.getExpression().accept(this, node2.getExpression());
+  }
+
+  @Override
+  public void visit(final ThisExpr node1, final Node other) {
+    ThisExpr node2 = (ThisExpr) other;
+    defaultAction(node1, node2);
+    node1.getTypeName().ifPresent(l -> l.accept(this, node2.getTypeName().get()));
+  }
+
+  @Override
+  public void visit(final ThrowStmt node1, final Node other) {
+    ThrowStmt node2 = (ThrowStmt) other;
+    defaultAction(node1, node2);
+    node1.getExpression().accept(this, node2.getExpression());
+  }
+
+  @Override
+  public void visit(final TryStmt node1, final Node other) {
+    TryStmt node2 = (TryStmt) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getCatchClauses(), node2.getCatchClauses());
+    node1.getFinallyBlock().ifPresent(l -> l.accept(this, node2.getFinallyBlock().get()));
+    visitLists(node1.getResources(), node2.getResources());
+    node1.getTryBlock().accept(this, node2.getTryBlock());
+  }
+
+  @Override
+  public void visit(final LocalClassDeclarationStmt node1, final Node other) {
+    LocalClassDeclarationStmt node2 = (LocalClassDeclarationStmt) other;
+    defaultAction(node1, node2);
+    node1.getClassDeclaration().accept(this, node2.getClassDeclaration());
+  }
+
+  @Override
+  public void visit(final TypeParameter node1, final Node other) {
+    TypeParameter node2 = (TypeParameter) other;
+    defaultAction(node1, node2);
+    node1.getName().accept(this, node2.getName());
+    // Since ajava files and its corresponding Java file may differ in whether they contain a type
+    // bound, only visit type bounds if they're present in both nodes.
+    if (node1.getTypeBound().isEmpty() == node2.getTypeBound().isEmpty()) {
+      visitLists(node1.getTypeBound(), node2.getTypeBound());
+    }
+  }
+
+  @Override
+  public void visit(final UnaryExpr node1, final Node other) {
+    UnaryExpr node2 = (UnaryExpr) other;
+    defaultAction(node1, node2);
+    node1.getExpression().accept(this, node2.getExpression());
+  }
+
+  @Override
+  public void visit(final UnknownType node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final VariableDeclarationExpr node1, final Node other) {
+    VariableDeclarationExpr node2 = (VariableDeclarationExpr) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getModifiers(), node2.getModifiers());
+    visitLists(node1.getVariables(), node2.getVariables());
+  }
+
+  @Override
+  public void visit(final VariableDeclarator node1, final Node other) {
+    VariableDeclarator node2 = (VariableDeclarator) other;
+    defaultAction(node1, node2);
+    node1.getInitializer().ifPresent(l -> l.accept(this, node2.getInitializer().get()));
+    node1.getName().accept(this, node2.getName());
+    node1.getType().accept(this, node2.getType());
+  }
+
+  @Override
+  public void visit(final VoidType node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final WhileStmt node1, final Node other) {
+    WhileStmt node2 = (WhileStmt) other;
+    defaultAction(node1, node2);
+    node1.getBody().accept(this, node2.getBody());
+    node1.getCondition().accept(this, node2.getCondition());
+  }
+
+  @Override
+  public void visit(final WildcardType node1, final Node other) {
+    WildcardType node2 = (WildcardType) other;
+    defaultAction(node1, node2);
+    node1.getExtendedType().ifPresent(l -> l.accept(this, node2.getExtendedType().get()));
+    node1.getSuperType().ifPresent(l -> l.accept(this, node2.getSuperType().get()));
+  }
+
+  @Override
+  public void visit(final LambdaExpr node1, final Node other) {
+    LambdaExpr node2 = (LambdaExpr) other;
+    defaultAction(node1, node2);
+    node1.getBody().accept(this, node2.getBody());
+    visitLists(node1.getParameters(), node2.getParameters());
+  }
+
+  @Override
+  public void visit(final MethodReferenceExpr node1, final Node other) {
+    MethodReferenceExpr node2 = (MethodReferenceExpr) other;
+    defaultAction(node1, node2);
+    node1.getScope().accept(this, node2.getScope());
+    node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get()));
+  }
+
+  @Override
+  public void visit(final TypeExpr node1, final Node other) {
+    TypeExpr node2 = (TypeExpr) other;
+    defaultAction(node1, node2);
+    node1.getType().accept(this, node2.getType());
+  }
+
+  @Override
+  public void visit(final ImportDeclaration node1, final Node other) {
+    ImportDeclaration node2 = (ImportDeclaration) other;
+    defaultAction(node1, node2);
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final ModuleDeclaration node1, final Node other) {
+    ModuleDeclaration node2 = (ModuleDeclaration) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getDirectives(), node2.getDirectives());
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final ModuleRequiresDirective node1, final Node other) {
+    ModuleRequiresDirective node2 = (ModuleRequiresDirective) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getModifiers(), node2.getModifiers());
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final ModuleExportsDirective node1, final Node other) {
+    ModuleExportsDirective node2 = (ModuleExportsDirective) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getModuleNames(), node2.getModuleNames());
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final ModuleProvidesDirective node1, final Node other) {
+    ModuleProvidesDirective node2 = (ModuleProvidesDirective) other;
+    defaultAction(node1, node2);
+    node1.getName().accept(this, node2.getName());
+    visitLists(node1.getWith(), node2.getWith());
+  }
+
+  @Override
+  public void visit(final ModuleUsesDirective node1, final Node other) {
+    ModuleUsesDirective node2 = (ModuleUsesDirective) other;
+    defaultAction(node1, node2);
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final ModuleOpensDirective node1, final Node other) {
+    ModuleOpensDirective node2 = (ModuleOpensDirective) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getModuleNames(), node2.getModuleNames());
+    node1.getName().accept(this, node2.getName());
+  }
+
+  @Override
+  public void visit(final UnparsableStmt node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final ReceiverParameter node1, final Node other) {
+    ReceiverParameter node2 = (ReceiverParameter) other;
+    defaultAction(node1, node2);
+    node1.getName().accept(this, node2.getName());
+    node1.getType().accept(this, node2.getType());
+  }
+
+  @Override
+  public void visit(final VarType node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final Modifier node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final SwitchExpr node1, final Node other) {
+    SwitchExpr node2 = (SwitchExpr) other;
+    defaultAction(node1, node2);
+    visitLists(node1.getEntries(), node2.getEntries());
+    node1.getSelector().accept(this, node2.getSelector());
+  }
+
+  @Override
+  public void visit(final TextBlockLiteralExpr node1, final Node other) {
+    defaultAction(node1, other);
+  }
+
+  @Override
+  public void visit(final YieldStmt node1, final Node other) {
+    YieldStmt node2 = (YieldStmt) other;
+    defaultAction(node1, node2);
+    node1.getExpression().accept(this, node2.getExpression());
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/ExpectedTreesVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/ExpectedTreesVisitor.java
new file mode 100644
index 0000000..a8c2714
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/ajava/ExpectedTreesVisitor.java
@@ -0,0 +1,367 @@
+package org.checkerframework.framework.ajava;
+
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.DoWhileLoopTree;
+import com.sun.source.tree.ExpressionStatementTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.ForLoopTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModifiersTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.SwitchTree;
+import com.sun.source.tree.SynchronizedTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.tree.WhileLoopTree;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.util.Position;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * After this visitor visits a tree, {@link #getTrees} returns all the trees that should match with
+ * some JavaParser node. Some trees shouldn't be matched with a JavaParser node because there isn't
+ * a corresponding JavaParser node. These trees are excluded.
+ *
+ * <p>The primary purpose is to test the {@link JointJavacJavaParserVisitor} class when the
+ * -AajavaChecks flag is used. That class traverses a javac tree and JavaParser AST simultaneously,
+ * so the trees this class stores can be used to test if the entirety of the javac tree was visited.
+ */
+public class ExpectedTreesVisitor extends TreeScannerWithDefaults {
+  /** The set of trees that should be matched to a JavaParser node when visiting both. */
+  private Set<Tree> trees = new HashSet<>();
+
+  /**
+   * Returns the visited trees that should match to some JavaParser node.
+   *
+   * @return the visited trees that should match to some JavaParser node
+   */
+  public Set<Tree> getTrees() {
+    return trees;
+  }
+
+  /**
+   * Records that {@code tree} should have a corresponding JavaParser node.
+   *
+   * @param tree the tree to record
+   */
+  @Override
+  public void defaultAction(Tree tree) {
+    trees.add(tree);
+  }
+
+  @Override
+  public Void visitAnnotation(AnnotationTree tree, Void p) {
+    // Skip annotations because ajava files are not required to have the same annotations as
+    // their corresponding java files.
+    return null;
+  }
+
+  @Override
+  public Void visitClass(ClassTree tree, Void p) {
+    defaultAction(tree);
+    visit(tree.getModifiers(), p);
+    visit(tree.getTypeParameters(), p);
+    visit(tree.getExtendsClause(), p);
+    visit(tree.getImplementsClause(), p);
+    if (tree.getKind() == Kind.ENUM) {
+      // Enum constants expand to a VariableTree like
+      //    public static final MY_ENUM_CONSTANT = new MyEnum(args ...)
+      // The constructor invocation in the initializer has no corresponding JavaParser node,
+      // so this removes those invocations. This doesn't remove any trees that should be
+      // matched to a JavaParser node, because it's illegal to explicitly construct an
+      // instance of an enum.
+      for (Tree member : tree.getMembers()) {
+        member.accept(this, p);
+        if (member.getKind() != Kind.VARIABLE) {
+          continue;
+        }
+
+        VariableTree variable = (VariableTree) member;
+        ExpressionTree initializer = variable.getInitializer();
+        if (initializer == null || initializer.getKind() != Kind.NEW_CLASS) {
+          continue;
+        }
+
+        NewClassTree constructor = (NewClassTree) initializer;
+        if (constructor.getIdentifier().getKind() != Kind.IDENTIFIER) {
+          continue;
+        }
+
+        IdentifierTree name = (IdentifierTree) constructor.getIdentifier();
+        if (name.getName().contentEquals(tree.getSimpleName())) {
+          trees.remove(variable.getType());
+          trees.remove(constructor);
+          trees.remove(constructor.getIdentifier());
+        }
+      }
+    } else {
+      visit(tree.getMembers(), p);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitExpressionStatement(ExpressionStatementTree tree, Void p) {
+    // Javac inserts calls to super() at the start of constructors with no this or super call.
+    // These don't have matching JavaParser nodes.
+    if (JointJavacJavaParserVisitor.isDefaultSuperConstructorCall(tree)) {
+      return null;
+    }
+
+    // Whereas synthetic constructors should be skipped, regular super() and this() should still
+    // be added. JavaParser has no expression statement surrounding these, so remove the
+    // expression statement itself.
+    Void result = super.visitExpressionStatement(tree, p);
+    if (tree.getExpression().getKind() == Kind.METHOD_INVOCATION) {
+      MethodInvocationTree invocation = (MethodInvocationTree) tree.getExpression();
+      if (invocation.getMethodSelect().getKind() == Kind.IDENTIFIER) {
+        IdentifierTree identifier = (IdentifierTree) invocation.getMethodSelect();
+        if (identifier.getName().contentEquals("this")
+            || identifier.getName().contentEquals("super")) {
+          trees.remove(tree);
+          trees.remove(identifier);
+        }
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public Void visitForLoop(ForLoopTree tree, Void p) {
+    // Javac nests a for loop's updates in expression statements but JavaParser stores the
+    // statements directly, so remove the expression statements.
+    Void result = super.visitForLoop(tree, p);
+    for (StatementTree initializer : tree.getInitializer()) {
+      trees.remove(initializer);
+    }
+
+    for (ExpressionStatementTree update : tree.getUpdate()) {
+      trees.remove(update);
+    }
+
+    return result;
+  }
+
+  @Override
+  public Void visitSwitch(SwitchTree tree, Void p) {
+    super.visitSwitch(tree, p);
+    // javac surrounds switch expression in a ParenthesizedTree but JavaParser does not.
+    trees.remove(tree.getExpression());
+    return null;
+  }
+
+  @Override
+  public Void visitSynchronized(SynchronizedTree tree, Void p) {
+    super.visitSynchronized(tree, p);
+    // javac surrounds synchronized expressions in a ParenthesizedTree but JavaParser does not.
+    trees.remove(tree.getExpression());
+    return null;
+  }
+
+  @Override
+  public Void visitIf(IfTree tree, Void p) {
+    // In an if statement, javac stores the condition as a parenthesized expression, which has no
+    // corresponding JavaParserNode, so remove the parenthesized expression, but not its child.
+    Void result = super.visitIf(tree, p);
+    trees.remove(tree.getCondition());
+    return result;
+  }
+
+  @Override
+  public Void visitImport(ImportTree tree, Void p) {
+    // Javac stores an import like a.* as a member select, but JavaParser just stores "a", so
+    // don't add the member select in that case.
+    if (tree.getQualifiedIdentifier().getKind() == Kind.MEMBER_SELECT) {
+      MemberSelectTree memberSelect = (MemberSelectTree) tree.getQualifiedIdentifier();
+      if (memberSelect.getIdentifier().contentEquals("*")) {
+        memberSelect.getExpression().accept(this, p);
+        return null;
+      }
+    }
+
+    return super.visitImport(tree, p);
+  }
+
+  @Override
+  public Void visitMethod(MethodTree tree, Void p) {
+    // Synthetic default constructors don't have matching JavaParser nodes. Conservatively skip
+    // nullary (no-argument) constructor calls, even if they may not be synthetic.
+    if (JointJavacJavaParserVisitor.isNoArgumentConstructor(tree)) {
+      return null;
+    }
+
+    Void result = super.visitMethod(tree, p);
+    // A varargs parameter like String... is converted to String[], where the array type doesn't
+    // have a corresponding JavaParser node. Conservatively skip the array type (but not the
+    // component type) if it's the last argument.
+    if (!tree.getParameters().isEmpty()) {
+      VariableTree last = tree.getParameters().get(tree.getParameters().size() - 1);
+      if (last.getType().getKind() == Kind.ARRAY_TYPE) {
+        trees.remove(last.getType());
+      }
+
+      if (last.getType().getKind() == Kind.ANNOTATED_TYPE) {
+        AnnotatedTypeTree annotatedType = (AnnotatedTypeTree) last.getType();
+        if (annotatedType.getUnderlyingType().getKind() == Kind.ARRAY_TYPE) {
+          trees.remove(annotatedType);
+          trees.remove(annotatedType.getUnderlyingType());
+        }
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree tree, Void p) {
+    Void result = super.visitMethodInvocation(tree, p);
+    // In a method invocation like myObject.myMethod(), the method invocation stores
+    // myObject.myMethod as its own MemberSelectTree which has no corresponding JavaParserNode.
+    if (tree.getMethodSelect().getKind() == Kind.MEMBER_SELECT) {
+      trees.remove(tree.getMethodSelect());
+    }
+
+    return result;
+  }
+
+  @Override
+  public Void visitModifiers(ModifiersTree tree, Void p) {
+    // Don't add ModifierTrees or children because they have no corresponding JavaParser node.
+    return null;
+  }
+
+  @Override
+  public Void visitNewArray(NewArrayTree tree, Void p) {
+    // Skip array initialization because it's not implemented yet.
+    return null;
+  }
+
+  @Override
+  public Void visitNewClass(NewClassTree tree, Void p) {
+    defaultAction(tree);
+
+    if (tree.getEnclosingExpression() != null) {
+      tree.getEnclosingExpression().accept(this, p);
+    }
+
+    tree.getIdentifier().accept(this, p);
+    for (Tree typeArgument : tree.getTypeArguments()) {
+      typeArgument.accept(this, p);
+    }
+
+    for (Tree arg : tree.getTypeArguments()) {
+      arg.accept(this, p);
+    }
+
+    if (tree.getClassBody() == null) {
+      return null;
+    }
+
+    // Anonymous class bodies require special handling. There isn't a corresponding JavaParser
+    // node, and synthetic constructors must be skipped.
+    ClassTree body = tree.getClassBody();
+    visit(body.getModifiers(), p);
+    visit(body.getTypeParameters(), p);
+    visit(body.getImplementsClause(), p);
+    for (Tree member : body.getMembers()) {
+      // Constructors cannot be declared in an anonymous class, so don't add them.
+      if (member.getKind() == Kind.METHOD) {
+        MethodTree methodTree = (MethodTree) member;
+        if (methodTree.getName().contentEquals("<init>")) {
+          continue;
+        }
+      }
+
+      member.accept(this, p);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) {
+    for (VariableTree parameter : tree.getParameters()) {
+      // Programmers may omit parameter types for lambda expressions. When not specified,
+      // javac infers them but JavaParser uses UnknownType. Conservatively, don't add
+      // parameter types for lambda expressions.
+      visit(parameter.getModifiers(), p);
+      visit(parameter.getNameExpression(), p);
+      assert parameter.getInitializer() == null;
+    }
+
+    visit(tree.getBody(), p);
+    return null;
+  }
+
+  @Override
+  public Void visitWhileLoop(WhileLoopTree tree, Void p) {
+    super.visitWhileLoop(tree, p);
+    // javac surrounds while loop conditions in a ParenthesizedTree but JavaParser does not.
+    trees.remove(tree.getCondition());
+    return null;
+  }
+
+  @Override
+  public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) {
+    super.visitDoWhileLoop(tree, p);
+    // javac surrounds while loop conditions in a ParenthesizedTree but JavaParser does not.
+    trees.remove(tree.getCondition());
+    return null;
+  }
+
+  @Override
+  public Void visitVariable(VariableTree tree, Void p) {
+    // Javac expands the keyword "var" in a variable declaration to its inferred type.
+    // JavaParser has a special "var" construct, so they won't match. If a javac type was generated
+    // this way, then it won't have a position in source code so in that case we don't add it.
+    if (((JCExpression) tree.getType()).pos == Position.NOPOS) {
+      return null;
+    }
+
+    return super.visitVariable(tree, p);
+  }
+
+  /**
+   * Calls the correct visit method for {@code tree} if {@code tree} is non-null.
+   *
+   * @param tree the tree to visit
+   * @param p secondary parameter to the visitor
+   */
+  private void visit(@Nullable Tree tree, Void p) {
+    if (tree != null) {
+      tree.accept(this, p);
+    }
+  }
+
+  /**
+   * If {@code trees} is non-null, visits each non-null tree in {@code trees} in order.
+   *
+   * @param trees the list of trees to visit
+   * @param p secondary parameter to the visitor
+   */
+  private void visit(@Nullable List<? extends @Nullable Tree> trees, Void p) {
+    if (trees == null) {
+      return;
+    }
+
+    for (Tree tree : trees) {
+      visit(tree, p);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java b/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java
new file mode 100644
index 0000000..d249ce0
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java
@@ -0,0 +1,599 @@
+package org.checkerframework.framework.ajava;
+
+import com.github.javaparser.Position;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.ImportDeclaration;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.body.FieldDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.TypeDeclaration;
+import com.github.javaparser.ast.expr.AnnotationExpr;
+import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations;
+import com.github.javaparser.ast.type.ArrayType;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import com.github.javaparser.ast.type.Type;
+import com.github.javaparser.printer.DefaultPrettyPrinter;
+import com.github.javaparser.utils.Pair;
+import com.sun.source.util.JavacTask;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.ToolProvider;
+import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.framework.stub.AnnotationFileParser;
+import org.checkerframework.framework.util.JavaParserUtil;
+import org.plumelib.util.FilesPlume;
+
+/** This program inserts annotations from an ajava file into a Java file. See {@link #main}. */
+public class InsertAjavaAnnotations {
+  /** Element utilities. */
+  private Elements elements;
+
+  /**
+   * Constructs an {@code InsertAjavaAnnotations} using the given {@code Elements} instance.
+   *
+   * @param elements an instance of {@code Elements}
+   */
+  public InsertAjavaAnnotations(Elements elements) {
+    this.elements = elements;
+  }
+
+  /**
+   * Gets an instance of {@code Elements} from the current Java compiler.
+   *
+   * @return Element utilities
+   */
+  private static Elements createElements() {
+    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+    if (compiler == null) {
+      System.err.println("Could not get compiler instance");
+      System.exit(1);
+    }
+
+    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
+    JavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
+    if (fileManager == null) {
+      System.err.println("Could not get file manager");
+      System.exit(1);
+    }
+
+    CompilationTask cTask =
+        compiler.getTask(
+            null, fileManager, diagnostics, Collections.emptyList(), null, Collections.emptyList());
+    if (!(cTask instanceof JavacTask)) {
+      System.err.println("Could not get a valid JavacTask: " + cTask.getClass());
+      System.exit(1);
+    }
+
+    return ((JavacTask) cTask).getElements();
+  }
+
+  /** Represents some text to be inserted at a file and its location. */
+  private static class Insertion {
+    /** Offset of the insertion in the file, measured in characters from the beginning. */
+    public int position;
+    /** The contents of the insertion. */
+    public String contents;
+    /** Whether the insertion should be on its own separate line. */
+    public boolean ownLine;
+
+    /**
+     * Constructs an insertion with the given position and contents.
+     *
+     * @param position offset of the insertion in the file
+     * @param contents contents of the insertion
+     */
+    public Insertion(int position, String contents) {
+      this(position, contents, false);
+    }
+
+    /**
+     * Constructs an insertion with the given position and contents.
+     *
+     * @param position offset of the insertion in the file
+     * @param contents contents of the insertion
+     * @param ownLine true if this insertion should appear on its own separate line (doesn't affect
+     *     the contents of the insertion)
+     */
+    public Insertion(int position, String contents, boolean ownLine) {
+      this.position = position;
+      this.contents = contents;
+      this.ownLine = ownLine;
+    }
+
+    @Override
+    public String toString() {
+      return "Insertion [contents=" + contents + ", position=" + position + "]";
+    }
+  }
+
+  /**
+   * Given two JavaParser ASTs representing the same Java file but with differing annotations,
+   * stores a list of {@link Insertion}s. The {@link Insertion}s represent how to textually modify
+   * the file of the second AST to insert all annotations in the first AST into the second AST, but
+   * this class doesn't modify the second AST itself. To use this class, call {@link
+   * #visit(CompilationUnit, Node)} on a pair of ASTs and then use the contents of {@link
+   * #insertions}.
+   */
+  private class BuildInsertionsVisitor extends DoubleJavaParserVisitor {
+    /**
+     * The set of annotations found in the file. Keys are both fully-qualified and simple names.
+     * Contains an entry for the fully qualified name of each annotation and, if it was imported,
+     * its simple name.
+     *
+     * <p>The map is populated from import statements and also when parsing a file that uses the
+     * fully qualified name of an annotation it doesn't import.
+     */
+    private Map<String, TypeElement> allAnnotations;
+
+    /** The annotation insertions seen so far. */
+    public List<Insertion> insertions;
+    /** A printer for annotations. */
+    private DefaultPrettyPrinter printer;
+    /** The lines of the String representation of the second AST. */
+    private List<String> lines;
+    /** The line separator used in the text the second AST was parsed from */
+    private String lineSeparator;
+    /**
+     * Stores the offsets of the lines in the string representation of the second AST. At index i,
+     * stores the number of characters from the start of the file to the beginning of the ith line.
+     */
+    private List<Integer> cumulativeLineSizes;
+
+    /**
+     * Constructs a {@code BuildInsertionsVisitor} where {@code destFileContents} is the String
+     * representation of the AST to insert annotation into, that uses the given line separator. When
+     * visiting a node pair, the second node must always be from an AST generated from this String.
+     *
+     * @param destFileContents the String the second vistide AST was parsed from
+     * @param lineSeparator the line separator that {@code destFileContents} uses
+     */
+    public BuildInsertionsVisitor(String destFileContents, String lineSeparator) {
+      allAnnotations = null;
+      insertions = new ArrayList<>();
+      printer = new DefaultPrettyPrinter();
+      String[] lines = destFileContents.split(lineSeparator);
+      this.lines = Arrays.asList(lines);
+      this.lineSeparator = lineSeparator;
+      cumulativeLineSizes = new ArrayList<>();
+      cumulativeLineSizes.add(0);
+      for (int i = 1; i < lines.length; i++) {
+        int lastSize = cumulativeLineSizes.get(i - 1);
+        int lastLineLength = lines[i - 1].length() + lineSeparator.length();
+        cumulativeLineSizes.add(lastSize + lastLineLength);
+      }
+    }
+
+    @Override
+    public void defaultAction(Node src, Node dest) {
+      if (!(src instanceof NodeWithAnnotations<?>)) {
+        return;
+      }
+      NodeWithAnnotations<?> srcWithAnnos = (NodeWithAnnotations<?>) src;
+
+      // If `src` is a declaration, its annotations are declaration annotations.
+      if (src instanceof MethodDeclaration) {
+        addAnnotationOnOwnLine(dest.getBegin().get(), srcWithAnnos.getAnnotations());
+        return;
+      } else if (src instanceof FieldDeclaration) {
+        addAnnotationOnOwnLine(dest.getBegin().get(), srcWithAnnos.getAnnotations());
+        return;
+      }
+
+      // `src`'s annotations are type annotations.
+      Position position;
+      if (dest instanceof ClassOrInterfaceType) {
+        // In a multi-part name like my.package.MyClass, type annotations go directly in front of
+        // MyClass instead of the full name.
+        position = ((ClassOrInterfaceType) dest).getName().getBegin().get();
+      } else {
+        position = dest.getBegin().get();
+      }
+      addAnnotations(position, srcWithAnnos.getAnnotations(), 0, false);
+    }
+
+    @Override
+    public void visit(ArrayType src, Node other) {
+      ArrayType dest = (ArrayType) other;
+      // The second component of this pair contains a list of ArrayBracketPairs from left to
+      // right. For example, if src contains String[][], then the list will contain the
+      // types String[] and String[][]. To insert array annotations in the correct location,
+      // we insert them directly to the right of the end of the previous element.
+      Pair<Type, List<ArrayType.ArrayBracketPair>> srcArrayTypes = ArrayType.unwrapArrayTypes(src);
+      Pair<Type, List<ArrayType.ArrayBracketPair>> destArrayTypes =
+          ArrayType.unwrapArrayTypes(dest);
+      // The first annotations go directly after the element type.
+      Position firstPosition = destArrayTypes.a.getEnd().get();
+      addAnnotations(firstPosition, srcArrayTypes.b.get(0).getAnnotations(), 1, false);
+      for (int i = 1; i < srcArrayTypes.b.size(); i++) {
+        Position position = destArrayTypes.b.get(i - 1).getTokenRange().get().toRange().get().end;
+        addAnnotations(position, srcArrayTypes.b.get(i).getAnnotations(), 1, true);
+      }
+
+      // Visit the component type.
+      srcArrayTypes.a.accept(this, destArrayTypes.a);
+    }
+
+    @Override
+    public void visit(CompilationUnit src, Node other) {
+      CompilationUnit dest = (CompilationUnit) other;
+      defaultAction(src, dest);
+
+      // Gather annotations used in the ajava file.
+      allAnnotations = getImportedAnnotations(src);
+
+      // Move any annotations that JavaParser puts in the declaration position but belong only in
+      // the type position.
+      src.accept(new TypeAnnotationMover(allAnnotations, elements), null);
+
+      // Transfer import statements from the ajava file to the Java file.
+
+      List<String> newImports;
+      { // set `newImports`
+        Set<String> existingImports = new HashSet<>();
+        for (ImportDeclaration importDecl : dest.getImports()) {
+          existingImports.add(printer.print(importDecl));
+        }
+
+        newImports = new ArrayList<>();
+        for (ImportDeclaration importDecl : src.getImports()) {
+          String importString = printer.print(importDecl);
+          if (!existingImports.contains(importString)) {
+            newImports.add(importString);
+          }
+        }
+      }
+
+      if (!newImports.isEmpty()) {
+        int position;
+        int lineBreaksBeforeFirstImport;
+        if (!dest.getImports().isEmpty()) {
+          Position lastImportPosition =
+              dest.getImports().get(dest.getImports().size() - 1).getEnd().get();
+          position = getFilePosition(lastImportPosition) + 1;
+          lineBreaksBeforeFirstImport = 1;
+        } else if (dest.getPackageDeclaration().isPresent()) {
+          Position packagePosition = dest.getPackageDeclaration().get().getEnd().get();
+          position = getFilePosition(packagePosition) + 1;
+          lineBreaksBeforeFirstImport = 2;
+        } else {
+          position = 0;
+          lineBreaksBeforeFirstImport = 0;
+        }
+
+        String insertionContent = "";
+        // In Java 11, use String::repeat.
+        for (int i = 0; i < lineBreaksBeforeFirstImport; i++) {
+          insertionContent += lineSeparator;
+        }
+        insertionContent += String.join("", newImports);
+
+        insertions.add(new Insertion(position, insertionContent));
+      }
+
+      src.getModule().ifPresent(l -> l.accept(this, dest.getModule().get()));
+      src.getPackageDeclaration()
+          .ifPresent(l -> l.accept(this, dest.getPackageDeclaration().get()));
+      for (int i = 0; i < src.getTypes().size(); i++) {
+        src.getTypes().get(i).accept(this, dest.getTypes().get(i));
+      }
+    }
+
+    /**
+     * Creates an insertion for a collection of annotations and adds it to {@link #insertions}. The
+     * annotations will appear on their own line (unless any non-whitespace characters precede the
+     * insertion position on its own line).
+     *
+     * @param position the position of the insertion
+     * @param annotations List of annotations to insert
+     */
+    private void addAnnotationOnOwnLine(Position position, List<AnnotationExpr> annotations) {
+      String line = lines.get(position.line - 1);
+      int insertionColumn = position.column - 1;
+      boolean ownLine = true;
+      for (int i = 0; i < insertionColumn; i++) {
+        if (line.charAt(i) != ' ' && line.charAt(i) != '\t') {
+          ownLine = false;
+          break;
+        }
+      }
+
+      if (ownLine) {
+        StringJoiner insertionContent = new StringJoiner(" ");
+        for (AnnotationExpr annotation : annotations) {
+          insertionContent.add(printer.print(annotation));
+        }
+
+        if (insertionContent.length() == 0) {
+          return;
+        }
+
+        String leadingWhitespace = line.substring(0, insertionColumn);
+        int filePosition = getFilePosition(position);
+        insertions.add(
+            new Insertion(
+                filePosition,
+                insertionContent.toString() + lineSeparator + leadingWhitespace,
+                true));
+      } else {
+        addAnnotations(position, annotations, 0, false);
+      }
+    }
+
+    /**
+     * Creates an insertion for a collection of annotations at {@code position} + {@code offset} and
+     * adds it to {@link #insertions}.
+     *
+     * @param position the position of the insertion
+     * @param annotations List of annotations to insert
+     * @param offset additional offset of the insertion after {@code position}
+     * @param addSpaceBefore if true, the insertion content will start with a space
+     */
+    private void addAnnotations(
+        Position position,
+        Iterable<AnnotationExpr> annotations,
+        int offset,
+        boolean addSpaceBefore) {
+      StringBuilder insertionContent = new StringBuilder();
+      for (AnnotationExpr annotation : annotations) {
+        insertionContent.append(printer.print(annotation));
+        insertionContent.append(" ");
+      }
+
+      // Can't test `annotations.isEmpty()` earlier because `annotations` has type `Iterable`.
+      if (insertionContent.length() == 0) {
+        return;
+      }
+
+      if (addSpaceBefore) {
+        insertionContent.insert(0, " ");
+      }
+
+      int filePosition = getFilePosition(position) + offset;
+      insertions.add(new Insertion(filePosition, insertionContent.toString()));
+    }
+
+    /**
+     * Converts a Position (which contains a line and column) to an offset from the start of the
+     * file, in characters.
+     *
+     * @param position a Position
+     * @return the total offset of the position from the start of the file
+     */
+    private int getFilePosition(Position position) {
+      return cumulativeLineSizes.get(position.line - 1) + (position.column - 1);
+    }
+  }
+
+  /**
+   * Returns all annotations imported by the annotation file as a mapping from simple and qualified
+   * names to TypeElements.
+   *
+   * @param cu compilation unit to extract imports from
+   * @return a map from names to TypeElement, for all annotations imported by the annotation file.
+   *     Two entries for each annotation: one for the simple name and another for the
+   *     fully-qualified name, with the same value.
+   */
+  private Map<String, TypeElement> getImportedAnnotations(CompilationUnit cu) {
+    if (cu.getImports() == null) {
+      return Collections.emptyMap();
+    }
+
+    Map<String, TypeElement> result = new HashMap<>();
+    for (ImportDeclaration importDecl : cu.getImports()) {
+      if (importDecl.isAsterisk()) {
+        @SuppressWarnings("signature" // https://tinyurl.com/cfissue/3094:
+        // com.github.javaparser.ast.expr.Name inherits toString,
+        // so there can be no annotation for it
+        )
+        @DotSeparatedIdentifiers String imported = importDecl.getName().toString();
+        if (importDecl.isStatic()) {
+          // Wildcard import of members of a type (class or interface)
+          TypeElement element = elements.getTypeElement(imported);
+          if (element != null) {
+            // Find nested annotations
+            result.putAll(AnnotationFileParser.annosInType(element));
+          }
+
+        } else {
+          // Wildcard import of members of a package
+          PackageElement element = elements.getPackageElement(imported);
+          if (element != null) {
+            result.putAll(AnnotationFileParser.annosInPackage(element));
+          }
+        }
+      } else {
+        @SuppressWarnings("signature" // importDecl is non-wildcard, so its name is
+        // @FullyQualifiedName
+        )
+        @FullyQualifiedName String imported = importDecl.getNameAsString();
+        TypeElement importType = elements.getTypeElement(imported);
+        if (importType != null && importType.getKind() == ElementKind.ANNOTATION_TYPE) {
+          TypeElement annoElt = elements.getTypeElement(imported);
+          if (annoElt != null) {
+            result.put(annoElt.getSimpleName().toString(), annoElt);
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Inserts all annotations from the ajava file read from {@code annotationFile} into a Java file
+   * with contents {@code javaFileContents} that uses the given line separator and returns the
+   * resulting String.
+   *
+   * @param annotationFile input stream for an ajava file for {@code javaFileContents}
+   * @param javaFileContents contents of a Java file to insert annotations into
+   * @param lineSeparator the line separator {@code javaFileContents} uses
+   * @return a modified {@code javaFileContents} with annotations from {@code annotationFile}
+   *     inserted
+   */
+  public String insertAnnotations(
+      InputStream annotationFile, String javaFileContents, String lineSeparator) {
+    CompilationUnit annotationCu = JavaParserUtil.parseCompilationUnit(annotationFile);
+    CompilationUnit javaCu = JavaParserUtil.parseCompilationUnit(javaFileContents);
+    BuildInsertionsVisitor insertionVisitor =
+        new BuildInsertionsVisitor(javaFileContents, lineSeparator);
+    annotationCu.accept(insertionVisitor, javaCu);
+    List<Insertion> insertions = insertionVisitor.insertions;
+    insertions.sort(InsertAjavaAnnotations::compareInsertions);
+
+    StringBuilder result = new StringBuilder(javaFileContents);
+    for (Insertion insertion : insertions) {
+      result.insert(insertion.position, insertion.contents);
+    }
+    return result.toString();
+  }
+
+  /**
+   * Compares two insertions in the reverse order of where their content should appear in the file.
+   * Making an insertion changes the offset values of all content after the insertion, so performing
+   * the insertions in reverse order of appearance removes the need to recalculate the positions of
+   * other insertions.
+   *
+   * <p>The order in which insertions should appear is determined first by their absolute position
+   * in the file, and second by whether they have their own line. In a method like
+   * {@code @Pure @Tainting String myMethod()} both annotations should be inserted at the same
+   * location (right before "String"), but {@code @Pure} should always come first because it belongs
+   * on its own line.
+   *
+   * @param insertion1 the first insertion
+   * @param insertion2 the second insertion
+   * @return a negative integer, zero, or a positive integer if {@code insertion1} belongs before,
+   *     at the same position, or after {@code insertion2} respectively in the above ordering
+   */
+  private static int compareInsertions(Insertion insertion1, Insertion insertion2) {
+    int cmp = Integer.compare(insertion1.position, insertion2.position);
+    if (cmp == 0 && (insertion1.ownLine != insertion2.ownLine)) {
+      if (insertion1.ownLine) {
+        cmp = -1;
+      } else {
+        cmp = 1;
+      }
+    }
+
+    return -cmp;
+  }
+
+  /**
+   * Inserts all annotations from the ajava file at {@code annotationFilePath} into {@code
+   * javaFilePath}.
+   *
+   * @param annotationFilePath path to an ajava file
+   * @param javaFilePath path to a Java file to insert annotation into
+   */
+  public void insertAnnotations(String annotationFilePath, String javaFilePath) {
+    try {
+      File javaFile = new File(javaFilePath);
+      String fileContents = FilesPlume.readFile(javaFile);
+      String lineSeparator = FilesPlume.inferLineSeparator(annotationFilePath);
+      FileInputStream annotationInputStream = new FileInputStream(annotationFilePath);
+      String result = insertAnnotations(annotationInputStream, fileContents, lineSeparator);
+      annotationInputStream.close();
+      FilesPlume.writeFile(javaFile, result);
+    } catch (IOException e) {
+      System.err.println(
+          "Failed to insert annotations from file "
+              + annotationFilePath
+              + " into file "
+              + javaFilePath);
+      System.exit(1);
+    }
+  }
+
+  /**
+   * Inserts annotations from ajava files into Java files in place.
+   *
+   * <p>The first argument is an ajava file or a directory containing ajava files.
+   *
+   * <p>The second argument is a Java file or a directory containing Java files to insert
+   * annotations into.
+   *
+   * <p>For each Java file, checks if any ajava files from the first argument match it. For each
+   * such ajava file, inserts all its annotations into the Java file.
+   *
+   * @param args command line arguments: the first element should be a path to ajava files and the
+   *     second should be the directory containing Java files to insert into
+   */
+  public static void main(String[] args) {
+    if (args.length != 2) {
+      System.out.println(
+          "Usage: java InsertAjavaAnnotations <ajava-file-or-directory> <java-file-or-directory");
+      System.exit(1);
+    }
+
+    String ajavaDir = args[0];
+    String javaSourceDir = args[1];
+    AnnotationFileStore annotationFiles = new AnnotationFileStore();
+    annotationFiles.addFileOrDirectory(new File(ajavaDir));
+    InsertAjavaAnnotations inserter = new InsertAjavaAnnotations(createElements());
+    // For each Java file, this visitor inserts annotations into it.
+    FileVisitor<Path> insertionVisitor =
+        new SimpleFileVisitor<Path>() {
+          @Override
+          public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
+            if (!path.getFileName().toString().endsWith(".java")) {
+              return FileVisitResult.CONTINUE;
+            }
+
+            CompilationUnit root = null;
+            try {
+              root = JavaParserUtil.parseCompilationUnit(path.toFile());
+            } catch (IOException e) {
+              System.err.println("Failed to read file: " + path);
+              System.exit(1);
+            }
+
+            Set<String> annotationFilesForRoot = new LinkedHashSet<>();
+            for (TypeDeclaration<?> type : root.getTypes()) {
+              String name = JavaParserUtil.getFullyQualifiedName(type, root);
+              annotationFilesForRoot.addAll(annotationFiles.getAnnotationFileForType(name));
+            }
+
+            for (String annotationFile : annotationFilesForRoot) {
+              inserter.insertAnnotations(annotationFile, path.toString());
+            }
+
+            return FileVisitResult.CONTINUE;
+          }
+        };
+
+    try {
+      Files.walkFileTree(Paths.get(javaSourceDir), insertionVisitor);
+    } catch (IOException e) {
+      System.out.println("Error while adding annotations to: " + javaSourceDir);
+      e.printStackTrace();
+      System.exit(1);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java
new file mode 100644
index 0000000..908c5c7
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java
@@ -0,0 +1,2153 @@
+package org.checkerframework.framework.ajava;
+
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.ImportDeclaration;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.PackageDeclaration;
+import com.github.javaparser.ast.body.AnnotationDeclaration;
+import com.github.javaparser.ast.body.AnnotationMemberDeclaration;
+import com.github.javaparser.ast.body.BodyDeclaration;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.ConstructorDeclaration;
+import com.github.javaparser.ast.body.EnumConstantDeclaration;
+import com.github.javaparser.ast.body.EnumDeclaration;
+import com.github.javaparser.ast.body.InitializerDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.Parameter;
+import com.github.javaparser.ast.body.ReceiverParameter;
+import com.github.javaparser.ast.body.VariableDeclarator;
+import com.github.javaparser.ast.expr.ArrayAccessExpr;
+import com.github.javaparser.ast.expr.AssignExpr;
+import com.github.javaparser.ast.expr.BinaryExpr;
+import com.github.javaparser.ast.expr.CastExpr;
+import com.github.javaparser.ast.expr.ClassExpr;
+import com.github.javaparser.ast.expr.ConditionalExpr;
+import com.github.javaparser.ast.expr.EnclosedExpr;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.FieldAccessExpr;
+import com.github.javaparser.ast.expr.InstanceOfExpr;
+import com.github.javaparser.ast.expr.LambdaExpr;
+import com.github.javaparser.ast.expr.LiteralExpr;
+import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
+import com.github.javaparser.ast.expr.MemberValuePair;
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import com.github.javaparser.ast.expr.MethodReferenceExpr;
+import com.github.javaparser.ast.expr.Name;
+import com.github.javaparser.ast.expr.NameExpr;
+import com.github.javaparser.ast.expr.NormalAnnotationExpr;
+import com.github.javaparser.ast.expr.ObjectCreationExpr;
+import com.github.javaparser.ast.expr.SimpleName;
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
+import com.github.javaparser.ast.expr.SuperExpr;
+import com.github.javaparser.ast.expr.ThisExpr;
+import com.github.javaparser.ast.expr.TypeExpr;
+import com.github.javaparser.ast.expr.UnaryExpr;
+import com.github.javaparser.ast.modules.ModuleDeclaration;
+import com.github.javaparser.ast.modules.ModuleExportsDirective;
+import com.github.javaparser.ast.modules.ModuleOpensDirective;
+import com.github.javaparser.ast.modules.ModuleProvidesDirective;
+import com.github.javaparser.ast.modules.ModuleRequiresDirective;
+import com.github.javaparser.ast.modules.ModuleUsesDirective;
+import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations;
+import com.github.javaparser.ast.stmt.AssertStmt;
+import com.github.javaparser.ast.stmt.BlockStmt;
+import com.github.javaparser.ast.stmt.BreakStmt;
+import com.github.javaparser.ast.stmt.CatchClause;
+import com.github.javaparser.ast.stmt.ContinueStmt;
+import com.github.javaparser.ast.stmt.DoStmt;
+import com.github.javaparser.ast.stmt.EmptyStmt;
+import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
+import com.github.javaparser.ast.stmt.ExpressionStmt;
+import com.github.javaparser.ast.stmt.ForEachStmt;
+import com.github.javaparser.ast.stmt.ForStmt;
+import com.github.javaparser.ast.stmt.IfStmt;
+import com.github.javaparser.ast.stmt.LabeledStmt;
+import com.github.javaparser.ast.stmt.LocalClassDeclarationStmt;
+import com.github.javaparser.ast.stmt.ReturnStmt;
+import com.github.javaparser.ast.stmt.Statement;
+import com.github.javaparser.ast.stmt.SwitchEntry;
+import com.github.javaparser.ast.stmt.SwitchStmt;
+import com.github.javaparser.ast.stmt.SynchronizedStmt;
+import com.github.javaparser.ast.stmt.ThrowStmt;
+import com.github.javaparser.ast.stmt.TryStmt;
+import com.github.javaparser.ast.stmt.WhileStmt;
+import com.github.javaparser.ast.type.ArrayType;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import com.github.javaparser.ast.type.IntersectionType;
+import com.github.javaparser.ast.type.PrimitiveType;
+import com.github.javaparser.ast.type.TypeParameter;
+import com.github.javaparser.ast.type.UnionType;
+import com.github.javaparser.ast.type.VoidType;
+import com.github.javaparser.ast.type.WildcardType;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.PeekingIterator;
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.AssertTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.BreakTree;
+import com.sun.source.tree.CaseTree;
+import com.sun.source.tree.CatchTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ContinueTree;
+import com.sun.source.tree.DoWhileLoopTree;
+import com.sun.source.tree.EmptyStatementTree;
+import com.sun.source.tree.EnhancedForLoopTree;
+import com.sun.source.tree.ErroneousTree;
+import com.sun.source.tree.ExportsTree;
+import com.sun.source.tree.ExpressionStatementTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.ForLoopTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.IntersectionTypeTree;
+import com.sun.source.tree.LabeledStatementTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModifiersTree;
+import com.sun.source.tree.ModuleTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.OpensTree;
+import com.sun.source.tree.PackageTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.PrimitiveTypeTree;
+import com.sun.source.tree.ProvidesTree;
+import com.sun.source.tree.RequiresTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.SwitchTree;
+import com.sun.source.tree.SynchronizedTree;
+import com.sun.source.tree.ThrowTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.TreeVisitor;
+import com.sun.source.tree.TryTree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.TypeParameterTree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.UnionTypeTree;
+import com.sun.source.tree.UsesTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.tree.WhileLoopTree;
+import com.sun.source.tree.WildcardTree;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * A visitor that processes javac trees and JavaParser nodes simultaneously, matching corresponding
+ * nodes.
+ *
+ * <p>By default, visits all children of a javac tree along with corresponding JavaParser nodes. The
+ * JavaParser node corresponding to a javac tree is always passed as the secondary parameter to the
+ * {@code visit} methods.
+ *
+ * <p>To perform an action on a particular tree type, override one of the methods starting with
+ * "process". For each javac tree type JavacType, and for each possible JavaParser node type
+ * JavaParserNode that it may be matched to, this class contains a method {@code
+ * processJavacType(JavacTypeTree javacTree, JavaParserNode javaParserNode)}. These are named after
+ * the visit methods in {@code com.sun.source.tree.TreeVisitor}, but for each javac tree type there
+ * may be multiple process methods for each possible node type it could be matched to.
+ *
+ * <p>The {@code process} methods are called in pre-order. That is, process methods for a parent are
+ * called before its children.
+ */
+public abstract class JointJavacJavaParserVisitor implements TreeVisitor<Void, Node> {
+  @Override
+  public Void visitAnnotation(AnnotationTree javacTree, Node javaParserNode) {
+    // javac stores annotation arguments as assignments, so @MyAnno("myArg") is stored the same
+    // as @MyAnno(value="myArg") which has a single element argument list with an assignment.
+    if (javaParserNode instanceof MarkerAnnotationExpr) {
+      processAnnotation(javacTree, (MarkerAnnotationExpr) javaParserNode);
+    } else if (javaParserNode instanceof SingleMemberAnnotationExpr) {
+      SingleMemberAnnotationExpr node = (SingleMemberAnnotationExpr) javaParserNode;
+      processAnnotation(javacTree, node);
+      assert javacTree.getArguments().size() == 1;
+      ExpressionTree value = javacTree.getArguments().get(0);
+      assert value instanceof AssignmentTree;
+      AssignmentTree assignment = (AssignmentTree) value;
+      assert assignment.getVariable().getKind() == Kind.IDENTIFIER;
+      assert ((IdentifierTree) assignment.getVariable()).getName().contentEquals("value");
+      assignment.getExpression().accept(this, node.getMemberValue());
+    } else if (javaParserNode instanceof NormalAnnotationExpr) {
+      NormalAnnotationExpr node = (NormalAnnotationExpr) javaParserNode;
+      processAnnotation(javacTree, node);
+      assert javacTree.getArguments().size() == node.getPairs().size();
+      Iterator<MemberValuePair> argIter = node.getPairs().iterator();
+      for (ExpressionTree arg : javacTree.getArguments()) {
+        assert arg instanceof AssignmentTree;
+        AssignmentTree assignment = (AssignmentTree) arg;
+        IdentifierTree memberName = (IdentifierTree) assignment.getVariable();
+        MemberValuePair javaParserArg = argIter.next();
+        assert memberName.getName().contentEquals(javaParserArg.getNameAsString());
+        assignment.getExpression().accept(this, javaParserArg.getValue());
+      }
+    } else {
+      throwUnexpectedNodeType(javacTree, javaParserNode);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) {
+    castNode(NodeWithAnnotations.class, javaParserNode, javacTree);
+    processAnnotatedType(javacTree, javaParserNode);
+    javacTree.getUnderlyingType().accept(this, javaParserNode);
+    return null;
+  }
+
+  @Override
+  public Void visitArrayAccess(ArrayAccessTree javacTree, Node javaParserNode) {
+    ArrayAccessExpr node = castNode(ArrayAccessExpr.class, javaParserNode, javacTree);
+    processArrayAccess(javacTree, node);
+    javacTree.getExpression().accept(this, node.getName());
+    javacTree.getIndex().accept(this, node.getIndex());
+    return null;
+  }
+
+  @Override
+  public Void visitArrayType(ArrayTypeTree javacTree, Node javaParserNode) {
+    ArrayType node = castNode(ArrayType.class, javaParserNode, javacTree);
+    processArrayType(javacTree, node);
+    javacTree.getType().accept(this, node.getComponentType());
+    return null;
+  }
+
+  @Override
+  public Void visitAssert(AssertTree javacTree, Node javaParserNode) {
+    AssertStmt node = castNode(AssertStmt.class, javaParserNode, javacTree);
+    processAssert(javacTree, node);
+    javacTree.getCondition().accept(this, node.getCheck());
+    visitOptional(javacTree.getDetail(), node.getMessage());
+
+    return null;
+  }
+
+  @Override
+  public Void visitAssignment(AssignmentTree javacTree, Node javaParserNode) {
+    AssignExpr node = castNode(AssignExpr.class, javaParserNode, javacTree);
+    processAssignment(javacTree, node);
+    javacTree.getVariable().accept(this, node.getTarget());
+    javacTree.getExpression().accept(this, node.getValue());
+    return null;
+  }
+
+  @Override
+  public Void visitBinary(BinaryTree javacTree, Node javaParserNode) {
+    BinaryExpr node = castNode(BinaryExpr.class, javaParserNode, javacTree);
+    processBinary(javacTree, node);
+    javacTree.getLeftOperand().accept(this, node.getLeft());
+    javacTree.getRightOperand().accept(this, node.getRight());
+    return null;
+  }
+
+  @Override
+  public Void visitBlock(BlockTree javacTree, Node javaParserNode) {
+    if (javaParserNode instanceof InitializerDeclaration) {
+      return javacTree.accept(this, ((InitializerDeclaration) javaParserNode).getBody());
+    }
+
+    BlockStmt node = castNode(BlockStmt.class, javaParserNode, javacTree);
+    processBlock(javacTree, node);
+    processStatements(javacTree.getStatements(), node.getStatements());
+    return null;
+  }
+
+  /**
+   * Given a matching sequence of statements for a block, visits each javac statement with its
+   * corresponding JavaParser statement, excluding synthetic javac trees like no-argument
+   * constructors.
+   *
+   * @param javacStatements sequence of javac trees for statements
+   * @param javaParserStatements sequence of JavaParser statements representing the same block as
+   *     {@code javacStatements}
+   */
+  private void processStatements(
+      Iterable<? extends StatementTree> javacStatements, Iterable<Statement> javaParserStatements) {
+    PeekingIterator<StatementTree> javacIter =
+        Iterators.peekingIterator(javacStatements.iterator());
+    PeekingIterator<Statement> javaParserIter =
+        Iterators.peekingIterator(javaParserStatements.iterator());
+
+    while (javacIter.hasNext() || javaParserIter.hasNext()) {
+      // Skip synthetic javac super() calls by checking if the JavaParser statement matches.
+      if (javacIter.hasNext()
+          && isDefaultSuperConstructorCall(javacIter.peek())
+          && (!javaParserIter.hasNext() || !isDefaultSuperConstructorCall(javaParserIter.peek()))) {
+        javacIter.next();
+        continue;
+      }
+
+      // In javac, a line like "int i = 0, j = 0" is expanded as two sibling VariableTree
+      // instances. In javaParser this is one VariableDeclarationExpr with two nested
+      // VariableDeclarators. Match the declarators with the VariableTrees.
+      if (javaParserIter.hasNext()
+          && javaParserIter.peek().isExpressionStmt()
+          && javaParserIter.peek().asExpressionStmt().getExpression().isVariableDeclarationExpr()) {
+        for (VariableDeclarator decl :
+            javaParserIter
+                .next()
+                .asExpressionStmt()
+                .getExpression()
+                .asVariableDeclarationExpr()
+                .getVariables()) {
+          assert javacIter.hasNext();
+          assert javacIter.peek().getKind() == Kind.VARIABLE;
+          javacIter.next().accept(this, decl);
+        }
+
+        continue;
+      }
+
+      assert javacIter.hasNext();
+      assert javaParserIter.hasNext();
+      javacIter.next().accept(this, javaParserIter.next());
+    }
+
+    assert !javacIter.hasNext();
+    assert !javaParserIter.hasNext();
+  }
+
+  /**
+   * Returns whether a javac statement represents a method call {@code super()}.
+   *
+   * @param statement the javac statement to check
+   * @return true if statement is a method invocation named "super" with no arguments, false
+   *     otherwise
+   */
+  public static boolean isDefaultSuperConstructorCall(StatementTree statement) {
+    if (statement.getKind() != Kind.EXPRESSION_STATEMENT) {
+      return false;
+    }
+
+    ExpressionStatementTree expressionStatement = (ExpressionStatementTree) statement;
+    if (expressionStatement.getExpression().getKind() != Kind.METHOD_INVOCATION) {
+      return false;
+    }
+
+    MethodInvocationTree invocation = (MethodInvocationTree) expressionStatement.getExpression();
+    if (invocation.getMethodSelect().getKind() != Kind.IDENTIFIER) {
+      return false;
+    }
+
+    if (!((IdentifierTree) invocation.getMethodSelect()).getName().contentEquals("super")) {
+      return false;
+    }
+
+    return invocation.getArguments().isEmpty();
+  }
+
+  /**
+   * Returns whether a JavaParser statement represents a method call {@code super()}.
+   *
+   * @param statement the JavaParser statement to check
+   * @return true if statement is an explicit super constructor invocation with no arguments
+   */
+  private boolean isDefaultSuperConstructorCall(Statement statement) {
+    if (!statement.isExplicitConstructorInvocationStmt()) {
+      return false;
+    }
+
+    ExplicitConstructorInvocationStmt invocation = statement.asExplicitConstructorInvocationStmt();
+    boolean isSuper = !invocation.isThis();
+    return isSuper && invocation.getArguments().isEmpty();
+  }
+
+  @Override
+  public Void visitBreak(BreakTree javacTree, Node javaParserNode) {
+    BreakStmt node = castNode(BreakStmt.class, javaParserNode, javacTree);
+    processBreak(javacTree, node);
+    return null;
+  }
+
+  @Override
+  public Void visitCase(CaseTree javacTree, Node javaParserNode) {
+    SwitchEntry node = castNode(SwitchEntry.class, javaParserNode, javacTree);
+    processCase(javacTree, node);
+    // The expression is null if and only if the case is the default case.
+    // Java 12 introduced multiple label cases, but expressions should contain at most one
+    // element for Java 11 and below.
+    List<Expression> expressions = node.getLabels();
+    if (javacTree.getExpression() == null) {
+      assert expressions.isEmpty();
+    } else {
+      assert expressions.size() == 1;
+      javacTree.getExpression().accept(this, expressions.get(0));
+    }
+
+    processStatements(javacTree.getStatements(), node.getStatements());
+    return null;
+  }
+
+  @Override
+  public Void visitCatch(CatchTree javacTree, Node javaParserNode) {
+    CatchClause node = castNode(CatchClause.class, javaParserNode, javacTree);
+    processCatch(javacTree, node);
+    javacTree.getParameter().accept(this, node.getParameter());
+    javacTree.getBlock().accept(this, node.getBody());
+    return null;
+  }
+
+  @Override
+  public Void visitClass(ClassTree javacTree, Node javaParserNode) {
+    if (javaParserNode instanceof ClassOrInterfaceDeclaration) {
+      ClassOrInterfaceDeclaration node = (ClassOrInterfaceDeclaration) javaParserNode;
+      processClass(javacTree, node);
+      visitLists(javacTree.getTypeParameters(), node.getTypeParameters());
+
+      if (javacTree.getKind() == Kind.CLASS) {
+        if (javacTree.getExtendsClause() == null) {
+          assert node.getExtendedTypes().isEmpty();
+        } else {
+          assert node.getExtendedTypes().size() == 1;
+          javacTree.getExtendsClause().accept(this, node.getExtendedTypes().get(0));
+        }
+
+        visitLists(javacTree.getImplementsClause(), node.getImplementedTypes());
+      } else if (javacTree.getKind() == Kind.INTERFACE) {
+        visitLists(javacTree.getImplementsClause(), node.getExtendedTypes());
+      }
+
+      visitClassMembers(javacTree.getMembers(), node.getMembers());
+    } else if (javaParserNode instanceof AnnotationDeclaration) {
+      AnnotationDeclaration node = (AnnotationDeclaration) javaParserNode;
+      processClass(javacTree, node);
+      visitClassMembers(javacTree.getMembers(), node.getMembers());
+    } else if (javaParserNode instanceof LocalClassDeclarationStmt) {
+      javacTree.accept(this, ((LocalClassDeclarationStmt) javaParserNode).getClassDeclaration());
+    } else if (javaParserNode instanceof EnumDeclaration) {
+      EnumDeclaration node = (EnumDeclaration) javaParserNode;
+      processClass(javacTree, node);
+      visitLists(javacTree.getImplementsClause(), node.getImplementedTypes());
+      // In an enum declaration, javac stores the enum constants expanded as constant variable
+      // members, whereas JavaParser stores them as one object.  Need to match them.
+      assert javacTree.getKind() == Kind.ENUM;
+      List<Tree> javacMembers = new ArrayList<>(javacTree.getMembers());
+      // Discard a synthetic constructor if it exists.  If there are any constants in this
+      // enum, then they will show up as the first members of the javac tree, except for
+      // possibly a synthetic constructor.
+      if (!node.getEntries().isEmpty()) {
+        while (!javacMembers.isEmpty() && javacMembers.get(0).getKind() != Kind.VARIABLE) {
+          javacMembers.remove(0);
+        }
+      }
+
+      for (EnumConstantDeclaration entry : node.getEntries()) {
+        assert !javacMembers.isEmpty();
+        javacMembers.get(0).accept(this, entry);
+        javacMembers.remove(0);
+      }
+
+      visitClassMembers(javacMembers, node.getMembers());
+    } else {
+      throwUnexpectedNodeType(javacTree, javaParserNode);
+    }
+
+    return null;
+  }
+
+  /**
+   * Given a list of class members for javac and JavaParser, visits each javac member with its
+   * corresponding JavaParser member. Skips synthetic javac members.
+   *
+   * @param javacMembers a list of trees forming the members of a javac {@code ClassTree}
+   * @param javaParserMembers a list of nodes forming the members of a JavaParser {@code
+   *     ClassOrInterfaceDeclaration} or an {@code ObjectCreationExpr} with an anonymous class body
+   *     that corresponds to {@code javacMembers}
+   */
+  private void visitClassMembers(
+      List<? extends Tree> javacMembers, List<BodyDeclaration<?>> javaParserMembers) {
+    PeekingIterator<Tree> javacIter = Iterators.peekingIterator(javacMembers.iterator());
+    PeekingIterator<BodyDeclaration<?>> javaParserIter =
+        Iterators.peekingIterator(javaParserMembers.iterator());
+    while (javacIter.hasNext() || javaParserIter.hasNext()) {
+      // Skip javac's synthetic no-argument constructors.
+      if (javacIter.hasNext()
+          && isNoArgumentConstructor(javacIter.peek())
+          && (!javaParserIter.hasNext() || !isNoArgumentConstructor(javaParserIter.peek()))) {
+        javacIter.next();
+        continue;
+      }
+
+      // In javac, a line like int i = 0, j = 0 is expanded as two sibling VariableTree
+      // instances. In JavaParser this is one FieldDeclaration with two nested
+      // VariableDeclarators. Match the declarators with the VariableTrees.
+      if (javaParserIter.hasNext() && javaParserIter.peek().isFieldDeclaration()) {
+        for (VariableDeclarator decl : javaParserIter.next().asFieldDeclaration().getVariables()) {
+          assert javacIter.hasNext();
+          assert javacIter.peek().getKind() == Kind.VARIABLE;
+          javacIter.next().accept(this, decl);
+        }
+
+        continue;
+      }
+
+      assert javacIter.hasNext();
+      assert javaParserIter.hasNext();
+      javacIter.next().accept(this, javaParserIter.next());
+    }
+
+    assert !javacIter.hasNext();
+    assert !javaParserIter.hasNext();
+  }
+
+  /**
+   * Visits the the members of an anonymous class body.
+   *
+   * <p>In normal classes, javac inserts a synthetic no-argument constructor if no constructor is
+   * explicitly defined, which is skipped when visiting members. Anonymous class bodies may
+   * introduce constructors that take arguments if the constructor invocation that created them was
+   * passed arguments. For example, if {@code MyClass} has a constructor taking a single integer
+   * argument, then writing {@code new MyClass(5) { }} expands to the javac tree
+   *
+   * <pre>{@code
+   * new MyClass(5) {
+   *     (int arg) {
+   *         super(arg);
+   *     }
+   * }
+   * }</pre>
+   *
+   * <p>This method skips these synthetic constructors.
+   *
+   * @param javacBody body of an anonymous class body
+   * @param javaParserMembers list of members for the anonymous class body of an {@code
+   *     ObjectCreationExpr}
+   */
+  public void visitAnonymousClassBody(
+      ClassTree javacBody, List<BodyDeclaration<?>> javaParserMembers) {
+    List<Tree> javacMembers = new ArrayList<>(javacBody.getMembers());
+    if (!javacMembers.isEmpty()) {
+      Tree member = javacMembers.get(0);
+      if (member.getKind() == Kind.METHOD) {
+        MethodTree methodTree = (MethodTree) member;
+        if (methodTree.getName().contentEquals("<init>")) {
+          javacMembers.remove(0);
+        }
+      }
+    }
+
+    visitClassMembers(javacMembers, javaParserMembers);
+  }
+
+  /**
+   * Returns whether {@code member} is a javac constructor declaration that takes no arguments.
+   *
+   * @param member the javac tree to check
+   * @return true if {@code member} is a method declaration with name {@code <init>} that takes no
+   *     arguments
+   */
+  public static boolean isNoArgumentConstructor(Tree member) {
+    if (member.getKind() != Kind.METHOD) {
+      return false;
+    }
+
+    MethodTree methodTree = (MethodTree) member;
+    return methodTree.getName().contentEquals("<init>") && methodTree.getParameters().isEmpty();
+  }
+
+  /**
+   * Returns whether {@code member} is a JavaParser constructor declaration that takes no arguments.
+   *
+   * @param member the JavaParser body declaration to check
+   * @return true if {@code member} is a constructor declaration that takes no arguments
+   */
+  private boolean isNoArgumentConstructor(BodyDeclaration<?> member) {
+    return member.isConstructorDeclaration()
+        && member.asConstructorDeclaration().getParameters().isEmpty();
+  }
+
+  @Override
+  public Void visitCompilationUnit(CompilationUnitTree javacTree, Node javaParserNode) {
+    CompilationUnit node = castNode(CompilationUnit.class, javaParserNode, javacTree);
+    processCompilationUnit(javacTree, node);
+    visitOptional(javacTree.getPackage(), node.getPackageDeclaration());
+    visitLists(javacTree.getImports(), node.getImports());
+    visitLists(javacTree.getTypeDecls(), node.getTypes());
+    return null;
+  }
+
+  @Override
+  public Void visitCompoundAssignment(CompoundAssignmentTree javacTree, Node javaParserNode) {
+    AssignExpr node = castNode(AssignExpr.class, javaParserNode, javacTree);
+    processCompoundAssignment(javacTree, node);
+    javacTree.getVariable().accept(this, node.getTarget());
+    javacTree.getExpression().accept(this, node.getValue());
+    return null;
+  }
+
+  @Override
+  public Void visitConditionalExpression(ConditionalExpressionTree javacTree, Node javaParserNode) {
+    ConditionalExpr node = castNode(ConditionalExpr.class, javaParserNode, javacTree);
+    processConditionalExpression(javacTree, node);
+    javacTree.getCondition().accept(this, node.getCondition());
+    javacTree.getTrueExpression().accept(this, node.getThenExpr());
+    javacTree.getFalseExpression().accept(this, node.getElseExpr());
+    return null;
+  }
+
+  @Override
+  public Void visitContinue(ContinueTree javacTree, Node javaParserNode) {
+    ContinueStmt node = castNode(ContinueStmt.class, javaParserNode, javacTree);
+    processContinue(javacTree, node);
+    return null;
+  }
+
+  @Override
+  public Void visitDoWhileLoop(DoWhileLoopTree javacTree, Node javaParserNode) {
+    DoStmt node = castNode(DoStmt.class, javaParserNode, javacTree);
+    processDoWhileLoop(javacTree, node);
+    // In javac the condition is parenthesized but not in JavaParser.
+    ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression();
+    condition.accept(this, node.getCondition());
+    javacTree.getStatement().accept(this, node.getBody());
+    return null;
+  }
+
+  @Override
+  public Void visitEmptyStatement(EmptyStatementTree javacTree, Node javaParserNode) {
+    EmptyStmt node = castNode(EmptyStmt.class, javaParserNode, javacTree);
+    processEmptyStatement(javacTree, node);
+    return null;
+  }
+
+  @Override
+  public Void visitEnhancedForLoop(EnhancedForLoopTree javacTree, Node javaParserNode) {
+    ForEachStmt node = castNode(ForEachStmt.class, javaParserNode, javacTree);
+    processEnhancedForLoop(javacTree, node);
+    javacTree.getVariable().accept(this, node.getVariableDeclarator());
+    javacTree.getExpression().accept(this, node.getIterable());
+    javacTree.getStatement().accept(this, node.getBody());
+    return null;
+  }
+
+  @Override
+  public Void visitErroneous(ErroneousTree javacTree, Node javaParserNode) {
+    // An erroneous tree is a malformed expression, so skip.
+    return null;
+  }
+
+  @Override
+  public Void visitExports(ExportsTree javacTree, Node javaParserNode) {
+    ModuleExportsDirective node = castNode(ModuleExportsDirective.class, javaParserNode, javacTree);
+    processExports(javacTree, node);
+    visitLists(javacTree.getModuleNames(), node.getModuleNames());
+    javacTree.getPackageName().accept(this, node.getName());
+    return null;
+  }
+
+  @Override
+  public Void visitExpressionStatement(ExpressionStatementTree javacTree, Node javaParserNode) {
+    if (javaParserNode instanceof ExpressionStmt) {
+      ExpressionStmt node = (ExpressionStmt) javaParserNode;
+      processExpressionStatemen(javacTree, node);
+      javacTree.getExpression().accept(this, node.getExpression());
+    } else if (javaParserNode instanceof ExplicitConstructorInvocationStmt) {
+      // In this case the javac expression will be a MethodTree. Since JavaParser doesn't
+      // surround explicit constructor invocations in an expression statement, we match
+      // javaParserNode to the javac expression rather than the javac expression statement.
+      javacTree.getExpression().accept(this, javaParserNode);
+    } else {
+      throwUnexpectedNodeType(javacTree, javaParserNode);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitForLoop(ForLoopTree javacTree, Node javaParserNode) {
+    ForStmt node = castNode(ForStmt.class, javaParserNode, javacTree);
+    processForLoop(javacTree, node);
+    Iterator<? extends StatementTree> javacInitializers = javacTree.getInitializer().iterator();
+    for (Expression initializer : node.getInitialization()) {
+      if (initializer.isVariableDeclarationExpr()) {
+        for (VariableDeclarator declarator :
+            initializer.asVariableDeclarationExpr().getVariables()) {
+          assert javacInitializers.hasNext();
+          javacInitializers.next().accept(this, declarator);
+        }
+      } else if (initializer.isAssignExpr()) {
+        ExpressionStatementTree statement = (ExpressionStatementTree) javacInitializers.next();
+        statement.getExpression().accept(this, initializer);
+      } else {
+        assert javacInitializers.hasNext();
+        javacInitializers.next().accept(this, initializer);
+      }
+    }
+    assert !javacInitializers.hasNext();
+
+    visitOptional(javacTree.getCondition(), node.getCompare());
+
+    // Javac stores a list of expression statements and JavaParser stores a list of statements,
+    // the javac statements must be unwrapped.
+    assert javacTree.getUpdate().size() == node.getUpdate().size();
+    Iterator<Expression> javaParserUpdates = node.getUpdate().iterator();
+    for (ExpressionStatementTree javacUpdate : javacTree.getUpdate()) {
+      // Match the inner javac expression with the JavaParser expression.
+      javacUpdate.getExpression().accept(this, javaParserUpdates.next());
+    }
+
+    javacTree.getStatement().accept(this, node.getBody());
+    return null;
+  }
+
+  @Override
+  public Void visitIdentifier(IdentifierTree javacTree, Node javaParserNode) {
+    if (javaParserNode instanceof ClassOrInterfaceType) {
+      processIdentifier(javacTree, (ClassOrInterfaceType) javaParserNode);
+    } else if (javaParserNode instanceof Name) {
+      processIdentifier(javacTree, (Name) javaParserNode);
+    } else if (javaParserNode instanceof NameExpr) {
+      processIdentifier(javacTree, (NameExpr) javaParserNode);
+    } else if (javaParserNode instanceof SimpleName) {
+      processIdentifier(javacTree, (SimpleName) javaParserNode);
+    } else if (javaParserNode instanceof ThisExpr) {
+      processIdentifier(javacTree, (ThisExpr) javaParserNode);
+    } else if (javaParserNode instanceof SuperExpr) {
+      processIdentifier(javacTree, (SuperExpr) javaParserNode);
+    } else if (javaParserNode instanceof TypeExpr) {
+      // This occurs in a member reference like MyClass::myMember. The MyClass is wrapped in a
+      // TypeExpr.
+      javacTree.accept(this, ((TypeExpr) javaParserNode).getType());
+    } else {
+      throwUnexpectedNodeType(javacTree, javaParserNode);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitIf(IfTree javacTree, Node javaParserNode) {
+    IfStmt node = castNode(IfStmt.class, javaParserNode, javacTree);
+    processIf(javacTree, node);
+    assert javacTree.getCondition().getKind() == Kind.PARENTHESIZED;
+    ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression();
+    condition.accept(this, node.getCondition());
+    javacTree.getThenStatement().accept(this, node.getThenStmt());
+    visitOptional(javacTree.getElseStatement(), node.getElseStmt());
+
+    return null;
+  }
+
+  @Override
+  public Void visitImport(ImportTree javacTree, Node javaParserNode) {
+    ImportDeclaration node = castNode(ImportDeclaration.class, javaParserNode, javacTree);
+    processImport(javacTree, node);
+    // In javac trees, a name like "a.*" is stored as a member select, but JavaParser just
+    // stores "a" and records that the name ends in an asterisk.
+    if (node.isAsterisk()) {
+      assert javacTree.getQualifiedIdentifier().getKind() == Kind.MEMBER_SELECT;
+      MemberSelectTree identifier = (MemberSelectTree) javacTree.getQualifiedIdentifier();
+      identifier.getExpression().accept(this, node.getName());
+    } else {
+      javacTree.getQualifiedIdentifier().accept(this, node.getName());
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitInstanceOf(InstanceOfTree javacTree, Node javaParserNode) {
+    InstanceOfExpr node = castNode(InstanceOfExpr.class, javaParserNode, javacTree);
+    processInstanceOf(javacTree, node);
+    javacTree.getExpression().accept(this, node.getExpression());
+    javacTree.getType().accept(this, node.getType());
+    return null;
+  }
+
+  @Override
+  public Void visitIntersectionType(IntersectionTypeTree javacTree, Node javaParserNode) {
+    IntersectionType node = castNode(IntersectionType.class, javaParserNode, javacTree);
+    processIntersectionType(javacTree, node);
+    visitLists(javacTree.getBounds(), node.getElements());
+    return null;
+  }
+
+  @Override
+  public Void visitLabeledStatement(LabeledStatementTree javacTree, Node javaParserNode) {
+    LabeledStmt node = castNode(LabeledStmt.class, javaParserNode, javacTree);
+    processLabeledStatement(javacTree, node);
+    javacTree.getStatement().accept(this, node.getStatement());
+    return null;
+  }
+
+  @Override
+  public Void visitLambdaExpression(LambdaExpressionTree javacTree, Node javaParserNode) {
+    LambdaExpr node = castNode(LambdaExpr.class, javaParserNode, javacTree);
+    processLambdaExpression(javacTree, node);
+    visitLists(javacTree.getParameters(), node.getParameters());
+    switch (javacTree.getBodyKind()) {
+      case EXPRESSION:
+        assert node.getBody() instanceof ExpressionStmt;
+        ExpressionStmt body = (ExpressionStmt) node.getBody();
+        javacTree.getBody().accept(this, body.getExpression());
+        break;
+      case STATEMENT:
+        javacTree.getBody().accept(this, node.getBody());
+        break;
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitLiteral(LiteralTree javacTree, Node javaParserNode) {
+    if (javaParserNode instanceof LiteralExpr) {
+      processLiteral(javacTree, (LiteralExpr) javaParserNode);
+    } else if (javaParserNode instanceof UnaryExpr) {
+      // Occurs for negative literals such as -7.
+      processLiteral(javacTree, (UnaryExpr) javaParserNode);
+    } else if (javaParserNode instanceof BinaryExpr) {
+      // Occurs for expression like "a" + "b" where javac compresses them to "ab" but
+      // JavaParser doesn't.
+      processLiteral(javacTree, (BinaryExpr) javaParserNode);
+    } else {
+      throwUnexpectedNodeType(javacTree, javaParserNode);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitMemberReference(MemberReferenceTree javacTree, Node javaParserNode) {
+    MethodReferenceExpr node = castNode(MethodReferenceExpr.class, javaParserNode, javacTree);
+    processMemberReference(javacTree, node);
+    if (node.getScope().isTypeExpr()) {
+      javacTree.getQualifierExpression().accept(this, node.getScope().asTypeExpr().getType());
+    } else {
+      javacTree.getQualifierExpression().accept(this, node.getScope());
+    }
+
+    assert (javacTree.getTypeArguments() != null) == node.getTypeArguments().isPresent();
+    if (javacTree.getTypeArguments() != null) {
+      visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get());
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitMemberSelect(MemberSelectTree javacTree, Node javaParserNode) {
+    if (javaParserNode instanceof FieldAccessExpr) {
+      FieldAccessExpr node = (FieldAccessExpr) javaParserNode;
+      processMemberSelect(javacTree, node);
+      javacTree.getExpression().accept(this, node.getScope());
+    } else if (javaParserNode instanceof Name) {
+      Name node = (Name) javaParserNode;
+      processMemberSelect(javacTree, node);
+      assert node.getQualifier().isPresent();
+      javacTree.getExpression().accept(this, node.getQualifier().get());
+    } else if (javaParserNode instanceof ClassOrInterfaceType) {
+      ClassOrInterfaceType node = (ClassOrInterfaceType) javaParserNode;
+      processMemberSelect(javacTree, node);
+      assert node.getScope().isPresent();
+      javacTree.getExpression().accept(this, node.getScope().get());
+    } else if (javaParserNode instanceof ClassExpr) {
+      ClassExpr node = (ClassExpr) javaParserNode;
+      processMemberSelect(javacTree, node);
+      javacTree.getExpression().accept(this, node.getType());
+    } else if (javaParserNode instanceof ThisExpr) {
+      ThisExpr node = (ThisExpr) javaParserNode;
+      processMemberSelect(javacTree, node);
+      assert node.getTypeName().isPresent();
+      javacTree.getExpression().accept(this, node.getTypeName().get());
+    } else if (javaParserNode instanceof SuperExpr) {
+      SuperExpr node = (SuperExpr) javaParserNode;
+      processMemberSelect(javacTree, node);
+      assert node.getTypeName().isPresent();
+      javacTree.getExpression().accept(this, node.getTypeName().get());
+    } else {
+      throwUnexpectedNodeType(javacTree, javaParserNode);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitMethod(MethodTree javacTree, Node javaParserNode) {
+    if (javaParserNode instanceof MethodDeclaration) {
+      visitMethodForMethodDeclaration(javacTree, (MethodDeclaration) javaParserNode);
+    } else if (javaParserNode instanceof ConstructorDeclaration) {
+      visitMethodForConstructorDeclaration(javacTree, (ConstructorDeclaration) javaParserNode);
+    } else if (javaParserNode instanceof AnnotationMemberDeclaration) {
+      visitMethodForAnnotationMemberDeclaration(
+          javacTree, (AnnotationMemberDeclaration) javaParserNode);
+    } else {
+      throwUnexpectedNodeType(javacTree, javaParserNode);
+      throw new BugInCF("unreachable");
+    }
+    return null;
+  }
+
+  /**
+   * Visits a method declaration in the case where the matched JavaParser node was a {@code
+   * MethodDeclaration}.
+   *
+   * @param javacTree method declaration to visit
+   * @param javaParserNode corresponding JavaParser method declaration
+   */
+  private void visitMethodForMethodDeclaration(
+      MethodTree javacTree, MethodDeclaration javaParserNode) {
+    processMethod(javacTree, javaParserNode);
+    // TODO: Handle modifiers. In javac this is a ModifiersTree but in JavaParser it's a list of
+    // modifiers. This is a problem because a ModifiersTree has separate accessors to
+    // annotations and other modifiers, so the order doesn't match. It might be that for
+    // JavaParser, the annotations and other modifiers are also accessed separately.
+    javacTree.getReturnType().accept(this, javaParserNode.getType());
+    // Unlike other javac constructs, the javac list is non-null even if no type parameters are
+    // present.
+    visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters());
+    // JavaParser sometimes inserts a receiver parameter that is not present in the source code.
+    // (Example: on an explicitly-written toString for an enum class.)
+    if (javacTree.getReceiverParameter() != null
+        && javaParserNode.getReceiverParameter().isPresent()) {
+      javacTree.getReceiverParameter().accept(this, javaParserNode.getReceiverParameter().get());
+    }
+
+    visitLists(javacTree.getParameters(), javaParserNode.getParameters());
+
+    visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions());
+    visitOptional(javacTree.getBody(), javaParserNode.getBody());
+  }
+
+  /**
+   * Visits a method declaration in the case where the matched JavaParser node was a {@code
+   * ConstructorDeclaration}.
+   *
+   * @param javacTree method declaration to visit
+   * @param javaParserNode corresponding JavaParser constructor declaration
+   */
+  private void visitMethodForConstructorDeclaration(
+      MethodTree javacTree, ConstructorDeclaration javaParserNode) {
+    processMethod(javacTree, javaParserNode);
+    visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters());
+    visitOptional(javacTree.getReceiverParameter(), javaParserNode.getReceiverParameter());
+    visitLists(javacTree.getParameters(), javaParserNode.getParameters());
+    visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions());
+    javacTree.getBody().accept(this, javaParserNode.getBody());
+  }
+
+  /**
+   * Visits a method declaration in the case where the matched JavaParser node was a {@code
+   * AnnotationMemberDeclaration}.
+   *
+   * @param javacTree method declaration to visit
+   * @param javaParserNode corresponding JavaParser annotation member declaration
+   */
+  private void visitMethodForAnnotationMemberDeclaration(
+      MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) {
+    processMethod(javacTree, javaParserNode);
+    javacTree.getReturnType().accept(this, javaParserNode.getType());
+    visitOptional(javacTree.getDefaultValue(), javaParserNode.getDefaultValue());
+  }
+
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree javacTree, Node javaParserNode) {
+    if (javaParserNode instanceof MethodCallExpr) {
+      MethodCallExpr node = (MethodCallExpr) javaParserNode;
+      processMethodInvocation(javacTree, node);
+      // In javac, the type arguments will be empty if no type arguments are specified, but in
+      // JavaParser the type arguments will have the none Optional value.
+      assert javacTree.getTypeArguments().isEmpty() != node.getTypeArguments().isPresent();
+      if (!javacTree.getTypeArguments().isEmpty()) {
+        visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get());
+      }
+
+      // In JavaParser, the method name itself and receiver are stored as fields of the
+      // invocation itself, but in javac they might be combined into one MemberSelectTree.
+      // That member select may also be a single IdentifierTree if no receiver was written.
+      // This requires one layer of unnesting.
+      ExpressionTree methodSelect = javacTree.getMethodSelect();
+      if (methodSelect.getKind() == Kind.IDENTIFIER) {
+        methodSelect.accept(this, node.getName());
+      } else if (methodSelect.getKind() == Kind.MEMBER_SELECT) {
+        MemberSelectTree selection = (MemberSelectTree) methodSelect;
+        assert node.getScope().isPresent();
+        selection.getExpression().accept(this, node.getScope().get());
+      } else {
+        throw new BugInCF("Unexpected method selection type: %s", methodSelect);
+      }
+
+      visitLists(javacTree.getArguments(), node.getArguments());
+    } else if (javaParserNode instanceof ExplicitConstructorInvocationStmt) {
+      ExplicitConstructorInvocationStmt node = (ExplicitConstructorInvocationStmt) javaParserNode;
+      processMethodInvocation(javacTree, node);
+      assert javacTree.getTypeArguments().isEmpty() != node.getTypeArguments().isPresent();
+      if (!javacTree.getTypeArguments().isEmpty()) {
+        visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get());
+      }
+
+      visitLists(javacTree.getArguments(), node.getArguments());
+    } else {
+      throwUnexpectedNodeType(javacTree, javaParserNode);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitModifiers(ModifiersTree arg0, Node arg1) {
+    // TODO How to handle this? I don't think there's a corresponding JavaParser class, maybe
+    // the NodeWithModifiers interface?
+    return null;
+  }
+
+  @Override
+  public Void visitModule(ModuleTree javacTree, Node javaParserNode) {
+    ModuleDeclaration node = castNode(ModuleDeclaration.class, javaParserNode, javacTree);
+    processModule(javacTree, node);
+    javacTree.getName().accept(this, node.getName());
+    return null;
+  }
+
+  @Override
+  public Void visitNewArray(NewArrayTree javacTree, Node javaParserNode) {
+    // TODO: Implement this.
+    //
+    // Some notes:
+    // - javacTree.getAnnotations() seems to always return empty, any annotations on the base
+    // type seem to go on the type itself in javacTree.getType(). The JavaParser version doesn't
+    // even have a corresponding getAnnotations method.
+    // - When there are no initializers, both systems use similar representations. The
+    // dimensions line up.
+    // - When there is an initializer, they differ greatly for multi-dimensional arrays. Javac
+    // turns an expression like new int[][]{{1, 2}, {3, 4}} into a single NewArray tree with
+    // type int[] and two initializer elements {1, 2} and {3, 4}. However, for each of the
+    // sub-initializers, it creates an implicit NewArray tree with a null component type.
+    // JavaParser keeps the whole expression as one ArrayCreationExpr with multiple dimensions
+    // and the initializer stored in special ArrayInitializerExpr type.
+    return null;
+  }
+
+  @Override
+  public Void visitNewClass(NewClassTree javacTree, Node javaParserNode) {
+    ObjectCreationExpr node = castNode(ObjectCreationExpr.class, javaParserNode, javacTree);
+    processNewClass(javacTree, node);
+    // When using Java 11 javac, an expression like this.new MyInnerClass() would store "this"
+    // as the enclosing expression. In Java 8 javac, this would be stored as new
+    // MyInnerClass(this).  So, we only traverse the enclosing expression if present in both.
+    if (javacTree.getEnclosingExpression() != null && node.getScope().isPresent()) {
+      javacTree.getEnclosingExpression().accept(this, node.getScope().get());
+    }
+
+    javacTree.getIdentifier().accept(this, node.getType());
+    if (javacTree.getTypeArguments().isEmpty()) {
+      assert !node.getTypeArguments().isPresent();
+    } else {
+      assert node.getTypeArguments().isPresent();
+      visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get());
+    }
+
+    // Remove synthetic javac argument. When using Java 11, an expression like this.new
+    // MyInnerClass() would store "this" as the enclosing expression. In Java 8, this would be
+    // stored as new MyInnerClass(this). So, for the argument lists to match, we may have to
+    // remove the first argument.
+    List<? extends ExpressionTree> javacArgs = new ArrayList<>(javacTree.getArguments());
+    if (javacArgs.size() > node.getArguments().size()) {
+      javacArgs.remove(0);
+    }
+
+    visitLists(javacArgs, node.getArguments());
+    assert (javacTree.getClassBody() != null) == node.getAnonymousClassBody().isPresent();
+    if (javacTree.getClassBody() != null) {
+      visitAnonymousClassBody(javacTree.getClassBody(), node.getAnonymousClassBody().get());
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitOpens(OpensTree javacTree, Node javaParserNode) {
+    ModuleOpensDirective node = castNode(ModuleOpensDirective.class, javaParserNode, javacTree);
+    processOpens(javacTree, node);
+    javacTree.getPackageName().accept(this, node.getName());
+    visitLists(javacTree.getModuleNames(), node.getModuleNames());
+    return null;
+  }
+
+  @Override
+  public Void visitOther(Tree javacTree, Node javaParserNode) {
+    processOther(javacTree, javaParserNode);
+    return null;
+  }
+
+  @Override
+  public Void visitPackage(PackageTree javacTree, Node javaParserNode) {
+    PackageDeclaration node = castNode(PackageDeclaration.class, javaParserNode, javacTree);
+    processPackage(javacTree, node);
+    // visitLists(javacTree.getAnnotations(), node.getAnnotations());
+    javacTree.getPackageName().accept(this, node.getName());
+    return null;
+  }
+
+  @Override
+  public Void visitParameterizedType(ParameterizedTypeTree javacTree, Node javaParserNode) {
+    ClassOrInterfaceType node = castNode(ClassOrInterfaceType.class, javaParserNode, javacTree);
+    processParameterizedType(javacTree, node);
+    javacTree.getType().accept(this, node);
+    // TODO: In a parameterized type, will the first branch ever run?
+    if (javacTree.getTypeArguments().isEmpty()) {
+      assert !node.getTypeArguments().isPresent() || node.getTypeArguments().get().isEmpty();
+    } else {
+      assert node.getTypeArguments().isPresent();
+      visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get());
+    }
+    return null;
+  }
+
+  @Override
+  public Void visitParenthesized(ParenthesizedTree javacTree, Node javaParserNode) {
+    EnclosedExpr node = castNode(EnclosedExpr.class, javaParserNode, javacTree);
+    processParenthesized(javacTree, node);
+    javacTree.getExpression().accept(this, node.getInner());
+    return null;
+  }
+
+  @Override
+  public Void visitPrimitiveType(PrimitiveTypeTree javacTree, Node javaParserNode) {
+    if (javaParserNode instanceof PrimitiveType) {
+      processPrimitiveType(javacTree, (PrimitiveType) javaParserNode);
+    } else if (javaParserNode instanceof VoidType) {
+      processPrimitiveType(javacTree, (VoidType) javaParserNode);
+    } else {
+      throwUnexpectedNodeType(javacTree, javaParserNode);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitProvides(ProvidesTree javacTree, Node javaParserNode) {
+    ModuleProvidesDirective node =
+        castNode(ModuleProvidesDirective.class, javaParserNode, javacTree);
+    processProvides(javacTree, node);
+    javacTree.getServiceName().accept(this, node.getName());
+    visitLists(javacTree.getImplementationNames(), node.getWith());
+    return null;
+  }
+
+  @Override
+  public Void visitRequires(RequiresTree javacTree, Node javaParserNode) {
+    ModuleRequiresDirective node =
+        castNode(ModuleRequiresDirective.class, javaParserNode, javacTree);
+    processRequires(javacTree, node);
+    javacTree.getModuleName().accept(this, node.getName());
+    return null;
+  }
+
+  @Override
+  public Void visitReturn(ReturnTree javacTree, Node javaParserNode) {
+    ReturnStmt node = castNode(ReturnStmt.class, javaParserNode, javacTree);
+    processReturn(javacTree, node);
+    visitOptional(javacTree.getExpression(), node.getExpression());
+
+    return null;
+  }
+
+  @Override
+  public Void visitSwitch(SwitchTree javacTree, Node javaParserNode) {
+    SwitchStmt node = castNode(SwitchStmt.class, javaParserNode, javacTree);
+    processSwitch(javacTree, node);
+    // Switch expressions are always parenthesized in javac but never in JavaParser.
+    ExpressionTree expression = ((ParenthesizedTree) javacTree.getExpression()).getExpression();
+    expression.accept(this, node.getSelector());
+    visitLists(javacTree.getCases(), node.getEntries());
+    return null;
+  }
+
+  @Override
+  public Void visitSynchronized(SynchronizedTree javacTree, Node javaParserNode) {
+    SynchronizedStmt node = castNode(SynchronizedStmt.class, javaParserNode, javacTree);
+    processSynchronized(javacTree, node);
+    ((ParenthesizedTree) javacTree.getExpression())
+        .getExpression()
+        .accept(this, node.getExpression());
+    javacTree.getBlock().accept(this, node.getBody());
+    return null;
+  }
+
+  @Override
+  public Void visitThrow(ThrowTree javacTree, Node javaParserNode) {
+    ThrowStmt node = castNode(ThrowStmt.class, javaParserNode, javacTree);
+    processThrow(javacTree, node);
+    javacTree.getExpression().accept(this, node.getExpression());
+    return null;
+  }
+
+  @Override
+  public Void visitTry(TryTree javacTree, Node javaParserNode) {
+    TryStmt node = castNode(TryStmt.class, javaParserNode, javacTree);
+    processTry(javacTree, node);
+    Iterator<? extends Tree> javacResources = javacTree.getResources().iterator();
+    for (Expression resource : node.getResources()) {
+      if (resource.isVariableDeclarationExpr()) {
+        for (VariableDeclarator declarator : resource.asVariableDeclarationExpr().getVariables()) {
+          assert javacResources.hasNext();
+          javacResources.next().accept(this, declarator);
+        }
+      } else {
+        assert javacResources.hasNext();
+        javacResources.next().accept(this, resource);
+      }
+    }
+
+    javacTree.getBlock().accept(this, node.getTryBlock());
+    visitLists(javacTree.getCatches(), node.getCatchClauses());
+    visitOptional(javacTree.getFinallyBlock(), node.getFinallyBlock());
+
+    return null;
+  }
+
+  @Override
+  public Void visitTypeCast(TypeCastTree javacTree, Node javaParserNode) {
+    CastExpr node = castNode(CastExpr.class, javaParserNode, javacTree);
+    processTypeCast(javacTree, node);
+    javacTree.getType().accept(this, node.getType());
+    javacTree.getExpression().accept(this, node.getExpression());
+    return null;
+  }
+
+  @Override
+  public Void visitTypeParameter(TypeParameterTree javacTree, Node javaParserNode) {
+    TypeParameter node = castNode(TypeParameter.class, javaParserNode, javacTree);
+    processTypeParameter(javacTree, node);
+    visitLists(javacTree.getBounds(), node.getTypeBound());
+    return null;
+  }
+
+  @Override
+  public Void visitUnary(UnaryTree javacTree, Node javaParserNode) {
+    UnaryExpr node = castNode(UnaryExpr.class, javaParserNode, javacTree);
+    processUnary(javacTree, node);
+    javacTree.getExpression().accept(this, node.getExpression());
+    return null;
+  }
+
+  @Override
+  public Void visitUnionType(UnionTypeTree javacTree, Node javaParserNode) {
+    UnionType node = castNode(UnionType.class, javaParserNode, javacTree);
+    processUnionType(javacTree, node);
+    visitLists(javacTree.getTypeAlternatives(), node.getElements());
+    return null;
+  }
+
+  @Override
+  public Void visitUses(UsesTree javacTree, Node javaParserNode) {
+    ModuleUsesDirective node = castNode(ModuleUsesDirective.class, javaParserNode, javacTree);
+    processUses(javacTree, node);
+    javacTree.getServiceName().accept(this, node.getName());
+    return null;
+  }
+
+  @Override
+  public Void visitVariable(VariableTree javacTree, Node javaParserNode) {
+    // Javac uses the class VariableTree to represent multiple syntactic concepts such as
+    // variable declarations, parameters, and fields.
+    if (javaParserNode instanceof VariableDeclarator) {
+      // JavaParser uses VariableDeclarator as parts of other declaration types like
+      // VariableDeclarationExpr when multiple variables may be declared.
+      VariableDeclarator node = (VariableDeclarator) javaParserNode;
+      processVariable(javacTree, node);
+      // Don't process the variable type when it's the Java keyword "var".
+      if (!node.getType().isVarType()
+          && (!node.getType().isClassOrInterfaceType()
+              || !node.getType().asClassOrInterfaceType().getName().asString().equals("var"))) {
+        javacTree.getType().accept(this, node.getType());
+      }
+
+      // The name expression can be null, even when a name exists.
+      if (javacTree.getNameExpression() != null) {
+        javacTree.getNameExpression().accept(this, node.getName());
+      }
+
+      visitOptional(javacTree.getInitializer(), node.getInitializer());
+    } else if (javaParserNode instanceof Parameter) {
+      Parameter node = (Parameter) javaParserNode;
+      processVariable(javacTree, node);
+      if (node.isVarArgs()) {
+        ArrayTypeTree arrayType;
+        // A varargs parameter's type will either be an ArrayTypeTree or an
+        // AnnotatedType depending on whether it has an annotation.
+        if (javacTree.getType().getKind() == Kind.ARRAY_TYPE) {
+          arrayType = (ArrayTypeTree) javacTree.getType();
+        } else {
+          AnnotatedTypeTree annotatedType = (AnnotatedTypeTree) javacTree.getType();
+          arrayType = (ArrayTypeTree) annotatedType.getUnderlyingType();
+        }
+
+        arrayType.getType().accept(this, node.getType());
+      } else {
+        // Types for lambda parameters without explicit types don't have JavaParser nodes,
+        // don't process them.
+        if (!node.getType().isUnknownType()) {
+          javacTree.getType().accept(this, node.getType());
+        }
+      }
+
+      // The name expression can be null, even when a name exists.
+      if (javacTree.getNameExpression() != null) {
+        javacTree.getNameExpression().accept(this, node.getName());
+      }
+
+      assert javacTree.getInitializer() == null;
+    } else if (javaParserNode instanceof ReceiverParameter) {
+      ReceiverParameter node = (ReceiverParameter) javaParserNode;
+      processVariable(javacTree, node);
+      javacTree.getType().accept(this, node.getType());
+      // The name expression can be null, even when a name exists.
+      if (javacTree.getNameExpression() != null) {
+        javacTree.getNameExpression().accept(this, node.getName());
+      }
+
+      assert javacTree.getInitializer() == null;
+    } else if (javaParserNode instanceof EnumConstantDeclaration) {
+      // In javac, an enum constant is expanded as a variable declaration initialized to a
+      // constuctor call.
+      EnumConstantDeclaration node = (EnumConstantDeclaration) javaParserNode;
+      processVariable(javacTree, node);
+      if (javacTree.getNameExpression() != null) {
+        javacTree.getNameExpression().accept(this, node.getName());
+      }
+
+      assert javacTree.getInitializer().getKind() == Kind.NEW_CLASS;
+      NewClassTree constructor = (NewClassTree) javacTree.getInitializer();
+      visitLists(constructor.getArguments(), node.getArguments());
+      if (constructor.getClassBody() != null) {
+        visitAnonymousClassBody(constructor.getClassBody(), node.getClassBody());
+      } else {
+        assert node.getClassBody().isEmpty();
+      }
+    } else {
+      throwUnexpectedNodeType(javacTree, javaParserNode);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitWhileLoop(WhileLoopTree javacTree, Node javaParserNode) {
+    WhileStmt node = castNode(WhileStmt.class, javaParserNode, javacTree);
+    processWhileLoop(javacTree, node);
+    // While loop conditions are always parenthesized in javac but never in JavaParser.
+    assert javacTree.getCondition().getKind() == Kind.PARENTHESIZED;
+    ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression();
+    condition.accept(this, node.getCondition());
+    javacTree.getStatement().accept(this, node.getBody());
+    return null;
+  }
+
+  @Override
+  public Void visitWildcard(WildcardTree javacTree, Node javaParserNode) {
+    WildcardType node = castNode(WildcardType.class, javaParserNode, javacTree);
+    processWildcard(javacTree, node);
+    // In javac, whether the bound is an extends or super clause depends on the kind of the tree.
+    assert (javacTree.getKind() == Kind.EXTENDS_WILDCARD) == node.getExtendedType().isPresent();
+    assert (javacTree.getKind() == Kind.SUPER_WILDCARD) == node.getSuperType().isPresent();
+    switch (javacTree.getKind()) {
+      case UNBOUNDED_WILDCARD:
+        break;
+      case EXTENDS_WILDCARD:
+        javacTree.getBound().accept(this, node.getExtendedType().get());
+        break;
+      case SUPER_WILDCARD:
+        javacTree.getBound().accept(this, node.getSuperType().get());
+        break;
+      default:
+        throw new BugInCF("Unexpected wildcard kind: %s", javacTree);
+    }
+
+    return null;
+  }
+
+  /**
+   * Process an {@code AnnotationTree} with multiple key-value pairs like {@code @MyAnno(a=5,
+   * b=10)}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processAnnotation(
+      AnnotationTree javacTree, NormalAnnotationExpr javaParserNode);
+
+  /**
+   * Process an {@code AnnotationTree} with no arguments like {@code @MyAnno}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processAnnotation(
+      AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode);
+
+  /**
+   * Process an {@code AnnotationTree} with a single argument like {@code MyAnno(5)}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processAnnotation(
+      AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode);
+
+  /**
+   * Process an {@code AnnotatedTypeTree}.
+   *
+   * <p>In javac, a type with an annotation is represented as an {@code AnnotatedTypeTree} with a
+   * nested tree for the base type whereas in JavaParser the annotations are store directly on the
+   * node for the base type. As a result, the JavaParser base type node will be processed twice,
+   * once with the {@code AnnotatedTypeTree} and once with the tree for the base type.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode);
+
+  /**
+   * Process an {@code ArrayAccessTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processArrayAccess(
+      ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode);
+
+  /**
+   * Process an {@code ArrayTypeTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode);
+
+  /**
+   * Process an {@code AssertTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processAssert(AssertTree javacTree, AssertStmt javaParserNode);
+
+  /**
+   * Process an {@code AssignmentTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode);
+
+  /**
+   * Process a {@code BinaryTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode);
+
+  /**
+   * Process a {@code BlockTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processBlock(BlockTree javacTree, BlockStmt javaParserNode);
+
+  /**
+   * Process a {@code BreakTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processBreak(BreakTree javacTree, BreakStmt javaParserNode);
+
+  /**
+   * Process a {@code CaseTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processCase(CaseTree javacTree, SwitchEntry javaParserNode);
+
+  /**
+   * Process a {@code CatchTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processCatch(CatchTree javacTree, CatchClause javaParserNode);
+
+  /**
+   * Process a {@code ClassTree} representing an annotation declaration.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode);
+
+  /**
+   * Process a {@code ClassTree} representing an annotation declaration.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processClass(
+      ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode);
+
+  /**
+   * Process a {@code ClassTree} representing an enum declaration.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processClass(ClassTree javacTree, EnumDeclaration javaParserNode);
+
+  /**
+   * Process a {@code CompilationUnitTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processCompilationUnit(
+      CompilationUnitTree javacTree, CompilationUnit javaParserNode);
+
+  /**
+   * Process a {@code ConditionalExpressionTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processConditionalExpression(
+      ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode);
+
+  /**
+   * Process a {@code ContinueTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode);
+
+  /**
+   * Process a {@code DoWhileLoopTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode);
+
+  /**
+   * Process an {@code EmptyStatementTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processEmptyStatement(
+      EmptyStatementTree javacTree, EmptyStmt javaParserNode);
+
+  /**
+   * Process an {@code EnhancedForLoopTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processEnhancedForLoop(
+      EnhancedForLoopTree javacTree, ForEachStmt javaParserNode);
+
+  /**
+   * Process an {@code ExportsTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode);
+
+  /**
+   * Process an {@code ExpressionStatementTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processExpressionStatemen(
+      ExpressionStatementTree javacTree, ExpressionStmt javaParserNode);
+
+  /**
+   * Process a {@code ForLoopTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode);
+
+  /**
+   * Process an {@code IdentifierTree} representing a class or interface type.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processIdentifier(
+      IdentifierTree javacTree, ClassOrInterfaceType javaParserNode);
+
+  /**
+   * Process an {@code IdentifierTree} representing a name that may contain dots.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processIdentifier(IdentifierTree javacTree, Name javaParserNode);
+
+  /**
+   * Process an {@code IdentifierTree} representing an expression that evaluates to the value of a
+   * variable.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode);
+
+  /**
+   * Process an {@code IdentifierTree} representing a name without dots.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode);
+
+  /**
+   * Process an {@code IdentifierTree} representing a {@code super} expression like the {@code
+   * super} in {@code super.myMethod()} or {@code MyClass.super.myMethod()}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode);
+
+  /**
+   * Process an {@code IdentifierTree} representing a {@code this} expression like the {@code this}
+   * in {@code MyClass = this}, {@code this.myMethod()}, or {@code MyClass.this.myMethod()}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode);
+
+  /**
+   * Process an {@code IfTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processIf(IfTree javacTree, IfStmt javaParserNode);
+
+  /**
+   * Process an {@code ImportTree}.
+   *
+   * <p>Wildcards are stored differently between the two. In a statement like {@code import a.*;},
+   * the name is stored as a {@code MemberSelectTree} with {@code a} and {@code *}. In JavaParser
+   * this is just stored as {@code a} but with a method that returns whether it has a wildcard.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processImport(ImportTree javacTree, ImportDeclaration javaParserNode);
+
+  /**
+   * Process an {@code InstanceOfTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode);
+
+  /**
+   * Process an {@code IntersectionType}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processIntersectionType(
+      IntersectionTypeTree javacTree, IntersectionType javaParserNode);
+
+  /**
+   * Process a {@code LabeledStatement}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processLabeledStatement(
+      LabeledStatementTree javacTree, LabeledStmt javaParserNode);
+
+  /**
+   * Process a {@code LambdaExpressionTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processLambdaExpression(
+      LambdaExpressionTree javacTree, LambdaExpr javaParserNode);
+
+  /**
+   * Process a {@code LiteralTree} for a String literal defined using concatenation.
+   *
+   * <p>For an expression like {@code "a" + "b"}, javac stores a single String literal {@code "ab"}
+   * but JavaParser stores it as an operation with two operands.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode);
+
+  /**
+   * Process a {@code LiteralTree} for a literal expression prefixed with {@code +} or {@code -}
+   * like {@code +5} or {@code -2}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode);
+
+  /**
+   * Process a {@code LiteralTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode);
+
+  /**
+   * Process a {@code MemberReferenceTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processMemberReference(
+      MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode);
+
+  /**
+   * Process a {@code MemberSelectTree} for a class expression like {@code MyClass.class}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode);
+
+  /**
+   * Process a {@code MemberSelectTree} for a type with a name containing dots, like {@code
+   * mypackage.MyClass}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processMemberSelect(
+      MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode);
+  /**
+   * Process a {@code MemberSelectTree} for a field access expression like {@code myObj.myField}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processMemberSelect(
+      MemberSelectTree javacTree, FieldAccessExpr javaParserNode);
+
+  /**
+   * Process a {@code MemberSelectTree} for a name that contains dots.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode);
+
+  /**
+   * Process a {@code MemberSelectTree} for a this expression with a class like {@code
+   * MyClass.this}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode);
+
+  /**
+   * Process a {@code MemberSelectTree} for a super expression with a class like {@code
+   * super.MyClass}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode);
+
+  /**
+   * Process a {@code MethodTree} representing a regular method declaration.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode);
+
+  /**
+   * Process a {@code MethodTree} representing a constructor declaration.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode);
+
+  /**
+   * Process a {@code MethodTree} representing a value field for an annotation.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processMethod(
+      MethodTree javacTree, AnnotationMemberDeclaration javaParserNode);
+
+  /**
+   * Process a {@code MethodInvocationTree} representing a constructor invocation.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processMethodInvocation(
+      MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode);
+
+  /**
+   * Process a {@code MethodInvocationTree} representing a regular method invocation.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processMethodInvocation(
+      MethodInvocationTree javacTree, MethodCallExpr javaParserNode);
+
+  /**
+   * Process a {@code ModuleTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode);
+
+  /**
+   * Process a {@code NewClassTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode);
+
+  /**
+   * Process an {@code OpensTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode);
+
+  /**
+   * Process a {@code Tree} that isn't an instance of any specific tree class.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processOther(Tree javacTree, Node javaParserNode);
+
+  /**
+   * Process a {@code PackageTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode);
+
+  /**
+   * Process a {@code ParameterizedTypeTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processParameterizedType(
+      ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode);
+
+  /**
+   * Process a {@code ParenthesizedTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processParenthesized(
+      ParenthesizedTree javacTree, EnclosedExpr javaParserNode);
+
+  /**
+   * Process a {@code PrimitiveTypeTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processPrimitiveType(
+      PrimitiveTypeTree javacTree, PrimitiveType javaParserNode);
+
+  /**
+   * Process a {@code PrimitiveTypeTree} representing a void type.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode);
+
+  /**
+   * Process a {@code ProvidesTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processProvides(
+      ProvidesTree javacTree, ModuleProvidesDirective javaParserNode);
+
+  /**
+   * Process a {@code RequiresTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processRequires(
+      RequiresTree javacTree, ModuleRequiresDirective javaParserNode);
+
+  /**
+   * Process a {@code RetrunTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode);
+
+  /**
+   * Process a {@code SwitchTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode);
+
+  /**
+   * Process a {@code SynchronizedTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processSynchronized(
+      SynchronizedTree javacTree, SynchronizedStmt javaParserNode);
+
+  /**
+   * Process a {@code ThrowTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode);
+
+  /**
+   * Process a {@code TryTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processTry(TryTree javacTree, TryStmt javaParserNode);
+
+  /**
+   * Process a {@code TypeCastTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode);
+
+  /**
+   * Process a {@code TypeParameterTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processTypeParameter(
+      TypeParameterTree javacTree, TypeParameter javaParserNode);
+
+  /**
+   * Process a {@code UnaryTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode);
+
+  /**
+   * Process a {@code UnionTypeTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode);
+
+  /**
+   * Process a {@code UsesTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode);
+
+  /**
+   * Process a {@code VariableTree} representing an enum constant declaration. In an enum like
+   * {@code enum MyEnum { MY_CONSTANT }}, javac expands {@code MY_CONSTANT} as a constant variable.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processVariable(
+      VariableTree javacTree, EnumConstantDeclaration javaParserNode);
+
+  /**
+   * Process a {@code VariableTree} representing a parameter to a method or constructor.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processVariable(VariableTree javacTree, Parameter javaParserNode);
+
+  /**
+   * Process a {@code VariableTree} representing the receiver parameter of a method.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode);
+
+  /**
+   * Process a {@code VariableTree} representing a regular variable declaration.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode);
+
+  /**
+   * Process a {@code WhileLoopTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode);
+
+  /**
+   * Process a {@code WhileLoopTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processWildcard(WildcardTree javacTree, WildcardType javaParserNode);
+
+  /**
+   * Process a {@code CompoundAssignmentTree}.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void processCompoundAssignment(
+      CompoundAssignmentTree javacTree, AssignExpr javaParserNode);
+
+  /**
+   * Given a list of javac trees and a list of JavaParser nodes, where the elements of the lists
+   * correspond to each other, visit each javac tree along with its corresponding JavaParser node.
+   *
+   * <p>The two lists must be of the same length and elements at corresponding positions must match.
+   *
+   * @param javacTrees list of trees
+   * @param javaParserNodes list of corresponding JavaParser nodes
+   */
+  private void visitLists(List<? extends Tree> javacTrees, List<? extends Node> javaParserNodes) {
+    assert javacTrees.size() == javaParserNodes.size();
+    Iterator<? extends Node> nodeIter = javaParserNodes.iterator();
+    for (Tree tree : javacTrees) {
+      tree.accept(this, nodeIter.next());
+    }
+  }
+
+  /**
+   * Visit an optional syntax construct. Whether the javac tree is non-null must match whether the
+   * JavaParser optional is present.
+   *
+   * @param javacTree a javac tree or null
+   * @param javaParserNode an optional JavaParser node, which might not be present
+   */
+  private void visitOptional(Tree javacTree, Optional<? extends Node> javaParserNode) {
+    assert javacTree != null == javaParserNode.isPresent()
+        : String.format("visitOptional(%s, %s)", javacTree, javaParserNode);
+    if (javacTree != null) {
+      javacTree.accept(this, javaParserNode.get());
+    }
+  }
+
+  /**
+   * Cast {@code javaParserNode} to type {@code type} and return it.
+   *
+   * @param <T> the type of {@code type}
+   * @param type the type to cast to
+   * @param javaParserNode the object to cast
+   * @param javacTree the javac tree that corresponds to {@code javaParserNode}; used only for error
+   *     reporting
+   * @return javaParserNode, casted to {@code type}
+   */
+  public <T> T castNode(Class<T> type, Node javaParserNode, Tree javacTree) {
+    if (type.isInstance(javaParserNode)) {
+      return type.cast(javaParserNode);
+    }
+    throwUnexpectedNodeType(javacTree, javaParserNode, type);
+    throw new BugInCF("unreachable");
+  }
+
+  /**
+   * Given a javac tree and JavaPaser node which were visited but didn't correspond to each other,
+   * throws an exception indicating that the visiting process failed for those nodes.
+   *
+   * @param javacTree a tree that was visited
+   * @param javaParserNode a node that was visited at the same time as {@code javacTree}, but which
+   *     was not of the correct type for that tree
+   * @throws BugInCF that indicates the javac trees and JavaParser nodes were desynced during the
+   *     visitng process at {@code javacTree} and {@code javaParserNode}
+   */
+  private void throwUnexpectedNodeType(Tree javacTree, Node javaParserNode) {
+    throw new BugInCF(
+        "desynced trees: %s [%s], %s [%s]",
+        javacTree, javacTree.getClass(), javaParserNode, javaParserNode.getClass());
+  }
+
+  /**
+   * Given a javac tree and JavaPaser node which were visited but didn't correspond to each other,
+   * throws an exception indicating that the visiting process failed for those nodes because {@code
+   * javaParserNode} was expected to be of type {@code expectedType}.
+   *
+   * @param javacTree a tree that was visited
+   * @param javaParserNode a node that was visited at the same time as {@code javacTree}, but which
+   *     was not of the correct type for that tree
+   * @param expectedType the type {@code javaParserNode} was expected to be based on {@code
+   *     javacTree}
+   * @throws BugInCF that indicates the javac trees and JavaParser nodes were desynced during the
+   *     visitng process at {@code javacTree} and {@code javaParserNode}
+   */
+  private void throwUnexpectedNodeType(Tree javacTree, Node javaParserNode, Class<?> expectedType) {
+    throw new BugInCF(
+        "desynced trees: %s [%s], %s [%s (expected %s)]",
+        javacTree, javacTree.getClass(), javaParserNode, javaParserNode.getClass(), expectedType);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/JointVisitorWithDefaultAction.java b/framework/src/main/java/org/checkerframework/framework/ajava/JointVisitorWithDefaultAction.java
new file mode 100644
index 0000000..72e52bf
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/ajava/JointVisitorWithDefaultAction.java
@@ -0,0 +1,556 @@
+package org.checkerframework.framework.ajava;
+
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.ImportDeclaration;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.PackageDeclaration;
+import com.github.javaparser.ast.body.AnnotationDeclaration;
+import com.github.javaparser.ast.body.AnnotationMemberDeclaration;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.ConstructorDeclaration;
+import com.github.javaparser.ast.body.EnumConstantDeclaration;
+import com.github.javaparser.ast.body.EnumDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.Parameter;
+import com.github.javaparser.ast.body.ReceiverParameter;
+import com.github.javaparser.ast.body.VariableDeclarator;
+import com.github.javaparser.ast.expr.ArrayAccessExpr;
+import com.github.javaparser.ast.expr.AssignExpr;
+import com.github.javaparser.ast.expr.BinaryExpr;
+import com.github.javaparser.ast.expr.CastExpr;
+import com.github.javaparser.ast.expr.ClassExpr;
+import com.github.javaparser.ast.expr.ConditionalExpr;
+import com.github.javaparser.ast.expr.EnclosedExpr;
+import com.github.javaparser.ast.expr.FieldAccessExpr;
+import com.github.javaparser.ast.expr.InstanceOfExpr;
+import com.github.javaparser.ast.expr.LambdaExpr;
+import com.github.javaparser.ast.expr.LiteralExpr;
+import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import com.github.javaparser.ast.expr.MethodReferenceExpr;
+import com.github.javaparser.ast.expr.Name;
+import com.github.javaparser.ast.expr.NameExpr;
+import com.github.javaparser.ast.expr.NormalAnnotationExpr;
+import com.github.javaparser.ast.expr.ObjectCreationExpr;
+import com.github.javaparser.ast.expr.SimpleName;
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
+import com.github.javaparser.ast.expr.SuperExpr;
+import com.github.javaparser.ast.expr.ThisExpr;
+import com.github.javaparser.ast.expr.UnaryExpr;
+import com.github.javaparser.ast.modules.ModuleDeclaration;
+import com.github.javaparser.ast.modules.ModuleExportsDirective;
+import com.github.javaparser.ast.modules.ModuleOpensDirective;
+import com.github.javaparser.ast.modules.ModuleProvidesDirective;
+import com.github.javaparser.ast.modules.ModuleRequiresDirective;
+import com.github.javaparser.ast.modules.ModuleUsesDirective;
+import com.github.javaparser.ast.stmt.AssertStmt;
+import com.github.javaparser.ast.stmt.BlockStmt;
+import com.github.javaparser.ast.stmt.BreakStmt;
+import com.github.javaparser.ast.stmt.CatchClause;
+import com.github.javaparser.ast.stmt.ContinueStmt;
+import com.github.javaparser.ast.stmt.DoStmt;
+import com.github.javaparser.ast.stmt.EmptyStmt;
+import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
+import com.github.javaparser.ast.stmt.ExpressionStmt;
+import com.github.javaparser.ast.stmt.ForEachStmt;
+import com.github.javaparser.ast.stmt.ForStmt;
+import com.github.javaparser.ast.stmt.IfStmt;
+import com.github.javaparser.ast.stmt.LabeledStmt;
+import com.github.javaparser.ast.stmt.ReturnStmt;
+import com.github.javaparser.ast.stmt.SwitchEntry;
+import com.github.javaparser.ast.stmt.SwitchStmt;
+import com.github.javaparser.ast.stmt.SynchronizedStmt;
+import com.github.javaparser.ast.stmt.ThrowStmt;
+import com.github.javaparser.ast.stmt.TryStmt;
+import com.github.javaparser.ast.stmt.WhileStmt;
+import com.github.javaparser.ast.type.ArrayType;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import com.github.javaparser.ast.type.IntersectionType;
+import com.github.javaparser.ast.type.PrimitiveType;
+import com.github.javaparser.ast.type.TypeParameter;
+import com.github.javaparser.ast.type.UnionType;
+import com.github.javaparser.ast.type.VoidType;
+import com.github.javaparser.ast.type.WildcardType;
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.AssertTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.BreakTree;
+import com.sun.source.tree.CaseTree;
+import com.sun.source.tree.CatchTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ContinueTree;
+import com.sun.source.tree.DoWhileLoopTree;
+import com.sun.source.tree.EmptyStatementTree;
+import com.sun.source.tree.EnhancedForLoopTree;
+import com.sun.source.tree.ExportsTree;
+import com.sun.source.tree.ExpressionStatementTree;
+import com.sun.source.tree.ForLoopTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.IntersectionTypeTree;
+import com.sun.source.tree.LabeledStatementTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModuleTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.OpensTree;
+import com.sun.source.tree.PackageTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.PrimitiveTypeTree;
+import com.sun.source.tree.ProvidesTree;
+import com.sun.source.tree.RequiresTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.SwitchTree;
+import com.sun.source.tree.SynchronizedTree;
+import com.sun.source.tree.ThrowTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TryTree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.TypeParameterTree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.UnionTypeTree;
+import com.sun.source.tree.UsesTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.tree.WhileLoopTree;
+import com.sun.source.tree.WildcardTree;
+
+/**
+ * A {@code JointJavacJavaParserVisitor} that visits all javac trees with their corresponding
+ * JavaParser nodes and performs some default action on each pair.
+ *
+ * <p>To use this class, override {@code defaultAction}.
+ */
+public abstract class JointVisitorWithDefaultAction extends JointJavacJavaParserVisitor {
+  /**
+   * Action performed on each javac tree and JavaParser node pair.
+   *
+   * @param javacTree tree to process
+   * @param javaParserNode corresponding JavaParser node
+   */
+  public abstract void defaultAction(Tree javacTree, Node javaParserNode);
+
+  @Override
+  public void processAnnotation(AnnotationTree javacTree, NormalAnnotationExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processAnnotation(AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processAnnotation(
+      AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processArrayAccess(ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processAssert(AssertTree javacTree, AssertStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processBlock(BlockTree javacTree, BlockStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processBreak(BreakTree javacTree, BreakStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processCase(CaseTree javacTree, SwitchEntry javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processCatch(CatchTree javacTree, CatchClause javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processClass(ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processCompilationUnit(
+      CompilationUnitTree javacTree, CompilationUnit javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processConditionalExpression(
+      ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processEmptyStatement(EmptyStatementTree javacTree, EmptyStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processEnhancedForLoop(EnhancedForLoopTree javacTree, ForEachStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processExpressionStatemen(
+      ExpressionStatementTree javacTree, ExpressionStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processIdentifier(IdentifierTree javacTree, ClassOrInterfaceType javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processIdentifier(IdentifierTree javacTree, Name javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processIf(IfTree javacTree, IfStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processImport(ImportTree javacTree, ImportDeclaration javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processIntersectionType(
+      IntersectionTypeTree javacTree, IntersectionType javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processLabeledStatement(LabeledStatementTree javacTree, LabeledStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processLambdaExpression(LambdaExpressionTree javacTree, LambdaExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processMemberReference(
+      MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processMemberSelect(MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processMemberSelect(MemberSelectTree javacTree, FieldAccessExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processMethod(MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processMethodInvocation(
+      MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processMethodInvocation(
+      MethodInvocationTree javacTree, MethodCallExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processOther(Tree javacTree, Node javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processParameterizedType(
+      ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processParenthesized(ParenthesizedTree javacTree, EnclosedExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processPrimitiveType(PrimitiveTypeTree javacTree, PrimitiveType javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processProvides(ProvidesTree javacTree, ModuleProvidesDirective javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processRequires(RequiresTree javacTree, ModuleRequiresDirective javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processSynchronized(SynchronizedTree javacTree, SynchronizedStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processTry(TryTree javacTree, TryStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processTypeParameter(TypeParameterTree javacTree, TypeParameter javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processVariable(VariableTree javacTree, EnumConstantDeclaration javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processVariable(VariableTree javacTree, Parameter javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processWildcard(WildcardTree javacTree, WildcardType javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+
+  @Override
+  public void processCompoundAssignment(
+      CompoundAssignmentTree javacTree, AssignExpr javaParserNode) {
+    defaultAction(javacTree, javaParserNode);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/TreeScannerWithDefaults.java b/framework/src/main/java/org/checkerframework/framework/ajava/TreeScannerWithDefaults.java
new file mode 100644
index 0000000..ec8c977
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/ajava/TreeScannerWithDefaults.java
@@ -0,0 +1,437 @@
+package org.checkerframework.framework.ajava;
+
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.AssertTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.BreakTree;
+import com.sun.source.tree.CaseTree;
+import com.sun.source.tree.CatchTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ContinueTree;
+import com.sun.source.tree.DoWhileLoopTree;
+import com.sun.source.tree.EmptyStatementTree;
+import com.sun.source.tree.EnhancedForLoopTree;
+import com.sun.source.tree.ErroneousTree;
+import com.sun.source.tree.ExportsTree;
+import com.sun.source.tree.ExpressionStatementTree;
+import com.sun.source.tree.ForLoopTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.IntersectionTypeTree;
+import com.sun.source.tree.LabeledStatementTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModifiersTree;
+import com.sun.source.tree.ModuleTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.OpensTree;
+import com.sun.source.tree.PackageTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.PrimitiveTypeTree;
+import com.sun.source.tree.ProvidesTree;
+import com.sun.source.tree.RequiresTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.SwitchTree;
+import com.sun.source.tree.SynchronizedTree;
+import com.sun.source.tree.ThrowTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TryTree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.TypeParameterTree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.UnionTypeTree;
+import com.sun.source.tree.UsesTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.tree.WhileLoopTree;
+import com.sun.source.tree.WildcardTree;
+import com.sun.source.util.TreeScanner;
+
+/**
+ * A visitor that performs some default action on a tree and then all of its children. To use this
+ * class, override {@code defaultAction}.
+ */
+public abstract class TreeScannerWithDefaults extends TreeScanner<Void, Void> {
+
+  /**
+   * Action performed on each visited tree.
+   *
+   * @param tree tree to perform action on
+   */
+  public abstract void defaultAction(Tree tree);
+
+  @Override
+  public Void visitAnnotatedType(AnnotatedTypeTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitAnnotatedType(tree, p);
+  }
+
+  @Override
+  public Void visitAnnotation(AnnotationTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitAnnotation(tree, p);
+  }
+
+  @Override
+  public Void visitArrayAccess(ArrayAccessTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitArrayAccess(tree, p);
+  }
+
+  @Override
+  public Void visitArrayType(ArrayTypeTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitArrayType(tree, p);
+  }
+
+  @Override
+  public Void visitAssert(AssertTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitAssert(tree, p);
+  }
+
+  @Override
+  public Void visitAssignment(AssignmentTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitAssignment(tree, p);
+  }
+
+  @Override
+  public Void visitBinary(BinaryTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitBinary(tree, p);
+  }
+
+  @Override
+  public Void visitBlock(BlockTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitBlock(tree, p);
+  }
+
+  @Override
+  public Void visitBreak(BreakTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitBreak(tree, p);
+  }
+
+  @Override
+  public Void visitCase(CaseTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitCase(tree, p);
+  }
+
+  @Override
+  public Void visitCatch(CatchTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitCatch(tree, p);
+  }
+
+  @Override
+  public Void visitClass(ClassTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitClass(tree, p);
+  }
+
+  @Override
+  public Void visitCompilationUnit(CompilationUnitTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitCompilationUnit(tree, p);
+  }
+
+  @Override
+  public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitCompoundAssignment(tree, p);
+  }
+
+  @Override
+  public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitConditionalExpression(tree, p);
+  }
+
+  @Override
+  public Void visitContinue(ContinueTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitContinue(tree, p);
+  }
+
+  @Override
+  public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitDoWhileLoop(tree, p);
+  }
+
+  @Override
+  public Void visitEmptyStatement(EmptyStatementTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitEmptyStatement(tree, p);
+  }
+
+  @Override
+  public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitEnhancedForLoop(tree, p);
+  }
+
+  @Override
+  public Void visitErroneous(ErroneousTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitErroneous(tree, p);
+  }
+
+  @Override
+  public Void visitExports(ExportsTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitExports(tree, p);
+  }
+
+  @Override
+  public Void visitExpressionStatement(ExpressionStatementTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitExpressionStatement(tree, p);
+  }
+
+  @Override
+  public Void visitForLoop(ForLoopTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitForLoop(tree, p);
+  }
+
+  @Override
+  public Void visitIdentifier(IdentifierTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitIdentifier(tree, p);
+  }
+
+  @Override
+  public Void visitIf(IfTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitIf(tree, p);
+  }
+
+  @Override
+  public Void visitImport(ImportTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitImport(tree, p);
+  }
+
+  @Override
+  public Void visitInstanceOf(InstanceOfTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitInstanceOf(tree, p);
+  }
+
+  @Override
+  public Void visitIntersectionType(IntersectionTypeTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitIntersectionType(tree, p);
+  }
+
+  @Override
+  public Void visitLabeledStatement(LabeledStatementTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitLabeledStatement(tree, p);
+  }
+
+  @Override
+  public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitLambdaExpression(tree, p);
+  }
+
+  @Override
+  public Void visitLiteral(LiteralTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitLiteral(tree, p);
+  }
+
+  @Override
+  public Void visitMemberReference(MemberReferenceTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitMemberReference(tree, p);
+  }
+
+  @Override
+  public Void visitMemberSelect(MemberSelectTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitMemberSelect(tree, p);
+  }
+
+  @Override
+  public Void visitMethod(MethodTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitMethod(tree, p);
+  }
+
+  @Override
+  public Void visitMethodInvocation(MethodInvocationTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitMethodInvocation(tree, p);
+  }
+
+  @Override
+  public Void visitModifiers(ModifiersTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitModifiers(tree, p);
+  }
+
+  @Override
+  public Void visitModule(ModuleTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitModule(tree, p);
+  }
+
+  @Override
+  public Void visitNewArray(NewArrayTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitNewArray(tree, p);
+  }
+
+  @Override
+  public Void visitNewClass(NewClassTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitNewClass(tree, p);
+  }
+
+  @Override
+  public Void visitOpens(OpensTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitOpens(tree, p);
+  }
+
+  @Override
+  public Void visitOther(Tree tree, Void p) {
+    defaultAction(tree);
+    return super.visitOther(tree, p);
+  }
+
+  @Override
+  public Void visitPackage(PackageTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitPackage(tree, p);
+  }
+
+  @Override
+  public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitParameterizedType(tree, p);
+  }
+
+  @Override
+  public Void visitParenthesized(ParenthesizedTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitParenthesized(tree, p);
+  }
+
+  @Override
+  public Void visitPrimitiveType(PrimitiveTypeTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitPrimitiveType(tree, p);
+  }
+
+  @Override
+  public Void visitProvides(ProvidesTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitProvides(tree, p);
+  }
+
+  @Override
+  public Void visitRequires(RequiresTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitRequires(tree, p);
+  }
+
+  @Override
+  public Void visitReturn(ReturnTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitReturn(tree, p);
+  }
+
+  @Override
+  public Void visitSwitch(SwitchTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitSwitch(tree, p);
+  }
+
+  @Override
+  public Void visitSynchronized(SynchronizedTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitSynchronized(tree, p);
+  }
+
+  @Override
+  public Void visitThrow(ThrowTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitThrow(tree, p);
+  }
+
+  @Override
+  public Void visitTry(TryTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitTry(tree, p);
+  }
+
+  @Override
+  public Void visitTypeCast(TypeCastTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitTypeCast(tree, p);
+  }
+
+  @Override
+  public Void visitTypeParameter(TypeParameterTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitTypeParameter(tree, p);
+  }
+
+  @Override
+  public Void visitUnary(UnaryTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitUnary(tree, p);
+  }
+
+  @Override
+  public Void visitUnionType(UnionTypeTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitUnionType(tree, p);
+  }
+
+  @Override
+  public Void visitUses(UsesTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitUses(tree, p);
+  }
+
+  @Override
+  public Void visitVariable(VariableTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitVariable(tree, p);
+  }
+
+  @Override
+  public Void visitWhileLoop(WhileLoopTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitWhileLoop(tree, p);
+  }
+
+  @Override
+  public Void visitWildcard(WildcardTree tree, Void p) {
+    defaultAction(tree);
+    return super.visitWildcard(tree, p);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/TypeAnnotationMover.java b/framework/src/main/java/org/checkerframework/framework/ajava/TypeAnnotationMover.java
new file mode 100644
index 0000000..14586d6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/ajava/TypeAnnotationMover.java
@@ -0,0 +1,221 @@
+package org.checkerframework.framework.ajava;
+
+import com.github.javaparser.ast.body.FieldDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.expr.AnnotationExpr;
+import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations;
+import com.github.javaparser.ast.type.Type;
+import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.framework.stub.AnnotationFileParser;
+
+/**
+ * Moves annotations in a JavaParser AST from declaration position onto the types they correspond
+ * to.
+ *
+ * <p>When parsing a method or field such as {@code @Tainted String myField}, JavaParser puts all
+ * annotations on the declaration.
+ *
+ * <p>For each non-declaration annotation on a method or field declaration, this class moves it to
+ * the type position. A non-declaration annotation is one with a {@code TYPE_USE} target but no
+ * declaration target.
+ */
+public class TypeAnnotationMover extends VoidVisitorAdapter<Void> {
+  /**
+   * Annotations imported by the file, stored as a mapping from names to the TypeElements for the
+   * annotations. Contains entries for the simple and fully qualified names of each annotation.
+   */
+  private Map<String, TypeElement> allAnnotations;
+  /** Element utilities. */
+  private Elements elements;
+
+  /**
+   * Constructs a {@code TypeAnnotationMover}.
+   *
+   * @param allAnnotations the annotations imported by the file, as a mapping from annotation name
+   *     to TypeElement. There should be two entries for each annotation: the annotation's simple
+   *     name and its fully-qualified name both mapped to its TypeElement.
+   * @param elements Element utilities
+   */
+  public TypeAnnotationMover(Map<String, TypeElement> allAnnotations, Elements elements) {
+    this.allAnnotations = new HashMap<>(allAnnotations);
+    this.elements = elements;
+  }
+
+  @Override
+  public void visit(FieldDeclaration node, Void p) {
+    // Use the type of the first declared variable in the field declaration.
+    Type type = node.getVariable(0).getType();
+    if (!type.isClassOrInterfaceType()) {
+      return;
+    }
+
+    if (isMultiPartName(type)) {
+      return;
+    }
+
+    List<AnnotationExpr> annosToMove = getAnnotationsToMove(node, ElementType.FIELD);
+    if (annosToMove.isEmpty()) {
+      return;
+    }
+
+    node.getAnnotations().removeAll(annosToMove);
+    annosToMove.forEach(anno -> type.asClassOrInterfaceType().addAnnotation(anno));
+  }
+
+  @Override
+  public void visit(MethodDeclaration node, Void p) {
+    Type type = node.getType();
+    if (!type.isClassOrInterfaceType()) {
+      return;
+    }
+
+    if (isMultiPartName(type)) {
+      return;
+    }
+
+    List<AnnotationExpr> annosToMove = getAnnotationsToMove(node, ElementType.METHOD);
+    if (annosToMove.isEmpty()) {
+      return;
+    }
+
+    node.getAnnotations().removeAll(annosToMove);
+    annosToMove.forEach(anno -> type.asClassOrInterfaceType().addAnnotation(anno));
+  }
+
+  /**
+   * Given a declaration, returns a List of annotations currently in declaration position that can't
+   * possibly be declaration annotations for that type of declaration.
+   *
+   * @param node JavaParser node for declaration
+   * @param declarationType the type of declaration {@code node} represents; always FIELD or METHOD
+   * @return a list of annotations in declaration position that should be on the declaration's type
+   */
+  private List<AnnotationExpr> getAnnotationsToMove(
+      NodeWithAnnotations<?> node, ElementType declarationType) {
+    List<AnnotationExpr> annosToMove = new ArrayList<>();
+    for (AnnotationExpr annotation : node.getAnnotations()) {
+      if (!isPossiblyDeclarationAnnotation(annotation, declarationType)) {
+        annosToMove.add(annotation);
+      }
+    }
+
+    return annosToMove;
+  }
+
+  /**
+   * Returns the TypeElement for an annotation, or null if it cannot be found.
+   *
+   * @param annotation a JavaParser annotation
+   * @return the TypeElement for {@code annotation}, or null if it cannot be found
+   */
+  private @Nullable TypeElement getAnnotationDeclaration(AnnotationExpr annotation) {
+    @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094
+    @FullyQualifiedName String annoNameFq = annotation.getNameAsString();
+    TypeElement annoTypeElt = allAnnotations.get(annoNameFq);
+    if (annoTypeElt == null) {
+      annoTypeElt = elements.getTypeElement(annoNameFq);
+      if (annoTypeElt == null) {
+        // Not a supported annotation.
+        return null;
+      }
+      AnnotationFileParser.putAllNew(
+          allAnnotations,
+          AnnotationFileParser.createNameToAnnotationMap(Collections.singletonList(annoTypeElt)));
+    }
+
+    return annoTypeElt;
+  }
+
+  /**
+   * Returns if {@code annotation} could be a declaration annotation for {@code declarationType}.
+   * This would be the case if the annotation isn't recognized at all, or if it has no
+   * {@code @Target} meta-annotation, or if it has {@code declarationType} as one of its targets.
+   *
+   * @param annotation a JavaParser annotation expression
+   * @param declarationType the declaration type to check if {@code annotation} might be a
+   *     declaration annotation for
+   * @return true unless {@code annotation} definitely cannot be a declaration annotation for {@code
+   *     declarationType}
+   */
+  private boolean isPossiblyDeclarationAnnotation(
+      AnnotationExpr annotation, ElementType declarationType) {
+    TypeElement annotationType = getAnnotationDeclaration(annotation);
+    if (annotationType == null) {
+      return true;
+    }
+
+    return isDeclarationAnnotation(annotationType, declarationType);
+  }
+
+  /**
+   * Returns whether the annotation represented by {@code annotationDeclaration} might be a
+   * declaration annotation for {@code declarationType}. This holds if the TypeElement has no
+   * {@code @Target} meta-annotation, or if {@code declarationType} is a target of the annotation.
+   *
+   * @param annotationDeclaration declaration for an annotation
+   * @param declarationType the declaration type to check if the annotation might be a declaration
+   *     annotation for
+   * @return true if {@code annotationDeclaration} contains {@code declarationType} as a target or
+   *     doesn't contain {@code ElementType.TYPE_USE} as a target
+   */
+  private boolean isDeclarationAnnotation(
+      TypeElement annotationDeclaration, ElementType declarationType) {
+    Target target = annotationDeclaration.getAnnotation(Target.class);
+    if (target == null) {
+      return true;
+    }
+
+    boolean hasTypeUse = false;
+    for (ElementType elementType : target.value()) {
+      if (elementType == declarationType) {
+        return true;
+      }
+
+      if (elementType == ElementType.TYPE_USE) {
+        hasTypeUse = true;
+      }
+    }
+
+    if (!hasTypeUse) {
+      throw new Error(
+          String.format(
+              "Annotation %s cannot be used on declaration with type %s",
+              annotationDeclaration.getQualifiedName(), declarationType));
+    }
+    return false;
+  }
+
+  /**
+   * Returns whether {@code type} has a name containing multiple parts separated by dots, e.g.
+   * "java.lang.String" or "Outer.Inner".
+   *
+   * <p>Annotations should not be moved onto a Type for which this method returns true. A type like
+   * {@code @Anno java.lang.String} is illegal since the annotation should go directly next to the
+   * rightmost part of the fully qualified name, like {@code java.lang. @Anno String}. So if a file
+   * contains a declaration like {@code @Anno java.lang.String myField}, the annotation must belong
+   * to the declaration and not the type.
+   *
+   * <p>If a declaration contains an inner class type like {@code @Anno Outer.Inner myField}, it may
+   * be the case that {@code @Anno} belongs to the type {@code Outer}, not the declaration, and
+   * should be moved, but it's impossible to distinguish this from the above case using only the
+   * JavaParser AST for a file. To be safe, the annotation still shouldn't be moved, but this may
+   * lead to suboptimal formatting placing {@code @Anno} on its own line.
+   *
+   * @param type a JavaParser type node
+   * @return true if {@code type} has a multi-part name
+   */
+  private boolean isMultiPartName(Type type) {
+    return type.isClassOrInterfaceType() && type.asClassOrInterfaceType().getScope().isPresent();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractAnalysis.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractAnalysis.java
new file mode 100644
index 0000000..405577d
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractAnalysis.java
@@ -0,0 +1,219 @@
+package org.checkerframework.framework.flow;
+
+import java.util.List;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.analysis.ForwardAnalysisImpl;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.TypeHierarchy;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * {@link CFAbstractAnalysis} is an extensible org.checkerframework.dataflow analysis for the
+ * Checker Framework that tracks the annotations using a flow-sensitive analysis. It uses an {@link
+ * AnnotatedTypeFactory} to provide checker-specific logic how to combine types (e.g., what is the
+ * type of a string concatenation, given the types of the two operands) and as an abstraction
+ * function (e.g., determine the annotations on literals).
+ *
+ * <p>The purpose of this class is twofold: Firstly, it serves as factory for abstract values,
+ * stores and the transfer function. Furthermore, it makes it easy for the transfer function and the
+ * stores to access the {@link AnnotatedTypeFactory}, the qualifier hierarchy, etc.
+ */
+public abstract class CFAbstractAnalysis<
+        V extends CFAbstractValue<V>,
+        S extends CFAbstractStore<V, S>,
+        T extends CFAbstractTransfer<V, S, T>>
+    extends ForwardAnalysisImpl<V, S, T> {
+  /** The qualifier hierarchy for which to track annotations. */
+  protected final QualifierHierarchy qualifierHierarchy;
+
+  /** The type hierarchy. */
+  protected final TypeHierarchy typeHierarchy;
+
+  /**
+   * The dependent type helper used to standardize both annotations belonging to the type hierarchy,
+   * and contract expressions.
+   */
+  protected final DependentTypesHelper dependentTypesHelper;
+
+  /** A type factory that can provide static type annotations for AST Trees. */
+  protected final GenericAnnotatedTypeFactory<V, S, T, ? extends CFAbstractAnalysis<V, S, T>>
+      atypeFactory;
+
+  /** A checker that contains command-line arguments and other information. */
+  protected final SourceChecker checker;
+
+  /** Initial abstract types for fields. */
+  protected final List<Pair<VariableElement, V>> fieldValues;
+
+  /** The associated processing environment. */
+  protected final ProcessingEnvironment env;
+
+  /** Instance of the types utility. */
+  protected final Types types;
+
+  /**
+   * Create a CFAbstractAnalysis.
+   *
+   * @param checker a checker that contains command-line arguments and other information
+   * @param factory an annotated type factory to introduce type and dataflow rules
+   * @param fieldValues initial abstract types for fields
+   * @param maxCountBeforeWidening number of times a block can be analyzed before widening
+   */
+  protected CFAbstractAnalysis(
+      BaseTypeChecker checker,
+      GenericAnnotatedTypeFactory<V, S, T, ? extends CFAbstractAnalysis<V, S, T>> factory,
+      List<Pair<VariableElement, V>> fieldValues,
+      int maxCountBeforeWidening) {
+    super(maxCountBeforeWidening);
+    env = checker.getProcessingEnvironment();
+    types = env.getTypeUtils();
+    qualifierHierarchy = factory.getQualifierHierarchy();
+    typeHierarchy = factory.getTypeHierarchy();
+    dependentTypesHelper = factory.getDependentTypesHelper();
+    this.atypeFactory = factory;
+    this.checker = checker;
+    this.transferFunction = createTransferFunction();
+    // TODO: remove parameter and set to empty list.
+    this.fieldValues = fieldValues;
+  }
+
+  protected CFAbstractAnalysis(
+      BaseTypeChecker checker,
+      GenericAnnotatedTypeFactory<V, S, T, ? extends CFAbstractAnalysis<V, S, T>> factory,
+      List<Pair<VariableElement, V>> fieldValues) {
+    this(
+        checker,
+        factory,
+        fieldValues,
+        factory.getQualifierHierarchy().numberOfIterationsBeforeWidening());
+  }
+
+  public void performAnalysis(ControlFlowGraph cfg, List<Pair<VariableElement, V>> fieldValues) {
+    this.fieldValues.clear();
+    this.fieldValues.addAll(fieldValues);
+    super.performAnalysis(cfg);
+  }
+
+  public List<Pair<VariableElement, V>> getFieldValues() {
+    return fieldValues;
+  }
+
+  /**
+   * Returns the transfer function to be used by the analysis.
+   *
+   * @return the transfer function to be used by the analysis
+   */
+  public T createTransferFunction() {
+    return atypeFactory.createFlowTransferFunction(this);
+  }
+
+  /**
+   * Returns an empty store of the appropriate type.
+   *
+   * @return an empty store of the appropriate type
+   */
+  public abstract S createEmptyStore(boolean sequentialSemantics);
+
+  /**
+   * Returns an identical copy of the store {@code s}.
+   *
+   * @return an identical copy of the store {@code s}
+   */
+  public abstract S createCopiedStore(S s);
+
+  /**
+   * Creates an abstract value from the annotated type mirror. The value contains the set of primary
+   * annotations on the type, unless the type is an AnnotatedWildcardType. For an
+   * AnnotatedWildcardType, the annotations in the created value are the primary annotations on the
+   * extends bound. See {@link CFAbstractValue} for an explanation.
+   *
+   * @param type the type to convert into an abstract value
+   * @return an abstract value containing the given annotated {@code type}
+   */
+  public @Nullable V createAbstractValue(AnnotatedTypeMirror type) {
+    Set<AnnotationMirror> annos;
+    if (type.getKind() == TypeKind.WILDCARD) {
+      annos = ((AnnotatedWildcardType) type).getExtendsBound().getAnnotations();
+    } else {
+      annos = type.getAnnotations();
+    }
+    return createAbstractValue(annos, type.getUnderlyingType());
+  }
+
+  /**
+   * Returns an abstract value containing the given {@code annotations} and {@code underlyingType}.
+   * Returns null if the annotation set has missing annotations.
+   *
+   * @return an abstract value containing the given {@code annotations} and {@code underlyingType}
+   */
+  public abstract @Nullable V createAbstractValue(
+      Set<AnnotationMirror> annotations, TypeMirror underlyingType);
+
+  /** Default implementation for {@link #createAbstractValue(Set, TypeMirror)}. */
+  public CFValue defaultCreateAbstractValue(
+      CFAbstractAnalysis<CFValue, ?, ?> analysis,
+      Set<AnnotationMirror> annotations,
+      TypeMirror underlyingType) {
+    if (!CFAbstractValue.validateSet(annotations, underlyingType, qualifierHierarchy)) {
+      return null;
+    }
+    return new CFValue(analysis, annotations, underlyingType);
+  }
+
+  public TypeHierarchy getTypeHierarchy() {
+    return typeHierarchy;
+  }
+
+  public GenericAnnotatedTypeFactory<V, S, T, ? extends CFAbstractAnalysis<V, S, T>>
+      getTypeFactory() {
+    return atypeFactory;
+  }
+
+  /**
+   * Returns an abstract value containing an annotated type with the annotation {@code anno}, and
+   * 'top' for all other hierarchies. The underlying type is {@code underlyingType}.
+   */
+  public V createSingleAnnotationValue(AnnotationMirror anno, TypeMirror underlyingType) {
+    QualifierHierarchy hierarchy = getTypeFactory().getQualifierHierarchy();
+    Set<AnnotationMirror> annos = AnnotationUtils.createAnnotationSet();
+    annos.addAll(hierarchy.getTopAnnotations());
+    AnnotationMirror f = hierarchy.findAnnotationInSameHierarchy(annos, anno);
+    annos.remove(f);
+    annos.add(anno);
+    return createAbstractValue(annos, underlyingType);
+  }
+
+  /**
+   * Get the types utility.
+   *
+   * @return {@link #types}
+   */
+  public Types getTypes() {
+    return types;
+  }
+
+  /**
+   * Get the processing environment.
+   *
+   * @return {@link #env}
+   */
+  public ProcessingEnvironment getEnv() {
+    return env;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java
new file mode 100644
index 0000000..607f5dc
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java
@@ -0,0 +1,1280 @@
+package org.checkerframework.framework.flow;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BinaryOperator;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ThisNode;
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
+import org.checkerframework.dataflow.cfg.visualize.StringCFGVisualizer;
+import org.checkerframework.dataflow.expression.ArrayAccess;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.MethodCall;
+import org.checkerframework.dataflow.expression.ThisReference;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.dataflow.util.PurityUtils;
+import org.checkerframework.framework.qual.MonotonicQualifier;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.Pair;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.ToStringComparator;
+import org.plumelib.util.UniqueId;
+
+/**
+ * A store for the Checker Framework analysis. It tracks the annotations of memory locations such as
+ * local variables and fields.
+ *
+ * <p>When adding a new field to track values for a code construct (similar to {@code
+ * localVariableValues} and {@code thisValue}), it is important to review all constructors and
+ * methods in this class for locations where the new field must be handled (such as the copy
+ * constructor and {@code clearValue}), as well as all constructors/methods in subclasses of {code
+ * CFAbstractStore}. Note that this includes not only overridden methods in the subclasses, but new
+ * methods in the subclasses as well. Also check if
+ * BaseTypeVisitor#getJavaExpressionContextFromNode(Node) needs to be updated. Failing to do so may
+ * result in silent failures that are time consuming to debug.
+ */
+// TODO: Split this class into two parts: one that is reusable generally and
+// one that is specific to the Checker Framework.
+public abstract class CFAbstractStore<V extends CFAbstractValue<V>, S extends CFAbstractStore<V, S>>
+    implements Store<S>, UniqueId {
+
+  /** The analysis class this store belongs to. */
+  protected final CFAbstractAnalysis<V, S, ?> analysis;
+
+  /** Information collected about local variables (including method parameters). */
+  protected final Map<LocalVariable, V> localVariableValues;
+
+  /** Information collected about the current object. */
+  protected V thisValue;
+
+  /** Information collected about fields, using the internal representation {@link FieldAccess}. */
+  protected Map<FieldAccess, V> fieldValues;
+
+  /**
+   * Returns information about fields. Clients should not side-effect the returned value, which is
+   * aliased to internal state.
+   *
+   * @return information about fields
+   */
+  public Map<FieldAccess, V> getFieldValues() {
+    return fieldValues;
+  }
+
+  /**
+   * Information collected about array elements, using the internal representation {@link
+   * ArrayAccess}.
+   */
+  protected Map<ArrayAccess, V> arrayValues;
+
+  /**
+   * Information collected about method calls, using the internal representation {@link MethodCall}.
+   */
+  protected Map<MethodCall, V> methodValues;
+
+  /**
+   * Information collected about <i>classname</i>.class values, using the internal representation
+   * {@link ClassName}.
+   */
+  protected Map<ClassName, V> classValues;
+
+  /**
+   * Should the analysis use sequential Java semantics (i.e., assume that only one thread is running
+   * at all times)?
+   */
+  protected final boolean sequentialSemantics;
+
+  /** The unique ID for the next-created object. */
+  static final AtomicLong nextUid = new AtomicLong(0);
+  /** The unique ID of this object. */
+  final transient long uid = nextUid.getAndIncrement();
+
+  @Override
+  public long getUid() {
+    return uid;
+  }
+
+  /* --------------------------------------------------------- */
+  /* Initialization */
+  /* --------------------------------------------------------- */
+
+  /**
+   * Creates a new CFAbstractStore.
+   *
+   * @param analysis the analysis class this store belongs to
+   * @param sequentialSemantics should the analysis use sequential Java semantics?
+   */
+  protected CFAbstractStore(CFAbstractAnalysis<V, S, ?> analysis, boolean sequentialSemantics) {
+    this.analysis = analysis;
+    localVariableValues = new HashMap<>();
+    thisValue = null;
+    fieldValues = new HashMap<>();
+    methodValues = new HashMap<>();
+    arrayValues = new HashMap<>();
+    classValues = new HashMap<>();
+    this.sequentialSemantics = sequentialSemantics;
+  }
+
+  /** Copy constructor. */
+  protected CFAbstractStore(CFAbstractStore<V, S> other) {
+    this.analysis = other.analysis;
+    localVariableValues = new HashMap<>(other.localVariableValues);
+    thisValue = other.thisValue;
+    fieldValues = new HashMap<>(other.fieldValues);
+    methodValues = new HashMap<>(other.methodValues);
+    arrayValues = new HashMap<>(other.arrayValues);
+    classValues = new HashMap<>(other.classValues);
+    sequentialSemantics = other.sequentialSemantics;
+  }
+
+  /**
+   * Set the abstract value of a method parameter (only adds the information to the store, does not
+   * remove any other knowledge). Any previous information is erased; this method should only be
+   * used to initialize the abstract value.
+   */
+  public void initializeMethodParameter(LocalVariableNode p, @Nullable V value) {
+    if (value != null) {
+      localVariableValues.put(new LocalVariable(p.getElement()), value);
+    }
+  }
+
+  /**
+   * Set the value of the current object. Any previous information is erased; this method should
+   * only be used to initialize the value.
+   */
+  public void initializeThisValue(AnnotationMirror a, TypeMirror underlyingType) {
+    if (a != null) {
+      thisValue = analysis.createSingleAnnotationValue(a, underlyingType);
+    }
+  }
+
+  /**
+   * Indicates whether the given method is side-effect-free as far as the current store is
+   * concerned. In some cases, a store for a checker allows for other mechanisms to specify whether
+   * a method is side-effect-free. For example, unannotated methods may be considered
+   * side-effect-free by default.
+   *
+   * @param atypeFactory the type factory used to retrieve annotations on the method element
+   * @param method the method element
+   * @return whether the method is side-effect-free
+   */
+  protected boolean isSideEffectFree(AnnotatedTypeFactory atypeFactory, ExecutableElement method) {
+    return PurityUtils.isSideEffectFree(atypeFactory, method);
+  }
+
+  /* --------------------------------------------------------- */
+  /* Handling of fields */
+  /* --------------------------------------------------------- */
+
+  /**
+   * Remove any information that might not be valid any more after a method call, and add
+   * information guaranteed by the method.
+   *
+   * <ol>
+   *   <li>If the method is side-effect-free (as indicated by {@link
+   *       org.checkerframework.dataflow.qual.SideEffectFree} or {@link
+   *       org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed.
+   *   <li>Otherwise, all information about field accesses {@code a.f} needs to be removed, except
+   *       if the method {@code n} cannot modify {@code a.f} (e.g., if {@code a} is a local variable
+   *       or {@code this}, and {@code f} is final).
+   *   <li>Furthermore, if the field has a monotonic annotation, then its information can also be
+   *       kept.
+   * </ol>
+   *
+   * Furthermore, if the method is deterministic, we store its result {@code val} in the store.
+   */
+  public void updateForMethodCall(
+      MethodInvocationNode n, AnnotatedTypeFactory atypeFactory, V val) {
+    ExecutableElement method = n.getTarget().getMethod();
+
+    // case 1: remove information if necessary
+    if (!(analysis.checker.hasOption("assumeSideEffectFree")
+        || analysis.checker.hasOption("assumePure")
+        || isSideEffectFree(atypeFactory, method))) {
+
+      boolean sideEffectsUnrefineAliases =
+          ((GenericAnnotatedTypeFactory) atypeFactory).sideEffectsUnrefineAliases;
+
+      // update local variables
+      // TODO: Also remove if any element/argument to the annotation is not
+      // isUnmodifiableByOtherCode.  Example: @KeyFor("valueThatCanBeMutated").
+      if (sideEffectsUnrefineAliases) {
+        localVariableValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode());
+      }
+
+      // update this value
+      if (sideEffectsUnrefineAliases) {
+        thisValue = null;
+      }
+
+      // update field values
+      if (sideEffectsUnrefineAliases) {
+        fieldValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode());
+      } else {
+        Map<FieldAccess, V> newFieldValues =
+            new HashMap<>(CollectionsPlume.mapCapacity(fieldValues));
+        for (Map.Entry<FieldAccess, V> e : fieldValues.entrySet()) {
+          FieldAccess fieldAccess = e.getKey();
+          V otherVal = e.getValue();
+
+          // case 3: the field has a monotonic annotation
+          if (!((GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory)
+              .getSupportedMonotonicTypeQualifiers()
+              .isEmpty()) {
+            List<Pair<AnnotationMirror, AnnotationMirror>> fieldAnnotations =
+                atypeFactory.getAnnotationWithMetaAnnotation(
+                    fieldAccess.getField(), MonotonicQualifier.class);
+            V newOtherVal = null;
+            for (Pair<AnnotationMirror, AnnotationMirror> fieldAnnotation : fieldAnnotations) {
+              AnnotationMirror monotonicAnnotation = fieldAnnotation.second;
+              @SuppressWarnings("deprecation") // permitted for use in the framework
+              Name annotation =
+                  AnnotationUtils.getElementValueClassName(monotonicAnnotation, "value", false);
+              AnnotationMirror target =
+                  AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annotation);
+              // Make sure the 'target' annotation is present.
+              if (AnnotationUtils.containsSame(otherVal.getAnnotations(), target)) {
+                newOtherVal =
+                    analysis
+                        .createSingleAnnotationValue(target, otherVal.getUnderlyingType())
+                        .mostSpecific(newOtherVal, null);
+              }
+            }
+            if (newOtherVal != null) {
+              // keep information for all hierarchies where we had a
+              // monotone annotation.
+              newFieldValues.put(fieldAccess, newOtherVal);
+              continue;
+            }
+          }
+
+          // case 2:
+          if (!fieldAccess.isUnassignableByOtherCode()) {
+            continue; // remove information completely
+          }
+
+          // keep information
+          newFieldValues.put(fieldAccess, otherVal);
+        }
+        fieldValues = newFieldValues;
+      }
+
+      // update array values
+      arrayValues.clear();
+
+      // update method values
+      methodValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode());
+    }
+
+    // store information about method call if possible
+    JavaExpression methodCall = JavaExpression.fromNode(n);
+    replaceValue(methodCall, val);
+  }
+
+  /**
+   * Add the annotation {@code a} for the expression {@code expr} (correctly deciding where to store
+   * the information depending on the type of the expression {@code expr}).
+   *
+   * <p>This method does not take care of removing other information that might be influenced by
+   * changes to certain parts of the state.
+   *
+   * <p>If there is already a value {@code v} present for {@code expr}, then the stronger of the new
+   * and old value are taken (according to the lattice). Note that this happens per hierarchy, and
+   * if the store already contains information about a hierarchy other than {@code a}s hierarchy,
+   * that information is preserved.
+   *
+   * <p>If {@code expr} is nondeterministic, this method does not insert {@code value} into the
+   * store.
+   *
+   * @param expr an expression
+   * @param a an annotation for the expression
+   */
+  public void insertValue(JavaExpression expr, AnnotationMirror a) {
+    insertValue(expr, analysis.createSingleAnnotationValue(a, expr.getType()));
+  }
+
+  /**
+   * Like {@link #insertValue(JavaExpression, AnnotationMirror)}, but permits nondeterministic
+   * expressions to be stored.
+   *
+   * <p>For an explanation of when to permit nondeterministic expressions, see {@link
+   * #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}.
+   *
+   * @param expr an expression
+   * @param a an annotation for the expression
+   */
+  public void insertValuePermitNondeterministic(JavaExpression expr, AnnotationMirror a) {
+    insertValuePermitNondeterministic(
+        expr, analysis.createSingleAnnotationValue(a, expr.getType()));
+  }
+
+  /**
+   * Add the annotation {@code newAnno} for the expression {@code expr} (correctly deciding where to
+   * store the information depending on the type of the expression {@code expr}).
+   *
+   * <p>This method does not take care of removing other information that might be influenced by
+   * changes to certain parts of the state.
+   *
+   * <p>If there is already a value {@code v} present for {@code expr}, then the greatest lower
+   * bound of the new and old value is inserted into the store.
+   *
+   * <p>Note that this happens per hierarchy, and if the store already contains information about a
+   * hierarchy other than {@code newAnno}'s hierarchy, that information is preserved.
+   *
+   * <p>If {@code expr} is nondeterministic, this method does not insert {@code value} into the
+   * store.
+   *
+   * @param expr an expression
+   * @param newAnno the expression's annotation
+   */
+  public final void insertOrRefine(JavaExpression expr, AnnotationMirror newAnno) {
+    insertOrRefine(expr, newAnno, false);
+  }
+
+  /**
+   * Like {@link #insertOrRefine(JavaExpression, AnnotationMirror)}, but permits nondeterministic
+   * expressions to be inserted.
+   *
+   * <p>For an explanation of when to permit nondeterministic expressions, see {@link
+   * #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}.
+   *
+   * @param expr an expression
+   * @param newAnno the expression's annotation
+   */
+  public final void insertOrRefinePermitNondeterministic(
+      JavaExpression expr, AnnotationMirror newAnno) {
+    insertOrRefine(expr, newAnno, true);
+  }
+
+  /**
+   * Helper function for {@link #insertOrRefine(JavaExpression, AnnotationMirror)} and {@link
+   * #insertOrRefinePermitNondeterministic}.
+   *
+   * @param expr an expression
+   * @param newAnno the expression's annotation
+   * @param permitNondeterministic whether nondeterministic expressions may be inserted into the
+   *     store
+   */
+  protected void insertOrRefine(
+      JavaExpression expr, AnnotationMirror newAnno, boolean permitNondeterministic) {
+    if (!canInsertJavaExpression(expr)) {
+      return;
+    }
+    if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) {
+      return;
+    }
+
+    V newValue = analysis.createSingleAnnotationValue(newAnno, expr.getType());
+    V oldValue = getValue(expr);
+    if (oldValue == null) {
+      insertValue(
+          expr,
+          analysis.createSingleAnnotationValue(newAnno, expr.getType()),
+          permitNondeterministic);
+      return;
+    }
+    computeNewValueAndInsert(
+        expr, newValue, CFAbstractValue<V>::greatestLowerBound, permitNondeterministic);
+  }
+
+  /** Returns true if {@code expr} can be stored in this store. */
+  public static boolean canInsertJavaExpression(JavaExpression expr) {
+    if (expr instanceof FieldAccess
+        || expr instanceof ThisReference
+        || expr instanceof LocalVariable
+        || expr instanceof MethodCall
+        || expr instanceof ArrayAccess
+        || expr instanceof ClassName) {
+      return !expr.containsUnknown();
+    }
+    return false;
+  }
+
+  /**
+   * Add the abstract value {@code value} for the expression {@code expr} (correctly deciding where
+   * to store the information depending on the type of the expression {@code expr}).
+   *
+   * <p>This method does not take care of removing other information that might be influenced by
+   * changes to certain parts of the state.
+   *
+   * <p>If there is already a value {@code v} present for {@code expr}, then the stronger of the new
+   * and old value are taken (according to the lattice). Note that this happens per hierarchy, and
+   * if the store already contains information about a hierarchy for which {@code value} does not
+   * contain information, then that information is preserved.
+   *
+   * <p>If {@code expr} is nondeterministic, this method does not insert {@code value} into the
+   * store.
+   *
+   * @param expr the expression to insert in the store
+   * @param value the value of the expression
+   */
+  public final void insertValue(JavaExpression expr, @Nullable V value) {
+    insertValue(expr, value, false);
+  }
+  /**
+   * Like {@link #insertValue(JavaExpression, CFAbstractValue)}, but updates the store even if
+   * {@code expr} is nondeterministic.
+   *
+   * <p>Usually, nondeterministic JavaExpressions should not be stored in a Store. For example, in
+   * the body of {@code if (nondet() == 3) {...}}, the store should not record that the value of
+   * {@code nondet()} is 3, because it might not be 3 the next time {@code nondet()} is executed.
+   *
+   * <p>However, contracts can mention a nondeterministic JavaExpression. For example, a contract
+   * might have a postcondition that{@code nondet()} is odd. This means that the next call to{@code
+   * nondet()} will return odd. Such a postcondition may be evicted from the store by calling a
+   * side-effecting method.
+   *
+   * @param expr the expression to insert in the store
+   * @param value the value of the expression
+   */
+  public final void insertValuePermitNondeterministic(JavaExpression expr, @Nullable V value) {
+    insertValue(expr, value, true);
+  }
+
+  /**
+   * Returns true if the given (expression, value) pair can be inserted in the store, namely if the
+   * value is non-null and the expression does not contain unknown or a nondeterministic expression.
+   *
+   * <p>This method returning true does not guarantee that the value will be inserted; the
+   * implementation of {@link #insertValue( JavaExpression, CFAbstractValue, boolean)} might still
+   * not insert it.
+   *
+   * @param expr the expression to insert in the store
+   * @param value the value of the expression
+   * @param permitNondeterministic if false, returns false if {@code expr} is nondeterministic; if
+   *     true, permits nondeterministic expressions to be placed in the store
+   * @return true if the given (expression, value) pair can be inserted in the store
+   */
+  @EnsuresNonNullIf(expression = "#2", result = true)
+  protected boolean shouldInsert(
+      JavaExpression expr, @Nullable V value, boolean permitNondeterministic) {
+    if (value == null) {
+      // No need to insert a null abstract value because it represents
+      // top and top is also the default value.
+      return false;
+    }
+    if (expr.containsUnknown()) {
+      // Expressions containing unknown expressions are not stored.
+      return false;
+    }
+    if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) {
+      // Nondeterministic expressions may not be stored.
+      // (They are likely to be quickly evicted, as soon as a side-effecting method is called.)
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Helper method for {@link #insertValue(JavaExpression, CFAbstractValue)} and {@link
+   * #insertValuePermitNondeterministic}.
+   *
+   * <p>Every overriding implementation should start with
+   *
+   * <pre>{@code
+   * if (!shouldInsert) {
+   *   return;
+   * }
+   * }</pre>
+   *
+   * @param expr the expression to insert in the store
+   * @param value the value of the expression
+   * @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if
+   *     true, permits nondeterministic expressions to be placed in the store
+   */
+  protected void insertValue(
+      JavaExpression expr, @Nullable V value, boolean permitNondeterministic) {
+    computeNewValueAndInsert(
+        expr, value, (old, newValue) -> newValue.mostSpecific(old, null), permitNondeterministic);
+  }
+
+  /**
+   * Inserts the result of applying {@code merger} to {@code value} and the previous value for
+   * {@code expr}.
+   *
+   * @param expr the JavaExpression
+   * @param value the value of the JavaExpression
+   * @param merger the function used to merge {@code value} and the previous value of {@code expr}
+   * @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if
+   *     true, permits nondeterministic expressions to be placed in the store
+   */
+  protected void computeNewValueAndInsert(
+      JavaExpression expr,
+      @Nullable V value,
+      BinaryOperator<V> merger,
+      boolean permitNondeterministic) {
+    if (!shouldInsert(expr, value, permitNondeterministic)) {
+      return;
+    }
+
+    if (expr instanceof LocalVariable) {
+      LocalVariable localVar = (LocalVariable) expr;
+      V oldValue = localVariableValues.get(localVar);
+      V newValue = merger.apply(oldValue, value);
+      if (newValue != null) {
+        localVariableValues.put(localVar, newValue);
+      }
+    } else if (expr instanceof FieldAccess) {
+      FieldAccess fieldAcc = (FieldAccess) expr;
+      // Only store information about final fields (where the receiver is
+      // also fixed) if concurrent semantics are enabled.
+      boolean isMonotonic = isMonotonicUpdate(fieldAcc, value);
+      if (sequentialSemantics || isMonotonic || fieldAcc.isUnassignableByOtherCode()) {
+        V oldValue = fieldValues.get(fieldAcc);
+        V newValue = merger.apply(oldValue, value);
+        if (newValue != null) {
+          fieldValues.put(fieldAcc, newValue);
+        }
+      }
+    } else if (expr instanceof MethodCall) {
+      MethodCall method = (MethodCall) expr;
+      // Don't store any information if concurrent semantics are enabled.
+      if (sequentialSemantics) {
+        V oldValue = methodValues.get(method);
+        V newValue = merger.apply(oldValue, value);
+        if (newValue != null) {
+          methodValues.put(method, newValue);
+        }
+      }
+    } else if (expr instanceof ArrayAccess) {
+      ArrayAccess arrayAccess = (ArrayAccess) expr;
+      if (sequentialSemantics) {
+        V oldValue = arrayValues.get(arrayAccess);
+        V newValue = merger.apply(oldValue, value);
+        if (newValue != null) {
+          arrayValues.put(arrayAccess, newValue);
+        }
+      }
+    } else if (expr instanceof ThisReference) {
+      ThisReference thisRef = (ThisReference) expr;
+      if (sequentialSemantics || thisRef.isUnassignableByOtherCode()) {
+        V oldValue = thisValue;
+        V newValue = merger.apply(oldValue, value);
+        if (newValue != null) {
+          thisValue = newValue;
+        }
+      }
+    } else if (expr instanceof ClassName) {
+      ClassName className = (ClassName) expr;
+      if (sequentialSemantics || className.isUnassignableByOtherCode()) {
+        V oldValue = classValues.get(className);
+        V newValue = merger.apply(oldValue, value);
+        if (newValue != null) {
+          classValues.put(className, newValue);
+        }
+      }
+    } else {
+      // No other types of expressions need to be stored.
+    }
+  }
+
+  /**
+   * Return true if fieldAcc is an update of a monotonic qualifier to its target qualifier.
+   * (e.g. @MonotonicNonNull to @NonNull). Always returns false if {@code sequentialSemantics} is
+   * true.
+   *
+   * @return true if fieldAcc is an update of a monotonic qualifier to its target qualifier
+   *     (e.g. @MonotonicNonNull to @NonNull)
+   */
+  protected boolean isMonotonicUpdate(FieldAccess fieldAcc, V value) {
+    if (analysis.atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) {
+      return false;
+    }
+    boolean isMonotonic = false;
+    // TODO: This check for !sequentialSemantics is an optimization that breaks the contract of
+    // the method, since the method name and documentation say nothing about sequential
+    // semantics.  This check should be performed by callers of this method when needed.
+    // TODO: Update the javadoc of this method when the above to-do item is addressed.
+    if (!sequentialSemantics) { // only compute if necessary
+      AnnotatedTypeFactory atypeFactory = this.analysis.atypeFactory;
+      List<Pair<AnnotationMirror, AnnotationMirror>> fieldAnnotations =
+          atypeFactory.getAnnotationWithMetaAnnotation(
+              fieldAcc.getField(), MonotonicQualifier.class);
+      for (Pair<AnnotationMirror, AnnotationMirror> fieldAnnotation : fieldAnnotations) {
+        AnnotationMirror monotonicAnnotation = fieldAnnotation.second;
+        @SuppressWarnings("deprecation") // permitted for use in the framework
+        Name annotation =
+            AnnotationUtils.getElementValueClassName(monotonicAnnotation, "value", false);
+        AnnotationMirror target =
+            AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annotation);
+        // Make sure the 'target' annotation is present.
+        if (AnnotationUtils.containsSame(value.getAnnotations(), target)) {
+          isMonotonic = true;
+          break;
+        }
+      }
+    }
+    return isMonotonic;
+  }
+
+  public void insertThisValue(AnnotationMirror a, TypeMirror underlyingType) {
+    if (a == null) {
+      return;
+    }
+
+    V value = analysis.createSingleAnnotationValue(a, underlyingType);
+
+    V oldValue = thisValue;
+    V newValue = value.mostSpecific(oldValue, null);
+    if (newValue != null) {
+      thisValue = newValue;
+    }
+  }
+
+  /**
+   * Completely replaces the abstract value {@code value} for the expression {@code expr} (correctly
+   * deciding where to store the information depending on the type of the expression {@code expr}).
+   * Any previous information is discarded.
+   *
+   * <p>This method does not take care of removing other information that might be influenced by
+   * changes to certain parts of the state.
+   */
+  public void replaceValue(JavaExpression expr, @Nullable V value) {
+    clearValue(expr);
+    insertValue(expr, value);
+  }
+
+  /**
+   * Remove any knowledge about the expression {@code expr} (correctly deciding where to remove the
+   * information depending on the type of the expression {@code expr}).
+   */
+  public void clearValue(JavaExpression expr) {
+    if (expr.containsUnknown()) {
+      // Expressions containing unknown expressions are not stored.
+      return;
+    }
+    if (expr instanceof LocalVariable) {
+      LocalVariable localVar = (LocalVariable) expr;
+      localVariableValues.remove(localVar);
+    } else if (expr instanceof FieldAccess) {
+      FieldAccess fieldAcc = (FieldAccess) expr;
+      fieldValues.remove(fieldAcc);
+    } else if (expr instanceof MethodCall) {
+      MethodCall method = (MethodCall) expr;
+      methodValues.remove(method);
+    } else if (expr instanceof ArrayAccess) {
+      ArrayAccess a = (ArrayAccess) expr;
+      arrayValues.remove(a);
+    } else if (expr instanceof ClassName) {
+      ClassName c = (ClassName) expr;
+      classValues.remove(c);
+    } else { // thisValue ...
+      // No other types of expressions are stored.
+    }
+  }
+
+  /**
+   * Returns the current abstract value of a Java expression, or {@code null} if no information is
+   * available.
+   *
+   * @return the current abstract value of a Java expression, or {@code null} if no information is
+   *     available
+   */
+  public @Nullable V getValue(JavaExpression expr) {
+    if (expr instanceof LocalVariable) {
+      LocalVariable localVar = (LocalVariable) expr;
+      return localVariableValues.get(localVar);
+    } else if (expr instanceof ThisReference) {
+      return thisValue;
+    } else if (expr instanceof FieldAccess) {
+      FieldAccess fieldAcc = (FieldAccess) expr;
+      return fieldValues.get(fieldAcc);
+    } else if (expr instanceof MethodCall) {
+      MethodCall method = (MethodCall) expr;
+      return methodValues.get(method);
+    } else if (expr instanceof ArrayAccess) {
+      ArrayAccess a = (ArrayAccess) expr;
+      return arrayValues.get(a);
+    } else if (expr instanceof ClassName) {
+      ClassName c = (ClassName) expr;
+      return classValues.get(c);
+    } else {
+      throw new BugInCF("Unexpected JavaExpression: " + expr + " (" + expr.getClass() + ")");
+    }
+  }
+
+  /**
+   * Returns the current abstract value of a field access, or {@code null} if no information is
+   * available.
+   *
+   * @param n the node whose abstract value to return
+   * @return the current abstract value of a field access, or {@code null} if no information is
+   *     available
+   */
+  public @Nullable V getValue(FieldAccessNode n) {
+    JavaExpression je = JavaExpression.fromNodeFieldAccess(n);
+    if (je instanceof FieldAccess) {
+      return fieldValues.get((FieldAccess) je);
+    } else if (je instanceof ClassName) {
+      return classValues.get((ClassName) je);
+    } else if (je instanceof ThisReference) {
+      // "return thisValue" is wrong, because the node refers to an outer this.
+      // So, return null for now.  TODO: improve.
+      return null;
+    } else {
+      throw new BugInCF(
+          "Unexpected JavaExpression %s %s for FieldAccessNode %s",
+          je.getClass().getSimpleName(), je, n);
+    }
+  }
+
+  /**
+   * Returns the current abstract value of a field access, or {@code null} if no information is
+   * available.
+   *
+   * @param fieldAccess the field access to look up in this store
+   * @return current abstract value of a field access, or {@code null} if no information is
+   *     available
+   */
+  public @Nullable V getFieldValue(FieldAccess fieldAccess) {
+    return fieldValues.get(fieldAccess);
+  }
+
+  /**
+   * Returns the current abstract value of a method call, or {@code null} if no information is
+   * available.
+   *
+   * @param n a method call
+   * @return the current abstract value of a method call, or {@code null} if no information is
+   *     available
+   */
+  public @Nullable V getValue(MethodInvocationNode n) {
+    JavaExpression method = JavaExpression.fromNode(n);
+    if (method == null) {
+      return null;
+    }
+    return methodValues.get(method);
+  }
+
+  /**
+   * Returns the current abstract value of a field access, or {@code null} if no information is
+   * available.
+   *
+   * @param n the node whose abstract value to return
+   * @return the current abstract value of a field access, or {@code null} if no information is
+   *     available
+   */
+  public @Nullable V getValue(ArrayAccessNode n) {
+    ArrayAccess arrayAccess = JavaExpression.fromArrayAccess(n);
+    return arrayValues.get(arrayAccess);
+  }
+
+  /**
+   * Update the information in the store by considering an assignment with target {@code n}.
+   *
+   * @param n the left-hand side of an assignment
+   * @param val the right-hand value of an assignment
+   */
+  public void updateForAssignment(Node n, @Nullable V val) {
+    JavaExpression je = JavaExpression.fromNode(n);
+    if (je instanceof ArrayAccess) {
+      updateForArrayAssignment((ArrayAccess) je, val);
+    } else if (je instanceof FieldAccess) {
+      updateForFieldAccessAssignment((FieldAccess) je, val);
+    } else if (je instanceof LocalVariable) {
+      updateForLocalVariableAssignment((LocalVariable) je, val);
+    } else {
+      throw new BugInCF("Unexpected je of class " + je.getClass());
+    }
+  }
+
+  /**
+   * Update the information in the store by considering a field assignment with target {@code n},
+   * where the right hand side has the abstract value {@code val}.
+   *
+   * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the
+   *     abstract value is not known).
+   */
+  protected void updateForFieldAccessAssignment(FieldAccess fieldAccess, @Nullable V val) {
+    removeConflicting(fieldAccess, val);
+    if (!fieldAccess.containsUnknown() && val != null) {
+      // Only store information about final fields (where the receiver is
+      // also fixed) if concurrent semantics are enabled.
+      if (sequentialSemantics
+          || isMonotonicUpdate(fieldAccess, val)
+          || fieldAccess.isUnassignableByOtherCode()) {
+        fieldValues.put(fieldAccess, val);
+      }
+    }
+  }
+
+  /**
+   * Update the information in the store by considering an assignment with target {@code n}, where
+   * the target is an array access.
+   *
+   * <p>See {@link #removeConflicting(ArrayAccess,CFAbstractValue)}, as it is called first by this
+   * method.
+   */
+  protected void updateForArrayAssignment(ArrayAccess arrayAccess, @Nullable V val) {
+    removeConflicting(arrayAccess, val);
+    if (!arrayAccess.containsUnknown() && val != null) {
+      // Only store information about final fields (where the receiver is
+      // also fixed) if concurrent semantics are enabled.
+      if (sequentialSemantics) {
+        arrayValues.put(arrayAccess, val);
+      }
+    }
+  }
+
+  /**
+   * Set the abstract value of a local variable in the store. Overwrites any value that might have
+   * been available previously.
+   *
+   * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the
+   *     abstract value is not known).
+   */
+  protected void updateForLocalVariableAssignment(LocalVariable receiver, @Nullable V val) {
+    removeConflicting(receiver);
+    if (val != null) {
+      localVariableValues.put(receiver, val);
+    }
+  }
+
+  /**
+   * Remove any information in this store that might not be true any more after {@code fieldAccess}
+   * has been assigned a new value (with the abstract value {@code val}). This includes the
+   * following steps (assume that {@code fieldAccess} is of the form <em>a.f</em> for some
+   * <em>a</em>.
+   *
+   * <ol>
+   *   <li value="1">Update the abstract value of other field accesses <em>b.g</em> where the field
+   *       is equal (that is, <em>f=g</em>), and the receiver <em>b</em> might alias the receiver of
+   *       {@code fieldAccess}, <em>a</em>. This update will raise the abstract value for such field
+   *       accesses to at least {@code val} (or the old value, if that was less precise). However,
+   *       this is only necessary if the field <em>g</em> is not final.
+   *   <li value="2">Remove any abstract values for field accesses <em>b.g</em> where {@code
+   *       fieldAccess} might alias any expression in the receiver <em>b</em>.
+   *   <li value="3">Remove any information about method calls.
+   *   <li value="4">Remove any abstract values an array access <em>b[i]</em> where {@code
+   *       fieldAccess} might alias any expression in the receiver <em>a</em> or index <em>i</em>.
+   * </ol>
+   *
+   * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the
+   *     abstract value is not known).
+   */
+  protected void removeConflicting(FieldAccess fieldAccess, @Nullable V val) {
+    final Iterator<Map.Entry<FieldAccess, V>> fieldValuesIterator =
+        fieldValues.entrySet().iterator();
+    while (fieldValuesIterator.hasNext()) {
+      Map.Entry<FieldAccess, V> entry = fieldValuesIterator.next();
+      FieldAccess otherFieldAccess = entry.getKey();
+      V otherVal = entry.getValue();
+      // case 2:
+      if (otherFieldAccess.getReceiver().containsModifiableAliasOf(this, fieldAccess)) {
+        fieldValuesIterator.remove(); // remove information completely
+      }
+      // case 1:
+      else if (fieldAccess.getField().equals(otherFieldAccess.getField())) {
+        if (canAlias(fieldAccess.getReceiver(), otherFieldAccess.getReceiver())) {
+          if (!otherFieldAccess.isFinal()) {
+            if (val != null) {
+              V newVal = val.leastUpperBound(otherVal);
+              entry.setValue(newVal);
+            } else {
+              // remove information completely
+              fieldValuesIterator.remove();
+            }
+          }
+        }
+      }
+    }
+
+    final Iterator<Map.Entry<ArrayAccess, V>> arrayValuesIterator =
+        arrayValues.entrySet().iterator();
+    while (arrayValuesIterator.hasNext()) {
+      Map.Entry<ArrayAccess, V> entry = arrayValuesIterator.next();
+      ArrayAccess otherArrayAccess = entry.getKey();
+      if (otherArrayAccess.containsModifiableAliasOf(this, fieldAccess)) {
+        // remove information completely
+        arrayValuesIterator.remove();
+      }
+    }
+
+    // case 3:
+    methodValues.clear();
+  }
+
+  /**
+   * Remove any information in the store that might not be true any more after {@code arrayAccess}
+   * has been assigned a new value (with the abstract value {@code val}). This includes the
+   * following steps (assume that {@code arrayAccess} is of the form <em>a[i]</em> for some
+   * <em>a</em>.
+   *
+   * <ol>
+   *   <li value="1">Remove any abstract value for other array access <em>b[j]</em> where <em>a</em>
+   *       and <em>b</em> can be aliases, or where either <em>b</em> or <em>j</em> contains a
+   *       modifiable alias of <em>a[i]</em>.
+   *   <li value="2">Remove any abstract values for field accesses <em>b.g</em> where <em>a[i]</em>
+   *       might alias any expression in the receiver <em>b</em> and there is an array expression
+   *       somewhere in the receiver.
+   *   <li value="3">Remove any information about method calls.
+   * </ol>
+   *
+   * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the
+   *     abstract value is not known).
+   */
+  protected void removeConflicting(ArrayAccess arrayAccess, @Nullable V val) {
+    final Iterator<Map.Entry<ArrayAccess, V>> arrayValuesIterator =
+        arrayValues.entrySet().iterator();
+    while (arrayValuesIterator.hasNext()) {
+      Map.Entry<ArrayAccess, V> entry = arrayValuesIterator.next();
+      ArrayAccess otherArrayAccess = entry.getKey();
+      // case 1:
+      if (otherArrayAccess.containsModifiableAliasOf(this, arrayAccess)) {
+        arrayValuesIterator.remove(); // remove information completely
+      } else if (canAlias(arrayAccess.getArray(), otherArrayAccess.getArray())) {
+        // TODO: one could be less strict here, and only raise the abstract
+        // value for all array expressions with potentially aliasing receivers.
+        arrayValuesIterator.remove(); // remove information completely
+      }
+    }
+
+    // case 2:
+    final Iterator<Map.Entry<FieldAccess, V>> fieldValuesIterator =
+        fieldValues.entrySet().iterator();
+    while (fieldValuesIterator.hasNext()) {
+      Map.Entry<FieldAccess, V> entry = fieldValuesIterator.next();
+      FieldAccess otherFieldAccess = entry.getKey();
+      JavaExpression otherReceiver = otherFieldAccess.getReceiver();
+      if (otherReceiver.containsModifiableAliasOf(this, arrayAccess)
+          && otherReceiver.containsOfClass(ArrayAccess.class)) {
+        // remove information completely
+        fieldValuesIterator.remove();
+      }
+    }
+
+    // case 3:
+    methodValues.clear();
+  }
+
+  /**
+   * Remove any information in this store that might not be true any more after {@code localVar} has
+   * been assigned a new value. This includes the following steps:
+   *
+   * <ol>
+   *   <li value="1">Remove any abstract values for field accesses <em>b.g</em> where {@code
+   *       localVar} might alias any expression in the receiver <em>b</em>.
+   *   <li value="2">Remove any abstract values for array accesses <em>a[i]</em> where {@code
+   *       localVar} might alias the receiver <em>a</em>.
+   *   <li value="3">Remove any information about method calls where the receiver or any of the
+   *       parameters contains {@code localVar}.
+   * </ol>
+   */
+  protected void removeConflicting(LocalVariable var) {
+    final Iterator<Map.Entry<FieldAccess, V>> fieldValuesIterator =
+        fieldValues.entrySet().iterator();
+    while (fieldValuesIterator.hasNext()) {
+      Map.Entry<FieldAccess, V> entry = fieldValuesIterator.next();
+      FieldAccess otherFieldAccess = entry.getKey();
+      // case 1:
+      if (otherFieldAccess.containsSyntacticEqualJavaExpression(var)) {
+        fieldValuesIterator.remove();
+      }
+    }
+
+    final Iterator<Map.Entry<ArrayAccess, V>> arrayValuesIterator =
+        arrayValues.entrySet().iterator();
+    while (arrayValuesIterator.hasNext()) {
+      Map.Entry<ArrayAccess, V> entry = arrayValuesIterator.next();
+      ArrayAccess otherArrayAccess = entry.getKey();
+      // case 2:
+      if (otherArrayAccess.containsSyntacticEqualJavaExpression(var)) {
+        arrayValuesIterator.remove();
+      }
+    }
+
+    final Iterator<Map.Entry<MethodCall, V>> methodValuesIterator =
+        methodValues.entrySet().iterator();
+    while (methodValuesIterator.hasNext()) {
+      Map.Entry<MethodCall, V> entry = methodValuesIterator.next();
+      MethodCall otherMethodAccess = entry.getKey();
+      // case 3:
+      if (otherMethodAccess.containsSyntacticEqualJavaExpression(var)) {
+        methodValuesIterator.remove();
+      }
+    }
+  }
+
+  /**
+   * Can the objects {@code a} and {@code b} be aliases? Returns a conservative answer (i.e.,
+   * returns {@code true} if not enough information is available to determine aliasing).
+   */
+  @Override
+  public boolean canAlias(JavaExpression a, JavaExpression b) {
+    TypeMirror tb = b.getType();
+    TypeMirror ta = a.getType();
+    Types types = analysis.getTypes();
+    return types.isSubtype(ta, tb) || types.isSubtype(tb, ta);
+  }
+
+  /* --------------------------------------------------------- */
+  /* Handling of local variables */
+  /* --------------------------------------------------------- */
+
+  /**
+   * Returns the current abstract value of a local variable, or {@code null} if no information is
+   * available.
+   *
+   * @return the current abstract value of a local variable, or {@code null} if no information is
+   *     available
+   */
+  public @Nullable V getValue(LocalVariableNode n) {
+    Element el = n.getElement();
+    return localVariableValues.get(new LocalVariable(el));
+  }
+
+  /* --------------------------------------------------------- */
+  /* Handling of the current object */
+  /* --------------------------------------------------------- */
+
+  /**
+   * Returns the current abstract value of the current object, or {@code null} if no information is
+   * available.
+   *
+   * @param n a reference to "this"
+   * @return the current abstract value of the current object, or {@code null} if no information is
+   *     available
+   */
+  public @Nullable V getValue(ThisNode n) {
+    return thisValue;
+  }
+
+  /* --------------------------------------------------------- */
+  /* Helper and miscellaneous methods */
+  /* --------------------------------------------------------- */
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public S copy() {
+    return analysis.createCopiedStore((S) this);
+  }
+
+  @Override
+  public S leastUpperBound(S other) {
+    return upperBound(other, false);
+  }
+
+  @Override
+  public S widenedUpperBound(S previous) {
+    return upperBound(previous, true);
+  }
+
+  private S upperBound(S other, boolean shouldWiden) {
+    S newStore = analysis.createEmptyStore(sequentialSemantics);
+
+    for (Map.Entry<LocalVariable, V> e : other.localVariableValues.entrySet()) {
+      // local variables that are only part of one store, but not the other are discarded, as one of
+      // store implicitly contains 'top' for that variable.
+      LocalVariable localVar = e.getKey();
+      V thisVal = localVariableValues.get(localVar);
+      if (thisVal != null) {
+        V otherVal = e.getValue();
+        V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
+
+        if (mergedVal != null) {
+          newStore.localVariableValues.put(localVar, mergedVal);
+        }
+      }
+    }
+
+    // information about the current object
+    {
+      V otherVal = other.thisValue;
+      V myVal = thisValue;
+      V mergedVal = myVal == null ? null : upperBoundOfValues(otherVal, myVal, shouldWiden);
+      if (mergedVal != null) {
+        newStore.thisValue = mergedVal;
+      }
+    }
+
+    for (Map.Entry<FieldAccess, V> e : other.fieldValues.entrySet()) {
+      // information about fields that are only part of one store, but not the other are discarded,
+      // as one store implicitly contains 'top' for that field.
+      FieldAccess el = e.getKey();
+      V thisVal = fieldValues.get(el);
+      if (thisVal != null) {
+        V otherVal = e.getValue();
+        V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
+        if (mergedVal != null) {
+          newStore.fieldValues.put(el, mergedVal);
+        }
+      }
+    }
+    for (Map.Entry<ArrayAccess, V> e : other.arrayValues.entrySet()) {
+      // information about arrays that are only part of one store, but not the other are discarded,
+      // as one store implicitly contains 'top' for that array access.
+      ArrayAccess el = e.getKey();
+      V thisVal = arrayValues.get(el);
+      if (thisVal != null) {
+        V otherVal = e.getValue();
+        V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
+        if (mergedVal != null) {
+          newStore.arrayValues.put(el, mergedVal);
+        }
+      }
+    }
+    for (Map.Entry<MethodCall, V> e : other.methodValues.entrySet()) {
+      // information about methods that are only part of one store, but not the other are discarded,
+      // as one store implicitly contains 'top' for that field.
+      MethodCall el = e.getKey();
+      V thisVal = methodValues.get(el);
+      if (thisVal != null) {
+        V otherVal = e.getValue();
+        V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
+        if (mergedVal != null) {
+          newStore.methodValues.put(el, mergedVal);
+        }
+      }
+    }
+    for (Map.Entry<ClassName, V> e : other.classValues.entrySet()) {
+      ClassName el = e.getKey();
+      V thisVal = classValues.get(el);
+      if (thisVal != null) {
+        V otherVal = e.getValue();
+        V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
+        if (mergedVal != null) {
+          newStore.classValues.put(el, mergedVal);
+        }
+      }
+    }
+    return newStore;
+  }
+
+  private V upperBoundOfValues(V otherVal, V thisVal, boolean shouldWiden) {
+    return shouldWiden ? thisVal.widenUpperBound(otherVal) : thisVal.leastUpperBound(otherVal);
+  }
+
+  /**
+   * Returns true iff this {@link CFAbstractStore} contains a superset of the map entries of the
+   * argument {@link CFAbstractStore}. Note that we test the entry keys and values by Java equality,
+   * not by any subtype relationship. This method is used primarily to simplify the equals
+   * predicate.
+   */
+  protected boolean supersetOf(CFAbstractStore<V, S> other) {
+    for (Map.Entry<LocalVariable, V> e : other.localVariableValues.entrySet()) {
+      LocalVariable key = e.getKey();
+      V value = localVariableValues.get(key);
+      if (value == null || !value.equals(e.getValue())) {
+        return false;
+      }
+    }
+    for (Map.Entry<FieldAccess, V> e : other.fieldValues.entrySet()) {
+      FieldAccess key = e.getKey();
+      V value = fieldValues.get(key);
+      if (value == null || !value.equals(e.getValue())) {
+        return false;
+      }
+    }
+    for (Map.Entry<ArrayAccess, V> e : other.arrayValues.entrySet()) {
+      ArrayAccess key = e.getKey();
+      V value = arrayValues.get(key);
+      if (value == null || !value.equals(e.getValue())) {
+        return false;
+      }
+    }
+    for (Map.Entry<MethodCall, V> e : other.methodValues.entrySet()) {
+      MethodCall key = e.getKey();
+      V value = methodValues.get(key);
+      if (value == null || !value.equals(e.getValue())) {
+        return false;
+      }
+    }
+    for (Map.Entry<ClassName, V> e : other.classValues.entrySet()) {
+      ClassName key = e.getKey();
+      V value = classValues.get(key);
+      if (value == null || !value.equals(e.getValue())) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public boolean equals(@Nullable Object o) {
+    if (o instanceof CFAbstractStore) {
+      @SuppressWarnings("unchecked")
+      CFAbstractStore<V, S> other = (CFAbstractStore<V, S>) o;
+      return this.supersetOf(other) && other.supersetOf(this);
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    // What is a good hash code to use?
+    return 22;
+  }
+
+  @SideEffectFree
+  @Override
+  public String toString() {
+    return visualize(new StringCFGVisualizer<>());
+  }
+
+  @Override
+  public String visualize(CFGVisualizer<?, S, ?> viz) {
+    /* This cast is guaranteed to be safe, as long as the CFGVisualizer is created by
+     * CFGVisualizer<Value, Store, TransferFunction> createCFGVisualizer() of GenericAnnotatedTypeFactory */
+    @SuppressWarnings("unchecked")
+    CFGVisualizer<V, S, ?> castedViz = (CFGVisualizer<V, S, ?>) viz;
+    String internal = internalVisualize(castedViz);
+    if (internal.trim().isEmpty()) {
+      return this.getClassAndUid() + "()";
+    } else {
+      return this.getClassAndUid() + "(" + viz.getSeparator() + internal + ")";
+    }
+  }
+
+  /**
+   * Adds a representation of the internal information of this Store to visualizer {@code viz}.
+   *
+   * @param viz the visualizer
+   * @return a representation of the internal information of this {@link Store}
+   */
+  protected String internalVisualize(CFGVisualizer<V, S, ?> viz) {
+    StringJoiner res = new StringJoiner(viz.getSeparator());
+    for (Map.Entry<LocalVariable, V> entry : localVariableValues.entrySet()) {
+      res.add(viz.visualizeStoreLocalVar(entry.getKey(), entry.getValue()));
+    }
+    if (thisValue != null) {
+      res.add(viz.visualizeStoreThisVal(thisValue));
+    }
+    for (FieldAccess fa : ToStringComparator.sorted(fieldValues.keySet())) {
+      res.add(viz.visualizeStoreFieldVal(fa, fieldValues.get(fa)));
+    }
+    for (ArrayAccess fa : ToStringComparator.sorted(arrayValues.keySet())) {
+      res.add(viz.visualizeStoreArrayVal(fa, arrayValues.get(fa)));
+    }
+    for (MethodCall fa : ToStringComparator.sorted(methodValues.keySet())) {
+      res.add(viz.visualizeStoreMethodVals(fa, methodValues.get(fa)));
+    }
+    for (ClassName fa : ToStringComparator.sorted(classValues.keySet())) {
+      res.add(viz.visualizeStoreClassVals(fa, classValues.get(fa)));
+    }
+    return res.toString();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java
new file mode 100644
index 0000000..6dda2b2
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java
@@ -0,0 +1,1334 @@
+package org.checkerframework.framework.flow;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+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.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
+import org.checkerframework.dataflow.analysis.ForwardTransferFunction;
+import org.checkerframework.dataflow.analysis.RegularTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.Kind;
+import org.checkerframework.dataflow.cfg.node.AbstractNodeVisitor;
+import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.CaseNode;
+import org.checkerframework.dataflow.cfg.node.ClassNameNode;
+import org.checkerframework.dataflow.cfg.node.ConditionalNotNode;
+import org.checkerframework.dataflow.cfg.node.EqualToNode;
+import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
+import org.checkerframework.dataflow.cfg.node.InstanceOfNode;
+import org.checkerframework.dataflow.cfg.node.LambdaResultExpressionNode;
+import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.NarrowingConversionNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.NotEqualNode;
+import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+import org.checkerframework.dataflow.cfg.node.StringConcatenateAssignmentNode;
+import org.checkerframework.dataflow.cfg.node.StringConversionNode;
+import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
+import org.checkerframework.dataflow.cfg.node.ThisNode;
+import org.checkerframework.dataflow.cfg.node.VariableDeclarationNode;
+import org.checkerframework.dataflow.cfg.node.WideningConversionNode;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.ThisReference;
+import org.checkerframework.dataflow.util.NodeUtils;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.Contract;
+import org.checkerframework.framework.util.Contract.ConditionalPostcondition;
+import org.checkerframework.framework.util.Contract.Postcondition;
+import org.checkerframework.framework.util.Contract.Precondition;
+import org.checkerframework.framework.util.ContractsFromMethod;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.framework.util.StringToJavaExpression;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * The default analysis transfer function for the Checker Framework propagates information through
+ * assignments and uses the {@link AnnotatedTypeFactory} to provide checker-specific logic how to
+ * combine types (e.g., what is the type of a string concatenation, given the types of the two
+ * operands) and as an abstraction function (e.g., determine the annotations on literals).
+ *
+ * <p>Design note: CFAbstractTransfer and its subclasses are supposed to act as transfer functions.
+ * But, since the AnnotatedTypeFactory already existed and performed checker-independent type
+ * propagation, CFAbstractTransfer delegates work to it instead of duplicating some logic in
+ * CFAbstractTransfer. The checker-specific subclasses of CFAbstractTransfer do implement transfer
+ * function logic themselves.
+ */
+public abstract class CFAbstractTransfer<
+        V extends CFAbstractValue<V>,
+        S extends CFAbstractStore<V, S>,
+        T extends CFAbstractTransfer<V, S, T>>
+    extends AbstractNodeVisitor<TransferResult<V, S>, TransferInput<V, S>>
+    implements ForwardTransferFunction<V, S> {
+
+  /** The analysis used by this transfer function. */
+  protected final CFAbstractAnalysis<V, S, T> analysis;
+
+  /**
+   * Should the analysis use sequential Java semantics (i.e., assume that only one thread is running
+   * at all times)?
+   */
+  protected final boolean sequentialSemantics;
+
+  /** Indicates that the whole-program inference is on. */
+  private final boolean infer;
+
+  /**
+   * Create a CFAbstractTransfer.
+   *
+   * @param analysis the analysis used by this transfer function
+   */
+  protected CFAbstractTransfer(CFAbstractAnalysis<V, S, T> analysis) {
+    this(analysis, false);
+  }
+
+  /**
+   * Constructor that allows forcing concurrent semantics to be on for this instance of
+   * CFAbstractTransfer.
+   *
+   * @param analysis the analysis used by this transfer function
+   * @param forceConcurrentSemantics whether concurrent semantics should be forced to be on. If
+   *     false, concurrent semantics are turned off by default, but the user can still turn them on
+   *     via {@code -AconcurrentSemantics}. If true, the user cannot turn off concurrent semantics.
+   */
+  protected CFAbstractTransfer(
+      CFAbstractAnalysis<V, S, T> analysis, boolean forceConcurrentSemantics) {
+    this.analysis = analysis;
+    this.sequentialSemantics =
+        !(forceConcurrentSemantics || analysis.checker.hasOption("concurrentSemantics"));
+    this.infer = analysis.checker.hasOption("infer");
+  }
+
+  /**
+   * Returns true if the transfer function uses sequential semantics, false if it uses concurrent
+   * semantics. Useful when creating an empty store, since a store makes different decisions
+   * depending on whether sequential or concurrent semantics are used.
+   *
+   * @return true if the transfer function uses sequential semantics, false if it uses concurrent
+   *     semantics
+   */
+  public boolean usesSequentialSemantics() {
+    return sequentialSemantics;
+  }
+
+  /**
+   * A hook for subclasses to modify the result of the transfer function. This method is called
+   * before returning the abstract value {@code value} as the result of the transfer function.
+   *
+   * <p>If a subclass overrides this method, the subclass should also override {@link
+   * #finishValue(CFAbstractValue,CFAbstractStore,CFAbstractStore)}.
+   *
+   * @param value a value to possibly modify
+   * @param store the store
+   * @return the possibly-modified value
+   */
+  protected @Nullable V finishValue(@Nullable V value, S store) {
+    return value;
+  }
+
+  /**
+   * A hook for subclasses to modify the result of the transfer function. This method is called
+   * before returning the abstract value {@code value} as the result of the transfer function.
+   *
+   * <p>If a subclass overrides this method, the subclass should also override {@link
+   * #finishValue(CFAbstractValue,CFAbstractStore)}.
+   *
+   * @param value the value to finish
+   * @param thenStore the "then" store
+   * @param elseStore the "else" store
+   * @return the possibly-modified value
+   */
+  protected @Nullable V finishValue(@Nullable V value, S thenStore, S elseStore) {
+    return value;
+  }
+
+  /**
+   * Returns the abstract value of a non-leaf tree {@code tree}, as computed by the {@link
+   * AnnotatedTypeFactory}.
+   *
+   * @return the abstract value of a non-leaf tree {@code tree}, as computed by the {@link
+   *     AnnotatedTypeFactory}
+   */
+  protected V getValueFromFactory(Tree tree, Node node) {
+    GenericAnnotatedTypeFactory<V, S, T, ? extends CFAbstractAnalysis<V, S, T>> factory =
+        analysis.atypeFactory;
+    Tree preTree = analysis.getCurrentTree();
+    Pair<Tree, AnnotatedTypeMirror> preContext = factory.getVisitorState().getAssignmentContext();
+    analysis.setCurrentTree(tree);
+    // is there an assignment context node available?
+    if (node != null && node.getAssignmentContext() != null) {
+      // Get the declared type of the assignment context by looking up the assignment context tree's
+      // type in the factory while flow is disabled.
+      Tree contextTree = node.getAssignmentContext().getContextTree();
+      AnnotatedTypeMirror assignmentContext = null;
+      if (contextTree != null) {
+        assignmentContext = factory.getAnnotatedTypeLhs(contextTree);
+      } else {
+        Element assignmentContextElement = node.getAssignmentContext().getElementForType();
+        if (assignmentContextElement != null) {
+          // if contextTree is null, use the element to get the type
+          assignmentContext = factory.getAnnotatedType(assignmentContextElement);
+        }
+      }
+
+      if (assignmentContext != null) {
+        if (assignmentContext instanceof AnnotatedExecutableType) {
+          // For a MethodReturnContext, we get the full type of the
+          // method, but we only want the return type.
+          assignmentContext = ((AnnotatedExecutableType) assignmentContext).getReturnType();
+        }
+        factory
+            .getVisitorState()
+            .setAssignmentContext(
+                Pair.of(node.getAssignmentContext().getContextTree(), assignmentContext));
+      }
+    }
+    AnnotatedTypeMirror at;
+    if (node instanceof MethodInvocationNode
+        && ((MethodInvocationNode) node).getIterableExpression() != null) {
+      ExpressionTree iter = ((MethodInvocationNode) node).getIterableExpression();
+      at = factory.getIterableElementType(iter);
+    } else if (node instanceof ArrayAccessNode
+        && ((ArrayAccessNode) node).getArrayExpression() != null) {
+      ExpressionTree array = ((ArrayAccessNode) node).getArrayExpression();
+      at = factory.getIterableElementType(array);
+    } else {
+      at = factory.getAnnotatedType(tree);
+    }
+    analysis.setCurrentTree(preTree);
+    factory.getVisitorState().setAssignmentContext(preContext);
+    return analysis.createAbstractValue(at);
+  }
+
+  /**
+   * Returns an abstract value with the given {@code type} and the annotations from {@code
+   * annotatedValue}.
+   *
+   * @param type the type to return
+   * @param annotatedValue the annotations to return
+   * @return an abstract value with the given {@code type} and the annotations from {@code
+   *     annotatedValue}
+   * @deprecated use {@link #getWidenedValue} or {@link #getNarrowedValue}
+   */
+  @Deprecated // 2020-10-02
+  protected V getValueWithSameAnnotations(TypeMirror type, V annotatedValue) {
+    if (annotatedValue == null) {
+      return null;
+    }
+    return analysis.createAbstractValue(annotatedValue.getAnnotations(), type);
+  }
+
+  /** The fixed initial store. */
+  private S fixedInitialStore = null;
+
+  /** Set a fixed initial Store. */
+  public void setFixedInitialStore(S s) {
+    fixedInitialStore = s;
+  }
+
+  /** The initial store maps method formal parameters to their currently most refined type. */
+  @Override
+  public S initialStore(UnderlyingAST underlyingAST, @Nullable List<LocalVariableNode> parameters) {
+    if (underlyingAST.getKind() != Kind.LAMBDA && underlyingAST.getKind() != Kind.METHOD) {
+      if (fixedInitialStore != null) {
+        return fixedInitialStore;
+      } else {
+        return analysis.createEmptyStore(sequentialSemantics);
+      }
+    }
+
+    S info;
+
+    if (underlyingAST.getKind() == Kind.METHOD) {
+
+      if (fixedInitialStore != null) {
+        // copy knowledge
+        info = analysis.createCopiedStore(fixedInitialStore);
+      } else {
+        info = analysis.createEmptyStore(sequentialSemantics);
+      }
+
+      AnnotatedTypeFactory factory = analysis.getTypeFactory();
+      for (LocalVariableNode p : parameters) {
+        AnnotatedTypeMirror anno = factory.getAnnotatedType(p.getElement());
+        info.initializeMethodParameter(p, analysis.createAbstractValue(anno));
+      }
+
+      // add properties known through precondition
+      CFGMethod method = (CFGMethod) underlyingAST;
+      MethodTree methodDeclTree = method.getMethod();
+      ExecutableElement methodElem = TreeUtils.elementFromDeclaration(methodDeclTree);
+      addInformationFromPreconditions(info, factory, method, methodDeclTree, methodElem);
+
+      final ClassTree classTree = method.getClassTree();
+      addFieldValues(info, factory, classTree, methodDeclTree);
+
+      addFinalLocalValues(info, methodElem);
+
+      if (shouldPerformWholeProgramInference(methodDeclTree, methodElem)) {
+        Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
+            AnnotatedTypes.overriddenMethods(
+                analysis.atypeFactory.getElementUtils(), analysis.atypeFactory, methodElem);
+        for (Map.Entry<AnnotatedDeclaredType, ExecutableElement> pair :
+            overriddenMethods.entrySet()) {
+          AnnotatedExecutableType overriddenMethod =
+              AnnotatedTypes.asMemberOf(
+                  analysis.atypeFactory.getProcessingEnv().getTypeUtils(),
+                  analysis.atypeFactory,
+                  pair.getKey(),
+                  pair.getValue());
+
+          // Infers parameter and receiver types of the method based
+          // on the overridden method.
+          analysis
+              .atypeFactory
+              .getWholeProgramInference()
+              .updateFromOverride(methodDeclTree, methodElem, overriddenMethod);
+        }
+      }
+
+    } else if (underlyingAST.getKind() == Kind.LAMBDA) {
+      // Create a copy and keep only the field values (nothing else applies).
+      info = analysis.createCopiedStore(fixedInitialStore);
+      // Allow that local variables are retained; they are effectively final,
+      // otherwise Java wouldn't allow access from within the lambda.
+      // TODO: what about the other information? Can code further down be simplified?
+      // info.localVariableValues.clear();
+      info.classValues.clear();
+      info.arrayValues.clear();
+      info.methodValues.clear();
+
+      AnnotatedTypeFactory factory = analysis.getTypeFactory();
+      for (LocalVariableNode p : parameters) {
+        AnnotatedTypeMirror anno = factory.getAnnotatedType(p.getElement());
+        info.initializeMethodParameter(p, analysis.createAbstractValue(anno));
+      }
+
+      CFGLambda lambda = (CFGLambda) underlyingAST;
+      @SuppressWarnings("interning:assignment") // used in == tests
+      @InternedDistinct Tree enclosingTree =
+          TreePathUtil.enclosingOfKind(
+              factory.getPath(lambda.getLambdaTree()),
+              new HashSet<>(
+                  Arrays.asList(
+                      Tree.Kind.METHOD,
+                      // Tree.Kind for which TreeUtils.isClassTree is true
+                      Tree.Kind.CLASS,
+                      Tree.Kind.INTERFACE,
+                      Tree.Kind.ANNOTATION_TYPE,
+                      Tree.Kind.ENUM)));
+
+      Element enclosingElement = null;
+      if (enclosingTree.getKind() == Tree.Kind.METHOD) {
+        // If it is in an initializer, we need to use locals from the initializer.
+        enclosingElement = TreeUtils.elementFromTree(enclosingTree);
+
+      } else if (TreeUtils.isClassTree(enclosingTree)) {
+
+        // Try to find an enclosing initializer block.
+        // Would love to know if there was a better way.
+        // Find any enclosing element of the lambda (using trees).
+        // Then go up the elements to find an initializer element (which can't be found with the
+        // tree).
+        TreePath loopTree = factory.getPath(lambda.getLambdaTree()).getParentPath();
+        Element anEnclosingElement = null;
+        while (loopTree.getLeaf() != enclosingTree) {
+          Element sym = TreeUtils.elementFromTree(loopTree.getLeaf());
+          if (sym != null) {
+            anEnclosingElement = sym;
+            break;
+          }
+          loopTree = loopTree.getParentPath();
+        }
+        while (anEnclosingElement != null
+            && !anEnclosingElement.equals(TreeUtils.elementFromTree(enclosingTree))) {
+          if (anEnclosingElement.getKind() == ElementKind.INSTANCE_INIT
+              || anEnclosingElement.getKind() == ElementKind.STATIC_INIT) {
+            enclosingElement = anEnclosingElement;
+            break;
+          }
+          anEnclosingElement = anEnclosingElement.getEnclosingElement();
+        }
+      }
+      if (enclosingElement != null) {
+        addFinalLocalValues(info, enclosingElement);
+      }
+
+      // We want the initialization stuff, but need to throw out any refinements.
+      Map<FieldAccess, V> fieldValuesClone = new HashMap<>(info.fieldValues);
+      for (Map.Entry<FieldAccess, V> fieldValue : fieldValuesClone.entrySet()) {
+        AnnotatedTypeMirror declaredType = factory.getAnnotatedType(fieldValue.getKey().getField());
+        V lubbedValue =
+            analysis.createAbstractValue(declaredType).leastUpperBound(fieldValue.getValue());
+        info.fieldValues.put(fieldValue.getKey(), lubbedValue);
+      }
+    } else {
+      assert false : "Unexpected tree: " + underlyingAST;
+      info = null;
+    }
+
+    return info;
+  }
+
+  private void addFieldValues(
+      S info, AnnotatedTypeFactory factory, ClassTree classTree, MethodTree methodTree) {
+
+    // Add knowledge about final fields, or values of non-final fields
+    // if we are inside a constructor (information about initializers)
+    TypeMirror classType = TreeUtils.typeOf(classTree);
+    List<Pair<VariableElement, V>> fieldValues = analysis.getFieldValues();
+    for (Pair<VariableElement, V> p : fieldValues) {
+      VariableElement element = p.first;
+      V value = p.second;
+      if (ElementUtils.isFinal(element) || TreeUtils.isConstructor(methodTree)) {
+        JavaExpression receiver;
+        if (ElementUtils.isStatic(element)) {
+          receiver = new ClassName(classType);
+        } else {
+          receiver = new ThisReference(classType);
+        }
+        TypeMirror fieldType = ElementUtils.getType(element);
+        JavaExpression field = new FieldAccess(receiver, fieldType, element);
+        info.insertValue(field, value);
+      }
+    }
+
+    // add properties about fields (static information from type)
+    boolean isNotFullyInitializedReceiver = isNotFullyInitializedReceiver(methodTree);
+    if (isNotFullyInitializedReceiver && !TreeUtils.isConstructor(methodTree)) {
+      // cannot add information about fields if the receiver isn't initialized
+      // and the method isn't a constructor
+      return;
+    }
+    for (Tree member : classTree.getMembers()) {
+      if (member instanceof VariableTree) {
+        VariableTree vt = (VariableTree) member;
+        final VariableElement element = TreeUtils.elementFromDeclaration(vt);
+        AnnotatedTypeMirror type =
+            ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) factory).getAnnotatedTypeLhs(vt);
+        TypeMirror fieldType = ElementUtils.getType(element);
+        JavaExpression receiver;
+        if (ElementUtils.isStatic(element)) {
+          receiver = new ClassName(classType);
+        } else {
+          receiver = new ThisReference(classType);
+        }
+        V value = analysis.createAbstractValue(type);
+        if (value == null) {
+          continue;
+        }
+        if (TreeUtils.isConstructor(methodTree)) {
+          // If we are in a constructor, then we can still use the static type, but only if there is
+          // also an initializer that already does some initialization.
+          boolean found = false;
+          for (Pair<VariableElement, V> fieldValue : fieldValues) {
+            if (fieldValue.first.equals(element)) {
+              value = value.leastUpperBound(fieldValue.second);
+              found = true;
+              break;
+            }
+          }
+          if (!found) {
+            // no initializer found, cannot use static type
+            continue;
+          }
+        }
+        JavaExpression field = new FieldAccess(receiver, fieldType, element);
+        info.insertValue(field, value);
+      }
+    }
+  }
+
+  private void addFinalLocalValues(S info, Element enclosingElement) {
+    // add information about effectively final variables (from outer scopes)
+    for (Map.Entry<Element, V> e : analysis.atypeFactory.getFinalLocalValues().entrySet()) {
+
+      Element elem = e.getKey();
+
+      // TODO: There is a design flaw where the values of final local values leaks
+      // into other methods of the same class. For example, in
+      // class a { void b() {...} void c() {...} }
+      // final local values from b() would be visible in the store for c(),
+      // even though they should only be visible in b() and in classes
+      // defined inside the method body of b().
+      // This is partly because GenericAnnotatedTypeFactory.performFlowAnalysis does not call itself
+      // recursively to analyze inner classes, but instead pops classes off of a queue, and the
+      // information about known final local values is stored by GenericAnnotatedTypeFactory.analyze
+      // in GenericAnnotatedTypeFactory.flowResult, which is visible to all classes in the queue
+      // regardless of their level of recursion.
+
+      // We work around this here by ensuring that we only add a final local value to a method's
+      // store if that method is enclosed by the method where the local variables were declared.
+
+      // Find the enclosing method of the element
+      Element enclosingMethodOfVariableDeclaration = elem.getEnclosingElement();
+
+      if (enclosingMethodOfVariableDeclaration != null) {
+
+        // Now find all the enclosing methods of the code we are analyzing. If any one of
+        // them matches the above, then the final local variable value applies.
+        Element enclosingMethodOfCurrentMethod = enclosingElement;
+
+        while (enclosingMethodOfCurrentMethod != null) {
+          if (enclosingMethodOfVariableDeclaration.equals(enclosingMethodOfCurrentMethod)) {
+            LocalVariable l = new LocalVariable(elem);
+            info.insertValue(l, e.getValue());
+            break;
+          }
+
+          enclosingMethodOfCurrentMethod = enclosingMethodOfCurrentMethod.getEnclosingElement();
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns true if the receiver of a method or constructor might not yet be fully initialized.
+   *
+   * @param methodDeclTree the declaration of the method or constructor
+   * @return true if the receiver of a method or constructormight not yet be fully initialized
+   */
+  protected boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) {
+    return TreeUtils.isConstructor(methodDeclTree);
+  }
+
+  /**
+   * Add the information from all the preconditions of a method to the initial store in the method
+   * body.
+   *
+   * @param initialStore the initial store for the method body
+   * @param factory the type factory
+   * @param methodAst the AST for a method declaration
+   * @param methodDeclTree the declaration of the method; is a field of {@code methodAst}
+   * @param methodElement the element for the method
+   */
+  protected void addInformationFromPreconditions(
+      S initialStore,
+      AnnotatedTypeFactory factory,
+      CFGMethod methodAst,
+      MethodTree methodDeclTree,
+      ExecutableElement methodElement) {
+    ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod();
+    Set<Precondition> preconditions = contractsUtils.getPreconditions(methodElement);
+    StringToJavaExpression stringToJavaExpr =
+        stringExpr ->
+            StringToJavaExpression.atMethodBody(stringExpr, methodDeclTree, analysis.checker);
+    for (Precondition p : preconditions) {
+      String stringExpr = p.expressionString;
+      AnnotationMirror annotation =
+          p.viewpointAdaptDependentTypeAnnotation(
+              analysis.atypeFactory, stringToJavaExpr, /*errorTree=*/ null);
+      JavaExpression exprJe;
+      try {
+        // TODO: currently, these expressions are parsed at the declaration (i.e. here) and for
+        // every use. this could be optimized to store the result the first time.
+        // (same for other annotations)
+        exprJe = StringToJavaExpression.atMethodBody(stringExpr, methodDeclTree, analysis.checker);
+      } catch (JavaExpressionParseException e) {
+        // Errors are reported by BaseTypeVisitor.checkContractsAtMethodDeclaration().
+        continue;
+      }
+      initialStore.insertValuePermitNondeterministic(exprJe, annotation);
+    }
+  }
+
+  /**
+   * The default visitor returns the input information unchanged, or in the case of conditional
+   * input information, merged.
+   */
+  @Override
+  public TransferResult<V, S> visitNode(Node n, TransferInput<V, S> in) {
+    V value = null;
+
+    // TODO: handle implicit/explicit this and go to correct factory method
+    Tree tree = n.getTree();
+    if (tree != null) {
+      if (TreeUtils.canHaveTypeAnnotation(tree)) {
+        value = getValueFromFactory(tree, n);
+      }
+    }
+
+    return createTransferResult(value, in);
+  }
+
+  /**
+   * Creates a TransferResult.
+   *
+   * <p>This default implementation returns the input information unchanged, or in the case of
+   * conditional input information, merged.
+   *
+   * @param value the value; possibly null
+   * @param in the transfer input
+   * @return the input information, as a TransferResult
+   */
+  protected TransferResult<V, S> createTransferResult(@Nullable V value, TransferInput<V, S> in) {
+    if (in.containsTwoStores()) {
+      S thenStore = in.getThenStore();
+      S elseStore = in.getElseStore();
+      return new ConditionalTransferResult<>(
+          finishValue(value, thenStore, elseStore), thenStore, elseStore);
+    } else {
+      S info = in.getRegularStore();
+      return new RegularTransferResult<>(finishValue(value, info), info);
+    }
+  }
+
+  /**
+   * Creates a TransferResult just like the given one, but with the given value.
+   *
+   * <p>This default implementation returns the input information unchanged, or in the case of
+   * conditional input information, merged.
+   *
+   * @param value the value; possibly null
+   * @param in the TransferResult to copy
+   * @return the input informatio
+   */
+  protected TransferResult<V, S> recreateTransferResult(
+      @Nullable V value, TransferResult<V, S> in) {
+    if (in.containsTwoStores()) {
+      S thenStore = in.getThenStore();
+      S elseStore = in.getElseStore();
+      return new ConditionalTransferResult<>(
+          finishValue(value, thenStore, elseStore), thenStore, elseStore);
+    } else {
+      S info = in.getRegularStore();
+      return new RegularTransferResult<>(finishValue(value, info), info);
+    }
+  }
+
+  @Override
+  public TransferResult<V, S> visitClassName(ClassNameNode n, TransferInput<V, S> in) {
+    // The tree underlying a class name is a type tree.
+    V value = null;
+
+    Tree tree = n.getTree();
+    if (tree != null) {
+      if (TreeUtils.canHaveTypeAnnotation(tree)) {
+        GenericAnnotatedTypeFactory<V, S, T, ? extends CFAbstractAnalysis<V, S, T>> factory =
+            analysis.atypeFactory;
+        analysis.setCurrentTree(tree);
+        AnnotatedTypeMirror at = factory.getAnnotatedTypeFromTypeTree(tree);
+        analysis.setCurrentTree(null);
+        value = analysis.createAbstractValue(at);
+      }
+    }
+
+    return createTransferResult(value, in);
+  }
+
+  @Override
+  public TransferResult<V, S> visitFieldAccess(FieldAccessNode n, TransferInput<V, S> p) {
+    S store = p.getRegularStore();
+    V storeValue = store.getValue(n);
+    // look up value in factory, and take the more specific one
+    // TODO: handle cases, where this is not allowed (e.g. constructors in non-null type systems)
+    V factoryValue = getValueFromFactory(n.getTree(), n);
+    V value = moreSpecificValue(factoryValue, storeValue);
+    return new RegularTransferResult<>(finishValue(value, store), store);
+  }
+
+  @Override
+  public TransferResult<V, S> visitArrayAccess(ArrayAccessNode n, TransferInput<V, S> p) {
+    S store = p.getRegularStore();
+    V storeValue = store.getValue(n);
+    // look up value in factory, and take the more specific one
+    V factoryValue = getValueFromFactory(n.getTree(), n);
+    V value = moreSpecificValue(factoryValue, storeValue);
+    return new RegularTransferResult<>(finishValue(value, store), store);
+  }
+
+  /** Use the most specific type information available according to the store. */
+  @Override
+  public TransferResult<V, S> visitLocalVariable(LocalVariableNode n, TransferInput<V, S> in) {
+    S store = in.getRegularStore();
+    V valueFromStore = store.getValue(n);
+    V valueFromFactory = getValueFromFactory(n.getTree(), n);
+    V value = moreSpecificValue(valueFromFactory, valueFromStore);
+    return new RegularTransferResult<>(finishValue(value, store), store);
+  }
+
+  @Override
+  public TransferResult<V, S> visitThis(ThisNode n, TransferInput<V, S> in) {
+    S store = in.getRegularStore();
+    V valueFromStore = store.getValue(n);
+
+    V valueFromFactory = null;
+    V value = null;
+    Tree tree = n.getTree();
+    if (tree != null && TreeUtils.canHaveTypeAnnotation(tree)) {
+      valueFromFactory = getValueFromFactory(tree, n);
+    }
+
+    if (valueFromFactory == null) {
+      value = valueFromStore;
+    } else {
+      value = moreSpecificValue(valueFromFactory, valueFromStore);
+    }
+
+    return new RegularTransferResult<>(finishValue(value, store), store);
+  }
+
+  @Override
+  public TransferResult<V, S> visitTernaryExpression(
+      TernaryExpressionNode n, TransferInput<V, S> p) {
+    TransferResult<V, S> result = super.visitTernaryExpression(n, p);
+    S thenStore = result.getThenStore();
+    S elseStore = result.getElseStore();
+    V thenValue = p.getValueOfSubNode(n.getThenOperand());
+    V elseValue = p.getValueOfSubNode(n.getElseOperand());
+    V resultValue = null;
+    if (thenValue != null && elseValue != null) {
+      // The resulting abstract value is the merge of the 'then' and 'else' branch.
+      resultValue = thenValue.leastUpperBound(elseValue);
+    }
+    V finishedValue = finishValue(resultValue, thenStore, elseStore);
+    return new ConditionalTransferResult<>(finishedValue, thenStore, elseStore);
+  }
+
+  /** Reverse the role of the 'thenStore' and 'elseStore'. */
+  @Override
+  public TransferResult<V, S> visitConditionalNot(ConditionalNotNode n, TransferInput<V, S> p) {
+    TransferResult<V, S> result = super.visitConditionalNot(n, p);
+    S thenStore = result.getThenStore();
+    S elseStore = result.getElseStore();
+    return new ConditionalTransferResult<>(result.getResultValue(), elseStore, thenStore);
+  }
+
+  @Override
+  public TransferResult<V, S> visitEqualTo(EqualToNode n, TransferInput<V, S> p) {
+    TransferResult<V, S> res = super.visitEqualTo(n, p);
+
+    Node leftN = n.getLeftOperand();
+    Node rightN = n.getRightOperand();
+    V leftV = p.getValueOfSubNode(leftN);
+    V rightV = p.getValueOfSubNode(rightN);
+
+    if (res.containsTwoStores()
+        && (NodeUtils.isConstantBoolean(leftN, false)
+            || NodeUtils.isConstantBoolean(rightN, false))) {
+      S thenStore = res.getElseStore();
+      S elseStore = res.getThenStore();
+      res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore);
+    }
+
+    // if annotations differ, use the one that is more precise for both
+    // sides (and add it to the store if possible)
+    res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false);
+    res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false);
+    return res;
+  }
+
+  @Override
+  public TransferResult<V, S> visitNotEqual(NotEqualNode n, TransferInput<V, S> p) {
+    TransferResult<V, S> res = super.visitNotEqual(n, p);
+
+    Node leftN = n.getLeftOperand();
+    Node rightN = n.getRightOperand();
+    V leftV = p.getValueOfSubNode(leftN);
+    V rightV = p.getValueOfSubNode(rightN);
+
+    if (res.containsTwoStores()
+        && (NodeUtils.isConstantBoolean(leftN, true)
+            || NodeUtils.isConstantBoolean(rightN, true))) {
+      S thenStore = res.getElseStore();
+      S elseStore = res.getThenStore();
+      res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore);
+    }
+
+    // if annotations differ, use the one that is more precise for both
+    // sides (and add it to the store if possible)
+    res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true);
+    res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true);
+
+    return res;
+  }
+
+  /**
+   * Refine the annotation of {@code secondNode} if the annotation {@code secondValue} is less
+   * precise than {@code firstValue}. This is possible, if {@code secondNode} is an expression that
+   * is tracked by the store (e.g., a local variable or a field). Clients usually call this twice
+   * with {@code firstNode} and {@code secondNode} reversed, to refine each of them.
+   *
+   * <p>Note that when overriding this method, when a new type is inserted into the store, {@link
+   * #splitAssignments} should be called, and the new type should be inserted into the store for
+   * each of the resulting nodes.
+   *
+   * @param firstNode the node that might be more precise
+   * @param secondNode the node whose type to possibly refine
+   * @param firstValue the abstract value that might be more precise
+   * @param secondValue the abstract value that might be less precise
+   * @param res the previous result
+   * @param notEqualTo if true, indicates that the logic is flipped (i.e., the information is added
+   *     to the {@code elseStore} instead of the {@code thenStore}) for a not-equal comparison.
+   * @return the conditional transfer result (if information has been added), or {@code null}
+   */
+  protected TransferResult<V, S> strengthenAnnotationOfEqualTo(
+      TransferResult<V, S> res,
+      Node firstNode,
+      Node secondNode,
+      V firstValue,
+      V secondValue,
+      boolean notEqualTo) {
+    if (firstValue != null) {
+      // Only need to insert if the second value is actually different.
+      if (!firstValue.equals(secondValue)) {
+        List<Node> secondParts = splitAssignments(secondNode);
+        for (Node secondPart : secondParts) {
+          JavaExpression secondInternal = JavaExpression.fromNode(secondPart);
+          if (!secondInternal.isDeterministic(analysis.atypeFactory)) {
+            continue;
+          }
+          if (CFAbstractStore.canInsertJavaExpression(secondInternal)) {
+            S thenStore = res.getThenStore();
+            S elseStore = res.getElseStore();
+            if (notEqualTo) {
+              elseStore.insertValue(secondInternal, firstValue);
+            } else {
+              thenStore.insertValue(secondInternal, firstValue);
+            }
+            // To handle `(a = b = c) == x`, repeat for all insertable receivers of
+            // splitted assignments instead of returning.
+            res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore);
+          }
+        }
+      }
+    }
+    return res;
+  }
+
+  /**
+   * Takes a node, and either returns the node itself again (as a singleton list), or if the node is
+   * an assignment node, returns the lhs and rhs (where splitAssignments is applied recursively to
+   * the rhs -- that is, the rhs may not appear in the result, but rather its lhs and rhs may).
+   *
+   * @param node possibly an assignment node
+   * @return a list containing all the right- and left-hand sides in the given assignment node; it
+   *     contains just the node itself if it is not an assignment)
+   */
+  protected List<Node> splitAssignments(Node node) {
+    if (node instanceof AssignmentNode) {
+      List<Node> result = new ArrayList<>(2);
+      AssignmentNode a = (AssignmentNode) node;
+      result.add(a.getTarget());
+      result.addAll(splitAssignments(a.getExpression()));
+      return result;
+    } else {
+      return Collections.singletonList(node);
+    }
+  }
+
+  @Override
+  public TransferResult<V, S> visitAssignment(AssignmentNode n, TransferInput<V, S> in) {
+    Node lhs = n.getTarget();
+    Node rhs = n.getExpression();
+
+    S info = in.getRegularStore();
+    V rhsValue = in.getValueOfSubNode(rhs);
+
+    if (shouldPerformWholeProgramInference(n.getTree(), lhs.getTree())) {
+      // Fields defined in interfaces are LocalVariableNodes with ElementKind of FIELD.
+      if (lhs instanceof FieldAccessNode
+          || (lhs instanceof LocalVariableNode
+              && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.FIELD)) {
+        // Updates inferred field type
+        analysis
+            .atypeFactory
+            .getWholeProgramInference()
+            .updateFromFieldAssignment(lhs, rhs, analysis.getContainingClass(n.getTree()));
+      } else if (lhs instanceof LocalVariableNode
+          && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.PARAMETER) {
+        // lhs is a formal parameter of some method
+        VariableElement param = (VariableElement) ((LocalVariableNode) lhs).getElement();
+        analysis
+            .atypeFactory
+            .getWholeProgramInference()
+            .updateFromFormalParameterAssignment((LocalVariableNode) lhs, rhs, param);
+      }
+    }
+
+    processCommonAssignment(in, lhs, rhs, info, rhsValue);
+
+    return new RegularTransferResult<>(finishValue(rhsValue, info), info);
+  }
+
+  @Override
+  public TransferResult<V, S> visitReturn(ReturnNode n, TransferInput<V, S> p) {
+    TransferResult<V, S> result = super.visitReturn(n, p);
+
+    if (shouldPerformWholeProgramInference(n.getTree())) {
+      // Retrieves class containing the method
+      ClassTree classTree = analysis.getContainingClass(n.getTree());
+      // classTree is null e.g. if this is a return statement in a lambda.
+      if (classTree == null) {
+        return result;
+      }
+      ClassSymbol classSymbol = (ClassSymbol) TreeUtils.elementFromTree(classTree);
+
+      ExecutableElement methodElem =
+          TreeUtils.elementFromDeclaration(analysis.getContainingMethod(n.getTree()));
+
+      Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
+          AnnotatedTypes.overriddenMethods(
+              analysis.atypeFactory.getElementUtils(), analysis.atypeFactory, methodElem);
+
+      // Updates the inferred return type of the method
+      analysis
+          .atypeFactory
+          .getWholeProgramInference()
+          .updateFromReturn(
+              n, classSymbol, analysis.getContainingMethod(n.getTree()), overriddenMethods);
+    }
+
+    return result;
+  }
+
+  @Override
+  public TransferResult<V, S> visitLambdaResultExpression(
+      LambdaResultExpressionNode n, TransferInput<V, S> in) {
+    return n.getResult().accept(this, in);
+  }
+
+  @Override
+  public TransferResult<V, S> visitStringConcatenateAssignment(
+      StringConcatenateAssignmentNode n, TransferInput<V, S> in) {
+    // This gets the type of LHS + RHS
+    TransferResult<V, S> result = super.visitStringConcatenateAssignment(n, in);
+    Node lhs = n.getLeftOperand();
+    Node rhs = n.getRightOperand();
+
+    // update the results store if the assignment target is something we can process
+    S info = result.getRegularStore();
+    // ResultValue is the type of LHS + RHS
+    V resultValue = result.getResultValue();
+
+    if (lhs instanceof FieldAccessNode
+        && shouldPerformWholeProgramInference(n.getTree(), lhs.getTree())) {
+      // Updates inferred field type
+      analysis
+          .atypeFactory
+          .getWholeProgramInference()
+          .updateFromFieldAssignment(
+              (FieldAccessNode) lhs, rhs, analysis.getContainingClass(n.getTree()));
+    }
+
+    processCommonAssignment(in, lhs, rhs, info, resultValue);
+
+    return new RegularTransferResult<>(finishValue(resultValue, info), info);
+  }
+
+  /**
+   * Determine abstract value of right-hand side and update the store accordingly to the assignment.
+   */
+  protected void processCommonAssignment(
+      TransferInput<V, S> in, Node lhs, Node rhs, S info, V rhsValue) {
+
+    // update information in the store
+    info.updateForAssignment(lhs, rhsValue);
+  }
+
+  @Override
+  public TransferResult<V, S> visitObjectCreation(ObjectCreationNode n, TransferInput<V, S> p) {
+    if (shouldPerformWholeProgramInference(n.getTree())) {
+      ExecutableElement constructorElt =
+          analysis.getTypeFactory().constructorFromUse(n.getTree()).executableType.getElement();
+      analysis
+          .atypeFactory
+          .getWholeProgramInference()
+          .updateFromObjectCreation(n, constructorElt, p.getRegularStore());
+    }
+    return super.visitObjectCreation(n, p);
+  }
+
+  @Override
+  public TransferResult<V, S> visitMethodInvocation(
+      MethodInvocationNode n, TransferInput<V, S> in) {
+
+    S store = in.getRegularStore();
+    ExecutableElement method = n.getTarget().getMethod();
+
+    // Perform WPI before the store has been side-effected.
+    if (shouldPerformWholeProgramInference(n.getTree(), method)) {
+      // Updates the inferred parameter types of the invoked method.
+      analysis.atypeFactory.getWholeProgramInference().updateFromMethodInvocation(n, method, store);
+    }
+
+    Tree invocationTree = n.getTree();
+
+    // Determine the abstract value for the method call.
+    // look up the call's value from factory
+    V factoryValue = (invocationTree == null) ? null : getValueFromFactory(invocationTree, n);
+    // look up the call's value in the store (if possible)
+    V storeValue = store.getValue(n);
+    V resValue = moreSpecificValue(factoryValue, storeValue);
+
+    store.updateForMethodCall(n, analysis.atypeFactory, resValue);
+
+    // add new information based on postcondition
+    processPostconditions(n, store, method, invocationTree);
+
+    S thenStore = store;
+    S elseStore = thenStore.copy();
+
+    // add new information based on conditional postcondition
+    processConditionalPostconditions(n, method, invocationTree, thenStore, elseStore);
+
+    return new ConditionalTransferResult<>(
+        finishValue(resValue, thenStore, elseStore), thenStore, elseStore);
+  }
+
+  @Override
+  public TransferResult<V, S> visitInstanceOf(InstanceOfNode node, TransferInput<V, S> in) {
+    TransferResult<V, S> result = super.visitInstanceOf(node, in);
+    // The "reference type" is the type after "instanceof".
+    Tree refTypeTree = node.getTree().getType();
+    if (refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) {
+      AnnotatedTypeMirror refType = analysis.atypeFactory.getAnnotatedType(refTypeTree);
+      AnnotatedTypeMirror expType =
+          analysis.atypeFactory.getAnnotatedType(node.getTree().getExpression());
+      if (analysis.atypeFactory.getTypeHierarchy().isSubtype(refType, expType)
+          && !refType.getAnnotations().equals(expType.getAnnotations())
+          && !expType.getAnnotations().isEmpty()) {
+        JavaExpression expr = JavaExpression.fromTree(node.getTree().getExpression());
+        for (AnnotationMirror anno : refType.getAnnotations()) {
+          in.getRegularStore().insertOrRefine(expr, anno);
+        }
+        return new RegularTransferResult<>(result.getResultValue(), in.getRegularStore());
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns true if whole-program inference should be performed. If the tree is in the scope of
+   * a @SuppressWarnings, then this method returns false.
+   *
+   * @param tree a tree
+   * @return whether to perform whole-program inference on the tree
+   */
+  private boolean shouldPerformWholeProgramInference(Tree tree) {
+    return infer && (tree == null || !analysis.checker.shouldSuppressWarnings(tree, ""));
+  }
+
+  /**
+   * Returns true if whole-program inference should be performed. If the expressionTree or lhsTree
+   * is in the scope of a @SuppressWarnings, then this method returns false.
+   *
+   * @param expressionTree the right-hand side of an assignment
+   * @param lhsTree the left-hand side of an assignment
+   * @return whether to perform whole-program inference
+   */
+  private boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) {
+    // Check that infer is true and the tree isn't in scope of a @SuppressWarnings
+    // before calling InternalUtils.symbol(lhs).
+    if (!shouldPerformWholeProgramInference(expressionTree)) {
+      return false;
+    }
+    Element elt = TreeUtils.elementFromTree(lhsTree);
+    return !analysis.checker.shouldSuppressWarnings(elt, "");
+  }
+
+  /**
+   * Returns true if whole-program inference should be performed. If the tree or element is in the
+   * scope of a @SuppressWarnings, then this method returns false.
+   *
+   * @param tree a tree
+   * @param elt its element
+   * @return whether to perform whole-program inference
+   */
+  private boolean shouldPerformWholeProgramInference(Tree tree, Element elt) {
+    return shouldPerformWholeProgramInference(tree)
+        && !analysis.checker.shouldSuppressWarnings(elt, "");
+  }
+
+  /**
+   * Add information from the postconditions of a method to the store after an invocation.
+   *
+   * @param invocationNode a method call
+   * @param store a store; is side-effected by this method
+   * @param methodElement the method being called
+   * @param invocationTree the tree for the method call
+   */
+  protected void processPostconditions(
+      MethodInvocationNode invocationNode,
+      S store,
+      ExecutableElement methodElement,
+      Tree invocationTree) {
+    ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod();
+    Set<Postcondition> postconditions = contractsUtils.getPostconditions(methodElement);
+    processPostconditionsAndConditionalPostconditions(
+        invocationNode, invocationTree, store, null, postconditions);
+  }
+
+  /**
+   * Add information from the conditional postconditions of a method to the stores after an
+   * invocation.
+   *
+   * @param invocationNode a method call
+   * @param methodElement the method being called
+   * @param invocationTree the tree for the method call
+   * @param thenStore the "then" store; is side-effected by this method
+   * @param elseStore the "else" store; is side-effected by this method
+   */
+  protected void processConditionalPostconditions(
+      MethodInvocationNode invocationNode,
+      ExecutableElement methodElement,
+      Tree invocationTree,
+      S thenStore,
+      S elseStore) {
+    ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod();
+    Set<ConditionalPostcondition> conditionalPostconditions =
+        contractsUtils.getConditionalPostconditions(methodElement);
+    processPostconditionsAndConditionalPostconditions(
+        invocationNode, invocationTree, thenStore, elseStore, conditionalPostconditions);
+  }
+
+  /**
+   * Add information from the postconditions and conditional postconditions of a method to the
+   * stores after an invocation.
+   *
+   * @param invocationNode a method call
+   * @param invocationTree the tree for the method call
+   * @param thenStore the "then" store; is side-effected by this method
+   * @param elseStore the "else" store; is side-effected by this method
+   * @param postconditions the postconditions
+   */
+  private void processPostconditionsAndConditionalPostconditions(
+      MethodInvocationNode invocationNode,
+      Tree invocationTree,
+      S thenStore,
+      S elseStore,
+      Set<? extends Contract> postconditions) {
+
+    StringToJavaExpression stringToJavaExpr =
+        stringExpr ->
+            StringToJavaExpression.atMethodInvocation(stringExpr, invocationNode, analysis.checker);
+    for (Contract p : postconditions) {
+      // Viewpoint-adapt to the method use (the call site).
+      AnnotationMirror anno =
+          p.viewpointAdaptDependentTypeAnnotation(
+              analysis.atypeFactory, stringToJavaExpr, /*errorTree=*/ null);
+
+      String expressionString = p.expressionString;
+      try {
+        JavaExpression je = stringToJavaExpr.toJavaExpression(expressionString);
+
+        // "insertOrRefine" is called so that the postcondition information is added to any
+        // existing information rather than replacing it.  If the called method is not
+        // side-effect-free, then the values that might have been changed by the method call
+        // are removed from the store before this method is called.
+        if (p.kind == Contract.Kind.CONDITIONALPOSTCONDITION) {
+          if (((ConditionalPostcondition) p).resultValue) {
+            thenStore.insertOrRefinePermitNondeterministic(je, anno);
+          } else {
+            elseStore.insertOrRefinePermitNondeterministic(je, anno);
+          }
+        } else {
+          thenStore.insertOrRefinePermitNondeterministic(je, anno);
+        }
+      } catch (JavaExpressionParseException e) {
+        // report errors here
+        if (e.isFlowParseError()) {
+          Object[] args = new Object[e.args.length + 1];
+          args[0] =
+              ElementUtils.getSimpleSignature(TreeUtils.elementFromUse(invocationNode.getTree()));
+          System.arraycopy(e.args, 0, args, 1, e.args.length);
+          analysis.checker.reportError(invocationTree, "flowexpr.parse.error.postcondition", args);
+        } else {
+          analysis.checker.report(invocationTree, e.getDiagMessage());
+        }
+      }
+    }
+  }
+
+  /**
+   * A case produces no value, but it may imply some facts about the argument to the switch
+   * statement.
+   */
+  @Override
+  public TransferResult<V, S> visitCase(CaseNode n, TransferInput<V, S> in) {
+    S store = in.getRegularStore();
+    TransferResult<V, S> result =
+        new ConditionalTransferResult<>(
+            finishValue(null, store), in.getThenStore(), in.getElseStore(), false);
+
+    V caseValue = in.getValueOfSubNode(n.getCaseOperand());
+    AssignmentNode assign = (AssignmentNode) n.getSwitchOperand();
+    V switchValue = store.getValue(JavaExpression.fromNode(assign.getTarget()));
+    result =
+        strengthenAnnotationOfEqualTo(
+            result, n.getCaseOperand(), assign.getExpression(), caseValue, switchValue, false);
+
+    // Update value of switch temporary variable
+    result =
+        strengthenAnnotationOfEqualTo(
+            result, n.getCaseOperand(), assign.getTarget(), caseValue, switchValue, false);
+    return result;
+  }
+
+  /**
+   * In a cast {@code (@A C) e} of some expression {@code e} to a new type {@code @A C}, we usually
+   * take the annotation of the type {@code C} (here {@code @A}). However, if the inferred
+   * annotation of {@code e} is more precise, we keep that one.
+   */
+  // @Override
+  // public TransferResult<V, S> visitTypeCast(TypeCastNode n,
+  // TransferInput<V, S> p) {
+  // TransferResult<V, S> result = super.visitTypeCast(n, p);
+  // V value = result.getResultValue();
+  // V operandValue = p.getValueOfSubNode(n.getOperand());
+  // // Normally we take the value of the type cast node. However, if the old
+  // // flow-refined value was more precise, we keep that value.
+  // V resultValue = moreSpecificValue(value, operandValue);
+  // result.setResultValue(resultValue);
+  // return result;
+  // }
+
+  /**
+   * Returns the abstract value of {@code (value1, value2)} that is more specific. If the two are
+   * incomparable, then {@code value1} is returned.
+   */
+  public V moreSpecificValue(V value1, V value2) {
+    if (value1 == null) {
+      return value2;
+    }
+    if (value2 == null) {
+      return value1;
+    }
+    return value1.mostSpecific(value2, value1);
+  }
+
+  @Override
+  public TransferResult<V, S> visitVariableDeclaration(
+      VariableDeclarationNode n, TransferInput<V, S> p) {
+    S store = p.getRegularStore();
+    return new RegularTransferResult<>(finishValue(null, store), store);
+  }
+
+  @Override
+  public TransferResult<V, S> visitWideningConversion(
+      WideningConversionNode n, TransferInput<V, S> p) {
+    TransferResult<V, S> result = super.visitWideningConversion(n, p);
+    // Combine annotations from the operand with the wide type
+    V operandValue = p.getValueOfSubNode(n.getOperand());
+    V widenedValue = getWidenedValue(n.getType(), operandValue);
+    result.setResultValue(widenedValue);
+    return result;
+  }
+
+  /**
+   * Returns an abstract value with the given {@code type} and the annotations from {@code
+   * annotatedValue}, adapted for narrowing. This is only called at a narrowing conversion.
+   *
+   * @param type the type to narrow to
+   * @param annotatedValue the type to narrow from
+   * @return an abstract value with the given {@code type} and the annotations from {@code
+   *     annotatedValue}; returns null if {@code annotatedValue} is null
+   */
+  protected V getNarrowedValue(TypeMirror type, V annotatedValue) {
+    if (annotatedValue == null) {
+      return null;
+    }
+    Set<AnnotationMirror> narrowedAnnos =
+        analysis.atypeFactory.getNarrowedAnnotations(
+            annotatedValue.getAnnotations(),
+            annotatedValue.getUnderlyingType().getKind(),
+            type.getKind());
+
+    return analysis.createAbstractValue(narrowedAnnos, type);
+  }
+
+  /**
+   * Returns an abstract value with the given {@code type} and the annotations from {@code
+   * annotatedValue}, adapted for widening. This is only called at a widening conversion.
+   *
+   * @param type the type to widen to
+   * @param annotatedValue the type to widen from
+   * @return an abstract value with the given {@code type} and the annotations from {@code
+   *     annotatedValue}; returns null if {@code annotatedValue} is null
+   */
+  protected V getWidenedValue(TypeMirror type, V annotatedValue) {
+    if (annotatedValue == null) {
+      return null;
+    }
+    Set<AnnotationMirror> widenedAnnos =
+        analysis.atypeFactory.getWidenedAnnotations(
+            annotatedValue.getAnnotations(),
+            annotatedValue.getUnderlyingType().getKind(),
+            type.getKind());
+
+    return analysis.createAbstractValue(widenedAnnos, type);
+  }
+
+  @Override
+  public TransferResult<V, S> visitNarrowingConversion(
+      NarrowingConversionNode n, TransferInput<V, S> p) {
+    TransferResult<V, S> result = super.visitNarrowingConversion(n, p);
+    // Combine annotations from the operand with the narrow type
+    V operandValue = p.getValueOfSubNode(n.getOperand());
+    V narrowedValue = getNarrowedValue(n.getType(), operandValue);
+    result.setResultValue(narrowedValue);
+    return result;
+  }
+
+  @Override
+  public TransferResult<V, S> visitStringConversion(StringConversionNode n, TransferInput<V, S> p) {
+    TransferResult<V, S> result = super.visitStringConversion(n, p);
+    result.setResultValue(p.getValueOfSubNode(n.getOperand()));
+    return result;
+  }
+
+  /**
+   * Inserts newAnno as the value into all stores (conditional or not) in the result for node. This
+   * is a utility method for subclasses.
+   *
+   * @param result the TransferResult holding the stores to modify
+   * @param target the receiver whose value should be modified
+   * @param newAnno the new value
+   */
+  public static void insertIntoStores(
+      TransferResult<CFValue, CFStore> result, JavaExpression target, AnnotationMirror newAnno) {
+    if (result.containsTwoStores()) {
+      result.getThenStore().insertValue(target, newAnno);
+      result.getElseStore().insertValue(target, newAnno);
+    } else {
+      result.getRegularStore().insertValue(target, newAnno);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java
new file mode 100644
index 0000000..cfc25d9
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java
@@ -0,0 +1,744 @@
+package org.checkerframework.framework.flow;
+
+import java.util.Objects;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.analysis.AbstractValue;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * An implementation of an abstract value used by the Checker Framework
+ * org.checkerframework.dataflow analysis.
+ *
+ * <p>A value holds a set of annotations and a type mirror. The set of annotations represents the
+ * primary annotation on a type; therefore, the set of annotations must have an annotation for each
+ * hierarchy unless the type mirror is a type variable or a wildcard that extends a type variable.
+ * Both type variables and wildcards may be missing a primary annotation. For this set of
+ * annotations, there is an additional constraint that only wildcards that extend type variables can
+ * be missing annotations.
+ *
+ * <p>In order to compute {@link #leastUpperBound(CFAbstractValue)} and {@link
+ * #mostSpecific(CFAbstractValue, CFAbstractValue)}, the case where one value has an annotation in a
+ * hierarchy and the other does not must be handled. For type variables, the {@link
+ * AnnotatedTypeVariable} for the declaration of the type variable is used. The {@link
+ * AnnotatedTypeVariable} is computed using the type mirror. For wildcards, it is not always
+ * possible to get the {@link AnnotatedWildcardType} for the type mirror. However, a
+ * CFAbstractValue's type mirror is only a wildcard if the type of some expression is a wildcard.
+ * The type of an expression is only a wildcard because the Checker Framework does not implement
+ * capture conversion. For these uses of uncaptured wildcards, only the primary annotation on the
+ * upper bound is ever used. So, the set of annotations represents the primary annotation on the
+ * wildcard's upper bound. If that upper bound is a type variable, then the set of annotations could
+ * be missing an annotation in a hierarchy.
+ */
+public abstract class CFAbstractValue<V extends CFAbstractValue<V>> implements AbstractValue<V> {
+
+  /** The analysis class this value belongs to. */
+  protected final CFAbstractAnalysis<V, ?, ?> analysis;
+
+  /** The underlying (Java) type in this abstract value. */
+  protected final TypeMirror underlyingType;
+  /** The annotations in this abstract value. */
+  protected final Set<AnnotationMirror> annotations;
+
+  /**
+   * Creates a new CFAbstractValue.
+   *
+   * @param analysis the analysis class this value belongs to
+   * @param annotations the annotations in this abstract value
+   * @param underlyingType the underlying (Java) type in this abstract value
+   */
+  protected CFAbstractValue(
+      CFAbstractAnalysis<V, ?, ?> analysis,
+      Set<AnnotationMirror> annotations,
+      TypeMirror underlyingType) {
+    this.analysis = analysis;
+    this.annotations = annotations;
+    this.underlyingType = underlyingType;
+
+    assert validateSet(
+            this.getAnnotations(),
+            this.getUnderlyingType(),
+            analysis.getTypeFactory().getQualifierHierarchy())
+        : "Encountered invalid type: "
+            + underlyingType
+            + " annotations: "
+            + StringsPlume.join(", ", annotations);
+  }
+
+  /**
+   * Returns true if the set has an annotation from every hierarchy (or if it doesn't need to);
+   * returns false if the set is missing an annotation from some hierarchy.
+   *
+   * @param annos set of annotations
+   * @param typeMirror where the annotations are written
+   * @param hierarchy the qualifier hierarchy
+   * @return true if no annotations are missing
+   */
+  public static boolean validateSet(
+      Set<AnnotationMirror> annos, TypeMirror typeMirror, QualifierHierarchy hierarchy) {
+
+    if (canBeMissingAnnotations(typeMirror)) {
+      return true;
+    }
+
+    Set<AnnotationMirror> missingHierarchy = null;
+    for (AnnotationMirror top : hierarchy.getTopAnnotations()) {
+      AnnotationMirror anno = hierarchy.findAnnotationInHierarchy(annos, top);
+      if (anno == null) {
+        if (missingHierarchy == null) {
+          missingHierarchy = AnnotationUtils.createAnnotationSet();
+        }
+        missingHierarchy.add(top);
+      }
+    }
+
+    return missingHierarchy == null;
+  }
+
+  /**
+   * Returns whether or not the set of annotations can be missing an annotation for any hierarchy.
+   *
+   * @return whether or not the set of annotations can be missing an annotation
+   */
+  public boolean canBeMissingAnnotations() {
+    return canBeMissingAnnotations(underlyingType);
+  }
+
+  /**
+   * Returns true if it is OK for the given type mirror not to be annotated, such as for VOID, NONE,
+   * PACKAGE, TYPEVAR, and some WILDCARD.
+   *
+   * @param typeMirror a type
+   * @return true if it is OK for the given type mirror not to be annotated
+   */
+  private static boolean canBeMissingAnnotations(TypeMirror typeMirror) {
+    if (typeMirror == null) {
+      return false;
+    }
+    if (typeMirror.getKind() == TypeKind.VOID
+        || typeMirror.getKind() == TypeKind.NONE
+        || typeMirror.getKind() == TypeKind.PACKAGE) {
+      return true;
+    }
+    if (typeMirror.getKind() == TypeKind.WILDCARD) {
+      return canBeMissingAnnotations(((WildcardType) typeMirror).getExtendsBound());
+    }
+    return typeMirror.getKind() == TypeKind.TYPEVAR;
+  }
+
+  /**
+   * Returns a set of annotations. If {@link #canBeMissingAnnotations()} returns true, then the set
+   * of annotations may not have an annotation for every hierarchy.
+   *
+   * <p>To get the single annotation in a particular hierarchy, use {@link
+   * QualifierHierarchy#findAnnotationInHierarchy}.
+   *
+   * @return a set of annotations
+   */
+  @Pure
+  public Set<AnnotationMirror> getAnnotations() {
+    return annotations;
+  }
+
+  @Pure
+  public TypeMirror getUnderlyingType() {
+    return underlyingType;
+  }
+
+  @SuppressWarnings("interning:not.interned") // efficiency pre-test
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof CFAbstractValue)) {
+      return false;
+    }
+
+    CFAbstractValue<?> other = (CFAbstractValue<?>) obj;
+    if (this.getUnderlyingType() != other.getUnderlyingType()
+        && !analysis.getTypes().isSameType(this.getUnderlyingType(), other.getUnderlyingType())) {
+      return false;
+    }
+    return AnnotationUtils.areSame(this.getAnnotations(), other.getAnnotations());
+  }
+
+  @Pure
+  @Override
+  public int hashCode() {
+    return Objects.hash(getAnnotations(), underlyingType);
+  }
+
+  /**
+   * Returns the string representation, using fully-qualified names.
+   *
+   * @return the string representation, using fully-qualified names
+   */
+  @SideEffectFree
+  public String toStringFullyQualified() {
+    return "CFAV{" + annotations + ", " + underlyingType + '}';
+  }
+
+  /**
+   * Returns the string representation, using simple (not fully-qualified) names.
+   *
+   * @return the string representation, using simple (not fully-qualified) names
+   */
+  @SideEffectFree
+  public String toStringSimple() {
+    return "CFAV{"
+        + AnnotationUtils.toStringSimple(annotations)
+        + ", "
+        + TypesUtils.simpleTypeName(underlyingType)
+        + '}';
+  }
+
+  /**
+   * Returns the string representation.
+   *
+   * @return the string representation
+   */
+  @SideEffectFree
+  @Override
+  public String toString() {
+    return toStringSimple();
+  }
+
+  /**
+   * Returns the more specific version of two values {@code this} and {@code other}. If they do not
+   * contain information for all hierarchies, then it is possible that information from both {@code
+   * this} and {@code other} are taken.
+   *
+   * <p>If neither of the two is more specific for one of the hierarchies (i.e., if the two are
+   * incomparable as determined by {@link QualifierHierarchy#isSubtype(AnnotationMirror,
+   * AnnotationMirror)}, then the respective value from {@code backup} is used.
+   */
+  public V mostSpecific(@Nullable V other, @Nullable V backup) {
+    if (other == null) {
+      @SuppressWarnings("unchecked")
+      V v = (V) this;
+      return v;
+    }
+    Types types = analysis.getTypes();
+    TypeMirror mostSpecifTypeMirror;
+    if (types.isAssignable(this.getUnderlyingType(), other.getUnderlyingType())) {
+      mostSpecifTypeMirror = this.getUnderlyingType();
+    } else if (types.isAssignable(other.getUnderlyingType(), this.getUnderlyingType())) {
+      mostSpecifTypeMirror = other.getUnderlyingType();
+    } else if (TypesUtils.isErasedSubtype(
+        this.getUnderlyingType(), other.getUnderlyingType(), types)) {
+      mostSpecifTypeMirror = this.getUnderlyingType();
+    } else if (TypesUtils.isErasedSubtype(
+        other.getUnderlyingType(), this.getUnderlyingType(), types)) {
+      mostSpecifTypeMirror = other.getUnderlyingType();
+    } else {
+      mostSpecifTypeMirror = this.getUnderlyingType();
+    }
+
+    MostSpecificVisitor ms =
+        new MostSpecificVisitor(this.getUnderlyingType(), other.getUnderlyingType(), backup);
+    Set<AnnotationMirror> mostSpecific =
+        ms.combineSets(
+            this.getUnderlyingType(),
+            this.getAnnotations(),
+            other.getUnderlyingType(),
+            other.getAnnotations(),
+            canBeMissingAnnotations(mostSpecifTypeMirror));
+    if (ms.error) {
+      return backup;
+    }
+    return analysis.createAbstractValue(mostSpecific, mostSpecifTypeMirror);
+  }
+
+  /** Computes the most specific annotations. */
+  private class MostSpecificVisitor extends AnnotationSetCombiner {
+    /** If set to true, then this visitor was unable to find a most specific annotation. */
+    boolean error = false;
+
+    /** Set of annotations to use if a most specific value cannot be found. */
+    final Set<AnnotationMirror> backupSet;
+
+    /** TypeMirror for the "a" value. */
+    final TypeMirror aTypeMirror;
+
+    /** TypeMirror for the "b" value. */
+    final TypeMirror bTypeMirror;
+
+    /**
+     * Create a {@link MostSpecificVisitor}.
+     *
+     * @param aTypeMirror type of the "a" value
+     * @param bTypeMirror type of the "b" value
+     * @param backup value to use if no most specific value is found
+     */
+    public MostSpecificVisitor(TypeMirror aTypeMirror, TypeMirror bTypeMirror, V backup) {
+      this.aTypeMirror = aTypeMirror;
+      this.bTypeMirror = bTypeMirror;
+      if (backup != null) {
+        this.backupSet = backup.getAnnotations();
+        // this.backupTypeMirror = backup.getUnderlyingType();
+        // this.backupAtv = getEffectTypeVar(backupTypeMirror);
+      } else {
+        // this.backupAtv = null;
+        // this.backupTypeMirror = null;
+        this.backupSet = null;
+      }
+    }
+
+    private AnnotationMirror getBackUpAnnoIn(AnnotationMirror top) {
+      if (backupSet == null) {
+        // If there is no back up value, but one is required then the resulting set will
+        // not be the most specific.  Indicate this with the error.
+        error = true;
+        return null;
+      }
+      QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy();
+      return hierarchy.findAnnotationInHierarchy(backupSet, top);
+    }
+
+    @Override
+    protected @Nullable AnnotationMirror combineTwoAnnotations(
+        AnnotationMirror a, AnnotationMirror b, AnnotationMirror top) {
+      QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy();
+      if (analysis
+              .getTypeFactory()
+              .hasQualifierParameterInHierarchy(TypesUtils.getTypeElement(aTypeMirror), top)
+          && analysis
+              .getTypeFactory()
+              .hasQualifierParameterInHierarchy(TypesUtils.getTypeElement(bTypeMirror), top)) {
+        // Both types have qualifier parameters, so they are related by invariance rather than
+        // subtyping.
+        if (hierarchy.isSubtype(a, b) && hierarchy.isSubtype(b, a)) {
+          return b;
+        }
+      } else if (hierarchy.isSubtype(a, b)) {
+        return a;
+      } else if (hierarchy.isSubtype(b, a)) {
+        return b;
+      }
+      return getBackUpAnnoIn(top);
+    }
+
+    @Override
+    protected @Nullable AnnotationMirror combineNoAnnotations(
+        AnnotatedTypeVariable aAtv,
+        AnnotatedTypeVariable bAtv,
+        AnnotationMirror top,
+        boolean canCombinedSetBeMissingAnnos) {
+      if (canCombinedSetBeMissingAnnos) {
+        return null;
+      } else {
+        AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top);
+        AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top);
+        return combineTwoAnnotations(aUB, bUB, top);
+      }
+    }
+
+    @Override
+    protected @Nullable AnnotationMirror combineOneAnnotation(
+        AnnotationMirror annotation,
+        AnnotatedTypeVariable typeVar,
+        AnnotationMirror top,
+        boolean canCombinedSetBeMissingAnnos) {
+
+      QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy();
+      AnnotationMirror upperBound = typeVar.getEffectiveAnnotationInHierarchy(top);
+
+      if (!canCombinedSetBeMissingAnnos) {
+        return combineTwoAnnotations(annotation, upperBound, top);
+      }
+      Set<AnnotationMirror> lBSet =
+          AnnotatedTypes.findEffectiveLowerBoundAnnotations(hierarchy, typeVar);
+      AnnotationMirror lowerBound = hierarchy.findAnnotationInHierarchy(lBSet, top);
+      if (hierarchy.isSubtype(upperBound, annotation)) {
+        // no anno is more specific than anno
+        return null;
+      } else if (hierarchy.isSubtype(annotation, lowerBound)) {
+        return annotation;
+      } else {
+        return getBackUpAnnoIn(top);
+      }
+    }
+  }
+
+  @Override
+  public V leastUpperBound(@Nullable V other) {
+    return upperBound(other, false);
+  }
+
+  /**
+   * Compute an upper bound of two values that is wider than the least upper bound of the two
+   * values. Used to jump to a higher abstraction to allow faster termination of the fixed point
+   * computations in {@link Analysis}.
+   *
+   * <p>A particular analysis might not require widening and should implement this method by calling
+   * leastUpperBound.
+   *
+   * <p><em>Important</em>: This method must fulfill the following contract:
+   *
+   * <ul>
+   *   <li>Does not change {@code this}.
+   *   <li>Does not change {@code previous}.
+   *   <li>Returns a fresh object which is not aliased yet.
+   *   <li>Returns an object of the same (dynamic) type as {@code this}, even if the signature is
+   *       more permissive.
+   *   <li>Is commutative.
+   * </ul>
+   *
+   * @param previous must be the previous value
+   * @return an upper bound of two values that is wider than the least upper bound of the two values
+   */
+  public V widenUpperBound(@Nullable V previous) {
+    return upperBound(previous, true);
+  }
+
+  private V upperBound(@Nullable V other, boolean shouldWiden) {
+    if (other == null) {
+      @SuppressWarnings("unchecked")
+      V v = (V) this;
+      return v;
+    }
+    ProcessingEnvironment processingEnv = analysis.getTypeFactory().getProcessingEnv();
+    TypeMirror lubTypeMirror =
+        TypesUtils.leastUpperBound(
+            this.getUnderlyingType(), other.getUnderlyingType(), processingEnv);
+
+    ValueLub valueLub = new ValueLub(shouldWiden);
+    Set<AnnotationMirror> lub =
+        valueLub.combineSets(
+            this.getUnderlyingType(),
+            this.getAnnotations(),
+            other.getUnderlyingType(),
+            other.getAnnotations(),
+            canBeMissingAnnotations(lubTypeMirror));
+    return analysis.createAbstractValue(lub, lubTypeMirror);
+  }
+
+  /**
+   * Computes the least upper bound or, if {@code shouldWiden} is true, an upper bounds of two sets
+   * of annotations. The computation accounts for sets that are missing annotations in hierarchies.
+   */
+  protected class ValueLub extends AnnotationSetCombiner {
+
+    /**
+     * If true, this class computes an upper bound; if false, this class computes the least upper
+     * bound.
+     */
+    private final boolean widen;
+
+    /**
+     * Creates a {@link ValueLub}.
+     *
+     * @param shouldWiden if true, this class computes an upper bound
+     */
+    public ValueLub(boolean shouldWiden) {
+      this.widen = shouldWiden;
+    }
+
+    @Override
+    protected @Nullable AnnotationMirror combineTwoAnnotations(
+        AnnotationMirror a, AnnotationMirror b, AnnotationMirror top) {
+      QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy();
+      if (widen) {
+        return hierarchy.widenedUpperBound(a, b);
+      } else {
+        return hierarchy.leastUpperBound(a, b);
+      }
+    }
+
+    @Override
+    protected @Nullable AnnotationMirror combineNoAnnotations(
+        AnnotatedTypeVariable aAtv,
+        AnnotatedTypeVariable bAtv,
+        AnnotationMirror top,
+        boolean canCombinedSetBeMissingAnnos) {
+      if (canCombinedSetBeMissingAnnos) {
+        // don't add an annotation
+        return null;
+      } else {
+        AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top);
+        AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top);
+        return combineTwoAnnotations(aUB, bUB, top);
+      }
+    }
+
+    @Override
+    protected @Nullable AnnotationMirror combineOneAnnotation(
+        AnnotationMirror annotation,
+        AnnotatedTypeVariable typeVar,
+        AnnotationMirror top,
+        boolean canCombinedSetBeMissingAnnos) {
+      QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy();
+      if (canCombinedSetBeMissingAnnos) {
+        // anno is the primary annotation on the use of a type variable. typeVar is a use of the
+        // same type variable that does not have a primary annotation. The lub of the two type
+        // variables is computed as follows. If anno is a subtype (or equal) to the annotation on
+        // the lower bound of typeVar, then typeVar is the lub, so no annotation is added to lubset.
+        // If anno is a supertype of the annotation on the lower bound of typeVar, then the lub is
+        // typeVar with a primary annotation of lub(anno, upperBound), where upperBound is the
+        // annotation on the upper bound of typeVar.
+        Set<AnnotationMirror> lBSet =
+            AnnotatedTypes.findEffectiveLowerBoundAnnotations(hierarchy, typeVar);
+        AnnotationMirror lowerBound = hierarchy.findAnnotationInHierarchy(lBSet, top);
+        if (hierarchy.isSubtype(annotation, lowerBound)) {
+          return null;
+        } else {
+          return combineTwoAnnotations(
+              annotation, typeVar.getEffectiveAnnotationInHierarchy(top), top);
+        }
+      } else {
+        return combineTwoAnnotations(
+            annotation, typeVar.getEffectiveAnnotationInHierarchy(top), top);
+      }
+    }
+  }
+
+  /**
+   * Compute the greatest lower bound of two values.
+   *
+   * <p><em>Important</em>: This method must fulfill the following contract:
+   *
+   * <ul>
+   *   <li>Does not change {@code this}.
+   *   <li>Does not change {@code other}.
+   *   <li>Returns a fresh object which is not aliased yet.
+   *   <li>Returns an object of the same (dynamic) type as {@code this}, even if the signature is
+   *       more permissive.
+   *   <li>Is commutative.
+   * </ul>
+   *
+   * @param other another value
+   * @return the greatest lower bound of two values
+   */
+  public V greatestLowerBound(@Nullable V other) {
+    if (other == null) {
+      @SuppressWarnings("unchecked")
+      V v = (V) this;
+      return v;
+    }
+    ProcessingEnvironment processingEnv = analysis.getTypeFactory().getProcessingEnv();
+    TypeMirror glbTypeMirror =
+        TypesUtils.greatestLowerBound(
+            this.getUnderlyingType(), other.getUnderlyingType(), processingEnv);
+
+    ValueGlb valueGlb = new ValueGlb();
+    Set<AnnotationMirror> glb =
+        valueGlb.combineSets(
+            this.getUnderlyingType(),
+            this.getAnnotations(),
+            other.getUnderlyingType(),
+            other.getAnnotations(),
+            canBeMissingAnnotations(glbTypeMirror));
+    return analysis.createAbstractValue(glb, glbTypeMirror);
+  }
+
+  /**
+   * Computes the GLB of two sets of annotations. The computation accounts for sets that are missing
+   * annotations in hierarchies.
+   */
+  protected class ValueGlb extends AnnotationSetCombiner {
+
+    @Override
+    protected @Nullable AnnotationMirror combineTwoAnnotations(
+        AnnotationMirror a, AnnotationMirror b, AnnotationMirror top) {
+      QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy();
+      return hierarchy.greatestLowerBound(a, b);
+    }
+
+    @Override
+    protected @Nullable AnnotationMirror combineNoAnnotations(
+        AnnotatedTypeVariable aAtv,
+        AnnotatedTypeVariable bAtv,
+        AnnotationMirror top,
+        boolean canCombinedSetBeMissingAnnos) {
+      if (canCombinedSetBeMissingAnnos) {
+        // don't add an annotation
+        return null;
+      } else {
+        AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top);
+        AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top);
+        return combineTwoAnnotations(aUB, bUB, top);
+      }
+    }
+
+    @Override
+    protected @Nullable AnnotationMirror combineOneAnnotation(
+        AnnotationMirror annotation,
+        AnnotatedTypeVariable typeVar,
+        AnnotationMirror top,
+        boolean canCombinedSetBeMissingAnnos) {
+      QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy();
+      if (canCombinedSetBeMissingAnnos) {
+        // anno is the primary annotation on the use of a type variable. typeVar is a use of the
+        // same type variable that does not have a primary annotation. The glb of the two type
+        // variables is computed as follows. If anno is a supertype (or equal) to the annotation on
+        // the upper bound of typeVar, then typeVar is the glb, so no annotation is added to glbset.
+        // If anno is a subtype of the annotation on the upper bound of typeVar, then the glb is
+        // typeVar with a primary annotation of glb(anno, lowerBound), where lowerBound is the
+        // annotation on the lower bound of typeVar.
+        AnnotationMirror upperBound = typeVar.getEffectiveAnnotationInHierarchy(top);
+        if (hierarchy.isSubtype(upperBound, annotation)) {
+          return null;
+        } else {
+          Set<AnnotationMirror> lBSet =
+              AnnotatedTypes.findEffectiveLowerBoundAnnotations(hierarchy, typeVar);
+          AnnotationMirror lowerBound = hierarchy.findAnnotationInHierarchy(lBSet, top);
+          return combineTwoAnnotations(annotation, lowerBound, top);
+        }
+      } else {
+        return combineTwoAnnotations(
+            annotation, typeVar.getEffectiveAnnotationInHierarchy(top), top);
+      }
+    }
+  }
+
+  /**
+   * Combines two sets of AnnotationMirrors by hierarchy.
+   *
+   * <p>Subclasses must define how to combine sets by implementing the following methods:
+   *
+   * <ol>
+   *   <li>{@code #combineTwoAnnotations(AnnotationMirror, AnnotationMirror, AnnotationMirror)}
+   *   <li>{@code #combineOneAnnotation(AnnotationMirror, AnnotatedTypeVariable, AnnotationMirror,
+   *       boolean)}
+   *   <li>{@code #combineNoAnnotations(AnnotatedTypeVariable, AnnotatedTypeVariable,
+   *       AnnotationMirror, boolean)}
+   * </ol>
+   *
+   * If a set is missing an annotation in a hierarchy, and if the combined set can be missing an
+   * annotation, then there must be a TypeVariable for the set that can be used to find annotations
+   * on its bounds.
+   */
+  protected abstract class AnnotationSetCombiner {
+
+    /**
+     * Combines the two sets.
+     *
+     * @param aTypeMirror the type mirror associated with {@code aSet}
+     * @param aSet a set of annotation mirrors
+     * @param bTypeMirror the type mirror associated with {@code bSet}
+     * @param bSet a set of annotation mirrors
+     * @param canCombinedSetBeMissingAnnos whether or not the combined set can be missing
+     *     annotations
+     * @return the combined sets
+     */
+    protected Set<AnnotationMirror> combineSets(
+        TypeMirror aTypeMirror,
+        Set<AnnotationMirror> aSet,
+        TypeMirror bTypeMirror,
+        Set<AnnotationMirror> bSet,
+        boolean canCombinedSetBeMissingAnnos) {
+
+      AnnotatedTypeVariable aAtv = getEffectTypeVar(aTypeMirror);
+      AnnotatedTypeVariable bAtv = getEffectTypeVar(bTypeMirror);
+      QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy();
+      Set<? extends AnnotationMirror> tops = hierarchy.getTopAnnotations();
+      Set<AnnotationMirror> combinedSets = AnnotationUtils.createAnnotationSet();
+      for (AnnotationMirror top : tops) {
+        AnnotationMirror a = hierarchy.findAnnotationInHierarchy(aSet, top);
+        AnnotationMirror b = hierarchy.findAnnotationInHierarchy(bSet, top);
+        AnnotationMirror result;
+        if (a != null && b != null) {
+          result = combineTwoAnnotations(a, b, top);
+        } else if (a != null) {
+          result = combineOneAnnotation(a, bAtv, top, canCombinedSetBeMissingAnnos);
+        } else if (b != null) {
+          result = combineOneAnnotation(b, aAtv, top, canCombinedSetBeMissingAnnos);
+        } else {
+          result = combineNoAnnotations(aAtv, bAtv, top, canCombinedSetBeMissingAnnos);
+        }
+        if (result != null) {
+          combinedSets.add(result);
+        }
+      }
+      return combinedSets;
+    }
+
+    /**
+     * Returns the result of combining the two annotations. This method is called when an annotation
+     * exists in both sets for the hierarchy whose top is {@code top}.
+     *
+     * @param a an annotation in the hierarchy
+     * @param b an annotation in the hierarchy
+     * @param top the top annotation in the hierarchy
+     * @return the result of combining the two annotations or null if no combination exists
+     */
+    protected abstract @Nullable AnnotationMirror combineTwoAnnotations(
+        AnnotationMirror a, AnnotationMirror b, AnnotationMirror top);
+
+    /**
+     * Returns the primary annotation that result from of combining the two {@link
+     * AnnotatedTypeVariable}. If the result has not primary annotation, the {@code null} is
+     * returned. This method is called when no annotation exists in either sets for the hierarchy
+     * whose top is {@code top}.
+     *
+     * @param aAtv a type variable that does not have a primary annotation in {@code top} hierarchy
+     * @param bAtv a type variable that does not have a primary annotation in {@code top} hierarchy
+     * @param top the top annotation in the hierarchy
+     * @param canCombinedSetBeMissingAnnos whether or not
+     * @return the result of combining the two type variables, which may be null
+     */
+    protected abstract @Nullable AnnotationMirror combineNoAnnotations(
+        AnnotatedTypeVariable aAtv,
+        AnnotatedTypeVariable bAtv,
+        AnnotationMirror top,
+        boolean canCombinedSetBeMissingAnnos);
+
+    /**
+     * Returns the result of combining {@code annotation} with {@code typeVar}.
+     *
+     * <p>This is called when an annotation exists for the hierarchy in on set, but not the other.
+     *
+     * @param annotation an annotation
+     * @param typeVar a type variable that does not have a primary annotation in the hierarchy
+     * @param top the top annotation of the hierarchy
+     * @param canCombinedSetBeMissingAnnos whether or not
+     * @return the result of combining {@code annotation} with {@code typeVar}
+     */
+    protected abstract @Nullable AnnotationMirror combineOneAnnotation(
+        AnnotationMirror annotation,
+        AnnotatedTypeVariable typeVar,
+        AnnotationMirror top,
+        boolean canCombinedSetBeMissingAnnos);
+  }
+
+  /**
+   * Returns the AnnotatedTypeVariable associated with the given TypeMirror or null.
+   *
+   * <p>If {@code typeMirror} is a type variable, then the {@link AnnotatedTypeVariable} of its
+   * declaration is returned. If {@code typeMirror} is a wildcard whose extends bounds is a type
+   * variable, then the {@link AnnotatedTypeVariable} for its declaration is returned. Otherwise,
+   * {@code null} is returned.
+   *
+   * @param typeMirror a type mirror
+   * @return the AnnotatedTypeVariable associated with the given TypeMirror or null
+   */
+  private @Nullable AnnotatedTypeVariable getEffectTypeVar(@Nullable TypeMirror typeMirror) {
+    if (typeMirror == null) {
+      return null;
+    } else if (typeMirror.getKind() == TypeKind.WILDCARD) {
+      return getEffectTypeVar(((WildcardType) typeMirror).getExtendsBound());
+
+    } else if (typeMirror.getKind() == TypeKind.TYPEVAR) {
+      TypeVariable typevar = ((TypeVariable) typeMirror);
+      AnnotatedTypeMirror atm = analysis.getTypeFactory().getAnnotatedType(typevar.asElement());
+      return (AnnotatedTypeVariable) atm;
+    } else {
+      return null;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAnalysis.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAnalysis.java
new file mode 100644
index 0000000..9253329
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAnalysis.java
@@ -0,0 +1,36 @@
+package org.checkerframework.framework.flow;
+
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.javacutil.Pair;
+
+/** The default org.checkerframework.dataflow analysis used in the Checker Framework. */
+public class CFAnalysis extends CFAbstractAnalysis<CFValue, CFStore, CFTransfer> {
+
+  public CFAnalysis(
+      BaseTypeChecker checker,
+      GenericAnnotatedTypeFactory<CFValue, CFStore, CFTransfer, CFAnalysis> factory,
+      List<Pair<VariableElement, CFValue>> fieldValues) {
+    super(checker, factory, fieldValues);
+  }
+
+  @Override
+  public CFStore createEmptyStore(boolean sequentialSemantics) {
+    return new CFStore(this, sequentialSemantics);
+  }
+
+  @Override
+  public CFStore createCopiedStore(CFStore s) {
+    return new CFStore(s);
+  }
+
+  @Override
+  public CFValue createAbstractValue(Set<AnnotationMirror> annotations, TypeMirror underlyingType) {
+    return defaultCreateAbstractValue(this, annotations, underlyingType);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFCFGBuilder.java b/framework/src/main/java/org/checkerframework/framework/flow/CFCFGBuilder.java
new file mode 100644
index 0000000..e216b51
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/flow/CFCFGBuilder.java
@@ -0,0 +1,220 @@
+package org.checkerframework.framework.flow;
+
+import com.sun.source.tree.AssertTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import java.util.Collection;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.builder.CFGBuilder;
+import org.checkerframework.dataflow.cfg.builder.CFGTranslationPhaseOne;
+import org.checkerframework.dataflow.cfg.builder.CFGTranslationPhaseThree;
+import org.checkerframework.dataflow.cfg.builder.CFGTranslationPhaseTwo;
+import org.checkerframework.dataflow.cfg.builder.PhaseOneResult;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.UserError;
+
+/**
+ * A control-flow graph builder (see {@link CFGBuilder}) that knows about the Checker Framework
+ * annotations and their representation as {@link AnnotatedTypeMirror}s.
+ */
+public class CFCFGBuilder extends CFGBuilder {
+  /** This class should never be instantiated. Protected to still allow subclasses. */
+  protected CFCFGBuilder() {}
+
+  /** Build the control flow graph of some code. */
+  public static ControlFlowGraph build(
+      CompilationUnitTree root,
+      UnderlyingAST underlyingAST,
+      BaseTypeChecker checker,
+      AnnotatedTypeFactory factory,
+      ProcessingEnvironment env) {
+    boolean assumeAssertionsEnabled = checker.hasOption("assumeAssertionsAreEnabled");
+    boolean assumeAssertionsDisabled = checker.hasOption("assumeAssertionsAreDisabled");
+    if (assumeAssertionsEnabled && assumeAssertionsDisabled) {
+      throw new UserError(
+          "Assertions cannot be assumed to be enabled and disabled at the same time.");
+    }
+
+    // Subcheckers with dataflow share control-flow graph structure to
+    // allow a super-checker to query the stores of a subchecker.
+    if (factory instanceof GenericAnnotatedTypeFactory) {
+      GenericAnnotatedTypeFactory<?, ?, ?, ?> asGATF =
+          (GenericAnnotatedTypeFactory<?, ?, ?, ?>) factory;
+      if (asGATF.hasOrIsSubchecker) {
+        ControlFlowGraph sharedCFG = asGATF.getSharedCFGForTree(underlyingAST.getCode());
+        if (sharedCFG != null) {
+          return sharedCFG;
+        }
+      }
+    }
+
+    CFTreeBuilder builder = new CFTreeBuilder(env);
+    PhaseOneResult phase1result =
+        new CFCFGTranslationPhaseOne(
+                builder, checker, factory, assumeAssertionsEnabled, assumeAssertionsDisabled, env)
+            .process(root, underlyingAST);
+    ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result);
+    ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result);
+    if (factory instanceof GenericAnnotatedTypeFactory) {
+      GenericAnnotatedTypeFactory<?, ?, ?, ?> asGATF =
+          (GenericAnnotatedTypeFactory<?, ?, ?, ?>) factory;
+      if (asGATF.hasOrIsSubchecker) {
+        asGATF.addSharedCFGForTree(underlyingAST.getCode(), phase3result);
+      }
+    }
+    return phase3result;
+  }
+
+  /**
+   * Given a SourceChecker and an AssertTree, returns whether the AssertTree uses
+   * an @AssumeAssertion string that is relevant to the SourceChecker.
+   *
+   * @param checker the checker
+   * @param tree an assert tree
+   * @return true if the assert tree contains an @AssumeAssertion(checker) message string for any
+   *     subchecker of the given checker's ultimate parent checker
+   */
+  public static boolean assumeAssertionsActivatedForAssertTree(
+      BaseTypeChecker checker, AssertTree tree) {
+    ExpressionTree detail = tree.getDetail();
+    if (detail != null) {
+      String msg = detail.toString();
+      BaseTypeChecker ultimateParent = checker.getUltimateParentChecker();
+      Collection<String> prefixes = ultimateParent.getSuppressWarningsPrefixesOfSubcheckers();
+      for (String prefix : prefixes) {
+        String assumeAssert = "@AssumeAssertion(" + prefix + ")";
+        if (msg.contains(assumeAssert)) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * A specialized phase-one CFG builder, with a few modifications that make use of the type
+   * factory. It is responsible for: 1) translating foreach loops so that the declarations of their
+   * iteration variables have the right annotations, 2) registering the containing elements of
+   * artificial trees with the relevant type factories, and 3) generating appropriate assertion CFG
+   * structure in the presence of @AssumeAssertion assertion strings which mention the checker or
+   * its supercheckers.
+   */
+  protected static class CFCFGTranslationPhaseOne extends CFGTranslationPhaseOne {
+    /** The associated checker. */
+    protected final BaseTypeChecker checker;
+
+    /** Type factory to provide types used during CFG building. */
+    protected final AnnotatedTypeFactory factory;
+
+    public CFCFGTranslationPhaseOne(
+        CFTreeBuilder builder,
+        BaseTypeChecker checker,
+        AnnotatedTypeFactory factory,
+        boolean assumeAssertionsEnabled,
+        boolean assumeAssertionsDisabled,
+        ProcessingEnvironment env) {
+      super(builder, factory, assumeAssertionsEnabled, assumeAssertionsDisabled, env);
+      this.checker = checker;
+      this.factory = factory;
+    }
+
+    @Override
+    protected boolean assumeAssertionsEnabledFor(AssertTree tree) {
+      if (assumeAssertionsActivatedForAssertTree(checker, tree)) {
+        return true;
+      }
+      return super.assumeAssertionsEnabledFor(tree);
+    }
+
+    @Override
+    public void handleArtificialTree(Tree tree) {
+      MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath());
+      if (enclosingMethod != null) {
+        Element methodElement = TreeUtils.elementFromDeclaration(enclosingMethod);
+        factory.setEnclosingElementForArtificialTree(tree, methodElement);
+      } else {
+        ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath());
+        if (enclosingClass != null) {
+          Element classElement = TreeUtils.elementFromDeclaration(enclosingClass);
+          factory.setEnclosingElementForArtificialTree(tree, classElement);
+        }
+      }
+    }
+
+    @Override
+    protected VariableTree createEnhancedForLoopIteratorVariable(
+        MethodInvocationTree iteratorCall, VariableElement variableElement) {
+      Tree annotatedIteratorTypeTree =
+          ((CFTreeBuilder) treeBuilder).buildAnnotatedType(TreeUtils.typeOf(iteratorCall));
+      handleArtificialTree(annotatedIteratorTypeTree);
+
+      // Declare and initialize a new, unique iterator variable
+      VariableTree iteratorVariable =
+          treeBuilder.buildVariableDecl(
+              annotatedIteratorTypeTree,
+              uniqueName("iter"),
+              variableElement.getEnclosingElement(),
+              iteratorCall);
+      return iteratorVariable;
+    }
+
+    @Override
+    protected VariableTree createEnhancedForLoopArrayVariable(
+        ExpressionTree expression, VariableElement variableElement) {
+
+      TypeMirror type = null;
+      if (TreeUtils.isLocalVariable(expression)) {
+        // It is necessary to get the elt because just getting the type of expression
+        // directly (via TreeUtils.typeOf) doesn't include annotations on the declarations
+        // of local variables, for some reason.
+        Element elt = TreeUtils.elementFromTree(expression);
+        if (elt != null) {
+          type = ElementUtils.getType(elt);
+        }
+      }
+
+      // In all other cases cases, instead get the type of the expression. This case is
+      // also triggered when the type from the element is not an array, which can occur
+      // if the declaration of the local is a generic, such as in
+      // framework/tests/all-systems/java8inference/Issue1775.java.
+      // Getting the type from the expression itself guarantees the result will be an array.
+      if (type == null || type.getKind() != TypeKind.ARRAY) {
+        TypeMirror expressionType = TreeUtils.typeOf(expression);
+        type = expressionType;
+      }
+
+      assert (type instanceof ArrayType) : "array types must be represented by ArrayType";
+
+      Tree annotatedArrayTypeTree = ((CFTreeBuilder) treeBuilder).buildAnnotatedType(type);
+      handleArtificialTree(annotatedArrayTypeTree);
+
+      // Declare and initialize a temporary array variable
+      VariableTree arrayVariable =
+          treeBuilder.buildVariableDecl(
+              annotatedArrayTypeTree,
+              uniqueName("array"),
+              variableElement.getEnclosingElement(),
+              expression);
+      return arrayVariable;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFStore.java
new file mode 100644
index 0000000..b802442
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/flow/CFStore.java
@@ -0,0 +1,18 @@
+package org.checkerframework.framework.flow;
+
+/** The default store used in the Checker Framework. */
+public class CFStore extends CFAbstractStore<CFValue, CFStore> {
+
+  public CFStore(CFAbstractAnalysis<CFValue, CFStore, ?> analysis, boolean sequentialSemantics) {
+    super(analysis, sequentialSemantics);
+  }
+
+  /**
+   * Copy constructor.
+   *
+   * @param other the CFStore to copy
+   */
+  public CFStore(CFAbstractStore<CFValue, CFStore> other) {
+    super(other);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFTransfer.java b/framework/src/main/java/org/checkerframework/framework/flow/CFTransfer.java
new file mode 100644
index 0000000..55905ac
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/flow/CFTransfer.java
@@ -0,0 +1,9 @@
+package org.checkerframework.framework.flow;
+
+/** The default transfer function used in the Checker Framework. */
+public class CFTransfer extends CFAbstractTransfer<CFValue, CFStore, CFTransfer> {
+
+  public CFTransfer(CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
+    super(analysis);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFTreeBuilder.java b/framework/src/main/java/org/checkerframework/framework/flow/CFTreeBuilder.java
new file mode 100644
index 0000000..fef48a6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/flow/CFTreeBuilder.java
@@ -0,0 +1,205 @@
+package org.checkerframework.framework.flow;
+
+import com.sun.source.tree.Tree;
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.BoundKind;
+import com.sun.tools.javac.code.Symbol.TypeSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.TypeTag;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCAnnotatedType;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCTypeApply;
+import com.sun.tools.javac.util.List;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+import org.checkerframework.javacutil.TypeAnnotationUtils;
+import org.checkerframework.javacutil.trees.TreeBuilder;
+
+/**
+ * The TreeBuilder permits the creation of new AST Trees using the non-public Java compiler API
+ * TreeMaker. Initially, it will support construction of desugared Trees required by the CFGBuilder,
+ * e.g. the pieces of a desugared enhanced for loop.
+ */
+public class CFTreeBuilder extends TreeBuilder {
+
+  /**
+   * To avoid infinite recursions, record each wildcard that has been converted to a tree. This set
+   * is cleared each time {@link #buildAnnotatedType(TypeMirror)} is called.
+   */
+  private final Set<WildcardType> visitedWildcards = new HashSet<>();
+
+  /**
+   * Creates a {@code CFTreeBuilder}.
+   *
+   * @param env environment
+   */
+  public CFTreeBuilder(ProcessingEnvironment env) {
+    super(env);
+  }
+
+  /**
+   * Builds an AST Tree representing a type, including AnnotationTrees for its annotations.
+   *
+   * @param type the type
+   * @return a Tree representing the type
+   */
+  public Tree buildAnnotatedType(TypeMirror type) {
+    visitedWildcards.clear();
+    return createAnnotatedType(type);
+  }
+
+  /**
+   * Converts a list of AnnotationMirrors to the a corresponding list of new AnnotationTrees.
+   *
+   * @param annotations the annotations
+   * @return new annotation trees representing the annotations
+   */
+  private List<JCAnnotation> convertAnnotationMirrorsToAnnotationTrees(
+      Collection<? extends AnnotationMirror> annotations) {
+    List<JCAnnotation> annotationTrees = List.nil();
+
+    for (AnnotationMirror am : annotations) {
+      // TODO: what TypeAnnotationPosition should be used?
+      Attribute.TypeCompound typeCompound =
+          TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror(
+              am, TypeAnnotationUtils.unknownTAPosition(), env);
+      JCAnnotation annotationTree = maker.Annotation(typeCompound);
+      JCAnnotation typeAnnotationTree =
+          maker.TypeAnnotation(annotationTree.getAnnotationType(), annotationTree.getArguments());
+
+      typeAnnotationTree.attribute = typeCompound;
+
+      annotationTrees = annotationTrees.append(typeAnnotationTree);
+    }
+    return annotationTrees;
+  }
+
+  /**
+   * Builds an AST Tree representing a type, including AnnotationTrees for its annotations. This
+   * internal method differs from the public {@link #buildAnnotatedType(TypeMirror)} only in that it
+   * does not reset the list of visited wildcards.
+   *
+   * @param type the type for which to create a tree
+   * @return a Tree representing the type
+   */
+  private Tree createAnnotatedType(TypeMirror type) {
+    // Implementation based on com.sun.tools.javac.tree.TreeMaker.Type
+
+    // Convert the annotations from a set of AnnotationMirrors
+    // to a list of AnnotationTrees.
+    java.util.List<? extends AnnotationMirror> annotations = type.getAnnotationMirrors();
+    List<JCAnnotation> annotationTrees = convertAnnotationMirrorsToAnnotationTrees(annotations);
+
+    // Convert the underlying type from a TypeMirror to an ExpressionTree and combine with the
+    // AnnotationTrees to form a ClassTree of kind ANNOTATION_TYPE.
+    JCExpression typeTree;
+    switch (type.getKind()) {
+      case BYTE:
+        typeTree = maker.TypeIdent(TypeTag.BYTE);
+        break;
+      case CHAR:
+        typeTree = maker.TypeIdent(TypeTag.CHAR);
+        break;
+      case SHORT:
+        typeTree = maker.TypeIdent(TypeTag.SHORT);
+        break;
+      case INT:
+        typeTree = maker.TypeIdent(TypeTag.INT);
+        break;
+      case LONG:
+        typeTree = maker.TypeIdent(TypeTag.LONG);
+        break;
+      case FLOAT:
+        typeTree = maker.TypeIdent(TypeTag.FLOAT);
+        break;
+      case DOUBLE:
+        typeTree = maker.TypeIdent(TypeTag.DOUBLE);
+        break;
+      case BOOLEAN:
+        typeTree = maker.TypeIdent(TypeTag.BOOLEAN);
+        break;
+      case VOID:
+        typeTree = maker.TypeIdent(TypeTag.VOID);
+        break;
+      case TYPEVAR:
+        // No recursive annotations.
+        TypeVariable underlyingTypeVar = (TypeVariable) type;
+        typeTree = maker.Ident((TypeSymbol) underlyingTypeVar.asElement());
+        break;
+      case WILDCARD:
+        WildcardType wildcardType = (WildcardType) type;
+        boolean visitedBefore = !visitedWildcards.add(wildcardType);
+        if (!visitedBefore && wildcardType.getExtendsBound() != null) {
+          Tree annotatedExtendsBound = createAnnotatedType(wildcardType.getExtendsBound());
+          typeTree =
+              maker.Wildcard(
+                  maker.TypeBoundKind(BoundKind.EXTENDS), (JCTree) annotatedExtendsBound);
+        } else if (!visitedBefore && wildcardType.getSuperBound() != null) {
+          Tree annotatedSuperBound = createAnnotatedType(wildcardType.getSuperBound());
+          typeTree =
+              maker.Wildcard(maker.TypeBoundKind(BoundKind.SUPER), (JCTree) annotatedSuperBound);
+        } else {
+          typeTree = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null);
+        }
+        break;
+      case INTERSECTION:
+        IntersectionType intersectionType = (IntersectionType) type;
+        List<JCExpression> components = List.nil();
+        for (TypeMirror bound : intersectionType.getBounds()) {
+          components = components.append((JCExpression) createAnnotatedType(bound));
+        }
+        typeTree = maker.TypeIntersection(components);
+        break;
+        // case UNION:
+        // TODO: case UNION similar to INTERSECTION, but write test first.
+      case DECLARED:
+        typeTree = maker.Type((Type) type);
+
+        if (typeTree instanceof JCTypeApply) {
+          // Replace the type parameters with annotated versions.
+          DeclaredType annotatedDeclaredType = (DeclaredType) type;
+          List<JCExpression> typeArgTrees = List.nil();
+          for (TypeMirror arg : annotatedDeclaredType.getTypeArguments()) {
+            typeArgTrees = typeArgTrees.append((JCExpression) createAnnotatedType(arg));
+          }
+          JCExpression clazz = (JCExpression) ((JCTypeApply) typeTree).getType();
+          typeTree = maker.TypeApply(clazz, typeArgTrees);
+        }
+        break;
+      case ARRAY:
+        ArrayType arrayType = (ArrayType) type;
+        Tree componentTree = createAnnotatedType(arrayType.getComponentType());
+        typeTree = maker.TypeArray((JCExpression) componentTree);
+        break;
+      case ERROR:
+        typeTree = maker.TypeIdent(TypeTag.ERROR);
+        break;
+      default:
+        assert false : "unexpected type: " + type;
+        typeTree = null;
+        break;
+    }
+
+    typeTree.setType((Type) type);
+
+    if (annotationTrees.isEmpty()) {
+      return typeTree;
+    }
+
+    JCAnnotatedType annotatedTypeTree = maker.AnnotatedType(annotationTrees, typeTree);
+    annotatedTypeTree.setType((Type) type);
+
+    return annotatedTypeTree;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFValue.java b/framework/src/main/java/org/checkerframework/framework/flow/CFValue.java
new file mode 100644
index 0000000..2a36201
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/flow/CFValue.java
@@ -0,0 +1,18 @@
+package org.checkerframework.framework.flow;
+
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * The default abstract value used in the Checker Framework: a set of annotations and a TypeMirror.
+ */
+public class CFValue extends CFAbstractValue<CFValue> {
+
+  public CFValue(
+      CFAbstractAnalysis<CFValue, ?, ?> analysis,
+      Set<AnnotationMirror> annotations,
+      TypeMirror underlyingType) {
+    super(analysis, annotations, underlyingType);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/flow/package-info.java b/framework/src/main/java/org/checkerframework/framework/flow/package-info.java
new file mode 100644
index 0000000..52f8f24
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/flow/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Contains an extension of the general org.checkerframework.dataflow framework that is specific to
+ * the Checker Framework and can be used by any checker.
+ */
+package org.checkerframework.framework.flow;
diff --git a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java
new file mode 100644
index 0000000..d8f5774
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java
@@ -0,0 +1,174 @@
+package org.checkerframework.framework.source;
+
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Log;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic.Kind;
+
+/**
+ * An aggregate checker that packages multiple checkers together. The resulting checker invokes the
+ * component checkers in turn on the processed files.
+ *
+ * <p>There is no communication, interaction, or cooperation between the component checkers, even to
+ * the extent of being able to read one another's qualifiers. An aggregate checker is merely
+ * shorthand to invoke a sequence of checkers.
+ *
+ * <p>This class delegates {@code AbstractTypeProcessor} responsibilities to each component checker.
+ *
+ * <p>Checker writers need to subclass this class and only override {@link #getSupportedCheckers()}
+ * to indicate the classes of the checkers to be bundled.
+ */
+public abstract class AggregateChecker extends SourceChecker {
+
+  protected final List<SourceChecker> checkers;
+
+  /**
+   * Returns the list of supported checkers to be run together. Subclasses need to override this
+   * method.
+   */
+  protected abstract Collection<Class<? extends SourceChecker>> getSupportedCheckers();
+
+  /** Create a new AggregateChecker. */
+  protected AggregateChecker() {
+    Collection<Class<? extends SourceChecker>> checkerClasses = getSupportedCheckers();
+
+    checkers = new ArrayList<>(checkerClasses.size());
+    for (Class<? extends SourceChecker> checkerClass : checkerClasses) {
+      try {
+        SourceChecker instance = checkerClass.getDeclaredConstructor().newInstance();
+        instance.setParentChecker(this);
+        checkers.add(instance);
+      } catch (Exception e) {
+        message(Kind.ERROR, "Couldn't instantiate an instance of " + checkerClass);
+      }
+    }
+  }
+
+  /**
+   * processingEnv needs to be set on each checker since we are not calling init on the checker,
+   * which leaves it null. If one of checkers is an AggregateChecker, its visitors will try use
+   * checker's processing env which should not be null.
+   */
+  @Override
+  protected void setProcessingEnvironment(ProcessingEnvironment env) {
+    super.setProcessingEnvironment(env);
+    for (SourceChecker checker : checkers) {
+      checker.setProcessingEnvironment(env);
+    }
+  }
+
+  @Override
+  public void initChecker() {
+    // No need to call super, it might result in reflective instantiations
+    // of visitor/factory classes.
+    // super.initChecker();
+    // To prevent the warning that initChecker wasn't called.
+    messager = processingEnv.getMessager();
+
+    // first initialize all checkers
+    for (SourceChecker checker : checkers) {
+      checker.initChecker();
+    }
+    // then share options as necessary
+    for (SourceChecker checker : checkers) {
+      // We need to add all options that are activated for the aggregate to
+      // the individual checkers.
+      checker.addOptions(super.getOptions());
+      // Each checker should "support" all possible lint options - otherwise
+      // subchecker A would complain about a lint option for subchecker B.
+      checker.setSupportedLintOptions(this.getSupportedLintOptions());
+    }
+    allCheckersInited = true;
+  }
+
+  // Whether all checkers were successfully initialized.
+  private boolean allCheckersInited = false;
+
+  // AbstractTypeProcessor delegation
+  @Override
+  public final void typeProcess(TypeElement element, TreePath tree) {
+    Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
+    Log log = Log.instance(context);
+    if (log.nerrors > this.errsOnLastExit) {
+      // If there is a Java error, do not perform any of the component type checks, but come back
+      // for the next compilation unit.
+      this.errsOnLastExit = log.nerrors;
+      return;
+    }
+    if (!allCheckersInited) {
+      // If there was an initialization problem, an
+      // error was already output. Just quit.
+      return;
+    }
+    for (SourceChecker checker : checkers) {
+      checker.errsOnLastExit = this.errsOnLastExit;
+      checker.typeProcess(element, tree);
+      if (checker.javacErrored) {
+        this.javacErrored = true;
+        return;
+      }
+      this.errsOnLastExit = checker.errsOnLastExit;
+    }
+  }
+
+  @Override
+  public void typeProcessingOver() {
+    for (SourceChecker checker : checkers) {
+      checker.typeProcessingOver();
+    }
+    super.typeProcessingOver();
+  }
+
+  @Override
+  public final Set<String> getSupportedOptions() {
+    Set<String> options = new HashSet<>();
+    for (SourceChecker checker : checkers) {
+      options.addAll(checker.getSupportedOptions());
+    }
+    options.addAll(expandCFOptions(Arrays.asList(this.getClass()), options.toArray(new String[0])));
+    return options;
+  }
+
+  @Override
+  public final Map<String, String> getOptions() {
+    Map<String, String> options = new HashMap<>(super.getOptions());
+    for (SourceChecker checker : checkers) {
+      options.putAll(checker.getOptions());
+    }
+    return options;
+  }
+
+  @Override
+  public final Set<String> getSupportedLintOptions() {
+    Set<String> lints = new HashSet<>();
+    for (SourceChecker checker : checkers) {
+      lints.addAll(checker.getSupportedLintOptions());
+    }
+    return lints;
+  }
+
+  @Override
+  protected SourceVisitor<?, ?> createSourceVisitor() {
+    return new SourceVisitor<Void, Void>(this) {
+      // Aggregate checkers do not visit source,
+      // the checkers in the aggregate checker do.
+    };
+  }
+
+  // TODO some methods in a component checker should behave differently if they
+  // are part of an aggregate, e.g. getSuppressWarningKeys should additionally
+  // return the name of the aggregate checker.
+  // We could add a query method in SourceChecker that refers to the aggregate, if present.
+  // At the moment, all the component checkers manually need to add the name of the aggregate.
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java b/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java
new file mode 100644
index 0000000..b283b0c
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java
@@ -0,0 +1,121 @@
+package org.checkerframework.framework.source;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import javax.tools.Diagnostic.Kind;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+/**
+ * A {@code DiagMessage} is a kind, a message key, and arguments. The message key will be expanded
+ * according to the user locale. Any arguments will then be interpolated into the localized message.
+ *
+ * <p>By contrast, {@code javax.tools.Diagnostic} has just a string message.
+ */
+@AnnotatedFor("nullness")
+public class DiagMessage {
+  /** The kind of message. */
+  private final Kind kind;
+  /** The message key. */
+  private final @CompilerMessageKey String messageKey;
+  /** The arguments that will be interpolated into the localized message. */
+  private final Object[] args;
+
+  /**
+   * Create a DiagMessage.
+   *
+   * @param kind the kind of message
+   * @param messageKey the message key
+   * @param args the arguments that will be interpolated into the localized message
+   */
+  public DiagMessage(Kind kind, @CompilerMessageKey String messageKey, Object... args) {
+    this.kind = kind;
+    this.messageKey = messageKey;
+    if (args == null) {
+      this.args = new Object[0]; /*null->nn*/
+    } else {
+      this.args = Arrays.copyOf(args, args.length);
+    }
+  }
+
+  /**
+   * Returns the kind of this DiagMessage.
+   *
+   * @return the kind of this DiagMessage
+   */
+  public Kind getKind() {
+    return this.kind;
+  }
+
+  /**
+   * Returns the message key of this DiagMessage.
+   *
+   * @return the message key of this DiagMessage
+   */
+  public @CompilerMessageKey String getMessageKey() {
+    return this.messageKey;
+  }
+
+  /**
+   * Returns the customized optional arguments for the message.
+   *
+   * @return the customized optional arguments for the message
+   */
+  public Object[] getArgs() {
+    return this.args;
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof DiagMessage)) {
+      return false;
+    }
+
+    DiagMessage other = (DiagMessage) obj;
+
+    return (kind == other.kind
+        && messageKey.equals(other.messageKey)
+        && Arrays.equals(args, other.args));
+  }
+
+  @Pure
+  @Override
+  public int hashCode() {
+    return Objects.hash(kind, messageKey, Arrays.hashCode(args));
+  }
+
+  @SideEffectFree
+  @Override
+  public String toString() {
+    if (args.length == 0) {
+      return messageKey;
+    }
+
+    return kind + messageKey + " : " + Arrays.toString(args);
+  }
+
+  /**
+   * Returns the concatenation of the lists.
+   *
+   * @param list1 a list of DiagMessage, or null
+   * @param list2 a list of DiagMessage, or null
+   * @return the concatenation of the lists
+   */
+  public static List<DiagMessage> mergeLists(List<DiagMessage> list1, List<DiagMessage> list2) {
+    if (list1 == null || list1.isEmpty()) {
+      return list2;
+    } else if (list2 == null || list2.isEmpty()) {
+      return list1;
+    } else {
+      List<DiagMessage> result = new ArrayList<>(list1.size() + list2.size());
+      result.addAll(list1);
+      result.addAll(list2);
+      return result;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java
new file mode 100644
index 0000000..1c3cb9a
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java
@@ -0,0 +1,2780 @@
+package org.checkerframework.framework.source;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.SourcePositions;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.code.Source;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.DiagnosticSource;
+import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
+import com.sun.tools.javac.util.Log;
+import io.github.classgraph.ClassGraph;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryPoolMXBean;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+import java.net.URI;
+import java.time.Instant;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.StringJoiner;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+import javax.tools.Diagnostic.Kind;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.util.CheckerMain;
+import org.checkerframework.framework.util.OptionConfiguration;
+import org.checkerframework.javacutil.AbstractTypeProcessor;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.SystemUtil;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypeSystemError;
+import org.checkerframework.javacutil.UserError;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.SystemPlume;
+import org.plumelib.util.UtilPlume;
+
+/**
+ * An abstract annotation processor designed for implementing a source-file checker as an annotation
+ * processor (a compiler plug-in). It provides an interface to {@code javac}'s annotation processing
+ * API, routines for error reporting via the JSR 199 compiler API, and an implementation for using a
+ * {@link SourceVisitor} to perform the type-checking.
+ *
+ * <p>Most type-checker plug-ins should extend {@link BaseTypeChecker}, instead of this class. Only
+ * checkers that require annotated types but not subtype checking (e.g. for testing purposes) should
+ * extend this. Non-type checkers (e.g. for enforcing coding styles) may extend {@link
+ * AbstractProcessor} (or even this class).
+ */
+@SupportedOptions({
+  // When adding a new standard option:
+  // 1. Add a brief blurb here about the use case
+  //    and a pointer to one prominent use of the option.
+  // 2. Update the Checker Framework manual:
+  //     * docs/manual/introduction.tex contains a list of all options,
+  //       which should be in the same order as this source code file.
+  //     * a specific section should contain a detailed discussion.
+
+  ///
+  /// Unsound checking: ignore some errors
+  ///
+
+  // A comma-separated list of warnings to suppress
+  // org.checkerframework.framework.source.SourceChecker.createSuppressWarnings
+  "suppressWarnings",
+
+  // Set inclusion/exclusion of type uses or definitions
+  // org.checkerframework.framework.source.SourceChecker.shouldSkipUses and similar
+  "skipUses",
+  "onlyUses",
+  "skipDefs",
+  "onlyDefs",
+
+  // Unsoundly assume all methods have no side effects, are deterministic, or both.
+  "assumeSideEffectFree",
+  "assumeDeterministic",
+  "assumePure",
+
+  // Whether to assume that assertions are enabled or disabled
+  // org.checkerframework.framework.flow.CFCFGBuilder.CFCFGBuilder
+  "assumeAssertionsAreEnabled",
+  "assumeAssertionsAreDisabled",
+
+  // Treat checker errors as warnings
+  // org.checkerframework.framework.source.SourceChecker.report
+  "warns",
+
+  ///
+  /// More sound (strict checking): enable errors that are disabled by default
+  ///
+
+  // The next ones *increase* rather than *decrease* soundness.  They will eventually be replaced by
+  // their complements (except -AconcurrentSemantics) and moved into the above section.
+
+  // TODO: Checking of bodies of @SideEffectFree, @Deterministic, and
+  // @Pure methods is temporarily disabled unless -AcheckPurityAnnotations is
+  // supplied on the command line.
+  // Re-enable it after making the analysis more precise.
+  // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void)
+  "checkPurityAnnotations",
+
+  // TODO: Temporary option to make array subtyping invariant,
+  // which will be the new default soon.
+  "invariantArrays",
+
+  // TODO:  Temporary option to make casts stricter, in particular when
+  // casting to an array or generic type. This will be the new default soon.
+  "checkCastElementType",
+
+  // Whether to use conservative defaults for bytecode and/or source code.
+  // This option takes arguments "source" and/or "bytecode".
+  // The default is "-source,-bytecode" (eventually this will be changed to "-source,bytecode").
+  // Note, in source code, conservative defaults are never
+  // applied to code in the scope of an @AnnotatedFor.
+  // See the "Compiling partially-annotated libraries" and
+  // "Default qualifiers for \<.class> files (conservative library defaults)"
+  // sections in the manual for more details
+  // org.checkerframework.framework.source.SourceChecker.useConservativeDefault
+  "useConservativeDefaultsForUncheckedCode",
+  // Temporary, for backward compatibility
+  "useDefaultsForUncheckedCode",
+
+  // Whether to assume sound concurrent semantics or
+  // simplified sequential semantics
+  // org.checkerframework.framework.flow.CFAbstractTransfer.sequentialSemantics
+  "concurrentSemantics",
+
+  // Whether to use a conservative value for type arguments that could not be inferred.
+  // See Issue 979.
+  "conservativeUninferredTypeArguments",
+
+  // Whether to ignore all subtype tests for type arguments that
+  // were inferred for a raw type. Defaults to true.
+  // org.checkerframework.framework.type.TypeHierarchy.isSubtypeTypeArguments
+  "ignoreRawTypeArguments",
+
+  ///
+  /// Type-checking modes:  enable/disable functionality
+  ///
+
+  // Lint options
+  // org.checkerframework.framework.source.SourceChecker.getSupportedLintOptions() and similar
+  "lint",
+
+  // Whether to suggest methods that could be marked @SideEffectFree,
+  // @Deterministic, or @Pure
+  // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void)
+  "suggestPureMethods",
+
+  // Whether to resolve reflective method invocations.
+  // "-AresolveReflection=debug" causes debugging information
+  // to be output.
+  "resolveReflection",
+
+  // Whether to use whole-program inference. Takes an argument to specify the output format:
+  // "-Ainfer=stubs" or "-Ainfer=jaifs".
+  "infer",
+
+  // With each warning, in addition to the concrete error key,
+  // output the SuppressWarnings strings that can be used to
+  // suppress that warning.
+  "showSuppressWarningsStrings",
+
+  // Warn about @SuppressWarnings annotations that do not suppress any warnings.
+  // org.checkerframework.common.basetype.BaseTypeChecker.warnUnneededSuppressions
+  // org.checkerframework.framework.source.SourceChecker.warnUnneededSuppressions
+  // org.checkerframework.framework.source.SourceChecker.shouldSuppressWarnings(javax.lang.model.element.Element, java.lang.String)
+  // org.checkerframework.framework.source.SourceVisitor.checkForSuppressWarningsAnno
+  "warnUnneededSuppressions",
+
+  // Exceptions to -AwarnUnneededSuppressions.
+  "warnUnneededSuppressionsExceptions",
+
+  // Require that warning suppression annotations contain a checker key as a prefix in order for
+  // the warning to be suppressed.
+  // org.checkerframework.framework.source.SourceChecker.checkSuppressWarnings(java.lang.String[],
+  // java.lang.String)
+  "requirePrefixInWarningSuppressions",
+
+  // Ignore annotations in bytecode that have invalid annotation locations.
+  // See https://github.com/typetools/checker-framework/issues/2173
+  // org.checkerframework.framework.type.ElementAnnotationApplier.apply
+  "ignoreInvalidAnnotationLocations",
+
+  ///
+  /// Partially-annotated libraries
+  ///
+
+  // Additional stub files to use
+  // org.checkerframework.framework.type.AnnotatedTypeFactory.parseStubFiles()
+  "stubs",
+  // Additional ajava files to use
+  // org.checkerframework.framework.type.AnnotatedTypeFactory.parserAjavaFiles()
+  "ajava",
+  // Whether to print warnings about types/members in a stub file
+  // that were not found on the class path
+  // org.checkerframework.framework.stub.AnnotationFileParser.warnIfNotFound
+  "stubWarnIfNotFound",
+  // Whether to ignore missing classes even when warnIfNotFound is set to true and other classes
+  // from the same package are present (useful if a package spans more than one jar).
+  // org.checkerframework.framework.stub.AnnotationFileParser.warnIfNotFoundIgnoresClasses
+  "stubWarnIfNotFoundIgnoresClasses",
+  // Whether to print warnings about stub files that overwrite annotations from bytecode.
+  "stubWarnIfOverwritesBytecode",
+  // Whether to print warnings about stub files that are redundant with the annotations from
+  // bytecode.
+  "stubWarnIfRedundantWithBytecode",
+  // Whether to issue a NOTE rather than a WARNING for -AstubWarn* command-line options
+  "stubWarnNote",
+  // With this option, annotations in stub files are used EVEN IF THE SOURCE FILE IS
+  // PRESENT. Only use this option when you intend to store types in stub files rather than
+  // directly in source code, such as during whole-program inference. The annotations in the
+  // stub files will be glb'd with those in the source code before local inference begins.
+  "mergeStubsWithSource",
+  // Already listed above, but worth noting again in this section:
+  // "useConservativeDefaultsForUncheckedCode"
+
+  ///
+  /// Debugging
+  ///
+
+  /// Amount of detail in messages
+
+  // Print the version of the Checker Framework
+  "version",
+  // Print info about git repository from which the Checker Framework was compiled
+  "printGitProperties",
+
+  // Whether to print @InvisibleQualifier marked annotations
+  // org.checkerframework.framework.type.AnnotatedTypeMirror.toString()
+  "printAllQualifiers",
+
+  // Whether to print [] around a set of type parameters in order to clearly see where they end
+  // e.g.  <E extends F, F extends Object>
+  // without this option the E is printed: E extends F extends Object
+  // with this option:                     E [ extends F [ extends Object super Void ] super Void ]
+  // when multiple type variables are used this becomes useful very quickly
+  "printVerboseGenerics",
+
+  // Whether to NOT output a stack trace for each framework error.
+  // org.checkerframework.framework.source.SourceChecker.logBugInCF
+  "noPrintErrorStack",
+
+  // Only output error code, useful for testing framework
+  // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...)
+  "nomsgtext",
+
+  /// Format of messages
+
+  // Output detailed message in simple-to-parse format, useful
+  // for tools parsing Checker Framework output.
+  // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...)
+  "detailedmsgtext",
+
+  /// Stub and JDK libraries
+
+  // Ignore the standard jdk.astub file; primarily for testing or debugging.
+  // org.checkerframework.framework.type.AnnotatedTypeFactory.parseStubFiles()
+  "ignorejdkastub",
+
+  // Whether to check that the annotated JDK is correctly provided
+  // org.checkerframework.common.basetype.BaseTypeVisitor.checkForAnnotatedJdk()
+  "permitMissingJdk",
+  "nocheckjdk", // temporary, for backward compatibility
+
+  // Parse all JDK files at startup rather than as needed.
+  // org.checkerframework.framework.stub.AnnotationFileElementTypes.AnnotationFileElementTypes
+  "parseAllJdk",
+
+  // Whether to print debugging messages while processing the stub files
+  // org.checkerframework.framework.stub.AnnotationFileParser.debugAnnotationFileParser
+  "stubDebug",
+
+  /// Progress tracing
+
+  // Output file names before checking
+  // org.checkerframework.framework.source.SourceChecker.typeProcess()
+  "filenames",
+
+  // Output all subtyping checks
+  // org.checkerframework.common.basetype.BaseTypeVisitor
+  "showchecks",
+
+  // Output information about intermediate steps in method type argument inference
+  // org.checkerframework.framework.util.typeinference.DefaultTypeArgumentInference
+  "showInferenceSteps",
+
+  // Output a stack trace when reporting errors or warnings
+  // org.checkerframework.common.basetype.SourceChecker.printStackTrace()
+  "dumpOnErrors",
+
+  /// Visualizing the CFG
+
+  // Implemented in the wrapper rather than this file, but worth noting here.
+  // -AoutputArgsToFile
+
+  // Mechanism to visualize the control flow graph (CFG).
+  // The argument is a sequence of values or key-value pairs.
+  // The first argument has to be the fully-qualified name of the
+  // org.checkerframework.dataflow.cfg.CFGVisualizer implementation that should be used. The
+  // remaining values or key-value pairs are passed to CFGVisualizer.init.
+  // For example:
+  //    -Acfgviz=MyViz,a,b=c,d
+  // instantiates class MyViz and calls CFGVisualizer.init
+  // with {"a" -> true, "b" -> "c", "d" -> true}.
+  "cfgviz",
+
+  // Directory for .dot files generated from the CFG visualization in
+  // org.checkerframework.dataflow.cfg.DOTCFGVisualizer
+  // as initialized by
+  // org.checkerframework.framework.type.GenericAnnotatedTypeFactory.createCFGVisualizer()
+  // -Aflowdotdir=xyz
+  // is short-hand for
+  // -Acfgviz=org.checkerframework.dataflow.cfg.DOTCFGVisualizer,outdir=xyz
+  "flowdotdir",
+
+  // Enable additional output in the CFG visualization.
+  // -Averbosecfg
+  // is short-hand for
+  // -Acfgviz=MyClass,verbose
+  "verbosecfg",
+
+  /// Caches
+
+  // Set the cache size for caches in AnnotatedTypeFactory
+  "atfCacheSize",
+
+  // Sets AnnotatedTypeFactory shouldCache to false
+  "atfDoNotCache",
+
+  /// Miscellaneous debugging options
+
+  // Whether to output resource statistics at JVM shutdown
+  // org.checkerframework.framework.source.SourceChecker.shutdownHook()
+  "resourceStats",
+
+  // Parse all JDK files at startup rather than as needed.
+  "parseAllJdk",
+
+  // Run checks that test ajava files.
+  //
+  // Whenever processing a source file, parse it with JavaParser and check that the AST can be
+  // matched with javac's tree. Crash if not. For testing the class JointJavacJavaParserVisitor.
+  //
+  // Also checks that annotations can be inserted. For each Java file, clears all annotations and
+  // reinserts them, then checks if the original and modified ASTs are equivalent.
+  "ajavaChecks",
+})
+public abstract class SourceChecker extends AbstractTypeProcessor implements OptionConfiguration {
+
+  // TODO A checker should export itself through a separate interface, and maybe have an interface
+  // for all the methods for which it's safe to override.
+
+  /** The line separator. */
+  private static final String LINE_SEPARATOR = System.lineSeparator().intern();
+
+  /** The message key that will suppress all warnings (it matches any message key). */
+  public static final String SUPPRESS_ALL_MESSAGE_KEY = "all";
+
+  /** The SuppressWarnings prefix that will suppress warnings for all checkers. */
+  public static final String SUPPRESS_ALL_PREFIX = "allcheckers";
+
+  /** The message key emitted when an unused warning suppression is found. */
+  public static final @CompilerMessageKey String UNNEEDED_SUPPRESSION_KEY = "unneeded.suppression";
+
+  /** File name of the localized messages. */
+  protected static final String MSGS_FILE = "messages.properties";
+
+  /**
+   * Maps error keys to localized/custom error messages. Do not use directly; call {@link
+   * #fullMessageOf} or {@link #processArg}.
+   */
+  protected Properties messagesProperties;
+
+  /** Used to report error messages and warnings via the compiler. */
+  protected Messager messager;
+
+  /** Used as a helper for the {@link SourceVisitor}. */
+  protected Trees trees;
+
+  /** The source tree that is being scanned. */
+  protected @InternedDistinct CompilationUnitTree currentRoot;
+
+  /** The visitor to use. */
+  protected SourceVisitor<?, ?> visitor;
+
+  /**
+   * Exceptions to -AwarnUnneededSuppressions processing. No warning about unneeded suppressions is
+   * issued if the SuppressWarnings string matches this pattern.
+   */
+  private @Nullable Pattern warnUnneededSuppressionsExceptions;
+
+  /**
+   * SuppressWarnings strings supplied via the -AsuppressWarnings option. Do not use directly, call
+   * {@link #getSuppressWarningsStringsFromOption()}.
+   */
+  private String @Nullable [] suppressWarningsStringsFromOption;
+
+  /**
+   * If true, use the "allcheckers:" warning string prefix.
+   *
+   * <p>Checkers that never issue any error messages should set this to false. That prevents {@code
+   * -AwarnUnneededSuppressions} from issuing warnings about
+   * {@code @SuppressWarnings("allcheckers:...")}.
+   */
+  protected boolean useAllcheckersPrefix = true;
+
+  /**
+   * Regular expression pattern to specify Java classes that are not annotated, so warnings about
+   * uses of them should be suppressed.
+   *
+   * <p>It contains the pattern specified by the user, through the option {@code checkers.skipUses};
+   * otherwise it contains a pattern that can match no class.
+   */
+  private Pattern skipUsesPattern;
+
+  /**
+   * Regular expression pattern to specify Java classes that are annotated, so warnings about them
+   * should be issued but warnings about all other classes should be suppressed.
+   *
+   * <p>It contains the pattern specified by the user, through the option {@code checkers.onlyUses};
+   * otherwise it contains a pattern that matches every class.
+   */
+  private Pattern onlyUsesPattern;
+
+  /**
+   * Regular expression pattern to specify Java classes whose definition should not be checked.
+   *
+   * <p>It contains the pattern specified by the user, through the option {@code checkers.skipDefs};
+   * otherwise it contains a pattern that can match no class.
+   */
+  private Pattern skipDefsPattern;
+
+  /**
+   * Regular expression pattern to specify Java classes whose definition should be checked.
+   *
+   * <p>It contains the pattern specified by the user, through the option {@code checkers.onlyDefs};
+   * otherwise it contains a pattern that matches every class.
+   */
+  private Pattern onlyDefsPattern;
+
+  /** The supported lint options. */
+  private Set<String> supportedLints;
+
+  /** The enabled lint options. */
+  private Set<String> activeLints;
+
+  /**
+   * The active options for this checker. This is a processed version of {@link
+   * ProcessingEnvironment#getOptions()}: If the option is of the form "-ACheckerName_key=value" and
+   * the current checker class, or one of its superclasses, is named "CheckerName", then add key
+   * &rarr; value. If the option is of the form "-ACheckerName_key=value" and the current checker
+   * class, and none of its superclasses, is named "CheckerName", then do not add key &rarr; value.
+   * If the option is of the form "-Akey=value", then add key &rarr; value.
+   *
+   * <p>Both the simple and the canonical name of the checker can be used. Superclasses of the
+   * current checker are also considered.
+   */
+  private Map<String, String> activeOptions;
+
+  /**
+   * The string that separates the checker name from the option name in a "-A" command-line
+   * argument. This string may only consist of valid Java identifier part characters, because it
+   * will be used within the key of an option.
+   */
+  protected static final String OPTION_SEPARATOR = "_";
+
+  /**
+   * The checker that called this one, whether that be a BaseTypeChecker (used as a compound
+   * checker) or an AggregateChecker. Null if this is the checker that calls all others. Note that
+   * in the case of a compound checker, the compound checker is the parent, not the checker that was
+   * run prior to this one by the compound checker.
+   */
+  protected @Nullable SourceChecker parentChecker;
+
+  /** List of upstream checker names. Includes the current checker. */
+  protected List<@FullyQualifiedName String> upstreamCheckerNames;
+
+  @Override
+  public final synchronized void init(ProcessingEnvironment env) {
+    ProcessingEnvironment unwrappedEnv = unwrapProcessingEnvironment(env);
+    super.init(unwrappedEnv);
+    // The processingEnvironment field will be set by the superclass's init method.
+    // This is used to trigger AggregateChecker's setProcessingEnvironment.
+    setProcessingEnvironment(unwrappedEnv);
+
+    // Keep in sync with check in checker-framework/build.gradle and text in installation
+    // section of manual.
+    int jreVersion = SystemUtil.getJreVersion();
+    if (jreVersion < 8) {
+      throw new UserError(
+          "Use JDK 8 or JDK 11 to run the Checker Framework.  You are using version %d.",
+          jreVersion);
+    } else if (jreVersion > 12) {
+      throw new UserError(
+          String.format(
+              "Use JDK 8 or JDK 11 to run the Checker Framework.  You are using version %d.",
+              jreVersion));
+    } else if (jreVersion != 8 && jreVersion != 11) {
+      message(
+          Kind.WARNING,
+          "Use JDK 8 or JDK 11 to run the Checker Framework.  You are using version %d.",
+          jreVersion);
+    }
+
+    if (!hasOption("warnUnneededSuppressionsExceptions")) {
+      warnUnneededSuppressionsExceptions = null;
+    } else {
+      String warnUnneededSuppressionsExceptionsString =
+          getOption("warnUnneededSuppressionsExceptions");
+      if (warnUnneededSuppressionsExceptionsString == null) {
+        throw new UserError("Must supply an argument to -AwarnUnneededSuppressionsExceptions");
+      }
+      try {
+        warnUnneededSuppressionsExceptions =
+            Pattern.compile(warnUnneededSuppressionsExceptionsString);
+      } catch (PatternSyntaxException e) {
+        throw new UserError(
+            "Argument to -AwarnUnneededSuppressionsExceptions is not a regular expression: "
+                + e.getMessage());
+      }
+    }
+
+    if (hasOption("printGitProperties")) {
+      printGitProperties();
+    }
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Getters and setters
+  ///
+
+  /**
+   * Returns the {@link ProcessingEnvironment} that was supplied to this checker.
+   *
+   * @return the {@link ProcessingEnvironment} that was supplied to this checker
+   */
+  public ProcessingEnvironment getProcessingEnvironment() {
+    return this.processingEnv;
+  }
+
+  /** Set the processing environment of the current checker. */
+  /* This method is protected only to allow the AggregateChecker and BaseTypeChecker to call it. */
+  protected void setProcessingEnvironment(ProcessingEnvironment env) {
+    this.processingEnv = env;
+  }
+
+  /** Set the parent checker of the current checker. */
+  protected void setParentChecker(SourceChecker parentChecker) {
+    this.parentChecker = parentChecker;
+  }
+
+  /**
+   * Returns the immediate parent checker of the current checker.
+   *
+   * @return the immediate parent checker of the current checker, or null if there is none
+   */
+  public @Nullable SourceChecker getParentChecker() {
+    return this.parentChecker;
+  }
+
+  /**
+   * Invoked when the current compilation unit root changes.
+   *
+   * @param newRoot the new compilation unit root
+   */
+  @SuppressWarnings("interning:assignment") // used in == tests
+  protected void setRoot(CompilationUnitTree newRoot) {
+    this.currentRoot = newRoot;
+    visitor.setRoot(currentRoot);
+  }
+
+  /**
+   * Returns a list containing this checker name and all checkers it is a part of (that is, checkers
+   * that called it).
+   *
+   * @return a list containing this checker name and all checkers it is a part of (that is, checkers
+   *     that called it)
+   */
+  public List<@FullyQualifiedName String> getUpstreamCheckerNames() {
+    if (upstreamCheckerNames == null) {
+      upstreamCheckerNames = new ArrayList<>();
+
+      SourceChecker checker = this;
+
+      while (checker != null) {
+        upstreamCheckerNames.add(checker.getClass().getCanonicalName());
+        checker = checker.parentChecker;
+      }
+    }
+
+    return upstreamCheckerNames;
+  }
+
+  /**
+   * Returns the OptionConfiguration associated with this.
+   *
+   * @return the OptionConfiguration associated with this
+   */
+  public OptionConfiguration getOptionConfiguration() {
+    return this;
+  }
+
+  /**
+   * Returns the element utilities associated with this.
+   *
+   * @return the element utilities associated with this
+   */
+  public Elements getElementUtils() {
+    return getProcessingEnvironment().getElementUtils();
+  }
+
+  /**
+   * Returns the type utilities associated with this.
+   *
+   * @return the type utilities associated with this
+   */
+  public Types getTypeUtils() {
+    return getProcessingEnvironment().getTypeUtils();
+  }
+
+  /**
+   * Returns the tree utilities associated with this.
+   *
+   * @return the tree utilities associated with this
+   */
+  public Trees getTreeUtils() {
+    return Trees.instance(getProcessingEnvironment());
+  }
+
+  /**
+   * Returns the SourceVisitor associated with this.
+   *
+   * @return the SourceVisitor associated with this
+   */
+  public SourceVisitor<?, ?> getVisitor() {
+    return this.visitor;
+  }
+
+  /**
+   * Provides the {@link SourceVisitor} that the checker should use to scan input source trees.
+   *
+   * @return a {@link SourceVisitor} to use to scan source trees
+   */
+  protected abstract SourceVisitor<?, ?> createSourceVisitor();
+
+  /**
+   * Returns the AnnotationProvider (the type factory) associated with this.
+   *
+   * @return the AnnotationProvider (the type factory) associated with this
+   */
+  public AnnotationProvider getAnnotationProvider() {
+    throw new UnsupportedOperationException(
+        "getAnnotationProvider is not implemented for this class.");
+  }
+
+  /**
+   * Provides a mapping of error keys to custom error messages.
+   *
+   * <p>As a default, this implementation builds a {@link Properties} out of file {@code
+   * messages.properties}. It accumulates all the properties files in the Java class hierarchy from
+   * the checker up to {@code SourceChecker}. This permits subclasses to inherit default messages
+   * while being able to override them.
+   *
+   * @return a {@link Properties} that maps error keys to error message text
+   */
+  public Properties getMessagesProperties() {
+    if (messagesProperties != null) {
+      return messagesProperties;
+    }
+
+    messagesProperties = new Properties();
+
+    ArrayDeque<Class<?>> checkers = new ArrayDeque<>();
+    Class<?> currClass = this.getClass();
+    while (currClass != AbstractTypeProcessor.class) {
+      checkers.addFirst(currClass);
+      currClass = currClass.getSuperclass();
+    }
+
+    for (Class<?> checker : checkers) {
+      messagesProperties.putAll(getProperties(checker, MSGS_FILE, true));
+    }
+    return messagesProperties;
+  }
+
+  /**
+   * Return the given skip pattern if supplied by the user, or else a pattern that matches nothing.
+   *
+   * @param patternName "skipUses" or "skipDefs"
+   * @param options the command-line options
+   * @return the user-supplied regex for the given pattern, or a regex that matches nothing
+   */
+  private Pattern getSkipPattern(String patternName, Map<String, String> options) {
+    // Default is an illegal Java identifier substring
+    // so that it won't match anything.
+    // Note that AnnotatedType's toString output format contains characters such as "():{}".
+    return getPattern(patternName, options, "\\]'\"\\]");
+  }
+
+  /**
+   * Return the given only pattern if supplied by the user, or else a pattern that matches
+   * everything.
+   *
+   * @param patternName "onlyUses" or "onlyDefs"
+   * @param options the command-line options
+   * @return the user-supplied regex for the given pattern, or a regex that matches everything
+   */
+  private Pattern getOnlyPattern(String patternName, Map<String, String> options) {
+    // default matches everything
+    return getPattern(patternName, options, ".");
+  }
+
+  private Pattern getPattern(
+      String patternName, Map<String, String> options, String defaultPattern) {
+    String pattern = "";
+
+    if (options.containsKey(patternName)) {
+      pattern = options.get(patternName);
+      if (pattern == null) {
+        message(
+            Kind.WARNING,
+            "The " + patternName + " property is empty; please fix your command line");
+        pattern = "";
+      }
+    } else if (System.getProperty("checkers." + patternName) != null) {
+      pattern = System.getProperty("checkers." + patternName);
+    } else if (System.getenv(patternName) != null) {
+      pattern = System.getenv(patternName);
+    }
+
+    if (pattern.indexOf("/") != -1) {
+      message(
+          Kind.WARNING,
+          "The "
+              + patternName
+              + " property contains \"/\", which will never match a class name: "
+              + pattern);
+    }
+
+    if (pattern.equals("")) {
+      pattern = defaultPattern;
+    }
+
+    return Pattern.compile(pattern);
+  }
+
+  private Pattern getSkipUsesPattern(Map<String, String> options) {
+    return getSkipPattern("skipUses", options);
+  }
+
+  private Pattern getOnlyUsesPattern(Map<String, String> options) {
+    return getOnlyPattern("onlyUses", options);
+  }
+
+  private Pattern getSkipDefsPattern(Map<String, String> options) {
+    return getSkipPattern("skipDefs", options);
+  }
+
+  private Pattern getOnlyDefsPattern(Map<String, String> options) {
+    return getOnlyPattern("onlyDefs", options);
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Type-checking
+  ///
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>Type-checkers are not supposed to override this. Instead override initChecker. This allows
+   * us to handle BugInCF only here and doesn't require all overriding implementations to be aware
+   * of BugInCF.
+   *
+   * @see AbstractProcessor#init(ProcessingEnvironment)
+   * @see SourceChecker#initChecker()
+   */
+  @Override
+  public void typeProcessingStart() {
+    try {
+      super.typeProcessingStart();
+      initChecker();
+      if (this.messager == null) {
+        messager = processingEnv.getMessager();
+        messager.printMessage(
+            Kind.WARNING,
+            "You have forgotten to call super.initChecker in your "
+                + "subclass of SourceChecker, "
+                + this.getClass()
+                + "! Please ensure your checker is properly initialized.");
+      }
+      if (shouldAddShutdownHook()) {
+        Runtime.getRuntime()
+            .addShutdownHook(
+                new Thread() {
+                  @Override
+                  public void run() {
+                    shutdownHook();
+                  }
+                });
+      }
+      if (hasOption("version")) {
+        messager.printMessage(Kind.NOTE, "Checker Framework " + getCheckerVersion());
+      }
+    } catch (UserError ce) {
+      logUserError(ce);
+    } catch (TypeSystemError ce) {
+      logTypeSystemError(ce);
+    } catch (BugInCF ce) {
+      logBugInCF(ce);
+    } catch (Throwable t) {
+      logBugInCF(wrapThrowableAsBugInCF("SourceChecker.typeProcessingStart", t, null));
+    }
+  }
+
+  /**
+   * Initialize the checker.
+   *
+   * @see AbstractProcessor#init(ProcessingEnvironment)
+   */
+  public void initChecker() {
+    // Grab the Trees and Messager instances now; other utilities
+    // (like Types and Elements) can be retrieved by subclasses.
+    @Nullable Trees trees = Trees.instance(processingEnv);
+    assert trees != null;
+    this.trees = trees;
+
+    this.messager = processingEnv.getMessager();
+    this.messagesProperties = getMessagesProperties();
+
+    this.visitor = createSourceVisitor();
+
+    // Validate the lint flags, if they haven't been used already.
+    if (this.activeLints == null) {
+      this.activeLints = createActiveLints(getOptions());
+    }
+  }
+
+  /** Output the warning about source level at most once. */
+  private boolean warnedAboutSourceLevel = false;
+
+  /**
+   * If true, javac failed to compile the code or a previously-run annotation processor issued an
+   * error.
+   */
+  protected boolean javacErrored = false;
+
+  /** Output the warning about memory at most once. */
+  private boolean warnedAboutGarbageCollection = false;
+
+  /**
+   * The number of errors at the last exit of the type processor. At entry to the type processor we
+   * check whether the current error count is higher and then don't process the file, as it contains
+   * some Java errors. Needs to be protected to allow access from AggregateChecker and
+   * BaseTypeChecker.
+   */
+  protected int errsOnLastExit = 0;
+
+  /**
+   * Report "type.checking.not.run" error.
+   *
+   * @param p error is reported at the leaf of the path
+   */
+  @SuppressWarnings("interning:assignment") // used in == tests
+  protected void reportJavacError(TreePath p) {
+    // If javac issued any errors, do not type check any file, so that the Checker Framework
+    // does not have to deal with error types.
+    currentRoot = p.getCompilationUnit();
+    reportError(p.getLeaf(), "type.checking.not.run", getClass().getSimpleName());
+  }
+
+  /**
+   * Type-check the code using this checker's visitor.
+   *
+   * @see Processor#process(Set, RoundEnvironment)
+   */
+  @Override
+  public void typeProcess(TypeElement e, TreePath p) {
+    if (javacErrored) {
+      reportJavacError(p);
+      return;
+    }
+
+    // Cannot use BugInCF here because it is outside of the try/catch for BugInCF.
+    if (e == null) {
+      messager.printMessage(Kind.ERROR, "Refusing to process empty TypeElement");
+      return;
+    }
+    if (p == null) {
+      messager.printMessage(Kind.ERROR, "Refusing to process empty TreePath in TypeElement: " + e);
+      return;
+    }
+    if (!warnedAboutGarbageCollection && SystemPlume.gcPercentage() > .25) {
+      messager.printMessage(
+          Kind.WARNING, "Garbage collection consumed over 25% of CPU during the past minute.");
+      messager.printMessage(
+          Kind.WARNING,
+          String.format(
+              "Perhaps increase max heap size"
+                  + " (max memory = %d, total memory = %d, free memory = %d).",
+              Runtime.getRuntime().maxMemory(),
+              Runtime.getRuntime().totalMemory(),
+              Runtime.getRuntime().freeMemory()));
+      warnedAboutGarbageCollection = true;
+    }
+
+    Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
+    Source source = Source.instance(context);
+    // Don't use source.allowTypeAnnotations() because that API changed after 9.
+    // Also the enum constant Source.JDK1_8 was renamed at some point...
+    if (!warnedAboutSourceLevel && source.compareTo(Source.lookup("8")) < 0) {
+      messager.printMessage(
+          Kind.WARNING, "-source " + source.name + " does not support type annotations");
+      warnedAboutSourceLevel = true;
+    }
+
+    Log log = Log.instance(context);
+    if (log.nerrors > this.errsOnLastExit) {
+      this.errsOnLastExit = log.nerrors;
+      javacErrored = true;
+      reportJavacError(p);
+      return;
+    }
+
+    if (visitor == null) {
+      // typeProcessingStart invokes initChecker, which should have set the visitor. If the field is
+      // still null, an exception occurred during initialization, which was already logged
+      // there. Don't also cause a NPE here.
+      return;
+    }
+    if (p.getCompilationUnit() != currentRoot) {
+      setRoot(p.getCompilationUnit());
+      if (hasOption("filenames")) {
+        // TODO: Have a command-line option to turn the timestamps on/off too, because
+        // they are nondeterministic across runs.
+
+        // Add timestamp to indicate how long operations are taking.
+        // Duplicate messages are suppressed, so this might not appear in front of every "
+        // is type-checking " message (when a file takes less than a second to type-check).
+        message(Kind.NOTE, Instant.now().toString());
+        message(
+            Kind.NOTE,
+            "%s is type-checking %s",
+            (Object) this.getClass().getSimpleName(),
+            currentRoot.getSourceFile().getName());
+      }
+    }
+
+    // Visit the attributed tree.
+    try {
+      visitor.visit(p);
+      warnUnneededSuppressions();
+    } catch (UserError ce) {
+      logUserError(ce);
+    } catch (TypeSystemError ce) {
+      logTypeSystemError(ce);
+    } catch (BugInCF ce) {
+      logBugInCF(ce);
+    } catch (Throwable t) {
+      logBugInCF(wrapThrowableAsBugInCF("SourceChecker.typeProcess", t, p));
+    } finally {
+      // Also add possibly deferred diagnostics, which will get published back in
+      // AbstractTypeProcessor.
+      this.errsOnLastExit = log.nerrors;
+    }
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Reporting type-checking errors; most clients use reportError() or reportWarning()
+  ///
+
+  /**
+   * Reports an error. By default, prints it to the screen via the compiler's internal messager.
+   *
+   * @param source the source position information; may be an Element, a Tree, or null
+   * @param messageKey the message key
+   * @param args arguments for interpolation in the string corresponding to the given message key
+   */
+  public void reportError(Object source, @CompilerMessageKey String messageKey, Object... args) {
+    report(source, Kind.ERROR, messageKey, args);
+  }
+
+  /**
+   * Reports a warning. By default, prints it to the screen via the compiler's internal messager.
+   *
+   * @param source the source position information; may be an Element, a Tree, or null
+   * @param messageKey the message key
+   * @param args arguments for interpolation in the string corresponding to the given message key
+   */
+  public void reportWarning(Object source, @CompilerMessageKey String messageKey, Object... args) {
+    report(source, Kind.MANDATORY_WARNING, messageKey, args);
+  }
+
+  /**
+   * Reports a diagnostic message. By default, prints it to the screen via the compiler's internal
+   * messager.
+   *
+   * <p>Most clients should use {@link #reportError} or {@link #reportWarning}.
+   *
+   * @param source the source position information; may be an Element, a Tree, or null
+   * @param d the diagnostic message
+   */
+  public void report(Object source, DiagMessage d) {
+    report(source, d.getKind(), d.getMessageKey(), d.getArgs());
+  }
+
+  /**
+   * Reports a diagnostic message. By default, it prints it to the screen via the compiler's
+   * internal messager; however, it might also store it for later output.
+   *
+   * @param source the source position information; may be an Element, a Tree, or null
+   * @param kind the type of message
+   * @param messageKey the message key
+   * @param args arguments for interpolation in the string corresponding to the given message key
+   */
+  // Not a format method.  However, messageKey should be either a format string for `args`, or  a
+  // property key that maps to a format string for `args`.
+  // @FormatMethod
+  @SuppressWarnings("formatter:format.string") // arg is a format string or a property key
+  private void report(
+      Object source,
+      javax.tools.Diagnostic.Kind kind,
+      @CompilerMessageKey String messageKey,
+      Object... args) {
+    assert messagesProperties != null : "null messagesProperties";
+
+    if (shouldSuppressWarnings(source, messageKey)) {
+      return;
+    }
+
+    if (args != null) {
+      for (int i = 0; i < args.length; ++i) {
+        args[i] = processArg(args[i]);
+      }
+    }
+
+    if (kind == Kind.NOTE) {
+      System.err.println("(NOTE) " + String.format(messageKey, args));
+      return;
+    }
+
+    final String defaultFormat = "(" + messageKey + ")";
+    String fmtString;
+    if (this.processingEnv.getOptions() != null /*nnbug*/
+        && this.processingEnv.getOptions().containsKey("nomsgtext")) {
+      fmtString = defaultFormat;
+    } else if (this.processingEnv.getOptions() != null /*nnbug*/
+        && this.processingEnv.getOptions().containsKey("detailedmsgtext")) {
+      // The -Adetailedmsgtext command-line option was given, so output
+      // a stylized error message for easy parsing by a tool.
+      fmtString =
+          detailedMsgTextPrefix(source, defaultFormat, args)
+              + fullMessageOf(messageKey, defaultFormat);
+    } else {
+      fmtString =
+          "["
+              + suppressWarningsString(messageKey)
+              + "] "
+              + fullMessageOf(messageKey, defaultFormat);
+    }
+    String messageText;
+    try {
+      messageText = String.format(fmtString, args);
+    } catch (Exception e) {
+      throw new BugInCF(
+          "Invalid format string: \"" + fmtString + "\" args: " + Arrays.toString(args), e);
+    }
+
+    if (kind == Kind.ERROR && hasOption("warns")) {
+      kind = Kind.MANDATORY_WARNING;
+    }
+
+    if (source instanceof Element) {
+      messager.printMessage(kind, messageText, (Element) source);
+    } else if (source instanceof Tree) {
+      printOrStoreMessage(kind, messageText, (Tree) source, currentRoot);
+    } else {
+      throw new BugInCF("invalid position source, class=" + source.getClass());
+    }
+  }
+
+  /**
+   * Print a non-localized message using the javac messager. This is preferable to using System.out
+   * or System.err, but should only be used for exceptional cases that don't happen in correct
+   * usage. Localized messages should be raised using {@link #reportError}, {@link #reportWarning},
+   * etc.
+   *
+   * @param kind the kind of message to print
+   * @param msg the message text
+   * @param args optional arguments to substitute in the message
+   * @see SourceChecker#report(Object, DiagMessage)
+   */
+  @FormatMethod
+  public void message(javax.tools.Diagnostic.Kind kind, String msg, Object... args) {
+    message(kind, String.format(msg, args));
+  }
+
+  /**
+   * Print a non-localized message using the javac messager. This is preferable to using System.out
+   * or System.err, but should only be used for exceptional cases that don't happen in correct
+   * usage. Localized messages should be raised using {@link #reportError}, {@link #reportWarning},
+   * etc.
+   *
+   * @param kind the kind of message to print
+   * @param msg the message text
+   * @see SourceChecker#report(Object, DiagMessage)
+   */
+  public void message(javax.tools.Diagnostic.Kind kind, String msg) {
+    if (messager == null) {
+      // If this method is called before initChecker() sets the field
+      messager = processingEnv.getMessager();
+    }
+    messager.printMessage(kind, msg);
+  }
+
+  /**
+   * Print the given message.
+   *
+   * @param msg the message to print x
+   */
+  private void printMessage(String msg) {
+    if (messager == null) {
+      // If this method is called before initChecker() sets the field
+      messager = processingEnv.getMessager();
+    }
+    messager.printMessage(Kind.ERROR, msg);
+  }
+
+  /**
+   * Do not call this method. Call {@link #reportError} or {@link #reportWarning} instead.
+   *
+   * <p>This method exists so that the BaseTypeChecker can override it. For compound checkers, it
+   * stores all messages and sorts them by location before outputting them.
+   *
+   * @param kind the kind of message to print
+   * @param message the message text
+   * @param source the source code position of the diagnostic message
+   * @param root the compilation unit
+   */
+  protected void printOrStoreMessage(
+      javax.tools.Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) {
+    StackTraceElement[] trace = Thread.currentThread().getStackTrace();
+    printOrStoreMessage(kind, message, source, root, trace);
+  }
+
+  /**
+   * Do not call this method. Call {@link #reportError} or {@link #reportWarning} instead.
+   *
+   * <p>This method exists so that the BaseTypeChecker can override it. For compound checkers, it
+   * stores all messages and sorts them by location before outputting them.
+   *
+   * @param kind the kind of message to print
+   * @param message the message text
+   * @param source the source code position of the diagnostic message
+   * @param root the compilation unit
+   * @param trace the stack trace where the checker encountered an error. It is printed when the
+   *     dumpOnErrors option is enabled.
+   */
+  protected void printOrStoreMessage(
+      javax.tools.Diagnostic.Kind kind,
+      String message,
+      Tree source,
+      CompilationUnitTree root,
+      StackTraceElement[] trace) {
+    Trees.instance(processingEnv).printMessage(kind, message, source, root);
+    printStackTrace(trace);
+  }
+
+  /**
+   * Output the given stack trace if the "dumpOnErrors" option is enabled.
+   *
+   * @param trace stack trace when the checker encountered a warning/error
+   */
+  private void printStackTrace(StackTraceElement[] trace) {
+    if (hasOption("dumpOnErrors")) {
+      StringBuilder msg = new StringBuilder();
+      for (StackTraceElement elem : trace) {
+        msg.append("\tat " + elem + "\n");
+      }
+      message(Diagnostic.Kind.NOTE, msg.toString());
+    }
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Diagnostic message formatting
+  ///
+
+  /**
+   * Returns the localized long message corresponding to this key. If not found, tries suffixes of
+   * this key, stripping off dot-separated prefixes. If still not found, returns {@code
+   * defaultValue}.
+   *
+   * @param messageKey a message key
+   * @param defaultValue a default value to use if {@code messageKey} is not a message key
+   * @return the localized long message corresponding to this key or a suffix, or {@code
+   *     defaultValue}
+   */
+  protected String fullMessageOf(String messageKey, String defaultValue) {
+    String key = messageKey;
+
+    do {
+      if (messagesProperties.containsKey(key)) {
+        return messagesProperties.getProperty(key);
+      }
+
+      int dot = key.indexOf('.');
+      if (dot < 0) {
+        return defaultValue;
+      }
+      key = key.substring(dot + 1);
+    } while (true);
+  }
+
+  /**
+   * Process an argument to an error message before it is passed to String.format.
+   *
+   * <p>This implementation expands the argument if it is exactly a message key.
+   *
+   * <p>By contrast, {@link #fullMessageOf} processes the message key itself but not the arguments,
+   * and tries suffixes.
+   *
+   * @param arg the argument
+   * @return the result after processing
+   */
+  protected Object processArg(Object arg) {
+    // Check to see if the argument itself is a property to be expanded
+    if (arg instanceof String) {
+      return messagesProperties.getProperty((String) arg, (String) arg);
+    } else {
+      return arg;
+    }
+  }
+
+  /** Separates parts of a "detailed message", to permit easier parsing. */
+  public static final String DETAILS_SEPARATOR = " $$ ";
+
+  /**
+   * Returns all but the message key part of the message format output by -Adetailedmsgtext.
+   *
+   * @param source the object from which to obtain source position information; may be an Element, a
+   *     Tree, or null
+   * @param defaultFormat the message key, in parentheses
+   * @param args arguments for interpolation in the string corresponding to the given message key
+   * @return the first part of the message format output by -Adetailedmsgtext
+   */
+  private String detailedMsgTextPrefix(Object source, String defaultFormat, Object[] args) {
+    StringJoiner sj = new StringJoiner(DETAILS_SEPARATOR);
+
+    // The parts, separated by " $$ " (DETAILS_SEPARATOR), are:
+
+    // (1) error key
+    sj.add(defaultFormat);
+
+    // (2) number of additional tokens, and those tokens; this depends on the error message, and an
+    // example is the found and expected types
+    if (args != null) {
+      sj.add(Integer.toString(args.length));
+      for (Object arg : args) {
+        sj.add(Objects.toString(arg));
+      }
+    } else {
+      // Output 0 for null arguments.
+      sj.add(Integer.toString(0));
+    }
+
+    // (3) The error position, as starting and ending characters in the source file.
+    sj.add(detailedMsgTextPositionString(sourceToTree(source), currentRoot));
+
+    // (4) The human-readable error message will be added by the caller.
+    sj.add(""); // Add DETAILS_SEPARATOR at the end.
+    return sj.toString();
+  }
+
+  /**
+   * Returns the most specific warning suppression string for the warning/error being printed. This
+   * is {@code msg} prefixed by a checker name (or "allcheckers") and a colon.
+   *
+   * @param messageKey the simple, checker-specific error message key
+   * @return the most specific SuppressWarnings string for the warning/error being printed
+   */
+  private String suppressWarningsString(String messageKey) {
+    Collection<String> prefixes = this.getSuppressWarningsPrefixes();
+    prefixes.remove(SUPPRESS_ALL_PREFIX);
+    if (hasOption("showSuppressWarningsStrings")) {
+      List<String> list = new ArrayList<>(prefixes);
+      // Make sure "allcheckers" is at the end of the list.
+      if (useAllcheckersPrefix) {
+        list.add(SUPPRESS_ALL_PREFIX);
+      }
+      return list + ":" + messageKey;
+    } else if (hasOption("requirePrefixInWarningSuppressions")) {
+      // If the warning key must be prefixed with a prefix (a checker name), then add that to
+      // the SuppressWarnings string that is printed.
+      String defaultPrefix = getDefaultSuppressWarningsPrefix();
+      if (prefixes.contains(defaultPrefix)) {
+        return defaultPrefix + ":" + messageKey;
+      } else {
+        String firstKey = prefixes.iterator().next();
+        return firstKey + ":" + messageKey;
+      }
+    } else {
+      return messageKey;
+    }
+  }
+
+  /**
+   * Convert a Tree, Element, or null, into a Tree or null.
+   *
+   * @param source the object from which to obtain source position information; may be an Element, a
+   *     Tree, or null
+   * @return the tree associated with the given source object, or null if none
+   */
+  private @Nullable Tree sourceToTree(@Nullable Object source) {
+    if (source instanceof Element) {
+      return trees.getTree((Element) source);
+    } else if (source instanceof Tree) {
+      return (Tree) source;
+    } else if (source == null) {
+      return null;
+    } else {
+      throw new BugInCF("Unexpected source %s [%s]", source, source.getClass());
+    }
+  }
+
+  /**
+   * For the given tree, compute the source positions for that tree. Return a "tuple"-like string
+   * (e.g. "( 1, 200 )" ) that contains the start and end position of the tree in the current
+   * compilation unit. Used only by the -Adetailedmsgtext output format.
+   *
+   * @param tree tree to locate within the current compilation unit
+   * @param currentRoot the current compilation unit
+   * @return a tuple string representing the range of characters that tree occupies in the source
+   *     file, or the empty string if {@code tree} is null
+   */
+  private String detailedMsgTextPositionString(Tree tree, CompilationUnitTree currentRoot) {
+    if (tree == null) {
+      return "";
+    }
+
+    SourcePositions sourcePositions = trees.getSourcePositions();
+    long start = sourcePositions.getStartPosition(currentRoot, tree);
+    long end = sourcePositions.getEndPosition(currentRoot, tree);
+
+    return "( " + start + ", " + end + " )";
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Lint options ("-Alint:xxxx" and "-Alint:-xxxx")
+  ///
+
+  /**
+   * Determine which lint options are artive.
+   *
+   * @param options the command-line options
+   * @return the active lint options
+   */
+  private Set<String> createActiveLints(Map<String, String> options) {
+    if (!options.containsKey("lint")) {
+      return Collections.emptySet();
+    }
+
+    String lintString = options.get("lint");
+    if (lintString == null) {
+      return Collections.singleton("all");
+    }
+
+    Set<String> activeLint = new HashSet<>();
+    for (String s : lintString.split(",")) {
+      if (!this.getSupportedLintOptions().contains(s)
+          && !(s.charAt(0) == '-' && this.getSupportedLintOptions().contains(s.substring(1)))
+          && !s.equals("all")
+          && !s.equals("none")) {
+        this.messager.printMessage(
+            Kind.WARNING,
+            "Unsupported lint option: " + s + "; All options: " + this.getSupportedLintOptions());
+      }
+
+      activeLint.add(s);
+      if (s.equals("none")) {
+        activeLint.add("-all");
+      }
+    }
+
+    return Collections.unmodifiableSet(activeLint);
+  }
+
+  /**
+   * Determines the value of the lint option with the given name. Just as <a
+   * href="https://docs.oracle.com/javase/7/docs/technotes/guides/javac/index.html">javac</a> uses
+   * "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint options
+   * are enabled with "-Alint:xxx" and disabled with "-Alint:-xxx".
+   *
+   * @throws IllegalArgumentException if the option name is not recognized via the {@link
+   *     SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions}
+   *     method
+   * @param name the name of the lint option to check for
+   * @return true if the lint option was given, false if it was not given or was given prepended
+   *     with a "-"
+   * @see SourceChecker#getLintOption(String, boolean)
+   */
+  public final boolean getLintOption(String name) {
+    return getLintOption(name, false);
+  }
+
+  /**
+   * Determines the value of the lint option with the given name. Just as <a
+   * href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html">javac</a> uses
+   * "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint options
+   * are enabled with "-Alint=xxx" and disabled with "-Alint=-xxx".
+   *
+   * @throws IllegalArgumentException if the option name is not recognized via the {@link
+   *     SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions}
+   *     method
+   * @param name the name of the lint option to check for
+   * @param def the default option value, returned if the option was not given
+   * @return true if the lint option was given, false if it was given prepended with a "-", or
+   *     {@code def} if it was not given at all
+   * @see SourceChecker#getLintOption(String)
+   * @see SourceChecker#getOption(String)
+   */
+  public final boolean getLintOption(String name, boolean def) {
+
+    if (!this.getSupportedLintOptions().contains(name)) {
+      throw new UserError("Illegal lint option: " + name);
+    }
+
+    if (activeLints == null) {
+      activeLints = createActiveLints(getOptions());
+    }
+
+    if (activeLints.isEmpty()) {
+      return def;
+    }
+
+    String tofind = name;
+    while (tofind != null) {
+      if (activeLints.contains(tofind)) {
+        return true;
+      } else if (activeLints.contains(String.format("-%s", tofind))) {
+        return false;
+      }
+
+      tofind = parentOfOption(tofind);
+    }
+
+    return def;
+  }
+
+  /**
+   * Set the value of the lint option with the given name. Just as <a
+   * href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html">javac</a> uses
+   * "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint options
+   * are enabled with "-Alint=xxx" and disabled with "-Alint=-xxx". This method can be used by
+   * subclasses to enforce having certain lint options enabled/disabled.
+   *
+   * @throws IllegalArgumentException if the option name is not recognized via the {@link
+   *     SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions}
+   *     method
+   * @param name the name of the lint option to set
+   * @param val the option value
+   * @see SourceChecker#getLintOption(String)
+   * @see SourceChecker#getLintOption(String,boolean)
+   */
+  protected final void setLintOption(String name, boolean val) {
+    if (!this.getSupportedLintOptions().contains(name)) {
+      throw new UserError("Illegal lint option: " + name);
+    }
+
+    /* TODO: warn if the option is also provided on the command line(?)
+    boolean exists = false;
+    if (!activeLints.isEmpty()) {
+        String tofind = name;
+        while (tofind != null) {
+            if (activeLints.contains(tofind) || // direct
+                    activeLints.contains(String.format("-%s", tofind)) || // negation
+                    activeLints.contains(tofind.substring(1))) { // name was negation
+                exists = true;
+            }
+            tofind = parentOfOption(tofind);
+        }
+    }
+
+    if (exists) {
+        // TODO: Issue warning?
+    }
+    TODO: assert that name doesn't start with '-'
+    */
+
+    Set<String> newlints = new HashSet<>();
+    newlints.addAll(activeLints);
+    if (val) {
+      newlints.add(name);
+    } else {
+      newlints.add(String.format("-%s", name));
+    }
+    activeLints = Collections.unmodifiableSet(newlints);
+  }
+
+  /**
+   * Helper method to find the parent of a lint key. The lint hierarchy level is denoted by a colon
+   * ':'. 'all' is the root for all hierarchy.
+   *
+   * <pre>
+   * Example
+   *    cast:unsafe &rarr; cast
+   *    cast        &rarr; all
+   *    all         &rarr; {@code null}
+   * </pre>
+   *
+   * @param name the lint key whose parest to find
+   * @return the parent of the lint key
+   */
+  private String parentOfOption(String name) {
+    if (name.equals("all")) {
+      return null;
+    }
+    int colonIndex = name.lastIndexOf(':');
+    if (colonIndex != -1) {
+      return name.substring(0, colonIndex);
+    } else {
+      return "all";
+    }
+  }
+
+  /**
+   * Returns the lint options recognized by this checker. Lint options are those which can be
+   * checked for via {@link SourceChecker#getLintOption}.
+   *
+   * @return an unmodifiable {@link Set} of the lint options recognized by this checker
+   */
+  public Set<String> getSupportedLintOptions() {
+    if (supportedLints == null) {
+      supportedLints = createSupportedLintOptions();
+    }
+    return supportedLints;
+  }
+
+  /** Compute the set of supported lint options. */
+  protected Set<String> createSupportedLintOptions() {
+    @Nullable SupportedLintOptions sl = this.getClass().getAnnotation(SupportedLintOptions.class);
+
+    if (sl == null) {
+      return Collections.emptySet();
+    }
+
+    @Nullable String @Nullable [] slValue = sl.value();
+    assert slValue != null;
+
+    @Nullable String[] lintArray = slValue;
+    Set<String> lintSet = new HashSet<>(lintArray.length);
+    for (String s : lintArray) {
+      lintSet.add(s);
+    }
+    return Collections.unmodifiableSet(lintSet);
+  }
+
+  /**
+   * Set the supported lint options. Use of this method should be limited to the AggregateChecker,
+   * who needs to set the lint options to the union of all subcheckers. Also, e.g. the
+   * NullnessSubchecker need to use this method, as one is created by the other.
+   *
+   * @param newLints the new supported lint options, which replace any existing ones
+   */
+  protected void setSupportedLintOptions(Set<String> newLints) {
+    supportedLints = newLints;
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Regular (non-lint) options ("-Axxxx")
+  ///
+
+  /**
+   * Determine which options are active.
+   *
+   * @param options all provided options
+   * @return a value for {@link #activeOptions}
+   */
+  private Map<String, String> createActiveOptions(Map<String, String> options) {
+    if (options.isEmpty()) {
+      return Collections.emptyMap();
+    }
+
+    Map<String, String> activeOpts = new HashMap<>(CollectionsPlume.mapCapacity(options));
+
+    for (Map.Entry<String, String> opt : options.entrySet()) {
+      String key = opt.getKey();
+      String value = opt.getValue();
+
+      String[] split = key.split(OPTION_SEPARATOR);
+
+      splitlengthswitch:
+      switch (split.length) {
+        case 1:
+          // No separator, option always active.
+          activeOpts.put(key, value);
+          break;
+        case 2:
+          Class<?> clazz = this.getClass();
+
+          do {
+            if (clazz.getCanonicalName().equals(split[0])
+                || clazz.getSimpleName().equals(split[0])) {
+              // Valid class-option pair.
+              activeOpts.put(split[1], value);
+              break splitlengthswitch;
+            }
+
+            clazz = clazz.getSuperclass();
+          } while (clazz != null
+              && !clazz.getName().equals(AbstractTypeProcessor.class.getCanonicalName()));
+          // Didn't find a matching class. Option might be for another processor. Add
+          // option anyways. javac will warn if no processor supports the option.
+          activeOpts.put(key, value);
+          break;
+        default:
+          // Too many separators. Option might be for another processor. Add option
+          // anyways. javac will warn if no processor supports the option.
+          activeOpts.put(key, value);
+      }
+    }
+    return Collections.unmodifiableMap(activeOpts);
+  }
+
+  /**
+   * Add additional active options. Use of this method should be limited to the AggregateChecker,
+   * who needs to set the active options to the union of all subcheckers.
+   *
+   * @param moreOpts the active options to add
+   */
+  protected void addOptions(Map<String, String> moreOpts) {
+    Map<String, String> activeOpts = new HashMap<>(getOptions());
+    activeOpts.putAll(moreOpts);
+    activeOptions = Collections.unmodifiableMap(activeOpts);
+  }
+
+  /**
+   * Check whether the given option is provided.
+   *
+   * <p>Note that {@link #getOption(String)} can still return null even if {@code hasOption} returns
+   * true: this happens e.g. for {@code -Amyopt}
+   *
+   * @param name the name of the option to check
+   * @return true if the option name was provided, false otherwise
+   */
+  @Override
+  public final boolean hasOption(String name) {
+    return getOptions().containsKey(name);
+  }
+
+  /**
+   * Determines the value of the option with the given name.
+   *
+   * @param name the name of the option to check
+   * @see SourceChecker#getLintOption(String,boolean)
+   */
+  @Override
+  public final String getOption(String name) {
+    return getOption(name, null);
+  }
+
+  /**
+   * Determines the boolean value of the option with the given name. Returns false if the option is
+   * not set.
+   *
+   * @param name the name of the option to check
+   * @see SourceChecker#getLintOption(String,boolean)
+   */
+  @Override
+  public final boolean getBooleanOption(String name) {
+    return getBooleanOption(name, false);
+  }
+
+  /**
+   * Determines the boolean value of the option with the given name. Returns the given default value
+   * if the option is not set.
+   *
+   * @param name the name of the option to check
+   * @param defaultValue the default value to use if the option is not set
+   * @see SourceChecker#getLintOption(String,boolean)
+   */
+  @Override
+  public final boolean getBooleanOption(String name, boolean defaultValue) {
+    String value = getOption(name);
+    if (value == null) {
+      return defaultValue;
+    }
+    if (value.equals("true")) {
+      return true;
+    }
+    if (value.equals("false")) {
+      return false;
+    }
+    throw new UserError(
+        String.format("Value of %s option should be a boolean, but is \"%s\".", name, value));
+  }
+
+  /**
+   * Return all active options for this checker.
+   *
+   * @return all active options for this checker
+   */
+  @Override
+  public Map<String, String> getOptions() {
+    if (activeOptions == null) {
+      activeOptions = createActiveOptions(processingEnv.getOptions());
+    }
+    return activeOptions;
+  }
+
+  /**
+   * Determines the value of the lint option with the given name and returns the default value if
+   * nothing is specified.
+   *
+   * @param name the name of the option to check
+   * @param defaultValue the default value to use if the option is not set
+   * @see SourceChecker#getOption(String)
+   * @see SourceChecker#getLintOption(String)
+   */
+  @Override
+  public final String getOption(String name, String defaultValue) {
+
+    if (!this.getSupportedOptions().contains(name)) {
+      throw new UserError("Illegal option: " + name);
+    }
+
+    if (activeOptions == null) {
+      activeOptions = createActiveOptions(processingEnv.getOptions());
+    }
+
+    if (activeOptions.isEmpty()) {
+      return defaultValue;
+    }
+
+    if (activeOptions.containsKey(name)) {
+      return activeOptions.get(name);
+    } else {
+      return defaultValue;
+    }
+  }
+
+  /**
+   * Map the Checker Framework version of {@link SupportedOptions} to the standard annotation
+   * provided version {@link javax.annotation.processing.SupportedOptions}.
+   */
+  @Override
+  public Set<String> getSupportedOptions() {
+    Set<String> options = new HashSet<>();
+
+    // Support all options provided with the standard {@link
+    // javax.annotation.processing.SupportedOptions} annotation.
+    options.addAll(super.getSupportedOptions());
+
+    // For the Checker Framework annotation
+    // {@link org.checkerframework.framework.source.SupportedOptions}
+    // we additionally add
+    Class<?> clazz = this.getClass();
+    List<Class<?>> clazzPrefixes = new ArrayList<>();
+
+    do {
+      clazzPrefixes.add(clazz);
+
+      SupportedOptions so = clazz.getAnnotation(SupportedOptions.class);
+      if (so != null) {
+        options.addAll(expandCFOptions(clazzPrefixes, so.value()));
+      }
+      clazz = clazz.getSuperclass();
+    } while (clazz != null
+        && !clazz.getName().equals(AbstractTypeProcessor.class.getCanonicalName()));
+
+    return Collections.unmodifiableSet(options);
+  }
+
+  /**
+   * Generate the possible command-line option names by prefixing each class name from {@code
+   * classPrefixes} to {@code options}, separated by {@link #OPTION_SEPARATOR}.
+   *
+   * @param clazzPrefixes the classes to prefix
+   * @param options the option names
+   * @return the possible combinations that should be supported
+   */
+  protected Collection<String> expandCFOptions(
+      List<? extends Class<?>> clazzPrefixes, String[] options) {
+    Set<String> res = new HashSet<>();
+
+    for (String option : options) {
+      res.add(option);
+      for (Class<?> clazz : clazzPrefixes) {
+        res.add(clazz.getCanonicalName() + OPTION_SEPARATOR + option);
+        res.add(clazz.getSimpleName() + OPTION_SEPARATOR + option);
+      }
+    }
+    return res;
+  }
+
+  /**
+   * Overrides the default implementation to always return a singleton set containing only "*".
+   *
+   * <p>javac uses this list to determine which classes process; javac only runs an annotation
+   * processor on classes that contain at least one of the mentioned annotations. Thus, the effect
+   * of returning "*" is as if the checker were annotated by {@code @SupportedAnnotationTypes("*")}:
+   * javac runs the checker on every class mentioned on the javac command line. This method also
+   * checks that subclasses do not contain a {@link SupportedAnnotationTypes} annotation.
+   *
+   * <p>To specify the annotations that a checker recognizes as type qualifiers, see {@link
+   * AnnotatedTypeFactory#createSupportedTypeQualifiers()}.
+   *
+   * @throws Error if a subclass is annotated with {@link SupportedAnnotationTypes}
+   */
+  @Override
+  public final Set<String> getSupportedAnnotationTypes() {
+
+    SupportedAnnotationTypes supported =
+        this.getClass().getAnnotation(SupportedAnnotationTypes.class);
+    if (supported != null) {
+      throw new BugInCF(
+          "@SupportedAnnotationTypes should not be written on any checker;"
+              + " supported annotation types are inherited from SourceChecker.");
+    }
+    return Collections.singleton("*");
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Warning suppression and unneeded warnings
+  ///
+
+  /**
+   * Returns the argument to -AsuppressWarnings, split on commas, or null if no such argument. Only
+   * ever called once; the value is cached in field {@link #suppressWarningsStringsFromOption}.
+   *
+   * @return the argument to -AsuppressWarnings, split on commas, or null if no such argument
+   */
+  private String @Nullable [] getSuppressWarningsStringsFromOption() {
+    Map<String, String> options = getOptions();
+    if (this.suppressWarningsStringsFromOption == null) {
+      if (!options.containsKey("suppressWarnings")) {
+        return null;
+      }
+
+      String swStrings = options.get("suppressWarnings");
+      if (swStrings == null) {
+        return null;
+      }
+      this.suppressWarningsStringsFromOption = swStrings.split(",");
+    }
+
+    return this.suppressWarningsStringsFromOption;
+  }
+
+  /**
+   * Issues a warning about any {@code @SuppressWarnings} that didn't suppress a warning, but starts
+   * with this checker name or "allcheckers".
+   */
+  protected void warnUnneededSuppressions() {
+    if (!hasOption("warnUnneededSuppressions")) {
+      return;
+    }
+
+    Set<Element> elementsSuppress = new HashSet<>(this.elementsWithSuppressedWarnings);
+    this.elementsWithSuppressedWarnings.clear();
+    Set<String> prefixes = new HashSet<>(getSuppressWarningsPrefixes());
+    Set<String> errorKeys = new HashSet<>(messagesProperties.stringPropertyNames());
+    warnUnneededSuppressions(elementsSuppress, prefixes, errorKeys);
+    getVisitor().treesWithSuppressWarnings.clear();
+  }
+
+  /**
+   * Issues a warning about any {@code @SuppressWarnings} string that didn't suppress a warning, but
+   * starts with one of the given prefixes (checker names).
+   *
+   * @param elementsSuppress elements with a {@code @SuppressWarnings} that actually suppressed a
+   *     warning
+   * @param prefixes the SuppressWarnings prefixes that suppress all warnings from this checker
+   * @param allErrorKeys all error keys that can be issued by this checker
+   */
+  protected void warnUnneededSuppressions(
+      Set<Element> elementsSuppress, Set<String> prefixes, Set<String> allErrorKeys) {
+    for (Tree tree : getVisitor().treesWithSuppressWarnings) {
+      Element elt = TreeUtils.elementFromTree(tree);
+      // TODO: This test is too coarse.  The fact that this @SuppressWarnings suppressed
+      // *some* warning doesn't mean that every value in it did so.
+      if (elementsSuppress.contains(elt)) {
+        continue;
+      }
+      // tree has a @SuppressWarnings annotation that didn't suppress any warnings.
+      SuppressWarnings suppressAnno = elt.getAnnotation(SuppressWarnings.class);
+      String[] suppressWarningsStrings = suppressAnno.value();
+      for (String suppressWarningsString : suppressWarningsStrings) {
+        if (warnUnneededSuppressionsExceptions != null
+            && warnUnneededSuppressionsExceptions.matcher(suppressWarningsString).find(0)) {
+          continue;
+        }
+        for (String prefix : prefixes) {
+          if (suppressWarningsString.equals(prefix)
+              || suppressWarningsString.startsWith(prefix + ":")) {
+            reportUnneededSuppression(tree, suppressWarningsString);
+            break; // Don't report the same warning string more than once.
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Issues a warning that the string in a {@code @SuppressWarnings} on {@code tree} isn't needed.
+   *
+   * @param tree has unneeded {@code @SuppressWarnings}
+   * @param suppressWarningsString the SuppressWarnings string that isn't needed
+   */
+  private void reportUnneededSuppression(Tree tree, String suppressWarningsString) {
+    Tree swTree = findSuppressWarningsTree(tree);
+    report(
+        swTree,
+        Kind.MANDATORY_WARNING,
+        SourceChecker.UNNEEDED_SUPPRESSION_KEY,
+        "\"" + suppressWarningsString + "\"",
+        getClass().getSimpleName());
+  }
+
+  /** The name of the @SuppressWarnings annotation. */
+  private final @CanonicalName String suppressWarningsClassName =
+      SuppressWarnings.class.getCanonicalName();
+  /**
+   * Finds the tree that is a {@code @SuppressWarnings} annotation.
+   *
+   * @param tree a class, method, or variable tree annotated with {@code @SuppressWarnings}
+   * @return tree for {@code @SuppressWarnings} or {@code default} if one isn't found
+   */
+  private Tree findSuppressWarningsTree(Tree tree) {
+    List<? extends AnnotationTree> annotations;
+    if (TreeUtils.isClassTree(tree)) {
+      annotations = ((ClassTree) tree).getModifiers().getAnnotations();
+    } else if (tree.getKind() == Tree.Kind.METHOD) {
+      annotations = ((MethodTree) tree).getModifiers().getAnnotations();
+    } else {
+      annotations = ((VariableTree) tree).getModifiers().getAnnotations();
+    }
+
+    for (AnnotationTree annotationTree : annotations) {
+      if (AnnotationUtils.areSameByName(
+          TreeUtils.annotationFromAnnotationTree(annotationTree), suppressWarningsClassName)) {
+        return annotationTree;
+      }
+    }
+    throw new BugInCF("Did not find @SuppressWarnings: " + tree);
+  }
+
+  /**
+   * Returns true if all the warnings pertaining to the given source should be suppressed. This
+   * implementation just that delegates to an overloaded, more specific version of {@code
+   * shouldSuppressWarnings()}.
+   *
+   * @param src the position object to test; may be an Element, a Tree, or null
+   * @param errKey the error key the checker is emitting
+   * @return true if all warnings pertaining to the given source should be suppressed
+   * @see #shouldSuppressWarnings(Element, String)
+   * @see #shouldSuppressWarnings(Tree, String)
+   */
+  private boolean shouldSuppressWarnings(@Nullable Object src, String errKey) {
+    if (src instanceof Element) {
+      return shouldSuppressWarnings((Element) src, errKey);
+    } else if (src instanceof Tree) {
+      return shouldSuppressWarnings((Tree) src, errKey);
+    } else if (src == null) {
+      return false;
+    } else {
+      throw new BugInCF("Unexpected source " + src);
+    }
+  }
+
+  /**
+   * Determines whether all the warnings pertaining to a given tree should be suppressed. Returns
+   * true if the tree is within the scope of a @SuppressWarnings annotation, one of whose values
+   * suppresses the checker's warnings. Also, returns true if the {@code errKey} matches a string in
+   * {@code -AsuppressWarnings}.
+   *
+   * @param tree the tree that might be a source of a warning
+   * @param errKey the error key the checker is emitting
+   * @return true if no warning should be emitted for the given tree because it is contained by a
+   *     declaration with an appropriately-valued {@literal @}SuppressWarnings annotation; false
+   *     otherwise
+   */
+  public boolean shouldSuppressWarnings(Tree tree, String errKey) {
+
+    Collection<String> prefixes = getSuppressWarningsPrefixes();
+    if (prefixes.isEmpty() || (prefixes.contains(SUPPRESS_ALL_PREFIX) && prefixes.size() == 1)) {
+      throw new BugInCF(
+          "Checker must provide a SuppressWarnings prefix."
+              + " SourceChecker#getSuppressWarningsPrefixes was not overridden correctly.");
+    }
+    if (shouldSuppress(getSuppressWarningsStringsFromOption(), errKey)) {
+      return true;
+    }
+
+    if (shouldSuppress(getSuppressWarningsStringsFromOption(), errKey)) {
+      // If the error key matches a warning string in the -AsuppressWarnings, then suppress
+      // the warning.
+      return true;
+    }
+
+    // trees.getPath might be slow, but this is only used in error reporting
+    @Nullable TreePath path = trees.getPath(this.currentRoot, tree);
+
+    @Nullable VariableTree var = TreePathUtil.enclosingVariable(path);
+    if (var != null && shouldSuppressWarnings(TreeUtils.elementFromTree(var), errKey)) {
+      return true;
+    }
+
+    @Nullable MethodTree method = TreePathUtil.enclosingMethod(path);
+    if (method != null) {
+      @Nullable Element elt = TreeUtils.elementFromTree(method);
+
+      if (shouldSuppressWarnings(elt, errKey)) {
+        return true;
+      }
+
+      if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) {
+        // Return false immediately. Do NOT check for AnnotatedFor in the enclosing elements,
+        // because they may not have an @AnnotatedFor.
+        return false;
+      }
+    }
+
+    @Nullable ClassTree cls = TreePathUtil.enclosingClass(path);
+    if (cls != null) {
+      @Nullable Element elt = TreeUtils.elementFromTree(cls);
+
+      if (shouldSuppressWarnings(elt, errKey)) {
+        return true;
+      }
+
+      if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) {
+        // Return false immediately. Do NOT check for AnnotatedFor in the enclosing elements,
+        // because they may not have an @AnnotatedFor.
+        return false;
+      }
+    }
+
+    if (useConservativeDefault("source")) {
+      // If we got this far without hitting an @AnnotatedFor and returning
+      // false, we DO suppress the warning.
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Should conservative defaults be used for the kind of unchecked code indicated by the parameter?
+   *
+   * @param kindOfCode source or bytecode
+   * @return whether conservative defaults should be used
+   */
+  public boolean useConservativeDefault(String kindOfCode) {
+    final boolean useUncheckedDefaultsForSource = false;
+    final boolean useUncheckedDefaultsForByteCode = false;
+    String option = this.getOption("useConservativeDefaultsForUncheckedCode");
+    // Temporary, for backward compatibility.
+    if (option == null) {
+      this.getOption("useDefaultsForUncheckedCode");
+    }
+
+    String[] args = option != null ? option.split(",") : new String[0];
+    for (String arg : args) {
+      boolean value = arg.indexOf("-") != 0;
+      arg = value ? arg : arg.substring(1);
+      if (arg.equals(kindOfCode)) {
+        return value;
+      }
+    }
+    if (kindOfCode.equals("source")) {
+      return useUncheckedDefaultsForSource;
+    } else if (kindOfCode.equals("bytecode")) {
+      return useUncheckedDefaultsForByteCode;
+    } else {
+      throw new UserError(
+          "SourceChecker: unexpected argument to useConservativeDefault: " + kindOfCode);
+    }
+  }
+
+  /**
+   * Elements with a {@code @SuppressWarnings} that actually suppressed a warning for this checker.
+   */
+  protected final Set<Element> elementsWithSuppressedWarnings = new HashSet<>();
+
+  /**
+   * Determines whether all the warnings pertaining to a given element should be suppressed. Returns
+   * true if the element is within the scope of a @SuppressWarnings annotation, one of whose values
+   * suppresses all the checker's warnings.
+   *
+   * @param elt the Element that might be a source of, or related to, a warning
+   * @param errKey the error key the checker is emitting
+   * @return true if no warning should be emitted for the given Element because it is contained by a
+   *     declaration with an appropriately-valued {@code @SuppressWarnings} annotation; false
+   *     otherwise
+   */
+  public boolean shouldSuppressWarnings(@Nullable Element elt, String errKey) {
+    if (UNNEEDED_SUPPRESSION_KEY.equals(errKey)) {
+      // Never suppress an "unneeded.suppression" warning.
+      // TODO: This choice is questionable, because these warnings should be suppressable just
+      // like any others.  The reason for the choice is that if a user writes
+      // `@SuppressWarnings("nullness")` that isn't needed, then that annotation would
+      // suppress the unneeded suppression warning.  It would take extra work to permit more
+      // desirable behavior in that case.
+      return false;
+    }
+
+    if (shouldSuppress(getSuppressWarningsStringsFromOption(), errKey)) {
+      return true;
+    }
+
+    while (elt != null) {
+      SuppressWarnings suppressWarningsAnno = elt.getAnnotation(SuppressWarnings.class);
+      if (suppressWarningsAnno != null) {
+        String[] suppressWarningsStrings = suppressWarningsAnno.value();
+        if (shouldSuppress(suppressWarningsStrings, errKey)) {
+          if (hasOption("warnUnneededSuppressions")) {
+            elementsWithSuppressedWarnings.add(elt);
+          }
+          return true;
+        }
+      }
+      if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) {
+        // Return false immediately. Do NOT check for AnnotatedFor in the
+        // enclosing elements, because they may not have an @AnnotatedFor.
+        return false;
+      }
+      elt = elt.getEnclosingElement();
+    }
+    return false;
+  }
+
+  /**
+   * Determines whether an error (whose message key is {@code messageKey}) should be suppressed. It
+   * is suppressed if any of the given SuppressWarnings strings suppresses it.
+   *
+   * <p>A SuppressWarnings string may be of the following pattern:
+   *
+   * <ol>
+   *   <li>{@code "prefix"}, where prefix is a SuppressWarnings prefix, as specified by {@link
+   *       #getSuppressWarningsPrefixes()}. For example, {@code "nullness"} and {@code
+   *       "initialization"} for the Nullness Checker, {@code "regex"} for the Regex Checker.
+   *   <li>{@code "partial-message-key"}, where partial-message-key is a prefix or suffix of the
+   *       message key that it may suppress. So "generic.argument" would suppress any errors whose
+   *       message key contains "generic.argument".
+   *   <li>{@code "prefix:partial-message-key}, where the prefix and partial-message-key is as
+   *       above. So "nullness:generic.argument", would suppress any errors in the Nullness Checker
+   *       with a message key that contains "generic.argument".
+   * </ol>
+   *
+   * {@code "allcheckers"} is a prefix that suppresses a warning from any checker. {@code "all"} is
+   * a partial-message-key that suppresses a warning with any message key.
+   *
+   * <p>If the {@code -ArequirePrefixInWarningSuppressions} command-line argument was supplied, then
+   * {@code "partial-message-key"} has no effect; {@code "prefix"} and {@code
+   * "prefix:partial-message-key"} are the only SuppressWarnings strings that have an effect.
+   *
+   * @param suppressWarningsStrings the SuppressWarnings strings that are in effect. May be null, in
+   *     which case this method returns false.
+   * @param messageKey the message key of the error the checker is emitting; a lowercase string,
+   *     without any "checkername:" prefix
+   * @return true if an element of {@code suppressWarningsStrings} suppresses the error
+   */
+  private boolean shouldSuppress(String[] suppressWarningsStrings, String messageKey) {
+    Set<String> prefixes = this.getSuppressWarningsPrefixes();
+    return shouldSuppress(prefixes, suppressWarningsStrings, messageKey);
+  }
+
+  /**
+   * Helper method for {@link #shouldSuppress(String[], String)}.
+   *
+   * @param prefixes the SuppressWarnings prefixes used by this checker
+   * @param suppressWarningsStrings the SuppressWarnings strings that are in effect. May be null, in
+   *     which case this method returns false.
+   * @param messageKey the message key of the error the checker is emitting; a lowercase string,
+   *     without any "checkername:" prefix
+   * @return true if one of the {@code suppressWarningsStrings} suppresses the error
+   */
+  private boolean shouldSuppress(
+      Set<String> prefixes, String[] suppressWarningsStrings, String messageKey) {
+    if (suppressWarningsStrings == null) {
+      return false;
+    }
+    // Is the name of the checker required to suppress a warning?
+    boolean requirePrefix = hasOption("requirePrefixInWarningSuppressions");
+
+    for (String suppressWarningsString : suppressWarningsStrings) {
+      int colonPos = suppressWarningsString.indexOf(":");
+      String messageKeyInSuppressWarningsString;
+      if (colonPos == -1) {
+        // The SuppressWarnings string is not of the form prefix:partial-message-key
+        if (prefixes.contains(suppressWarningsString)) {
+          // The value in the @SuppressWarnings is exactly a prefix. Suppress the warning
+          // no matter its message key.
+          return true;
+        } else if (requirePrefix) {
+          // A prefix is required, but this SuppressWarnings string does not have a
+          // prefix; check the next SuppressWarnings string.
+          continue;
+        } else if (suppressWarningsString.equals(SUPPRESS_ALL_MESSAGE_KEY)) {
+          // Prefixes aren't required and the SuppressWarnings string is "all".  Suppress
+          // the warning no matter its message key.
+          return true;
+        }
+        // The suppressWarningsString is not a prefix or a prefix:message-key, so it might
+        // be a message key.
+        messageKeyInSuppressWarningsString = suppressWarningsString;
+      } else {
+        // The SuppressWarnings string has a prefix.
+        String suppressWarningsPrefix = suppressWarningsString.substring(0, colonPos);
+        if (!prefixes.contains(suppressWarningsPrefix)) {
+          // The prefix of this SuppressWarnings string is a not a prefix supported by
+          // this checker. Proceed to the next SuppressWarnings string.
+          continue;
+        }
+        messageKeyInSuppressWarningsString = suppressWarningsString.substring(colonPos + 1);
+      }
+      // Check if the message key in the warning suppression is part of the message key that
+      // the checker is emiting.
+      if (messageKey.equals(messageKeyInSuppressWarningsString)
+          || messageKey.startsWith(messageKeyInSuppressWarningsString + ".")
+          || messageKey.endsWith("." + messageKeyInSuppressWarningsString)
+          || messageKey.contains("." + messageKeyInSuppressWarningsString + ".")) {
+        return true;
+      }
+    }
+
+    // None of the SuppressWarnings strings suppress this error.
+    return false;
+  }
+
+  /**
+   * Return true if the element has an {@code @AnnotatedFor} annotation, for this checker or an
+   * upstream checker that called this one.
+   *
+   * @param elt the source code element to check, or null
+   * @return true if the element is annotated for this checker or an upstream checker
+   */
+  private boolean isAnnotatedForThisCheckerOrUpstreamChecker(@Nullable Element elt) {
+
+    if (elt == null || !useConservativeDefault("source")) {
+      return false;
+    }
+
+    @Nullable AnnotatedFor anno = elt.getAnnotation(AnnotatedFor.class);
+
+    String[] userAnnotatedFors = (anno == null ? null : anno.value());
+
+    if (userAnnotatedFors != null) {
+      List<@FullyQualifiedName String> upstreamCheckerNames = getUpstreamCheckerNames();
+
+      for (String userAnnotatedFor : userAnnotatedFors) {
+        if (CheckerMain.matchesCheckerOrSubcheckerFromList(
+            userAnnotatedFor, upstreamCheckerNames)) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Returns a modifiable set of lower-case strings that are prefixes for SuppressWarnings strings.
+   *
+   * <p>The collection must not be empty and must not contain only {@link #SUPPRESS_ALL_PREFIX}.
+   *
+   * @return non-empty modifiable set of lower-case prefixes for SuppressWarnings strings
+   */
+  public SortedSet<String> getSuppressWarningsPrefixes() {
+    return getStandardSuppressWarningsPrefixes();
+  }
+
+  /**
+   * Returns a sorted set of SuppressWarnings prefixes read from the {@link SuppressWarningsPrefix}
+   * meta-annotation on the checker class. Or if no {@link SuppressWarningsPrefix} is used, the
+   * checker name is used. {@link #SUPPRESS_ALL_PREFIX} is also added, at the end, unless {@link
+   * #useAllcheckersPrefix} is false.
+   *
+   * @return a sorted set of SuppressWarnings prefixes
+   */
+  protected final NavigableSet<String> getStandardSuppressWarningsPrefixes() {
+    NavigableSet<String> prefixes = new TreeSet<>();
+    if (useAllcheckersPrefix) {
+      prefixes.add(SUPPRESS_ALL_PREFIX);
+    }
+    SuppressWarningsPrefix prefixMetaAnno =
+        this.getClass().getAnnotation(SuppressWarningsPrefix.class);
+    if (prefixMetaAnno != null) {
+      for (String prefix : prefixMetaAnno.value()) {
+        prefixes.add(prefix);
+      }
+      return prefixes;
+    }
+
+    @SuppressWarnings("deprecation") // SuppressWarningsKeys was renamed to SuppressWarningsPrefix
+    SuppressWarningsKeys annotation = this.getClass().getAnnotation(SuppressWarningsKeys.class);
+    if (annotation != null) {
+      for (String prefix : annotation.value()) {
+        prefixes.add(prefix);
+      }
+      return prefixes;
+    }
+
+    // No @SuppressWarningsPrefixes annotation, by default infer key from class name.
+    String defaultPrefix = getDefaultSuppressWarningsPrefix();
+    prefixes.add(defaultPrefix);
+    return prefixes;
+  }
+
+  /**
+   * Returns the default SuppressWarnings prefix for this checker based on the checker name.
+   *
+   * @return the default SuppressWarnings prefix for this checker based on the checker name
+   */
+  private String getDefaultSuppressWarningsPrefix() {
+    String className = this.getClass().getSimpleName();
+    int indexOfChecker = className.lastIndexOf("Checker");
+    if (indexOfChecker == -1) {
+      indexOfChecker = className.lastIndexOf("Subchecker");
+    }
+    String result = (indexOfChecker == -1) ? className : className.substring(0, indexOfChecker);
+    return result.toLowerCase();
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Skipping uses and defs
+  ///
+
+  /**
+   * Tests whether the class owner of the passed element is an unannotated class and matches the
+   * pattern specified in the {@code checker.skipUses} property.
+   *
+   * @param element an element
+   * @return true iff the enclosing class of element should be skipped
+   */
+  public final boolean shouldSkipUses(Element element) {
+    if (element == null) {
+      return false;
+    }
+    TypeElement typeElement = ElementUtils.enclosingTypeElement(element);
+    if (typeElement == null) {
+      throw new BugInCF("enclosingTypeElement(%s [%s]) => null%n", element, element.getClass());
+    }
+    @SuppressWarnings("signature:assignment") // TypeElement.toString(): @FullyQualifiedName
+    @FullyQualifiedName String name = typeElement.toString();
+    return shouldSkipUses(name);
+  }
+
+  /**
+   * Tests whether the class owner of the passed type matches the pattern specified in the {@code
+   * checker.skipUses} property. In contrast to {@link #shouldSkipUses(Element)} this version can
+   * also be used from primitive types, which don't have an element.
+   *
+   * <p>Checkers that require their annotations not to be checked on certain JDK classes may
+   * override this method to skip them. They shall call {@code super.shouldSkipUses(typeName)} to
+   * also skip the classes matching the pattern.
+   *
+   * @param typeName the fully-qualified name of a type
+   * @return true iff the enclosing class of element should be skipped
+   */
+  public boolean shouldSkipUses(@FullyQualifiedName String typeName) {
+    // System.out.printf("shouldSkipUses(%s) %s%nskipUses %s%nonlyUses %s%nresult %s%n",
+    //                   element,
+    //                   name,
+    //                   skipUsesPattern.matcher(name).find(),
+    //                   onlyUsesPattern.matcher(name).find(),
+    //                   (skipUsesPattern.matcher(name).find()
+    //                    || ! onlyUsesPattern.matcher(name).find()));
+    // StackTraceElement[] stea = new Throwable().getStackTrace();
+    // for (int i=0; i<3; i++) {
+    //     System.out.println("  " + stea[i]);
+    // }
+    // System.out.println();
+    if (skipUsesPattern == null) {
+      skipUsesPattern = getSkipUsesPattern(getOptions());
+    }
+    if (onlyUsesPattern == null) {
+      onlyUsesPattern = getOnlyUsesPattern(getOptions());
+    }
+    return skipUsesPattern.matcher(typeName).find() || !onlyUsesPattern.matcher(typeName).find();
+  }
+
+  /**
+   * Tests whether the class definition should not be checked because it matches the {@code
+   * checker.skipDefs} property.
+   *
+   * @param node class to potentially skip
+   * @return true if checker should not test node
+   */
+  public final boolean shouldSkipDefs(ClassTree node) {
+    String qualifiedName = TreeUtils.typeOf(node).toString();
+    // System.out.printf("shouldSkipDefs(%s) %s%nskipDefs %s%nonlyDefs %s%nresult %s%n%n",
+    //                   node,
+    //                   qualifiedName,
+    //                   skipDefsPattern.matcher(qualifiedName).find(),
+    //                   onlyDefsPattern.matcher(qualifiedName).find(),
+    //                   (skipDefsPattern.matcher(qualifiedName).find()
+    //                    || ! onlyDefsPattern.matcher(qualifiedName).find()));
+    if (skipDefsPattern == null) {
+      skipDefsPattern = getSkipDefsPattern(getOptions());
+    }
+    if (onlyDefsPattern == null) {
+      onlyDefsPattern = getOnlyDefsPattern(getOptions());
+    }
+
+    return skipDefsPattern.matcher(qualifiedName).find()
+        || !onlyDefsPattern.matcher(qualifiedName).find();
+  }
+
+  /**
+   * Tests whether the method definition should not be checked because it matches the {@code
+   * checker.skipDefs} property.
+   *
+   * <p>TODO: currently only uses the class definition. Refine pattern. Same for skipUses.
+   *
+   * @param cls class to potentially skip
+   * @param meth method to potentially skip
+   * @return true if checker should not test node
+   */
+  public final boolean shouldSkipDefs(ClassTree cls, MethodTree meth) {
+    return shouldSkipDefs(cls);
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Errors other than type-checking errors
+  ///
+
+  /**
+   * Log (that is, print) a user error.
+   *
+   * @param ce the user error to output
+   */
+  private void logUserError(UserError ce) {
+    String msg = ce.getMessage();
+    printMessage(msg);
+  }
+
+  /**
+   * Log (that is, print) a type system error.
+   *
+   * @param ce the type system error to output
+   */
+  private void logTypeSystemError(TypeSystemError ce) {
+    String msg = ce.getMessage();
+    printMessage(msg);
+  }
+
+  /**
+   * Log (that is, print) an internal error in the framework or a checker.
+   *
+   * @param ce the internal error to output
+   */
+  private void logBugInCF(BugInCF ce) {
+    StringJoiner msg = new StringJoiner(LINE_SEPARATOR);
+    if (ce.getCause() != null && ce.getCause() instanceof OutOfMemoryError) {
+      msg.add(
+          String.format(
+              "OutOfMemoryError (max memory = %d, total memory = %d, free memory = %d)",
+              Runtime.getRuntime().maxMemory(),
+              Runtime.getRuntime().totalMemory(),
+              Runtime.getRuntime().freeMemory()));
+    } else {
+      msg.add(ce.getMessage());
+      boolean noPrintErrorStack =
+          (processingEnv != null
+              && processingEnv.getOptions() != null
+              && processingEnv.getOptions().containsKey("noPrintErrorStack"));
+
+      msg.add("; The Checker Framework crashed.  Please report the crash.");
+      if (noPrintErrorStack) {
+        msg.add(" To see the full stack trace, don't invoke the compiler with -AnoPrintErrorStack");
+      } else {
+        if (this.currentRoot != null && this.currentRoot.getSourceFile() != null) {
+          msg.add("Compilation unit: " + this.currentRoot.getSourceFile().getName());
+        }
+
+        if (this.visitor != null) {
+          DiagnosticPosition pos = (DiagnosticPosition) this.visitor.lastVisited;
+          if (pos != null) {
+            DiagnosticSource source = new DiagnosticSource(this.currentRoot.getSourceFile(), null);
+            int linenr = source.getLineNumber(pos.getStartPosition());
+            int col = source.getColumnNumber(pos.getStartPosition(), true);
+            String line = source.getLine(pos.getStartPosition());
+
+            msg.add("Last visited tree at line " + linenr + " column " + col + ":");
+            msg.add(line);
+          }
+        }
+      }
+    }
+
+    msg.add("Exception: " + ce.getCause() + "; " + UtilPlume.stackTraceToString(ce.getCause()));
+    boolean printClasspath = ce.getCause() instanceof NoClassDefFoundError;
+    Throwable cause = ce.getCause().getCause();
+    while (cause != null) {
+      msg.add("Underlying Exception: " + cause + "; " + UtilPlume.stackTraceToString(cause));
+      printClasspath |= cause instanceof NoClassDefFoundError;
+      cause = cause.getCause();
+    }
+
+    if (printClasspath) {
+      msg.add("Classpath:");
+      for (URI uri : new ClassGraph().getClasspathURIs()) {
+        msg.add(uri.toString());
+      }
+    }
+
+    printMessage(msg.toString());
+  }
+
+  /**
+   * Converts a throwable to a BugInCF.
+   *
+   * @param methodName the method that caught the exception (redundant with stack trace)
+   * @param t the throwable to be converted to a BugInCF
+   * @param p what source code was being processed
+   * @return a BugInCF that wraps the given throwable
+   */
+  private BugInCF wrapThrowableAsBugInCF(String methodName, Throwable t, @Nullable TreePath p) {
+    return new BugInCF(
+        methodName
+            + ": unexpected Throwable ("
+            + t.getClass().getSimpleName()
+            + ")"
+            + ((p == null)
+                ? ""
+                : " while processing " + p.getCompilationUnit().getSourceFile().getName())
+            + (t.getMessage() == null ? "" : "; message: " + t.getMessage()),
+        t);
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Shutdown
+  ///
+
+  /**
+   * Return true to indicate that method {@link #shutdownHook} should be added as a shutdownHook of
+   * the JVM.
+   *
+   * @return true to add {@link #shutdownHook} as a shutdown hook of the JVM
+   */
+  protected boolean shouldAddShutdownHook() {
+    return hasOption("resourceStats");
+  }
+
+  /**
+   * Method that gets called exactly once at shutdown time of the JVM. Checkers can override this
+   * method to customize the behavior.
+   */
+  protected void shutdownHook() {
+    if (hasOption("resourceStats")) {
+      // Check for the "resourceStats" option and don't call shouldAddShutdownHook
+      // to allow subclasses to override shouldXXX and shutdownHook and simply
+      // call the super implementations.
+      printStats();
+    }
+  }
+
+  /** Print resource usage statistics. */
+  protected void printStats() {
+    List<MemoryPoolMXBean> memoryPools = ManagementFactory.getMemoryPoolMXBeans();
+    for (MemoryPoolMXBean memoryPool : memoryPools) {
+      System.out.println("Memory pool " + memoryPool.getName() + " statistics");
+      System.out.println("  Pool type: " + memoryPool.getType());
+      System.out.println("  Peak usage: " + memoryPool.getPeakUsage());
+    }
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Miscellaneous
+  ///
+
+  /**
+   * A helper function to parse a Properties file.
+   *
+   * @param cls the class whose location is the base of the file path
+   * @param filePath the name/path of the file to be read
+   * @param permitNonExisting if true, return an empty Properties if the file does not exist or
+   *     cannot be parsed; if false, issue an error
+   * @return the properties
+   */
+  protected Properties getProperties(Class<?> cls, String filePath, boolean permitNonExisting) {
+    Properties prop = new Properties();
+    try {
+      InputStream base = cls.getResourceAsStream(filePath);
+
+      if (base == null) {
+        // The property file was not found.
+        if (permitNonExisting) {
+          return prop;
+        } else {
+          throw new BugInCF("Couldn't locate properties file " + filePath);
+        }
+      }
+
+      prop.load(base);
+    } catch (IOException e) {
+      throw new BugInCF("Couldn't parse properties file: " + filePath, e);
+    }
+    return prop;
+  }
+
+  @Override
+  public final SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latest();
+  }
+
+  /** True if the git.properties file has been printed. */
+  private static boolean gitPropertiesPrinted = false;
+
+  /** Print information about the git repository from which the Checker Framework was compiled. */
+  private void printGitProperties() {
+    if (gitPropertiesPrinted) {
+      return;
+    }
+    gitPropertiesPrinted = true;
+
+    try (InputStream in = getClass().getResourceAsStream("/git.properties");
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in)); ) {
+      String line;
+      while ((line = reader.readLine()) != null) {
+        System.out.println(line);
+      }
+    } catch (IOException e) {
+      System.out.println("IOException while reading git.properties: " + e.getMessage());
+    }
+  }
+
+  /**
+   * Returns the version of the Checker Framework.
+   *
+   * @return the Checker Framework version
+   */
+  private String getCheckerVersion() {
+    Properties gitProperties = getProperties(getClass(), "/git.properties", false);
+    String version = gitProperties.getProperty("git.build.version");
+    if (version != null) {
+      return version;
+    }
+    throw new BugInCF("Could not find the version in git.properties");
+  }
+
+  /**
+   * Gradle and IntelliJ wrap the processing environment to gather information about modifications
+   * done by annotation processor during incremental compilation. But the Checker Framework calls
+   * methods from javac that require the processing environment to be {@code
+   * com.sun.tools.javac.processing.JavacProcessingEnvironment}. They fail if given a proxy. This
+   * method unwraps a proxy if one is used.
+   *
+   * @param env a processing environment
+   * @return unwrapped environment if the argument is a proxy created by IntelliJ or Gradle;
+   *     original value (the argument) if the argument is a javac processing environment
+   * @throws BugInCF if method fails to retrieve {@code
+   *     com.sun.tools.javac.processing.JavacProcessingEnvironment}
+   */
+  private static ProcessingEnvironment unwrapProcessingEnvironment(ProcessingEnvironment env) {
+    if (env.getClass().getName()
+        == "com.sun.tools.javac.processing.JavacProcessingEnvironment") { // interned
+      return env;
+    }
+    // IntelliJ >2020.3 wraps the processing environment in a dynamic proxy.
+    ProcessingEnvironment unwrappedIntelliJ = unwrapIntelliJ(env);
+    if (unwrappedIntelliJ != null) {
+      return unwrapProcessingEnvironment(unwrappedIntelliJ);
+    }
+    // Gradle incremental build also wraps the processing environment.
+    for (Class<?> envClass = env.getClass();
+        envClass != null;
+        envClass = envClass.getSuperclass()) {
+      ProcessingEnvironment unwrappedGradle = unwrapGradle(envClass, env);
+      if (unwrappedGradle != null) {
+        return unwrapProcessingEnvironment(unwrappedGradle);
+      }
+    }
+    throw new BugInCF("Unexpected processing environment: %s %s", env, env.getClass());
+  }
+
+  /**
+   * Tries to unwrap ProcessingEnvironment from proxy in IntelliJ 2020.3 or later.
+   *
+   * @param env possibly a dynamic proxy wrapping processing environment
+   * @return unwrapped processing environment, null if not successful
+   */
+  private static @Nullable ProcessingEnvironment unwrapIntelliJ(ProcessingEnvironment env) {
+    if (!Proxy.isProxyClass(env.getClass())) {
+      return null;
+    }
+    InvocationHandler handler = Proxy.getInvocationHandler(env);
+    try {
+      Field field = handler.getClass().getDeclaredField("val$delegateTo");
+      field.setAccessible(true);
+      Object o = field.get(handler);
+      if (o instanceof ProcessingEnvironment) {
+        return (ProcessingEnvironment) o;
+      }
+      return null;
+    } catch (NoSuchFieldException | IllegalAccessException e) {
+      return null;
+    }
+  }
+
+  /**
+   * Tries to unwrap processing environment in Gradle incremental processing. Inspired by project
+   * Lombok.
+   *
+   * @param delegateClass a class in which to find a {@code delegate} field
+   * @param env a processing environment wrapper
+   * @return unwrapped processing environment, null if not successful
+   */
+  private static @Nullable ProcessingEnvironment unwrapGradle(
+      Class<?> delegateClass, ProcessingEnvironment env) {
+    try {
+      Field field = delegateClass.getDeclaredField("delegate");
+      field.setAccessible(true);
+      Object o = field.get(env);
+      if (o instanceof ProcessingEnvironment) {
+        return (ProcessingEnvironment) o;
+      }
+      return null;
+    } catch (NoSuchFieldException | IllegalAccessException e) {
+      return null;
+    }
+  }
+
+  /**
+   * Return the path to the current compilation unit.
+   *
+   * @return path to the current compilation unit
+   */
+  public TreePath getPathToCompilationUnit() {
+    return TreePath.getPath(currentRoot, currentRoot);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceVisitor.java b/framework/src/main/java/org/checkerframework/framework/source/SourceVisitor.java
new file mode 100644
index 0000000..82021f4
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/source/SourceVisitor.java
@@ -0,0 +1,123 @@
+package org.checkerframework.framework.source;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
+import com.sun.source.util.Trees;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * An AST visitor that provides a variety of compiler utilities and interfaces to facilitate
+ * type-checking.
+ */
+public abstract class SourceVisitor<R, P> extends TreePathScanner<R, P> {
+
+  /** The {@link Trees} instance to use for scanning. */
+  protected final Trees trees;
+
+  /** The {@link Elements} helper to use when scanning. */
+  protected final Elements elements;
+
+  /** The {@link Types} helper to use when scanning. */
+  protected final Types types;
+
+  /** The root of the AST that this {@link SourceVisitor} will scan. */
+  protected CompilationUnitTree root;
+
+  /** A set of trees that are annotated with {@code @SuppressWarnings}. */
+  public final List<Tree> treesWithSuppressWarnings;
+
+  /** Whether or not a warning should be issued for unneeded warning suppressions. */
+  private final boolean warnUnneededSuppressions;
+
+  /**
+   * Creates a {@link SourceVisitor} to use for scanning a source tree.
+   *
+   * @param checker the checker to invoke on the input source tree
+   */
+  protected SourceVisitor(SourceChecker checker) {
+    // Use the checker's processing environment to get the helpers we need.
+    ProcessingEnvironment env = checker.getProcessingEnvironment();
+
+    this.trees = Trees.instance(env);
+    this.elements = env.getElementUtils();
+    this.types = env.getTypeUtils();
+
+    this.treesWithSuppressWarnings = new ArrayList<>();
+    this.warnUnneededSuppressions = checker.hasOption("warnUnneededSuppressions");
+  }
+
+  /**
+   * Set the CompilationUnitTree to be used during any visits. For any later calls of {@code
+   * com.sun.source.util.TreePathScanner.scan(TreePath, P)}, the CompilationUnitTree of the TreePath
+   * has to be equal to {@code root}.
+   */
+  public void setRoot(CompilationUnitTree root) {
+    this.root = root;
+  }
+
+  /**
+   * Store the last Tree visited by the SourceVisitor. This is necessary because the finally blocks
+   * in {@link com.sun.source.util.TreePathScanner#scan(TreePath, Object)} and {@link
+   * com.sun.source.util.TreePathScanner#scan(Tree, Object)} set the visited Path to null. This
+   * field is used to report a rough location for the error in {@link
+   * org.checkerframework.framework.source.SourceChecker#logBugInCF(BugInCF)}.
+   */
+  /*package-private*/ Tree lastVisited;
+
+  /** Entry point for a type processor: the TreePath leaf is a top-level type tree within root. */
+  public void visit(TreePath path) {
+    lastVisited = path.getLeaf();
+    this.scan(path, null);
+  }
+
+  @Override
+  public R scan(Tree tree, P p) {
+    lastVisited = tree;
+    return super.scan(tree, p);
+  }
+
+  @Override
+  public R visitClass(ClassTree classTree, P p) {
+    storeSuppressWarningsAnno(classTree);
+    return super.visitClass(classTree, p);
+  }
+
+  @Override
+  public R visitVariable(VariableTree variableTree, P p) {
+    storeSuppressWarningsAnno(variableTree);
+    return super.visitVariable(variableTree, p);
+  }
+
+  @Override
+  public R visitMethod(MethodTree node, P p) {
+    storeSuppressWarningsAnno(node);
+    return super.visitMethod(node, p);
+  }
+
+  /**
+   * If {@code tree} has a {@code @SuppressWarnings} add it to treesWithSuppressWarnings.
+   *
+   * @param tree a declaration on which a {@code @SuppressWarnings} annotation may be placed
+   */
+  private void storeSuppressWarningsAnno(Tree tree) {
+    if (!warnUnneededSuppressions) {
+      return;
+    }
+    Element elt = TreeUtils.elementFromTree(tree);
+    if (elt.getAnnotation(SuppressWarnings.class) != null) {
+      treesWithSuppressWarnings.add(tree);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/source/SupportedLintOptions.java b/framework/src/main/java/org/checkerframework/framework/source/SupportedLintOptions.java
new file mode 100644
index 0000000..238b7c6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/source/SupportedLintOptions.java
@@ -0,0 +1,32 @@
+package org.checkerframework.framework.source;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// TODO: Are superclasses considered? Should we?
+/**
+ * An annotation used to indicate what lint options a checker supports. For example, if a checker
+ * class (one that extends BaseTypeChecker) is annotated with
+ * {@code @SupportedLintOptions({"dotequals"})}, then the checker accepts the command-line option
+ * {@code -Alint=-dotequals}.
+ *
+ * <p>This annotation is optional and many checkers do not contain an {@code @SupportedLintOptions}
+ * annotation.
+ *
+ * <p>The {@link SourceChecker#getSupportedLintOptions} method can construct its result from the
+ * value of this annotation.
+ *
+ * @see org.checkerframework.framework.source.SupportedOptions
+ * @checker_framework.manual #creating-compiler-interface The checker class: Compiler interface
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface SupportedLintOptions {
+  String[] value();
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/source/SupportedOptions.java b/framework/src/main/java/org/checkerframework/framework/source/SupportedOptions.java
new file mode 100644
index 0000000..2353aea
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/source/SupportedOptions.java
@@ -0,0 +1,27 @@
+package org.checkerframework.framework.source;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation used to indicate what Checker Framework options a checker supports. The {@link
+ * SourceChecker#getSupportedOptions} method constructs its result from the value of this annotation
+ * and additionally prefixing the checker class name.
+ *
+ * <p>In contrast to {@link javax.annotation.processing.SupportedOptions}, note that this qualifier
+ * is {@link Inherited}.
+ *
+ * @see SupportedLintOptions
+ * @see javax.annotation.processing.SupportedOptions
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface SupportedOptions {
+  String[] value();
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsKeys.java b/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsKeys.java
new file mode 100644
index 0000000..a5ef02f
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsKeys.java
@@ -0,0 +1,43 @@
+package org.checkerframework.framework.source;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies the prefixes or checkernames that suppress warnings issued by this checker. When used
+ * as the argument to {@link SuppressWarnings}, any of the given arguments suppresses all warnings
+ * related to the checker. They can also be used as a prefix, followed by a colon and a message key.
+ *
+ * <p>In order for this annotation to have an effect, it must be placed on the declaration of a
+ * class that extends {@link SourceChecker}.
+ *
+ * <p>If this annotation is not present on a checker class, then the lowercase name of the checker
+ * is used by default. The name of the checker is the part of the checker classname that comes
+ * before "Checker" or "Subchecker". If the checker classname is not of this form, then the
+ * classname is the checker name.)
+ *
+ * @checker_framework.manual #suppresswarnings-annotation-syntax
+ * @deprecated Use {@link SuppressWarningsPrefix} instead.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+@Deprecated // 2020-06-26
+// In the manual section #suppresswarnings-annotation-syntax, the term checkername is used instead
+// of prefix.
+public @interface SuppressWarningsKeys {
+
+  /**
+   * Returns array of strings, any one of which causes this checker to suppress a warning when
+   * passed as the argument of {@literal @}{@link SuppressWarnings}.
+   *
+   * @return array of strings, any one of which causes this checker to suppress a warning when
+   *     passed as the argument of {@literal @}{@link SuppressWarnings}
+   */
+  String[] value();
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsPrefix.java b/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsPrefix.java
new file mode 100644
index 0000000..18b2805
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsPrefix.java
@@ -0,0 +1,41 @@
+package org.checkerframework.framework.source;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies the prefixes or checkernames that suppress warnings issued by this checker. When used
+ * as the argument to {@link SuppressWarnings}, any of the given arguments suppresses all warnings
+ * related to the checker. They can also be used as a prefix, followed by a colon and a message key.
+ *
+ * <p>In order for this annotation to have an effect, it must be placed on the declaration of a
+ * class that extends {@link SourceChecker}.
+ *
+ * <p>If this annotation is not present on a checker class, then the lowercase name of the checker
+ * is used by default. The name of the checker is the part of the checker classname that comes
+ * before "Checker" or "Subchecker". If the checker classname is not of this form, then the
+ * classname is the checker name.)
+ *
+ * @checker_framework.manual #suppresswarnings-annotation-syntax
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+// In the manual section #suppresswarnings-annotation-syntax, the term checkername is used instead
+// of prefix.
+public @interface SuppressWarningsPrefix {
+
+  /**
+   * Returns array of strings, any one of which causes this checker to suppress a warning when
+   * passed as the argument of {@literal @}{@link SuppressWarnings}.
+   *
+   * @return array of strings, any one of which causes this checker to suppress a warning when
+   *     passed as the argument of {@literal @}{@link SuppressWarnings}
+   */
+  String[] value();
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/source/messages.properties b/framework/src/main/java/org/checkerframework/framework/source/messages.properties
new file mode 100644
index 0000000..0875938
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/source/messages.properties
@@ -0,0 +1,3 @@
+annotation.not.completed=Element %s contains annotation %s which can't be found. Make sure your classpath is set correctly.
+unneeded.suppression=warning suppression %s is not used by %s
+type.checking.not.run=%s did not run because of a previous error issued by javac
diff --git a/framework/src/main/java/org/checkerframework/framework/source/package-info.java b/framework/src/main/java/org/checkerframework/framework/source/package-info.java
new file mode 100644
index 0000000..56a0e0c
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/source/package-info.java
@@ -0,0 +1,11 @@
+/**
+ * Contains the essential functionality for interfacing a compile-time (source) type-checker plug-in
+ * to the Java compiler. This allows a checker to use the compiler's error reporting mechanism and
+ * to access abstract syntax trees and compiler utility classes.
+ *
+ * <p>Most classes won't want to extend the classes in this package directly; the classes in the
+ * {@code org.checkerframework.common.basetype} package provide subtype checking functionality.
+ *
+ * @checker_framework.manual #creating-a-checker Writing a Checker
+ */
+package org.checkerframework.framework.source;
diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java b/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java
new file mode 100644
index 0000000..d2fa2dd
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java
@@ -0,0 +1,247 @@
+package org.checkerframework.framework.stub;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import scenelib.annotations.Annotation;
+import scenelib.annotations.Annotations;
+import scenelib.annotations.el.ABlock;
+import scenelib.annotations.el.AClass;
+import scenelib.annotations.el.ADeclaration;
+import scenelib.annotations.el.AElement;
+import scenelib.annotations.el.AExpression;
+import scenelib.annotations.el.AField;
+import scenelib.annotations.el.AMethod;
+import scenelib.annotations.el.AScene;
+import scenelib.annotations.el.ATypeElement;
+import scenelib.annotations.el.ATypeElementWithType;
+import scenelib.annotations.el.AnnotationDef;
+import scenelib.annotations.el.DefException;
+import scenelib.annotations.el.ElementVisitor;
+import scenelib.annotations.field.ArrayAFT;
+import scenelib.annotations.field.BasicAFT;
+import scenelib.annotations.io.IndexFileParser;
+import scenelib.annotations.io.IndexFileWriter;
+import scenelib.annotations.io.ParseException;
+
+/**
+ * Utility that generates {@code @AnnotatedFor} class annotations. The {@link #main} method acts as
+ * a filter: it reads a JAIF from standard input and writes an augmented JAIF to standard output.
+ */
+public class AddAnnotatedFor {
+  /** Definition of {@code @AnnotatedFor} annotation. */
+  private static AnnotationDef adAnnotatedFor;
+
+  static {
+    Class<?> annotatedFor = org.checkerframework.framework.qual.AnnotatedFor.class;
+    Set<Annotation> annotatedForMetaAnnotations = new HashSet<>(2);
+    annotatedForMetaAnnotations.add(Annotations.aRetentionSource);
+    annotatedForMetaAnnotations.add(
+        Annotations.createValueAnnotation(
+            Annotations.adTarget, Arrays.asList("TYPE", "METHOD", "CONSTRUCTOR", "PACKAGE")));
+    @SuppressWarnings(
+        "signature") // TODO bug: AnnotationDef requires @BinaryName, gets CanonicalName
+    @BinaryName String name = annotatedFor.getCanonicalName();
+    adAnnotatedFor =
+        new AnnotationDef(
+            name,
+            annotatedForMetaAnnotations,
+            Collections.singletonMap("value", new ArrayAFT(BasicAFT.forType(String.class))),
+            "AddAnnotatedFor.<clinit>");
+  }
+
+  /**
+   * Reads JAIF from the file indicated by the first element, or standard input if the argument
+   * array is empty; inserts any appropriate {@code @AnnotatedFor} annotations, based on the
+   * annotations defined in the input JAIF; and writes the augmented JAIF to standard output.
+   */
+  public static void main(String[] args) throws IOException, DefException, ParseException {
+    AScene scene = new AScene();
+    String filename;
+    Reader r;
+    if (args.length > 0) {
+      filename = args[0];
+      r = new FileReader(filename);
+    } else {
+      filename = "System.in";
+      r = new InputStreamReader(System.in);
+    }
+    IndexFileParser.parse(new LineNumberReader(r), filename, scene);
+    scene.prune();
+    addAnnotatedFor(scene);
+    IndexFileWriter.write(scene, new PrintWriter(System.out, true));
+  }
+
+  /**
+   * Add {@code @AnnotatedFor} annotations to each class in the given scene.
+   *
+   * @param scene an {@code @AnnotatedFor} annotation is added to each class in this scene
+   */
+  public static void addAnnotatedFor(AScene scene) {
+    for (AClass clazz : new HashSet<>(scene.classes.values())) {
+      Set<String> annotatedFor = new HashSet<>(2); // usually few @AnnotatedFor are applicable
+      clazz.accept(annotatedForVisitor, annotatedFor);
+      if (!annotatedFor.isEmpty()) {
+        // Set eliminates duplicates, but it must be converted to List; for whatever reason,
+        // IndexFileWriter recognizes array arguments only in List form.
+        List<String> annotatedForList = new ArrayList<>(annotatedFor);
+        clazz.tlAnnotationsHere.add(
+            new Annotation(adAnnotatedFor, Annotations.valueFieldOnly(annotatedForList)));
+      }
+    }
+  }
+
+  /**
+   * This visitor collects the names of all the type systems, one of whose annotations is written.
+   * These need to be the arguments to an {@code AnnotatedFor} annotation on the class, so that all
+   * of the given type systems are run.
+   */
+  private static ElementVisitor<Void, Set<String>> annotatedForVisitor =
+      new ElementVisitor<Void, Set<String>>() {
+        @Override
+        public Void visitAnnotationDef(AnnotationDef el, final Set<String> annotatedFor) {
+          return null;
+        }
+
+        @Override
+        public Void visitBlock(ABlock el, final Set<String> annotatedFor) {
+          for (AField e : el.locals.values()) {
+            e.accept(this, annotatedFor);
+          }
+          return visitExpression(el, annotatedFor);
+        }
+
+        @Override
+        public Void visitClass(AClass el, final Set<String> annotatedFor) {
+          for (ATypeElement e : el.bounds.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (ATypeElement e : el.extendsImplements.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (AExpression e : el.fieldInits.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (AField e : el.fields.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (ABlock e : el.instanceInits.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (AMethod e : el.methods.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (ABlock e : el.staticInits.values()) {
+            e.accept(this, annotatedFor);
+          }
+          return visitDeclaration(el, annotatedFor);
+        }
+
+        @Override
+        public Void visitDeclaration(ADeclaration el, final Set<String> annotatedFor) {
+          for (ATypeElement e : el.insertAnnotations.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (ATypeElementWithType e : el.insertTypecasts.values()) {
+            e.accept(this, annotatedFor);
+          }
+          return visitElement(el, annotatedFor);
+        }
+
+        @Override
+        public Void visitExpression(AExpression el, final Set<String> annotatedFor) {
+          for (ATypeElement e : el.calls.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (AMethod e : el.funs.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (ATypeElement e : el.instanceofs.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (ATypeElement e : el.news.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (ATypeElement e : el.refs.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (ATypeElement e : el.typecasts.values()) {
+            e.accept(this, annotatedFor);
+          }
+          return visitElement(el, annotatedFor);
+        }
+
+        @Override
+        public Void visitField(AField el, final Set<String> annotatedFor) {
+          if (el.init != null) {
+            el.init.accept(this, annotatedFor);
+          }
+          return visitDeclaration(el, annotatedFor);
+        }
+
+        @Override
+        public Void visitMethod(AMethod el, final Set<String> annotatedFor) {
+          if (el.body != null) {
+            el.body.accept(this, annotatedFor);
+          }
+          if (el.receiver != null) {
+            el.receiver.accept(this, annotatedFor);
+          }
+          if (el.returnType != null) {
+            el.returnType.accept(this, annotatedFor);
+          }
+          for (ATypeElement e : el.bounds.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (AField e : el.parameters.values()) {
+            e.accept(this, annotatedFor);
+          }
+          for (ATypeElement e : el.throwsException.values()) {
+            e.accept(this, annotatedFor);
+          }
+          return visitDeclaration(el, annotatedFor);
+        }
+
+        @Override
+        public Void visitTypeElement(ATypeElement el, final Set<String> annotatedFor) {
+          for (ATypeElement e : el.innerTypes.values()) {
+            e.accept(this, annotatedFor);
+          }
+          return visitElement(el, annotatedFor);
+        }
+
+        @Override
+        public Void visitTypeElementWithType(
+            ATypeElementWithType el, final Set<String> annotatedFor) {
+          return visitTypeElement(el, annotatedFor);
+        }
+
+        @Override
+        public Void visitElement(AElement el, final Set<String> annotatedFor) {
+          for (Annotation a : el.tlAnnotationsHere) {
+            String s = a.def().name;
+            int j = s.indexOf(".qual.");
+            if (j > 0) {
+              int i = s.lastIndexOf('.', j - 1);
+              if (i > 0 && j - i > 1) {
+                annotatedFor.add(s.substring(i + 1, j));
+              }
+            }
+          }
+          if (el.type != null) {
+            el.type.accept(this, annotatedFor);
+          }
+          return null;
+        }
+      };
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java
new file mode 100644
index 0000000..407a234
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java
@@ -0,0 +1,716 @@
+package org.checkerframework.framework.stub;
+
+import com.sun.source.tree.CompilationUnitTree;
+import io.github.classgraph.ClassGraph;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.JarURLConnection;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.tools.Diagnostic.Kind;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.StubFiles;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.stub.AnnotationFileParser.AnnotationFileAnnotations;
+import org.checkerframework.framework.stub.AnnotationFileUtil.AnnotationFileType;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.SystemUtil;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Holds information about types parsed from annotation files (stub files or ajava files). When
+ * using an ajava file, only holds information on public elements as with stub files.
+ */
+public class AnnotationFileElementTypes {
+  /** Annotations from annotation files (but not from annotated JDK files). */
+  private final AnnotationFileAnnotations annotationFileAnnos;
+
+  /**
+   * Whether or not a file is currently being parsed. (If one is being parsed, don't try to parse
+   * another.)
+   */
+  private boolean parsing;
+
+  /** AnnotatedTypeFactory. */
+  private final AnnotatedTypeFactory factory;
+
+  /**
+   * Mapping from fully-qualified class name to corresponding JDK stub file from the file system. By
+   * contrast, {@link #jdkStubFilesJar} contains JDK stub files from checker.jar.
+   */
+  private final Map<String, Path> jdkStubFiles = new HashMap<>();
+
+  /**
+   * Mapping from fully-qualified class name to corresponding JDK stub files from checker.jar. By
+   * contrast, {@link #jdkStubFiles} contains JDK stub files from the file system.
+   */
+  private final Map<String, String> jdkStubFilesJar = new HashMap<>();
+
+  /** Which version number of the annotated JDK should be used? */
+  private final String annotatedJdkVersion;
+
+  /** Should the JDK be parsed? */
+  private final boolean shouldParseJdk;
+
+  /** Parse all JDK files at startup rather than as needed. */
+  private final boolean parseAllJdkFiles;
+
+  /**
+   * Creates an empty annotation source.
+   *
+   * @param factory AnnotatedTypeFactory
+   */
+  public AnnotationFileElementTypes(AnnotatedTypeFactory factory) {
+    this.factory = factory;
+    this.annotationFileAnnos = new AnnotationFileAnnotations();
+    this.parsing = false;
+    String release = SystemUtil.getReleaseValue(factory.getProcessingEnv());
+    this.annotatedJdkVersion =
+        release != null ? release : String.valueOf(SystemUtil.getJreVersion());
+
+    this.shouldParseJdk = !factory.getChecker().hasOption("ignorejdkastub");
+    this.parseAllJdkFiles = factory.getChecker().hasOption("parseAllJdk");
+  }
+
+  /**
+   * Returns true if files are currently being parsed; otherwise, false.
+   *
+   * @return true if files are currently being parsed; otherwise, false
+   */
+  public boolean isParsing() {
+    return parsing;
+  }
+
+  /**
+   * Parses the stub files in the following order:
+   *
+   * <ol>
+   *   <li>jdk.astub in the same directory as the checker, if it exists and ignorejdkastub option is
+   *       not supplied
+   *   <li>If parsing annotated JDK as stub files, all package-info.java files under the jdk/
+   *       directory
+   *   <li>Stub files listed in @StubFiles annotation on the checker; must be in same directory as
+   *       the checker
+   *   <li>Stub files returned by {@link BaseTypeChecker#getExtraStubFiles} (treated like those
+   *       listed in @StubFiles annotation)
+   *   <li>Stub files provided via {@code -Astubs} compiler option
+   * </ol>
+   *
+   * <p>If a type is annotated with a qualifier from the same hierarchy in more than one stub file,
+   * the qualifier in the last stub file is applied.
+   *
+   * <p>If using JDK 11, then the JDK stub files are only parsed if a type or declaration annotation
+   * is requested from a class in that file.
+   */
+  public void parseStubFiles() {
+    parsing = true;
+    BaseTypeChecker checker = factory.getChecker();
+    ProcessingEnvironment processingEnv = factory.getProcessingEnv();
+    // 1. jdk.astub
+    // Only look in .jar files, and parse it right away.
+    if (!checker.hasOption("ignorejdkastub")) {
+      InputStream jdkStubIn = checker.getClass().getResourceAsStream("jdk.astub");
+      if (jdkStubIn != null) {
+        AnnotationFileParser.parseStubFile(
+            checker.getClass().getResource("jdk.astub").toString(),
+            jdkStubIn,
+            factory,
+            processingEnv,
+            annotationFileAnnos,
+            AnnotationFileType.BUILTIN_STUB);
+      }
+      String jdkVersionStub = "jdk" + annotatedJdkVersion + ".astub";
+      InputStream jdkVersionStubIn = checker.getClass().getResourceAsStream(jdkVersionStub);
+      if (jdkVersionStubIn != null) {
+        AnnotationFileParser.parseStubFile(
+            checker.getClass().getResource(jdkVersionStub).toString(),
+            jdkVersionStubIn,
+            factory,
+            processingEnv,
+            annotationFileAnnos,
+            AnnotationFileType.BUILTIN_STUB);
+      }
+
+      // 2. Annotated JDK
+      // This preps but does not parse the JDK files (except package-info.java files).
+      // The JDK source code files will be parsed later, on demand.
+      prepJdkStubs();
+      // prepping the JDK parses all package-info.java files, which sets the `parsing` field to
+      // false, so re-set it to true.
+      parsing = true;
+    }
+
+    // 3. Stub files listed in @StubFiles annotation on the checker
+    StubFiles stubFilesAnnotation = checker.getClass().getAnnotation(StubFiles.class);
+    if (stubFilesAnnotation != null) {
+      parseAnnotationFiles(
+          Arrays.asList(stubFilesAnnotation.value()), AnnotationFileType.BUILTIN_STUB);
+    }
+
+    // 4. Stub files returned by the `getExtraStubFiles()` method
+    parseAnnotationFiles(checker.getExtraStubFiles(), AnnotationFileType.BUILTIN_STUB);
+
+    // 5. Stub files provided via -Astubs command-line option
+    String stubsOption = checker.getOption("stubs");
+    if (stubsOption != null) {
+      parseAnnotationFiles(
+          Arrays.asList(stubsOption.split(File.pathSeparator)),
+          AnnotationFileType.COMMAND_LINE_STUB);
+    }
+
+    parsing = false;
+  }
+
+  /** Parses the ajava files passed through the -Aajava command-line option. */
+  public void parseAjavaFiles() {
+    parsing = true;
+    // TODO: Error if this is called more than once?
+    SourceChecker checker = factory.getChecker();
+    List<String> ajavaFiles = new ArrayList<>();
+    String ajavaOption = checker.getOption("ajava");
+    if (ajavaOption != null) {
+      Collections.addAll(ajavaFiles, ajavaOption.split(File.pathSeparator));
+    }
+
+    parseAnnotationFiles(ajavaFiles, AnnotationFileType.AJAVA);
+    parsing = false;
+  }
+
+  /**
+   * Parses the ajava file at {@code ajavaPath} assuming {@code root} represents the compilation
+   * unit of that file. Uses {@code root} to get information from javac on specific elements of
+   * {@code ajavaPath}, enabling storage of more detailed annotation information than with just the
+   * ajava file.
+   *
+   * @param ajavaPath path to an ajava file
+   * @param root javac tree for the compilation unit stored in {@code ajavaFile}
+   */
+  public void parseAjavaFileWithTree(String ajavaPath, CompilationUnitTree root) {
+    parsing = true;
+    SourceChecker checker = factory.getChecker();
+    ProcessingEnvironment processingEnv = factory.getProcessingEnv();
+    try {
+      InputStream in = new FileInputStream(ajavaPath);
+      AnnotationFileParser.parseAjavaFile(
+          ajavaPath, in, root, factory, processingEnv, annotationFileAnnos);
+    } catch (IOException e) {
+      checker.message(Kind.NOTE, "Could not read ajava file: " + ajavaPath);
+    }
+
+    parsing = false;
+  }
+
+  /**
+   * Parses the files in {@code annotationFiles} of the given file type. This includes files listed
+   * directly in {@code annotationFiles} and for each listed directory, also includes all files
+   * located in that directory (recursively).
+   *
+   * @param annotationFiles list of files and directories to parse
+   * @param fileType the file type of files to parse
+   */
+  private void parseAnnotationFiles(List<String> annotationFiles, AnnotationFileType fileType) {
+    SourceChecker checker = factory.getChecker();
+    ProcessingEnvironment processingEnv = factory.getProcessingEnv();
+    for (String path : annotationFiles) {
+      // Special case when running in jtreg.
+      String base = System.getProperty("test.src");
+      String fullPath = (base == null) ? path : base + "/" + path;
+
+      List<AnnotationFileResource> allFiles =
+          AnnotationFileUtil.allAnnotationFiles(fullPath, fileType);
+      if (allFiles != null) {
+        for (AnnotationFileResource resource : allFiles) {
+          InputStream annotationFileStream;
+          try {
+            annotationFileStream = resource.getInputStream();
+          } catch (IOException e) {
+            checker.message(
+                Kind.NOTE, "Could not read annotation resource: " + resource.getDescription());
+            continue;
+          }
+          // We use parseStubFile here even for ajava files because at this stage ajava
+          // files are parsed as stub files. The extra annotation data in an ajava file is
+          // parsed when type-checking the ajava file's corresponding Java file.
+          AnnotationFileParser.parseStubFile(
+              resource.getDescription(),
+              annotationFileStream,
+              factory,
+              processingEnv,
+              annotationFileAnnos,
+              fileType == AnnotationFileType.AJAVA ? AnnotationFileType.AJAVA_AS_STUB : fileType);
+        }
+      } else {
+        // We didn't find the files.
+        // If the file has a prefix of "checker.jar/" then look for the file in the top
+        // level directory of the jar that contains the checker.
+        if (path.startsWith("checker.jar/")) {
+          path = path.substring("checker.jar/".length());
+        }
+        InputStream in = checker.getClass().getResourceAsStream(path);
+        if (in != null) {
+          AnnotationFileParser.parseStubFile(
+              path, in, factory, processingEnv, annotationFileAnnos, fileType);
+        } else {
+          // Didn't find the file.  Issue a warning.
+
+          // When using a compound checker, the target file may be found by the
+          // current checker's parent checkers. Also check this to avoid a false
+          // warning. Currently, only the original checker will try to parse the target
+          // file, the parent checkers are only used to reduce false warnings.
+          SourceChecker currentChecker = checker;
+          boolean findByParentCheckers = false;
+          while (currentChecker != null) {
+            URL topLevelResource = currentChecker.getClass().getResource("/" + path);
+            if (topLevelResource != null) {
+              currentChecker.message(
+                  Kind.WARNING,
+                  path
+                      + " should be in the same directory as "
+                      + currentChecker.getClass().getSimpleName()
+                      + ".class, but is at the top level of a jar file: "
+                      + topLevelResource);
+              findByParentCheckers = true;
+              break;
+            } else {
+              currentChecker = currentChecker.getParentChecker();
+            }
+          }
+          // If there exists one parent checker that can find this file, don't report a warning.
+          if (!findByParentCheckers) {
+            File parentPath = new File(path).getParentFile();
+            String parentPathDescription =
+                (parentPath == null
+                    ? "current directory"
+                    : "directory " + parentPath.getAbsolutePath());
+            String msg =
+                checker.getClass().getSimpleName()
+                    + " did not find annotation file or directory "
+                    + path
+                    + " on classpath or within "
+                    + parentPathDescription
+                    + (fullPath.equals(path) ? "" : (" or at " + fullPath));
+            StringJoiner sj = new StringJoiner(System.lineSeparator() + "  ");
+            sj.add(msg);
+            sj.add("Classpath:");
+            for (URI uri : new ClassGraph().getClasspathURIs()) {
+              sj.add(uri.toString());
+            }
+            checker.message(Kind.WARNING, sj.toString());
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns the annotated type for {@code e} containing only annotations explicitly written in an
+   * annotation file or {@code null} if {@code e} does not appear in an annotation file.
+   *
+   * @param e an Element whose type is returned
+   * @return an AnnotatedTypeMirror for {@code e} containing only annotations explicitly written in
+   *     the annotation file and in the element. {@code null} is returned if {@code element} does
+   *     not appear in an annotation file.
+   */
+  public AnnotatedTypeMirror getAnnotatedTypeMirror(Element e) {
+    if (parsing) {
+      return null;
+    }
+    parseEnclosingClass(e);
+    AnnotatedTypeMirror type = annotationFileAnnos.atypes.get(e);
+    return type == null ? null : type.deepCopy();
+  }
+
+  /**
+   * Returns the set of declaration annotations for {@code e} containing only annotations explicitly
+   * written in an annotation file or the empty set if {@code e} does not appear in an annotation
+   * file.
+   *
+   * @param elt element for which annotations are returned
+   * @return an AnnotatedTypeMirror for {@code e} containing only annotations explicitly written in
+   *     the annotation file and in the element. {@code null} is returned if {@code element} does
+   *     not appear in an annotation file.
+   */
+  public Set<AnnotationMirror> getDeclAnnotation(Element elt) {
+    if (parsing) {
+      return Collections.emptySet();
+    }
+
+    parseEnclosingClass(elt);
+    String eltName = ElementUtils.getQualifiedName(elt);
+    if (annotationFileAnnos.declAnnos.containsKey(eltName)) {
+      return annotationFileAnnos.declAnnos.get(eltName);
+    }
+    return Collections.emptySet();
+  }
+
+  /**
+   * Returns the method type of the most specific fake override for the given element, when used as
+   * a member of the given type.
+   *
+   * @param elt element for which annotations are returned
+   * @param receiverType the type of the class that contains member (or a subtype of it)
+   * @return the most specific AnnotatedTypeMirror for {@code elt} that is a fake override, or null
+   *     if there are no fake overrides
+   */
+  public @Nullable AnnotatedTypeMirror getFakeOverride(
+      Element elt, AnnotatedTypeMirror receiverType) {
+    if (parsing) {
+      throw new BugInCF("parsing while calling getFakeOverride");
+    }
+
+    if (elt.getKind() != ElementKind.METHOD) {
+      return null;
+    }
+
+    ExecutableElement method = (ExecutableElement) elt;
+
+    TypeMirror methodReceiverType = method.getReceiverType();
+    if (methodReceiverType != null && methodReceiverType.getKind() == TypeKind.NONE) {
+      return null;
+    }
+
+    // This is a list of pairs of (where defined, method type) for fake overrides.  The second
+    // element of each pair is currently always an AnnotatedExecutableType.
+    List<Pair<TypeMirror, AnnotatedTypeMirror>> candidates =
+        annotationFileAnnos.fakeOverrides.get(elt);
+
+    if (candidates == null || candidates.isEmpty()) {
+      return null;
+    }
+
+    TypeMirror receiverTypeMirror = receiverType.getUnderlyingType();
+
+    // A list of fake receiver types.
+    List<TypeMirror> applicableClasses = new ArrayList<>();
+    List<TypeMirror> applicableInterfaces = new ArrayList<>();
+    for (Pair<TypeMirror, AnnotatedTypeMirror> candidatePair : candidates) {
+      TypeMirror fakeLocation = candidatePair.first;
+      AnnotatedExecutableType candidate = (AnnotatedExecutableType) candidatePair.second;
+      if (factory.types.isSameType(receiverTypeMirror, fakeLocation)) {
+        return candidate;
+      } else if (factory.types.isSubtype(receiverTypeMirror, fakeLocation)) {
+        TypeElement fakeElement = TypesUtils.getTypeElement(fakeLocation);
+        switch (fakeElement.getKind()) {
+          case CLASS:
+          case ENUM:
+            applicableClasses.add(fakeLocation);
+            break;
+          case INTERFACE:
+          case ANNOTATION_TYPE:
+            applicableInterfaces.add(fakeLocation);
+            break;
+          default:
+            throw new BugInCF(
+                "What type? %s %s %s", fakeElement.getKind(), fakeElement.getClass(), fakeElement);
+        }
+      }
+    }
+
+    if (applicableClasses.isEmpty() && applicableInterfaces.isEmpty()) {
+      return null;
+    }
+    TypeMirror fakeReceiverType =
+        TypesUtils.mostSpecific(
+            !applicableClasses.isEmpty() ? applicableClasses : applicableInterfaces,
+            factory.getProcessingEnv());
+    if (fakeReceiverType == null) {
+      StringJoiner message = new StringJoiner(System.lineSeparator());
+      message.add(
+          String.format(
+              "No most specific fake override found for %s with receiver %s."
+                  + " These fake overrides are applicable:",
+              elt, receiverTypeMirror));
+      for (TypeMirror candidate : applicableClasses) {
+        message.add("  class candidate: " + candidate);
+      }
+      for (TypeMirror candidate : applicableInterfaces) {
+        message.add("  interface candidate: " + candidate);
+      }
+      throw new BugInCF(message.toString());
+    }
+
+    for (Pair<TypeMirror, AnnotatedTypeMirror> candidatePair : candidates) {
+      TypeMirror candidateReceiverType = candidatePair.first;
+      if (factory.types.isSameType(fakeReceiverType, candidateReceiverType)) {
+        return (AnnotatedExecutableType) candidatePair.second;
+      }
+    }
+
+    throw new BugInCF(
+        "No match for %s in %s %s %s",
+        fakeReceiverType, candidates, applicableClasses, applicableInterfaces);
+  }
+
+  ///
+  /// End of public methods, private helper methods follow
+  ///
+
+  /**
+   * Parses the outermost enclosing class of {@code e} if there exists an annotation file for it and
+   * it has not already been parsed.
+   *
+   * @param e element whose outermost enclosing class will be parsed
+   */
+  private void parseEnclosingClass(Element e) {
+    if (!shouldParseJdk) {
+      return;
+    }
+    String className = getOutermostEnclosingClass(e);
+    if (className == null || className.isEmpty()) {
+      return;
+    }
+    if (jdkStubFiles.containsKey(className)) {
+      parseJdkStubFile(jdkStubFiles.get(className));
+      jdkStubFiles.remove(className);
+    } else if (jdkStubFilesJar.containsKey(className)) {
+      parseJdkJarEntry(jdkStubFilesJar.get(className));
+      jdkStubFilesJar.remove(className);
+    }
+  }
+
+  /**
+   * Returns the fully qualified name of the outermost enclosing class of {@code e} or {@code null}
+   * if no such class exists for {@code e}.
+   *
+   * @param e an element whose outermost enclosing class to return
+   * @return the canonical name of the outermost enclosing class of {@code e} or {@code null} if no
+   *     class encloses {@code e}
+   */
+  private @CanonicalNameOrEmpty String getOutermostEnclosingClass(Element e) {
+    TypeElement enclosingClass = ElementUtils.enclosingTypeElement(e);
+    if (enclosingClass == null) {
+      return null;
+    }
+    while (true) {
+      Element element = enclosingClass.getEnclosingElement();
+      if (element == null || element.getKind() == ElementKind.PACKAGE) {
+        break;
+      }
+      TypeElement t = ElementUtils.enclosingTypeElement(element);
+      if (t == null) {
+        break;
+      }
+      enclosingClass = t;
+    }
+    @SuppressWarnings("signature:assignment" // https://tinyurl.com/cfissue/658:
+    // Name.toString should be @PolySignature
+    )
+    @CanonicalNameOrEmpty String result = enclosingClass.getQualifiedName().toString();
+    return result;
+  }
+
+  /**
+   * Parses the stub file in {@code path}.
+   *
+   * @param path path to file to parse
+   */
+  private void parseJdkStubFile(Path path) {
+    parsing = true;
+    try (FileInputStream jdkStub = new FileInputStream(path.toFile())) {
+      AnnotationFileParser.parseJdkFileAsStub(
+          path.toFile().getName(),
+          jdkStub,
+          factory,
+          factory.getProcessingEnv(),
+          annotationFileAnnos);
+    } catch (IOException e) {
+      throw new BugInCF("cannot open the jdk stub file " + path, e);
+    } finally {
+      parsing = false;
+    }
+  }
+
+  /**
+   * Parses the stub file in the given jar entry.
+   *
+   * @param jarEntryName name of the jar entry to parse
+   */
+  private void parseJdkJarEntry(String jarEntryName) {
+    JarURLConnection connection = getJarURLConnectionToJdk();
+    parsing = true;
+    try (JarFile jarFile = connection.getJarFile()) {
+      InputStream jdkStub;
+      try {
+        jdkStub = jarFile.getInputStream(jarFile.getJarEntry(jarEntryName));
+      } catch (IOException e) {
+        throw new BugInCF("cannot open the jdk stub file " + jarEntryName, e);
+      }
+      AnnotationFileParser.parseJdkFileAsStub(
+          jarEntryName, jdkStub, factory, factory.getProcessingEnv(), annotationFileAnnos);
+    } catch (IOException e) {
+      throw new BugInCF("cannot open the Jar file " + connection.getEntryName(), e);
+    } catch (BugInCF e) {
+      throw new BugInCF("Exception while parsing " + jarEntryName + ": " + e.getMessage(), e);
+    } finally {
+      parsing = false;
+    }
+  }
+
+  /**
+   * Returns a JarURLConnection to "/jdk*".
+   *
+   * @return a JarURLConnection to "/jdk*"
+   */
+  private JarURLConnection getJarURLConnectionToJdk() {
+    URL resourceURL = factory.getClass().getResource("/annotated-jdk");
+    JarURLConnection connection;
+    try {
+      connection = (JarURLConnection) resourceURL.openConnection();
+
+      // disable caching / connection sharing of the low level URLConnection to the Jarfile
+      connection.setDefaultUseCaches(false);
+      connection.setUseCaches(false);
+
+      connection.connect();
+    } catch (IOException e) {
+      throw new BugInCF("cannot open a connection to the Jar file " + resourceURL.getFile(), e);
+    }
+    return connection;
+  }
+
+  /**
+   * Walk through the jdk directory and create a mapping, {@link #jdkStubFiles}, from file name to
+   * the class contained with in it. Also, parses all package-info.java files.
+   */
+  private void prepJdkStubs() {
+    if (!shouldParseJdk) {
+      return;
+    }
+    URL resourceURL = factory.getClass().getResource("/annotated-jdk");
+    if (resourceURL == null) {
+      if (factory.getChecker().hasOption("permitMissingJdk")
+          // temporary, for backward compatibility
+          || factory.getChecker().hasOption("nocheckjdk")) {
+        return;
+      }
+      throw new BugInCF("JDK not found");
+    } else if (resourceURL.getProtocol().contentEquals("jar")) {
+      prepJdkFromJar(resourceURL);
+    } else if (resourceURL.getProtocol().contentEquals("file")) {
+      prepJdkFromFile(resourceURL);
+    } else {
+      if (factory.getChecker().hasOption("permitMissingJdk")
+          // temporary, for backward compatibility
+          || factory.getChecker().hasOption("nocheckjdk")) {
+        return;
+      }
+      throw new BugInCF("JDK not found");
+    }
+  }
+
+  /**
+   * Walk through the JDK directory and create a mapping, {@link #jdkStubFiles}, from file name to
+   * the class contained with in it. Also, parses all package-info.java files.
+   *
+   * @param resourceURL the URL pointing to the JDK directory
+   */
+  private void prepJdkFromFile(URL resourceURL) {
+    Path root;
+    try {
+      root = Paths.get(resourceURL.toURI());
+    } catch (URISyntaxException e) {
+      throw new BugInCF("Can parse URL: " + resourceURL.toString(), e);
+    }
+
+    try (Stream<Path> walk = Files.walk(root)) {
+      List<Path> paths =
+          walk.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".java"))
+              .collect(Collectors.toList());
+      for (Path path : paths) {
+        if (path.getFileName().toString().equals("package-info.java")) {
+          parseJdkStubFile(path);
+          continue;
+        }
+        if (path.getFileName().toString().equals("module-info.java")) {
+          // JavaParser can't parse module-info files, so skip them.
+          continue;
+        }
+        if (parseAllJdkFiles) {
+          parseJdkStubFile(path);
+          continue;
+        }
+        Path relativePath = root.relativize(path);
+        // 4: /src/<module>/share/classes
+        Path savepath = relativePath.subpath(4, relativePath.getNameCount());
+        String s = savepath.toString().replace(".java", "").replace(File.separatorChar, '.');
+        jdkStubFiles.put(s, path);
+      }
+    } catch (IOException e) {
+      throw new BugInCF("prepJdkFromFile(" + resourceURL + ")", e);
+    }
+  }
+
+  /**
+   * Walk through the JDK directory and create a mapping, {@link #jdkStubFilesJar}, from file name
+   * to the class contained with in it. Also, parses all package-info.java files.
+   *
+   * @param resourceURL the URL pointing to the JDK directory
+   */
+  private void prepJdkFromJar(URL resourceURL) {
+    JarURLConnection connection = getJarURLConnectionToJdk();
+
+    try (JarFile jarFile = connection.getJarFile()) {
+      for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements(); ) {
+        JarEntry jarEntry = e.nextElement();
+        // filter out directories and non-class files
+        if (!jarEntry.isDirectory()
+            && jarEntry.getName().endsWith(".java")
+            && jarEntry.getName().startsWith("annotated-jdk")
+            // JavaParser can't parse module-info files, so skip them.
+            && !jarEntry.getName().contains("module-info")) {
+          String jarEntryName = jarEntry.getName();
+          if (parseAllJdkFiles) {
+            parseJdkJarEntry(jarEntryName);
+            continue;
+          }
+          int index = jarEntry.getName().indexOf("/share/classes/");
+          String shortName =
+              jarEntryName
+                  .substring(index + "/share/classes/".length())
+                  .replace(".java", "")
+                  .replace('/', '.');
+          jdkStubFilesJar.put(shortName, jarEntryName);
+          if (jarEntryName.endsWith("package-info.java")) {
+            parseJdkJarEntry(jarEntryName);
+          }
+        }
+      }
+    } catch (IOException e) {
+      throw new BugInCF("cannot open the Jar file " + resourceURL.getFile(), e);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java
new file mode 100644
index 0000000..4ca7079
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java
@@ -0,0 +1,2901 @@
+package org.checkerframework.framework.stub;
+
+import com.github.javaparser.ParseProblemException;
+import com.github.javaparser.Position;
+import com.github.javaparser.Problem;
+import com.github.javaparser.ast.AccessSpecifier;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.ImportDeclaration;
+import com.github.javaparser.ast.Modifier;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.NodeList;
+import com.github.javaparser.ast.PackageDeclaration;
+import com.github.javaparser.ast.StubUnit;
+import com.github.javaparser.ast.body.AnnotationDeclaration;
+import com.github.javaparser.ast.body.BodyDeclaration;
+import com.github.javaparser.ast.body.CallableDeclaration;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.ConstructorDeclaration;
+import com.github.javaparser.ast.body.EnumConstantDeclaration;
+import com.github.javaparser.ast.body.EnumDeclaration;
+import com.github.javaparser.ast.body.FieldDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.Parameter;
+import com.github.javaparser.ast.body.ReceiverParameter;
+import com.github.javaparser.ast.body.TypeDeclaration;
+import com.github.javaparser.ast.body.VariableDeclarator;
+import com.github.javaparser.ast.expr.AnnotationExpr;
+import com.github.javaparser.ast.expr.ArrayInitializerExpr;
+import com.github.javaparser.ast.expr.BooleanLiteralExpr;
+import com.github.javaparser.ast.expr.CharLiteralExpr;
+import com.github.javaparser.ast.expr.ClassExpr;
+import com.github.javaparser.ast.expr.DoubleLiteralExpr;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.FieldAccessExpr;
+import com.github.javaparser.ast.expr.IntegerLiteralExpr;
+import com.github.javaparser.ast.expr.LongLiteralExpr;
+import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
+import com.github.javaparser.ast.expr.MemberValuePair;
+import com.github.javaparser.ast.expr.NameExpr;
+import com.github.javaparser.ast.expr.NormalAnnotationExpr;
+import com.github.javaparser.ast.expr.NullLiteralExpr;
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
+import com.github.javaparser.ast.expr.StringLiteralExpr;
+import com.github.javaparser.ast.expr.UnaryExpr;
+import com.github.javaparser.ast.nodeTypes.NodeWithRange;
+import com.github.javaparser.ast.nodeTypes.modifiers.NodeWithAccessModifiers;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import com.github.javaparser.ast.type.PrimitiveType;
+import com.github.javaparser.ast.type.ReferenceType;
+import com.github.javaparser.ast.type.Type;
+import com.github.javaparser.ast.type.TypeParameter;
+import com.github.javaparser.ast.type.WildcardType;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.VariableTree;
+import java.io.File;
+import java.io.InputStream;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.framework.ajava.DefaultJointVisitor;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.qual.FromStubFile;
+import org.checkerframework.framework.stub.AnnotationFileUtil.AnnotationFileType;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.util.JavaParserUtil;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.ErrorTypeKindException;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreeUtils;
+
+// From an implementation perspective, this class represents a single annotation file (stub file or
+// ajava file), notably its annotated types and its declaration annotations.
+// From a client perspective, it has static methods as described below in the Javadoc.
+/**
+ * This class has three static methods. Each method parses an annotation file and adds annotations
+ * to the {@link AnnotationFileAnnotations} passed as an argument.
+ *
+ * <p>The first main entry point is {@link #parseStubFile(String, InputStream, AnnotatedTypeFactory,
+ * ProcessingEnvironment, AnnotationFileAnnotations, AnnotationFileUtil.AnnotationFileType)}, which
+ * side-effects its last argument. It operates in two steps. First, it calls the Annotation File
+ * Parser to parse an annotation file. Then, it walks the AST to create/collect types and
+ * declaration annotations.
+ *
+ * <p>The second main entry point is {@link #parseAjavaFile(String, InputStream,
+ * CompilationUnitTree, AnnotatedTypeFactory, ProcessingEnvironment, AnnotationFileAnnotations)}.
+ * This behaves the same as {@link AnnotationFileParser#parseStubFile(String, InputStream,
+ * AnnotatedTypeFactory, ProcessingEnvironment, AnnotationFileAnnotations,
+ * AnnotationFileUtil.AnnotationFileType)}, but takes an ajava file instead.
+ *
+ * <p>The other entry point is {@link #parseJdkFileAsStub}.
+ */
+public class AnnotationFileParser {
+
+  /**
+   * The type of file being parsed: stub file or ajava file. Also indicates its source, such as from
+   * the JDK, built in, or from the command line.
+   *
+   * <p>Non-JDK stub files override JDK stub files. (Ordinarily, if two stubs are provided, they are
+   * merged.)
+   *
+   * <p>For a built-in stub file,
+   *
+   * <ul>
+   *   <li>private declarations are ignored,
+   *   <li>some warning messages are not issued, and
+   * </ul>
+   */
+  private final AnnotationFileType fileType;
+
+  /**
+   * If parsing an ajava file, represents the javac tree for the compilation root of the file being
+   * parsed.
+   */
+  private CompilationUnitTree root;
+
+  /**
+   * Whether to print warnings about types/members that were not found. The warning states that a
+   * class/field in the file is not found on the user's real classpath. Since the file may contain
+   * packages that are not on the classpath, this can be OK, so default to false.
+   */
+  private final boolean warnIfNotFound;
+
+  /**
+   * Whether to ignore missing classes even when warnIfNotFound is set to true. This allows the
+   * files to contain classes not in the classpath (even if another class in the classpath has the
+   * same package), but still warn if members of the class (methods, fields) are missing. This
+   * option does nothing unless warnIfNotFound is also set.
+   */
+  private final boolean warnIfNotFoundIgnoresClasses;
+
+  /** Whether to print warnings about stub files that overwrite annotations from bytecode. */
+  private final boolean warnIfStubOverwritesBytecode;
+
+  /**
+   * Whether to print warnings about stub files that are redundant with annotations from bytecode.
+   */
+  private final boolean warnIfStubRedundantWithBytecode;
+
+  /** The diagnostic kind for stub file warnings: NOTE or WARNING. */
+  private final Diagnostic.Kind stubWarnDiagnosticKind;
+
+  /** Whether to print verbose debugging messages. */
+  private final boolean debugAnnotationFileParser;
+
+  /** The name of the file being processed; used only for diagnostic messages. */
+  private final String filename;
+
+  /**
+   * The AST of the parsed file that this class is processing. May be null if there was a problem
+   * parsing the file. (TODO: Should the Checker Framework just halt in that case?)
+   */
+  // Not final in order to accommodate a default value.
+  private StubUnit stubUnit;
+
+  private final ProcessingEnvironment processingEnv;
+  private final AnnotatedTypeFactory atypeFactory;
+  private final Elements elements;
+
+  /**
+   * The set of annotations found in the file. Keys are both fully-qualified and simple names. There
+   * are two entries for each annotation: the annotation's simple name and its fully-qualified name.
+   *
+   * <p>The map is populated from import statements and also by {@link #getAnnotation(
+   * AnnotationExpr, Map)} for annotations that are used fully-qualified.
+   *
+   * @see #getImportedAnnotations
+   */
+  private Map<String, TypeElement> allAnnotations;
+
+  /**
+   * A list of the fully-qualified names of enum constants and static fields with constant values
+   * that have been imported.
+   */
+  private final List<@FullyQualifiedName String> importedConstants = new ArrayList<>();
+
+  /** A map of imported fully-qualified type names to type elements. */
+  private final Map<String, TypeElement> importedTypes = new HashMap<>();
+
+  /** The annotation {@code @FromStubFile}. */
+  private final AnnotationMirror fromStubFileAnno;
+
+  /**
+   * List of AnnotatedTypeMirrors for class or method type parameters that are in scope of the
+   * elements currently parsed.
+   */
+  private final List<AnnotatedTypeVariable> typeParameters = new ArrayList<>();
+
+  /**
+   * The annotations on the declared package of the complation unit being processed. Contains null
+   * if not processing a compilation unit or if the file has no declared package.
+   */
+  @Nullable List<AnnotationExpr> packageAnnos;
+
+  // The following variables are stored in the AnnotationFileParser because otherwise they would
+  // need to be passed through everywhere, which would be verbose.
+
+  /**
+   * The name of the type that is currently being parsed. After processing a package declaration but
+   * before processing a type declaration, the type part of this may be null.
+   *
+   * <p>It is used both for resolving symbols and for error messages.
+   */
+  private FqName typeBeingParsed;
+
+  /**
+   * Contains the annotations of the file currently being processed, or null if not currently
+   * processing a file. The {@code process*} methods side-effect this data structure.
+   */
+  @Nullable AnnotationFileAnnotations annotationFileAnnos;
+
+  /** The line separator. */
+  private static final String LINE_SEPARATOR = System.lineSeparator().intern();
+
+  /** Whether or not the {@code -AmergeStubsWithSource} command-line argument was passed. */
+  private final boolean mergeStubsWithSource;
+
+  /**
+   * The result of calling AnnotationFileParser.parse: the annotated types and declaration
+   * annotations from the file.
+   */
+  public static class AnnotationFileAnnotations {
+
+    /**
+     * Map from element to its type as declared in the annotation file.
+     *
+     * <p>This is a fine-grained mapping that contains all sorts of elements; contrast with {@link
+     * #fakeOverrides}.
+     */
+    public final Map<Element, AnnotatedTypeMirror> atypes = new HashMap<>();
+
+    /**
+     * Map from a name (actually declaration element string) to the set of declaration annotations
+     * on it, as written in the annotation file.
+     *
+     * <p>Map keys cannot be Element, because a different Element appears in the annotation files
+     * than in the real files. So, map keys are the verbose element name, as returned by
+     * ElementUtils.getQualifiedName.
+     */
+    public final Map<String, Set<AnnotationMirror>> declAnnos = new HashMap<>(1);
+
+    /**
+     * Map from a method element to all the fake overrides of it. Given a key {@code ee}, the fake
+     * overrides are always in subtypes of {@code ee.getEnclosingElement()}, which is the same as
+     * {@code ee.getReceiverType()}.
+     */
+    public final Map<ExecutableElement, List<Pair<TypeMirror, AnnotatedTypeMirror>>> fakeOverrides =
+        new HashMap<>(1);
+  }
+
+  /**
+   * Create a new AnnotationFileParser object, which will parse and extract annotations from the
+   * given file.
+   *
+   * @param filename name of annotation file, used only for diagnostic messages
+   * @param atypeFactory AnnotatedTypeFactory to use
+   * @param processingEnv ProcessingEnvironment to use
+   * @param fileType the type of file being parsed (stub file or ajava file) and its source
+   */
+  private AnnotationFileParser(
+      String filename,
+      AnnotatedTypeFactory atypeFactory,
+      ProcessingEnvironment processingEnv,
+      AnnotationFileType fileType) {
+    this.filename = filename;
+    this.atypeFactory = atypeFactory;
+    this.processingEnv = processingEnv;
+    this.elements = processingEnv.getElementUtils();
+    this.fileType = fileType;
+    this.root = null;
+
+    // TODO: This should use SourceChecker.getOptions() to allow
+    // setting these flags per checker.
+    Map<String, String> options = processingEnv.getOptions();
+    this.warnIfNotFound = fileType.isCommandLine() || options.containsKey("stubWarnIfNotFound");
+    this.warnIfNotFoundIgnoresClasses = options.containsKey("stubWarnIfNotFoundIgnoresClasses");
+    this.warnIfStubOverwritesBytecode = options.containsKey("stubWarnIfOverwritesBytecode");
+    this.warnIfStubRedundantWithBytecode =
+        options.containsKey("stubWarnIfRedundantWithBytecode")
+            && atypeFactory.shouldWarnIfStubRedundantWithBytecode();
+    this.stubWarnDiagnosticKind =
+        options.containsKey("stubWarnNote") ? Diagnostic.Kind.NOTE : Diagnostic.Kind.WARNING;
+    this.debugAnnotationFileParser = options.containsKey("stubDebug");
+
+    this.fromStubFileAnno = AnnotationBuilder.fromClass(elements, FromStubFile.class);
+
+    this.mergeStubsWithSource = atypeFactory.getChecker().hasOption("mergeStubsWithSource");
+  }
+
+  /**
+   * Sets the root of the file currently being parsed to {@code root}.
+   *
+   * @param root compilation unit for the file being parsed
+   */
+  private void setRoot(CompilationUnitTree root) {
+    this.root = root;
+  }
+
+  /**
+   * All annotations defined in the package (but not those nested within classes in the package).
+   * Keys are both fully-qualified and simple names.
+   *
+   * @param packageElement a package
+   * @return a map from annotation name to TypeElement
+   */
+  public static Map<String, TypeElement> annosInPackage(PackageElement packageElement) {
+    return createNameToAnnotationMap(ElementFilter.typesIn(packageElement.getEnclosedElements()));
+  }
+
+  /**
+   * All annotations declared (directly) within a class. Keys are both fully-qualified and simple
+   * names.
+   *
+   * @param typeElement a type
+   * @return a map from annotation name to TypeElement
+   */
+  public static Map<String, TypeElement> annosInType(TypeElement typeElement) {
+    return createNameToAnnotationMap(ElementFilter.typesIn(typeElement.getEnclosedElements()));
+  }
+
+  /**
+   * All annotations declared within any of the given elements.
+   *
+   * @param typeElements the elements whose annotations to retrieve
+   * @return a map from annotation names (both fully-qualified and simple names) to TypeElement
+   */
+  public static Map<String, TypeElement> createNameToAnnotationMap(List<TypeElement> typeElements) {
+    Map<String, TypeElement> result = new HashMap<>();
+    for (TypeElement typeElm : typeElements) {
+      if (typeElm.getKind() == ElementKind.ANNOTATION_TYPE) {
+        putIfAbsent(result, typeElm.getSimpleName().toString(), typeElm);
+        putIfAbsent(result, typeElm.getQualifiedName().toString(), typeElm);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Get all members of a Type that are importable in an annotation file. Currently these are values
+   * of enums, or compile time constants.
+   *
+   * @param typeElement the type whose members to return
+   * @return a list of fully-qualified member names
+   */
+  private static List<@FullyQualifiedName String> getImportableMembers(TypeElement typeElement) {
+    List<VariableElement> memberElements =
+        ElementFilter.fieldsIn(typeElement.getEnclosedElements());
+    List<@FullyQualifiedName String> result = new ArrayList<>();
+    for (VariableElement varElement : memberElements) {
+      if (varElement.getConstantValue() != null
+          || varElement.getKind() == ElementKind.ENUM_CONSTANT) {
+        @SuppressWarnings("signature") // string concatenation
+        @FullyQualifiedName String fqName =
+            typeElement.getQualifiedName().toString() + "." + varElement.getSimpleName().toString();
+        result.add(fqName);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns all annotations imported by the annotation file, as a value for {@link
+   * #allAnnotations}. Note that this also modifies {@link #importedConstants} and {@link
+   * #importedTypes}.
+   *
+   * <p>This method misses annotations that are not imported. The {@link #getAnnotation} method
+   * compensates for this deficiency by adding any fully-qualified annotation that it encounters.
+   *
+   * @return a map from names to TypeElement, for all annotations imported by the annotation file.
+   *     Two entries for each annotation: one for the simple name and another for the
+   *     fully-qualified name, with the same value.
+   * @see #allAnnotations
+   */
+  private Map<String, TypeElement> getImportedAnnotations() {
+    Map<String, TypeElement> result = new HashMap<>();
+
+    // TODO: The size can be greater than 1, but this ignores all but the first element.
+    assert !stubUnit.getCompilationUnits().isEmpty();
+    CompilationUnit cu = stubUnit.getCompilationUnits().get(0);
+
+    if (cu.getImports() == null) {
+      return result;
+    }
+
+    for (ImportDeclaration importDecl : cu.getImports()) {
+      try {
+        if (importDecl.isAsterisk()) {
+          @SuppressWarnings("signature" // https://tinyurl.com/cfissue/3094:
+          // com.github.javaparser.ast.expr.Name inherits toString,
+          // so there can be no annotation for it
+          )
+          @DotSeparatedIdentifiers String imported = importDecl.getName().toString();
+          if (importDecl.isStatic()) {
+            // Wildcard import of members of a type (class or interface)
+            TypeElement element = getTypeElement(imported, "Imported type not found", importDecl);
+            if (element != null) {
+              // Find nested annotations
+              // Find compile time constant fields, or values of an enum
+              putAllNew(result, annosInType(element));
+              importedConstants.addAll(getImportableMembers(element));
+              addEnclosingTypesToImportedTypes(element);
+            }
+
+          } else {
+            // Wildcard import of members of a package
+            PackageElement element = findPackage(imported, importDecl);
+            if (element != null) {
+              putAllNew(result, annosInPackage(element));
+              addEnclosingTypesToImportedTypes(element);
+            }
+          }
+        } else {
+          // A single (non-wildcard) import.
+          @SuppressWarnings("signature" // importDecl is non-wildcard, so its name is
+          // @FullyQualifiedName
+          )
+          @FullyQualifiedName String imported = importDecl.getNameAsString();
+
+          final TypeElement importType = elements.getTypeElement(imported);
+          if (importType == null && !importDecl.isStatic()) {
+            // Class or nested class (according to JSL), but we can't resolve
+
+            stubWarnNotFound(importDecl, "Imported type not found: " + imported);
+          } else if (importType == null) {
+            // static import of field or method.
+
+            Pair<@FullyQualifiedName String, String> typeParts =
+                AnnotationFileUtil.partitionQualifiedName(imported);
+            String type = typeParts.first;
+            String fieldName = typeParts.second;
+            TypeElement enclType =
+                getTypeElement(
+                    type,
+                    String.format("Enclosing type of static field %s not found", fieldName),
+                    importDecl);
+
+            if (enclType != null) {
+              // Don't use findFieldElement(enclType, fieldName), because we don't
+              // want a warning, imported might be a method.
+              for (VariableElement field : ElementUtils.getAllFieldsIn(enclType, elements)) {
+                // field.getSimpleName() is a CharSequence, not a String
+                if (fieldName.equals(field.getSimpleName().toString())) {
+                  importedConstants.add(imported);
+                }
+              }
+            }
+
+          } else if (importType.getKind() == ElementKind.ANNOTATION_TYPE) {
+            // Single annotation or nested annotation
+            TypeElement annoElt = elements.getTypeElement(imported);
+            if (annoElt != null) {
+              putIfAbsent(result, annoElt.getSimpleName().toString(), annoElt);
+              importedTypes.put(annoElt.getSimpleName().toString(), annoElt);
+            } else {
+              stubWarnNotFound(importDecl, "Could not load import: " + imported);
+            }
+          } else {
+            // Class or nested class
+            // TODO: Is this needed?
+            importedConstants.add(imported);
+            TypeElement element = getTypeElement(imported, "Imported type not found", importDecl);
+            importedTypes.put(element.getSimpleName().toString(), element);
+          }
+        }
+      } catch (AssertionError error) {
+        stubWarnNotFound(importDecl, error.toString());
+      }
+    }
+    return result;
+  }
+
+  // If a member is imported, then consider every containing class to also be imported.
+  private void addEnclosingTypesToImportedTypes(Element element) {
+    for (Element enclosedEle : element.getEnclosedElements()) {
+      if (enclosedEle.getKind().isClass()) {
+        importedTypes.put(enclosedEle.getSimpleName().toString(), (TypeElement) enclosedEle);
+      }
+    }
+  }
+
+  /**
+   * The main entry point. Parse a stub file and side-effects the {@code annotationFileAnnos}
+   * argument.
+   *
+   * @param filename name of stub file, used only for diagnostic messages
+   * @param inputStream of stub file to parse
+   * @param atypeFactory AnnotatedTypeFactory to use
+   * @param processingEnv ProcessingEnvironment to use
+   * @param annotationFileAnnos annotations from the annotation file; side-effected by this method
+   * @param fileType the annotation file type and source
+   */
+  public static void parseStubFile(
+      String filename,
+      InputStream inputStream,
+      AnnotatedTypeFactory atypeFactory,
+      ProcessingEnvironment processingEnv,
+      AnnotationFileAnnotations annotationFileAnnos,
+      AnnotationFileType fileType) {
+    AnnotationFileParser afp =
+        new AnnotationFileParser(filename, atypeFactory, processingEnv, fileType);
+    try {
+      afp.parseStubUnit(inputStream);
+      afp.process(annotationFileAnnos);
+    } catch (ParseProblemException e) {
+      for (Problem p : e.getProblems()) {
+        afp.warn(null, p.getVerboseMessage());
+      }
+    }
+  }
+
+  /**
+   * The main entry point when parsing an ajava file. Parses an ajava file and side-effects the last
+   * two arguments.
+   *
+   * @param filename name of ajava file, used only for diagnostic messages
+   * @param inputStream of ajava file to parse
+   * @param root javac tree for the file to be parsed
+   * @param atypeFactory AnnotatedTypeFactory to use
+   * @param processingEnv ProcessingEnvironment to use
+   * @param ajavaAnnos annotations from the ajava file; side-effected by this method
+   */
+  public static void parseAjavaFile(
+      String filename,
+      InputStream inputStream,
+      CompilationUnitTree root,
+      AnnotatedTypeFactory atypeFactory,
+      ProcessingEnvironment processingEnv,
+      AnnotationFileAnnotations ajavaAnnos) {
+    AnnotationFileParser afp =
+        new AnnotationFileParser(filename, atypeFactory, processingEnv, AnnotationFileType.AJAVA);
+    try {
+      afp.parseStubUnit(inputStream);
+      JavaParserUtil.concatenateAddedStringLiterals(afp.stubUnit);
+      afp.setRoot(root);
+      afp.process(ajavaAnnos);
+    } catch (ParseProblemException e) {
+      for (Problem p : e.getProblems()) {
+        afp.warn(null, p.getVerboseMessage());
+      }
+    }
+  }
+
+  /**
+   * Parse a stub file that is a part of the annotated JDK and side-effects the {@code stubAnnos}
+   * argument.
+   *
+   * @param filename name of stub file, used only for diagnostic messages
+   * @param inputStream of stub file to parse
+   * @param atypeFactory AnnotatedTypeFactory to use
+   * @param processingEnv ProcessingEnvironment to use
+   * @param stubAnnos annotations from the stub file; side-effected by this method
+   */
+  public static void parseJdkFileAsStub(
+      String filename,
+      InputStream inputStream,
+      AnnotatedTypeFactory atypeFactory,
+      ProcessingEnvironment processingEnv,
+      AnnotationFileAnnotations stubAnnos) {
+    parseStubFile(
+        filename, inputStream, atypeFactory, processingEnv, stubAnnos, AnnotationFileType.JDK_STUB);
+  }
+
+  /**
+   * Delegate to the Stub Parser to parse the annotation file to an AST, and save it in {@link
+   * #stubUnit}. Also sets {@link #allAnnotations}. Does not copy annotations out of {@link
+   * #stubUnit}; that is done by the {@code process*} methods.
+   *
+   * <p>Subsequently, all work uses the AST.
+   *
+   * @param inputStream the stream from which to read an annotation file
+   */
+  private void parseStubUnit(InputStream inputStream) {
+    if (debugAnnotationFileParser) {
+      stubDebug(String.format("parsing annotation file %s", filename));
+    }
+    stubUnit = JavaParserUtil.parseStubUnit(inputStream);
+
+    // getImportedAnnotations() also modifies importedConstants and importedTypes. This should
+    // be refactored to be nicer.
+    allAnnotations = getImportedAnnotations();
+    if (allAnnotations.isEmpty()
+        && fileType.isStub()
+        && fileType != AnnotationFileType.AJAVA_AS_STUB) {
+      // Issue a warning if the stub file contains no import statements.  The warning is
+      // incorrect if the stub file contains fully-qualified annotations.
+      stubWarnNotFound(
+          null,
+          String.format(
+              "No supported annotations found! Does stub file %s import them?", filename));
+    }
+    // Annotations in java.lang might be used without an import statement, so add them in case.
+    allAnnotations.putAll(annosInPackage(findPackage("java.lang", null)));
+  }
+
+  /**
+   * Process {@link #stubUnit}, which is the AST produced by {@link #parseStubUnit}. Processing
+   * means copying annotations from Stub Parser data structures to {@code #annotationFileAnnos}.
+   *
+   * @param annotationFileAnnos annotations from the file; side-effected by this method
+   */
+  private void process(AnnotationFileAnnotations annotationFileAnnos) {
+    this.annotationFileAnnos = annotationFileAnnos;
+    processStubUnit(this.stubUnit);
+    this.annotationFileAnnos = null;
+  }
+
+  /**
+   * Process the given StubUnit: copy its annotations to {@code #annotationFileAnnos}.
+   *
+   * @param su the StubUnit to process
+   */
+  private void processStubUnit(StubUnit su) {
+    for (CompilationUnit cu : su.getCompilationUnits()) {
+      processCompilationUnit(cu);
+    }
+  }
+
+  /**
+   * Process the given CompilationUnit: copy its annotations to {@code #annotationFileAnnos}.
+   *
+   * @param cu the CompilationUnit to process
+   */
+  private void processCompilationUnit(CompilationUnit cu) {
+
+    if (!cu.getPackageDeclaration().isPresent()) {
+      packageAnnos = null;
+      typeBeingParsed = new FqName(null, null);
+    } else {
+      PackageDeclaration pDecl = cu.getPackageDeclaration().get();
+      packageAnnos = pDecl.getAnnotations();
+      processPackage(pDecl);
+    }
+
+    if (fileType.isStub()) {
+      if (cu.getTypes() != null) {
+        for (TypeDeclaration<?> typeDeclaration : cu.getTypes()) {
+          // Not processing an ajava file, so ignore the return value.
+          processTypeDecl(typeDeclaration, null, null);
+        }
+      }
+    } else {
+      root.accept(new AjavaAnnotationCollectorVisitor(), cu);
+    }
+
+    packageAnnos = null;
+  }
+
+  /**
+   * Process the given package declaration: copy its annotations to {@code #annotationFileAnnos}.
+   *
+   * @param packDecl the package declaration to process
+   */
+  private void processPackage(PackageDeclaration packDecl) {
+    assert (packDecl != null);
+    if (!isAnnotatedForThisChecker(packDecl.getAnnotations())) {
+      return;
+    }
+    String packageName = packDecl.getNameAsString();
+    typeBeingParsed = new FqName(packageName, null);
+    Element elem = elements.getPackageElement(packageName);
+    // If the element lookup fails (that is, elem == null), it's because we have an annotation for a
+    // package that isn't on the classpath, which is fine.
+    if (elem != null) {
+      recordDeclAnnotation(elem, packDecl.getAnnotations(), packDecl);
+    }
+    // TODO: Handle atypes???
+  }
+
+  /**
+   * Returns true if the given program construct need not be read: it is private and one of the
+   * following is true:
+   *
+   * <ul>
+   *   <li>It is in the annotated JDK. Private constructs can't be referenced outside of the JDK and
+   *       might refer to types that are not accessible.
+   *   <li>It is not an ajava file and {@code -AmergeStubsWithSource} was not supplied. As described
+   *       at https://checkerframework.org/manual/#stub-multiple-specifications, source files take
+   *       precedence over stub files unless {@code -AmergeStubsWithSource} is supplied. As
+   *       described at https://checkerframework.org/manual/#ajava-using, source files do not take
+   *       precedence over ajava files (when reading an ajava file, it is as if {@code
+   *       -AmergeStubsWithSource} were supplied).
+   * </ul>
+   *
+   * @param node a declaration
+   * @return true if the given program construct is in the annotated JDK and is private
+   */
+  private boolean skipNode(NodeWithAccessModifiers<?> node) {
+    // Must include everything with no access modifier, because stub files are allowed to omit the
+    // access modifier.  Also, interface methods have no access modifier, but they are still public.
+    // Must include protected JDK methods.  For example, Object.clone is protected, but it contains
+    // annotations that apply to calls like `super.clone()` and `myArray.clone()`.
+    return (fileType == AnnotationFileType.BUILTIN_STUB
+            || (fileType.isStub() && !mergeStubsWithSource))
+        && node.getModifiers().contains(Modifier.privateModifier());
+  }
+
+  /**
+   * Process a type declaration: copy its annotations to {@code #annotationFileAnnos}.
+   *
+   * <p>This method stores the declaration's type parameters in {@link #typeParameters}. When
+   * processing an ajava file, where traversal is handled externaly by a {@link
+   * org.checkerframework.framework.ajava.JointJavacJavaParserVisitor}, these type variables must be
+   * removed after processing the type's members. Otherwise, this method removes them.
+   *
+   * @param typeDecl the type declaration to process
+   * @param outertypeName the name of the containing class, when processing a nested class;
+   *     otherwise null
+   * @param classTree the tree corresponding to typeDecl if processing an ajava file, null otherwise
+   * @return a list of types variables for {@code typeDecl}. Only non-null if processing an ajava
+   *     file, in which case the contents should be removed from {@link #typeParameters} after
+   *     processing the type declaration's members
+   */
+  private List<AnnotatedTypeVariable> processTypeDecl(
+      TypeDeclaration<?> typeDecl, String outertypeName, @Nullable ClassTree classTree) {
+    assert typeBeingParsed != null;
+    if (skipNode(typeDecl)) {
+      return null;
+    }
+    String innerName;
+    @FullyQualifiedName String fqTypeName;
+    TypeElement typeElt;
+    if (classTree != null) {
+      typeElt = TreeUtils.elementFromDeclaration(classTree);
+      innerName = typeElt.getQualifiedName().toString();
+      typeBeingParsed = new FqName(typeBeingParsed.packageName, innerName);
+      fqTypeName = typeBeingParsed.toString();
+    } else {
+      String packagePrefix = outertypeName == null ? "" : outertypeName + ".";
+      innerName = packagePrefix + typeDecl.getNameAsString();
+      typeBeingParsed = new FqName(typeBeingParsed.packageName, innerName);
+      fqTypeName = typeBeingParsed.toString();
+      typeElt = elements.getTypeElement(fqTypeName);
+    }
+
+    if (!isAnnotatedForThisChecker(typeDecl.getAnnotations())) {
+      return null;
+    }
+    if (typeElt == null) {
+      if (debugAnnotationFileParser
+          || (!warnIfNotFoundIgnoresClasses
+              && !hasNoAnnotationFileParserWarning(typeDecl.getAnnotations())
+              && !hasNoAnnotationFileParserWarning(packageAnnos))) {
+        if (elements.getAllTypeElements(fqTypeName).isEmpty()) {
+          stubWarnNotFound(typeDecl, "Type not found: " + fqTypeName);
+        } else {
+          stubWarnNotFound(
+              typeDecl,
+              "Type not found uniquely: "
+                  + fqTypeName
+                  + " : "
+                  + elements.getAllTypeElements(fqTypeName));
+        }
+      }
+      return null;
+    }
+
+    List<AnnotatedTypeVariable> typeDeclTypeParameters = null;
+    if (typeElt.getKind() == ElementKind.ENUM) {
+      if (!(typeDecl instanceof EnumDeclaration)) {
+        warn(
+            typeDecl,
+            innerName
+                + " is an enum, but stub file declared it as "
+                + typeDecl.toString().split("\\R", 2)[0]
+                + "...");
+        return null;
+      }
+      typeDeclTypeParameters = processEnum((EnumDeclaration) typeDecl, typeElt);
+      typeParameters.addAll(typeDeclTypeParameters);
+    } else if (typeElt.getKind() == ElementKind.ANNOTATION_TYPE) {
+      if (!(typeDecl instanceof AnnotationDeclaration)) {
+        warn(
+            typeDecl,
+            innerName
+                + " is an annotation, but stub file declared it as "
+                + typeDecl.toString().split("\\R", 2)[0]
+                + "...");
+        return null;
+      }
+      stubWarnNotFound(typeDecl, "Skipping annotation type: " + fqTypeName);
+    } else if (typeDecl instanceof ClassOrInterfaceDeclaration) {
+      if (!(typeDecl instanceof ClassOrInterfaceDeclaration)) {
+        warn(
+            typeDecl,
+            innerName
+                + " is a class or interface, but stub file declared it as "
+                + typeDecl.toString().split("\\R", 2)[0]
+                + "...");
+        return null;
+      }
+      typeDeclTypeParameters = processType((ClassOrInterfaceDeclaration) typeDecl, typeElt);
+      typeParameters.addAll(typeDeclTypeParameters);
+    } // else it's an EmptyTypeDeclaration.  TODO:  An EmptyTypeDeclaration can have
+    // annotations, right?
+
+    // If processing an ajava file, then traversal is handled by a visitor, rather than the rest
+    // of this method.
+    if (fileType == AnnotationFileType.AJAVA) {
+      return typeDeclTypeParameters;
+    }
+
+    Pair<Map<Element, BodyDeclaration<?>>, Map<Element, List<BodyDeclaration<?>>>> members =
+        getMembers(typeDecl, typeElt, typeDecl);
+    for (Map.Entry<Element, BodyDeclaration<?>> entry : members.first.entrySet()) {
+      final Element elt = entry.getKey();
+      final BodyDeclaration<?> decl = entry.getValue();
+      switch (elt.getKind()) {
+        case FIELD:
+          processField((FieldDeclaration) decl, (VariableElement) elt);
+          break;
+        case ENUM_CONSTANT:
+          processEnumConstant((EnumConstantDeclaration) decl, (VariableElement) elt);
+          break;
+        case CONSTRUCTOR:
+        case METHOD:
+          processCallableDeclaration((CallableDeclaration<?>) decl, (ExecutableElement) elt);
+          break;
+        case CLASS:
+        case INTERFACE:
+          // Not processing an ajava file, so ignore the return value.
+          processTypeDecl((ClassOrInterfaceDeclaration) decl, innerName, null);
+          break;
+        case ENUM:
+          // Not processing an ajava file, so ignore the return value.
+          processTypeDecl((EnumDeclaration) decl, innerName, null);
+          break;
+        default:
+          /* do nothing */
+          stubWarnNotFound(decl, "AnnotationFileParser ignoring: " + elt);
+          break;
+      }
+    }
+    for (Map.Entry<Element, List<BodyDeclaration<?>>> entry : members.second.entrySet()) {
+      ExecutableElement fakeOverridden = (ExecutableElement) entry.getKey();
+      List<BodyDeclaration<?>> fakeOverrideDecls = entry.getValue();
+      for (BodyDeclaration<?> bodyDecl : fakeOverrideDecls) {
+        processFakeOverride(fakeOverridden, (CallableDeclaration<?>) bodyDecl, typeElt);
+      }
+    }
+
+    if (typeDeclTypeParameters != null) {
+      typeParameters.removeAll(typeDeclTypeParameters);
+    }
+
+    return null;
+  }
+
+  /**
+   * Returns true if the argument contains {@code @NoAnnotationFileParserWarning}.
+   *
+   * @param aexprs collection of annotation expressions
+   * @return true if {@code aexprs} contains {@code @NoAnnotationFileParserWarning}
+   */
+  private boolean hasNoAnnotationFileParserWarning(Iterable<AnnotationExpr> aexprs) {
+    if (aexprs == null) {
+      return false;
+    }
+    for (AnnotationExpr anno : aexprs) {
+      if (anno.getNameAsString().equals("NoAnnotationFileParserWarning")) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Process the type's declaration: copy its annotations to {@code #annotationFileAnnos}. Does not
+   * process any of its members. Returns the type's type parameter declarations.
+   *
+   * @param decl a type declaration
+   * @param elt the type's element
+   * @return the type's type parameter declarations
+   */
+  private List<AnnotatedTypeVariable> processType(
+      ClassOrInterfaceDeclaration decl, TypeElement elt) {
+
+    recordDeclAnnotation(elt, decl.getAnnotations(), decl);
+    AnnotatedDeclaredType type = atypeFactory.fromElement(elt);
+    annotate(type, decl.getAnnotations(), decl);
+
+    final List<? extends AnnotatedTypeMirror> typeArguments = type.getTypeArguments();
+    final List<TypeParameter> typeParameters = decl.getTypeParameters();
+
+    // It can be the case that args=[] and params=null, so don't crash in that case.
+    // if ((typeParameters == null) != (typeArguments == null)) {
+    //     throw new Error(String.format("parseType (%s, %s): inconsistent nullness for args and
+    // params%n  args = %s%n  params = %s%n", decl, elt, typeArguments, typeParameters));
+    // }
+
+    if (debugAnnotationFileParser) {
+      int numParams = (typeParameters == null ? 0 : typeParameters.size());
+      int numArgs = (typeArguments == null ? 0 : typeArguments.size());
+      if (numParams != numArgs) {
+        stubDebug(
+            String.format(
+                "parseType:  mismatched sizes for typeParameters=%s (size %d) and typeArguments=%s"
+                    + " (size %d); decl=%s; elt=%s (%s); type=%s (%s); typeBeingParsed=%s",
+                typeParameters,
+                numParams,
+                typeArguments,
+                numArgs,
+                decl.toString().replace(LINE_SEPARATOR, " "),
+                elt.toString().replace(LINE_SEPARATOR, " "),
+                elt.getClass(),
+                type,
+                type.getClass(),
+                typeBeingParsed));
+        stubDebug("Proceeding despite mismatched sizes");
+      }
+    }
+
+    annotateTypeParameters(decl, elt, typeArguments, typeParameters);
+    annotateSupertypes(decl, type);
+    putMerge(annotationFileAnnos.atypes, elt, type);
+    List<AnnotatedTypeVariable> typeVariables = new ArrayList<>(type.getTypeArguments().size());
+    for (AnnotatedTypeMirror typeV : type.getTypeArguments()) {
+      if (typeV.getKind() != TypeKind.TYPEVAR) {
+        warn(
+            decl,
+            "expected an AnnotatedTypeVariable but found type kind "
+                + typeV.getKind()
+                + ": "
+                + typeV);
+      } else {
+        typeVariables.add((AnnotatedTypeVariable) typeV);
+      }
+    }
+    return typeVariables;
+  }
+
+  /**
+   * Process an enum: copy its annotations to {@code #annotationFileAnnos}. Returns the enum's type
+   * parameter declarations.
+   *
+   * @param decl enum declaration
+   * @param elt element representing enum
+   * @return the enum's type parameter declarations
+   */
+  private List<AnnotatedTypeVariable> processEnum(EnumDeclaration decl, TypeElement elt) {
+
+    recordDeclAnnotation(elt, decl.getAnnotations(), decl);
+    AnnotatedDeclaredType type = atypeFactory.fromElement(elt);
+    annotate(type, decl.getAnnotations(), decl);
+
+    putMerge(annotationFileAnnos.atypes, elt, type);
+    List<AnnotatedTypeVariable> typeVariables = new ArrayList<>(type.getTypeArguments().size());
+    for (AnnotatedTypeMirror typeV : type.getTypeArguments()) {
+      if (typeV.getKind() != TypeKind.TYPEVAR) {
+        warn(
+            decl,
+            "expected an AnnotatedTypeVariable but found type kind "
+                + typeV.getKind()
+                + ": "
+                + typeV);
+      } else {
+        typeVariables.add((AnnotatedTypeVariable) typeV);
+      }
+    }
+    return typeVariables;
+  }
+
+  private void annotateSupertypes(
+      ClassOrInterfaceDeclaration typeDecl, AnnotatedDeclaredType type) {
+    if (typeDecl.getExtendedTypes() != null) {
+      for (ClassOrInterfaceType supertype : typeDecl.getExtendedTypes()) {
+        AnnotatedDeclaredType annotatedSupertype =
+            findAnnotatedType(supertype, type.directSupertypes(), typeDecl);
+        if (annotatedSupertype == null) {
+          warn(
+              typeDecl,
+              "stub file does not match bytecode: "
+                  + "could not find superclass "
+                  + supertype
+                  + " from type "
+                  + type);
+        } else {
+          annotate(annotatedSupertype, supertype, null, typeDecl);
+        }
+      }
+    }
+    if (typeDecl.getImplementedTypes() != null) {
+      for (ClassOrInterfaceType supertype : typeDecl.getImplementedTypes()) {
+        AnnotatedDeclaredType annotatedSupertype =
+            findAnnotatedType(supertype, type.directSupertypes(), typeDecl);
+        if (annotatedSupertype == null) {
+          warn(
+              typeDecl,
+              "stub file does not match bytecode: "
+                  + "could not find superinterface "
+                  + supertype
+                  + " from type "
+                  + type);
+        } else {
+          annotate(annotatedSupertype, supertype, null, typeDecl);
+        }
+      }
+    }
+  }
+
+  /**
+   * Process a method or constructor declaration: copy its annotations to {@code
+   * #annotationFileAnnos}.
+   *
+   * @param decl a method or constructor declaration, as read from an annotation file
+   * @param elt the method or constructor's element
+   * @return type variables for the method
+   */
+  private List<AnnotatedTypeVariable> processCallableDeclaration(
+      CallableDeclaration<?> decl, ExecutableElement elt) {
+    if (!isAnnotatedForThisChecker(decl.getAnnotations())) {
+      return null;
+    }
+    // Declaration annotations
+    recordDeclAnnotation(elt, decl.getAnnotations(), decl);
+    if (decl.isMethodDeclaration()) {
+      // AnnotationFileParser parses all annotations in type annotation position as type
+      // annotations.
+      recordDeclAnnotation(elt, ((MethodDeclaration) decl).getType().getAnnotations(), decl);
+    }
+    markAsFromStubFile(elt);
+
+    AnnotatedExecutableType methodType = atypeFactory.fromElement(elt);
+    AnnotatedExecutableType origMethodType =
+        warnIfStubRedundantWithBytecode ? methodType.deepCopy() : null;
+
+    // Type Parameters
+    annotateTypeParameters(decl, elt, methodType.getTypeVariables(), decl.getTypeParameters());
+    typeParameters.addAll(methodType.getTypeVariables());
+
+    // Return type, from declaration annotations on the method or constructor
+    if (decl.isMethodDeclaration()) {
+      try {
+        annotate(
+            methodType.getReturnType(),
+            ((MethodDeclaration) decl).getType(),
+            decl.getAnnotations(),
+            decl);
+      } catch (ErrorTypeKindException e) {
+        // Do nothing, per Issue #244.
+      }
+    } else {
+      assert decl.isConstructorDeclaration();
+      annotate(methodType.getReturnType(), decl.getAnnotations(), decl);
+    }
+
+    // Parameters
+    processParameters(decl, elt, methodType);
+
+    // Receiver
+    if (decl.getReceiverParameter().isPresent()) {
+      ReceiverParameter receiverParameter = decl.getReceiverParameter().get();
+      if (methodType.getReceiverType() == null) {
+        if (decl.isConstructorDeclaration()) {
+          warn(
+              receiverParameter,
+              "parseParameter: constructor %s of a top-level class cannot have receiver"
+                  + " annotations %s",
+              methodType,
+              decl.getReceiverParameter().get().getAnnotations());
+        } else {
+          warn(
+              receiverParameter,
+              "parseParameter: static method %s cannot have receiver annotations %s",
+              methodType,
+              decl.getReceiverParameter().get().getAnnotations());
+        }
+      } else {
+        // Add declaration annotations.
+        annotate(
+            methodType.getReceiverType(),
+            decl.getReceiverParameter().get().getAnnotations(),
+            receiverParameter);
+        // Add type annotations.
+        annotate(
+            methodType.getReceiverType(),
+            decl.getReceiverParameter().get().getType(),
+            decl.getReceiverParameter().get().getAnnotations(),
+            receiverParameter);
+      }
+    }
+
+    if (warnIfStubRedundantWithBytecode
+        && methodType.toString().equals(origMethodType.toString())
+        && fileType != AnnotationFileType.BUILTIN_STUB) {
+      warn(
+          decl,
+          String.format(
+              "redundant stub file specification for %s", ElementUtils.getQualifiedName(elt)));
+    }
+
+    // Store the type.
+    putMerge(annotationFileAnnos.atypes, elt, methodType);
+    if (fileType.isStub()) {
+      typeParameters.removeAll(methodType.getTypeVariables());
+    }
+
+    return methodType.getTypeVariables();
+  }
+
+  /**
+   * Process the parameters of a method or constructor declaration: copy their annotations to {@code
+   * #annotationFileAnnos}.
+   *
+   * @param method a Method or Constructor declaration
+   * @param elt ExecutableElement of {@code method}
+   * @param methodType annotated type of {@code method}
+   */
+  private void processParameters(
+      CallableDeclaration<?> method, ExecutableElement elt, AnnotatedExecutableType methodType) {
+    List<Parameter> params = method.getParameters();
+    List<? extends VariableElement> paramElts = elt.getParameters();
+    List<? extends AnnotatedTypeMirror> paramTypes = methodType.getParameterTypes();
+
+    for (int i = 0; i < methodType.getParameterTypes().size(); ++i) {
+      VariableElement paramElt = paramElts.get(i);
+      AnnotatedTypeMirror paramType = paramTypes.get(i);
+      Parameter param = params.get(i);
+
+      recordDeclAnnotation(paramElt, param.getAnnotations(), param);
+      recordDeclAnnotation(paramElt, param.getType().getAnnotations(), param);
+
+      if (param.isVarArgs()) {
+        assert paramType.getKind() == TypeKind.ARRAY;
+        // The "type" of param is actually the component type of the vararg.
+        // For example, in "Object..." the type would be "Object".
+        annotate(
+            ((AnnotatedArrayType) paramType).getComponentType(),
+            param.getType(),
+            param.getAnnotations(),
+            param);
+        // The "VarArgsAnnotations" are those just before "...".
+        annotate(paramType, param.getVarArgsAnnotations(), param);
+      } else {
+        annotate(paramType, param.getType(), param.getAnnotations(), param);
+        putMerge(annotationFileAnnos.atypes, paramElt, paramType);
+      }
+    }
+  }
+
+  /**
+   * Clear (remove) existing annotations on the type.
+   *
+   * <p>Stub files override annotations read from .class files. Using {@code replaceAnnotation}
+   * usually achieves this; however, for annotations on type variables, it is sometimes necessary to
+   * remove an existing annotation, leaving no annotation on the type variable. This method does so.
+   *
+   * @param atype the type to modify
+   * @param typeDef the type from the annotation file, used only for diagnostic messages
+   */
+  @SuppressWarnings("unused") // for disabled warning message
+  private void clearAnnotations(AnnotatedTypeMirror atype, Type typeDef) {
+    // TODO: only produce output if the removed annotation isn't the top or default
+    // annotation in the type hierarchy.  See https://tinyurl.com/cfissue/2759 .
+    /*
+    if (!atype.getAnnotations().isEmpty()) {
+        stubWarnOverwritesBytecode(
+                String.format(
+                        "in file %s at line %s removed existing annotations on type: %s",
+                        filename.substring(filename.lastIndexOf('/') + 1),
+                        typeDef.getBegin().get().line,
+                        atype.toString(true)));
+    }
+    */
+    // Clear existing annotations, which only makes a difference for
+    // type variables, but doesn't hurt in other cases.
+    atype.clearAnnotations();
+  }
+
+  /**
+   * Add the annotations from {@code type} to {@code atype}. Type annotations that parsed as
+   * declaration annotations (i.e., type annotations in {@code declAnnos}) are applied to the
+   * innermost component type.
+   *
+   * @param atype annotated type to which to add annotations
+   * @param type parsed type
+   * @param declAnnos annotations stored on the declaration of the variable with this type or null
+   * @param astNode where to report errors
+   */
+  private void annotateAsArray(
+      AnnotatedArrayType atype,
+      ReferenceType type,
+      @Nullable NodeList<AnnotationExpr> declAnnos,
+      NodeWithRange<?> astNode) {
+    annotateInnermostComponentType(atype, declAnnos, astNode);
+    Type typeDef = type;
+    AnnotatedTypeMirror currentAtype = atype;
+    while (typeDef.isArrayType()) {
+      if (currentAtype.getKind() != TypeKind.ARRAY) {
+        warn(astNode, "Mismatched array lengths; atype: " + atype + "%n  type: " + type);
+        return;
+      }
+
+      // handle generic type
+      clearAnnotations(currentAtype, typeDef);
+
+      List<AnnotationExpr> annotations = typeDef.getAnnotations();
+      if (annotations != null) {
+        annotate(currentAtype, annotations, astNode);
+      }
+      typeDef = ((com.github.javaparser.ast.type.ArrayType) typeDef).getComponentType();
+      currentAtype = ((AnnotatedArrayType) currentAtype).getComponentType();
+    }
+    if (currentAtype.getKind() == TypeKind.ARRAY) {
+      warn(astNode, "Mismatched array lengths; atype: " + atype + "%n  type: " + type);
+    }
+  }
+
+  private ClassOrInterfaceType unwrapDeclaredType(Type type) {
+    if (type instanceof ClassOrInterfaceType) {
+      return (ClassOrInterfaceType) type;
+    } else if (type instanceof ReferenceType && type.getArrayLevel() == 0) {
+      return unwrapDeclaredType(type.getElementType());
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Add to formal parameter {@code atype}:
+   *
+   * <ol>
+   *   <li>the annotations from {@code typeDef}, and
+   *   <li>any type annotations that parsed as declaration annotations (i.e., type annotations in
+   *       {@code declAnnos}).
+   * </ol>
+   *
+   * @param atype annotated type to which to add annotations
+   * @param typeDef parsed type
+   * @param declAnnos annotations stored on the declaration of the variable with this type, or null
+   * @param astNode where to report errors
+   */
+  private void annotate(
+      AnnotatedTypeMirror atype,
+      Type typeDef,
+      @Nullable NodeList<AnnotationExpr> declAnnos,
+      NodeWithRange<?> astNode) {
+    if (atype.getKind() == TypeKind.ARRAY) {
+      if (typeDef instanceof ReferenceType) {
+        annotateAsArray((AnnotatedArrayType) atype, (ReferenceType) typeDef, declAnnos, astNode);
+      } else {
+        warn(astNode, "expected ReferenceType but found: " + typeDef);
+      }
+      return;
+    }
+
+    clearAnnotations(atype, typeDef);
+
+    // Primary annotations for the type of a variable declaration are not stored in typeDef, but
+    // rather as declaration annotations (passed as declAnnos to this method).  But, if typeDef
+    // is not the type of a variable, then the primary annotations are stored in typeDef.
+    NodeList<AnnotationExpr> primaryAnnotations;
+    if (typeDef.getAnnotations().isEmpty() && declAnnos != null) {
+      primaryAnnotations = declAnnos;
+    } else {
+      primaryAnnotations = typeDef.getAnnotations();
+    }
+    if (atype.getKind() != TypeKind.WILDCARD) {
+      // The primary annotation on a wildcard applies to the super or extends bound and
+      // are added below.
+      annotate(atype, primaryAnnotations, astNode);
+    }
+
+    switch (atype.getKind()) {
+      case DECLARED:
+        ClassOrInterfaceType declType = unwrapDeclaredType(typeDef);
+        if (declType == null) {
+          break;
+        }
+        AnnotatedDeclaredType adeclType = (AnnotatedDeclaredType) atype;
+        // Process type arguments.
+        if (declType.getTypeArguments().isPresent()
+            && !declType.getTypeArguments().get().isEmpty()
+            && !adeclType.getTypeArguments().isEmpty()) {
+          if (declType.getTypeArguments().get().size() != adeclType.getTypeArguments().size()) {
+            warn(
+                astNode,
+                String.format(
+                    "Mismatch in type argument size between %s (%d) and %s (%d)",
+                    declType,
+                    declType.getTypeArguments().get().size(),
+                    adeclType,
+                    adeclType.getTypeArguments().size()));
+            break;
+          }
+          for (int i = 0; i < declType.getTypeArguments().get().size(); ++i) {
+            annotate(
+                adeclType.getTypeArguments().get(i),
+                declType.getTypeArguments().get().get(i),
+                null,
+                astNode);
+          }
+        }
+        break;
+      case WILDCARD:
+        AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) atype;
+        // Ensure that the file also has a wildcard type, report an error otherwise
+        if (!typeDef.isWildcardType()) {
+          // We throw an error here, as otherwise we are just getting a generic cast error
+          // on the very next line.
+          warn(
+              astNode,
+              "Wildcard type <"
+                  + atype
+                  + "> does not match type in stubs file"
+                  + filename
+                  + ": <"
+                  + typeDef
+                  + ">"
+                  + " while parsing "
+                  + typeBeingParsed);
+          return;
+        }
+        WildcardType wildcardDef = (WildcardType) typeDef;
+        if (wildcardDef.getExtendedType().isPresent()) {
+          annotate(
+              wildcardType.getExtendsBound(), wildcardDef.getExtendedType().get(), null, astNode);
+          annotate(wildcardType.getSuperBound(), primaryAnnotations, astNode);
+        } else if (wildcardDef.getSuperType().isPresent()) {
+          annotate(wildcardType.getSuperBound(), wildcardDef.getSuperType().get(), null, astNode);
+          annotate(wildcardType.getExtendsBound(), primaryAnnotations, astNode);
+        } else {
+          annotate(atype, primaryAnnotations, astNode);
+        }
+        break;
+      case TYPEVAR:
+        // Add annotations from the declaration of the TypeVariable
+        AnnotatedTypeVariable typeVarUse = (AnnotatedTypeVariable) atype;
+        Types typeUtils = processingEnv.getTypeUtils();
+        for (AnnotatedTypeVariable typePar : typeParameters) {
+          if (typeUtils.isSameType(typePar.getUnderlyingType(), atype.getUnderlyingType())) {
+            atypeFactory.replaceAnnotations(typePar.getUpperBound(), typeVarUse.getUpperBound());
+            atypeFactory.replaceAnnotations(typePar.getLowerBound(), typeVarUse.getLowerBound());
+          }
+        }
+        break;
+      default:
+        // No additional annotations to add.
+    }
+  }
+
+  /**
+   * Process the field declaration in decl: copy its annotations to {@code #annotationFileAnnos}.
+   *
+   * @param decl the declaration in the annotation file
+   * @param elt the element representing that same declaration
+   */
+  private void processField(FieldDeclaration decl, VariableElement elt) {
+    if (skipNode(decl)) {
+      // Don't process private fields of the JDK.  They can't be referenced outside of the JDK
+      // and might refer to types that are not accessible.
+      return;
+    }
+    markAsFromStubFile(elt);
+    recordDeclAnnotation(elt, decl.getAnnotations(), decl);
+    // AnnotationFileParser parses all annotations in type annotation position as type annotations
+    recordDeclAnnotation(elt, decl.getElementType().getAnnotations(), decl);
+    AnnotatedTypeMirror fieldType = atypeFactory.fromElement(elt);
+
+    VariableDeclarator fieldVarDecl = null;
+    String eltName = elt.getSimpleName().toString();
+    for (VariableDeclarator var : decl.getVariables()) {
+      if (var.getName().toString().equals(eltName)) {
+        fieldVarDecl = var;
+        break;
+      }
+    }
+    assert fieldVarDecl != null;
+    annotate(fieldType, fieldVarDecl.getType(), decl.getAnnotations(), fieldVarDecl);
+    putMerge(annotationFileAnnos.atypes, elt, fieldType);
+  }
+
+  /**
+   * Adds the annotations present on the declaration of an enum constant to the ATM of that
+   * constant.
+   *
+   * @param decl the enum constant, in Javaparser AST form (the source of annotations)
+   * @param elt the enum constant declaration, as an element (the destination for annotations)
+   */
+  private void processEnumConstant(EnumConstantDeclaration decl, VariableElement elt) {
+    markAsFromStubFile(elt);
+    recordDeclAnnotation(elt, decl.getAnnotations(), decl);
+    AnnotatedTypeMirror enumConstType = atypeFactory.fromElement(elt);
+    annotate(enumConstType, decl.getAnnotations(), decl);
+    putMerge(annotationFileAnnos.atypes, elt, enumConstType);
+  }
+
+  /**
+   * Returns the innermost component type of {@code type}.
+   *
+   * @param type array type
+   * @return the innermost component type of {@code type}
+   */
+  private AnnotatedTypeMirror innermostComponentType(AnnotatedArrayType type) {
+    AnnotatedTypeMirror componentType = type;
+    while (componentType.getKind() == TypeKind.ARRAY) {
+      componentType = ((AnnotatedArrayType) componentType).getComponentType();
+    }
+    return componentType;
+  }
+
+  /**
+   * Adds {@code annotations} to the innermost component type of {@code type}.
+   *
+   * @param type array type
+   * @param annotations annotations to add
+   * @param astNode where to report errors
+   */
+  private void annotateInnermostComponentType(
+      AnnotatedArrayType type, List<AnnotationExpr> annotations, NodeWithRange<?> astNode) {
+    annotate(innermostComponentType(type), annotations, astNode);
+  }
+
+  /**
+   * Annotate the type with the given type annotations, removing any existing annotations from the
+   * same qualifier hierarchies.
+   *
+   * @param type the type to annotate
+   * @param annotations the new annotations for the type
+   * @param astNode where to report errors
+   */
+  private void annotate(
+      AnnotatedTypeMirror type, List<AnnotationExpr> annotations, NodeWithRange<?> astNode) {
+    if (annotations == null) {
+      return;
+    }
+    for (AnnotationExpr annotation : annotations) {
+      AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations);
+      if (annoMirror != null) {
+        type.replaceAnnotation(annoMirror);
+      } else {
+        // TODO: Maybe always warn here.  It's so easy to forget an import statement and
+        // have an annotation silently ignored.
+        stubWarnNotFound(astNode, "Unknown annotation " + annotation);
+      }
+    }
+  }
+
+  /**
+   * Adds to {@code annotationFileAnnos} all the annotations in {@code annotations} that are
+   * applicable to {@code elt}'s location. For example, if an annotation is a type annotation but
+   * {@code elt} is a field declaration, the type annotation will be ignored.
+   *
+   * @param elt the element to be annotated
+   * @param annotations set of annotations that may be applicable to elt
+   * @param astNode where to report errors
+   */
+  private void recordDeclAnnotation(
+      Element elt, List<AnnotationExpr> annotations, NodeWithRange<?> astNode) {
+    if (annotations == null || annotations.isEmpty()) {
+      return;
+    }
+    Set<AnnotationMirror> annos = AnnotationUtils.createAnnotationSet();
+    for (AnnotationExpr annotation : annotations) {
+      AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations);
+      if (annoMirror != null) {
+        // The @Target annotation on `annotation`/`annoMirror`
+        Target target = annoMirror.getAnnotationType().asElement().getAnnotation(Target.class);
+        // Only add the declaration annotation if the annotation applies to the element.
+        if (AnnotationUtils.getElementKindsForTarget(target).contains(elt.getKind())) {
+          // `annoMirror` is applicable to `elt`
+          annos.add(annoMirror);
+        }
+      } else {
+        // TODO: Maybe always warn here.  It's so easy to forget an import statement and
+        // have an annotation silently ignored.
+        stubWarnNotFound(astNode, String.format("Unknown annotation %s", annotation));
+      }
+    }
+    String eltName = ElementUtils.getQualifiedName(elt);
+    putOrAddToDeclAnnos(eltName, annos);
+  }
+
+  /**
+   * Adds the declaration annotation {@code @FromStubFile} to {@link #annotationFileAnnos}, unless
+   * we are parsing the JDK as a stub file.
+   *
+   * @param elt an element to be annotated as {@code @FromStubFile}
+   */
+  private void markAsFromStubFile(Element elt) {
+    if (fileType == AnnotationFileType.AJAVA || fileType == AnnotationFileType.JDK_STUB) {
+      return;
+    }
+    putOrAddToDeclAnnos(
+        ElementUtils.getQualifiedName(elt), Collections.singleton(fromStubFileAnno));
+  }
+
+  private void annotateTypeParameters(
+      BodyDeclaration<?> decl, // for debugging
+      Object elt, // for debugging; TypeElement or ExecutableElement
+      List<? extends AnnotatedTypeMirror> typeArguments,
+      List<TypeParameter> typeParameters) {
+    if (typeParameters == null) {
+      return;
+    }
+
+    if (typeParameters.size() != typeArguments.size()) {
+      String msg =
+          String.format(
+              "annotateTypeParameters: mismatched sizes:  typeParameters (size %d)=%s; "
+                  + " typeArguments (size %d)=%s;  decl=%s;  elt=%s (%s).",
+              typeParameters.size(),
+              typeParameters,
+              typeArguments.size(),
+              typeArguments,
+              decl.toString().replace(LINE_SEPARATOR, " "),
+              elt.toString().replace(LINE_SEPARATOR, " "),
+              elt.getClass());
+      if (!debugAnnotationFileParser) {
+        msg = msg + "; for more details, run with -AstubDebug";
+      }
+      warn(decl, msg);
+      return;
+    }
+    for (int i = 0; i < typeParameters.size(); ++i) {
+      TypeParameter param = typeParameters.get(i);
+      AnnotatedTypeVariable paramType = (AnnotatedTypeVariable) typeArguments.get(i);
+
+      if (param.getTypeBound() == null || param.getTypeBound().isEmpty()) {
+        // No bound so annotations are both lower and upper bounds
+        annotate(paramType, param.getAnnotations(), param);
+      } else if (param.getTypeBound() != null && !param.getTypeBound().isEmpty()) {
+        annotate(paramType.getLowerBound(), param.getAnnotations(), param);
+        annotate(paramType.getUpperBound(), param.getTypeBound().get(0), null, param);
+        if (param.getTypeBound().size() > 1) {
+          // TODO: add support for intersection types
+          stubWarnNotFound(param, "Annotations on intersection types are not yet supported");
+        }
+      }
+      putMerge(annotationFileAnnos.atypes, paramType.getUnderlyingType().asElement(), paramType);
+    }
+  }
+
+  /**
+   * Returns a pair of mappings. For each member declaration of the JavaParser type declaration
+   * {@code typeDecl}:
+   *
+   * <ul>
+   *   <li>If {@code typeElt} contains a member element for it, the first mapping maps the member
+   *       element to it.
+   *   <li>If it is a fake override, the second mapping maps each element it overrides to it.
+   *   <li>Otherwise, does nothing.
+   * </ul>
+   *
+   * This method does not read or write the field {@link #annotationFileAnnos}.
+   *
+   * @param typeDecl a JavaParser type declaration
+   * @param typeElt the javac element for {@code typeDecl}
+   * @return two mappings: from javac elements to their JavaParser declaration, and from javac
+   *     elements to fake overrides of them
+   * @param astNode where to report errors
+   */
+  private Pair<Map<Element, BodyDeclaration<?>>, Map<Element, List<BodyDeclaration<?>>>> getMembers(
+      TypeDeclaration<?> typeDecl, TypeElement typeElt, NodeWithRange<?> astNode) {
+    assert (typeElt.getSimpleName().contentEquals(typeDecl.getNameAsString())
+            || typeDecl.getNameAsString().endsWith("$" + typeElt.getSimpleName()))
+        : String.format("%s  %s", typeElt.getSimpleName(), typeDecl.getName());
+
+    Map<Element, BodyDeclaration<?>> elementsToDecl = new LinkedHashMap<>();
+    Map<Element, List<BodyDeclaration<?>>> fakeOverrideDecls = new LinkedHashMap<>();
+
+    for (BodyDeclaration<?> member : typeDecl.getMembers()) {
+      putNewElement(
+          elementsToDecl, fakeOverrideDecls, typeElt, member, typeDecl.getNameAsString(), astNode);
+    }
+    // For an enum type declaration, also add the enum constants
+    if (typeDecl instanceof EnumDeclaration) {
+      EnumDeclaration enumDecl = (EnumDeclaration) typeDecl;
+      // getEntries() gives the list of enum constant declarations
+      for (BodyDeclaration<?> member : enumDecl.getEntries()) {
+        putNewElement(
+            elementsToDecl,
+            fakeOverrideDecls,
+            typeElt,
+            member,
+            typeDecl.getNameAsString(),
+            astNode);
+      }
+    }
+
+    return Pair.of(elementsToDecl, fakeOverrideDecls);
+  }
+
+  // Used only by getMembers().
+  /**
+   * If {@code typeElt} contains an element for {@code member}, adds to {@code elementsToDecl} a
+   * mapping from member's element to member. Does nothing if a mapping already exists.
+   *
+   * <p>Otherwise (if there is no element for {@code member}), adds to {@code fakeOverrideDecls}
+   * zero or more mappings. Each mapping is from an element that {@code member} would override to
+   * {@code member}.
+   *
+   * <p>This method does not read or write field {@link annotationFileAnnos}.
+   *
+   * @param elementsToDecl the mapping that is side-effected by this method
+   * @param fakeOverrideDecls fake overrides, also side-effected by this method
+   * @param typeElt the class in which {@code member} is declared
+   * @param member the stub file declaration of a method
+   * @param typeDeclName used only for debugging
+   * @param astNode where to report errors
+   */
+  private void putNewElement(
+      Map<Element, BodyDeclaration<?>> elementsToDecl,
+      Map<Element, List<BodyDeclaration<?>>> fakeOverrideDecls,
+      TypeElement typeElt,
+      BodyDeclaration<?> member,
+      String typeDeclName,
+      NodeWithRange<?> astNode) {
+    if (member instanceof MethodDeclaration) {
+      MethodDeclaration method = (MethodDeclaration) member;
+      Element elt = findElement(typeElt, method, /*noWarn=*/ true);
+      if (elt != null) {
+        putIfAbsent(elementsToDecl, elt, method);
+      } else {
+        ExecutableElement overriddenMethod = fakeOverriddenMethod(typeElt, method);
+        if (overriddenMethod == null) {
+          // Didn't find the element and it isn't a fake override.  Issue a warning.
+          findElement(typeElt, method, /*noWarn=*/ false);
+        } else {
+          List<BodyDeclaration<?>> l =
+              fakeOverrideDecls.computeIfAbsent(overriddenMethod, __ -> new ArrayList<>());
+          l.add(member);
+        }
+      }
+    } else if (member instanceof ConstructorDeclaration) {
+      Element elt = findElement(typeElt, (ConstructorDeclaration) member);
+      if (elt != null) {
+        putIfAbsent(elementsToDecl, elt, member);
+      }
+    } else if (member instanceof FieldDeclaration) {
+      FieldDeclaration fieldDecl = (FieldDeclaration) member;
+      for (VariableDeclarator var : fieldDecl.getVariables()) {
+        Element varelt = findElement(typeElt, var);
+        if (varelt != null) {
+          putIfAbsent(elementsToDecl, varelt, fieldDecl);
+        }
+      }
+    } else if (member instanceof EnumConstantDeclaration) {
+      Element elt = findElement(typeElt, (EnumConstantDeclaration) member, astNode);
+      if (elt != null) {
+        putIfAbsent(elementsToDecl, elt, member);
+      }
+    } else if (member instanceof ClassOrInterfaceDeclaration) {
+      Element elt = findElement(typeElt, (ClassOrInterfaceDeclaration) member);
+      if (elt != null) {
+        putIfAbsent(elementsToDecl, elt, member);
+      }
+    } else if (member instanceof EnumDeclaration) {
+      Element elt = findElement(typeElt, (EnumDeclaration) member);
+      if (elt != null) {
+        putIfAbsent(elementsToDecl, elt, member);
+      }
+    } else {
+      stubDebug(
+          String.format("Ignoring element of type %s in %s", member.getClass(), typeDeclName));
+    }
+  }
+
+  /**
+   * Given a method declaration that does not correspond to an element, returns the method it
+   * directly overrides or implements. As Java does, this prefers a method in a superclass to one in
+   * an interface.
+   *
+   * <p>As with regular overrides, the parameter types must be exact matches; contravariance is not
+   * permitted.
+   *
+   * @param typeElt the type in which the method appears
+   * @param methodDecl the method declaration that does not correspond to an element
+   * @return the methods that the given method declaration would override, or null if none
+   */
+  private @Nullable ExecutableElement fakeOverriddenMethod(
+      TypeElement typeElt, MethodDeclaration methodDecl) {
+    for (Element elt : typeElt.getEnclosedElements()) {
+      if (elt.getKind() != ElementKind.METHOD) {
+        continue;
+      }
+      ExecutableElement candidate = (ExecutableElement) elt;
+      if (!candidate.getSimpleName().contentEquals(methodDecl.getName().getIdentifier())) {
+        continue;
+      }
+      List<? extends VariableElement> candidateParams = candidate.getParameters();
+      if (sameTypes(candidateParams, methodDecl.getParameters())) {
+        return candidate;
+      }
+    }
+
+    TypeElement superType = ElementUtils.getSuperClass(typeElt);
+    if (superType != null) {
+      ExecutableElement result = fakeOverriddenMethod(superType, methodDecl);
+      if (result != null) {
+        return result;
+      }
+    }
+
+    for (TypeMirror interfaceTypeMirror : typeElt.getInterfaces()) {
+      TypeElement interfaceElement = (TypeElement) ((DeclaredType) interfaceTypeMirror).asElement();
+      ExecutableElement result = fakeOverriddenMethod(interfaceElement, methodDecl);
+      if (result != null) {
+        return result;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Returns true if the two signatures (represented as lists of formal parameters) are the same. No
+   * contravariance is permitted.
+   *
+   * @param javacParams parameter list in javac form
+   * @param javaParserParams parameter list in JavaParser form
+   * @return true if the two signatures are the same
+   */
+  private boolean sameTypes(
+      List<? extends VariableElement> javacParams, NodeList<Parameter> javaParserParams) {
+    if (javacParams.size() != javaParserParams.size()) {
+      return false;
+    }
+    for (int i = 0; i < javacParams.size(); i++) {
+      TypeMirror javacType = javacParams.get(i).asType();
+      Parameter javaParserParam = javaParserParams.get(i);
+      Type javaParserType = javaParserParam.getType();
+      if (javacType.getKind() == TypeKind.TYPEVAR) {
+        // TODO: Hack, need to viewpoint-adapt.
+        javacType = ((TypeVariable) javacType).getUpperBound();
+      }
+      if (!sameType(javacType, javaParserType)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns true if the two types are the same.
+   *
+   * @param javacType type in javac form
+   * @param javaParserType type in JavaParser form
+   * @return true if the two types are the same
+   */
+  private boolean sameType(TypeMirror javacType, Type javaParserType) {
+
+    switch (javacType.getKind()) {
+      case BOOLEAN:
+        return javaParserType.equals(PrimitiveType.booleanType());
+      case BYTE:
+        return javaParserType.equals(PrimitiveType.byteType());
+      case CHAR:
+        return javaParserType.equals(PrimitiveType.charType());
+      case DOUBLE:
+        return javaParserType.equals(PrimitiveType.doubleType());
+      case FLOAT:
+        return javaParserType.equals(PrimitiveType.floatType());
+      case INT:
+        return javaParserType.equals(PrimitiveType.intType());
+      case LONG:
+        return javaParserType.equals(PrimitiveType.longType());
+      case SHORT:
+        return javaParserType.equals(PrimitiveType.shortType());
+
+      case DECLARED:
+      case TYPEVAR:
+        if (!(javaParserType instanceof ClassOrInterfaceType)) {
+          return false;
+        }
+        com.sun.tools.javac.code.Type javacTypeInternal = (com.sun.tools.javac.code.Type) javacType;
+        ClassOrInterfaceType javaParserClassType = (ClassOrInterfaceType) javaParserType;
+
+        // Use asString() because toString() includes annotations.
+        String javaParserString = javaParserClassType.asString();
+        Element javacElement = javacTypeInternal.asElement();
+        // Check both fully-qualified name and simple name.
+        return javacElement.toString().equals(javaParserString)
+            || javacElement.getSimpleName().contentEquals(javaParserString);
+
+      case ARRAY:
+        return javaParserType.isArrayType()
+            && sameType(
+                ((ArrayType) javacType).getComponentType(),
+                javaParserType.asArrayType().getComponentType());
+
+      default:
+        throw new BugInCF("Unhandled type %s of kind %s", javacType, javacType.getKind());
+    }
+  }
+
+  /**
+   * Process a fake override: copy its annotations to the fake overrides part of {@code
+   * #annotationFileAnnos}.
+   *
+   * @param element a real element
+   * @param decl a fake override of the element
+   * @param fakeLocation where the fake override was defined
+   */
+  private void processFakeOverride(
+      ExecutableElement element, CallableDeclaration<?> decl, TypeElement fakeLocation) {
+    // This is a fresh type, which this code may side-effect.
+    AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(element);
+
+    // Here is a hacky solution that does not use the visitor.  It just handles the return type.
+    // TODO: Walk the type and the declaration, copying annotations from the declaration to the
+    // element.  I think PR #3977 has a visitor that does that, which I should use after it is
+    // merged.
+
+    // The annotations on the method.  These include type annotations on the return type.
+    NodeList<AnnotationExpr> annotations = decl.getAnnotations();
+    annotate(methodType.getReturnType(), ((MethodDeclaration) decl).getType(), annotations, decl);
+
+    List<Pair<TypeMirror, AnnotatedTypeMirror>> l =
+        annotationFileAnnos.fakeOverrides.computeIfAbsent(element, __ -> new ArrayList<>());
+    l.add(Pair.of(fakeLocation.asType(), methodType));
+  }
+
+  /**
+   * Return the annotated type corresponding to {@code type}, or null if none exists. More
+   * specifically, returns the element of {@code types} whose name matches {@code type}.
+   *
+   * @param type the type to search for
+   * @param types the list of AnnotatedDeclaredTypes to search in
+   * @param astNode where to report errors
+   * @return the annotated type in {@code types} corresponding to {@code type}, or null if none
+   *     exists
+   */
+  private @Nullable AnnotatedDeclaredType findAnnotatedType(
+      ClassOrInterfaceType type, List<AnnotatedDeclaredType> types, NodeWithRange<?> astNode) {
+    String typeString = type.getNameAsString();
+    for (AnnotatedDeclaredType supertype : types) {
+      if (supertype.getUnderlyingType().asElement().getSimpleName().contentEquals(typeString)) {
+        return supertype;
+      }
+    }
+    stubWarnNotFound(astNode, "Supertype " + typeString + " not found");
+    if (debugAnnotationFileParser) {
+      stubDebug("Supertypes that were searched:");
+      for (AnnotatedDeclaredType supertype : types) {
+        stubDebug(String.format("  %s", supertype));
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Looks for the nested type element in the typeElt and returns it if the element has the same
+   * name as provided class or interface declaration. In case nested element is not found it returns
+   * null.
+   *
+   * @param typeElt an element where nested type element should be looked for
+   * @param ciDecl class or interface declaration which name should be found among nested elements
+   *     of the typeElt
+   * @return nested in typeElt element with the name of the class or interface or null if nested
+   *     element is not found
+   */
+  private @Nullable Element findElement(TypeElement typeElt, ClassOrInterfaceDeclaration ciDecl) {
+    final String wantedClassOrInterfaceName = ciDecl.getNameAsString();
+    for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) {
+      if (wantedClassOrInterfaceName.equals(typeElement.getSimpleName().toString())) {
+        return typeElement;
+      }
+    }
+
+    stubWarnNotFound(
+        ciDecl, "Class/interface " + wantedClassOrInterfaceName + " not found in type " + typeElt);
+    if (debugAnnotationFileParser) {
+      stubDebug(String.format("  Here are the type declarations of %s:", typeElt));
+      for (TypeElement method : ElementFilter.typesIn(typeElt.getEnclosedElements())) {
+        stubDebug(String.format("    %s", method));
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Looks for the nested enum element in the typeElt and returns it if the element has the same
+   * name as provided enum declaration. In case nested element is not found it returns null.
+   *
+   * @param typeElt an element where nested enum element should be looked for
+   * @param enumDecl enum declaration which name should be found among nested elements of the
+   *     typeElt
+   * @return nested in typeElt enum element with the name of the provided enum or null if nested
+   *     element is not found
+   */
+  private @Nullable Element findElement(TypeElement typeElt, EnumDeclaration enumDecl) {
+    final String wantedEnumName = enumDecl.getNameAsString();
+    for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) {
+      if (wantedEnumName.equals(typeElement.getSimpleName().toString())) {
+        return typeElement;
+      }
+    }
+
+    stubWarnNotFound(enumDecl, "Enum " + wantedEnumName + " not found in type " + typeElt);
+    if (debugAnnotationFileParser) {
+      stubDebug(String.format("  Here are the type declarations of %s:", typeElt));
+      for (TypeElement method : ElementFilter.typesIn(typeElt.getEnclosedElements())) {
+        stubDebug(String.format("    %s", method));
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Looks for an enum constant element in the typeElt and returns it if the element has the same
+   * name as provided. In case enum constant element is not found it returns null.
+   *
+   * @param typeElt type element where enum constant element should be looked for
+   * @param enumConstDecl the declaration of the enum constant
+   * @param astNode where to report errors
+   * @return enum constant element in typeElt with the provided name or null if enum constant
+   *     element is not found
+   */
+  private @Nullable VariableElement findElement(
+      TypeElement typeElt, EnumConstantDeclaration enumConstDecl, NodeWithRange<?> astNode) {
+    final String enumConstName = enumConstDecl.getNameAsString();
+    return findFieldElement(typeElt, enumConstName, astNode);
+  }
+
+  /**
+   * Looks for a method element in {@code typeElt} that has the same name and formal parameter types
+   * as {@code methodDecl}. Returns null, and possibly issues a warning, if no such method element
+   * is found.
+   *
+   * @param typeElt type element where method element should be looked for
+   * @param methodDecl method declaration with signature that should be found among methods in the
+   *     typeElt
+   * @param noWarn if true, don't issue a warning if the element is not found
+   * @return method element in typeElt with the same signature as the provided method declaration or
+   *     null if method element is not found
+   */
+  private @Nullable ExecutableElement findElement(
+      TypeElement typeElt, MethodDeclaration methodDecl, boolean noWarn) {
+    if (skipNode(methodDecl)) {
+      return null;
+    }
+    final String wantedMethodName = methodDecl.getNameAsString();
+    final int wantedMethodParams =
+        (methodDecl.getParameters() == null) ? 0 : methodDecl.getParameters().size();
+    final String wantedMethodString = AnnotationFileUtil.toString(methodDecl);
+    for (ExecutableElement method : ElementFilter.methodsIn(typeElt.getEnclosedElements())) {
+      if (wantedMethodParams == method.getParameters().size()
+          && wantedMethodName.contentEquals(method.getSimpleName().toString())
+          && ElementUtils.getSimpleSignature(method).equals(wantedMethodString)) {
+        return method;
+      }
+    }
+    if (!noWarn) {
+      if (methodDecl.getAccessSpecifier() == AccessSpecifier.PACKAGE_PRIVATE) {
+        // This might be a false positive warning.  The stub parser permits a stub file to
+        // omit the access specifier, but package-private methods aren't in the TypeElement.
+        stubWarnNotFound(
+            methodDecl,
+            "Package-private method "
+                + wantedMethodString
+                + " not found in type "
+                + typeElt
+                + System.lineSeparator()
+                + "If the method is not package-private, add an access specifier in the stub file"
+                + " and use pass -AstubDebug to receive a more useful error message.");
+      } else {
+        stubWarnNotFound(
+            methodDecl, "Method " + wantedMethodString + " not found in type " + typeElt);
+        if (debugAnnotationFileParser) {
+          stubDebug(String.format("  Here are the methods of %s:", typeElt));
+          for (ExecutableElement method : ElementFilter.methodsIn(typeElt.getEnclosedElements())) {
+            stubDebug(String.format("    %s", method));
+          }
+        }
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Looks for a constructor element in the typeElt and returns it if the element has the same
+   * signature as provided constructor declaration. In case constructor element is not found it
+   * returns null.
+   *
+   * @param typeElt type element where constructor element should be looked for
+   * @param constructorDecl constructor declaration with signature that should be found among
+   *     constructors in the typeElt
+   * @return constructor element in typeElt with the same signature as the provided constructor
+   *     declaration or null if constructor element is not found
+   */
+  private @Nullable ExecutableElement findElement(
+      TypeElement typeElt, ConstructorDeclaration constructorDecl) {
+    if (skipNode(constructorDecl)) {
+      return null;
+    }
+    final int wantedMethodParams =
+        (constructorDecl.getParameters() == null) ? 0 : constructorDecl.getParameters().size();
+    final String wantedMethodString = AnnotationFileUtil.toString(constructorDecl);
+    for (ExecutableElement method : ElementFilter.constructorsIn(typeElt.getEnclosedElements())) {
+      if (wantedMethodParams == method.getParameters().size()
+          && ElementUtils.getSimpleSignature(method).equals(wantedMethodString)) {
+        return method;
+      }
+    }
+
+    stubWarnNotFound(
+        constructorDecl, "Constructor " + wantedMethodString + " not found in type " + typeElt);
+    if (debugAnnotationFileParser) {
+      for (ExecutableElement method : ElementFilter.constructorsIn(typeElt.getEnclosedElements())) {
+        stubDebug(String.format("  %s", method));
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns the element for the given variable.
+   *
+   * @param typeElt the type in which the variable is contained
+   * @param variable the variable whose element to return
+   * @return the element for the given variable
+   */
+  private VariableElement findElement(TypeElement typeElt, VariableDeclarator variable) {
+    final String fieldName = variable.getNameAsString();
+    return findFieldElement(typeElt, fieldName, variable);
+  }
+
+  /**
+   * Looks for a field element in the typeElt and returns it if the element has the same name as
+   * provided. In case field element is not found it returns null.
+   *
+   * @param typeElt type element where field element should be looked for
+   * @param fieldName field name that should be found
+   * @param astNode where to report errors
+   * @return field element in typeElt with the provided name or null if field element is not found
+   */
+  private @Nullable VariableElement findFieldElement(
+      TypeElement typeElt, String fieldName, NodeWithRange<?> astNode) {
+    for (VariableElement field : ElementUtils.getAllFieldsIn(typeElt, elements)) {
+      // field.getSimpleName() is a CharSequence, not a String
+      if (fieldName.equals(field.getSimpleName().toString())) {
+        return field;
+      }
+    }
+
+    stubWarnNotFound(astNode, "Field " + fieldName + " not found in type " + typeElt);
+    if (debugAnnotationFileParser) {
+      for (VariableElement field : ElementFilter.fieldsIn(typeElt.getEnclosedElements())) {
+        stubDebug(String.format("  %s", field));
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Given a fully-qualified type name, return a TypeElement for it, or null if none exists. Also
+   * cache in importedTypes.
+   *
+   * @param name a fully-qualified type name
+   * @return a TypeElement for the name, or null
+   */
+  private @Nullable TypeElement getTypeElementOrNull(@FullyQualifiedName String name) {
+    TypeElement typeElement = elements.getTypeElement(name);
+    if (typeElement != null) {
+      importedTypes.put(name, typeElement);
+    }
+    // for debugging: warn("getTypeElementOrNull(%s) => %s", name, typeElement);
+    return typeElement;
+  }
+
+  /**
+   * Get the type element for the given fully-qualified type name. If none is found, issue a warning
+   * and return null.
+   *
+   * @param typeName a type name
+   * @param msg a warning message to issue if the type element for {@code typeName} cannot be found
+   * @param astNode where to report errors
+   * @return the type element for the given fully-qualified type name, or null
+   */
+  private TypeElement getTypeElement(
+      @FullyQualifiedName String typeName, String msg, NodeWithRange<?> astNode) {
+    TypeElement classElement = elements.getTypeElement(typeName);
+    if (classElement == null) {
+      stubWarnNotFound(astNode, msg + ": " + typeName);
+    }
+    return classElement;
+  }
+
+  /**
+   * Returns the element for the given package.
+   *
+   * @param packageName the package's name
+   * @param astNode where to report errors
+   * @return the element for the given package
+   */
+  private PackageElement findPackage(String packageName, NodeWithRange<?> astNode) {
+    PackageElement packageElement = elements.getPackageElement(packageName);
+    if (packageElement == null) {
+      stubWarnNotFound(astNode, "Imported package not found: " + packageName);
+    }
+    return packageElement;
+  }
+
+  /**
+   * Returns true if one of the annotations is {@link AnnotatedFor} and this checker is in its list
+   * of checkers. If none of the annotations are {@code AnnotatedFor}, then also return true.
+   *
+   * @param annotations a list of JavaParser annotations
+   * @return true if one of the annotations is {@link AnnotatedFor} and its list of checkers does
+   *     not contain this checker
+   */
+  private boolean isAnnotatedForThisChecker(List<AnnotationExpr> annotations) {
+    if (fileType == AnnotationFileType.JDK_STUB) {
+      // The JDK stubs have purity annotations that should be read for all checkers.
+      // TODO: Parse the JDK stubs, but only save the declaration annotations.
+      return true;
+    }
+    for (AnnotationExpr ae : annotations) {
+      if (ae.getNameAsString().equals("AnnotatedFor")
+          || ae.getNameAsString().equals("org.checkerframework.framework.qual.AnnotatedFor")) {
+        AnnotationMirror af = getAnnotation(ae, allAnnotations);
+        if (atypeFactory.areSameByClass(af, AnnotatedFor.class)) {
+          return atypeFactory.doesAnnotatedForApplyToThisChecker(af);
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Convert {@code annotation} into an AnnotationMirror. Returns null if the annotation isn't
+   * supported by the checker or if some error occurred while converting it.
+   *
+   * @param annotation syntax tree for an annotation
+   * @param allAnnotations map from simple name to annotation definition; side-effected by this
+   *     method
+   * @return the AnnotationMirror for the annotation, or null if it cannot be built
+   */
+  private @Nullable AnnotationMirror getAnnotation(
+      AnnotationExpr annotation, Map<String, TypeElement> allAnnotations) {
+
+    @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094
+    @FullyQualifiedName String annoNameFq = annotation.getNameAsString();
+    TypeElement annoTypeElt = allAnnotations.get(annoNameFq);
+    if (annoTypeElt == null) {
+      // If the annotation was not imported, then #getImportedAnnotations did not add it to the
+      // allAnnotations field. This code adds the annotation when it is encountered (i.e. here).
+      // Note that this does not call AnnotationFileParser#getTypeElement to avoid a spurious
+      // diagnostic if the annotation is actually unknown.
+      annoTypeElt = elements.getTypeElement(annoNameFq);
+      if (annoTypeElt == null) {
+        // Not a supported annotation -> ignore
+        return null;
+      }
+      putAllNew(allAnnotations, createNameToAnnotationMap(Collections.singletonList(annoTypeElt)));
+    }
+    @SuppressWarnings("signature") // not anonymous, so name is not empty
+    @CanonicalName String annoName = annoTypeElt.getQualifiedName().toString();
+
+    if (annotation instanceof MarkerAnnotationExpr) {
+      return AnnotationBuilder.fromName(elements, annoName);
+    } else if (annotation instanceof NormalAnnotationExpr) {
+      NormalAnnotationExpr nrmanno = (NormalAnnotationExpr) annotation;
+      AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoName);
+      List<MemberValuePair> pairs = nrmanno.getPairs();
+      if (pairs != null) {
+        for (MemberValuePair mvp : pairs) {
+          String member = mvp.getNameAsString();
+          Expression exp = mvp.getValue();
+          try {
+            builderAddElement(builder, member, exp);
+          } catch (AnnotationFileParserException e) {
+            warn(
+                exp,
+                "For annotation %s, could not add  %s=%s  because %s",
+                annotation,
+                member,
+                exp,
+                e.getMessage());
+            return null;
+          }
+        }
+      }
+      return builder.build();
+    } else if (annotation instanceof SingleMemberAnnotationExpr) {
+      SingleMemberAnnotationExpr sglanno = (SingleMemberAnnotationExpr) annotation;
+      AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoName);
+      Expression valExpr = sglanno.getMemberValue();
+      try {
+        builderAddElement(builder, "value", valExpr);
+      } catch (AnnotationFileParserException e) {
+        warn(
+            valExpr,
+            "For annotation %s, could not add  value=%s  because %s",
+            annotation,
+            valExpr,
+            e.getMessage());
+        return null;
+      }
+      return builder.build();
+    } else {
+      throw new BugInCF("AnnotationFileParser: unknown annotation type: " + annotation);
+    }
+  }
+
+  /**
+   * Returns the value of {@code expr}.
+   *
+   * @param name the name of an annotation element/argument, used for diagnostic messages
+   * @param expr the expression to determine the value of
+   * @param valueKind the type of the result
+   * @return the value of {@code expr}
+   * @throws AnnotationFileParserException if a problem occurred getting the value
+   */
+  private Object getValueOfExpressionInAnnotation(String name, Expression expr, TypeKind valueKind)
+      throws AnnotationFileParserException {
+    if (expr instanceof FieldAccessExpr || expr instanceof NameExpr) {
+      VariableElement elem;
+      if (expr instanceof NameExpr) {
+        elem = findVariableElement((NameExpr) expr);
+      } else {
+        elem = findVariableElement((FieldAccessExpr) expr);
+      }
+      if (elem == null) {
+        throw new AnnotationFileParserException(String.format("variable %s not found", expr));
+      }
+      Object value = elem.getConstantValue() != null ? elem.getConstantValue() : elem;
+      if (value instanceof Number) {
+        return convert((Number) value, valueKind);
+      } else {
+        return value;
+      }
+    } else if (expr instanceof StringLiteralExpr) {
+      return ((StringLiteralExpr) expr).asString();
+    } else if (expr instanceof BooleanLiteralExpr) {
+      return ((BooleanLiteralExpr) expr).getValue();
+    } else if (expr instanceof CharLiteralExpr) {
+      return convert((int) ((CharLiteralExpr) expr).asChar(), valueKind);
+    } else if (expr instanceof DoubleLiteralExpr) {
+      // No conversion needed if the expression is a double, the annotation value must be a
+      // double, too.
+      return ((DoubleLiteralExpr) expr).asDouble();
+    } else if (expr instanceof IntegerLiteralExpr) {
+      return convert(((IntegerLiteralExpr) expr).asNumber(), valueKind);
+    } else if (expr instanceof LongLiteralExpr) {
+      return convert(((LongLiteralExpr) expr).asNumber(), valueKind);
+    } else if (expr instanceof UnaryExpr) {
+      switch (expr.toString()) {
+          // Special-case the minimum values.  Separately parsing a "-" and a value
+          // doesn't correctly handle the minimum values, because the absolute value of
+          // the smallest member of an integral type is larger than the largest value.
+        case "-9223372036854775808L":
+        case "-9223372036854775808l":
+          return convert(Long.MIN_VALUE, valueKind, false);
+        case "-2147483648":
+          return convert(Integer.MIN_VALUE, valueKind, false);
+        default:
+          if (((UnaryExpr) expr).getOperator() == UnaryExpr.Operator.MINUS) {
+            Object value =
+                getValueOfExpressionInAnnotation(
+                    name, ((UnaryExpr) expr).getExpression(), valueKind);
+            if (value instanceof Number) {
+              return convert((Number) value, valueKind, true);
+            }
+          }
+          throw new AnnotationFileParserException(
+              "unexpected Unary annotation expression: " + expr);
+      }
+    } else if (expr instanceof ClassExpr) {
+      ClassExpr classExpr = (ClassExpr) expr;
+      @SuppressWarnings("signature") // Type.toString(): @FullyQualifiedName
+      @FullyQualifiedName String className = classExpr.getType().toString();
+      if (importedTypes.containsKey(className)) {
+        return importedTypes.get(className).asType();
+      }
+      TypeElement typeElement = findTypeOfName(className);
+      if (typeElement == null) {
+        throw new AnnotationFileParserException("unknown class name " + className);
+      }
+
+      return typeElement.asType();
+    } else if (expr instanceof NullLiteralExpr) {
+      throw new AnnotationFileParserException("Illegal annotation value null, for " + name);
+    } else {
+      throw new AnnotationFileParserException("Unexpected annotation expression: " + expr);
+    }
+  }
+
+  /**
+   * Returns the TypeElement with the name {@code name}, if one exists. Otherwise, checks the class
+   * and package of {@code typeBeingParsed} for a class named {@code name}.
+   *
+   * @param name classname (simple, or Outer.Inner, or fully-qualified)
+   * @return the TypeElement for {@code name}, or null if not found
+   */
+  @SuppressWarnings("signature:argument") // string concatenation
+  private @Nullable TypeElement findTypeOfName(@FullyQualifiedName String name) {
+    String packageName = typeBeingParsed.packageName;
+    String packagePrefix = (packageName == null) ? "" : packageName + ".";
+
+    // warn("findTypeOfName(%s), typeBeingParsed %s %s", name, packageName, enclosingClass);
+
+    // As soon as typeElement is set to a non-null value, it will be returned.
+    TypeElement typeElement = getTypeElementOrNull(name);
+    if (typeElement == null && packageName != null) {
+      typeElement = getTypeElementOrNull(packagePrefix + name);
+    }
+    String enclosingClass = typeBeingParsed.className;
+    while (typeElement == null && enclosingClass != null) {
+      typeElement = getTypeElementOrNull(packagePrefix + enclosingClass + "." + name);
+      int lastDot = enclosingClass.lastIndexOf('.');
+      if (lastDot == -1) {
+        break;
+      } else {
+        enclosingClass = enclosingClass.substring(0, lastDot);
+      }
+    }
+    if (typeElement == null && !"java.lang".equals(packageName)) {
+      typeElement = getTypeElementOrNull("java.lang." + name);
+    }
+    return typeElement;
+  }
+
+  /**
+   * Converts {@code number} to {@code expectedKind}.
+   *
+   * <pre><code>
+   * &nbsp; @interface Anno { long value(); }
+   * &nbsp; @Anno(1)
+   * </code></pre>
+   *
+   * To properly build @Anno, the IntegerLiteralExpr "1" must be converted from an int to a long.
+   */
+  private Object convert(Number number, TypeKind expectedKind) {
+    return convert(number, expectedKind, false);
+  }
+
+  /**
+   * Converts {@code number} to {@code expectedKind}. The value converted is multiplied by -1 if
+   * {@code negate} is true
+   *
+   * @param number a Number value to be converted
+   * @param expectedKind one of type {byte, short, int, long, char, float, double}
+   * @param negate whether to negate the value of the Number Object while converting
+   * @return the converted Object
+   */
+  private Object convert(Number number, TypeKind expectedKind, boolean negate) {
+    byte scalefactor = (byte) (negate ? -1 : 1);
+    switch (expectedKind) {
+      case BYTE:
+        return number.byteValue() * scalefactor;
+      case SHORT:
+        return number.shortValue() * scalefactor;
+      case INT:
+        return number.intValue() * scalefactor;
+      case LONG:
+        return number.longValue() * scalefactor;
+      case CHAR:
+        // It's not possible for `number` to be negative when `expectedkind` is a CHAR, and
+        // casting a negative value to char is illegal.
+        if (negate) {
+          throw new BugInCF(
+              "convert(%s, %s, %s): can't negate a char", number, expectedKind, negate);
+        }
+        return (char) number.intValue();
+      case FLOAT:
+        return number.floatValue() * scalefactor;
+      case DOUBLE:
+        return number.doubleValue() * scalefactor;
+      default:
+        throw new BugInCF("Unexpected expectedKind: " + expectedKind);
+    }
+  }
+
+  /**
+   * Adds an annotation element (argument) to {@code builder}. The element is a Java expression.
+   *
+   * @param builder the builder to side-effect
+   * @param name the element name
+   * @param expr the element value
+   * @throws AnnotationFileParserException if the expression cannot be parsed and added to {@code
+   *     builder}
+   */
+  private void builderAddElement(AnnotationBuilder builder, String name, Expression expr)
+      throws AnnotationFileParserException {
+    ExecutableElement var = builder.findElement(name);
+    TypeMirror declaredType = var.getReturnType();
+    TypeKind valueKind;
+    if (declaredType.getKind() == TypeKind.ARRAY) {
+      valueKind = ((ArrayType) declaredType).getComponentType().getKind();
+    } else {
+      valueKind = declaredType.getKind();
+    }
+    if (expr instanceof ArrayInitializerExpr) {
+      if (declaredType.getKind() != TypeKind.ARRAY) {
+        throw new AnnotationFileParserException(
+            "unhandled annotation attribute type: " + expr + " and declaredType: " + declaredType);
+      }
+
+      List<Expression> arrayExpressions = ((ArrayInitializerExpr) expr).getValues();
+      Object[] values = new Object[arrayExpressions.size()];
+
+      for (int i = 0; i < arrayExpressions.size(); ++i) {
+        Expression eltExpr = arrayExpressions.get(i);
+        values[i] = getValueOfExpressionInAnnotation(name, eltExpr, valueKind);
+      }
+      builder.setValue(name, values);
+    } else {
+      Object value = getValueOfExpressionInAnnotation(name, expr, valueKind);
+      if (declaredType.getKind() == TypeKind.ARRAY) {
+        Object[] valueArray = {value};
+        builder.setValue(name, valueArray);
+      } else {
+        builderSetValue(builder, name, value);
+      }
+    }
+  }
+
+  /**
+   * Cast to non-array values so that correct the correct AnnotationBuilder#setValue method is
+   * called. (Different types of values are handled differently.)
+   *
+   * @param builder the builder to side-effect
+   * @param name the element name
+   * @param value the element value
+   */
+  private void builderSetValue(AnnotationBuilder builder, String name, Object value) {
+    if (value instanceof Boolean) {
+      builder.setValue(name, (Boolean) value);
+    } else if (value instanceof Character) {
+      builder.setValue(name, (Character) value);
+    } else if (value instanceof Class<?>) {
+      builder.setValue(name, (Class<?>) value);
+    } else if (value instanceof Double) {
+      builder.setValue(name, (Double) value);
+    } else if (value instanceof Enum<?>) {
+      builder.setValue(name, (Enum<?>) value);
+    } else if (value instanceof Float) {
+      builder.setValue(name, (Float) value);
+    } else if (value instanceof Integer) {
+      builder.setValue(name, (Integer) value);
+    } else if (value instanceof Long) {
+      builder.setValue(name, (Long) value);
+    } else if (value instanceof Short) {
+      builder.setValue(name, (Short) value);
+    } else if (value instanceof String) {
+      builder.setValue(name, (String) value);
+    } else if (value instanceof TypeMirror) {
+      builder.setValue(name, (TypeMirror) value);
+    } else if (value instanceof VariableElement) {
+      builder.setValue(name, (VariableElement) value);
+    } else {
+      throw new BugInCF("Unexpected builder value: %s", value);
+    }
+  }
+
+  /**
+   * Mapping of a name access expression that has already been encountered to the resolved variable
+   * element.
+   */
+  private final Map<NameExpr, VariableElement> findVariableElementNameCache = new HashMap<>();
+
+  /**
+   * Returns the element for the given variable.
+   *
+   * @param nexpr the variable name
+   * @return the element for the given variable
+   */
+  private @Nullable VariableElement findVariableElement(NameExpr nexpr) {
+    if (findVariableElementNameCache.containsKey(nexpr)) {
+      return findVariableElementNameCache.get(nexpr);
+    }
+
+    VariableElement res = null;
+    boolean importFound = false;
+    for (String imp : importedConstants) {
+      Pair<@FullyQualifiedName String, String> partitionedName =
+          AnnotationFileUtil.partitionQualifiedName(imp);
+      String typeName = partitionedName.first;
+      String fieldName = partitionedName.second;
+      if (fieldName.equals(nexpr.getNameAsString())) {
+        TypeElement enclType =
+            getTypeElement(
+                typeName,
+                String.format("Enclosing type of static import %s not found", fieldName),
+                nexpr);
+
+        if (enclType == null) {
+          return null;
+        } else {
+          importFound = true;
+          res = findFieldElement(enclType, fieldName, nexpr);
+          break;
+        }
+      }
+    }
+
+    if (res == null) {
+      if (importFound) {
+        // TODO: Is this warning redundant?  Maybe imported but invalid types or fields will
+        // have warnings from above.
+        stubWarnNotFound(nexpr, nexpr.getName() + " was imported but not found");
+      } else {
+        stubWarnNotFound(nexpr, "Static field " + nexpr.getName() + " is not imported");
+      }
+    }
+
+    findVariableElementNameCache.put(nexpr, res);
+    return res;
+  }
+
+  /**
+   * Mapping of a field access expression that has already been encountered to the resolved variable
+   * element.
+   */
+  private final Map<FieldAccessExpr, VariableElement> findVariableElementFieldCache =
+      new HashMap<>();
+
+  /**
+   * Returns the VariableElement for the given field access.
+   *
+   * @param faexpr a field access expression
+   * @return the VariableElement for the given field access
+   */
+  @SuppressWarnings("signature:argument") // string manipulation
+  private @Nullable VariableElement findVariableElement(FieldAccessExpr faexpr) {
+    if (findVariableElementFieldCache.containsKey(faexpr)) {
+      return findVariableElementFieldCache.get(faexpr);
+    }
+    TypeElement rcvElt = elements.getTypeElement(faexpr.getScope().toString());
+    if (rcvElt == null) {
+      // Search importedConstants for full annotation name.
+      for (String imp : importedConstants) {
+        // TODO: should this use AnnotationFileUtil.partitionQualifiedName?
+        String[] importDelimited = imp.split("\\.");
+        if (importDelimited[importDelimited.length - 1].equals(faexpr.getScope().toString())) {
+          StringBuilder fullAnnotation = new StringBuilder();
+          for (int i = 0; i < importDelimited.length - 1; i++) {
+            fullAnnotation.append(importDelimited[i]);
+            fullAnnotation.append('.');
+          }
+          fullAnnotation.append(faexpr.getScope().toString());
+          rcvElt = elements.getTypeElement(fullAnnotation);
+          break;
+        }
+      }
+
+      if (rcvElt == null) {
+        stubWarnNotFound(faexpr, "Type " + faexpr.getScope() + " not found");
+        return null;
+      }
+    }
+
+    VariableElement res = findFieldElement(rcvElt, faexpr.getNameAsString(), faexpr);
+    findVariableElementFieldCache.put(faexpr, res);
+    return res;
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Map utilities
+  ///
+
+  /**
+   * Just like Map.put, but does not override any existing value in the map.
+   *
+   * @param <K> the key type
+   * @param <V> the value type
+   * @param m a map
+   * @param key a key
+   * @param value the value to associate with the key, if the key isn't already in the map
+   */
+  public static <K, V> void putIfAbsent(Map<K, V> m, K key, V value) {
+    if (key == null) {
+      throw new BugInCF("AnnotationFileParser: key is null for value " + value);
+    }
+    if (!m.containsKey(key)) {
+      m.put(key, value);
+    }
+  }
+
+  /**
+   * If the key is already in the {@code annotationFileAnnos.declAnnos} map, then add the annos to
+   * the map value. Otherwise put the key and the annos in the map.
+   *
+   * @param key a name (actually declaration element string)
+   * @param annos the the set of declaration annotations on it, as written in the annotation file
+   */
+  private void putOrAddToDeclAnnos(String key, Set<AnnotationMirror> annos) {
+    Set<AnnotationMirror> stored = annotationFileAnnos.declAnnos.get(key);
+    if (stored == null) {
+      annotationFileAnnos.declAnnos.put(key, new HashSet<>(annos));
+    } else {
+      if (fileType != AnnotationFileType.JDK_STUB) {
+        stored.addAll(annos);
+      } else {
+        // JDK annotations should not replace any annotation of the same type.
+        List<AnnotationMirror> origStored = new ArrayList<>(stored);
+        for (AnnotationMirror anno : annos) {
+          if (!AnnotationUtils.containsSameByName(origStored, anno)) {
+            stored.add(anno);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Just like Map.put, but modifies an existing annotated type for the given key in {@code m}. If
+   * {@code m} already has an annotated type for {@code key}, each annotation in {@code newType}
+   * will replace annotations from the same hierarchy at the same location in the existing annotated
+   * type. Annotations in other hierarchies will be preserved.
+   *
+   * @param m the map to put the new type into
+   * @param key the key for the map
+   * @param newType the new type for the key
+   */
+  private void putMerge(
+      Map<Element, AnnotatedTypeMirror> m, Element key, AnnotatedTypeMirror newType) {
+    if (key == null) {
+      throw new BugInCF("AnnotationFileParser: key is null");
+    }
+    if (m.containsKey(key)) {
+      AnnotatedTypeMirror existingType = m.get(key);
+      // If the newType is from a JDK stub file, then keep the existing type.  This
+      // way user-supplied stub files override JDK stub files.
+      // This works because the JDK is always parsed last, on demand, after all other stub files.
+      if (fileType != AnnotationFileType.JDK_STUB) {
+        atypeFactory.replaceAnnotations(newType, existingType);
+      }
+      m.put(key, existingType);
+    } else {
+      m.put(key, newType);
+    }
+  }
+
+  /**
+   * Just like Map.putAll, but modifies existing values using {@link #putIfAbsent(Map, Object,
+   * Object)}.
+   *
+   * @param m the destination map
+   * @param m2 the source map
+   * @param <K> the key type for the maps
+   * @param <V> the value type for the maps
+   */
+  public static <K, V> void putAllNew(Map<K, V> m, Map<K, V> m2) {
+    for (Map.Entry<K, V> e2 : m2.entrySet()) {
+      putIfAbsent(m, e2.getKey(), e2.getValue());
+    }
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Issue warnings
+  ///
+
+  /** The warnings that have been issued so far. */
+  private static final Set<String> warnings = new HashSet<>();
+
+  /**
+   * Issues the given warning about missing elements, only if it has not been previously issued and
+   * the -AstubWarnIfNotFound command-line argument was passed.
+   *
+   * @param astNode where to report errors
+   * @param warning warning to print
+   */
+  private void stubWarnNotFound(NodeWithRange<?> astNode, String warning) {
+    stubWarnNotFound(astNode, warning, warnIfNotFound);
+  }
+
+  /**
+   * Issues the given warning about missing elements, only if it has not been previously issued and
+   * the {@code warnIfNotFound} formal parameter is true.
+   *
+   * @param astNode where to report errors
+   * @param warning warning to print
+   * @param warnIfNotFound whether to print warnings about types/members that were not found
+   */
+  private void stubWarnNotFound(NodeWithRange<?> astNode, String warning, boolean warnIfNotFound) {
+    if ((fileType.isCommandLine() || warnIfNotFound) || debugAnnotationFileParser) {
+      warn(astNode, warning);
+    }
+  }
+
+  /**
+   * Issues the given warning about overwriting bytecode, only if it has not been previously issued
+   * and the -AstubWarnIfOverwritesBytecode command-line argument was passed.
+   *
+   * @param astNode where to report errors
+   * @param message the warning message to print
+   */
+  @SuppressWarnings("UnusedMethod") // not currently used
+  private void stubWarnOverwritesBytecode(NodeWithRange<?> astNode, String message) {
+    if (warnIfStubOverwritesBytecode || debugAnnotationFileParser) {
+      warn(astNode, message);
+    }
+  }
+
+  /**
+   * Issues a warning, only if it has not been previously issued.
+   *
+   * @param astNode where to report errors
+   * @param warning a format string
+   * @param args the arguments for {@code warning}
+   */
+  @FormatMethod
+  private void warn(@Nullable NodeWithRange<?> astNode, String warning, Object... args) {
+    if (!fileType.isBuiltIn()) {
+      warn(astNode, String.format(warning, args));
+    }
+  }
+
+  /**
+   * Issues a warning, only if it has not been previously issued.
+   *
+   * @param astNode where to report errors
+   * @param warning a warning message
+   */
+  private void warn(@Nullable NodeWithRange<?> astNode, String warning) {
+    if (fileType != AnnotationFileType.JDK_STUB) {
+      if (warnings.add(warning)) {
+        processingEnv
+            .getMessager()
+            .printMessage(stubWarnDiagnosticKind, fileAndLine(astNode) + warning);
+      }
+    }
+  }
+
+  /**
+   * If {@code warning} hasn't been printed yet, and {@code debugAnnotationFileParser} is true,
+   * prints the given warning as a diagnostic message.
+   *
+   * @param warning warning to print
+   */
+  private void stubDebug(String warning) {
+    if (debugAnnotationFileParser && warnings.add(warning)) {
+      processingEnv
+          .getMessager()
+          .printMessage(javax.tools.Diagnostic.Kind.NOTE, "AnnotationFileParser: " + warning);
+    }
+  }
+
+  /**
+   * After obtaining the JavaParser AST for an ajava file and the javac tree for its corresponding
+   * Java file, walks both in tandem. For each program construct with annotations, stores the
+   * annotations from the ajava file in {@link #annotationFileAnnos} by calling the process method
+   * corresponding to that construct, such as {@link #processCallableDeclaration} or {@link
+   * #processField}.
+   */
+  private class AjavaAnnotationCollectorVisitor extends DefaultJointVisitor {
+    @Override
+    public Void visitClass(ClassTree javacTree, Node javaParserNode) {
+      List<AnnotatedTypeVariable> typeDeclTypeParameters = null;
+      if (javaParserNode instanceof TypeDeclaration<?>
+          && !(javaParserNode instanceof AnnotationDeclaration)) {
+        typeDeclTypeParameters =
+            processTypeDecl((TypeDeclaration<?>) javaParserNode, null, javacTree);
+      }
+
+      super.visitClass(javacTree, javaParserNode);
+      if (typeDeclTypeParameters != null) {
+        typeParameters.removeAll(typeDeclTypeParameters);
+      }
+
+      return null;
+    }
+
+    @Override
+    public Void visitVariable(VariableTree javacTree, Node javaParserNode) {
+      if (TreeUtils.elementFromTree(javacTree) != null) {
+        VariableElement elt = TreeUtils.elementFromDeclaration(javacTree);
+        if (elt != null) {
+          if (elt.getKind() == ElementKind.FIELD) {
+            processField((FieldDeclaration) javaParserNode.getParentNode().get(), elt);
+          }
+
+          if (elt.getKind() == ElementKind.ENUM_CONSTANT) {
+            processEnumConstant((EnumConstantDeclaration) javaParserNode, elt);
+          }
+        }
+      }
+
+      super.visitVariable(javacTree, javaParserNode);
+      return null;
+    }
+
+    @Override
+    public Void visitMethod(MethodTree javacTree, Node javaParserNode) {
+      ExecutableElement elt = TreeUtils.elementFromDeclaration(javacTree);
+      List<AnnotatedTypeVariable> variablesToClear = null;
+      if (javaParserNode instanceof CallableDeclaration<?>) {
+        variablesToClear = processCallableDeclaration((CallableDeclaration<?>) javaParserNode, elt);
+      }
+
+      super.visitMethod(javacTree, javaParserNode);
+      if (variablesToClear != null) {
+        typeParameters.removeAll(variablesToClear);
+      }
+
+      return null;
+    }
+  }
+
+  /**
+   * Return the prefix for a warning line: A file name, line number, and column number.
+   *
+   * @param astNode where to report errors
+   * @return file name, line number, and column number
+   */
+  private String fileAndLine(NodeWithRange<?> astNode) {
+    String filenamePrinted =
+        (processingEnv.getOptions().containsKey("nomsgtext")
+            ? new File(filename).getName()
+            : filename);
+
+    Optional<Position> begin = astNode == null ? Optional.empty() : astNode.getBegin();
+    String lineAndColumn = (begin.isPresent() ? begin.get() + ":" : "");
+    return filenamePrinted + ":" + lineAndColumn + " ";
+  }
+
+  /** An exception indicating a problem while parsing an annotation file. */
+  public static class AnnotationFileParserException extends Exception {
+
+    private static final long serialVersionUID = 20201222;
+
+    /**
+     * Create a new AnnotationFileParserException.
+     *
+     * @param message a description of the problem
+     */
+    AnnotationFileParserException(String message) {
+      super(message);
+    }
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Parse state
+  ///
+
+  /** Represents a class: its package name and name (including outer class names if any). */
+  private static class FqName {
+    /** Name of the package being parsed, or null. */
+    public @Nullable String packageName;
+
+    /**
+     * Name of the type being parsed. Includes outer class names if any. Null if the parser has
+     * parsed a package declaration but has not yet gotten to a type declaration.
+     */
+    public @Nullable String className;
+
+    /**
+     * Create a new FqName, which represents a class.
+     *
+     * @param packageName name of the package, or null
+     * @param className unqualified name of the type, including outer class names if any. May be
+     *     null.
+     */
+    public FqName(String packageName, @Nullable String className) {
+      this.packageName = packageName;
+      this.className = className;
+    }
+
+    /** Fully-qualified name of the class. */
+    @Override
+    @SuppressWarnings("signature") // string concatenation
+    public @FullyQualifiedName String toString() {
+      if (packageName == null) {
+        return className;
+      } else {
+        return packageName + "." + className;
+      }
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileResource.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileResource.java
new file mode 100644
index 0000000..a120d91
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileResource.java
@@ -0,0 +1,17 @@
+package org.checkerframework.framework.stub;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Interface for sources of stub data. */
+public interface AnnotationFileResource {
+  /**
+   * Returns a user-friendly description of the resource (e.g. a filesystem path).
+   *
+   * @return a description of the resource
+   */
+  String getDescription();
+
+  /** Returns a stream for reading the contents of the resource. */
+  InputStream getInputStream() throws IOException;
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java
new file mode 100644
index 0000000..27ecc5d
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java
@@ -0,0 +1,476 @@
+package org.checkerframework.framework.stub;
+
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.StubUnit;
+import com.github.javaparser.ast.body.BodyDeclaration;
+import com.github.javaparser.ast.body.ConstructorDeclaration;
+import com.github.javaparser.ast.body.FieldDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.Parameter;
+import com.github.javaparser.ast.body.TypeDeclaration;
+import com.github.javaparser.ast.body.VariableDeclarator;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import com.github.javaparser.ast.type.PrimitiveType;
+import com.github.javaparser.ast.type.VoidType;
+import com.github.javaparser.ast.type.WildcardType;
+import com.github.javaparser.ast.visitor.SimpleVoidVisitor;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.Pair;
+
+/** Utility class for annotation files (stub files and ajava files). */
+public class AnnotationFileUtil {
+  /**
+   * The types of files that can contain annotations. Also indicates the file's source, such as from
+   * the JDK, built in, or from the command line.
+   *
+   * <p>Stub files have extension ".astub". Ajava files have extension ".ajava".
+   */
+  public enum AnnotationFileType {
+    /** Stub file in the annotated JDK. */
+    JDK_STUB,
+    /** Stub file built into a checker. */
+    BUILTIN_STUB,
+    /** Stub file provided on command line. */
+    COMMAND_LINE_STUB,
+    /** Ajava file being parsed as if it is a stub file. */
+    AJAVA_AS_STUB,
+    /** Ajava file provided on command line. */
+    AJAVA;
+
+    /**
+     * Returns true if this represents a stub file.
+     *
+     * @return true if this represents a stub file
+     */
+    public boolean isStub() {
+      switch (this) {
+        case JDK_STUB:
+        case BUILTIN_STUB:
+        case COMMAND_LINE_STUB:
+        case AJAVA_AS_STUB:
+          return true;
+        case AJAVA:
+          return false;
+        default:
+          throw new Error("unhandled case " + this);
+      }
+    }
+
+    /**
+     * Returns true if this annotation file is built-in (not provided on the command line).
+     *
+     * @return true if this annotation file is built-in (not provided on the command line)
+     */
+    public boolean isBuiltIn() {
+      switch (this) {
+        case JDK_STUB:
+        case BUILTIN_STUB:
+          return true;
+        case COMMAND_LINE_STUB:
+        case AJAVA_AS_STUB:
+        case AJAVA:
+          return false;
+        default:
+          throw new Error("unhandled case " + this);
+      }
+    }
+
+    /**
+     * Returns true if this annotation file was provided on the command line (not built-in).
+     *
+     * @return true if this annotation file was provided on the command line (not built-in)
+     */
+    public boolean isCommandLine() {
+      switch (this) {
+        case JDK_STUB:
+        case BUILTIN_STUB:
+          return false;
+        case COMMAND_LINE_STUB:
+        case AJAVA_AS_STUB:
+        case AJAVA:
+          return true;
+        default:
+          throw new Error("unhandled case " + this);
+      }
+    }
+  }
+
+  /**
+   * Finds the type declaration with the given class name in a StubUnit.
+   *
+   * @param className fully qualified name of the type declaration to find
+   * @param indexFile StubUnit to search
+   * @return the declaration in {@code indexFile} with {@code className} if it exists, null
+   *     otherwise.
+   */
+  /*package-scope*/ static TypeDeclaration<?> findDeclaration(
+      String className, StubUnit indexFile) {
+    int indexOfDot = className.lastIndexOf('.');
+
+    if (indexOfDot == -1) {
+      // classes not within a package needs to be the first in the index file
+      assert !indexFile.getCompilationUnits().isEmpty();
+      assert indexFile.getCompilationUnits().get(0).getPackageDeclaration() == null;
+      return findDeclaration(className, indexFile.getCompilationUnits().get(0));
+    }
+
+    final String packageName = className.substring(0, indexOfDot);
+    final String simpleName = className.substring(indexOfDot + 1);
+
+    for (CompilationUnit cu : indexFile.getCompilationUnits()) {
+      if (cu.getPackageDeclaration().isPresent()
+          && cu.getPackageDeclaration().get().getNameAsString().equals(packageName)) {
+        TypeDeclaration<?> type = findDeclaration(simpleName, cu);
+        if (type != null) {
+          return type;
+        }
+      }
+    }
+
+    // Couldn't find it
+    return null;
+  }
+
+  /*package-scope*/ static TypeDeclaration<?> findDeclaration(
+      TypeElement type, StubUnit indexFile) {
+    return findDeclaration(type.getQualifiedName().toString(), indexFile);
+  }
+
+  /*package-scope*/ static FieldDeclaration findDeclaration(
+      VariableElement field, StubUnit indexFile) {
+    TypeDeclaration<?> type = findDeclaration((TypeElement) field.getEnclosingElement(), indexFile);
+    if (type == null) {
+      return null;
+    }
+
+    for (BodyDeclaration<?> member : type.getMembers()) {
+      if (!(member instanceof FieldDeclaration)) {
+        continue;
+      }
+      FieldDeclaration decl = (FieldDeclaration) member;
+      for (VariableDeclarator var : decl.getVariables()) {
+        if (toString(var).equals(field.getSimpleName().toString())) {
+          return decl;
+        }
+      }
+    }
+    return null;
+  }
+
+  /*package-scope*/ static BodyDeclaration<?> findDeclaration(
+      ExecutableElement method, StubUnit indexFile) {
+    TypeDeclaration<?> type =
+        findDeclaration((TypeElement) method.getEnclosingElement(), indexFile);
+    if (type == null) {
+      return null;
+    }
+
+    String methodRep = toString(method);
+
+    for (BodyDeclaration<?> member : type.getMembers()) {
+      if (member instanceof MethodDeclaration) {
+        if (toString((MethodDeclaration) member).equals(methodRep)) {
+          return member;
+        }
+      } else if (member instanceof ConstructorDeclaration) {
+        if (toString((ConstructorDeclaration) member).equals(methodRep)) {
+          return member;
+        }
+      }
+    }
+    return null;
+  }
+
+  /*package-scope*/ static TypeDeclaration<?> findDeclaration(
+      String simpleName, CompilationUnit cu) {
+    for (TypeDeclaration<?> type : cu.getTypes()) {
+      if (simpleName.equals(type.getNameAsString())) {
+        return type;
+      }
+    }
+    // Couldn't find it
+    return null;
+  }
+
+  /*package-scope*/ static String toString(MethodDeclaration method) {
+    return ElementPrinter.toString(method);
+  }
+
+  /*package-scope*/ static String toString(ConstructorDeclaration constructor) {
+    return ElementPrinter.toString(constructor);
+  }
+
+  /*package-scope*/ static String toString(VariableDeclarator field) {
+    return field.getNameAsString();
+  }
+
+  /*package-scope*/ static String toString(FieldDeclaration field) {
+    assert field.getVariables().size() == 1;
+    return toString(field.getVariables().get(0));
+  }
+
+  /*package-scope*/ static String toString(VariableElement element) {
+    assert element.getKind().isField();
+    return element.getSimpleName().toString();
+  }
+
+  /*package-scope*/ static String toString(Element element) {
+    if (element instanceof ExecutableElement) {
+      return toString((ExecutableElement) element);
+    } else if (element instanceof VariableElement) {
+      return toString((VariableElement) element);
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Split a name (which comes from an import statement) into the part before the last period and
+   * the part after the last period.
+   *
+   * @param imported the name to split
+   * @return a pair of the type name and the field name
+   */
+  @SuppressWarnings("signature") // string parsing
+  public static Pair<@FullyQualifiedName String, String> partitionQualifiedName(String imported) {
+    @FullyQualifiedName String typeName = imported.substring(0, imported.lastIndexOf("."));
+    String name = imported.substring(imported.lastIndexOf(".") + 1);
+    Pair<String, String> typeParts = Pair.of(typeName, name);
+    return typeParts;
+  }
+
+  private static final class ElementPrinter extends SimpleVoidVisitor<Void> {
+    public static String toString(Node n) {
+      ElementPrinter printer = new ElementPrinter();
+      n.accept(printer, null);
+      return printer.getOutput();
+    }
+
+    private final StringBuilder sb = new StringBuilder();
+
+    public String getOutput() {
+      return sb.toString();
+    }
+
+    @Override
+    public void visit(ConstructorDeclaration n, Void arg) {
+      sb.append("<init>");
+
+      sb.append("(");
+      if (n.getParameters() != null) {
+        for (Iterator<Parameter> i = n.getParameters().iterator(); i.hasNext(); ) {
+          Parameter p = i.next();
+          p.accept(this, arg);
+
+          if (i.hasNext()) {
+            sb.append(",");
+          }
+        }
+      }
+      sb.append(")");
+    }
+
+    @Override
+    public void visit(MethodDeclaration n, Void arg) {
+      sb.append(n.getName());
+
+      sb.append("(");
+      if (n.getParameters() != null) {
+        for (Iterator<Parameter> i = n.getParameters().iterator(); i.hasNext(); ) {
+          Parameter p = i.next();
+          p.accept(this, arg);
+
+          if (i.hasNext()) {
+            sb.append(",");
+          }
+        }
+      }
+      sb.append(")");
+    }
+
+    @Override
+    public void visit(Parameter n, Void arg) {
+      n.getType().accept(this, arg);
+      if (n.isVarArgs()) {
+        sb.append("[]");
+      }
+    }
+
+    // Types
+    @Override
+    public void visit(ClassOrInterfaceType n, Void arg) {
+      sb.append(n.getName());
+    }
+
+    @Override
+    public void visit(PrimitiveType n, Void arg) {
+      switch (n.getType()) {
+        case BOOLEAN:
+          sb.append("boolean");
+          break;
+        case BYTE:
+          sb.append("byte");
+          break;
+        case CHAR:
+          sb.append("char");
+          break;
+        case DOUBLE:
+          sb.append("double");
+          break;
+        case FLOAT:
+          sb.append("float");
+          break;
+        case INT:
+          sb.append("int");
+          break;
+        case LONG:
+          sb.append("long");
+          break;
+        case SHORT:
+          sb.append("short");
+          break;
+        default:
+          throw new BugInCF("AnnotationFileUtil: unknown type: " + n.getType());
+      }
+    }
+
+    @Override
+    public void visit(com.github.javaparser.ast.type.ArrayType n, Void arg) {
+      n.getComponentType().accept(this, arg);
+      sb.append("[]");
+    }
+
+    @Override
+    public void visit(VoidType n, Void arg) {
+      sb.append("void");
+    }
+
+    @Override
+    public void visit(WildcardType n, Void arg) {
+      // We don't write type arguments
+      // TODO: Why?
+      throw new BugInCF("AnnotationFileUtil: don't print type args");
+    }
+  }
+
+  /**
+   * Return annotation files found at a given file system location (does not look on classpath).
+   *
+   * @param location an annotation file (stub file or ajava file), a jarfile, or a directory. Look
+   *     for it as an absolute file and relative to the current directory.
+   * @param fileType file type of files to collect
+   * @return annotation files with the given file type found in the file system (does not look on
+   *     classpath). Returns null if the file system location does not exist; the caller may wish to
+   *     issue a warning in that case.
+   */
+  public static @Nullable List<AnnotationFileResource> allAnnotationFiles(
+      String location, AnnotationFileType fileType) {
+    File file = new File(location);
+    if (file.exists()) {
+      List<AnnotationFileResource> resources = new ArrayList<>();
+      addAnnotationFilesToList(file, resources, fileType);
+      return resources;
+    }
+
+    // The file doesn't exist.  Maybe it is relative to the current working directory, so try that.
+    String workingDir = System.getProperty("user.dir") + System.getProperty("file.separator");
+    file = new File(workingDir + location);
+    if (file.exists()) {
+      List<AnnotationFileResource> resources = new ArrayList<>();
+      addAnnotationFilesToList(file, resources, fileType);
+      return resources;
+    }
+
+    return null;
+  }
+
+  /**
+   * Returns true if the given file is an annotation file of the given type.
+   *
+   * @param f the file to check
+   * @param fileType the type of file to check against
+   * @return true if {@code f} is a file with file type matching {@code fileType}, false otherwise
+   */
+  private static boolean isAnnotationFile(File f, AnnotationFileType fileType) {
+    return f.isFile() && isAnnotationFile(f.getName(), fileType);
+  }
+
+  /**
+   * Returns true if the given file is an annotation file of the given kind.
+   *
+   * @param path a file
+   * @param fileType the type of file to check against
+   * @return true if {@code path} represents a file with file type matching {@code fileType}, false
+   *     otherwise
+   */
+  private static boolean isAnnotationFile(String path, AnnotationFileType fileType) {
+    return path.endsWith(fileType.isStub() ? ".astub" : ".ajava");
+  }
+
+  private static boolean isJar(File f) {
+    return f.isFile() && f.getName().endsWith(".jar");
+  }
+
+  /**
+   * Side-effects {@code resources} by adding annotation files of the given file type to it.
+   *
+   * @param location an annotation file (a stub file or ajava file), a jarfile, or a directory. If a
+   *     stub file or ajava file, add it to the {@code resources} list. If a jarfile, use all
+   *     annotation files (of type {@code fileType}) contained in it. If a directory, recurse on all
+   *     files contained in it.
+   * @param resources the list to add the found files to
+   * @param fileType type of annotation files to add
+   */
+  @SuppressWarnings("JdkObsolete") // JarFile.entries()
+  private static void addAnnotationFilesToList(
+      File location, List<AnnotationFileResource> resources, AnnotationFileType fileType) {
+    if (isAnnotationFile(location, fileType)) {
+      resources.add(new FileAnnotationFileResource(location));
+    } else if (isJar(location)) {
+      JarFile file;
+      try {
+        file = new JarFile(location);
+      } catch (IOException e) {
+        System.err.println("AnnotationFileUtil: could not process JAR file: " + location);
+        return;
+      }
+      Enumeration<JarEntry> entries = file.entries();
+      while (entries.hasMoreElements()) {
+        JarEntry entry = entries.nextElement();
+        if (isAnnotationFile(entry.getName(), fileType)) {
+          resources.add(new JarEntryAnnotationFileResource(file, entry));
+        }
+      }
+    } else if (location.isDirectory()) {
+      File[] directoryContents = location.listFiles();
+      Arrays.sort(
+          directoryContents,
+          new Comparator<File>() {
+            @Override
+            public int compare(File o1, File o2) {
+              return o1.getName().compareTo(o2.getName());
+            }
+          });
+      for (File enclosed : directoryContents) {
+        addAnnotationFilesToList(enclosed, resources, fileType);
+      }
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/stub/FileAnnotationFileResource.java b/framework/src/main/java/org/checkerframework/framework/stub/FileAnnotationFileResource.java
new file mode 100644
index 0000000..d79cf51
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/stub/FileAnnotationFileResource.java
@@ -0,0 +1,32 @@
+package org.checkerframework.framework.stub;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** {@link File}-based implementation of {@link AnnotationFileResource}. */
+public class FileAnnotationFileResource implements AnnotationFileResource {
+  /** The underlying file. */
+  private final File file;
+
+  /**
+   * Constructs a {@code AnnotationFileResource} for the specified annotation file (stub file or
+   * ajava file).
+   *
+   * @param file the annotation file
+   */
+  public FileAnnotationFileResource(File file) {
+    this.file = file;
+  }
+
+  @Override
+  public String getDescription() {
+    return file.getAbsolutePath();
+  }
+
+  @Override
+  public InputStream getInputStream() throws IOException {
+    return new FileInputStream(file);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/stub/JarEntryAnnotationFileResource.java b/framework/src/main/java/org/checkerframework/framework/stub/JarEntryAnnotationFileResource.java
new file mode 100644
index 0000000..df11ca7
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/stub/JarEntryAnnotationFileResource.java
@@ -0,0 +1,35 @@
+package org.checkerframework.framework.stub;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/** {@link JarEntry}-based implementation of {@link AnnotationFileResource}. */
+public class JarEntryAnnotationFileResource implements AnnotationFileResource {
+  /** The underlying JarFile. */
+  private final JarFile file;
+  /** The entry in the jar file. */
+  private final JarEntry entry;
+
+  /**
+   * Constructs a {@code AnnotationFileResource} for the specified entry in the specified JAR file.
+   *
+   * @param file the JAR file
+   * @param entry the JAR entry
+   */
+  public JarEntryAnnotationFileResource(JarFile file, JarEntry entry) {
+    this.file = file;
+    this.entry = entry;
+  }
+
+  @Override
+  public String getDescription() {
+    return file.getName() + "!" + entry.getName();
+  }
+
+  @Override
+  public InputStream getInputStream() throws IOException {
+    return file.getInputStream(entry);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/stub/JavaStubifier.java b/framework/src/main/java/org/checkerframework/framework/stub/JavaStubifier.java
new file mode 100644
index 0000000..5d2fd8d
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/stub/JavaStubifier.java
@@ -0,0 +1,259 @@
+package org.checkerframework.framework.stub;
+
+import com.github.javaparser.ParseResult;
+import com.github.javaparser.ParserConfiguration;
+import com.github.javaparser.ast.AccessSpecifier;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.NodeList;
+import com.github.javaparser.ast.body.AnnotationDeclaration;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.ConstructorDeclaration;
+import com.github.javaparser.ast.body.EnumDeclaration;
+import com.github.javaparser.ast.body.FieldDeclaration;
+import com.github.javaparser.ast.body.InitializerDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.expr.NormalAnnotationExpr;
+import com.github.javaparser.ast.nodeTypes.modifiers.NodeWithAccessModifiers;
+import com.github.javaparser.ast.stmt.BlockStmt;
+import com.github.javaparser.ast.visitor.ModifierVisitor;
+import com.github.javaparser.utils.CollectionStrategy;
+import com.github.javaparser.utils.ParserCollectionStrategy;
+import com.github.javaparser.utils.ProjectRoot;
+import com.github.javaparser.utils.SourceRoot;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+/**
+ * Process Java source files in a directory to produce, in-place, minimal stub files.
+ *
+ * <p>To process a file means to remove:
+ *
+ * <ol>
+ *   <li>everything that is private or package-private,
+ *   <li>all comments, except for an initial copyright header,
+ *   <li>all method bodies,
+ *   <li>all field initializers,
+ *   <li>all initializer blocks,
+ *   <li>attributes to the {@code Deprecated} annotation (to be Java 8 compatible).
+ * </ol>
+ */
+public class JavaStubifier {
+  /**
+   * Processes each provided command-line argument; see class documentation for details.
+   *
+   * @param args command-line arguments: directories to process
+   */
+  public static void main(String[] args) {
+    if (args.length < 1) {
+      System.err.println("Usage: provide one or more directory names to process");
+      System.exit(1);
+    }
+    for (String arg : args) {
+      process(arg);
+    }
+  }
+
+  /**
+   * Process each file in the given directory; see class documentation for details.
+   *
+   * @param dir directory to process
+   */
+  private static void process(String dir) {
+    Path root = dirnameToPath(dir);
+    MinimizerCallback mc = new MinimizerCallback();
+    CollectionStrategy strategy = new ParserCollectionStrategy();
+    // Required to include directories that contain a module-info.java, which don't parse by
+    // default.
+    strategy.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_11);
+    ProjectRoot projectRoot = strategy.collect(root);
+
+    projectRoot
+        .getSourceRoots()
+        .forEach(
+            sourceRoot -> {
+              try {
+                sourceRoot.parse("", mc);
+              } catch (IOException e) {
+                System.err.println("IOException: " + e);
+              }
+            });
+  }
+
+  /**
+   * Converts a directory name to a path. It issues a warning and terminates the program if the
+   * argument does not exist or is not a directory.
+   *
+   * <p>Unlike {@code Paths.get}, it handles "." which means the current directory in Unix.
+   *
+   * @param dir a directory name
+   * @return a path for the directory name
+   */
+  public static Path dirnameToPath(String dir) {
+    File f = new File(dir);
+    if (!f.exists()) {
+      System.err.printf("Directory %s (%s) does not exist.%n", dir, f);
+      System.exit(1);
+    }
+    if (!f.isDirectory()) {
+      System.err.printf("Not a directory: %s (%s).%n", dir, f);
+      System.exit(1);
+    }
+    String absoluteDir = f.getAbsolutePath();
+    if (absoluteDir.endsWith("/.")) {
+      absoluteDir = absoluteDir.substring(0, absoluteDir.length() - 2);
+    }
+    return Paths.get(absoluteDir);
+  }
+
+  /** Callback to process each Java file; see class documentation for details. */
+  private static class MinimizerCallback implements SourceRoot.Callback {
+    /** The visitor instance. */
+    private final MinimizerVisitor mv;
+
+    /** Create a MinimizerCallback instance. */
+    public MinimizerCallback() {
+      this.mv = new MinimizerVisitor();
+    }
+
+    @Override
+    public Result process(Path localPath, Path absolutePath, ParseResult<CompilationUnit> result) {
+      Result res = Result.SAVE;
+      // System.out.printf("Minimizing %s%n", absolutePath);
+      Optional<CompilationUnit> opt = result.getResult();
+      if (opt.isPresent()) {
+        CompilationUnit cu = opt.get();
+        // Only remove the "contained" comments so that the copyright comment is not removed.
+        cu.getAllContainedComments().forEach(Node::remove);
+        mv.visit(cu, null);
+        if (cu.findAll(ClassOrInterfaceDeclaration.class).isEmpty()
+            && cu.findAll(AnnotationDeclaration.class).isEmpty()
+            && cu.findAll(EnumDeclaration.class).isEmpty()
+            && !absolutePath.endsWith("package-info.java")) {
+          // All content is removed, delete this file.
+          new File(absolutePath.toUri()).delete();
+          res = Result.DONT_SAVE;
+        }
+      }
+      return res;
+    }
+  }
+
+  /** Visitor to process one compilation unit; see class documentation for details. */
+  private static class MinimizerVisitor extends ModifierVisitor<Void> {
+    /** Whether to consider members implicitly public. */
+    private boolean implicitlyPublic = false;
+
+    @Override
+    public ClassOrInterfaceDeclaration visit(ClassOrInterfaceDeclaration cid, Void arg) {
+      boolean prevIP = implicitlyPublic;
+      if (cid.isInterface()) {
+        // All members of interfaces are implicitly public.
+        implicitlyPublic = true;
+      }
+      super.visit(cid, arg);
+      if (cid.isInterface()) {
+        implicitlyPublic = prevIP;
+      }
+      // Do not remove private or package-private classes, because there could
+      // be externally-visible members in externally-visible subclasses.
+      return cid;
+    }
+
+    @Override
+    public EnumDeclaration visit(EnumDeclaration ed, Void arg) {
+      super.visit(ed, arg);
+      // Enums can't be extended, so it is ok to remove them if they are not externally visible.
+      removeIfPrivateOrPkgPrivate(ed);
+      return ed;
+    }
+
+    @Override
+    public ConstructorDeclaration visit(ConstructorDeclaration cd, Void arg) {
+      super.visit(cd, arg);
+      // Constructors cannot be overridden, so it is ok to remove them if they are
+      // not externally visible.
+      if (!removeIfPrivateOrPkgPrivate(cd)) {
+        // ConstructorDeclaration has to have a body
+        cd.setBody(new BlockStmt());
+      }
+      return cd;
+    }
+
+    @Override
+    public MethodDeclaration visit(MethodDeclaration md, Void arg) {
+      super.visit(md, arg);
+      // Non-private methods could be overriden with larger visibility.
+      // So it is only safe to remove private methods, which can't be overridden.
+      if (!removeIfPrivate(md)) {
+        md.removeBody();
+      }
+      return md;
+    }
+
+    @Override
+    public FieldDeclaration visit(FieldDeclaration fd, Void arg) {
+      super.visit(fd, arg);
+      // It is safe to remove fields that are not externally visible.
+      if (!removeIfPrivateOrPkgPrivate(fd)) {
+        fd.getVariables().forEach(v -> v.getInitializer().ifPresent(Node::remove));
+      }
+      return fd;
+    }
+
+    @Override
+    public InitializerDeclaration visit(InitializerDeclaration id, Void arg) {
+      super.visit(id, arg);
+      id.remove();
+      return id;
+    }
+
+    @Override
+    public NormalAnnotationExpr visit(NormalAnnotationExpr nae, Void arg) {
+      super.visit(nae, arg);
+      if (nae.getNameAsString().equals("Deprecated")) {
+        nae.setPairs(new NodeList<>());
+      }
+      return nae;
+    }
+
+    /**
+     * Remove the whole node if it is private or package private.
+     *
+     * @param node a Node to inspect
+     * @return true if the node was removed
+     */
+    private boolean removeIfPrivateOrPkgPrivate(NodeWithAccessModifiers<?> node) {
+      if (implicitlyPublic) {
+        return false;
+      }
+      AccessSpecifier as = node.getAccessSpecifier();
+      if (as == AccessSpecifier.PRIVATE || as == AccessSpecifier.PACKAGE_PRIVATE) {
+        ((Node) node).remove();
+        return true;
+      }
+      return false;
+    }
+
+    /**
+     * Remove the whole node if it is private.
+     *
+     * @param node a Node to inspect
+     * @return true if the node was removed
+     */
+    private boolean removeIfPrivate(NodeWithAccessModifiers<?> node) {
+      if (implicitlyPublic) {
+        return false;
+      }
+      AccessSpecifier as = node.getAccessSpecifier();
+      if (as == AccessSpecifier.PRIVATE) {
+        ((Node) node).remove();
+        return true;
+      }
+      return false;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java b/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java
new file mode 100644
index 0000000..89354e9
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java
@@ -0,0 +1,522 @@
+package org.checkerframework.framework.stub;
+
+import com.github.javaparser.ParseResult;
+import com.github.javaparser.ParserConfiguration;
+import com.github.javaparser.Position;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.NodeList;
+import com.github.javaparser.ast.expr.AnnotationExpr;
+import com.github.javaparser.ast.expr.ArrayInitializerExpr;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
+import com.github.javaparser.ast.expr.MemberValuePair;
+import com.github.javaparser.ast.expr.NormalAnnotationExpr;
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
+import com.github.javaparser.ast.expr.StringLiteralExpr;
+import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations;
+import com.github.javaparser.ast.visitor.GenericListVisitorAdapter;
+import com.github.javaparser.utils.CollectionStrategy;
+import com.github.javaparser.utils.ParserCollectionStrategy;
+import com.github.javaparser.utils.PositionUtils;
+import com.github.javaparser.utils.ProjectRoot;
+import com.github.javaparser.utils.SourceRoot;
+import com.google.common.base.CharMatcher;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.reflect.ClassPath;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import org.checkerframework.javacutil.BugInCF;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * Process Java source files to remove annotations that ought to be inferred.
+ *
+ * <p>Removes annotations from all files in the given directories. Modifies the files in place.
+ *
+ * <p>Does not remove trusted annotations: those that the checker trusts rather than verifies.
+ *
+ * <p>Does not remove annotations at locations where inference does no work:
+ *
+ * <ul>
+ *   <li>within the scope of a relevant @SuppressWarnings
+ *   <li>within the scope of @IgnoreInWholeProgramInference or an annotation meta-annotated with
+ *       that, such as @Option
+ * </ul>
+ *
+ * After removing annotations, javac may issue "warning: [cast] redundant cast to ..." if {@code
+ * -Alint:cast} (or {@code -Alint:all} which implies it) is passed to javac. You can suppress the
+ * warning by passing {@code -Alint:-cast} to javac.
+ */
+public class RemoveAnnotationsForInference {
+
+  /**
+   * Processes each provided command-line argument; see {@link RemoveAnnotationsForInference class
+   * documentation} for details.
+   *
+   * @param args command-line arguments: directories to process
+   */
+  public static void main(String[] args) {
+    if (args.length < 1) {
+      System.err.println("Usage: provide one or more directory names to process");
+      System.exit(1);
+    }
+    for (String arg : args) {
+      process(arg);
+    }
+  }
+
+  /**
+   * Maps from simple names to fully-qualified names of annotations. (Actually, it includes every
+   * class on the classpath.)
+   */
+  static Multimap<String, String> simpleToFullyQualified = ArrayListMultimap.create();
+
+  static {
+    try {
+      ClassPath cp = ClassPath.from(RemoveAnnotationsForInference.class.getClassLoader());
+      for (ClassPath.ClassInfo ci : cp.getTopLevelClasses()) {
+        // There is no way to determine whether `ci` represents an annotation, without loading it.
+        // I could filter using a heuristic: only include classes in a package named "qual".
+        simpleToFullyQualified.put(ci.getSimpleName(), ci.getName());
+      }
+    } catch (IOException e) {
+      throw new BugInCF(e);
+    }
+  }
+
+  /**
+   * Process each file in the given directory; see the {@link RemoveAnnotationsForInference class
+   * documentation} for details.
+   *
+   * @param dir directory to process
+   */
+  private static void process(String dir) {
+
+    Path root = JavaStubifier.dirnameToPath(dir);
+
+    RemoveAnnotationsCallback rac = new RemoveAnnotationsCallback();
+    CollectionStrategy strategy = new ParserCollectionStrategy();
+    // Required to include directories that contain a module-info.java, which don't parse by
+    // default.
+    strategy.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_11);
+    ProjectRoot projectRoot = strategy.collect(root);
+
+    for (SourceRoot sourceRoot : projectRoot.getSourceRoots()) {
+      try {
+        sourceRoot.parse("", rac);
+      } catch (IOException e) {
+        throw new BugInCF(e);
+      }
+    }
+  }
+
+  /**
+   * Callback to process each Java file; see the {@link RemoveAnnotationsForInference class
+   * documentation} for details.
+   */
+  private static class RemoveAnnotationsCallback implements SourceRoot.Callback {
+    /** The visitor instance. */
+    private final RemoveAnnotationsVisitor rav = new RemoveAnnotationsVisitor();
+
+    @Override
+    public Result process(Path localPath, Path absolutePath, ParseResult<CompilationUnit> result) {
+      Optional<CompilationUnit> opt = result.getResult();
+      if (opt.isPresent()) {
+        CompilationUnit cu = opt.get();
+        List<AnnotationExpr> removals = rav.visit(cu, null);
+        removeAnnotations(absolutePath, removals);
+      }
+      return Result.DONT_SAVE;
+    }
+  }
+
+  // An earlier implementation used ModifierVisitor.  However, JavaParser's unparser can change
+  // the structure of the program. For example, it changes `protected @Nullable Object x;` to
+  // `@Nullable protected Object x;` which yields a type.anno.before.modifier error.
+
+  /**
+   * Rewrites the file in place, removing the given annotations from it.
+   *
+   * @param absolutePath the path to the file
+   * @param removals the annotations to remove
+   */
+  static void removeAnnotations(Path absolutePath, List<AnnotationExpr> removals) {
+    if (removals.isEmpty()) {
+      return;
+    }
+
+    List<String> lines;
+    try {
+      lines = Files.readAllLines(absolutePath);
+    } catch (IOException e) {
+      System.out.printf("Problem reading %s: %s%n", absolutePath, e.getMessage());
+      System.exit(1);
+      throw new Error("unreachable");
+    }
+
+    PositionUtils.sortByBeginPosition(removals);
+    Collections.reverse(removals);
+
+    // This code (correctly) assumes that no element of `removals` is contained within another.
+    for (AnnotationExpr removal : removals) {
+      Position begin = removal.getBegin().get();
+      Position end = removal.getEnd().get();
+      int beginLine = begin.line - 1;
+      int beginColumn = begin.column - 1;
+      int endLine = end.line - 1;
+      int endColumn = end.column; // a JavaParser range is inclusive of the character at "end"
+      if (beginLine == endLine) {
+        String line = lines.get(beginLine);
+        String prefix = line.substring(0, beginColumn);
+        String suffix = line.substring(endColumn);
+
+        // Remove whitespace to beautify formatting.
+        suffix = CharMatcher.whitespace().trimLeadingFrom(suffix);
+        if (suffix.startsWith("[")) {
+          prefix = CharMatcher.whitespace().trimTrailingFrom(prefix);
+        }
+
+        String newLine = prefix + suffix;
+        replaceLine(lines, beginLine, newLine);
+      } else {
+        String newLastLine = lines.get(endLine).substring(0, endColumn);
+        replaceLine(lines, endLine, newLastLine);
+        for (int lineno = endLine - 1; lineno > beginLine; lineno--) {
+          lines.remove(lineno);
+        }
+        String newFirstLine = lines.get(beginLine).substring(0, beginColumn);
+        replaceLine(lines, beginLine, newFirstLine);
+      }
+    }
+
+    try {
+      PrintWriter pw = new PrintWriter(absolutePath.toString());
+      for (String line : lines) {
+        pw.println(line);
+      }
+      pw.close();
+    } catch (IOException e) {
+      throw new Error(e);
+    }
+  }
+
+  /**
+   * If {@code newLine} is blank, removes the given line. Otherwise replaces the given line.
+   *
+   * @param lines the list in which to do replacement or removal
+   * @param lineno the index of the line to be removed or replaced
+   * @param newLine the new line for index {@code lineno}
+   */
+  static void replaceLine(List<String> lines, int lineno, String newLine) {
+    if (isBlank(newLine)) {
+      lines.remove(lineno);
+    } else {
+      lines.set(lineno, newLine);
+    }
+  }
+
+  // TODO: Put the following utility methods in StringsPlume.
+
+  /**
+   * Returns true if the string contains only white space codepoints, otherwise false.
+   *
+   * <p>In Java 11, use {@code String.isBlank()} instead.
+   *
+   * @param s a string
+   * @return true if the string contains only white space codepoints, otherwise false
+   */
+  static boolean isBlank(String s) {
+    return s.chars().allMatch(Character::isWhitespace);
+  }
+
+  /**
+   * Visits one compilation unit, collecting the annotations that should be removed. See the {@link
+   * RemoveAnnotationsForInference class documentation} for more details.
+   *
+   * <p>The annotations will be removed from the source code by the {@link #removeAnnotations}
+   * method.
+   */
+  private static class RemoveAnnotationsVisitor
+      extends GenericListVisitorAdapter<AnnotationExpr, Void> {
+
+    /**
+     * Returns the argument if it should be removed from source code.
+     *
+     * @param n an annotation
+     * @param superResult the result of processing the subcomponents of n
+     * @return the argument to remove it, or superResult to retain it
+     */
+    List<AnnotationExpr> processAnnotation(AnnotationExpr n, List<AnnotationExpr> superResult) {
+      if (n == null) {
+        return superResult;
+      }
+
+      String name = n.getNameAsString();
+
+      // Retain annotations defined in the JDK.
+      if (isJdkAnnotation(name)) {
+        return superResult;
+      }
+      // Retain trusted annotations.
+      if (isTrustedAnnotation(name)) {
+        return superResult;
+      }
+      // Retain annotations for which warnings are suppressed.
+      if (isSuppressed(n)) {
+        return superResult;
+      }
+
+      // The default behavior is to remove the annotation.
+      // Don't include superResult, which is contained within `n`.
+      return Collections.singletonList(n);
+    }
+
+    // There are three JavaParser AST nodes that represent annotations
+
+    @Override
+    public List<AnnotationExpr> visit(final MarkerAnnotationExpr n, final Void arg) {
+      return processAnnotation(n, super.visit(n, arg));
+    }
+
+    @Override
+    public List<AnnotationExpr> visit(final NormalAnnotationExpr n, final Void arg) {
+      return processAnnotation(n, super.visit(n, arg));
+    }
+
+    @Override
+    public List<AnnotationExpr> visit(final SingleMemberAnnotationExpr n, final Void arg) {
+      return processAnnotation(n, super.visit(n, arg));
+    }
+  }
+
+  /**
+   * Returns true if the given annotation is defined in the JDK.
+   *
+   * @param name the annotation's name (simple or fully-qualified)
+   * @return true if the given annotation is defined in the JDK
+   */
+  static boolean isJdkAnnotation(String name) {
+    return name.equals("Serial")
+        || name.equals("java.io.Serial")
+        || name.equals("Deprecated")
+        || name.equals("java.lang.Deprecated")
+        || name.equals("FunctionalInterface")
+        || name.equals("java.lang.FunctionalInterface")
+        || name.equals("Override")
+        || name.equals("java.lang.Override")
+        || name.equals("SafeVarargs")
+        || name.equals("java.lang.SafeVarargs")
+        || name.equals("Documented")
+        || name.equals("java.lang.annotation.Documented")
+        || name.equals("Inherited")
+        || name.equals("java.lang.annotation.Inherited")
+        || name.equals("Native")
+        || name.equals("java.lang.annotation.Native")
+        || name.equals("Repeatable")
+        || name.equals("java.lang.annotation.Repeatable")
+        || name.equals("Retention")
+        || name.equals("java.lang.annotation.Retention")
+        || name.equals("SuppressWarnings")
+        || name.equals("java.lang.SuppressWarnings")
+        || name.equals("Target")
+        || name.equals("java.lang.annotation.Target");
+  }
+
+  /**
+   * Returns true if the given annotation is trusted, not checked/verified.
+   *
+   * @param name the annotation's name (simple or fully-qualified)
+   * @return true if the given annotation is trusted, not verified
+   */
+  static boolean isTrustedAnnotation(String name) {
+    // This list was determined by grepping for "trusted" in `qual` directories.
+    return name.equals("Untainted")
+        || name.equals("org.checkerframework.checker.tainting.qual.Untainted")
+        || name.equals("InternedDistinct")
+        || name.equals("org.checkerframework.checker.interning.qual.InternedDistinct")
+        || name.equals("ReturnsReceiver")
+        || name.equals("org.checkerframework.checker.builder.qual.ReturnsReceiver")
+        || name.equals("TerminatesExecution")
+        || name.equals("org.checkerframework.dataflow.qual.TerminatesExecution")
+        || name.equals("Covariant")
+        || name.equals("org.checkerframework.framework.qual.Covariant")
+        || name.equals("NonLeaked")
+        || name.equals("org.checkerframework.common.aliasing.qual.NonLeaked")
+        || name.equals("LeakedToResult")
+        || name.equals("org.checkerframework.common.aliasing.qual.LeakedToResult");
+  }
+
+  // This approach searches upward to find all the active warning suppressions.
+  // An alternative, more efficient approach would be to track the current set of warning
+  // suppressions, using a stack.
+  // There are two problems with the alternative approach (and besides, this approach is fast
+  // enough as it is).
+  //  1. JavaParser sometimes visits members before the annotation, so there was not a chance to
+  //     observe the annotation and place it on the suppression stack.  This should be fixed for
+  //     ModifierVisitor (but not for other visitors such as GenericListVisitorAdapter) in
+  //     JavaParser release 3.19.0.
+  //  2. A user might write an annotation before @SuppressWarnings, as in:
+  //       @Interned @SuppressWarnings("interning")
+  //     The {@code @Interned} annotation is visited before the {@code @SuppressWarnings}
+  //     annotation is.  This could be addressed by searching just the parent's annotations.
+
+  /**
+   * Returns true if warnings about the given annotation are suppressed.
+   *
+   * <p>Its heuristic is to look for a {@code @SuppressWarnings} annotation on a containing program
+   * element, whose string is one of the elements of the annotation's fully-qualified name.
+   *
+   * @param arg an annotation
+   * @return true if warnings about the given annotation are suppressed
+   */
+  private static boolean isSuppressed(AnnotationExpr arg) {
+    String name = arg.getNameAsString();
+
+    // If it's a simple name for which we know a fully-qualified name,
+    // try all fully-qualified names that it could expand to.
+    Collection<String> names;
+    if (simpleToFullyQualified.containsKey(name)) {
+      names = simpleToFullyQualified.get(name);
+    } else {
+      names = Collections.singletonList(name);
+    }
+
+    Iterator<Node> itor = new Node.ParentsVisitor(arg);
+    while (itor.hasNext()) {
+      Node n = itor.next();
+      if (n instanceof NodeWithAnnotations) {
+        for (AnnotationExpr ae : ((NodeWithAnnotations<?>) n).getAnnotations()) {
+          if (suppresses(ae, names)) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if {@code suppressor} suppresses warnings regarding {@code suppressees}.
+   *
+   * @param suppressor an annotation that might be {@code @SuppressWarnings} or like it
+   * @param suppressees an annotation for which warnings might be suppressed. This is actually a
+   *     list: if the annotation was written unqualified, it contains all the fully-qualified names
+   *     that the unqualified annotation might stand for.
+   * @return true if {@code suppressor} suppresses warnings regarding {@code suppressees}
+   */
+  static boolean suppresses(AnnotationExpr suppressor, Collection<String> suppressees) {
+    List<String> suppressWarningsStrings = suppressWarningsStrings(suppressor);
+    if (suppressWarningsStrings == null) {
+      return false;
+    }
+    List<String> checkerNames =
+        CollectionsPlume.mapList(
+            RemoveAnnotationsForInference::checkerName, suppressWarningsStrings);
+    // "allcheckers" suppresses all warnings.
+    if (checkerNames.contains("allcheckers")) {
+      return true;
+    }
+
+    // Try every element of suppressee's fully-qualified name.
+    for (String suppressee : suppressees) {
+      for (String fqPart : suppressee.split("\\.")) {
+        if (checkerNames.contains(fqPart)) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Given a @SuppressWarnings annotation, returns its strings. Given an annotation that suppresses
+   * warnings, returns strings for what it suppresses. Otherwise, returns null.
+   *
+   * @param n an annotation
+   * @return the (effective) arguments to {@code @SuppressWarnings}, or null
+   */
+  private static List<String> suppressWarningsStrings(AnnotationExpr n) {
+    String name = n.getNameAsString();
+
+    if (name.equals("SuppressWarnings") || name.equals("java.lang.SuppressWarnings")) {
+      if (n instanceof MarkerAnnotationExpr) {
+        return Collections.emptyList();
+      } else if (n instanceof NormalAnnotationExpr) {
+        NodeList<MemberValuePair> pairs = ((NormalAnnotationExpr) n).getPairs();
+        assert pairs.size() == 1;
+        MemberValuePair pair = pairs.get(0);
+        assert pair.getName().asString().equals("value");
+        return annotationElementStrings(pair.getValue());
+      } else if (n instanceof SingleMemberAnnotationExpr) {
+        return annotationElementStrings(((SingleMemberAnnotationExpr) n).getMemberValue());
+      } else {
+        throw new BugInCF("Unexpected AnnotationExpr of type %s: %s", n.getClass(), n);
+      }
+    }
+
+    if (name.equals("IgnoreInWholeProgramInference")
+        || name.equals("org.checkerframework.framework.qual.IgnoreInWholeProgramInference")
+        || name.equals("Inject")
+        || name.equals("javax.inject.Inject")
+        || name.equals("Singleton")
+        || name.equals("javax.inject.Singleton")
+        || name.equals("Option")
+        || name.equals("org.plumelib.options.Option")) {
+      return Collections.singletonList("allcheckers");
+    }
+
+    return null;
+  }
+
+  /**
+   * Given an annotation argument for an element of type String[], return a list of strings.
+   *
+   * @param e an annotation argument
+   * @return the strings expressed by {@code e}
+   */
+  private static List<String> annotationElementStrings(Expression e) {
+    if (e instanceof StringLiteralExpr) {
+      return Collections.singletonList(((StringLiteralExpr) e).asString());
+    } else if (e instanceof ArrayInitializerExpr) {
+      NodeList<Expression> values = ((ArrayInitializerExpr) e).getValues();
+      List<String> result = new ArrayList<>(values.size());
+      for (Expression v : values) {
+        if (v instanceof StringLiteralExpr) {
+          result.add(((StringLiteralExpr) v).asString());
+        } else {
+          throw new BugInCF("Unexpected annotation element of type %s: %s", v.getClass(), v);
+        }
+      }
+      return result;
+    } else {
+      throw new BugInCF("Unexpected %s: %s", e.getClass(), e);
+    }
+  }
+
+  /**
+   * Returns the "checker name" part of a SuppressWarnings string: the part before the colon, or the
+   * whole thing if it contains no colon.
+   *
+   * @param s a SuppressWarnings string: the argument to {@code @SuppressWarnings}
+   * @return the part of s before the colon, or the whole thing if it contains no colon
+   */
+  private static String checkerName(String s) {
+    int colonPos = s.indexOf(":");
+    if (colonPos == -1) {
+      return s;
+    } else {
+      return s.substring(colonPos + 1);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java b/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java
new file mode 100644
index 0000000..ed230fe
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java
@@ -0,0 +1,454 @@
+package org.checkerframework.framework.stub;
+
+import com.sun.tools.javac.main.JavaCompiler;
+import com.sun.tools.javac.main.Option;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Options;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.SystemUtil;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Generates a stub file from a single class or an entire package.
+ *
+ * <p>TODO: StubGenerator needs to be reimplemented, because it no longer works due to changes in
+ * JDK 9.
+ *
+ * @checker_framework.manual #stub Using stub classes
+ */
+public class StubGenerator {
+  /** The indentation for the class. */
+  private static final String INDENTION = "    ";
+
+  /** The output stream. */
+  private final PrintStream out;
+
+  /** the current indentation for the line being processed. */
+  private String currentIndention = "";
+
+  /** the package of the class being processed. */
+  private String currentPackage = null;
+
+  /** Constructs a {@code StubGenerator} that outputs to {@code System.out}. */
+  public StubGenerator() {
+    this(System.out);
+  }
+
+  /**
+   * Constructs a {@code StubGenerator} that outputs to the provided output stream.
+   *
+   * @param out the output stream
+   */
+  public StubGenerator(PrintStream out) {
+    this.out = out;
+  }
+
+  /**
+   * Constructs a {@code StubGenerator} that outputs to the provided output stream.
+   *
+   * @param out the output stream
+   */
+  public StubGenerator(OutputStream out) {
+    this.out = new PrintStream(out);
+  }
+
+  /** Generate the stub file for all the classes within the provided package. */
+  public void stubFromField(Element elt) {
+    if (!(elt.getKind() == ElementKind.FIELD)) {
+      return;
+    }
+
+    String pkg = ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(elt));
+    if (!"".equals(pkg)) {
+      currentPackage = pkg;
+      currentIndention = "    ";
+      indent();
+    }
+    VariableElement field = (VariableElement) elt;
+    printFieldDecl(field);
+  }
+
+  /** Generate the stub file for all the classes within the provided package. */
+  public void stubFromPackage(PackageElement packageElement) {
+    currentPackage = packageElement.getQualifiedName().toString();
+
+    indent();
+    out.print("package ");
+    out.print(currentPackage);
+    out.println(";");
+
+    for (TypeElement element : ElementFilter.typesIn(packageElement.getEnclosedElements())) {
+      if (isPublicOrProtected(element)) {
+        out.println();
+        printClass(element);
+      }
+    }
+  }
+
+  /** Generate the stub file for all the classes within the provided package. */
+  public void stubFromMethod(Element elt) {
+    if (!(elt.getKind() == ElementKind.CONSTRUCTOR || elt.getKind() == ElementKind.METHOD)) {
+      return;
+    }
+
+    String newPackage = ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(elt));
+    if (!newPackage.equals("")) {
+      currentPackage = newPackage;
+      currentIndention = "    ";
+      indent();
+    }
+    ExecutableElement method = (ExecutableElement) elt;
+
+    printMethodDecl(method);
+  }
+
+  /** Generate the stub file for provided class. The generated file includes the package name. */
+  public void stubFromType(TypeElement typeElement) {
+
+    // only output stub for classes or interfaces.  not enums
+    if (typeElement.getKind() != ElementKind.CLASS
+        && typeElement.getKind() != ElementKind.INTERFACE) {
+      return;
+    }
+
+    String newPackageName =
+        ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(typeElement));
+    boolean newPackage = !newPackageName.equals(currentPackage);
+    currentPackage = newPackageName;
+
+    if (newPackage) {
+      indent();
+
+      out.print("package ");
+      out.print(currentPackage);
+      out.println(";");
+      out.println();
+    }
+    String fullClassName = ElementUtils.getQualifiedClassName(typeElement).toString();
+
+    String className =
+        fullClassName.substring(
+            fullClassName.indexOf(currentPackage)
+                + currentPackage.length()
+                // +1 because currentPackage doesn't include
+                // the . between the package name and the classname
+                + 1);
+
+    int index = className.lastIndexOf('.');
+    if (index == -1) {
+      printClass(typeElement);
+    } else {
+      String outer = className.substring(0, index);
+      printClass(typeElement, outer.replace('.', '$'));
+    }
+  }
+
+  /** helper method that outputs the index for the provided class. */
+  private void printClass(TypeElement typeElement) {
+    printClass(typeElement, null);
+  }
+
+  /**
+   * Helper method that prints the stub file for the provided class.
+   *
+   * @param typeElement the class to output
+   * @param outerClass the outer class of the class, or null if {@code typeElement} is a top-level
+   *     class
+   */
+  private void printClass(TypeElement typeElement, @Nullable String outerClass) {
+    indent();
+
+    List<? extends AnnotationMirror> teannos = typeElement.getAnnotationMirrors();
+    if (teannos != null && !teannos.isEmpty()) {
+      for (AnnotationMirror am : teannos) {
+        out.println(am);
+      }
+    }
+
+    if (typeElement.getKind() == ElementKind.INTERFACE) {
+      out.print("interface");
+    } else if (typeElement.getKind() == ElementKind.CLASS) {
+      out.print("class");
+    } else {
+      return;
+    }
+
+    out.print(' ');
+    if (outerClass != null) {
+      out.print(outerClass + "$");
+    }
+    out.print(typeElement.getSimpleName());
+
+    // Type parameters
+    if (!typeElement.getTypeParameters().isEmpty()) {
+      out.print('<');
+      out.print(formatList(typeElement.getTypeParameters()));
+      out.print('>');
+    }
+
+    // Extends
+    if (typeElement.getSuperclass().getKind() != TypeKind.NONE
+        && !TypesUtils.isObject(typeElement.getSuperclass())) {
+      out.print(" extends ");
+      out.print(formatType(typeElement.getSuperclass()));
+    }
+
+    // implements
+    if (!typeElement.getInterfaces().isEmpty()) {
+      final boolean isInterface = typeElement.getKind() == ElementKind.INTERFACE;
+      out.print(isInterface ? " extends " : " implements ");
+      List<String> ls =
+          CollectionsPlume.mapList(StubGenerator::formatType, typeElement.getInterfaces());
+      out.print(formatList(ls));
+    }
+
+    out.println(" {");
+    String tempIndention = currentIndention;
+
+    currentIndention = currentIndention + INDENTION;
+
+    // Inner classes, which the stub generator prints later.
+    List<TypeElement> innerClass = new ArrayList<>();
+    // side-effects innerClass
+    printTypeMembers(typeElement.getEnclosedElements(), innerClass);
+
+    currentIndention = tempIndention;
+    indent();
+    out.println("}");
+
+    for (TypeElement element : innerClass) {
+      printClass(element, typeElement.getSimpleName().toString());
+    }
+  }
+
+  /**
+   * Helper method that outputs the public or protected inner members of a class.
+   *
+   * @param members list of the class members
+   */
+  private void printTypeMembers(List<? extends Element> members, List<TypeElement> innerClass) {
+    for (Element element : members) {
+      if (isPublicOrProtected(element)) {
+        printMember(element, innerClass);
+      }
+    }
+  }
+
+  /** Helper method that outputs the declaration of the member. */
+  private void printMember(Element member, List<TypeElement> innerClass) {
+    if (member.getKind().isField()) {
+      printFieldDecl((VariableElement) member);
+    } else if (member instanceof ExecutableElement) {
+      printMethodDecl((ExecutableElement) member);
+    } else if (member instanceof TypeElement) {
+      innerClass.add((TypeElement) member);
+    }
+  }
+
+  /**
+   * Helper method that outputs the field declaration for the given field.
+   *
+   * <p>It indicates whether the field is {@code protected}.
+   */
+  private void printFieldDecl(VariableElement field) {
+    if ("class".equals(field.getSimpleName().toString())) {
+      error("Cannot write class literals in stub files.");
+      return;
+    }
+
+    indent();
+
+    List<? extends AnnotationMirror> veannos = field.getAnnotationMirrors();
+    if (veannos != null && !veannos.isEmpty()) {
+      for (AnnotationMirror am : veannos) {
+        out.println(am);
+      }
+    }
+
+    // if protected, indicate that, but not public
+    if (field.getModifiers().contains(Modifier.PROTECTED)) {
+      out.print("protected ");
+    }
+    if (field.getModifiers().contains(Modifier.STATIC)) {
+      out.print("static ");
+    }
+    if (field.getModifiers().contains(Modifier.FINAL)) {
+      out.print("final ");
+    }
+
+    out.print(formatType(field.asType()));
+
+    out.print(" ");
+    out.print(field.getSimpleName());
+    out.println(';');
+  }
+
+  /**
+   * Helper method that outputs the method declaration for the given method.
+   *
+   * <p>IT indicates whether the field is {@code protected}.
+   */
+  private void printMethodDecl(ExecutableElement method) {
+    indent();
+
+    List<? extends AnnotationMirror> eeannos = method.getAnnotationMirrors();
+    if (eeannos != null && !eeannos.isEmpty()) {
+      for (AnnotationMirror am : eeannos) {
+        out.println(am);
+      }
+    }
+
+    // if protected, indicate that, but not public
+    if (method.getModifiers().contains(Modifier.PROTECTED)) {
+      out.print("protected ");
+    }
+    if (method.getModifiers().contains(Modifier.STATIC)) {
+      out.print("static ");
+    }
+
+    // print Generic arguments
+    if (!method.getTypeParameters().isEmpty()) {
+      out.print('<');
+      out.print(formatList(method.getTypeParameters()));
+      out.print("> ");
+    }
+
+    // not return type for constructors
+    if (method.getKind() != ElementKind.CONSTRUCTOR) {
+      out.print(formatType(method.getReturnType()));
+      out.print(" ");
+      out.print(method.getSimpleName());
+    } else {
+      out.print(method.getEnclosingElement().getSimpleName());
+    }
+
+    out.print('(');
+
+    boolean isFirst = true;
+    for (VariableElement param : method.getParameters()) {
+      if (!isFirst) {
+        out.print(", ");
+      }
+      out.print(formatType(param.asType()));
+      out.print(' ');
+      out.print(param.getSimpleName());
+      isFirst = false;
+    }
+
+    out.print(')');
+
+    if (!method.getThrownTypes().isEmpty()) {
+      out.print(" throws ");
+      List<String> ltt =
+          CollectionsPlume.mapList(StubGenerator::formatType, method.getThrownTypes());
+      out.print(formatList(ltt));
+    }
+    out.println(';');
+  }
+
+  /** Indent the current line. */
+  private void indent() {
+    out.print(currentIndention);
+  }
+
+  /**
+   * Return a string representation of the list in the form of {@code item1, item2, item3, ...},
+   * without surrounding square brackets as the default representation has.
+   *
+   * @param lst a list to format
+   * @return a string representation of the list, without surrounding square brackets
+   */
+  private String formatList(List<?> lst) {
+    return StringsPlume.join(", ", lst);
+  }
+
+  /** Returns true if the element is public or protected element. */
+  private boolean isPublicOrProtected(Element element) {
+    return element.getModifiers().contains(Modifier.PUBLIC)
+        || element.getModifiers().contains(Modifier.PROTECTED);
+  }
+
+  /**
+   * Returns the simple name of the type.
+   *
+   * @param typeRep a type
+   * @return the simple name of the type
+   */
+  private static String formatType(TypeMirror typeRep) {
+    StringTokenizer tokenizer = new StringTokenizer(typeRep.toString(), "()<>[], ", true);
+    StringBuilder sb = new StringBuilder();
+
+    while (tokenizer.hasMoreTokens()) {
+      String token = tokenizer.nextToken();
+      if (token.length() == 1 || token.lastIndexOf('.') == -1) {
+        sb.append(token);
+      } else {
+        int index = token.lastIndexOf('.');
+        sb.append(token.substring(index + 1));
+      }
+    }
+    return sb.toString();
+  }
+
+  /**
+   * The main entry point to StubGenerator.
+   *
+   * @param args command-line arguments
+   */
+  @SuppressWarnings("signature") // User-supplied arguments to main
+  public static void main(String[] args) {
+    if (args.length != 1) {
+      System.out.println("Usage:");
+      System.out.println("    java StubGenerator [class or package name]");
+      return;
+    }
+
+    Context context = new Context();
+    Options options = Options.instance(context);
+    if (SystemUtil.getJreVersion() == 8) {
+      options.put(Option.SOURCE, "8");
+      options.put(Option.TARGET, "8");
+    }
+
+    JavaCompiler javac = JavaCompiler.instance(context);
+    javac.initModules(com.sun.tools.javac.util.List.nil());
+    javac.enterDone();
+
+    ProcessingEnvironment env = JavacProcessingEnvironment.instance(context);
+
+    StubGenerator generator = new StubGenerator();
+
+    if (env.getElementUtils().getPackageElement(args[0]) != null) {
+      generator.stubFromPackage(env.getElementUtils().getPackageElement(args[0]));
+    } else if (env.getElementUtils().getTypeElement(args[0]) != null) {
+      generator.stubFromType(env.getElementUtils().getTypeElement(args[0]));
+    } else {
+      error("Couldn't find a package or a class named " + args[0]);
+    }
+  }
+
+  private static void error(String string) {
+    System.err.println("StubGenerator: " + string);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java b/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java
new file mode 100644
index 0000000..dad0e8b
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java
@@ -0,0 +1,696 @@
+package org.checkerframework.framework.stub;
+
+import com.github.javaparser.ParseException;
+import com.github.javaparser.ParseProblemException;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.ImportDeclaration;
+import com.github.javaparser.ast.NodeList;
+import com.github.javaparser.ast.PackageDeclaration;
+import com.github.javaparser.ast.StubUnit;
+import com.github.javaparser.ast.body.AnnotationDeclaration;
+import com.github.javaparser.ast.body.BodyDeclaration;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.ConstructorDeclaration;
+import com.github.javaparser.ast.body.EnumConstantDeclaration;
+import com.github.javaparser.ast.body.EnumDeclaration;
+import com.github.javaparser.ast.body.FieldDeclaration;
+import com.github.javaparser.ast.body.InitializerDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.Parameter;
+import com.github.javaparser.ast.body.ReceiverParameter;
+import com.github.javaparser.ast.body.TypeDeclaration;
+import com.github.javaparser.ast.body.VariableDeclarator;
+import com.github.javaparser.ast.expr.AnnotationExpr;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.ObjectCreationExpr;
+import com.github.javaparser.ast.expr.VariableDeclarationExpr;
+import com.github.javaparser.ast.stmt.BlockStmt;
+import com.github.javaparser.ast.type.ArrayType;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import com.github.javaparser.ast.type.PrimitiveType;
+import com.github.javaparser.ast.type.ReferenceType;
+import com.github.javaparser.ast.type.Type;
+import com.github.javaparser.ast.type.TypeParameter;
+import com.github.javaparser.ast.type.VoidType;
+import com.github.javaparser.ast.type.WildcardType;
+import com.github.javaparser.ast.visitor.GenericVisitorAdapter;
+import java.io.BufferedWriter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.checker.signature.qual.ClassGetName;
+import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.framework.util.JavaParserUtil;
+import org.checkerframework.javacutil.BugInCF;
+import org.plumelib.reflection.Signatures;
+import scenelib.annotations.Annotation;
+import scenelib.annotations.el.AClass;
+import scenelib.annotations.el.ADeclaration;
+import scenelib.annotations.el.AElement;
+import scenelib.annotations.el.AField;
+import scenelib.annotations.el.AMethod;
+import scenelib.annotations.el.AScene;
+import scenelib.annotations.el.ATypeElement;
+import scenelib.annotations.el.AnnotationDef;
+import scenelib.annotations.el.BoundLocation;
+import scenelib.annotations.el.DefException;
+import scenelib.annotations.el.LocalLocation;
+import scenelib.annotations.el.TypePathEntry;
+import scenelib.annotations.io.IndexFileParser;
+import scenelib.annotations.io.IndexFileWriter;
+
+/**
+ * Convert a JAIF file plus a stub file into index files (JAIFs). Note that the resulting index
+ * files will not include annotation definitions, for which stubfiles do not generally provide
+ * complete information.
+ *
+ * <p>An instance of the class represents conversion of 1 stub file, but the static {@link
+ * #main(String[])} method converts multiple stub files, instantiating the class multiple times.
+ */
+public class ToIndexFileConverter extends GenericVisitorAdapter<Void, AElement> {
+  // The possessive modifiers "*+" are for efficiency only.
+  // private static Pattern packagePattern =
+  //         Pattern.compile("\\bpackage *+((?:[^.]*+[.] *+)*+[^ ]*) *+;");
+  private static Pattern importPattern =
+      Pattern.compile("\\bimport *+((?:[^.]*+[.] *+)*+[^ ]*) *+;");
+
+  /**
+   * Package name that is active at the current point in the input file. Changes as package
+   * declarations are encountered.
+   */
+  private final @DotSeparatedIdentifiers String pkgName;
+  /** Imports that appear in the stub file. */
+  private final List<String> imports;
+  /** A scene read from the input JAIF file, and will be written to the output JAIF file. */
+  private final AScene scene;
+
+  /**
+   * @param pkgDecl AST node for package declaration
+   * @param importDecls AST nodes for import declarations
+   * @param scene scene for visitor methods to fill in
+   */
+  @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for getNameAsString
+  public ToIndexFileConverter(
+      @Nullable PackageDeclaration pkgDecl, List<ImportDeclaration> importDecls, AScene scene) {
+    this.scene = scene;
+    pkgName = pkgDecl == null ? null : pkgDecl.getNameAsString();
+    if (importDecls == null) {
+      imports = Collections.emptyList();
+    } else {
+      ArrayList<String> imps = new ArrayList<>(importDecls.size());
+      for (ImportDeclaration decl : importDecls) {
+        if (!decl.isStatic()) {
+          Matcher m = importPattern.matcher(decl.toString());
+          if (m.find()) {
+            String s = m.group(1);
+            if (s != null) {
+              imps.add(s);
+            }
+          }
+        }
+      }
+      imps.trimToSize();
+      imports = Collections.unmodifiableList(imps);
+    }
+  }
+
+  /**
+   * Parse stub files and write out equivalent JAIFs. Note that the results do not include
+   * annotation definitions, for which stubfiles do not generally provide complete information.
+   *
+   * @param args name of JAIF with annotation definition, followed by names of stub files to be
+   *     converted (if none given, program reads from standard input)
+   */
+  public static void main(String[] args) {
+    if (args.length < 1) {
+      System.err.println("usage: java ToIndexFileConverter myfile.jaif [stubfile...]");
+      System.err.println("(myfile.jaif contains needed annotation definitions)");
+      System.exit(1);
+    }
+
+    AScene scene = new AScene();
+    try {
+      // args[0] is a jaif file with needed annotation definitions
+      IndexFileParser.parseFile(args[0], scene);
+
+      if (args.length == 1) {
+        convert(scene, System.in, System.out);
+        return;
+      }
+
+      for (int i = 1; i < args.length; i++) {
+        String f0 = args[i];
+        String f1 = (f0.endsWith(".astub") ? f0.substring(0, f0.length() - 6) : f0) + ".jaif";
+        try (InputStream in = new FileInputStream(f0);
+            OutputStream out = new FileOutputStream(f1); ) {
+          convert(new AScene(scene), in, out);
+        }
+      }
+    } catch (Throwable e) {
+      e.printStackTrace();
+      System.exit(1);
+    }
+  }
+
+  /**
+   * Augment given scene with information from stubfile, reading stubs from input stream and writing
+   * JAIF to output stream.
+   *
+   * @param scene the initial scene
+   * @param in stubfile contents
+   * @param out JAIF representing augmented scene
+   * @throws ParseException if the stub file cannot be parsed
+   * @throws DefException if two different definitions of the same annotation cannot be unified
+   * @throws IOException if there is trouble with file reading or writing
+   */
+  private static void convert(AScene scene, InputStream in, OutputStream out)
+      throws IOException, DefException, ParseException {
+    StubUnit iu;
+    try {
+      iu = JavaParserUtil.parseStubUnit(in);
+    } catch (ParseProblemException e) {
+      iu = null;
+      throw new BugInCF(
+          "ToIndexFileConverter: exception from JavaParser.parseStubUnit for InputStream."
+              + System.lineSeparator()
+              + "Problem message with problems encountered: "
+              + e.getMessage());
+    }
+    extractScene(iu, scene);
+    try (Writer w = new BufferedWriter(new OutputStreamWriter(out))) {
+      IndexFileWriter.write(scene, w);
+    }
+  }
+
+  /**
+   * Entry point of recursive-descent IndexUnit to AScene transformer. It operates by visiting the
+   * stub and scene in parallel, descending into them in the same way. It augments the existing
+   * scene (it does not create a new scene).
+   *
+   * @param iu {@link StubUnit} representing stubfile
+   */
+  private static void extractScene(StubUnit iu, AScene scene) {
+    for (CompilationUnit cu : iu.getCompilationUnits()) {
+      NodeList<TypeDeclaration<?>> typeDecls = cu.getTypes();
+      if (typeDecls != null && cu.getPackageDeclaration().isPresent()) {
+        List<ImportDeclaration> impDecls = cu.getImports();
+        PackageDeclaration pkgDecl = cu.getPackageDeclaration().get();
+        for (TypeDeclaration<?> typeDecl : typeDecls) {
+          ToIndexFileConverter converter = new ToIndexFileConverter(pkgDecl, impDecls, scene);
+          String pkgName = converter.pkgName;
+          String name = typeDecl.getNameAsString();
+          if (pkgName != null) {
+            name = pkgName + "." + name;
+          }
+          typeDecl.accept(converter, scene.classes.getVivify(name));
+        }
+      }
+    }
+  }
+
+  /**
+   * Builds simplified annotation from its declaration. Only the name is included, because stubfiles
+   * do not generally have access to the full definitions of annotations.
+   */
+  private static Annotation extractAnnotation(AnnotationExpr expr) {
+    String exprName = expr.toString().substring(1); // leave off leading '@'
+
+    // Eliminate jdk.Profile+Annotation, a synthetic annotation that
+    // the JDK adds, apparently for profiling.
+    if (exprName.contains("+")) {
+      return null;
+    }
+    @SuppressWarnings("signature") // special case for annotations containing "+"
+    AnnotationDef def =
+        new AnnotationDef(exprName, "ToIndexFileConverter.extractAnnotation(" + expr + ")");
+    def.setFieldTypes(Collections.emptyMap());
+    return new Annotation(def, Collections.emptyMap());
+  }
+
+  @Override
+  public Void visit(AnnotationDeclaration decl, AElement elem) {
+    return null;
+  }
+
+  @Override
+  public Void visit(BlockStmt stmt, AElement elem) {
+    return null;
+    // super.visit(stmt, elem);
+  }
+
+  @Override
+  public Void visit(ClassOrInterfaceDeclaration decl, AElement elem) {
+    visitDecl(decl, (ADeclaration) elem);
+    return super.visit(decl, elem);
+  }
+
+  @Override
+  public Void visit(ConstructorDeclaration decl, AElement elem) {
+    List<Parameter> params = decl.getParameters();
+    List<AnnotationExpr> rcvrAnnos = decl.getAnnotations();
+    BlockStmt body = decl.getBody();
+    StringBuilder sb = new StringBuilder("<init>(");
+    AClass clazz = (AClass) elem;
+    AMethod method;
+
+    // Some of the methods in the generated parser use null to represent an empty list.
+    if (params != null) {
+      for (Parameter param : params) {
+        Type ptype = param.getType();
+        sb.append(getJVML(ptype));
+      }
+    }
+    sb.append(")V");
+    method = clazz.methods.getVivify(sb.toString());
+    visitDecl(decl, method);
+    if (params != null) {
+      for (int i = 0; i < params.size(); i++) {
+        Parameter param = params.get(i);
+        AField field = method.parameters.getVivify(i);
+        visitType(param.getType(), field.type);
+      }
+    }
+    if (rcvrAnnos != null) {
+      for (AnnotationExpr expr : rcvrAnnos) {
+        Annotation anno = extractAnnotation(expr);
+        method.receiver.tlAnnotationsHere.add(anno);
+      }
+    }
+    return body == null ? null : body.accept(this, method);
+    // return super.visit(decl, elem);
+  }
+
+  @Override
+  public Void visit(EnumConstantDeclaration decl, AElement elem) {
+    AField field = ((AClass) elem).fields.getVivify(decl.getNameAsString());
+    visitDecl(decl, field);
+    return super.visit(decl, field);
+  }
+
+  @Override
+  public Void visit(EnumDeclaration decl, AElement elem) {
+    visitDecl(decl, (ADeclaration) elem);
+    return super.visit(decl, elem);
+  }
+
+  @Override
+  public Void visit(FieldDeclaration decl, AElement elem) {
+    for (VariableDeclarator v : decl.getVariables()) {
+      AClass clazz = (AClass) elem;
+      AField field = clazz.fields.getVivify(v.getNameAsString());
+      visitDecl(decl, field);
+      visitType(decl.getCommonType(), field.type);
+    }
+    return null;
+  }
+
+  @Override
+  public Void visit(InitializerDeclaration decl, AElement elem) {
+    BlockStmt block = decl.getBody();
+    AClass clazz = (AClass) elem;
+    block.accept(this, clazz.methods.getVivify(decl.isStatic() ? "<clinit>" : "<init>"));
+    return null;
+  }
+
+  @Override
+  public Void visit(MethodDeclaration decl, AElement elem) {
+    Type type = decl.getType();
+    List<Parameter> params = decl.getParameters();
+    List<TypeParameter> typeParams = decl.getTypeParameters();
+    Optional<ReceiverParameter> rcvrParam = decl.getReceiverParameter();
+    BlockStmt body = decl.getBody().orElse(null);
+    StringBuilder sb = new StringBuilder(decl.getNameAsString()).append('(');
+    AClass clazz = (AClass) elem;
+    AMethod method;
+    if (params != null) {
+      for (Parameter param : params) {
+        Type ptype = param.getType();
+        sb.append(getJVML(ptype));
+      }
+    }
+    sb.append(')').append(getJVML(type));
+    method = clazz.methods.getVivify(sb.toString());
+    visitDecl(decl, method);
+    visitType(type, method.returnType);
+    if (params != null) {
+      for (int i = 0; i < params.size(); i++) {
+        Parameter param = params.get(i);
+        AField field = method.parameters.getVivify(i);
+        visitType(param.getType(), field.type);
+      }
+    }
+    if (rcvrParam.isPresent()) {
+      for (AnnotationExpr expr : rcvrParam.get().getAnnotations()) {
+        Annotation anno = extractAnnotation(expr);
+        method.receiver.type.tlAnnotationsHere.add(anno);
+      }
+    }
+    if (typeParams != null) {
+      for (int i = 0; i < typeParams.size(); i++) {
+        TypeParameter typeParam = typeParams.get(i);
+        List<ClassOrInterfaceType> bounds = typeParam.getTypeBound();
+        if (bounds != null) {
+          for (int j = 0; j < bounds.size(); j++) {
+            ClassOrInterfaceType bound = bounds.get(j);
+            BoundLocation loc = new BoundLocation(i, j);
+            bound.accept(this, method.bounds.getVivify(loc));
+          }
+        }
+      }
+    }
+    return body == null ? null : body.accept(this, method);
+  }
+
+  @Override
+  public Void visit(ObjectCreationExpr expr, AElement elem) {
+    ClassOrInterfaceType type = expr.getType();
+    AClass clazz = scene.classes.getVivify(type.getNameAsString());
+    Expression scope = expr.getScope().orElse(null);
+    List<Type> typeArgs = expr.getTypeArguments().orElse(null);
+    List<Expression> args = expr.getArguments();
+    NodeList<BodyDeclaration<?>> bodyDecls = expr.getAnonymousClassBody().orElse(null);
+    if (scope != null) {
+      scope.accept(this, elem);
+    }
+    if (args != null) {
+      for (Expression arg : args) {
+        arg.accept(this, elem);
+      }
+    }
+    if (typeArgs != null) {
+      for (Type typeArg : typeArgs) {
+        typeArg.accept(this, elem);
+      }
+    }
+    type.accept(this, clazz);
+    if (bodyDecls != null) {
+      for (BodyDeclaration<?> decl : bodyDecls) {
+        decl.accept(this, clazz);
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public Void visit(VariableDeclarationExpr expr, AElement elem) {
+    List<AnnotationExpr> annos = expr.getAnnotations();
+    AMethod method = (AMethod) elem;
+    List<VariableDeclarator> varDecls = expr.getVariables();
+    for (int i = 0; i < varDecls.size(); i++) {
+      VariableDeclarator decl = varDecls.get(i);
+      LocalLocation loc = new LocalLocation(i, decl.getNameAsString());
+      AField field = method.body.locals.getVivify(loc);
+      visitType(expr.getCommonType(), field.type);
+      if (annos != null) {
+        for (AnnotationExpr annoExpr : annos) {
+          Annotation anno = extractAnnotation(annoExpr);
+          field.tlAnnotationsHere.add(anno);
+        }
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Copies information from an AST declaration node to an {@link ADeclaration}. Called by visitors
+   * for BodyDeclaration subclasses.
+   */
+  private Void visitDecl(BodyDeclaration<?> decl, ADeclaration elem) {
+    NodeList<AnnotationExpr> annoExprs = decl.getAnnotations();
+    if (annoExprs != null) {
+      for (AnnotationExpr annoExpr : annoExprs) {
+        Annotation anno = extractAnnotation(annoExpr);
+        elem.tlAnnotationsHere.add(anno);
+      }
+    }
+    return null;
+  }
+
+  /** Copies information from an AST type node to an {@link ATypeElement}. */
+  private Void visitType(Type type, final ATypeElement elem) {
+    List<AnnotationExpr> exprs = type.getAnnotations();
+    if (exprs != null) {
+      for (AnnotationExpr expr : exprs) {
+        Annotation anno = extractAnnotation(expr);
+        if (anno != null) {
+          elem.tlAnnotationsHere.add(anno);
+        }
+      }
+    }
+    visitInnerTypes(type, elem);
+    return null;
+  }
+
+  /**
+   * Copies information from an AST type node's inner type nodes to an {@link ATypeElement}.
+   *
+   * @param type AST Type node to inspect
+   * @param elem destination type element
+   */
+  private static Void visitInnerTypes(Type type, final ATypeElement elem) {
+    return type.accept(
+        new GenericVisitorAdapter<Void, List<TypePathEntry>>() {
+          @Override
+          public Void visit(ClassOrInterfaceType type, List<TypePathEntry> loc) {
+            if (type.getTypeArguments().isPresent()) {
+              List<Type> typeArgs = type.getTypeArguments().get();
+              for (int i = 0; i < typeArgs.size(); i++) {
+                Type inner = typeArgs.get(i);
+                List<TypePathEntry> ext = extendedTypePath(loc, 3, i);
+                visitInnerType(inner, ext);
+              }
+            }
+            return null;
+          }
+
+          @Override
+          public Void visit(ArrayType type, List<TypePathEntry> loc) {
+            List<TypePathEntry> ext = loc;
+            int n = type.getArrayLevel();
+            Type currentType = type;
+            for (int i = 0; i < n; i++) {
+              ext = extendedTypePath(ext, 1, 0);
+              for (AnnotationExpr expr : currentType.getAnnotations()) {
+                ATypeElement typeElem = elem.innerTypes.getVivify(ext);
+                Annotation anno = extractAnnotation(expr);
+                typeElem.tlAnnotationsHere.add(anno);
+              }
+              currentType =
+                  ((com.github.javaparser.ast.type.ArrayType) currentType).getComponentType();
+            }
+            return null;
+          }
+
+          @Override
+          public Void visit(WildcardType type, List<TypePathEntry> loc) {
+            ReferenceType lower = type.getExtendedType().orElse(null);
+            ReferenceType upper = type.getSuperType().orElse(null);
+            if (lower != null) {
+              List<TypePathEntry> ext = extendedTypePath(loc, 2, 0);
+              visitInnerType(lower, ext);
+            }
+            if (upper != null) {
+              List<TypePathEntry> ext = extendedTypePath(loc, 2, 0);
+              visitInnerType(upper, ext);
+            }
+            return null;
+          }
+
+          /** Copies information from an AST inner type node to an {@link ATypeElement}. */
+          private void visitInnerType(Type type, List<TypePathEntry> loc) {
+            ATypeElement typeElem = elem.innerTypes.getVivify(loc);
+            for (AnnotationExpr expr : type.getAnnotations()) {
+              Annotation anno = extractAnnotation(expr);
+              typeElem.tlAnnotationsHere.add(anno);
+              type.accept(this, loc);
+            }
+          }
+
+          /**
+           * Extends type path by one element.
+           *
+           * @see TypePathEntry(int, int)
+           */
+          private List<TypePathEntry> extendedTypePath(List<TypePathEntry> loc, int tag, int arg) {
+            List<TypePathEntry> path = new ArrayList<>(loc.size() + 1);
+            path.addAll(loc);
+            path.add(TypePathEntry.create(tag, arg));
+            return path;
+          }
+        },
+        Collections.emptyList());
+  }
+
+  /**
+   * Computes a type's "binary name".
+   *
+   * @param type the type
+   * @return the type's binary name
+   */
+  private String getJVML(Type type) {
+    return type.accept(
+        new GenericVisitorAdapter<String, Void>() {
+          @Override
+          public String visit(ClassOrInterfaceType type, Void v) {
+            @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for getNameAsString
+            @FullyQualifiedName String typeName = type.getNameAsString();
+            @SuppressWarnings("signature" // TODO:  bug in ToIndexFileConverter:
+            // resolve requires a @BinaryName, but this passes a @FullyQualifiedName.
+            // They differ for inner classes.
+            )
+            String name = resolve(typeName);
+            if (name == null) {
+              // could be defined in the same stub file
+              return "L" + typeName + ";";
+            }
+            return "L" + String.join("/", name.split("\\.")) + ";";
+          }
+
+          @Override
+          public String visit(PrimitiveType type, Void v) {
+            switch (type.getType()) {
+              case BOOLEAN:
+                return "Z";
+              case BYTE:
+                return "B";
+              case CHAR:
+                return "C";
+              case DOUBLE:
+                return "D";
+              case FLOAT:
+                return "F";
+              case INT:
+                return "I";
+              case LONG:
+                return "J";
+              case SHORT:
+                return "S";
+              default:
+                throw new BugInCF("unknown primitive type " + type);
+            }
+          }
+
+          @Override
+          public String visit(ArrayType type, Void v) {
+            String typeName = type.getElementType().accept(this, null);
+            StringBuilder sb = new StringBuilder();
+            int n = type.getArrayLevel();
+            for (int i = 0; i < n; i++) {
+              sb.append("[");
+            }
+            sb.append(typeName);
+            return sb.toString();
+          }
+
+          @Override
+          public String visit(VoidType type, Void v) {
+            return "V";
+          }
+
+          @Override
+          public String visit(WildcardType type, Void v) {
+            return type.getSuperType().get().accept(this, null);
+          }
+        },
+        null);
+  }
+
+  /**
+   * Finds the fully qualified name of the class with the given name.
+   *
+   * @param className possibly unqualified name of class
+   * @return fully qualified name of class that {@code className} identifies in the current context,
+   *     or null if resolution fails
+   */
+  private @BinaryName String resolve(@BinaryName String className) {
+
+    if (pkgName != null) {
+      String qualifiedName = Signatures.addPackage(pkgName, className);
+      if (loadClass(qualifiedName) != null) {
+        return qualifiedName;
+      }
+    }
+
+    {
+      // Every Java program implicitly does "import java.lang.*",
+      // so see whether this class is in that package.
+      String qualifiedName = Signatures.addPackage("java.lang", className);
+      if (loadClass(qualifiedName) != null) {
+        return qualifiedName;
+      }
+    }
+
+    for (String declName : imports) {
+      String qualifiedName = mergeImport(declName, className);
+      if (loadClass(qualifiedName) != null) {
+        return qualifiedName;
+      }
+    }
+
+    if (loadClass(className) != null) {
+      return className;
+    }
+
+    return null;
+  }
+
+  /**
+   * Combines an import with a partial binary name, yielding a binary name.
+   *
+   * @param importName package name or (for an inner class) the outer class name
+   * @param className the class name
+   * @return fully qualified class name if resolution succeeds, null otherwise
+   */
+  @SuppressWarnings("signature") // string manipulation of signature strings
+  private static @BinaryName String mergeImport(String importName, @BinaryName String className) {
+    if (importName.isEmpty() || importName.equals(className)) {
+      return className;
+    }
+    String[] importSplit = importName.split("\\.");
+    String[] classSplit = className.split("\\.");
+    String importEnd = importSplit[importSplit.length - 1];
+    if ("*".equals(importEnd)) {
+      return importName.substring(0, importName.length() - 1) + className;
+    } else {
+      // find overlap such as in
+      //   import a.b.C.D;
+      //   C.D myvar;
+      int i = importSplit.length;
+      int n = i - classSplit.length;
+      while (--i >= n) {
+        if (!classSplit[i - n].equals(importSplit[i])) {
+          return null;
+        }
+      }
+      return importName;
+    }
+  }
+
+  /**
+   * Finds {@link Class} corresponding to a name.
+   *
+   * @param className a class name
+   * @return {@link Class} object corresponding to className, or null if none found
+   */
+  private static Class<?> loadClass(@ClassGetName String className) {
+    assert className != null;
+    try {
+      return Class.forName(className, false, null);
+    } catch (ClassNotFoundException e) {
+      return null;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java
new file mode 100644
index 0000000..1015fc3
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java
@@ -0,0 +1,355 @@
+package org.checkerframework.framework.type;
+
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * AnnotatedTypeCopier is a visitor that deep copies an AnnotatedTypeMirror exactly, including any
+ * lazily initialized fields. That is, if a field has already been initialized, it will be
+ * initialized in the copied type.
+ *
+ * <p>When making copies, a map of encountered {@literal references => copied} types is maintained.
+ * This ensures that, if a reference appears in multiple locations in the original type, a
+ * corresponding copy of the original type appears in the same locations in the output copy. This
+ * ensures that the recursive loops in the input type are preserved in its output copy (see
+ * makeOrReturnCopy)
+ *
+ * <p>In general, AnnotatedTypeMirrors should be copied via AnnotatedTypeMirror#deepCopy and
+ * AnnotatedTypeMirror#shallowCopy. AnnotatedTypeMirror#deepCopy makes use of AnnotatedTypeCopier
+ * under the covers. However, this visitor and its subclasses can be invoked as follows:
+ *
+ * <pre>{@code new AnnotatedTypeCopier().visit(myTypeVar);}</pre>
+ *
+ * Note: There are methods that may require a copy of a type mirror with slight changes. It is
+ * intended that this class can be overridden for these cases.
+ *
+ * @see org.checkerframework.framework.type.TypeVariableSubstitutor
+ * @see org.checkerframework.framework.type.AnnotatedTypeCopierWithReplacement
+ */
+public class AnnotatedTypeCopier
+    implements AnnotatedTypeVisitor<
+        AnnotatedTypeMirror, IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror>> {
+
+  /**
+   * This is a hack to handle the curious behavior of substitution on an AnnotatedExecutableType.
+   *
+   * @see org.checkerframework.framework.type.TypeVariableSubstitutor It is poor form to include
+   *     such a flag on the base class for exclusive use in a subclass but it is the least bad
+   *     option in this case.
+   */
+  protected boolean visitingExecutableTypeParam = false;
+
+  /**
+   * See {@link #AnnotatedTypeCopier(boolean)}.
+   *
+   * @see #AnnotatedTypeCopier(boolean)
+   */
+  protected final boolean copyAnnotations;
+
+  /**
+   * Creates an AnnotatedTypeCopier that may or may not copyAnnotations By default
+   * AnnotatedTypeCopier provides two major properties in its copies:
+   *
+   * <ol>
+   *   <li>Structure preservation -- the exact structure of the original AnnotatedTypeMirror is
+   *       preserved in the copy including all component types.
+   *   <li>Annotation preservation -- All of the annotations from the original AnnotatedTypeMirror
+   *       and its components have been copied to the new type.
+   * </ol>
+   *
+   * If copyAnnotations is set to false, the second property, annotation preservation, is removed.
+   * This is useful for cases in which the user may want to copy the structure of a type exactly but
+   * NOT its annotations.
+   */
+  public AnnotatedTypeCopier(final boolean copyAnnotations) {
+    this.copyAnnotations = copyAnnotations;
+  }
+
+  /**
+   * Creates an AnnotatedTypeCopier that copies both the structure and annotations of the source
+   * AnnotatedTypeMirror.
+   *
+   * @see #AnnotatedTypeCopier(boolean)
+   */
+  public AnnotatedTypeCopier() {
+    this(true);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visit(AnnotatedTypeMirror type) {
+    return type.accept(this, new IdentityHashMap<>());
+  }
+
+  @Override
+  public AnnotatedTypeMirror visit(
+      AnnotatedTypeMirror type,
+      IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+    return type.accept(this, originalToCopy);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitDeclared(
+      AnnotatedDeclaredType original,
+      IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+    if (originalToCopy.containsKey(original)) {
+      return originalToCopy.get(original);
+    }
+
+    final AnnotatedDeclaredType copy = makeOrReturnCopy(original, originalToCopy);
+
+    if (original.wasRaw()) {
+      copy.setWasRaw();
+    }
+
+    if (original.enclosingType != null) {
+      copy.enclosingType = (AnnotatedDeclaredType) visit(original.enclosingType, originalToCopy);
+    }
+
+    if (original.typeArgs != null) {
+      final List<AnnotatedTypeMirror> copyTypeArgs =
+          CollectionsPlume.mapList(
+              (AnnotatedTypeMirror typeArg) -> visit(typeArg, originalToCopy),
+              original.getTypeArguments());
+      copy.setTypeArguments(copyTypeArgs);
+    }
+
+    return copy;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitIntersection(
+      AnnotatedIntersectionType original,
+      IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+    if (originalToCopy.containsKey(original)) {
+      return originalToCopy.get(original);
+    }
+
+    final AnnotatedIntersectionType copy = makeOrReturnCopy(original, originalToCopy);
+
+    if (original.bounds != null) {
+      List<AnnotatedTypeMirror> copySupertypes =
+          CollectionsPlume.mapList(
+              (AnnotatedTypeMirror bound) -> visit(bound, originalToCopy), original.bounds);
+      copy.bounds = Collections.unmodifiableList(copySupertypes);
+    }
+
+    return copy;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitUnion(
+      AnnotatedUnionType original,
+      IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+    if (originalToCopy.containsKey(original)) {
+      return originalToCopy.get(original);
+    }
+
+    final AnnotatedUnionType copy = makeOrReturnCopy(original, originalToCopy);
+
+    if (original.alternatives != null) {
+      final List<AnnotatedDeclaredType> copyAlternatives =
+          CollectionsPlume.mapList(
+              (AnnotatedDeclaredType supertype) ->
+                  (AnnotatedDeclaredType) visit(supertype, originalToCopy),
+              original.alternatives);
+      copy.alternatives = Collections.unmodifiableList(copyAlternatives);
+    }
+
+    return copy;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitExecutable(
+      AnnotatedExecutableType original,
+      IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+    if (originalToCopy.containsKey(original)) {
+      return originalToCopy.get(original);
+    }
+
+    final AnnotatedExecutableType copy = makeOrReturnCopy(original, originalToCopy);
+
+    copy.setElement(original.getElement());
+
+    if (original.receiverType != null) {
+      copy.receiverType = (AnnotatedDeclaredType) visit(original.receiverType, originalToCopy);
+    }
+
+    for (final AnnotatedTypeMirror param : original.paramTypes) {
+      copy.paramTypes.add(visit(param, originalToCopy));
+    }
+
+    for (final AnnotatedTypeMirror thrown : original.throwsTypes) {
+      copy.throwsTypes.add(visit(thrown, originalToCopy));
+    }
+
+    copy.returnType = visit(original.returnType, originalToCopy);
+
+    for (final AnnotatedTypeVariable typeVariable : original.typeVarTypes) {
+      // This field is needed to identify exactly when the declaration of an executable's
+      // type parameter is visited.  When subtypes of this class visit the type parameter's
+      // component types, they will likely set visitingExecutableTypeParam to false.
+      // Therefore, we set this variable on each iteration of the loop.
+      // See TypeVariableSubstitutor.Visitor.visitTypeVariable for an example of this.
+      visitingExecutableTypeParam = true;
+      copy.typeVarTypes.add((AnnotatedTypeVariable) visit(typeVariable, originalToCopy));
+    }
+    visitingExecutableTypeParam = false;
+
+    return copy;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitArray(
+      AnnotatedArrayType original,
+      IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+    if (originalToCopy.containsKey(original)) {
+      return originalToCopy.get(original);
+    }
+
+    final AnnotatedArrayType copy = makeOrReturnCopy(original, originalToCopy);
+
+    copy.setComponentType(visit(original.getComponentType(), originalToCopy));
+
+    return copy;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitTypeVariable(
+      AnnotatedTypeVariable original,
+      IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+    if (originalToCopy.containsKey(original)) {
+      return originalToCopy.get(original);
+    }
+
+    final AnnotatedTypeVariable copy = makeOrReturnCopy(original, originalToCopy);
+
+    if (original.getUpperBoundField() != null) {
+      // TODO: figure out why asUse is needed here and remove it.
+      copy.setUpperBound(visit(original.getUpperBoundField(), originalToCopy).asUse());
+    }
+
+    if (original.getLowerBoundField() != null) {
+      // TODO: figure out why asUse is needed here and remove it.
+      copy.setLowerBound(visit(original.getLowerBoundField(), originalToCopy).asUse());
+    }
+
+    return copy;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitPrimitive(
+      AnnotatedPrimitiveType original,
+      IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+    return makeOrReturnCopy(original, originalToCopy);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitNoType(
+      AnnotatedNoType original,
+      IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+    return makeCopy(original);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitNull(
+      AnnotatedNullType original,
+      IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+    return makeOrReturnCopy(original, originalToCopy);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitWildcard(
+      AnnotatedWildcardType original,
+      IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+    if (originalToCopy.containsKey(original)) {
+      return originalToCopy.get(original);
+    }
+
+    final AnnotatedWildcardType copy = makeOrReturnCopy(original, originalToCopy);
+
+    if (original.isUninferredTypeArgument()) {
+      copy.setUninferredTypeArgument();
+    }
+
+    if (original.getExtendsBoundField() != null) {
+      copy.setExtendsBound(visit(original.getExtendsBoundField(), originalToCopy).asUse());
+    }
+
+    if (original.getSuperBoundField() != null) {
+      copy.setSuperBound(visit(original.getSuperBoundField(), originalToCopy).asUse());
+    }
+
+    copy.setTypeVariable(original.getTypeVariable());
+
+    return copy;
+  }
+
+  /**
+   * For any given object in the type being copied, we only want to generate one copy of that
+   * object. When that object is encountered again, using the previously generated copy will
+   * preserve the structure of the original AnnotatedTypeMirror.
+   *
+   * <p>makeOrReturnCopy first checks to see if an object has been encountered before. If so, it
+   * returns the previously generated duplicate of that object if not, it creates a duplicate of the
+   * object and stores it in the history, originalToCopy
+   *
+   * @param original a reference to a type to copy
+   * @param originalToCopy a mapping of previously encountered references to the copies made for
+   *     those references
+   * @param <T> the type of original copy, this is a shortcut to avoid having to insert casts all
+   *     over the visitor
+   * @return a copy of original
+   */
+  @SuppressWarnings("unchecked")
+  protected <T extends AnnotatedTypeMirror> T makeOrReturnCopy(
+      T original, IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+    if (originalToCopy.containsKey(original)) {
+      return (T) originalToCopy.get(original);
+    }
+
+    final T copy = makeCopy(original);
+    originalToCopy.put(original, copy);
+
+    return copy;
+  }
+
+  @SuppressWarnings("unchecked")
+  protected <T extends AnnotatedTypeMirror> T makeCopy(T original) {
+
+    final T copy =
+        (T)
+            AnnotatedTypeMirror.createType(
+                original.getUnderlyingType(), original.atypeFactory, original.isDeclaration());
+    maybeCopyPrimaryAnnotations(original, copy);
+
+    return copy;
+  }
+
+  /**
+   * This method is called in any location in which a primary annotation would be copied from source
+   * to dest. Note, this method obeys the copyAnnotations field. Subclasses of AnnotatedTypeCopier
+   * can use this method to customize annotations before copying.
+   *
+   * @param source the type whose primary annotations are being copied
+   * @param dest a copy of source that should receive its primary annotations
+   */
+  protected void maybeCopyPrimaryAnnotations(
+      final AnnotatedTypeMirror source, final AnnotatedTypeMirror dest) {
+    if (copyAnnotations) {
+      dest.addAnnotations(source.getAnnotationsField());
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java
new file mode 100644
index 0000000..883c1e9
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java
@@ -0,0 +1,81 @@
+package org.checkerframework.framework.type;
+
+import java.util.IdentityHashMap;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+
+/** Duplicates annotated types and replaces components according to a replacement map. */
+public class AnnotatedTypeCopierWithReplacement {
+
+  /**
+   * Return a copy of type after making the specified replacements.
+   *
+   * @param type the type that will be copied with replaced components
+   * @param replacementMap a mapping of {@literal referenceToReplace => referenceOfReplacement}
+   * @return a duplicate of type in which every reference that was a key in replacementMap has been
+   *     replaced by its corresponding value
+   */
+  public static AnnotatedTypeMirror replace(
+      AnnotatedTypeMirror type,
+      IdentityHashMap<? extends AnnotatedTypeMirror, ? extends AnnotatedTypeMirror>
+          replacementMap) {
+    return new Visitor(replacementMap).visit(type);
+  }
+
+  /**
+   * AnnotatedTypeCopier maintains a mapping of {@literal typeVisited => copyOfTypeVisited} When a
+   * reference, typeVisited, is encountered again, it will use the recorded reference,
+   * copyOfTypeVisited, instead of generating a new copy of typeVisited. Visitor pre-populates this
+   * mapping so that references are replaced not by their copies but by those in the replacementMap
+   * provided in the constructor.
+   *
+   * <p>All types NOT in the replacement map are duplicated as per AnnotatedTypeCopier.visit
+   */
+  protected static class Visitor extends AnnotatedTypeCopier {
+
+    private final IdentityHashMap<? extends AnnotatedTypeMirror, ? extends AnnotatedTypeMirror>
+        originalMappings;
+
+    public Visitor(
+        final IdentityHashMap<? extends AnnotatedTypeMirror, ? extends AnnotatedTypeMirror>
+            mappings) {
+      originalMappings = new IdentityHashMap<>(mappings);
+    }
+
+    @Override
+    public AnnotatedTypeMirror visit(AnnotatedTypeMirror type) {
+      return type.accept(this, new IdentityHashMap<>(originalMappings));
+    }
+
+    @Override
+    public AnnotatedTypeMirror visitTypeVariable(
+        AnnotatedTypeVariable original,
+        IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+      // AnnotatedTypeCopier will visit the type parameters of a method and copy them.
+      // Without this flag, any mappings in originalToCopy would replace the type parameters.
+      // However, we do not replace the type parameters in an AnnotatedExecutableType.  Also,
+      // AnnotatedExecutableType.typeVarTypes is of type List<AnnotatedTypeVariable> so if the
+      // mapping contained a type parameter -> (Non-type variable AnnotatedTypeMirror) then a
+      // runtime exception would occur.
+      if (visitingExecutableTypeParam) {
+        visitingExecutableTypeParam = false;
+        final AnnotatedTypeVariable copy =
+            (AnnotatedTypeVariable)
+                AnnotatedTypeMirror.createType(
+                    original.getUnderlyingType(), original.atypeFactory, original.isDeclaration());
+        maybeCopyPrimaryAnnotations(original, copy);
+        originalToCopy.put(original, copy);
+
+        if (original.getUpperBoundField() != null) {
+          copy.setUpperBound(visit(original.getUpperBoundField(), originalToCopy));
+        }
+
+        if (original.getLowerBoundField() != null) {
+          copy.setLowerBound(visit(original.getLowerBoundField(), originalToCopy));
+        }
+        return copy;
+      }
+
+      return super.visitTypeVariable(original, originalToCopy);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java
new file mode 100644
index 0000000..f7061f3
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java
@@ -0,0 +1,4924 @@
+package org.checkerframework.framework.type;
+
+// The imports from com.sun are all @jdk.Exported and therefore somewhat safe to use.
+// Try to avoid using non-@jdk.Exported classes.
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.tree.JCTree.JCNewClass;
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Target;
+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.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.checker.interning.qual.InternedDistinct;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.reflection.DefaultReflectionResolver;
+import org.checkerframework.common.reflection.MethodValAnnotatedTypeFactory;
+import org.checkerframework.common.reflection.MethodValChecker;
+import org.checkerframework.common.reflection.ReflectionResolver;
+import org.checkerframework.common.reflection.qual.MethodVal;
+import org.checkerframework.common.wholeprograminference.WholeProgramInference;
+import org.checkerframework.common.wholeprograminference.WholeProgramInferenceImplementation;
+import org.checkerframework.common.wholeprograminference.WholeProgramInferenceJavaParserStorage;
+import org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesStorage;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.qual.EnsuresQualifier;
+import org.checkerframework.framework.qual.EnsuresQualifierIf;
+import org.checkerframework.framework.qual.FieldInvariant;
+import org.checkerframework.framework.qual.FromStubFile;
+import org.checkerframework.framework.qual.HasQualifierParameter;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+import org.checkerframework.framework.qual.NoQualifierParameter;
+import org.checkerframework.framework.qual.RequiresQualifier;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.stub.AnnotationFileElementTypes;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeCombiner;
+import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.AnnotationFormatter;
+import org.checkerframework.framework.util.CheckerMain;
+import org.checkerframework.framework.util.DefaultAnnotationFormatter;
+import org.checkerframework.framework.util.FieldInvariants;
+import org.checkerframework.framework.util.TreePathCacher;
+import org.checkerframework.framework.util.typeinference.DefaultTypeArgumentInference;
+import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
+import org.checkerframework.framework.util.typeinference.TypeArgumentInference;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationProvider;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.CollectionUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypeKindUtils;
+import org.checkerframework.javacutil.TypeSystemError;
+import org.checkerframework.javacutil.TypesUtils;
+import org.checkerframework.javacutil.UserError;
+import org.checkerframework.javacutil.trees.DetachedVarSymbol;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.StringsPlume;
+import scenelib.annotations.el.AMethod;
+import scenelib.annotations.el.ATypeElement;
+
+/**
+ * The methods of this class take an element or AST node, and return the annotated type as an {@link
+ * AnnotatedTypeMirror}. The methods are:
+ *
+ * <ul>
+ *   <li>{@link #getAnnotatedType(ClassTree)}
+ *   <li>{@link #getAnnotatedType(MethodTree)}
+ *   <li>{@link #getAnnotatedType(Tree)}
+ *   <li>{@link #getAnnotatedTypeFromTypeTree(Tree)}
+ *   <li>{@link #getAnnotatedType(TypeElement)}
+ *   <li>{@link #getAnnotatedType(ExecutableElement)}
+ *   <li>{@link #getAnnotatedType(Element)}
+ * </ul>
+ *
+ * This implementation only adds qualifiers explicitly specified by the programmer. Subclasses
+ * override {@link #addComputedTypeAnnotations} to add defaults, flow-sensitive refinement, and
+ * type-system-specific rules.
+ *
+ * <p>Unless otherwise indicated, each public method in this class returns a "fully annotated" type,
+ * which is one that has an annotation in all positions.
+ *
+ * <p>Type system checker writers may need to subclass this class, to add default qualifiers
+ * according to the type system semantics. Subclasses should especially override {@link
+ * #addComputedTypeAnnotations(Element, AnnotatedTypeMirror)} and {@link
+ * #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)} to handle default annotations. (Also,
+ * {@link #addDefaultAnnotations(AnnotatedTypeMirror)} adds annotations, but that method is a
+ * workaround for <a href="https://github.com/typetools/checker-framework/issues/979">Issue
+ * 979</a>.)
+ *
+ * @checker_framework.manual #creating-a-checker How to write a checker plug-in
+ */
+public class AnnotatedTypeFactory implements AnnotationProvider {
+
+  /** Whether to print verbose debugging messages about stub files. */
+  private final boolean debugStubParser;
+
+  /** The {@link Trees} instance to use for tree node path finding. */
+  protected final Trees trees;
+
+  /** Optional! The AST of the source file being operated on. */
+  // TODO: when should root be null? What are the use cases?
+  // None of the existing test checkers has a null root.
+  // Should not be modified between calls to "visit".
+  protected @Nullable CompilationUnitTree root;
+
+  /** The processing environment to use for accessing compiler internals. */
+  protected final ProcessingEnvironment processingEnv;
+
+  /** Utility class for working with {@link Element}s. */
+  protected final Elements elements;
+
+  /** Utility class for working with {@link TypeMirror}s. */
+  public final Types types;
+
+  /** The state of the visitor. */
+  protected final VisitorState visitorState;
+
+  /** The AnnotatedFor.value argument/element. */
+  private final ExecutableElement annotatedForValueElement;
+  /** The EnsuresQualifier.expression field/element. */
+  final ExecutableElement ensuresQualifierExpressionElement;
+  /** The EnsuresQualifier.List.value field/element. */
+  final ExecutableElement ensuresQualifierListValueElement;
+  /** The EnsuresQualifierIf.expression field/element. */
+  final ExecutableElement ensuresQualifierIfExpressionElement;
+  /** The EnsuresQualifierIf.result argument/element. */
+  final ExecutableElement ensuresQualifierIfResultElement;
+  /** The EnsuresQualifierIf.List.value field/element. */
+  final ExecutableElement ensuresQualifierIfListValueElement;
+  /** The FieldInvariant.field argument/element. */
+  private final ExecutableElement fieldInvariantFieldElement;
+  /** The FieldInvariant.qualifier argument/element. */
+  private final ExecutableElement fieldInvariantQualifierElement;
+  /** The HasQualifierParameter.value field/element. */
+  private final ExecutableElement hasQualifierParameterValueElement;
+  /** The MethodVal.className argument/element. */
+  public final ExecutableElement methodValClassNameElement;
+  /** The MethodVal.methodName argument/element. */
+  public final ExecutableElement methodValMethodNameElement;
+  /** The MethodVal.params argument/element. */
+  public final ExecutableElement methodValParamsElement;
+  /** The NoQualifierParameter.value field/element. */
+  private final ExecutableElement noQualifierParameterValueElement;
+  /** The RequiresQualifier.expression field/element. */
+  final ExecutableElement requiresQualifierExpressionElement;
+  /** The RequiresQualifier.List.value field/element. */
+  final ExecutableElement requiresQualifierListValueElement;
+
+  /** The RequiresQualifier type. */
+  TypeMirror requiresQualifierTM;
+  /** The RequiresQualifier.List type. */
+  TypeMirror requiresQualifierListTM;
+  /** The EnsuresQualifier type. */
+  TypeMirror ensuresQualifierTM;
+  /** The EnsuresQualifier.List type. */
+  TypeMirror ensuresQualifierListTM;
+  /** The EnsuresQualifierIf type. */
+  TypeMirror ensuresQualifierIfTM;
+  /** The EnsuresQualifierIf.List type. */
+  TypeMirror ensuresQualifierIfListTM;
+
+  /**
+   * ===== postInit initialized fields ==== Note: qualHierarchy and typeHierarchy are both
+   * initialized in the postInit.
+   *
+   * @see #postInit() This means, they cannot be final and cannot be referred to in any subclass
+   *     constructor or method until after postInit is called
+   */
+
+  /** Represent the annotation relations. */
+  protected QualifierHierarchy qualHierarchy;
+
+  /** Represent the type relations. */
+  protected TypeHierarchy typeHierarchy;
+
+  /** Performs whole-program inference. If null, whole-program inference is disabled. */
+  private final @Nullable WholeProgramInference wholeProgramInference;
+
+  /**
+   * This formatter is used for converting AnnotatedTypeMirrors to Strings. This formatter will be
+   * used by all AnnotatedTypeMirrors created by this factory in their toString methods.
+   */
+  protected final AnnotatedTypeFormatter typeFormatter;
+
+  /**
+   * Annotation formatter is used to format AnnotationMirrors. It is primarily used by SourceChecker
+   * when generating error messages.
+   */
+  private final AnnotationFormatter annotationFormatter;
+
+  /** Holds the qualifier upper bounds for type uses. */
+  protected QualifierUpperBounds qualifierUpperBounds;
+
+  /**
+   * Provides utility method to substitute arguments for their type variables. Field should be
+   * final, but can only be set in postInit, because subtypes might need other state to be
+   * initialized first.
+   */
+  protected TypeVariableSubstitutor typeVarSubstitutor;
+
+  /** Provides utility method to infer type arguments. */
+  protected TypeArgumentInference typeArgumentInference;
+
+  /**
+   * Caches the supported type qualifier classes. Call {@link #getSupportedTypeQualifiers()} instead
+   * of using this field directly, as it may not have been initialized.
+   */
+  private final Set<Class<? extends Annotation>> supportedQuals;
+
+  /**
+   * Caches the fully-qualified names of the classes in {@link #supportedQuals}. Call {@link
+   * #getSupportedTypeQualifierNames()} instead of using this field directly, as it may not have
+   * been initialized.
+   */
+  private final Set<@CanonicalName String> supportedQualNames;
+
+  /** Parses stub files and stores annotations on public elements from stub files. */
+  public final AnnotationFileElementTypes stubTypes;
+
+  /** Parses ajava files and stores annotations on public elements from ajava files. */
+  public final AnnotationFileElementTypes ajavaTypes;
+
+  /**
+   * If type checking a Java file, stores annotations read from an ajava file for that class if one
+   * exists. Unlike {@link #ajavaTypes}, which only stores annotations on public elements, this
+   * stores annotations on all element locations such as in anonymous class bodies.
+   */
+  public @Nullable AnnotationFileElementTypes currentFileAjavaTypes;
+
+  /**
+   * A cache used to store elements whose declaration annotations have already been stored by
+   * calling the method {@link #getDeclAnnotations(Element)}.
+   */
+  private final Map<Element, Set<AnnotationMirror>> cacheDeclAnnos;
+
+  /**
+   * A set containing declaration annotations that should be inherited. A declaration annotation
+   * will be inherited if it is in this set, or if it has the meta-annotation @InheritedAnnotation.
+   */
+  private final Set<AnnotationMirror> inheritedAnnotations = AnnotationUtils.createAnnotationSet();
+
+  /** The checker to use for option handling and resource management. */
+  protected final BaseTypeChecker checker;
+
+  /** Map keys are canonical names of aliased annotations. */
+  private final Map<@FullyQualifiedName String, Alias> aliases = new HashMap<>();
+
+  /**
+   * Information about one annotation alias.
+   *
+   * <p>The information is either an AnotationMirror that can be used directly, or information for a
+   * builder (name and fields not to copy); see checkRep.
+   */
+  private static class Alias {
+    /** The canonical annotation (or null if copyElements == true). */
+    AnnotationMirror canonical;
+    /** Whether elements should be copied over when translating to the canonical annotation. */
+    boolean copyElements;
+    /** The canonical annotation name (or null if copyElements == false). */
+    @CanonicalName String canonicalName;
+    /** Which elements should not be copied over (or null if copyElements == false). */
+    String[] ignorableElements;
+
+    /**
+     * Create an Alias with the given components.
+     *
+     * @param aliasName the alias name; only used for debugging
+     * @param canonical the canonical annotation
+     * @param copyElements whether elements should be copied over when translating to the canonical
+     *     annotation
+     * @param canonicalName the canonical annotation name (or null if copyElements == false)
+     * @param ignorableElements elements that should not be copied over
+     */
+    Alias(
+        String aliasName,
+        AnnotationMirror canonical,
+        boolean copyElements,
+        @CanonicalName String canonicalName,
+        String[] ignorableElements) {
+      this.canonical = canonical;
+      this.copyElements = copyElements;
+      this.canonicalName = canonicalName;
+      this.ignorableElements = ignorableElements;
+      checkRep(aliasName);
+    }
+
+    /**
+     * Throw an exception if this object is malformed.
+     *
+     * @param aliasName the alias name; only used for diagnostic messages
+     */
+    void checkRep(String aliasName) {
+      if (copyElements) {
+        if (!(canonical == null && canonicalName != null && ignorableElements != null)) {
+          throw new BugInCF(
+              "Bad Alias for %s: [canonical=%s] copyElements=%s canonicalName=%s"
+                  + " ignorableElements=%s",
+              aliasName, canonical, copyElements, canonicalName, ignorableElements);
+        }
+      } else {
+        if (!(canonical != null && canonicalName == null && ignorableElements == null)) {
+          throw new BugInCF(
+              "Bad Alias for %s: canonical=%s copyElements=%s [canonicalName=%s"
+                  + " ignorableElements=%s]",
+              aliasName, canonical, copyElements, canonicalName, ignorableElements);
+        }
+      }
+    }
+  }
+
+  /**
+   * A map from the class of an annotation to the set of classes for annotations with the same
+   * meaning, as well as the annotation mirror that should be used.
+   */
+  private final Map<
+          Class<? extends Annotation>, Pair<AnnotationMirror, Set<Class<? extends Annotation>>>>
+      declAliases = new HashMap<>();
+
+  /** Unique ID counter; for debugging purposes. */
+  private static int uidCounter = 0;
+
+  /** Unique ID of the current object; for debugging purposes. */
+  public final int uid;
+
+  /**
+   * Object that is used to resolve reflective method calls, if reflection resolution is turned on.
+   */
+  protected ReflectionResolver reflectionResolver;
+
+  /** AnnotationClassLoader used to load type annotation classes via reflective lookup. */
+  protected AnnotationClassLoader loader;
+
+  /**
+   * Which whole-program inference output format to use, if doing whole-program inference. This
+   * variable would be final, but it is not set unless WPI is enabled.
+   */
+  protected WholeProgramInference.OutputFormat wpiOutputFormat;
+
+  /**
+   * Should results be cached? This means that ATM.deepCopy() will be called. ATM.deepCopy() used to
+   * (and perhaps still does) side effect the ATM being copied. So setting this to false is not
+   * equivalent to setting shouldReadCache to false.
+   */
+  public boolean shouldCache;
+
+  /** Size of LRU cache if one isn't specified using the atfCacheSize option. */
+  private static final int DEFAULT_CACHE_SIZE = 300;
+
+  /** Mapping from a Tree to its annotated type; defaults have been applied. */
+  private final Map<Tree, AnnotatedTypeMirror> classAndMethodTreeCache;
+
+  /**
+   * Mapping from an expression tree to its annotated type; before defaults are applied, just what
+   * the programmer wrote.
+   */
+  protected final Map<Tree, AnnotatedTypeMirror> fromExpressionTreeCache;
+
+  /**
+   * Mapping from a member tree to its annotated type; before defaults are applied, just what the
+   * programmer wrote.
+   */
+  protected final Map<Tree, AnnotatedTypeMirror> fromMemberTreeCache;
+
+  /**
+   * Mapping from a type tree to its annotated type; before defaults are applied, just what the
+   * programmer wrote.
+   */
+  protected final Map<Tree, AnnotatedTypeMirror> fromTypeTreeCache;
+
+  /**
+   * Mapping from an Element to its annotated type; before defaults are applied, just what the
+   * programmer wrote.
+   */
+  private final Map<Element, AnnotatedTypeMirror> elementCache;
+
+  /** Mapping from an Element to the source Tree of the declaration. */
+  private final Map<Element, Tree> elementToTreeCache;
+
+  /** Mapping from a Tree to its TreePath. Shared between all instances. */
+  private final TreePathCacher treePathCache;
+
+  /** Mapping from CFG-generated trees to their enclosing elements. */
+  protected final Map<Tree, Element> artificialTreeToEnclosingElementMap;
+
+  /**
+   * Whether to ignore uninferred type arguments. This is a temporary flag to work around Issue 979.
+   */
+  public final boolean ignoreUninferredTypeArguments;
+
+  /** The Object.getClass method. */
+  protected final ExecutableElement objectGetClass;
+
+  /** Size of the annotationClassNames cache. */
+  private static final int ANNOTATION_CACHE_SIZE = 500;
+
+  /** Maps classes representing AnnotationMirrors to their canonical names. */
+  private final Map<Class<? extends Annotation>, @CanonicalName String> annotationClassNames;
+
+  /** An annotated type of the declaration of {@link Iterable} without any annotations. */
+  private AnnotatedDeclaredType iterableDeclType;
+
+  /**
+   * Constructs a factory from the given {@link ProcessingEnvironment} instance and syntax tree
+   * root. (These parameters are required so that the factory may conduct the appropriate
+   * annotation-gathering analyses on certain tree types.)
+   *
+   * <p>Root can be {@code null} if the factory does not operate on trees.
+   *
+   * <p>A subclass must call postInit at the end of its constructor. postInit must be the last call
+   * in the constructor or else types from stub files may not be created as expected.
+   *
+   * @param checker the {@link SourceChecker} to which this factory belongs
+   * @throws IllegalArgumentException if either argument is {@code null}
+   */
+  public AnnotatedTypeFactory(BaseTypeChecker checker) {
+    uid = ++uidCounter;
+    this.processingEnv = checker.getProcessingEnvironment();
+    // this.root = root;
+    this.checker = checker;
+    this.trees = Trees.instance(processingEnv);
+    this.elements = processingEnv.getElementUtils();
+    this.types = processingEnv.getTypeUtils();
+    this.visitorState = new VisitorState();
+
+    this.supportedQuals = new HashSet<>();
+    this.supportedQualNames = new HashSet<>();
+    this.stubTypes = new AnnotationFileElementTypes(this);
+    this.ajavaTypes = new AnnotationFileElementTypes(this);
+    this.currentFileAjavaTypes = null;
+
+    this.cacheDeclAnnos = new HashMap<>();
+
+    this.artificialTreeToEnclosingElementMap = new HashMap<>();
+    // get the shared instance from the checker
+    this.treePathCache = checker.getTreePathCacher();
+
+    this.shouldCache = !checker.hasOption("atfDoNotCache");
+    if (shouldCache) {
+      int cacheSize = getCacheSize();
+      this.classAndMethodTreeCache = CollectionUtils.createLRUCache(cacheSize);
+      this.fromExpressionTreeCache = CollectionUtils.createLRUCache(cacheSize);
+      this.fromMemberTreeCache = CollectionUtils.createLRUCache(cacheSize);
+      this.fromTypeTreeCache = CollectionUtils.createLRUCache(cacheSize);
+      this.elementCache = CollectionUtils.createLRUCache(cacheSize);
+      this.elementToTreeCache = CollectionUtils.createLRUCache(cacheSize);
+      this.annotationClassNames =
+          Collections.synchronizedMap(CollectionUtils.createLRUCache(ANNOTATION_CACHE_SIZE));
+    } else {
+      this.classAndMethodTreeCache = null;
+      this.fromExpressionTreeCache = null;
+      this.fromMemberTreeCache = null;
+      this.fromTypeTreeCache = null;
+      this.elementCache = null;
+      this.elementToTreeCache = null;
+      this.annotationClassNames = null;
+    }
+
+    this.typeFormatter = createAnnotatedTypeFormatter();
+    this.annotationFormatter = createAnnotationFormatter();
+
+    if (checker.hasOption("infer")) {
+      checkInvalidOptionsInferSignatures();
+      String inferArg = checker.getOption("infer");
+      // No argument means "jaifs", for (temporary) backwards compatibility.
+      if (inferArg == null) {
+        inferArg = "jaifs";
+      }
+      switch (inferArg) {
+        case "stubs":
+          wpiOutputFormat = WholeProgramInference.OutputFormat.STUB;
+          break;
+        case "jaifs":
+          wpiOutputFormat = WholeProgramInference.OutputFormat.JAIF;
+          break;
+        case "ajava":
+          wpiOutputFormat = WholeProgramInference.OutputFormat.AJAVA;
+          break;
+        default:
+          throw new UserError(
+              "Bad argument -Ainfer="
+                  + inferArg
+                  + " should be one of: -Ainfer=jaifs, -Ainfer=stubs, -Ainfer=ajava");
+      }
+      if (wpiOutputFormat == WholeProgramInference.OutputFormat.AJAVA) {
+        wholeProgramInference =
+            new WholeProgramInferenceImplementation<AnnotatedTypeMirror>(
+                this, new WholeProgramInferenceJavaParserStorage(this));
+      } else {
+        wholeProgramInference =
+            new WholeProgramInferenceImplementation<ATypeElement>(
+                this, new WholeProgramInferenceScenesStorage(this));
+      }
+      if (!checker.hasOption("warns")) {
+        // Without -Awarns, the inference output may be incomplete, because javac halts
+        // after issuing an error.
+        checker.message(Diagnostic.Kind.ERROR, "Do not supply -Ainfer without -Awarns");
+      }
+    } else {
+      wholeProgramInference = null;
+    }
+    ignoreUninferredTypeArguments = !checker.hasOption("conservativeUninferredTypeArguments");
+
+    objectGetClass = TreeUtils.getMethod("java.lang.Object", "getClass", 0, processingEnv);
+
+    this.debugStubParser = checker.hasOption("stubDebug");
+
+    annotatedForValueElement = TreeUtils.getMethod(AnnotatedFor.class, "value", 0, processingEnv);
+    ensuresQualifierExpressionElement =
+        TreeUtils.getMethod(EnsuresQualifier.class, "expression", 0, processingEnv);
+    ensuresQualifierListValueElement =
+        TreeUtils.getMethod(EnsuresQualifier.List.class, "value", 0, processingEnv);
+    ensuresQualifierIfExpressionElement =
+        TreeUtils.getMethod(EnsuresQualifierIf.class, "expression", 0, processingEnv);
+    ensuresQualifierIfResultElement =
+        TreeUtils.getMethod(EnsuresQualifierIf.class, "result", 0, processingEnv);
+    ensuresQualifierIfListValueElement =
+        TreeUtils.getMethod(EnsuresQualifierIf.List.class, "value", 0, processingEnv);
+    fieldInvariantFieldElement =
+        TreeUtils.getMethod(FieldInvariant.class, "field", 0, processingEnv);
+    fieldInvariantQualifierElement =
+        TreeUtils.getMethod(FieldInvariant.class, "qualifier", 0, processingEnv);
+    hasQualifierParameterValueElement =
+        TreeUtils.getMethod(HasQualifierParameter.class, "value", 0, processingEnv);
+    methodValClassNameElement = TreeUtils.getMethod(MethodVal.class, "className", 0, processingEnv);
+    methodValMethodNameElement =
+        TreeUtils.getMethod(MethodVal.class, "methodName", 0, processingEnv);
+    methodValParamsElement = TreeUtils.getMethod(MethodVal.class, "params", 0, processingEnv);
+    noQualifierParameterValueElement =
+        TreeUtils.getMethod(NoQualifierParameter.class, "value", 0, processingEnv);
+    requiresQualifierExpressionElement =
+        TreeUtils.getMethod(RequiresQualifier.class, "expression", 0, processingEnv);
+    requiresQualifierListValueElement =
+        TreeUtils.getMethod(RequiresQualifier.List.class, "value", 0, processingEnv);
+
+    requiresQualifierTM =
+        ElementUtils.getTypeElement(processingEnv, RequiresQualifier.class).asType();
+    requiresQualifierListTM =
+        ElementUtils.getTypeElement(processingEnv, RequiresQualifier.List.class).asType();
+    ensuresQualifierTM =
+        ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.class).asType();
+    ensuresQualifierListTM =
+        ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.List.class).asType();
+    ensuresQualifierIfTM =
+        ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.class).asType();
+    ensuresQualifierIfListTM =
+        ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.List.class).asType();
+  }
+
+  /**
+   * @throws BugInCF If supportedQuals is empty or if any of the support qualifiers has a @Target
+   *     meta-annotation that contain something besides TYPE_USE or TYPE_PARAMETER. (@Target({}) is
+   *     allowed.)
+   */
+  private void checkSupportedQuals() {
+    if (supportedQuals.isEmpty()) {
+      throw new TypeSystemError("Found no supported qualifiers.");
+    }
+    for (Class<? extends Annotation> annotationClass : supportedQuals) {
+      // Check @Target values
+      ElementType[] targetValues = annotationClass.getAnnotation(Target.class).value();
+      List<ElementType> badTargetValues = new ArrayList<>();
+      for (ElementType element : targetValues) {
+        if (!(element == ElementType.TYPE_USE || element == ElementType.TYPE_PARAMETER)) {
+          // if there's an ElementType with an enumerated value of something other
+          // than TYPE_USE or TYPE_PARAMETER then it isn't a valid qualifier
+          badTargetValues.add(element);
+        }
+      }
+      if (!badTargetValues.isEmpty()) {
+        String msg =
+            "The @Target meta-annotation on type qualifier "
+                + annotationClass.toString()
+                + " must not contain "
+                + StringsPlume.conjunction("or", badTargetValues)
+                + ".";
+        throw new TypeSystemError(msg);
+      }
+    }
+  }
+
+  /**
+   * This method is called only when {@code -Ainfer} is passed as an option. It checks if another
+   * option that should not occur simultaneously with the whole-program inference is also passed as
+   * argument, and aborts the process if that is the case. For example, the whole-program inference
+   * process was not designed to work with conservative defaults.
+   *
+   * <p>Subclasses may override this method to add more options.
+   */
+  protected void checkInvalidOptionsInferSignatures() {
+    // See Issue 683
+    // https://github.com/typetools/checker-framework/issues/683
+    if (checker.useConservativeDefault("source") || checker.useConservativeDefault("bytecode")) {
+      throw new UserError(
+          "The option -Ainfer=... cannot be used together with conservative defaults.");
+    }
+  }
+
+  /**
+   * Actions that logically belong in the constructor, but need to run after the subclass
+   * constructor has completed. In particular, parseStubFiles() may try to do type resolution with
+   * this AnnotatedTypeFactory.
+   */
+  protected void postInit() {
+    this.qualHierarchy = createQualifierHierarchy();
+    if (qualHierarchy == null) {
+      throw new TypeSystemError(
+          "AnnotatedTypeFactory with null qualifier hierarchy not supported.");
+    } else if (!qualHierarchy.isValid()) {
+      throw new TypeSystemError(
+          "AnnotatedTypeFactory: invalid qualifier hierarchy: %s %s ",
+          qualHierarchy.getClass(), qualHierarchy);
+    }
+    this.typeHierarchy = createTypeHierarchy();
+    this.typeVarSubstitutor = createTypeVariableSubstitutor();
+    this.typeArgumentInference = createTypeArgumentInference();
+    this.qualifierUpperBounds = createQualifierUpperBounds();
+
+    // TODO: is this the best location for declaring this alias?
+    addAliasedDeclAnnotation(
+        org.jmlspecs.annotation.Pure.class,
+        org.checkerframework.dataflow.qual.Pure.class,
+        AnnotationBuilder.fromClass(elements, org.checkerframework.dataflow.qual.Pure.class));
+
+    // Accommodate the inability to write @InheritedAnnotation on these annotations.
+    addInheritedAnnotation(
+        AnnotationBuilder.fromClass(elements, org.checkerframework.dataflow.qual.Pure.class));
+    addInheritedAnnotation(
+        AnnotationBuilder.fromClass(
+            elements, org.checkerframework.dataflow.qual.SideEffectFree.class));
+    addInheritedAnnotation(
+        AnnotationBuilder.fromClass(
+            elements, org.checkerframework.dataflow.qual.Deterministic.class));
+    addInheritedAnnotation(
+        AnnotationBuilder.fromClass(
+            elements, org.checkerframework.dataflow.qual.TerminatesExecution.class));
+
+    initializeReflectionResolution();
+
+    if (this.getClass() == AnnotatedTypeFactory.class) {
+      this.parseAnnotationFiles();
+    }
+    TypeMirror iterableTypeMirror =
+        ElementUtils.getTypeElement(processingEnv, Iterable.class).asType();
+    this.iterableDeclType =
+        (AnnotatedDeclaredType) AnnotatedTypeMirror.createType(iterableTypeMirror, this, true);
+  }
+
+  /**
+   * Returns the checker associated with this factory.
+   *
+   * @return the checker associated with this factory
+   */
+  public BaseTypeChecker getChecker() {
+    return checker;
+  }
+
+  /**
+   * Creates {@link QualifierUpperBounds} for this type factory.
+   *
+   * @return a new {@link QualifierUpperBounds} for this type factory
+   */
+  protected QualifierUpperBounds createQualifierUpperBounds() {
+    return new QualifierUpperBounds(this);
+  }
+
+  /**
+   * Return {@link QualifierUpperBounds} for this type factory.
+   *
+   * @return {@link QualifierUpperBounds} for this type factory
+   */
+  public QualifierUpperBounds getQualifierUpperBounds() {
+    return qualifierUpperBounds;
+  }
+
+  /**
+   * Returns the WholeProgramInference instance (may be null).
+   *
+   * @return the WholeProgramInference instance, or null
+   */
+  public WholeProgramInference getWholeProgramInference() {
+    return wholeProgramInference;
+  }
+
+  protected void initializeReflectionResolution() {
+    if (checker.shouldResolveReflection()) {
+      boolean debug = "debug".equals(checker.getOption("resolveReflection"));
+
+      MethodValChecker methodValChecker = checker.getSubchecker(MethodValChecker.class);
+      assert methodValChecker != null
+          : "AnnotatedTypeFactory: reflection resolution was requested, but MethodValChecker isn't"
+              + " a subchecker.";
+      MethodValAnnotatedTypeFactory methodValATF =
+          (MethodValAnnotatedTypeFactory) methodValChecker.getAnnotationProvider();
+
+      reflectionResolver = new DefaultReflectionResolver(checker, methodValATF, debug);
+    }
+  }
+
+  /**
+   * Set the CompilationUnitTree that should be used.
+   *
+   * @param root the new compilation unit to use
+   */
+  public void setRoot(@Nullable CompilationUnitTree root) {
+    if (root != null && wholeProgramInference != null) {
+      for (Tree typeDecl : root.getTypeDecls()) {
+        if (typeDecl.getKind() == Kind.CLASS) {
+          ClassTree classTree = (ClassTree) typeDecl;
+          wholeProgramInference.preprocessClassTree(classTree);
+        }
+      }
+    }
+
+    this.root = root;
+    // Do not clear here. Only the primary checker should clear this cache.
+    // treePathCache.clear();
+
+    // setRoot in a GenericAnnotatedTypeFactory will clear this;
+    // if this isn't a GenericATF, then it must clear it itself.
+    if (!(this instanceof GenericAnnotatedTypeFactory)) {
+      artificialTreeToEnclosingElementMap.clear();
+    }
+
+    if (shouldCache) {
+      // Clear the caches with trees because once the compilation unit changes,
+      // the trees may be modified and lose type arguments.
+      elementToTreeCache.clear();
+      fromExpressionTreeCache.clear();
+      fromMemberTreeCache.clear();
+      fromTypeTreeCache.clear();
+      classAndMethodTreeCache.clear();
+
+      // There is no need to clear the following cache, it is limited by cache size and it
+      // contents won't change between compilation units.
+      // elementCache.clear();
+    }
+
+    if (root != null && checker.hasOption("ajava")) {
+      // Search for an ajava file with annotations for the current source file and the current
+      // checker. It will be in a directory specified by the "ajava" option in a subdirectory
+      // corresponding to this file's package. For example, a file in package a.b would be in a
+      // subdirectory a/b. The filename is ClassName-checker.qualified.name.ajava. If such a file
+      // exists, read its detailed annotation data, including annotations on private elements.
+
+      String packagePrefix =
+          root.getPackageName() != null
+              ? TreeUtils.nameExpressionToString(root.getPackageName()) + "."
+              : "";
+
+      // The method getName() returns a path.
+      String className = root.getSourceFile().getName();
+      // Extract the basename.
+      int lastSeparator = className.lastIndexOf(File.separator);
+      if (lastSeparator != -1) {
+        className = className.substring(lastSeparator + 1);
+      }
+      // Drop the ".java" extension.
+      if (className.endsWith(".java")) {
+        className = className.substring(0, className.length() - ".java".length());
+      }
+
+      String qualifiedName = packagePrefix + className;
+
+      for (String ajavaLocation : checker.getOption("ajava").split(File.pathSeparator)) {
+        String ajavaPath =
+            ajavaLocation
+                + File.separator
+                + qualifiedName.replaceAll("\\.", "/")
+                + "-"
+                + checker.getClass().getCanonicalName()
+                + ".ajava";
+        File ajavaFile = new File(ajavaPath);
+        if (ajavaFile.exists()) {
+          currentFileAjavaTypes = new AnnotationFileElementTypes(this);
+          currentFileAjavaTypes.parseAjavaFileWithTree(ajavaPath, root);
+          break;
+        }
+      }
+    } else {
+      currentFileAjavaTypes = null;
+    }
+  }
+
+  @SideEffectFree
+  @Override
+  public String toString() {
+    return getClass().getSimpleName() + "#" + uid;
+  }
+
+  /**
+   * Returns the {@link QualifierHierarchy} to be used by this checker.
+   *
+   * <p>The implementation builds the type qualifier hierarchy for the {@link
+   * #getSupportedTypeQualifiers()} using the meta-annotations found in them. The current
+   * implementation returns an instance of {@code NoElementQualifierHierarchy}.
+   *
+   * <p>Subclasses must override this method if their qualifiers have elements; the method must
+   * return an implementation of {@link QualifierHierarchy}, such as {@link
+   * ElementQualifierHierarchy}.
+   *
+   * @return a QualifierHierarchy for this type system
+   */
+  protected QualifierHierarchy createQualifierHierarchy() {
+    return new NoElementQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
+  }
+
+  /**
+   * Returns the type qualifier hierarchy graph to be used by this processor.
+   *
+   * @see #createQualifierHierarchy()
+   * @return the {@link QualifierHierarchy} for this checker
+   */
+  public final QualifierHierarchy getQualifierHierarchy() {
+    return qualHierarchy;
+  }
+
+  /**
+   * To continue to use a subclass of {@link
+   * org.checkerframework.framework.util.MultiGraphQualifierHierarchy} or {@link
+   * org.checkerframework.framework.util.GraphQualifierHierarchy}, override this method so that it
+   * returns a new instance of the subclass. Then override {@link #createQualifierHierarchy()} so
+   * that it returns the result of a call to {@link
+   * org.checkerframework.framework.util.MultiGraphQualifierHierarchy#createMultiGraphQualifierHierarchy(AnnotatedTypeFactory)}.
+   *
+   * @param factory MultiGraphFactory
+   * @return QualifierHierarchy
+   * @deprecated Use either {@link ElementQualifierHierarchy}, {@link NoElementQualifierHierarchy},
+   *     or {@link MostlyNoElementQualifierHierarchy} instead. This method will be removed in a
+   *     future release.
+   */
+  @Deprecated // 2020-09-10
+  public QualifierHierarchy createQualifierHierarchyWithMultiGraphFactory(
+      org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory factory) {
+    throw new TypeSystemError(
+        "Checker must override AnnotatedTypeFactory#createQualifierHierarchyWithMultiGraphFactory"
+            + " when using AnnotatedTypeFactory#createMultiGraphQualifierHierarchy.");
+  }
+
+  /**
+   * Creates the type hierarchy to be used by this factory.
+   *
+   * <p>Subclasses may override this method to specify new type-checking rules beyond the typical
+   * Java subtyping rules.
+   *
+   * @return the type relations class to check type subtyping
+   */
+  protected TypeHierarchy createTypeHierarchy() {
+    return new DefaultTypeHierarchy(
+        checker,
+        getQualifierHierarchy(),
+        checker.getBooleanOption("ignoreRawTypeArguments", true),
+        checker.hasOption("invariantArrays"));
+  }
+
+  public final TypeHierarchy getTypeHierarchy() {
+    return typeHierarchy;
+  }
+
+  /** TypeVariableSubstitutor provides a method to replace type parameters with their arguments. */
+  protected TypeVariableSubstitutor createTypeVariableSubstitutor() {
+    return new TypeVariableSubstitutor();
+  }
+
+  public TypeVariableSubstitutor getTypeVarSubstitutor() {
+    return typeVarSubstitutor;
+  }
+
+  /**
+   * TypeArgumentInference infers the method type arguments when they are not explicitly written.
+   */
+  protected TypeArgumentInference createTypeArgumentInference() {
+    return new DefaultTypeArgumentInference(this);
+  }
+
+  public TypeArgumentInference getTypeArgumentInference() {
+    return typeArgumentInference;
+  }
+
+  /**
+   * Factory method to easily change what {@link AnnotationClassLoader} is created to load type
+   * annotation classes. Subclasses can override this method and return a custom
+   * AnnotationClassLoader subclass to customize loading logic.
+   */
+  protected AnnotationClassLoader createAnnotationClassLoader() {
+    return new AnnotationClassLoader(checker);
+  }
+
+  /**
+   * Returns a mutable set of annotation classes that are supported by a checker.
+   *
+   * <p>Subclasses may override this method to return a mutable set of their supported type
+   * qualifiers through one of the 5 approaches shown below.
+   *
+   * <p>Subclasses should not call this method; they should call {@link #getSupportedTypeQualifiers}
+   * instead.
+   *
+   * <p>By default, a checker supports all annotations located in a subdirectory called {@literal
+   * qual} that's located in the same directory as the checker. Note that only annotations defined
+   * with the {@code @Target({ElementType.TYPE_USE})} meta-annotation (and optionally with the
+   * additional value of {@code ElementType.TYPE_PARAMETER}, but no other {@code ElementType}
+   * values) are automatically considered as supported annotations.
+   *
+   * <p>To support a different set of annotations than those in the {@literal qual} subdirectory, or
+   * that have other {@code ElementType} values, see examples below.
+   *
+   * <p>In total, there are 5 ways to indicate annotations that are supported by a checker:
+   *
+   * <ol>
+   *   <li>Only support annotations located in a checker's {@literal qual} directory:
+   *       <p>This is the default behavior. Simply place those annotations within the {@literal
+   *       qual} directory.
+   *   <li>Support annotations located in a checker's {@literal qual} directory and a list of other
+   *       annotations:
+   *       <p>Place those annotations within the {@literal qual} directory, and override {@link
+   *       #createSupportedTypeQualifiers()} by calling {@link #getBundledTypeQualifiers(Class...)}
+   *       with a varargs parameter list of the other annotations. Code example:
+   *       <pre>
+   * {@code @Override protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+   *      return getBundledTypeQualifiers(Regex.class, PartialRegex.class, RegexBottom.class, UnknownRegex.class);
+   *  } }
+   * </pre>
+   *   <li>Supporting only annotations that are explicitly listed: Override {@link
+   *       #createSupportedTypeQualifiers()} and return a mutable set of the supported annotations.
+   *       Code example:
+   *       <pre>
+   * {@code @Override protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+   *      return new HashSet<Class<? extends Annotation>>(
+   *              Arrays.asList(A.class, B.class));
+   *  } }
+   * </pre>
+   *       The set of qualifiers returned by {@link #createSupportedTypeQualifiers()} must be a
+   *       fresh, mutable set. The methods {@link #getBundledTypeQualifiers(Class...)} must return a
+   *       fresh, mutable set
+   * </ol>
+   *
+   * @return the type qualifiers supported this processor, or an empty set if none
+   */
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return getBundledTypeQualifiers();
+  }
+
+  /**
+   * Loads all annotations contained in the qual directory of a checker via reflection; if a
+   * polymorphic type qualifier exists, and an explicit array of annotations to the set of
+   * annotation classes.
+   *
+   * <p>This method can be called in the overridden versions of {@link
+   * #createSupportedTypeQualifiers()} in each checker.
+   *
+   * @param explicitlyListedAnnotations a varargs array of explicitly listed annotation classes to
+   *     be added to the returned set. For example, it is used frequently to add Bottom qualifiers.
+   * @return a mutable set of the loaded and listed annotation classes
+   */
+  @SafeVarargs
+  protected final Set<Class<? extends Annotation>> getBundledTypeQualifiers(
+      Class<? extends Annotation>... explicitlyListedAnnotations) {
+    return loadTypeAnnotationsFromQualDir(explicitlyListedAnnotations);
+  }
+
+  /**
+   * Instantiates the AnnotationClassLoader and loads all annotations contained in the qual
+   * directory of a checker via reflection, and has the option to include an explicitly stated list
+   * of annotations (eg ones found in a different directory than qual).
+   *
+   * <p>The annotations that are automatically loaded must have the {@link
+   * java.lang.annotation.Target Target} meta-annotation with the value of {@link
+   * ElementType#TYPE_USE} (and optionally {@link ElementType#TYPE_PARAMETER}). If it has other
+   * {@link ElementType} values, it won't be loaded. Other annotation classes must be explicitly
+   * listed even if they are in the same directory as the checker's qual directory.
+   *
+   * @param explicitlyListedAnnotations a set of explicitly listed annotation classes to be added to
+   *     the returned set, for example, it is used frequently to add Bottom qualifiers
+   * @return a set of annotation class instances
+   */
+  @SafeVarargs
+  @SuppressWarnings("varargs")
+  private final Set<Class<? extends Annotation>> loadTypeAnnotationsFromQualDir(
+      Class<? extends Annotation>... explicitlyListedAnnotations) {
+    loader = createAnnotationClassLoader();
+
+    Set<Class<? extends Annotation>> annotations = loader.getBundledAnnotationClasses();
+
+    // add in all explicitly Listed qualifiers
+    if (explicitlyListedAnnotations != null) {
+      annotations.addAll(Arrays.asList(explicitlyListedAnnotations));
+    }
+
+    return annotations;
+  }
+
+  /**
+   * Creates the AnnotatedTypeFormatter used by this type factory and all AnnotatedTypeMirrors it
+   * creates. The AnnotatedTypeFormatter is used in AnnotatedTypeMirror.toString and will affect the
+   * error messages printed for checkers that use this type factory.
+   *
+   * @return the AnnotatedTypeFormatter to pass to all instantiated AnnotatedTypeMirrors
+   */
+  protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() {
+    boolean printVerboseGenerics = checker.hasOption("printVerboseGenerics");
+    return new DefaultAnnotatedTypeFormatter(
+        printVerboseGenerics,
+        // -AprintVerboseGenerics implies -AprintAllQualifiers
+        printVerboseGenerics || checker.hasOption("printAllQualifiers"));
+  }
+
+  public AnnotatedTypeFormatter getAnnotatedTypeFormatter() {
+    return typeFormatter;
+  }
+
+  protected AnnotationFormatter createAnnotationFormatter() {
+    return new DefaultAnnotationFormatter();
+  }
+
+  public AnnotationFormatter getAnnotationFormatter() {
+    return annotationFormatter;
+  }
+
+  /**
+   * Returns an immutable set of the classes corresponding to the type qualifiers supported by this
+   * checker.
+   *
+   * <p>Subclasses cannot override this method; they should override {@link
+   * #createSupportedTypeQualifiers createSupportedTypeQualifiers} instead.
+   *
+   * @see #createSupportedTypeQualifiers()
+   * @return an immutable set of the supported type qualifiers, or an empty set if no qualifiers are
+   *     supported
+   */
+  public final Set<Class<? extends Annotation>> getSupportedTypeQualifiers() {
+    if (this.supportedQuals.isEmpty()) {
+      supportedQuals.addAll(createSupportedTypeQualifiers());
+      checkSupportedQuals();
+    }
+    return Collections.unmodifiableSet(supportedQuals);
+  }
+
+  /**
+   * Returns an immutable set of the fully qualified names of the type qualifiers supported by this
+   * checker.
+   *
+   * <p>Subclasses cannot override this method; they should override {@link
+   * #createSupportedTypeQualifiers createSupportedTypeQualifiers} instead.
+   *
+   * @see #createSupportedTypeQualifiers()
+   * @return an immutable set of the supported type qualifiers, or an empty set if no qualifiers are
+   *     supported
+   */
+  public final Set<@CanonicalName String> getSupportedTypeQualifierNames() {
+    if (this.supportedQualNames.isEmpty()) {
+      for (Class<?> clazz : getSupportedTypeQualifiers()) {
+        supportedQualNames.add(clazz.getCanonicalName());
+      }
+    }
+    return Collections.unmodifiableSet(supportedQualNames);
+  }
+
+  // **********************************************************************
+  // Factories for annotated types that account for default qualifiers
+  // **********************************************************************
+
+  /**
+   * Returns the size for LRU caches. It is either the value supplied via the {@code -AatfCacheSize}
+   * option or the default cache size.
+   *
+   * @return cache size passed as argument to checker or DEFAULT_CACHE_SIZE
+   */
+  protected int getCacheSize() {
+    String option = checker.getOption("atfCacheSize");
+    if (option == null) {
+      return DEFAULT_CACHE_SIZE;
+    }
+    try {
+      return Integer.valueOf(option);
+    } catch (NumberFormatException ex) {
+      throw new UserError("atfCacheSize was not an integer: " + option);
+    }
+  }
+
+  /**
+   * Returns an AnnotatedTypeMirror representing the annotated type of {@code elt}.
+   *
+   * @param elt the element
+   * @return the annotated type of {@code elt}
+   */
+  public AnnotatedTypeMirror getAnnotatedType(Element elt) {
+    if (elt == null) {
+      throw new BugInCF("AnnotatedTypeFactory.getAnnotatedType: null element");
+    }
+    // Annotations explicitly written in the source code,
+    // or obtained from bytecode.
+    AnnotatedTypeMirror type = fromElement(elt);
+    addComputedTypeAnnotations(elt, type);
+    return type;
+  }
+
+  /**
+   * Returns an AnnotatedTypeMirror representing the annotated type of {@code clazz}.
+   *
+   * @param clazz a class
+   * @return the annotated type of {@code clazz}
+   */
+  public AnnotatedTypeMirror getAnnotatedType(Class<?> clazz) {
+    return getAnnotatedType(elements.getTypeElement(clazz.getCanonicalName()));
+  }
+
+  @Override
+  public @Nullable AnnotationMirror getAnnotationMirror(
+      Tree tree, Class<? extends Annotation> target) {
+    if (isSupportedQualifier(target)) {
+      AnnotatedTypeMirror atm = getAnnotatedType(tree);
+      return atm.getAnnotation(target);
+    }
+    return null;
+  }
+
+  /**
+   * Returns an AnnotatedTypeMirror representing the annotated type of {@code tree}.
+   *
+   * @param tree the AST node
+   * @return the annotated type of {@code tree}
+   */
+  public AnnotatedTypeMirror getAnnotatedType(Tree tree) {
+
+    if (tree == null) {
+      throw new BugInCF("AnnotatedTypeFactory.getAnnotatedType: null tree");
+    }
+    if (shouldCache && classAndMethodTreeCache.containsKey(tree)) {
+      return classAndMethodTreeCache.get(tree).deepCopy();
+    }
+
+    AnnotatedTypeMirror type;
+    if (TreeUtils.isClassTree(tree)) {
+      type = fromClass((ClassTree) tree);
+    } else if (tree.getKind() == Tree.Kind.METHOD || tree.getKind() == Tree.Kind.VARIABLE) {
+      type = fromMember(tree);
+    } else if (TreeUtils.isExpressionTree(tree)) {
+      tree = TreeUtils.withoutParens((ExpressionTree) tree);
+      type = fromExpression((ExpressionTree) tree);
+    } else {
+      throw new BugInCF(
+          "AnnotatedTypeFactory.getAnnotatedType: query of annotated type for tree "
+              + tree.getKind());
+    }
+
+    addComputedTypeAnnotations(tree, type);
+
+    if (TreeUtils.isClassTree(tree) || tree.getKind() == Tree.Kind.METHOD) {
+      // Don't cache VARIABLE
+      if (shouldCache) {
+        classAndMethodTreeCache.put(tree, type.deepCopy());
+      }
+    } else {
+      // No caching otherwise
+    }
+
+    return type;
+  }
+
+  /**
+   * Called by {@link BaseTypeVisitor#visitClass(ClassTree, Void)} before the classTree is type
+   * checked.
+   *
+   * @param classTree ClassTree on which to perform preprocessing
+   */
+  public void preProcessClassTree(ClassTree classTree) {}
+
+  /**
+   * Called by {@link BaseTypeVisitor#visitClass(ClassTree, Void)} after the ClassTree has been type
+   * checked.
+   *
+   * <p>The default implementation uses this to store the defaulted AnnotatedTypeMirrors and
+   * inherited declaration annotations back into the corresponding Elements. Subclasses might want
+   * to override this method if storing defaulted types is not desirable.
+   */
+  public void postProcessClassTree(ClassTree tree) {
+    TypesIntoElements.store(processingEnv, this, tree);
+    DeclarationsIntoElements.store(processingEnv, this, tree);
+    if (wholeProgramInference != null) {
+      // Write out the results of whole-program inference, just once for each class.  As soon as any
+      // class is finished processing, all modified scenes are written to files, in case this was
+      // the last class to be processed.  Post-processing of subsequent classes might result in
+      // re-writing some of the scenes if new information has been written to them.
+      wholeProgramInference.writeResultsToFile(wpiOutputFormat, this.checker);
+    }
+  }
+
+  /**
+   * Determines the annotated type from a type in tree form.
+   *
+   * <p>Note that we cannot decide from a Tree whether it is a type use or an expression.
+   * TreeUtils.isTypeTree is only an under-approximation. For example, an identifier can be either a
+   * type or an expression.
+   *
+   * @param tree the type tree
+   * @return the annotated type of the type in the AST
+   */
+  public AnnotatedTypeMirror getAnnotatedTypeFromTypeTree(Tree tree) {
+    if (tree == null) {
+      throw new BugInCF("AnnotatedTypeFactory.getAnnotatedTypeFromTypeTree: null tree");
+    }
+    AnnotatedTypeMirror type = fromTypeTree(tree);
+    addComputedTypeAnnotations(tree, type);
+    return type;
+  }
+
+  /**
+   * Returns the set of qualifiers that are the upper bounds for a use of the type.
+   *
+   * @param type a type whose upper bounds to obtain
+   */
+  public Set<AnnotationMirror> getTypeDeclarationBounds(TypeMirror type) {
+    return qualifierUpperBounds.getBoundQualifiers(type);
+  }
+
+  /**
+   * Returns the set of qualifiers that are the upper bound for a type use if no other bound is
+   * specified for the type.
+   *
+   * <p>This implementation returns the top qualifiers by default. Subclass may override to return
+   * different qualifiers.
+   *
+   * @return the set of qualifiers that are the upper bound for a type use if no other bound is
+   *     specified for the type
+   */
+  protected Set<? extends AnnotationMirror> getDefaultTypeDeclarationBounds() {
+    return qualHierarchy.getTopAnnotations();
+  }
+
+  /**
+   * Returns the type of the extends or implements clause.
+   *
+   * <p>The primary qualifier is either an explicit annotation on {@code clause}, or it is the
+   * qualifier upper bounds for uses of the type of the clause.
+   *
+   * @param clause tree that represents an extends or implements clause
+   * @return the type of the extends or implements clause
+   */
+  public AnnotatedTypeMirror getTypeOfExtendsImplements(Tree clause) {
+    AnnotatedTypeMirror fromTypeTree = fromTypeTree(clause);
+    Set<AnnotationMirror> bound = getTypeDeclarationBounds(fromTypeTree.getUnderlyingType());
+    fromTypeTree.addMissingAnnotations(bound);
+    return fromTypeTree;
+  }
+
+  // **********************************************************************
+  // Factories for annotated types that do not account for default qualifiers.
+  // They only include qualifiers explicitly inserted by the user.
+  // **********************************************************************
+
+  /**
+   * Creates an AnnotatedTypeMirror for {@code elt} that includes: annotations explicitly written on
+   * the element and annotations from stub files.
+   *
+   * <p>Does not include default qualifiers. To obtain them, use {@link #getAnnotatedType(Element)}.
+   *
+   * <p>Does not include fake overrides from the stub file.
+   *
+   * @param elt the element
+   * @return AnnotatedTypeMirror of the element with explicitly-written and stub file annotations
+   */
+  public AnnotatedTypeMirror fromElement(Element elt) {
+    if (shouldCache && elementCache.containsKey(elt)) {
+      return elementCache.get(elt).deepCopy();
+    }
+    if (elt.getKind() == ElementKind.PACKAGE) {
+      return toAnnotatedType(elt.asType(), false);
+    }
+    AnnotatedTypeMirror type;
+
+    // Because of a bug in Java 8, annotations on type parameters are not stored in elements, so get
+    // explicit annotations from the tree. (This bug has been fixed in Java 9.)  Also, since
+    // annotations computed by the AnnotatedTypeFactory are stored in the element, the annotations
+    // have to be retrieved from the tree so that only explicit annotations are returned.
+    Tree decl = declarationFromElement(elt);
+
+    if (decl == null) {
+      type = stubTypes.getAnnotatedTypeMirror(elt);
+      if (type == null) {
+        type = toAnnotatedType(elt.asType(), ElementUtils.isTypeDeclaration(elt));
+        ElementAnnotationApplier.apply(type, elt, this);
+      }
+    } else if (decl instanceof ClassTree) {
+      type = fromClass((ClassTree) decl);
+    } else if (decl instanceof VariableTree) {
+      type = fromMember(decl);
+    } else if (decl instanceof MethodTree) {
+      type = fromMember(decl);
+    } else if (decl.getKind() == Tree.Kind.TYPE_PARAMETER) {
+      type = fromTypeTree(decl);
+    } else {
+      throw new BugInCF(
+          "AnnotatedTypeFactory.fromElement: cannot be here. decl: "
+              + decl.getKind()
+              + " elt: "
+              + elt);
+    }
+
+    type = mergeAnnotationFileAnnosIntoType(type, elt, ajavaTypes);
+    if (currentFileAjavaTypes != null) {
+      type = mergeAnnotationFileAnnosIntoType(type, elt, currentFileAjavaTypes);
+    }
+
+    if (checker.hasOption("mergeStubsWithSource")) {
+      if (debugStubParser) {
+        System.out.printf("fromElement: mergeStubsIntoType(%s, %s)", type, elt);
+      }
+      type = mergeAnnotationFileAnnosIntoType(type, elt, stubTypes);
+      if (debugStubParser) {
+        System.out.printf(" => %s%n", type);
+      }
+    }
+    // Caching is disabled if annotation files are being parsed, because calls to this
+    // method before the annotation files are fully read can return incorrect results.
+    if (shouldCache
+        && !stubTypes.isParsing()
+        && !ajavaTypes.isParsing()
+        && (currentFileAjavaTypes == null || !currentFileAjavaTypes.isParsing())) {
+      elementCache.put(elt, type.deepCopy());
+    }
+    return type;
+  }
+
+  /**
+   * Returns an AnnotatedDeclaredType with explicit annotations from the ClassTree {@code tree}.
+   *
+   * @param tree the class declaration
+   * @return AnnotatedDeclaredType with explicit annotations from {@code tree}
+   */
+  private AnnotatedDeclaredType fromClass(ClassTree tree) {
+    return TypeFromTree.fromClassTree(this, tree);
+  }
+
+  /**
+   * Creates an AnnotatedTypeMirror for a variable or method declaration tree. The
+   * AnnotatedTypeMirror contains annotations explicitly written on the tree.
+   *
+   * <p>If a VariableTree is a parameter to a lambda, this method also adds annotations from the
+   * declared type of the functional interface and the executable type of its method.
+   *
+   * <p>The returned AnnotatedTypeMirror also contains explicitly written annotations from any ajava
+   * file and if {@code -AmergeStubsWithSource} is passed, it also merges any explicitly written
+   * annotations from stub files.
+   *
+   * @param tree MethodTree or VariableTree
+   * @return AnnotatedTypeMirror with explicit annotations from {@code tree}
+   */
+  private final AnnotatedTypeMirror fromMember(Tree tree) {
+    if (!(tree instanceof MethodTree || tree instanceof VariableTree)) {
+      throw new BugInCF(
+          "AnnotatedTypeFactory.fromMember: not a method or variable declaration: " + tree);
+    }
+    if (shouldCache && fromMemberTreeCache.containsKey(tree)) {
+      return fromMemberTreeCache.get(tree).deepCopy();
+    }
+    AnnotatedTypeMirror result = TypeFromTree.fromMember(this, tree);
+
+    result = mergeAnnotationFileAnnosIntoType(result, tree, ajavaTypes);
+    if (currentFileAjavaTypes != null) {
+      result = mergeAnnotationFileAnnosIntoType(result, tree, currentFileAjavaTypes);
+    }
+
+    if (checker.hasOption("mergeStubsWithSource")) {
+      if (debugStubParser) {
+        System.out.printf("fromClass: mergeStubsIntoType(%s, %s)", result, tree);
+      }
+      result = mergeAnnotationFileAnnosIntoType(result, tree, stubTypes);
+      if (debugStubParser) {
+        System.out.printf(" => %s%n", result);
+      }
+    }
+
+    if (shouldCache) {
+      fromMemberTreeCache.put(tree, result.deepCopy());
+    }
+
+    return result;
+  }
+
+  /**
+   * Merges types from annotation files for {@code tree} into {@code type} by taking the greatest
+   * lower bound of the annotations in both.
+   *
+   * @param type the type to apply annotation file types to
+   * @param tree the tree from which to read annotation file types
+   * @param source storage for current annotation file annotations
+   * @return the given type, side-effected to add the annotation file types
+   */
+  private AnnotatedTypeMirror mergeAnnotationFileAnnosIntoType(
+      @Nullable AnnotatedTypeMirror type, Tree tree, AnnotationFileElementTypes source) {
+    Element elt = TreeUtils.elementFromTree(tree);
+    return mergeAnnotationFileAnnosIntoType(type, elt, source);
+  }
+
+  /**
+   * A scanner used to combine annotations from two AnnotatedTypeMirrors. The scanner requires
+   * {@link #qualHierarchy}, which is set in {@link #postInit()} rather than the construtor, so
+   * lazily initialize this field before use.
+   */
+  private @MonotonicNonNull AnnotatedTypeCombiner annotatedTypeCombiner = null;
+
+  /**
+   * Merges types from annotation files for {@code elt} into {@code type} by taking the greatest
+   * lower bound of the annotations in both.
+   *
+   * @param type the type to apply annotation file types to
+   * @param elt the element from which to read annotation file types
+   * @param source storage for current annotation file annotations
+   * @return the type, side-effected to add the annotation file types
+   */
+  protected AnnotatedTypeMirror mergeAnnotationFileAnnosIntoType(
+      @Nullable AnnotatedTypeMirror type, Element elt, AnnotationFileElementTypes source) {
+    AnnotatedTypeMirror typeFromFile = source.getAnnotatedTypeMirror(elt);
+    if (typeFromFile == null) {
+      return type;
+    }
+    if (type == null) {
+      return typeFromFile;
+    }
+    if (annotatedTypeCombiner == null) {
+      annotatedTypeCombiner = new AnnotatedTypeCombiner(qualHierarchy);
+    }
+    // Must merge (rather than only take the annotation file type if it is a subtype) to support
+    // WPI.
+    annotatedTypeCombiner.visit(typeFromFile, type);
+    return type;
+  }
+
+  /**
+   * Creates an AnnotatedTypeMirror for an ExpressionTree. The AnnotatedTypeMirror contains explicit
+   * annotations written on the expression and for some expressions, annotations from
+   * sub-expressions that could have been explicitly written, defaulted, refined, or otherwise
+   * computed. (Expression whose type include annotations from sub-expressions are: ArrayAccessTree,
+   * ConditionalExpressionTree, IdentifierTree, MemberSelectTree, and MethodInvocationTree.)
+   *
+   * <p>For example, the AnnotatedTypeMirror returned for an array access expression is the fully
+   * annotated type of the array component of the array being accessed.
+   *
+   * @param tree an expression
+   * @return AnnotatedTypeMirror of the expressions either fully-annotated or partially annotated
+   *     depending on the kind of expression
+   * @see TypeFromExpressionVisitor
+   */
+  private AnnotatedTypeMirror fromExpression(ExpressionTree tree) {
+    if (shouldCache && fromExpressionTreeCache.containsKey(tree)) {
+      return fromExpressionTreeCache.get(tree).deepCopy();
+    }
+
+    AnnotatedTypeMirror result = TypeFromTree.fromExpression(this, tree);
+
+    if (shouldCache
+        && tree.getKind() != Tree.Kind.NEW_CLASS
+        && tree.getKind() != Kind.NEW_ARRAY
+        && tree.getKind() != Kind.CONDITIONAL_EXPRESSION) {
+      // Don't cache the type of some expressions, because incorrect annotations would be
+      // cached during dataflow analysis. See Issue #602.
+      fromExpressionTreeCache.put(tree, result.deepCopy());
+    }
+    return result;
+  }
+
+  /**
+   * Creates an AnnotatedTypeMirror for the tree. The AnnotatedTypeMirror contains annotations
+   * explicitly written on the tree. It also adds type arguments to raw types that include
+   * annotations from the element declaration of the type {@link #fromElement(Element)}.
+   *
+   * <p>Called on the following trees: AnnotatedTypeTree, ArrayTypeTree, ParameterizedTypeTree,
+   * PrimitiveTypeTree, TypeParameterTree, WildcardTree, UnionType, IntersectionTypeTree, and
+   * IdentifierTree, MemberSelectTree.
+   *
+   * @param tree the type tree
+   * @return the (partially) annotated type of the type in the AST
+   */
+  /*package private*/ final AnnotatedTypeMirror fromTypeTree(Tree tree) {
+    if (shouldCache && fromTypeTreeCache.containsKey(tree)) {
+      return fromTypeTreeCache.get(tree).deepCopy();
+    }
+
+    AnnotatedTypeMirror result = TypeFromTree.fromTypeTree(this, tree);
+
+    if (shouldCache) {
+      fromTypeTreeCache.put(tree, result.deepCopy());
+    }
+    return result;
+  }
+
+  // **********************************************************************
+  // Customization methods meant to be overridden by subclasses to include
+  // defaulted annotations
+  // **********************************************************************
+
+  /**
+   * Changes annotations on a type obtained from a {@link Tree}. By default, this method does
+   * nothing. GenericAnnotatedTypeFactory uses this method to implement defaulting and inference
+   * (flow-sensitive type refinement). Its subclasses usually override it only to customize default
+   * annotations.
+   *
+   * <p>Subclasses that override this method should also override {@link
+   * #addComputedTypeAnnotations(Element, AnnotatedTypeMirror)}.
+   *
+   * <p>In classes that extend {@link GenericAnnotatedTypeFactory}, override {@link
+   * GenericAnnotatedTypeFactory#addComputedTypeAnnotations(Tree, AnnotatedTypeMirror, boolean)}
+   * instead of this method.
+   *
+   * @param tree an AST node
+   * @param type the type obtained from {@code tree}
+   */
+  protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) {
+    // Pass.
+  }
+
+  /**
+   * Changes annotations on a type obtained from an {@link Element}. By default, this method does
+   * nothing. GenericAnnotatedTypeFactory uses this method to implement defaulting.
+   *
+   * <p>Subclasses that override this method should also override {@link
+   * #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}.
+   *
+   * @param elt an element
+   * @param type the type obtained from {@code elt}
+   */
+  protected void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) {
+    // Pass.
+  }
+
+  /**
+   * Adds default annotations to {@code type}. This method should only be used in places where the
+   * correct annotations cannot be computed because of uninferred type arguments. (See {@link
+   * AnnotatedWildcardType#isUninferredTypeArgument()}.)
+   *
+   * @param type annotated type to which default annotations are added
+   */
+  public void addDefaultAnnotations(AnnotatedTypeMirror type) {
+    // Pass.
+  }
+
+  /**
+   * A callback method for the AnnotatedTypeFactory subtypes to customize directSupertypes().
+   * Overriding methods should merely change the annotations on the supertypes, without adding or
+   * removing new types.
+   *
+   * <p>The default provided implementation adds {@code type} annotations to {@code supertypes}.
+   * This allows the {@code type} and its supertypes to have the qualifiers.
+   *
+   * @param type the type whose supertypes are desired
+   * @param supertypes the supertypes as specified by the base AnnotatedTypeFactory
+   */
+  protected void postDirectSuperTypes(
+      AnnotatedTypeMirror type, List<? extends AnnotatedTypeMirror> supertypes) {
+    // Use the effective annotations here to get the correct annotations
+    // for type variables and wildcards.
+    Set<AnnotationMirror> annotations = type.getEffectiveAnnotations();
+    for (AnnotatedTypeMirror supertype : supertypes) {
+      if (!annotations.equals(supertype.getEffectiveAnnotations())) {
+        supertype.clearAnnotations();
+        // TODO: is this correct for type variables and wildcards?
+        supertype.addAnnotations(annotations);
+      }
+    }
+  }
+
+  /**
+   * A callback method for the AnnotatedTypeFactory subtypes to customize
+   * AnnotatedTypes.asMemberOf(). Overriding methods should merely change the annotations on the
+   * subtypes, without changing the types.
+   *
+   * @param type the annotated type of the element
+   * @param owner the annotated type of the receiver of the accessing tree
+   * @param element the element of the field or method
+   */
+  public void postAsMemberOf(AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) {
+    if (element.getKind() == ElementKind.FIELD) {
+      addAnnotationFromFieldInvariant(type, owner, (VariableElement) element);
+    }
+    addComputedTypeAnnotations(element, type);
+  }
+
+  /**
+   * Adds the qualifier specified by a field invariant for {@code field} to {@code type}.
+   *
+   * @param type annotated type to which the annotation is added
+   * @param accessedVia the annotated type of the receiver of the accessing tree. (Only used to get
+   *     the type element of the underling type.)
+   * @param field element representing the field
+   */
+  protected void addAnnotationFromFieldInvariant(
+      AnnotatedTypeMirror type, AnnotatedTypeMirror accessedVia, VariableElement field) {
+    TypeMirror declaringType = accessedVia.getUnderlyingType();
+    // Find the first upper bound that isn't a wildcard or type variable
+    while (declaringType.getKind() == TypeKind.WILDCARD
+        || declaringType.getKind() == TypeKind.TYPEVAR) {
+      if (declaringType.getKind() == TypeKind.WILDCARD) {
+        declaringType = TypesUtils.wildUpperBound(declaringType, processingEnv);
+      } else if (declaringType.getKind() == TypeKind.TYPEVAR) {
+        declaringType = ((TypeVariable) declaringType).getUpperBound();
+      }
+    }
+    TypeElement typeElement = TypesUtils.getTypeElement(declaringType);
+    if (ElementUtils.enclosingTypeElement(field).equals(typeElement)) {
+      // If the field is declared in the accessedVia class, then the field in the invariant
+      // cannot be this field, even if the field has the same name.
+      return;
+    }
+
+    FieldInvariants invariants = getFieldInvariants(typeElement);
+    if (invariants == null) {
+      return;
+    }
+    List<AnnotationMirror> invariantAnnos = invariants.getQualifiersFor(field.getSimpleName());
+    type.replaceAnnotations(invariantAnnos);
+  }
+
+  /**
+   * Returns the field invariants for the given class, as expressed by the user in {@link
+   * FieldInvariant @FieldInvariant} method annotations.
+   *
+   * <p>Subclasses may implement their own field invariant annotations if {@link
+   * FieldInvariant @FieldInvariant} is not expressive enough. They must override this method to
+   * properly create AnnotationMirror and also override {@link
+   * #getFieldInvariantDeclarationAnnotations()} to return their field invariants.
+   *
+   * @param element class for which to get invariants
+   * @return field invariants for {@code element}
+   */
+  public FieldInvariants getFieldInvariants(TypeElement element) {
+    if (element == null) {
+      return null;
+    }
+    AnnotationMirror fieldInvarAnno = getDeclAnnotation(element, FieldInvariant.class);
+    if (fieldInvarAnno == null) {
+      return null;
+    }
+    List<String> fields =
+        AnnotationUtils.getElementValueArray(
+            fieldInvarAnno, fieldInvariantFieldElement, String.class);
+    List<@CanonicalName Name> classes =
+        AnnotationUtils.getElementValueClassNames(fieldInvarAnno, fieldInvariantQualifierElement);
+    List<AnnotationMirror> qualifiers =
+        CollectionsPlume.mapList(
+            (Name name) ->
+                // Calling AnnotationBuilder.fromName (which ignores elements/fields) is acceptable
+                // because @FieldInvariant does not handle classes with elements/fields.
+                AnnotationBuilder.fromName(elements, name),
+            classes);
+    if (qualifiers.size() == 1) {
+      while (fields.size() > qualifiers.size()) {
+        qualifiers.add(qualifiers.get(0));
+      }
+    }
+    if (fields.size() != qualifiers.size()) {
+      // The user wrote a malformed @FieldInvariant annotation, so just return a malformed
+      // FieldInvariants object.  The BaseTypeVisitor will issue an error.
+      return new FieldInvariants(fields, qualifiers);
+    }
+
+    // Only keep qualifiers that are supported by this checker.  (The other qualifiers cannot
+    // be checked by this checker, so they must be ignored.)
+    List<String> annotatedFields = new ArrayList<>();
+    List<AnnotationMirror> supportedQualifiers = new ArrayList<>();
+    for (int i = 0; i < fields.size(); i++) {
+      if (isSupportedQualifier(qualifiers.get(i))) {
+        annotatedFields.add(fields.get(i));
+        supportedQualifiers.add(qualifiers.get(i));
+      }
+    }
+    if (annotatedFields.isEmpty()) {
+      return null;
+    }
+
+    return new FieldInvariants(annotatedFields, supportedQualifiers);
+  }
+
+  /**
+   * Returns the AnnotationTree which is a use of one of the field invariant annotations (as
+   * specified via {@link #getFieldInvariantDeclarationAnnotations()}. If one isn't found, null is
+   * returned.
+   *
+   * @param annoTrees list of trees to search; the result is one of the list elements, or null
+   * @return the AnnotationTree that is a use of one of the field invariant annotations, or null if
+   *     one isn't found
+   */
+  public AnnotationTree getFieldInvariantAnnotationTree(List<? extends AnnotationTree> annoTrees) {
+    List<AnnotationMirror> annos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees);
+    for (int i = 0; i < annos.size(); i++) {
+      for (Class<? extends Annotation> clazz : getFieldInvariantDeclarationAnnotations()) {
+        if (areSameByClass(annos.get(i), clazz)) {
+          return annoTrees.get(i);
+        }
+      }
+    }
+    return null;
+  }
+
+  /** Returns the set of classes of field invariant annotations. */
+  protected Set<Class<? extends Annotation>> getFieldInvariantDeclarationAnnotations() {
+    return Collections.singleton(FieldInvariant.class);
+  }
+
+  /**
+   * A callback method for the AnnotatedTypeFactory subtypes to customize
+   * AnnotatedTypeMirror.substitute().
+   *
+   * @param varDecl a declaration of a type variable
+   * @param varUse a use of the same type variable
+   * @param value the new type to substitute in for the type variable
+   */
+  public void postTypeVarSubstitution(
+      AnnotatedTypeVariable varDecl, AnnotatedTypeVariable varUse, AnnotatedTypeMirror value) {
+    if (!varUse.getAnnotationsField().isEmpty()
+        && !AnnotationUtils.areSame(varUse.getAnnotationsField(), varDecl.getAnnotationsField())) {
+      value.replaceAnnotations(varUse.getAnnotationsField());
+    }
+  }
+
+  /**
+   * Adapt the upper bounds of the type variables of a class relative to the type instantiation. In
+   * some type systems, the upper bounds depend on the instantiation of the class. For example, in
+   * the Generic Universe Type system, consider a class declaration
+   *
+   * <pre>{@code   class C<X extends @Peer Object> }</pre>
+   *
+   * then the instantiation
+   *
+   * <pre>{@code   @Rep C<@Rep Object> }</pre>
+   *
+   * is legal. The upper bounds of class C have to be adapted by the main modifier.
+   *
+   * <p>An example of an adaptation follows. Suppose, I have a declaration:
+   *
+   * <pre>{@code  class MyClass<E extends List<E>>}</pre>
+   *
+   * And an instantiation:
+   *
+   * <pre>{@code  new MyClass<@NonNull String>()}</pre>
+   *
+   * <p>The upper bound of E adapted to the argument String, would be {@code List<@NonNull String>}
+   * and the lower bound would be an AnnotatedNullType.
+   *
+   * <p>TODO: ensure that this method is consistently used instead of directly querying the type
+   * variables.
+   *
+   * @param type the use of the type
+   * @param element the corresponding element
+   * @return the adapted bounds of the type parameters
+   */
+  public List<AnnotatedTypeParameterBounds> typeVariablesFromUse(
+      AnnotatedDeclaredType type, TypeElement element) {
+
+    AnnotatedDeclaredType generic = getAnnotatedType(element);
+    List<AnnotatedTypeMirror> targs = type.getTypeArguments();
+    List<AnnotatedTypeMirror> tvars = generic.getTypeArguments();
+
+    assert targs.size() == tvars.size()
+        : "Mismatch in type argument size between " + type + " and " + generic;
+
+    // System.err.printf("TVFU%n  type: %s%n  generic: %s%n", type, generic);
+
+    Map<TypeVariable, AnnotatedTypeMirror> mapping = new HashMap<>();
+
+    AnnotatedDeclaredType enclosing = type;
+    while (enclosing != null) {
+      List<AnnotatedTypeMirror> enclosingTArgs = enclosing.getTypeArguments();
+      AnnotatedDeclaredType declaredType =
+          getAnnotatedType((TypeElement) enclosing.getUnderlyingType().asElement());
+      List<AnnotatedTypeMirror> enclosingTVars = declaredType.getTypeArguments();
+      for (int i = 0; i < enclosingTArgs.size(); i++) {
+        AnnotatedTypeVariable enclosingTVar = (AnnotatedTypeVariable) enclosingTVars.get(i);
+        mapping.put(enclosingTVar.getUnderlyingType(), enclosingTArgs.get(i));
+      }
+      enclosing = enclosing.getEnclosingType();
+    }
+
+    List<AnnotatedTypeParameterBounds> res = new ArrayList<>(tvars.size());
+
+    for (AnnotatedTypeMirror atm : tvars) {
+      AnnotatedTypeVariable atv = (AnnotatedTypeVariable) atm;
+      AnnotatedTypeMirror upper = typeVarSubstitutor.substitute(mapping, atv.getUpperBound());
+      AnnotatedTypeMirror lower = typeVarSubstitutor.substitute(mapping, atv.getLowerBound());
+      res.add(new AnnotatedTypeParameterBounds(upper, lower));
+    }
+    return res;
+  }
+
+  /**
+   * Creates and returns an AnnotatedNullType qualified with {@code annotations}.
+   *
+   * @param annotations set of AnnotationMirrors to qualify the returned type with
+   * @return AnnotatedNullType qualified with {@code annotations}
+   */
+  public AnnotatedNullType getAnnotatedNullType(Set<? extends AnnotationMirror> annotations) {
+    final AnnotatedTypeMirror.AnnotatedNullType nullType =
+        (AnnotatedNullType) toAnnotatedType(processingEnv.getTypeUtils().getNullType(), false);
+    nullType.addAnnotations(annotations);
+    return nullType;
+  }
+
+  // **********************************************************************
+  // Utilities method for getting specific types from trees or elements
+  // **********************************************************************
+
+  /**
+   * Return the implicit receiver type of an expression tree.
+   *
+   * <p>The result is null for expressions that don't have a receiver, e.g. for a local variable or
+   * method parameter access. The result is also null for expressions that have an explicit
+   * receiver.
+   *
+   * <p>Clients should generally call {@link #getReceiverType}.
+   *
+   * @param tree the expression that might have an implicit receiver
+   * @return the type of the implicit receiver
+   */
+  protected @Nullable AnnotatedDeclaredType getImplicitReceiverType(ExpressionTree tree) {
+    assert (tree.getKind() == Tree.Kind.IDENTIFIER
+            || tree.getKind() == Tree.Kind.MEMBER_SELECT
+            || tree.getKind() == Tree.Kind.METHOD_INVOCATION
+            || tree.getKind() == Tree.Kind.NEW_CLASS)
+        : "Unexpected tree kind: " + tree.getKind();
+
+    // Return null if the element kind has no receiver.
+    Element element = TreeUtils.elementFromTree(tree);
+    assert element != null : "Unexpected null element for tree: " + tree;
+    if (!ElementUtils.hasReceiver(element)) {
+      return null;
+    }
+
+    // Return null if the receiver is explicit.
+    if (TreeUtils.getReceiverTree(tree) != null) {
+      return null;
+    }
+
+    TypeElement elementOfImplicitReceiver = ElementUtils.enclosingTypeElement(element);
+    if (tree.getKind() == Kind.NEW_CLASS) {
+      if (elementOfImplicitReceiver.getEnclosingElement() != null) {
+        elementOfImplicitReceiver =
+            ElementUtils.enclosingTypeElement(elementOfImplicitReceiver.getEnclosingElement());
+      } else {
+        elementOfImplicitReceiver = null;
+      }
+      if (elementOfImplicitReceiver == null) {
+        // If the typeElt does not have an enclosing class, then the NewClassTree
+        // does not have an implicit receiver.
+        return null;
+      }
+    }
+
+    TypeMirror typeOfImplicitReceiver = elementOfImplicitReceiver.asType();
+    AnnotatedDeclaredType thisType = getSelfType(tree);
+    if (thisType == null) {
+      return null;
+    }
+    // An implicit receiver is the first enclosing type that is a subtype of the type where
+    // element is declared.
+    while (!isSubtype(thisType.getUnderlyingType(), typeOfImplicitReceiver)) {
+      thisType = thisType.getEnclosingType();
+    }
+    return thisType;
+  }
+
+  /**
+   * Returns the type of {@code this} at the location of {@code tree}. Returns {@code null} if
+   * {@code tree} is in a location where {@code this} has no meaning, such as the body of a static
+   * method.
+   *
+   * <p>The parameter is an arbitrary tree and does not have to mention "this", neither explicitly
+   * nor implicitly. This method can be overridden for type-system specific behavior.
+   *
+   * @param tree location used to decide the type of {@code this}
+   * @return the type of {@code this} at the location of {@code tree}
+   */
+  public @Nullable AnnotatedDeclaredType getSelfType(Tree tree) {
+    if (TreeUtils.isClassTree(tree)) {
+      return getAnnotatedType(TreeUtils.elementFromDeclaration((ClassTree) tree));
+    }
+
+    Tree enclosingTree = getEnclosingClassOrMethod(tree);
+    if (enclosingTree == null) {
+      // tree is inside an annotation, where "this" is not allowed. So, no self type exists.
+      return null;
+    } else if (enclosingTree.getKind() == Kind.METHOD) {
+      MethodTree enclosingMethod = (MethodTree) enclosingTree;
+      if (TreeUtils.isConstructor(enclosingMethod)) {
+        return (AnnotatedDeclaredType) getAnnotatedType(enclosingMethod).getReturnType();
+      } else {
+        return getAnnotatedType(enclosingMethod).getReceiverType();
+      }
+    } else if (TreeUtils.isClassTree(enclosingTree)) {
+      return (AnnotatedDeclaredType) getAnnotatedType(enclosingTree);
+    }
+    return null;
+  }
+
+  /** A set containing class, method, and annotation tree kinds. */
+  private static final Set<Tree.Kind> classMethodAnnotationKinds =
+      EnumSet.copyOf(TreeUtils.classTreeKinds());
+
+  static {
+    classMethodAnnotationKinds.add(Kind.METHOD);
+    classMethodAnnotationKinds.add(Kind.TYPE_ANNOTATION);
+    classMethodAnnotationKinds.add(Kind.ANNOTATION);
+  }
+
+  /**
+   * Returns the innermost enclosing method or class tree of {@code tree}. If {@code tree} is
+   * artificial (that is, created by dataflow), then {@link #artificialTreeToEnclosingElementMap} is
+   * used to find the enclosing tree.
+   *
+   * <p>If the tree is inside an annotation, then {@code null} is returned.
+   *
+   * @param tree tree to whose innermost enclosing method or class to return
+   * @return the innermost enclosing method or class tree of {@code tree}, or {@code null} if {@code
+   *     tree} is inside an annotation
+   */
+  public @Nullable Tree getEnclosingClassOrMethod(Tree tree) {
+    TreePath path = getPath(tree);
+    Tree enclosing = TreePathUtil.enclosingOfKind(path, classMethodAnnotationKinds);
+    if (enclosing != null) {
+      if (enclosing.getKind() == Kind.ANNOTATION || enclosing.getKind() == Kind.TYPE_ANNOTATION) {
+        return null;
+      }
+      return enclosing;
+    }
+    Element e = getEnclosingElementForArtificialTree(tree);
+    if (e != null) {
+      Element enclosingMethodOrClass = e;
+      while (enclosingMethodOrClass != null
+          && enclosingMethodOrClass.getKind() != ElementKind.METHOD
+          && !enclosingMethodOrClass.getKind().isClass()
+          && !enclosingMethodOrClass.getKind().isInterface()) {
+        enclosingMethodOrClass = enclosingMethodOrClass.getEnclosingElement();
+      }
+      return declarationFromElement(enclosingMethodOrClass);
+    }
+    return getCurrentClassTree(tree);
+  }
+
+  /**
+   * Returns the {@link AnnotatedTypeMirror} of the enclosing type at the location of {@code tree}
+   * that is the same type as {@code typeElement}.
+   *
+   * @param typeElement type of the enclosing type to return
+   * @param tree location to use
+   * @return the enclosing type at the location of {@code tree} that is the same type as {@code
+   *     typeElement}
+   */
+  public AnnotatedDeclaredType getEnclosingType(TypeElement typeElement, Tree tree) {
+    AnnotatedDeclaredType thisType = getSelfType(tree);
+    while (!isSameType(thisType.getUnderlyingType(), typeElement.asType())) {
+      thisType = thisType.getEnclosingType();
+    }
+    return thisType;
+  }
+
+  /**
+   * Returns true if the erasure of {@code type1} is a subtype of the erasure of {@code type2}.
+   *
+   * @param type1 a type
+   * @param type2 a type
+   * @return true if the erasure of {@code type1} is a subtype of the erasure of {@code type2}
+   */
+  private boolean isSubtype(TypeMirror type1, TypeMirror type2) {
+    return types.isSubtype(types.erasure(type1), types.erasure(type2));
+  }
+
+  /**
+   * Returns true if the erasure of {@code type1} is the same type as the erasure of {@code type2}.
+   *
+   * @param type1 a type
+   * @param type2 a type
+   * @return true if the erasure of {@code type1} is the same type as the erasure of {@code type2}
+   */
+  private boolean isSameType(TypeMirror type1, TypeMirror type2) {
+    return types.isSameType(types.erasure(type1), types.erasure(type2));
+  }
+
+  /**
+   * Returns the receiver type of the expression tree, which might be the type of an implicit {@code
+   * this}. Returns null if the expression has no explicit or implicit receiver.
+   *
+   * @param expression the expression for which to determine the receiver type
+   * @return the type of the receiver of expression
+   */
+  public final AnnotatedTypeMirror getReceiverType(ExpressionTree expression) {
+    ExpressionTree receiver = TreeUtils.getReceiverTree(expression);
+    if (receiver != null) {
+      return getAnnotatedType(receiver);
+    }
+
+    Element element = TreeUtils.elementFromUse(expression);
+    if (element != null && ElementUtils.hasReceiver(element)) {
+      // The tree references an element that has a receiver, but the tree does not have an explicit
+      // receiver. So, the tree must have an implicit receiver of "this" or "Outer.this".
+      return getImplicitReceiverType(expression);
+    } else {
+      return null;
+    }
+  }
+
+  /** The type for an instantiated generic method or constructor. */
+  public static class ParameterizedExecutableType {
+    /** The method's/constructor's type. */
+    public final AnnotatedExecutableType executableType;
+    /** The types of the generic type arguments. */
+    public final List<AnnotatedTypeMirror> typeArgs;
+    /** Create a ParameterizedExecutableType. */
+    public ParameterizedExecutableType(
+        AnnotatedExecutableType executableType, List<AnnotatedTypeMirror> typeArgs) {
+      this.executableType = executableType;
+      this.typeArgs = typeArgs;
+    }
+
+    @Override
+    public String toString() {
+      if (typeArgs.isEmpty()) {
+        return executableType.toString();
+      } else {
+        StringJoiner typeArgsString = new StringJoiner(",", "<", ">");
+        for (AnnotatedTypeMirror atm : typeArgs) {
+          typeArgsString.add(atm.toString());
+        }
+        return typeArgsString + " " + executableType.toString();
+      }
+    }
+  }
+
+  /**
+   * Determines the type of the invoked method based on the passed method invocation tree.
+   *
+   * <p>The returned method type has all type variables resolved, whether based on receiver type,
+   * passed type parameters if any, and method invocation parameter.
+   *
+   * <p>Subclasses may override this method to customize inference of types or qualifiers based on
+   * method invocation parameters.
+   *
+   * <p>As an implementation detail, this method depends on {@link AnnotatedTypes#asMemberOf(Types,
+   * AnnotatedTypeFactory, AnnotatedTypeMirror, Element)}, and customization based on receiver type
+   * should be in accordance to its specification.
+   *
+   * <p>The return type is a pair of the type of the invoked method and the (inferred) type
+   * arguments. Note that neither the explicitly passed nor the inferred type arguments are
+   * guaranteed to be subtypes of the corresponding upper bounds. See method {@link
+   * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of type
+   * argument well-formedness.
+   *
+   * <p>Note that "this" and "super" constructor invocations are also handled by this method
+   * (explicit or implicit ones, at the beginning of a constructor). Method {@link
+   * #constructorFromUse(NewClassTree)} is only used for a constructor invocation in a "new"
+   * expression.
+   *
+   * @param tree the method invocation tree
+   * @return the method type being invoked with tree and the (inferred) type arguments
+   */
+  public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) {
+    ExecutableElement methodElt = TreeUtils.elementFromUse(tree);
+    AnnotatedTypeMirror receiverType = getReceiverType(tree);
+    if (receiverType == null && TreeUtils.isSuperConstructorCall(tree)) {
+      // super() calls don't have a receiver, but they should be view-point adapted as if
+      // "this" is the receiver.
+      receiverType = getSelfType(tree);
+    }
+
+    ParameterizedExecutableType result = methodFromUse(tree, methodElt, receiverType);
+    if (checker.shouldResolveReflection()
+        && reflectionResolver.isReflectiveMethodInvocation(tree)) {
+      result = reflectionResolver.resolveReflectiveCall(this, tree, result);
+    }
+
+    AnnotatedExecutableType method = result.executableType;
+    if (method.getReturnType().getKind() == TypeKind.WILDCARD
+        && ((AnnotatedWildcardType) method.getReturnType()).isUninferredTypeArgument()) {
+      // Get the correct Java type from the tree and use it as the upper bound of the wildcard.
+      TypeMirror tm = TreeUtils.typeOf(tree);
+      AnnotatedTypeMirror t = toAnnotatedType(tm, false);
+
+      AnnotatedWildcardType wildcard = (AnnotatedWildcardType) method.getReturnType();
+      if (ignoreUninferredTypeArguments) {
+        // Remove the annotations so that default annotations are used instead.
+        // (See call to addDefaultAnnotations below.)
+        t.clearAnnotations();
+      } else {
+        t.replaceAnnotations(wildcard.getExtendsBound().getAnnotations());
+      }
+      wildcard.setExtendsBound(t);
+      addDefaultAnnotations(wildcard);
+    }
+
+    return result;
+  }
+
+  /**
+   * Determines the type of the invoked method based on the passed expression tree, executable
+   * element, and receiver type.
+   *
+   * @param tree either a MethodInvocationTree or a MemberReferenceTree
+   * @param methodElt the element of the referenced method
+   * @param receiverType the type of the receiver
+   * @return the method type being invoked with tree and the (inferred) type arguments
+   * @see #methodFromUse(MethodInvocationTree)
+   */
+  public ParameterizedExecutableType methodFromUse(
+      ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) {
+
+    AnnotatedExecutableType memberTypeWithoutOverrides =
+        getAnnotatedType(methodElt); // get unsubstituted type
+    AnnotatedExecutableType memberTypeWithOverrides =
+        (AnnotatedExecutableType)
+            applyFakeOverrides(receiverType, methodElt, memberTypeWithoutOverrides);
+    methodFromUsePreSubstitution(tree, memberTypeWithOverrides);
+
+    AnnotatedExecutableType methodType =
+        AnnotatedTypes.asMemberOf(types, this, receiverType, methodElt, memberTypeWithOverrides);
+    List<AnnotatedTypeMirror> typeargs = new ArrayList<>(methodType.getTypeVariables().size());
+
+    Map<TypeVariable, AnnotatedTypeMirror> typeVarMapping =
+        AnnotatedTypes.findTypeArguments(processingEnv, this, tree, methodElt, methodType);
+
+    if (!typeVarMapping.isEmpty()) {
+      for (AnnotatedTypeVariable tv : methodType.getTypeVariables()) {
+        if (typeVarMapping.get(tv.getUnderlyingType()) == null) {
+          throw new BugInCF(
+              "AnnotatedTypeFactory.methodFromUse:mismatch between declared method type variables"
+                  + " and the inferred method type arguments. Method type variables: "
+                  + methodType.getTypeVariables()
+                  + "; "
+                  + "Inferred method type arguments: "
+                  + typeVarMapping);
+        }
+        typeargs.add(typeVarMapping.get(tv.getUnderlyingType()));
+      }
+      methodType =
+          (AnnotatedExecutableType) typeVarSubstitutor.substitute(typeVarMapping, methodType);
+    }
+
+    if (tree.getKind() == Tree.Kind.METHOD_INVOCATION
+        && TreeUtils.isMethodInvocation(tree, objectGetClass, processingEnv)) {
+      adaptGetClassReturnTypeToReceiver(methodType, receiverType);
+    }
+
+    return new ParameterizedExecutableType(methodType, typeargs);
+  }
+
+  /**
+   * Given a member and its type, returns the type with fake overrides applied to it.
+   *
+   * @param receiverType the type of the class that contains member (or a subtype of it)
+   * @param member a type member, such as a method or field
+   * @param memberType the type of {@code member}
+   * @return {@code memberType}, adjusted according to fake overrides
+   */
+  private AnnotatedTypeMirror applyFakeOverrides(
+      AnnotatedTypeMirror receiverType, Element member, AnnotatedTypeMirror memberType) {
+    // Currently, handle only methods, not fields.  TODO: Handle fields.
+    if (memberType.getKind() != TypeKind.EXECUTABLE) {
+      return memberType;
+    }
+
+    AnnotationFileElementTypes afet = stubTypes;
+    AnnotatedExecutableType methodType =
+        (AnnotatedExecutableType) afet.getFakeOverride(member, receiverType);
+    if (methodType == null) {
+      methodType = (AnnotatedExecutableType) memberType;
+    }
+    return methodType;
+  }
+
+  /**
+   * A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the
+   * declared method type before type variable substitution.
+   *
+   * @param tree either a method invocation or a member reference tree
+   * @param type declared method type before type variable substitution
+   */
+  protected void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) {
+    assert tree instanceof MethodInvocationTree || tree instanceof MemberReferenceTree;
+  }
+
+  /**
+   * Java special-cases the return type of {@link java.lang.Class#getClass() getClass()}. Though the
+   * method has a return type of {@code Class<?>}, the compiler special cases this return-type and
+   * changes the bound of the type argument to the erasure of the receiver type. For example:
+   *
+   * <ul>
+   *   <li>{@code x.getClass()} has the type {@code Class< ? extends erasure_of_x >}
+   *   <li>{@code someInteger.getClass()} has the type {@code Class< ? extends Integer >}
+   * </ul>
+   *
+   * @param getClassType this must be a type representing a call to Object.getClass otherwise a
+   *     runtime exception will be thrown. It is modified by side effect.
+   * @param receiverType the receiver type of the method invocation (not the declared receiver type)
+   */
+  protected void adaptGetClassReturnTypeToReceiver(
+      final AnnotatedExecutableType getClassType, final AnnotatedTypeMirror receiverType) {
+    // TODO: should the receiver type ever be a declaration??
+    // Work on removing the asUse() call.
+    final AnnotatedTypeMirror newBound = receiverType.getErased().asUse();
+
+    final AnnotatedTypeMirror returnType = getClassType.getReturnType();
+    if (returnType == null
+        || !(returnType.getKind() == TypeKind.DECLARED)
+        || ((AnnotatedDeclaredType) returnType).getTypeArguments().size() != 1) {
+      throw new BugInCF(
+          "Unexpected type passed to AnnotatedTypes.adaptGetClassReturnTypeToReceiver%n"
+              + "getClassType=%s%nreceiverType=%s",
+          getClassType, receiverType);
+    }
+
+    final AnnotatedDeclaredType returnAdt = (AnnotatedDeclaredType) getClassType.getReturnType();
+    final List<AnnotatedTypeMirror> typeArgs = returnAdt.getTypeArguments();
+
+    // Usually, the only locations that will add annotations to the return type are getClass in stub
+    // files defaults and propagation tree annotator.  Since getClass is final they cannot come from
+    // source code.  Also, since the newBound is an erased type we have no type arguments.  So, we
+    // just copy the annotations from the bound of the declared type to the new bound.
+    final AnnotatedWildcardType classWildcardArg = (AnnotatedWildcardType) typeArgs.get(0);
+    Set<AnnotationMirror> newAnnos = AnnotationUtils.createAnnotationSet();
+    Set<AnnotationMirror> typeBoundAnnos = getTypeDeclarationBounds(newBound.getUnderlyingType());
+    Set<AnnotationMirror> wildcardBoundAnnos = classWildcardArg.getExtendsBound().getAnnotations();
+    for (AnnotationMirror typeBoundAnno : typeBoundAnnos) {
+      AnnotationMirror wildcardAnno =
+          qualHierarchy.findAnnotationInSameHierarchy(wildcardBoundAnnos, typeBoundAnno);
+      if (qualHierarchy.isSubtype(typeBoundAnno, wildcardAnno)) {
+        newAnnos.add(typeBoundAnno);
+      } else {
+        newAnnos.add(wildcardAnno);
+      }
+    }
+    newBound.replaceAnnotations(newAnnos);
+
+    classWildcardArg.setExtendsBound(newBound);
+  }
+
+  /**
+   * Return the element type of {@code expression}. This is usually the type of {@code
+   * expression.itertor().next()}. If {@code expression} is an array, it is the component type of
+   * the array.
+   *
+   * @param expression an expression whose type is an array or implements {@link Iterable}
+   * @return the type of {@code expression.itertor().next()} or if {@code expression} is an array,
+   *     the component type of the array.
+   */
+  public AnnotatedTypeMirror getIterableElementType(ExpressionTree expression) {
+    return getIterableElementType(expression, getAnnotatedType(expression));
+  }
+
+  /**
+   * Return the element type of {@code iterableType}. This is usually the type of {@code
+   * expression.itertor().next()}. If {@code expression} is an array, it is the component type of
+   * the array.
+   *
+   * @param expression an expression whose type is an array or implements {@link Iterable}
+   * @param iterableType the type of the expression
+   * @return the type of {@code expression.itertor().next()} or if {@code expression} is an array,
+   *     the component type of the array.
+   */
+  protected AnnotatedTypeMirror getIterableElementType(
+      ExpressionTree expression, AnnotatedTypeMirror iterableType) {
+    switch (iterableType.getKind()) {
+      case ARRAY:
+        return ((AnnotatedArrayType) iterableType).getComponentType();
+      case WILDCARD:
+        return getIterableElementType(
+            expression, ((AnnotatedWildcardType) iterableType).getExtendsBound().deepCopy());
+      case TYPEVAR:
+        return getIterableElementType(
+            expression, ((AnnotatedTypeVariable) iterableType).getUpperBound());
+      case DECLARED:
+        AnnotatedDeclaredType dt =
+            AnnotatedTypes.asSuper(this, iterableType, this.iterableDeclType);
+        if (dt.getTypeArguments().isEmpty()) {
+          TypeElement e = ElementUtils.getTypeElement(processingEnv, Object.class);
+          return getAnnotatedType(e);
+        } else {
+          return dt.getTypeArguments().get(0);
+        }
+
+        // TODO: Properly desugar Iterator.next(), which is needed if an annotated JDK has
+        // annotations on Iterator#next.
+        // The below doesn't work because methodFromUse() assumes that the expression tree
+        // matches the method element.
+        // TypeElement iteratorElement =
+        //         ElementUtils.getTypeElement(processingEnv, Iterator.class);
+        // AnnotatedTypeMirror iteratorType =
+        //         AnnotatedTypeMirror.createType(iteratorElement.asType(), this, false);
+        // Map<TypeVariable, AnnotatedTypeMirror> mapping = new HashMap<>();
+        // mapping.put(
+        //         (TypeVariable) iteratorElement.getTypeParameters().get(0).asType(),
+        //          typeArg);
+        // iteratorType = typeVarSubstitutor.substitute(mapping, iteratorType);
+        // ExecutableElement next =
+        //         TreeUtils.getMethod("java.util.Iterator", "next", 0, processingEnv);
+        // ParameterizedExecutableType m = methodFromUse(expression, next, iteratorType);
+        // return m.executableType.getReturnType();
+      default:
+        throw new BugInCF(
+            "AnnotatedTypeFactory.getIterableElementType: not iterable type: " + iterableType);
+    }
+  }
+
+  /**
+   * Determines the type of the invoked constructor based on the passed new class tree.
+   *
+   * <p>The returned method type has all type variables resolved, whether based on receiver type,
+   * passed type parameters if any, and constructor invocation parameter.
+   *
+   * <p>Subclasses may override this method to customize inference of types or qualifiers based on
+   * constructor invocation parameters.
+   *
+   * <p>As an implementation detail, this method depends on {@link AnnotatedTypes#asMemberOf(Types,
+   * AnnotatedTypeFactory, AnnotatedTypeMirror, Element)}, and customization based on receiver type
+   * should be in accordance with its specification.
+   *
+   * <p>The return type is a pair of the type of the invoked constructor and the (inferred) type
+   * arguments. Note that neither the explicitly passed nor the inferred type arguments are
+   * guaranteed to be subtypes of the corresponding upper bounds. See method {@link
+   * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of type
+   * argument well-formedness.
+   *
+   * <p>Note that "this" and "super" constructor invocations are handled by method {@link
+   * #methodFromUse}. This method only handles constructor invocations in a "new" expression.
+   *
+   * @param tree the constructor invocation tree
+   * @return the annotated type of the invoked constructor (as an executable type) and the
+   *     (inferred) type arguments
+   */
+  public ParameterizedExecutableType constructorFromUse(NewClassTree tree) {
+    AnnotatedTypeMirror type = fromNewClass(tree);
+    addComputedTypeAnnotations(tree, type);
+
+    ExecutableElement ctor = TreeUtils.constructor(tree);
+    AnnotatedExecutableType con = getAnnotatedType(ctor); // get unsubstituted type
+    if (TreeUtils.hasSyntheticArgument(tree)) {
+      AnnotatedExecutableType t =
+          (AnnotatedExecutableType) getAnnotatedType(((JCNewClass) tree).constructor);
+      List<AnnotatedTypeMirror> p = new ArrayList<>(con.getParameterTypes().size() + 1);
+      p.add(t.getParameterTypes().get(0));
+      p.addAll(1, con.getParameterTypes());
+      t.setParameterTypes(p);
+      con = t;
+    }
+
+    constructorFromUsePreSubstitution(tree, con);
+
+    con = AnnotatedTypes.asMemberOf(types, this, type, ctor, con);
+
+    Map<TypeVariable, AnnotatedTypeMirror> typeVarMapping =
+        AnnotatedTypes.findTypeArguments(processingEnv, this, tree, ctor, con);
+
+    List<AnnotatedTypeMirror> typeargs = new ArrayList<>(con.getTypeVariables().size());
+    if (typeVarMapping.isEmpty()) {
+      typeargs = Collections.emptyList();
+    } else {
+      typeargs =
+          CollectionsPlume.mapList(
+              (AnnotatedTypeVariable tv) -> typeVarMapping.get(tv.getUnderlyingType()),
+              con.getTypeVariables());
+      con = (AnnotatedExecutableType) typeVarSubstitutor.substitute(typeVarMapping, con);
+    }
+
+    return new ParameterizedExecutableType(con, typeargs);
+  }
+
+  /**
+   * A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the
+   * declared constructor type before type variable substitution.
+   *
+   * @param tree a NewClassTree from constructorFromUse()
+   * @param type declared method type before type variable substitution
+   */
+  protected void constructorFromUsePreSubstitution(
+      NewClassTree tree, AnnotatedExecutableType type) {}
+
+  /**
+   * Returns the return type of the method {@code m}.
+   *
+   * @param m tree of a method declaration
+   * @return the return type of the method
+   */
+  public AnnotatedTypeMirror getMethodReturnType(MethodTree m) {
+    AnnotatedExecutableType methodType = getAnnotatedType(m);
+    AnnotatedTypeMirror ret = methodType.getReturnType();
+    return ret;
+  }
+
+  /**
+   * Returns the return type of the method {@code m} at the return statement {@code r}. This
+   * implementation just calls {@link #getMethodReturnType(MethodTree)}, but subclasses may override
+   * this method to change the type based on the return statement.
+   *
+   * @param m tree of a method declaration
+   * @param r a return statement within method {@code m}
+   * @return the return type of the method {@code m} at the return statement {@code r}
+   */
+  public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) {
+    return getMethodReturnType(m);
+  }
+
+  /**
+   * Creates an AnnotatedDeclaredType for a NewClassTree. Only adds explicit annotations, unless
+   * newClassTree has a diamond operator. In that case, the annotations on the type arguments are
+   * inferred using the assignment context and contain defaults.
+   *
+   * <p>Also, fully annotates the enclosing type of the returned declared type.
+   *
+   * <p>(Subclass beside {@link GenericAnnotatedTypeFactory} should not override this method.)
+   *
+   * @param newClassTree NewClassTree
+   * @return AnnotatedDeclaredType
+   */
+  public AnnotatedDeclaredType fromNewClass(NewClassTree newClassTree) {
+
+    AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType) getReceiverType(newClassTree);
+
+    // Diamond trees that are not anonymous classes.
+    if (TreeUtils.isDiamondTree(newClassTree) && newClassTree.getClassBody() == null) {
+      AnnotatedDeclaredType type =
+          (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(newClassTree), false);
+      if (((com.sun.tools.javac.code.Type) type.underlyingType)
+          .tsym
+          .getTypeParameters()
+          .nonEmpty()) {
+        Pair<Tree, AnnotatedTypeMirror> ctx = this.visitorState.getAssignmentContext();
+        if (ctx != null) {
+          AnnotatedTypeMirror ctxtype = ctx.second;
+          fromNewClassContextHelper(type, ctxtype);
+        } else {
+          TreePath p = getPath(newClassTree);
+          AnnotatedTypeMirror ctxtype = TypeArgInferenceUtil.assignedTo(this, p);
+          if (ctxtype != null) {
+            fromNewClassContextHelper(type, ctxtype);
+          } else {
+            // give up trying and set to raw.
+            type.setWasRaw();
+          }
+        }
+      }
+      AnnotatedDeclaredType fromTypeTree =
+          (AnnotatedDeclaredType) TypeFromTree.fromTypeTree(this, newClassTree.getIdentifier());
+      type.replaceAnnotations(fromTypeTree.getAnnotations());
+      type.setEnclosingType(enclosingType);
+      return type;
+    } else if (newClassTree.getClassBody() != null) {
+      AnnotatedDeclaredType type =
+          (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(newClassTree), false);
+      // If newClassTree creates an anonymous class, then annotations in this location:
+      //   new @HERE Class() {}
+      // are on not on the identifier newClassTree, but rather on the modifier newClassTree.
+      List<? extends AnnotationTree> annos =
+          newClassTree.getClassBody().getModifiers().getAnnotations();
+      type.addAnnotations(TreeUtils.annotationsFromTypeAnnotationTrees(annos));
+      type.setEnclosingType(enclosingType);
+      return type;
+    } else {
+      // If newClassTree does not create anonymous class,
+      // newClassTree.getIdentifier includes the explicit annotations in this location:
+      //   new @HERE Class()
+      AnnotatedDeclaredType type =
+          (AnnotatedDeclaredType) TypeFromTree.fromTypeTree(this, newClassTree.getIdentifier());
+      type.setEnclosingType(enclosingType);
+      return type;
+    }
+  }
+
+  // This method extracts the ugly hacky parts.
+  // This method should be rewritten and in particular diamonds should be
+  // implemented cleanly.
+  // See Issue 289.
+  private void fromNewClassContextHelper(AnnotatedDeclaredType type, AnnotatedTypeMirror ctxtype) {
+    switch (ctxtype.getKind()) {
+      case DECLARED:
+        AnnotatedDeclaredType adctx = (AnnotatedDeclaredType) ctxtype;
+
+        if (type.getTypeArguments().size() == adctx.getTypeArguments().size()) {
+          // Try to simply take the type arguments from LHS.
+          List<AnnotatedTypeMirror> oldArgs = type.getTypeArguments();
+          List<AnnotatedTypeMirror> newArgs = adctx.getTypeArguments();
+          for (int i = 0; i < type.getTypeArguments().size(); ++i) {
+            if (!types.isSubtype(newArgs.get(i).underlyingType, oldArgs.get(i).underlyingType)) {
+              // One of the underlying types doesn't match. Give up.
+              return;
+            }
+          }
+
+          type.setTypeArguments(newArgs);
+
+          /* It would be nice to call isSubtype for a basic sanity check.
+           * However, the type might not have been completely initialized yet,
+           * so isSubtype might fail.
+           *
+          if (!typeHierarchy.isSubtype(type, ctxtype)) {
+              // Simply taking the newArgs didn't result in a valid subtype.
+              // Give up and simply use the inferred types.
+              type.setTypeArguments(oldArgs);
+          }
+          */
+        } else {
+          // TODO: Find a way to determine annotated type arguments.
+          // Look at what Attr and Resolve are doing and rework this whole method.
+        }
+        break;
+
+      case ARRAY:
+        // This new class is in the initializer of an array.
+        // The array being created can't have a generic component type, so nothing to be done.
+        break;
+      case TYPEVAR:
+        // TODO: this should NOT be necessary.
+        // org.checkerframework.dataflow.cfg.node.MethodAccessNode.MethodAccessNode(ExpressionTree,
+        // Node)
+        // Uses an ExecutableElement, which did not substitute type variables.
+        break;
+      case WILDCARD:
+        // TODO: look at bounds of wildcard and see whether we can improve.
+        break;
+      default:
+        if (ctxtype.getKind().isPrimitive()) {
+          // See Issue 438. Ignore primitive types for diamond inference - a primitive
+          // type is never a suitable context anyway.
+        } else {
+          throw new BugInCF(
+              "AnnotatedTypeFactory.fromNewClassContextHelper: unexpected context: "
+                  + ctxtype
+                  + " ("
+                  + ctxtype.getKind()
+                  + ")");
+        }
+    }
+  }
+
+  /**
+   * Returns the annotated boxed type of the given primitive type. The returned type would only have
+   * the annotations on the given type.
+   *
+   * <p>Subclasses may override this method safely to override this behavior.
+   *
+   * @param type the primitive type
+   * @return the boxed declared type of the passed primitive type
+   */
+  public AnnotatedDeclaredType getBoxedType(AnnotatedPrimitiveType type) {
+    TypeElement typeElt = types.boxedClass(type.getUnderlyingType());
+    AnnotatedDeclaredType dt = fromElement(typeElt);
+    dt.addAnnotations(type.getAnnotations());
+    return dt;
+  }
+
+  /**
+   * Return a primitive type: either the argument, or the result of unboxing it (which might affect
+   * its annotations).
+   *
+   * <p>Subclasses should override {@link #getUnboxedType} rather than this method.
+   *
+   * @param type a type: a primitive or boxed primitive
+   * @return the unboxed variant of the type
+   */
+  public final AnnotatedPrimitiveType applyUnboxing(AnnotatedTypeMirror type) {
+    TypeMirror underlying = type.getUnderlyingType();
+    if (TypesUtils.isPrimitive(underlying)) {
+      return (AnnotatedPrimitiveType) type;
+    } else if (TypesUtils.isBoxedPrimitive(underlying)) {
+      return getUnboxedType((AnnotatedDeclaredType) type);
+    } else {
+      throw new BugInCF("Bad argument to applyUnboxing: " + type);
+    }
+  }
+
+  /**
+   * Returns the annotated primitive type of the given declared type if it is a boxed declared type.
+   * Otherwise, it throws <i>IllegalArgumentException</i> exception.
+   *
+   * <p>In the {@code AnnotatedTypeFactory} implementation, the returned type has the same primary
+   * annotations as the given type. Subclasses may override this behavior.
+   *
+   * @param type the declared type
+   * @return the unboxed primitive type
+   * @throws IllegalArgumentException if the type given has no unbox conversion
+   */
+  public AnnotatedPrimitiveType getUnboxedType(AnnotatedDeclaredType type)
+      throws IllegalArgumentException {
+    PrimitiveType primitiveType = types.unboxedType(type.getUnderlyingType());
+    AnnotatedPrimitiveType pt =
+        (AnnotatedPrimitiveType) AnnotatedTypeMirror.createType(primitiveType, this, false);
+    pt.addAnnotations(type.getAnnotations());
+    return pt;
+  }
+
+  /**
+   * Returns AnnotatedDeclaredType with underlying type String and annotations copied from type.
+   * Subclasses may change the annotations.
+   *
+   * @param type type to convert to String
+   * @return AnnotatedTypeMirror that results from converting type to a String type
+   */
+  // TODO: Test that this is called in all the correct locations
+  // See Issue #715
+  // https://github.com/typetools/checker-framework/issues/715
+  public AnnotatedDeclaredType getStringType(AnnotatedTypeMirror type) {
+    TypeMirror stringTypeMirror = TypesUtils.typeFromClass(String.class, types, elements);
+    AnnotatedDeclaredType stringATM =
+        (AnnotatedDeclaredType)
+            AnnotatedTypeMirror.createType(stringTypeMirror, this, type.isDeclaration());
+    stringATM.addAnnotations(type.getEffectiveAnnotations());
+    return stringATM;
+  }
+
+  /**
+   * Returns a widened type if applicable, otherwise returns its first argument.
+   *
+   * <p>Subclasses should override {@link #getWidenedAnnotations} rather than this method.
+   *
+   * @param exprType type to possibly widen
+   * @param widenedType type to possibly widen to; its annotations are ignored
+   * @return if widening is applicable, the result of converting {@code type} to the underlying type
+   *     of {@code widenedType}; otherwise {@code type}
+   */
+  public final AnnotatedTypeMirror getWidenedType(
+      AnnotatedTypeMirror exprType, AnnotatedTypeMirror widenedType) {
+    TypeKind exprKind = exprType.getKind();
+    TypeKind widenedKind = widenedType.getKind();
+
+    if (!TypeKindUtils.isNumeric(widenedKind)) {
+      // The target type is not a numeric primitive, so primitive widening is not applicable.
+      return exprType;
+    }
+
+    AnnotatedPrimitiveType exprPrimitiveType;
+    if (TypeKindUtils.isNumeric(exprKind)) {
+      exprPrimitiveType = (AnnotatedPrimitiveType) exprType;
+    } else if (TypesUtils.isNumericBoxed(exprType.getUnderlyingType())) {
+      exprPrimitiveType = getUnboxedType((AnnotatedDeclaredType) exprType);
+    } else {
+      return exprType;
+    }
+
+    switch (TypeKindUtils.getPrimitiveConversionKind(
+        exprPrimitiveType.getKind(), widenedType.getKind())) {
+      case WIDENING:
+        return getWidenedPrimitive(exprPrimitiveType, widenedType.getUnderlyingType());
+      case NARROWING:
+        return getNarrowedPrimitive(exprPrimitiveType, widenedType.getUnderlyingType());
+      case SAME:
+        return exprType;
+      default:
+        throw new Error("unhandled PrimitiveConversionKind");
+    }
+  }
+
+  /**
+   * Applies widening if applicable, otherwise returns its first argument.
+   *
+   * <p>Subclasses should override {@link #getWidenedAnnotations} rather than this method.
+   *
+   * @param exprAnnos annotations to possibly widen
+   * @param exprTypeMirror type to possibly widen
+   * @param widenedType type to possibly widen to; its annotations are ignored
+   * @return if widening is applicable, the result of converting {@code type} to the underlying type
+   *     of {@code widenedType}; otherwise {@code type}
+   */
+  public final AnnotatedTypeMirror getWidenedType(
+      Set<AnnotationMirror> exprAnnos, TypeMirror exprTypeMirror, AnnotatedTypeMirror widenedType) {
+    AnnotatedTypeMirror exprType = toAnnotatedType(exprTypeMirror, false);
+    exprType.replaceAnnotations(exprAnnos);
+    return getWidenedType(exprType, widenedType);
+  }
+
+  /**
+   * Returns an AnnotatedPrimitiveType with underlying type {@code widenedTypeMirror} and with
+   * annotations copied or adapted from {@code type}.
+   *
+   * @param type type to widen; a primitive or boxed primitive
+   * @param widenedTypeMirror underlying type for the returned type mirror; a primitive or boxed
+   *     primitive (same boxing as {@code type})
+   * @return result of converting {@code type} to {@code widenedTypeMirror}
+   */
+  private AnnotatedPrimitiveType getWidenedPrimitive(
+      AnnotatedPrimitiveType type, TypeMirror widenedTypeMirror) {
+    AnnotatedPrimitiveType result =
+        (AnnotatedPrimitiveType)
+            AnnotatedTypeMirror.createType(widenedTypeMirror, this, type.isDeclaration());
+    result.addAnnotations(
+        getWidenedAnnotations(type.getAnnotations(), type.getKind(), result.getKind()));
+    return result;
+  }
+
+  /**
+   * Returns annotations applicable to type {@code narrowedTypeKind}, that are copied or adapted
+   * from {@code annos}.
+   *
+   * @param annos annotations to narrow, from a primitive or boxed primitive
+   * @param typeKind primitive type to narrow
+   * @param narrowedTypeKind target for the returned annotations; a primitive type that is narrower
+   *     than {@code typeKind} (in the sense of JLS 5.1.3).
+   * @return result of converting {@code annos} from {@code typeKind} to {@code narrowedTypeKind}
+   */
+  public Set<AnnotationMirror> getNarrowedAnnotations(
+      Set<AnnotationMirror> annos, TypeKind typeKind, TypeKind narrowedTypeKind) {
+    return annos;
+  }
+
+  /**
+   * Returns annotations applicable to type {@code widenedTypeKind}, that are copied or adapted from
+   * {@code annos}.
+   *
+   * @param annos annotations to widen, from a primitive or boxed primitive
+   * @param typeKind primitive type to widen
+   * @param widenedTypeKind target for the returned annotations; a primitive type that is wider than
+   *     {@code typeKind} (in the sense of JLS 5.1.2)
+   * @return result of converting {@code annos} from {@code typeKind} to {@code widenedTypeKind}
+   */
+  public Set<AnnotationMirror> getWidenedAnnotations(
+      Set<AnnotationMirror> annos, TypeKind typeKind, TypeKind widenedTypeKind) {
+    return annos;
+  }
+
+  /**
+   * Returns the types of the two arguments to the BinaryTree, accounting for widening and unboxing
+   * if applicable.
+   *
+   * @param node a binary tree
+   * @return the types of the two arguments
+   */
+  public Pair<AnnotatedTypeMirror, AnnotatedTypeMirror> binaryTreeArgTypes(BinaryTree node) {
+    return binaryTreeArgTypes(
+        getAnnotatedType(node.getLeftOperand()), getAnnotatedType(node.getRightOperand()));
+  }
+
+  /**
+   * Returns the types of the two arguments to the CompoundAssignmentTree, accounting for widening
+   * and unboxing if applicable.
+   *
+   * @param node a compound assignment tree
+   * @return the types of the two arguments
+   */
+  public Pair<AnnotatedTypeMirror, AnnotatedTypeMirror> compoundAssignmentTreeArgTypes(
+      CompoundAssignmentTree node) {
+    return binaryTreeArgTypes(
+        getAnnotatedType(node.getVariable()), getAnnotatedType(node.getExpression()));
+  }
+
+  /**
+   * Returns the types of the two arguments to a binarya operation, accounting for widening and
+   * unboxing if applicable.
+   *
+   * @param left the type of the left argument of a binary operation
+   * @param right the type of the right argument of a binary operation
+   * @return the types of the two arguments
+   */
+  public Pair<AnnotatedTypeMirror, AnnotatedTypeMirror> binaryTreeArgTypes(
+      AnnotatedTypeMirror left, AnnotatedTypeMirror right) {
+    TypeKind resultTypeKind =
+        TypeKindUtils.widenedNumericType(left.getUnderlyingType(), right.getUnderlyingType());
+    if (TypeKindUtils.isNumeric(resultTypeKind)) {
+      TypeMirror resultTypeMirror = types.getPrimitiveType(resultTypeKind);
+      AnnotatedPrimitiveType leftUnboxed = applyUnboxing(left);
+      AnnotatedPrimitiveType rightUnboxed = applyUnboxing(right);
+      AnnotatedPrimitiveType leftWidened =
+          (leftUnboxed.getKind() == resultTypeKind
+              ? leftUnboxed
+              : getWidenedPrimitive(leftUnboxed, resultTypeMirror));
+      AnnotatedPrimitiveType rightWidened =
+          (rightUnboxed.getKind() == resultTypeKind
+              ? rightUnboxed
+              : getWidenedPrimitive(rightUnboxed, resultTypeMirror));
+      return Pair.of(leftWidened, rightWidened);
+    } else {
+      return Pair.of(left, right);
+    }
+  }
+
+  /**
+   * Returns AnnotatedPrimitiveType with underlying type {@code narrowedTypeMirror} and with
+   * annotations copied or adapted from {@code type}.
+   *
+   * <p>Currently this method is called only for primitives that are narrowed at assignments from
+   * literal ints, for example, {@code byte b = 1;}. All other narrowing conversions happen at
+   * typecasts.
+   *
+   * @param type type to narrow
+   * @param narrowedTypeMirror underlying type for the returned type mirror
+   * @return result of converting {@code type} to {@code narrowedTypeMirror}
+   */
+  public AnnotatedPrimitiveType getNarrowedPrimitive(
+      AnnotatedPrimitiveType type, TypeMirror narrowedTypeMirror) {
+    AnnotatedPrimitiveType narrowed =
+        (AnnotatedPrimitiveType)
+            AnnotatedTypeMirror.createType(narrowedTypeMirror, this, type.isDeclaration());
+    narrowed.addAnnotations(type.getAnnotations());
+    return narrowed;
+  }
+
+  /**
+   * Returns the VisitorState instance used by the factory to infer types.
+   *
+   * @return the VisitorState instance used by the factory to infer types
+   */
+  public VisitorState getVisitorState() {
+    return this.visitorState;
+  }
+
+  // **********************************************************************
+  // random methods wrapping #getAnnotatedType(Tree) and #fromElement(Tree)
+  // with appropriate casts to reduce casts on the client side
+  // **********************************************************************
+
+  /**
+   * See {@link #getAnnotatedType(Tree)}.
+   *
+   * @see #getAnnotatedType(Tree)
+   */
+  public final AnnotatedDeclaredType getAnnotatedType(ClassTree tree) {
+    return (AnnotatedDeclaredType) getAnnotatedType((Tree) tree);
+  }
+
+  /**
+   * See {@link #getAnnotatedType(Tree)}.
+   *
+   * @see #getAnnotatedType(Tree)
+   */
+  public final AnnotatedDeclaredType getAnnotatedType(NewClassTree tree) {
+    return (AnnotatedDeclaredType) getAnnotatedType((Tree) tree);
+  }
+
+  /**
+   * See {@link #getAnnotatedType(Tree)}.
+   *
+   * @see #getAnnotatedType(Tree)
+   */
+  public final AnnotatedArrayType getAnnotatedType(NewArrayTree tree) {
+    return (AnnotatedArrayType) getAnnotatedType((Tree) tree);
+  }
+
+  /**
+   * See {@link #getAnnotatedType(Tree)}.
+   *
+   * @see #getAnnotatedType(Tree)
+   */
+  public final AnnotatedExecutableType getAnnotatedType(MethodTree tree) {
+    return (AnnotatedExecutableType) getAnnotatedType((Tree) tree);
+  }
+
+  /**
+   * See {@link #getAnnotatedType(Element)}.
+   *
+   * @see #getAnnotatedType(Element)
+   */
+  public final AnnotatedDeclaredType getAnnotatedType(TypeElement elt) {
+    return (AnnotatedDeclaredType) getAnnotatedType((Element) elt);
+  }
+
+  /**
+   * See {@link #getAnnotatedType(Element)}.
+   *
+   * @see #getAnnotatedType(Element)
+   */
+  public final AnnotatedExecutableType getAnnotatedType(ExecutableElement elt) {
+    return (AnnotatedExecutableType) getAnnotatedType((Element) elt);
+  }
+
+  /**
+   * See {@link #fromElement(Element)}.
+   *
+   * @see #fromElement(Element)
+   */
+  public final AnnotatedDeclaredType fromElement(TypeElement elt) {
+    return (AnnotatedDeclaredType) fromElement((Element) elt);
+  }
+
+  /**
+   * See {@link #fromElement(Element)}.
+   *
+   * @see #fromElement(Element)
+   */
+  public final AnnotatedExecutableType fromElement(ExecutableElement elt) {
+    return (AnnotatedExecutableType) fromElement((Element) elt);
+  }
+
+  // **********************************************************************
+  // Helper methods for this classes
+  // **********************************************************************
+
+  /**
+   * Determines whether the given annotation is a part of the type system under which this type
+   * factory operates. Null is never a supported qualifier; the parameter is nullable to allow the
+   * result of canonicalAnnotation to be passed in directly.
+   *
+   * @param a any annotation
+   * @return true if that annotation is part of the type system under which this type factory
+   *     operates, false otherwise
+   */
+  public boolean isSupportedQualifier(@Nullable AnnotationMirror a) {
+    if (a == null) {
+      return false;
+    }
+    return isSupportedQualifier(AnnotationUtils.annotationName(a));
+  }
+
+  /**
+   * Determines whether the given class is a part of the type system under which this type factory
+   * operates.
+   *
+   * @param clazz annotation class
+   * @return true if that class is a type qualifier in the type system under which this type factory
+   *     operates, false otherwise
+   */
+  public boolean isSupportedQualifier(Class<? extends Annotation> clazz) {
+    return getSupportedTypeQualifiers().contains(clazz);
+  }
+
+  /**
+   * Determines whether the given class name is a part of the type system under which this type
+   * factory operates.
+   *
+   * @param className fully-qualified annotation class name
+   * @return true if that class name is a type qualifier in the type system under which this type
+   *     factory operates, false otherwise
+   */
+  public boolean isSupportedQualifier(String className) {
+    return getSupportedTypeQualifierNames().contains(className);
+  }
+
+  /**
+   * Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code type}
+   * that will be used by the Checker Framework in the alias's place.
+   *
+   * <p>By specifying the alias/canonical relationship using this method, the elements of the alias
+   * are not preserved when the canonical annotation to use is constructed from the alias. If you
+   * want the elements to be copied over as well, use {@link #addAliasedTypeAnnotation(Class, Class,
+   * boolean, String...)}.
+   *
+   * @param aliasClass the class of the aliased annotation
+   * @param type the canonical annotation
+   * @deprecated use {@code addAliasedTypeAnnotation}
+   */
+  @Deprecated // 2020-12-15
+  protected void addAliasedAnnotation(Class<?> aliasClass, AnnotationMirror type) {
+    addAliasedTypeAnnotation(aliasClass, type);
+  }
+
+  /**
+   * Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code
+   * canonicalAnno} that will be used by the Checker Framework in the alias's place.
+   *
+   * <p>By specifying the alias/canonical relationship using this method, the elements of the alias
+   * are not preserved when the canonical annotation to use is constructed from the alias. If you
+   * want the elements to be copied over as well, use {@link #addAliasedTypeAnnotation(Class, Class,
+   * boolean, String...)}.
+   *
+   * @param aliasClass the class of the aliased annotation
+   * @param canonicalAnno the canonical annotation
+   */
+  protected void addAliasedTypeAnnotation(Class<?> aliasClass, AnnotationMirror canonicalAnno) {
+    if (getSupportedTypeQualifiers().contains(aliasClass)) {
+      throw new BugInCF(
+          "AnnotatedTypeFactory: alias %s should not be in type hierarchy for %s",
+          aliasClass, this.getClass().getSimpleName());
+    }
+    addAliasedTypeAnnotation(aliasClass.getCanonicalName(), canonicalAnno);
+  }
+
+  /**
+   * Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias for
+   * the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework in
+   * the alias's place.
+   *
+   * <p>Use this method if the alias class is not necessarily on the classpath at Checker Framework
+   * compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, AnnotationMirror)}
+   * which prevents the possibility of a typo in the class name.
+   *
+   * @param aliasName the canonical name of the aliased annotation
+   * @param canonicalAnno the canonical annotation
+   * @deprecated use {@link #addAliasedTypeAnnotation}
+   */
+  // aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the
+  // name of an external annotation is a canoncal name.
+  @Deprecated // 2020-12-15
+  protected void addAliasedAnnotation(
+      @FullyQualifiedName String aliasName, AnnotationMirror canonicalAnno) {
+    addAliasedTypeAnnotation(aliasName, canonicalAnno);
+  }
+
+  /**
+   * Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias for
+   * the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework in
+   * the alias's place.
+   *
+   * <p>Use this method if the alias class is not necessarily on the classpath at Checker Framework
+   * compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, AnnotationMirror)}
+   * which prevents the possibility of a typo in the class name.
+   *
+   * @param aliasName the canonical name of the aliased annotation
+   * @param canonicalAnno the canonical annotation
+   */
+  // aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the
+  // name of an external annotation is a canoncal name.
+  protected void addAliasedTypeAnnotation(
+      @FullyQualifiedName String aliasName, AnnotationMirror canonicalAnno) {
+
+    aliases.put(aliasName, new Alias(aliasName, canonicalAnno, false, null, null));
+  }
+
+  /**
+   * Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code
+   * canonicalAnno} that will be used by the Checker Framework in the alias's place.
+   *
+   * <p>You may specify the copyElements flag to indicate whether you want the elements of the alias
+   * to be copied over when the canonical annotation is constructed as a copy of {@code
+   * canonicalAnno}. Be careful that the framework will try to copy the elements by name matching,
+   * so make sure that names and types of the elements to be copied over are exactly the same as the
+   * ones in the canonical annotation. Otherwise, an 'Couldn't find element in annotation' error is
+   * raised.
+   *
+   * <p>To facilitate the cases where some of the elements are ignored on purpose when constructing
+   * the canonical annotation, this method also provides a varargs {@code ignorableElements} for you
+   * to explicitly specify the ignoring rules. For example, {@code
+   * org.checkerframework.checker.index.qual.IndexFor} is an alias of {@code
+   * org.checkerframework.checker.index.qual.NonNegative}, but the element "value" of
+   * {@code @IndexFor} should be ignored when constructing {@code @NonNegative}. In the cases where
+   * all elements are ignored, we can simply use {@link #addAliasedTypeAnnotation(Class,
+   * AnnotationMirror)} instead.
+   *
+   * @param aliasClass the class of the aliased annotation
+   * @param canonical the canonical annotation
+   * @param copyElements a flag that indicates whether you want to copy the elements over when
+   *     getting the alias from the canonical annotation
+   * @param ignorableElements a list of elements that can be safely dropped when the elements are
+   *     being copied over
+   * @deprecated use {@code addAliasedTypeAnnotation}
+   */
+  @Deprecated // 2020-12-15
+  protected void addAliasedAnnotation(
+      Class<?> aliasClass, Class<?> canonical, boolean copyElements, String... ignorableElements) {
+    addAliasedTypeAnnotation(aliasClass, canonical, copyElements, ignorableElements);
+  }
+
+  /**
+   * Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code
+   * canonicalClass} that will be used by the Checker Framework in the alias's place.
+   *
+   * <p>You may specify the copyElements flag to indicate whether you want the elements of the alias
+   * to be copied over when the canonical annotation is constructed as a copy of {@code
+   * canonicalClass}. Be careful that the framework will try to copy the elements by name matching,
+   * so make sure that names and types of the elements to be copied over are exactly the same as the
+   * ones in the canonical annotation. Otherwise, an 'Couldn't find element in annotation' error is
+   * raised.
+   *
+   * <p>To facilitate the cases where some of the elements are ignored on purpose when constructing
+   * the canonical annotation, this method also provides a varargs {@code ignorableElements} for you
+   * to explicitly specify the ignoring rules. For example, {@code
+   * org.checkerframework.checker.index.qual.IndexFor} is an alias of {@code
+   * org.checkerframework.checker.index.qual.NonNegative}, but the element "value" of
+   * {@code @IndexFor} should be ignored when constructing {@code @NonNegative}. In the cases where
+   * all elements are ignored, we can simply use {@link #addAliasedTypeAnnotation(Class,
+   * AnnotationMirror)} instead.
+   *
+   * @param aliasClass the class of the aliased annotation
+   * @param canonicalClass the class of the canonical annotation
+   * @param copyElements a flag that indicates whether you want to copy the elements over when
+   *     getting the alias from the canonical annotation
+   * @param ignorableElements a list of elements that can be safely dropped when the elements are
+   *     being copied over
+   */
+  protected void addAliasedTypeAnnotation(
+      Class<?> aliasClass,
+      Class<?> canonicalClass,
+      boolean copyElements,
+      String... ignorableElements) {
+    if (getSupportedTypeQualifiers().contains(aliasClass)) {
+      throw new BugInCF(
+          "AnnotatedTypeFactory: alias %s should not be in type hierarchy for %s",
+          aliasClass, this.getClass().getSimpleName());
+    }
+    addAliasedTypeAnnotation(
+        aliasClass.getCanonicalName(), canonicalClass, copyElements, ignorableElements);
+  }
+
+  /**
+   * Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias for
+   * the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework in
+   * the alias's place.
+   *
+   * <p>Use this method if the alias class is not necessarily on the classpath at Checker Framework
+   * compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, Class, boolean,
+   * String[])} which prevents the possibility of a typo in the class name.
+   *
+   * @param aliasName the canonical name of the aliased class
+   * @param canonicalAnno the canonical annotation
+   * @param copyElements a flag that indicates whether we want to copy the elements over when
+   *     getting the alias from the canonical annotation
+   * @param ignorableElements a list of elements that can be safely dropped when the elements are
+   *     being copied over
+   */
+  // aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the
+  // name of an external annotation is a canoncal name.
+  protected void addAliasedTypeAnnotation(
+      @FullyQualifiedName String aliasName,
+      Class<?> canonicalAnno,
+      boolean copyElements,
+      String... ignorableElements) {
+    // The copyElements argument disambiguates overloading.
+    if (!copyElements) {
+      throw new BugInCF("Do not call with false");
+    }
+    aliases.put(
+        aliasName,
+        new Alias(
+            aliasName, null, copyElements, canonicalAnno.getCanonicalName(), ignorableElements));
+  }
+
+  /**
+   * Returns the canonical annotation for the passed annotation. Returns null if the passed
+   * annotation is not an alias of a canonical one in the framework.
+   *
+   * <p>A canonical annotation is the internal annotation that will be used by the Checker Framework
+   * in the aliased annotation's place.
+   *
+   * @param a the qualifier to check for an alias
+   * @return the canonical annotation, or null if none exists
+   */
+  public @Nullable AnnotationMirror canonicalAnnotation(AnnotationMirror a) {
+    TypeElement elem = (TypeElement) a.getAnnotationType().asElement();
+    String qualName = elem.getQualifiedName().toString();
+    Alias alias = aliases.get(qualName);
+    if (alias == null) {
+      return null;
+    }
+    if (alias.copyElements) {
+      AnnotationBuilder builder = new AnnotationBuilder(processingEnv, alias.canonicalName);
+      builder.copyElementValuesFromAnnotation(a, alias.ignorableElements);
+      return builder.build();
+    } else {
+      return alias.canonical;
+    }
+  }
+
+  /**
+   * Add the annotation {@code alias} as an alias for the declaration annotation {@code annotation},
+   * where the annotation mirror {@code annotationToUse} will be used instead. If multiple calls are
+   * made with the same {@code annotation}, then the {@code annotationToUse} must be the same.
+   *
+   * <p>The point of {@code annotationToUse} is that it may include elements/fields.
+   */
+  protected void addAliasedDeclAnnotation(
+      Class<? extends Annotation> alias,
+      Class<? extends Annotation> annotation,
+      AnnotationMirror annotationToUse) {
+    Pair<AnnotationMirror, Set<Class<? extends Annotation>>> pair = declAliases.get(annotation);
+    if (pair != null) {
+      if (!AnnotationUtils.areSame(annotationToUse, pair.first)) {
+        throw new BugInCF("annotationToUse should be the same: %s %s", pair.first, annotationToUse);
+      }
+    } else {
+      pair = Pair.of(annotationToUse, new HashSet<>());
+      declAliases.put(annotation, pair);
+    }
+    Set<Class<? extends Annotation>> aliases = pair.second;
+    aliases.add(alias);
+  }
+
+  /**
+   * Adds the annotation {@code annotation} in the set of declaration annotations that should be
+   * inherited. A declaration annotation will be inherited if it is in this list, or if it has the
+   * meta-annotation @InheritedAnnotation. The meta-annotation @InheritedAnnotation should be used
+   * instead of this method, if possible.
+   */
+  protected void addInheritedAnnotation(AnnotationMirror annotation) {
+    inheritedAnnotations.add(annotation);
+  }
+
+  /**
+   * A convenience method that converts a {@link TypeMirror} to an empty {@link AnnotatedTypeMirror}
+   * using {@link AnnotatedTypeMirror#createType}.
+   *
+   * @param t the {@link TypeMirror}
+   * @param declaration true if the result should be marked as a type declaration
+   * @return an {@link AnnotatedTypeMirror} that has {@code t} as its underlying type
+   */
+  protected final AnnotatedTypeMirror toAnnotatedType(TypeMirror t, boolean declaration) {
+    return AnnotatedTypeMirror.createType(t, this, declaration);
+  }
+
+  /**
+   * Determines an empty annotated type of the given tree. In other words, finds the {@link
+   * TypeMirror} for the tree and converts that into an {@link AnnotatedTypeMirror}, but does not
+   * add any annotations to the result.
+   *
+   * <p>Most users will want to use {@link #getAnnotatedType(Tree)} instead; this method is mostly
+   * for internal use.
+   *
+   * @param node the tree to analyze
+   * @return the type of {@code node}, without any annotations
+   */
+  protected final AnnotatedTypeMirror type(Tree node) {
+    boolean isDeclaration = TreeUtils.isTypeDeclaration(node);
+
+    // Attempt to obtain the type via JCTree.
+    if (TreeUtils.typeOf(node) != null) {
+      AnnotatedTypeMirror result = toAnnotatedType(TreeUtils.typeOf(node), isDeclaration);
+      return result;
+    }
+
+    // Attempt to obtain the type via TreePath (slower).
+    TreePath path = this.getPath(node);
+    assert path != null
+        : "No path or type in tree: " + node + " [" + node.getClass().getSimpleName() + "]";
+
+    TypeMirror t = trees.getTypeMirror(path);
+    assert validType(t) : "Invalid type " + t + " for node " + t;
+
+    AnnotatedTypeMirror result = toAnnotatedType(t, isDeclaration);
+    return result;
+  }
+
+  /**
+   * Gets the declaration tree for the element, if the source is available.
+   *
+   * <p>TODO: would be nice to move this to InternalUtils/TreeUtils.
+   *
+   * @param elt an element
+   * @return the tree declaration of the element if found
+   */
+  public final Tree declarationFromElement(Element elt) {
+    // if root is null, we cannot find any declaration
+    if (root == null) {
+      return null;
+    }
+    if (shouldCache && elementToTreeCache.containsKey(elt)) {
+      return elementToTreeCache.get(elt);
+    }
+
+    // Check for new declarations, outside of the AST.
+    if (elt instanceof DetachedVarSymbol) {
+      return ((DetachedVarSymbol) elt).getDeclaration();
+    }
+
+    // TODO: handle type parameter declarations?
+    Tree fromElt;
+    // Prevent calling declarationFor on elements we know we don't have the tree for.
+
+    switch (elt.getKind()) {
+      case CLASS:
+      case ENUM:
+      case INTERFACE:
+      case ANNOTATION_TYPE:
+      case FIELD:
+      case ENUM_CONSTANT:
+      case METHOD:
+      case CONSTRUCTOR:
+        fromElt = trees.getTree(elt);
+        break;
+      default:
+        fromElt =
+            com.sun.tools.javac.tree.TreeInfo.declarationFor(
+                (com.sun.tools.javac.code.Symbol) elt, (com.sun.tools.javac.tree.JCTree) root);
+        break;
+    }
+    if (shouldCache) {
+      elementToTreeCache.put(elt, fromElt);
+    }
+    return fromElt;
+  }
+
+  /**
+   * Returns the current class type being visited by the visitor. The method uses the parameter only
+   * if the most enclosing class cannot be found directly.
+   *
+   * @return type of the most enclosing class being visited
+   */
+  // This method is used to wrap access to visitorState
+  protected final ClassTree getCurrentClassTree(Tree tree) {
+    if (visitorState.getClassTree() != null) {
+      return visitorState.getClassTree();
+    }
+    return TreePathUtil.enclosingClass(getPath(tree));
+  }
+
+  protected final AnnotatedDeclaredType getCurrentClassType(Tree tree) {
+    return getAnnotatedType(getCurrentClassTree(tree));
+  }
+
+  /**
+   * Returns the receiver type of the current method being visited, and returns null if the visited
+   * tree is not within a method or if that method has no receiver (e.g. a static method).
+   *
+   * <p>The method uses the parameter only if the most enclosing method cannot be found directly.
+   *
+   * @return receiver type of the most enclosing method being visited
+   */
+  protected final @Nullable AnnotatedDeclaredType getCurrentMethodReceiver(Tree tree) {
+    AnnotatedDeclaredType res = visitorState.getMethodReceiver();
+    if (res == null) {
+      TreePath path = getPath(tree);
+      if (path != null) {
+        @SuppressWarnings("interning:assignment") // used for == test
+        @InternedDistinct MethodTree enclosingMethod = TreePathUtil.enclosingMethod(path);
+        ClassTree enclosingClass = TreePathUtil.enclosingClass(path);
+
+        boolean found = false;
+
+        for (Tree member : enclosingClass.getMembers()) {
+          if (member.getKind() == Tree.Kind.METHOD) {
+            if (member == enclosingMethod) {
+              found = true;
+            }
+          }
+        }
+
+        if (found && enclosingMethod != null) {
+          AnnotatedExecutableType method = getAnnotatedType(enclosingMethod);
+          res = method.getReceiverType();
+          // TODO: three tests fail if one adds the following, which would make sense, or not?
+          // visitorState.setMethodReceiver(res);
+        } else {
+          // We are within an anonymous class or field initializer
+          res = this.getAnnotatedType(enclosingClass);
+        }
+      }
+    }
+    return res;
+  }
+
+  protected final boolean isWithinConstructor(Tree tree) {
+    if (visitorState.getClassType() != null) {
+      return visitorState.getMethodTree() != null
+          && TreeUtils.isConstructor(visitorState.getMethodTree());
+    }
+
+    MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getPath(tree));
+    return enclosingMethod != null && TreeUtils.isConstructor(enclosingMethod);
+  }
+
+  /**
+   * Gets the path for the given {@link Tree} under the current root by checking from the visitor's
+   * current path, and using {@link Trees#getPath(CompilationUnitTree, Tree)} (which is much slower)
+   * only if {@code node} is not found on the current path.
+   *
+   * <p>Note that the given Tree has to be within the current compilation unit, otherwise null will
+   * be returned.
+   *
+   * @param node the {@link Tree} to get the path for
+   * @return the path for {@code node} under the current root. Returns null if {@code node} is not
+   *     within the current compilation unit.
+   */
+  public final @Nullable TreePath getPath(@FindDistinct Tree node) {
+    assert root != null
+        : "AnnotatedTypeFactory.getPath("
+            + node.getKind()
+            + "): root needs to be set when used on trees; factory: "
+            + this.getClass().getSimpleName();
+
+    if (node == null) {
+      return null;
+    }
+
+    if (artificialTreeToEnclosingElementMap.containsKey(node)) {
+      return null;
+    }
+
+    if (treePathCache.isCached(node)) {
+      return treePathCache.getPath(root, node);
+    }
+
+    TreePath currentPath = visitorState.getPath();
+    if (currentPath == null) {
+      TreePath path = TreePath.getPath(root, node);
+      treePathCache.addPath(node, path);
+      return path;
+    }
+
+    // This method uses multiple heuristics to avoid calling
+    // TreePath.getPath()
+
+    // If the current path you are visiting is for this node we are done
+    if (currentPath.getLeaf() == node) {
+      treePathCache.addPath(node, currentPath);
+      return currentPath;
+    }
+
+    // When running on Daikon, we noticed that a lot of calls happened
+    // within a small subtree containing the node we are currently visiting
+
+    // When testing on Daikon, two steps resulted in the best performance
+    if (currentPath.getParentPath() != null) {
+      currentPath = currentPath.getParentPath();
+      treePathCache.addPath(currentPath.getLeaf(), currentPath);
+      if (currentPath.getLeaf() == node) {
+        return currentPath;
+      }
+      if (currentPath.getParentPath() != null) {
+        currentPath = currentPath.getParentPath();
+        treePathCache.addPath(currentPath.getLeaf(), currentPath);
+        if (currentPath.getLeaf() == node) {
+          return currentPath;
+        }
+      }
+    }
+
+    final TreePath pathWithinSubtree = TreePath.getPath(currentPath, node);
+    if (pathWithinSubtree != null) {
+      treePathCache.addPath(node, pathWithinSubtree);
+      return pathWithinSubtree;
+    }
+
+    // climb the current path till we see that
+    // Works when getPath called on the enclosing method, enclosing class.
+    TreePath current = currentPath;
+    while (current != null) {
+      treePathCache.addPath(current.getLeaf(), current);
+      if (current.getLeaf() == node) {
+        return current;
+      }
+      current = current.getParentPath();
+    }
+
+    // OK, we give up. Use the cache to look up.
+    return treePathCache.getPath(root, node);
+  }
+
+  /**
+   * Gets the {@link Element} representing the declaration of the method enclosing a tree node. This
+   * feature is used to record the enclosing methods of {@link Tree}s that are created internally by
+   * the checker.
+   *
+   * <p>TODO: Find a better way to store information about enclosing Trees.
+   *
+   * @param node the {@link Tree} to get the enclosing method for
+   * @return the method {@link Element} enclosing the argument, or null if none has been recorded
+   */
+  public final Element getEnclosingElementForArtificialTree(Tree node) {
+    return artificialTreeToEnclosingElementMap.get(node);
+  }
+
+  /**
+   * Adds the given mapping from a synthetic (generated) tree to its enclosing element.
+   *
+   * <p>See {@code
+   * org.checkerframework.framework.flow.CFCFGBuilder.CFCFGTranslationPhaseOne.handleArtificialTree(Tree)}.
+   *
+   * @param tree artifical tree
+   * @param enclosing element that encloses {@code tree}
+   */
+  public final void setEnclosingElementForArtificialTree(Tree tree, Element enclosing) {
+    artificialTreeToEnclosingElementMap.put(tree, enclosing);
+  }
+
+  /**
+   * Assert that the type is a type of valid type mirror, i.e. not an ERROR or OTHER type.
+   *
+   * @param type an annotated type
+   * @return true if the type is a valid annotated type, false otherwise
+   */
+  static final boolean validAnnotatedType(AnnotatedTypeMirror type) {
+    if (type == null) {
+      return false;
+    }
+    return validType(type.getUnderlyingType());
+  }
+
+  /**
+   * Used for asserting that a type is valid for converting to an annotated type.
+   *
+   * @return true if {@code type} can be converted to an annotated type, false otherwise
+   */
+  private static final boolean validType(TypeMirror type) {
+    if (type == null) {
+      return false;
+    }
+    switch (type.getKind()) {
+      case ERROR:
+      case OTHER:
+      case PACKAGE:
+        return false;
+      default:
+        return true;
+    }
+  }
+
+  /**
+   * Parses all annotation files in the following order:
+   *
+   * <ol>
+   *   <li>jdk.astub in the same directory as the checker, if it exists and ignorejdkastub option is
+   *       not supplied <br>
+   *   <li>jdkN.astub, where N is the Java version in the same directory as the checker, if it
+   *       exists and ignorejdkastub option is not supplied <br>
+   *   <li>Stub files listed in @StubFiles annotation on the checker; must be in same directory as
+   *       the checker<br>
+   *   <li>Stub files provided via -Astubs compiler option
+   *   <li>Ajava files provided via -Aajava compiler option
+   * </ol>
+   *
+   * <p>If a type is annotated with a qualifier from the same hierarchy in more than one stub file,
+   * the qualifier in the last stub file is applied.
+   *
+   * <p>The annotations are stored by side-effecting {@link #stubTypes} and {@link #ajavaTypes}.
+   */
+  protected void parseAnnotationFiles() {
+    stubTypes.parseStubFiles();
+    ajavaTypes.parseAjavaFiles();
+  }
+
+  /**
+   * Returns all of the declaration annotations whose name equals the passed annotation class (or is
+   * an alias for it) including annotations:
+   *
+   * <ul>
+   *   <li>on the element
+   *   <li>written in stubfiles
+   *   <li>inherited from overriden methods, (see {@link InheritedAnnotation})
+   *   <li>inherited from superclasses or super interfaces (see {@link Inherited})
+   * </ul>
+   *
+   * @see #getDeclAnnotationNoAliases
+   * @param elt the element to retrieve the declaration annotation from
+   * @param anno annotation class
+   * @return the annotation mirror for anno
+   */
+  @Override
+  public final AnnotationMirror getDeclAnnotation(Element elt, Class<? extends Annotation> anno) {
+    return getDeclAnnotation(elt, anno, true);
+  }
+
+  /**
+   * Returns the actual annotation mirror used to annotate this element, whose name equals the
+   * passed annotation class. Returns null if none exists. Does not check for aliases of the
+   * annotation class.
+   *
+   * <p>Call this method from a checker that needs to alias annotations for one purpose and not for
+   * another. For example, in the Lock Checker, {@code @LockingFree} and {@code @ReleasesNoLocks}
+   * are both aliases of {@code @SideEffectFree} since they are all considered side-effect-free with
+   * regard to the set of locks held before and after the method call. However, a {@code
+   * synchronized} block is permitted inside a {@code @ReleasesNoLocks} method but not inside a
+   * {@code @LockingFree} or {@code @SideEffectFree} method.
+   *
+   * @see #getDeclAnnotation
+   * @param elt the element to retrieve the declaration annotation from
+   * @param anno annotation class
+   * @return the annotation mirror for anno
+   */
+  public final AnnotationMirror getDeclAnnotationNoAliases(
+      Element elt, Class<? extends Annotation> anno) {
+    return getDeclAnnotation(elt, anno, false);
+  }
+
+  /**
+   * Returns true if the element appears in a stub file (Currently only works for methods,
+   * constructors, and fields).
+   */
+  public boolean isFromStubFile(Element element) {
+    return this.getDeclAnnotation(element, FromStubFile.class) != null;
+  }
+
+  /**
+   * Returns true if the element is from bytecode and the if the element did not appear in a stub
+   * file. Currently only works for methods, constructors, and fields.
+   */
+  public boolean isFromByteCode(Element element) {
+    if (isFromStubFile(element)) {
+      return false;
+    }
+    return ElementUtils.isElementFromByteCode(element);
+  }
+
+  /**
+   * Returns true if redundancy between a stub file and bytecode should be reported.
+   *
+   * <p>For most type systems the default behavior of returning true is correct. For subcheckers,
+   * redundancy in one of the type hierarchies can be ok. Such implementations should return false.
+   *
+   * @return whether to warn about redundancy between a stub file and bytecode
+   */
+  public boolean shouldWarnIfStubRedundantWithBytecode() {
+    return true;
+  }
+
+  /**
+   * Returns the actual annotation mirror used to annotate this element, whose name equals the
+   * passed annotation class (or is an alias for it). Returns null if none exists. May return the
+   * canonical annotation that annotationName is an alias for.
+   *
+   * <p>This is the private implementation of the same-named, public method.
+   *
+   * <p>An option is provided to not to check for aliases of annotations. For example, an annotated
+   * type factory may use aliasing for a pair of annotations for convenience while needing in some
+   * cases to determine a strict ordering between them, such as when determining whether the
+   * annotations on an overrider method are more specific than the annotations of an overridden
+   * method.
+   *
+   * @param elt the element to retrieve the annotation from
+   * @param annoClass the class the annotation to retrieve
+   * @param checkAliases whether to return an annotation mirror for an alias of the requested
+   *     annotation class name
+   * @return the annotation mirror for the requested annotation, or null if not found
+   */
+  private AnnotationMirror getDeclAnnotation(
+      Element elt, Class<? extends Annotation> annoClass, boolean checkAliases) {
+    Set<AnnotationMirror> declAnnos = getDeclAnnotations(elt);
+
+    for (AnnotationMirror am : declAnnos) {
+      if (areSameByClass(am, annoClass)) {
+        return am;
+      }
+    }
+    // Look through aliases.
+    if (checkAliases) {
+      Pair<AnnotationMirror, Set<Class<? extends Annotation>>> aliases = declAliases.get(annoClass);
+      if (aliases != null) {
+        for (Class<? extends Annotation> alias : aliases.second) {
+          for (AnnotationMirror am : declAnnos) {
+            if (areSameByClass(am, alias)) {
+              // TODO: need to copy over elements/fields
+              return aliases.first;
+            }
+          }
+        }
+      }
+    }
+    // Not found.
+    return null;
+  }
+
+  /**
+   * Returns all of the declaration annotations on this element including annotations:
+   *
+   * <ul>
+   *   <li>on the element
+   *   <li>written in stubfiles
+   *   <li>inherited from overriden methods, (see {@link InheritedAnnotation})
+   *   <li>inherited from superclasses or super interfaces (see {@link Inherited})
+   * </ul>
+   *
+   * <p>This method returns the actual annotations not their aliases. {@link
+   * #getDeclAnnotation(Element, Class)} returns aliases.
+   *
+   * @param elt the element for which to determine annotations
+   * @return all of the declaration annotations on this element, written in stub files, or inherited
+   */
+  public Set<AnnotationMirror> getDeclAnnotations(Element elt) {
+    Set<AnnotationMirror> cachedValue = cacheDeclAnnos.get(elt);
+    if (cachedValue != null) {
+      // Found in cache, return result.
+      return cachedValue;
+    }
+
+    Set<AnnotationMirror> results = AnnotationUtils.createAnnotationSet();
+    // Retrieving the annotations from the element.
+    // This includes annotations inherited from superclasses, but not superinterfaces or
+    // overriden methods.
+    List<? extends AnnotationMirror> fromEle = elements.getAllAnnotationMirrors(elt);
+    for (AnnotationMirror annotation : fromEle) {
+      try {
+        results.add(annotation);
+      } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) {
+        // If a CompletionFailure occurs, issue a warning.
+        checker.reportWarning(
+            annotation.getAnnotationType().asElement(),
+            "annotation.not.completed",
+            ElementUtils.getQualifiedName(elt),
+            annotation);
+      }
+    }
+
+    // If parsing annotation files, return only the annotations in the element.
+    if (stubTypes.isParsing()
+        || ajavaTypes.isParsing()
+        || (currentFileAjavaTypes != null && currentFileAjavaTypes.isParsing())) {
+      return results;
+    }
+
+    // Add annotations from annotation files.
+    results.addAll(stubTypes.getDeclAnnotation(elt));
+    results.addAll(ajavaTypes.getDeclAnnotation(elt));
+    if (currentFileAjavaTypes != null) {
+      results.addAll(currentFileAjavaTypes.getDeclAnnotation(elt));
+    }
+
+    if (elt.getKind() == ElementKind.METHOD) {
+      // Retrieve the annotations from the overridden method's element.
+      inheritOverriddenDeclAnnos((ExecutableElement) elt, results);
+    } else if (ElementUtils.isTypeDeclaration(elt)) {
+      inheritOverriddenDeclAnnosFromTypeDecl(elt.asType(), results);
+    }
+
+    // Add the element and its annotations to the cache.
+    cacheDeclAnnos.put(elt, results);
+    return results;
+  }
+
+  /**
+   * Adds into {@code results} the inherited declaration annotations found in all elements of the
+   * super types of {@code typeMirror}. (Both superclasses and superinterfaces.)
+   *
+   * @param typeMirror type
+   * @param results set of AnnotationMirrors to which this method adds declarations annotations
+   */
+  private void inheritOverriddenDeclAnnosFromTypeDecl(
+      TypeMirror typeMirror, Set<AnnotationMirror> results) {
+    List<? extends TypeMirror> superTypes = types.directSupertypes(typeMirror);
+    for (TypeMirror superType : superTypes) {
+      TypeElement elt = TypesUtils.getTypeElement(superType);
+      if (elt == null) {
+        continue;
+      }
+      Set<AnnotationMirror> superAnnos = getDeclAnnotations(elt);
+      for (AnnotationMirror annotation : superAnnos) {
+        List<? extends AnnotationMirror> annotationsOnAnnotation;
+        try {
+          annotationsOnAnnotation =
+              annotation.getAnnotationType().asElement().getAnnotationMirrors();
+        } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) {
+          // Fix for Issue 348: If a CompletionFailure occurs, issue a warning.
+          checker.reportWarning(
+              annotation.getAnnotationType().asElement(),
+              "annotation.not.completed",
+              ElementUtils.getQualifiedName(elt),
+              annotation);
+          continue;
+        }
+        if (containsSameByClass(annotationsOnAnnotation, Inherited.class)
+            || AnnotationUtils.containsSameByName(inheritedAnnotations, annotation)) {
+          addOrMerge(results, annotation);
+        }
+      }
+    }
+  }
+
+  /**
+   * Adds into {@code results} the declaration annotations found in all elements that the method
+   * element {@code elt} overrides.
+   *
+   * @param elt method element
+   * @param results {@code elt} local declaration annotations. The ones found in stub files and in
+   *     the element itself.
+   */
+  private void inheritOverriddenDeclAnnos(ExecutableElement elt, Set<AnnotationMirror> results) {
+    Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
+        AnnotatedTypes.overriddenMethods(elements, this, elt);
+
+    if (overriddenMethods != null) {
+      for (ExecutableElement superElt : overriddenMethods.values()) {
+        Set<AnnotationMirror> superAnnos = getDeclAnnotations(superElt);
+
+        for (AnnotationMirror annotation : superAnnos) {
+          List<? extends AnnotationMirror> annotationsOnAnnotation;
+          try {
+            annotationsOnAnnotation =
+                annotation.getAnnotationType().asElement().getAnnotationMirrors();
+          } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) {
+            // Fix for Issue 348: If a CompletionFailure occurs,
+            // issue a warning.
+            checker.reportWarning(
+                annotation.getAnnotationType().asElement(),
+                "annotation.not.completed",
+                ElementUtils.getQualifiedName(elt),
+                annotation);
+            continue;
+          }
+          if (containsSameByClass(annotationsOnAnnotation, InheritedAnnotation.class)
+              || AnnotationUtils.containsSameByName(inheritedAnnotations, annotation)) {
+            addOrMerge(results, annotation);
+          }
+        }
+      }
+    }
+  }
+
+  private void addOrMerge(Set<AnnotationMirror> results, AnnotationMirror annotation) {
+    if (AnnotationUtils.containsSameByName(results, annotation)) {
+      /*
+       * TODO: feature request: figure out a way to merge multiple annotations
+       * of the same kind. For some annotations this might mean merging some
+       * arrays, for others it might mean converting a single annotation into a
+       * container annotation. We should define a protected method for subclasses
+       * to adapt the behavior.
+       * For now, do nothing and just take the first, most concrete, annotation.
+      AnnotationMirror prev = null;
+      for (AnnotationMirror an : results) {
+          if (AnnotationUtils.areSameByName(an, annotation)) {
+              prev = an;
+              break;
+          }
+      }
+      results.remove(prev);
+      AnnotationMirror merged = ...;
+      results.add(merged);
+      */
+    } else {
+      results.add(annotation);
+    }
+  }
+
+  /**
+   * Returns a list of all declaration annotations used to annotate the element, which have a
+   * meta-annotation (i.e., an annotation on that annotation) with class {@code
+   * metaAnnotationClass}.
+   *
+   * @param element the element for which to determine annotations
+   * @param metaAnnotationClass the class of the meta-annotation that needs to be present
+   * @return a list of pairs {@code (anno, metaAnno)} where {@code anno} is the annotation mirror at
+   *     {@code element}, and {@code metaAnno} is the annotation mirror (of type {@code
+   *     metaAnnotationClass}) used to meta-annotate the declaration of {@code anno}
+   */
+  public List<Pair<AnnotationMirror, AnnotationMirror>> getDeclAnnotationWithMetaAnnotation(
+      Element element, Class<? extends Annotation> metaAnnotationClass) {
+    List<Pair<AnnotationMirror, AnnotationMirror>> result = new ArrayList<>();
+    Set<AnnotationMirror> annotationMirrors = getDeclAnnotations(element);
+
+    for (AnnotationMirror candidate : annotationMirrors) {
+      List<? extends AnnotationMirror> metaAnnotationsOnAnnotation;
+      try {
+        metaAnnotationsOnAnnotation =
+            candidate.getAnnotationType().asElement().getAnnotationMirrors();
+      } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) {
+        // Fix for Issue 309: If a CompletionFailure occurs, issue a warning.
+        // I didn't find a nicer alternative to check whether the Symbol can be completed.
+        // The completer field of a Symbol might be non-null also in successful cases.
+        // Issue a warning (exception only happens once) and continue.
+        checker.reportWarning(
+            candidate.getAnnotationType().asElement(),
+            "annotation.not.completed",
+            ElementUtils.getQualifiedName(element),
+            candidate);
+        continue;
+      }
+      // First call copier, if exception, continue normal modula laws.
+      for (AnnotationMirror ma : metaAnnotationsOnAnnotation) {
+        if (areSameByClass(ma, metaAnnotationClass)) {
+          // This candidate has the right kind of meta-annotation.
+          // It might be a real contract, or a list of contracts.
+          if (isListForRepeatedAnnotation(candidate)) {
+            @SuppressWarnings("deprecation") // concrete annotation class is not known
+            List<AnnotationMirror> wrappedCandidates =
+                AnnotationUtils.getElementValueArray(
+                    candidate, "value", AnnotationMirror.class, false);
+            for (AnnotationMirror wrappedCandidate : wrappedCandidates) {
+              result.add(Pair.of(wrappedCandidate, ma));
+            }
+          } else {
+            result.add(Pair.of(candidate, ma));
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  /** Cache for {@link #isListForRepeatedAnnotation}. */
+  private final Map<DeclaredType, Boolean> isListForRepeatedAnnotationCache = new HashMap<>();
+
+  /**
+   * Returns true if the given annotation is a wrapper for multiple repeated annotations.
+   *
+   * @param a an annotation that might be a wrapper
+   * @return true if the argument is a wrapper for multiple repeated annotations
+   */
+  private boolean isListForRepeatedAnnotation(AnnotationMirror a) {
+    DeclaredType annotationType = a.getAnnotationType();
+    Boolean resultObject = isListForRepeatedAnnotationCache.get(annotationType);
+    if (resultObject != null) {
+      return resultObject;
+    }
+    boolean result = isListForRepeatedAnnotationImplementation(annotationType);
+    isListForRepeatedAnnotationCache.put(annotationType, result);
+    return result;
+  }
+
+  /**
+   * Returns true if the annotation is a wrapper for multiple repeated annotations.
+   *
+   * @param annotationType the declaration of the annotation to test
+   * @return true if the annotation is a wrapper for multiple repeated annotations
+   */
+  private boolean isListForRepeatedAnnotationImplementation(DeclaredType annotationType) {
+    TypeMirror enclosingType = annotationType.getEnclosingType();
+    if (enclosingType == null) {
+      return false;
+    }
+    if (!annotationType.asElement().getSimpleName().contentEquals("List")) {
+      return false;
+    }
+    List<? extends Element> annoElements = annotationType.asElement().getEnclosedElements();
+    if (annoElements.size() != 1) {
+      return false;
+    }
+    // TODO: should check that the type of the single element is: "array of enclosingType".
+    return true;
+  }
+
+  /**
+   * Returns a list of all annotations used to annotate this element, which have a meta-annotation
+   * (i.e., an annotation on that annotation) with class {@code metaAnnotationClass}.
+   *
+   * @param element the element at which to look for annotations
+   * @param metaAnnotationClass the class of the meta-annotation that needs to be present
+   * @return a list of pairs {@code (anno, metaAnno)} where {@code anno} is the annotation mirror at
+   *     {@code element}, and {@code metaAnno} is the annotation mirror used to annotate {@code
+   *     anno}.
+   */
+  public List<Pair<AnnotationMirror, AnnotationMirror>> getAnnotationWithMetaAnnotation(
+      Element element, Class<? extends Annotation> metaAnnotationClass) {
+
+    Set<AnnotationMirror> annotationMirrors = AnnotationUtils.createAnnotationSet();
+    // Consider real annotations.
+    annotationMirrors.addAll(getAnnotatedType(element).getAnnotations());
+    // Consider declaration annotations
+    annotationMirrors.addAll(getDeclAnnotations(element));
+
+    List<Pair<AnnotationMirror, AnnotationMirror>> result = new ArrayList<>();
+
+    // Go through all annotations found.
+    for (AnnotationMirror annotation : annotationMirrors) {
+      List<? extends AnnotationMirror> annotationsOnAnnotation =
+          annotation.getAnnotationType().asElement().getAnnotationMirrors();
+      for (AnnotationMirror a : annotationsOnAnnotation) {
+        if (areSameByClass(a, metaAnnotationClass)) {
+          result.add(Pair.of(annotation, a));
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Whether or not the {@code annotatedTypeMirror} has a qualifier parameter.
+   *
+   * @param annotatedTypeMirror AnnotatedTypeMirror to check
+   * @param top the top of the hierarchy to check
+   * @return true if the type has a qualifier parameter
+   */
+  public boolean hasQualifierParameterInHierarchy(
+      AnnotatedTypeMirror annotatedTypeMirror, AnnotationMirror top) {
+    return AnnotationUtils.containsSame(getQualifierParameterHierarchies(annotatedTypeMirror), top);
+  }
+
+  /**
+   * Whether or not the {@code element} has a qualifier parameter.
+   *
+   * @param element element to check
+   * @param top the top of the hierarchy to check
+   * @return true if the type has a qualifier parameter
+   */
+  public boolean hasQualifierParameterInHierarchy(@Nullable Element element, AnnotationMirror top) {
+    if (element == null) {
+      return false;
+    }
+    return AnnotationUtils.containsSame(getQualifierParameterHierarchies(element), top);
+  }
+
+  /**
+   * Returns whether the {@code HasQualifierParameter} annotation was explicitly written on {@code
+   * element} for the hierarchy given by {@code top}.
+   *
+   * @param element the Element to check
+   * @param top the top qualifier for the hierarchy to check
+   * @return whether the class given by {@code element} has been explicitly annotated with {@code
+   *     HasQualifierParameter} for the given hierarchy
+   */
+  public boolean hasExplicitQualifierParameterInHierarchy(Element element, AnnotationMirror top) {
+    return AnnotationUtils.containsSame(
+        getSupportedAnnotationsInElementAnnotation(
+            element, HasQualifierParameter.class, hasQualifierParameterValueElement),
+        top);
+  }
+
+  /**
+   * Returns whether the {@code NoQualifierParameter} annotation was explicitly written on {@code
+   * element} for the hierarchy given by {@code top}.
+   *
+   * @param element the Element to check
+   * @param top the top qualifier for the hierarchy to check
+   * @return whether the class given by {@code element} has been explicitly annotated with {@code
+   *     NoQualifierParameter} for the given hierarchy
+   */
+  public boolean hasExplicitNoQualifierParameterInHierarchy(Element element, AnnotationMirror top) {
+    return AnnotationUtils.containsSame(
+        getSupportedAnnotationsInElementAnnotation(
+            element, NoQualifierParameter.class, noQualifierParameterValueElement),
+        top);
+  }
+
+  /**
+   * Returns the set of top annotations representing all the hierarchies for which this type has a
+   * qualifier parameter.
+   *
+   * @param annotatedType AnnotatedTypeMirror to check
+   * @return the set of top annotations representing all the hierarchies for which this type has a
+   *     qualifier parameter
+   */
+  public Set<AnnotationMirror> getQualifierParameterHierarchies(AnnotatedTypeMirror annotatedType) {
+    while (annotatedType.getKind() == TypeKind.TYPEVAR
+        || annotatedType.getKind() == TypeKind.WILDCARD) {
+      if (annotatedType.getKind() == TypeKind.TYPEVAR) {
+        annotatedType = ((AnnotatedTypeVariable) annotatedType).getUpperBound();
+      } else if (annotatedType.getKind() == TypeKind.WILDCARD) {
+        annotatedType = ((AnnotatedWildcardType) annotatedType).getSuperBound();
+      }
+    }
+
+    if (annotatedType.getKind() != TypeKind.DECLARED) {
+      return Collections.emptySet();
+    }
+
+    AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) annotatedType;
+    Element element = declaredType.getUnderlyingType().asElement();
+    if (element == null) {
+      return Collections.emptySet();
+    }
+    return getQualifierParameterHierarchies(element);
+  }
+
+  /**
+   * Returns the set of top annotations representing all the hierarchies for which this element has
+   * a qualifier parameter.
+   *
+   * @param element the Element to check
+   * @return the set of top annotations representing all the hierarchies for which this element has
+   *     a qualifier parameter
+   */
+  public Set<AnnotationMirror> getQualifierParameterHierarchies(Element element) {
+    if (!ElementUtils.isTypeDeclaration(element)) {
+      return Collections.emptySet();
+    }
+
+    Set<AnnotationMirror> found = AnnotationUtils.createAnnotationSet();
+    found.addAll(
+        getSupportedAnnotationsInElementAnnotation(
+            element, HasQualifierParameter.class, hasQualifierParameterValueElement));
+    Set<AnnotationMirror> hasQualifierParameterTops = AnnotationUtils.createAnnotationSet();
+    PackageElement packageElement = ElementUtils.enclosingPackage(element);
+
+    // Traverse all packages containing this element.
+    while (packageElement != null) {
+      Set<AnnotationMirror> packageDefaultTops =
+          getSupportedAnnotationsInElementAnnotation(
+              packageElement, HasQualifierParameter.class, hasQualifierParameterValueElement);
+      hasQualifierParameterTops.addAll(packageDefaultTops);
+
+      packageElement = ElementUtils.parentPackage(packageElement, elements);
+    }
+
+    Set<AnnotationMirror> noQualifierParamClasses =
+        getSupportedAnnotationsInElementAnnotation(
+            element, NoQualifierParameter.class, noQualifierParameterValueElement);
+    for (AnnotationMirror anno : hasQualifierParameterTops) {
+      if (!AnnotationUtils.containsSame(noQualifierParamClasses, anno)) {
+        found.add(anno);
+      }
+    }
+
+    return found;
+  }
+
+  /**
+   * Returns a set of supported annotation mirrors corresponding to the annotation classes listed in
+   * the value element of an annotation with class {@code annoClass} on {@code element}.
+   *
+   * @param element the Element to check
+   * @param annoClass the class for an annotation that's written on elements, whose value element is
+   *     a list of annotation classes. It is always HasQualifierParameter or NoQualifierParameter
+   * @param valueElement the {@code value} field/element of an annotation with class {@code
+   *     annoClass}
+   * @return the set of supported annotations with classes listed in the value element of an
+   *     annotation with class {@code annoClass} on the {@code element}. Returns an empty set if
+   *     {@code annoClass} is not written on {@code element} or {@code element} is null.
+   */
+  private Set<AnnotationMirror> getSupportedAnnotationsInElementAnnotation(
+      @Nullable Element element,
+      Class<? extends Annotation> annoClass,
+      ExecutableElement valueElement) {
+    if (element == null) {
+      return Collections.emptySet();
+    }
+    // TODO: caching
+    AnnotationMirror annotation = getDeclAnnotation(element, annoClass);
+    if (annotation == null) {
+      return Collections.emptySet();
+    }
+
+    Set<AnnotationMirror> found = AnnotationUtils.createAnnotationSet();
+    List<@CanonicalName Name> qualClasses =
+        AnnotationUtils.getElementValueClassNames(annotation, valueElement);
+    for (Name qual : qualClasses) {
+      AnnotationMirror annotationMirror = AnnotationBuilder.fromName(elements, qual);
+      if (isSupportedQualifier(annotationMirror)) {
+        found.add(annotationMirror);
+      }
+    }
+    return found;
+  }
+
+  /**
+   * A scanner that replaces annotations in one type with annotations from another. Used by {@link
+   * #replaceAnnotations(AnnotatedTypeMirror, AnnotatedTypeMirror)} and {@link
+   * #replaceAnnotations(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}.
+   */
+  private final AnnotatedTypeReplacer annotatedTypeReplacer = new AnnotatedTypeReplacer();
+
+  /**
+   * Replaces or adds all annotations from {@code from} to {@code to}. Annotations from {@code from}
+   * will be used everywhere they exist, but annotations in {@code to} will be kept anywhere that
+   * {@code from} is unannotated.
+   *
+   * @param from the annotated type mirror from which to take new annotations
+   * @param to the annotated type mirror to which the annotations will be added
+   */
+  public void replaceAnnotations(final AnnotatedTypeMirror from, final AnnotatedTypeMirror to) {
+    annotatedTypeReplacer.visit(from, to);
+  }
+
+  /**
+   * Replaces or adds annotations in {@code top}'s hierarchy from {@code from} to {@code to}.
+   * Annotations from {@code from} will be used everywhere they exist, but annotations in {@code to}
+   * will be kept anywhere that {@code from} is unannotated.
+   *
+   * @param from the annotated type mirror from which to take new annotations
+   * @param to the annotated type mirror to which the annotations will be added
+   * @param top the top type of the hierarchy whose annotations will be added
+   */
+  public void replaceAnnotations(
+      final AnnotatedTypeMirror from, final AnnotatedTypeMirror to, final AnnotationMirror top) {
+    annotatedTypeReplacer.setTop(top);
+    annotatedTypeReplacer.visit(from, to);
+    annotatedTypeReplacer.setTop(null);
+  }
+
+  /** The implementation of the visitor for #containsUninferredTypeArguments. */
+  private final SimpleAnnotatedTypeScanner<Boolean, Void> uninferredTypeArgumentScanner =
+      new SimpleAnnotatedTypeScanner<>(
+          (type, p) ->
+              type.getKind() == TypeKind.WILDCARD
+                  && ((AnnotatedWildcardType) type).isUninferredTypeArgument(),
+          Boolean::logicalOr,
+          false);
+
+  /**
+   * Returns whether this type or any component type is a wildcard type for which Java 7 type
+   * inference is insufficient. See issue 979, or the documentation on AnnotatedWildcardType.
+   *
+   * @param type type to check
+   * @return whether this type or any component type is a wildcard type for which Java 7 type
+   *     inference is insufficient
+   */
+  public boolean containsUninferredTypeArguments(AnnotatedTypeMirror type) {
+    return uninferredTypeArgumentScanner.visit(type);
+  }
+
+  /**
+   * Returns a wildcard type to be used as a type argument when the correct type could not be
+   * inferred. The wildcard will be marked as an uninferred wildcard so that {@link
+   * AnnotatedWildcardType#isUninferredTypeArgument()} returns true.
+   *
+   * <p>This method should only be used by type argument inference.
+   * org.checkerframework.framework.util.AnnotatedTypes.inferTypeArguments(ProcessingEnvironment,
+   * AnnotatedTypeFactory, ExpressionTree, ExecutableElement)
+   *
+   * @param typeVar TypeVariable which could not be inferred
+   * @return a wildcard that is marked as an uninferred type argument
+   */
+  public AnnotatedWildcardType getUninferredWildcardType(AnnotatedTypeVariable typeVar) {
+    final boolean intersectionType;
+    final TypeMirror boundType;
+    if (typeVar.getUpperBound().getKind() == TypeKind.INTERSECTION) {
+      boundType = typeVar.getUpperBound().directSupertypes().get(0).getUnderlyingType();
+      intersectionType = true;
+    } else {
+      boundType = typeVar.getUnderlyingType().getUpperBound();
+      intersectionType = false;
+    }
+
+    WildcardType wc = types.getWildcardType(boundType, null);
+    AnnotatedWildcardType wctype =
+        (AnnotatedWildcardType) AnnotatedTypeMirror.createType(wc, this, false);
+    wctype.setTypeVariable(typeVar.getUnderlyingType());
+    if (!intersectionType) {
+      wctype.setExtendsBound(typeVar.getUpperBound().deepCopy());
+    } else {
+      wctype.getExtendsBound().addAnnotations(typeVar.getUpperBound().getAnnotations());
+    }
+    wctype.setSuperBound(typeVar.getLowerBound().deepCopy());
+    wctype.addAnnotations(typeVar.getAnnotations());
+    addDefaultAnnotations(wctype);
+    wctype.setUninferredTypeArgument();
+    return wctype;
+  }
+
+  /**
+   * If {@code wildcard}'s upper bound is a super type of {@code annotatedTypeMirror}, this method
+   * returns an AnnotatedTypeMirror with the same qualifiers as {@code annotatedTypeMirror}, but the
+   * underlying Java type is the most specific base type of {@code annotatedTypeMirror} whose
+   * erasure type is equivalent to the upper bound of {@code wildcard}.
+   *
+   * <p>Otherwise, returns {@code annotatedTypeMirror} unmodified.
+   *
+   * <p>For example:
+   *
+   * <pre>
+   * wildcard := @NonNull ? extends @NonNull Object
+   * annotatedTypeMirror := @Nullable String
+   *
+   * widenToUpperBound(annotatedTypeMirror, wildcard) returns @Nullable Object
+   * </pre>
+   *
+   * This method is needed because, the Java compiler allows wildcards to have upper bounds above
+   * the type variable upper bounds for which they are type arguments. For example, given the
+   * following parametric type:
+   *
+   * <pre>
+   * {@code class MyClass<T extends Number>}
+   * </pre>
+   *
+   * the following is legal:
+   *
+   * <pre>
+   * {@code MyClass<? extends Object>}
+   * </pre>
+   *
+   * This is sound because in Java the wildcard is capture converted to: {@code CAP#1 extends Number
+   * from capture of ? extends Object}.
+   *
+   * <p>Because the Checker Framework does not implement capture conversion, wildcard upper bounds
+   * may cause spurious errors in subtyping checks. This method prevents those errors by widening
+   * the upper bound of the type parameter.
+   *
+   * <p>This method widens the underlying Java type of the upper bound of the type parameter rather
+   * than narrowing the bound of the wildcard in order to avoid issuing an error with an upper bound
+   * that is not in source code.
+   *
+   * <p>The widened type should only be used for typing checks that require it. Using the widened
+   * type elsewhere would cause confusing error messages with types not in the source code.
+   *
+   * @param annotatedTypeMirror AnnotatedTypeMirror to widen
+   * @param wildcard AnnotatedWildcardType whose upper bound is used to widen
+   * @return {@code annotatedTypeMirror} widen to the upper bound of {@code wildcard}
+   */
+  public AnnotatedTypeMirror widenToUpperBound(
+      final AnnotatedTypeMirror annotatedTypeMirror, final AnnotatedWildcardType wildcard) {
+    final TypeMirror toModifyTypeMirror = annotatedTypeMirror.getUnderlyingType();
+    final TypeMirror wildcardUBTypeMirror = wildcard.getExtendsBound().getUnderlyingType();
+    if (TypesUtils.isErasedSubtype(wildcardUBTypeMirror, toModifyTypeMirror, types)) {
+      return annotatedTypeMirror;
+    } else if (TypesUtils.isErasedSubtype(toModifyTypeMirror, wildcardUBTypeMirror, types)) {
+      return AnnotatedTypes.asSuper(this, annotatedTypeMirror, wildcard);
+    } else if (wildcardUBTypeMirror.getKind() == TypeKind.DECLARED
+        && TypesUtils.getTypeElement(wildcardUBTypeMirror).getKind().isInterface()) {
+      // If the Checker Framework implemented capture conversion, then in this case, then
+      // the upper bound of the capture converted wildcard would be an intersection type.
+      // See JLS 15.1.10
+      // (https://docs.oracle.com/javase/specs/jls/se11/html/jls-5.html#jls-5.1.10)
+
+      // For example:
+      // class MyClass<@A T extends @B Number> {}
+      // MyClass<@C ? extends @D Serializable>
+      // The upper bound of the captured wildcard:
+      // glb(@B Number, @D Serializable) = @B Number & @D Serializable
+      // The about upper bound must be a subtype of the declared upper bound:
+      // @B Number & @D Serializable <: @B Number, which is always true.
+
+      // So, replace the upper bound at the declaration with the wildcard's upper bound so
+      // that the rest of the subtyping test pass.
+      return wildcard.getExtendsBound();
+    }
+
+    return annotatedTypeMirror;
+  }
+
+  /**
+   * Returns the function type that this member reference targets.
+   *
+   * <p>The function type is the type of the single method declared in the functional interface
+   * adapted as if it were invoked using the functional interface as the receiver expression.
+   *
+   * <p>The target type of a member reference is the type to which it is assigned or casted.
+   *
+   * @param tree member reference tree
+   * @return the function type that this method reference targets
+   */
+  public AnnotatedExecutableType getFunctionTypeFromTree(MemberReferenceTree tree) {
+    return getFnInterfaceFromTree(tree).second;
+  }
+
+  /**
+   * Returns the function type that this lambda targets.
+   *
+   * <p>The function type is the type of the single method declared in the functional interface
+   * adapted as if it were invoked using the functional interface as the receiver expression.
+   *
+   * <p>The target type of a lambda is the type to which it is assigned or casted.
+   *
+   * @param tree lambda expression tree
+   * @return the function type that this lambda targets
+   */
+  public AnnotatedExecutableType getFunctionTypeFromTree(LambdaExpressionTree tree) {
+    return getFnInterfaceFromTree(tree).second;
+  }
+
+  /**
+   * Returns the functional interface and the function type that this lambda or member references
+   * targets.
+   *
+   * <p>The function type is the type of the single method declared in the functional interface
+   * adapted as if it were invoked using the functional interface as the receiver expression.
+   *
+   * <p>The target type of a lambda or a method reference is the type to which it is assigned or
+   * casted.
+   *
+   * @param tree lambda expression tree or member reference tree
+   * @return the functional interface and the function type that this method reference or lambda
+   *     targets
+   */
+  public Pair<AnnotatedTypeMirror, AnnotatedExecutableType> getFnInterfaceFromTree(Tree tree) {
+
+    // Functional interface
+    AnnotatedTypeMirror functionalInterfaceType = getFunctionalInterfaceType(tree);
+    if (functionalInterfaceType.getKind() == TypeKind.DECLARED) {
+      makeGroundTargetType(
+          (AnnotatedDeclaredType) functionalInterfaceType, (DeclaredType) TreeUtils.typeOf(tree));
+    }
+
+    // Functional method
+    Element fnElement = TreeUtils.findFunction(tree, processingEnv);
+
+    // Function type
+    AnnotatedExecutableType functionType =
+        (AnnotatedExecutableType)
+            AnnotatedTypes.asMemberOf(types, this, functionalInterfaceType, fnElement);
+
+    return Pair.of(functionalInterfaceType, functionType);
+  }
+
+  /**
+   * Get the AnnotatedDeclaredType for the FunctionalInterface from assignment context of the method
+   * reference or lambda expression which may be a variable assignment, a method call, or a cast.
+   *
+   * <p>The assignment context is not always correct, so we must search up the AST. It will
+   * recursively search for lambdas nested in lambdas.
+   *
+   * @param tree the tree of the lambda or method reference
+   * @return the functional interface type or an uninferred type argument
+   */
+  private AnnotatedTypeMirror getFunctionalInterfaceType(Tree tree) {
+
+    Tree parentTree = getPath(tree).getParentPath().getLeaf();
+    switch (parentTree.getKind()) {
+      case PARENTHESIZED:
+        return getFunctionalInterfaceType(parentTree);
+
+      case TYPE_CAST:
+        TypeCastTree cast = (TypeCastTree) parentTree;
+        assert isFunctionalInterface(
+            trees.getTypeMirror(getPath(cast.getType())), parentTree, tree);
+        AnnotatedTypeMirror castATM = getAnnotatedType(cast.getType());
+        if (castATM.getKind() == TypeKind.INTERSECTION) {
+          AnnotatedIntersectionType itype = (AnnotatedIntersectionType) castATM;
+          for (AnnotatedTypeMirror t : itype.directSupertypes()) {
+            if (TypesUtils.isFunctionalInterface(t.getUnderlyingType(), getProcessingEnv())) {
+              return t;
+            }
+          }
+          // We should never reach here: isFunctionalInterface performs the same check
+          // and would have raised an error already.
+          throw new BugInCF(
+              "Expected the type of a cast tree in an assignment context to contain a functional"
+                  + " interface bound. Found type: %s for tree: %s in lambda tree: %s",
+              castATM, cast, tree);
+        }
+        return castATM;
+
+      case NEW_CLASS:
+        NewClassTree newClass = (NewClassTree) parentTree;
+        int indexOfLambda = newClass.getArguments().indexOf(tree);
+        ParameterizedExecutableType con = this.constructorFromUse(newClass);
+        AnnotatedTypeMirror constructorParam =
+            AnnotatedTypes.getAnnotatedTypeMirrorOfParameter(con.executableType, indexOfLambda);
+        assert isFunctionalInterface(constructorParam.getUnderlyingType(), parentTree, tree);
+        return constructorParam;
+
+      case NEW_ARRAY:
+        NewArrayTree newArray = (NewArrayTree) parentTree;
+        AnnotatedArrayType newArrayATM = getAnnotatedType(newArray);
+        AnnotatedTypeMirror elementATM = newArrayATM.getComponentType();
+        assert isFunctionalInterface(elementATM.getUnderlyingType(), parentTree, tree);
+        return elementATM;
+
+      case METHOD_INVOCATION:
+        MethodInvocationTree method = (MethodInvocationTree) parentTree;
+        int index = method.getArguments().indexOf(tree);
+        ParameterizedExecutableType exe = this.methodFromUse(method);
+        AnnotatedTypeMirror param =
+            AnnotatedTypes.getAnnotatedTypeMirrorOfParameter(exe.executableType, index);
+        if (param.getKind() == TypeKind.WILDCARD) {
+          // param is an uninferred wildcard.
+          TypeMirror typeMirror = TreeUtils.typeOf(tree);
+          param = AnnotatedTypeMirror.createType(typeMirror, this, false);
+          addDefaultAnnotations(param);
+        }
+        assert isFunctionalInterface(param.getUnderlyingType(), parentTree, tree);
+        return param;
+
+      case VARIABLE:
+        VariableTree varTree = (VariableTree) parentTree;
+        assert isFunctionalInterface(TreeUtils.typeOf(varTree), parentTree, tree);
+        return getAnnotatedType(varTree.getType());
+
+      case ASSIGNMENT:
+        AssignmentTree assignmentTree = (AssignmentTree) parentTree;
+        assert isFunctionalInterface(TreeUtils.typeOf(assignmentTree), parentTree, tree);
+        return getAnnotatedType(assignmentTree.getVariable());
+
+      case RETURN:
+        Tree enclosing =
+            TreePathUtil.enclosingOfKind(
+                getPath(parentTree),
+                new HashSet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)));
+
+        if (enclosing.getKind() == Tree.Kind.METHOD) {
+          MethodTree enclosingMethod = (MethodTree) enclosing;
+          return getAnnotatedType(enclosingMethod.getReturnType());
+        } else {
+          LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) enclosing;
+          AnnotatedExecutableType methodExe = getFunctionTypeFromTree(enclosingLambda);
+          return methodExe.getReturnType();
+        }
+
+      case LAMBDA_EXPRESSION:
+        LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) parentTree;
+        AnnotatedExecutableType methodExe = getFunctionTypeFromTree(enclosingLambda);
+        return methodExe.getReturnType();
+
+      case CONDITIONAL_EXPRESSION:
+        ConditionalExpressionTree conditionalExpressionTree =
+            (ConditionalExpressionTree) parentTree;
+        final AnnotatedTypeMirror falseType =
+            getAnnotatedType(conditionalExpressionTree.getFalseExpression());
+        final AnnotatedTypeMirror trueType =
+            getAnnotatedType(conditionalExpressionTree.getTrueExpression());
+
+        // Known cases where we must use LUB because falseType/trueType will not be equal:
+        // a) when one of the types is a type variable that extends a functional interface
+        //    or extends a type variable that extends a functional interface
+        // b) When one of the two sides of the expression is a reference to a sub-interface.
+        //   e.g.   interface ConsumeStr {
+        //              public void consume(String s)
+        //          }
+        //          interface SubConsumer extends ConsumeStr {
+        //              default void someOtherMethod() { ... }
+        //          }
+        //   SubConsumer s = ...;
+        //   ConsumeStr stringConsumer = (someCondition) ? s : System.out::println;
+        AnnotatedTypeMirror conditionalType =
+            AnnotatedTypes.leastUpperBound(this, trueType, falseType);
+        assert isFunctionalInterface(conditionalType.getUnderlyingType(), parentTree, tree);
+        return conditionalType;
+
+      default:
+        throw new BugInCF(
+            "Could not find functional interface from assignment context. "
+                + "Unexpected tree type: "
+                + parentTree.getKind()
+                + " For lambda tree: "
+                + tree);
+    }
+  }
+
+  private boolean isFunctionalInterface(TypeMirror typeMirror, Tree contextTree, Tree tree) {
+    if (typeMirror.getKind() == TypeKind.WILDCARD) {
+      // Ignore wildcards, because they are uninferred type arguments.
+      return true;
+    }
+    Type type = (Type) typeMirror;
+
+    if (!TypesUtils.isFunctionalInterface(type, processingEnv)) {
+      if (type.getKind() == TypeKind.INTERSECTION) {
+        IntersectionType itype = (IntersectionType) type;
+        for (TypeMirror t : itype.getBounds()) {
+          if (TypesUtils.isFunctionalInterface(t, processingEnv)) {
+            // As long as any of the bounds is a functional interface
+            // we should be fine.
+            return true;
+          }
+        }
+      }
+      throw new BugInCF(
+          "Expected the type of %s tree in assignment context to be a functional interface. "
+              + "Found type: %s for tree: %s in lambda tree: %s",
+          contextTree.getKind(), type, contextTree, tree);
+    }
+    return true;
+  }
+
+  /**
+   * Create the ground target type of the functional interface.
+   *
+   * <p>Basically, it replaces the wildcards with their bounds doing a capture conversion like glb
+   * for extends bounds.
+   *
+   * @see "JLS 9.9"
+   * @param functionalType the functional interface type
+   * @param groundTargetJavaType the Java type as found by javac
+   */
+  private void makeGroundTargetType(
+      AnnotatedDeclaredType functionalType, DeclaredType groundTargetJavaType) {
+    if (functionalType.getTypeArguments().isEmpty()) {
+      return;
+    }
+
+    List<AnnotatedTypeParameterBounds> bounds =
+        this.typeVariablesFromUse(
+            functionalType, (TypeElement) functionalType.getUnderlyingType().asElement());
+
+    List<AnnotatedTypeMirror> newTypeArguments = new ArrayList<>(functionalType.getTypeArguments());
+    boolean sizesDiffer =
+        functionalType.getTypeArguments().size() != groundTargetJavaType.getTypeArguments().size();
+
+    for (int i = 0; i < functionalType.getTypeArguments().size(); i++) {
+      AnnotatedTypeMirror argType = functionalType.getTypeArguments().get(i);
+      if (argType.getKind() == TypeKind.WILDCARD) {
+        AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) argType;
+
+        TypeMirror wildcardUbType = wildcardType.getExtendsBound().getUnderlyingType();
+
+        if (wildcardType.isUninferredTypeArgument()) {
+          // Keep the uninferred type so that it is ignored by later subtyping and containment
+          // checks.
+          newTypeArguments.set(i, wildcardType);
+        } else if (isExtendsWildcard(wildcardType)) {
+          TypeMirror correctArgType;
+          if (sizesDiffer) {
+            // The java type is raw.
+            TypeMirror typeParamUbType = bounds.get(i).getUpperBound().getUnderlyingType();
+            correctArgType =
+                TypesUtils.greatestLowerBound(
+                    typeParamUbType, wildcardUbType, this.checker.getProcessingEnvironment());
+          } else {
+            correctArgType = groundTargetJavaType.getTypeArguments().get(i);
+          }
+
+          final AnnotatedTypeMirror newArg;
+          if (types.isSameType(wildcardUbType, correctArgType)) {
+            newArg = wildcardType.getExtendsBound().deepCopy();
+          } else if (correctArgType.getKind() == TypeKind.TYPEVAR) {
+            newArg = this.toAnnotatedType(correctArgType, false);
+            AnnotatedTypeVariable newArgAsTypeVar = (AnnotatedTypeVariable) newArg;
+            newArgAsTypeVar
+                .getUpperBound()
+                .replaceAnnotations(wildcardType.getExtendsBound().getAnnotations());
+            newArgAsTypeVar
+                .getLowerBound()
+                .replaceAnnotations(wildcardType.getSuperBound().getAnnotations());
+          } else {
+            newArg = this.toAnnotatedType(correctArgType, false);
+            newArg.replaceAnnotations(wildcardType.getExtendsBound().getAnnotations());
+          }
+          newTypeArguments.set(i, newArg);
+        } else {
+          newTypeArguments.set(i, wildcardType.getSuperBound());
+        }
+      }
+    }
+    functionalType.setTypeArguments(newTypeArguments);
+
+    // When the groundTargetJavaType is different from the underlying type of functionalType, only
+    // the main annotations are copied.  Add default annotations in places without annotations.
+    addDefaultAnnotations(functionalType);
+  }
+
+  /**
+   * Check that a wildcard is an extends wildcard.
+   *
+   * @param awt the wildcard type
+   * @return true if awt is an extends wildcard
+   */
+  private boolean isExtendsWildcard(AnnotatedWildcardType awt) {
+    return awt.getUnderlyingType().getSuperBound() == null;
+  }
+
+  /** Accessor for the element utilities. */
+  public Elements getElementUtils() {
+    return this.elements;
+  }
+
+  /** Accessor for the tree utilities. */
+  public Trees getTreeUtils() {
+    return this.trees;
+  }
+
+  /** Accessor for the processing environment. */
+  public ProcessingEnvironment getProcessingEnv() {
+    return this.processingEnv;
+  }
+
+  /** Matches addition of a constant. */
+  static final Pattern plusConstant = Pattern.compile(" *\\+ *(-?[0-9]+)$");
+  /** Matches subtraction of a constant. */
+  static final Pattern minusConstant = Pattern.compile(" *- *(-?[0-9]+)$");
+
+  /** Matches a string whose only parens are at the beginning and end of the string. */
+  private static Pattern surroundingParensPattern = Pattern.compile("^\\([^()]\\)");
+
+  /**
+   * Given an expression, split it into a subexpression and a constant offset. For example:
+   *
+   * <pre>{@code
+   * "a" => <"a", "0">
+   * "a + 5" => <"a", "5">
+   * "a + -5" => <"a", "-5">
+   * "a - 5" => <"a", "-5">
+   * }</pre>
+   *
+   * There are methods that can only take as input an expression that represents a JavaExpression.
+   * The purpose of this is to pre-process expressions to make those methods more likely to succeed.
+   *
+   * @param expression an expression to remove a constant offset from
+   * @return a sub-expression and a constant offset. The offset is "0" if this routine is unable to
+   *     splite the given expression
+   */
+  // TODO: generalize.  There is no reason this couldn't handle arbitrary addition and subtraction
+  // expressions, given the Index Checker's support for OffsetEquation.  That might even make its
+  // implementation simpler.
+  public static Pair<String, String> getExpressionAndOffset(String expression) {
+    String expr = expression;
+    String offset = "0";
+
+    // Is this normalization necessary?
+    // Remove surrounding whitespace.
+    expr = expr.trim();
+    // Remove surrounding parentheses.
+    if (surroundingParensPattern.matcher(expr).matches()) {
+      expr = expr.substring(1, expr.length() - 2).trim();
+    }
+
+    Matcher mPlus = plusConstant.matcher(expr);
+    Matcher mMinus = minusConstant.matcher(expr);
+    if (mPlus.find()) {
+      expr = expr.substring(0, mPlus.start());
+      offset = mPlus.group(1);
+    } else if (mMinus.find()) {
+      expr = expr.substring(0, mMinus.start());
+      offset = negateConstant(mMinus.group(1));
+    }
+
+    if (offset.equals("-0")) {
+      offset = "0";
+    }
+
+    expr = expr.intern();
+    offset = offset.intern();
+
+    return Pair.of(expr, offset);
+  }
+
+  /**
+   * Given an expression string, returns its negation.
+   *
+   * @param constantExpression a string representing an integer constant
+   * @return the negation of constantExpression
+   */
+  // Also see Subsequence.negateString which is similar but more sophisticated.
+  public static String negateConstant(String constantExpression) {
+    if (constantExpression.startsWith("-")) {
+      return constantExpression.substring(1);
+    } else {
+      if (constantExpression.startsWith("+")) {
+        constantExpression = constantExpression.substring(1);
+      }
+      return "-" + constantExpression;
+    }
+  }
+
+  /**
+   * Returns {@code null} or an annotated type mirror that type argument inference should assume
+   * {@code expressionTree} is assigned to.
+   *
+   * <p>If {@code null} is returned, inference proceeds normally.
+   *
+   * <p>If a type is returned, then inference assumes that {@code expressionTree} was asigned to it.
+   * This biases the inference algorithm toward the annotations in the returned type. In particular,
+   * if the annotations on type variables in invariant positions are a super type of the annotations
+   * inferred, the super type annotations are chosen.
+   *
+   * <p>This implementation returns null, but subclasses may override this method to return a type.
+   *
+   * @param expressionTree an expression which has no assignment context and for which type
+   *     arguments need to be inferred
+   * @return {@code null} or an annotated type mirror that inferrence should pretend {@code
+   *     expressionTree} is assigned to
+   */
+  public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) {
+    return null;
+  }
+
+  /**
+   * Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored.
+   *
+   * <p>This method is faster than {@link AnnotationUtils#areSameByClass(AnnotationMirror, Class)}
+   * because it caches the name of the class rather than computing it each time.
+   *
+   * @param am the AnnotationMirror whose class to compare
+   * @param annoClass the class to compare
+   * @return true if annoclass is the class of am
+   */
+  public boolean areSameByClass(AnnotationMirror am, Class<? extends Annotation> annoClass) {
+    if (!shouldCache) {
+      return AnnotationUtils.areSameByName(am, annoClass.getCanonicalName());
+    }
+    @SuppressWarnings("nullness") // assume getCanonicalName returns non-null
+    String canonicalName = annotationClassNames.computeIfAbsent(annoClass, Class::getCanonicalName);
+    return AnnotationUtils.areSameByName(am, canonicalName);
+  }
+
+  /**
+   * Checks that the collection contains the annotation. Using Collection.contains does not always
+   * work, because it does not use areSame for comparison.
+   *
+   * <p>This method is faster than {@link AnnotationUtils#containsSameByClass(Collection, Class)}
+   * because is caches the name of the class rather than computing it each time.
+   *
+   * @param c a collection of AnnotationMirrors
+   * @param anno the annotation class to search for in c
+   * @return true iff c contains anno, according to areSameByClass
+   */
+  public boolean containsSameByClass(
+      Collection<? extends AnnotationMirror> c, Class<? extends Annotation> anno) {
+    return getAnnotationByClass(c, anno) != null;
+  }
+
+  /**
+   * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}.
+   *
+   * <p>This method is faster than {@link AnnotationUtils#getAnnotationByClass(Collection, Class)}
+   * because is caches the name of the class rather than computing it each time.
+   *
+   * @param c a collection of AnnotationMirrors
+   * @param anno the class to search for in c
+   * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to
+   *     areSameByClass; otherwise, {@code null}
+   */
+  public @Nullable AnnotationMirror getAnnotationByClass(
+      Collection<? extends AnnotationMirror> c, Class<? extends Annotation> anno) {
+    for (AnnotationMirror an : c) {
+      if (areSameByClass(an, anno)) {
+        return an;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Changes the type of {@code rhsATM} when being assigned to a field, for use by whole-program
+   * inference. The default implementation does nothing.
+   *
+   * @param lhsTree the tree for the field whose type will be changed
+   * @param element the element for the field whose type will be changed
+   * @param fieldName the name of the field whose type will be changed
+   * @param rhsATM the type of the expression being assigned to the field, which is side-effected by
+   *     this method
+   */
+  public void wpiAdjustForUpdateField(
+      Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) {}
+
+  /**
+   * Changes the type of {@code rhsATM} when being assigned to anything other than a field, for use
+   * by whole-program inference. The default implementation does nothing.
+   *
+   * @param rhsATM the type of the rhs of the pseudo-assignment, which is side-effected by this
+   *     method
+   */
+  public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) {}
+
+  /**
+   * Side-effects the method or constructor annotations to make any desired changes before writing
+   * to an annotation file.
+   *
+   * @param methodAnnos the method or constructor annotations to modify
+   */
+  public void prepareMethodForWriting(AMethod methodAnnos) {
+    // This implementation does nothing.
+  }
+
+  /**
+   * Side-effects the method or constructor annotations to make any desired changes before writing
+   * to an ajava file.
+   *
+   * @param methodAnnos the method or constructor annotations to modify
+   */
+  public void prepareMethodForWriting(
+      WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) {
+    // This implementation does nothing.
+  }
+
+  /**
+   * Does {@code anno}, which is an {@link org.checkerframework.framework.qual.AnnotatedFor}
+   * annotation, apply to this checker?
+   *
+   * @param annotatedForAnno an {@link AnnotatedFor} annotation
+   * @return whether {@code anno} applies to this checker
+   */
+  public boolean doesAnnotatedForApplyToThisChecker(AnnotationMirror annotatedForAnno) {
+    List<String> annotatedForCheckers =
+        AnnotationUtils.getElementValueArray(
+            annotatedForAnno, annotatedForValueElement, String.class);
+    for (String annoForChecker : annotatedForCheckers) {
+      if (checker.getUpstreamCheckerNames().contains(annoForChecker)
+          || CheckerMain.matchesFullyQualifiedProcessor(
+              annoForChecker, checker.getUpstreamCheckerNames(), true)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Get the {@code expression} field/element of the given contract annotation.
+   *
+   * @param contractAnno a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link
+   *     EnsuresQualifier}
+   * @return the {@code expression} field/element of the given annotation
+   */
+  public List<String> getContractExpressions(AnnotationMirror contractAnno) {
+    DeclaredType annoType = contractAnno.getAnnotationType();
+    if (types.isSameType(annoType, requiresQualifierTM)) {
+      return AnnotationUtils.getElementValueArray(
+          contractAnno, requiresQualifierExpressionElement, String.class);
+    } else if (types.isSameType(annoType, ensuresQualifierTM)) {
+      return AnnotationUtils.getElementValueArray(
+          contractAnno, ensuresQualifierExpressionElement, String.class);
+    } else if (types.isSameType(annoType, ensuresQualifierIfTM)) {
+      return AnnotationUtils.getElementValueArray(
+          contractAnno, ensuresQualifierIfExpressionElement, String.class);
+    } else {
+      throw new BugInCF("Not a contract annotation: " + contractAnno);
+    }
+  }
+
+  /**
+   * Get the {@code value} field/element of the given contract list annotation.
+   *
+   * @param contractListAnno a {@link RequiresQualifier.List}, {@link EnsuresQualifier.List}, or
+   *     {@link EnsuresQualifier.List}
+   * @return the {@code value} field/element of the given annotation
+   */
+  public List<AnnotationMirror> getContractListValues(AnnotationMirror contractListAnno) {
+    DeclaredType annoType = contractListAnno.getAnnotationType();
+    if (types.isSameType(annoType, requiresQualifierListTM)) {
+      return AnnotationUtils.getElementValueArray(
+          contractListAnno, requiresQualifierListValueElement, AnnotationMirror.class);
+    } else if (types.isSameType(annoType, ensuresQualifierListTM)) {
+      return AnnotationUtils.getElementValueArray(
+          contractListAnno, ensuresQualifierListValueElement, AnnotationMirror.class);
+    } else if (types.isSameType(annoType, ensuresQualifierIfListTM)) {
+      return AnnotationUtils.getElementValueArray(
+          contractListAnno, ensuresQualifierIfListValueElement, AnnotationMirror.class);
+    } else {
+      throw new BugInCF("Not a contract list annotation: " + contractListAnno);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFormatter.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFormatter.java
new file mode 100644
index 0000000..74bc187
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFormatter.java
@@ -0,0 +1,32 @@
+package org.checkerframework.framework.type;
+
+import org.checkerframework.dataflow.qual.SideEffectFree;
+
+/**
+ * Converts an AnnotatedTypeMirror mirror into a formatted string. For converting AnnotationMirrors:
+ *
+ * @see org.checkerframework.framework.util.AnnotationFormatter
+ */
+public interface AnnotatedTypeFormatter {
+  /**
+   * Formats type into a String. Uses an implementation specific default for printing "invisible
+   * annotations"
+   *
+   * @see org.checkerframework.framework.qual.InvisibleQualifier
+   * @param type the type to be converted
+   * @return a string representation of type
+   */
+  @SideEffectFree
+  public String format(AnnotatedTypeMirror type);
+
+  /**
+   * Formats type into a String.
+   *
+   * @param type the type to be converted
+   * @param printVerbose whether or not to print verbosely
+   * @see org.checkerframework.framework.qual.InvisibleQualifier
+   * @return a string representation of type
+   */
+  @SideEffectFree
+  public String format(AnnotatedTypeMirror type, boolean printVerbose);
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java
new file mode 100644
index 0000000..e50e919
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java
@@ -0,0 +1,2270 @@
+package org.checkerframework.framework.type;
+
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+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.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.NoType;
+import javax.lang.model.type.NullType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.UnionType;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.ErrorTypeKindException;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TypeKindUtils;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * Represents an annotated type in the Java programming language. Types include primitive types,
+ * declared types (class and interface types), array types, type variables, and the null type. Also
+ * represented are wildcard type arguments, the signature and return types of executables, and
+ * pseudo-types corresponding to packages and to the keyword {@code void}.
+ *
+ * <p>To implement operations based on the class of an {@code AnnotatedTypeMirror} object, either
+ * use a visitor or use the result of the {@link #getKind()} method.
+ *
+ * @see TypeMirror
+ */
+public abstract class AnnotatedTypeMirror {
+
+  /**
+   * Creates an AnnotatedTypeMirror for the provided type. The result contains no annotations.
+   *
+   * @param type the underlying type for the resulting AnnotatedTypeMirror
+   * @param atypeFactory the type factory that will build the result
+   * @param isDeclaration true if the result should represent a declaration, rather than a use, of a
+   *     type
+   * @return an AnnotatedTypeMirror whose underlying type is {@code type}
+   */
+  public static AnnotatedTypeMirror createType(
+      TypeMirror type, AnnotatedTypeFactory atypeFactory, boolean isDeclaration) {
+    if (type == null) {
+      throw new BugInCF("AnnotatedTypeMirror.createType: input type must not be null");
+    }
+
+    AnnotatedTypeMirror result;
+    switch (type.getKind()) {
+      case ARRAY:
+        result = new AnnotatedArrayType((ArrayType) type, atypeFactory);
+        break;
+      case DECLARED:
+        result = new AnnotatedDeclaredType((DeclaredType) type, atypeFactory, isDeclaration);
+        break;
+      case ERROR:
+        throw new BugInCF(
+            "AnnotatedTypeMirror.createType: input is not compilable. Found error type: " + type);
+
+      case EXECUTABLE:
+        result = new AnnotatedExecutableType((ExecutableType) type, atypeFactory);
+        break;
+      case VOID:
+      case PACKAGE:
+      case NONE:
+        result = new AnnotatedNoType((NoType) type, atypeFactory);
+        break;
+      case NULL:
+        result = new AnnotatedNullType((NullType) type, atypeFactory);
+        break;
+      case TYPEVAR:
+        result = new AnnotatedTypeVariable((TypeVariable) type, atypeFactory, isDeclaration);
+        break;
+      case WILDCARD:
+        result = new AnnotatedWildcardType((WildcardType) type, atypeFactory);
+        break;
+      case INTERSECTION:
+        result = new AnnotatedIntersectionType((IntersectionType) type, atypeFactory);
+        break;
+      case UNION:
+        result = new AnnotatedUnionType((UnionType) type, atypeFactory);
+        break;
+      default:
+        if (type.getKind().isPrimitive()) {
+          result = new AnnotatedPrimitiveType((PrimitiveType) type, atypeFactory);
+          break;
+        }
+        throw new BugInCF(
+            "AnnotatedTypeMirror.createType: unidentified type "
+                + type
+                + " ("
+                + type.getKind()
+                + ")");
+    }
+    /*if (jctype.isAnnotated()) {
+        result.addAnnotations(jctype.getAnnotationMirrors());
+    }*/
+    return result;
+  }
+
+  protected static final EqualityAtmComparer EQUALITY_COMPARER = new EqualityAtmComparer();
+  protected static final HashcodeAtmVisitor HASHCODE_VISITOR = new HashcodeAtmVisitor();
+
+  /** The factory to use for lazily creating annotated types. */
+  protected final AnnotatedTypeFactory atypeFactory;
+
+  /** Actual type wrapped with this AnnotatedTypeMirror. */
+  protected final TypeMirror underlyingType;
+
+  /**
+   * Saves the result of {@code underlyingType.toString().hashcode()} to use when computing the hash
+   * code of this. (Because AnnotatedTypeMirrors are mutable, the hash code for this cannot be
+   * saved.) Call {@link #getUnderlyingTypeHashCode()} rather than using the field directly.
+   */
+  private int underlyingTypeHashCode = -1;
+
+  /** The annotations on this type. */
+  // AnnotationMirror doesn't override Object.hashCode, .equals, so we use
+  // the class name of Annotation instead.
+  // Caution: Assumes that a type can have at most one AnnotationMirror for any Annotation type.
+  protected final Set<AnnotationMirror> annotations = AnnotationUtils.createAnnotationSet();
+
+  /** The explicitly written annotations on this type. */
+  // TODO: use this to cache the result once computed? For generic types?
+  // protected final Set<AnnotationMirror> explicitannotations =
+  // AnnotationUtils.createAnnotationSet();
+
+  /**
+   * Constructor for AnnotatedTypeMirror.
+   *
+   * @param underlyingType the underlying type
+   * @param atypeFactory used to create further types and to access global information (Types,
+   *     Elements, ...)
+   */
+  private AnnotatedTypeMirror(TypeMirror underlyingType, AnnotatedTypeFactory atypeFactory) {
+    this.underlyingType = underlyingType;
+    assert atypeFactory != null;
+    this.atypeFactory = atypeFactory;
+  }
+
+  @Override
+  public final boolean equals(Object o) {
+    if (o == this) {
+      return true;
+    }
+
+    if (!(o instanceof AnnotatedTypeMirror)) {
+      return false;
+    }
+
+    return EQUALITY_COMPARER.visit(this, (AnnotatedTypeMirror) o, null);
+  }
+
+  @Pure
+  @Override
+  public final int hashCode() {
+    return HASHCODE_VISITOR.visit(this);
+  }
+
+  /**
+   * Applies a visitor to this type.
+   *
+   * @param <R> the return type of the visitor's methods
+   * @param <P> the type of the additional parameter to the visitor's methods
+   * @param v the visitor operating on this type
+   * @param p additional parameter to the visitor
+   * @return a visitor-specified result
+   */
+  public abstract <R, P> R accept(AnnotatedTypeVisitor<R, P> v, P p);
+
+  /**
+   * Returns the {@code kind} of this type.
+   *
+   * @return the kind of this type
+   */
+  public TypeKind getKind() {
+    return underlyingType.getKind();
+  }
+
+  /**
+   * Given a primitive type, return its kind. Given a boxed primitive type, return the corresponding
+   * primitive type kind. Otherwise, return null.
+   *
+   * @return a primitive type kind if this is a primitive type or boxed primitive type; otherwise
+   *     null
+   */
+  public TypeKind getPrimitiveKind() {
+    return TypeKindUtils.primitiveOrBoxedToTypeKind(getUnderlyingType());
+  }
+
+  /**
+   * Returns the underlying unannotated Java type, which this wraps.
+   *
+   * @return the underlying type
+   */
+  public TypeMirror getUnderlyingType() {
+    return underlyingType;
+  }
+
+  /**
+   * Returns true if this type mirror represents a declaration, rather than a use, of a type.
+   *
+   * <p>For example, {@code class List<T> { ... }} declares a new type {@code List<T>}, while {@code
+   * List<Integer>} is a use of the type.
+   *
+   * @return true if this represents a declaration
+   */
+  public boolean isDeclaration() {
+    return false;
+  }
+
+  public AnnotatedTypeMirror asUse() {
+    return this;
+  }
+
+  /**
+   * Returns true if an annotation from the given sub-hierarchy targets this type.
+   *
+   * <p>It doesn't account for annotations in deep types (type arguments, array components, etc).
+   *
+   * @param p the qualifier hierarchy to check for
+   * @return true iff an annotation from the same hierarchy as p is present
+   */
+  public boolean isAnnotatedInHierarchy(AnnotationMirror p) {
+    return getAnnotationInHierarchy(p) != null;
+  }
+
+  /**
+   * Returns an annotation from the given sub-hierarchy, if such an annotation targets this type;
+   * otherwise returns null.
+   *
+   * <p>It doesn't account for annotations in deep types (type arguments, array components, etc).
+   *
+   * <p>If there is only one hierarchy, you can use {@link #getAnnotation()} instead.
+   *
+   * <p>May return null if the receiver is a type variable or a wildcard without a primary
+   * annotation, or if the receiver is not yet fully annotated.
+   *
+   * @param p the qualifier hierarchy to check for
+   * @return an annotation from the same hierarchy as p if present
+   */
+  public AnnotationMirror getAnnotationInHierarchy(AnnotationMirror p) {
+    AnnotationMirror aliased = p;
+    if (!atypeFactory.isSupportedQualifier(aliased)) {
+      aliased = atypeFactory.canonicalAnnotation(p);
+    }
+    if (atypeFactory.isSupportedQualifier(aliased)) {
+      QualifierHierarchy qualHier = this.atypeFactory.getQualifierHierarchy();
+      AnnotationMirror anno = qualHier.findAnnotationInSameHierarchy(annotations, aliased);
+      if (anno != null) {
+        return anno;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns an annotation from the given sub-hierarchy, if such an annotation is present on this
+   * type or on its extends bounds; otherwise returns null.
+   *
+   * <p>It doesn't account for annotations in deep types (type arguments, array components, etc).
+   *
+   * @param p the qualifier hierarchy to check for
+   * @return an annotation from the same hierarchy as p if present
+   */
+  public AnnotationMirror getEffectiveAnnotationInHierarchy(AnnotationMirror p) {
+    AnnotationMirror aliased = p;
+    if (!atypeFactory.isSupportedQualifier(aliased)) {
+      aliased = atypeFactory.canonicalAnnotation(p);
+    }
+    if (atypeFactory.isSupportedQualifier(aliased)) {
+      QualifierHierarchy qualHier = this.atypeFactory.getQualifierHierarchy();
+      AnnotationMirror anno =
+          qualHier.findAnnotationInSameHierarchy(getEffectiveAnnotations(), aliased);
+      if (anno != null) {
+        return anno;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns the annotations on this type. It does not include annotations in deep types (type
+   * arguments, array components, etc).
+   *
+   * <p>To get the single annotation in a particular hierarchy, use {@link
+   * #getAnnotationInHierarchy}. If there is only one hierarchy, you can use {@link #getAnnotation}.
+   *
+   * @return a unmodifiable set of the annotations on this
+   */
+  public final Set<AnnotationMirror> getAnnotations() {
+    return Collections.unmodifiableSet(annotations);
+  }
+
+  /**
+   * Returns the annotations on this type; mutations affect this object, because the return type is
+   * an alias of the {@code annotations} field. It does not include annotations in deep types (type
+   * arguments, array components, etc).
+   *
+   * <p>The returned set should not be modified, but for efficiency reasons modification is not
+   * prevented. Modifications might break invariants.
+   *
+   * @return the set of the annotations on this; mutations affect this object
+   */
+  protected final Set<AnnotationMirror> getAnnotationsField() {
+    return annotations;
+  }
+
+  /**
+   * Returns the single annotation on this type. It does not include annotations in deep types (type
+   * arguments, array components, etc).
+   *
+   * <p>This method requires that there is only a single hierarchy. In that case, it is equivalent
+   * to {@link #getAnnotationInHierarchy}.
+   *
+   * @see #getAnnotations
+   * @return the annotation on this, or null if none (which can only happen if {@code this} is a
+   *     type variable or wildcard)
+   */
+  public final @Nullable AnnotationMirror getAnnotation() {
+    if (annotations.isEmpty()) {
+      // This AnnotatedTypeMirror must be a type variable or wildcard.
+      return null;
+    }
+    if (annotations.size() != 1) {
+      throw new BugInCF("Bad annotation size for getAnnotation(): " + this);
+    }
+    return annotations.iterator().next();
+  }
+
+  /**
+   * Returns the "effective" annotations on this type, i.e. the annotations on the type itself, or
+   * on the upper/extends bound of a type variable/wildcard (recursively, until a class type is
+   * reached).
+   *
+   * @return a set of the annotations on this
+   */
+  public Set<AnnotationMirror> getEffectiveAnnotations() {
+    Set<AnnotationMirror> effectiveAnnotations = getErased().getAnnotations();
+    //        assert atypeFactory.qualHierarchy.getWidth() == effectiveAnnotations
+    //                .size() : "Invalid number of effective annotations ("
+    //                + effectiveAnnotations + "). Should be "
+    //                + atypeFactory.qualHierarchy.getWidth() + " but is "
+    //                + effectiveAnnotations.size() + ". Type: " + this;
+    return effectiveAnnotations;
+  }
+
+  /**
+   * Returns the actual annotation mirror used to annotate this type, whose Class equals the passed
+   * annoClass if one exists, null otherwise.
+   *
+   * @param annoClass annotation class
+   * @return the annotation mirror for anno
+   */
+  public AnnotationMirror getAnnotation(Class<? extends Annotation> annoClass) {
+    for (AnnotationMirror annoMirror : annotations) {
+      if (atypeFactory.areSameByClass(annoMirror, annoClass)) {
+        return annoMirror;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns the actual annotation mirror used to annotate this type, whose name equals the passed
+   * {@code annoName} if one exists, null otherwise.
+   *
+   * @param annoName annotation name
+   * @return the annotation mirror for annoName
+   */
+  public AnnotationMirror getAnnotation(String annoName) {
+    for (AnnotationMirror annoMirror : annotations) {
+      if (AnnotationUtils.areSameByName(annoMirror, annoName)) {
+        return annoMirror;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns the set of explicitly written annotations on this type that are supported by this
+   * checker. This is useful to check the validity of annotations explicitly present on a type, as
+   * flow inference might add annotations that were not previously present. Note that since
+   * AnnotatedTypeMirror instances are created for type uses, this method will return explicit
+   * annotations in type use locations but will not return explicit annotations that had an impact
+   * on defaulting, such as an explicit annotation on a class declaration. For example, given:
+   *
+   * <p>{@code @MyExplicitAnno class MyClass {}; MyClass myClassInstance; }
+   *
+   * <p>the result of calling {@code
+   * atypeFactory.getAnnotatedType(variableTreeForMyClassInstance).getExplicitAnnotations()}
+   *
+   * <p>will not contain {@code @MyExplicitAnno}.
+   *
+   * @return the set of explicitly written annotations on this type that are supported by this
+   *     checker
+   */
+  public Set<AnnotationMirror> getExplicitAnnotations() {
+    // TODO JSR 308: The explicit type annotations should be always present
+    Set<AnnotationMirror> explicitAnnotations = AnnotationUtils.createAnnotationSet();
+    List<? extends AnnotationMirror> typeAnnotations =
+        this.getUnderlyingType().getAnnotationMirrors();
+
+    for (AnnotationMirror explicitAnno : typeAnnotations) {
+      if (atypeFactory.isSupportedQualifier(explicitAnno)) {
+        explicitAnnotations.add(explicitAnno);
+      }
+    }
+
+    return explicitAnnotations;
+  }
+
+  /**
+   * Determines whether this type contains the given annotation. This method considers the
+   * annotation's values, that is, if the type is "@A("s") @B(3) Object" a call with "@A("t") or
+   * "@A" will return false, whereas a call with "@B(3)" will return true.
+   *
+   * <p>In contrast to {@link #hasAnnotationRelaxed(AnnotationMirror)} this method also compares
+   * annotation values.
+   *
+   * @param a the annotation to check for
+   * @return true iff the type contains the annotation {@code a}
+   * @see #hasAnnotationRelaxed(AnnotationMirror)
+   */
+  public boolean hasAnnotation(AnnotationMirror a) {
+    return AnnotationUtils.containsSame(annotations, a);
+  }
+
+  /**
+   * Determines whether this type contains an annotation with the same annotation type as a
+   * particular annotation. This method does not consider an annotation's values.
+   *
+   * @param a the class of annotation to check for
+   * @return true iff the type contains an annotation with the same type as the annotation given by
+   *     {@code a}
+   */
+  public boolean hasAnnotation(Class<? extends Annotation> a) {
+    return getAnnotation(a) != null;
+  }
+
+  /**
+   * Returns the actual effective annotation mirror used to annotate this type, whose Class equals
+   * the passed annoClass if one exists, null otherwise.
+   *
+   * @param annoClass annotation class
+   * @return the annotation mirror for anno
+   */
+  public AnnotationMirror getEffectiveAnnotation(Class<? extends Annotation> annoClass) {
+    for (AnnotationMirror annoMirror : getEffectiveAnnotations()) {
+      if (atypeFactory.areSameByClass(annoMirror, annoClass)) {
+        return annoMirror;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * A version of hasAnnotation that considers annotations on the upper bound of wildcards and type
+   * variables.
+   *
+   * @see #hasAnnotation(Class)
+   */
+  public boolean hasEffectiveAnnotation(Class<? extends Annotation> a) {
+    return getEffectiveAnnotation(a) != null;
+  }
+
+  /**
+   * A version of hasAnnotation that considers annotations on the upper bound of wildcards and type
+   * variables.
+   *
+   * @see #hasAnnotation(AnnotationMirror)
+   */
+  public boolean hasEffectiveAnnotation(AnnotationMirror a) {
+    return AnnotationUtils.containsSame(getEffectiveAnnotations(), a);
+  }
+
+  /**
+   * Determines whether this type contains the given annotation explicitly written at declaration.
+   * This method considers the annotation's values, that is, if the type is {@code @A("s") @B(3)
+   * Object}, a call with {@code @A("t")} or {@code @A} will return false, whereas a call with
+   * {@code @B(3)} will return true.
+   *
+   * <p>In contrast to {@link #hasExplicitAnnotationRelaxed(AnnotationMirror)} this method also
+   * compares annotation values.
+   *
+   * <p>See the documentation for {@link #getExplicitAnnotations()} for details on which explicit
+   * annotations are not included.
+   *
+   * @param a the annotation to check for
+   * @return true iff the annotation {@code a} is explicitly written on the type
+   * @see #hasExplicitAnnotationRelaxed(AnnotationMirror)
+   * @see #getExplicitAnnotations()
+   */
+  public boolean hasExplicitAnnotation(AnnotationMirror a) {
+    return AnnotationUtils.containsSame(getExplicitAnnotations(), a);
+  }
+
+  /**
+   * Determines whether this type contains an annotation with the same annotation type as a
+   * particular annotation. This method does not consider an annotation's values, that is, if the
+   * type is "@A("s") @B(3) Object" a call with "@A("t"), "@A", or "@B" will return true.
+   *
+   * @param a the annotation to check for
+   * @return true iff the type contains an annotation with the same type as the annotation given by
+   *     {@code a}
+   * @see #hasAnnotation(AnnotationMirror)
+   */
+  public boolean hasAnnotationRelaxed(AnnotationMirror a) {
+    return AnnotationUtils.containsSameByName(annotations, a);
+  }
+
+  /**
+   * A version of hasAnnotationRelaxed that considers annotations on the upper bound of wildcards
+   * and type variables.
+   *
+   * @see #hasAnnotationRelaxed(AnnotationMirror)
+   */
+  public boolean hasEffectiveAnnotationRelaxed(AnnotationMirror a) {
+    return AnnotationUtils.containsSameByName(getEffectiveAnnotations(), a);
+  }
+
+  /**
+   * A version of hasAnnotationRelaxed that only considers annotations that are explicitly written
+   * on the type.
+   *
+   * <p>See the documentation for {@link #getExplicitAnnotations()} for details on which explicit
+   * annotations are not included.
+   *
+   * @see #hasAnnotationRelaxed(AnnotationMirror)
+   * @see #getExplicitAnnotations()
+   */
+  public boolean hasExplicitAnnotationRelaxed(AnnotationMirror a) {
+    return AnnotationUtils.containsSameByName(getExplicitAnnotations(), a);
+  }
+
+  /**
+   * Determines whether this type contains an explicitly written annotation with the same annotation
+   * type as a particular annotation. This method does not consider an annotation's values.
+   *
+   * <p>See the documentation for {@link #getExplicitAnnotations()} for details on which explicit
+   * annotations are not included.
+   *
+   * @param a the class of annotation to check for
+   * @return true iff the type contains an explicitly written annotation with the same type as the
+   *     annotation given by {@code a}
+   * @see #getExplicitAnnotations()
+   */
+  public boolean hasExplicitAnnotation(Class<? extends Annotation> a) {
+    return AnnotationUtils.containsSameByName(getExplicitAnnotations(), getAnnotation(a));
+  }
+
+  /**
+   * Adds an annotation to this type. Only annotations supported by the type factory are added.
+   *
+   * @param a the annotation to add
+   */
+  public void addAnnotation(AnnotationMirror a) {
+    if (a == null) {
+      throw new BugInCF("AnnotatedTypeMirror.addAnnotation: null argument.");
+    }
+    if (atypeFactory.isSupportedQualifier(a)) {
+      this.annotations.add(a);
+    } else {
+      AnnotationMirror aliased = atypeFactory.canonicalAnnotation(a);
+      if (atypeFactory.isSupportedQualifier(aliased)) {
+        addAnnotation(aliased);
+      }
+    }
+  }
+
+  /**
+   * Adds an annotation to this type, removing any existing annotation from the same qualifier
+   * hierarchy first.
+   *
+   * @param a the annotation to add
+   */
+  public void replaceAnnotation(AnnotationMirror a) {
+    this.removeAnnotationInHierarchy(a);
+    this.addAnnotation(a);
+  }
+
+  /**
+   * Adds an annotation to this type.
+   *
+   * @param a the class of the annotation to add
+   */
+  public void addAnnotation(Class<? extends Annotation> a) {
+    AnnotationMirror anno = AnnotationBuilder.fromClass(atypeFactory.elements, a);
+    addAnnotation(anno);
+  }
+
+  /**
+   * Adds multiple annotations to this type.
+   *
+   * @param annotations the annotations to add
+   */
+  public void addAnnotations(Iterable<? extends AnnotationMirror> annotations) {
+    for (AnnotationMirror a : annotations) {
+      this.addAnnotation(a);
+    }
+  }
+
+  /**
+   * Adds each of the given annotations to the current type, only if no annotation from the same
+   * qualifier hierarchy is present.
+   *
+   * @param annotations the annotations to add
+   */
+  public void addMissingAnnotations(Iterable<? extends AnnotationMirror> annotations) {
+    for (AnnotationMirror a : annotations) {
+      if (!this.isAnnotatedInHierarchy(a)) {
+        this.addAnnotation(a);
+      }
+    }
+  }
+
+  /**
+   * Adds multiple annotations to this type, removing any existing annotations from the same
+   * qualifier hierarchy first.
+   *
+   * @param replAnnos the annotations to replace
+   */
+  public void replaceAnnotations(Iterable<? extends AnnotationMirror> replAnnos) {
+    for (AnnotationMirror a : replAnnos) {
+      this.replaceAnnotation(a);
+    }
+  }
+
+  /**
+   * Removes an annotation from the type.
+   *
+   * @param a the annotation to remove
+   * @return true if the annotation was removed, false if the type's annotations were unchanged
+   */
+  public boolean removeAnnotation(AnnotationMirror a) {
+    AnnotationMirror anno = AnnotationUtils.getSame(annotations, a);
+    if (anno != null) {
+      return annotations.remove(anno);
+    }
+    return false;
+  }
+
+  /**
+   * Removes an annotation of the given class from the type.
+   *
+   * @param a the class of the annotation to remove
+   * @return true if the annotation was removed, false if the type's annotations were unchanged
+   */
+  public boolean removeAnnotationByClass(Class<? extends Annotation> a) {
+    AnnotationMirror anno = atypeFactory.getAnnotationByClass(annotations, a);
+    if (anno != null) {
+      return annotations.remove(anno);
+    }
+    return false;
+  }
+
+  /**
+   * Remove any annotation that is in the same qualifier hierarchy as the parameter.
+   *
+   * @param a an annotation from the same qualifier hierarchy
+   * @return if an annotation was removed
+   */
+  public boolean removeAnnotationInHierarchy(AnnotationMirror a) {
+    AnnotationMirror prev = this.getAnnotationInHierarchy(a);
+    if (prev != null) {
+      return this.removeAnnotation(prev);
+    }
+    return false;
+  }
+
+  /**
+   * Remove an annotation that is in the same qualifier hierarchy as the parameter, unless it's the
+   * top annotation.
+   *
+   * @param a an annotation from the same qualifier hierarchy
+   * @return if an annotation was removed
+   */
+  public boolean removeNonTopAnnotationInHierarchy(AnnotationMirror a) {
+    AnnotationMirror prev = this.getAnnotationInHierarchy(a);
+    QualifierHierarchy qualHier = this.atypeFactory.getQualifierHierarchy();
+    if (prev != null && !prev.equals(qualHier.getTopAnnotation(a))) {
+      return this.removeAnnotation(prev);
+    }
+    return false;
+  }
+
+  /**
+   * Removes multiple annotations from the type.
+   *
+   * @param annotations the annotations to remove
+   * @return true if at least one annotation was removed, false if the type's annotations were
+   *     unchanged
+   */
+  public boolean removeAnnotations(Iterable<? extends AnnotationMirror> annotations) {
+    boolean changed = false;
+    for (AnnotationMirror a : annotations) {
+      changed |= this.removeAnnotation(a);
+    }
+    return changed;
+  }
+
+  /**
+   * Removes all primary annotations on this type. Make sure to add an annotation after calling
+   * this.
+   *
+   * <p>This method should only be used in very specific situations. For individual type systems, it
+   * is generally better to use {@link #removeAnnotation(AnnotationMirror)} and similar methods.
+   */
+  public void clearAnnotations() {
+    annotations.clear();
+  }
+
+  @SideEffectFree
+  @Override
+  public final String toString() {
+    return atypeFactory.getAnnotatedTypeFormatter().format(this);
+  }
+
+  @SideEffectFree
+  public final String toString(boolean verbose) {
+    return atypeFactory.getAnnotatedTypeFormatter().format(this, verbose);
+  }
+
+  /**
+   * Returns the erasure type of the this type, according to JLS specifications.
+   *
+   * @see <a
+   *     href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.6">https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.6</a>
+   * @return the erasure of this AnnotatedTypeMirror, this is always a copy even if the erasure and
+   *     the original type are equivalent
+   */
+  public AnnotatedTypeMirror getErased() {
+    return deepCopy();
+  }
+
+  /**
+   * Returns a deep copy of this type. A deep copy implies that each component type is copied
+   * recursively and the returned type refers to those copies in its component locations.
+   *
+   * <p>Note: deepCopy provides two important properties in the returned copy:
+   *
+   * <ol>
+   *   <li>Structure preservation -- The exact structure of the original AnnotatedTypeMirror is
+   *       preserved in the copy including all component types.
+   *   <li>Annotation preservation -- All of the annotations from the original AnnotatedTypeMirror
+   *       and its components have been copied to the new type.
+   * </ol>
+   *
+   * If copyAnnotations is set to false, the second property, Annotation preservation, is removed.
+   * This is useful for cases in which the user may want to copy the structure of a type exactly but
+   * NOT its annotations.
+   *
+   * @return a deep copy
+   */
+  public abstract AnnotatedTypeMirror deepCopy(final boolean copyAnnotations);
+
+  /**
+   * Returns a deep copy of this type with annotations.
+   *
+   * <p>Each subclass implements this method with the subclass return type. The method body must
+   * always be a call to deepCopy(true).
+   *
+   * @return a deep copy of this type with annotations
+   * @see #deepCopy(boolean)
+   */
+  public abstract AnnotatedTypeMirror deepCopy();
+
+  /**
+   * Returns a shallow copy of this type. A shallow copy implies that each component type in the
+   * output copy refers to the same object as the object being copied.
+   *
+   * @param copyAnnotations whether copy should have annotations, i.e. whether field {@code
+   *     annotations} should be copied.
+   */
+  public abstract AnnotatedTypeMirror shallowCopy(boolean copyAnnotations);
+
+  /**
+   * Returns a shallow copy of this type with annotations.
+   *
+   * <p>Each subclass implements this method with the subclass return type. The method body must
+   * always be a call to shallowCopy(true).
+   *
+   * @see #shallowCopy(boolean)
+   * @return a shallow copy of this type with annotations
+   */
+  public abstract AnnotatedTypeMirror shallowCopy();
+
+  /**
+   * Returns whether this type or any component type is a wildcard type for which Java 7 type
+   * inference is insufficient. See issue 979, or the documentation on AnnotatedWildcardType.
+   *
+   * @return whether this type or any component type is a wildcard type for which Java 7 type
+   *     inference is insufficient
+   */
+  public boolean containsUninferredTypeArguments() {
+    return atypeFactory.containsUninferredTypeArguments(this);
+  }
+
+  /**
+   * Create an {@link AnnotatedDeclaredType} with the underlying type of {@link Object}. It includes
+   * any annotations placed by {@link AnnotatedTypeFactory#fromElement(Element)}.
+   *
+   * @param atypeFactory type factory to use
+   * @return AnnotatedDeclaredType for Object
+   */
+  protected static AnnotatedDeclaredType createTypeOfObject(AnnotatedTypeFactory atypeFactory) {
+    AnnotatedDeclaredType objectType =
+        atypeFactory.fromElement(
+            atypeFactory.elements.getTypeElement(Object.class.getCanonicalName()));
+    objectType.declaration = false;
+    return objectType;
+  }
+
+  /**
+   * Returns the result of calling {@code underlyingType.toString().hashcode()}. This method saves
+   * the result in a field so that it isn't recomputed each time.
+   *
+   * @return the result of calling {@code underlyingType.toString().hashcode()}
+   */
+  public int getUnderlyingTypeHashCode() {
+    if (underlyingTypeHashCode == -1) {
+      underlyingTypeHashCode = underlyingType.toString().hashCode();
+    }
+    return underlyingTypeHashCode;
+  }
+
+  /** Represents a declared type (whether class or interface). */
+  public static class AnnotatedDeclaredType extends AnnotatedTypeMirror {
+
+    /** Parametrized Type Arguments. */
+    protected List<AnnotatedTypeMirror> typeArgs;
+
+    /**
+     * Whether the type was initially raw, i.e. the user did not provide the type arguments.
+     * typeArgs will contain inferred type arguments, which might be too conservative at the moment.
+     * TODO: improve inference.
+     *
+     * <p>Ideally, the field would be final. However, when we determine the supertype of a raw type,
+     * we need to set wasRaw for the supertype.
+     */
+    private boolean wasRaw;
+
+    /** The enclosing type. May be null. */
+    protected @Nullable AnnotatedDeclaredType enclosingType;
+
+    /** True if this represents a declaration, rather than a use, of a type. */
+    private boolean declaration;
+
+    /**
+     * Constructor for this type. The result contains no annotations.
+     *
+     * @param type underlying kind of this type
+     * @param atypeFactory the AnnotatedTypeFactory used to create this type
+     */
+    private AnnotatedDeclaredType(
+        DeclaredType type, AnnotatedTypeFactory atypeFactory, boolean declaration) {
+      super(type, atypeFactory);
+      TypeElement typeelem = (TypeElement) type.asElement();
+      DeclaredType declty = (DeclaredType) typeelem.asType();
+      wasRaw = !declty.getTypeArguments().isEmpty() && type.getTypeArguments().isEmpty();
+
+      TypeMirror encl = type.getEnclosingType();
+      if (encl.getKind() == TypeKind.DECLARED) {
+        this.enclosingType = (AnnotatedDeclaredType) createType(encl, atypeFactory, declaration);
+      } else if (encl.getKind() == TypeKind.NONE) {
+        this.enclosingType = null;
+      } else {
+        throw new BugInCF(
+            "AnnotatedDeclaredType: unsupported enclosing type: "
+                + type.getEnclosingType()
+                + " ("
+                + encl.getKind()
+                + ")");
+      }
+
+      this.declaration = declaration;
+    }
+
+    @Override
+    public boolean isDeclaration() {
+      return declaration;
+    }
+
+    @Override
+    public AnnotatedDeclaredType deepCopy(boolean copyAnnotations) {
+      return (AnnotatedDeclaredType) new AnnotatedTypeCopier(copyAnnotations).visit(this);
+    }
+
+    @Override
+    public AnnotatedDeclaredType deepCopy() {
+      return deepCopy(true);
+    }
+
+    @Override
+    public AnnotatedDeclaredType asUse() {
+      if (!this.isDeclaration()) {
+        return this;
+      }
+      AnnotatedDeclaredType result = this.shallowCopy(true);
+      result.declaration = false;
+      // setTypeArguments calls asUse on all the new type arguments.
+      result.setTypeArguments(typeArgs);
+
+      return result;
+    }
+
+    @Override
+    public <R, P> R accept(AnnotatedTypeVisitor<R, P> v, P p) {
+      return v.visitDeclared(this, p);
+    }
+
+    /**
+     * Sets the type arguments on this type.
+     *
+     * @param ts the type arguments
+     */
+    // WMD
+    public void setTypeArguments(List<? extends AnnotatedTypeMirror> ts) {
+      if (ts == null || ts.isEmpty()) {
+        typeArgs = Collections.emptyList();
+      } else {
+        if (isDeclaration()) {
+          // TODO: check that all args are really declarations
+          typeArgs = Collections.unmodifiableList(ts);
+        } else {
+          List<AnnotatedTypeMirror> uses = CollectionsPlume.mapList(AnnotatedTypeMirror::asUse, ts);
+          typeArgs = Collections.unmodifiableList(uses);
+        }
+      }
+    }
+
+    /**
+     * Returns the type argument for this type.
+     *
+     * @return the type argument for this type
+     */
+    public List<AnnotatedTypeMirror> getTypeArguments() {
+      if (typeArgs != null) {
+        return typeArgs;
+      } else if (wasRaw()) {
+        // Initialize the type arguments with uninferred wildcards.
+        BoundsInitializer.initializeTypeArgs(this);
+        return typeArgs;
+      } else if (getUnderlyingType().getTypeArguments().isEmpty()) {
+        typeArgs = Collections.emptyList();
+        return typeArgs;
+      } else {
+        // Initialize type argument for a non-raw declared type that has type arguments/
+        BoundsInitializer.initializeTypeArgs(this);
+        return typeArgs;
+      }
+    }
+
+    /**
+     * Returns true if the type was raw, that is, type arguments were not provided but instead
+     * inferred.
+     *
+     * @return true iff the type was raw
+     */
+    public boolean wasRaw() {
+      return wasRaw;
+    }
+
+    /**
+     * Set the wasRaw flag to true. This should only be necessary when determining the supertypes of
+     * a raw type.
+     */
+    protected void setWasRaw() {
+      this.wasRaw = true;
+    }
+
+    @Override
+    public DeclaredType getUnderlyingType() {
+      return (DeclaredType) underlyingType;
+    }
+
+    @Override
+    public List<AnnotatedDeclaredType> directSupertypes() {
+      return Collections.unmodifiableList(SupertypeFinder.directSupertypes(this));
+    }
+
+    @Override
+    public AnnotatedDeclaredType shallowCopy() {
+      return shallowCopy(true);
+    }
+
+    @Override
+    public AnnotatedDeclaredType shallowCopy(boolean copyAnnotations) {
+      AnnotatedDeclaredType type =
+          new AnnotatedDeclaredType(getUnderlyingType(), atypeFactory, declaration);
+      if (copyAnnotations) {
+        type.addAnnotations(this.getAnnotationsField());
+      }
+      type.setEnclosingType(getEnclosingType());
+      type.setTypeArguments(getTypeArguments());
+      return type;
+    }
+
+    /**
+     * Return the declared type with its type arguments removed. This also replaces the underlying
+     * type with its erasure.
+     *
+     * @return a fresh copy of the declared type with no type arguments
+     */
+    @Override
+    public AnnotatedDeclaredType getErased() {
+      AnnotatedDeclaredType erased =
+          (AnnotatedDeclaredType)
+              AnnotatedTypeMirror.createType(
+                  atypeFactory.types.erasure(underlyingType), atypeFactory, declaration);
+      erased.addAnnotations(this.getAnnotations());
+      AnnotatedDeclaredType erasedEnclosing = erased.getEnclosingType();
+      AnnotatedDeclaredType thisEnclosing = this.getEnclosingType();
+      while (erasedEnclosing != null) {
+        erasedEnclosing.addAnnotations(thisEnclosing.getAnnotations());
+        erasedEnclosing = erasedEnclosing.getEnclosingType();
+        thisEnclosing = thisEnclosing.getEnclosingType();
+      }
+      return erased;
+    }
+
+    /**
+     * Sets the enclosing type.
+     *
+     * @param enclosingType the new enclosing type
+     */
+    /*default-visibility*/ void setEnclosingType(@Nullable AnnotatedDeclaredType enclosingType) {
+      this.enclosingType = enclosingType;
+    }
+
+    /**
+     * Returns the enclosing type, as in the type of {@code A} in the type {@code A.B}. May return
+     * null.
+     *
+     * @return enclosingType the enclosing type, or null if this is a top-level type
+     */
+    public @Nullable AnnotatedDeclaredType getEnclosingType() {
+      return enclosingType;
+    }
+  }
+
+  /** Represents a type of an executable. An executable is a method, constructor, or initializer. */
+  public static class AnnotatedExecutableType extends AnnotatedTypeMirror {
+
+    private ExecutableElement element;
+
+    private AnnotatedExecutableType(ExecutableType type, AnnotatedTypeFactory factory) {
+      super(type, factory);
+    }
+
+    protected final List<AnnotatedTypeMirror> paramTypes = new ArrayList<>();
+    protected AnnotatedDeclaredType receiverType;
+    protected AnnotatedTypeMirror returnType;
+    protected final List<AnnotatedTypeMirror> throwsTypes = new ArrayList<>();
+    protected final List<AnnotatedTypeVariable> typeVarTypes = new ArrayList<>();
+
+    /**
+     * Returns true if this type represents a varargs method.
+     *
+     * @return true if this type represents a varargs method
+     */
+    public boolean isVarArgs() {
+      return this.element.isVarArgs();
+    }
+
+    @Override
+    public <R, P> R accept(AnnotatedTypeVisitor<R, P> v, P p) {
+      return v.visitExecutable(this, p);
+    }
+
+    @Override
+    public ExecutableType getUnderlyingType() {
+      return (ExecutableType) this.underlyingType;
+    }
+
+    /**
+     * It never makes sense to add annotations to an executable type - instead, they should be added
+     * to the right component.
+     */
+    @Override
+    public void addAnnotation(AnnotationMirror a) {
+      assert false : "AnnotatedExecutableType.addAnnotation should never be called";
+    }
+
+    /**
+     * Sets the parameter types of this executable type, excluding the receiver.
+     *
+     * @param params the parameter types, excluding the receiver
+     */
+    void setParameterTypes(List<? extends AnnotatedTypeMirror> params) {
+      paramTypes.clear();
+      paramTypes.addAll(params);
+    }
+
+    /**
+     * Returns the parameter types of this executable type, excluding the receiver.
+     *
+     * @return the parameter types of this executable type, excluding the receiver
+     */
+    public List<AnnotatedTypeMirror> getParameterTypes() {
+      if (paramTypes.isEmpty()
+          && !((ExecutableType) underlyingType).getParameterTypes().isEmpty()) { // lazy init
+        for (TypeMirror t : ((ExecutableType) underlyingType).getParameterTypes()) {
+          paramTypes.add(createType(t, atypeFactory, false));
+        }
+      }
+      return Collections.unmodifiableList(paramTypes);
+    }
+
+    /**
+     * Sets the return type of this executable type.
+     *
+     * @param returnType the return type
+     */
+    void setReturnType(AnnotatedTypeMirror returnType) {
+      this.returnType = returnType;
+    }
+
+    /**
+     * The return type of a method or constructor. For constructors, the return type is not VOID,
+     * but the type of the enclosing class.
+     *
+     * @return the return type of this executable type
+     */
+    public AnnotatedTypeMirror getReturnType() {
+      if (returnType == null
+          && element != null
+          && ((ExecutableType) underlyingType).getReturnType() != null) { // lazy init
+        TypeMirror aret = ((ExecutableType) underlyingType).getReturnType();
+        if (aret.getKind() == TypeKind.ERROR) {
+          // Maybe the input is uncompilable, or maybe the type is not completed yet (see Issue
+          // #244).
+          throw new ErrorTypeKindException(
+              "Problem with return type of %s.%s: %s [%s %s]",
+              element, element.getEnclosingElement(), aret, aret.getKind(), aret.getClass());
+        }
+        if (((MethodSymbol) element).isConstructor()) {
+          // For constructors, the underlying return type is void.
+          // Take the type of the enclosing class instead.
+          aret = element.getEnclosingElement().asType();
+          if (aret.getKind() == TypeKind.ERROR) {
+            throw new ErrorTypeKindException(
+                "Input is not compilable; problem with constructor %s return type: %s [%s %s]"
+                    + " (enclosing element = %s [%s])",
+                element,
+                aret,
+                aret.getKind(),
+                aret.getClass(),
+                element.getEnclosingElement(),
+                element.getEnclosingElement().getClass());
+          }
+        }
+        returnType = createType(aret, atypeFactory, false);
+      }
+      return returnType;
+    }
+
+    /**
+     * Sets the receiver type on this executable type.
+     *
+     * @param receiverType the receiver type
+     */
+    void setReceiverType(AnnotatedDeclaredType receiverType) {
+      this.receiverType = receiverType;
+    }
+
+    /**
+     * Returns the receiver type of this executable type; null for static methods and constructors
+     * of top-level classes.
+     *
+     * @return the receiver type of this executable type; null for static methods and constructors
+     *     of top-level classes
+     */
+    public @Nullable AnnotatedDeclaredType getReceiverType() {
+      if (receiverType == null && ElementUtils.hasReceiver(getElement())) {
+
+        TypeElement encl = ElementUtils.enclosingTypeElement(getElement());
+        if (getElement().getKind() == ElementKind.CONSTRUCTOR) {
+          // Can only reach this branch if we're the constructor of a nested class
+          encl = ElementUtils.enclosingTypeElement(encl.getEnclosingElement());
+        }
+        AnnotatedTypeMirror type = createType(encl.asType(), atypeFactory, false);
+        assert type instanceof AnnotatedDeclaredType;
+        receiverType = (AnnotatedDeclaredType) type;
+      }
+      return receiverType;
+    }
+
+    /**
+     * Sets the thrown types of this executable type.
+     *
+     * @param thrownTypes the thrown types
+     */
+    void setThrownTypes(List<? extends AnnotatedTypeMirror> thrownTypes) {
+      this.throwsTypes.clear();
+      this.throwsTypes.addAll(thrownTypes);
+    }
+
+    /**
+     * Returns the thrown types of this executable type.
+     *
+     * @return the thrown types of this executable type
+     */
+    public List<AnnotatedTypeMirror> getThrownTypes() {
+      if (throwsTypes.isEmpty()
+          && !((ExecutableType) underlyingType).getThrownTypes().isEmpty()) { // lazy init
+        for (TypeMirror t : ((ExecutableType) underlyingType).getThrownTypes()) {
+          throwsTypes.add(createType(t, atypeFactory, false));
+        }
+      }
+      return Collections.unmodifiableList(throwsTypes);
+    }
+
+    /**
+     * Sets the type variables associated with this executable type.
+     *
+     * @param types the type variables of this executable type
+     */
+    void setTypeVariables(List<AnnotatedTypeVariable> types) {
+      typeVarTypes.clear();
+      typeVarTypes.addAll(types);
+    }
+
+    /**
+     * Returns the type variables of this executable type, if any.
+     *
+     * @return the type variables of this executable type, if any
+     */
+    public List<AnnotatedTypeVariable> getTypeVariables() {
+      if (typeVarTypes.isEmpty()
+          && !((ExecutableType) underlyingType).getTypeVariables().isEmpty()) { // lazy init
+        for (TypeMirror t : ((ExecutableType) underlyingType).getTypeVariables()) {
+          typeVarTypes.add((AnnotatedTypeVariable) createType(t, atypeFactory, true));
+        }
+      }
+      return Collections.unmodifiableList(typeVarTypes);
+    }
+
+    @Override
+    public AnnotatedExecutableType deepCopy(boolean copyAnnotations) {
+      return (AnnotatedExecutableType) new AnnotatedTypeCopier(copyAnnotations).visit(this);
+    }
+
+    @Override
+    public AnnotatedExecutableType deepCopy() {
+      return deepCopy(true);
+    }
+
+    @Override
+    public AnnotatedExecutableType shallowCopy(boolean copyAnnotations) {
+      AnnotatedExecutableType type = new AnnotatedExecutableType(getUnderlyingType(), atypeFactory);
+
+      type.setElement(getElement());
+      type.setParameterTypes(getParameterTypes());
+      type.setReceiverType(getReceiverType());
+      type.setReturnType(getReturnType());
+      type.setThrownTypes(getThrownTypes());
+      type.setTypeVariables(getTypeVariables());
+
+      return type;
+    }
+
+    @Override
+    public AnnotatedExecutableType shallowCopy() {
+      return shallowCopy(true);
+    }
+
+    public @NonNull ExecutableElement getElement() {
+      return element;
+    }
+
+    public void setElement(@NonNull ExecutableElement elem) {
+      this.element = elem;
+    }
+
+    @Override
+    public AnnotatedExecutableType getErased() {
+      AnnotatedExecutableType type =
+          new AnnotatedExecutableType(
+              (ExecutableType) atypeFactory.types.erasure(getUnderlyingType()), atypeFactory);
+      type.setElement(getElement());
+      type.setParameterTypes(erasureList(getParameterTypes()));
+      if (getReceiverType() != null) {
+        type.setReceiverType(getReceiverType().getErased());
+      } else {
+        type.setReceiverType(null);
+      }
+      type.setReturnType(getReturnType().getErased());
+      type.setThrownTypes(erasureList(getThrownTypes()));
+
+      return type;
+    }
+
+    /**
+     * Returns the erased types corresponding to the given types.
+     *
+     * @param lst annotated type mirrors
+     * @return erased annotated type mirrors
+     */
+    private List<AnnotatedTypeMirror> erasureList(Iterable<? extends AnnotatedTypeMirror> lst) {
+      return CollectionsPlume.mapList(AnnotatedTypeMirror::getErased, lst);
+    }
+  }
+
+  /**
+   * Represents Array types in java. A multidimensional array type is represented as an array type
+   * whose component type is also an array type.
+   */
+  public static class AnnotatedArrayType extends AnnotatedTypeMirror {
+
+    private AnnotatedArrayType(ArrayType type, AnnotatedTypeFactory factory) {
+      super(type, factory);
+    }
+
+    /** The component type of this array type. */
+    private AnnotatedTypeMirror componentType;
+
+    @Override
+    public <R, P> R accept(AnnotatedTypeVisitor<R, P> v, P p) {
+      return v.visitArray(this, p);
+    }
+
+    @Override
+    public ArrayType getUnderlyingType() {
+      return (ArrayType) this.underlyingType;
+    }
+
+    /**
+     * Sets the component type of this array.
+     *
+     * @param type the component type
+     */
+    // WMD
+    public void setComponentType(AnnotatedTypeMirror type) {
+      this.componentType = type;
+    }
+
+    /**
+     * Returns the component type of this array.
+     *
+     * @return the component type of this array
+     */
+    public AnnotatedTypeMirror getComponentType() {
+      if (componentType == null) { // lazy init
+        setComponentType(
+            createType(((ArrayType) underlyingType).getComponentType(), atypeFactory, false));
+      }
+      return componentType;
+    }
+
+    @Override
+    public AnnotatedArrayType deepCopy(boolean copyAnnotations) {
+      return (AnnotatedArrayType) new AnnotatedTypeCopier(copyAnnotations).visit(this);
+    }
+
+    @Override
+    public AnnotatedArrayType deepCopy() {
+      return deepCopy(true);
+    }
+
+    @Override
+    public AnnotatedArrayType shallowCopy(boolean copyAnnotations) {
+      AnnotatedArrayType type = new AnnotatedArrayType((ArrayType) underlyingType, atypeFactory);
+      if (copyAnnotations) {
+        type.addAnnotations(this.getAnnotationsField());
+      }
+      type.setComponentType(getComponentType());
+      return type;
+    }
+
+    @Override
+    public AnnotatedArrayType shallowCopy() {
+      return shallowCopy(true);
+    }
+
+    @Override
+    public AnnotatedArrayType getErased() {
+      // IMPORTANT NOTE: The returned type is a fresh Object because
+      // the componentType is the only component of arrays and the
+      // call to getErased will return a fresh object.
+      // | T[ ] | = |T| [ ]
+      AnnotatedArrayType at = shallowCopy();
+      AnnotatedTypeMirror ct = at.getComponentType().getErased();
+      at.setComponentType(ct);
+      return at;
+    }
+  }
+
+  /**
+   * Throw an exception if the boundType is null or a declaration.
+   *
+   * @param boundDescription the variety of bound: "Lower", "Super", or "Extends"
+   * @param boundType the type being tested
+   * @param thisType the object for which boundType is a bound
+   */
+  private static void checkBound(
+      String boundDescription, AnnotatedTypeMirror boundType, AnnotatedTypeMirror thisType) {
+    if (boundType == null || boundType.isDeclaration()) {
+      throw new BugInCF(
+          "%s bounds should never be null or a declaration.%n  new bound = %s%n  type = %s",
+          boundDescription, boundType, thisType);
+    }
+  }
+
+  /**
+   * Represents a type variable. A type variable may be explicitly declared by a type parameter of a
+   * type, method, or constructor. A type variable may also be declared implicitly, as by the
+   * capture conversion of a wildcard type argument (see chapter 5 of The Java Language
+   * Specification, Third Edition).
+   */
+  public static class AnnotatedTypeVariable extends AnnotatedTypeMirror {
+
+    private AnnotatedTypeVariable(
+        TypeVariable type, AnnotatedTypeFactory atypeFactory, boolean declaration) {
+      super(type, atypeFactory);
+      this.declaration = declaration;
+    }
+
+    /** The lower bound of the type variable. */
+    private AnnotatedTypeMirror lowerBound;
+
+    /** The upper bound of the type variable. */
+    private AnnotatedTypeMirror upperBound;
+
+    private boolean declaration;
+
+    @Override
+    public boolean isDeclaration() {
+      return declaration;
+    }
+
+    @Override
+    public void addAnnotation(AnnotationMirror a) {
+      super.addAnnotation(a);
+      fixupBoundAnnotations();
+    }
+
+    /**
+     * Change whether this {@code AnnotatedTypeVariable} is considered a use or a declaration (use
+     * this method with caution).
+     *
+     * @param declaration true if this type variable should be considered a declaration
+     */
+    public void setDeclaration(boolean declaration) {
+      this.declaration = declaration;
+    }
+
+    @Override
+    public AnnotatedTypeVariable asUse() {
+      if (!this.isDeclaration()) {
+        return this;
+      }
+
+      AnnotatedTypeVariable result = this.shallowCopy();
+      result.declaration = false;
+
+      return result;
+    }
+
+    @Override
+    public <R, P> R accept(AnnotatedTypeVisitor<R, P> v, P p) {
+      return v.visitTypeVariable(this, p);
+    }
+
+    @Override
+    public TypeVariable getUnderlyingType() {
+      return (TypeVariable) this.underlyingType;
+    }
+
+    /**
+     * Set the lower bound of this variable type.
+     *
+     * <p>Returns the lower bound of this type variable. While a type parameter cannot include an
+     * explicit lower bound declaration, capture conversion can produce a type variable with a
+     * non-trivial lower bound. Type variables otherwise have a lower bound of NullType.
+     *
+     * @param type the lower bound type
+     */
+    void setLowerBound(AnnotatedTypeMirror type) {
+      checkBound("Lower", type, this);
+      this.lowerBound = type;
+      fixupBoundAnnotations();
+    }
+
+    /**
+     * Get the lower bound field directly, bypassing any lazy initialization. This method is
+     * necessary to prevent infinite recursions in initialization. In general, prefer getLowerBound.
+     *
+     * @return the lower bound field
+     */
+    public AnnotatedTypeMirror getLowerBoundField() {
+      return lowerBound;
+    }
+
+    /**
+     * Returns the lower bound type of this type variable.
+     *
+     * @return the lower bound type of this type variable
+     */
+    public AnnotatedTypeMirror getLowerBound() {
+      if (lowerBound == null) { // lazy init
+        BoundsInitializer.initializeBounds(this);
+        fixupBoundAnnotations();
+      }
+      return lowerBound;
+    }
+
+    // If the lower bound was not present in underlyingType, then its annotation was defaulted from
+    // the AnnotatedTypeFactory.  If the lower bound annotation is a supertype of the upper bound
+    // annotation, then the type is ill-formed.  In that case, change the defaulted lower bound to
+    // be consistent with the explicitly-written upper bound.
+    //
+    // As a concrete example, if the default annotation is @Nullable, then the type "X extends
+    // @NonNull Y" should not be converted into "X extends @NonNull Y super @Nullable bottomtype"
+    // but be converted into "X extends @NonNull Y super @NonNull bottomtype".
+    //
+    // In addition, ensure consistency of annotations on type variables
+    // and the upper bound. Assume class C<X extends @Nullable Object>.
+    // The type of "@Nullable X" has to be "@Nullable X extends @Nullable Object",
+    // because otherwise the annotations are inconsistent.
+    private void fixupBoundAnnotations() {
+      if (!this.getAnnotationsField().isEmpty()) {
+        Set<AnnotationMirror> newAnnos = this.getAnnotationsField();
+        if (upperBound != null) {
+          upperBound.replaceAnnotations(newAnnos);
+        }
+
+        // Note:
+        // if the lower bound is a type variable
+        // then when we place annotations on the primary annotation
+        //   this will actually cause the type variable to be exact and propagate the primary
+        //   annotation to the type variable because primary annotations overwrite the upper and
+        //   lower bounds of type variables when getUpperBound/getLowerBound is called
+        if (lowerBound != null) {
+          lowerBound.replaceAnnotations(newAnnos);
+        }
+      }
+    }
+
+    /**
+     * Set the upper bound of this variable type.
+     *
+     * @param type the upper bound type
+     */
+    void setUpperBound(AnnotatedTypeMirror type) {
+      checkBound("Upper", type, this);
+      this.upperBound = type;
+      fixupBoundAnnotations();
+    }
+
+    /**
+     * Get the upper bound field directly, bypassing any lazy initialization. This method is
+     * necessary to prevent infinite recursions in initialization. In general, prefer getUpperBound.
+     *
+     * @return the upper bound field
+     */
+    public AnnotatedTypeMirror getUpperBoundField() {
+      return upperBound;
+    }
+
+    /**
+     * Get the upper bound of the type variable, possibly lazily initializing it. Attention: If the
+     * upper bound is lazily initialized, it will not contain any annotations! Callers of the method
+     * have to make sure that an AnnotatedTypeFactory first processed the bound.
+     *
+     * @return the upper bound type of this type variable
+     */
+    public AnnotatedTypeMirror getUpperBound() {
+      if (upperBound == null) { // lazy init
+        BoundsInitializer.initializeBounds(this);
+        fixupBoundAnnotations();
+      }
+      return upperBound;
+    }
+
+    public AnnotatedTypeParameterBounds getBounds() {
+      return new AnnotatedTypeParameterBounds(getUpperBound(), getLowerBound());
+    }
+
+    public AnnotatedTypeParameterBounds getBoundFields() {
+      return new AnnotatedTypeParameterBounds(getUpperBoundField(), getLowerBoundField());
+    }
+
+    /** Used to terminate recursion into upper bounds. */
+    private boolean inUpperBounds = false;
+
+    @Override
+    public AnnotatedTypeVariable deepCopy(boolean copyAnnotations) {
+      return (AnnotatedTypeVariable) new AnnotatedTypeCopier(copyAnnotations).visit(this);
+    }
+
+    @Override
+    public AnnotatedTypeVariable deepCopy() {
+      return deepCopy(true);
+    }
+
+    @Override
+    public AnnotatedTypeVariable shallowCopy(boolean copyAnnotations) {
+      AnnotatedTypeVariable type =
+          new AnnotatedTypeVariable(((TypeVariable) underlyingType), atypeFactory, declaration);
+
+      if (copyAnnotations) {
+        type.addAnnotations(this.getAnnotationsField());
+      }
+
+      if (!inUpperBounds) {
+        inUpperBounds = true;
+        type.inUpperBounds = true;
+        type.setUpperBound(getUpperBound().shallowCopy());
+        inUpperBounds = false;
+        type.inUpperBounds = false;
+      }
+
+      type.setLowerBound(getLowerBound().shallowCopy());
+
+      return type;
+    }
+
+    @Override
+    public AnnotatedTypeVariable shallowCopy() {
+      return shallowCopy(true);
+    }
+
+    /**
+     * This method will traverse the upper bound of this type variable calling getErased until it
+     * finds the concrete upper bound. e.g.
+     *
+     * <pre>{@code  <E extends T>, T extends S, S extends List<String>>}</pre>
+     *
+     * A call to getErased will return the type List
+     *
+     * @return the erasure of the upper bound of this type
+     *     <p>IMPORTANT NOTE: getErased should always return a FRESH object. This will occur for
+     *     type variables if all other getErased methods are implemented appropriately. Therefore,
+     *     to avoid extra copy calls, this method will not call deepCopy on getUpperBound
+     */
+    @Override
+    public AnnotatedTypeMirror getErased() {
+      // |T extends A&B| = |A|
+      return this.getUpperBound().getErased();
+    }
+  }
+
+  /**
+   * A pseudo-type used where no actual type is appropriate. The kinds of NoType are:
+   *
+   * <ul>
+   *   <li>VOID -- corresponds to the keyword void.
+   *   <li>PACKAGE -- the pseudo-type of a package element.
+   *   <li>NONE -- used in other cases where no actual type is appropriate; for example, the
+   *       superclass of java.lang.Object.
+   * </ul>
+   */
+  public static class AnnotatedNoType extends AnnotatedTypeMirror {
+
+    private AnnotatedNoType(NoType type, AnnotatedTypeFactory factory) {
+      super(type, factory);
+    }
+
+    // No need for methods
+    // Might like to override annotate(), include(), execlude()
+    // AS NoType does not accept any annotations
+
+    @Override
+    public <R, P> R accept(AnnotatedTypeVisitor<R, P> v, P p) {
+      return v.visitNoType(this, p);
+    }
+
+    @Override
+    public NoType getUnderlyingType() {
+      return (NoType) this.underlyingType;
+    }
+
+    @Override
+    public AnnotatedNoType deepCopy(boolean copyAnnotations) {
+      return (AnnotatedNoType) new AnnotatedTypeCopier(copyAnnotations).visit(this);
+    }
+
+    @Override
+    public AnnotatedNoType deepCopy() {
+      return deepCopy(true);
+    }
+
+    @Override
+    public AnnotatedNoType shallowCopy(boolean copyAnnotations) {
+      AnnotatedNoType type = new AnnotatedNoType((NoType) underlyingType, atypeFactory);
+      if (copyAnnotations) {
+        type.addAnnotations(this.getAnnotationsField());
+      }
+      return type;
+    }
+
+    @Override
+    public AnnotatedNoType shallowCopy() {
+      return shallowCopy(true);
+    }
+  }
+
+  /** Represents the null type. This is the type of the expression {@code null}. */
+  public static class AnnotatedNullType extends AnnotatedTypeMirror {
+
+    private AnnotatedNullType(NullType type, AnnotatedTypeFactory factory) {
+      super(type, factory);
+    }
+
+    @Override
+    public <R, P> R accept(AnnotatedTypeVisitor<R, P> v, P p) {
+      return v.visitNull(this, p);
+    }
+
+    @Override
+    public NullType getUnderlyingType() {
+      return (NullType) this.underlyingType;
+    }
+
+    @Override
+    public AnnotatedNullType deepCopy(boolean copyAnnotations) {
+      return (AnnotatedNullType) new AnnotatedTypeCopier(copyAnnotations).visit(this);
+    }
+
+    @Override
+    public AnnotatedNullType deepCopy() {
+      return deepCopy(true);
+    }
+
+    @Override
+    public AnnotatedNullType shallowCopy(boolean copyAnnotations) {
+      AnnotatedNullType type = new AnnotatedNullType((NullType) underlyingType, atypeFactory);
+      if (copyAnnotations) {
+        type.addAnnotations(this.getAnnotationsField());
+      }
+      return type;
+    }
+
+    @Override
+    public AnnotatedNullType shallowCopy() {
+      return shallowCopy(true);
+    }
+  }
+
+  /**
+   * Represents a primitive type. These include {@code boolean}, {@code byte}, {@code short}, {@code
+   * int}, {@code long}, {@code char}, {@code float}, and {@code double}.
+   */
+  public static class AnnotatedPrimitiveType extends AnnotatedTypeMirror {
+
+    private AnnotatedPrimitiveType(PrimitiveType type, AnnotatedTypeFactory factory) {
+      super(type, factory);
+    }
+
+    @Override
+    public <R, P> R accept(AnnotatedTypeVisitor<R, P> v, P p) {
+      return v.visitPrimitive(this, p);
+    }
+
+    @Override
+    public PrimitiveType getUnderlyingType() {
+      return (PrimitiveType) this.underlyingType;
+    }
+
+    @Override
+    public AnnotatedPrimitiveType deepCopy(boolean copyAnnotations) {
+      return (AnnotatedPrimitiveType) new AnnotatedTypeCopier(copyAnnotations).visit(this);
+    }
+
+    @Override
+    public AnnotatedPrimitiveType deepCopy() {
+      return deepCopy(true);
+    }
+
+    @Override
+    public AnnotatedPrimitiveType shallowCopy(boolean copyAnnotations) {
+      AnnotatedPrimitiveType type =
+          new AnnotatedPrimitiveType((PrimitiveType) underlyingType, atypeFactory);
+      if (copyAnnotations) {
+        type.addAnnotations(this.getAnnotationsField());
+      }
+      return type;
+    }
+
+    @Override
+    public AnnotatedPrimitiveType shallowCopy() {
+      return shallowCopy(true);
+    }
+  }
+
+  /**
+   * Represents a wildcard type argument. Examples include:
+   *
+   * <p>? ? extends Number ? super T
+   *
+   * <p>A wildcard may have its upper bound explicitly set by an extends clause, its lower bound
+   * explicitly set by a super clause, or neither (but not both).
+   */
+  public static class AnnotatedWildcardType extends AnnotatedTypeMirror {
+    /** Lower ({@code super}) bound. */
+    private AnnotatedTypeMirror superBound;
+
+    /** Upper ({@code extends} bound. */
+    private AnnotatedTypeMirror extendsBound;
+
+    /**
+     * The type variable to which this wildcard is an argument. Used to initialize the upper bound
+     * of unbounded wildcards and wildcards in raw types.
+     */
+    private TypeVariable typeVariable = null;
+
+    private AnnotatedWildcardType(WildcardType type, AnnotatedTypeFactory factory) {
+      super(type, factory);
+    }
+
+    @Override
+    public void addAnnotation(AnnotationMirror a) {
+      super.addAnnotation(a);
+      fixupBoundAnnotations();
+    }
+
+    /**
+     * Sets the super bound of this wildcard.
+     *
+     * @param type the type of the lower bound
+     */
+    void setSuperBound(AnnotatedTypeMirror type) {
+      checkBound("Super", type, this);
+      this.superBound = type;
+      fixupBoundAnnotations();
+    }
+
+    public AnnotatedTypeMirror getSuperBoundField() {
+      return superBound;
+    }
+
+    /**
+     * Returns the lower bound of this wildcard. If no lower bound is explicitly declared, returns
+     * {@code null}.
+     *
+     * @return the lower bound of this wildcard, or null if none is explicitly declared
+     */
+    public AnnotatedTypeMirror getSuperBound() {
+      if (superBound == null) {
+        BoundsInitializer.initializeSuperBound(this);
+        fixupBoundAnnotations();
+      }
+      return this.superBound;
+    }
+
+    /**
+     * Sets the upper bound of this wildcard.
+     *
+     * @param type the type of the upper bound
+     */
+    void setExtendsBound(AnnotatedTypeMirror type) {
+      checkBound("Extends", type, this);
+      this.extendsBound = type;
+      fixupBoundAnnotations();
+    }
+
+    public AnnotatedTypeMirror getExtendsBoundField() {
+      return extendsBound;
+    }
+
+    /**
+     * Returns the upper bound of this wildcard. If no upper bound is explicitly declared, returns
+     * the upper bound of the type variable to which the wildcard is bound.
+     *
+     * @return the upper bound of this wildcard. If no upper bound is explicitly declared, returns
+     *     the upper bound of the type variable to which the wildcard is bound.
+     */
+    public AnnotatedTypeMirror getExtendsBound() {
+      if (extendsBound == null) {
+        BoundsInitializer.initializeExtendsBound(this);
+        fixupBoundAnnotations();
+      }
+      return this.extendsBound;
+    }
+
+    private void fixupBoundAnnotations() {
+      if (!this.getAnnotationsField().isEmpty()) {
+        if (superBound != null) {
+          superBound.replaceAnnotations(this.getAnnotationsField());
+        }
+        if (extendsBound != null) {
+          extendsBound.replaceAnnotations(this.getAnnotationsField());
+        }
+      }
+    }
+
+    /**
+     * Sets type variable to which this wildcard is an argument. This method should only be called
+     * during initialization of the type.
+     */
+    void setTypeVariable(TypeParameterElement typeParameterElement) {
+      this.typeVariable = (TypeVariable) typeParameterElement.asType();
+    }
+
+    /**
+     * Sets type variable to which this wildcard is an argument. This method should only be called
+     * during initialization of the type.
+     */
+    void setTypeVariable(TypeVariable typeVariable) {
+      this.typeVariable = typeVariable;
+    }
+
+    /**
+     * Returns the type variable to which this wildcard is an argument. Used to initialize the upper
+     * bound of unbounded wildcards and wildcards in raw types.
+     *
+     * @return the type variable to which this wildcard is an argument
+     */
+    public TypeVariable getTypeVariable() {
+      return typeVariable;
+    }
+
+    @Override
+    public <R, P> R accept(AnnotatedTypeVisitor<R, P> v, P p) {
+      return v.visitWildcard(this, p);
+    }
+
+    @Override
+    public WildcardType getUnderlyingType() {
+      return (WildcardType) this.underlyingType;
+    }
+
+    @Override
+    public AnnotatedWildcardType deepCopy(boolean copyAnnotations) {
+      return (AnnotatedWildcardType) new AnnotatedTypeCopier(copyAnnotations).visit(this);
+    }
+
+    @Override
+    public AnnotatedWildcardType deepCopy() {
+      return deepCopy(true);
+    }
+
+    @Override
+    public AnnotatedWildcardType shallowCopy(boolean copyAnnotations) {
+      AnnotatedWildcardType type =
+          new AnnotatedWildcardType((WildcardType) underlyingType, atypeFactory);
+      type.setExtendsBound(getExtendsBound().shallowCopy());
+      type.setSuperBound(getSuperBound().shallowCopy());
+      if (copyAnnotations) {
+        type.addAnnotations(this.getAnnotationsField());
+      }
+
+      type.uninferredTypeArgument = uninferredTypeArgument;
+      type.typeVariable = typeVariable;
+
+      return type;
+    }
+
+    @Override
+    public AnnotatedWildcardType shallowCopy() {
+      return shallowCopy(true);
+    }
+
+    /**
+     * @see
+     *     org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable#getErased()
+     */
+    @Override
+    public AnnotatedTypeMirror getErased() {
+      // |? extends A&B| = |A|
+      return getExtendsBound().getErased();
+    }
+
+    // Remove the uninferredTypeArgument once method type
+    // argument inference and raw type handling is improved.
+    private boolean uninferredTypeArgument = false;
+
+    /**
+     * Set that this wildcard is from an uninferred type argument. This method should only be used
+     * within the framework. Once issues that depend on this hack, in particular Issue 979, are
+     * fixed, this must be removed.
+     */
+    public void setUninferredTypeArgument() {
+      uninferredTypeArgument = true;
+    }
+
+    /**
+     * Returns whether or not this wildcard is a type argument for which inference failed to infer a
+     * type.
+     *
+     * @return true if this wildcard is a type argument for which inference failed
+     */
+    public boolean isUninferredTypeArgument() {
+      return uninferredTypeArgument;
+    }
+  }
+
+  /**
+   * Represents an intersection type.
+   *
+   * <p>For example: {@code MyObject & Serializable & Comparable<MyObject>}
+   */
+  public static class AnnotatedIntersectionType extends AnnotatedTypeMirror {
+
+    /** A list of the bounds of this which are also its direct super types. */
+    protected List<AnnotatedTypeMirror> bounds;
+
+    /**
+     * Creates an {@code AnnotatedIntersectionType} with the underlying type {@code type}. The
+     * result contains no annotations.
+     *
+     * @param type underlying kind of this type
+     * @param atypeFactory the factory used to construct this intersection type
+     */
+    private AnnotatedIntersectionType(IntersectionType type, AnnotatedTypeFactory atypeFactory) {
+      super(type, atypeFactory);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Also, copies {@code a} to all the bounds.
+     *
+     * @param a the annotation to add
+     */
+    @Override
+    public void addAnnotation(AnnotationMirror a) {
+      super.addAnnotation(a);
+      fixupBoundAnnotations();
+    }
+
+    /**
+     * Copies {@link #annotations} to all the bounds, replacing any existing annotations in the same
+     * hierarchy.
+     */
+    private void fixupBoundAnnotations() {
+      if (!this.getAnnotationsField().isEmpty()) {
+        Set<AnnotationMirror> newAnnos = this.getAnnotationsField();
+        if (bounds != null) {
+          for (AnnotatedTypeMirror bound : bounds) {
+            if (bound.getKind() != TypeKind.TYPEVAR) {
+              bound.replaceAnnotations(newAnnos);
+            }
+          }
+        }
+      }
+    }
+
+    @Override
+    public <R, P> R accept(AnnotatedTypeVisitor<R, P> v, P p) {
+      return v.visitIntersection(this, p);
+    }
+
+    @Override
+    public IntersectionType getUnderlyingType() {
+      return (IntersectionType) super.getUnderlyingType();
+    }
+
+    @Override
+    public AnnotatedIntersectionType deepCopy(boolean copyAnnotations) {
+      return (AnnotatedIntersectionType) new AnnotatedTypeCopier(copyAnnotations).visit(this);
+    }
+
+    @Override
+    public AnnotatedIntersectionType deepCopy() {
+      return deepCopy(true);
+    }
+
+    @Override
+    public AnnotatedIntersectionType shallowCopy(boolean copyAnnotations) {
+      AnnotatedIntersectionType type =
+          new AnnotatedIntersectionType((IntersectionType) underlyingType, atypeFactory);
+      if (copyAnnotations) {
+        type.addAnnotations(this.getAnnotationsField());
+      }
+      type.bounds = this.bounds;
+      return type;
+    }
+
+    @Override
+    public AnnotatedIntersectionType shallowCopy() {
+      return shallowCopy(true);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This returns the same types as {@link #getBounds()}.
+     *
+     * @return the direct super types of this
+     */
+    @Override
+    public List<? extends AnnotatedTypeMirror> directSupertypes() {
+      return getBounds();
+    }
+
+    /**
+     * This returns the bounds of the intersection type. Although only declared types can appear in
+     * an explicitly written intersections, during capture conversion, intersections with other
+     * kinds of types are created.
+     *
+     * <p>This returns the same types as {@link #directSupertypes()}.
+     *
+     * @return the bounds of this, which are also the direct super types of this
+     */
+    public List<AnnotatedTypeMirror> getBounds() {
+      if (bounds == null) {
+        List<? extends TypeMirror> ubounds = ((IntersectionType) underlyingType).getBounds();
+        List<AnnotatedTypeMirror> res =
+            CollectionsPlume.mapList(
+                (TypeMirror bnd) -> createType(bnd, atypeFactory, false), ubounds);
+        bounds = Collections.unmodifiableList(res);
+        fixupBoundAnnotations();
+      }
+      return bounds;
+    }
+
+    /**
+     * Sets the bounds.
+     *
+     * @param bounds bounds to use
+     */
+    /*default-visibility*/ void setBounds(List<AnnotatedTypeMirror> bounds) {
+      this.bounds = bounds;
+    }
+
+    /**
+     * Copy the first annotation (in each hierarchy) on a bound to the primary annotation location
+     * of the intersection type.
+     *
+     * <p>For example, in the type {@code @NonNull Object & @Initialized @Nullable Serializable},
+     * {@code @Nullable} and {@code @Initialized} are copied to the primary annotation location.
+     */
+    public void copyIntersectionBoundAnnotations() {
+      Set<AnnotationMirror> annos = AnnotationUtils.createAnnotationSet();
+      for (AnnotatedTypeMirror bound : getBounds()) {
+        for (AnnotationMirror a : bound.getAnnotations()) {
+          if (atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(annos, a)
+              == null) {
+            annos.add(a);
+          }
+        }
+      }
+      addAnnotations(annos);
+    }
+  }
+
+  // TODO: Ensure union types are handled everywhere.
+  // TODO: Should field "annotations" contain anything?
+  public static class AnnotatedUnionType extends AnnotatedTypeMirror {
+
+    /**
+     * Creates a new AnnotatedUnionType.
+     *
+     * @param type underlying kind of this type
+     * @param atypeFactory type factory
+     */
+    private AnnotatedUnionType(UnionType type, AnnotatedTypeFactory atypeFactory) {
+      super(type, atypeFactory);
+    }
+
+    @Override
+    public <R, P> R accept(AnnotatedTypeVisitor<R, P> v, P p) {
+      return v.visitUnion(this, p);
+    }
+
+    @Override
+    public AnnotatedUnionType deepCopy(boolean copyAnnotations) {
+      return (AnnotatedUnionType) new AnnotatedTypeCopier(copyAnnotations).visit(this);
+    }
+
+    @Override
+    public AnnotatedUnionType deepCopy() {
+      return deepCopy(true);
+    }
+
+    @Override
+    public AnnotatedUnionType shallowCopy(boolean copyAnnotations) {
+      AnnotatedUnionType type = new AnnotatedUnionType((UnionType) underlyingType, atypeFactory);
+      if (copyAnnotations) {
+        type.addAnnotations(this.getAnnotationsField());
+      }
+      type.alternatives = this.alternatives;
+      return type;
+    }
+
+    @Override
+    public AnnotatedUnionType shallowCopy() {
+      return shallowCopy(true);
+    }
+
+    /** The types that are unioned to form this AnnotatedUnionType. */
+    protected List<AnnotatedDeclaredType> alternatives;
+
+    /**
+     * Returns the types that are unioned to form this AnnotatedUnionType.
+     *
+     * @return the types that are unioned to form this AnnotatedUnionType
+     */
+    public List<AnnotatedDeclaredType> getAlternatives() {
+      if (alternatives == null) {
+        List<? extends TypeMirror> ualts = ((UnionType) underlyingType).getAlternatives();
+        List<AnnotatedDeclaredType> res =
+            CollectionsPlume.mapList(
+                (TypeMirror alt) -> (AnnotatedDeclaredType) createType(alt, atypeFactory, false),
+                ualts);
+        alternatives = Collections.unmodifiableList(res);
+      }
+      return alternatives;
+    }
+  }
+
+  /**
+   * This method returns a list of AnnotatedTypeMirrors where the Java type of each ATM is an
+   * immediate supertype (class or interface) of the Java type of this. The interface types, if any,
+   * appear at the end of the list. If the directSuperType has type arguments, then the annotations
+   * on those type arguments are taken with proper translation from the declaration of the Java type
+   * of this.
+   *
+   * <p>For example,
+   *
+   * <pre>
+   * {@code class B<T> { ... } }
+   * {@code class A extends B<@NonNull String> { ... } }
+   * {@code @Nullable A a;}
+   * </pre>
+   *
+   * The direct supertype of the ATM {@code @Nullable A} is {@code @Nullable B<@NonNull String>}.
+   *
+   * <p>An example with more complex type arguments:
+   *
+   * <pre>
+   * {@code class D<Q,R> { ... } }
+   * {@code class A<T,S> extends D<S,T> { ... } }
+   * {@code @Nullable A<@NonNull String, @NonNull Object> a;}
+   * </pre>
+   *
+   * The direct supertype of the ATM {@code @Nullable A<@NonNull String, @NonNull Object>} is
+   * {@code @Nullable B<@NonNull Object, @NonNull String>}.
+   *
+   * <p>An example with more than one direct supertype:
+   *
+   * <pre>
+   * {@code class B<T> implements List<Integer> { ... } }
+   * {@code class A extends B<@NonNull String> implements List<Integer> { ... } }
+   * {@code @Nullable A a;}
+   * </pre>
+   *
+   * The direct supertypes of the ATM {@code @Nullable A} are {@code @Nullable B <@NonNull String>}
+   * and {@code @Nullable List<@NonNull Integer>}.
+   *
+   * @return the immediate supertypes of this
+   * @see Types#directSupertypes(TypeMirror)
+   */
+  public List<? extends AnnotatedTypeMirror> directSupertypes() {
+    return SupertypeFinder.directSupertypes(this);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeParameterBounds.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeParameterBounds.java
new file mode 100644
index 0000000..b18c115
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeParameterBounds.java
@@ -0,0 +1,56 @@
+package org.checkerframework.framework.type;
+
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/** Represents upper and lower bounds, each an AnnotatedTypeMirror. */
+public class AnnotatedTypeParameterBounds {
+  private final AnnotatedTypeMirror upper;
+  private final AnnotatedTypeMirror lower;
+
+  public AnnotatedTypeParameterBounds(AnnotatedTypeMirror upper, AnnotatedTypeMirror lower) {
+    this.upper = upper;
+    this.lower = lower;
+  }
+
+  public AnnotatedTypeMirror getUpperBound() {
+    return upper;
+  }
+
+  public AnnotatedTypeMirror getLowerBound() {
+    return lower;
+  }
+
+  @Override
+  public String toString() {
+    return "[extends " + upper + " super " + lower + "]";
+  }
+
+  /**
+   * Return a possibly-verbose string representation of this.
+   *
+   * @param verbose if true, returned representation is verbose
+   * @return a possibly-verbose string representation of this
+   */
+  public String toString(boolean verbose) {
+    return "[extends " + upper.toString(verbose) + " super " + lower.toString(verbose) + "]";
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(upper, lower);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object obj) {
+    if (!(obj instanceof AnnotatedTypeParameterBounds)) {
+      return false;
+    }
+    AnnotatedTypeParameterBounds other = (AnnotatedTypeParameterBounds) obj;
+    return this.upper == null
+        ? other.upper == null
+        : this.upper.equals(other.upper) && this.lower == null
+            ? other.lower == null
+            : this.lower.equals(other.lower);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java
new file mode 100644
index 0000000..40640a7
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java
@@ -0,0 +1,162 @@
+package org.checkerframework.framework.type;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.DoubleAnnotatedTypeScanner;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Replaces or adds all the annotations in the parameter with the annotations from the visited type.
+ * An annotation is replaced if the parameter type already has an annotation in the same hierarchy
+ * at the same location as the visited type.
+ *
+ * <p>Example use:
+ *
+ * <pre>{@code
+ * AnnotatedTypeMirror visitType = ...;
+ * AnnotatedTypeMirror parameter = ...;
+ * visitType.accept(new AnnotatedTypeReplacer(), parameter);
+ * }</pre>
+ */
+public class AnnotatedTypeReplacer extends DoubleAnnotatedTypeScanner<Void> {
+
+  /**
+   * Replaces or adds all annotations from {@code from} to {@code to}. Annotations from {@code from}
+   * will be used everywhere they exist, but annotations in {@code to} will be kept anywhere that
+   * {@code from} is unannotated.
+   *
+   * @param from the annotated type mirror from which to take new annotations
+   * @param to the annotated type mirror to which the annotations will be added
+   * @deprecated use {@link AnnotatedTypeFactory#replaceAnnotations(AnnotatedTypeMirror,
+   *     AnnotatedTypeMirror)} instead.
+   */
+  @Deprecated // 2021-03-25
+  @SuppressWarnings("interning:not.interned") // assertion
+  public static void replace(final AnnotatedTypeMirror from, final AnnotatedTypeMirror to) {
+    if (from == to) {
+      throw new BugInCF("From == to");
+    }
+    new AnnotatedTypeReplacer().visit(from, to);
+  }
+
+  /**
+   * Replaces or adds annotations in {@code top}'s hierarchy from {@code from} to {@code to}.
+   * Annotations from {@code from} will be used everywhere they exist, but annotations in {@code to}
+   * will be kept anywhere that {@code from} is unannotated.
+   *
+   * @param from the annotated type mirror from which to take new annotations
+   * @param to the annotated type mirror to which the annotations will be added
+   * @param top the top type of the hierarchy whose annotations will be added
+   * @deprecated use {@link AnnotatedTypeFactory#replaceAnnotations(AnnotatedTypeMirror,
+   *     AnnotatedTypeMirror, AnnotationMirror)} instead.
+   */
+  @Deprecated // 2021-03-25
+  @SuppressWarnings("interning:not.interned") // assertion
+  public static void replace(
+      final AnnotatedTypeMirror from, final AnnotatedTypeMirror to, final AnnotationMirror top) {
+    if (from == to) {
+      throw new BugInCF("from == to: %s", from);
+    }
+    new AnnotatedTypeReplacer(top).visit(from, to);
+  }
+
+  /** If top != null we replace only the annotations in the hierarchy of top. */
+  private AnnotationMirror top;
+
+  /** Construct an AnnotatedTypeReplacer that will replace all annotations. */
+  public AnnotatedTypeReplacer() {
+    this.top = null;
+  }
+
+  /**
+   * Construct an AnnotatedTypeReplacer that will only replace annotations in {@code top}'s
+   * hierarchy.
+   *
+   * @param top if top != null, then only annotation in the hierarchy of top are affected
+   */
+  public AnnotatedTypeReplacer(final AnnotationMirror top) {
+    this.top = top;
+  }
+
+  /**
+   * If {@code top != null}, then only annotations in the hierarchy of {@code top} are affected;
+   * otherwise, all annotations are replaced.
+   *
+   * @param top if top != null, then only annotations in the hierarchy of top are replaced;
+   *     otherwise, all annotations are replaced.
+   */
+  public void setTop(@Nullable AnnotationMirror top) {
+    this.top = top;
+  }
+
+  @SuppressWarnings("interning:not.interned") // assertion
+  @Override
+  protected Void defaultAction(AnnotatedTypeMirror from, AnnotatedTypeMirror to) {
+    assert from != to;
+    if (from != null && to != null) {
+      replaceAnnotations(from, to);
+    }
+    return null;
+  }
+
+  /**
+   * Replace the annotations in to with the annotations in from, wherever from has an annotation.
+   *
+   * @param from the source of the annotations
+   * @param to the destination of the annotations, modified by this method
+   */
+  protected void replaceAnnotations(final AnnotatedTypeMirror from, final AnnotatedTypeMirror to) {
+    if (top == null) {
+      to.replaceAnnotations(from.getAnnotations());
+    } else {
+      final AnnotationMirror replacement = from.getAnnotationInHierarchy(top);
+      if (replacement != null) {
+        to.replaceAnnotation(from.getAnnotationInHierarchy(top));
+      }
+    }
+  }
+
+  @Override
+  public Void visitTypeVariable(AnnotatedTypeVariable from, AnnotatedTypeMirror to) {
+    resolvePrimaries(from, to);
+    return super.visitTypeVariable(from, to);
+  }
+
+  @Override
+  public Void visitWildcard(AnnotatedWildcardType from, AnnotatedTypeMirror to) {
+    resolvePrimaries(from, to);
+    return super.visitWildcard(from, to);
+  }
+
+  /**
+   * For type variables and wildcards, the absence of a primary annotations has an implied meaning
+   * on substitution. Therefore, in these cases we remove the primary annotation and rely on the
+   * fact that the bounds are also merged into the type to.
+   *
+   * @param from a type variable or wildcard
+   * @param to the destination annotated type mirror
+   */
+  public void resolvePrimaries(AnnotatedTypeMirror from, AnnotatedTypeMirror to) {
+    if (from.getKind() == TypeKind.WILDCARD || from.getKind() == TypeKind.TYPEVAR) {
+      if (top != null) {
+        if (from.getAnnotationInHierarchy(top) == null) {
+          to.removeAnnotationInHierarchy(top);
+        }
+      } else {
+        for (final AnnotationMirror toPrimaryAnno : to.getAnnotations()) {
+          if (from.getAnnotationInHierarchy(toPrimaryAnno) == null) {
+            to.removeAnnotation(toPrimaryAnno);
+          }
+        }
+      }
+    } else {
+      throw new BugInCF(
+          "ResolvePrimaries's from argument should be a type variable OR wildcard%n"
+              + "from=%s%nto=%s",
+          from.toString(true), to.toString(true));
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java
new file mode 100644
index 0000000..d1c41af
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java
@@ -0,0 +1,828 @@
+package org.checkerframework.framework.type;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.regex.Pattern;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic.Kind;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
+import org.checkerframework.checker.signature.qual.Identifier;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.InternalUtils;
+import org.checkerframework.javacutil.UserError;
+import org.plumelib.reflection.Signatures;
+
+/**
+ * This class assists the {@link AnnotatedTypeFactory} by reflectively looking up the list of
+ * annotation class names in each checker's qual directory, and then loading and returning it as a
+ * set of annotation classes. It can also look up and load annotation classes from external
+ * directories that are passed as arguments to checkers that have extension capabilities such as the
+ * Subtyping Checker, Fenum Checker, and Units Checker.
+ *
+ * <p>To load annotations using this class, their directory structure and package structure must be
+ * identical.
+ *
+ * <p>Only annotation classes that have the {@link Target} meta-annotation with the value of {@link
+ * ElementType#TYPE_USE} (and optionally {@link ElementType#TYPE_PARAMETER}) are loaded. If it has
+ * other {@link ElementType} values, it won't be loaded. Other annotation classes must be manually
+ * listed in a checker's annotated type factory by overriding {@link
+ * AnnotatedTypeFactory#createSupportedTypeQualifiers()}.
+ *
+ * <p>Checker writers may wish to subclass this class if they wish to implement some custom rules to
+ * filter or process loaded annotation classes, by providing an override implementation of {@link
+ * #isSupportedAnnotationClass(Class)}. See {@code
+ * org.checkerframework.checker.units.UnitsAnnotationClassLoader} for an example.
+ */
+public class AnnotationClassLoader {
+  /** For issuing errors to the user. */
+  protected final BaseTypeChecker checker;
+
+  // For loading from a source package directory
+  /** The package name. */
+  private final @DotSeparatedIdentifiers String packageName;
+  /** The package name, with periods replaced by slashes. */
+  private final String packageNameWithSlashes;
+  /** The atomic package names (the package name split at dots). */
+  private final List<@Identifier String> fullyQualifiedPackageNameSegments;
+  /** The name of a Checker's qualifier package. */
+  private static final String QUAL_PACKAGE = "qual";
+
+  // For loading from a Jar file
+  /** The suffix for a .jar file. */
+  private static final String JAR_SUFFIX = ".jar";
+  /** The suffix for a .class file. */
+  private static final String CLASS_SUFFIX = ".class";
+
+  // Constants
+  /** The package separator. */
+  private static final char DOT = '.';
+  /** The path separator, in .jar files, binary names, etc. */
+  private static final char SLASH = '/';
+
+  /**
+   * Processing Env used to create an {@link AnnotationBuilder}, which is in turn used to build the
+   * annotation mirror from the loaded class.
+   */
+  protected final ProcessingEnvironment processingEnv;
+
+  /** The resource URL of the qual directory of a checker class. */
+  private final URL resourceURL;
+
+  /** The class loader used to load annotation classes. */
+  protected final URLClassLoader classLoader;
+
+  /**
+   * The annotation classes bundled with a checker (located in its qual directory) that are deemed
+   * supported by the checker (non-alias annotations). Each checker can override {@link
+   * #isSupportedAnnotationClass(Class)} to determine whether an annotation is supported or not.
+   * Call {@link #getBundledAnnotationClasses()} to obtain a reference to the set of classes.
+   */
+  private final Set<Class<? extends Annotation>> supportedBundledAnnotationClasses;
+
+  /** The package separator: ".". */
+  private static final Pattern DOT_LITERAL_PATTERN =
+      Pattern.compile(Character.toString(DOT), Pattern.LITERAL);
+
+  /**
+   * Constructor for loading annotations defined for a checker.
+   *
+   * @param checker a {@link BaseTypeChecker} or its subclass
+   */
+  @SuppressWarnings("signature") // TODO: reduce use of string manipulation
+  public AnnotationClassLoader(final BaseTypeChecker checker) {
+    this.checker = checker;
+    processingEnv = checker.getProcessingEnvironment();
+
+    // package name must use dots, this is later prepended to annotation
+    // class names as we load the classes using the class loader
+    Package checkerPackage = checker.getClass().getPackage();
+    packageName =
+        checkerPackage != null && !checkerPackage.getName().isEmpty()
+            ? checkerPackage.getName() + DOT + QUAL_PACKAGE
+            : QUAL_PACKAGE;
+
+    // the package name with dots replaced by slashes will be used to scan file directories
+    packageNameWithSlashes = packageName.replace(DOT, SLASH);
+
+    // Each component of the fully qualified package name will be used later to recursively descend
+    // from a root directory to see if the package exists in some particular root directory.
+    fullyQualifiedPackageNameSegments = new ArrayList<>();
+
+    // from the fully qualified package name, split it at every dot then add to the list
+    fullyQualifiedPackageNameSegments.addAll(Arrays.asList(DOT_LITERAL_PATTERN.split(packageName)));
+
+    classLoader = getClassLoader();
+
+    URL localResourceURL;
+    if (classLoader != null) {
+      // if the application classloader is accessible, then directly retrieve the resource URL of
+      // the qual package resource URLs must use slashes
+      localResourceURL = classLoader.getResource(packageNameWithSlashes);
+
+      // thread based application classloader, if needed in the future:
+      // resourceURL =
+      // Thread.currentThread().getContextClassLoader().getResource(packageNameWithSlashes);
+    } else {
+      // Signal failure to find resource
+      localResourceURL = null;
+    }
+
+    if (localResourceURL == null) {
+      // if the application classloader is not accessible (which means the checker class was loaded
+      // using the bootstrap classloader)
+      // or if the classloader didn't find the package,
+      // then scan the classpaths to find a jar or directory which contains the qual package and set
+      // the resource URL to that jar or qual directory
+      localResourceURL = getURLFromClasspaths();
+    }
+    resourceURL = localResourceURL;
+
+    supportedBundledAnnotationClasses = new LinkedHashSet<>();
+
+    loadBundledAnnotationClasses();
+  }
+
+  /**
+   * Scans all classpaths and returns the resource URL to the jar which contains the checker's qual
+   * package, or the qual package directory if it exists, or null if no jar or directory contains
+   * the package.
+   *
+   * @return a URL to the jar that contains the qual package, or to the qual package's directory, or
+   *     null if no jar or directory contains the qual package
+   */
+  private final @Nullable URL getURLFromClasspaths() {
+    // TODO: This method could probably be replaced with
+    // io.github.classgraph.ClassGraph#getClasspathURIs()
+
+    // Debug use, uncomment if needed to see all of the classpaths (boot
+    // classpath, extension classpath, and classpath)
+    // printPaths();
+
+    URL url = null;
+
+    // obtain all classpaths
+    Set<String> paths = getClasspaths();
+
+    // In checkers, there will be a resource URL for the qual directory. But when called in the
+    // framework (eg GeneralAnnotatedTypeFactory), there won't be a resourceURL since there isn't a
+    // qual directory.
+
+    // Each path from the set of classpaths will be checked to see if it contains the qual directory
+    // of a checker, if so, the first directory or jar that contains the package will be used as the
+    // source for loading classes from the qual package.
+
+    // If either a directory or a jar contains the package, resourceURL will be updated to refer to
+    // that source, otherwise resourceURL remains as null.
+
+    // If both a jar and a directory contain the qual package, then the order of the jar and the
+    // directory in the command line option(s) or environment variables will decide which one gets
+    // examined first.
+    for (String path : paths) {
+      // see if the current classpath segment is a jar or a directory
+      if (path.endsWith(JAR_SUFFIX)) {
+        // current classpath segment is a jar
+        url = getJarURL(path);
+
+        // see if the jar contains the package
+        if (url != null && containsPackage(url)) {
+          return url;
+        }
+      } else {
+        // current classpath segment is a directory
+        url = getDirectoryURL(path);
+
+        // see if the directory contains the package
+        if (url != null && containsPackage(url)) {
+          // append a slash if necessary
+          if (!path.endsWith(Character.toString(SLASH))) {
+            path += SLASH;
+          }
+
+          // update URL to the qual directory
+          url = getDirectoryURL(path + packageNameWithSlashes);
+
+          return url;
+        }
+      }
+    }
+
+    // if no jar or directory contains the qual package, then return null
+    return null;
+  }
+
+  /**
+   * Checks to see if the jar or directory referred by the URL contains the qual package of a
+   * specific checker.
+   *
+   * @param url a URL referring to either a jar or a directory
+   * @return true if the jar or the directory contains the qual package, false otherwise
+   */
+  private final boolean containsPackage(final URL url) {
+    // see whether the resource URL has a protocol of jar or file
+    if (url.getProtocol().equals("jar")) {
+      // try to open up the jar file
+      try {
+        JarURLConnection connection = (JarURLConnection) url.openConnection();
+        JarFile jarFile = connection.getJarFile();
+
+        // check to see if the jar file contains the package
+        return checkJarForPackage(jarFile);
+      } catch (IOException e) {
+        // do nothing for missing or un-openable Jar files
+      }
+    } else if (url.getProtocol().equals("file")) {
+      // open up the directory
+      File rootDir = new File(url.getFile());
+
+      // check to see if the directory contains the package
+      return checkDirForPackage(rootDir, fullyQualifiedPackageNameSegments.iterator());
+    }
+
+    return false;
+  }
+
+  /**
+   * Checks to see if the jar file contains the qual package of a specific checker.
+   *
+   * @param jar a jar file
+   * @return true if the jar file contains the qual package, false otherwise
+   */
+  @SuppressWarnings("JdkObsolete")
+  private final boolean checkJarForPackage(final JarFile jar) {
+    Enumeration<JarEntry> jarEntries = jar.entries();
+
+    // loop through the entries in the jar
+    while (jarEntries.hasMoreElements()) {
+      JarEntry je = jarEntries.nextElement();
+
+      // Each entry is the fully qualified path and file name to a particular artifact in the jar
+      // file (eg a class file).
+      // If the jar has the package, one of the entry's name will begin with the package name in
+      // slash notation.
+      String entryName = je.getName();
+      if (entryName.startsWith(packageNameWithSlashes + SLASH)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Checks to see if the current directory contains the qual package through recursion currentDir
+   * starts at the root directory (a directory passed in as part of the classpaths), the iterator
+   * goes through each segment of the fully qualified package name (each segment is separated by a
+   * dot).
+   *
+   * <p>Each step of the recursion checks to see if there's a subdirectory in the current directory
+   * that has a name matching the package name segment, if so, it recursively descends into that
+   * subdirectory to check the next package name segment
+   *
+   * <p>If there's no more segments left, then we've found the qual directory of interest
+   *
+   * <p>If we've checked every subdirectory and none of them match the current package name segment,
+   * then the qual directory of interest does not exist in the given root directory (at the
+   * beginning of recursion)
+   *
+   * @param currentDir current directory
+   * @param pkgNames an iterator which provides each segment of the fully qualified qual package
+   *     name
+   * @return true if the qual package exists within the root directory, false otherwise
+   */
+  private final boolean checkDirForPackage(final File currentDir, final Iterator<String> pkgNames) {
+    // if the iterator has no more package name segments, then we've found
+    // the qual directory of interest
+    if (!pkgNames.hasNext()) {
+      return true;
+    }
+    // if the file doesn't exist or it isn't a directory, return false
+    if (currentDir == null || !currentDir.isDirectory()) {
+      return false;
+    }
+
+    // if it isn't empty, dequeue one segment of the fully qualified package name
+    String currentPackageDirName = pkgNames.next();
+
+    // scan current directory to see if there's a sub-directory that has a
+    // matching name as the package name segment
+    for (File file : currentDir.listFiles()) {
+      if (file.isDirectory() && file.getName().equals(currentPackageDirName)) {
+        // if so, recursively descend and look at the next segment of
+        // the package name
+        return checkDirForPackage(file, pkgNames);
+      }
+    }
+
+    // if no sub-directory has a matching name, then that means there isn't
+    // a matching qual package
+    return false;
+  }
+
+  /**
+   * Given an absolute path to a directory, this method will return a URL reference to that
+   * directory.
+   *
+   * @param absolutePathToDirectory an absolute path to a directory
+   * @return a URL reference to the directory, or null if the URL is malformed
+   */
+  private final @Nullable URL getDirectoryURL(final String absolutePathToDirectory) {
+    URL directoryURL = null;
+
+    try {
+      directoryURL = new File(absolutePathToDirectory).toURI().toURL();
+    } catch (MalformedURLException e) {
+      processingEnv
+          .getMessager()
+          .printMessage(Kind.NOTE, "Directory URL " + absolutePathToDirectory + " is malformed");
+    }
+
+    return directoryURL;
+  }
+
+  /**
+   * Given an absolute path to a jar file, this method will return a URL reference to that jar file.
+   *
+   * @param absolutePathToJarFile an absolute path to a jar file
+   * @return a URL reference to the jar file, or null if the URL is malformed
+   */
+  private final @Nullable URL getJarURL(final String absolutePathToJarFile) {
+    URL jarURL = null;
+
+    try {
+      jarURL = new URL("jar:file:" + absolutePathToJarFile + "!/");
+    } catch (MalformedURLException e) {
+      processingEnv
+          .getMessager()
+          .printMessage(Kind.NOTE, "Jar URL " + absolutePathToJarFile + " is malformed");
+    }
+
+    return jarURL;
+  }
+
+  /**
+   * Obtains and returns a set of the classpaths from compiler options, system environment
+   * variables, and by examining the classloader to see what paths it has access to.
+   *
+   * <p>The classpaths will be obtained in the order of:
+   *
+   * <ol>
+   *   <li>extension paths (from java.ext.dirs)
+   *   <li>classpaths (set in {@code CLASSPATH}, or through {@code -classpath} and {@code -cp})
+   *   <li>paths accessible and examined by the classloader
+   * </ol>
+   *
+   * In each of these paths, the order of the paths as specified in the command line options or
+   * environment variables will be the order returned in the set
+   *
+   * @return an immutable linked hashset of the classpaths
+   */
+  private final Set<String> getClasspaths() {
+    Set<String> paths = new LinkedHashSet<>();
+
+    // add all extension paths
+    String extdirs = System.getProperty("java.ext.dirs");
+    if (extdirs != null && !extdirs.isEmpty()) {
+      paths.addAll(Arrays.asList(extdirs.split(File.pathSeparator)));
+    }
+
+    // add all paths in CLASSPATH, -cp, and -classpath
+    paths.addAll(Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator)));
+
+    // add all paths that are examined by the classloader
+    if (classLoader != null) {
+      URL[] urls = classLoader.getURLs();
+      for (int i = 0; i < urls.length; i++) {
+        paths.add(urls[i].getFile().toString());
+      }
+    }
+
+    return Collections.unmodifiableSet(paths);
+  }
+
+  /**
+   * Obtains the classloader used to load the checker class, if that isn't available then it will
+   * try to obtain the system classloader.
+   *
+   * @return the classloader used to load the checker class, or the system classloader, or null if
+   *     both are unavailable
+   */
+  private final @Nullable URLClassLoader getClassLoader() {
+    ClassLoader result = InternalUtils.getClassLoaderForClass(checker.getClass());
+    if (result instanceof URLClassLoader) {
+      return (@Nullable URLClassLoader) result;
+    } else {
+      // Java 9+ use an internal classloader that doesn't support getting URLs. Ignore.
+      return null;
+    }
+  }
+
+  /** Debug Use: Displays all classpaths examined by the class loader. */
+  @SuppressWarnings("unused") // for debugging
+  protected final void printPaths() {
+    // all paths in Xbootclasspath
+    String[] bootclassPaths = System.getProperty("sun.boot.class.path").split(File.pathSeparator);
+    processingEnv.getMessager().printMessage(Kind.NOTE, "bootclass path:");
+    for (String path : bootclassPaths) {
+      processingEnv.getMessager().printMessage(Kind.NOTE, "\t" + path);
+    }
+
+    // all extension paths
+    String[] extensionDirs = System.getProperty("java.ext.dirs").split(File.pathSeparator);
+    processingEnv.getMessager().printMessage(Kind.NOTE, "extension dirs:");
+    for (String path : extensionDirs) {
+      processingEnv.getMessager().printMessage(Kind.NOTE, "\t" + path);
+    }
+
+    // all paths in CLASSPATH, -cp, and -classpath
+    processingEnv.getMessager().printMessage(Kind.NOTE, "java.class.path property:");
+    for (String path : System.getProperty("java.class.path").split(File.pathSeparator)) {
+      processingEnv.getMessager().printMessage(Kind.NOTE, "\t" + path);
+    }
+
+    // add all paths that are examined by the classloader
+    processingEnv.getMessager().printMessage(Kind.NOTE, "classloader examined paths:");
+    if (classLoader != null) {
+      URL[] urls = classLoader.getURLs();
+      for (int i = 0; i < urls.length; i++) {
+        processingEnv.getMessager().printMessage(Kind.NOTE, "\t" + urls[i].getFile());
+      }
+    } else {
+      processingEnv.getMessager().printMessage(Kind.NOTE, "classloader unavailable");
+    }
+  }
+
+  /**
+   * Loads the set of annotation classes in the qual directory of a checker shipped with the Checker
+   * Framework.
+   */
+  private void loadBundledAnnotationClasses() {
+    // retrieve the fully qualified class names of the annotations
+    Set<@BinaryName String> annotationNames;
+    // see whether the resource URL has a protocol of jar or file
+    if (resourceURL != null && resourceURL.getProtocol().contentEquals("jar")) {
+      // if the checker class file is contained within a jar, then the resource URL for the qual
+      // directory will have the protocol "jar". This means the whole checker is loaded as a jar
+      // file.
+
+      JarURLConnection connection;
+      // create a connection to the jar file
+      try {
+        connection = (JarURLConnection) resourceURL.openConnection();
+
+        // disable caching / connection sharing of the low level URLConnection to the Jar file
+        connection.setDefaultUseCaches(false);
+        connection.setUseCaches(false);
+
+        // connect to the Jar file
+        connection.connect();
+      } catch (IOException e) {
+        throw new BugInCF(
+            "AnnotationClassLoader: cannot open a connection to the Jar file "
+                + resourceURL.getFile());
+      }
+
+      // open up that jar file and extract annotation class names
+      try (JarFile jarFile = connection.getJarFile()) {
+        // get class names inside the jar file within the particular package
+        annotationNames = getBundledAnnotationNamesFromJar(jarFile);
+      } catch (IOException e) {
+        throw new BugInCF(
+            "AnnotationClassLoader: cannot open the Jar file " + resourceURL.getFile());
+      }
+
+    } else if (resourceURL != null && resourceURL.getProtocol().contentEquals("file")) {
+      // If the checker class file is found within the file system itself within some directory
+      // (usually development build directories), then process the package as a file directory in
+      // the file system and load the annotations contained in the qual directory.
+
+      // open up the directory
+      File packageDir = new File(resourceURL.getFile());
+      annotationNames = getAnnotationNamesFromDirectory(packageName, packageDir, packageDir);
+    } else {
+      // We do not support a resource URL with any other protocols, so create an empty set.
+      annotationNames = Collections.emptySet();
+    }
+    if (annotationNames.isEmpty()) {
+      PackageElement pkgEle = checker.getElementUtils().getPackageElement(packageName);
+      if (pkgEle != null) {
+        for (Element e : pkgEle.getEnclosedElements()) {
+          if (e.getKind() == ElementKind.ANNOTATION_TYPE) {
+            @SuppressWarnings("signature:assignment") // Elements needs to be annotated.
+            @BinaryName String annoBinName =
+                checker.getElementUtils().getBinaryName((TypeElement) e).toString();
+            annotationNames.add(annoBinName);
+          }
+        }
+      }
+    }
+    supportedBundledAnnotationClasses.addAll(loadAnnotationClasses(annotationNames));
+  }
+
+  /**
+   * Gets the set of annotation classes in the qual directory of a checker shipped with the Checker
+   * Framework. Note that the returned set from this method is mutable. This method is intended to
+   * be called within {@link AnnotatedTypeFactory#createSupportedTypeQualifiers()
+   * createSupportedTypeQualifiers()} (or its helper methods) to help define the set of supported
+   * qualifiers.
+   *
+   * @see AnnotatedTypeFactory#createSupportedTypeQualifiers()
+   * @return a mutable set of the loaded bundled annotation classes
+   */
+  public final Set<Class<? extends Annotation>> getBundledAnnotationClasses() {
+    return supportedBundledAnnotationClasses;
+  }
+
+  /**
+   * Retrieves the annotation class file names from the qual directory contained inside a jar.
+   *
+   * @param jar the JarFile containing the annotation class files
+   * @return a set of fully qualified class names of the annotations
+   */
+  @SuppressWarnings("JdkObsolete")
+  private final Set<@BinaryName String> getBundledAnnotationNamesFromJar(final JarFile jar) {
+    Set<@BinaryName String> annos = new LinkedHashSet<>();
+
+    // get an enumeration iterator for all the content entries in the jar file
+    Enumeration<JarEntry> jarEntries = jar.entries();
+
+    // enumerate through the entries
+    while (jarEntries.hasMoreElements()) {
+      JarEntry je = jarEntries.nextElement();
+      // filter out directories and non-class files
+      if (je.isDirectory() || !je.getName().endsWith(CLASS_SUFFIX)) {
+        continue;
+      }
+
+      String className = Signatures.classfilenameToBinaryName(je.getName());
+
+      // filter for qual package
+      if (className.startsWith(packageName + DOT)) {
+        // add to set
+        annos.add(className);
+      }
+    }
+
+    return annos;
+  }
+
+  /**
+   * This method takes as input the canonical name of an external annotation class and loads and
+   * returns that class via the class loader. This method returns null if the external annotation
+   * class was loaded successfully but was deemed not supported by a checker. Errors are issued if
+   * the external class is not an annotation, or if it could not be loaded successfully.
+   *
+   * @param annoName canonical name of an external annotation class, e.g.
+   *     "myproject.qual.myannotation"
+   * @return the loaded annotation class, or null if it was not a supported annotation as decided by
+   *     {@link #isSupportedAnnotationClass(Class)}
+   */
+  public final @Nullable Class<? extends Annotation> loadExternalAnnotationClass(
+      final @BinaryName String annoName) {
+    return loadAnnotationClass(annoName, true);
+  }
+
+  /**
+   * This method takes as input a fully qualified path to a directory, and loads and returns the set
+   * of all supported annotation classes from that directory.
+   *
+   * @param dirName absolute path to a directory containing annotation classes
+   * @return a set of annotation classes
+   */
+  public final Set<Class<? extends Annotation>> loadExternalAnnotationClassesFromDirectory(
+      final String dirName) {
+    File rootDirectory = new File(dirName);
+    Set<@BinaryName String> annoNames =
+        getAnnotationNamesFromDirectory(null, rootDirectory, rootDirectory);
+    return loadAnnotationClasses(annoNames);
+  }
+
+  /**
+   * Retrieves all annotation names from the current directory, and recursively descends and
+   * retrieves annotation names from sub-directories.
+   *
+   * @param packageName the name of the package that contains the qual package, or null
+   * @param rootDirectory a {@link File} object representing the root directory of a set of
+   *     annotations, which is subtracted from class names to retrieve each class's fully qualified
+   *     class names
+   * @param currentDirectory a {@link File} object representing the current sub-directory of the
+   *     root directory
+   * @return a set fully qualified annotation class name, for annotations in the root directory or
+   *     its sub-directories
+   */
+  @SuppressWarnings("signature") // TODO: reduce use of string manipulation
+  private final Set<@BinaryName String> getAnnotationNamesFromDirectory(
+      final @Nullable @DotSeparatedIdentifiers String packageName,
+      final File rootDirectory,
+      final File currentDirectory) {
+    Set<@BinaryName String> results = new LinkedHashSet<>();
+
+    // Full path to root directory
+    String rootPath = rootDirectory.getAbsolutePath();
+
+    // check every file and directory within the current directory
+    File[] directoryContents = currentDirectory.listFiles();
+    Arrays.sort(
+        directoryContents,
+        new Comparator<File>() {
+          @Override
+          public int compare(File o1, File o2) {
+            return o1.getName().compareTo(o2.getName());
+          }
+        });
+    for (File file : directoryContents) {
+      if (file.isFile()) {
+        // TODO: simplify all this string manipulation.
+
+        // Full file name, including path to file
+        String fullFileName = file.getAbsolutePath();
+        // Simple file name
+        String fileName =
+            fullFileName.substring(
+                fullFileName.lastIndexOf(File.separator) + 1, fullFileName.length());
+        // Path to file
+        String filePath = fullFileName.substring(0, fullFileName.lastIndexOf(File.separator));
+        // Package name beginning with "qual"
+        String qualPackage = null;
+        if (!filePath.equals(rootPath)) {
+          qualPackage =
+              filePath.substring(rootPath.length() + 1, filePath.length()).replace(SLASH, DOT);
+        }
+        // Simple annotation name, which is the same as the file name (without directory)
+        // but with file extension removed.
+        @BinaryName String annotationName = fileName;
+        if (fileName.lastIndexOf(DOT) != -1) {
+          annotationName = fileName.substring(0, fileName.lastIndexOf(DOT));
+        }
+
+        // Fully qualified annotation class name (a @BinaryName, not a @FullyQualifiedName)
+        @BinaryName String fullyQualifiedAnnoName =
+            Signatures.addPackage(packageName, Signatures.addPackage(qualPackage, annotationName));
+
+        if (fileName.endsWith(CLASS_SUFFIX)) {
+          // add the fully qualified annotation class name to the set
+          results.add(fullyQualifiedAnnoName);
+        }
+      } else if (file.isDirectory()) {
+        // recursively add all sub directories's fully qualified annotation class name
+        results.addAll(getAnnotationNamesFromDirectory(packageName, rootDirectory, file));
+      }
+    }
+
+    return results;
+  }
+
+  /**
+   * Loads the class indicated by the name, and checks to see if it is an annotation that is
+   * supported by a checker.
+   *
+   * @param className the name of the class, in binary name format
+   * @param issueError set to true to issue a warning when a loaded annotation is not a type
+   *     annotation. It is useful to set this to true if a given annotation must be a well-defined
+   *     type annotation (eg for annotation class names given as command line arguments). It should
+   *     be set to false if the annotation is a meta-annotation or non-type annotation.
+   * @return the loaded annotation class if it has a {@code @Target} meta-annotation with the
+   *     required ElementType values, and is a supported annotation by a checker. If the annotation
+   *     is not supported by a checker, null is returned.
+   */
+  protected final @Nullable Class<? extends Annotation> loadAnnotationClass(
+      final @BinaryName String className, boolean issueError) {
+
+    // load the class
+    Class<?> cls = null;
+    try {
+      if (classLoader != null) {
+        cls = Class.forName(className, true, classLoader);
+      } else {
+        cls = Class.forName(className);
+      }
+    } catch (ClassNotFoundException e) {
+      throw new UserError(
+          checker.getClass().getSimpleName()
+              + ": could not load class for annotation: "
+              + className
+              + ". Ensure that it is a type annotation"
+              + " and your classpath is correct.");
+    }
+
+    // If the freshly loaded class is not an annotation, then issue error if required and then
+    // return null
+    if (!cls.isAnnotation()) {
+      if (issueError) {
+        throw new UserError(
+            checker.getClass().getSimpleName()
+                + ": the loaded class: "
+                + cls.getCanonicalName()
+                + " is not a type annotation.");
+      }
+      return null;
+    }
+
+    Class<? extends Annotation> annoClass = cls.asSubclass(Annotation.class);
+    // Check the loaded annotation to see if it has a @Target meta-annotation with the required
+    // ElementType values
+    if (hasWellDefinedTargetMetaAnnotation(annoClass)) {
+      // If so, return the loaded annotation if it is supported by a checker
+      return isSupportedAnnotationClass(annoClass) ? annoClass : null;
+    } else if (issueError) {
+      // issueError is set to true for loading explicitly named external annotations.
+      // We issue an error here when one of those annotations is not well-defined, since the
+      // user expects these external annotations to be loaded.
+      throw new UserError(
+          checker.getClass().getSimpleName()
+              + ": the loaded annotation: "
+              + annoClass.getCanonicalName()
+              + " is not a type annotation."
+              + " Check its @Target meta-annotation.");
+    } else {
+      // issueError is set to false for loading the qual directory or any external directories.
+      // We don't issue any errors since there may be meta-annotations or non-type annotations
+      // in such directories.
+      return null;
+    }
+  }
+
+  /**
+   * Loads a set of annotations indicated by their names.
+   *
+   * @param annoNames a set of binary names for annotation classes
+   * @return a set of loaded annotation classes
+   * @see #loadAnnotationClass(String, boolean)
+   */
+  protected final Set<Class<? extends Annotation>> loadAnnotationClasses(
+      final @Nullable Set<@BinaryName String> annoNames) {
+    Set<Class<? extends Annotation>> loadedClasses = new LinkedHashSet<>();
+
+    if (annoNames != null && !annoNames.isEmpty()) {
+      // loop through each class name & load the class
+      for (String annoName : annoNames) {
+        Class<? extends Annotation> annoClass = loadAnnotationClass(annoName, false);
+        if (annoClass != null) {
+          loadedClasses.add(annoClass);
+        }
+      }
+    }
+
+    return loadedClasses;
+  }
+
+  /**
+   * Checks to see whether a particular annotation class has the {@link Target} meta-annotation, and
+   * has the required {@link ElementType} values.
+   *
+   * <p>A subclass may override this method to load annotations that are not intended to be
+   * annotated in source code. E.g.: {@code SubtypingChecker} overrides this method to load {@code
+   * Unqualified}.
+   *
+   * @param annoClass an annotation class
+   * @return true if the annotation is well defined, false if it isn't
+   */
+  protected boolean hasWellDefinedTargetMetaAnnotation(
+      final Class<? extends Annotation> annoClass) {
+    return annoClass.getAnnotation(Target.class) != null
+        && AnnotationUtils.hasTypeQualifierElementTypes(
+            annoClass.getAnnotation(Target.class).value(), annoClass);
+  }
+
+  /**
+   * Checks to see whether a particular annotation class is supported.
+   *
+   * <p>By default, all loaded annotations that pass the basic checks in {@link
+   * #loadAnnotationClass(String, boolean)} are supported.
+   *
+   * <p>Individual checkers can create a subclass of AnnotationClassLoader and override this method
+   * to indicate whether a particular annotation is supported.
+   *
+   * @param annoClass an annotation class
+   * @return true if the annotation is supported, false if it isn't
+   */
+  protected boolean isSupportedAnnotationClass(final Class<? extends Annotation> annoClass) {
+    return true;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java
new file mode 100644
index 0000000..2b07a02
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java
@@ -0,0 +1,841 @@
+package org.checkerframework.framework.type;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AbstractAtmComboVisitor;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Implements asSuper {@link AnnotatedTypes#asSuper(AnnotatedTypeFactory, AnnotatedTypeMirror,
+ * AnnotatedTypeMirror)}.
+ */
+public class AsSuperVisitor extends AbstractAtmComboVisitor<AnnotatedTypeMirror, Void> {
+
+  /** Type utilities. */
+  private final Types types;
+  /** The type factory. */
+  private final AnnotatedTypeFactory atypeFactory;
+  /**
+   * Whether or not the type being visited is an uninferred type argument. If true, then the
+   * underlying type may not have the correct relationship with the supertype.
+   */
+  private boolean isUninferredTypeArgument = false;
+
+  /**
+   * Create a new AsSuperVisitor.
+   *
+   * @param atypeFactory the type factory
+   */
+  public AsSuperVisitor(AnnotatedTypeFactory atypeFactory) {
+    this.atypeFactory = atypeFactory;
+    types = atypeFactory.types;
+  }
+
+  /**
+   * Implements asSuper. See {@link AnnotatedTypes#asSuper(AnnotatedTypeFactory,
+   * AnnotatedTypeMirror, AnnotatedTypeMirror)} for details.
+   *
+   * @param <T> the type of the supertype
+   * @param type type from which to copy annotations
+   * @param superType a type whose erased Java type is a supertype of {@code type}'s erased Java
+   *     type.
+   * @return a copy of {@code superType} with annotations copied from {@code type} and type
+   *     variables substituted from {@code type}.
+   */
+  @SuppressWarnings({
+    "unchecked",
+    "interning:not.interned" // optimized special case
+  })
+  public <T extends AnnotatedTypeMirror> T asSuper(AnnotatedTypeMirror type, T superType) {
+    if (type == null || superType == null) {
+      throw new BugInCF("AsSuperVisitor type and supertype cannot be null.");
+    }
+
+    if (type == superType) {
+      return (T) type.deepCopy();
+    }
+
+    // This visitor modifies superType and may return type, so pass it copies so that the
+    // parameters to asSuper are not changed and a copy is returned.
+    AnnotatedTypeMirror copyType = type.deepCopy();
+    AnnotatedTypeMirror copySuperType = superType.deepCopy();
+    reset();
+    AnnotatedTypeMirror result = visit(copyType, copySuperType, null);
+
+    if (result == null) {
+      throw new BugInCF(
+          "AsSuperVisitor returned null.%ntype: %s%nsuperType: %s", type, copySuperType);
+    }
+
+    return (T) result;
+  }
+
+  private void reset() {
+    isUninferredTypeArgument = false;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visit(
+      AnnotatedTypeMirror type, AnnotatedTypeMirror superType, Void p) {
+    ensurePrimaryIsCorrectForUnions(type);
+    return super.visit(type, superType, p);
+  }
+
+  /**
+   * The code in this class is assuming that the primary annotation of an {@link AnnotatedUnionType}
+   * is the least upper bound of its alternatives. This method makes this assumption true.
+   *
+   * @param type any kind of {@code AnnotatedTypeMirror}
+   */
+  private void ensurePrimaryIsCorrectForUnions(AnnotatedTypeMirror type) {
+    if (type.getKind() == TypeKind.UNION) {
+      AnnotatedUnionType annotatedUnionType = (AnnotatedUnionType) type;
+      Set<AnnotationMirror> lubs = null;
+      for (AnnotatedDeclaredType altern : annotatedUnionType.getAlternatives()) {
+        if (lubs == null) {
+          lubs = altern.getAnnotations();
+        } else {
+          Set<AnnotationMirror> newLubs = AnnotationUtils.createAnnotationSet();
+          for (AnnotationMirror lub : lubs) {
+            AnnotationMirror anno = altern.getAnnotationInHierarchy(lub);
+            newLubs.add(atypeFactory.getQualifierHierarchy().leastUpperBound(anno, lub));
+          }
+          lubs = newLubs;
+        }
+      }
+      type.replaceAnnotations(lubs);
+    }
+  }
+
+  @Override
+  protected String defaultErrorMessage(
+      AnnotatedTypeMirror type, AnnotatedTypeMirror superType, Void p) {
+    return String.format(
+        "AsSuperVisitor: Unexpected combination: type: %s superType: %s.%n"
+            + "type: %s%nsuperType: %s",
+        type.getKind(), superType.getKind(), type, superType);
+  }
+
+  private AnnotatedTypeMirror errorTypeNotErasedSubtypeOfSuperType(
+      AnnotatedTypeMirror type, AnnotatedTypeMirror superType, Void p) {
+    if (TypesUtils.isString(superType.getUnderlyingType())) {
+      // Any type can be converted to String
+      return visit(atypeFactory.getStringType(type), superType, p);
+    }
+    if (isUninferredTypeArgument) {
+      return copyPrimaryAnnos(type, superType);
+    }
+    throw new BugInCF(
+        "AsSuperVisitor: type is not an erased subtype of supertype." + "%ntype: %s%nsuperType: %s",
+        type, superType);
+  }
+
+  private AnnotatedTypeMirror copyPrimaryAnnos(AnnotatedTypeMirror from, AnnotatedTypeMirror to) {
+    // There may have been annotations added by a recursive call to asSuper, so replace existing
+    // annotations
+    to.replaceAnnotations(new ArrayList<>(from.getAnnotations()));
+    // if to is a Typevar or Wildcard, then replaceAnnotations also sets primary annotations on
+    // the bounds to from.getAnnotations()
+
+    if (to.getKind() == TypeKind.UNION) {
+      // Make sure that the alternatives have a primary annotations
+      // Alternatives cannot have type arguments, so asSuper isn't called recursively
+      AnnotatedUnionType unionType = (AnnotatedUnionType) to;
+      for (AnnotatedDeclaredType altern : unionType.getAlternatives()) {
+        altern.addMissingAnnotations(unionType.getAnnotations());
+      }
+    }
+    return to;
+  }
+
+  /**
+   * A helper method for asSuper(AMT, Wildcard) methods to use to annotate the wildcard's lower
+   * bound.
+   *
+   * <p>If the lower bound of superType is Null, then return copyPrimarayAnnos(type, superType)
+   *
+   * <p>otherwise, return asSuper(type, superType.getLowerBound()
+   *
+   * <p>An error is issued if type is a Primitive or Wildcard -- those case are handled in
+   * asSuper(Primitive, Wildcard) and asSuper(Wildcard, Wildcard)
+   *
+   * <p>An error is issued if the lower bound of superType is not Null and type is not a subtype of
+   * the lower bound.
+   */
+  private AnnotatedTypeMirror asSuperWildcardLowerBound(
+      AnnotatedTypeMirror type, AnnotatedWildcardType superType, Void p) {
+    AnnotatedTypeMirror lowerBound = superType.getSuperBound();
+    return asSuperLowerBound(type, p, lowerBound);
+  }
+
+  /** Same as #asSuperWildcardLowerBound, but for Typevars. */
+  private AnnotatedTypeMirror asSuperTypevarLowerBound(
+      AnnotatedTypeMirror type, AnnotatedTypeVariable superType, Void p) {
+    AnnotatedTypeMirror lowerBound = superType.getLowerBound();
+    return asSuperLowerBound(type, p, lowerBound);
+  }
+
+  private AnnotatedTypeMirror asSuperLowerBound(
+      AnnotatedTypeMirror type, Void p, AnnotatedTypeMirror lowerBound) {
+    if (lowerBound.getKind() == TypeKind.NULL) {
+      Set<AnnotationMirror> typeLowerBound =
+          AnnotatedTypes.findEffectiveLowerBoundAnnotations(
+              atypeFactory.getQualifierHierarchy(), type);
+      lowerBound.replaceAnnotations(typeLowerBound);
+      return lowerBound;
+    }
+    if (areErasedJavaTypesEquivalent(type, lowerBound)) {
+      return visit(type, lowerBound, p);
+    }
+    // If type and lowerBound are not the same type, then lowerBound is a subtype of type,
+    // but there is no way to convert type to a subtype -- there is not an asSub method.  So,
+    // just copy the primary annotations.
+    return copyPrimaryAnnos(type, lowerBound);
+  }
+
+  /**
+   * Returns true if the underlying, erased Java type of {@code subtype} is a subtype of the
+   * underlying, erased Java type of {@code supertype}.
+   *
+   * @param subtype a type
+   * @param supertype a type
+   * @return true if the underlying, erased Java type of {@code subtype} is a subtype of the
+   *     underlying, erased Java type of {@code supertype}
+   */
+  private boolean isErasedJavaSubtype(
+      AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype) {
+    TypeMirror javaSubtype = types.erasure(subtype.getUnderlyingType());
+    TypeMirror javaSupertype = types.erasure(supertype.getUnderlyingType());
+    return types.isSubtype(javaSubtype, javaSupertype);
+  }
+
+  /**
+   * Returns true if the underlying, erased Java type of {@code typeA} and {@code typeB} are
+   * equivalent.
+   *
+   * @param typeA a type
+   * @param typeB a type
+   * @return true if the underlying, erased Java type of {@code typeA} and {@code typeB} are
+   *     equivalent
+   */
+  private boolean areErasedJavaTypesEquivalent(
+      AnnotatedTypeMirror typeA, AnnotatedTypeMirror typeB) {
+    TypeMirror underlyingTypeA = types.erasure(typeA.getUnderlyingType());
+    TypeMirror underlyingTypeB = types.erasure(typeB.getUnderlyingType());
+    return types.isSameType(underlyingTypeA, underlyingTypeB);
+  }
+
+  // <editor-fold defaultstate="collapsed" desc="visitArray_Other methods">
+  @Override
+  public AnnotatedTypeMirror visitArray_Array(
+      AnnotatedArrayType type, AnnotatedArrayType superType, Void p) {
+    AnnotatedTypeMirror asSuperCT = visit(type.getComponentType(), superType.getComponentType(), p);
+    superType.setComponentType(asSuperCT);
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitArray_Intersection(
+      AnnotatedArrayType type, AnnotatedIntersectionType superType, Void p) {
+    for (AnnotatedTypeMirror bounds : superType.getBounds()) {
+      if (!(TypesUtils.isObject(bounds.getUnderlyingType())
+          || TypesUtils.isDeclaredOfName(bounds.getUnderlyingType(), "java.lang.Cloneable")
+          || TypesUtils.isDeclaredOfName(bounds.getUnderlyingType(), "java.io.Serializable"))) {
+        return errorTypeNotErasedSubtypeOfSuperType(type, superType, p);
+      }
+      copyPrimaryAnnos(type, bounds);
+    }
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitArray_Declared(
+      AnnotatedArrayType type, AnnotatedDeclaredType superType, Void p) {
+
+    TypeElement array = TypesUtils.getTypeElement(type.getUnderlyingType());
+    TypeElement possibleArray = TypesUtils.getTypeElement(superType.getUnderlyingType());
+    // If the TypeElements of type and superType are equal, then superType's underlyingType is
+    // Array.class.  Array.class is the receiver of methods such as clone() of which an array
+    // can be the receiver. (new int[].clone())
+    boolean isArrayClass = array.equals(possibleArray);
+
+    if (isArrayClass
+        || TypesUtils.isObject(superType.getUnderlyingType())
+        || TypesUtils.isDeclaredOfName(superType.getUnderlyingType(), "java.lang.Cloneable")
+        || TypesUtils.isDeclaredOfName(superType.getUnderlyingType(), "java.io.Serializable")) {
+      return copyPrimaryAnnos(type, superType);
+    }
+    return errorTypeNotErasedSubtypeOfSuperType(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitArray_Typevar(
+      AnnotatedArrayType type, AnnotatedTypeVariable superType, Void p) {
+    AnnotatedTypeMirror upperBound = visit(type, superType.getUpperBound(), p);
+    superType.setUpperBound(upperBound);
+
+    AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p);
+    superType.setLowerBound(lowerBound);
+
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitArray_Wildcard(
+      AnnotatedArrayType type, AnnotatedWildcardType superType, Void p) {
+    AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p);
+    superType.setExtendsBound(upperBound);
+
+    AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p);
+    superType.setSuperBound(lowerBound);
+
+    return copyPrimaryAnnos(type, superType);
+  }
+  // </editor-fold>
+
+  // <editor-fold defaultstate="collapsed" desc="visitDeclared_Other methods">
+  @Override
+  public AnnotatedTypeMirror visitDeclared_Declared(
+      AnnotatedDeclaredType type, AnnotatedDeclaredType superType, Void p) {
+    if (areErasedJavaTypesEquivalent(type, superType)) {
+      return type;
+    }
+
+    // Not same erased Java type.
+    // Walk up the directSupertypes.
+    // directSupertypes() annotates type variables correctly and handles substitution.
+    for (AnnotatedDeclaredType dst : type.directSupertypes()) {
+      if (isErasedJavaSubtype(dst, superType)) {
+        // If two direct supertypes of type, dst1 and dst2, are subtypes of superType then
+        // asSuper(dst1, superType) and asSuper(dst2, superType) return equivalent ATMs, so
+        // return the first one found.
+        return visit(dst, superType, p);
+      }
+    }
+
+    return errorTypeNotErasedSubtypeOfSuperType(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitDeclared_Intersection(
+      AnnotatedDeclaredType type, AnnotatedIntersectionType superType, Void p) {
+    List<AnnotatedTypeMirror> newBounds = new ArrayList<>();
+    // Each type in the intersection must be a supertype of type, so call asSuper on all types
+    // in the intersection.
+    for (AnnotatedTypeMirror superBound : superType.getBounds()) {
+      if (types.isSubtype(type.getUnderlyingType(), superBound.getUnderlyingType())) {
+        AnnotatedTypeMirror found = visit(type, superBound, p);
+        newBounds.add(found);
+      }
+    }
+    // The ATM for each type in an intersection is stored in the direct super types field.
+    superType.setBounds(newBounds);
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitDeclared_Primitive(
+      AnnotatedDeclaredType type, AnnotatedPrimitiveType superType, Void p) {
+    if (!TypesUtils.isBoxedPrimitive(type.getUnderlyingType())) {
+      throw new BugInCF("AsSuperVisitor Declared_Primitive: type is not a boxed primitive.");
+    }
+    AnnotatedTypeMirror unboxedType = atypeFactory.getUnboxedType(type);
+    return copyPrimaryAnnos(unboxedType, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitDeclared_Typevar(
+      AnnotatedDeclaredType type, AnnotatedTypeVariable superType, Void p) {
+    // setUpperBound() may have a side effect on parameter "type" when the upper bound of
+    // "superType" equals to "type" (referencing the same object: changes will be shared)
+    // copy before visiting to avoid
+    // without fix, this would fail:
+    // https://github.com/typetools/checker-framework/blob/ed340b2dfa1e51bbc0a7313f22638179d15bf2df/checker/tests/nullness/Issue2432b.java
+    AnnotatedTypeMirror typeCopy = type.deepCopy();
+    AnnotatedTypeMirror upperBound = visit(typeCopy, superType.getUpperBound(), p).asUse();
+    superType.setUpperBound(upperBound);
+
+    AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p).asUse();
+    superType.setLowerBound(lowerBound);
+
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitDeclared_Union(
+      AnnotatedDeclaredType type, AnnotatedUnionType superType, Void p) {
+    // Alternatives in a union type can't have type args, so just copy the primary annotation
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitDeclared_Wildcard(
+      AnnotatedDeclaredType type, AnnotatedWildcardType superType, Void p) {
+    AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p).asUse();
+    superType.setExtendsBound(upperBound);
+
+    AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p).asUse();
+    superType.setSuperBound(lowerBound);
+
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  // </editor-fold>
+
+  // <editor-fold defaultstate="collapsed" desc="visitIntersection_Other methods">
+
+  @Override
+  public AnnotatedTypeMirror visitIntersection_Declared(
+      AnnotatedIntersectionType type, AnnotatedDeclaredType superType, Void p) {
+    for (AnnotatedTypeMirror bound : type.getBounds()) {
+      // Find the directSuperType that is a subtype of superType, then recur on that type so that
+      // type arguments in superType are annotated correctly.
+      if (bound.getKind() == TypeKind.DECLARED
+          && isErasedJavaSubtype((AnnotatedDeclaredType) bound, superType)) {
+        AnnotatedTypeMirror asSuper = visit(bound, superType, p);
+
+        // The directSuperType might have a primary annotation that is a supertype of primary
+        // annotation on type. Copy the primary annotation, because it is more precise.
+        return copyPrimaryAnnos(type, asSuper);
+      }
+    }
+    return errorTypeNotErasedSubtypeOfSuperType(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitIntersection_Intersection(
+      AnnotatedIntersectionType type, AnnotatedIntersectionType superType, Void p) {
+    List<AnnotatedTypeMirror> newDirectSupertypes = new ArrayList<>();
+    for (AnnotatedTypeMirror superBound : superType.getBounds()) {
+      AnnotatedTypeMirror found = null;
+      TypeMirror javaSupertype = types.erasure(superBound.getUnderlyingType());
+      for (AnnotatedTypeMirror bound : type.getBounds()) {
+        TypeMirror javaSubtype = types.erasure(bound.getUnderlyingType());
+        if (types.isSubtype(javaSubtype, javaSupertype)) {
+          found = visit(bound, superBound, p);
+          newDirectSupertypes.add(found);
+          break;
+        }
+      }
+      if (found == null) {
+        throw new BugInCF(
+            "AsSuperVisitor visitIntersection_Intersection:%ntype: %s superType: %s",
+            type, superType);
+      }
+    }
+    superType.setBounds(newDirectSupertypes);
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitIntersection_Primitive(
+      AnnotatedIntersectionType type, AnnotatedPrimitiveType superType, Void p) {
+    for (AnnotatedTypeMirror bound : type.getBounds()) {
+      // Find the directSuperType that is a subtype of superType, then recur on that type
+      // so that type arguments in superType are annotated correctly
+      if (TypesUtils.isBoxedPrimitive(bound.getUnderlyingType())) {
+        AnnotatedTypeMirror asSuper = visit(bound, superType, p);
+
+        // The directSuperType might have a primary annotation that is a supertype of primary
+        // annotation on type. Copy the primary annotation, because it is more precise.
+        return copyPrimaryAnnos(type, asSuper);
+      }
+    }
+    // Cannot happen: one of the types in the intersection must be a subtype of superType.
+    throw new BugInCF(
+        "AsSuperVisitor visitIntersection_Primitive:%ntype: %s superType: %s", type, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitIntersection_Typevar(
+      AnnotatedIntersectionType type, AnnotatedTypeVariable superType, Void p) {
+    AnnotatedTypeMirror upperBound = visit(type, superType.getUpperBound(), p);
+    superType.setUpperBound(upperBound);
+
+    AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p);
+    superType.setLowerBound(lowerBound);
+
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitIntersection_Union(
+      AnnotatedIntersectionType type, AnnotatedUnionType superType, Void p) {
+    TypeMirror javaSupertype = types.erasure(type.getUnderlyingType());
+    for (AnnotatedTypeMirror bound : type.getBounds()) {
+      TypeMirror javaSubtype = types.erasure(superType.getUnderlyingType());
+      if (types.isSubtype(javaSubtype, javaSupertype)) {
+        AnnotatedTypeMirror asSuper = visit(bound, superType, p);
+        return copyPrimaryAnnos(type, asSuper);
+      }
+    }
+    // Cannot happen: one of the types in the intersection must be a subtype of superType.
+    throw new BugInCF(
+        "AsSuperVisitor visitIntersection_Union:%ntype: %s%nsuperType: %s", type, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitIntersection_Wildcard(
+      AnnotatedIntersectionType type, AnnotatedWildcardType superType, Void p) {
+    AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p);
+    superType.setExtendsBound(upperBound);
+
+    AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p);
+    superType.setSuperBound(lowerBound);
+
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  // </editor-fold>
+
+  // <editor-fold defaultstate="collapsed" desc="visitPrimitive_Other methods">
+
+  @Override
+  public AnnotatedTypeMirror visitPrimitive_Primitive(
+      AnnotatedPrimitiveType type, AnnotatedPrimitiveType superType, Void p) {
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  /**
+   * A helper method for visiting a primitive and a non-primitive.
+   *
+   * @param type a primitive type
+   * @param superType some other type
+   * @param p ignore
+   * @return {@code type}, viewed as a {@code superType}
+   */
+  private AnnotatedTypeMirror visitPrimitive_Other(
+      AnnotatedPrimitiveType type, AnnotatedTypeMirror superType, Void p) {
+    return visit(atypeFactory.getBoxedType(type), superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitPrimitive_Declared(
+      AnnotatedPrimitiveType type, AnnotatedDeclaredType superType, Void p) {
+    if (TypesUtils.isBoxedPrimitive(superType.getUnderlyingType())) {
+      TypeMirror unboxedSuper = types.unboxedType(superType.getUnderlyingType());
+      if (unboxedSuper.getKind() != type.getKind()
+          && canBeNarrowingPrimitiveConversion(unboxedSuper)) {
+        AnnotatedPrimitiveType narrowedType = atypeFactory.getNarrowedPrimitive(type, unboxedSuper);
+        return visit(narrowedType, superType, p);
+      }
+    }
+    return visitPrimitive_Other(type, superType, p);
+  }
+
+  /**
+   * Returns true if the type is byte, short, char, Byte, Short, or Character. All other narrowings
+   * require a cast. See JLS 5.1.3.
+   *
+   * @param type a type
+   * @return true if assignment to the type may be a narrowing
+   */
+  private boolean canBeNarrowingPrimitiveConversion(TypeMirror type) {
+    // See CFGBuilder.CFGTranslationPhaseOne#conversionRequiresNarrowing()
+    TypeMirror unboxedType = TypesUtils.isBoxedPrimitive(type) ? types.unboxedType(type) : type;
+    TypeKind unboxedKind = unboxedType.getKind();
+    return unboxedKind == TypeKind.BYTE
+        || unboxedKind == TypeKind.SHORT
+        || unboxedKind == TypeKind.CHAR;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitPrimitive_Intersection(
+      AnnotatedPrimitiveType type, AnnotatedIntersectionType superType, Void p) {
+    return visitPrimitive_Other(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitPrimitive_Typevar(
+      AnnotatedPrimitiveType type, AnnotatedTypeVariable superType, Void p) {
+    return visitPrimitive_Other(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitPrimitive_Union(
+      AnnotatedPrimitiveType type, AnnotatedUnionType superType, Void p) {
+    return visitPrimitive_Other(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitPrimitive_Wildcard(
+      AnnotatedPrimitiveType type, AnnotatedWildcardType superType, Void p) {
+    return visitPrimitive_Other(type, superType, p);
+  }
+  // </editor-fold>
+
+  // <editor-fold defaultstate="collapsed" desc="visitTypevar_Other methods">
+  private AnnotatedTypeMirror visitTypevar_NotTypevarNorWildcard(
+      AnnotatedTypeVariable type, AnnotatedTypeMirror superType, Void p) {
+    AnnotatedTypeMirror asSuper = visit(type.getUpperBound(), superType, p);
+    return copyPrimaryAnnos(type, asSuper);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitTypevar_Declared(
+      AnnotatedTypeVariable type, AnnotatedDeclaredType superType, Void p) {
+    return visitTypevar_NotTypevarNorWildcard(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitTypevar_Intersection(
+      AnnotatedTypeVariable type, AnnotatedIntersectionType superType, Void p) {
+    return visitTypevar_NotTypevarNorWildcard(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitTypevar_Primitive(
+      AnnotatedTypeVariable type, AnnotatedPrimitiveType superType, Void p) {
+    return visitTypevar_NotTypevarNorWildcard(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitTypevar_Typevar(
+      AnnotatedTypeVariable type, AnnotatedTypeVariable superType, Void p) {
+    // Clear the superType annotations and copy over the primary annotations before computing
+    // bounds, so that the superType annotations don't override the type annotations on the bounds.
+    superType.clearAnnotations();
+    copyPrimaryAnnos(type, superType);
+
+    AnnotatedTypeMirror upperBound = visit(type.getUpperBound(), superType.getUpperBound(), p);
+    superType.setUpperBound(upperBound);
+
+    AnnotatedTypeMirror lowerBound;
+    if (type.getLowerBound().getKind() == TypeKind.NULL
+        && superType.getLowerBound().getKind() == TypeKind.NULL) {
+      lowerBound = copyPrimaryAnnos(type.getLowerBound(), superType.getLowerBound());
+    } else if (type.getLowerBound().getKind() == TypeKind.NULL) {
+      lowerBound = visit(type, superType.getLowerBound(), p);
+    } else {
+      lowerBound = asSuperTypevarLowerBound(type.getLowerBound(), superType, p);
+    }
+    superType.setLowerBound(lowerBound);
+
+    return superType;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitTypevar_Union(
+      AnnotatedTypeVariable type, AnnotatedUnionType superType, Void p) {
+    return visitTypevar_NotTypevarNorWildcard(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitTypevar_Wildcard(
+      AnnotatedTypeVariable type, AnnotatedWildcardType superType, Void p) {
+    AnnotatedTypeMirror upperBound = visit(type.getUpperBound(), superType.getExtendsBound(), p);
+    superType.setExtendsBound(upperBound);
+
+    AnnotatedTypeMirror lowerBound;
+    if (type.getLowerBound().getKind() == TypeKind.NULL
+        && superType.getSuperBound().getKind() == TypeKind.NULL) {
+      lowerBound = copyPrimaryAnnos(type.getLowerBound(), superType.getSuperBound());
+    } else if (type.getLowerBound().getKind() == TypeKind.NULL) {
+      lowerBound = visit(type, superType.getSuperBound(), p);
+    } else {
+      lowerBound = asSuperWildcardLowerBound(type.getLowerBound(), superType, p);
+    }
+    superType.setSuperBound(lowerBound);
+
+    return copyPrimaryAnnos(type, superType);
+  }
+  // </editor-fold>
+
+  /* The primary annotation on a union type is the LUB of the primary annotations on its alternatives. #ensurePrimaryIsCorrectForUnions ensures that this is the case.
+
+  All the alternatives in a union type must be subtype of Throwable and cannot have type arguments;
+  however, a union type can be a subtype of an interface with a type argument. For example:
+  interface Interface<T>{}
+  class MyException1 extends Throwable implements Interface<Number>{}
+  class MyException2 extends Throwable implements Interface<Number>{}
+
+  MyException1 <: MyException1 | MyException2 <: Interface<Number>
+  MyException1 | MyException2 <: Throwable & Interface<Number>
+  */
+  // <editor-fold defaultstate="collapsed" desc="visitUnion_Other methods">
+
+  private AnnotatedTypeMirror visitUnion_Other(
+      AnnotatedUnionType type, AnnotatedTypeMirror superType, Void p) {
+    // asSuper on any of the alternatives is the same, so just use the first one.
+    AnnotatedTypeMirror asSuper = visit(type.getAlternatives().get(0), superType, p);
+    return copyPrimaryAnnos(type, asSuper);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitUnion_Declared(
+      AnnotatedUnionType type, AnnotatedDeclaredType superType, Void p) {
+    return visitUnion_Other(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitUnion_Intersection(
+      AnnotatedUnionType type, AnnotatedIntersectionType superType, Void p) {
+    return visitUnion_Other(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitUnion_Typevar(
+      AnnotatedUnionType type, AnnotatedTypeVariable superType, Void p) {
+    return visitUnion_Other(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitUnion_Union(
+      AnnotatedUnionType type, AnnotatedUnionType superType, Void p) {
+    for (AnnotatedTypeMirror superAltern : superType.getAlternatives()) {
+      copyPrimaryAnnos(type, superAltern);
+    }
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitUnion_Wildcard(
+      AnnotatedUnionType type, AnnotatedWildcardType superType, Void p) {
+    return visitUnion_Other(type, superType, p);
+  }
+  // </editor-fold>
+
+  // <editor-fold defaultstate="collapsed" desc="visitWildCard_Other methods">
+
+  private AnnotatedTypeMirror visitWildcard_NotTypvarNorWildcard(
+      AnnotatedWildcardType type, AnnotatedTypeMirror superType, Void p) {
+    boolean oldIsUninferredTypeArgument = isUninferredTypeArgument;
+    if (type.isUninferredTypeArgument()) {
+      isUninferredTypeArgument = true;
+    }
+    AnnotatedTypeMirror asSuper = visit(type.getExtendsBound(), superType, p);
+    isUninferredTypeArgument = oldIsUninferredTypeArgument;
+    atypeFactory.addDefaultAnnotations(superType);
+
+    return copyPrimaryAnnos(type, asSuper);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitWildcard_Array(
+      AnnotatedWildcardType type, AnnotatedArrayType superType, Void p) {
+    return visitWildcard_NotTypvarNorWildcard(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitWildcard_Declared(
+      AnnotatedWildcardType type, AnnotatedDeclaredType superType, Void p) {
+    return visitWildcard_NotTypvarNorWildcard(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitWildcard_Intersection(
+      AnnotatedWildcardType type, AnnotatedIntersectionType superType, Void p) {
+    return visitWildcard_NotTypvarNorWildcard(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitWildcard_Primitive(
+      AnnotatedWildcardType type, AnnotatedPrimitiveType superType, Void p) {
+    return visitWildcard_NotTypvarNorWildcard(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitWildcard_Typevar(
+      AnnotatedWildcardType type, AnnotatedTypeVariable superType, Void p) {
+    boolean oldIsUninferredTypeArgument = isUninferredTypeArgument;
+    if (type.isUninferredTypeArgument()) {
+      isUninferredTypeArgument = true;
+    }
+    AnnotatedTypeMirror upperBound = visit(type.getExtendsBound(), superType.getUpperBound(), p);
+    superType.setUpperBound(upperBound);
+
+    AnnotatedTypeMirror lowerBound;
+    if (type.getSuperBound().getKind() == TypeKind.NULL
+        && superType.getLowerBound().getKind() == TypeKind.NULL) {
+      lowerBound = copyPrimaryAnnos(type.getSuperBound(), superType.getLowerBound());
+    } else if (type.getSuperBound().getKind() == TypeKind.NULL) {
+      lowerBound = visit(type, superType.getLowerBound(), p);
+    } else {
+      lowerBound = asSuperTypevarLowerBound(type.getSuperBound(), superType, p);
+    }
+    superType.setLowerBound(lowerBound);
+    isUninferredTypeArgument = oldIsUninferredTypeArgument;
+    atypeFactory.addDefaultAnnotations(superType);
+
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitWildcard_Union(
+      AnnotatedWildcardType type, AnnotatedUnionType superType, Void p) {
+    return visitWildcard_NotTypvarNorWildcard(type, superType, p);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitWildcard_Wildcard(
+      AnnotatedWildcardType type, AnnotatedWildcardType superType, Void p) {
+    boolean oldIsUninferredTypeArgument = isUninferredTypeArgument;
+    if (type.isUninferredTypeArgument()) {
+      isUninferredTypeArgument = true;
+      superType.setUninferredTypeArgument();
+    }
+    if (types.isSubtype(
+        type.getExtendsBound().getUnderlyingType(),
+        superType.getExtendsBound().getUnderlyingType())) {
+      AnnotatedTypeMirror upperBound =
+          visit(type.getExtendsBound(), superType.getExtendsBound(), p);
+      superType.setExtendsBound(upperBound);
+    } else {
+      // The upper bound of a wildcard can be a super type of upper bound of the type
+      // parameter for which it is an argument.
+      // See org.checkerframework.framework.type.AnnotatedTypeFactory.widenToUpperBound for an
+      // example.  In these cases, the upper bound of type might be a super type of the
+      // upper bound of superType.
+
+      // The underlying type of the annotated type mirror returned by asSuper must be the
+      // same as the passed type, so just copy the primary annotations.
+      copyPrimaryAnnos(type.getExtendsBound(), superType.getExtendsBound());
+
+      // Add defaults in case any locations are missing annotations.
+      atypeFactory.addDefaultAnnotations(superType.getExtendsBound());
+    }
+
+    AnnotatedTypeMirror lowerBound;
+    if (type.getSuperBound().getKind() == TypeKind.NULL
+        && superType.getSuperBound().getKind() == TypeKind.NULL) {
+      lowerBound = copyPrimaryAnnos(type.getSuperBound(), superType.getSuperBound());
+    } else if (type.getSuperBound().getKind() == TypeKind.NULL) {
+      lowerBound = visit(type, superType.getSuperBound(), p);
+    } else {
+      lowerBound = asSuperWildcardLowerBound(type.getSuperBound(), superType, p);
+    }
+    superType.setSuperBound(lowerBound);
+    isUninferredTypeArgument = oldIsUninferredTypeArgument;
+    atypeFactory.addDefaultAnnotations(superType);
+
+    return copyPrimaryAnnos(type, superType);
+  }
+
+  /**
+   * Returns true if the atypeFactory for this is the given value.
+   *
+   * @param atypeFactory a factory to compare to that of this
+   * @return true if the atypeFactory for this is the given value
+   */
+  public boolean sameAnnotatedTypeFactory(@FindDistinct AnnotatedTypeFactory atypeFactory) {
+    return this.atypeFactory == atypeFactory;
+  }
+  // </editor-fold>
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java b/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java
new file mode 100644
index 0000000..935db22
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java
@@ -0,0 +1,1379 @@
+package org.checkerframework.framework.type;
+
+import com.sun.tools.javac.code.Symtab;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TypeAnnotationUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * BoundsInitializer creates AnnotatedTypeMirrors (without annotations) for the bounds of type
+ * variables and wildcards. Its static helper methods are called from AnnotatedTypeMirror. When an
+ * initializer method is called for a particular bound, the entirety of that bound, including
+ * circular references, will be created.
+ */
+public class BoundsInitializer {
+  // ============================================================================================
+  // Static helper methods called from AnnotatedTypeMirror to initialize bounds of wildcards or
+  // type variables
+  // ============================================================================================
+
+  /**
+   * Initializes the type arguments of {@code declaredType}. The upper bound of unbound wildcards is
+   * set to the upper bound of the type parameter for which it is an argument. If {@code
+   * declaredType} is raw, then the type arguments are uninferred wildcards.
+   *
+   * @param declaredType type whose arguments are initialized
+   */
+  public static void initializeTypeArgs(AnnotatedDeclaredType declaredType) {
+    final DeclaredType underlyingType = (DeclaredType) declaredType.underlyingType;
+    if (underlyingType.getTypeArguments().isEmpty() && !declaredType.wasRaw()) {
+      // No type arguments to infer.
+      return;
+    }
+
+    final TypeElement typeElement =
+        (TypeElement) declaredType.atypeFactory.types.asElement(underlyingType);
+    int numTypeParameters = typeElement.getTypeParameters().size();
+    final List<AnnotatedTypeMirror> typeArgs = new ArrayList<>(numTypeParameters);
+
+    // Create AnnotatedTypeMirror for each type argument and store them in the typeArgsMap.
+    // Take un-annotated type variables as the key for this map.
+    Map<TypeVariable, AnnotatedTypeMirror> typeArgMap = new HashMap<>(numTypeParameters);
+    for (int i = 0; i < numTypeParameters; i++) {
+      TypeMirror javaTypeArg;
+      if (declaredType.wasRaw()) {
+        TypeVariable typeVariable = (TypeVariable) typeElement.getTypeParameters().get(i).asType();
+        javaTypeArg = getUpperBoundAsWildcard(typeVariable, declaredType.atypeFactory);
+      } else {
+        javaTypeArg = declaredType.getUnderlyingType().getTypeArguments().get(i);
+      }
+
+      final AnnotatedTypeMirror typeArg =
+          AnnotatedTypeMirror.createType(javaTypeArg, declaredType.atypeFactory, false);
+      if (typeArg.getKind() == TypeKind.WILDCARD) {
+        AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) typeArg;
+        wildcardType.setTypeVariable(typeElement.getTypeParameters().get(i));
+        if (declaredType.wasRaw()) {
+          wildcardType.setUninferredTypeArgument();
+        }
+      }
+      typeArgs.add(typeArg);
+
+      // Add mapping from type parameter to the annotated type argument.
+      TypeVariable key =
+          (TypeVariable)
+              TypeAnnotationUtils.unannotatedType(typeElement.getTypeParameters().get(i).asType());
+      typeArgMap.put(key, typeArg);
+
+      if (javaTypeArg.getKind() == TypeKind.TYPEVAR) {
+        // Add mapping from Java type argument to the annotated type argument.
+        key = (TypeVariable) TypeAnnotationUtils.unannotatedType(javaTypeArg);
+        typeArgMap.put(key, typeArg);
+      }
+    }
+
+    // Initialize type argument bounds using the typeArgsMap.
+    for (AnnotatedTypeMirror typeArg : typeArgs) {
+      switch (typeArg.getKind()) {
+        case WILDCARD:
+          AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) typeArg;
+          initializeExtendsBound(wildcardType, typeArgMap);
+          initializeSuperBound(wildcardType, typeArgMap);
+          break;
+        case TYPEVAR:
+          initializeBounds((AnnotatedTypeVariable) typeArg, typeArgMap);
+          break;
+        default:
+          // do nothing
+      }
+    }
+    declaredType.typeArgs = Collections.unmodifiableList(typeArgs);
+  }
+
+  /**
+   * Returns a wildcard whose upper bound is the same as {@code typeVariable}. If the upper bound is
+   * an intersection, then this method returns an unbound wildcard.
+   */
+  private static WildcardType getUpperBoundAsWildcard(
+      TypeVariable typeVariable, AnnotatedTypeFactory factory) {
+    TypeMirror upperBound = typeVariable.getUpperBound();
+    switch (upperBound.getKind()) {
+      case ARRAY:
+      case DECLARED:
+      case TYPEVAR:
+        return factory.types.getWildcardType(upperBound, null);
+      case INTERSECTION:
+        // Can't create a wildcard with an intersection as the upper bound, so use
+        // an unbound wildcard instead.  The extends bound of the
+        // AnnotatedWildcardType will be initialized properly by this class.
+        return factory.types.getWildcardType(null, null);
+      default:
+        throw new BugInCF(
+            "Unexpected upper bound kind: %s type: %s", upperBound.getKind(), upperBound);
+    }
+  }
+
+  /**
+   * Create the entire lower bound and upper bound, with no missing information, for typeVar. If a
+   * typeVar is recursive the appropriate cycles will be introduced in the type
+   *
+   * @param typeVar the type variable whose lower bound is being initialized
+   */
+  public static void initializeBounds(final AnnotatedTypeVariable typeVar) {
+    initializeBounds(typeVar, null);
+  }
+
+  /**
+   * Create the entire lower bound and upper bound, with no missing information, for typeVar. If a
+   * typeVar is recursive the appropriate cycles will be introduced in the type
+   *
+   * @param typeVar the type variable whose lower bound is being initialized
+   * @param map a mapping of type parameters to type arguments. May be null.
+   */
+  private static void initializeBounds(
+      final AnnotatedTypeVariable typeVar, Map<TypeVariable, AnnotatedTypeMirror> map) {
+    final Set<AnnotationMirror> annos = saveAnnotations(typeVar);
+
+    InitializerVisitor visitor = new InitializerVisitor(new TypeVariableStructure(typeVar), map);
+    visitor.initializeLowerBound(typeVar);
+    visitor.resolveTypeVarReferences(typeVar);
+
+    InitializerVisitor visitor2 = new InitializerVisitor(new TypeVariableStructure(typeVar), map);
+    visitor2.initializeUpperBound(typeVar);
+    visitor2.resolveTypeVarReferences(typeVar);
+
+    restoreAnnotations(typeVar, annos);
+  }
+
+  /**
+   * If we are initializing a type variable with a primary annotation than we should first
+   * initialize it as if it were a declaration (i.e. as if it had no primary annotations) and then
+   * apply the primary annotations. We do this so that when we make copies of the original type to
+   * represent recursive references the recursive references don't have the primary annotation.
+   *
+   * <pre>{@code
+   * e.g.   given the declaration {@code <E extends List<E>>}
+   *        if we do not do this, the NonNull on the use @NonNull E
+   *        would be copied to the primary annotation on E in the bound {@code List<E>}
+   *        i.e. the use would be {@code <@NonNull E extends @NonNull List<@NonNull E>>}
+   *             rather than      {@code <@NonNull E extends @NonNull List<E>>}
+   * }</pre>
+   */
+  private static Set<AnnotationMirror> saveAnnotations(final AnnotatedTypeMirror type) {
+    if (!type.getAnnotationsField().isEmpty()) {
+      final Set<AnnotationMirror> annos = new HashSet<>(type.getAnnotations());
+      type.clearAnnotations();
+      return annos;
+    }
+
+    return null;
+  }
+
+  private static void restoreAnnotations(
+      final AnnotatedTypeMirror type, final Set<AnnotationMirror> annos) {
+    if (annos != null) {
+      type.addAnnotations(annos);
+    }
+  }
+
+  /**
+   * Create the entire super bound, with no missing information, for wildcard. If a wildcard is
+   * recursive the appropriate cycles will be introduced in the type
+   *
+   * @param wildcard the wildcard whose lower bound is being initialized
+   */
+  public static void initializeSuperBound(final AnnotatedWildcardType wildcard) {
+    initializeSuperBound(wildcard, null);
+  }
+
+  /**
+   * Create the entire super bound, with no missing information, for wildcard. If a wildcard is
+   * recursive the appropriate cycles will be introduced in the type
+   *
+   * @param wildcard the wildcard whose lower bound is being initialized
+   * @param map a mapping of type parameters to type arguments. May be null.
+   */
+  private static void initializeSuperBound(
+      final AnnotatedWildcardType wildcard, Map<TypeVariable, AnnotatedTypeMirror> map) {
+    final Set<AnnotationMirror> annos = saveAnnotations(wildcard);
+
+    InitializerVisitor visitor = new InitializerVisitor(new RecursiveTypeStructure(), map);
+    visitor.initializeSuperBound(wildcard);
+    visitor.resolveTypeVarReferences(wildcard);
+
+    restoreAnnotations(wildcard, annos);
+  }
+
+  /**
+   * Create the entire extends bound, with no missing information, for wildcard. If a wildcard is
+   * recursive the appropriate cycles will be introduced in the type
+   *
+   * @param wildcard the wildcard whose extends bound is being initialized
+   */
+  public static void initializeExtendsBound(final AnnotatedWildcardType wildcard) {
+    initializeExtendsBound(wildcard, null);
+  }
+
+  /**
+   * Create the entire extends bound, with no missing information, for wildcard. If a wildcard is
+   * recursive the appropriate cycles will be introduced in the type
+   *
+   * @param wildcard the wildcard whose extends bound is being initialized
+   * @param map a mapping of type parameters to type arguments. May be null.
+   */
+  private static void initializeExtendsBound(
+      final AnnotatedWildcardType wildcard, Map<TypeVariable, AnnotatedTypeMirror> map) {
+    final Set<AnnotationMirror> annos = saveAnnotations(wildcard);
+    InitializerVisitor visitor = new InitializerVisitor(new RecursiveTypeStructure(), map);
+    visitor.initializeExtendsBound(wildcard);
+    visitor.resolveTypeVarReferences(wildcard);
+    restoreAnnotations(wildcard, annos);
+  }
+
+  // ============================================================================================
+  // Classes and methods used to make the above static helper methods work
+  // ============================================================================================
+
+  /**
+   * Creates the AnnotatedTypeMirrors (without annotations) for the bounds of all type variables and
+   * wildcards in a given type. If the type is recursive, {@code T extends Comparable<T>}, then all
+   * references to the same type variable are references to the same AnnotatedTypeMirror.
+   */
+  private static class InitializerVisitor implements AnnotatedTypeVisitor<Void, Void> {
+    /**
+     * The {@link RecursiveTypeStructure} corresponding to the first wildcard or type variable bound
+     * initialization that kicked this visitation off.
+     */
+    private final RecursiveTypeStructure topLevelStructure;
+
+    /**
+     * The {@link RecursiveTypeStructure} corresponding to the wildcard or type variable that is
+     * currently being visited.
+     */
+    private RecursiveTypeStructure currentStructure;
+
+    /** A mapping from TypeVariable to its {@link TypeVariableStructure}. */
+    private final Map<TypeVariable, TypeVariableStructure> typeVarToStructure = new HashMap<>();
+
+    /**
+     * A mapping from WildcardType to its AnnotatedWildcardType. The first time this visitor
+     * encounters a wildcard it creates an annotated type and adds it to this map. The next time the
+     * wilcard is encounter, the annotated type in this map is returned.
+     */
+    private final Map<WildcardType, AnnotatedWildcardType> wildcards = new HashMap<>();
+
+    /**
+     * A mapping from IntersectionType to its AnnotatedIntersectionType. The first time this visitor
+     * encounters an intersection it creates an annotated type and adds it to this map. The next
+     * time the intersection is encounter, the annotated type in this map is returned.
+     */
+    private final Map<IntersectionType, AnnotatedIntersectionType> intersections = new HashMap<>();
+
+    /**
+     * Mapping from TypeVariable to AnnotatedTypeMirror. The annotated type mirror should be used
+     * for any use of the type variable rather than creating and initializing a new annotated type.
+     * This is used for type arguments that have already been initialized outside of this visitor.
+     */
+    private final Map<TypeVariable, AnnotatedTypeMirror> typevars;
+
+    /**
+     * Creates an InitializerVisitor.
+     *
+     * @param recursiveTypeStructure structure for the type being initialized
+     * @param typevars a mapping from type variable to annotated types that have already been
+     *     initialized
+     */
+    public InitializerVisitor(
+        RecursiveTypeStructure recursiveTypeStructure,
+        Map<TypeVariable, AnnotatedTypeMirror> typevars) {
+      this.topLevelStructure = recursiveTypeStructure;
+      this.currentStructure = recursiveTypeStructure;
+      if (typevars != null) {
+        this.typevars = typevars;
+      } else {
+        this.typevars = Collections.emptyMap();
+      }
+      if (recursiveTypeStructure instanceof TypeVariableStructure) {
+        TypeVariableStructure typeVarStruct = (TypeVariableStructure) recursiveTypeStructure;
+        typeVarToStructure.put(typeVarStruct.typeVar, typeVarStruct);
+      }
+    }
+
+    // ----------------------------------------------------------------------------------------
+    // Visit methods that keep track of the path traversed through type variable bounds, and the
+    // wildcards/intersections that have been encountered.
+    // ----------------------------------------------------------------------------------------
+
+    @Override
+    public Void visit(AnnotatedTypeMirror type) {
+      type.accept(this, null);
+      return null;
+    }
+
+    @Override
+    public Void visit(AnnotatedTypeMirror type, Void aVoid) {
+      visit(type);
+      return null;
+    }
+
+    @Override
+    public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) {
+      initializeTypeArgs(type);
+      if (type.enclosingType != null) {
+        TypePathNode node = currentStructure.addPathNode(new EnclosingTypeNode());
+        visit(type.enclosingType);
+        currentStructure.removePathNode(node);
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) {
+
+      if (intersections.containsKey(type.getUnderlyingType())) {
+        return null;
+      }
+
+      intersections.put(type.getUnderlyingType(), type);
+
+      List<AnnotatedTypeMirror> bounds = type.getBounds();
+      for (int i = 0; i < bounds.size(); i++) {
+        AnnotatedTypeMirror supertype = bounds.get(i);
+        TypePathNode node = currentStructure.addPathNode(new IntersectionBoundNode(i));
+        visit(supertype);
+        currentStructure.removePathNode(node);
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitUnion(AnnotatedUnionType type, Void aVoid) {
+
+      List<AnnotatedDeclaredType> alts = type.getAlternatives();
+      for (int i = 0; i < alts.size(); i++) {
+        AnnotatedDeclaredType alt = alts.get(i);
+        TypePathNode node = currentStructure.addPathNode(new AlternativeTypeNode(i));
+        visit(alt);
+        currentStructure.removePathNode(node);
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitArray(AnnotatedArrayType type, Void aVoid) {
+      if (!TypesUtils.isPrimitive(type.getComponentType().getUnderlyingType())) {
+        // Only recur on component type if it's not a primitive.
+        // Array component types are the only place a primitive is allowed in bounds
+        TypePathNode componentNode = currentStructure.addPathNode(new ArrayComponentNode());
+        type.setComponentType(getOrVisit(type.getComponentType()));
+        currentStructure.removePathNode(componentNode);
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) {
+      this.currentStructure.addTypeVar(type.getUnderlyingType());
+      if (typeVarToStructure.containsKey(type.getUnderlyingType())) {
+        return null;
+      }
+      TypeVariableStructure typeVarStruct = new TypeVariableStructure(type);
+      typeVarToStructure.put(type.getUnderlyingType(), typeVarStruct);
+      RecursiveTypeStructure parentStructure = this.currentStructure;
+
+      this.currentStructure = typeVarStruct;
+      initializeUpperBound(type);
+      initializeLowerBound(type);
+      this.currentStructure = parentStructure;
+
+      return null;
+    }
+
+    @Override
+    public Void visitNull(AnnotatedNullType type, Void aVoid) {
+      return null;
+    }
+
+    @Override
+    public Void visitWildcard(AnnotatedWildcardType wildcard, Void aVoid) {
+      if (wildcard.getSuperBoundField() == null) {
+        initializeSuperBound(wildcard);
+
+      } else {
+        throw new BugInCF(
+            "Wildcard super field should not be initialized:%n"
+                + "wildcard=%s%n"
+                + "currentStructure=%s%n",
+            wildcard, currentStructure);
+      }
+
+      if (wildcard.getExtendsBoundField() == null) {
+        initializeExtendsBound(wildcard);
+      } else {
+        throw new BugInCF(
+            "Wildcard extends field should not be initialized:%n"
+                + "wildcard=%s%n"
+                + "currentStructure=%s%n",
+            wildcard, currentStructure);
+      }
+
+      return null;
+    }
+
+    @Override
+    public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) {
+      throw new BugInCF("Unexpected AnnotatedPrimitiveType " + type);
+    }
+
+    @Override
+    public Void visitNoType(AnnotatedNoType type, Void aVoid) {
+      throw new BugInCF("Unexpected AnnotatedNoType " + type);
+    }
+
+    @Override
+    public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) {
+      throw new BugInCF("Unexpected AnnotatedExecutableType " + type);
+    }
+
+    /**
+     * If the underlying type of {@code type} has been visited before, return the previous
+     * AnnotatedTypeMirror. Otherwise, visit {@code type} and return it.
+     *
+     * @param type type to visit
+     * @return {@code type} or an AnnotatedTypeMirror with the same underlying type that was
+     *     previously visited.
+     */
+    public AnnotatedTypeMirror getOrVisit(AnnotatedTypeMirror type) {
+      switch (type.getKind()) {
+        case WILDCARD:
+          AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type;
+          if (wildcards.containsKey(wildcard.getUnderlyingType())) {
+            return wildcards.get(wildcard.getUnderlyingType());
+          }
+          break;
+        case INTERSECTION:
+          if (intersections.containsKey(type.getUnderlyingType())) {
+            return intersections.get(type.getUnderlyingType());
+          }
+          break;
+        case TYPEVAR:
+          TypeVariable key =
+              (TypeVariable) TypeAnnotationUtils.unannotatedType(type.getUnderlyingType());
+          if (typevars.containsKey(key)) {
+            return typevars.get(key);
+          }
+          break;
+        default:
+          // do nothing
+      }
+      visit(type);
+      return type;
+    }
+
+    // ----------------------------------------------------------------------------------------
+    //
+
+    /**
+     * Initialize {@code typeVar}'s upper bound.
+     *
+     * @param typeVar type variable whose upper bound is initialized
+     */
+    public void initializeUpperBound(AnnotatedTypeVariable typeVar) {
+      AnnotatedTypeMirror upperBound = createAndSetUpperBound(typeVar);
+
+      TypePathNode pathNode = new UpperBoundNode();
+      currentStructure.addPathNode(pathNode);
+      visit(upperBound);
+      currentStructure.removePathNode(pathNode);
+    }
+
+    /**
+     * Initialize {@code typeVar}'s lower bound.
+     *
+     * @param typeVar type variable whose lower bound is initialized
+     */
+    public void initializeLowerBound(AnnotatedTypeVariable typeVar) {
+      AnnotatedTypeMirror lowerBound = createAndSetLowerBound(typeVar);
+
+      TypePathNode pathNode = new LowerBoundNode();
+      currentStructure.addPathNode(pathNode);
+      visit(lowerBound);
+      currentStructure.removePathNode(pathNode);
+    }
+
+    /**
+     * Initialize {@code wildcard}'s super bound.
+     *
+     * @param wildcard wildcard whose super bound is initialized
+     */
+    public void initializeSuperBound(AnnotatedWildcardType wildcard) {
+      AnnotatedTypeFactory typeFactory = wildcard.atypeFactory;
+
+      WildcardType underlyingType = wildcard.getUnderlyingType();
+      TypeMirror underlyingSuperBound = underlyingType.getSuperBound();
+      if (underlyingSuperBound == null) {
+        underlyingSuperBound =
+            TypesUtils.wildLowerBound(underlyingType, wildcard.atypeFactory.processingEnv);
+      }
+
+      AnnotatedTypeMirror superBound =
+          AnnotatedTypeMirror.createType(underlyingSuperBound, typeFactory, false);
+      wildcard.setSuperBound(superBound);
+
+      this.wildcards.put(wildcard.getUnderlyingType(), wildcard);
+
+      TypePathNode superNode = currentStructure.addPathNode(new SuperBoundNode());
+      visit(superBound);
+      currentStructure.removePathNode(superNode);
+    }
+
+    /**
+     * Initialize {@code wildcard}'s extends bound.
+     *
+     * @param wildcard wildcard whose extends bound is initialized
+     */
+    public void initializeExtendsBound(AnnotatedWildcardType wildcard) {
+      AnnotatedTypeFactory typeFactory = wildcard.atypeFactory;
+
+      WildcardType javaWildcardType = wildcard.getUnderlyingType();
+      TypeMirror javaExtendsBound;
+      if (javaWildcardType.getExtendsBound() != null) {
+        // If the wildcard type has an extends bound, use it.
+        javaExtendsBound = javaWildcardType.getExtendsBound();
+      } else if (wildcard.getTypeVariable() != null) {
+        // Otherwise use the upper bound of the type variable associated with this wildcard.
+        javaExtendsBound = wildcard.getTypeVariable().getUpperBound();
+      } else {
+        // Otherwise use the upper bound of the java wildcard.
+        javaExtendsBound =
+            TypesUtils.wildUpperBound(javaWildcardType, wildcard.atypeFactory.processingEnv);
+      }
+
+      if (wildcard.isUninferredTypeArgument()) {
+        rawTypeWildcards.put(wildcard.getTypeVariable(), wildcard.getUnderlyingType());
+      }
+
+      AnnotatedTypeMirror extendsBound =
+          AnnotatedTypeMirror.createType(javaExtendsBound, typeFactory, false);
+      wildcard.setExtendsBound(extendsBound);
+
+      this.wildcards.put(wildcard.getUnderlyingType(), wildcard);
+
+      TypePathNode extendsNode = currentStructure.addPathNode(new ExtendsBoundNode());
+      visit(extendsBound);
+      currentStructure.removePathNode(extendsNode);
+    }
+
+    /**
+     * Initialize {@code declaredType}'s type arguments.
+     *
+     * @param declaredType declared type whose type arguments are initialized
+     */
+    private void initializeTypeArgs(AnnotatedDeclaredType declaredType) {
+      DeclaredType underlyingType = (DeclaredType) declaredType.underlyingType;
+      if (underlyingType.getTypeArguments().isEmpty() && !declaredType.wasRaw()) {
+        return;
+      }
+      TypeElement typeElement =
+          (TypeElement) declaredType.atypeFactory.types.asElement(underlyingType);
+      List<AnnotatedTypeMirror> typeArgs;
+      if (declaredType.typeArgs == null) {
+        int numTypeParameters = typeElement.getTypeParameters().size();
+        typeArgs = new ArrayList<>(numTypeParameters);
+        for (int i = 0; i < numTypeParameters; i++) {
+          TypeMirror javaTypeArg = getJavaType(declaredType, typeElement.getTypeParameters(), i);
+          AnnotatedTypeMirror atmArg =
+              AnnotatedTypeMirror.createType(javaTypeArg, declaredType.atypeFactory, false);
+          typeArgs.add(atmArg);
+          if (atmArg.getKind() == TypeKind.WILDCARD && declaredType.wasRaw()) {
+            ((AnnotatedWildcardType) atmArg).setUninferredTypeArgument();
+          }
+        }
+      } else {
+        typeArgs = declaredType.typeArgs;
+      }
+
+      List<AnnotatedTypeMirror> typeArgReplacements = new ArrayList<>(typeArgs.size());
+      for (int i = 0; i < typeArgs.size(); i++) {
+        AnnotatedTypeMirror typeArg = typeArgs.get(i);
+        TypePathNode node = currentStructure.addPathNode(new TypeArgNode(i));
+        if (typeArg.getKind() == TypeKind.WILDCARD) {
+          ((AnnotatedWildcardType) typeArg).setTypeVariable(typeElement.getTypeParameters().get(i));
+        }
+        typeArgReplacements.add(getOrVisit(typeArg));
+        currentStructure.removePathNode(node);
+      }
+
+      declaredType.setTypeArguments(typeArgReplacements);
+    }
+
+    /**
+     * Store the wildcards created as type arguments to raw types.
+     *
+     * <p>{@code class Foo<T extends Foo> {}} The upper bound of the wildcard in {@code Foo<?>} is
+     * {@code Foo}. The type argument of {@code Foo} is initialized to {@code ? extends Foo}. The
+     * type argument of {@code Foo} in {@code ? extends Foo} needs to be initialized to the same
+     * type argument as the first {@code Foo} so that
+     * BoundsInitializer.InitializerVisitor#getOrVisit will return the cached AnnotatedWildcardType.
+     */
+    private final Map<TypeVariable, WildcardType> rawTypeWildcards = new HashMap<>();
+
+    /**
+     * Returns the underlying java type of the {@code i}-th type argument of {@code type}. If {@code
+     * type} is raw, then a new wildcard is created or returned from {@code rawTypeWildcards}.
+     *
+     * @param type declared type
+     * @param parameters elements of the type parameters
+     * @param i index of the type parameter
+     * @return the underlying java type of the {@code i}-th type argument of {@code type}
+     */
+    private TypeMirror getJavaType(
+        AnnotatedDeclaredType type, List<? extends TypeParameterElement> parameters, int i) {
+      if (type.wasRaw()) {
+        TypeVariable typeVariable = (TypeVariable) parameters.get(i).asType();
+        if (rawTypeWildcards.containsKey(typeVariable)) {
+          return rawTypeWildcards.get(typeVariable);
+        }
+        WildcardType wildcard = getUpperBoundAsWildcard(typeVariable, type.atypeFactory);
+        rawTypeWildcards.put(typeVariable, wildcard);
+        return wildcard;
+      } else {
+        return type.getUnderlyingType().getTypeArguments().get(i);
+      }
+    }
+
+    /**
+     * Replace all type variables in type with the AnnotatedTypeMirrors created when initializing
+     * it.
+     *
+     * @param type all type variables are replaced
+     */
+    public void resolveTypeVarReferences(AnnotatedTypeMirror type) {
+      List<AnnotatedTypeVariable> annotatedTypeVars = new ArrayList<>();
+      if (type.getKind() == TypeKind.TYPEVAR) {
+        annotatedTypeVars.add((AnnotatedTypeVariable) type);
+      }
+
+      // Gather a list of all AnnotatedTypeVariables and all the replacements to perform.
+      for (TypeVariableStructure typeVarStruct : typeVarToStructure.values()) {
+        typeVarStruct.findAllReplacements(typeVarToStructure);
+        annotatedTypeVars.addAll(typeVarStruct.getAnnotatedTypeVars());
+      }
+
+      // Do the replacements.
+      for (AnnotatedTypeVariable atv : annotatedTypeVars) {
+        TypeVariableStructure list = typeVarToStructure.get(atv.getUnderlyingType());
+        list.replaceTypeVariablesInType(atv);
+      }
+
+      if (type.getKind() == TypeKind.WILDCARD) {
+        // Do the "top level" replacements.
+        AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type;
+        topLevelStructure.findAllReplacements(typeVarToStructure);
+        for (AnnotatedTypeVariable typeVar : topLevelStructure.getAnnotatedTypeVars()) {
+          TypeVariableStructure list = typeVarToStructure.get(typeVar.getUnderlyingType());
+          list.replaceTypeVariablesInType(typeVar);
+        }
+        topLevelStructure.replaceTypeVariablesInType(wildcard);
+      }
+    }
+  }
+
+  /**
+   * Creates the upper bound type for {@code typeVar} and sets it.
+   *
+   * @param typeVar type variable
+   * @return the newly created upper bound
+   */
+  private static AnnotatedTypeMirror createAndSetUpperBound(AnnotatedTypeVariable typeVar) {
+    AnnotatedTypeMirror upperBound =
+        AnnotatedTypeMirror.createType(
+            typeVar.getUnderlyingType().getUpperBound(), typeVar.atypeFactory, false);
+    typeVar.setUpperBound(upperBound);
+    return upperBound;
+  }
+
+  /**
+   * Creates the lower bound type for {@code typeVar} and sets it. If the type variable does not
+   * have a lower bound, then a null type is created.
+   *
+   * @param typeVar type variable
+   * @return the newly created lower bound
+   */
+  private static AnnotatedTypeMirror createAndSetLowerBound(AnnotatedTypeVariable typeVar) {
+    TypeMirror lb = typeVar.getUnderlyingType().getLowerBound();
+    if (lb == null) {
+      // Use bottom type to ensure there is a lower bound.
+      Context context =
+          ((JavacProcessingEnvironment) typeVar.atypeFactory.processingEnv).getContext();
+      Symtab syms = Symtab.instance(context);
+      lb = syms.botType;
+    }
+    AnnotatedTypeMirror lowerBound =
+        AnnotatedTypeMirror.createType(lb, typeVar.atypeFactory, false);
+    typeVar.setLowerBound(lowerBound);
+    return lowerBound;
+  }
+
+  /**
+   * Contains all the type variables and the type path to reach them found when scanning a
+   * particular type variable or wildcard. Then uses this information to replace the type variables
+   * with AnnotatedTypeVariables.
+   */
+  private static class RecursiveTypeStructure {
+
+    /** List of TypePath and TypeVariables that were found will traversing this type. */
+    private final List<Pair<TypePath, TypeVariable>> typeVarsInType = new ArrayList<>();
+
+    /** Current path used to mark the locations of TypeVariables. */
+    private final TypePath currentPath = new TypePath();
+
+    /**
+     * Add a type variable found at the current path while visiting the type variable or wildcard
+     * associated with this structure.
+     *
+     * @param typeVariable TypeVariable
+     */
+    public void addTypeVar(TypeVariable typeVariable) {
+      typeVarsInType.add(Pair.of(this.currentPath.copy(), typeVariable));
+    }
+
+    /**
+     * Add a node in the path.
+     *
+     * @param node node to add
+     * @return {@code node}
+     */
+    public TypePathNode addPathNode(TypePathNode node) {
+      currentPath.add(node);
+      return node;
+    }
+
+    /**
+     * Remove the last node in the path if it is {@code node}; otherwise, throw an exception.
+     *
+     * @param node last node in the path
+     */
+    public void removePathNode(@FindDistinct TypePathNode node) {
+      if (currentPath.getLeaf() != node) {
+        throw new BugInCF(
+            "Cannot remove node: %s. It is not the last node. currentPath= %s", node, currentPath);
+      }
+      currentPath.removeLeaf();
+    }
+
+    /**
+     * For all type variables contained within the type variable or wildcard that this structure
+     * represents, this a list of the replacement {@link AnnotatedTypeVariable} for the location
+     * specified by the {@link TypePath}.
+     */
+    private List<Pair<TypePath, AnnotatedTypeVariable>> replacementList;
+
+    /**
+     * Find the AnnotatedTypeVariables that should replace the type variables found in this type.
+     *
+     * @param typeVarToStructure a mapping from TypeVariable to TypeVariableStructure
+     */
+    public void findAllReplacements(Map<TypeVariable, TypeVariableStructure> typeVarToStructure) {
+      this.annotatedTypeVariables = new ArrayList<>(typeVarsInType.size());
+      this.replacementList = new ArrayList<>(typeVarsInType.size());
+      for (Pair<TypePath, TypeVariable> pair : typeVarsInType) {
+        TypeVariableStructure targetStructure = typeVarToStructure.get(pair.second);
+        AnnotatedTypeVariable template = targetStructure.annotatedTypeVar.deepCopy().asUse();
+        annotatedTypeVariables.add(template);
+        replacementList.add(Pair.of(pair.first, template));
+      }
+    }
+
+    /** List of {@link AnnotatedTypeVariable}s found in this type. */
+    private List<AnnotatedTypeVariable> annotatedTypeVariables;
+
+    /**
+     * A list of all AnnotatedTypeVariables found in this type. {@link #findAllReplacements(Map)}
+     * must be called first.
+     *
+     * @return a list of all AnnotatedTypeVariables found in this type
+     */
+    public List<AnnotatedTypeVariable> getAnnotatedTypeVars() {
+      if (annotatedTypeVariables == null) {
+        throw new BugInCF("Call createReplacementList before calling this method.");
+      }
+      return annotatedTypeVariables;
+    }
+
+    /**
+     * Replaces all type variables in {@code type} with their replacements. ({@link
+     * #findAllReplacements(Map)} must be called first so that the replacements can be found.)
+     *
+     * @param type annotated type whose type variables are replaced
+     */
+    public void replaceTypeVariablesInType(AnnotatedTypeMirror type) {
+      if (replacementList == null) {
+        throw new BugInCF("Call createReplacementList before calling this method.");
+      }
+      for (Pair<TypePath, AnnotatedTypeVariable> entry : replacementList) {
+        TypePath path = entry.first;
+        AnnotatedTypeVariable replacement = entry.second;
+        path.replaceTypeVariable(type, replacement);
+      }
+    }
+  }
+
+  /** A {@link RecursiveTypeStructure} for a type variable. */
+  private static class TypeVariableStructure extends RecursiveTypeStructure {
+    /** The type variable whose structure is being described. */
+    public final TypeVariable typeVar;
+
+    /**
+     * The first annotated type variable that was encountered and traversed in order to describe
+     * typeVar. It is expanded during visitation and it is later used as a template for other uses
+     * of typeVar
+     */
+    public final AnnotatedTypeVariable annotatedTypeVar;
+
+    /**
+     * Creates an {@link TypeVariableStructure}
+     *
+     * @param annotatedTypeVar annotated type for the type variable whose structure is being
+     *     described
+     */
+    public TypeVariableStructure(AnnotatedTypeVariable annotatedTypeVar) {
+      this.typeVar = annotatedTypeVar.getUnderlyingType();
+      this.annotatedTypeVar = annotatedTypeVar;
+    }
+  }
+
+  /**
+   * A list of {@link TypePathNode}s. Each node represents a "location" of a composite type. For
+   * example, an {@link UpperBoundNode} represents the upper bound type of a type variable
+   */
+  @SuppressWarnings("serial")
+  private static class TypePath extends ArrayList<TypePathNode> {
+
+    @Override
+    public String toString() {
+      return StringsPlume.join(",", this);
+    }
+
+    /**
+     * Create a copy of this path.
+     *
+     * @return a copy of this path
+     */
+    public TypePath copy() {
+      TypePath copy = new TypePath();
+      for (TypePathNode node : this) {
+        copy.add(node.copy());
+      }
+      return copy;
+    }
+
+    /**
+     * Return the leaf node of this path.
+     *
+     * @return the leaf node or null if the path is empty
+     */
+    public TypePathNode getLeaf() {
+      if (this.isEmpty()) {
+        return null;
+      }
+      return this.get(size() - 1);
+    }
+
+    /** Remove the leaf node if one exists. */
+    public void removeLeaf() {
+      if (this.isEmpty()) {
+        return;
+      }
+      this.remove(size() - 1);
+    }
+
+    /**
+     * In {@code type}, replace the type at the location specified by this path with {@code
+     * replacement}.
+     *
+     * @param type annotated type that is side-effected
+     * @param replacement annotated type to add to {@code type}
+     */
+    public void replaceTypeVariable(AnnotatedTypeMirror type, AnnotatedTypeVariable replacement) {
+      AnnotatedTypeMirror current = type;
+      for (int i = 0; i < size() - 1; i++) {
+        current = get(i).getType(current);
+      }
+      this.getLeaf().replaceType(current, replacement);
+    }
+  }
+
+  /**
+   * A {@link TypePathNode} represents a "location" of a composite type. For example, an {@link
+   * UpperBoundNode} represents the upper bound type of a type variable.
+   */
+  private abstract static class TypePathNode {
+
+    /** The {@link TypeKind} of the parent of this node. */
+    public final TypeKind parentTypeKind;
+
+    /**
+     * Creates a {@link TypePathNode}.
+     *
+     * @param parentTypeKind kind of parent of this node
+     */
+    TypePathNode(TypeKind parentTypeKind) {
+      this.parentTypeKind = parentTypeKind;
+    }
+
+    /**
+     * A copy constructor.
+     *
+     * @param template node to copy
+     */
+    TypePathNode(TypePathNode template) {
+      this.parentTypeKind = template.parentTypeKind;
+    }
+
+    @Override
+    public String toString() {
+      return this.getClass().getSimpleName();
+    }
+
+    /**
+     * Returns the annotated type at the location represented by this node in {@code type}.
+     *
+     * @param type parent type
+     * @return the annotated type at the location represented by this node in {@code type}
+     * @throws BugInCF if {@code type} does not have a type at this location
+     */
+    public final AnnotatedTypeMirror getType(AnnotatedTypeMirror type) {
+      abortIfNotKind(parentTypeKind, null, type);
+      return getTypeInternal(type);
+    }
+
+    /**
+     * Internal implementation of {@link #getType(AnnotatedTypeMirror)}.
+     *
+     * @param parent type that is sideffected by this method
+     * @return the annotated type at the location represented by this node in {@code type}
+     */
+    protected abstract AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent);
+
+    /**
+     * Replaces the type at the location represented by this node in {@code parent} with {@code
+     * replacement}.
+     *
+     * @param parent type that is sideffected by this method
+     * @param replacement the replacement
+     * @throws BugInCF if {@code type} does not have a type at this location
+     */
+    public final void replaceType(AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) {
+      abortIfNotKind(parentTypeKind, replacement, parent);
+      replaceTypeInternal(parent, replacement);
+    }
+
+    /**
+     * Internal implementation of #replaceType.
+     *
+     * @param parent type that is sideffected by this method
+     * @param replacement the replacement
+     */
+    protected abstract void replaceTypeInternal(
+        AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement);
+
+    /**
+     * Returns a copy of the node.
+     *
+     * @return a copy of this node
+     */
+    public abstract TypePathNode copy();
+
+    /**
+     * Throws a {@link BugInCF} if {@code parent} is {@code typeKind}.
+     *
+     * @param typeKind TypeKind
+     * @param replacement for debugging
+     * @param parent possible parent type of this node
+     * @throws BugInCF if {@code parent} is {@code typeKind}
+     */
+    private void abortIfNotKind(
+        TypeKind typeKind, AnnotatedTypeVariable replacement, AnnotatedTypeMirror parent) {
+      if (parent.getKind() == typeKind) {
+        return;
+      }
+
+      throw new BugInCF(
+          "Unexpected parent kind:%nparent= %s%nreplacements= %s%n expected= %s",
+          parent, replacement, typeKind);
+    }
+  }
+
+  /** Represents an enclosing type of a declared type. */
+  private static class EnclosingTypeNode extends TypePathNode {
+
+    /** Create an enclosing node. */
+    EnclosingTypeNode() {
+      super(TypeKind.DECLARED);
+    }
+
+    @Override
+    protected void replaceTypeInternal(
+        AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) {
+      // An enclosing type cannot be a type variable, so do nothing.
+    }
+
+    @Override
+    public AnnotatedDeclaredType getTypeInternal(AnnotatedTypeMirror parent) {
+      return ((AnnotatedDeclaredType) parent).getEnclosingType();
+    }
+
+    @Override
+    public TypePathNode copy() {
+      return new EnclosingTypeNode();
+    }
+  }
+
+  /** Represents an extends bound of a wildcard. */
+  private static class ExtendsBoundNode extends TypePathNode {
+    /** Creates an ExtendsBoundNode. */
+    ExtendsBoundNode() {
+      super(TypeKind.WILDCARD);
+    }
+
+    @Override
+    protected void replaceTypeInternal(
+        AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) {
+      ((AnnotatedWildcardType) parent).setExtendsBound(replacement);
+    }
+
+    @Override
+    protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) {
+      return ((AnnotatedWildcardType) parent).getExtendsBound();
+    }
+
+    @Override
+    public TypePathNode copy() {
+      return new ExtendsBoundNode();
+    }
+  }
+
+  /** Represents a super bound of a wildcard. */
+  private static class SuperBoundNode extends TypePathNode {
+    /** Creates a SuperBoundNode. */
+    SuperBoundNode() {
+      super(TypeKind.WILDCARD);
+    }
+
+    @Override
+    protected void replaceTypeInternal(
+        AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) {
+      ((AnnotatedWildcardType) parent).setSuperBound(replacement);
+    }
+
+    @Override
+    protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) {
+      return ((AnnotatedWildcardType) parent).getSuperBound();
+    }
+
+    @Override
+    public TypePathNode copy() {
+      return new SuperBoundNode();
+    }
+  }
+
+  /** Represents an upper bound of a type variable. */
+  private static class UpperBoundNode extends TypePathNode {
+
+    /** Creates an UpperBoundNode. */
+    UpperBoundNode() {
+      super(TypeKind.TYPEVAR);
+    }
+
+    @Override
+    protected void replaceTypeInternal(
+        AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) {
+      ((AnnotatedTypeVariable) parent).setUpperBound(replacement);
+    }
+
+    @Override
+    protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) {
+      AnnotatedTypeVariable parentAtv = (AnnotatedTypeVariable) parent;
+      if (parentAtv.getUpperBoundField() != null) {
+        return parentAtv.getUpperBoundField();
+      }
+      return createAndSetUpperBound((AnnotatedTypeVariable) parent);
+    }
+
+    @Override
+    public TypePathNode copy() {
+      return new UpperBoundNode();
+    }
+  }
+
+  /** Represents a lower bound of a type variable. */
+  private static class LowerBoundNode extends TypePathNode {
+
+    /** Creates a LowerBoundNode. */
+    LowerBoundNode() {
+      super(TypeKind.TYPEVAR);
+    }
+
+    @Override
+    protected void replaceTypeInternal(
+        AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) {
+      ((AnnotatedTypeVariable) parent).setLowerBound(replacement);
+    }
+
+    @Override
+    protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) {
+
+      AnnotatedTypeVariable parentAtv = (AnnotatedTypeVariable) parent;
+      if (parentAtv.getLowerBoundField() != null) {
+        return parentAtv.getLowerBoundField();
+      }
+      // else // TODO: I think this should never happen at this point, throw exception
+      return createAndSetLowerBound((AnnotatedTypeVariable) parent);
+    }
+
+    @Override
+    public TypePathNode copy() {
+      return new LowerBoundNode();
+    }
+  }
+
+  /** Represents a component type of an array type. */
+  private static class ArrayComponentNode extends TypePathNode {
+
+    /** Create ArrayComponentNode. */
+    ArrayComponentNode() {
+      super(TypeKind.ARRAY);
+    }
+
+    @Override
+    protected void replaceTypeInternal(
+        AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) {
+      ((AnnotatedArrayType) parent).setComponentType(replacement);
+    }
+
+    @Override
+    protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) {
+      return ((AnnotatedArrayType) parent).getComponentType();
+    }
+
+    @Override
+    public TypePathNode copy() {
+      return new ArrayComponentNode();
+    }
+  }
+
+  /** A bound type of an intersection type. */
+  private static class IntersectionBoundNode extends TypePathNode {
+
+    /** The index of the particular bound type of an intersection type this node represents. */
+    public final int boundIndex;
+
+    /**
+     * Creates an IntersectionBoundNode.
+     *
+     * @param boundIndex the index of the particular bound type of an intersection type this node
+     *     represents
+     */
+    IntersectionBoundNode(int boundIndex) {
+      super(TypeKind.INTERSECTION);
+      this.boundIndex = boundIndex;
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param template node to copy
+     */
+    IntersectionBoundNode(IntersectionBoundNode template) {
+      super(template);
+      boundIndex = template.boundIndex;
+    }
+
+    @Override
+    public String toString() {
+      return super.toString() + "( superIndex=" + boundIndex + " )";
+    }
+
+    @Override
+    protected void replaceTypeInternal(
+        AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) {
+      AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) parent;
+      List<AnnotatedTypeMirror> bounds = new ArrayList<>(intersection.bounds);
+      bounds.set(boundIndex, replacement);
+      intersection.setBounds(bounds);
+    }
+
+    @Override
+    protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) {
+      AnnotatedIntersectionType isect = (AnnotatedIntersectionType) parent;
+      if (isect.getBounds().size() <= boundIndex) {
+        throw new BugInCF("Invalid superIndex %d: parent=%s", boundIndex, parent);
+      }
+
+      return isect.getBounds().get(boundIndex);
+    }
+
+    @Override
+    public TypePathNode copy() {
+      return new IntersectionBoundNode(this);
+    }
+  }
+
+  /** Represents an alternative type of a union node. */
+  private static class AlternativeTypeNode extends TypePathNode {
+
+    /** The index of the particular alternative type of the union node that this node represents. */
+    public final int altIndex;
+
+    /**
+     * Creates a AlternativeTypeNode.
+     *
+     * @param altIndex the index of the particular alternative type of the union node that this node
+     *     represents
+     */
+    AlternativeTypeNode(int altIndex) {
+      super(TypeKind.UNION);
+      this.altIndex = altIndex;
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param template node to copy
+     */
+    AlternativeTypeNode(AlternativeTypeNode template) {
+      super(template);
+      altIndex = template.altIndex;
+    }
+
+    @Override
+    public String toString() {
+      return super.toString() + "( altIndex=" + altIndex + " )";
+    }
+
+    @Override
+    protected void replaceTypeInternal(
+        AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) {
+      throw new BugInCF(
+          "Union types cannot be intersection bounds.%nparent=%s%nreplacement=%s",
+          parent, replacement);
+    }
+
+    @Override
+    protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) {
+      AnnotatedUnionType isect = (AnnotatedUnionType) parent;
+      if (parent.directSupertypes().size() <= altIndex) {
+        throw new BugInCF("Invalid altIndex( %s ):%nparent=%s", altIndex, parent);
+      }
+
+      return isect.directSupertypes().get(altIndex);
+    }
+
+    @Override
+    public TypePathNode copy() {
+      return new AlternativeTypeNode(this);
+    }
+  }
+
+  /** Represents a type argument of a declared type. */
+  private static class TypeArgNode extends TypePathNode {
+
+    /** The index of the type argument that this node represents. */
+    public final int argIndex;
+
+    /**
+     * Creates a TypeArgumentNode.
+     *
+     * @param argIndex index of the type argument that this node represents
+     */
+    TypeArgNode(int argIndex) {
+      super(TypeKind.DECLARED);
+      this.argIndex = argIndex;
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param template node to copy
+     */
+    TypeArgNode(TypeArgNode template) {
+      super(template);
+      this.argIndex = template.argIndex;
+    }
+
+    @Override
+    public String toString() {
+      return super.toString() + "( argIndex=" + argIndex + " )";
+    }
+
+    @Override
+    protected void replaceTypeInternal(
+        AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) {
+      AnnotatedDeclaredType parentAdt = (AnnotatedDeclaredType) parent;
+      List<AnnotatedTypeMirror> typeArgs = new ArrayList<>(parentAdt.getTypeArguments());
+      if (argIndex >= typeArgs.size()) {
+        throw new BugInCF(
+            StringsPlume.joinLines(
+                "Invalid type arg index.",
+                "parent=" + parent,
+                "replacement=" + replacement,
+                "argIndex=" + argIndex));
+      }
+      typeArgs.add(argIndex, replacement);
+      typeArgs.remove(argIndex + 1);
+      parentAdt.setTypeArguments(typeArgs);
+    }
+
+    @Override
+    protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) {
+      AnnotatedDeclaredType parentAdt = (AnnotatedDeclaredType) parent;
+
+      List<AnnotatedTypeMirror> typeArgs = parentAdt.getTypeArguments();
+      if (argIndex >= typeArgs.size()) {
+        throw new BugInCF(
+            StringsPlume.joinLines(
+                "Invalid type arg index.", "parent=" + parent, "argIndex=" + argIndex));
+      }
+
+      return typeArgs.get(argIndex);
+    }
+
+    @Override
+    public TypePathNode copy() {
+      return new TypeArgNode(this);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/DeclarationsIntoElements.java b/framework/src/main/java/org/checkerframework/framework/type/DeclarationsIntoElements.java
new file mode 100644
index 0000000..0e766c6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/DeclarationsIntoElements.java
@@ -0,0 +1,68 @@
+package org.checkerframework.framework.type;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.tools.javac.code.Attribute.Compound;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.util.List;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypeAnnotationUtils;
+
+/**
+ * A helper class that puts the declaration annotations from a method declaration back into the
+ * corresponding Elements, so that they get stored in the bytecode by the compiler.
+ *
+ * <p>This is similar to {@code TypesIntoElements} but for declaration annotations.
+ *
+ * <p>This class deals with javac internals and liberally imports such classes.
+ */
+public class DeclarationsIntoElements {
+
+  /**
+   * The entry point.
+   *
+   * @param atypeFactory the type factory
+   * @param tree the ClassTree to process
+   */
+  public static void store(
+      ProcessingEnvironment env, AnnotatedTypeFactory atypeFactory, ClassTree tree) {
+    for (Tree mem : tree.getMembers()) {
+      if (mem.getKind() == Tree.Kind.METHOD) {
+        storeMethod(env, atypeFactory, (MethodTree) mem);
+      }
+    }
+  }
+
+  /**
+   * Add inherited declaration annotations from overridden methods into the corresponding Elements
+   * so they are written into bytecode.
+   *
+   * @param env ProcessingEnvironment
+   * @param atypeFactory the type factory
+   * @param meth the MethodTree to add the annotations
+   */
+  private static void storeMethod(
+      ProcessingEnvironment env, AnnotatedTypeFactory atypeFactory, MethodTree meth) {
+    ExecutableElement element = TreeUtils.elementFromDeclaration(meth);
+    MethodSymbol sym = (MethodSymbol) element;
+    java.util.List<? extends AnnotationMirror> elementAnnos = element.getAnnotationMirrors();
+
+    Set<AnnotationMirror> declAnnotations = atypeFactory.getDeclAnnotations(sym);
+    List<Compound> tcs = List.nil();
+
+    for (AnnotationMirror anno : declAnnotations) {
+      // Only add the annotation if it isn't in the Element already.
+      if (!AnnotationUtils.containsSame(elementAnnos, anno)) {
+        tcs = tcs.append(TypeAnnotationUtils.createCompoundFromAnnotationMirror(anno, env));
+      }
+    }
+
+    sym.appendAttributes(tcs);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java
new file mode 100644
index 0000000..e777ef7
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java
@@ -0,0 +1,423 @@
+package org.checkerframework.framework.type;
+
+import com.sun.tools.javac.code.Type;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor;
+import org.checkerframework.framework.util.AnnotationFormatter;
+import org.checkerframework.framework.util.DefaultAnnotationFormatter;
+import org.checkerframework.javacutil.TypeAnnotationUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * An AnnotatedTypeFormatter used by default by all AnnotatedTypeFactory (and therefore all
+ * annotated types).
+ *
+ * @see org.checkerframework.framework.type.AnnotatedTypeFormatter
+ * @see org.checkerframework.framework.type.AnnotatedTypeMirror#toString
+ */
+public class DefaultAnnotatedTypeFormatter implements AnnotatedTypeFormatter {
+  protected final FormattingVisitor formattingVisitor;
+
+  /**
+   * Constructs a DefaultAnnotatedTypeFormatter that does not print invisible annotations by
+   * default.
+   */
+  public DefaultAnnotatedTypeFormatter() {
+    this(new DefaultAnnotationFormatter(), true, false);
+  }
+
+  /**
+   * @param printVerboseGenerics for type parameters, their uses, and wildcards, print more
+   *     information
+   * @param defaultPrintInvisibleAnnos whether or not this AnnotatedTypeFormatter should print
+   *     invisible annotations
+   */
+  public DefaultAnnotatedTypeFormatter(
+      boolean printVerboseGenerics, boolean defaultPrintInvisibleAnnos) {
+    this(new DefaultAnnotationFormatter(), printVerboseGenerics, defaultPrintInvisibleAnnos);
+  }
+
+  /**
+   * @param formatter an object that converts annotation mirrors to strings
+   * @param printVerboseGenerics for type parameters, their uses, and wildcards, print more
+   *     information
+   * @param defaultPrintInvisibleAnnos whether or not this AnnotatedTypeFormatter should print
+   *     invisible annotations
+   */
+  public DefaultAnnotatedTypeFormatter(
+      AnnotationFormatter formatter,
+      boolean printVerboseGenerics,
+      boolean defaultPrintInvisibleAnnos) {
+    this(new FormattingVisitor(formatter, printVerboseGenerics, defaultPrintInvisibleAnnos));
+  }
+
+  /**
+   * Used by subclasses and other constructors to specify the underlying implementation of this
+   * DefaultAnnotatedTypeFormatter.
+   */
+  protected DefaultAnnotatedTypeFormatter(FormattingVisitor visitor) {
+    this.formattingVisitor = visitor;
+  }
+
+  @Override
+  public String format(final AnnotatedTypeMirror type) {
+    formattingVisitor.resetPrintVerboseSettings();
+    return formattingVisitor.visit(type);
+  }
+
+  @Override
+  public String format(final AnnotatedTypeMirror type, final boolean printVerbose) {
+    formattingVisitor.setVerboseSettings(printVerbose);
+    return formattingVisitor.visit(type);
+  }
+
+  /** A scanning visitor that prints the entire AnnotatedTypeMirror passed to visit. */
+  protected static class FormattingVisitor
+      implements AnnotatedTypeVisitor<String, Set<AnnotatedTypeMirror>> {
+
+    /** The object responsible for converting annotations to strings. */
+    protected final AnnotationFormatter annoFormatter;
+
+    /**
+     * Represents whether or not invisible annotations should be printed if the client of this class
+     * does not use the printInvisibleAnnos parameter.
+     */
+    protected final boolean defaultInvisiblesSetting;
+
+    /**
+     * For a given call to format, this setting specifies whether or not to printInvisibles. If a
+     * user did not specify a printInvisible parameter in the call to format then this value will
+     * equal DefaultAnnotatedTypeFormatter.defaultInvisibleSettings for this object
+     */
+    protected boolean currentPrintInvisibleSetting;
+
+    /** Default value of currentPrintVerboseGenerics. */
+    protected final boolean defaultPrintVerboseGenerics;
+
+    /**
+     * Prints type variables in a less ambiguous manner using [] to delimit them. Always prints both
+     * bounds even if they lower bound is an AnnotatedNull type.
+     */
+    protected boolean currentPrintVerboseGenerics;
+
+    public FormattingVisitor(
+        AnnotationFormatter annoFormatter,
+        boolean printVerboseGenerics,
+        boolean defaultInvisiblesSetting) {
+      this.annoFormatter = annoFormatter;
+      this.defaultPrintVerboseGenerics = printVerboseGenerics;
+      this.currentPrintVerboseGenerics = printVerboseGenerics;
+      this.defaultInvisiblesSetting = defaultInvisiblesSetting;
+      this.currentPrintInvisibleSetting = false;
+    }
+
+    /** Set the current verbose settings to use while printing. */
+    protected void setVerboseSettings(boolean printVerbose) {
+      this.currentPrintInvisibleSetting = printVerbose;
+      this.currentPrintVerboseGenerics = printVerbose;
+    }
+
+    /** Set verbose settings to the default. */
+    protected void resetPrintVerboseSettings() {
+      this.currentPrintInvisibleSetting = defaultInvisiblesSetting;
+      this.currentPrintVerboseGenerics = defaultPrintVerboseGenerics;
+    }
+
+    /**
+     * Print, to sb, {@code keyWord} followed by {@code field}. NULL types are substituted with
+     * their annotations followed by " Void"
+     */
+    @SideEffectFree
+    protected void printBound(
+        final String keyWord,
+        final AnnotatedTypeMirror field,
+        final Set<AnnotatedTypeMirror> visiting,
+        final StringBuilder sb) {
+      if (!currentPrintVerboseGenerics && (field == null || field.getKind() == TypeKind.NULL)) {
+        return;
+      }
+
+      sb.append(" ");
+      sb.append(keyWord);
+      sb.append(" ");
+
+      if (field == null) {
+        sb.append("<null>");
+      } else if (field.getKind() != TypeKind.NULL) {
+        sb.append(visit(field, visiting));
+      } else {
+        sb.append(
+            annoFormatter.formatAnnotationString(
+                field.getAnnotations(), currentPrintInvisibleSetting));
+        sb.append("Void");
+      }
+    }
+
+    @SideEffectFree
+    @Override
+    public String visit(AnnotatedTypeMirror type) {
+      return type.accept(this, Collections.newSetFromMap(new IdentityHashMap<>()));
+    }
+
+    @Override
+    public String visit(AnnotatedTypeMirror type, Set<AnnotatedTypeMirror> annotatedTypeVariables) {
+      return type.accept(this, annotatedTypeVariables);
+    }
+
+    @Override
+    public String visitDeclared(AnnotatedDeclaredType type, Set<AnnotatedTypeMirror> visiting) {
+      StringBuilder sb = new StringBuilder();
+      if (type.isDeclaration() && currentPrintInvisibleSetting) {
+        sb.append("/*DECL*/ ");
+      }
+      if (type.getEnclosingType() != null) {
+        sb.append(this.visit(type.getEnclosingType(), visiting));
+        sb.append('.');
+      }
+      final Element typeElt = type.getUnderlyingType().asElement();
+      String smpl = typeElt.getSimpleName().toString();
+      if (smpl.isEmpty()) {
+        // For anonymous classes smpl is empty - toString
+        // of the element is more useful.
+        smpl = typeElt.toString();
+      }
+      sb.append(
+          annoFormatter.formatAnnotationString(
+              type.getAnnotations(), currentPrintInvisibleSetting));
+      sb.append(smpl);
+
+      if (type.typeArgs != null) {
+        // getTypeArguments sets the field if it does not already exist.
+        final List<AnnotatedTypeMirror> typeArgs = type.typeArgs;
+        if (!typeArgs.isEmpty()) {
+          StringJoiner sj = new StringJoiner(", ", "<", ">");
+          for (AnnotatedTypeMirror typeArg : typeArgs) {
+            sj.add(visit(typeArg, visiting));
+          }
+          sb.append(sj);
+        }
+      }
+      return sb.toString();
+    }
+
+    @Override
+    public String visitIntersection(
+        AnnotatedIntersectionType type, Set<AnnotatedTypeMirror> visiting) {
+      StringBuilder sb = new StringBuilder();
+
+      boolean isFirst = true;
+      for (AnnotatedTypeMirror bound : type.getBounds()) {
+        if (!isFirst) {
+          sb.append(" & ");
+        }
+        sb.append(visit(bound, visiting));
+        isFirst = false;
+      }
+      return sb.toString();
+    }
+
+    @Override
+    public String visitUnion(AnnotatedUnionType type, Set<AnnotatedTypeMirror> visiting) {
+      StringBuilder sb = new StringBuilder();
+
+      boolean isFirst = true;
+      for (AnnotatedDeclaredType adt : type.getAlternatives()) {
+        if (!isFirst) {
+          sb.append(" | ");
+        }
+        sb.append(visit(adt, visiting));
+        isFirst = false;
+      }
+      return sb.toString();
+    }
+
+    @Override
+    public String visitExecutable(AnnotatedExecutableType type, Set<AnnotatedTypeMirror> visiting) {
+      StringBuilder sb = new StringBuilder();
+      if (!type.getTypeVariables().isEmpty()) {
+        StringJoiner sj = new StringJoiner(", ", "<", "> ");
+        for (AnnotatedTypeVariable atv : type.getTypeVariables()) {
+          sj.add(visit(atv, visiting));
+        }
+        sb.append(sj.toString());
+      }
+      if (type.getReturnType() != null) {
+        sb.append(visit(type.getReturnType(), visiting));
+      } else {
+        sb.append("<UNKNOWNRETURN>");
+      }
+      sb.append(' ');
+      if (type.getElement() != null) {
+        sb.append(type.getElement().getSimpleName());
+      } else {
+        sb.append("METHOD");
+      }
+      sb.append('(');
+      AnnotatedDeclaredType rcv = type.getReceiverType();
+      if (rcv != null) {
+        sb.append(visit(rcv, visiting));
+        sb.append(" this");
+      }
+      if (!type.getParameterTypes().isEmpty()) {
+        int p = 0;
+        for (AnnotatedTypeMirror atm : type.getParameterTypes()) {
+          if (rcv != null || p > 0) {
+            sb.append(", ");
+          }
+          sb.append(visit(atm, visiting));
+          // Output some parameter names to make it look more like a method.
+          // TODO: go to the element and look up real parameter names, maybe.
+          sb.append(" p");
+          sb.append(p++);
+        }
+      }
+      sb.append(')');
+      if (!type.getThrownTypes().isEmpty()) {
+        sb.append(" throws ");
+        for (AnnotatedTypeMirror atm : type.getThrownTypes()) {
+          sb.append(visit(atm, visiting));
+        }
+      }
+      return sb.toString();
+    }
+
+    @Override
+    public String visitArray(AnnotatedArrayType type, Set<AnnotatedTypeMirror> visiting) {
+      StringBuilder sb = new StringBuilder();
+
+      AnnotatedArrayType array = type;
+      AnnotatedTypeMirror component;
+      while (true) {
+        component = array.getComponentType();
+        if (!array.getAnnotations().isEmpty()) {
+          sb.append(' ');
+          sb.append(
+              annoFormatter.formatAnnotationString(
+                  array.getAnnotations(), currentPrintInvisibleSetting));
+        }
+        sb.append("[]");
+        if (!(component instanceof AnnotatedArrayType)) {
+          sb.insert(0, visit(component, visiting));
+          break;
+        }
+        array = (AnnotatedArrayType) component;
+      }
+      return sb.toString();
+    }
+
+    @Override
+    public String visitTypeVariable(AnnotatedTypeVariable type, Set<AnnotatedTypeMirror> visiting) {
+      StringBuilder sb = new StringBuilder();
+      if (TypesUtils.isCaptured(type.underlyingType)) {
+        String underlyingType = type.underlyingType.toString();
+        // underlyingType has this form: "capture#826 of ? extends java.lang.Object".
+        // We output only the "capture#826" part.
+        // NOTE: The number is the hash code of the captured type, so it's nondeterministic, but it
+        // is still important to print it in order to tell the difference between two captured
+        // types.
+        sb.append(underlyingType, 0, underlyingType.indexOf(" of "));
+      } else {
+        sb.append(type.underlyingType);
+      }
+
+      if (!visiting.contains(type)) {
+        if (type.isDeclaration() && currentPrintInvisibleSetting) {
+          sb.append("/*DECL*/ ");
+        }
+
+        try {
+          visiting.add(type);
+          if (currentPrintVerboseGenerics) {
+            sb.append("[");
+          }
+          printBound("extends", type.getUpperBoundField(), visiting, sb);
+          printBound("super", type.getLowerBoundField(), visiting, sb);
+          if (currentPrintVerboseGenerics) {
+            sb.append("]");
+          }
+
+        } finally {
+          visiting.remove(type);
+        }
+      }
+      return sb.toString();
+    }
+
+    @SideEffectFree
+    @Override
+    public String visitPrimitive(AnnotatedPrimitiveType type, Set<AnnotatedTypeMirror> visiting) {
+      return formatFlatType(type);
+    }
+
+    @SideEffectFree
+    @Override
+    public String visitNoType(AnnotatedNoType type, Set<AnnotatedTypeMirror> visiting) {
+      return formatFlatType(type);
+    }
+
+    @SideEffectFree
+    @Override
+    public String visitNull(AnnotatedNullType type, Set<AnnotatedTypeMirror> visiting) {
+      return annoFormatter.formatAnnotationString(
+              type.getAnnotations(), currentPrintInvisibleSetting)
+          + "NullType";
+    }
+
+    @Override
+    public String visitWildcard(AnnotatedWildcardType type, Set<AnnotatedTypeMirror> visiting) {
+      StringBuilder sb = new StringBuilder();
+      if (type.isUninferredTypeArgument()) {
+        sb.append("/*INFERENCE FAILED for:*/ ");
+      }
+
+      sb.append(
+          annoFormatter.formatAnnotationString(
+              type.getAnnotationsField(), currentPrintInvisibleSetting));
+
+      sb.append("?");
+      if (!visiting.contains(type)) {
+
+        try {
+          visiting.add(type);
+
+          if (currentPrintVerboseGenerics) {
+            sb.append("[");
+          }
+          printBound("extends", type.getExtendsBoundField(), visiting, sb);
+          printBound("super", type.getSuperBoundField(), visiting, sb);
+          if (currentPrintVerboseGenerics) {
+            sb.append("]");
+          }
+
+        } finally {
+          visiting.remove(type);
+        }
+      }
+      return sb.toString();
+    }
+
+    @SideEffectFree
+    protected String formatFlatType(final AnnotatedTypeMirror flatType) {
+      return annoFormatter.formatAnnotationString(
+              flatType.getAnnotations(), currentPrintInvisibleSetting)
+          + TypeAnnotationUtils.unannotatedType((Type) flatType.getUnderlyingType());
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultInferredTypesApplier.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultInferredTypesApplier.java
new file mode 100644
index 0000000..ebce18e
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultInferredTypesApplier.java
@@ -0,0 +1,132 @@
+package org.checkerframework.framework.type;
+
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.BugInCF;
+
+/** Utility class for applying the annotations inferred by dataflow to a given type. */
+public class DefaultInferredTypesApplier {
+
+  // At the moment, only Inference uses the omitSubtypingCheck option.
+  // In actuality the subtyping check should be unnecessary since inferred
+  // types should be subtypes of their declaration.
+  private final boolean omitSubtypingCheck;
+
+  private final QualifierHierarchy hierarchy;
+  private final AnnotatedTypeFactory factory;
+
+  public DefaultInferredTypesApplier(QualifierHierarchy hierarchy, AnnotatedTypeFactory factory) {
+    this(false, hierarchy, factory);
+  }
+
+  public DefaultInferredTypesApplier(
+      boolean omitSubtypingCheck, QualifierHierarchy hierarchy, AnnotatedTypeFactory factory) {
+    this.omitSubtypingCheck = omitSubtypingCheck;
+    this.hierarchy = hierarchy;
+    this.factory = factory;
+  }
+
+  /**
+   * For each top in qualifier hierarchy, traverse inferred and copy the required annotations over
+   * to type.
+   *
+   * @param type the type to which annotations are being applied
+   * @param inferredSet the type inferred by data flow
+   * @param inferredTypeMirror underlying inferred type
+   */
+  public void applyInferredType(
+      final AnnotatedTypeMirror type,
+      final Set<AnnotationMirror> inferredSet,
+      TypeMirror inferredTypeMirror) {
+    if (inferredSet == null) {
+      return;
+    }
+    if (inferredTypeMirror.getKind() == TypeKind.WILDCARD) {
+      // Dataflow might infer a wildcard that extends a type variable for types that are
+      // actually type variables.  Use the type variable instead.
+      while (inferredTypeMirror.getKind() == TypeKind.WILDCARD
+          && (((WildcardType) inferredTypeMirror).getExtendsBound() != null)) {
+        inferredTypeMirror = ((WildcardType) inferredTypeMirror).getExtendsBound();
+      }
+    }
+    for (final AnnotationMirror top : hierarchy.getTopAnnotations()) {
+      AnnotationMirror inferred = hierarchy.findAnnotationInHierarchy(inferredSet, top);
+
+      apply(type, inferred, inferredTypeMirror, top);
+    }
+  }
+
+  private void apply(
+      AnnotatedTypeMirror type,
+      AnnotationMirror inferred,
+      TypeMirror inferredTypeMirror,
+      AnnotationMirror top) {
+    AnnotationMirror primary = type.getAnnotationInHierarchy(top);
+    if (inferred == null) {
+
+      if (primary == null) {
+        // Type doesn't have a primary either, nothing to remove
+      } else if (type.getKind() == TypeKind.TYPEVAR) {
+        removePrimaryAnnotationTypeVar(
+            (AnnotatedTypeVariable) type, inferredTypeMirror, top, primary);
+      } else {
+        removePrimaryTypeVarApplyUpperBound(type, inferredTypeMirror, top, primary);
+      }
+    } else {
+      if (primary == null) {
+        Set<AnnotationMirror> lowerbounds =
+            AnnotatedTypes.findEffectiveLowerBoundAnnotations(hierarchy, type);
+        primary = hierarchy.findAnnotationInHierarchy(lowerbounds, top);
+      }
+      if ((omitSubtypingCheck || hierarchy.isSubtype(inferred, primary))) {
+        type.replaceAnnotation(inferred);
+      }
+    }
+  }
+
+  private void removePrimaryTypeVarApplyUpperBound(
+      AnnotatedTypeMirror type,
+      TypeMirror inferredTypeMirror,
+      AnnotationMirror top,
+      AnnotationMirror notInferred) {
+    if (inferredTypeMirror.getKind() != TypeKind.TYPEVAR) {
+      throw new BugInCF("Inferred value should not be missing annotations: " + inferredTypeMirror);
+    }
+
+    TypeVariable typeVar = (TypeVariable) inferredTypeMirror;
+    AnnotatedTypeVariable typeVariableDecl =
+        (AnnotatedTypeVariable) factory.getAnnotatedType(typeVar.asElement());
+    AnnotationMirror upperBound = typeVariableDecl.getEffectiveAnnotationInHierarchy(top);
+
+    if (omitSubtypingCheck || hierarchy.isSubtype(upperBound, notInferred)) {
+      type.replaceAnnotation(upperBound);
+    }
+  }
+
+  private void removePrimaryAnnotationTypeVar(
+      AnnotatedTypeVariable annotatedTypeVariable,
+      TypeMirror inferredTypeMirror,
+      AnnotationMirror top,
+      AnnotationMirror previousAnnotation) {
+    if (inferredTypeMirror.getKind() != TypeKind.TYPEVAR) {
+      throw new BugInCF("Missing annos");
+    }
+    TypeVariable typeVar = (TypeVariable) inferredTypeMirror;
+    AnnotatedTypeVariable typeVariableDecl =
+        (AnnotatedTypeVariable) factory.getAnnotatedType(typeVar.asElement());
+    AnnotationMirror upperBound = typeVariableDecl.getEffectiveAnnotationInHierarchy(top);
+    if (omitSubtypingCheck || hierarchy.isSubtype(upperBound, previousAnnotation)) {
+      annotatedTypeVariable.removeAnnotationInHierarchy(top);
+      AnnotationMirror ub = typeVariableDecl.getUpperBound().getAnnotationInHierarchy(top);
+      apply(annotatedTypeVariable.getUpperBound(), ub, typeVar.getUpperBound(), top);
+      AnnotationMirror lb = typeVariableDecl.getLowerBound().getAnnotationInHierarchy(top);
+      apply(annotatedTypeVariable.getLowerBound(), lb, typeVar.getLowerBound(), top);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java
new file mode 100644
index 0000000..a0fc9b2
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java
@@ -0,0 +1,1092 @@
+package org.checkerframework.framework.type;
+
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.Types;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.Covariant;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AbstractAtmComboVisitor;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.AtmCombo;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Default implementation of TypeHierarchy that implements the JLS specification with minor
+ * deviations as outlined by the Checker Framework manual. Changes to the JLS include forbidding
+ * covariant array types, raw types, and allowing covariant type arguments depending on various
+ * options passed to DefaultTypeHierarchy.
+ *
+ * <p>Subtyping rules of the JLS can be found in <a
+ * href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.10">section 4.10,
+ * "Subtyping"</a>.
+ *
+ * <p>Note: The visit methods of this class must be public but it is intended to be used through a
+ * TypeHierarchy interface reference which will only allow isSubtype to be called. Clients should
+ * not call the visit methods.
+ */
+public class DefaultTypeHierarchy extends AbstractAtmComboVisitor<Boolean, Void>
+    implements TypeHierarchy {
+  // used for processingEnvironment when needed
+  protected final BaseTypeChecker checker;
+
+  protected final QualifierHierarchy qualifierHierarchy;
+  protected final StructuralEqualityComparer equalityComparer;
+
+  protected final boolean ignoreRawTypes;
+  protected final boolean invariantArrayComponents;
+
+  // TODO: Incorporate feedback from David/Suzanne
+  // IMPORTANT_NOTE:
+  //
+  // For MultigraphQualifierHierarchies, we check the subtyping relationship of each annotation
+  // hierarchy individually.  This is done because when comparing a pair of type variables,
+  // sometimes you need to traverse and compare the bounds of two type variables.  Other times it
+  // is incorrect to compare the bounds.  These two cases can occur simultaneously when comparing
+  // two hierarchies at once.  In this case, comparing both hierarchies simultaneously will lead
+  // to an error.  More detail is given below.
+  //
+  // Recall, type variables may or may not have a primary annotation for each individual
+  // hierarchy.  When comparing
+  // two type variables for a specific hierarchy we have five possible cases:
+  //      case 1:  only one of the type variables has a primary annotation
+  //      case 2a: both type variables have primary annotations and they are uses of the same type
+  //               parameter
+  //      case 2b: both type variables have primary annotations and they are uses of different
+  //               type parameters
+  //      case 3a: neither type variable has a primary annotation and they are uses of the same
+  //               type parameter
+  //      case 3b: neither type variable has a primary annotation and they are uses of different
+  //               type parameters
+  //
+  // Case 1, 2b, and 3b require us to traverse both type variables bounds to ensure that the
+  // subtype's upper bound is a subtype of the supertype's lower bound. Cases 2a requires only
+  // that we check that the primary annotation on the subtype is a subtype of the primary
+  // annotation on the supertype.  In case 3a, we can just return true, since two
+  // non-primary-annotated uses of the same type parameter are equivalent.  In this case it would
+  // be an error to check the bounds because the check would only return true when the bounds are
+  // exact but it should always return true.
+  //
+  // A problem occurs when, one hierarchy matches cases 1, 2b, or 3b and the other matches 3a.  In
+  // the first set of cases we MUST check the type variables' bounds.  In case 3a we MUST NOT
+  // check the bounds.  e.g.
+  //
+  // Suppose I have a hierarchy with two tops @A1 and @B1.  Let @A0 <: @A1 and @B0 <: @B1.
+  //  @A1 T t1;  T t2;
+  //  t1 = t2;
+  //
+  // To typecheck "t1 = t2;" in the hierarchy topped by @A1, we need to descend into the bounds of
+  // t1 and t2 (where t1's bounds will be overridden by @A1).  However, for hierarchy B we need
+  // only recognize that since neither variable has a primary annotation, the types are equivalent
+  // and no traversal is needed.  If we tried to do these two actions simultaneously, in every
+  // visit and isSubtype call, we would have to check to see that the @B hierarchy has been
+  // handled and ignore those annotations.
+  //
+  // Solutions:
+  // We could handle this problem by keeping track of which hierarchies have already been taken
+  // care of.  We could then check each hierarchy before making comparisons.  But this would lead
+  // to complicated plumbing that would be hard to understand.
+  // The chosen solution is to only check one hierarchy at a time.  One trade-off to this approach
+  // is that we have to re-traverse the types for each hierarchy being checked.
+  //
+  // The field currentTop identifies the hierarchy for which the types are currently being checked.
+  // Final note: all annotation comparisons are done via isPrimarySubtype, isBottom, and
+  // isAnnoSubtype in order to ensure that we first get the annotations in the hierarchy of
+  // currentTop before passing annotations to qualifierHierarchy.
+  /** The top annotation of the hierarchy currently being checked. */
+  protected AnnotationMirror currentTop;
+
+  /** Stores the result of isSubtype, if that result is true. */
+  protected final SubtypeVisitHistory isSubtypeVisitHistory;
+
+  /**
+   * Stores the result of {@link #areEqualInHierarchy(AnnotatedTypeMirror, AnnotatedTypeMirror)} for
+   * type arguments. Prevents infinite recursion on types that refer to themselves. (Stores both
+   * true and false results.)
+   */
+  protected final StructuralEqualityVisitHistory areEqualVisitHistory;
+
+  /** The Covariant.value field/element. */
+  final ExecutableElement covariantValueElement;
+
+  /**
+   * Creates a DefaultTypeHierarchy.
+   *
+   * @param checker the type-checker that is associated with this
+   * @param qualifierHierarchy the qualiifer hierarchy that is associated with this
+   * @param ignoreRawTypes whether to ignore raw types
+   * @param invariantArrayComponents whether to make array subtyping invariant with respect to array
+   *     component types
+   */
+  public DefaultTypeHierarchy(
+      final BaseTypeChecker checker,
+      final QualifierHierarchy qualifierHierarchy,
+      boolean ignoreRawTypes,
+      boolean invariantArrayComponents) {
+    this.checker = checker;
+    this.qualifierHierarchy = qualifierHierarchy;
+    this.isSubtypeVisitHistory = new SubtypeVisitHistory();
+    this.areEqualVisitHistory = new StructuralEqualityVisitHistory();
+    this.equalityComparer = createEqualityComparer();
+
+    this.ignoreRawTypes = ignoreRawTypes;
+    this.invariantArrayComponents = invariantArrayComponents;
+
+    covariantValueElement =
+        TreeUtils.getMethod(Covariant.class, "value", 0, checker.getProcessingEnvironment());
+  }
+
+  /**
+   * Create the equality comparer.
+   *
+   * @return the equality comparer
+   */
+  protected StructuralEqualityComparer createEqualityComparer() {
+    return new StructuralEqualityComparer(areEqualVisitHistory);
+  }
+
+  /**
+   * Returns true if subtype {@literal <:} supertype.
+   *
+   * <p>This implementation iterates over all top annotations and invokes {@link
+   * #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}. Most type systems
+   * should not override this method, but instead override {@link #isSubtype(AnnotatedTypeMirror,
+   * AnnotatedTypeMirror, AnnotationMirror)} or some of the {@code visitXXX} methods.
+   *
+   * @param subtype expected subtype
+   * @param supertype expected supertype
+   * @return true if subtype is a subtype of supertype or equal to it
+   */
+  @Override
+  public boolean isSubtype(final AnnotatedTypeMirror subtype, final AnnotatedTypeMirror supertype) {
+    for (final AnnotationMirror top : qualifierHierarchy.getTopAnnotations()) {
+      if (!isSubtype(subtype, supertype, top)) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Returns true if {@code subtype <: supertype}, but only for the hierarchy of which {@code top}
+   * is the top.
+   *
+   * @param subtype expected subtype
+   * @param supertype expected supertype
+   * @param top the top of the hierarchy for which we want to make a comparison
+   * @return true if {@code subtype} is a subtype of, or equal to, {@code supertype} in the
+   *     qualifier hierarchy whose top is {@code top}
+   */
+  protected boolean isSubtype(
+      final AnnotatedTypeMirror subtype,
+      final AnnotatedTypeMirror supertype,
+      final AnnotationMirror top) {
+    assert top != null;
+    currentTop = top;
+    return AtmCombo.accept(subtype, supertype, null, this);
+  }
+
+  /**
+   * Returns error message for the case when two types shouldn't be compared.
+   *
+   * @return error message for the case when two types shouldn't be compared
+   */
+  @Override
+  protected String defaultErrorMessage(
+      final AnnotatedTypeMirror subtype, final AnnotatedTypeMirror supertype, final Void p) {
+    return "Incomparable types ("
+        + subtype
+        + ", "
+        + supertype
+        + ") visitHistory = "
+        + isSubtypeVisitHistory;
+  }
+
+  /**
+   * Compare the primary annotations of {@code subtype} and {@code supertype}. Neither type can be
+   * missing annotations.
+   *
+   * @param subtype a type that might be a subtype (with respect to primary annotations)
+   * @param supertype a type that might be a supertype (with respect to primary annotations)
+   * @return true if the primary annotation on subtype {@literal <:} primary annotation on supertype
+   *     for the current top.
+   */
+  protected boolean isPrimarySubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) {
+    final AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop);
+    final AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop);
+    if (checker.getTypeFactory().hasQualifierParameterInHierarchy(supertype, currentTop)
+        && checker.getTypeFactory().hasQualifierParameterInHierarchy(subtype, currentTop)) {
+      // If the types have a class qualifier parameter, the qualifiers must be equivalent.
+      return qualifierHierarchy.isSubtype(subtypeAnno, supertypeAnno)
+          && qualifierHierarchy.isSubtype(supertypeAnno, subtypeAnno);
+    }
+
+    return qualifierHierarchy.isSubtype(subtypeAnno, supertypeAnno);
+  }
+
+  /**
+   * Like {@link #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}, but uses a cache to prevent
+   * infinite recursion on recursive types.
+   *
+   * @param subtype a type that may be a subtype
+   * @param supertype a type that may be a supertype
+   * @return true if subtype {@literal <:} supertype
+   */
+  protected boolean isSubtypeCaching(
+      final AnnotatedTypeMirror subtype, final AnnotatedTypeMirror supertype) {
+    if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) {
+      // visitHistory only contains pairs in a subtype relationship.
+      return true;
+    }
+
+    boolean result = isSubtype(subtype, supertype, currentTop);
+    // The call to put has no effect if result is false.
+    isSubtypeVisitHistory.put(subtype, supertype, currentTop, result);
+    return result;
+  }
+
+  /**
+   * Are all the types in {@code subtypes} a subtype of {@code supertype}?
+   *
+   * <p>The underlying type mirrors of {@code subtypes} must be subtypes of the underlying type
+   * mirror of {@code supertype}.
+   */
+  protected boolean areAllSubtypes(
+      final Iterable<? extends AnnotatedTypeMirror> subtypes, final AnnotatedTypeMirror supertype) {
+    for (final AnnotatedTypeMirror subtype : subtypes) {
+      if (!isSubtype(subtype, supertype, currentTop)) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  protected boolean areEqualInHierarchy(
+      final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
+    return equalityComparer.areEqualInHierarchy(type1, type2, currentTop);
+  }
+
+  /**
+   * A declared type is considered a supertype of another declared type only if all of the type
+   * arguments of the declared type "contain" the corresponding type arguments of the subtype.
+   * Containment is described in <a
+   * href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.5.1">JLS section
+   * 4.5.1 "Type Arguments of Parameterized Types"</a>.
+   *
+   * @param inside a type argument of the "subtype"
+   * @param outside a type argument of the "supertype"
+   * @param canBeCovariant whether or not type arguments are allowed to be covariant
+   * @return true if inside is contained by outside, or if canBeCovariant == true and {@code inside
+   *     <: outside}
+   */
+  protected boolean isContainedBy(
+      final AnnotatedTypeMirror inside, final AnnotatedTypeMirror outside, boolean canBeCovariant) {
+
+    if (ignoreUninferredTypeArgument(inside) || ignoreUninferredTypeArgument(outside)) {
+      areEqualVisitHistory.put(inside, outside, currentTop, true);
+      return true;
+    }
+
+    if (outside.getKind() != TypeKind.WILDCARD
+        && !TypesUtils.isCaptured(outside.getUnderlyingType())) {
+      if (canBeCovariant) {
+        return isSubtype(inside, outside, currentTop);
+      }
+      return areEqualInHierarchy(inside, outside);
+    }
+
+    AnnotatedTypeMirror outsideUpperBound;
+    AnnotatedTypeMirror outsideLowerBound;
+    if (outside.getKind() == TypeKind.WILDCARD) {
+      outsideUpperBound = ((AnnotatedWildcardType) outside).getExtendsBound();
+      outsideLowerBound = ((AnnotatedWildcardType) outside).getSuperBound();
+    } else if (TypesUtils.isCaptured(outside.getUnderlyingType())) {
+      outsideUpperBound = ((AnnotatedTypeVariable) outside).getUpperBound();
+      outsideLowerBound = ((AnnotatedTypeVariable) outside).getLowerBound();
+    } else {
+      throw new BugInCF("Expected a wildcard or captured type variable, but found " + outside);
+    }
+    Boolean previousResult = areEqualVisitHistory.get(inside, outside, currentTop);
+    if (previousResult != null) {
+      return previousResult;
+    }
+
+    areEqualVisitHistory.put(inside, outside, currentTop, true);
+    boolean result =
+        isContainedWildcard(inside, outside, outsideUpperBound, outsideLowerBound, canBeCovariant);
+    areEqualVisitHistory.put(inside, outside, currentTop, result);
+    return result;
+  }
+
+  private boolean isContainedWildcard(
+      AnnotatedTypeMirror inside,
+      AnnotatedTypeMirror outside,
+      AnnotatedTypeMirror outsideUpperBound,
+      AnnotatedTypeMirror outsideLowerBound,
+      boolean canBeCovariant) {
+
+    if (inside.equals(outside)) {
+      // If they are equal, outside always contains inside.
+      return true;
+    }
+
+    if (inside.getKind() == TypeKind.WILDCARD) {
+      outsideUpperBound =
+          checker
+              .getTypeFactory()
+              .widenToUpperBound(outsideUpperBound, (AnnotatedWildcardType) inside);
+    }
+    while (outsideUpperBound.getKind() == TypeKind.WILDCARD) {
+      if (ignoreUninferredTypeArgument(outsideUpperBound)) {
+        return true;
+      }
+      outsideUpperBound = ((AnnotatedWildcardType) outsideUpperBound).getExtendsBound();
+    }
+
+    if (!TypesUtils.isErasedSubtype(
+        inside.getUnderlyingType(),
+        outsideUpperBound.getUnderlyingType(),
+        inside.atypeFactory.types)) {
+      // TODO: subtype is a wildcard that should have been captured, so just check the primary
+      // annotations.
+      AnnotationMirror subAnno = inside.getEffectiveAnnotationInHierarchy(currentTop);
+      AnnotationMirror superAnno = outsideUpperBound.getAnnotationInHierarchy(currentTop);
+      return qualifierHierarchy.isSubtype(subAnno, superAnno);
+    }
+
+    AnnotatedTypeMirror castedInside =
+        AnnotatedTypes.castedAsSuper(inside.atypeFactory, inside, outsideUpperBound);
+    if (!isSubtypeCaching(castedInside, outsideUpperBound)) {
+      return false;
+    }
+
+    if (outside.getKind() == TypeKind.WILDCARD && outsideLowerBound.getKind() == TypeKind.TYPEVAR) {
+      // tests/all-systems/Issue1991.java crashes without this.
+      return true;
+    }
+    return canBeCovariant || isSubtypeCaching(outsideLowerBound, inside);
+  }
+
+  /**
+   * Returns true if {@code type} is an uninferred type argument and if the checker should not issue
+   * warnings about uninferred type arguments.
+   *
+   * @param type type to check
+   * @return true if {@code type} is an uninferred type argument and if the checker should not issue
+   *     warnings about uninferred type arguments
+   */
+  private boolean ignoreUninferredTypeArgument(AnnotatedTypeMirror type) {
+    return type.atypeFactory.ignoreUninferredTypeArguments
+        && type.getKind() == TypeKind.WILDCARD
+        && ((AnnotatedWildcardType) type).isUninferredTypeArgument();
+  }
+
+  // ------------------------------------------------------------------------
+  // The rest of this file is the visitor methods.  It is a lot of methods, one for each
+  // combination of types.
+
+  // ------------------------------------------------------------------------
+  // Arrays as subtypes
+
+  @Override
+  public Boolean visitArray_Array(
+      AnnotatedArrayType subtype, AnnotatedArrayType supertype, Void p) {
+    return isPrimarySubtype(subtype, supertype)
+        && (invariantArrayComponents
+            ? areEqualInHierarchy(subtype.getComponentType(), supertype.getComponentType())
+            : isSubtype(subtype.getComponentType(), supertype.getComponentType(), currentTop));
+  }
+
+  @Override
+  public Boolean visitArray_Declared(
+      AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, Void p) {
+    return isPrimarySubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitArray_Null(AnnotatedArrayType subtype, AnnotatedNullType supertype, Void p) {
+    return isPrimarySubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitArray_Intersection(
+      AnnotatedArrayType subtype, AnnotatedIntersectionType supertype, Void p) {
+    return isSubtype(
+        AnnotatedTypes.castedAsSuper(subtype.atypeFactory, subtype, supertype),
+        supertype,
+        currentTop);
+  }
+
+  @Override
+  public Boolean visitArray_Wildcard(
+      AnnotatedArrayType subtype, AnnotatedWildcardType supertype, Void p) {
+    return visitWildcardSupertype(subtype, supertype);
+  }
+
+  // ------------------------------------------------------------------------
+  // Declared as subtype
+  @Override
+  public Boolean visitDeclared_Array(
+      AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, Void p) {
+    return isPrimarySubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitDeclared_Declared(
+      AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, Void p) {
+    if (!isPrimarySubtype(subtype, supertype)) {
+      return false;
+    }
+    AnnotatedTypeFactory factory = subtype.atypeFactory;
+    if (factory.ignoreUninferredTypeArguments
+        && (factory.containsUninferredTypeArguments(subtype)
+            || factory.containsUninferredTypeArguments(supertype))) {
+      // Calling castedAsSuper may cause the uninferredTypeArguments to be lost. So, just
+      // return true here.
+      return true;
+    }
+    AnnotatedDeclaredType subtypeAsSuper =
+        AnnotatedTypes.castedAsSuper(subtype.atypeFactory, subtype, supertype);
+
+    if (isSubtypeVisitHistory.contains(subtypeAsSuper, supertype, currentTop)) {
+      return true;
+    }
+
+    final boolean result =
+        visitTypeArgs(subtypeAsSuper, supertype, subtype.wasRaw(), supertype.wasRaw());
+    isSubtypeVisitHistory.put(subtypeAsSuper, supertype, currentTop, result);
+
+    return result;
+  }
+
+  /**
+   * A helper class for visitDeclared_Declared. There are subtypes of DefaultTypeHierarchy that need
+   * to customize the handling of type arguments. This method provides a convenient extension point.
+   */
+  protected boolean visitTypeArgs(
+      final AnnotatedDeclaredType subtype,
+      final AnnotatedDeclaredType supertype,
+      final boolean subtypeRaw,
+      final boolean supertypeRaw) {
+
+    final boolean ignoreTypeArgs = ignoreRawTypes && (subtypeRaw || supertypeRaw);
+
+    if (ignoreTypeArgs) {
+      return true;
+    }
+
+    final List<? extends AnnotatedTypeMirror> subtypeTypeArgs = subtype.getTypeArguments();
+    final List<? extends AnnotatedTypeMirror> supertypeTypeArgs = supertype.getTypeArguments();
+
+    if (subtypeTypeArgs.size() != supertypeTypeArgs.size()) {
+      return false;
+    }
+    if (subtypeTypeArgs.isEmpty()) {
+      return true;
+    }
+
+    final TypeElement supertypeElem = (TypeElement) supertype.getUnderlyingType().asElement();
+    AnnotationMirror covariantAnno =
+        supertype.atypeFactory.getDeclAnnotation(supertypeElem, Covariant.class);
+
+    List<Integer> covariantArgIndexes =
+        (covariantAnno == null)
+            ? null
+            : AnnotationUtils.getElementValueArray(
+                covariantAnno, covariantValueElement, Integer.class);
+
+    for (int i = 0; i < supertypeTypeArgs.size(); i++) {
+      final AnnotatedTypeMirror superTypeArg = supertypeTypeArgs.get(i);
+      final AnnotatedTypeMirror subTypeArg = subtypeTypeArgs.get(i);
+      final boolean covariant = covariantArgIndexes != null && covariantArgIndexes.contains(i);
+
+      boolean result = isContainedBy(subTypeArg, superTypeArg, covariant);
+
+      if (!result) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  @Override
+  public Boolean visitDeclared_Intersection(
+      AnnotatedDeclaredType subtype, AnnotatedIntersectionType supertype, Void p) {
+    return visitIntersectionSupertype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitDeclared_Null(
+      AnnotatedDeclaredType subtype, AnnotatedNullType supertype, Void p) {
+    return isPrimarySubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitDeclared_Primitive(
+      AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, Void p) {
+    // We do an asSuper first because in some cases unboxing implies a more specific annotation
+    // e.g. @UnknownInterned Integer => @Interned int  because primitives are always interned
+    final AnnotatedPrimitiveType subAsSuper =
+        AnnotatedTypes.castedAsSuper(subtype.atypeFactory, subtype, supertype);
+    if (subAsSuper == null) {
+      return isPrimarySubtype(subtype, supertype);
+    }
+    return isPrimarySubtype(subAsSuper, supertype);
+  }
+
+  @Override
+  public Boolean visitDeclared_Typevar(
+      AnnotatedDeclaredType subtype, AnnotatedTypeVariable supertype, Void p) {
+    return visitTypevarSupertype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitDeclared_Union(
+      AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, Void p) {
+    Types types = checker.getTypeUtils();
+    for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) {
+      if (TypesUtils.isErasedSubtype(
+              subtype.getUnderlyingType(), supertypeAltern.getUnderlyingType(), types)
+          && isSubtype(subtype, supertypeAltern, currentTop)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public Boolean visitDeclared_Wildcard(
+      AnnotatedDeclaredType subtype, AnnotatedWildcardType supertype, Void p) {
+    return visitWildcardSupertype(subtype, supertype);
+  }
+
+  // ------------------------------------------------------------------------
+  // Intersection as subtype
+  @Override
+  public Boolean visitIntersection_Declared(
+      AnnotatedIntersectionType subtype, AnnotatedDeclaredType supertype, Void p) {
+    return visitIntersectionSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitIntersection_Primitive(
+      AnnotatedIntersectionType subtype, AnnotatedPrimitiveType supertype, Void p) {
+    for (AnnotatedTypeMirror subtypeBound : subtype.getBounds()) {
+      if (TypesUtils.isBoxedPrimitive(subtypeBound.getUnderlyingType())
+          && isSubtype(subtypeBound, supertype, currentTop)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public Boolean visitIntersection_Intersection(
+      AnnotatedIntersectionType subtype, AnnotatedIntersectionType supertype, Void p) {
+    Types types = checker.getTypeUtils();
+    for (AnnotatedTypeMirror subBound : subtype.getBounds()) {
+      for (AnnotatedTypeMirror superBound : supertype.getBounds()) {
+        if (TypesUtils.isErasedSubtype(
+                subBound.getUnderlyingType(), superBound.getUnderlyingType(), types)
+            && !isSubtype(subBound, superBound, currentTop)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public Boolean visitIntersection_Null(
+      AnnotatedIntersectionType subtype, AnnotatedNullType supertype, Void p) {
+    // this can occur through capture conversion/comparing bounds
+    for (AnnotatedTypeMirror bound : subtype.getBounds()) {
+      if (isPrimarySubtype(bound, supertype)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public Boolean visitIntersection_Typevar(
+      AnnotatedIntersectionType subtype, AnnotatedTypeVariable supertype, Void p) {
+    return visitIntersectionSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitIntersection_Wildcard(
+      AnnotatedIntersectionType subtype, AnnotatedWildcardType supertype, Void p) {
+    return visitIntersectionSubtype(subtype, supertype);
+  }
+
+  // ------------------------------------------------------------------------
+  // Null as subtype
+  @Override
+  public Boolean visitNull_Array(AnnotatedNullType subtype, AnnotatedArrayType supertype, Void p) {
+    return isPrimarySubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitNull_Declared(
+      AnnotatedNullType subtype, AnnotatedDeclaredType supertype, Void p) {
+    return isPrimarySubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitNull_Typevar(
+      AnnotatedNullType subtype, AnnotatedTypeVariable supertype, Void p) {
+    return visitTypevarSupertype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitNull_Wildcard(
+      AnnotatedNullType subtype, AnnotatedWildcardType supertype, Void p) {
+    return visitWildcardSupertype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitNull_Null(AnnotatedNullType subtype, AnnotatedNullType supertype, Void p) {
+    // this can occur when comparing typevar lower bounds since they are usually null types
+    return isPrimarySubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitNull_Union(AnnotatedNullType subtype, AnnotatedUnionType supertype, Void p) {
+    for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) {
+      if (isSubtype(subtype, supertypeAltern, currentTop)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public Boolean visitNull_Intersection(
+      AnnotatedNullType subtype, AnnotatedIntersectionType supertype, Void p) {
+    return isPrimarySubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitNull_Primitive(
+      AnnotatedNullType subtype, AnnotatedPrimitiveType supertype, Void p) {
+    return isPrimarySubtype(subtype, supertype);
+  }
+
+  // ------------------------------------------------------------------------
+  // Primitive as subtype
+  @Override
+  public Boolean visitPrimitive_Declared(
+      AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, Void p) {
+    // see comment in visitDeclared_Primitive
+    final AnnotatedDeclaredType subAsSuper =
+        AnnotatedTypes.castedAsSuper(subtype.atypeFactory, subtype, supertype);
+    if (subAsSuper == null) {
+      return isPrimarySubtype(subtype, supertype);
+    }
+    return isPrimarySubtype(subAsSuper, supertype);
+  }
+
+  @Override
+  public Boolean visitPrimitive_Primitive(
+      AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, Void p) {
+    return isPrimarySubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitPrimitive_Intersection(
+      AnnotatedPrimitiveType subtype, AnnotatedIntersectionType supertype, Void p) {
+    return visitIntersectionSupertype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitPrimitive_Typevar(
+      AnnotatedPrimitiveType subtype, AnnotatedTypeVariable supertype, Void p) {
+    return AtmCombo.accept(subtype, supertype.getUpperBound(), null, this);
+  }
+
+  @Override
+  public Boolean visitPrimitive_Wildcard(
+      AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, Void p) {
+    if (supertype.atypeFactory.ignoreUninferredTypeArguments
+        && supertype.isUninferredTypeArgument()) {
+      return true;
+    }
+    // this can occur when passing a primitive to a method on a raw type (see test
+    // checker/tests/nullness/RawAndPrimitive.java).  This can also occur because we don't box
+    // primitives when we should and don't capture convert.
+    return isPrimarySubtype(subtype, supertype.getSuperBound());
+  }
+
+  // ------------------------------------------------------------------------
+  // Union as subtype
+  @Override
+  public Boolean visitUnion_Declared(
+      AnnotatedUnionType subtype, AnnotatedDeclaredType supertype, Void p) {
+    return visitUnionSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitUnion_Intersection(
+      AnnotatedUnionType subtype, AnnotatedIntersectionType supertype, Void p) {
+    // For example:
+    // <T extends Throwable & Cloneable> void method(T param) {}
+    // ...
+    // catch (Exception1 | Exception2 union) { // Assuming Exception1 and Exception2 implement
+    // Cloneable
+    //   method(union);
+    // This case happens when checking that the inferred type argument is a subtype of the
+    // declared type argument of method.
+    // See org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments
+    return visitUnionSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitUnion_Union(
+      AnnotatedUnionType subtype, AnnotatedUnionType supertype, Void p) {
+    // For example:
+    // <T> void method(T param) {}
+    // ...
+    // catch (Exception1 | Exception2 union) {
+    //   method(union);
+    // This case happens when checking the arguments to method after type variable substitution
+    return visitUnionSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitUnion_Wildcard(
+      AnnotatedUnionType subtype, AnnotatedWildcardType supertype, Void p) {
+    // For example:
+    // } catch (RuntimeException | IOException e) {
+    //     ArrayList<? super Exception> lWildcard = new ArrayList<>();
+    //     lWildcard.add(e);
+
+    return visitWildcardSupertype(subtype, supertype);
+  }
+
+  // ------------------------------------------------------------------------
+  // typevar as subtype
+  @Override
+  public Boolean visitTypevar_Declared(
+      AnnotatedTypeVariable subtype, AnnotatedDeclaredType supertype, Void p) {
+    return visitTypevarSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitTypevar_Intersection(
+      AnnotatedTypeVariable subtype, AnnotatedIntersectionType supertype, Void p) {
+    // this can happen when checking type param bounds
+    return visitIntersectionSupertype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitTypevar_Primitive(
+      AnnotatedTypeVariable subtype, AnnotatedPrimitiveType supertype, Void p) {
+    return visitTypevarSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitTypevar_Typevar(
+      AnnotatedTypeVariable subtype, AnnotatedTypeVariable supertype, Void p) {
+
+    if (AnnotatedTypes.haveSameDeclaration(checker.getTypeUtils(), subtype, supertype)) {
+      // subtype and supertype are uses of the same type parameter
+      boolean subtypeHasAnno = subtype.getAnnotationInHierarchy(currentTop) != null;
+      boolean supertypeHasAnno = supertype.getAnnotationInHierarchy(currentTop) != null;
+
+      if (subtypeHasAnno && supertypeHasAnno) {
+        // if both have primary annotations then you can just check the primary annotations
+        // as the bounds are the same
+        return isPrimarySubtype(subtype, supertype);
+
+      } else if (!subtypeHasAnno && !supertypeHasAnno && areEqualInHierarchy(subtype, supertype)) {
+        // two unannotated uses of the same type parameter are of the same type
+        return true;
+
+      } else if (subtype.getUpperBound().getKind() == TypeKind.INTERSECTION) {
+        // This case happens when a type has an intersection bound.  e.g.,
+        // T extends A & B
+        //
+        // And one use of the type has an annotation and the other does not. e.g.,
+        // @X T xt = ...;  T t = ..;
+        // xt = t;
+        //
+        return visit(subtype.getUpperBound(), supertype.getLowerBound(), null);
+      }
+    }
+
+    if (AnnotatedTypes.areCorrespondingTypeVariables(
+        checker.getProcessingEnvironment().getElementUtils(), subtype, supertype)) {
+      if (areEqualInHierarchy(subtype, supertype)) {
+        return true;
+      }
+    }
+
+    // check that the upper bound of the subtype is below the lower bound of the supertype
+    return visitTypevarSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitTypevar_Null(
+      AnnotatedTypeVariable subtype, AnnotatedNullType supertype, Void p) {
+    return visitTypevarSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitTypevar_Wildcard(
+      AnnotatedTypeVariable subtype, AnnotatedWildcardType supertype, Void p) {
+    return visitWildcardSupertype(subtype, supertype);
+  }
+
+  // ------------------------------------------------------------------------
+  // wildcard as subtype
+
+  @Override
+  public Boolean visitWildcard_Array(
+      AnnotatedWildcardType subtype, AnnotatedArrayType supertype, Void p) {
+    return visitWildcardSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitWildcard_Declared(
+      AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, Void p) {
+    if (subtype.isUninferredTypeArgument()) {
+      if (subtype.atypeFactory.ignoreUninferredTypeArguments) {
+        return true;
+      } else if (supertype.getTypeArguments().isEmpty()) {
+        // visitWildcardSubtype doesn't check uninferred type arguments, because the
+        // underlying Java types may not be in the correct relationship.  But, if the
+        // declared type does not have type arguments, then checking primary annotations is
+        // sufficient.
+        // For example, if the wildcard is ? extends @Nullable Object and the supertype is
+        // @Nullable String, then it is safe to return true. However if the supertype is
+        // @NullableList<@NonNull String> then it's not possible to decide if it is a
+        // subtype of the wildcard.
+        AnnotationMirror subtypeAnno = subtype.getEffectiveAnnotationInHierarchy(currentTop);
+        AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop);
+        return qualifierHierarchy.isSubtype(subtypeAnno, supertypeAnno);
+      }
+    }
+    return visitWildcardSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitWildcard_Intersection(
+      AnnotatedWildcardType subtype, AnnotatedIntersectionType supertype, Void p) {
+    return visitWildcardSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitWildcard_Primitive(
+      AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, Void p) {
+    if (subtype.isUninferredTypeArgument()) {
+      AnnotationMirror subtypeAnno = subtype.getEffectiveAnnotationInHierarchy(currentTop);
+      AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop);
+      return qualifierHierarchy.isSubtype(subtypeAnno, supertypeAnno);
+    }
+    return visitWildcardSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitWildcard_Typevar(
+      AnnotatedWildcardType subtype, AnnotatedTypeVariable supertype, Void p) {
+    return visitWildcardSubtype(subtype, supertype);
+  }
+
+  @Override
+  public Boolean visitWildcard_Wildcard(
+      AnnotatedWildcardType subtype, AnnotatedWildcardType supertype, Void p) {
+    return visitWildcardSubtype(subtype, supertype);
+  }
+
+  // ------------------------------------------------------------------------
+  // These "visit" methods are utility methods that aren't part of the visit interface
+  // but that handle cases that more than one visit method shares in common.
+
+  /**
+   * An intersection is a supertype if all of its bounds are a supertype of subtype.
+   *
+   * @param subtype the possible subtype
+   * @param supertype the possible supertype
+   * @return true {@code subtype} is a subtype of {@code supertype}
+   */
+  protected boolean visitIntersectionSupertype(
+      AnnotatedTypeMirror subtype, AnnotatedIntersectionType supertype) {
+    if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) {
+      return true;
+    }
+    boolean result = true;
+    for (AnnotatedTypeMirror bound : supertype.getBounds()) {
+      // Only call isSubtype if the Java type is actually a subtype; otherwise,
+      // only check primary qualifiers.
+      if (TypesUtils.isErasedSubtype(
+              subtype.getUnderlyingType(), bound.getUnderlyingType(), subtype.atypeFactory.types)
+          && !isSubtype(subtype, bound, currentTop)) {
+        result = false;
+        break;
+      }
+    }
+    isSubtypeVisitHistory.put(subtype, supertype, currentTop, result);
+    return result;
+  }
+
+  /**
+   * An intersection is a subtype if one of its bounds is a subtype of {@code supertype}.
+   *
+   * @param subtype an intersection type
+   * @param supertype an annotated type
+   * @return whether {@code subtype} is a subtype of {@code supertype}
+   */
+  protected boolean visitIntersectionSubtype(
+      AnnotatedIntersectionType subtype, AnnotatedTypeMirror supertype) {
+    Types types = checker.getTypeUtils();
+    // The primary annotations of the bounds should already be the same as the annotations on
+    // the intersection type.
+    for (AnnotatedTypeMirror subtypeBound : subtype.getBounds()) {
+      if (TypesUtils.isErasedSubtype(
+              subtypeBound.getUnderlyingType(), supertype.getUnderlyingType(), types)
+          && isSubtype(subtypeBound, supertype, currentTop)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * A type variable is a supertype if its lower bound is above subtype.
+   *
+   * @param subtype a type that might be a subtype
+   * @param supertype a type that might be a supertype
+   * @return true if {@code subtype} is a subtype of {@code supertype}
+   */
+  protected boolean visitTypevarSupertype(
+      AnnotatedTypeMirror subtype, AnnotatedTypeVariable supertype) {
+    return isSubtypeCaching(subtype, supertype.getLowerBound());
+  }
+
+  /**
+   * A type variable is a subtype if its upper bounds is below the supertype. Note: When comparing
+   * two type variables this method and visitTypevarSupertype will combine to isValid the subtypes
+   * upper bound against the supertypes lower bound.
+   */
+  protected boolean visitTypevarSubtype(
+      AnnotatedTypeVariable subtype, AnnotatedTypeMirror supertype) {
+    AnnotatedTypeMirror upperBound = subtype.getUpperBound();
+    if (TypesUtils.isBoxedPrimitive(upperBound.getUnderlyingType())
+        && supertype instanceof AnnotatedPrimitiveType) {
+      upperBound = supertype.atypeFactory.getUnboxedType((AnnotatedDeclaredType) upperBound);
+    }
+    if (supertype.getKind() == TypeKind.DECLARED
+        && TypesUtils.getTypeElement(supertype.getUnderlyingType()).getKind()
+            == ElementKind.INTERFACE) {
+      // Make sure the upper bound is no wildcard or type variable
+      while (upperBound.getKind() == TypeKind.TYPEVAR
+          || upperBound.getKind() == TypeKind.WILDCARD) {
+        if (upperBound.getKind() == TypeKind.TYPEVAR) {
+          upperBound = ((AnnotatedTypeVariable) upperBound).getUpperBound();
+        }
+        if (upperBound.getKind() == TypeKind.WILDCARD) {
+          upperBound = ((AnnotatedWildcardType) upperBound).getExtendsBound();
+        }
+      }
+      // If the supertype is an interface, only compare the primary annotations.
+      // The actual type argument could implement the interface and the bound of
+      // the type variable must not implement the interface.
+      if (upperBound.getKind() == TypeKind.INTERSECTION) {
+        Types types = checker.getTypeUtils();
+        for (AnnotatedTypeMirror ub : ((AnnotatedIntersectionType) upperBound).getBounds()) {
+          if (TypesUtils.isErasedSubtype(
+                  ub.getUnderlyingType(), supertype.getUnderlyingType(), types)
+              && isPrimarySubtype(ub, supertype)) {
+            return true;
+          }
+        }
+        return false;
+      }
+    }
+    return isSubtypeCaching(upperBound, supertype);
+  }
+
+  /** A union type is a subtype if ALL of its alternatives are subtypes of supertype. */
+  protected boolean visitUnionSubtype(AnnotatedUnionType subtype, AnnotatedTypeMirror supertype) {
+    return areAllSubtypes(subtype.getAlternatives(), supertype);
+  }
+
+  protected boolean visitWildcardSupertype(
+      AnnotatedTypeMirror subtype, AnnotatedWildcardType supertype) {
+    if (supertype.isUninferredTypeArgument()) { // TODO: REMOVE WHEN WE FIX TYPE ARG INFERENCE
+      // Can't call isSubtype because underlying Java types won't be subtypes.
+      return supertype.atypeFactory.ignoreUninferredTypeArguments;
+    }
+    return isSubtype(subtype, supertype.getSuperBound(), currentTop);
+  }
+
+  protected boolean visitWildcardSubtype(
+      AnnotatedWildcardType subtype, AnnotatedTypeMirror supertype) {
+    if (subtype.isUninferredTypeArgument()) {
+      return subtype.atypeFactory.ignoreUninferredTypeArguments;
+    }
+    TypeMirror superTypeMirror = supertype.getUnderlyingType();
+    if (supertype.getKind() == TypeKind.TYPEVAR) {
+      TypeVariable atv = (TypeVariable) supertype.getUnderlyingType();
+      if (TypesUtils.isCaptured(atv)) {
+        superTypeMirror = TypesUtils.getCapturedWildcard(atv);
+      }
+    }
+
+    if (superTypeMirror.getKind() == TypeKind.WILDCARD) {
+      // This can happen at a method invocation where a type variable in the method
+      // declaration is substituted with a wildcard.
+      // For example:
+      // <T> void method(Gen<T> t) {}
+      // Gen<?> x;
+      // method(x); // this method is called when checking this method call
+      // And also when checking lambdas
+
+      boolean subtypeHasAnno = subtype.getAnnotationInHierarchy(currentTop) != null;
+      boolean supertypeHasAnno = supertype.getAnnotationInHierarchy(currentTop) != null;
+
+      if (subtypeHasAnno && supertypeHasAnno) {
+        // if both have primary annotations then just check the primary annotations
+        // as the bounds are the same
+        return isPrimarySubtype(subtype, supertype);
+
+      } else if (!subtypeHasAnno && !supertypeHasAnno && areEqualInHierarchy(subtype, supertype)) {
+        // TODO: wildcard capture conversion
+        // Two unannotated uses of wildcard types are the same type
+        return true;
+      }
+    }
+
+    if (TypesUtils.isErasedSubtype(
+        subtype.getExtendsBound().getUnderlyingType(),
+        supertype.getUnderlyingType(),
+        subtype.atypeFactory.types)) {
+      return isSubtype(subtype.getExtendsBound(), supertype, currentTop);
+    }
+    // TODO: subtype is a wildcard that should have been captured, so just check the primary
+    // annotations.
+    AnnotationMirror subAnno = subtype.getEffectiveAnnotationInHierarchy(currentTop);
+    AnnotationMirror superAnno = supertype.getAnnotationInHierarchy(currentTop);
+    return qualifierHierarchy.isSubtype(subAnno, superAnno);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java
new file mode 100644
index 0000000..2e8ec5a
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java
@@ -0,0 +1,233 @@
+package org.checkerframework.framework.type;
+
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import com.sun.tools.javac.code.Symbol;
+import java.util.List;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
+import org.checkerframework.framework.util.element.ClassTypeParamApplier;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.ErrorTypeKindException;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException;
+import org.checkerframework.framework.util.element.MethodApplier;
+import org.checkerframework.framework.util.element.MethodTypeParamApplier;
+import org.checkerframework.framework.util.element.ParamApplier;
+import org.checkerframework.framework.util.element.SuperTypeApplier;
+import org.checkerframework.framework.util.element.TypeDeclarationApplier;
+import org.checkerframework.framework.util.element.TypeVarUseApplier;
+import org.checkerframework.framework.util.element.VariableApplier;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * Utility methods for adding the annotations that are stored in an Element to the type that
+ * represents that element (or a use of that Element).
+ *
+ * <p>In a way, this class is a hack: the Type representation for the Elements should contain all
+ * annotations that we want. However, due to <a
+ * href="http://mail.openjdk.java.net/pipermail/type-annotations-dev/2013-December/001449.html">javac
+ * bugs</a> decoding the type annotations from the Element is necessary.
+ *
+ * <p>Even once these bugs are fixed, this class might be useful: in TypesIntoElements it is easy to
+ * add additional annotations to the element and have them stored in the bytecode by the compiler.
+ * It would be more work (and might not work in the end) to instead modify the Type directly. The
+ * interaction between TypeFromElement and TypesIntoElements allows us to write the defaulted
+ * annotations into the Element and have them read later by other parts.
+ */
+public class ElementAnnotationApplier {
+
+  /**
+   * Add all of the relevant annotations stored in Element to type. This includes both top-level
+   * primary annotations and nested annotations. For the most part the TypeAnnotationPosition of the
+   * element annotations are used to locate the annotation in the right AnnotatedTypeMirror location
+   * though the individual applier classes may have special rules (such as those for upper and lower
+   * bounds and intersections).
+   *
+   * <p>Note: Element annotations come from two sources.
+   *
+   * <ol>
+   *   <li>Annotations found on elements may represent those in source code or bytecode; these are
+   *       added to the element by the compiler.
+   *   <li>The annotations may also represent those that were inferred or defaulted by the Checker
+   *       Framework after a previous call to this method. The Checker Framework will store any
+   *       annotations on declarations back into the elements that represent them (see {@link
+   *       org.checkerframework.framework.type.TypesIntoElements}). Subsequent, calls to apply will
+   *       encounter these annotations on the provided element.
+   * </ol>
+   *
+   * Note: This is not the ONLY place that annotations are explicitly added to types. See {@link
+   * org.checkerframework.framework.type.TypeFromTree}.
+   *
+   * @param type the type to which we wish to apply the element's annotations
+   * @param element an element that possibly contains annotations
+   * @param typeFactory the typeFactory used to create the given type
+   */
+  public static void apply(
+      final AnnotatedTypeMirror type,
+      final Element element,
+      final AnnotatedTypeFactory typeFactory) {
+    try {
+      try {
+        applyInternal(type, element, typeFactory);
+      } catch (UnexpectedAnnotationLocationException e) {
+        reportInvalidLocation(element, typeFactory);
+      }
+      // Also copy annotations from type parameters to their uses.
+      new TypeVarAnnotator().visit(type, typeFactory);
+    } catch (ErrorTypeKindException e) {
+      // Do nothing if an ERROR TypeKind was found.
+      // This is triggered by Issue #244.
+    }
+  }
+
+  /** Issues an "invalid.annotation.location.bytecode warning. */
+  private static void reportInvalidLocation(Element element, AnnotatedTypeFactory typeFactory) {
+    Element report = element;
+    if (element.getEnclosingElement().getKind() == ElementKind.METHOD) {
+      report = element.getEnclosingElement();
+    }
+    // There's a bug in Java 8 compiler that creates bad bytecode such that an
+    // annotation on a lambda parameter is applied to a method parameter. (This bug has
+    // been fixed in Java 9.) If this happens, then the location could refer to a
+    // location, such as a type argument, that doesn't exist. Since Java 8 bytecode
+    // might be on the classpath, catch this exception and ignore the type.
+    // TODO: Issue an error if this annotation is from Java 9+ bytecode.
+    if (!typeFactory.checker.hasOption("ignoreInvalidAnnotationLocations")) {
+      typeFactory.checker.reportWarning(
+          element, "invalid.annotation.location.bytecode", ElementUtils.getQualifiedName(report));
+    }
+  }
+
+  /** Same as apply except that annotations aren't copied from type parameter declarations. */
+  private static void applyInternal(
+      final AnnotatedTypeMirror type, final Element element, final AnnotatedTypeFactory typeFactory)
+      throws UnexpectedAnnotationLocationException {
+
+    if (element == null) {
+      throw new BugInCF("ElementAnnotationUtil.apply: element cannot be null");
+
+    } else if (TypeVarUseApplier.accepts(type, element)) {
+      TypeVarUseApplier.apply(type, element, typeFactory);
+
+    } else if (VariableApplier.accepts(type, element)) {
+      if (element.getKind() != ElementKind.LOCAL_VARIABLE) {
+        // For local variables we have the source code,
+        // so there is no need to look at the Element.
+        // This is needed to avoid a bug in the JDK:
+        // https://github.com/eisop/checker-framework/issues/14
+        VariableApplier.apply(type, element);
+      }
+
+    } else if (MethodApplier.accepts(type, element)) {
+      MethodApplier.apply(type, element, typeFactory);
+
+    } else if (TypeDeclarationApplier.accepts(type, element)) {
+      TypeDeclarationApplier.apply(type, element, typeFactory);
+
+    } else if (ClassTypeParamApplier.accepts(type, element)) {
+      ClassTypeParamApplier.apply((AnnotatedTypeVariable) type, element, typeFactory);
+
+    } else if (MethodTypeParamApplier.accepts(type, element)) {
+      MethodTypeParamApplier.apply((AnnotatedTypeVariable) type, element, typeFactory);
+
+    } else if (ParamApplier.accepts(type, element)) {
+      ParamApplier.apply(type, element, typeFactory);
+
+    } else if (isCaptureConvertedTypeVar(element)) {
+      // Types resulting from capture conversion cannot have explicit annotations
+
+    } else {
+      throw new BugInCF(
+          "ElementAnnotationUtil.apply: illegal argument: "
+              + element
+              + " ["
+              + element.getKind()
+              + "]"
+              + " with type "
+              + type);
+    }
+  }
+
+  /**
+   * Annotate the list of supertypes using the annotations on the TypeElement representing a class
+   * or interface.
+   *
+   * @param supertypes types representing supertype declarations of TypeElement
+   * @param subtypeElement an element representing the declaration of the class which is a subtype
+   *     of supertypes
+   */
+  public static void annotateSupers(
+      List<AnnotatedDeclaredType> supertypes, TypeElement subtypeElement) {
+    try {
+      SuperTypeApplier.annotateSupers(supertypes, subtypeElement);
+    } catch (UnexpectedAnnotationLocationException e) {
+      reportInvalidLocation(subtypeElement, supertypes.get(0).atypeFactory);
+    }
+  }
+
+  /**
+   * Helper method to get the lambda tree for ParamApplier. Ideally, this method would be located in
+   * ElementAnnotationUtil but since AnnotatedTypeFactory.declarationFromElement is protected, it
+   * has been placed here.
+   *
+   * @param varEle the element that may represent a lambda's parameter
+   * @return a LambdaExpressionTree if the varEle represents a parameter in a lambda expression,
+   *     otherwise null
+   */
+  public static Pair<VariableTree, LambdaExpressionTree> getParamAndLambdaTree(
+      VariableElement varEle, AnnotatedTypeFactory typeFactory) {
+    VariableTree paramDecl = (VariableTree) typeFactory.declarationFromElement(varEle);
+
+    if (paramDecl != null) {
+      final Tree parentTree = typeFactory.getPath(paramDecl).getParentPath().getLeaf();
+      if (parentTree != null && parentTree.getKind() == Kind.LAMBDA_EXPRESSION) {
+        return Pair.of(paramDecl, (LambdaExpressionTree) parentTree);
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Was the type passed in generated by capture conversion.
+   *
+   * @param element the element which type represents
+   * @return true if type was generated via capture conversion false otherwise
+   */
+  private static boolean isCaptureConvertedTypeVar(final Element element) {
+    final Element enclosure = element.getEnclosingElement();
+    return (((Symbol) enclosure).kind == com.sun.tools.javac.code.Kinds.Kind.NIL);
+  }
+
+  /**
+   * Annotates uses of type variables with annotation written explicitly on the type parameter
+   * declaration and/or its upper bound.
+   */
+  private static class TypeVarAnnotator extends AnnotatedTypeScanner<Void, AnnotatedTypeFactory> {
+    @Override
+    public Void visitTypeVariable(AnnotatedTypeVariable type, AnnotatedTypeFactory factory) {
+      TypeParameterElement tpelt = (TypeParameterElement) type.getUnderlyingType().asElement();
+
+      if (type.getAnnotations().isEmpty()
+          && type.getUpperBound().getAnnotations().isEmpty()
+          && tpelt.getEnclosingElement().getKind() != ElementKind.TYPE_PARAMETER) {
+        try {
+          ElementAnnotationApplier.applyInternal(type, tpelt, factory);
+        } catch (UnexpectedAnnotationLocationException e) {
+          // The above is the second call to applyInternal on this type and element, so any errors
+          // were already reported by the first call. (See the only use of this class.)
+        }
+      }
+      return super.visitTypeVariable(type, factory);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/ElementQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/ElementQualifierHierarchy.java
new file mode 100644
index 0000000..02cff6a
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/ElementQualifierHierarchy.java
@@ -0,0 +1,253 @@
+package org.checkerframework.framework.type;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.util.DefaultQualifierKindHierarchy;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.framework.util.QualifierKindHierarchy;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypeSystemError;
+
+/**
+ * A {@link QualifierHierarchy} where qualifiers may be represented by annotations with elements.
+ *
+ * <p>ElementQualifierHierarchy uses a {@link QualifierKindHierarchy} to model the relationships
+ * between qualifiers. (By contrast, {@link MostlyNoElementQualifierHierarchy} uses the {@link
+ * QualifierKindHierarchy} to implement {@code isSubtype}, {@code leastUpperBound}, and {@code
+ * greatestLowerBound} methods for qualifiers without elements.)
+ *
+ * <p>Subclasses can override {@link #createQualifierKindHierarchy(Collection)} to return a subclass
+ * of QualifierKindHierarchy.
+ */
+@AnnotatedFor("nullness")
+public abstract class ElementQualifierHierarchy implements QualifierHierarchy {
+
+  /** {@link org.checkerframework.javacutil.ElementUtils}. */
+  private Elements elements;
+
+  /** {@link QualifierKindHierarchy}. */
+  protected final QualifierKindHierarchy qualifierKindHierarchy;
+
+  // The following fields duplicate information in qualifierKindHierarchy, but using
+  // AnnotationMirrors instead of QualifierKinds.
+
+  /** A mapping from top QualifierKinds to their corresponding AnnotationMirror. */
+  protected final Map<QualifierKind, AnnotationMirror> topsMap;
+
+  /** The set of top annotation mirrors. */
+  protected final Set<AnnotationMirror> tops;
+
+  /** A mapping from bottom QualifierKinds to their corresponding AnnotationMirror. */
+  protected final Map<QualifierKind, AnnotationMirror> bottomsMap;
+
+  /** The set of bottom annotation mirrors. */
+  protected final Set<AnnotationMirror> bottoms;
+
+  /**
+   * A mapping from QualifierKind to AnnotationMirror for all qualifiers whose annotations do not
+   * have elements.
+   */
+  protected final Map<QualifierKind, AnnotationMirror> kindToElementlessQualifier;
+
+  /**
+   * Creates a ElementQualifierHierarchy from the given classes.
+   *
+   * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+   * @param elements element utils
+   */
+  protected ElementQualifierHierarchy(
+      Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+    this.elements = elements;
+    this.qualifierKindHierarchy = createQualifierKindHierarchy(qualifierClasses);
+
+    this.topsMap = Collections.unmodifiableMap(createTopsMap());
+    this.tops = AnnotationUtils.createUnmodifiableAnnotationSet(topsMap.values());
+
+    this.bottomsMap = Collections.unmodifiableMap(createBottomsMap());
+    this.bottoms = AnnotationUtils.createUnmodifiableAnnotationSet(bottomsMap.values());
+
+    this.kindToElementlessQualifier = createElementlessQualifierMap();
+  }
+
+  @Override
+  public boolean isValid() {
+    for (AnnotationMirror top : tops) {
+      // This throws an error if poly is a qualifier that has an element.
+      getPolymorphicAnnotation(top);
+    }
+    return true;
+  }
+
+  /**
+   * Create the {@link QualifierKindHierarchy}. (Subclasses may override to return a subclass of
+   * QualifierKindHierarchy.)
+   *
+   * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+   * @return the newly created qualifier kind hierarchy
+   */
+  protected QualifierKindHierarchy createQualifierKindHierarchy(
+      @UnderInitialization ElementQualifierHierarchy this,
+      Collection<Class<? extends Annotation>> qualifierClasses) {
+    return new DefaultQualifierKindHierarchy(qualifierClasses);
+  }
+
+  /**
+   * Creates a mapping from QualifierKind to AnnotationMirror for all qualifiers whose annotations
+   * do not have elements.
+   *
+   * @return the mapping
+   */
+  @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"})
+  protected Map<QualifierKind, AnnotationMirror> createElementlessQualifierMap(
+      @UnderInitialization ElementQualifierHierarchy this) {
+    Map<QualifierKind, AnnotationMirror> quals = new TreeMap<>();
+    for (QualifierKind kind : qualifierKindHierarchy.allQualifierKinds()) {
+      if (!kind.hasElements()) {
+        quals.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass()));
+      }
+    }
+    return Collections.unmodifiableMap(quals);
+  }
+
+  /**
+   * Creates a mapping from QualifierKind to AnnotationMirror, where the QualifierKind is top and
+   * the AnnotationMirror is top in their respective hierarchies.
+   *
+   * <p>This implementation works if the top annotation has no elements, or if it has elements,
+   * provides a default, and that default is the top. Otherwise, subclasses must override this.
+   *
+   * @return a mapping from top QualifierKind to top AnnotationMirror
+   */
+  @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"})
+  protected Map<QualifierKind, AnnotationMirror> createTopsMap(
+      @UnderInitialization ElementQualifierHierarchy this) {
+    Map<QualifierKind, AnnotationMirror> topsMap = new TreeMap<>();
+    for (QualifierKind kind : qualifierKindHierarchy.getTops()) {
+      topsMap.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass()));
+    }
+    return topsMap;
+  }
+
+  /**
+   * Creates a mapping from QualifierKind to AnnotationMirror, where the QualifierKind is bottom and
+   * the AnnotationMirror is bottom in their respective hierarchies.
+   *
+   * <p>This implementation works if the bottom annotation has no elements, or if it has elements,
+   * provides a default, and that default is the bottom. Otherwise, subclasses must override this.
+   *
+   * @return a mapping from bottom QualifierKind to bottom AnnotationMirror
+   */
+  @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"})
+  protected Map<QualifierKind, AnnotationMirror> createBottomsMap(
+      @UnderInitialization ElementQualifierHierarchy this) {
+    Map<QualifierKind, AnnotationMirror> bottomsMap = new TreeMap<>();
+    for (QualifierKind kind : qualifierKindHierarchy.getBottoms()) {
+      bottomsMap.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass()));
+    }
+    return bottomsMap;
+  }
+
+  /**
+   * Returns the qualifier kind for the given annotation.
+   *
+   * @param anno annotation mirror
+   * @return the qualifier kind for the given annotation
+   */
+  protected QualifierKind getQualifierKind(AnnotationMirror anno) {
+    String name = AnnotationUtils.annotationName(anno);
+    return getQualifierKind(name);
+  }
+
+  /**
+   * Returns the qualifier kind for the annotation with the canonical name {@code name}.
+   *
+   * @param name fully qualified annotation name
+   * @return the qualifier kind for the annotation named {@code name}
+   */
+  protected QualifierKind getQualifierKind(@CanonicalName String name) {
+    QualifierKind kind = qualifierKindHierarchy.getQualifierKind(name);
+    if (kind == null) {
+      throw new BugInCF("QualifierKind %s not in hierarchy", name);
+    }
+    return kind;
+  }
+
+  @Override
+  public Set<? extends AnnotationMirror> getTopAnnotations() {
+    return tops;
+  }
+
+  @Override
+  public AnnotationMirror getTopAnnotation(AnnotationMirror start) {
+    QualifierKind kind = getQualifierKind(start);
+    @SuppressWarnings("nullness:assignment") // All tops are a key for topsMap.
+    @NonNull AnnotationMirror result = topsMap.get(kind.getTop());
+    return result;
+  }
+
+  @Override
+  public Set<? extends AnnotationMirror> getBottomAnnotations() {
+    return bottoms;
+  }
+
+  @Override
+  public @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) {
+    QualifierKind polyKind = getQualifierKind(start).getPolymorphic();
+    if (polyKind == null) {
+      return null;
+    }
+    AnnotationMirror poly = kindToElementlessQualifier.get(polyKind);
+    if (poly == null) {
+      throw new TypeSystemError(
+          "Poly %s has an element. Override ElementQualifierHierarchy#getPolymorphicAnnotation.",
+          polyKind);
+    }
+    return poly;
+  }
+
+  @Override
+  public boolean isPolymorphicQualifier(AnnotationMirror qualifier) {
+    return getQualifierKind(qualifier).isPoly();
+  }
+
+  @Override
+  public AnnotationMirror getBottomAnnotation(AnnotationMirror start) {
+    QualifierKind kind = getQualifierKind(start);
+    @SuppressWarnings("nullness:assignment") // All bottoms are keys for bottomsMap.
+    @NonNull AnnotationMirror result = bottomsMap.get(kind.getBottom());
+    return result;
+  }
+
+  @Override
+  public @Nullable AnnotationMirror findAnnotationInSameHierarchy(
+      Collection<? extends AnnotationMirror> annos, AnnotationMirror annotationMirror) {
+    QualifierKind kind = getQualifierKind(annotationMirror);
+    for (AnnotationMirror candidate : annos) {
+      QualifierKind candidateKind = getQualifierKind(candidate);
+      if (candidateKind.isInSameHierarchyAs(kind)) {
+        return candidate;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public @Nullable AnnotationMirror findAnnotationInHierarchy(
+      Collection<? extends AnnotationMirror> annos, AnnotationMirror top) {
+    return findAnnotationInSameHierarchy(annos, top);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java b/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java
new file mode 100644
index 0000000..70499f5
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java
@@ -0,0 +1,89 @@
+package org.checkerframework.framework.type;
+
+import org.checkerframework.checker.interning.qual.EqualsMethod;
+import org.checkerframework.framework.type.visitor.EquivalentAtmComboScanner;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Compares two annotated type mirrors for structural equality using only the primary annotations
+ * and underlying types of the two input types and their component types. Note, this leaves out
+ * other fields specific to some AnnotatedTypeMirrors (like directSupertypes, wasRaw,
+ * isUninferredTypeArgument etc...). Ideally, both EqualityAtmComparer and HashcodeAtmVisitor would
+ * visit relevant fields.
+ *
+ * <p>This class is used by AnnotatedTypeMirror#equals
+ *
+ * <p>This class should be kept synchronized with HashcodeAtmVisitor.
+ *
+ * @see org.checkerframework.framework.type.HashcodeAtmVisitor
+ *     <p>Unlike HashcodeAtmVisitor this class is intended to be overridden.
+ */
+public class EqualityAtmComparer extends EquivalentAtmComboScanner<Boolean, Void> {
+
+  /**
+   * Called when a visit method is called on two types that do not have the same class, i.e. when
+   * !type1.getClass().equals(type2.getClass())
+   */
+  @Override
+  protected String defaultErrorMessage(
+      AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void v) {
+    throw new UnsupportedOperationException(
+        StringsPlume.joinLines(
+            "Comparing two different subclasses of AnnotatedTypeMirror.",
+            "type1=" + type1,
+            "type2=" + type2));
+  }
+
+  /** Return true if type1 and type2 have equivalent sets of annotations. */
+  protected boolean arePrimeAnnosEqual(
+      final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
+    return AnnotationUtils.areSame(type1.getAnnotations(), type2.getAnnotations());
+  }
+
+  /**
+   * Return true if the twe types are the same.
+   *
+   * @param type1 the first type to compare
+   * @param type2 the second type to compare
+   * @return true if the twe types are the same
+   */
+  @EqualsMethod // to make Interning Checker permit the == comparison
+  protected boolean compare(final AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) {
+    if (type1 == type2) {
+      return true;
+    }
+
+    if (type1 == null || type2 == null) {
+      return false;
+    }
+
+    @SuppressWarnings("TypeEquals") // TODO
+    boolean sameUnderlyingType = type1.getUnderlyingType().equals(type2.getUnderlyingType());
+    return sameUnderlyingType && arePrimeAnnosEqual(type1, type2);
+  }
+
+  @SuppressWarnings("interning:not.interned")
+  @Override
+  protected Boolean scanWithNull(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) {
+    // one of them should be null, therefore they are only equal if the other is null
+    return type1 == type2;
+  }
+
+  @Override
+  protected Boolean scan(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void v) {
+    return compare(type1, type2) && reduce(true, super.scan(type1, type2, v));
+  }
+
+  /** Used to combine the results from component types or a type and its component types. */
+  @Override
+  protected Boolean reduce(Boolean r1, Boolean r2) {
+    if (r1 == null) {
+      return r2;
+    } else if (r2 == null) {
+      return r1;
+    } else {
+      return r1 && r2;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java
new file mode 100644
index 0000000..1e70414
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java
@@ -0,0 +1,2714 @@
+package org.checkerframework.framework.type;
+
+import com.google.common.collect.Ordering;
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import java.lang.annotation.Annotation;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.wholeprograminference.WholeProgramInferenceImplementation;
+import org.checkerframework.common.wholeprograminference.WholeProgramInferenceJavaParserStorage;
+import org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesStorage;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.dataflow.analysis.Analysis.BeforeOrAfter;
+import org.checkerframework.dataflow.analysis.AnalysisResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.ControlFlowGraph;
+import org.checkerframework.dataflow.cfg.UnderlyingAST;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGStatement;
+import org.checkerframework.dataflow.cfg.node.AssignmentNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
+import org.checkerframework.dataflow.cfg.node.ReturnNode;
+import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
+import org.checkerframework.dataflow.cfg.visualize.DOTCFGVisualizer;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFAbstractStore;
+import org.checkerframework.framework.flow.CFAbstractTransfer;
+import org.checkerframework.framework.flow.CFAbstractValue;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFCFGBuilder;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.EnsuresQualifier;
+import org.checkerframework.framework.qual.EnsuresQualifierIf;
+import org.checkerframework.framework.qual.MonotonicQualifier;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+import org.checkerframework.framework.qual.RequiresQualifier;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.poly.DefaultQualifierPolymorphism;
+import org.checkerframework.framework.type.poly.QualifierPolymorphism;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.type.typeannotator.DefaultForTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.DefaultQualifierForUseTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.IrrelevantTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator;
+import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.Contract;
+import org.checkerframework.framework.util.ContractsFromMethod;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.framework.util.StringToJavaExpression;
+import org.checkerframework.framework.util.defaults.QualifierDefaults;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesTreeAnnotator;
+import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.CollectionUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypeSystemError;
+import org.checkerframework.javacutil.TypesUtils;
+import org.checkerframework.javacutil.UserError;
+import org.plumelib.reflection.Signatures;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.SystemPlume;
+import scenelib.annotations.el.AField;
+import scenelib.annotations.el.AMethod;
+
+/**
+ * A factory that extends {@link AnnotatedTypeFactory} to optionally use flow-sensitive qualifier
+ * inference.
+ *
+ * <p>It also adds other features: qualifier polymorphism, default annotations via {@link
+ * DefaultFor}, user-specified defaults via {@link DefaultQualifier}, standardization via {@link
+ * DependentTypesHelper}, etc. Those features, and {@link #addComputedTypeAnnotations} (other than
+ * the part related to flow-sensitivity), could and should be in the superclass {@link
+ * AnnotatedTypeFactory}; it is not clear why they are defined in this class.
+ */
+public abstract class GenericAnnotatedTypeFactory<
+        Value extends CFAbstractValue<Value>,
+        Store extends CFAbstractStore<Value, Store>,
+        TransferFunction extends CFAbstractTransfer<Value, Store, TransferFunction>,
+        FlowAnalysis extends CFAbstractAnalysis<Value, Store, TransferFunction>>
+    extends AnnotatedTypeFactory {
+
+  /** Should use flow by default. */
+  protected static boolean flowByDefault = true;
+
+  /** To cache the supported monotonic type qualifiers. */
+  private Set<Class<? extends Annotation>> supportedMonotonicQuals;
+
+  /** to annotate types based on the given tree */
+  protected TypeAnnotator typeAnnotator;
+
+  /** for use in addAnnotationsFromDefaultForType */
+  private DefaultQualifierForUseTypeAnnotator defaultQualifierForUseTypeAnnotator;
+
+  /** for use in addAnnotationsFromDefaultForType */
+  private DefaultForTypeAnnotator defaultForTypeAnnotator;
+
+  /** to annotate types based on the given un-annotated types */
+  protected TreeAnnotator treeAnnotator;
+
+  /** to handle any polymorphic types */
+  protected QualifierPolymorphism poly;
+
+  /** to handle defaults specified by the user */
+  protected QualifierDefaults defaults;
+
+  /** To handle dependent type annotations and contract expressions. */
+  protected DependentTypesHelper dependentTypesHelper;
+
+  /** to handle method pre- and postconditions */
+  protected ContractsFromMethod contractsUtils;
+
+  /**
+   * The Java types on which users may write this type system's type annotations. null means no
+   * restrictions. Arrays are handled by separate field {@code #arraysAreRelevant}.
+   *
+   * <p>If the relevant type is generic, this contains its erasure.
+   *
+   * <p>Although a {@code Class<?>} object exists for every element, this does not contain those
+   * {@code Class<?>} objects because the elements will be compared to TypeMirrors for which Class
+   * objects may not exist (they might not be on the classpath).
+   */
+  public @Nullable Set<TypeMirror> relevantJavaTypes;
+
+  /**
+   * Whether users may write type annotations on arrays. Ignored unless {@link #relevantJavaTypes}
+   * is non-null.
+   */
+  boolean arraysAreRelevant = false;
+
+  // Flow related fields
+
+  /**
+   * Should use flow-sensitive type refinement analysis? This value can be changed when an
+   * AnnotatedTypeMirror without annotations from data flow is required.
+   *
+   * @see #getAnnotatedTypeLhs(Tree)
+   */
+  private boolean useFlow;
+
+  /** Is this type factory configured to use flow-sensitive type refinement? */
+  private final boolean everUseFlow;
+
+  /**
+   * Should the local variable default annotation be applied to type variables?
+   *
+   * <p>It is initialized to true if data flow is used by the checker. It is set to false when
+   * getting the assignment context for type argument inference.
+   *
+   * @see GenericAnnotatedTypeFactory#getAnnotatedTypeLhsNoTypeVarDefault
+   */
+  private boolean shouldDefaultTypeVarLocals;
+
+  /**
+   * Elements representing variables for which the type of the initializer is being determined in
+   * order to apply qualifier parameter defaults.
+   *
+   * <p>Local variables with a qualifier parameter get their declared type from the type of their
+   * initializer. Sometimes the initializer's type depends on the type of the variable, such as
+   * during type variable inference or when a variable is used in its own initializer as in "Object
+   * o = (o = null)". This creates a circular dependency resulting in infinite recursion. To prevent
+   * this, variables in this set should not be typed based on their initializer, but by using normal
+   * defaults.
+   *
+   * <p>This set should only be modified in
+   * GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults which clears variables
+   * after computing their initializer types.
+   *
+   * @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults
+   */
+  private Set<VariableElement> variablesUnderInitialization;
+
+  /**
+   * Caches types of initializers for local variables with a qualifier parameter, so that they
+   * aren't computed each time the type of a variable is looked up.
+   *
+   * @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults
+   */
+  private Map<Tree, AnnotatedTypeMirror> initializerCache;
+
+  /**
+   * Should the analysis assume that side effects to a value can change the type of aliased
+   * references?
+   *
+   * <p>For many type systems, once a local variable's type is refined, side effects to the
+   * variable's value do not change the variable's type annotations. For some type systems, a side
+   * effect to the value could change them; set this field to true.
+   */
+  // Not final so that subclasses can set it.
+  public boolean sideEffectsUnrefineAliases = false;
+
+  /**
+   * True if this checker either has one or more subcheckers, or if this checker is a subchecker.
+   * False otherwise. All uses of the methods {@link #addSharedCFGForTree(Tree, ControlFlowGraph)}
+   * and {@link #getSharedCFGForTree(Tree)} should be guarded by a check that this is true.
+   */
+  public final boolean hasOrIsSubchecker;
+
+  /** An empty store. */
+  // Set in postInit only
+  protected Store emptyStore;
+
+  // Set in postInit only
+  protected FlowAnalysis analysis;
+
+  // Set in postInit only
+  protected TransferFunction transfer;
+
+  // Maintain for every class the store that is used when we analyze initialization code
+  protected Store initializationStore;
+
+  // Maintain for every class the store that is used when we analyze static initialization code
+  protected Store initializationStaticStore;
+
+  /**
+   * Caches for {@link AnalysisResult#runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput,
+   * IdentityHashMap, Map)}. This cache is enabled if {@link #shouldCache} is true. The cache size
+   * is derived from {@link #getCacheSize()}.
+   *
+   * @see AnalysisResult#runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput,
+   *     IdentityHashMap, Map)
+   */
+  protected final Map<
+          TransferInput<Value, Store>, IdentityHashMap<Node, TransferResult<Value, Store>>>
+      flowResultAnalysisCaches;
+
+  /**
+   * Subcheckers share the same ControlFlowGraph for each analyzed code statement. This maps from
+   * code statements to the shared control flow graphs. This map is null in all subcheckers (i.e.
+   * any checker for which getParentChecker() returns non-null). This map is also unused (and
+   * therefore null) for a checker with no subcheckers with which it can share CFGs.
+   *
+   * <p>The initial capacity of the map is set by {@link #getCacheSize()}.
+   */
+  protected @Nullable Map<Tree, ControlFlowGraph> subcheckerSharedCFG;
+
+  /**
+   * If true, {@link #setRoot(CompilationUnitTree)} should clear the {@link #subcheckerSharedCFG}
+   * map, freeing memory.
+   *
+   * <p>For each compilation unit, all the subcheckers run first and finally the ultimate parent
+   * checker runs. The ultimate parent checker's {@link #setRoot(CompilationUnitTree)} (the last to
+   * run) sets this field to true.
+   *
+   * <p>In first subchecker to run for the next compilation unit, {@link
+   * #setRoot(CompilationUnitTree)} observes the true value, clears the {@link #subcheckerSharedCFG}
+   * map, and sets this field back to false. That first subchecker will create a CFG and re-populate
+   * the map, and subsequent subcheckers will use the map.
+   */
+  protected boolean shouldClearSubcheckerSharedCFGs = true;
+
+  /**
+   * Creates a type factory. Its compilation unit is not yet set.
+   *
+   * @param checker the checker to which this type factory belongs
+   * @param useFlow whether flow analysis should be performed
+   */
+  protected GenericAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) {
+    super(checker);
+
+    this.everUseFlow = useFlow;
+    this.shouldDefaultTypeVarLocals = useFlow;
+    this.useFlow = useFlow;
+
+    this.variablesUnderInitialization = new HashSet<>();
+    this.scannedClasses = new HashMap<>();
+    this.flowResult = null;
+    this.regularExitStores = null;
+    this.exceptionalExitStores = null;
+    this.methodInvocationStores = null;
+    this.returnStatementStores = null;
+
+    this.initializationStore = null;
+    this.initializationStaticStore = null;
+
+    this.cfgVisualizer = createCFGVisualizer();
+
+    if (shouldCache) {
+      int cacheSize = getCacheSize();
+      flowResultAnalysisCaches = CollectionUtils.createLRUCache(cacheSize);
+      initializerCache = CollectionUtils.createLRUCache(cacheSize);
+    } else {
+      flowResultAnalysisCaches = null;
+      initializerCache = null;
+    }
+
+    RelevantJavaTypes relevantJavaTypesAnno =
+        checker.getClass().getAnnotation(RelevantJavaTypes.class);
+    if (relevantJavaTypesAnno == null) {
+      this.relevantJavaTypes = null;
+      this.arraysAreRelevant = true;
+    } else {
+      Types types = getChecker().getTypeUtils();
+      Elements elements = getElementUtils();
+      Class<?>[] classes = relevantJavaTypesAnno.value();
+      this.relevantJavaTypes = new HashSet<>(CollectionsPlume.mapCapacity(classes.length));
+      this.arraysAreRelevant = false;
+      for (Class<?> clazz : classes) {
+        if (clazz == Object[].class) {
+          arraysAreRelevant = true;
+        } else if (clazz.isArray()) {
+          throw new TypeSystemError(
+              "Don't use arrays other than Object[] in @RelevantJavaTypes on "
+                  + this.getClass().getSimpleName());
+        } else {
+          TypeMirror relevantType = TypesUtils.typeFromClass(clazz, types, elements);
+          relevantJavaTypes.add(types.erasure(relevantType));
+        }
+      }
+    }
+
+    contractsUtils = createContractsFromMethod();
+
+    hasOrIsSubchecker =
+        !this.getChecker().getSubcheckers().isEmpty()
+            || this.getChecker().getParentChecker() != null;
+
+    // Every subclass must call postInit, but it must be called after
+    // all other initialization is finished.
+  }
+
+  @Override
+  protected void postInit() {
+    super.postInit();
+
+    this.dependentTypesHelper = createDependentTypesHelper();
+    this.defaults = createAndInitQualifierDefaults();
+    this.treeAnnotator = createTreeAnnotator();
+    this.typeAnnotator = createTypeAnnotator();
+    this.defaultQualifierForUseTypeAnnotator = createDefaultForUseTypeAnnotator();
+    this.defaultForTypeAnnotator = createDefaultForTypeAnnotator();
+
+    this.poly = createQualifierPolymorphism();
+
+    this.analysis = createFlowAnalysis(new ArrayList<>());
+    this.transfer = analysis.getTransferFunction();
+    this.emptyStore = analysis.createEmptyStore(transfer.usesSequentialSemantics());
+
+    this.parseAnnotationFiles();
+  }
+
+  /**
+   * Performs flow-sensitive type refinement on {@code classTree} if this type factory is configured
+   * to do so.
+   *
+   * @param classTree tree on which to perform flow-sensitive type refinement
+   */
+  @Override
+  public void preProcessClassTree(ClassTree classTree) {
+    if (this.everUseFlow) {
+      checkAndPerformFlowAnalysis(classTree);
+    }
+  }
+
+  /**
+   * Creates a type factory. Its compilation unit is not yet set.
+   *
+   * @param checker the checker to which this type factory belongs
+   */
+  protected GenericAnnotatedTypeFactory(BaseTypeChecker checker) {
+    this(checker, flowByDefault);
+  }
+
+  @Override
+  public void setRoot(@Nullable CompilationUnitTree root) {
+    super.setRoot(root);
+    this.scannedClasses.clear();
+    this.flowResult = null;
+    this.regularExitStores = null;
+    this.exceptionalExitStores = null;
+    this.methodInvocationStores = null;
+    this.returnStatementStores = null;
+    this.initializationStore = null;
+    this.initializationStaticStore = null;
+
+    if (shouldCache) {
+      this.flowResultAnalysisCaches.clear();
+      this.initializerCache.clear();
+      this.defaultQualifierForUseTypeAnnotator.clearCache();
+
+      if (this.checker.getParentChecker() == null) {
+        // This is an ultimate parent checker, so after it runs the shared CFG it is using
+        // will no longer be needed, and can be cleared.
+        this.shouldClearSubcheckerSharedCFGs = true;
+        if (this.checker.getSubcheckers().isEmpty()) {
+          // If this checker has no subcheckers, then any maps that are currently
+          // being maintained should be cleared right away.
+          clearSharedCFG(this);
+        }
+      } else {
+        GenericAnnotatedTypeFactory<?, ?, ?, ?> ultimateParentATF =
+            this.checker.getUltimateParentChecker().getTypeFactory();
+        clearSharedCFG(ultimateParentATF);
+      }
+    }
+  }
+
+  /**
+   * Clears the caches associated with the shared CFG for the given type factory, if it is safe to
+   * do so.
+   *
+   * @param factory a type factory
+   */
+  private void clearSharedCFG(GenericAnnotatedTypeFactory<?, ?, ?, ?> factory) {
+    if (factory.shouldClearSubcheckerSharedCFGs) {
+      // This is the first subchecker running in a group that share CFGs, so it must clear its
+      // ultimate parent's shared CFG before adding a new shared CFG.
+      factory.shouldClearSubcheckerSharedCFGs = false;
+      if (factory.subcheckerSharedCFG != null) {
+        factory.subcheckerSharedCFG.clear();
+      }
+      // The same applies to this map.
+      factory.artificialTreeToEnclosingElementMap.clear();
+    }
+  }
+
+  // **********************************************************************
+  // Factory Methods for the appropriate annotator classes
+  // **********************************************************************
+
+  /**
+   * Returns an immutable set of the <em>monotonic</em> type qualifiers supported by this checker.
+   *
+   * @return the monotonic type qualifiers supported this processor, or an empty set if none
+   * @see MonotonicQualifier
+   */
+  public final Set<Class<? extends Annotation>> getSupportedMonotonicTypeQualifiers() {
+    if (supportedMonotonicQuals == null) {
+      supportedMonotonicQuals = new HashSet<>();
+      for (Class<? extends Annotation> anno : getSupportedTypeQualifiers()) {
+        MonotonicQualifier mono = anno.getAnnotation(MonotonicQualifier.class);
+        if (mono != null) {
+          supportedMonotonicQuals.add(anno);
+        }
+      }
+    }
+    return supportedMonotonicQuals;
+  }
+
+  /**
+   * Returns a {@link TreeAnnotator} that adds annotations to a type based on the contents of a
+   * tree.
+   *
+   * <p>The default tree annotator is a {@link ListTreeAnnotator} of the following:
+   *
+   * <ol>
+   *   <li>{@link PropagationTreeAnnotator}: Propagates annotations from subtrees
+   *   <li>{@link LiteralTreeAnnotator}: Adds annotations based on {@link QualifierForLiterals}
+   *       meta-annotations
+   *   <li>{@link DependentTypesTreeAnnotator}: Adapts dependent annotations based on context
+   * </ol>
+   *
+   * <p>Subclasses may override this method to specify additional tree annotators, for example:
+   *
+   * <pre>
+   * new ListTreeAnnotator(super.createTreeAnnotator(), new KeyLookupTreeAnnotator(this));
+   * </pre>
+   *
+   * @return a tree annotator
+   */
+  protected TreeAnnotator createTreeAnnotator() {
+    List<TreeAnnotator> treeAnnotators = new ArrayList<>(2);
+    treeAnnotators.add(new PropagationTreeAnnotator(this));
+    treeAnnotators.add(new LiteralTreeAnnotator(this).addStandardLiteralQualifiers());
+    if (dependentTypesHelper.hasDependentAnnotations()) {
+      treeAnnotators.add(dependentTypesHelper.createDependentTypesTreeAnnotator());
+    }
+    return new ListTreeAnnotator(treeAnnotators);
+  }
+
+  /**
+   * Returns a {@link DefaultForTypeAnnotator} that adds annotations to a type based on the content
+   * of the type itself.
+   *
+   * <p>Subclass may override this method. The default type annotator is a {@link ListTypeAnnotator}
+   * of the following:
+   *
+   * <ol>
+   *   <li>{@link IrrelevantTypeAnnotator}: Adds top to types not listed in the {@code @}{@link
+   *       RelevantJavaTypes} annotation on the checker.
+   *   <li>{@link PropagationTypeAnnotator}: Propagates annotation onto wildcards.
+   * </ol>
+   *
+   * @return a type annotator
+   */
+  protected TypeAnnotator createTypeAnnotator() {
+    List<TypeAnnotator> typeAnnotators = new ArrayList<>(1);
+    if (relevantJavaTypes != null) {
+      typeAnnotators.add(
+          new IrrelevantTypeAnnotator(this, getQualifierHierarchy().getTopAnnotations()));
+    }
+    typeAnnotators.add(new PropagationTypeAnnotator(this));
+    return new ListTypeAnnotator(typeAnnotators);
+  }
+
+  /**
+   * Creates an {@link DefaultQualifierForUseTypeAnnotator}.
+   *
+   * @return a new {@link DefaultQualifierForUseTypeAnnotator}
+   */
+  protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() {
+    return new DefaultQualifierForUseTypeAnnotator(this);
+  }
+
+  /**
+   * Creates an {@link DefaultForTypeAnnotator}.
+   *
+   * @return a new {@link DefaultForTypeAnnotator}
+   */
+  protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() {
+    return new DefaultForTypeAnnotator(this);
+  }
+
+  /**
+   * Returns the {@link DefaultForTypeAnnotator}.
+   *
+   * @return the {@link DefaultForTypeAnnotator}
+   */
+  public DefaultForTypeAnnotator getDefaultForTypeAnnotator() {
+    return defaultForTypeAnnotator;
+  }
+
+  /**
+   * Returns the appropriate flow analysis class that is used for the org.checkerframework.dataflow
+   * analysis.
+   *
+   * <p>This implementation uses the checker naming convention to create the appropriate analysis.
+   * If no transfer function is found, it returns an instance of {@link CFAnalysis}.
+   *
+   * <p>Subclasses have to override this method to create the appropriate analysis if they do not
+   * follow the checker naming convention.
+   */
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  protected FlowAnalysis createFlowAnalysis(List<Pair<VariableElement, Value>> fieldValues) {
+
+    // Try to reflectively load the visitor.
+    Class<?> checkerClass = checker.getClass();
+
+    while (checkerClass != BaseTypeChecker.class) {
+      FlowAnalysis result =
+          BaseTypeChecker.invokeConstructorFor(
+              BaseTypeChecker.getRelatedClassName(checkerClass, "Analysis"),
+              new Class<?>[] {BaseTypeChecker.class, this.getClass(), List.class},
+              new Object[] {checker, this, fieldValues});
+      if (result != null) {
+        return result;
+      }
+      checkerClass = checkerClass.getSuperclass();
+    }
+
+    // If an analysis couldn't be loaded reflectively, return the default.
+    List<Pair<VariableElement, CFValue>> tmp =
+        CollectionsPlume.mapList(
+            (Pair<VariableElement, Value> fieldVal) ->
+                Pair.of(fieldVal.first, (CFValue) fieldVal.second),
+            fieldValues);
+    return (FlowAnalysis) new CFAnalysis(checker, (GenericAnnotatedTypeFactory) this, tmp);
+  }
+
+  /**
+   * Returns the appropriate transfer function that is used for the org.checkerframework.dataflow
+   * analysis.
+   *
+   * <p>This implementation uses the checker naming convention to create the appropriate transfer
+   * function. If no transfer function is found, it returns an instance of {@link CFTransfer}.
+   *
+   * <p>Subclasses have to override this method to create the appropriate transfer function if they
+   * do not follow the checker naming convention.
+   */
+  // A more precise type for the parameter would be FlowAnalysis, which
+  // is the type parameter bounded by the current parameter type CFAbstractAnalysis<Value, Store,
+  // TransferFunction>.
+  // However, we ran into issues in callers of the method if we used that type.
+  public TransferFunction createFlowTransferFunction(
+      CFAbstractAnalysis<Value, Store, TransferFunction> analysis) {
+
+    // Try to reflectively load the visitor.
+    Class<?> checkerClass = checker.getClass();
+
+    while (checkerClass != BaseTypeChecker.class) {
+      TransferFunction result =
+          BaseTypeChecker.invokeConstructorFor(
+              BaseTypeChecker.getRelatedClassName(checkerClass, "Transfer"),
+              new Class<?>[] {analysis.getClass()},
+              new Object[] {analysis});
+      if (result != null) {
+        return result;
+      }
+      checkerClass = checkerClass.getSuperclass();
+    }
+
+    // If a transfer function couldn't be loaded reflectively, return the default.
+    @SuppressWarnings("unchecked")
+    TransferFunction ret =
+        (TransferFunction)
+            new CFTransfer((CFAbstractAnalysis<CFValue, CFStore, CFTransfer>) analysis);
+    return ret;
+  }
+
+  /**
+   * Creates a {@link DependentTypesHelper} and returns it. Use {@link #getDependentTypesHelper} to
+   * access the value.
+   *
+   * @return a new {@link DependentTypesHelper}
+   */
+  protected DependentTypesHelper createDependentTypesHelper() {
+    return new DependentTypesHelper(this);
+  }
+
+  /**
+   * Returns the DependentTypesHelper.
+   *
+   * @return the DependentTypesHelper
+   */
+  public DependentTypesHelper getDependentTypesHelper() {
+    return dependentTypesHelper;
+  }
+
+  /**
+   * Creates an {@link ContractsFromMethod} and returns it.
+   *
+   * @return a new {@link ContractsFromMethod}
+   */
+  protected ContractsFromMethod createContractsFromMethod() {
+    return new ContractsFromMethod(this);
+  }
+
+  /**
+   * Returns the helper for method pre- and postconditions.
+   *
+   * @return the helper for method pre- and postconditions
+   */
+  public ContractsFromMethod getContractsFromMethod() {
+    return contractsUtils;
+  }
+
+  @Override
+  public AnnotatedDeclaredType fromNewClass(NewClassTree newClassTree) {
+    AnnotatedDeclaredType superResult = super.fromNewClass(newClassTree);
+    dependentTypesHelper.atExpression(superResult, newClassTree);
+    return superResult;
+  }
+
+  /**
+   * Create {@link QualifierDefaults} which handles checker specified defaults, and initialize the
+   * created {@link QualifierDefaults}. Subclasses should override {@link
+   * GenericAnnotatedTypeFactory#addCheckedCodeDefaults(QualifierDefaults defs)} to add more
+   * defaults or use different defaults.
+   *
+   * @return the QualifierDefaults object
+   */
+  // TODO: When changing this method, also look into
+  // {@link
+  // org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesHelper#shouldIgnore}.
+  // Both methods should have some functionality merged into a single location.
+  // See Issue 683
+  // https://github.com/typetools/checker-framework/issues/683
+  protected final QualifierDefaults createAndInitQualifierDefaults() {
+    QualifierDefaults defs = createQualifierDefaults();
+    addCheckedCodeDefaults(defs);
+    addCheckedStandardDefaults(defs);
+    addUncheckedStandardDefaults(defs);
+    checkForDefaultQualifierInHierarchy(defs);
+
+    return defs;
+  }
+
+  /**
+   * Create {@link QualifierDefaults} which handles checker specified defaults. Sub-classes override
+   * this method to provide a different {@code QualifierDefault} implementation.
+   */
+  protected QualifierDefaults createQualifierDefaults() {
+    return new QualifierDefaults(elements, this);
+  }
+
+  /**
+   * Creates and returns a string containing the number of qualifiers and the canonical class names
+   * of each qualifier that has been added to this checker's supported qualifier set. The names are
+   * alphabetically sorted.
+   *
+   * @return a string containing the number of qualifiers and canonical names of each qualifier
+   */
+  protected final String getSortedQualifierNames() {
+    Set<Class<? extends Annotation>> stq = getSupportedTypeQualifiers();
+    if (stq.isEmpty()) {
+      return "No qualifiers examined";
+    }
+    if (stq.size() == 1) {
+      return "1 qualifier examined: " + stq.iterator().next().getCanonicalName();
+    }
+
+    // Create a list of the supported qualifiers and sort the list alphabetically
+    List<Class<? extends Annotation>> sortedSupportedQuals = new ArrayList<>(stq);
+    sortedSupportedQuals.sort(Comparator.comparing(Class::getCanonicalName));
+
+    // display the number of qualifiers as well as the names of each qualifier.
+    StringJoiner sj =
+        new StringJoiner(", ", sortedSupportedQuals.size() + " qualifiers examined: ", "");
+    for (Class<? extends Annotation> qual : sortedSupportedQuals) {
+      sj.add(qual.getCanonicalName());
+    }
+    return sj.toString();
+  }
+
+  /**
+   * Adds default qualifiers for type-checked code by reading {@link DefaultFor} and {@link
+   * DefaultQualifierInHierarchy} meta-annotations. Subclasses may override this method to add
+   * defaults that cannot be specified with a {@link DefaultFor} or {@link
+   * DefaultQualifierInHierarchy} meta-annotations.
+   *
+   * @param defs QualifierDefault object to which defaults are added
+   */
+  protected void addCheckedCodeDefaults(QualifierDefaults defs) {
+    // Add defaults from @DefaultFor and @DefaultQualifierInHierarchy
+    for (Class<? extends Annotation> qual : getSupportedTypeQualifiers()) {
+      DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class);
+      if (defaultFor != null) {
+        final TypeUseLocation[] locations = defaultFor.value();
+        defs.addCheckedCodeDefaults(AnnotationBuilder.fromClass(elements, qual), locations);
+      }
+
+      if (qual.getAnnotation(DefaultQualifierInHierarchy.class) != null) {
+        defs.addCheckedCodeDefault(
+            AnnotationBuilder.fromClass(elements, qual), TypeUseLocation.OTHERWISE);
+      }
+    }
+  }
+
+  /**
+   * Adds the standard CLIMB defaults that do not conflict with previously added defaults.
+   *
+   * @param defs {@link QualifierDefaults} object to which defaults are added
+   */
+  protected void addCheckedStandardDefaults(QualifierDefaults defs) {
+    if (this.everUseFlow) {
+      defs.addClimbStandardDefaults();
+    }
+  }
+
+  /**
+   * Adds standard unchecked defaults that do not conflict with previously added defaults.
+   *
+   * @param defs {@link QualifierDefaults} object to which defaults are added
+   */
+  protected void addUncheckedStandardDefaults(QualifierDefaults defs) {
+    defs.addUncheckedStandardDefaults();
+  }
+
+  /**
+   * Check that a default qualifier (in at least one hierarchy) has been set and issue an error if
+   * not.
+   *
+   * @param defs {@link QualifierDefaults} object to which defaults are added
+   */
+  protected void checkForDefaultQualifierInHierarchy(QualifierDefaults defs) {
+    if (!defs.hasDefaultsForCheckedCode()) {
+      throw new BugInCF(
+          "GenericAnnotatedTypeFactory.createQualifierDefaults: "
+              + "@DefaultQualifierInHierarchy or @DefaultFor(TypeUseLocation.OTHERWISE) not found. "
+              + "Every checker must specify a default qualifier. "
+              + getSortedQualifierNames());
+    }
+
+    // If a default unchecked code qualifier isn't specified, the defaults
+    // for checked code will be used.
+  }
+
+  /**
+   * Creates the {@link QualifierPolymorphism} instance which supports the QualifierPolymorphism
+   * mechanism.
+   *
+   * @return the QualifierPolymorphism instance to use
+   */
+  protected QualifierPolymorphism createQualifierPolymorphism() {
+    return new DefaultQualifierPolymorphism(processingEnv, this);
+  }
+
+  /**
+   * Gives the current {@link QualifierPolymorphism} instance which supports the
+   * QualifierPolymorphism mechanism.
+   *
+   * @return the QualifierPolymorphism instance to use
+   */
+  public QualifierPolymorphism getQualifierPolymorphism() {
+    return this.poly;
+  }
+
+  // **********************************************************************
+  // Factory Methods for the appropriate annotator classes
+  // **********************************************************************
+
+  @Override
+  protected void postDirectSuperTypes(
+      AnnotatedTypeMirror type, List<? extends AnnotatedTypeMirror> supertypes) {
+    super.postDirectSuperTypes(type, supertypes);
+    if (type.getKind() == TypeKind.DECLARED) {
+      for (AnnotatedTypeMirror supertype : supertypes) {
+        Element elt = ((DeclaredType) supertype.getUnderlyingType()).asElement();
+        addComputedTypeAnnotations(elt, supertype);
+      }
+    }
+  }
+
+  /**
+   * Gets the type of the resulting constructor call of a MemberReferenceTree.
+   *
+   * @param memberReferenceTree MemberReferenceTree where the member is a constructor
+   * @param constructorType AnnotatedExecutableType of the declaration of the constructor
+   * @return AnnotatedTypeMirror of the resulting type of the constructor
+   */
+  public AnnotatedTypeMirror getResultingTypeOfConstructorMemberReference(
+      MemberReferenceTree memberReferenceTree, AnnotatedExecutableType constructorType) {
+    assert memberReferenceTree.getMode() == MemberReferenceTree.ReferenceMode.NEW;
+
+    // The return type for constructors should only have explicit annotations from the
+    // constructor.  Recreate some of the logic from TypeFromTree.visitNewClass here.
+
+    // The return type of the constructor will be the type of the expression of the member
+    // reference tree.
+    AnnotatedDeclaredType constructorReturnType =
+        (AnnotatedDeclaredType) fromTypeTree(memberReferenceTree.getQualifierExpression());
+
+    // Keep only explicit annotations and those from @Poly
+    AnnotatedTypes.copyOnlyExplicitConstructorAnnotations(
+        this, constructorReturnType, constructorType);
+
+    // Now add back defaulting.
+    addComputedTypeAnnotations(memberReferenceTree.getQualifierExpression(), constructorReturnType);
+    return constructorReturnType;
+  }
+
+  /**
+   * Returns the primary annotation on expression if it were evaluated at path.
+   *
+   * @param expression a Java expression
+   * @param tree current tree
+   * @param path location at which expression is evaluated
+   * @param clazz class of the annotation
+   * @return the annotation on expression or null if one does not exist
+   * @throws JavaExpressionParseException thrown if the expression cannot be parsed
+   */
+  public AnnotationMirror getAnnotationFromJavaExpressionString(
+      String expression, Tree tree, TreePath path, Class<? extends Annotation> clazz)
+      throws JavaExpressionParseException {
+
+    JavaExpression expressionObj = parseJavaExpressionString(expression, path);
+    return getAnnotationFromJavaExpression(expressionObj, tree, clazz);
+  }
+
+  /**
+   * Returns the primary annotation on an expression, at a particular location.
+   *
+   * @param expr the expression for which the annotation is returned
+   * @param tree current tree
+   * @param clazz the Class of the annotation
+   * @return the annotation on expression or null if one does not exist
+   */
+  public AnnotationMirror getAnnotationFromJavaExpression(
+      JavaExpression expr, Tree tree, Class<? extends Annotation> clazz) {
+    return getAnnotationByClass(getAnnotationsFromJavaExpression(expr, tree), clazz);
+  }
+
+  /**
+   * Returns the primary annotations on an expression, at a particular location.
+   *
+   * @param expr the expression for which the annotation is returned
+   * @param tree current tree
+   * @return the annotation on expression or null if one does not exist
+   */
+  public Set<AnnotationMirror> getAnnotationsFromJavaExpression(JavaExpression expr, Tree tree) {
+
+    // Look in the store
+    if (CFAbstractStore.canInsertJavaExpression(expr)) {
+      Store store = getStoreBefore(tree);
+      // `store` can be null if the tree is in a field initializer.
+      if (store != null) {
+        Value value = store.getValue(expr);
+        if (value != null) {
+          // Is it possible that this lacks some annotations that appear in the type factory?
+          return value.getAnnotations();
+        }
+      }
+    }
+
+    // Look in the type factory, if not found in the store.
+    if (expr instanceof LocalVariable) {
+      Element ele = ((LocalVariable) expr).getElement();
+      // Because of
+      // https://github.com/eisop/checker-framework/issues/14
+      // and the workaround in
+      // org.checkerframework.framework.type.ElementAnnotationApplier.applyInternal
+      // The annotationMirror may not contain all explicitly written annotations.
+      return getAnnotatedType(ele).getAnnotations();
+    } else if (expr instanceof FieldAccess) {
+      Element ele = ((FieldAccess) expr).getField();
+      return getAnnotatedType(ele).getAnnotations();
+    } else {
+      return Collections.emptySet();
+    }
+  }
+
+  /**
+   * Produces the JavaExpression as if {@code expression} were written at {@code currentPath}.
+   *
+   * @param expression a Java expression
+   * @param currentPath the current path
+   * @return the JavaExpression associated with expression on currentPath
+   * @throws JavaExpressionParseException thrown if the expression cannot be parsed
+   */
+  public JavaExpression parseJavaExpressionString(String expression, TreePath currentPath)
+      throws JavaExpressionParseException {
+
+    return StringToJavaExpression.atPath(expression, currentPath, checker);
+  }
+
+  /**
+   * Produces the JavaExpression and offset associated with an expression. For instance, "n+1" has
+   * no associated JavaExpression, but this method produces a pair of a JavaExpression (for "n") and
+   * an offset ("1").
+   *
+   * @param expression a Java expression, possibly with a constant offset
+   * @param currentPath location at which expression is evaluated
+   * @return the JavaExpression and offset for the given expression
+   * @throws JavaExpressionParseException thrown if the expression cannot be parsed
+   */
+  public Pair<JavaExpression, String> getExpressionAndOffsetFromJavaExpressionString(
+      String expression, TreePath currentPath) throws JavaExpressionParseException {
+    Pair<String, String> p = getExpressionAndOffset(expression);
+    JavaExpression r = parseJavaExpressionString(p.first, currentPath);
+    return Pair.of(r, p.second);
+  }
+
+  /**
+   * Returns the annotation mirror from dataflow for {@code expression}.
+   *
+   * <p>This will output a different annotation than {@link
+   * #getAnnotationFromJavaExpressionString(String, Tree, TreePath, Class)}, because if the
+   * specified annotation isn't found in the store, the type from the factory is used.
+   *
+   * @param expression a Java expression
+   * @param tree the tree at the location to parse the expression
+   * @param currentPath location at which expression is evaluated
+   * @throws JavaExpressionParseException thrown if the expression cannot be parsed
+   * @return an AnnotationMirror representing the type in the store at the given location from this
+   *     type factory's type system, or null if one is not available
+   */
+  public AnnotationMirror getAnnotationMirrorFromJavaExpressionString(
+      String expression, Tree tree, TreePath currentPath) throws JavaExpressionParseException {
+    JavaExpression je = parseJavaExpressionString(expression, currentPath);
+    if (je == null || !CFAbstractStore.canInsertJavaExpression(je)) {
+      return null;
+    }
+    Store store = getStoreBefore(tree);
+    Value value = store.getValue(je);
+    return value != null ? value.getAnnotations().iterator().next() : null;
+  }
+
+  /**
+   * Track the state of org.checkerframework.dataflow analysis scanning for each class tree in the
+   * compilation unit.
+   */
+  protected enum ScanState {
+    /** Dataflow analysis in progress. */
+    IN_PROGRESS,
+    /** Dataflow analysis finished. */
+    FINISHED
+  }
+
+  /** Map from ClassTree to their dataflow analysis state. */
+  protected final Map<ClassTree, ScanState> scannedClasses;
+
+  /**
+   * The result of the flow analysis. Invariant:
+   *
+   * <pre>
+   *  scannedClasses.get(c) == FINISHED for some class c &rArr; flowResult != null
+   * </pre>
+   *
+   * Note that flowResult contains analysis results for Trees from multiple classes which are
+   * produced by multiple calls to performFlowAnalysis.
+   */
+  protected AnalysisResult<Value, Store> flowResult;
+
+  /**
+   * A mapping from methods (or other code blocks) to their regular exit store (used to check
+   * postconditions).
+   */
+  protected IdentityHashMap<Tree, Store> regularExitStores;
+
+  /** A mapping from methods (or other code blocks) to their exceptional exit store. */
+  protected IdentityHashMap<Tree, Store> exceptionalExitStores;
+
+  /** A mapping from methods to a list with all return statements and the corresponding store. */
+  protected IdentityHashMap<MethodTree, List<Pair<ReturnNode, TransferResult<Value, Store>>>>
+      returnStatementStores;
+
+  /**
+   * A mapping from methods to their a list with all return statements and the corresponding store.
+   */
+  protected IdentityHashMap<MethodInvocationTree, Store> methodInvocationStores;
+
+  /**
+   * Returns the regular exit store for a method or another code block (such as static
+   * initializers).
+   *
+   * @param tree a MethodTree or other code block, such as a static initializer
+   * @return the regular exit store, or {@code null}, if there is no such store (because the method
+   *     cannot exit through the regular exit block).
+   */
+  public @Nullable Store getRegularExitStore(Tree tree) {
+    return regularExitStores.get(tree);
+  }
+
+  /**
+   * Returns the exceptional exit store for a method or another code block (such as static
+   * initializers).
+   *
+   * @param tree a MethodTree or other code block, such as a static initializer
+   * @return the exceptional exit store, or {@code null}, if there is no such store
+   */
+  public @Nullable Store getExceptionalExitStore(Tree tree) {
+    return exceptionalExitStores.get(tree);
+  }
+
+  /**
+   * Returns a list of all return statements of {@code method} paired with their corresponding
+   * {@link TransferResult}. If {@code method} has no return statement, then the empty list is
+   * returned.
+   *
+   * @param methodTree method whose return statements should be returned
+   * @return a list of all return statements of {@code method} paired with their corresponding
+   *     {@link TransferResult} or an empty list if {@code method} has no return statements
+   */
+  public List<Pair<ReturnNode, TransferResult<Value, Store>>> getReturnStatementStores(
+      MethodTree methodTree) {
+    assert returnStatementStores.containsKey(methodTree);
+    return returnStatementStores.get(methodTree);
+  }
+
+  /**
+   * Returns the store immediately before a given {@link Tree}.
+   *
+   * @return the store immediately before a given {@link Tree}
+   */
+  public Store getStoreBefore(Tree tree) {
+    if (!analysis.isRunning()) {
+      return flowResult.getStoreBefore(tree);
+    }
+    Set<Node> nodes = analysis.getNodesForTree(tree);
+    if (nodes != null) {
+      return getStoreBefore(nodes);
+    } else {
+      return flowResult.getStoreBefore(tree);
+    }
+  }
+
+  /**
+   * Returns the store immediately before a given Set of {@link Node}s.
+   *
+   * @return the store immediately before a given Set of {@link Node}s
+   */
+  public Store getStoreBefore(Set<Node> nodes) {
+    Store merge = null;
+    for (Node aNode : nodes) {
+      Store s = getStoreBefore(aNode);
+      if (merge == null) {
+        merge = s;
+      } else if (s != null) {
+        merge = merge.leastUpperBound(s);
+      }
+    }
+    return merge;
+  }
+
+  /**
+   * Returns the store immediately before a given node.
+   *
+   * @param node a node whose pre-store to return
+   * @return the store immediately before {@code node}
+   */
+  public Store getStoreBefore(Node node) {
+    if (!analysis.isRunning()) {
+      return flowResult.getStoreBefore(node);
+    }
+    TransferInput<Value, Store> prevStore = analysis.getInput(node.getBlock());
+    if (prevStore == null) {
+      return null;
+    }
+    Store store =
+        AnalysisResult.runAnalysisFor(
+            node,
+            Analysis.BeforeOrAfter.BEFORE,
+            prevStore,
+            analysis.getNodeValues(),
+            flowResultAnalysisCaches);
+    return store;
+  }
+
+  /**
+   * Returns the store immediately after a given tree.
+   *
+   * <p>May return null; for example, after a {@code return} statement.
+   *
+   * @param tree the tree whose post-store to return
+   * @return the store immediately after a given tree
+   */
+  public Store getStoreAfter(Tree tree) {
+    if (!analysis.isRunning()) {
+      return flowResult.getStoreAfter(tree);
+    }
+    Set<Node> nodes = analysis.getNodesForTree(tree);
+    return getStoreAfter(nodes);
+  }
+
+  /**
+   * Returns the store immediately after a given set of nodes.
+   *
+   * @param nodes the nodes whose post-stores to LUB
+   * @return the LUB of the stores store immediately after {@code nodes}
+   */
+  public Store getStoreAfter(Set<Node> nodes) {
+    Store merge = null;
+    for (Node node : nodes) {
+      Store s = getStoreAfter(node);
+      if (merge == null) {
+        merge = s;
+      } else if (s != null) {
+        merge = merge.leastUpperBound(s);
+      }
+    }
+    return merge;
+  }
+
+  /**
+   * Returns the store immediately after a given {@link Node}.
+   *
+   * @param node node after which the store is returned
+   * @return the store immediately after a given {@link Node}
+   */
+  public Store getStoreAfter(Node node) {
+    if (!analysis.isRunning()) {
+      return flowResult.getStoreAfter(node);
+    }
+    Store res =
+        AnalysisResult.runAnalysisFor(
+            node,
+            Analysis.BeforeOrAfter.AFTER,
+            analysis.getInput(node.getBlock()),
+            analysis.getNodeValues(),
+            flowResultAnalysisCaches);
+    return res;
+  }
+
+  /**
+   * See {@link org.checkerframework.dataflow.analysis.AnalysisResult#getNodesForTree(Tree)}.
+   *
+   * @return the {@link Node}s for a given {@link Tree}
+   * @see org.checkerframework.dataflow.analysis.AnalysisResult#getNodesForTree(Tree)
+   */
+  public Set<Node> getNodesForTree(Tree tree) {
+    return flowResult.getNodesForTree(tree);
+  }
+
+  /**
+   * Return the first {@link Node} for a given {@link Tree} that has class {@code kind}.
+   *
+   * <p>You probably don't want to use this function: iterate over the result of {@link
+   * #getNodesForTree(Tree)} yourself or ask for a conservative approximation of the store using
+   * {@link #getStoreBefore(Tree)} or {@link #getStoreAfter(Tree)}. This method is for code that
+   * uses a {@link Node} in a rather unusual way. Callers should probably be rewritten to not use a
+   * {@link Node} at all.
+   *
+   * @param <T> the class of the node to return
+   * @param tree a tree in which to search for a node of class {@code kind}
+   * @param kind the class of the node to return
+   * @return the first {@link Node} for a given {@link Tree} that has class {@code kind}
+   * @see #getNodesForTree(Tree)
+   * @see #getStoreBefore(Tree)
+   * @see #getStoreAfter(Tree)
+   */
+  public <T extends Node> T getFirstNodeOfKindForTree(Tree tree, Class<T> kind) {
+    Set<Node> nodes = getNodesForTree(tree);
+    for (Node node : nodes) {
+      if (node.getClass() == kind) {
+        return kind.cast(node);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns the value of effectively final local variables.
+   *
+   * @return the value of effectively final local variables
+   */
+  public HashMap<Element, Value> getFinalLocalValues() {
+    return flowResult.getFinalLocalValues();
+  }
+
+  /**
+   * Perform a org.checkerframework.dataflow analysis over a single class tree and its nested
+   * classes.
+   *
+   * @param classTree the class to analyze
+   */
+  protected void performFlowAnalysis(ClassTree classTree) {
+    if (flowResult == null) {
+      regularExitStores = new IdentityHashMap<>();
+      exceptionalExitStores = new IdentityHashMap<>();
+      returnStatementStores = new IdentityHashMap<>();
+      flowResult = new AnalysisResult<>(flowResultAnalysisCaches);
+    }
+
+    // no need to scan annotations
+    if (classTree.getKind() == Kind.ANNOTATION_TYPE) {
+      // Mark finished so that default annotations will be applied.
+      scannedClasses.put(classTree, ScanState.FINISHED);
+      return;
+    }
+
+    Queue<Pair<ClassTree, Store>> queue = new ArrayDeque<>();
+    List<Pair<VariableElement, Value>> fieldValues = new ArrayList<>();
+
+    // No captured store for top-level classes.
+    queue.add(Pair.of(classTree, null));
+
+    while (!queue.isEmpty()) {
+      final Pair<ClassTree, Store> qel = queue.remove();
+      final ClassTree ct = qel.first;
+      final Store capturedStore = qel.second;
+      scannedClasses.put(ct, ScanState.IN_PROGRESS);
+
+      TreePath preTreePath = visitorState.getPath();
+      AnnotatedDeclaredType preClassType = visitorState.getClassType();
+      ClassTree preClassTree = visitorState.getClassTree();
+      AnnotatedDeclaredType preAMT = visitorState.getMethodReceiver();
+      MethodTree preMT = visitorState.getMethodTree();
+
+      // Don't use getPath, because that depends on the visitorState path.
+      visitorState.setPath(TreePath.getPath(this.root, ct));
+      visitorState.setClassType(getAnnotatedType(TreeUtils.elementFromDeclaration(ct)));
+      visitorState.setClassTree(ct);
+      visitorState.setMethodReceiver(null);
+      visitorState.setMethodTree(null);
+
+      // start with the captured store as initialization store
+      initializationStaticStore = capturedStore;
+      initializationStore = capturedStore;
+
+      Queue<Pair<LambdaExpressionTree, Store>> lambdaQueue = new ArrayDeque<>();
+
+      try {
+        List<CFGMethod> methods = new ArrayList<>();
+        for (Tree m : ct.getMembers()) {
+          switch (m.getKind()) {
+            case METHOD:
+              MethodTree mt = (MethodTree) m;
+
+              // Skip abstract and native methods because they have no body.
+              Set<Modifier> flags = mt.getModifiers().getFlags();
+              if (flags.contains(Modifier.ABSTRACT) || flags.contains(Modifier.NATIVE)) {
+                break;
+              }
+              // Abstract methods in an interface have a null body but do not have an
+              // ABSTRACT flag.
+              if (mt.getBody() == null) {
+                break;
+              }
+
+              // Wait with scanning the method until all other members
+              // have been processed.
+              CFGMethod met = new CFGMethod(mt, ct);
+              methods.add(met);
+              break;
+            case VARIABLE:
+              VariableTree vt = (VariableTree) m;
+              ExpressionTree initializer = vt.getInitializer();
+              // analyze initializer if present
+              if (initializer != null) {
+                boolean isStatic = vt.getModifiers().getFlags().contains(Modifier.STATIC);
+                analyze(
+                    queue,
+                    lambdaQueue,
+                    new CFGStatement(vt, ct),
+                    fieldValues,
+                    classTree,
+                    true,
+                    true,
+                    isStatic,
+                    capturedStore);
+                Value value = flowResult.getValue(initializer);
+                if (vt.getModifiers().getFlags().contains(Modifier.FINAL) && value != null) {
+                  // Store the abstract value for the field.
+                  VariableElement element = TreeUtils.elementFromDeclaration(vt);
+                  fieldValues.add(Pair.of(element, value));
+                }
+              }
+              break;
+            case CLASS:
+            case ANNOTATION_TYPE:
+            case INTERFACE:
+            case ENUM:
+              // Visit inner and nested class trees.
+              // TODO: Use no store for them? What can be captured?
+              queue.add(Pair.of((ClassTree) m, capturedStore));
+              break;
+            case BLOCK:
+              BlockTree b = (BlockTree) m;
+              analyze(
+                  queue,
+                  lambdaQueue,
+                  new CFGStatement(b, ct),
+                  fieldValues,
+                  ct,
+                  true,
+                  true,
+                  b.isStatic(),
+                  capturedStore);
+              break;
+            default:
+              assert false : "Unexpected member: " + m.getKind();
+              break;
+          }
+        }
+
+        // Now analyze all methods.
+        // TODO: at this point, we don't have any information about
+        // fields of superclasses.
+        for (CFGMethod met : methods) {
+          analyze(
+              queue,
+              lambdaQueue,
+              met,
+              fieldValues,
+              classTree,
+              TreeUtils.isConstructor(met.getMethod()),
+              false,
+              false,
+              capturedStore);
+        }
+
+        while (!lambdaQueue.isEmpty()) {
+          Pair<LambdaExpressionTree, Store> lambdaPair = lambdaQueue.poll();
+          MethodTree mt =
+              (MethodTree) TreePathUtil.enclosingOfKind(getPath(lambdaPair.first), Kind.METHOD);
+          analyze(
+              queue,
+              lambdaQueue,
+              new CFGLambda(lambdaPair.first, classTree, mt),
+              fieldValues,
+              classTree,
+              false,
+              false,
+              false,
+              lambdaPair.second);
+        }
+
+        // by convention we store the static initialization store as the regular exit
+        // store of the class node, so that it can later be used to check
+        // that all fields are initialized properly.
+        // see InitializationVisitor.visitClass
+        if (initializationStaticStore == null) {
+          regularExitStores.put(ct, emptyStore);
+        } else {
+          regularExitStores.put(ct, initializationStaticStore);
+        }
+      } finally {
+        visitorState.setPath(preTreePath);
+        visitorState.setClassType(preClassType);
+        visitorState.setClassTree(preClassTree);
+        visitorState.setMethodReceiver(preAMT);
+        visitorState.setMethodTree(preMT);
+      }
+
+      scannedClasses.put(ct, ScanState.FINISHED);
+    }
+  }
+
+  /**
+   * Analyze the AST {@code ast} and store the result. Additional operations that should be
+   * performed after analysis should be implemented in {@link #postAnalyze(ControlFlowGraph)}.
+   *
+   * @param queue the queue for encountered class trees and their initial stores
+   * @param lambdaQueue the queue for encountered lambda expression trees and their initial stores
+   * @param ast the AST to analyze
+   * @param fieldValues the abstract values for all fields of the same class
+   * @param currentClass the class we are currently looking at
+   * @param isInitializationCode are we analyzing a (static/non-static) initializer block of a class
+   * @param updateInitializationStore should the initialization store be updated
+   * @param isStatic are we analyzing a static construct
+   * @param capturedStore the input Store to use for captured variables, e.g. in a lambda
+   * @see #postAnalyze(org.checkerframework.dataflow.cfg.ControlFlowGraph)
+   */
+  protected void analyze(
+      Queue<Pair<ClassTree, Store>> queue,
+      Queue<Pair<LambdaExpressionTree, Store>> lambdaQueue,
+      UnderlyingAST ast,
+      List<Pair<VariableElement, Value>> fieldValues,
+      ClassTree currentClass,
+      boolean isInitializationCode,
+      boolean updateInitializationStore,
+      boolean isStatic,
+      Store capturedStore) {
+    ControlFlowGraph cfg = CFCFGBuilder.build(root, ast, checker, this, processingEnv);
+
+    if (isInitializationCode) {
+      Store initStore = !isStatic ? initializationStore : initializationStaticStore;
+      if (initStore != null) {
+        // we have already seen initialization code and analyzed it, and
+        // the analysis ended with the store initStore.
+        // use it to start the next analysis.
+        transfer.setFixedInitialStore(initStore);
+      } else {
+        transfer.setFixedInitialStore(capturedStore);
+      }
+    } else {
+      transfer.setFixedInitialStore(capturedStore);
+    }
+    analysis.performAnalysis(cfg, fieldValues);
+    AnalysisResult<Value, Store> result = analysis.getResult();
+
+    // store result
+    flowResult.combine(result);
+    if (ast.getKind() == UnderlyingAST.Kind.METHOD) {
+      // store exit store (for checking postconditions)
+      CFGMethod mast = (CFGMethod) ast;
+      MethodTree method = mast.getMethod();
+      Store regularExitStore = analysis.getRegularExitStore();
+      if (regularExitStore != null) {
+        regularExitStores.put(method, regularExitStore);
+      }
+      Store exceptionalExitStore = analysis.getExceptionalExitStore();
+      if (exceptionalExitStore != null) {
+        exceptionalExitStores.put(method, exceptionalExitStore);
+      }
+      returnStatementStores.put(method, analysis.getReturnStatementStores());
+    } else if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) {
+      CFGStatement block = (CFGStatement) ast;
+      Store regularExitStore = analysis.getRegularExitStore();
+      if (regularExitStore != null) {
+        regularExitStores.put(block.getCode(), regularExitStore);
+      }
+      Store exceptionalExitStore = analysis.getExceptionalExitStore();
+      if (exceptionalExitStore != null) {
+        exceptionalExitStores.put(block.getCode(), exceptionalExitStore);
+      }
+    } else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) {
+      // TODO: Postconditions?
+
+      CFGLambda block = (CFGLambda) ast;
+      Store regularExitStore = analysis.getRegularExitStore();
+      if (regularExitStore != null) {
+        regularExitStores.put(block.getCode(), regularExitStore);
+      }
+      Store exceptionalExitStore = analysis.getExceptionalExitStore();
+      if (exceptionalExitStore != null) {
+        exceptionalExitStores.put(block.getCode(), exceptionalExitStore);
+      }
+    } else {
+      assert false : "Unexpected AST kind: " + ast.getKind();
+    }
+
+    if (isInitializationCode && updateInitializationStore) {
+      Store newInitStore = analysis.getRegularExitStore();
+      if (!isStatic) {
+        initializationStore = newInitStore;
+      } else {
+        initializationStaticStore = newInitStore;
+      }
+    }
+
+    // add classes declared in CFG
+    for (ClassTree cls : cfg.getDeclaredClasses()) {
+      queue.add(Pair.of(cls, getStoreBefore(cls)));
+    }
+    // add lambdas declared in CFG
+    for (LambdaExpressionTree lambda : cfg.getDeclaredLambdas()) {
+      lambdaQueue.add(Pair.of(lambda, getStoreBefore(lambda)));
+    }
+
+    postAnalyze(cfg);
+  }
+
+  /**
+   * Perform any additional operations on a CFG. Called once per CFG, after the CFG has been
+   * analyzed by {@link #analyze(Queue, Queue, UnderlyingAST, List, ClassTree, boolean, boolean,
+   * boolean, CFAbstractStore)}. This method can be used to initialize additional state or to
+   * perform any analyses that are easier to perform on the CFG instead of the AST.
+   *
+   * @param cfg the CFG
+   * @see #analyze(java.util.Queue, java.util.Queue,
+   *     org.checkerframework.dataflow.cfg.UnderlyingAST, java.util.List,
+   *     com.sun.source.tree.ClassTree, boolean, boolean, boolean,
+   *     org.checkerframework.framework.flow.CFAbstractStore)
+   */
+  protected void postAnalyze(ControlFlowGraph cfg) {
+    handleCFGViz(cfg);
+  }
+
+  /**
+   * Handle the visualization of the CFG, if necessary.
+   *
+   * @param cfg the CFG
+   */
+  protected void handleCFGViz(ControlFlowGraph cfg) {
+    if (checker.hasOption("flowdotdir") || checker.hasOption("cfgviz")) {
+      getCFGVisualizer().visualize(cfg, cfg.getEntryBlock(), analysis);
+    }
+  }
+
+  /**
+   * Returns the type of the left-hand side of an assignment without applying local variable
+   * defaults to type variables.
+   *
+   * <p>The type variables that are types of local variables are defaulted to top so that they can
+   * be refined by dataflow. When these types are used as context during type argument inference,
+   * this default is too conservative. So this method is used instead of {@link
+   * GenericAnnotatedTypeFactory#getAnnotatedTypeLhs(Tree)}.
+   *
+   * <p>{@link TypeArgInferenceUtil#assignedToVariable(AnnotatedTypeFactory, Tree)} explains why a
+   * different type is used.
+   *
+   * @param lhsTree left-hand side of an assignment
+   * @return AnnotatedTypeMirror of {@code lhsTree}
+   */
+  public AnnotatedTypeMirror getAnnotatedTypeLhsNoTypeVarDefault(Tree lhsTree) {
+    boolean old = this.shouldDefaultTypeVarLocals;
+    shouldDefaultTypeVarLocals = false;
+    AnnotatedTypeMirror type = getAnnotatedTypeLhs(lhsTree);
+    this.shouldDefaultTypeVarLocals = old;
+    return type;
+  }
+
+  /**
+   * Returns the type of a left-hand side of an assignment.
+   *
+   * <p>The default implementation returns the type without considering dataflow type refinement.
+   * Subclass can override this method and add additional logic for computing the type of a LHS.
+   *
+   * @param lhsTree left-hand side of an assignment
+   * @return AnnotatedTypeMirror of {@code lhsTree}
+   */
+  public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) {
+    AnnotatedTypeMirror res;
+    boolean oldUseFlow = useFlow;
+    boolean oldShouldCache = shouldCache;
+    useFlow = false;
+    // Don't cache the result because getAnnotatedType(lhsTree) could
+    // be called from elsewhere and would expect flow-sensitive type refinements.
+    shouldCache = false;
+    switch (lhsTree.getKind()) {
+      case VARIABLE:
+      case IDENTIFIER:
+      case MEMBER_SELECT:
+      case ARRAY_ACCESS:
+        res = getAnnotatedType(lhsTree);
+        break;
+      case PARENTHESIZED:
+        res = getAnnotatedTypeLhs(TreeUtils.withoutParens((ExpressionTree) lhsTree));
+        break;
+      default:
+        if (TreeUtils.isTypeTree(lhsTree)) {
+          // lhsTree is a type tree at the pseudo assignment of a returned expression to
+          // declared return type.
+          res = getAnnotatedType(lhsTree);
+        } else {
+          throw new BugInCF(
+              "GenericAnnotatedTypeFactory: Unexpected tree passed to getAnnotatedTypeLhs. "
+                  + "lhsTree: "
+                  + lhsTree
+                  + " Tree.Kind: "
+                  + lhsTree.getKind());
+        }
+    }
+    useFlow = oldUseFlow;
+    shouldCache = oldShouldCache;
+    return res;
+  }
+
+  /**
+   * Returns the type of a varargs array of a method invocation or a constructor invocation. Returns
+   * null only if private field {@code useFlow} is false.
+   *
+   * @param tree a method invocation or a constructor invocation
+   * @return AnnotatedTypeMirror of varargs array for a method or constructor invocation {@code
+   *     tree}; returns null if private field {@code useFlow} is false
+   */
+  public @Nullable AnnotatedTypeMirror getAnnotatedTypeVarargsArray(Tree tree) {
+    if (!useFlow) {
+      return null;
+    }
+
+    // Get the synthetic NewArray tree that dataflow creates as the last argument of a call to a
+    // vararg method. Do this by getting the MethodInvocationNode to which "tree" maps. The last
+    // argument node of the MethodInvocationNode stores the synthetic NewArray tree.
+    List<Node> args;
+    switch (tree.getKind()) {
+      case METHOD_INVOCATION:
+        args = getFirstNodeOfKindForTree(tree, MethodInvocationNode.class).getArguments();
+        break;
+      case NEW_CLASS:
+        args = getFirstNodeOfKindForTree(tree, ObjectCreationNode.class).getArguments();
+        break;
+      default:
+        throw new BugInCF("Unexpected kind of tree: " + tree);
+    }
+
+    assert !args.isEmpty() : "Arguments are empty";
+    Node varargsArray = args.get(args.size() - 1);
+    AnnotatedTypeMirror varargtype = getAnnotatedType(varargsArray.getTree());
+    return varargtype;
+  }
+
+  /* Returns the type of a right-hand side of an assignment for unary operation like prefix or
+   * postfix increment or decrement.
+   *
+   * @param tree unary operation tree for compound assignment
+   * @return AnnotatedTypeMirror of a right-hand side of an assignment for unary operation
+   */
+  public AnnotatedTypeMirror getAnnotatedTypeRhsUnaryAssign(UnaryTree tree) {
+    if (!useFlow) {
+      return getAnnotatedType(tree);
+    }
+    AssignmentNode n = flowResult.getAssignForUnaryTree(tree);
+    return getAnnotatedType(n.getExpression().getTree());
+  }
+
+  @Override
+  public ParameterizedExecutableType constructorFromUse(NewClassTree tree) {
+    ParameterizedExecutableType mType = super.constructorFromUse(tree);
+    AnnotatedExecutableType method = mType.executableType;
+    dependentTypesHelper.atConstructorInvocation(method, tree);
+    return mType;
+  }
+
+  @Override
+  protected void constructorFromUsePreSubstitution(
+      NewClassTree tree, AnnotatedExecutableType type) {
+    poly.resolve(tree, type);
+  }
+
+  @Override
+  public AnnotatedTypeMirror getMethodReturnType(MethodTree m) {
+    AnnotatedTypeMirror returnType = super.getMethodReturnType(m);
+    dependentTypesHelper.atMethodBody(returnType, m);
+    return returnType;
+  }
+
+  @Override
+  public void addDefaultAnnotations(AnnotatedTypeMirror type) {
+    addAnnotationsFromDefaultForType(null, type);
+    typeAnnotator.visit(type, null);
+    defaults.annotate((Element) null, type);
+  }
+
+  /**
+   * This method is final; override {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror,
+   * boolean)} instead.
+   *
+   * <p>{@inheritDoc}
+   */
+  @Override
+  protected final void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) {
+    addComputedTypeAnnotations(tree, type, this.useFlow);
+  }
+
+  /**
+   * Like {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. Overriding implementations
+   * typically simply pass the boolean to calls to super.
+   *
+   * @param tree an AST node
+   * @param type the type obtained from tree
+   * @param iUseFlow whether to use information from dataflow analysis
+   */
+  protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) {
+    if (root == null && ajavaTypes.isParsing()) {
+      return;
+    }
+    assert root != null
+        : "GenericAnnotatedTypeFactory.addComputedTypeAnnotations: "
+            + " root needs to be set when used on trees; factory: "
+            + this.getClass();
+
+    String thisClass = null;
+    String treeString = null;
+    if (debug) {
+      thisClass = this.getClass().getSimpleName();
+      if (thisClass.endsWith("AnnotatedTypeFactory")) {
+        thisClass = thisClass.substring(0, thisClass.length() - "AnnotatedTypeFactory".length());
+      }
+      treeString = TreeUtils.toStringTruncated(tree, 60);
+    }
+    log(
+        "%s GATF.addComputedTypeAnnotations#1(%s, %s, %s)%n",
+        thisClass, treeString, type, iUseFlow);
+    if (!TreeUtils.isExpressionTree(tree)) {
+      // Don't apply defaults to expressions. Their types may be computed from subexpressions
+      // in treeAnnotator.
+      addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type);
+      log("%s GATF.addComputedTypeAnnotations#2(%s, %s)%n", thisClass, treeString, type);
+    }
+    applyQualifierParameterDefaults(tree, type);
+    log("%s GATF.addComputedTypeAnnotations#3(%s, %s)%n", thisClass, treeString, type);
+    treeAnnotator.visit(tree, type);
+    log("%s GATF.addComputedTypeAnnotations#4(%s, %s)%n", thisClass, treeString, type);
+    if (TreeUtils.isExpressionTree(tree)) {
+      // If a tree annotator, did not add a type, add the DefaultForUse default.
+      addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type);
+      log("%s GATF.addComputedTypeAnnotations#5(%s, %s)%n", thisClass, treeString, type);
+    }
+    typeAnnotator.visit(type, null);
+    log("%s GATF.addComputedTypeAnnotations#6(%s, %s)%n", thisClass, treeString, type);
+    defaults.annotate(tree, type);
+    log("%s GATF.addComputedTypeAnnotations#7(%s, %s)%n", thisClass, treeString, type);
+
+    if (iUseFlow) {
+      Value as = getInferredValueFor(tree);
+      if (as != null) {
+        applyInferredAnnotations(type, as);
+        log(
+            "%s GATF.addComputedTypeAnnotations#8(%s, %s), as=%s%n",
+            thisClass, treeString, type, as);
+      }
+    }
+    log(
+        "%s GATF.addComputedTypeAnnotations#9(%s, %s, %s) done%n",
+        thisClass, treeString, type, iUseFlow);
+  }
+
+  /**
+   * Flow analysis will be performed if all of the following are true.
+   *
+   * <ul>
+   *   <li>tree is a {@link ClassTree}
+   *   <li>Flow analysis has not already been performed on tree
+   * </ul>
+   *
+   * @param tree the tree to check and possibly perform flow analysis on
+   */
+  protected void checkAndPerformFlowAnalysis(Tree tree) {
+    // For performance reasons, we require that getAnnotatedType is called
+    // on the ClassTree before it's called on any code contained in the class,
+    // so that we can perform flow analysis on the class.  Previously we
+    // used TreePath.getPath to find enclosing classes, but that call
+    // alone consumed more than 10% of execution time.  See BaseTypeVisitor
+    // .visitClass for the call to getAnnotatedType that triggers analysis.
+    if (tree instanceof ClassTree) {
+      ClassTree classTree = (ClassTree) tree;
+      if (!scannedClasses.containsKey(classTree)) {
+        performFlowAnalysis(classTree);
+      }
+    }
+  }
+
+  /**
+   * Returns the inferred value (by the org.checkerframework.dataflow analysis) for a given tree.
+   */
+  public Value getInferredValueFor(Tree tree) {
+    if (tree == null) {
+      throw new BugInCF("GenericAnnotatedTypeFactory.getInferredValueFor called with null tree");
+    }
+    Value as = null;
+    if (analysis.isRunning()) {
+      as = analysis.getValue(tree);
+    }
+    if (as == null) {
+      as = flowResult.getValue(tree);
+    }
+    return as;
+  }
+
+  /**
+   * Applies the annotations inferred by the org.checkerframework.dataflow analysis to the type
+   * {@code type}.
+   */
+  protected void applyInferredAnnotations(AnnotatedTypeMirror type, Value as) {
+    DefaultInferredTypesApplier applier =
+        new DefaultInferredTypesApplier(getQualifierHierarchy(), this);
+    applier.applyInferredType(type, as.getAnnotations(), as.getUnderlyingType());
+  }
+
+  /**
+   * Applies defaults for types in a class with an qualifier parameter.
+   *
+   * <p>Within a class with {@code @HasQualifierParameter}, types with that class default to the
+   * polymorphic qualifier rather than the typical default. Local variables with a type that has a
+   * qualifier parameter are initialized to the type of their initializer, rather than the default
+   * for local variables.
+   *
+   * @param tree a Tree whose type is {@code type}
+   * @param type where the defaults are applied
+   */
+  protected void applyQualifierParameterDefaults(Tree tree, AnnotatedTypeMirror type) {
+    applyQualifierParameterDefaults(TreeUtils.elementFromTree(tree), type);
+  }
+
+  /**
+   * Applies defaults for types in a class with an qualifier parameter.
+   *
+   * <p>Within a class with {@code @HasQualifierParameter}, types with that class default to the
+   * polymorphic qualifier rather than the typical default. Local variables with a type that has a
+   * qualifier parameter are initialized to the type of their initializer, rather than the default
+   * for local variables.
+   *
+   * @param elt an Element whose type is {@code type}
+   * @param type where the defaults are applied
+   */
+  protected void applyQualifierParameterDefaults(@Nullable Element elt, AnnotatedTypeMirror type) {
+    if (elt == null) {
+      return;
+    }
+    switch (elt.getKind()) {
+      case CONSTRUCTOR:
+      case METHOD:
+      case FIELD:
+      case LOCAL_VARIABLE:
+      case PARAMETER:
+        break;
+      default:
+        return;
+    }
+
+    applyLocalVariableQualifierParameterDefaults(elt, type);
+
+    TypeElement enclosingClass = ElementUtils.enclosingTypeElement(elt);
+    Set<AnnotationMirror> tops;
+    if (enclosingClass != null) {
+      tops = getQualifierParameterHierarchies(enclosingClass);
+    } else {
+      return;
+    }
+    if (tops.isEmpty()) {
+      return;
+    }
+    Set<AnnotationMirror> polyWithQualParam = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror top : tops) {
+      AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top);
+      if (poly != null) {
+        polyWithQualParam.add(poly);
+      }
+    }
+    new TypeAnnotator(this) {
+      @Override
+      public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) {
+        if (type.getUnderlyingType().asElement().equals(enclosingClass)) {
+          type.addMissingAnnotations(polyWithQualParam);
+        }
+        return super.visitDeclared(type, aVoid);
+      }
+    }.visit(type);
+  }
+
+  /**
+   * Defaults local variables with types that have a qualifier parameter to the type of their
+   * initializer, if an initializer is present. Does nothing for local variables with no
+   * initializer.
+   *
+   * @param elt an Element whose type is {@code type}
+   * @param type where the defaults are applied
+   */
+  private void applyLocalVariableQualifierParameterDefaults(Element elt, AnnotatedTypeMirror type) {
+    if (elt.getKind() != ElementKind.LOCAL_VARIABLE
+        || getQualifierParameterHierarchies(type).isEmpty()
+        || variablesUnderInitialization.contains(elt)) {
+      return;
+    }
+
+    Tree declTree = declarationFromElement(elt);
+    if (declTree == null || declTree.getKind() != Kind.VARIABLE) {
+      return;
+    }
+
+    ExpressionTree initializer = ((VariableTree) declTree).getInitializer();
+    if (initializer == null) {
+      return;
+    }
+
+    VariableElement variableElt = (VariableElement) elt;
+    variablesUnderInitialization.add(variableElt);
+    AnnotatedTypeMirror initializerType;
+    if (shouldCache && initializerCache.containsKey(initializer)) {
+      initializerType = initializerCache.get(initializer);
+    } else {
+      // When this method is called by getAnnotatedTypeLhs, flow is turned off.
+      // Turn it back on so the type of the initializer is the refined type.
+      boolean oldUseFlow = useFlow;
+      useFlow = everUseFlow;
+      try {
+        initializerType = getAnnotatedType(initializer);
+      } finally {
+        useFlow = oldUseFlow;
+      }
+    }
+
+    Set<AnnotationMirror> qualParamTypes = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror initializerAnnotation : initializerType.getAnnotations()) {
+      if (hasQualifierParameterInHierarchy(
+          type, qualHierarchy.getTopAnnotation(initializerAnnotation))) {
+        qualParamTypes.add(initializerAnnotation);
+      }
+    }
+
+    type.addMissingAnnotations(qualParamTypes);
+    variablesUnderInitialization.remove(variableElt);
+    if (shouldCache) {
+      initializerCache.put(initializer, initializerType);
+    }
+  }
+
+  /**
+   * To add annotations to the type of method or constructor parameters, add a {@link TypeAnnotator}
+   * using {@link #createTypeAnnotator()} and see the comment in {@link
+   * TypeAnnotator#visitExecutable(org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType,
+   * Void)}.
+   *
+   * @param elt an element
+   * @param type the type obtained from {@code elt}
+   */
+  @Override
+  public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) {
+    addAnnotationsFromDefaultForType(elt, type);
+    applyQualifierParameterDefaults(elt, type);
+    typeAnnotator.visit(type, null);
+    defaults.annotate(elt, type);
+    dependentTypesHelper.atLocalVariable(type, elt);
+  }
+
+  @Override
+  public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) {
+    ParameterizedExecutableType mType = super.methodFromUse(tree);
+    AnnotatedExecutableType method = mType.executableType;
+    dependentTypesHelper.atMethodInvocation(method, tree);
+    return mType;
+  }
+
+  @Override
+  public void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) {
+    super.methodFromUsePreSubstitution(tree, type);
+    if (tree instanceof MethodInvocationTree) {
+      poly.resolve((MethodInvocationTree) tree, type);
+    }
+  }
+
+  @Override
+  public List<AnnotatedTypeParameterBounds> typeVariablesFromUse(
+      AnnotatedDeclaredType type, TypeElement element) {
+    List<AnnotatedTypeParameterBounds> f = super.typeVariablesFromUse(type, element);
+    dependentTypesHelper.atParameterizedTypeUse(f, element);
+    return f;
+  }
+
+  /**
+   * Returns the empty store.
+   *
+   * @return the empty store
+   */
+  public Store getEmptyStore() {
+    return emptyStore;
+  }
+
+  /**
+   * Returns the AnnotatedTypeFactory of the subchecker and copies the current visitor state to the
+   * sub-factory so that the types are computed properly. Because the visitor state is copied, call
+   * this method each time a subfactory is needed rather than store the returned subfactory in a
+   * field.
+   *
+   * @see BaseTypeChecker#getTypeFactoryOfSubchecker(Class)
+   */
+  @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse
+  public <T extends GenericAnnotatedTypeFactory<?, ?, ?, ?>, U extends BaseTypeChecker>
+      T getTypeFactoryOfSubchecker(Class<U> checkerClass) {
+    T subFactory = checker.getTypeFactoryOfSubchecker(checkerClass);
+    if (subFactory != null && subFactory.getVisitorState() != null) {
+      // Copy the visitor state so that the types are computed properly.
+      VisitorState subFactoryVisitorState = subFactory.getVisitorState();
+      subFactoryVisitorState.setPath(visitorState.getPath());
+      subFactoryVisitorState.setClassTree(visitorState.getClassTree());
+      subFactoryVisitorState.setClassType(visitorState.getClassType());
+      subFactoryVisitorState.setMethodTree(visitorState.getMethodTree());
+      subFactoryVisitorState.setMethodReceiver(visitorState.getMethodReceiver());
+    }
+    return subFactory;
+  }
+
+  /**
+   * Should the local variable default annotation be applied to type variables?
+   *
+   * <p>It is initialized to true if data flow is used by the checker. It is set to false when
+   * getting the assignment context for type argument inference.
+   *
+   * @see GenericAnnotatedTypeFactory#getAnnotatedTypeLhsNoTypeVarDefault
+   * @return shouldDefaultTypeVarLocals
+   */
+  public boolean getShouldDefaultTypeVarLocals() {
+    return shouldDefaultTypeVarLocals;
+  }
+
+  /** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */
+  protected final CFGVisualizer<Value, Store, TransferFunction> cfgVisualizer;
+
+  /**
+   * Create a new CFGVisualizer.
+   *
+   * @return a new CFGVisualizer, or null if none will be used on this run
+   */
+  protected @Nullable CFGVisualizer<Value, Store, TransferFunction> createCFGVisualizer() {
+    if (checker.hasOption("flowdotdir")) {
+      String flowdotdir = checker.getOption("flowdotdir");
+      if (flowdotdir.equals("")) {
+        throw new UserError("Emtpy string provided for -Aflowdotdir command-line argument");
+      }
+      boolean verbose = checker.hasOption("verbosecfg");
+
+      Map<String, Object> args = new HashMap<>(2);
+      args.put("outdir", flowdotdir);
+      args.put("verbose", verbose);
+      args.put("checkerName", getCheckerName());
+
+      CFGVisualizer<Value, Store, TransferFunction> res = new DOTCFGVisualizer<>();
+      res.init(args);
+      return res;
+    } else if (checker.hasOption("cfgviz")) {
+      String cfgviz = checker.getOption("cfgviz");
+      if (cfgviz == null) {
+        throw new UserError(
+            "-Acfgviz specified without arguments, should be -Acfgviz=VizClassName[,opts,...]");
+      }
+      String[] opts = cfgviz.split(",");
+      String vizClassName = opts[0];
+      if (!Signatures.isBinaryName(vizClassName)) {
+        throw new UserError(
+            "Bad -Acfgviz class name \"%s\", should be a binary name.", vizClassName);
+      }
+
+      Map<String, Object> args = processCFGVisualizerOption(opts);
+      if (!args.containsKey("verbose")) {
+        boolean verbose = checker.hasOption("verbosecfg");
+        args.put("verbose", verbose);
+      }
+      args.put("checkerName", getCheckerName());
+
+      CFGVisualizer<Value, Store, TransferFunction> res =
+          BaseTypeChecker.invokeConstructorFor(vizClassName, null, null);
+      res.init(args);
+      return res;
+    }
+    // Nobody expected to use cfgVisualizer if neither option given.
+    return null;
+  }
+
+  /** A simple utility method to determine a short checker name to be used by CFG visualizations. */
+  private String getCheckerName() {
+    String checkerName = checker.getClass().getSimpleName();
+    if (checkerName.endsWith("Checker")) {
+      checkerName = checkerName.substring(0, checkerName.length() - "Checker".length());
+    } else if (checkerName.endsWith("Subchecker")) {
+      checkerName = checkerName.substring(0, checkerName.length() - "Subchecker".length());
+    }
+    return checkerName;
+  }
+
+  /**
+   * Parse keys or key-value pairs into a map from key to value (to true if no value is provided).
+   *
+   * @param opts the CFG visualization options
+   * @return a map that represents the options
+   */
+  private Map<String, Object> processCFGVisualizerOption(String[] opts) {
+    Map<String, Object> res = new HashMap<>(opts.length - 1);
+    // Index 0 is the visualizer class name and can be ignored.
+    for (int i = 1; i < opts.length; ++i) {
+      String opt = opts[i];
+      String[] split = opt.split("=");
+      switch (split.length) {
+        case 1:
+          res.put(split[0], true);
+          break;
+        case 2:
+          res.put(split[0], split[1]);
+          break;
+        default:
+          throw new UserError("Too many '=' in cfgviz option: " + opt);
+      }
+    }
+    return res;
+  }
+
+  /** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */
+  public CFGVisualizer<Value, Store, TransferFunction> getCFGVisualizer() {
+    return cfgVisualizer;
+  }
+
+  @Override
+  public void postAsMemberOf(AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) {
+    super.postAsMemberOf(type, owner, element);
+    if (element.getKind() == ElementKind.FIELD) {
+      poly.resolve(((VariableElement) element), owner, type);
+    }
+  }
+
+  /**
+   * Adds default qualifiers based on the underlying type of {@code type} to {@code type}. If {@code
+   * element} is a local variable, then the defaults are not added.
+   *
+   * <p>(This uses both the {@link DefaultQualifierForUseTypeAnnotator} and {@link
+   * DefaultForTypeAnnotator}.)
+   *
+   * @param element possibly null element whose type is {@code type}
+   * @param type the type to which defaults are added
+   */
+  protected void addAnnotationsFromDefaultForType(
+      @Nullable Element element, AnnotatedTypeMirror type) {
+    if (element != null && element.getKind() == ElementKind.LOCAL_VARIABLE) {
+      if (type.getKind() == TypeKind.DECLARED) {
+        // If this is a type for a local variable, don't apply the default to the primary location.
+        AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type;
+        if (declaredType.getEnclosingType() != null) {
+          defaultQualifierForUseTypeAnnotator.visit(declaredType.getEnclosingType());
+          defaultForTypeAnnotator.visit(declaredType.getEnclosingType());
+        }
+        for (AnnotatedTypeMirror typeArg : declaredType.getTypeArguments()) {
+          defaultQualifierForUseTypeAnnotator.visit(typeArg);
+          defaultForTypeAnnotator.visit(typeArg);
+        }
+      } else if (type.getKind().isPrimitive()) {
+        // Don't apply the default for local variables with primitive types.
+      } else {
+        defaultQualifierForUseTypeAnnotator.visit(type);
+        defaultForTypeAnnotator.visit(type);
+      }
+    } else {
+      defaultQualifierForUseTypeAnnotator.visit(type);
+      defaultForTypeAnnotator.visit(type);
+    }
+  }
+
+  // You can change this temporarily to produce verbose logs.
+  /** Whether to output verbose, low-level debugging messages. */
+  private static final boolean debug = false;
+
+  /**
+   * Output a message, if logging is on.
+   *
+   * @param format a format string
+   * @param args arguments to the format string
+   */
+  @FormatMethod
+  private static void log(String format, Object... args) {
+    if (debug) {
+      SystemPlume.sleep(1); // logging can interleave with typechecker output
+      System.out.printf(format, args);
+    }
+  }
+
+  /**
+   * Cache of types found that are relevantTypes or subclass of supported types. Used so that
+   * isSubtype doesn't need to be called repeatedly on the same types.
+   */
+  private Map<TypeMirror, Boolean> allFoundRelevantTypes = CollectionUtils.createLRUCache(300);
+
+  /**
+   * Returns true if users can write type annotations from this type system on the given Java type.
+   *
+   * @param tm a type
+   * @return true if users can write type annotations from this type system on the given Java type
+   */
+  public boolean isRelevant(TypeMirror tm) {
+    tm = types.erasure(tm);
+    Boolean cachedResult = allFoundRelevantTypes.get(tm);
+    if (cachedResult != null) {
+      return cachedResult;
+    }
+    boolean result = isRelevantHelper(tm);
+    allFoundRelevantTypes.put(tm, result);
+    return result;
+  }
+
+  /**
+   * Returns true if users can write type annotations from this type system on the given Java type.
+   * Does not use a cache. Is a helper method for {@link #isRelevant}.
+   *
+   * @param tm a type
+   * @return true if users can write type annotations from this type system on the given Java type
+   */
+  private boolean isRelevantHelper(TypeMirror tm) {
+
+    if (relevantJavaTypes.contains(tm)) {
+      return true;
+    }
+
+    switch (tm.getKind()) {
+
+        // Primitives have no subtyping relationships, but the lookup might have failed
+        // because tm has metadata such as annotations.
+      case BOOLEAN:
+      case BYTE:
+      case CHAR:
+      case DOUBLE:
+      case FLOAT:
+      case INT:
+      case LONG:
+      case SHORT:
+        for (TypeMirror relevantJavaType : relevantJavaTypes) {
+          if (types.isSameType(tm, relevantJavaType)) {
+            return true;
+          }
+        }
+        return false;
+
+        // Void is never relevant
+      case VOID:
+        return false;
+
+      case ARRAY:
+        return arraysAreRelevant;
+
+      case DECLARED:
+        for (TypeMirror relevantJavaType : relevantJavaTypes) {
+          if (types.isSubtype(relevantJavaType, tm) || types.isSubtype(tm, relevantJavaType)) {
+            return true;
+          }
+        }
+        return false;
+
+      case TYPEVAR:
+        return isRelevant(((TypeVariable) tm).getUpperBound());
+
+      default:
+        throw new BugInCF("isRelevantHelper(%s): Unexpected TypeKind %s", tm, tm.getKind());
+    }
+  }
+
+  /**
+   * Return the type of the default value of the given type. The default value is 0, false, or null.
+   *
+   * @param typeMirror a type
+   * @return the annotated type of {@code type}'s default value
+   */
+  // TODO: Cache results to avoid recomputation.
+  public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) {
+    AnnotatedTypeMirror defaultValue = AnnotatedTypeMirror.createType(typeMirror, this, false);
+    addComputedTypeAnnotations(
+        TreeUtils.getDefaultValueTree(typeMirror, processingEnv), defaultValue, false);
+    return defaultValue;
+  }
+
+  /**
+   * Return the contract annotations (that is, pre- and post-conditions) for the given AMethod. Does
+   * not modify the AMethod. This method must only be called when using WholeProgramInferenceScenes.
+   *
+   * @param m AFU representation of a method
+   * @return contract annotations for the method
+   */
+  public List<AnnotationMirror> getContractAnnotations(AMethod m) {
+    List<AnnotationMirror> preconds = getPreconditionAnnotations(m);
+    List<AnnotationMirror> postconds = getPostconditionAnnotations(m, preconds);
+    List<AnnotationMirror> result = preconds;
+    result.addAll(postconds);
+    return result;
+  }
+
+  /**
+   * Return the precondition annotations for the given AMethod. Does not modify the AMethod. This
+   * method must only be called when using WholeProgramInferenceScenes.
+   *
+   * @param m AFU representation of a method
+   * @return precondition annotations for the method
+   */
+  public List<AnnotationMirror> getPreconditionAnnotations(AMethod m) {
+    List<AnnotationMirror> result = new ArrayList<>(m.getPreconditions().size());
+    for (Map.Entry<VariableElement, AField> entry : m.getPreconditions().entrySet()) {
+      WholeProgramInferenceImplementation<?> wholeProgramInference =
+          (WholeProgramInferenceImplementation<?>) getWholeProgramInference();
+      WholeProgramInferenceScenesStorage storage =
+          (WholeProgramInferenceScenesStorage) wholeProgramInference.getStorage();
+      TypeMirror typeMirror = entry.getKey().asType();
+      AnnotatedTypeMirror inferredType =
+          storage.atmFromStorageLocation(typeMirror, entry.getValue().type);
+      result.addAll(getPreconditionAnnotation(entry.getKey(), inferredType));
+    }
+    Collections.sort(result, Ordering.usingToString());
+    return result;
+  }
+
+  /**
+   * Return the postcondition annotations for the given AMethod. Does not modify the AMethod. This
+   * method must only be called when using WholeProgramInferenceScenes.
+   *
+   * @param m AFU representation of a method
+   * @param preconds the precondition annotations for the method; used to suppress redundant
+   *     postconditions
+   * @return postcondition annotations for the method
+   */
+  public List<AnnotationMirror> getPostconditionAnnotations(
+      AMethod m, List<AnnotationMirror> preconds) {
+    List<AnnotationMirror> result = new ArrayList<>(m.getPostconditions().size());
+    for (Map.Entry<VariableElement, AField> entry : m.getPostconditions().entrySet()) {
+      WholeProgramInferenceImplementation<?> wholeProgramInference =
+          (WholeProgramInferenceImplementation<?>) getWholeProgramInference();
+      WholeProgramInferenceScenesStorage storage =
+          (WholeProgramInferenceScenesStorage) wholeProgramInference.getStorage();
+      TypeMirror typeMirror = entry.getKey().asType();
+      AnnotatedTypeMirror inferredType =
+          storage.atmFromStorageLocation(typeMirror, entry.getValue().type);
+      result.addAll(getPostconditionAnnotation(entry.getKey(), inferredType, preconds));
+    }
+    Collections.sort(result, Ordering.usingToString());
+    return result;
+  }
+
+  /**
+   * Return the contract annotations (that is, pre- and post-conditions) for the given
+   * CallableDeclarationAnnos. Does not modify the CallableDeclarationAnnos.
+   *
+   * @param methodAnnos annotation data for a method
+   * @return contract annotations for the method
+   */
+  public List<AnnotationMirror> getContractAnnotations(
+      WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) {
+    List<AnnotationMirror> preconds = getPreconditionAnnotations(methodAnnos);
+    List<AnnotationMirror> postconds = getPostconditionAnnotations(methodAnnos, preconds);
+    List<AnnotationMirror> result = preconds;
+    result.addAll(postconds);
+    return result;
+  }
+
+  /**
+   * Return the precondition annotations for the given CallableDeclarationAnnos. Does not modify the
+   * CallableDeclarationAnnos.
+   *
+   * @param methodAnnos annotation data for a method
+   * @return precondition annotations for the method
+   */
+  public List<AnnotationMirror> getPreconditionAnnotations(
+      WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) {
+    List<AnnotationMirror> result = new ArrayList<>();
+    for (Map.Entry<VariableElement, AnnotatedTypeMirror> entry :
+        methodAnnos.getFieldToPreconditions().entrySet()) {
+      result.addAll(getPreconditionAnnotation(entry.getKey(), entry.getValue()));
+    }
+    Collections.sort(result, Ordering.usingToString());
+    return result;
+  }
+
+  /**
+   * Return the postcondition annotations for the given CallableDeclarationAnnos. Does not modify
+   * the CallableDeclarationAnnos.
+   *
+   * @param methodAnnos annotation data for a method
+   * @param preconds the precondition annotations for the method; used to suppress redundant
+   *     postconditions
+   * @return postcondition annotations for the method
+   */
+  public List<AnnotationMirror> getPostconditionAnnotations(
+      WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos,
+      List<AnnotationMirror> preconds) {
+    List<AnnotationMirror> result = new ArrayList<>();
+    for (Map.Entry<VariableElement, AnnotatedTypeMirror> entry :
+        methodAnnos.getFieldToPostconditions().entrySet()) {
+      result.addAll(getPostconditionAnnotation(entry.getKey(), entry.getValue(), preconds));
+    }
+    Collections.sort(result, Ordering.usingToString());
+    return result;
+  }
+
+  /**
+   * Return a list of precondition annotations for the given field. Returns an empty list if none
+   * can be created, because the qualifier has elements/arguments, which {@code @RequiresQualifier}
+   * does not support.
+   *
+   * <p>This is of the form {@code @RequiresQualifier(expression="this.elt",
+   * qualifier=MyQual.class)} when elt is declared as {@code @A} or {@code @Poly*} and f contains
+   * {@code @B} which is a sub-qualifier of {@code @A}.
+   *
+   * @param elt element for a field, which is declared in the same class as the method
+   * @param inferredType the type of the field, on method entry
+   * @return precondition annotations for the element (possibly an empty list)
+   */
+  public List<AnnotationMirror> getPreconditionAnnotation(
+      VariableElement elt, AnnotatedTypeMirror inferredType) {
+    return getPreOrPostconditionAnnotation(elt, inferredType, BeforeOrAfter.BEFORE, null);
+  }
+
+  /**
+   * Returns an {@code @EnsuresQualifier} annotation for the given field. Returns an empty list if
+   * none can be created, because the qualifier has elements/arguments, which
+   * {@code @EnsuresQualifier} does not support.
+   *
+   * <p>This implementation makes no assumptions about preconditions suppressing postconditions, but
+   * subclasses may do so.
+   *
+   * <p>This is of the form {@code @EnsuresQualifier(expression="this.elt", qualifier=MyQual.class)}
+   * when elt is declared as {@code @A} or {@code @Poly*} and f contains {@code @B} which is a
+   * sub-qualifier of {@code @A}.
+   *
+   * @param elt element for a field
+   * @param inferredType the type of the field, on method exit
+   * @param preconds the precondition annotations for the method; used to suppress redundant
+   *     postconditions
+   * @return postcondition annotations for the element (possibly an empty list)
+   */
+  public List<AnnotationMirror> getPostconditionAnnotation(
+      VariableElement elt, AnnotatedTypeMirror inferredType, List<AnnotationMirror> preconds) {
+    return getPreOrPostconditionAnnotation(elt, inferredType, BeforeOrAfter.AFTER, preconds);
+  }
+
+  /**
+   * Helper method for {@link #getPreconditionAnnotation} and {@link #getPostconditionAnnotation}.
+   *
+   * <p>Returns a {@code @RequiresQualifier} or {@code @EnsuresQualifier} annotation for the given
+   * field. Returns an empty list if none can be created, because the qualifier has
+   * elements/arguments, which {@code @RequiresQualifier} and {@code @EnsuresQualifier} do not
+   * support.
+   *
+   * <p>This implementation makes no assumptions about preconditions suppressing postconditions, but
+   * subclasses may do so.
+   *
+   * @param elt element for a field
+   * @param inferredType the type of the field, on method entry or exit (depending on the value of
+   *     {@code preOrPost})
+   * @param preOrPost whether to return preconditions or postconditions
+   * @param preconds the precondition annotations for the method; used to suppress redundant
+   *     postconditions; non-null exactly when {@code preOrPost} is {@code AFTER}
+   * @return precondition or postcondition annotations for the element (possibly an empty list)
+   */
+  protected List<AnnotationMirror> getPreOrPostconditionAnnotation(
+      VariableElement elt,
+      AnnotatedTypeMirror inferredType,
+      Analysis.BeforeOrAfter preOrPost,
+      @Nullable List<AnnotationMirror> preconds) {
+    assert (preOrPost == BeforeOrAfter.BEFORE) == (preconds == null);
+
+    if (getWholeProgramInference() == null) {
+      return Collections.emptyList();
+    }
+
+    AnnotatedTypeMirror declaredType = fromElement(elt);
+
+    // TODO: should this only check the top-level annotations?
+    if (declaredType.equals(inferredType)) {
+      return Collections.emptyList();
+    }
+
+    List<AnnotationMirror> result = new ArrayList<AnnotationMirror>();
+    for (AnnotationMirror inferredAm : inferredType.getAnnotations()) {
+      AnnotationMirror declaredAm = declaredType.getAnnotationInHierarchy(inferredAm);
+      if (declaredAm == null && declaredType.getKind() != TypeKind.TYPEVAR) {
+        // There is no explicitly-written annotation for the given qualifier hierarchy.
+        // Determine the default.
+        addComputedTypeAnnotations(elt, declaredType);
+        declaredAm = declaredType.getAnnotationInHierarchy(inferredAm);
+        if (declaredAm == null) {
+          throw new BugInCF(
+              "getPreOrPostconditionAnnotation(%s, %s): no defaulted annotation%n  declaredType=%s"
+                  + "  [%s %s]%n  inferredType=%s  [%s %s]%n",
+              elt,
+              inferredType,
+              declaredType.toString(true),
+              declaredType.getKind(),
+              declaredType.getClass(),
+              inferredType.toString(true),
+              inferredType.getKind(),
+              inferredType.getClass());
+        }
+      }
+
+      if (declaredAm == null || AnnotationUtils.areSame(inferredAm, declaredAm)) {
+        continue;
+      }
+      // inferredAm must be a subtype of declaredAm (since they are not equal).
+      AnnotationMirror anno = requiresOrEnsuresQualifierAnno(elt, inferredAm, preOrPost);
+      if (anno != null) {
+        result.add(anno);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns a {@code RequiresQualifier("...")} or {@code EnsuresQualifier("...")} annotation for
+   * the given field. Returns null if none can be created, because the qualifier has
+   * elements/arguments, which {@code @RequiresQualifier} and {@code @EnsuresQualifier} do not
+   * support.
+   *
+   * <p>This is of the form {@code @RequiresQualifier(expression="this.elt",
+   * qualifier=MyQual.class)} or {@code @EnsuresQualifier(expression="this.elt",
+   * qualifier=MyQual.class)} when elt is declared as {@code @A} or {@code @Poly*} and f contains
+   * {@code @B} which is a sub-qualifier of {@code @A}.
+   *
+   * @param fieldElement a field
+   * @param qualifier the qualifier that must be present
+   * @param preOrPost whether to return a precondition or postcondition annotation
+   * @return a {@code RequiresQualifier("...")} or {@code EnsuresQualifier("...")} annotation for
+   *     the given field, or null
+   */
+  protected @Nullable AnnotationMirror requiresOrEnsuresQualifierAnno(
+      VariableElement fieldElement, AnnotationMirror qualifier, Analysis.BeforeOrAfter preOrPost) {
+    if (!qualifier.getElementValues().isEmpty()) {
+      // @RequiresQualifier does not yet support annotations with elements/arguments.
+      return null;
+    }
+
+    AnnotationBuilder builder =
+        new AnnotationBuilder(
+            processingEnv,
+            preOrPost == BeforeOrAfter.BEFORE ? RequiresQualifier.class : EnsuresQualifier.class);
+    String receiver = JavaExpression.getImplicitReceiver(fieldElement).toString();
+    String expression = receiver + "." + fieldElement.getSimpleName();
+    builder.setValue("expression", new String[] {expression});
+    builder.setValue("qualifier", AnnotationUtils.annotationMirrorToClass(qualifier));
+    return builder.build();
+  }
+
+  /**
+   * Add a new entry to the shared CFG. If this is a subchecker, this method delegates to the
+   * superchecker's GenericAnnotatedTypeFactory, if it exists. Duplicate keys must map to the same
+   * CFG.
+   *
+   * <p>Calls to this method should be guarded by checking {@link #hasOrIsSubchecker}; it is
+   * nonsensical to have a shared CFG when a checker is running alone.
+   *
+   * @param tree the source code corresponding to cfg
+   * @param cfg the control flow graph to use for tree
+   * @return whether a shared CFG was found to actually add to (duplicate keys also return true)
+   */
+  public boolean addSharedCFGForTree(Tree tree, ControlFlowGraph cfg) {
+    if (!shouldCache) {
+      return false;
+    }
+    BaseTypeChecker parentChecker = this.checker.getUltimateParentChecker();
+    @SuppressWarnings("interning") // Checking reference equality.
+    boolean parentIsThisChecker = parentChecker == this.checker;
+    if (parentIsThisChecker) {
+      // This is the ultimate parent.
+      if (this.subcheckerSharedCFG == null) {
+        this.subcheckerSharedCFG = new HashMap<>(getCacheSize());
+      }
+      if (!this.subcheckerSharedCFG.containsKey(tree)) {
+        this.subcheckerSharedCFG.put(tree, cfg);
+      } else {
+        assert this.subcheckerSharedCFG.get(tree).equals(cfg);
+      }
+      return true;
+    }
+
+    // This is a subchecker.
+    if (parentChecker != null) {
+      GenericAnnotatedTypeFactory<?, ?, ?, ?> parentAtf = parentChecker.getTypeFactory();
+      return parentAtf.addSharedCFGForTree(tree, cfg);
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Get the shared control flow graph used for {@code tree} by this checker's topmost superchecker.
+   * Returns null if no information is available about the given tree, or if this checker has a
+   * parent checker that does not have a GenericAnnotatedTypeFactory.
+   *
+   * <p>Calls to this method should be guarded by checking {@link #hasOrIsSubchecker}; it is
+   * nonsensical to have a shared CFG when a checker is running alone.
+   *
+   * @param tree the tree whose CFG should be looked up
+   * @return the CFG stored by this checker's uppermost superchecker for tree, or null if it is not
+   *     available
+   */
+  public @Nullable ControlFlowGraph getSharedCFGForTree(Tree tree) {
+    if (!shouldCache) {
+      return null;
+    }
+    BaseTypeChecker parentChecker = this.checker.getUltimateParentChecker();
+    @SuppressWarnings("interning") // Checking reference equality.
+    boolean parentIsThisChecker = parentChecker == this.checker;
+    if (parentIsThisChecker) {
+      // This is the ultimate parent;
+      return this.subcheckerSharedCFG == null
+          ? null
+          : this.subcheckerSharedCFG.getOrDefault(tree, null);
+    }
+
+    // This is a subchecker.
+    if (parentChecker != null) {
+      GenericAnnotatedTypeFactory<?, ?, ?, ?> parentAtf = parentChecker.getTypeFactory();
+      return parentAtf.getSharedCFGForTree(tree);
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * If kind = CONDITIONALPOSTCONDITION, return the result element, e.g. {@link
+   * EnsuresQualifierIf#result}. Otherwise, return null.
+   *
+   * @param kind the kind of {@code contractAnnotation}
+   * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link
+   *     EnsuresQualifierIf}
+   * @return the {@code result} element of {@code contractAnnotation}, or null if it doesn't have a
+   *     {@code result} element
+   */
+  public Boolean getEnsuresQualifierIfResult(
+      Contract.Kind kind, AnnotationMirror contractAnnotation) {
+    if (kind == Contract.Kind.CONDITIONALPOSTCONDITION) {
+      if (contractAnnotation instanceof EnsuresQualifierIf) {
+        // It's the framework annotation @EnsuresQualifierIf
+        return AnnotationUtils.getElementValueBoolean(
+            contractAnnotation, ensuresQualifierIfResultElement, /*default is irrelevant*/ false);
+      } else {
+        // It's a checker-specific annotation such as @EnsuresMinLenIf
+        @SuppressWarnings("deprecation") // concrete annotation class is not known
+        Boolean result =
+            AnnotationUtils.getElementValue(contractAnnotation, "result", Boolean.class, false);
+        return result;
+      }
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * If {@code contractAnnotation} is a framework annotation, return its {@code expression} element.
+   * Otherwise, {@code contractAnnotation} is defined in a checker. If kind =
+   * CONDITIONALPOSTCONDITION, return its {@code expression} element, else return its {@code value}
+   * element.
+   *
+   * @param kind the kind of {@code contractAnnotation}
+   * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link
+   *     EnsuresQualifierIf}
+   * @return the {@code result} element of {@code contractAnnotation}, or null if it doesn't have a
+   *     {@code result} element
+   */
+  public List<String> getContractExpressions(
+      Contract.Kind kind, AnnotationMirror contractAnnotation) {
+    // First, handle framework annotations.
+    if (contractAnnotation instanceof RequiresQualifier) {
+      return AnnotationUtils.getElementValueArray(
+          contractAnnotation, requiresQualifierExpressionElement, String.class);
+    } else if (contractAnnotation instanceof EnsuresQualifier) {
+      return AnnotationUtils.getElementValueArray(
+          contractAnnotation, ensuresQualifierExpressionElement, String.class);
+    } else if (contractAnnotation instanceof EnsuresQualifierIf) {
+      return AnnotationUtils.getElementValueArray(
+          contractAnnotation, ensuresQualifierIfExpressionElement, String.class);
+    }
+    // `contractAnnotation` is defined in a checker.
+    String elementName = kind == Contract.Kind.CONDITIONALPOSTCONDITION ? "expression" : "value";
+    @SuppressWarnings("deprecation") // concrete annotation class is not known
+    List<String> result =
+        AnnotationUtils.getElementValueArray(contractAnnotation, elementName, String.class, true);
+    return result;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/HashcodeAtmVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/HashcodeAtmVisitor.java
new file mode 100644
index 0000000..1796ef1
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/HashcodeAtmVisitor.java
@@ -0,0 +1,37 @@
+package org.checkerframework.framework.type;
+
+import java.util.Objects;
+import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
+
+/**
+ * Computes the hashcode of an AnnotatedTypeMirror using the underlying type and primary annotations
+ * and the hash code of component types of AnnotatedTypeMirror.
+ *
+ * <p>This class should be synchronized with EqualityAtmComparer.
+ *
+ * @see org.checkerframework.framework.type.EqualityAtmComparer for more details.
+ *     <p>This is used by AnnotatedTypeMirror.hashcode.
+ */
+public class HashcodeAtmVisitor extends SimpleAnnotatedTypeScanner<Integer, Void> {
+
+  /** Creates a {@link HashcodeAtmVisitor}. */
+  public HashcodeAtmVisitor() {
+    super(Integer::sum, 0);
+  }
+
+  /**
+   * Generates hashcode for type using the underlying type and the primary annotation. This method
+   * does not descend into component types (this occurs in the scan method)
+   *
+   * @param type the type
+   */
+  @Override
+  protected Integer defaultAction(AnnotatedTypeMirror type, Void v) {
+    // To differentiate between partially initialized type's (which may have null components)
+    // and fully initialized types, null values are allowed
+    if (type == null) {
+      return 0;
+    }
+    return Objects.hash(type.getUnderlyingTypeHashCode(), type.getAnnotations().toString());
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/MostlyNoElementQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/MostlyNoElementQualifierHierarchy.java
new file mode 100644
index 0000000..7dbbf03
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/MostlyNoElementQualifierHierarchy.java
@@ -0,0 +1,154 @@
+package org.checkerframework.framework.type;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.framework.util.QualifierKindHierarchy;
+
+/**
+ * A {@link org.checkerframework.framework.type.QualifierHierarchy} where qualifiers may be
+ * represented by annotations with elements, but most of the qualifiers do not have elements. In
+ * contrast to {@link org.checkerframework.framework.type.ElementQualifierHierarchy}, this class
+ * partially implements {@link #isSubtype(AnnotationMirror, AnnotationMirror)}, {@link
+ * #leastUpperBound(AnnotationMirror, AnnotationMirror)}, and {@link
+ * #greatestLowerBound(AnnotationMirror, AnnotationMirror)} and calls *WithElements when the result
+ * cannot be computed from the meta-annotations {@link
+ * org.checkerframework.framework.qual.SubtypeOf}.
+ *
+ * <p>Subclasses must implement the following methods when annotations have elements:
+ *
+ * <ul>
+ *   <li>{@link #isSubtypeWithElements(AnnotationMirror, QualifierKind, AnnotationMirror,
+ *       QualifierKind)}
+ *   <li>{@link #leastUpperBoundWithElements(AnnotationMirror, QualifierKind, AnnotationMirror,
+ *       QualifierKind,QualifierKind)}
+ *   <li>{@link #greatestLowerBoundWithElements(AnnotationMirror, QualifierKind, AnnotationMirror,
+ *       QualifierKind,QualifierKind)}
+ * </ul>
+ *
+ * <p>MostlyNoElementQualifierHierarchy uses a {@link QualifierKindHierarchy} to model the
+ * relationships between qualifiers. Subclasses can override {@link
+ * #createQualifierKindHierarchy(Collection)} to return a subclass of QualifierKindHierarchy.
+ */
+@AnnotatedFor("nullness")
+public abstract class MostlyNoElementQualifierHierarchy extends ElementQualifierHierarchy {
+
+  /**
+   * Creates a MostlyNoElementQualifierHierarchy from the given classes.
+   *
+   * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+   * @param elements element utils
+   */
+  protected MostlyNoElementQualifierHierarchy(
+      Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+    super(qualifierClasses, elements);
+  }
+
+  @Override
+  public final boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+    QualifierKind subKind = getQualifierKind(subAnno);
+    QualifierKind superKind = getQualifierKind(superAnno);
+    if (subKind.isSubtypeOf(superKind)) {
+      if (superKind.hasElements() && subKind.hasElements()) {
+        return isSubtypeWithElements(subAnno, subKind, superAnno, superKind);
+      } else {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if {@code subAnno} is a subtype of {@code superAnno}. Both {@code subAnno} and
+   * {@code superAnno} are annotations with elements. {@code subKind} is a sub qualifier kind of
+   * {@code superKind}.
+   *
+   * @param subAnno possible subtype annotation; has elements
+   * @param subKind QualifierKind of {@code subAnno}
+   * @param superAnno possible super annotation; has elements
+   * @param superKind QualifierKind of {@code superAnno}
+   * @return true if {@code subAnno} is a subtype of {@code superAnno}
+   */
+  protected abstract boolean isSubtypeWithElements(
+      AnnotationMirror subAnno,
+      QualifierKind subKind,
+      AnnotationMirror superAnno,
+      QualifierKind superKind);
+
+  @Override
+  public final @Nullable AnnotationMirror leastUpperBound(
+      AnnotationMirror a1, AnnotationMirror a2) {
+    QualifierKind qual1 = getQualifierKind(a1);
+    QualifierKind qual2 = getQualifierKind(a2);
+    QualifierKind lub = qualifierKindHierarchy.leastUpperBound(qual1, qual2);
+    if (lub == null) {
+      // Qualifiers are not in the same hierarchy.
+      return null;
+    }
+    if (lub.hasElements()) {
+      return leastUpperBoundWithElements(a1, qual1, a2, qual2, lub);
+    }
+    return kindToElementlessQualifier.get(lub);
+  }
+
+  /**
+   * Returns the least upper bound of {@code a1} and {@code a2} in cases where the lub of {@code
+   * qualifierKind1} and {@code qualifierKind2} is a qualifier kind that has elements. If the lub of
+   * {@code qualifierKind1} and {@code qualifierKind2} does not have elements, then {@link
+   * #leastUpperBound(AnnotationMirror, AnnotationMirror)} returns the correct {@code
+   * AnnotationMirror} without calling this method.
+   *
+   * @param a1 first annotation
+   * @param qualifierKind1 QualifierKind for {@code a1}
+   * @param a2 second annotation
+   * @param qualifierKind2 QualifierKind for {@code a2}
+   * @param lubKind the kind of the lub of {@code qualifierKind1} and {@code qualifierKind2}
+   * @return the least upper bound of {@code a1} and {@code a2}
+   */
+  protected abstract AnnotationMirror leastUpperBoundWithElements(
+      AnnotationMirror a1,
+      QualifierKind qualifierKind1,
+      AnnotationMirror a2,
+      QualifierKind qualifierKind2,
+      QualifierKind lubKind);
+
+  @Override
+  public final @Nullable AnnotationMirror greatestLowerBound(
+      AnnotationMirror a1, AnnotationMirror a2) {
+    QualifierKind qual1 = getQualifierKind(a1);
+    QualifierKind qual2 = getQualifierKind(a2);
+    QualifierKind glb = qualifierKindHierarchy.greatestLowerBound(qual1, qual2);
+    if (glb == null) {
+      // Qualifiers are not in the same hierarchy.
+      return null;
+    }
+    if (glb.hasElements()) {
+      return greatestLowerBoundWithElements(a1, qual1, a2, qual2, glb);
+    }
+    return kindToElementlessQualifier.get(glb);
+  }
+
+  /**
+   * Returns the greatest lower bound of {@code a1} and {@code a2} in cases where the glb of {@code
+   * qualifierKind1} and {@code qualifierKind2} is a qualifier kind that has elements. If the glb of
+   * {@code qualifierKind1} and {@code qualifierKind2} does not have elements, then {@link
+   * #greatestLowerBound(AnnotationMirror, AnnotationMirror)} returns the correct {@code
+   * AnnotationMirror} without calling this method.
+   *
+   * @param a1 first annotation
+   * @param qualifierKind1 QualifierKind for {@code a1}
+   * @param a2 second annotation
+   * @param qualifierKind2 QualifierKind for {@code a2}
+   * @return the greatest lower bound between {@code a1} and {@code a2}
+   */
+  protected abstract AnnotationMirror greatestLowerBoundWithElements(
+      AnnotationMirror a1,
+      QualifierKind qualifierKind1,
+      AnnotationMirror a2,
+      QualifierKind qualifierKind2,
+      QualifierKind glbKind);
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java
new file mode 100644
index 0000000..9f3d635
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java
@@ -0,0 +1,245 @@
+package org.checkerframework.framework.type;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.util.DefaultQualifierKindHierarchy;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.framework.util.QualifierKindHierarchy;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypeSystemError;
+
+/**
+ * A {@link QualifierHierarchy} where no qualifier has arguments; that is, no qualifier is
+ * represented by an annotation with elements. The meta-annotation {@link
+ * org.checkerframework.framework.qual.SubtypeOf} specifies the subtyping relationships.
+ *
+ * <p>It uses a {@link QualifierKindHierarchy} to model the relationships between qualifiers.
+ * Subclasses can override {@link #createQualifierKindHierarchy(Collection)} to return a subclass of
+ * QualifierKindHierarchy.
+ */
+@AnnotatedFor("nullness")
+public class NoElementQualifierHierarchy implements QualifierHierarchy {
+
+  /** {@link QualifierKindHierarchy}. */
+  protected final QualifierKindHierarchy qualifierKindHierarchy;
+
+  /** Set of top annotation mirrors. */
+  protected final Set<AnnotationMirror> tops;
+
+  /** Set of bottom annotation mirrors. */
+  protected final Set<AnnotationMirror> bottoms;
+
+  /** Mapping from {@link QualifierKind} to its corresponding {@link AnnotationMirror}. */
+  protected final Map<QualifierKind, AnnotationMirror> kindToAnnotationMirror;
+
+  /** Set of all annotations in all the hierarchies. */
+  protected final Set<? extends AnnotationMirror> qualifiers;
+
+  /**
+   * Creates a NoElementQualifierHierarchy from the given classes.
+   *
+   * @param qualifierClasses classes of annotations that are the qualifiers
+   * @param elements element utils
+   */
+  public NoElementQualifierHierarchy(
+      Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+    this.qualifierKindHierarchy = createQualifierKindHierarchy(qualifierClasses);
+
+    this.kindToAnnotationMirror = createAnnotationMirrors(elements);
+    this.qualifiers =
+        AnnotationUtils.createUnmodifiableAnnotationSet(kindToAnnotationMirror.values());
+
+    this.tops = createTops();
+    this.bottoms = createBottoms();
+  }
+
+  /**
+   * Create the {@link QualifierKindHierarchy}. (Subclasses may override to return a subclass of
+   * QualifierKindHierarchy.)
+   *
+   * @param qualifierClasses classes of annotations that are the qualifiers
+   * @return the newly created qualifier kind hierarchy
+   */
+  protected QualifierKindHierarchy createQualifierKindHierarchy(
+      @UnderInitialization NoElementQualifierHierarchy this,
+      Collection<Class<? extends Annotation>> qualifierClasses) {
+    return new DefaultQualifierKindHierarchy(qualifierClasses);
+  }
+
+  /**
+   * Creates and returns a mapping from qualifier kind to an annotation mirror created from the
+   * qualifier kind's annotation class.
+   *
+   * @param elements element utils
+   * @return a mapping from qualifier kind to its annotation mirror
+   */
+  @RequiresNonNull("this.qualifierKindHierarchy")
+  protected Map<QualifierKind, AnnotationMirror> createAnnotationMirrors(
+      @UnderInitialization NoElementQualifierHierarchy this, Elements elements) {
+    Map<QualifierKind, AnnotationMirror> quals = new TreeMap<>();
+    for (QualifierKind kind : qualifierKindHierarchy.allQualifierKinds()) {
+      if (kind.hasElements()) {
+        throw new TypeSystemError(kind + "has elements");
+      }
+      quals.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass()));
+    }
+    return Collections.unmodifiableMap(quals);
+  }
+
+  /**
+   * Creates and returns the unmodifiable set of top {@link AnnotationMirror}s.
+   *
+   * @return the unmodifiable set of top {@link AnnotationMirror}s
+   */
+  @RequiresNonNull({"this.kindToAnnotationMirror", "this.qualifierKindHierarchy"})
+  protected Set<AnnotationMirror> createTops(
+      @UnderInitialization NoElementQualifierHierarchy this) {
+    Set<AnnotationMirror> tops = AnnotationUtils.createAnnotationSet();
+    for (QualifierKind top : qualifierKindHierarchy.getTops()) {
+      @SuppressWarnings(
+          "nullness:assignment" // All QualifierKinds are keys in kindToAnnotationMirror
+      )
+      @NonNull AnnotationMirror topAnno = kindToAnnotationMirror.get(top);
+      tops.add(topAnno);
+    }
+    return Collections.unmodifiableSet(tops);
+  }
+
+  /**
+   * Creates and returns the unmodifiable set of bottom {@link AnnotationMirror}s.
+   *
+   * @return the unmodifiable set of bottom {@link AnnotationMirror}s
+   */
+  @RequiresNonNull({"this.kindToAnnotationMirror", "this.qualifierKindHierarchy"})
+  protected Set<AnnotationMirror> createBottoms(
+      @UnderInitialization NoElementQualifierHierarchy this) {
+    Set<AnnotationMirror> bottoms = AnnotationUtils.createAnnotationSet();
+    for (QualifierKind bottom : qualifierKindHierarchy.getBottoms()) {
+      @SuppressWarnings(
+          "nullness:assignment" // All QualifierKinds are keys in kindToAnnotationMirror
+      )
+      @NonNull AnnotationMirror bottomAnno = kindToAnnotationMirror.get(bottom);
+      bottoms.add(bottomAnno);
+    }
+    return Collections.unmodifiableSet(bottoms);
+  }
+
+  /**
+   * Returns the {@link QualifierKind} for the given annotation.
+   *
+   * @param anno an annotation that is a qualifier in this
+   * @return the {@code QualifierKind} for the given annotation
+   */
+  protected QualifierKind getQualifierKind(AnnotationMirror anno) {
+    String name = AnnotationUtils.annotationName(anno);
+    QualifierKind kind = qualifierKindHierarchy.getQualifierKind(name);
+    if (kind == null) {
+      throw new BugInCF("Annotation not in hierarchy: %s", anno);
+    }
+    return kind;
+  }
+
+  @Override
+  public @Nullable AnnotationMirror findAnnotationInSameHierarchy(
+      Collection<? extends AnnotationMirror> annos, AnnotationMirror annotationMirror) {
+    QualifierKind kind = getQualifierKind(annotationMirror);
+    for (AnnotationMirror candidate : annos) {
+      QualifierKind candidateKind = getQualifierKind(candidate);
+      if (candidateKind.isInSameHierarchyAs(kind)) {
+        return candidate;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public @Nullable AnnotationMirror findAnnotationInHierarchy(
+      Collection<? extends AnnotationMirror> annos, AnnotationMirror top) {
+    return findAnnotationInSameHierarchy(annos, top);
+  }
+
+  @Override
+  public Set<? extends AnnotationMirror> getTopAnnotations() {
+    return tops;
+  }
+
+  @Override
+  @SuppressWarnings(
+      "nullness:return" // every QualifierKind is a key in its corresponding kindToAnnotationMirror
+  )
+  public AnnotationMirror getTopAnnotation(AnnotationMirror start) {
+    QualifierKind kind = getQualifierKind(start);
+    return kindToAnnotationMirror.get(kind.getTop());
+  }
+
+  @Override
+  public Set<? extends AnnotationMirror> getBottomAnnotations() {
+    return bottoms;
+  }
+
+  @Override
+  @SuppressWarnings(
+      "nullness:return" // every QualifierKind is a key in its corresponding kindToAnnotationMirror
+  )
+  public AnnotationMirror getBottomAnnotation(AnnotationMirror start) {
+    QualifierKind kind = getQualifierKind(start);
+    return kindToAnnotationMirror.get(kind.getBottom());
+  }
+
+  @Override
+  public @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) {
+    QualifierKind poly = getQualifierKind(start).getPolymorphic();
+    if (poly == null) {
+      return null;
+    }
+    return kindToAnnotationMirror.get(poly);
+  }
+
+  @Override
+  public boolean isPolymorphicQualifier(AnnotationMirror qualifier) {
+    return getQualifierKind(qualifier).isPoly();
+  }
+
+  @Override
+  public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+    QualifierKind subKind = getQualifierKind(subAnno);
+    QualifierKind superKind = getQualifierKind(superAnno);
+    return subKind.isSubtypeOf(superKind);
+  }
+
+  @Override
+  public @Nullable AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
+    QualifierKind qual1 = getQualifierKind(a1);
+    QualifierKind qual2 = getQualifierKind(a2);
+
+    QualifierKind lub = qualifierKindHierarchy.leastUpperBound(qual1, qual2);
+    if (lub == null) {
+      return null;
+    }
+    return kindToAnnotationMirror.get(lub);
+  }
+
+  @Override
+  public @Nullable AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
+    QualifierKind qual1 = getQualifierKind(a1);
+    QualifierKind qual2 = getQualifierKind(a2);
+    QualifierKind glb = qualifierKindHierarchy.greatestLowerBound(qual1, qual2);
+    if (glb == null) {
+      return null;
+    }
+    return kindToAnnotationMirror.get(glb);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java
new file mode 100644
index 0000000..5513523
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java
@@ -0,0 +1,798 @@
+package org.checkerframework.framework.type;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Represents multiple type qualifier hierarchies. {@link #getWidth} gives the number of hierarchies
+ * that this object represents. Each hierarchy has its own top and bottom, and subtyping
+ * relationships exist only within each hierarchy.
+ *
+ * <p>Note the distinction in terminology between a qualifier hierarchy, which has one top and one
+ * bottom, and a {@code QualifierHierarchy}, which represents multiple qualifier hierarchies.
+ *
+ * <p>All type annotations need to be type qualifiers recognized within this hierarchy.
+ *
+ * <p>This assumes that every annotated type in a program is annotated with exactly one qualifier
+ * from each hierarchy.
+ */
+@AnnotatedFor("nullness")
+public interface QualifierHierarchy {
+
+  /**
+   * Determine whether this is valid.
+   *
+   * @return whether this is valid
+   */
+  default boolean isValid() {
+    return true;
+  }
+
+  // **********************************************************************
+  // Getter methods about this hierarchy
+  // **********************************************************************
+
+  /**
+   * Returns the width of this hierarchy, i.e. the expected number of annotations on any valid type.
+   *
+   * @return the width of this QualifierHierarchy
+   */
+  default int getWidth() {
+    return getTopAnnotations().size();
+  }
+
+  /**
+   * Returns the top (ultimate super) type qualifiers in the type system. The size of this set is
+   * equal to {@link #getWidth}.
+   *
+   * @return the top (ultimate super) type qualifiers in the type system
+   */
+  Set<? extends AnnotationMirror> getTopAnnotations();
+
+  /**
+   * Return the top qualifier for the given qualifier, that is, the qualifier that is a supertype of
+   * {@code qualifier} but no further supertypes exist.
+   *
+   * @param qualifier any qualifier from one of the qualifier hierarchies represented by this
+   * @return the top qualifier of {@code qualifier}'s hierarchy
+   */
+  AnnotationMirror getTopAnnotation(AnnotationMirror qualifier);
+
+  /**
+   * Returns the bottom type qualifiers in the hierarchy. The size of this set is equal to {@link
+   * #getWidth}.
+   *
+   * @return the bottom type qualifiers in the hierarchy
+   */
+  Set<? extends AnnotationMirror> getBottomAnnotations();
+
+  /**
+   * Return the bottom for the given qualifier, that is, the qualifier that is a subtype of {@code
+   * qualifier} but no further subtypes exist.
+   *
+   * @param qualifier any qualifier from one of the qualifier hierarchies represented by this
+   * @return the bottom qualifier of {@code qualifier}'s hierarchy
+   */
+  AnnotationMirror getBottomAnnotation(AnnotationMirror qualifier);
+
+  /**
+   * Returns the polymorphic qualifier for the hierarchy containing {@code qualifier}, or {@code
+   * null} if there is no polymorphic qualifier in that hierarchy.
+   *
+   * @param qualifier any qualifier from one of the qualifier hierarchies represented by this
+   * @return the polymorphic qualifier for the hierarchy containing {@code qualifier}, or {@code
+   *     null} if there is no polymorphic qualifier in that hierarchy
+   */
+  @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror qualifier);
+
+  /**
+   * Returns {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code
+   * false}.
+   *
+   * @param qualifier qualifier
+   * @return {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code
+   *     false}.
+   */
+  boolean isPolymorphicQualifier(AnnotationMirror qualifier);
+
+  // **********************************************************************
+  // Qualifier Hierarchy Queries
+  // **********************************************************************
+
+  /**
+   * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier},
+   * according to the type qualifier hierarchy.
+   *
+   * @param subQualifier possible subqualifier of {@code superQualifier}
+   * @param superQualifier possible superqualifier of {@code subQualifier}
+   * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code superQualifier}
+   */
+  boolean isSubtype(AnnotationMirror subQualifier, AnnotationMirror superQualifier);
+
+  /**
+   * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier or equal to the
+   * qualifier in the same hierarchy in {@code superQualifiers}.
+   *
+   * @param subQualifiers set of qualifiers; exactly one per hierarchy
+   * @param superQualifiers set of qualifiers; exactly one per hierarchy
+   * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the
+   *     qualifier in the same hierarchy in {@code superQualifiers}
+   */
+  default boolean isSubtype(
+      Collection<? extends AnnotationMirror> subQualifiers,
+      Collection<? extends AnnotationMirror> superQualifiers) {
+    assertSameSize(subQualifiers, superQualifiers);
+    for (AnnotationMirror subQual : subQualifiers) {
+      AnnotationMirror superQual = findAnnotationInSameHierarchy(superQualifiers, subQual);
+      if (superQual == null) {
+        throw new BugInCF(
+            "QualifierHierarchy: missing annotation in hierarchy %s. found: %s",
+            subQual, StringsPlume.join(",", superQualifiers));
+      }
+      if (!isSubtype(subQual, superQual)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code
+   * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier hierarchy.
+   *
+   * <p>Examples:
+   *
+   * <ul>
+   *   <li>For NonNull, leastUpperBound('Nullable', 'NonNull') &rArr; Nullable
+   * </ul>
+   *
+   * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2}
+   * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code qualifier1}
+   * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from
+   *     different hierarchies
+   */
+  // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the
+  // collection version of LUB below.
+  @Nullable AnnotationMirror leastUpperBound(AnnotationMirror qualifier1, AnnotationMirror qualifier2);
+
+  /**
+   * Returns the least upper bound of the two sets of qualifiers. The result is the lub of the
+   * qualifier for the same hierarchy in each set.
+   *
+   * @param qualifiers1 set of qualifiers; exactly one per hierarchy
+   * @param qualifiers2 set of qualifiers; exactly one per hierarchy
+   * @return the least upper bound of the two sets of qualifiers
+   */
+  default Set<? extends AnnotationMirror> leastUpperBounds(
+      Collection<? extends AnnotationMirror> qualifiers1,
+      Collection<? extends AnnotationMirror> qualifiers2) {
+    assertSameSize(qualifiers1, qualifiers2);
+    if (qualifiers1.isEmpty()) {
+      throw new BugInCF(
+          "QualifierHierarchy.leastUpperBounds: tried to determine LUB with empty sets");
+    }
+
+    Set<AnnotationMirror> result = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror a1 : qualifiers1) {
+      for (AnnotationMirror a2 : qualifiers2) {
+        AnnotationMirror lub = leastUpperBound(a1, a2);
+        if (lub != null) {
+          result.add(lub);
+        }
+      }
+    }
+
+    assertSameSize(result, qualifiers1);
+    return result;
+  }
+
+  /**
+   * Returns the number of iterations dataflow should perform before {@link
+   * #widenedUpperBound(AnnotationMirror, AnnotationMirror)} is called or -1 if it should never be
+   * called.
+   *
+   * @return the number of iterations dataflow should perform before {@link
+   *     #widenedUpperBound(AnnotationMirror, AnnotationMirror)} is called or -1 if it should never
+   *     be called.
+   */
+  default int numberOfIterationsBeforeWidening() {
+    return -1;
+  }
+
+  /**
+   * If the qualifier hierarchy has an infinite ascending chain, then the dataflow analysis might
+   * never reach a fixed point. To prevent this, implement this method such that it returns an upper
+   * bound for the two qualifiers that is a strict super type of the least upper bound. If this
+   * method is implemented, also override {@link #numberOfIterationsBeforeWidening()} to return a
+   * positive number.
+   *
+   * <p>{@code newQualifier} is newest qualifier dataflow computed for some expression and {@code
+   * previousQualifier} is the qualifier dataflow computed on the last iteration.
+   *
+   * <p>If the qualifier hierarchy has no infinite ascending chain, returns the least upper bound of
+   * the two annotations.
+   *
+   * @param newQualifier new qualifier dataflow computed for some expression; must be in the same
+   *     hierarchy as {@code previousQualifier}
+   * @param previousQualifier the previous qualifier dataflow computed on the last iteration; must
+   *     be in the same hierarchy as {@code previousQualifier}
+   * @return an upper bound that is higher than the least upper bound of newQualifier and
+   *     previousQualifier (or the lub if the qualifier hierarchy does not require this)
+   */
+  default AnnotationMirror widenedUpperBound(
+      AnnotationMirror newQualifier, AnnotationMirror previousQualifier) {
+    AnnotationMirror widenUpperBound = leastUpperBound(newQualifier, previousQualifier);
+    if (widenUpperBound == null) {
+      throw new BugInCF(
+          "Passed two unrelated qualifiers to QualifierHierarchy#widenedUpperBound. %s %s.",
+          newQualifier, previousQualifier);
+    }
+    return widenUpperBound;
+  }
+
+  /**
+   * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null if
+   * the qualifiers are not from the same qualifier hierarchy.
+   *
+   * @param qualifier1 first qualifier
+   * @param qualifier2 second qualifier
+   * @return greatest lower bound of the two annotations or null if the two annotations are not from
+   *     the same hierarchy
+   */
+  // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the
+  // collection version of LUB below.
+  @Nullable AnnotationMirror greatestLowerBound(AnnotationMirror qualifier1, AnnotationMirror qualifier2);
+
+  /**
+   * Returns the greatest lower bound of the two sets of qualifiers. The result is the lub of the
+   * qualifier for the same hierarchy in each set.
+   *
+   * @param qualifiers1 set of qualifiers; exactly one per hierarchy
+   * @param qualifiers2 set of qualifiers; exactly one per hierarchy
+   * @return the greatest lower bound of the two sets of qualifiers
+   */
+  default Set<? extends AnnotationMirror> greatestLowerBounds(
+      Collection<? extends AnnotationMirror> qualifiers1,
+      Collection<? extends AnnotationMirror> qualifiers2) {
+    assertSameSize(qualifiers1, qualifiers2);
+    if (qualifiers1.isEmpty()) {
+      throw new BugInCF(
+          "QualifierHierarchy.greatestLowerBounds: tried to determine GLB with empty sets");
+    }
+
+    Set<AnnotationMirror> result = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror a1 : qualifiers1) {
+      for (AnnotationMirror a2 : qualifiers2) {
+        AnnotationMirror glb = greatestLowerBound(a1, a2);
+        if (glb != null) {
+          result.add(glb);
+        }
+      }
+    }
+
+    assertSameSize(qualifiers1, qualifiers2, result);
+    return result;
+  }
+
+  /**
+   * Returns true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set with
+   * fewer qualifiers than the width of the QualifierHierarchy.
+   *
+   * @param type the type to test
+   * @return true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set with
+   *     fewer qualifiers than the width of the QualifierHierarchy
+   */
+  static boolean canHaveEmptyAnnotationSet(AnnotatedTypeMirror type) {
+    return type.getKind() == TypeKind.TYPEVAR
+        || type.getKind() == TypeKind.WILDCARD
+        ||
+        // TODO: or should the union/intersection be the LUB of the alternatives?
+        type.getKind() == TypeKind.UNION
+        || type.getKind() == TypeKind.INTERSECTION;
+  }
+
+  /**
+   * Returns the annotation in {@code qualifiers} that is in the same hierarchy as {@code
+   * qualifier}.
+   *
+   * <p>The default implementation calls {@link #getTopAnnotation(AnnotationMirror)} and then calls
+   * {@link #findAnnotationInHierarchy(Collection, AnnotationMirror)}. So, if {@code qualifier} is a
+   * top qualifier, then call {@link #findAnnotationInHierarchy(Collection, AnnotationMirror)}
+   * directly is faster.
+   *
+   * @param qualifiers set of annotations to search
+   * @param qualifier annotation that is in the same hierarchy as the returned annotation
+   * @return annotation in the same hierarchy as qualifier, or null if one is not found
+   */
+  default @Nullable AnnotationMirror findAnnotationInSameHierarchy(
+      Collection<? extends AnnotationMirror> qualifiers, AnnotationMirror qualifier) {
+    AnnotationMirror top = this.getTopAnnotation(qualifier);
+    return findAnnotationInHierarchy(qualifiers, top);
+  }
+
+  /**
+   * Returns the annotation in {@code qualifiers} that is in the hierarchy for which {@code top} is
+   * top.
+   *
+   * @param qualifiers set of annotations to search
+   * @param top the top annotation in the hierarchy to which the returned annotation belongs
+   * @return annotation in the same hierarchy as annotationMirror, or null if one is not found
+   */
+  default @Nullable AnnotationMirror findAnnotationInHierarchy(
+      Collection<? extends AnnotationMirror> qualifiers, AnnotationMirror top) {
+    for (AnnotationMirror anno : qualifiers) {
+      if (isSubtype(anno, top)) {
+        return anno;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Update a mapping from {@code key} to a set of AnnotationMirrors. If {@code key} is not already
+   * in the map, then put it in the map with a value of a new set containing {@code qualifier}. If
+   * the map contains {@code key}, then add {@code qualifier} to the set to which {@code key} maps.
+   * If that set contains a qualifier in the same hierarchy as {@code qualifier}, then don't add it
+   * and return false.
+   *
+   * @param map the mapping to modify
+   * @param key the key to update or add
+   * @param qualifier the value to update or add
+   * @param <T> type of the map's keys
+   * @return true if the update was done; false if there was a qualifier hierarchy collision
+   */
+  default <T> boolean updateMappingToMutableSet(
+      Map<T, Set<AnnotationMirror>> map, T key, AnnotationMirror qualifier) {
+    // https://github.com/typetools/checker-framework/issues/2000
+    @SuppressWarnings("nullness:argument")
+    boolean mapContainsKey = map.containsKey(key);
+    if (mapContainsKey) {
+      @SuppressWarnings("nullness:assignment") // key is a key for map.
+      @NonNull Set<AnnotationMirror> prevs = map.get(key);
+      AnnotationMirror old = findAnnotationInSameHierarchy(prevs, qualifier);
+      if (old != null) {
+        return false;
+      }
+      prevs.add(qualifier);
+      map.put(key, prevs);
+    } else {
+      Set<AnnotationMirror> set = AnnotationUtils.createAnnotationSet();
+      set.add(qualifier);
+      map.put(key, set);
+    }
+    return true;
+  }
+
+  /**
+   * Throws an exception if the given collections do not have the same size.
+   *
+   * @param c1 the first collection
+   * @param c2 the second collection
+   */
+  static void assertSameSize(Collection<?> c1, Collection<?> c2) {
+    if (c1.size() != c2.size()) {
+      throw new BugInCF(
+          "inconsistent sizes (%d, %d):%n  [%s]%n  [%s]",
+          c1.size(), c2.size(), StringsPlume.join(",", c1), StringsPlume.join(",", c2));
+    }
+  }
+
+  /**
+   * Throws an exception if the result does not have the same size as the inputs (which are assumed
+   * to have the same size as one another).
+   *
+   * @param c1 the first collection
+   * @param c2 the second collection
+   * @param result the result collection
+   */
+  static void assertSameSize(Collection<?> c1, Collection<?> c2, Collection<?> result) {
+    if (c1.size() != result.size() || c2.size() != result.size()) {
+      throw new BugInCF(
+          "inconsistent sizes (%d, %d, %d):%n  %s%n  %s%n  %s",
+          c1.size(),
+          c2.size(),
+          result.size(),
+          StringsPlume.join(",", c1),
+          StringsPlume.join(",", c2),
+          StringsPlume.join(",", result));
+    }
+  }
+
+  // **********************************************************************
+  // Deprecated methods
+  // **********************************************************************
+
+  /**
+   * Tests whether {@code subQualifier} is a sub-qualifier of, or equal to, {@code superQualifier},
+   * according to the type qualifier hierarchy.
+   *
+   * <p>This method works even if the underlying Java type is a type variable. In that case, a
+   * 'null' AnnotationMirror is a legal argument that represents no annotation.
+   *
+   * @param subQualifier a qualifier that might be a subtype
+   * @param superQualifier a qualifier that might be a subtype
+   * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code superQualifier}
+   * @deprecated Without the bounds of the type variable, it is not possible to correctly compute
+   *     the subtype relationship between "no qualifier" and a qualifier. Use {@link
+   *     TypeHierarchy#isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}.
+   */
+  @Deprecated // 2020-07-29
+  default boolean isSubtypeTypeVariable(
+      @Nullable AnnotationMirror subQualifier, @Nullable AnnotationMirror superQualifier) {
+    if (subQualifier == null) {
+      // [] is a supertype of any qualifier, and [] <: []
+      return true;
+    } else if (superQualifier == null) {
+      // [] is a subtype of no qualifier (only [])
+      return false;
+    }
+    return isSubtype(subQualifier, superQualifier);
+  }
+
+  /**
+   * Tests whether {@code subQualifier} is a sub-qualifier of, or equal to, {@code superQualifier},
+   * according to the type qualifier hierarchy. This checks only the qualifiers, not the Java type.
+   *
+   * <p>This method takes an annotated type to decide if the type variable version of the method
+   * should be invoked, or if the normal version is sufficient (which provides more strict checks).
+   *
+   * @param subType used to decide whether to call isSubtypeTypeVariable
+   * @param superType used to decide whether to call isSubtypeTypeVariable
+   * @param subQualifier the type qualifier that might be a subtype
+   * @param superQualifier the type qualifier that might be a supertype
+   * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code superQualifier}
+   * @deprecated Without the bounds of the type variable, it is not possible to correctly compute
+   *     the subtype relationship between "no qualifier" and a qualifier. Use {@link
+   *     TypeHierarchy#isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}.
+   */
+  @Deprecated
+  default boolean isSubtype(
+      AnnotatedTypeMirror subType,
+      AnnotatedTypeMirror superType,
+      AnnotationMirror subQualifier,
+      AnnotationMirror superQualifier) {
+    if (canHaveEmptyAnnotationSet(subType) || canHaveEmptyAnnotationSet(superType)) {
+      return isSubtypeTypeVariable(subQualifier, superQualifier);
+    } else {
+      return isSubtype(subQualifier, superQualifier);
+    }
+  }
+
+  /**
+   * Tests whether there is any annotation in {@code supers} that is a superqualifier of, or equal
+   * to, some annotation in {@code subs}. {@code supers} and {@code subs} contain only the
+   * annotations, not the Java type.
+   *
+   * <p>This method takes an annotated type to decide if the type variable version of the method
+   * should be invoked, or if the normal version is sufficient (which provides more strict checks).
+   *
+   * @param subType used to decide whether to call isSubtypeTypeVariable
+   * @param superType used to decide whether to call isSubtypeTypeVariable
+   * @param subs the type qualifiers that might be a subtype
+   * @param supers the type qualifiers that might be a supertype
+   * @return true iff an annotation in {@code supers} is a supertype of, or equal to, one in {@code
+   *     subs}
+   * @deprecated Without the bounds of the type variable, it is not possible to correctly compute
+   *     the subtype relationship between "no qualifier" and a qualifier. Use {@link
+   *     TypeHierarchy#isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}.
+   */
+  @Deprecated // 2020-07-29
+  default boolean isSubtype(
+      AnnotatedTypeMirror subType,
+      AnnotatedTypeMirror superType,
+      Collection<? extends AnnotationMirror> subs,
+      Collection<AnnotationMirror> supers) {
+    if (canHaveEmptyAnnotationSet(subType) || canHaveEmptyAnnotationSet(superType)) {
+      return isSubtypeTypeVariable(subs, supers);
+    } else {
+      return isSubtype(subs, supers);
+    }
+  }
+
+  /**
+   * Tests whether there is any annotation in superAnnos that is a superqualifier of or equal to
+   * some annotation in subAnnos. superAnnos and subAnnos contain only the annotations, not the Java
+   * type.
+   *
+   * <p>This method works even if the underlying Java type is a type variable. In that case, the
+   * empty set is a legal argument that represents no annotation.
+   *
+   * @param subAnnos qualifiers
+   * @param superAnnos qualifiers
+   * @return true iff an annotation in superAnnos is a supertype of, or equal to, one in subAnnos
+   * @deprecated Without the bounds of the type variable, it is not possible to correctly compute
+   *     the subtype relationship between "no qualifier" and a qualifier. Use {@link
+   *     TypeHierarchy#isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}.
+   */
+  // This method requires more revision.
+  @Deprecated // 2020-07-29
+  default boolean isSubtypeTypeVariable(
+      Collection<? extends AnnotationMirror> subAnnos,
+      Collection<? extends AnnotationMirror> superAnnos) {
+    for (AnnotationMirror top : getTopAnnotations()) {
+      AnnotationMirror rhsForTop = findAnnotationInHierarchy(subAnnos, top);
+      AnnotationMirror lhsForTop = findAnnotationInHierarchy(superAnnos, top);
+      if (!isSubtypeTypeVariable(rhsForTop, lhsForTop)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns the least upper bound for the qualifiers a1 and a2. Returns null if the qualifiers are
+   * not from the same qualifier hierarchy.
+   *
+   * <p>Examples:
+   *
+   * <ul>
+   *   <li>For NonNull, leastUpperBound('Nullable', 'NonNull') &rarr; Nullable
+   * </ul>
+   *
+   * <p>This method works even if the underlying Java type is a type variable. In that case, a
+   * 'null' AnnotationMirror is a legal argument that represents no annotation.
+   *
+   * @param a1 anno1
+   * @param a2 anno2
+   * @return the least restrictive qualifiers for both types
+   * @deprecated Without the bounds of the type variable, it is not possible to correctly compute
+   *     the relationship between "no qualifier" and a qualifier. Use {@link
+   *     org.checkerframework.framework.util.AnnotatedTypes#leastUpperBound(AnnotatedTypeFactory,
+   *     AnnotatedTypeMirror, AnnotatedTypeMirror)}.
+   */
+  @Deprecated // 2020-07-29
+  default @Nullable AnnotationMirror leastUpperBoundTypeVariable(
+      AnnotationMirror a1, AnnotationMirror a2) {
+    if (a1 == null || a2 == null) {
+      // [] is a supertype of any qualifier, and [] <: []
+      return null;
+    }
+    return leastUpperBound(a1, a2);
+  }
+
+  /**
+   * Returns the least upper bound for the qualifiers a1 and a2. Returns null if the qualifiers are
+   * not from the same qualifier hierarchy.
+   *
+   * <p>Examples:
+   *
+   * <ul>
+   *   <li>For NonNull, leastUpperBound('Nullable', 'NonNull') &rarr; Nullable
+   * </ul>
+   *
+   * <p>This method takes an annotated type to decide if the type variable version of the method
+   * should be invoked, or if the normal version is sufficient (which provides more strict checks).
+   *
+   * @param type1 type 1
+   * @param type2 type 2
+   * @param a1 annotation
+   * @param a2 annotation
+   * @return the least restrictive qualifiers for both types
+   * @deprecated Without the bounds of the type variable, it is not possible to correctly compute
+   *     the relationship between "no qualifier" and a qualifier. Use {@link
+   *     org.checkerframework.framework.util.AnnotatedTypes#leastUpperBound(AnnotatedTypeFactory,
+   *     AnnotatedTypeMirror, AnnotatedTypeMirror)}.
+   */
+  @Deprecated // 2020-07-29
+  default @Nullable AnnotationMirror leastUpperBound(
+      AnnotatedTypeMirror type1,
+      AnnotatedTypeMirror type2,
+      AnnotationMirror a1,
+      AnnotationMirror a2) {
+    if (canHaveEmptyAnnotationSet(type1) || canHaveEmptyAnnotationSet(type2)) {
+      return leastUpperBoundTypeVariable(a1, a2);
+    } else {
+      return leastUpperBound(a1, a2);
+    }
+  }
+
+  /**
+   * Returns the type qualifiers that are the least upper bound of the qualifiers in annos1 and
+   * annos2.
+   *
+   * <p>This is necessary for determining the type of a conditional expression ({@code ?:}), where
+   * the type of the expression is the least upper bound of the true and false clauses.
+   *
+   * <p>This method works even if the underlying Java type is a type variable. In that case, the
+   * empty set is a legal argument that represents no annotation.
+   *
+   * @param annos1 qualifiers
+   * @param annos2 qualifiers
+   * @return the least upper bound of annos1 and annos2
+   * @deprecated Without the bounds of the type variable, it is not possible to correctly compute
+   *     the relationship between "no qualifier" and a qualifier. Use {@link
+   *     org.checkerframework.framework.util.AnnotatedTypes#leastUpperBound(AnnotatedTypeFactory,
+   *     AnnotatedTypeMirror, AnnotatedTypeMirror)}.
+   */
+  @Deprecated // 2020-07-29
+  @SuppressWarnings("nullness") // Don't check deprecated method.
+  default Set<? extends AnnotationMirror> leastUpperBoundsTypeVariable(
+      Collection<? extends AnnotationMirror> annos1,
+      Collection<? extends AnnotationMirror> annos2) {
+    Set<AnnotationMirror> result = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror top : getTopAnnotations()) {
+      AnnotationMirror anno1ForTop = null;
+      for (AnnotationMirror anno1 : annos1) {
+        if (isSubtypeTypeVariable(anno1, top)) {
+          anno1ForTop = anno1;
+        }
+      }
+      AnnotationMirror anno2ForTop = null;
+      for (AnnotationMirror anno2 : annos2) {
+        if (isSubtypeTypeVariable(anno2, top)) {
+          anno2ForTop = anno2;
+        }
+      }
+      AnnotationMirror t = leastUpperBoundTypeVariable(anno1ForTop, anno2ForTop);
+      if (t != null) {
+        result.add(t);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns the type qualifiers that are the least upper bound of the qualifiers in annos1 and
+   * annos2.
+   *
+   * <p>This is necessary for determining the type of a conditional expression ({@code ?:}), where
+   * the type of the expression is the least upper bound of the true and false clauses.
+   *
+   * <p>This method takes an annotated type to decide if the type variable version of the method
+   * should be invoked, or if the normal version is sufficient (which provides more strict checks).
+   *
+   * @param type1 annotated type
+   * @param type2 annotated type
+   * @param annos1 qualifiers
+   * @param annos2 qualifiers
+   * @return the least upper bound of annos1 and annos2
+   * @deprecated Without the bounds of the type variable, it is not possible to correctly compute
+   *     the relationship between "no qualifier" and a qualifier. Use {@link
+   *     org.checkerframework.framework.util.AnnotatedTypes#leastUpperBound(AnnotatedTypeFactory,
+   *     AnnotatedTypeMirror, AnnotatedTypeMirror)}.
+   */
+  @Deprecated // 2020-07-29
+  default Set<? extends AnnotationMirror> leastUpperBounds(
+      AnnotatedTypeMirror type1,
+      AnnotatedTypeMirror type2,
+      Collection<? extends AnnotationMirror> annos1,
+      Collection<AnnotationMirror> annos2) {
+    if (canHaveEmptyAnnotationSet(type1) || canHaveEmptyAnnotationSet(type2)) {
+      return leastUpperBoundsTypeVariable(annos1, annos2);
+    } else {
+      return leastUpperBounds(annos1, annos2);
+    }
+  }
+
+  /**
+   * Returns the greatest lower bound for the qualifiers a1 and a2. Returns null if the qualifiers
+   * are not from the same qualifier hierarchy.
+   *
+   * <p>This method works even if the underlying Java type is a type variable. In that case, a
+   * 'null' AnnotationMirror is a legal argument that represents no annotation.
+   *
+   * @param a1 first annotation
+   * @param a2 second annotation
+   * @return greatest lower bound of the two annotations
+   * @deprecated Without the bounds of the type variable, it is not possible to correctly compute
+   *     the relationship between "no qualifier" and a qualifier
+   */
+  @Deprecated // 2020-07-29
+  default @Nullable AnnotationMirror greatestLowerBoundTypeVariable(
+      AnnotationMirror a1, AnnotationMirror a2) {
+    if (a1 == null) {
+      // [] is a supertype of any qualifier, and [] <: []
+      return a2;
+    }
+    if (a2 == null) {
+      // [] is a supertype of any qualifier, and [] <: []
+      return a1;
+    }
+    return greatestLowerBound(a1, a2);
+  }
+
+  /**
+   * Returns the type qualifiers that are the greatest lower bound of the qualifiers in annos1 and
+   * annos2. Returns null if the qualifiers are not from the same qualifier hierarchy.
+   *
+   * <p>This method works even if the underlying Java type is a type variable. In that case, the
+   * empty set is a legal argument that represents no annotation.
+   *
+   * @param annos1 first collection of qualifiers
+   * @param annos2 second collection of qualifiers
+   * @return greatest lower bound of the two collections of qualifiers
+   * @deprecated Without the bounds of the type variable, it is not possible to correctly compute
+   *     the relationship between "no qualifier" and a qualifier
+   */
+  @Deprecated // 2020-07-29
+  @SuppressWarnings("nullness") // Don't check deprecated method.
+  default Set<? extends AnnotationMirror> greatestLowerBoundsTypeVariable(
+      Collection<? extends AnnotationMirror> annos1,
+      Collection<? extends AnnotationMirror> annos2) {
+    Set<AnnotationMirror> result = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror top : getTopAnnotations()) {
+      AnnotationMirror anno1ForTop = null;
+      for (AnnotationMirror anno1 : annos1) {
+        if (isSubtypeTypeVariable(anno1, top)) {
+          anno1ForTop = anno1;
+        }
+      }
+      AnnotationMirror anno2ForTop = null;
+      for (AnnotationMirror anno2 : annos2) {
+        if (isSubtypeTypeVariable(anno2, top)) {
+          anno2ForTop = anno2;
+        }
+      }
+      AnnotationMirror t = greatestLowerBoundTypeVariable(anno1ForTop, anno2ForTop);
+      if (t != null) {
+        result.add(t);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns the greatest lower bound for the qualifiers a1 and a2. Returns null if the qualifiers
+   * are not from the same qualifier hierarchy.
+   *
+   * <p>This method takes an annotated type to decide if the type variable version of the method
+   * should be invoked, or if the normal version is sufficient (which provides more strict checks).
+   *
+   * @param type1 annotated type
+   * @param type2 annotated type
+   * @param a1 first annotation
+   * @param a2 second annotation
+   * @return greatest lower bound of the two annotations
+   * @deprecated Without the bounds of the type variable, it is not possible to correctly compute
+   *     the relationship between "no qualifier" and a qualifier
+   */
+  @Deprecated // 2020-07-29
+  default @Nullable AnnotationMirror greatestLowerBound(
+      AnnotatedTypeMirror type1,
+      AnnotatedTypeMirror type2,
+      AnnotationMirror a1,
+      AnnotationMirror a2) {
+    if (canHaveEmptyAnnotationSet(type1) || canHaveEmptyAnnotationSet(type2)) {
+      return greatestLowerBoundTypeVariable(a1, a2);
+    } else {
+      return greatestLowerBound(a1, a2);
+    }
+  }
+
+  /**
+   * Returns the type qualifiers that are the greatest lower bound of the qualifiers in annos1 and
+   * annos2. Returns null if the qualifiers are not from the same qualifier hierarchy.
+   *
+   * <p>This method takes an annotated type to decide if the type variable version of the method
+   * should be invoked, or if the normal version is sufficient (which provides more strict checks).
+   *
+   * @param type1 annotated type
+   * @param type2 annotated type
+   * @param annos1 first collection of qualifiers
+   * @param annos2 second collection of qualifiers
+   * @return greatest lower bound of the two collections of qualifiers
+   * @deprecated Without the bounds of the type variable, it is not possible to correctly compute
+   *     the relationship between "no qualifier" and a qualifier
+   */
+  @Deprecated // 2020-07-29
+  default Set<? extends AnnotationMirror> greatestLowerBounds(
+      AnnotatedTypeMirror type1,
+      AnnotatedTypeMirror type2,
+      Collection<? extends AnnotationMirror> annos1,
+      Collection<AnnotationMirror> annos2) {
+    if (canHaveEmptyAnnotationSet(type1) || canHaveEmptyAnnotationSet(type2)) {
+      return greatestLowerBoundsTypeVariable(annos1, annos2);
+    } else {
+      return greatestLowerBounds(annos1, annos2);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/QualifierUpperBounds.java b/framework/src/main/java/org/checkerframework/framework/type/QualifierUpperBounds.java
new file mode 100644
index 0000000..7897c88
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/QualifierUpperBounds.java
@@ -0,0 +1,159 @@
+package org.checkerframework.framework.type;
+
+import java.lang.annotation.Annotation;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.framework.qual.UpperBoundFor;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** Class that computes and stores the qualifier upper bounds for type uses. */
+public class QualifierUpperBounds {
+
+  /** Map from {@link TypeKind} to annotations. */
+  private final Map<TypeKind, Set<AnnotationMirror>> typeKinds;
+  /** Map from canonical class name strings to annotations. */
+  private final Map<@CanonicalName String, Set<AnnotationMirror>> types;
+
+  /** {@link QualifierHierarchy} */
+  private final QualifierHierarchy qualHierarchy;
+
+  private final AnnotatedTypeFactory atypeFactory;
+
+  /**
+   * Creates a {@link QualifierUpperBounds} from the given checker, using that checker to determine
+   * the annotations that are in the type hierarchy.
+   */
+  public QualifierUpperBounds(AnnotatedTypeFactory typeFactory) {
+    this.atypeFactory = typeFactory;
+    this.typeKinds = new EnumMap<>(TypeKind.class);
+    this.types = new HashMap<>();
+
+    this.qualHierarchy = typeFactory.getQualifierHierarchy();
+
+    // Get type qualifiers from the checker.
+    Set<Class<? extends Annotation>> quals = typeFactory.getSupportedTypeQualifiers();
+
+    // For each qualifier, read the @UpperBoundFor annotation and put its type classes and kinds
+    // into maps.
+    for (Class<? extends Annotation> qual : quals) {
+      UpperBoundFor upperBoundFor = qual.getAnnotation(UpperBoundFor.class);
+      if (upperBoundFor == null) {
+        continue;
+      }
+
+      AnnotationMirror theQual = AnnotationBuilder.fromClass(typeFactory.getElementUtils(), qual);
+      for (org.checkerframework.framework.qual.TypeKind typeKind : upperBoundFor.typeKinds()) {
+        TypeKind mappedTk = mapTypeKinds(typeKind);
+        addTypeKind(mappedTk, theQual);
+      }
+
+      for (Class<?> typeName : upperBoundFor.types()) {
+        addType(typeName, theQual);
+      }
+    }
+  }
+
+  /**
+   * Map between {@link org.checkerframework.framework.qual.TypeKind} and {@link TypeKind}.
+   *
+   * @param typeKind the Checker Framework TypeKind
+   * @return the javax TypeKind
+   */
+  private TypeKind mapTypeKinds(org.checkerframework.framework.qual.TypeKind typeKind) {
+    return TypeKind.valueOf(typeKind.name());
+  }
+
+  /** Add default qualifier, {@code theQual}, for the given TypeKind. */
+  public void addTypeKind(TypeKind typeKind, AnnotationMirror theQual) {
+    boolean res = qualHierarchy.updateMappingToMutableSet(typeKinds, typeKind, theQual);
+    if (!res) {
+      throw new BugInCF(
+          "QualifierUpperBounds: invalid update of typeKinds $s at %s with %s.",
+          typeKinds, typeKind, theQual);
+    }
+  }
+
+  /** Add default qualifier, {@code theQual}, for the given class. */
+  public void addType(Class<?> type, AnnotationMirror theQual) {
+    String typeNameString = type.getCanonicalName();
+    boolean res = qualHierarchy.updateMappingToMutableSet(types, typeNameString, theQual);
+    if (!res) {
+      throw new BugInCF(
+          "QualifierUpperBounds: invalid update of types $s at %s with %s.", types, type, theQual);
+    }
+  }
+
+  /**
+   * Returns the set of qualifiers that are the upper bounds for a use of the type.
+   *
+   * @param type the TypeMirror
+   * @return the set of qualifiers that are the upper bounds for a use of the type
+   */
+  public Set<AnnotationMirror> getBoundQualifiers(TypeMirror type) {
+    AnnotationMirrorSet bounds = new AnnotationMirrorSet();
+    String qname;
+    if (type.getKind() == TypeKind.DECLARED) {
+      DeclaredType declaredType = (DeclaredType) type;
+      bounds.addAll(getAnnotationFromElement(declaredType.asElement()));
+      qname = TypesUtils.getQualifiedName(declaredType);
+    } else if (type.getKind().isPrimitive()) {
+      qname = type.toString();
+    } else {
+      qname = null;
+    }
+
+    if (qname != null && types.containsKey(qname)) {
+      Set<AnnotationMirror> fnd = types.get(qname);
+      addMissingAnnotations(bounds, fnd);
+    }
+
+    // If the type's kind is in the appropriate map, annotate the type.
+
+    if (typeKinds.containsKey(type.getKind())) {
+      Set<AnnotationMirror> fnd = typeKinds.get(type.getKind());
+      addMissingAnnotations(bounds, fnd);
+    }
+
+    addMissingAnnotations(bounds, atypeFactory.getDefaultTypeDeclarationBounds());
+    return bounds;
+  }
+
+  /**
+   * Returns the explicit annotations on the element. Subclass can override this behavior to add
+   * annotations.
+   *
+   * @param element element whose annotations to return
+   * @return the explicit annotations on the element
+   */
+  protected Set<AnnotationMirror> getAnnotationFromElement(Element element) {
+    return atypeFactory.fromElement(element).getAnnotations();
+  }
+
+  /**
+   * Adds each annotation in {@code missing} to {@code annos}, for which no annotation from the same
+   * qualifier hierarchy is present.
+   *
+   * @param annos an annotation set to side-effect
+   * @param missing annotations to add to {@code annos}, if {@code annos} does not have an
+   *     annotation from the same qualifier hierarchy
+   */
+  private void addMissingAnnotations(
+      AnnotationMirrorSet annos, Set<? extends AnnotationMirror> missing) {
+    for (AnnotationMirror miss : missing) {
+      if (atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(annos, miss) == null) {
+        annos.add(miss);
+      }
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java
new file mode 100644
index 0000000..3d8b525
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java
@@ -0,0 +1,519 @@
+package org.checkerframework.framework.type;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.interning.qual.EqualsMethod;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AbstractAtmComboVisitor;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.AtmCombo;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * A visitor used to compare two type mirrors for "structural" equality. Structural equality implies
+ * that, for two objects, all fields are also structurally equal and for primitives their values are
+ * equal. One reason this class is necessary is that at the moment we compare wildcards and type
+ * variables for "equality". This occurs because we do not employ capture conversion.
+ *
+ * <p>See also DefaultTypeHierarchy, and SubtypeVisitHistory
+ */
+public class StructuralEqualityComparer extends AbstractAtmComboVisitor<Boolean, Void> {
+  /** History saving the result of previous comparisons. */
+  protected final StructuralEqualityVisitHistory visitHistory;
+
+  // See org.checkerframework.framework.type.DefaultTypeHierarchy.currentTop
+  private AnnotationMirror currentTop = null;
+
+  /**
+   * Create a StructuralEqualityComparer.
+   *
+   * @param typeargVisitHistory history saving the result of previous comparisons
+   */
+  public StructuralEqualityComparer(StructuralEqualityVisitHistory typeargVisitHistory) {
+    this.visitHistory = typeargVisitHistory;
+  }
+
+  @Override
+  protected Boolean defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) {
+    if (type1.atypeFactory.ignoreUninferredTypeArguments) {
+      if (type1.getKind() == TypeKind.WILDCARD
+          && ((AnnotatedWildcardType) type1).isUninferredTypeArgument()) {
+        return true;
+      }
+
+      if (type2.getKind() == TypeKind.WILDCARD
+          && ((AnnotatedWildcardType) type2).isUninferredTypeArgument()) {
+        return true;
+      }
+    }
+    if (type1.getKind() == TypeKind.TYPEVAR || type2.getKind() == TypeKind.TYPEVAR) {
+      // TODO: Handle any remaining typevar combinations correctly.
+      return true;
+    }
+    if (type1.getKind() == TypeKind.NULL || type2.getKind() == TypeKind.NULL) {
+      // If one of the types is the NULL type, compare main qualifiers only.
+      return arePrimeAnnosEqual(type1, type2);
+    }
+    return super.defaultAction(type1, type2, p);
+  }
+
+  /**
+   * Called for every combination that isn't specifically handled.
+   *
+   * @return error message explaining the two types' classes are not the same
+   */
+  @Override
+  protected String defaultErrorMessage(
+      AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) {
+    return StringsPlume.joinLines(
+        "AnnotatedTypeMirrors aren't structurally equal.",
+        "  type1 = " + type1.getClass().getSimpleName() + "( " + type1 + " )",
+        "  type2 = " + type2.getClass().getSimpleName() + "( " + type2 + " )",
+        "  visitHistory = " + visitHistory);
+  }
+
+  /**
+   * Returns true if type1 and type2 are structurally equivalent. With one exception,
+   * type1.getClass().equals(type2.getClass()) must be true. However, because the Checker Framework
+   * sometimes "infers" Typevars to be Wildcards, we allow the combination Wildcard,Typevar. In this
+   * case, the two types are "equal" if their bounds are.
+   *
+   * @param type1 the first AnnotatedTypeMirror to compare
+   * @param type2 the second AnnotatedTypeMirror to compare
+   * @return true if type1 and type2 are equal
+   */
+  @EqualsMethod
+  private boolean areEqual(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
+    if (type1 == type2) {
+      return true;
+    }
+    assert currentTop != null;
+    if (type1 == null || type2 == null) {
+      return false;
+    }
+    return AtmCombo.accept(type1, type2, null, this);
+  }
+
+  public boolean areEqualInHierarchy(
+      final AnnotatedTypeMirror type1,
+      final AnnotatedTypeMirror type2,
+      final AnnotationMirror top) {
+    assert top != null;
+    boolean areEqual;
+    AnnotationMirror prevTop = currentTop;
+    currentTop = top;
+    try {
+      areEqual = areEqual(type1, type2);
+    } finally {
+      currentTop = prevTop;
+    }
+
+    return areEqual;
+  }
+
+  /** Return true if type1 and type2 have the same set of annotations. */
+  protected boolean arePrimeAnnosEqual(
+      final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
+    if (currentTop != null) {
+      return AnnotationUtils.areSame(
+          type1.getAnnotationInHierarchy(currentTop), type2.getAnnotationInHierarchy(currentTop));
+    } else {
+      throw new BugInCF("currentTop null");
+    }
+  }
+
+  /**
+   * Compare each type in types1 and types2 pairwise and return true if they are all equal. This
+   * method throws an exceptions if types1.size() != types2.size()
+   *
+   * @return true if for each pair (t1 = types1.get(i); t2 = types2.get(i)), areEqual(t1,t2)
+   */
+  protected boolean areAllEqual(
+      final Collection<? extends AnnotatedTypeMirror> types1,
+      final Collection<? extends AnnotatedTypeMirror> types2) {
+    if (types1.size() != types2.size()) {
+      throw new BugInCF(
+          "Mismatching collection sizes:%n    types 1: %s (%d)%n    types 2: %s (%d)",
+          StringsPlume.join("; ", types1),
+          types1.size(),
+          StringsPlume.join("; ", types2),
+          types2.size());
+    }
+
+    final Iterator<? extends AnnotatedTypeMirror> types1Iter = types1.iterator();
+    final Iterator<? extends AnnotatedTypeMirror> types2Iter = types2.iterator();
+    while (types1Iter.hasNext()) {
+      final AnnotatedTypeMirror type1 = types1Iter.next();
+      final AnnotatedTypeMirror type2 = types2Iter.next();
+      if (!checkOrAreEqual(type1, type2)) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * First check visitHistory to see if type1 and type2 have been compared once already. If so
+   * return true; otherwise compare them and put them in visitHistory.
+   *
+   * @param type1 the first type
+   * @param type2 the second type
+   * @return whether the two types are equal
+   */
+  protected boolean checkOrAreEqual(
+      final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
+    Boolean pastResult = visitHistory.get(type1, type2, currentTop);
+    if (pastResult != null) {
+      return pastResult;
+    }
+
+    final Boolean result = areEqual(type1, type2);
+    visitHistory.put(type1, type2, currentTop, result);
+    return result;
+  }
+
+  /**
+   * Two arrays are equal if:
+   *
+   * <ol>
+   *   <li>Their sets of primary annotations are equal, and
+   *   <li>Their component types are equal
+   * </ol>
+   */
+  @Override
+  public Boolean visitArray_Array(
+      final AnnotatedArrayType type1, final AnnotatedArrayType type2, final Void p) {
+    if (!arePrimeAnnosEqual(type1, type2)) {
+      return false;
+    }
+
+    return areEqual(type1.getComponentType(), type2.getComponentType());
+  }
+
+  /**
+   * Two declared types are equal if:
+   *
+   * <ol>
+   *   <li>The types are of the same class/interfaces
+   *   <li>Their sets of primary annotations are equal
+   *   <li>Their sets of type arguments are equal or one type is raw
+   * </ol>
+   */
+  @Override
+  public Boolean visitDeclared_Declared(
+      final AnnotatedDeclaredType type1, final AnnotatedDeclaredType type2, final Void p) {
+    Boolean pastResult = visitHistory.get(type1, type2, currentTop);
+    if (pastResult != null) {
+      return pastResult;
+    }
+
+    // TODO: same class/interface is not enforced. Why?
+
+    if (!arePrimeAnnosEqual(type1, type2)) {
+      return false;
+    }
+
+    // Prevent infinite recursion e.g. in Issue1587b
+    visitHistory.put(type1, type2, currentTop, true);
+
+    boolean result = visitTypeArgs(type1, type2);
+    visitHistory.put(type1, type2, currentTop, result);
+    return result;
+  }
+
+  /**
+   * A helper class for visitDeclared_Declared. There are subtypes of DefaultTypeHierarchy that need
+   * to customize the handling of type arguments. This method provides a convenient extension point.
+   */
+  protected boolean visitTypeArgs(
+      final AnnotatedDeclaredType type1, final AnnotatedDeclaredType type2) {
+
+    // TODO: ANYTHING WITH RAW TYPES? SHOULD WE HANDLE THEM LIKE DefaultTypeHierarchy, i.e. use
+    // ignoreRawTypes
+    final List<? extends AnnotatedTypeMirror> type1Args = type1.getTypeArguments();
+    final List<? extends AnnotatedTypeMirror> type2Args = type2.getTypeArguments();
+
+    if (type1Args.isEmpty() && type2Args.isEmpty()) {
+      return true;
+    }
+
+    if (type1Args.size() == type2Args.size()) {
+      return areAllEqual(type1Args, type2Args);
+    } else {
+      return true;
+      /* TODO! This should be an error. See framework/tests/all-systems/InitializationVisitor.java
+      * for a failure.
+         throw new BugInCF(
+                 "Mismatching type argument sizes:%n    type 1: %s (%d)%n    type 2: %s (%d)",
+                          type1, type1Args.size(), type2, type2Args.size());
+         return false;
+         */
+    }
+  }
+
+  /**
+   * TODO: SHOULD PRIMARY ANNOTATIONS OVERRIDE INDIVIDUAL BOUND ANNOTATIONS? IF SO THEN WE SHOULD
+   * REMOVE THE arePrimeAnnosEqual AND FIX AnnotatedIntersectionType.
+   *
+   * <p>Two intersection types are equal if:
+   *
+   * <ul>
+   *   <li>Their sets of primary annotations are equal
+   *   <li>Their sets of bounds (the types being intersected) are equal
+   * </ul>
+   */
+  @Override
+  public Boolean visitIntersection_Intersection(
+      final AnnotatedIntersectionType type1, final AnnotatedIntersectionType type2, final Void p) {
+    if (!arePrimeAnnosEqual(type1, type2)) {
+      return false;
+    }
+
+    boolean result = areAllEqual(type1.getBounds(), type2.getBounds());
+    visitHistory.put(type1, type2, currentTop, result);
+    return result;
+  }
+
+  /**
+   * Two primitive types are equal if:
+   *
+   * <ul>
+   *   <li>Their sets of primary annotations are equal
+   * </ul>
+   */
+  @Override
+  public Boolean visitPrimitive_Primitive(
+      final AnnotatedPrimitiveType type1, final AnnotatedPrimitiveType type2, final Void p) {
+    return arePrimeAnnosEqual(type1, type2);
+  }
+
+  /**
+   * Two type variables are equal if:
+   *
+   * <ul>
+   *   <li>Their bounds are equal
+   * </ul>
+   *
+   * Note: Primary annotations will be taken into account when the bounds are retrieved
+   */
+  @Override
+  public Boolean visitTypevar_Typevar(
+      final AnnotatedTypeVariable type1, final AnnotatedTypeVariable type2, final Void p) {
+    Boolean pastResult = visitHistory.get(type1, type2, currentTop);
+    if (pastResult != null) {
+      return pastResult;
+    }
+
+    // TODO: Remove this code when capture conversion is implemented
+    if (TypesUtils.isCaptured(type1.getUnderlyingType())
+        || TypesUtils.isCaptured(type2.getUnderlyingType())) {
+      if (!boundsMatch(type1, type2)) {
+        Boolean result =
+            subtypeAndCompare(type1.getUpperBound(), type2.getUpperBound())
+                && subtypeAndCompare(type1.getLowerBound(), type2.getLowerBound());
+        visitHistory.put(type1, type2, currentTop, result);
+        return result;
+      }
+    }
+
+    Boolean result =
+        areEqual(type1.getUpperBound(), type2.getUpperBound())
+            && areEqual(type1.getLowerBound(), type2.getLowerBound());
+    visitHistory.put(type1, type2, currentTop, result);
+    return result;
+  }
+
+  /**
+   * A temporary solution until we handle CaptureConversion, subtypeAndCompare handles cases in
+   * which we encounter a captured type being compared against a non-captured type. The captured
+   * type may have type arguments that are subtypes of the other type it is being compared to. In
+   * these cases, we will convert the bounds via this method to the other type and then continue on
+   * with the equality comparison. If neither of the type args can be converted to the other than we
+   * just compare the effective annotations on the two types for equality.
+   */
+  boolean subtypeAndCompare(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
+    final Types types = type1.atypeFactory.types;
+    final AnnotatedTypeMirror t1;
+    final AnnotatedTypeMirror t2;
+
+    if (type1.getKind() == TypeKind.NULL && type2.getKind() == TypeKind.NULL) {
+      return areEqual(type1, type2);
+    }
+    if (type1.getKind() == TypeKind.NULL || type2.getKind() == TypeKind.NULL) {
+      t1 = type1;
+      t2 = type2;
+    } else if (types.isSubtype(type2.getUnderlyingType(), type1.getUnderlyingType())) {
+      t1 = type1;
+      t2 = AnnotatedTypes.asSuper(type1.atypeFactory, type2, type1);
+
+    } else if (types.isSubtype(type1.getUnderlyingType(), type2.getUnderlyingType())) {
+      t1 = AnnotatedTypes.asSuper(type1.atypeFactory, type1, type2);
+      t2 = type2;
+
+    } else {
+      t1 = null;
+      t2 = null;
+    }
+
+    if (t1 == null || t2 == null) {
+      final QualifierHierarchy qualifierHierarchy = type1.atypeFactory.getQualifierHierarchy();
+      if (currentTop != null) {
+        return AnnotationUtils.areSame(
+            AnnotatedTypes.findEffectiveAnnotationInHierarchy(
+                qualifierHierarchy, type1, currentTop),
+            AnnotatedTypes.findEffectiveAnnotationInHierarchy(
+                qualifierHierarchy, type2, currentTop));
+      } else {
+        throw new BugInCF("currentTop null");
+      }
+    }
+
+    return areEqual(t1, t2);
+  }
+
+  /**
+   * Returns true if the underlying types of the bounds for type1 and type2 are equal.
+   *
+   * @return true if the underlying types of the bounds for type1 and type2 are equal
+   */
+  public boolean boundsMatch(final AnnotatedTypeVariable type1, final AnnotatedTypeVariable type2) {
+    final Types types = type1.atypeFactory.types;
+
+    return types.isSameType(
+            type1.getUpperBound().getUnderlyingType(), type2.getUpperBound().getUnderlyingType())
+        && types.isSameType(
+            type1.getLowerBound().getUnderlyingType(), type2.getLowerBound().getUnderlyingType());
+  }
+
+  /**
+   * Two wildcards are equal if:
+   *
+   * <ul>
+   *   <li>Their bounds are equal
+   * </ul>
+   *
+   * Note: Primary annotations will be taken into account when the bounds are retrieved
+   *
+   * <p>TODO: IDENTIFY TESTS THAT LEAD TO RECURSIVE BOUNDED WILDCARDS, PERHAPS THE RIGHT THING IS TO
+   * MOVE THE CODE THAT IDENTIFIES REFERENCES TO THE SAME WILDCARD TYPE HERE. WOULD WE EVER WANT TO
+   * HAVE A REFERENCE TO THE SAME WILDCARD WITH DIFFERENT ANNOTATIONS?
+   */
+  @Override
+  public Boolean visitWildcard_Wildcard(
+      final AnnotatedWildcardType type1, final AnnotatedWildcardType type2, final Void p) {
+    Boolean pastResult = visitHistory.get(type1, type2, currentTop);
+    if (pastResult != null) {
+      return pastResult;
+    }
+
+    if (type1.atypeFactory.ignoreUninferredTypeArguments
+        && (type1.isUninferredTypeArgument() || type2.isUninferredTypeArgument())) {
+      return true;
+    }
+
+    Boolean result =
+        areEqual(type1.getExtendsBound(), type2.getExtendsBound())
+            && areEqual(type1.getSuperBound(), type2.getSuperBound());
+    visitHistory.put(type1, type2, currentTop, result);
+    return result;
+  }
+
+  // since we don't do a boxing conversion between primitive and declared types in some cases
+  // we must compare primitives with their boxed counterparts
+  @Override
+  public Boolean visitDeclared_Primitive(
+      AnnotatedDeclaredType type1, AnnotatedPrimitiveType type2, Void p) {
+    if (!TypesUtils.isBoxOf(type1.getUnderlyingType(), type2.getUnderlyingType())) {
+      defaultErrorMessage(type1, type2, p);
+    }
+
+    return arePrimeAnnosEqual(type1, type2);
+  }
+
+  @Override
+  public Boolean visitPrimitive_Declared(
+      AnnotatedPrimitiveType type1, AnnotatedDeclaredType type2, Void p) {
+    if (!TypesUtils.isBoxOf(type2.getUnderlyingType(), type1.getUnderlyingType())) {
+      defaultErrorMessage(type1, type2, p);
+    }
+
+    return arePrimeAnnosEqual(type1, type2);
+  }
+
+  // The following methods are because we use WILDCARDS instead of TYPEVARS for capture converted
+  // wildcards.
+  // TODO: REMOVE THE METHOD BELOW WHEN CAPTURE CONVERSION IS IMPLEMENTED
+  /**
+   * Since the Checker Framework doesn't engage in capture conversion, and since sometimes type
+   * variables are "inferred" to be wildcards, this method allows the comparison of a wildcard to a
+   * type variable even though they should never truly be equal.
+   *
+   * <p>A wildcard is equal to a type variable if:
+   *
+   * <ul>
+   *   <li>The wildcard's bounds are equal to the type variable's bounds
+   * </ul>
+   */
+  @Override
+  public Boolean visitWildcard_Typevar(
+      final AnnotatedWildcardType type1, final AnnotatedTypeVariable type2, final Void p) {
+    Boolean pastResult = visitHistory.get(type1, type2, currentTop);
+    if (pastResult != null) {
+      return pastResult;
+    }
+
+    if (type1.atypeFactory.ignoreUninferredTypeArguments && type1.isUninferredTypeArgument()) {
+      return true;
+    }
+
+    Boolean result =
+        areEqual(type1.getExtendsBound(), type2.getUpperBound())
+            && areEqual(type1.getSuperBound(), type2.getLowerBound());
+
+    visitHistory.put(type1, type2, currentTop, result);
+    return result;
+  }
+
+  @Override
+  public Boolean visitWildcard_Declared(
+      AnnotatedWildcardType type1, AnnotatedDeclaredType type2, Void p) {
+    if (type1.atypeFactory.ignoreUninferredTypeArguments && type1.isUninferredTypeArgument()) {
+      return true;
+    }
+    // TODO: add proper checks
+    return arePrimeAnnosEqual(type1.getExtendsBound(), type2);
+  }
+
+  @Override
+  public Boolean visitDeclared_Wildcard(
+      AnnotatedDeclaredType type1, AnnotatedWildcardType type2, Void p) {
+    if (type2.atypeFactory.ignoreUninferredTypeArguments && type2.isUninferredTypeArgument()) {
+      return true;
+    }
+    final QualifierHierarchy qualifierHierarchy = type1.atypeFactory.getQualifierHierarchy();
+
+    // TODO: add proper checks
+    // TODO: compare whole types instead of just main modifiers
+    AnnotationMirror q1 =
+        AnnotatedTypes.findEffectiveAnnotationInHierarchy(qualifierHierarchy, type1, currentTop);
+    AnnotationMirror q2 =
+        AnnotatedTypes.findEffectiveAnnotationInHierarchy(qualifierHierarchy, type2, currentTop);
+
+    Boolean result = qualifierHierarchy.isSubtype(q1, q2);
+    visitHistory.put(type1, type2, currentTop, result);
+    return result;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityVisitHistory.java b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityVisitHistory.java
new file mode 100644
index 0000000..0062c04
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityVisitHistory.java
@@ -0,0 +1,78 @@
+package org.checkerframework.framework.type;
+
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Stores the result of {@link StructuralEqualityComparer} for type arguments.
+ *
+ * <p>This is similar to {@link SubtypeVisitHistory}, but both true and false results are stored.
+ */
+public class StructuralEqualityVisitHistory {
+
+  /**
+   * Types in this history are structurally equal. (Use {@link SubtypeVisitHistory} because it
+   * implements a {@code Map<Pair<AnnotatedTypeMirror, AnnotatedTypeMirror>,
+   * Set<AnnotationMirror>>})
+   */
+  private final SubtypeVisitHistory trueHistory;
+  /**
+   * Types in this history are not structurally equal. (Use {@link SubtypeVisitHistory} because it
+   * implements a {@code Map<Pair<AnnotatedTypeMirror, AnnotatedTypeMirror>,
+   * Set<AnnotationMirror>>})
+   */
+  private final SubtypeVisitHistory falseHistory;
+
+  /** Creates an empty StructuralEqualityVisitHistory. */
+  public StructuralEqualityVisitHistory() {
+    this.trueHistory = new SubtypeVisitHistory();
+    this.falseHistory = new SubtypeVisitHistory();
+  }
+
+  /**
+   * Put result of comparing {@code type1} and {@code type2} for structural equality for the given
+   * hierarchy.
+   *
+   * @param type1 the first type
+   * @param type2 the second type
+   * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy
+   *     are considered
+   * @param result whether {@code type1} is structurally equal to {@code type2}
+   */
+  public void put(
+      final AnnotatedTypeMirror type1,
+      final AnnotatedTypeMirror type2,
+      AnnotationMirror hierarchy,
+      boolean result) {
+    if (result) {
+      trueHistory.put(type1, type2, hierarchy, true);
+      falseHistory.remove(type1, type2, hierarchy);
+    } else {
+      falseHistory.put(type1, type2, hierarchy, true);
+      trueHistory.remove(type1, type2, hierarchy);
+    }
+  }
+
+  /**
+   * Return whether or not the two types are structurally equal for the given hierarchy or {@code
+   * null} if the types have not been visited for the given hierarchy.
+   *
+   * @param type1 the first type
+   * @param type2 the second type
+   * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy
+   *     are considered
+   * @return whether or not the two types are structurally equal for the given hierarchy or {@code
+   *     null} if the types have not been visited for the given hierarchy
+   */
+  public @Nullable Boolean get(
+      final AnnotatedTypeMirror type1,
+      final AnnotatedTypeMirror type2,
+      AnnotationMirror hierarchy) {
+    if (falseHistory.contains(type1, type2, hierarchy)) {
+      return false;
+    } else if (trueHistory.contains(type1, type2, hierarchy)) {
+      return true;
+    }
+    return null;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSubsetQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSubsetQualifierHierarchy.java
new file mode 100644
index 0000000..3a43da3
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSubsetQualifierHierarchy.java
@@ -0,0 +1,126 @@
+package org.checkerframework.framework.type;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * A {@link org.checkerframework.framework.type.QualifierHierarchy} where, when a qualifier has
+ * arguments, the subtype relation is determined by a subset test on the elements (arguments). The
+ * elements must be strings.
+ *
+ * <p>This assumes that if the lub or glb of two qualifiers has elements, then both of the arguments
+ * had the same kind as the result does.
+ */
+@AnnotatedFor("nullness")
+public class SubtypeIsSubsetQualifierHierarchy extends MostlyNoElementQualifierHierarchy {
+
+  /** The processing environment; used for creating annotations. */
+  ProcessingEnvironment processingEnv;
+
+  /**
+   * Creates a SubtypeIsSubsetQualifierHierarchy from the given classes.
+   *
+   * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+   * @param processingEnv processing environment
+   */
+  public SubtypeIsSubsetQualifierHierarchy(
+      Collection<Class<? extends Annotation>> qualifierClasses,
+      ProcessingEnvironment processingEnv) {
+    super(qualifierClasses, processingEnv.getElementUtils());
+    this.processingEnv = processingEnv;
+  }
+
+  @Override
+  protected boolean isSubtypeWithElements(
+      AnnotationMirror subAnno,
+      QualifierKind subKind,
+      AnnotationMirror superAnno,
+      QualifierKind superKind) {
+    if (subKind == superKind) {
+      List<String> superValues = valuesStringList(superAnno);
+      List<String> subValues = valuesStringList(subAnno);
+      return superValues.containsAll(subValues);
+    }
+    return subKind.isSubtypeOf(superKind);
+  }
+
+  @Override
+  protected AnnotationMirror leastUpperBoundWithElements(
+      AnnotationMirror a1,
+      QualifierKind qualifierKind1,
+      AnnotationMirror a2,
+      QualifierKind qualifierKind2,
+      QualifierKind lubKind) {
+    if (qualifierKind1 == qualifierKind2) {
+      List<String> a1Values = valuesStringList(a1);
+      List<String> a2Values = valuesStringList(a2);
+      LinkedHashSet<String> set = new LinkedHashSet<>(a1Values);
+      set.addAll(a2Values);
+      return createAnnotationMirrorWithValue(lubKind, set);
+    } else if (lubKind == qualifierKind1) {
+      return a1;
+    } else if (lubKind == qualifierKind2) {
+      return a2;
+    } else {
+      throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, lubKind);
+    }
+  }
+
+  @Override
+  protected AnnotationMirror greatestLowerBoundWithElements(
+      AnnotationMirror a1,
+      QualifierKind qualifierKind1,
+      AnnotationMirror a2,
+      QualifierKind qualifierKind2,
+      QualifierKind glbKind) {
+    if (qualifierKind1 == qualifierKind2) {
+      List<String> a1Values = valuesStringList(a1);
+      List<String> a2Values = valuesStringList(a2);
+      LinkedHashSet<String> set = new LinkedHashSet<>(a1Values);
+      set.retainAll(a2Values);
+      return createAnnotationMirrorWithValue(glbKind, set);
+    } else if (glbKind == qualifierKind1) {
+      return a1;
+    } else if (glbKind == qualifierKind2) {
+      return a2;
+    } else {
+      throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, glbKind);
+    }
+  }
+
+  /**
+   * Returns a mutable list containing the {@code values} element of the given annotation. The
+   * {@code values} element must be an array of strings.
+   *
+   * @param anno an annotation
+   * @return a mutable list containing the {@code values} element; may be the empty list
+   */
+  private List<String> valuesStringList(AnnotationMirror anno) {
+    @SuppressWarnings("deprecation") // concrete annotation class is not known
+    List<String> result = AnnotationUtils.getElementValueArray(anno, "value", String.class, true);
+    return result;
+  }
+
+  /**
+   * Returns an AnnotationMirror corresponding to the given kind and values.
+   *
+   * @param kind the qualifier kind
+   * @param values the annotation's {@code values} element/argument
+   * @return an annotation of the given kind and values
+   */
+  private AnnotationMirror createAnnotationMirrorWithValue(
+      QualifierKind kind, LinkedHashSet<String> values) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, kind.getAnnotationClass());
+    builder.setValue("value", values.toArray());
+    return builder.build();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSupersetQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSupersetQualifierHierarchy.java
new file mode 100644
index 0000000..99b8f81
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSupersetQualifierHierarchy.java
@@ -0,0 +1,126 @@
+package org.checkerframework.framework.type;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * A {@link org.checkerframework.framework.type.QualifierHierarchy} where, when a qualifier has
+ * arguments, the subtype relation is determined by a superset test on the elements (arguments). The
+ * elements must be strings.
+ *
+ * <p>This assumes that if the lub or glb of two qualifiers has elements, then both of the arguments
+ * had the same kind as the result does.
+ */
+@AnnotatedFor("nullness")
+public class SubtypeIsSupersetQualifierHierarchy extends MostlyNoElementQualifierHierarchy {
+
+  /** The processing environment; used for creating annotations. */
+  ProcessingEnvironment processingEnv;
+
+  /**
+   * Creates a SubtypeIsSupersetQualifierHierarchy from the given classes.
+   *
+   * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+   * @param processingEnv processing environment
+   */
+  public SubtypeIsSupersetQualifierHierarchy(
+      Collection<Class<? extends Annotation>> qualifierClasses,
+      ProcessingEnvironment processingEnv) {
+    super(qualifierClasses, processingEnv.getElementUtils());
+    this.processingEnv = processingEnv;
+  }
+
+  @Override
+  protected boolean isSubtypeWithElements(
+      AnnotationMirror subAnno,
+      QualifierKind subKind,
+      AnnotationMirror superAnno,
+      QualifierKind superKind) {
+    if (subKind == superKind) {
+      List<String> superValues = valuesStringList(superAnno);
+      List<String> subValues = valuesStringList(subAnno);
+      return subValues.containsAll(superValues);
+    }
+    return subKind.isSubtypeOf(superKind);
+  }
+
+  @Override
+  protected AnnotationMirror leastUpperBoundWithElements(
+      AnnotationMirror a1,
+      QualifierKind qualifierKind1,
+      AnnotationMirror a2,
+      QualifierKind qualifierKind2,
+      QualifierKind lubKind) {
+    if (qualifierKind1 == qualifierKind2) {
+      List<String> a1Values = valuesStringList(a1);
+      List<String> a2Values = valuesStringList(a2);
+      LinkedHashSet<String> set = new LinkedHashSet<>(a1Values);
+      set.retainAll(a2Values);
+      return createAnnotationMirrorWithValue(lubKind, set);
+    } else if (lubKind == qualifierKind1) {
+      return a1;
+    } else if (lubKind == qualifierKind2) {
+      return a2;
+    } else {
+      throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, lubKind);
+    }
+  }
+
+  @Override
+  protected AnnotationMirror greatestLowerBoundWithElements(
+      AnnotationMirror a1,
+      QualifierKind qualifierKind1,
+      AnnotationMirror a2,
+      QualifierKind qualifierKind2,
+      QualifierKind glbKind) {
+    if (qualifierKind1 == qualifierKind2) {
+      List<String> a1Values = valuesStringList(a1);
+      List<String> a2Values = valuesStringList(a2);
+      LinkedHashSet<String> set = new LinkedHashSet<>(a1Values);
+      set.addAll(a2Values);
+      return createAnnotationMirrorWithValue(glbKind, set);
+    } else if (glbKind == qualifierKind1) {
+      return a1;
+    } else if (glbKind == qualifierKind2) {
+      return a2;
+    } else {
+      throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, glbKind);
+    }
+  }
+
+  /**
+   * Returns a mutable list containing the {@code values} element of the given annotation. The
+   * {@code values} element must be an array of strings.
+   *
+   * @param anno an annotation
+   * @return a mutable list containing the {@code values} element; may be the empty list
+   */
+  private List<String> valuesStringList(AnnotationMirror anno) {
+    @SuppressWarnings("deprecation") // concrete annotation class is not known
+    List<String> result = AnnotationUtils.getElementValueArray(anno, "value", String.class, true);
+    return result;
+  }
+
+  /**
+   * Returns an AnnotationMirror corresponding to the given kind and values.
+   *
+   * @param kind the qualifier kind
+   * @param values the annotation's {@code values} element/argument
+   * @return an annotation of the given kind and values
+   */
+  private AnnotationMirror createAnnotationMirrorWithValue(
+      QualifierKind kind, LinkedHashSet<String> values) {
+    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, kind.getAnnotationClass());
+    builder.setValue("value", values.toArray());
+    return builder.build();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java b/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java
new file mode 100644
index 0000000..f8f8206
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java
@@ -0,0 +1,104 @@
+package org.checkerframework.framework.type;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * THIS CLASS IS DESIGNED FOR USE WITH DefaultTypeHierarchy, DefaultRawnessComparer, and
+ * StructuralEqualityComparer ONLY.
+ *
+ * <p>VisitHistory tracks triples of (type1, type2, top), where type1 is a subtype of type2. It does
+ * not track when type1 is not a subtype of type2; such entries are missing from the history.
+ * Clients of this class can check whether or not they have visited an equivalent pair of
+ * AnnotatedTypeMirrors already. This is necessary in order to halt visiting on recursive bounds.
+ *
+ * <p>This class is primarily used to implement isSubtype(ATM, ATM). The pair of types corresponds
+ * to the subtype and the supertype being checked. A single subtype may be visited more than once,
+ * but with a different supertype. For example, if the two types are {@code @A T extends @B
+ * Serializable<T>} and {@code @C Serializable<?>}, then isSubtype is first called one those types
+ * and then on {@code @B Serializable<T>} and {@code @C Serializable<?>}.
+ */
+// TODO: do we need to clear the history sometimes?
+public class SubtypeVisitHistory {
+
+  /**
+   * The keys are pairs of types; the value is the set of qualifier hierarchy roots for which the
+   * key is in a subtype relationship.
+   */
+  private final Map<Pair<AnnotatedTypeMirror, AnnotatedTypeMirror>, Set<AnnotationMirror>> visited;
+
+  public SubtypeVisitHistory() {
+    this.visited = new HashMap<>();
+  }
+
+  /**
+   * Put a visit for {@code type1}, {@code type2}, and {@code top} in the history. Has no effect if
+   * isSubtype is false.
+   *
+   * @param type1 the first type
+   * @param type2 the second type
+   * @param currentTop the top of the relevant type hierarchy; only annotations from that hierarchy
+   *     are considered
+   * @param isSubtype whether {@code type1} is a subtype of {@code type2}; if false, this method
+   *     does nothing
+   */
+  public void put(
+      final AnnotatedTypeMirror type1,
+      final AnnotatedTypeMirror type2,
+      AnnotationMirror currentTop,
+      boolean isSubtype) {
+    if (!isSubtype) {
+      // Only store information about subtype relations that hold.
+      return;
+    }
+    Pair<AnnotatedTypeMirror, AnnotatedTypeMirror> key = Pair.of(type1, type2);
+    Set<AnnotationMirror> hit = visited.get(key);
+
+    if (hit != null) {
+      hit.add(currentTop);
+    } else {
+      hit = new HashSet<>();
+      hit.add(currentTop);
+      this.visited.put(key, hit);
+    }
+  }
+
+  /** Remove {@code type1} and {@code type2}. */
+  public void remove(
+      final AnnotatedTypeMirror type1,
+      final AnnotatedTypeMirror type2,
+      AnnotationMirror currentTop) {
+    Pair<AnnotatedTypeMirror, AnnotatedTypeMirror> key = Pair.of(type1, type2);
+    Set<AnnotationMirror> hit = visited.get(key);
+    if (hit != null) {
+      hit.remove(currentTop);
+      if (hit.isEmpty()) {
+        visited.remove(key);
+      }
+    }
+  }
+
+  /**
+   * Returns true if type1 and type2 (or an equivalent pair) have been passed to the put method
+   * previously.
+   *
+   * @return true if an equivalent pair has already been added to the history
+   */
+  public boolean contains(
+      final AnnotatedTypeMirror type1,
+      final AnnotatedTypeMirror type2,
+      AnnotationMirror currentTop) {
+    Pair<AnnotatedTypeMirror, AnnotatedTypeMirror> key = Pair.of(type1, type2);
+    Set<AnnotationMirror> hit = visited.get(key);
+    return hit != null && hit.contains(currentTop);
+  }
+
+  @Override
+  public String toString() {
+    return "VisitHistory( " + visited + " )";
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java b/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java
new file mode 100644
index 0000000..795b187
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java
@@ -0,0 +1,453 @@
+package org.checkerframework.framework.type;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.Tree;
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
+import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeVisitor;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * Finds the direct supertypes of an input AnnotatedTypeMirror. See <a
+ * href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.10.2">JLS section
+ * 4.10.2</a>.
+ *
+ * @see Types#directSupertypes(TypeMirror)
+ */
+class SupertypeFinder {
+
+  // Version of method below for declared types
+  /**
+   * See {@link Types#directSupertypes(TypeMirror)}.
+   *
+   * @param type the type whose supertypes to return
+   * @return the immediate supertypes of {@code type}
+   * @see Types#directSupertypes(TypeMirror)
+   */
+  public static List<AnnotatedDeclaredType> directSupertypes(AnnotatedDeclaredType type) {
+    SupertypeFindingVisitor supertypeFindingVisitor =
+        new SupertypeFindingVisitor(type.atypeFactory);
+    List<AnnotatedDeclaredType> supertypes = supertypeFindingVisitor.visitDeclared(type, null);
+    type.atypeFactory.postDirectSuperTypes(type, supertypes);
+    return supertypes;
+  }
+
+  // Version of method above for all types
+  /**
+   * See {@link Types#directSupertypes(TypeMirror)}.
+   *
+   * @param type the type whose supertypes to return
+   * @return the immediate supertypes of {@code type}
+   * @see Types#directSupertypes(TypeMirror)
+   */
+  public static final List<? extends AnnotatedTypeMirror> directSupertypes(
+      AnnotatedTypeMirror type) {
+    SupertypeFindingVisitor supertypeFindingVisitor =
+        new SupertypeFindingVisitor(type.atypeFactory);
+    List<? extends AnnotatedTypeMirror> supertypes = supertypeFindingVisitor.visit(type, null);
+    type.atypeFactory.postDirectSuperTypes(type, supertypes);
+    return supertypes;
+  }
+
+  private static class SupertypeFindingVisitor
+      extends SimpleAnnotatedTypeVisitor<List<? extends AnnotatedTypeMirror>, Void> {
+    private final Types types;
+    private final AnnotatedTypeFactory atypeFactory;
+    private final TypeParamReplacer typeParamReplacer;
+
+    SupertypeFindingVisitor(AnnotatedTypeFactory atypeFactory) {
+      this.atypeFactory = atypeFactory;
+      this.types = atypeFactory.types;
+      this.typeParamReplacer = new TypeParamReplacer(types);
+    }
+
+    @Override
+    public List<AnnotatedTypeMirror> defaultAction(AnnotatedTypeMirror t, Void p) {
+      return Collections.emptyList();
+    }
+
+    /**
+     * Primitive Rules:
+     *
+     * <pre>{@code
+     * double >1 float
+     * float >1 long
+     * long >1 int
+     * int >1 char
+     * int >1 short
+     * short >1 byte
+     * }</pre>
+     *
+     * For easiness:
+     *
+     * <pre>{@code
+     * boxed(primitiveType) >: primitiveType
+     * }</pre>
+     */
+    @Override
+    public List<AnnotatedTypeMirror> visitPrimitive(AnnotatedPrimitiveType type, Void p) {
+      List<AnnotatedTypeMirror> superTypes = new ArrayList<>(1);
+      Set<AnnotationMirror> annotations = type.getAnnotations();
+
+      // Find Boxed type
+      TypeElement boxed = types.boxedClass(type.getUnderlyingType());
+      AnnotatedDeclaredType boxedType = atypeFactory.getAnnotatedType(boxed);
+      boxedType.replaceAnnotations(annotations);
+      superTypes.add(boxedType);
+
+      TypeKind superPrimitiveType = null;
+
+      if (type.getKind() == TypeKind.BOOLEAN) {
+        // Nothing
+      } else if (type.getKind() == TypeKind.BYTE) {
+        superPrimitiveType = TypeKind.SHORT;
+      } else if (type.getKind() == TypeKind.CHAR) {
+        superPrimitiveType = TypeKind.INT;
+      } else if (type.getKind() == TypeKind.DOUBLE) {
+        // Nothing
+      } else if (type.getKind() == TypeKind.FLOAT) {
+        superPrimitiveType = TypeKind.DOUBLE;
+      } else if (type.getKind() == TypeKind.INT) {
+        superPrimitiveType = TypeKind.LONG;
+      } else if (type.getKind() == TypeKind.LONG) {
+        superPrimitiveType = TypeKind.FLOAT;
+      } else if (type.getKind() == TypeKind.SHORT) {
+        superPrimitiveType = TypeKind.INT;
+      } else {
+        assert false : "Forgot the primitive " + type;
+      }
+
+      if (superPrimitiveType != null) {
+        AnnotatedPrimitiveType superPrimitive =
+            (AnnotatedPrimitiveType)
+                atypeFactory.toAnnotatedType(types.getPrimitiveType(superPrimitiveType), false);
+        superPrimitive.addAnnotations(annotations);
+        superTypes.add(superPrimitive);
+      }
+
+      return superTypes;
+    }
+
+    @Override
+    public List<AnnotatedDeclaredType> visitDeclared(AnnotatedDeclaredType type, Void p) {
+      // Set<AnnotationMirror> annotations = type.getAnnotations();
+
+      TypeElement typeElement = (TypeElement) type.getUnderlyingType().asElement();
+
+      // Mapping of type variable to actual types
+      Map<TypeParameterElement, AnnotatedTypeMirror> mapping = new HashMap<>();
+
+      if (type.getTypeArguments().size() != typeElement.getTypeParameters().size()) {
+        if (!type.wasRaw()) {
+          throw new BugInCF(
+              "AnnotatedDeclaredType's element has a different number of type parameters than"
+                  + " type.%ntype=%s%nelement=%s",
+              type, typeElement);
+        }
+      }
+
+      AnnotatedDeclaredType enclosing = type;
+      while (enclosing != null) {
+        TypeElement enclosingTypeElement = (TypeElement) enclosing.getUnderlyingType().asElement();
+        List<AnnotatedTypeMirror> typeArgs = enclosing.getTypeArguments();
+        List<? extends TypeParameterElement> typeParams = enclosingTypeElement.getTypeParameters();
+        for (int i = 0; i < enclosing.getTypeArguments().size(); ++i) {
+          AnnotatedTypeMirror typArg = typeArgs.get(i);
+          TypeParameterElement ele = typeParams.get(i);
+          mapping.put(ele, typArg);
+        }
+
+        enclosing = enclosing.getEnclosingType();
+      }
+
+      List<AnnotatedDeclaredType> supertypes = new ArrayList<>();
+
+      ClassTree classTree = atypeFactory.trees.getTree(typeElement);
+      // Testing against enum and annotation. Ideally we can simply use element!
+      if (classTree != null) {
+        supertypes.addAll(supertypesFromTree(type, classTree));
+      } else {
+        supertypes.addAll(supertypesFromElement(type, typeElement));
+        // final Element elem = type.getElement() == null ? typeElement : type.getElement();
+      }
+
+      if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) {
+        TypeElement jlaElement =
+            atypeFactory.elements.getTypeElement(Annotation.class.getCanonicalName());
+        AnnotatedDeclaredType jlaAnnotation = atypeFactory.fromElement(jlaElement);
+        jlaAnnotation.addAnnotations(type.getAnnotations());
+        supertypes.add(jlaAnnotation);
+      }
+
+      for (AnnotatedDeclaredType dt : supertypes) {
+        typeParamReplacer.visit(dt, mapping);
+      }
+
+      return supertypes;
+    }
+
+    private List<AnnotatedDeclaredType> supertypesFromElement(
+        AnnotatedDeclaredType type, TypeElement typeElement) {
+      List<AnnotatedDeclaredType> supertypes = new ArrayList<>();
+      // Find the super types: Start with enums and superclass
+      if (typeElement.getKind() == ElementKind.ENUM) {
+        supertypes.add(createEnumSuperType(type, typeElement));
+      } else if (typeElement.getSuperclass().getKind() != TypeKind.NONE) {
+        DeclaredType superClass = (DeclaredType) typeElement.getSuperclass();
+        AnnotatedDeclaredType dt =
+            (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(superClass, false);
+        supertypes.add(dt);
+
+      } else if (!ElementUtils.isObject(typeElement)) {
+        supertypes.add(AnnotatedTypeMirror.createTypeOfObject(atypeFactory));
+      }
+
+      for (TypeMirror st : typeElement.getInterfaces()) {
+        if (type.wasRaw()) {
+          st = types.erasure(st);
+        }
+        AnnotatedDeclaredType ast = (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(st, false);
+        supertypes.add(ast);
+        if (type.wasRaw()) {
+          if (st.getKind() == TypeKind.DECLARED) {
+            final List<? extends TypeMirror> typeArgs = ((DeclaredType) st).getTypeArguments();
+            final List<AnnotatedTypeMirror> annotatedTypeArgs = ast.getTypeArguments();
+            for (int i = 0; i < typeArgs.size(); i++) {
+              atypeFactory.addComputedTypeAnnotations(
+                  types.asElement(typeArgs.get(i)), annotatedTypeArgs.get(i));
+            }
+          }
+        }
+      }
+      ElementAnnotationApplier.annotateSupers(supertypes, typeElement);
+
+      if (type.wasRaw()) {
+        for (AnnotatedDeclaredType adt : supertypes) {
+          adt.setWasRaw();
+        }
+      }
+      return supertypes;
+    }
+
+    private List<AnnotatedDeclaredType> supertypesFromTree(
+        AnnotatedDeclaredType type, ClassTree classTree) {
+      List<AnnotatedDeclaredType> supertypes = new ArrayList<>();
+      if (classTree.getExtendsClause() != null) {
+        AnnotatedDeclaredType adt =
+            (AnnotatedDeclaredType)
+                atypeFactory.getAnnotatedTypeFromTypeTree(classTree.getExtendsClause());
+        supertypes.add(adt);
+      } else if (!ElementUtils.isObject(TreeUtils.elementFromDeclaration(classTree))) {
+        supertypes.add(AnnotatedTypeMirror.createTypeOfObject(atypeFactory));
+      }
+
+      for (Tree implemented : classTree.getImplementsClause()) {
+        AnnotatedDeclaredType adt =
+            (AnnotatedDeclaredType) atypeFactory.getAnnotatedTypeFromTypeTree(implemented);
+        if (adt.getTypeArguments().size() != adt.getUnderlyingType().getTypeArguments().size()
+            && classTree.getSimpleName().contentEquals("")) {
+          // classTree is an anonymous class with a diamond.
+          List<AnnotatedTypeMirror> args =
+              CollectionsPlume.mapList(
+                  (TypeParameterElement element) -> {
+                    AnnotatedTypeMirror arg =
+                        AnnotatedTypeMirror.createType(element.asType(), atypeFactory, false);
+                    // TODO: After #979 is fixed, calculate the correct type
+                    // using inference.
+                    return atypeFactory.getUninferredWildcardType((AnnotatedTypeVariable) arg);
+                  },
+                  TypesUtils.getTypeElement(adt.getUnderlyingType()).getTypeParameters());
+          adt.setTypeArguments(args);
+        }
+        supertypes.add(adt);
+      }
+
+      TypeElement elem = TreeUtils.elementFromDeclaration(classTree);
+      if (elem.getKind() == ElementKind.ENUM) {
+        supertypes.add(createEnumSuperType(type, elem));
+      }
+      if (type.wasRaw()) {
+        for (AnnotatedDeclaredType adt : supertypes) {
+          adt.setWasRaw();
+        }
+      }
+      return supertypes;
+    }
+
+    /**
+     * All enums implicitly extend {@code Enum<MyEnum>}, where {@code MyEnum} is the type of the
+     * enum. This method creates the AnnotatedTypeMirror for {@code Enum<MyEnum>} where the
+     * annotation on {@code MyEnum} is copied from the annotation on the upper bound of the type
+     * argument to Enum. For example, {@code class Enum<E extend @HERE Enum<E>>}.
+     *
+     * @param type annotated type of an enum
+     * @param elem element corresponding to {@code type}
+     * @return enum super type
+     */
+    private AnnotatedDeclaredType createEnumSuperType(
+        AnnotatedDeclaredType type, TypeElement elem) {
+      DeclaredType dt = (DeclaredType) elem.getSuperclass();
+      AnnotatedDeclaredType adt = (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(dt, false);
+      for (AnnotatedTypeMirror t : adt.getTypeArguments()) {
+        // If the type argument of super is the same as the input type
+        if (atypeFactory.types.isSameType(t.getUnderlyingType(), type.getUnderlyingType())) {
+          Set<AnnotationMirror> bounds =
+              ((AnnotatedDeclaredType) atypeFactory.getAnnotatedType(dt.asElement()))
+                  .typeArgs
+                  .get(0)
+                  .getEffectiveAnnotations();
+          t.addAnnotations(bounds);
+        }
+      }
+      adt.addAnnotations(type.getAnnotations());
+      return adt;
+    }
+
+    /**
+     *
+     *
+     * <pre>{@code
+     * For type = A[ ] ==>
+     *  Object >: A[ ]
+     *  Clonable >: A[ ]
+     *  java.io.Serializable >: A[ ]
+     *
+     * if A is reference type, then also
+     *  B[ ] >: A[ ] for any B[ ] >: A[ ]
+     * }</pre>
+     */
+    @Override
+    public List<AnnotatedTypeMirror> visitArray(AnnotatedArrayType type, Void p) {
+      List<AnnotatedTypeMirror> superTypes = new ArrayList<>();
+      Set<AnnotationMirror> annotations = type.getAnnotations();
+      final AnnotatedTypeMirror objectType = atypeFactory.getAnnotatedType(Object.class);
+      objectType.addAnnotations(annotations);
+      superTypes.add(objectType);
+
+      final AnnotatedTypeMirror cloneableType = atypeFactory.getAnnotatedType(Cloneable.class);
+      cloneableType.addAnnotations(annotations);
+      superTypes.add(cloneableType);
+
+      final AnnotatedTypeMirror serializableType =
+          atypeFactory.getAnnotatedType(Serializable.class);
+      serializableType.addAnnotations(annotations);
+      superTypes.add(serializableType);
+
+      for (AnnotatedTypeMirror sup : type.getComponentType().directSupertypes()) {
+        ArrayType arrType = atypeFactory.types.getArrayType(sup.getUnderlyingType());
+        AnnotatedArrayType aarrType =
+            (AnnotatedArrayType) atypeFactory.toAnnotatedType(arrType, false);
+        aarrType.setComponentType(sup);
+        aarrType.addAnnotations(annotations);
+        superTypes.add(aarrType);
+      }
+
+      return superTypes;
+    }
+
+    @Override
+    public List<AnnotatedTypeMirror> visitTypeVariable(AnnotatedTypeVariable type, Void p) {
+      return Collections.singletonList(type.getUpperBound().deepCopy());
+    }
+
+    @Override
+    public List<AnnotatedTypeMirror> visitWildcard(AnnotatedWildcardType type, Void p) {
+      return Collections.singletonList(type.getExtendsBound().deepCopy());
+    }
+
+    /**
+     * Note: The explanation below is my interpretation of why we have this code. I am not sure if
+     * this was the author's original intent but I can see no other reasoning, exercise caution:
+     *
+     * <p>Classes may have type parameters that are used in extends or implements clauses. E.g.
+     * {@code class MyList<T> extends List<T>}
+     *
+     * <p>Direct supertypes will contain a type {@code List<T>} but the type T may become out of
+     * sync with the annotations on type {@code MyList<T>}. To keep them in-sync, we substitute out
+     * the copy of T with the same reference to T that is on {@code MyList<T>}
+     */
+    private static class TypeParamReplacer
+        extends AnnotatedTypeScanner<Void, Map<TypeParameterElement, AnnotatedTypeMirror>> {
+      private final Types types;
+
+      public TypeParamReplacer(Types types) {
+        this.types = types;
+      }
+
+      @Override
+      public Void visitDeclared(
+          AnnotatedDeclaredType type, Map<TypeParameterElement, AnnotatedTypeMirror> mapping) {
+        if (visitedNodes.containsKey(type)) {
+          return visitedNodes.get(type);
+        }
+        visitedNodes.put(type, null);
+        if (type.getEnclosingType() != null) {
+          scan(type.getEnclosingType(), mapping);
+        }
+
+        List<AnnotatedTypeMirror> args = new ArrayList<>(type.getTypeArguments().size());
+        for (AnnotatedTypeMirror arg : type.getTypeArguments()) {
+          Element elem = types.asElement(arg.getUnderlyingType());
+          if ((elem != null)
+              && (elem.getKind() == ElementKind.TYPE_PARAMETER)
+              && mapping.containsKey(elem)) {
+            AnnotatedTypeMirror other = mapping.get(elem).deepCopy();
+            other.replaceAnnotations(arg.getAnnotationsField());
+            args.add(other);
+          } else {
+            args.add(arg);
+            scan(arg, mapping);
+          }
+        }
+        type.setTypeArguments(args);
+
+        return null;
+      }
+
+      @Override
+      public Void visitArray(
+          AnnotatedArrayType type, Map<TypeParameterElement, AnnotatedTypeMirror> mapping) {
+        AnnotatedTypeMirror comptype = type.getComponentType();
+        Element elem = types.asElement(comptype.getUnderlyingType());
+        AnnotatedTypeMirror other;
+        if ((elem != null)
+            && (elem.getKind() == ElementKind.TYPE_PARAMETER)
+            && mapping.containsKey(elem)) {
+          other = mapping.get(elem);
+          other.replaceAnnotations(comptype.getAnnotationsField());
+          type.setComponentType(other);
+        } else {
+          scan(type.getComponentType(), mapping);
+        }
+
+        return null;
+      }
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/SyntheticArrays.java b/framework/src/main/java/org/checkerframework/framework/type/SyntheticArrays.java
new file mode 100644
index 0000000..5891780
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/SyntheticArrays.java
@@ -0,0 +1,43 @@
+package org.checkerframework.framework.type;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+
+/**
+ * SyntheticArrays exists solely to fix AnnotatedTypeMirrors that need to be adapted from Array type
+ * to a specific kind of array. There are no classes for arrays. Instead, for each type of array
+ * (e.g. String[]) the compiler/JVM creates a synthetic type for them.
+ */
+public class SyntheticArrays {
+
+  /**
+   * Returns true if this combination of type/elem represents an array.clone.
+   *
+   * @param type a type with a method/field of elem
+   * @param elem an element which is a member of type
+   * @return true if this combination of type/elem represents an array.clone
+   */
+  public static boolean isArrayClone(final AnnotatedTypeMirror type, final Element elem) {
+    return type.getKind() == TypeKind.ARRAY
+        && elem.getKind() == ElementKind.METHOD
+        && elem.getSimpleName().contentEquals("clone");
+  }
+
+  /**
+   * Returns the annotated type of methodElem with its return type replaced by newReturnType.
+   *
+   * @param methodElem identifies a method that should have an AnnotatedArrayType as its return type
+   * @param newReturnType identifies a type that should replace methodElem's return type
+   * @return the annotated type of methodElem with its return type replaced by newReturnType
+   */
+  public static AnnotatedExecutableType replaceReturnType(
+      final Element methodElem, final AnnotatedArrayType newReturnType) {
+    final AnnotatedExecutableType method =
+        (AnnotatedExecutableType) newReturnType.atypeFactory.getAnnotatedType(methodElem);
+    method.returnType = newReturnType;
+    return method;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromClassVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromClassVisitor.java
new file mode 100644
index 0000000..7516fce
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromClassVisitor.java
@@ -0,0 +1,23 @@
+package org.checkerframework.framework.type;
+
+import com.sun.source.tree.ClassTree;
+import javax.lang.model.element.TypeElement;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Converts ClassTrees into AnnotatedDeclaredType.
+ *
+ * @see org.checkerframework.framework.type.TypeFromTree
+ */
+class TypeFromClassVisitor extends TypeFromTreeVisitor {
+
+  @Override
+  public AnnotatedTypeMirror visitClass(ClassTree node, AnnotatedTypeFactory f) {
+    TypeElement elt = TreeUtils.elementFromDeclaration(node);
+    AnnotatedTypeMirror result = f.toAnnotatedType(elt.asType(), true);
+
+    ElementAnnotationApplier.apply(result, elt, f);
+
+    return result;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java
new file mode 100644
index 0000000..13b180d
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java
@@ -0,0 +1,355 @@
+package org.checkerframework.framework.type;
+
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.InstanceOfTree;
+import com.sun.source.tree.IntersectionTypeTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.PrimitiveTypeTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.WildcardTree;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Converts ExpressionTrees into AnnotatedTypeMirrors.
+ *
+ * <p>The type of some expressions depends on the checker, so for these expressions, a checker
+ * should add annotations in a {@link
+ * org.checkerframework.framework.type.treeannotator.TreeAnnotator} and/or the {@link
+ * org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator}. These trees are:
+ *
+ * <ul>
+ *   <li>{@code BinaryTree}
+ *   <li>{@code CompoundAssignmentTree}
+ *   <li>{@code InstanceOfTree}
+ *   <li>{@code LiteralTree}
+ *   <li>{@code UnaryTree}
+ * </ul>
+ *
+ * Other expressions are in fact type trees and their annotataed type mirrors are computed as type
+ * trees:
+ *
+ * <ul>
+ *   <li>{@code AnnotatedTypeTree}
+ *   <li>{@code TypeCastTree}
+ *   <li>{@code PrimitiveTypeTree}
+ *   <li>{@code ArrayTypeTree}
+ *   <li>{@code ParameterizedTypeTree}
+ *   <li>{@code IntersectionTypeTree}
+ * </ul>
+ */
+class TypeFromExpressionVisitor extends TypeFromTreeVisitor {
+
+  @Override
+  public AnnotatedTypeMirror visitBinary(BinaryTree node, AnnotatedTypeFactory f) {
+    return f.type(node);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitCompoundAssignment(
+      CompoundAssignmentTree node, AnnotatedTypeFactory f) {
+    return f.type(node);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitInstanceOf(InstanceOfTree node, AnnotatedTypeFactory f) {
+    return f.type(node);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitLiteral(LiteralTree node, AnnotatedTypeFactory f) {
+    return f.type(node);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitUnary(UnaryTree node, AnnotatedTypeFactory f) {
+    return f.type(node);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree node, AnnotatedTypeFactory f) {
+    return f.fromTypeTree(node);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitTypeCast(TypeCastTree node, AnnotatedTypeFactory f) {
+
+    // Use the annotated type of the type in the cast.
+    return f.fromTypeTree(node.getType());
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree node, AnnotatedTypeFactory f) {
+    // for e.g. "int.class"
+    return f.fromTypeTree(node);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitArrayType(ArrayTypeTree node, AnnotatedTypeFactory f) {
+    // for e.g. "int[].class"
+    return f.fromTypeTree(node);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitParameterizedType(
+      ParameterizedTypeTree node, AnnotatedTypeFactory f) {
+    return f.fromTypeTree(node);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitIntersectionType(
+      IntersectionTypeTree node, AnnotatedTypeFactory f) {
+    return f.fromTypeTree(node);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitMemberReference(
+      MemberReferenceTree node, AnnotatedTypeFactory f) {
+    return f.toAnnotatedType(TreeUtils.typeOf(node), false);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitLambdaExpression(
+      LambdaExpressionTree node, AnnotatedTypeFactory f) {
+    return f.toAnnotatedType(TreeUtils.typeOf(node), false);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitAssignment(AssignmentTree node, AnnotatedTypeFactory f) {
+
+    // Recurse on the type of the variable.
+    return visit(node.getVariable(), f);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitConditionalExpression(
+      ConditionalExpressionTree node, AnnotatedTypeFactory f) {
+    // The Java type of a conditional expression is generally the LUB of the boxed types
+    // of the true and false expressions, but with a few exceptions. See JLS 15.25.
+    // So, use the type of the ConditionalExpressionTree instead of
+    // InternalUtils#leastUpperBound
+    TypeMirror alub = TreeUtils.typeOf(node);
+
+    AnnotatedTypeMirror trueType = f.getAnnotatedType(node.getTrueExpression());
+    AnnotatedTypeMirror falseType = f.getAnnotatedType(node.getFalseExpression());
+
+    return AnnotatedTypes.leastUpperBound(f, trueType, falseType, alub);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitIdentifier(IdentifierTree node, AnnotatedTypeFactory f) {
+    if (node.getName().contentEquals("this") || node.getName().contentEquals("super")) {
+      AnnotatedDeclaredType res = f.getSelfType(node);
+      return res;
+    }
+
+    Element elt = TreeUtils.elementFromUse(node);
+    AnnotatedTypeMirror selfType = f.getImplicitReceiverType(node);
+    if (selfType != null) {
+      return AnnotatedTypes.asMemberOf(f.types, f, selfType, elt).asUse();
+    }
+
+    return f.getAnnotatedType(elt);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitMemberSelect(MemberSelectTree node, AnnotatedTypeFactory f) {
+    Element elt = TreeUtils.elementFromUse(node);
+
+    if (TreeUtils.isClassLiteral(node)) {
+      // the type of a class literal is the type of the "class" element.
+      return f.getAnnotatedType(elt);
+    }
+    switch (elt.getKind()) {
+      case METHOD:
+      case PACKAGE: // "java.lang" in new java.lang.Short("2")
+      case CLASS: // o instanceof MyClass.InnerClass
+      case ENUM:
+      case INTERFACE: // o instanceof MyClass.InnerInterface
+      case ANNOTATION_TYPE:
+        return f.fromElement(elt);
+      default:
+        // Fall-through.
+    }
+
+    if (node.getIdentifier().contentEquals("this")) {
+      // Node is "MyClass.this", where "MyClass" may be the innermost enclosing type or any
+      // outer type.
+      return f.getEnclosingType(TypesUtils.getTypeElement(TreeUtils.typeOf(node)), node);
+    } else {
+      // node must be a field access, so get the type of the expression, and then call asMemberOf.
+      AnnotatedTypeMirror t = f.getAnnotatedType(node.getExpression());
+      return AnnotatedTypes.asMemberOf(f.types, f, t, elt).asUse();
+    }
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitArrayAccess(ArrayAccessTree node, AnnotatedTypeFactory f) {
+
+    Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = f.visitorState.getAssignmentContext();
+    try {
+      // TODO: what other trees shouldn't maintain the context?
+      f.visitorState.setAssignmentContext(null);
+
+      AnnotatedTypeMirror type = f.getAnnotatedType(node.getExpression());
+      if (type.getKind() == TypeKind.ARRAY) {
+        return ((AnnotatedArrayType) type).getComponentType();
+      } else if (type.getKind() == TypeKind.WILDCARD
+          && ((AnnotatedWildcardType) type).isUninferredTypeArgument()) {
+        // Clean-up after Issue #979.
+        AnnotatedTypeMirror wcbound = ((AnnotatedWildcardType) type).getExtendsBound();
+        if (wcbound instanceof AnnotatedArrayType) {
+          return ((AnnotatedArrayType) wcbound).getComponentType();
+        }
+      }
+      throw new BugInCF("Unexpected type: " + type);
+    } finally {
+      f.visitorState.setAssignmentContext(preAssignmentContext);
+    }
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitNewArray(NewArrayTree node, AnnotatedTypeFactory f) {
+
+    // Don't use fromTypeTree here, because node.getType() is not an array type!
+    AnnotatedArrayType result = (AnnotatedArrayType) f.type(node);
+
+    if (node.getType() == null) { // e.g., byte[] b = {(byte)1, (byte)2};
+      return result;
+    }
+
+    annotateArrayAsArray(result, node, f);
+
+    return result;
+  }
+
+  private AnnotatedTypeMirror descendBy(AnnotatedTypeMirror type, int depth) {
+    AnnotatedTypeMirror result = type;
+    while (depth > 0) {
+      result = ((AnnotatedArrayType) result).getComponentType();
+      depth--;
+    }
+    return result;
+  }
+
+  private void annotateArrayAsArray(
+      AnnotatedArrayType result, NewArrayTree node, AnnotatedTypeFactory f) {
+    // Copy annotations from the type.
+    AnnotatedTypeMirror treeElem = f.fromTypeTree(node.getType());
+    boolean hasInit = node.getInitializers() != null;
+    AnnotatedTypeMirror typeElem = descendBy(result, hasInit ? 1 : node.getDimensions().size());
+    while (true) {
+      typeElem.addAnnotations(treeElem.getAnnotations());
+      if (!(treeElem instanceof AnnotatedArrayType)) {
+        break;
+      }
+      assert typeElem instanceof AnnotatedArrayType;
+      treeElem = ((AnnotatedArrayType) treeElem).getComponentType();
+      typeElem = ((AnnotatedArrayType) typeElem).getComponentType();
+    }
+    // Add all dimension annotations.
+    int idx = 0;
+    AnnotatedTypeMirror level = result;
+    while (level.getKind() == TypeKind.ARRAY) {
+      AnnotatedArrayType array = (AnnotatedArrayType) level;
+      List<? extends AnnotationMirror> annos = TreeUtils.annotationsFromArrayCreation(node, idx++);
+      array.addAnnotations(annos);
+      level = array.getComponentType();
+    }
+
+    // Add top-level annotations.
+    result.addAnnotations(TreeUtils.annotationsFromArrayCreation(node, -1));
+  }
+
+  /**
+   * Creates an AnnotatedDeclaredType for the NewClassTree and adds, for each hierarchy, one of:
+   *
+   * <ul>
+   *   <li>an explicit annotation on the new class expression ({@code new @HERE MyClass()}), or
+   *   <li>an explicit annotation on the declaration of the class ({@code @HERE class MyClass {}}),
+   *       or
+   *   <li>an explicit or default annotation on the declaration of the constructor ({@code @HERE
+   *       public MyClass() {}}).
+   * </ul>
+   *
+   * @param node NewClassTree
+   * @param f the type factory
+   * @return AnnotatedDeclaredType of {@code node}
+   */
+  @Override
+  public AnnotatedTypeMirror visitNewClass(NewClassTree node, AnnotatedTypeFactory f) {
+    // constructorFromUse return type has default annotations
+    // so use fromNewClass which does diamond inference and only
+    // contains explicit annotations.
+    AnnotatedDeclaredType type = f.fromNewClass(node);
+
+    // Add annotations that are on the constructor declaration.
+    AnnotatedExecutableType ex = f.constructorFromUse(node).executableType;
+    type.addMissingAnnotations(ex.getReturnType().getAnnotations());
+
+    return type;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitMethodInvocation(
+      MethodInvocationTree node, AnnotatedTypeFactory f) {
+    AnnotatedExecutableType ex = f.methodFromUse(node).executableType;
+    return ex.getReturnType().asUse();
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitParenthesized(ParenthesizedTree node, AnnotatedTypeFactory f) {
+
+    // Recurse on the expression inside the parens.
+    return visit(node.getExpression(), f);
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitWildcard(WildcardTree node, AnnotatedTypeFactory f) {
+
+    AnnotatedTypeMirror bound = visit(node.getBound(), f);
+
+    AnnotatedTypeMirror result = f.type(node);
+    assert result instanceof AnnotatedWildcardType;
+
+    // Instead of directly overwriting the bound, replace each annotation
+    // to ensure that the structure of the wildcard will match that created by
+    // BoundsInitializer/createType.
+    if (node.getKind() == Tree.Kind.SUPER_WILDCARD) {
+      f.replaceAnnotations(bound, ((AnnotatedWildcardType) result).getSuperBound());
+
+    } else if (node.getKind() == Tree.Kind.EXTENDS_WILDCARD) {
+      f.replaceAnnotations(bound, ((AnnotatedWildcardType) result).getExtendsBound());
+    }
+    return result;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java
new file mode 100644
index 0000000..ed0c580
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java
@@ -0,0 +1,171 @@
+package org.checkerframework.framework.type;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import java.util.Collections;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Converts a field or methods tree into an AnnotatedTypeMirror.
+ *
+ * @see org.checkerframework.framework.type.TypeFromTree
+ */
+class TypeFromMemberVisitor extends TypeFromTreeVisitor {
+
+  @Override
+  public AnnotatedTypeMirror visitVariable(VariableTree variableTree, AnnotatedTypeFactory f) {
+    Element elt = TreeUtils.elementFromDeclaration(variableTree);
+
+    // Create the ATM and add non-primary annotations
+    // (variableTree.getType() does not include the annotation before the type, so those
+    // are added to the type below).
+    AnnotatedTypeMirror result = TypeFromTree.fromTypeTree(f, variableTree.getType());
+
+    // Handle any annotations in variableTree.getModifiers().
+    List<AnnotationMirror> modifierAnnos;
+    List<? extends AnnotationTree> annoTrees = variableTree.getModifiers().getAnnotations();
+    if (annoTrees != null && !annoTrees.isEmpty()) {
+      modifierAnnos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees);
+    } else {
+      modifierAnnos = Collections.emptyList();
+    }
+
+    if (result.getKind() == TypeKind.DECLARED
+        &&
+        // Annotations on enum constants are not in the TypeMirror and always apply to the
+        // innermost type, so handle them in the else block.
+        elt.getKind() != ElementKind.ENUM_CONSTANT) {
+
+      // Decode the annotations from the type mirror because the annotations are already in
+      // the correct place for enclosing types.  The annotations in
+      // variableTree.getModifiers()
+      // might apply to the enclosing type or the type itself. For example, @Tainted
+      // Outer.Inner y and @Tainted
+      // Inner x.  @Tainted is stored in variableTree.getModifiers() of the variable tree
+      // corresponding to both x and y, but @Tainted applies to different types.
+      AnnotatedDeclaredType annotatedDeclaredType = (AnnotatedDeclaredType) result;
+      // The underlying type of result does not have all annotations, but the TypeMirror of
+      // variableTree.getType() does.
+      DeclaredType declaredType = (DeclaredType) TreeUtils.typeOf(variableTree.getType());
+      AnnotatedTypes.applyAnnotationsFromDeclaredType(annotatedDeclaredType, declaredType);
+
+      // Handle declaration annotations
+      for (AnnotationMirror anno : modifierAnnos) {
+        if (AnnotationUtils.isDeclarationAnnotation(anno)) {
+          // This does not treat Checker Framework compatqual annotations differently,
+          // because it's not clear whether the annotation should apply to the outermost
+          // enclosing type or the innermost.
+          result.addAnnotation(anno);
+        }
+        // If anno is not a declaration annotation, it should have been applied in the call
+        // to applyAnnotationsFromDeclaredType above.
+      }
+    } else {
+      // Add the primary annotation from the variableTree.getModifiers();
+      AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(result);
+      for (AnnotationMirror anno : modifierAnnos) {
+        // The code here is similar to
+        // org.checkerframework.framework.util.element.ElementAnnotationUtil.addDeclarationAnnotationsFromElement.
+        if (AnnotationUtils.isDeclarationAnnotation(anno)
+            // Always treat Checker Framework annotations as type annotations.
+            && !AnnotationUtils.annotationName(anno).startsWith("org.checkerframework")) {
+          // Declaration annotations apply to the outer type.
+          result.addAnnotation(anno);
+        } else {
+          // Type annotations apply to the innermost type.
+          innerType.addAnnotation(anno);
+        }
+      }
+    }
+
+    AnnotatedTypeMirror lambdaParamType = inferLambdaParamAnnotations(f, result, elt);
+    if (lambdaParamType != null) {
+      return lambdaParamType;
+    }
+    return result;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitMethod(MethodTree node, AnnotatedTypeFactory f) {
+    ExecutableElement elt = TreeUtils.elementFromDeclaration(node);
+
+    AnnotatedExecutableType result =
+        (AnnotatedExecutableType) f.toAnnotatedType(elt.asType(), false);
+    result.setElement(elt);
+    // Make sure the return type field gets initialized... otherwise
+    // some code throws NPE. This should be cleaned up.
+    result.getReturnType();
+
+    // TODO: Needed to visit parameter types, etc.
+    // It would be nicer if this didn't decode the information from the Element and
+    // instead also used the Tree. If this is implemented, then care needs to be taken to put
+    // any alias declaration annotations in the correct place for return types that are arrays.
+    // This would be similar to
+    // org.checkerframework.framework.util.element.ElementAnnotationUtil.addDeclarationAnnotationsFromElement.
+    ElementAnnotationApplier.apply(result, elt, f);
+    return result;
+  }
+
+  /**
+   * Returns the type of the lambda parameter, or null if paramElement is not a lambda parameter.
+   *
+   * @return the type of the lambda parameter, or null if paramElement is not a lambda parameter
+   */
+  private static AnnotatedTypeMirror inferLambdaParamAnnotations(
+      AnnotatedTypeFactory f, AnnotatedTypeMirror lambdaParam, Element paramElement) {
+    if (paramElement.getKind() != ElementKind.PARAMETER
+        || f.declarationFromElement(paramElement) == null
+        || f.getPath(f.declarationFromElement(paramElement)) == null
+        || f.getPath(f.declarationFromElement(paramElement)).getParentPath() == null) {
+
+      return null;
+    }
+    Tree declaredInTree =
+        f.getPath(f.declarationFromElement(paramElement)).getParentPath().getLeaf();
+    if (declaredInTree.getKind() == Kind.LAMBDA_EXPRESSION) {
+      LambdaExpressionTree lambdaDecl = (LambdaExpressionTree) declaredInTree;
+      int index = lambdaDecl.getParameters().indexOf(f.declarationFromElement(paramElement));
+      AnnotatedExecutableType functionType = f.getFunctionTypeFromTree(lambdaDecl);
+      AnnotatedTypeMirror funcTypeParam = functionType.getParameterTypes().get(index);
+      if (TreeUtils.isImplicitlyTypedLambda(declaredInTree)) {
+        // The Java types should be exactly the same, but because invocation type
+        // inference (#979) isn't implement, check first. Use the erased types because the
+        // type arguments are not substituted when the annotated type arguments are.
+        if (TypesUtils.isErasedSubtype(
+            funcTypeParam.underlyingType, lambdaParam.underlyingType, f.types)) {
+          return AnnotatedTypes.asSuper(f, funcTypeParam, lambdaParam);
+        }
+        lambdaParam.addMissingAnnotations(funcTypeParam.getAnnotations());
+        return lambdaParam;
+
+      } else {
+        // The lambda expression is explicitly typed, so the parameters have declared types:
+        // (String s) -> ...
+        // The declared type may or may not have explicit annotations.
+        // If it does not have an annotation for a hierarchy, then copy the annotation from
+        // the function type rather than use usual defaulting rules.
+        // Note lambdaParam is a super type of funcTypeParam, so only primary annotations
+        // can be copied.
+        lambdaParam.addMissingAnnotations(funcTypeParam.getAnnotations());
+        return lambdaParam;
+      }
+    }
+    return null;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTree.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTree.java
new file mode 100644
index 0000000..d6cf173
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTree.java
@@ -0,0 +1,136 @@
+package org.checkerframework.framework.type;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.Tree;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * A utility class to convert trees into corresponding AnnotatedTypeMirrors. This class should be
+ * used ONLY from AnnotatedTypeFactory.
+ *
+ * <p>For each method in TypeFromTree there is a corresponding TypeFromTreeVisitor that handles the
+ * input tree. The list of methods implemented by these visitors outline which trees each method
+ * will support. If a tree kind is not handled by the given visitor, then execution is halted and an
+ * RuntimeException is thrown which includes a list of supported tree types.
+ */
+class TypeFromTree {
+
+  private static final TypeFromTypeTreeVisitor typeTreeVisitor = new TypeFromTypeTreeVisitor();
+  private static final TypeFromMemberVisitor memberVisitor = new TypeFromMemberVisitor();
+  private static final TypeFromClassVisitor classVisitor = new TypeFromClassVisitor();
+  private static final TypeFromExpressionVisitor expressionVisitor =
+      new TypeFromExpressionVisitor();
+
+  /**
+   * Returns an AnnotatedTypeMirror representing the input expression tree.
+   *
+   * @param tree must be an ExpressionTree
+   * @return an AnnotatedTypeMirror representing the input expression tree
+   */
+  public static AnnotatedTypeMirror fromExpression(
+      final AnnotatedTypeFactory typeFactory, final ExpressionTree tree) {
+    abortIfTreeIsNull(typeFactory, tree);
+
+    final AnnotatedTypeMirror type;
+    try {
+      type = expressionVisitor.visit(tree, typeFactory);
+    } catch (Throwable t) {
+      throw new BugInCF(
+          t,
+          "Error in AnnotatedTypeMirror.fromExpression(%s, %s): %s",
+          typeFactory.getClass().getSimpleName(),
+          tree,
+          t.getMessage());
+    }
+    ifExecutableCheckElement(typeFactory, tree, type);
+
+    return type;
+  }
+
+  /**
+   * Returns an AnnotatedTypeMirror representing the input tree.
+   *
+   * @param tree must represent a class member
+   * @return an AnnotatedTypeMirror representing the input tree
+   */
+  public static AnnotatedTypeMirror fromMember(
+      final AnnotatedTypeFactory typeFactory, final Tree tree) {
+    abortIfTreeIsNull(typeFactory, tree);
+
+    final AnnotatedTypeMirror type = memberVisitor.visit(tree, typeFactory);
+    ifExecutableCheckElement(typeFactory, tree, type);
+    return type;
+  }
+
+  /**
+   * Returns an AnnotatedTypeMirror representing the input type tree.
+   *
+   * @param tree must be a type tree
+   * @return an AnnotatedTypeMirror representing the input type tree
+   */
+  public static AnnotatedTypeMirror fromTypeTree(
+      final AnnotatedTypeFactory typeFactory, final Tree tree) {
+    abortIfTreeIsNull(typeFactory, tree);
+
+    final AnnotatedTypeMirror type = typeTreeVisitor.visit(tree, typeFactory);
+    abortIfTypeIsExecutable(typeFactory, tree, type);
+    return type;
+  }
+
+  /**
+   * Returns an AnnotatedDeclaredType representing the input ClassTree.
+   *
+   * @return an AnnotatedDeclaredType representing the input ClassTree
+   */
+  public static AnnotatedDeclaredType fromClassTree(
+      final AnnotatedTypeFactory typeFactory, final ClassTree tree) {
+    abortIfTreeIsNull(typeFactory, tree);
+
+    final AnnotatedDeclaredType type =
+        (AnnotatedDeclaredType) classVisitor.visit(tree, typeFactory);
+    abortIfTypeIsExecutable(typeFactory, tree, type);
+    return type;
+  }
+
+  protected static void abortIfTreeIsNull(final AnnotatedTypeFactory typeFactory, final Tree tree) {
+    if (tree == null) {
+      throw new BugInCF("Encountered null tree" + summarize(typeFactory, tree));
+    }
+  }
+
+  protected static void ifExecutableCheckElement(
+      final AnnotatedTypeFactory typeFactory, final Tree tree, final AnnotatedTypeMirror type) {
+    if (type.getKind() == TypeKind.EXECUTABLE) {
+      if (((AnnotatedExecutableType) type).getElement() == null) {
+        throw new BugInCF("Executable has no element:%n%s", summarize(typeFactory, tree, type));
+      }
+    }
+  }
+
+  protected static void abortIfTypeIsExecutable(
+      final AnnotatedTypeFactory typeFactory, final Tree tree, final AnnotatedTypeMirror type) {
+    if (type.getKind() == TypeKind.EXECUTABLE) {
+      throw new BugInCF("Unexpected Executable typekind:%n%s", summarize(typeFactory, tree, type));
+    }
+  }
+
+  /**
+   * Return a string with the two arguments, for diagnostics.
+   *
+   * @param typeFactory a type factory
+   * @param tree a tree
+   * @return a string with the two arguments
+   */
+  protected static String summarize(final AnnotatedTypeFactory typeFactory, final Tree tree) {
+    return String.format("tree=%s%ntypeFactory=%s", tree, typeFactory.getClass().getSimpleName());
+  }
+
+  protected static String summarize(
+      final AnnotatedTypeFactory typeFactory, final Tree tree, final AnnotatedTypeMirror type) {
+    return "type=" + type + System.lineSeparator() + summarize(typeFactory, tree);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTreeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTreeVisitor.java
new file mode 100644
index 0000000..1083d06
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTreeVisitor.java
@@ -0,0 +1,32 @@
+package org.checkerframework.framework.type;
+
+import com.sun.source.tree.Tree;
+import com.sun.source.util.SimpleTreeVisitor;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Converts a Tree into an AnnotatedTypeMirror. This class is abstract and provides 2 important
+ * properties to subclasses:
+ *
+ * <ol>
+ *   <li>It implements SimpleTreeVisitor with the appropriate type parameters
+ *   <li>It provides a defaultAction that causes all visit methods to abort if the subclass does not
+ *       override them
+ * </ol>
+ *
+ * @see org.checkerframework.framework.type.TypeFromTree
+ */
+abstract class TypeFromTreeVisitor
+    extends SimpleTreeVisitor<AnnotatedTypeMirror, AnnotatedTypeFactory> {
+
+  TypeFromTreeVisitor() {}
+
+  @Override
+  public AnnotatedTypeMirror defaultAction(Tree node, AnnotatedTypeFactory f) {
+    if (node == null) {
+      throw new BugInCF("TypeFromTree.defaultAction: null tree");
+    }
+    throw new BugInCF(
+        "TypeFromTree.defaultAction: conversion undefined for tree type " + node.getKind());
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java
new file mode 100644
index 0000000..707feed
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java
@@ -0,0 +1,348 @@
+package org.checkerframework.framework.type;
+
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.IntersectionTypeTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.PrimitiveTypeTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.TypeParameterTree;
+import com.sun.source.tree.UnionTypeTree;
+import com.sun.source.tree.WildcardTree;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.TypeVariableSymbol;
+import com.sun.tools.javac.code.Type.TypeVar;
+import com.sun.tools.javac.code.Type.WildcardType;
+import com.sun.tools.javac.tree.JCTree.JCWildcard;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+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.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeVariable;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * Converts type trees into AnnotatedTypeMirrors.
+ *
+ * @see org.checkerframework.framework.type.TypeFromTree
+ */
+class TypeFromTypeTreeVisitor extends TypeFromTreeVisitor {
+
+  private final Map<Tree, AnnotatedTypeMirror> visitedBounds = new HashMap<>();
+
+  @Override
+  public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree node, AnnotatedTypeFactory f) {
+    AnnotatedTypeMirror type = visit(node.getUnderlyingType(), f);
+    if (type == null) { // e.g., for receiver type
+      type = f.toAnnotatedType(f.types.getNoType(TypeKind.NONE), false);
+    }
+    assert AnnotatedTypeFactory.validAnnotatedType(type);
+    List<? extends AnnotationMirror> annos = TreeUtils.annotationsFromTree(node);
+
+    if (type.getKind() == TypeKind.WILDCARD) {
+      // Work-around for https://github.com/eisop/checker-framework/issues/17
+      // For an annotated wildcard tree node, the type attached to the
+      // node is a WildcardType with a correct bound (set to the type
+      // variable which the wildcard instantiates). The underlying type is
+      // also a WildcardType but with a bound of null. Here we update the
+      // bound of the underlying WildcardType to be consistent.
+      WildcardType wildcardAttachedToNode = (WildcardType) TreeUtils.typeOf(node);
+      WildcardType underlyingWildcard = (WildcardType) type.getUnderlyingType();
+      underlyingWildcard.withTypeVar(wildcardAttachedToNode.bound);
+      // End of work-around
+
+      final AnnotatedWildcardType wctype = ((AnnotatedWildcardType) type);
+      final ExpressionTree underlyingTree = node.getUnderlyingType();
+
+      if (underlyingTree.getKind() == Kind.UNBOUNDED_WILDCARD) {
+        // primary annotations on unbounded wildcard types apply to both bounds
+        wctype.getExtendsBound().addAnnotations(annos);
+        wctype.getSuperBound().addAnnotations(annos);
+      } else if (underlyingTree.getKind() == Kind.EXTENDS_WILDCARD) {
+        wctype.getSuperBound().addAnnotations(annos);
+      } else if (underlyingTree.getKind() == Kind.SUPER_WILDCARD) {
+        wctype.getExtendsBound().addAnnotations(annos);
+      } else {
+        throw new BugInCF(
+            "Unexpected kind for type.  node="
+                + node
+                + " type="
+                + type
+                + " kind="
+                + underlyingTree.getKind());
+      }
+    } else {
+      type.addAnnotations(annos);
+    }
+
+    return type;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitArrayType(ArrayTypeTree node, AnnotatedTypeFactory f) {
+    AnnotatedTypeMirror component = visit(node.getType(), f);
+
+    AnnotatedTypeMirror result = f.type(node);
+    assert result instanceof AnnotatedArrayType;
+    ((AnnotatedArrayType) result).setComponentType(component);
+    return result;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitParameterizedType(
+      ParameterizedTypeTree node, AnnotatedTypeFactory f) {
+
+    ClassSymbol baseType = (ClassSymbol) TreeUtils.elementFromTree(node.getType());
+    updateWildcardBounds(node.getTypeArguments(), baseType.getTypeParameters());
+
+    List<AnnotatedTypeMirror> args =
+        CollectionsPlume.mapList((Tree t) -> visit(t, f), node.getTypeArguments());
+
+    AnnotatedTypeMirror result = f.type(node); // use creator?
+    AnnotatedTypeMirror atype = visit(node.getType(), f);
+    result.addAnnotations(atype.getAnnotations());
+    // new ArrayList<>() type is AnnotatedExecutableType for some reason
+
+    // Don't initialize the type arguments if they are empty. The type arguments might be a
+    // diamond which should be inferred.
+    if (result instanceof AnnotatedDeclaredType && !args.isEmpty()) {
+      assert result instanceof AnnotatedDeclaredType : node + " --> " + result;
+      ((AnnotatedDeclaredType) result).setTypeArguments(args);
+    }
+    return result;
+  }
+
+  /**
+   * Work around a bug in javac 9 where sometimes the bound field is set to the transitive
+   * supertype's type parameter instead of the type parameter which the wildcard directly
+   * instantiates. See https://github.com/eisop/checker-framework/issues/18
+   *
+   * <p>Sets each wildcard type argument's bound from typeArgs to the corresponding type parameter
+   * from typeParams.
+   *
+   * <p>If typeArgs.size() == 0 the method does nothing and returns. Otherwise, typeArgs.size() has
+   * to be equal to typeParams.size().
+   *
+   * <p>For each wildcard type argument and corresponding type parameter, sets the
+   * WildcardType.bound field to the corresponding type parameter, if and only if the owners of the
+   * existing bound and the type parameter are different.
+   *
+   * <p>In scenarios where the bound's owner is the same, we don't want to replace a
+   * capture-converted bound in the wildcard type with a non-capture-converted bound given by the
+   * type parameter declaration.
+   *
+   * @param typeArgs the type of the arguments at (e.g., at the call side)
+   * @param typeParams the type of the formal parameters (e.g., at the method declaration)
+   */
+  @SuppressWarnings("interning:not.interned") // workaround for javac bug
+  private void updateWildcardBounds(
+      List<? extends Tree> typeArgs, List<TypeVariableSymbol> typeParams) {
+    if (typeArgs.isEmpty()) {
+      // Nothing to do for empty type arguments.
+      return;
+    }
+    assert typeArgs.size() == typeParams.size();
+
+    Iterator<? extends Tree> typeArgsItr = typeArgs.iterator();
+    Iterator<TypeVariableSymbol> typeParamsItr = typeParams.iterator();
+    while (typeArgsItr.hasNext()) {
+      Tree typeArg = typeArgsItr.next();
+      TypeVariableSymbol typeParam = typeParamsItr.next();
+      if (typeArg instanceof WildcardTree) {
+        TypeVar typeVar = (TypeVar) typeParam.asType();
+        WildcardType wcType = (WildcardType) ((JCWildcard) typeArg).type;
+        if (wcType.bound != null
+            && wcType.bound.tsym != null
+            && typeVar.tsym != null
+            && wcType.bound.tsym.owner != typeVar.tsym.owner) {
+          wcType.withTypeVar(typeVar);
+        }
+      }
+    }
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree node, AnnotatedTypeFactory f) {
+    return f.type(node);
+  }
+
+  @Override
+  public AnnotatedTypeVariable visitTypeParameter(
+      TypeParameterTree node, @FindDistinct AnnotatedTypeFactory f) {
+
+    List<AnnotatedTypeMirror> bounds = new ArrayList<>(node.getBounds().size());
+    for (Tree t : node.getBounds()) {
+      AnnotatedTypeMirror bound;
+      if (visitedBounds.containsKey(t) && f == visitedBounds.get(t).atypeFactory) {
+        bound = visitedBounds.get(t);
+      } else {
+        visitedBounds.put(t, f.type(t));
+        bound = visit(t, f);
+        visitedBounds.remove(t);
+      }
+      bounds.add(bound);
+    }
+
+    AnnotatedTypeVariable result = (AnnotatedTypeVariable) f.type(node);
+    List<? extends AnnotationMirror> annotations = TreeUtils.annotationsFromTree(node);
+    result.getLowerBound().addAnnotations(annotations);
+
+    switch (bounds.size()) {
+      case 0:
+        break;
+      case 1:
+        result.setUpperBound(bounds.get(0));
+        break;
+      default:
+        AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) result.getUpperBound();
+        intersection.setBounds(bounds);
+        intersection.copyIntersectionBoundAnnotations();
+    }
+
+    return result;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitWildcard(WildcardTree node, AnnotatedTypeFactory f) {
+
+    AnnotatedTypeMirror bound = visit(node.getBound(), f);
+
+    AnnotatedTypeMirror result = f.type(node);
+    assert result instanceof AnnotatedWildcardType;
+
+    // for wildcards unlike type variables there are bounds that differ in type from
+    // result.  These occur for RAW types.  In this case, use the newly created bound
+    // rather than merging into result
+    if (node.getKind() == Tree.Kind.SUPER_WILDCARD) {
+      ((AnnotatedWildcardType) result).setSuperBound(bound);
+
+    } else if (node.getKind() == Tree.Kind.EXTENDS_WILDCARD) {
+      ((AnnotatedWildcardType) result).setExtendsBound(bound);
+    }
+    return result;
+  }
+
+  /**
+   * If a tree is can be found for the declaration of the type variable {@code type}, then a {@link
+   * AnnotatedTypeVariable} is returned with explicit annotations from the type variables declared
+   * bounds. If a tree cannot be found, then {@code type}, converted to a use, is returned.
+   *
+   * @param type type variable used to find declaration tree
+   * @param f annotated type factory
+   * @return the AnnotatedTypeVariable from the declaration of {@code type} or {@code type} if no
+   *     tree is found.
+   */
+  private AnnotatedTypeVariable getTypeVariableFromDeclaration(
+      AnnotatedTypeVariable type, AnnotatedTypeFactory f) {
+    TypeVariable typeVar = type.getUnderlyingType();
+    TypeParameterElement tpe = (TypeParameterElement) typeVar.asElement();
+    Element elt = tpe.getGenericElement();
+    if (elt instanceof TypeElement) {
+      TypeElement typeElt = (TypeElement) elt;
+      int idx = typeElt.getTypeParameters().indexOf(tpe);
+      ClassTree cls = (ClassTree) f.declarationFromElement(typeElt);
+      if (cls == null || cls.getTypeParameters().isEmpty()) {
+        // The type parameters in the source tree were already erased. The element already
+        // contains all necessary information and we can return that.
+        return type.asUse();
+      }
+
+      // `forTypeVariable` is called for Identifier, MemberSelect and UnionType trees,
+      // none of which are declarations.  But `cls.getTypeParameters()` returns a list
+      // of type parameter declarations (`TypeParameterTree`), so this  call
+      // will return a declaration ATV.  So change it to a use.
+      return visitTypeParameter(cls.getTypeParameters().get(idx), f).asUse();
+    } else if (elt instanceof ExecutableElement) {
+      ExecutableElement exElt = (ExecutableElement) elt;
+      int idx = exElt.getTypeParameters().indexOf(tpe);
+      MethodTree meth = (MethodTree) f.declarationFromElement(exElt);
+      if (meth == null) {
+        // throw new BugInCF("TypeFromTree.forTypeVariable: did not find source for: "
+        //                   + elt);
+        return type.asUse();
+      }
+      // This works the same as the case above.  Even though `meth` itself is not a
+      // type declaration tree, the elements of `meth.getTypeParameters()` still are.
+      AnnotatedTypeVariable result =
+          visitTypeParameter(meth.getTypeParameters().get(idx), f).shallowCopy();
+      result.setDeclaration(false);
+      return result;
+    } else if (TypesUtils.isCaptured(typeVar)) {
+      // Captured types can have a generic element (owner) that is
+      // not an element at all, namely Symtab.noSymbol.
+      return type.asUse();
+    } else {
+      throw new BugInCF("TypeFromTree.forTypeVariable: not a supported element: " + elt);
+    }
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitIdentifier(IdentifierTree node, AnnotatedTypeFactory f) {
+
+    AnnotatedTypeMirror type = f.type(node);
+
+    if (type.getKind() == TypeKind.TYPEVAR) {
+      return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f);
+    }
+
+    return type;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitMemberSelect(MemberSelectTree node, AnnotatedTypeFactory f) {
+
+    AnnotatedTypeMirror type = f.type(node);
+
+    if (type.getKind() == TypeKind.TYPEVAR) {
+      return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f);
+    }
+
+    return type;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitUnionType(UnionTypeTree node, AnnotatedTypeFactory f) {
+    AnnotatedTypeMirror type = f.type(node);
+
+    if (type.getKind() == TypeKind.TYPEVAR) {
+      return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f);
+    }
+
+    return type;
+  }
+
+  @Override
+  public AnnotatedTypeMirror visitIntersectionType(
+      IntersectionTypeTree node, AnnotatedTypeFactory f) {
+    // This method is only called for IntersectionTypes in casts.  There is no IntersectionTypeTree
+    // for a type variable bound that is an intersection.  See #visitTypeParameter.
+    AnnotatedIntersectionType type = (AnnotatedIntersectionType) f.type(node);
+    List<AnnotatedTypeMirror> bounds =
+        CollectionsPlume.mapList((Tree boundTree) -> visit(boundTree, f), node.getBounds());
+    type.setBounds(bounds);
+    type.copyIntersectionBoundAnnotations();
+    return type;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java
new file mode 100644
index 0000000..72c8f82
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java
@@ -0,0 +1,61 @@
+package org.checkerframework.framework.type;
+
+import org.checkerframework.framework.util.AnnotatedTypes;
+
+/** Compares AnnotatedTypeMirrors for subtype relationships. See also {@link QualifierHierarchy}. */
+public interface TypeHierarchy {
+
+  /**
+   * Returns true if {@code subtype} is a subtype of or convertible to {@code supertype} for all
+   * hierarchies present. If the underlying Java type of {@code subtype} is not a subtype of or
+   * convertible to the underlying Java type of {@code supertype}, then the behavior of this method
+   * is undefined.
+   *
+   * <p>Ideally, types that require conversions would be converted before isSubtype is called, but
+   * instead, isSubtype performs some of these conversions.
+   *
+   * <p>JLS 5.1 specifies 13 categories of conversions.
+   *
+   * <p>4 categories are converted in isSubtype:
+   *
+   * <ul>
+   *   <li>Boxing conversions: isSubtype calls {@link AnnotatedTypes#asSuper( AnnotatedTypeFactory,
+   *       AnnotatedTypeMirror, AnnotatedTypeMirror)} which calls {@link
+   *       AnnotatedTypeFactory#getBoxedType}
+   *   <li>Unboxing conversions: isSubtype calls {@link AnnotatedTypes#asSuper(
+   *       AnnotatedTypeFactory, AnnotatedTypeMirror, AnnotatedTypeMirror)} which calls {@link
+   *       AnnotatedTypeFactory#getUnboxedType}
+   *   <li>Capture conversions: Wildcards are treated as though they were converted to type
+   *       variables
+   *   <li>String conversions: Any type to String. isSubtype calls {@link AnnotatedTypes#asSuper}
+   *       which calls {@link AnnotatedTypeFactory#getStringType(AnnotatedTypeMirror)}
+   * </ul>
+   *
+   * 1 happens elsewhere:
+   *
+   * <ul>
+   *   <li>Unchecked conversions: Generic type to raw type. Raw types are instantiated with bounds
+   *       in AnnotatedTypeFactory#fromTypeTree before is subtype is called
+   * </ul>
+   *
+   * 7 are not explicitly converted and are treated as though the types are actually subtypes.
+   *
+   * <ul>
+   *   <li>Identity conversions: type to same type
+   *   <li>Widening primitive conversions: primitive to primitive (no loss of information, byte to
+   *       short for example)
+   *   <li>Narrowing primitive conversions: primitive to primitive (possibly loss of information,
+   *       short to byte for example)
+   *   <li>Widening and Narrowing Primitive Conversion: byte to char
+   *   <li>Widening reference conversions: Upcast
+   *   <li>Narrowing reference conversions: Downcast
+   *   <li>Value set conversions: floating-point value from one value set to another without
+   *       changing its type.
+   * </ul>
+   *
+   * @param subtype possible subtype
+   * @param supertype possible supertype
+   * @return true if {@code subtype} is a subtype of {@code supertype} for all hierarchies present.
+   */
+  boolean isSubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype);
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java
new file mode 100644
index 0000000..2cbc850
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java
@@ -0,0 +1,150 @@
+package org.checkerframework.framework.type;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** TypeVariableSusbtitutor replaces type variables from a declaration with arguments to its use. */
+public class TypeVariableSubstitutor {
+
+  /**
+   * Given a mapping between type variable's to typeArgument, replace each instance of type variable
+   * with a copy of type argument.
+   *
+   * @see #substituteTypeVariable(AnnotatedTypeMirror,
+   *     org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable)
+   * @return a copy of typeMirror with its type variables substituted
+   */
+  public AnnotatedTypeMirror substitute(
+      final Map<TypeVariable, AnnotatedTypeMirror> typeParamToArg,
+      final AnnotatedTypeMirror typeMirror) {
+
+    return new Visitor(typeParamToArg).visit(typeMirror);
+  }
+
+  /**
+   * Given the types of a type parameter declaration, the argument to that type parameter
+   * declaration, and a given use of that declaration, return a substitute for the use with the
+   * correct annotations.
+   *
+   * <p>To determine what primary annotations are correct for the substitute the following rules are
+   * used: If the type variable use has a primary annotation then apply that primary annotation to
+   * the substitute. Otherwise, use the annotations of the argument.
+   *
+   * @param argument the argument to declaration (this will be a value in typeParamToArg)
+   * @param use the use that is being replaced
+   * @return a deep copy of argument with the appropriate annotations applied
+   */
+  protected AnnotatedTypeMirror substituteTypeVariable(
+      final AnnotatedTypeMirror argument, final AnnotatedTypeVariable use) {
+    final AnnotatedTypeMirror substitute = argument.deepCopy(true);
+    substitute.addAnnotations(argument.getAnnotationsField());
+
+    if (!use.getAnnotationsField().isEmpty()) {
+      substitute.replaceAnnotations(use.getAnnotations());
+    }
+
+    return substitute;
+  }
+
+  /**
+   * Visitor that makes the substitution. This is an inner class so that its methods cannot be
+   * called by clients of {@link TypeVariableSubstitutor}.
+   */
+  protected class Visitor extends AnnotatedTypeCopier {
+
+    /**
+     * A mapping from {@link TypeParameterElement} to the {@link AnnotatedTypeMirror} that should
+     * replace its uses.
+     */
+    private final Map<TypeParameterElement, AnnotatedTypeMirror> elementToArgMap;
+
+    /**
+     * A list of type variables that should be replaced by the type mirror at the same index in
+     * {@code typeMirrors}
+     */
+    private final List<TypeVariable> typeVars;
+
+    /**
+     * A list of TypeMirrors that should replace the type variable at the same index in {@code
+     * typeVars}
+     */
+    private final List<TypeMirror> typeMirrors;
+
+    /**
+     * Creates the Visitor.
+     *
+     * @param typeParamToArg mapping from TypeVariable to the AnnotatedTypeMirror that will replace
+     *     it
+     */
+    public Visitor(final Map<TypeVariable, AnnotatedTypeMirror> typeParamToArg) {
+      int size = typeParamToArg.size();
+      elementToArgMap = new HashMap<>(size);
+      typeVars = new ArrayList<>(size);
+      typeMirrors = new ArrayList<>(size);
+
+      for (Map.Entry<TypeVariable, AnnotatedTypeMirror> paramToArg : typeParamToArg.entrySet()) {
+        elementToArgMap.put(
+            (TypeParameterElement) paramToArg.getKey().asElement(), paramToArg.getValue());
+        typeVars.add(paramToArg.getKey());
+        typeMirrors.add(paramToArg.getValue().getUnderlyingType());
+      }
+    }
+
+    @Override
+    protected <T extends AnnotatedTypeMirror> T makeCopy(T original) {
+      if (original.getKind() == TypeKind.TYPEVAR) {
+        return super.makeCopy(original);
+      }
+      TypeMirror s =
+          TypesUtils.substitute(
+              original.getUnderlyingType(),
+              typeVars,
+              typeMirrors,
+              original.atypeFactory.processingEnv);
+
+      @SuppressWarnings("unchecked")
+      T copy =
+          (T) AnnotatedTypeMirror.createType(s, original.atypeFactory, original.isDeclaration());
+      maybeCopyPrimaryAnnotations(original, copy);
+
+      return copy;
+    }
+
+    @Override
+    public AnnotatedTypeMirror visitTypeVariable(
+        AnnotatedTypeVariable original,
+        IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
+
+      if (visitingExecutableTypeParam) {
+        // AnnotatedExecutableType differs from AnnotatedDeclaredType in that its list of
+        // type parameters cannot be adapted in place since the
+        // AnnotatedExecutable.typeVarTypes field is of type AnnotatedTypeVariable and not
+        // AnnotatedTypeMirror.  When substituting, all component types that contain a use
+        // of the executable's type parameters will be substituted.  The executable's type
+        // parameters will have their bounds substituted but the top-level
+        // AnnotatedTypeVariable's will remain
+        visitingExecutableTypeParam = false;
+        return super.visitTypeVariable(original, originalToCopy);
+
+      } else {
+        final Element typeVarElem = original.getUnderlyingType().asElement();
+        if (elementToArgMap.containsKey(typeVarElem)) {
+          final AnnotatedTypeMirror argument = elementToArgMap.get(typeVarElem);
+          return substituteTypeVariable(argument, original);
+        }
+      }
+
+      return super.visitTypeVariable(original, originalToCopy);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypesIntoElements.java b/framework/src/main/java/org/checkerframework/framework/type/TypesIntoElements.java
new file mode 100644
index 0000000..52df91b
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/TypesIntoElements.java
@@ -0,0 +1,496 @@
+package org.checkerframework.framework.type;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeParameterTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Attribute.TypeCompound;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.TypeAnnotationPosition;
+import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry;
+import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.ListBuffer;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Types;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypeAnnotationUtils;
+
+/**
+ * A helper class that puts the annotations from an AnnotatedTypeMirrors back into the corresponding
+ * Elements, so that they get stored in the bytecode by the compiler.
+ *
+ * <p>This has kind-of the symmetric function to {@code TypeFromElement}.
+ *
+ * <p>This class deals with javac internals and liberally imports such classes.
+ */
+public class TypesIntoElements {
+
+  /**
+   * The entry point.
+   *
+   * @param processingEnv the environment
+   * @param atypeFactory the type factory
+   * @param tree the ClassTree to process
+   */
+  public static void store(
+      ProcessingEnvironment processingEnv, AnnotatedTypeFactory atypeFactory, ClassTree tree) {
+    Symbol.ClassSymbol csym = (Symbol.ClassSymbol) TreeUtils.elementFromDeclaration(tree);
+    Types types = processingEnv.getTypeUtils();
+
+    storeTypeParameters(processingEnv, types, atypeFactory, tree.getTypeParameters(), csym);
+
+    /* TODO: storing extends/implements types results in
+     * a strange error e.g. from the Nullness Checker.
+     * I think somewhere we take the annotations on extends/implements as
+     * the receiver annotation on a constructor, breaking logic there.
+     * I assume that the problem is the default that we use for these locations.
+     * Once we've decided the defaulting, enable this.
+     * See example of code that fails when this is enabled in
+     * checker/jtreg/nullness/annotationsOnExtends. Also, see
+     * https://github.com/typetools/checker-framework/pull/876 for
+     * a better implementation (though it also causes the error).
+    storeClassExtends(processingEnv, types, atypeFactory, tree.getExtendsClause(), csym, -1);
+    {
+        int implidx = 0;
+        for (Tree imp : tree.getImplementsClause()) {
+            storeClassExtends(processingEnv, types, atypeFactory, imp, csym, implidx);
+            ++implidx;
+        }
+    }
+    */
+
+    for (Tree mem : tree.getMembers()) {
+      if (mem.getKind() == Tree.Kind.METHOD) {
+        storeMethod(processingEnv, types, atypeFactory, (MethodTree) mem);
+      } else if (mem.getKind() == Tree.Kind.VARIABLE) {
+        storeVariable(processingEnv, types, atypeFactory, (VariableTree) mem);
+      } else {
+        // System.out.println("Unhandled member tree: " + mem);
+      }
+    }
+  }
+
+  private static void storeMethod(
+      ProcessingEnvironment processingEnv,
+      Types types,
+      AnnotatedTypeFactory atypeFactory,
+      MethodTree meth) {
+    AnnotatedExecutableType mtype = atypeFactory.getAnnotatedType(meth);
+    MethodSymbol sym = (MethodSymbol) TreeUtils.elementFromDeclaration(meth);
+    TypeAnnotationPosition tapos;
+    List<Attribute.TypeCompound> tcs = List.nil();
+
+    storeTypeParameters(processingEnv, types, atypeFactory, meth.getTypeParameters(), sym);
+
+    {
+      // return type
+      JCTree ret = ((JCTree.JCMethodDecl) meth).getReturnType();
+      if (ret != null) {
+        tapos = TypeAnnotationUtils.methodReturnTAPosition(ret.pos);
+        tcs = tcs.appendList(generateTypeCompounds(processingEnv, mtype.getReturnType(), tapos));
+      }
+    }
+    {
+      // receiver
+      JCTree receiverTree = ((JCTree.JCMethodDecl) meth).getReceiverParameter();
+      if (receiverTree != null) {
+        tapos = TypeAnnotationUtils.methodReceiverTAPosition(receiverTree.pos);
+        tcs = tcs.appendList(generateTypeCompounds(processingEnv, mtype.getReceiverType(), tapos));
+      }
+    }
+    {
+      // parameters
+      int pidx = 0;
+      java.util.List<AnnotatedTypeMirror> ptypes = mtype.getParameterTypes();
+      for (JCTree param : ((JCTree.JCMethodDecl) meth).getParameters()) {
+        tapos = TypeAnnotationUtils.methodParameterTAPosition(pidx, param.pos);
+        tcs = tcs.appendList(generateTypeCompounds(processingEnv, ptypes.get(pidx), tapos));
+        ++pidx;
+      }
+    }
+    {
+      // throws clauses
+      int tidx = 0;
+      java.util.List<AnnotatedTypeMirror> ttypes = mtype.getThrownTypes();
+      for (JCTree thr : ((JCTree.JCMethodDecl) meth).getThrows()) {
+        tapos = TypeAnnotationUtils.methodThrowsTAPosition(tidx, thr.pos);
+        tcs = tcs.appendList(generateTypeCompounds(processingEnv, ttypes.get(tidx), tapos));
+        ++tidx;
+      }
+    }
+
+    addUniqueTypeCompounds(types, sym, tcs);
+  }
+
+  private static void storeVariable(
+      ProcessingEnvironment processingEnv,
+      Types types,
+      AnnotatedTypeFactory atypeFactory,
+      VariableTree var) {
+    VarSymbol sym = (VarSymbol) TreeUtils.elementFromDeclaration(var);
+    AnnotatedTypeMirror type;
+    if (atypeFactory instanceof GenericAnnotatedTypeFactory) {
+      // TODO: this is rather ugly: we do not want refinement from the
+      // initializer of the field. We need a general way to get
+      // the "defaulted" type of a variable.
+      type = ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory).getAnnotatedTypeLhs(var);
+    } else {
+      type = atypeFactory.getAnnotatedType(var);
+    }
+
+    TypeAnnotationPosition tapos = TypeAnnotationUtils.fieldTAPosition(((JCTree) var).pos);
+
+    List<Attribute.TypeCompound> tcs;
+    tcs = generateTypeCompounds(processingEnv, type, tapos);
+    addUniqueTypeCompounds(types, sym, tcs);
+  }
+
+  @SuppressWarnings("unused") // TODO: see usage in comments above
+  private static void storeClassExtends(
+      ProcessingEnvironment processingEnv,
+      Types types,
+      AnnotatedTypeFactory atypeFactory,
+      Tree ext,
+      Symbol.ClassSymbol csym,
+      int implidx) {
+
+    AnnotatedTypeMirror type;
+    int pos;
+    if (ext == null) {
+      // The implicit superclass is always java.lang.Object.
+      // TODO: is this a good way to get the type?
+      type = atypeFactory.fromElement(csym.getSuperclass().asElement());
+      pos = -1;
+    } else {
+      type = atypeFactory.getAnnotatedTypeFromTypeTree(ext);
+      pos = ((JCTree) ext).pos;
+    }
+
+    TypeAnnotationPosition tapos = TypeAnnotationUtils.classExtendsTAPosition(implidx, pos);
+
+    List<Attribute.TypeCompound> tcs;
+    tcs = generateTypeCompounds(processingEnv, type, tapos);
+    addUniqueTypeCompounds(types, csym, tcs);
+  }
+
+  private static void storeTypeParameters(
+      ProcessingEnvironment processingEnv,
+      Types types,
+      AnnotatedTypeFactory atypeFactory,
+      java.util.List<? extends TypeParameterTree> tps,
+      Symbol sym) {
+    boolean isClassOrInterface = sym.getKind().isClass() || sym.getKind().isInterface();
+    List<Attribute.TypeCompound> tcs = List.nil();
+
+    int tpidx = 0;
+    for (TypeParameterTree tp : tps) {
+      AnnotatedTypeVariable typeVar =
+          (AnnotatedTypeVariable) atypeFactory.getAnnotatedTypeFromTypeTree(tp);
+      // System.out.println("The Type for type parameter " + tp + " is " + type);
+
+      TypeAnnotationPosition tapos;
+      // Note: we use the type parameter pos also for the bounds;
+      // the bounds may not be explicit and we couldn't look up separate pos.
+      if (isClassOrInterface) {
+        tapos = TypeAnnotationUtils.typeParameterTAPosition(tpidx, ((JCTree) tp).pos);
+      } else {
+        tapos = TypeAnnotationUtils.methodTypeParameterTAPosition(tpidx, ((JCTree) tp).pos);
+      }
+
+      { // This block is essentially direct annotations, perhaps we should refactor that
+        // method out
+        List<Attribute.TypeCompound> res = List.nil();
+        for (AnnotationMirror am : typeVar.getLowerBound().getAnnotations()) {
+          Attribute.TypeCompound tc =
+              TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror(am, tapos, processingEnv);
+          res = res.prepend(tc);
+        }
+        tcs = tcs.appendList(res);
+      }
+
+      AnnotatedTypeMirror tpbound = typeVar.getUpperBound();
+      java.util.List<? extends AnnotatedTypeMirror> bounds;
+      if (tpbound.getKind() == TypeKind.INTERSECTION) {
+        bounds = ((AnnotatedIntersectionType) tpbound).getBounds();
+      } else {
+        bounds = List.of(tpbound);
+      }
+
+      int bndidx = 0;
+      for (AnnotatedTypeMirror bound : bounds) {
+        if (bndidx == 0 && ((Type) bound.getUnderlyingType()).isInterface()) {
+          // If the first bound is an interface, there is an implicit java.lang.Object
+          ++bndidx;
+        }
+
+        if (isClassOrInterface) {
+          tapos =
+              TypeAnnotationUtils.typeParameterBoundTAPosition(tpidx, bndidx, ((JCTree) tp).pos);
+        } else {
+          tapos =
+              TypeAnnotationUtils.methodTypeParameterBoundTAPosition(
+                  tpidx, bndidx, ((JCTree) tp).pos);
+        }
+
+        tcs = tcs.appendList(generateTypeCompounds(processingEnv, bound, tapos));
+        ++bndidx;
+      }
+      ++tpidx;
+    }
+
+    // System.out.println("Adding " + tcs + " to " + sym);
+    addUniqueTypeCompounds(types, sym, tcs);
+  }
+
+  private static void addUniqueTypeCompounds(Types types, Symbol sym, List<TypeCompound> tcs) {
+    List<TypeCompound> raw = sym.getRawTypeAttributes();
+    List<Attribute.TypeCompound> res = List.nil();
+
+    for (Attribute.TypeCompound tc : tcs) {
+      if (!TypeAnnotationUtils.isTypeCompoundContained(raw, tc, types)) {
+        res = res.append(tc);
+      }
+    }
+    // That method only uses reference equality. isTypeCompoundContained does a deep comparison.
+    sym.appendUniqueTypeAttributes(res);
+  }
+
+  // Do not return null.  Return List.nil() if there are no TypeCompounds to return.
+  private static List<Attribute.TypeCompound> generateTypeCompounds(
+      ProcessingEnvironment processingEnv, AnnotatedTypeMirror type, TypeAnnotationPosition tapos) {
+    return new TCConvert(processingEnv).scan(type, tapos);
+  }
+
+  /**
+   * Convert an AnnotatedTypeMirror and a TypeAnnotationPosition into the corresponding
+   * TypeCompounds.
+   */
+  private static class TCConvert
+      extends AnnotatedTypeScanner<List<Attribute.TypeCompound>, TypeAnnotationPosition> {
+
+    /** ProcessingEnvironment. */
+    private final ProcessingEnvironment processingEnv;
+
+    /**
+     * Creates a {@link TCConvert}.
+     *
+     * @param processingEnv ProcessEnvironment
+     */
+    TCConvert(ProcessingEnvironment processingEnv) {
+      super(List.nil());
+      this.processingEnv = processingEnv;
+    }
+
+    @Override
+    public List<TypeCompound> scan(AnnotatedTypeMirror type, TypeAnnotationPosition pos) {
+      if (pos == null) {
+        throw new BugInCF("TypesIntoElements: invalid usage, null pos with type: " + type);
+      }
+      List<TypeCompound> res = super.scan(type, pos);
+      return res;
+    }
+
+    @Override
+    public List<TypeCompound> reduce(List<TypeCompound> r1, List<TypeCompound> r2) {
+      if (r1 == null) {
+        return r2;
+      }
+      if (r2 == null) {
+        return r1;
+      }
+      return r1.appendList(r2);
+    }
+
+    private List<TypeCompound> directAnnotations(
+        AnnotatedTypeMirror type, TypeAnnotationPosition tapos) {
+      List<Attribute.TypeCompound> res = List.nil();
+
+      for (AnnotationMirror am : type.getAnnotations()) {
+        // TODO: I BELIEVE THIS ISN'T TRUE BECAUSE PARAMETERS MAY HAVE ANNOTATIONS THAT CAME
+        // FROM THE ELEMENT OF THE CLASS WHICH PREVIOUSLY WAS WRITTEN OUT BY
+        // TYPESINTOELEMENT.
+        //                if (am instanceof Attribute.TypeCompound) {
+        //                    // If it is a TypeCompound it was already present in source
+        // (right?),
+        //                    // so there is nothing to do.
+        //                    // System.out.println("  found TypeComound: " + am + " pos: "
+        // + ((Attribute.TypeCompound)am).position);
+        //                } else {
+        // TODO: DOES THIS LEAD TO DOUBLING UP ON THE SAME ANNOTATION IN THE ELEMENT?
+        Attribute.TypeCompound tc =
+            TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror(am, tapos, processingEnv);
+        res = res.prepend(tc);
+        //                }
+      }
+      return res;
+    }
+
+    @Override
+    public List<TypeCompound> visitDeclared(
+        AnnotatedDeclaredType type, TypeAnnotationPosition tapos) {
+      if (visitedNodes.containsKey(type)) {
+        return visitedNodes.get(type);
+      }
+      // Hack for termination
+      visitedNodes.put(type, List.nil());
+      List<Attribute.TypeCompound> res;
+
+      TypeAnnotationPosition oldpos = TypeAnnotationUtils.copyTAPosition(tapos);
+      locateNestedTypes(type, tapos);
+
+      res = directAnnotations(type, tapos);
+
+      // we sometimes fix-up raw types with wildcards, do not write these into the bytecode as there
+      // are no corresponding type arguments and therefore no location to actually add them to
+      if (!type.wasRaw()) {
+        int arg = 0;
+        for (AnnotatedTypeMirror ta : type.getTypeArguments()) {
+          TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos);
+          newpos.location =
+              tapos.location.append(new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg));
+          res = scanAndReduce(ta, newpos, res);
+          ++arg;
+        }
+      }
+
+      AnnotatedTypeMirror encl = type.getEnclosingType();
+      if (encl != null && encl.getKind() != TypeKind.NONE && encl.getKind() != TypeKind.ERROR) {
+        // use original tapos
+        res = scanAndReduce(encl, oldpos, res);
+      }
+      visitedNodes.put(type, res);
+      return res;
+    }
+
+    /* Modeled after
+     * {@link com.sun.tools.javac.code.TypeAnnotations.TypeAnnotationPositions#locateNestedTypes(Type, TypeAnnotationPosition)}
+     */
+    private void locateNestedTypes(AnnotatedDeclaredType type, TypeAnnotationPosition p) {
+      // The number of "steps" to get from the full type to the
+      // left-most outer type.
+      ListBuffer<TypePathEntry> depth = new ListBuffer<>();
+
+      Type encl = (Type) type.getUnderlyingType().getEnclosingType();
+      while (encl != null && encl.getKind() != TypeKind.NONE && encl.getKind() != TypeKind.ERROR) {
+        depth = depth.append(TypePathEntry.INNER_TYPE);
+        encl = encl.getEnclosingType();
+      }
+
+      if (depth.nonEmpty()) {
+        p.location = p.location.appendList(depth.toList());
+      }
+    }
+
+    @Override
+    public List<TypeCompound> visitIntersection(
+        AnnotatedIntersectionType type, TypeAnnotationPosition tapos) {
+      if (visitedNodes.containsKey(type)) {
+        return visitedNodes.get(type);
+      }
+      visitedNodes.put(type, List.nil());
+      List<Attribute.TypeCompound> res;
+      res = directAnnotations(type, tapos);
+
+      int arg = 0;
+      for (AnnotatedTypeMirror bound : type.getBounds()) {
+        TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos);
+        newpos.location =
+            tapos.location.append(new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg));
+        res = scanAndReduce(bound, newpos, res);
+        ++arg;
+      }
+      visitedNodes.put(type, res);
+      return res;
+    }
+
+    @Override
+    public List<TypeCompound> visitUnion(AnnotatedUnionType type, TypeAnnotationPosition tapos) {
+      // We should never need to write a union type, so raise an error.
+      throw new BugInCF(
+          "TypesIntoElement: encountered union type: " + type + " at position: " + tapos);
+    }
+
+    @Override
+    public List<TypeCompound> visitArray(AnnotatedArrayType type, TypeAnnotationPosition tapos) {
+      List<Attribute.TypeCompound> res;
+      res = directAnnotations(type, tapos);
+
+      TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos);
+      newpos.location = tapos.location.append(TypePathEntry.ARRAY);
+
+      return reduce(super.visitArray(type, newpos), res);
+    }
+
+    @Override
+    public List<TypeCompound> visitPrimitive(
+        AnnotatedPrimitiveType type, TypeAnnotationPosition tapos) {
+      List<Attribute.TypeCompound> res;
+      res = directAnnotations(type, tapos);
+      return res;
+    }
+
+    @Override
+    public List<TypeCompound> visitTypeVariable(
+        AnnotatedTypeVariable type, TypeAnnotationPosition tapos) {
+      List<Attribute.TypeCompound> res;
+      res = directAnnotations(type, tapos);
+      // Do not call super. The bound will be visited separately.
+      return res;
+    }
+
+    @Override
+    public List<TypeCompound> visitWildcard(
+        AnnotatedWildcardType type, TypeAnnotationPosition tapos) {
+      if (this.visitedNodes.containsKey(type)) {
+        return List.nil();
+      }
+      // Hack for termination, otherwise we'll visit one type too far (the same recursive
+      // wildcard twice and generate extra type annos)
+      visitedNodes.put(type, List.nil());
+      List<Attribute.TypeCompound> res;
+
+      // Note: By default, an Unbound wildcard will return true for both isExtendsBound and
+      // isSuperBound
+      if (((Type.WildcardType) type.getUnderlyingType()).isExtendsBound()) {
+        res = directAnnotations(type.getSuperBound(), tapos);
+
+        AnnotatedTypeMirror ext = type.getExtendsBound();
+        if (ext != null) {
+          TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos);
+          newpos.location = tapos.location.append(TypePathEntry.WILDCARD);
+          res = scanAndReduce(ext, newpos, res);
+        }
+
+      } else {
+        res = directAnnotations(type.getExtendsBound(), tapos);
+        AnnotatedTypeMirror sup = type.getSuperBoundField();
+        if (sup != null) {
+          TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos);
+          newpos.location = tapos.location.append(TypePathEntry.WILDCARD);
+          res = scanAndReduce(sup, newpos, res);
+        }
+      }
+      visitedNodes.put(type, res);
+      return res;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/VisitorState.java b/framework/src/main/java/org/checkerframework/framework/type/VisitorState.java
new file mode 100644
index 0000000..a51ccb3
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/VisitorState.java
@@ -0,0 +1,143 @@
+package org.checkerframework.framework.type;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * Represents the state of a visitor. Stores the relevant information to find the type of 'this' in
+ * the visitor.
+ */
+public class VisitorState {
+  /** The type of the enclosing class tree. */
+  private AnnotatedDeclaredType act;
+  /** The enclosing class tree. */
+  private ClassTree ct;
+
+  /** The receiver type of the enclosing method tree. */
+  private AnnotatedDeclaredType mrt;
+  /** The enclosing method tree. */
+  private MethodTree mt;
+
+  /** The assignment context is a tree as well as its type. */
+  private Pair<Tree, AnnotatedTypeMirror> assignmentContext;
+
+  /** The visitor's current tree path. */
+  private TreePath path;
+
+  /** Updates the type of the class currently visited. */
+  public void setClassType(AnnotatedDeclaredType act) {
+    this.act = act;
+  }
+
+  /** Updates the tree of the class currently visited. */
+  public void setClassTree(ClassTree ct) {
+    this.ct = ct;
+  }
+
+  /** Updates the method receiver type currently visited. */
+  public void setMethodReceiver(AnnotatedDeclaredType mrt) {
+    this.mrt = mrt;
+  }
+
+  /** Updates the method currently visited. */
+  public void setMethodTree(MethodTree mt) {
+    this.mt = mt;
+  }
+
+  /**
+   * Updates the assignment context.
+   *
+   * @param assignmentContext the new assignment context to use
+   */
+  public void setAssignmentContext(Pair<Tree, AnnotatedTypeMirror> assignmentContext) {
+    this.assignmentContext = assignmentContext;
+  }
+
+  /** Sets the current path for the visitor. */
+  public void setPath(TreePath path) {
+    this.path = path;
+  }
+
+  /**
+   * Returns the type of the enclosing class.
+   *
+   * @return the type of the enclosing class
+   */
+  public AnnotatedDeclaredType getClassType() {
+    if (act == null) {
+      return null;
+    }
+    return act.deepCopy();
+  }
+
+  /**
+   * Returns the class tree currently visiting.
+   *
+   * @return the class tree currently visiting
+   */
+  public ClassTree getClassTree() {
+    return this.ct;
+  }
+
+  /**
+   * Returns the method receiver type of the enclosing method.
+   *
+   * @return the method receiver type of the enclosing method
+   */
+  public AnnotatedDeclaredType getMethodReceiver() {
+    if (mrt == null) {
+      return null;
+    }
+    return mrt.deepCopy();
+  }
+
+  /**
+   * Returns the method tree currently visiting.
+   *
+   * @return the method tree currently visiting
+   */
+  public MethodTree getMethodTree() {
+    return this.mt;
+  }
+
+  /**
+   * Returns the assignment context.
+   *
+   * <p>NOTE: This method is known to be buggy.
+   *
+   * @return the assignment context
+   */
+  public Pair<Tree, AnnotatedTypeMirror> getAssignmentContext() {
+    return assignmentContext;
+  }
+
+  /**
+   * Returns the current path for the visitor.
+   *
+   * @return the current path for the visitor
+   */
+  public TreePath getPath() {
+    return this.path;
+  }
+
+  @SideEffectFree
+  @Override
+  public String toString() {
+    return String.format(
+        "VisitorState: method %s (%s) / class %s (%s)%n"
+            + "    assignment context %s (%s)%n"
+            + "    path is non-null: %s",
+        (mt != null ? mt.getName() : "null"),
+        mrt,
+        (ct != null ? ct.getSimpleName() : "null"),
+        act,
+        (assignmentContext != null ? assignmentContext.first : "null"),
+        (assignmentContext != null ? assignmentContext.second : "null"),
+        path != null);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/package-info.java b/framework/src/main/java/org/checkerframework/framework/type/package-info.java
new file mode 100644
index 0000000..c609376
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * Contains a way of representing the type of a program element that considers the type qualifiers
+ * on that element (and ignores its Java type). The package additionally provides utilities for
+ * obtaining and manipulating this type representation.
+ *
+ * @checker_framework.manual #creating-a-checker How to write a checker plugin
+ */
+package org.checkerframework.framework.type;
diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java
new file mode 100644
index 0000000..29ed8a8
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java
@@ -0,0 +1,577 @@
+package org.checkerframework.framework.type.poly;
+
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewClassTree;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.visitor.EquivalentAtmComboScanner;
+import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.AnnotationMirrorMap;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Implements framework support for qualifier polymorphism.
+ *
+ * <p>{@link DefaultQualifierPolymorphism} implements the abstract methods in this class. Subclasses
+ * can alter the way instantiations of polymorphic qualifiers are {@link #combine combined}.
+ *
+ * <p>An "instantiation" is a mapping from declaration type to use-site type &mdash; that is, a
+ * mapping from {@code @Poly*} to concrete qualifiers.
+ *
+ * <p>The implementation performs these steps:
+ *
+ * <ul>
+ *   <li>the PolyCollector creates an instantiation
+ *   <li>if the instantiation is non-empty: the Replacer does resolution -- that is, it replaces
+ *       each occurrence of {@code @Poly*} by the concrete qualifier it maps to in the instantiation
+ *   <li>if the instantiation is empty, the Completer replaces each {@code @Poly*} by the top
+ *       qualifier
+ * </ul>
+ */
+public abstract class AbstractQualifierPolymorphism implements QualifierPolymorphism {
+
+  /** Annotated type factory. */
+  protected final AnnotatedTypeFactory atypeFactory;
+
+  /** The qualifier hierarchy to use. */
+  protected final QualifierHierarchy qualHierarchy;
+
+  /**
+   * The polymorphic qualifiers: mapping from a polymorphic qualifier of {@code qualHierarchy} to
+   * the top qualifier of that hierarchy.
+   */
+  protected final AnnotationMirrorMap<AnnotationMirror> polyQuals = new AnnotationMirrorMap<>();
+
+  /**
+   * The qualifiers at the top of {@code qualHierarchy}. These are the values in {@code polyQuals}.
+   */
+  protected final AnnotationMirrorSet topQuals;
+
+  /** Determines the instantiations for each polymorphic qualifier. */
+  private PolyCollector collector = new PolyCollector();
+
+  /** Resolves each polymorphic qualifier by replacing it with its instantiation. */
+  private final SimpleAnnotatedTypeScanner<Void, AnnotationMirrorMap<AnnotationMirror>> replacer;
+
+  /**
+   * Completes a type by removing any unresolved polymorphic qualifiers, replacing them with the
+   * bottom qualifiers.
+   */
+  private final SimpleAnnotatedTypeScanner<Void, Void> completer;
+
+  /** Mapping from poly qualifier to its instantiation for types with a qualifier parameter. */
+  protected final AnnotationMirrorMap<AnnotationMirror> polyInstantiationForQualifierParameter =
+      new AnnotationMirrorMap<>();
+
+  /**
+   * Creates an {@link AbstractQualifierPolymorphism} instance that uses the given checker for
+   * querying type qualifiers and the given factory for getting annotated types. Subclasses need to
+   * add polymorphic qualifiers to {@code this.polyQuals}.
+   *
+   * @param env the processing environment
+   * @param factory the factory for the current checker
+   */
+  protected AbstractQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) {
+    this.atypeFactory = factory;
+    this.qualHierarchy = factory.getQualifierHierarchy();
+    this.topQuals = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations());
+
+    this.completer =
+        new SimpleAnnotatedTypeScanner<>(
+            (type, p) -> {
+              for (Map.Entry<AnnotationMirror, AnnotationMirror> entry : polyQuals.entrySet()) {
+                AnnotationMirror poly = entry.getKey();
+                AnnotationMirror top = entry.getValue();
+                if (type.hasAnnotation(poly)) {
+                  type.removeAnnotation(poly);
+                  if (type.getKind() != TypeKind.TYPEVAR && type.getKind() != TypeKind.WILDCARD) {
+                    // Do not add qualifiers to type variables and
+                    // wildcards
+                    type.addAnnotation(this.qualHierarchy.getBottomAnnotation(top));
+                  }
+                }
+              }
+              return null;
+            });
+
+    this.replacer =
+        new SimpleAnnotatedTypeScanner<>(
+            (type, map) -> {
+              replace(type, map);
+              return null;
+            });
+  }
+
+  /**
+   * Reset to allow reuse of the same instance. Subclasses should override this method. The
+   * overriding implementation should clear its additional state and then call the super
+   * implementation.
+   */
+  protected void reset() {
+    collector.reset();
+    replacer.reset();
+    completer.reset();
+    polyInstantiationForQualifierParameter.clear();
+  }
+
+  /**
+   * Resolves polymorphism annotations for the given type.
+   *
+   * @param tree the tree associated with the type
+   * @param type the type to annotate
+   */
+  @Override
+  public void resolve(MethodInvocationTree tree, AnnotatedExecutableType type) {
+    if (polyQuals.isEmpty()) {
+      return;
+    }
+
+    // javac produces enum super calls with zero arguments even though the
+    // method element requires two.
+    // See also BaseTypeVisitor.visitMethodInvocation and
+    // CFGBuilder.CFGTranslationPhaseOne.visitMethodInvocation.
+    if (TreeUtils.isEnumSuper(tree)) {
+      return;
+    }
+    List<AnnotatedTypeMirror> parameters =
+        AnnotatedTypes.expandVarArgs(atypeFactory, type, tree.getArguments());
+    List<AnnotatedTypeMirror> arguments =
+        AnnotatedTypes.getAnnotatedTypes(atypeFactory, parameters, tree.getArguments());
+
+    AnnotationMirrorMap<AnnotationMirror> instantiationMapping =
+        collector.visit(arguments, parameters);
+
+    // For super() and this() method calls, getReceiverType(tree) does not return the correct
+    // type. So, just skip those.  This is consistent with skipping receivers of constructors below.
+    if (type.getReceiverType() != null
+        && !TreeUtils.isSuperConstructorCall(tree)
+        && !TreeUtils.isThisConstructorCall(tree)) {
+      instantiationMapping =
+          collector.reduce(
+              instantiationMapping,
+              collector.visit(atypeFactory.getReceiverType(tree), type.getReceiverType()));
+    }
+
+    if (instantiationMapping != null && !instantiationMapping.isEmpty()) {
+      replacer.visit(type, instantiationMapping);
+    } else {
+      completer.visit(type);
+    }
+    reset();
+  }
+
+  @Override
+  public void resolve(NewClassTree tree, AnnotatedExecutableType type) {
+    if (polyQuals.isEmpty()) {
+      return;
+    }
+    List<AnnotatedTypeMirror> parameters =
+        AnnotatedTypes.expandVarArgs(atypeFactory, type, tree.getArguments());
+    List<AnnotatedTypeMirror> arguments =
+        AnnotatedTypes.getAnnotatedTypes(atypeFactory, parameters, tree.getArguments());
+
+    AnnotationMirrorMap<AnnotationMirror> instantiationMapping =
+        collector.visit(arguments, parameters);
+    // TODO: poly on receiver for constructors?
+    // instantiationMapping = collector.reduce(instantiationMapping,
+    //        collector.visit(factory.getReceiverType(tree), type.getReceiverType()));
+
+    AnnotatedTypeMirror newClassType = atypeFactory.fromNewClass(tree);
+    instantiationMapping =
+        collector.reduce(
+            instantiationMapping, mapQualifierToPoly(newClassType, type.getReturnType()));
+
+    if (instantiationMapping != null && !instantiationMapping.isEmpty()) {
+      replacer.visit(type, instantiationMapping);
+    } else {
+      completer.visit(type);
+    }
+    reset();
+  }
+
+  @Override
+  public void resolve(VariableElement field, AnnotatedTypeMirror owner, AnnotatedTypeMirror type) {
+    if (polyQuals.isEmpty()) {
+      return;
+    }
+    AnnotationMirrorMap<AnnotationMirror> matchingMapping = new AnnotationMirrorMap<>();
+    polyQuals.forEach(
+        (polyAnnotation, topAnno) -> {
+          AnnotationMirror annoOnOwner = owner.getAnnotationInHierarchy(topAnno);
+          if (annoOnOwner != null) {
+            matchingMapping.put(polyAnnotation, annoOnOwner);
+          }
+        });
+    if (!matchingMapping.isEmpty()) {
+      replacer.visit(type, matchingMapping);
+    } else {
+      completer.visit(type);
+    }
+    reset();
+  }
+
+  @Override
+  public void resolve(
+      AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference) {
+    for (AnnotationMirror type : functionalInterface.getReturnType().getAnnotations()) {
+      if (atypeFactory.getQualifierHierarchy().isPolymorphicQualifier(type)) {
+        // functional interface has a polymorphic qualifier, so they should not be resolved
+        // on memberReference.
+        return;
+      }
+    }
+    AnnotationMirrorMap<AnnotationMirror> instantiationMapping;
+
+    List<AnnotatedTypeMirror> parameters = memberReference.getParameterTypes();
+    List<AnnotatedTypeMirror> args = functionalInterface.getParameterTypes();
+    if (args.size() == parameters.size() + 1) {
+      // If the member reference is a reference to an instance method of an arbitrary
+      // object, then first parameter of the functional interface corresponds to the
+      // receiver of the member reference.
+      List<AnnotatedTypeMirror> newParameters = new ArrayList<>(parameters.size() + 1);
+      newParameters.add(memberReference.getReceiverType());
+      newParameters.addAll(parameters);
+      parameters = newParameters;
+      instantiationMapping = new AnnotationMirrorMap<>();
+    } else {
+      if (memberReference.getReceiverType() != null
+          && functionalInterface.getReceiverType() != null) {
+        instantiationMapping =
+            mapQualifierToPoly(
+                functionalInterface.getReceiverType(), memberReference.getReceiverType());
+      } else {
+        instantiationMapping = new AnnotationMirrorMap<>();
+      }
+    }
+    // Deal with varargs
+    if (memberReference.isVarArgs() && !functionalInterface.isVarArgs()) {
+      parameters = AnnotatedTypes.expandVarArgsFromTypes(memberReference, args);
+    }
+
+    instantiationMapping =
+        collector.reduce(instantiationMapping, collector.visit(args, parameters));
+
+    if (instantiationMapping != null && !instantiationMapping.isEmpty()) {
+      replacer.visit(memberReference, instantiationMapping);
+    } else {
+      // TODO: Do we need this (return type?)
+      completer.visit(memberReference);
+    }
+    reset();
+  }
+
+  /**
+   * If the primary annotation of {@code polyType} is a polymorphic qualifier, then it is mapped to
+   * the primary annotation of {@code type} and the map is returned. Otherwise, an empty map is
+   * returned.
+   *
+   * @param type type with qualifier to us in the map
+   * @param polyType type that may have polymorphic qualifiers
+   * @return a mapping from the polymorphic qualifiers in {@code polyType} to the qualifiers in
+   *     {@code type}
+   */
+  private AnnotationMirrorMap<AnnotationMirror> mapQualifierToPoly(
+      AnnotatedTypeMirror type, AnnotatedTypeMirror polyType) {
+    AnnotationMirrorMap<AnnotationMirror> result = new AnnotationMirrorMap<>();
+
+    for (Map.Entry<AnnotationMirror, AnnotationMirror> kv : polyQuals.entrySet()) {
+      AnnotationMirror top = kv.getValue();
+      AnnotationMirror poly = kv.getKey();
+      if (polyType.hasAnnotation(poly)) {
+        AnnotationMirror typeQual = type.getAnnotationInHierarchy(top);
+        if (typeQual != null) {
+          if (atypeFactory.hasQualifierParameterInHierarchy(type, top)) {
+            polyInstantiationForQualifierParameter.put(poly, typeQual);
+          }
+          result.put(poly, typeQual);
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns annotation that is the combination of the two annotations. The annotations are
+   * instantiations for {@code polyQual}.
+   *
+   * <p>The combination is typically their least upper bound. (It could be the GLB in the case that
+   * all arguments to a polymorphic method must have the same annotation.)
+   *
+   * @param polyQual polymorphic qualifier for which {@code a1} and {@code a2} are instantiations
+   * @param a1 an annotation that is an instantiation of {@code polyQual}
+   * @param a2 an annotation that is an instantiation of {@code polyQual}
+   * @return an annotation that is the combination of the two annotations
+   */
+  protected abstract AnnotationMirror combine(
+      AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2);
+
+  /**
+   * Replaces the top-level polymorphic annotations in {@code type} with the instantiations in
+   * {@code replacements}.
+   *
+   * <p>This method is called on all parts of a type.
+   *
+   * @param type AnnotatedTypeMirror whose poly annotations are replaced; it is side-effected by
+   *     this method
+   * @param replacements mapping from polymorphic annotation to instantiation
+   */
+  protected abstract void replace(
+      AnnotatedTypeMirror type, AnnotationMirrorMap<AnnotationMirror> replacements);
+
+  /**
+   * A helper class that resolves the polymorphic qualifiers with the most restrictive qualifier. It
+   * returns a mapping from the polymorphic qualifier to the substitution for that qualifier.
+   */
+  private class PolyCollector
+      extends EquivalentAtmComboScanner<AnnotationMirrorMap<AnnotationMirror>, Void> {
+
+    /**
+     * Set of {@link AnnotatedTypeVariable} or {@link AnnotatedWildcardType} that have been visited.
+     * Used to prevent infinite recursion on recursive types.
+     *
+     * <p>Uses reference equality rather than equals because the visitor may visit two types that
+     * are structurally equal, but not actually the same. For example, the wildcards in {@code
+     * Pair<?,?>} may be equal, but they both should be visited.
+     */
+    private final Set<AnnotatedTypeMirror> visitedTypes =
+        Collections.newSetFromMap(new IdentityHashMap<AnnotatedTypeMirror, Boolean>());
+
+    /**
+     * Returns true if the {@link AnnotatedTypeMirror} has been visited. If it has not, then it is
+     * added to the list of visited AnnotatedTypeMirrors.
+     */
+    private boolean visited(AnnotatedTypeMirror atm) {
+      return !visitedTypes.add(atm);
+    }
+
+    @Override
+    protected AnnotationMirrorMap<AnnotationMirror> scanWithNull(
+        AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) {
+      return new AnnotationMirrorMap<>();
+    }
+
+    @Override
+    public AnnotationMirrorMap<AnnotationMirror> reduce(
+        AnnotationMirrorMap<AnnotationMirror> r1, AnnotationMirrorMap<AnnotationMirror> r2) {
+
+      if (r1 == null || r1.isEmpty()) {
+        return r2;
+      }
+      if (r2 == null || r2.isEmpty()) {
+        return r1;
+      }
+
+      AnnotationMirrorMap<AnnotationMirror> res = new AnnotationMirrorMap<>();
+      // Ensure that all qualifiers from r1 and r2 are visited.
+      AnnotationMirrorSet r2remain = new AnnotationMirrorSet();
+      r2remain.addAll(r2.keySet());
+      for (Map.Entry<AnnotationMirror, AnnotationMirror> entry : r1.entrySet()) {
+        AnnotationMirror polyQual = entry.getKey();
+        AnnotationMirror a1Annos = entry.getValue();
+        AnnotationMirror a2Annos = r2.get(polyQual);
+        if (a2Annos == null) {
+          res.put(polyQual, a1Annos);
+        } else {
+          res.put(polyQual, combine(polyQual, a1Annos, a2Annos));
+        }
+        r2remain.remove(polyQual);
+      }
+      for (AnnotationMirror key2 : r2remain) {
+        res.put(key2, r2.get(key2));
+      }
+      return res;
+    }
+
+    /**
+     * Calls {@link #visit(AnnotatedTypeMirror, AnnotatedTypeMirror)} for each type in {@code
+     * types}.
+     *
+     * @param types AnnotateTypeMirrors used to find instantiations
+     * @param polyTypes AnnotatedTypeMirrors that may have polymorphic qualifiers
+     * @return a mapping of polymorphic qualifiers to their instantiations
+     */
+    private AnnotationMirrorMap<AnnotationMirror> visit(
+        Iterable<? extends AnnotatedTypeMirror> types,
+        Iterable<? extends AnnotatedTypeMirror> polyTypes) {
+      AnnotationMirrorMap<AnnotationMirror> result = new AnnotationMirrorMap<>();
+
+      Iterator<? extends AnnotatedTypeMirror> itert = types.iterator();
+      Iterator<? extends AnnotatedTypeMirror> itera = polyTypes.iterator();
+
+      while (itert.hasNext() && itera.hasNext()) {
+        AnnotatedTypeMirror type = itert.next();
+        AnnotatedTypeMirror actualType = itera.next();
+        result = reduce(result, visit(type, actualType));
+      }
+      if (itert.hasNext()) {
+        throw new BugInCF(
+            "PolyCollector.visit: types is longer than polyTypes:%n"
+                + "  types = %s%n  polyTypes = %s%n",
+            types, polyTypes);
+      }
+      if (itera.hasNext()) {
+        throw new BugInCF(
+            "PolyCollector.visit: types is shorter than polyTypes:%n"
+                + "  types = %s%n  polyTypes = %s%n",
+            types, polyTypes);
+      }
+      return result;
+    }
+
+    /**
+     * Creates a mapping of polymorphic qualifiers to their instantiations by visiting each
+     * composite type in {@code type}.
+     *
+     * @param type AnnotateTypeMirror used to find instantiations
+     * @param polyType AnnotatedTypeMirror that may have polymorphic qualifiers
+     * @return a mapping of polymorphic qualifiers to their instantiations
+     */
+    private AnnotationMirrorMap<AnnotationMirror> visit(
+        AnnotatedTypeMirror type, AnnotatedTypeMirror polyType) {
+      if (type.getKind() == TypeKind.NULL) {
+        return mapQualifierToPoly(type, polyType);
+      }
+
+      if (type.getKind() == TypeKind.WILDCARD) {
+        AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) type;
+        if (wildcardType.getExtendsBound().getKind() == TypeKind.WILDCARD) {
+          wildcardType = (AnnotatedWildcardType) wildcardType.getExtendsBound();
+        }
+        if (wildcardType.isUninferredTypeArgument()) {
+          return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType);
+        }
+
+        switch (polyType.getKind()) {
+          case WILDCARD:
+            AnnotatedTypeMirror asSuper =
+                AnnotatedTypes.asSuper(atypeFactory, wildcardType, polyType);
+            return visit(asSuper, polyType, null);
+          case TYPEVAR:
+            return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType);
+          default:
+            return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType);
+        }
+      }
+
+      AnnotatedTypeMirror asSuper = AnnotatedTypes.asSuper(atypeFactory, type, polyType);
+
+      return visit(asSuper, polyType, null);
+    }
+
+    @Override
+    protected String defaultErrorMessage(
+        AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) {
+      return String.format(
+          "AbstractQualifierPolymorphism: Unexpected combination: type1: %s (%s) type2: %s (%s).",
+          type1, type1.getKind(), type2, type2.getKind());
+    }
+
+    @Override
+    public AnnotationMirrorMap<AnnotationMirror> visitArray_Array(
+        AnnotatedArrayType type1, AnnotatedArrayType type2, Void aVoid) {
+      AnnotationMirrorMap<AnnotationMirror> result = mapQualifierToPoly(type1, type2);
+      return reduce(result, super.visitArray_Array(type1, type2, aVoid));
+    }
+
+    @Override
+    public AnnotationMirrorMap<AnnotationMirror> visitDeclared_Declared(
+        AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, Void aVoid) {
+      // Don't call super because asSuper has to be called on each type argument.
+      if (visited(type2)) {
+        return new AnnotationMirrorMap<>();
+      }
+
+      AnnotationMirrorMap<AnnotationMirror> result = mapQualifierToPoly(type1, type2);
+
+      Iterator<AnnotatedTypeMirror> type2Args = type2.getTypeArguments().iterator();
+      for (AnnotatedTypeMirror type1Arg : type1.getTypeArguments()) {
+        AnnotatedTypeMirror type2Arg = type2Args.next();
+        if (TypesUtils.isErasedSubtype(
+            type1Arg.getUnderlyingType(),
+            type2Arg.getUnderlyingType(),
+            atypeFactory.getChecker().getTypeUtils())) {
+          result = reduce(result, visit(type1Arg, type2Arg));
+        } // else an unchecked warning was issued by Java, ignore this part of the type.
+      }
+
+      return result;
+    }
+
+    @Override
+    public AnnotationMirrorMap<AnnotationMirror> visitIntersection_Intersection(
+        AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, Void aVoid) {
+      AnnotationMirrorMap<AnnotationMirror> result = mapQualifierToPoly(type1, type2);
+      return reduce(result, super.visitIntersection_Intersection(type1, type2, aVoid));
+    }
+
+    @Override
+    public AnnotationMirrorMap<AnnotationMirror> visitNull_Null(
+        AnnotatedNullType type1, AnnotatedNullType type2, Void aVoid) {
+      return mapQualifierToPoly(type1, type2);
+    }
+
+    @Override
+    public AnnotationMirrorMap<AnnotationMirror> visitPrimitive_Primitive(
+        AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, Void aVoid) {
+      return mapQualifierToPoly(type1, type2);
+    }
+
+    @Override
+    public AnnotationMirrorMap<AnnotationMirror> visitTypevar_Typevar(
+        AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, Void aVoid) {
+      if (visited(type2)) {
+        return new AnnotationMirrorMap<>();
+      }
+      AnnotationMirrorMap<AnnotationMirror> result = mapQualifierToPoly(type1, type2);
+      return reduce(result, super.visitTypevar_Typevar(type1, type2, aVoid));
+    }
+
+    @Override
+    public AnnotationMirrorMap<AnnotationMirror> visitUnion_Union(
+        AnnotatedUnionType type1, AnnotatedUnionType type2, Void aVoid) {
+      AnnotationMirrorMap<AnnotationMirror> result = mapQualifierToPoly(type1, type2);
+      return reduce(result, super.visitUnion_Union(type1, type2, aVoid));
+    }
+
+    @Override
+    public AnnotationMirrorMap<AnnotationMirror> visitWildcard_Wildcard(
+        AnnotatedWildcardType type1, AnnotatedWildcardType type2, Void aVoid) {
+      if (visited(type2)) {
+        return new AnnotationMirrorMap<>();
+      }
+      AnnotationMirrorMap<AnnotationMirror> result = mapQualifierToPoly(type1, type2);
+      return reduce(result, super.visitWildcard_Wildcard(type1, type2, aVoid));
+    }
+
+    /** Resets the state. */
+    public void reset() {
+      this.visitedTypes.clear();
+      this.visited.clear();
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java
new file mode 100644
index 0000000..3bcc50b
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java
@@ -0,0 +1,64 @@
+package org.checkerframework.framework.type.poly;
+
+import java.util.Map;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.util.AnnotationMirrorMap;
+
+/**
+ * Default implementation of {@link AbstractQualifierPolymorphism}. The polymorphic qualifiers for a
+ * checker that uses this class are found by searching all supported qualifiers. Instantiations of a
+ * polymorphic qualifier are combined using lub.
+ */
+public class DefaultQualifierPolymorphism extends AbstractQualifierPolymorphism {
+
+  /**
+   * Creates a {@link DefaultQualifierPolymorphism} instance that uses {@code factory} for querying
+   * type qualifiers and for getting annotated types.
+   *
+   * @param env the processing environment
+   * @param factory the factory for the current checker
+   */
+  public DefaultQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) {
+    super(env, factory);
+
+    for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) {
+      AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top);
+      if (poly != null) {
+        polyQuals.put(poly, top);
+      }
+    }
+  }
+
+  @Override
+  protected void replace(
+      AnnotatedTypeMirror type, AnnotationMirrorMap<AnnotationMirror> replacements) {
+    for (Map.Entry<AnnotationMirror, AnnotationMirror> pqentry : replacements.entrySet()) {
+      AnnotationMirror poly = pqentry.getKey();
+      if (type.hasAnnotation(poly)) {
+        type.removeAnnotation(poly);
+        AnnotationMirror qual;
+        if (polyInstantiationForQualifierParameter.containsKey(poly)) {
+          qual = polyInstantiationForQualifierParameter.get(poly);
+        } else {
+          qual = pqentry.getValue();
+        }
+        type.replaceAnnotation(qual);
+      }
+    }
+  }
+
+  /** Combines the two annotations using the least upper bound. */
+  @Override
+  protected AnnotationMirror combine(
+      AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2) {
+    if (a1 == null) {
+      return a2;
+    } else if (a2 == null) {
+      return a1;
+    }
+    return qualHierarchy.leastUpperBound(a1, a2);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java
new file mode 100644
index 0000000..a0944c6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java
@@ -0,0 +1,52 @@
+package org.checkerframework.framework.type.poly;
+
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewClassTree;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+
+/**
+ * Interface to implement qualifier polymorphism.
+ *
+ * @see PolymorphicQualifier
+ * @see AbstractQualifierPolymorphism
+ * @see DefaultQualifierPolymorphism
+ */
+public interface QualifierPolymorphism {
+
+  /**
+   * Resolves polymorphism annotations for the given type.
+   *
+   * @param tree the tree associated with the type
+   * @param type the type to annotate; is side-effected by this method
+   */
+  void resolve(MethodInvocationTree tree, AnnotatedExecutableType type);
+
+  /**
+   * Resolves polymorphism annotations for the given type.
+   *
+   * @param tree the tree associated with the type
+   * @param type the type to annotate; is side-effected by this method
+   */
+  void resolve(NewClassTree tree, AnnotatedExecutableType type);
+
+  /**
+   * Resolves polymorphism annotations for the given type.
+   *
+   * @param functionalInterface the function type of {@code memberReference}
+   * @param memberReference the type of a member reference; is side-effected by this method
+   */
+  void resolve(
+      AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference);
+
+  /**
+   * Resolves polymorphism annotations for the given field type.
+   *
+   * @param field field element to whose poly annotation must be resolved
+   * @param owner the type of the object whose field is being typed
+   * @param type type of the field which still has poly annotations
+   */
+  void resolve(VariableElement field, AnnotatedTypeMirror owner, AnnotatedTypeMirror type);
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/DebugListTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/DebugListTreeAnnotator.java
new file mode 100644
index 0000000..0b97f29
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/DebugListTreeAnnotator.java
@@ -0,0 +1,41 @@
+package org.checkerframework.framework.type.treeannotator;
+
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/** A ListTreeAnnotator implementation that additionally outputs debugging information. */
+public class DebugListTreeAnnotator extends ListTreeAnnotator {
+  private final Set<Kind> kinds;
+
+  public DebugListTreeAnnotator(TreeAnnotator... annotators) {
+    super(annotators);
+    kinds = Collections.emptySet();
+  }
+
+  public DebugListTreeAnnotator(Tree.Kind[] kinds, TreeAnnotator... annotators) {
+    super(annotators);
+    this.kinds = new HashSet<>(Arrays.asList(kinds));
+  }
+
+  @Override
+  public Void defaultAction(Tree node, AnnotatedTypeMirror type) {
+    if (kinds.contains(node.getKind())) {
+      System.out.println("DebugListTreeAnnotator input tree: " + node);
+      System.out.println("    Initial type: " + type);
+      for (TreeAnnotator annotator : annotators) {
+        System.out.println("    Running annotator: " + annotator.getClass());
+        annotator.visit(node, type);
+        System.out.println("    Current type: " + type);
+      }
+    } else {
+      super.defaultAction(node, type);
+    }
+
+    return null;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/ListTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/ListTreeAnnotator.java
new file mode 100644
index 0000000..183ca5f
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/ListTreeAnnotator.java
@@ -0,0 +1,62 @@
+package org.checkerframework.framework.type.treeannotator;
+
+import com.sun.source.tree.Tree;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/**
+ * ListTreeAnnotator is a TreeVisitor that executes a list of {@link TreeAnnotator} for each tree
+ * visited.
+ *
+ * <p>Checkers should not extend ListTreeAnnotator; they should instead pass a custom TreeAnnotator
+ * to the constructor.
+ *
+ * @see LiteralTreeAnnotator
+ * @see PropagationTreeAnnotator
+ */
+public class ListTreeAnnotator extends TreeAnnotator {
+
+  protected final List<TreeAnnotator> annotators;
+
+  /**
+   * @param annotators the annotators that will be executed for each tree scanned by this
+   *     TreeAnnotator. They are executed in the order passed in.
+   */
+  public ListTreeAnnotator(TreeAnnotator... annotators) {
+    this(Arrays.asList(annotators));
+  }
+
+  /**
+   * @param annotators the annotators that will be executed for each tree scanned by this
+   *     TreeAnnotator. They are executed in the order passed in.
+   */
+  public ListTreeAnnotator(List<TreeAnnotator> annotators) {
+    super(null);
+    List<TreeAnnotator> annotatorList = new ArrayList<>(annotators.size());
+    for (TreeAnnotator annotator : annotators) {
+      if (annotator instanceof ListTreeAnnotator) {
+        annotatorList.addAll(((ListTreeAnnotator) annotator).annotators);
+      } else {
+        annotatorList.add(annotator);
+      }
+    }
+    this.annotators = Collections.unmodifiableList(annotatorList);
+  }
+
+  @Override
+  public Void defaultAction(Tree node, AnnotatedTypeMirror type) {
+    for (TreeAnnotator annotator : annotators) {
+      annotator.visit(node, type);
+    }
+
+    return null;
+  }
+
+  @Override
+  public String toString() {
+    return "ListTreeAnnotator(" + annotators + ")";
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/LiteralTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/LiteralTreeAnnotator.java
new file mode 100644
index 0000000..ef4a784
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/LiteralTreeAnnotator.java
@@ -0,0 +1,266 @@
+package org.checkerframework.framework.type.treeannotator;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.typeannotator.DefaultForTypeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.BugInCF;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Adds annotations to a type based on the contents of a tree. This class applies annotations
+ * specified by {@link org.checkerframework.framework.qual.QualifierForLiterals}; it is designed to
+ * be added to a {@link ListTreeAnnotator} via {@link
+ * GenericAnnotatedTypeFactory#createTreeAnnotator()}
+ *
+ * <p>{@link LiteralTreeAnnotator} does not traverse trees deeply.
+ *
+ * @see TreeAnnotator
+ */
+public class LiteralTreeAnnotator extends TreeAnnotator {
+
+  /* The following three fields are mappings from a particular AST kind,
+   * AST Class, or String literal pattern to the set of AnnotationMirrors
+   * that should be defaulted.
+   * There can be at most one qualifier per qualifier hierarchy.
+   * For type systems with single top qualifiers, the sets will always contain
+   * at most one element.
+   */
+  private final Map<Kind, Set<AnnotationMirror>> treeKinds;
+  private final Map<Class<?>, Set<AnnotationMirror>> treeClasses;
+  private final IdentityHashMap<Pattern, Set<AnnotationMirror>> stringPatterns;
+
+  protected final QualifierHierarchy qualHierarchy;
+
+  /**
+   * Map of {@link LiteralKind}s to {@link Tree.Kind}s. This is here and not in LiteralKinds because
+   * LiteralKind is in the checker-qual.jar which cannot depend on classes, such as Tree.Kind, that
+   * are in the tools.jar
+   */
+  private static final Map<LiteralKind, Tree.Kind> literalKindToTreeKind =
+      new EnumMap<>(LiteralKind.class);
+
+  static {
+    literalKindToTreeKind.put(LiteralKind.BOOLEAN, Kind.BOOLEAN_LITERAL);
+    literalKindToTreeKind.put(LiteralKind.CHAR, Kind.CHAR_LITERAL);
+    literalKindToTreeKind.put(LiteralKind.DOUBLE, Kind.DOUBLE_LITERAL);
+    literalKindToTreeKind.put(LiteralKind.FLOAT, Kind.FLOAT_LITERAL);
+    literalKindToTreeKind.put(LiteralKind.INT, Kind.INT_LITERAL);
+    literalKindToTreeKind.put(LiteralKind.LONG, Kind.LONG_LITERAL);
+    literalKindToTreeKind.put(LiteralKind.NULL, Kind.NULL_LITERAL);
+    literalKindToTreeKind.put(LiteralKind.STRING, Kind.STRING_LITERAL);
+  }
+
+  /** Creates a {@link LiteralTreeAnnotator} for the given {@code atypeFactory}. */
+  public LiteralTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+    super(atypeFactory);
+    this.treeKinds = new EnumMap<>(Kind.class);
+    this.treeClasses = new HashMap<>();
+    this.stringPatterns = new IdentityHashMap<>();
+
+    this.qualHierarchy = atypeFactory.getQualifierHierarchy();
+
+    // Get type qualifiers from the checker.
+    Set<Class<? extends Annotation>> quals = atypeFactory.getSupportedTypeQualifiers();
+
+    // For each qualifier, read the @QualifierForLiterals annotation and put its contents into maps.
+    for (Class<? extends Annotation> qual : quals) {
+      QualifierForLiterals forLiterals = qual.getAnnotation(QualifierForLiterals.class);
+      if (forLiterals == null) {
+        continue;
+      }
+
+      AnnotationMirror theQual = AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), qual);
+      for (LiteralKind literalKind : forLiterals.value()) {
+        addLiteralKind(literalKind, theQual);
+      }
+
+      for (String pattern : forLiterals.stringPatterns()) {
+        addStringPattern(pattern, theQual);
+      }
+
+      if (forLiterals.value().length == 0 && forLiterals.stringPatterns().length == 0) {
+        addLiteralKind(LiteralKind.ALL, theQual);
+      }
+    }
+  }
+
+  /**
+   * Adds standard qualifiers for literals. Currently sets the null literal to bottom if no other
+   * default is set for null literals. Also, see {@link
+   * DefaultForTypeAnnotator#addStandardDefaults()}.
+   *
+   * @return this
+   */
+  public LiteralTreeAnnotator addStandardLiteralQualifiers() {
+    // Set null to bottom if no other qualifier is given.
+    if (!treeKinds.containsKey(Kind.NULL_LITERAL)) {
+      for (AnnotationMirror bottom : qualHierarchy.getBottomAnnotations()) {
+        addLiteralKind(LiteralKind.NULL, bottom);
+      }
+      return this;
+    }
+    Set<? extends AnnotationMirror> tops = qualHierarchy.getTopAnnotations();
+    Set<AnnotationMirror> defaultForNull = treeKinds.get(Kind.NULL_LITERAL);
+    if (tops.size() == defaultForNull.size()) {
+      return this;
+    }
+    for (AnnotationMirror top : tops) {
+      if (qualHierarchy.findAnnotationInHierarchy(defaultForNull, top) == null) {
+        defaultForNull.add(qualHierarchy.getBottomAnnotation(top));
+      }
+    }
+    return this;
+  }
+
+  /**
+   * Added a rule for a particular {@link LiteralKind}
+   *
+   * @param literalKind {@code LiteralKind} that should be defaulted to {@code theQual}
+   * @param theQual the {@code AnnotationMirror} that should be applied to the {@code literalKind}
+   */
+  public void addLiteralKind(LiteralKind literalKind, AnnotationMirror theQual) {
+    if (literalKind == LiteralKind.ALL) {
+      for (LiteralKind iterLiteralKind : LiteralKind.allLiteralKinds()) {
+        addLiteralKind(iterLiteralKind, theQual);
+      }
+    } else if (literalKind == LiteralKind.PRIMITIVE) {
+      for (LiteralKind iterLiteralKind : LiteralKind.primitiveLiteralKinds()) {
+        addLiteralKind(iterLiteralKind, theQual);
+      }
+    } else {
+      Tree.Kind treeKind = literalKindToTreeKind.get(literalKind);
+      if (treeKind != null) {
+        addTreeKind(treeKind, theQual);
+      } else {
+        throw new BugInCF("LiteralKind " + literalKind + " is not mapped to a Tree.Kind.");
+      }
+    }
+  }
+
+  /**
+   * Added a rule for a particular {@link com.sun.source.tree.Tree.Kind}
+   *
+   * @param treeKind {@code Tree.Kind} that should be implicited to {@code theQual}
+   * @param theQual the {@code AnnotationMirror} that should be applied to the {@code treeKind}
+   */
+  private void addTreeKind(Kind treeKind, AnnotationMirror theQual) {
+    boolean res = qualHierarchy.updateMappingToMutableSet(treeKinds, treeKind, theQual);
+    if (!res) {
+      throw new BugInCF(
+          "LiteralTreeAnnotator: tried to add mapping %s=%s to %s", treeKind, theQual, treeKinds);
+    }
+  }
+
+  /**
+   * Added a rule for all String literals that match the given pattern.
+   *
+   * @param pattern pattern to match Strings against
+   * @param theQual {@code AnnotationMirror} to apply to Strings that match the pattern
+   */
+  public void addStringPattern(String pattern, AnnotationMirror theQual) {
+    boolean res =
+        qualHierarchy.updateMappingToMutableSet(stringPatterns, Pattern.compile(pattern), theQual);
+    if (!res) {
+      throw new BugInCF(
+          "LiteralTreeAnnotator: invalid update of stringPatterns "
+              + stringPatterns
+              + " at "
+              + pattern
+              + " with "
+              + theQual);
+    }
+  }
+
+  @Override
+  public Void defaultAction(Tree tree, AnnotatedTypeMirror type) {
+    if (tree == null || type == null) {
+      return null;
+    }
+
+    // If this tree's kind is in treeKinds, annotate the type.
+
+    // If this tree's class or any of its interfaces are in treeClasses, annotate the type, and
+    // if it was an interface add a mapping for it to treeClasses.
+    if (treeKinds.containsKey(tree.getKind())) {
+      Set<AnnotationMirror> fnd = treeKinds.get(tree.getKind());
+      type.addMissingAnnotations(fnd);
+    } else if (!treeClasses.isEmpty()) {
+      Class<? extends Tree> t = tree.getClass();
+      if (treeClasses.containsKey(t)) {
+        Set<AnnotationMirror> fnd = treeClasses.get(t);
+        type.addMissingAnnotations(fnd);
+      }
+      for (Class<?> c : t.getInterfaces()) {
+        if (treeClasses.containsKey(c)) {
+          Set<AnnotationMirror> fnd = treeClasses.get(c);
+          type.addMissingAnnotations(fnd);
+          treeClasses.put(t, treeClasses.get(c));
+        }
+      }
+    }
+    return null;
+  }
+
+  /** Go through the string patterns and add the greatest lower bound of all matching patterns. */
+  @Override
+  public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
+    if (!stringPatterns.isEmpty() && tree.getKind() == Kind.STRING_LITERAL) {
+      List<Set<? extends AnnotationMirror>> matches = new ArrayList<>();
+      List<Set<? extends AnnotationMirror>> nonMatches = new ArrayList<>();
+
+      String string = (String) tree.getValue();
+      for (Pattern pattern : stringPatterns.keySet()) {
+        Set<AnnotationMirror> sam = stringPatterns.get(pattern);
+        if (pattern.matcher(string).matches()) {
+          matches.add(sam);
+        } else {
+          nonMatches.add(sam);
+        }
+      }
+      if (!matches.isEmpty()) {
+        Set<? extends AnnotationMirror> res = matches.get(0);
+        for (Set<? extends AnnotationMirror> sam : matches) {
+          res = qualHierarchy.greatestLowerBounds(res, sam);
+        }
+        // Verify that res is not a subtype of any type in nonMatches
+        for (Set<? extends AnnotationMirror> sam : nonMatches) {
+          if (qualHierarchy.isSubtype(res, sam)) {
+            String matchesOnePerLine = "";
+            for (Set<? extends AnnotationMirror> match : matches) {
+              matchesOnePerLine += System.lineSeparator() + "     " + match;
+            }
+            throw new BugInCF(
+                StringsPlume.joinLines(
+                    "Bug in @QualifierForLiterals(stringpatterns=...) in type hierarchy"
+                        + " definition:",
+                    " the glb of `matches` for \"" + string + "\" is " + res,
+                    " which is a subtype of " + sam,
+                    " whose pattern does not match \"" + string + "\".",
+                    "  matches = " + matchesOnePerLine,
+                    "  nonMatches = " + nonMatches));
+          }
+        }
+        type.addAnnotations(res);
+      }
+    }
+    return super.visitLiteral(tree, type);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java
new file mode 100644
index 0000000..6d1b7a4
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java
@@ -0,0 +1,249 @@
+package org.checkerframework.framework.type.treeannotator;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.UnaryTree;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TypeKindUtils;
+
+/**
+ * {@link PropagationTreeAnnotator} adds qualifiers to types where the resulting type is a function
+ * of an input type, e.g. the result of a binary operation is a LUB of the type of expressions in
+ * the binary operation.
+ *
+ * <p>{@link PropagationTreeAnnotator} is generally run first by {@link ListTreeAnnotator} since the
+ * trees it handles are not usually targets of {@code @DefaultFor}.
+ *
+ * <p>{@link PropagationTreeAnnotator} does not traverse trees deeply by default.
+ *
+ * @see TreeAnnotator
+ */
+public class PropagationTreeAnnotator extends TreeAnnotator {
+
+  private final QualifierHierarchy qualHierarchy;
+
+  /** Creates a {@link PropagationTreeAnnotator} for the given {@code atypeFactory}. */
+  public PropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+    super(atypeFactory);
+    this.qualHierarchy = atypeFactory.getQualifierHierarchy();
+  }
+
+  @Override
+  public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) {
+    assert type.getKind() == TypeKind.ARRAY
+        : "PropagationTreeAnnotator.visitNewArray: should be an array type";
+
+    AnnotatedTypeMirror componentType = ((AnnotatedArrayType) type).getComponentType();
+
+    // prev is the lub of the initializers if they exist, otherwise the current component type.
+    Set<? extends AnnotationMirror> prev = null;
+    if (tree.getInitializers() != null && !tree.getInitializers().isEmpty()) {
+      // We have initializers, either with or without an array type.
+
+      // TODO (issue #599): This only works at the top level.  It should work at all levels of
+      // the array.
+      for (ExpressionTree init : tree.getInitializers()) {
+        AnnotatedTypeMirror initType = atypeFactory.getAnnotatedType(init);
+        // initType might be a typeVariable, so use effectiveAnnotations.
+        Set<AnnotationMirror> annos = initType.getEffectiveAnnotations();
+
+        prev = (prev == null) ? annos : qualHierarchy.leastUpperBounds(prev, annos);
+      }
+    } else {
+      prev = componentType.getAnnotations();
+    }
+
+    assert prev != null
+        : "PropagationTreeAnnotator.visitNewArray: violated assumption about qualifiers";
+
+    Pair<Tree, AnnotatedTypeMirror> context = atypeFactory.getVisitorState().getAssignmentContext();
+    Set<? extends AnnotationMirror> post;
+
+    if (context != null && context.second != null && context.second instanceof AnnotatedArrayType) {
+      AnnotatedTypeMirror contextComponentType =
+          ((AnnotatedArrayType) context.second).getComponentType();
+      // Only compare the qualifiers that existed in the array type.
+      // Defaulting wasn't performed yet, so prev might have fewer qualifiers than
+      // contextComponentType, which would cause a failure.
+      // TODO: better solution?
+      boolean prevIsSubtype = true;
+      for (AnnotationMirror am : prev) {
+        if (contextComponentType.isAnnotatedInHierarchy(am)
+            && !this.qualHierarchy.isSubtype(
+                am, contextComponentType.getAnnotationInHierarchy(am))) {
+          prevIsSubtype = false;
+        }
+      }
+      // TODO: checking conformance of component kinds is a basic sanity check
+      // It fails for array initializer expressions. Those should be handled nicer.
+      if (contextComponentType.getKind() == componentType.getKind()
+          && (prev.isEmpty()
+              || (!contextComponentType.getAnnotations().isEmpty() && prevIsSubtype))) {
+        post = contextComponentType.getAnnotations();
+      } else {
+        // The type of the array initializers is incompatible with the context type!
+        // Somebody else will complain.
+        post = prev;
+      }
+    } else {
+      // No context is available - simply use what we have.
+      post = prev;
+    }
+    // TODO (issue #599): This only works at the top level.  It should work at all levels of
+    // the array.
+    addAnnoOrBound(componentType, post);
+
+    return null;
+  }
+
+  @Override
+  public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
+    if (hasPrimaryAnnotationInAllHierarchies(type)) {
+      // If the type already has a primary annotation in all hierarchies, then the
+      // propagated annotations won't be applied.  So don't compute them.
+      return null;
+    }
+    AnnotatedTypeMirror rhs = atypeFactory.getAnnotatedType(node.getExpression());
+    AnnotatedTypeMirror lhs = atypeFactory.getAnnotatedType(node.getVariable());
+    Set<? extends AnnotationMirror> lubs =
+        qualHierarchy.leastUpperBounds(
+            rhs.getEffectiveAnnotations(), lhs.getEffectiveAnnotations());
+    type.addMissingAnnotations(lubs);
+    return null;
+  }
+
+  @Override
+  public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
+    if (hasPrimaryAnnotationInAllHierarchies(type)) {
+      // If the type already has a primary annotation in all hierarchies, then the
+      // propagated annotations won't be applied.  So don't compute them.
+      // Also, calling getAnnotatedType on the left and right operands is potentially expensive.
+      return null;
+    }
+
+    Pair<AnnotatedTypeMirror, AnnotatedTypeMirror> argTypes = atypeFactory.binaryTreeArgTypes(node);
+    Set<? extends AnnotationMirror> lubs =
+        qualHierarchy.leastUpperBounds(
+            argTypes.first.getEffectiveAnnotations(), argTypes.second.getEffectiveAnnotations());
+    type.addMissingAnnotations(lubs);
+
+    return null;
+  }
+
+  @Override
+  public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) {
+    if (hasPrimaryAnnotationInAllHierarchies(type)) {
+      // If the type already has a primary annotation in all hierarchies, then the
+      // propagated annotations won't be applied.  So don't compute them.
+      return null;
+    }
+
+    AnnotatedTypeMirror exp = atypeFactory.getAnnotatedType(node.getExpression());
+    type.addMissingAnnotations(exp.getAnnotations());
+    return null;
+  }
+
+  /*
+   * TODO: would this make sense in general?
+  @Override
+  public Void visitConditionalExpression(ConditionalExpressionTree node, AnnotatedTypeMirror type) {
+      if (!type.isAnnotated()) {
+          AnnotatedTypeMirror a = typeFactory.getAnnotatedType(node.getTrueExpression());
+          AnnotatedTypeMirror b = typeFactory.getAnnotatedType(node.getFalseExpression());
+          Set<AnnotationMirror> lubs = qualHierarchy.leastUpperBounds(a.getEffectiveAnnotations(), b.getEffectiveAnnotations());
+          type.replaceAnnotations(lubs);
+      }
+      return super.visitConditionalExpression(node, type);
+  }*/
+
+  @Override
+  public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) {
+    if (hasPrimaryAnnotationInAllHierarchies(type)) {
+      // If the type is already has a primary annotation in all hierarchies, then the
+      // propagated annotations won't be applied.  So don't compute them.
+      return null;
+    }
+
+    AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(node.getExpression());
+    if (type.getKind() == TypeKind.TYPEVAR) {
+      if (exprType.getKind() == TypeKind.TYPEVAR) {
+        // If both types are type variables, take the direct annotations.
+        type.addMissingAnnotations(exprType.getAnnotations());
+      }
+      // else do nothing.
+    } else {
+      // Use effective annotations from the expression, to get upper bound of type variables.
+      Set<AnnotationMirror> expressionAnnos = exprType.getEffectiveAnnotations();
+
+      TypeKind castKind = type.getPrimitiveKind();
+      if (castKind != null) {
+        TypeKind exprKind = exprType.getPrimitiveKind();
+        if (exprKind != null) {
+          switch (TypeKindUtils.getPrimitiveConversionKind(exprKind, castKind)) {
+            case WIDENING:
+              expressionAnnos =
+                  atypeFactory.getWidenedAnnotations(expressionAnnos, exprKind, castKind);
+              break;
+            case NARROWING:
+              atypeFactory.getNarrowedAnnotations(expressionAnnos, exprKind, castKind);
+              break;
+            case SAME:
+              // Nothing to do
+              break;
+          }
+        }
+      }
+
+      // If the qualifier on the expression type is a supertype of the qualifier upper bound
+      // of the cast type, then apply the bound as the default qualifier rather than the
+      // expression qualifier.
+      addAnnoOrBound(type, expressionAnnos);
+    }
+
+    return null;
+  }
+
+  private boolean hasPrimaryAnnotationInAllHierarchies(AnnotatedTypeMirror type) {
+    boolean annotated = true;
+    for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) {
+      if (type.getEffectiveAnnotationInHierarchy(top) == null) {
+        annotated = false;
+      }
+    }
+    return annotated;
+  }
+
+  /**
+   * Adds the qualifiers in {@code annos} to {@code type} that are below the qualifier upper bound
+   * of type and for which type does not already have annotation in the same hierarchy. If a
+   * qualifier in {@code annos} is above the bound, then the bound is added to {@code type} instead.
+   *
+   * @param type annotations are added to this type
+   * @param annos annotations to add to type
+   */
+  private void addAnnoOrBound(AnnotatedTypeMirror type, Set<? extends AnnotationMirror> annos) {
+    Set<AnnotationMirror> boundAnnos =
+        atypeFactory.getQualifierUpperBounds().getBoundQualifiers(type.getUnderlyingType());
+    Set<AnnotationMirror> annosToAdd = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror boundAnno : boundAnnos) {
+      AnnotationMirror anno = qualHierarchy.findAnnotationInSameHierarchy(annos, boundAnno);
+      if (anno != null && !qualHierarchy.isSubtype(anno, boundAnno)) {
+        annosToAdd.add(boundAnno);
+      }
+    }
+    type.addMissingAnnotations(annosToAdd);
+    type.addMissingAnnotations(annos);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java
new file mode 100644
index 0000000..4bb7682
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java
@@ -0,0 +1,57 @@
+package org.checkerframework.framework.type.treeannotator;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.SimpleTreeVisitor;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/**
+ * {@link TreeAnnotator} is an abstract SimpleTreeVisitor to be used with {@link ListTreeAnnotator}.
+ *
+ * <p>This class does not visit component parts of the tree. By default, the visit methods all call
+ * {@link #defaultAction(Tree, Object)}, which does nothing unless overriden. Therefore, subclass do
+ * not need to call super unless they override {@link #defaultAction(Tree, Object)}.
+ *
+ * @see ListTreeAnnotator
+ * @see PropagationTreeAnnotator
+ * @see LiteralTreeAnnotator
+ */
+public abstract class TreeAnnotator extends SimpleTreeVisitor<Void, AnnotatedTypeMirror> {
+
+  protected final AnnotatedTypeFactory atypeFactory;
+
+  protected TreeAnnotator(AnnotatedTypeFactory atypeFactory) {
+    this.atypeFactory = atypeFactory;
+  }
+
+  /**
+   * This method is not called when checking a method invocation against its declaration. So,
+   * instead of overriding this method, override TypeAnnotator.visitExecutable.
+   * TypeAnnotator.visitExecutable is called both when checking method declarations and method
+   * invocations.
+   *
+   * @see org.checkerframework.framework.type.typeannotator.TypeAnnotator
+   */
+  @Override
+  public Void visitMethod(MethodTree node, AnnotatedTypeMirror p) {
+    return super.visitMethod(node, p);
+  }
+
+  /**
+   * When overriding this method, getAnnotatedType on the left and right operands should only be
+   * called when absolutely necessary. Otherwise, the checker will be very slow on heavily nested
+   * binary trees. (For example, a + b + c + d + e + f + g + h.)
+   *
+   * <p>If a checker's performance is still too slow, the types of binary trees could be computed in
+   * a subclass of {@link org.checkerframework.framework.flow.CFTransfer}. When computing the types
+   * in a transfer, look up the value in the store rather than the AnnotatedTypeFactory. Then this
+   * method should annotate binary trees with top so that the type applied in the transfer is always
+   * a subtype of the type the AnnotatedTypeFactory computes.
+   */
+  @Override
+  public Void visitBinary(BinaryTree node, AnnotatedTypeMirror mirror) {
+    return super.visitBinary(node, mirror);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultForTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultForTypeAnnotator.java
new file mode 100644
index 0000000..3bb9e77
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultForTypeAnnotator.java
@@ -0,0 +1,353 @@
+package org.checkerframework.framework.type.typeannotator;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.EnumMap;
+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;
+import java.util.regex.PatternSyntaxException;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypeSystemError;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Adds annotations to a type based on the use of a type. This class applies annotations specified
+ * by {@link DefaultFor}; it is designed to be used in a {@link ListTypeAnnotator} constructed in
+ * {@link GenericAnnotatedTypeFactory#createTypeAnnotator()} ()}
+ *
+ * <p>{@link DefaultForTypeAnnotator} traverses types deeply.
+ *
+ * <p>This class takes care of two of the attributes of {@link DefaultFor}; the others are handled
+ * in {@link org.checkerframework.framework.util.defaults.QualifierDefaults}.
+ *
+ * @see ListTypeAnnotator
+ */
+public class DefaultForTypeAnnotator extends TypeAnnotator {
+
+  /** Map from {@link TypeKind} to annotations. */
+  private final Map<TypeKind, Set<AnnotationMirror>> typeKinds;
+  /** Map from {@link AnnotatedTypeMirror} classes to annotations. */
+  private final Map<Class<? extends AnnotatedTypeMirror>, Set<AnnotationMirror>> atmClasses;
+  /** Map from fully qualified class name strings to annotations. */
+  private final Map<String, Set<AnnotationMirror>> types;
+  /**
+   * A list where each element associates an annotation with name regexes and name exception
+   * regexes.
+   */
+  private final ListOfNameRegexes listOfNameRegexes;
+
+  /** {@link QualifierHierarchy} */
+  private final QualifierHierarchy qualHierarchy;
+
+  /**
+   * Creates a {@link DefaultForTypeAnnotator} from the given checker, using that checker to
+   * determine the annotations that are in the type hierarchy.
+   */
+  public DefaultForTypeAnnotator(AnnotatedTypeFactory typeFactory) {
+    super(typeFactory);
+    this.typeKinds = new EnumMap<>(TypeKind.class);
+    this.atmClasses = new HashMap<>();
+    this.types = new HashMap<>();
+    this.listOfNameRegexes = new ListOfNameRegexes();
+
+    this.qualHierarchy = typeFactory.getQualifierHierarchy();
+
+    // Get type qualifiers from the checker.
+    Set<Class<? extends Annotation>> quals = typeFactory.getSupportedTypeQualifiers();
+
+    // For each qualifier, read the @DefaultFor annotation and put its types, kinds, and names
+    // into maps.
+    for (Class<? extends Annotation> qual : quals) {
+      DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class);
+      if (defaultFor == null) {
+        continue;
+      }
+
+      AnnotationMirror theQual = AnnotationBuilder.fromClass(typeFactory.getElementUtils(), qual);
+
+      for (org.checkerframework.framework.qual.TypeKind typeKind : defaultFor.typeKinds()) {
+        TypeKind mappedTk = mapTypeKinds(typeKind);
+        addTypeKind(mappedTk, theQual);
+      }
+
+      for (Class<?> typeName : defaultFor.types()) {
+        addTypes(typeName, theQual);
+      }
+
+      listOfNameRegexes.add(theQual, defaultFor);
+    }
+  }
+
+  /**
+   * Map between {@link org.checkerframework.framework.qual.TypeKind} and {@link
+   * javax.lang.model.type.TypeKind}.
+   *
+   * @param typeKind the Checker Framework TypeKind
+   * @return the javax TypeKind
+   */
+  private TypeKind mapTypeKinds(org.checkerframework.framework.qual.TypeKind typeKind) {
+    return TypeKind.valueOf(typeKind.name());
+  }
+
+  /** Add default qualifier, {@code theQual}, for the given TypeKind. */
+  public void addTypeKind(TypeKind typeKind, AnnotationMirror theQual) {
+    boolean res = qualHierarchy.updateMappingToMutableSet(typeKinds, typeKind, theQual);
+    if (!res) {
+      throw new BugInCF(
+          "TypeAnnotator: invalid update of typeKinds "
+              + typeKinds
+              + " at "
+              + typeKind
+              + " with "
+              + theQual);
+    }
+  }
+
+  /** Add default qualifier, {@code theQual}, for the given {@link AnnotatedTypeMirror} class. */
+  public void addAtmClass(
+      Class<? extends AnnotatedTypeMirror> typeClass, AnnotationMirror theQual) {
+    boolean res = qualHierarchy.updateMappingToMutableSet(atmClasses, typeClass, theQual);
+    if (!res) {
+      throw new BugInCF(
+          "TypeAnnotator: invalid update of atmClasses "
+              + atmClasses
+              + " at "
+              + typeClass
+              + " with "
+              + theQual);
+    }
+  }
+
+  /** Add default qualifier, {@code theQual}, for the given type. */
+  public void addTypes(Class<?> clazz, AnnotationMirror theQual) {
+    String typeNameString = clazz.getCanonicalName();
+    boolean res = qualHierarchy.updateMappingToMutableSet(types, typeNameString, theQual);
+    if (!res) {
+      throw new BugInCF(
+          "TypeAnnotator: invalid update of types " + types + " at " + clazz + " with " + theQual);
+    }
+  }
+
+  @Override
+  protected Void scan(AnnotatedTypeMirror type, Void p) {
+    // If the type's fully-qualified name is in the appropriate map, annotate the type. Do this
+    // before looking at kind or class, as this information is more specific.
+
+    String qname;
+    if (type.getKind() == TypeKind.DECLARED) {
+      qname = TypesUtils.getQualifiedName((DeclaredType) type.getUnderlyingType());
+    } else if (type.getKind().isPrimitive()) {
+      qname = type.getUnderlyingType().toString();
+    } else {
+      qname = null;
+    }
+
+    if (qname != null) {
+      Set<AnnotationMirror> fromQname = types.get(qname);
+      if (fromQname != null) {
+        type.addMissingAnnotations(fromQname);
+      }
+    }
+
+    // If the type's kind or class is in the appropriate map, annotate the type.
+    Set<AnnotationMirror> fromKind = typeKinds.get(type.getKind());
+    if (fromKind != null) {
+      type.addMissingAnnotations(fromKind);
+    } else if (!atmClasses.isEmpty()) {
+      Class<? extends AnnotatedTypeMirror> t = type.getClass();
+      Set<AnnotationMirror> fromClass = atmClasses.get(t);
+      if (fromClass != null) {
+        type.addMissingAnnotations(fromClass);
+      }
+    }
+
+    return super.scan(type, p);
+  }
+
+  /**
+   * Adds standard rules. Currently, sets Void to bottom if no other qualifier is set for Void.
+   * Also, see {@link LiteralTreeAnnotator#addStandardLiteralQualifiers()}.
+   *
+   * @return this
+   */
+  public DefaultForTypeAnnotator addStandardDefaults() {
+    if (!types.containsKey(Void.class.getCanonicalName())) {
+      for (AnnotationMirror bottom : qualHierarchy.getBottomAnnotations()) {
+        addTypes(Void.class, bottom);
+      }
+    } else {
+      Set<AnnotationMirror> annos = types.get(Void.class.getCanonicalName());
+      for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) {
+        if (qualHierarchy.findAnnotationInHierarchy(annos, top) == null) {
+          addTypes(Void.class, qualHierarchy.getBottomAnnotation(top));
+        }
+      }
+    }
+
+    return this;
+  }
+
+  /**
+   * Apply defaults based on a variable name to a type.
+   *
+   * @param type a type to apply defaults to
+   * @param name the name of the variable that has type {@code type}, or the name of the method
+   *     whose return type is {@code type}
+   */
+  public void defaultTypeFromName(AnnotatedTypeMirror type, String name) {
+    // TODO: Check whether the annotation is applicable to this Java type?
+    AnnotationMirror defaultAnno = listOfNameRegexes.getDefaultAnno(name);
+    if (defaultAnno != null) {
+      if (typeFactory
+              .getQualifierHierarchy()
+              .findAnnotationInHierarchy(type.getAnnotations(), defaultAnno)
+          == null) {
+        type.addAnnotation(defaultAnno);
+      }
+    }
+  }
+
+  @Override
+  public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) {
+    ExecutableElement element = type.getElement();
+
+    Iterator<AnnotatedTypeMirror> paramTypes = type.getParameterTypes().iterator();
+    for (VariableElement paramElt : element.getParameters()) {
+      String paramName = paramElt.getSimpleName().toString();
+      AnnotatedTypeMirror paramType = paramTypes.next();
+      defaultTypeFromName(paramType, paramName);
+    }
+
+    String methodName = element.getSimpleName().toString();
+    AnnotatedTypeMirror returnType = type.getReturnType();
+    defaultTypeFromName(returnType, methodName);
+
+    return super.visitExecutable(type, aVoid);
+  }
+
+  /**
+   * A list where each element associates an annotation with name regexes and name exception
+   * regexes.
+   */
+  private static class ListOfNameRegexes extends ArrayList<NameRegexes> {
+
+    static final long serialVersionUID = 20200218L;
+
+    /**
+     * Update this list from the {@code names} and {@code namesExceptions} fields of a @DefaultFor
+     * annotation.
+     *
+     * @param theQual the qualifier that a @DefaultFor annotation is written on
+     * @param defaultFor the @DefaultFor annotation written on {@code theQual}
+     */
+    void add(AnnotationMirror theQual, DefaultFor defaultFor) {
+      if (defaultFor.names().length != 0) {
+        NameRegexes thisName = new NameRegexes(theQual);
+        for (String nameRegex : defaultFor.names()) {
+          try {
+            thisName.names.add(Pattern.compile(nameRegex));
+          } catch (PatternSyntaxException e) {
+            throw new TypeSystemError(
+                "In annotation %s, names() value \"%s\" is not a regular expression",
+                theQual, nameRegex);
+          }
+        }
+        for (String namesExceptionsRegex : defaultFor.namesExceptions()) {
+          try {
+            thisName.namesExceptions.add(Pattern.compile(namesExceptionsRegex));
+          } catch (PatternSyntaxException e) {
+            throw new TypeSystemError(
+                "In annotation %s, namesExceptions() value \"%s\" is not a regular expression",
+                theQual, namesExceptionsRegex);
+          }
+        }
+        add(thisName);
+      } else if (defaultFor.namesExceptions().length != 0) {
+        throw new TypeSystemError(
+            "On annotation %s, %s has empty names() but nonempty namesExceptions()",
+            theQual, defaultFor);
+      }
+    }
+
+    /**
+     * Returns the annotation that should be the default for a variable of the given name, or for
+     * the return type of a method of the given name.
+     *
+     * @param name a variable name
+     * @return the annotation that should be the default for a variable named {@code name}, or null
+     *     if none
+     */
+    @Nullable AnnotationMirror getDefaultAnno(String name) {
+      if (this.isEmpty()) {
+        return null;
+      }
+      AnnotationMirror result = null;
+      for (NameRegexes nameRegexes : this) {
+        if (nameRegexes.matches(name)) {
+          if (result == null) {
+            result = nameRegexes.anno;
+          } else {
+            // This could combine the annotatations instead, but I think doing so
+            // silently would confuse users.
+            throw new TypeSystemError(
+                "Multiple annotations are applicable to the name \"%s\"", name);
+          }
+        }
+      }
+      return result;
+    }
+  }
+
+  /**
+   * Associates an annotation with the variable names that cause the annotation to be chosen as a
+   * default.
+   */
+  private static class NameRegexes {
+    /** The annotation. */
+    final AnnotationMirror anno;
+    /** The name regexes. */
+    final List<Pattern> names = new ArrayList<>(0);
+    /** The name exception regexes. */
+    final List<Pattern> namesExceptions = new ArrayList<>(0);
+
+    /**
+     * Constructs a NameRegexes from a @DefaultFor annotation.
+     *
+     * @param theQual the qualifier that {@code defaultFor} is written on
+     */
+    NameRegexes(AnnotationMirror theQual) {
+      this.anno = theQual;
+    }
+
+    /**
+     * Returns true if the regular expressions match the given name -- that is, if {@link #anno}
+     * should be used as the default type for a variable named {@code name}, or for the return type
+     * of a method named {@code name}.
+     *
+     * @param name a variable or method name
+     * @return true if {@link #anno} should be used as the default for a variable named {@code name}
+     */
+    public boolean matches(String name) {
+      return names.stream().anyMatch(p -> p.matcher(name).matches())
+          && namesExceptions.stream().noneMatch(p -> p.matcher(name).matches());
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultQualifierForUseTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultQualifierForUseTypeAnnotator.java
new file mode 100644
index 0000000..7c1773f
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultQualifierForUseTypeAnnotator.java
@@ -0,0 +1,178 @@
+package org.checkerframework.framework.type.typeannotator;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.framework.qual.DefaultQualifierForUse;
+import org.checkerframework.framework.qual.NoDefaultQualifierForUse;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.CollectionUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/** Implements support for {@link DefaultQualifierForUse} and {@link NoDefaultQualifierForUse}. */
+public class DefaultQualifierForUseTypeAnnotator extends TypeAnnotator {
+
+  /** The DefaultQualifierForUse.value field/element. */
+  private ExecutableElement defaultQualifierForUseValueElement;
+  /** The NoDefaultQualifierForUse.value field/element. */
+  private ExecutableElement noDefaultQualifierForUseValueElement;
+
+  /**
+   * Creates an DefaultQualifierForUseTypeAnnotator for {@code typeFactory}.
+   *
+   * @param typeFactory the type factory
+   */
+  public DefaultQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) {
+    super(typeFactory);
+    ProcessingEnvironment processingEnv = typeFactory.getProcessingEnv();
+    defaultQualifierForUseValueElement =
+        TreeUtils.getMethod(DefaultQualifierForUse.class, "value", 0, processingEnv);
+    noDefaultQualifierForUseValueElement =
+        TreeUtils.getMethod(NoDefaultQualifierForUse.class, "value", 0, processingEnv);
+  }
+
+  @Override
+  public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) {
+    Element element = type.getUnderlyingType().asElement();
+    Set<AnnotationMirror> annosToApply = getDefaultAnnosForUses(element);
+    type.addMissingAnnotations(annosToApply);
+    return super.visitDeclared(type, aVoid);
+  }
+
+  /**
+   * Cache of elements to the set of annotations that should be applied to unannotated uses of the
+   * element.
+   */
+  protected Map<Element, Set<AnnotationMirror>> elementToDefaults =
+      CollectionUtils.createLRUCache(100);
+
+  /** Clears all caches. */
+  public void clearCache() {
+    elementToDefaults.clear();
+  }
+
+  /** Returns the set of qualifiers that should be applied to unannotated uses of this element. */
+  protected Set<AnnotationMirror> getDefaultAnnosForUses(Element element) {
+    if (typeFactory.shouldCache && elementToDefaults.containsKey(element)) {
+      return elementToDefaults.get(element);
+    }
+    Set<AnnotationMirror> explictAnnos = getExplicitAnnos(element);
+    Set<AnnotationMirror> defaultAnnos = getDefaultQualifierForUses(element);
+    Set<AnnotationMirror> noDefaultAnnos = getHierarchiesNoDefault(element);
+    AnnotationMirrorSet annosToApply = new AnnotationMirrorSet();
+
+    for (AnnotationMirror top : typeFactory.getQualifierHierarchy().getTopAnnotations()) {
+      if (AnnotationUtils.containsSame(noDefaultAnnos, top)) {
+        continue;
+      }
+      AnnotationMirror defaultAnno =
+          typeFactory.getQualifierHierarchy().findAnnotationInHierarchy(defaultAnnos, top);
+      if (defaultAnno != null) {
+        annosToApply.add(defaultAnno);
+      } else {
+        AnnotationMirror explict =
+            typeFactory.getQualifierHierarchy().findAnnotationInHierarchy(explictAnnos, top);
+        if (explict != null) {
+          annosToApply.add(explict);
+        }
+      }
+    }
+    // If parsing stub files, then the annosToApply is incomplete, so don't cache them.
+    if (typeFactory.shouldCache
+        && !typeFactory.stubTypes.isParsing()
+        && !typeFactory.ajavaTypes.isParsing()) {
+      elementToDefaults.put(element, annosToApply);
+    }
+    return annosToApply;
+  }
+
+  /** Return the annotations explicitly written on the element. */
+  protected Set<AnnotationMirror> getExplicitAnnos(Element element) {
+    AnnotatedTypeMirror explicitAnnoOnDecl = typeFactory.fromElement(element);
+    return explicitAnnoOnDecl.getAnnotations();
+  }
+
+  /**
+   * Return the default qualifiers for uses of {@code element} as specified by a {@link
+   * DefaultQualifierForUse} annotation.
+   *
+   * <p>Subclasses may override to use an annotation other than {@link DefaultQualifierForUse}.
+   *
+   * @param element an element
+   * @return the default qualifiers for uses of {@code element}
+   */
+  protected Set<AnnotationMirror> getDefaultQualifierForUses(Element element) {
+    AnnotationMirror defaultQualifier =
+        typeFactory.getDeclAnnotation(element, DefaultQualifierForUse.class);
+    if (defaultQualifier == null) {
+      return Collections.emptySet();
+    }
+    return supportedAnnosFromAnnotationMirror(
+        AnnotationUtils.getElementValueClassNames(
+            defaultQualifier, defaultQualifierForUseValueElement));
+  }
+
+  /**
+   * Returns top annotations in hierarchies for which no default for use qualifier should be added.
+   */
+  protected Set<AnnotationMirror> getHierarchiesNoDefault(Element element) {
+    AnnotationMirror noDefaultQualifier =
+        typeFactory.getDeclAnnotation(element, NoDefaultQualifierForUse.class);
+    if (noDefaultQualifier == null) {
+      return Collections.emptySet();
+    }
+    return supportedAnnosFromAnnotationMirror(
+        AnnotationUtils.getElementValueClassNames(
+            noDefaultQualifier, noDefaultQualifierForUseValueElement));
+  }
+
+  /**
+   * Returns the set of qualifiers supported by this type system from the value element of {@code
+   * annotationMirror}.
+   *
+   * @param annotationMirror a non-null annotation with a value element that is an array of
+   *     annotation classes
+   * @return the set of qualifiers supported by this type system from the value element of {@code
+   *     annotationMirror}
+   * @deprecated use {@link #supportedAnnosFromAnnotationMirror(List)}
+   */
+  @SuppressWarnings("deprecation") // This method is itself deprecated.
+  @Deprecated // 2021-03-21
+  protected final AnnotationMirrorSet supportedAnnosFromAnnotationMirror(
+      AnnotationMirror annotationMirror) {
+    return supportedAnnosFromAnnotationMirror(
+        AnnotationUtils.getElementValueClassNames(annotationMirror, "value", true));
+  }
+
+  /**
+   * Returns the set of qualifiers supported by this type system from the value element of {@code
+   * annotationMirror}.
+   *
+   * @param annoClassNames a list of annotation class names
+   * @return the set of qualifiers supported by this type system from the value element of {@code
+   *     annotationMirror}
+   */
+  protected final AnnotationMirrorSet supportedAnnosFromAnnotationMirror(
+      List<@CanonicalName Name> annoClassNames) {
+    AnnotationMirrorSet supportAnnos = new AnnotationMirrorSet();
+    for (Name annoName : annoClassNames) {
+      AnnotationMirror anno = AnnotationBuilder.fromName(typeFactory.getElementUtils(), annoName);
+      if (typeFactory.isSupportedQualifier(anno)) {
+        supportAnnos.add(anno);
+      }
+    }
+    return supportAnnos;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java
new file mode 100644
index 0000000..00fb309
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java
@@ -0,0 +1,59 @@
+package org.checkerframework.framework.type.typeannotator;
+
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.framework.qual.RelevantJavaTypes;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+
+/**
+ * Adds annotations to types that are not relevant specified by the {@link RelevantJavaTypes} on a
+ * checker.
+ */
+public class IrrelevantTypeAnnotator extends TypeAnnotator {
+
+  /** Annotations to add. */
+  private Set<? extends AnnotationMirror> annotations;
+
+  /**
+   * Annotate every type with the annotationMirror except for those whose underlying Java type is
+   * one of (or a subtype of) a class in relevantClasses. (Only adds annotationMirror if no
+   * annotation in the hierarchy are already on the type.) If relevantClasses includes
+   * Object[].class, then all arrays are considered relevant.
+   *
+   * @param typeFactory AnnotatedTypeFactory
+   * @param annotations annotations to add
+   */
+  @SuppressWarnings("rawtypes")
+  public IrrelevantTypeAnnotator(
+      GenericAnnotatedTypeFactory typeFactory, Set<? extends AnnotationMirror> annotations) {
+    super(typeFactory);
+    this.annotations = annotations;
+  }
+
+  @Override
+  protected Void scan(AnnotatedTypeMirror type, Void aVoid) {
+    switch (type.getKind()) {
+      case TYPEVAR:
+      case WILDCARD:
+      case EXECUTABLE:
+      case INTERSECTION:
+      case UNION:
+      case NULL:
+      case NONE:
+      case PACKAGE:
+      case VOID:
+        return super.scan(type, aVoid);
+      default:
+        // go on
+    }
+
+    TypeMirror typeMirror = type.getUnderlyingType();
+
+    if (!((GenericAnnotatedTypeFactory) typeFactory).isRelevant(typeMirror)) {
+      type.addMissingAnnotations(annotations);
+    }
+    return super.scan(type, aVoid);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/ListTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/ListTypeAnnotator.java
new file mode 100644
index 0000000..d141335
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/ListTypeAnnotator.java
@@ -0,0 +1,67 @@
+package org.checkerframework.framework.type.typeannotator;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/**
+ * ListTypeAnnotator is a TypeAnnotator that executes a list of {@link TypeAnnotator} for each type
+ * visited.
+ *
+ * <p>Checkers should not extend ListTypeAnnotator; they should instead pass a custom TypeAnnotator
+ * to the constructor.
+ *
+ * @see DefaultForTypeAnnotator
+ * @see org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator
+ */
+public final class ListTypeAnnotator extends TypeAnnotator {
+
+  /**
+   * The annotators that will be executed for each type scanned by this TypeAnnotator. They are
+   * executed in order.
+   */
+  final List<TypeAnnotator> annotators;
+
+  /**
+   * Create a new ListTypeAnnotator.
+   *
+   * @param annotators the annotators that will be executed for each type scanned by this
+   *     TypeAnnotator. They are executed in the order passed in.
+   */
+  public ListTypeAnnotator(TypeAnnotator... annotators) {
+    this(Arrays.asList(annotators));
+  }
+
+  /**
+   * @param annotators the annotators that will be executed for each type scanned by this
+   *     TypeAnnotator. They are executed in the order passed in.
+   */
+  public ListTypeAnnotator(List<TypeAnnotator> annotators) {
+    super(null);
+    List<TypeAnnotator> annotatorList = new ArrayList<>(annotators.size());
+    for (TypeAnnotator annotator : annotators) {
+      if (annotator instanceof ListTypeAnnotator) {
+        annotatorList.addAll(((ListTypeAnnotator) annotator).annotators);
+      } else {
+        annotatorList.add(annotator);
+      }
+    }
+    this.annotators = Collections.unmodifiableList(annotatorList);
+  }
+
+  @Override
+  protected Void scan(AnnotatedTypeMirror type, Void aVoid) {
+    for (TypeAnnotator annotator : annotators) {
+      annotator.visit(type, aVoid);
+    }
+
+    return null;
+  }
+
+  @Override
+  public String toString() {
+    return "ListTypeAnnotator" + annotators;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java
new file mode 100644
index 0000000..35ae6bf
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java
@@ -0,0 +1,232 @@
+package org.checkerframework.framework.type.typeannotator;
+
+import com.sun.tools.javac.code.Type.WildcardType;
+import java.util.ArrayDeque;
+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.TypeElement;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * {@link PropagationTypeAnnotator} adds qualifiers to types where the qualifier to add should be
+ * transferred from one or more other types.
+ *
+ * <p>At the moment, the only function PropagationTypeAnnotator provides, is the propagation of
+ * generic type parameter annotations to unannotated wildcards with missing bounds annotations.
+ *
+ * @see
+ *     #visitWildcard(org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType,
+ *     Object)
+ *     <p>PropagationTypeAnnotator traverses trees deeply by default.
+ */
+public class PropagationTypeAnnotator extends TypeAnnotator {
+
+  // The PropagationTypeAnnotator is called recursively via
+  // TypeAnnotatorUtil.eraseBoundsThenAnnotate.
+  // This flag prevents infinite recursion.
+  private boolean pause = false;
+  private ArrayDeque<AnnotatedDeclaredType> parents = new ArrayDeque<>();
+
+  public PropagationTypeAnnotator(AnnotatedTypeFactory typeFactory) {
+    super(typeFactory);
+  }
+
+  @Override
+  public void reset() {
+    if (!pause) {
+      // when the PropagationTypeAnnotator is called recursively we don't
+      // want the visit method to reset the list of visited types
+      super.reset();
+    }
+  }
+
+  /*
+   * When pause == true, the PropagationTypeAnnotator caused a recursive call
+   * and there is no need to execute the PropagationTypeAnnotator
+   */
+  @Override
+  protected Void scan(AnnotatedTypeMirror type, Void aVoid) {
+    if (pause) {
+      return null;
+    }
+
+    return super.scan(type, aVoid);
+  }
+
+  /**
+   * Sometimes the underlying type parameters of AnnotatedWildcardTypes are not available on the
+   * wildcards themselves. Instead, record enclosing class to find the type parameter to use as a
+   * backup in visitWildcards.
+   *
+   * @param declaredType type to record
+   */
+  @Override
+  public Void visitDeclared(AnnotatedDeclaredType declaredType, Void aVoid) {
+    if (pause) {
+      return null;
+    }
+    if (declaredType.wasRaw()) {
+      // Copy annotations from the declaration to the wildcards.
+      AnnotatedDeclaredType declaration =
+          (AnnotatedDeclaredType)
+              typeFactory.fromElement(declaredType.getUnderlyingType().asElement());
+      List<AnnotatedTypeMirror> typeArgs = declaredType.getTypeArguments();
+      for (int i = 0; i < typeArgs.size(); i++) {
+        if (typeArgs.get(i).getKind() != TypeKind.WILDCARD
+            || !((AnnotatedWildcardType) typeArgs.get(i)).isUninferredTypeArgument()) {
+          // Sometimes the framework infers a more precise type argument, so just use it.
+          continue;
+        }
+        AnnotatedTypeVariable typeParam =
+            (AnnotatedTypeVariable) declaration.getTypeArguments().get(i);
+        AnnotatedWildcardType wct = (AnnotatedWildcardType) typeArgs.get(i);
+        wct.getExtendsBound().replaceAnnotations(typeParam.getUpperBound().getAnnotations());
+        wct.getSuperBound().replaceAnnotations(typeParam.getLowerBound().getAnnotations());
+        wct.replaceAnnotations(typeParam.getAnnotations());
+      }
+    }
+
+    parents.addFirst(declaredType);
+    super.visitDeclared(declaredType, aVoid);
+    parents.removeFirst();
+    return null;
+  }
+
+  /**
+   * Rather than defaulting the missing bounds of a wildcard, find the bound annotations on the type
+   * parameter it replaced. Place those annotations on the wildcard.
+   *
+   * @param wildcardAtm type to annotate
+   */
+  @Override
+  public Void visitWildcard(AnnotatedWildcardType wildcardAtm, Void aVoid) {
+    if (visitedNodes.containsKey(wildcardAtm) || pause) {
+      return null;
+    }
+    visitedNodes.put(wildcardAtm, null);
+
+    final WildcardType wildcard = (WildcardType) wildcardAtm.getUnderlyingType();
+    Element typeParamElement = TypesUtils.wildcardToTypeParam(wildcard);
+    if (typeParamElement == null) {
+      typeParamElement =
+          parents.isEmpty()
+              ? null
+              : getTypeParamFromEnclosingClass(wildcardAtm, parents.peekFirst());
+    }
+
+    if (typeParamElement != null) {
+      pause = true;
+      AnnotatedTypeVariable typeParam =
+          (AnnotatedTypeVariable) typeFactory.getAnnotatedType(typeParamElement);
+      pause = false;
+
+      final Set<? extends AnnotationMirror> tops =
+          typeFactory.getQualifierHierarchy().getTopAnnotations();
+
+      if (wildcard.isUnbound()) {
+        propagateExtendsBound(wildcardAtm, typeParam, tops);
+        propagateSuperBound(wildcardAtm, typeParam, tops);
+
+      } else if (wildcard.isExtendsBound()) {
+        propagateSuperBound(wildcardAtm, typeParam, tops);
+
+      } else { // is super bound
+        propagateExtendsBound(wildcardAtm, typeParam, tops);
+      }
+    }
+    scan(wildcardAtm.getExtendsBound(), null);
+    scan(wildcardAtm.getSuperBound(), null);
+    return null;
+  }
+
+  private void propagateSuperBound(
+      AnnotatedWildcardType wildcard,
+      AnnotatedTypeVariable typeParam,
+      Set<? extends AnnotationMirror> tops) {
+    applyAnnosFromBound(wildcard.getSuperBound(), typeParam.getLowerBound(), tops);
+  }
+
+  private void propagateExtendsBound(
+      AnnotatedWildcardType wildcard,
+      AnnotatedTypeVariable typeParam,
+      Set<? extends AnnotationMirror> tops) {
+    applyAnnosFromBound(wildcard.getExtendsBound(), typeParam.getUpperBound(), tops);
+  }
+
+  /**
+   * Take the primary annotations from typeParamBound and place them as primary annotations on
+   * wildcard bound.
+   */
+  private void applyAnnosFromBound(
+      final AnnotatedTypeMirror wildcardBound,
+      final AnnotatedTypeMirror typeParamBound,
+      final Set<? extends AnnotationMirror> tops) {
+    // Type variables do not need primary annotations.
+    // The type variable will have annotations placed on its
+    // bounds via its declaration or defaulting rules
+    if (wildcardBound.getKind() == TypeKind.TYPEVAR
+        || typeParamBound.getKind() == TypeKind.TYPEVAR) {
+      return;
+    }
+
+    for (final AnnotationMirror top : tops) {
+      if (wildcardBound.getAnnotationInHierarchy(top) == null) {
+        final AnnotationMirror typeParamAnno = typeParamBound.getAnnotationInHierarchy(top);
+        if (typeParamAnno == null) {
+          throw new BugInCF(
+              StringsPlume.joinLines(
+                  "Missing annotation on type parameter",
+                  "top=" + top,
+                  "wildcardBound=" + wildcardBound,
+                  "typeParamBound=" + typeParamBound));
+        } // else
+        wildcardBound.addAnnotation(typeParamAnno);
+      }
+    }
+  }
+
+  /**
+   * Search parent's type arguments for wildcard. Using the index of wildcard, find the
+   * corresponding type parameter element and return it. Returns null if the wildcard is the result
+   * of substitution and therefore not in the list of type arguments.
+   *
+   * @param wildcard the wildcard type whose corresponding type argument to determine
+   * @param parent the type that may have a type argument corresponding to {@code wildcard}
+   * @return the type argument in {@code parent} that corresponds to {@code wildcard}
+   */
+  private Element getTypeParamFromEnclosingClass(
+      final @FindDistinct AnnotatedWildcardType wildcard, final AnnotatedDeclaredType parent) {
+    Integer wildcardIndex = null;
+    int currentIndex = 0;
+    for (AnnotatedTypeMirror typeArg : parent.getTypeArguments()) {
+      // the only cases in which the wildcard is not one of the type arguments are cases in
+      // which they should have been replaced by capture
+      if (typeArg == wildcard) {
+        wildcardIndex = currentIndex;
+        break;
+      }
+      currentIndex += 1;
+    }
+
+    if (wildcardIndex != null) {
+      final TypeElement typeElement =
+          (TypeElement)
+              typeFactory.getProcessingEnv().getTypeUtils().asElement(parent.getUnderlyingType());
+
+      return typeElement.getTypeParameters().get(wildcardIndex);
+    }
+
+    return null;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/TypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/TypeAnnotator.java
new file mode 100644
index 0000000..a9a6608
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/TypeAnnotator.java
@@ -0,0 +1,38 @@
+package org.checkerframework.framework.type.typeannotator;
+
+import javax.lang.model.element.Element;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
+
+/**
+ * {@link TypeAnnotator} is an abstract AnnotatedTypeScanner to be used with {@link
+ * ListTypeAnnotator}.
+ *
+ * @see org.checkerframework.framework.type.typeannotator.ListTypeAnnotator
+ * @see org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator
+ * @see DefaultForTypeAnnotator
+ */
+public abstract class TypeAnnotator extends AnnotatedTypeScanner<Void, Void> {
+
+  protected final AnnotatedTypeFactory typeFactory;
+
+  protected TypeAnnotator(AnnotatedTypeFactory typeFactory) {
+    this.typeFactory = typeFactory;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>If this method adds annotations to the type of method parameters, then {@link
+   * org.checkerframework.framework.type.GenericAnnotatedTypeFactory#addComputedTypeAnnotations(Element,
+   * AnnotatedTypeMirror)} should be overriden and the same annotations added to the type of
+   * elements with kind {@link javax.lang.model.element.ElementKind#PARAMETER}. Likewise for return
+   * types.
+   */
+  @Override
+  public Void visitExecutable(AnnotatedExecutableType method, Void aVoid) {
+    return super.visitExecutable(method, aVoid);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AbstractAtmComboVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AbstractAtmComboVisitor.java
new file mode 100644
index 0000000..0ce0575
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AbstractAtmComboVisitor.java
@@ -0,0 +1,653 @@
+package org.checkerframework.framework.type.visitor;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.util.AtmCombo;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Implements all methods from AtmComboVisitor. By default all methods throw an exception. Implement
+ * only those methods you expect to be called on your subclass.
+ *
+ * <p>This class does no traversal.
+ */
+public abstract class AbstractAtmComboVisitor<RETURN_TYPE, PARAM>
+    implements AtmComboVisitor<RETURN_TYPE, PARAM> {
+
+  /**
+   * Formats type1, type2 and param into an error message used by all methods of
+   * AbstractAtmComboVisitor that are not overridden. Normally, this method should indicate that the
+   * given method (and therefore the given pair of type mirror classes) is not supported by this
+   * class.
+   *
+   * @param type1 the first AnnotatedTypeMirror parameter to the visit method called
+   * @param type2 the second AnnotatedTypeMirror parameter to the visit method called
+   * @param param subtype specific parameter passed to every visit method
+   * @return an error message
+   */
+  protected abstract String defaultErrorMessage(
+      AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param);
+
+  /**
+   * Called by the default implementation of every AbstractAtmComboVisitor visit method. This
+   * methodnS issues a runtime exception by default. In general, it should handle the case where a
+   * visit method has been called with a pair of type mirrors that should never be passed to this
+   * particular visitor.
+   *
+   * @param type1 the first AnnotatedTypeMirror parameter to the visit method called
+   * @param type2 the second AnnotatedTypeMirror parameter to the visit method called
+   * @param param subtype specific parameter passed to every visit method
+   */
+  protected RETURN_TYPE defaultAction(
+      AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) {
+    throw new BugInCF(defaultErrorMessage(type1, type2, param));
+  }
+
+  /**
+   * Dispatches to a more specific {@code visit*} method.
+   *
+   * @param type1 the first type to visit
+   * @param type2 the second type to visit
+   * @param param a value passed to every visit method
+   * @return the result of calling the more specific {@code visit*} method
+   */
+  public RETURN_TYPE visit(
+      final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2, PARAM param) {
+    return AtmCombo.accept(type1, type2, param, this);
+  }
+
+  @Override
+  public RETURN_TYPE visitArray_Array(
+      AnnotatedArrayType type1, AnnotatedArrayType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitArray_Declared(
+      AnnotatedArrayType type1, AnnotatedDeclaredType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitArray_Executable(
+      AnnotatedArrayType type1, AnnotatedExecutableType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitArray_Intersection(
+      AnnotatedArrayType type1, AnnotatedIntersectionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitArray_None(AnnotatedArrayType type1, AnnotatedNoType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitArray_Null(
+      AnnotatedArrayType type1, AnnotatedNullType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitArray_Primitive(
+      AnnotatedArrayType type1, AnnotatedPrimitiveType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitArray_Typevar(
+      AnnotatedArrayType type1, AnnotatedTypeVariable type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitArray_Union(
+      AnnotatedArrayType type1, AnnotatedUnionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitArray_Wildcard(
+      AnnotatedArrayType type1, AnnotatedWildcardType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitDeclared_Array(
+      AnnotatedDeclaredType type1, AnnotatedArrayType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitDeclared_Declared(
+      AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitDeclared_Executable(
+      AnnotatedDeclaredType type1, AnnotatedExecutableType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitDeclared_Intersection(
+      AnnotatedDeclaredType type1, AnnotatedIntersectionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitDeclared_None(
+      AnnotatedDeclaredType type1, AnnotatedNoType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitDeclared_Null(
+      AnnotatedDeclaredType type1, AnnotatedNullType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitDeclared_Primitive(
+      AnnotatedDeclaredType type1, AnnotatedPrimitiveType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitDeclared_Typevar(
+      AnnotatedDeclaredType type1, AnnotatedTypeVariable type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitDeclared_Union(
+      AnnotatedDeclaredType type1, AnnotatedUnionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitDeclared_Wildcard(
+      AnnotatedDeclaredType type1, AnnotatedWildcardType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitExecutable_Array(
+      AnnotatedExecutableType type1, AnnotatedArrayType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitExecutable_Declared(
+      AnnotatedExecutableType type1, AnnotatedDeclaredType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitExecutable_Executable(
+      AnnotatedExecutableType type1, AnnotatedExecutableType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitExecutable_Intersection(
+      AnnotatedExecutableType type1, AnnotatedIntersectionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitExecutable_None(
+      AnnotatedExecutableType type1, AnnotatedNoType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitExecutable_Null(
+      AnnotatedExecutableType type1, AnnotatedNullType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitExecutable_Primitive(
+      AnnotatedExecutableType type1, AnnotatedPrimitiveType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitExecutable_Typevar(
+      AnnotatedExecutableType type1, AnnotatedTypeVariable type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitExecutable_Union(
+      AnnotatedExecutableType type1, AnnotatedUnionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitExecutable_Wildcard(
+      AnnotatedExecutableType type1, AnnotatedWildcardType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitIntersection_Array(
+      AnnotatedIntersectionType type1, AnnotatedArrayType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitIntersection_Declared(
+      AnnotatedIntersectionType type1, AnnotatedDeclaredType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitIntersection_Executable(
+      AnnotatedIntersectionType type1, AnnotatedExecutableType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitIntersection_Intersection(
+      AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitIntersection_None(
+      AnnotatedIntersectionType type1, AnnotatedNoType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitIntersection_Null(
+      AnnotatedIntersectionType type1, AnnotatedNullType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitIntersection_Primitive(
+      AnnotatedIntersectionType type1, AnnotatedPrimitiveType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitIntersection_Typevar(
+      AnnotatedIntersectionType type1, AnnotatedTypeVariable type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitIntersection_Union(
+      AnnotatedIntersectionType type1, AnnotatedUnionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitIntersection_Wildcard(
+      AnnotatedIntersectionType type1, AnnotatedWildcardType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNone_Array(AnnotatedNoType type1, AnnotatedArrayType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNone_Declared(
+      AnnotatedNoType type1, AnnotatedDeclaredType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNone_Executable(
+      AnnotatedNoType type1, AnnotatedExecutableType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNone_Intersection(
+      AnnotatedNoType type1, AnnotatedIntersectionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNone_None(AnnotatedNoType type1, AnnotatedNoType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNone_Null(AnnotatedNoType type1, AnnotatedNullType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNone_Primitive(
+      AnnotatedNoType type1, AnnotatedPrimitiveType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNone_Union(AnnotatedNoType type1, AnnotatedUnionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNone_Wildcard(
+      AnnotatedNoType type1, AnnotatedWildcardType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNull_Array(
+      AnnotatedNullType type1, AnnotatedArrayType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNull_Declared(
+      AnnotatedNullType type1, AnnotatedDeclaredType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNull_Executable(
+      AnnotatedNullType type1, AnnotatedExecutableType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNull_Intersection(
+      AnnotatedNullType type1, AnnotatedIntersectionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNull_None(AnnotatedNullType type1, AnnotatedNoType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNull_Null(AnnotatedNullType type1, AnnotatedNullType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNull_Primitive(
+      AnnotatedNullType type1, AnnotatedPrimitiveType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNull_Typevar(
+      AnnotatedNullType type1, AnnotatedTypeVariable type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNull_Union(
+      AnnotatedNullType type1, AnnotatedUnionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNull_Wildcard(
+      AnnotatedNullType type1, AnnotatedWildcardType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitPrimitive_Array(
+      AnnotatedPrimitiveType type1, AnnotatedArrayType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitPrimitive_Declared(
+      AnnotatedPrimitiveType type1, AnnotatedDeclaredType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitPrimitive_Executable(
+      AnnotatedPrimitiveType type1, AnnotatedExecutableType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitPrimitive_Intersection(
+      AnnotatedPrimitiveType type1, AnnotatedIntersectionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitPrimitive_None(
+      AnnotatedPrimitiveType type1, AnnotatedNoType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitPrimitive_Null(
+      AnnotatedPrimitiveType type1, AnnotatedNullType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitPrimitive_Primitive(
+      AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitPrimitive_Typevar(
+      AnnotatedPrimitiveType type1, AnnotatedTypeVariable type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitPrimitive_Union(
+      AnnotatedPrimitiveType type1, AnnotatedUnionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitPrimitive_Wildcard(
+      AnnotatedPrimitiveType type1, AnnotatedWildcardType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitUnion_Array(
+      AnnotatedUnionType type1, AnnotatedArrayType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitUnion_Declared(
+      AnnotatedUnionType type1, AnnotatedDeclaredType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitUnion_Executable(
+      AnnotatedUnionType type1, AnnotatedExecutableType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitUnion_Intersection(
+      AnnotatedUnionType type1, AnnotatedIntersectionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitUnion_None(AnnotatedUnionType type1, AnnotatedNoType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitUnion_Null(
+      AnnotatedUnionType type1, AnnotatedNullType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitUnion_Primitive(
+      AnnotatedUnionType type1, AnnotatedPrimitiveType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitUnion_Typevar(
+      AnnotatedUnionType type1, AnnotatedTypeVariable type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitUnion_Union(
+      AnnotatedUnionType type1, AnnotatedUnionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitUnion_Wildcard(
+      AnnotatedUnionType type1, AnnotatedWildcardType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitTypevar_Array(
+      AnnotatedTypeVariable type1, AnnotatedArrayType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitTypevar_Declared(
+      AnnotatedTypeVariable type1, AnnotatedDeclaredType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitTypevar_Executable(
+      AnnotatedTypeVariable type1, AnnotatedExecutableType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitTypevar_Intersection(
+      AnnotatedTypeVariable type1, AnnotatedIntersectionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitTypevar_None(
+      AnnotatedTypeVariable type1, AnnotatedNoType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitTypevar_Null(
+      AnnotatedTypeVariable type1, AnnotatedNullType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitTypevar_Primitive(
+      AnnotatedTypeVariable type1, AnnotatedPrimitiveType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitTypevar_Typevar(
+      AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitTypevar_Union(
+      AnnotatedTypeVariable type1, AnnotatedUnionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitTypevar_Wildcard(
+      AnnotatedTypeVariable type1, AnnotatedWildcardType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitWildcard_Array(
+      AnnotatedWildcardType type1, AnnotatedArrayType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitWildcard_Declared(
+      AnnotatedWildcardType type1, AnnotatedDeclaredType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitWildcard_Executable(
+      AnnotatedWildcardType type1, AnnotatedExecutableType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitWildcard_Intersection(
+      AnnotatedWildcardType type1, AnnotatedIntersectionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitWildcard_None(
+      AnnotatedWildcardType type1, AnnotatedNoType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitWildcard_Null(
+      AnnotatedWildcardType type1, AnnotatedNullType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitWildcard_Primitive(
+      AnnotatedWildcardType type1, AnnotatedPrimitiveType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitWildcard_Typevar(
+      AnnotatedWildcardType type1, AnnotatedTypeVariable type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitWildcard_Union(
+      AnnotatedWildcardType type1, AnnotatedUnionType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+
+  @Override
+  public RETURN_TYPE visitWildcard_Wildcard(
+      AnnotatedWildcardType type1, AnnotatedWildcardType type2, PARAM param) {
+    return defaultAction(type1, type2, param);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java
new file mode 100644
index 0000000..a2a4b08
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java
@@ -0,0 +1,77 @@
+package org.checkerframework.framework.type.visitor;
+
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+
+/** Changes each parameter type to be the GLB of the parameter type and visited type. */
+public class AnnotatedTypeCombiner extends DoubleAnnotatedTypeScanner<Void> {
+
+  /**
+   * Combines all annotations from {@code from} and {@code to} into {@code to} using the GLB.
+   *
+   * @param from the annotated type mirror from which to take annotations
+   * @param to the annotated type mirror into which annotations should be combined
+   * @param hierarchy the top type of the hierarchy whose annotations should be combined
+   */
+  @SuppressWarnings("interning:not.interned") // assertion
+  public static void combine(
+      final AnnotatedTypeMirror from,
+      final AnnotatedTypeMirror to,
+      final QualifierHierarchy hierarchy) {
+    if (from == to) {
+      throw new BugInCF("from == to: %s", from);
+    }
+    new AnnotatedTypeCombiner(hierarchy).visit(from, to);
+  }
+
+  /** The hierarchy used to compute the GLB. */
+  private final QualifierHierarchy hierarchy;
+
+  /**
+   * Create an AnnotatedTypeCombiner.
+   *
+   * @param hierarchy the hierarchy used to the compute the GLB
+   */
+  public AnnotatedTypeCombiner(final QualifierHierarchy hierarchy) {
+    this.hierarchy = hierarchy;
+  }
+
+  @Override
+  @SuppressWarnings("interning:not.interned") // assertion
+  protected Void defaultAction(AnnotatedTypeMirror one, AnnotatedTypeMirror two) {
+    assert one != two;
+    if (one != null && two != null) {
+      combineAnnotations(one, two);
+    }
+    return null;
+  }
+
+  /**
+   * Computes the greatest lower bound of each set of annotations shared by from and to, and
+   * replaces the annotations in to with the result.
+   *
+   * @param from the first set of annotations
+   * @param to the second set of annotations. This is modified by side-effect to hold the result.
+   */
+  protected void combineAnnotations(final AnnotatedTypeMirror from, final AnnotatedTypeMirror to) {
+
+    Set<AnnotationMirror> combinedAnnotations = AnnotationUtils.createAnnotationSet();
+
+    for (AnnotationMirror top : hierarchy.getTopAnnotations()) {
+      AnnotationMirror aFrom = from.getAnnotationInHierarchy(top);
+      AnnotationMirror aTo = to.getAnnotationInHierarchy(top);
+      if (aFrom != null && aTo != null) {
+        combinedAnnotations.add(hierarchy.greatestLowerBound(aFrom, aTo));
+      } else if (aFrom != null) {
+        combinedAnnotations.add(aFrom);
+      } else if (aTo != null) {
+        combinedAnnotations.add(aTo);
+      }
+    }
+    to.replaceAnnotations(combinedAnnotations);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeScanner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeScanner.java
new file mode 100644
index 0000000..fcbd03b
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeScanner.java
@@ -0,0 +1,363 @@
+package org.checkerframework.framework.type.visitor;
+
+import java.util.IdentityHashMap;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+
+/**
+ * An {@code AnnotatedTypeScanner} visits an {@link AnnotatedTypeMirror} and all of its child {@link
+ * AnnotatedTypeMirror} and preforms some function depending on the kind of type. (By contrast, a
+ * {@link SimpleAnnotatedTypeScanner} scans an {@link AnnotatedTypeMirror} and performs the
+ * <em>same</em> function regardless of the kind of type.) The function returns some value with type
+ * {@code R} and takes an argument of type {@code P}. If the function does not return any value,
+ * then {@code R} should be {@link Void}. If the function takes no additional argument, then {@code
+ * P} should be {@link Void}.
+ *
+ * <p>The default implementation of the visitAnnotatedTypeMirror methods will determine a result as
+ * follows:
+ *
+ * <ul>
+ *   <li>If the type being visited has no children, the {@link #defaultResult} is returned.
+ *   <li>If the type being visited has one child, the result of visiting the child type is returned.
+ *   <li>If the type being visited has more than one child, the result is determined by visiting
+ *       each child in turn, and then combining the result of each with the cumulative result so
+ *       far, as determined by the {@link #reduce} method.
+ * </ul>
+ *
+ * The {@link #reduce} method combines the results of visiting child types. It can be specified by
+ * passing a {@link Reduce} object to one of the constructors or by overriding the method directly.
+ * If it is not otherwise specified, then reduce returns the first result if it is not null;
+ * otherwise, the second result is returned. If the default result is nonnull and reduce never
+ * returns null, then both parameters passed to reduce will be nonnull.
+ *
+ * <p>When overriding a visitAnnotatedTypeMirror method, the returned expression should be {@code
+ * reduce(super.visitAnnotatedTypeMirror(type, parameter), result)} so that the whole type is
+ * scanned.
+ *
+ * <p>To begin scanning a type call {@link #visit(AnnotatedTypeMirror, Object)} or (to pass {@code
+ * null} as the last parameter) call {@link #visit(AnnotatedTypeMirror)}. Both methods call {@link
+ * #reset()}.
+ *
+ * <p>Here is an example of a scanner that counts the number of {@link AnnotatedTypeVariable} in an
+ * AnnotatedTypeMirror.
+ *
+ * <pre>
+ * {@code class CountTypeVariable extends AnnotatedTypeScanner<Integer, Void>} {
+ *    public CountTypeVariable() {
+ *        super(Integer::sum, 0);
+ *    }
+ *
+ *    {@literal @}Override
+ *     public Integer visitTypeVariable(AnnotatedTypeVariable type, Void p) {
+ *         return reduce(super.visitTypeVariable(type, p), 1);
+ *     }
+ * }
+ * </pre>
+ *
+ * An {@code AnnotatedTypeScanner} keeps a map of visited types, in order to prevent infinite
+ * recursion on recursive types. Because of this map, you should not create a new {@code
+ * AnnotatedTypeScanner} for each use. Instead, store an {@code AnnotatedTypeScanner} as a field in
+ * the {@link org.checkerframework.framework.type.AnnotatedTypeFactory} or {@link
+ * org.checkerframework.common.basetype.BaseTypeVisitor} of the checker.
+ *
+ * <p>Below is an example of how to use {@code CountTypeVariable}.
+ *
+ * <pre>{@code
+ * private final CountTypeVariable countTypeVariable = new CountTypeVariable();
+ *
+ * void method(AnnotatedTypeMirror type) {
+ *     int count = countTypeVariable.visit(type);
+ * }
+ * }</pre>
+ *
+ * @param <R> the return type of this visitor's methods. Use Void for visitors that do not need to
+ *     return results.
+ * @param <P> the type of the additional parameter to this visitor's methods. Use Void for visitors
+ *     that do not need an additional parameter.
+ */
+public abstract class AnnotatedTypeScanner<R, P> implements AnnotatedTypeVisitor<R, P> {
+
+  /**
+   * Reduces two results into a single result.
+   *
+   * @param <R> the result type
+   */
+  @FunctionalInterface
+  public interface Reduce<R> {
+
+    /**
+     * Returns the combination of two results.
+     *
+     * @param r1 the first result
+     * @param r2 the second result
+     * @return the combination of the two results
+     */
+    R reduce(R r1, R r2);
+  }
+
+  /** The reduce function to use. */
+  protected final Reduce<R> reduceFunction;
+
+  /** The result to return if no other result is provided. It should be immutable. */
+  protected final R defaultResult;
+
+  /**
+   * Constructs an AnnotatedTypeScanner with the given reduce function. If {@code reduceFunction} is
+   * null, then the reduce function returns the first result if it is nonnull; otherwise the second
+   * result is returned.
+   *
+   * @param reduceFunction function used to combine two results
+   * @param defaultResult the result to return if a visit type method is not overridden; it should
+   *     be immutable
+   */
+  protected AnnotatedTypeScanner(@Nullable Reduce<R> reduceFunction, R defaultResult) {
+    if (reduceFunction == null) {
+      this.reduceFunction = (r1, r2) -> r1 == null ? r2 : r1;
+    } else {
+      this.reduceFunction = reduceFunction;
+    }
+    this.defaultResult = defaultResult;
+  }
+
+  /**
+   * Constructs an AnnotatedTypeScanner with the given reduce function. If {@code reduceFunction} is
+   * null, then the reduce function returns the first result if it is nonnull; otherwise the second
+   * result is returned. The default result is {@code null}
+   *
+   * @param reduceFunction function used to combine two results
+   */
+  protected AnnotatedTypeScanner(@Nullable Reduce<R> reduceFunction) {
+    this(reduceFunction, null);
+  }
+
+  /**
+   * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it is
+   * nonnull; otherwise the second result is returned.
+   *
+   * @param defaultResult the result to return if a visit type method is not overridden; it should
+   *     be immutable
+   */
+  protected AnnotatedTypeScanner(R defaultResult) {
+    this(null, defaultResult);
+  }
+
+  /**
+   * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it is
+   * nonnull; otherwise the second result is returned. The default result is {@code null}.
+   */
+  protected AnnotatedTypeScanner() {
+    this(null, null);
+  }
+
+  // To prevent infinite loops
+  protected final IdentityHashMap<AnnotatedTypeMirror, R> visitedNodes = new IdentityHashMap<>();
+
+  /**
+   * Reset the scanner to allow reuse of the same instance. Subclasses should override this method
+   * to clear their additional state; they must call the super implementation.
+   */
+  public void reset() {
+    visitedNodes.clear();
+  }
+
+  /**
+   * Calls {@link #reset()} and then scans {@code type} using null as the parameter.
+   *
+   * @param type type to scan
+   * @return result of scanning {@code type}
+   */
+  @Override
+  public final R visit(AnnotatedTypeMirror type) {
+    return visit(type, null);
+  }
+
+  /**
+   * Calls {@link #reset()} and then scans {@code type} using {@code p} as the parameter.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return result of scanning {@code type}
+   */
+  @Override
+  public final R visit(AnnotatedTypeMirror type, P p) {
+    reset();
+    return scan(type, p);
+  }
+
+  /**
+   * Scan {@code type} by calling {@code type.accept(this, p)}; this method may be overridden by
+   * subclasses.
+   *
+   * @param type type to scan
+   * @param p the parameter to use
+   * @return the result of visiting {@code type}
+   */
+  protected R scan(AnnotatedTypeMirror type, P p) {
+    return type.accept(this, p);
+  }
+
+  /**
+   * Scan all the types and returns the reduced result.
+   *
+   * @param types types to scan
+   * @param p the parameter to use
+   * @return the reduced result of scanning all the types
+   */
+  protected R scan(@Nullable Iterable<? extends AnnotatedTypeMirror> types, P p) {
+    if (types == null) {
+      return defaultResult;
+    }
+    R r = defaultResult;
+    boolean first = true;
+    for (AnnotatedTypeMirror type : types) {
+      r = (first ? scan(type, p) : scanAndReduce(type, p, r));
+      first = false;
+    }
+    return r;
+  }
+
+  protected R scanAndReduce(Iterable<? extends AnnotatedTypeMirror> types, P p, R r) {
+    return reduce(scan(types, p), r);
+  }
+
+  /**
+   * Scans {@code type} with the parameter {@code p} and reduces the result with {@code r}.
+   *
+   * @param type type to scan
+   * @param p parameter to use for when scanning {@code type}
+   * @param r result to combine with the result of scanning {@code type}
+   * @return the combination of {@code r} with the result of scanning {@code type}
+   */
+  protected R scanAndReduce(AnnotatedTypeMirror type, P p, R r) {
+    return reduce(scan(type, p), r);
+  }
+
+  /**
+   * Combines {@code r1} and {@code r2} and returns the result. The default implementation returns
+   * {@code r1} if it is not null; otherwise, it returns {@code r2}.
+   *
+   * @param r1 a result of scan, nonnull if {@link #defaultResult} is nonnull and this method never
+   *     returns null
+   * @param r2 a result of scan, nonnull if {@link #defaultResult} is nonnull and this method never
+   *     returns null
+   * @return the combination of {@code r1} and {@code r2}
+   */
+  protected R reduce(R r1, R r2) {
+    return reduceFunction.reduce(r1, r2);
+  }
+
+  @Override
+  public R visitDeclared(AnnotatedDeclaredType type, P p) {
+    // Only declared types with type arguments might be recursive, so only store those.
+    boolean shouldStoreType = !type.getTypeArguments().isEmpty();
+    if (shouldStoreType && visitedNodes.containsKey(type)) {
+      return visitedNodes.get(type);
+    }
+    if (shouldStoreType) {
+      visitedNodes.put(type, defaultResult);
+    }
+    R r = defaultResult;
+    if (type.getEnclosingType() != null) {
+      r = scan(type.getEnclosingType(), p);
+      if (shouldStoreType) {
+        visitedNodes.put(type, r);
+      }
+    }
+    r = scanAndReduce(type.getTypeArguments(), p, r);
+    if (shouldStoreType) {
+      visitedNodes.put(type, r);
+    }
+    return r;
+  }
+
+  @Override
+  public R visitIntersection(AnnotatedIntersectionType type, P p) {
+    if (visitedNodes.containsKey(type)) {
+      return visitedNodes.get(type);
+    }
+    visitedNodes.put(type, defaultResult);
+    R r = scan(type.getBounds(), p);
+    visitedNodes.put(type, r);
+    return r;
+  }
+
+  @Override
+  public R visitUnion(AnnotatedUnionType type, P p) {
+    if (visitedNodes.containsKey(type)) {
+      return visitedNodes.get(type);
+    }
+    visitedNodes.put(type, defaultResult);
+    R r = scan(type.getAlternatives(), p);
+    visitedNodes.put(type, r);
+    return r;
+  }
+
+  @Override
+  public R visitArray(AnnotatedArrayType type, P p) {
+    R r = scan(type.getComponentType(), p);
+    return r;
+  }
+
+  @Override
+  public R visitExecutable(AnnotatedExecutableType type, P p) {
+    R r = scan(type.getReturnType(), p);
+    if (type.getReceiverType() != null) {
+      r = scanAndReduce(type.getReceiverType(), p, r);
+    }
+    r = scanAndReduce(type.getParameterTypes(), p, r);
+    r = scanAndReduce(type.getThrownTypes(), p, r);
+    r = scanAndReduce(type.getTypeVariables(), p, r);
+    return r;
+  }
+
+  @Override
+  public R visitTypeVariable(AnnotatedTypeVariable type, P p) {
+    if (visitedNodes.containsKey(type)) {
+      return visitedNodes.get(type);
+    }
+    visitedNodes.put(type, defaultResult);
+    R r = scan(type.getLowerBound(), p);
+    visitedNodes.put(type, r);
+    r = scanAndReduce(type.getUpperBound(), p, r);
+    visitedNodes.put(type, r);
+    return r;
+  }
+
+  @Override
+  public R visitNoType(AnnotatedNoType type, P p) {
+    return defaultResult;
+  }
+
+  @Override
+  public R visitNull(AnnotatedNullType type, P p) {
+    return defaultResult;
+  }
+
+  @Override
+  public R visitPrimitive(AnnotatedPrimitiveType type, P p) {
+    return defaultResult;
+  }
+
+  @Override
+  public R visitWildcard(AnnotatedWildcardType type, P p) {
+    if (visitedNodes.containsKey(type)) {
+      return visitedNodes.get(type);
+    }
+    visitedNodes.put(type, defaultResult);
+    R r = scan(type.getExtendsBound(), p);
+    visitedNodes.put(type, r);
+    r = scanAndReduce(type.getSuperBound(), p, r);
+    visitedNodes.put(type, r);
+    return r;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeVisitor.java
new file mode 100644
index 0000000..60d7261
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeVisitor.java
@@ -0,0 +1,147 @@
+package org.checkerframework.framework.type.visitor;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+
+/**
+ * A visitor of annotated types, in the style of the visitor design pattern.
+ *
+ * <p>Classes implementing this interface are used to operate on a type when the kind of type is
+ * unknown at compile time. When a visitor is passed to a type's accept method, the visitXYZ method
+ * most applicable to that type is invoked.
+ *
+ * <p>Classes implementing this interface may or may not throw a NullPointerException if the
+ * additional parameter p is {@code null}; see documentation of the implementing class for details.
+ *
+ * @param <R> the return type of this visitor's methods. Use Void for visitors that do not need to
+ *     return results.
+ * @param <P> the type of the additional parameter to this visitor's methods. Use Void for visitors
+ *     that do not need an additional parameter.
+ */
+public interface AnnotatedTypeVisitor<R, P> {
+
+  /**
+   * A Convenience method equivalent to {@code v.visit(t, null)}.
+   *
+   * @param type the type to visit
+   * @return a visitor-specified result
+   */
+  public R visit(AnnotatedTypeMirror type);
+
+  /**
+   * Visits a type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  public R visit(AnnotatedTypeMirror type, P p);
+
+  /**
+   * Visits a declared type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  //    public R visitType(AnnotatedTypeMirror type, P p);
+
+  /**
+   * Visits a declared type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  public R visitDeclared(AnnotatedDeclaredType type, P p);
+
+  /**
+   * Visits an intersection type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  public R visitIntersection(AnnotatedIntersectionType type, P p);
+
+  /**
+   * Visits an union type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  public R visitUnion(AnnotatedUnionType type, P p);
+
+  /**
+   * Visits an executable type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  public R visitExecutable(AnnotatedExecutableType type, P p);
+
+  /**
+   * Visits an array type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  public R visitArray(AnnotatedArrayType type, P p);
+
+  /**
+   * Visits a type variable.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  public R visitTypeVariable(AnnotatedTypeVariable type, P p);
+
+  /**
+   * Visits a primitive type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  public R visitPrimitive(AnnotatedPrimitiveType type, P p);
+
+  /**
+   * Visits NoType type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  public R visitNoType(AnnotatedNoType type, P p);
+
+  /**
+   * Visits a {@code null} type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  public R visitNull(AnnotatedNullType type, P p);
+
+  /**
+   * Visits a wildcard type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  public R visitWildcard(AnnotatedWildcardType type, P p);
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java
new file mode 100644
index 0000000..951ffec
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java
@@ -0,0 +1,321 @@
+package org.checkerframework.framework.type.visitor;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+
+/**
+ * Visitor interface for all pair-wise combinations of AnnotatedTypeMirrors. See AtmCombo, it
+ * enumerates all possible combinations and provides an "accept" method used to call AtmComboVisitor
+ * visit methods.
+ *
+ * @param <RETURN_TYPE> the type returned by each visit method
+ * @param <PARAM> the type of a single value that is passed to every visit method. It can act as
+ *     global state.
+ */
+public interface AtmComboVisitor<RETURN_TYPE, PARAM> {
+
+  public RETURN_TYPE visitArray_Array(
+      AnnotatedArrayType subtype, AnnotatedArrayType supertype, PARAM param);
+
+  public RETURN_TYPE visitArray_Declared(
+      AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, PARAM param);
+
+  public RETURN_TYPE visitArray_Executable(
+      AnnotatedArrayType subtype, AnnotatedExecutableType supertype, PARAM param);
+
+  public RETURN_TYPE visitArray_Intersection(
+      AnnotatedArrayType subtype, AnnotatedIntersectionType supertype, PARAM param);
+
+  public RETURN_TYPE visitArray_None(
+      AnnotatedArrayType subtype, AnnotatedNoType supertype, PARAM param);
+
+  public RETURN_TYPE visitArray_Null(
+      AnnotatedArrayType subtype, AnnotatedNullType supertype, PARAM param);
+
+  public RETURN_TYPE visitArray_Primitive(
+      AnnotatedArrayType subtype, AnnotatedPrimitiveType supertype, PARAM param);
+
+  public RETURN_TYPE visitArray_Typevar(
+      AnnotatedArrayType subtype, AnnotatedTypeVariable supertype, PARAM param);
+
+  public RETURN_TYPE visitArray_Union(
+      AnnotatedArrayType subtype, AnnotatedUnionType supertype, PARAM param);
+
+  public RETURN_TYPE visitArray_Wildcard(
+      AnnotatedArrayType subtype, AnnotatedWildcardType supertype, PARAM param);
+
+  public RETURN_TYPE visitDeclared_Array(
+      AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, PARAM param);
+
+  public RETURN_TYPE visitDeclared_Declared(
+      AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, PARAM param);
+
+  public RETURN_TYPE visitDeclared_Executable(
+      AnnotatedDeclaredType subtype, AnnotatedExecutableType supertype, PARAM param);
+
+  public RETURN_TYPE visitDeclared_Intersection(
+      AnnotatedDeclaredType subtype, AnnotatedIntersectionType supertype, PARAM param);
+
+  public RETURN_TYPE visitDeclared_None(
+      AnnotatedDeclaredType subtype, AnnotatedNoType supertype, PARAM param);
+
+  public RETURN_TYPE visitDeclared_Null(
+      AnnotatedDeclaredType subtype, AnnotatedNullType supertype, PARAM param);
+
+  public RETURN_TYPE visitDeclared_Primitive(
+      AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, PARAM param);
+
+  public RETURN_TYPE visitDeclared_Typevar(
+      AnnotatedDeclaredType subtype, AnnotatedTypeVariable supertype, PARAM param);
+
+  public RETURN_TYPE visitDeclared_Union(
+      AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, PARAM param);
+
+  public RETURN_TYPE visitDeclared_Wildcard(
+      AnnotatedDeclaredType subtype, AnnotatedWildcardType supertype, PARAM param);
+
+  public RETURN_TYPE visitExecutable_Array(
+      AnnotatedExecutableType subtype, AnnotatedArrayType supertype, PARAM param);
+
+  public RETURN_TYPE visitExecutable_Declared(
+      AnnotatedExecutableType subtype, AnnotatedDeclaredType supertype, PARAM param);
+
+  public RETURN_TYPE visitExecutable_Executable(
+      AnnotatedExecutableType subtype, AnnotatedExecutableType supertype, PARAM param);
+
+  public RETURN_TYPE visitExecutable_Intersection(
+      AnnotatedExecutableType subtype, AnnotatedIntersectionType supertype, PARAM param);
+
+  public RETURN_TYPE visitExecutable_None(
+      AnnotatedExecutableType subtype, AnnotatedNoType supertype, PARAM param);
+
+  public RETURN_TYPE visitExecutable_Null(
+      AnnotatedExecutableType subtype, AnnotatedNullType supertype, PARAM param);
+
+  public RETURN_TYPE visitExecutable_Primitive(
+      AnnotatedExecutableType subtype, AnnotatedPrimitiveType supertype, PARAM param);
+
+  public RETURN_TYPE visitExecutable_Typevar(
+      AnnotatedExecutableType subtype, AnnotatedTypeVariable supertype, PARAM param);
+
+  public RETURN_TYPE visitExecutable_Union(
+      AnnotatedExecutableType subtype, AnnotatedUnionType supertype, PARAM param);
+
+  public RETURN_TYPE visitExecutable_Wildcard(
+      AnnotatedExecutableType subtype, AnnotatedWildcardType supertype, PARAM param);
+
+  public RETURN_TYPE visitIntersection_Array(
+      AnnotatedIntersectionType subtype, AnnotatedArrayType supertype, PARAM param);
+
+  public RETURN_TYPE visitIntersection_Declared(
+      AnnotatedIntersectionType subtype, AnnotatedDeclaredType supertype, PARAM param);
+
+  public RETURN_TYPE visitIntersection_Executable(
+      AnnotatedIntersectionType subtype, AnnotatedExecutableType supertype, PARAM param);
+
+  public RETURN_TYPE visitIntersection_Intersection(
+      AnnotatedIntersectionType subtype, AnnotatedIntersectionType supertype, PARAM param);
+
+  public RETURN_TYPE visitIntersection_None(
+      AnnotatedIntersectionType subtype, AnnotatedNoType supertype, PARAM param);
+
+  public RETURN_TYPE visitIntersection_Null(
+      AnnotatedIntersectionType subtype, AnnotatedNullType supertype, PARAM param);
+
+  public RETURN_TYPE visitIntersection_Primitive(
+      AnnotatedIntersectionType subtype, AnnotatedPrimitiveType supertype, PARAM param);
+
+  public RETURN_TYPE visitIntersection_Typevar(
+      AnnotatedIntersectionType subtype, AnnotatedTypeVariable supertype, PARAM param);
+
+  public RETURN_TYPE visitIntersection_Union(
+      AnnotatedIntersectionType subtype, AnnotatedUnionType supertype, PARAM param);
+
+  public RETURN_TYPE visitIntersection_Wildcard(
+      AnnotatedIntersectionType subtype, AnnotatedWildcardType supertype, PARAM param);
+
+  public RETURN_TYPE visitNone_Array(
+      AnnotatedNoType subtype, AnnotatedArrayType supertype, PARAM param);
+
+  public RETURN_TYPE visitNone_Declared(
+      AnnotatedNoType subtype, AnnotatedDeclaredType supertype, PARAM param);
+
+  public RETURN_TYPE visitNone_Executable(
+      AnnotatedNoType subtype, AnnotatedExecutableType supertype, PARAM param);
+
+  public RETURN_TYPE visitNone_Intersection(
+      AnnotatedNoType subtype, AnnotatedIntersectionType supertype, PARAM param);
+
+  public RETURN_TYPE visitNone_None(
+      AnnotatedNoType subtype, AnnotatedNoType supertype, PARAM param);
+
+  public RETURN_TYPE visitNone_Null(
+      AnnotatedNoType subtype, AnnotatedNullType supertype, PARAM param);
+
+  public RETURN_TYPE visitNone_Primitive(
+      AnnotatedNoType subtype, AnnotatedPrimitiveType supertype, PARAM param);
+
+  public RETURN_TYPE visitNone_Union(
+      AnnotatedNoType subtype, AnnotatedUnionType supertype, PARAM param);
+
+  public RETURN_TYPE visitNone_Wildcard(
+      AnnotatedNoType subtype, AnnotatedWildcardType supertype, PARAM param);
+
+  public RETURN_TYPE visitNull_Array(
+      AnnotatedNullType subtype, AnnotatedArrayType supertype, PARAM param);
+
+  public RETURN_TYPE visitNull_Declared(
+      AnnotatedNullType subtype, AnnotatedDeclaredType supertype, PARAM param);
+
+  public RETURN_TYPE visitNull_Executable(
+      AnnotatedNullType subtype, AnnotatedExecutableType supertype, PARAM param);
+
+  public RETURN_TYPE visitNull_Intersection(
+      AnnotatedNullType subtype, AnnotatedIntersectionType supertype, PARAM param);
+
+  public RETURN_TYPE visitNull_None(
+      AnnotatedNullType subtype, AnnotatedNoType supertype, PARAM param);
+
+  public RETURN_TYPE visitNull_Null(
+      AnnotatedNullType subtype, AnnotatedNullType supertype, PARAM param);
+
+  public RETURN_TYPE visitNull_Primitive(
+      AnnotatedNullType subtype, AnnotatedPrimitiveType supertype, PARAM param);
+
+  public RETURN_TYPE visitNull_Typevar(
+      AnnotatedNullType subtype, AnnotatedTypeVariable supertype, PARAM param);
+
+  public RETURN_TYPE visitNull_Union(
+      AnnotatedNullType subtype, AnnotatedUnionType supertype, PARAM param);
+
+  public RETURN_TYPE visitNull_Wildcard(
+      AnnotatedNullType subtype, AnnotatedWildcardType supertype, PARAM param);
+
+  public RETURN_TYPE visitPrimitive_Array(
+      AnnotatedPrimitiveType subtype, AnnotatedArrayType supertype, PARAM param);
+
+  public RETURN_TYPE visitPrimitive_Declared(
+      AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, PARAM param);
+
+  public RETURN_TYPE visitPrimitive_Executable(
+      AnnotatedPrimitiveType subtype, AnnotatedExecutableType supertype, PARAM param);
+
+  public RETURN_TYPE visitPrimitive_Intersection(
+      AnnotatedPrimitiveType subtype, AnnotatedIntersectionType supertype, PARAM param);
+
+  public RETURN_TYPE visitPrimitive_None(
+      AnnotatedPrimitiveType subtype, AnnotatedNoType supertype, PARAM param);
+
+  public RETURN_TYPE visitPrimitive_Null(
+      AnnotatedPrimitiveType subtype, AnnotatedNullType supertype, PARAM param);
+
+  public RETURN_TYPE visitPrimitive_Primitive(
+      AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, PARAM param);
+
+  public RETURN_TYPE visitPrimitive_Typevar(
+      AnnotatedPrimitiveType subtype, AnnotatedTypeVariable supertype, PARAM param);
+
+  public RETURN_TYPE visitPrimitive_Union(
+      AnnotatedPrimitiveType subtype, AnnotatedUnionType supertype, PARAM param);
+
+  public RETURN_TYPE visitPrimitive_Wildcard(
+      AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, PARAM param);
+
+  public RETURN_TYPE visitUnion_Array(
+      AnnotatedUnionType subtype, AnnotatedArrayType supertype, PARAM param);
+
+  public RETURN_TYPE visitUnion_Declared(
+      AnnotatedUnionType subtype, AnnotatedDeclaredType supertype, PARAM param);
+
+  public RETURN_TYPE visitUnion_Executable(
+      AnnotatedUnionType subtype, AnnotatedExecutableType supertype, PARAM param);
+
+  public RETURN_TYPE visitUnion_Intersection(
+      AnnotatedUnionType subtype, AnnotatedIntersectionType supertype, PARAM param);
+
+  public RETURN_TYPE visitUnion_None(
+      AnnotatedUnionType subtype, AnnotatedNoType supertype, PARAM param);
+
+  public RETURN_TYPE visitUnion_Null(
+      AnnotatedUnionType subtype, AnnotatedNullType supertype, PARAM param);
+
+  public RETURN_TYPE visitUnion_Primitive(
+      AnnotatedUnionType subtype, AnnotatedPrimitiveType supertype, PARAM param);
+
+  public RETURN_TYPE visitUnion_Typevar(
+      AnnotatedUnionType subtype, AnnotatedTypeVariable supertype, PARAM param);
+
+  public RETURN_TYPE visitUnion_Union(
+      AnnotatedUnionType subtype, AnnotatedUnionType supertype, PARAM param);
+
+  public RETURN_TYPE visitUnion_Wildcard(
+      AnnotatedUnionType subtype, AnnotatedWildcardType supertype, PARAM param);
+
+  public RETURN_TYPE visitTypevar_Array(
+      AnnotatedTypeVariable subtype, AnnotatedArrayType supertype, PARAM param);
+
+  public RETURN_TYPE visitTypevar_Declared(
+      AnnotatedTypeVariable subtype, AnnotatedDeclaredType supertype, PARAM param);
+
+  public RETURN_TYPE visitTypevar_Executable(
+      AnnotatedTypeVariable subtype, AnnotatedExecutableType supertype, PARAM param);
+
+  public RETURN_TYPE visitTypevar_Intersection(
+      AnnotatedTypeVariable subtype, AnnotatedIntersectionType supertype, PARAM param);
+
+  public RETURN_TYPE visitTypevar_None(
+      AnnotatedTypeVariable subtype, AnnotatedNoType supertype, PARAM param);
+
+  public RETURN_TYPE visitTypevar_Null(
+      AnnotatedTypeVariable subtype, AnnotatedNullType supertype, PARAM param);
+
+  public RETURN_TYPE visitTypevar_Primitive(
+      AnnotatedTypeVariable subtype, AnnotatedPrimitiveType supertype, PARAM param);
+
+  public RETURN_TYPE visitTypevar_Typevar(
+      AnnotatedTypeVariable subtype, AnnotatedTypeVariable supertype, PARAM param);
+
+  public RETURN_TYPE visitTypevar_Union(
+      AnnotatedTypeVariable subtype, AnnotatedUnionType supertype, PARAM param);
+
+  public RETURN_TYPE visitTypevar_Wildcard(
+      AnnotatedTypeVariable subtype, AnnotatedWildcardType supertype, PARAM param);
+
+  public RETURN_TYPE visitWildcard_Array(
+      AnnotatedWildcardType subtype, AnnotatedArrayType supertype, PARAM param);
+
+  public RETURN_TYPE visitWildcard_Declared(
+      AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, PARAM param);
+
+  public RETURN_TYPE visitWildcard_Executable(
+      AnnotatedWildcardType subtype, AnnotatedExecutableType supertype, PARAM param);
+
+  public RETURN_TYPE visitWildcard_Intersection(
+      AnnotatedWildcardType subtype, AnnotatedIntersectionType supertype, PARAM param);
+
+  public RETURN_TYPE visitWildcard_None(
+      AnnotatedWildcardType subtype, AnnotatedNoType supertype, PARAM param);
+
+  public RETURN_TYPE visitWildcard_Null(
+      AnnotatedWildcardType subtype, AnnotatedNullType supertype, PARAM param);
+
+  public RETURN_TYPE visitWildcard_Primitive(
+      AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, PARAM param);
+
+  public RETURN_TYPE visitWildcard_Typevar(
+      AnnotatedWildcardType subtype, AnnotatedTypeVariable supertype, PARAM param);
+
+  public RETURN_TYPE visitWildcard_Union(
+      AnnotatedWildcardType subtype, AnnotatedUnionType supertype, PARAM param);
+
+  public RETURN_TYPE visitWildcard_Wildcard(
+      AnnotatedWildcardType subtype, AnnotatedWildcardType supertype, PARAM param);
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/DoubleAnnotatedTypeScanner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/DoubleAnnotatedTypeScanner.java
new file mode 100644
index 0000000..bfc3773
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/DoubleAnnotatedTypeScanner.java
@@ -0,0 +1,208 @@
+package org.checkerframework.framework.type.visitor;
+
+import java.util.Iterator;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * An {@link AnnotatedTypeScanner} that scans two {@link AnnotatedTypeMirror}s simultaneously and
+ * performs {@link #defaultAction(AnnotatedTypeMirror, AnnotatedTypeMirror)} on the pair. Both
+ * AnnotatedTypeMirrors must have the same structure, or a subclass must arrange not to continue
+ * recursing past the point at which their structure diverges.
+ *
+ * <p>If the default action does not return a result, then {@code R} should be {@link Void} and
+ * {@code DoubleAnnotatedTypeScanner()} should be used to construct the scanner. If the default
+ * action returns a result, then specify a {@link #reduce} function and use {@code
+ * DoubleAnnotatedTypeScanner(Reduce, Object)}.
+ *
+ * @see AnnotatedTypeScanner
+ * @param <R> the result of scanning the two {@code AnnotatedTypeMirror}s
+ */
+public abstract class DoubleAnnotatedTypeScanner<R>
+    extends AnnotatedTypeScanner<R, AnnotatedTypeMirror> {
+
+  /**
+   * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it is
+   * nonnull; otherwise the second result is returned. The default result is {@code null}.
+   */
+  protected DoubleAnnotatedTypeScanner() {
+    super();
+  }
+
+  /**
+   * Creates a scanner with the given {@code reduce} function and {@code defaultResult}.
+   *
+   * @param reduce function used to combine the results of scan
+   * @param defaultResult result to use by default
+   */
+  protected DoubleAnnotatedTypeScanner(Reduce<R> reduce, R defaultResult) {
+    super(reduce, defaultResult);
+  }
+
+  /**
+   * Called by default for any visit method that is not overridden.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  protected abstract R defaultAction(AnnotatedTypeMirror type, AnnotatedTypeMirror p);
+
+  /**
+   * Scans {@code types1} and {@code types2}. If they are empty, then {@link #defaultResult} is
+   * returned.
+   *
+   * @param types1 types
+   * @param types2 types
+   * @return the result of scanning and reducing all the types in {@code types1} and {@code types2}
+   *     or {@link #defaultResult} if they are empty
+   */
+  protected R scan(
+      Iterable<? extends AnnotatedTypeMirror> types1,
+      Iterable<? extends AnnotatedTypeMirror> types2) {
+    if (types1 == null || types2 == null) {
+      return defaultResult;
+    }
+    R r = defaultResult;
+    boolean first = true;
+    Iterator<? extends AnnotatedTypeMirror> iter1 = types1.iterator();
+    Iterator<? extends AnnotatedTypeMirror> iter2 = types2.iterator();
+    while (iter1.hasNext() && iter2.hasNext()) {
+      r = (first ? scan(iter1.next(), iter2.next()) : scanAndReduce(iter1.next(), iter2.next(), r));
+      first = false;
+    }
+    return r;
+  }
+
+  /**
+   * Run {@link #scan} on types and p, then run {@link #reduce} on the result (plus r) to return a
+   * single element.
+   */
+  protected R scanAndReduce(
+      Iterable<? extends AnnotatedTypeMirror> types,
+      Iterable<? extends AnnotatedTypeMirror> p,
+      R r) {
+    return reduce(scan(types, p), r);
+  }
+
+  @Override
+  protected final R scanAndReduce(
+      Iterable<? extends AnnotatedTypeMirror> types, AnnotatedTypeMirror p, R r) {
+    throw new BugInCF(
+        "DoubleAnnotatedTypeScanner.scanAndReduce: "
+            + p
+            + " is not Iterable<? extends AnnotatedTypeMirror>");
+  }
+
+  @Override
+  protected R scan(AnnotatedTypeMirror type, AnnotatedTypeMirror p) {
+    return reduce(super.scan(type, p), defaultAction(type, p));
+  }
+
+  @Override
+  public final R visitDeclared(AnnotatedDeclaredType type, AnnotatedTypeMirror p) {
+    assert p instanceof AnnotatedDeclaredType : p;
+    R r = scan(type.getTypeArguments(), ((AnnotatedDeclaredType) p).getTypeArguments());
+    if (type.getEnclosingType() != null) {
+      r = scanAndReduce(type.getEnclosingType(), ((AnnotatedDeclaredType) p).getEnclosingType(), r);
+    }
+    return r;
+  }
+
+  @Override
+  public final R visitArray(AnnotatedArrayType type, AnnotatedTypeMirror p) {
+    assert p instanceof AnnotatedArrayType : p;
+    R r = scan(type.getComponentType(), ((AnnotatedArrayType) p).getComponentType());
+    return r;
+  }
+
+  @Override
+  public final R visitExecutable(AnnotatedExecutableType type, AnnotatedTypeMirror p) {
+    assert p instanceof AnnotatedExecutableType : p;
+    AnnotatedExecutableType ex = (AnnotatedExecutableType) p;
+    R r = scan(type.getReturnType(), ex.getReturnType());
+    if (type.getReceiverType() != null) {
+      r = scanAndReduce(type.getReceiverType(), ex.getReceiverType(), r);
+    }
+    r = scanAndReduce(type.getParameterTypes(), ex.getParameterTypes(), r);
+    r = scanAndReduce(type.getThrownTypes(), ex.getThrownTypes(), r);
+    r = scanAndReduce(type.getTypeVariables(), ex.getTypeVariables(), r);
+    return r;
+  }
+
+  @Override
+  public R visitTypeVariable(AnnotatedTypeVariable type, AnnotatedTypeMirror p) {
+    if (visitedNodes.containsKey(type)) {
+      return visitedNodes.get(type);
+    }
+    visitedNodes.put(type, null);
+
+    R r;
+    if (p instanceof AnnotatedTypeVariable) {
+      AnnotatedTypeVariable tv = (AnnotatedTypeVariable) p;
+      r = scan(type.getLowerBound(), tv.getLowerBound());
+      visitedNodes.put(type, r);
+      r = scanAndReduce(type.getUpperBound(), tv.getUpperBound(), r);
+      visitedNodes.put(type, r);
+    } else {
+      r = scan(type.getLowerBound(), p.getErased());
+      visitedNodes.put(type, r);
+      r = scanAndReduce(type.getUpperBound(), p.getErased(), r);
+      visitedNodes.put(type, r);
+    }
+    return r;
+  }
+
+  @Override
+  public R visitWildcard(AnnotatedWildcardType type, AnnotatedTypeMirror p) {
+    if (visitedNodes.containsKey(type)) {
+      return visitedNodes.get(type);
+    }
+    visitedNodes.put(type, null);
+
+    R r;
+    if (p instanceof AnnotatedWildcardType) {
+      AnnotatedWildcardType w = (AnnotatedWildcardType) p;
+      r = scan(type.getExtendsBound(), w.getExtendsBound());
+      visitedNodes.put(type, r);
+      r = scanAndReduce(type.getSuperBound(), w.getSuperBound(), r);
+      visitedNodes.put(type, r);
+    } else {
+      r = scan(type.getExtendsBound(), p.getErased());
+      visitedNodes.put(type, r);
+      r = scanAndReduce(type.getSuperBound(), p.getErased(), r);
+      visitedNodes.put(type, r);
+    }
+    return r;
+  }
+
+  @Override
+  public R visitIntersection(AnnotatedIntersectionType type, AnnotatedTypeMirror p) {
+    assert p instanceof AnnotatedIntersectionType : p;
+
+    if (visitedNodes.containsKey(type)) {
+      return visitedNodes.get(type);
+    }
+    visitedNodes.put(type, null);
+    R r = scan(type.getBounds(), ((AnnotatedIntersectionType) p).getBounds());
+    return r;
+  }
+
+  @Override
+  public R visitUnion(AnnotatedUnionType type, AnnotatedTypeMirror p) {
+    assert p instanceof AnnotatedUnionType : p;
+    if (visitedNodes.containsKey(type)) {
+      return visitedNodes.get(type);
+    }
+    visitedNodes.put(type, null);
+    R r = scan(type.getAlternatives(), ((AnnotatedUnionType) p).getAlternatives());
+    return r;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/EquivalentAtmComboScanner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/EquivalentAtmComboScanner.java
new file mode 100644
index 0000000..7d9ef2d
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/EquivalentAtmComboScanner.java
@@ -0,0 +1,241 @@
+package org.checkerframework.framework.type.visitor;
+
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.util.AtmCombo;
+
+/**
+ * EquivalentAtmComboScanner is an AtmComboVisitor that accepts combinations that are identical in
+ * TypeMirror structure but might differ in contained AnnotationMirrors. This method will scan the
+ * individual components of the visited type pairs together.
+ */
+public abstract class EquivalentAtmComboScanner<RETURN_TYPE, PARAM>
+    extends AbstractAtmComboVisitor<RETURN_TYPE, PARAM> {
+
+  /** A history of type pairs that have already been visited and the return type of their visit. */
+  protected final Visited visited = new Visited();
+
+  /** Entry point for this scanner. */
+  @Override
+  public RETURN_TYPE visit(
+      final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2, PARAM param) {
+    visited.clear();
+    return scan(type1, type2, param);
+  }
+
+  /**
+   * In an AnnotatedTypeScanner a null type is encounter than null is returned. A user may want to
+   * customize the behavior of this scanner depending on whether or not one or both types is null.
+   *
+   * @param type1 a nullable AnnotatedTypeMirror
+   * @param type2 a nullable AnnotatedTypeMirror
+   * @param param the visitor param
+   * @return a subclass specific return type/value
+   */
+  protected abstract RETURN_TYPE scanWithNull(
+      AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param);
+
+  protected RETURN_TYPE scan(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) {
+    if (type1 == null || type2 == null) {
+      return scanWithNull(type1, type2, param);
+    }
+
+    return AtmCombo.accept(type1, type2, param, this);
+  }
+
+  protected RETURN_TYPE scan(
+      Iterable<? extends AnnotatedTypeMirror> types1,
+      Iterable<? extends AnnotatedTypeMirror> types2,
+      PARAM param) {
+    RETURN_TYPE r = null;
+    boolean first = true;
+
+    Iterator<? extends AnnotatedTypeMirror> tIter1 = types1.iterator();
+    Iterator<? extends AnnotatedTypeMirror> tIter2 = types2.iterator();
+
+    while (tIter1.hasNext() && tIter2.hasNext()) {
+      final AnnotatedTypeMirror type1 = tIter1.next();
+      final AnnotatedTypeMirror type2 = tIter2.next();
+
+      r = first ? scan(type1, type2, param) : scanAndReduce(type1, type2, param, r);
+    }
+
+    return r;
+  }
+
+  protected RETURN_TYPE scanAndReduce(
+      Iterable<? extends AnnotatedTypeMirror> types1,
+      Iterable<? extends AnnotatedTypeMirror> types2,
+      PARAM param,
+      RETURN_TYPE r) {
+    return reduce(scan(types1, types2, param), r);
+  }
+
+  protected RETURN_TYPE scanAndReduce(
+      AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param, RETURN_TYPE r) {
+    return reduce(scan(type1, type2, param), r);
+  }
+
+  protected RETURN_TYPE reduce(RETURN_TYPE r1, RETURN_TYPE r2) {
+    if (r1 == null) {
+      return r2;
+    }
+    return r1;
+  }
+
+  @Override
+  public RETURN_TYPE visitArray_Array(
+      AnnotatedArrayType type1, AnnotatedArrayType type2, PARAM param) {
+    if (visited.contains(type1, type2)) {
+      return visited.getResult(type1, type2);
+    }
+    visited.add(type1, type2, null);
+
+    return scan(type1.getComponentType(), type2.getComponentType(), param);
+  }
+
+  @Override
+  public RETURN_TYPE visitDeclared_Declared(
+      AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, PARAM param) {
+    if (visited.contains(type1, type2)) {
+      return visited.getResult(type1, type2);
+    }
+    visited.add(type1, type2, null);
+
+    return scan(type1.getTypeArguments(), type2.getTypeArguments(), param);
+  }
+
+  @Override
+  public RETURN_TYPE visitExecutable_Executable(
+      AnnotatedExecutableType type1, AnnotatedExecutableType type2, PARAM param) {
+    if (visited.contains(type1, type2)) {
+      return visited.getResult(type1, type2);
+    }
+    visited.add(type1, type2, null);
+
+    RETURN_TYPE r = scan(type1.getReturnType(), type2.getReturnType(), param);
+    r = scanAndReduce(type1.getReceiverType(), type2.getReceiverType(), param, r);
+    r = scanAndReduce(type1.getParameterTypes(), type2.getParameterTypes(), param, r);
+    r = scanAndReduce(type1.getThrownTypes(), type2.getThrownTypes(), param, r);
+    r = scanAndReduce(type1.getTypeVariables(), type2.getTypeVariables(), param, r);
+    return r;
+  }
+
+  @Override
+  public RETURN_TYPE visitIntersection_Intersection(
+      AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, PARAM param) {
+    if (visited.contains(type1, type2)) {
+      return visited.getResult(type1, type2);
+    }
+    visited.add(type1, type2, null);
+
+    return scan(type1.getBounds(), type2.getBounds(), param);
+  }
+
+  @Override
+  public RETURN_TYPE visitNone_None(AnnotatedNoType type1, AnnotatedNoType type2, PARAM param) {
+    return null;
+  }
+
+  @Override
+  public RETURN_TYPE visitNull_Null(AnnotatedNullType type1, AnnotatedNullType type2, PARAM param) {
+    return null;
+  }
+
+  @Override
+  public RETURN_TYPE visitPrimitive_Primitive(
+      AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, PARAM param) {
+    return null;
+  }
+
+  @Override
+  public RETURN_TYPE visitUnion_Union(
+      AnnotatedUnionType type1, AnnotatedUnionType type2, PARAM param) {
+    if (visited.contains(type1, type2)) {
+      return visited.getResult(type1, type2);
+    }
+
+    visited.add(type1, type2, null);
+
+    return scan(type1.getAlternatives(), type2.getAlternatives(), param);
+  }
+
+  @Override
+  public RETURN_TYPE visitTypevar_Typevar(
+      AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, PARAM param) {
+    if (visited.contains(type1, type2)) {
+      return visited.getResult(type1, type2);
+    }
+
+    visited.add(type1, type2, null);
+
+    RETURN_TYPE r = scan(type1.getUpperBound(), type2.getUpperBound(), param);
+    r = scanAndReduce(type1.getLowerBound(), type2.getLowerBound(), param, r);
+    return r;
+  }
+
+  @Override
+  public RETURN_TYPE visitWildcard_Wildcard(
+      AnnotatedWildcardType type1, AnnotatedWildcardType type2, PARAM param) {
+    if (visited.contains(type1, type2)) {
+      return visited.getResult(type1, type2);
+    }
+
+    visited.add(type1, type2, null);
+
+    RETURN_TYPE r = scan(type1.getExtendsBound(), type2.getExtendsBound(), param);
+    r = scanAndReduce(type1.getSuperBound(), type2.getSuperBound(), param, r);
+    return r;
+  }
+
+  /** A history of type pairs that have already been visited and the return type of their visit. */
+  protected class Visited {
+
+    private final IdentityHashMap<
+            AnnotatedTypeMirror, IdentityHashMap<AnnotatedTypeMirror, RETURN_TYPE>>
+        visits = new IdentityHashMap<>();
+
+    public void clear() {
+      visits.clear();
+    }
+
+    public boolean contains(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
+      IdentityHashMap<AnnotatedTypeMirror, RETURN_TYPE> recordFor1 = visits.get(type1);
+      return recordFor1 != null && recordFor1.containsKey(type2);
+    }
+
+    public RETURN_TYPE getResult(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
+      IdentityHashMap<AnnotatedTypeMirror, RETURN_TYPE> recordFor1 = visits.get(type1);
+      if (recordFor1 == null) {
+        return null;
+      }
+
+      return recordFor1.get(type2);
+    }
+
+    /**
+     * Add a new pair to the history.
+     *
+     * @param type1 the first type
+     * @param type2 the second type
+     * @param ret the result
+     */
+    public void add(
+        final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2, final RETURN_TYPE ret) {
+      IdentityHashMap<AnnotatedTypeMirror, RETURN_TYPE> recordFor1 =
+          visits.computeIfAbsent(type1, __ -> new IdentityHashMap<>());
+      recordFor1.put(type2, ret);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeScanner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeScanner.java
new file mode 100644
index 0000000..568cad6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeScanner.java
@@ -0,0 +1,214 @@
+package org.checkerframework.framework.type.visitor;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * An {@link AnnotatedTypeScanner} that scans an {@link AnnotatedTypeMirror} and performs some
+ * {@link #defaultAction} on each type. The defaultAction can be passed to the constructor {@link
+ * #SimpleAnnotatedTypeScanner(DefaultAction)} or this class can be extended and {@link
+ * #defaultAction} can be overridden.
+ *
+ * <p>If the default action does not return a result, then {@code R} should be {@link Void}. If the
+ * default action returns a result, then specify a {@link #reduce} function.
+ *
+ * @param <R> the return type of this visitor's methods. Use Void for visitors that do not need to
+ *     return results.
+ * @param <P> the type of the additional parameter to this visitor's methods. Use Void for visitors
+ *     that do not need an additional parameter.
+ */
+public class SimpleAnnotatedTypeScanner<R, P> extends AnnotatedTypeScanner<R, P> {
+
+  /**
+   * Represents an action to perform on every type.
+   *
+   * @param <R> the type of the result of the action
+   * @param <P> the type of the parameter of action
+   */
+  @FunctionalInterface
+  public interface DefaultAction<R, P> {
+
+    /**
+     * The action to perform on every type.
+     *
+     * @param type AnnotatedTypeMirror on which to perform some action
+     * @param p argument to pass to the action
+     * @return result of the action
+     */
+    R defaultAction(AnnotatedTypeMirror type, P p);
+  }
+
+  /** The action to perform on every type. */
+  protected final DefaultAction<R, P> defaultAction;
+
+  /**
+   * Creates a scanner that performs {@code defaultAction} on every type.
+   *
+   * <p>Use this constructor if the type of result of the default action is {@link Void}.
+   *
+   * @param defaultAction action to perform on every type
+   */
+  public SimpleAnnotatedTypeScanner(DefaultAction<R, P> defaultAction) {
+    this(defaultAction, null, null);
+  }
+
+  /**
+   * Creates a scanner that performs {@code defaultAction} on every type and use {@code reduce} to
+   * combine the results.
+   *
+   * <p>Use this constructor if the default action returns a result.
+   *
+   * @param defaultAction action to perform on every type
+   * @param reduce function used to combine results
+   * @param defaultResult result to use by default
+   */
+  public SimpleAnnotatedTypeScanner(
+      DefaultAction<R, P> defaultAction, Reduce<R> reduce, R defaultResult) {
+    super(reduce, defaultResult);
+    this.defaultAction = defaultAction;
+  }
+
+  /**
+   * Creates a scanner without specifing the default action. Subclasses may only use this
+   * constructor if they also override {@link #defaultAction(AnnotatedTypeMirror, Object)}.
+   */
+  protected SimpleAnnotatedTypeScanner() {
+    this(null, null, null);
+  }
+
+  /**
+   * Creates a scanner without specifing the default action. Subclasses may only use this
+   * constructor if they also override {@link #defaultAction(AnnotatedTypeMirror, Object)}.
+   *
+   * @param reduce function used to combine results
+   * @param defaultResult result to use by default
+   */
+  protected SimpleAnnotatedTypeScanner(Reduce<R> reduce, R defaultResult) {
+    this(null, reduce, defaultResult);
+  }
+
+  /**
+   * Called by default for any visit method that is not overridden.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  protected R defaultAction(AnnotatedTypeMirror type, P p) {
+    if (defaultAction == null) {
+      // The no argument constructor sets default action to null.
+      throw new BugInCF(
+          "%s did not provide a default action.  Please override #defaultAction", this.getClass());
+    }
+    return defaultAction.defaultAction(type, p);
+  }
+
+  /**
+   * Visits a declared type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  @Override
+  public final R visitDeclared(AnnotatedDeclaredType type, P p) {
+    R r = defaultAction(type, p);
+    return reduce(super.visitDeclared(type, p), r);
+  }
+
+  /**
+   * Visits an executable type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  @Override
+  public final R visitExecutable(AnnotatedExecutableType type, P p) {
+    R r = defaultAction(type, p);
+    return reduce(super.visitExecutable(type, p), r);
+  }
+
+  /**
+   * Visits an array type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  @Override
+  public final R visitArray(AnnotatedArrayType type, P p) {
+    R r = defaultAction(type, p);
+    return reduce(super.visitArray(type, p), r);
+  }
+
+  /**
+   * Visits a type variable.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  @Override
+  public final R visitTypeVariable(AnnotatedTypeVariable type, P p) {
+    R r = defaultAction(type, p);
+    return reduce(super.visitTypeVariable(type, p), r);
+  }
+
+  /**
+   * Visits a primitive type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  @Override
+  public final R visitPrimitive(AnnotatedPrimitiveType type, P p) {
+    return defaultAction(type, p);
+  }
+
+  /**
+   * Visits NoType type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  @Override
+  public final R visitNoType(AnnotatedNoType type, P p) {
+    return defaultAction(type, p);
+  }
+
+  /**
+   * Visits a {@code null} type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  @Override
+  public final R visitNull(AnnotatedNullType type, P p) {
+    return defaultAction(type, p);
+  }
+
+  /**
+   * Visits a wildcard type.
+   *
+   * @param type the type to visit
+   * @param p a visitor-specified parameter
+   * @return a visitor-specified result
+   */
+  @Override
+  public final R visitWildcard(AnnotatedWildcardType type, P p) {
+    R r = defaultAction(type, p);
+    return reduce(super.visitWildcard(type, p), r);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeVisitor.java
new file mode 100644
index 0000000..b5ec218
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeVisitor.java
@@ -0,0 +1,116 @@
+package org.checkerframework.framework.type.visitor;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+
+/**
+ * A simple visitor for {@link AnnotatedTypeMirror}s.
+ *
+ * @param <R> the return type of this visitor's methods. Use {@link Void} for visitors that do not
+ *     need to return results.
+ * @param <P> the type of the additional parameter to this visitor's methods. Use {@link Void} for
+ *     visitors that do not need an additional parameter.
+ */
+public abstract class SimpleAnnotatedTypeVisitor<R, P> implements AnnotatedTypeVisitor<R, P> {
+
+  /** The default value to return as a default action. */
+  protected final R DEFAULT_VALUE;
+
+  /**
+   * Creates an instance of {@link SimpleAnnotatedTypeVisitor} with default value being {@code
+   * null}.
+   */
+  protected SimpleAnnotatedTypeVisitor() {
+    this(null);
+  }
+
+  /**
+   * Creates an instance of {@link SimpleAnnotatedTypeVisitor} with the default value being the
+   * passed defaultValue.
+   *
+   * @param defaultValue the default value this class should return
+   */
+  protected SimpleAnnotatedTypeVisitor(R defaultValue) {
+    this.DEFAULT_VALUE = defaultValue;
+  }
+
+  /**
+   * Performs the default action for visiting trees, if subclasses do not override the visitFOO
+   * node.
+   *
+   * <p>This implementation merely returns the default value (as specified by the protected field
+   * {@code DEFAULT_VALUE}).
+   */
+  protected R defaultAction(AnnotatedTypeMirror type, P p) {
+    return DEFAULT_VALUE;
+  }
+
+  @Override
+  public R visit(AnnotatedTypeMirror type) {
+    return visit(type, null);
+  }
+
+  @Override
+  public R visit(AnnotatedTypeMirror type, P p) {
+    return (type == null) ? null : type.accept(this, p);
+  }
+
+  @Override
+  public R visitDeclared(AnnotatedDeclaredType type, P p) {
+    return defaultAction(type, p);
+  }
+
+  @Override
+  public R visitIntersection(AnnotatedIntersectionType type, P p) {
+    return defaultAction(type, p);
+  }
+
+  @Override
+  public R visitUnion(AnnotatedUnionType type, P p) {
+    return defaultAction(type, p);
+  }
+
+  @Override
+  public R visitArray(AnnotatedArrayType type, P p) {
+    return defaultAction(type, p);
+  }
+
+  @Override
+  public R visitExecutable(AnnotatedExecutableType type, P p) {
+    return defaultAction(type, p);
+  }
+
+  @Override
+  public R visitTypeVariable(AnnotatedTypeVariable type, P p) {
+    return defaultAction(type, p);
+  }
+
+  @Override
+  public R visitWildcard(AnnotatedWildcardType type, P p) {
+    return defaultAction(type, p);
+  }
+
+  @Override
+  public R visitPrimitive(AnnotatedPrimitiveType type, P p) {
+    return defaultAction(type, p);
+  }
+
+  @Override
+  public R visitNull(AnnotatedNullType type, P p) {
+    return defaultAction(type, p);
+  }
+
+  @Override
+  public R visitNoType(AnnotatedNoType type, P p) {
+    return defaultAction(type, p);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/package-info.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/package-info.java
new file mode 100644
index 0000000..ee67440
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/package-info.java
@@ -0,0 +1,6 @@
+package org.checkerframework.framework.type.visitor;
+
+/**
+ * Despite the name {@code visitor/}, this package does not just contain visitors. It contains types
+ * that are intended to be subclassed, such as interfaces and abstract classes.
+ */
diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java
new file mode 100644
index 0000000..5b3b8bd
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java
@@ -0,0 +1,1399 @@
+package org.checkerframework.framework.util;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MemberReferenceTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.WildcardType;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+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.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.AsSuperVisitor;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.SyntheticArrays;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Utility methods for operating on {@code AnnotatedTypeMirror}. This class mimics the class {@link
+ * Types}.
+ */
+public class AnnotatedTypes {
+  /** Class cannot be instantiated. */
+  private AnnotatedTypes() {
+    throw new AssertionError("Class AnnotatedTypes cannot be instantiated.");
+  }
+
+  private static AsSuperVisitor asSuperVisitor;
+
+  /**
+   * Copies annotations from {@code type} to a copy of {@code superType} where the type variables of
+   * {@code superType} have been substituted. How the annotations are copied depends on the kinds of
+   * AnnotatedTypeMirrors given. Generally, if {@code type} and {@code superType} are both declared
+   * types, asSuper is called recursively on the direct super types, see {@link
+   * AnnotatedTypeMirror#directSupertypes()}, of {@code type} until {@code type}'s erased Java type
+   * is the same as {@code superType}'s erased super type. Then {@code type is returned}. For
+   * compound types, asSuper is called recursively on components.
+   *
+   * <p>Preconditions:<br>
+   * {@code superType} may have annotations, but they are ignored. <br>
+   * {@code type} may not be an instanceof AnnotatedNullType, because if {@code superType} is a
+   * compound type, the annotations on the component types are undefined.<br>
+   * The underlying {@code type} (ie the Java type) of {@code type} should be a subtype (or the same
+   * type) of the underlying type of {@code superType}. Except for these cases:
+   *
+   * <ul>
+   *   <li>If {@code type} is a primitive, then the boxed type of {@code type} must be subtype of
+   *       {@code superType}.
+   *   <li>If {@code superType} is a primitive, then {@code type} must be convertible to {@code
+   *       superType}.
+   *   <li>If {@code superType} is a type variable or wildcard without a lower bound, then {@code
+   *       type} must be a subtype of the upper bound of {@code superType}. (This relaxed rule is
+   *       used during type argument inference where the type variable or wildcard is the type
+   *       argument that was inferred.)
+   *   <li>If {@code superType} is a wildcard with a lower bound, then {@code type} must be a
+   *       subtype of the lower bound of {@code superType}.
+   * </ul>
+   *
+   * <p>Postconditions: {@code type} and {@code superType} are not modified.
+   *
+   * @param atypeFactory {@link AnnotatedTypeFactory}
+   * @param type type from which to copy annotations
+   * @param superType a type whose erased Java type is a supertype of {@code type}'s erased Java
+   *     type.
+   * @return {@code superType} with annotations copied from {@code type} and type variables
+   *     substituted from {@code type}.
+   */
+  public static <T extends AnnotatedTypeMirror> T asSuper(
+      AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror type, T superType) {
+    if (asSuperVisitor == null || !asSuperVisitor.sameAnnotatedTypeFactory(atypeFactory)) {
+      asSuperVisitor = new AsSuperVisitor(atypeFactory);
+    }
+    return asSuperVisitor.asSuper(type, superType);
+  }
+
+  /**
+   * Calls asSuper and casts the result to the same type as the input supertype.
+   *
+   * @param subtype subtype to be transformed to supertype
+   * @param supertype supertype that subtype is transformed to
+   * @param <T> the type of supertype and return type
+   * @return subtype as an instance of supertype
+   */
+  public static <T extends AnnotatedTypeMirror> T castedAsSuper(
+      final AnnotatedTypeFactory atypeFactory,
+      final AnnotatedTypeMirror subtype,
+      final T supertype) {
+    final Types types = atypeFactory.getProcessingEnv().getTypeUtils();
+    final Elements elements = atypeFactory.getProcessingEnv().getElementUtils();
+
+    if (subtype.getKind() == TypeKind.NULL) {
+      // Make a copy of the supertype so that if supertype is a composite type, the
+      // returned type will be fully annotated.  (For example, if sub is @C null and super is
+      // @A List<@B String>, then the returned type is @C List<@B String>.)
+      @SuppressWarnings("unchecked")
+      T copy = (T) supertype.deepCopy();
+      copy.replaceAnnotations(subtype.getAnnotations());
+      return copy;
+    }
+
+    final T asSuperType = AnnotatedTypes.asSuper(atypeFactory, subtype, supertype);
+
+    fixUpRawTypes(subtype, asSuperType, supertype, types);
+
+    // if we have a type for enum MyEnum {...}
+    // When the supertype is the declaration of java.lang.Enum<E>, MyEnum values become
+    // Enum<MyEnum>.  Where really, we would like an Enum<E> with the annotations from
+    // Enum<MyEnum> are transferred to Enum<E>.  That is, if we have a type:
+    // @1 Enum<@2 MyEnum>
+    // asSuper should return:
+    // @1 Enum<E extends @2 Enum<E>>
+    if (asSuperType != null
+        && AnnotatedTypes.isEnum(asSuperType)
+        && AnnotatedTypes.isDeclarationOfJavaLangEnum(types, elements, supertype)) {
+      final AnnotatedDeclaredType resultAtd = ((AnnotatedDeclaredType) supertype).deepCopy();
+      resultAtd.clearAnnotations();
+      resultAtd.addAnnotations(asSuperType.getAnnotations());
+
+      final AnnotatedDeclaredType asSuperAdt = (AnnotatedDeclaredType) asSuperType;
+      if (!resultAtd.getTypeArguments().isEmpty() && !asSuperAdt.getTypeArguments().isEmpty()) {
+        final AnnotatedTypeMirror sourceTypeArg = asSuperAdt.getTypeArguments().get(0);
+        final AnnotatedTypeMirror resultTypeArg = resultAtd.getTypeArguments().get(0);
+        resultTypeArg.clearAnnotations();
+        if (resultTypeArg.getKind() == TypeKind.TYPEVAR) {
+          // Only change the upper bound of a type variable.
+          AnnotatedTypeVariable resultTypeArgTV = (AnnotatedTypeVariable) resultTypeArg;
+          resultTypeArgTV.getUpperBound().addAnnotations(sourceTypeArg.getAnnotations());
+        } else {
+          resultTypeArg.addAnnotations(sourceTypeArg.getEffectiveAnnotations());
+        }
+        @SuppressWarnings("unchecked")
+        T result = (T) resultAtd;
+        return result;
+      }
+    }
+    return asSuperType;
+  }
+
+  /**
+   * Some times we create type arguments for types that were raw. When we do an asSuper we lose
+   * these arguments. If in the converted type (i.e. the subtype as super) is missing type arguments
+   * AND those type arguments should come from the original subtype's type arguments then we copy
+   * the original type arguments to the converted type. e.g. We have a type W, that "wasRaw" {@code
+   * ArrayList<? extends Object>} When W is converted to type A, List, using asSuper it no longer
+   * has its type argument. But since the type argument to List should be the same as that to
+   * ArrayList we copy over the type argument of W to A. A becomes {@code List<? extends Object>}
+   *
+   * @param originalSubtype the subtype before being converted by asSuper
+   * @param asSuperType he subtype after being converted by asSuper
+   * @param supertype the supertype for which asSuperType should have the same underlying type
+   * @param types the types utility
+   */
+  private static void fixUpRawTypes(
+      final AnnotatedTypeMirror originalSubtype,
+      final AnnotatedTypeMirror asSuperType,
+      final AnnotatedTypeMirror supertype,
+      final Types types) {
+    if (asSuperType == null
+        || asSuperType.getKind() != TypeKind.DECLARED
+        || originalSubtype.getKind() != TypeKind.DECLARED) {
+      return;
+    }
+
+    final AnnotatedDeclaredType declaredAsSuper = (AnnotatedDeclaredType) asSuperType;
+    final AnnotatedDeclaredType declaredSubtype = (AnnotatedDeclaredType) originalSubtype;
+
+    if (!declaredAsSuper.wasRaw()
+        || !declaredAsSuper.getTypeArguments().isEmpty()
+        || declaredSubtype.getTypeArguments().isEmpty()) {
+      return;
+    }
+
+    Set<Pair<Integer, Integer>> typeArgMap =
+        TypeArgumentMapper.mapTypeArgumentIndices(
+            (TypeElement) declaredSubtype.getUnderlyingType().asElement(),
+            (TypeElement) declaredAsSuper.getUnderlyingType().asElement(),
+            types);
+
+    if (typeArgMap.size() != declaredSubtype.getTypeArguments().size()) {
+      return;
+    }
+
+    List<Pair<Integer, Integer>> orderedByDestination = new ArrayList<>(typeArgMap);
+    orderedByDestination.sort(Comparator.comparingInt(o -> o.second));
+
+    if (typeArgMap.size() == ((AnnotatedDeclaredType) supertype).getTypeArguments().size()) {
+      List<? extends AnnotatedTypeMirror> subTypeArgs = declaredSubtype.getTypeArguments();
+      List<AnnotatedTypeMirror> newTypeArgs =
+          CollectionsPlume.mapList(
+              mapping -> subTypeArgs.get(mapping.first).deepCopy(), orderedByDestination);
+      declaredAsSuper.setTypeArguments(newTypeArgs);
+    } else {
+      declaredAsSuper.setTypeArguments(Collections.emptyList());
+    }
+  }
+
+  /** This method identifies wildcard types that are unbound. */
+  public static boolean hasNoExplicitBound(final AnnotatedTypeMirror wildcard) {
+    return ((Type.WildcardType) wildcard.getUnderlyingType()).isUnbound();
+  }
+
+  /**
+   * This method identifies wildcard types that have an explicit super bound. NOTE:
+   * Type.WildcardType.isSuperBound will return true for BOTH unbound and super bound wildcards
+   * which necessitates this method
+   */
+  public static boolean hasExplicitSuperBound(final AnnotatedTypeMirror wildcard) {
+    final Type.WildcardType wildcardType = (Type.WildcardType) wildcard.getUnderlyingType();
+    return wildcardType.isSuperBound()
+        && !((WildcardType) wildcard.getUnderlyingType()).isUnbound();
+  }
+
+  /**
+   * This method identifies wildcard types that have an explicit extends bound. NOTE:
+   * Type.WildcardType.isExtendsBound will return true for BOTH unbound and extends bound wildcards
+   * which necessitates this method
+   */
+  public static boolean hasExplicitExtendsBound(final AnnotatedTypeMirror wildcard) {
+    final Type.WildcardType wildcardType = (Type.WildcardType) wildcard.getUnderlyingType();
+    return wildcardType.isExtendsBound()
+        && !((WildcardType) wildcard.getUnderlyingType()).isUnbound();
+  }
+
+  /**
+   * Return the base type of type or any of its outer types that starts with the given type. If none
+   * exists, return null.
+   *
+   * @param type a type
+   * @param superType a type
+   */
+  private static AnnotatedTypeMirror asOuterSuper(
+      Types types,
+      AnnotatedTypeFactory atypeFactory,
+      AnnotatedTypeMirror type,
+      AnnotatedTypeMirror superType) {
+    if (type.getKind() == TypeKind.DECLARED) {
+      AnnotatedDeclaredType dt = (AnnotatedDeclaredType) type;
+      AnnotatedDeclaredType enclosingType = dt;
+      TypeMirror superTypeMirror = types.erasure(superType.getUnderlyingType());
+      while (enclosingType != null) {
+        TypeMirror enclosingTypeMirror = types.erasure(enclosingType.getUnderlyingType());
+        if (types.isSubtype(enclosingTypeMirror, superTypeMirror)) {
+          dt = enclosingType;
+          break;
+        }
+        enclosingType = enclosingType.getEnclosingType();
+      }
+      if (enclosingType == null) {
+        // TODO: https://github.com/typetools/checker-framework/issues/724
+        // testcase javacheck -processor nullness  src/java/util/AbstractMap.java
+        //                SourceChecker checker =  atypeFactory.getChecker().getChecker();
+        //                String msg = (String.format("OuterAsSuper did not find outer
+        // class. type: %s superType: %s", type, superType));
+        //                checker.message(Kind.WARNING, msg);
+        return superType;
+      }
+      return asSuper(atypeFactory, dt, superType);
+    }
+    return asSuper(atypeFactory, type, superType);
+  }
+
+  /**
+   * Specialization of {@link #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror,
+   * Element)} with more precise return type.
+   *
+   * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element)
+   * @param types the Types instance to use
+   * @param atypeFactory the type factory to use
+   * @param t the receiver type
+   * @param elem the element that should be viewed as member of t
+   * @return the type of elem as member of t
+   */
+  public static AnnotatedExecutableType asMemberOf(
+      Types types,
+      AnnotatedTypeFactory atypeFactory,
+      AnnotatedTypeMirror t,
+      ExecutableElement elem) {
+    return (AnnotatedExecutableType) asMemberOf(types, atypeFactory, t, (Element) elem);
+  }
+
+  /**
+   * Specialization of {@link #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element,
+   * AnnotatedTypeMirror)} with more precise return type.
+   *
+   * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element,
+   *     AnnotatedTypeMirror)
+   * @param types the Types instance to use
+   * @param atypeFactory the type factory to use
+   * @param t the receiver type
+   * @param elem the element that should be viewed as member of t
+   * @param type unsubstituted type of member
+   * @return the type of member as member of of, with initial type memberType; can be an alias to
+   *     memberType
+   */
+  public static AnnotatedExecutableType asMemberOf(
+      Types types,
+      AnnotatedTypeFactory atypeFactory,
+      AnnotatedTypeMirror t,
+      ExecutableElement elem,
+      AnnotatedExecutableType type) {
+    return (AnnotatedExecutableType) asMemberOf(types, atypeFactory, t, (Element) elem, type);
+  }
+
+  /**
+   * Returns the type of an element when that element is viewed as a member of, or otherwise
+   * directly contained by, a given type.
+   *
+   * <p>For example, when viewed as a member of the parameterized type {@code Set<@NonNull String>},
+   * the {@code Set.add} method is an {@code ExecutableType} whose parameter is of type
+   * {@code @NonNull String}.
+   *
+   * <p>Before returning the result, this method adjusts it by calling {@link
+   * AnnotatedTypeFactory#postAsMemberOf(AnnotatedTypeMirror, AnnotatedTypeMirror, Element)}.
+   *
+   * <p>Note that this method does not currently return (top level) captured types for type
+   * parameters, parameters, and return types. Instead, the original wildcard is returned, or
+   * sometimes inferring type arguments will create a wildcard type which is returned. The bounds of
+   * an inferred wildcard may itself have captures.
+   *
+   * <p>To prevent unsoundness, the rest of the Checker Framework must handle wildcards in places
+   * where captures should appear (like type arguments). This should just involve the bounds of the
+   * wildcard where the bounds of the capture would have been used.
+   *
+   * @param types the Types instance to use
+   * @param atypeFactory the type factory to use
+   * @param t the receiver type
+   * @param elem the element that should be viewed as member of t
+   * @return the type of elem as member of t
+   */
+  public static AnnotatedTypeMirror asMemberOf(
+      Types types, AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror t, Element elem) {
+    final AnnotatedTypeMirror memberType = atypeFactory.getAnnotatedType(elem);
+    return asMemberOf(types, atypeFactory, t, elem, memberType);
+  }
+
+  /**
+   * Returns the type of an element when that element is viewed as a member of, or otherwise
+   * directly contained by, a given type. An initial type for the member is provided, to allow for
+   * earlier changes to the declared type of elem. For example, polymorphic qualifiers must be
+   * substituted before type variables are substituted.
+   *
+   * @param types the Types instance to use
+   * @param atypeFactory the type factory to use
+   * @param t the receiver type
+   * @param elem the element that should be viewed as member of t
+   * @param elemType unsubstituted type of elem
+   * @return the type of elem as member of t
+   * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element)
+   */
+  public static AnnotatedTypeMirror asMemberOf(
+      Types types,
+      AnnotatedTypeFactory atypeFactory,
+      @Nullable AnnotatedTypeMirror t,
+      Element elem,
+      AnnotatedTypeMirror elemType) {
+    // asMemberOf is only for fields, variables, and methods!
+    // Otherwise, simply use fromElement.
+    switch (elem.getKind()) {
+      case PACKAGE:
+      case INSTANCE_INIT:
+      case OTHER:
+      case STATIC_INIT:
+      case TYPE_PARAMETER:
+        return elemType;
+      default:
+        if (t == null || ElementUtils.isStatic(elem)) {
+          return elemType;
+        }
+        AnnotatedTypeMirror res = asMemberOfImpl(types, atypeFactory, t, elem, elemType);
+        atypeFactory.postAsMemberOf(res, t, elem);
+        return res;
+    }
+  }
+
+  /**
+   * Helper for {@link AnnotatedTypes#asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror,
+   * Element)}.
+   *
+   * @param types the Types instance to use
+   * @param atypeFactory the type factory to use
+   * @param receiverType the receiver type
+   * @param member the element that should be viewed as member of receiverType
+   * @param memberType unsubstituted type of member
+   * @return the type of member as a member of receiverType; can be an alias to memberType
+   */
+  private static AnnotatedTypeMirror asMemberOfImpl(
+      final Types types,
+      final AnnotatedTypeFactory atypeFactory,
+      final AnnotatedTypeMirror receiverType,
+      final Element member,
+      final AnnotatedTypeMirror memberType) {
+    switch (receiverType.getKind()) {
+      case ARRAY:
+        // Method references like String[]::clone should have a return type of String[]
+        // rather than Object.
+        if (SyntheticArrays.isArrayClone(receiverType, member)) {
+          return SyntheticArrays.replaceReturnType(member, (AnnotatedArrayType) receiverType);
+        }
+        return memberType;
+      case TYPEVAR:
+        return asMemberOf(
+            types,
+            atypeFactory,
+            ((AnnotatedTypeVariable) receiverType).getUpperBound(),
+            member,
+            memberType);
+      case WILDCARD:
+        if (((AnnotatedWildcardType) receiverType).isUninferredTypeArgument()) {
+          return substituteUninferredTypeArgs(atypeFactory, member, memberType);
+        }
+        return asMemberOf(
+            types,
+            atypeFactory,
+            ((AnnotatedWildcardType) receiverType).getExtendsBound().deepCopy(),
+            member,
+            memberType);
+      case INTERSECTION:
+        AnnotatedTypeMirror result = memberType;
+        for (AnnotatedTypeMirror bound : ((AnnotatedIntersectionType) receiverType).getBounds()) {
+          result = substituteTypeVariables(types, atypeFactory, bound, member, result);
+        }
+        return result;
+      case UNION:
+      case DECLARED:
+        return substituteTypeVariables(types, atypeFactory, receiverType, member, memberType);
+      default:
+        throw new BugInCF("asMemberOf called on unexpected type.%nt: %s", receiverType);
+    }
+  }
+
+  /**
+   * Substitute type variables.
+   *
+   * @param types type utilities
+   * @param atypeFactory the type factory
+   * @param receiverType the type of the class that contains member (or a subtype of it)
+   * @param member a type member, such as a method or field
+   * @param memberType the type of {@code member}
+   * @return {@code memberType}, substituted
+   */
+  private static AnnotatedTypeMirror substituteTypeVariables(
+      Types types,
+      AnnotatedTypeFactory atypeFactory,
+      AnnotatedTypeMirror receiverType,
+      Element member,
+      AnnotatedTypeMirror memberType) {
+
+    // Basic Algorithm:
+    // 1. Find the enclosingClassOfMember of the element
+    // 2. Find the base type of enclosingClassOfMember (e.g. type of enclosingClassOfMember as
+    //      supertype of passed type)
+    // 3. Substitute for type variables if any exist
+    TypeElement enclosingClassOfMember = ElementUtils.enclosingTypeElement(member);
+    final Map<TypeVariable, AnnotatedTypeMirror> mappings = new HashMap<>();
+
+    // Look for all enclosing classes that have type variables
+    // and collect type to be substituted for those type variables
+    while (enclosingClassOfMember != null) {
+      addTypeVarMappings(types, atypeFactory, receiverType, enclosingClassOfMember, mappings);
+      enclosingClassOfMember =
+          ElementUtils.enclosingTypeElement(enclosingClassOfMember.getEnclosingElement());
+    }
+
+    if (!mappings.isEmpty()) {
+      memberType = atypeFactory.getTypeVarSubstitutor().substitute(mappings, memberType);
+    }
+
+    return memberType;
+  }
+
+  private static void addTypeVarMappings(
+      Types types,
+      AnnotatedTypeFactory atypeFactory,
+      AnnotatedTypeMirror t,
+      TypeElement enclosingClassOfElem,
+      Map<TypeVariable, AnnotatedTypeMirror> mappings) {
+    if (enclosingClassOfElem.getTypeParameters().isEmpty()) {
+      return;
+    }
+    AnnotatedDeclaredType enclosingType = atypeFactory.getAnnotatedType(enclosingClassOfElem);
+    AnnotatedDeclaredType base =
+        (AnnotatedDeclaredType) asOuterSuper(types, atypeFactory, t, enclosingType);
+
+    final List<AnnotatedTypeVariable> ownerParams =
+        new ArrayList<>(enclosingType.getTypeArguments().size());
+    for (final AnnotatedTypeMirror typeParam : enclosingType.getTypeArguments()) {
+      if (typeParam.getKind() != TypeKind.TYPEVAR) {
+        throw new BugInCF(
+            StringsPlume.joinLines(
+                "Type arguments of a declaration should be type variables.",
+                "  enclosingClassOfElem=" + enclosingClassOfElem,
+                "  enclosingType=" + enclosingType,
+                "  typeMirror=" + t));
+      }
+      ownerParams.add((AnnotatedTypeVariable) typeParam);
+    }
+
+    List<AnnotatedTypeMirror> baseParams = base.getTypeArguments();
+    if (ownerParams.size() != baseParams.size() && !base.wasRaw()) {
+      throw new BugInCF(
+          StringsPlume.joinLines(
+              "Unexpected number of parameters.",
+              "enclosingType=" + enclosingType,
+              "baseType=" + base));
+    }
+    if (!ownerParams.isEmpty() && baseParams.isEmpty() && base.wasRaw()) {
+      // If base type was raw and the type arguments are missing, set them to the erased
+      // type of the type variable (which is the erased type of the upper bound).
+      baseParams = CollectionsPlume.mapList(AnnotatedTypeVariable::getErased, ownerParams);
+    }
+
+    for (int i = 0; i < ownerParams.size(); ++i) {
+      mappings.put(ownerParams.get(i).getUnderlyingType(), baseParams.get(i));
+    }
+  }
+
+  /**
+   * Substitutes uninferred type arguments for type variables in {@code memberType}.
+   *
+   * @param atypeFactory the type factory
+   * @param member the element with type {@code memberType}; used to obtain the enclosing type
+   * @param memberType the type to side-effect
+   * @return memberType, with type arguments substituted for type variables
+   */
+  private static AnnotatedTypeMirror substituteUninferredTypeArgs(
+      AnnotatedTypeFactory atypeFactory, Element member, AnnotatedTypeMirror memberType) {
+    TypeElement enclosingClassOfMember = ElementUtils.enclosingTypeElement(member);
+    final Map<TypeVariable, AnnotatedTypeMirror> mappings = new HashMap<>();
+
+    while (enclosingClassOfMember != null) {
+      if (!enclosingClassOfMember.getTypeParameters().isEmpty()) {
+        AnnotatedDeclaredType enclosingType = atypeFactory.getAnnotatedType(enclosingClassOfMember);
+        for (final AnnotatedTypeMirror type : enclosingType.getTypeArguments()) {
+          AnnotatedTypeVariable typeParameter = (AnnotatedTypeVariable) type;
+          mappings.put(
+              typeParameter.getUnderlyingType(),
+              atypeFactory.getUninferredWildcardType(typeParameter));
+        }
+      }
+      enclosingClassOfMember =
+          ElementUtils.enclosingTypeElement(enclosingClassOfMember.getEnclosingElement());
+    }
+
+    if (!mappings.isEmpty()) {
+      return atypeFactory.getTypeVarSubstitutor().substitute(mappings, memberType);
+    }
+
+    return memberType;
+  }
+
+  /**
+   * Returns all the supertypes (direct or indirect) of the given declared type.
+   *
+   * @param type a declared type
+   * @return all the supertypes of the given type
+   */
+  public static Set<AnnotatedDeclaredType> getSuperTypes(AnnotatedDeclaredType type) {
+
+    Set<AnnotatedDeclaredType> supertypes = new LinkedHashSet<>();
+    if (type == null) {
+      return supertypes;
+    }
+
+    // Set up a stack containing the type mirror of subtype, which
+    // is our starting point.
+    Deque<AnnotatedDeclaredType> stack = new ArrayDeque<>();
+    stack.push(type);
+
+    while (!stack.isEmpty()) {
+      AnnotatedDeclaredType current = stack.pop();
+
+      // For each direct supertype of the current type, if it
+      // hasn't already been visited, push it onto the stack and
+      // add it to our supertypes set.
+      for (AnnotatedDeclaredType supertype : current.directSupertypes()) {
+        if (!supertypes.contains(supertype)) {
+          stack.push(supertype);
+          supertypes.add(supertype);
+        }
+      }
+    }
+
+    return Collections.unmodifiableSet(supertypes);
+  }
+
+  /**
+   * Given a method, return the methods that it overrides.
+   *
+   * @param method the overriding method
+   * @return a map from types to methods that {@code method} overrides
+   */
+  public static Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods(
+      Elements elements, AnnotatedTypeFactory atypeFactory, ExecutableElement method) {
+    final TypeElement elem = (TypeElement) method.getEnclosingElement();
+    final AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(elem);
+    final Collection<AnnotatedDeclaredType> supertypes = getSuperTypes(type);
+    return overriddenMethods(elements, method, supertypes);
+  }
+
+  /**
+   * Given a method and all supertypes (recursively) of the method's containing class, returns the
+   * methods that the method overrides.
+   *
+   * @param method the overriding method
+   * @param supertypes the set of supertypes to check for methods that are overridden by {@code
+   *     method}
+   * @return a map from types to methods that {@code method} overrides
+   */
+  public static Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods(
+      Elements elements, ExecutableElement method, Collection<AnnotatedDeclaredType> supertypes) {
+
+    Map<AnnotatedDeclaredType, ExecutableElement> overrides = new LinkedHashMap<>();
+
+    for (AnnotatedDeclaredType supertype : supertypes) {
+      @Nullable TypeElement superElement = (TypeElement) supertype.getUnderlyingType().asElement();
+      assert superElement != null;
+      // For all method in the supertype, add it to the set if
+      // it overrides the given method.
+      for (ExecutableElement supermethod :
+          ElementFilter.methodsIn(superElement.getEnclosedElements())) {
+        if (elements.overrides(method, supermethod, superElement)) {
+          overrides.put(supertype, supermethod);
+          break;
+        }
+      }
+    }
+
+    return Collections.unmodifiableMap(overrides);
+  }
+
+  /**
+   * Given a method or constructor invocation, return a mapping of the type variables to their type
+   * arguments, if any exist.
+   *
+   * <p>It uses the method or constructor invocation type arguments if they were specified and
+   * otherwise it infers them based on the passed arguments or the return type context, according to
+   * JLS 15.12.2.
+   *
+   * @param atypeFactory the annotated type factory
+   * @param expr the method or constructor invocation tree; the passed argument has to be a subtype
+   *     of MethodInvocationTree or NewClassTree
+   * @param elt the element corresponding to the tree
+   * @param preType the (partially annotated) type corresponding to the tree - the result of
+   *     AnnotatedTypes.asMemberOf with the receiver and elt
+   * @return the mapping of the type variables to type arguments for this method or constructor
+   *     invocation
+   */
+  public static Map<TypeVariable, AnnotatedTypeMirror> findTypeArguments(
+      final ProcessingEnvironment processingEnv,
+      final AnnotatedTypeFactory atypeFactory,
+      final ExpressionTree expr,
+      final ExecutableElement elt,
+      final AnnotatedExecutableType preType) {
+
+    // Is the method a generic method?
+    if (elt.getTypeParameters().isEmpty()) {
+      return Collections.emptyMap();
+    }
+
+    List<? extends Tree> targs;
+    if (expr instanceof MethodInvocationTree) {
+      targs = ((MethodInvocationTree) expr).getTypeArguments();
+    } else if (expr instanceof NewClassTree) {
+      targs = ((NewClassTree) expr).getTypeArguments();
+    } else if (expr instanceof MemberReferenceTree) {
+      targs = ((MemberReferenceTree) expr).getTypeArguments();
+      if (targs == null) {
+        // TODO: Add type argument inference as part of fix for #979
+        return new HashMap<>();
+      }
+    } else {
+      // This case should never happen.
+      throw new BugInCF("AnnotatedTypes.findTypeArguments: unexpected tree: " + expr);
+    }
+
+    // Has the user supplied type arguments?
+    if (!targs.isEmpty()) {
+      List<? extends AnnotatedTypeVariable> tvars = preType.getTypeVariables();
+
+      Map<TypeVariable, AnnotatedTypeMirror> typeArguments = new HashMap<>();
+      for (int i = 0; i < elt.getTypeParameters().size(); ++i) {
+        AnnotatedTypeVariable typeVar = tvars.get(i);
+        AnnotatedTypeMirror typeArg = atypeFactory.getAnnotatedTypeFromTypeTree(targs.get(i));
+        // TODO: the call to getTypeParameterDeclaration shouldn't be necessary - typeVar
+        // already should be a declaration.
+        typeArguments.put(typeVar.getUnderlyingType(), typeArg);
+      }
+      return typeArguments;
+    } else {
+      return atypeFactory
+          .getTypeArgumentInference()
+          .inferTypeArgs(atypeFactory, expr, elt, preType);
+    }
+  }
+
+  /**
+   * Returns the lub of two annotated types.
+   *
+   * @param atypeFactory AnnotatedTypeFactory
+   * @param type1 annotated type
+   * @param type2 annotated type
+   * @return the lub of type1 and type2
+   */
+  public static AnnotatedTypeMirror leastUpperBound(
+      AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) {
+    TypeMirror lub =
+        TypesUtils.leastUpperBound(
+            type1.getUnderlyingType(), type2.getUnderlyingType(), atypeFactory.getProcessingEnv());
+    return leastUpperBound(atypeFactory, type1, type2, lub);
+  }
+
+  /**
+   * Returns the lub, whose underlying type is {@code lubTypeMirror} of two annotated types.
+   *
+   * @param atypeFactory AnnotatedTypeFactory
+   * @param type1 annotated type whose underlying type must be a subtype or convertible to
+   *     lubTypeMirror
+   * @param type2 annotated type whose underlying type must be a subtype or convertible to
+   *     lubTypeMirror
+   * @param lubTypeMirror underlying type of the returned lub
+   * @return the lub of type1 and type2 with underlying type lubTypeMirror
+   */
+  public static AnnotatedTypeMirror leastUpperBound(
+      AnnotatedTypeFactory atypeFactory,
+      AnnotatedTypeMirror type1,
+      AnnotatedTypeMirror type2,
+      TypeMirror lubTypeMirror) {
+    return new AtmLubVisitor(atypeFactory).lub(type1, type2, lubTypeMirror);
+  }
+
+  /**
+   * Returns the method parameters for the invoked method, with the same number of arguments passed
+   * in the methodInvocation tree.
+   *
+   * <p>If the invoked method is not a vararg method or it is a vararg method but the invocation
+   * passes an array to the vararg parameter, it would simply return the method parameters.
+   *
+   * <p>Otherwise, it would return the list of parameters as if the vararg is expanded to match the
+   * size of the passed arguments.
+   *
+   * @param method the method's type
+   * @param args the arguments to the method invocation
+   * @return the types that the method invocation arguments need to be subtype of
+   */
+  public static List<AnnotatedTypeMirror> expandVarArgs(
+      AnnotatedTypeFactory atypeFactory,
+      AnnotatedExecutableType method,
+      List<? extends ExpressionTree> args) {
+    List<AnnotatedTypeMirror> parameters = method.getParameterTypes();
+    if (!method.getElement().isVarArgs()) {
+      return parameters;
+    }
+
+    AnnotatedArrayType varargs = (AnnotatedArrayType) parameters.get(parameters.size() - 1);
+
+    if (parameters.size() == args.size()) {
+      // Check if one sent an element or an array
+      AnnotatedTypeMirror lastArg = atypeFactory.getAnnotatedType(args.get(args.size() - 1));
+      if (lastArg.getKind() == TypeKind.ARRAY
+          && getArrayDepth(varargs) == getArrayDepth((AnnotatedArrayType) lastArg)) {
+        return parameters;
+      }
+    }
+
+    parameters = new ArrayList<>(parameters.subList(0, parameters.size() - 1));
+    for (int i = args.size() - parameters.size(); i > 0; --i) {
+      parameters.add(varargs.getComponentType().deepCopy());
+    }
+
+    return parameters;
+  }
+
+  public static List<AnnotatedTypeMirror> expandVarArgsFromTypes(
+      AnnotatedExecutableType method, List<AnnotatedTypeMirror> args) {
+    List<AnnotatedTypeMirror> parameters = method.getParameterTypes();
+    if (!method.getElement().isVarArgs()) {
+      return parameters;
+    }
+
+    AnnotatedArrayType varargs = (AnnotatedArrayType) parameters.get(parameters.size() - 1);
+
+    if (parameters.size() == args.size()) {
+      // Check if one sent an element or an array
+      AnnotatedTypeMirror lastArg = args.get(args.size() - 1);
+      if (lastArg.getKind() == TypeKind.ARRAY
+          && (getArrayDepth(varargs) == getArrayDepth((AnnotatedArrayType) lastArg)
+              // If the array depths don't match, but the component type of the vararg
+              // is a type variable, then that type variable might later be
+              // substituted for an array.
+              || varargs.getComponentType().getKind() == TypeKind.TYPEVAR)) {
+        return parameters;
+      }
+    }
+
+    parameters = new ArrayList<>(parameters.subList(0, parameters.size() - 1));
+    for (int i = args.size() - parameters.size(); i > 0; --i) {
+      parameters.add(varargs.getComponentType());
+    }
+
+    return parameters;
+  }
+
+  /**
+   * Given an AnnotatedExecutableType of a method or constructor declaration, get the parameter type
+   * expected at the indexth position (unwrapping varargs if necessary).
+   *
+   * @param methodType AnnotatedExecutableType of method or constructor containing parameter to
+   *     return
+   * @param index position of parameter type to return
+   * @return if that parameter is a varArgs, return the component of the var args and NOT the array
+   *     type. Otherwise, return the exact type of the parameter in the index position.
+   */
+  public static AnnotatedTypeMirror getAnnotatedTypeMirrorOfParameter(
+      AnnotatedExecutableType methodType, int index) {
+    List<AnnotatedTypeMirror> parameterTypes = methodType.getParameterTypes();
+    boolean hasVarArg = methodType.getElement().isVarArgs();
+
+    final int lastIndex = parameterTypes.size() - 1;
+    final AnnotatedTypeMirror lastType = parameterTypes.get(lastIndex);
+    final boolean parameterBeforeVarargs = index < lastIndex;
+    if (!parameterBeforeVarargs && lastType instanceof AnnotatedArrayType) {
+      final AnnotatedArrayType arrayType = (AnnotatedArrayType) lastType;
+      if (hasVarArg) {
+        return arrayType.getComponentType();
+      }
+    }
+    return parameterTypes.get(index);
+  }
+
+  /**
+   * Return a list of the AnnotatedTypeMirror of the passed expression trees, in the same order as
+   * the trees.
+   *
+   * @param paramTypes the parameter types to use as assignment context
+   * @param trees the AST nodes
+   * @return a list with the AnnotatedTypeMirror of each tree in trees
+   */
+  public static List<AnnotatedTypeMirror> getAnnotatedTypes(
+      AnnotatedTypeFactory atypeFactory,
+      List<AnnotatedTypeMirror> paramTypes,
+      List<? extends ExpressionTree> trees) {
+    if (paramTypes.size() != trees.size()) {
+      throw new BugInCF(
+          "AnnotatedTypes.getAnnotatedTypes: size mismatch! "
+              + "Parameter types: "
+              + paramTypes
+              + " Arguments: "
+              + trees);
+    }
+    Pair<Tree, AnnotatedTypeMirror> preAssignmentContext =
+        atypeFactory.getVisitorState().getAssignmentContext();
+
+    List<AnnotatedTypeMirror> types = new ArrayList<>();
+    try {
+      for (int i = 0; i < trees.size(); ++i) {
+        AnnotatedTypeMirror param = paramTypes.get(i);
+        atypeFactory.getVisitorState().setAssignmentContext(Pair.of((Tree) null, param));
+        ExpressionTree arg = trees.get(i);
+        types.add(atypeFactory.getAnnotatedType(arg));
+      }
+    } finally {
+      atypeFactory.getVisitorState().setAssignmentContext(preAssignmentContext);
+    }
+    return types;
+  }
+
+  /**
+   * Returns the depth of the array type of the provided array.
+   *
+   * @param array the type of the array
+   * @return the depth of the provided array
+   */
+  public static int getArrayDepth(AnnotatedArrayType array) {
+    int counter = 0;
+    AnnotatedTypeMirror type = array;
+    while (type.getKind() == TypeKind.ARRAY) {
+      counter++;
+      type = ((AnnotatedArrayType) type).getComponentType();
+    }
+    return counter;
+  }
+
+  // The innermost *array* type.
+  public static AnnotatedTypeMirror innerMostType(AnnotatedTypeMirror t) {
+    AnnotatedTypeMirror inner = t;
+    while (inner.getKind() == TypeKind.ARRAY) {
+      inner = ((AnnotatedArrayType) inner).getComponentType();
+    }
+    return inner;
+  }
+
+  /**
+   * Checks whether type contains the given modifier, also recursively in type arguments and arrays.
+   * This method might be easier to implement directly as instance method in AnnotatedTypeMirror; it
+   * corresponds to a "deep" version of {@link AnnotatedTypeMirror#hasAnnotation(AnnotationMirror)}.
+   *
+   * @param type the type to search
+   * @param modifier the modifier to search for
+   * @return whether the type contains the modifier
+   */
+  public static boolean containsModifier(AnnotatedTypeMirror type, AnnotationMirror modifier) {
+    return containsModifierImpl(type, modifier, new ArrayList<>());
+  }
+
+  /*
+   * For type variables we might hit the same type again. We keep a list of visited types.
+   */
+  private static boolean containsModifierImpl(
+      AnnotatedTypeMirror type, AnnotationMirror modifier, List<AnnotatedTypeMirror> visited) {
+    boolean found = type.hasAnnotation(modifier);
+    boolean vis = visited.contains(type);
+    visited.add(type);
+
+    if (!found && !vis) {
+      if (type.getKind() == TypeKind.DECLARED) {
+        AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type;
+        for (AnnotatedTypeMirror typeMirror : declaredType.getTypeArguments()) {
+          found |= containsModifierImpl(typeMirror, modifier, visited);
+          if (found) {
+            break;
+          }
+        }
+      } else if (type.getKind() == TypeKind.ARRAY) {
+        AnnotatedArrayType arrayType = (AnnotatedArrayType) type;
+        found = containsModifierImpl(arrayType.getComponentType(), modifier, visited);
+      } else if (type.getKind() == TypeKind.TYPEVAR) {
+        AnnotatedTypeVariable atv = (AnnotatedTypeVariable) type;
+        if (atv.getUpperBound() != null) {
+          found = containsModifierImpl(atv.getUpperBound(), modifier, visited);
+        }
+        if (!found && atv.getLowerBound() != null) {
+          found = containsModifierImpl(atv.getLowerBound(), modifier, visited);
+        }
+      } else if (type.getKind() == TypeKind.WILDCARD) {
+        AnnotatedWildcardType awc = (AnnotatedWildcardType) type;
+        if (awc.getExtendsBound() != null) {
+          found = containsModifierImpl(awc.getExtendsBound(), modifier, visited);
+        }
+        if (!found && awc.getSuperBound() != null) {
+          found = containsModifierImpl(awc.getSuperBound(), modifier, visited);
+        }
+      }
+    }
+
+    return found;
+  }
+
+  /** java.lang.annotation.Annotation.class canonical name. */
+  private static @CanonicalName String annotationClassName =
+      java.lang.annotation.Annotation.class.getCanonicalName();
+
+  /**
+   * Returns true if the underlying type of this atm is a java.lang.annotation.Annotation.
+   *
+   * @return true if the underlying type of this atm is a java.lang.annotation.Annotation
+   */
+  public static boolean isJavaLangAnnotation(final AnnotatedTypeMirror atm) {
+    return TypesUtils.isDeclaredOfName(atm.getUnderlyingType(), annotationClassName);
+  }
+
+  /**
+   * Returns true if atm is an Annotation interface, i.e., an implementation of
+   * java.lang.annotation.Annotation. Given {@code @interface MyAnno}, a call to {@code
+   * implementsAnnotation} returns true when called on an AnnotatedDeclaredType representing a use
+   * of MyAnno.
+   *
+   * @return true if atm is an Annotation interface
+   */
+  public static boolean implementsAnnotation(final AnnotatedTypeMirror atm) {
+    if (atm.getKind() != TypeKind.DECLARED) {
+      return false;
+    }
+    final AnnotatedTypeMirror.AnnotatedDeclaredType declaredType =
+        (AnnotatedTypeMirror.AnnotatedDeclaredType) atm;
+
+    Symbol.ClassSymbol classSymbol =
+        (Symbol.ClassSymbol) declaredType.getUnderlyingType().asElement();
+    for (final Type iface : classSymbol.getInterfaces()) {
+      if (TypesUtils.isDeclaredOfName(iface, annotationClassName)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  public static boolean isEnum(final AnnotatedTypeMirror typeMirror) {
+    if (typeMirror.getKind() == TypeKind.DECLARED) {
+      final AnnotatedDeclaredType adt = (AnnotatedDeclaredType) typeMirror;
+      return TypesUtils.isDeclaredOfName(adt.getUnderlyingType(), java.lang.Enum.class.getName());
+    }
+
+    return false;
+  }
+
+  public static boolean isDeclarationOfJavaLangEnum(
+      final Types types, final Elements elements, final AnnotatedTypeMirror typeMirror) {
+    if (isEnum(typeMirror)) {
+      return elements
+          .getTypeElement(Enum.class.getCanonicalName())
+          .equals(((AnnotatedDeclaredType) typeMirror).getUnderlyingType().asElement());
+    }
+
+    return false;
+  }
+
+  /**
+   * Returns true if the typeVar1 and typeVar2 are two uses of the same type variable.
+   *
+   * @return true if the typeVar1 and typeVar2 are two uses of the same type variable
+   */
+  public static boolean haveSameDeclaration(
+      Types types, final AnnotatedTypeVariable typeVar1, final AnnotatedTypeVariable typeVar2) {
+    return types.isSameType(typeVar1.getUnderlyingType(), typeVar2.getUnderlyingType());
+  }
+
+  /**
+   * When overriding a method, you must include the same number of type parameters as the base
+   * method. By index, these parameters are considered equivalent to the type parameters of the
+   * overridden method. Necessary conditions: Both type variables are defined in methods One of the
+   * two methods overrides the other Within their method declaration, both types have the same type
+   * parameter index
+   *
+   * @return true if type1 and type2 are corresponding type variables (that is, either one
+   *     "overrides" the other)
+   */
+  public static boolean areCorrespondingTypeVariables(
+      Elements elements, AnnotatedTypeVariable type1, AnnotatedTypeVariable type2) {
+    final TypeParameterElement type1ParamElem =
+        (TypeParameterElement) type1.getUnderlyingType().asElement();
+    final TypeParameterElement type2ParamElem =
+        (TypeParameterElement) type2.getUnderlyingType().asElement();
+
+    if (type1ParamElem.getGenericElement() instanceof ExecutableElement
+        && type2ParamElem.getGenericElement() instanceof ExecutableElement) {
+      final ExecutableElement type1Executable =
+          (ExecutableElement) type1ParamElem.getGenericElement();
+      final ExecutableElement type2Executable =
+          (ExecutableElement) type2ParamElem.getGenericElement();
+
+      final TypeElement type1Class = (TypeElement) type1Executable.getEnclosingElement();
+      final TypeElement type2Class = (TypeElement) type2Executable.getEnclosingElement();
+
+      boolean methodIsOverriden =
+          elements.overrides(type1Executable, type2Executable, type1Class)
+              || elements.overrides(type2Executable, type1Executable, type2Class);
+      if (methodIsOverriden) {
+        boolean haveSameIndex =
+            type1Executable.getTypeParameters().indexOf(type1ParamElem)
+                == type2Executable.getTypeParameters().indexOf(type2ParamElem);
+        return haveSameIndex;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * When comparing types against the bounds of a type variable, we may encounter other type
+   * variables, wildcards, and intersections in those bounds. This method traverses the bounds until
+   * it finds a concrete type from which it can pull an annotation.
+   *
+   * @param top the top of the hierarchy for which you are searching
+   * @return the AnnotationMirror that represents the type of toSearch in the hierarchy of top
+   */
+  public static AnnotationMirror findEffectiveAnnotationInHierarchy(
+      final QualifierHierarchy qualifierHierarchy,
+      final AnnotatedTypeMirror toSearch,
+      final AnnotationMirror top) {
+    return findEffectiveAnnotationInHierarchy(qualifierHierarchy, toSearch, top, false);
+  }
+
+  /**
+   * When comparing types against the bounds of a type variable, we may encounter other type
+   * variables, wildcards, and intersections in those bounds. This method traverses the bounds until
+   * it finds a concrete type from which it can pull an annotation.
+   *
+   * @param top the top of the hierarchy for which you are searching
+   * @param canBeEmpty whether or not the effective type can have NO annotation in the hierarchy
+   *     specified by top If this param is false, an exception will be thrown if no annotation is
+   *     found Otherwise the result is null
+   * @return the AnnotationMirror that represents the type of toSearch in the hierarchy of top
+   */
+  public static AnnotationMirror findEffectiveAnnotationInHierarchy(
+      final QualifierHierarchy qualifierHierarchy,
+      final AnnotatedTypeMirror toSearch,
+      final AnnotationMirror top,
+      final boolean canBeEmpty) {
+    AnnotatedTypeMirror source = toSearch;
+    while (source.getAnnotationInHierarchy(top) == null) {
+
+      switch (source.getKind()) {
+        case TYPEVAR:
+          source = ((AnnotatedTypeVariable) source).getUpperBound();
+          break;
+
+        case WILDCARD:
+          source = ((AnnotatedWildcardType) source).getExtendsBound();
+          break;
+
+        case INTERSECTION:
+          // if there are multiple conflicting annotations, choose the lowest
+          final AnnotationMirror glb =
+              glbOfBoundsInHierarchy((AnnotatedIntersectionType) source, top, qualifierHierarchy);
+
+          if (glb == null) {
+            throw new BugInCF(
+                "AnnotatedIntersectionType has no annotation in hierarchy "
+                    + "on any of its supertypes."
+                    + System.lineSeparator()
+                    + "intersectionType="
+                    + source);
+          }
+          return glb;
+
+        default:
+          if (canBeEmpty) {
+            return null;
+          }
+
+          throw new BugInCF(
+              StringsPlume.joinLines(
+                  "Unexpected AnnotatedTypeMirror with no primary annotation.",
+                  "toSearch=" + toSearch,
+                  "top=" + top,
+                  "source=" + source));
+      }
+    }
+
+    return source.getAnnotationInHierarchy(top);
+  }
+
+  /**
+   * When comparing types against the bounds of a type variable, we may encounter other type
+   * variables, wildcards, and intersections in those bounds. This method traverses the lower bounds
+   * until it finds a concrete type from which it can pull an annotation. This occurs for every
+   * hierarchy in QualifierHierarchy
+   *
+   * @return the set of effective annotation mirrors in all hierarchies
+   */
+  public static Set<AnnotationMirror> findEffectiveLowerBoundAnnotations(
+      final QualifierHierarchy qualifierHierarchy, final AnnotatedTypeMirror toSearch) {
+    AnnotatedTypeMirror source = toSearch;
+    TypeKind kind = source.getKind();
+    while (kind == TypeKind.TYPEVAR || kind == TypeKind.WILDCARD || kind == TypeKind.INTERSECTION) {
+
+      switch (source.getKind()) {
+        case TYPEVAR:
+          source = ((AnnotatedTypeVariable) source).getLowerBound();
+          break;
+
+        case WILDCARD:
+          source = ((AnnotatedWildcardType) source).getSuperBound();
+          break;
+
+        case INTERSECTION:
+          // if there are multiple conflicting annotations, choose the lowest
+          final Set<AnnotationMirror> glb =
+              glbOfBounds((AnnotatedIntersectionType) source, qualifierHierarchy);
+          return glb;
+
+        default:
+          throw new BugInCF(
+              "Unexpected AnnotatedTypeMirror with no primary annotation;"
+                  + " toSearch="
+                  + toSearch
+                  + " source="
+                  + source);
+      }
+
+      kind = source.getKind();
+    }
+
+    return source.getAnnotations();
+  }
+
+  /**
+   * When comparing types against the bounds of a type variable, we may encounter other type
+   * variables, wildcards, and intersections in those bounds. This method traverses the bounds until
+   * it finds a concrete type from which it can pull an annotation. This occurs for every hierarchy
+   * in QualifierHierarchy
+   *
+   * @return the set of effective annotation mirrors in all hierarchies
+   */
+  public static Set<AnnotationMirror> findEffectiveAnnotations(
+      final QualifierHierarchy qualifierHierarchy, final AnnotatedTypeMirror toSearch) {
+    AnnotatedTypeMirror source = toSearch;
+    TypeKind kind = source.getKind();
+    while (kind == TypeKind.TYPEVAR || kind == TypeKind.WILDCARD || kind == TypeKind.INTERSECTION) {
+
+      switch (source.getKind()) {
+        case TYPEVAR:
+          source = ((AnnotatedTypeVariable) source).getUpperBound();
+          break;
+
+        case WILDCARD:
+          source = ((AnnotatedWildcardType) source).getExtendsBound();
+          break;
+
+        case INTERSECTION:
+          // if there are multiple conflicting annotations, choose the lowest
+          final Set<AnnotationMirror> glb =
+              glbOfBounds((AnnotatedIntersectionType) source, qualifierHierarchy);
+          return glb;
+
+        default:
+          throw new BugInCF(
+              "Unexpected AnnotatedTypeMirror with no primary annotation;"
+                  + " toSearch="
+                  + toSearch
+                  + " source="
+                  + source);
+      }
+
+      kind = source.getKind();
+    }
+
+    return source.getAnnotations();
+  }
+
+  private static AnnotationMirror glbOfBoundsInHierarchy(
+      final AnnotatedIntersectionType isect,
+      final AnnotationMirror top,
+      final QualifierHierarchy qualifierHierarchy) {
+    AnnotationMirror anno = isect.getAnnotationInHierarchy(top);
+    for (AnnotatedTypeMirror bound : isect.getBounds()) {
+      AnnotationMirror boundAnno = bound.getAnnotationInHierarchy(top);
+      if (boundAnno != null && (anno == null || qualifierHierarchy.isSubtype(boundAnno, anno))) {
+        anno = boundAnno;
+      }
+    }
+
+    return anno;
+  }
+
+  /**
+   * Gets the lowest primary annotation of all bounds in the intersection.
+   *
+   * @param isect the intersection for which we are glbing bounds
+   * @param qualifierHierarchy the qualifier used to get the hierarchies in which to glb
+   * @return a set of annotations representing the glb of the intersection's bounds
+   */
+  public static Set<AnnotationMirror> glbOfBounds(
+      final AnnotatedIntersectionType isect, final QualifierHierarchy qualifierHierarchy) {
+    Set<AnnotationMirror> result = AnnotationUtils.createAnnotationSet();
+    for (final AnnotationMirror top : qualifierHierarchy.getTopAnnotations()) {
+      final AnnotationMirror glbAnno = glbOfBoundsInHierarchy(isect, top, qualifierHierarchy);
+      if (glbAnno != null) {
+        result.add(glbAnno);
+      }
+    }
+
+    return result;
+  }
+
+  // For Wildcards, isSuperBound and isExtendsBound will return true if isUnbound does.
+
+  public static boolean isExplicitlySuperBounded(final AnnotatedWildcardType wildcardType) {
+    return ((Type.WildcardType) wildcardType.getUnderlyingType()).isSuperBound()
+        && !((Type.WildcardType) wildcardType.getUnderlyingType()).isUnbound();
+  }
+
+  /** Returns true if wildcard type was explicitly unbounded. */
+  public static boolean isExplicitlyExtendsBounded(final AnnotatedWildcardType wildcardType) {
+    return ((Type.WildcardType) wildcardType.getUnderlyingType()).isExtendsBound()
+        && !((Type.WildcardType) wildcardType.getUnderlyingType()).isUnbound();
+  }
+
+  /** Returns true if this type is super bounded or unbounded. */
+  public static boolean isUnboundedOrSuperBounded(final AnnotatedWildcardType wildcardType) {
+    return ((Type.WildcardType) wildcardType.getUnderlyingType()).isSuperBound();
+  }
+
+  /** Returns true if this type is extends bounded or unbounded. */
+  public static boolean isUnboundedOrExtendsBounded(final AnnotatedWildcardType wildcardType) {
+    return ((Type.WildcardType) wildcardType.getUnderlyingType()).isExtendsBound();
+  }
+
+  /**
+   * Copies explicit annotations and annotations resulting from resolution of polymorphic qualifiers
+   * from {@code constructor} to {@code returnType}. If {@code returnType} has an annotation in the
+   * same hierarchy of an annotation to be copied, that annotation is not copied.
+   *
+   * @param atypeFactory type factory
+   * @param returnType return type to copy annotations to
+   * @param constructor the ATM for the constructor
+   */
+  public static void copyOnlyExplicitConstructorAnnotations(
+      AnnotatedTypeFactory atypeFactory,
+      AnnotatedDeclaredType returnType,
+      AnnotatedExecutableType constructor) {
+
+    // TODO: There will be a nicer way to access this in 308 soon.
+    List<Attribute.TypeCompound> decall =
+        ((Symbol) constructor.getElement()).getRawTypeAttributes();
+    Set<AnnotationMirror> decret = AnnotationUtils.createAnnotationSet();
+    for (Attribute.TypeCompound da : decall) {
+      if (da.position.type == com.sun.tools.javac.code.TargetType.METHOD_RETURN) {
+        decret.add(da);
+      }
+    }
+
+    // Collect all polymorphic qualifiers; we should substitute them.
+    Set<AnnotationMirror> polys = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror anno : returnType.getAnnotations()) {
+      if (atypeFactory.getQualifierHierarchy().isPolymorphicQualifier(anno)) {
+        polys.add(anno);
+      }
+    }
+
+    for (AnnotationMirror cta : constructor.getReturnType().getAnnotations()) {
+      AnnotationMirror ctatop = atypeFactory.getQualifierHierarchy().getTopAnnotation(cta);
+      if (returnType.isAnnotatedInHierarchy(cta)) {
+        continue;
+      }
+      if (atypeFactory.isSupportedQualifier(cta) && !returnType.isAnnotatedInHierarchy(cta)) {
+        for (AnnotationMirror fromDecl : decret) {
+          if (atypeFactory.isSupportedQualifier(fromDecl)
+              && AnnotationUtils.areSame(
+                  ctatop, atypeFactory.getQualifierHierarchy().getTopAnnotation(fromDecl))) {
+            returnType.addAnnotation(cta);
+            break;
+          }
+        }
+      }
+
+      // Go through the polymorphic qualifiers and see whether
+      // there is anything left to replace.
+      for (AnnotationMirror pa : polys) {
+        if (AnnotationUtils.areSame(
+            ctatop, atypeFactory.getQualifierHierarchy().getTopAnnotation(pa))) {
+          returnType.replaceAnnotation(cta);
+          break;
+        }
+      }
+    }
+  }
+
+  /**
+   * Add all the annotations in {@code declaredType} to {@code annotatedDeclaredType}.
+   *
+   * <p>(The {@code TypeMirror} returned by {@code annotatedDeclaredType#getUnderlyingType} may have
+   * not have all the annotations on the type, so allow the user to specify a different one.)
+   *
+   * @param annotatedDeclaredType annotated type to which annotations are added
+   * @param declaredType TypeMirror that may have annotations
+   */
+  public static void applyAnnotationsFromDeclaredType(
+      AnnotatedDeclaredType annotatedDeclaredType, DeclaredType declaredType) {
+    TypeMirror underlyingTypeMirror = declaredType;
+    while (annotatedDeclaredType != null) {
+      List<? extends AnnotationMirror> annosOnTypeMirror =
+          underlyingTypeMirror.getAnnotationMirrors();
+      annotatedDeclaredType.addAnnotations(annosOnTypeMirror);
+      annotatedDeclaredType = annotatedDeclaredType.getEnclosingType();
+      underlyingTypeMirror = ((DeclaredType) underlyingTypeMirror).getEnclosingType();
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotationMirrorMap.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotationMirrorMap.java
new file mode 100644
index 0000000..1ba6ec7
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotationMirrorMap.java
@@ -0,0 +1,131 @@
+package org.checkerframework.framework.util;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * The Map interface defines some of its methods with respect to the equals method. This
+ * implementation of Map violates those specifications, but fulfills the same property using {@link
+ * AnnotationUtils#areSame}.
+ *
+ * <p>For example, the specification for the containsKey(Object key) method says: "returns true if
+ * and only if this map contains a mapping for a key k such that (key == null ? k == null :
+ * key.equals(k))." The specification for {@link AnnotationMirrorMap#containsKey} is "returns true
+ * if and only if this map contains a mapping for a key k such that (key == null ? k == null :
+ * AnnotationUtils.areSame(key, k))."
+ *
+ * <p>AnnotationMirror is an interface and not all implementing classes provide a correct equals
+ * method; therefore, existing implementations of Map cannot be used.
+ */
+public class AnnotationMirrorMap<V> implements Map<AnnotationMirror, V> {
+
+  /** The actual map to which all work is delegated. */
+  private final Map<AnnotationMirror, V> shadowMap;
+
+  /** Default constructor. */
+  public AnnotationMirrorMap() {
+    this.shadowMap = new TreeMap<>(AnnotationUtils::compareAnnotationMirrors);
+  }
+
+  /**
+   * Creates an annotation mirror map and adds all the mappings in {@code copy}.
+   *
+   * @param copy a map whose contents should be copied to the newly created map
+   */
+  public AnnotationMirrorMap(Map<AnnotationMirror, ? extends V> copy) {
+    this();
+    this.putAll(copy);
+  }
+
+  @Override
+  public int size() {
+    return shadowMap.size();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return shadowMap.isEmpty();
+  }
+
+  @Override
+  public boolean containsKey(Object key) {
+    if (key instanceof AnnotationMirror) {
+      return AnnotationUtils.containsSame(shadowMap.keySet(), (AnnotationMirror) key);
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public boolean containsValue(Object value) {
+    return shadowMap.containsValue(value);
+  }
+
+  @Override
+  public V get(Object key) {
+    if (key instanceof AnnotationMirror) {
+      AnnotationMirror keyAnno =
+          AnnotationUtils.getSame(shadowMap.keySet(), (AnnotationMirror) key);
+      if (keyAnno != null) {
+        return shadowMap.get(keyAnno);
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public V put(AnnotationMirror key, V value) {
+    V pre = get(key);
+    remove(key);
+    shadowMap.put(key, value);
+    return pre;
+  }
+
+  @Override
+  public V remove(Object key) {
+    if (key instanceof AnnotationMirror) {
+      AnnotationMirror keyAnno =
+          AnnotationUtils.getSame(shadowMap.keySet(), (AnnotationMirror) key);
+      if (keyAnno != null) {
+        return shadowMap.remove(keyAnno);
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public void putAll(Map<? extends AnnotationMirror, ? extends V> m) {
+    for (Map.Entry<? extends AnnotationMirror, ? extends V> entry : m.entrySet()) {
+      put(entry.getKey(), entry.getValue());
+    }
+  }
+
+  @Override
+  public void clear() {
+    shadowMap.clear();
+  }
+
+  @Override
+  public Set<AnnotationMirror> keySet() {
+    return new AnnotationMirrorSet(shadowMap.keySet());
+  }
+
+  @Override
+  public Collection<V> values() {
+    return shadowMap.values();
+  }
+
+  @Override
+  public Set<Map.Entry<AnnotationMirror, V>> entrySet() {
+    return shadowMap.entrySet();
+  }
+
+  @Override
+  public String toString() {
+    return shadowMap.toString();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotationMirrorSet.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotationMirrorSet.java
new file mode 100644
index 0000000..8cc5c43
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotationMirrorSet.java
@@ -0,0 +1,155 @@
+package org.checkerframework.framework.util;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * The Set interface defines many methods with respect to the equals method. This implementation of
+ * Set violates those specifications, but fulfills the same property using {@link
+ * AnnotationUtils#areSame} rather than equals.
+ *
+ * <p>For example, the specification for the contains(Object o) method says: "returns true if and
+ * only if this collection contains at least one element e such that (o == null ? e == null :
+ * o.equals(e))." The specification for {@link AnnotationMirrorSet#contains} is "returns true if and
+ * only if this collection contains at least one element e such that (o == null ? e == null :
+ * AnnotationUtils.areSame(o, e))".
+ *
+ * <p>AnnotationMirror is an interface and not all implementing classes provide a correct equals
+ * method; therefore, the existing implementations of Set cannot be used.
+ */
+public class AnnotationMirrorSet implements Set<AnnotationMirror> {
+
+  /** Backing set. */
+  private Set<AnnotationMirror> shadowSet =
+      new TreeSet<>(AnnotationUtils::compareAnnotationMirrors);
+
+  /** Default constructor. */
+  public AnnotationMirrorSet() {}
+
+  public AnnotationMirrorSet(Collection<? extends AnnotationMirror> values) {
+    this();
+    this.addAll(values);
+  }
+
+  @Override
+  public int size() {
+    return shadowSet.size();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return shadowSet.isEmpty();
+  }
+
+  @Override
+  public boolean contains(Object o) {
+    return o instanceof AnnotationMirror
+        && AnnotationUtils.containsSame(shadowSet, (AnnotationMirror) o);
+  }
+
+  @Override
+  public Iterator<AnnotationMirror> iterator() {
+    return shadowSet.iterator();
+  }
+
+  @Override
+  public Object[] toArray() {
+    return shadowSet.toArray();
+  }
+
+  @Override
+  public <T> T[] toArray(T[] a) {
+    return shadowSet.toArray(a);
+  }
+
+  @Override
+  public boolean add(AnnotationMirror annotationMirror) {
+    if (contains(annotationMirror)) {
+      return false;
+    }
+    shadowSet.add(annotationMirror);
+    return true;
+  }
+
+  @Override
+  public boolean remove(Object o) {
+    if (o instanceof AnnotationMirror) {
+      AnnotationMirror found = AnnotationUtils.getSame(shadowSet, (AnnotationMirror) o);
+      return found != null && shadowSet.remove(found);
+    }
+    return false;
+  }
+
+  @Override
+  public boolean containsAll(Collection<?> c) {
+    for (Object o : c) {
+      if (!contains(o)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public boolean addAll(Collection<? extends AnnotationMirror> c) {
+    boolean result = true;
+    for (AnnotationMirror a : c) {
+      if (!add(a)) {
+        result = false;
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public boolean retainAll(Collection<?> c) {
+    Set<AnnotationMirror> newSet = new TreeSet<>(AnnotationUtils::compareAnnotationMirrors);
+    for (Object o : c) {
+      if (contains(o)) {
+        newSet.add((AnnotationMirror) o);
+      }
+    }
+    if (newSet.size() != shadowSet.size()) {
+      shadowSet = newSet;
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public boolean removeAll(Collection<?> c) {
+    boolean result = true;
+    for (Object a : c) {
+      if (!remove(a)) {
+        result = false;
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public void clear() {
+    shadowSet.clear();
+  }
+
+  /**
+   * Returns a new {@link AnnotationMirrorSet} that contains {@code value}.
+   *
+   * @param value AnnotationMirror to put in the set
+   * @return a new {@link AnnotationMirrorSet} that contains {@code value}
+   */
+  public static AnnotationMirrorSet singleElementSet(AnnotationMirror value) {
+    AnnotationMirrorSet newSet = new AnnotationMirrorSet();
+    newSet.add(value);
+    return newSet;
+  }
+
+  @Override
+  public String toString() {
+    return shadowSet.toString();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java b/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java
new file mode 100644
index 0000000..499d947
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java
@@ -0,0 +1,667 @@
+package org.checkerframework.framework.util;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AtmComboVisitor;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * AtmKind should mirror TypeKind except that each member has a reference to the AnnotatedTypeMirror
+ * that would represents types of its kind.
+ *
+ * <p>Note: This class is only useful so that AtmCombo can look up combinations via the
+ * AtmKind.ordinal(). See AtmCombo.comboMap
+ */
+enum AtmKind {
+  ARRAY(AnnotatedArrayType.class),
+  DECLARED(AnnotatedDeclaredType.class),
+  EXECUTABLE(AnnotatedExecutableType.class),
+  INTERSECTION(AnnotatedIntersectionType.class),
+  NONE(AnnotatedNoType.class),
+  NULL(AnnotatedNullType.class),
+  PRIMITIVE(AnnotatedPrimitiveType.class),
+  TYPEVAR(AnnotatedTypeVariable.class),
+  UNION(AnnotatedUnionType.class),
+  WILDCARD(AnnotatedWildcardType.class);
+
+  // The AnnotatedTypeMirror subclass that represents types of this kind
+  public final Class<? extends AnnotatedTypeMirror> atmClass;
+
+  AtmKind(Class<? extends AnnotatedTypeMirror> atmClass) {
+    this.atmClass = atmClass;
+  }
+
+  /**
+   * Returns the AtmKind corresponding to the class of atm.
+   *
+   * @return the AtmKind corresponding to the class of atm
+   */
+  public static AtmKind valueOf(final AnnotatedTypeMirror atm) {
+    final Class<?> argClass = atm.getClass();
+
+    for (AtmKind atmKind : AtmKind.values()) {
+      final Class<?> kindClass = atmKind.atmClass;
+      if (argClass == kindClass) {
+        return atmKind;
+      }
+    }
+
+    throw new BugInCF("Unhandled AnnotatedTypeMirror ( " + atm.getClass() + " )");
+  }
+}
+
+/**
+ * An enum representing the cartesian product of the set of AtmKinds with itself. This represents
+ * all pair-wise combinations of AnnotatedTypeMirror subclasses. AtmCombo can be used in a switch to
+ * easily (and in a readable fashion) enumerate a subset of Atm pairs to handle. It is also used to
+ * execute AtmComboVisitor, which is a visitor of all possible combinations of AnnotatedTypeMirror
+ * subclasses.
+ *
+ * <p>For example:
+ *
+ * <pre>{@code
+ * switch (AtmCombo.valueOf(atm1, atm2)) {
+ *     case WILDCARD_WILDCARD:
+ *     case TYPEVAR_TYPEVAR:
+ *         doSomething(atm1, atm2);
+ *         break;
+ * }
+ * }</pre>
+ *
+ * @see AtmCombo#accept
+ */
+public enum AtmCombo {
+  ARRAY_ARRAY(AtmKind.ARRAY, AtmKind.ARRAY),
+  ARRAY_DECLARED(AtmKind.ARRAY, AtmKind.DECLARED),
+  ARRAY_EXECUTABLE(AtmKind.ARRAY, AtmKind.EXECUTABLE),
+  ARRAY_INTERSECTION(AtmKind.ARRAY, AtmKind.INTERSECTION),
+  ARRAY_NONE(AtmKind.ARRAY, AtmKind.NONE),
+  ARRAY_NULL(AtmKind.ARRAY, AtmKind.NULL),
+  ARRAY_PRIMITIVE(AtmKind.ARRAY, AtmKind.PRIMITIVE),
+  ARRAY_UNION(AtmKind.ARRAY, AtmKind.UNION),
+  ARRAY_TYPEVAR(AtmKind.ARRAY, AtmKind.TYPEVAR),
+  ARRAY_WILDCARD(AtmKind.ARRAY, AtmKind.WILDCARD),
+
+  DECLARED_ARRAY(AtmKind.DECLARED, AtmKind.ARRAY),
+  DECLARED_DECLARED(AtmKind.DECLARED, AtmKind.DECLARED),
+  DECLARED_EXECUTABLE(AtmKind.DECLARED, AtmKind.EXECUTABLE),
+  DECLARED_INTERSECTION(AtmKind.DECLARED, AtmKind.INTERSECTION),
+  DECLARED_NONE(AtmKind.DECLARED, AtmKind.NONE),
+  DECLARED_NULL(AtmKind.DECLARED, AtmKind.NULL),
+  DECLARED_PRIMITIVE(AtmKind.DECLARED, AtmKind.PRIMITIVE),
+  DECLARED_TYPEVAR(AtmKind.DECLARED, AtmKind.TYPEVAR),
+  DECLARED_UNION(AtmKind.DECLARED, AtmKind.UNION),
+  DECLARED_WILDCARD(AtmKind.DECLARED, AtmKind.WILDCARD),
+
+  EXECUTABLE_ARRAY(AtmKind.EXECUTABLE, AtmKind.ARRAY),
+  EXECUTABLE_DECLARED(AtmKind.EXECUTABLE, AtmKind.DECLARED),
+  EXECUTABLE_EXECUTABLE(AtmKind.EXECUTABLE, AtmKind.EXECUTABLE),
+  EXECUTABLE_INTERSECTION(AtmKind.EXECUTABLE, AtmKind.INTERSECTION),
+  EXECUTABLE_NONE(AtmKind.EXECUTABLE, AtmKind.NONE),
+  EXECUTABLE_NULL(AtmKind.EXECUTABLE, AtmKind.NULL),
+  EXECUTABLE_PRIMITIVE(AtmKind.EXECUTABLE, AtmKind.PRIMITIVE),
+  EXECUTABLE_TYPEVAR(AtmKind.EXECUTABLE, AtmKind.TYPEVAR),
+  EXECUTABLE_UNION(AtmKind.EXECUTABLE, AtmKind.UNION),
+  EXECUTABLE_WILDCARD(AtmKind.EXECUTABLE, AtmKind.WILDCARD),
+
+  INTERSECTION_ARRAY(AtmKind.INTERSECTION, AtmKind.ARRAY),
+  INTERSECTION_DECLARED(AtmKind.INTERSECTION, AtmKind.DECLARED),
+  INTERSECTION_EXECUTABLE(AtmKind.INTERSECTION, AtmKind.EXECUTABLE),
+  INTERSECTION_INTERSECTION(AtmKind.INTERSECTION, AtmKind.INTERSECTION),
+  INTERSECTION_NONE(AtmKind.INTERSECTION, AtmKind.NONE),
+  INTERSECTION_NULL(AtmKind.INTERSECTION, AtmKind.NULL),
+  INTERSECTION_PRIMITIVE(AtmKind.INTERSECTION, AtmKind.PRIMITIVE),
+  INTERSECTION_TYPEVAR(AtmKind.INTERSECTION, AtmKind.TYPEVAR),
+  INTERSECTION_UNION(AtmKind.INTERSECTION, AtmKind.UNION),
+  INTERSECTION_WILDCARD(AtmKind.INTERSECTION, AtmKind.WILDCARD),
+
+  NONE_ARRAY(AtmKind.NONE, AtmKind.ARRAY),
+  NONE_DECLARED(AtmKind.NONE, AtmKind.DECLARED),
+  NONE_EXECUTABLE(AtmKind.NONE, AtmKind.EXECUTABLE),
+  NONE_INTERSECTION(AtmKind.NONE, AtmKind.INTERSECTION),
+  NONE_NONE(AtmKind.NONE, AtmKind.NONE),
+  NONE_NULL(AtmKind.NONE, AtmKind.NULL),
+  NONE_PRIMITIVE(AtmKind.NONE, AtmKind.PRIMITIVE),
+  NONE_TYPEVAR(AtmKind.NONE, AtmKind.TYPEVAR),
+  NONE_UNION(AtmKind.NONE, AtmKind.UNION),
+  NONE_WILDCARD(AtmKind.NONE, AtmKind.WILDCARD),
+
+  NULL_ARRAY(AtmKind.NULL, AtmKind.ARRAY),
+  NULL_DECLARED(AtmKind.NULL, AtmKind.DECLARED),
+  NULL_EXECUTABLE(AtmKind.NULL, AtmKind.EXECUTABLE),
+  NULL_INTERSECTION(AtmKind.NULL, AtmKind.INTERSECTION),
+  NULL_NONE(AtmKind.NULL, AtmKind.NONE),
+  NULL_NULL(AtmKind.NULL, AtmKind.NULL),
+  NULL_PRIMITIVE(AtmKind.NULL, AtmKind.PRIMITIVE),
+  NULL_TYPEVAR(AtmKind.NULL, AtmKind.TYPEVAR),
+  NULL_UNION(AtmKind.NULL, AtmKind.UNION),
+  NULL_WILDCARD(AtmKind.NULL, AtmKind.WILDCARD),
+
+  PRIMITIVE_ARRAY(AtmKind.PRIMITIVE, AtmKind.ARRAY),
+  PRIMITIVE_DECLARED(AtmKind.PRIMITIVE, AtmKind.DECLARED),
+  PRIMITIVE_EXECUTABLE(AtmKind.PRIMITIVE, AtmKind.EXECUTABLE),
+  PRIMITIVE_INTERSECTION(AtmKind.PRIMITIVE, AtmKind.INTERSECTION),
+  PRIMITIVE_NONE(AtmKind.PRIMITIVE, AtmKind.NONE),
+  PRIMITIVE_NULL(AtmKind.PRIMITIVE, AtmKind.NULL),
+  PRIMITIVE_PRIMITIVE(AtmKind.PRIMITIVE, AtmKind.PRIMITIVE),
+  PRIMITIVE_TYPEVAR(AtmKind.PRIMITIVE, AtmKind.TYPEVAR),
+  PRIMITIVE_UNION(AtmKind.PRIMITIVE, AtmKind.UNION),
+  PRIMITIVE_WILDCARD(AtmKind.PRIMITIVE, AtmKind.WILDCARD),
+
+  TYPEVAR_ARRAY(AtmKind.TYPEVAR, AtmKind.ARRAY),
+  TYPEVAR_DECLARED(AtmKind.TYPEVAR, AtmKind.DECLARED),
+  TYPEVAR_EXECUTABLE(AtmKind.TYPEVAR, AtmKind.EXECUTABLE),
+  TYPEVAR_INTERSECTION(AtmKind.TYPEVAR, AtmKind.INTERSECTION),
+  TYPEVAR_NONE(AtmKind.TYPEVAR, AtmKind.NONE),
+  TYPEVAR_NULL(AtmKind.TYPEVAR, AtmKind.NULL),
+  TYPEVAR_PRIMITIVE(AtmKind.TYPEVAR, AtmKind.PRIMITIVE),
+  TYPEVAR_TYPEVAR(AtmKind.TYPEVAR, AtmKind.TYPEVAR),
+  TYPEVAR_UNION(AtmKind.TYPEVAR, AtmKind.UNION),
+  TYPEVAR_WILDCARD(AtmKind.TYPEVAR, AtmKind.WILDCARD),
+
+  UNION_ARRAY(AtmKind.UNION, AtmKind.ARRAY),
+  UNION_DECLARED(AtmKind.UNION, AtmKind.DECLARED),
+  UNION_EXECUTABLE(AtmKind.UNION, AtmKind.EXECUTABLE),
+  UNION_INTERSECTION(AtmKind.UNION, AtmKind.INTERSECTION),
+  UNION_NONE(AtmKind.UNION, AtmKind.NONE),
+  UNION_NULL(AtmKind.UNION, AtmKind.NULL),
+  UNION_PRIMITIVE(AtmKind.UNION, AtmKind.PRIMITIVE),
+  UNION_TYPEVAR(AtmKind.UNION, AtmKind.TYPEVAR),
+  UNION_UNION(AtmKind.UNION, AtmKind.UNION),
+  UNION_WILDCARD(AtmKind.UNION, AtmKind.WILDCARD),
+
+  WILDCARD_ARRAY(AtmKind.WILDCARD, AtmKind.ARRAY),
+  WILDCARD_DECLARED(AtmKind.WILDCARD, AtmKind.DECLARED),
+  WILDCARD_EXECUTABLE(AtmKind.WILDCARD, AtmKind.EXECUTABLE),
+  WILDCARD_INTERSECTION(AtmKind.WILDCARD, AtmKind.INTERSECTION),
+  WILDCARD_NONE(AtmKind.WILDCARD, AtmKind.NONE),
+  WILDCARD_NULL(AtmKind.WILDCARD, AtmKind.NULL),
+  WILDCARD_PRIMITIVE(AtmKind.WILDCARD, AtmKind.PRIMITIVE),
+  WILDCARD_TYPEVAR(AtmKind.WILDCARD, AtmKind.TYPEVAR),
+  WILDCARD_UNION(AtmKind.WILDCARD, AtmKind.UNION),
+  WILDCARD_WILDCARD(AtmKind.WILDCARD, AtmKind.WILDCARD);
+
+  /** First AtmKind. */
+  public final AtmKind type1Kind;
+  /** Second AtmKind. */
+  public final AtmKind type2Kind;
+
+  /**
+   * Creates an AtmCombo.
+   *
+   * @param type1Kind first kind
+   * @param type2Kind second kind
+   */
+  AtmCombo(final AtmKind type1Kind, AtmKind type2Kind) {
+    this.type1Kind = type1Kind;
+    this.type2Kind = type2Kind;
+  }
+
+  /**
+   * Used to locate AtmCombo pairs using AtmKinds as indices into a two-dimensional array. This
+   * ensures that all pairs are included.
+   */
+  private static final AtmCombo[][] comboMap =
+      new AtmCombo[AtmKind.values().length][AtmKind.values().length];
+
+  static {
+    for (final AtmCombo atmCombo : AtmCombo.values()) {
+      comboMap[atmCombo.type1Kind.ordinal()][atmCombo.type2Kind.ordinal()] = atmCombo;
+    }
+  }
+
+  /**
+   * Returns the AtmCombo corresponding to the given ATM pair of the given ATMKinds. e.g. {@literal
+   * (AtmKind.NULL, AtmKind.EXECUTABLE) => AtmCombo.NULL_EXECUTABLE}.
+   *
+   * @return the AtmCombo corresponding to the given ATM pair of the given ATMKinds. e.g. {@literal
+   *     (AtmKind.NULL, AtmKind.EXECUTABLE) => AtmCombo.NULL_EXECUTABLE}
+   */
+  public static AtmCombo valueOf(final AtmKind type1, final AtmKind type2) {
+    return comboMap[type1.ordinal()][type2.ordinal()];
+  }
+
+  /**
+   * Returns the AtmCombo corresponding to the pair of the classes for the given
+   * AnnotatedTypeMirrors. e.g. {@literal (AnnotatedPrimitiveType, AnnotatedDeclaredType) =>
+   * AtmCombo.PRIMITIVE_DECLARED}
+   *
+   * @return the AtmCombo corresponding to the pair of the classes for the given
+   *     AnnotatedTypeMirrors
+   */
+  public static AtmCombo valueOf(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
+    return valueOf(AtmKind.valueOf(type1), AtmKind.valueOf(type2));
+  }
+
+  /**
+   * Call the visit method that corresponds to the AtmCombo that represents the classes of type1 and
+   * type2. That is, get the combo for type1 and type 2, use it to identify the correct visitor
+   * method, and call that method with type1, type2, and initialParam as arguments to the visit
+   * method.
+   *
+   * @param type1 first argument to the called visit method
+   * @param type2 second argument to the called visit method
+   * @param initialParam the parameter passed to the called visit method
+   * @param visitor the visitor that is visiting the given types
+   * @param <RETURN_TYPE> the return type of the visitor's visit methods
+   * @param <PARAM> the parameter type of the visitor's visit methods
+   * @return the return value of the visit method called
+   */
+  public static <RETURN_TYPE, PARAM> RETURN_TYPE accept(
+      final AnnotatedTypeMirror type1,
+      final AnnotatedTypeMirror type2,
+      final PARAM initialParam,
+      final AtmComboVisitor<RETURN_TYPE, PARAM> visitor) {
+    final AtmCombo combo = valueOf(type1, type2);
+    switch (combo) {
+      case ARRAY_ARRAY:
+        return visitor.visitArray_Array(
+            (AnnotatedArrayType) type1, (AnnotatedArrayType) type2, initialParam);
+
+      case ARRAY_DECLARED:
+        return visitor.visitArray_Declared(
+            (AnnotatedArrayType) type1, (AnnotatedDeclaredType) type2, initialParam);
+
+      case ARRAY_EXECUTABLE:
+        return visitor.visitArray_Executable(
+            (AnnotatedArrayType) type1, (AnnotatedExecutableType) type2, initialParam);
+
+      case ARRAY_INTERSECTION:
+        return visitor.visitArray_Intersection(
+            (AnnotatedArrayType) type1, (AnnotatedIntersectionType) type2, initialParam);
+
+      case ARRAY_NONE:
+        return visitor.visitArray_None(
+            (AnnotatedArrayType) type1, (AnnotatedNoType) type2, initialParam);
+
+      case ARRAY_NULL:
+        return visitor.visitArray_Null(
+            (AnnotatedArrayType) type1, (AnnotatedNullType) type2, initialParam);
+
+      case ARRAY_PRIMITIVE:
+        return visitor.visitArray_Primitive(
+            (AnnotatedArrayType) type1, (AnnotatedPrimitiveType) type2, initialParam);
+
+      case ARRAY_TYPEVAR:
+        return visitor.visitArray_Typevar(
+            (AnnotatedArrayType) type1, (AnnotatedTypeVariable) type2, initialParam);
+
+      case ARRAY_UNION:
+        return visitor.visitArray_Union(
+            (AnnotatedArrayType) type1, (AnnotatedUnionType) type2, initialParam);
+
+      case ARRAY_WILDCARD:
+        return visitor.visitArray_Wildcard(
+            (AnnotatedArrayType) type1, (AnnotatedWildcardType) type2, initialParam);
+
+      case DECLARED_ARRAY:
+        return visitor.visitDeclared_Array(
+            (AnnotatedDeclaredType) type1, (AnnotatedArrayType) type2, initialParam);
+
+      case DECLARED_DECLARED:
+        return visitor.visitDeclared_Declared(
+            (AnnotatedDeclaredType) type1, (AnnotatedDeclaredType) type2, initialParam);
+
+      case DECLARED_EXECUTABLE:
+        return visitor.visitDeclared_Executable(
+            (AnnotatedDeclaredType) type1, (AnnotatedExecutableType) type2, initialParam);
+
+      case DECLARED_INTERSECTION:
+        return visitor.visitDeclared_Intersection(
+            (AnnotatedDeclaredType) type1, (AnnotatedIntersectionType) type2, initialParam);
+
+      case DECLARED_NONE:
+        return visitor.visitDeclared_None(
+            (AnnotatedDeclaredType) type1, (AnnotatedNoType) type2, initialParam);
+
+      case DECLARED_NULL:
+        return visitor.visitDeclared_Null(
+            (AnnotatedDeclaredType) type1, (AnnotatedNullType) type2, initialParam);
+
+      case DECLARED_PRIMITIVE:
+        return visitor.visitDeclared_Primitive(
+            (AnnotatedDeclaredType) type1, (AnnotatedPrimitiveType) type2, initialParam);
+
+      case DECLARED_TYPEVAR:
+        return visitor.visitDeclared_Typevar(
+            (AnnotatedDeclaredType) type1, (AnnotatedTypeVariable) type2, initialParam);
+
+      case DECLARED_UNION:
+        return visitor.visitDeclared_Union(
+            (AnnotatedDeclaredType) type1, (AnnotatedUnionType) type2, initialParam);
+
+      case DECLARED_WILDCARD:
+        return visitor.visitDeclared_Wildcard(
+            (AnnotatedDeclaredType) type1, (AnnotatedWildcardType) type2, initialParam);
+
+      case EXECUTABLE_ARRAY:
+        return visitor.visitExecutable_Array(
+            (AnnotatedExecutableType) type1, (AnnotatedArrayType) type2, initialParam);
+
+      case EXECUTABLE_DECLARED:
+        return visitor.visitExecutable_Declared(
+            (AnnotatedExecutableType) type1, (AnnotatedDeclaredType) type2, initialParam);
+
+      case EXECUTABLE_EXECUTABLE:
+        return visitor.visitExecutable_Executable(
+            (AnnotatedExecutableType) type1, (AnnotatedExecutableType) type2, initialParam);
+
+      case EXECUTABLE_INTERSECTION:
+        return visitor.visitExecutable_Intersection(
+            (AnnotatedExecutableType) type1, (AnnotatedIntersectionType) type2, initialParam);
+
+      case EXECUTABLE_NONE:
+        return visitor.visitExecutable_None(
+            (AnnotatedExecutableType) type1, (AnnotatedNoType) type2, initialParam);
+
+      case EXECUTABLE_NULL:
+        return visitor.visitExecutable_Null(
+            (AnnotatedExecutableType) type1, (AnnotatedNullType) type2, initialParam);
+
+      case EXECUTABLE_PRIMITIVE:
+        return visitor.visitExecutable_Primitive(
+            (AnnotatedExecutableType) type1, (AnnotatedPrimitiveType) type2, initialParam);
+
+      case EXECUTABLE_TYPEVAR:
+        return visitor.visitExecutable_Typevar(
+            (AnnotatedExecutableType) type1, (AnnotatedTypeVariable) type2, initialParam);
+
+      case EXECUTABLE_UNION:
+        return visitor.visitExecutable_Union(
+            (AnnotatedExecutableType) type1, (AnnotatedUnionType) type2, initialParam);
+
+      case EXECUTABLE_WILDCARD:
+        return visitor.visitExecutable_Wildcard(
+            (AnnotatedExecutableType) type1, (AnnotatedWildcardType) type2, initialParam);
+
+      case INTERSECTION_ARRAY:
+        return visitor.visitIntersection_Array(
+            (AnnotatedIntersectionType) type1, (AnnotatedArrayType) type2, initialParam);
+
+      case INTERSECTION_DECLARED:
+        return visitor.visitIntersection_Declared(
+            (AnnotatedIntersectionType) type1, (AnnotatedDeclaredType) type2, initialParam);
+
+      case INTERSECTION_EXECUTABLE:
+        return visitor.visitIntersection_Executable(
+            (AnnotatedIntersectionType) type1, (AnnotatedExecutableType) type2, initialParam);
+
+      case INTERSECTION_INTERSECTION:
+        return visitor.visitIntersection_Intersection(
+            (AnnotatedIntersectionType) type1, (AnnotatedIntersectionType) type2, initialParam);
+
+      case INTERSECTION_NONE:
+        return visitor.visitIntersection_None(
+            (AnnotatedIntersectionType) type1, (AnnotatedNoType) type2, initialParam);
+
+      case INTERSECTION_NULL:
+        return visitor.visitIntersection_Null(
+            (AnnotatedIntersectionType) type1, (AnnotatedNullType) type2, initialParam);
+
+      case INTERSECTION_PRIMITIVE:
+        return visitor.visitIntersection_Primitive(
+            (AnnotatedIntersectionType) type1, (AnnotatedPrimitiveType) type2, initialParam);
+
+      case INTERSECTION_TYPEVAR:
+        return visitor.visitIntersection_Typevar(
+            (AnnotatedIntersectionType) type1, (AnnotatedTypeVariable) type2, initialParam);
+
+      case INTERSECTION_UNION:
+        return visitor.visitIntersection_Union(
+            (AnnotatedIntersectionType) type1, (AnnotatedUnionType) type2, initialParam);
+
+      case INTERSECTION_WILDCARD:
+        return visitor.visitIntersection_Wildcard(
+            (AnnotatedIntersectionType) type1, (AnnotatedWildcardType) type2, initialParam);
+
+      case NONE_ARRAY:
+        return visitor.visitNone_Array(
+            (AnnotatedNoType) type1, (AnnotatedArrayType) type2, initialParam);
+
+      case NONE_DECLARED:
+        return visitor.visitNone_Declared(
+            (AnnotatedNoType) type1, (AnnotatedDeclaredType) type2, initialParam);
+
+      case NONE_EXECUTABLE:
+        return visitor.visitNone_Executable(
+            (AnnotatedNoType) type1, (AnnotatedExecutableType) type2, initialParam);
+
+      case NONE_INTERSECTION:
+        return visitor.visitNone_Intersection(
+            (AnnotatedNoType) type1, (AnnotatedIntersectionType) type2, initialParam);
+
+      case NONE_NONE:
+        return visitor.visitNone_None(
+            (AnnotatedNoType) type1, (AnnotatedNoType) type2, initialParam);
+
+      case NONE_NULL:
+        return visitor.visitNone_Null(
+            (AnnotatedNoType) type1, (AnnotatedNullType) type2, initialParam);
+
+      case NONE_PRIMITIVE:
+        return visitor.visitNone_Primitive(
+            (AnnotatedNoType) type1, (AnnotatedPrimitiveType) type2, initialParam);
+
+      case NONE_UNION:
+        return visitor.visitNone_Union(
+            (AnnotatedNoType) type1, (AnnotatedUnionType) type2, initialParam);
+
+      case NONE_WILDCARD:
+        return visitor.visitNone_Wildcard(
+            (AnnotatedNoType) type1, (AnnotatedWildcardType) type2, initialParam);
+
+      case NULL_ARRAY:
+        return visitor.visitNull_Array(
+            (AnnotatedNullType) type1, (AnnotatedArrayType) type2, initialParam);
+
+      case NULL_DECLARED:
+        return visitor.visitNull_Declared(
+            (AnnotatedNullType) type1, (AnnotatedDeclaredType) type2, initialParam);
+
+      case NULL_EXECUTABLE:
+        return visitor.visitNull_Executable(
+            (AnnotatedNullType) type1, (AnnotatedExecutableType) type2, initialParam);
+
+      case NULL_INTERSECTION:
+        return visitor.visitNull_Intersection(
+            (AnnotatedNullType) type1, (AnnotatedIntersectionType) type2, initialParam);
+
+      case NULL_NONE:
+        return visitor.visitNull_None(
+            (AnnotatedNullType) type1, (AnnotatedNoType) type2, initialParam);
+
+      case NULL_NULL:
+        return visitor.visitNull_Null(
+            (AnnotatedNullType) type1, (AnnotatedNullType) type2, initialParam);
+
+      case NULL_PRIMITIVE:
+        return visitor.visitNull_Primitive(
+            (AnnotatedNullType) type1, (AnnotatedPrimitiveType) type2, initialParam);
+
+      case NULL_TYPEVAR:
+        return visitor.visitNull_Typevar(
+            (AnnotatedNullType) type1, (AnnotatedTypeVariable) type2, initialParam);
+
+      case NULL_UNION:
+        return visitor.visitNull_Union(
+            (AnnotatedNullType) type1, (AnnotatedUnionType) type2, initialParam);
+
+      case NULL_WILDCARD:
+        return visitor.visitNull_Wildcard(
+            (AnnotatedNullType) type1, (AnnotatedWildcardType) type2, initialParam);
+
+      case PRIMITIVE_ARRAY:
+        return visitor.visitPrimitive_Array(
+            (AnnotatedPrimitiveType) type1, (AnnotatedArrayType) type2, initialParam);
+
+      case PRIMITIVE_DECLARED:
+        return visitor.visitPrimitive_Declared(
+            (AnnotatedPrimitiveType) type1, (AnnotatedDeclaredType) type2, initialParam);
+
+      case PRIMITIVE_EXECUTABLE:
+        return visitor.visitPrimitive_Executable(
+            (AnnotatedPrimitiveType) type1, (AnnotatedExecutableType) type2, initialParam);
+
+      case PRIMITIVE_INTERSECTION:
+        return visitor.visitPrimitive_Intersection(
+            (AnnotatedPrimitiveType) type1, (AnnotatedIntersectionType) type2, initialParam);
+
+      case PRIMITIVE_NONE:
+        return visitor.visitPrimitive_None(
+            (AnnotatedPrimitiveType) type1, (AnnotatedNoType) type2, initialParam);
+
+      case PRIMITIVE_NULL:
+        return visitor.visitPrimitive_Null(
+            (AnnotatedPrimitiveType) type1, (AnnotatedNullType) type2, initialParam);
+
+      case PRIMITIVE_PRIMITIVE:
+        return visitor.visitPrimitive_Primitive(
+            (AnnotatedPrimitiveType) type1, (AnnotatedPrimitiveType) type2, initialParam);
+
+      case PRIMITIVE_TYPEVAR:
+        return visitor.visitPrimitive_Typevar(
+            (AnnotatedPrimitiveType) type1, (AnnotatedTypeVariable) type2, initialParam);
+
+      case PRIMITIVE_UNION:
+        return visitor.visitPrimitive_Union(
+            (AnnotatedPrimitiveType) type1, (AnnotatedUnionType) type2, initialParam);
+
+      case PRIMITIVE_WILDCARD:
+        return visitor.visitPrimitive_Wildcard(
+            (AnnotatedPrimitiveType) type1, (AnnotatedWildcardType) type2, initialParam);
+
+      case UNION_ARRAY:
+        return visitor.visitUnion_Array(
+            (AnnotatedUnionType) type1, (AnnotatedArrayType) type2, initialParam);
+
+      case UNION_DECLARED:
+        return visitor.visitUnion_Declared(
+            (AnnotatedUnionType) type1, (AnnotatedDeclaredType) type2, initialParam);
+
+      case UNION_EXECUTABLE:
+        return visitor.visitUnion_Executable(
+            (AnnotatedUnionType) type1, (AnnotatedExecutableType) type2, initialParam);
+
+      case UNION_INTERSECTION:
+        return visitor.visitUnion_Intersection(
+            (AnnotatedUnionType) type1, (AnnotatedIntersectionType) type2, initialParam);
+
+      case UNION_NONE:
+        return visitor.visitUnion_None(
+            (AnnotatedUnionType) type1, (AnnotatedNoType) type2, initialParam);
+
+      case UNION_NULL:
+        return visitor.visitUnion_Null(
+            (AnnotatedUnionType) type1, (AnnotatedNullType) type2, initialParam);
+
+      case UNION_PRIMITIVE:
+        return visitor.visitUnion_Primitive(
+            (AnnotatedUnionType) type1, (AnnotatedPrimitiveType) type2, initialParam);
+
+      case UNION_TYPEVAR:
+        return visitor.visitUnion_Typevar(
+            (AnnotatedUnionType) type1, (AnnotatedTypeVariable) type2, initialParam);
+
+      case UNION_UNION:
+        return visitor.visitUnion_Union(
+            (AnnotatedUnionType) type1, (AnnotatedUnionType) type2, initialParam);
+
+      case UNION_WILDCARD:
+        return visitor.visitUnion_Wildcard(
+            (AnnotatedUnionType) type1, (AnnotatedWildcardType) type2, initialParam);
+
+      case TYPEVAR_ARRAY:
+        return visitor.visitTypevar_Array(
+            (AnnotatedTypeVariable) type1, (AnnotatedArrayType) type2, initialParam);
+
+      case TYPEVAR_DECLARED:
+        return visitor.visitTypevar_Declared(
+            (AnnotatedTypeVariable) type1, (AnnotatedDeclaredType) type2, initialParam);
+
+      case TYPEVAR_EXECUTABLE:
+        return visitor.visitTypevar_Executable(
+            (AnnotatedTypeVariable) type1, (AnnotatedExecutableType) type2, initialParam);
+
+      case TYPEVAR_INTERSECTION:
+        return visitor.visitTypevar_Intersection(
+            (AnnotatedTypeVariable) type1, (AnnotatedIntersectionType) type2, initialParam);
+
+      case TYPEVAR_NONE:
+        return visitor.visitTypevar_None(
+            (AnnotatedTypeVariable) type1, (AnnotatedNoType) type2, initialParam);
+
+      case TYPEVAR_NULL:
+        return visitor.visitTypevar_Null(
+            (AnnotatedTypeVariable) type1, (AnnotatedNullType) type2, initialParam);
+
+      case TYPEVAR_PRIMITIVE:
+        return visitor.visitTypevar_Primitive(
+            (AnnotatedTypeVariable) type1, (AnnotatedPrimitiveType) type2, initialParam);
+
+      case TYPEVAR_TYPEVAR:
+        return visitor.visitTypevar_Typevar(
+            (AnnotatedTypeVariable) type1, (AnnotatedTypeVariable) type2, initialParam);
+
+      case TYPEVAR_UNION:
+        return visitor.visitTypevar_Union(
+            (AnnotatedTypeVariable) type1, (AnnotatedUnionType) type2, initialParam);
+
+      case TYPEVAR_WILDCARD:
+        return visitor.visitTypevar_Wildcard(
+            (AnnotatedTypeVariable) type1, (AnnotatedWildcardType) type2, initialParam);
+
+      case WILDCARD_ARRAY:
+        return visitor.visitWildcard_Array(
+            (AnnotatedWildcardType) type1, (AnnotatedArrayType) type2, initialParam);
+
+      case WILDCARD_DECLARED:
+        return visitor.visitWildcard_Declared(
+            (AnnotatedWildcardType) type1, (AnnotatedDeclaredType) type2, initialParam);
+
+      case WILDCARD_EXECUTABLE:
+        return visitor.visitWildcard_Executable(
+            (AnnotatedWildcardType) type1, (AnnotatedExecutableType) type2, initialParam);
+
+      case WILDCARD_INTERSECTION:
+        return visitor.visitWildcard_Intersection(
+            (AnnotatedWildcardType) type1, (AnnotatedIntersectionType) type2, initialParam);
+
+      case WILDCARD_NONE:
+        return visitor.visitWildcard_None(
+            (AnnotatedWildcardType) type1, (AnnotatedNoType) type2, initialParam);
+
+      case WILDCARD_NULL:
+        return visitor.visitWildcard_Null(
+            (AnnotatedWildcardType) type1, (AnnotatedNullType) type2, initialParam);
+
+      case WILDCARD_PRIMITIVE:
+        return visitor.visitWildcard_Primitive(
+            (AnnotatedWildcardType) type1, (AnnotatedPrimitiveType) type2, initialParam);
+
+      case WILDCARD_TYPEVAR:
+        return visitor.visitWildcard_Typevar(
+            (AnnotatedWildcardType) type1, (AnnotatedTypeVariable) type2, initialParam);
+
+      case WILDCARD_UNION:
+        return visitor.visitWildcard_Union(
+            (AnnotatedWildcardType) type1, (AnnotatedUnionType) type2, initialParam);
+
+      case WILDCARD_WILDCARD:
+        return visitor.visitWildcard_Wildcard(
+            (AnnotatedWildcardType) type1, (AnnotatedWildcardType) type2, initialParam);
+
+      default:
+        // Reaching this point indicates that there is an AtmCombo missing
+        throw new BugInCF("Unhandled AtmCombo ( " + combo + " ) ");
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java
new file mode 100644
index 0000000..4c7a0ee
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java
@@ -0,0 +1,388 @@
+package org.checkerframework.framework.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.visitor.AbstractAtmComboVisitor;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypesUtils;
+
+/**
+ * Helper class to compute the least upper bound of two AnnotatedTypeMirrors.
+ *
+ * <p>This class should only be used by {@link AnnotatedTypes#leastUpperBound(AnnotatedTypeFactory,
+ * AnnotatedTypeMirror, AnnotatedTypeMirror)}.
+ */
+class AtmLubVisitor extends AbstractAtmComboVisitor<Void, AnnotatedTypeMirror> {
+
+  private final AnnotatedTypeFactory atypeFactory;
+  private final QualifierHierarchy qualifierHierarchy;
+  /**
+   * List of {@link AnnotatedTypeVariable} or {@link AnnotatedWildcardType} that have been visited.
+   * Call {@link #visited(AnnotatedTypeMirror)} to check if the type have been visited, so that
+   * reference equality is used rather than {@link #equals(Object)}.
+   */
+  private final List<AnnotatedTypeMirror> visited = new ArrayList<>();
+
+  AtmLubVisitor(AnnotatedTypeFactory atypeFactory) {
+    this.atypeFactory = atypeFactory;
+    this.qualifierHierarchy = atypeFactory.getQualifierHierarchy();
+  }
+
+  /**
+   * Returns an ATM that is the least upper bound of type1 and type2 and whose Java type is
+   * lubJavaType. lubJavaType must be a super type or convertible to the Java types of type1 and
+   * type2.
+   */
+  AnnotatedTypeMirror lub(
+      AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, TypeMirror lubJavaType) {
+    AnnotatedTypeMirror lub = AnnotatedTypeMirror.createType(lubJavaType, atypeFactory, false);
+
+    if (type1.getKind() == TypeKind.NULL) {
+      return lubWithNull((AnnotatedNullType) type1, type2, lub);
+    }
+    if (type2.getKind() == TypeKind.NULL) {
+      return lubWithNull((AnnotatedNullType) type2, type1, lub);
+    }
+
+    AnnotatedTypeMirror type1AsLub = AnnotatedTypes.asSuper(atypeFactory, type1, lub);
+    AnnotatedTypeMirror type2AsLub = AnnotatedTypes.asSuper(atypeFactory, type2, lub);
+
+    visit(type1AsLub, type2AsLub, lub);
+    visited.clear();
+    return lub;
+  }
+
+  private AnnotatedTypeMirror lubWithNull(
+      AnnotatedNullType nullType, AnnotatedTypeMirror otherType, AnnotatedTypeMirror lub) {
+    AnnotatedTypeMirror otherAsLub;
+    if (otherType.getKind() == TypeKind.NULL) {
+      otherAsLub = otherType.deepCopy();
+    } else {
+      otherAsLub = AnnotatedTypes.asSuper(atypeFactory, otherType, lub);
+    }
+
+    lub = otherAsLub.deepCopy();
+
+    if (otherAsLub.getKind() != TypeKind.TYPEVAR && otherAsLub.getKind() != TypeKind.WILDCARD) {
+      for (AnnotationMirror nullAnno : nullType.getAnnotations()) {
+        AnnotationMirror otherAnno = otherAsLub.getAnnotationInHierarchy(nullAnno);
+        AnnotationMirror lubAnno = qualifierHierarchy.leastUpperBound(nullAnno, otherAnno);
+        lub.replaceAnnotation(lubAnno);
+      }
+      return lub;
+    }
+
+    // LUB(@N null, T), where T's upper bound is @U and T's lower bound is @L
+    // if @L <: @U <: @N then LUB(@N null, T) = @N T
+    // if @L <: @N <:@U && @N != @L  then LUB(@N null, T) = @U T
+    // if @N <: @L <: @U             then LUB(@N null, T) =    T
+    Set<AnnotationMirror> lowerBounds =
+        AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualifierHierarchy, otherAsLub);
+    for (AnnotationMirror lowerBound : lowerBounds) {
+      AnnotationMirror nullAnno = nullType.getAnnotationInHierarchy(lowerBound);
+      AnnotationMirror upperBound = otherAsLub.getEffectiveAnnotationInHierarchy(lowerBound);
+      if (qualifierHierarchy.isSubtype(upperBound, nullAnno)) {
+        // @L <: @U <: @N
+        lub.replaceAnnotation(nullAnno);
+      } else if (qualifierHierarchy.isSubtype(lowerBound, nullAnno)
+          && !qualifierHierarchy.isSubtype(nullAnno, lowerBound)) {
+        // @L <: @N <:@U && @N != @L
+        lub.replaceAnnotation(upperBound);
+      } // else @N <: @L <: @U
+    }
+    return lub;
+  }
+
+  /**
+   * Replaces the primary annotations of lub with the lub of the primary annotations of type1 and
+   * type2.
+   */
+  private void lubPrimaryAnnotations(
+      AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) {
+    Set<? extends AnnotationMirror> lubSet;
+    if (type1.getAnnotations().isEmpty()) {
+      lubSet = type2.getAnnotations();
+    } else if (type2.getAnnotations().isEmpty()) {
+      lubSet = type1.getAnnotations();
+    } else {
+      lubSet = qualifierHierarchy.leastUpperBounds(type1.getAnnotations(), type2.getAnnotations());
+    }
+    lub.replaceAnnotations(lubSet);
+  }
+
+  /** Casts lub to the type of type and issues an error if type and lub are not the same kind. */
+  private <T extends AnnotatedTypeMirror> T castLub(T type, AnnotatedTypeMirror lub) {
+    if (type.getKind() != lub.getKind()) {
+      throw new BugInCF(
+          "AtmLubVisitor: unexpected type. Found: %s Required %s", lub.getKind(), type.getKind());
+    }
+    @SuppressWarnings("unchecked")
+    T castedLub = (T) lub;
+    return castedLub;
+  }
+
+  @Override
+  public Void visitNull_Null(
+      AnnotatedNullType type1, AnnotatedNullType type2, AnnotatedTypeMirror lub) {
+    // Called to issue warning
+    castLub(type1, lub);
+    lubPrimaryAnnotations(type1, type2, lub);
+    return null;
+  }
+
+  @Override
+  public Void visitArray_Array(
+      AnnotatedArrayType type1, AnnotatedArrayType type2, AnnotatedTypeMirror lub) {
+    AnnotatedArrayType lubArray = castLub(type1, lub);
+    lubPrimaryAnnotations(type1, type2, lubArray);
+
+    visit(type1.getComponentType(), type2.getComponentType(), lubArray.getComponentType());
+    return null;
+  }
+
+  @Override
+  public Void visitDeclared_Declared(
+      AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, AnnotatedTypeMirror lub) {
+    AnnotatedDeclaredType castedLub = castLub(type1, lub);
+
+    lubPrimaryAnnotations(type1, type2, lub);
+
+    if (lub.getKind() == TypeKind.DECLARED) {
+      AnnotatedDeclaredType enclosingLub = ((AnnotatedDeclaredType) lub).getEnclosingType();
+      AnnotatedDeclaredType enclosing1 = type1.getEnclosingType();
+      AnnotatedDeclaredType enclosing2 = type2.getEnclosingType();
+      if (enclosingLub != null && enclosing1 != null && enclosing2 != null) {
+        visitDeclared_Declared(enclosing1, enclosing2, enclosingLub);
+      }
+    }
+
+    for (int i = 0; i < type1.getTypeArguments().size(); i++) {
+      AnnotatedTypeMirror type1TypeArg = type1.getTypeArguments().get(i);
+      AnnotatedTypeMirror type2TypeArg = type2.getTypeArguments().get(i);
+      AnnotatedTypeMirror lubTypeArg = castedLub.getTypeArguments().get(i);
+      lubTypeArgument(type1TypeArg, type2TypeArg, lubTypeArg);
+    }
+    return null;
+  }
+
+  private void lubTypeArgument(
+      AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) {
+    // In lub(), asSuper is called on type1 and type2, but asSuper does not recur into type
+    // arguments, so call asSuper on the type arguments so that they have the same underlying type.
+    final AnnotatedTypeMirror type1AsLub = AnnotatedTypes.asSuper(atypeFactory, type1, lub);
+    final AnnotatedTypeMirror type2AsLub = AnnotatedTypes.asSuper(atypeFactory, type2, lub);
+
+    // If the type argument is a wildcard or captured wildcard, then the lub computation is
+    // slightly different.  The primary annotation on the lower bound is the glb of lower bounds
+    // of the type types.  This is because the lub of Gen<@A ? extends @A Object> and Gen<@B ?
+    // extends @A Object> is Gen<@B ? extends @A Object>.  If visit(type1AsLub, type2AsLub, lub)
+    // was called instead of the below code, then the lub would be Gen<@A ? extends @A Object>.
+    // (Note the lub of Gen<@A ? super @A Object> and Gen<@A ? super @B Object> does not exist,
+    // but Gen<@A ? super @B Object> is returned.)
+    if (lub.getKind() == TypeKind.WILDCARD) {
+      if (visited(lub)) {
+        return;
+      }
+      AnnotatedWildcardType type1Wildcard = (AnnotatedWildcardType) type1AsLub;
+      AnnotatedWildcardType type2Wildcard = (AnnotatedWildcardType) type2AsLub;
+      AnnotatedWildcardType lubWildcard = (AnnotatedWildcardType) lub;
+      if (type1Wildcard.isUninferredTypeArgument() || type2Wildcard.isUninferredTypeArgument()) {
+        lubWildcard.setUninferredTypeArgument();
+      }
+      lubWildcard(
+          type1Wildcard.getSuperBound(),
+          type1Wildcard.getExtendsBound(),
+          type2Wildcard.getSuperBound(),
+          type2Wildcard.getExtendsBound(),
+          lubWildcard.getSuperBound(),
+          lubWildcard.getExtendsBound());
+    } else if (lub.getKind() == TypeKind.TYPEVAR
+        && TypesUtils.isCaptured((TypeVariable) lub.getUnderlyingType())) {
+      if (visited(lub)) {
+        return;
+      }
+      AnnotatedTypeVariable type1typevar = (AnnotatedTypeVariable) type1AsLub;
+      AnnotatedTypeVariable type2typevar = (AnnotatedTypeVariable) type2AsLub;
+      AnnotatedTypeVariable lubTypevar = (AnnotatedTypeVariable) lub;
+      lubWildcard(
+          type1typevar.getLowerBound(),
+          type1typevar.getUpperBound(),
+          type2typevar.getLowerBound(),
+          type2typevar.getUpperBound(),
+          lubTypevar.getLowerBound(),
+          lubTypevar.getUpperBound());
+    } else {
+      // Don't add to visit history because that will happen in visitTypevar_Typevar or
+      // visitWildcard_Wildcard if needed.
+      visit(type1AsLub, type2AsLub, lub);
+    }
+  }
+
+  private void lubWildcard(
+      AnnotatedTypeMirror type1LowerBound,
+      AnnotatedTypeMirror type1UpperBound,
+      AnnotatedTypeMirror type2LowerBound,
+      AnnotatedTypeMirror type2UpperBound,
+      AnnotatedTypeMirror lubLowerBound,
+      AnnotatedTypeMirror lubUpperBound) {
+    visit(type1UpperBound, type2UpperBound, lubUpperBound);
+    visit(type1LowerBound, type2LowerBound, lubLowerBound);
+
+    for (AnnotationMirror top : qualifierHierarchy.getTopAnnotations()) {
+      AnnotationMirror anno1 = type1LowerBound.getAnnotationInHierarchy(top);
+      AnnotationMirror anno2 = type2LowerBound.getAnnotationInHierarchy(top);
+
+      if (anno1 != null && anno2 != null) {
+        AnnotationMirror glb = qualifierHierarchy.greatestLowerBound(anno1, anno2);
+        lubLowerBound.replaceAnnotation(glb);
+      }
+    }
+  }
+
+  @Override
+  public Void visitPrimitive_Primitive(
+      AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, AnnotatedTypeMirror lub) {
+    // Called to issue warning
+    castLub(type1, lub);
+    lubPrimaryAnnotations(type1, type2, lub);
+    return null;
+  }
+
+  @Override
+  public Void visitTypevar_Typevar(
+      AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, AnnotatedTypeMirror lub1) {
+    if (visited(lub1)) {
+      return null;
+    }
+
+    AnnotatedTypeVariable lub = castLub(type1, lub1);
+    visit(type1.getUpperBound(), type2.getUpperBound(), lub.getUpperBound());
+    visit(type1.getLowerBound(), type2.getLowerBound(), lub.getLowerBound());
+
+    lubPrimaryOnBoundedType(type1, type2, lub);
+
+    return null;
+  }
+
+  @Override
+  public Void visitWildcard_Wildcard(
+      AnnotatedWildcardType type1, AnnotatedWildcardType type2, AnnotatedTypeMirror lub1) {
+    if (visited(lub1)) {
+      return null;
+    }
+    AnnotatedWildcardType lub = castLub(type1, lub1);
+    visit(type1.getExtendsBound(), type2.getExtendsBound(), lub.getExtendsBound());
+    visit(type1.getSuperBound(), type2.getSuperBound(), lub.getSuperBound());
+    lubPrimaryOnBoundedType(type1, type2, lub);
+
+    return null;
+  }
+
+  private void lubPrimaryOnBoundedType(
+      AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) {
+    // For each hierarchy, if type1 is not a subtype of type2 and type2 is not a
+    // subtype of type1, then the primary annotation on lub must be the effective upper
+    // bound of type1 or type2, whichever is higher.
+    Set<AnnotationMirror> type1LowerBoundAnnos =
+        AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualifierHierarchy, type1);
+    Set<AnnotationMirror> type2LowerBoundAnnos =
+        AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualifierHierarchy, type2);
+
+    for (AnnotationMirror lower1 : type1LowerBoundAnnos) {
+      AnnotationMirror top = qualifierHierarchy.getTopAnnotation(lower1);
+
+      // Can't just call isSubtype because it will return false if bounds have
+      // different annotations on component types
+      AnnotationMirror lower2 =
+          qualifierHierarchy.findAnnotationInHierarchy(type2LowerBoundAnnos, top);
+      AnnotationMirror upper1 = type1.getEffectiveAnnotationInHierarchy(lower1);
+      AnnotationMirror upper2 = type2.getEffectiveAnnotationInHierarchy(lower1);
+
+      if (qualifierHierarchy.isSubtype(upper2, upper1)
+          && qualifierHierarchy.isSubtype(upper1, upper2)
+          && qualifierHierarchy.isSubtype(lower1, lower2)
+          && qualifierHierarchy.isSubtype(lower2, lower1)) {
+        continue;
+      }
+
+      if (!qualifierHierarchy.isSubtype(upper2, lower1)
+          && !qualifierHierarchy.isSubtype(upper1, lower2)) {
+        lub.replaceAnnotation(qualifierHierarchy.leastUpperBound(upper1, upper2));
+      }
+    }
+  }
+
+  @Override
+  public Void visitIntersection_Intersection(
+      AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, AnnotatedTypeMirror lub) {
+    AnnotatedIntersectionType castedLub = castLub(type1, lub);
+    lubPrimaryAnnotations(type1, type2, lub);
+
+    for (int i = 0; i < castedLub.getBounds().size(); i++) {
+      AnnotatedTypeMirror lubST = castedLub.getBounds().get(i);
+      visit(type1.getBounds().get(i), type2.getBounds().get(i), lubST);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitUnion_Union(
+      AnnotatedUnionType type1, AnnotatedUnionType type2, AnnotatedTypeMirror lub) {
+    AnnotatedUnionType castedLub = castLub(type1, lub);
+    lubPrimaryAnnotations(type1, type2, lub);
+
+    for (int i = 0; i < castedLub.getAlternatives().size(); i++) {
+      AnnotatedDeclaredType lubAltern = castedLub.getAlternatives().get(i);
+      visit(type1.getAlternatives().get(i), type2.getAlternatives().get(i), lubAltern);
+    }
+    return null;
+  }
+
+  @Override
+  protected String defaultErrorMessage(
+      AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) {
+    return String.format(
+        "AtmLubVisitor: Unexpected combination: type1: %s type2: %s.%n"
+            + "type1: %s%ntype2: %s%nlub: %s",
+        type1.getKind(), type2.getKind(), type1, type2, lub);
+  }
+
+  /**
+   * Returns true if the {@link AnnotatedTypeMirror} has been visited. If it has not, then it is
+   * added to the list of visited AnnotatedTypeMirrors. This prevents infinite recursion on
+   * recursive types.
+   *
+   * @param atm the type that might have been visited
+   * @return true if the given type has been visited
+   */
+  private boolean visited(@FindDistinct AnnotatedTypeMirror atm) {
+    for (AnnotatedTypeMirror atmVisit : visited) {
+      // Use reference equality rather than equals because the visitor may visit two types
+      // that are structurally equal, but not actually the same.  For example, the
+      // wildcards in Pair<?,?> may be equal, but they both should be visited.
+      if (atmVisit == atm) {
+        return true;
+      }
+    }
+    visited.add(atm);
+    return false;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java b/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java
new file mode 100644
index 0000000..f6e8b22
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java
@@ -0,0 +1,918 @@
+package org.checkerframework.framework.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.JarInputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.SystemUtil;
+import org.checkerframework.javacutil.UserError;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * This class behaves similarly to javac. CheckerMain does the following:
+ *
+ * <ul>
+ *   <li>add the {@code javac.jar} to the runtime classpath of the process that runs the Checker
+ *       Framework.
+ *   <li>parse and implement any special options used by the Checker Framework, e.g., using
+ *       "shortnames" for annotation processors
+ *   <li>pass all remaining command-line arguments to the real javac
+ * </ul>
+ *
+ * To debug this class, use the {@code -AoutputArgsToFile=FILENAME} command-line argument or {@code
+ * -AoutputArgsToFile=-} to output to standard out.
+ *
+ * <p>"To run the Checker Framework" really means to run java, where the program being run is javac
+ * and javac is passed a {@code -processor} command-line argument that mentions a Checker Framework
+ * checker. There are 5 relevant classpaths: The classpath and bootclasspath when running java, and
+ * the classpath, bootclasspath, and processorpath used by javac. The latter three are the only
+ * important ones.
+ *
+ * <p>Note for developers: Try to limit the work done (and options interpreted) by CheckerMain,
+ * because its functionality is not available to users who choose not to use the Checker Framework
+ * javac script.
+ */
+public class CheckerMain {
+
+  /**
+   * Any exception thrown by the Checker Framework escapes to the command line.
+   *
+   * @param args command-line arguments
+   */
+  public static void main(String[] args) {
+    final File pathToThisJar = new File(findPathTo(CheckerMain.class, false));
+    ArrayList<String> alargs = new ArrayList<>(Arrays.asList(args));
+    final CheckerMain program = new CheckerMain(pathToThisJar, alargs);
+    final int exitStatus = program.invokeCompiler();
+    System.exit(exitStatus);
+  }
+
+  /** The path to the javacJar to use. */
+  protected final File javacJar;
+
+  /** The path to the jar containing CheckerMain.class (i.e. checker.jar). */
+  protected final File checkerJar;
+
+  /** The path to checker-qual.jar. */
+  protected final File checkerQualJar;
+
+  /** The path to checker-util.jar. */
+  protected final File checkerUtilJar;
+
+  /** Compilation bootclasspath. */
+  private final List<String> compilationBootclasspath;
+
+  private final List<String> runtimeClasspath;
+
+  private final List<String> jvmOpts;
+
+  /**
+   * Each element is either a classpath element (a directory or jar file) or is a classpath
+   * (containing elements separated by File.pathSeparator). To produce the final classpath,
+   * concatenate them all (separated by File.pathSeparator).
+   */
+  private final List<String> cpOpts;
+
+  /** Processor path options. */
+  private final List<String> ppOpts;
+
+  /** Arguments to the Checker Framework. */
+  private final List<String> toolOpts;
+
+  /** Command-line argument files (specified with @ on the command line). */
+  private final List<File> argListFiles;
+
+  /**
+   * Option name for specifying an alternative checker-qual.jar location. The accompanying value
+   * MUST be the path to the jar file (NOT the path to its encompassing directory)
+   */
+  public static final String CHECKER_QUAL_PATH_OPT = "-checkerQualJar";
+
+  /**
+   * Option name for specifying an alternative checker-util.jar location. The accompanying value
+   * MUST be the path to the jar file (NOT the path to its encompassing directory)
+   */
+  public static final String CHECKER_UTIL_PATH_OPT = "-checkerUtilJar";
+
+  /**
+   * Option name for specifying an alternative javac.jar location. The accompanying value MUST be
+   * the path to the jar file (NOT the path to its encompassing directory)
+   */
+  public static final String JAVAC_PATH_OPT = "-javacJar";
+
+  /**
+   * Option name for specifying an alternative jdk.jar location. The accompanying value MUST be the
+   * path to the jar file (NOT the path to its encompassing directory)
+   */
+  public static final String JDK_PATH_OPT = "-jdkJar";
+
+  /**
+   * Construct all the relevant file locations and Java version given the path to this jar and a set
+   * of directories in which to search for jars.
+   */
+  public CheckerMain(final File checkerJar, final List<String> args) {
+
+    this.checkerJar = checkerJar;
+    final File searchPath = checkerJar.getParentFile();
+
+    replaceShorthandProcessor(args);
+    argListFiles = collectArgFiles(args);
+
+    this.checkerQualJar =
+        extractFileArg(CHECKER_QUAL_PATH_OPT, new File(searchPath, "checker-qual.jar"), args);
+
+    this.checkerUtilJar =
+        extractFileArg(CHECKER_UTIL_PATH_OPT, new File(searchPath, "checker-util.jar"), args);
+
+    this.javacJar = extractFileArg(JAVAC_PATH_OPT, new File(searchPath, "javac.jar"), args);
+
+    this.compilationBootclasspath = createCompilationBootclasspath(args);
+    this.runtimeClasspath = createRuntimeClasspath(args);
+    this.jvmOpts = extractJvmOpts(args);
+
+    this.cpOpts = createCpOpts(args);
+    this.ppOpts = createPpOpts(args);
+    this.toolOpts = args;
+
+    assertValidState();
+  }
+
+  /** Assert that required jars exist. */
+  protected void assertValidState() {
+    if (SystemUtil.getJreVersion() < 9) {
+      assertFilesExist(Arrays.asList(javacJar, checkerJar, checkerQualJar, checkerUtilJar));
+    } else {
+      assertFilesExist(Arrays.asList(checkerJar, checkerQualJar, checkerUtilJar));
+    }
+  }
+
+  public void addToClasspath(List<String> cpOpts) {
+    this.cpOpts.addAll(cpOpts);
+  }
+
+  public void addToProcessorpath(List<String> ppOpts) {
+    this.ppOpts.addAll(ppOpts);
+  }
+
+  public void addToRuntimeClasspath(List<String> runtimeClasspathOpts) {
+    this.runtimeClasspath.addAll(runtimeClasspathOpts);
+  }
+
+  protected List<String> createRuntimeClasspath(final List<String> argsList) {
+    return new ArrayList<>(Arrays.asList(javacJar.getAbsolutePath()));
+  }
+
+  /**
+   * Returns the compilation bootclasspath from {@code argsList}.
+   *
+   * @param argsList args to add
+   * @return the compilation bootclasspath from {@code argsList}
+   */
+  protected List<String> createCompilationBootclasspath(final List<String> argsList) {
+    return extractBootClassPath(argsList);
+  }
+
+  protected List<String> createCpOpts(final List<String> argsList) {
+    final List<String> extractedOpts = extractCpOpts(argsList);
+    extractedOpts.add(0, this.checkerQualJar.getAbsolutePath());
+    extractedOpts.add(0, this.checkerUtilJar.getAbsolutePath());
+
+    return extractedOpts;
+  }
+
+  /**
+   * Returns processor path options.
+   *
+   * <p>This method assumes that createCpOpts has already been run.
+   *
+   * @param argsList arguments
+   * @return processor path options
+   */
+  protected List<String> createPpOpts(final List<String> argsList) {
+    final List<String> extractedOpts = new ArrayList<>(extractPpOpts(argsList));
+    if (extractedOpts.isEmpty()) {
+      // If processorpath is not provided, then javac uses the classpath.
+      // CheckerMain always supplies a processorpath, so if the user
+      // didn't specify a processorpath, then use the classpath.
+      extractedOpts.addAll(this.cpOpts);
+    }
+    extractedOpts.add(0, this.checkerJar.getAbsolutePath());
+    extractedOpts.add(0, this.checkerUtilJar.getAbsolutePath());
+
+    return extractedOpts;
+  }
+
+  /**
+   * Return the arguments that start with @ and therefore are files that contain javac arguments.
+   *
+   * @param args a list of command-line arguments; is not modified
+   * @return a List of files representing all arguments that started with @
+   */
+  protected List<File> collectArgFiles(final List<String> args) {
+    final List<File> argListFiles = new ArrayList<>();
+    for (final String arg : args) {
+      if (arg.startsWith("@")) {
+        argListFiles.add(new File(arg.substring(1)));
+      }
+    }
+
+    return argListFiles;
+  }
+
+  /**
+   * Remove the argument given by argumentName and the subsequent value from the list args if
+   * present. Return the subsequent value.
+   *
+   * @param argumentName a command-line option name whose argument to extract
+   * @param alternative default value to return if argumentName does not appear in args
+   * @param args the current list of arguments
+   * @return the string that follows argumentName if argumentName is in args, or alternative if
+   *     argumentName is not present in args
+   */
+  protected static String extractArg(
+      final String argumentName, final String alternative, final List<String> args) {
+    int i = args.indexOf(argumentName);
+    if (i == -1) {
+      return alternative;
+    } else if (i == args.size() - 1) {
+      throw new BugInCF("Command line contains " + argumentName + " but no value following it");
+    } else {
+      args.remove(i);
+      return args.remove(i);
+    }
+  }
+
+  /**
+   * Remove the argument given by argumentName and the subsequent value from the list args if
+   * present. Return the subsequent value wrapped as a File.
+   *
+   * @param argumentName argument to extract
+   * @param alternative file to return if argumentName is not found in args
+   * @param args the current list of arguments
+   * @return the string that follows argumentName wrapped as a File if argumentName is in args or
+   *     alternative if argumentName is not present in args
+   */
+  protected static File extractFileArg(
+      final String argumentName, final File alternative, final List<String> args) {
+    final String filePath = extractArg(argumentName, null, args);
+    if (filePath == null) {
+      return alternative;
+    } else {
+      return new File(filePath);
+    }
+  }
+
+  /**
+   * Find all args that match the given pattern and extract their index 1 group. Add all the index 1
+   * groups to the returned list. Remove all matching args from the input args list.
+   *
+   * @param pattern a pattern with at least one matching group
+   * @param allowEmpties whether or not to add empty group(1) matches to the returned list
+   * @param args the arguments to extract from
+   * @return a list of arguments from the first group that matched the pattern for each input args
+   *     or the empty list if there were none
+   */
+  protected static List<String> extractOptWithPattern(
+      final Pattern pattern, boolean allowEmpties, final List<String> args) {
+    final List<String> matchedArgs = new ArrayList<>();
+
+    int i = 0;
+    while (i < args.size()) {
+      final Matcher matcher = pattern.matcher(args.get(i));
+      if (matcher.matches()) {
+        final String arg = matcher.group(1).trim();
+
+        if (!arg.isEmpty() || allowEmpties) {
+          matchedArgs.add(arg);
+        }
+
+        args.remove(i);
+      } else {
+        i++;
+      }
+    }
+
+    return matchedArgs;
+  }
+
+  /**
+   * A pattern to match bootclasspath prepend entries, used to construct one {@code
+   * -Xbootclasspath/p:} command-line argument.
+   */
+  protected static final Pattern BOOT_CLASS_PATH_REGEX =
+      Pattern.compile("^(?:-J)?-Xbootclasspath/p:(.*)$");
+
+  // TODO: Why does this treat -J and -J-X the same?  They have different semantics, don't they?
+  /**
+   * Remove all {@code -Xbootclasspath/p:} or {@code -J-Xbootclasspath/p:} arguments from args and
+   * add them to the returned list.
+   *
+   * @param args the arguments to extract from
+   * @return all non-empty arguments matching BOOT_CLASS_PATH_REGEX or an empty list if there were
+   *     none
+   */
+  protected static List<String> extractBootClassPath(final List<String> args) {
+    return extractOptWithPattern(BOOT_CLASS_PATH_REGEX, false, args);
+  }
+
+  /** Matches all {@code -J} arguments. */
+  protected static final Pattern JVM_OPTS_REGEX = Pattern.compile("^(?:-J)(.*)$");
+
+  /**
+   * Remove all {@code -J} arguments from {@code args} and add them to the returned list (without
+   * the {@code -J} prefix).
+   *
+   * @param args the arguments to extract from
+   * @return all {@code -J} arguments (without the {@code -J} prefix) or an empty list if there were
+   *     none
+   */
+  protected static List<String> extractJvmOpts(final List<String> args) {
+    return extractOptWithPattern(JVM_OPTS_REGEX, false, args);
+  }
+
+  /**
+   * Return the last {@code -cp} or {@code -classpath} option. If no {@code -cp} or {@code
+   * -classpath} arguments were present, then return the CLASSPATH environment variable (if set)
+   * followed by the current directory.
+   *
+   * <p>Also removes all {@code -cp} and {@code -classpath} options from args.
+   *
+   * @param args a list of arguments to extract from; is side-effected by this
+   * @return collection of classpaths to concatenate to use when calling javac.jar
+   */
+  protected static List<String> extractCpOpts(final List<String> args) {
+    List<String> actualArgs = new ArrayList<>();
+
+    String lastCpArg = null;
+
+    for (int i = 0; i < args.size(); i++) {
+      if ((args.get(i).equals("-cp") || args.get(i).equals("-classpath"))
+          && (i + 1 < args.size())) {
+        args.remove(i);
+        // Every classpath entry overrides the one before it.
+        lastCpArg = args.remove(i);
+        // re-process whatever is currently at element i
+        i--;
+      }
+    }
+
+    // The logic below is exactly what the javac script does.  If no command-line classpath is
+    // specified, use the "CLASSPATH" environment variable followed by the current directory.
+    if (lastCpArg == null) {
+      final String systemClassPath = System.getenv("CLASSPATH");
+      if (systemClassPath != null && !systemClassPath.trim().isEmpty()) {
+        actualArgs.add(systemClassPath.trim());
+      }
+
+      actualArgs.add(".");
+    } else {
+      actualArgs.add(lastCpArg);
+    }
+
+    return actualArgs;
+  }
+
+  /**
+   * Remove the {@code -processorpath} options and their arguments from args. Return the last
+   * argument.
+   *
+   * @param args a list of arguments to extract from
+   * @return the arguments that should be put on the processorpath when calling javac.jar
+   */
+  protected static List<String> extractPpOpts(final List<String> args) {
+    String path = null;
+
+    for (int i = 0; i < args.size(); i++) {
+      if (args.get(i).equals("-processorpath") && (i + 1 < args.size())) {
+        args.remove(i);
+        path = args.remove(i);
+        // re-process whatever is currently at element i
+        i--;
+      }
+    }
+
+    if (path != null) {
+      return Collections.singletonList(path);
+    } else {
+      return Collections.emptyList();
+    }
+  }
+
+  protected void addMainToArgs(final List<String> args) {
+    args.add("com.sun.tools.javac.Main");
+  }
+
+  /** Invoke the compiler with all relevant jars on its classpath and/or bootclasspath. */
+  public List<String> getExecArguments() {
+    List<String> args = new ArrayList<>(jvmOpts.size() + cpOpts.size() + toolOpts.size() + 7);
+
+    // TODO: do we need java.exe on Windows?
+    final String java = "java";
+    args.add(java);
+
+    if (SystemUtil.getJreVersion() == 8) {
+      args.add("-Xbootclasspath/p:" + String.join(File.pathSeparator, runtimeClasspath));
+    } else {
+      args.addAll(
+          Arrays.asList(
+              "--illegal-access=warn",
+              "--add-opens",
+              "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED"));
+    }
+
+    args.add("-classpath");
+    args.add(String.join(File.pathSeparator, runtimeClasspath));
+    args.add("-ea");
+    // com.sun.tools needs to be enabled separately
+    args.add("-ea:com.sun.tools...");
+
+    args.addAll(jvmOpts);
+
+    addMainToArgs(args);
+
+    if (!argsListHasClassPath(argListFiles)) {
+      args.add("-classpath");
+      args.add(quote(concatenatePaths(cpOpts)));
+    }
+    if (!argsListHasProcessorPath(argListFiles)) {
+      args.add("-processorpath");
+      args.add(quote(concatenatePaths(ppOpts)));
+    }
+
+    if (SystemUtil.getJreVersion() == 8) {
+      // No classes on the compilation bootclasspath will be loaded
+      // during compilation, but the classes are read by the compiler
+      // without loading them.  The compiler assumes that any class on
+      // this bootclasspath will be on the bootclasspath of the JVM used
+      // to later run the classfiles that Javac produces.
+      args.add("-Xbootclasspath/p:" + String.join(File.pathSeparator, compilationBootclasspath));
+
+      // We currently provide a Java 8 JDK and want to be runnable
+      // on a Java 8 JVM. So set source/target to 8.
+      args.add("-source");
+      args.add("8");
+      args.add("-target");
+      args.add("8");
+    }
+
+    args.addAll(toolOpts);
+    return args;
+  }
+
+  /** Given a list of paths, concatenate them to form a single path. Also expand wildcards. */
+  private String concatenatePaths(List<String> paths) {
+    List<String> elements = new ArrayList<>();
+    for (String path : paths) {
+      for (String element : path.split(File.pathSeparator)) {
+        elements.addAll(expandWildcards(element));
+      }
+    }
+    return String.join(File.pathSeparator, elements);
+  }
+
+  /** The string "/*" (on Unix). */
+  private static final String FILESEP_STAR = File.separator + "*";
+
+  /**
+   * Given a path element that might be a wildcard, return a list of the elements it expands to. If
+   * the element isn't a wildcard, return a singleton list containing the argument.
+   */
+  private List<String> expandWildcards(String pathElement) {
+    if (pathElement.equals("*")) {
+      return jarFiles(".");
+    } else if (pathElement.endsWith(FILESEP_STAR)) {
+      return jarFiles(pathElement.substring(0, pathElement.length() - 1));
+    } else if (pathElement.equals("")) {
+      return Collections.emptyList();
+    } else {
+      return Collections.singletonList(pathElement);
+    }
+  }
+
+  /**
+   * Returns all the .jar and .JAR files in the given directory.
+   *
+   * @param directory a directory
+   * @return all the .jar and .JAR files in the given directory
+   */
+  private List<String> jarFiles(String directory) {
+    File dir = new File(directory);
+    return Arrays.asList(dir.list((d, name) -> name.endsWith(".jar") || name.endsWith(".JAR")));
+  }
+
+  /** Invoke the compiler with all relevant jars on its classpath and/or bootclasspath. */
+  public int invokeCompiler() {
+    List<String> args = getExecArguments();
+
+    for (int i = 0; i < args.size(); i++) {
+      String arg = args.get(i);
+
+      if (arg.startsWith("-AoutputArgsToFile=")) {
+        String fileName = arg.substring(19);
+        args.remove(i);
+        outputArgumentsToFile(fileName, args);
+        break;
+      }
+    }
+
+    // Actually invoke the compiler
+    return ExecUtil.execute(args.toArray(new String[args.size()]), System.out, System.err);
+  }
+
+  private static void outputArgumentsToFile(String outputFilename, List<String> args) {
+    if (outputFilename != null) {
+      String errorMessage = null;
+
+      try {
+        PrintWriter writer =
+            (outputFilename.equals("-")
+                ? new PrintWriter(System.out)
+                : new PrintWriter(outputFilename, "UTF-8"));
+        for (int i = 0; i < args.size(); i++) {
+          String arg = args.get(i);
+
+          // We would like to include the filename of the argfile instead of its contents.
+          // The problem is that the file will sometimes disappear by the time the user
+          // can look at or run the resulting script. Maven deletes the argfile very
+          // shortly after it has been handed off to javac, for example. Ideally we would
+          // print the argfile filename as a comment but the resulting file couldn't then
+          // be run as a script on Unix or Windows.
+          if (arg.startsWith("@")) {
+            // Read argfile and include its parameters in the output file.
+            String inputFilename = arg.substring(1);
+
+            BufferedReader br = new BufferedReader(new FileReader(inputFilename));
+            String line;
+            while ((line = br.readLine()) != null) {
+              writer.print(line);
+              writer.print(" ");
+            }
+            br.close();
+          } else {
+            writer.print(arg);
+            writer.print(" ");
+          }
+        }
+        writer.close();
+      } catch (IOException e) {
+        errorMessage = e.toString();
+      }
+
+      if (errorMessage != null) {
+        System.err.println(
+            "Failed to output command-line arguments to file "
+                + outputFilename
+                + " due to exception: "
+                + errorMessage);
+      }
+    }
+  }
+
+  /**
+   * Returns true if some @arglist file sets the classpath.
+   *
+   * @param argListFiles command-line argument files (specified with @ on the command line)
+   */
+  private static boolean argsListHasClassPath(final List<File> argListFiles) {
+    for (final String arg : expandArgFiles(argListFiles)) {
+      if (arg.contains("-classpath") || arg.contains("-cp")) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Returns true if some @arglist file sets the processorpath.
+   *
+   * @param argListFiles command-line argument files (specified with @ on the command line)
+   */
+  private static boolean argsListHasProcessorPath(final List<File> argListFiles) {
+    for (final String arg : expandArgFiles(argListFiles)) {
+      if (arg.contains("-processorpath")) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Return all the lines in all the files.
+   *
+   * @param files a list of files
+   * @return a list of all the lines in all the files
+   */
+  protected static List<String> expandArgFiles(final List<File> files) {
+    final List<String> content = new ArrayList<>();
+    for (final File file : files) {
+      try {
+        content.addAll(Files.readAllLines(file.toPath()));
+      } catch (final IOException exc) {
+        throw new RuntimeException("Could not open file: " + file.getAbsolutePath(), exc);
+      }
+    }
+    return content;
+  }
+
+  /**
+   * Find the jar file or directory containing the .class file from which cls was loaded.
+   *
+   * @param cls the class whose .class file we wish to locate; if null, CheckerMain.class
+   * @param errIfFromDirectory if false, throw an exception if the file was loaded from a directory
+   */
+  public static String findPathTo(Class<?> cls, boolean errIfFromDirectory)
+      throws IllegalStateException {
+    if (cls == null) {
+      cls = CheckerMain.class;
+    }
+    String name = cls.getName();
+    String classFileName;
+    /* name is something like pakkage.name.ContainingClass$ClassName. We need to turn this into ContainingClass$ClassName.class. */
+    {
+      int idx = name.lastIndexOf('.');
+      classFileName = (idx == -1 ? name : name.substring(idx + 1)) + ".class";
+    }
+
+    String uri = cls.getResource(classFileName).toString();
+    if (uri.startsWith("file:")) {
+      if (errIfFromDirectory) {
+        return uri;
+      } else {
+        throw new IllegalStateException(
+            "This class has been loaded from a directory and not from a jar file.");
+      }
+    }
+    if (!uri.startsWith("jar:file:")) {
+      int idx = uri.indexOf(':');
+      String protocol = idx == -1 ? "(unknown)" : uri.substring(0, idx);
+      throw new IllegalStateException(
+          "This class has been loaded remotely via the "
+              + protocol
+              + " protocol. Only loading from a jar on the local file system is supported.");
+    }
+
+    int idx = uri.indexOf('!');
+    // Sanity check
+    if (idx == -1) {
+      throw new IllegalStateException(
+          "You appear to have loaded this class from a local jar file, but URI has no \"!\": "
+              + uri);
+    }
+
+    try {
+      String fileName =
+          URLDecoder.decode(
+              uri.substring("jar:file:".length(), idx), Charset.defaultCharset().name());
+      return new File(fileName).getAbsolutePath();
+    } catch (UnsupportedEncodingException e) {
+      throw new BugInCF("Default charset doesn't exist. Your VM is borked.");
+    }
+  }
+
+  /**
+   * Assert that all files in the list exist and if they don't, throw a RuntimeException with a list
+   * of the files that do not exist.
+   *
+   * @param expectedFiles files that must exist
+   */
+  private static void assertFilesExist(final List<File> expectedFiles) {
+    final List<File> missingFiles = new ArrayList<>();
+    for (final File file : expectedFiles) {
+      if (file == null) {
+        throw new RuntimeException("Null passed to assertFilesExist");
+      }
+      if (!file.exists()) {
+        missingFiles.add(file);
+      }
+    }
+
+    if (!missingFiles.isEmpty()) {
+      if (missingFiles.size() == 1) {
+        File missingFile = missingFiles.get(0);
+        if (missingFile.getName().equals("javac.jar")) {
+          throw new UserError(
+              "Could not find "
+                  + missingFile.getAbsolutePath()
+                  + ". This may be because you built the Checker Framework under Java 11 but are"
+                  + " running it under Java 8.");
+        }
+      }
+      List<String> missingAbsoluteFilenames =
+          CollectionsPlume.mapList(File::getAbsolutePath, missingFiles);
+      throw new RuntimeException(
+          "The following files could not be located: "
+              + String.join(", ", missingAbsoluteFilenames));
+    }
+  }
+
+  private static String quote(final String str) {
+    if (str.contains(" ")) {
+      if (str.contains("\"")) {
+        throw new BugInCF(
+            "Don't know how to quote a string containing a double-quote character " + str);
+      }
+      return "\"" + str + "\"";
+    }
+    return str;
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Shorthand checker names
+  ///
+
+  /** Processor shorthand is enabled for processors in this directory in checker.jar. */
+  protected static final String CHECKER_BASE_DIR_NAME = "org/checkerframework/checker/";
+  /** Processor shorthand is enabled for processors in this directory in checker.jar. */
+  protected static final String COMMON_BASE_DIR_NAME = "org/checkerframework/common/";
+
+  /**
+   * Returns true if processorString, once transformed into fully-qualified form, is present in
+   * fullyQualifiedCheckerNames. Used by SourceChecker to determine whether a class is annotated for
+   * any processor that is being run.
+   *
+   * @param processorString the name of a single processor, not a comma-separated list of processors
+   * @param fullyQualifiedCheckerNames a list of fully-qualified checker names
+   * @return true if the fully-qualified version of {@code processorString} is in {@code
+   *     fullyQualifiedCheckerNames}
+   */
+  public static boolean matchesCheckerOrSubcheckerFromList(
+      final String processorString, List<@FullyQualifiedName String> fullyQualifiedCheckerNames) {
+    if (processorString.contains(",")) {
+      return false; // Do not process strings containing multiple processors.
+    }
+
+    return fullyQualifiedCheckerNames.contains(
+        unshorthandProcessorNames(processorString, fullyQualifiedCheckerNames, true));
+  }
+
+  /**
+   * For every "-processor" argument in args, replace its immediate successor argument using
+   * unabbreviateProcessorNames.
+   */
+  protected void replaceShorthandProcessor(final List<String> args) {
+    for (int i = 0; i < args.size(); i++) {
+      final int nextIndex = i + 1;
+      if (args.size() > nextIndex) {
+        if (args.get(i).equals("-processor")) {
+          final String replacement =
+              unshorthandProcessorNames(args.get(nextIndex), getAllCheckerClassNames(), false);
+          args.remove(nextIndex);
+          args.add(nextIndex, replacement);
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns the list of fully qualified names of the checkers found in checker.jar. This covers
+   * only checkers with the name ending in "Checker". Checkers with a name ending in "Subchecker"
+   * are not included in the returned list. Note however that it is possible for a checker with the
+   * name ending in "Checker" to be used as a subchecker.
+   *
+   * @return fully qualified names of the checkers found in checker.jar
+   */
+  private List<@FullyQualifiedName String> getAllCheckerClassNames() {
+    ArrayList<@FullyQualifiedName String> checkerClassNames = new ArrayList<>();
+    try {
+      final JarInputStream checkerJarIs = new JarInputStream(new FileInputStream(checkerJar));
+      ZipEntry entry;
+      while ((entry = checkerJarIs.getNextEntry()) != null) {
+        final String name = entry.getName();
+        // Checkers ending in "Subchecker" are not included in this list used by CheckerMain.
+        if ((name.startsWith(CHECKER_BASE_DIR_NAME) || name.startsWith(COMMON_BASE_DIR_NAME))
+            && name.endsWith("Checker.class")) {
+          // Forward slash is used instead of File.separator because checker.jar uses / as
+          // the separator.
+          @SuppressWarnings("signature") // string manipulation
+          @FullyQualifiedName String fqName =
+              String.join(".", name.substring(0, name.length() - ".class".length()).split("/"));
+          checkerClassNames.add(fqName);
+        }
+      }
+      checkerJarIs.close();
+    } catch (IOException e) {
+      // Issue a warning instead of aborting execution.
+      System.err.printf(
+          "Could not read %s. Shorthand processor names will not work.%n", checkerJar);
+    }
+
+    return checkerClassNames;
+  }
+
+  /**
+   * Takes a string of comma-separated processor names, and expands any shorthands to
+   * fully-qualified names from the fullyQualifiedCheckerNames list. For example:
+   *
+   * <pre>
+   * NullnessChecker &rarr; org.checkerframework.checker.nullness.NullnessChecker
+   * nullness &rarr; org.checkerframework.checker.nullness.NullnessChecker
+   * NullnessChecker,RegexChecker &rarr; org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.regex.RegexChecker
+   * </pre>
+   *
+   * Note, a processor entry only gets replaced if it contains NO "." (i.e., it is not qualified by
+   * a package name) and can be found under the package org.checkerframework.checker in checker.jar.
+   *
+   * @param processorsString a comma-separated string identifying processors
+   * @param fullyQualifiedCheckerNames a list of fully-qualified checker names to match
+   *     processorsString against
+   * @param allowSubcheckers whether to match against fully qualified checker names ending with
+   *     "Subchecker"
+   * @return processorsString where all shorthand references to Checker Framework built-in checkers
+   *     are replaced with fully-qualified references
+   */
+  protected static String unshorthandProcessorNames(
+      final String processorsString,
+      List<@FullyQualifiedName String> fullyQualifiedCheckerNames,
+      boolean allowSubcheckers) {
+    final String[] processors = processorsString.split(",");
+    for (int i = 0; i < processors.length; i++) {
+      if (!processors[i].contains(".")) { // Not already fully qualified
+        processors[i] =
+            unshorthandProcessorName(processors[i], fullyQualifiedCheckerNames, allowSubcheckers);
+      }
+    }
+
+    return String.join(",", processors);
+  }
+
+  /**
+   * Given a processor name, tries to expand it to a checker in the fullyQualifiedCheckerNames list.
+   * Returns that expansion, or the argument itself if the expansion fails.
+   *
+   * @param processorName a processor name, possibly in shorthand
+   * @param fullyQualifiedCheckerNames all checker names
+   * @param allowSubcheckers whether to match subcheckers as well as checkers
+   * @return the fully-qualified version of {@code processorName} in {@code
+   *     fullyQualifiedCheckerNames}, or else {@code processorName} itself
+   */
+  private static String unshorthandProcessorName(
+      final String processorName,
+      List<@FullyQualifiedName String> fullyQualifiedCheckerNames,
+      boolean allowSubcheckers) {
+    for (final String name : fullyQualifiedCheckerNames) {
+      boolean tryMatch = false;
+      String[] checkerPath = name.substring(0, name.length() - "Checker".length()).split("\\.");
+      String checkerNameShort = checkerPath[checkerPath.length - 1];
+      String checkerName = checkerNameShort + "Checker";
+
+      if (name.endsWith("Checker")) {
+        checkerPath = name.substring(0, name.length() - "Checker".length()).split("\\.");
+        checkerNameShort = checkerPath[checkerPath.length - 1];
+        checkerName = checkerNameShort + "Checker";
+        tryMatch = true;
+      } else if (allowSubcheckers && name.endsWith("Subchecker")) {
+        checkerPath = name.substring(0, name.length() - "Subchecker".length()).split("\\.");
+        checkerNameShort = checkerPath[checkerPath.length - 1];
+        checkerName = checkerNameShort + "Subchecker";
+        tryMatch = true;
+      }
+
+      if (tryMatch) {
+        if (processorName.equalsIgnoreCase(checkerName)
+            || processorName.equalsIgnoreCase(checkerNameShort)) {
+          return name;
+        }
+      }
+    }
+
+    return processorName; // If not matched, return the input string.
+  }
+
+  /**
+   * Given a shorthand processor name, returns true if it can be expanded to a checker in the
+   * fullyQualifiedCheckerNames list.
+   *
+   * @param processorName a string identifying one processor
+   * @param fullyQualifiedCheckerNames a list of fully-qualified checker names to match
+   *     processorName against
+   * @param allowSubcheckers whether to match against fully qualified checker names ending with
+   *     "Subchecker"
+   * @return true if the shorthand processor name can be expanded to a checker in {@code
+   *     fullyQualifiedCheckerNames}
+   */
+  public static boolean matchesFullyQualifiedProcessor(
+      final String processorName,
+      List<@FullyQualifiedName String> fullyQualifiedCheckerNames,
+      boolean allowSubcheckers) {
+    return !processorName.equals(
+        unshorthandProcessorName(processorName, fullyQualifiedCheckerNames, allowSubcheckers));
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/Contract.java b/framework/src/main/java/org/checkerframework/framework/util/Contract.java
new file mode 100644
index 0000000..e9f0d2d
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/Contract.java
@@ -0,0 +1,309 @@
+package org.checkerframework.framework.util;
+
+import com.sun.source.tree.Tree;
+import java.lang.annotation.Annotation;
+import java.util.Objects;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
+import org.checkerframework.framework.qual.EnsuresQualifier;
+import org.checkerframework.framework.qual.EnsuresQualifierIf;
+import org.checkerframework.framework.qual.PostconditionAnnotation;
+import org.checkerframework.framework.qual.PreconditionAnnotation;
+import org.checkerframework.framework.qual.RequiresQualifier;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * A contract represents an annotation on an expression. It is a precondition, postcondition, or
+ * conditional postcondition.
+ *
+ * @see Precondition
+ * @see Postcondition
+ * @see ConditionalPostcondition
+ */
+public abstract class Contract {
+
+  /**
+   * The expression for which the condition must hold, such as {@code "foo"} in
+   * {@code @RequiresNonNull("foo")}.
+   *
+   * <p>An annotation like {@code @RequiresNonNull({"a", "b", "c"})} would be represented by
+   * multiple Contracts.
+   */
+  public final String expressionString;
+
+  /** The annotation on the type of expression, according to this contract. */
+  public final AnnotationMirror annotation;
+
+  /** The annotation that expressed this contract; used for diagnostic messages. */
+  public final AnnotationMirror contractAnnotation;
+
+  // This is redundant with the contract's class and is not used in this file, but the field
+  // is used by clients, for its fields.
+  /** The kind of contract: precondition, postcondition, or conditional postcondition. */
+  public final Kind kind;
+
+  /** Enumerates the kinds of contracts. */
+  public enum Kind {
+    /** A precondition. */
+    PRECONDITION(
+        "precondition",
+        PreconditionAnnotation.class,
+        RequiresQualifier.class,
+        RequiresQualifier.List.class),
+    /** A postcondition. */
+    POSTCONDITION(
+        "postcondition",
+        PostconditionAnnotation.class,
+        EnsuresQualifier.class,
+        EnsuresQualifier.List.class),
+    /** A conditional postcondition. */
+    CONDITIONALPOSTCONDITION(
+        "conditional postcondition",
+        ConditionalPostconditionAnnotation.class,
+        EnsuresQualifierIf.class,
+        EnsuresQualifierIf.List.class);
+
+    /** Used for constructing error messages. */
+    public final String errorKey;
+
+    /** The meta-annotation identifying annotations of this kind. */
+    public final Class<? extends Annotation> metaAnnotation;
+    /** The built-in framework qualifier for this contract. */
+    public final Class<? extends Annotation> frameworkContractClass;
+    /** The built-in framework qualifier for repeated occurrences of this contract. */
+    public final Class<? extends Annotation> frameworkContractListClass;
+
+    /**
+     * Create a new Kind.
+     *
+     * @param errorKey used for constructing error messages
+     * @param metaAnnotation the meta-annotation identifying annotations of this kind
+     * @param frameworkContractClass the built-in framework qualifier for this contract
+     * @param frameworkContractListClass the built-in framework qualifier for repeated occurrences
+     *     of this contract
+     */
+    Kind(
+        String errorKey,
+        Class<? extends Annotation> metaAnnotation,
+        Class<? extends Annotation> frameworkContractClass,
+        Class<? extends Annotation> frameworkContractListClass) {
+      this.errorKey = errorKey;
+      this.metaAnnotation = metaAnnotation;
+      this.frameworkContractClass = frameworkContractClass;
+      this.frameworkContractListClass = frameworkContractListClass;
+    }
+  }
+
+  /**
+   * Creates a new Contract. This should be called only by the constructors for {@link
+   * Precondition}, {@link Postcondition}, and {@link ConditionalPostcondition}.
+   *
+   * @param kind precondition, postcondition, or conditional postcondition
+   * @param expressionString the Java expression that should have a type qualifier
+   * @param annotation the type qualifier that {@code expressionString} should have
+   * @param contractAnnotation the pre- or post-condition annotation that the programmer wrote; used
+   *     for diagnostic messages
+   */
+  private Contract(
+      Kind kind,
+      String expressionString,
+      AnnotationMirror annotation,
+      AnnotationMirror contractAnnotation) {
+    this.expressionString = expressionString;
+    this.annotation = annotation;
+    this.contractAnnotation = contractAnnotation;
+    this.kind = kind;
+  }
+
+  /**
+   * Creates a new Contract.
+   *
+   * @param kind precondition, postcondition, or conditional postcondition
+   * @param expressionString the Java expression that should have a type qualifier
+   * @param annotation the type qualifier that {@code expressionString} should have
+   * @param contractAnnotation the pre- or post-condition annotation that the programmer wrote; used
+   *     for diagnostic messages
+   * @param ensuresQualifierIf the ensuresQualifierIf field, for a conditional postcondition
+   * @return a new contract
+   */
+  public static Contract create(
+      Kind kind,
+      String expressionString,
+      AnnotationMirror annotation,
+      AnnotationMirror contractAnnotation,
+      Boolean ensuresQualifierIf) {
+    if ((ensuresQualifierIf != null) != (kind == Kind.CONDITIONALPOSTCONDITION)) {
+      throw new BugInCF(
+          "Mismatch: Contract.create(%s, %s, %s, %s, %s)",
+          kind, expressionString, annotation, contractAnnotation, ensuresQualifierIf);
+    }
+    switch (kind) {
+      case PRECONDITION:
+        return new Precondition(expressionString, annotation, contractAnnotation);
+      case POSTCONDITION:
+        return new Postcondition(expressionString, annotation, contractAnnotation);
+      case CONDITIONALPOSTCONDITION:
+        return new ConditionalPostcondition(
+            expressionString, annotation, contractAnnotation, ensuresQualifierIf);
+      default:
+        throw new BugInCF("Unrecognized kind: " + kind);
+    }
+  }
+
+  // Note that equality requires exact match of the run-time class and that it ignores the
+  // `contractAnnotation` field.
+  @Override
+  public boolean equals(@Nullable Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null) {
+      return false;
+    }
+    if (getClass() != o.getClass()) {
+      return false;
+    }
+
+    Contract otherContract = (Contract) o;
+
+    return kind == otherContract.kind
+        && Objects.equals(expressionString, otherContract.expressionString)
+        && Objects.equals(annotation, otherContract.annotation);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(kind, expressionString, annotation);
+  }
+
+  @Override
+  public String toString() {
+    return String.format(
+        "%s{expressionString=%s, annotation=%s, contractAnnotation=%s}",
+        getClass().getSimpleName(), expressionString, annotation, contractAnnotation);
+  }
+
+  /**
+   * Viewpoint-adapt {@link #annotation} using {@code stringToJavaExpr}.
+   *
+   * <p>For example, if the contract is {@code @EnsuresKeyFor(value = "this.field", map = "map")},
+   * {@code annotation} is {@code @KeyFor("map")}. This method applies {@code stringToJava} to "map"
+   * and returns a new {@code KeyFor} annotation with the result.
+   *
+   * @param factory used to get {@link DependentTypesHelper}
+   * @param stringToJavaExpr function used to convert strings to {@link JavaExpression}s
+   * @param errorTree if non-null, where to report any errors that occur when parsing the dependent
+   *     type annotation; if null, report no errors
+   * @return the viewpoint-adapted annotation, or {@link #annotation} if it is not a dependent type
+   *     annotation
+   */
+  public AnnotationMirror viewpointAdaptDependentTypeAnnotation(
+      GenericAnnotatedTypeFactory<?, ?, ?, ?> factory,
+      StringToJavaExpression stringToJavaExpr,
+      @Nullable Tree errorTree) {
+    DependentTypesHelper dependentTypesHelper = factory.getDependentTypesHelper();
+    AnnotationMirror standardized =
+        dependentTypesHelper.convertAnnotationMirror(stringToJavaExpr, annotation);
+    if (standardized == null) {
+      return annotation;
+    }
+    if (errorTree != null) {
+      dependentTypesHelper.checkAnnotationForErrorExpressions(standardized, errorTree);
+    }
+    return standardized;
+  }
+
+  /** A precondition contract. */
+  public static class Precondition extends Contract {
+    /**
+     * Create a precondition contract.
+     *
+     * @param expressionString the Java expression that should have a type qualifier
+     * @param annotation the type qualifier that {@code expressionString} should have
+     * @param contractAnnotation the precondition annotation that the programmer wrote; used for
+     *     diagnostic messages
+     */
+    public Precondition(
+        String expressionString, AnnotationMirror annotation, AnnotationMirror contractAnnotation) {
+      super(Kind.PRECONDITION, expressionString, annotation, contractAnnotation);
+    }
+  }
+
+  /** A postcondition contract. */
+  public static class Postcondition extends Contract {
+    /**
+     * Create a postcondition contract.
+     *
+     * @param expressionString the Java expression that should have a type qualifier
+     * @param annotation the type qualifier that {@code expressionString} should have
+     * @param contractAnnotation the postcondition annotation that the programmer wrote; used for
+     *     diagnostic messages
+     */
+    public Postcondition(
+        String expressionString, AnnotationMirror annotation, AnnotationMirror contractAnnotation) {
+      super(Kind.POSTCONDITION, expressionString, annotation, contractAnnotation);
+    }
+  }
+
+  /**
+   * Represents a conditional postcondition that must be verified by {@code BaseTypeVisitor} or one
+   * of its subclasses. Automatically extracted from annotations with meta-annotation
+   * {@code @ConditionalPostconditionAnnotation}, such as {@code EnsuresNonNullIf}.
+   */
+  public static class ConditionalPostcondition extends Contract {
+
+    /**
+     * The return value for the annotated method that ensures that the conditional postcondition
+     * holds. For example, given
+     *
+     * <pre>
+     * {@code @EnsuresNonNullIf(expression="foo", result=false) boolean method()}
+     * </pre>
+     *
+     * {@code foo} is guaranteed to be {@code @NonNull} after a call to {@code method()} that
+     * returns {@code false}.
+     */
+    public final boolean resultValue;
+
+    /**
+     * Create a new conditional postcondition.
+     *
+     * @param expressionString the Java expression that should have a type qualifier
+     * @param annotation the type qualifier that {@code expressionString} should have
+     * @param contractAnnotation the postcondition annotation that the programmer wrote; used for
+     *     diagnostic messages
+     * @param resultValue whether the condition is the method returning true or false
+     */
+    public ConditionalPostcondition(
+        String expressionString,
+        AnnotationMirror annotation,
+        AnnotationMirror contractAnnotation,
+        boolean resultValue) {
+      super(Kind.CONDITIONALPOSTCONDITION, expressionString, annotation, contractAnnotation);
+      this.resultValue = resultValue;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+      return super.equals(o) && resultValue == ((ConditionalPostcondition) o).resultValue;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(super.hashCode(), resultValue);
+    }
+
+    @Override
+    public String toString() {
+      String superToString = super.toString();
+      return superToString.substring(0, superToString.length() - 1)
+          + ", annoResult="
+          + resultValue
+          + "}";
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/ContractsFromMethod.java b/framework/src/main/java/org/checkerframework/framework/util/ContractsFromMethod.java
new file mode 100644
index 0000000..be0e307
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/ContractsFromMethod.java
@@ -0,0 +1,309 @@
+package org.checkerframework.framework.util;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+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.Name;
+import javax.lang.model.util.ElementFilter;
+import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
+import org.checkerframework.framework.qual.EnsuresQualifier;
+import org.checkerframework.framework.qual.EnsuresQualifierIf;
+import org.checkerframework.framework.qual.PostconditionAnnotation;
+import org.checkerframework.framework.qual.PreconditionAnnotation;
+import org.checkerframework.framework.qual.QualifierArgument;
+import org.checkerframework.framework.qual.RequiresQualifier;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.util.Contract.Kind;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A utility class to retrieve pre- and postconditions from a method.
+ *
+ * @see PreconditionAnnotation
+ * @see RequiresQualifier
+ * @see PostconditionAnnotation
+ * @see EnsuresQualifier
+ * @see ConditionalPostconditionAnnotation
+ * @see EnsuresQualifierIf
+ */
+// TODO: This class assumes that most annotations have a field named "expression". If not, issue a
+// more helpful error message.
+public class ContractsFromMethod {
+
+  /** The QualifierArgument.value field/element. */
+  ExecutableElement qualifierArgumentValueElement;
+
+  /** The factory that this ContractsFromMethod is associated with. */
+  protected GenericAnnotatedTypeFactory<?, ?, ?, ?> factory;
+
+  /**
+   * Creates a ContractsFromMethod for the given factory.
+   *
+   * @param factory the type factory associated with the newly-created ContractsFromMethod
+   */
+  public ContractsFromMethod(GenericAnnotatedTypeFactory<?, ?, ?, ?> factory) {
+    this.factory = factory;
+    qualifierArgumentValueElement =
+        TreeUtils.getMethod(QualifierArgument.class, "value", 0, factory.getProcessingEnv());
+  }
+
+  /**
+   * Returns all the contracts on method or constructor {@code executableElement}.
+   *
+   * @param executableElement the method or constructor whose contracts to retrieve
+   * @return the contracts on {@code executableElement}
+   */
+  public Set<Contract> getContracts(ExecutableElement executableElement) {
+    Set<Contract> contracts = new LinkedHashSet<>();
+    contracts.addAll(getPreconditions(executableElement));
+    contracts.addAll(getPostconditions(executableElement));
+    contracts.addAll(getConditionalPostconditions(executableElement));
+    return contracts;
+  }
+
+  /**
+   * Returns the precondition contracts on method or constructor {@code executableElement}.
+   *
+   * @param executableElement the method whose contracts to return
+   * @return the precondition contracts on {@code executableElement}
+   */
+  public Set<Contract.Precondition> getPreconditions(ExecutableElement executableElement) {
+    return getContracts(executableElement, Kind.PRECONDITION, Contract.Precondition.class);
+  }
+
+  /**
+   * Returns the postcondition contracts on {@code executableElement}.
+   *
+   * @param executableElement the method whose contracts to return
+   * @return the postcondition contracts on {@code executableElement}
+   */
+  public Set<Contract.Postcondition> getPostconditions(ExecutableElement executableElement) {
+    return getContracts(executableElement, Kind.POSTCONDITION, Contract.Postcondition.class);
+  }
+
+  /**
+   * Returns the conditional postcondition contracts on method {@code methodElement}.
+   *
+   * @param methodElement the method whose contracts to return
+   * @return the conditional postcondition contracts on {@code methodElement}
+   */
+  public Set<Contract.ConditionalPostcondition> getConditionalPostconditions(
+      ExecutableElement methodElement) {
+    return getContracts(
+        methodElement, Kind.CONDITIONALPOSTCONDITION, Contract.ConditionalPostcondition.class);
+  }
+
+  /// Helper methods
+
+  /**
+   * Returns the contracts (of a particular kind) on method or constructor {@code
+   * executableElement}.
+   *
+   * @param <T> the type of {@link Contract} to return
+   * @param executableElement the method whose contracts to return
+   * @param kind the kind of contracts to retrieve
+   * @param clazz the class to determine the return type
+   * @return the contracts on {@code executableElement}
+   */
+  private <T extends Contract> Set<T> getContracts(
+      ExecutableElement executableElement, Kind kind, Class<T> clazz) {
+    Set<T> result = new LinkedHashSet<>();
+    // Check for a single framework-defined contract annotation.
+    AnnotationMirror frameworkContractAnno =
+        factory.getDeclAnnotation(executableElement, kind.frameworkContractClass);
+    result.addAll(getContract(kind, frameworkContractAnno, clazz));
+
+    // Check for a framework-defined wrapper around contract annotations.
+    // The result is RequiresQualifier.List, EnsuresQualifier.List, or EnsuresQualifierIf.List.
+    AnnotationMirror frameworkContractListAnno =
+        factory.getDeclAnnotation(executableElement, kind.frameworkContractListClass);
+    if (frameworkContractListAnno != null) {
+      List<AnnotationMirror> frameworkContractAnnoList =
+          factory.getContractListValues(frameworkContractListAnno);
+      for (AnnotationMirror a : frameworkContractAnnoList) {
+        result.addAll(getContract(kind, a, clazz));
+      }
+    }
+
+    // Check for type-system specific annotations.
+    List<Pair<AnnotationMirror, AnnotationMirror>> declAnnotations =
+        factory.getDeclAnnotationWithMetaAnnotation(executableElement, kind.metaAnnotation);
+    for (Pair<AnnotationMirror, AnnotationMirror> r : declAnnotations) {
+      AnnotationMirror anno = r.first;
+      // contractAnno is the meta-annotation on anno.
+      AnnotationMirror contractAnno = r.second;
+      AnnotationMirror enforcedQualifier =
+          getQualifierEnforcedByContractAnnotation(contractAnno, anno);
+      if (enforcedQualifier == null) {
+        continue;
+      }
+      List<String> expressions = factory.getContractExpressions(kind, anno);
+      Collections.sort(expressions);
+      Boolean ensuresQualifierIfResult = factory.getEnsuresQualifierIfResult(kind, anno);
+
+      for (String expr : expressions) {
+        T contract =
+            clazz.cast(
+                Contract.create(kind, expr, enforcedQualifier, anno, ensuresQualifierIfResult));
+        result.add(contract);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns the contracts expressed by the given framework contract annotation.
+   *
+   * @param <T> the type of {@link Contract} to return
+   * @param kind the kind of {@code contractAnnotation}
+   * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, {@link
+   *     EnsuresQualifierIf}, or null
+   * @param clazz the class to determine the return type
+   * @return the contracts expressed by the given annotation, or the empty set if the argument is
+   *     null
+   */
+  private <T extends Contract> Set<T> getContract(
+      Contract.Kind kind, AnnotationMirror contractAnnotation, Class<T> clazz) {
+    if (contractAnnotation == null) {
+      return Collections.emptySet();
+    }
+
+    AnnotationMirror enforcedQualifier =
+        getQualifierEnforcedByContractAnnotation(contractAnnotation);
+    if (enforcedQualifier == null) {
+      return Collections.emptySet();
+    }
+
+    List<String> expressions = factory.getContractExpressions(contractAnnotation);
+    Collections.sort(expressions);
+
+    Boolean ensuresQualifierIfResult =
+        factory.getEnsuresQualifierIfResult(kind, contractAnnotation);
+
+    Set<T> result = new LinkedHashSet<>();
+    for (String expr : expressions) {
+      T contract =
+          clazz.cast(
+              Contract.create(
+                  kind, expr, enforcedQualifier, contractAnnotation, ensuresQualifierIfResult));
+      result.add(contract);
+    }
+    return result;
+  }
+
+  /**
+   * Returns the annotation mirror as specified by the {@code qualifier} element in {@code
+   * contractAnno}. May return null.
+   *
+   * @param contractAnno a pre- or post-condition annotation, such as {@code @RequiresQualifier}
+   * @return the type annotation specified in {@code contractAnno.qualifier}
+   */
+  private AnnotationMirror getQualifierEnforcedByContractAnnotation(AnnotationMirror contractAnno) {
+    return getQualifierEnforcedByContractAnnotation(contractAnno, null, null);
+  }
+
+  /**
+   * Returns the annotation mirror as specified by the {@code qualifier} element in {@code
+   * contractAnno}, with elements/arguments taken from {@code argumentAnno}. May return null.
+   *
+   * @param contractAnno a pre- or post-condition annotation, such as {@code @RequiresQualifier}
+   * @param argumentAnno supplies the elements/fields in the return value
+   * @return the type annotation specified in {@code contractAnno.qualifier}
+   */
+  private AnnotationMirror getQualifierEnforcedByContractAnnotation(
+      AnnotationMirror contractAnno, AnnotationMirror argumentAnno) {
+
+    Map<String, String> argumentRenaming =
+        makeArgumentRenaming(argumentAnno.getAnnotationType().asElement());
+    return getQualifierEnforcedByContractAnnotation(contractAnno, argumentAnno, argumentRenaming);
+  }
+
+  /**
+   * Returns the annotation mirror as specified by the "qualifier" element in {@code contractAnno}.
+   * If {@code argumentAnno} is specified, then elements/arguments are copied from {@code
+   * argumentAnno} to the returned annotation, renamed according to {@code argumentRenaming}. If
+   * {@code argumentAnno} is not specified, the result has no elements/arguments; this may make it
+   * invalid.
+   *
+   * <p>This is a helper method. Use one of its overloads if possible.
+   *
+   * @param contractAnno a contract annotation, such as {@code @RequiresQualifier}, which has a
+   *     {@code qualifier} element/field
+   * @param argumentAnno annotation containing the element {@code values}, or {@code null}
+   * @param argumentRenaming renaming of argument names, which maps from names in {@code
+   *     argumentAnno} to names used in the returned annotation, or {@code null}
+   * @return a qualifier whose type is that of {@code contract.qualifier}, or an alias for it, or
+   *     null if it is not a supported qualifier of the type system
+   */
+  private AnnotationMirror getQualifierEnforcedByContractAnnotation(
+      AnnotationMirror contractAnno,
+      AnnotationMirror argumentAnno,
+      Map<String, String> argumentRenaming) {
+
+    @SuppressWarnings("deprecation") // permitted for use in the framework
+    Name c = AnnotationUtils.getElementValueClassName(contractAnno, "qualifier", false);
+
+    AnnotationMirror anno;
+    if (argumentAnno == null || argumentRenaming.isEmpty()) {
+      // If there are no arguments, use factory method that allows caching
+      anno = AnnotationBuilder.fromName(factory.getElementUtils(), c);
+    } else {
+      AnnotationBuilder builder = new AnnotationBuilder(factory.getProcessingEnv(), c);
+      builder.copyRenameElementValuesFromAnnotation(argumentAnno, argumentRenaming);
+      anno = builder.build();
+    }
+
+    if (factory.isSupportedQualifier(anno)) {
+      return anno;
+    } else {
+      AnnotationMirror aliasedAnno = factory.canonicalAnnotation(anno);
+      if (factory.isSupportedQualifier(aliasedAnno)) {
+        return aliasedAnno;
+      } else {
+        return null;
+      }
+    }
+  }
+
+  /**
+   * Makes a map from element names of a contract annotation to qualifier argument names, as defined
+   * by {@link QualifierArgument}.
+   *
+   * <p>Each element of {@code contractAnnoElement} that is annotated by {@link QualifierArgument}
+   * is mapped to the name specified by the value of {@link QualifierArgument}. If the value is not
+   * specified or is an empty string, then the element is mapped to an argument of the same name.
+   *
+   * @param contractAnnoElement the declaration of the contract annotation containing the elements
+   * @return map from the names of elements of {@code sourceArgumentNames} to the corresponding
+   *     qualifier argument names
+   * @see QualifierArgument
+   */
+  private Map<String, String> makeArgumentRenaming(Element contractAnnoElement) {
+    HashMap<String, String> argumentRenaming = new HashMap<>();
+    for (ExecutableElement meth :
+        ElementFilter.methodsIn(contractAnnoElement.getEnclosedElements())) {
+      AnnotationMirror argumentAnnotation =
+          factory.getDeclAnnotation(meth, QualifierArgument.class);
+      if (argumentAnnotation != null) {
+        String sourceName = meth.getSimpleName().toString();
+        String targetName =
+            AnnotationUtils.getElementValue(
+                argumentAnnotation, qualifierArgumentValueElement, String.class);
+        if (targetName == null || targetName.isEmpty()) {
+          targetName = sourceName;
+        }
+        argumentRenaming.put(sourceName, targetName);
+      }
+    }
+    return argumentRenaming;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/DefaultQualifierKindHierarchy.java b/framework/src/main/java/org/checkerframework/framework/util/DefaultQualifierKindHierarchy.java
new file mode 100644
index 0000000..6784c4a
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/DefaultQualifierKindHierarchy.java
@@ -0,0 +1,821 @@
+package org.checkerframework.framework.util;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypeSystemError;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * This is the default implementation of {@link QualifierKindHierarchy}.
+ *
+ * <p>By default, the subtyping information and information about polymorphic qualifiers is read
+ * from meta-annotations on the annotation classes. This information is used to infer further
+ * information such as top and bottom qualifiers. Subclasses can override the following methods to
+ * change this behavior:
+ *
+ * <ul>
+ *   <li>{@link #createQualifierKinds(Collection)}
+ *   <li>{@link #createDirectSuperMap()}
+ *   <li>{@link #initializePolymorphicQualifiers()}
+ *   <li>{@link #initializeQualifierKindFields(Map)}
+ *   <li>{@link #createLubsMap()}
+ *   <li>{@link #createGlbsMap()}
+ * </ul>
+ *
+ * {@link DefaultQualifierKindHierarchy.DefaultQualifierKind} is the implementation used for {@link
+ * QualifierKind} by this class.
+ */
+@AnnotatedFor("nullness")
+public class DefaultQualifierKindHierarchy implements QualifierKindHierarchy {
+
+  /**
+   * A mapping from canonical name of a qualifier class to the QualifierKind object representing
+   * that class.
+   */
+  protected final Map<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind;
+
+  /**
+   * A list of all {@link QualifierKind}s for this DefaultQualifierKindHierarchy, sorted in
+   * ascending order.
+   */
+  protected final List<DefaultQualifierKind> qualifierKinds;
+
+  /** All the qualifier kinds that are the top qualifier in their hierarchy. */
+  private final Set<DefaultQualifierKind> tops;
+
+  /** All the qualifier kinds that are the bottom qualifier in their hierarchy. */
+  private final Set<DefaultQualifierKind> bottoms;
+
+  /**
+   * Holds the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of kind1
+   * and kind2.
+   */
+  private final Map<QualifierKind, Map<QualifierKind, QualifierKind>> lubs;
+
+  /**
+   * Holds the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of kind1
+   * and kind2.
+   */
+  private final Map<QualifierKind, Map<QualifierKind, QualifierKind>> glbs;
+
+  @Override
+  public Set<? extends QualifierKind> getTops() {
+    return tops;
+  }
+
+  @Override
+  public Set<? extends QualifierKind> getBottoms() {
+    return bottoms;
+  }
+
+  @Override
+  public @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2) {
+    @SuppressWarnings("nullness:dereference.of.nullable") // All QualifierKinds are keys in lubs.
+    QualifierKind result = lubs.get(q1).get(q2);
+    return result;
+  }
+
+  @Override
+  public @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2) {
+    @SuppressWarnings("nullness:dereference.of.nullable") // All QualifierKinds are keys in glbs.
+    QualifierKind result = glbs.get(q1).get(q2);
+    return result;
+  }
+
+  @Override
+  public List<? extends QualifierKind> allQualifierKinds() {
+    return qualifierKinds;
+  }
+
+  @Override
+  public @Nullable QualifierKind getQualifierKind(@CanonicalName String name) {
+    return nameToQualifierKind.get(name);
+  }
+
+  /**
+   * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its
+   * qualifier kinds.
+   *
+   * @param qualifierClasses all the classes of qualifiers supported by this hierarchy
+   */
+  public DefaultQualifierKindHierarchy(Collection<Class<? extends Annotation>> qualifierClasses) {
+    this(qualifierClasses, null, null);
+  }
+
+  /**
+   * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its
+   * qualifier kinds.
+   *
+   * <p>For some type systems, qualifiers may be added at run time, so the {@link SubtypeOf}
+   * meta-annotation on the bottom qualifier class cannot specify all other qualifiers. For those
+   * type systems, use this constructor. Otherwise, use {@link
+   * #DefaultQualifierKindHierarchy(Collection)}.
+   *
+   * @param qualifierClasses all the classes of qualifiers supported by this hierarchy
+   * @param bottom the bottom qualifier of this hierarchy
+   */
+  public DefaultQualifierKindHierarchy(
+      Collection<Class<? extends Annotation>> qualifierClasses,
+      Class<? extends Annotation> bottom) {
+    this(qualifierClasses, bottom, null);
+  }
+
+  /**
+   * Private constructor that sets the bottom qualifier if {@code bottom} is nonnull.
+   *
+   * @param qualifierClasses all the classes of qualifiers supported by this hierarchy
+   * @param bottom the bottom qualifier of this hierarchy or null if bottom can be inferred from the
+   *     meta-annotations
+   * @param voidParam void parameter to differentiate from {@link
+   *     #DefaultQualifierKindHierarchy(Collection, Class)}
+   */
+  private DefaultQualifierKindHierarchy(
+      Collection<Class<? extends Annotation>> qualifierClasses,
+      @Nullable Class<? extends Annotation> bottom,
+      @SuppressWarnings("UnusedVariable") Void voidParam) {
+    this.nameToQualifierKind = createQualifierKinds(qualifierClasses);
+    this.qualifierKinds = new ArrayList<>(nameToQualifierKind.values());
+    Collections.sort(qualifierKinds);
+
+    Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap = createDirectSuperMap();
+    if (bottom != null) {
+      setBottom(bottom, directSuperMap);
+    }
+    this.tops = createTopsSet(directSuperMap);
+    this.bottoms = createBottomsSet(directSuperMap);
+    initializePolymorphicQualifiers();
+    initializeQualifierKindFields(directSuperMap);
+    this.lubs = createLubsMap();
+    this.glbs = createGlbsMap();
+
+    verifyHierarchy(directSuperMap);
+  }
+
+  /**
+   * Verifies that the {@link DefaultQualifierKindHierarchy} is a valid hierarchy.
+   *
+   * @param directSuperMap mapping from qualifier to its direct supertypes; used to verify that a
+   *     polymorphic annotation does not have a {@link SubtypeOf} meta-annotation
+   * @throws TypeSystemError if the hierarchy isn't valid
+   */
+  @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"})
+  protected void verifyHierarchy(
+      @UnderInitialization DefaultQualifierKindHierarchy this,
+      Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) {
+    for (DefaultQualifierKind qualifierKind : qualifierKinds) {
+      boolean isPoly = qualifierKind.isPoly();
+      boolean hasSubtypeOfAnno = directSuperMap.containsKey(qualifierKind);
+      if (isPoly && hasSubtypeOfAnno) {
+        // Polymorphic qualifiers with upper and lower bounds are currently not supported.
+        throw new TypeSystemError(
+            "AnnotatedTypeFactory: "
+                + qualifierKind
+                + " is polymorphic and specifies super qualifiers.%n"
+                + "Remove the @PolymorphicQualifier or @SubtypeOf annotation from it.");
+      } else if (!isPoly && !hasSubtypeOfAnno) {
+        throw new TypeSystemError(
+            "AnnotatedTypeFactory: %s does not specify its super qualifiers.%n"
+                + "Add an @SubtypeOf or @PolymorphicQualifier annotation to it,%n"
+                + "or if it is an alias, exclude it from `createSupportedTypeQualifiers()`.",
+            qualifierKind);
+      } else if (isPoly) {
+        if (qualifierKind.top == null) {
+          throw new TypeSystemError(
+              "PolymorphicQualifier, %s, has to specify a type hierarchy in its"
+                  + " @PolymorphicQualifier meta-annotation, if more than one exists; top types:"
+                  + " [%s].",
+              qualifierKind, StringsPlume.join(", ", tops));
+        } else if (!tops.contains(qualifierKind.top)) {
+          throw new TypeSystemError(
+              "Polymorphic qualifier %s has invalid top %s. Top qualifiers: %s",
+              qualifierKind, qualifierKind.top, StringsPlume.join(", ", tops));
+        }
+      }
+    }
+
+    if (bottoms.size() != tops.size()) {
+      throw new TypeSystemError(
+          "Number of tops not equal to number of bottoms: Tops: [%s] Bottoms: [%s]",
+          StringsPlume.join(", ", tops), StringsPlume.join(", ", bottoms));
+    }
+  }
+
+  /**
+   * Creates all QualifierKind objects for the given qualifier classes and adds them to
+   * qualifierClassMap. This method does not initialize all fields in the {@link QualifierKind};
+   * that is done by {@link #initializeQualifierKindFields(Map)}.
+   *
+   * @param qualifierClasses classes of annotations that are type qualifiers
+   * @return a mapping from the canonical name of an annotation class to {@link QualifierKind}
+   */
+  protected Map<@Interned @CanonicalName String, DefaultQualifierKind> createQualifierKinds(
+      @UnderInitialization DefaultQualifierKindHierarchy this,
+      Collection<Class<? extends Annotation>> qualifierClasses) {
+    TreeMap<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind =
+        new TreeMap<>();
+    for (Class<? extends Annotation> clazz : qualifierClasses) {
+      @SuppressWarnings("interning") // uniqueness is tested immediately below
+      @Interned DefaultQualifierKind qualifierKind = new DefaultQualifierKind(clazz);
+      if (nameToQualifierKind.containsKey(qualifierKind.getName())) {
+        throw new TypeSystemError("Duplicate QualifierKind " + qualifierKind.getName());
+      }
+      nameToQualifierKind.put(qualifierKind.getName(), qualifierKind);
+    }
+    return Collections.unmodifiableMap(nameToQualifierKind);
+  }
+
+  /**
+   * Creates a mapping from a {@link QualifierKind} to a set of its direct super qualifier kinds.
+   * The direct super qualifier kinds do not contain the qualifier itself. This mapping is used to
+   * create the bottom set, to create the top set, and by {@link
+   * #initializeQualifierKindFields(Map)}.
+   *
+   * <p>This implementation uses the {@link SubtypeOf} meta-annotation. Subclasses may override this
+   * method to create the direct super map some other way.
+   *
+   * <p>Note that this method is called from the constructor when {@link #nameToQualifierKind} and
+   * {@link #qualifierKinds} are the only fields that have nonnull values. This method is not
+   * static, so it can be overridden by subclasses.
+   *
+   * @return a mapping from each {@link QualifierKind} to a set of its direct super qualifiers
+   */
+  @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"})
+  protected Map<DefaultQualifierKind, Set<DefaultQualifierKind>> createDirectSuperMap(
+      @UnderInitialization DefaultQualifierKindHierarchy this) {
+    Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap = new TreeMap<>();
+    for (DefaultQualifierKind qualifierKind : qualifierKinds) {
+      SubtypeOf subtypeOfMetaAnno =
+          qualifierKind.getAnnotationClass().getAnnotation(SubtypeOf.class);
+      if (subtypeOfMetaAnno == null) {
+        // qualifierKind has no @SubtypeOf: it must be top or polymorphic
+        continue;
+      }
+      Set<DefaultQualifierKind> directSupers = new TreeSet<>();
+      for (Class<? extends Annotation> superClazz : subtypeOfMetaAnno.value()) {
+        String superName = QualifierKindHierarchy.annotationClassName(superClazz);
+        DefaultQualifierKind superQualifier = nameToQualifierKind.get(superName);
+        if (superQualifier == null) {
+          throw new TypeSystemError(
+              "%s @Subtype argument %s isn't in the hierarchy. Qualifiers: [%s]",
+              qualifierKind, superName, StringsPlume.join(", ", qualifierKinds));
+        }
+        directSupers.add(superQualifier);
+      }
+      directSuperMap.put(qualifierKind, directSupers);
+    }
+    return directSuperMap;
+  }
+
+  /**
+   * This method sets bottom to the given class and modifies {@code directSuperMap} to add all
+   * leaves to its super qualifier kinds. Leaves are qualifier kinds that are not super qualifier
+   * kinds of another qualifier kind and are not polymorphic.
+   *
+   * @param bottom the class of the bottom qualifier in the hierarchy
+   * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super
+   *     qualifiers; side-effected by this method
+   */
+  @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"})
+  private void setBottom(
+      @UnderInitialization DefaultQualifierKindHierarchy this,
+      Class<? extends Annotation> bottom,
+      Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) {
+    DefaultQualifierKind bottomKind =
+        nameToQualifierKind.get(QualifierKindHierarchy.annotationClassName(bottom));
+    if (bottomKind == null) {
+      throw new TypeSystemError(
+          "QualifierKindHierarchy#setBottom: %s is not in the hierarchy",
+          bottom.getCanonicalName());
+    }
+
+    Set<DefaultQualifierKind> leaves = new TreeSet<>(qualifierKinds);
+    leaves.remove(bottomKind);
+    directSuperMap.forEach((sub, supers) -> leaves.removeAll(supers));
+    Set<DefaultQualifierKind> bottomDirectSuperQuals = directSuperMap.get(bottomKind);
+    if (bottomDirectSuperQuals == null) {
+      directSuperMap.put(bottomKind, leaves);
+    } else {
+      bottomDirectSuperQuals.addAll(leaves);
+    }
+  }
+
+  /**
+   * Creates the set of top {@link QualifierKind}s by searching {@code directSuperMap} for qualifier
+   * kinds without any direct super qualifier kinds.
+   *
+   * <p>Subclasses should override {@link #createDirectSuperMap} to change the tops and not this
+   * method, because other methods expect the directSuperMap to be complete.
+   *
+   * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super
+   *     qualifier kinds; created by {@link #createDirectSuperMap()}
+   * @return the set of top {@link QualifierKind}s
+   */
+  private Set<DefaultQualifierKind> createTopsSet(
+      @UnderInitialization DefaultQualifierKindHierarchy this,
+      Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) {
+    Set<DefaultQualifierKind> tops = new TreeSet<>();
+    directSuperMap.forEach(
+        (qualifierKind, superQuals) -> {
+          if (superQuals.isEmpty()) {
+            tops.add(qualifierKind);
+          }
+        });
+    return tops;
+  }
+
+  /**
+   * Creates the set of bottom {@link QualifierKind}s by searching {@code directSuperMap} for
+   * qualifiers that are not a direct super qualifier kind of another qualifier kind.
+   *
+   * <p>Subclasses should override {@link #createDirectSuperMap} or {@link #setBottom} to change the
+   * bottoms and not this method, because other methods expect the directSuperMap to be complete.
+   *
+   * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super
+   *     qualifier kinds; created by {@link #createDirectSuperMap()}
+   * @return the set of bottom {@link QualifierKind}s
+   */
+  private Set<DefaultQualifierKind> createBottomsSet(
+      @UnderInitialization DefaultQualifierKindHierarchy this,
+      Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) {
+    Set<DefaultQualifierKind> bottoms = new HashSet<>(directSuperMap.keySet());
+    for (Set<DefaultQualifierKind> superKinds : directSuperMap.values()) {
+      bottoms.removeAll(superKinds);
+    }
+    return bottoms;
+  }
+
+  /**
+   * Iterates over all the qualifier kinds and adds all polymorphic qualifier kinds to
+   * polymorphicQualifiers. Also sets {@link DefaultQualifierKind#poly} and {@link
+   * DefaultQualifierKind#top} for the polymorphic qualifiers, and sets {@link
+   * DefaultQualifierKind#poly} for the top qualifiers.
+   *
+   * <p>Requires that tops has been initialized.
+   */
+  @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds", "this.tops"})
+  protected void initializePolymorphicQualifiers(
+      @UnderInitialization DefaultQualifierKindHierarchy this) {
+    for (DefaultQualifierKind qualifierKind : qualifierKinds) {
+      Class<? extends Annotation> clazz = qualifierKind.getAnnotationClass();
+      PolymorphicQualifier polyMetaAnno = clazz.getAnnotation(PolymorphicQualifier.class);
+      if (polyMetaAnno == null) {
+        continue;
+      }
+      qualifierKind.poly = qualifierKind;
+      String topName = QualifierKindHierarchy.annotationClassName(polyMetaAnno.value());
+      if (nameToQualifierKind.containsKey(topName)) {
+        qualifierKind.top = nameToQualifierKind.get(topName);
+      } else if (topName.equals(Annotation.class.getCanonicalName())) {
+        // Annotation.class is the default value of PolymorphicQualifier. If it is used,
+        // then there must be exactly one top.
+        if (tops.size() == 1) {
+          qualifierKind.top = tops.iterator().next();
+        } else {
+          throw new TypeSystemError(
+              "Polymorphic qualifier %s did not specify a top annotation class. Tops: [%s]",
+              qualifierKind, StringsPlume.join(", ", tops));
+        }
+      } else {
+        throw new TypeSystemError(
+            "Polymorphic qualifier %s's top, %s, is not a qualifier.", qualifierKind, topName);
+      }
+      qualifierKind.strictSuperTypes = Collections.singleton(qualifierKind.top);
+      qualifierKind.top.poly = qualifierKind;
+    }
+  }
+
+  /**
+   * For each qualifier kind in {@code directSuperMap}, initializes {@link
+   * DefaultQualifierKind#strictSuperTypes}, {@link DefaultQualifierKind#top}, {@link
+   * DefaultQualifierKind#bottom}, and {@link DefaultQualifierKind#poly}.
+   *
+   * <p>Requires tops, bottoms, and polymorphicQualifiers to be initialized.
+   *
+   * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super
+   *     qualifier kinds; created by {@link #createDirectSuperMap()}
+   */
+  @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"})
+  protected void initializeQualifierKindFields(
+      @UnderInitialization DefaultQualifierKindHierarchy this,
+      Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) {
+    for (DefaultQualifierKind qualifierKind : directSuperMap.keySet()) {
+      if (!qualifierKind.isPoly()) {
+        qualifierKind.strictSuperTypes = findAllTheSupers(qualifierKind, directSuperMap);
+      }
+    }
+    for (DefaultQualifierKind qualifierKind : qualifierKinds) {
+      for (DefaultQualifierKind top : tops) {
+        if (qualifierKind.isSubtypeOf(top)) {
+          if (qualifierKind.top == null) {
+            qualifierKind.top = top;
+          } else if (qualifierKind.top != top) {
+            throw new TypeSystemError(
+                "Multiple tops found for qualifier %s. Tops: %s and %s.",
+                qualifierKind, top, qualifierKind.top);
+          }
+        }
+      }
+      if (qualifierKind.top == null) {
+        throw new TypeSystemError(
+            "Qualifier %s isn't a subtype of any top. tops = %s", qualifierKind, tops);
+      }
+      qualifierKind.poly = qualifierKind.top.poly;
+    }
+    for (DefaultQualifierKind qualifierKind : qualifierKinds) {
+      for (DefaultQualifierKind bot : bottoms) {
+        if (bot.top != qualifierKind.top) {
+          continue;
+        }
+        if (qualifierKind.bottom == null) {
+          qualifierKind.bottom = bot;
+        } else if (qualifierKind.top != bot) {
+          throw new TypeSystemError(
+              "Multiple bottoms found for qualifier %s. Bottoms: %s and %s.",
+              qualifierKind, bot, qualifierKind.bottom);
+        }
+        if (qualifierKind.isPoly()) {
+          assert bot.strictSuperTypes != null
+              : "@AssumeAssertion(nullness): strictSuperTypes should be nonnull.";
+          bot.strictSuperTypes.add(qualifierKind);
+        }
+      }
+      if (qualifierKind.bottom == null) {
+        throw new TypeSystemError(
+            "Cannot find a bottom qualifier for %s. bottoms = %s", qualifierKind, bottoms);
+      }
+    }
+  }
+
+  /**
+   * Returns the set of all qualifier kinds that are a strict supertype of {@code qualifierKind}.
+   *
+   * @param qualifierKind the qualifier kind whose super types should be returned
+   * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super
+   *     qualifier kinds; created by {@link #createDirectSuperMap()}
+   * @return the set of all qualifier kinds that are a strict supertype of {@code qualifierKind}
+   */
+  private Set<QualifierKind> findAllTheSupers(
+      @UnderInitialization DefaultQualifierKindHierarchy this,
+      @KeyFor("#2") QualifierKind qualifierKind,
+      Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) {
+
+    Set<QualifierKind> allSupers = new TreeSet<>(directSuperMap.get(qualifierKind));
+
+    // Visit every super qualifier kind and add its super qualifier kinds to allSupers.
+    Queue<DefaultQualifierKind> toVisit = new ArrayDeque<>(directSuperMap.get(qualifierKind));
+    Set<DefaultQualifierKind> visited = new HashSet<>();
+    while (!toVisit.isEmpty()) {
+      DefaultQualifierKind superQualKind = toVisit.remove();
+      if (superQualKind == qualifierKind) {
+        throw new TypeSystemError("Cycle in hierarchy: %s", qualifierKind);
+      }
+
+      if (!visited.add(superQualKind) || superQualKind.isPoly()) {
+        continue;
+      }
+
+      Set<DefaultQualifierKind> superSuperQuals = directSuperMap.get(superQualKind);
+      if (superSuperQuals == null) {
+        throw new TypeSystemError(superQualKind + " is not a key in the directSuperMap");
+      }
+      toVisit.addAll(superSuperQuals);
+      allSupers.addAll(superSuperQuals);
+    }
+    return allSupers;
+  }
+
+  /**
+   * Creates the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of kind1
+   * and kind2.
+   *
+   * @return a mapping of lubs
+   */
+  @RequiresNonNull("this.qualifierKinds")
+  protected Map<QualifierKind, Map<QualifierKind, QualifierKind>> createLubsMap(
+      @UnderInitialization DefaultQualifierKindHierarchy this) {
+    Map<QualifierKind, Map<QualifierKind, QualifierKind>> lubs = new HashMap<>();
+    for (QualifierKind qual1 : qualifierKinds) {
+      for (QualifierKind qual2 : qualifierKinds) {
+        if (qual1.getTop() != qual2.getTop()) {
+          continue;
+        }
+        QualifierKind lub = findLub(qual1, qual2);
+        addToMapOfMap(lubs, qual1, qual2, lub, "lub");
+        addToMapOfMap(lubs, qual2, qual1, lub, "lub");
+      }
+    }
+    return lubs;
+  }
+
+  /**
+   * Returns the least upper bound of {@code qual1} and {@code qual2}.
+   *
+   * @param qual1 a qualifier kind
+   * @param qual2 a qualifier kind
+   * @return the least upper bound of {@code qual1} and {@code qual2}
+   */
+  private QualifierKind findLub(
+      @UnderInitialization DefaultQualifierKindHierarchy this,
+      QualifierKind qual1,
+      QualifierKind qual2) {
+    if (qual1 == qual2) {
+      return qual1;
+    } else if (qual1.isSubtypeOf(qual2)) {
+      return qual2;
+    } else if (qual2.isSubtypeOf(qual1)) {
+      return qual1;
+    }
+    Set<QualifierKind> allSuperTypes = new TreeSet<>(qual1.getStrictSuperTypes());
+    Set<? extends QualifierKind> qual2StrictSuperTypes = qual2.getStrictSuperTypes();
+    allSuperTypes.retainAll(qual2StrictSuperTypes);
+    Set<? extends QualifierKind> lubs = findLowestQualifiers(allSuperTypes);
+    if (lubs.size() != 1) {
+      throw new TypeSystemError(
+          "lub(%s, %s) should have size 1: [%s]", qual1, qual2, StringsPlume.join(", ", lubs));
+    }
+    QualifierKind lub = lubs.iterator().next();
+    if (lub.isPoly() && !qual1.isPoly() && !qual2.isPoly()) {
+      throw new TypeSystemError("lub(%s, %s) can't be poly: %s", qual1, qual2, lub);
+    }
+    return lub;
+  }
+
+  /**
+   * Returns the lowest qualifiers in the passed set.
+   *
+   * @param qualifierKinds a set of qualifiers
+   * @return the lowest qualifiers in the passed set
+   */
+  protected static Set<QualifierKind> findLowestQualifiers(Set<QualifierKind> qualifierKinds) {
+    Set<QualifierKind> lowestQualifiers = new TreeSet<>(qualifierKinds);
+    for (QualifierKind a1 : qualifierKinds) {
+      lowestQualifiers.removeIf(a2 -> a1 != a2 && a1.isSubtypeOf(a2));
+    }
+    return lowestQualifiers;
+  }
+
+  /**
+   * Creates the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of kind1
+   * and kind2.
+   *
+   * @return a mapping of glb
+   */
+  @RequiresNonNull("this.qualifierKinds")
+  protected Map<QualifierKind, Map<QualifierKind, QualifierKind>> createGlbsMap(
+      @UnderInitialization DefaultQualifierKindHierarchy this) {
+    Map<QualifierKind, Map<QualifierKind, QualifierKind>> glbs = new TreeMap<>();
+    for (QualifierKind qual1 : qualifierKinds) {
+      for (QualifierKind qual2 : qualifierKinds) {
+        if (qual1.getTop() != qual2.getTop()) {
+          continue;
+        }
+        QualifierKind glb = findGlb(qual1, qual2);
+        addToMapOfMap(glbs, qual1, qual2, glb, "glb");
+        addToMapOfMap(glbs, qual2, qual1, glb, "glb");
+      }
+    }
+    return glbs;
+  }
+
+  /**
+   * Returns the greatest lower bound of {@code qual1} and {@code qual2}.
+   *
+   * @param qual1 a qualifier kind
+   * @param qual2 a qualifier kind
+   * @return the greatest lower bound of {@code qual1} and {@code qual2}
+   */
+  @RequiresNonNull("this.qualifierKinds")
+  private QualifierKind findGlb(
+      @UnderInitialization DefaultQualifierKindHierarchy this,
+      QualifierKind qual1,
+      QualifierKind qual2) {
+    if (qual1 == qual2) {
+      return qual1;
+    } else if (qual1.isSubtypeOf(qual2)) {
+      return qual1;
+    } else if (qual2.isSubtypeOf(qual1)) {
+      return qual2;
+    }
+    Set<QualifierKind> allSubTypes = new TreeSet<>();
+    for (QualifierKind qualifierKind : qualifierKinds) {
+      if (qualifierKind.isSubtypeOf(qual1) && qualifierKind.isSubtypeOf(qual2)) {
+        allSubTypes.add(qualifierKind);
+      }
+    }
+    Set<QualifierKind> glbs = findHighestQualifiers(allSubTypes);
+    if (glbs.size() != 1) {
+      throw new TypeSystemError(
+          "glb(%s, %s) should have size 1: [%s]", qual1, qual2, StringsPlume.join(", ", glbs));
+    }
+    QualifierKind glb = glbs.iterator().next();
+    if (glb.isPoly() && !qual1.isPoly() && !qual2.isPoly()) {
+      throw new TypeSystemError("glb(%s, %s) can't be poly: %s", qual1, qual2, glb);
+    }
+    return glb;
+  }
+
+  /**
+   * Returns the highest qualifiers in the passed set.
+   *
+   * @param qualifierKinds a set of qualifiers
+   * @return the highest qualifiers in the passed set
+   */
+  protected static Set<QualifierKind> findHighestQualifiers(Set<QualifierKind> qualifierKinds) {
+    Set<QualifierKind> highestQualifiers = new TreeSet<>(qualifierKinds);
+    for (QualifierKind a1 : qualifierKinds) {
+      highestQualifiers.removeIf(a2 -> a1 != a2 && a2.isSubtypeOf(a1));
+    }
+    return highestQualifiers;
+  }
+
+  /**
+   * Add Key: qual1, Value: (Key: qual2, Value: value) to {@code map}. If already in map, throw an
+   * exception if value is different.
+   *
+   * @param map mapping to side-effect
+   * @param qual1 the first qualifier kind
+   * @param qual2 the second qualifier kind
+   * @param value the value to add
+   * @param operationName "lub" or "glb"; used only for error messages
+   */
+  private static void addToMapOfMap(
+      Map<QualifierKind, Map<QualifierKind, QualifierKind>> map,
+      QualifierKind qual1,
+      QualifierKind qual2,
+      QualifierKind value,
+      String operationName) {
+    Map<QualifierKind, QualifierKind> qual1Map = map.computeIfAbsent(qual1, k -> new HashMap<>());
+    QualifierKind existingValue = qual1Map.get(qual2);
+    if (existingValue == null) {
+      qual1Map.put(qual2, value);
+    } else {
+      if (existingValue != value) {
+        throw new TypeSystemError(
+            "Multiple %ss for qualifiers %s and %s. Found map %s and %s",
+            operationName, qual1, qual2, value, existingValue);
+      }
+    }
+  }
+
+  /**
+   * The default implementation of {@link QualifierKind}.
+   *
+   * <p>The fields in this class that refer to {@link QualifierKind}s are set when creating the
+   * {@link DefaultQualifierKindHierarchy}. So the getter methods for these fields should not be
+   * called until after the {@code DefaultQualifierKindHierarchy} is created.
+   */
+  @AnnotatedFor("nullness")
+  public @Interned static class DefaultQualifierKind implements QualifierKind {
+
+    /** The canonical name of the annotation class of this. */
+    private final @Interned @CanonicalName String name;
+
+    /** The annotation class for this. */
+    private final Class<? extends Annotation> clazz;
+
+    /** True if the annotation class of this has annotation elements/arguments. */
+    private final boolean hasElements;
+
+    /** The top of the hierarchy to which this belongs. */
+    // Set while creating the QualifierKindHierarchy.
+    protected @MonotonicNonNull DefaultQualifierKind top;
+
+    /** The bottom of the hierarchy to which this belongs. */
+    // Set while creating the QualifierKindHierarchy.
+    protected @MonotonicNonNull DefaultQualifierKind bottom;
+
+    /** The polymorphic qualifier of the hierarchy to which this belongs. */
+    // Set while creating the QualifierKindHierarchy.
+    protected @Nullable DefaultQualifierKind poly;
+
+    /**
+     * All the qualifier kinds that are a strict super qualifier kind of this. Does not include this
+     * qualifier kind itself.
+     */
+    // Set while creating the QualifierKindHierarchy.
+    protected @MonotonicNonNull Set<QualifierKind> strictSuperTypes;
+
+    /**
+     * Creates a {@link DefaultQualifierKind} for the given annotation class.
+     *
+     * @param clazz annotation class for a qualifier
+     */
+    DefaultQualifierKind(Class<? extends Annotation> clazz) {
+      this.clazz = clazz;
+      this.hasElements = clazz.getDeclaredMethods().length != 0;
+      this.name = QualifierKindHierarchy.annotationClassName(clazz).intern();
+      this.poly = null;
+    }
+
+    @Override
+    public @Interned @CanonicalName String getName() {
+      return name;
+    }
+
+    @Override
+    public Class<? extends Annotation> getAnnotationClass() {
+      return clazz;
+    }
+
+    @Override
+    public QualifierKind getTop() {
+      if (top == null) {
+        throw new BugInCF(
+            "DefaultQualifierKindHierarchy#getTop: Top is null for QualifierKind %s."
+                + " Don't call this method during initialization of DefaultQualifierKindHierarchy.",
+            name);
+      }
+      return top;
+    }
+
+    @Override
+    public boolean isTop() {
+      return this.top == this;
+    }
+
+    @Override
+    public QualifierKind getBottom() {
+      if (bottom == null) {
+        throw new BugInCF(
+            "DefaultQualifierKind#getBottom:Bottom is null for QualifierKind %s. Don't call this"
+                + " method during initialization of DefaultQualifierKindHierarchy.",
+            name);
+      }
+      return bottom;
+    }
+
+    @Override
+    public boolean isBottom() {
+      return this.bottom == this;
+    }
+
+    @Override
+    public @Nullable QualifierKind getPolymorphic() {
+      return poly;
+    }
+
+    @Pure
+    @Override
+    public boolean isPoly() {
+      return this.poly == this;
+    }
+
+    @Override
+    public boolean hasElements() {
+      return hasElements;
+    }
+
+    @Override
+    public Set<? extends QualifierKind> getStrictSuperTypes() {
+      if (strictSuperTypes == null) {
+        throw new BugInCF(
+            "DefaultQualifierKind#getStrictSuperTypes: strictSuperTypes was null. Don't call this"
+                + " method during initialization of DefaultQualifierKindHierarchy.");
+      }
+      return strictSuperTypes;
+    }
+
+    @Override
+    public boolean isInSameHierarchyAs(QualifierKind other) {
+      return this.top == other.getTop();
+    }
+
+    @Override
+    public boolean isSubtypeOf(QualifierKind superQualKind) {
+      if (strictSuperTypes == null) {
+        throw new BugInCF(
+            "DefaultQualifierKind#isSubtypeOf: strictSuperTypes was null. Don't call this method"
+                + " during initialization of DefaultQualifierKindHierarchy.");
+      }
+      return this == superQualKind || strictSuperTypes.contains(superQualKind);
+    }
+
+    @Override
+    public String toString() {
+      return clazz.getSimpleName();
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java b/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java
new file mode 100644
index 0000000..f82b8b7
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java
@@ -0,0 +1,117 @@
+package org.checkerframework.framework.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+
+public class ExecUtil {
+
+  public static int execute(final String[] cmd, final OutputStream std, final OutputStream err) {
+
+    final Redirection outRedirect = new Redirection(std, BLOCK_SIZE);
+    final Redirection errRedirect = new Redirection(err, BLOCK_SIZE);
+
+    try {
+      final Process proc = Runtime.getRuntime().exec(cmd);
+      outRedirect.redirect(proc.getInputStream());
+      errRedirect.redirect(proc.getErrorStream());
+
+      final IOException stdExc = outRedirect.join();
+      final IOException errExc = errRedirect.join();
+      final int exitStatus = proc.waitFor();
+
+      if (stdExc != null) {
+        throw stdExc;
+      }
+
+      if (errExc != null) {
+        throw errExc;
+      }
+
+      return exitStatus;
+
+    } catch (InterruptedException e) {
+      throw new RuntimeException("Exception executing command: " + String.join(" ", cmd), e);
+    } catch (IOException e) {
+      throw new RuntimeException("Exception executing command: " + String.join(" ", cmd), e);
+    }
+  }
+
+  public static final int BLOCK_SIZE = 1024;
+
+  public static class Redirection {
+    private final char[] buffer;
+    private final OutputStreamWriter out;
+
+    private Thread thread;
+    private IOException exception;
+
+    public Redirection(final OutputStream out, final int bufferSize) {
+      this.buffer = new char[bufferSize];
+      this.out = new OutputStreamWriter(out);
+    }
+
+    public void redirect(final InputStream inStream) {
+
+      exception = null;
+
+      this.thread =
+          new Thread(
+              () -> {
+                final InputStreamReader in = new InputStreamReader(inStream);
+                try {
+
+                  int read = 0;
+                  while (read > -1) {
+                    read = in.read(buffer);
+                    if (read > 0) {
+                      out.write(buffer, 0, read);
+                    }
+                    out.flush();
+                  }
+
+                } catch (IOException exc) {
+                  exception = exc;
+                } finally {
+                  quietlyClose(in);
+                }
+              });
+      thread.start();
+    }
+
+    public IOException join() throws InterruptedException {
+      thread.join();
+      return exception;
+    }
+  }
+
+  /**
+   * Close the given writer, ignoring exceptions.
+   *
+   * @param writer the writer to close
+   */
+  @SuppressWarnings("EmptyCatch") // the purpose of this method is to ignore exceptions
+  public static void quietlyClose(final Writer writer) {
+    try {
+      writer.close();
+    } catch (IOException ioExc) {
+    }
+  }
+
+  /**
+   * Close the given reader, ignoring exceptions.
+   *
+   * @param reader the reader to close
+   */
+  @SuppressWarnings("EmptyCatch") // the purpose of this method is to ignore exceptions
+  public static void quietlyClose(final Reader reader) {
+    try {
+      reader.close();
+    } catch (IOException ioExc) {
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/FieldInvariants.java b/framework/src/main/java/org/checkerframework/framework/util/FieldInvariants.java
new file mode 100644
index 0000000..f5ee427
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/FieldInvariants.java
@@ -0,0 +1,139 @@
+package org.checkerframework.framework.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.tools.Diagnostic.Kind;
+import org.checkerframework.framework.source.DiagMessage;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Represents field invariants, which the user states by writing {@code @FieldInvariant}. Think of
+ * this as a set of (field, qualifier) pairs.
+ *
+ * <p>A FieldInvariants object may be malformed (inconsistent number of fields and qualifiers). In
+ * this case, the BaseTypeVisitor will issue an error.
+ */
+public class FieldInvariants {
+
+  /**
+   * A list of simple field names. A field may appear more than once in this list. This list has the
+   * same length as {@code qualifiers}.
+   */
+  private final List<String> fields;
+
+  /**
+   * A list of qualifiers that apply to the field at the same index in {@code fields}. In a
+   * well-formed FieldInvariants, has the same length as {@code fields}.
+   */
+  private final List<AnnotationMirror> qualifiers;
+
+  /**
+   * Creates a new FieldInvariants object. The result is well-formed if length of qualifiers is
+   * either 1 or equal to length of {@code fields}.
+   *
+   * @param fields list of fields
+   * @param qualifiers list of qualifiers
+   */
+  public FieldInvariants(List<String> fields, List<AnnotationMirror> qualifiers) {
+    this(null, fields, qualifiers);
+  }
+
+  /**
+   * Creates a new object with all the invariants in {@code other}, plus those specified by {@code
+   * fields} and {@code qualifiers}. The result is well-formed if length of qualifiers is either 1
+   * or equal to length of {@code fields}.
+   *
+   * @param other other invariant object, may be null
+   * @param fields list of fields
+   * @param qualifiers list of qualifiers
+   */
+  public FieldInvariants(
+      FieldInvariants other, List<String> fields, List<AnnotationMirror> qualifiers) {
+    if (qualifiers.size() == 1) {
+      while (fields.size() > qualifiers.size()) {
+        qualifiers.add(qualifiers.get(0));
+      }
+    }
+    if (other != null) {
+      fields.addAll(other.fields);
+      qualifiers.addAll(other.qualifiers);
+    }
+
+    this.fields = Collections.unmodifiableList(fields);
+    this.qualifiers = qualifiers;
+  }
+
+  /** The simple names of the fields that have a qualifier. May contain duplicates. */
+  public List<String> getFields() {
+    return fields;
+  }
+
+  /**
+   * Returns a list of qualifiers for {@code field}. If {@code field} has no qualifiers, returns an
+   * empty list.
+   *
+   * @param field simple field name
+   * @return a list of qualifiers for {@code field}, possibly empty
+   */
+  public List<AnnotationMirror> getQualifiersFor(CharSequence field) {
+    if (!isWellFormed()) {
+      throw new BugInCF("malformed FieldInvariants");
+    }
+    String fieldString = field.toString();
+    int index = fields.indexOf(fieldString);
+    if (index == -1) {
+      return Collections.emptyList();
+    }
+    List<AnnotationMirror> list = new ArrayList<>();
+    for (int i = 0; i < fields.size(); i++) {
+      if (fields.get(i).equals(fieldString)) {
+        list.add(qualifiers.get(i));
+      }
+    }
+    return list;
+  }
+
+  /**
+   * Returns true if there is a qualifier for each field in {@code fields}.
+   *
+   * @return true if there is a qualifier for each field in {@code fields}
+   */
+  public boolean isWellFormed() {
+    return qualifiers.size() == fields.size();
+  }
+
+  /**
+   * Returns null if {@code superInvar} is a super invariant, otherwise returns the error message.
+   *
+   * @param superInvar the value to check for being a super invariant
+   * @param factory the type factory
+   * @return null if {@code superInvar} is a super invariant, otherwise returns the error message
+   */
+  public DiagMessage isSuperInvariant(FieldInvariants superInvar, AnnotatedTypeFactory factory) {
+    QualifierHierarchy qualifierHierarchy = factory.getQualifierHierarchy();
+    if (!this.fields.containsAll(superInvar.fields)) {
+      List<String> missingFields = new ArrayList<>(superInvar.fields);
+      missingFields.removeAll(fields);
+      return new DiagMessage(
+          Kind.ERROR, "field.invariant.not.found.superclass", String.join(", ", missingFields));
+    }
+
+    for (String field : superInvar.fields) {
+      List<AnnotationMirror> superQualifiers = superInvar.getQualifiersFor(field);
+      List<AnnotationMirror> subQualifiers = this.getQualifiersFor(field);
+      for (AnnotationMirror superA : superQualifiers) {
+        AnnotationMirror sub =
+            qualifierHierarchy.findAnnotationInSameHierarchy(subQualifiers, superA);
+        if (sub == null || !qualifierHierarchy.isSubtype(sub, superA)) {
+          return new DiagMessage(
+              Kind.ERROR, "field.invariant.not.subtype.superclass", field, sub, superA);
+        }
+      }
+    }
+    return null;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/GraphQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/util/GraphQualifierHierarchy.java
new file mode 100644
index 0000000..0954fde
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/GraphQualifierHierarchy.java
@@ -0,0 +1,105 @@
+package org.checkerframework.framework.util;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Represents the type qualifier hierarchy of a type system.
+ *
+ * <p>This class is immutable and can be only created through {@link
+ * MultiGraphQualifierHierarchy.MultiGraphFactory}.
+ *
+ * @deprecated See notes in {@link MultiGraphQualifierHierarchy} about how to convert existing
+ *     subclasses to the new classes.
+ */
+@Deprecated // 2020-09-10
+public class GraphQualifierHierarchy extends MultiGraphQualifierHierarchy {
+
+  public GraphQualifierHierarchy(MultiGraphFactory f, AnnotationMirror bottom) {
+    super(f, bottom);
+    // this.bottom = bottom;
+  }
+
+  // private final AnnotationMirror bottom;
+
+  @Override
+  protected void finish(
+      QualifierHierarchy qualHierarchy,
+      Map<AnnotationMirror, Set<AnnotationMirror>> fullMap,
+      Map<AnnotationMirror, AnnotationMirror> polyQualifiers,
+      Set<AnnotationMirror> tops,
+      Set<AnnotationMirror> bottoms,
+      Object... args) {
+    // Careful, when this method is called, a field this.bottom would not be set yet.
+    if (args != null && args[0] != null) {
+      AnnotationMirror thebottom = (AnnotationMirror) args[0];
+      // A special bottom qualifier was provided; go through the existing
+      // bottom qualifiers and tie them all to this bottom qualifier.
+      // Set<AnnotationMirror> bottoms = findBottoms(supertypes);
+      Set<AnnotationMirror> allQuals = AnnotationUtils.createAnnotationSet();
+      allQuals.addAll(fullMap.keySet());
+      allQuals.remove(thebottom);
+      AnnotationUtils.updateMappingToImmutableSet(fullMap, thebottom, allQuals);
+      // thebottom is initially a top qualifier
+      tops.remove(thebottom);
+      // thebottom is now the single bottom qualifier
+      bottoms.clear();
+      bottoms.add(thebottom);
+    }
+  }
+
+  /**
+   * Returns the top qualifiers for this hierarchy. Returns multiple values for a compound checker
+   * (such as the Nullness Checker).
+   *
+   * <p>The top qualifier is inferred from the hierarchy, as being the only one without any super
+   * qualifiers
+   */
+  @Override
+  public Set<? extends AnnotationMirror> getTopAnnotations() {
+    if (tops.size() != 1) {
+      throw new BugInCF(
+          "Expected 1 possible top qualifier, found "
+              + tops.size()
+              + " (does the checker know about all type qualifiers?): "
+              + tops);
+    }
+    return this.tops;
+  }
+
+  @Override
+  public Set<? extends AnnotationMirror> getBottomAnnotations() {
+    // TODO: checks?
+    return this.bottoms;
+  }
+
+  @Override
+  public boolean isSubtype(
+      Collection<? extends AnnotationMirror> rhs, Collection<? extends AnnotationMirror> lhs) {
+    if (lhs.isEmpty() || rhs.isEmpty()) {
+      throw new BugInCF(
+          "GraphQualifierHierarchy: Empty annotations in lhs: " + lhs + " or rhs: " + rhs);
+    }
+    if (lhs.size() > 1) {
+      throw new BugInCF(
+          "GraphQualifierHierarchy: Type with more than one annotation found: " + lhs);
+    }
+    if (rhs.size() > 1) {
+      throw new BugInCF(
+          "GraphQualifierHierarchy: Type with more than one annotation found: " + rhs);
+    }
+    for (AnnotationMirror lhsAnno : lhs) {
+      for (AnnotationMirror rhsAnno : rhs) {
+        if (isSubtype(rhsAnno, lhsAnno)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java b/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java
new file mode 100644
index 0000000..3f9c40f
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java
@@ -0,0 +1,240 @@
+package org.checkerframework.framework.util;
+
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.util.SimpleTreeVisitor;
+import com.sun.source.util.TreePath;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Utilities for determining tree-based heuristics.
+ *
+ * <p>For an example, see {@code org.checkerframework.checker.interning.InterningVisitor}.
+ */
+public class Heuristics {
+
+  /**
+   * Determines whether a tree has a particular set of direct parents, ignoring blocks and
+   * parentheses.
+   *
+   * <p>For example, to test whether an expression (specified by {@code path}) is immediately
+   * contained by an if statement which is immediately contained in a method, one would invoke:
+   *
+   * <pre>
+   * matchParents(path, Kind.IF, Kind.METHOD)
+   * </pre>
+   *
+   * @param path the path to match
+   * @param kinds the tree kinds to match against, in ascending order starting from the desired kind
+   *     of the parent
+   * @return true if the tree path matches the desired kinds, skipping blocks and parentheses, for
+   *     as many kinds as specified
+   */
+  public static boolean matchParents(TreePath path, Tree.Kind... kinds) {
+
+    TreePath parentPath = path.getParentPath();
+    boolean result = true;
+
+    ArrayDeque<Tree.Kind> queue = new ArrayDeque<>(Arrays.asList(kinds));
+
+    Tree tree;
+    while ((tree = parentPath.getLeaf()) != null) {
+
+      if (queue.isEmpty()) {
+        break;
+      }
+
+      if (tree.getKind() == Tree.Kind.BLOCK || tree.getKind() == Tree.Kind.PARENTHESIZED) {
+        parentPath = parentPath.getParentPath();
+        continue;
+      }
+
+      result &= queue.removeFirst() == parentPath.getLeaf().getKind();
+      parentPath = parentPath.getParentPath();
+    }
+
+    return result;
+  }
+
+  /** A base class for tree-matching algorithms. Skips parentheses by default. */
+  public static class Matcher extends SimpleTreeVisitor<Boolean, Void> {
+
+    @Override
+    protected Boolean defaultAction(Tree node, Void p) {
+      return false;
+    }
+
+    @Override
+    public Boolean visitParenthesized(ParenthesizedTree node, Void p) {
+      return visit(node.getExpression(), p);
+    }
+
+    /**
+     * Returns true if the given path matches this Matcher.
+     *
+     * @param path the path to test
+     * @return true if the given path matches this Matcher
+     */
+    public boolean match(TreePath path) {
+      return visit(path.getLeaf(), null);
+    }
+  }
+
+  public static class PreceededBy extends Matcher {
+    private final Matcher matcher;
+
+    public PreceededBy(Matcher matcher) {
+      this.matcher = matcher;
+    }
+
+    @SuppressWarnings("interning:not.interned")
+    @Override
+    public boolean match(TreePath path) {
+      StatementTree stmt = TreePathUtil.enclosingOfClass(path, StatementTree.class);
+      if (stmt == null) {
+        return false;
+      }
+      TreePath p = path;
+      while (p.getLeaf() != stmt) {
+        p = p.getParentPath();
+      }
+      assert p.getLeaf() == stmt;
+
+      while (p != null && p.getLeaf() instanceof StatementTree) {
+        if (p.getParentPath().getLeaf() instanceof BlockTree) {
+          BlockTree block = (BlockTree) p.getParentPath().getLeaf();
+          for (StatementTree st : block.getStatements()) {
+            if (st == p.getLeaf()) {
+              break;
+            }
+
+            if (matcher.match(new TreePath(p, st))) {
+              return true;
+            }
+          }
+        }
+        p = p.getParentPath();
+      }
+
+      return false;
+    }
+  }
+
+  /**
+   * {@code match()} returns true if called on a path, any element of which matches the given
+   * matcher (supplied at object initialization). That matcher is usually one that matches only the
+   * leaf of a path, ignoring all other parts of it.
+   */
+  public static class Within extends Matcher {
+    /** The matcher that {@code Within.match} will try, on every parent of the path it is given. */
+    private final Matcher matcher;
+
+    /**
+     * Create a new Within matcher.
+     *
+     * @param matcher the matcher that {@code Within.match} will try, on every parent of the path it
+     *     is given
+     */
+    public Within(Matcher matcher) {
+      this.matcher = matcher;
+    }
+
+    @Override
+    public boolean match(TreePath path) {
+      TreePath p = path;
+      while (p != null) {
+        if (matcher.match(p)) {
+          return true;
+        }
+        p = p.getParentPath();
+      }
+
+      return false;
+    }
+  }
+
+  /**
+   * {@code match()} returns true if called on a path whose leaf is within the "then" clause of an
+   * if whose conditon matches the matcher (supplied at object initialization). Also returns true if
+   * the leaf is within the "else" of a negated condition that matches the supplied matcher.
+   */
+  public static class WithinTrueBranch extends Matcher {
+    private final Matcher matcher;
+    /** @param conditionMatcher for the condition */
+    public WithinTrueBranch(Matcher conditionMatcher) {
+      this.matcher = conditionMatcher;
+    }
+
+    @SuppressWarnings("interning:not.interned")
+    @Override
+    public boolean match(TreePath path) {
+      TreePath prev = path, p = path.getParentPath();
+      while (p != null) {
+        if (p.getLeaf().getKind() == Tree.Kind.IF) {
+          IfTree ifTree = (IfTree) p.getLeaf();
+          ExpressionTree cond = TreeUtils.withoutParens(ifTree.getCondition());
+          if (ifTree.getThenStatement() == prev.getLeaf() && matcher.match(new TreePath(p, cond))) {
+            return true;
+          }
+          if (cond.getKind() == Tree.Kind.LOGICAL_COMPLEMENT
+              && matcher.match(new TreePath(p, ((UnaryTree) cond).getExpression()))) {
+            return true;
+          }
+        }
+        prev = p;
+        p = p.getParentPath();
+      }
+
+      return false;
+    }
+  }
+
+  /**
+   * {@code match()} returns true if called on a path whose leaf has the given kind (supplied at
+   * object initialization).
+   */
+  public static class OfKind extends Matcher {
+    private final Tree.Kind kind;
+    private final Matcher matcher;
+
+    public OfKind(Tree.Kind kind, Matcher matcher) {
+      this.kind = kind;
+      this.matcher = matcher;
+    }
+
+    @Override
+    public boolean match(TreePath path) {
+      if (path.getLeaf().getKind() == kind) {
+        return matcher.match(path);
+      }
+      return false;
+    }
+  }
+
+  /** {@code match()} returns true if any of the given matchers returns true. */
+  public static class OrMatcher extends Matcher {
+    private final Matcher[] matchers;
+
+    public OrMatcher(Matcher... matchers) {
+      this.matchers = matchers;
+    }
+
+    @Override
+    public boolean match(TreePath path) {
+      for (Matcher matcher : matchers) {
+        if (matcher.match(path)) {
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java b/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java
new file mode 100644
index 0000000..28879ed
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java
@@ -0,0 +1,1182 @@
+package org.checkerframework.framework.util;
+
+import com.github.javaparser.ParseProblemException;
+import com.github.javaparser.ast.ArrayCreationLevel;
+import com.github.javaparser.ast.expr.ArrayAccessExpr;
+import com.github.javaparser.ast.expr.ArrayCreationExpr;
+import com.github.javaparser.ast.expr.BinaryExpr;
+import com.github.javaparser.ast.expr.BooleanLiteralExpr;
+import com.github.javaparser.ast.expr.CharLiteralExpr;
+import com.github.javaparser.ast.expr.ClassExpr;
+import com.github.javaparser.ast.expr.DoubleLiteralExpr;
+import com.github.javaparser.ast.expr.EnclosedExpr;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.FieldAccessExpr;
+import com.github.javaparser.ast.expr.IntegerLiteralExpr;
+import com.github.javaparser.ast.expr.LongLiteralExpr;
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import com.github.javaparser.ast.expr.NameExpr;
+import com.github.javaparser.ast.expr.NullLiteralExpr;
+import com.github.javaparser.ast.expr.StringLiteralExpr;
+import com.github.javaparser.ast.expr.SuperExpr;
+import com.github.javaparser.ast.expr.ThisExpr;
+import com.github.javaparser.ast.expr.UnaryExpr;
+import com.github.javaparser.ast.type.Type;
+import com.github.javaparser.ast.visitor.GenericVisitorWithDefaults;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Symbol.PackageSymbol;
+import com.sun.tools.javac.code.Type.ArrayType;
+import com.sun.tools.javac.code.Type.ClassType;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.processing.ProcessingEnvironment;
+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.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic.Kind;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.expression.ArrayAccess;
+import org.checkerframework.dataflow.expression.ArrayCreation;
+import org.checkerframework.dataflow.expression.BinaryOperation;
+import org.checkerframework.dataflow.expression.ClassName;
+import org.checkerframework.dataflow.expression.FieldAccess;
+import org.checkerframework.dataflow.expression.FormalParameter;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.MethodCall;
+import org.checkerframework.dataflow.expression.ThisReference;
+import org.checkerframework.dataflow.expression.UnaryOperation;
+import org.checkerframework.dataflow.expression.ValueLiteral;
+import org.checkerframework.framework.source.DiagMessage;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesError;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.Resolver;
+import org.checkerframework.javacutil.TypesUtils;
+import org.checkerframework.javacutil.trees.TreeBuilder;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Helper methods to parse a string that represents a restricted Java expression.
+ *
+ * @checker_framework.manual #java-expressions-as-arguments Writing Java expressions as annotation
+ *     arguments
+ * @checker_framework.manual #dependent-types Annotations whose argument is a Java expression
+ *     (dependent type annotations)
+ */
+public class JavaExpressionParseUtil {
+
+  /** Regular expression for a formal parameter use. */
+  protected static final String PARAMETER_REGEX = "#([1-9][0-9]*)";
+
+  /**
+   * Anchored pattern for a formal parameter use; matches a string that is exactly a formal
+   * parameter use.
+   */
+  protected static final Pattern ANCHORED_PARAMETER_PATTERN =
+      Pattern.compile("^" + PARAMETER_REGEX + "$");
+
+  /**
+   * Unanchored pattern for a formal parameter use; can be used to find all formal parameter uses.
+   */
+  protected static final Pattern UNANCHORED_PARAMETER_PATTERN = Pattern.compile(PARAMETER_REGEX);
+
+  /**
+   * Parsable replacement for formal parameter references. It is parsable because it is a Java
+   * identifier.
+   */
+  private static final String PARAMETER_PREFIX = "_param_";
+
+  /** The length of {@link #PARAMETER_PREFIX}. */
+  private static final int PARAMETER_PREFIX_LENGTH = PARAMETER_PREFIX.length();
+
+  /** A pattern that matches the start of a formal parameter in "#2" syntax. */
+  private static Pattern FORMAL_PARAMETER = Pattern.compile("#(\\d)");
+
+  /** The replacement for a formal parameter in "#2" syntax. */
+  private static final String PARAMETER_REPLACEMENT = PARAMETER_PREFIX + "$1";
+
+  /**
+   * Parses a string to a {@link JavaExpression}.
+   *
+   * <p>For most uses, clients should call one of the static methods in {@link
+   * StringToJavaExpression} rather than calling this method directly.
+   *
+   * @param expression the string expression to parse
+   * @param enclosingType type of the class that encloses the JavaExpression
+   * @param thisReference the JavaExpression to which to parse "this", or null if "this" should not
+   *     appear in the expression
+   * @param parameters list of JavaExpressions to which to parse formal parameter references such as
+   *     "#2", or null if formal parameter references should not appear in the expression
+   * @param localVarPath if non-null, the expression is parsed as if it were written at this
+   *     location; affects only parsing of local variables
+   * @param pathToCompilationUnit required to use the underlying Javac API
+   * @param env the processing environment
+   * @return {@code expression} as a {@code JavaExpression}
+   * @throws JavaExpressionParseException if the string cannot be parsed
+   */
+  public static JavaExpression parse(
+      String expression,
+      TypeMirror enclosingType,
+      @Nullable ThisReference thisReference,
+      @Nullable List<FormalParameter> parameters,
+      @Nullable TreePath localVarPath,
+      TreePath pathToCompilationUnit,
+      ProcessingEnvironment env)
+      throws JavaExpressionParseException {
+
+    String expressionWithParameterNames =
+        StringsPlume.replaceAll(expression, FORMAL_PARAMETER, PARAMETER_REPLACEMENT);
+    Expression expr;
+    try {
+      expr = JavaParserUtil.parseExpression(expressionWithParameterNames);
+    } catch (ParseProblemException e) {
+      String extra = ".";
+      if (!e.getProblems().isEmpty()) {
+        String message = e.getProblems().get(0).getMessage();
+        int newLine = message.indexOf(System.lineSeparator());
+        if (newLine != -1) {
+          message = message.substring(0, newLine);
+        }
+        extra = ". Error message: " + message;
+      }
+      throw constructJavaExpressionParseError(expression, "the expression did not parse" + extra);
+    }
+
+    JavaExpression result =
+        ExpressionToJavaExpressionVisitor.convert(
+            expr,
+            enclosingType,
+            thisReference,
+            parameters,
+            localVarPath,
+            pathToCompilationUnit,
+            env);
+
+    if (result instanceof ClassName && !expression.endsWith(".class")) {
+      throw constructJavaExpressionParseError(
+          expression,
+          String.format(
+              "a class name cannot terminate a Java expression string, where result=%s [%s]",
+              result, result.getClass()));
+    }
+    return result;
+  }
+
+  /**
+   * A visitor class that converts a JavaParser {@link Expression} to a {@link JavaExpression}. This
+   * class does not viewpoint-adapt the expression.
+   */
+  private static class ExpressionToJavaExpressionVisitor
+      extends GenericVisitorWithDefaults<JavaExpression, Void> {
+
+    /**
+     * The underlying javac API used to convert from Strings to Elements requires a tree path even
+     * when the information could be deduced from elements alone. So use the path to the current
+     * CompilationUnit.
+     */
+    private final TreePath pathToCompilationUnit;
+
+    /** If non-null, the expression is parsed as if it were written at this location. */
+    private final @Nullable TreePath localVarPath;
+
+    /** The processing environment. */
+    private final ProcessingEnvironment env;
+
+    /** The resolver. Computed from the environment, but lazily initialized. */
+    private @MonotonicNonNull Resolver resolver = null;
+
+    /** The type utilities. */
+    private final Types types;
+
+    /** The java.lang.String type. */
+    private final TypeMirror stringTypeMirror;
+
+    /** The enclosing type. Used to look up unqualified method, field, and class names. */
+    private final TypeMirror enclosingType;
+
+    /**
+     * The expression to use for "this". If {@code null}, a parse error will be thrown if "this"
+     * appears in the expression.
+     */
+    private final @Nullable ThisReference thisReference;
+    /**
+     * For each formal parameter, the expression to which to parse it. For example, the second
+     * (index 1) element of the list is what "#2" parses to. If this field is {@code null}, a parse
+     * error will be thrown if "#2" appears in the expression.
+     */
+    private final @Nullable List<FormalParameter> parameters;
+
+    /**
+     * Create a new ExpressionToJavaExpressionVisitor.
+     *
+     * @param enclosingType type of the class that encloses the JavaExpression
+     * @param thisReference JavaExpression to which to parse "this", or null if "this" should not
+     *     appear in the expression
+     * @param parameters list of JavaExpressions to which to parse a formal parameter reference such
+     *     as "#2", or null if parameters should not appear in the expression
+     * @param localVarPath if non-null, the expression is parsed as if it were written at this
+     *     location
+     * @param pathToCompilationUnit required to use the underlying Javac API
+     * @param env the processing environment
+     */
+    private ExpressionToJavaExpressionVisitor(
+        TypeMirror enclosingType,
+        @Nullable ThisReference thisReference,
+        @Nullable List<FormalParameter> parameters,
+        @Nullable TreePath localVarPath,
+        TreePath pathToCompilationUnit,
+        ProcessingEnvironment env) {
+      this.pathToCompilationUnit = pathToCompilationUnit;
+      this.localVarPath = localVarPath;
+      this.env = env;
+      this.types = env.getTypeUtils();
+      this.stringTypeMirror = env.getElementUtils().getTypeElement("java.lang.String").asType();
+      this.enclosingType = enclosingType;
+      this.thisReference = thisReference;
+      this.parameters = parameters;
+    }
+
+    /**
+     * Converts a JavaParser {@link Expression} to a {@link JavaExpression}.
+     *
+     * @param expr the JavaParser {@link Expression} to convert
+     * @param enclosingType type of the class that encloses the JavaExpression
+     * @param thisReference JavaExpression to which to parse "this", or null if "this" should not
+     *     appear in the expression
+     * @param parameters list of JavaExpressions to which to parse parameters, or null if parameters
+     *     should not appear in the expression
+     * @param localVarPath if non-null, the expression is parsed as if it were written at this
+     *     location
+     * @param pathToCompilationUnit required to use the underlying Javac API
+     * @param env the processing environment
+     * @return {@code expr} as a {@code JavaExpression}
+     * @throws JavaExpressionParseException if {@code expr} cannot be converted to a {@code
+     *     JavaExpression}
+     */
+    public static JavaExpression convert(
+        Expression expr,
+        TypeMirror enclosingType,
+        @Nullable ThisReference thisReference,
+        @Nullable List<FormalParameter> parameters,
+        @Nullable TreePath localVarPath,
+        TreePath pathToCompilationUnit,
+        ProcessingEnvironment env)
+        throws JavaExpressionParseException {
+      try {
+        return expr.accept(
+            new ExpressionToJavaExpressionVisitor(
+                enclosingType, thisReference, parameters, localVarPath, pathToCompilationUnit, env),
+            null);
+      } catch (ParseRuntimeException e) {
+        // Convert unchecked to checked exception. Visitor methods can't throw checked
+        // exceptions. They override the methods in the superclass, and a checked exception
+        // would change the method signature.
+        throw e.getCheckedException();
+      }
+    }
+
+    /**
+     * Initializes the {@code resolver} field if necessary. Does nothing on invocations after the
+     * first.
+     */
+    private void setResolverField() {
+      if (resolver == null) {
+        resolver = new Resolver(env);
+      }
+    }
+
+    /** If the expression is not supported, throw a {@link ParseRuntimeException} by default. */
+    @Override
+    public JavaExpression defaultAction(com.github.javaparser.ast.Node n, Void aVoid) {
+      throw new ParseRuntimeException(
+          constructJavaExpressionParseError(
+              n.toString(), n.getClass() + " is not a supported expression"));
+    }
+
+    @Override
+    public JavaExpression visit(NullLiteralExpr expr, Void aVoid) {
+      return new ValueLiteral(types.getNullType(), (Object) null);
+    }
+
+    @Override
+    public JavaExpression visit(IntegerLiteralExpr expr, Void aVoid) {
+      return new ValueLiteral(types.getPrimitiveType(TypeKind.INT), expr.asNumber());
+    }
+
+    @Override
+    public JavaExpression visit(LongLiteralExpr expr, Void aVoid) {
+      return new ValueLiteral(types.getPrimitiveType(TypeKind.LONG), expr.asNumber());
+    }
+
+    @Override
+    public JavaExpression visit(CharLiteralExpr expr, Void aVoid) {
+      return new ValueLiteral(types.getPrimitiveType(TypeKind.CHAR), expr.asChar());
+    }
+
+    @Override
+    public JavaExpression visit(DoubleLiteralExpr expr, Void aVoid) {
+      return new ValueLiteral(types.getPrimitiveType(TypeKind.DOUBLE), expr.asDouble());
+    }
+
+    @Override
+    public JavaExpression visit(StringLiteralExpr expr, Void aVoid) {
+      return new ValueLiteral(stringTypeMirror, expr.asString());
+    }
+
+    @Override
+    public JavaExpression visit(BooleanLiteralExpr expr, Void aVoid) {
+      return new ValueLiteral(types.getPrimitiveType(TypeKind.BOOLEAN), expr.getValue());
+    }
+
+    @Override
+    public JavaExpression visit(ThisExpr n, Void aVoid) {
+      if (thisReference == null) {
+        throw new ParseRuntimeException(
+            constructJavaExpressionParseError("this", "\"this\" isn't allowed here"));
+      }
+      return thisReference;
+    }
+
+    @Override
+    public JavaExpression visit(SuperExpr n, Void aVoid) {
+      // super literal
+      TypeMirror superclass = TypesUtils.getSuperclass(enclosingType, types);
+      if (superclass == null) {
+        throw new ParseRuntimeException(
+            constructJavaExpressionParseError("super", enclosingType + " has no superclass"));
+      }
+      return new ThisReference(superclass);
+    }
+
+    // expr is an expression in parentheses.
+    @Override
+    public JavaExpression visit(EnclosedExpr expr, Void aVoid) {
+      return expr.getInner().accept(this, null);
+    }
+
+    @Override
+    public JavaExpression visit(ArrayAccessExpr expr, Void aVoid) {
+      JavaExpression array = expr.getName().accept(this, null);
+      TypeMirror arrayType = array.getType();
+      if (arrayType.getKind() != TypeKind.ARRAY) {
+        throw new ParseRuntimeException(
+            constructJavaExpressionParseError(
+                expr.toString(),
+                String.format(
+                    "expected an array, found %s of type %s [%s]",
+                    array, arrayType, arrayType.getKind())));
+      }
+      TypeMirror componentType = ((ArrayType) arrayType).getComponentType();
+
+      JavaExpression index = expr.getIndex().accept(this, null);
+
+      return new ArrayAccess(componentType, array, index);
+    }
+
+    // expr is an identifier with no dots in its name.
+    @Override
+    public JavaExpression visit(NameExpr expr, Void aVoid) {
+      String s = expr.getNameAsString();
+      setResolverField();
+
+      // Formal parameter, using "#2" syntax.
+      JavaExpression parameter = getParameterJavaExpression(s);
+      if (parameter != null) {
+        // A parameter is a local variable, but it can be referenced outside of local scope
+        // (at the method scope) using the special #NN syntax.
+        return parameter;
+      }
+
+      // Local variable or parameter.
+      if (localVarPath != null) {
+        // Attempt to match a local variable within the scope of the
+        // given path before attempting to match a field.
+        VariableElement varElem = resolver.findLocalVariableOrParameter(s, localVarPath);
+        if (varElem != null) {
+          return new LocalVariable(varElem);
+        }
+      }
+
+      // Field access
+      JavaExpression fieldAccessReceiver;
+      if (thisReference != null) {
+        fieldAccessReceiver = thisReference;
+      } else {
+        fieldAccessReceiver = new ClassName(enclosingType);
+      }
+      FieldAccess fieldAccess = getIdentifierAsFieldAccess(fieldAccessReceiver, s);
+      if (fieldAccess != null) {
+        return fieldAccess;
+      }
+
+      if (localVarPath != null) {
+        Element classElem = resolver.findClass(s, localVarPath);
+        TypeMirror classType = ElementUtils.getType(classElem);
+        if (classType != null) {
+          return new ClassName(classType);
+        }
+      }
+
+      ClassName classType = getIdentifierAsUnqualifiedClassName(s);
+      if (classType != null) {
+        return classType;
+      }
+
+      // Err if a formal parameter name is used, instead of the "#2" syntax.
+      if (parameters != null) {
+        for (int i = 0; i < parameters.size(); i++) {
+          Element varElt = parameters.get(i).getElement();
+          if (varElt.getSimpleName().contentEquals(s)) {
+            throw new ParseRuntimeException(
+                constructJavaExpressionParseError(
+                    s, String.format(DependentTypesError.FORMAL_PARAM_NAME_STRING, i + 1, s)));
+          }
+        }
+      }
+
+      throw new ParseRuntimeException(constructJavaExpressionParseError(s, "identifier not found"));
+    }
+
+    /**
+     * If {@code s} a parameter expressed using the {@code #NN} syntax, then returns a
+     * JavaExpression for the given parameter; that is, returns an element of {@code parameters}.
+     * Otherwise, returns {@code null}.
+     *
+     * @param s a String that starts with PARAMETER_PREFIX
+     * @return the JavaExpression for the given parameter or {@code null} if {@code s} is not a
+     *     parameter
+     */
+    private @Nullable JavaExpression getParameterJavaExpression(String s) {
+      if (!s.startsWith(PARAMETER_PREFIX)) {
+        return null;
+      }
+      if (parameters == null) {
+        throw new ParseRuntimeException(
+            constructJavaExpressionParseError(s, "no parameters found"));
+      }
+      int idx = Integer.parseInt(s.substring(PARAMETER_PREFIX_LENGTH));
+
+      if (idx == 0) {
+        throw new ParseRuntimeException(
+            constructJavaExpressionParseError(
+                "#0", "Use \"this\" for the receiver or \"#1\" for the first formal parameter"));
+      }
+      if (idx > parameters.size()) {
+        throw new ParseRuntimeException(
+            new JavaExpressionParseException(
+                "flowexpr.parse.index.too.big", Integer.toString(idx)));
+      }
+      return parameters.get(idx - 1);
+    }
+
+    /**
+     * If {@code identifier} is the simple class name of any inner class of {@code type}, return the
+     * {@link ClassName} for the inner class. If not, return null.
+     *
+     * @param type type to search for {@code identifier}
+     * @param identifier possible simple class name
+     * @return the {@code ClassName} for {@code identifier}, or null if it is not a simple class
+     *     name
+     */
+    protected @Nullable ClassName getIdentifierAsInnerClassName(
+        TypeMirror type, String identifier) {
+      if (type.getKind() != TypeKind.DECLARED) {
+        return null;
+      }
+
+      Element outerClass = ((DeclaredType) type).asElement();
+      for (Element memberElement : outerClass.getEnclosedElements()) {
+        if (!(memberElement.getKind().isClass() || memberElement.getKind().isInterface())) {
+          continue;
+        }
+        if (memberElement.getSimpleName().contentEquals(identifier)) {
+          return new ClassName(ElementUtils.getType(memberElement));
+        }
+      }
+      return null;
+    }
+
+    /**
+     * If {@code identifier} is a class name with that can be referenced using only its simple name
+     * within {@code enclosingType}, return the {@link ClassName} for the class. If not, return
+     * null.
+     *
+     * <p>{@code identifier} may be
+     *
+     * <ol>
+     *   <li>the simple name of {@code type}.
+     *   <li>the simple name of a class declared in {@code type} or in an enclosing type of {@code
+     *       type}.
+     *   <li>the simple name of a class in the java.lang package.
+     *   <li>the simple name of a class in the unnamed package.
+     * </ol>
+     *
+     * @param identifier possible class name
+     * @return the {@code ClassName} for {@code identifier}, or null if it is not a class name
+     */
+    protected @Nullable ClassName getIdentifierAsUnqualifiedClassName(String identifier) {
+      // Is identifier an inner class of enclosingType or of any enclosing class of enclosingType?
+      TypeMirror searchType = enclosingType;
+      while (searchType.getKind() == TypeKind.DECLARED) {
+        DeclaredType searchDeclaredType = (DeclaredType) searchType;
+        if (searchDeclaredType.asElement().getSimpleName().contentEquals(identifier)) {
+          return new ClassName(searchType);
+        }
+        ClassName className = getIdentifierAsInnerClassName(searchType, identifier);
+        if (className != null) {
+          return className;
+        }
+        searchType = getTypeOfEnclosingClass(searchDeclaredType);
+      }
+
+      if (enclosingType.getKind() == TypeKind.DECLARED) {
+        // Is identifier in the same package as this?
+        PackageSymbol packageSymbol =
+            (PackageSymbol)
+                ElementUtils.enclosingPackage(((DeclaredType) enclosingType).asElement());
+        ClassSymbol classSymbol =
+            resolver.findClassInPackage(identifier, packageSymbol, pathToCompilationUnit);
+        if (classSymbol != null) {
+          return new ClassName(classSymbol.asType());
+        }
+      }
+      // Is identifier a simple name for a class in java.lang?
+      Symbol.PackageSymbol packageSymbol = resolver.findPackage("java.lang", pathToCompilationUnit);
+      if (packageSymbol == null) {
+        throw new BugInCF("Can't find java.lang package.");
+      }
+      ClassSymbol classSymbol =
+          resolver.findClassInPackage(identifier, packageSymbol, pathToCompilationUnit);
+      if (classSymbol != null) {
+        return new ClassName(classSymbol.asType());
+      }
+
+      // Is identifier a class in the unnamed package?
+      Element classElem = resolver.findClass(identifier, pathToCompilationUnit);
+      if (classElem != null) {
+        PackageElement pkg = ElementUtils.enclosingPackage(classElem);
+        if (pkg != null && pkg.isUnnamed()) {
+          TypeMirror classType = ElementUtils.getType(classElem);
+          if (classType != null) {
+            return new ClassName(classType);
+          }
+        }
+      }
+
+      return null;
+    }
+
+    /**
+     * Return the {@link FieldAccess} expression for the field with name {@code identifier} accessed
+     * via {@code receiverExpr}. If no such field exists, then {@code null} is returned.
+     *
+     * @param receiverExpr the receiver of the field access; the expression used to access the field
+     * @param identifier possibly a field name
+     * @return a field access, or null if {@code identifier} is not a field that can be accessed via
+     *     {@code receiverExpr}
+     */
+    protected @Nullable FieldAccess getIdentifierAsFieldAccess(
+        JavaExpression receiverExpr, String identifier) {
+      // Find the field element.
+      TypeMirror enclosingTypeOfField = receiverExpr.getType();
+      VariableElement fieldElem;
+      if (identifier.equals("length") && enclosingTypeOfField.getKind() == TypeKind.ARRAY) {
+        fieldElem = resolver.findField(identifier, enclosingTypeOfField, pathToCompilationUnit);
+        if (fieldElem == null) {
+          throw new BugInCF("length field not found for type %s", enclosingTypeOfField);
+        }
+      } else {
+        fieldElem = null;
+        // Search for field in each enclosing class.
+        while (enclosingTypeOfField.getKind() == TypeKind.DECLARED) {
+          fieldElem = resolver.findField(identifier, enclosingTypeOfField, pathToCompilationUnit);
+          if (fieldElem != null) {
+            break;
+          }
+          enclosingTypeOfField = getTypeOfEnclosingClass((DeclaredType) enclosingTypeOfField);
+        }
+        if (fieldElem == null) {
+          // field not found.
+          return null;
+        }
+      }
+
+      // Construct a FieldAccess expression.
+      if (ElementUtils.isStatic(fieldElem)) {
+        Element classElem = fieldElem.getEnclosingElement();
+        JavaExpression staticClassReceiver = new ClassName(ElementUtils.getType(classElem));
+        return new FieldAccess(staticClassReceiver, fieldElem);
+      }
+
+      // fieldElem is an instance field.
+
+      if (receiverExpr instanceof ClassName) {
+        throw new ParseRuntimeException(
+            constructJavaExpressionParseError(
+                fieldElem.getSimpleName().toString(),
+                "a non-static field cannot have a class name as a receiver."));
+      }
+
+      // There are two possibilities, captured by local variable fieldDeclaredInReceiverType:
+      //  * true: it's an instance field declared in the type (or supertype) of receiverExpr.
+      //  * false: it's an instance field declared in an enclosing type of receiverExpr.
+
+      @SuppressWarnings("interning:not.interned") // Checking for exact object
+      boolean fieldDeclaredInReceiverType = enclosingTypeOfField == receiverExpr.getType();
+      if (fieldDeclaredInReceiverType) {
+        TypeMirror fieldType = ElementUtils.getType(fieldElem);
+        return new FieldAccess(receiverExpr, fieldType, fieldElem);
+      } else {
+        if (!(receiverExpr instanceof ThisReference)) {
+          String msg =
+              String.format(
+                  "%s is declared in an outer type of the type of the receiver expression, %s.",
+                  identifier, receiverExpr);
+          throw new ParseRuntimeException(constructJavaExpressionParseError(identifier, msg));
+        }
+        TypeElement receiverTypeElement = TypesUtils.getTypeElement(receiverExpr.getType());
+        if (receiverTypeElement == null || ElementUtils.isStatic(receiverTypeElement)) {
+          String msg =
+              String.format("%s is a non-static field declared in an outer type this.", identifier);
+          throw new ParseRuntimeException(constructJavaExpressionParseError(identifier, msg));
+        }
+        JavaExpression locationOfField = new ThisReference(enclosingTypeOfField);
+        return new FieldAccess(locationOfField, fieldElem);
+      }
+    }
+
+    @Override
+    public JavaExpression visit(MethodCallExpr expr, Void aVoid) {
+      setResolverField();
+
+      JavaExpression receiverExpr;
+      if (expr.getScope().isPresent()) {
+        receiverExpr = expr.getScope().get().accept(this, null);
+        expr = expr.removeScope();
+      } else if (thisReference != null) {
+        receiverExpr = thisReference;
+      } else {
+        receiverExpr = new ClassName(enclosingType);
+      }
+
+      String methodName = expr.getNameAsString();
+
+      // parse argument list
+      List<JavaExpression> arguments =
+          CollectionsPlume.mapList(argument -> argument.accept(this, null), expr.getArguments());
+
+      ExecutableElement methodElement;
+      try {
+        methodElement =
+            getMethodElement(
+                methodName, receiverExpr.getType(), pathToCompilationUnit, arguments, resolver);
+      } catch (JavaExpressionParseException e) {
+        throw new ParseRuntimeException(e);
+      }
+
+      // Box any arguments that require it.
+      for (int i = 0; i < arguments.size(); i++) {
+        VariableElement parameter = methodElement.getParameters().get(i);
+        TypeMirror parameterType = parameter.asType();
+        JavaExpression argument = arguments.get(i);
+        TypeMirror argumentType = argument.getType();
+        // is boxing necessary?
+        if (TypesUtils.isBoxedPrimitive(parameterType) && TypesUtils.isPrimitive(argumentType)) {
+          // boxing is necessary
+          MethodSymbol valueOfMethod = TreeBuilder.getValueOfMethod(env, parameterType);
+          JavaExpression boxedParam =
+              new MethodCall(
+                  parameterType,
+                  valueOfMethod,
+                  new ClassName(parameterType),
+                  Collections.singletonList(argument));
+          arguments.set(i, boxedParam);
+        }
+      }
+
+      // Build the MethodCall expression object.
+      if (ElementUtils.isStatic(methodElement)) {
+        Element classElem = methodElement.getEnclosingElement();
+        JavaExpression staticClassReceiver = new ClassName(ElementUtils.getType(classElem));
+        return new MethodCall(
+            ElementUtils.getType(methodElement), methodElement, staticClassReceiver, arguments);
+      } else {
+        if (receiverExpr instanceof ClassName) {
+          throw new ParseRuntimeException(
+              constructJavaExpressionParseError(
+                  expr.toString(),
+                  "a non-static method call cannot have a class name as a receiver"));
+        }
+        TypeMirror methodType =
+            TypesUtils.substituteMethodReturnType(methodElement, receiverExpr.getType(), env);
+        return new MethodCall(methodType, methodElement, receiverExpr, arguments);
+      }
+    }
+
+    /**
+     * Returns the ExecutableElement for a method, or throws an exception.
+     *
+     * <p>(This method takes into account autoboxing.)
+     *
+     * @param methodName the method name
+     * @param receiverType the receiver type
+     * @param pathToCompilationUnit the path to the compilation unit
+     * @param arguments the arguments
+     * @param resolver the resolver
+     * @return the ExecutableElement for a method, or throws an exception
+     * @throws JavaExpressionParseException if the string cannot be parsed as a method name
+     */
+    private ExecutableElement getMethodElement(
+        String methodName,
+        TypeMirror receiverType,
+        TreePath pathToCompilationUnit,
+        List<JavaExpression> arguments,
+        Resolver resolver)
+        throws JavaExpressionParseException {
+
+      List<TypeMirror> argumentTypes = CollectionsPlume.mapList(JavaExpression::getType, arguments);
+
+      if (receiverType.getKind() == TypeKind.ARRAY) {
+        ExecutableElement element =
+            resolver.findMethod(methodName, receiverType, pathToCompilationUnit, argumentTypes);
+        if (element == null) {
+          throw constructJavaExpressionParseError(methodName, "no such method");
+        }
+        return element;
+      }
+
+      // Search for method in each enclosing class.
+      while (receiverType.getKind() == TypeKind.DECLARED) {
+        ExecutableElement element =
+            resolver.findMethod(methodName, receiverType, pathToCompilationUnit, argumentTypes);
+        if (element != null) {
+          return element;
+        }
+        receiverType = getTypeOfEnclosingClass((DeclaredType) receiverType);
+      }
+
+      // Method not found.
+      throw constructJavaExpressionParseError(methodName, "no such method");
+    }
+
+    // expr is a field access, a fully qualified class name, or a class name qualified with
+    // another class name (e.g. {@code OuterClass.InnerClass})
+    @Override
+    public JavaExpression visit(FieldAccessExpr expr, Void aVoid) {
+      setResolverField();
+
+      Expression scope = expr.getScope();
+      String name = expr.getNameAsString();
+
+      // Check for fully qualified class name.
+      Symbol.PackageSymbol packageSymbol =
+          resolver.findPackage(scope.toString(), pathToCompilationUnit);
+      if (packageSymbol != null) {
+        ClassSymbol classSymbol =
+            resolver.findClassInPackage(name, packageSymbol, pathToCompilationUnit);
+        if (classSymbol != null) {
+          return new ClassName(classSymbol.asType());
+        }
+        throw new ParseRuntimeException(
+            constructJavaExpressionParseError(
+                expr.toString(),
+                "could not find class "
+                    + expr.getNameAsString()
+                    + " in package "
+                    + scope.toString()));
+      }
+
+      JavaExpression receiver = scope.accept(this, null);
+
+      // Check for field access expression.
+      FieldAccess fieldAccess = getIdentifierAsFieldAccess(receiver, name);
+      if (fieldAccess != null) {
+        return fieldAccess;
+      }
+
+      // Check for inner class.
+      ClassName classType = getIdentifierAsInnerClassName(receiver.getType(), name);
+      if (classType != null) {
+        return classType;
+      }
+
+      throw new ParseRuntimeException(
+          constructJavaExpressionParseError(
+              name, String.format("field or class %s not found in %s", name, receiver)));
+    }
+
+    // expr is a Class literal
+    @Override
+    public JavaExpression visit(ClassExpr expr, Void aVoid) {
+      TypeMirror result = convertTypeToTypeMirror(expr.getType());
+      if (result == null) {
+        throw new ParseRuntimeException(
+            constructJavaExpressionParseError(
+                expr.toString(), "it is an unparsable class literal"));
+      }
+      return new ClassName(result);
+    }
+
+    @Override
+    public JavaExpression visit(ArrayCreationExpr expr, Void aVoid) {
+      List<JavaExpression> dimensions =
+          CollectionsPlume.mapList(
+              (ArrayCreationLevel dimension) ->
+                  dimension.getDimension().isPresent()
+                      ? dimension.getDimension().get().accept(this, aVoid)
+                      : null,
+              expr.getLevels());
+
+      List<JavaExpression> initializers;
+      if (expr.getInitializer().isPresent()) {
+        initializers =
+            CollectionsPlume.mapList(
+                (Expression initializer) -> initializer.accept(this, null),
+                expr.getInitializer().get().getValues());
+      } else {
+        initializers = Collections.emptyList();
+      }
+      TypeMirror arrayType = convertTypeToTypeMirror(expr.getElementType());
+      if (arrayType == null) {
+        throw new ParseRuntimeException(
+            constructJavaExpressionParseError(
+                expr.getElementType().asString(), "type not parsable"));
+      }
+      for (int i = 0; i < dimensions.size(); i++) {
+        arrayType = TypesUtils.createArrayType(arrayType, env.getTypeUtils());
+      }
+      return new ArrayCreation(arrayType, dimensions, initializers);
+    }
+
+    @Override
+    public JavaExpression visit(UnaryExpr expr, Void aVoid) {
+      Tree.Kind treeKind = javaParserUnaryOperatorToTreeKind(expr.getOperator());
+      JavaExpression operand = expr.getExpression().accept(this, null);
+      // This eliminates + and performs constant-folding for -; it could also do so for other
+      // operations.
+      switch (treeKind) {
+        case UNARY_PLUS:
+          return operand;
+        case UNARY_MINUS:
+          if (operand instanceof ValueLiteral) {
+            return ((ValueLiteral) operand).negate();
+          }
+          break;
+        default:
+          // Not optimization for this operand
+          break;
+      }
+      return new UnaryOperation(operand.getType(), treeKind, operand);
+    }
+
+    /**
+     * Convert a JavaParser unary operator to a TreeKind.
+     *
+     * @param op a JavaParser unary operator
+     * @return a TreeKind for the unary operator
+     */
+    private Tree.Kind javaParserUnaryOperatorToTreeKind(UnaryExpr.Operator op) {
+      switch (op) {
+        case BITWISE_COMPLEMENT:
+          return Tree.Kind.BITWISE_COMPLEMENT;
+        case LOGICAL_COMPLEMENT:
+          return Tree.Kind.LOGICAL_COMPLEMENT;
+        case MINUS:
+          return Tree.Kind.UNARY_MINUS;
+        case PLUS:
+          return Tree.Kind.UNARY_PLUS;
+        case POSTFIX_DECREMENT:
+          return Tree.Kind.POSTFIX_DECREMENT;
+        case POSTFIX_INCREMENT:
+          return Tree.Kind.POSTFIX_INCREMENT;
+        case PREFIX_DECREMENT:
+          return Tree.Kind.PREFIX_DECREMENT;
+        case PREFIX_INCREMENT:
+          return Tree.Kind.PREFIX_INCREMENT;
+        default:
+          throw new BugInCF("unhandled " + op);
+      }
+    }
+
+    @Override
+    public JavaExpression visit(BinaryExpr expr, Void aVoid) {
+      JavaExpression leftJe = expr.getLeft().accept(this, null);
+      JavaExpression rightJe = expr.getRight().accept(this, null);
+      TypeMirror leftType = leftJe.getType();
+      TypeMirror rightType = rightJe.getType();
+      TypeMirror type;
+      // isSubtype() first does the cheaper test isSameType(), so no need to do it here.
+      if (types.isSubtype(leftType, rightType)) {
+        type = rightType;
+      } else if (types.isSubtype(rightType, leftType)) {
+        type = leftType;
+      } else if (expr.getOperator() == BinaryExpr.Operator.PLUS
+          && (TypesUtils.isString(leftType) || TypesUtils.isString(rightType))) {
+        type = stringTypeMirror;
+      } else {
+        throw new ParseRuntimeException(
+            constructJavaExpressionParseError(
+                expr.toString(),
+                String.format("inconsistent types %s %s for %s", leftType, rightType, expr)));
+      }
+      return new BinaryOperation(
+          type, javaParserBinaryOperatorToTreeKind(expr.getOperator()), leftJe, rightJe);
+    }
+
+    /**
+     * Convert a JavaParser binary operator to a TreeKind.
+     *
+     * @param op a JavaParser binary operator
+     * @return a TreeKind for the binary operator
+     */
+    private Tree.Kind javaParserBinaryOperatorToTreeKind(BinaryExpr.Operator op) {
+      switch (op) {
+        case AND:
+          return Tree.Kind.CONDITIONAL_AND;
+        case BINARY_AND:
+          return Tree.Kind.AND;
+        case BINARY_OR:
+          return Tree.Kind.OR;
+        case DIVIDE:
+          return Tree.Kind.DIVIDE;
+        case EQUALS:
+          return Tree.Kind.EQUAL_TO;
+        case GREATER:
+          return Tree.Kind.GREATER_THAN;
+        case GREATER_EQUALS:
+          return Tree.Kind.GREATER_THAN_EQUAL;
+        case LEFT_SHIFT:
+          return Tree.Kind.LEFT_SHIFT;
+        case LESS:
+          return Tree.Kind.LESS_THAN;
+        case LESS_EQUALS:
+          return Tree.Kind.LESS_THAN_EQUAL;
+        case MINUS:
+          return Tree.Kind.MINUS;
+        case MULTIPLY:
+          return Tree.Kind.MULTIPLY;
+        case NOT_EQUALS:
+          return Tree.Kind.NOT_EQUAL_TO;
+        case OR:
+          return Tree.Kind.CONDITIONAL_OR;
+        case PLUS:
+          return Tree.Kind.PLUS;
+        case REMAINDER:
+          return Tree.Kind.REMAINDER;
+        case SIGNED_RIGHT_SHIFT:
+          return Tree.Kind.RIGHT_SHIFT;
+        case UNSIGNED_RIGHT_SHIFT:
+          return Tree.Kind.UNSIGNED_RIGHT_SHIFT;
+        case XOR:
+          return Tree.Kind.XOR;
+        default:
+          throw new Error("unhandled " + op);
+      }
+    }
+
+    /**
+     * Converts the JavaParser type to a TypeMirror. Returns null if {@code type} is not handled;
+     * this method does not handle type variables, union types, or intersection types.
+     *
+     * @param type a JavaParser type
+     * @return a TypeMirror corresponding to {@code type}, or null if {@code type} isn't handled
+     */
+    private @Nullable TypeMirror convertTypeToTypeMirror(Type type) {
+      if (type.isClassOrInterfaceType()) {
+        try {
+          return JavaParserUtil.parseExpression(type.asString()).accept(this, null).getType();
+        } catch (ParseProblemException e) {
+          return null;
+        }
+      } else if (type.isPrimitiveType()) {
+        switch (type.asPrimitiveType().getType()) {
+          case BOOLEAN:
+            return types.getPrimitiveType(TypeKind.BOOLEAN);
+          case BYTE:
+            return types.getPrimitiveType(TypeKind.BYTE);
+          case SHORT:
+            return types.getPrimitiveType(TypeKind.SHORT);
+          case INT:
+            return types.getPrimitiveType(TypeKind.INT);
+          case CHAR:
+            return types.getPrimitiveType(TypeKind.CHAR);
+          case FLOAT:
+            return types.getPrimitiveType(TypeKind.FLOAT);
+          case LONG:
+            return types.getPrimitiveType(TypeKind.LONG);
+          case DOUBLE:
+            return types.getPrimitiveType(TypeKind.DOUBLE);
+        }
+      } else if (type.isVoidType()) {
+        return types.getNoType(TypeKind.VOID);
+      } else if (type.isArrayType()) {
+        TypeMirror componentType = convertTypeToTypeMirror(type.asArrayType().getComponentType());
+        if (componentType == null) {
+          return null;
+        }
+        return types.getArrayType(componentType);
+      }
+      return null;
+    }
+  }
+
+  /**
+   * If {@code s} is exactly a formal parameter, return its 1-based index. Returns -1 otherwise.
+   *
+   * @param s a Java expression
+   * @return the 1-based index of the formal parameter that {@code s} represents, or -1
+   */
+  public static int parameterIndex(String s) {
+    Matcher matcher = ANCHORED_PARAMETER_PATTERN.matcher(s);
+    if (matcher.find()) {
+      return Integer.parseInt(matcher.group(1));
+    }
+    return -1;
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Contexts
+  ///
+
+  /**
+   * Returns the type of the innermost enclosing class. Returns Type.noType if the type is a
+   * top-level class.
+   *
+   * <p>If the innermost enclosing class is static, this method returns the type of that class. By
+   * contrast, {@link DeclaredType#getEnclosingType()} returns the type of the innermost enclosing
+   * class that is not static.
+   *
+   * @param type a DeclaredType
+   * @return the type of the innermost enclosing class or Type.noType
+   */
+  private static TypeMirror getTypeOfEnclosingClass(DeclaredType type) {
+    if (type instanceof ClassType) {
+      // enclClass() needs to be called on tsym.owner, because tsym.enclClass() == tsym.
+      Symbol sym = ((ClassType) type).tsym.owner;
+      if (sym == null) {
+        return com.sun.tools.javac.code.Type.noType;
+      }
+
+      ClassSymbol cs = sym.enclClass();
+      if (cs == null) {
+        return com.sun.tools.javac.code.Type.noType;
+      }
+
+      return cs.asType();
+    } else {
+      return type.getEnclosingType();
+    }
+  }
+
+  ///////////////////////////////////////////////////////////////////////////
+  /// Exceptions
+  ///
+
+  /**
+   * An exception that indicates a parse error. Call {@link #getDiagMessage} to obtain a {@link
+   * DiagMessage} that can be used for error reporting.
+   */
+  public static class JavaExpressionParseException extends Exception {
+    private static final long serialVersionUID = 2L;
+    /** The error message key. */
+    private @CompilerMessageKey String errorKey;
+    /** The arguments to the error message key. */
+    public final Object[] args;
+
+    /**
+     * Create a new JavaExpressionParseException.
+     *
+     * @param errorKey the error message key
+     * @param args the arguments to the error message key
+     */
+    public JavaExpressionParseException(@CompilerMessageKey String errorKey, Object... args) {
+      this(null, errorKey, args);
+    }
+
+    /**
+     * Create a new JavaExpressionParseException.
+     *
+     * @param cause cause
+     * @param errorKey the error message key
+     * @param args the arguments to the error message key
+     */
+    public JavaExpressionParseException(
+        @Nullable Throwable cause, @CompilerMessageKey String errorKey, Object... args) {
+      super(cause);
+      this.errorKey = errorKey;
+      this.args = args;
+    }
+
+    @Override
+    public String getMessage() {
+      return errorKey + " " + Arrays.toString(args);
+    }
+
+    /**
+     * Return a DiagMessage that can be used for error reporting.
+     *
+     * @return a DiagMessage that can be used for error reporting
+     */
+    public DiagMessage getDiagMessage() {
+      return new DiagMessage(Kind.ERROR, errorKey, args);
+    }
+
+    public boolean isFlowParseError() {
+      return errorKey.endsWith("flowexpr.parse.error");
+    }
+  }
+
+  /**
+   * Returns a {@link JavaExpressionParseException} with error key "flowexpr.parse.error" for the
+   * expression {@code expr} with explanation {@code explanation}.
+   *
+   * @param expr the string that could not be parsed
+   * @param explanation an explanation of the parse failure
+   * @return a {@link JavaExpressionParseException} for the expression {@code expr} with explanation
+   *     {@code explanation}.
+   */
+  private static JavaExpressionParseException constructJavaExpressionParseError(
+      String expr, String explanation) {
+    if (expr == null) {
+      throw new BugInCF("Must have an expression.");
+    }
+    if (explanation == null) {
+      throw new BugInCF("Must have an explanation.");
+    }
+    return new JavaExpressionParseException(
+        (Throwable) null, "flowexpr.parse.error", "Invalid '" + expr + "' because " + explanation);
+  }
+
+  /**
+   * The unchecked exception equivalent of checked exception {@link JavaExpressionParseException}.
+   */
+  private static class ParseRuntimeException extends RuntimeException {
+    private static final long serialVersionUID = 2L;
+    private final JavaExpressionParseException exception;
+
+    private ParseRuntimeException(JavaExpressionParseException exception) {
+      this.exception = exception;
+    }
+
+    private JavaExpressionParseException getCheckedException() {
+      return exception;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/JavaParserUtil.java b/framework/src/main/java/org/checkerframework/framework/util/JavaParserUtil.java
new file mode 100644
index 0000000..f59e9c6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/JavaParserUtil.java
@@ -0,0 +1,279 @@
+package org.checkerframework.framework.util;
+
+import com.github.javaparser.JavaParser;
+import com.github.javaparser.ParseProblemException;
+import com.github.javaparser.ParseResult;
+import com.github.javaparser.ParserConfiguration;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.StubUnit;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.EnumDeclaration;
+import com.github.javaparser.ast.body.TypeDeclaration;
+import com.github.javaparser.ast.expr.AnnotationExpr;
+import com.github.javaparser.ast.expr.BinaryExpr;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.StringLiteralExpr;
+import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Optional;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Utility methods for working with JavaParser. It is a replacement for StaticJavaParser that does
+ * not leak memory, and it provides some other methods.
+ */
+public class JavaParserUtil {
+
+  ///
+  /// Replacements for StaticJavaParser
+  ///
+
+  /**
+   * Parses the Java code contained in the {@code InputStream} and returns a {@code CompilationUnit}
+   * that represents it.
+   *
+   * <p>This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because it
+   * creates a new instance of JavaParser each time it is invoked. Re-using {@code StaticJavaParser}
+   * causes memory problems because it retains too much memory.
+   *
+   * @param inputStream the Java source code
+   * @return CompilationUnit representing the Java source code
+   * @throws ParseProblemException if the source code has parser errors
+   */
+  public static CompilationUnit parseCompilationUnit(InputStream inputStream) {
+    JavaParser javaParser = new JavaParser(new ParserConfiguration());
+    ParseResult<CompilationUnit> parseResult = javaParser.parse(inputStream);
+    if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) {
+      return parseResult.getResult().get();
+    } else {
+      throw new ParseProblemException(parseResult.getProblems());
+    }
+  }
+
+  /**
+   * Parses the Java code contained in the {@code File} and returns a {@code CompilationUnit} that
+   * represents it.
+   *
+   * <p>This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because it
+   * creates a new instance of JavaParser each time it is invoked. Re-using {@code StaticJavaParser}
+   * causes memory problems because it retains too much memory.
+   *
+   * @param file the Java source code
+   * @return CompilationUnit representing the Java source code
+   * @throws ParseProblemException if the source code has parser errors
+   * @throws FileNotFoundException if the file was not found
+   */
+  public static CompilationUnit parseCompilationUnit(File file) throws FileNotFoundException {
+    JavaParser javaParser = new JavaParser(new ParserConfiguration());
+    ParseResult<CompilationUnit> parseResult = javaParser.parse(file);
+    if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) {
+      return parseResult.getResult().get();
+    } else {
+      throw new ParseProblemException(parseResult.getProblems());
+    }
+  }
+
+  /**
+   * Parses the Java code contained in the {@code String} and returns a {@code CompilationUnit} that
+   * represents it.
+   *
+   * <p>This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because it
+   * creates a new instance of JavaParser each time it is invoked. Re-using {@code StaticJavaParser}
+   * causes memory problems because it retains too much memory.
+   *
+   * @param javaSource the Java source code
+   * @return CompilationUnit representing the Java source code
+   * @throws ParseProblemException if the source code has parser errors
+   */
+  public static CompilationUnit parseCompilationUnit(String javaSource) {
+    JavaParser javaParser = new JavaParser(new ParserConfiguration());
+    ParseResult<CompilationUnit> parseResult = javaParser.parse(javaSource);
+    if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) {
+      return parseResult.getResult().get();
+    } else {
+      throw new ParseProblemException(parseResult.getProblems());
+    }
+  }
+
+  /**
+   * Parses the stub file contained in the {@code InputStream} and returns a {@code StubUnit} that
+   * represents it.
+   *
+   * <p>This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because it
+   * creates a new instance of JavaParser each time it is invoked. Re-using {@code StaticJavaParser}
+   * causes memory problems because it retains too much memory.
+   *
+   * @param inputStream the stub file
+   * @return StubUnit representing the stub file
+   * @throws ParseProblemException if the source code has parser errors
+   */
+  public static StubUnit parseStubUnit(InputStream inputStream) {
+    // The ParserConfiguration accumulates data each time parse is called, so create a new one each
+    // time.  There's no method to set the ParserConfiguration used by a JavaParser, so a JavaParser
+    // has to be created each time.
+    ParserConfiguration configuration = new ParserConfiguration();
+    // Store the tokens so that errors have line and column numbers.
+    // configuration.setStoreTokens(false);
+    configuration.setLexicalPreservationEnabled(false);
+    configuration.setAttributeComments(false);
+    configuration.setDetectOriginalLineSeparator(false);
+    JavaParser javaParser = new JavaParser(configuration);
+    ParseResult<StubUnit> parseResult = javaParser.parseStubUnit(inputStream);
+    if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) {
+      return parseResult.getResult().get();
+    } else {
+      throw new ParseProblemException(parseResult.getProblems());
+    }
+  }
+
+  /**
+   * Parses the {@code expression} and returns an {@code Expression} that represents it.
+   *
+   * <p>This is like {@code StaticJavaParser.parseExpression}, but it does not lead to memory leaks
+   * because it creates a new instance of JavaParser each time it is invoked. Re-using {@code
+   * StaticJavaParser} causes memory problems because it retains too much memory.
+   *
+   * @param expression the expression string
+   * @return the parsed expression
+   * @throws ParseProblemException if the expression has parser errors
+   */
+  public static Expression parseExpression(String expression) {
+    // The ParserConfiguration accumulates data each time parse is called, so create a new one each
+    // time.  There's no method to set the ParserConfiguration used by a JavaParser, so a JavaParser
+    // has to be created each time.
+    ParserConfiguration configuration = new ParserConfiguration();
+    configuration.setStoreTokens(false);
+    configuration.setLexicalPreservationEnabled(false);
+    configuration.setAttributeComments(false);
+    configuration.setDetectOriginalLineSeparator(false);
+    JavaParser javaParser = new JavaParser(configuration);
+    ParseResult<Expression> parseResult = javaParser.parseExpression(expression);
+    if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) {
+      return parseResult.getResult().get();
+    } else {
+      throw new ParseProblemException(parseResult.getProblems());
+    }
+  }
+
+  ///
+  /// Other methods
+  ///
+
+  /**
+   * Given the compilation unit node for a source file, returns the top level type definition with
+   * the given name.
+   *
+   * @param root compilation unit to search
+   * @param name name of a top level type declaration in {@code root}
+   * @return a top level type declaration in {@code root} named {@code name}
+   */
+  public static TypeDeclaration<?> getTypeDeclarationByName(CompilationUnit root, String name) {
+    Optional<ClassOrInterfaceDeclaration> classDecl = root.getClassByName(name);
+    if (classDecl.isPresent()) {
+      return classDecl.get();
+    }
+
+    Optional<ClassOrInterfaceDeclaration> interfaceDecl = root.getInterfaceByName(name);
+    if (interfaceDecl.isPresent()) {
+      return interfaceDecl.get();
+    }
+
+    Optional<EnumDeclaration> enumDecl = root.getEnumByName(name);
+    if (enumDecl.isPresent()) {
+      return enumDecl.get();
+    }
+
+    Optional<CompilationUnit.Storage> storage = root.getStorage();
+    if (storage.isPresent()) {
+      throw new BugInCF("Type " + name + " not found in " + storage.get().getPath());
+    } else {
+      throw new BugInCF("Type " + name + " not found in " + root);
+    }
+  }
+
+  /**
+   * Returns the fully qualified name of a type appearing in a given compilation unit.
+   *
+   * @param type a type declaration
+   * @param compilationUnit the compilation unit containing {@code type}
+   * @return the fully qualified name of {@code type} if {@code compilationUnit} contains a package
+   *     declaration, or just the name of {@code type} otherwise
+   */
+  public static String getFullyQualifiedName(
+      TypeDeclaration<?> type, CompilationUnit compilationUnit) {
+    if (compilationUnit.getPackageDeclaration().isPresent()) {
+      return compilationUnit.getPackageDeclaration().get().getNameAsString()
+          + "."
+          + type.getNameAsString();
+    } else {
+      return type.getNameAsString();
+    }
+  }
+
+  /**
+   * Side-effects {@code node} by removing all annotations from anywhere inside its subtree.
+   *
+   * @param node a JavaParser Node
+   */
+  public static void clearAnnotations(Node node) {
+    node.accept(new ClearAnnotationsVisitor(), null);
+  }
+
+  /** A visitor that clears all annotations from a JavaParser AST. */
+  private static class ClearAnnotationsVisitor extends VoidVisitorWithDefaultAction {
+    @Override
+    public void defaultAction(Node node) {
+      for (Node child : new ArrayList<>(node.getChildNodes())) {
+        if (child instanceof AnnotationExpr) {
+          node.remove(child);
+        }
+      }
+    }
+  }
+
+  /**
+   * Side-effects node by combining any added String literals in node's subtree into their
+   * concatenation. For example, the expression {@code "a" + "b"} becomes {@code "ab"}. This occurs
+   * even if, when reading from left to right, the two string literals are not added directly. For
+   * example, the expression {@code 1 + "a" + "b"} parses as {@code (1 + "a") + "b"}}, but it is
+   * transformed into {@code 1 + "ab"}.
+   *
+   * <p>This is the same transformation performed by javac automatically. Javac seems to ignore
+   * string literals surrounded in parentheses, so this method does as well.
+   *
+   * @param node a JavaParser Node
+   */
+  public static void concatenateAddedStringLiterals(Node node) {
+    node.accept(new StringLiteralConcatenateVisitor(), null);
+  }
+
+  /** Visitor that combines added String literals, see {@link #concatenateAddedStringLiterals}. */
+  public static class StringLiteralConcatenateVisitor extends VoidVisitorAdapter<Void> {
+    @Override
+    public void visit(BinaryExpr node, Void p) {
+      super.visit(node, p);
+      if (node.getOperator() == BinaryExpr.Operator.PLUS && node.getRight().isStringLiteralExpr()) {
+        String right = node.getRight().asStringLiteralExpr().asString();
+        if (node.getLeft().isStringLiteralExpr()) {
+          String left = node.getLeft().asStringLiteralExpr().asString();
+          node.replace(new StringLiteralExpr(left + right));
+        } else if (node.getLeft().isBinaryExpr()) {
+          BinaryExpr leftExpr = node.getLeft().asBinaryExpr();
+          if (leftExpr.getOperator() == BinaryExpr.Operator.PLUS
+              && leftExpr.getRight().isStringLiteralExpr()) {
+            String left = leftExpr.getRight().asStringLiteralExpr().asString();
+            node.replace(
+                new BinaryExpr(
+                    leftExpr.getLeft(),
+                    new StringLiteralExpr(left + right),
+                    BinaryExpr.Operator.PLUS));
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java
new file mode 100644
index 0000000..6722cef
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java
@@ -0,0 +1,1085 @@
+package org.checkerframework.framework.util;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Name;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.ElementQualifierHierarchy;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.type.NoElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypeSystemError;
+
+/**
+ * Represents the type qualifier hierarchy of a type system that supports multiple separate subtype
+ * hierarchies.
+ *
+ * <p>This class is immutable and can be only created through {@link MultiGraphFactory}.
+ *
+ * @deprecated Use {@link ElementQualifierHierarchy}, {@link MostlyNoElementQualifierHierarchy}, or
+ *     {@link NoElementQualifierHierarchy} instead. This class will be removed in a future release.
+ *     <p>Here are instructions on how to convert from a subclass of MultiGraphQualifierHierarchy to
+ *     the new implementations:
+ *     <p>If the subclass implements isSubtype and calls super when annotations do not have
+ *     elements, then use the following instructions to convert to {@link
+ *     MostlyNoElementQualifierHierarchy}.
+ *     <ol>
+ *       <li>Change {@code extends MultiGraphQualifierHierarchy} to {@code extends
+ *           MostlyNoElementQualifierHierarchy}.
+ *       <li>Create a constructor matching super.
+ *       <li>Implement isSubtypeWithElements, leastUpperBoundWithElements, and
+ *           greatestLowerBoundWithElements. You may be able to reuse parts of your current
+ *           implementation of isSubtype, leastUpperBound, and greatestLowerBound.
+ *     </ol>
+ *     <p>If the subclass implements isSubtype and does not call super in that implementation, then
+ *     use the following instructions to convert to a subclass of {@link ElementQualifierHierarchy}.
+ *     <ol>
+ *       <li>Change {@code extends MultiGraphQualifierHierarchy} to {@code extends
+ *           ElementQualifierHierarchy}.
+ *       <li>Create a constructor matching super.
+ *       <li>Implement {@link #leastUpperBound(AnnotationMirror, AnnotationMirror)} and {@link
+ *           #greatestLowerBound(AnnotationMirror, AnnotationMirror)} if missing. (In the past, it
+ *           was very easy to forget to implement these, now they are abstract methods.)
+ *     </ol>
+ *     If you wish to continue to use a subclass of {@link MultiGraphQualifierHierarchy} or {@link
+ *     GraphQualifierHierarchy}, you may do so by adding the following to AnnotatedTypeFactory.
+ *     (It's better to convert to one of the new classes because MultiGraphQualifierHierarchy and
+ *     GraphQualifierHierarchy are buggy and no longer supported.)
+ *     <p>If any qualifier has an annotation element without a default value, you will need to
+ *     convert to one of the new subclasses. If you do not, then MultiGraphQualifierHierarchy will
+ *     throw an exception with a message like "AnnotationBuilder.fromName: no value for element
+ *     value() of checkers.inference.qual.VarAnnot".
+ *     <pre>
+ * {@code @Override}
+ * {@code @SuppressWarnings("deprecation")}
+ * <code> public QualifierHierarchy createQualifierHierarchy() {
+ *      return org.checkerframework.framework.util.MultiGraphQualifierHierarchy
+ *              .createMultiGraphQualifierHierarchy(this);
+ *   }
+ * </code>
+ * {@code @Override}
+ * {@code @SuppressWarnings("deprecation")}
+ * <code> public QualifierHierarchy createQualifierHierarchyWithMultiGraphFactory(
+ *          org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory
+ *                  factory) {
+ *      return new YourSubclassQualifierHierarchy(factory);
+ *  }
+ * </code></pre>
+ */
+@SuppressWarnings("interning") // Class is deprecated.
+@Deprecated // 2020-09-10
+public class MultiGraphQualifierHierarchy implements QualifierHierarchy {
+
+  /**
+   * Creates the QualifierHierarchy using {@link
+   * org.checkerframework.framework.util.MultiGraphQualifierHierarchy}
+   *
+   * @param annotatedTypeFactory annotated type factory
+   * @return qualifier hierarchy
+   * @deprecated Use {@link ElementQualifierHierarchy} instead.
+   */
+  @Deprecated // 2020-09-10
+  public static QualifierHierarchy createMultiGraphQualifierHierarchy(
+      AnnotatedTypeFactory annotatedTypeFactory) {
+    Set<Class<? extends Annotation>> supportedTypeQualifiers =
+        annotatedTypeFactory.getSupportedTypeQualifiers();
+    MultiGraphFactory factory = new MultiGraphFactory(annotatedTypeFactory);
+    for (Class<? extends Annotation> typeQualifier : supportedTypeQualifiers) {
+      AnnotationMirror typeQualifierAnno =
+          AnnotationBuilder.fromClass(annotatedTypeFactory.getElementUtils(), typeQualifier);
+      factory.addQualifier(typeQualifierAnno);
+      // Polymorphic qualifiers can't declare their supertypes.
+      // An error is raised if one is present.
+      if (typeQualifier.getAnnotation(PolymorphicQualifier.class) != null) {
+        if (typeQualifier.getAnnotation(SubtypeOf.class) != null) {
+          // This is currently not supported. At some point we might add
+          // polymorphic qualifiers with upper and lower bounds.
+          throw new TypeSystemError(
+              "AnnotatedTypeFactory: "
+                  + typeQualifier
+                  + " is polymorphic and specifies super qualifiers."
+                  + " Remove the @org.checkerframework.framework.qual.SubtypeOf or"
+                  + " @org.checkerframework.framework.qual.PolymorphicQualifier annotation from"
+                  + " it.");
+        }
+        continue;
+      }
+      if (typeQualifier.getAnnotation(SubtypeOf.class) == null) {
+        throw new TypeSystemError(
+            "AnnotatedTypeFactory: %s does not specify its super qualifiers.%n"
+                + "Add an @org.checkerframework.framework.qual.SubtypeOf annotation to it,%n"
+                + "or if it is an alias, exclude it from `createSupportedTypeQualifiers()`.%n",
+            typeQualifier);
+      }
+      Class<? extends Annotation>[] superQualifiers =
+          typeQualifier.getAnnotation(SubtypeOf.class).value();
+      for (Class<? extends Annotation> superQualifier : superQualifiers) {
+        if (!supportedTypeQualifiers.contains(superQualifier)) {
+          throw new TypeSystemError(
+              "Found unsupported qualifier in SubTypeOf: %s on qualifier: %s",
+              superQualifier.getCanonicalName(), typeQualifier.getCanonicalName());
+        }
+        if (superQualifier.getAnnotation(PolymorphicQualifier.class) != null) {
+          // This is currently not supported. No qualifier can have a polymorphic
+          // qualifier as super qualifier.
+          throw new TypeSystemError(
+              "Found polymorphic qualifier in SubTypeOf: %s on qualifier: %s",
+              superQualifier.getCanonicalName(), typeQualifier.getCanonicalName());
+        }
+        AnnotationMirror superAnno =
+            AnnotationBuilder.fromClass(annotatedTypeFactory.getElementUtils(), superQualifier);
+        factory.addSubtype(typeQualifierAnno, superAnno);
+      }
+    }
+
+    QualifierHierarchy hierarchy = factory.build();
+
+    if (!hierarchy.isValid()) {
+      throw new TypeSystemError(
+          "AnnotatedTypeFactory: invalid qualifier hierarchy: "
+              + hierarchy.getClass()
+              + " "
+              + hierarchy);
+    }
+
+    return hierarchy;
+  }
+
+  /**
+   * Factory used to create an instance of {@link GraphQualifierHierarchy}. A factory can be used to
+   * create at most one {@link GraphQualifierHierarchy}.
+   *
+   * <p>To create a hierarchy, a client may do so in three steps:
+   *
+   * <ol>
+   *   <li>add qualifiers using {@link #addQualifier(AnnotationMirror)};
+   *   <li>add subtype relations using {@link #addSubtype(AnnotationMirror, AnnotationMirror)}
+   *   <li>build the hierarchy and gets using {@link #build()}.
+   * </ol>
+   *
+   * Notice that {@link #addSubtype(AnnotationMirror, AnnotationMirror)} adds the two qualifiers to
+   * the hierarchy if they are not already in.
+   *
+   * <p>Also, once the client builds a hierarchy through {@link #build()}, no further modifications
+   * are allowed nor can it making a new instance.
+   *
+   * <p>Clients build the hierarchy using {@link #addQualifier(AnnotationMirror)} and {@link
+   * #addSubtype(AnnotationMirror, AnnotationMirror)}, then get the instance with calling {@link
+   * #build()}
+   *
+   * @deprecated Use {@link ElementQualifierHierarchy} instead.
+   */
+  @Deprecated // 2020-09-10
+  public static class MultiGraphFactory {
+    /**
+     * Map from qualifiers to the direct supertypes of the qualifier. Only the subtype relations
+     * given by addSubtype are in this mapping, no transitive relationships. It is immutable once
+     * GraphQualifierHierarchy is built. No polymorphic qualifiers are contained in this map.
+     */
+    protected final Map<AnnotationMirror, Set<AnnotationMirror>> supertypesDirect;
+
+    /**
+     * Map from qualifier hierarchy to the corresponding polymorphic qualifier. The key is:
+     *
+     * <ul>
+     *   <li>the argument to @PolymorphicQualifier (typically the top qualifier in the hierarchy),
+     *       or
+     *   <li>"Annotation" if @PolymorphicQualifier is used without an argument, or
+     * </ul>
+     */
+    protected final Map<AnnotationMirror, AnnotationMirror> polyQualifiers;
+
+    /** The annotated type factory associated with this hierarchy. */
+    protected final AnnotatedTypeFactory atypeFactory;
+
+    /** Create a factory. */
+    public MultiGraphFactory(AnnotatedTypeFactory atypeFactory) {
+      this.supertypesDirect = AnnotationUtils.createAnnotationMap();
+      this.polyQualifiers = AnnotationUtils.createAnnotationMap();
+      this.atypeFactory = atypeFactory;
+    }
+
+    /**
+     * Adds the passed qualifier to the hierarchy. Clients need to specify its super qualifiers in
+     * subsequent calls to {@link #addSubtype(AnnotationMirror, AnnotationMirror)}.
+     */
+    public void addQualifier(AnnotationMirror qual) {
+      assertNotBuilt();
+      if (AnnotationUtils.containsSame(supertypesDirect.keySet(), qual)) {
+        return;
+      }
+
+      @CanonicalName Name pqtopclass = getPolymorphicQualifierElement(qual);
+      if (pqtopclass != null) {
+        AnnotationMirror pqtop;
+        if (pqtopclass.contentEquals(Annotation.class.getName())) {
+          // A @PolymorphicQualifier with no value defaults to Annotation.class.
+          // That means there is only one top in the hierarchy. The top qualifier
+          // may not be known at this point, so use the qualifier itself.
+          // This is changed to top in MultiGraphQualifierHierarchy.addPolyRelations
+          pqtop = qual;
+        } else {
+          pqtop = AnnotationBuilder.fromName(atypeFactory.getElementUtils(), pqtopclass);
+        }
+        // use given top (which might be Annotation) as key
+        this.polyQualifiers.put(pqtop, qual);
+      } else {
+        supertypesDirect.put(qual, AnnotationUtils.createAnnotationSet());
+      }
+    }
+
+    /**
+     * Returns the {@link PolymorphicQualifier} meta-annotation on {@code qual} if one exists;
+     * otherwise return null.
+     *
+     * @param qual qualifier
+     * @return the {@link PolymorphicQualifier} meta-annotation on {@code qual} if one exists;
+     *     otherwise return null
+     */
+    private AnnotationMirror getPolymorphicQualifier(AnnotationMirror qual) {
+      if (qual == null) {
+        return null;
+      }
+      Element qualElt = qual.getAnnotationType().asElement();
+      for (AnnotationMirror am : qualElt.getAnnotationMirrors()) {
+        if (atypeFactory.areSameByClass(am, PolymorphicQualifier.class)) {
+          return am;
+        }
+      }
+      return null;
+    }
+
+    /**
+     * If {@code qual} is a polymorphic qualifier, return the class specified by the {@link
+     * PolymorphicQualifier} meta-annotation on the polymorphic qualifier is returned. Otherwise,
+     * return null.
+     *
+     * <p>This value identifies the qualifier hierarchy to which this polymorphic qualifier belongs.
+     * By convention, it is the top qualifier of the hierarchy. Use of {@code Annotation.class} is
+     * discouraged, because it can lead to ambiguity if used for multiple type systems.
+     *
+     * @param qual an annotation
+     * @return the name of the class specified by the {@link PolymorphicQualifier} meta-annotation
+     *     on {@code qual}, if {@code qual} is a polymorphic qualifier; otherwise, null.
+     * @see org.checkerframework.framework.qual.PolymorphicQualifier#value()
+     */
+    private @Nullable @CanonicalName Name getPolymorphicQualifierElement(AnnotationMirror qual) {
+      AnnotationMirror poly = getPolymorphicQualifier(qual);
+
+      // System.out.println("poly: " + poly + " pq: " +
+      //     PolymorphicQualifier.class.getCanonicalName());
+      if (poly == null) {
+        return null;
+      }
+      @SuppressWarnings("deprecation"
+      // It would be possible to use the ExecutableElement for `PolymorphicQualifier.value` rather
+      // than the string "value", but it is not convenient to obtain the ExecutableElement.
+      )
+      Name ret = AnnotationUtils.getElementValueClassName(poly, "value", true);
+      return ret;
+    }
+
+    /**
+     * Adds a subtype relationship between the two type qualifiers. Assumes that both qualifiers are
+     * part of the same qualifier hierarchy; callers should ensure this.
+     *
+     * @param sub the sub type qualifier
+     * @param sup the super type qualifier
+     */
+    public void addSubtype(AnnotationMirror sub, AnnotationMirror sup) {
+      assertNotBuilt();
+      addQualifier(sub);
+      addQualifier(sup);
+      supertypesDirect.get(sub).add(sup);
+    }
+
+    /**
+     * Returns an instance of {@link GraphQualifierHierarchy} that represents the hierarchy built so
+     * far.
+     */
+    public QualifierHierarchy build() {
+      assertNotBuilt();
+      QualifierHierarchy result = createQualifierHierarchy();
+      wasBuilt = true;
+      return result;
+    }
+
+    /**
+     * Create {@link QualifierHierarchy}
+     *
+     * @return QualifierHierarchy
+     */
+    protected QualifierHierarchy createQualifierHierarchy() {
+      return atypeFactory.createQualifierHierarchyWithMultiGraphFactory(this);
+    }
+
+    /** True if the factory has already been built. */
+    private boolean wasBuilt = false;
+
+    /** Throw an exception if the factory was already built. */
+    protected void assertNotBuilt() {
+      if (wasBuilt) {
+        throw new BugInCF("MultiGraphQualifierHierarchy.Factory was already built.");
+      }
+    }
+  }
+
+  /**
+   * The declared, direct supertypes for each qualifier, without added transitive relations.
+   * Immutable after construction finishes. No polymorphic qualifiers are contained in this map.
+   *
+   * @see MultiGraphQualifierHierarchy.MultiGraphFactory#supertypesDirect
+   */
+  protected final Map<AnnotationMirror, Set<AnnotationMirror>> supertypesDirect;
+
+  /** The transitive closure of the supertypesDirect. Immutable after construction finishes. */
+  protected final Map<AnnotationMirror, Set<AnnotationMirror>> supertypesTransitive;
+
+  /** The top qualifiers of the individual type hierarchies. */
+  protected final Set<AnnotationMirror> tops;
+
+  /** The bottom qualifiers of the type hierarchies. TODO: clarify relation to tops. */
+  protected final Set<AnnotationMirror> bottoms;
+
+  /**
+   * See {@link MultiGraphQualifierHierarchy.MultiGraphFactory#polyQualifiers}.
+   *
+   * @see MultiGraphQualifierHierarchy.MultiGraphFactory#polyQualifiers
+   */
+  protected final Map<AnnotationMirror, AnnotationMirror> polyQualifiers;
+
+  /** All qualifiers, including polymorphic qualifiers. */
+  private final Set<AnnotationMirror> typeQualifiers;
+
+  public MultiGraphQualifierHierarchy(MultiGraphFactory f) {
+    this(f, (Object[]) null);
+  }
+
+  // Allow a subclass to provide additional constructor parameters that
+  // are simply passed back via a call to the "finish" method.
+  public MultiGraphQualifierHierarchy(MultiGraphFactory f, Object... args) {
+    super();
+    // no need for copying as f.supertypes has no mutable references to it
+    // TODO: also make the Set of supertypes immutable?
+    this.supertypesDirect = Collections.unmodifiableMap(f.supertypesDirect);
+
+    // Calculate the transitive closure
+    Map<AnnotationMirror, Set<AnnotationMirror>> supertypesTransitive =
+        transitiveClosure(f.supertypesDirect);
+
+    Set<AnnotationMirror> newtops = findTops(supertypesTransitive);
+    Set<AnnotationMirror> newbottoms = findBottoms(supertypesTransitive);
+
+    this.polyQualifiers = f.polyQualifiers;
+
+    addPolyRelations(this, supertypesTransitive, this.polyQualifiers, newtops, newbottoms);
+
+    finish(this, supertypesTransitive, this.polyQualifiers, newtops, newbottoms, args);
+
+    this.tops = Collections.unmodifiableSet(newtops);
+    this.bottoms = Collections.unmodifiableSet(newbottoms);
+    // TODO: make polyQualifiers immutable also?
+
+    this.supertypesTransitive = Collections.unmodifiableMap(supertypesTransitive);
+    Set<AnnotationMirror> typeQualifiers = AnnotationUtils.createAnnotationSet();
+    typeQualifiers.addAll(supertypesTransitive.keySet());
+    this.typeQualifiers = Collections.unmodifiableSet(typeQualifiers);
+    // System.out.println("MGH: " + this);
+  }
+
+  @Override
+  public boolean isValid() {
+    return !typeQualifiers.isEmpty();
+  }
+
+  /**
+   * Method to finalize the qualifier hierarchy before it becomes unmodifiable. The parameters pass
+   * all fields and allow modification.
+   */
+  protected void finish(
+      QualifierHierarchy qualHierarchy,
+      Map<AnnotationMirror, Set<AnnotationMirror>> supertypesTransitive,
+      Map<AnnotationMirror, AnnotationMirror> polyQualifiers,
+      Set<AnnotationMirror> tops,
+      Set<AnnotationMirror> bottoms,
+      Object... args) {}
+
+  @SideEffectFree
+  @Override
+  public String toString() {
+    StringJoiner sj = new StringJoiner(System.lineSeparator());
+    sj.add("Supertypes Graph: ");
+
+    for (Map.Entry<AnnotationMirror, Set<AnnotationMirror>> qual : supertypesDirect.entrySet()) {
+      sj.add("\t" + qual.getKey() + " = " + qual.getValue());
+    }
+
+    sj.add("Supertypes Map: ");
+
+    for (Map.Entry<AnnotationMirror, Set<AnnotationMirror>> qual :
+        supertypesTransitive.entrySet()) {
+      String keyOpen = "\t" + qual.getKey() + " = [";
+
+      Set<AnnotationMirror> supertypes = qual.getValue();
+
+      if (supertypes.size() == 1) {
+        // If there's only 1 supertype for this qual, then display that in the same row.
+        sj.add(keyOpen + supertypes.iterator().next() + "]");
+      } else {
+        // otherwise, display each supertype in its own row
+        sj.add(keyOpen);
+        for (Iterator<AnnotationMirror> iterator = supertypes.iterator(); iterator.hasNext(); ) {
+          sj.add("\t\t" + iterator.next() + (iterator.hasNext() ? ", " : ""));
+        }
+        sj.add("\t\t]");
+      }
+    }
+
+    sj.add("Tops: " + tops);
+    sj.add("Bottoms: " + bottoms);
+
+    return sj.toString();
+  }
+
+  @Override
+  public Set<? extends AnnotationMirror> getTopAnnotations() {
+    return this.tops;
+  }
+
+  @Override
+  public AnnotationMirror getTopAnnotation(AnnotationMirror start) {
+    for (AnnotationMirror top : tops) {
+      if (AnnotationUtils.areSame(start, top) || isSubtype(start, top)) {
+        return top;
+      }
+    }
+    throw new BugInCF(
+        "MultiGraphQualifierHierarchy: did not find the top corresponding to qualifier "
+            + start
+            + " all tops: "
+            + tops);
+  }
+
+  @Override
+  public Set<? extends AnnotationMirror> getBottomAnnotations() {
+    return this.bottoms;
+  }
+
+  @Override
+  public AnnotationMirror getBottomAnnotation(AnnotationMirror start) {
+    for (AnnotationMirror bot : bottoms) {
+      if (AnnotationUtils.areSame(start, bot) || isSubtype(bot, start)) {
+        return bot;
+      }
+    }
+    throw new BugInCF(
+        "MultiGraphQualifierHierarchy: did not find the bottom corresponding to qualifier "
+            + start
+            + "; all bottoms: "
+            + bottoms
+            + "; this: "
+            + this);
+  }
+
+  @Override
+  public AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) {
+    AnnotationMirror top = getTopAnnotation(start);
+    for (AnnotationMirror key : polyQualifiers.keySet()) {
+      if (key != null && AnnotationUtils.areSame(key, top)) {
+        return polyQualifiers.get(key);
+      }
+    }
+    // No polymorphic qualifier exists for that hierarchy.
+    return null;
+  }
+
+  @Override
+  public boolean isSubtype(
+      Collection<? extends AnnotationMirror> rhs, Collection<? extends AnnotationMirror> lhs) {
+    if (lhs.isEmpty() || rhs.isEmpty()) {
+      throw new BugInCF(
+          "MultiGraphQualifierHierarchy: empty annotations in lhs: " + lhs + " or rhs: " + rhs);
+    }
+    if (lhs.size() != rhs.size()) {
+      throw new BugInCF(
+          "MultiGraphQualifierHierarchy: mismatched number of annotations in lhs: "
+              + lhs
+              + " and rhs: "
+              + rhs);
+    }
+    int valid = 0;
+    for (AnnotationMirror lhsAnno : lhs) {
+      for (AnnotationMirror rhsAnno : rhs) {
+        if (AnnotationUtils.areSame(getTopAnnotation(lhsAnno), getTopAnnotation(rhsAnno))
+            && isSubtype(rhsAnno, lhsAnno)) {
+          ++valid;
+        }
+      }
+    }
+    return lhs.size() == valid;
+  }
+
+  /** For caching results of lubs * */
+  private Map<AnnotationPair, AnnotationMirror> lubs = null;
+
+  @Override
+  public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
+    if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) {
+      return null;
+    } else if (isSubtype(a1, a2)) {
+      return a2;
+    } else if (isSubtype(a2, a1)) {
+      return a1;
+    } else if (AnnotationUtils.areSameByName(a1, a2)) {
+      return getTopAnnotation(a1);
+    }
+    if (lubs == null) {
+      lubs = calculateLubs();
+    }
+    AnnotationPair pair = new AnnotationPair(a1, a2);
+    return lubs.get(pair);
+  }
+
+  /** A cache of the results of glb computations. Maps from a pair of annotations to their glb. */
+  private Map<AnnotationPair, AnnotationMirror> glbs = null;
+
+  @Override
+  public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) {
+    if (AnnotationUtils.areSameByName(a1, a2)) {
+      return AnnotationUtils.sameElementValues(a1, a2) ? a1 : getBottomAnnotation(a1);
+    }
+    if (glbs == null) {
+      glbs = calculateGlbs();
+    }
+    AnnotationPair pair = new AnnotationPair(a1, a2);
+    return glbs.get(pair);
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>Most qualifiers have no value fields. However, two annotations with values are subtype of
+   * each other only if they have the same values. i.e. I(m) is a subtype of I(n) iff m = n.
+   *
+   * <p>When client specifies an annotation, a1, to be a subtype of annotation with values, a2, then
+   * a1 is a subtype of all instances of a2 regardless of a2 values.
+   *
+   * @param subAnno the sub qualifier
+   * @param superAnno the super qualifier
+   */
+  @Override
+  public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+    checkAnnoInGraph(subAnno);
+    checkAnnoInGraph(superAnno);
+
+    /* TODO: this optimization leads to recursion
+    for (AnnotationMirror top : tops) {
+        System.out.println("Looking at top: " + tops + " and " + anno1);
+        // We cannot use getRootAnnotation, as that would use subtyping and recurse
+        if (isSubtype(anno1, top) && AnnotationUtils.areSame(top, anno2)) {
+        return true;
+        }
+    }*/
+    if (AnnotationUtils.areSameByName(subAnno, superAnno)) {
+      return AnnotationUtils.sameElementValues(subAnno, superAnno);
+    }
+    Set<AnnotationMirror> supermap1 = this.supertypesTransitive.get(subAnno);
+    return AnnotationUtils.containsSame(supermap1, superAnno);
+  }
+
+  /**
+   * Throw a {@link BugInCF} if {@code a} is not in the {@link #supertypesTransitive} or {@link
+   * #polyQualifiers}.
+   *
+   * @param a qualifier
+   */
+  private final void checkAnnoInGraph(AnnotationMirror a) {
+    if (AnnotationUtils.containsSame(supertypesTransitive.keySet(), a)
+        || AnnotationUtils.containsSame(polyQualifiers.values(), a)) {
+      return;
+    }
+
+    if (a == null) {
+      throw new BugInCF(
+          "MultiGraphQualifierHierarchy found an unqualified type.  Please ensure that "
+              + "your defaulting rules cover all cases and/or "
+              + "use a @DefaultQualifierInHierarchy annotation.  "
+              + "Also ensure that overrides of addComputedTypeAnnotations call super.");
+    } else {
+      // System.out.println("MultiGraphQH: " + this);
+      throw new BugInCF(
+          "MultiGraphQualifierHierarchy found the unrecognized qualifier: "
+              + a
+              + ". Please ensure that the qualifier is correctly included in the subtype"
+              + " hierarchy.");
+    }
+  }
+
+  /**
+   * Infer the tops of the subtype hierarchy. Simply finds the qualifiers that have no supertypes.
+   */
+  // Not static to allow adaptation in subclasses. Only parameters should be modified.
+  protected Set<AnnotationMirror> findTops(
+      Map<AnnotationMirror, Set<AnnotationMirror>> supertypes) {
+    Set<AnnotationMirror> possibleTops = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror anno : supertypes.keySet()) {
+      if (supertypes.get(anno).isEmpty()) {
+        possibleTops.add(anno);
+      }
+    }
+    return possibleTops;
+  }
+
+  /**
+   * Infer the bottoms of the subtype hierarchy. Simple finds the qualifiers that are not supertypes
+   * of other qualifiers.
+   */
+  // Not static to allow adaptation in subclasses. Only parameters should be modified.
+  protected Set<AnnotationMirror> findBottoms(
+      Map<AnnotationMirror, Set<AnnotationMirror>> supertypes) {
+    Set<AnnotationMirror> possibleBottoms = AnnotationUtils.createAnnotationSet();
+    possibleBottoms.addAll(supertypes.keySet());
+    for (Set<AnnotationMirror> supers : supertypes.values()) {
+      possibleBottoms.removeAll(supers);
+    }
+    return possibleBottoms;
+  }
+
+  /** Computes the transitive closure of the given map and returns it. */
+  /* The method gets all required parameters passed in and could be static. However,
+   * we want to allow subclasses to adapt the behavior and therefore make it an instance method.
+   */
+  protected Map<AnnotationMirror, Set<AnnotationMirror>> transitiveClosure(
+      Map<AnnotationMirror, Set<AnnotationMirror>> supertypes) {
+    Map<AnnotationMirror, Set<AnnotationMirror>> result = AnnotationUtils.createAnnotationMap();
+    for (AnnotationMirror anno : supertypes.keySet()) {
+      // this method directly modifies result and is
+      // ignoring the returned value
+      findAllSupers(anno, supertypes, result);
+    }
+    return result;
+  }
+
+  /**
+   * Add the relationships for polymorphic qualifiers.
+   *
+   * <p>A polymorphic qualifier, such as {@code PolyNull}, needs to be:
+   *
+   * <ol>
+   *   <li>a subtype of the top qualifier (e.g. {@code Nullable})
+   *   <li>a supertype of all the bottom qualifiers (e.g. {@code NonNull})
+   * </ol>
+   *
+   * Field supertypesTransitive is not set yet when this method is called -- use parameter fullMap
+   * instead.
+   */
+  // The method gets all required parameters passed in and could be static. However,
+  // we want to allow subclasses to adapt the behavior and therefore make it an instance method.
+  protected void addPolyRelations(
+      QualifierHierarchy qualHierarchy,
+      Map<AnnotationMirror, Set<AnnotationMirror>> fullMap,
+      Map<AnnotationMirror, AnnotationMirror> polyQualifiers,
+      Set<AnnotationMirror> tops,
+      Set<AnnotationMirror> bottoms) {
+    if (polyQualifiers.isEmpty()) {
+      return;
+    }
+
+    // Handle the case where @PolymorphicQualifier uses the default value Annotation.class.
+    if (polyQualifiers.size() == 1 && tops.size() == 1) {
+      Map.Entry<AnnotationMirror, AnnotationMirror> entry =
+          polyQualifiers.entrySet().iterator().next();
+      AnnotationMirror poly = entry.getKey();
+      AnnotationMirror maybeTop = entry.getValue();
+      if (AnnotationUtils.areSameByName(poly, maybeTop)) {
+        // If the value of @PolymorphicQualifier is the default value, Annotation.class,
+        // then map is set to polyQual -> polyQual in
+        // MultiGraphQualifierHierarchy.MultiGraphFactory.addQualifier,
+        // because the top is unknown there.
+        // Reset it to top here.
+        polyQualifiers.put(tops.iterator().next(), poly);
+        polyQualifiers.remove(poly);
+      }
+    }
+
+    for (Map.Entry<AnnotationMirror, AnnotationMirror> kv : polyQualifiers.entrySet()) {
+      AnnotationMirror declTop = kv.getKey();
+      AnnotationMirror polyQualifier = kv.getValue();
+      // Ensure that it's really the top of the hierarchy
+      Set<AnnotationMirror> declSupers = fullMap.get(declTop);
+      AnnotationMirror polyTop = null;
+      if (declSupers.isEmpty()) {
+        polyTop = declTop;
+      } else {
+        for (AnnotationMirror ds : declSupers) {
+          if (AnnotationUtils.containsSameByName(tops, ds)) {
+            polyTop = ds;
+          }
+        }
+      }
+      boolean found = (polyTop != null);
+      if (found) {
+        AnnotationUtils.updateMappingToImmutableSet(
+            fullMap, polyQualifier, Collections.singleton(polyTop));
+      } else if (AnnotationUtils.areSameByName(polyQualifier, declTop)) {
+        throw new BugInCF(
+            "MultiGraphQualifierHierarchy.addPolyRelations: "
+                + "incorrect or missing top qualifier given in polymorphic qualifier "
+                + polyQualifier
+                + "; possible top qualifiers: "
+                + tops);
+      } else {
+        throw new BugInCF(
+            "MultiGraphQualifierHierarchy.addPolyRelations: "
+                + "incorrect top qualifier given in polymorphic qualifier: "
+                + polyQualifier
+                + " could not find: "
+                + polyTop);
+      }
+
+      found = false;
+      AnnotationMirror bottom = null;
+      outer:
+      for (AnnotationMirror btm : bottoms) {
+        for (AnnotationMirror btmsuper : fullMap.get(btm)) {
+          if (AnnotationUtils.areSameByName(btmsuper, polyTop)) {
+            found = true;
+            bottom = btm;
+            break outer;
+          }
+        }
+      }
+      if (found) {
+        AnnotationUtils.updateMappingToImmutableSet(
+            fullMap, bottom, Collections.singleton(polyQualifier));
+      } else {
+        // TODO: in a type system with a single qualifier this check will fail.
+        // throw new BugInCF("MultiGraphQualifierHierarchy.addPolyRelations:
+        // " +
+        //        "incorrect top qualifier given in polymorphic qualifier: "
+        //
+        //        + polyQualifier + " could not find bottom for: " + polyTop);
+      }
+    }
+  }
+
+  private Map<AnnotationPair, AnnotationMirror> calculateLubs() {
+    Map<AnnotationPair, AnnotationMirror> newlubs = new HashMap<>();
+    for (AnnotationMirror a1 : typeQualifiers) {
+      for (AnnotationMirror a2 : typeQualifiers) {
+        if (AnnotationUtils.areSameByName(a1, a2)) {
+          continue;
+        }
+        if (!AnnotationUtils.areSame(getTopAnnotation(a1), getTopAnnotation(a2))) {
+          continue;
+        }
+        AnnotationPair pair = new AnnotationPair(a1, a2);
+        if (newlubs.containsKey(pair)) {
+          continue;
+        }
+        AnnotationMirror lub = findLub(a1, a2);
+        newlubs.put(pair, lub);
+      }
+    }
+    return newlubs;
+  }
+
+  /**
+   * Finds and returns the Least Upper Bound (LUB) of two annotation mirrors a1 and a2 by
+   * recursively climbing the qualifier hierarchy of a1 until one of them is a subtype of the other,
+   * or returns null if no subtype relationships can be found.
+   *
+   * @param a1 first annotation mirror
+   * @param a2 second annotation mirror
+   * @return the LUB of a1 and a2, or null if none can be found
+   */
+  protected AnnotationMirror findLub(AnnotationMirror a1, AnnotationMirror a2) {
+    if (isSubtype(a1, a2)) {
+      return a2;
+    }
+    if (isSubtype(a2, a1)) {
+      return a1;
+    }
+
+    assert getTopAnnotation(a1) == getTopAnnotation(a2)
+        : "MultiGraphQualifierHierarchy.findLub: this method may only be called "
+            + "with qualifiers from the same hierarchy. Found a1: "
+            + a1
+            + " [top: "
+            + getTopAnnotation(a1)
+            + "], a2: "
+            + a2
+            + " [top: "
+            + getTopAnnotation(a2)
+            + "]";
+
+    if (isPolymorphicQualifier(a1)) {
+      return findLubWithPoly(a1, a2);
+    } else if (isPolymorphicQualifier(a2)) {
+      return findLubWithPoly(a2, a1);
+    }
+
+    Set<AnnotationMirror> outset = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror a1Super : supertypesDirect.get(a1)) {
+      // TODO: we take the first of the smallest supertypes, maybe we would
+      // get a different LUB if we used a different one?
+      AnnotationMirror a1Lub = findLub(a1Super, a2);
+      if (a1Lub != null) {
+        outset.add(a1Lub);
+      } else {
+        throw new BugInCF(
+            "GraphQualifierHierarchy could not determine LUB for "
+                + a1
+                + " and "
+                + a2
+                + ". Please ensure that the checker knows about all type qualifiers.");
+      }
+    }
+    return requireSingleton(outset, a1, a2, /*lub=*/ true);
+  }
+
+  private AnnotationMirror findLubWithPoly(AnnotationMirror poly, AnnotationMirror other) {
+    AnnotationMirror bottom = getBottomAnnotation(other);
+    if (AnnotationUtils.areSame(bottom, other)) {
+      return poly;
+    }
+
+    return getTopAnnotation(poly);
+  }
+
+  @Override
+  public boolean isPolymorphicQualifier(AnnotationMirror qual) {
+    return AnnotationUtils.containsSame(polyQualifiers.values(), qual);
+  }
+
+  /** Remove all supertypes of elements contained in the set. */
+  private Set<AnnotationMirror> findSmallestTypes(Set<AnnotationMirror> inset) {
+    Set<AnnotationMirror> outset = AnnotationUtils.createAnnotationSet();
+    outset.addAll(inset);
+
+    for (AnnotationMirror a1 : inset) {
+      outset.removeIf(a2 -> a1 != a2 && isSubtype(a1, a2));
+    }
+    return outset;
+  }
+
+  /** Finds all the super qualifiers for a qualifier. */
+  private static Set<AnnotationMirror> findAllSupers(
+      AnnotationMirror anno,
+      Map<AnnotationMirror, Set<AnnotationMirror>> supertypes,
+      Map<AnnotationMirror, Set<AnnotationMirror>> allSupersSoFar) {
+    Set<AnnotationMirror> supers = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror superAnno : supertypes.get(anno)) {
+      // add the current super to the superset
+      supers.add(superAnno);
+      // add all of current super's super into superset
+      supers.addAll(findAllSupers(superAnno, supertypes, allSupersSoFar));
+    }
+    allSupersSoFar.put(anno, Collections.unmodifiableSet(supers));
+    return supers;
+  }
+
+  /** Returns a map from each possible pair of annotations to their glb. */
+  private Map<AnnotationPair, AnnotationMirror> calculateGlbs() {
+    Map<AnnotationPair, AnnotationMirror> newglbs = new HashMap<>();
+    for (AnnotationMirror a1 : typeQualifiers) {
+      for (AnnotationMirror a2 : typeQualifiers) {
+        if (AnnotationUtils.areSameByName(a1, a2)) {
+          continue;
+        }
+        if (!AnnotationUtils.areSame(getTopAnnotation(a1), getTopAnnotation(a2))) {
+          continue;
+        }
+        AnnotationPair pair = new AnnotationPair(a1, a2);
+        if (newglbs.containsKey(pair)) {
+          continue;
+        }
+        AnnotationMirror glb = findGlb(a1, a2);
+        newglbs.put(pair, glb);
+      }
+    }
+    return newglbs;
+  }
+
+  private AnnotationMirror findGlb(AnnotationMirror a1, AnnotationMirror a2) {
+    if (isSubtype(a1, a2)) {
+      return a1;
+    }
+    if (isSubtype(a2, a1)) {
+      return a2;
+    }
+
+    assert getTopAnnotation(a1) == getTopAnnotation(a2)
+        : "MultiGraphQualifierHierarchy.findGlb: this method may only be called "
+            + "with qualifiers from the same hierarchy. Found a1: "
+            + a1
+            + " [top: "
+            + getTopAnnotation(a1)
+            + "], a2: "
+            + a2
+            + " [top: "
+            + getTopAnnotation(a2)
+            + "]";
+
+    if (isPolymorphicQualifier(a1)) {
+      return findGlbWithPoly(a1, a2);
+    } else if (isPolymorphicQualifier(a2)) {
+      return findGlbWithPoly(a2, a1);
+    }
+
+    Set<AnnotationMirror> outset = AnnotationUtils.createAnnotationSet();
+    for (AnnotationMirror a1Sub : supertypesDirect.keySet()) {
+      if (isSubtype(a1Sub, a1) && !a1Sub.equals(a1)) {
+        AnnotationMirror a1lb = findGlb(a1Sub, a2);
+        if (a1lb != null) {
+          outset.add(a1lb);
+        }
+      }
+    }
+    return requireSingleton(outset, a1, a2, /*lub=*/ false);
+  }
+
+  private AnnotationMirror findGlbWithPoly(AnnotationMirror poly, AnnotationMirror other) {
+    AnnotationMirror top = getTopAnnotation(other);
+    if (AnnotationUtils.areSame(top, other)) {
+      return poly;
+    }
+
+    return getBottomAnnotation(poly);
+  }
+
+  /** Remove all subtypes of elements contained in the set. */
+  private Set<AnnotationMirror> findGreatestTypes(Set<AnnotationMirror> inset) {
+    Set<AnnotationMirror> outset = AnnotationUtils.createAnnotationSet();
+    outset.addAll(inset);
+
+    for (AnnotationMirror a1 : inset) {
+      Iterator<AnnotationMirror> outit = outset.iterator();
+      while (outit.hasNext()) {
+        AnnotationMirror a2 = outit.next();
+        if (a1 != a2 && isSubtype(a2, a1)) {
+          outit.remove();
+        }
+      }
+    }
+    return outset;
+  }
+
+  /**
+   * Require that outset is a singleton set, after polymorphic qualifiers have been removed. If not,
+   * report a bug: the type hierarchy is not a lattice.
+   *
+   * @param outset the set of upper or lower bounds of a1 and a2 (depending on whether lub==true)
+   * @param a1 the first annotation being lubbed or glbed
+   * @param a2 the second annotation being lubbed or glbed
+   * @param lub true if computing lub(a1, a2), false if computing glb(a1, a2)
+   * @return the unique element of outset; issues an error if outset.size() != 1
+   */
+  private AnnotationMirror requireSingleton(
+      Set<AnnotationMirror> outset, AnnotationMirror a1, AnnotationMirror a2, boolean lub) {
+    if (outset.size() == 0) {
+      throw new BugInCF(
+          "MultiGraphQualifierHierarchy could not determine "
+              + (lub ? "LUB" : "GLB")
+              + " for "
+              + a1
+              + " and "
+              + a2
+              + ". Please ensure that the checker knows about all type qualifiers.");
+    } else if (outset.size() == 1) {
+      return outset.iterator().next();
+    } else {
+      // outset.size() > 1
+
+      outset = lub ? findSmallestTypes(outset) : findGreatestTypes(outset);
+
+      AnnotationMirror result = null;
+      for (AnnotationMirror anno : outset) {
+        if (isPolymorphicQualifier(anno)) {
+          continue;
+        } else if (result == null) {
+          result = anno;
+        } else {
+          throw new BugInCF(
+              "Bug in checker implementation:  type hierarchy is not a lattice.%n"
+                  + "There is no unique "
+                  + (lub ? "lub" : "glb")
+                  + "(%s, %s).%n"
+                  + "Two incompatible candidates are: %s %s",
+              a1,
+              a2,
+              result,
+              anno);
+        }
+      }
+      return result;
+    }
+  }
+
+  /** Two annotations; used for caching the result of calls to lub and glb. */
+  private static class AnnotationPair {
+    /** The first annotation. */
+    public final AnnotationMirror a1;
+    /** The second annotation. */
+    public final AnnotationMirror a2;
+    /** The cached hashCode of this; -1 until computed. */
+    private int hashCode = -1;
+
+    /** Create a new AnnotationPair. */
+    public AnnotationPair(AnnotationMirror a1, AnnotationMirror a2) {
+      this.a1 = a1;
+      this.a2 = a2;
+    }
+
+    @Pure
+    @Override
+    public int hashCode() {
+      if (hashCode == -1) {
+        hashCode =
+            Objects.hash(AnnotationUtils.annotationName(a1), AnnotationUtils.annotationName(a2));
+      }
+      return hashCode;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+      if (!(o instanceof AnnotationPair)) {
+        return false;
+      }
+      AnnotationPair other = (AnnotationPair) o;
+      if (AnnotationUtils.areSameByName(a1, other.a1)
+          && AnnotationUtils.areSameByName(a2, other.a2)) {
+        return true;
+      }
+      if (AnnotationUtils.areSameByName(a2, other.a1)
+          && AnnotationUtils.areSameByName(a1, other.a2)) {
+        return true;
+      }
+      return false;
+    }
+
+    @SideEffectFree
+    @Override
+    public String toString() {
+      return "AnnotationPair(" + a1 + ", " + a2 + ")";
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/OptionConfiguration.java b/framework/src/main/java/org/checkerframework/framework/util/OptionConfiguration.java
new file mode 100644
index 0000000..9bf584d
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/OptionConfiguration.java
@@ -0,0 +1,53 @@
+package org.checkerframework.framework.util;
+
+import java.util.Map;
+import java.util.Set;
+
+/** Provides methods for querying the Checker's options. */
+public interface OptionConfiguration {
+
+  Map<String, String> getOptions();
+
+  /**
+   * Check whether the given option is provided.
+   *
+   * @param name the name of the option to check
+   * @return true if the option name was provided, false otherwise
+   */
+  boolean hasOption(String name);
+
+  /**
+   * Determines the value of the option with the given name.
+   *
+   * @param name the name of the option to check
+   */
+  String getOption(String name);
+
+  /**
+   * Determines the boolean value of the option with the given name. Returns {@code defaultValue} if
+   * the option is not set.
+   *
+   * @param name the name of the option to check
+   * @param defaultValue the default value to return if the option is not set
+   */
+  String getOption(String name, String defaultValue);
+
+  /**
+   * Determines the boolean value of the option with the given name. Returns false if the option is
+   * not set.
+   *
+   * @param name the name of the option to check
+   */
+  boolean getBooleanOption(String name);
+
+  /**
+   * Determines the boolean value of the option with the given name. Returns the given default value
+   * if the option is not set.
+   *
+   * @param name the name of the option to check
+   * @param defaultValue the default value to use if the option is not set
+   */
+  boolean getBooleanOption(String name, boolean defaultValue);
+
+  Set<String> getSupportedOptions();
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/PurityAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/util/PurityAnnotatedTypeFactory.java
new file mode 100644
index 0000000..ff62ab4
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/PurityAnnotatedTypeFactory.java
@@ -0,0 +1,23 @@
+package org.checkerframework.framework.util;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.PurityUnqualified;
+
+/** AnnotatedTypeFactory for the {@link PurityChecker}. */
+public class PurityAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  public PurityAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<>(Arrays.asList(PurityUnqualified.class));
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java b/framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java
new file mode 100644
index 0000000..6d50fac
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java
@@ -0,0 +1,15 @@
+package org.checkerframework.framework.util;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+/**
+ * Perform purity checking only.
+ *
+ * @checker_framework.manual #type-refinement-purity Side effects, determinism, purity, and
+ *     flow-sensitive analysis
+ */
+public class PurityChecker extends BaseTypeChecker {
+  // There is no implementation here.
+  // It uses functionality from BaseTypeChecker, which itself calls
+  // dataflow's purity implementation.
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/QualifierKind.java b/framework/src/main/java/org/checkerframework/framework/util/QualifierKind.java
new file mode 100644
index 0000000..5ad5bab
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/QualifierKind.java
@@ -0,0 +1,124 @@
+package org.checkerframework.framework.util;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.qual.AnnotatedFor;
+
+/**
+ * Represents a kind of qualifier, which is an annotation class. If two qualifiers use the same
+ * annotation class, then they have the same qualifier kind. Two qualifiers can have the same "kind"
+ * of qualifier but not be the same qualifier; an example is {@code @IndexFor("a")} and
+ * {@code @IndexFor("b")}.
+ *
+ * <p>A {@code QualifierKind} holds information about the relationship between itself and other
+ * {@link QualifierKind}s.
+ *
+ * <p>Exactly one qualifier kind is created for each annotation class.
+ *
+ * <p>The set of all QualifierKinds for a checker is like an enum. One QualifierKind is like an enum
+ * constant in that they are immutable after initialization and only a finite number exist per type
+ * system.
+ */
+@AnnotatedFor("nullness")
+public @Interned interface QualifierKind extends Comparable<QualifierKind> {
+
+  /**
+   * Returns the canonical name of the annotation class of this.
+   *
+   * @return the canonical name of the annotation class of this
+   */
+  @Interned @CanonicalName String getName();
+
+  /**
+   * Returns the annotation class for this.
+   *
+   * @return the annotation class for this
+   */
+  Class<? extends Annotation> getAnnotationClass();
+
+  /**
+   * Returns the top qualifier kind of the hierarchy to which this qualifier kind belongs.
+   *
+   * @return the top qualifier kind of the hierarchy to which this qualifier kind belongs
+   */
+  QualifierKind getTop();
+
+  /**
+   * Returns true if this is the top qualifier of its hierarchy.
+   *
+   * @return true if this is the top qualifier of its hierarchy
+   */
+  boolean isTop();
+
+  /**
+   * Returns the bottom qualifier kind of the hierarchy to which this qualifier kind belongs.
+   *
+   * @return the bottom qualifier kind of the hierarchy to which this qualifier kind belongs
+   */
+  QualifierKind getBottom();
+
+  /**
+   * Returns true if this is the bottom qualifier of its hierarchy.
+   *
+   * @return true if this is the bottom qualifier of its hierarchy
+   */
+  boolean isBottom();
+
+  /**
+   * Returns the polymorphic qualifier kind of the hierarchy to which this qualifier kind belongs,
+   * or null if one does not exist.
+   *
+   * @return the polymorphic qualifier kind of the hierarchy to which this qualifier kind belongs,
+   *     or null if one does not exist
+   */
+  @Nullable QualifierKind getPolymorphic();
+
+  /**
+   * Returns true if this is polymorphic.
+   *
+   * @return true if this is polymorphic
+   */
+  @Pure
+  boolean isPoly();
+
+  /**
+   * Returns true if the annotation class this qualifier kind represents has annotation
+   * elements/arguments.
+   *
+   * @return true if the annotation class this qualifier kind represents has elements/arguments
+   */
+  boolean hasElements();
+
+  /**
+   * All the qualifier kinds that are a strict super qualifier of this qualifier. Does not include
+   * this qualifier kind itself.
+   *
+   * @return all the qualifier kinds that are a strict super qualifier of this qualifier
+   */
+  Set<? extends QualifierKind> getStrictSuperTypes();
+
+  /**
+   * Returns true if this and {@code other} are in the same hierarchy.
+   *
+   * @param other a qualifier kind
+   * @return true if this and {@code other} are in the same hierarchy
+   */
+  boolean isInSameHierarchyAs(QualifierKind other);
+
+  /**
+   * Returns true if this qualifier kind is a subtype of or equal to {@code superQualKind}.
+   *
+   * @param superQualKind other qualifier kind
+   * @return true if this qualifier kind is a subtype of or equal to {@code superQualKind}
+   */
+  boolean isSubtypeOf(QualifierKind superQualKind);
+
+  @Override
+  default int compareTo(QualifierKind o) {
+    return this.getName().compareTo(o.getName());
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/QualifierKindHierarchy.java b/framework/src/main/java/org/checkerframework/framework/util/QualifierKindHierarchy.java
new file mode 100644
index 0000000..b23d781
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/QualifierKindHierarchy.java
@@ -0,0 +1,106 @@
+package org.checkerframework.framework.util;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.type.ElementQualifierHierarchy;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.type.NoElementQualifierHierarchy;
+import org.checkerframework.javacutil.TypeSystemError;
+
+/**
+ * This interface holds information about the subtyping relationships between kinds of qualifiers. A
+ * "kind" of qualifier is its annotation class and is represented by the {@link QualifierKind}
+ * class. If a type system has more than one hierarchy, information about all hierarchies is stored
+ * in this class.
+ *
+ * <p>The qualifier kind subtyping relationship may be an over-approximation of the qualifier
+ * subtyping relationship, for qualifiers that have elements/arguments. In other words, if a
+ * qualifier kind is a subtype of another qualifier kind, then qualifiers of those kinds may or may
+ * not be subtypes, depending on the values of any elements of the qualifiers. If qualifier kinds
+ * are not subtypes, then qualifiers of those kinds are never subtypes.
+ *
+ * <p>This interface is used by {@link NoElementQualifierHierarchy} and {@link
+ * ElementQualifierHierarchy} (but <em>not</em> {@link MostlyNoElementQualifierHierarchy}) to
+ * implement methods that compare {@link javax.lang.model.element.AnnotationMirror}s, such as {@link
+ * org.checkerframework.framework.type.QualifierHierarchy#isSubtype(AnnotationMirror,
+ * AnnotationMirror)}.
+ *
+ * @see DefaultQualifierKindHierarchy
+ * @see org.checkerframework.framework.util.DefaultQualifierKindHierarchy.DefaultQualifierKind
+ */
+@AnnotatedFor("nullness")
+public interface QualifierKindHierarchy {
+
+  /**
+   * Returns the qualifier kinds that are the top qualifier in their hierarchies.
+   *
+   * @return the qualifier kinds that are the top qualifier in their hierarchies
+   */
+  Set<? extends QualifierKind> getTops();
+
+  /**
+   * Returns the qualifier kinds that are the bottom qualifier in their hierarchies.
+   *
+   * @return the qualifier kinds that are the bottom qualifier in their hierarchies
+   */
+  Set<? extends QualifierKind> getBottoms();
+
+  /**
+   * Returns the least upper bound of {@code q1} and {@code q2}, or {@code null} if the qualifier
+   * kinds are not in the same hierarchy. Ignores elements/arguments (as QualifierKind always does).
+   *
+   * @param q1 a qualifier kind
+   * @param q2 a qualifier kind
+   * @return the least upper bound of {@code q1} and {@code q2}, or {@code null} if the qualifier
+   *     kinds are not in the same hierarchy
+   */
+  @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2);
+
+  /**
+   * Returns the greatest lower bound of {@code q1} and {@code q2}, or {@code null} if the qualifier
+   * kinds are not in the same hierarchy. Ignores elements/arguments (as QualifierKind always does).
+   *
+   * @param q1 a qualifier kind
+   * @param q2 a qualifier kind
+   * @return the greatest lower bound of {@code q1} and {@code q2}, or {@code null} if the qualifier
+   *     kinds are not in the same hierarchy
+   */
+  @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2);
+
+  /**
+   * Returns a list of all {@link QualifierKind}s sorted in ascending order.
+   *
+   * @return a list of all {@link QualifierKind}s sorted in ascending order
+   */
+  List<? extends QualifierKind> allQualifierKinds();
+
+  /**
+   * Returns the {@link QualifierKind} for the given annotation class name, or null if one does not
+   * exist.
+   *
+   * @param name canonical name of an annotation class
+   * @return the {@link QualifierKind} for the given annotation class name, or null if one does not
+   *     exist
+   */
+  @Nullable QualifierKind getQualifierKind(@CanonicalName String name);
+
+  /**
+   * Returns the canonical name of {@code clazz}. Throws a {@link TypeSystemError} if {@code clazz}
+   * is anonymous or otherwise does not have a name.
+   *
+   * @param clazz annotation class
+   * @return the canonical name of {@code clazz}
+   */
+  static @CanonicalName String annotationClassName(Class<? extends Annotation> clazz) {
+    String name = clazz.getCanonicalName();
+    if (name == null) {
+      throw new TypeSystemError("Qualifier classes must not be anonymous.");
+    }
+    return name;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/StringToJavaExpression.java b/framework/src/main/java/org/checkerframework/framework/util/StringToJavaExpression.java
new file mode 100644
index 0000000..2c91a25
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/StringToJavaExpression.java
@@ -0,0 +1,357 @@
+package org.checkerframework.framework.util;
+
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import java.util.ArrayList;
+import java.util.List;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.expression.FormalParameter;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.ThisReference;
+import org.checkerframework.dataflow.expression.ViewpointAdaptJavaExpression;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * This interface is both a functional interface, see {@link #toJavaExpression(String)}, and also a
+ * collection of static methods that convert a string to a JavaExpression at common locations.
+ *
+ * <p>Some conversion routines merely do parsing. Other conversion routines parse and then transform
+ * the result of parsing into another {@code JavaExpression}; for all the static methods, this
+ * transformation is viewpoint-adaptation.
+ *
+ * <p>To parse a string "at a location" means to parse it as if it were written in an annotation
+ * that is written on that location.
+ */
+@FunctionalInterface
+public interface StringToJavaExpression {
+
+  /**
+   * Convert a string to a {@link JavaExpression}. Returns {@code null} if no conversion exists.
+   *
+   * <p>Conversion includes parsing {@code stringExpr} to a {@code JavaExpression} and optionally
+   * transforming the result of parsing into another {@code JavaExpression}. An example of
+   * transformation is viewpoint adaptation.
+   *
+   * @param stringExpr a Java expression
+   * @return a {@code JavaExpression} or {@code null} if no conversion from {@code stringExpr}
+   *     exists
+   * @throws JavaExpressionParseException if {@code stringExpr} cannot be parsed to a {@code
+   *     JavaExpression}
+   */
+  @Nullable JavaExpression toJavaExpression(String stringExpr) throws JavaExpressionParseException;
+
+  /**
+   * Parses a string to a {@link JavaExpression} as if it were written at {@code typeElement}.
+   *
+   * @param expression a Java expression to parse
+   * @param typeElement type element at which {@code expression} is parsed
+   * @param checker checker used to get the {@link
+   *     javax.annotation.processing.ProcessingEnvironment} and current {@link
+   *     com.sun.source.tree.CompilationUnitTree}
+   * @return a {@code JavaExpression} for {@code expression}
+   * @throws JavaExpressionParseException if {@code expression} cannot be parsed
+   */
+  static JavaExpression atTypeDecl(
+      String expression, TypeElement typeElement, SourceChecker checker)
+      throws JavaExpressionParseException {
+    ThisReference thisReference = new ThisReference(typeElement.asType());
+    List<FormalParameter> parameters = null;
+    return JavaExpressionParseUtil.parse(
+        expression,
+        typeElement.asType(),
+        thisReference,
+        parameters,
+        null,
+        checker.getPathToCompilationUnit(),
+        checker.getProcessingEnvironment());
+  }
+
+  /**
+   * Parses a string to a {@link JavaExpression} as if it were written at {@code fieldElement}.
+   *
+   * @param expression a Java expression to parse
+   * @param fieldElement variable element at which {@code expression} is parsed
+   * @param checker checker used to get the {@link
+   *     javax.annotation.processing.ProcessingEnvironment} and current {@link
+   *     com.sun.source.tree.CompilationUnitTree}
+   * @return a {@code JavaExpression} for {@code expression}
+   * @throws JavaExpressionParseException if {@code expression} cannot be parsed
+   */
+  static JavaExpression atFieldDecl(
+      String expression, VariableElement fieldElement, SourceChecker checker)
+      throws JavaExpressionParseException {
+    TypeMirror enclosingType = ElementUtils.enclosingTypeElement(fieldElement).asType();
+    ThisReference thisReference;
+    if (ElementUtils.isStatic(fieldElement)) {
+      // Can't use "this" on a static fieldElement
+      thisReference = null;
+    } else {
+      thisReference = new ThisReference(enclosingType);
+    }
+    List<FormalParameter> parameters = null;
+    return JavaExpressionParseUtil.parse(
+        expression,
+        enclosingType,
+        thisReference,
+        parameters,
+        null,
+        checker.getPathToCompilationUnit(),
+        checker.getProcessingEnvironment());
+  }
+
+  /**
+   * Parses a string to a {@link JavaExpression} as if it were written at {@code method}. The
+   * returned {@code JavaExpression} uses {@link FormalParameter}s to represent parameters. Use
+   * {@link #atMethodBody(String, MethodTree, SourceChecker)} if parameters should be {@link
+   * LocalVariable}s instead.
+   *
+   * @param expression a Java expression to parse
+   * @param method method element at which {@code expression} is parsed
+   * @param checker checker used to get the {@link
+   *     javax.annotation.processing.ProcessingEnvironment} and current {@link
+   *     com.sun.source.tree.CompilationUnitTree}
+   * @return a {@code JavaExpression} for {@code expression}
+   * @throws JavaExpressionParseException if {@code expression} cannot be parsed
+   */
+  static JavaExpression atMethodDecl(
+      String expression, ExecutableElement method, SourceChecker checker)
+      throws JavaExpressionParseException {
+    TypeMirror enclosingType = ElementUtils.enclosingTypeElement(method).asType();
+    ThisReference thisReference;
+    if (ElementUtils.isStatic(method)) {
+      // Can't use "this" on a static method
+      thisReference = null;
+    } else {
+      thisReference = new ThisReference(enclosingType);
+    }
+    List<FormalParameter> parameters = JavaExpression.getFormalParameters(method);
+    return JavaExpressionParseUtil.parse(
+        expression,
+        enclosingType,
+        thisReference,
+        parameters,
+        null,
+        checker.getPathToCompilationUnit(),
+        checker.getProcessingEnvironment());
+  }
+
+  /**
+   * Parses a string to a {@link JavaExpression} as if it were written at {@code methodTree}. The
+   * returned {@code JavaExpression} uses {@link LocalVariable}s to represent parameters. Use {@link
+   * #atMethodDecl(String, ExecutableElement, SourceChecker)} if parameters should be {@link
+   * FormalParameter}s instead.
+   *
+   * @param expression a Java expression to parse
+   * @param methodTree method declaration tree at which {@code expression} is parsed
+   * @param checker checker used to get the {@link
+   *     javax.annotation.processing.ProcessingEnvironment} and current {@link
+   *     com.sun.source.tree.CompilationUnitTree}
+   * @return a {@code JavaExpression} for {@code expression}
+   * @throws JavaExpressionParseException if {@code expression} cannot be parsed
+   */
+  static JavaExpression atMethodBody(
+      String expression, MethodTree methodTree, SourceChecker checker)
+      throws JavaExpressionParseException {
+    ExecutableElement ee = TreeUtils.elementFromDeclaration(methodTree);
+    JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker);
+    return javaExpr.atMethodBody(methodTree);
+  }
+
+  /**
+   * Parses a string as if it were written at the declaration of the invoked method and then
+   * viewpoint-adapts the result to the call site.
+   *
+   * @param expression a Java expression to parse
+   * @param methodInvocationTree method invocation tree
+   * @param checker checker used to get the {@link
+   *     javax.annotation.processing.ProcessingEnvironment} and current {@link
+   *     com.sun.source.tree.CompilationUnitTree}
+   * @return a {@code JavaExpression} for {@code expression}
+   * @throws JavaExpressionParseException if {@code expression} cannot be parsed
+   */
+  static JavaExpression atMethodInvocation(
+      String expression, MethodInvocationTree methodInvocationTree, SourceChecker checker)
+      throws JavaExpressionParseException {
+    ExecutableElement ee = TreeUtils.elementFromUse(methodInvocationTree);
+    JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker);
+    return javaExpr.atMethodInvocation(methodInvocationTree);
+  }
+
+  /**
+   * Parses a string as if it were written at the declaration of the invoked method and then
+   * viewpoint-adapts the result to the call site.
+   *
+   * @param expression a Java expression to parse
+   * @param methodInvocationNode method invocation node
+   * @param checker checker used to get the {@link
+   *     javax.annotation.processing.ProcessingEnvironment} and current {@link
+   *     com.sun.source.tree.CompilationUnitTree}
+   * @return a {@code JavaExpression} for {@code expression}
+   * @throws JavaExpressionParseException if {@code expression} cannot be parsed
+   */
+  static JavaExpression atMethodInvocation(
+      String expression, MethodInvocationNode methodInvocationNode, SourceChecker checker)
+      throws JavaExpressionParseException {
+    ExecutableElement ee = TreeUtils.elementFromUse(methodInvocationNode.getTree());
+    JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker);
+    return javaExpr.atMethodInvocation(methodInvocationNode);
+  }
+
+  /**
+   * Parses a string as if it were written at the declaration of the invoked constructor and then
+   * viewpoint-adapts the result to the call site.
+   *
+   * @param expression a Java expression to parse
+   * @param newClassTree constructor invocation
+   * @param checker checker used to get the {@link
+   *     javax.annotation.processing.ProcessingEnvironment} and current {@link
+   *     com.sun.source.tree.CompilationUnitTree}
+   * @return a {@code JavaExpression} for {@code expression}
+   * @throws JavaExpressionParseException if {@code expression} cannot be parsed
+   */
+  static JavaExpression atConstructorInvocation(
+      String expression, NewClassTree newClassTree, SourceChecker checker)
+      throws JavaExpressionParseException {
+    ExecutableElement ee = TreeUtils.elementFromUse(newClassTree);
+    JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker);
+    return javaExpr.atConstructorInvocation(newClassTree);
+  }
+
+  /**
+   * uf found Parses a string as if it were written at the declaration of the field and then
+   * viewpoint-adapts the result to the use.
+   *
+   * @param expression a Java expression to parse
+   * @param fieldAccess the field access tree
+   * @param checker checker used to get the {@link
+   *     javax.annotation.processing.ProcessingEnvironment} and current {@link
+   *     com.sun.source.tree.CompilationUnitTree}
+   * @return a {@code JavaExpression} for {@code expression}
+   * @throws JavaExpressionParseException if {@code expression} cannot be parsed
+   */
+  static JavaExpression atFieldAccess(
+      String expression, MemberSelectTree fieldAccess, SourceChecker checker)
+      throws JavaExpressionParseException {
+
+    Element ele = TreeUtils.elementFromUse(fieldAccess);
+    if (ele.getKind() != ElementKind.FIELD && ele.getKind() != ElementKind.ENUM_CONSTANT) {
+      throw new BugInCF("Expected a field, but found %s for %s", ele.getKind(), fieldAccess);
+    }
+    VariableElement fieldEle = (VariableElement) ele;
+    JavaExpression receiver = JavaExpression.fromTree(fieldAccess.getExpression());
+    JavaExpression javaExpr = StringToJavaExpression.atFieldDecl(expression, fieldEle, checker);
+    return javaExpr.atFieldAccess(receiver);
+  }
+
+  /**
+   * Parses a string as if it were written at one of the parameters of {@code lambdaTree}.
+   * Parameters of the lambda are expressed as {@link LocalVariable}s.
+   *
+   * @param expression a Java expression to parse
+   * @param lambdaTree the lambda tree
+   * @param parentPath path to the parent of {@code lambdaTree}; required because the expression can
+   *     reference final local variables of the enclosing method
+   * @param checker checker used to get the {@link
+   *     javax.annotation.processing.ProcessingEnvironment} and current {@link
+   *     com.sun.source.tree.CompilationUnitTree}
+   * @return a {@code JavaExpression} for {@code expression}
+   * @throws JavaExpressionParseException if {@code expression} cannot be parsed
+   */
+  static JavaExpression atLambdaParameter(
+      String expression,
+      LambdaExpressionTree lambdaTree,
+      TreePath parentPath,
+      SourceChecker checker)
+      throws JavaExpressionParseException {
+
+    TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(parentPath));
+    JavaExpression receiver = JavaExpression.getPseudoReceiver(parentPath, enclosingType);
+    // If receiver isn't a ThisReference, then the lambda is in a static context and "this"
+    // cannot be referenced in the expression.
+    ThisReference thisReference =
+        receiver instanceof ThisReference ? (ThisReference) receiver : null;
+    List<JavaExpression> paramsAsLocals = new ArrayList<>(lambdaTree.getParameters().size());
+    List<FormalParameter> parameters = new ArrayList<>(lambdaTree.getParameters().size());
+    int oneBasedIndex = 1;
+    for (VariableTree arg : lambdaTree.getParameters()) {
+      LocalVariable param = (LocalVariable) JavaExpression.fromVariableTree(arg);
+      paramsAsLocals.add(param);
+      parameters.add(new FormalParameter(oneBasedIndex, (VariableElement) param.getElement()));
+      oneBasedIndex++;
+    }
+
+    JavaExpression javaExpr =
+        JavaExpressionParseUtil.parse(
+            expression,
+            enclosingType,
+            thisReference,
+            parameters,
+            parentPath,
+            checker.getPathToCompilationUnit(),
+            checker.getProcessingEnvironment());
+    return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals);
+  }
+
+  /**
+   * Parses a string as if it were written at {@code localVarPath}.
+   *
+   * @param expression a Java expression to parse
+   * @param localVarPath location at which {@code expression} is parsed
+   * @param checker checker used to get the {@link
+   *     javax.annotation.processing.ProcessingEnvironment} and current {@link
+   *     com.sun.source.tree.CompilationUnitTree}
+   * @return a {@code JavaExpression} for {@code expression}
+   * @throws JavaExpressionParseException if {@code expression} cannot be parsed
+   */
+  static JavaExpression atPath(String expression, TreePath localVarPath, SourceChecker checker)
+      throws JavaExpressionParseException {
+
+    TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(localVarPath));
+    ThisReference thisReference =
+        TreePathUtil.isTreeInStaticScope(localVarPath) ? null : new ThisReference(enclosingType);
+
+    MethodTree methodTree = TreePathUtil.enclosingMethod(localVarPath);
+    if (methodTree == null) {
+      return JavaExpressionParseUtil.parse(
+          expression,
+          enclosingType,
+          thisReference,
+          null,
+          localVarPath,
+          checker.getPathToCompilationUnit(),
+          checker.getProcessingEnvironment());
+    }
+
+    ExecutableElement methodEle = TreeUtils.elementFromDeclaration(methodTree);
+    List<FormalParameter> parameters = JavaExpression.getFormalParameters(methodEle);
+    JavaExpression javaExpr =
+        JavaExpressionParseUtil.parse(
+            expression,
+            enclosingType,
+            thisReference,
+            parameters,
+            localVarPath,
+            checker.getPathToCompilationUnit(),
+            checker.getProcessingEnvironment());
+    List<JavaExpression> paramsAsLocals = JavaExpression.getParametersAsLocalVariables(methodEle);
+    return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java b/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java
new file mode 100644
index 0000000..5f319cc
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java
@@ -0,0 +1,115 @@
+package org.checkerframework.framework.util;
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreeScanner;
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * TreePathCacher is a TreeScanner that creates and caches a TreePath for a target Tree.
+ *
+ * <p>This class replicates some logic from TreePath.getPath but also adds caching to all
+ * intermediate TreePaths that are generated. The intermediate TreePaths are reused when other
+ * targets have overlapping paths.
+ */
+public class TreePathCacher extends TreeScanner<TreePath, Tree> {
+
+  private final Map<Tree, TreePath> foundPaths = new HashMap<>(32);
+
+  /**
+   * The TreePath of the previous tree scanned. It is always set back to null after a scan has
+   * completed.
+   */
+  private TreePath path;
+
+  /**
+   * Returns true if the tree is cached.
+   *
+   * @param target the tree to search for
+   * @return true if the tree is cached
+   */
+  public boolean isCached(Tree target) {
+    return foundPaths.containsKey(target);
+  }
+
+  /**
+   * Adds the given key and value to the cache.
+   *
+   * @param target the tree to add
+   * @param path the path to cache
+   */
+  public void addPath(Tree target, TreePath path) {
+    foundPaths.put(target, path);
+  }
+
+  /**
+   * Return the TreePath for a Tree.
+   *
+   * @param root the compilation unit to search in
+   * @param target the target tree to look for
+   * @return the TreePath corresponding to target, or null if target is not found in the compilation
+   *     root
+   */
+  public @Nullable TreePath getPath(CompilationUnitTree root, @FindDistinct Tree target) {
+    // This method uses try/catch and the private {@code Result} exception for control flow to
+    // stop the superclass from scanning other subtrees when target is found.
+
+    if (foundPaths.containsKey(target)) {
+      return foundPaths.get(target);
+    }
+
+    TreePath path = new TreePath(root);
+    if (path.getLeaf() == target) {
+      return path;
+    }
+
+    try {
+      this.scan(path, target);
+    } catch (Result result) {
+      return result.path;
+    }
+    // If a path wasn't found, cache null so the whole compilation unit isn't searched again.
+    foundPaths.put(target, null);
+    return null;
+  }
+
+  private static class Result extends Error {
+    private static final long serialVersionUID = 4948452207518392627L;
+    TreePath path;
+
+    Result(TreePath path) {
+      this.path = path;
+    }
+  }
+
+  public void clear() {
+    foundPaths.clear();
+  }
+
+  /** Scan a single node. The current path is updated for the duration of the scan. */
+  @SuppressWarnings("interning:not.interned") // assertion
+  @Override
+  public TreePath scan(Tree tree, Tree target) {
+    TreePath prev = path;
+    if (tree != null && foundPaths.get(tree) == null) {
+      TreePath current = new TreePath(path, tree);
+      foundPaths.put(tree, current);
+      path = current;
+    } else {
+      this.path = foundPaths.get(tree);
+    }
+
+    if (tree == target) {
+      throw new Result(path);
+    }
+    try {
+      return super.scan(tree, target);
+    } finally {
+      this.path = prev;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/TypeArgumentMapper.java b/framework/src/main/java/org/checkerframework/framework/util/TypeArgumentMapper.java
new file mode 100644
index 0000000..c2fff6a
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/TypeArgumentMapper.java
@@ -0,0 +1,341 @@
+package org.checkerframework.framework.util;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+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.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * Records any mapping between the type parameters of a subtype to the corresponding type parameters
+ * of a supertype. For example, suppose we have the following classes:
+ *
+ * <pre>{@code
+ * class Map<M1,M2>
+ * class HashMap<H1, H2> extends Map<H1,H2>
+ * }</pre>
+ *
+ * And we pass HashMap and Map to mapTypeArguments, the result would be:
+ *
+ * <pre>{@code
+ * Map(H1 => M1, H2 => M2)
+ * }</pre>
+ *
+ * Note, a single type argument in the subtype can map to multiple type parameters in the supertype.
+ * e.g.,
+ *
+ * <pre>{@code
+ * class OneTypeMap<O1> extends Map<O1,O1>
+ * }</pre>
+ *
+ * would have the result:
+ *
+ * <pre>{@code
+ * Map(O1 => [M1,M2])
+ * }</pre>
+ *
+ * This utility only maps between corresponding type parameters, so the following class:
+ *
+ * <pre>{@code
+ * class StringMap extends Map<String,String>
+ * }</pre>
+ *
+ * would have an empty map as a result:
+ *
+ * <pre>{@code
+ * Map() // there are no type argument relationships between the two types
+ * }</pre>
+ */
+public class TypeArgumentMapper {
+
+  /**
+   * Returns a mapping from subtype's type parameter indices to the indices of corresponding type
+   * parameters in supertype.
+   */
+  public static Set<Pair<Integer, Integer>> mapTypeArgumentIndices(
+      final TypeElement subtype, final TypeElement supertype, final Types types) {
+    Set<Pair<Integer, Integer>> result = new HashSet<>();
+    if (subtype.equals(supertype)) {
+      for (int i = 0; i < subtype.getTypeParameters().size(); i++) {
+        result.add(Pair.of(Integer.valueOf(i), Integer.valueOf(i)));
+      }
+
+    } else {
+      Map<TypeParameterElement, Set<TypeParameterElement>> subToSuperElements =
+          mapTypeArguments(subtype, supertype, types);
+      Map<TypeParameterElement, Integer> supertypeIndexes = getElementToIndex(supertype);
+
+      final List<? extends TypeParameterElement> subtypeParams = subtype.getTypeParameters();
+      for (int subtypeIndex = 0; subtypeIndex < subtypeParams.size(); subtypeIndex++) {
+        final TypeParameterElement subtypeParam = subtypeParams.get(subtypeIndex);
+
+        final Set<TypeParameterElement> correspondingSuperArgs =
+            subToSuperElements.get(subtypeParam);
+        if (correspondingSuperArgs != null) {
+          for (TypeParameterElement supertypeParam : subToSuperElements.get(subtypeParam)) {
+            result.add(Pair.of(subtypeIndex, supertypeIndexes.get(supertypeParam)));
+          }
+        }
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns a Map(type parameter symbol &rarr; index in type parameter list).
+   *
+   * @param typeElement a type whose type parameters to summarize
+   * @return a Map(type parameter symbol &rarr; index in type parameter list)
+   */
+  private static Map<TypeParameterElement, Integer> getElementToIndex(TypeElement typeElement) {
+    Map<TypeParameterElement, Integer> result = new LinkedHashMap<>();
+
+    List<? extends TypeParameterElement> params = typeElement.getTypeParameters();
+    for (int i = 0; i < params.size(); i++) {
+      result.put(params.get(i), Integer.valueOf(i));
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns a mapping from the type parameters of subtype to a list of the type parameters in
+   * supertype that must be the same type as subtype.
+   *
+   * <p>e.g.,
+   *
+   * <pre>{@code
+   * class A<A1,A2,A3>
+   * class B<B1,B2,B3,B4> extends A<B1,B1,B3> {}
+   * }</pre>
+   *
+   * results in a {@code Map(B1 => [A1,A2], B2 => [], B3 => [A3], B4 => [])}
+   *
+   * @return a mapping from the type parameters of subtype to the supertype type parameter's that to
+   *     which they are a type argument
+   */
+  public static Map<TypeParameterElement, Set<TypeParameterElement>> mapTypeArguments(
+      final TypeElement subtype, final TypeElement supertype, final Types types) {
+
+    final List<TypeRecord> pathToSupertype =
+        depthFirstSearchForSupertype(subtype, supertype, types);
+
+    if (pathToSupertype == null || pathToSupertype.isEmpty()) {
+      return new LinkedHashMap<>();
+    }
+
+    final Map<TypeParameterElement, Set<TypeParameterElement>> intermediate = new LinkedHashMap<>();
+    final Set<TypeParameterElement> currentTypeParams = new HashSet<>();
+
+    // takes a type records of the form:
+    //  TypeRecord(element = MyMap<Y1,Y2>, type = null)
+    //  TypeRecord(element = AbstractMap<A1,A2>, type = AbstractMap<Y1,Y2>)
+    //  TypeRecord(element = Map<M1,M2>, type = AbstractMap<A1,A2>)
+    // And makes a map:
+    //   Map(Y1 -> [A1], Y2 -> [A2], A1 -> [M1], A2 -> M2]
+    Iterator<TypeRecord> path = pathToSupertype.iterator();
+    TypeRecord current = path.next();
+    while (path.hasNext()) {
+      TypeRecord next = path.next();
+
+      final List<? extends TypeParameterElement> nextTypeParameter =
+          next.element.getTypeParameters();
+      final List<? extends TypeMirror> nextTypeArgs =
+          next.type != null ? next.type.getTypeArguments() : Collections.emptyList();
+      currentTypeParams.clear();
+      currentTypeParams.addAll(current.element.getTypeParameters());
+
+      for (int i = 0; i < nextTypeArgs.size(); i++) {
+        final TypeParameterElement correspondingParameter = nextTypeParameter.get(i);
+        final TypeMirror typeArg = nextTypeArgs.get(i);
+        final Element typeArgEle = types.asElement(typeArg);
+
+        if (currentTypeParams.contains(typeArgEle)) {
+          addToSetMap(intermediate, (TypeParameterElement) typeArgEle, correspondingParameter);
+        }
+      }
+    }
+
+    List<? extends TypeParameterElement> supertypeParams = supertype.getTypeParameters();
+    final Map<TypeParameterElement, Set<TypeParameterElement>> result =
+        new LinkedHashMap<>(subtype.getTypeParameters().size());
+
+    // You can think of the map above as a set of links from SubtypeParameter -> Supertype Parameter
+    for (TypeParameterElement subtypeParam : subtype.getTypeParameters()) {
+      Set<TypeParameterElement> subtypePath =
+          flattenPath(intermediate.get(subtypeParam), intermediate);
+      subtypePath.retainAll(supertypeParams);
+      result.put(subtypeParam, subtypePath);
+    }
+
+    return result;
+  }
+
+  private static Set<TypeParameterElement> flattenPath(
+      Set<TypeParameterElement> elements,
+      Map<TypeParameterElement, Set<TypeParameterElement>> map) {
+    Set<TypeParameterElement> result = new HashSet<>();
+    if (elements == null) {
+      return result;
+    }
+    for (final TypeParameterElement oldElement : elements) {
+      Set<TypeParameterElement> substitutions = map.get(oldElement);
+      if (substitutions != null) {
+        result.addAll(flattenPath(elements, map));
+      } else {
+        result.add(oldElement);
+      }
+    }
+    return result;
+  }
+
+  private static void addToSetMap(
+      final Map<TypeParameterElement, Set<TypeParameterElement>> setMap,
+      final TypeParameterElement element,
+      final TypeParameterElement typeParam) {
+    Set<TypeParameterElement> set = setMap.computeIfAbsent(element, __ -> new HashSet<>());
+    set.add(typeParam);
+  }
+
+  /**
+   * Create a list of TypeRecord's that form a "path" to target from subtype. e.g. Suppose I have
+   * the types
+   *
+   * <pre>{@code
+   * interface Map<M1,M2>
+   * class AbstractMap<A1,A2> implements Map<A1,A2>, Iterable<Map.Entry<M1,M2>>
+   * class MyMap<Y1,Y2> extends AbstractMap<Y1,Y2> implements List<Map.Entry<Y1,Y2>>
+   * }</pre>
+   *
+   * The path from MyMap to Map would be:
+   *
+   * <pre>{@code
+   * TypeRecord(element = MyMap<Y1,Y2>, type = null)
+   * TypeRecord(element = AbstractMap<A1,A2>, type = AbstractMap<Y1,Y2>)
+   * TypeRecord(element = Map<M1,M2>, type = AbstractMap<A1,A2>)
+   * }</pre>
+   *
+   * Note: You can have an implementation of the same interface inherited multiple times as long as
+   * the parameterization of that interface remains the same e.g.
+   *
+   * <pre>{@code
+   * interface List<E>
+   * class AbstractList<A> implements List<E>
+   * class ArrayList<T> extends AbstractList<T> implements List<T>
+   * }</pre>
+   *
+   * Notice how ArrayList implements list both by inheriting from AbstractList and from explicitly
+   * listing it in the implements clause. We prioritize finding a path through the list of
+   * interfaces first since this will be the shorter path.
+   *
+   * @param subtype the start of the resulting sequence
+   * @param target the end of the resulting sequence
+   * @param types utility methods for operating on types
+   * @return a list of type records that represents the sequence of directSupertypes between subtype
+   *     and target
+   */
+  private static List<TypeRecord> depthFirstSearchForSupertype(
+      final TypeElement subtype, final TypeElement target, final Types types) {
+    ArrayDeque<TypeRecord> pathFromRoot = new ArrayDeque<>();
+    final TypeRecord pathStart = new TypeRecord(subtype, null);
+    pathFromRoot.push(pathStart);
+    final List<TypeRecord> result = recursiveDepthFirstSearch(pathFromRoot, target, types);
+    return result;
+  }
+
+  /**
+   * Computes one level for depthFirstSearchForSupertype then recurses.
+   *
+   * @param pathFromRoot the path so far
+   * @param target the end of the resulting path
+   * @param types utility methods for operating on types
+   * @return a list of type records that extends pathFromRoot (a sequence of directSupertypes) to
+   *     target
+   */
+  private static List<TypeRecord> recursiveDepthFirstSearch(
+      final ArrayDeque<TypeRecord> pathFromRoot, final TypeElement target, final Types types) {
+    if (pathFromRoot.isEmpty()) {
+      return null;
+    }
+
+    final TypeRecord currentRecord = pathFromRoot.peekLast();
+    final TypeElement currentElement = currentRecord.element;
+
+    if (currentElement.equals(target)) {
+      return new ArrayList<>(pathFromRoot);
+    }
+
+    final Iterator<? extends TypeMirror> interfaces = currentElement.getInterfaces().iterator();
+    final TypeMirror superclassType = currentElement.getSuperclass();
+
+    List<TypeRecord> path = null;
+
+    while (path == null && interfaces.hasNext()) {
+      final TypeMirror intface = interfaces.next();
+      if (intface.getKind() != TypeKind.NONE) {
+        DeclaredType interfaceDeclared = (DeclaredType) intface;
+        pathFromRoot.addLast(
+            new TypeRecord((TypeElement) types.asElement(interfaceDeclared), interfaceDeclared));
+        path = recursiveDepthFirstSearch(pathFromRoot, target, types);
+        pathFromRoot.removeLast();
+      }
+    }
+
+    if (path == null && superclassType.getKind() != TypeKind.NONE) {
+      final DeclaredType superclass = (DeclaredType) superclassType;
+
+      pathFromRoot.addLast(new TypeRecord((TypeElement) types.asElement(superclass), superclass));
+      path = recursiveDepthFirstSearch(pathFromRoot, target, types);
+      pathFromRoot.removeLast();
+    }
+
+    return path;
+  }
+
+  /**
+   * Maps a class or interface's declaration element to the type it would be if viewed from a
+   * subtype class or interface.
+   *
+   * <p>e.g. suppose we have the elements for the declarations:
+   *
+   * <pre>{@code
+   * class A<Ta>
+   * class B<Tb> extends A<Tb>
+   * }</pre>
+   *
+   * The type record of B if it is viewed as class A would bed:
+   *
+   * <pre>{@code
+   * TypeRecord( element = A<Ta>, type = A<Tb> )
+   * }</pre>
+   *
+   * That is, B can be viewed as an object of type A with an type argument of type parameter Tb
+   */
+  private static class TypeRecord {
+    public final TypeElement element;
+    public final DeclaredType type;
+
+    TypeRecord(final TypeElement element, final DeclaredType type) {
+      this.element = element;
+      this.type = type;
+    }
+
+    @Override
+    public String toString() {
+      return String.format("[%s => %s]", element, type);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/VoidVisitorWithDefaultAction.java b/framework/src/main/java/org/checkerframework/framework/util/VoidVisitorWithDefaultAction.java
new file mode 100644
index 0000000..bbde91e
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/VoidVisitorWithDefaultAction.java
@@ -0,0 +1,693 @@
+package org.checkerframework.framework.util;
+
+import com.github.javaparser.ast.ArrayCreationLevel;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.ImportDeclaration;
+import com.github.javaparser.ast.Modifier;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.PackageDeclaration;
+import com.github.javaparser.ast.StubUnit;
+import com.github.javaparser.ast.body.AnnotationDeclaration;
+import com.github.javaparser.ast.body.AnnotationMemberDeclaration;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.ConstructorDeclaration;
+import com.github.javaparser.ast.body.EnumConstantDeclaration;
+import com.github.javaparser.ast.body.EnumDeclaration;
+import com.github.javaparser.ast.body.FieldDeclaration;
+import com.github.javaparser.ast.body.InitializerDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.Parameter;
+import com.github.javaparser.ast.body.ReceiverParameter;
+import com.github.javaparser.ast.body.VariableDeclarator;
+import com.github.javaparser.ast.comments.BlockComment;
+import com.github.javaparser.ast.comments.JavadocComment;
+import com.github.javaparser.ast.comments.LineComment;
+import com.github.javaparser.ast.expr.ArrayAccessExpr;
+import com.github.javaparser.ast.expr.ArrayCreationExpr;
+import com.github.javaparser.ast.expr.ArrayInitializerExpr;
+import com.github.javaparser.ast.expr.AssignExpr;
+import com.github.javaparser.ast.expr.BinaryExpr;
+import com.github.javaparser.ast.expr.BooleanLiteralExpr;
+import com.github.javaparser.ast.expr.CastExpr;
+import com.github.javaparser.ast.expr.CharLiteralExpr;
+import com.github.javaparser.ast.expr.ClassExpr;
+import com.github.javaparser.ast.expr.ConditionalExpr;
+import com.github.javaparser.ast.expr.DoubleLiteralExpr;
+import com.github.javaparser.ast.expr.EnclosedExpr;
+import com.github.javaparser.ast.expr.FieldAccessExpr;
+import com.github.javaparser.ast.expr.InstanceOfExpr;
+import com.github.javaparser.ast.expr.IntegerLiteralExpr;
+import com.github.javaparser.ast.expr.LambdaExpr;
+import com.github.javaparser.ast.expr.LongLiteralExpr;
+import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
+import com.github.javaparser.ast.expr.MemberValuePair;
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import com.github.javaparser.ast.expr.MethodReferenceExpr;
+import com.github.javaparser.ast.expr.Name;
+import com.github.javaparser.ast.expr.NameExpr;
+import com.github.javaparser.ast.expr.NormalAnnotationExpr;
+import com.github.javaparser.ast.expr.NullLiteralExpr;
+import com.github.javaparser.ast.expr.ObjectCreationExpr;
+import com.github.javaparser.ast.expr.SimpleName;
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
+import com.github.javaparser.ast.expr.StringLiteralExpr;
+import com.github.javaparser.ast.expr.SuperExpr;
+import com.github.javaparser.ast.expr.SwitchExpr;
+import com.github.javaparser.ast.expr.TextBlockLiteralExpr;
+import com.github.javaparser.ast.expr.ThisExpr;
+import com.github.javaparser.ast.expr.TypeExpr;
+import com.github.javaparser.ast.expr.UnaryExpr;
+import com.github.javaparser.ast.expr.VariableDeclarationExpr;
+import com.github.javaparser.ast.modules.ModuleDeclaration;
+import com.github.javaparser.ast.modules.ModuleExportsDirective;
+import com.github.javaparser.ast.modules.ModuleOpensDirective;
+import com.github.javaparser.ast.modules.ModuleProvidesDirective;
+import com.github.javaparser.ast.modules.ModuleRequiresDirective;
+import com.github.javaparser.ast.modules.ModuleUsesDirective;
+import com.github.javaparser.ast.stmt.AssertStmt;
+import com.github.javaparser.ast.stmt.BlockStmt;
+import com.github.javaparser.ast.stmt.BreakStmt;
+import com.github.javaparser.ast.stmt.CatchClause;
+import com.github.javaparser.ast.stmt.ContinueStmt;
+import com.github.javaparser.ast.stmt.DoStmt;
+import com.github.javaparser.ast.stmt.EmptyStmt;
+import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
+import com.github.javaparser.ast.stmt.ExpressionStmt;
+import com.github.javaparser.ast.stmt.ForEachStmt;
+import com.github.javaparser.ast.stmt.ForStmt;
+import com.github.javaparser.ast.stmt.IfStmt;
+import com.github.javaparser.ast.stmt.LabeledStmt;
+import com.github.javaparser.ast.stmt.LocalClassDeclarationStmt;
+import com.github.javaparser.ast.stmt.ReturnStmt;
+import com.github.javaparser.ast.stmt.SwitchEntry;
+import com.github.javaparser.ast.stmt.SwitchStmt;
+import com.github.javaparser.ast.stmt.SynchronizedStmt;
+import com.github.javaparser.ast.stmt.ThrowStmt;
+import com.github.javaparser.ast.stmt.TryStmt;
+import com.github.javaparser.ast.stmt.UnparsableStmt;
+import com.github.javaparser.ast.stmt.WhileStmt;
+import com.github.javaparser.ast.stmt.YieldStmt;
+import com.github.javaparser.ast.type.ArrayType;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import com.github.javaparser.ast.type.IntersectionType;
+import com.github.javaparser.ast.type.PrimitiveType;
+import com.github.javaparser.ast.type.TypeParameter;
+import com.github.javaparser.ast.type.UnionType;
+import com.github.javaparser.ast.type.UnknownType;
+import com.github.javaparser.ast.type.VarType;
+import com.github.javaparser.ast.type.VoidType;
+import com.github.javaparser.ast.type.WildcardType;
+import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
+
+/**
+ * A visitor that visits every node in an AST by default and performs a default action on each node
+ * after visiting its children.
+ *
+ * <p>To use this class, override {@code defaultAction}. Unlike JavaParser's {@code
+ * VoidVisitorWithDefaults}, visiting a node also visits all its children. This allows easily
+ * performing an action on each node of an AST.
+ */
+public abstract class VoidVisitorWithDefaultAction extends VoidVisitorAdapter<Void> {
+  /**
+   * Action performed on each visited node.
+   *
+   * @param node node to perform action on
+   */
+  public abstract void defaultAction(Node node);
+
+  @Override
+  public void visit(AnnotationDeclaration n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(AnnotationMemberDeclaration n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ArrayAccessExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ArrayCreationExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ArrayCreationLevel n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ArrayInitializerExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ArrayType n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(AssertStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(AssignExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(BinaryExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(BlockComment n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(BlockStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(BooleanLiteralExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(BreakStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(CastExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(CatchClause n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(CharLiteralExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ClassExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ClassOrInterfaceDeclaration n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ClassOrInterfaceType n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(CompilationUnit n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(StubUnit n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ConditionalExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ConstructorDeclaration n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ContinueStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(DoStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(DoubleLiteralExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(EmptyStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(EnclosedExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(EnumConstantDeclaration n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(EnumDeclaration n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ExplicitConstructorInvocationStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ExpressionStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(FieldAccessExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(FieldDeclaration n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ForStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ForEachStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(IfStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ImportDeclaration n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(InitializerDeclaration n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(InstanceOfExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(IntegerLiteralExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(IntersectionType n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(JavadocComment n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(LabeledStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(LambdaExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(LineComment n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(LocalClassDeclarationStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(LongLiteralExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(MarkerAnnotationExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(MemberValuePair n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(MethodCallExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(MethodDeclaration n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(MethodReferenceExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(NameExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(Name n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(NormalAnnotationExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(NullLiteralExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ObjectCreationExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(PackageDeclaration n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(Parameter n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(PrimitiveType n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ReturnStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(SimpleName n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(SingleMemberAnnotationExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(StringLiteralExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(SuperExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(SwitchEntry n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(SwitchStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(SynchronizedStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ThisExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ThrowStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(TryStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(TypeExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(TypeParameter n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(UnaryExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(UnionType n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(UnknownType n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(VariableDeclarationExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(VariableDeclarator n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(VoidType n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(WhileStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(WildcardType n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ModuleDeclaration n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ModuleRequiresDirective n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ModuleExportsDirective n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ModuleProvidesDirective n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ModuleUsesDirective n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ModuleOpensDirective n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(UnparsableStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(ReceiverParameter n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(VarType n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(Modifier n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(SwitchExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(TextBlockLiteralExpr n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+
+  @Override
+  public void visit(YieldStmt n, Void p) {
+    super.visit(n, p);
+    defaultAction(n);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/Default.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/Default.java
new file mode 100644
index 0000000..f1677a8
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/Default.java
@@ -0,0 +1,58 @@
+package org.checkerframework.framework.util.defaults;
+
+import java.util.Objects;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * Represents a mapping from an Annotation to a TypeUseLocation it should be applied to during
+ * defaulting. The Comparable ordering of this class first tests location then tests annotation
+ * ordering (via {@link org.checkerframework.javacutil.AnnotationUtils}).
+ *
+ * <p>It also has a handy toString method that is useful for debugging.
+ */
+public class Default implements Comparable<Default> {
+  // please remember to add any fields to the hashcode calculation
+  public final AnnotationMirror anno;
+  public final TypeUseLocation location;
+
+  public Default(final AnnotationMirror anno, final TypeUseLocation location) {
+    this.anno = anno;
+    this.location = location;
+  }
+
+  @Override
+  public int compareTo(Default other) {
+    int locationOrder = location.compareTo(other.location);
+    if (locationOrder == 0) {
+      return AnnotationUtils.compareAnnotationMirrors(anno, other.anno);
+    } else {
+      return locationOrder;
+    }
+  }
+
+  @Override
+  public boolean equals(@Nullable Object thatObj) {
+    if (thatObj == this) {
+      return true;
+    }
+
+    if (thatObj == null || thatObj.getClass() != Default.class) {
+      return false;
+    }
+
+    return compareTo((Default) thatObj) == 0;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(anno, location);
+  }
+
+  @Override
+  public String toString() {
+    return "( " + location.name() + " => " + anno + " )";
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/DefaultSet.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/DefaultSet.java
new file mode 100644
index 0000000..465bc25
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/DefaultSet.java
@@ -0,0 +1,24 @@
+package org.checkerframework.framework.util.defaults;
+
+import java.util.TreeSet;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * An ordered set of Defaults (see {@link org.checkerframework.framework.util.defaults.Default}).
+ * This class provides a little syntactic sugar and a better toString over TreeSet.
+ */
+@SuppressWarnings("serial")
+class DefaultSet extends TreeSet<Default> {
+
+  /** Creates a DefaultSet. */
+  public DefaultSet() {
+    super(Default::compareTo);
+  }
+
+  @Override
+  public String toString() {
+    return "DefaultSet( " + StringsPlume.join(", ", this) + " )";
+  }
+
+  public static final DefaultSet EMPTY = new DefaultSet();
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java
new file mode 100644
index 0000000..2c79c89
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java
@@ -0,0 +1,1200 @@
+package org.checkerframework.framework.util.defaults;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeParameterTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.code.Type.WildcardType;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Elements;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.CollectionUtils;
+import org.checkerframework.javacutil.ElementUtils;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Determines the default qualifiers on a type. Default qualifiers are specified via the {@link
+ * org.checkerframework.framework.qual.DefaultQualifier} annotation.
+ *
+ * @see org.checkerframework.framework.qual.DefaultQualifier
+ */
+public class QualifierDefaults {
+
+  // TODO add visitor state to get the default annotations from the top down?
+  // TODO apply from package elements also
+  // TODO try to remove some dependencies (e.g. on factory)
+
+  /**
+   * This field indicates whether or not a default should be applied to type vars located in the
+   * type being defaulted. This should only ever be true when the type variable is a local variable,
+   * non-component use, i.e.
+   *
+   * <pre>{@code
+   * <T> void method(@NOT_HERE T tIn) {
+   *     T t = tIn;
+   * }
+   * }</pre>
+   *
+   * The local variable T will be defaulted in order to allow dataflow to refine T. This variable
+   * will be false if dataflow is not in use.
+   */
+  private boolean applyToTypeVar = false;
+
+  /** Element utilities to use. */
+  private final Elements elements;
+
+  /** The value() element/field of a @DefaultQualifier annotation. */
+  protected final ExecutableElement defaultQualifierValueElement;
+  /** The locations() element/field of a @DefaultQualifier annotation. */
+  protected final ExecutableElement defaultQualifierLocationsElement;
+  /** The value() element/field of a @DefaultQualifier.List annotation. */
+  protected final ExecutableElement defaultQualifierListValueElement;
+
+  /** AnnotatedTypeFactory to use. */
+  private final AnnotatedTypeFactory atypeFactory;
+
+  /** Defaults for checked code. */
+  private final DefaultSet checkedCodeDefaults = new DefaultSet();
+
+  /** Defaults for unchecked code. */
+  private final DefaultSet uncheckedCodeDefaults = new DefaultSet();
+
+  /** Size for caches. */
+  private static final int CACHE_SIZE = 300;
+
+  /** Mapping from an Element to the bound type. */
+  protected final Map<Element, BoundType> elementToBoundType =
+      CollectionUtils.createLRUCache(CACHE_SIZE);
+
+  /**
+   * Defaults that apply for a certain Element. On the one hand this is used for caching (an earlier
+   * name for the field was "qualifierCache"). It can also be used by type systems to set defaults
+   * for certain Elements.
+   */
+  private final IdentityHashMap<Element, DefaultSet> elementDefaults = new IdentityHashMap<>();
+
+  /** A mapping of Element &rarr; Whether or not that element is AnnotatedFor this type system. */
+  private final IdentityHashMap<Element, Boolean> elementAnnotatedFors = new IdentityHashMap<>();
+
+  /** CLIMB locations whose standard default is top for a given type system. */
+  public static final List<TypeUseLocation> STANDARD_CLIMB_DEFAULTS_TOP =
+      Collections.unmodifiableList(
+          Arrays.asList(
+              TypeUseLocation.LOCAL_VARIABLE,
+              TypeUseLocation.RESOURCE_VARIABLE,
+              TypeUseLocation.EXCEPTION_PARAMETER,
+              TypeUseLocation.IMPLICIT_UPPER_BOUND));
+
+  /** CLIMB locations whose standard default is bottom for a given type system. */
+  public static final List<TypeUseLocation> STANDARD_CLIMB_DEFAULTS_BOTTOM =
+      Collections.unmodifiableList(Arrays.asList(TypeUseLocation.IMPLICIT_LOWER_BOUND));
+
+  /** List of TypeUseLocations that are valid for unchecked code defaults. */
+  private static final List<TypeUseLocation> validUncheckedCodeDefaultLocations =
+      Collections.unmodifiableList(
+          Arrays.asList(
+              TypeUseLocation.FIELD,
+              TypeUseLocation.PARAMETER,
+              TypeUseLocation.RETURN,
+              TypeUseLocation.RECEIVER,
+              TypeUseLocation.UPPER_BOUND,
+              TypeUseLocation.LOWER_BOUND,
+              TypeUseLocation.OTHERWISE,
+              TypeUseLocation.ALL));
+
+  /** Standard unchecked default locations that should be top. */
+  // Fields are defaulted to top so that warnings are issued at field reads, which we believe are
+  // more common than field writes. Future work is to specify different defaults for field reads
+  // and field writes.  (When a field is written to, its type should be bottom.)
+  public static final List<TypeUseLocation> STANDARD_UNCHECKED_DEFAULTS_TOP =
+      Collections.unmodifiableList(
+          Arrays.asList(
+              TypeUseLocation.RETURN, TypeUseLocation.FIELD, TypeUseLocation.UPPER_BOUND));
+
+  /** Standard unchecked default locations that should be bottom. */
+  public static final List<TypeUseLocation> STANDARD_UNCHECKED_DEFAULTS_BOTTOM =
+      Collections.unmodifiableList(
+          Arrays.asList(TypeUseLocation.PARAMETER, TypeUseLocation.LOWER_BOUND));
+
+  /** True if conservative defaults should be used in unannotated source code. */
+  private final boolean useConservativeDefaultsSource;
+
+  /** True if conservative defaults should be used for bytecode. */
+  private final boolean useConservativeDefaultsBytecode;
+
+  /**
+   * Returns an array of locations that are valid for the unchecked value defaults. These are simply
+   * by syntax, since an entire file is typechecked, it is not possible for local variables to be
+   * unchecked.
+   */
+  public static List<TypeUseLocation> validLocationsForUncheckedCodeDefaults() {
+    return validUncheckedCodeDefaultLocations;
+  }
+
+  /**
+   * @param elements interface to Element data in the current processing environment
+   * @param atypeFactory an annotation factory, used to get annotations by name
+   */
+  public QualifierDefaults(Elements elements, AnnotatedTypeFactory atypeFactory) {
+    this.elements = elements;
+    this.atypeFactory = atypeFactory;
+    this.useConservativeDefaultsBytecode =
+        atypeFactory.getChecker().useConservativeDefault("bytecode");
+    this.useConservativeDefaultsSource = atypeFactory.getChecker().useConservativeDefault("source");
+    ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv();
+    this.defaultQualifierValueElement =
+        TreeUtils.getMethod(DefaultQualifier.class, "value", 0, processingEnv);
+    this.defaultQualifierLocationsElement =
+        TreeUtils.getMethod(DefaultQualifier.class, "locations", 0, processingEnv);
+    this.defaultQualifierListValueElement =
+        TreeUtils.getMethod(DefaultQualifier.List.class, "value", 0, processingEnv);
+  }
+
+  @Override
+  public String toString() {
+    // displays the checked and unchecked code defaults
+    return StringsPlume.joinLines(
+        "Checked code defaults: ",
+        StringsPlume.joinLines(checkedCodeDefaults),
+        "Unchecked code defaults: ",
+        StringsPlume.joinLines(uncheckedCodeDefaults),
+        "useConservativeDefaultsSource: " + useConservativeDefaultsSource,
+        "useConservativeDefaultsBytecode: " + useConservativeDefaultsBytecode);
+  }
+
+  /**
+   * Check that a default with TypeUseLocation OTHERWISE or ALL is specified.
+   *
+   * @return whether we found a Default with location OTHERWISE or ALL
+   */
+  public boolean hasDefaultsForCheckedCode() {
+    for (Default def : checkedCodeDefaults) {
+      if (def.location == TypeUseLocation.OTHERWISE || def.location == TypeUseLocation.ALL) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /** Add standard unchecked defaults that do not conflict with previously added defaults. */
+  public void addUncheckedStandardDefaults() {
+    QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy();
+    Set<? extends AnnotationMirror> tops = qualHierarchy.getTopAnnotations();
+    Set<? extends AnnotationMirror> bottoms = qualHierarchy.getBottomAnnotations();
+
+    for (TypeUseLocation loc : STANDARD_UNCHECKED_DEFAULTS_TOP) {
+      // Only add standard defaults in locations where a default has not be specified
+      for (AnnotationMirror top : tops) {
+        if (!conflictsWithExistingDefaults(uncheckedCodeDefaults, top, loc)) {
+          addUncheckedCodeDefault(top, loc);
+        }
+      }
+    }
+
+    for (TypeUseLocation loc : STANDARD_UNCHECKED_DEFAULTS_BOTTOM) {
+      for (AnnotationMirror bottom : bottoms) {
+        // Only add standard defaults in locations where a default has not be specified
+        if (!conflictsWithExistingDefaults(uncheckedCodeDefaults, bottom, loc)) {
+          addUncheckedCodeDefault(bottom, loc);
+        }
+      }
+    }
+  }
+
+  /** Add standard CLIMB defaults that do not conflict with previously added defaults. */
+  public void addClimbStandardDefaults() {
+    QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy();
+    Set<? extends AnnotationMirror> tops = qualHierarchy.getTopAnnotations();
+    Set<? extends AnnotationMirror> bottoms = qualHierarchy.getBottomAnnotations();
+
+    for (TypeUseLocation loc : STANDARD_CLIMB_DEFAULTS_TOP) {
+      for (AnnotationMirror top : tops) {
+        if (!conflictsWithExistingDefaults(checkedCodeDefaults, top, loc)) {
+          // Only add standard defaults in locations where a default has not been specified
+          addCheckedCodeDefault(top, loc);
+        }
+      }
+    }
+
+    for (TypeUseLocation loc : STANDARD_CLIMB_DEFAULTS_BOTTOM) {
+      for (AnnotationMirror bottom : bottoms) {
+        if (!conflictsWithExistingDefaults(checkedCodeDefaults, bottom, loc)) {
+          // Only add standard defaults in locations where a default has not been specified
+          addCheckedCodeDefault(bottom, loc);
+        }
+      }
+    }
+  }
+
+  /**
+   * Sets the default annotations. A programmer may override this by writing the @DefaultQualifier
+   * annotation on an element.
+   */
+  public void addCheckedCodeDefault(
+      AnnotationMirror absoluteDefaultAnno, TypeUseLocation location) {
+    checkDuplicates(checkedCodeDefaults, absoluteDefaultAnno, location);
+    checkedCodeDefaults.add(new Default(absoluteDefaultAnno, location));
+  }
+
+  /** Sets the default annotation for unchecked elements. */
+  public void addUncheckedCodeDefault(
+      AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location) {
+    checkDuplicates(uncheckedCodeDefaults, uncheckedDefaultAnno, location);
+    checkIsValidUncheckedCodeLocation(uncheckedDefaultAnno, location);
+
+    uncheckedCodeDefaults.add(new Default(uncheckedDefaultAnno, location));
+  }
+
+  /** Sets the default annotation for unchecked elements, with specific locations. */
+  public void addUncheckedCodeDefaults(
+      AnnotationMirror absoluteDefaultAnno, TypeUseLocation[] locations) {
+    for (TypeUseLocation location : locations) {
+      addUncheckedCodeDefault(absoluteDefaultAnno, location);
+    }
+  }
+
+  public void addCheckedCodeDefaults(
+      AnnotationMirror absoluteDefaultAnno, TypeUseLocation[] locations) {
+    for (TypeUseLocation location : locations) {
+      addCheckedCodeDefault(absoluteDefaultAnno, location);
+    }
+  }
+
+  /** Sets the default annotations for a certain Element. */
+  public void addElementDefault(
+      Element elem, AnnotationMirror elementDefaultAnno, TypeUseLocation location) {
+    DefaultSet prevset = elementDefaults.get(elem);
+    if (prevset != null) {
+      checkDuplicates(prevset, elementDefaultAnno, location);
+    } else {
+      prevset = new DefaultSet();
+    }
+    prevset.add(new Default(elementDefaultAnno, location));
+    elementDefaults.put(elem, prevset);
+  }
+
+  private void checkIsValidUncheckedCodeLocation(
+      AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location) {
+    boolean isValidUntypeLocation = false;
+    for (TypeUseLocation validLoc : validLocationsForUncheckedCodeDefaults()) {
+      if (location == validLoc) {
+        isValidUntypeLocation = true;
+        break;
+      }
+    }
+
+    if (!isValidUntypeLocation) {
+      throw new BugInCF(
+          "Invalid unchecked code default location: " + location + " -> " + uncheckedDefaultAnno);
+    }
+  }
+
+  private void checkDuplicates(
+      DefaultSet previousDefaults, AnnotationMirror newAnno, TypeUseLocation newLoc) {
+    if (conflictsWithExistingDefaults(previousDefaults, newAnno, newLoc)) {
+      throw new BugInCF(
+          "Only one qualifier from a hierarchy can be the default. Existing: "
+              + previousDefaults
+              + " and new: "
+              + new Default(newAnno, newLoc));
+    }
+  }
+
+  private boolean conflictsWithExistingDefaults(
+      DefaultSet previousDefaults, AnnotationMirror newAnno, TypeUseLocation newLoc) {
+    final QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy();
+
+    for (Default previous : previousDefaults) {
+      if (!AnnotationUtils.areSame(newAnno, previous.anno) && previous.location == newLoc) {
+        final AnnotationMirror previousTop = qualHierarchy.getTopAnnotation(previous.anno);
+        if (qualHierarchy.isSubtype(newAnno, previousTop)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Applies default annotations to a type given an {@link javax.lang.model.element.Element}.
+   *
+   * @param elt the element from which the type was obtained
+   * @param type the type to annotate
+   */
+  public void annotate(Element elt, AnnotatedTypeMirror type) {
+    if (elt != null) {
+      switch (elt.getKind()) {
+        case FIELD:
+        case LOCAL_VARIABLE:
+        case PARAMETER:
+        case RESOURCE_VARIABLE:
+        case EXCEPTION_PARAMETER:
+        case ENUM_CONSTANT:
+          String varName = elt.getSimpleName().toString();
+          ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory)
+              .getDefaultForTypeAnnotator()
+              .defaultTypeFromName(type, varName);
+
+          break;
+
+        case METHOD:
+          String methodName = elt.getSimpleName().toString();
+          AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) type).getReturnType();
+          ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory)
+              .getDefaultForTypeAnnotator()
+              .defaultTypeFromName(returnType, methodName);
+
+          break;
+
+        default:
+          break;
+      }
+    }
+
+    applyDefaultsElement(elt, type);
+  }
+
+  /**
+   * Applies default annotations to a type given a {@link com.sun.source.tree.Tree}.
+   *
+   * @param tree the tree from which the type was obtained
+   * @param type the type to annotate
+   */
+  public void annotate(Tree tree, AnnotatedTypeMirror type) {
+    applyDefaults(tree, type);
+  }
+
+  /**
+   * Determines the nearest enclosing element for a tree by climbing the tree toward the root and
+   * obtaining the element for the first declaration (variable, method, or class) that encloses the
+   * tree. Initializers of local variables are handled in a special way: within an initializer we
+   * look for the DefaultQualifier(s) annotation and keep track of the previously visited tree.
+   * TODO: explain the behavior better.
+   *
+   * @param tree the tree
+   * @return the nearest enclosing element for a tree
+   */
+  private Element nearestEnclosingExceptLocal(Tree tree) {
+    TreePath path = atypeFactory.getPath(tree);
+    if (path == null) {
+      Element element = atypeFactory.getEnclosingElementForArtificialTree(tree);
+      if (element != null) {
+        return element;
+      } else {
+        return TreeUtils.elementFromTree(tree);
+      }
+    }
+
+    Tree prev = null;
+
+    for (Tree t : path) {
+      switch (t.getKind()) {
+        case VARIABLE:
+          VariableTree vtree = (VariableTree) t;
+          ExpressionTree vtreeInit = vtree.getInitializer();
+          @SuppressWarnings("interning:not.interned") // check cached value
+          boolean sameAsPrev = (vtreeInit != null && prev == vtreeInit);
+          if (sameAsPrev) {
+            Element elt = TreeUtils.elementFromDeclaration((VariableTree) t);
+            AnnotationMirror d = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class);
+            AnnotationMirror ds = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class);
+
+            if (d == null && ds == null) {
+              break;
+            }
+          }
+          if (prev != null && prev.getKind() == Tree.Kind.MODIFIERS) {
+            // Annotations are modifiers. We do not want to apply the local variable default to
+            // annotations. Without this, test fenum/TestSwitch failed, because the default for an
+            // argument became incompatible with the declared type.
+            break;
+          }
+          return TreeUtils.elementFromDeclaration((VariableTree) t);
+        case METHOD:
+          return TreeUtils.elementFromDeclaration((MethodTree) t);
+        case CLASS:
+        case ENUM:
+        case INTERFACE:
+        case ANNOTATION_TYPE:
+          return TreeUtils.elementFromDeclaration((ClassTree) t);
+        default: // Do nothing.
+      }
+      prev = t;
+    }
+
+    return null;
+  }
+
+  /**
+   * Applies default annotations to a type. A {@link com.sun.source.tree.Tree} determines the
+   * appropriate scope for defaults.
+   *
+   * <p>For instance, if the tree is associated with a declaration (e.g., it's the use of a field,
+   * or a method invocation), defaults in the scope of the <i>declaration</i> are used; if the tree
+   * is not associated with a declaration (e.g., a typecast), defaults in the scope of the tree are
+   * used.
+   *
+   * @param tree the tree associated with the type
+   * @param type the type to which defaults will be applied
+   * @see #applyDefaultsElement(javax.lang.model.element.Element,
+   *     org.checkerframework.framework.type.AnnotatedTypeMirror)
+   */
+  private void applyDefaults(Tree tree, AnnotatedTypeMirror type) {
+
+    // The location to take defaults from.
+    Element elt;
+    switch (tree.getKind()) {
+      case MEMBER_SELECT:
+        elt = TreeUtils.elementFromUse((MemberSelectTree) tree);
+        break;
+
+      case IDENTIFIER:
+        elt = TreeUtils.elementFromUse((IdentifierTree) tree);
+        if (ElementUtils.isTypeDeclaration(elt)) {
+          // If the Idenitifer is a type, then use the scope of the tree.
+          elt = nearestEnclosingExceptLocal(tree);
+        }
+        break;
+
+      case METHOD_INVOCATION:
+        elt = TreeUtils.elementFromUse((MethodInvocationTree) tree);
+        break;
+
+        // TODO cases for array access, etc. -- every expression tree
+        // (The above probably means that we should use defaults in the
+        // scope of the declaration of the array.  Is that right?  -MDE)
+
+      default:
+        // If no associated symbol was found, use the tree's (lexical) scope.
+        elt = nearestEnclosingExceptLocal(tree);
+        // elt = nearestEnclosing(tree);
+    }
+    // System.out.println("applyDefaults on tree " + tree +
+    //        " gives elt: " + elt + "(" + elt.getKind() + ")");
+
+    boolean defaultTypeVarLocals =
+        (atypeFactory instanceof GenericAnnotatedTypeFactory<?, ?, ?, ?>)
+            && ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory)
+                .getShouldDefaultTypeVarLocals();
+    applyToTypeVar =
+        defaultTypeVarLocals
+            && elt != null
+            && elt.getKind() == ElementKind.LOCAL_VARIABLE
+            && type.getKind() == TypeKind.TYPEVAR;
+    applyDefaultsElement(elt, type);
+    applyToTypeVar = false;
+  }
+
+  /** The default {@code value} element for a @DefaultQualifier annotation. */
+  private static TypeUseLocation[] defaultQualifierValueDefault =
+      new TypeUseLocation[] {org.checkerframework.framework.qual.TypeUseLocation.ALL};
+
+  /**
+   * Create a DefaultSet from a @DefaultQualifier annotation.
+   *
+   * @param dq a @DefaultQualifier annotation
+   * @return a DefaultSet corresponding to the @DefaultQualifier annotation
+   */
+  private DefaultSet fromDefaultQualifier(AnnotationMirror dq) {
+    @SuppressWarnings("unchecked")
+    Name cls = AnnotationUtils.getElementValueClassName(dq, defaultQualifierValueElement);
+    AnnotationMirror anno = AnnotationBuilder.fromName(elements, cls);
+
+    if (anno == null) {
+      return null;
+    }
+
+    if (!atypeFactory.isSupportedQualifier(anno)) {
+      anno = atypeFactory.canonicalAnnotation(anno);
+    }
+
+    if (atypeFactory.isSupportedQualifier(anno)) {
+      TypeUseLocation[] locations =
+          AnnotationUtils.getElementValueEnumArray(
+              dq,
+              defaultQualifierLocationsElement,
+              TypeUseLocation.class,
+              defaultQualifierValueDefault);
+      DefaultSet ret = new DefaultSet();
+      for (TypeUseLocation loc : locations) {
+        ret.add(new Default(anno, loc));
+      }
+      return ret;
+    } else {
+      return null;
+    }
+  }
+
+  private boolean isElementAnnotatedForThisChecker(final Element elt) {
+    boolean elementAnnotatedForThisChecker = false;
+
+    if (elt == null) {
+      throw new BugInCF("Call of QualifierDefaults.isElementAnnotatedForThisChecker with null");
+    }
+
+    if (elementAnnotatedFors.containsKey(elt)) {
+      return elementAnnotatedFors.get(elt);
+    }
+
+    final AnnotationMirror annotatedFor = atypeFactory.getDeclAnnotation(elt, AnnotatedFor.class);
+
+    if (annotatedFor != null) {
+      elementAnnotatedForThisChecker =
+          atypeFactory.doesAnnotatedForApplyToThisChecker(annotatedFor);
+    }
+
+    if (!elementAnnotatedForThisChecker) {
+      Element parent;
+      if (elt.getKind() == ElementKind.PACKAGE) {
+        // elt.getEnclosingElement() on a package is null; therefore,
+        // use the dedicated method.
+        parent = ElementUtils.parentPackage((PackageElement) elt, elements);
+      } else {
+        parent = elt.getEnclosingElement();
+      }
+
+      if (parent != null && isElementAnnotatedForThisChecker(parent)) {
+        elementAnnotatedForThisChecker = true;
+      }
+    }
+
+    elementAnnotatedFors.put(elt, elementAnnotatedForThisChecker);
+
+    return elementAnnotatedForThisChecker;
+  }
+
+  private DefaultSet defaultsAt(final Element elt) {
+    if (elt == null) {
+      return DefaultSet.EMPTY;
+    }
+
+    if (elementDefaults.containsKey(elt)) {
+      return elementDefaults.get(elt);
+    }
+
+    DefaultSet qualifiers = null;
+
+    {
+      AnnotationMirror dqAnno = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class);
+
+      if (dqAnno != null) {
+        qualifiers = new DefaultSet();
+        Set<Default> p = fromDefaultQualifier(dqAnno);
+
+        if (p != null) {
+          qualifiers.addAll(p);
+        }
+      }
+    }
+
+    {
+      AnnotationMirror dqListAnno =
+          atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class);
+      if (dqListAnno != null) {
+        if (qualifiers == null) {
+          qualifiers = new DefaultSet();
+        }
+        @SuppressWarnings("unchecked")
+        List<AnnotationMirror> values =
+            AnnotationUtils.getElementValue(
+                dqListAnno, defaultQualifierListValueElement, List.class);
+        for (AnnotationMirror dqAnno : values) {
+          Set<Default> p = fromDefaultQualifier(dqAnno);
+          if (p != null) {
+            qualifiers.addAll(p);
+          }
+        }
+      }
+    }
+
+    Element parent;
+    if (elt.getKind() == ElementKind.PACKAGE) {
+      parent = ElementUtils.parentPackage((PackageElement) elt, elements);
+    } else {
+      parent = elt.getEnclosingElement();
+    }
+
+    DefaultSet parentDefaults = defaultsAt(parent);
+    if (qualifiers == null || qualifiers.isEmpty()) {
+      qualifiers = parentDefaults;
+    } else {
+      qualifiers.addAll(parentDefaults);
+    }
+
+    if (qualifiers != null && !qualifiers.isEmpty()) {
+      elementDefaults.put(elt, qualifiers);
+      return qualifiers;
+    } else {
+      return DefaultSet.EMPTY;
+    }
+  }
+
+  /**
+   * Given an element, returns whether the conservative default should be applied for it. Handles
+   * elements from bytecode or source code.
+   *
+   * @param annotationScope the element that the conservative default might apply to
+   * @return whether the conservative default applies to the given element
+   */
+  public boolean applyConservativeDefaults(final Element annotationScope) {
+    if (annotationScope == null) {
+      return false;
+    }
+
+    if (uncheckedCodeDefaults.isEmpty()) {
+      return false;
+    }
+
+    // TODO: I would expect this:
+    //   atypeFactory.isFromByteCode(annotationScope)) {
+    // to work instead of the
+    // isElementFromByteCode/declarationFromElement/isFromStubFile calls,
+    // but it doesn't work correctly and tests fail.
+
+    boolean isFromStubFile = atypeFactory.isFromStubFile(annotationScope);
+    boolean isBytecode =
+        ElementUtils.isElementFromByteCode(annotationScope)
+            && atypeFactory.declarationFromElement(annotationScope) == null
+            && !isFromStubFile;
+    if (isBytecode) {
+      return useConservativeDefaultsBytecode && !isElementAnnotatedForThisChecker(annotationScope);
+    } else if (isFromStubFile) {
+      // TODO: Types in stub files not annotated for a particular checker should be
+      // treated as unchecked bytecode.   For now, all types in stub files are treated as
+      // checked code. Eventually, @AnnotateFor(checker) will be programmatically added
+      // to methods in stub files supplied via the @Stubfile annotation.  Stub files will
+      // be treated like unchecked code except for methods in the scope for an @AnnotatedFor.
+      return false;
+    } else if (useConservativeDefaultsSource) {
+      return !isElementAnnotatedForThisChecker(annotationScope);
+    }
+    return false;
+  }
+
+  /**
+   * Applies default annotations to a type. Conservative defaults are applied first as appropriate,
+   * followed by source code defaults.
+   *
+   * <p>For a discussion on the rules for application of source code and conservative defaults,
+   * please see the linked manual sections.
+   *
+   * @param annotationScope the element representing the nearest enclosing default annotation scope
+   *     for the type
+   * @param type the type to which defaults will be applied
+   * @checker_framework.manual #effective-qualifier The effective qualifier on a type (defaults and
+   *     inference)
+   * @checker_framework.manual #annotating-libraries Annotating libraries
+   */
+  private void applyDefaultsElement(final Element annotationScope, final AnnotatedTypeMirror type) {
+    DefaultSet defaults = defaultsAt(annotationScope);
+    DefaultApplierElement applier =
+        createDefaultApplierElement(atypeFactory, annotationScope, type, applyToTypeVar);
+
+    for (Default def : defaults) {
+      applier.applyDefault(def);
+    }
+
+    if (applyConservativeDefaults(annotationScope)) {
+      for (Default def : uncheckedCodeDefaults) {
+        applier.applyDefault(def);
+      }
+    }
+
+    for (Default def : checkedCodeDefaults) {
+      applier.applyDefault(def);
+    }
+  }
+
+  protected DefaultApplierElement createDefaultApplierElement(
+      AnnotatedTypeFactory atypeFactory,
+      Element annotationScope,
+      AnnotatedTypeMirror type,
+      boolean applyToTypeVar) {
+    return new DefaultApplierElement(atypeFactory, annotationScope, type, applyToTypeVar);
+  }
+
+  /** A default applier element. */
+  protected class DefaultApplierElement {
+
+    /** The annotated type factory. */
+    protected final AnnotatedTypeFactory atypeFactory;
+
+    /** The scope of the default. */
+    protected final Element scope;
+
+    /** The type to which to apply the default. */
+    protected final AnnotatedTypeMirror type;
+
+    /** Location to which to apply the default. (Should only be set by the applyDefault method.) */
+    protected TypeUseLocation location;
+
+    /** The default element applier implementation. */
+    protected final DefaultApplierElementImpl impl;
+
+    /*
+      Local type variables are defaulted to top when flow is turned on
+      We only want to default the top level type variable (and not type variables that are nested
+      in its bounds). E.g.,
+        <T extends List<E>, E extends Object> void method() {
+           T t;
+        }
+      We would like t to have its primary annotation defaulted but NOT the E inside its upper bound.
+      we use referential equality with the top level type var to determine which ones are definite
+      type uses, i.e. uses which can be defaulted
+    */
+    private final AnnotatedTypeVariable defaultableTypeVar;
+
+    public DefaultApplierElement(
+        AnnotatedTypeFactory atypeFactory,
+        Element scope,
+        AnnotatedTypeMirror type,
+        boolean applyToTypeVar) {
+      this.atypeFactory = atypeFactory;
+      this.scope = scope;
+      this.type = type;
+      this.impl = new DefaultApplierElementImpl();
+      this.defaultableTypeVar = applyToTypeVar ? (AnnotatedTypeVariable) type : null;
+    }
+
+    /**
+     * Apply default to the type.
+     *
+     * @param def default to apply
+     */
+    public void applyDefault(Default def) {
+      this.location = def.location;
+      impl.visit(type, def.anno);
+    }
+
+    /**
+     * Returns true if the given qualifier should be applied to the given type. Currently we do not
+     * apply defaults to void types, packages, wildcards, and type variables.
+     *
+     * @param type type to which qual would be applied
+     * @return true if this application should proceed
+     */
+    protected boolean shouldBeAnnotated(
+        final AnnotatedTypeMirror type, final boolean applyToTypeVar) {
+
+      return !(type == null
+          // TODO: executables themselves should not be annotated
+          // For some reason h1h2checker-tests fails with this.
+          // || type.getKind() == TypeKind.EXECUTABLE
+          || type.getKind() == TypeKind.NONE
+          || type.getKind() == TypeKind.WILDCARD
+          || (type.getKind() == TypeKind.TYPEVAR && !applyToTypeVar)
+          || type instanceof AnnotatedNoType);
+    }
+
+    /**
+     * Add the qualifier to the type if it does not already have an annotation in the same hierarchy
+     * as qual.
+     *
+     * @param type type to add qual
+     * @param qual annotation to add
+     */
+    protected void addAnnotation(AnnotatedTypeMirror type, AnnotationMirror qual) {
+      // Add the default annotation, but only if no other
+      // annotation is present.
+      if (!type.isAnnotatedInHierarchy(qual) && type.getKind() != TypeKind.EXECUTABLE) {
+        type.addAnnotation(qual);
+      }
+    }
+
+    protected class DefaultApplierElementImpl extends AnnotatedTypeScanner<Void, AnnotationMirror> {
+
+      @Override
+      public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) {
+        if (!shouldBeAnnotated(t, t == defaultableTypeVar)) {
+          return super.scan(t, qual);
+        }
+
+        // Some defaults only apply to the top level type.
+        boolean isTopLevelType = t == type;
+        switch (location) {
+          case FIELD:
+            if (scope != null && scope.getKind() == ElementKind.FIELD && isTopLevelType) {
+              addAnnotation(t, qual);
+            }
+            break;
+          case LOCAL_VARIABLE:
+            if (scope != null && scope.getKind() == ElementKind.LOCAL_VARIABLE && isTopLevelType) {
+              // TODO: how do we determine that we are in a cast or instanceof type?
+              addAnnotation(t, qual);
+            }
+            break;
+          case RESOURCE_VARIABLE:
+            if (scope != null
+                && scope.getKind() == ElementKind.RESOURCE_VARIABLE
+                && isTopLevelType) {
+              addAnnotation(t, qual);
+            }
+            break;
+          case EXCEPTION_PARAMETER:
+            if (scope != null
+                && scope.getKind() == ElementKind.EXCEPTION_PARAMETER
+                && isTopLevelType) {
+              addAnnotation(t, qual);
+              if (t.getKind() == TypeKind.UNION) {
+                AnnotatedUnionType aut = (AnnotatedUnionType) t;
+                // Also apply the default to the alternative types
+                for (AnnotatedDeclaredType anno : aut.getAlternatives()) {
+                  addAnnotation(anno, qual);
+                }
+              }
+            }
+            break;
+          case PARAMETER:
+            if (scope != null && scope.getKind() == ElementKind.PARAMETER && isTopLevelType) {
+              addAnnotation(t, qual);
+            } else if (scope != null
+                && (scope.getKind() == ElementKind.METHOD
+                    || scope.getKind() == ElementKind.CONSTRUCTOR)
+                && t.getKind() == TypeKind.EXECUTABLE
+                && isTopLevelType) {
+
+              for (AnnotatedTypeMirror atm : ((AnnotatedExecutableType) t).getParameterTypes()) {
+                if (shouldBeAnnotated(atm, false)) {
+                  addAnnotation(atm, qual);
+                }
+              }
+            }
+            break;
+          case RECEIVER:
+            if (scope != null
+                && scope.getKind() == ElementKind.PARAMETER
+                && isTopLevelType
+                && scope.getSimpleName().contentEquals("this")) {
+              // TODO: comparison against "this" is ugly, won't work
+              // for all possible names for receiver parameter.
+              // Comparison to Names._this might be a bit faster.
+              addAnnotation(t, qual);
+            } else if (scope != null
+                && (scope.getKind() == ElementKind.METHOD)
+                && t.getKind() == TypeKind.EXECUTABLE
+                && isTopLevelType) {
+
+              final AnnotatedDeclaredType receiver =
+                  ((AnnotatedExecutableType) t).getReceiverType();
+              if (shouldBeAnnotated(receiver, false)) {
+                addAnnotation(receiver, qual);
+              }
+            }
+            break;
+          case RETURN:
+            if (scope != null
+                && scope.getKind() == ElementKind.METHOD
+                && t.getKind() == TypeKind.EXECUTABLE
+                && isTopLevelType) {
+              final AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) t).getReturnType();
+              if (shouldBeAnnotated(returnType, false)) {
+                addAnnotation(returnType, qual);
+              }
+            }
+            break;
+          case CONSTRUCTOR_RESULT:
+            if (scope != null
+                && scope.getKind() == ElementKind.CONSTRUCTOR
+                && t.getKind() == TypeKind.EXECUTABLE
+                && isTopLevelType) {
+              final AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) t).getReturnType();
+              if (shouldBeAnnotated(returnType, false)) {
+                addAnnotation(returnType, qual);
+              }
+            }
+            break;
+          case IMPLICIT_LOWER_BOUND:
+            if (isLowerBound && boundType.isOneOf(BoundType.UNBOUNDED, BoundType.UPPER)) {
+              addAnnotation(t, qual);
+            }
+            break;
+          case EXPLICIT_LOWER_BOUND:
+            if (isLowerBound && boundType.isOneOf(BoundType.LOWER)) {
+              addAnnotation(t, qual);
+            }
+            break;
+          case LOWER_BOUND:
+            if (isLowerBound) {
+              addAnnotation(t, qual);
+            }
+            break;
+          case IMPLICIT_UPPER_BOUND:
+            if (isUpperBound && boundType.isOneOf(BoundType.UNBOUNDED, BoundType.LOWER)) {
+              addAnnotation(t, qual);
+            }
+            break;
+          case EXPLICIT_UPPER_BOUND:
+            if (isUpperBound && boundType.isOneOf(BoundType.UPPER)) {
+              addAnnotation(t, qual);
+            }
+            break;
+          case UPPER_BOUND:
+            if (this.isUpperBound) {
+              addAnnotation(t, qual);
+            }
+            break;
+          case OTHERWISE:
+          case ALL:
+            // TODO: forbid ALL if anything else was given.
+            addAnnotation(t, qual);
+            break;
+          default:
+            throw new BugInCF(
+                "QualifierDefaults.DefaultApplierElement: unhandled location: " + location);
+        }
+
+        return super.scan(t, qual);
+      }
+
+      @Override
+      public void reset() {
+        super.reset();
+        impl.isLowerBound = false;
+        impl.isUpperBound = false;
+        impl.boundType = BoundType.UNBOUNDED;
+      }
+
+      // are we currently defaulting the lower bound of a type variable or wildcard
+      private boolean isLowerBound = false;
+
+      // are we currently defaulting the upper bound of a type variable or wildcard
+      private boolean isUpperBound = false;
+
+      // the bound type of the current wildcard or type variable being defaulted
+      private BoundType boundType = BoundType.UNBOUNDED;
+
+      @Override
+      public Void visitTypeVariable(AnnotatedTypeVariable type, AnnotationMirror qual) {
+        if (visitedNodes.containsKey(type)) {
+          return visitedNodes.get(type);
+        }
+
+        visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual);
+        return null;
+      }
+
+      @Override
+      public Void visitWildcard(AnnotatedWildcardType type, AnnotationMirror qual) {
+        if (visitedNodes.containsKey(type)) {
+          return visitedNodes.get(type);
+        }
+
+        visitBounds(type, type.getExtendsBound(), type.getSuperBound(), qual);
+        return null;
+      }
+
+      /**
+       * Visit the bounds of a type variable or a wildcard and potentially apply qual to those
+       * bounds. This method will also update the boundType, isLowerBound, and isUpperbound fields.
+       */
+      protected void visitBounds(
+          AnnotatedTypeMirror boundedType,
+          AnnotatedTypeMirror upperBound,
+          AnnotatedTypeMirror lowerBound,
+          AnnotationMirror qual) {
+
+        final boolean prevIsUpperBound = isUpperBound;
+        final boolean prevIsLowerBound = isLowerBound;
+        final BoundType prevBoundType = boundType;
+
+        boundType = getBoundType(boundedType);
+
+        try {
+          isLowerBound = true;
+          isUpperBound = false;
+          scanAndReduce(lowerBound, qual, null);
+
+          visitedNodes.put(type, null);
+
+          isLowerBound = false;
+          isUpperBound = true;
+          scanAndReduce(upperBound, qual, null);
+
+          visitedNodes.put(type, null);
+
+        } finally {
+          isUpperBound = prevIsUpperBound;
+          isLowerBound = prevIsLowerBound;
+          boundType = prevBoundType;
+        }
+      }
+    }
+  }
+
+  /**
+   * Specifies whether the type variable or wildcard has an explicit upper bound (UPPER), an
+   * explicit lower bound (LOWER), or no explicit bounds (UNBOUNDED).
+   */
+  protected enum BoundType {
+
+    /** Indicates an upper-bounded type variable or wildcard. */
+    UPPER,
+
+    /** Indicates a lower-bounded type variable or wildcard. */
+    LOWER,
+
+    /**
+     * Neither bound is specified, BOTH are implicit. (If a type variable is declared in bytecode
+     * and the type of the upper bound is Object, then the checker assumes that the bound was not
+     * explicitly written in source code.)
+     */
+    UNBOUNDED;
+
+    public boolean isOneOf(final BoundType... choices) {
+      for (final BoundType choice : choices) {
+        if (this == choice) {
+          return true;
+        }
+      }
+
+      return false;
+    }
+  }
+
+  /**
+   * Returns the boundType for type.
+   *
+   * @param type the type whose boundType is returned. type must be an AnnotatedWildcardType or
+   *     AnnotatedTypeVariable.
+   * @return the boundType for type
+   */
+  private BoundType getBoundType(final AnnotatedTypeMirror type) {
+    if (type instanceof AnnotatedTypeVariable) {
+      return getTypeVarBoundType((AnnotatedTypeVariable) type);
+    }
+
+    if (type instanceof AnnotatedWildcardType) {
+      return getWildcardBoundType((AnnotatedWildcardType) type);
+    }
+
+    throw new BugInCF("Unexpected type kind: type=" + type);
+  }
+
+  /**
+   * Returns the bound type of the input typeVar.
+   *
+   * @param typeVar the type variable
+   * @return the bound type of the input typeVar
+   */
+  private BoundType getTypeVarBoundType(final AnnotatedTypeVariable typeVar) {
+    return getTypeVarBoundType((TypeParameterElement) typeVar.getUnderlyingType().asElement());
+  }
+
+  /**
+   * Returns the boundType (UPPER or UNBOUNDED) of the declaration of typeParamElem.
+   *
+   * @param typeParamElem the type parameter element
+   * @return the boundType (UPPER or UNBOUNDED) of the declaration of typeParamElem
+   */
+  // Results are cached in {@link elementToBoundType}.
+  private BoundType getTypeVarBoundType(final TypeParameterElement typeParamElem) {
+    final BoundType prev = elementToBoundType.get(typeParamElem);
+    if (prev != null) {
+      return prev;
+    }
+
+    TreePath declaredTypeVarEle = atypeFactory.getTreeUtils().getPath(typeParamElem);
+    Tree typeParamDecl = declaredTypeVarEle == null ? null : declaredTypeVarEle.getLeaf();
+
+    final BoundType boundType;
+    if (typeParamDecl == null) {
+      // This is not only for elements from binaries, but also
+      // when the compilation unit is no-longer available.
+      if (typeParamElem.getBounds().size() == 1
+          && TypesUtils.isObject(typeParamElem.getBounds().get(0))) {
+        // If the bound was Object, then it may or may not have been explicitly written.
+        // Assume that it was not.
+        boundType = BoundType.UNBOUNDED;
+      } else {
+        // The bound is not Object, so it must have been explicitly written and thus the
+        // type variable has an upper bound.
+        boundType = BoundType.UPPER;
+      }
+
+    } else {
+      if (typeParamDecl.getKind() == Tree.Kind.TYPE_PARAMETER) {
+        final TypeParameterTree tptree = (TypeParameterTree) typeParamDecl;
+
+        List<? extends Tree> bnds = tptree.getBounds();
+        if (bnds != null && !bnds.isEmpty()) {
+          boundType = BoundType.UPPER;
+        } else {
+          boundType = BoundType.UNBOUNDED;
+        }
+      } else {
+        throw new BugInCF(
+            StringsPlume.joinLines(
+                "Unexpected tree type for typeVar Element:",
+                "typeParamElem=" + typeParamElem,
+                typeParamDecl));
+      }
+    }
+
+    elementToBoundType.put(typeParamElem, boundType);
+    return boundType;
+  }
+
+  /**
+   * Returns the BoundType of annotatedWildcard. If it is unbounded, use the type parameter to which
+   * its an argument.
+   *
+   * @param annotatedWildcard the annotated wildcard type
+   * @return the BoundType of annotatedWildcard. If it is unbounded, use the type parameter to which
+   *     its an argument
+   */
+  public BoundType getWildcardBoundType(final AnnotatedWildcardType annotatedWildcard) {
+
+    final WildcardType wildcard = (WildcardType) annotatedWildcard.getUnderlyingType();
+
+    final BoundType boundType;
+    if (wildcard.isUnbound() && wildcard.bound != null) {
+      boundType = getTypeVarBoundType((TypeParameterElement) wildcard.bound.asElement());
+
+    } else {
+      // note: isSuperBound will be true for unbounded and lowers, but the unbounded case is
+      // already handled
+      boundType = wildcard.isSuperBound() ? BoundType.LOWER : BoundType.UPPER;
+    }
+
+    return boundType;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java
new file mode 100644
index 0000000..ef1c02e
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java
@@ -0,0 +1,128 @@
+package org.checkerframework.framework.util.dependenttypes;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.checkerframework.checker.formatter.qual.ConversionCategory;
+import org.checkerframework.checker.formatter.qual.Format;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Helper class for creating dependent type annotation error strings.
+ *
+ * <p><b>IMPORTANT:</b> This is not an Exception. It is a regular class that is returned, not
+ * thrown. The errors are not thrown so that they are only reported once rather than every time the
+ * annotation is parsed. See {@link DependentTypesHelper} for more details.
+ */
+public class DependentTypesError {
+
+  /// Static fields
+
+  /** How elements of this class are formatted. */
+  @SuppressWarnings("InlineFormatString") // https://github.com/google/error-prone/issues/1650
+  private static final String FORMAT_STRING = "[error for expression: %s; error: %s]";
+  /** Regular expression for unparsing string representations of this class (gross). */
+  private static final Pattern ERROR_PATTERN =
+      Pattern.compile("\\[error for expression: (.*); error: (.*)\\]");
+  /**
+   * Returns whether or not the given expression string is an error. That is, whether it is a string
+   * that was generated by this class.
+   *
+   * @param expression expression string to test
+   * @return whether or not the given expressions string is an error
+   */
+  public static boolean isExpressionError(String expression) {
+    return expression.startsWith("[error");
+  }
+
+  /** How to format warnings about use of formal parameter name. */
+  public static final @Format({ConversionCategory.INT, ConversionCategory.GENERAL}) String
+      FORMAL_PARAM_NAME_STRING = "Use \"#%d\" rather than \"%s\"";
+  /** Matches warnings about use of formal parameter name. */
+  private static final Pattern FORMAL_PARAM_NAME_PATTERN =
+      Pattern.compile("^'([a-zA-Z_$][a-zA-Z0-9_$]*)' because (Use \"#\\d+\" rather than \"\\1\")$");
+
+  /// Instance fields
+
+  /** The expression that is unparsable or otherwise problematic. */
+  public final String expression;
+  /** An error message about that expression. */
+  public final String error;
+
+  /// Constructors and methods
+
+  /**
+   * Create a DependentTypesError for the given expression and error message.
+   *
+   * @param expression the incorrect Java expression
+   * @param error an error message about the expression
+   */
+  public DependentTypesError(String expression, String error) {
+    this.expression = expression;
+    this.error = error;
+  }
+
+  /**
+   * Create a DependentTypesError for the given expression and exception.
+   *
+   * @param expression the incorrect Java expression
+   * @param e wraps an error message about the expression
+   */
+  public DependentTypesError(String expression, JavaExpressionParseException e) {
+    this.expression = expression;
+    this.error = e.getDiagMessage().getArgs()[0].toString();
+  }
+
+  /**
+   * Create a DependentTypesError by parsing a printed one.
+   *
+   * @param formattedError the toString() representation of a DependentTypesError
+   */
+  public static DependentTypesError unparse(String formattedError) {
+    Matcher matcher = ERROR_PATTERN.matcher(formattedError);
+    if (matcher.matches()) {
+      assert matcher.groupCount() == 2;
+      return new DependentTypesError(matcher.group(1), matcher.group(2));
+    } else {
+      throw new BugInCF("Cannot unparse: " + formattedError);
+    }
+  }
+
+  @Override
+  public boolean equals(@Nullable Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    DependentTypesError that = (DependentTypesError) o;
+
+    return expression.equals(that.expression) && error.equals(that.error);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(expression, error);
+  }
+
+  @Override
+  public String toString() {
+    return String.format(FORMAT_STRING, expression, error);
+  }
+
+  /**
+   * Like toString, but uses better formatting sometimes. Use this only for the final output,
+   * because of the design that hides error messages in toString().
+   */
+  public String format() {
+    Matcher m = FORMAL_PARAM_NAME_PATTERN.matcher(error);
+    if (m.matches()) {
+      return m.group(2);
+    }
+    return toString();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java
new file mode 100644
index 0000000..00cc26b
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java
@@ -0,0 +1,1194 @@
+package org.checkerframework.framework.util.dependenttypes;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModifiersTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.expression.FormalParameter;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.expression.JavaExpressionConverter;
+import org.checkerframework.dataflow.expression.LocalVariable;
+import org.checkerframework.dataflow.expression.Unknown;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeParameterBounds;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
+import org.checkerframework.framework.type.visitor.DoubleAnnotatedTypeScanner;
+import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
+import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
+import org.checkerframework.framework.util.StringToJavaExpression;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * A class that helps checkers use qualifiers that are represented by annotations with Java
+ * expression strings. This class performs the following main functions:
+ *
+ * <ol>
+ *   <li>Converts the expression strings in an {@link AnnotationMirror} {@code am}, by creating a
+ *       new annotation whose Java expression elements are the result of the conversion. See {@link
+ *       #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)}. Subclasses can
+ *       specialize this process by overriding methods in this class. Methods in this class always
+ *       standardize Java expressions and may additionally viewpoint-adapt or delocalize
+ *       expressions. Below is an explanation of each kind of conversion.
+ *       <ul>
+ *         <li>Standardization: the expressions in the annotations are converted such that two
+ *             expression strings that are equivalent are made to be equal. For example, an instance
+ *             field f may appear in an expression string as "f" or "this.f"; this class
+ *             standardizes both strings to "this.f". All dependent type annotations must be
+ *             standardized so that the implementation of {@link
+ *             org.checkerframework.framework.type.QualifierHierarchy#isSubtype(AnnotationMirror,
+ *             AnnotationMirror)} can assume that two expressions are equivalent if their string
+ *             representations are {@code equals()}.
+ *         <li>Viewpoint-adaption: converts an expression to some use site. For example, in method
+ *             bodies, formal parameter references such as "#2" are converted to the name of the
+ *             formal parameter. Another example, is at method call site, "this" is converted to the
+ *             receiver of the method invocation.
+ *         <li>Delocalization: removes all expressions with references to local variables that are
+ *             not parameters and changes parameters to the "#1" syntax.
+ *       </ul>
+ *   <li>If any of the conversions above results in an invalid expression, this class changes
+ *       invalid expression strings to an error string that includes the reason why the expression
+ *       is invalid. For example, {@code @KeyFor("m")} would be changed to {@code @KeyFor("[error
+ *       for expression: m error: m: identifier not found]")} if m is not a valid identifier. This
+ *       allows subtyping checks to assume that if two strings are equal and not errors, they
+ *       reference the same valid Java expression.
+ *   <li>Checks annotated types for error strings that have been added by this class and issues an
+ *       error if any are found.
+ * </ol>
+ *
+ * <p>Steps 2 and 3 are separated so that an error is issued only once per invalid expression string
+ * rather than every time the expression string is parsed. (The expression string is parsed multiple
+ * times because annotated types are created multiple times.)
+ */
+public class DependentTypesHelper {
+
+  /** AnnotatedTypeFactory */
+  protected final AnnotatedTypeFactory factory;
+
+  /**
+   * Maps from an annotation name, the fully-qualified name of its class, to its elements that are
+   * Java expressions.
+   */
+  private final Map<String, List<ExecutableElement>> annoToElements;
+
+  /** This scans an annotated type and returns a list of {@link DependentTypesError}. */
+  private final ExpressionErrorCollector expressionErrorCollector = new ExpressionErrorCollector();
+
+  /**
+   * A scanner that applies a function to each {@link AnnotationMirror} and replaces it in the given
+   * {@code AnnotatedTypeMirror}. (This side-effects the {@code AnnotatedTypeMirror}.)
+   */
+  private final AnnotatedTypeReplacer annotatedTypeReplacer = new AnnotatedTypeReplacer();
+
+  /**
+   * Copies annotations that might have been viewpoint adapted from the visited type (the first
+   * formal parameter of {@code ViewpointAdaptedCopier#visit}) to the second formal parameter.
+   */
+  private final ViewpointAdaptedCopier viewpointAdaptedCopier = new ViewpointAdaptedCopier();
+
+  /** The type mirror for java.lang.Object. */
+  protected final TypeMirror objectTM;
+
+  /**
+   * Creates a {@code DependentTypesHelper}.
+   *
+   * @param factory annotated type factory
+   */
+  public DependentTypesHelper(AnnotatedTypeFactory factory) {
+    this.factory = factory;
+
+    this.annoToElements = new HashMap<>();
+    for (Class<? extends Annotation> expressionAnno : factory.getSupportedTypeQualifiers()) {
+      List<ExecutableElement> elementList =
+          getExpressionElements(expressionAnno, factory.getProcessingEnv());
+      if (!elementList.isEmpty()) {
+        annoToElements.put(expressionAnno.getCanonicalName(), elementList);
+      }
+    }
+
+    this.objectTM =
+        TypesUtils.typeFromClass(Object.class, factory.types, factory.getElementUtils());
+  }
+
+  /**
+   * Returns true if any qualifier in the type system is a dependent type annotation.
+   *
+   * @return true if any qualifier in the type system is a dependent type annotation
+   */
+  public boolean hasDependentAnnotations() {
+    return !annoToElements.isEmpty();
+  }
+
+  /**
+   * Returns a list of the elements in the annotation class that should be interpreted as Java
+   * expressions, namely those annotated with {@code @}{@link JavaExpression}.
+   *
+   * @param clazz annotation class
+   * @param env processing environment for getting the ExecutableElement
+   * @return a list of the elements in the annotation class that should be interpreted as Java
+   *     expressions
+   */
+  private static List<ExecutableElement> getExpressionElements(
+      Class<? extends Annotation> clazz, ProcessingEnvironment env) {
+    Method[] methods = clazz.getMethods();
+    if (methods == null) {
+      return Collections.emptyList();
+    }
+    List<ExecutableElement> elements = new ArrayList<>();
+    for (Method method : methods) {
+      org.checkerframework.framework.qual.JavaExpression javaExpressionAnno =
+          method.getAnnotation(org.checkerframework.framework.qual.JavaExpression.class);
+      if (javaExpressionAnno != null) {
+        elements.add(TreeUtils.getMethod(clazz, method.getName(), method.getParameterCount(), env));
+      }
+    }
+    return elements;
+  }
+
+  /**
+   * Returns the elements of the annotation that are Java expressions.
+   *
+   * @param am AnnotationMirror
+   * @return the elements of the annotation that are Java expressions
+   */
+  private List<ExecutableElement> getListOfExpressionElements(AnnotationMirror am) {
+    return annoToElements.getOrDefault(AnnotationUtils.annotationName(am), Collections.emptyList());
+  }
+
+  /**
+   * Creates a TreeAnnotator that viewpoint-adapts dependent type annotations.
+   *
+   * @return a new TreeAnnotator that viewpoint-adapts dependent type annotations
+   */
+  public TreeAnnotator createDependentTypesTreeAnnotator() {
+    assert hasDependentAnnotations();
+    return new DependentTypesTreeAnnotator(factory, this);
+  }
+
+  ///
+  /// Methods that convert annotations
+  ///
+
+  /** If true, log information about where lambdas are created. */
+  private static boolean debugStringToJavaExpression = false;
+
+  /**
+   * Viewpoint-adapts the dependent type annotations on the bounds of the type parameters of the
+   * declaration of {@code typeUse} to {@code typeUse}.
+   *
+   * @param bounds annotated types of the bounds of the type parameters; its elements are
+   *     side-effected by this method (but the list itself is not side-effected)
+   * @param typeUse a use of a type with type parameter bounds {@code bounds}
+   */
+  public void atParameterizedTypeUse(
+      List<AnnotatedTypeParameterBounds> bounds, TypeElement typeUse) {
+    if (!hasDependentAnnotations()) {
+      return;
+    }
+
+    StringToJavaExpression stringToJavaExpr =
+        stringExpr -> StringToJavaExpression.atTypeDecl(stringExpr, typeUse, factory.getChecker());
+    if (debugStringToJavaExpression) {
+      System.out.printf(
+          "atParameterizedTypeUse(%s, %s) created %s%n", bounds, typeUse, stringToJavaExpr);
+    }
+    for (AnnotatedTypeParameterBounds bound : bounds) {
+      convertAnnotatedTypeMirror(stringToJavaExpr, bound.getUpperBound());
+      convertAnnotatedTypeMirror(stringToJavaExpr, bound.getLowerBound());
+    }
+  }
+
+  /**
+   * Viewpoint-adapts the dependent type annotations in the methodType to the methodInvocationTree.
+   *
+   * <p>{@code methodType} has been viewpoint-adapted to the call site, except for any dependent
+   * type annotations. This method viewpoint-adapts the dependent type annotations.
+   *
+   * @param methodType type of the method invocation; is side-effected by this method
+   * @param methodInvocationTree use of the method
+   */
+  public void atMethodInvocation(
+      AnnotatedExecutableType methodType, MethodInvocationTree methodInvocationTree) {
+    if (!hasDependentAnnotations()) {
+      return;
+    }
+    atInvocation(methodType, methodInvocationTree);
+  }
+
+  /**
+   * Viewpoint-adapts the dependent type annotations in the constructorType to the newClassTree.
+   *
+   * <p>{@code constructorType} has been viewpoint-adapted to the call site, except for any
+   * dependent type annotations. This method viewpoint-adapts the dependent type annotations.
+   *
+   * @param constructorType type of the constructor invocation; is side-effected by this method
+   * @param newClassTree invocation of the constructor
+   */
+  public void atConstructorInvocation(
+      AnnotatedExecutableType constructorType, NewClassTree newClassTree) {
+    if (!hasDependentAnnotations()) {
+      return;
+    }
+    atInvocation(constructorType, newClassTree);
+  }
+
+  /**
+   * Viewpoint-adapts a method or constructor invocation.
+   *
+   * <p>{@code methodType} has been viewpoint-adapted to the call site, except for any dependent
+   * type annotations. (For example, type variables have been substituted and polymorphic qualifiers
+   * have been resolved.) This method viewpoint-adapts the dependent type annotations.
+   *
+   * @param methodType type of the method or constructor invocation; is side-effected by this method
+   * @param tree invocation of the method or constructor
+   */
+  private void atInvocation(AnnotatedExecutableType methodType, ExpressionTree tree) {
+    assert hasDependentAnnotations();
+    Element methodElt = TreeUtils.elementFromUse(tree);
+    // Because methodType is the type post type variable substitution, it has annotations from
+    // both the method declaration and the type arguments at the use of the method. Annotations
+    // from type arguments must not be viewpoint-adapted to the call site. For example:
+    //   Map<String, String> map = ...;
+    //   List<@KeyFor("this.map") String> list = ...;
+    //   list.get(0)
+    //
+    // methodType is @KeyFor("this.map") String get(int)
+    // "this.map" must not be viewpoint-adapted to the invocation because it is not from
+    // the method declaration, but added during type variable substitution.
+    //
+    // So this implementation gets the declared type of the method, declaredMethodType,
+    // viewpoint-adapts all dependent type annotations in declaredMethodType to the call site,
+    // and then copies the viewpoint-adapted annotations from methodType except for types that
+    // are replaced by type variable substitution. (Those annotations are viewpoint-adapted
+    // before type variable substitution.)
+
+    // The annotations on `declaredMethodType` will be copied to `methodType`.
+    AnnotatedExecutableType declaredMethodType =
+        (AnnotatedExecutableType) factory.getAnnotatedType(methodElt);
+    if (!hasDependentType(declaredMethodType)) {
+      return;
+    }
+
+    StringToJavaExpression stringToJavaExpr;
+    if (tree instanceof MethodInvocationTree) {
+      stringToJavaExpr =
+          stringExpr ->
+              StringToJavaExpression.atMethodInvocation(
+                  stringExpr, (MethodInvocationTree) tree, factory.getChecker());
+      if (debugStringToJavaExpression) {
+        System.out.printf(
+            "atInvocation(%s, %s) 1 created %s%n",
+            methodType, TreeUtils.toStringTruncated(tree, 65), stringToJavaExpr);
+      }
+    } else if (tree instanceof NewClassTree) {
+      stringToJavaExpr =
+          stringExpr ->
+              StringToJavaExpression.atConstructorInvocation(
+                  stringExpr, (NewClassTree) tree, factory.getChecker());
+      if (debugStringToJavaExpression) {
+        System.out.printf(
+            "atInvocation(%s, %s) 2 created %s%n",
+            methodType, TreeUtils.toStringTruncated(tree, 65), stringToJavaExpr);
+      }
+    } else {
+      throw new BugInCF("Unexpected tree: %s kind: %s", tree, tree.getKind());
+    }
+    convertAnnotatedTypeMirror(stringToJavaExpr, declaredMethodType);
+    this.viewpointAdaptedCopier.visit(declaredMethodType, methodType);
+  }
+
+  /**
+   * Viewpoint-adapts the Java expressions in annotations written on a field declaration to the use
+   * at {@code fieldAccess}.
+   *
+   * @param type its type; is side-effected by this method
+   * @param fieldAccess a field access
+   */
+  public void atFieldAccess(AnnotatedTypeMirror type, MemberSelectTree fieldAccess) {
+    if (!hasDependentType(type)) {
+      return;
+    }
+
+    StringToJavaExpression stringToJavaExpr =
+        stringExpr ->
+            StringToJavaExpression.atFieldAccess(stringExpr, fieldAccess, factory.getChecker());
+    if (debugStringToJavaExpression) {
+      System.out.printf(
+          "atFieldAccess(%s, %s) created %s%n",
+          type, TreeUtils.toStringTruncated(fieldAccess, 65), stringToJavaExpr);
+    }
+    convertAnnotatedTypeMirror(stringToJavaExpr, type);
+  }
+
+  /**
+   * Viewpoint-adapts the Java expressions in annotations written on the signature of the method
+   * declaration (for example, a return type) to the body of the method. This means the parameter
+   * syntax, e.g. "#2", is converted to the names of the parameter.
+   *
+   * @param atm a type at the method signature; is side-effected by this method
+   * @param methodDeclTree a method declaration
+   */
+  public void atMethodBody(AnnotatedTypeMirror atm, MethodTree methodDeclTree) {
+    if (!hasDependentType(atm)) {
+      return;
+    }
+
+    StringToJavaExpression stringToJavaExpr =
+        stringExpr ->
+            StringToJavaExpression.atMethodBody(stringExpr, methodDeclTree, factory.getChecker());
+    if (debugStringToJavaExpression) {
+      System.out.printf(
+          "atMethodBody(%s, %s) 1 created %s%n",
+          atm, TreeUtils.toStringTruncated(methodDeclTree, 65), stringToJavaExpr);
+    }
+    convertAnnotatedTypeMirror(stringToJavaExpr, atm);
+  }
+
+  /**
+   * Standardizes the Java expressions in annotations to a type declaration.
+   *
+   * @param type the type of the type declaration; is side-effected by this method
+   * @param typeElt the element of the type declaration
+   */
+  public void atTypeDecl(AnnotatedTypeMirror type, TypeElement typeElt) {
+    if (!hasDependentType(type)) {
+      return;
+    }
+
+    StringToJavaExpression stringToJavaExpr =
+        stringExpr -> StringToJavaExpression.atTypeDecl(stringExpr, typeElt, factory.getChecker());
+    if (debugStringToJavaExpression) {
+      System.out.printf("atTypeDecl(%s, %s) created %s%n", type, typeElt, stringToJavaExpr);
+    }
+    convertAnnotatedTypeMirror(stringToJavaExpr, type);
+  }
+
+  /** A set containing {@link Tree.Kind#METHOD} and {@link Tree.Kind#LAMBDA_EXPRESSION}. */
+  private static final Set<Tree.Kind> METHOD_OR_LAMBDA =
+      EnumSet.of(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION);
+
+  /**
+   * Standardize the Java expressions in annotations in a variable declaration. Converts the
+   * parameter syntax, e.g "#1", to the parameter name.
+   *
+   * @param type the type of the variable declaration; is side-effected by this method
+   * @param declarationTree the variable declaration
+   * @param variableElt the element of the variable declaration
+   */
+  public void atVariableDeclaration(
+      AnnotatedTypeMirror type, Tree declarationTree, VariableElement variableElt) {
+    if (!hasDependentType(type)) {
+      return;
+    }
+
+    TreePath pathToVariableDecl = factory.getPath(declarationTree);
+    if (pathToVariableDecl == null) {
+      // If this is a synthetic created by dataflow, the path will be null.
+      return;
+    }
+    switch (variableElt.getKind()) {
+      case PARAMETER:
+        TreePath pathTillEnclTree =
+            TreePathUtil.pathTillOfKind(pathToVariableDecl, METHOD_OR_LAMBDA);
+        if (pathTillEnclTree == null) {
+          throw new BugInCF("no enclosing method or lambda found for " + variableElt);
+        }
+        Tree enclTree = pathTillEnclTree.getLeaf();
+
+        if (enclTree.getKind() == Kind.METHOD) {
+          MethodTree methodDeclTree = (MethodTree) enclTree;
+          StringToJavaExpression stringToJavaExpr =
+              stringExpr ->
+                  StringToJavaExpression.atMethodBody(
+                      stringExpr, methodDeclTree, factory.getChecker());
+          if (debugStringToJavaExpression) {
+            System.out.printf(
+                "atVariableDeclaration(%s, %s, %s) 1 created %s%n",
+                type,
+                TreeUtils.toStringTruncated(declarationTree, 65),
+                variableElt,
+                stringToJavaExpr);
+          }
+          convertAnnotatedTypeMirror(stringToJavaExpr, type);
+        } else {
+          // Lambdas can use local variables defined in the enclosing method, so allow
+          // identifiers to be locals in scope at the location of the lambda.
+          StringToJavaExpression stringToJavaExpr =
+              stringExpr ->
+                  StringToJavaExpression.atLambdaParameter(
+                      stringExpr,
+                      (LambdaExpressionTree) enclTree,
+                      pathToVariableDecl.getParentPath(),
+                      factory.getChecker());
+          if (debugStringToJavaExpression) {
+            System.out.printf(
+                "atVariableDeclaration(%s, %s, %s) 2 created %s%n",
+                type,
+                TreeUtils.toStringTruncated(declarationTree, 65),
+                variableElt,
+                stringToJavaExpr);
+          }
+          convertAnnotatedTypeMirror(stringToJavaExpr, type);
+        }
+        break;
+
+      case LOCAL_VARIABLE:
+      case RESOURCE_VARIABLE:
+      case EXCEPTION_PARAMETER:
+        StringToJavaExpression stringToJavaExprVar =
+            stringExpr ->
+                StringToJavaExpression.atPath(stringExpr, pathToVariableDecl, factory.getChecker());
+        if (debugStringToJavaExpression) {
+          System.out.printf(
+              "atVariableDeclaration(%s, %s, %s) 3 created %s%n",
+              type,
+              TreeUtils.toStringTruncated(declarationTree, 65),
+              variableElt,
+              stringToJavaExprVar);
+        }
+        convertAnnotatedTypeMirror(stringToJavaExprVar, type);
+        break;
+
+      case FIELD:
+      case ENUM_CONSTANT:
+        StringToJavaExpression stringToJavaExprField =
+            stringExpr ->
+                StringToJavaExpression.atFieldDecl(stringExpr, variableElt, factory.getChecker());
+        if (debugStringToJavaExpression) {
+          System.out.printf(
+              "atVariableDeclaration(%s, %s, %s) 4 created %s%n",
+              type,
+              TreeUtils.toStringTruncated(declarationTree, 65),
+              variableElt,
+              stringToJavaExprField);
+        }
+        convertAnnotatedTypeMirror(stringToJavaExprField, type);
+        break;
+
+      default:
+        throw new BugInCF(
+            "unexpected element kind " + variableElt.getKind() + " for " + variableElt);
+    }
+  }
+
+  /**
+   * Standardize the Java expressions in annotations in written in the {@code expressionTree}. Also,
+   * converts the parameter syntax, e.g. "#1", to the parameter name.
+   *
+   * <p>{@code expressionTree} must be an expressions which can contain explicitly written
+   * annotations, namely a {@link NewClassTree}, {@link com.sun.source.tree.NewArrayTree}, or {@link
+   * com.sun.source.tree.TypeCastTree}. For example, this method standardizes the {@code KeyFor}
+   * annotation in {@code (@KeyFor("map") String) key }.
+   *
+   * @param annotatedType its type; is side-effected by this method
+   * @param expressionTree a {@link NewClassTree}, {@link com.sun.source.tree.NewArrayTree}, or
+   *     {@link com.sun.source.tree.TypeCastTree}
+   */
+  public void atExpression(AnnotatedTypeMirror annotatedType, ExpressionTree expressionTree) {
+    if (!hasDependentType(annotatedType)) {
+      return;
+    }
+
+    TreePath path = factory.getPath(expressionTree);
+    if (path == null) {
+      return;
+    }
+    StringToJavaExpression stringToJavaExpr =
+        stringExpr -> StringToJavaExpression.atPath(stringExpr, path, factory.getChecker());
+    if (debugStringToJavaExpression) {
+      System.out.printf(
+          "atExpression(%s, %s) created %s%n",
+          annotatedType, TreeUtils.toStringTruncated(expressionTree, 65), stringToJavaExpr);
+    }
+    convertAnnotatedTypeMirror(stringToJavaExpr, annotatedType);
+  }
+
+  /**
+   * Standardize the Java expressions in annotations in a type. Converts the parameter syntax, e.g.
+   * "#2", to the parameter name.
+   *
+   * @param type the type to standardize; is side-effected by this method
+   * @param elt the element whose type is {@code type}
+   */
+  public void atLocalVariable(AnnotatedTypeMirror type, Element elt) {
+    if (!hasDependentType(type)) {
+      return;
+    }
+
+    switch (elt.getKind()) {
+      case PARAMETER:
+      case LOCAL_VARIABLE:
+      case RESOURCE_VARIABLE:
+      case EXCEPTION_PARAMETER:
+        Tree declarationTree = factory.declarationFromElement(elt);
+        if (declarationTree == null) {
+          if (elt.getKind() == ElementKind.PARAMETER) {
+            // The tree might be null when
+            // org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() gets the
+            // assignment context for a pseudo assignment of an argument to a method parameter.
+            return;
+          }
+          throw new BugInCF(this.getClass() + ": tree not found");
+        } else if (TreeUtils.typeOf(declarationTree) == null) {
+          // org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() gets the
+          // assignment context for a pseudo assignment of an argument to a method parameter.
+          return;
+        }
+
+        atVariableDeclaration(type, declarationTree, (VariableElement) elt);
+        return;
+
+      default:
+        // It's not a local variable (it might be METHOD, CONSTRUCTOR, CLASS, or INTERFACE,
+        // for example), so there is nothing to do.
+        break;
+    }
+  }
+
+  /** Thrown when a non-parameter local variable is found. */
+  @SuppressWarnings("serial")
+  private static class FoundLocalException extends RuntimeException {}
+
+  /**
+   * Viewpoint-adapt all dependent type annotations to the method declaration, {@code
+   * methodDeclTree}. This method changes occurrences of formal parameter names to the "#2" syntax,
+   * and it removes expressions that contain other local variables.
+   *
+   * <p>If a Java expression in {@code atm} references local variables (other than formal
+   * parameters), the expression is removed from the annotation. This could result in dependent type
+   * annotations with empty lists of expressions. If this is a problem, a subclass can override
+   * {@link #buildAnnotation(AnnotationMirror, Map)} to do something besides creating an annotation
+   * with a empty list.
+   *
+   * @param atm type to viewpoint-adapt; is side-effected by this method
+   * @param methodDeclTree the method declaration to which the annotations are viewpoint-adapted
+   */
+  public void delocalize(AnnotatedTypeMirror atm, MethodTree methodDeclTree) {
+    if (!hasDependentType(atm)) {
+      return;
+    }
+
+    TreePath pathToMethodDecl = factory.getPath(methodDeclTree);
+    ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodDeclTree);
+    List<FormalParameter> parameters = JavaExpression.getFormalParameters(methodElement);
+    List<JavaExpression> paramsAsLocals =
+        JavaExpression.getParametersAsLocalVariables(methodElement);
+
+    StringToJavaExpression stringToJavaExpr =
+        expression -> {
+          JavaExpression javaExpr;
+          try {
+            javaExpr =
+                StringToJavaExpression.atPath(expression, pathToMethodDecl, factory.getChecker());
+          } catch (JavaExpressionParseException ex) {
+            return null;
+          }
+          JavaExpressionConverter jec =
+              new JavaExpressionConverter() {
+                @Override
+                protected JavaExpression visitLocalVariable(
+                    LocalVariable localVarExpr, Void unused) {
+                  int index = paramsAsLocals.indexOf(localVarExpr);
+                  if (index == -1) {
+                    throw new FoundLocalException();
+                  }
+                  return parameters.get(index);
+                }
+              };
+          try {
+            return jec.convert(javaExpr);
+          } catch (FoundLocalException ex) {
+            return null;
+          }
+        };
+    if (debugStringToJavaExpression) {
+      System.out.printf(
+          "delocalize(%s, %s) created %s%n",
+          atm, TreeUtils.toStringTruncated(methodDeclTree, 65), stringToJavaExpr);
+    }
+    convertAnnotatedTypeMirror(stringToJavaExpr, atm);
+  }
+
+  /**
+   * Calls {@link #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)} on each
+   * annotation mirror on type with {@code stringToJavaExpr}. And replaces the annotation with the
+   * one created by {@code convertAnnotationMirror}, if it's not null. If it is null, the original
+   * annotation is used. See {@link #convertAnnotationMirror(StringToJavaExpression,
+   * AnnotationMirror)} for more details.
+   *
+   * @param stringToJavaExpr function to convert a string to a {@link JavaExpression}
+   * @param type the type that is side-effected by this method
+   */
+  protected void convertAnnotatedTypeMirror(
+      StringToJavaExpression stringToJavaExpr, AnnotatedTypeMirror type) {
+    this.annotatedTypeReplacer.visit(type, anno -> convertAnnotationMirror(stringToJavaExpr, anno));
+  }
+
+  /**
+   * Given an annotation {@code anno}, this method builds a new annotation with the Java expressions
+   * transformed according to {@code stringToJavaExpr}. If {@code anno} is not a dependent type
+   * annotation, {@code null} is returned.
+   *
+   * <p>If {@code stringToJavaExpr} returns {@code null}, then that expression is removed from the
+   * returned annotation.
+   *
+   * <p>Instead of overriding this method, subclasses can override the following methods to change
+   * the behavior of this class:
+   *
+   * <ul>
+   *   <li>{@link #shouldPassThroughExpression(String)}: to control which expressions are skipped.
+   *       If this method returns true, then the expression string is not parsed and is included in
+   *       the new annotation unchanged.
+   *   <li>{@link #transform(JavaExpression)}: make changes to the JavaExpression produced by {@code
+   *       stringToJavaExpr}.
+   *   <li>{@link #buildAnnotation(AnnotationMirror, Map)}: to change the annotation returned by
+   *       this method.
+   * </ul>
+   *
+   * @param stringToJavaExpr function that converts strings to {@code JavaExpression}s
+   * @param anno annotation mirror
+   * @return an annotation created by applying {@code stringToJavaExpr} to all expression strings in
+   *     {@code anno}, or null if there would be no effect
+   */
+  public @Nullable AnnotationMirror convertAnnotationMirror(
+      StringToJavaExpression stringToJavaExpr, AnnotationMirror anno) {
+    if (!isExpressionAnno(anno)) {
+      return null;
+    }
+
+    Map<ExecutableElement, List<JavaExpression>> newElements = new HashMap<>();
+    for (ExecutableElement element : getListOfExpressionElements(anno)) {
+      List<String> expressionStrings =
+          AnnotationUtils.getElementValueArray(
+              anno, element, String.class, Collections.emptyList());
+      List<JavaExpression> javaExprs = new ArrayList<>(expressionStrings.size());
+      newElements.put(element, javaExprs);
+      for (String expression : expressionStrings) {
+        JavaExpression result;
+        if (shouldPassThroughExpression(expression)) {
+          result = new PassThroughExpression(objectTM, expression);
+        } else {
+          try {
+            result = stringToJavaExpr.toJavaExpression(expression);
+          } catch (JavaExpressionParseException e) {
+            result = createError(expression, e);
+          }
+        }
+
+        if (result != null) {
+          result = transform(result);
+          javaExprs.add(result);
+        }
+      }
+    }
+    return buildAnnotation(anno, newElements);
+  }
+
+  /**
+   * This method is for subclasses to override to change JavaExpressions in some way before they are
+   * inserted into new annotations. This method is called after parsing and viewpoint-adaptation
+   * have occurred. {@code javaExpr} may be a {@link PassThroughExpression}.
+   *
+   * <p>If {@code null} is returned then the expression is not added to the new annotation.
+   *
+   * <p>The default implementation returns the argument, but subclasses may override it.
+   *
+   * @param javaExpr a JavaExpression
+   * @return a transformed JavaExpression or {@code null} if no transformation exists
+   */
+  protected @Nullable JavaExpression transform(JavaExpression javaExpr) {
+    return javaExpr;
+  }
+
+  /**
+   * Whether or not {@code expression} should be passed to the new annotation unchanged. If this
+   * method returns true, the {@code expression} is not parsed.
+   *
+   * <p>The default implementation returns true if the {@code expression} is an expression error
+   * according to {@link DependentTypesError#isExpressionError(String)}. Subclasses may override
+   * this method to add additional logic.
+   *
+   * @param expression an expression string in a dependent types annotation
+   * @return whether or not {@code expression} should be passed through unchanged to the new
+   *     annotation
+   */
+  protected boolean shouldPassThroughExpression(String expression) {
+    return DependentTypesError.isExpressionError(expression);
+  }
+
+  /**
+   * Create a new annotation of the same type as {@code originalAnno} using the provided {@code
+   * elementMap}.
+   *
+   * @param originalAnno the annotation passed to {@link
+   *     #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)} (this method is a
+   *     helper method for {@link #convertAnnotationMirror(StringToJavaExpression,
+   *     AnnotationMirror)})
+   * @param elementMap a mapping from element of {@code originalAnno} to {@code JavaExpression}s
+   * @return an annotation created from {@code elementMap}
+   */
+  protected AnnotationMirror buildAnnotation(
+      AnnotationMirror originalAnno, Map<ExecutableElement, List<JavaExpression>> elementMap) {
+    AnnotationBuilder builder =
+        new AnnotationBuilder(
+            factory.getProcessingEnv(), AnnotationUtils.annotationName(originalAnno));
+    builder.copyElementValuesFromAnnotation(originalAnno, elementMap.keySet());
+    for (Map.Entry<ExecutableElement, List<JavaExpression>> entry : elementMap.entrySet()) {
+      List<String> strings = CollectionsPlume.mapList(JavaExpression::toString, entry.getValue());
+      builder.setValue(entry.getKey(), strings);
+    }
+    return builder.build();
+  }
+
+  /**
+   * A {@link JavaExpression} that does not represent a {@link JavaExpression}, but rather allows an
+   * expression string to be converted to a JavaExpression and then to a string without parsing.
+   */
+  static class PassThroughExpression extends Unknown {
+    /** Some string. */
+    public final String string;
+
+    /**
+     * Creates a PassThroughExpression.
+     *
+     * @param type some type
+     * @param string the string to convert to a JavaExpression
+     */
+    public PassThroughExpression(TypeMirror type, String string) {
+      super(type);
+      this.string = string;
+    }
+
+    @Override
+    public String toString() {
+      return string;
+    }
+  }
+
+  /**
+   * Creates a {@link JavaExpression} representing the exception thrown when parsing {@code
+   * expression}.
+   *
+   * @param expression an expression that caused {@code e} when parsed
+   * @param e the exception thrown when parsing {@code expression}
+   * @return a java expression
+   */
+  protected PassThroughExpression createError(String expression, JavaExpressionParseException e) {
+    return new PassThroughExpression(objectTM, new DependentTypesError(expression, e).toString());
+  }
+
+  /**
+   * Creates a {@link JavaExpression} representing the error caused when parsing {@code expression}
+   *
+   * @param expression an expression that caused {@code error} when parsed
+   * @param error the error message caused by {@code expression}
+   * @return a java expression
+   */
+  protected PassThroughExpression createError(String expression, String error) {
+    return new PassThroughExpression(
+        objectTM, new DependentTypesError(expression, error).toString());
+  }
+
+  /**
+   * Applies the passed function to each annotation in the given {@link AnnotatedTypeMirror}. If the
+   * function returns a non-null annotation, then the original annotation is replaced with the
+   * result. If the function returns null, the original annotation is retained.
+   */
+  private static class AnnotatedTypeReplacer
+      extends AnnotatedTypeScanner<Void, Function<AnnotationMirror, AnnotationMirror>> {
+
+    @Override
+    public Void visitTypeVariable(
+        AnnotatedTypeMirror.AnnotatedTypeVariable type,
+        Function<AnnotationMirror, AnnotationMirror> func) {
+      if (visitedNodes.containsKey(type)) {
+        return visitedNodes.get(type);
+      }
+      visitedNodes.put(type, null);
+
+      // If the type variable has a primary annotation, then it is viewpoint-adapted before this
+      // method is called.  The viewpoint-adapted primary annotation was already copied to the upper
+      // and lower bounds.  These annotations cannot be viewpoint-adapted again, so remove them,
+      // viewpoint-adapt any other annotations in the bound, and then add them back.
+      Set<AnnotationMirror> primarys = type.getAnnotations();
+      type.getLowerBound().removeAnnotations(primarys);
+      Void r = scan(type.getLowerBound(), func);
+      type.getLowerBound().addAnnotations(primarys);
+      visitedNodes.put(type, r);
+
+      type.getUpperBound().removeAnnotations(primarys);
+      r = scanAndReduce(type.getUpperBound(), func, r);
+      type.getUpperBound().addAnnotations(primarys);
+      visitedNodes.put(type, r);
+      return r;
+    }
+
+    @Override
+    protected Void scan(
+        AnnotatedTypeMirror type, Function<AnnotationMirror, AnnotationMirror> func) {
+      for (AnnotationMirror anno : AnnotationUtils.createAnnotationSet(type.getAnnotations())) {
+        AnnotationMirror newAnno = func.apply(anno);
+        if (newAnno != null) {
+          // This code must remove and then add, rather than call `replace`, because a
+          // type may have multiple annotations with the same class, but different
+          // elements.  (This is a bug; see
+          // https://github.com/typetools/checker-framework/issues/4451.)
+          // AnnotatedTypeMirror#replace only removes one annotation that is in the same
+          // hierarchy as the passed argument.
+          type.removeAnnotation(anno);
+          type.addAnnotation(newAnno);
+        }
+      }
+      return super.scan(type, func);
+    }
+  }
+
+  ///
+  /// Methods that check and report errors
+  ///
+
+  /**
+   * Reports an expression.unparsable error for each Java expression in the given type that is an
+   * expression error string.
+   *
+   * @param atm annotated type to check for expression errors
+   * @param errorTree the tree at which to report any found errors
+   */
+  public void checkTypeForErrorExpressions(AnnotatedTypeMirror atm, Tree errorTree) {
+    if (!hasDependentAnnotations()) {
+      return;
+    }
+
+    List<DependentTypesError> errors = expressionErrorCollector.visit(atm);
+    if (errors.isEmpty()) {
+      return;
+    }
+
+    if (errorTree.getKind() == Kind.VARIABLE) {
+      ModifiersTree modifiers = ((VariableTree) errorTree).getModifiers();
+      errorTree = ((VariableTree) errorTree).getType();
+      for (AnnotationTree annoTree : modifiers.getAnnotations()) {
+        String annoString = annoTree.toString();
+        for (String annoName : annoToElements.keySet()) {
+          // TODO: Simple string containment seems too simplistic.  At least check for a word
+          // boundary.
+          if (annoString.contains(annoName)) {
+            errorTree = annoTree;
+            break;
+          }
+        }
+      }
+    }
+    reportErrors(errorTree, errors);
+  }
+
+  /**
+   * Report the given errors as "expression.unparsable".
+   *
+   * @param errorTree where to report the errors
+   * @param errors the errors to report
+   */
+  protected void reportErrors(Tree errorTree, List<DependentTypesError> errors) {
+    SourceChecker checker = factory.getChecker();
+    for (DependentTypesError dte : errors) {
+      checker.reportError(errorTree, "expression.unparsable", dte.format());
+    }
+  }
+
+  /**
+   * Returns a list of {@link DependentTypesError}s for all the Java expression elements of the
+   * annotation that are an error string as specified by DependentTypesError#isExpressionError.
+   *
+   * @param am an annotation
+   * @return a list of {@link DependentTypesError}s for the error strings in the given annotation
+   */
+  private List<DependentTypesError> errorElements(AnnotationMirror am) {
+    assert hasDependentAnnotations();
+
+    List<DependentTypesError> errors = new ArrayList<>();
+
+    for (ExecutableElement element : getListOfExpressionElements(am)) {
+      // It's always an array, not a single value, because @JavaExpression may only be written
+      // on an annotation element of type String[].
+      List<String> value =
+          AnnotationUtils.getElementValueArray(am, element, String.class, Collections.emptyList());
+      for (String v : value) {
+        if (DependentTypesError.isExpressionError(v)) {
+          errors.add(DependentTypesError.unparse(v));
+        }
+      }
+    }
+    return errors;
+  }
+
+  /**
+   * Reports a flowexpr.parse.error error for each Java expression in the given annotation that is
+   * an expression error string.
+   *
+   * @param annotation annotation to check
+   * @param errorTree location at which to issue errors
+   */
+  public void checkAnnotationForErrorExpressions(AnnotationMirror annotation, Tree errorTree) {
+    if (!hasDependentAnnotations()) {
+      return;
+    }
+
+    List<DependentTypesError> errors = errorElements(annotation);
+    if (errors.isEmpty()) {
+      return;
+    }
+    SourceChecker checker = factory.getChecker();
+    for (DependentTypesError error : errors) {
+      checker.reportError(errorTree, "flowexpr.parse.error", error);
+    }
+  }
+
+  /**
+   * Reports an expression.unparsable error for each Java expression in the given class declaration
+   * AnnotatedTypeMirror that is an expression error string. Note that this reports errors in the
+   * class declaration itself, not the body or extends/implements clauses.
+   *
+   * @param classTree class to check
+   * @param type annotated type of the class
+   */
+  public void checkClassForErrorExpressions(ClassTree classTree, AnnotatedDeclaredType type) {
+    if (!hasDependentAnnotations()) {
+      return;
+    }
+
+    // TODO: check that invalid annotations in type variable bounds are properly
+    // formatted. They are part of the type, but the output isn't nicely formatted.
+    checkTypeForErrorExpressions(type, classTree);
+  }
+
+  /**
+   * Reports an expression.unparsable error for each Java expression in the method declaration
+   * AnnotatedTypeMirror that is an expression error string.
+   *
+   * @param methodDeclTree method to check
+   * @param type annotated type of the method
+   */
+  public void checkMethodForErrorExpressions(
+      MethodTree methodDeclTree, AnnotatedExecutableType type) {
+    if (!hasDependentAnnotations()) {
+      return;
+    }
+
+    // Parameters and receivers are checked by visitVariable
+    // So only type parameters and return type need to be checked here.
+
+    checkTypeVariablesForErrorExpressions(methodDeclTree, type);
+    // Check return type
+    if (type.getReturnType().getKind() != TypeKind.VOID) {
+      AnnotatedTypeMirror returnType = factory.getMethodReturnType(methodDeclTree);
+      Tree treeForError =
+          TreeUtils.isConstructor(methodDeclTree) ? methodDeclTree : methodDeclTree.getReturnType();
+      checkTypeForErrorExpressions(returnType, treeForError);
+    }
+  }
+
+  /**
+   * Reports an expression.unparsable error for each Java expression in the given type variables
+   * that is an expression error string.
+   *
+   * @param node a method declaration
+   * @param methodType annotated type of the method
+   */
+  private void checkTypeVariablesForErrorExpressions(
+      MethodTree node, AnnotatedExecutableType methodType) {
+    for (int i = 0; i < methodType.getTypeVariables().size(); i++) {
+      AnnotatedTypeMirror atm = methodType.getTypeVariables().get(i);
+      StringToJavaExpression stringToJavaExpr =
+          stringExpr -> StringToJavaExpression.atMethodBody(stringExpr, node, factory.getChecker());
+      if (debugStringToJavaExpression) {
+        System.out.printf(
+            "checkTypeVariablesForErrorExpressions(%s, %s) created %s%n",
+            node, methodType, stringToJavaExpr);
+      }
+      convertAnnotatedTypeMirror(stringToJavaExpr, atm);
+      checkTypeForErrorExpressions(atm, node.getTypeParameters().get(i));
+    }
+  }
+
+  /**
+   * Returns true if {@code am} is an expression annotation, that is, an annotation whose element is
+   * a Java expression.
+   *
+   * @param am an annotation
+   * @return true if {@code am} is an expression annotation
+   */
+  private boolean isExpressionAnno(AnnotationMirror am) {
+    if (!hasDependentAnnotations()) {
+      return false;
+    }
+    return annoToElements.containsKey(AnnotationUtils.annotationName(am));
+  }
+
+  /**
+   * Checks all dependent type annotations in the given annotated type to see if the expression
+   * string is an error string as specified by DependentTypesError#isExpressionError. If the
+   * annotated type has any errors, then a non-empty list of {@link DependentTypesError} is
+   * returned.
+   */
+  private class ExpressionErrorCollector
+      extends SimpleAnnotatedTypeScanner<List<DependentTypesError>, Void> {
+
+    /** Create ExpressionErrorCollector. */
+    private ExpressionErrorCollector() {
+      super(
+          (AnnotatedTypeMirror type, Void aVoid) -> {
+            List<DependentTypesError> errors = new ArrayList<>();
+            for (AnnotationMirror am : type.getAnnotations()) {
+              if (isExpressionAnno(am)) {
+                errors.addAll(errorElements(am));
+              }
+            }
+            return errors;
+          },
+          DependentTypesHelper::concatenate,
+          Collections.emptyList());
+    }
+  }
+
+  /**
+   * Appends list2 to list1 in a new list. If either list is empty, returns the other. Thus, the
+   * result may be aliased to one of the arguments and the client should only read, not write into,
+   * the result.
+   *
+   * @param list1 a list
+   * @param list2 a list
+   * @return the lists, concatenated
+   */
+  private static List<DependentTypesError> concatenate(
+      List<DependentTypesError> list1, List<DependentTypesError> list2) {
+    if (list1.isEmpty()) {
+      return list2;
+    } else if (list2.isEmpty()) {
+      return list1;
+    }
+    List<DependentTypesError> newList = new ArrayList<>(list1.size() + list2.size());
+    newList.addAll(list1);
+    newList.addAll(list2);
+    return newList;
+  }
+
+  /**
+   * The underlying type of the second parameter is the result of applying type variable
+   * substitution to the visited type (the first parameter). This class copies annotations from the
+   * visited type to the second formal parameter except for annotations on types that have been
+   * substituted.
+   */
+  private class ViewpointAdaptedCopier extends DoubleAnnotatedTypeScanner<Void> {
+    @Override
+    protected Void scan(AnnotatedTypeMirror from, AnnotatedTypeMirror to) {
+      if (from == null || to == null) {
+        return null;
+      }
+      Set<AnnotationMirror> replacements = AnnotationUtils.createAnnotationSet();
+      for (String vpa : annoToElements.keySet()) {
+        AnnotationMirror anno = from.getAnnotation(vpa);
+        if (anno != null) {
+          // Only replace annotations that might have been changed.
+          replacements.add(anno);
+        }
+      }
+      to.replaceAnnotations(replacements);
+      if (from.getKind() != to.getKind()) {
+        // If the underlying types don't match, then this from has been substituted for a
+        // from variable, so don't recur. The primary annotation was copied because
+        // the from variable might have had a primary annotation at a use.
+        // For example:
+        // <T> void method(@KeyFor("a") T t) {...}
+        // void use(@KeyFor("b") String s) {
+        //      method(s);  // the from of the parameter should be @KeyFor("a") String
+        // }
+        return null;
+      }
+      return super.scan(from, to);
+    }
+
+    @Override
+    protected Void defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) {
+      if (type1 == null || type2 == null) {
+        return null;
+      }
+      if (type1.getKind() != type2.getKind()) {
+        throw new BugInCF("Should be the same. type: %s p: %s ", type1, type2);
+      }
+      return null;
+    }
+  }
+
+  /**
+   * Returns true if {@code atm} has any dependent type annotations. If an annotated type does not
+   * have a dependent type annotation, then no standardization or viewpoint adaption is performed.
+   * (This check avoids calling time-intensive methods unless required.)
+   *
+   * @param atm a type
+   * @return true if {@code atm} has any dependent type annotations
+   */
+  private boolean hasDependentType(AnnotatedTypeMirror atm) {
+    if (atm == null) {
+      return false;
+    }
+    // This is a test about the type system.
+    if (!hasDependentAnnotations()) {
+      return false;
+    }
+    // This is a test about this specific type.
+    return hasDependentTypeScanner.visit(atm);
+  }
+
+  /** Returns true if the passed AnnotatedTypeMirror has any dependent type annotations. */
+  private final AnnotatedTypeScanner<Boolean, Void> hasDependentTypeScanner =
+      new SimpleAnnotatedTypeScanner<>(
+          (type, __) -> {
+            for (AnnotationMirror annotationMirror : type.getAnnotations()) {
+              if (isExpressionAnno(annotationMirror)) {
+                return true;
+              }
+            }
+            return false;
+          },
+          Boolean::logicalOr,
+          false);
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesTreeAnnotator.java
new file mode 100644
index 0000000..8f907c6
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesTreeAnnotator.java
@@ -0,0 +1,81 @@
+package org.checkerframework.framework.util.dependenttypes;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.VariableTree;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * Standardizes Java expressions in annotations and also viewpoint-adapts field accesses. Other
+ * viewpoint adaption is handled in {@link DependentTypesHelper}.
+ */
+public class DependentTypesTreeAnnotator extends TreeAnnotator {
+  private final DependentTypesHelper helper;
+
+  public DependentTypesTreeAnnotator(
+      AnnotatedTypeFactory atypeFactory, DependentTypesHelper helper) {
+    super(atypeFactory);
+    this.helper = helper;
+  }
+
+  @Override
+  public Void visitClass(ClassTree node, AnnotatedTypeMirror annotatedTypeMirror) {
+    TypeElement ele = TreeUtils.elementFromDeclaration(node);
+    helper.atTypeDecl(annotatedTypeMirror, ele);
+    return super.visitClass(node, annotatedTypeMirror);
+  }
+
+  @Override
+  public Void visitNewArray(NewArrayTree node, AnnotatedTypeMirror annotatedType) {
+    helper.atExpression(annotatedType, node);
+    return super.visitNewArray(node, annotatedType);
+  }
+
+  @Override
+  public Void visitNewClass(NewClassTree node, AnnotatedTypeMirror annotatedType) {
+    helper.atExpression(annotatedType, node);
+    return super.visitNewClass(node, annotatedType);
+  }
+
+  @Override
+  public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror annotatedType) {
+    helper.atExpression(annotatedType, node);
+    return super.visitTypeCast(node, annotatedType);
+  }
+
+  @Override
+  public Void visitVariable(VariableTree node, AnnotatedTypeMirror annotatedTypeMirror) {
+    VariableElement ele = TreeUtils.elementFromDeclaration(node);
+    helper.atVariableDeclaration(annotatedTypeMirror, node, ele);
+    return super.visitVariable(node, annotatedTypeMirror);
+  }
+
+  @Override
+  public Void visitIdentifier(IdentifierTree node, AnnotatedTypeMirror annotatedTypeMirror) {
+    Element ele = TreeUtils.elementFromUse(node);
+    if (ele.getKind() == ElementKind.FIELD || ele.getKind() == ElementKind.ENUM_CONSTANT) {
+      helper.atVariableDeclaration(annotatedTypeMirror, node, (VariableElement) ele);
+    }
+    return super.visitIdentifier(node, annotatedTypeMirror);
+  }
+
+  @Override
+  public Void visitMemberSelect(MemberSelectTree node, AnnotatedTypeMirror type) {
+    Element ele = TreeUtils.elementFromUse(node);
+    if (ele.getKind() == ElementKind.FIELD || ele.getKind() == ElementKind.ENUM_CONSTANT) {
+      helper.atFieldAccess(type, node);
+    }
+    return super.visitMemberSelect(node, type);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/ClassTypeParamApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/ClassTypeParamApplier.java
new file mode 100644
index 0000000..0b687f3
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/element/ClassTypeParamApplier.java
@@ -0,0 +1,108 @@
+package org.checkerframework.framework.util.element;
+
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.TargetType;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Applies the annotations present for a class type parameter onto an AnnotatedTypeVariable. See
+ * {@link TypeParamElementAnnotationApplier} for details.
+ */
+public class ClassTypeParamApplier extends TypeParamElementAnnotationApplier {
+
+  /** Apply annotations from {@code element} to {@code type}. */
+  public static void apply(
+      AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory typeFactory)
+      throws UnexpectedAnnotationLocationException {
+    new ClassTypeParamApplier(type, element, typeFactory).extractAndApply();
+  }
+
+  /**
+   * Returns true if element represents a type parameter for a class.
+   *
+   * @param type ignored
+   * @param element the element that might be a type parameter
+   * @return true if element represents a type parameter for a class
+   */
+  public static boolean accepts(final AnnotatedTypeMirror type, final Element element) {
+    return element.getKind() == ElementKind.TYPE_PARAMETER
+        && element.getEnclosingElement() instanceof Symbol.ClassSymbol;
+  }
+
+  /** The class that holds the type parameter element. */
+  private final Symbol.ClassSymbol enclosingClass;
+
+  ClassTypeParamApplier(
+      AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory typeFactory) {
+    super(type, element, typeFactory);
+
+    if (!(element.getEnclosingElement() instanceof Symbol.ClassSymbol)) {
+      throw new BugInCF(
+          "TypeParameter not enclosed by class?  Type( "
+              + type
+              + " ) "
+              + "Element ( "
+              + element
+              + " ) ");
+    }
+
+    enclosingClass = (Symbol.ClassSymbol) element.getEnclosingElement();
+  }
+
+  /**
+   * Returns TargetType.CLASS_TYPE_PARAMETER.
+   *
+   * @return TargetType.CLASS_TYPE_PARAMETER
+   */
+  @Override
+  protected TargetType lowerBoundTarget() {
+    return TargetType.CLASS_TYPE_PARAMETER;
+  }
+
+  /**
+   * Returns TargetType.CLASS_TYPE_PARAMETER_BOUND.
+   *
+   * @return TargetType.CLASS_TYPE_PARAMETER_BOUND
+   */
+  @Override
+  protected TargetType upperBoundTarget() {
+    return TargetType.CLASS_TYPE_PARAMETER_BOUND;
+  }
+
+  /**
+   * Returns the index of element in the type parameter list of its enclosing class.
+   *
+   * @return the index of element in the type parameter list of its enclosing class
+   */
+  @Override
+  public int getElementIndex() {
+    return enclosingClass.getTypeParameters().indexOf(element);
+  }
+
+  @Override
+  protected TargetType[] validTargets() {
+    return new TargetType[] {TargetType.CLASS_EXTENDS};
+  }
+
+  /**
+   * Returns the raw type attributes of the enclosing class.
+   *
+   * @return the raw type attributes of the enclosing class
+   */
+  @Override
+  protected Iterable<Attribute.TypeCompound> getRawTypeAttributes() {
+    return enclosingClass.getRawTypeAttributes();
+  }
+
+  @Override
+  protected boolean isAccepted() {
+    return accepts(type, element);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java b/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java
new file mode 100644
index 0000000..9e04378
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java
@@ -0,0 +1,632 @@
+package org.checkerframework.framework.util.element;
+
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Attribute.TypeCompound;
+import com.sun.tools.javac.code.TargetType;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.TypeAnnotationPosition;
+import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry;
+import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeVariable;
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.ElementAnnotationApplier;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Utility methods for adding the annotations that are stored in an Element to the type that
+ * represents that element (or a use of that Element). This class also contains package private
+ * methods used by the ElementAnnotationAppliers that do most of the work.
+ */
+public class ElementAnnotationUtil {
+
+  /**
+   * For each type/element pair, add all of the annotations stored in Element to type. See apply for
+   * more details.
+   *
+   * @param types the types to which we wish to apply element annotations
+   * @param elements the elements that may contain annotations to apply. elements.size must ==
+   *     types.size
+   * @param typeFactory the type factory used to create the AnnotatedTypeMirrors contained by types
+   */
+  public static void applyAllElementAnnotations(
+      final List<? extends AnnotatedTypeMirror> types,
+      final List<? extends Element> elements,
+      final AnnotatedTypeFactory typeFactory) {
+
+    if (types.size() != elements.size()) {
+      throw new BugInCF(
+          "Number of types and elements don't match. "
+              + "types ( "
+              + StringsPlume.join(", ", types)
+              + " ) "
+              + "element ( "
+              + StringsPlume.join(", ", elements)
+              + " ) ");
+    }
+
+    for (int i = 0; i < types.size(); i++) {
+      ElementAnnotationApplier.apply(types.get(i), elements.get(i), typeFactory);
+    }
+  }
+
+  /**
+   * When a declaration annotation is an alias for a type annotation, then the Checker Framework may
+   * move the annotation before replacing it by the canonical version.
+   *
+   * <p>If the annotation is one of the Checker Framework compatibility annotations, for example
+   * org.checkerframework.checker.nullness.compatqual.NonNullDecl, then it is interpreted as a type
+   * annotation in the same location.
+   *
+   * @param type the type to annotate
+   * @param annotations the annotations to add
+   */
+  @SuppressWarnings("interning:not.interned") // AST node comparison
+  static void addDeclarationAnnotationsFromElement(
+      final AnnotatedTypeMirror type, final List<? extends AnnotationMirror> annotations) {
+    // The code here should be similar to
+    // org.checkerframework.framework.type.TypeFromMemberVisitor.visitVariable
+    AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(type);
+    if (innerType != type) {
+      for (AnnotationMirror annotation : annotations) {
+        if (AnnotationUtils.annotationName(annotation).startsWith("org.checkerframework")) {
+          innerType.addAnnotation(annotation);
+        } else {
+          type.addAnnotation(annotation);
+        }
+      }
+    } else {
+      type.addAnnotations(annotations);
+    }
+  }
+
+  /**
+   * Does expectedValues contain enumValue. This is just a linear search.
+   *
+   * @param enumValue value to search for, a needle
+   * @param expectedValues values to search through, a haystack
+   * @return true if enumValue is in expectedValues, false otherwise
+   */
+  static boolean contains(Object enumValue, Object[] expectedValues) {
+    for (final Object expected : expectedValues) {
+      if (enumValue.equals(expected)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Use a map to partition annotations with the given TargetTypes into Lists, where each target
+   * type is a key in the output map. Any annotation that does not have one of these target types
+   * will be added to unmatched
+   *
+   * @param annos the collection of annotations to partition
+   * @param unmatched a list to add annotations with unmatched target types to
+   * @param targetTypes a list of target types to partition annos with
+   * @return a map from targetType &rarr; List of Annotations that have that targetType
+   */
+  static Map<TargetType, List<TypeCompound>> partitionByTargetType(
+      Collection<TypeCompound> annos, List<TypeCompound> unmatched, TargetType... targetTypes) {
+    final Map<TargetType, List<TypeCompound>> targetTypeToAnnos = new HashMap<>();
+    for (TargetType targetType : targetTypes) {
+      targetTypeToAnnos.put(targetType, new ArrayList<>());
+    }
+
+    for (final TypeCompound anno : annos) {
+      final List<TypeCompound> annoSet = targetTypeToAnnos.get(anno.getPosition().type);
+      if (annoSet != null) {
+        annoSet.add(anno);
+      } else if (unmatched != null) {
+        unmatched.add(anno);
+      }
+    }
+
+    return targetTypeToAnnos;
+  }
+
+  /**
+   * A class used solely to annotate wildcards from Element annotations. Instances of
+   * WildcardBoundAnnos are used to aggregate ALL annotations for a given Wildcard and then apply
+   * them all at once in order to resolve the annotations in front of unbound wildcards.
+   *
+   * <p>Wildcard annotations are applied as follows:
+   *
+   * <ul>
+   *   <li>a) If an Annotation is in front of a extends or super bounded wildcard, it applies to the
+   *       bound that is NOT explicitly present. e.g.
+   *       <pre>{@code
+   * <@A ? extends Object> -- @A is placed on the super bound (Void)
+   * <@B ? super CharSequence> -- @B is placed on the extends bound (probably Object)
+   * }</pre>
+   *   <li>b) If an Annotation is on a bound, it applies to that bound. E.g.
+   *       <pre>{@code
+   * <? extends @A Object> -- @A is placed on the extends bound (Object)
+   * <? super @B CharSequence> -- @B is placed on the super bound (CharSequence)
+   * }</pre>
+   *   <li>c) If an Annotation is on an unbounded wildcard there are two subcases.
+   *       <ul>
+   *         <li>c.1 The user wrote the annotation explicitly -- these annotations apply to both
+   *             bounds e.g. the user wrote
+   *             <pre>{@code
+   * <@C ?> -- the annotation is placed on the extends/super bounds
+   * }</pre>
+   *         <li>c.2 Previous calls to getAnnotatedType have annotated this wildcard with BOTH
+   *             bounds e.g. the user wrote {@code <?>} but the checker framework added {@code <@C ?
+   *             extends @D Object>} to the corresponding element.
+   *             <pre>
+   *             {@code <?> -- @C is placed on the lower bound and @D is placed on the upper bound
+   *          This case is treated just like annotations in cases a/b.
+   * }</pre>
+   *       </ul>
+   * </ul>
+   */
+  private static final class WildcardBoundAnnos {
+    public final AnnotatedWildcardType wildcard;
+    public final Set<AnnotationMirror> upperBoundAnnos;
+    public final Set<AnnotationMirror> lowerBoundAnnos;
+
+    // indicates that this is an annotation in front of an unbounded wildcard
+    // e.g.  < @A ? >
+    // For each annotation in this set, if there is no annotation in upperBoundAnnos
+    // that is in the same hierarchy then the annotation will be applied to both bounds
+    // otherwise the annotation applies to the lower bound only
+    public final Set<AnnotationMirror> possiblyBoth;
+
+    /** Whether or not wildcard has an explicit super bound. */
+    private final boolean isSuperBounded;
+
+    /** Whether or not wildcard has NO explicit bound whatsoever. */
+    private final boolean isUnbounded;
+
+    WildcardBoundAnnos(AnnotatedWildcardType wildcard) {
+      this.wildcard = wildcard;
+      this.upperBoundAnnos = AnnotationUtils.createAnnotationSet();
+      this.lowerBoundAnnos = AnnotationUtils.createAnnotationSet();
+      this.possiblyBoth = AnnotationUtils.createAnnotationSet();
+
+      this.isSuperBounded = AnnotatedTypes.hasExplicitSuperBound(wildcard);
+      this.isUnbounded = AnnotatedTypes.hasNoExplicitBound(wildcard);
+    }
+
+    void addAnnotation(final TypeCompound anno) {
+      // if the typepath entry ends in Wildcard then the annotation should go on a bound
+      // otherwise, the annotation is in front of the wildcard
+      // e.g. @HERE ? extends Object
+      final boolean isInFrontOfWildcard =
+          anno.getPosition().location.last() != TypePathEntry.WILDCARD;
+      if (isInFrontOfWildcard && isUnbounded) {
+        possiblyBoth.add(anno);
+
+      } else {
+        // A TypePathEntry of WILDCARD indicates that it is placed on the bound
+        // use the type of the wildcard bound to determine which set to put it in
+
+        if (isInFrontOfWildcard) {
+          if (isSuperBounded) {
+            upperBoundAnnos.add(anno);
+          } else {
+            lowerBoundAnnos.add(anno);
+          }
+        } else { // it's on the bound
+          if (isSuperBounded) {
+            lowerBoundAnnos.add(anno);
+          } else {
+            upperBoundAnnos.add(anno);
+          }
+        }
+      }
+    }
+
+    /**
+     * Apply the annotations to wildcard according to the rules outlined in the comment at the
+     * beginning of this class.
+     */
+    void apply() {
+      final AnnotatedTypeMirror extendsBound = wildcard.getExtendsBound();
+      final AnnotatedTypeMirror superBound = wildcard.getSuperBound();
+
+      for (AnnotationMirror extAnno : upperBoundAnnos) {
+        extendsBound.addAnnotation(extAnno);
+      }
+      for (AnnotationMirror supAnno : lowerBoundAnnos) {
+        superBound.addAnnotation(supAnno);
+      }
+
+      for (AnnotationMirror anno : possiblyBoth) {
+        superBound.addAnnotation(anno);
+
+        // This will be false if we've defaulted the bounds and are reading them again.
+        // In that case, we will have already created an annotation for the extends bound
+        // that should be honored and NOT overwritten.
+        if (!extendsBound.isAnnotatedInHierarchy(anno)) {
+          extendsBound.addAnnotation(anno);
+        }
+      }
+    }
+  }
+
+  /**
+   * TypeCompounds are implementations of AnnotationMirror that are stored on Elements. Each type
+   * compound has a TypeAnnotationPosition which identifies, relative to the "root" of a type, where
+   * an annotation should be placed. This method adds all of the given TypeCompounds to the correct
+   * location on type by interpreting the TypeAnnotationPosition.
+   *
+   * <p>Note: We handle all of the Element annotations on a type at once because we need to identify
+   * whether or not the element annotation in front of an unbound wildcard (e.g. {@code <@HERE ?>})
+   * should apply to only the super bound or both the super bound and the extends bound.
+   *
+   * @see org.checkerframework.framework.util.element.ElementAnnotationUtil.WildcardBoundAnnos
+   * @param type the type in which annos should be placed
+   * @param annos all of the element annotations, TypeCompounds, for type
+   */
+  static void annotateViaTypeAnnoPosition(
+      final AnnotatedTypeMirror type, final Collection<TypeCompound> annos)
+      throws UnexpectedAnnotationLocationException {
+    final IdentityHashMap<AnnotatedWildcardType, WildcardBoundAnnos> wildcardToAnnos =
+        new IdentityHashMap<>();
+    for (final TypeCompound anno : annos) {
+      AnnotatedTypeMirror target = getTypeAtLocation(type, anno.position.location, anno, false);
+      if (target.getKind() == TypeKind.WILDCARD) {
+        addWildcardToBoundMap((AnnotatedWildcardType) target, anno, wildcardToAnnos);
+      } else {
+        target.addAnnotation(anno);
+      }
+    }
+
+    for (WildcardBoundAnnos wildcardAnnos : wildcardToAnnos.values()) {
+      wildcardAnnos.apply();
+    }
+  }
+
+  /**
+   * Creates an entry in wildcardToAnnos for wildcard if one does not already exists. Adds anno to
+   * the WildcardBoundAnnos object for wildcard.
+   */
+  private static void addWildcardToBoundMap(
+      final AnnotatedWildcardType wildcard,
+      final TypeCompound anno,
+      final Map<AnnotatedWildcardType, WildcardBoundAnnos> wildcardToAnnos) {
+    WildcardBoundAnnos boundAnnos =
+        wildcardToAnnos.computeIfAbsent(wildcard, WildcardBoundAnnos::new);
+    boundAnnos.addAnnotation(anno);
+  }
+
+  /**
+   * Returns true if the typeCompound is a primary annotation for the type it targets (or lower
+   * bound if this is a type variable or wildcard ). If you think of a type as a tree-like structure
+   * then a nested type any type that is not the root. E.g. {@code @T List< @N String>}, @T is on a
+   * top-level NON-nested type where as the annotation @N is on a nested type.
+   *
+   * @param typeCompound the type compound to inspect
+   * @return true if typeCompound is placed on a nested type, false otherwise
+   */
+  static boolean isOnComponentType(final Attribute.TypeCompound typeCompound) {
+    return !typeCompound.position.location.isEmpty();
+  }
+
+  /**
+   * See the Type Annotation Specification on bounds
+   * (https://checkerframework.org/jsr308/specification/java-annotation-design.html).
+   *
+   * <p>TypeAnnotationPositions have bound indices when they represent an upper bound on a
+   * TypeVariable. The index 0 ALWAYS refers to the superclass type. If that supertype is implied to
+   * be Object (because we didn't specify an extends) then the actual types will be offset by 1
+   * (because index 0 is ALWAYS a class.
+   *
+   * <p>Therefore, These indices will be offset by -1 if the first type in the bound is an interface
+   * which implies the specified type itself is an interface.
+   *
+   * <p>Reminder: There will only be multiple bound types if the upperBound is an intersection.
+   *
+   * @param upperBoundTypes the list of upperBounds for the type with bound positions you wish to
+   *     offset
+   * @return the bound offset for all TypeAnnotationPositions of TypeCompounds targeting these
+   *     bounds
+   */
+  static int getBoundIndexOffset(final List<? extends AnnotatedTypeMirror> upperBoundTypes) {
+    final int boundIndexOffset;
+    if (((Type) upperBoundTypes.get(0).getUnderlyingType()).isInterface()) {
+      boundIndexOffset = -1;
+    } else {
+      boundIndexOffset = 0;
+    }
+
+    return boundIndexOffset;
+  }
+
+  /**
+   * Overload of getTypeAtLocation with default values null/false for the annotation and array
+   * component flag, to make usage easier. Default visibility to allow usage within package.
+   */
+  static AnnotatedTypeMirror getTypeAtLocation(
+      AnnotatedTypeMirror type, List<TypeAnnotationPosition.TypePathEntry> location)
+      throws UnexpectedAnnotationLocationException {
+    return getTypeAtLocation(type, location, null, false);
+  }
+
+  /**
+   * Given a TypePath into a type, return the component type that is located at the end of the
+   * TypePath.
+   *
+   * @param type a type containing the type specified by location
+   * @param location a type path into type
+   * @param anno an annotation to be applied to the inner types of a declared type if the declared
+   *     type is itself a component type of an array
+   * @param isComponentTypeOfArray indicates whether the type under analysis is a component type of
+   *     some array type
+   * @return the type specified by location
+   */
+  private static AnnotatedTypeMirror getTypeAtLocation(
+      AnnotatedTypeMirror type,
+      List<TypeAnnotationPosition.TypePathEntry> location,
+      TypeCompound anno,
+      boolean isComponentTypeOfArray)
+      throws UnexpectedAnnotationLocationException {
+    if (location.isEmpty() && type.getKind() != TypeKind.DECLARED) {
+      // An annotation with an empty type path on a declared type applies to the outermost enclosing
+      // type. This logic is handled together with non-empty type paths in getLocationTypeADT. For
+      // other kinds of types, no work is required for an empty type path.
+      return type;
+    }
+    switch (type.getKind()) {
+      case NULL:
+        return getLocationTypeANT((AnnotatedNullType) type, location);
+      case DECLARED:
+        return getLocationTypeADT(
+            (AnnotatedDeclaredType) type, location, anno, isComponentTypeOfArray);
+      case WILDCARD:
+        return getLocationTypeAWT((AnnotatedWildcardType) type, location);
+      case TYPEVAR:
+        if (TypesUtils.isCaptured((TypeVariable) type.getUnderlyingType())) {
+          // Work-around for Issue 1696: ignore captured wildcards.
+          // There is no reason to observe such a type and it would be better
+          // to prevent that this type ever reaches this point.
+          return type;
+        }
+        // Raise an error for all other type variables (why isn't this needed?).
+        break;
+      case ARRAY:
+        return getLocationTypeAAT((AnnotatedArrayType) type, location, anno);
+      case UNION:
+        return getLocationTypeAUT((AnnotatedUnionType) type, location);
+      case INTERSECTION:
+        return getLocationTypeAIT((AnnotatedIntersectionType) type, location);
+      default:
+        // Raise an error for all other types below.
+    }
+    throw new UnexpectedAnnotationLocationException(
+        "ElementAnnotationUtil.getTypeAtLocation: "
+            + "unexpected annotation with location found for type: %s (kind: %s ) location: ",
+        type, type.getKind(), location);
+  }
+
+  /**
+   * Given a TypePath into a declared type, return the component type that is located at the end of
+   * the TypePath.
+   *
+   * @param type a type containing the type specified by location
+   * @param location a type path into type
+   * @param anno an annotation to be applied to the inner types of the declared type if the declared
+   *     type is itself a component type of an array
+   * @param isComponentTypeOfArray indicates whether the type under analysis is a component type of
+   *     some array type
+   * @return the type specified by location
+   */
+  @SuppressWarnings("JdkObsolete") // error is issued on every operation, must suppress here
+  private static AnnotatedTypeMirror getLocationTypeADT(
+      AnnotatedDeclaredType type,
+      List<TypeAnnotationPosition.TypePathEntry> location,
+      TypeCompound anno,
+      boolean isComponentTypeOfArray)
+      throws UnexpectedAnnotationLocationException {
+    // List order by outermost type to innermost type.
+    ArrayDeque<AnnotatedDeclaredType> outerToInner = new ArrayDeque<>();
+    AnnotatedDeclaredType enclosing = type;
+    while (enclosing != null) {
+      outerToInner.addFirst(enclosing);
+      enclosing = enclosing.getEnclosingType();
+    }
+
+    // If the AnnotatedDeclaredType is a component of an array type, then apply anno to all
+    // possible inner types.
+    // NOTE: This workaround can be removed once
+    // https://bugs.openjdk.java.net/browse/JDK-8208470 is fixed
+    // The number of enclosing types is outerToInner.size() - 1; there only is
+    // work to do if outerToInner contains more than one element.
+    if (anno != null && isComponentTypeOfArray && location.isEmpty() && outerToInner.size() > 1) {
+      ArrayDeque<AnnotatedDeclaredType> innerTypes = new ArrayDeque<>(outerToInner);
+      innerTypes.removeFirst();
+      while (!innerTypes.isEmpty()) {
+        innerTypes.removeFirst().addAnnotation(anno);
+      }
+    }
+
+    // Create a linked list of the location, so removing the first element is easier.
+    // Also, the tail() operation wouldn't work with a Deque.
+    @SuppressWarnings("JdkObsolete")
+    LinkedList<TypePathEntry> tailOfLocations = new LinkedList<>(location);
+    boolean error = false;
+    while (!tailOfLocations.isEmpty()) {
+      TypePathEntry currentLocation = tailOfLocations.removeFirst();
+      switch (currentLocation.tag) {
+        case INNER_TYPE:
+          outerToInner.removeFirst();
+          break;
+        case TYPE_ARGUMENT:
+          AnnotatedDeclaredType innerType = outerToInner.getFirst();
+          if (currentLocation.arg < innerType.getTypeArguments().size()) {
+            AnnotatedTypeMirror typeArg = innerType.getTypeArguments().get(currentLocation.arg);
+            return getTypeAtLocation(typeArg, tailOfLocations);
+          } else {
+            error = true;
+            break;
+          }
+        default:
+          error = true;
+      }
+      if (error) {
+        break;
+      }
+    }
+
+    if (outerToInner.isEmpty() || error) {
+      throw new UnexpectedAnnotationLocationException(
+          "ElementAnnotationUtil.getLocationTypeADT: invalid location %s for type: %s",
+          location, type);
+    }
+
+    return outerToInner.getFirst();
+  }
+
+  private static AnnotatedTypeMirror getLocationTypeANT(
+      AnnotatedNullType type, List<TypeAnnotationPosition.TypePathEntry> location)
+      throws UnexpectedAnnotationLocationException {
+    if (location.size() == 1 && location.get(0).tag == TypePathEntryKind.TYPE_ARGUMENT) {
+      return type;
+    }
+
+    throw new UnexpectedAnnotationLocationException(
+        "ElementAnnotationUtil.getLocationTypeANT: " + "invalid location %s for type: %s ",
+        location, type);
+  }
+
+  private static AnnotatedTypeMirror getLocationTypeAWT(
+      final AnnotatedWildcardType type, final List<TypeAnnotationPosition.TypePathEntry> location)
+      throws UnexpectedAnnotationLocationException {
+
+    // the last step into the Wildcard type is handled in WildcardToBoundAnnos.addAnnotation
+    if (location.size() == 1) {
+      return type;
+    }
+
+    if (!location.isEmpty()
+        && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.WILDCARD) {
+      if (AnnotatedTypes.hasExplicitExtendsBound(type)) {
+        return getTypeAtLocation(type.getExtendsBound(), tail(location));
+      } else if (AnnotatedTypes.hasExplicitSuperBound(type)) {
+        return getTypeAtLocation(type.getSuperBound(), tail(location));
+      } else {
+        return getTypeAtLocation(type.getExtendsBound(), tail(location));
+      }
+
+    } else {
+      throw new UnexpectedAnnotationLocationException(
+          "ElementAnnotationUtil.getLocationTypeAWT: " + "invalid location %s for type: %s ",
+          location, type);
+    }
+  }
+
+  /**
+   * When we have an (e.g. @Odd int @NonNull []) the type-annotation position of the array
+   * annotation (@NonNull) is really the outermost type in the TypeAnnotationPosition and it will
+   * NOT have TypePathEntryKind.ARRAY at the end of its position. The position of the component type
+   * (@Odd) is considered deeper in the type and therefore has the TypePathEntryKind.ARRAY in its
+   * position.
+   */
+  private static AnnotatedTypeMirror getLocationTypeAAT(
+      AnnotatedArrayType type,
+      List<TypeAnnotationPosition.TypePathEntry> location,
+      TypeCompound anno)
+      throws UnexpectedAnnotationLocationException {
+    if (location.size() >= 1
+        && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) {
+      AnnotatedTypeMirror comptype = type.getComponentType();
+      return getTypeAtLocation(comptype, tail(location), anno, true);
+    } else {
+      throw new UnexpectedAnnotationLocationException(
+          "ElementAnnotationUtil.annotateAAT: " + "invalid location %s for type: %s ",
+          location, type);
+    }
+  }
+
+  /*
+   * TODO: this case should never occur!
+   * A union type can only occur in special locations, e.g. for exception
+   * parameters. The EXCEPTION_PARAMETER TartetType should be used to
+   * decide which of the alternatives in the union to annotate.
+   * Only the TypePathEntry is not enough.
+   * As a hack, always annotate the first alternative.
+   */
+  private static AnnotatedTypeMirror getLocationTypeAUT(
+      AnnotatedUnionType type, List<TypeAnnotationPosition.TypePathEntry> location)
+      throws UnexpectedAnnotationLocationException {
+    AnnotatedTypeMirror comptype = type.getAlternatives().get(0);
+    return getTypeAtLocation(comptype, location);
+  }
+
+  /** Intersection types use the TYPE_ARGUMENT index to separate the individual types. */
+  private static AnnotatedTypeMirror getLocationTypeAIT(
+      AnnotatedIntersectionType type, List<TypeAnnotationPosition.TypePathEntry> location)
+      throws UnexpectedAnnotationLocationException {
+    if (location.size() >= 1
+        && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.TYPE_ARGUMENT) {
+      AnnotatedTypeMirror bound = type.getBounds().get(location.get(0).arg);
+      return getTypeAtLocation(bound, tail(location));
+    } else {
+      throw new UnexpectedAnnotationLocationException(
+          "ElementAnnotationUtil.getLocatonTypeAIT: invalid location %s for type: %s ",
+          location, type);
+    }
+  }
+
+  private static <T> List<T> tail(List<T> list) {
+    return list.subList(1, list.size());
+  }
+
+  /** Exception indicating an invalid location for an annotation was found. */
+  @SuppressWarnings("serial")
+  public static class UnexpectedAnnotationLocationException extends Exception {
+
+    /**
+     * Creates an UnexpectedAnnotationLocationException.
+     *
+     * @param format format string
+     * @param args arguments to the format string
+     */
+    @FormatMethod
+    private UnexpectedAnnotationLocationException(String format, Object... args) {
+      super(String.format(format, args));
+    }
+  }
+
+  /** An ERROR TypeKind was found. */
+  @SuppressWarnings("serial")
+  public static class ErrorTypeKindException extends Error {
+
+    /**
+     * Creates an ErrorTypeKindException.
+     *
+     * @param format format string
+     * @param args arguments to the format string
+     */
+    @FormatMethod
+    public ErrorTypeKindException(String format, Object... args) {
+      super(String.format(format, args));
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/IndexedElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/IndexedElementAnnotationApplier.java
new file mode 100644
index 0000000..821ae5c
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/element/IndexedElementAnnotationApplier.java
@@ -0,0 +1,59 @@
+package org.checkerframework.framework.util.element;
+
+import com.sun.tools.javac.code.Attribute;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.Element;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/**
+ * Some Elements are members of a list (formal method parameters and type parameters). This class
+ * ensures that the targeted annotations passed by handleTargeted -- see {@link
+ * TargetedElementAnnotationApplier} -- only include those with a position that matches the index
+ * returned by getElementIndex.
+ */
+abstract class IndexedElementAnnotationApplier extends TargetedElementAnnotationApplier {
+
+  protected IndexedElementAnnotationApplier(AnnotatedTypeMirror type, Element element) {
+    super(type, element);
+  }
+
+  /** The index of element in the list of elements that contains it. */
+  public abstract int getElementIndex();
+
+  /**
+   * A TypeAnnotationPosition has a number of different indexes (type_index, bound_index,
+   * param_index) Return the index we are interested in. If offsetting needs to be done it should be
+   * done in getElementIndex not here. (see ElementAnnotationUtils.getBoundIndexOffset )
+   *
+   * @param anno an annotation we might wish to apply
+   * @return the index value this applier compares against the getElementIndex
+   */
+  public abstract int getTypeCompoundIndex(final Attribute.TypeCompound anno);
+
+  @Override
+  protected Map<TargetClass, List<Attribute.TypeCompound>> sift(
+      Iterable<Attribute.TypeCompound> typeCompounds) {
+    final Map<TargetClass, List<Attribute.TypeCompound>> targetClassToAnnos =
+        super.sift(typeCompounds);
+
+    final List<Attribute.TypeCompound> targeted = targetClassToAnnos.get(TargetClass.TARGETED);
+    final List<Attribute.TypeCompound> valid = targetClassToAnnos.get(TargetClass.VALID);
+
+    final int paramIndex = getElementIndex();
+
+    // filter out annotations in targeted that don't have the correct parameter index. (i.e the
+    // one's that are on the same method but don't pertain to the parameter element being
+    // processed, see class comments ).  Place these annotations into the valid list.
+    int i = 0;
+    while (i < targeted.size()) {
+      if (getTypeCompoundIndex(targeted.get(i)) != paramIndex) {
+        valid.add(targeted.remove(i));
+      } else {
+        ++i;
+      }
+    }
+
+    return targetClassToAnnos;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java
new file mode 100644
index 0000000..d697b2d
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java
@@ -0,0 +1,223 @@
+package org.checkerframework.framework.util.element;
+
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Attribute.TypeCompound;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.TargetType;
+import com.sun.tools.javac.code.TypeAnnotationPosition;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.Element;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.ElementUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Adds annotations from element to the return type, formal parameter types, type parameters, and
+ * throws clauses of the AnnotatedExecutableType type.
+ */
+public class MethodApplier extends TargetedElementAnnotationApplier {
+
+  /** Apply annotations from {@code element} to {@code type}. */
+  public static void apply(
+      AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory)
+      throws UnexpectedAnnotationLocationException {
+    new MethodApplier(type, element, typeFactory).extractAndApply();
+  }
+
+  public static boolean accepts(final AnnotatedTypeMirror typeMirror, final Element element) {
+    return element instanceof Symbol.MethodSymbol && typeMirror instanceof AnnotatedExecutableType;
+  }
+
+  private final AnnotatedTypeFactory typeFactory;
+
+  /** Method being annotated, this symbol contains all relevant annotations. */
+  private final Symbol.MethodSymbol methodSymbol;
+
+  private final AnnotatedExecutableType methodType;
+
+  MethodApplier(AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory) {
+    super(type, element);
+    this.typeFactory = typeFactory;
+    this.methodSymbol = (Symbol.MethodSymbol) element;
+    this.methodType = (AnnotatedExecutableType) type;
+  }
+
+  /**
+   * Returns receiver, returns, and throws. See extract and apply as we also annotate type params.
+   *
+   * @return receiver, returns, and throws
+   */
+  @Override
+  protected TargetType[] annotatedTargets() {
+    return new TargetType[] {
+      TargetType.METHOD_RECEIVER, TargetType.METHOD_RETURN, TargetType.THROWS
+    };
+  }
+
+  /**
+   * Returns all possible annotation positions for a method except those in annotatedTargets.
+   *
+   * @return all possible annotation positions for a method except those in annotatedTargets
+   */
+  @Override
+  protected TargetType[] validTargets() {
+    return new TargetType[] {
+      TargetType.LOCAL_VARIABLE,
+      TargetType.RESOURCE_VARIABLE,
+      TargetType.EXCEPTION_PARAMETER,
+      TargetType.NEW,
+      TargetType.CAST,
+      TargetType.INSTANCEOF,
+      TargetType.METHOD_INVOCATION_TYPE_ARGUMENT,
+      TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT,
+      TargetType.METHOD_REFERENCE,
+      TargetType.CONSTRUCTOR_REFERENCE,
+      TargetType.METHOD_REFERENCE_TYPE_ARGUMENT,
+      TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT,
+      TargetType.METHOD_TYPE_PARAMETER,
+      TargetType.METHOD_TYPE_PARAMETER_BOUND,
+      TargetType.METHOD_FORMAL_PARAMETER,
+      // TODO: from generic anonymous classes; remove when
+      // we can depend on only seeing classfiles that were
+      // generated by a javac that contains a fix for:
+      // https://bugs.openjdk.java.net/browse/JDK-8198945
+      TargetType.CLASS_EXTENDS,
+      // TODO: Test case from Issue 3277 produces invalid position.
+      // Ignore until this javac bug is fixed:
+      // https://bugs.openjdk.java.net/browse/JDK-8233945
+      TargetType.UNKNOWN
+    };
+  }
+
+  /**
+   * Returns the annotations on the method symbol (element).
+   *
+   * @return the annotations on the method symbol (element)
+   */
+  @Override
+  protected Iterable<Attribute.TypeCompound> getRawTypeAttributes() {
+    return methodSymbol.getRawTypeAttributes();
+  }
+
+  @Override
+  protected boolean isAccepted() {
+    return MethodApplier.accepts(type, element);
+  }
+
+  /**
+   * Sets the method's element, annotates its return type, parameters, type parameters, and throws
+   * annotations.
+   */
+  @Override
+  public void extractAndApply() throws UnexpectedAnnotationLocationException {
+    methodType.setElement(methodSymbol); // Preserves previous behavior
+
+    // Add declaration annotations to the return type if
+    if (methodType.getReturnType() instanceof AnnotatedTypeVariable) {
+      applyTypeVarUseOnReturnType();
+    }
+    ElementAnnotationUtil.addDeclarationAnnotationsFromElement(
+        methodType.getReturnType(), methodSymbol.getAnnotationMirrors());
+
+    final List<AnnotatedTypeMirror> params = methodType.getParameterTypes();
+    for (int i = 0; i < params.size(); ++i) {
+      // Add declaration annotations to the parameter type
+      ElementAnnotationUtil.addDeclarationAnnotationsFromElement(
+          params.get(i), methodSymbol.getParameters().get(i).getAnnotationMirrors());
+    }
+
+    // ensures that we check that there are only valid target types on this class, there are no
+    // "invalid" locations
+    super.extractAndApply();
+
+    ElementAnnotationUtil.applyAllElementAnnotations(
+        methodType.getParameterTypes(), methodSymbol.getParameters(), typeFactory);
+    ElementAnnotationUtil.applyAllElementAnnotations(
+        methodType.getTypeVariables(), methodSymbol.getTypeParameters(), typeFactory);
+  }
+
+  // NOTE that these are the only locations not handled elsewhere, otherwise we call apply
+  @Override
+  protected void handleTargeted(final List<TypeCompound> targeted)
+      throws UnexpectedAnnotationLocationException {
+    final List<TypeCompound> unmatched = new ArrayList<>();
+    final Map<TargetType, List<TypeCompound>> targetTypeToAnno =
+        ElementAnnotationUtil.partitionByTargetType(
+            targeted,
+            unmatched,
+            TargetType.METHOD_RECEIVER,
+            TargetType.METHOD_RETURN,
+            TargetType.THROWS);
+
+    ElementAnnotationUtil.annotateViaTypeAnnoPosition(
+        methodType.getReceiverType(), targetTypeToAnno.get(TargetType.METHOD_RECEIVER));
+    ElementAnnotationUtil.annotateViaTypeAnnoPosition(
+        methodType.getReturnType(), targetTypeToAnno.get(TargetType.METHOD_RETURN));
+    applyThrowsAnnotations(targetTypeToAnno.get(TargetType.THROWS));
+
+    if (!unmatched.isEmpty()) {
+      throw new BugInCF(
+          "Unexpected annotations ( "
+              + StringsPlume.join(",", unmatched)
+              + " ) for"
+              + "type ( "
+              + type
+              + " ) and element ( "
+              + element
+              + " ) ");
+    }
+  }
+
+  /** For each thrown type, collect all the annotations for that type and apply them. */
+  private void applyThrowsAnnotations(final List<Attribute.TypeCompound> annos)
+      throws UnexpectedAnnotationLocationException {
+    final List<AnnotatedTypeMirror> thrown = methodType.getThrownTypes();
+    if (thrown.isEmpty()) {
+      return;
+    }
+
+    Map<AnnotatedTypeMirror, List<TypeCompound>> typeToAnnos = new LinkedHashMap<>();
+    for (final AnnotatedTypeMirror thrownType : thrown) {
+      typeToAnnos.put(thrownType, new ArrayList<>());
+    }
+
+    for (TypeCompound anno : annos) {
+      final TypeAnnotationPosition annoPos = anno.position;
+      if (annoPos.type_index >= 0 && annoPos.type_index < thrown.size()) {
+        final AnnotatedTypeMirror thrownType = thrown.get(annoPos.type_index);
+        typeToAnnos.get(thrownType).add(anno);
+
+      } else {
+        throw new BugInCF(
+            "MethodApplier.applyThrowsAnnotation: "
+                + "invalid throws index "
+                + annoPos.type_index
+                + " for annotation: "
+                + anno
+                + " for element: "
+                + ElementUtils.getQualifiedName(element));
+      }
+    }
+
+    for (final Map.Entry<AnnotatedTypeMirror, List<TypeCompound>> typeToAnno :
+        typeToAnnos.entrySet()) {
+      ElementAnnotationUtil.annotateViaTypeAnnoPosition(typeToAnno.getKey(), typeToAnno.getValue());
+    }
+  }
+
+  /**
+   * If the return type is a use of a type variable first apply the bound annotations from the type
+   * variables declaration.
+   */
+  private void applyTypeVarUseOnReturnType() throws UnexpectedAnnotationLocationException {
+    new TypeVarUseApplier(methodType.getReturnType(), methodSymbol, typeFactory).extractAndApply();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/MethodTypeParamApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/MethodTypeParamApplier.java
new file mode 100644
index 0000000..caed7aa
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/element/MethodTypeParamApplier.java
@@ -0,0 +1,130 @@
+package org.checkerframework.framework.util.element;
+
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.TargetType;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException;
+import org.checkerframework.javacutil.BugInCF;
+
+/** Applies the annotations present for a method type parameter onto an AnnotatedTypeVariable. */
+public class MethodTypeParamApplier extends TypeParamElementAnnotationApplier {
+
+  /** Apply annotations from {@code element} to {@code type}. */
+  public static void apply(
+      AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory typeFactory)
+      throws UnexpectedAnnotationLocationException {
+    new MethodTypeParamApplier(type, element, typeFactory).extractAndApply();
+  }
+
+  /**
+   * Returns true if element represents a type parameter for a method.
+   *
+   * @param type ignored
+   * @param element the element that might be a type parameter for a method
+   * @return true if element represents a type parameter for a method
+   */
+  public static boolean accepts(final AnnotatedTypeMirror type, final Element element) {
+    return element.getKind() == ElementKind.TYPE_PARAMETER
+        && element.getEnclosingElement() instanceof Symbol.MethodSymbol;
+  }
+
+  private final Symbol.MethodSymbol enclosingMethod;
+
+  MethodTypeParamApplier(
+      AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory typeFactory) {
+    super(type, element, typeFactory);
+
+    if (!(element.getEnclosingElement() instanceof Symbol.MethodSymbol)) {
+      throw new BugInCF(
+          "TypeParameter not enclosed by method?  Type( "
+              + type
+              + " ) "
+              + "Element ( "
+              + element
+              + " ) ");
+    }
+
+    enclosingMethod = (Symbol.MethodSymbol) element.getEnclosingElement();
+  }
+
+  /**
+   * Returns TargetType.METHOD_TYPE_PARAMETER.
+   *
+   * @return TargetType.METHOD_TYPE_PARAMETER
+   */
+  @Override
+  protected TargetType lowerBoundTarget() {
+    return TargetType.METHOD_TYPE_PARAMETER;
+  }
+
+  /**
+   * Returns TargetType.METHOD_TYPE_PARAMETER_BOUND.
+   *
+   * @return TargetType.METHOD_TYPE_PARAMETER_BOUND
+   */
+  @Override
+  protected TargetType upperBoundTarget() {
+    return TargetType.METHOD_TYPE_PARAMETER_BOUND;
+  }
+
+  /**
+   * Returns the index of element in the type parameter list of its enclosing method.
+   *
+   * @return the index of element in the type parameter list of its enclosing method
+   */
+  @Override
+  public int getElementIndex() {
+    return enclosingMethod.getTypeParameters().indexOf(element);
+  }
+
+  @Override
+  protected TargetType[] validTargets() {
+    return new TargetType[] {
+      TargetType.METHOD_RETURN,
+      TargetType.METHOD_FORMAL_PARAMETER,
+      TargetType.METHOD_RECEIVER,
+      TargetType.THROWS,
+      TargetType.LOCAL_VARIABLE,
+      TargetType.RESOURCE_VARIABLE,
+      TargetType.EXCEPTION_PARAMETER,
+      TargetType.NEW,
+      TargetType.CAST,
+      TargetType.INSTANCEOF,
+      TargetType.METHOD_INVOCATION_TYPE_ARGUMENT,
+      TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT,
+      TargetType.METHOD_REFERENCE,
+      TargetType.CONSTRUCTOR_REFERENCE,
+      TargetType.METHOD_REFERENCE_TYPE_ARGUMENT,
+      TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT,
+      // TODO: from generic anonymous classes; remove when
+      // we can depend on only seeing classfiles that were
+      // generated by a javac that contains a fix for:
+      // https://bugs.openjdk.java.net/browse/JDK-8198945
+      TargetType.CLASS_EXTENDS,
+      // TODO: Test case from Issue 3277 produces invalid position.
+      // Ignore until this javac bug is fixed:
+      // https://bugs.openjdk.java.net/browse/JDK-8233945
+      TargetType.UNKNOWN
+    };
+  }
+
+  /**
+   * Returns the TypeCompounds (annotations) of the declaring element.
+   *
+   * @return the TypeCompounds (annotations) of the declaring element
+   */
+  @Override
+  protected Iterable<Attribute.TypeCompound> getRawTypeAttributes() {
+    return enclosingMethod.getRawTypeAttributes();
+  }
+
+  @Override
+  protected boolean isAccepted() {
+    return accepts(type, element);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/ParamApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/ParamApplier.java
new file mode 100644
index 0000000..4e17ec0
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/element/ParamApplier.java
@@ -0,0 +1,266 @@
+package org.checkerframework.framework.util.element;
+
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Attribute.TypeCompound;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.TargetType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.ElementAnnotationApplier;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.Pair;
+
+/** Adds annotations to one formal parameter of a method or lambda within a method. */
+public class ParamApplier extends IndexedElementAnnotationApplier {
+
+  /** Apply annotations from {@code element} to {@code type}. */
+  public static void apply(
+      AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory)
+      throws UnexpectedAnnotationLocationException {
+    new ParamApplier(type, element, typeFactory).extractAndApply();
+  }
+
+  public static final int RECEIVER_PARAM_INDEX = Integer.MIN_VALUE;
+
+  public static boolean accepts(final AnnotatedTypeMirror type, final Element element) {
+    return element.getKind() == ElementKind.PARAMETER;
+  }
+
+  private final Symbol.MethodSymbol enclosingMethod;
+  private final boolean isLambdaParam;
+  private final Integer lambdaParamIndex;
+  private final LambdaExpressionTree lambdaTree;
+
+  ParamApplier(AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory) {
+    super(type, element);
+    enclosingMethod = getParentMethod(element);
+
+    if (enclosingMethod.getKind() != ElementKind.INSTANCE_INIT
+        && enclosingMethod.getKind() != ElementKind.STATIC_INIT
+        && enclosingMethod.getParameters().contains(element)) {
+      lambdaTree = null;
+      isLambdaParam = false;
+      lambdaParamIndex = null;
+
+    } else {
+      Pair<VariableTree, LambdaExpressionTree> paramToEnclosingLambda =
+          ElementAnnotationApplier.getParamAndLambdaTree((VariableElement) element, typeFactory);
+
+      if (paramToEnclosingLambda != null) {
+        VariableTree paramDecl = paramToEnclosingLambda.first;
+        lambdaTree = paramToEnclosingLambda.second;
+        isLambdaParam = true;
+        lambdaParamIndex = lambdaTree.getParameters().indexOf(paramDecl);
+
+      } else {
+        lambdaTree = null;
+        isLambdaParam = false;
+        lambdaParamIndex = null;
+      }
+    }
+  }
+
+  /**
+   * Returns the index of element its parent method's parameter list. Integer.MIN_VALUE if the
+   * element is the receiver parameter.
+   *
+   * @return the index of element its parent method's parameter list. Integer.MIN_VALUE if the
+   *     element is the receiver parameter
+   */
+  @Override
+  public int getElementIndex() {
+    if (isLambdaParam) {
+      return lambdaParamIndex;
+    }
+
+    if (isReceiver(element)) {
+      return RECEIVER_PARAM_INDEX;
+    }
+
+    final int paramIndex = enclosingMethod.getParameters().indexOf(element);
+    if (paramIndex == -1) {
+      throw new BugInCF(
+          "Could not find parameter Element in parameter list. "
+              + "Parameter( "
+              + element
+              + " ) Parent ( "
+              + enclosingMethod
+              + " ) ");
+    }
+
+    return paramIndex;
+  }
+
+  /**
+   * Returns the parameter index of anno's TypeAnnotationPosition.
+   *
+   * @return the parameter index of anno's TypeAnnotationPosition
+   */
+  @Override
+  public int getTypeCompoundIndex(Attribute.TypeCompound anno) {
+    return anno.getPosition().parameter_index;
+  }
+
+  /**
+   * Returns {TargetType.METHOD_FORMAL_PARAMETER, TargetType.METHOD_RECEIVER}.
+   *
+   * @return {TargetType.METHOD_FORMAL_PARAMETER, TargetType.METHOD_RECEIVER}
+   */
+  @Override
+  protected TargetType[] annotatedTargets() {
+    return new TargetType[] {TargetType.METHOD_FORMAL_PARAMETER, TargetType.METHOD_RECEIVER};
+  }
+
+  /**
+   * Returns any annotation TargetType that can be found on a method.
+   *
+   * @return any annotation TargetType that can be found on a method
+   */
+  @Override
+  protected TargetType[] validTargets() {
+    return new TargetType[] {
+      TargetType.METHOD_FORMAL_PARAMETER,
+      TargetType.METHOD_RETURN,
+      TargetType.THROWS,
+      TargetType.METHOD_TYPE_PARAMETER,
+      TargetType.METHOD_TYPE_PARAMETER_BOUND,
+      TargetType.LOCAL_VARIABLE,
+      TargetType.RESOURCE_VARIABLE,
+      TargetType.EXCEPTION_PARAMETER,
+      TargetType.NEW,
+      TargetType.CAST,
+      TargetType.INSTANCEOF,
+      TargetType.METHOD_INVOCATION_TYPE_ARGUMENT,
+      TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT,
+      TargetType.METHOD_REFERENCE,
+      TargetType.CONSTRUCTOR_REFERENCE,
+      TargetType.METHOD_REFERENCE_TYPE_ARGUMENT,
+      TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT,
+      // TODO: from generic anonymous classes; remove when
+      // we can depend on only seeing classfiles that were
+      // generated by a javac that contains a fix for:
+      // https://bugs.openjdk.java.net/browse/JDK-8198945
+      TargetType.CLASS_EXTENDS
+    };
+  }
+
+  /**
+   * Returns the TypeCompounds (annotations) of the enclosing method for this parameter.
+   *
+   * @return the TypeCompounds (annotations) of the enclosing method for this parameter
+   */
+  @Override
+  protected Iterable<Attribute.TypeCompound> getRawTypeAttributes() {
+    return enclosingMethod.getRawTypeAttributes();
+  }
+
+  @Override
+  protected Map<TargetClass, List<TypeCompound>> sift(
+      Iterable<Attribute.TypeCompound> typeCompounds) {
+    // this will sift out the annotations that do not have the right position index
+    final Map<TargetClass, List<Attribute.TypeCompound>> targetClassToAnnos =
+        super.sift(typeCompounds);
+
+    final List<Attribute.TypeCompound> targeted = targetClassToAnnos.get(TargetClass.TARGETED);
+    final List<Attribute.TypeCompound> valid = targetClassToAnnos.get(TargetClass.VALID);
+
+    // if this is a lambdaParam, filter out from targeted those annos that apply to method
+    // formal parameters if this is a method formal param, filter out from targeted those annos
+    // that apply to lambdas
+    int i = 0;
+    while (i < targeted.size()) {
+      final Tree onLambda = targeted.get(i).position.onLambda;
+      if (onLambda == null) {
+        if (!isLambdaParam) {
+          ++i;
+        } else {
+          valid.add(targeted.remove(i));
+        }
+
+      } else {
+        if (onLambda.equals(this.lambdaTree)) {
+          ++i;
+        } else {
+          valid.add(targeted.remove(i));
+        }
+      }
+    }
+
+    return targetClassToAnnos;
+  }
+
+  /**
+   * @param targeted type compounds with formal method parameter target types with parameter_index
+   *     == getIndex
+   */
+  @Override
+  protected void handleTargeted(final List<TypeCompound> targeted)
+      throws UnexpectedAnnotationLocationException {
+
+    final List<TypeCompound> formalParams = new ArrayList<>();
+    Map<TargetType, List<TypeCompound>> targetToAnnos =
+        ElementAnnotationUtil.partitionByTargetType(
+            targeted, formalParams, TargetType.METHOD_RECEIVER);
+
+    if (isReceiver(element)) {
+      ElementAnnotationUtil.annotateViaTypeAnnoPosition(
+          type, targetToAnnos.get(TargetType.METHOD_RECEIVER));
+
+    } else {
+      ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, formalParams);
+    }
+  }
+
+  /**
+   * Returns true if element represents the receiver parameter of a method.
+   *
+   * @param element an element
+   * @return true if element represents the receiver parameter of a method
+   */
+  private boolean isReceiver(final Element element) {
+    return element.getKind() == ElementKind.PARAMETER
+        && element.getSimpleName().contentEquals("this");
+  }
+
+  @Override
+  protected boolean isAccepted() {
+    return accepts(type, element);
+  }
+
+  /**
+   * Return the enclosing MethodSymbol of the given element, throwing an exception of the symbol's
+   * enclosing element is not a MethodSymbol.
+   *
+   * @param methodChildElem some element that is a child of a method typeDeclaration (e.g. a
+   *     parameter or return type)
+   * @return the MethodSymbol of the method containing methodChildElem
+   */
+  public static Symbol.MethodSymbol getParentMethod(final Element methodChildElem) {
+    if (!(methodChildElem.getEnclosingElement() instanceof Symbol.MethodSymbol)) {
+      throw new BugInCF(
+          "Element is not a direct child of a MethodSymbol. Element ( "
+              + methodChildElem
+              + " parent ( "
+              + methodChildElem.getEnclosingElement()
+              + " ) ");
+    }
+    return (Symbol.MethodSymbol) methodChildElem.getEnclosingElement();
+  }
+
+  @Override
+  public void extractAndApply() throws UnexpectedAnnotationLocationException {
+    ElementAnnotationUtil.addDeclarationAnnotationsFromElement(
+        type, element.getAnnotationMirrors());
+    super.extractAndApply();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/SuperTypeApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/SuperTypeApplier.java
new file mode 100644
index 0000000..1d12397
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/element/SuperTypeApplier.java
@@ -0,0 +1,136 @@
+package org.checkerframework.framework.util.element;
+
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Attribute.TypeCompound;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.TargetType;
+import java.util.List;
+import javax.lang.model.element.TypeElement;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException;
+
+/**
+ * When discovering supertypes of an AnnotatedTypeMirror we want to annotate each supertype with the
+ * annotations of the subtypes class declaration. This class provides static methods to do this for
+ * a list of supertypes. An instance of this class handles ONE supertype.
+ */
+public class SuperTypeApplier extends IndexedElementAnnotationApplier {
+
+  /**
+   * Annotates each supertype with annotations from subtypeElement's extends/implements clauses.
+   *
+   * @param supertypes supertypes to annotate
+   * @param subtypeElement element that may have annotations to apply to supertypes
+   */
+  public static void annotateSupers(
+      List<AnnotatedTypeMirror.AnnotatedDeclaredType> supertypes, TypeElement subtypeElement)
+      throws UnexpectedAnnotationLocationException {
+    for (int i = 0; i < supertypes.size(); i++) {
+      final AnnotatedTypeMirror supertype = supertypes.get(i);
+      // Offset i by -1 since typeIndex should start from -1.
+      // -1 represents the (implicit) extends clause class.
+      // 0 and greater represent the implements clause interfaces.
+      // For details see the JSR 308 specification:
+      // http://types.cs.washington.edu/jsr308/specification/java-annotation-design.html#class-file%3Aext%3Ari%3Aextends
+      final int typeIndex = i - 1;
+      new SuperTypeApplier(supertype, subtypeElement, typeIndex).extractAndApply();
+    }
+  }
+
+  private final Symbol.ClassSymbol subclassSymbol;
+
+  /**
+   * The type_index of the supertype being annotated.
+   *
+   * <p>Note: Due to the semantics of TypeAnnotationPosition, type_index/index numbering works as
+   * follows:
+   *
+   * <p>If subtypeElement represents a class and not an interface:
+   *
+   * <p>then the first member of supertypes represents the object and the relevant type_index = -1;
+   * interface indices are offset by 1.
+   *
+   * <p>else all members of supertypes represent interfaces and their indices == their index in the
+   * supertypes list
+   */
+  private final int index;
+
+  /**
+   * Note: This is not meant to be used in apply explicitly unlike all other AnnotationAppliers it
+   * is intended to be used for annotate super types via the static annotateSuper method, hence the
+   * private constructor.
+   */
+  SuperTypeApplier(
+      final AnnotatedTypeMirror supertype, final TypeElement subclassElement, final int index) {
+    super(supertype, subclassElement);
+    this.subclassSymbol = (Symbol.ClassSymbol) subclassElement;
+    this.index = index;
+  }
+
+  /**
+   * Returns the type_index that should represent supertype.
+   *
+   * @return the type_index that should represent supertype
+   */
+  @Override
+  public int getElementIndex() {
+    return index;
+  }
+
+  /**
+   * Returns the type_index of anno's TypeAnnotationPosition.
+   *
+   * @return the type_index of anno's TypeAnnotationPosition
+   */
+  @Override
+  public int getTypeCompoundIndex(Attribute.TypeCompound anno) {
+    int typeIndex = anno.getPosition().type_index;
+    // TODO: this is a workaround of a bug in langtools
+    // https://bugs.openjdk.java.net/browse/JDK-8164519
+    // This bug is fixed in Java 9.
+    return typeIndex == 0xffff ? -1 : typeIndex;
+  }
+
+  /**
+   * Returns TargetType.CLASS_EXTENDS.
+   *
+   * @return TargetType.CLASS_EXTENDS
+   */
+  @Override
+  protected TargetType[] annotatedTargets() {
+    return new TargetType[] {TargetType.CLASS_EXTENDS};
+  }
+
+  /**
+   * Returns TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND.
+   *
+   * @return TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND
+   */
+  @Override
+  protected TargetType[] validTargets() {
+    return new TargetType[] {
+      TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND
+    };
+  }
+
+  /**
+   * Returns the TypeCompounds (annotations) of the subclass.
+   *
+   * @return the TypeCompounds (annotations) of the subclass
+   */
+  @Override
+  protected Iterable<Attribute.TypeCompound> getRawTypeAttributes() {
+    return subclassSymbol.getRawTypeAttributes();
+  }
+
+  @Override
+  protected void handleTargeted(List<TypeCompound> targeted)
+      throws UnexpectedAnnotationLocationException {
+    ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, targeted);
+  }
+
+  @Override
+  protected boolean isAccepted() {
+    return true;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TargetedElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TargetedElementAnnotationApplier.java
new file mode 100644
index 0000000..049181e
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/element/TargetedElementAnnotationApplier.java
@@ -0,0 +1,209 @@
+package org.checkerframework.framework.util.element;
+
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Attribute.TypeCompound;
+import com.sun.tools.javac.code.TargetType;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException;
+import org.checkerframework.javacutil.BugInCF;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * TargetedElementAnnotationApplier filters annotations for an element into 3 groups. TARGETED
+ * annotations are those we wish to apply in this ElementAnnotationApplier. VALID annotations are
+ * those that are valid on the current element (or its enclosure, see getRawTypeAttributes) but
+ * should not be applied to the given type. Invalid annotations are those that should NEVER appear
+ * for the given element. Invalid annotations are reported as errors by default in the handleInvalid
+ * method. See method extractAndApply. Please read getRawTypeAttributes for an idea of what types of
+ * annotations may be encountered by this ElementAnnotationApplier.
+ *
+ * <p>Note: Subtypes of this class likely want to implement the handleTargeted and handleValid
+ * methods though they have default empty implementations for brevity.
+ */
+abstract class TargetedElementAnnotationApplier {
+  /**
+   * Three annotation types that may be encountered when calling getRawTypeAttributes. see sift().
+   */
+  static enum TargetClass {
+    TARGETED,
+    VALID,
+    INVALID
+  }
+
+  /** The type to which we wish to apply annotations. */
+  protected final AnnotatedTypeMirror type;
+
+  /** An Element that type represents. */
+  protected final Element element;
+
+  /**
+   * Returns the TargetTypes that identify annotations we wish to apply with this object. Any
+   * annotations that have these target types will be passed to handleTargeted.
+   *
+   * @return the TargetTypes that identify annotations we wish to apply with this object. Any
+   *     annotations that have these target types will be passed to handleTargeted
+   */
+  protected abstract TargetType[] annotatedTargets();
+
+  /**
+   * Returns the TargetTypes that identify annotations that are valid but we wish to ignore. Any
+   * annotations that have these target types will be passed to handleValid, providing they aren't
+   * also in annotatedTargets.
+   *
+   * @return the TargetTypes that identify annotations that are valid but we wish to ignore
+   */
+  protected abstract TargetType[] validTargets();
+
+  /**
+   * Annotations on elements are represented as Attribute.TypeCompounds ( a subtype of
+   * AnnotationMirror) that are usually accessed through a getRawTypeAttributes method on the
+   * element.
+   *
+   * <p>In Java 8 and later these annotations are generally contained by elements to which they
+   * apply. However, in earlier versions of Java many of these annotations are handled by either the
+   * enclosing method, e.g. parameters and method type parameters, or enclosing class, e.g. class
+   * type parameters. Therefore, many annotations are addressed by first getting all annotations on
+   * a method or class and the picking out only the ones we wish to target (see extractAndApply).
+   *
+   * @return the annotations that we MAY wish to apply to the given type
+   */
+  protected abstract Iterable<Attribute.TypeCompound> getRawTypeAttributes();
+
+  /**
+   * Tests element/type fields to ensure that this TargetedElementAnnotationApplier is valid for
+   * this element/type pair.
+   *
+   * @return true if the type/element members are handled by this class false otherwise
+   */
+  protected abstract boolean isAccepted();
+
+  /**
+   * @param type the type to annotate
+   * @param element an element identifying type
+   */
+  TargetedElementAnnotationApplier(final AnnotatedTypeMirror type, final Element element) {
+    this.type = type;
+    this.element = element;
+  }
+
+  /**
+   * This method should apply all annotations that are handled by this object.
+   *
+   * @param targeted the list of annotations that were returned by getRawTypeAttributes and had a
+   *     TargetType contained by annotatedTargets
+   */
+  protected abstract void handleTargeted(List<TypeCompound> targeted)
+      throws UnexpectedAnnotationLocationException;
+
+  /**
+   * The default implementation of this method does nothing.
+   *
+   * @param valid the list of annotations that were returned by getRawTypeAttributes and had a
+   *     TargetType contained by valid and NOT annotatedTargets
+   */
+  protected void handleValid(List<Attribute.TypeCompound> valid) {}
+
+  /**
+   * This implementation reports all invalid annotations as errors.
+   *
+   * @param invalid the list of annotations that were returned by getRawTypeAttributes and were not
+   *     handled by handleTargeted or handleValid
+   */
+  protected void handleInvalid(List<Attribute.TypeCompound> invalid) {
+    List<Attribute.TypeCompound> remaining = new ArrayList<>(invalid.size());
+    for (Attribute.TypeCompound tc : invalid) {
+      if (tc.getAnnotationType().getKind() != TypeKind.ERROR) {
+        // Filter out annotations that have an error type. javac will
+        // already have raised an error for them.
+        remaining.add(tc);
+      }
+    }
+    if (!remaining.isEmpty()) {
+      StringJoiner msg = new StringJoiner(System.lineSeparator());
+      msg.add("handleInvalid(this=" + this.getClass().getName() + "):");
+      msg.add("Invalid variable and element passed to extractAndApply; type: " + type);
+      String elementInfoPrefix =
+          "  element: " + element + " (kind: " + element.getKind() + "), invalid annotations: ";
+      StringJoiner remainingInfo = new StringJoiner(", ", elementInfoPrefix, "");
+      for (Attribute.TypeCompound r : remaining) {
+        remainingInfo.add(r.toString() + " (" + r.position + ")");
+      }
+      msg.add(remainingInfo.toString());
+      msg.add("Targeted annotations: " + StringsPlume.join(", ", annotatedTargets()));
+      msg.add("Valid annotations: " + StringsPlume.join(", ", validTargets()));
+
+      throw new BugInCF(msg.toString());
+    }
+  }
+
+  /**
+   * Separate the input annotations into a Map of TargetClass (TARGETED, VALID, INVALID) to the
+   * annotations that fall into each of those categories.
+   *
+   * @param typeCompounds annotations to sift through, should be those returned by
+   *     getRawTypeAttributes
+   * @return a {@literal Map<TargetClass => Annotations>.}
+   */
+  protected Map<TargetClass, List<Attribute.TypeCompound>> sift(
+      final Iterable<Attribute.TypeCompound> typeCompounds) {
+
+    final Map<TargetClass, List<Attribute.TypeCompound>> targetClassToCompound =
+        new EnumMap<>(TargetClass.class);
+    for (TargetClass targetClass : TargetClass.values()) {
+      targetClassToCompound.put(targetClass, new ArrayList<>());
+    }
+
+    for (final Attribute.TypeCompound typeCompound : typeCompounds) {
+      final TargetType typeCompoundTarget = typeCompound.position.type;
+      final List<Attribute.TypeCompound> destList;
+
+      if (ElementAnnotationUtil.contains(typeCompoundTarget, annotatedTargets())) {
+        destList = targetClassToCompound.get(TargetClass.TARGETED);
+
+      } else if (ElementAnnotationUtil.contains(typeCompoundTarget, validTargets())) {
+        destList = targetClassToCompound.get(TargetClass.VALID);
+
+      } else {
+        destList = targetClassToCompound.get(TargetClass.INVALID);
+      }
+
+      destList.add(typeCompound);
+    }
+
+    return targetClassToCompound;
+  }
+
+  /**
+   * Reads the list of annotations that apply to this element (see getRawTypeAttributes). Sifts them
+   * into three groups (TARGETED, INVALID, VALID) and then calls the appropriate handle method on
+   * them. The handleTargeted method should apply all annotations that are handled by this object.
+   *
+   * <p>This method will throw a runtime exception if isAccepted returns false.
+   */
+  public void extractAndApply() throws UnexpectedAnnotationLocationException {
+    if (!isAccepted()) {
+      throw new BugInCF(
+          "LocalVariableExtractor.extractAndApply: "
+              + "Invalid variable and element passed to "
+              + this.getClass().getName()
+              + "::extractAndApply ("
+              + type
+              + ", "
+              + element);
+    }
+
+    final Map<TargetClass, List<Attribute.TypeCompound>> targetClassToAnno =
+        sift(getRawTypeAttributes());
+
+    handleInvalid(targetClassToAnno.get(TargetClass.INVALID));
+    handleValid(targetClassToAnno.get(TargetClass.VALID));
+    handleTargeted(targetClassToAnno.get(TargetClass.TARGETED));
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TypeDeclarationApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TypeDeclarationApplier.java
new file mode 100644
index 0000000..a49c950
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/element/TypeDeclarationApplier.java
@@ -0,0 +1,124 @@
+package org.checkerframework.framework.util.element;
+
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Attribute.TypeCompound;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.TargetType;
+import java.util.List;
+import javax.lang.model.element.Element;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** Apply annotations to a declared type based on its declaration. */
+public class TypeDeclarationApplier extends TargetedElementAnnotationApplier {
+
+  public static void apply(
+      final AnnotatedTypeMirror type, final Element element, final AnnotatedTypeFactory typeFactory)
+      throws UnexpectedAnnotationLocationException {
+    new TypeDeclarationApplier(type, element, typeFactory).extractAndApply();
+  }
+
+  /**
+   * If a type_index == -1 it means that the index refers to the immediate supertype class of the
+   * declaration. There is only ever one of these since java has no multiple inheritance
+   */
+  public static final int SUPERCLASS_INDEX = -1;
+
+  /**
+   * Returns true if type is an annotated declared type and element is a ClassSymbol.
+   *
+   * @param type a type
+   * @param element an element
+   * @return true if type is an annotated declared type and element is a ClassSymbol
+   */
+  public static boolean accepts(final AnnotatedTypeMirror type, final Element element) {
+    return type instanceof AnnotatedDeclaredType && element instanceof Symbol.ClassSymbol;
+  }
+
+  private final AnnotatedTypeFactory typeFactory;
+  private final Symbol.ClassSymbol typeSymbol;
+  private final AnnotatedDeclaredType declaredType;
+
+  TypeDeclarationApplier(
+      final AnnotatedTypeMirror type,
+      final Element element,
+      final AnnotatedTypeFactory typeFactory) {
+    super(type, element);
+    this.typeFactory = typeFactory;
+    this.typeSymbol = (Symbol.ClassSymbol) element;
+    this.declaredType = (AnnotatedDeclaredType) type;
+  }
+
+  @Override
+  protected TargetType[] validTargets() {
+    return new TargetType[] {
+      TargetType.RESOURCE_VARIABLE,
+      TargetType.EXCEPTION_PARAMETER,
+      TargetType.NEW,
+      TargetType.CAST,
+      TargetType.INSTANCEOF,
+      TargetType.METHOD_INVOCATION_TYPE_ARGUMENT,
+      TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT,
+      TargetType.METHOD_REFERENCE,
+      TargetType.CONSTRUCTOR_REFERENCE,
+      TargetType.METHOD_REFERENCE_TYPE_ARGUMENT,
+      TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT,
+      TargetType.CLASS_TYPE_PARAMETER,
+      TargetType.CLASS_TYPE_PARAMETER_BOUND
+    };
+  }
+
+  @Override
+  protected TargetType[] annotatedTargets() {
+    return new TargetType[] {TargetType.CLASS_EXTENDS};
+  }
+
+  /** All TypeCompounds (annotations) on the ClassSymbol. */
+  @Override
+  protected Iterable<Attribute.TypeCompound> getRawTypeAttributes() {
+    return typeSymbol.getRawTypeAttributes();
+  }
+
+  /**
+   * While more than just annotations on extends or implements clause are annotated by this class,
+   * only these annotations are passed to handleTargeted (as they are the only in the
+   * annotatedTargets list). See extractAndApply for type parameters
+   *
+   * @param extendsAndImplementsAnnos annotations with a TargetType of CLASS_EXTENDS
+   */
+  @Override
+  protected void handleTargeted(List<TypeCompound> extendsAndImplementsAnnos)
+      throws UnexpectedAnnotationLocationException {
+    if (TypesUtils.isAnonymous(typeSymbol.type)) {
+      // If this is an anonymous class, then the annotations after "new" but before the class name
+      // are stored as super class annotations. Treat them as annotations on the class.
+      for (final Attribute.TypeCompound anno : extendsAndImplementsAnnos) {
+        if (anno.position.type_index >= SUPERCLASS_INDEX && anno.position.location.isEmpty()) {
+          type.addAnnotation(anno);
+        }
+      }
+    }
+  }
+
+  /** Adds extends/implements and class annotations to type. Annotates type parameters. */
+  @Override
+  public void extractAndApply() throws UnexpectedAnnotationLocationException {
+    // ensures that we check that there only valid target types on this class, there are no
+    // "targeted" locations
+    super.extractAndApply();
+
+    // Annotate raw types // TODO: ASK WERNER WHAT THIS MIGHT MEAN?  WHAT ACTUALLY GOES HERE?
+    type.addAnnotations(typeSymbol.getAnnotationMirrors());
+
+    ElementAnnotationUtil.applyAllElementAnnotations(
+        declaredType.getTypeArguments(), typeSymbol.getTypeParameters(), typeFactory);
+  }
+
+  @Override
+  protected boolean isAccepted() {
+    return accepts(type, element);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TypeParamElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TypeParamElementAnnotationApplier.java
new file mode 100644
index 0000000..ffd9006
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/element/TypeParamElementAnnotationApplier.java
@@ -0,0 +1,234 @@
+package org.checkerframework.framework.util.element;
+
+import com.sun.tools.javac.code.Attribute.TypeCompound;
+import com.sun.tools.javac.code.TargetType;
+import java.util.ArrayList;
+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.ElementKind;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Applies Element annotations to a single AnnotatedTypeVariable representing a type parameter.
+ * Note, the index of IndexedElementAnnotationApplier refers to the type parameter's index in the
+ * list that encloses it.
+ */
+abstract class TypeParamElementAnnotationApplier extends IndexedElementAnnotationApplier {
+
+  /**
+   * Returns true if element is a TYPE_PARAMETER.
+   *
+   * @param typeMirror ignored
+   * @param element the element that might be a TYPE_PARAMETER
+   * @return true if element is a TYPE_PARAMETER
+   */
+  public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) {
+    return element.getKind() == ElementKind.TYPE_PARAMETER;
+  }
+
+  protected final AnnotatedTypeVariable typeParam;
+  protected final AnnotatedTypeFactory typeFactory;
+
+  /**
+   * Returns target type that represents the location of the lower bound of element.
+   *
+   * @return target type that represents the location of the lower bound of element
+   */
+  protected abstract TargetType lowerBoundTarget();
+
+  /**
+   * Returns target type that represents the location of the upper bound of element.
+   *
+   * @return target type that represents the location of the upper bound of element
+   */
+  protected abstract TargetType upperBoundTarget();
+
+  TypeParamElementAnnotationApplier(
+      final AnnotatedTypeVariable type,
+      final Element element,
+      final AnnotatedTypeFactory typeFactory) {
+    super(type, element);
+    this.typeParam = type;
+    this.typeFactory = typeFactory;
+  }
+
+  /**
+   * Returns the lower bound and upper bound targets.
+   *
+   * @return the lower bound and upper bound targets
+   */
+  @Override
+  protected TargetType[] annotatedTargets() {
+    return new TargetType[] {lowerBoundTarget(), upperBoundTarget()};
+  }
+
+  /**
+   * Returns the parameter_index of anno's TypeAnnotationPosition which will actually point to the
+   * type parameter's index in its enclosing type parameter list.
+   *
+   * @return the parameter_index of anno's TypeAnnotationPosition which will actually point to the
+   *     type parameter's index in its enclosing type parameter list
+   */
+  @Override
+  public int getTypeCompoundIndex(final TypeCompound anno) {
+    return anno.getPosition().parameter_index;
+  }
+
+  /**
+   * @param targeted the list of annotations that were on the lower/upper bounds of the type
+   *     parameter
+   *     <p>Note: When handling type parameters we NEVER add primary annotations to the type
+   *     parameter. Primary annotations are reserved for the use of a type parameter (e.g. @Nullable
+   *     T t; )
+   *     <p>If an annotation is present on the type parameter itself, it represents the lower-bound
+   *     annotation of that type parameter. Any annotation on the extends bound of a type parameter
+   *     is placed on that bound.
+   */
+  @Override
+  protected void handleTargeted(final List<TypeCompound> targeted)
+      throws UnexpectedAnnotationLocationException {
+    final int paramIndex = getElementIndex();
+    final List<TypeCompound> upperBoundAnnos = new ArrayList<>();
+    final List<TypeCompound> lowerBoundAnnos = new ArrayList<>();
+
+    for (final TypeCompound anno : targeted) {
+      final AnnotationMirror aliasedAnno = typeFactory.canonicalAnnotation(anno);
+      final AnnotationMirror canonicalAnno = (aliasedAnno != null) ? aliasedAnno : anno;
+
+      if (anno.position.parameter_index != paramIndex
+          || !typeFactory.isSupportedQualifier(canonicalAnno)) {
+        continue;
+      }
+
+      if (ElementAnnotationUtil.isOnComponentType(anno)) {
+        applyComponentAnnotation(anno);
+
+      } else if (anno.position.type == upperBoundTarget()) {
+        upperBoundAnnos.add(anno);
+
+      } else {
+        lowerBoundAnnos.add(anno);
+      }
+    }
+
+    applyLowerBounds(lowerBoundAnnos);
+    applyUpperBounds(upperBoundAnnos);
+  }
+
+  /**
+   * Applies a list of annotations to the upperBound of the type parameter. If the type of the upper
+   * bound is an intersection we must first find the correct location for each annotation.
+   */
+  private void applyUpperBounds(final List<TypeCompound> upperBounds) {
+    if (!upperBounds.isEmpty()) {
+      final AnnotatedTypeMirror upperBoundType = typeParam.getUpperBound();
+
+      if (upperBoundType.getKind() == TypeKind.INTERSECTION) {
+
+        final List<AnnotatedTypeMirror> bounds =
+            ((AnnotatedIntersectionType) upperBoundType).getBounds();
+        final int boundIndexOffset = ElementAnnotationUtil.getBoundIndexOffset(bounds);
+
+        for (final TypeCompound anno : upperBounds) {
+          final int boundIndex = anno.position.bound_index + boundIndexOffset;
+
+          if (boundIndex < 0 || boundIndex > bounds.size()) {
+            throw new BugInCF(
+                "Invalid bound index on element annotation ( "
+                    + anno
+                    + " ) "
+                    + "for type ( "
+                    + typeParam
+                    + " ) with "
+                    + "upper bound ( "
+                    + typeParam.getUpperBound()
+                    + " ) "
+                    + "and boundIndex( "
+                    + boundIndex
+                    + " ) ");
+          }
+
+          bounds.get(boundIndex).replaceAnnotation(anno); // TODO: WHY NOT ADD?
+        }
+        ((AnnotatedIntersectionType) upperBoundType).copyIntersectionBoundAnnotations();
+
+      } else {
+        upperBoundType.addAnnotations(upperBounds);
+      }
+    }
+  }
+
+  /**
+   * In the event of multiple annotations on an AnnotatedNullType lower bound we want to preserve
+   * the multiple annotations so that a type.invalid error is issued later.
+   *
+   * @param annos the annotations to add to the lower bound
+   */
+  private void applyLowerBounds(final List<? extends AnnotationMirror> annos) {
+    if (!annos.isEmpty()) {
+      final AnnotatedTypeMirror lowerBound = typeParam.getLowerBound();
+
+      for (AnnotationMirror anno : annos) {
+        lowerBound.addAnnotation(anno);
+      }
+    }
+  }
+
+  private void addAnnotationToMap(
+      final AnnotatedTypeMirror type,
+      final TypeCompound anno,
+      final Map<AnnotatedTypeMirror, List<TypeCompound>> typeToAnnos) {
+    List<TypeCompound> annoList = typeToAnnos.computeIfAbsent(type, __ -> new ArrayList<>());
+    annoList.add(anno);
+  }
+
+  private void applyComponentAnnotation(final TypeCompound anno)
+      throws UnexpectedAnnotationLocationException {
+    final AnnotatedTypeMirror upperBoundType = typeParam.getUpperBound();
+
+    Map<AnnotatedTypeMirror, List<TypeCompound>> typeToAnnotations = new HashMap<>();
+
+    if (anno.position.type == upperBoundTarget()) {
+
+      if (upperBoundType.getKind() == TypeKind.INTERSECTION) {
+        final List<AnnotatedTypeMirror> bounds =
+            ((AnnotatedIntersectionType) upperBoundType).getBounds();
+        final int boundIndex =
+            anno.position.bound_index + ElementAnnotationUtil.getBoundIndexOffset(bounds);
+
+        if (boundIndex < 0 || boundIndex > bounds.size()) {
+          throw new BugInCF(
+              "Invalid bound index on element annotation ( "
+                  + anno
+                  + " ) "
+                  + "for type ( "
+                  + typeParam
+                  + " ) with upper bound ( "
+                  + typeParam.getUpperBound()
+                  + " )");
+        }
+        addAnnotationToMap(bounds.get(boundIndex), anno, typeToAnnotations);
+
+      } else {
+        addAnnotationToMap(upperBoundType, anno, typeToAnnotations);
+      }
+
+    } else {
+      addAnnotationToMap(typeParam.getLowerBound(), anno, typeToAnnotations);
+    }
+
+    for (Map.Entry<AnnotatedTypeMirror, List<TypeCompound>> typeToAnno :
+        typeToAnnotations.entrySet()) {
+      ElementAnnotationUtil.annotateViaTypeAnnoPosition(typeToAnno.getKey(), typeToAnno.getValue());
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java
new file mode 100644
index 0000000..0e0cd92
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java
@@ -0,0 +1,306 @@
+package org.checkerframework.framework.util.element;
+
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import com.sun.tools.javac.code.TargetType;
+import com.sun.tools.javac.code.TypeAnnotationPosition;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.ElementAnnotationApplier;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException;
+import org.checkerframework.javacutil.BugInCF;
+
+/** Apply annotations to the use of a type parameter declaration. */
+public class TypeVarUseApplier {
+
+  public static void apply(
+      final AnnotatedTypeMirror type, final Element element, final AnnotatedTypeFactory typeFactory)
+      throws UnexpectedAnnotationLocationException {
+    new TypeVarUseApplier(type, element, typeFactory).extractAndApply();
+  }
+
+  private static ElementKind[] acceptedKinds = {
+    ElementKind.PARAMETER,
+    ElementKind.FIELD,
+    ElementKind.LOCAL_VARIABLE,
+    ElementKind.RESOURCE_VARIABLE,
+    ElementKind.METHOD
+  };
+
+  /**
+   * Returns true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type variable
+   * component, and the element is not a TYPE_PARAMETER.
+   *
+   * @return true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type variable
+   *     component, and the element is not a TYPE_PARAMETER
+   */
+  public static boolean accepts(AnnotatedTypeMirror type, Element element) {
+    return (type instanceof AnnotatedTypeVariable || isGenericArrayType(type))
+        && ElementAnnotationUtil.contains(element.getKind(), acceptedKinds);
+  }
+
+  private static boolean isGenericArrayType(AnnotatedTypeMirror type) {
+    return type instanceof AnnotatedArrayType
+        && getNestedComponentType(type) instanceof AnnotatedTypeVariable;
+  }
+
+  private static AnnotatedTypeMirror getNestedComponentType(AnnotatedTypeMirror type) {
+
+    AnnotatedTypeMirror componentType = type;
+    while (componentType instanceof AnnotatedArrayType) {
+      componentType = ((AnnotatedArrayType) componentType).getComponentType();
+    }
+
+    return componentType;
+  }
+
+  // In order to avoid sprinkling code for type parameter uses all over the various locations
+  // uses can show up we also handle generic array types.  T [] myTArr;
+  private final AnnotatedArrayType arrayType;
+
+  private final AnnotatedTypeVariable typeVariable;
+  private final TypeParameterElement declarationElem;
+  private final Element useElem;
+
+  private AnnotatedTypeFactory typeFactory;
+
+  TypeVarUseApplier(
+      final AnnotatedTypeMirror type,
+      final Element element,
+      final AnnotatedTypeFactory typeFactory) {
+    if (!accepts(type, element)) {
+      throw new BugInCF(
+          "TypeParamUseApplier does not accept type/element combination ("
+              + " type ( "
+              + type
+              + " ) element ( "
+              + element
+              + " ) ");
+    }
+
+    if (isGenericArrayType(type)) {
+      this.arrayType = (AnnotatedArrayType) type;
+      this.typeVariable = (AnnotatedTypeVariable) getNestedComponentType(type);
+      this.declarationElem = (TypeParameterElement) typeVariable.getUnderlyingType().asElement();
+      this.useElem = element;
+      this.typeFactory = typeFactory;
+
+    } else {
+      this.arrayType = null;
+      this.typeVariable = (AnnotatedTypeVariable) type;
+      this.declarationElem = (TypeParameterElement) typeVariable.getUnderlyingType().asElement();
+      this.useElem = element;
+      this.typeFactory = typeFactory;
+    }
+  }
+
+  /**
+   * Applies the bound annotations from the declaration of the type parameter and then applies the
+   * explicit annotations written on the type variable.
+   *
+   * @throws UnexpectedAnnotationLocationException if invalid location for an annotation was found
+   */
+  public void extractAndApply() throws UnexpectedAnnotationLocationException {
+    ElementAnnotationUtil.addDeclarationAnnotationsFromElement(
+        typeVariable, useElem.getAnnotationMirrors());
+
+    // apply declaration annotations
+    ElementAnnotationApplier.apply(typeVariable, declarationElem, typeFactory);
+
+    final List<Attribute.TypeCompound> annotations = getAnnotations(useElem, declarationElem);
+
+    final List<Attribute.TypeCompound> typeVarAnnotations;
+    if (arrayType != null) {
+      // if the outer-most type is an array type then we want to ensure the outer annotations
+      // are not applied as the type variables primary annotation
+      typeVarAnnotations = removeComponentAnnotations(arrayType, annotations);
+      ElementAnnotationUtil.annotateViaTypeAnnoPosition(arrayType, annotations);
+
+    } else {
+      typeVarAnnotations = annotations;
+    }
+
+    for (final Attribute.TypeCompound annotation : typeVarAnnotations) {
+      typeVariable.replaceAnnotation(annotation);
+    }
+  }
+
+  private List<Attribute.TypeCompound> removeComponentAnnotations(
+      final AnnotatedArrayType arrayType, final List<Attribute.TypeCompound> annotations) {
+
+    final List<Attribute.TypeCompound> componentAnnotations = new ArrayList<>();
+
+    if (arrayType != null) {
+      for (int i = 0; i < annotations.size(); ) {
+        final Attribute.TypeCompound anno = annotations.get(i);
+        if (isBaseComponent(arrayType, anno)) {
+          componentAnnotations.add(anno);
+          annotations.remove(anno);
+        } else {
+          i++;
+        }
+      }
+    }
+
+    return componentAnnotations;
+  }
+
+  private boolean isBaseComponent(
+      final AnnotatedArrayType arrayType, final Attribute.TypeCompound anno) {
+    try {
+      return ElementAnnotationUtil.getTypeAtLocation(arrayType, anno.getPosition().location)
+              .getKind()
+          == TypeKind.TYPEVAR;
+    } catch (UnexpectedAnnotationLocationException ex) {
+      return false;
+    }
+  }
+
+  /**
+   * Depending on what element type the annotations are stored on, the relevant annotations might be
+   * stored with different annotation positions. getAnnotations finds the correct annotations by
+   * annotation position and element kind and returns them
+   */
+  private static List<Attribute.TypeCompound> getAnnotations(
+      final Element useElem, final Element declarationElem) {
+    final List<Attribute.TypeCompound> annotations;
+    switch (useElem.getKind()) {
+      case METHOD:
+        annotations = getReturnAnnos(useElem);
+        break;
+
+      case PARAMETER:
+        annotations = getParameterAnnos(useElem);
+        break;
+
+      case FIELD:
+      case LOCAL_VARIABLE:
+      case RESOURCE_VARIABLE:
+        annotations = getVariableAnnos(useElem);
+        break;
+
+      default:
+        throw new BugInCF(
+            "TypeVarUseApplier::extractAndApply : "
+                + "Unhandled element kind "
+                + useElem.getKind()
+                + "useElem ( "
+                + useElem
+                + " ) "
+                + "declarationElem ( "
+                + declarationElem
+                + " ) ");
+    }
+
+    return annotations;
+  }
+
+  /**
+   * Returns annotations on an element that apply to variable declarations.
+   *
+   * @param variableElem the element whose annotations to check
+   * @return annotations on an element that apply to variable declarations
+   */
+  private static List<Attribute.TypeCompound> getVariableAnnos(final Element variableElem) {
+    final VarSymbol varSymbol = (VarSymbol) variableElem;
+    final List<Attribute.TypeCompound> annotations = new ArrayList<>();
+
+    for (Attribute.TypeCompound anno : varSymbol.getRawTypeAttributes()) {
+
+      TypeAnnotationPosition pos = anno.position;
+      switch (pos.type) {
+        case FIELD:
+        case LOCAL_VARIABLE:
+        case RESOURCE_VARIABLE:
+        case EXCEPTION_PARAMETER:
+          annotations.add(anno);
+          break;
+
+        default:
+      }
+    }
+
+    return annotations;
+  }
+
+  /**
+   * Currently, the metadata for storing annotations (i.e. the Attribute.TypeCompounds) is null for
+   * binary-only parameters and type parameters. However, it is present on the method. So in order
+   * to ensure that we correctly retrieve the annotations we need to index from the method and
+   * retrieve the annotations from its metadata.
+   *
+   * @return a list of annotations that were found on METHOD_FORMAL_PARAMETERS that match the
+   *     parameter index of the input element in the parent methods formal parameter list
+   */
+  private static List<Attribute.TypeCompound> getParameterAnnos(final Element paramElem) {
+    final Element enclosingElement = paramElem.getEnclosingElement();
+    if (!(enclosingElement instanceof ExecutableElement)) {
+      throw new BugInCF(
+          "Bad element passed to TypeFromElement.getTypeParameterAnnotationAttributes: "
+              + "element: "
+              + paramElem
+              + " not found in enclosing executable: "
+              + enclosingElement);
+    }
+
+    final MethodSymbol enclosingMethod = (MethodSymbol) enclosingElement;
+
+    if (enclosingMethod.getKind() != ElementKind.CONSTRUCTOR
+        && enclosingMethod.getKind() != ElementKind.METHOD) {
+      // Initializer blocks don't have parameters, so there is nothing to do.
+      return Collections.emptyList();
+    }
+
+    // TODO: for the parameter in a lambda expression, the enclosingMethod isn't
+    // the lambda expression. Does this read the correct annotations?
+
+    final int paramIndex = enclosingMethod.getParameters().indexOf(paramElem);
+    final List<Attribute.TypeCompound> annotations = enclosingMethod.getRawTypeAttributes();
+
+    final List<Attribute.TypeCompound> result = new ArrayList<>();
+    for (final Attribute.TypeCompound typeAnno : annotations) {
+      if (typeAnno.position.type == TargetType.METHOD_FORMAL_PARAMETER) {
+        if (typeAnno.position.parameter_index == paramIndex) {
+          result.add(typeAnno);
+        }
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns the annotations on the return type of the input ExecutableElement.
+   *
+   * @param methodElem the method whose return type annotations to return
+   * @return the annotations on the return type of the input ExecutableElement
+   */
+  private static List<Attribute.TypeCompound> getReturnAnnos(final Element methodElem) {
+    if (!(methodElem instanceof ExecutableElement)) {
+      throw new BugInCF("Bad element passed to TypeVarUseApplier.getReturnAnnos:" + methodElem);
+    }
+
+    final MethodSymbol enclosingMethod = (MethodSymbol) methodElem;
+
+    final List<Attribute.TypeCompound> annotations = enclosingMethod.getRawTypeAttributes();
+    final List<Attribute.TypeCompound> result = new ArrayList<>();
+    for (final Attribute.TypeCompound typeAnno : annotations) {
+      if (typeAnno.position.type == TargetType.METHOD_RETURN) {
+        result.add(typeAnno);
+      }
+    }
+
+    return result;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java
new file mode 100644
index 0000000..1c270b9
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java
@@ -0,0 +1,108 @@
+package org.checkerframework.framework.util.element;
+
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Attribute.TypeCompound;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.TargetType;
+import java.util.List;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Applies annotations to variable declaration (providing they are not the use of a TYPE_PARAMETER).
+ */
+public class VariableApplier extends TargetedElementAnnotationApplier {
+
+  /** Apply annotations from {@code element} to {@code type}. */
+  public static void apply(final AnnotatedTypeMirror type, final Element element)
+      throws UnexpectedAnnotationLocationException {
+    new VariableApplier(type, element).extractAndApply();
+  }
+
+  private static final ElementKind[] acceptedKinds = {
+    ElementKind.LOCAL_VARIABLE, ElementKind.RESOURCE_VARIABLE, ElementKind.EXCEPTION_PARAMETER
+  };
+
+  /**
+   * Returns true if this is a variable declaration including fields an enum constants.
+   *
+   * @param typeMirror ignored
+   * @return true if this is a variable declaration including fields an enum constants
+   */
+  public static boolean accepts(final AnnotatedTypeMirror typeMirror, final Element element) {
+    return ElementAnnotationUtil.contains(element.getKind(), acceptedKinds)
+        || element.getKind().isField();
+  }
+
+  private final Symbol.VarSymbol varSymbol;
+
+  VariableApplier(final AnnotatedTypeMirror type, final Element element) {
+    super(type, element);
+    varSymbol = (Symbol.VarSymbol) element;
+
+    if (type.getKind() == TypeKind.UNION && element.getKind() != ElementKind.EXCEPTION_PARAMETER) {
+      throw new BugInCF(
+          "Union types only allowed for exception parameters. "
+              + "Type: "
+              + type
+              + " for element: "
+              + element);
+    }
+    // TODO: need a way to split the union types into the right alternative
+    // to use for the annotation. The exception_index is probably what we
+    // need to look at, but it might not be set at this point.
+  }
+
+  @Override
+  protected TargetType[] annotatedTargets() {
+    return new TargetType[] {
+      TargetType.LOCAL_VARIABLE,
+      TargetType.RESOURCE_VARIABLE,
+      TargetType.EXCEPTION_PARAMETER,
+      TargetType.FIELD
+    };
+  }
+
+  @Override
+  protected TargetType[] validTargets() {
+    return new TargetType[] {
+      TargetType.NEW,
+      TargetType.CAST,
+      TargetType.INSTANCEOF,
+      TargetType.METHOD_INVOCATION_TYPE_ARGUMENT,
+      TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT,
+      TargetType.METHOD_REFERENCE,
+      TargetType.CONSTRUCTOR_REFERENCE,
+      TargetType.METHOD_REFERENCE_TYPE_ARGUMENT,
+      TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+    };
+  }
+
+  @Override
+  protected Iterable<Attribute.TypeCompound> getRawTypeAttributes() {
+    return varSymbol.getRawTypeAttributes();
+  }
+
+  @Override
+  protected boolean isAccepted() {
+    return accepts(type, element);
+  }
+
+  @Override
+  protected void handleTargeted(final List<TypeCompound> targeted)
+      throws UnexpectedAnnotationLocationException {
+    ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, targeted);
+  }
+
+  @Override
+  public void extractAndApply() throws UnexpectedAnnotationLocationException {
+    // Add declaration annotations to the local variable type
+    ElementAnnotationUtil.addDeclarationAnnotationsFromElement(
+        type, varSymbol.getAnnotationMirrors());
+    super.extractAndApply();
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/DefaultTypeArgumentInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/DefaultTypeArgumentInference.java
new file mode 100644
index 0000000..a054662
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/DefaultTypeArgumentInference.java
@@ -0,0 +1,868 @@
+package org.checkerframework.framework.util.typeinference;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic.Kind;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.TypeHierarchy;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+import org.checkerframework.framework.util.typeinference.constraint.A2F;
+import org.checkerframework.framework.util.typeinference.constraint.A2FReducer;
+import org.checkerframework.framework.util.typeinference.constraint.AFConstraint;
+import org.checkerframework.framework.util.typeinference.constraint.AFReducer;
+import org.checkerframework.framework.util.typeinference.constraint.F2A;
+import org.checkerframework.framework.util.typeinference.constraint.F2AReducer;
+import org.checkerframework.framework.util.typeinference.constraint.FIsA;
+import org.checkerframework.framework.util.typeinference.constraint.FIsAReducer;
+import org.checkerframework.framework.util.typeinference.constraint.TSubU;
+import org.checkerframework.framework.util.typeinference.constraint.TSuperU;
+import org.checkerframework.framework.util.typeinference.constraint.TUConstraint;
+import org.checkerframework.framework.util.typeinference.solver.ConstraintMap;
+import org.checkerframework.framework.util.typeinference.solver.ConstraintMapBuilder;
+import org.checkerframework.framework.util.typeinference.solver.EqualitiesSolver;
+import org.checkerframework.framework.util.typeinference.solver.InferenceResult;
+import org.checkerframework.framework.util.typeinference.solver.InferredValue;
+import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType;
+import org.checkerframework.framework.util.typeinference.solver.SubtypesSolver;
+import org.checkerframework.framework.util.typeinference.solver.SupertypesSolver;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.Pair;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TypeAnnotationUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * An implementation of TypeArgumentInference that mostly follows the process outlined in JLS7 See
+ * the JLS 7: <a
+ * href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.7">JLS
+ * &sect;5.12.2.7</a>
+ *
+ * <p>Note, there are some deviations JLS 7 for the following cases:
+ *
+ * <ul>
+ *   <li>Places where the JLS is vague. For these cases, first the OpenJDK implementation was
+ *       consulted and then we favored the behavior we desire rather than the implied behavior of
+ *       the JLS or JDK implementation.
+ *   <li>The fact that any given type variable type may or may not have annotations for multiple
+ *       hierarchies means that constraints are more complicated than their Java equivalents. Every
+ *       constraint must identify the hierarchies to which they apply. This makes solving the
+ *       constraint sets more complicated.
+ *   <li>If an argument to a method is null, then the JLS says that it does not constrain the type
+ *       argument. However, null may constrain the qualifiers on the type argument, so it is
+ *       included in the constraints but is not used as the underlying type of the type argument.
+ * </ul>
+ *
+ * TODO: The following limitations need to be fixed, as at the time of this writing we do not have
+ * the time to handle them:
+ *
+ * <ul>
+ *   <li>The GlbUtil does not correctly handled wildcards/typevars when the glb result should be a
+ *       wildcard or typevar
+ *   <li>Interdependent Method Invocations -- Currently we do not correctly handle the case where
+ *       two methods need to have their arguments inferred and one is the argument to the other.
+ *       E.g.
+ *       <pre>{@code
+ * <T> T get()
+ * <S> void set(S s)
+ * set(get())
+ * }</pre>
+ *       Presumably, we want to detect these situations and combine the set of constraints with
+ *       {@code T <: S}.
+ * </ul>
+ */
+public class DefaultTypeArgumentInference implements TypeArgumentInference {
+  private final EqualitiesSolver equalitiesSolver = new EqualitiesSolver();
+  private final SupertypesSolver supertypesSolver = new SupertypesSolver();
+  private final SubtypesSolver subtypesSolver = new SubtypesSolver();
+  private final ConstraintMapBuilder constraintMapBuilder = new ConstraintMapBuilder();
+
+  private final boolean showInferenceSteps;
+
+  public DefaultTypeArgumentInference(AnnotatedTypeFactory typeFactory) {
+    this.showInferenceSteps = typeFactory.getChecker().hasOption("showInferenceSteps");
+  }
+
+  @Override
+  public Map<TypeVariable, AnnotatedTypeMirror> inferTypeArgs(
+      AnnotatedTypeFactory typeFactory,
+      ExpressionTree expressionTree,
+      ExecutableElement methodElem,
+      AnnotatedExecutableType methodType) {
+
+    final List<AnnotatedTypeMirror> argTypes =
+        TypeArgInferenceUtil.getArgumentTypes(expressionTree, typeFactory);
+    final TreePath pathToExpression = typeFactory.getPath(expressionTree);
+    assert pathToExpression != null;
+    AnnotatedTypeMirror assignedTo = TypeArgInferenceUtil.assignedTo(typeFactory, pathToExpression);
+
+    SourceChecker checker = typeFactory.getChecker();
+
+    if (showInferenceSteps) {
+      checker.message(
+          Kind.NOTE,
+          "DTAI: expression: %s%n  argTypes: %s%n  assignedTo: %s",
+          expressionTree.toString().replace(System.lineSeparator(), " "),
+          argTypes,
+          assignedTo);
+    }
+
+    final Set<TypeVariable> targets = TypeArgInferenceUtil.methodTypeToTargets(methodType);
+
+    if (TreePathUtil.enclosingNonParen(pathToExpression).first.getKind()
+            == Tree.Kind.LAMBDA_EXPRESSION
+        || (assignedTo == null && TreePathUtil.getAssignmentContext(pathToExpression) != null)) {
+      // If the type of the assignment context isn't found, but the expression is assigned,
+      // then don't attempt to infer type arguments, because the Java type inferred will be
+      // incorrect.  The assignment type is null when it includes uninferred type arguments.
+      // For example:
+      // <T> T outMethod()
+      // <U> void inMethod(U u);
+      // inMethod(outMethod())
+      // would require solving the constraints for both type argument inferences
+      // simultaneously
+      // Also, if the parent of the expression is a lambda, then the type arguments cannot be
+      // inferred.
+      Map<TypeVariable, AnnotatedTypeMirror> inferredArgs = new LinkedHashMap<>();
+      handleUninferredTypeVariables(typeFactory, methodType, targets, inferredArgs);
+      return inferredArgs;
+    }
+    if (assignedTo == null) {
+      assignedTo = typeFactory.getDummyAssignedTo(expressionTree);
+    }
+    Map<TypeVariable, AnnotatedTypeMirror> inferredArgs;
+    try {
+      inferredArgs =
+          infer(typeFactory, argTypes, assignedTo, methodElem, methodType, targets, true);
+      if (showInferenceSteps) {
+        checker.message(Kind.NOTE, "  after infer: %s", inferredArgs);
+      }
+      handleNullTypeArguments(
+          typeFactory, methodElem, methodType, argTypes, assignedTo, targets, inferredArgs);
+      if (showInferenceSteps) {
+        checker.message(Kind.NOTE, "  after handleNull: %s", inferredArgs);
+      }
+    } catch (Exception ex) {
+      // Catch any errors thrown by inference.
+      inferredArgs = new LinkedHashMap<>();
+      if (showInferenceSteps) {
+        checker.message(Kind.NOTE, "  exception: %s", ex.getLocalizedMessage());
+      }
+    }
+
+    handleUninferredTypeVariables(typeFactory, methodType, targets, inferredArgs);
+
+    if (showInferenceSteps) {
+      checker.message(Kind.NOTE, "  results: %s", inferredArgs);
+    }
+    try {
+      return TypeArgInferenceUtil.correctResults(
+          inferredArgs, expressionTree, methodType.getUnderlyingType(), typeFactory);
+    } catch (Throwable ex) {
+      // Ignore any exceptions
+      return inferredArgs;
+    }
+  }
+
+  /**
+   * If one of the inferredArgs are NullType, then re-run inference ignoring null method arguments.
+   * Then lub the result of the second inference with the NullType and put the new result back into
+   * inferredArgs.
+   *
+   * @param typeFactory type factory
+   * @param methodElem element of the method
+   * @param methodType annotated type of the method
+   * @param argTypes annotated types of arguments to the method
+   * @param assignedTo annotated type to which the result of the method invocation is assigned
+   * @param targets set of type variables to infer
+   * @param inferredArgs map of type variables to the annotated types of their type arguments
+   */
+  private void handleNullTypeArguments(
+      AnnotatedTypeFactory typeFactory,
+      ExecutableElement methodElem,
+      AnnotatedExecutableType methodType,
+      List<AnnotatedTypeMirror> argTypes,
+      AnnotatedTypeMirror assignedTo,
+      Set<TypeVariable> targets,
+      Map<TypeVariable, AnnotatedTypeMirror> inferredArgs) {
+    if (!hasNullType(inferredArgs)) {
+      return;
+    }
+    final Map<TypeVariable, AnnotatedTypeMirror> inferredArgsWithoutNull =
+        infer(typeFactory, argTypes, assignedTo, methodElem, methodType, targets, false);
+    for (AnnotatedTypeVariable atv : methodType.getTypeVariables()) {
+      TypeVariable typeVar = atv.getUnderlyingType();
+      AnnotatedTypeMirror result = inferredArgs.get(typeVar);
+      if (result == null) {
+        AnnotatedTypeMirror withoutNullResult = inferredArgsWithoutNull.get(typeVar);
+        if (withoutNullResult != null) {
+          inferredArgs.put(typeVar, withoutNullResult);
+        }
+      } else if (result.getKind() == TypeKind.NULL) {
+        AnnotatedTypeMirror withoutNullResult = inferredArgsWithoutNull.get(typeVar);
+        if (withoutNullResult == null) {
+          // withoutNullResult is null when the only constraint on a type argument is
+          // where a method argument is null.
+          withoutNullResult = atv.getUpperBound().deepCopy();
+        }
+        AnnotatedTypeMirror lub =
+            AnnotatedTypes.leastUpperBound(typeFactory, withoutNullResult, result);
+        inferredArgs.put(typeVar, lub);
+      }
+    }
+  }
+
+  private boolean hasNullType(Map<TypeVariable, AnnotatedTypeMirror> inferredArgs) {
+    for (AnnotatedTypeMirror atm : inferredArgs.values()) {
+      if (atm.getKind() == TypeKind.NULL) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * This algorithm works as follows:
+   *
+   * <ul>
+   *   <!-- ul rather than ol because of many cross-references within the text -->
+   *   <li>1. Build Argument Constraints -- create a set of constraints using the arguments to the
+   *       type parameter declarations, the formal parameters, and the arguments to the method call
+   *   <li>2. Solve Argument Constraints -- Create two solutions from the arguments.
+   *       <ol>
+   *         <li>Equality Arg Solution: Solution inferred from arguments used in an invariant
+   *             position (i.e. from equality constraints)
+   *         <li>Supertypes Arg Solution: Solution inferred from constraints in which the parameter
+   *             is a supertype of argument types. These are kept separate and merged later.
+   *       </ol>
+   *       Note: If there is NO assignment context we just combine the results from 2.a and 2.b,
+   *       giving preference to those in 2.a, and return the result.
+   *   <li>3. Build and Solve Initial Assignment Constraints -- Create a set of constraints from the
+   *       assignment context WITHOUT substituting either solution from step 2.
+   *   <li>4. Combine the solutions from steps 2.b and 3. This handles cases like the following:
+   *       <pre>{@code
+   * <T> List<T> method(T t1) {}
+   * List<@Nullable String> nl = method("");
+   * }</pre>
+   *       If we use just the arguments to infer T we will infer @NonNull String (since the lub of
+   *       all arguments would be @NonNull String). However, this would cause the assignment to
+   *       fail. Instead, since {@literal @NonNull String <: @Nullable String}, we can safely infer
+   *       T to be @Nullable String and both the argument types and the assignment types are
+   *       compatible. In step 4, we combine the results of Step 2.b (which came from lubbing
+   *       argument and argument component types) with the solution from equality constraints via
+   *       the assignment context.
+   *       <p>Note, we always give preference to the results inferred from method arguments if there
+   *       is a conflict between the steps 2 and 4. For example:
+   *       <pre>{@code
+   * <T> List<T> method(T t1) {}
+   * List<@NonNull String> nl = method(null);
+   * }</pre>
+   *       In the above example, the null argument requires that T must be @Nullable String. But the
+   *       assignment context requires that the T must be @NonNull String. But, in this case if we
+   *       use @NonNull String the argument "null" is invalid. In this case, we use @Nullable String
+   *       and report an assignment because we ALWAYS favor the arguments over the assignment
+   *       context.
+   *   <li>5. Combine the result from 2.a and step 4, if there is a conflict use the result from
+   *       step 2.a
+   *       <p>Suppose we have the following:
+   *       <pre>{@code
+   * <T> void method(List<@NonNull T> t, @Initialized Tt) { ... }
+   * List<@FBCBottom String> lBottom = ...;
+   * method( lbBottom, "nonNullString" );
+   * }</pre>
+   *       From the first argument we can infer that T must be exactly @FBCBottom String but we
+   *       cannot infer anything for the Nullness hierarchy. For the second argument we can infer
+   *       that T is at most @NonNull String but we can infer nothing in the initialization
+   *       hierarchy. In this step we combine these two results, always favoring the equality
+   *       constraints if there is a conflict. For the above example we would infer the following:
+   *       <pre>{@code
+   * T => @FBCBottom @NonNull String
+   * }</pre>
+   *       Another case covered in this step is:
+   *       <pre>{@code
+   * <T> List<T> method(List<T> t1) {}
+   * List<@NonNull String> nonNullList = new ArrayList<>();
+   * List<@Nullable String> nl = method(nonNullList);
+   * }</pre>
+   *       The above assignment should fail because T is forced to be both @NonNull and @Nullable.
+   *       In cases like these, we use @NonNull String becasue we always favor constraints from the
+   *       arguments over the assignment context.
+   *   <li>6. Infer from Assignment Context Finally, the JLS states that we should substitute the
+   *       types we have inferred up until this point back into the original argument constraints.
+   *       We should then combine the constraints we get from the assignment context and solve using
+   *       the greatest lower bounds of all of the constraints of the form: {@literal F :> U} (these
+   *       are referred to as "subtypes" in the ConstraintMap.TargetConstraints).
+   *   <li>7. Merge the result from steps 5 and 6 giving preference to 5 (the argument constraints).
+   *       Return the result.
+   * </ul>
+   */
+  private Map<TypeVariable, AnnotatedTypeMirror> infer(
+      final AnnotatedTypeFactory typeFactory,
+      final List<AnnotatedTypeMirror> argumentTypes,
+      final AnnotatedTypeMirror assignedTo,
+      final ExecutableElement methodElem,
+      final AnnotatedExecutableType methodType,
+      final Set<TypeVariable> targets,
+      final boolean useNullArguments) {
+
+    // 1.  Step 1 - Build up argument constraints
+    // The AFConstraints for arguments are used also in the
+    Set<AFConstraint> afArgumentConstraints =
+        createArgumentAFConstraints(
+            typeFactory, argumentTypes, methodType, targets, useNullArguments);
+
+    // 2. Step 2 - Solve the constraints.
+    Pair<InferenceResult, InferenceResult> argInference =
+        inferFromArguments(typeFactory, afArgumentConstraints, targets);
+
+    final InferenceResult fromArgEqualities = argInference.first; // result 2.a
+    final InferenceResult fromArgSubandSupers = argInference.second; // result 2.b
+
+    clampToLowerBound(fromArgSubandSupers, methodType.getTypeVariables(), typeFactory);
+
+    // if this method invocation's has a return type and it is assigned/pseudo-assigned to
+    // a variable, assignedTo is the type of that variable
+    if (assignedTo == null) {
+      fromArgEqualities.mergeSubordinate(fromArgSubandSupers);
+
+      return fromArgEqualities.toAtmMap();
+    } // else
+
+    final AnnotatedTypeMirror declaredReturnType = methodType.getReturnType();
+    final AnnotatedTypeMirror boxedReturnType;
+    if (declaredReturnType == null) {
+      boxedReturnType = null;
+    } else if (declaredReturnType.getKind().isPrimitive()) {
+      boxedReturnType = typeFactory.getBoxedType((AnnotatedPrimitiveType) declaredReturnType);
+    } else {
+      boxedReturnType = declaredReturnType;
+    }
+
+    final InferenceResult fromArguments = fromArgEqualities;
+    if (!((MethodSymbol) methodElem).isConstructor()) {
+      // Step 3 - Infer a solution from the equality constraints in the assignment context
+      InferenceResult fromAssignmentEqualities =
+          inferFromAssignmentEqualities(assignedTo, boxedReturnType, targets, typeFactory);
+
+      // Step 4 - Combine the results from 2.b and step 3
+      InferenceResult combinedSupertypesAndAssignment =
+          combineSupertypeAndAssignmentResults(
+              targets, typeFactory, fromAssignmentEqualities, fromArgSubandSupers);
+
+      // Step 5 - Combine the result from 2.a and step 4, if there is a conflict use the
+      // result from step 2.a
+      fromArgEqualities.mergeSubordinate(combinedSupertypesAndAssignment);
+
+      // if we don't have a result for all type arguments
+      // Step 6 - Infer the type arguments from the greatest-lower-bounds of all "subtype"
+      // constraints
+      if (!fromArguments.isComplete(targets)) {
+        InferenceResult fromAssignment =
+            inferFromAssignment(
+                assignedTo,
+                boxedReturnType,
+                methodType,
+                afArgumentConstraints,
+                fromArguments,
+                targets,
+                typeFactory);
+
+        // Step 7 - Merge the argument and the assignment constraints
+        fromArguments.mergeSubordinate(fromAssignment);
+      }
+
+    } else {
+
+      fromArguments.mergeSubordinate(fromArgSubandSupers);
+    }
+
+    return fromArguments.toAtmMap();
+  }
+
+  /**
+   * If we have inferred a type argument from the supertype constraints and this type argument is
+   * BELOW the lower bound, make it AT the lower bound.
+   *
+   * <p>e.g.
+   *
+   * <pre>{@code
+   * <@Initialized T extends @Initialized Object> void id(T t) { return t; }
+   * id(null);
+   *
+   * // The invocation of id will result in a type argument with primary annotations of @FBCBottom @Nullable
+   * // but this is below the lower bound of T in the initialization hierarchy so instead replace
+   * // @FBCBottom with @Initialized
+   *
+   * // This should happen ONLY with supertype constraints because raising the primary annotation would still
+   * // be valid for these constraints (since we just LUB the arguments involved) but would violate any
+   * // equality constraints
+   * }</pre>
+   *
+   * TODO: NOTE WE ONLY DO THIS FOR InferredType results for now but we should probably include
+   * targest as well
+   *
+   * @param fromArgSupertypes types inferred from LUBbing types from the arguments to the formal
+   *     parameters
+   * @param targetDeclarations the declared types of the type parameters whose arguments are being
+   *     inferred
+   */
+  private void clampToLowerBound(
+      InferenceResult fromArgSupertypes,
+      List<AnnotatedTypeVariable> targetDeclarations,
+      AnnotatedTypeFactory typeFactory) {
+    final QualifierHierarchy qualifierHierarchy = typeFactory.getQualifierHierarchy();
+    final AnnotationMirrorSet tops =
+        new AnnotationMirrorSet(qualifierHierarchy.getTopAnnotations());
+
+    for (AnnotatedTypeVariable targetDecl : targetDeclarations) {
+      InferredValue inferred = fromArgSupertypes.get(targetDecl.getUnderlyingType());
+      if (inferred instanceof InferredType) {
+        final AnnotatedTypeMirror lowerBoundAsArgument = targetDecl.getLowerBound();
+        for (AnnotationMirror top : tops) {
+          final AnnotationMirror lowerBoundAnno =
+              lowerBoundAsArgument.getEffectiveAnnotationInHierarchy(top);
+          final AnnotationMirror argAnno =
+              ((InferredType) inferred).type.getEffectiveAnnotationInHierarchy(top);
+          if (qualifierHierarchy.isSubtype(argAnno, lowerBoundAnno)) {
+            ((InferredType) inferred).type.replaceAnnotation(lowerBoundAnno);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Step 1: Create a constraint {@code Ai << Fi} for each Argument(Ai) to formal parameter(Fi).
+   * Remove any constraint that does not involve a type parameter to be inferred. Reduce the
+   * remaining constraints so that Fi = Tj where Tj is a type parameter with an argument to be
+   * inferred. Return the resulting constraint set.
+   *
+   * @param typeFactory AnnotatedTypeFactory
+   * @param argTypes list of annotated types corresponding to the arguments to the method
+   * @param methodType annotated type of the method
+   * @param targets type variables to be inferred
+   * @param useNullArguments whether or not null method arguments should be considered
+   * @return a set of argument constraints
+   */
+  protected Set<AFConstraint> createArgumentAFConstraints(
+      final AnnotatedTypeFactory typeFactory,
+      final List<AnnotatedTypeMirror> argTypes,
+      final AnnotatedExecutableType methodType,
+      final Set<TypeVariable> targets,
+      boolean useNullArguments) {
+    final List<AnnotatedTypeMirror> paramTypes =
+        AnnotatedTypes.expandVarArgsFromTypes(methodType, argTypes);
+
+    if (argTypes.size() != paramTypes.size()) {
+      throw new BugInCF(
+          StringsPlume.joinLines(
+              "Mismatch between formal parameter count and argument count.",
+              "paramTypes=" + StringsPlume.join(",", paramTypes),
+              "argTypes=" + StringsPlume.join(",", argTypes)));
+    }
+
+    final int numberOfParams = paramTypes.size();
+    final ArrayDeque<AFConstraint> afConstraints = new ArrayDeque<>(numberOfParams);
+    for (int i = 0; i < numberOfParams; i++) {
+      if (!useNullArguments && argTypes.get(i).getKind() == TypeKind.NULL) {
+        continue;
+      }
+      afConstraints.add(new A2F(argTypes.get(i), paramTypes.get(i)));
+    }
+
+    final Set<AFConstraint> reducedConstraints = new LinkedHashSet<>();
+
+    reduceAfConstraints(typeFactory, reducedConstraints, afConstraints, targets);
+    return reducedConstraints;
+  }
+
+  /**
+   * Step 2. Infer type arguments from the equality (TisU) and the supertype (TSuperU) constraints
+   * of the methods arguments.
+   */
+  private Pair<InferenceResult, InferenceResult> inferFromArguments(
+      final AnnotatedTypeFactory typeFactory,
+      final Set<AFConstraint> afArgumentConstraints,
+      final Set<TypeVariable> targets) {
+    Set<TUConstraint> tuArgConstraints = afToTuConstraints(afArgumentConstraints, targets);
+    addConstraintsBetweenTargets(tuArgConstraints, targets, false, typeFactory);
+
+    ConstraintMap argConstraints =
+        constraintMapBuilder.build(targets, tuArgConstraints, typeFactory);
+
+    InferenceResult inferredFromArgEqualities =
+        equalitiesSolver.solveEqualities(targets, argConstraints, typeFactory);
+
+    Set<TypeVariable> remainingTargets =
+        inferredFromArgEqualities.getRemainingTargets(targets, true);
+    InferenceResult fromSupertypes =
+        supertypesSolver.solveFromSupertypes(remainingTargets, argConstraints, typeFactory);
+
+    InferenceResult fromSubtypes =
+        subtypesSolver.solveFromSubtypes(remainingTargets, argConstraints, typeFactory);
+    fromSupertypes.mergeSubordinate(fromSubtypes);
+
+    return Pair.of(inferredFromArgEqualities, fromSupertypes);
+  }
+
+  /** Step 3. Infer type arguments from the equality constraints of the assignment context. */
+  private InferenceResult inferFromAssignmentEqualities(
+      final AnnotatedTypeMirror assignedTo,
+      final AnnotatedTypeMirror boxedReturnType,
+      final Set<TypeVariable> targets,
+      final AnnotatedTypeFactory typeFactory) {
+    Set<FIsA> afInitialAssignmentConstraints =
+        createInitialAssignmentConstraints(assignedTo, boxedReturnType, typeFactory, targets);
+
+    Set<TUConstraint> tuInitialAssignmentConstraints =
+        afToTuConstraints(afInitialAssignmentConstraints, targets);
+    ConstraintMap initialAssignmentConstraints =
+        constraintMapBuilder.build(targets, tuInitialAssignmentConstraints, typeFactory);
+    return equalitiesSolver.solveEqualities(targets, initialAssignmentConstraints, typeFactory);
+  }
+
+  /**
+   * Create a set of constraints between return type and any type to which it is assigned. Reduce
+   * these set of constraints and remove any that is not an equality (FIsA) constraint.
+   */
+  protected Set<FIsA> createInitialAssignmentConstraints(
+      final AnnotatedTypeMirror assignedTo,
+      final AnnotatedTypeMirror boxedReturnType,
+      final AnnotatedTypeFactory typeFactory,
+      final Set<TypeVariable> targets) {
+    final Set<FIsA> result = new LinkedHashSet<>();
+
+    if (assignedTo != null) {
+      final Set<AFConstraint> reducedConstraints = new LinkedHashSet<>();
+
+      final Queue<AFConstraint> constraints = new ArrayDeque<>();
+      constraints.add(new F2A(boxedReturnType, assignedTo));
+
+      reduceAfConstraints(typeFactory, reducedConstraints, constraints, targets);
+
+      for (final AFConstraint reducedConstraint : reducedConstraints) {
+        if (reducedConstraint instanceof FIsA) {
+          result.add((FIsA) reducedConstraint);
+        }
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * The first half of Step 6.
+   *
+   * <p>This method creates constraints:
+   *
+   * <ul>
+   *   <li>between the bounds of types that are already inferred and their inferred arguments
+   *   <li>between the assignment context and the return type of the method (with the previously
+   *       inferred arguments substituted into these constraints)
+   * </ul>
+   */
+  public ConstraintMap createAssignmentConstraints(
+      final AnnotatedTypeMirror assignedTo,
+      final AnnotatedTypeMirror boxedReturnType,
+      final AnnotatedExecutableType methodType,
+      final Set<AFConstraint> afArgumentConstraints,
+      final Map<TypeVariable, AnnotatedTypeMirror> inferredArgs,
+      final Set<TypeVariable> targets,
+      final AnnotatedTypeFactory typeFactory) {
+
+    final ArrayDeque<AFConstraint> assignmentAfs =
+        new ArrayDeque<>(2 * methodType.getTypeVariables().size() + afArgumentConstraints.size());
+    for (AnnotatedTypeVariable typeParam : methodType.getTypeVariables()) {
+      final TypeVariable target = typeParam.getUnderlyingType();
+      final AnnotatedTypeMirror inferredType = inferredArgs.get(target);
+      // for all inferred types Ti:  Ti >> Bi where Bi is upper bound and Ti << Li where Li is
+      // the lower bound for all uninferred types Tu: Tu >> Bi and Lu >> Tu
+      if (inferredType != null) {
+        assignmentAfs.add(new A2F(inferredType, typeParam.getUpperBound()));
+        assignmentAfs.add(new F2A(typeParam.getLowerBound(), inferredType));
+      } else {
+        assignmentAfs.add(new F2A(typeParam, typeParam.getUpperBound()));
+        assignmentAfs.add(new A2F(typeParam.getLowerBound(), typeParam));
+      }
+    }
+
+    for (AFConstraint argConstraint : afArgumentConstraints) {
+      if (argConstraint instanceof F2A) {
+        assignmentAfs.add(argConstraint);
+      }
+    }
+
+    ArrayDeque<AFConstraint> substitutedAssignmentConstraints =
+        new ArrayDeque<>(assignmentAfs.size() + 1);
+    for (AFConstraint afConstraint : assignmentAfs) {
+      substitutedAssignmentConstraints.add(afConstraint.substitute(inferredArgs));
+    }
+
+    final AnnotatedTypeMirror substitutedReturnType =
+        TypeArgInferenceUtil.substitute(inferredArgs, boxedReturnType);
+    substitutedAssignmentConstraints.add(new F2A(substitutedReturnType, assignedTo));
+
+    final Set<AFConstraint> reducedConstraints = new LinkedHashSet<>();
+    reduceAfConstraints(typeFactory, reducedConstraints, substitutedAssignmentConstraints, targets);
+    final Set<TUConstraint> tuAssignmentConstraints =
+        afToTuConstraints(reducedConstraints, targets);
+    addConstraintsBetweenTargets(tuAssignmentConstraints, targets, true, typeFactory);
+    return constraintMapBuilder.build(targets, tuAssignmentConstraints, typeFactory);
+  }
+
+  /** The Second half of step 6. Use the assignment context to infer a result. */
+  private InferenceResult inferFromAssignment(
+      final AnnotatedTypeMirror assignedTo,
+      final AnnotatedTypeMirror boxedReturnType,
+      final AnnotatedExecutableType methodType,
+      final Set<AFConstraint> afArgumentConstraints,
+      final InferenceResult inferredArgs,
+      final Set<TypeVariable> targets,
+      final AnnotatedTypeFactory typeFactory) {
+    ConstraintMap assignmentConstraints =
+        createAssignmentConstraints(
+            assignedTo,
+            boxedReturnType,
+            methodType,
+            afArgumentConstraints,
+            inferredArgs.toAtmMap(),
+            targets,
+            typeFactory);
+
+    InferenceResult equalitiesResult =
+        equalitiesSolver.solveEqualities(targets, assignmentConstraints, typeFactory);
+
+    Set<TypeVariable> remainingTargets = equalitiesResult.getRemainingTargets(targets, true);
+    InferenceResult subtypesResult =
+        subtypesSolver.solveFromSubtypes(remainingTargets, assignmentConstraints, typeFactory);
+
+    equalitiesResult.mergeSubordinate(subtypesResult);
+    return equalitiesResult;
+  }
+
+  /**
+   * Step 4. Combine the results from using the Supertype constraints the Equality constraints from
+   * the assignment context.
+   */
+  private InferenceResult combineSupertypeAndAssignmentResults(
+      Set<TypeVariable> targets,
+      AnnotatedTypeFactory typeFactory,
+      InferenceResult equalityResult,
+      InferenceResult supertypeResult) {
+    final TypeHierarchy typeHierarchy = typeFactory.getTypeHierarchy();
+
+    final InferenceResult result = new InferenceResult();
+    for (final TypeVariable target : targets) {
+      final InferredValue equalityInferred = equalityResult.get(target);
+      final InferredValue supertypeInferred = supertypeResult.get(target);
+
+      final InferredValue outputValue;
+      if (equalityInferred instanceof InferredType) {
+
+        if (supertypeInferred instanceof InferredType) {
+          AnnotatedTypeMirror superATM = ((InferredType) supertypeInferred).type;
+          AnnotatedTypeMirror equalityATM = ((InferredType) equalityInferred).type;
+          if (TypesUtils.isErasedSubtype(
+              equalityATM.getUnderlyingType(),
+              superATM.getUnderlyingType(),
+              typeFactory.getChecker().getTypeUtils())) {
+            // If the underlying type of equalityATM is a subtype of the underlying
+            // type of superATM, then the call to isSubtype below will issue an error.
+            // So call asSuper so that the isSubtype call below works correctly.
+            equalityATM = AnnotatedTypes.asSuper(typeFactory, equalityATM, superATM);
+          }
+          if (typeHierarchy.isSubtype(superATM, equalityATM)) {
+            outputValue = equalityInferred;
+          } else {
+            outputValue = supertypeInferred;
+          }
+
+        } else {
+          outputValue = equalityInferred;
+        }
+      } else {
+        if (supertypeInferred != null) {
+          outputValue = supertypeInferred;
+        } else {
+          outputValue = null;
+        }
+      }
+
+      if (outputValue != null) {
+        result.put(target, outputValue);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * For any types we have not inferred, use a wildcard with the bounds from the original type
+   * parameter.
+   */
+  private void handleUninferredTypeVariables(
+      AnnotatedTypeFactory typeFactory,
+      AnnotatedExecutableType methodType,
+      Set<TypeVariable> targets,
+      Map<TypeVariable, AnnotatedTypeMirror> inferredArgs) {
+
+    for (AnnotatedTypeVariable atv : methodType.getTypeVariables()) {
+      final TypeVariable typeVar = atv.getUnderlyingType();
+      if (targets.contains((TypeVariable) TypeAnnotationUtils.unannotatedType(typeVar))) {
+        final AnnotatedTypeMirror inferredType = inferredArgs.get(typeVar);
+        if (inferredType == null) {
+          AnnotatedTypeMirror dummy = typeFactory.getUninferredWildcardType(atv);
+          inferredArgs.put(atv.getUnderlyingType(), dummy);
+        } else {
+          typeFactory.addDefaultAnnotations(inferredType);
+        }
+      }
+    }
+  }
+
+  /**
+   * Given a set of AFConstraints, remove all constraints that are not relevant to inference and
+   * return a set of AFConstraints in which the F is a use of one of the type parameters to infer.
+   */
+  protected void reduceAfConstraints(
+      final AnnotatedTypeFactory typeFactory,
+      final Set<AFConstraint> outgoing,
+      final Queue<AFConstraint> toProcess,
+      final Set<TypeVariable> targets) {
+
+    final Set<AFConstraint> visited = new HashSet<>();
+
+    List<AFReducer> reducers =
+        Arrays.asList(
+            new A2FReducer(typeFactory), new F2AReducer(typeFactory), new FIsAReducer(typeFactory));
+
+    Set<AFConstraint> newConstraints = new HashSet<>(10);
+    while (!toProcess.isEmpty()) {
+      newConstraints.clear();
+      AFConstraint constraint = toProcess.remove();
+
+      if (!visited.contains(constraint)) {
+        if (constraint.isIrreducible(targets)) {
+          outgoing.add(constraint);
+        } else {
+
+          final Iterator<AFReducer> reducerIterator = reducers.iterator();
+          boolean handled = false;
+          while (!handled && reducerIterator.hasNext()) {
+            handled = reducerIterator.next().reduce(constraint, newConstraints);
+          }
+
+          if (!handled) {
+            throw new BugInCF("Unhandled constraint type: " + constraint);
+          }
+
+          toProcess.addAll(newConstraints);
+        }
+        visited.add(constraint);
+      }
+    }
+  }
+
+  /** Convert AFConstraints to TUConstraints. */
+  protected Set<TUConstraint> afToTuConstraints(
+      Set<? extends AFConstraint> afConstraints, Set<TypeVariable> targets) {
+    final Set<TUConstraint> outgoing = new LinkedHashSet<>();
+    for (final AFConstraint afConstraint : afConstraints) {
+      if (!afConstraint.isIrreducible(targets)) {
+        throw new BugInCF(
+            StringsPlume.joinLines(
+                "All afConstraints should be irreducible before conversion.",
+                "afConstraints=[ " + StringsPlume.join(", ", afConstraints) + " ]",
+                "targets=[ " + StringsPlume.join(", ", targets) + "]"));
+      }
+
+      outgoing.add(afConstraint.toTUConstraint());
+    }
+
+    return outgoing;
+  }
+
+  /**
+   * Declarations of the form: {@code <A, B extends A>} implies a TUConstraint of {@code B <: A}.
+   * Add these to the constraint list.
+   */
+  public void addConstraintsBetweenTargets(
+      Set<TUConstraint> constraints,
+      Set<TypeVariable> targets,
+      boolean asSubtype,
+      AnnotatedTypeFactory typeFactory) {
+    final Types types = typeFactory.getProcessingEnv().getTypeUtils();
+    final List<TypeVariable> targetList = new ArrayList<>(targets);
+
+    final Map<TypeVariable, AnnotatedTypeVariable> paramDeclarations = new HashMap<>();
+
+    for (int i = 0; i < targetList.size(); i++) {
+      final TypeVariable earlierTarget = targetList.get(i);
+
+      for (int j = i + 1; j < targetList.size(); j++) {
+        final TypeVariable laterTarget = targetList.get(j);
+        if (types.isSameType(earlierTarget.getUpperBound(), laterTarget)) {
+          final AnnotatedTypeVariable headDecl =
+              addOrGetDeclarations(earlierTarget, typeFactory, paramDeclarations);
+          final AnnotatedTypeVariable nextDecl =
+              addOrGetDeclarations(laterTarget, typeFactory, paramDeclarations);
+
+          if (asSubtype) {
+            constraints.add(new TSubU(headDecl, nextDecl));
+
+          } else {
+            constraints.add(new TSuperU(nextDecl, headDecl));
+          }
+        } else if (types.isSameType(laterTarget.getUpperBound(), earlierTarget)) {
+          final AnnotatedTypeVariable headDecl =
+              addOrGetDeclarations(earlierTarget, typeFactory, paramDeclarations);
+          final AnnotatedTypeVariable nextDecl =
+              addOrGetDeclarations(laterTarget, typeFactory, paramDeclarations);
+
+          if (asSubtype) {
+            constraints.add(new TSubU(nextDecl, headDecl));
+
+          } else {
+            constraints.add(new TSuperU(headDecl, nextDecl));
+          }
+        }
+      }
+    }
+  }
+
+  public AnnotatedTypeVariable addOrGetDeclarations(
+      TypeVariable target,
+      AnnotatedTypeFactory typeFactory,
+      Map<TypeVariable, AnnotatedTypeVariable> declarations) {
+    AnnotatedTypeVariable atv =
+        declarations.computeIfAbsent(
+            target, __ -> (AnnotatedTypeVariable) typeFactory.getAnnotatedType(target.asElement()));
+    return atv;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/GlbUtil.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/GlbUtil.java
new file mode 100644
index 0000000..8f33aaf
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/GlbUtil.java
@@ -0,0 +1,175 @@
+package org.checkerframework.framework.util.typeinference;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.TypeHierarchy;
+import org.checkerframework.framework.util.AnnotationMirrorMap;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TypesUtils;
+
+/** A class used to determine the greatest lower bounds for a set of AnnotatedTypeMirrors. */
+public class GlbUtil {
+
+  /**
+   * Note: This method can be improved for wildcards and type variables.
+   *
+   * @return the greatest lower bound of typeMirrors. If any of the type mirrors are incomparable,
+   *     use an AnnotatedNullType that will contain the greatest lower bounds of the primary
+   *     annotations of typeMirrors.
+   */
+  public static AnnotatedTypeMirror glbAll(
+      final Map<AnnotatedTypeMirror, AnnotationMirrorSet> typeMirrors,
+      final AnnotatedTypeFactory typeFactory) {
+    final QualifierHierarchy qualifierHierarchy = typeFactory.getQualifierHierarchy();
+    if (typeMirrors.isEmpty()) {
+      return null;
+    }
+
+    // dtermine the greatest lower bounds for the primary annotations
+    AnnotationMirrorMap<AnnotationMirror> glbPrimaries = new AnnotationMirrorMap<>();
+    for (Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> tmEntry : typeMirrors.entrySet()) {
+      final AnnotationMirrorSet typeAnnoHierarchies = tmEntry.getValue();
+      final AnnotatedTypeMirror type = tmEntry.getKey();
+
+      for (AnnotationMirror top : typeAnnoHierarchies) {
+        // TODO: When all of the typeMirrors are either wildcards or type variables than the
+        // greatest lower bound should involve handling the bounds individually rather than
+        // using the effective annotation.  We are doing this for expediency.
+        final AnnotationMirror typeAnno = type.getEffectiveAnnotationInHierarchy(top);
+        final AnnotationMirror currentAnno = glbPrimaries.get(top);
+        if (typeAnno != null && currentAnno != null) {
+          glbPrimaries.put(top, qualifierHierarchy.greatestLowerBound(currentAnno, typeAnno));
+        } else if (typeAnno != null) {
+          glbPrimaries.put(top, typeAnno);
+        }
+      }
+    }
+
+    final List<AnnotatedTypeMirror> glbTypes = new ArrayList<>();
+
+    // create a copy of all of the types and apply the glb primary annotation
+    final AnnotationMirrorSet values = new AnnotationMirrorSet(glbPrimaries.values());
+    for (AnnotatedTypeMirror type : typeMirrors.keySet()) {
+      if (type.getKind() != TypeKind.TYPEVAR
+          || !qualifierHierarchy.isSubtype(type.getEffectiveAnnotations(), values)) {
+        final AnnotatedTypeMirror copy = type.deepCopy();
+        copy.replaceAnnotations(values);
+        glbTypes.add(copy);
+
+      } else {
+        // if the annotations came from the upper bound of this typevar
+        // we do NOT want to place them as primary annotations (and destroy the
+        // type vars lower bound)
+        glbTypes.add(type);
+      }
+    }
+
+    final TypeHierarchy typeHierarchy = typeFactory.getTypeHierarchy();
+
+    // sort placing supertypes first
+    sortForGlb(glbTypes, typeFactory);
+
+    // find the lowest type in the list that is not an AnnotatedNullType
+    AnnotatedTypeMirror glbType = glbTypes.get(0);
+    int index = 1;
+    while (index < glbTypes.size()) {
+      // avoid using null if possible, since constraints form the lower bound will often have
+      // NULL types
+      if (glbType.getKind() != TypeKind.NULL) {
+        glbType = glbTypes.get(index);
+      }
+      index += 1;
+    }
+
+    // if the lowest type is a subtype of all glbTypes then it is the GLB, otherwise there are two
+    // types in glbTypes that are incomparable and we need to use bottom (AnnotatedNullType)
+    boolean incomparable = false;
+    for (final AnnotatedTypeMirror type : glbTypes) {
+      if (!incomparable
+          && type.getKind() != TypeKind.NULL
+          && (!TypesUtils.isErasedSubtype(
+                  glbType.getUnderlyingType(),
+                  type.getUnderlyingType(),
+                  typeFactory.getChecker().getTypeUtils())
+              || !typeHierarchy.isSubtype(glbType, type))) {
+        incomparable = true;
+      }
+    }
+
+    // we had two incomparable types in glbTypes
+    if (incomparable) {
+      return createBottom(typeFactory, glbType.getEffectiveAnnotations());
+    }
+
+    return glbType;
+  }
+
+  /** Returns an AnnotatedNullType with the given annotations as primaries. */
+  private static AnnotatedNullType createBottom(
+      final AnnotatedTypeFactory typeFactory, final Set<? extends AnnotationMirror> annos) {
+    return typeFactory.getAnnotatedNullType(annos);
+  }
+
+  /**
+   * Sort the list of type mirrors, placing supertypes first and subtypes last.
+   *
+   * <p>E.g. the list: {@code ArrayList<String>, List<String>, AbstractList<String>} becomes: {@code
+   * List<String>, AbstractList<String>, ArrayList<String>}
+   */
+  public static void sortForGlb(
+      final List<? extends AnnotatedTypeMirror> typeMirrors,
+      final AnnotatedTypeFactory typeFactory) {
+    final QualifierHierarchy qualifierHierarchy = typeFactory.getQualifierHierarchy();
+    final Types types = typeFactory.getProcessingEnv().getTypeUtils();
+
+    Collections.sort(
+        typeMirrors,
+        new Comparator<AnnotatedTypeMirror>() {
+          @Override
+          public int compare(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) {
+            final TypeMirror underlyingType1 = type1.getUnderlyingType();
+            final TypeMirror underlyingType2 = type2.getUnderlyingType();
+
+            if (types.isSameType(underlyingType1, underlyingType2)) {
+              return compareAnnotations(qualifierHierarchy, type1, type2);
+            }
+
+            if (types.isSubtype(underlyingType1, underlyingType2)) {
+              return 1;
+            }
+
+            // if they're incomparable or type2 is a subtype of type1
+            return -1;
+          }
+
+          private int compareAnnotations(
+              final QualifierHierarchy qualHierarchy,
+              final AnnotatedTypeMirror type1,
+              final AnnotatedTypeMirror type2) {
+            if (AnnotationUtils.areSame(type1.getAnnotations(), type2.getAnnotations())) {
+              return 0;
+            }
+
+            if (qualHierarchy.isSubtype(type1.getAnnotations(), type2.getAnnotations())) {
+              return 1;
+
+            } else {
+              return -1;
+            }
+          }
+        });
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java
new file mode 100644
index 0000000..8f4a3bc
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java
@@ -0,0 +1,710 @@
+package org.checkerframework.framework.util.typeinference;
+
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.LambdaExpressionTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.NoType;
+import javax.lang.model.type.NullType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.TypeVisitor;
+import javax.lang.model.type.UnionType;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.TypeVariableSubstitutor;
+import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.AnnotationMirrorMap;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreePathUtil;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypeAnnotationUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+
+/** Miscellaneous utilities to help in type argument inference. */
+public class TypeArgInferenceUtil {
+
+  /**
+   * Returns a list of boxed annotated types corresponding to the arguments in {@code
+   * methodInvocation}.
+   *
+   * @param methodInvocation {@link MethodInvocationTree} or {@link NewClassTree}
+   * @param typeFactory type factory
+   * @return a list of boxed annotated types corresponding to the arguments in {@code
+   *     methodInvocation}.
+   */
+  public static List<AnnotatedTypeMirror> getArgumentTypes(
+      final ExpressionTree methodInvocation, final AnnotatedTypeFactory typeFactory) {
+    final List<? extends ExpressionTree> argTrees;
+
+    if (methodInvocation.getKind() == Kind.METHOD_INVOCATION) {
+      argTrees = ((MethodInvocationTree) methodInvocation).getArguments();
+
+    } else if (methodInvocation.getKind() == Kind.NEW_CLASS) {
+      argTrees = ((NewClassTree) methodInvocation).getArguments();
+
+    } else {
+      throw new BugInCF(
+          "TypeArgumentInference.relationsFromMethodArguments:%n"
+              + "couldn't determine arguments from tree: %s",
+          methodInvocation);
+    }
+
+    List<AnnotatedTypeMirror> argTypes =
+        CollectionsPlume.mapList(
+            (Tree arg) -> {
+              AnnotatedTypeMirror argType = typeFactory.getAnnotatedType(arg);
+              if (TypesUtils.isPrimitive(argType.getUnderlyingType())) {
+                return typeFactory.getBoxedType((AnnotatedPrimitiveType) argType);
+              } else {
+                return argType;
+              }
+            },
+            argTrees);
+    return argTypes;
+  }
+
+  /**
+   * Given a set of type variables for which we are inferring a type, returns true if type is a use
+   * of a type variable in the list of targetTypeVars.
+   */
+  public static boolean isATarget(
+      final AnnotatedTypeMirror type, final Set<TypeVariable> targetTypeVars) {
+    return type.getKind() == TypeKind.TYPEVAR
+        && targetTypeVars.contains(
+            (TypeVariable) TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()));
+  }
+
+  /**
+   * Given an AnnotatedExecutableType return a set of type variables that represents the generic
+   * type parameters of that method.
+   */
+  public static Set<TypeVariable> methodTypeToTargets(final AnnotatedExecutableType methodType) {
+    final List<AnnotatedTypeVariable> annotatedTypeVars = methodType.getTypeVariables();
+    final Set<TypeVariable> targets = new LinkedHashSet<>(annotatedTypeVars.size());
+
+    for (final AnnotatedTypeVariable atv : annotatedTypeVars) {
+      targets.add((TypeVariable) TypeAnnotationUtils.unannotatedType(atv.getUnderlyingType()));
+    }
+
+    return targets;
+  }
+
+  /**
+   * Returns the annotated type that the leaf of path is assigned to, if it is within an assignment
+   * context. Returns the annotated type that the method invocation at the leaf is assigned to. If
+   * the result is a primitive, return the boxed version.
+   *
+   * @param atypeFactory the type factory, for looking up types
+   * @param path the path whole leaf to look up a type for
+   * @return the type of path's leaf
+   */
+  @SuppressWarnings("interning:not.interned") // AST node comparisons
+  public static AnnotatedTypeMirror assignedTo(AnnotatedTypeFactory atypeFactory, TreePath path) {
+    Tree assignmentContext = TreePathUtil.getAssignmentContext(path);
+    AnnotatedTypeMirror res;
+    if (assignmentContext == null) {
+      res = null;
+    } else if (assignmentContext instanceof AssignmentTree) {
+      ExpressionTree variable = ((AssignmentTree) assignmentContext).getVariable();
+      res = atypeFactory.getAnnotatedType(variable);
+    } else if (assignmentContext instanceof CompoundAssignmentTree) {
+      ExpressionTree variable = ((CompoundAssignmentTree) assignmentContext).getVariable();
+      res = atypeFactory.getAnnotatedType(variable);
+    } else if (assignmentContext instanceof MethodInvocationTree) {
+      MethodInvocationTree methodInvocation = (MethodInvocationTree) assignmentContext;
+      // TODO move to getAssignmentContext
+      if (methodInvocation.getMethodSelect() instanceof MemberSelectTree
+          && ((MemberSelectTree) methodInvocation.getMethodSelect()).getExpression()
+              == path.getLeaf()) {
+        return null;
+      }
+      ExecutableElement methodElt = TreeUtils.elementFromUse(methodInvocation);
+      AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(methodInvocation);
+      res =
+          assignedToExecutable(
+              atypeFactory, path, methodElt, receiver, methodInvocation.getArguments());
+    } else if (assignmentContext instanceof NewArrayTree) {
+      // TODO: I left the previous implementation below, it definitely caused infinite loops
+      // TODO: if you called it from places like the TreeAnnotator.
+      res = null;
+
+      // TODO: This may cause infinite loop
+      //            AnnotatedTypeMirror type =
+      //                    atypeFactory.getAnnotatedType((NewArrayTree)assignmentContext);
+      //            type = AnnotatedTypes.innerMostType(type);
+      //            return type;
+
+    } else if (assignmentContext instanceof NewClassTree) {
+      // This need to be basically like MethodTree
+      NewClassTree newClassTree = (NewClassTree) assignmentContext;
+      if (newClassTree.getEnclosingExpression() instanceof NewClassTree
+          && (newClassTree.getEnclosingExpression() == path.getLeaf())) {
+        return null;
+      }
+      ExecutableElement constructorElt = TreeUtils.constructor(newClassTree);
+      AnnotatedTypeMirror receiver = atypeFactory.fromNewClass(newClassTree);
+      res =
+          assignedToExecutable(
+              atypeFactory, path, constructorElt, receiver, newClassTree.getArguments());
+    } else if (assignmentContext instanceof ReturnTree) {
+      HashSet<Kind> kinds = new HashSet<>(Arrays.asList(Kind.LAMBDA_EXPRESSION, Kind.METHOD));
+      Tree enclosing = TreePathUtil.enclosingOfKind(path, kinds);
+
+      if (enclosing.getKind() == Kind.METHOD) {
+        res = atypeFactory.getAnnotatedType((MethodTree) enclosing).getReturnType();
+      } else {
+        AnnotatedExecutableType fninf =
+            atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing);
+        res = fninf.getReturnType();
+      }
+
+    } else if (assignmentContext instanceof VariableTree) {
+      res = assignedToVariable(atypeFactory, assignmentContext);
+    } else {
+      throw new BugInCF("AnnotatedTypes.assignedTo: shouldn't be here");
+    }
+
+    if (res != null && TypesUtils.isPrimitive(res.getUnderlyingType())) {
+      return atypeFactory.getBoxedType((AnnotatedPrimitiveType) res);
+    } else {
+      return res;
+    }
+  }
+
+  private static AnnotatedTypeMirror assignedToExecutable(
+      AnnotatedTypeFactory atypeFactory,
+      TreePath path,
+      ExecutableElement methodElt,
+      AnnotatedTypeMirror receiver,
+      List<? extends ExpressionTree> arguments) {
+    AnnotatedExecutableType method =
+        AnnotatedTypes.asMemberOf(
+            atypeFactory.getChecker().getTypeUtils(), atypeFactory, receiver, methodElt);
+    int treeIndex = -1;
+    for (int i = 0; i < arguments.size(); ++i) {
+      ExpressionTree argumentTree = arguments.get(i);
+      if (isArgument(path, argumentTree)) {
+        treeIndex = i;
+        break;
+      }
+    }
+    final AnnotatedTypeMirror paramType;
+    if (treeIndex == -1) {
+      // The tree wasn't found as an argument, so it has to be the receiver.
+      // This can happen for inner class constructors that take an outer class argument.
+      paramType = method.getReceiverType();
+    } else if (treeIndex + 1 >= method.getParameterTypes().size() && methodElt.isVarArgs()) {
+      AnnotatedTypeMirror varArgsType =
+          method.getParameterTypes().get(method.getParameterTypes().size() - 1);
+      paramType = ((AnnotatedArrayType) varArgsType).getComponentType();
+    } else {
+      paramType = method.getParameterTypes().get(treeIndex);
+    }
+
+    // Examples like this:
+    // <T> T outMethod()
+    // <U> void inMethod(U u);
+    // inMethod(outMethod())
+    // would require solving the constraints for both type argument inferences simultaneously
+    if (paramType == null || containsUninferredTypeParameter(paramType, method)) {
+      return null;
+    }
+
+    return paramType;
+  }
+
+  /**
+   * Returns whether argumentTree is the tree at the leaf of path. If tree is a conditional
+   * expression, isArgument is called recursively on the true and false expressions.
+   *
+   * @param path the path whose leaf to test
+   * @param argumentTree the expression that might be path's leaf
+   * @return true if {@code argumentTree} is the leaf of {@code path}
+   */
+  private static boolean isArgument(TreePath path, @FindDistinct ExpressionTree argumentTree) {
+    argumentTree = TreeUtils.withoutParens(argumentTree);
+    if (argumentTree == path.getLeaf()) {
+      return true;
+    } else if (argumentTree.getKind() == Kind.CONDITIONAL_EXPRESSION) {
+      ConditionalExpressionTree conditionalExpressionTree =
+          (ConditionalExpressionTree) argumentTree;
+      return isArgument(path, conditionalExpressionTree.getTrueExpression())
+          || isArgument(path, conditionalExpressionTree.getFalseExpression());
+    }
+    return false;
+  }
+
+  /**
+   * If the variable's type is a type variable, return getAnnotatedTypeLhsNoTypeVarDefault(tree).
+   * Rational:
+   *
+   * <p>For example:
+   *
+   * <pre>{@code
+   * <S> S bar () {...}
+   *
+   * <T> T foo(T p) {
+   *     T local = bar();
+   *     return local;
+   *   }
+   * }</pre>
+   *
+   * During type argument inference of {@code bar}, the assignment context is {@code local}. If the
+   * local variable default is used, then the type of assignment context type is {@code @Nullable T}
+   * and the type argument inferred for {@code bar()} is {@code @Nullable T}. And an incompatible
+   * types in return error is issued.
+   *
+   * <p>If instead, the local variable default is not applied, then the assignment context type is
+   * {@code T} (with lower bound {@code @NonNull Void} and upper bound {@code @Nullable Object}) and
+   * the type argument inferred for {@code bar()} is {@code T}. During dataflow, the type of {@code
+   * local} is refined to {@code T} and the return is legal.
+   *
+   * <p>If the assignment context type was a declared type, for example:
+   *
+   * <pre>{@code
+   * <S> S bar () {...}
+   * Object foo() {
+   *     Object local = bar();
+   *     return local;
+   * }
+   * }</pre>
+   *
+   * The local variable default must be used or else the assignment context type is missing an
+   * annotation. So, an incompatible types in return error is issued in the above code. We could
+   * improve type argument inference in this case and by using the lower bound of {@code S} instead
+   * of the local variable default.
+   *
+   * @param atypeFactory AnnotatedTypeFactory
+   * @param assignmentContext VariableTree
+   * @return AnnotatedTypeMirror of Assignment context
+   */
+  public static AnnotatedTypeMirror assignedToVariable(
+      AnnotatedTypeFactory atypeFactory, Tree assignmentContext) {
+    if (atypeFactory instanceof GenericAnnotatedTypeFactory<?, ?, ?, ?>) {
+      final GenericAnnotatedTypeFactory<?, ?, ?, ?> gatf =
+          ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory);
+      return gatf.getAnnotatedTypeLhsNoTypeVarDefault(assignmentContext);
+    } else {
+      return atypeFactory.getAnnotatedType(assignmentContext);
+    }
+  }
+
+  /**
+   * Returns true if the type contains a use of a type variable from methodType.
+   *
+   * @return true if the type contains a use of a type variable from methodType
+   */
+  private static boolean containsUninferredTypeParameter(
+      AnnotatedTypeMirror type, AnnotatedExecutableType methodType) {
+    final List<AnnotatedTypeVariable> annotatedTypeVars = methodType.getTypeVariables();
+    final List<TypeVariable> typeVars =
+        CollectionsPlume.mapList(
+            (AnnotatedTypeVariable annotatedTypeVar) ->
+                (TypeVariable)
+                    TypeAnnotationUtils.unannotatedType(annotatedTypeVar.getUnderlyingType()),
+            annotatedTypeVars);
+
+    return containsTypeParameter(type, typeVars);
+  }
+
+  /**
+   * Returns true if {@code type} contains a use of a type variable in {@code typeVariables}.
+   *
+   * @param type type to search
+   * @param typeVariables collection of type variables
+   * @return true if {@code type} contains a use of a type variable in {@code typeVariables}
+   */
+  public static boolean containsTypeParameter(
+      AnnotatedTypeMirror type, Collection<TypeVariable> typeVariables) {
+    // note NULL values creep in because the underlying visitor uses them in various places
+    final Boolean result = type.accept(new TypeVariableFinder(), typeVariables);
+    return result != null && result;
+  }
+
+  /**
+   * Take a set of annotations and separate them into a mapping of {@code hierarchy top =>
+   * annotations in hierarchy}.
+   */
+  public static AnnotationMirrorMap<AnnotationMirror> createHierarchyMap(
+      final AnnotationMirrorSet annos, final QualifierHierarchy qualifierHierarchy) {
+    AnnotationMirrorMap<AnnotationMirror> result = new AnnotationMirrorMap<>();
+
+    for (AnnotationMirror anno : annos) {
+      result.put(qualifierHierarchy.getTopAnnotation(anno), anno);
+    }
+
+    return result;
+  }
+
+  /**
+   * Throws an exception if the type is an uninferred type argument.
+   *
+   * <p>The error will be caught in DefaultTypeArgumentInference#infer and inference will be
+   * aborted, but type-checking will continue.
+   */
+  public static void checkForUninferredTypes(AnnotatedTypeMirror type) {
+    if (type.getKind() != TypeKind.WILDCARD) {
+      return;
+    }
+    if (((AnnotatedWildcardType) type).isUninferredTypeArgument()) {
+      throw new BugInCF("Can't make a constraint that includes an uninferred type argument.");
+    }
+  }
+
+  /**
+   * Used to detect if the visited type contains one of the type variables in the typeVars
+   * parameter.
+   */
+  private static class TypeVariableFinder
+      extends AnnotatedTypeScanner<Boolean, Collection<TypeVariable>> {
+
+    /** Create TypeVariableFinder. */
+    protected TypeVariableFinder() {
+      super(Boolean::logicalOr, false);
+    }
+
+    @Override
+    public Boolean visitTypeVariable(
+        AnnotatedTypeVariable type, Collection<TypeVariable> typeVars) {
+      if (typeVars.contains(
+          (TypeVariable) TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()))) {
+        return true;
+      } else {
+        return super.visitTypeVariable(type, typeVars);
+      }
+    }
+  }
+
+  /*
+   * Various TypeArgumentInference steps require substituting types for type arguments that have already been
+   * inferred into constraints that are used infer other type arguments.  Substituter is used in
+   * the utility methods to do this.
+   */
+  private static final TypeVariableSubstitutor substitutor = new TypeVariableSubstitutor();
+
+  // Substituter requires an input map that the substitute methods build.  We just reuse the same
+  // map rather than recreate it each time.
+  private static final Map<TypeVariable, AnnotatedTypeMirror> substituteMap = new HashMap<>(5);
+
+  /**
+   * Replace all uses of typeVariable with substitution in a copy of toModify using the normal
+   * substitution rules. Return the copy
+   *
+   * @see TypeVariableSubstitutor
+   */
+  public static AnnotatedTypeMirror substitute(
+      final TypeVariable typeVariable,
+      final AnnotatedTypeMirror substitution,
+      final AnnotatedTypeMirror toModify) {
+    substituteMap.clear();
+    substituteMap.put(typeVariable, substitution.deepCopy());
+
+    final AnnotatedTypeMirror toModifyCopy = toModify.deepCopy();
+    substitutor.substitute(substituteMap, toModifyCopy);
+    return toModifyCopy;
+  }
+
+  /**
+   * Create a copy of toModify. In the copy, for each pair {@code typeVariable => annotated type}
+   * replace uses of typeVariable with the corresponding annotated type using normal substitution
+   * rules (@see TypeVariableSubstitutor). Return the copy.
+   */
+  public static AnnotatedTypeMirror substitute(
+      Map<TypeVariable, AnnotatedTypeMirror> substitutions, final AnnotatedTypeMirror toModify) {
+    final AnnotatedTypeMirror substitution;
+    if (toModify.getKind() == TypeKind.TYPEVAR) {
+      substitution =
+          substitutions.get(
+              (TypeVariable) TypeAnnotationUtils.unannotatedType(toModify.getUnderlyingType()));
+    } else {
+      substitution = null;
+    }
+    if (substitution != null) {
+      return substitution.deepCopy();
+    }
+
+    final AnnotatedTypeMirror toModifyCopy = toModify.deepCopy();
+    substitutor.substitute(substitutions, toModifyCopy);
+    return toModifyCopy;
+  }
+
+  /**
+   * Successively calls least upper bound on the elements of types. Unlike leastUpperBound, this
+   * method will box primitives if necessary
+   */
+  public static AnnotatedTypeMirror leastUpperBound(
+      final AnnotatedTypeFactory typeFactory, final Iterable<AnnotatedTypeMirror> types) {
+    final Iterator<AnnotatedTypeMirror> typesIter = types.iterator();
+    if (!typesIter.hasNext()) {
+      throw new BugInCF("Calling LUB on empty list");
+    }
+    AnnotatedTypeMirror lubType = typesIter.next();
+    AnnotatedTypeMirror nextType = null;
+    while (typesIter.hasNext()) {
+      nextType = typesIter.next();
+
+      if (lubType.getKind().isPrimitive()) {
+        if (!nextType.getKind().isPrimitive()) {
+          lubType = typeFactory.getBoxedType((AnnotatedPrimitiveType) lubType);
+        }
+      } else if (nextType.getKind().isPrimitive()) {
+        if (!lubType.getKind().isPrimitive()) {
+          nextType = typeFactory.getBoxedType((AnnotatedPrimitiveType) nextType);
+        }
+      }
+      lubType = AnnotatedTypes.leastUpperBound(typeFactory, lubType, nextType);
+    }
+
+    return lubType;
+  }
+
+  /**
+   * If the type arguments computed by DefaultTypeArgumentInference don't match the return type
+   * mirror of {@code invocation}, then replace those type arguments with an uninferred wildcard.
+   */
+  protected static Map<TypeVariable, AnnotatedTypeMirror> correctResults(
+      Map<TypeVariable, AnnotatedTypeMirror> result,
+      ExpressionTree invocation,
+      ExecutableType methodType,
+      AnnotatedTypeFactory factory) {
+    ProcessingEnvironment env = factory.getProcessingEnv();
+    Types types = env.getTypeUtils();
+    Map<TypeVariable, TypeMirror> fromReturn =
+        getMappingFromReturnType(invocation, methodType, env);
+    for (Map.Entry<TypeVariable, AnnotatedTypeMirror> entry :
+        // result is side-effected by this loop, so iterate over a copy
+        new ArrayList<>(result.entrySet())) {
+      TypeVariable typeVariable = entry.getKey();
+      if (!fromReturn.containsKey(typeVariable)) {
+        continue;
+      }
+      TypeMirror correctType = fromReturn.get(typeVariable);
+      TypeMirror inferredType = entry.getValue().getUnderlyingType();
+      if (types.isSameType(types.erasure(correctType), types.erasure(inferredType))) {
+        if (areSameCapture(correctType, inferredType, types)) {
+          continue;
+        }
+      }
+      if (!types.isSameType(correctType, inferredType)) {
+        AnnotatedWildcardType wt =
+            factory.getUninferredWildcardType(
+                (AnnotatedTypeVariable)
+                    AnnotatedTypeMirror.createType(typeVariable, factory, false));
+        wt.replaceAnnotations(entry.getValue().getAnnotations());
+        result.put(typeVariable, wt);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns true if actual and inferred are captures of the same wildcard or declared type.
+   *
+   * @return true if actual and inferred are captures of the same wildcard or declared type
+   */
+  private static boolean areSameCapture(TypeMirror actual, TypeMirror inferred, Types types) {
+    if (TypesUtils.isCaptured(actual) && TypesUtils.isCaptured(inferred)) {
+      return true;
+    } else if (TypesUtils.isCaptured(actual) && inferred.getKind() == TypeKind.WILDCARD) {
+      return true;
+    } else if (actual.getKind() == TypeKind.DECLARED && inferred.getKind() == TypeKind.DECLARED) {
+      DeclaredType actualDT = (DeclaredType) actual;
+      DeclaredType inferredDT = (DeclaredType) inferred;
+      if (actualDT.getTypeArguments().size() == inferredDT.getTypeArguments().size()) {
+        for (int i = 0; i < actualDT.getTypeArguments().size(); i++) {
+          if (!areSameCapture(
+              actualDT.getTypeArguments().get(i), inferredDT.getTypeArguments().get(i), types)) {
+            return false;
+          }
+        }
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns a mapping of type variable to type argument computed using the type of {@code
+   * methodInvocationTree} and the return type of {@code methodType}.
+   */
+  private static Map<TypeVariable, TypeMirror> getMappingFromReturnType(
+      ExpressionTree methodInvocationTree, ExecutableType methodType, ProcessingEnvironment env) {
+    TypeMirror methodCallType = TreeUtils.typeOf(methodInvocationTree);
+    Types types = env.getTypeUtils();
+    GetMapping mapping = new GetMapping(methodType.getTypeVariables(), types);
+    mapping.visit(methodType.getReturnType(), methodCallType);
+    return mapping.subs;
+  }
+
+  /**
+   * Helper class for {@link #getMappingFromReturnType(ExpressionTree, ExecutableType,
+   * ProcessingEnvironment)}
+   */
+  private static class GetMapping implements TypeVisitor<Void, TypeMirror> {
+
+    final Map<TypeVariable, TypeMirror> subs = new HashMap<>();
+    final List<? extends TypeVariable> typeVariables;
+    final Types types;
+
+    private GetMapping(List<? extends TypeVariable> typeVariables, Types types) {
+      this.typeVariables = typeVariables;
+      this.types = types;
+    }
+
+    @Override
+    public Void visit(TypeMirror t, TypeMirror mirror) {
+      if (t == null || mirror == null) {
+        return null;
+      }
+      return t.accept(this, mirror);
+    }
+
+    @Override
+    public Void visit(TypeMirror t) {
+      return null;
+    }
+
+    @Override
+    public Void visitPrimitive(PrimitiveType t, TypeMirror mirror) {
+      return null;
+    }
+
+    @Override
+    public Void visitNull(NullType t, TypeMirror mirror) {
+      return null;
+    }
+
+    @Override
+    public Void visitArray(ArrayType t, TypeMirror mirror) {
+      assert mirror.getKind() == TypeKind.ARRAY : mirror;
+      return visit(t.getComponentType(), ((ArrayType) mirror).getComponentType());
+    }
+
+    @Override
+    public Void visitDeclared(DeclaredType t, TypeMirror mirror) {
+      assert mirror.getKind() == TypeKind.DECLARED : mirror;
+      DeclaredType param = (DeclaredType) mirror;
+      if (types.isSubtype(mirror, param)) {
+        //                param = (DeclaredType) types.asSuper((Type) mirror, ((Type)
+        // param).asElement());
+      }
+      if (t.getTypeArguments().size() == param.getTypeArguments().size()) {
+        for (int i = 0; i < t.getTypeArguments().size(); i++) {
+          visit(t.getTypeArguments().get(i), param.getTypeArguments().get(i));
+        }
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitError(ErrorType t, TypeMirror mirror) {
+      return null;
+    }
+
+    @Override
+    public Void visitTypeVariable(TypeVariable t, TypeMirror mirror) {
+      if (typeVariables.contains(t)) {
+        subs.put(t, mirror);
+      } else if (mirror.getKind() == TypeKind.TYPEVAR) {
+        TypeVariable param = (TypeVariable) mirror;
+        visit(t.getUpperBound(), param.getUpperBound());
+        visit(t.getLowerBound(), param.getLowerBound());
+      }
+      // else it's not a method type variable
+      return null;
+    }
+
+    @Override
+    public Void visitWildcard(javax.lang.model.type.WildcardType t, TypeMirror mirror) {
+      if (mirror.getKind() == TypeKind.WILDCARD) {
+        javax.lang.model.type.WildcardType param = (javax.lang.model.type.WildcardType) mirror;
+        visit(t.getExtendsBound(), param.getExtendsBound());
+        visit(t.getSuperBound(), param.getSuperBound());
+      } else if (mirror.getKind() == TypeKind.TYPEVAR) {
+        TypeVariable param = (TypeVariable) mirror;
+        visit(t.getExtendsBound(), param.getUpperBound());
+        visit(t.getSuperBound(), param.getLowerBound());
+      } else {
+        assert false : mirror;
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitExecutable(ExecutableType t, TypeMirror mirror) {
+      return null;
+    }
+
+    @Override
+    public Void visitNoType(NoType t, TypeMirror mirror) {
+      return null;
+    }
+
+    @Override
+    public Void visitUnknown(TypeMirror t, TypeMirror mirror) {
+      return null;
+    }
+
+    @Override
+    public Void visitUnion(UnionType t, TypeMirror mirror) {
+      return null;
+    }
+
+    @Override
+    public Void visitIntersection(IntersectionType t, TypeMirror mirror) {
+      assert mirror.getKind() == TypeKind.INTERSECTION : mirror;
+      IntersectionType param = (IntersectionType) mirror;
+      assert t.getBounds().size() == param.getBounds().size();
+
+      for (int i = 0; i < t.getBounds().size(); i++) {
+        visit(t.getBounds().get(i), param.getBounds().get(i));
+      }
+
+      return null;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java
new file mode 100644
index 0000000..b4b93ae
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java
@@ -0,0 +1,58 @@
+package org.checkerframework.framework.util.typeinference;
+
+import com.sun.source.tree.ExpressionTree;
+import java.util.Map;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
+
+/**
+ * Instances of TypeArgumentInference are used to infer the types of method type arguments when no
+ * explicit arguments are provided.
+ *
+ * <p>e.g. If we have a method declaration:
+ *
+ * <pre>{@code
+ * <A,B> B method(A a, B b) {...}
+ * }</pre>
+ *
+ * And an invocation of that method:
+ *
+ * <pre>{@code
+ * method("some Str", 35);
+ * }</pre>
+ *
+ * TypeArgumentInference will determine what the type arguments to type parameters A and B are. In
+ * Java, if T(A) = the type argument for a, in the above example T(A) == String and T(B) == Integer
+ *
+ * <p>For the Checker Framework we also need to infer reasonable annotations for these type
+ * arguments. For information on inferring type arguments see the documentation in JLS7 and JLS8:
+ * https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html
+ * https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.7
+ *
+ * <p>Note: It appears that Java 8 greatly improved the type argument inference and related error
+ * messaging but I have found it useful to consult the JLS 7 as well.
+ */
+public interface TypeArgumentInference {
+
+  /**
+   * Infer the type arguments for the method or constructor invocation given by invocation.
+   *
+   * @param typeFactory the type factory used to create methodType
+   * @param invocation a tree representing the method or constructor invocation for which we are
+   *     inferring type arguments
+   * @param methodElem the element for the declaration of the method being invoked
+   * @param methodType the declaration type of method elem
+   * @return a mapping between the Java type parameter and the annotated type that was inferred for
+   *     it. Note: We use the Java TypeVariable type because this uniquely identifies a declaration
+   *     where as two uses of an AnnotatedTypeVariable may be uses of the same declaration but are
+   *     not .equals to each other.
+   */
+  public Map<TypeVariable, AnnotatedTypeMirror> inferTypeArgs(
+      final AnnotatedTypeFactory typeFactory,
+      final ExpressionTree invocation,
+      final ExecutableElement methodElem,
+      final AnnotatedExecutableType methodType);
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2F.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2F.java
new file mode 100644
index 0000000..d6c96de
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2F.java
@@ -0,0 +1,32 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+
+/**
+ * A constraint of the form: A 《 F or F 》 A
+ *
+ * @see org.checkerframework.framework.util.typeinference.constraint.AFConstraint
+ */
+public class A2F extends AFConstraint {
+
+  /** Create a constraint with an argument less than a formal. */
+  public A2F(AnnotatedTypeMirror argument, AnnotatedTypeMirror formalParameter) {
+    super(argument, formalParameter);
+  }
+
+  @Override
+  public TUConstraint toTUConstraint() {
+    return new TSuperU((AnnotatedTypeVariable) formalParameter, argument, true);
+  }
+
+  @Override
+  protected A2F construct(AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter) {
+    return new A2F(newArgument, newFormalParameter);
+  }
+
+  @Override
+  public String toString() {
+    return "A2F( " + argument + " << " + formalParameter + " )";
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2FReducer.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2FReducer.java
new file mode 100644
index 0000000..800aab1
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2FReducer.java
@@ -0,0 +1,71 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import java.util.Set;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/**
+ * A2FReducer takes an A2F constraint that is not irreducible (@see AFConstraint.isIrreducible) and
+ * reduces it by one step. The resulting constraint may still be reducible.
+ *
+ * <p>Generally reductions should map to corresponding rules in
+ * https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2.7
+ */
+public class A2FReducer implements AFReducer {
+
+  protected final A2FReducingVisitor visitor;
+
+  public A2FReducer(final AnnotatedTypeFactory typeFactory) {
+    this.visitor = new A2FReducingVisitor(typeFactory);
+  }
+
+  @Override
+  public boolean reduce(AFConstraint constraint, Set<AFConstraint> newConstraints) {
+    if (constraint instanceof A2F) {
+      final A2F a2f = (A2F) constraint;
+      visitor.visit(a2f.argument, a2f.formalParameter, newConstraints);
+      return true;
+
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Given an A2F constraint of the form: A2F( typeFromMethodArgument, typeFromFormalParameter )
+   *
+   * <p>A2FReducingVisitor visits the constraint as follows: visit ( typeFromMethodArgument,
+   * typeFromFormalParameter, newConstraints )
+   *
+   * <p>The visit method will determine if the given constraint should either:
+   *
+   * <ul>
+   *   <li>be discarded -- in this case, the visitor just returns
+   *   <li>reduced to a simpler constraint or set of constraints -- in this case, the new constraint
+   *       or set of constraints is added to newConstraints
+   * </ul>
+   */
+  private static class A2FReducingVisitor extends AFReducingVisitor {
+
+    public A2FReducingVisitor(AnnotatedTypeFactory typeFactory) {
+      super(A2F.class, typeFactory);
+    }
+
+    @Override
+    public AFConstraint makeConstraint(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) {
+      return new A2F(subtype, supertype);
+    }
+
+    @Override
+    public AFConstraint makeInverseConstraint(
+        AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) {
+      return new F2A(subtype, supertype);
+    }
+
+    @Override
+    public AFConstraint makeEqualityConstraint(
+        AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) {
+      return new FIsA(supertype, subtype);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java
new file mode 100644
index 0000000..720c50f
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java
@@ -0,0 +1,121 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import javax.lang.model.type.TypeVariable;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
+
+/**
+ * AFConstraint represent the initial constraints used to infer type arguments for method
+ * invocations and new class invocations. These constraints are simplified then converted to
+ * TUConstraints during type argument inference.
+ *
+ * <p>Subclasses of AFConstraint represent the following <a
+ * href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.7">types of
+ * constraints</a>:
+ *
+ * <p>A 《 F and F 》 A both imply that A is convertible to F. F 《 A and A 》 F both imply that F is
+ * convertible to A (this may happen due to wildcard/typevar bounds and recursive types) A = F
+ * implies that A is exactly F
+ *
+ * <p>In the Checker Framework a type, A will be convertible to another type F, if
+ * AnnotatedTypes.asSuper will return a non-null value when A is passed as a subtype and F the
+ * supertype to the method.
+ *
+ * <p>In Java type A will be convertible to another type F if there exists a conversion
+ * context/method that transforms the one type into the other.
+ *
+ * <p>A 《 F and F 》 A are represented by class A2F F 《 A and A 》 F are represented by class F2A F =
+ * A is represented by class FIsA
+ */
+public abstract class AFConstraint {
+  /** The argument type. */
+  public final AnnotatedTypeMirror argument;
+  /** The formal parameter type. */
+  public final AnnotatedTypeMirror formalParameter;
+
+  /** Create a constraint for type arguments for a methodd invocation or new class invocation. */
+  protected AFConstraint(
+      final AnnotatedTypeMirror argument, final AnnotatedTypeMirror formalParameter) {
+    this.argument = argument;
+    this.formalParameter = formalParameter;
+    TypeArgInferenceUtil.checkForUninferredTypes(argument);
+  }
+
+  /**
+   * Returns true if this constraint can't be broken up into other constraints or further
+   * simplified. In general, if either argument or formal parameter is a use of the type parameters
+   * we are inferring over then the constraint cannot be reduced further.
+   *
+   * @param targets the type parameters whose arguments we are trying to solve for
+   * @return true if this constraint can't be broken up into other constraints or further simplified
+   */
+  public boolean isIrreducible(final Set<TypeVariable> targets) {
+    return TypeArgInferenceUtil.isATarget(argument, targets)
+        || TypeArgInferenceUtil.isATarget(formalParameter, targets);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object thatObject) {
+    if (this == thatObject) {
+      return true;
+    } // else
+
+    if (thatObject == null || this.getClass() != thatObject.getClass()) {
+      return false;
+    }
+
+    final AFConstraint that = (AFConstraint) thatObject;
+
+    return this.argument.equals(that.argument) && this.formalParameter.equals(that.formalParameter);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(this.getClass(), formalParameter, argument);
+  }
+
+  /**
+   * Once AFConstraints are irreducible it can be converted to a TU constraint, constraints between
+   * individual type parameters for which we are inferring an argument (T) and Java types (U).
+   *
+   * @return a TUConstraint that represents this AFConstraint
+   */
+  public abstract TUConstraint toTUConstraint();
+
+  /**
+   * Given a partial solution to our type argument inference, replace any uses of type parameters
+   * that have been solved with their arguments.
+   *
+   * <p>That is: Let S be a partial solution to our inference (i.e. we have inferred type arguments
+   * for some types) Let S be a map {@code (T0 => A0, T1 => A1, ..., TN => AN)} where Ti is a type
+   * parameter and Ai is its solved argument. For all uses of Ti in this constraint, replace them
+   * with Ai.
+   *
+   * <p>For the mapping {@code (T0 => A0)}, the following constraint: {@code ArrayList<T0> <<
+   * List<T1>}
+   *
+   * <p>Becomes: {@code ArrayList<A0> << List<T1>}
+   *
+   * <p>A constraint: {@code T0 << T1}
+   *
+   * <p>Becomes: {@code A0 << T1}
+   *
+   * @param substitutions a mapping of target type parameter to the type argument to
+   * @return a new constraint that contains no use of the keys in substitutions
+   */
+  public AFConstraint substitute(final Map<TypeVariable, AnnotatedTypeMirror> substitutions) {
+    final AnnotatedTypeMirror newArgument =
+        TypeArgInferenceUtil.substitute(substitutions, argument);
+    final AnnotatedTypeMirror newFormalParameter =
+        TypeArgInferenceUtil.substitute(substitutions, formalParameter);
+    return construct(newArgument, newFormalParameter);
+  }
+
+  /** Used to create a new constraint of the same subclass of AFConstraint. */
+  protected abstract AFConstraint construct(
+      AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter);
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducer.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducer.java
new file mode 100644
index 0000000..5871c88
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducer.java
@@ -0,0 +1,27 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import java.util.Set;
+
+/**
+ * AFReducer implementations reduce AFConstraints into one or more "simpler" AFConstraints until
+ * these constraints are irreducible.
+ *
+ * @see
+ *     org.checkerframework.framework.util.typeinference.constraint.AFConstraint#isIrreducible(java.util.Set)
+ *     <p>There is one AFReducer for each type of AFConstraint.
+ */
+public interface AFReducer {
+
+  /**
+   * Determines if the input constraint should be handled by this reducer. If so: Reduces the
+   * constraint into one or more new constraints. Any new constraint that can still be reduced is
+   * placed in newConstraints. New irreducible constraints are placed in finish. Return true Return
+   * false (indicating that some other reducer needs to handle this constraint) If false is
+   * returned, the reducer should NOT place any constraints in newConstraints or finished
+   *
+   * @param constraint the constraint to reduce
+   * @param newConstraints the new constraints that may still need to be reduced
+   * @return true if the input constraint was handled by this reducer, false otherwise
+   */
+  public boolean reduce(AFConstraint constraint, Set<AFConstraint> newConstraints);
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducingVisitor.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducingVisitor.java
new file mode 100644
index 0000000..a9d1bb3
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducingVisitor.java
@@ -0,0 +1,557 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AbstractAtmComboVisitor;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Takes a single step in reducing a AFConstraint.
+ *
+ * <p>The visit method will determine if the given constraint should either:
+ *
+ * <ul>
+ *   <li>be discarded - in this case, the visitor just returns
+ *   <li>reduced to a simpler constraint or set of constraints - in this case, the new constraint or
+ *       set of constraints is added to newConstraints
+ * </ul>
+ *
+ * Sprinkled throughout this class are comments of the form:
+ *
+ * <pre>{@code
+ * // If F has the form G<..., Yk-1, ? super U, Yk+1, ...>, where U involves Tj
+ * }</pre>
+ *
+ * These are excerpts from the JLS, if you search for them you will find the corresponding JLS
+ * description of the case being covered.
+ */
+abstract class AFReducingVisitor extends AbstractAtmComboVisitor<Void, Set<AFConstraint>> {
+
+  public final Class<? extends AFConstraint> reducerType;
+  public final AnnotatedTypeFactory typeFactory;
+
+  protected AFReducingVisitor(
+      final Class<? extends AFConstraint> reducerType, final AnnotatedTypeFactory typeFactory) {
+    this.reducerType = reducerType;
+    this.typeFactory = typeFactory;
+  }
+
+  public abstract AFConstraint makeConstraint(
+      AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype);
+
+  public abstract AFConstraint makeInverseConstraint(
+      AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype);
+
+  public abstract AFConstraint makeEqualityConstraint(
+      AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype);
+
+  public void addConstraint(
+      AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Set<AFConstraint> constraints) {
+    constraints.add(makeConstraint(subtype, supertype));
+  }
+
+  public void addInverseConstraint(
+      AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Set<AFConstraint> constraints) {
+    constraints.add(makeInverseConstraint(subtype, supertype));
+  }
+
+  public void addEqualityConstraint(
+      AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Set<AFConstraint> constraints) {
+    constraints.add(makeEqualityConstraint(subtype, supertype));
+  }
+
+  /**
+   * Called when we encounter an AF constraint on a type combination that we did not think is
+   * possible. This either implies that the type combination is possible, we accidentally created an
+   * invalid A2F or F2A Constraint, or we called the visit method on two AnnotatedTypeMirrors that
+   * do not appear together in a constraint.
+   */
+  @Override
+  protected String defaultErrorMessage(
+      AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Set<AFConstraint> constraints) {
+    return StringsPlume.joinLines(
+        "Unexpected " + reducerType.getSimpleName() + " + Combination:",
+        "subtype=" + subtype,
+        "supertype=" + supertype,
+        "constraints=[",
+        StringsPlume.join(", ", constraints),
+        "]");
+  }
+
+  // ------------------------------------------------------------------------
+  // Arrays as arguments
+  // From the JLS:
+  //    If F = U[], where the type U involves Tj, then if A is an array type V[], or a type
+  //    variable with an upper bound that is an array type V[], where V is a reference type, this
+  //    algorithm is applied recursively to the constraint V << U or U << V (depending on the
+  //    constraint type).
+
+  @Override
+  public Void visitArray_Array(
+      AnnotatedArrayType subtype, AnnotatedArrayType supertype, Set<AFConstraint> constraints) {
+    addConstraint(subtype.getComponentType(), supertype.getComponentType(), constraints);
+    return null;
+  }
+
+  @Override
+  public Void visitArray_Declared(
+      AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  @Override
+  public Void visitArray_Null(
+      AnnotatedArrayType subtype, AnnotatedNullType supertype, Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  @Override
+  public Void visitArray_Wildcard(
+      AnnotatedArrayType subtype, AnnotatedWildcardType supertype, Set<AFConstraint> constraints) {
+    visitWildcardAsSuperType(subtype, supertype, constraints);
+    return null;
+  }
+
+  // Despite the above the comment at the beginning of the "array as arguments" section, a type
+  // variable cannot actually have an array type as its upper bound (e.g. <T extends Integer[]> is
+  // not allowed).
+  // So the only cases in which we visitArray_Typevar would be cases in which either:
+  //   1) Typevar is a type parameter for which we are inferring an argument, in which case the
+  //      combination is already irreducible and we would not pass it to this class.
+  //   2) Typevar is an outer scope type variable, in which case it could NOT reference any of the
+  //      type parameters for which we are inferring arguments and therefore will not lead to any
+  //      meaningful AFConstraints.
+  // public void visitArray_Typevar
+
+  // ------------------------------------------------------------------------
+  // Declared as argument
+
+  /**
+   * I believe there should be only 1 way to have a constraint of this form: {@code visit (Array<T>,
+   * T [])} At this point, I don't think that's a valid argument for a formal parameter. If this
+   * occurs it is because of idiosyncrasies with the Checker Framework . We're going to skip this
+   * case for now.
+   */
+  @Override
+  public Void visitDeclared_Array(
+      AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  // From the JLS Spec:
+  //  If F has the form G<..., Yk-1,U, Yk+1, ...>, where U involves Tj
+  @Override
+  public Void visitDeclared_Declared(
+      AnnotatedDeclaredType subtype,
+      AnnotatedDeclaredType supertype,
+      Set<AFConstraint> constraints) {
+    if (subtype.wasRaw() || supertype.wasRaw()) {
+      // The error will be caught in {@link DefaultTypeArgumentInference#infer} and
+      // inference will be aborted, but type-checking will continue.
+      throw new BugInCF("Can't infer type arguments when raw types are involved.");
+    }
+
+    if (!TypesUtils.isErasedSubtype(
+        subtype.getUnderlyingType(),
+        supertype.getUnderlyingType(),
+        typeFactory.getChecker().getTypeUtils())) {
+      return null;
+    }
+    AnnotatedDeclaredType subAsSuper =
+        AnnotatedTypes.castedAsSuper(typeFactory, subtype, supertype);
+
+    final List<AnnotatedTypeMirror> subTypeArgs = subAsSuper.getTypeArguments();
+    final List<AnnotatedTypeMirror> superTypeArgs = supertype.getTypeArguments();
+    for (int i = 0; i < subTypeArgs.size(); i++) {
+      final AnnotatedTypeMirror subTypeArg = subTypeArgs.get(i);
+      final AnnotatedTypeMirror superTypeArg = superTypeArgs.get(i);
+
+      // If F has the form G<..., Yk-1, ? extends U, Yk+1, ...>, where U involves Tj
+      // If F has the form G<..., Yk-1, ? super U, Yk+1, ...>, where U involves Tj
+      // Since we always have both bounds in the checker framework we always compare both
+      if (superTypeArg.getKind() == TypeKind.WILDCARD) {
+        final AnnotatedWildcardType superWc = (AnnotatedWildcardType) superTypeArg;
+
+        if (subTypeArg.getKind() == TypeKind.WILDCARD) {
+          final AnnotatedWildcardType subWc = (AnnotatedWildcardType) subTypeArg;
+          TypeArgInferenceUtil.checkForUninferredTypes(subWc);
+          addConstraint(subWc.getExtendsBound(), superWc.getExtendsBound(), constraints);
+          addInverseConstraint(superWc.getSuperBound(), subWc.getSuperBound(), constraints);
+        } else {
+          addConstraint(subTypeArg, superWc.getExtendsBound(), constraints);
+          addInverseConstraint(superWc.getSuperBound(), subTypeArg, constraints);
+        }
+
+      } else {
+        // if F has the form G<..., Yk-1, U, Yk+1, ...>, where U is a type expression that involves
+        // Tj
+        addEqualityConstraint(subTypeArg, superTypeArg, constraints);
+      }
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitDeclared_Intersection(
+      AnnotatedDeclaredType subtype,
+      AnnotatedIntersectionType supertype,
+      Set<AFConstraint> constraints) {
+
+    // Note: AnnotatedIntersectionTypes cannot have a type variable as one of the direct
+    // parameters but a type variable may be the type subtype to an intersection bound <e.g.  <T
+    // extends Serializable & Iterable<T>>
+    for (final AnnotatedTypeMirror intersectionBound : supertype.getBounds()) {
+      if (intersectionBound instanceof AnnotatedDeclaredType
+          && !((AnnotatedDeclaredType) intersectionBound).getTypeArguments().isEmpty()) {
+        addConstraint(subtype, supertype, constraints);
+      }
+    }
+
+    return null;
+  }
+
+  // Remember that NULL types can come from lower bounds
+  @Override
+  public Void visitDeclared_Null(
+      AnnotatedDeclaredType subtype, AnnotatedNullType supertype, Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  // a primitive supertype provides us no information on the type of any type parameters for that
+  // method
+  @Override
+  public Void visitDeclared_Primitive(
+      AnnotatedDeclaredType subtype,
+      AnnotatedPrimitiveType supertype,
+      Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  @Override
+  public Void visitDeclared_Typevar(
+      AnnotatedDeclaredType subtype,
+      AnnotatedTypeVariable supertype,
+      Set<AFConstraint> constraints) {
+    // Note: We expect the A2F constraints where F == a targeted type supertype to already be
+    // removed.  Therefore, supertype should NOT be a target.
+    addConstraint(subtype, supertype, constraints);
+    return null;
+  }
+
+  @Override
+  public Void visitDeclared_Union(
+      AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, Set<AFConstraint> constraints) {
+    return null; // TODO: NOT SUPPORTED AT THE MOMENT
+  }
+
+  @Override
+  public Void visitDeclared_Wildcard(
+      AnnotatedDeclaredType subtype,
+      AnnotatedWildcardType supertype,
+      Set<AFConstraint> constraints) {
+    visitWildcardAsSuperType(subtype, supertype, constraints);
+    return null;
+  }
+
+  // ------------------------------------------------------------------------
+  // Intersection as subtype
+  @Override
+  public Void visitIntersection_Declared(
+      AnnotatedIntersectionType subtype,
+      AnnotatedDeclaredType supertype,
+      Set<AFConstraint> constraints) {
+
+    // at least one of the intersection bound types must be convertible to the param type
+    final AnnotatedDeclaredType subtypeAsParam =
+        AnnotatedTypes.castedAsSuper(typeFactory, subtype, supertype);
+    if (subtypeAsParam != null && !subtypeAsParam.equals(supertype)) {
+      addConstraint(subtypeAsParam, supertype, constraints);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitIntersection_Intersection(
+      AnnotatedIntersectionType argument,
+      AnnotatedIntersectionType parameter,
+      Set<AFConstraint> constraints) {
+    return null; // TODO: NOT SUPPORTED AT THE MOMENT
+  }
+
+  // provides no information as the AnnotatedNullType cannot refer to a type parameter
+  @Override
+  public Void visitIntersection_Null(
+      AnnotatedIntersectionType argument,
+      AnnotatedNullType parameter,
+      Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  // ------------------------------------------------------------------------
+  // Null as argument
+
+  /**
+   * NULL types only have primary annotations. A type parameter could only appear as a component of
+   * the parameter type and therefore has no relationship to these primary annotations
+   */
+  @Override
+  public Void visitNull_Array(
+      AnnotatedNullType argument, AnnotatedArrayType parameter, Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  /**
+   * NULL types only have primary annotations. A type parameter could only appear as a component of
+   * the parameter type and therefore has no relationship to these primary annotations
+   */
+  @Override
+  public Void visitNull_Declared(
+      AnnotatedNullType argument, AnnotatedDeclaredType parameter, Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  /**
+   * TODO: PERHAPS FOR ALL OF THESE WHERE WE COMPARE AGAINST THE LOWER BOUND, WE SHOULD INSTEAD
+   * COMPARE TODO: against the UPPER_BOUND with the LOWER_BOUND's PRIMARY ANNOTATIONS For captured
+   * types, the lower bound might be interesting so we compare against the lower bound but for most
+   * types the constraint added in this method is probably discarded in the next round of reduction
+   * (especially since we don't implement capture at the moment).
+   */
+  @Override
+  public Void visitNull_Typevar(
+      AnnotatedNullType subtype, AnnotatedTypeVariable supertype, Set<AFConstraint> constraints) {
+    // Note: We would expect that parameter is not one of the targets or else it would already
+    // be removed. Therefore we compare NULL against its bound.
+    addConstraint(subtype, supertype.getLowerBound(), constraints);
+    return null;
+  }
+
+  @Override
+  public Void visitNull_Wildcard(
+      AnnotatedNullType subtype, AnnotatedWildcardType supertype, Set<AFConstraint> constraints) {
+    TypeArgInferenceUtil.checkForUninferredTypes(supertype);
+    // we don't use visitSupertype because Null types won't have interesting components
+    constraints.add(new A2F(subtype, supertype.getSuperBound()));
+    return null;
+  }
+
+  @Override
+  public Void visitNull_Null(
+      AnnotatedNullType argument, AnnotatedNullType parameter, Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  @Override
+  public Void visitNull_Union(
+      AnnotatedNullType argument, AnnotatedUnionType parameter, Set<AFConstraint> constraints) {
+    return null; // TODO: UNIONS ARE NOT YET SUPPORTED
+  }
+
+  // Despite the fact that intersections are not yet supported, this is the right impelementation.
+  // NULL types only have primary annotations.  Since type parameters cannot be a member of the
+  // intersection's bounds (though they can be component types), we do not need to do anything
+  // further.
+  @Override
+  public Void visitNull_Intersection(
+      AnnotatedNullType argument,
+      AnnotatedIntersectionType parameter,
+      Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  // Primitive parameter types tell us nothing about the type parameters
+  @Override
+  public Void visitNull_Primitive(
+      AnnotatedNullType argument, AnnotatedPrimitiveType parameter, Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  // ------------------------------------------------------------------------
+  // Primitive as argument
+
+  @Override
+  public Void visitPrimitive_Declared(
+      AnnotatedPrimitiveType subtype,
+      AnnotatedDeclaredType supertype,
+      Set<AFConstraint> constraints) {
+    // we may be able to eliminate this case, since I believe the corresponding constraint will
+    // just be discarded as the parameter must be a boxed primitive
+    addConstraint(typeFactory.getBoxedType(subtype), supertype, constraints);
+    return null;
+  }
+
+  // Primitive parameter types tell us nothing about the type parameters
+  @Override
+  public Void visitPrimitive_Primitive(
+      AnnotatedPrimitiveType subtype,
+      AnnotatedPrimitiveType supertype,
+      Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  @Override
+  public Void visitPrimitive_Intersection(
+      AnnotatedPrimitiveType subtype,
+      AnnotatedIntersectionType supertype,
+      Set<AFConstraint> constraints) {
+    addConstraint(typeFactory.getBoxedType(subtype), supertype, constraints);
+    return null;
+  }
+
+  // ------------------------------------------------------------------------
+  // Union as argument
+  @Override
+  public Void visitUnion_Declared(
+      AnnotatedUnionType argument, AnnotatedDeclaredType parameter, Set<AFConstraint> constraints) {
+    return null; // TODO: UNIONS ARE NOT CURRENTLY SUPPORTED
+  }
+
+  // ------------------------------------------------------------------------
+  // typevar as argument
+  // If we've reached this point, the typevar is NOT one of the types we are inferring.
+
+  @Override
+  public Void visitTypevar_Declared(
+      AnnotatedTypeVariable subtype,
+      AnnotatedDeclaredType supertype,
+      Set<AFConstraint> constraints) {
+    addConstraint(subtype.getUpperBound(), supertype, constraints);
+    return null;
+  }
+
+  @Override
+  public Void visitTypevar_Typevar(
+      AnnotatedTypeVariable subtype,
+      AnnotatedTypeVariable supertype,
+      Set<AFConstraint> constraints) {
+    // if we've reached this point and the two are corresponding type variables, then they are
+    // NOT ones that may have a type variable we are inferring types for and therefore we can
+    // discard this constraint
+    if (!AnnotatedTypes.areCorrespondingTypeVariables(
+        typeFactory.getElementUtils(), subtype, supertype)) {
+      addConstraint(subtype.getUpperBound(), supertype.getLowerBound(), constraints);
+    }
+
+    return null;
+  }
+
+  @Override
+  public Void visitTypevar_Null(
+      AnnotatedTypeVariable subtype, AnnotatedNullType supertype, Set<AFConstraint> constraints) {
+    addConstraint(subtype.getUpperBound(), supertype, constraints);
+    return null;
+  }
+
+  @Override
+  public Void visitTypevar_Wildcard(
+      AnnotatedTypeVariable subtype,
+      AnnotatedWildcardType supertype,
+      Set<AFConstraint> constraints) {
+    visitWildcardAsSuperType(subtype, supertype, constraints);
+    return null;
+  }
+
+  @Override
+  public Void visitTypevar_Intersection(
+      AnnotatedTypeVariable subtype,
+      AnnotatedIntersectionType supertype,
+      Set<AFConstraint> constraints) {
+    addConstraint(subtype.getUpperBound(), supertype, constraints);
+    return null;
+  }
+
+  // ------------------------------------------------------------------------
+  // wildcard as subtype
+  @Override
+  public Void visitWildcard_Array(
+      AnnotatedWildcardType subtype, AnnotatedArrayType supertype, Set<AFConstraint> constraints) {
+    TypeArgInferenceUtil.checkForUninferredTypes(subtype);
+    addConstraint(subtype.getExtendsBound(), supertype, constraints);
+    return null;
+  }
+
+  @Override
+  public Void visitWildcard_Declared(
+      AnnotatedWildcardType subtype,
+      AnnotatedDeclaredType supertype,
+      Set<AFConstraint> constraints) {
+    TypeArgInferenceUtil.checkForUninferredTypes(subtype);
+    addConstraint(subtype.getExtendsBound(), supertype, constraints);
+    return null;
+  }
+
+  @Override
+  public Void visitWildcard_Intersection(
+      AnnotatedWildcardType subtype,
+      AnnotatedIntersectionType supertype,
+      Set<AFConstraint> constraints) {
+    TypeArgInferenceUtil.checkForUninferredTypes(subtype);
+    addConstraint(subtype.getExtendsBound(), supertype, constraints);
+    return null;
+  }
+
+  @Override
+  public Void visitWildcard_Primitive(
+      AnnotatedWildcardType subtype,
+      AnnotatedPrimitiveType supertype,
+      Set<AFConstraint> constraints) {
+    return null;
+  }
+
+  @Override
+  public Void visitWildcard_Typevar(
+      AnnotatedWildcardType subtype,
+      AnnotatedTypeVariable supertype,
+      Set<AFConstraint> constraints) {
+    TypeArgInferenceUtil.checkForUninferredTypes(subtype);
+    addConstraint(subtype.getExtendsBound(), supertype, constraints);
+    return null;
+  }
+
+  @Override
+  public Void visitWildcard_Wildcard(
+      AnnotatedWildcardType subtype,
+      AnnotatedWildcardType supertype,
+      Set<AFConstraint> constraints) {
+    TypeArgInferenceUtil.checkForUninferredTypes(subtype);
+    // since wildcards are handled in visitDeclared_Declared this could only occur if two
+    // wildcards were passed to type subtype inference at the top level.  This can only occur
+    // because we do not implement capture conversion.
+    visitWildcardAsSuperType(subtype.getExtendsBound(), supertype, constraints);
+    return null;
+  }
+
+  // should the same logic apply to typevars?
+  public void visitWildcardAsSuperType(
+      AnnotatedTypeMirror subtype, AnnotatedWildcardType supertype, Set<AFConstraint> constraints) {
+    TypeArgInferenceUtil.checkForUninferredTypes(supertype);
+    // this case occur only when supertype should actually be capture converted (which we don't
+    // do) because all other wildcard cases would be handled via Declared_Declared
+    addConstraint(subtype, supertype.getSuperBound(), constraints);
+
+    // if type1 is below the superbound then it is necessarily below the extends bound
+    // BUT the extends bound may have interesting component types (like the array component)
+    // to which we also want to apply constraints
+    // e.g. visitArray_Wildcard(@I String[], ? extends @A String[])
+    // if @I is an annotation we are trying to infer then we still want to infer that @I <: @A
+    // in fact
+    addInverseConstraint(subtype, supertype.getExtendsBound(), constraints);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2A.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2A.java
new file mode 100644
index 0000000..74ce873
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2A.java
@@ -0,0 +1,32 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+
+/**
+ * A constraint of the form: F 《 A or A 》 F
+ *
+ * @see org.checkerframework.framework.util.typeinference.constraint.AFConstraint
+ */
+public class F2A extends AFConstraint {
+
+  /** Create a constraint with an argument greater than a formal. */
+  public F2A(AnnotatedTypeMirror formalParameter, AnnotatedTypeMirror argument) {
+    super(argument, formalParameter);
+  }
+
+  @Override
+  public TUConstraint toTUConstraint() {
+    return new TSubU((AnnotatedTypeVariable) formalParameter, argument, true);
+  }
+
+  @Override
+  protected F2A construct(AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter) {
+    return new F2A(newFormalParameter, newArgument);
+  }
+
+  @Override
+  public String toString() {
+    return "F2A( " + formalParameter + " << " + argument + " )";
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2AReducer.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2AReducer.java
new file mode 100644
index 0000000..75dac99
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2AReducer.java
@@ -0,0 +1,80 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import java.util.Set;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+
+/**
+ * F2AReducer takes an F2A constraint that is not irreducible (@see AFConstraint.isIrreducible) and
+ * reduces it by one step. The resulting constraint may still be reducible.
+ *
+ * <p>Generally reductions should map to corresponding rules in
+ * https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2.7
+ */
+public class F2AReducer implements AFReducer {
+
+  protected final F2AReducingVisitor visitor;
+
+  public F2AReducer(final AnnotatedTypeFactory typeFactory) {
+    this.visitor = new F2AReducingVisitor(typeFactory);
+  }
+
+  @Override
+  public boolean reduce(AFConstraint constraint, Set<AFConstraint> newConstraints) {
+    if (constraint instanceof F2A) {
+      final F2A f2A = (F2A) constraint;
+      visitor.visit(f2A.formalParameter, f2A.argument, newConstraints);
+      return true;
+
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Given an F2A constraint of the form: F2A( typeFromFormalParameter, typeFromMethodArgument )
+   *
+   * <p>F2AReducingVisitor visits the constraint as follows: visit ( typeFromFormalParameter,
+   * typeFromMethodArgument, newConstraints )
+   *
+   * <p>The visit method will determine if the given constraint should either:
+   *
+   * <ul>
+   *   <li>be discarded -- in this case, the visitor just returns
+   *   <li>reduced to a simpler constraint or set of constraints -- in this case, the new constraint
+   *       or set of constraints is added to newConstraints
+   * </ul>
+   *
+   * Sprinkled throughout this class are comments of the form:
+   *
+   * <pre>{@code
+   * // If F has the form G<..., Yk-1, ? super U, Yk+1, ...>, where U involves Tj
+   * }</pre>
+   *
+   * These are excerpts from the JLS, if you search for them you will find the corresponding JLS
+   * description of the case being covered.
+   */
+  private static class F2AReducingVisitor extends AFReducingVisitor {
+
+    public F2AReducingVisitor(AnnotatedTypeFactory typeFactory) {
+      super(F2A.class, typeFactory);
+    }
+
+    @Override
+    public AFConstraint makeConstraint(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) {
+      return new F2A(subtype, supertype);
+    }
+
+    @Override
+    public AFConstraint makeInverseConstraint(
+        AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) {
+      return new A2F(subtype, supertype);
+    }
+
+    @Override
+    public AFConstraint makeEqualityConstraint(
+        AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) {
+      return new FIsA(subtype, supertype);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsA.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsA.java
new file mode 100644
index 0000000..dbfec18
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsA.java
@@ -0,0 +1,33 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+
+/**
+ * A constraint of the form: F = A or A = F
+ *
+ * @see org.checkerframework.framework.util.typeinference.constraint.AFConstraint
+ */
+public class FIsA extends AFConstraint {
+
+  /** Create a constraint with an argument equal to a formal. */
+  public FIsA(AnnotatedTypeMirror parameter, AnnotatedTypeMirror argument) {
+    super(argument, parameter);
+  }
+
+  @Override
+  public TUConstraint toTUConstraint() {
+    return new TIsU((AnnotatedTypeVariable) formalParameter, argument, true);
+  }
+
+  @Override
+  protected FIsA construct(
+      AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter) {
+    return new FIsA(newFormalParameter, newArgument);
+  }
+
+  @Override
+  public String toString() {
+    return "FisA( " + formalParameter + " = " + argument + " )";
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsAReducer.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsAReducer.java
new file mode 100644
index 0000000..b26e047
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsAReducer.java
@@ -0,0 +1,257 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
+import org.checkerframework.framework.type.visitor.AbstractAtmComboVisitor;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * FIsAReducer takes an FIsA constraint that is not irreducible (@see AFConstraint.isIrreducible)
+ * and reduces it by one step. The resulting constraint may still be reducible.
+ *
+ * <p>Generally reductions should map to corresponding rules in
+ * https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2.7
+ */
+public class FIsAReducer implements AFReducer {
+
+  protected final FIsAReducingVisitor visitor;
+  private final AnnotatedTypeFactory typeFactory;
+
+  public FIsAReducer(final AnnotatedTypeFactory typeFactory) {
+    this.typeFactory = typeFactory;
+    this.visitor = new FIsAReducingVisitor();
+  }
+
+  @Override
+  public boolean reduce(AFConstraint constraint, Set<AFConstraint> newConstraints) {
+    if (constraint instanceof FIsA) {
+      final FIsA fIsA = (FIsA) constraint;
+      visitor.visit(fIsA.formalParameter, fIsA.argument, newConstraints);
+      return true;
+
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Given an FIsA constraint of the form: FIsA( typeFromFormalParameter, typeFromMethodArgument )
+   *
+   * <p>FIsAReducingVisitor visits the constraint as follows: visit ( typeFromFormalParameter,
+   * typeFromMethodArgument, newConstraints )
+   *
+   * <p>The visit method will determine if the given constraint should either:
+   *
+   * <ul>
+   *   <li>be discarded -- in this case, the visitor just returns
+   *   <li>reduced to a simpler constraint or set of constraints -- in this case, the new constraint
+   *       or set of constraints is added to newConstraints
+   * </ul>
+   *
+   * From the JLS, in general there are 2 rules that govern F = A constraints: If F = Tj, then the
+   * constraint Tj = A is implied. If F = U[], where the type U involves Tj, then if A is an array
+   * type V[], or a type variable with an upper bound that is an array type V[], where V is a
+   * reference type, this algorithm is applied recursively to the constraint {@code V >> U}.
+   * Otherwise, no constraint is implied on Tj.
+   *
+   * <p>Since both F and A may have component types this visitor delves into their components and
+   * applies these rules to the components. However, only one step is taken at a time (i.e. this is
+   * not a scanner)
+   */
+  private class FIsAReducingVisitor extends AbstractAtmComboVisitor<Void, Set<AFConstraint>> {
+    @Override
+    protected String defaultErrorMessage(
+        AnnotatedTypeMirror argument,
+        AnnotatedTypeMirror parameter,
+        Set<AFConstraint> afConstraints) {
+      return StringsPlume.joinLines(
+          "Unexpected FIsA Combination:",
+          "argument=" + argument,
+          "parameter=" + parameter,
+          "constraints=[",
+          StringsPlume.join(", ", afConstraints),
+          "]");
+    }
+    // ------------------------------------------------------------------------
+    // Arrays as arguments
+
+    @Override
+    public Void visitArray_Array(
+        AnnotatedArrayType parameter, AnnotatedArrayType argument, Set<AFConstraint> constraints) {
+      constraints.add(new FIsA(parameter.getComponentType(), argument.getComponentType()));
+      return null;
+    }
+
+    @Override
+    public Void visitArray_Declared(
+        AnnotatedArrayType parameter,
+        AnnotatedDeclaredType argument,
+        Set<AFConstraint> constraints) {
+      return null;
+    }
+
+    @Override
+    public Void visitArray_Null(
+        AnnotatedArrayType parameter, AnnotatedNullType argument, Set<AFConstraint> constraints) {
+      return null;
+    }
+
+    @Override
+    public Void visitArray_Wildcard(
+        AnnotatedArrayType parameter,
+        AnnotatedWildcardType argument,
+        Set<AFConstraint> constraints) {
+      constraints.add(new FIsA(parameter, argument.getExtendsBound()));
+      return null;
+    }
+
+    // ------------------------------------------------------------------------
+    // Declared as argument
+    @Override
+    public Void visitDeclared_Array(
+        AnnotatedDeclaredType parameter,
+        AnnotatedArrayType argument,
+        Set<AFConstraint> constraints) {
+      // should this be Array<String> - T[] the new A2F(String, T)
+      return null;
+    }
+
+    @Override
+    public Void visitDeclared_Declared(
+        AnnotatedDeclaredType parameter,
+        AnnotatedDeclaredType argument,
+        Set<AFConstraint> constraints) {
+      if (argument.wasRaw() || parameter.wasRaw()) {
+        return null;
+      }
+
+      AnnotatedDeclaredType argumentAsParam =
+          AnnotatedTypes.castedAsSuper(typeFactory, argument, parameter);
+      if (argumentAsParam == null) {
+        return null;
+      }
+
+      final List<AnnotatedTypeMirror> argTypeArgs = argumentAsParam.getTypeArguments();
+      final List<AnnotatedTypeMirror> paramTypeArgs = parameter.getTypeArguments();
+      for (int i = 0; i < argTypeArgs.size(); i++) {
+        final AnnotatedTypeMirror argTypeArg = argTypeArgs.get(i);
+        final AnnotatedTypeMirror paramTypeArg = paramTypeArgs.get(i);
+
+        if (paramTypeArg.getKind() == TypeKind.WILDCARD) {
+          final AnnotatedWildcardType paramWc = (AnnotatedWildcardType) paramTypeArg;
+
+          if (argTypeArg.getKind() == TypeKind.WILDCARD) {
+            final AnnotatedWildcardType argWc = (AnnotatedWildcardType) argTypeArg;
+            constraints.add(new FIsA(paramWc.getExtendsBound(), argWc.getExtendsBound()));
+            constraints.add(new FIsA(paramWc.getSuperBound(), argWc.getSuperBound()));
+          }
+
+        } else {
+          constraints.add(new FIsA(paramTypeArgs.get(i), argTypeArgs.get(i)));
+        }
+      }
+
+      return null;
+    }
+
+    @Override
+    public Void visitDeclared_Null(
+        AnnotatedDeclaredType parameter,
+        AnnotatedNullType argument,
+        Set<AFConstraint> constraints) {
+      return null;
+    }
+
+    @Override
+    public Void visitDeclared_Primitive(
+        AnnotatedDeclaredType parameter,
+        AnnotatedPrimitiveType argument,
+        Set<AFConstraint> constraints) {
+      return null;
+    }
+
+    @Override
+    public Void visitDeclared_Union(
+        AnnotatedDeclaredType parameter,
+        AnnotatedUnionType argument,
+        Set<AFConstraint> constraints) {
+      return null; // TODO: NOT SUPPORTED AT THE MOMENT
+    }
+
+    @Override
+    public Void visitIntersection_Intersection(
+        AnnotatedIntersectionType parameter,
+        AnnotatedIntersectionType argument,
+        Set<AFConstraint> constraints) {
+      return null; // TODO: NOT SUPPORTED AT THE MOMENT
+    }
+
+    @Override
+    public Void visitIntersection_Null(
+        AnnotatedIntersectionType parameter,
+        AnnotatedNullType argument,
+        Set<AFConstraint> constraints) {
+      return null;
+    }
+
+    @Override
+    public Void visitNull_Null(
+        AnnotatedNullType parameter, AnnotatedNullType argument, Set<AFConstraint> afConstraints) {
+      // we sometimes get these when we have captured types passed as arguments
+      // regardless they don't give any information
+      return null;
+    }
+
+    // ------------------------------------------------------------------------
+    // Primitive as argument
+    @Override
+    public Void visitPrimitive_Declared(
+        AnnotatedPrimitiveType parameter,
+        AnnotatedDeclaredType argument,
+        Set<AFConstraint> constraints) {
+      // we may be able to eliminate this case, since I believe the corresponding constraint
+      // will just be discarded as the parameter must be a boxed primitive
+      constraints.add(new FIsA(typeFactory.getBoxedType(parameter), argument));
+      return null;
+    }
+
+    @Override
+    public Void visitPrimitive_Primitive(
+        AnnotatedPrimitiveType parameter,
+        AnnotatedPrimitiveType argument,
+        Set<AFConstraint> constraints) {
+      return null;
+    }
+
+    @Override
+    public Void visitTypevar_Typevar(
+        AnnotatedTypeVariable parameter,
+        AnnotatedTypeVariable argument,
+        Set<AFConstraint> constraints) {
+      // if we've reached this point and the two are corresponding type variables, then they
+      // are NOT ones that may have a type variable we are inferring types for and therefore
+      // we can discard this constraint
+      return null;
+    }
+
+    @Override
+    public Void visitTypevar_Null(
+        AnnotatedTypeVariable argument,
+        AnnotatedNullType parameter,
+        Set<AFConstraint> constraints) {
+      return null;
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TIsU.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TIsU.java
new file mode 100644
index 0000000..9343baf
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TIsU.java
@@ -0,0 +1,25 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+
+/**
+ * A constraint of the form: T = U
+ *
+ * @see org.checkerframework.framework.util.typeinference.constraint.TUConstraint
+ */
+public class TIsU extends TUConstraint {
+  public TIsU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType) {
+    this(typeVariable, relatedType, false);
+  }
+
+  /** Create a constraint with a variable equal to a type. */
+  public TIsU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) {
+    super(typeVariable, relatedType, uIsArg);
+  }
+
+  @Override
+  public String toString() {
+    return "TIsU( " + typeVariable + ", " + relatedType + " )";
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSubU.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSubU.java
new file mode 100644
index 0000000..9e5bad5
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSubU.java
@@ -0,0 +1,26 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+
+/**
+ * A constraint of the form: {@code T <: U}
+ *
+ * @see org.checkerframework.framework.util.typeinference.constraint.TUConstraint
+ */
+public class TSubU extends TUConstraint {
+  public TSubU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType) {
+    this(typeVariable, relatedType, false);
+  }
+
+  /** Create a constraint with a variable less than a type. */
+  public TSubU(
+      AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) {
+    super(typeVariable, relatedType, uIsArg);
+  }
+
+  @Override
+  public String toString() {
+    return "TSubU( " + typeVariable + " <: " + relatedType + " )";
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSuperU.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSuperU.java
new file mode 100644
index 0000000..44703ca
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSuperU.java
@@ -0,0 +1,26 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+
+/**
+ * A constraint of the form: {@code T :> U}
+ *
+ * @see org.checkerframework.framework.util.typeinference.constraint.TUConstraint
+ */
+public class TSuperU extends TUConstraint {
+  public TSuperU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType) {
+    this(typeVariable, relatedType, false);
+  }
+
+  /** Create a constraint with a variable greater than a type. */
+  public TSuperU(
+      AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) {
+    super(typeVariable, relatedType, uIsArg);
+  }
+
+  @Override
+  public String toString() {
+    return "TSuperU( " + typeVariable + " :> " + relatedType + " )";
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TUConstraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TUConstraint.java
new file mode 100644
index 0000000..8595cb9
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TUConstraint.java
@@ -0,0 +1,84 @@
+package org.checkerframework.framework.util.typeinference.constraint;
+
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
+
+/**
+ * Subclasses of TUConstraint represent constraints between a type parameter, whose type arguments
+ * are being inferred, and the types used to do that inference. These constraints are used by the
+ * TASolver to infer arguments.
+ *
+ * <p>TU constraints come in the classic form of subtype, supertype, and equality constraints.<br>
+ *
+ * <ul>
+ *   <li>{@code T <: U} -- implies T is a subtype of U, it is represented by TSubU <br>
+ *   <li>{@code T >: U} -- implies T is a supertype of U, it is represented by TSuperU <br>
+ *   <li>{@code T = U} -- implies T is equal to U, it is represented by TIsU <br>
+ * </ul>
+ *
+ * <p>Note, it is important that the type parameter is represented by an AnnotatedTypeVariable
+ * because if a use of the type parameter has a primary annotation, then the two types represented
+ * in by a TUConstraint are NOT constrained in the hierarchy of that annotation. e.g.
+ *
+ * <pre>{@code
+ * <T> void method(List<@NonNull T> t1, T t2)
+ * method(new ArrayList<@NonNull String>(), null);
+ * }</pre>
+ *
+ * The above method call would eventually be reduced to constraints: {@code [@NonNull String
+ * == @NonNull T, @Nullable null <: T]}
+ *
+ * <p>In this example, if we did not ignore the first constraint then the type argument would be
+ * exactly @NonNull String and the second argument would be invalid. However, the correct inference
+ * would be @Nullable String and both arguments would be valid.
+ */
+public abstract class TUConstraint {
+  /**
+   * An AnnotatedTypeVariable representing a target type parameter for which we are inferring a type
+   * argument. This is the T in the TUConstraints.
+   */
+  public final AnnotatedTypeVariable typeVariable;
+
+  /**
+   * A type used to infer an argument for the typeVariable T. This would be the U in the
+   * TUConstraints.
+   */
+  public final AnnotatedTypeMirror relatedType;
+
+  /** Whether or not U is a type from an argument to the method. */
+  public final boolean uIsArg;
+
+  protected TUConstraint(
+      final AnnotatedTypeVariable typeVariable,
+      final AnnotatedTypeMirror relatedType,
+      boolean uIsArg) {
+    this.typeVariable = typeVariable;
+    this.relatedType = relatedType;
+    this.uIsArg = uIsArg;
+
+    TypeArgInferenceUtil.checkForUninferredTypes(relatedType);
+  }
+
+  @Override
+  public boolean equals(@Nullable Object thatObject) {
+    if (this == thatObject) {
+      return true;
+    } // else
+
+    if (thatObject == null || this.getClass() != thatObject.getClass()) {
+      return false;
+    }
+
+    final TUConstraint that = (TUConstraint) thatObject;
+
+    return this.typeVariable.equals(that.typeVariable) && this.relatedType.equals(that.relatedType);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(this.getClass(), typeVariable, relatedType);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMap.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMap.java
new file mode 100644
index 0000000..b0ed7c8
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMap.java
@@ -0,0 +1,196 @@
+package org.checkerframework.framework.util.typeinference.solver;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Equalities;
+import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Subtypes;
+import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Supertypes;
+
+/**
+ * ConstraintMap holds simplified versions of the TUConstraints for ALL type variable for which we
+ * are inferring an argument. The ConstraintMap is edited on the fly as the various solvers work
+ * (unlike the AF/TU Constraints which are immutable).
+ *
+ * <p>This really consists of these things:
+ *
+ * <ol>
+ *   <li>a Map({@code target => constraints for target})
+ *   <li>Methods to easily build up the constraints in the map
+ *   <li>A getter for the constraints of individual targets.
+ * </ol>
+ *
+ * Note: This class, along with TargetConstraints, uses a lot of mutable state and few
+ * setters/getters be careful. This choice was made as it makes the resulting code more readable.
+ */
+public class ConstraintMap {
+
+  private final Map<TypeVariable, TargetConstraints> targetToRecords = new LinkedHashMap<>();
+
+  public ConstraintMap(Set<TypeVariable> targets) {
+    for (final TypeVariable target : targets) {
+      targetToRecords.put(target, new TargetConstraints(target));
+    }
+  }
+
+  public ConstraintMap(final ConstraintMap toCopy) {
+    this.targetToRecords.putAll(toCopy.targetToRecords);
+  }
+
+  /** Gets the equality, subtypes, and supertypes constraints for a particular target. */
+  public TargetConstraints getConstraints(final TypeVariable target) {
+    return targetToRecords.get(target);
+  }
+
+  /**
+   * Returns the set of all targets passed to the constructor of this constraint map (a target will
+   * appear in this list whether or not it has any constraints added).
+   *
+   * @return the set of all targets passed to the constructor of this constraint map (a target will
+   *     appear in this list whether or not it has any constraints added)
+   */
+  public Set<TypeVariable> getTargets() {
+    return targetToRecords.keySet();
+  }
+
+  /**
+   * Add a constraint indicating that the equivalent is equal to target in the given qualifier
+   * hierarchies.
+   */
+  public void addTargetEquality(
+      final TypeVariable target, final TypeVariable equivalent, AnnotationMirrorSet hierarchies) {
+    final Equalities equalities = targetToRecords.get(target).equalities;
+    final AnnotationMirrorSet equivalentTops =
+        equalities.targets.computeIfAbsent(equivalent, __ -> new AnnotationMirrorSet());
+    equivalentTops.addAll(hierarchies);
+  }
+
+  /**
+   * Add a constraint indicating that target has primary annotations equal to the given annotations.
+   */
+  public void addPrimaryEqualities(
+      final TypeVariable target,
+      QualifierHierarchy qualHierarchy,
+      final AnnotationMirrorSet annos) {
+    final Equalities equalities = targetToRecords.get(target).equalities;
+
+    for (final AnnotationMirror anno : annos) {
+      final AnnotationMirror top = qualHierarchy.getTopAnnotation(anno);
+      if (!equalities.primaries.containsKey(top)) {
+        equalities.primaries.put(top, anno);
+      }
+    }
+  }
+
+  /**
+   * Add a constraint indicating that target is a supertype of subtype in the given qualifier
+   * hierarchies.
+   *
+   * @param hierarchies a set of TOP annotations
+   */
+  public void addTargetSupertype(
+      final TypeVariable target, final TypeVariable subtype, AnnotationMirrorSet hierarchies) {
+    final Supertypes supertypes = targetToRecords.get(target).supertypes;
+    final AnnotationMirrorSet supertypeTops =
+        supertypes.targets.computeIfAbsent(subtype, __ -> new AnnotationMirrorSet());
+    supertypeTops.addAll(hierarchies);
+  }
+
+  /**
+   * Add a constraint indicating that target is a supertype of subtype in the given qualifier
+   * hierarchies.
+   *
+   * @param hierarchies a set of TOP annotations
+   */
+  public void addTypeSupertype(
+      final TypeVariable target,
+      final AnnotatedTypeMirror subtype,
+      AnnotationMirrorSet hierarchies) {
+    final Supertypes supertypes = targetToRecords.get(target).supertypes;
+    final AnnotationMirrorSet supertypeTops =
+        supertypes.types.computeIfAbsent(subtype, __ -> new AnnotationMirrorSet());
+    supertypeTops.addAll(hierarchies);
+  }
+
+  /**
+   * Add a constraint indicating that target's primary annotations are subtypes of the given
+   * annotations.
+   */
+  public void addPrimarySupertype(
+      final TypeVariable target,
+      QualifierHierarchy qualifierHierarchy,
+      final AnnotationMirrorSet annos) {
+    final Supertypes supertypes = targetToRecords.get(target).supertypes;
+    for (final AnnotationMirror anno : annos) {
+      final AnnotationMirror top = qualifierHierarchy.getTopAnnotation(anno);
+      AnnotationMirrorSet entries =
+          supertypes.primaries.computeIfAbsent(top, __ -> new AnnotationMirrorSet());
+      entries.add(anno);
+    }
+  }
+
+  /**
+   * Add a constraint indicating that target is a subtype of supertype in the given qualifier
+   * hierarchies.
+   *
+   * @param hierarchies a set of TOP annotations
+   */
+  public void addTargetSubtype(
+      final TypeVariable target, final TypeVariable supertype, AnnotationMirrorSet hierarchies) {
+    final Subtypes subtypes = targetToRecords.get(target).subtypes;
+    final AnnotationMirrorSet subtypesTops =
+        subtypes.targets.computeIfAbsent(supertype, __ -> new AnnotationMirrorSet());
+    subtypesTops.addAll(hierarchies);
+  }
+
+  /**
+   * Add a constraint indicating that target is a subtype of supertype in the given qualifier
+   * hierarchies.
+   *
+   * @param hierarchies a set of TOP annotations
+   */
+  public void addTypeSubtype(
+      final TypeVariable target,
+      final AnnotatedTypeMirror supertype,
+      AnnotationMirrorSet hierarchies) {
+    final Subtypes subtypes = targetToRecords.get(target).subtypes;
+    final AnnotationMirrorSet subtypesTops =
+        subtypes.types.computeIfAbsent(supertype, __ -> new AnnotationMirrorSet());
+    subtypesTops.addAll(hierarchies);
+  }
+
+  /**
+   * Add a constraint indicating that target's primary annotations are subtypes of the given
+   * annotations.
+   */
+  public void addPrimarySubtypes(
+      final TypeVariable target,
+      QualifierHierarchy qualifierHierarchy,
+      final AnnotationMirrorSet annos) {
+    final Subtypes subtypes = targetToRecords.get(target).subtypes;
+    for (final AnnotationMirror anno : annos) {
+      final AnnotationMirror top = qualifierHierarchy.getTopAnnotation(anno);
+      AnnotationMirrorSet entries =
+          subtypes.primaries.computeIfAbsent(top, __ -> new AnnotationMirrorSet());
+      entries.add(anno);
+    }
+  }
+
+  /**
+   * Add a constraint indicating that target is equal to type in the given hierarchies.
+   *
+   * @param hierarchies a set of TOP annotations
+   */
+  public void addTypeEqualities(
+      TypeVariable target, AnnotatedTypeMirror type, AnnotationMirrorSet hierarchies) {
+    final Equalities equalities = targetToRecords.get(target).equalities;
+    final AnnotationMirrorSet equalityTops =
+        equalities.types.computeIfAbsent(type, __ -> new AnnotationMirrorSet());
+    equalityTops.addAll(hierarchies);
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMapBuilder.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMapBuilder.java
new file mode 100644
index 0000000..3fcc5f9
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMapBuilder.java
@@ -0,0 +1,207 @@
+package org.checkerframework.framework.util.typeinference.solver;
+
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+import org.checkerframework.framework.util.typeinference.constraint.TIsU;
+import org.checkerframework.framework.util.typeinference.constraint.TSuperU;
+import org.checkerframework.framework.util.typeinference.constraint.TUConstraint;
+import org.checkerframework.javacutil.TypeAnnotationUtils;
+
+/** Converts a set of TUConstraints into a ConstraintMap. */
+public class ConstraintMapBuilder {
+
+  /**
+   * Let Ti be a the ith target being inferred Let ATV(i) be the annotated type variable that
+   * represents as use of Ti which may or may not have primary annotations. Let ATM be an annotated
+   * type mirror that may or may not be target Tx, or have a component target Tx Let Ai be the type
+   * argument we are trying to infer for Ti
+   *
+   * <p>We have a set of constraints of the form: {@code ATV(i) <?> ATM}
+   *
+   * <p>Where {@code <?>} is either a subtype ({@code <:}), supertype ({@code :>}), or equality
+   * relationship ({@code =}).
+   *
+   * <p>Regardless of what {@code <?>} is, a constraint will only imply constraints on Ai in a given
+   * hierarchy if ATV(i) does NOT have a primary annotation in that hierarchy. That is:
+   *
+   * <p>E.g. Let ATV(i) be @NonNull Ti, the constraints @NonNull Ti = @NonNull @Initialized String
+   * does not imply any primary annotation in the Nullness hierarchy for type argument Ai because
+   * the Annotated type mirror has a primary annotation in the NUllness hierarchy.
+   *
+   * <p>However, it does imply that Ai has a primary annotation of @Initialized since ATV(i) has no
+   * primary annotation in the initialization hierarchy.
+   *
+   * <p>Note, constraints come in 2 forms:
+   *
+   * <ul>
+   *   <li>between a target and a concrete AnnotatedTypeMirror. E.g., As seen above {@code (@NonNull
+   *       Ti = @NonNull @Initialized String)}
+   *   <li>between two targets E.g., {@code (@NonNull Ti = Tj)}
+   * </ul>
+   */
+  public ConstraintMap build(
+      Set<TypeVariable> targets, Set<TUConstraint> constraints, AnnotatedTypeFactory typeFactory) {
+
+    final QualifierHierarchy qualifierHierarchy = typeFactory.getQualifierHierarchy();
+    final AnnotationMirrorSet tops =
+        new AnnotationMirrorSet(qualifierHierarchy.getTopAnnotations());
+    final ConstraintMap result = new ConstraintMap(targets);
+
+    final AnnotationMirrorSet tAnnos = new AnnotationMirrorSet();
+    final AnnotationMirrorSet uAnnos = new AnnotationMirrorSet();
+    final AnnotationMirrorSet hierarchiesInRelation = new AnnotationMirrorSet();
+
+    for (TUConstraint constraint : constraints) {
+      tAnnos.clear();
+      uAnnos.clear();
+      hierarchiesInRelation.clear();
+
+      final AnnotatedTypeVariable typeT = constraint.typeVariable;
+      final AnnotatedTypeMirror typeU = constraint.relatedType;
+
+      // If typeU is from an argument to the method, then treat typeU as an ordinary type even
+      // if it is a target type variable.  This is for the case where the inferred type is the
+      // declared type parameter.  For example,
+      // public <T> T get(T t) {
+      //   return this.get(t);
+      // }
+      // The inferred type of T should be T.
+      if (!constraint.uIsArg
+          && typeU.getKind() == TypeKind.TYPEVAR
+          && targets.contains(
+              (TypeVariable) TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()))) {
+        if (typeT.getAnnotations().isEmpty() && typeU.getAnnotations().isEmpty()) {
+          hierarchiesInRelation.addAll(tops);
+
+        } else {
+
+          for (AnnotationMirror top : tops) {
+            final AnnotationMirror tAnno = typeT.getAnnotationInHierarchy(top);
+            final AnnotationMirror uAnno = typeU.getAnnotationInHierarchy(top);
+
+            if (tAnno == null) {
+              if (uAnno == null) {
+                hierarchiesInRelation.add(top);
+
+              } else {
+                tAnnos.add(uAnno);
+              }
+            } else {
+              if (uAnno == null) {
+                uAnnos.add(tAnno);
+
+              } else {
+                // This tells us nothing, they both should be equal but either way
+                // we gain no information if both type vars have annotations
+              }
+            }
+          }
+
+          // If we have a case where Ti = @NonNull Tj we know that for the @Initialization hierarchy
+          // Ti = TJ and we know that for the @Nullable hierarchy Ti = @NonNull <some other type>.
+          // This step saves @NonNull annotation.
+          // This case also covers the case where i = j.
+          if (!tAnnos.isEmpty()) {
+            addToPrimaryRelationship(
+                (TypeVariable) TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()),
+                constraint,
+                result,
+                tAnnos,
+                qualifierHierarchy);
+          }
+
+          if (!uAnnos.isEmpty()) {
+            addToPrimaryRelationship(
+                (TypeVariable) TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()),
+                constraint,
+                result,
+                uAnnos,
+                qualifierHierarchy);
+          }
+        }
+
+        // This is the case where we have a relationship between two different targets (Ti
+        // <?> Tj and i != j)
+        if (!typeFactory.types.isSameType(
+            TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()),
+            TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()))) {
+          addToTargetRelationship(
+              (TypeVariable) TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()),
+              (TypeVariable) TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()),
+              result,
+              constraint,
+              hierarchiesInRelation);
+        }
+      } else {
+        for (AnnotationMirror top : tops) {
+          final AnnotationMirror tAnno = typeT.getAnnotationInHierarchy(top);
+
+          if (tAnno == null) {
+            hierarchiesInRelation.add(top);
+          }
+        }
+
+        addToTypeRelationship(
+            (TypeVariable) TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()),
+            typeU,
+            result,
+            constraint,
+            hierarchiesInRelation);
+      }
+    }
+
+    return result;
+  }
+
+  private void addToTargetRelationship(
+      TypeVariable typeT,
+      TypeVariable typeU,
+      ConstraintMap result,
+      TUConstraint constraint,
+      AnnotationMirrorSet hierarchiesInRelation) {
+    if (constraint instanceof TIsU) {
+      result.addTargetEquality(typeT, typeU, hierarchiesInRelation);
+    } else if (constraint instanceof TSuperU) {
+      result.addTargetSupertype(typeT, typeU, hierarchiesInRelation);
+    } else {
+      result.addTargetSubtype(typeT, typeU, hierarchiesInRelation);
+    }
+  }
+
+  public void addToPrimaryRelationship(
+      TypeVariable typeVariable,
+      TUConstraint constraint,
+      ConstraintMap result,
+      AnnotationMirrorSet annotationMirrors,
+      QualifierHierarchy qualifierHierarchy) {
+    if (constraint instanceof TIsU) {
+      result.addPrimaryEqualities(typeVariable, qualifierHierarchy, annotationMirrors);
+    } else if (constraint instanceof TSuperU) {
+      result.addPrimarySupertype(typeVariable, qualifierHierarchy, annotationMirrors);
+    } else {
+      result.addPrimarySubtypes(typeVariable, qualifierHierarchy, annotationMirrors);
+    }
+  }
+
+  public void addToTypeRelationship(
+      TypeVariable target,
+      AnnotatedTypeMirror type,
+      ConstraintMap result,
+      TUConstraint constraint,
+      AnnotationMirrorSet hierarchies) {
+    if (constraint instanceof TIsU) {
+      result.addTypeEqualities(target, type, hierarchies);
+    } else if (constraint instanceof TSuperU) {
+      result.addTypeSupertype(target, type, hierarchies);
+    } else {
+      result.addTypeSubtype(target, type, hierarchies);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java
new file mode 100644
index 0000000..7067fd2
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java
@@ -0,0 +1,481 @@
+package org.checkerframework.framework.util.typeinference.solver;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeVariable;
+import org.checkerframework.checker.interning.qual.FindDistinct;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.util.AnnotationMirrorMap;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
+import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredTarget;
+import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType;
+import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Equalities;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * EqualitiesSolver infers type arguments for targets using the equality constraints in
+ * ConstraintMap. When a type is inferred, it rewrites the remaining equality/supertype constraints
+ */
+public class EqualitiesSolver {
+  private boolean dirty = false;
+
+  /**
+   * For each target, if there is one or more equality constraints involving concrete types that
+   * lets us infer a primary annotation in all qualifier hierarchies then infer a concrete type
+   * argument. else if there is one or more equality constraints involving other targets that lets
+   * us infer a primary annotation in all qualifier hierarchies then infer that type argument is the
+   * other type argument
+   *
+   * <p>if we have inferred either a concrete type or another target as type argument rewrite all of
+   * the constraints for the current target to instead use the inferred type/target
+   *
+   * <p>We do this iteratively until NO new inferred type argument is found
+   *
+   * @param targets the list of type parameters for which we are inferring type arguments
+   * @param constraintMap the set of constraints over the set of targets
+   * @return a Map from target to (inferred type or target)
+   */
+  public InferenceResult solveEqualities(
+      Set<TypeVariable> targets, ConstraintMap constraintMap, AnnotatedTypeFactory typeFactory) {
+    final InferenceResult solution = new InferenceResult();
+
+    do {
+      dirty = false;
+      for (TypeVariable target : targets) {
+        if (solution.containsKey(target)) {
+          continue;
+        }
+
+        Equalities equalities = constraintMap.getConstraints(target).equalities;
+
+        InferredValue inferred =
+            mergeConstraints(target, equalities, solution, constraintMap, typeFactory);
+        if (inferred != null) {
+          if (inferred instanceof InferredType) {
+            rewriteWithInferredType(target, ((InferredType) inferred).type, constraintMap);
+          } else {
+            rewriteWithInferredTarget(
+                target, ((InferredTarget) inferred).target, constraintMap, typeFactory);
+          }
+
+          solution.put(target, inferred);
+        }
+      }
+
+    } while (dirty);
+
+    solution.resolveChainedTargets();
+
+    return solution;
+  }
+
+  /**
+   * Let Ti be a target type parameter. When we reach this method we have inferred an argument, Ai,
+   * for Ti.
+   *
+   * <p>However, there still may be constraints of the form {@literal Ti = Tj}, {@literal Ti <: Tj},
+   * {@literal Tj <: Ti} in the constraint map. In this case we need to replace Ti with the type.
+   * That is, they become {@literal Ai = Tj}, {@literal Ai <: Tj}, and {@literal Tj <: Ai}
+   *
+   * <p>To do this, we find the TargetConstraints for Tj and add these constraints to the
+   * appropriate map in TargetConstraints. We can then clear the constraints for the current target
+   * since we have inferred a type.
+   *
+   * @param target the target for which we have inferred a concrete type argument
+   * @param type the type inferred
+   * @param constraints the constraints that are side-effected by this method
+   */
+  private void rewriteWithInferredType(
+      final @FindDistinct TypeVariable target,
+      final AnnotatedTypeMirror type,
+      final ConstraintMap constraints) {
+
+    final TargetConstraints targetRecord = constraints.getConstraints(target);
+    final Map<TypeVariable, AnnotationMirrorSet> equivalentTargets =
+        targetRecord.equalities.targets;
+    // each target that was equivalent to this one needs to be equivalent in the same
+    // hierarchies as the inferred type
+    for (final Map.Entry<TypeVariable, AnnotationMirrorSet> eqEntry :
+        equivalentTargets.entrySet()) {
+      constraints.addTypeEqualities(eqEntry.getKey(), type, eqEntry.getValue());
+    }
+
+    for (TypeVariable otherTarget : constraints.getTargets()) {
+      if (otherTarget != target) {
+        final TargetConstraints record = constraints.getConstraints(otherTarget);
+
+        // each target that was equivalent to this one needs to be equivalent in the same
+        // hierarchies as the inferred type
+        final AnnotationMirrorSet hierarchies = record.equalities.targets.get(target);
+        if (hierarchies != null) {
+          record.equalities.targets.remove(target);
+          constraints.addTypeEqualities(otherTarget, type, hierarchies);
+        }
+
+        // otherTypes may have AnnotatedTypeVariables of type target, run substitution on
+        // these with type
+        Map<AnnotatedTypeMirror, AnnotationMirrorSet> toIterate =
+            new LinkedHashMap<>(record.equalities.types);
+        record.equalities.types.clear();
+        for (AnnotatedTypeMirror otherType : toIterate.keySet()) {
+          final AnnotatedTypeMirror copy = TypeArgInferenceUtil.substitute(target, type, otherType);
+          final AnnotationMirrorSet otherHierarchies = toIterate.get(otherType);
+          record.equalities.types.put(copy, otherHierarchies);
+        }
+      }
+    }
+
+    for (TypeVariable otherTarget : constraints.getTargets()) {
+      if (otherTarget != target) {
+        final TargetConstraints record = constraints.getConstraints(otherTarget);
+
+        // each target that was equivalent to this one needs to be equivalent in the same
+        // hierarchies as the inferred type
+        final AnnotationMirrorSet hierarchies = record.supertypes.targets.get(target);
+        if (hierarchies != null) {
+          record.supertypes.targets.remove(target);
+          constraints.addTypeEqualities(otherTarget, type, hierarchies);
+        }
+
+        // otherTypes may have AnnotatedTypeVariables of type target, run substitution on
+        // these with type
+        Map<AnnotatedTypeMirror, AnnotationMirrorSet> toIterate =
+            new LinkedHashMap<>(record.supertypes.types);
+        record.supertypes.types.clear();
+        for (AnnotatedTypeMirror otherType : toIterate.keySet()) {
+          final AnnotatedTypeMirror copy = TypeArgInferenceUtil.substitute(target, type, otherType);
+          final AnnotationMirrorSet otherHierarchies = toIterate.get(otherType);
+          record.supertypes.types.put(copy, otherHierarchies);
+        }
+      }
+    }
+
+    targetRecord.equalities.clear();
+    targetRecord.supertypes.clear();
+  }
+
+  /**
+   * Let Ti be a target type parameter. When we reach this method we have inferred that Ti has the
+   * exact same argument as another target Tj
+   *
+   * <p>Therefore, we want to stop solving for Ti and instead wait till we solve for Tj and use that
+   * result.
+   *
+   * <p>Let ATM be any annotated type mirror and Tk be a target type parameter where k != i and k !=
+   * j Even though we've inferred Ti = Tj, there still may be constraints of the form Ti = ATM or
+   * {@literal Ti <: Tk} These constraints are still useful for inferring a argument for Ti/Tj. So,
+   * we replace Ti in these constraints with Tj and place those constraints in the TargetConstraints
+   * object for Tj.
+   *
+   * <p>We then clear the constraints for Ti.
+   *
+   * @param target the target for which we know another target is exactly equal to this target
+   * @param inferredTarget the other target inferred to be equal
+   * @param constraints the constraints that are side-effected by this method
+   * @param typeFactory type factory
+   */
+  private void rewriteWithInferredTarget(
+      final @FindDistinct TypeVariable target,
+      final @FindDistinct TypeVariable inferredTarget,
+      final ConstraintMap constraints,
+      final AnnotatedTypeFactory typeFactory) {
+    final TargetConstraints targetRecord = constraints.getConstraints(target);
+    final Map<AnnotatedTypeMirror, AnnotationMirrorSet> equivalentTypes =
+        targetRecord.equalities.types;
+    final Map<AnnotatedTypeMirror, AnnotationMirrorSet> supertypes = targetRecord.supertypes.types;
+
+    // each type that was equivalent to this one needs to be equivalent in the same hierarchies
+    // to the inferred target
+    for (final Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> eqEntry :
+        equivalentTypes.entrySet()) {
+      constraints.addTypeEqualities(inferredTarget, eqEntry.getKey(), eqEntry.getValue());
+    }
+
+    for (final Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> superEntry :
+        supertypes.entrySet()) {
+      constraints.addTypeSupertype(inferredTarget, superEntry.getKey(), superEntry.getValue());
+    }
+
+    for (TypeVariable otherTarget : constraints.getTargets()) {
+      if (otherTarget != target && otherTarget != inferredTarget) {
+        final TargetConstraints record = constraints.getConstraints(otherTarget);
+
+        // each target that was equivalent to this one needs to be equivalent in the same
+        // hierarchies as the inferred target
+        final AnnotationMirrorSet hierarchies = record.equalities.targets.get(target);
+        if (hierarchies != null) {
+          record.equalities.targets.remove(target);
+          constraints.addTargetEquality(otherTarget, inferredTarget, hierarchies);
+        }
+
+        // otherTypes may have AnnotatedTypeVariables of type target, run substitution on
+        // these with type
+        Map<AnnotatedTypeMirror, AnnotationMirrorSet> toIterate =
+            new LinkedHashMap<>(record.equalities.types);
+        record.equalities.types.clear();
+        for (AnnotatedTypeMirror otherType : toIterate.keySet()) {
+          final AnnotatedTypeMirror copy =
+              TypeArgInferenceUtil.substitute(
+                  target, createAnnotatedTypeVar(target, typeFactory), otherType);
+          final AnnotationMirrorSet otherHierarchies = toIterate.get(otherType);
+          record.equalities.types.put(copy, otherHierarchies);
+        }
+      }
+    }
+
+    for (TypeVariable otherTarget : constraints.getTargets()) {
+      if (otherTarget != target && otherTarget != inferredTarget) {
+        final TargetConstraints record = constraints.getConstraints(otherTarget);
+
+        final AnnotationMirrorSet hierarchies = record.supertypes.targets.get(target);
+        if (hierarchies != null) {
+          record.supertypes.targets.remove(target);
+          constraints.addTargetSupertype(otherTarget, inferredTarget, hierarchies);
+        }
+
+        // otherTypes may have AnnotatedTypeVariables of type target, run substitution on
+        // these with type
+        Map<AnnotatedTypeMirror, AnnotationMirrorSet> toIterate =
+            new LinkedHashMap<>(record.supertypes.types);
+        record.supertypes.types.clear();
+        for (AnnotatedTypeMirror otherType : toIterate.keySet()) {
+          final AnnotatedTypeMirror copy =
+              TypeArgInferenceUtil.substitute(
+                  target, createAnnotatedTypeVar(target, typeFactory), otherType);
+          final AnnotationMirrorSet otherHierarchies = toIterate.get(otherType);
+          record.supertypes.types.put(copy, otherHierarchies);
+        }
+      }
+    }
+
+    targetRecord.equalities.clear();
+    targetRecord.supertypes.clear();
+  }
+
+  /** Creates a declaration AnnotatedTypeVariable for TypeVariable. */
+  private AnnotatedTypeVariable createAnnotatedTypeVar(
+      final TypeVariable typeVariable, final AnnotatedTypeFactory typeFactory) {
+    return (AnnotatedTypeVariable) typeFactory.getAnnotatedType(typeVariable.asElement());
+  }
+
+  /**
+   * Returns a concrete type argument or null if there was not enough information to infer one.
+   *
+   * @param typesToHierarchies a mapping of (types &rarr; hierarchies) that indicate that the
+   *     argument being inferred is equal to the types in each of the hierarchies
+   * @param primaries a map (hierarchy &rarr; annotation in hierarchy) where the annotation in
+   *     hierarchy is equal to the primary annotation on the argument being inferred
+   * @param tops the set of top annotations in the qualifier hierarchy
+   * @return a concrete type argument or null if there was not enough information to infer one
+   */
+  private InferredType mergeTypesAndPrimaries(
+      Map<AnnotatedTypeMirror, AnnotationMirrorSet> typesToHierarchies,
+      AnnotationMirrorMap<AnnotationMirror> primaries,
+      final AnnotationMirrorSet tops,
+      AnnotatedTypeFactory typeFactory) {
+    final AnnotationMirrorSet missingAnnos = new AnnotationMirrorSet(tops);
+
+    Iterator<Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet>> entryIterator =
+        typesToHierarchies.entrySet().iterator();
+    if (!entryIterator.hasNext()) {
+      throw new BugInCF("Merging a list of empty types.");
+    }
+
+    final Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> head = entryIterator.next();
+
+    AnnotatedTypeMirror mergedType = head.getKey();
+    missingAnnos.removeAll(head.getValue());
+
+    // 1. if there are multiple equality constraints in a ConstraintMap then the types better
+    // have the same underlying type or Javac will complain and we won't be here.  When building
+    // ConstraintMaps constraints involving AnnotatedTypeMirrors that are exactly equal are
+    // combined so there must be some difference between two types being merged here.
+    //
+    // 2. Otherwise, we might have the same underlying type but conflicting annotations, then we
+    // take the first set of annotations and show an error for the argument/return type that
+    // caused the second differing constraint.
+    //
+    // 3. Finally, we expect the following types to be involved in equality constraints:
+    // AnnotatedDeclaredTypes, AnnotatedTypeVariables, and AnnotatedArrayTypes
+    while (entryIterator.hasNext() && !missingAnnos.isEmpty()) {
+      final Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> current = entryIterator.next();
+      final AnnotatedTypeMirror currentType = current.getKey();
+      final AnnotationMirrorSet currentHierarchies = current.getValue();
+
+      AnnotationMirrorSet found = new AnnotationMirrorSet();
+      for (AnnotationMirror top : missingAnnos) {
+        if (currentHierarchies.contains(top)) {
+          final AnnotationMirror newAnno = currentType.getAnnotationInHierarchy(top);
+          if (newAnno != null) {
+            mergedType.replaceAnnotation(newAnno);
+            found.add(top);
+
+          } else if (mergedType.getKind() == TypeKind.TYPEVAR
+              && typeFactory.types.isSameType(
+                  currentType.getUnderlyingType(), mergedType.getUnderlyingType())) {
+            // the options here are we are merging with the same typevar, in which case
+            // we can just remove the annotation from the missing list
+            found.add(top);
+
+          } else {
+            // otherwise the other type is missing an annotation
+            throw new BugInCF(
+                "Missing annotation.%nmergedType=%s%ncurrentType=%s", mergedType, currentType);
+          }
+        }
+      }
+
+      missingAnnos.removeAll(found);
+    }
+
+    // add all the annotations from the primaries
+    for (final AnnotationMirror top : missingAnnos) {
+      final AnnotationMirror anno = primaries.get(top);
+      if (anno != null) {
+        mergedType.replaceAnnotation(anno);
+      }
+    }
+
+    typesToHierarchies.clear();
+
+    if (missingAnnos.isEmpty()) {
+      return new InferredType(mergedType);
+    }
+
+    // TODO: we probably can do more with this information than just putting it back into the
+    // TODO: ConstraintMap (which is what's happening here)
+    final AnnotationMirrorSet hierarchies = new AnnotationMirrorSet(tops);
+    hierarchies.removeAll(missingAnnos);
+    typesToHierarchies.put(mergedType, hierarchies);
+
+    return null;
+  }
+
+  public InferredValue mergeConstraints(
+      final TypeVariable target,
+      final Equalities equalities,
+      final InferenceResult solution,
+      ConstraintMap constraintMap,
+      AnnotatedTypeFactory typeFactory) {
+    final AnnotationMirrorSet tops =
+        new AnnotationMirrorSet(typeFactory.getQualifierHierarchy().getTopAnnotations());
+    InferredValue inferred = null;
+    if (!equalities.types.isEmpty()) {
+      inferred = mergeTypesAndPrimaries(equalities.types, equalities.primaries, tops, typeFactory);
+    }
+
+    if (inferred != null) {
+      return inferred;
+    } // else
+
+    // We did not have enough information to infer an annotation in all hierarchies for one
+    // concrete type.
+    // However, we have a "partial solution", one in which we know the type in some but not all
+    // qualifier hierarchies.
+    // Update our set of constraints with this information
+    dirty |= updateTargetsWithPartiallyInferredType(equalities, constraintMap, typeFactory);
+    inferred = findEqualTarget(equalities, tops);
+
+    if (inferred == null && equalities.types.size() == 1) {
+      // Still could not find an inferred type in all hierarchies, so just use what type is known.
+      AnnotatedTypeMirror type = equalities.types.keySet().iterator().next();
+      inferred = new InferredType(type);
+    }
+    return inferred;
+  }
+
+  // If we determined that this target T1 is equal to a type ATM in hierarchies @A,@B,@C
+  // for each of those hierarchies, if a target is equal to T1 in that hierarchy it is also equal
+  // to ATM.
+  // e.g.
+  //   if : T1 == @A @B @C ATM in only the A,B hierarchies
+  //    and T1 == T2 only in @A hierarchy
+  //
+  //   then T2 == @A @B @C only in the @A hierarchy
+  //
+  public boolean updateTargetsWithPartiallyInferredType(
+      final Equalities equalities, ConstraintMap constraintMap, AnnotatedTypeFactory typeFactory) {
+
+    boolean updated = false;
+
+    if (!equalities.types.isEmpty()) {
+      if (equalities.types.size() != 1) {
+        throw new BugInCF("Equalities should have at most 1 constraint.");
+      }
+
+      Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> remainingTypeEquality;
+      remainingTypeEquality = equalities.types.entrySet().iterator().next();
+
+      final AnnotatedTypeMirror remainingType = remainingTypeEquality.getKey();
+      final AnnotationMirrorSet remainingHierarchies = remainingTypeEquality.getValue();
+
+      // update targets
+      for (Map.Entry<TypeVariable, AnnotationMirrorSet> targetToHierarchies :
+          equalities.targets.entrySet()) {
+        final TypeVariable equalTarget = targetToHierarchies.getKey();
+        final AnnotationMirrorSet hierarchies = targetToHierarchies.getValue();
+
+        final AnnotationMirrorSet equalTypeHierarchies =
+            new AnnotationMirrorSet(remainingHierarchies);
+        equalTypeHierarchies.retainAll(hierarchies);
+
+        final Map<AnnotatedTypeMirror, AnnotationMirrorSet> otherTargetsEqualTypes =
+            constraintMap.getConstraints(equalTarget).equalities.types;
+
+        AnnotationMirrorSet equalHierarchies = otherTargetsEqualTypes.get(remainingType);
+        if (equalHierarchies == null) {
+          equalHierarchies = new AnnotationMirrorSet(equalTypeHierarchies);
+          otherTargetsEqualTypes.put(remainingType, equalHierarchies);
+          updated = true;
+
+        } else {
+          final int size = equalHierarchies.size();
+          equalHierarchies.addAll(equalTypeHierarchies);
+          updated = size == equalHierarchies.size();
+        }
+      }
+    }
+
+    return updated;
+  }
+
+  /**
+   * Attempt to find a target which is equal to this target.
+   *
+   * @return a target equal to this target in all hierarchies, or null
+   */
+  public InferredTarget findEqualTarget(final Equalities equalities, AnnotationMirrorSet tops) {
+    for (Map.Entry<TypeVariable, AnnotationMirrorSet> targetToHierarchies :
+        equalities.targets.entrySet()) {
+      final TypeVariable equalTarget = targetToHierarchies.getKey();
+      final AnnotationMirrorSet hierarchies = targetToHierarchies.getValue();
+
+      // Now see if target is equal to equalTarget in all hierarchies
+      boolean targetIsEqualInAllHierarchies = hierarchies.size() == tops.size();
+      if (targetIsEqualInAllHierarchies) {
+        return new InferredTarget(equalTarget, new AnnotationMirrorSet());
+
+      } else {
+        // annos in primaries that are not covered by the target's list of equal hierarchies
+        final AnnotationMirrorSet requiredPrimaries =
+            new AnnotationMirrorSet(equalities.primaries.keySet());
+        requiredPrimaries.removeAll(hierarchies);
+
+        boolean typeWithPrimariesIsEqual =
+            (requiredPrimaries.size() + hierarchies.size()) == tops.size();
+        if (typeWithPrimariesIsEqual) {
+          return new InferredTarget(equalTarget, requiredPrimaries);
+        }
+      }
+    }
+
+    return null;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferenceResult.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferenceResult.java
new file mode 100644
index 0000000..30bdb02
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferenceResult.java
@@ -0,0 +1,169 @@
+package org.checkerframework.framework.util.typeinference.solver;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredTarget;
+import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType;
+
+/**
+ * Represents the result from inferring type arguments. InferenceResult is a map from: target type
+ * variable to (inferred type or target).
+ */
+public class InferenceResult extends LinkedHashMap<TypeVariable, InferredValue> {
+  private static final long serialVersionUID = 6911459752070485818L;
+
+  /**
+   * Returns the set of targets that still don't have an inferred argument.
+   *
+   * @return the set of targets that still don't have an inferred argument
+   */
+  public Set<TypeVariable> getRemainingTargets(
+      final Set<TypeVariable> allTargets, boolean inferredTypesOnly) {
+    final LinkedHashSet<TypeVariable> remainingTargets = new LinkedHashSet<>(allTargets);
+
+    if (inferredTypesOnly) {
+
+      for (TypeVariable target : keySet()) {
+        if (this.get(target) instanceof InferredType) {
+          remainingTargets.remove(target);
+        }
+      }
+
+    } else {
+      remainingTargets.removeAll(this.keySet());
+    }
+
+    return remainingTargets;
+  }
+
+  /**
+   * Returns true if we have inferred a concrete type for all targets.
+   *
+   * @param targets type variables to check
+   * @return true if we have inferred a concrete type for all targets
+   */
+  public boolean isComplete(final Set<TypeVariable> targets) {
+    for (final TypeVariable target : targets) {
+      final InferredValue inferred = this.get(target);
+
+      if (inferred == null || inferred instanceof InferredTarget) {
+        return false;
+      } else if (inferred instanceof InferredType
+          && ((InferredType) inferred).type.getKind() == TypeKind.NULL) {
+        // NullType is not a valid type argument, so continue looking for the correct type.
+        return false;
+      }
+    }
+    return this.keySet().containsAll(targets);
+  }
+
+  /**
+   * If we had a set of inferred results, (e.g. T1 = T2, T2 = T3, T3 = String) propagate any results
+   * we have (the above constraints become T1 = String, T2 = String, T3 = String)
+   */
+  public void resolveChainedTargets() {
+    final Map<TypeVariable, InferredValue> inferredTypes = new LinkedHashMap<>(this.size());
+
+    // TODO: we can probably make this a bit more efficient
+    boolean grew = true;
+    while (grew) {
+      grew = false;
+      for (final Map.Entry<TypeVariable, InferredValue> inferred : this.entrySet()) {
+        final TypeVariable target = inferred.getKey();
+        final InferredValue value = inferred.getValue();
+
+        if (value instanceof InferredType) {
+          inferredTypes.put(target, value);
+
+        } else {
+          final InferredTarget currentTarget = (InferredTarget) value;
+          final InferredType equivalentType =
+              (InferredType) inferredTypes.get(((InferredTarget) value).target);
+
+          if (equivalentType != null) {
+            grew = true;
+            final AnnotatedTypeMirror type = equivalentType.type.deepCopy();
+            type.replaceAnnotations(currentTarget.additionalAnnotations);
+
+            final InferredType newConstraint = new InferredType(type);
+            inferredTypes.put(currentTarget.target, newConstraint);
+          }
+        }
+      }
+    }
+
+    this.putAll(inferredTypes);
+  }
+
+  public Map<TypeVariable, AnnotatedTypeMirror> toAtmMap() {
+    final Map<TypeVariable, AnnotatedTypeMirror> result = new LinkedHashMap<>(this.size());
+    for (final Map.Entry<TypeVariable, InferredValue> entry : this.entrySet()) {
+      final InferredValue inferredValue = entry.getValue();
+      if (inferredValue instanceof InferredType) {
+        result.put(entry.getKey(), ((InferredType) inferredValue).type);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Merges values in subordinate into this result, keeping the results form any type arguments that
+   * were already contained by this InferenceResult.
+   *
+   * @param subordinate a result which we wish to merge into this result
+   */
+  public void mergeSubordinate(final InferenceResult subordinate) {
+    final LinkedHashSet<TypeVariable> previousKeySet = new LinkedHashSet<>(this.keySet());
+    final LinkedHashSet<TypeVariable> remainingSubKeys = new LinkedHashSet<>(subordinate.keySet());
+    remainingSubKeys.removeAll(keySet());
+
+    for (TypeVariable target : previousKeySet) {
+      mergeTarget(target, subordinate);
+    }
+
+    for (TypeVariable target : remainingSubKeys) {
+      this.put(target, subordinate.get(target));
+    }
+
+    resolveChainedTargets();
+  }
+
+  /** Performs a merge for a specific target, we keep only results that lead to a concrete type. */
+  protected InferredType mergeTarget(final TypeVariable target, final InferenceResult subordinate) {
+    final InferredValue inferred = this.get(target);
+    if (inferred instanceof InferredTarget) {
+      InferredType newType = mergeTarget(((InferredTarget) inferred).target, subordinate);
+
+      if (newType == null) {
+        final InferredValue subValue = subordinate.get(target);
+        if (subValue instanceof InferredType) {
+          this.put(target, subValue);
+          return null;
+        }
+      } else {
+        if (newType.type.getKind() == TypeKind.NULL) {
+          // If the newType is null, then use the subordinate type, but with the
+          // primary annotations on null.
+          final InferredValue subValue = subordinate.get(target);
+          if (subValue instanceof InferredType) {
+            AnnotatedTypeMirror copy = ((InferredType) subValue).type.deepCopy();
+            copy.replaceAnnotations(newType.type.getAnnotations());
+            newType = new InferredType(copy);
+          }
+        }
+        this.put(target, newType);
+        return newType;
+      }
+
+      return null;
+    } // else
+
+    return (InferredType) inferred;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferredValue.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferredValue.java
new file mode 100644
index 0000000..5a9b4fb
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferredValue.java
@@ -0,0 +1,54 @@
+package org.checkerframework.framework.util.typeinference.solver;
+
+import java.util.Collection;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+
+/**
+ * When one of the constraint solvers infers that a the target has a given type/target in ALL
+ * qualifier hierarchies or that given an additional set of annotations that we know the target must
+ * hold we have covered all hierarchies then it creates an InferredValue to represent this
+ * inference.
+ *
+ * <p>There are subclasses to represent two cases:
+ *
+ * <ul>
+ *   <li>The target was inferred to be an AnnotatedTypeMirror
+ *   <li>The target was inferred to be equal to another target
+ * </ul>
+ */
+public class InferredValue {
+  /**
+   * Indicates that a corresponding target was inferred to be the field "type" in all hierarchies.
+   */
+  public static class InferredType extends InferredValue {
+    public final AnnotatedTypeMirror type;
+
+    public InferredType(final AnnotatedTypeMirror type) {
+      this.type = type;
+    }
+  }
+
+  /**
+   * Indicates that a corresponding target was inferred to be the field "target" in the hierarchies
+   * not overridden by additionalAnnotations.
+   */
+  public static class InferredTarget extends InferredValue {
+    public final TypeVariable target;
+
+    /**
+     * Indicates that the inferred type should have these primary annotations and the remainder
+     * should come from the annotations inferred for target.
+     */
+    public final AnnotationMirrorSet additionalAnnotations;
+
+    public InferredTarget(
+        final TypeVariable target,
+        final Collection<? extends AnnotationMirror> additionalAnnotations) {
+      this.target = target;
+      this.additionalAnnotations = new AnnotationMirrorSet(additionalAnnotations);
+    }
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SubtypesSolver.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SubtypesSolver.java
new file mode 100644
index 0000000..113b98d
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SubtypesSolver.java
@@ -0,0 +1,181 @@
+package org.checkerframework.framework.util.typeinference.solver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.Types;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.util.AnnotationMirrorMap;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+import org.checkerframework.framework.util.typeinference.GlbUtil;
+import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType;
+import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Subtypes;
+
+/**
+ * Infers type arguments by using the Greatest Lower Bound computation on the subtype relationships
+ * in a constraint map.
+ */
+public class SubtypesSolver {
+
+  /**
+   * Infers type arguments using subtype constraints.
+   *
+   * @param remainingTargets targets for which we still need to infer a value
+   * @param constraints the set of constraints for all targets
+   * @return a mapping from target to inferred type. Note this class always infers concrete types
+   *     and will not infer that the target is equivalent to another target.
+   */
+  public InferenceResult solveFromSubtypes(
+      final Set<TypeVariable> remainingTargets,
+      final ConstraintMap constraints,
+      final AnnotatedTypeFactory typeFactory) {
+    return glbSubtypes(remainingTargets, constraints, typeFactory);
+  }
+
+  public InferenceResult glbSubtypes(
+      final Set<TypeVariable> remainingTargets,
+      final ConstraintMap constraints,
+      final AnnotatedTypeFactory typeFactory) {
+    final InferenceResult inferenceResult = new InferenceResult();
+    final QualifierHierarchy qualifierHierarchy = typeFactory.getQualifierHierarchy();
+
+    final Types types = typeFactory.getProcessingEnv().getTypeUtils();
+
+    List<TypeVariable> targetsSubtypesLast = new ArrayList<>(remainingTargets);
+
+    // If we have two type variables <A, A extends B> order them A then B
+    // this is required because we will use the fact that B must be below A
+    // when determining the glb of B
+    Collections.sort(
+        targetsSubtypesLast,
+        new Comparator<TypeVariable>() {
+          @Override
+          public int compare(TypeVariable o1, TypeVariable o2) {
+            if (types.isSubtype(o1, o2)) {
+              return 1;
+            } else if (types.isSubtype(o2, o1)) {
+              return -1;
+            }
+
+            return 0;
+          }
+        });
+
+    for (final TypeVariable target : targetsSubtypesLast) {
+      Subtypes subtypes = constraints.getConstraints(target).subtypes;
+
+      if (subtypes.types.isEmpty()) {
+        continue;
+      }
+
+      propagatePreviousGlbs(subtypes, inferenceResult, subtypes.types);
+
+      // if the subtypes size is only 1 then we need not do any GLBing on the underlying types
+      // but we may have primary annotations that need to be GLBed
+      AnnotationMirrorMap<AnnotationMirrorSet> primaries = subtypes.primaries;
+      if (subtypes.types.size() == 1) {
+        final Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> entry =
+            subtypes.types.entrySet().iterator().next();
+        AnnotatedTypeMirror supertype = entry.getKey().deepCopy();
+
+        for (AnnotationMirror top : entry.getValue()) {
+          final AnnotationMirrorSet superAnnos = primaries.get(top);
+          // if it is null we're just going to use the anno already on supertype
+          if (superAnnos != null) {
+            final AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(top);
+            superAnnos.add(supertypeAnno);
+          }
+        }
+
+        if (!primaries.isEmpty()) {
+          for (AnnotationMirror top : qualifierHierarchy.getTopAnnotations()) {
+            final AnnotationMirror glb =
+                greatestLowerBound(subtypes.primaries.get(top), qualifierHierarchy);
+            supertype.replaceAnnotation(glb);
+          }
+        }
+
+        inferenceResult.put(target, new InferredType(supertype));
+
+      } else {
+
+        // GLB all of the types than combine this with the GLB of primary annotation constraints
+        final AnnotatedTypeMirror glbType = GlbUtil.glbAll(subtypes.types, typeFactory);
+        if (glbType != null) {
+          if (!primaries.isEmpty()) {
+            for (AnnotationMirror top : qualifierHierarchy.getTopAnnotations()) {
+              final AnnotationMirror glb =
+                  greatestLowerBound(subtypes.primaries.get(top), qualifierHierarchy);
+              final AnnotationMirror currentAnno = glbType.getAnnotationInHierarchy(top);
+
+              if (currentAnno == null) {
+                glbType.addAnnotation(glb);
+              } else if (glb != null) {
+                glbType.replaceAnnotation(qualifierHierarchy.greatestLowerBound(glb, currentAnno));
+              }
+            }
+          }
+
+          inferenceResult.put(target, new InferredType(glbType));
+        }
+      }
+    }
+
+    return inferenceResult;
+  }
+
+  /**
+   * /** If the target corresponding to targetRecord must be a subtype of another target for which
+   * we have already determined a GLB, add that target's GLB to the list of subtypes to be GLBed for
+   * this target.
+   */
+  protected static void propagatePreviousGlbs(
+      final Subtypes targetSubtypes,
+      InferenceResult solution,
+      final Map<AnnotatedTypeMirror, AnnotationMirrorSet> subtypesOfTarget) {
+
+    for (final Map.Entry<TypeVariable, AnnotationMirrorSet> subtypeTarget :
+        targetSubtypes.targets.entrySet()) {
+      final InferredValue subtargetInferredGlb = solution.get(subtypeTarget.getKey());
+
+      if (subtargetInferredGlb != null) {
+        final AnnotatedTypeMirror subtargetGlbType = ((InferredType) subtargetInferredGlb).type;
+        AnnotationMirrorSet subtargetAnnos = subtypesOfTarget.get(subtargetGlbType);
+        if (subtargetAnnos != null) {
+          // there is already an equivalent type in the list of subtypes, just add
+          // any hierarchies that are not in its list but are in the supertarget's list
+          subtargetAnnos.addAll(subtypeTarget.getValue());
+        } else {
+          subtypesOfTarget.put(subtargetGlbType, subtypeTarget.getValue());
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns the GLB of annos.
+   *
+   * @param annos a set of annotations in the same annotation hierarchy
+   * @param qualifierHierarchy the qualifier of the annotation hierarchy
+   * @return the GLB of annos
+   */
+  private final AnnotationMirror greatestLowerBound(
+      final Iterable<? extends AnnotationMirror> annos, QualifierHierarchy qualifierHierarchy) {
+    Iterator<? extends AnnotationMirror> annoIter = annos.iterator();
+    AnnotationMirror glb = annoIter.next();
+
+    while (annoIter.hasNext()) {
+      glb = qualifierHierarchy.greatestLowerBound(glb, annoIter.next());
+    }
+
+    return glb;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SupertypesSolver.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SupertypesSolver.java
new file mode 100644
index 0000000..020a70e
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SupertypesSolver.java
@@ -0,0 +1,424 @@
+package org.checkerframework.framework.util.typeinference.solver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.Types;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.framework.util.AnnotationMirrorMap;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
+import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType;
+import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Equalities;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+
+/**
+ * Infers type arguments by using the Least Upper Bound computation on the supertype relationships
+ * in a constraint map.
+ */
+public class SupertypesSolver {
+
+  /**
+   * Infers type arguments using supertype constraints.
+   *
+   * @param remainingTargets targets for which we still need to infer a value
+   * @param constraintMap the set of constraints for all targets
+   * @return a mapping from target to inferred type. Note this class always infers concrete types
+   *     and will not infer that the target is equivalent to another target.
+   */
+  public InferenceResult solveFromSupertypes(
+      final Set<TypeVariable> remainingTargets,
+      final ConstraintMap constraintMap,
+      final AnnotatedTypeFactory typeFactory) {
+    // infer a type for all targets that have supertype constraints
+    final Lubs lubs = targetToTypeLubs(remainingTargets, constraintMap, typeFactory);
+
+    // add the lub types to the outgoing solution
+    final InferenceResult solution = new InferenceResult();
+    for (final TypeVariable target : remainingTargets) {
+      final AnnotatedTypeMirror lub = lubs.getType(target);
+      AnnotationMirrorMap<AnnotationMirror> lubAnnos = lubs.getPrimaries(target);
+
+      // we may have a partial solution present in the equality constraints, override
+      // any annotations found in the lub with annotations from the equality constraints
+      final InferredValue inferred;
+      if (lub != null) {
+        inferred = mergeLubTypeWithEqualities(target, lub, constraintMap, typeFactory);
+      } else if (lubAnnos != null) {
+        inferred = mergeLubAnnosWithEqualities(target, lubAnnos, constraintMap, typeFactory);
+      } else {
+        inferred = null;
+      }
+
+      if (inferred != null) {
+        solution.put(target, inferred);
+      }
+    }
+
+    return solution;
+  }
+
+  /**
+   * We previously found a type that is equal to target but not in all hierarchies. Use the primary
+   * annotations from the lub type to fill in the missing annotations in this type. Use that type as
+   * the inferred argument.
+   *
+   * <p>If we failed to infer any annotation for a given hierarchy, either previously from
+   * equalities or from the lub, return null.
+   */
+  protected InferredType mergeLubTypeWithEqualities(
+      final TypeVariable target,
+      final AnnotatedTypeMirror lub,
+      final ConstraintMap constraintMap,
+      final AnnotatedTypeFactory typeFactory) {
+    final Equalities equalities = constraintMap.getConstraints(target).equalities;
+    final AnnotationMirrorSet tops =
+        new AnnotationMirrorSet(typeFactory.getQualifierHierarchy().getTopAnnotations());
+
+    if (!equalities.types.isEmpty()) {
+      // there should be only one equality type if any at this point
+      final Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> eqEntry =
+          equalities.types.entrySet().iterator().next();
+      final AnnotatedTypeMirror equalityType = eqEntry.getKey();
+      final AnnotationMirrorSet equalityAnnos = eqEntry.getValue();
+
+      boolean failed = false;
+      for (final AnnotationMirror top : tops) {
+        if (!equalityAnnos.contains(top)) {
+          final AnnotationMirror lubAnno = lub.getAnnotationInHierarchy(top);
+          if (lubAnno == null) {
+            // If the LUB and the Equality were the SAME typevar, and the lub was
+            // unannotated then "NO ANNOTATION" is the correct choice.
+            if (lub.getKind() == TypeKind.TYPEVAR
+                && typeFactory.types.isSameType(
+                    equalityType.getUnderlyingType(), lub.getUnderlyingType())) {
+              equalityAnnos.add(top);
+            } else {
+              failed = true;
+            }
+
+          } else {
+            equalityType.replaceAnnotation(lubAnno);
+            equalityAnnos.add(top);
+          }
+        }
+      }
+
+      if (!failed) {
+        return new InferredType(equalityType);
+      }
+    }
+
+    return new InferredType(lub);
+  }
+
+  /**
+   * We previously found a type that is equal to target but not in all hierarchies. Use the primary
+   * annotations from the lub annos to fill in the missing annotations in this type. Use that type
+   * as the inferred argument.
+   *
+   * <p>If we failed to infer any annotation for a given hierarchy, either previously from
+   * equalities or from the lub, return null.
+   */
+  protected InferredType mergeLubAnnosWithEqualities(
+      final TypeVariable target,
+      final AnnotationMirrorMap<AnnotationMirror> lubAnnos,
+      final ConstraintMap constraintMap,
+      final AnnotatedTypeFactory typeFactory) {
+    final Equalities equalities = constraintMap.getConstraints(target).equalities;
+    final AnnotationMirrorSet tops =
+        new AnnotationMirrorSet(typeFactory.getQualifierHierarchy().getTopAnnotations());
+
+    if (!equalities.types.isEmpty()) {
+      // there should be only equality type if any at this point
+      final Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> eqEntry =
+          equalities.types.entrySet().iterator().next();
+      final AnnotatedTypeMirror equalityType = eqEntry.getKey();
+      final AnnotationMirrorSet equalityAnnos = eqEntry.getValue();
+
+      boolean failed = false;
+      for (final AnnotationMirror top : tops) {
+        if (!equalityAnnos.contains(top)) {
+          final AnnotationMirror lubAnno = lubAnnos.get(top);
+          if (lubAnno == null) {
+            failed = true;
+
+          } else {
+            equalityType.replaceAnnotation(lubAnno);
+            equalityAnnos.add(top);
+          }
+        }
+      }
+
+      if (!failed) {
+        return new InferredType(equalityType);
+      }
+    }
+
+    return null;
+  }
+
+  /** Holds the least upper bounds for every target type parameter. */
+  static class Lubs {
+    public final Map<TypeVariable, AnnotatedTypeMirror> types = new LinkedHashMap<>();
+    public final Map<TypeVariable, AnnotationMirrorMap<AnnotationMirror>> primaries =
+        new LinkedHashMap<>();
+
+    public void addPrimaries(
+        final TypeVariable target, AnnotationMirrorMap<AnnotationMirror> primaries) {
+      this.primaries.put(target, new AnnotationMirrorMap<>(primaries));
+    }
+
+    public void addType(final TypeVariable target, final AnnotatedTypeMirror type) {
+      types.put(target, type);
+    }
+
+    public AnnotationMirrorMap<AnnotationMirror> getPrimaries(final TypeVariable target) {
+      return primaries.get(target);
+    }
+
+    public AnnotatedTypeMirror getType(final TypeVariable target) {
+      return types.get(target);
+    }
+  }
+
+  /**
+   * For each target, lub all of the types/annotations in its supertypes constraints and return the
+   * lubs.
+   *
+   * @param remainingTargets targets that do not already have an inferred type argument
+   * @param constraintMap the set of constraints for all targets
+   * @return the lub determined for each target that has at least 1 supertype constraint
+   */
+  private Lubs targetToTypeLubs(
+      Set<TypeVariable> remainingTargets,
+      ConstraintMap constraintMap,
+      AnnotatedTypeFactory typeFactory) {
+    final QualifierHierarchy qualifierHierarchy = typeFactory.getQualifierHierarchy();
+    final AnnotationMirrorSet tops =
+        new AnnotationMirrorSet(qualifierHierarchy.getTopAnnotations());
+
+    Lubs solution = new Lubs();
+
+    AnnotationMirrorMap<AnnotationMirror> lubOfPrimaries = new AnnotationMirrorMap<>();
+
+    List<TypeVariable> targetsSupertypesLast = new ArrayList<>(remainingTargets);
+
+    final Types types = typeFactory.getProcessingEnv().getTypeUtils();
+    // If we have two type variables <A, A extends B> order them B then A
+    // this is required because we will use the fact that A must be above B
+    // when determining the LUB of A
+    Collections.sort(
+        targetsSupertypesLast,
+        new Comparator<TypeVariable>() {
+          @Override
+          public int compare(TypeVariable o1, TypeVariable o2) {
+            if (types.isSubtype(o1, o2)) {
+              return -1;
+            } else if (types.isSubtype(o2, o1)) {
+              return 1;
+            }
+
+            return 0;
+          }
+        });
+
+    for (final TypeVariable target : targetsSupertypesLast) {
+      TargetConstraints targetRecord = constraintMap.getConstraints(target);
+      final AnnotationMirrorMap<AnnotationMirrorSet> subtypeAnnos =
+          targetRecord.supertypes.primaries;
+      final Map<AnnotatedTypeMirror, AnnotationMirrorSet> subtypesOfTarget =
+          targetRecord.supertypes.types;
+
+      // If this target is a supertype of other targets and those targets have already been lubbed
+      // add that LUB to the list of lubs for this target (as it must be above this target).
+      propagatePreviousLubs(targetRecord, solution, subtypesOfTarget);
+
+      // lub all the primary annotations and put them in lubOfPrimaries
+      lubPrimaries(lubOfPrimaries, subtypeAnnos, tops, qualifierHierarchy);
+      solution.addPrimaries(target, lubOfPrimaries);
+
+      if (!subtypesOfTarget.isEmpty()) {
+        final AnnotatedTypeMirror lub = leastUpperBound(target, typeFactory, subtypesOfTarget);
+        final AnnotationMirrorSet effectiveLubAnnos =
+            new AnnotationMirrorSet(lub.getEffectiveAnnotations());
+
+        for (AnnotationMirror lubAnno : effectiveLubAnnos) {
+          final AnnotationMirror hierarchy = qualifierHierarchy.getTopAnnotation(lubAnno);
+          final AnnotationMirror primaryLub = lubOfPrimaries.get(hierarchy);
+
+          if (primaryLub != null) {
+            if (qualifierHierarchy.isSubtype(lubAnno, primaryLub)
+                && !AnnotationUtils.areSame(lubAnno, primaryLub)) {
+              lub.replaceAnnotation(primaryLub);
+            }
+          }
+        }
+
+        solution.addType(target, lub);
+      }
+    }
+    return solution;
+  }
+
+  /**
+   * If the target corresponding to targetRecord must be a supertype of another target for which we
+   * have already determined a lub, add that target's lub to this list.
+   */
+  protected static void propagatePreviousLubs(
+      final TargetConstraints targetRecord,
+      Lubs solution,
+      final Map<AnnotatedTypeMirror, AnnotationMirrorSet> subtypesOfTarget) {
+
+    for (final Map.Entry<TypeVariable, AnnotationMirrorSet> supertypeTarget :
+        targetRecord.supertypes.targets.entrySet()) {
+      final AnnotatedTypeMirror supertargetLub = solution.getType(supertypeTarget.getKey());
+      if (supertargetLub != null) {
+        AnnotationMirrorSet supertargetTypeAnnos = subtypesOfTarget.get(supertargetLub);
+        if (supertargetTypeAnnos != null) {
+          // there is already an equivalent type in the list of subtypes, just add
+          // any hierarchies that are not in its list but are in the supertarget's list
+          supertargetTypeAnnos.addAll(supertypeTarget.getValue());
+        } else {
+          subtypesOfTarget.put(supertargetLub, supertypeTarget.getValue());
+        }
+      }
+    }
+  }
+
+  /**
+   * For each qualifier hierarchy in tops, take the lub of the annos in subtypeAnnos that correspond
+   * to that hierarchy place the lub in lubOfPrimaries.
+   */
+  protected static void lubPrimaries(
+      AnnotationMirrorMap<AnnotationMirror> lubOfPrimaries,
+      AnnotationMirrorMap<AnnotationMirrorSet> subtypeAnnos,
+      AnnotationMirrorSet tops,
+      QualifierHierarchy qualifierHierarchy) {
+
+    lubOfPrimaries.clear();
+    for (final AnnotationMirror top : tops) {
+      final AnnotationMirrorSet annosInHierarchy = subtypeAnnos.get(top);
+      if (annosInHierarchy != null && !annosInHierarchy.isEmpty()) {
+        lubOfPrimaries.put(top, leastUpperBound(annosInHierarchy, qualifierHierarchy));
+      } else {
+        // If there are no annotations for this hierarchy, add bottom.  This happens
+        // when the only constraint for a type variable is a use that is annotated in
+        // this hierarchy. Calls to the method below have this property.
+        // <T> void method(@NonNull T t) {}
+        lubOfPrimaries.put(top, qualifierHierarchy.getBottomAnnotation(top));
+      }
+    }
+  }
+
+  /**
+   * For each type in typeToHierarchies, if that type does not have a corresponding annotation for a
+   * given hierarchy replace it with the corresponding value in lowerBoundAnnos.
+   */
+  public static AnnotatedTypeMirror groundMissingHierarchies(
+      final Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> typeToHierarchies,
+      final AnnotationMirrorMap<AnnotationMirror> lowerBoundAnnos) {
+    final AnnotationMirrorSet presentHierarchies = typeToHierarchies.getValue();
+    final AnnotationMirrorSet missingAnnos = new AnnotationMirrorSet();
+    for (AnnotationMirror top : lowerBoundAnnos.keySet()) {
+      if (!presentHierarchies.contains(top)) {
+        missingAnnos.add(lowerBoundAnnos.get(top));
+      }
+    }
+
+    if (!missingAnnos.isEmpty()) {
+      AnnotatedTypeMirror copy = typeToHierarchies.getKey().deepCopy();
+      copy.replaceAnnotations(missingAnnos);
+
+      return copy;
+    }
+
+    return typeToHierarchies.getKey();
+  }
+
+  /**
+   * Successively calls least upper bound on the elements of types. Unlike
+   * AnnotatedTypes.leastUpperBound, this method will box primitives if necessary
+   */
+  public static AnnotatedTypeMirror leastUpperBound(
+      final TypeVariable target,
+      final AnnotatedTypeFactory typeFactory,
+      final Map<AnnotatedTypeMirror, AnnotationMirrorSet> types) {
+
+    QualifierHierarchy qualifierHierarchy = typeFactory.getQualifierHierarchy();
+    AnnotatedTypeVariable targetsDeclaredType =
+        (AnnotatedTypeVariable) typeFactory.getAnnotatedType(target.asElement());
+    final AnnotationMirrorMap<AnnotationMirror> lowerBoundAnnos =
+        TypeArgInferenceUtil.createHierarchyMap(
+            new AnnotationMirrorSet(targetsDeclaredType.getLowerBound().getEffectiveAnnotations()),
+            qualifierHierarchy);
+
+    final Iterator<Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet>> typesIter =
+        types.entrySet().iterator();
+    if (!typesIter.hasNext()) {
+      throw new BugInCF("Calling LUB on empty list.");
+    }
+
+    /**
+     * If a constraint implies that a type parameter Ti is a supertype of an annotated type mirror
+     * Ai but only in a subset of all qualifier hierarchies then for all other qualifier hierarchies
+     * replace the primary annotation on Ai with the lowest possible annotation (ensuring that it
+     * won't be the LUB unless there are no other constraints, or all other constraints imply the
+     * bottom annotation is the LUB). Note: Even if we choose bottom as the lub here, the assignment
+     * context may raise this annotation.
+     */
+    final Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> head = typesIter.next();
+
+    AnnotatedTypeMirror lubType = groundMissingHierarchies(head, lowerBoundAnnos);
+    AnnotatedTypeMirror nextType = null;
+    while (typesIter.hasNext()) {
+      nextType = groundMissingHierarchies(typesIter.next(), lowerBoundAnnos);
+
+      if (lubType.getKind().isPrimitive()) {
+        if (!nextType.getKind().isPrimitive()) {
+          lubType = typeFactory.getBoxedType((AnnotatedPrimitiveType) lubType);
+        }
+      } else if (nextType.getKind().isPrimitive()) {
+        if (!lubType.getKind().isPrimitive()) {
+          nextType = typeFactory.getBoxedType((AnnotatedPrimitiveType) nextType);
+        }
+      }
+      lubType = AnnotatedTypes.leastUpperBound(typeFactory, lubType, nextType);
+    }
+
+    return lubType;
+  }
+
+  /**
+   * Returns the lub of all the annotations in annos.
+   *
+   * @param annos a set of annotations in the same annotation hierarchy
+   * @param qualifierHierarchy the qualifier hierarchy that contains each annotation
+   * @return the lub of all the annotations in annos
+   */
+  private static final AnnotationMirror leastUpperBound(
+      final Iterable<? extends AnnotationMirror> annos, QualifierHierarchy qualifierHierarchy) {
+    Iterator<? extends AnnotationMirror> annoIter = annos.iterator();
+    AnnotationMirror lub = annoIter.next();
+
+    while (annoIter.hasNext()) {
+      lub = qualifierHierarchy.leastUpperBound(lub, annoIter.next());
+    }
+
+    return lub;
+  }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/TargetConstraints.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/TargetConstraints.java
new file mode 100644
index 0000000..05ff1d5
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/TargetConstraints.java
@@ -0,0 +1,114 @@
+package org.checkerframework.framework.util.typeinference.solver;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeVariable;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.util.AnnotationMirrorMap;
+import org.checkerframework.framework.util.AnnotationMirrorSet;
+
+/**
+ * TargetConstraints represents the set of all TUConstraints for which target was the type
+ * parameter, i.e. the T in the TUConstraint. Unlike AF/TU Constraints, this class holds multiple
+ * constraints and is mutated during solving (where the TU/AF Constraints are immutable).
+ *
+ * @see org.checkerframework.framework.util.typeinference.solver.ConstraintMap
+ */
+public class TargetConstraints {
+  /**
+   * The type parameter for which we are inferring a type argument. All constraints in this object
+   * are related to this target.
+   */
+  public final TypeVariable target;
+
+  public final Equalities equalities;
+
+  /**
+   * The target is the supertype in this case, that these are supertype constraints in which target
+   * is the supertype. These are NOT supertypes of the target.
+   */
+  public final Supertypes supertypes;
+
+  /**
+   * The target is the supertype in this case, that these are subtype constraints in which target is
+   * the subtype. These are NOT subtypes of the target.
+   */
+  public final Subtypes subtypes;
+
+  public TargetConstraints(final TypeVariable target) {
+    this.target = target;
+    this.equalities = new Equalities();
+    this.supertypes = new Supertypes();
+    this.subtypes = new Subtypes();
+  }
+
+  protected static class Equalities {
+    // Map( hierarchy top -> exact annotation in hierarchy)
+    public AnnotationMirrorMap<AnnotationMirror> primaries = new AnnotationMirrorMap<>();
+
+    // Map( type -> hierarchy top for which the primary annotation of type is equal to the
+    // primary annotation of the target)
+    // note all components and underlying types are EXACTLY equal to the key to this map
+    public final Map<AnnotatedTypeMirror, AnnotationMirrorSet> types = new LinkedHashMap<>();
+
+    // Map( type -> hierarchy top for which the primary annotation of target is equal to the
+    // primary annotaiton of the target)
+    // note all components and underlying types are EXACTLY equal to the key to this map
+    public final Map<TypeVariable, AnnotationMirrorSet> targets = new LinkedHashMap<>();
+
+    public void clear() {
+      primaries.clear();
+      types.clear();
+      targets.clear();
+    }
+  }
+
+  // remember these are constraint in which target is the supertype
+  protected static class Supertypes {
+    // Map( hierarchy top -> annotations that are subtypes to target in hierarchy)
+    public AnnotationMirrorMap<AnnotationMirrorSet> primaries = new AnnotationMirrorMap<>();
+
+    // Map( type -> hierarchy tops for which the primary annotations of type are subtypes of the
+    // primary annotations of the target)
+    // note all components and underlying types must uphold the supertype relationship in all
+    // hierarchies
+    public final Map<AnnotatedTypeMirror, AnnotationMirrorSet> types = new LinkedHashMap<>();
+
+    // Map( otherTarget -> hierarchy tops for which the primary annotations of otherTarget are
+    // subtypes of the primary annotations of the target)
+    // note all components and underlying types must uphold the subtype relationship in all
+    // hierarchies
+    public final Map<TypeVariable, AnnotationMirrorSet> targets = new LinkedHashMap<>();
+
+    public void clear() {
+      primaries.clear();
+      types.clear();
+      targets.clear();
+    }
+  }
+
+  // remember these are constraint in which target is the subtype
+  protected static class Subtypes {
+    // Map( hierarchy top -> annotations that are supertypes to target in hierarchy)
+    public AnnotationMirrorMap<AnnotationMirrorSet> primaries = new AnnotationMirrorMap<>();
+
+    // Map( type -> hierarchy tops for which the primary annotations of type are supertypes of
+    // the primary annotations of the target)
+    // note all components and underlying types must uphold the supertype relationship in all
+    // hierarchies
+    public final Map<AnnotatedTypeMirror, AnnotationMirrorSet> types = new LinkedHashMap<>();
+
+    // Map( otherTarget -> hierarchy tops for which the primary annotations of otherTarget are
+    // supertypes of the primary annotations of the target)
+    // note all components and underlying types must uphold the subtype relationship in all
+    // hierarchies
+    public final Map<TypeVariable, AnnotationMirrorSet> targets = new LinkedHashMap<>();
+
+    public void clear() {
+      primaries.clear();
+      types.clear();
+      targets.clear();
+    }
+  }
+}
diff --git a/framework/src/main/java/org/jmlspecs/annotation/Pure.java b/framework/src/main/java/org/jmlspecs/annotation/Pure.java
new file mode 100644
index 0000000..eade35c
--- /dev/null
+++ b/framework/src/main/java/org/jmlspecs/annotation/Pure.java
@@ -0,0 +1,12 @@
+package org.jmlspecs.annotation;
+
+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;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Pure {}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationNoReturnsReceiverTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationNoReturnsReceiverTest.java
new file mode 100644
index 0000000..c705a03
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationNoReturnsReceiverTest.java
@@ -0,0 +1,30 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.testaccumulation.TestAccumulationNoReturnsReceiverChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * A test that the accumulation abstract checker is working correctly, using a simple accumulation
+ * checker without a returns-receiver analysis.
+ */
+public class AccumulationNoReturnsReceiverTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public AccumulationNoReturnsReceiverTest(List<File> testFiles) {
+    super(
+        testFiles,
+        TestAccumulationNoReturnsReceiverChecker.class,
+        "accumulation-norr",
+        "-Anomsgtext",
+        "-encoding",
+        "UTF-8");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"accumulation-norr", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationTest.java
new file mode 100644
index 0000000..c38c03a
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationTest.java
@@ -0,0 +1,30 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.testaccumulation.TestAccumulationChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * A test that the accumulation abstract checker is working correctly, using a simple accumulation
+ * checker.
+ */
+public class AccumulationTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public AccumulationTest(List<File> testFiles) {
+    super(
+        testFiles,
+        TestAccumulationChecker.class,
+        "accumulation",
+        "-Anomsgtext",
+        "-encoding",
+        "UTF-8");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"accumulation", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AggregateTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AggregateTest.java
new file mode 100644
index 0000000..b55476b
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AggregateTest.java
@@ -0,0 +1,25 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.aggregate.AggregateOfCompoundChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+public class AggregateTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public AggregateTest(List<File> testFiles) {
+    super(
+        testFiles,
+        AggregateOfCompoundChecker.class,
+        "aggregate",
+        "-Anomsgtext",
+        "-AresolveReflection");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"aggregate"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AliasingTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AliasingTest.java
new file mode 100644
index 0000000..372fb7d
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AliasingTest.java
@@ -0,0 +1,23 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class AliasingTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public AliasingTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.aliasing.AliasingChecker.class,
+        "aliasing",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"aliasing", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotatedForTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotatedForTest.java
new file mode 100644
index 0000000..fe57a76
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotatedForTest.java
@@ -0,0 +1,26 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Created by jthaine on 6/25/15. */
+public class AnnotatedForTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public AnnotatedForTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.subtyping.SubtypingChecker.class,
+        "subtyping",
+        "-Anomsgtext",
+        "-Aquals=org.checkerframework.framework.testchecker.util.SubQual,org.checkerframework.framework.testchecker.util.SuperQual",
+        "-AuseConservativeDefaultsForUncheckedCode=source,bytecode");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"conservative-defaults/annotatedfor"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotationBuilderTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotationBuilderTest.java
new file mode 100644
index 0000000..7e583b8
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotationBuilderTest.java
@@ -0,0 +1,321 @@
+package org.checkerframework.framework.test.junit;
+
+import com.sun.tools.javac.main.JavaCompiler;
+import com.sun.tools.javac.main.Option;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.Options;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.framework.testchecker.util.AnnoWithStringArg;
+import org.checkerframework.framework.testchecker.util.Encrypted;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.BugInCF;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class AnnotationBuilderTest {
+
+  private final ProcessingEnvironment env;
+
+  public AnnotationBuilderTest() {
+    Context context = new Context();
+    // Set source and target to 8
+    Options options = Options.instance(context);
+    options.put(Option.SOURCE, "8");
+    options.put(Option.TARGET, "8");
+
+    env = JavacProcessingEnvironment.instance(context);
+    JavaCompiler javac = JavaCompiler.instance(context);
+    // Even though source/target are set to 8, the modules in the JavaCompiler
+    // need to be initialized by setting the list of modules to nil.
+    javac.initModules(List.nil());
+    javac.enterDone();
+  }
+
+  @Test
+  public void createAnnoWithoutValues() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, Encrypted.class);
+    // AnnotationMirror anno =
+    builder.build();
+  }
+
+  @Test
+  public void createAnnoWithoutValues1() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class);
+    AnnotationMirror anno = builder.build();
+    Assert.assertEquals(0, anno.getElementValues().size());
+  }
+
+  @Test
+  public void createAnnoWithValues0() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class);
+    builder.setValue("value", "m");
+    AnnotationMirror anno = builder.build();
+    Assert.assertEquals(1, anno.getElementValues().size());
+  }
+
+  @Test(expected = BugInCF.class)
+  public void buildingTwice() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, Encrypted.class);
+    builder.build();
+    builder.build();
+  }
+
+  @Test(expected = BugInCF.class)
+  public void addingValuesAfterBuilding() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class);
+    builder.setValue("value", "m");
+    // AnnotationMirror anno =
+    builder.build();
+    builder.setValue("value", "n");
+  }
+
+  @Test(expected = BugInCF.class)
+  public void notFoundElements() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class);
+    builder.setValue("n", "m");
+  }
+
+  @Test(expected = BugInCF.class)
+  public void illegalValue() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class);
+    builder.setValue("value", 1);
+  }
+
+  public static @interface A {
+    int[] numbers();
+  }
+
+  public static @interface B {
+    String[] strings();
+  }
+
+  @Test
+  public void listArrayPrimitive() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, A.class);
+    builder.setValue("numbers", new Integer[] {34, 32, 43});
+    Assert.assertEquals(1, builder.build().getElementValues().size());
+  }
+
+  @Test
+  public void listArrayObject() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, B.class);
+    builder.setValue("strings", new String[] {"m", "n"});
+    Assert.assertEquals(1, builder.build().getElementValues().size());
+  }
+
+  @Test(expected = BugInCF.class)
+  public void listArrayObjectWrongType() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, B.class);
+    builder.setValue("strings", new Object[] {"m", "n", 1});
+    Assert.assertEquals(1, builder.build().getElementValues().size());
+  }
+
+  @Test(expected = BugInCF.class)
+  public void listArrayObjectWrongType1() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, B.class);
+    builder.setValue("strings", 1);
+    Assert.assertEquals(1, builder.build().getElementValues().size());
+  }
+
+  public static @interface Prim {
+    int a();
+  }
+
+  @Test
+  public void primitiveValue() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, Prim.class);
+    builder.setValue("a", 3);
+    Assert.assertEquals(1, builder.build().getElementValues().size());
+  }
+
+  @Test(expected = BugInCF.class)
+  public void primitiveValueWithException() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, A.class);
+    builder.setValue("a", 3.0);
+    Assert.assertEquals(1, builder.build().getElementValues().size());
+  }
+
+  // Multiple values
+  public static @interface Mult {
+    int a();
+
+    String b();
+  }
+
+  @Test
+  public void multiple1() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class);
+    builder.setValue("a", 2);
+    Assert.assertEquals(1, builder.build().getElementValues().size());
+  }
+
+  @Test(expected = BugInCF.class)
+  public void multiple2() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class);
+    builder.setValue("a", "m");
+    Assert.assertEquals(1, builder.build().getElementValues().size());
+  }
+
+  @Test
+  public void multiple3() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class);
+    builder.setValue("a", 1);
+    builder.setValue("b", "mark");
+    Assert.assertEquals(2, builder.build().getElementValues().size());
+  }
+
+  public static @interface ClassElt {
+    Class<?> value();
+  }
+
+  @Test
+  public void testClassPositive() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, ClassElt.class);
+    builder.setValue("value", String.class);
+    builder.setValue("value", int.class);
+    builder.setValue("value", int[].class);
+    builder.setValue("value", void.class);
+    Object storedValue = builder.build().getElementValues().values().iterator().next().getValue();
+    Assert.assertTrue(
+        "storedValue is " + storedValue.getClass(), storedValue instanceof TypeMirror);
+  }
+
+  @Test(expected = BugInCF.class)
+  public void testClassNegative() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, ClassElt.class);
+    builder.setValue("value", 2);
+  }
+
+  public static @interface RestrictedClassElt {
+    Class<? extends Number> value();
+  }
+
+  @Test
+  public void testRestClassPositive() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, RestrictedClassElt.class);
+    builder.setValue("value", Integer.class);
+  }
+
+  // Failing test for now.  AnnotationBuilder is a bit permissive
+  // It doesn't not check type argument subtyping
+  @Test(expected = BugInCF.class)
+  @Ignore // bug for now
+  public void testRetClassNegative() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, RestrictedClassElt.class);
+    builder.setValue("value", String.class);
+  }
+
+  enum MyEnum {
+    OK,
+    NOT;
+  }
+
+  enum OtherEnum {
+    TEST;
+  }
+
+  public static @interface EnumElt {
+    MyEnum value();
+  }
+
+  @Test
+  public void testEnumPositive() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class);
+    builder.setValue("value", MyEnum.OK);
+    builder.setValue("value", MyEnum.NOT);
+  }
+
+  @Test(expected = BugInCF.class)
+  public void testEnumNegative() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class);
+    builder.setValue("value", 2);
+  }
+
+  @Test(expected = BugInCF.class)
+  public void testEnumNegative2() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class);
+    builder.setValue("value", OtherEnum.TEST);
+  }
+
+  public static @interface Anno {
+    String value();
+
+    int[] can();
+  }
+
+  @Test
+  public void testToString1() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class);
+    Assert.assertEquals(
+        "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno",
+        builder.build().toString());
+  }
+
+  @Test
+  public void testToString2() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class);
+    builder.setValue("value", "string");
+    Assert.assertEquals(
+        "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno(\"string\")",
+        builder.build().toString());
+  }
+
+  @Test
+  public void testToString3() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class);
+    builder.setValue("can", new Object[] {1});
+    Assert.assertEquals(
+        "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno(can={1})",
+        builder.build().toString());
+  }
+
+  @Test
+  public void testToString4() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class);
+    builder.setValue("value", "m");
+    builder.setValue("can", new Object[] {1});
+    Assert.assertEquals(
+        "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno"
+            + "(value=\"m\", can={1})",
+        builder.build().toString());
+  }
+
+  @Test
+  public void testToString5() {
+    AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class);
+    builder.setValue("can", new Object[] {1});
+    builder.setValue("value", "m");
+    Assert.assertEquals(
+        "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno"
+            + "(can={1}, value=\"m\")",
+        builder.build().toString());
+  }
+
+  public static @interface MyAnno {}
+
+  public static @interface ContainingAnno {
+    MyAnno value();
+  }
+
+  @Test
+  public void testAnnoAsArgPositive() {
+    AnnotationMirror anno = AnnotationBuilder.fromClass(env.getElementUtils(), MyAnno.class);
+    AnnotationBuilder builder = new AnnotationBuilder(env, ContainingAnno.class);
+    builder.setValue("value", anno);
+    Assert.assertEquals(
+        "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.ContainingAnno(@org.checkerframework.framework.test.junit.AnnotationBuilderTest.MyAnno)",
+        builder.build().toString());
+  }
+
+  @Test(expected = BugInCF.class)
+  public void testAnnoAsArgNegative() {
+    AnnotationMirror anno = AnnotationBuilder.fromClass(env.getElementUtils(), Anno.class);
+    AnnotationBuilder builder = new AnnotationBuilder(env, ContainingAnno.class);
+    builder.setValue("value", anno);
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ClassValTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ClassValTest.java
new file mode 100644
index 0000000..1199ab3
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ClassValTest.java
@@ -0,0 +1,24 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Tests the ClassVal Checker. */
+public class ClassValTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public ClassValTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.reflection.ClassValChecker.class,
+        "classval",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"classval"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/CompoundCheckerTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/CompoundCheckerTest.java
new file mode 100644
index 0000000..8ec4b1f
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/CompoundCheckerTest.java
@@ -0,0 +1,26 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.compound.CompoundChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Tests for the compound checker design pattern. */
+public class CompoundCheckerTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public CompoundCheckerTest(List<File> testFiles) {
+    super(
+        testFiles,
+        CompoundChecker.class,
+        "compound-checker",
+        "-Anomsgtext",
+        "-AsuppressWarnings=type.checking.not.run");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"compound-checker"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingLowerBoundTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingLowerBoundTest.java
new file mode 100644
index 0000000..4f4b4ad
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingLowerBoundTest.java
@@ -0,0 +1,21 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.defaulting.DefaultingLowerBoundChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Created by jburke on 9/29/14. */
+public class DefaultingLowerBoundTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public DefaultingLowerBoundTest(List<File> testFiles) {
+    super(testFiles, DefaultingLowerBoundChecker.class, "defaulting", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"defaulting/lowerbound"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingUpperBoundTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingUpperBoundTest.java
new file mode 100644
index 0000000..017f0f3
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingUpperBoundTest.java
@@ -0,0 +1,21 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.defaulting.DefaultingUpperBoundChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Created by jburke on 9/29/14. */
+public class DefaultingUpperBoundTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public DefaultingUpperBoundTest(List<File> testFiles) {
+    super(testFiles, DefaultingUpperBoundChecker.class, "defaulting", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"defaulting/upperbound"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/Flow2Test.java b/framework/src/test/java/org/checkerframework/framework/test/junit/Flow2Test.java
new file mode 100644
index 0000000..72372d5
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/Flow2Test.java
@@ -0,0 +1,25 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.util.FlowTestChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests for the flow-sensitive part of the framework. These tests complement the tests of {@link
+ * FlowTest} and have been written when the org.checkerframework.dataflow analysis has been
+ * completely rewritten.
+ */
+public class Flow2Test extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public Flow2Test(List<File> testFiles) {
+    super(testFiles, FlowTestChecker.class, "flow", "-Anomsgtext", "-AcheckPurityAnnotations");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"flow2"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/FlowExpressionCheckerTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/FlowExpressionCheckerTest.java
new file mode 100644
index 0000000..480e1eb
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/FlowExpressionCheckerTest.java
@@ -0,0 +1,20 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.flowexpression.FlowExpressionChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+public class FlowExpressionCheckerTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public FlowExpressionCheckerTest(List<File> testFiles) {
+    super(testFiles, FlowExpressionChecker.class, "flowexpression", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"flowexpression", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/FlowTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/FlowTest.java
new file mode 100644
index 0000000..179ad8a
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/FlowTest.java
@@ -0,0 +1,21 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.util.FlowTestChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/** */
+public class FlowTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public FlowTest(List<File> testFiles) {
+    super(testFiles, FlowTestChecker.class, "flow", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"flow", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkTest.java
new file mode 100644
index 0000000..6c0a24c
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkTest.java
@@ -0,0 +1,19 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import org.checkerframework.framework.test.CheckerFrameworkPerFileTest;
+import org.checkerframework.framework.testchecker.util.EvenOddChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Checker Framework, using the {@link EvenOddChecker}. */
+public class FrameworkTest extends CheckerFrameworkPerFileTest {
+
+  public FrameworkTest(File testFile) {
+    super(testFile, EvenOddChecker.class, "framework", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"framework", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/H1H2CheckerTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/H1H2CheckerTest.java
new file mode 100644
index 0000000..e5b2900
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/H1H2CheckerTest.java
@@ -0,0 +1,26 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.h1h2checker.H1H2Checker;
+import org.junit.runners.Parameterized.Parameters;
+
+/** */
+public class H1H2CheckerTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public H1H2CheckerTest(List<File> testFiles) {
+    super(
+        testFiles,
+        H1H2Checker.class,
+        "h1h2checker",
+        "-Anomsgtext",
+        "-Astubs=tests/h1h2checker/h1h2checker.astub");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"h1h2checker"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsTest.java
new file mode 100644
index 0000000..1c319d5
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsTest.java
@@ -0,0 +1,24 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.common.initializedfields.InitializedFieldsChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class InitializedFieldsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a InitializedFieldsTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public InitializedFieldsTest(List<File> testFiles) {
+    super(testFiles, InitializedFieldsChecker.class, "initialized-fields", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"initialized-fields", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsValueTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsValueTest.java
new file mode 100644
index 0000000..766a436
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsValueTest.java
@@ -0,0 +1,33 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class InitializedFieldsValueTest extends CheckerFrameworkPerDirectoryTest {
+
+  /**
+   * Create a InitializedFieldsValueTest.
+   *
+   * @param testFiles the files containing test code, which will be type-checked
+   */
+  public InitializedFieldsValueTest(List<File> testFiles) {
+    super(
+        testFiles,
+        Arrays.asList(
+            "org.checkerframework.common.initializedfields.InitializedFieldsChecker",
+            "org.checkerframework.common.value.ValueChecker"),
+        "initialized-fields-value",
+        Collections.emptyList(), // classpathextra
+        "-Anomsgtext",
+        "-AsuppressWarnings=type.checking.not.run");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"initialized-fields-value", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/LubGlbTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/LubGlbTest.java
new file mode 100644
index 0000000..6c1eff3
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/LubGlbTest.java
@@ -0,0 +1,21 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.lubglb.LubGlbChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/** */
+public class LubGlbTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public LubGlbTest(List<File> testFiles) {
+    super(testFiles, LubGlbChecker.class, "lubglb", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"lubglb"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/MethodValTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/MethodValTest.java
new file mode 100644
index 0000000..5086967
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/MethodValTest.java
@@ -0,0 +1,24 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Tests the MethodVal Checker. */
+public class MethodValTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public MethodValTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.reflection.MethodValChecker.class,
+        "methodval",
+        "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"methodval"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/NonTopDefaultTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/NonTopDefaultTest.java
new file mode 100644
index 0000000..bf43c9d
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/NonTopDefaultTest.java
@@ -0,0 +1,21 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.nontopdefault.NTDChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Tests the NonTopDefault Checker. */
+public class NonTopDefaultTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public NonTopDefaultTest(List<File> testFiles) {
+    super(testFiles, NTDChecker.class, "nontopdefault", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"nontopdefault"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/PuritySuggestionsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/PuritySuggestionsTest.java
new file mode 100644
index 0000000..4b8d52e
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/PuritySuggestionsTest.java
@@ -0,0 +1,27 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.util.FlowTestChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Tests for the {@code -AsuggestPureMethods} command-line argument. */
+public class PuritySuggestionsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public PuritySuggestionsTest(List<File> testFiles) {
+    super(
+        testFiles,
+        FlowTestChecker.class,
+        "flow",
+        "-Anomsgtext",
+        "-AsuggestPureMethods",
+        "-AcheckPurityAnnotations");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"purity-suggestions"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java
new file mode 100644
index 0000000..f05f761
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java
@@ -0,0 +1,665 @@
+package org.checkerframework.framework.test.junit;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import javax.lang.model.type.TypeKind;
+import org.checkerframework.common.value.util.Range;
+import org.junit.Assert;
+import org.junit.Test;
+
+/** This class tests the Range class, independent of the Value Checker. */
+public class RangeTest {
+
+  // These sets of values may be excessively long; perhaps trim them down later.
+
+  long[] rangeBounds = {
+    Long.MIN_VALUE,
+    Long.MIN_VALUE + 1,
+    Integer.MIN_VALUE - 1000L,
+    Integer.MIN_VALUE - 10L,
+    Integer.MIN_VALUE,
+    Integer.MIN_VALUE + 1L,
+    Short.MIN_VALUE - 1000L,
+    Short.MIN_VALUE - 10L,
+    Short.MIN_VALUE,
+    Short.MIN_VALUE + 1L,
+    Character.MIN_VALUE - 1000L,
+    Character.MIN_VALUE - 10L,
+    Character.MIN_VALUE, // 0L
+    Character.MIN_VALUE + 1L,
+    Byte.MIN_VALUE - 1000L,
+    Byte.MIN_VALUE - 10L,
+    Byte.MIN_VALUE,
+    Byte.MIN_VALUE + 1L,
+    Byte.MAX_VALUE - 1L,
+    Byte.MAX_VALUE,
+    Byte.MAX_VALUE + 10L,
+    Byte.MAX_VALUE + 1000L,
+    Short.MAX_VALUE - 1L,
+    Short.MAX_VALUE,
+    Short.MAX_VALUE + 10L,
+    Short.MAX_VALUE + 1000L,
+    Character.MAX_VALUE - 1L,
+    Character.MAX_VALUE,
+    Character.MAX_VALUE + 10L,
+    Character.MAX_VALUE + 1000L,
+    Integer.MAX_VALUE - 1,
+    Integer.MAX_VALUE,
+    Integer.MAX_VALUE + 10L,
+    Integer.MAX_VALUE + 1000L,
+    Long.MAX_VALUE - 1,
+    Long.MAX_VALUE
+  };
+  long[] values = {
+    Long.MIN_VALUE,
+    Long.MIN_VALUE + 1L,
+    Integer.MIN_VALUE,
+    Integer.MIN_VALUE + 1L,
+    Short.MIN_VALUE,
+    Short.MIN_VALUE + 1L,
+    Byte.MIN_VALUE,
+    Byte.MIN_VALUE + 1L,
+    -8L,
+    -4L,
+    -2L,
+    -1L,
+    Character.MIN_VALUE, // 0L
+    Character.MIN_VALUE + 1L, // 1L
+    2L,
+    4L,
+    8L,
+    Byte.MAX_VALUE - 1L,
+    Byte.MAX_VALUE,
+    Short.MAX_VALUE - 1L,
+    Short.MAX_VALUE,
+    Character.MAX_VALUE - 1L,
+    Character.MAX_VALUE,
+    Integer.MAX_VALUE - 1L,
+    Integer.MAX_VALUE,
+    Long.MAX_VALUE - 1L,
+    Long.MAX_VALUE
+  };
+
+  /** Contains a Range for every combination of values in rangeBounds. */
+  Range[] ranges;
+
+  static final long INT_WIDTH = (long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE + 1;
+
+  static final long SHORT_WIDTH = Short.MAX_VALUE - Short.MIN_VALUE + 1;
+
+  static final long BYTE_WIDTH = Byte.MAX_VALUE - Byte.MIN_VALUE + 1;
+
+  static final long CHAR_WIDTH = Character.MAX_VALUE - Character.MIN_VALUE + 1;
+
+  public RangeTest() {
+    // Initialize the ranges list to every combination of values in rangeBounds.
+    List<Range> rangesList = new ArrayList<>();
+    for (long lowerbound : rangeBounds) {
+      for (long upperbound : rangeBounds) {
+        if (lowerbound <= upperbound) {
+          rangesList.add(Range.create(lowerbound, upperbound));
+        }
+      }
+    }
+    ranges = rangesList.toArray(new Range[0]);
+  }
+
+  /** The element is a member of the range. */
+  class RangeAndElement {
+    Range range;
+    long element;
+
+    RangeAndElement(Range range, long element) {
+      if (!range.contains(element)) {
+        throw new IllegalArgumentException();
+      }
+      this.range = range;
+      this.element = element;
+    }
+  }
+
+  class RangeAndTwoElements {
+    Range range;
+    long a;
+    long b;
+  }
+
+  ValuesInRangeIterator valuesInRange(Range r) {
+    return new ValuesInRangeIterator(r);
+  }
+
+  RangeAndElementIterator rangeAndElements() {
+    return new RangeAndElementIterator();
+  }
+
+  class RangeAndElementIterator implements Iterator<RangeAndElement>, Iterable<RangeAndElement> {
+    // This is the index of the range that is currently being examined.
+    // It is in [0..ranges.length].
+    int ri;
+    Range range;
+    ValuesInRangeIterator vi;
+    RangeAndElement nextValue;
+    boolean nextValueValid = false;
+
+    public RangeAndElementIterator() {
+      ri = 0;
+      range = ranges[ri];
+      vi = new ValuesInRangeIterator(range);
+    }
+
+    @Override
+    public RangeAndElement next() {
+      if (!hasNext()) {
+        throw new NoSuchElementException();
+      }
+      nextValueValid = false;
+      return nextValue;
+    }
+
+    @Override
+    public boolean hasNext() {
+      if (nextValueValid) {
+        return true;
+      }
+      while (!vi.hasNext()) {
+        ri++;
+        if (ri == ranges.length) {
+          return false;
+        }
+        range = ranges[ri];
+        vi = new ValuesInRangeIterator(range);
+      }
+      nextValue = new RangeAndElement(range, vi.next());
+      nextValueValid = true;
+      return true;
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Iterator<RangeAndElement> iterator() {
+      return this;
+    }
+  }
+
+  class ValuesInRangeIterator implements Iterator<Long>, Iterable<Long> {
+
+    Range range;
+    // This is the first index that has NOT been examined.  It is in [0..values.length].
+    int i = 0;
+    long nextValue;
+    boolean nextValueValid = false;
+
+    public ValuesInRangeIterator(Range range) {
+      this.range = range;
+      Range.ignoreOverflow = false;
+    }
+
+    @Override
+    public Long next() {
+      if (!hasNext()) {
+        throw new NoSuchElementException();
+      }
+      nextValueValid = false;
+      return nextValue;
+    }
+
+    @Override
+    public boolean hasNext() {
+      if (nextValueValid) {
+        return true;
+      }
+      while (i < values.length) {
+        nextValue = values[i];
+        i++;
+        if (range.contains(nextValue)) {
+          nextValueValid = true;
+          break;
+        }
+      }
+      return nextValueValid;
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Iterator<Long> iterator() {
+      return this;
+    }
+  }
+
+  @Test
+  public void testIntRange() {
+    for (Range range : ranges) {
+      Range result = range.intRange();
+      for (long value : values) {
+        if (value < range.from + INT_WIDTH
+            && value > range.to - INT_WIDTH
+            && (Math.abs(range.from) - 1) / Integer.MIN_VALUE
+                == (Math.abs(range.to) - 1) / Integer.MIN_VALUE) {
+          // filter out test data that would cause Range.intRange to return INT_EVERYTHING
+          int intValue = (int) value;
+          assert range.contains(value) && result.contains(intValue)
+                  || !range.contains(value) && !result.contains(intValue)
+              : String.format(
+                  "Range.intRange failure: %s => %s; witness = %s", range, result, intValue);
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testShortRange() {
+    for (Range range : ranges) {
+      Range result = range.shortRange();
+      for (long value : values) {
+        if (value < range.from + SHORT_WIDTH
+            && value > range.to - SHORT_WIDTH
+            && (Math.abs(range.from) - 1) / Short.MIN_VALUE
+                == (Math.abs(range.to) - 1) / Short.MIN_VALUE) {
+          // filter out test data that would cause Range.shortRange to return
+          // SHORT_EVERYTHING
+          short shortValue = (short) value;
+          assert range.contains(value) && result.contains(shortValue)
+                  || !range.contains(value) && !result.contains(shortValue)
+              : String.format(
+                  "Range.shortRange failure: %s => %s; witness = %s", range, result, shortValue);
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testCharRange() {
+    Range.ignoreOverflow = false;
+    for (Range range : ranges) {
+      Range result = range.charRange();
+      for (long value : values) {
+        if (value < range.from + CHAR_WIDTH
+            && value > range.to - CHAR_WIDTH
+            && (Math.abs(range.from + Short.MIN_VALUE) - 1) / Short.MIN_VALUE
+                == (Math.abs(range.to + Short.MIN_VALUE) - 1) / Short.MIN_VALUE) {
+          // filter out test data that would cause Range.CharRange to return
+          // CHAR_EVERYTHING
+          // char range interval is a right shift of the short range interval
+          char charValue = (char) value;
+          assert range.contains(value) && result.contains(charValue)
+                  || !range.contains(value) && !result.contains(charValue)
+              : String.format(
+                  "Range.byteRange failure: %s => %s; witness = %s", range, result, charValue);
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testByteRange() {
+    for (Range range : ranges) {
+      Range result = range.byteRange();
+      for (long value : values) {
+        if (value < range.from + BYTE_WIDTH
+            && value > range.to - BYTE_WIDTH
+            && (Math.abs(range.from) - 1) / Byte.MIN_VALUE
+                == (Math.abs(range.to) - 1) / Byte.MIN_VALUE) {
+          // filter out test data that would cause Range.ByteRange to return
+          // BYTE_EVERYTHING
+          byte byteValue = (byte) value;
+          assert range.contains(value) && result.contains(byteValue)
+                  || !range.contains(value) && !result.contains(byteValue)
+              : String.format(
+                  "Range.byteRange failure: %s => %s; witness = %s", range, result, byteValue);
+        }
+      }
+    }
+
+    Range r1 = Range.create(5, 1000);
+    Range r2 = Range.create(1024 + 17, 1024 + 22);
+    Range r3 = Range.create(5, Byte.MAX_VALUE + 2);
+
+    Range.ignoreOverflow = true;
+
+    assert r1.byteRange().equals(Range.create(5, Byte.MAX_VALUE));
+    assert r2.byteRange().equals(Range.create(Byte.MAX_VALUE, Byte.MAX_VALUE));
+    assert r3.byteRange().equals(Range.create(5, Byte.MAX_VALUE));
+
+    Range.ignoreOverflow = false;
+
+    assert r1.byteRange().equals(Range.BYTE_EVERYTHING);
+    assert r2.byteRange().equals(Range.create(17, 22));
+    assert r3.byteRange().equals(Range.BYTE_EVERYTHING);
+  }
+
+  @Test
+  public void testUnion() {
+    for (Range range1 : ranges) {
+      for (Range range2 : ranges) {
+        Range result = range1.union(range2);
+        for (long value : values) {
+          if (range1.contains(value) || range2.contains(value)) {
+            assert result.contains(value)
+                : String.format(
+                    "Range.union failure: %s %s %s; witness = %s", range1, range2, result, value);
+          }
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testIntersect() {
+    for (Range range1 : ranges) {
+      for (Range range2 : ranges) {
+        Range result = range1.intersect(range2);
+        for (long value : values) {
+          assert ((range1.contains(value) && range2.contains(value)) == (result.contains(value)))
+              : String.format(
+                  "Range.intersect failure: %s %s => %s; witness = %s",
+                  range1, range2, result, value);
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testPlus() {
+    for (RangeAndElement re1 : rangeAndElements()) {
+      for (RangeAndElement re2 : rangeAndElements()) {
+        Range result = re1.range.plus(re2.range);
+        assert result.contains(re1.element + re2.element)
+            : String.format(
+                "Range.plus failure: %s %s => %s; witnesses %s + %s => %s",
+                re1.range, re2.range, result, re1.element, re2.element, re1.element + re2.element);
+      }
+    }
+  }
+
+  @Test
+  public void testMinus() {
+    for (RangeAndElement re1 : rangeAndElements()) {
+      for (RangeAndElement re2 : rangeAndElements()) {
+        Range result = re1.range.minus(re2.range);
+        assert result.contains(re1.element - re2.element)
+            : String.format(
+                "Range.minus failure: %s %s => %s; witnesses %s - %s => %s",
+                re1.range, re2.range, result, re1.element, re2.element, re1.element - re2.element);
+      }
+    }
+  }
+
+  @Test
+  public void testTimes() {
+    for (RangeAndElement re1 : rangeAndElements()) {
+      for (RangeAndElement re2 : rangeAndElements()) {
+        Range result = re1.range.times(re2.range);
+        assert result.contains(re1.element * re2.element)
+            : String.format(
+                "Range.times failure: %s %s => %s; witnesses %s * %s => %s",
+                re1.range, re2.range, result, re1.element, re2.element, re1.element * re2.element);
+      }
+    }
+  }
+
+  @Test
+  public void testDivide() {
+    assert Range.create(1, 2).divide(Range.create(0, 0)) == Range.NOTHING;
+    for (RangeAndElement re1 : rangeAndElements()) {
+      for (RangeAndElement re2 : rangeAndElements()) {
+        if (re2.element == 0) {
+          continue;
+        }
+        Range result = re1.range.divide(re2.range);
+        Long witness = re1.element / re2.element;
+        assert result.contains(witness)
+            : String.format(
+                "Range.divide failure: %s %s => %s; witnesses %s / %s => %s",
+                re1.range, re2.range, result, re1.element, re2.element, witness);
+      }
+    }
+  }
+
+  @Test
+  public void testRemainder() {
+    assert Range.create(1, 2).remainder(Range.create(0, 0)) == Range.NOTHING;
+    for (RangeAndElement re1 : rangeAndElements()) {
+      for (RangeAndElement re2 : rangeAndElements()) {
+        if (re2.element == 0) {
+          continue;
+        }
+        Range result = re1.range.remainder(re2.range);
+        Long witness = re1.element % re2.element;
+        assert result.contains(witness)
+            : String.format(
+                "Range.remainder failure: %s %s => %s; witnesses %s %% %s => %s",
+                re1.range, re2.range, result, re1.element, re2.element, witness);
+      }
+    }
+  }
+
+  @Test
+  public void testShiftLeft() {
+    for (RangeAndElement re1 : rangeAndElements()) {
+      for (RangeAndElement re2 : rangeAndElements()) {
+        Range result = re1.range.shiftLeft(re2.range);
+        assert result.contains(re1.element << re2.element)
+            : String.format(
+                "Range.shiftLeft failure: %s %s => %s; witnesses %s << %s => %s",
+                re1.range, re2.range, result, re1.element, re2.element, re1.element << re2.element);
+      }
+    }
+  }
+
+  @Test
+  public void testSignedShiftRight() {
+    for (RangeAndElement re1 : rangeAndElements()) {
+      for (RangeAndElement re2 : rangeAndElements()) {
+        Range result = re1.range.signedShiftRight(re2.range);
+        assert result.contains(re1.element >> re2.element)
+            : String.format(
+                "Range.signedShiftRight failure: %s %s => %s; witnesses %s >> %s => %s",
+                re1.range, re2.range, result, re1.element, re2.element, re1.element >> re2.element);
+      }
+    }
+  }
+
+  @Test
+  public void testUnsignedShiftRight() {
+    for (RangeAndElement re1 : rangeAndElements()) {
+      for (RangeAndElement re2 : rangeAndElements()) {
+        Range result = re1.range.unsignedShiftRight(re2.range);
+        if (re1.range.from >= 0) {
+          assert result.contains(re1.element >>> re2.element)
+              : String.format(
+                  "Range.unsignedShiftRight failure: %s %s => %s; witnesses %s >> %s => %s",
+                  re1.range,
+                  re2.range,
+                  result,
+                  re1.element,
+                  re2.element,
+                  re1.element >> re2.element);
+        } else {
+          assert result == Range.EVERYTHING;
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testUnaryPlus() {
+    for (RangeAndElement re : rangeAndElements()) {
+      Range result = re.range.unaryPlus();
+      assert result.contains(+re.element)
+          : String.format(
+              "Range.unaryPlus failure: %s => %s; witness %s => %s",
+              re.range, result, re.element, +re.element);
+    }
+  }
+
+  @Test
+  public void testUnaryMinus() {
+    for (RangeAndElement re : rangeAndElements()) {
+      Range result = re.range.unaryMinus();
+      assert result.contains(-re.element)
+          : String.format(
+              "Range.unaryMinus failure: %s => %s; witness %s => %s",
+              re.range, result, re.element, -re.element);
+    }
+  }
+
+  @Test
+  public void testBitwiseComplement() {
+    for (RangeAndElement re : rangeAndElements()) {
+      Range result = re.range.bitwiseComplement();
+      assert result.contains(~re.element)
+          : String.format(
+              "Range.bitwiseComplement failure: %s => %s; witness %s => %s",
+              re.range, result, re.element, ~re.element);
+    }
+  }
+
+  @Test
+  public void testBitwiseAnd() {
+    for (RangeAndElement re1 : rangeAndElements()) {
+      for (RangeAndElement re2 : rangeAndElements()) {
+        Range result1 = re1.range.bitwiseAnd(re2.range);
+        Range result2 = re2.range.bitwiseAnd(re1.range);
+        if (re1.range.isConstant() || re2.range.isConstant()) {
+          Long witness = re1.element & re2.element;
+          assert result1.from == result2.from;
+          assert result1.to == result2.to;
+          assert result1.contains(witness)
+              : String.format(
+                  "Range.bitwiseAnd failure: %s %s => %s; witnesses %s & %s => %s",
+                  re1.range, re2.range, result1, re1.element, re2.element, witness);
+        } else {
+          assert result1 == Range.EVERYTHING;
+          assert result2 == Range.EVERYTHING;
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testLessThan() {
+    for (Range range1 : ranges) {
+      for (Range range2 : ranges) {
+        for (long value : values) {
+          Range result = range1.refineLessThan(range2);
+          assert (value >= range2.to
+                  ? !result.contains(value)
+                  : range1.contains(value) == result.contains(value))
+              : String.format(
+                  "Range.refineLessThan failure: %s %s %s; witness = %s",
+                  range1, range2, result, value);
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testLessThanEq() {
+    for (Range range1 : ranges) {
+      for (Range range2 : ranges) {
+        for (long value : values) {
+          Range result = range1.refineLessThanEq(range2);
+          assert (value > range2.to
+                  ? !result.contains(value)
+                  : range1.contains(value) == result.contains(value))
+              : String.format(
+                  "Range.refineLessThanEq failure: %s %s %s; witness = %s",
+                  range1, range2, result, value);
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testGreaterThan() {
+    for (Range range1 : ranges) {
+      for (Range range2 : ranges) {
+        for (long value : values) {
+          Range result = range1.refineGreaterThan(range2);
+          assert (value <= range2.from
+                  ? !result.contains(value)
+                  : range1.contains(value) == result.contains(value))
+              : String.format(
+                  "Range.refineGreaterThan failure: %s %s %s; witness = %s",
+                  range1, range2, result, value);
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testGreaterThanEq() {
+    for (Range range1 : ranges) {
+      for (Range range2 : ranges) {
+        for (long value : values) {
+          Range result = range1.refineGreaterThanEq(range2);
+          assert (value < range2.from
+                  ? !result.contains(value)
+                  : range1.contains(value) == result.contains(value))
+              : String.format(
+                  "Range.refineGreaterThanEq failure: %s %s %s; witness = %s",
+                  range1, range2, result, value);
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testEqualTo() {
+    for (Range range1 : ranges) {
+      for (Range range2 : ranges) {
+        for (long value : values) {
+          Range result = range1.refineEqualTo(range2);
+          assert (value < range2.from || value > range2.to
+                  ? !result.contains(value)
+                  : range1.contains(value) == result.contains(value))
+              : String.format(
+                  "Range.refineEqualTo failure: %s %s %s; witness = %s",
+                  range1, range2, result, value);
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testFactoryLongLong() {
+    Assert.assertEquals((long) 1, Range.create(1, 2).from);
+    Assert.assertEquals((long) 2, Range.create(1, 2).to);
+  }
+
+  @Test
+  public void testFactoryList() {
+    Assert.assertEquals((long) 1, Range.create(Arrays.asList(1, 2, 3)).from);
+    Assert.assertEquals((long) 3, Range.create(Arrays.asList(1, 2, 3)).to);
+    Assert.assertEquals((long) 1, Range.create(Arrays.asList(3, 2, 1)).from);
+    Assert.assertEquals((long) 3, Range.create(Arrays.asList(3, 2, 1)).to);
+    Assert.assertEquals(Range.NOTHING, Range.create(Collections.<Integer>emptyList()));
+    Assert.assertTrue(Range.NOTHING == Range.create(Collections.<Integer>emptyList()));
+  }
+
+  @Test
+  public void testFactoryTypeKind() {
+    Assert.assertEquals(Range.BYTE_EVERYTHING, Range.create(TypeKind.BYTE));
+    Assert.assertEquals(Range.INT_EVERYTHING, Range.create(TypeKind.INT));
+    Assert.assertEquals(Range.SHORT_EVERYTHING, Range.create(TypeKind.SHORT));
+    Assert.assertEquals(Range.CHAR_EVERYTHING, Range.create(TypeKind.CHAR));
+    Assert.assertEquals(Range.LONG_EVERYTHING, Range.create(TypeKind.LONG));
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testFactoryTypeKindFailure() {
+    Range.create(TypeKind.FLOAT);
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReflectionTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReflectionTest.java
new file mode 100644
index 0000000..845bae7
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReflectionTest.java
@@ -0,0 +1,35 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.reflection.ReflectionTestChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Tests the reflection resolution using a simple type system. */
+public class ReflectionTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public ReflectionTest(List<File> testFiles) {
+    super(testFiles, ReflectionTestChecker.class, "reflection", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"reflection"};
+  }
+
+  @Override
+  public List<String> customizeOptions(List<String> previousOptions) {
+    final List<String> optionsWithStub = new ArrayList<>(checkerOptions);
+    optionsWithStub.add("-Astubs=" + getFullPath(testFiles.get(0), "reflection.astub"));
+    optionsWithStub.add("-AresolveReflection");
+    return optionsWithStub;
+  }
+
+  protected String getFullPath(final File javaFile, final String filename) {
+    final String dirname = javaFile.getParentFile().getAbsolutePath();
+    return dirname + File.separator + filename;
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportModifiersTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportModifiersTest.java
new file mode 100644
index 0000000..8f0b68e
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportModifiersTest.java
@@ -0,0 +1,24 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class ReportModifiersTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public ReportModifiersTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.util.report.ReportChecker.class,
+        "report",
+        "-Anomsgtext",
+        "-AreportModifiers=native");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"reportmodifiers"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTest.java
new file mode 100644
index 0000000..c380c90
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTest.java
@@ -0,0 +1,24 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class ReportTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public ReportTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.util.report.ReportChecker.class,
+        "report",
+        "-Anomsgtext",
+        "-Astubs=tests/report/reporttest.astub");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"report"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTreeKindsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTreeKindsTest.java
new file mode 100644
index 0000000..c777da1
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTreeKindsTest.java
@@ -0,0 +1,24 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class ReportTreeKindsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public ReportTreeKindsTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.util.report.ReportChecker.class,
+        "report",
+        "-Anomsgtext",
+        "-AreportTreeKinds=WHILE_LOOP,CONDITIONAL_AND");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"reporttreekinds"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverAutoValueTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverAutoValueTest.java
new file mode 100644
index 0000000..fc5b5e4
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverAutoValueTest.java
@@ -0,0 +1,34 @@
+package org.checkerframework.framework.test.junit;
+
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** tests the returns receiver checker's AutoValue integration. */
+public class ReturnsReceiverAutoValueTest extends CheckerFrameworkPerDirectoryTest {
+
+  public ReturnsReceiverAutoValueTest(List<File> testFiles) {
+    super(
+        testFiles,
+        ImmutableList.of(
+            "com.google.auto.value.extension.memoized.processor.MemoizedValidator",
+            "com.google.auto.value.processor.AutoAnnotationProcessor",
+            "com.google.auto.value.processor.AutoOneOfProcessor",
+            "com.google.auto.value.processor.AutoValueBuilderProcessor",
+            "com.google.auto.value.processor.AutoValueProcessor",
+            ReturnsReceiverChecker.class.getName()),
+        "basic",
+        Collections.emptyList(),
+        "-Anomsgtext",
+        "-nowarn");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"returnsreceiverautovalue"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverLombokTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverLombokTest.java
new file mode 100644
index 0000000..25b899b
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverLombokTest.java
@@ -0,0 +1,38 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests the returns receiver checker's lombok integration, the test files in
+ * tests/returnsreceiverlombok package will be delomboked into tests/returnsreceiverdelomboked
+ * package before running the test and the returns receiver checker will run on the generated codes.
+ */
+public class ReturnsReceiverLombokTest extends CheckerFrameworkPerDirectoryTest {
+  public ReturnsReceiverLombokTest(List<File> testFiles) {
+    super(
+        testFiles,
+        ReturnsReceiverChecker.class,
+        "returnsreceiverdelomboked",
+        "-Anomsgtext",
+        "-nowarn",
+        "-AsuppressWarnings=type.anno.before.modifier");
+  }
+
+  @Override
+  public void run() {
+    // Only run if delomboked codes have been created.
+    if (!new File("tests/returnsreceiverdelomboked/").exists()) {
+      throw new RuntimeException("delombok task must be run before this test.");
+    }
+    super.run();
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"returnsreceiverdelomboked"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverTest.java
new file mode 100644
index 0000000..e398a4b
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverTest.java
@@ -0,0 +1,30 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test runner for tests of the Returns Receiver Checker.
+ *
+ * <p>Tests appear as Java files in the {@code tests/returnsreceiver} folder. To add a new test
+ * case, create a Java file in that directory.
+ */
+public class ReturnsReceiverTest extends CheckerFrameworkPerDirectoryTest {
+  public ReturnsReceiverTest(List<File> testFiles) {
+    super(
+        testFiles,
+        ReturnsReceiverChecker.class,
+        "returnsreceiver",
+        "-Anomsgtext",
+        "-Astubs=stubs/",
+        "-nowarn");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"returnsreceiver", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingEncryptedTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingEncryptedTest.java
new file mode 100644
index 0000000..a7037d4
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingEncryptedTest.java
@@ -0,0 +1,25 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Test suite for the Subtyping Checker, using a simple {@link Encrypted} annotation. */
+public class SubtypingEncryptedTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public SubtypingEncryptedTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.subtyping.SubtypingChecker.class,
+        "subtyping",
+        "-Anomsgtext",
+        "-Aquals=org.checkerframework.framework.testchecker.util.Encrypted,org.checkerframework.framework.testchecker.util.PolyEncrypted,org.checkerframework.common.subtyping.qual.Unqualified");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"subtyping", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingStringPatternsFullTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingStringPatternsFullTest.java
new file mode 100644
index 0000000..610c839
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingStringPatternsFullTest.java
@@ -0,0 +1,25 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Test suite for the Subtyping Checker, using {@code @QualifierForLiterals}. */
+public class SubtypingStringPatternsFullTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public SubtypingStringPatternsFullTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.subtyping.SubtypingChecker.class,
+        "stringpatterns/stringpatterns-full",
+        "-Anomsgtext",
+        "-Aquals=org.checkerframework.framework.testchecker.util.PatternUnknown,org.checkerframework.framework.testchecker.util.PatternAB,org.checkerframework.framework.testchecker.util.PatternBC,org.checkerframework.framework.testchecker.util.PatternAC,org.checkerframework.framework.testchecker.util.PatternA,org.checkerframework.framework.testchecker.util.PatternB,org.checkerframework.framework.testchecker.util.PatternC,org.checkerframework.framework.testchecker.util.PatternBottomFull");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"stringpatterns/stringpatterns-full", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SupportedQualsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SupportedQualsTest.java
new file mode 100644
index 0000000..34cf989
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SupportedQualsTest.java
@@ -0,0 +1,20 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.supportedquals.SupportedQualsChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+public class SupportedQualsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public SupportedQualsTest(List<File> testFiles) {
+    super(testFiles, SupportedQualsChecker.class, "simple", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"simple"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/TreeParserTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/TreeParserTest.java
new file mode 100644
index 0000000..6d30c36
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/TreeParserTest.java
@@ -0,0 +1,94 @@
+package org.checkerframework.framework.test.junit;
+
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import javax.annotation.processing.ProcessingEnvironment;
+import org.checkerframework.javacutil.trees.TreeParser;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TreeParserTest {
+  private final ProcessingEnvironment env;
+  private final TreeParser parser;
+
+  public TreeParserTest() {
+    env = JavacProcessingEnvironment.instance(new Context());
+    parser = new TreeParser(env);
+  }
+
+  @Test
+  public void parsesIdentifiers() {
+    String value = "id";
+    ExpressionTree parsed = parser.parseTree(value);
+
+    Assert.assertTrue(parsed instanceof IdentifierTree);
+  }
+
+  @Test
+  public void parsesNumbers() {
+    String value = "23";
+    ExpressionTree parsed = parser.parseTree(value);
+
+    Assert.assertTrue(parsed instanceof LiteralTree);
+  }
+
+  @Test
+  public void parsesMethodInvocations() {
+    String value = "test()";
+    ExpressionTree parsed = parser.parseTree(value);
+
+    Assert.assertTrue(parsed instanceof MethodInvocationTree);
+    MethodInvocationTree invocation = (MethodInvocationTree) parsed;
+    Assert.assertTrue(invocation.getMethodSelect() instanceof IdentifierTree);
+    Assert.assertEquals(
+        "test", ((IdentifierTree) invocation.getMethodSelect()).getName().toString());
+  }
+
+  @Test
+  public void parsesMethodInvocationsWithSelect() {
+    String value = "Class.test()";
+    ExpressionTree parsed = parser.parseTree(value);
+
+    Assert.assertTrue(parsed instanceof MethodInvocationTree);
+    MethodInvocationTree invocation = (MethodInvocationTree) parsed;
+    Assert.assertTrue(invocation.getMethodSelect() instanceof MemberSelectTree);
+    MemberSelectTree select = (MemberSelectTree) invocation.getMethodSelect();
+    Assert.assertEquals("test", select.getIdentifier().toString());
+    Assert.assertEquals("Class", select.getExpression().toString());
+  }
+
+  @Test
+  public void parsesIndex() {
+    String value = "array[2]";
+    ExpressionTree parsed = parser.parseTree(value);
+
+    Assert.assertTrue(parsed instanceof ArrayAccessTree);
+    ArrayAccessTree access = (ArrayAccessTree) parsed;
+
+    Assert.assertEquals(2, ((LiteralTree) access.getIndex()).getValue());
+    Assert.assertEquals("array", ((IdentifierTree) access.getExpression()).getName().toString());
+  }
+
+  @Test
+  public void randomParses() {
+    ExpressionTree parsed = parser.parseTree("Class.method()[4].field[3]");
+
+    Assert.assertTrue(parsed instanceof ArrayAccessTree);
+    MemberSelectTree array = (MemberSelectTree) ((ArrayAccessTree) parsed).getExpression();
+    Assert.assertEquals("field", array.getIdentifier().toString());
+    Assert.assertTrue(array.getExpression() instanceof ArrayAccessTree);
+  }
+
+  @Test
+  public void parsesMethodArguments() {
+    parser.parseTree("method()");
+    parser.parseTree("method(1)");
+    parser.parseTree("method(1,2)");
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclDefaultTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclDefaultTest.java
new file mode 100644
index 0000000..885d3d1
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclDefaultTest.java
@@ -0,0 +1,26 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.typedecldefault.TypeDeclDefaultChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Create the TypeDeclDefault test. */
+public class TypeDeclDefaultTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public TypeDeclDefaultTest(List<File> testFiles) {
+    super(
+        testFiles,
+        TypeDeclDefaultChecker.class,
+        "typedecldefault",
+        "-Anomsgtext",
+        "-Astubs=tests/typedecldefault/jdk.astub");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"typedecldefault"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueIgnoreRangeOverflowTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueIgnoreRangeOverflowTest.java
new file mode 100644
index 0000000..6d11e97
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueIgnoreRangeOverflowTest.java
@@ -0,0 +1,27 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Tests the constant value propagation type system without overflow. */
+public class ValueIgnoreRangeOverflowTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public ValueIgnoreRangeOverflowTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.value.ValueChecker.class,
+        "value",
+        "-Anomsgtext",
+        "-A" + ValueChecker.REPORT_EVAL_WARNS,
+        "-A" + ValueChecker.IGNORE_RANGE_OVERFLOW);
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"value", "all-systems", "value-ignore-range-overflow"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueNonNullStringsConcatenationTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueNonNullStringsConcatenationTest.java
new file mode 100644
index 0000000..1c74d7f
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueNonNullStringsConcatenationTest.java
@@ -0,0 +1,26 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+public class ValueNonNullStringsConcatenationTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public ValueNonNullStringsConcatenationTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.value.ValueChecker.class,
+        "value-non-null-strings-concatenation",
+        "-Anomsgtext",
+        "-A" + ValueChecker.REPORT_EVAL_WARNS,
+        "-A" + ValueChecker.NON_NULL_STRINGS_CONCATENATION);
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"all-systems", "value-non-null-strings-concatenation"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java
new file mode 100644
index 0000000..ecade2c
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java
@@ -0,0 +1,33 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests the constant value propagation type system.
+ *
+ * <p>NOTE: $CHECKERFRAMEWORK/framework/tests/value/ needs to be on the classpath. Otherwise
+ * ExceptionTest will fail because it cannot find the ExceptionTest.class file for reflective method
+ * resolution.
+ */
+public class ValueTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public ValueTest(List<File> testFiles) {
+    super(
+        testFiles,
+        org.checkerframework.common.value.ValueChecker.class,
+        "value",
+        "-Anomsgtext",
+        "-Astubs=tests/value/minints-stub.astub:tests/value/lowercase.astub",
+        "-A" + ValueChecker.REPORT_EVAL_WARNS);
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"value", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueUncheckedDefaultsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueUncheckedDefaultsTest.java
new file mode 100644
index 0000000..3c598c5
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueUncheckedDefaultsTest.java
@@ -0,0 +1,28 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Tests conservative defaults for the constant value propagation type system. */
+public class ValueUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public ValueUncheckedDefaultsTest(List<File> testFiles) {
+    super(
+        testFiles,
+        ValueChecker.class,
+        "value",
+        "-Anomsgtext",
+        "-AuseConservativeDefaultsForUncheckedCode=btyecode",
+        "-A" + ValueChecker.REPORT_EVAL_WARNS);
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    // The defaults for unchecked code should be the same as checked code, so use the same tests.
+    return new String[] {"value", "all-systems"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/VariableNameDefaultTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/VariableNameDefaultTest.java
new file mode 100644
index 0000000..1003f54
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/test/junit/VariableNameDefaultTest.java
@@ -0,0 +1,21 @@
+package org.checkerframework.framework.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.checkerframework.framework.testchecker.variablenamedefault.VariableNameDefaultChecker;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Create the VariableNameDefault test. */
+public class VariableNameDefaultTest extends CheckerFrameworkPerDirectoryTest {
+
+  /** @param testFiles the files containing test code, which will be type-checked */
+  public VariableNameDefaultTest(List<File> testFiles) {
+    super(testFiles, VariableNameDefaultChecker.class, "variablenamedefault", "-Anomsgtext");
+  }
+
+  @Parameters
+  public static String[] getTestDirs() {
+    return new String[] {"variablenamedefault"};
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/AggregateOfCompoundChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/AggregateOfCompoundChecker.java
new file mode 100644
index 0000000..853afd8
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/AggregateOfCompoundChecker.java
@@ -0,0 +1,17 @@
+package org.checkerframework.framework.testchecker.aggregate;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.source.AggregateChecker;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.testchecker.compound.CompoundChecker;
+
+/** An aggregate checker where one of the checkers is a compound checker. */
+public class AggregateOfCompoundChecker extends AggregateChecker {
+
+  @Override
+  protected Collection<Class<? extends SourceChecker>> getSupportedCheckers() {
+    return Arrays.asList(ValueChecker.class, CompoundChecker.class);
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/TestAggregateChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/TestAggregateChecker.java
new file mode 100644
index 0000000..90bea58
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/TestAggregateChecker.java
@@ -0,0 +1,17 @@
+package org.checkerframework.framework.testchecker.aggregate;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.checkerframework.common.aliasing.AliasingChecker;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.source.AggregateChecker;
+import org.checkerframework.framework.source.SourceChecker;
+
+/** Basic aggregate checker. */
+public class TestAggregateChecker extends AggregateChecker {
+
+  @Override
+  protected Collection<Class<? extends SourceChecker>> getSupportedCheckers() {
+    return Arrays.asList(ValueChecker.class, AliasingChecker.class);
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundChecker.java
new file mode 100644
index 0000000..d2542d5
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundChecker.java
@@ -0,0 +1,32 @@
+package org.checkerframework.framework.testchecker.compound;
+
+import java.util.LinkedHashSet;
+import org.checkerframework.common.aliasing.AliasingChecker;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.value.ValueChecker;
+
+public class AnotherCompoundChecker extends BaseTypeChecker {
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    // Make sure that options can be accessed by sub-checkers to determine
+    // which subcheckers to run.
+    @SuppressWarnings("unused")
+    String option = super.getOption("nomsgtext");
+    LinkedHashSet<Class<? extends BaseTypeChecker>> subcheckers = new LinkedHashSet<>();
+    subcheckers.addAll(super.getImmediateSubcheckerClasses());
+    subcheckers.add(AliasingChecker.class);
+    subcheckers.add(ValueChecker.class);
+    return subcheckers;
+  }
+
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new BaseTypeVisitor<AnotherCompoundCheckerAnnotatedTypeFactory>(this) {
+      @Override
+      protected AnotherCompoundCheckerAnnotatedTypeFactory createTypeFactory() {
+        return new AnotherCompoundCheckerAnnotatedTypeFactory(checker);
+      }
+    };
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundCheckerAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundCheckerAnnotatedTypeFactory.java
new file mode 100644
index 0000000..b1478c4
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundCheckerAnnotatedTypeFactory.java
@@ -0,0 +1,52 @@
+package org.checkerframework.framework.testchecker.compound;
+
+import com.sun.source.tree.Tree;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.common.aliasing.AliasingChecker;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.testchecker.compound.qual.ACCBottom;
+import org.checkerframework.framework.testchecker.compound.qual.ACCTop;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+
+public class AnotherCompoundCheckerAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  public AnotherCompoundCheckerAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<Class<? extends Annotation>>(Arrays.asList(ACCTop.class, ACCBottom.class));
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(
+        super.createTreeAnnotator(),
+        new TreeAnnotator(this) {
+          @Override
+          protected Void defaultAction(Tree node, AnnotatedTypeMirror p) {
+            // Just access the subchecker type factories to make
+            // sure they were created properly
+            GenericAnnotatedTypeFactory<?, ?, ?, ?> aliasingATF =
+                getTypeFactoryOfSubchecker(AliasingChecker.class);
+            @SuppressWarnings("unused")
+            AnnotatedTypeMirror aliasing = aliasingATF.getAnnotatedType(node);
+            GenericAnnotatedTypeFactory<?, ?, ?, ?> valueATF =
+                getTypeFactoryOfSubchecker(ValueChecker.class);
+            @SuppressWarnings("unused")
+            AnnotatedTypeMirror value = valueATF.getAnnotatedType(node);
+            return super.defaultAction(node, p);
+          }
+        });
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundChecker.java
new file mode 100644
index 0000000..e584bf6
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundChecker.java
@@ -0,0 +1,32 @@
+package org.checkerframework.framework.testchecker.compound;
+
+import java.util.LinkedHashSet;
+import org.checkerframework.common.aliasing.AliasingChecker;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+
+/**
+ * Used to test the compound checker design pattern. AliasingChecker and AnotherCompoundChecker are
+ * subcheckers of this checker AnotherCompoundChecker relies on the Aliasing Checker, too. This is
+ * so that the order of subcheckers is tested.
+ */
+public class CompoundChecker extends BaseTypeChecker {
+  @Override
+  protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
+    LinkedHashSet<Class<? extends BaseTypeChecker>> subcheckers = new LinkedHashSet<>();
+    subcheckers.addAll(super.getImmediateSubcheckerClasses());
+    subcheckers.add(AliasingChecker.class);
+    subcheckers.add(AnotherCompoundChecker.class);
+    return subcheckers;
+  }
+
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new BaseTypeVisitor<CompoundCheckerAnnotatedTypeFactory>(this) {
+      @Override
+      protected CompoundCheckerAnnotatedTypeFactory createTypeFactory() {
+        return new CompoundCheckerAnnotatedTypeFactory(checker);
+      }
+    };
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundCheckerAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundCheckerAnnotatedTypeFactory.java
new file mode 100644
index 0000000..6fe5fab
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundCheckerAnnotatedTypeFactory.java
@@ -0,0 +1,55 @@
+package org.checkerframework.framework.testchecker.compound;
+
+import com.sun.source.tree.Tree;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.common.aliasing.AliasingChecker;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.value.ValueChecker;
+import org.checkerframework.framework.testchecker.compound.qual.CCBottom;
+import org.checkerframework.framework.testchecker.compound.qual.CCTop;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+
+public class CompoundCheckerAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  public CompoundCheckerAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<Class<? extends Annotation>>(Arrays.asList(CCTop.class, CCBottom.class));
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(
+        super.createTreeAnnotator(),
+        new TreeAnnotator(this) {
+          @Override
+          protected Void defaultAction(Tree node, AnnotatedTypeMirror p) {
+            // Just access the subchecker type factories to make
+            // sure they were created properly
+            GenericAnnotatedTypeFactory<?, ?, ?, ?> accATF =
+                getTypeFactoryOfSubchecker(AnotherCompoundChecker.class);
+            @SuppressWarnings("unused")
+            AnnotatedTypeMirror another = accATF.getAnnotatedType(node);
+            GenericAnnotatedTypeFactory<?, ?, ?, ?> aliasingATF =
+                getTypeFactoryOfSubchecker(AliasingChecker.class);
+            @SuppressWarnings("unused")
+            AnnotatedTypeMirror aliasing = aliasingATF.getAnnotatedType(node);
+            GenericAnnotatedTypeFactory<?, ?, ?, ?> valueATF =
+                getTypeFactoryOfSubchecker(ValueChecker.class);
+            assert valueATF == null : "Should not be able to access the ValueChecker annotations.";
+            return super.defaultAction(node, p);
+          }
+        });
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCBottom.java
new file mode 100644
index 0000000..1ca0de9
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCBottom.java
@@ -0,0 +1,12 @@
+package org.checkerframework.framework.testchecker.compound.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+@SubtypeOf({ACCTop.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+public @interface ACCBottom {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCTop.java
new file mode 100644
index 0000000..2231a12
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCTop.java
@@ -0,0 +1,11 @@
+package org.checkerframework.framework.testchecker.compound.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf({})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@DefaultQualifierInHierarchy
+public @interface ACCTop {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCBottom.java
new file mode 100644
index 0000000..c91c822
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCBottom.java
@@ -0,0 +1,12 @@
+package org.checkerframework.framework.testchecker.compound.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+@SubtypeOf({CCTop.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+public @interface CCBottom {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCTop.java
new file mode 100644
index 0000000..7ce7b48
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCTop.java
@@ -0,0 +1,11 @@
+package org.checkerframework.framework.testchecker.compound.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf({})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@DefaultQualifierInHierarchy
+public @interface CCTop {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundAnnotatedTypeFactory.java
new file mode 100644
index 0000000..d6f6ea8
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundAnnotatedTypeFactory.java
@@ -0,0 +1,26 @@
+package org.checkerframework.framework.testchecker.defaulting;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.testchecker.defaulting.LowerBoundQual.LbBottom;
+import org.checkerframework.framework.testchecker.defaulting.LowerBoundQual.LbExplicit;
+import org.checkerframework.framework.testchecker.defaulting.LowerBoundQual.LbImplicit;
+import org.checkerframework.framework.testchecker.defaulting.LowerBoundQual.LbTop;
+
+public class DefaultingLowerBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  public DefaultingLowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<Class<? extends Annotation>>(
+        Arrays.asList(LbTop.class, LbExplicit.class, LbImplicit.class, LbBottom.class));
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundChecker.java
new file mode 100644
index 0000000..9558c1e
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundChecker.java
@@ -0,0 +1,5 @@
+package org.checkerframework.framework.testchecker.defaulting;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+public class DefaultingLowerBoundChecker extends BaseTypeChecker {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundAnnotatedTypeFactory.java
new file mode 100644
index 0000000..94bec6e
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundAnnotatedTypeFactory.java
@@ -0,0 +1,26 @@
+package org.checkerframework.framework.testchecker.defaulting;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+public class DefaultingUpperBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  public DefaultingUpperBoundAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<Class<? extends Annotation>>(
+        Arrays.asList(
+            UpperBoundQual.UbTop.class,
+            UpperBoundQual.UbExplicit.class,
+            UpperBoundQual.UbImplicit.class,
+            UpperBoundQual.UbBottom.class));
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundChecker.java
new file mode 100644
index 0000000..1bfc642
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundChecker.java
@@ -0,0 +1,5 @@
+package org.checkerframework.framework.testchecker.defaulting;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+public class DefaultingUpperBoundChecker extends BaseTypeChecker {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/LowerBoundQual.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/LowerBoundQual.java
new file mode 100644
index 0000000..d22af28
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/LowerBoundQual.java
@@ -0,0 +1,41 @@
+package org.checkerframework.framework.testchecker.defaulting;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+public class LowerBoundQual {
+
+  @DefaultQualifierInHierarchy
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @SubtypeOf({})
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  public static @interface LbTop {}
+
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @SubtypeOf(LbTop.class)
+  @DefaultFor(TypeUseLocation.IMPLICIT_LOWER_BOUND)
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  public static @interface LbImplicit {}
+
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @SubtypeOf(LbTop.class)
+  @DefaultFor(TypeUseLocation.EXPLICIT_LOWER_BOUND)
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  public static @interface LbExplicit {}
+
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @SubtypeOf({LbImplicit.class, LbExplicit.class})
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  public static @interface LbBottom {}
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/UpperBoundQual.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/UpperBoundQual.java
new file mode 100644
index 0000000..b1fe40d
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/UpperBoundQual.java
@@ -0,0 +1,43 @@
+package org.checkerframework.framework.testchecker.defaulting;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/** Created by jburke on 9/29/14. */
+public class UpperBoundQual {
+
+  @DefaultQualifierInHierarchy
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @SubtypeOf({})
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  public static @interface UbTop {}
+
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @SubtypeOf(UbTop.class)
+  @DefaultFor(TypeUseLocation.IMPLICIT_UPPER_BOUND)
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  public static @interface UbImplicit {}
+
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @SubtypeOf(UbTop.class)
+  @DefaultFor(TypeUseLocation.EXPLICIT_UPPER_BOUND)
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  public static @interface UbExplicit {}
+
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @SubtypeOf({UbImplicit.class, UbExplicit.class})
+  @DefaultFor(TypeUseLocation.LOWER_BOUND)
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  public static @interface UbBottom {}
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionAnnotatedTypeFactory.java
new file mode 100644
index 0000000..43c6515
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionAnnotatedTypeFactory.java
@@ -0,0 +1,116 @@
+package org.checkerframework.framework.testchecker.flowexpression;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.util.Elements;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.testchecker.flowexpression.qual.FEBottom;
+import org.checkerframework.framework.testchecker.flowexpression.qual.FETop;
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+public class FlowExpressionAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+  private AnnotationMirror TOP, BOTTOM;
+
+  /** The FlowExp.value field/element. */
+  ExecutableElement flowExpValueElement =
+      TreeUtils.getMethod(FlowExp.class, "value", 0, processingEnv);
+
+  public FlowExpressionAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    TOP = AnnotationBuilder.fromClass(elements, FETop.class);
+    BOTTOM = AnnotationBuilder.fromClass(elements, FEBottom.class);
+    postInit();
+  }
+
+  @Override
+  protected DependentTypesHelper createDependentTypesHelper() {
+    return new DependentTypesHelper(this);
+  }
+
+  @Override
+  protected FlowExpressionQualifierHierarchy createQualifierHierarchy() {
+    return new FlowExpressionQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
+  }
+
+  private class FlowExpressionQualifierHierarchy extends MostlyNoElementQualifierHierarchy {
+
+    /**
+     * Create a {@code FlowExpressionQualifierHierarchy}.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers
+     * @param elements element utils
+     */
+    public FlowExpressionQualifierHierarchy(
+        Set<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+    }
+
+    @Override
+    protected boolean isSubtypeWithElements(
+        AnnotationMirror subAnno,
+        QualifierKind subKind,
+        AnnotationMirror superAnno,
+        QualifierKind superKind) {
+      List<String> subtypeExpressions =
+          AnnotationUtils.getElementValueArray(subAnno, flowExpValueElement, String.class);
+      List<String> supertypeExpressions =
+          AnnotationUtils.getElementValueArray(superAnno, flowExpValueElement, String.class);
+      return subtypeExpressions.containsAll(supertypeExpressions)
+          && supertypeExpressions.containsAll(subtypeExpressions);
+    }
+
+    @Override
+    protected AnnotationMirror leastUpperBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind lubKind) {
+      if (qualifierKind1.getName() == FEBottom.class.getCanonicalName()) {
+        return a2;
+      } else if (qualifierKind2.getName() == FEBottom.class.getCanonicalName()) {
+        return a1;
+      }
+      List<String> a1Expressions =
+          AnnotationUtils.getElementValueArray(a1, flowExpValueElement, String.class);
+      List<String> a2Expressions =
+          AnnotationUtils.getElementValueArray(a2, flowExpValueElement, String.class);
+      if (a1Expressions.containsAll(a2Expressions) && a2Expressions.containsAll(a1Expressions)) {
+        return a1;
+      }
+      return TOP;
+    }
+
+    @Override
+    protected AnnotationMirror greatestLowerBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind glbKind) {
+      if (qualifierKind1.getName() == FETop.class.getCanonicalName()) {
+        return a2;
+      } else if (qualifierKind2.getName() == FETop.class.getCanonicalName()) {
+        return a1;
+      }
+      List<String> a1Expressions =
+          AnnotationUtils.getElementValueArray(a1, flowExpValueElement, String.class);
+      List<String> a2Expressions =
+          AnnotationUtils.getElementValueArray(a2, flowExpValueElement, String.class);
+      if (a1Expressions.containsAll(a2Expressions) && a2Expressions.containsAll(a1Expressions)) {
+        return a1;
+      }
+      return BOTTOM;
+    }
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionChecker.java
new file mode 100644
index 0000000..a6e0fe6
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionChecker.java
@@ -0,0 +1,5 @@
+package org.checkerframework.framework.testchecker.flowexpression;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+public class FlowExpressionChecker extends BaseTypeChecker {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FEBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FEBottom.java
new file mode 100644
index 0000000..ddad660
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FEBottom.java
@@ -0,0 +1,9 @@
+package org.checkerframework.framework.testchecker.flowexpression.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf({FlowExp.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface FEBottom {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FETop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FETop.java
new file mode 100644
index 0000000..172a763
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FETop.java
@@ -0,0 +1,11 @@
+package org.checkerframework.framework.testchecker.flowexpression.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@DefaultQualifierInHierarchy
+@SubtypeOf({})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface FETop {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FlowExp.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FlowExp.java
new file mode 100644
index 0000000..cd2a452
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FlowExp.java
@@ -0,0 +1,13 @@
+package org.checkerframework.framework.testchecker.flowexpression.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.JavaExpression;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf({FETop.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface FlowExp {
+  @JavaExpression
+  String[] value() default {};
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java
new file mode 100644
index 0000000..95125b9
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java
@@ -0,0 +1,59 @@
+package org.checkerframework.framework.testchecker.h1h2checker;
+
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import java.lang.annotation.Annotation;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Bot;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Invalid;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Poly;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1S1;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1S2;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Top;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H2Bot;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H2Poly;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H2S1;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H2S2;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H2Top;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.javacutil.AnnotationBuilder;
+
+public class H1H2AnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  AnnotationMirror H1S2;
+
+  public H1H2AnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    this.postInit();
+    H1S2 = AnnotationBuilder.fromClass(elements, H1S2.class);
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return getBundledTypeQualifiers(
+        H1Top.class,
+        H1S1.class,
+        H1S2.class,
+        H1Bot.class,
+        H2Top.class,
+        H2S1.class,
+        H2S2.class,
+        H2Bot.class,
+        H1Poly.class,
+        H2Poly.class,
+        H1Invalid.class);
+  }
+
+  @Override
+  protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) {
+    super.addComputedTypeAnnotations(tree, type, iUseFlow);
+    if (tree.getKind() == Kind.VARIABLE
+        && ((VariableTree) tree).getName().toString().contains("addH1S2")) {
+      type.replaceAnnotation(H1S2);
+    }
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Checker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Checker.java
new file mode 100644
index 0000000..61ae387
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Checker.java
@@ -0,0 +1,5 @@
+package org.checkerframework.framework.testchecker.h1h2checker;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+public class H1H2Checker extends BaseTypeChecker {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Visitor.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Visitor.java
new file mode 100644
index 0000000..ab88878
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Visitor.java
@@ -0,0 +1,47 @@
+package org.checkerframework.framework.testchecker.h1h2checker;
+
+import com.sun.source.tree.Tree;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeValidator;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Invalid;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.util.AnnotatedTypes;
+import org.checkerframework.javacutil.AnnotationBuilder;
+
+public class H1H2Visitor extends BaseTypeVisitor<H1H2AnnotatedTypeFactory> {
+
+  public H1H2Visitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  protected BaseTypeValidator createTypeValidator() {
+    return new H1H2TypeValidator(checker, this, atypeFactory);
+  }
+
+  private final class H1H2TypeValidator extends BaseTypeValidator {
+
+    public H1H2TypeValidator(
+        BaseTypeChecker checker, BaseTypeVisitor<?> visitor, AnnotatedTypeFactory atypeFactory) {
+      super(checker, visitor, atypeFactory);
+    }
+
+    @Override
+    public Void visitDeclared(AnnotatedDeclaredType type, Tree p) {
+      AnnotationMirror h1Invalid = AnnotationBuilder.fromClass(elements, H1Invalid.class);
+      if (AnnotatedTypes.containsModifier(type, h1Invalid)) {
+        checker.reportError(
+            p,
+            // An error specific to this type system, with no corresponding text
+            // in a messages.properties file; this checker is just for testing.
+            "h1h2checker.h1invalid.forbidden",
+            type.getAnnotations(),
+            type.toString());
+      }
+      return super.visitDeclared(type, p);
+    }
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Bot.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Bot.java
new file mode 100644
index 0000000..dcbc4ed
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Bot.java
@@ -0,0 +1,17 @@
+package org.checkerframework.framework.testchecker.h1h2checker.quals;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({H1S1.class, H1S2.class, H1Invalid.class})
+@DefaultFor({TypeUseLocation.LOWER_BOUND})
+public @interface H1Bot {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Invalid.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Invalid.java
new file mode 100644
index 0000000..a2e4a34
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Invalid.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.h1h2checker.quals;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({H1Top.class})
+public @interface H1Invalid {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Poly.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Poly.java
new file mode 100644
index 0000000..9f4201b
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Poly.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.h1h2checker.quals;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(H1Top.class)
+public @interface H1Poly {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S1.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S1.java
new file mode 100644
index 0000000..26798d8
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S1.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.h1h2checker.quals;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({H1Top.class})
+public @interface H1S1 {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S2.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S2.java
new file mode 100644
index 0000000..8608b20
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S2.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.h1h2checker.quals;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({H1Top.class})
+public @interface H1S2 {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Top.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Top.java
new file mode 100644
index 0000000..ec0da42
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Top.java
@@ -0,0 +1,16 @@
+package org.checkerframework.framework.testchecker.h1h2checker.quals;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface H1Top {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Bot.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Bot.java
new file mode 100644
index 0000000..44a1165
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Bot.java
@@ -0,0 +1,17 @@
+package org.checkerframework.framework.testchecker.h1h2checker.quals;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({H2S1.class, H2S2.class})
+@DefaultFor(TypeUseLocation.LOWER_BOUND)
+public @interface H2Bot {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Poly.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Poly.java
new file mode 100644
index 0000000..26e3a18
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Poly.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.h1h2checker.quals;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(H2Top.class)
+public @interface H2Poly {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S1.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S1.java
new file mode 100644
index 0000000..532e475
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S1.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.h1h2checker.quals;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({H2Top.class})
+public @interface H2S1 {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S2.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S2.java
new file mode 100644
index 0000000..0bc2a6a
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S2.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.h1h2checker.quals;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({H2Top.class})
+public @interface H2S2 {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Top.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Top.java
new file mode 100644
index 0000000..54edce7
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Top.java
@@ -0,0 +1,16 @@
+package org.checkerframework.framework.testchecker.h1h2checker.quals;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface H2Top {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java
new file mode 100644
index 0000000..4939d4b
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java
@@ -0,0 +1,13 @@
+// This class must be in a package.  If a class is in the default package, clients cannot static
+// import the class, or static import its members.
+package org.checkerframework.framework.testchecker.lib;
+
+public class Issue3105Fields {
+  public static final String FIELD1 = "foo";
+
+  public static final String FIELD2;
+
+  static {
+    FIELD2 = "bar";
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/README b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/README
new file mode 100644
index 0000000..eab7a08
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/README
@@ -0,0 +1,4 @@
+Test writers can add classes to this directory and reference them in tests in
+checker/tests and framework/tests to test unchecked bytecode behavior.  Classes
+in this directory are compiled without using a checker and so do not contain
+any type annotations.
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/UncheckedByteCode.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/UncheckedByteCode.java
new file mode 100644
index 0000000..96899a1
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/UncheckedByteCode.java
@@ -0,0 +1,42 @@
+package org.checkerframework.framework.testchecker.lib;
+
+public class UncheckedByteCode<CT> {
+  public CT classTypeVariableField;
+  public static Object nonFinalPublicField;
+
+  public CT getCT() {
+    return classTypeVariableField;
+  }
+
+  public <T> T identity(T t) {
+    return t;
+  }
+
+  public int getInt(int i) {
+    return i;
+  }
+
+  public Integer getInteger(Integer i) {
+    return i;
+  }
+
+  public String getString(CharSequence charSequence) {
+    return "";
+  }
+
+  public <I extends CharSequence> I getI(I i) {
+    return i;
+  }
+
+  public Object getObject(Object o) {
+    return o;
+  }
+
+  public static void unboundedWildcardParam(UncheckedByteCode<?> param) {}
+
+  public static void upperboundedWildcardParam(UncheckedByteCode<? extends Object> param) {}
+
+  public static void lowerboundedWildcardParam(UncheckedByteCode<? super Object> param) {}
+
+  public static <F extends Number> void methodWithTypeVarBoundedByNumber(F param) {}
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/VarArgMethods.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/VarArgMethods.java
new file mode 100644
index 0000000..32e905f
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/VarArgMethods.java
@@ -0,0 +1,33 @@
+package org.checkerframework.framework.testchecker.lib;
+
+import org.checkerframework.common.value.qual.StaticallyExecutable;
+
+/** Used by framework/tests/value/VarArgRe.java */
+public class VarArgMethods {
+  @StaticallyExecutable
+  public static int test0(Object... objects) {
+    if (objects == null) {
+      return -1;
+    } else {
+      return objects.length;
+    }
+  }
+
+  @StaticallyExecutable
+  public static int test1(String s, Object... objects) {
+    if (objects == null) {
+      return -1;
+    } else {
+      return objects.length;
+    }
+  }
+
+  @StaticallyExecutable
+  public static int test2(String s, String s2, Object... objects) {
+    if (objects == null) {
+      return -1;
+    } else {
+      return objects.length;
+    }
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbAnnotatedTypeFactory.java
new file mode 100644
index 0000000..e40c002
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbAnnotatedTypeFactory.java
@@ -0,0 +1,29 @@
+package org.checkerframework.framework.testchecker.lubglb;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.testchecker.lubglb.quals.A;
+import org.checkerframework.framework.testchecker.lubglb.quals.B;
+import org.checkerframework.framework.testchecker.lubglb.quals.C;
+import org.checkerframework.framework.testchecker.lubglb.quals.D;
+import org.checkerframework.framework.testchecker.lubglb.quals.E;
+import org.checkerframework.framework.testchecker.lubglb.quals.F;
+import org.checkerframework.framework.testchecker.lubglb.quals.Poly;
+
+public class LubGlbAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  public LubGlbAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<Class<? extends Annotation>>(
+        Arrays.asList(A.class, B.class, C.class, D.class, E.class, F.class, Poly.class));
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbChecker.java
new file mode 100644
index 0000000..5f79efe
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbChecker.java
@@ -0,0 +1,71 @@
+package org.checkerframework.framework.testchecker.lubglb;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.testchecker.lubglb.quals.A;
+import org.checkerframework.framework.testchecker.lubglb.quals.B;
+import org.checkerframework.framework.testchecker.lubglb.quals.C;
+import org.checkerframework.framework.testchecker.lubglb.quals.D;
+import org.checkerframework.framework.testchecker.lubglb.quals.E;
+import org.checkerframework.framework.testchecker.lubglb.quals.F;
+import org.checkerframework.framework.testchecker.lubglb.quals.Poly;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+// Type hierarchy:
+//    A       <-- @DefaultQualifierInHierarchy
+//   / \
+//  B   C
+//   \ / \
+//    D   E
+//     \ /
+//      F
+
+public class LubGlbChecker extends BaseTypeChecker {
+
+  private AnnotationMirror A, B, C, D, E, F, POLY;
+
+  @Override
+  public void initChecker() {
+    super.initChecker();
+
+    Elements elements = processingEnv.getElementUtils();
+
+    A = AnnotationBuilder.fromClass(elements, A.class);
+    B = AnnotationBuilder.fromClass(elements, B.class);
+    C = AnnotationBuilder.fromClass(elements, C.class);
+    D = AnnotationBuilder.fromClass(elements, D.class);
+    E = AnnotationBuilder.fromClass(elements, E.class);
+    F = AnnotationBuilder.fromClass(elements, F.class);
+    POLY = AnnotationBuilder.fromClass(elements, Poly.class);
+
+    QualifierHierarchy qh = ((BaseTypeVisitor<?>) visitor).getTypeFactory().getQualifierHierarchy();
+
+    // System.out.println("LUB of D and E: " + qh.leastUpperBound(D, E));
+    assert AnnotationUtils.areSame(qh.leastUpperBound(D, E), C) : "LUB of D and E is not C!";
+
+    // System.out.println("LUB of E and D: " + qh.leastUpperBound(E, D));
+    assert AnnotationUtils.areSame(qh.leastUpperBound(E, D), C) : "LUB of E and D is not C!";
+
+    // System.out.println("GLB of B and C: " + qh.greatestLowerBound(B, C));
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(B, C), D) : "GLB of B and C is not D!";
+
+    // System.out.println("GLB of C and B: " + qh.greatestLowerBound(C, B));
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(C, B), D) : "GLB of C and B is not D!";
+
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(POLY, B), F)
+        : "GLB of POLY and B is not F!";
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(POLY, F), F)
+        : "GLB of POLY and F is not F!";
+    assert AnnotationUtils.areSame(qh.greatestLowerBound(POLY, A), POLY)
+        : "GLB of POLY and A is not POLY!";
+
+    assert AnnotationUtils.areSame(qh.leastUpperBound(POLY, B), A) : "LUB of POLY and B is not A!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(POLY, F), POLY)
+        : "LUB of POLY and F is not POLY!";
+    assert AnnotationUtils.areSame(qh.leastUpperBound(POLY, A), A) : "LUB of POLY and A is not A!";
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/A.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/A.java
new file mode 100644
index 0000000..1b4b51d
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/A.java
@@ -0,0 +1,16 @@
+package org.checkerframework.framework.testchecker.lubglb.quals;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@DefaultQualifierInHierarchy
+@SubtypeOf({})
+public @interface A {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/B.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/B.java
new file mode 100644
index 0000000..f1890dc
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/B.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.lubglb.quals;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({A.class})
+public @interface B {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/C.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/C.java
new file mode 100644
index 0000000..500513d
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/C.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.lubglb.quals;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({A.class})
+public @interface C {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/D.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/D.java
new file mode 100644
index 0000000..0d8b973
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/D.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.lubglb.quals;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({C.class, B.class})
+public @interface D {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/E.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/E.java
new file mode 100644
index 0000000..0cedc8e
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/E.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.lubglb.quals;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({C.class})
+public @interface E {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/F.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/F.java
new file mode 100644
index 0000000..de3616c
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/F.java
@@ -0,0 +1,17 @@
+package org.checkerframework.framework.testchecker.lubglb.quals;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({D.class, E.class})
+@DefaultFor({TypeUseLocation.LOWER_BOUND})
+public @interface F {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/Poly.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/Poly.java
new file mode 100644
index 0000000..8447151
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/Poly.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.lubglb.quals;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(A.class)
+public @interface Poly {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDAnnotatedTypeFactory.java
new file mode 100644
index 0000000..4de4fc1
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDAnnotatedTypeFactory.java
@@ -0,0 +1,21 @@
+package org.checkerframework.framework.testchecker.nontopdefault;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+public class NTDAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+  public NTDAnnotatedTypeFactory(BaseTypeChecker checker) {
+    // use flow inference
+    super(checker, true);
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    // there's no polymorphic qualifiers in NTD
+    return getBundledTypeQualifiers();
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDChecker.java
new file mode 100644
index 0000000..b8fcf66
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDChecker.java
@@ -0,0 +1,13 @@
+package org.checkerframework.framework.testchecker.nontopdefault;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+/* Hierarchy:
+ *    NTDTop (default for local variables, implicit upper bound, and receiver)
+ *    /     \
+ * NTDSide  NTDMiddle (default in hierarchy, default for exceptions and resource variables)
+ *   \       /
+ *   NTDBottom (default for implicit and explicit lower bounds, implicit for null literal and Void.class)
+ */
+
+public class NTDChecker extends BaseTypeChecker {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDVisitor.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDVisitor.java
new file mode 100644
index 0000000..8b083e8
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDVisitor.java
@@ -0,0 +1,30 @@
+package org.checkerframework.framework.testchecker.nontopdefault;
+
+import com.sun.source.tree.Tree;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.testchecker.nontopdefault.qual.NTDBottom;
+import org.checkerframework.framework.testchecker.nontopdefault.qual.NTDMiddle;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+
+public class NTDVisitor extends BaseTypeVisitor<NTDAnnotatedTypeFactory> {
+  public NTDVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  // Because classes and interfaces are by default NTDMiddle, an override is defined here which
+  // allows references to be declared using any NDT type except NTDBottom.
+  @Override
+  public boolean isValidUse(
+      AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) {
+    // eg for the statement "@NTDSide Double x;" the declarationType is @NTDMiddle
+    // Double, and the useType is @NTDSide Double
+    if (declarationType.getEffectiveAnnotation(NTDMiddle.class) != null
+        && useType.getEffectiveAnnotation(NTDBottom.class) == null) {
+      return true;
+    } else {
+      // otherwise check the usage using super
+      return super.isValidUse(declarationType, useType, tree);
+    }
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDBottom.java
new file mode 100644
index 0000000..fd0bf90
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDBottom.java
@@ -0,0 +1,19 @@
+package org.checkerframework.framework.testchecker.nontopdefault.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@SubtypeOf({NTDMiddle.class, NTDSide.class})
+@DefaultFor({TypeUseLocation.LOWER_BOUND})
+public @interface NTDBottom {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDMiddle.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDMiddle.java
new file mode 100644
index 0000000..cd48dc7
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDMiddle.java
@@ -0,0 +1,17 @@
+package org.checkerframework.framework.testchecker.nontopdefault.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** Middle is the default type in hierarchy. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(NTDTop.class)
+@DefaultQualifierInHierarchy
+public @interface NTDMiddle {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDSide.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDSide.java
new file mode 100644
index 0000000..269c902
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDSide.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.nontopdefault.qual;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(NTDTop.class)
+public @interface NTDSide {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDTop.java
new file mode 100644
index 0000000..b8ba586
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDTop.java
@@ -0,0 +1,21 @@
+package org.checkerframework.framework.testchecker.nontopdefault.qual;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultFor({
+  TypeUseLocation.LOCAL_VARIABLE,
+  TypeUseLocation.IMPLICIT_UPPER_BOUND,
+  TypeUseLocation.RECEIVER
+})
+public @interface NTDTop {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestAnnotatedTypeFactory.java
new file mode 100644
index 0000000..0748d4c
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestAnnotatedTypeFactory.java
@@ -0,0 +1,33 @@
+package org.checkerframework.framework.testchecker.reflection;
+
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.testchecker.reflection.qual.ReflectBottom;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+
+/**
+ * AnnotatedTypeFactory with reflection resolution enabled. The used qualifier hierarchy is
+ * straightforward and only intended for test purposes.
+ */
+public final class ReflectionTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+  public ReflectionTestAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    postInit();
+  }
+
+  @Override
+  public TreeAnnotator createTreeAnnotator() {
+    LiteralTreeAnnotator literalTreeAnnotator = new LiteralTreeAnnotator(this);
+    AnnotationMirror bottom = AnnotationBuilder.fromClass(elements, ReflectBottom.class);
+    literalTreeAnnotator.addLiteralKind(LiteralKind.INT, bottom);
+    literalTreeAnnotator.addStandardLiteralQualifiers();
+
+    return new ListTreeAnnotator(new PropagationTreeAnnotator(this), literalTreeAnnotator);
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestChecker.java
new file mode 100644
index 0000000..84bc8d8
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestChecker.java
@@ -0,0 +1,13 @@
+package org.checkerframework.framework.testchecker.reflection;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+
+/** Checker for a simple type system to test reflection resolution. */
+public class ReflectionTestChecker extends BaseTypeChecker {
+
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new ReflectionTestVisitor(this);
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestVisitor.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestVisitor.java
new file mode 100644
index 0000000..75427bc
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestVisitor.java
@@ -0,0 +1,18 @@
+package org.checkerframework.framework.testchecker.reflection;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+
+/** Visitor for a simple type system to test reflection resolution. */
+public final class ReflectionTestVisitor
+    extends BaseTypeVisitor<ReflectionTestAnnotatedTypeFactory> {
+
+  public ReflectionTestVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  protected ReflectionTestAnnotatedTypeFactory createTypeFactory() {
+    return new ReflectionTestAnnotatedTypeFactory(checker);
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/PolyReflection.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/PolyReflection.java
new file mode 100644
index 0000000..db7db37
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/PolyReflection.java
@@ -0,0 +1,15 @@
+package org.checkerframework.framework.testchecker.reflection.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/**
+ * Toy type system for testing reflection resolution. Uses
+ * org.checkerframework.common.subtyping.qual.Bottom as bottom
+ *
+ * @see Sibling1, Sibling2
+ */
+@PolymorphicQualifier
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface PolyReflection {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/ReflectBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/ReflectBottom.java
new file mode 100644
index 0000000..5a5ef61
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/ReflectBottom.java
@@ -0,0 +1,20 @@
+package org.checkerframework.framework.testchecker.reflection.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/**
+ * Toy type system for testing reflection resolution. Uses
+ * org.checkerframework.common.subtyping.qual.Bottom as bottom.
+ *
+ * @see Sibling1, Sibling2
+ */
+@SubtypeOf({Sibling1.class, Sibling2.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+@DefaultFor(TypeUseLocation.LOWER_BOUND)
+public @interface ReflectBottom {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/Sibling1.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/Sibling1.java
new file mode 100644
index 0000000..4741e71
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/Sibling1.java
@@ -0,0 +1,15 @@
+package org.checkerframework.framework.testchecker.reflection.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Toy type system for testing reflection resolution. Uses
+ * org.checkerframework.common.subtyping.qual.Bottom as bottom.
+ *
+ * @see Top, Sibling2
+ */
+@SubtypeOf(Top.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Sibling1 {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/Sibling2.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/Sibling2.java
new file mode 100644
index 0000000..a0d6412
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/Sibling2.java
@@ -0,0 +1,15 @@
+package org.checkerframework.framework.testchecker.reflection.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Toy type system for testing reflection resolution. Uses
+ * org.checkerframework.common.subtyping.qual.Bottom as bottom.
+ *
+ * @see Top, Sibling1,
+ */
+@SubtypeOf(Top.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Sibling2 {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/Top.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/Top.java
new file mode 100644
index 0000000..da59dbf
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/Top.java
@@ -0,0 +1,17 @@
+package org.checkerframework.framework.testchecker.reflection.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * Toy type system for testing reflection resolution. Uses
+ * org.checkerframework.common.subtyping.qual.Bottom as bottom.
+ *
+ * @see Sibling1, Sibling2
+ */
+@SubtypeOf({})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@DefaultQualifierInHierarchy
+public @interface Top {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/SupportedQualsChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/SupportedQualsChecker.java
new file mode 100644
index 0000000..10f0398
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/SupportedQualsChecker.java
@@ -0,0 +1,41 @@
+package org.checkerframework.framework.testchecker.supportedquals;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.testchecker.supportedquals.qual.BottomQualifier;
+import org.checkerframework.framework.testchecker.supportedquals.qual.Qualifier;
+
+/**
+ * Tests that annotations that have @Target(TYPE_USE, OTHER) (where OTHER is not TYPE_PARAMETER) may
+ * be in the qual package so long as {@link BaseAnnotatedTypeFactory#createSupportedTypeQualifiers}
+ * is overridden.
+ */
+public class SupportedQualsChecker extends BaseTypeChecker {
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new BaseTypeVisitor<SupportedQualsAnnotatedTypeFactory>(this) {
+      @Override
+      protected SupportedQualsAnnotatedTypeFactory createTypeFactory() {
+        return new SupportedQualsAnnotatedTypeFactory(checker);
+      }
+    };
+  }
+
+  class SupportedQualsAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+    public SupportedQualsAnnotatedTypeFactory(BaseTypeChecker checker) {
+      super(checker);
+      postInit();
+    }
+
+    @Override
+    protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+      return new HashSet<Class<? extends Annotation>>(
+          Arrays.asList(Qualifier.class, BottomQualifier.class));
+    }
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/BottomQualifier.java b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/BottomQualifier.java
new file mode 100644
index 0000000..237e040
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/BottomQualifier.java
@@ -0,0 +1,12 @@
+package org.checkerframework.framework.testchecker.supportedquals.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+@SubtypeOf({Qualifier.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+public @interface BottomQualifier {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/NotQualifier.java b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/NotQualifier.java
new file mode 100644
index 0000000..8c112c7
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/NotQualifier.java
@@ -0,0 +1,7 @@
+package org.checkerframework.framework.testchecker.supportedquals.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE_USE, ElementType.FIELD})
+public @interface NotQualifier {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/Qualifier.java b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/Qualifier.java
new file mode 100644
index 0000000..e297d39
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/Qualifier.java
@@ -0,0 +1,11 @@
+package org.checkerframework.framework.testchecker.supportedquals.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf({})
+@Target(ElementType.TYPE_USE)
+@DefaultQualifierInHierarchy
+public @interface Qualifier {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationAnnotatedTypeFactory.java
new file mode 100644
index 0000000..c5e79b8
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationAnnotatedTypeFactory.java
@@ -0,0 +1,73 @@
+package org.checkerframework.framework.testchecker.testaccumulation;
+
+import com.sun.source.tree.MethodInvocationTree;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.common.accumulation.AccumulationAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.testchecker.testaccumulation.qual.TestAccumulation;
+import org.checkerframework.framework.testchecker.testaccumulation.qual.TestAccumulationBottom;
+import org.checkerframework.framework.testchecker.testaccumulation.qual.TestAccumulationPredicate;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * The annotated type factory for a test accumulation checker, which implements a basic called
+ * methods checker.
+ */
+public class TestAccumulationAnnotatedTypeFactory extends AccumulationAnnotatedTypeFactory {
+  /**
+   * Create a new accumulation checker's annotated type factory.
+   *
+   * @param checker the checker
+   */
+  public TestAccumulationAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(
+        checker,
+        TestAccumulation.class,
+        TestAccumulationBottom.class,
+        TestAccumulationPredicate.class);
+    this.postInit();
+  }
+
+  @Override
+  protected TreeAnnotator createTreeAnnotator() {
+    return new ListTreeAnnotator(
+        super.createTreeAnnotator(), new TestAccumulationTreeAnnotator(this));
+  }
+
+  /**
+   * Necessary for the type rule for called methods described below. A new accumulation analysis
+   * might have other type rules here, or none at all.
+   */
+  private class TestAccumulationTreeAnnotator extends AccumulationTreeAnnotator {
+    /**
+     * Creates an instance of this tree annotator for the given type factory.
+     *
+     * @param factory the type factory
+     */
+    public TestAccumulationTreeAnnotator(AccumulationAnnotatedTypeFactory factory) {
+      super(factory);
+    }
+
+    @Override
+    public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
+      // CalledMethods requires special treatment of the return values of methods that return
+      // their receiver: the default return type must include the method being invoked.
+      //
+      // The basic accumulation analysis cannot handle this case - it can use the RR checker to
+      // transfer an annotation from the receiver to the return type, but because accumulation (has
+      // to) happen in dataflow, the correct annotation may not yet be available. The basic
+      // accumulation analysis therefore only supports "pass-through" returns receiver methods; it
+      // does not support automatically accumulating at the same time.
+      if (returnsThis(tree)) {
+        String methodName = TreeUtils.getMethodName(tree.getMethodSelect());
+        AnnotationMirror oldAnno = type.getAnnotationInHierarchy(top);
+        type.replaceAnnotation(
+            qualHierarchy.greatestLowerBound(oldAnno, createAccumulatorAnnotation(methodName)));
+      }
+      return super.visitMethodInvocation(tree, type);
+    }
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationChecker.java
new file mode 100644
index 0000000..e43ccbb
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationChecker.java
@@ -0,0 +1,6 @@
+package org.checkerframework.framework.testchecker.testaccumulation;
+
+import org.checkerframework.common.accumulation.AccumulationChecker;
+
+/** A test accumulation checker that implements a basic version of called-methods accumulation. */
+public class TestAccumulationChecker extends AccumulationChecker {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverAnnotatedTypeFactory.java
new file mode 100644
index 0000000..2d7b4b0
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverAnnotatedTypeFactory.java
@@ -0,0 +1,19 @@
+package org.checkerframework.framework.testchecker.testaccumulation;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+/**
+ * Wrapper for TestAccumulationAnnotatedTypeFactory for a version of the checker without returns
+ * receiver support, to enable the checker's auto-discovery of its AnnotatedTypeFactory to succeed.
+ */
+public class TestAccumulationNoReturnsReceiverAnnotatedTypeFactory
+    extends TestAccumulationAnnotatedTypeFactory {
+  /**
+   * Create a new accumulation checker's annotated type factory.
+   *
+   * @param checker the checker
+   */
+  public TestAccumulationNoReturnsReceiverAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverChecker.java
new file mode 100644
index 0000000..7133fdc
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverChecker.java
@@ -0,0 +1,21 @@
+package org.checkerframework.framework.testchecker.testaccumulation;
+
+import java.util.EnumSet;
+import org.checkerframework.common.accumulation.AccumulationChecker;
+
+/**
+ * A test accumulation checker that implements a basic version of called-methods accumulation,
+ * without returns receiver support, to test the pluggable alias analysis functionality.
+ */
+public class TestAccumulationNoReturnsReceiverChecker extends AccumulationChecker {
+
+  /**
+   * Get the alias analyses that this checker should employ.
+   *
+   * @return the alias analyses
+   */
+  @Override
+  protected EnumSet<AliasAnalysis> createAliasAnalyses() {
+    return EnumSet.noneOf(AliasAnalysis.class);
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverTransfer.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverTransfer.java
new file mode 100644
index 0000000..b0c9c65
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverTransfer.java
@@ -0,0 +1,18 @@
+package org.checkerframework.framework.testchecker.testaccumulation;
+
+import org.checkerframework.framework.flow.CFAnalysis;
+
+/**
+ * Wrapper for TestAccumulationTransfer so that checker auto-discovery works for the version of the
+ * checker without support for the Returns Receiver Checker.
+ */
+public class TestAccumulationNoReturnsReceiverTransfer extends TestAccumulationTransfer {
+  /**
+   * default constructor
+   *
+   * @param analysis the analysis
+   */
+  public TestAccumulationNoReturnsReceiverTransfer(CFAnalysis analysis) {
+    super(analysis);
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationTransfer.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationTransfer.java
new file mode 100644
index 0000000..43894e2
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationTransfer.java
@@ -0,0 +1,35 @@
+package org.checkerframework.framework.testchecker.testaccumulation;
+
+import org.checkerframework.common.accumulation.AccumulationTransfer;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFValue;
+
+/** A basic transfer function that accumulates the names of methods called. */
+public class TestAccumulationTransfer extends AccumulationTransfer {
+
+  /**
+   * default constructor
+   *
+   * @param analysis the analysis
+   */
+  public TestAccumulationTransfer(final CFAnalysis analysis) {
+    super(analysis);
+  }
+
+  @Override
+  public TransferResult<CFValue, CFStore> visitMethodInvocation(
+      final MethodInvocationNode node, final TransferInput<CFValue, CFStore> input) {
+    TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(node, input);
+    Node receiver = node.getTarget().getReceiver();
+    if (receiver != null) {
+      String methodName = node.getTarget().getMethod().getSimpleName().toString();
+      accumulate(receiver, result, methodName);
+    }
+    return result;
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/PolyTestAccumulation.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/PolyTestAccumulation.java
new file mode 100644
index 0000000..4d9378a
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/PolyTestAccumulation.java
@@ -0,0 +1,10 @@
+package org.checkerframework.framework.testchecker.testaccumulation.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/** Polymorphic qualifier for the test accumulation type system. */
+@PolymorphicQualifier(TestAccumulation.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface PolyTestAccumulation {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulation.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulation.java
new file mode 100644
index 0000000..2e876a0
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulation.java
@@ -0,0 +1,22 @@
+package org.checkerframework.framework.testchecker.testaccumulation.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** A test accumulation analysis qualifier. It accumulates generic strings. */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface TestAccumulation {
+  /**
+   * Accumulated strings.
+   *
+   * @return the strings
+   */
+  public String[] value() default {};
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationBottom.java
new file mode 100644
index 0000000..6b42870
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationBottom.java
@@ -0,0 +1,16 @@
+package org.checkerframework.framework.testchecker.testaccumulation.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/** A test bottom type for an accumulation type system. */
+@SubtypeOf({TestAccumulation.class, TestAccumulationPredicate.class})
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+public @interface TestAccumulationBottom {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationPredicate.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationPredicate.java
new file mode 100644
index 0000000..2056217
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationPredicate.java
@@ -0,0 +1,21 @@
+package org.checkerframework.framework.testchecker.testaccumulation.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** A test accumulation predicate annotation. */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({TestAccumulation.class})
+public @interface TestAccumulationPredicate {
+  /**
+   * A boolean expression indicating which values have been accumulated.
+   *
+   * @return a boolean expression indicating which values have been accumulated
+   * @checker_framework.manual #accumulation-qualifiers
+   */
+  String value();
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultAnnotatedTypeFactory.java
new file mode 100644
index 0000000..f181548
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultAnnotatedTypeFactory.java
@@ -0,0 +1,29 @@
+package org.checkerframework.framework.testchecker.typedecldefault;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.testchecker.typedecldefault.quals.PolyTypeDeclDefault;
+import org.checkerframework.framework.testchecker.typedecldefault.quals.TypeDeclDefaultBottom;
+import org.checkerframework.framework.testchecker.typedecldefault.quals.TypeDeclDefaultMiddle;
+import org.checkerframework.framework.testchecker.typedecldefault.quals.TypeDeclDefaultTop;
+
+public class TypeDeclDefaultAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+  public TypeDeclDefaultAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<>(
+        Arrays.asList(
+            TypeDeclDefaultTop.class,
+            TypeDeclDefaultMiddle.class,
+            TypeDeclDefaultBottom.class,
+            PolyTypeDeclDefault.class));
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultChecker.java
new file mode 100644
index 0000000..37dbf6f
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultChecker.java
@@ -0,0 +1,6 @@
+package org.checkerframework.framework.testchecker.typedecldefault;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+/** A type-checker plug-in for the TypeDeclDefault type system. */
+public class TypeDeclDefaultChecker extends BaseTypeChecker {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/PolyTypeDeclDefault.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/PolyTypeDeclDefault.java
new file mode 100644
index 0000000..aa6872f
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/PolyTypeDeclDefault.java
@@ -0,0 +1,15 @@
+package org.checkerframework.framework.testchecker.typedecldefault.quals;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/** A polymorphic qualifier for the TyepDeclDefault type system. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier
+public @interface PolyTypeDeclDefault {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultBottom.java
new file mode 100644
index 0000000..324634f
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultBottom.java
@@ -0,0 +1,20 @@
+package org.checkerframework.framework.testchecker.typedecldefault.quals;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** TypeDeclDefault bottom qualifier. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(TypeDeclDefaultMiddle.class)
+@QualifierForLiterals(LiteralKind.STRING)
+@DefaultQualifierInHierarchy
+public @interface TypeDeclDefaultBottom {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultMiddle.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultMiddle.java
new file mode 100644
index 0000000..7a9d17c
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultMiddle.java
@@ -0,0 +1,15 @@
+package org.checkerframework.framework.testchecker.typedecldefault.quals;
+
+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;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** TypeDeclDefault middle qualifier. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(TypeDeclDefaultTop.class)
+public @interface TypeDeclDefaultMiddle {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultTop.java
new file mode 100644
index 0000000..40cde9d
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultTop.java
@@ -0,0 +1,18 @@
+package org.checkerframework.framework.testchecker.typedecldefault.quals;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/** This is the top qualifier of the TypeDeclDefault type system. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultFor(TypeUseLocation.CONSTRUCTOR_RESULT)
+public @interface TypeDeclDefaultTop {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/AnnoWithStringArg.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/AnnoWithStringArg.java
new file mode 100644
index 0000000..c491909
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/AnnoWithStringArg.java
@@ -0,0 +1,12 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.common.subtyping.qual.Unqualified;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf(Unqualified.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface AnnoWithStringArg {
+  String value();
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Critical.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Critical.java
new file mode 100644
index 0000000..e452855
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Critical.java
@@ -0,0 +1,11 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.common.subtyping.qual.Unqualified;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** Denotes an exception that is particularly important. */
+@SubtypeOf(Unqualified.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Critical {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Encrypted.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Encrypted.java
new file mode 100644
index 0000000..600cb2f
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Encrypted.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.common.subtyping.qual.Unqualified;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+/** Denotes an object with a representation that has been encrypted. */
+@SubtypeOf(Unqualified.class)
+@DefaultFor({TypeUseLocation.LOWER_BOUND})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Encrypted {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOdd.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOdd.java
new file mode 100644
index 0000000..0b0cd24
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOdd.java
@@ -0,0 +1,22 @@
+package org.checkerframework.framework.testchecker.util;
+
+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;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+import org.checkerframework.framework.qual.PostconditionAnnotation;
+
+/**
+ * A postcondition annotation to indicate that a method ensures certain expressions to be {@link
+ * Odd}.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@PostconditionAnnotation(qualifier = Odd.class)
+@InheritedAnnotation
+public @interface EnsuresOdd {
+  String[] value();
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOddIf.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOddIf.java
new file mode 100644
index 0000000..2cdeaba
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOddIf.java
@@ -0,0 +1,24 @@
+package org.checkerframework.framework.testchecker.util;
+
+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;
+import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+
+/**
+ * A conditional postcondition annotation to indicate that a method ensures certain expressions to
+ * be {@link Odd} given a certain result (either true or false).
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@ConditionalPostconditionAnnotation(qualifier = Odd.class)
+@InheritedAnnotation
+public @interface EnsuresOddIf {
+  String[] expression();
+
+  boolean result();
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Even.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Even.java
new file mode 100644
index 0000000..f803f98
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Even.java
@@ -0,0 +1,10 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.common.subtyping.qual.Unqualified;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf(Unqualified.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Even {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EvenOddChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EvenOddChecker.java
new file mode 100644
index 0000000..720688c
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EvenOddChecker.java
@@ -0,0 +1,90 @@
+package org.checkerframework.framework.testchecker.util;
+
+import com.sun.source.tree.Tree;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.common.subtyping.qual.Bottom;
+import org.checkerframework.common.subtyping.qual.Unqualified;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
+import org.checkerframework.framework.type.NoElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.util.DefaultQualifierKindHierarchy;
+import org.checkerframework.framework.util.QualifierKindHierarchy;
+import org.checkerframework.framework.util.defaults.QualifierDefaults;
+import org.checkerframework.javacutil.AnnotationBuilder;
+
+/**
+ * A simple checker used for testing the Checker Framework. It treats the {@code @Odd} and
+ * {@code @Even} annotations as a subtype-style qualifiers with no special semantics.
+ *
+ * <p>This checker should only be used for testing the framework.
+ */
+public final class EvenOddChecker extends BaseTypeChecker {
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new TestVisitor(this);
+  }
+}
+
+class TestVisitor extends BaseTypeVisitor<TestAnnotatedTypeFactory> {
+
+  public TestVisitor(BaseTypeChecker checker) {
+    super(checker);
+  }
+
+  @Override
+  protected TestAnnotatedTypeFactory createTypeFactory() {
+    return new TestAnnotatedTypeFactory(checker);
+  }
+
+  @Override
+  public boolean isValidUse(AnnotatedDeclaredType type, AnnotatedDeclaredType useType, Tree tree) {
+    // TODO: super would result in error, because of default on classes.
+    return true;
+  }
+}
+
+class TestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+  protected AnnotationMirror BOTTOM;
+
+  public TestAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker, true);
+    Elements elements = processingEnv.getElementUtils();
+    BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class);
+
+    this.postInit();
+  }
+
+  @Override
+  protected void addCheckedCodeDefaults(QualifierDefaults defs) {
+    defs.addCheckedCodeDefault(BOTTOM, TypeUseLocation.LOWER_BOUND);
+    AnnotationMirror unqualified = AnnotationBuilder.fromClass(elements, Unqualified.class);
+    defs.addCheckedCodeDefault(unqualified, TypeUseLocation.OTHERWISE);
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<>(
+        Arrays.asList(Odd.class, MonotonicOdd.class, Even.class, Unqualified.class, Bottom.class));
+  }
+
+  @Override
+  public QualifierHierarchy createQualifierHierarchy() {
+    return new NoElementQualifierHierarchy(getSupportedTypeQualifiers(), elements) {
+      @Override
+      protected QualifierKindHierarchy createQualifierKindHierarchy(
+          Collection<Class<? extends Annotation>> qualifierClasses) {
+        return new DefaultQualifierKindHierarchy(qualifierClasses, Bottom.class);
+      }
+    };
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/FactoryTestChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FactoryTestChecker.java
new file mode 100644
index 0000000..d449dda
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FactoryTestChecker.java
@@ -0,0 +1,281 @@
+package org.checkerframework.framework.testchecker.util;
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.Tree;
+import com.sun.tools.javac.tree.JCTree;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.processing.SupportedOptions;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.tools.JavaFileObject;
+import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.basetype.BaseTypeVisitor;
+import org.checkerframework.framework.source.SourceChecker;
+import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
+import org.checkerframework.javacutil.BugInCF;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * A specialized checker for testing purposes. It compares an expression's annotated type to an
+ * expected type.
+ *
+ * <p>The expected type is written in a stylized comment (starting with "///") in the same Java
+ * source file. The comment appears either on the same line as the expression, or else by itself on
+ * the line preceding the expression.
+ *
+ * <p>The comments are of two forms:
+ *
+ * <ul>
+ *   <li>{@code /// <expected type>}: to specify the type of the expression in the expression
+ *       statement
+ *   <li>{@code /// <subtree> -:- <expected type>}: to specify the type of the given subexpression
+ *       within the line.
+ * </ul>
+ *
+ * The specified types are allowed to use simple names (e.g., {@code List<String>}), instead of
+ * fully qualified names (e.g., {@code java.util.List<java.lang.String>}).
+ *
+ * <p>Example:
+ *
+ * <pre>
+ *  void test() {
+ *      // Comments in the same line
+ *      Collections.<@NonNull String>emptyList();  /// List<@NonNull String>
+ *      List<@NonNull String> l = Collections.emptyList(); /// Collections.emptyList() -:- List<@NonNull String>
+ *
+ *      // Comments in the previous lines
+ *      /// List<@NonNull String>
+ *      Collections.<@NonNull String>emptyList();
+ *
+ *      /// Collections.emptyList() -:- List<@NonNull String>
+ *      List<@NonNull String> l = Collections.emptyList();
+ *  }
+ * </pre>
+ *
+ * The fully qualified name of the custom <i>AnnotatedTypeFactory</i> is specified through an {@code
+ * -Afactory} command-line argument (e.g. {@code
+ * -Afactory=checkers.nullness.NullnessAnnotatedTypeFactory}). The factory needs to have a
+ * constructor of the form {@code <init>(ProcessingEnvironment, CompilationUnitTree)}.
+ */
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
+@SupportedOptions({"checker"})
+public class FactoryTestChecker extends BaseTypeChecker {
+  SourceChecker checker;
+
+  @Override
+  public void initChecker() {
+    super.initChecker();
+
+    // Find factory constructor
+    String checkerClassName = getOption("checker");
+    try {
+      if (checkerClassName != null) {
+        Class<?> checkerClass = Class.forName(checkerClassName);
+        Constructor<?> constructor = checkerClass.getConstructor();
+        Object o = constructor.newInstance();
+        if (o instanceof SourceChecker) {
+          checker = (SourceChecker) o;
+        }
+      }
+    } catch (Exception e) {
+      throw new BugInCF("Couldn't load " + checkerClassName + " class.");
+    }
+  }
+
+  /*
+  @Override
+  public AnnotatedTypeFactory createTypeFactory() {
+      return checker.createTypeFactory();
+  }*/
+
+  @Override
+  public Properties getMessagesProperties() {
+    // We don't have any properties
+    Properties prop = new Properties();
+    prop.setProperty(
+        "type.unexpected",
+        "unexpected type for the given tree%n"
+            + "Tree       : %s%n"
+            + "Found      : %s%n"
+            + "Expected   : %s%n");
+    return prop;
+  }
+
+  @Override
+  protected BaseTypeVisitor<?> createSourceVisitor() {
+    return new ToStringVisitor(this);
+  }
+
+  /** Builds the expected type for the trees from the source file of the tree compilation unit. */
+  // This method is extremely ugly
+  private Map<TreeSpec, String> buildExpected(CompilationUnitTree tree) {
+    Map<TreeSpec, String> expected = new HashMap<>();
+    try {
+      JavaFileObject o = tree.getSourceFile();
+      File sourceFile = new File(o.toUri());
+      LineNumberReader reader = new LineNumberReader(new FileReader(sourceFile));
+      String line = reader.readLine();
+      Pattern prevsubtreePattern = Pattern.compile("\\s*///(.*)-:-(.*)");
+      Pattern prevfulltreePattern = Pattern.compile("\\s*///(.*)");
+      Pattern subtreePattern = Pattern.compile("(.*)///(.*)-:-(.*)");
+      Pattern fulltreePattern = Pattern.compile("(.*)///(.*)");
+      while (line != null) {
+        Matcher prevsubtreeMatcher = prevsubtreePattern.matcher(line);
+        Matcher prevfulltreeMatcher = prevfulltreePattern.matcher(line);
+        Matcher subtreeMatcher = subtreePattern.matcher(line);
+        Matcher fulltreeMatcher = fulltreePattern.matcher(line);
+        if (prevsubtreeMatcher.matches()) {
+          String treeString = prevsubtreeMatcher.group(1).trim();
+          if (treeString.endsWith(";")) {
+            treeString = treeString.substring(0, treeString.length() - 1);
+          }
+          TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber() + 1);
+          expected.put(treeSpec, canonizeTypeString(prevsubtreeMatcher.group(2)));
+        } else if (prevfulltreeMatcher.matches()) {
+          String treeString = reader.readLine().trim();
+          if (treeString.endsWith(";")) {
+            treeString = treeString.substring(0, treeString.length() - 1);
+          }
+          TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber());
+          expected.put(treeSpec, canonizeTypeString(prevfulltreeMatcher.group(1)));
+        } else if (subtreeMatcher.matches()) {
+          String treeString = subtreeMatcher.group(2).trim();
+          if (treeString.endsWith(";")) {
+            treeString = treeString.substring(0, treeString.length() - 1);
+          }
+          TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber());
+          expected.put(treeSpec, canonizeTypeString(subtreeMatcher.group(3)));
+        } else if (fulltreeMatcher.matches()) {
+          String treeString = fulltreeMatcher.group(1).trim();
+          if (treeString.endsWith(";")) {
+            treeString = treeString.substring(0, treeString.length() - 1);
+          }
+          TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber());
+          expected.put(treeSpec, canonizeTypeString(fulltreeMatcher.group(2)));
+        }
+        line = reader.readLine();
+      }
+      reader.close();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    return expected;
+  }
+
+  /** A method to canonize the tree representation. */
+  private static String canonizeTreeString(String str) {
+    String canon = str.trim();
+    Pattern pattern = Pattern.compile("(@\\S+)\\(\\)");
+    Matcher matcher = pattern.matcher(canon);
+    while (matcher.find()) {
+      canon = matcher.replaceFirst(matcher.group(1));
+      matcher.reset(canon);
+    }
+    return canon.trim();
+  }
+
+  /**
+   * A method to canonize type string representation. It removes any unnecessary white spaces and
+   * finds the type simple name instead of the fully qualified name.
+   *
+   * @param str the type string representation
+   * @return a canonical representation of the type
+   */
+  private static String canonizeTypeString(String str) {
+    String canon = str.trim();
+    canon = canon.replaceAll("\\s+", " ");
+    // Remove spaces between [ ]
+    canon = canon.replaceAll("\\[\\s+", "[");
+    canon = canon.replaceAll("\\s+\\]", "]");
+
+    // Remove spaces between < >
+    canon = canon.replaceAll("<\\s+", "<");
+    canon = canon.replaceAll("\\s+>", ">");
+
+    // Take simply names!
+    canon = canon.replaceAll("[^\\<]*\\.(?=\\w)", "");
+    return canon;
+  }
+
+  /**
+   * A data structure that encapsulate a string and the line number that string appears in the
+   * buffer
+   */
+  private static class TreeSpec {
+    public final String treeString;
+    public final long lineNumber;
+
+    public TreeSpec(String treeString, long lineNumber) {
+      this.treeString = canonizeTreeString(treeString);
+      this.lineNumber = lineNumber;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(treeString, lineNumber);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof TreeSpec) {
+        TreeSpec other = (TreeSpec) o;
+        return treeString.equals(other.treeString) && lineNumber == other.lineNumber;
+      }
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      return lineNumber + ":" + treeString;
+    }
+  }
+
+  /**
+   * A specialized visitor that compares the actual and expected types for the specified trees and
+   * report an error if they differ
+   */
+  private class ToStringVisitor extends BaseTypeVisitor<GenericAnnotatedTypeFactory<?, ?, ?, ?>> {
+    Map<TreeSpec, String> expected;
+
+    public ToStringVisitor(BaseTypeChecker checker) {
+      super(checker);
+      this.expected = buildExpected(root);
+    }
+
+    @Override
+    public Void scan(Tree tree, Void p) {
+      if (TreeUtils.isExpressionTree(tree)) {
+        ExpressionTree expTree = (ExpressionTree) tree;
+        TreeSpec treeSpec =
+            new TreeSpec(
+                expTree.toString().trim(), root.getLineMap().getLineNumber(((JCTree) expTree).pos));
+        if (expected.containsKey(treeSpec)) {
+          String actualType = canonizeTypeString(atypeFactory.getAnnotatedType(expTree).toString());
+          String expectedType = expected.get(treeSpec);
+          if (!actualType.equals(expectedType)) {
+
+            // The key is added above using a setProperty call, which is not supported
+            // by the CompilerMessagesChecker.
+            @SuppressWarnings("compilermessages")
+            @CompilerMessageKey String key = "type.unexpected";
+            FactoryTestChecker.this.reportError(
+                tree, key, tree.toString(), actualType, expectedType);
+          }
+        }
+      }
+      return super.scan(tree, p);
+    }
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestAnnotatedTypeFactory.java
new file mode 100644
index 0000000..ac8d086
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestAnnotatedTypeFactory.java
@@ -0,0 +1,167 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Elements;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.common.subtyping.qual.Bottom;
+import org.checkerframework.common.subtyping.qual.Unqualified;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.util.DefaultQualifierKindHierarchy;
+import org.checkerframework.framework.util.QualifierKind;
+import org.checkerframework.framework.util.QualifierKindHierarchy;
+import org.checkerframework.framework.util.defaults.QualifierDefaults;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.BugInCF;
+
+public class FlowTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+  protected final AnnotationMirror VALUE, BOTTOM, TOP;
+
+  public FlowTestAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker, true);
+    VALUE = AnnotationBuilder.fromClass(elements, Value.class);
+    BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class);
+    TOP = AnnotationBuilder.fromClass(elements, Unqualified.class);
+
+    this.postInit();
+  }
+
+  @Override
+  protected void addCheckedCodeDefaults(QualifierDefaults defs) {
+    defs.addCheckedCodeDefault(BOTTOM, TypeUseLocation.LOWER_BOUND);
+    defs.addCheckedCodeDefault(TOP, TypeUseLocation.OTHERWISE);
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<Class<? extends Annotation>>(
+        Arrays.asList(Value.class, Odd.class, MonotonicOdd.class, Unqualified.class, Bottom.class));
+  }
+
+  @Override
+  @SuppressWarnings("deprecation") // TODO: REVERT: Just testing backward compatibility.
+  public QualifierHierarchy createQualifierHierarchy() {
+    return org.checkerframework.framework.util.MultiGraphQualifierHierarchy
+        .createMultiGraphQualifierHierarchy(this);
+  }
+
+  @Override
+  @SuppressWarnings("deprecation") // TODO: REVERT: Just testing backward compatibility.
+  public QualifierHierarchy createQualifierHierarchyWithMultiGraphFactory(
+      org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory factory) {
+    return new OldFlowQualifierHierarchy(factory, BOTTOM);
+  }
+
+  @SuppressWarnings("deprecation") // TODO: REVERT: Just testing backward compatibility.
+  class OldFlowQualifierHierarchy
+      extends org.checkerframework.framework.util.GraphQualifierHierarchy {
+
+    public OldFlowQualifierHierarchy(MultiGraphFactory f, AnnotationMirror bottom) {
+      super(f, bottom);
+    }
+
+    @Override
+    public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
+      if (AnnotationUtils.areSameByName(superAnno, VALUE)
+          && AnnotationUtils.areSameByName(subAnno, VALUE)) {
+        return AnnotationUtils.areSame(superAnno, subAnno);
+      }
+      if (AnnotationUtils.areSameByName(superAnno, VALUE)) {
+        superAnno = VALUE;
+      }
+      if (AnnotationUtils.areSameByName(subAnno, VALUE)) {
+        subAnno = VALUE;
+      }
+      return super.isSubtype(subAnno, superAnno);
+    }
+  }
+
+  //    @Override
+  //    protected QualifierHierarchy createQualifierHierarchy() {
+  //        return new FlowQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
+  //    }
+
+  /** FlowQualifierHierarchy: {@code @Value(a) <: @Value(b) iff a == b} */
+  class FlowQualifierHierarchy extends MostlyNoElementQualifierHierarchy {
+    final QualifierKind VALUE_KIND;
+
+    /**
+     * Creates a FlowQualifierHierarchy from the given classes.
+     *
+     * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
+     * @param elements element utils
+     */
+    public FlowQualifierHierarchy(
+        Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
+      super(qualifierClasses, elements);
+      this.VALUE_KIND = getQualifierKind(VALUE);
+    }
+
+    @Override
+    protected QualifierKindHierarchy createQualifierKindHierarchy(
+        Collection<Class<? extends Annotation>> qualifierClasses) {
+      return new DefaultQualifierKindHierarchy(qualifierClasses, Bottom.class);
+    }
+
+    @Override
+    protected boolean isSubtypeWithElements(
+        AnnotationMirror subAnno,
+        QualifierKind subKind,
+        AnnotationMirror superAnno,
+        QualifierKind superKind) {
+      return AnnotationUtils.areSame(superAnno, subAnno);
+    }
+
+    @Override
+    protected AnnotationMirror leastUpperBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind lubKind) {
+      if (qualifierKind1 == qualifierKind2) {
+        // Both are Value
+        if (AnnotationUtils.areSame(a1, a2)) {
+          return a1;
+        } else {
+          return TOP;
+        }
+      } else if (qualifierKind1 == VALUE_KIND) {
+        return a1;
+      } else if (qualifierKind2 == VALUE_KIND) {
+        return a2;
+      }
+      throw new BugInCF("Unexpected annotations: leastUpperBoundWithElements(%s, %s)", a1, a2);
+    }
+
+    @Override
+    protected AnnotationMirror greatestLowerBoundWithElements(
+        AnnotationMirror a1,
+        QualifierKind qualifierKind1,
+        AnnotationMirror a2,
+        QualifierKind qualifierKind2,
+        QualifierKind glbKind) {
+      if (qualifierKind1 == qualifierKind2) {
+        // Both are Value
+        if (AnnotationUtils.areSame(a1, a2)) {
+          return a1;
+        } else {
+          return BOTTOM;
+        }
+      } else if (qualifierKind1 == VALUE_KIND) {
+        return a1;
+      } else if (qualifierKind2 == VALUE_KIND) {
+        return a2;
+      }
+      throw new BugInCF("Unexpected annotations: greatestLowerBoundWithElements(%s, %s)", a1, a2);
+    }
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestChecker.java
new file mode 100644
index 0000000..ad60a10
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestChecker.java
@@ -0,0 +1,5 @@
+package org.checkerframework.framework.testchecker.util;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+public final class FlowTestChecker extends BaseTypeChecker {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/MonotonicOdd.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/MonotonicOdd.java
new file mode 100644
index 0000000..36a8492
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/MonotonicOdd.java
@@ -0,0 +1,14 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Target;
+import org.checkerframework.common.subtyping.qual.Unqualified;
+import org.checkerframework.framework.qual.MonotonicQualifier;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@Inherited
+@SubtypeOf(Unqualified.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@MonotonicQualifier(Odd.class)
+public @interface MonotonicOdd {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Odd.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Odd.java
new file mode 100644
index 0000000..fe54076
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Odd.java
@@ -0,0 +1,9 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf(MonotonicOdd.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Odd {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternA.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternA.java
new file mode 100644
index 0000000..07d863b
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternA.java
@@ -0,0 +1,11 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf({PatternAB.class, PatternAC.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@QualifierForLiterals(stringPatterns = "^[A]$")
+public @interface PatternA {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAB.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAB.java
new file mode 100644
index 0000000..dc27680
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAB.java
@@ -0,0 +1,11 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf(PatternUnknown.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@QualifierForLiterals(stringPatterns = "^[AB]$")
+public @interface PatternAB {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAC.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAC.java
new file mode 100644
index 0000000..bdcef53
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAC.java
@@ -0,0 +1,11 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf(PatternUnknown.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@QualifierForLiterals(stringPatterns = "^[AC]$")
+public @interface PatternAC {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternB.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternB.java
new file mode 100644
index 0000000..6b2b13d
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternB.java
@@ -0,0 +1,11 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf({PatternAB.class, PatternBC.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@QualifierForLiterals(stringPatterns = "^[B]$")
+public @interface PatternB {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBC.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBC.java
new file mode 100644
index 0000000..b68509f
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBC.java
@@ -0,0 +1,11 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf(PatternUnknown.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@QualifierForLiterals(stringPatterns = "^[BC]$")
+public @interface PatternBC {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBottomFull.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBottomFull.java
new file mode 100644
index 0000000..7135fb6
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBottomFull.java
@@ -0,0 +1,12 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.TargetLocations;
+import org.checkerframework.framework.qual.TypeUseLocation;
+
+@SubtypeOf({PatternA.class, PatternB.class, PatternC.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND})
+public @interface PatternBottomFull {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternC.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternC.java
new file mode 100644
index 0000000..ef12d93
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternC.java
@@ -0,0 +1,11 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.QualifierForLiterals;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf({PatternBC.class, PatternAC.class})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@QualifierForLiterals(stringPatterns = "^[C]$")
+public @interface PatternC {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternUnknown.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternUnknown.java
new file mode 100644
index 0000000..bbccec3
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternUnknown.java
@@ -0,0 +1,11 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@DefaultQualifierInHierarchy
+@SubtypeOf({})
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface PatternUnknown {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PolyEncrypted.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PolyEncrypted.java
new file mode 100644
index 0000000..05ec1d3
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PolyEncrypted.java
@@ -0,0 +1,9 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+@PolymorphicQualifier
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface PolyEncrypted {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/RequiresOdd.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/RequiresOdd.java
new file mode 100644
index 0000000..81dafd5
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/RequiresOdd.java
@@ -0,0 +1,20 @@
+package org.checkerframework.framework.testchecker.util;
+
+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;
+import org.checkerframework.framework.qual.PreconditionAnnotation;
+
+/**
+ * A precondition annotation to indicate that a method requires certain expressions to be {@link
+ * Odd}.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@PreconditionAnnotation(qualifier = Odd.class)
+public @interface RequiresOdd {
+  String[] value();
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/SubQual.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/SubQual.java
new file mode 100644
index 0000000..db0eab4
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/SubQual.java
@@ -0,0 +1,10 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** A subtype of SuperQual. */
+@SubtypeOf(SuperQual.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface SubQual {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/SuperQual.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/SuperQual.java
new file mode 100644
index 0000000..17b5e8b
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/SuperQual.java
@@ -0,0 +1,12 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** A supertype of SubQual. */
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface SuperQual {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Value.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Value.java
new file mode 100644
index 0000000..e17328e
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Value.java
@@ -0,0 +1,12 @@
+package org.checkerframework.framework.testchecker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.common.subtyping.qual.Unqualified;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+@SubtypeOf(Unqualified.class)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Value {
+  int value() default 0;
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/package-info.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/package-info.java
new file mode 100644
index 0000000..413e617
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/package-info.java
@@ -0,0 +1,11 @@
+/**
+ * A minimal checker and simple annotation used for testing the Checker Framework without the
+ * semantics of any particular (meaningful) checker.
+ *
+ * <p>The checker and annotation in this package should only be used for testing.
+ *
+ * <p>The {@code @Odd} annotation is a straightforward subtype-style qualifier. It has no special
+ * semantics; values that do not have an {@code @Odd} type cannot be assigned to values that do have
+ * the {@code @Odd} type.
+ */
+package org.checkerframework.framework.testchecker.util;
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultAnnotatedTypeFactory.java
new file mode 100644
index 0000000..80ab054
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultAnnotatedTypeFactory.java
@@ -0,0 +1,29 @@
+package org.checkerframework.framework.testchecker.variablenamedefault;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.testchecker.variablenamedefault.quals.PolyVariableNameDefault;
+import org.checkerframework.framework.testchecker.variablenamedefault.quals.VariableNameDefaultBottom;
+import org.checkerframework.framework.testchecker.variablenamedefault.quals.VariableNameDefaultMiddle;
+import org.checkerframework.framework.testchecker.variablenamedefault.quals.VariableNameDefaultTop;
+
+public class VariableNameDefaultAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+  public VariableNameDefaultAnnotatedTypeFactory(BaseTypeChecker checker) {
+    super(checker);
+    this.postInit();
+  }
+
+  @Override
+  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
+    return new HashSet<>(
+        Arrays.asList(
+            VariableNameDefaultTop.class,
+            VariableNameDefaultMiddle.class,
+            VariableNameDefaultBottom.class,
+            PolyVariableNameDefault.class));
+  }
+}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultChecker.java
new file mode 100644
index 0000000..83ce3c1
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultChecker.java
@@ -0,0 +1,6 @@
+package org.checkerframework.framework.testchecker.variablenamedefault;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+/** A type-checker plug-in for the VariableNameDefault type system. */
+public class VariableNameDefaultChecker extends BaseTypeChecker {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/PolyVariableNameDefault.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/PolyVariableNameDefault.java
new file mode 100644
index 0000000..7b148ed
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/PolyVariableNameDefault.java
@@ -0,0 +1,15 @@
+package org.checkerframework.framework.testchecker.variablenamedefault.quals;
+
+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;
+import org.checkerframework.framework.qual.PolymorphicQualifier;
+
+/** A polymorphic qualifier for the VariableNameDefault type system. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier
+public @interface PolyVariableNameDefault {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultBottom.java
new file mode 100644
index 0000000..436c04b
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultBottom.java
@@ -0,0 +1,17 @@
+package org.checkerframework.framework.testchecker.variablenamedefault.quals;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** VariableNameDefault bottom qualifier. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(VariableNameDefaultMiddle.class)
+@DefaultFor(names = ".*bottom.*", namesExceptions = ".*notbottom.*")
+public @interface VariableNameDefaultBottom {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultMiddle.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultMiddle.java
new file mode 100644
index 0000000..ddba035
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultMiddle.java
@@ -0,0 +1,17 @@
+package org.checkerframework.framework.testchecker.variablenamedefault.quals;
+
+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;
+import org.checkerframework.framework.qual.DefaultFor;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** VariableNameDefault middle qualifier. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf(VariableNameDefaultTop.class)
+@DefaultFor(names = ".*middle.*", namesExceptions = ".*notmiddle.*")
+public @interface VariableNameDefaultMiddle {}
diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultTop.java
new file mode 100644
index 0000000..b58a1c3
--- /dev/null
+++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultTop.java
@@ -0,0 +1,17 @@
+package org.checkerframework.framework.testchecker.variablenamedefault.quals;
+
+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;
+import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/** This is the top qualifier of the VariableNameDefault type system. */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@SubtypeOf({})
+@DefaultQualifierInHierarchy
+public @interface VariableNameDefaultTop {}
diff --git a/framework/src/testannotations/java/android/support/annotation/IntRange.java b/framework/src/testannotations/java/android/support/annotation/IntRange.java
new file mode 100644
index 0000000..03f547e
--- /dev/null
+++ b/framework/src/testannotations/java/android/support/annotation/IntRange.java
@@ -0,0 +1,13 @@
+package android.support.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Documented
+@Retention(RetentionPolicy.CLASS)
+public @interface IntRange {
+  long from() default Long.MIN_VALUE;
+
+  long to() default Long.MAX_VALUE;
+}
diff --git a/framework/tests/README b/framework/tests/README
new file mode 100644
index 0000000..5ed6227
--- /dev/null
+++ b/framework/tests/README
@@ -0,0 +1,5 @@
+The framework tests run without the annotated JDK.  This means they cannot
+depend on side-effect annotations (@Pure, @SideEffectFree) in the JDK.
+
+Since these are tests of the framework, they do not use the
+org.checkerframework.checker package.
diff --git a/framework/tests/accumulation-norr/SimpleFluent.java b/framework/tests/accumulation-norr/SimpleFluent.java
new file mode 100644
index 0000000..88e996d
--- /dev/null
+++ b/framework/tests/accumulation-norr/SimpleFluent.java
@@ -0,0 +1,125 @@
+// A simple test that the fluent API logic in the Accumulation Checker works.
+// This version has been edited to include expected errors because the Returns Receiver Checker
+// is disabled.
+
+import org.checkerframework.common.returnsreceiver.qual.*;
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+/* Simple inference of a fluent builder. */
+public class SimpleFluent {
+  SimpleFluent build(@TestAccumulation({"a", "b"}) SimpleFluent this) {
+    return this;
+  }
+
+  @This SimpleFluent build2(@TestAccumulation({"a", "b"}) SimpleFluent this) {
+    return this;
+  }
+
+  @This SimpleFluent a() {
+    return this;
+  }
+
+  @This SimpleFluent b() {
+    return this;
+  }
+
+  // intentionally does not have an @This annotation
+  SimpleFluent c() {
+    return this;
+  }
+
+  static void doStuffCorrect(@TestAccumulation({"a", "b"}) SimpleFluent s) {
+    // :: error: method.invocation
+    s.a().b().build();
+  }
+
+  static void doStuffWrong(@TestAccumulation({"a"}) SimpleFluent s) {
+    s.a()
+        // :: error: method.invocation
+        .build();
+  }
+
+  static void noReturnsReceiverAnno(@TestAccumulation({"a", "b"}) SimpleFluent s) {
+    s.a()
+        .b()
+        .c()
+        // :: error: method.invocation
+        .build();
+  }
+
+  static void mixFluentAndNonFluent(SimpleFluent s1) {
+    s1.a().b();
+    // :: error: method.invocation
+    s1.build();
+  }
+
+  static void mixFluentAndNonFluentWrong(SimpleFluent s) {
+    s.a(); // .b()
+    // :: error: method.invocation
+    s.build();
+  }
+
+  static void fluentLoop(SimpleFluent t) {
+    SimpleFluent s = t.a();
+    int i = 10;
+    while (i > 0) {
+      // :: error: method.invocation
+      s.b().build();
+      i--;
+      s = new SimpleFluent();
+    }
+  }
+
+  static void m1(SimpleFluent s) {
+    // :: error: method.invocation
+    s.c().a().b().build();
+  }
+
+  static void m2(SimpleFluent s) {
+    s.c().a().b();
+    // :: error: method.invocation
+    s.c().build();
+  }
+
+  static void m3(SimpleFluent s) {
+    // :: error: method.invocation
+    s.c().a().b().build();
+    // :: error: method.invocation
+    s.c().a().build();
+  }
+
+  static void m4(SimpleFluent s) {
+    // :: error: method.invocation
+    s.c().a().b().build2().build();
+  }
+
+  static void m5(SimpleFluent s) {
+    s.a().c();
+    // :: error: method.invocation
+    s.b().build();
+  }
+
+  static void m6(SimpleFluent s) {
+    // :: error: method.invocation
+    s.a().c().b().build();
+  }
+
+  static void m7(SimpleFluent s) {
+    // :: error: method.invocation
+    s.a().b().build().c().b().build();
+  }
+
+  static void m8(SimpleFluent s) {
+    // :: error: method.invocation
+    s.a().build().c().a().b().build();
+    // :: error: method.invocation
+    s.build();
+  }
+
+  static void m9() {
+    // :: error: method.invocation
+    new SimpleFluent().a().b().build();
+    // :: error: method.invocation
+    new SimpleFluent().a().build();
+  }
+}
diff --git a/framework/tests/accumulation/Generics.java b/framework/tests/accumulation/Generics.java
new file mode 100644
index 0000000..4fb90b5
--- /dev/null
+++ b/framework/tests/accumulation/Generics.java
@@ -0,0 +1,43 @@
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.common.returnsreceiver.qual.*;
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+public class Generics {
+
+  static interface Symbol {
+
+    boolean isStatic();
+
+    void finalize(@TestAccumulation("foo") Symbol this);
+  }
+
+  static List<@TestAccumulation("foo") Symbol> makeList(@TestAccumulation("foo") Symbol s) {
+    ArrayList<@TestAccumulation("foo") Symbol> l = new ArrayList<>();
+    l.add(s);
+    return l;
+  }
+
+  static void useList() {
+    Symbol s = null;
+    for (Symbol t : makeList(s)) {
+      t.finalize();
+    }
+  }
+
+  // reduced from real-world code
+  private <@TestAccumulation() T extends Symbol> T getMember(Class<T> type, boolean b) {
+    if (b) {
+      T sym = getMember(type, !b);
+      if (sym != null && sym.isStatic()) {
+        return sym;
+      }
+    } else {
+      T sym = getMember(type, b);
+      if (sym != null) {
+        return sym;
+      }
+    }
+    return null;
+  }
+}
diff --git a/framework/tests/accumulation/Not.java b/framework/tests/accumulation/Not.java
new file mode 100644
index 0000000..726c85f
--- /dev/null
+++ b/framework/tests/accumulation/Not.java
@@ -0,0 +1,75 @@
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+public class Not {
+
+  class Foo {
+    void a() {}
+
+    void b() {}
+
+    void c() {}
+
+    void notA(@TestAccumulationPredicate("!a") Foo this) {}
+
+    void notB(@TestAccumulationPredicate("!b") Foo this) {}
+  }
+
+  void test1(Foo f) {
+    f.notA();
+    f.notB();
+  }
+
+  void test2(Foo f) {
+    f.c();
+    f.notA();
+    f.notB();
+  }
+
+  void test3(Foo f) {
+    f.a();
+    // :: error: method.invocation
+    f.notA();
+    f.notB();
+  }
+
+  void test4(Foo f) {
+    f.b();
+    f.notA();
+    // :: error: method.invocation
+    f.notB();
+  }
+
+  void test5(Foo f) {
+    f.a();
+    f.b();
+    // :: error: method.invocation
+    f.notA();
+    // :: error: method.invocation
+    f.notB();
+  }
+
+  void callA(Foo f) {
+    f.a();
+  }
+
+  void test6(Foo f) {
+    callA(f);
+    // DEMONSTRATION OF "UNSOUNDNESS"
+    f.notA();
+  }
+
+  void test7(@TestAccumulation("a") Foo f) {
+    // :: error: method.invocation
+    f.notA();
+  }
+
+  void test8(Foo f, boolean test) {
+    if (test) {
+      f.a();
+    } else {
+      f.b();
+    }
+    // DEMONSTRATION OF "UNSOUNDNESS"
+    f.notA();
+  }
+}
diff --git a/framework/tests/accumulation/ObjectConstructionIssue20.java b/framework/tests/accumulation/ObjectConstructionIssue20.java
new file mode 100644
index 0000000..ee7d5e8
--- /dev/null
+++ b/framework/tests/accumulation/ObjectConstructionIssue20.java
@@ -0,0 +1,36 @@
+// test case for issue 20: https://github.com/kelloggm/object-construction-checker/issues/20
+
+import java.util.Map;
+
+public class ObjectConstructionIssue20<E> {
+
+  private boolean enableProtoAnnotations;
+
+  @SuppressWarnings({"unchecked"})
+  private <T, O extends Message, E extends ProtoElement> T getProtoExtension(
+      E element, GeneratedExtension<O, T> extension) {
+    // Use this method as the chokepoint for all field annotations processing, so we can
+    // toggle on/off annotations processing in one place.
+    if (!enableProtoAnnotations) {
+      return null;
+    }
+    return (T) element.getOptionFields().get(extension.getDescriptor());
+  }
+
+  // stubs of relevant classes
+  private class Message {}
+
+  private class ProtoElement {
+    public Map<FieldDescriptor, Object> getOptionFields() {
+      return null;
+    }
+  }
+
+  private class FieldDescriptor {}
+
+  private class GeneratedExtension<O, T> {
+    public FieldDescriptor getDescriptor() {
+      return null;
+    }
+  }
+}
diff --git a/framework/tests/accumulation/Parens.java b/framework/tests/accumulation/Parens.java
new file mode 100644
index 0000000..668e65a
--- /dev/null
+++ b/framework/tests/accumulation/Parens.java
@@ -0,0 +1,5 @@
+public class Parens {
+  public synchronized void incrementPushed(long[] pushed, int operationType) {
+    ++(pushed[operationType]);
+  }
+}
diff --git a/framework/tests/accumulation/Predicates.java b/framework/tests/accumulation/Predicates.java
new file mode 100644
index 0000000..2352830
--- /dev/null
+++ b/framework/tests/accumulation/Predicates.java
@@ -0,0 +1,607 @@
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+public class Predicates {
+
+  void testOr1() {
+    MyClass m1 = new MyClass();
+
+    // :: error: method.invocation
+    m1.c();
+  }
+
+  void testOr2() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.c();
+  }
+
+  void testOr3() {
+    MyClass m1 = new MyClass();
+
+    m1.b();
+    m1.c();
+  }
+
+  void testAnd1() {
+    MyClass m1 = new MyClass();
+
+    // :: error: method.invocation
+    m1.d();
+  }
+
+  void testAnd2() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    // :: error: method.invocation
+    m1.d();
+  }
+
+  void testAnd3() {
+    MyClass m1 = new MyClass();
+
+    m1.b();
+    // :: error: method.invocation
+    m1.d();
+  }
+
+  void testAnd4() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.c();
+    // :: error: method.invocation
+    m1.d();
+  }
+
+  void testAnd5() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.b();
+    m1.d();
+  }
+
+  void testAnd6() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.b();
+    m1.c();
+    m1.d();
+  }
+
+  void testAndOr1() {
+    MyClass m1 = new MyClass();
+
+    // :: error: method.invocation
+    m1.e();
+  }
+
+  void testAndOr2() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.e();
+  }
+
+  void testAndOr3() {
+    MyClass m1 = new MyClass();
+
+    m1.b();
+    // :: error: method.invocation
+    m1.e();
+  }
+
+  void testAndOr4() {
+    MyClass m1 = new MyClass();
+
+    m1.b();
+    m1.c();
+    m1.e();
+  }
+
+  void testAndOr5() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.b();
+    m1.c();
+    m1.d();
+    m1.e();
+  }
+
+  void testPrecedence1() {
+    MyClass m1 = new MyClass();
+
+    // :: error: method.invocation
+    m1.f();
+  }
+
+  void testPrecedence2() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    // :: error: method.invocation
+    m1.f();
+  }
+
+  void testPrecedence3() {
+    MyClass m1 = new MyClass();
+
+    m1.b();
+    // :: error: method.invocation
+    m1.f();
+  }
+
+  void testPrecedence4() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.b();
+    m1.f();
+  }
+
+  void testPrecedence5() {
+    MyClass m1 = new MyClass();
+
+    m1.a();
+    m1.c();
+    m1.f();
+  }
+
+  void testPrecedence6() {
+    MyClass m1 = new MyClass();
+
+    m1.b();
+    m1.c();
+    m1.f();
+  }
+
+  private static class MyClass {
+
+    @TestAccumulation("a") MyClass cmA;
+
+    @TestAccumulationPredicate("a") MyClass cmpA;
+
+    @TestAccumulation({"a", "b"}) MyClass aB;
+
+    @TestAccumulationPredicate("a || b") MyClass aOrB;
+
+    @TestAccumulationPredicate("a && b") MyClass aAndB;
+
+    @TestAccumulationPredicate("a || b && c") MyClass bAndCOrA;
+
+    @TestAccumulationPredicate("a || (b && c)") MyClass bAndCOrAParens;
+
+    @TestAccumulationPredicate("a && b || c") MyClass aAndBOrC;
+
+    @TestAccumulationPredicate("(a && b) || c") MyClass aAndBOrCParens;
+
+    @TestAccumulationPredicate("(a || b) && c") MyClass aOrBAndC;
+
+    @TestAccumulationPredicate("a && (b || c)") MyClass bOrCAndA;
+
+    @TestAccumulationPredicate("b && c") MyClass bAndC;
+
+    @TestAccumulationPredicate("(b && c)") MyClass bAndCParens;
+
+    void a() {}
+
+    void b() {}
+
+    void c(@TestAccumulationPredicate("a || b") MyClass this) {}
+
+    void d(@TestAccumulationPredicate("a && b") MyClass this) {}
+
+    void e(@TestAccumulationPredicate("a || (b && c)") MyClass this) {}
+
+    void f(@TestAccumulationPredicate("a && b || c") MyClass this) {}
+
+    static void testAssignability1(@TestAccumulationPredicate("a || b") MyClass cAble) {
+      cAble.c();
+      // :: error: method.invocation
+      cAble.d();
+      // :: error: method.invocation
+      cAble.e();
+      // :: error: method.invocation
+      cAble.f();
+    }
+
+    static void testAssignability2(@TestAccumulationPredicate("a && b") MyClass dAble) {
+      // These calls would work if subtyping between predicates was by implication. They issue
+      // errors, because it is not.
+      // :: error: method.invocation
+      dAble.c();
+      dAble.d();
+      // :: error: method.invocation
+      dAble.e();
+      // :: error: method.invocation
+      dAble.f();
+    }
+
+    void testAllAssignability() {
+
+      @TestAccumulation("a") MyClass cmALocal;
+      @TestAccumulationPredicate("a") MyClass cmpALocal;
+      @TestAccumulationPredicate("a || b") MyClass aOrBLocal;
+      @TestAccumulation({"a", "b"}) MyClass aBLocal;
+      @TestAccumulationPredicate("a && b") MyClass aAndBLocal;
+      @TestAccumulationPredicate("a || b && c") MyClass bAndCOrALocal;
+      @TestAccumulationPredicate("a || (b && c)") MyClass bAndCOrAParensLocal;
+      @TestAccumulationPredicate("a && b || c") MyClass aAndBOrCLocal;
+      @TestAccumulationPredicate("(a && b) || c") MyClass aAndBOrCParensLocal;
+      @TestAccumulationPredicate("(a || b) && c") MyClass aOrBAndCLocal;
+      @TestAccumulationPredicate("a && (b || c)") MyClass bOrCAndALocal;
+      @TestAccumulationPredicate("b && c") MyClass bAndCLocal;
+      @TestAccumulationPredicate("(b && c)") MyClass bAndCParensLocal;
+
+      cmALocal = cmA;
+      cmALocal = cmpA;
+      // :: error: assignment
+      cmALocal = aOrB;
+      cmALocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      cmALocal = aAndB;
+      // :: error: assignment
+      cmALocal = bAndCOrA;
+      // :: error: assignment
+      cmALocal = bAndCOrAParens;
+      // :: error: assignment
+      cmALocal = aAndBOrC;
+      // :: error: assignment
+      cmALocal = aAndBOrCParens;
+      // :: error: assignment
+      cmALocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      cmALocal = bOrCAndA;
+      // :: error: assignment
+      cmALocal = bAndC;
+      // :: error: assignment
+      cmALocal = bAndCParens;
+
+      cmpALocal = cmA;
+      cmpALocal = cmpA;
+      // :: error: assignment
+      cmpALocal = aOrB;
+      cmpALocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      cmpALocal = aAndB;
+      // :: error: assignment
+      cmpALocal = bAndCOrA;
+      // :: error: assignment
+      cmpALocal = bAndCOrAParens;
+      // :: error: assignment
+      cmpALocal = aAndBOrC;
+      // :: error: assignment
+      cmpALocal = aAndBOrCParens;
+      // :: error: assignment
+      cmpALocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      cmpALocal = bOrCAndA;
+      // :: error: assignment
+      cmpALocal = bAndC;
+      // :: error: assignment
+      cmpALocal = bAndCParens;
+
+      aOrBLocal = cmA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = cmpA;
+      aOrBLocal = aOrB;
+      aOrBLocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = aAndB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = bAndCOrA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = bAndCOrAParens;
+      // :: error: assignment
+      aOrBLocal = aAndBOrC;
+      // :: error: assignment
+      aOrBLocal = aAndBOrCParens;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBLocal = bAndCParens;
+
+      // :: error: (assignment)
+      aBLocal = cmA;
+      // :: error: (assignment)
+      aBLocal = cmpA;
+      // :: error: (assignment)
+      aBLocal = aOrB;
+      aBLocal = aB;
+      aBLocal = aAndB;
+      // :: error: (assignment)
+      aBLocal = bAndCOrA;
+      // :: error: (assignment)
+      aBLocal = bAndCOrAParens;
+      // :: error: (assignment)
+      aBLocal = aAndBOrC;
+      // :: error: (assignment)
+      aBLocal = aAndBOrCParens;
+      // :: error: (assignment)
+      aBLocal = aOrBAndC;
+      // :: error: (assignment)
+      aBLocal = bOrCAndA;
+      // :: error: (assignment)
+      aBLocal = bAndC;
+      // :: error: (assignment)
+      aBLocal = bAndCParens;
+
+      // :: error: (assignment)
+      aAndBLocal = cmA;
+      // :: error: (assignment)
+      aAndBLocal = cmpA;
+      // :: error: (assignment)
+      aAndBLocal = aOrB;
+      aAndBLocal = aB;
+      aAndBLocal = aAndB;
+      // :: error: (assignment)
+      aAndBLocal = bAndCOrA;
+      // :: error: (assignment)
+      aAndBLocal = bAndCOrAParens;
+      // :: error: (assignment)
+      aAndBLocal = aAndBOrC;
+      // :: error: (assignment)
+      aAndBLocal = aAndBOrCParens;
+      // :: error: (assignment)
+      aAndBLocal = aOrBAndC;
+      // :: error: (assignment)
+      aAndBLocal = bOrCAndA;
+      // :: error: (assignment)
+      aAndBLocal = bAndC;
+      // :: error: (assignment)
+      aAndBLocal = bAndCParens;
+
+      bAndCOrALocal = cmA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = cmpA;
+      // :: error: (assignment)
+      bAndCOrALocal = aOrB;
+      bAndCOrALocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = aAndB;
+      bAndCOrALocal = bAndCOrA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = bAndCOrAParens;
+      // :: error: (assignment)
+      bAndCOrALocal = aAndBOrC;
+      // :: error: (assignment)
+      bAndCOrALocal = aAndBOrCParens;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrALocal = bAndCParens;
+
+      bAndCOrAParensLocal = cmA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = cmpA;
+      // :: error: (assignment)
+      bAndCOrAParensLocal = aOrB;
+      bAndCOrAParensLocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = aAndB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = bAndCOrA;
+      bAndCOrAParensLocal = bAndCOrAParens;
+      // :: error: (assignment)
+      bAndCOrAParensLocal = aAndBOrC;
+      // :: error: (assignment)
+      bAndCOrAParensLocal = aAndBOrCParens;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCOrAParensLocal = bAndCParens;
+
+      // :: error: (assignment)
+      aAndBOrCLocal = cmA;
+      // :: error: (assignment)
+      aAndBOrCLocal = cmpA;
+      // :: error: (assignment)
+      aAndBOrCLocal = aOrB;
+      aAndBOrCLocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCLocal = aAndB;
+      // :: error: (assignment)
+      aAndBOrCLocal = bAndCOrA;
+      // :: error: (assignment)
+      aAndBOrCLocal = bAndCOrAParens;
+      aAndBOrCLocal = aAndBOrC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCLocal = aAndBOrCParens;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCLocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCLocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCLocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCLocal = bAndCParens;
+
+      // :: error: (assignment)
+      aAndBOrCParensLocal = cmA;
+      // :: error: (assignment)
+      aAndBOrCParensLocal = cmpA;
+      // :: error: (assignment)
+      aAndBOrCParensLocal = aOrB;
+      aAndBOrCParensLocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCParensLocal = aAndB;
+      // :: error: (assignment)
+      aAndBOrCParensLocal = bAndCOrA;
+      // :: error: (assignment)
+      aAndBOrCParensLocal = bAndCOrAParens;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCParensLocal = aAndBOrC;
+      aAndBOrCParensLocal = aAndBOrCParens;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCParensLocal = aOrBAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCParensLocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCParensLocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aAndBOrCParensLocal = bAndCParens;
+
+      // :: error: (assignment)
+      aOrBAndCLocal = cmA;
+      // :: error: (assignment)
+      aOrBAndCLocal = cmpA;
+      // :: error: (assignment)
+      aOrBAndCLocal = aOrB;
+      // :: error: (assignment)
+      aOrBAndCLocal = aB;
+      // :: error: (assignment)
+      aOrBAndCLocal = aAndB;
+      // :: error: (assignment)
+      aOrBAndCLocal = bAndCOrA;
+      // :: error: (assignment)
+      aOrBAndCLocal = bAndCOrAParens;
+      // :: error: (assignment)
+      aOrBAndCLocal = aAndBOrC;
+      // :: error: (assignment)
+      aOrBAndCLocal = aAndBOrCParens;
+      aOrBAndCLocal = aOrBAndC;
+      // :: error: (assignment)
+      aOrBAndCLocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBAndCLocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      aOrBAndCLocal = bAndCParens;
+
+      // :: error: (assignment)
+      bOrCAndALocal = cmA;
+      // :: error: (assignment)
+      bOrCAndALocal = cmpA;
+      // :: error: (assignment)
+      bOrCAndALocal = aOrB;
+      bOrCAndALocal = aB;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bOrCAndALocal = aAndB;
+      // :: error: (assignment)
+      bOrCAndALocal = bAndCOrA;
+      // :: error: (assignment)
+      bOrCAndALocal = bAndCOrAParens;
+      // :: error: (assignment)
+      bOrCAndALocal = aAndBOrC;
+      // :: error: (assignment)
+      bOrCAndALocal = aAndBOrCParens;
+      // :: error: (assignment)
+      bOrCAndALocal = aOrBAndC;
+      bOrCAndALocal = bOrCAndA;
+      // :: error: (assignment)
+      bOrCAndALocal = bAndC;
+      // :: error: (assignment)
+      bOrCAndALocal = bAndCParens;
+
+      // :: error: (assignment)
+      bAndCLocal = cmA;
+      // :: error: (assignment)
+      bAndCLocal = cmpA;
+      // :: error: (assignment)
+      bAndCLocal = aOrB;
+      // :: error: (assignment)
+      bAndCLocal = aB;
+      // :: error: (assignment)
+      bAndCLocal = aAndB;
+      // :: error: (assignment)
+      bAndCLocal = bAndCOrA;
+      // :: error: (assignment)
+      bAndCLocal = bAndCOrAParens;
+      // :: error: (assignment)
+      bAndCLocal = aAndBOrC;
+      // :: error: (assignment)
+      bAndCLocal = aAndBOrCParens;
+      // :: error: (assignment)
+      bAndCLocal = aOrBAndC;
+      // :: error: (assignment)
+      bAndCLocal = bOrCAndA;
+      bAndCLocal = bAndC;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCLocal = bAndCParens;
+
+      // :: error: (assignment)
+      bAndCParensLocal = cmA;
+      // :: error: (assignment)
+      bAndCParensLocal = cmpA;
+      // :: error: (assignment)
+      bAndCParensLocal = aOrB;
+      // :: error: (assignment)
+      bAndCParensLocal = aB;
+      // :: error: (assignment)
+      bAndCParensLocal = aAndB;
+      // :: error: (assignment)
+      bAndCParensLocal = bAndCOrA;
+      // :: error: (assignment)
+      bAndCParensLocal = bAndCOrAParens;
+      // :: error: (assignment)
+      bAndCParensLocal = aAndBOrC;
+      // :: error: (assignment)
+      bAndCParensLocal = aAndBOrCParens;
+      // :: error: (assignment)
+      bAndCParensLocal = aOrBAndC;
+      // :: error: (assignment)
+      bAndCParensLocal = bOrCAndA;
+      // The next line would not fail if predicate subtyping was decided by implication.
+      // :: error: assignment
+      bAndCParensLocal = bAndC;
+      bAndCParensLocal = bAndCParens;
+    }
+  }
+}
diff --git a/framework/tests/accumulation/SimpleFluent.java b/framework/tests/accumulation/SimpleFluent.java
new file mode 100644
index 0000000..2b9a566
--- /dev/null
+++ b/framework/tests/accumulation/SimpleFluent.java
@@ -0,0 +1,116 @@
+// A simple test that the fluent API logic in the Accumulation Checker works.
+
+import org.checkerframework.common.returnsreceiver.qual.*;
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+/* Simple inference of a fluent builder. */
+public class SimpleFluent {
+  SimpleFluent build(@TestAccumulation({"a", "b"}) SimpleFluent this) {
+    return this;
+  }
+
+  @This SimpleFluent build2(@TestAccumulation({"a", "b"}) SimpleFluent this) {
+    return this;
+  }
+
+  @This SimpleFluent a() {
+    return this;
+  }
+
+  @This SimpleFluent b() {
+    return this;
+  }
+
+  // intentionally does not have an @This annotation
+  SimpleFluent c() {
+    return this;
+  }
+
+  static void doStuffCorrect(@TestAccumulation({"a", "b"}) SimpleFluent s) {
+    s.a().b().build();
+  }
+
+  static void doStuffWrong(@TestAccumulation({"a"}) SimpleFluent s) {
+    s.a()
+        // :: error: method.invocation
+        .build();
+  }
+
+  static void noReturnsReceiverAnno(@TestAccumulation({"a", "b"}) SimpleFluent s) {
+    s.a()
+        .b()
+        .c()
+        // :: error: method.invocation
+        .build();
+  }
+
+  static void mixFluentAndNonFluent(SimpleFluent s1) {
+    s1.a().b();
+    s1.build();
+  }
+
+  static void mixFluentAndNonFluentWrong(SimpleFluent s) {
+    s.a(); // .b()
+    // :: error: method.invocation
+    s.build();
+  }
+
+  static void fluentLoop(SimpleFluent t) {
+    SimpleFluent s = t.a();
+    int i = 10;
+    while (i > 0) {
+      // :: error: method.invocation
+      s.b().build();
+      i--;
+      s = new SimpleFluent();
+    }
+  }
+
+  static void m1(SimpleFluent s) {
+    s.c().a().b().build();
+  }
+
+  static void m2(SimpleFluent s) {
+    s.c().a().b();
+    // :: error: method.invocation
+    s.c().build();
+  }
+
+  static void m3(SimpleFluent s) {
+    s.c().a().b().build();
+    // :: error: method.invocation
+    s.c().a().build();
+  }
+
+  static void m4(SimpleFluent s) {
+    s.c().a().b().build2().build();
+  }
+
+  static void m5(SimpleFluent s) {
+    s.a().c();
+    s.b().build();
+  }
+
+  static void m6(SimpleFluent s) {
+    // :: error: method.invocation
+    s.a().c().b().build();
+  }
+
+  static void m7(SimpleFluent s) {
+    // :: error: method.invocation
+    s.a().b().build().c().b().build();
+  }
+
+  static void m8(SimpleFluent s) {
+    // :: error: method.invocation
+    s.a().build().c().a().b().build();
+    // :: error: method.invocation
+    s.build();
+  }
+
+  static void m9() {
+    new SimpleFluent().a().b().build();
+    // :: error: method.invocation
+    new SimpleFluent().a().build();
+  }
+}
diff --git a/framework/tests/accumulation/SimpleInference.java b/framework/tests/accumulation/SimpleInference.java
new file mode 100644
index 0000000..b82c6a0
--- /dev/null
+++ b/framework/tests/accumulation/SimpleInference.java
@@ -0,0 +1,37 @@
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+public class SimpleInference {
+  void build(@TestAccumulation({"a"}) SimpleInference this) {}
+
+  void doublebuild(@TestAccumulation({"a", "b"}) SimpleInference this) {}
+
+  void a() {}
+
+  void b() {}
+
+  static void doStuffCorrect() {
+    SimpleInference s = new SimpleInference();
+    s.a();
+    s.build();
+  }
+
+  static void doStuffCorrect2() {
+    SimpleInference s = new SimpleInference();
+    s.a();
+    s.b();
+    s.doublebuild();
+  }
+
+  static void doStuffWrong() {
+    SimpleInference s = new SimpleInference();
+    // :: error: method.invocation
+    s.build();
+  }
+
+  static void doStuffWrong2() {
+    SimpleInference s = new SimpleInference();
+    s.a();
+    // :: error: method.invocation
+    s.doublebuild();
+  }
+}
diff --git a/framework/tests/accumulation/SimpleInferenceMerge.java b/framework/tests/accumulation/SimpleInferenceMerge.java
new file mode 100644
index 0000000..00987b0
--- /dev/null
+++ b/framework/tests/accumulation/SimpleInferenceMerge.java
@@ -0,0 +1,37 @@
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+public class SimpleInferenceMerge {
+  void build(@TestAccumulation({"a", "b"}) SimpleInferenceMerge this) {}
+
+  void a() {}
+
+  void b() {}
+
+  void c() {}
+
+  static void doStuffCorrectMerge(boolean b) {
+    SimpleInferenceMerge s = new SimpleInferenceMerge();
+    if (b) {
+      s.a();
+      s.b();
+    } else {
+      s.b();
+      s.a();
+      s.c();
+    }
+    s.build();
+  }
+
+  static void doStuffWrongMerge(boolean b) {
+    SimpleInferenceMerge s = new SimpleInferenceMerge();
+    if (b) {
+      s.a();
+      s.b();
+    } else {
+      s.b();
+      s.c();
+    }
+    // :: error: method.invocation
+    s.build();
+  }
+}
diff --git a/framework/tests/accumulation/SimplePolymorphic.java b/framework/tests/accumulation/SimplePolymorphic.java
new file mode 100644
index 0000000..8413c62
--- /dev/null
+++ b/framework/tests/accumulation/SimplePolymorphic.java
@@ -0,0 +1,19 @@
+// Tests that polymorphic annotations are supported by the Accumulation Checker.
+
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+public class SimplePolymorphic {
+  @PolyTestAccumulation Object id(@PolyTestAccumulation Object obj) {
+    return obj;
+  }
+
+  @TestAccumulation("foo") Object usePoly(@TestAccumulation("foo") Object obj) {
+    return id(obj);
+  }
+
+  // Check that polymorphic supertype with accumulator type doesn't cause a crash.
+  void noCrashOnPolySuper(@TestAccumulation("foo") Object obj) {
+    // :: error: assignment
+    @PolyTestAccumulation Object obj2 = obj;
+  }
+}
diff --git a/framework/tests/accumulation/SmallPredicate.java b/framework/tests/accumulation/SmallPredicate.java
new file mode 100644
index 0000000..39f1577
--- /dev/null
+++ b/framework/tests/accumulation/SmallPredicate.java
@@ -0,0 +1,18 @@
+// small test case for predicates, for debugging
+
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+public class SmallPredicate {
+  void a() {}
+
+  void b() {}
+
+  void d(@TestAccumulationPredicate("a && b") SmallPredicate this) {}
+
+  static void test(SmallPredicate smallPredicate) {
+    smallPredicate.a();
+    smallPredicate.b();
+    @TestAccumulation({"a", "b"}) SmallPredicate p2 = smallPredicate;
+    smallPredicate.d();
+  }
+}
diff --git a/framework/tests/accumulation/Subtyping.java b/framework/tests/accumulation/Subtyping.java
new file mode 100644
index 0000000..8e1fb0f
--- /dev/null
+++ b/framework/tests/accumulation/Subtyping.java
@@ -0,0 +1,73 @@
+// A basic test that subtyping between accumulation annotations works as expected. Note that GJF
+// botches the formatting in this file, so the comments for error messages are in weird places.
+
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+public class Subtyping {
+  void top(@TestAccumulation() Object o1) {
+    @TestAccumulation() Object o2 = o1;
+    @TestAccumulation("foo")
+    // :: error: assignment
+    Object o3 = o1;
+    @TestAccumulation("bar")
+    // :: error: assignment
+    Object o4 = o1;
+    @TestAccumulation({"foo", "bar"})
+    // :: error: assignment
+    Object o5 = o1;
+    // :: error: assignment
+    @TestAccumulationBottom Object o6 = o1;
+  }
+
+  void foo(@TestAccumulation("foo") Object o1) {
+    @TestAccumulation() Object o2 = o1;
+    @TestAccumulation("foo") Object o3 = o1;
+    @TestAccumulation("bar")
+    // :: error: assignment
+    Object o4 = o1;
+    @TestAccumulation({"foo", "bar"})
+    // :: error: assignment
+    Object o5 = o1;
+    // :: error: assignment
+    @TestAccumulationBottom Object o6 = o1;
+  }
+
+  void bar(@TestAccumulation("bar") Object o1) {
+    @TestAccumulation() Object o2 = o1;
+    @TestAccumulation("foo")
+    // :: error: assignment
+    Object o3 = o1;
+    @TestAccumulation("bar") Object o4 = o1;
+    @TestAccumulation({"foo", "bar"})
+    // :: error: assignment
+    Object o5 = o1;
+    // :: error: assignment
+    @TestAccumulationBottom Object o6 = o1;
+  }
+
+  void foobar(@TestAccumulation({"foo", "bar"}) Object o1) {
+    @TestAccumulation() Object o2 = o1;
+    @TestAccumulation("foo") Object o3 = o1;
+    @TestAccumulation("bar") Object o4 = o1;
+    @TestAccumulation({"foo", "bar"}) Object o5 = o1;
+    // :: error: assignment
+    @TestAccumulationBottom Object o6 = o1;
+  }
+
+  void barfoo(@TestAccumulation({"bar", "foo"}) Object o1) {
+    @TestAccumulation() Object o2 = o1;
+    @TestAccumulation("foo") Object o3 = o1;
+    @TestAccumulation("bar") Object o4 = o1;
+    @TestAccumulation({"foo", "bar"}) Object o5 = o1;
+    // :: error: assignment
+    @TestAccumulationBottom Object o6 = o1;
+  }
+
+  void bot(@TestAccumulationBottom Object o1) {
+    @TestAccumulation() Object o2 = o1;
+    @TestAccumulation("foo") Object o3 = o1;
+    @TestAccumulation("bar") Object o4 = o1;
+    @TestAccumulation({"foo", "bar"}) Object o5 = o1;
+    @TestAccumulationBottom Object o6 = o1;
+  }
+}
diff --git a/framework/tests/accumulation/UnparsablePredicate.java b/framework/tests/accumulation/UnparsablePredicate.java
new file mode 100644
index 0000000..618110c
--- /dev/null
+++ b/framework/tests/accumulation/UnparsablePredicate.java
@@ -0,0 +1,50 @@
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+public class UnparsablePredicate {
+
+  // :: error: predicate
+  void unclosedOpen(@TestAccumulationPredicate("(foo && bar") Object x) {}
+
+  // :: error: predicate
+  void unopenedClose(@TestAccumulationPredicate("foo || bar)") Object x) {}
+
+  // :: error: predicate
+  void badKeywords1(@TestAccumulationPredicate("foo OR bar") Object x) {}
+
+  // :: error: predicate
+  void badKeywords2(@TestAccumulationPredicate("foo AND bar") Object x) {}
+
+  // These tests check that valid java identifiers don't cause problems
+  // when evaluating predicates. Examples of identifiers from
+  // https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8
+
+  void jls0Example(@TestAccumulationPredicate("String") Object x) {}
+
+  void callJls0Example(@TestAccumulation("String") Object y) {
+    jls0Example(y);
+  }
+
+  void jls1Example(@TestAccumulationPredicate("i3") Object x) {}
+
+  void callJls1Example(@TestAccumulation("i3") Object y) {
+    jls1Example(y);
+  }
+
+  void jls2Example(@TestAccumulationPredicate("αρετη") Object x) {}
+
+  void callJls2Example(@TestAccumulation("αρετη") Object y) {
+    jls2Example(y);
+  }
+
+  void jls3Example(@TestAccumulationPredicate("MAX_VALUE") Object x) {}
+
+  void callJls3Example(@TestAccumulation("MAX_VALUE") Object y) {
+    jls3Example(y);
+  }
+
+  void jls4Example(@TestAccumulationPredicate("isLetterOrDigit") Object x) {}
+
+  void callJls4Example(@TestAccumulation("isLetterOrDigit") Object y) {
+    jls4Example(y);
+  }
+}
diff --git a/framework/tests/accumulation/UseSimpleFluent.java b/framework/tests/accumulation/UseSimpleFluent.java
new file mode 100644
index 0000000..20d1296
--- /dev/null
+++ b/framework/tests/accumulation/UseSimpleFluent.java
@@ -0,0 +1,9 @@
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+public class UseSimpleFluent {
+  static void req(@TestAccumulation({"a", "b"}) SimpleFluent s) {}
+
+  static void test() {
+    req(new SimpleFluent().a().b());
+  }
+}
diff --git a/framework/tests/accumulation/Xor.java b/framework/tests/accumulation/Xor.java
new file mode 100644
index 0000000..4a47763
--- /dev/null
+++ b/framework/tests/accumulation/Xor.java
@@ -0,0 +1,64 @@
+import org.checkerframework.framework.testchecker.testaccumulation.qual.*;
+
+public class Xor {
+
+  class Foo {
+    void a() {}
+
+    void b() {}
+
+    void c() {}
+    // use a standard gate encoding for xor
+    void aXorB(@TestAccumulationPredicate("(a || b) && !(a && b)") Foo this) {}
+  }
+
+  void test1(Foo f) {
+    // :: error: method.invocation
+    f.aXorB();
+  }
+
+  void test2(Foo f) {
+    f.c();
+    // :: error: method.invocation
+    f.aXorB();
+  }
+
+  void test3(Foo f) {
+    f.a();
+    f.aXorB();
+  }
+
+  void test4(Foo f) {
+    f.b();
+    f.aXorB();
+  }
+
+  void test5(Foo f) {
+    f.a();
+    f.b();
+    // :: error: method.invocation
+    f.aXorB();
+  }
+
+  void callA(Foo f) {
+    f.a();
+  }
+
+  void test6(Foo f) {
+    callA(f);
+    f.b();
+    // DEMONSTRATION OF "UNSOUNDNESS"
+    f.aXorB();
+  }
+
+  void test7(@TestAccumulation("a") Foo f) {
+    f.aXorB();
+  }
+
+  void test8(Foo f) {
+    callA(f);
+    // THIS IS AN UNAVOIDABLE FALSE POSITIVE
+    // :: error: method.invocation
+    f.aXorB();
+  }
+}
diff --git a/framework/tests/aggregate/BasicTest.java b/framework/tests/aggregate/BasicTest.java
new file mode 100644
index 0000000..055d39d
--- /dev/null
+++ b/framework/tests/aggregate/BasicTest.java
@@ -0,0 +1,12 @@
+public class BasicTest {
+  // Random code just to make sure that the aggregate design pattern
+  // does not throw any exceptions
+  Object field = new Object();
+  String[] array = {"hello", "world"};
+
+  void foo(int arg) {
+    field = array[0];
+    field.toString();
+    int a = 1 + arg;
+  }
+}
diff --git a/framework/tests/aggregate/JavaErrorTest.java b/framework/tests/aggregate/JavaErrorTest.java
new file mode 100644
index 0000000..eb8776f
--- /dev/null
+++ b/framework/tests/aggregate/JavaErrorTest.java
@@ -0,0 +1,12 @@
+public class JavaErrorTest {
+  // Checking that if one checker finds a Java error
+  // and therefor does not set a root, then
+  // checkers relying on that checker should also not run.
+  // otherwise, it might access a subchecker whose "root" has not been set.
+  // (See AnnotatedTypeFactory.setRoot(CompilationUnitTree) )
+  void foo() {
+    Object a;
+    // :: error: variable a is already defined in method foo()
+    Object a;
+  }
+}
diff --git a/framework/tests/aggregate/MultiError.java b/framework/tests/aggregate/MultiError.java
new file mode 100644
index 0000000..ec81e9a
--- /dev/null
+++ b/framework/tests/aggregate/MultiError.java
@@ -0,0 +1,19 @@
+import org.checkerframework.common.aliasing.qual.Unique;
+import org.checkerframework.common.reflection.qual.MethodVal;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class MultiError {
+  // Testing that errors from multiple checkers are issued
+  // on the same compilation unit
+  // :: error: (unique.location.forbidden)
+  @Unique String[] array;
+  // :: error: (assignment)
+  @StringVal("hello") String s = "goodbye";
+
+  @MethodVal(
+      className = "c",
+      methodName = "m",
+      params = {0, 0})
+  // :: error: (invalid.methodval)
+  Object o;
+}
diff --git a/framework/tests/aliasing/AliasingConstructorTest.java b/framework/tests/aliasing/AliasingConstructorTest.java
new file mode 100644
index 0000000..28d1d58
--- /dev/null
+++ b/framework/tests/aliasing/AliasingConstructorTest.java
@@ -0,0 +1,23 @@
+import org.checkerframework.common.aliasing.qual.*;
+
+public class AliasingConstructorTest {
+
+  public AliasingConstructorTest(@NonLeaked Object o) {}
+
+  // int and String parameters on the constructors below are used only
+  // to make a distinction among constructors.
+  public AliasingConstructorTest(@LeakedToResult Object o, int i) {}
+
+  public AliasingConstructorTest(Object o, String s) {}
+
+  public void annosInAliasingConstructorTest() {
+    @Unique Object o = new Object();
+    new AliasingConstructorTest(o);
+    Object o2 = new Object();
+    new AliasingConstructorTest(o2, 1);
+    AliasingConstructorTest ct = new AliasingConstructorTest(o2, 1);
+    @Unique Object o3 = new Object();
+    // ::error: (unique.leaked)
+    new AliasingConstructorTest(o3, "someString");
+  }
+}
diff --git a/framework/tests/aliasing/ArrayInitializerTest.java b/framework/tests/aliasing/ArrayInitializerTest.java
new file mode 100644
index 0000000..e63fb40
--- /dev/null
+++ b/framework/tests/aliasing/ArrayInitializerTest.java
@@ -0,0 +1,18 @@
+import org.checkerframework.common.aliasing.qual.Unique;
+
+public class ArrayInitializerTest {
+
+  void foo() {
+    @Unique Object o = new Object();
+    // :: error: (unique.leaked)
+    Object[] ar = new Object[] {o};
+
+    @Unique Object o2 = new Object();
+    // :: error: (unique.leaked)
+    Object @Unique [] ar2 = new Object[] {o2};
+
+    Object[] arr = new Object[] {new Object()};
+
+    Object @Unique [] arrr = new Object[2];
+  }
+}
diff --git a/framework/tests/aliasing/CatchTest.java b/framework/tests/aliasing/CatchTest.java
new file mode 100644
index 0000000..a7980ed
--- /dev/null
+++ b/framework/tests/aliasing/CatchTest.java
@@ -0,0 +1,17 @@
+import org.checkerframework.common.aliasing.qual.Unique;
+
+public class CatchTest {
+
+  void foo() {
+    @Unique Exception exVar = new Exception();
+    try {
+      // :: error: (unique.leaked)
+      throw exVar;
+
+      // :: error: (exception.parameter)
+    } catch (@Unique Exception e) {
+      // exVar and e points to the same object, therefore catch clauses
+      // are not allowed to have a @Unique parameter.
+    }
+  }
+}
diff --git a/framework/tests/aliasing/EnumTest.java b/framework/tests/aliasing/EnumTest.java
new file mode 100644
index 0000000..d4d0e1c
--- /dev/null
+++ b/framework/tests/aliasing/EnumTest.java
@@ -0,0 +1,4 @@
+public enum EnumTest {
+  val1,
+  val2
+}
diff --git a/framework/tests/aliasing/ExplicitAnnotationTest.java b/framework/tests/aliasing/ExplicitAnnotationTest.java
new file mode 100644
index 0000000..b459896
--- /dev/null
+++ b/framework/tests/aliasing/ExplicitAnnotationTest.java
@@ -0,0 +1,15 @@
+import org.checkerframework.common.aliasing.qual.Unique;
+
+@Unique class UniqueData {
+  @SuppressWarnings("unique.leaked")
+  UniqueData() {} // All objects of UniqueData are now @Unique
+}
+
+public class ExplicitAnnotationTest {
+  void check(UniqueData p) { // p is @Unique UniqueData Object
+    // :: error: (unique.leaked)
+    UniqueData y = p; // @Unique p is leaked
+    // :: error: (unique.leaked)
+    Object z = p; // @Unique p is leaked
+  }
+}
diff --git a/framework/tests/aliasing/ForbiddenUniqueTest.java b/framework/tests/aliasing/ForbiddenUniqueTest.java
new file mode 100644
index 0000000..5248d3c
--- /dev/null
+++ b/framework/tests/aliasing/ForbiddenUniqueTest.java
@@ -0,0 +1,20 @@
+import java.util.List;
+import org.checkerframework.common.aliasing.qual.Unique;
+
+public class ForbiddenUniqueTest {
+
+  // :: error: (unique.location.forbidden)
+  @Unique int field;
+
+  void notAllowed() {
+    // :: error: (unique.location.forbidden)
+    @Unique Object[] arr;
+    // :: error: (type.argument) :: error: (unique.location.forbidden)
+    List<@Unique Object> list;
+  }
+
+  void allowed() {
+    Object @Unique [] ar;
+    @Unique List<Object> l;
+  }
+}
diff --git a/framework/tests/aliasing/ReceiverParameterTest.java b/framework/tests/aliasing/ReceiverParameterTest.java
new file mode 100644
index 0000000..3a72e33
--- /dev/null
+++ b/framework/tests/aliasing/ReceiverParameterTest.java
@@ -0,0 +1,58 @@
+import org.checkerframework.common.aliasing.qual.*;
+
+public class ReceiverParameterTest {
+
+  public @Unique ReceiverParameterTest() {
+    nonLeaked();
+    // :: error: (unique.leaked)
+    mayLeak();
+  }
+
+  public @Unique ReceiverParameterTest(int i) {
+    leakedToResult();
+    // :: error: (unique.leaked)
+    ReceiverParameterTest b = leakedToResult();
+  }
+
+  public @Unique ReceiverParameterTest(String s) {}
+
+  void receiverTest() {
+    ReceiverParameterTest rec = new ReceiverParameterTest("s"); // @Unique
+    isUnique(rec);
+    rec.leakedToResult();
+    isUnique(rec);
+    ReceiverParameterTest other = rec.leakedToResult();
+    // :: error: (argument)
+    isUnique(rec);
+    // :: error: (argument)
+    isUnique(other);
+  }
+
+  void stubFileReceiverTest() {
+    // StringBuffer append(String s) @LeakedToResult;
+    StringBuffer sb = new StringBuffer();
+    isUnique(sb);
+    sb.append("something");
+    isUnique(sb);
+    StringBuffer sb2 = sb.append("something");
+    // :: error: (argument)
+    isUnique(sb);
+    // :: error: (argument)
+    isUnique(sb2);
+  }
+
+  ReceiverParameterTest leakedToResult(@LeakedToResult ReceiverParameterTest this) {
+    return this;
+  }
+
+  void nonLeaked(@NonLeaked ReceiverParameterTest this) {}
+
+  void mayLeak() {}
+
+  // @NonLeaked so it doesn't refine the type of the argument.
+  void isUnique(@NonLeaked @Unique ReceiverParameterTest s) {}
+  // @NonLeaked so it doesn't refine the type of the argument.
+  void isUnique(@NonLeaked @Unique String s) {}
+  // @NonLeaked so it doesn't refine the type of the argument.
+  void isUnique(@NonLeaked @Unique StringBuffer s) {}
+}
diff --git a/framework/tests/aliasing/SuperTest.java b/framework/tests/aliasing/SuperTest.java
new file mode 100644
index 0000000..16086f4
--- /dev/null
+++ b/framework/tests/aliasing/SuperTest.java
@@ -0,0 +1,22 @@
+import org.checkerframework.common.aliasing.qual.Unique;
+
+class A {
+  static A lastA;
+
+  public A() {
+    lastA = this;
+  }
+
+  public @Unique A(String s) {}
+}
+
+class B extends A {
+  public @Unique B() {
+    // :: error: (unique.leaked)
+    super(); // "this" is aliased to A.lastA.
+  }
+
+  public @Unique B(String s) {
+    super(s); // no aliases created
+  }
+}
diff --git a/framework/tests/aliasing/ThrowTest.java b/framework/tests/aliasing/ThrowTest.java
new file mode 100644
index 0000000..8dad69e
--- /dev/null
+++ b/framework/tests/aliasing/ThrowTest.java
@@ -0,0 +1,14 @@
+import org.checkerframework.common.aliasing.qual.Unique;
+
+public class ThrowTest {
+
+  void foo() throws Exception {
+    @Unique Exception e = new Exception();
+    // :: error: (unique.leaked)
+    throw e;
+  }
+
+  void bar() throws Exception {
+    throw new Exception();
+  }
+}
diff --git a/framework/tests/aliasing/TypeRefinement.java b/framework/tests/aliasing/TypeRefinement.java
new file mode 100644
index 0000000..431ba1a
--- /dev/null
+++ b/framework/tests/aliasing/TypeRefinement.java
@@ -0,0 +1,71 @@
+import org.checkerframework.common.aliasing.qual.*;
+
+public class TypeRefinement {
+
+  /**
+   * Type refinement is treated in the usual way, except that at (pseudo-)assignments the RHS may
+   * lose its type refinement, before the LHS is type-refined.
+   *
+   * <p>The RHS always loses its type refinement (it is widened to @MaybeAliased, and its declared
+   * type must have been @MaybeAliased) except in the following cases:
+   *
+   * <ol>
+   *   <li>The RHS is a fresh expression.
+   *   <li>The LHS is a @NonLeaked formal parameter and the RHS is an argument in a method call or
+   *       constructor invocation.
+   *   <li>The LHS is a @LeakedToResult formal parameter, the RHS is an argument in a method call or
+   *       constructor invocation, and the method's return value is discarded.
+   *       <ol>
+   */
+
+  // Test cases for the Aliasing type refinement cases below.
+  // One method for each exception case. The usual case is tested in every method too.
+  // As annotated in stubfile.astub, String() has type @Unique @NonLeaked.
+
+  void rule1() {
+    String unique = new String();
+    // unique is refined to @Unique here, according to the definition.
+    isUnique(unique);
+
+    String notUnique = unique; // unique loses its refinement.
+
+    // :: error: (argument)
+    isUnique(unique);
+    // :: error: (argument)
+    isUnique(notUnique);
+  }
+
+  void rule2() {
+    String unique = new String();
+
+    isUnique(unique);
+    nonLeaked(unique);
+    isUnique(unique);
+
+    leaked(unique);
+    // :: error: (argument)
+    isUnique(unique);
+  }
+
+  void rule3() {
+    String unique = new String();
+    isUnique(unique);
+    leakedToResult(unique);
+    isUnique(unique);
+
+    String notUnique = leakedToResult(unique);
+    // :: error: (argument)
+    isUnique(unique);
+  }
+
+  void nonLeaked(@NonLeaked String s) {}
+
+  void leaked(String s) {}
+
+  String leakedToResult(@LeakedToResult String s) {
+    return s;
+  }
+
+  // @NonLeaked so it doesn't refine the type of the argument.
+  void isUnique(@NonLeaked @Unique String s) {}
+}
diff --git a/framework/tests/aliasing/UniqueAnnoTest.java b/framework/tests/aliasing/UniqueAnnoTest.java
new file mode 100644
index 0000000..816ae0e
--- /dev/null
+++ b/framework/tests/aliasing/UniqueAnnoTest.java
@@ -0,0 +1,40 @@
+import org.checkerframework.common.aliasing.qual.*;
+
+public class UniqueAnnoTest {
+
+  // @Unique constructor
+  public @Unique UniqueAnnoTest() {}
+
+  // @Unique constructor leaking the "this" reference.
+  // Each unique.leaked error is a leak.
+  public @Unique UniqueAnnoTest(int i) {
+    notLeaked(this);
+    leakedToResult(this);
+    // :: error: (unique.leaked)
+    UniqueAnnoTest b = leakedToResult(this);
+
+    UniqueAnnoTest other = new UniqueAnnoTest();
+    // :: error: (unique.leaked)
+    other = this;
+    // :: error: (unique.leaked)
+    leaked(this);
+    // :: error: (unique.leaked)
+    leaked(other); // The receiver parameter is "this", so there is a leak.
+  }
+
+  // Not @Unique constructor. No warnings.
+  public UniqueAnnoTest(int i1, int i2) {
+    UniqueAnnoTest other = new UniqueAnnoTest();
+    other = this;
+    notLeaked(this);
+  }
+
+  void leaked(UniqueAnnoTest a) {}
+
+  void notLeaked(@NonLeaked UniqueAnnoTest this, @NonLeaked UniqueAnnoTest a) {}
+
+  UniqueAnnoTest leakedToResult(
+      @LeakedToResult UniqueAnnoTest this, @LeakedToResult UniqueAnnoTest a) {
+    return a;
+  }
+}
diff --git a/framework/tests/aliasing/UniqueConstructorTest.java b/framework/tests/aliasing/UniqueConstructorTest.java
new file mode 100644
index 0000000..5cc3a63
--- /dev/null
+++ b/framework/tests/aliasing/UniqueConstructorTest.java
@@ -0,0 +1,23 @@
+import org.checkerframework.common.aliasing.qual.Unique;
+
+public class UniqueConstructorTest {
+
+  @Unique UniqueConstructorTest() {
+    // Does not raise unique.leaked error since the parent constructor (Object) is unique
+  }
+
+  class ParentClass extends UniqueConstructorTest {
+
+    ParentClass() {
+      // Does not raise unique.leaked error since the parent constructor is unique
+    }
+  }
+
+  class ChildUniqueClass extends ParentClass {
+
+    // ::error: (unique.leaked)
+    @Unique ChildUniqueClass() {
+      // Raises unique.leaked error since the parent constructor is not unique
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Annotations.java b/framework/tests/all-systems/Annotations.java
new file mode 100644
index 0000000..da2404b
--- /dev/null
+++ b/framework/tests/all-systems/Annotations.java
@@ -0,0 +1,17 @@
+import java.lang.annotation.Annotation;
+
+@interface Anno {}
+
+public class Annotations {
+  void takeAnnotation(Annotation a) {}
+
+  // test that a Tree works (source for Anno is in same compilation unit)
+  void takeTree(Anno a1) {
+    takeAnnotation(a1);
+  }
+
+  // test that an Element works (annotation only available from class file)
+  void takeElem(SuppressWarnings a2) {
+    takeAnnotation(a2);
+  }
+}
diff --git a/framework/tests/all-systems/AnonymousClasses.java b/framework/tests/all-systems/AnonymousClasses.java
new file mode 100644
index 0000000..a37f2da
--- /dev/null
+++ b/framework/tests/all-systems/AnonymousClasses.java
@@ -0,0 +1,31 @@
+import java.util.Comparator;
+import java.util.Date;
+
+// Checkers may issue type checking errors for this class, but they should not crash
+@SuppressWarnings("all")
+public class AnonymousClasses {
+  // test anonymous classes
+  private void testAnonymous() {
+    Foo x = new Foo() {};
+    new Object() {
+      public boolean equals(Object o) {
+        return true;
+      }
+    }.equals(null);
+
+    Date d = new Date() {};
+  }
+
+  private <T extends Comparator<T>> void testGenericAnonymous() {
+    Gen<T> g = new Gen<T>() {};
+    GenInter<T> gi = new GenInter<T>() {};
+  }
+
+  class Gen<F extends Object> {
+    public Gen() {}
+  }
+
+  interface GenInter<E> {}
+
+  interface Foo {}
+}
diff --git a/framework/tests/all-systems/AnonymousFieldAccess.java b/framework/tests/all-systems/AnonymousFieldAccess.java
new file mode 100644
index 0000000..852c094
--- /dev/null
+++ b/framework/tests/all-systems/AnonymousFieldAccess.java
@@ -0,0 +1,12 @@
+@SuppressWarnings("all") // Check for crashes.
+public class AnonymousFieldAccess {
+  static class SomeClass {
+    Object fieldInSomeClass;
+  }
+
+  void createTreeAnnotator() {
+    new SomeClass() {
+      Object f = fieldInSomeClass;
+    };
+  }
+}
diff --git a/framework/tests/all-systems/AnoymousAndInnerClass.java b/framework/tests/all-systems/AnoymousAndInnerClass.java
new file mode 100644
index 0000000..c4b5c48
--- /dev/null
+++ b/framework/tests/all-systems/AnoymousAndInnerClass.java
@@ -0,0 +1,37 @@
+public class AnoymousAndInnerClass {
+  class MyInnerClass {
+    public MyInnerClass() {}
+
+    public MyInnerClass(String s) {}
+
+    public MyInnerClass(int... i) {}
+  }
+
+  static class MyClass {
+    public MyClass() {}
+
+    public MyClass(String s) {}
+
+    public MyClass(int... i) {}
+  }
+
+  void test(AnoymousAndInnerClass outer, String tainted) {
+    new MyClass() {};
+    new MyClass(tainted) {};
+    new MyClass(1, 2, 3) {};
+    new MyClass(1) {};
+    new MyInnerClass() {};
+    new MyInnerClass(tainted) {};
+    new MyInnerClass(1) {};
+    new MyInnerClass(1, 2, 3) {};
+    this.new MyInnerClass() {};
+    this.new MyInnerClass(tainted) {};
+    this.new MyInnerClass(1) {};
+    this.new MyInnerClass(1, 2, 3) {};
+    outer.new MyInnerClass() {};
+    outer.new MyInnerClass(tainted) {};
+    outer.new MyInnerClass(tainted) {};
+    outer.new MyInnerClass(1) {};
+    outer.new MyInnerClass(1, 2, 3) {};
+  }
+}
diff --git a/framework/tests/all-systems/Arrays.java b/framework/tests/all-systems/Arrays.java
new file mode 100644
index 0000000..89ba538
--- /dev/null
+++ b/framework/tests/all-systems/Arrays.java
@@ -0,0 +1,27 @@
+public class Arrays {
+  public static final String[] RELATIONSHIP_LABELS = {
+    "SJJ", "SJU", "SUJ", "SUU", "DJJ", "DJU", "DUJ", "DUU", "JM", "UM", "MJ", "MU"
+  };
+
+  public static final int[] ia = {1, -2, 3};
+
+  // Note that "-1.0" is _NOT_ a double literal! It's a unary minus,
+  // that needs to be handled correctly.
+  public static final double[] elts_plus_minus_one_float = {-1.0, 1.0, +1.0, 1.0 / 2.0};
+
+  String[] vis = new String[] {"a", "b"};
+
+  @SuppressWarnings("nullness") // Don't want to depend on @Nullable
+  void m() {
+    class VarInfo {}
+    VarInfo v1 = null;
+    VarInfo v2 = null;
+    VarInfo[] vis = null;
+
+    if (v2 == null) {
+      vis = new VarInfo[] {v1};
+    } else {
+      vis = new VarInfo[] {v1, v2};
+    }
+  }
+}
diff --git a/framework/tests/all-systems/AsSuperCrashes.java b/framework/tests/all-systems/AsSuperCrashes.java
new file mode 100644
index 0000000..eab7218
--- /dev/null
+++ b/framework/tests/all-systems/AsSuperCrashes.java
@@ -0,0 +1,91 @@
+package assuper;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.util.Date;
+import java.util.List;
+import org.checkerframework.dataflow.qual.Pure;
+
+// This class has code that used to cause AsSuper to crash
+@SuppressWarnings("all")
+public class AsSuperCrashes {
+  // TODO: Value Checker crashes on this
+  /*    void primitiveNarrowing() {
+          Byte b = 100;
+          Character c = 100;
+          Short s = 100;
+
+          byte bb = 100;
+          char cc = 100;
+          short ss = 100;
+      }
+  */
+
+  // test anonymous classes
+  private void testAnonymous() {
+    new Object() {
+      public boolean equals(Object o) {
+        return true;
+      }
+    }.equals(null);
+
+    Date d = new Date() {};
+  }
+
+  private void apply(Field field) {
+    Class<?> type = field.getType();
+    type.getSuperclass().getName().equals("java.lang.Enum");
+  }
+
+  void arrayAsMethodReceiver(Object[] array) {
+    array.clone();
+  }
+
+  <T> T lowerBoundedWildcard(java.util.List<? super Iterable<?>> l) {
+    lowerBoundedWildcard(new java.util.ArrayList<Object>());
+    throw new Error();
+  }
+
+  // Test super() and this()
+  class Inner {
+    public Inner() {
+      super();
+    }
+
+    public Inner(int i) {
+      this();
+    }
+  }
+
+  public static <T extends Interface<? super T>> void foo2(T a, T b) {
+    a.compareTo(b);
+  }
+
+  public static <T extends Object & Interface<? super T>> void foo(T a, T b) {
+    a.compareTo(b);
+  }
+
+  interface Interface<F> {
+    void compareTo(F t);
+  }
+
+  public void m1(Class<?> c) {
+    Class<? extends I2> x = c.asSubclass(I2.class);
+    new WeakReference<Class<? extends I2>>(c.asSubclass(I2.class));
+  }
+
+  interface I2 {}
+
+  @Pure
+  void bar() {
+    bar();
+  }
+
+  public static <Z> void copy(List<? super Z> dest, List<? extends Z> src) {
+    dest.set(0, src.get(0));
+  }
+
+  public static <F, E extends F> void copy2(List<? super F> dest, List<E> src) {
+    dest.set(0, src.get(0));
+  }
+}
diff --git a/framework/tests/all-systems/AssertWithSideEffect.java b/framework/tests/all-systems/AssertWithSideEffect.java
new file mode 100644
index 0000000..dfd0176
--- /dev/null
+++ b/framework/tests/all-systems/AssertWithSideEffect.java
@@ -0,0 +1,9 @@
+/* This idiom was found in Daikon and forces us to handle assignments
+ * in conditional mode, such as in the condition of an assert statement. */
+
+public class AssertWithSideEffect {
+  void CheckAssert() {
+    boolean assert_enabled = false;
+    assert (assert_enabled = true);
+  }
+}
diff --git a/framework/tests/all-systems/AssignmentContext.java b/framework/tests/all-systems/AssignmentContext.java
new file mode 100644
index 0000000..5010640
--- /dev/null
+++ b/framework/tests/all-systems/AssignmentContext.java
@@ -0,0 +1,29 @@
+import org.checkerframework.common.value.qual.MinLen;
+
+@SuppressWarnings("nullness") // Don't want to depend on @Nullable
+public class AssignmentContext {
+
+  void foo(String[] a) {}
+
+  void t1(boolean b) {
+    String[] s = b ? new String[] {""} : null;
+  }
+
+  void t2(boolean b) {
+    foo(b ? new String[] {""} : null);
+  }
+
+  String[] t3(boolean b) {
+    return b ? new String[] {""} : null;
+  }
+
+  void t4(boolean b) {
+    String[] s = null;
+    s = b ? new String[] {""} : null;
+  }
+
+  void assignToCast(String @MinLen(4) [] @MinLen(5) [] currentSample) {
+    // This statement used to cause a null pointer exception.
+    ((String @MinLen(5) []) currentSample[3])[4] = currentSample[3][4];
+  }
+}
diff --git a/framework/tests/all-systems/BigBinaryTrees.java b/framework/tests/all-systems/BigBinaryTrees.java
new file mode 100644
index 0000000..6c63c53
--- /dev/null
+++ b/framework/tests/all-systems/BigBinaryTrees.java
@@ -0,0 +1,270 @@
+// Test to ensure that checkers can type-check big binary trees in an
+// acceptable amount of time.  See comment on TreeAnnotator#visitBinary.
+
+// Checkers may correctly issue errors, so suppress them.
+@SuppressWarnings("all")
+public class BigBinaryTrees {
+  String string1;
+  String string2;
+  String string3;
+
+  public void testStrings() {
+    String s =
+        getClass().getName()
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3
+            + ",string1="
+            + string1
+            + ",string2="
+            + string2
+            + ",string3="
+            + string3;
+  }
+
+  void test() {
+    int i0 = 163;
+    int i1 = 153;
+    int i2 = 75;
+    int i3 = -72;
+    int i4 = 61;
+    int i5 = 7;
+    int i6 = 83;
+    int i7 = -36;
+    int i8 = -90;
+    int i9 = -93;
+    int i10 = 187;
+    int i11 = -76;
+    int i12 = -16;
+    int i13 = -99;
+    int i14 = 113;
+    int i15 = 72;
+    int i16 = 58;
+    int i17 = -97;
+    int i18 = 115;
+    int i19 = -85;
+    int i20 = 156;
+    int i21 = -10;
+    int i22 = -85;
+    int i23 = 81;
+    int i24 = 63;
+    int i25 = -49;
+    int i26 = 158;
+    int i27 = 158;
+    int i28 = 25;
+    int i29 = 136;
+    int i30 = -90;
+    int i31 = 115;
+    int i32 = 179;
+    int i33 = 11;
+    int i34 = -100;
+    int i35 = 70;
+    int i36 = -46;
+    int i37 = -56;
+    int i38 = 108;
+    int i39 = -41;
+    int i40 = 124;
+    int i41 = -88;
+    int i42 = 54;
+    int i43 = 117;
+    int i44 = -92;
+    int i45 = 7;
+    int i46 = -94;
+    int i47 = 162;
+    int i48 = -34;
+    int i49 = 104;
+    int i50 = 111;
+    int i51 = -16;
+    int i52 = 197;
+    int i53 = -8;
+    int i54 = 101;
+    int i55 = 96;
+    int i56 = 132;
+    int i57 = -36;
+    int i58 = 148;
+    int i59 = 43;
+    int i60 = -59;
+    int i61 = 150;
+    int i62 = 48;
+    int i63 = 130;
+    int i64 = 74;
+    int i65 = -1;
+    int i66 = 79;
+    int i67 = 109;
+    int i68 = -70;
+    int i69 = 111;
+    int i70 = 78;
+    int i71 = 155;
+    int i72 = 176;
+    int i73 = 80;
+    int i74 = 181;
+    int i75 = 41;
+    int i76 = -85;
+    int i77 = 189;
+    int i78 = 97;
+    int i79 = 139;
+    int i80 = 9;
+    int i81 = 42;
+    int i82 = -50;
+    int i83 = 82;
+    int i84 = -70;
+    int i85 = 162;
+    int i86 = -20;
+    int i87 = 52;
+    int i88 = -94;
+    int i89 = 133;
+    int i90 = 136;
+    int i91 = 129;
+    int i92 = -55;
+    int i93 = 153;
+    int i94 = 6;
+    int i95 = -18;
+    int i96 = 132;
+    int i97 = 45;
+    int i98 = 120;
+    int i99 = 60;
+    int result =
+        i0 + i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9 + i10 + i11 + i12 + i13 + i14 + i15 + i16
+            + i17 + i18 + i19 + i20 + i21 + i22 + i23 + i24 + i25 + i26 + i27 + i28 + i29 + i30
+            + i31 + i32 + i33 + i34 + i35 + i36 + i37 + i38 + i39 + i40 + i41 + i42 + i43 + i44
+            + i45 + i46 + i47 + i48 + i49 + i50 + i51 + i52 + i53 + i54 + i55 + i56 + i57 + i58
+            + i59 + i60 + i61 + i62 + i63 + i64 + i65 + i66 + i67 + i68 + i69 + i70 + i71 + i72
+            + i73 + i74 + i75 + i76 + i77 + i78 + i79 + i80 + i81 + i82 + i83 + i84 + i85 + i86
+            + i87 + i88 + i89 + i90 + i91 + i92 + i93 + i94 + i95 + i96 + i97 + i98 + i99;
+  }
+}
diff --git a/framework/tests/all-systems/BigString.java b/framework/tests/all-systems/BigString.java
new file mode 100644
index 0000000..35cdd4f
--- /dev/null
+++ b/framework/tests/all-systems/BigString.java
@@ -0,0 +1,254 @@
+public class BigString {
+  public static final String big =
+      "\u00e7\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa"
+          + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6"
+          + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a"
+          + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e"
+          + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532"
+          + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546"
+          + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a"
+          + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e"
+          + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582"
+          + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596"
+          + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa"
+          + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be"
+          + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2"
+          + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6"
+          + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa"
+          + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e"
+          + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622"
+          + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636"
+          + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a"
+          + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e"
+          + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672"
+          + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686"
+          + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a"
+          + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae"
+          + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2"
+          + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6"
+          + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea"
+          + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe"
+          + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712"
+          + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726"
+          + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a"
+          + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e"
+          + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762"
+          + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776"
+          + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a"
+          + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e"
+          + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2"
+          + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6"
+          + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da"
+          + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee"
+          + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802"
+          + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816"
+          + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a"
+          + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e"
+          + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852"
+          + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866"
+          + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a"
+          + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e"
+          + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2"
+          + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6";
+  public static final String bigger =
+      "\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa"
+          + "\u00fb\u00fc\u00fd\u00fe\u00ff\u0100\u0101\u0102\u0103\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e"
+          + "\u010f\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u0118\u0119\u011a\u011b\u011c\u011d\u011e\u011f\u0120\u0121\u0122"
+          + "\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012a\u012b\u012c\u012d\u012e\u012f\u0130\u0131\u0132\u0133\u0134\u0135\u0136"
+          + "\u0137\u0138\u0139\u013a\u013b\u013c\u013d\u013e\u013f\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u0149\u014a"
+          + "\u014b\u014c\u014d\u014e\u014f\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d\u015e"
+          + "\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016c\u016d\u016e\u016f\u0170\u0171\u0172"
+          + "\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017b\u017c\u017d\u017e\u017f\u0180\u0181\u0182\u0183\u0184\u0185\u0186"
+          + "\u0187\u0188\u0189\u018a\u018b\u018c\u018d\u018e\u018f\u0190\u0191\u0192\u0193\u0194\u0195\u0196\u0197\u0198\u0199\u019a"
+          + "\u019b\u019c\u019d\u019e\u019f\u01a0\u01a1\u01a2\u01a3\u01a4\u01a5\u01a6\u01a7\u01a8\u01a9\u01aa\u01ab\u01ac\u01ad\u01ae"
+          + "\u01af\u01b0\u01b1\u01b2\u01b3\u01b4\u01b5\u01b6\u01b7\u01b8\u01b9\u01ba\u01bb\u01bc\u01bd\u01be\u01bf\u01c0\u01c1\u01c2"
+          + "\u01c3\u01c4\u01c5\u01c6\u01c7\u01c8\u01c9\u01ca\u01cb\u01cc\u01cd\u01ce\u01cf\u01d0\u01d1\u01d2\u01d3\u01d4\u01d5\u01d6"
+          + "\u01d7\u01d8\u01d9\u01da\u01db\u01dc\u01dd\u01de\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01e6\u01e7\u01e8\u01e9\u01ea"
+          + "\u01eb\u01ec\u01ed\u01ee\u01ef\u01f0\u01f1\u01f2\u01f3\u01f4\u01f5\u01f6\u01f7\u01f8\u01f9\u01fa\u01fb\u01fc\u01fd\u01fe"
+          + "\u01ff\u0200\u0201\u0202\u0203\u0204\u0205\u0206\u0207\u0208\u0209\u020a\u020b\u020c\u020d\u020e\u020f\u0210\u0211\u0212"
+          + "\u0213\u0214\u0215\u0216\u0217\u0218\u0219\u021a\u021b\u021c\u021d\u021e\u021f\u0220\u0221\u0222\u0223\u0224\u0225\u0226"
+          + "\u0227\u0228\u0229\u022a\u022b\u022c\u022d\u022e\u022f\u0230\u0231\u0232\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023a"
+          + "\u023b\u023c\u023d\u023e\u023f\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e"
+          + "\u024f\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262"
+          + "\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276"
+          + "\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a"
+          + "\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e"
+          + "\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u02b0\u02b1\u02b2"
+          + "\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c2\u02c3\u02c4\u02c5\u02c6"
+          + "\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da"
+          + "\u02db\u02dc\u02dd\u02de\u02df\u02e0\u02e1\u02e2\u02e3\u02e4\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ee"
+          + "\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0300\u0301\u0302"
+          + "\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316"
+          + "\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a"
+          + "\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e"
+          + "\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352"
+          + "\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366"
+          + "\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0370\u0371\u0372\u0373\u0374\u0375\u0376\u0377\u0378\u0379\u037a"
+          + "\u037b\u037c\u037d\u037e\u037f\u0380\u0381\u0382\u0383\u0384\u0385\u0386\u0387\u0388\u0389\u038a\u038b\u038c\u038d\u038e"
+          + "\u038f\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a2"
+          + "\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6"
+          + "\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca"
+          + "\u03cb\u03cc\u03cd\u03ce\u03cf\u03d0\u03d1\u03d2\u03d3\u03d4\u03d5\u03d6\u03d7\u03d8\u03d9\u03da\u03db\u03dc\u03dd\u03de"
+          + "\u03df\u03e0\u03e1\u03e2\u03e3\u03e4\u03e5\u03e6\u03e7\u03e8\u03e9\u03ea\u03eb\u03ec\u03ed\u03ee\u03ef\u03f0\u03f1\u03f2"
+          + "\u03f3\u03f4\u03f5\u03f6\u03f7\u03f8\u03f9\u03fa\u03fb\u03fc\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406"
+          + "\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a"
+          + "\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e"
+          + "\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442"
+          + "\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456"
+          + "\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0460\u0461\u0462\u0463\u0464\u0465\u0466\u0467\u0468\u0469\u046a"
+          + "\u046b\u046c\u046d\u046e\u046f\u0470\u0471\u0472\u0473\u0474\u0475\u0476\u0477\u0478\u0479\u047a\u047b\u047c\u047d\u047e"
+          + "\u047f\u0480\u0481\u0482\u0483\u0484\u0485\u0486\u0487\u0488\u0489\u048a\u048b\u048c\u048d\u048e\u048f\u0490\u0491\u0492"
+          + "\u0493\u0494\u0495\u0496\u0497\u0498\u0499\u049a\u049b\u049c\u049d\u049e\u049f\u04a0\u04a1\u04a2\u04a3\u04a4\u04a5\u04a6"
+          + "\u04a7\u04a8\u04a9\u04aa\u04ab\u04ac\u04ad\u04ae\u04af\u04b0\u04b1\u04b2\u04b3\u04b4\u04b5\u04b6\u04b7\u04b8\u04b9\u04ba"
+          + "\u04bb\u04bc\u04bd\u04be\u04bf\u04c0\u04c1\u04c2\u04c3\u04c4\u04c5\u04c6\u04c7\u04c8\u04c9\u04ca\u04cb\u04cc\u04cd\u04ce"
+          + "\u04cf\u04d0\u04d1\u04d2\u04d3\u04d4\u04d5\u04d6\u04d7\u04d8\u04d9\u04da\u04db\u04dc\u04dd\u04de\u04df\u04e0\u04e1\u04e2"
+          + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6"
+          + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a"
+          + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e"
+          + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532"
+          + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546"
+          + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a"
+          + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e"
+          + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582"
+          + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596"
+          + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa"
+          + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be"
+          + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2"
+          + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6"
+          + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa"
+          + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e"
+          + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622"
+          + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636"
+          + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a"
+          + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e"
+          + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672"
+          + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686"
+          + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a"
+          + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae"
+          + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2"
+          + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6"
+          + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea"
+          + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe"
+          + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712"
+          + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726"
+          + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a"
+          + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e"
+          + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762"
+          + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776"
+          + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a"
+          + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e"
+          + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2"
+          + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6"
+          + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da"
+          + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee"
+          + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802"
+          + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816"
+          + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a"
+          + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e"
+          + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852"
+          + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866"
+          + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a"
+          + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e"
+          + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2"
+          + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6"
+          + "\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca"
+          + "\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de"
+          + "\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2"
+          + "\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u0901\u0902\u0903\u0904\u0905\u0906"
+          + "\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a"
+          + "\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e"
+          + "\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093a\u093b\u093c\u093d\u093e\u093f\u0940\u0941\u0942"
+          + "\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u094e\u094f\u0950\u0951\u0952\u0953\u0954\u0955\u0956"
+          + "\u0957\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0964\u0965\u0966\u0967\u0968\u0969\u096a"
+          + "\u096b\u096c\u096d\u096e\u096f\u0970\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e"
+          + "\u097f\u0980\u0981\u0982\u0983\u0984\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098d\u098e\u098f\u0990\u0991\u0992"
+          + "\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6"
+          + "\u09a7\u09a8\u09a9\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b1\u09b2\u09b3\u09b4\u09b5\u09b6\u09b7\u09b8\u09b9\u09ba"
+          + "\u09bb\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4\u09c5\u09c6\u09c7\u09c8\u09c9\u09ca\u09cb\u09cc\u09cd\u09ce"
+          + "\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d7\u09d8\u09d9\u09da\u09db\u09dc\u09dd\u09de\u09df\u09e0\u09e1\u09e2"
+          + "\u09e3\u09e4\u09e5\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09f0\u09f1\u09f2\u09f3\u09f4\u09f5\u09f6"
+          + "\u09f7\u09f8\u09f9\u09fa\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a01\u0a02\u0a03\u0a04\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a"
+          + "\u0a0b\u0a0c\u0a0d\u0a0e\u0a0f\u0a10\u0a11\u0a12\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e"
+          + "\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a29\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a31\u0a32"
+          + "\u0a33\u0a34\u0a35\u0a36\u0a37\u0a38\u0a39\u0a3a\u0a3b\u0a3c\u0a3d\u0a3e\u0a3f\u0a40\u0a41\u0a42\u0a43\u0a44\u0a45\u0a46"
+          + "\u0a47\u0a48\u0a49\u0a4a\u0a4b\u0a4c\u0a4d\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a59\u0a5a"
+          + "\u0a5b\u0a5c\u0a5d\u0a5e\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e"
+          + "\u0a6f\u0a70\u0a71\u0a72\u0a73\u0a74\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a81\u0a82"
+          + "\u0a83\u0a84\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8e\u0a8f\u0a90\u0a91\u0a92\u0a93\u0a94\u0a95\u0a96"
+          + "\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aa9\u0aaa"
+          + "\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab1\u0ab2\u0ab3\u0ab4\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0aba\u0abb\u0abc\u0abd\u0abe"
+          + "\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac6\u0ac7\u0ac8\u0ac9\u0aca\u0acb\u0acc\u0acd\u0ace\u0acf\u0ad0\u0ad1\u0ad2"
+          + "\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae0\u0ae1\u0ae2\u0ae3\u0ae4\u0ae5\u0ae6"
+          + "\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0af0\u0af1\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa"
+          + "\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b01\u0b02\u0b03\u0b04\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0d\u0b0e"
+          + "\u0b0f\u0b10\u0b11\u0b12\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22"
+          + "\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b29\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b31\u0b32\u0b33\u0b34\u0b35\u0b36"
+          + "\u0b37\u0b38\u0b39\u0b3a\u0b3b\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44\u0b45\u0b46\u0b47\u0b48\u0b49\u0b4a"
+          + "\u0b4b\u0b4c\u0b4d\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b56\u0b57\u0b58\u0b59\u0b5a\u0b5b\u0b5c\u0b5d\u0b5e"
+          + "\u0b5f\u0b60\u0b61\u0b62\u0b63\u0b64\u0b65\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b70\u0b71\u0b72"
+          + "\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b82\u0b83\u0b84\u0b85\u0b86"
+          + "\u0b87\u0b88\u0b89\u0b8a\u0b8b\u0b8c\u0b8d\u0b8e\u0b8f\u0b90\u0b91\u0b92\u0b93\u0b94\u0b95\u0b96\u0b97\u0b98\u0b99\u0b9a"
+          + "\u0b9b\u0b9c\u0b9d\u0b9e\u0b9f\u0ba0\u0ba1\u0ba2\u0ba3\u0ba4\u0ba5\u0ba6\u0ba7\u0ba8\u0ba9\u0baa\u0bab\u0bac\u0bad\u0bae"
+          + "\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0bba\u0bbb\u0bbc\u0bbd\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2"
+          + "\u0bc3\u0bc4\u0bc5\u0bc6\u0bc7\u0bc8\u0bc9\u0bca\u0bcb\u0bcc\u0bcd\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6"
+          + "\u0bd7\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0be6\u0be7\u0be8\u0be9\u0bea"
+          + "\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf0\u0bf1\u0bf2\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bf9\u0bfa\u0bfb\u0bfc\u0bfd\u0bfe"
+          + "\u0bff\u0c00\u0c01\u0c02\u0c03\u0c04\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0d\u0c0e\u0c0f\u0c10\u0c11\u0c12"
+          + "\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26"
+          + "\u0c27\u0c28\u0c29\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c34\u0c35\u0c36\u0c37\u0c38\u0c39\u0c3a"
+          + "\u0c3b\u0c3c\u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44\u0c45\u0c46\u0c47\u0c48\u0c49\u0c4a\u0c4b\u0c4c\u0c4d\u0c4e"
+          + "\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c55\u0c56\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c60\u0c61\u0c62"
+          + "\u0c63\u0c64\u0c65\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76"
+          + "\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c82\u0c83\u0c84\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a"
+          + "\u0c8b\u0c8c\u0c8d\u0c8e\u0c8f\u0c90\u0c91\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e"
+          + "\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0ca9\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2"
+          + "\u0cb3\u0cb4\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cba\u0cbb\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc5\u0cc6"
+          + "\u0cc7\u0cc8\u0cc9\u0cca\u0ccb\u0ccc\u0ccd\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd5\u0cd6\u0cd7\u0cd8\u0cd9\u0cda"
+          + "\u0cdb\u0cdc\u0cdd\u0cde\u0cdf\u0ce0\u0ce1\u0ce2\u0ce3\u0ce4\u0ce5\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee"
+          + "\u0cef\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d02"
+          + "\u0d03\u0d04\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0d\u0d0e\u0d0f\u0d10\u0d11\u0d12\u0d13\u0d14\u0d15\u0d16"
+          + "\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d29\u0d2a"
+          + "\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d3a\u0d3b\u0d3c\u0d3d\u0d3e"
+          + "\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44\u0d45\u0d46\u0d47\u0d48\u0d49\u0d4a\u0d4b\u0d4c\u0d4d\u0d4e\u0d4f\u0d50\u0d51\u0d52"
+          + "\u0d53\u0d54\u0d55\u0d56\u0d57\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d60\u0d61\u0d62\u0d63\u0d64\u0d65\u0d66"
+          + "\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a"
+          + "\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d82\u0d83\u0d84\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e"
+          + "\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d97\u0d98\u0d99\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2"
+          + "\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db2\u0db3\u0db4\u0db5\u0db6"
+          + "\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbc\u0dbd\u0dbe\u0dbf\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0dc7\u0dc8\u0dc9\u0dca"
+          + "\u0dcb\u0dcc\u0dcd\u0dce\u0dcf\u0dd0\u0dd1\u0dd2\u0dd3\u0dd4\u0dd5\u0dd6\u0dd7\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde"
+          + "\u0ddf\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df2"
+          + "\u0df3\u0df4\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06"
+          + "\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a"
+          + "\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e"
+          + "\u0e2f\u0e30\u0e31\u0e32\u0e33\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e3b\u0e3c\u0e3d\u0e3e\u0e3f\u0e40\u0e41\u0e42"
+          + "\u0e43\u0e44\u0e45\u0e46\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0e4f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56"
+          + "\u0e57\u0e58\u0e59\u0e5a\u0e5b\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a"
+          + "\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e"
+          + "\u0e7f\u0e80\u0e81\u0e82\u0e83\u0e84\u0e85\u0e86\u0e87\u0e88\u0e89\u0e8a\u0e8b\u0e8c\u0e8d\u0e8e\u0e8f\u0e90\u0e91\u0e92"
+          + "\u0e93\u0e94\u0e95\u0e96\u0e97\u0e98\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea0\u0ea1\u0ea2\u0ea3\u0ea4\u0ea5\u0ea6"
+          + "\u0ea7\u0ea8\u0ea9\u0eaa\u0eab\u0eac\u0ead\u0eae\u0eaf\u0eb0\u0eb1\u0eb2\u0eb3\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0eba"
+          + "\u0ebb\u0ebc\u0ebd\u0ebe\u0ebf\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0ec5\u0ec6\u0ec7\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0ece"
+          + "\u0ecf\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0eda\u0edb\u0edc\u0edd\u0ede\u0edf\u0ee0\u0ee1\u0ee2"
+          + "\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6"
+          + "\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f00\u0f01\u0f02\u0f03\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a"
+          + "\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f13\u0f14\u0f15\u0f16\u0f17\u0f18\u0f19\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e"
+          + "\u0f1f\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32"
+          + "\u0f33\u0f34\u0f35\u0f36\u0f37\u0f38\u0f39\u0f3a\u0f3b\u0f3c\u0f3d\u0f3e\u0f3f\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46"
+          + "\u0f47\u0f48\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a"
+          + "\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f6b\u0f6c\u0f6d\u0f6e"
+          + "\u0f6f\u0f70\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f7f\u0f80\u0f81\u0f82"
+          + "\u0f83\u0f84\u0f85\u0f86\u0f87\u0f88\u0f89\u0f8a\u0f8b\u0f8c\u0f8d\u0f8e\u0f8f\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96"
+          + "\u0f97\u0f98\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa"
+          + "\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fbd\u0fbe"
+          + "\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc6\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcd\u0fce\u0fcf\u0fd0\u0fd1\u0fd2"
+          + "\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6"
+          + "\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa"
+          + "\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e"
+          + "\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1022"
+          + "\u1023\u1024\u1025\u1026\u1027\u1028\u1029\u102a\u102b\u102c\u102d\u102e\u102f\u1030\u1031\u1032\u1033\u1034\u1035\u1036"
+          + "\u1037\u1038\u1039\u103a\u103b\u103c\u103d\u103e\u103f\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u104a"
+          + "\u104b\u104c\u104d\u104e\u104f\u1050\u1051\u1052\u1053\u1054\u1055\u1056\u1057\u1058\u1059\u105a\u105b\u105c\u105d\u105e"
+          + "\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072"
+          + "\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086";
+}
diff --git a/framework/tests/all-systems/Catch.java b/framework/tests/all-systems/Catch.java
new file mode 100644
index 0000000..6fe33c2
--- /dev/null
+++ b/framework/tests/all-systems/Catch.java
@@ -0,0 +1,17 @@
+public class Catch {
+  void defaultUnionType() throws Throwable {
+    try {
+      throw new Throwable();
+    } catch (IndexOutOfBoundsException | NullPointerException ex) {
+
+    }
+  }
+
+  void defaultDeclaredType() throws Throwable {
+    try {
+      throw new Throwable();
+    } catch (RuntimeException ex) {
+
+    }
+  }
+}
diff --git a/framework/tests/all-systems/CompoundAssignments.java b/framework/tests/all-systems/CompoundAssignments.java
new file mode 100644
index 0000000..c769e3d
--- /dev/null
+++ b/framework/tests/all-systems/CompoundAssignments.java
@@ -0,0 +1,22 @@
+public class CompoundAssignments {
+  static final int SIZE = 4;
+
+  // There used to be a bug creating the LeftShiftAssignmentNode where the target (e.g. pow) was
+  // replaced by the shift amount (e.g. 1).
+  static void left_shift_assign() {
+    for (int i = 0, pow = 1; i <= SIZE; i++) {
+      pow <<= 1;
+    }
+  }
+
+  // There used to be a bug computing the JavaExpression for a widening
+  // conversion, such as widening sum to a double below.
+  static int sum_with_widening() {
+    double[] freq = new double[SIZE];
+    int sum = 0;
+    for (int i = 0; i < SIZE; i++) {
+      sum += freq[i];
+    }
+    return sum;
+  }
+}
diff --git a/framework/tests/all-systems/ConditionalExpressions.java b/framework/tests/all-systems/ConditionalExpressions.java
new file mode 100644
index 0000000..e2b9139
--- /dev/null
+++ b/framework/tests/all-systems/ConditionalExpressions.java
@@ -0,0 +1,56 @@
+// Code to test that LUB of two AnnotatedTypeMirror does not crash.
+// See Issue 643
+// https://github.com/typetools/checker-framework/issues/643
+
+import java.io.Serializable;
+import java.util.List;
+
+public class ConditionalExpressions {
+  public static boolean flag = true;
+
+  class TypeVarTypeVar {
+    <T, S extends T> void foo1(T tExtendsNumber, S sExtendsT) {
+      T o = flag ? tExtendsNumber : sExtendsT;
+    }
+
+    <T extends Number, S extends T> void foo2(T tExtendsNumber, S sExtendsT) {
+      T o = flag ? tExtendsNumber : sExtendsT;
+    }
+
+    <T extends Number, S extends Integer> void foo3(T tExtendsNumber, S sExtendsInteger) {
+      Number o3 = flag ? tExtendsNumber : sExtendsInteger;
+    }
+
+    <T extends Number, S extends CharSequence> void foo4(T tExtendsNumber, S sExtendsCharSequence) {
+      Object o2 = flag ? tExtendsNumber : sExtendsCharSequence;
+    }
+
+    <T extends Long, S extends Integer> void foo5(T tExtendsLong, S sExtendsInt) {
+      Number o = flag ? tExtendsLong : sExtendsInt;
+    }
+  }
+
+  class ArrayTypes {
+    void foo1(String string, String[] strings) {
+      Serializable o2 = (flag ? string : strings);
+    }
+
+    void foo2(Integer[] integers, String[] strings) {
+      Object[] o = (flag ? integers : strings);
+    }
+
+    <T extends Cloneable & Serializable> void foo3(T ts, Number[] numbers) {
+      Cloneable o = flag ? ts : numbers;
+    }
+
+    <T> void foo4(T ts, Number[] numbers) {
+      Object o = (flag ? ts : numbers);
+    }
+  }
+
+  class Generics {
+    void foo1(List<Long> listS, List<Integer> listI) {
+      Number s = (flag ? listI : listS).get(0);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/CrazyEnum.java b/framework/tests/all-systems/CrazyEnum.java
new file mode 100644
index 0000000..492a2fd
--- /dev/null
+++ b/framework/tests/all-systems/CrazyEnum.java
@@ -0,0 +1,20 @@
+@SuppressWarnings("all") // Check for crashes.
+public class CrazyEnum {
+  private enum MyEnum {
+    ENUM_CONST1 {
+      private final String s = method();
+
+      private String method() {
+        return "hello";
+      }
+    },
+
+    ENUM_CONST2 {
+      private final String s = this.method();
+
+      private String method() {
+        return "hello";
+      }
+    }
+  }
+}
diff --git a/framework/tests/all-systems/DeepEquals.java b/framework/tests/all-systems/DeepEquals.java
new file mode 100644
index 0000000..d6167bf
--- /dev/null
+++ b/framework/tests/all-systems/DeepEquals.java
@@ -0,0 +1,12 @@
+public class DeepEquals {
+  public static int deepEquals(Object o1) {
+    if (o1 instanceof boolean[]) {
+      return 1;
+    }
+    if (o1 instanceof byte[]) {
+      return 2;
+    }
+
+    return 3;
+  }
+}
diff --git a/framework/tests/all-systems/Enums.java b/framework/tests/all-systems/Enums.java
new file mode 100644
index 0000000..7f8bd39
--- /dev/null
+++ b/framework/tests/all-systems/Enums.java
@@ -0,0 +1,48 @@
+import java.lang.annotation.ElementType;
+
+class MyEnumSet<E extends Enum<E>> {}
+
+public class Enums {
+  public enum VarFlags {
+    IS_PARAM,
+    NO_DUPS
+  };
+
+  public MyEnumSet<VarFlags> var_flags = new MyEnumSet<>();
+
+  VarFlags f1 = VarFlags.IS_PARAM;
+
+  void foo1(MyEnumSet<VarFlags> p) {}
+
+  void foo2(MyEnumSet<ElementType> p) {}
+
+  <E extends Enum<E>> void mtv(Class<E> p) {}
+
+  <T extends Object> T checkNotNull(T ref) {
+    return ref;
+  }
+
+  <T extends Object, S extends Object> T checkNotNull2(T ref, S ref2) {
+    return ref;
+  }
+
+  class Test<T extends Enum<T>> {
+    void m(Class<T> p) {
+      checkNotNull(p);
+    }
+
+    public <SSS extends Object> SSS firstNonNull(SSS first, SSS second) {
+      @SuppressWarnings("nullness:nulltest.redundant")
+      SSS res = first != null ? first : checkNotNull(second);
+      return res;
+    }
+  }
+
+  class Unbound<X extends Object> {}
+
+  class Test2<T extends Unbound<S>, S extends Unbound<T>> {
+    void m(Class<T> p, Class<S> q) {
+      checkNotNull2(p, q);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/EqualityTests.java b/framework/tests/all-systems/EqualityTests.java
new file mode 100644
index 0000000..3b33ca5
--- /dev/null
+++ b/framework/tests/all-systems/EqualityTests.java
@@ -0,0 +1,24 @@
+public class EqualityTests {
+  // the Interning checker correctly issues an error below, but we would like to keep this test in
+  // all-systems.
+  @SuppressWarnings("interning")
+  public boolean compareLongs(Long v1, Long v2) {
+    // This expression used to cause an assertion
+    // failure in GLB computation.
+    return !(((v1 == 0) || (v2 == 0)) && (v1 != v2));
+  }
+
+  public int charEquals(boolean cond) {
+    char result = 'F';
+    if (cond) {
+      result = 'T';
+    }
+
+    if (result == 'T') {
+      return 1;
+    } else {
+      assert result == '?';
+    }
+    return 10;
+  }
+}
diff --git a/framework/tests/all-systems/FieldAccessTest.java b/framework/tests/all-systems/FieldAccessTest.java
new file mode 100644
index 0000000..ec3cf45
--- /dev/null
+++ b/framework/tests/all-systems/FieldAccessTest.java
@@ -0,0 +1,40 @@
+// The test cases GenericNull, FieldAccessTest, and InferTypeArgs often fail together.
+// See the comments at GenericNull for some tips about what might be wrong.
+
+public class FieldAccessTest {
+  class MyClass {
+
+    Object field = new Object();
+  }
+
+  class MyException extends RuntimeException {
+    Object field = new Object();
+  }
+
+  class MyExceptionA extends MyException {}
+
+  class MyExceptionB extends MyException {}
+
+  @SuppressWarnings("nullness")
+  class MyGen<T extends MyClass> {
+    T myClass = null;
+  }
+
+  void test(Object o, MyGen raw) {
+    // Raw type field access:
+    raw.myClass.field = new Object();
+
+    // Intersection type field access
+    Object a = ((MyClass & Cloneable) o).field;
+    try {
+    } catch (MyExceptionA | MyExceptionB ex) {
+      // Union type field access
+      ex.field = new Object();
+    }
+  }
+
+  void classLiterals() {
+    Class<?> c = byte.class;
+    Class<?> d = void.class;
+  }
+}
diff --git a/framework/tests/all-systems/FieldWithInit.java b/framework/tests/all-systems/FieldWithInit.java
new file mode 100644
index 0000000..7937a66
--- /dev/null
+++ b/framework/tests/all-systems/FieldWithInit.java
@@ -0,0 +1,8 @@
+public class FieldWithInit {
+  @SuppressWarnings("nullness") // Don't want to depend on Nullness Checker
+  Object f = foo();
+
+  Object foo(FieldWithInit this) {
+    return new Object();
+  }
+}
diff --git a/framework/tests/all-systems/ForEach.java b/framework/tests/all-systems/ForEach.java
new file mode 100644
index 0000000..96d4e6e
--- /dev/null
+++ b/framework/tests/all-systems/ForEach.java
@@ -0,0 +1,46 @@
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class ForEach {
+  void m1() {
+    Set<? extends CharSequence> s = new HashSet<CharSequence>();
+    for (CharSequence cs : s) {
+      cs.toString();
+    }
+  }
+
+  void m2() {
+    Set<CharSequence> s = new HashSet<>();
+    for (CharSequence cs : s) {
+      cs.toString();
+    }
+  }
+
+  <T extends Object> void m3(T p) {
+    Set<T> s = new HashSet<>();
+    for (T cs : s) {
+      cs.toString();
+    }
+  }
+
+  <T extends Object> void m4(T p) {
+    Set<T> s = new HashSet<>();
+    for (Object cs : s) {
+      cs.toString();
+    }
+  }
+
+  public static <T extends Object> List<T> removeDuplicates(List<T> l) {
+    // There are shorter solutions that do not maintain order.
+    HashSet<T> hs = new HashSet<>(l.size());
+    List<T> result = new ArrayList<>();
+    for (T t : l) {
+      if (hs.add(t)) {
+        result.add(t);
+      }
+    }
+    return result;
+  }
+}
diff --git a/framework/tests/all-systems/GenericCrazyBounds.java b/framework/tests/all-systems/GenericCrazyBounds.java
new file mode 100644
index 0000000..6cd586d
--- /dev/null
+++ b/framework/tests/all-systems/GenericCrazyBounds.java
@@ -0,0 +1,87 @@
+/*
+ * This class is solely to set up complicated recursive bounds in order to ensure that the
+ * bounds initializer creates bounds with the right structure
+ */
+
+interface MyList<ZZ> {
+  ZZ getZZ();
+
+  void setZZ(ZZ ZZ);
+}
+
+interface MyMap<K, V> {
+  K getK();
+
+  V getV();
+
+  void setK(K k);
+
+  void setV(V v);
+}
+
+class MyRec<E extends MyList<E>> {}
+
+class RecMyList extends MyRec<RecMyList> implements MyList<RecMyList> {
+  @SuppressWarnings("return")
+  public RecMyList getZZ() {
+    return null;
+  }
+
+  public void setZZ(RecMyList rl) {
+    return;
+  }
+}
+
+class Context2 {
+
+  <T extends MyRec<? extends T> & MyList<T>> void main() {}
+
+  void context() {
+    this.<RecMyList>main();
+  }
+}
+
+interface Rec<T extends Rec<T>> {}
+
+class MyRec2<E extends Rec<? extends E>> {}
+
+class RecImpl implements Rec<RecImpl> {}
+
+class SubRec extends RecImpl {}
+
+class CrazyGen2<TT extends MyList<EE>, EE extends MyMap<TT, TT>> {
+  TT t2;
+  EE e2;
+
+  public CrazyGen2(TT t2, EE e2) {
+    this.t2 = t2;
+    this.e2 = e2;
+  }
+
+  public void context() {
+    t2.setZZ(e2);
+    e2.setK(t2.getZZ().getK());
+  }
+}
+
+class CrazyGen3<TTT extends MyList<TTT>, EEE extends MyMap<TTT, TTT>> {
+  TTT t3;
+  EEE e3;
+
+  public CrazyGen3(TTT t3, EEE e3) {
+    this.t3 = t3;
+    this.e3 = e3;
+  }
+
+  public void context() {
+    e3.setK(t3);
+    e3.setK(t3.getZZ());
+  }
+}
+
+class MyClass {
+
+  public <TV1 extends MyList<TV1>> String methodToPrint(TV1 tv1, int intParam) {
+    return "";
+  }
+}
diff --git a/framework/tests/all-systems/GenericExtendsTypeVars.java b/framework/tests/all-systems/GenericExtendsTypeVars.java
new file mode 100644
index 0000000..c465531
--- /dev/null
+++ b/framework/tests/all-systems/GenericExtendsTypeVars.java
@@ -0,0 +1,37 @@
+/*
+ * Sets up recursive bounds where the bounds themselves are type variables.
+ */
+
+interface MMyList<LL> {}
+
+interface MMyMap<KEY, VALUE> {}
+
+class Tester<EE extends TT, TT extends MMyList<EE>> {}
+
+class WithWildcard<ZZ extends QQ, QQ extends YY, YY extends MMyMap<QQ, ZZ>> {
+  void context() {
+    ZZ zz = null;
+    QQ qq = null;
+    YY yy = null;
+  }
+}
+
+class Test<KK extends FF, FF extends MMyMap<KK, KK>> {
+  KK kk;
+  FF ff;
+
+  Test(KK kk, FF ff) {
+    this.kk = kk;
+    this.ff = ff;
+  }
+}
+
+class RecursiveTypevarClass<T extends RecursiveTypevarClass<T>> {
+  T t;
+
+  RecursiveTypevarClass(T t) {
+    this.t = t;
+  }
+}
+
+class RecursiveImplements implements MMyList<RecursiveImplements> {}
diff --git a/framework/tests/all-systems/GenericNull.java b/framework/tests/all-systems/GenericNull.java
new file mode 100644
index 0000000..14d9547
--- /dev/null
+++ b/framework/tests/all-systems/GenericNull.java
@@ -0,0 +1,32 @@
+// The test cases GenericNull, FieldAccessTest, and InferTypeArgs often fail together.
+// Here are some things that might be wrong if they are failing.
+//  * If you have overridden AnnotatedTypeFactory.createTreeAnnotator(), the body should return a
+//    list that contains the result of running the overridden implementation, as in:
+//     public TreeAnnotator createTreeAnnotator() {
+//         return new ListTreeAnnotator(super.createTreeAnnotator(), new MyOwnTreeAnnotator(this));
+//     }
+//  * If 'null' is intentionally not the bottom type in your type hierarchy, then you may suppress
+//    this warning for your particular type system.
+//
+//  * The standard defaulting rules set the qualifier of type variable lower
+//    bounds to bottom. (https://checkerframework.org/manual/#climb-to-top)  If
+//    you change this default to top, then this error will go away. (Add the
+//    meta-annotation @DefaultFor(LOWER_BOUNDS) to the top annotation class.)
+//    This will make types like List<@Bottom Object> illegal, so carefully
+//    consider if this is desirable.
+//
+
+public class GenericNull {
+  /**
+   * In most type systems, null's type is bottom and therefore the generic return type T is a
+   * supertype of null's type.
+   *
+   * <p>However, in the nullness and lock type systems, null's type is not bottom, so they exclude
+   * this test. For the Lock Checker, null's type is bottom for the @GuardedByUnknown hierarchy but
+   * not for the @LockPossiblyHeld hierarchy.
+   */
+  @SuppressWarnings({"nullness", "lock"})
+  <T> T f() {
+    return null;
+  }
+}
diff --git a/framework/tests/all-systems/GenericTest11full.java b/framework/tests/all-systems/GenericTest11full.java
new file mode 100644
index 0000000..7e6744a
--- /dev/null
+++ b/framework/tests/all-systems/GenericTest11full.java
@@ -0,0 +1,23 @@
+public class GenericTest11full {
+  public void m(BeanManager beanManager) {
+    Bean<?> bean = beanManager.getBeans(GenericTest11full.class).iterator().next();
+    CreationalContext<?> context = beanManager.createCreationalContext(bean);
+    GenericTest11full b =
+        (GenericTest11full) beanManager.getReference(bean, GenericTest11full.class, context);
+  }
+
+  static interface BeanManager {
+    java.util.Set<Bean<?>> getBeans(
+        java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1);
+
+    <T> CreationalContext<T> createCreationalContext(Contextual<T> arg0);
+
+    Object getReference(Bean<?> arg0, java.lang.reflect.Type arg1, CreationalContext<?> arg2);
+  }
+
+  static interface Contextual<T> {}
+
+  static interface Bean<T> extends Contextual<T> {}
+
+  static interface CreationalContext<T> {}
+}
diff --git a/framework/tests/all-systems/GenericTest12.java b/framework/tests/all-systems/GenericTest12.java
new file mode 100644
index 0000000..24b4b63
--- /dev/null
+++ b/framework/tests/all-systems/GenericTest12.java
@@ -0,0 +1,10 @@
+// Test case from Issue 142
+abstract class GenericTest12 {
+  interface Task<V> {}
+
+  abstract <M> Task<M> create(Runnable runnable, M result);
+
+  void submit(Runnable runnable) {
+    Task<Void> task = create(runnable, null);
+  }
+}
diff --git a/framework/tests/all-systems/GenericTest12b.java b/framework/tests/all-systems/GenericTest12b.java
new file mode 100644
index 0000000..7fd8992
--- /dev/null
+++ b/framework/tests/all-systems/GenericTest12b.java
@@ -0,0 +1,20 @@
+@SuppressWarnings("nullness") // Don't want to depend on @Nullable
+public class GenericTest12b {
+  class Cell<T1 extends Object> {}
+
+  class Node<CONTENT extends Object> {
+    public Node(Cell<CONTENT> userObject) {}
+
+    void nodecall(Cell<CONTENT> userObject) {}
+  }
+
+  class RootNode extends Node<Void> {
+    public RootNode() {
+      super(new Cell<Void>());
+      call(new Cell<Void>());
+      nodecall(new Cell<Void>());
+    }
+
+    void call(Cell<Void> userObject) {}
+  }
+}
diff --git a/framework/tests/all-systems/GenericTest13.java b/framework/tests/all-systems/GenericTest13.java
new file mode 100644
index 0000000..7595597
--- /dev/null
+++ b/framework/tests/all-systems/GenericTest13.java
@@ -0,0 +1,16 @@
+// Test case for Issue 142
+// https://github.com/typetools/checker-framework/issues/142
+
+public class GenericTest13 {
+  interface Entry<K extends Object, V extends Object> {
+    V getValue();
+  }
+
+  interface Iterator<E extends Object> {
+    E next();
+  }
+
+  <S extends Object> S call(Iterator<? extends Entry<?, S>> entryIterator) {
+    return entryIterator.next().getValue();
+  }
+}
diff --git a/framework/tests/all-systems/GenericsBounds.java b/framework/tests/all-systems/GenericsBounds.java
new file mode 100644
index 0000000..34b6877
--- /dev/null
+++ b/framework/tests/all-systems/GenericsBounds.java
@@ -0,0 +1,33 @@
+import java.util.LinkedList;
+import java.util.List;
+
+interface A<ID> {}
+
+class B1<ID> implements A<ID> {}
+
+interface B2 extends A<Long> {}
+
+class C extends B1<Long> implements B2 {}
+
+class Upper<ID, X extends A<ID>, Y extends X> {}
+
+class Lower extends Upper<Long, B2, C> {}
+
+class GenericsBounds {
+  Upper<Long, B2, C> f = new Upper<>();
+}
+
+class Upper1<ID, X extends List<ID>> {}
+
+class Lower1 extends Upper1<Long, List<Long>> {}
+
+class Upper2<ID, X extends List<ID>, Y extends X> {}
+
+class Lower2 extends Upper2<Long, List<Long>, LinkedList<Long>> {}
+
+class GenericGetClass {
+
+  <U extends Object> Class<? extends U> getClass(Class<?> orig, Class<U> cast) {
+    return orig.asSubclass(cast);
+  }
+}
diff --git a/framework/tests/all-systems/GenericsBounds2.java b/framework/tests/all-systems/GenericsBounds2.java
new file mode 100644
index 0000000..f6990f4
--- /dev/null
+++ b/framework/tests/all-systems/GenericsBounds2.java
@@ -0,0 +1,7 @@
+// Test for Issue 258
+// https://github.com/typetools/checker-framework/issues/258
+public class GenericsBounds2 {
+  <I extends Object, C extends I> void method(C arg) {
+    arg.toString();
+  }
+}
diff --git a/framework/tests/all-systems/GenericsCasts.java b/framework/tests/all-systems/GenericsCasts.java
new file mode 100644
index 0000000..4f43b5a
--- /dev/null
+++ b/framework/tests/all-systems/GenericsCasts.java
@@ -0,0 +1,53 @@
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class GenericsCasts {
+  // Cast from a raw type to a generic type
+  // :: warning: [unchecked] unchecked cast
+  List<Object>[] o = (List<Object>[]) new List[] {new ArrayList()};
+
+  class Data<T> {}
+
+  // Use our own dummy method as to avoid a complaint from the Signature Checker
+  Data<?> forName(String p) {
+    throw new Error("");
+  }
+
+  void m() {
+    // Cast from a wildcard to a normal type argument.
+    // Warning only with -AcheckCastElementType.
+    // TODO:: warning: (cast.unsafe)
+    // :: warning: [unchecked] unchecked cast
+    Data<GenericsCasts> c = (Data<GenericsCasts>) forName("HaHa!");
+  }
+
+  // Casts from something with one type argument to two type arguments
+  // are currently problematic.
+  // TODO: try to find a problem with skipping this check.
+  class Test<K extends Object, V extends Object> {
+    class Entry<K extends Object, V extends Object> extends LinkedList<K> {}
+
+    class Queue<T extends Object> {
+      List<? extends T> poll() {
+        throw new Error("");
+      }
+    }
+
+    void trouble() {
+      Queue<K> queue = new Queue<>();
+      // Warning only with -AcheckCastElementType.
+      // TODO:: warning: (cast.unsafe)
+      // :: warning: [unchecked] unchecked cast
+      Entry<K, V> e = (Entry<K, V>) queue.poll();
+    }
+  }
+
+  public static <T extends Object> int indexOf(T[] a) {
+    return indexOfEq(a);
+  }
+
+  public static int indexOfEq(Object[] a) {
+    return 0;
+  }
+}
diff --git a/framework/tests/all-systems/GenericsEnclosing.java b/framework/tests/all-systems/GenericsEnclosing.java
new file mode 100644
index 0000000..62e8ab1
--- /dev/null
+++ b/framework/tests/all-systems/GenericsEnclosing.java
@@ -0,0 +1,25 @@
+import java.util.TreeMap;
+
+/**
+ * Resolution of outer classes must take substitution of generic types into account. Thanks to EMS
+ * for finding this problem.
+ *
+ * <p>Also see regex/GenericsEnclosing for a test case for the Regex Checker.
+ */
+public class GenericsEnclosing extends TreeMap<String, String> {
+  class Inner {
+    void foo() {
+      put("string", "string".toString());
+      put("string", "string");
+      GenericsEnclosing.this.put("string", "string".toString());
+      GenericsEnclosing.this.put("string", "string");
+    }
+  }
+}
+
+class OtherUse {
+  void m(GenericsEnclosing p) {
+    p.put("string", "string".toString());
+    p.put("string", "string");
+  }
+}
diff --git a/framework/tests/all-systems/GetClassTest.java b/framework/tests/all-systems/GetClassTest.java
new file mode 100644
index 0000000..bb80601
--- /dev/null
+++ b/framework/tests/all-systems/GetClassTest.java
@@ -0,0 +1,29 @@
+import java.util.Date;
+
+public class GetClassTest {
+
+  // See AnnotatedTypeFactory.adaptGetClassReturnTypeToReceiver
+
+  void context() {
+    Integer i = 4;
+    i.getClass();
+    Class<?> a = i.getClass();
+    // Type arguments don't match
+    @SuppressWarnings("fenum:assignment")
+    Class<? extends Object> b = i.getClass();
+    @SuppressWarnings({
+      "fenum:assignment", // Type arguments don't match
+      "signedness:assignment" // Type arguments don't match
+    })
+    Class<? extends Integer> c = i.getClass();
+
+    Class<?> d = i.getClass();
+    // not legal Java; that is, does not type-check under Java rules
+    // Class<Integer> e = i.getClass();
+  }
+
+  void m(Date d) {
+    @SuppressWarnings("fenum:assignment")
+    Class<? extends Date> c = d.getClass();
+  }
+}
diff --git a/framework/tests/all-systems/InferAndIntersection.java b/framework/tests/all-systems/InferAndIntersection.java
new file mode 100644
index 0000000..d4105bb
--- /dev/null
+++ b/framework/tests/all-systems/InferAndIntersection.java
@@ -0,0 +1,8 @@
+public class InferAndIntersection {
+
+  <T> void toInfer(Iterable<T> t) {}
+
+  <U extends Object & Iterable<Object>> void context(U u) {
+    toInfer(u);
+  }
+}
diff --git a/framework/tests/all-systems/InferAndWildcards.java b/framework/tests/all-systems/InferAndWildcards.java
new file mode 100644
index 0000000..fcb3f4b
--- /dev/null
+++ b/framework/tests/all-systems/InferAndWildcards.java
@@ -0,0 +1,10 @@
+@SuppressWarnings("interning")
+public class InferAndWildcards {
+  <UUU> Class<? extends UUU> b(Class<UUU> clazz) {
+    return clazz;
+  }
+
+  <TTT> void a(Class<TTT> clazz) {
+    Class<? extends TTT> v = b(clazz);
+  }
+}
diff --git a/framework/tests/all-systems/InferNullType.java b/framework/tests/all-systems/InferNullType.java
new file mode 100644
index 0000000..e24534a
--- /dev/null
+++ b/framework/tests/all-systems/InferNullType.java
@@ -0,0 +1,37 @@
+// See checker/tests/nullness/InferNullType.java for test that verifies correct Nullness Checker
+// errors
+public class InferNullType {
+
+  <T extends Object> T toInfer(T input) {
+    return input;
+  }
+
+  <T> T toInfer2(T input) {
+    return input;
+  }
+
+  <T, S extends T> T toInfer3(T input, S p2) {
+    return input;
+  }
+
+  <T extends Number, S extends T> T toInfer4(T input, S p2) {
+    return input;
+  }
+
+  void x() {
+    @SuppressWarnings("nullness:type.argument")
+    Object m = toInfer(null);
+    Object m2 = toInfer2(null);
+
+    Object m3 = toInfer3(null, null);
+    Object m4 = toInfer3(1, null);
+    Object m5 = toInfer3(null, 1);
+
+    @SuppressWarnings("nullness:type.argument")
+    Object m6 = toInfer4(null, null);
+    @SuppressWarnings("nullness:type.argument")
+    Object m7 = toInfer4(1, null);
+    @SuppressWarnings("nullness:type.argument")
+    Object m8 = toInfer4(null, 1);
+  }
+}
diff --git a/framework/tests/all-systems/InferTypeArgs.java b/framework/tests/all-systems/InferTypeArgs.java
new file mode 100644
index 0000000..cbb8d70
--- /dev/null
+++ b/framework/tests/all-systems/InferTypeArgs.java
@@ -0,0 +1,82 @@
+// The test cases GenericNull, FieldAccessTest, and InferTypeArgs often fail together.
+// See the comments at GenericNull for some tips about what might be wrong.
+
+/**
+ * This test came from running the compilermsgs checker in the checker-framework/checker directory
+ * It's to test the result of type argument inference. We used to have the following return found:
+ * FlowAnalysis[ extends @UnknownPropertyKey CFAbstractAnalysis<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value[ extends @UnknownPropertyKey CFAbstractValue<Value>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void], Store[
+ * extends @UnknownPropertyKey CFAbstractStore<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value[ extends @UnknownPropertyKey CFAbstractValue<Value>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void], Store[
+ * extends @UnknownPropertyKey CFAbstractStore<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value[ extends @UnknownPropertyKey CFAbstractValue<Value>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void], Store>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void], TransferFunction[
+ * extends @UnknownPropertyKey CFAbstractTransfer<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value[ extends @UnknownPropertyKey CFAbstractValue<Value>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void], Store[
+ * extends @UnknownPropertyKey CFAbstractStore<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value[ extends @UnknownPropertyKey CFAbstractValue<Value>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void], Store[
+ * extends @UnknownPropertyKey CFAbstractStore<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value[ extends @UnknownPropertyKey CFAbstractValue<Value>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void], Store>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void], TransferFunction[
+ * extends @UnknownPropertyKey CFAbstractTransfer<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value[ extends @UnknownPropertyKey CFAbstractValue<Value>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void], Store[
+ * extends @UnknownPropertyKey CFAbstractStore<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value[ extends @UnknownPropertyKey CFAbstractValue<Value>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void], Store[
+ * extends @UnknownPropertyKey CFAbstractStore<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value[ extends @UnknownPropertyKey CFAbstractValue<Value>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void], Store>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void], TransferFunction>
+ * super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void]> super @UnknownPropertyKey Void]
+ * required: FlowAnalysis[ extends @UnknownPropertyKey CFAbstractAnalysis<Value[
+ * extends @UnknownPropertyKey CFAbstractValue<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value> super @Bottom Void]> super @Bottom Void], Store[
+ * extends @UnknownPropertyKey CFAbstractStore<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value[ extends @UnknownPropertyKey CFAbstractValue<Value> super @Bottom Void]>
+ * super @Bottom Void], Store[ extends @UnknownPropertyKey CFAbstractStore<Value[
+ * extends @UnknownPropertyKey CFAbstractValue<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value> super @Bottom Void]> super @Bottom Void], Store> super @Bottom Void]>
+ * super @Bottom Void], TransferFunction[ extends @UnknownPropertyKey CFAbstractTransfer<Value[
+ * extends @UnknownPropertyKey CFAbstractValue<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value> super @Bottom Void]> super @Bottom Void], Store[
+ * extends @UnknownPropertyKey CFAbstractStore<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value[ extends @UnknownPropertyKey CFAbstractValue<Value> super @Bottom Void]>
+ * super @Bottom Void], Store[ extends @UnknownPropertyKey CFAbstractStore<Value[
+ * extends @UnknownPropertyKey CFAbstractValue<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value> super @Bottom Void]> super @Bottom Void], Store> super @Bottom Void]>
+ * super @Bottom Void], TransferFunction[ extends @UnknownPropertyKey CFAbstractTransfer<Value[
+ * extends @UnknownPropertyKey CFAbstractValue<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value> super @Bottom Void]> super @Bottom Void], Store[
+ * extends @UnknownPropertyKey CFAbstractStore<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value[ extends @UnknownPropertyKey CFAbstractValue<Value> super @Bottom Void]>
+ * super @Bottom Void], Store[ extends @UnknownPropertyKey CFAbstractStore<Value[
+ * extends @UnknownPropertyKey CFAbstractValue<Value[ extends @UnknownPropertyKey
+ * CFAbstractValue<Value> super @Bottom Void]> super @Bottom Void], Store> super @Bottom Void]>
+ * super @Bottom Void], TransferFunction> super @Bottom Void]> super @Bottom Void]> super @Bottom
+ * Void]
+ */
+class CFAbstractValue<V extends CFAbstractValue<V>> {}
+
+class CFAbstractAnalysis<V extends CFAbstractValue<V>> {}
+
+class GenericAnnotatedTypeFactory<
+    Value extends CFAbstractValue<Value>, FlowAnalysis extends CFAbstractAnalysis<Value>> {
+
+  @SuppressWarnings("immutability:type.argument")
+  protected FlowAnalysis createFlowAnalysis() {
+    FlowAnalysis result = invokeConstructorFor();
+    return result;
+  }
+
+  @SuppressWarnings({"nullness:return", "lock:return", "immutabilitysub:type.argument"})
+  public static <T> T invokeConstructorFor() {
+    return null;
+  }
+}
diff --git a/framework/tests/all-systems/InferTypeArgs2.java b/framework/tests/all-systems/InferTypeArgs2.java
new file mode 100644
index 0000000..f7e6fc0
--- /dev/null
+++ b/framework/tests/all-systems/InferTypeArgs2.java
@@ -0,0 +1,24 @@
+public class InferTypeArgs2<V extends InferTypeArgs2<V>> {}
+
+// having a concrete extension of InferTypeArgs2 is the key difference between this and
+// InferTypeArgs1.java.  In this case we end up comparing CFValue with V extends InferTypeArgs2<V>
+// which kicks off the DefaultRawnessComparer.  Before I fixed it, it then blew the stack
+class CFValue extends InferTypeArgs2<CFValue> {
+  public CFValue(InferTypeArgsAnalysis<CFValue, ?, ?> analysis) {}
+}
+
+class CFAbstractStore<V extends InferTypeArgs2<V>, S extends CFAbstractStore<V, S>> {}
+
+class CFAbstractTransfer<
+    V extends InferTypeArgs2<V>,
+    S extends CFAbstractStore<V, S>,
+    T extends CFAbstractTransfer<V, S, T>> {}
+
+class InferTypeArgsAnalysis<
+    V extends InferTypeArgs2<V>,
+    S extends CFAbstractStore<V, S>,
+    T extends CFAbstractTransfer<V, S, T>> {
+  public CFValue defaultCreateAbstractValue(InferTypeArgsAnalysis<CFValue, ?, ?> analysis) {
+    return new CFValue(analysis);
+  }
+}
diff --git a/framework/tests/all-systems/InferTypeArgs3.java b/framework/tests/all-systems/InferTypeArgs3.java
new file mode 100644
index 0000000..6f27f4e
--- /dev/null
+++ b/framework/tests/all-systems/InferTypeArgs3.java
@@ -0,0 +1,14 @@
+import java.util.Arrays;
+import java.util.HashSet;
+
+public class InferTypeArgs3 {
+  @SuppressWarnings({"deprecation", "cast.unsafe.constructor.invocation"})
+  void test() {
+    java.util.Arrays.asList(new Integer(1), "");
+  }
+
+  void foo() {
+    new HashSet<>(Arrays.asList(new Object()));
+    new HashSet<Object>(Arrays.asList(new Object())) {};
+  }
+}
diff --git a/framework/tests/all-systems/InferTypeArgsConditionalExpression.java b/framework/tests/all-systems/InferTypeArgsConditionalExpression.java
new file mode 100644
index 0000000..216a680
--- /dev/null
+++ b/framework/tests/all-systems/InferTypeArgsConditionalExpression.java
@@ -0,0 +1,15 @@
+// Used to cause crash similar to the one reported in #579
+// https://github.com/typetools/checker-framework/issues/579
+// Issue 579 test case is in checker/tests/nullness/java8/Issue579.java
+// A similar test case appears in checker/tests/nullness/InferTypeArgsConditionalExpression.java
+
+public class InferTypeArgsConditionalExpression {
+
+  public <T> void foo(Generic<T> real, Generic<? super T> other, boolean flag) {
+    bar(flag ? real : other);
+  }
+
+  <Q> void bar(Generic<? extends Q> param) {}
+
+  interface Generic<F> {}
+}
diff --git a/framework/tests/all-systems/InitializationVisitor.java b/framework/tests/all-systems/InitializationVisitor.java
new file mode 100644
index 0000000..c574139
--- /dev/null
+++ b/framework/tests/all-systems/InitializationVisitor.java
@@ -0,0 +1,34 @@
+// Minimized test case from InitializationVisitor.
+
+class IATF<
+        Value extends CFAV<Value>,
+        Store extends IS<Value, Store>,
+        Transfer extends IT<Value, Transfer, Store>,
+        Flow extends CFAA<Value, Store, Transfer>>
+    extends GATF<Value, Store, Transfer, Flow> {}
+
+class CFAV<V extends CFAV<V>> {}
+
+class IS<V extends CFAV<V>, S extends IS<V, S>> extends CFAS<V, S> {}
+
+class IT<V extends CFAV<V>, T extends IT<V, T, S>, S extends IS<V, S>> extends CFAT<V, S, T> {}
+
+class CFAA<V extends CFAV<V>, S extends CFAS<V, S>, T extends CFAT<V, S, T>> {}
+
+class CFAT<V extends CFAV<V>, S extends CFAS<V, S>, T extends CFAT<V, S, T>> {}
+
+class CFAS<V extends CFAV<V>, S extends CFAS<V, S>> {}
+
+class GATF<
+    Value extends CFAV<Value>,
+    Store extends CFAS<Value, Store>,
+    TransferFunction extends CFAT<Value, Store, TransferFunction>,
+    FlowAnalysis extends CFAA<Value, Store, TransferFunction>> {}
+
+class BTV<Factory extends GATF<?, ?, ?, ?>> {}
+
+class IV<
+        Factory extends IATF<Value, Store, ?, ?>,
+        Value extends CFAV<Value>,
+        Store extends IS<Value, Store>>
+    extends BTV<Factory> {}
diff --git a/framework/tests/all-systems/InstanceOf.java b/framework/tests/all-systems/InstanceOf.java
new file mode 100644
index 0000000..c0e79eb
--- /dev/null
+++ b/framework/tests/all-systems/InstanceOf.java
@@ -0,0 +1,30 @@
+/* This example causes an error when computing the GLB of two types
+ * because the GLB is empty. */
+
+class FieldInstruction {}
+
+class GETFIELD extends FieldInstruction {}
+
+class PUTFIELD extends FieldInstruction {}
+
+public class InstanceOf {
+  public void emptyGLB(FieldInstruction f) {
+    if (f instanceof GETFIELD || f instanceof PUTFIELD) {
+      if (f instanceof PUTFIELD) {
+        // During org.checkerframework.dataflow analysis, we can believe that f is both a
+        // GETFIELD and a PUTFIELD, which yields an empty GLB.  Once
+        // org.checkerframework.dataflow converges, it will know that f is a PUTFIELD.
+        return;
+      }
+      return;
+    }
+  }
+
+  public boolean assignInstanceOf(Object obj) {
+    // We fixed a bug where the type in an instanceof expression
+    // like Class<?> was stored as the abstract value result of
+    // the expression.
+    boolean is_class = obj instanceof Class<?>;
+    return is_class;
+  }
+}
diff --git a/framework/tests/all-systems/IntersectionTypes.java b/framework/tests/all-systems/IntersectionTypes.java
new file mode 100644
index 0000000..3f2b4d3
--- /dev/null
+++ b/framework/tests/all-systems/IntersectionTypes.java
@@ -0,0 +1,17 @@
+// Test case for Issue 247:
+// https://github.com/typetools/checker-framework/issues/247
+
+interface Foo {}
+
+interface Bar {}
+
+class Baz implements Foo, Bar {}
+
+public class IntersectionTypes {
+  void foo() {
+    Baz baz = new Baz();
+    call(baz);
+  }
+
+  <T extends Foo & Bar> void call(T p) {}
+}
diff --git a/framework/tests/all-systems/IsSubarrayEq.java b/framework/tests/all-systems/IsSubarrayEq.java
new file mode 100644
index 0000000..9468d4f
--- /dev/null
+++ b/framework/tests/all-systems/IsSubarrayEq.java
@@ -0,0 +1,13 @@
+import java.util.List;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class IsSubarrayEq {
+  // the Interning checker correctly issues an error below, but we would like to keep this test in
+  // all-systems.
+  // Fenum Checker should not issue a warning.  See issue 789
+  // https://github.com/typetools/checker-framework/issues/789
+  @SuppressWarnings({"interning", "fenum:return"})
+  public static boolean isSubarrayEq(Object @MinLen(1) [] a, List<?> sub) {
+    return (sub.get(0) != a[0]);
+  }
+}
diff --git a/framework/tests/all-systems/Issue1003.java b/framework/tests/all-systems/Issue1003.java
new file mode 100644
index 0000000..d4642fa
--- /dev/null
+++ b/framework/tests/all-systems/Issue1003.java
@@ -0,0 +1,21 @@
+// Test case for Issue 1003
+// https://github.com/typetools/checker-framework/issues/1003
+
+// The fact that M extends Issue1003 is unimportant,
+// I'm just doing this to wrap it up into a single class example:
+public class Issue1003<M extends Issue1003> {
+  public int field = 0;
+
+  public M getFoo() {
+    throw new RuntimeException();
+  }
+
+  public static void methodMemberAccess() {
+    // Use of raw generics:
+    Issue1003 m = new Issue1003();
+    // This version causes error but not exception:
+    // Issue1003<Issue1003> m = new Issue1003<>();
+    // Exception caused by this line:
+    int x = m.getFoo().field;
+  }
+}
diff --git a/framework/tests/all-systems/Issue1006.java b/framework/tests/all-systems/Issue1006.java
new file mode 100644
index 0000000..dfa7a93
--- /dev/null
+++ b/framework/tests/all-systems/Issue1006.java
@@ -0,0 +1,20 @@
+// Test case for Issue 1006:
+// https://github.com/typetools/checker-framework/issues/1006
+
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@SuppressWarnings("all") // Ignore type-checking errors.
+public class Issue1006 {
+  void foo(Stream<String> m, Map<String, Integer> im) {
+    Map<String, Integer> l = m.collect(Collectors.toMap(Function.identity(), im::get));
+  }
+
+  // alternative version with same crash
+  Map<String, Long> bar(String src) {
+    return Stream.of(src)
+        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
+  }
+}
diff --git a/framework/tests/all-systems/Issue1039.java b/framework/tests/all-systems/Issue1039.java
new file mode 100644
index 0000000..ee7c1c8
--- /dev/null
+++ b/framework/tests/all-systems/Issue1039.java
@@ -0,0 +1,8 @@
+// Test case for Issue 1039:
+// https://github.com/typetools/checker-framework/issues/1039
+
+public class Issue1039<T extends Issue1039<T>> {
+  Issue1039<?> foo() {
+    return new Issue1039<>();
+  }
+}
diff --git a/framework/tests/all-systems/Issue1043.java b/framework/tests/all-systems/Issue1043.java
new file mode 100644
index 0000000..fbf2d37
--- /dev/null
+++ b/framework/tests/all-systems/Issue1043.java
@@ -0,0 +1,14 @@
+// Test case for Issue 1043:
+// https://github.com/typetools/checker-framework/issues/1043
+
+public class Issue1043 {
+  <T> boolean foo(Class<T> p) {
+    return true;
+  }
+
+  void bar(Object p) {}
+
+  void baz() {
+    bar(foo(this.getClass()) ? "a" : "b");
+  }
+}
diff --git a/framework/tests/all-systems/Issue1049.java b/framework/tests/all-systems/Issue1049.java
new file mode 100644
index 0000000..06bc82f
--- /dev/null
+++ b/framework/tests/all-systems/Issue1049.java
@@ -0,0 +1,12 @@
+// Test case for Issue #1049
+// https://github.com/typetools/checker-framework/issues/1049
+public class Issue1049 {
+  interface Gen<T extends Gen<T>> {
+    T get();
+  }
+
+  @SuppressWarnings("nulltest.redundant")
+  void bar(Gen<?> g) {
+    Gen<?> l = g.get() != null ? g.get() : g;
+  }
+}
diff --git a/framework/tests/all-systems/Issue1102.java b/framework/tests/all-systems/Issue1102.java
new file mode 100644
index 0000000..f1e5dc5
--- /dev/null
+++ b/framework/tests/all-systems/Issue1102.java
@@ -0,0 +1,22 @@
+// Test case for Issue 1102:
+// https://github.com/typetools/checker-framework/issues/1102
+// Additional test in checker/tests/nullness/Issue1102.java
+
+interface Issue1102Itf {}
+
+class Issue1102Base {}
+
+class Issue1102Decl extends Issue1102Base {
+  static <S extends Object, T extends Issue1102Base & Issue1102Itf> Issue1102Decl newInstance(T s) {
+    return new Issue1102Decl();
+  }
+}
+
+@SuppressWarnings("all") // Only interested in possible crash
+class Issue1102Use<U extends Issue1102Base & Issue1102Itf> {
+  U f;
+
+  void bar() {
+    Issue1102Decl d = Issue1102Decl.newInstance(f);
+  }
+}
diff --git a/framework/tests/all-systems/Issue1111.java b/framework/tests/all-systems/Issue1111.java
new file mode 100644
index 0000000..122b9ec
--- /dev/null
+++ b/framework/tests/all-systems/Issue1111.java
@@ -0,0 +1,16 @@
+// Test case for Issue1111
+// https://github.com/typetools/checker-framework/issues/1111
+// Additional test case in checker/tests/tainting/Issue1111.java
+
+import java.util.List;
+
+@SuppressWarnings("all") // just check for crash
+public class Issue1111 {
+  void foo(Box<? super Integer> box, List<Integer> list) {
+    bar(box, list);
+  }
+
+  <T extends Number> void bar(Box<T> box, Iterable<? extends T> list) {}
+
+  class Box<T extends Number> {}
+}
diff --git a/framework/tests/all-systems/Issue116Graph.java b/framework/tests/all-systems/Issue116Graph.java
new file mode 100644
index 0000000..b8a836b
--- /dev/null
+++ b/framework/tests/all-systems/Issue116Graph.java
@@ -0,0 +1,12 @@
+/*
+ * Test case for Issue 116:
+ * https://github.com/typetools/checker-framework/issues/116
+ */
+
+class Issue116Node<EdgeType extends Issue116Edge<? extends Issue116Node<EdgeType>>> {}
+
+class Issue116Edge<NodeType extends Issue116Node<? extends Issue116Edge<NodeType>>> {}
+
+// The first two lines are already enough to trigger one bug. The next line reveals another.
+public class Issue116Graph<
+    GrNodeType extends Issue116Node<GrEdgeType>, GrEdgeType extends Issue116Edge<GrNodeType>> {}
diff --git a/framework/tests/all-systems/Issue1274.java b/framework/tests/all-systems/Issue1274.java
new file mode 100644
index 0000000..d7c5f90
--- /dev/null
+++ b/framework/tests/all-systems/Issue1274.java
@@ -0,0 +1,23 @@
+// Test case for Issue 1274
+// https://github.com/typetools/checker-framework/issues/1274
+
+@SuppressWarnings("all") // Just check for crashes
+public class Issue1274 {
+  static class Mine<T> {
+    static <S> Mine<S> of(S p1, S p2) {
+      return null;
+    }
+  }
+
+  class Two<U, V> {}
+
+  class C extends Two<Float, Float> {}
+
+  class D extends Two<String, String> {}
+
+  class Crash {
+    {
+      Mine.of(new C(), new D());
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue1431.java b/framework/tests/all-systems/Issue1431.java
new file mode 100644
index 0000000..400acac
--- /dev/null
+++ b/framework/tests/all-systems/Issue1431.java
@@ -0,0 +1,13 @@
+// Test case for Issue 1431
+// https://github.com/typetools/checker-framework/issues/1431
+public class Issue1431 {
+  static class Outer<V> {
+    class Inner<T extends V> {}
+  }
+
+  Outer<Object>.Inner<int[]> ic;
+
+  Issue1431(Outer<Object>.Inner<int[]> ic) {
+    this.ic = ic;
+  }
+}
diff --git a/framework/tests/all-systems/Issue1442.java b/framework/tests/all-systems/Issue1442.java
new file mode 100644
index 0000000..0ea99fc
--- /dev/null
+++ b/framework/tests/all-systems/Issue1442.java
@@ -0,0 +1,34 @@
+// Test case for Issue 1442.
+// https://github.com/typetools/checker-framework/issues/1442
+
+public class Issue1442 {
+  protected void configure(SubConfig<SubMyClass>.SubConfigInner x) {
+    SubMyClass subMyClass = x.getT().getSubConfigInner().outerClassTypeVar();
+  }
+
+  static class MyClass<A extends MyClass<A>> {}
+
+  static class SubMyClass extends MyClass<SubMyClass> {}
+
+  static class Config<B extends MyClass<B>> {
+    class ConfigInner<T> {
+      public T getT() {
+        throw new RuntimeException();
+      }
+    }
+  }
+
+  static class SubConfig<C extends MyClass<C>> extends Config<C> {
+    public class SubConfigInner extends ConfigInner<Thing> {
+      public C outerClassTypeVar() {
+        throw new RuntimeException();
+      }
+    }
+
+    class Thing {
+      public SubConfigInner getSubConfigInner() {
+        throw new RuntimeException();
+      }
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue1506.java b/framework/tests/all-systems/Issue1506.java
new file mode 100644
index 0000000..6c360a3
--- /dev/null
+++ b/framework/tests/all-systems/Issue1506.java
@@ -0,0 +1,17 @@
+// Test case for Issue 1506
+// https://github.com/typetools/checker-framework/issues/1506
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+@SuppressWarnings("all") // just check for crashes.
+public class Issue1506 {
+  static void m() {
+    ArrayList<? super Exception> l = new ArrayList<>();
+    try {
+      throw new IOException();
+    } catch (RuntimeException | IOException e) {
+      l.add(e);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue1520.java b/framework/tests/all-systems/Issue1520.java
new file mode 100644
index 0000000..387df82
--- /dev/null
+++ b/framework/tests/all-systems/Issue1520.java
@@ -0,0 +1,35 @@
+// Test case for Issue 1520
+// https://github.com/typetools/checker-framework/issues/1520
+
+import java.io.IOException;
+
+public class Issue1520 {
+  void start() {
+    new Runnable() {
+      public void run() {
+        try {
+          _run();
+        } finally {
+          signal(); // Evaluating this node type as member of implicit `this` will throw
+          // NPE
+        }
+      }
+    };
+  }
+
+  void signal() {}
+
+  void _run() {}
+
+  static class Inner {}
+
+  void test2() throws IOException {
+    try {
+      throwIO();
+    } finally {
+      Inner inner = new Inner();
+    }
+  }
+
+  void throwIO() throws IOException {}
+}
diff --git a/framework/tests/all-systems/Issue1526.java b/framework/tests/all-systems/Issue1526.java
new file mode 100644
index 0000000..100c860
--- /dev/null
+++ b/framework/tests/all-systems/Issue1526.java
@@ -0,0 +1,17 @@
+// Tet case for Issue 1526.
+// https://github.com/typetools/checker-framework/issues/1526
+public class Issue1526 {
+  public <T> T get(T t) {
+    return this.get(t);
+  }
+
+  public <T> T method(T[] t) {
+    return this.method(t);
+  }
+
+  public <T> T method2(Gen<T> t) {
+    return this.method2(t);
+  }
+
+  static class Gen<T> {}
+}
diff --git a/framework/tests/all-systems/Issue1543.java b/framework/tests/all-systems/Issue1543.java
new file mode 100644
index 0000000..6fe0011
--- /dev/null
+++ b/framework/tests/all-systems/Issue1543.java
@@ -0,0 +1,15 @@
+// Test case for Issue 1543
+// https://github.com/typetools/checker-framework/issues/1543
+@SuppressWarnings("all") // check for crashes only
+public class Issue1543 {
+  static class BClass<T> {}
+
+  interface AInterface<T> {}
+
+  static class GClass<T extends BClass<?> & AInterface<?>> {}
+
+  static class Test {
+    GClass gClassRaw;
+    GClass<?> gClassWC;
+  }
+}
diff --git a/framework/tests/all-systems/Issue1546.java b/framework/tests/all-systems/Issue1546.java
new file mode 100644
index 0000000..bf7c08d
--- /dev/null
+++ b/framework/tests/all-systems/Issue1546.java
@@ -0,0 +1,17 @@
+// Test case for Issue 1546
+// https://github.com/typetools/checker-framework/issues/1546
+@SuppressWarnings("all") // check for crashes
+public class Issue1546 {
+
+  <T> void m(T t) {}
+
+  {
+    try {
+      new Runnable() {
+        public void run() {}
+      };
+    } finally {
+      m("Hi");
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue1586.java b/framework/tests/all-systems/Issue1586.java
new file mode 100644
index 0000000..24235fa
--- /dev/null
+++ b/framework/tests/all-systems/Issue1586.java
@@ -0,0 +1,22 @@
+// Test case for Issue #1586:
+// https://github.com/typetools/checker-framework/issues/1586
+
+import java.util.concurrent.ExecutorService;
+
+public class Issue1586 {
+  void f(ExecutorService es) {
+    es.execute(
+        () -> {
+          try {
+            System.err.println();
+          } catch (Throwable throwable) {
+            System.err.println();
+          } finally {
+            es.execute(
+                () -> {
+                  System.err.println();
+                });
+          }
+        });
+  }
+}
diff --git a/framework/tests/all-systems/Issue1587.java b/framework/tests/all-systems/Issue1587.java
new file mode 100644
index 0000000..f749776
--- /dev/null
+++ b/framework/tests/all-systems/Issue1587.java
@@ -0,0 +1,28 @@
+abstract class Issue1587 {
+  static class MyObject {}
+
+  interface Six<T extends Six<T, R>, R> {
+    T d();
+
+    Iterable<R> q();
+  }
+
+  abstract Six<?, MyObject> e(Object entity);
+
+  public void method(MyObject one) {
+    g(e(one).d().q());
+  }
+
+  abstract Iterable<MyObject> g(Iterable<MyObject> r);
+
+  interface Class2<C, D extends Class2<C, D>> {}
+
+  static class Class1<A, B extends Class2<A, B>> {
+
+    static void test() {}
+
+    void use() {
+      Class1.test();
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue1587b.java b/framework/tests/all-systems/Issue1587b.java
new file mode 100644
index 0000000..ff3b1aa
--- /dev/null
+++ b/framework/tests/all-systems/Issue1587b.java
@@ -0,0 +1,33 @@
+abstract class Issue1587b {
+
+  static class One implements Two<One, Three, Four, Four> {}
+
+  interface Two<
+      E extends Two<E, K, C, I>, K extends Five<K>, C extends Enum<C>, I extends Enum<I>> {}
+
+  abstract static class Three implements Five<Three> {}
+
+  enum Four {}
+
+  interface Five<K extends Five<K>> extends Comparable<K> {}
+
+  interface Six {
+    <E extends Two<E, ?, ?, I>, I extends Enum<I>> Seven<?, E> e(Class<E> entity);
+  }
+
+  interface Seven<T extends Seven<T, E>, E extends Two<E, ?, ?, ?>> extends Eight<T, E, E> {}
+
+  interface Eight<T extends Eight<T, R, E>, R, E extends Two<E, ?, ?, ?>> extends Nine<T, R> {}
+
+  interface Nine<T extends Nine<T, R>, R> {
+    T d();
+
+    Iterable<R> q();
+  }
+
+  public Iterable<One> f(Six e) {
+    return g(e.e(One.class).d().q());
+  }
+
+  abstract Iterable<One> g(Iterable<One> r);
+}
diff --git a/framework/tests/all-systems/Issue1690.java b/framework/tests/all-systems/Issue1690.java
new file mode 100644
index 0000000..bf7ffe9
--- /dev/null
+++ b/framework/tests/all-systems/Issue1690.java
@@ -0,0 +1,14 @@
+// Test case for Issue 1690
+// https://github.com/typetools/checker-framework/issues/1690
+
+import java.io.Serializable;
+
+public class Issue1690<T extends Runnable & Serializable> {
+
+  public Issue1690() {}
+
+  // Can be an inner type or in its own file, shouldn't matter.
+  public static interface Issue16902<R extends Issue1690> {
+    public R getR();
+  }
+}
diff --git a/framework/tests/all-systems/Issue1696.java b/framework/tests/all-systems/Issue1696.java
new file mode 100644
index 0000000..289f1ff
--- /dev/null
+++ b/framework/tests/all-systems/Issue1696.java
@@ -0,0 +1,8 @@
+// Test case for Issue 1696:
+// https://github.com/typetools/checker-framework/issues/1696
+
+public class Issue1696 {
+  interface I<T extends I<T, S>, S> {}
+
+  void f(I<?, ?> x) {}
+}
diff --git a/framework/tests/all-systems/Issue1697.java b/framework/tests/all-systems/Issue1697.java
new file mode 100644
index 0000000..001bddf
--- /dev/null
+++ b/framework/tests/all-systems/Issue1697.java
@@ -0,0 +1,31 @@
+// Test case for Issue 1697:
+// https://github.com/typetools/checker-framework/issues/1697
+
+public class Issue1697 {
+
+  interface G<A> {
+    A h(byte[] l);
+  }
+
+  interface F {}
+
+  interface E extends F {
+    interface M extends F {}
+  }
+
+  abstract static class D<A extends D<A, B>, B extends D.M<A, B>> implements E {
+    abstract static class M<A extends D<A, B>, B extends M<A, B>> implements E.M {}
+  }
+
+  abstract static class C<A extends C<A, B>, B extends C.M<A, B>> extends D<A, B> {
+    abstract static class M<A extends C<A, B>, B extends M<A, B>> extends D.M<A, B> {}
+  }
+
+  static class W<T extends C<T, ?>> {
+    private W(T proto) {}
+  }
+
+  <T extends C<T, ?>> W<T> i(G<T> j, byte[] k) {
+    return new W<>(j.h(k));
+  }
+}
diff --git a/framework/tests/all-systems/Issue1698.java b/framework/tests/all-systems/Issue1698.java
new file mode 100644
index 0000000..ac75934
--- /dev/null
+++ b/framework/tests/all-systems/Issue1698.java
@@ -0,0 +1,12 @@
+// Test case for Issue 1698:
+// https://github.com/typetools/checker-framework/issues/1698
+
+public class Issue1698 {
+  static class B {
+    static C f() {
+      return new B().new C();
+    }
+
+    class C {}
+  }
+}
diff --git a/framework/tests/all-systems/Issue1708.java b/framework/tests/all-systems/Issue1708.java
new file mode 100644
index 0000000..33b6931
--- /dev/null
+++ b/framework/tests/all-systems/Issue1708.java
@@ -0,0 +1,42 @@
+// Test case for Issue 1708
+// https://github.com/typetools/checker-framework/issues/1708
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SuppressWarnings("unchecked")
+public class Issue1708 {
+
+  static class A<T extends B> {}
+
+  static class B<T1, T2> {}
+
+  static class C {}
+
+  static class D<T1 extends E, T2 extends F> {}
+
+  static class E<T1, T2 extends D> {}
+
+  static class F {}
+
+  static class B1 extends B<C, D> {}
+
+  static class B2 extends B<C, D> {}
+
+  static class B3 extends B<C, D> {}
+
+  public static class Example extends A<B<C, D>> {
+    private final List<B<C, D>> f;
+
+    public Example(B1 b1, B2 b2, B3 b3) {
+      f = ListUtil.of(b1, b2, b3);
+    }
+  }
+
+  public static class ListUtil {
+    public static <T> List<T> of(T... elems) {
+      return new ArrayList<>(Arrays.asList(elems));
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue1709.java b/framework/tests/all-systems/Issue1709.java
new file mode 100644
index 0000000..807817b
--- /dev/null
+++ b/framework/tests/all-systems/Issue1709.java
@@ -0,0 +1,11 @@
+// Testcase for Issue 1709
+// https://github.com/typetools/checker-framework/issues/1709
+
+import java.util.Iterator;
+import java.util.List;
+
+public class Issue1709 {
+  public static void m(final List<? super Integer> l) {
+    Iterator<? super Integer> it = l.iterator();
+  }
+}
diff --git a/framework/tests/all-systems/Issue1738.java b/framework/tests/all-systems/Issue1738.java
new file mode 100644
index 0000000..2e63033
--- /dev/null
+++ b/framework/tests/all-systems/Issue1738.java
@@ -0,0 +1,33 @@
+// Test case for Issue 1738:
+// https://github.com/typetools/checker-framework/issues/1738
+
+import java.util.Iterator;
+
+@SuppressWarnings("all") // Only check for crashes
+public class Issue1738 {
+  static class TwoParamIterator<T, R> implements Iterator<T> {
+    @Override
+    public boolean hasNext() {
+      return false;
+    }
+
+    @Override
+    public T next() {
+      return null;
+    }
+  }
+
+  static class TwoParamCollection<T, R> implements Iterable<T> {
+    @Override
+    public TwoParamIterator<T, R> iterator() {
+      return new TwoParamIterator<T, R>();
+    }
+  }
+
+  static void test() {
+    TwoParamCollection<String, String> c = new TwoParamCollection<>();
+    for (String s : c) {
+      s.hashCode();
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue1749.java b/framework/tests/all-systems/Issue1749.java
new file mode 100644
index 0000000..71efbc8
--- /dev/null
+++ b/framework/tests/all-systems/Issue1749.java
@@ -0,0 +1,16 @@
+// Testcase for Issue 1749
+// https://github.com/typetools/checker-framework/issues/1749
+abstract class Issue1749 {
+
+  public interface A {}
+
+  interface B extends A {}
+
+  public class I<X> {}
+
+  abstract <Y> I<Y> f(Class<? super Y> x);
+
+  void f() {
+    I<B> x = f(A.class);
+  }
+}
diff --git a/framework/tests/all-systems/Issue1809.java b/framework/tests/all-systems/Issue1809.java
new file mode 100644
index 0000000..7d2af47
--- /dev/null
+++ b/framework/tests/all-systems/Issue1809.java
@@ -0,0 +1,36 @@
+// Test case for Issue 1809:
+// https://github.com/typetools/checker-framework/issues/1809
+
+// Note that -AatfCacheSize=5 is required to exercise the problem.
+// This test is to ensure the basic code compiles.
+// For a reproduction of the issue, see checker/jtreg/nullness/Issue1809.java
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+@SuppressWarnings("unchecked")
+abstract class Issue1809 {
+
+  abstract <T> Stream<T> concat(Stream<? extends T>... streams);
+
+  abstract Optional<A> f();
+
+  private static class A {}
+
+  interface B {
+    List<C> g();
+  }
+
+  interface C {
+    List<S> h();
+  }
+
+  interface S {}
+
+  private Stream<A> xrefsFor(B b) {
+    return concat(b.g().stream().flatMap(a -> a.h().stream().map(c -> f())))
+        .filter(Optional::isPresent)
+        .map(Optional::get);
+  }
+}
diff --git a/framework/tests/all-systems/Issue1865.java b/framework/tests/all-systems/Issue1865.java
new file mode 100644
index 0000000..701c832
--- /dev/null
+++ b/framework/tests/all-systems/Issue1865.java
@@ -0,0 +1,23 @@
+// Test case for Issue 1865:
+// https://github.com/typetools/checker-framework/issues/1865
+
+abstract class Issue1865 {
+
+  // Widening conversion
+
+  abstract int f();
+
+  abstract int max(int... array);
+
+  void g() {
+    long l = max(f(), f());
+  }
+
+  // String conversion
+
+  abstract Object h(Object... args);
+
+  void i() {
+    Object o = "" + h();
+  }
+}
diff --git a/framework/tests/all-systems/Issue1867.java b/framework/tests/all-systems/Issue1867.java
new file mode 100644
index 0000000..6fbc00f
--- /dev/null
+++ b/framework/tests/all-systems/Issue1867.java
@@ -0,0 +1,20 @@
+// Test case for Issue 1867
+// https://github.com/typetools/checker-framework/issues/1867
+
+import java.util.List;
+
+public abstract class Issue1867 {
+  interface AInterface {}
+
+  interface BInterface<X extends AInterface> {
+    List<? extends X> g();
+  }
+
+  abstract List<? extends BInterface<? extends AInterface>> h();
+
+  void f() {
+    for (BInterface<? extends AInterface> x : h()) {
+      for (AInterface y : x.g()) {}
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue1920.java b/framework/tests/all-systems/Issue1920.java
new file mode 100644
index 0000000..3fa235f
--- /dev/null
+++ b/framework/tests/all-systems/Issue1920.java
@@ -0,0 +1,30 @@
+// Test case for Issue 1920:
+// https://github.com/typetools/checker-framework/issues/1920
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+@SuppressWarnings("all") // Only check for crashes
+public class Issue1920 {
+  static class Foo implements Iterable {
+    public Iterator iterator() {
+      return new Iterator() {
+        @Override
+        public boolean hasNext() {
+          return false;
+        }
+
+        @Override
+        public Object next() {
+          throw new NoSuchElementException();
+        }
+      };
+    }
+  }
+
+  static void testErasedIterator(Foo foo) {
+    for (Object x : foo) {
+      x.hashCode();
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue1948.java b/framework/tests/all-systems/Issue1948.java
new file mode 100644
index 0000000..9361526
--- /dev/null
+++ b/framework/tests/all-systems/Issue1948.java
@@ -0,0 +1,159 @@
+// Test case for Issue 1948:
+// https://github.com/typetools/checker-framework/issues/1948
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+@SuppressWarnings("all") // ensure no crash
+public class Issue1948<
+        K, V, E extends Issue1948.MyEntry<K, V, E>, S extends Issue1948.MyClass<K, V, E, S>>
+    implements ConcurrentMap<K, V> {
+
+  private Issue1948(MapMaker builder, InternalEntryHelper<K, V, E, S> entryHelper) {}
+
+  /** Returns a fresh {@link Issue1948} as specified by the given {@code builder}. */
+  static <K, V> Issue1948<K, V, ? extends MyEntry<K, V, ?>, ?> create(MapMaker builder) {
+    return new Issue1948<>(builder, Helper.<K, V>instance());
+  }
+
+  interface MyEntry<K, V, E extends MyEntry<K, V, E>> {}
+
+  abstract static class MyClass<K, V, E extends MyEntry<K, V, E>, S extends MyClass<K, V, E, S>> {}
+
+  static final class MapMaker {}
+
+  static final class Helper<K, V>
+      implements InternalEntryHelper<
+          K, V, StrongKeyStrongValueEntry<K, V>, StrongKeyStrongValueMyClass<K, V>> {
+    static <K, V> Helper<K, V> instance() {
+      return null;
+    }
+  }
+
+  interface InternalEntryHelper<K, V, E extends MyEntry<K, V, E>, S> {}
+
+  abstract static class StrongKeyStrongValueEntry<K, V>
+      extends AbstractStrongKeyEntry<K, V, StrongKeyStrongValueEntry<K, V>>
+      implements StrongValueEntry<K, V, StrongKeyStrongValueEntry<K, V>> {}
+
+  abstract static class AbstractStrongKeyEntry<K, V, E extends MyEntry<K, V, E>>
+      implements MyEntry<K, V, E> {}
+
+  interface StrongValueEntry<K, V, E extends MyEntry<K, V, E>> extends MyEntry<K, V, E> {}
+
+  abstract static class StrongKeyStrongValueMyClass<K, V>
+      extends MyClass<K, V, StrongKeyStrongValueEntry<K, V>, StrongKeyStrongValueMyClass<K, V>> {}
+
+  @Override
+  public int size() {
+    return 0;
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return false;
+  }
+
+  @Override
+  public boolean containsKey(Object key) {
+    return false;
+  }
+
+  @Override
+  public boolean containsValue(Object value) {
+    return false;
+  }
+
+  @Override
+  public V get(Object key) {
+    return null;
+  }
+
+  @Override
+  public V put(K key, V value) {
+    return null;
+  }
+
+  @Override
+  public V remove(Object key) {
+    return null;
+  }
+
+  @Override
+  public void putAll(Map<? extends K, ? extends V> m) {}
+
+  @Override
+  public void clear() {}
+
+  @Override
+  public Set<K> keySet() {
+    return null;
+  }
+
+  @Override
+  public Collection<V> values() {
+    return null;
+  }
+
+  @Override
+  public Set<Map.Entry<K, V>> entrySet() {
+    return null;
+  }
+
+  @Override
+  public V getOrDefault(Object key, V defaultValue) {
+    return null;
+  }
+
+  @Override
+  public void forEach(BiConsumer<? super K, ? super V> action) {}
+
+  @Override
+  public V putIfAbsent(K key, V value) {
+    return null;
+  }
+
+  @Override
+  public boolean remove(Object key, Object value) {
+    return false;
+  }
+
+  @Override
+  public boolean replace(K key, V oldValue, V newValue) {
+    return false;
+  }
+
+  @Override
+  public V replace(K key, V value) {
+    return null;
+  }
+
+  @Override
+  public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {}
+
+  @Override
+  public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+    return null;
+  }
+
+  @Override
+  public V computeIfPresent(
+      K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+    return null;
+  }
+
+  @Override
+  public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+    return null;
+  }
+
+  @Override
+  public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+    return null;
+  }
+}
diff --git a/framework/tests/all-systems/Issue1991.java b/framework/tests/all-systems/Issue1991.java
new file mode 100644
index 0000000..32208ae
--- /dev/null
+++ b/framework/tests/all-systems/Issue1991.java
@@ -0,0 +1,15 @@
+// Test case for Issue 1991:
+// https://github.com/typetools/checker-framework/issues/1991
+
+@SuppressWarnings("all") // Check for crashes only
+public class Issue1991 {
+  interface Comp<T extends Comp<T>> {}
+
+  interface C<X extends Comp<? super X>> {}
+
+  class D implements Comp<D> {}
+
+  void f(C<D> p) {
+    C<?> x = p;
+  }
+}
diff --git a/framework/tests/all-systems/Issue1991Full.java b/framework/tests/all-systems/Issue1991Full.java
new file mode 100644
index 0000000..9b89b24
--- /dev/null
+++ b/framework/tests/all-systems/Issue1991Full.java
@@ -0,0 +1,26 @@
+// Test case for Issue 1991:
+// https://github.com/typetools/checker-framework/issues/1991
+
+import java.io.Serializable;
+
+@SuppressWarnings("all") // Check for crashes only
+abstract class Issue1991Full {
+
+  abstract void g(A obj);
+
+  static class A {
+    A(C<?, ?> c) {}
+  }
+
+  interface B extends C<D, E> {}
+
+  interface C<X extends Comparable<? super X>, Y extends Serializable> {}
+
+  public class E implements Serializable {}
+
+  abstract static class D implements Comparable<D>, Serializable {}
+
+  void f(B b) {
+    g(new A(b));
+  }
+}
diff --git a/framework/tests/all-systems/Issue1992.java b/framework/tests/all-systems/Issue1992.java
new file mode 100644
index 0000000..0673d75
--- /dev/null
+++ b/framework/tests/all-systems/Issue1992.java
@@ -0,0 +1,29 @@
+// Test case for Issue 1992:
+// https://github.com/typetools/checker-framework/issues/1992
+
+import java.util.List;
+import java.util.function.Function;
+
+@SuppressWarnings("all") // Check for crashes only
+public class Issue1992 {
+
+  interface A {}
+
+  static class B<T extends A> {
+    C a;
+    T b;
+  }
+
+  static class C {
+    Function<? super A, E> c;
+
+    enum E {
+      NONE
+    }
+  }
+
+  boolean f(List<B<?>> x) {
+    B<?> d = x.get(x.size() - 1);
+    return d.a.c.apply(d.b) != C.E.NONE;
+  }
+}
diff --git a/framework/tests/all-systems/Issue2048.java b/framework/tests/all-systems/Issue2048.java
new file mode 100644
index 0000000..00aa40d
--- /dev/null
+++ b/framework/tests/all-systems/Issue2048.java
@@ -0,0 +1,17 @@
+// Test case for Issue #2048:
+// https://github.com/typetools/checker-framework/issues/2048
+//
+// There are two versions:
+// framework/tests/all-systems
+// checker/tests/nullness
+
+public class Issue2048 {
+  interface Foo {}
+
+  interface Fooer<R extends Foo> {}
+
+  class Use<T> {
+    @SuppressWarnings("all") // Check for crashes.
+    void foo(Fooer<? extends T> fooer) {}
+  }
+}
diff --git a/framework/tests/all-systems/Issue2082.java b/framework/tests/all-systems/Issue2082.java
new file mode 100644
index 0000000..380667a
--- /dev/null
+++ b/framework/tests/all-systems/Issue2082.java
@@ -0,0 +1,8 @@
+// Test case for Issue #2082:
+// https://github.com/typetools/checker-framework/issues/2082
+
+import java.util.concurrent.Callable;
+
+public class Issue2082 {
+  Callable foo = () -> 0;
+}
diff --git a/framework/tests/all-systems/Issue2088.java b/framework/tests/all-systems/Issue2088.java
new file mode 100644
index 0000000..1a1859f
--- /dev/null
+++ b/framework/tests/all-systems/Issue2088.java
@@ -0,0 +1,31 @@
+// Test case for Issue #2088:
+// https://github.com/typetools/checker-framework/issues/2088
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+@SuppressWarnings({"unchecked", "all"}) // Check for crashes only
+abstract class Issue2088 {
+
+  interface A<K extends Comparable<K>> {}
+
+  interface B extends A<Long> {}
+
+  interface C<P extends B, E extends B> {
+    interface F<T extends C<?, ?>> {}
+  }
+
+  interface D {}
+
+  static class Key<T> {
+    static Key<?> get(Type type) {
+      return null;
+    }
+  }
+
+  abstract ParameterizedType n(Type o, Class<?> r, Type... a);
+
+  <X extends B, Y extends C<?, X>, Z extends Y> void f(Class<Y> c) {
+    Key<C.F<Z>> f = (Key<C.F<Z>>) Key.get(n(C.class, C.F.class, c));
+  }
+}
diff --git a/framework/tests/all-systems/Issue2190.java b/framework/tests/all-systems/Issue2190.java
new file mode 100644
index 0000000..b75dc35
--- /dev/null
+++ b/framework/tests/all-systems/Issue2190.java
@@ -0,0 +1,24 @@
+// Test case for Issue 2190.
+public class Issue2190 {
+  interface A<X extends B> {}
+
+  abstract class B implements C {}
+
+  interface C {}
+
+  class D<T> {}
+
+  interface I<T> {
+    I<T> to(D<? extends T> x);
+  }
+
+  abstract class Z {
+    void f(I<A<?>> x, D<? extends A<? extends C>> y) {
+      x.to(y);
+    }
+
+    void g(I<A<? extends C>> x, D<? extends A<?>> y) {
+      x.to(y);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue2195.java b/framework/tests/all-systems/Issue2195.java
new file mode 100644
index 0000000..4bee9a2
--- /dev/null
+++ b/framework/tests/all-systems/Issue2195.java
@@ -0,0 +1,17 @@
+// Test case for Issue 2195.
+@SuppressWarnings("unchecked")
+public class Issue2195 {
+  interface A {}
+
+  interface B {}
+
+  class C<T extends B & A> {
+    C(T t) {}
+  }
+
+  class X {
+    X(B b) {
+      new C(b);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue2196.java b/framework/tests/all-systems/Issue2196.java
new file mode 100644
index 0000000..4b45057
--- /dev/null
+++ b/framework/tests/all-systems/Issue2196.java
@@ -0,0 +1,20 @@
+// Test case for Issue 2196.
+@SuppressWarnings("unchecked")
+public class Issue2196 {
+  interface A {}
+
+  interface B<V extends A, T> {}
+
+  interface C {}
+
+  abstract class X {
+
+    class D<T extends A> implements B<T, C> {}
+
+    abstract <T extends A> void f(B<T, Integer> b);
+
+    private void g() {
+      f(new D());
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue2198.java b/framework/tests/all-systems/Issue2198.java
new file mode 100644
index 0000000..6d6d20b
--- /dev/null
+++ b/framework/tests/all-systems/Issue2198.java
@@ -0,0 +1,17 @@
+// Test case for Issue 2198.
+@SuppressWarnings("unchecked")
+public class Issue2198 {
+  interface A {}
+
+  class B {}
+
+  class C<T extends B & A> {
+    C(T t) {}
+  }
+
+  class X {
+    X(B b) {
+      new C(b);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue2199.java b/framework/tests/all-systems/Issue2199.java
new file mode 100644
index 0000000..bcddf4b
--- /dev/null
+++ b/framework/tests/all-systems/Issue2199.java
@@ -0,0 +1,15 @@
+// Test case for Issue 2199.
+@SuppressWarnings("unchecked")
+public class Issue2199 {
+  static class StrangeConstructorTypeArgs<K, V> {
+    public StrangeConstructorTypeArgs(Abstract<String, byte[]> abs) {}
+  }
+
+  abstract static class Abstract<KEY, VALUE> {}
+
+  static class Concrete<K, V> extends Abstract<K, V> {}
+
+  static StrangeConstructorTypeArgs getStrangeConstructorTypeArgs() {
+    return new StrangeConstructorTypeArgs(new Concrete<>());
+  }
+}
diff --git a/framework/tests/all-systems/Issue2234.java b/framework/tests/all-systems/Issue2234.java
new file mode 100644
index 0000000..bc7c1be
--- /dev/null
+++ b/framework/tests/all-systems/Issue2234.java
@@ -0,0 +1,16 @@
+// Test case for Issue #2234:
+// https://github.com/typetools/checker-framework/issues/2234
+
+import java.util.LinkedList;
+import java.util.List;
+
+class Issue2234Super<T> {
+  Issue2234Super(List<Integer> p) {}
+}
+
+@SuppressWarnings("unchecked") // raw supertype
+class Issue2234Sub extends Issue2234Super {
+  Issue2234Sub() {
+    super(new LinkedList<>());
+  }
+}
diff --git a/framework/tests/all-systems/Issue2302.java b/framework/tests/all-systems/Issue2302.java
new file mode 100644
index 0000000..081a12f
--- /dev/null
+++ b/framework/tests/all-systems/Issue2302.java
@@ -0,0 +1,25 @@
+// Test case for Issue 2302
+// https://github.com/typetools/checker-framework/issues/2302
+
+@SuppressWarnings("unchecked")
+public class Issue2302 {
+  static class StrangeConstructorTypeArgs<V> {
+    // The constructor does not use the type parameter V.
+    public StrangeConstructorTypeArgs(MyClass<byte[]> abs) {}
+  }
+
+  static class MyClass<VALUE> {}
+
+  static StrangeConstructorTypeArgs getStrangeConstructorTypeArgs() {
+    // Crash with the diamond operator.
+    // Type inference chooses `Object` as the type argument.
+    // That is a bug, since it should choose exactly `byte[]`.
+    return new StrangeConstructorTypeArgs(new MyClass<>());
+
+    // No crash with an explicit type argument (no diamond operator), no matter what it is.
+    // return new StrangeConstructorTypeArgs(new MyClass<byte[]>());
+    // return new StrangeConstructorTypeArgs(new MyClass<Integer>());
+    // return new StrangeConstructorTypeArgs(new MyClass<Object>());
+    // return new StrangeConstructorTypeArgs(new MyClass<@Tainted Object>());
+  }
+}
diff --git a/framework/tests/all-systems/Issue2370.java b/framework/tests/all-systems/Issue2370.java
new file mode 100644
index 0000000..5fbc63f
--- /dev/null
+++ b/framework/tests/all-systems/Issue2370.java
@@ -0,0 +1,33 @@
+import java.util.Collection;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+@SuppressWarnings("all")
+public class Issue2370 {
+  private Stream<Action2370> getAction2370s(final State2370 state) {
+    return Stream.of(
+            toStream(state.getOnExit()).flatMap(t -> t.getAction2370s().stream()),
+            toStream(state.getOnSignal()).flatMap(t -> t.getAction2370s().stream()),
+            toStream(state.getOnEnter()).flatMap(t -> t.getAction2370s().stream()))
+        .flatMap(actionStream -> actionStream);
+  }
+
+  private <T> Stream<T> toStream(final Collection<T> obj) {
+    return Optional.ofNullable(obj)
+        .map(Stream::of)
+        .orElseGet(Stream::empty)
+        .flatMap(Collection::stream);
+  }
+}
+
+interface Action2370 {
+  public Collection<Action2370> getAction2370s();
+}
+
+interface State2370 {
+  public Collection<Action2370> getOnExit();
+
+  public Collection<Action2370> getOnSignal();
+
+  public Collection<Action2370> getOnEnter();
+}
diff --git a/framework/tests/all-systems/Issue2371.java b/framework/tests/all-systems/Issue2371.java
new file mode 100644
index 0000000..9abe318
--- /dev/null
+++ b/framework/tests/all-systems/Issue2371.java
@@ -0,0 +1,8 @@
+@SuppressWarnings("all")
+public class Issue2371<T extends Issue2371<T>> {
+  void method(Issue2371<? extends Object> i) {
+    other(i);
+  }
+
+  void other(Issue2371<?> e) {}
+}
diff --git a/framework/tests/all-systems/Issue2446.java b/framework/tests/all-systems/Issue2446.java
new file mode 100644
index 0000000..99ebd21
--- /dev/null
+++ b/framework/tests/all-systems/Issue2446.java
@@ -0,0 +1,13 @@
+public class Issue2446 {
+  static class One<T, V> {}
+
+  static class Two<V, B extends Two<V, B>> extends One<V, B> {}
+
+  static class Three<V, B extends Three<V, B>> extends Two<V, B> {}
+
+  static <B extends Three<Object, B>> Three<Object, ?> f() {
+    throw new AssertionError();
+  }
+
+  static final Three<?, ?> F = f();
+}
diff --git a/framework/tests/all-systems/Issue2480.java b/framework/tests/all-systems/Issue2480.java
new file mode 100644
index 0000000..c1b65d9
--- /dev/null
+++ b/framework/tests/all-systems/Issue2480.java
@@ -0,0 +1,13 @@
+// Test case for Issue 2480:
+// https://github.com/typetools/checker-framework/issues/2480
+
+import java.util.List;
+
+@SuppressWarnings({"unchecked", "all"}) // check for crashes only
+abstract class Issue2480 {
+  void testCase() {
+    for (Class<?> wrapperType : of(Character.class, Boolean.class)) {}
+  }
+
+  abstract <E> List<E> of(E... e1);
+}
diff --git a/framework/tests/all-systems/Issue263.java b/framework/tests/all-systems/Issue263.java
new file mode 100644
index 0000000..121f24b
--- /dev/null
+++ b/framework/tests/all-systems/Issue263.java
@@ -0,0 +1,31 @@
+// Test case for Issue 263:
+// https://github.com/typetools/checker-framework/issues/263
+
+abstract class Outer<T> {
+
+  public class Inner {
+    private T t;
+
+    public Inner(T t) {
+      this.t = t;
+    }
+
+    T get() {
+      return t;
+    }
+  }
+
+  public abstract Inner getInner();
+}
+
+public class Issue263 {
+  public Issue263(Outer<String> outer) {
+    this.outer = outer;
+  }
+
+  Outer<String> outer;
+
+  public void context() {
+    String s = outer.getInner().get();
+  }
+}
diff --git a/framework/tests/all-systems/Issue2678.java b/framework/tests/all-systems/Issue2678.java
new file mode 100644
index 0000000..2fffc3a
--- /dev/null
+++ b/framework/tests/all-systems/Issue2678.java
@@ -0,0 +1,6 @@
+public class Issue2678 {
+  @SuppressWarnings({"index:array.access.unsafe.low", "index:array.access.unsafe.high"})
+  public synchronized void incrementPushed(long[] pushed, int operationType) {
+    ++(pushed[operationType]);
+  }
+}
diff --git a/framework/tests/all-systems/Issue2717.java b/framework/tests/all-systems/Issue2717.java
new file mode 100644
index 0000000..573faad
--- /dev/null
+++ b/framework/tests/all-systems/Issue2717.java
@@ -0,0 +1,26 @@
+@SuppressWarnings("all")
+public class Issue2717 {
+
+  interface Tree {}
+
+  interface ExpressionTree extends Tree {}
+
+  interface BinaryTree extends ExpressionTree {}
+
+  interface Matcher<T extends Tree> {}
+
+  public static void test(Matcher<? super ExpressionTree> m) {}
+
+  public static void caller() {
+    test(toType(BinaryTree.class, Issue2717.<BinaryTree>allOf()));
+  }
+
+  public static <T extends Tree> Matcher<T> allOf() {
+    return null;
+  }
+
+  public static <S extends T, T extends Tree> Matcher<T> toType(
+      Class<S> type, Matcher<? super S> matcher) {
+    return null;
+  }
+}
diff --git a/framework/tests/all-systems/Issue2739.java b/framework/tests/all-systems/Issue2739.java
new file mode 100644
index 0000000..ddacf43
--- /dev/null
+++ b/framework/tests/all-systems/Issue2739.java
@@ -0,0 +1,9 @@
+public class Issue2739<E extends Object & Issue2739.EppEnum> {
+  public interface EppEnum {
+    String getXmlName();
+  }
+
+  void method(E e) {
+    String s = e.getXmlName();
+  }
+}
diff --git a/framework/tests/all-systems/Issue2779.java b/framework/tests/all-systems/Issue2779.java
new file mode 100644
index 0000000..e61cd02
--- /dev/null
+++ b/framework/tests/all-systems/Issue2779.java
@@ -0,0 +1,26 @@
+// Test case for Issue 2779
+// https://github.com/typetools/checker-framework/issues/2779
+
+// @below-java9-jdk-skip-test
+@SuppressWarnings("all") // Just check for crashes.
+interface Issue2779<S> {
+  S get();
+
+  static <T> Issue2779<T> wrap2(T val) {
+    return new Issue2779<T>() {
+      @Override
+      public T get() {
+        return val;
+      }
+    };
+  }
+
+  static <T> Issue2779<T> wrap(T val) {
+    return new Issue2779<>() {
+      @Override
+      public T get() {
+        return val;
+      }
+    };
+  }
+}
diff --git a/framework/tests/all-systems/Issue2781.java b/framework/tests/all-systems/Issue2781.java
new file mode 100644
index 0000000..5213c82
--- /dev/null
+++ b/framework/tests/all-systems/Issue2781.java
@@ -0,0 +1,22 @@
+// Test case for Issue 2781:
+// https://github.com/typetools/checker-framework/issues/2781
+
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+public class Issue2781 {
+  class Wrapper<T> {
+    Wrapper(T t) {}
+  }
+
+  Stream<Wrapper<Function<String, String>>> getStreamOfWrappedFunctions1() {
+    // inferred type in new
+    return Stream.<Wrapper<Function<String, String>>>of(new Wrapper<>(e -> e));
+  }
+
+  Stream<Wrapper<Function<String, String>>> getStreamOfWrappedFunctions2() {
+    // explicit type in new
+    return Stream.<Wrapper<Function<String, String>>>of(
+        new Wrapper<Function<String, String>>(e -> e));
+  }
+}
diff --git a/framework/tests/all-systems/Issue301.java b/framework/tests/all-systems/Issue301.java
new file mode 100644
index 0000000..f77023e
--- /dev/null
+++ b/framework/tests/all-systems/Issue301.java
@@ -0,0 +1,1463 @@
+// Test case for Issue 301:
+// https://github.com/typetools/checker-framework/issues/301
+public class Issue301 {
+
+  {
+    java.util.Vector<Object> v = new java.util.Vector<Object>();
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+    v.add(1.0);
+  }
+}
diff --git a/framework/tests/all-systems/Issue3021.java b/framework/tests/all-systems/Issue3021.java
new file mode 100644
index 0000000..c8ad7b0
--- /dev/null
+++ b/framework/tests/all-systems/Issue3021.java
@@ -0,0 +1,13 @@
+// Test case for Issue 3021:
+// https://github.com/typetools/checker-framework/issues/3021
+
+// Any arbitrary annotation can be used.
+import org.checkerframework.common.aliasing.qual.MaybeAliased;
+
+public class Issue3021 {
+  <T> void make() {
+    new Lib<@MaybeAliased T>() {};
+  }
+
+  class Lib<T> {}
+}
diff --git a/framework/tests/all-systems/Issue3055.java b/framework/tests/all-systems/Issue3055.java
new file mode 100644
index 0000000..d3e0078
--- /dev/null
+++ b/framework/tests/all-systems/Issue3055.java
@@ -0,0 +1,14 @@
+public class Issue3055 {
+
+  class C1<T extends C1<T>.Bound> {
+    class Bound {}
+  }
+
+  class C2<T extends C2.Bound> {
+    class Bound {}
+  }
+
+  class C3<T extends C3<?>.Bound> {
+    class Bound {}
+  }
+}
diff --git a/framework/tests/all-systems/Issue3120.java b/framework/tests/all-systems/Issue3120.java
new file mode 100644
index 0000000..2a623d8
--- /dev/null
+++ b/framework/tests/all-systems/Issue3120.java
@@ -0,0 +1,22 @@
+@SuppressWarnings("all") // Ensure no crashes
+public class Issue3120 {
+  CharSequence foo() {
+    return bar();
+  }
+
+  <T extends Enum<T>> CharSequence bar() {
+    return null;
+  }
+
+  CharSequence foo0() {
+    return bar0(null);
+  }
+
+  <EnumT extends Enum<EnumT> & AnotherType> CharSequence bar0(SomeType<?> type) {
+    return null;
+  }
+
+  class SomeType<T> {}
+
+  interface AnotherType {}
+}
diff --git a/framework/tests/all-systems/Issue3128.java b/framework/tests/all-systems/Issue3128.java
new file mode 100644
index 0000000..fd72ec1
--- /dev/null
+++ b/framework/tests/all-systems/Issue3128.java
@@ -0,0 +1,12 @@
+// Test case for Issue 3128:
+// https://github.com/typetools/checker-framework/issues/3128
+
+public class Issue3128 {
+  class One<S> {}
+
+  class Two<U extends Number, V extends One<U>> {}
+
+  One<Two<?, ?>> foo() {
+    return new One<>();
+  }
+}
diff --git a/framework/tests/all-systems/Issue3232.java b/framework/tests/all-systems/Issue3232.java
new file mode 100644
index 0000000..be29842
--- /dev/null
+++ b/framework/tests/all-systems/Issue3232.java
@@ -0,0 +1,13 @@
+// Test case for Issue 3232:
+// https://github.com/typetools/checker-framework/issues/3232
+
+class Issue3232A<B> {
+  @SuppressWarnings("unchecked")
+  void foo(B... values) {}
+}
+
+class Issue3232C extends Issue3232A<Integer> {
+  void bar(int value) {
+    foo(value);
+  }
+}
diff --git a/framework/tests/all-systems/Issue3277.java b/framework/tests/all-systems/Issue3277.java
new file mode 100644
index 0000000..877eb24
--- /dev/null
+++ b/framework/tests/all-systems/Issue3277.java
@@ -0,0 +1,19 @@
+// Test case for Issue 3277:
+// https://github.com/typetools/checker-framework/issues/3277
+
+// Any arbitrary annotation can be used.
+import org.checkerframework.common.aliasing.qual.MaybeAliased;
+
+public class Issue3277 {
+  <T> void f() {
+    Object o = new @MaybeAliased Generic<?>[0];
+    o = new Generic<@MaybeAliased ?>[0];
+  }
+
+  // TODO: Having the same code in fields crashes javac
+  // with an AssertionError.
+  // Object o1 = new @MaybeAliased Generic<?>[0];
+  // Object o2 = new Generic<@MaybeAliased ?>[0];
+
+  class Generic<U> {}
+}
diff --git a/framework/tests/all-systems/Issue3295.java b/framework/tests/all-systems/Issue3295.java
new file mode 100644
index 0000000..991f812
--- /dev/null
+++ b/framework/tests/all-systems/Issue3295.java
@@ -0,0 +1,17 @@
+import java.util.List;
+
+public class Issue3295 {
+  interface P<T> {
+
+    Class<? super T> h();
+
+    List<P<? super T>> g();
+  }
+
+  interface Q {}
+
+  @SuppressWarnings("interning:unnecessary.equals") // This warning is expected.
+  static void f(P<? extends Q> t) {
+    t.g().stream().filter(x -> x.h().equals(Q.class));
+  }
+}
diff --git a/framework/tests/all-systems/Issue3302.java b/framework/tests/all-systems/Issue3302.java
new file mode 100644
index 0000000..fa1bbf4
--- /dev/null
+++ b/framework/tests/all-systems/Issue3302.java
@@ -0,0 +1,12 @@
+// Test case for Issue 3302:
+// https://github.com/typetools/checker-framework/issues/3302
+
+public class Issue3302 {
+  void foo(Bar<?, ?> b) {}
+
+  interface Bar<S, T extends Box<S> & A> {}
+
+  interface A {}
+
+  interface Box<U> {}
+}
diff --git a/framework/tests/all-systems/Issue3377.java b/framework/tests/all-systems/Issue3377.java
new file mode 100644
index 0000000..3022ae4
--- /dev/null
+++ b/framework/tests/all-systems/Issue3377.java
@@ -0,0 +1,15 @@
+// @below-java11-jdk-skip-test
+@SuppressWarnings("all") // Check for crashes.
+public class Issue3377 {
+  static class Box<S> {}
+
+  interface Unboxer {
+    <T> T unbox(Box<T> p);
+  }
+
+  static class Crash {
+    Box<String> crash(Unboxer ub) {
+      return ub.unbox(new Box<>() {});
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue3569.java b/framework/tests/all-systems/Issue3569.java
new file mode 100644
index 0000000..211e75d
--- /dev/null
+++ b/framework/tests/all-systems/Issue3569.java
@@ -0,0 +1,9 @@
+public abstract class Issue3569 {
+  public interface MyInterface {}
+
+  public abstract <T extends MyInterface> T getT();
+
+  protected <K> K getK(Issue3569 ab) {
+    return ab.getT();
+  }
+}
diff --git a/framework/tests/all-systems/Issue3570.java b/framework/tests/all-systems/Issue3570.java
new file mode 100644
index 0000000..c64cbe4
--- /dev/null
+++ b/framework/tests/all-systems/Issue3570.java
@@ -0,0 +1,63 @@
+@SuppressWarnings("all") // Check for crashes.
+public class Issue3570 {
+  public interface Freezable<T extends Freezable> extends Cloneable {}
+
+  public static final class Key<T extends Keyed> extends Iced<Key<T>> implements Comparable {
+    @Override
+    public int compareTo(Object o) {
+      return 0;
+    }
+  }
+
+  public abstract static class Keyed<T extends Keyed> extends Iced<T> {}
+
+  public abstract static class Lockable<T extends Lockable<T>> extends Keyed<T> {}
+
+  public abstract static class Iced<D extends Iced>
+      implements Freezable<D>, java.io.Externalizable {
+    @Override
+    public void readExternal(java.io.ObjectInput ois)
+        throws java.io.IOException, ClassNotFoundException {}
+
+    @Override
+    public void writeExternal(java.io.ObjectOutput oos) throws java.io.IOException {}
+  }
+
+  public abstract static class Model<
+          M extends Model<M, P, O>, P extends Model.Parameters, O extends Model.Output>
+      extends Lockable<M> {
+    public P _parms;
+
+    public abstract static class Parameters extends Iced<Parameters> {
+      public Key<Frame> _train;
+    }
+
+    public abstract static class Output extends Iced {}
+  }
+
+  public static class Frame extends Lockable<Frame> {}
+
+  public abstract static class Schema<I extends Iced, S extends Schema<I, S>> extends Iced {}
+
+  public static class SchemaV3<I extends Iced, S extends SchemaV3<I, S>> extends Schema<I, S> {}
+
+  public static class KeyV3<I extends Iced, S extends KeyV3<I, S, K>, K extends Keyed>
+      extends SchemaV3<I, KeyV3<I, S, K>> {
+    public KeyV3(Key key) {}
+
+    public static class FrameKeyV3 extends KeyV3<Iced, FrameKeyV3, Frame> {
+      public FrameKeyV3(Key<Frame> key) {
+        super(key);
+      }
+    }
+  }
+
+  public static class ModelSchemaBaseV3<M extends Model<M, ?, ?>, S extends ModelSchemaBaseV3<M, S>>
+      extends SchemaV3<M, S> {
+    public KeyV3.FrameKeyV3 data_frame;
+
+    public ModelSchemaBaseV3(M m) {
+      this.data_frame = new KeyV3.FrameKeyV3(m._parms._train);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue3598.java b/framework/tests/all-systems/Issue3598.java
new file mode 100644
index 0000000..e7e06d1
--- /dev/null
+++ b/framework/tests/all-systems/Issue3598.java
@@ -0,0 +1,25 @@
+import java.util.function.Function;
+
+public class Issue3598 {
+
+  static class DClass extends EClass {}
+
+  static class EClass<F> {}
+
+  // Must be Function, can't use interface defined in this class.
+  static class XClass<P> implements Function<P, P> {
+
+    @Override
+    public P apply(P protoT) {
+      return protoT;
+    }
+
+    // DClass extends a raw class.
+    static Function<DClass, DClass> f(DClass k) {
+      // Crash on this line.
+      return new XClass<>(k);
+    }
+
+    XClass(P p) {}
+  }
+}
diff --git a/framework/tests/all-systems/Issue3826.java b/framework/tests/all-systems/Issue3826.java
new file mode 100644
index 0000000..3f38c90
--- /dev/null
+++ b/framework/tests/all-systems/Issue3826.java
@@ -0,0 +1,27 @@
+public class Issue3826 {
+  public static <B, T extends B> void getOption(Class<T> cls, B[] opts) {}
+
+  public static class ClassA {
+    public static class InnerClassA {
+      interface InnerInnerClassA {}
+    }
+  }
+
+  public static class ClassB {
+    public abstract static class InnerClassB {}
+  }
+
+  public static class ClassC {
+    interface InterfaceClassC extends ClassA.InnerClassA.InnerInnerClassA {}
+
+    private static class InnerClassC extends ClassA.InnerClassA implements InterfaceClassC {}
+
+    public ClassC(ClassA.InnerClassA.InnerInnerClassA... opts) {
+      // Does not crash
+      Issue3826.<ClassA.InnerClassA.InnerInnerClassA, InnerClassC>getOption(
+          InnerClassC.class, opts);
+      // Crashes
+      Issue3826.getOption(InnerClassC.class, opts);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue392.java b/framework/tests/all-systems/Issue392.java
new file mode 100644
index 0000000..385c526
--- /dev/null
+++ b/framework/tests/all-systems/Issue392.java
@@ -0,0 +1,9 @@
+// Test case for Issue 392:
+// https://github.com/typetools/checker-framework/issues/392
+
+public class Issue392<T> {
+
+  public <T> void getFields(T t) {
+    Object o = new Object[] {t, t};
+  }
+}
diff --git a/framework/tests/all-systems/Issue3929.java b/framework/tests/all-systems/Issue3929.java
new file mode 100644
index 0000000..9eec1ff
--- /dev/null
+++ b/framework/tests/all-systems/Issue3929.java
@@ -0,0 +1,15 @@
+import java.util.ArrayList;
+import java.util.List;
+
+public class Issue3929 {
+
+  public void endElement(DefaultKeyedValues3929 arg) {
+    for (Object o : arg.getKeys()) {}
+  }
+}
+
+class DefaultKeyedValues3929<K extends Comparable<K>> {
+  public List<K> getKeys() {
+    return new ArrayList<>();
+  }
+}
diff --git a/framework/tests/all-systems/Issue393.java b/framework/tests/all-systems/Issue393.java
new file mode 100644
index 0000000..436a033
--- /dev/null
+++ b/framework/tests/all-systems/Issue393.java
@@ -0,0 +1,11 @@
+// Test case for Issue 393
+// https://github.com/typetools/checker-framework/issues/393
+
+abstract class TypeVarTaintCheck {
+
+  void test() {
+    wrap(new Object());
+  }
+
+  abstract <T, U extends T> void wrap(U u);
+}
diff --git a/framework/tests/all-systems/Issue395.java b/framework/tests/all-systems/Issue395.java
new file mode 100644
index 0000000..9fa8bb5
--- /dev/null
+++ b/framework/tests/all-systems/Issue395.java
@@ -0,0 +1,11 @@
+// Test case for Issue 395:
+// https://github.com/typetools/checker-framework/issues/395
+
+import java.util.ArrayList;
+
+public class Issue395 {
+
+  Object[] testMethod() {
+    return new Object[] {new ArrayList<>()};
+  }
+}
diff --git a/framework/tests/all-systems/Issue396.java b/framework/tests/all-systems/Issue396.java
new file mode 100644
index 0000000..64b1f98
--- /dev/null
+++ b/framework/tests/all-systems/Issue396.java
@@ -0,0 +1,11 @@
+// Test case for Issue 396:
+// https://github.com/typetools/checker-framework/issues/396
+public class Issue396 {
+  void b() {
+    try {
+
+    } catch (LinkageError | AssertionError e) {
+      throw e;
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Issue3994.java b/framework/tests/all-systems/Issue3994.java
new file mode 100644
index 0000000..c91c07a
--- /dev/null
+++ b/framework/tests/all-systems/Issue3994.java
@@ -0,0 +1,7 @@
+public class Issue3994 {
+  interface MyInterface {}
+
+  interface OkRecursive<T extends OkRecursive> {}
+
+  interface Recursive<T extends MyInterface & Recursive> {}
+}
diff --git a/framework/tests/all-systems/Issue4083.java b/framework/tests/all-systems/Issue4083.java
new file mode 100644
index 0000000..d5aaa2b
--- /dev/null
+++ b/framework/tests/all-systems/Issue4083.java
@@ -0,0 +1,16 @@
+@SuppressWarnings("all") // Just check for crashes.
+abstract class Issue4083 {
+  abstract void use(Predicate<Annotation> predicate);
+
+  void go() {
+    use(Annotation::b);
+  }
+
+  @interface Annotation {
+    boolean b() default false;
+  }
+
+  interface Predicate<T> {
+    boolean apply(T t);
+  }
+}
diff --git a/framework/tests/all-systems/Issue4115.java b/framework/tests/all-systems/Issue4115.java
new file mode 100644
index 0000000..ac21d3f
--- /dev/null
+++ b/framework/tests/all-systems/Issue4115.java
@@ -0,0 +1,15 @@
+import java.util.List;
+import java.util.function.Function;
+
+abstract class Issue4115 {
+
+  interface V {}
+
+  abstract <F, T> Iterable<T> transform(Iterable<F> i, Function<? super F, ? extends T> f);
+
+  abstract <E> List<E> copyOf(Iterable<? extends E> e);
+
+  List<V> generateAppSuggestions(List<Integer> xs) {
+    return copyOf(transform(xs, x -> new V() {}));
+  }
+}
diff --git a/framework/tests/all-systems/Issue437.java b/framework/tests/all-systems/Issue437.java
new file mode 100644
index 0000000..f0dd4d0
--- /dev/null
+++ b/framework/tests/all-systems/Issue437.java
@@ -0,0 +1,28 @@
+// Test case for Issue 437:
+// https://github.com/typetools/checker-framework/issues/437
+
+abstract class I437Bar<T> {
+  private final T t;
+
+  class Norf {
+    T getT() {
+      return t;
+    }
+  }
+
+  I437Bar(T t) {
+    this.t = t;
+  }
+
+  abstract void quux(Norf norf);
+}
+
+class I437Foo extends I437Bar<Integer> {
+  I437Foo(Integer i) {
+    super(i);
+  }
+
+  void quux(Norf norf) {
+    Integer i = norf.getT();
+  }
+}
diff --git a/framework/tests/all-systems/Issue438.java b/framework/tests/all-systems/Issue438.java
new file mode 100644
index 0000000..9f7707e
--- /dev/null
+++ b/framework/tests/all-systems/Issue438.java
@@ -0,0 +1,19 @@
+// Test case for Issue 438:
+// https://github.com/typetools/checker-framework/issues/438
+
+import java.util.HashSet;
+import java.util.List;
+
+public class Issue438 {
+  boolean foo(List<String> list) {
+    if (list.isEmpty()) {
+      return new HashSet<>(list).isEmpty();
+    } else {
+      return new HashSet<>(list).contains("test");
+    }
+  }
+
+  int bar(List<String> list) {
+    return new HashSet<>(list).size();
+  }
+}
diff --git a/framework/tests/all-systems/Issue4384.java b/framework/tests/all-systems/Issue4384.java
new file mode 100644
index 0000000..4abb436
--- /dev/null
+++ b/framework/tests/all-systems/Issue4384.java
@@ -0,0 +1,21 @@
+public abstract class Issue4384 {
+
+  interface A<T extends B<T>> extends C<T>, D<T> {}
+
+  interface C<T> {}
+
+  interface D<T extends E<T>> {
+    void f();
+  }
+
+  interface B<T extends B<T>> extends E<T> {}
+
+  interface E<T extends E<T>> {}
+
+  void g(A<?> t) {
+    t.f();
+    h(t);
+  }
+
+  abstract void h(A<? extends B<?>> t);
+}
diff --git a/framework/tests/all-systems/Issue457.java b/framework/tests/all-systems/Issue457.java
new file mode 100644
index 0000000..f20634e
--- /dev/null
+++ b/framework/tests/all-systems/Issue457.java
@@ -0,0 +1,15 @@
+// See gist: https://gist.github.com/JonathanBurke/6c1c1c28161a451611ad
+// for more information on what was going wrong here
+public class Issue457<T extends Number> {
+
+  @SuppressWarnings("unused")
+  public void f(T t) {
+    final T obj = t;
+
+    @SuppressWarnings("signedness:assignment") // cast
+    Float objFloat = (obj instanceof Float) ? (Float) obj : null;
+
+    // An error will be emitted on this line before the fix for Issue457
+    t = obj;
+  }
+}
diff --git a/framework/tests/all-systems/Issue478.java b/framework/tests/all-systems/Issue478.java
new file mode 100644
index 0000000..81c7ae4
--- /dev/null
+++ b/framework/tests/all-systems/Issue478.java
@@ -0,0 +1,11 @@
+// Test case for Issue 478:
+// https://github.com/typetools/checker-framework/issues/478
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+public class Issue478 {
+  public static Comparator<Object> allTheSame() {
+    return (Comparator<Object> & Serializable) (c1, c2) -> 0;
+  }
+}
diff --git a/framework/tests/all-systems/Issue577.java b/framework/tests/all-systems/Issue577.java
new file mode 100644
index 0000000..1bf46ae
--- /dev/null
+++ b/framework/tests/all-systems/Issue577.java
@@ -0,0 +1,75 @@
+// Test case for issue #577:
+// Test with expected errors: checker/tests/nullness/Issue577.java
+// https://github.com/typetools/checker-framework/issues/577
+class Banana<T extends Number> extends Apple<int[]> {
+  @Override
+  void fooIssue577Outer(int[] array) {}
+
+  class InnerBanana extends InnerApple<long[]> {
+    @Override
+    <F2> void foo(int[] array, long[] array2, F2 param3) {}
+  }
+}
+
+class Apple<T> {
+  void fooIssue577Outer(T param) {}
+
+  class InnerApple<E> {
+    <F> void foo(T param, E param2, F param3) {}
+  }
+}
+
+class Pineapple<E> extends Apple<E> {
+  @Override
+  void fooIssue577Outer(E array) {}
+}
+
+class IntersectionAsMemberOf {
+  interface MyGenericInterface<F> {
+    F getF();
+  }
+
+  <T extends Object & MyGenericInterface<String>> void foo(T param) {
+    String s = param.getF();
+  }
+}
+
+class UnionAsMemberOf {
+  interface MyInterface<T> {
+    T getT();
+  }
+
+  class MyExceptionA extends Throwable implements Cloneable, MyInterface<String> {
+    public String getT() {
+      return "t";
+    }
+  }
+
+  class MyExceptionB extends Throwable implements Cloneable, MyInterface<String> {
+    public String getT() {
+      return "t";
+    }
+  }
+
+  void bar() throws MyExceptionA, MyExceptionB {}
+
+  void foo1(MyInterface<Throwable> param) throws Throwable {
+    try {
+      bar();
+    } catch (MyExceptionA | MyExceptionB ex1) {
+      String s = ex1.getT();
+    }
+  }
+}
+
+final class Issue577Outer<K extends Object> {
+  private Inner createInner(ReferenceQueue<? super K> q) {
+    return new Inner(q);
+  }
+
+  private final class Inner {
+    private Inner(ReferenceQueue<? super K> q) {}
+  }
+}
+
+class ReferenceQueue<T> {}
diff --git a/framework/tests/all-systems/Issue671.java b/framework/tests/all-systems/Issue671.java
new file mode 100644
index 0000000..2308aaf
--- /dev/null
+++ b/framework/tests/all-systems/Issue671.java
@@ -0,0 +1,13 @@
+// Test case for Issue #671
+// https://github.com/typetools/checker-framework/issues/671
+public class Issue671 {
+
+  void foo() {
+    byte var = 0;
+    boolean f = (var == (method() ? 2 : 0));
+  }
+
+  boolean method() {
+    return false;
+  }
+}
diff --git a/framework/tests/all-systems/Issue689.java b/framework/tests/all-systems/Issue689.java
new file mode 100644
index 0000000..cb713b1
--- /dev/null
+++ b/framework/tests/all-systems/Issue689.java
@@ -0,0 +1,20 @@
+// Test case for issue 689
+// https://github.com/typetools/checker-framework/issues/689
+public class Issue689 {
+
+  public int initializerFodder() {
+    return 3;
+  }
+
+  public Runnable trigger() {
+    return new Runnable() {
+      // Issue 689 triggers when examining a method invocation inside a field initializer of
+      // an anonymous inner class
+      public final int val = initializerFodder();
+
+      public void run() {
+        // do nothing
+      }
+    };
+  }
+}
diff --git a/framework/tests/all-systems/Issue691.java b/framework/tests/all-systems/Issue691.java
new file mode 100644
index 0000000..28a3143
--- /dev/null
+++ b/framework/tests/all-systems/Issue691.java
@@ -0,0 +1,12 @@
+// Test case for Issue 691:
+// https://github.com/typetools/checker-framework/issues/691
+
+interface MyInterface<T> {}
+
+// This code causes greatestLowerBound in the qualifier hierarchy to be executed, which results
+// in a crash if the default implementation isn't correct for a given checker. A checker could
+// issue a valid type checking error for this code, so suppress any warnings.
+@SuppressWarnings("all")
+public class Issue691<T> implements MyInterface<T> {
+  MyInterface<?> mi = new Issue691<>();
+}
diff --git a/framework/tests/all-systems/Issue692.java b/framework/tests/all-systems/Issue692.java
new file mode 100644
index 0000000..0d5a2b1
--- /dev/null
+++ b/framework/tests/all-systems/Issue692.java
@@ -0,0 +1,9 @@
+// Test case for #692
+// https://github.com/typetools/checker-framework/issues/692
+public class Issue692<T extends Enum<T>> {
+
+  private boolean method(Object param, Class<T> tClass) {
+    Class<?> paramClass = param.getClass();
+    return paramClass == tClass || paramClass.getSuperclass() == tClass;
+  }
+}
diff --git a/framework/tests/all-systems/Issue696.java b/framework/tests/all-systems/Issue696.java
new file mode 100644
index 0000000..a9529f5
--- /dev/null
+++ b/framework/tests/all-systems/Issue696.java
@@ -0,0 +1,9 @@
+// Testcase for Issue 696
+// https://github.com/typetools/checker-framework/issues/696
+
+import java.util.List;
+import java.util.Map;
+
+public class Issue696 {
+  public static void test(final List<? extends Map.Entry<byte[], byte[]>> input) {}
+}
diff --git a/framework/tests/all-systems/Issue717.java b/framework/tests/all-systems/Issue717.java
new file mode 100644
index 0000000..fdb7f6d
--- /dev/null
+++ b/framework/tests/all-systems/Issue717.java
@@ -0,0 +1,18 @@
+// A test case that should not longer crash once issue #717 is fixed
+// https://github.com/typetools/checker-framework/issues/717
+public class Issue717 {
+
+  public static <T extends Interface<? super T>> void foo2(T a, T b) {
+    a.compareTo(b);
+  }
+
+  public static <T extends Object & Interface<? super T>> void foo(T a, T b) {
+    // asSuper doesn't find Interface, so the type variable F is not substituted
+    // causing isSuptype to be called between Object & Interface and F.
+    a.compareTo(b);
+  }
+
+  interface Interface<F> {
+    void compareTo(F t);
+  }
+}
diff --git a/framework/tests/all-systems/Issue738.java b/framework/tests/all-systems/Issue738.java
new file mode 100644
index 0000000..499a68a
--- /dev/null
+++ b/framework/tests/all-systems/Issue738.java
@@ -0,0 +1,14 @@
+// Testcase for #738
+// https://github.com/typetools/checker-framework/issues/738
+// Also, see checker/tests/nullness/Issue738.java
+@SuppressWarnings("all") // This testcase is checking for crashes.
+public class Issue738 {
+  public static void methodA() {
+    methodB(0, new Object()); // This compiles fine.
+    methodB(new int[0], new Object[0]); // This crashes.
+  }
+
+  private static <T> void methodB(T paramA, T paramB) {
+    // Do nothing.
+  }
+}
diff --git a/framework/tests/all-systems/Issue759.java b/framework/tests/all-systems/Issue759.java
new file mode 100644
index 0000000..13f89fa
--- /dev/null
+++ b/framework/tests/all-systems/Issue759.java
@@ -0,0 +1,32 @@
+// Testcase for Issue759
+// https://github.com/typetools/checker-framework/issues/759
+@SuppressWarnings({"nullness", "unchecked"}) // See checker/test/nullness/Issue759.java
+public class Issue759 {
+  void possibleValues(final Class<? extends Enum> enumType) {
+    lowercase(enumType.getEnumConstants());
+    lowercase2(enumType.getEnumConstants());
+    lowercase3(enumType.getEnumConstants());
+  }
+
+  <T extends Enum<T>> void lowercase(final T... items) {}
+
+  <T extends Enum<T>> void lowercase2(final T[] items) {}
+
+  <T> void lowercase3(final T items) {}
+}
+
+@SuppressWarnings("nullness")
+class Gen<T extends Gen<T>> {
+  T[] getConstants() {
+    return null;
+  }
+}
+
+@SuppressWarnings("nullness")
+class IncompatibleTypes {
+  void possibleValues(final Gen<?> genType) {
+    lowercase(genType.getConstants());
+  }
+
+  <S> void lowercase(final S items) {}
+}
diff --git a/framework/tests/all-systems/Issue807.java b/framework/tests/all-systems/Issue807.java
new file mode 100644
index 0000000..376e303
--- /dev/null
+++ b/framework/tests/all-systems/Issue807.java
@@ -0,0 +1,20 @@
+// Test case for Issue #807:
+// https://github.com/typetools/checker-framework/issues/807
+
+import java.util.function.Consumer;
+
+public class Issue807 {
+
+  class MyEntry<K, V> {
+    MyEntry(MyEntry<? extends K, ? extends V> e) {}
+  }
+
+  <K, V> Consumer<MyEntry<K, V>> entryConsumer(Consumer<? super MyEntry<K, V>> action) {
+    // The "new MyEntry" isn't a subtype of "? super MyEntry" in
+    // most type systems. Suppress that error, as it's not the
+    // point of this test.
+    @SuppressWarnings("all")
+    Consumer<MyEntry<K, V>> res = e -> action.accept(new MyEntry<>(e));
+    return res;
+  }
+}
diff --git a/framework/tests/all-systems/Issue808.java b/framework/tests/all-systems/Issue808.java
new file mode 100644
index 0000000..87142f7
--- /dev/null
+++ b/framework/tests/all-systems/Issue808.java
@@ -0,0 +1,23 @@
+// Test case for Issue 808
+// https://github.com/typetools/checker-framework/issues/808
+
+import java.util.Arrays;
+import java.util.List;
+
+public class Issue808 {
+  void f() {
+    Arrays.asList(0, 0, "", Arrays.asList(new Object[0]));
+    foo(new Object(), bar());
+    new Issue808(bar());
+    foo(bar());
+    List<Object> list = Arrays.asList(new Object[0]);
+  }
+
+  <T> T bar() {
+    throw new RuntimeException();
+  }
+
+  void foo(Object... param) {}
+
+  Issue808(Object... param) {}
+}
diff --git a/framework/tests/all-systems/Issue810.java b/framework/tests/all-systems/Issue810.java
new file mode 100644
index 0000000..a0d2a3e
--- /dev/null
+++ b/framework/tests/all-systems/Issue810.java
@@ -0,0 +1,11 @@
+// Test case for Issue 810
+// https://github.com/typetools/checker-framework/issues/810
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class Issue810 {
+  Map<String, String> m = new HashMap<>();
+  Set<String> n = m.keySet();
+}
diff --git a/framework/tests/all-systems/Issue887.java b/framework/tests/all-systems/Issue887.java
new file mode 100644
index 0000000..1a66012
--- /dev/null
+++ b/framework/tests/all-systems/Issue887.java
@@ -0,0 +1,16 @@
+// Test case for Issue 887
+// https://github.com/typetools/checker-framework/issues/887
+// Additional test case in checker/tests/nullness/Issue887.java
+
+import java.util.List;
+
+public abstract class Issue887 {
+  @SuppressWarnings("nullness") // See checker/tests/nullness/Issue887.java
+  void test() {
+    method(foo(null).get(0));
+  }
+
+  void method(Number o) {}
+
+  abstract <T extends Number> List<? extends T> foo(T t);
+}
diff --git a/framework/tests/all-systems/Issue888.java b/framework/tests/all-systems/Issue888.java
new file mode 100644
index 0000000..c3a8dbb
--- /dev/null
+++ b/framework/tests/all-systems/Issue888.java
@@ -0,0 +1,12 @@
+// Test case for Issue #888
+// https://github.com/typetools/checker-framework/issues/888
+
+public class Issue888 {
+  <T> T foo(T t) {
+    return t;
+  }
+
+  void bar(int i) {
+    foo(i).toString();
+  }
+}
diff --git a/framework/tests/all-systems/Issue913.java b/framework/tests/all-systems/Issue913.java
new file mode 100644
index 0000000..4f5c441
--- /dev/null
+++ b/framework/tests/all-systems/Issue913.java
@@ -0,0 +1,16 @@
+// Test case for Issue 913
+// https://github.com/typetools/checker-framework/issues/913
+
+public class Issue913 {
+  void test(Ordering<Object> o) {
+    Multimap<Long> newMap = create(o);
+  }
+
+  static <V> Multimap<V> create(Ordering<? super V> valueComparator) {
+    throw new RuntimeException();
+  }
+
+  interface Multimap<V> {}
+
+  class Ordering<T> {}
+}
diff --git a/framework/tests/all-systems/Issue953.java b/framework/tests/all-systems/Issue953.java
new file mode 100644
index 0000000..106e257
--- /dev/null
+++ b/framework/tests/all-systems/Issue953.java
@@ -0,0 +1,24 @@
+// Test case for issue 953
+// https://github.com/typetools/checker-framework/issues/953
+
+import java.util.List;
+
+public class Issue953 {
+  class MyCollector<A, B, C> {}
+
+  class MyStream<E> {
+    <F, G> F collect(MyCollector<? super E, G, F> param) {
+      throw new RuntimeException();
+    }
+  }
+
+  public static void test(MyStream<Integer> y) {
+    // Type argument inference fails, so a checker may report a type checking error.
+    @SuppressWarnings("all")
+    List<Integer> counts = y.collect(toList());
+  }
+
+  static <H> MyCollector<H, ?, List<H>> toList() {
+    throw new RuntimeException();
+  }
+}
diff --git a/framework/tests/all-systems/Issue953b.java b/framework/tests/all-systems/Issue953b.java
new file mode 100644
index 0000000..64a570b
--- /dev/null
+++ b/framework/tests/all-systems/Issue953b.java
@@ -0,0 +1,25 @@
+// Test case for issue 953
+// https://github.com/typetools/checker-framework/issues/953
+// The signture of MyStream#collect is slightly different than the one in Issue953.java
+
+import java.util.List;
+
+public class Issue953b {
+  class MyCollector<A, B, C> {}
+
+  class MyStream<E> {
+    <F, G> F collect(MyCollector<? extends E, G, F> param) {
+      throw new RuntimeException();
+    }
+  }
+
+  public static void test(MyStream<Integer> y) {
+    // Type argument inference fails, so a checker may report a type checking error.
+    @SuppressWarnings("all")
+    List<Integer> counts = y.collect(toList());
+  }
+
+  static <H> MyCollector<H, ?, List<H>> toList() {
+    throw new RuntimeException();
+  }
+}
diff --git a/framework/tests/all-systems/Issue988.java b/framework/tests/all-systems/Issue988.java
new file mode 100644
index 0000000..b938852
--- /dev/null
+++ b/framework/tests/all-systems/Issue988.java
@@ -0,0 +1,12 @@
+// Test case for Issue 988:
+// https://github.com/typetools/checker-framework/issues/988
+
+abstract class Issue988 {
+  abstract Class getRawClass();
+
+  abstract Class<?> getGenericClass();
+
+  Class<?> getWithArg(boolean generic) {
+    return generic ? getGenericClass() : getRawClass();
+  }
+}
diff --git a/framework/tests/all-systems/LubRawTypes.java b/framework/tests/all-systems/LubRawTypes.java
new file mode 100644
index 0000000..913d8f0
--- /dev/null
+++ b/framework/tests/all-systems/LubRawTypes.java
@@ -0,0 +1,18 @@
+@SuppressWarnings("unchecked")
+public class LubRawTypes {
+  public static boolean flag = false;
+
+  class MyGen<T> {}
+
+  MyGen<MyGen<MyGen<String>>> test(MyGen myGen1, MyGen myGen2) {
+    return flag ? myGen1 : myGen2;
+  }
+
+  MyGen<MyGen<MyGen<String>>> test2(MyGen myGen1, MyGen<MyGen<MyGen<String>>> myGen2) {
+    return flag ? myGen1 : myGen2;
+  }
+
+  MyGen<MyGen<MyGen<String>>> test3(MyGen myGen1, MyGen<MyGen<MyGen<String>>> myGen2) {
+    return flag ? myGen2 : myGen1;
+  }
+}
diff --git a/framework/tests/all-systems/MethodTypeVars.java b/framework/tests/all-systems/MethodTypeVars.java
new file mode 100644
index 0000000..4749f8f
--- /dev/null
+++ b/framework/tests/all-systems/MethodTypeVars.java
@@ -0,0 +1,21 @@
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class MethodTypeVars {
+  private <T> void addToBindingList(Map<T, List<String>> map, T key, String value) {}
+
+  void call1() {
+    LinkedHashMap<Object, List<String>> multiMap = new LinkedHashMap<>();
+    String s = "s";
+    Object o = new Object();
+    addToBindingList(multiMap, o, s);
+  }
+
+  void call2() {
+    LinkedHashMap<Integer, List<String>> multiMap = new LinkedHashMap<>();
+    String s = "s";
+    Integer n = 5;
+    addToBindingList(multiMap, n, s);
+  }
+}
diff --git a/framework/tests/all-systems/MissingBoundAnnotations.java b/framework/tests/all-systems/MissingBoundAnnotations.java
new file mode 100644
index 0000000..2c15fde
--- /dev/null
+++ b/framework/tests/all-systems/MissingBoundAnnotations.java
@@ -0,0 +1,13 @@
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+public final class MissingBoundAnnotations {
+  @SuppressWarnings("nullness:type.argument")
+  public static <K extends Comparable<? super K>, V> Collection<K> sortedKeySet(Map<K, V> m) {
+    ArrayList<K> theKeys = new ArrayList<>(m.keySet());
+    Collections.sort(theKeys);
+    return theKeys;
+  }
+}
diff --git a/framework/tests/all-systems/MultipleUnions.java b/framework/tests/all-systems/MultipleUnions.java
new file mode 100644
index 0000000..4d9f41c
--- /dev/null
+++ b/framework/tests/all-systems/MultipleUnions.java
@@ -0,0 +1,38 @@
+public class MultipleUnions {
+  public static boolean flag = false;
+
+  void foo1(MyInterface<Throwable> param) throws Throwable {
+    try {
+      bar();
+    } catch (MyExceptionA | MyExceptionB ex1) {
+      try {
+        bar();
+      } catch (SubMyExceptionA | MyExceptionB ex2) {
+
+        Throwable t = flag ? ex1 : ex2;
+        typeVar(ex1, ex1);
+        typeVar(ex2, ex2);
+        // See UnionCrash for version that crashes
+        // typeVar(ex1, ex2);
+      }
+    }
+  }
+
+  <T extends Cloneable & MyInterface<String>> void typeVarIntersection(T param) {}
+
+  <T extends Throwable> void typeVar(T param, T param2) {}
+
+  <T extends Throwable> void typeVarWildcard(T param, MyInterface<? extends T> myInterface) {}
+
+  <T extends Throwable> void typeVarWildcard2(T param, MyInterface<? super T> myInterface) {}
+
+  void bar() throws MyExceptionA, MyExceptionB {}
+
+  interface MyInterface<T> {}
+
+  class MyExceptionA extends Throwable implements Cloneable, MyInterface<String> {}
+
+  class MyExceptionB extends Throwable implements Cloneable, MyInterface<String> {}
+
+  class SubMyExceptionA extends MyExceptionA {}
+}
diff --git a/framework/tests/all-systems/Options.java b/framework/tests/all-systems/Options.java
new file mode 100644
index 0000000..c203e94
--- /dev/null
+++ b/framework/tests/all-systems/Options.java
@@ -0,0 +1,8 @@
+public class Options {
+
+  private Class main_class;
+
+  public Options() {
+    throw new Error("" + main_class);
+  }
+}
diff --git a/framework/tests/all-systems/PolyCollectorTypeVars.java b/framework/tests/all-systems/PolyCollectorTypeVars.java
new file mode 100644
index 0000000..37dbcd1
--- /dev/null
+++ b/framework/tests/all-systems/PolyCollectorTypeVars.java
@@ -0,0 +1,29 @@
+import java.util.Comparator;
+
+class MyGen<K0> {}
+
+abstract class Ordering<T> implements Comparator<T> {
+  // Natural order
+
+  public static <C extends Comparable> Ordering<C> natural() {
+    return new Ordering<C>() {
+      @Override
+      public int compare(C o1, C o2) {
+        return 0;
+      }
+    };
+  }
+}
+
+public class PolyCollectorTypeVars {
+  // Both of these come from the extends Comparable on line 9
+  @SuppressWarnings({"rawtypes", "type.argument"})
+  public static MyGen<Comparable> treeKeys2() {
+    // See Limitation in DefaultTypeArgumentInference on interdependent methods
+    return treeKeys(Ordering.natural());
+  }
+
+  public static <K0> MyGen<K0> treeKeys(final Comparator<K0> comparator) {
+    return new MyGen<K0>();
+  }
+}
diff --git a/framework/tests/all-systems/PrintArray.java b/framework/tests/all-systems/PrintArray.java
new file mode 100644
index 0000000..509e048
--- /dev/null
+++ b/framework/tests/all-systems/PrintArray.java
@@ -0,0 +1,14 @@
+public class PrintArray {
+  // the I18n checker correctly issues an error and Nullness org.checkerframework.checker correctly
+  // issue a warning below, but we would like to keep this test in all-systems.
+  @SuppressWarnings({"i18n", "nullness:nulltest.redundant"})
+  public static final void print(java.io.PrintStream ps, Object[][] a) {
+    if (a == null) {
+      ps.println("null");
+      return;
+    }
+    // When analyzing this call, we see an exception about taking the LUB
+    // of ATMs with different numbers of qualifiers.
+    ps.print('7');
+  }
+}
diff --git a/framework/tests/all-systems/README b/framework/tests/all-systems/README
new file mode 100644
index 0000000..97f5580
--- /dev/null
+++ b/framework/tests/all-systems/README
@@ -0,0 +1,20 @@
+This directory contains test cases for all type systems.
+As they are meant for all type systems, they do not contain any
+actual annotations or expected errors.
+They exercise tricky Java coding patterns that should be handled
+correctly.
+
+If a new type system correctly issues an error or a warning for one of these
+tests, then suppress it using the most specific SuppressWarnings string
+possible, for example "keyfor:return".  Write the
+@SuppressWarnings annotation such that the scope is as limited as possible, but
+do not alter the code.  Write a comment on the warning suppression explaining
+why the error is expected. Add the test with the expected error to the type
+systems test and add the expected error and/or adding annotations to prevent the
+error to the test.
+
+TODO: move interesting general purpose test cases from specific
+type systems to this directory.
+
+Note that this directory is also linked from
+checker/tests/all-system
diff --git a/framework/tests/all-systems/RawTypeAssignment.java b/framework/tests/all-systems/RawTypeAssignment.java
new file mode 100644
index 0000000..242db23
--- /dev/null
+++ b/framework/tests/all-systems/RawTypeAssignment.java
@@ -0,0 +1,23 @@
+import java.util.ArrayList;
+import java.util.Calendar;
+
+class Component {}
+
+class Components extends ArrayList {}
+
+// If we include a type parameter in the superclass, then there is no error below.
+// class Components extends ArrayList<Component> {}
+
+public class RawTypeAssignment {
+  static Components getComponents() {
+    return new Components();
+  }
+
+  static void addTimes(Calendar calendar) {
+    // Type systems may issue an error below because of a mismatch between the type arguments.
+    @SuppressWarnings("assignment")
+    // :: warning: [unchecked] unchecked conversion
+    ArrayList<Component> clist = getComponents();
+    clist.get(0);
+  }
+}
diff --git a/framework/tests/all-systems/RawTypes.java b/framework/tests/all-systems/RawTypes.java
new file mode 100644
index 0000000..0a3c8d2
--- /dev/null
+++ b/framework/tests/all-systems/RawTypes.java
@@ -0,0 +1,15 @@
+import java.util.List;
+
+public class RawTypes {
+  public void m(ClassLoader cl) throws ClassNotFoundException {
+    Class clazz = cl.loadClass("java.lang.Object");
+  }
+}
+
+interface I {
+  public void m(List<? extends String> l);
+}
+
+class OtherClass implements I {
+  public void m(List l) {}
+}
diff --git a/framework/tests/all-systems/ResourceVariables.java b/framework/tests/all-systems/ResourceVariables.java
new file mode 100644
index 0000000..53d3329
--- /dev/null
+++ b/framework/tests/all-systems/ResourceVariables.java
@@ -0,0 +1,11 @@
+import java.io.*;
+
+// Tests related to resource variables in try-with-resources statements.
+
+public class ResourceVariables {
+  void foo(InputStream arg) {
+    try (InputStream in = arg) {
+    } catch (IOException e) {
+    }
+  }
+}
diff --git a/framework/tests/all-systems/SimpleLog.java b/framework/tests/all-systems/SimpleLog.java
new file mode 100644
index 0000000..5a48e27
--- /dev/null
+++ b/framework/tests/all-systems/SimpleLog.java
@@ -0,0 +1,9 @@
+public class SimpleLog {
+  public SimpleLog() {
+    try {
+      int i = 0;
+    } catch (Exception e) {
+      throw new RuntimeException("", e);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/StateMatch.java b/framework/tests/all-systems/StateMatch.java
new file mode 100644
index 0000000..6546b6a
--- /dev/null
+++ b/framework/tests/all-systems/StateMatch.java
@@ -0,0 +1,26 @@
+public class StateMatch {
+  private int num_elts = 0;
+
+  @SuppressWarnings("nullness")
+  private double[][] elts = null;
+
+  @SuppressWarnings({
+    "interning",
+    "index"
+  }) // This code is inherently unsafe for the index checker, but adding index annotations
+  // produces warnings for other checkers (fenum).
+  public boolean state_match(Object state) {
+    if (!(state instanceof double[][])) {
+      System.out.println("");
+    }
+
+    double[][] e = (double[][]) state;
+    boolean match = false;
+    if (elts[0] == e[0]) {
+      // When analyzing this statement, we get an exception about taking
+      // the LUB of ATMs with empty sets of qualifiers.
+      match = true;
+    }
+    return (true);
+  }
+}
diff --git a/framework/tests/all-systems/SuperThis.java b/framework/tests/all-systems/SuperThis.java
new file mode 100644
index 0000000..28e4176
--- /dev/null
+++ b/framework/tests/all-systems/SuperThis.java
@@ -0,0 +1,15 @@
+@SuppressWarnings("all")
+public class SuperThis {
+  class Super {}
+
+  // Test super() and this()
+  class Inner extends Super {
+    public Inner() {
+      super();
+    }
+
+    public Inner(int i) {
+      this();
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Ternary.java b/framework/tests/all-systems/Ternary.java
new file mode 100644
index 0000000..f992855
--- /dev/null
+++ b/framework/tests/all-systems/Ternary.java
@@ -0,0 +1,74 @@
+import java.lang.ref.WeakReference;
+
+public class Ternary<F> {
+  void m1(boolean b) {
+    String s = b ? new String("foo") : null;
+  }
+
+  void m2(boolean b) {
+    String s = b ? null : new String("foo");
+  }
+
+  @SuppressWarnings("nullness") // Don't want to depend on @Nullable
+  String m3(boolean b) {
+    return b ? new String("foo") : null;
+  }
+
+  void m4(boolean b) {
+    String[] s = b ? new String[] {""} : null;
+  }
+
+  void m5(boolean b) {
+    Object o = new Object();
+    String s = b ? (String) o : null;
+  }
+
+  void m6(boolean b) {
+    String p = "x*(((";
+    String s = b ? p : null;
+  }
+
+  class Generic<T extends Object> {
+    void cond(boolean b, T p1, T p2) {
+      p1 = b ? p1 : p2;
+    }
+  }
+
+  void array(boolean b) {
+    String[] s = b ? new String[] {""} : null;
+  }
+
+  void generic(boolean b, Generic<String> p) {
+    Generic<String> s = b ? p : null;
+  }
+
+  void primarray(boolean b) {
+    long[] result = b ? null : new long[10];
+  }
+
+  void vars() {
+    // String and Integer generate an intersection type.
+    String c = null;
+    Integer m = null;
+    Object s = (m != null) ? m : c;
+  }
+
+  void vars2() {
+    // String and Integer generate an intersection type.
+    String c = null;
+    Integer m = null;
+    Object s = (m != null) ? m : c;
+  }
+
+  public void test(MyWeakRef<? extends F> existingRef) {
+    @SuppressWarnings("nulltest.redundant")
+    F existing = existingRef == null ? null : existingRef.get();
+  }
+
+  private static final class MyWeakRef<L> extends WeakReference<L> {
+
+    public MyWeakRef(L referent) {
+      super(referent);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Throw.java b/framework/tests/all-systems/Throw.java
new file mode 100644
index 0000000..0edcff6
--- /dev/null
+++ b/framework/tests/all-systems/Throw.java
@@ -0,0 +1,18 @@
+import java.util.List;
+
+public class Throw {
+  <E extends Exception> void throwTypeVar(E ex) {
+    try {
+      throw ex;
+    } catch (Exception e) {
+    }
+  }
+
+  void throwWildcard(List<? extends Exception> list) {
+    try {
+      throw list.get(0);
+    } catch (Exception e) {
+
+    }
+  }
+}
diff --git a/framework/tests/all-systems/TypeVarAndArrayRefinement.java b/framework/tests/all-systems/TypeVarAndArrayRefinement.java
new file mode 100644
index 0000000..d162e44
--- /dev/null
+++ b/framework/tests/all-systems/TypeVarAndArrayRefinement.java
@@ -0,0 +1,20 @@
+public class TypeVarAndArrayRefinement {
+
+  private <T extends Enum<T>> T getEnumValue(Class<T> enumType, String name) {
+    T[] constants = enumType.getEnumConstants();
+    if (constants == null) {
+      throw new IllegalArgumentException(enumType.getName() + " is not an enum type");
+    }
+    // previously the constants method was considered nullable mainly because it was an invalid
+    // type because when lubbing type variables we didn't copy the declared types on the bounds
+    // over to the lub
+    for (T constant : constants) {
+      if (constant.name().equalsIgnoreCase(name.replace('-', '_'))) {
+        return constant;
+      }
+    }
+    // same error that's thrown by Enum.valueOf()
+    throw new IllegalArgumentException(
+        "No enum constant " + enumType.getCanonicalName() + "." + name);
+  }
+}
diff --git a/framework/tests/all-systems/TypeVarAndArrayRefinementSmall.java b/framework/tests/all-systems/TypeVarAndArrayRefinementSmall.java
new file mode 100644
index 0000000..2fe1aee
--- /dev/null
+++ b/framework/tests/all-systems/TypeVarAndArrayRefinementSmall.java
@@ -0,0 +1,8 @@
+public class TypeVarAndArrayRefinementSmall {
+  private <T extends Enum<T>> T getEnumValue(T[] constants) {
+    for (T constant : constants) {
+      return constant;
+    }
+    throw new Error();
+  }
+}
diff --git a/framework/tests/all-systems/TypeVarInstanceOf.java b/framework/tests/all-systems/TypeVarInstanceOf.java
new file mode 100644
index 0000000..990e807
--- /dev/null
+++ b/framework/tests/all-systems/TypeVarInstanceOf.java
@@ -0,0 +1,5 @@
+public class TypeVarInstanceOf {
+  public static <T> void clone(final T obj) {
+    if (obj instanceof Cloneable) {}
+  }
+}
diff --git a/framework/tests/all-systems/TypeVarPrimitives.java b/framework/tests/all-systems/TypeVarPrimitives.java
new file mode 100644
index 0000000..1ad25d0
--- /dev/null
+++ b/framework/tests/all-systems/TypeVarPrimitives.java
@@ -0,0 +1,12 @@
+// Annotated versions in
+// checker/tests/nullness/TypeVarPrimitivesNullness.java and
+// checker/tests/interning/TypeVarPrimitivesInterning.java
+public class TypeVarPrimitives {
+  <T extends Long> void method(T tLong) {
+    long l = tLong;
+  }
+
+  <T extends Long & Cloneable> void methodIntersection(T tLong) {
+    long l = tLong;
+  }
+}
diff --git a/framework/tests/all-systems/TypeVarVarargs.java b/framework/tests/all-systems/TypeVarVarargs.java
new file mode 100644
index 0000000..9a928cc
--- /dev/null
+++ b/framework/tests/all-systems/TypeVarVarargs.java
@@ -0,0 +1,9 @@
+abstract class TypeVarVarargs {
+  public abstract <T> T foo();
+
+  public abstract void bar(Integer... s);
+
+  public void m() {
+    bar(foo(), foo());
+  }
+}
diff --git a/framework/tests/all-systems/TypeVars.java b/framework/tests/all-systems/TypeVars.java
new file mode 100644
index 0000000..ef748e5
--- /dev/null
+++ b/framework/tests/all-systems/TypeVars.java
@@ -0,0 +1,22 @@
+public class TypeVars {
+
+  class Test1<T> {
+    void m() {
+      @SuppressWarnings("unchecked")
+      T x = (T) new Object();
+
+      Object o = x;
+    }
+
+    class Inner1<X extends T> {}
+
+    public Inner1<T> method1() {
+      return new Inner1<>();
+    }
+  }
+
+  // It's difficult to add more test cases that
+  // should work for all type systems.
+  // Ensure that for the different type systems, annotations
+  // on the type variable are propagated correctly.
+}
diff --git a/framework/tests/all-systems/UnionCrash.java b/framework/tests/all-systems/UnionCrash.java
new file mode 100644
index 0000000..d3c9ba2
--- /dev/null
+++ b/framework/tests/all-systems/UnionCrash.java
@@ -0,0 +1,28 @@
+// Test case for issue #775
+// https://github.com/typetools/checker-framework/issues/775
+public class UnionCrash {
+  void foo(MyInterface<Throwable> param) throws Throwable {
+    try {
+      bar();
+    } catch (MyExceptionA | MyExceptionB ex1) {
+      try {
+        bar();
+      } catch (SubMyExceptionA | MyExceptionB ex2) {
+        // This call cause a crash
+        typeVar(ex1, ex2);
+      }
+    }
+  }
+
+  <T extends Throwable> void typeVar(T param, T param2) {}
+
+  void bar() throws MyExceptionA, MyExceptionB {}
+
+  interface MyInterface<T> {}
+
+  class MyExceptionA extends Throwable implements Cloneable, MyInterface<String> {}
+
+  class MyExceptionB extends Throwable implements Cloneable, MyInterface<String> {}
+
+  class SubMyExceptionA extends MyExceptionA {}
+}
diff --git a/framework/tests/all-systems/UnionTypes.java b/framework/tests/all-systems/UnionTypes.java
new file mode 100644
index 0000000..4fc1717
--- /dev/null
+++ b/framework/tests/all-systems/UnionTypes.java
@@ -0,0 +1,12 @@
+// Test case for Issue 145
+// https://github.com/typetools/checker-framework/issues/145
+public class UnionTypes {
+  public void TryCatch() {
+    try {
+      int[] arr = new int[10];
+      arr[4] = 1;
+    } catch (ArrayIndexOutOfBoundsException | StringIndexOutOfBoundsException exc) {
+      Exception e = exc;
+    }
+  }
+}
diff --git a/framework/tests/all-systems/Unions.java b/framework/tests/all-systems/Unions.java
new file mode 100644
index 0000000..edaaca3
--- /dev/null
+++ b/framework/tests/all-systems/Unions.java
@@ -0,0 +1,49 @@
+public class Unions {
+  void foo1(MyInterface<Throwable> param) throws Throwable {
+    try {
+      bar();
+    } catch (MyExceptionA | MyExceptionB ex) {
+      typeVar(ex);
+      typeVarIntersection(ex);
+
+      typeVarWildcard(ex, param);
+      typeVarWildcard2(ex, param);
+    }
+  }
+
+  void foo2(MyInterface<Throwable> param) throws Throwable {
+    try {
+      bar();
+    } catch (SubMyExceptionA | SubMyExceptionA2 ex) {
+      typeVar(ex);
+      typeVar2(ex, ex);
+
+      typeVarIntersection(ex);
+
+      typeVarWildcard(ex, param);
+      typeVarWildcard2(ex, param);
+    }
+  }
+
+  <T extends Cloneable & MyInterface<String>> void typeVarIntersection(T param) {}
+
+  <T extends Throwable> void typeVar(T param) {}
+
+  <T extends Throwable> void typeVar2(T param, T param2) {}
+
+  <T extends Throwable> void typeVarWildcard(T param, MyInterface<? extends T> myInterface) {}
+
+  <T extends Throwable> void typeVarWildcard2(T param, MyInterface<? super T> myInterface) {}
+
+  void bar() throws MyExceptionA, MyExceptionB {}
+
+  interface MyInterface<T> {}
+
+  class MyExceptionA extends Throwable implements Cloneable, MyInterface<String> {}
+
+  class MyExceptionB extends Throwable implements Cloneable, MyInterface<String> {}
+
+  class SubMyExceptionA extends MyExceptionA {}
+
+  class SubMyExceptionA2 extends MyExceptionA {}
+}
diff --git a/framework/tests/all-systems/VarKeyword.java b/framework/tests/all-systems/VarKeyword.java
new file mode 100644
index 0000000..2d605a2
--- /dev/null
+++ b/framework/tests/all-systems/VarKeyword.java
@@ -0,0 +1,15 @@
+import java.util.List;
+
+// @below-java11-jdk-skip-test
+public class VarKeyword {
+  @SuppressWarnings("dereference.of.nullable")
+  void method(List<VarKeyword> list) {
+    var s = "Hello!";
+    s = null;
+    s.toString();
+    for (var i : list) {}
+
+    var listVar = list;
+    method((listVar));
+  }
+}
diff --git a/framework/tests/all-systems/Viz.java b/framework/tests/all-systems/Viz.java
new file mode 100644
index 0000000..417a1cc
--- /dev/null
+++ b/framework/tests/all-systems/Viz.java
@@ -0,0 +1,19 @@
+package wildcard;
+
+public class Viz {
+
+  static class AbstractValue<A extends AbstractValue<A>> {}
+
+  public interface Store<B extends Store<B>> {}
+
+  public interface TransferFunction<C extends AbstractValue<C>, D extends Store<D>> {}
+
+  public interface CFGVisualizer<
+      E extends AbstractValue<E>, F extends Store<F>, G extends TransferFunction<E, F>> {}
+
+  static class CFAbstractStore<V extends AbstractValue<V>, X extends CFAbstractStore<V, X>>
+      implements Store<X> {
+
+    void test(CFGVisualizer<?, X, ?> param) {}
+  }
+}
diff --git a/framework/tests/all-systems/WildCardCrash.java b/framework/tests/all-systems/WildCardCrash.java
new file mode 100644
index 0000000..c9408ad
--- /dev/null
+++ b/framework/tests/all-systems/WildCardCrash.java
@@ -0,0 +1,46 @@
+public class WildCardCrash {}
+
+abstract class AbstractTransfer123<
+        IndexStore extends CFAbstractStore123<CFValue123, IndexStore>,
+        MySelf extends AbstractTransfer123<IndexStore, MySelf>>
+    extends CFAbstractTransfer123<CFValue123, IndexStore, MySelf> {
+  void method() {
+    // There was a crash when checking the assignment of analysis to the formal parameter.
+    SomeGen<IndexStore> rfi = new SomeGen<>(analysis);
+  }
+}
+
+class SomeGen<IndexStore extends Store123<IndexStore>> {
+  public SomeGen(CFAbstractAnalysis123<CFValue123, ?, ?> analysis) {}
+}
+
+class CFValue123 extends CFAbstractValue123<CFValue123> {}
+
+@SuppressWarnings({"initialization", "initializedfields:contracts.postcondition"})
+class CFAbstractTransfer123<
+        V extends CFAbstractValue123<V>,
+        S extends CFAbstractStore123<V, S>,
+        T extends CFAbstractTransfer123<V, S, T>>
+    implements TransferFunction123<V, S> {
+  protected CFAbstractAnalysis123<V, S, T> analysis;
+}
+
+class CFAbstractValue123<V extends CFAbstractValue123<V>> implements AbstractValue123<V> {}
+
+class CFAbstractStore123<V extends CFAbstractValue123<V>, S extends CFAbstractStore123<V, S>>
+    implements Store123<S> {}
+
+abstract class CFAbstractAnalysis123<
+        V extends CFAbstractValue123<V>,
+        S extends CFAbstractStore123<V, S>,
+        T extends CFAbstractTransfer123<V, S, T>>
+    extends Analysis123<V, S, T> {}
+
+interface AbstractValue123<V extends AbstractValue123<V>> {}
+
+interface Store123<T extends Store123<T>> {}
+
+interface TransferFunction123<A extends AbstractValue123<A>, S extends Store123<S>> {}
+
+class Analysis123<
+    A extends AbstractValue123<A>, S extends Store123<S>, T extends TransferFunction123<A, S>> {}
diff --git a/framework/tests/all-systems/WildcardBounds.java b/framework/tests/all-systems/WildcardBounds.java
new file mode 100644
index 0000000..2e64892
--- /dev/null
+++ b/framework/tests/all-systems/WildcardBounds.java
@@ -0,0 +1,15 @@
+import java.io.Serializable;
+
+public class WildcardBounds {
+  class BoundedGeneric<B extends Cloneable> {}
+
+  class BoundedGeneric2<B extends Number> {}
+
+  class BoundedGeneric3<B extends Number & Cloneable> {}
+
+  void use() {
+    BoundedGeneric<? extends Serializable> a;
+    BoundedGeneric<? extends Serializable> b;
+    BoundedGeneric<? extends Serializable> c;
+  }
+}
diff --git a/framework/tests/all-systems/WildcardCharPrimitive.java b/framework/tests/all-systems/WildcardCharPrimitive.java
new file mode 100644
index 0000000..1dc6119
--- /dev/null
+++ b/framework/tests/all-systems/WildcardCharPrimitive.java
@@ -0,0 +1,24 @@
+public class WildcardCharPrimitive {
+  static interface Predicate<T> {
+    boolean apply(T t);
+  }
+
+  abstract static class Matcher {
+
+    public abstract boolean matches(char character);
+  }
+
+  public static void forPredicate(final Predicate<? super Character> predicate) {
+
+    new Matcher() {
+      //  this tests default type hierarchy visitPrimitive_Wildcard
+      public boolean matches(char c) {
+        //  this will happen when a type system does not have an EXPLICIT_LOWER_BOUND
+        //  that matches the default for char c
+        @SuppressWarnings("argument")
+        boolean value = predicate.apply(c);
+        return value;
+      }
+    };
+  }
+}
diff --git a/framework/tests/all-systems/WildcardCon.java b/framework/tests/all-systems/WildcardCon.java
new file mode 100644
index 0000000..c16b316
--- /dev/null
+++ b/framework/tests/all-systems/WildcardCon.java
@@ -0,0 +1,16 @@
+import java.util.Comparator;
+
+@SuppressWarnings("all") // Only testing for crashes
+public class WildcardCon<E> {
+  ComparatorClass<ComparableClass<? extends Object>> RANGE_LEX_ORDERING = null;
+
+  WildcardCon(Comparator<? super E> comparator) {}
+
+  <C extends Comparable> void use() {
+    new WildcardCon<ComparableClass<C>>(RANGE_LEX_ORDERING);
+  }
+
+  class ComparableClass<C extends Comparable> {}
+
+  abstract class ComparatorClass<T> implements Comparator<T> {}
+}
diff --git a/framework/tests/all-systems/WildcardForEach.java b/framework/tests/all-systems/WildcardForEach.java
new file mode 100644
index 0000000..3db717b
--- /dev/null
+++ b/framework/tests/all-systems/WildcardForEach.java
@@ -0,0 +1,11 @@
+import java.util.List;
+
+public class WildcardForEach {
+  static class Gen<T extends Gen<?>> {}
+
+  void test(List<Gen<?>> x) {
+    // This used to cause a crash in
+    // org.checkerframework.framework.flow.CFTreeBuilder#buildAnnotatedType
+    for (Gen<?> a : x) {}
+  }
+}
diff --git a/framework/tests/all-systems/WildcardIterable.java b/framework/tests/all-systems/WildcardIterable.java
new file mode 100644
index 0000000..1249a6a
--- /dev/null
+++ b/framework/tests/all-systems/WildcardIterable.java
@@ -0,0 +1,19 @@
+import java.util.ArrayList;
+import java.util.List;
+
+public class WildcardIterable {
+  private static <T> List<T> catListAndIterable(
+      final List<T> list, final Iterable<? extends T> iterable) {
+    final List<T> newList = new ArrayList<>();
+
+    for (T listObject : list) {
+      newList.add(listObject);
+    }
+
+    for (T iterObject : iterable) {
+      newList.add(iterObject);
+    }
+
+    return newList;
+  }
+}
diff --git a/framework/tests/all-systems/WildcardSuper.java b/framework/tests/all-systems/WildcardSuper.java
new file mode 100644
index 0000000..4fc25cd
--- /dev/null
+++ b/framework/tests/all-systems/WildcardSuper.java
@@ -0,0 +1,13 @@
+public class WildcardSuper {
+  interface Consumer<T> {
+    void consume(T object);
+  }
+
+  Consumer<String> testCast(Consumer<Object> consumer) {
+    return cast(consumer);
+  }
+
+  private static <T> Consumer<T> cast(final Consumer<? super T> consumer) {
+    throw new RuntimeException();
+  }
+}
diff --git a/framework/tests/all-systems/WildcardSuper2.java b/framework/tests/all-systems/WildcardSuper2.java
new file mode 100644
index 0000000..2278ba1
--- /dev/null
+++ b/framework/tests/all-systems/WildcardSuper2.java
@@ -0,0 +1,16 @@
+import java.util.List;
+
+// See also checker/nullness/generics/WildcardOverride.java
+
+interface AInterface<T> {
+  public abstract int transform(List<? super T> function);
+}
+
+class B implements AInterface<Object> {
+  // This shouldn't work for nullness as the function won't take possibly nullable values.
+  @SuppressWarnings({"nullness", "fenum:override.param", "aliasing"})
+  @Override
+  public int transform(List<Object> function) {
+    return 0;
+  }
+}
diff --git a/framework/tests/all-systems/java8/DefaultMethods.java b/framework/tests/all-systems/java8/DefaultMethods.java
new file mode 100644
index 0000000..5aab68f
--- /dev/null
+++ b/framework/tests/all-systems/java8/DefaultMethods.java
@@ -0,0 +1,17 @@
+interface DefaultMethods {
+
+  // Test that abstract methods are still ignored.
+  void abstractMethod();
+
+  default String method(String s) {
+    return s.toString();
+  }
+}
+
+interface DefaultMethods2 extends DefaultMethods {
+
+  @Override
+  default String method(String s) {
+    return s;
+  }
+}
diff --git a/framework/tests/all-systems/java8/lambda/Issue1681.java b/framework/tests/all-systems/java8/lambda/Issue1681.java
new file mode 100644
index 0000000..fed0882
--- /dev/null
+++ b/framework/tests/all-systems/java8/lambda/Issue1681.java
@@ -0,0 +1,19 @@
+// Test case for Issue 1681:
+// https://github.com/typetools/checker-framework/issues/1681
+
+public class Issue1681 {
+  @FunctionalInterface
+  interface StrReturn {
+    String op();
+  }
+
+  StrReturn[] v1 = new StrReturn[] {() -> "hello"};
+  StrReturn[] v2 = new StrReturn[] {() -> "hello", () -> "world"};
+  StrReturn[] v3 = {() -> "test"};
+
+  void foo(StrReturn[] p) {}
+
+  void bar() {
+    foo(new StrReturn[] {() -> "test", () -> "boo"});
+  }
+}
diff --git a/framework/tests/all-systems/java8/lambda/Issue1817.java b/framework/tests/all-systems/java8/lambda/Issue1817.java
new file mode 100644
index 0000000..68d7d21
--- /dev/null
+++ b/framework/tests/all-systems/java8/lambda/Issue1817.java
@@ -0,0 +1,14 @@
+// Test case for Issue 1817:
+// https://github.com/typetools/checker-framework/issues/1817
+
+import java.util.List;
+import java.util.function.Consumer;
+
+@SuppressWarnings("all") // only check for crashes
+public class Issue1817 {
+  {
+    Consumer<List<?>> c = values -> values.forEach(value -> f(value));
+  }
+
+  void f(Object o) {}
+}
diff --git a/framework/tests/all-systems/java8/lambda/Issue450.java b/framework/tests/all-systems/java8/lambda/Issue450.java
new file mode 100644
index 0000000..a73d3f8
--- /dev/null
+++ b/framework/tests/all-systems/java8/lambda/Issue450.java
@@ -0,0 +1,37 @@
+public class Issue450 {
+
+  Issue450(int i, Runnable... runnables) {}
+
+  Issue450(Consumer<String> consumer) {
+    consumer.consume("hello"); // Use lambda as a constructor argument
+  }
+
+  interface Top {
+    public void consume(String s);
+  }
+
+  interface Sub extends Top {
+    public default void otherMethod() {}
+  }
+
+  interface Consumer<T> {
+    void consume(T t);
+  }
+
+  void varargs(Runnable... runnables) {}
+
+  public static void consumeStr(String str) {}
+
+  public static void consumeStr2(String str) {}
+
+  <E extends Consumer<String>> void context(E e, Sub s) {
+    new Issue450(Issue450::consumeStr);
+
+    Consumer<String> cs1 = (false) ? Issue450::consumeStr2 : Issue450::consumeStr;
+    Consumer<String> cs2 = (false) ? e : Issue450::consumeStr;
+    Top t = (false) ? s : Issue450::consumeStr;
+
+    new Issue450(42, new Thread()::start); // Use lambda as a constructor argument
+    varargs(new Thread()::start, new Thread()::start); // Use lambda in a var arg list of method
+  }
+}
diff --git a/framework/tests/all-systems/java8/lambda/Issue573.java b/framework/tests/all-systems/java8/lambda/Issue573.java
new file mode 100644
index 0000000..17dcd73
--- /dev/null
+++ b/framework/tests/all-systems/java8/lambda/Issue573.java
@@ -0,0 +1,19 @@
+// Test case for Issue 573:
+// https://github.com/typetools/checker-framework/issues/573
+
+// Full test case:
+// http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/f0851bc0e7bf/src/share/classes/java/time/chrono/Chronology.java
+
+import java.io.Serializable;
+import java.time.chrono.ChronoLocalDate;
+import java.time.chrono.ChronoLocalDateTime;
+import java.time.chrono.Chronology;
+import java.util.Comparator;
+
+public abstract class Issue573 implements Chronology {
+  Object o =
+      (Comparator<ChronoLocalDateTime<? extends ChronoLocalDate>> & Serializable)
+          (dateTime1, dateTime2) -> {
+            return 0;
+          };
+}
diff --git a/framework/tests/all-systems/java8/lambda/Lambda.java b/framework/tests/all-systems/java8/lambda/Lambda.java
new file mode 100644
index 0000000..384cca6
--- /dev/null
+++ b/framework/tests/all-systems/java8/lambda/Lambda.java
@@ -0,0 +1,95 @@
+// see also the test for Issue450
+// Test file for lambda syntax
+
+interface Supplier<R> {
+  R supply();
+}
+
+interface Function<T, R> {
+  R apply(T t);
+}
+
+interface Consumer<T> {
+  void consume(T t);
+}
+
+interface BiFunction<T, U, R> {
+  R apply(T t, U u);
+}
+
+interface Noop {
+  void noop();
+}
+
+public class Lambda {
+
+  public static void consumeStr(String str) {}
+
+  Lambda(Consumer<String> consumer) {
+    consumer.consume("hello");
+  }
+
+  // No parameters; result is void
+  Noop f1 = () -> {};
+  // No parameters, expression body
+  Supplier<Integer> f2 = () -> 42;
+  // No parameters, expression body
+  //    Supplier<Void> f3 = () -> null;
+  // No parameters, block body with return
+  Supplier<Integer> f4 =
+      () -> {
+        return 42;
+      };
+  Noop f5 =
+      () -> {
+        System.gc();
+      }; // No parameters, void block body
+
+  // Complex block body with returns
+  Supplier<Integer> f6 =
+      () -> {
+        if (true) {
+          return 12;
+        } else {
+          int result = 15;
+          for (int i = 1; i < 10; i++) {
+            result *= i;
+          }
+          // conditional expression
+          Consumer<String> consumer = result > 100 ? Lambda::consumeStr : Lambda::consumeStr;
+          return result;
+        }
+      };
+
+  // Single declared-type parameter
+  Function<Integer, Integer> f7 = (Integer x) -> x + 1;
+  // Single declared-type parameter
+  Function<Integer, Integer> f9 =
+      (Integer x) -> {
+        return (Integer) x + 1;
+      };
+  // Single inferred-type parameter
+  Function<Integer, Integer> f10 = (x) -> x + 1;
+  // Parentheses optional for single inferred-type parameter
+  Function<Integer, Integer> f11 = x -> x + 1;
+
+  // Single declared-type parameter
+  Function<String, Integer> f12 = (String s) -> s.length();
+  // Single declared-type parameter
+  Consumer<Thread> f13 =
+      (Thread t) -> {
+        t.start();
+      };
+  // Single inferred-type parameter
+  Consumer<String> f14 = s -> s.length();
+  // Single inferred-type parameter
+  Consumer<Thread> f15 =
+      t -> {
+        t.start();
+      };
+
+  // Multiple declared-type parameters
+  BiFunction<Integer, Integer, Integer> f16 = (Integer x, final Integer y) -> x + y;
+  // Multiple inferred-type parameters
+  BiFunction<String, String, String> f18 = (x, y) -> x + y;
+}
diff --git a/framework/tests/all-systems/java8/memberref/AssignmentContext.java b/framework/tests/all-systems/java8/memberref/AssignmentContext.java
new file mode 100644
index 0000000..9d9e156
--- /dev/null
+++ b/framework/tests/all-systems/java8/memberref/AssignmentContext.java
@@ -0,0 +1,21 @@
+interface FunctionAC {
+  String apply(String s);
+}
+
+public class AssignmentContext {
+  // Test assign
+  FunctionAC f1 = String::toString;
+
+  // Test casts
+  Object o1 = (Object) (FunctionAC) String::toString;
+
+  void take(FunctionAC f) {
+    // Test argument assingment
+    take(String::toString);
+  }
+
+  FunctionAC supply() {
+    // Test return assingment
+    return String::toString;
+  }
+}
diff --git a/framework/tests/all-systems/java8/memberref/FromByteCode.java b/framework/tests/all-systems/java8/memberref/FromByteCode.java
new file mode 100644
index 0000000..786bf43
--- /dev/null
+++ b/framework/tests/all-systems/java8/memberref/FromByteCode.java
@@ -0,0 +1,7 @@
+interface FunctionFromByteCode<T, R> {
+  R apply(T t);
+}
+
+public class FromByteCode {
+  FunctionFromByteCode<String, String> f1 = String::toString;
+}
diff --git a/framework/tests/all-systems/java8/memberref/Issue871.java b/framework/tests/all-systems/java8/memberref/Issue871.java
new file mode 100644
index 0000000..b5c12de
--- /dev/null
+++ b/framework/tests/all-systems/java8/memberref/Issue871.java
@@ -0,0 +1,13 @@
+// Test case for issue #871: https://github.com/typetools/checker-framework/issues/871
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.function.Predicate;
+
+interface Issue871 {
+  default Iterable<Path> a() {
+    return f(Files::isRegularFile);
+  }
+
+  Iterable<Path> f(Predicate<Path> condition);
+}
diff --git a/framework/tests/all-systems/java8/memberref/Issue946.java b/framework/tests/all-systems/java8/memberref/Issue946.java
new file mode 100644
index 0000000..f0ffc21
--- /dev/null
+++ b/framework/tests/all-systems/java8/memberref/Issue946.java
@@ -0,0 +1,41 @@
+// Test case for Issue 946
+// https://github.com/typetools/checker-framework/issues/946
+
+interface Supply946<R> {
+  R supply();
+}
+
+public class Issue946 {
+  class MethodRefInnerA {
+    // this line of code causes a crash in CF
+    Supply946<MethodRefInnerB> constructorReferenceField = MethodRefInnerB::new;
+
+    MethodRefInnerA(Issue946 Issue946.this) {
+      // this line of code also causes a crash in CF
+      Supply946<MethodRefInnerB> constructorReference = MethodRefInnerB::new;
+    }
+
+    void method() {
+      // and so does this line
+      Supply946<MethodRefInnerB> constructorReference = MethodRefInnerB::new;
+    }
+
+    class MethodRefInnerAInner {
+      void method() {
+        Supply946<MethodRefInnerB> constructorReference = MethodRefInnerB::new;
+      }
+    }
+  }
+
+  class MethodRefInnerB {
+    MethodRefInnerB(Issue946 Issue946.this) {}
+
+    void method() {
+      Supply946<MethodRefInnerB> constructorReference = MethodRefInnerB::new;
+    }
+  }
+
+  void method() {
+    Supply946<MethodRefInnerB> constructorReference = MethodRefInnerB::new;
+  }
+}
diff --git a/framework/tests/all-systems/java8/memberref/MemberReferences.java b/framework/tests/all-systems/java8/memberref/MemberReferences.java
new file mode 100644
index 0000000..cd7fae8
--- /dev/null
+++ b/framework/tests/all-systems/java8/memberref/MemberReferences.java
@@ -0,0 +1,185 @@
+// If conservativeUninferredTypeArguments option is used, then the lines marked
+// "TODO: Issue 802", will issue a methodref.inference.unimplemented warning.
+
+interface Supplier<R> {
+  R supply();
+}
+
+interface FunctionMR<T, R> {
+  R apply(T t);
+}
+
+interface Consumer<T> {
+  void consume(T t);
+}
+
+interface BiFunctionMR<T, U, R> {
+  R apply(T t, U u);
+}
+
+/** super # instMethod */
+// SUPER(ReferenceMode.INVOKE, false),
+class Super {
+
+  Object func1(Object o) {
+    return o;
+  }
+
+  <T> T func2(T o) {
+    return o;
+  }
+
+  class Sub extends Super {
+    void context() {
+      FunctionMR<Object, Object> f1 = super::func1;
+      // TODO: Issue 802: type argument inference
+      FunctionMR f2 = super::func2;
+      // Top level wildcards are ignored when type checking
+      FunctionMR<? extends String, ? extends String> f3 = super::<String>func2;
+    }
+  }
+}
+
+class SuperWithArg<U> {
+
+  void func1(U o) {}
+
+  class Sub extends SuperWithArg<Number> {
+    void context() {
+      Consumer<Integer> f1 = super::func1;
+    }
+  }
+}
+
+/** Type # instMethod. */
+// UNBOUNDED(ReferenceMode.INVOKE, true),
+class Unbound {
+  <T> T func1(T o) {
+    return o;
+  }
+
+  void context() {
+    FunctionMR<String, String> f1 = String::toString;
+    // TODO: Issue 802: type argument inference
+    BiFunctionMR<Unbound, String, String> f2 = Unbound::func1;
+    @SuppressWarnings("nullness:type.argument")
+    BiFunctionMR<? extends Unbound, ? super Integer, ? extends Integer> f3 =
+        Unbound::<Integer>func1;
+  }
+}
+
+abstract class UnboundWithArg<U> {
+  abstract U func1();
+
+  void context() {
+    // TODO: Issue 802: type argument inference
+    FunctionMR<UnboundWithArg<String>, String> f1 = UnboundWithArg::func1;
+    FunctionMR<UnboundWithArg<String>, String> f2 = UnboundWithArg<String>::func1;
+    // TODO: Issue 802: type argument inference
+    FunctionMR<? extends UnboundWithArg<String>, String> f3 = UnboundWithArg::func1;
+    FunctionMR<? extends UnboundWithArg<String>, String> f4 = UnboundWithArg<String>::func1;
+  }
+}
+
+/** Type # staticMethod. */
+// STATIC(ReferenceMode.INVOKE, false),
+class Static {
+  static <T> T func1(T o) {
+    return o;
+  }
+
+  void context() {
+    // TODO: Issue 802: type argument inference
+    FunctionMR<String, String> f1 = Static::func1;
+    FunctionMR<String, String> f2 = Static::<String>func1;
+  }
+}
+
+/** Expr # instMethod. */
+// BOUND(ReferenceMode.INVOKE, false),
+class Bound {
+  <T> T func1(T o) {
+    return o;
+  }
+
+  void context(Bound bound) {
+    // TODO: Issue 802: type argument inference
+    FunctionMR<String, String> f1 = bound::func1;
+    // TODO: Issue 802: type argument inference
+    FunctionMR<String, String> f2 = this::func1;
+    FunctionMR<String, String> f3 = this::<String>func1;
+    FunctionMR<? extends String, ? extends String> f4 = this::<String>func1;
+  }
+}
+
+class BoundWithArg<U> {
+  void func1(U param) {}
+
+  void context(BoundWithArg<Number> bound) {
+    Consumer<Number> f1 = bound::func1;
+    Consumer<Integer> f2 = bound::func1;
+  }
+}
+
+/** Inner # new. */
+// IMPLICIT_INNER(ReferenceMode.NEW, false),
+class Outer {
+  void context(Outer other) {
+    Supplier<Inner> f1 = Inner::new;
+  }
+
+  class Inner extends Outer {}
+}
+
+class OuterWithArg {
+  void context() {
+    // TODO: Issue 802: type argument inference
+    Supplier<Inner<String>> f1 = Inner::new;
+    Supplier<? extends Inner<Number>> f2 = Inner<Number>::new;
+    Supplier<? extends Inner<? extends Number>> f3 = Inner<Integer>::new;
+  }
+
+  class Inner<T> extends OuterWithArg {}
+}
+
+/** Toplevel # new. */
+// TOPLEVEL(ReferenceMode.NEW, false),
+class TopLevel {
+  TopLevel() {}
+
+  <T> TopLevel(T s) {}
+
+  void context() {
+    Supplier<TopLevel> f1 = TopLevel::new;
+    // TODO: Issue 802: type argument inference
+    FunctionMR<String, TopLevel> f2 = TopLevel::new;
+    FunctionMR<String, TopLevel> f3 = TopLevel::<String>new;
+  }
+}
+
+class TopLevelWithArg<T> {
+  TopLevelWithArg() {}
+
+  <U> TopLevelWithArg(U s) {}
+
+  void context() {
+    // TODO: Issue 802: type argument inference
+    Supplier<TopLevelWithArg<String>> f1 = TopLevelWithArg::new;
+    Supplier<TopLevelWithArg<String>> f2 = TopLevelWithArg<String>::new;
+    FunctionMR<String, TopLevelWithArg<String>> f3 = TopLevelWithArg<String>::<String>new;
+  }
+}
+
+/** ArrayType # new. */
+// ARRAY_CTOR(ReferenceMode.NEW, false);
+
+class ArrayType {
+  void context() {
+    // TODO: Signedness Checker does not default boxed primitives correctly.
+    // See Issue #797: https://github.com/typetools/checker-framework/issues/797
+    @SuppressWarnings({"signedness"})
+    FunctionMR<Integer, String[]> string = String[]::new;
+    FunctionMR<String[], String[]> clone = String[]::clone;
+    FunctionMR<String[], String> toString = String[]::toString;
+  }
+}
diff --git a/framework/tests/all-systems/java8/memberref/Purity.java b/framework/tests/all-systems/java8/memberref/Purity.java
new file mode 100644
index 0000000..751c02f
--- /dev/null
+++ b/framework/tests/all-systems/java8/memberref/Purity.java
@@ -0,0 +1,24 @@
+import org.checkerframework.dataflow.qual.*;
+
+interface PureFunc {
+  @Pure
+  String doNothing();
+}
+
+class TestPure {
+
+  static String myMethod() {
+    return "";
+  }
+
+  @Pure
+  static String myPureMethod() {
+    return "";
+  }
+
+  void context() {
+    PureFunc f1 = TestPure::myPureMethod;
+    // :: error: (purity.methodref)
+    PureFunc f2 = TestPure::myMethod;
+  }
+}
diff --git a/framework/tests/all-systems/java8/memberref/Receivers.java b/framework/tests/all-systems/java8/memberref/Receivers.java
new file mode 100644
index 0000000..d20c39c
--- /dev/null
+++ b/framework/tests/all-systems/java8/memberref/Receivers.java
@@ -0,0 +1,53 @@
+/** BoundR and unbound constraints. */
+interface UnboundR {
+  void consume(/*1*/ UnboundR this, /*2*/ MyClass my, String s);
+}
+
+interface BoundR {
+  void consume(/*4*/ BoundR this, String s);
+}
+
+interface SupplierR<R> {
+  R supply();
+}
+
+class MyClass {
+
+  void take(/*6*/ MyClass this, String s) {}
+
+  void context(/*7*/ MyClass my) {
+    /*8*/ UnboundR u1 = /*9*/ MyClass::take;
+    // 2 <: 6 -- like an override
+    // No relation to 1 or 2
+    // No relationship or check for 8 / 9?
+    // Need to check on this.
+
+    u1.consume(my, "");
+    // 7 <: 2
+    // 8 <: 1
+
+    /*10*/ BoundR b1 = /*11*/ my::take;
+    // 7 <: 6 -- like an invocation
+    // No Relationship for 10 / 11?
+
+    b1.consume("");
+    // 10 <: 4
+  }
+}
+
+/** Constraints for implicit inner constraints and super. */
+@SuppressWarnings("lock")
+class OuterR {
+  class Inner {
+    Inner(/*1*/ OuterR OuterR.this) {}
+
+    void context() {
+      SupplierR<String> o = OuterR.super::toString;
+    }
+  }
+
+  void context(/*2*/ OuterR this) {
+    // This one is unbound and needs an OuterR as a param
+    SupplierR</*3*/ Inner> f = /*4*/ Inner::new;
+  }
+}
diff --git a/framework/tests/all-systems/java8/memberref/VarArgs.java b/framework/tests/all-systems/java8/memberref/VarArgs.java
new file mode 100644
index 0000000..9ecc432
--- /dev/null
+++ b/framework/tests/all-systems/java8/memberref/VarArgs.java
@@ -0,0 +1,40 @@
+interface VarArgsFunc {
+  void take(String... in);
+}
+
+interface ArrayFunc {
+  void take(String[] in);
+}
+
+class VarArgsTest {
+
+  static void myMethod(String... in) {}
+
+  static void myMethodArray(String[] in) {}
+
+  VarArgsFunc v1 = VarArgsTest::myMethod;
+  VarArgsFunc v2 = VarArgsTest::myMethodArray;
+
+  ArrayFunc v3 = VarArgsTest::myMethod;
+  ArrayFunc v4 = VarArgsTest::myMethodArray;
+}
+
+interface RegularFunc {
+  void take(Object o);
+}
+
+interface RegularFunc2 {
+  void take(Object o, String s);
+}
+
+interface RegularFunc3 {
+  void take(Object o, String s, String s2);
+}
+
+class MoreVarAgrgsTest {
+  static void myObjectArgArg(Object o, String... vararg) {}
+
+  RegularFunc v1 = MoreVarAgrgsTest::myObjectArgArg;
+  RegularFunc2 v2 = MoreVarAgrgsTest::myObjectArgArg;
+  RegularFunc3 v4 = MoreVarAgrgsTest::myObjectArgArg;
+}
diff --git a/framework/tests/all-systems/java8inference/ArrayInits.java b/framework/tests/all-systems/java8inference/ArrayInits.java
new file mode 100644
index 0000000..c3a0474
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/ArrayInits.java
@@ -0,0 +1,9 @@
+package inference;
+
+import java.util.Arrays;
+
+public class ArrayInits {
+  void method() {
+    Object[] objects = new Object[] {Arrays.asList(1, 2, 3)};
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug1.java b/framework/tests/all-systems/java8inference/Bug1.java
new file mode 100644
index 0000000..856f0f8
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug1.java
@@ -0,0 +1,19 @@
+package inference.guava;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@SuppressWarnings("all") // Just check for crashes.
+public class Bug1<B> {
+  @SuppressWarnings("type.inference.not.same")
+  public void method1(Map<? extends Class<? extends B>, ? extends B> map) {
+    Map<Class<? extends B>, B> copy = new LinkedHashMap<>(map);
+    for (Map.Entry<? extends Class<? extends B>, B> entry : copy.entrySet()) {
+      cast(entry.getKey(), entry.getValue());
+    }
+  }
+
+  private static <X, T extends X> T cast(Class<T> type, X value) {
+    throw new RuntimeException();
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug10.java b/framework/tests/all-systems/java8inference/Bug10.java
new file mode 100644
index 0000000..6b9ad50
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug10.java
@@ -0,0 +1,49 @@
+package inference.guava;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collector;
+
+@SuppressWarnings("all") // Just check for crashes.
+public class Bug10 {
+  public static <T> Collector<T, ?, List<T>> least(int k, Comparator<? super T> comparator) {
+    return Collector.of(
+        () -> TopKSelector.<T>least(k, comparator),
+        TopKSelector::offer,
+        TopKSelector::combine,
+        TopKSelector::topK,
+        Collector.Characteristics.UNORDERED);
+  }
+
+  static final class TopKSelector<T> {
+    TopKSelector<T> combine(TopKSelector<T> other) {
+      throw new RuntimeException();
+    }
+
+    public static <T extends Comparable<? super T>> TopKSelector<T> least(int k) {
+      throw new RuntimeException();
+    }
+
+    public static <T extends Comparable<? super T>> TopKSelector<T> greatest(int k) {
+      throw new RuntimeException();
+    }
+
+    public static <T> TopKSelector<T> least(int k, Comparator<? super T> comparator) {
+      return new TopKSelector<T>(comparator, k);
+    }
+
+    public static <T> TopKSelector<T> greatest(int k, Comparator<? super T> comparator) {
+      throw new RuntimeException();
+    }
+
+    private TopKSelector(Comparator<? super T> comparator, int k) {}
+
+    public void offer(T elem) {
+      throw new RuntimeException();
+    }
+
+    public List<T> topK() {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug11.java b/framework/tests/all-systems/java8inference/Bug11.java
new file mode 100644
index 0000000..ddc2bfb
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug11.java
@@ -0,0 +1,22 @@
+package inference.guava;
+
+import java.io.Serializable;
+import java.util.EnumMap;
+import java.util.Map;
+
+@SuppressWarnings("all") // Check for crashes.
+public class Bug11 {
+
+  public static <K1, V1> MyMap<K1, V1> copyOf(Map<? extends K1, ? extends V1> map) {
+    @SuppressWarnings("unchecked")
+    MyMap<K1, V1> kvMap = (MyMap<K1, V1>) copyOfEnumMap((EnumMap<?, ?>) map);
+    return kvMap;
+  }
+
+  private static <K2 extends Enum<K2>, V2> MyMap<K2, V2> copyOfEnumMap(
+      EnumMap<K2, ? extends V2> original) {
+    throw new RuntimeException();
+  }
+
+  public abstract static class MyMap<K3, V3> implements Map<K3, V3>, Serializable {}
+}
diff --git a/framework/tests/all-systems/java8inference/Bug12.java b/framework/tests/all-systems/java8inference/Bug12.java
new file mode 100644
index 0000000..69402ad
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug12.java
@@ -0,0 +1,17 @@
+package inference.guava;
+
+import java.util.Map;
+
+public class Bug12 {
+
+  private static Map<? extends Enum, LockGraphNode> getOrCreateNodes(
+      Map<? extends Enum, LockGraphNode> existing, Map<? extends Enum, LockGraphNode> created) {
+    return firstNonNull(existing, created);
+  }
+
+  public static <T> T firstNonNull(T first, T second) {
+    throw new RuntimeException();
+  }
+
+  private static class LockGraphNode {}
+}
diff --git a/framework/tests/all-systems/java8inference/Bug13.java b/framework/tests/all-systems/java8inference/Bug13.java
new file mode 100644
index 0000000..2f5c994
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug13.java
@@ -0,0 +1,36 @@
+package inference.guava;
+
+import java.util.Spliterator;
+import java.util.stream.Stream;
+
+@SuppressWarnings("all") // Just check for crashes.
+public class Bug13 {
+
+  public static class MyClass<X> extends MySuperClass<X> {
+    static <Z> Stream<Z> stream(MyClass<Z> s, Iterable<Z> iterable) {
+      throw new RuntimeException();
+    }
+
+    public void method(final Iterable<X> iterable) {
+      Spliterator<X> x = Stream.generate(() -> iterable).flatMap(super::stream).spliterator();
+    }
+  }
+
+  public static class MySuperClass<Z> {
+    Stream<Z> stream(Iterable<Z> iterable) {
+      throw new RuntimeException();
+    }
+
+    static <Z> Stream<Z> stream(MySuperClass<Z> s, Iterable<Z> iterable) {
+      throw new RuntimeException();
+    }
+  }
+
+  public static <Q> void method(final Iterable<Q> iterable, MyClass<Q> myClass) {
+    Spliterator<Q> x = Stream.generate(() -> iterable).flatMap(myClass::stream).spliterator();
+  }
+
+  public static <Z> Stream<Z> stream(Iterable<Z> iterable) {
+    throw new RuntimeException();
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug14.java b/framework/tests/all-systems/java8inference/Bug14.java
new file mode 100644
index 0000000..04ac6bb
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug14.java
@@ -0,0 +1,49 @@
+import java.io.Serializable;
+import java.util.AbstractCollection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.RandomAccess;
+import java.util.stream.Collector;
+
+@SuppressWarnings({"unchecked", "all"})
+public class Bug14 {
+  private static final Collector<Object, ?, ImmutableList<Object>> TO_IMMUTABLE_LIST =
+      Collector.of(
+          ImmutableList::<Object>builder,
+          ImmutableList.Builder::add,
+          ImmutableList.Builder::combine,
+          ImmutableList.Builder::build);
+
+  public abstract static class ImmutableList<E> extends ImmutableCollection<E>
+      implements List<E>, RandomAccess {
+    public static final class Builder<E> {
+
+      public Builder<E> add(E element) {
+        return this;
+      }
+
+      public Builder<E> add(E... elements) {
+        return this;
+      }
+
+      public Builder<E> addAll(Iterator<? extends E> elements) {
+        return this;
+      }
+
+      public ImmutableList<E> build() {
+        throw new RuntimeException();
+      }
+
+      Builder<E> combine(Builder<E> builder) {
+        return this;
+      }
+    }
+
+    public static <E> Builder<E> builder() {
+      return new Builder<E>();
+    }
+  }
+
+  public abstract static class ImmutableCollection<E> extends AbstractCollection<E>
+      implements Serializable {}
+}
diff --git a/framework/tests/all-systems/java8inference/Bug15.java b/framework/tests/all-systems/java8inference/Bug15.java
new file mode 100644
index 0000000..43484fa
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug15.java
@@ -0,0 +1,14 @@
+package inference;
+
+import java.util.Map.Entry;
+
+@SuppressWarnings("all") // check for crashes
+public class Bug15<B> {
+  public void putAll(Entry<? extends Class<? extends B>, B> entry) {
+    cast(entry.getKey(), entry.getValue());
+  }
+
+  private static <F, T extends F> T cast(Class<T> type, F value) {
+    throw new RuntimeException();
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug16.java b/framework/tests/all-systems/java8inference/Bug16.java
new file mode 100644
index 0000000..c1e368d
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug16.java
@@ -0,0 +1,20 @@
+package inference;
+
+public class Bug16 {
+
+  public interface Interface<K1, V1> {}
+
+  private static class Implementation<K, V> implements Interface<K, V> {
+    Implementation(Interface<? extends K, ? extends V> delegate, Interface<V, K> inverse) {}
+
+    Implementation(Interface<V, K> inverse, int o) {}
+
+    Implementation(Interface<? extends K, ? extends V> delegate) {}
+
+    void test(Interface<V, K> param) {
+      Interface<V, K> inverse1 = new Implementation<>(param, this);
+      Interface<V, K> inverse2 = new Implementation<>(param);
+      Interface<V, K> inverse3 = new Implementation<>(this, 1);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug2.java b/framework/tests/all-systems/java8inference/Bug2.java
new file mode 100644
index 0000000..933e3f7
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug2.java
@@ -0,0 +1,26 @@
+import java.io.Serializable;
+import java.util.AbstractMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+@SuppressWarnings("all") // Just check for crashes.
+public class Bug2 {
+
+  public <C, D> ConcurrentMap<C, D> makeMap() {
+    return Bug2.create(this);
+  }
+
+  static <A, B> MyMap<A, B, ? extends MyMap.MyEntry<A, B, ?>, ?> create(Bug2 builder) {
+    throw new RuntimeException();
+  }
+
+  abstract static class MyMap<
+          E, F, G extends MyMap.MyEntry<E, F, G>, H extends MyMap.MyLock<E, F, G, H>>
+      extends AbstractMap<E, F> implements ConcurrentMap<E, F>, Serializable {
+
+    interface MyEntry<I, J, K extends MyEntry<I, J, K>> {}
+
+    abstract static class MyLock<L, M, N extends MyEntry<L, M, N>, O extends MyLock<L, M, N, O>>
+        extends ReentrantLock {}
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug3.java b/framework/tests/all-systems/java8inference/Bug3.java
new file mode 100644
index 0000000..453da07
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug3.java
@@ -0,0 +1,28 @@
+package inference.guava;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+@SuppressWarnings("all") // Just check for crashes.
+public class Bug3 {
+
+  public abstract static class MySet<E> implements Set<E> {
+
+    public static <E> MySet<E> of() {
+      throw new RuntimeException("");
+    }
+
+    public static <E> MySet<E> of(E e) {
+      throw new RuntimeException("");
+    }
+  }
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  static MySet asMySet(EnumSet set) {
+    return MySet.of(getElement(set));
+  }
+
+  public static <T> T getElement(Iterable<T> iterable) {
+    throw new RuntimeException();
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug4.java b/framework/tests/all-systems/java8inference/Bug4.java
new file mode 100644
index 0000000..1f8d244
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug4.java
@@ -0,0 +1,16 @@
+package inference.guava;
+
+import java.lang.reflect.GenericDeclaration;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+@SuppressWarnings("all") // Just check for crashes.
+public class Bug4 {
+  Type resolveInternal(TypeVariable<?> var, Type[] types) {
+    return method(var.getGenericDeclaration(), var.getName(), types);
+  }
+
+  static <D extends GenericDeclaration> TypeVariable<D> method(D d, String n, Type... bounds) {
+    throw new RuntimeException();
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug5.java b/framework/tests/all-systems/java8inference/Bug5.java
new file mode 100644
index 0000000..e609304
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug5.java
@@ -0,0 +1,22 @@
+package inference.guava;
+
+import java.util.Map;
+import java.util.function.Predicate;
+
+@SuppressWarnings("all") // Just check for crashes.
+public class Bug5<K, V> {
+
+  boolean apply(Object key, V value, MyPredicate<? super Map.Entry<K, V>> predicate) {
+    @SuppressWarnings("unchecked")
+    K k = (K) key;
+    return predicate.apply(immutableEntry(k, value));
+  }
+
+  public static <K, V> Map.Entry<K, V> immutableEntry(K key, V value) {
+    throw new RuntimeException();
+  }
+
+  public interface MyPredicate<T> extends Predicate<T> {
+    boolean apply(T input);
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug6.java b/framework/tests/all-systems/java8inference/Bug6.java
new file mode 100644
index 0000000..9a6e41d
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug6.java
@@ -0,0 +1,32 @@
+package inference.guava;
+
+import java.util.Iterator;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+@SuppressWarnings("all") // Just check for crashes.
+public class Bug6 {
+  public static <Q> Iterable<Q> method(final Iterable<Q> iterable) {
+    return new Iterable<Q>() {
+      @Override
+      public Iterator<Q> iterator() {
+        throw new RuntimeException();
+      }
+
+      @Override
+      public void forEach(Consumer<? super Q> action) {
+        throw new RuntimeException();
+      }
+
+      @Override
+      public Spliterator<Q> spliterator() {
+        return Stream.generate(() -> iterable).flatMap(Bug6::stream).spliterator();
+      }
+    };
+  }
+
+  public static <Z> Stream<Z> stream(Iterable<Z> iterable) {
+    throw new RuntimeException();
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug7.java b/framework/tests/all-systems/java8inference/Bug7.java
new file mode 100644
index 0000000..d646627
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug7.java
@@ -0,0 +1,38 @@
+package inference.guava;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collector;
+
+@SuppressWarnings("all") // Just check for crashes.
+public class Bug7 {
+  static <T, K, V> Collector<T, ?, MyMap<K, V>> toMap(
+      Function<? super T, ? extends K> keyFunction,
+      Function<? super T, ? extends V> valueFunction) {
+    return Collector.of(
+        MyMap.Builder<K, V>::new,
+        (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)),
+        MyMap.Builder::combine,
+        MyMap.Builder::build);
+  }
+
+  public abstract static class MyMap<K, V> implements Map<K, V>, Serializable {
+    public static class Builder<K, V> {
+
+      public Builder() {}
+
+      public Builder<K, V> put(K key, V value) {
+        throw new RuntimeException();
+      }
+
+      Builder<K, V> combine(Builder<K, V> other) {
+        throw new RuntimeException();
+      }
+
+      public MyMap<K, V> build() {
+        throw new RuntimeException();
+      }
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug8.java b/framework/tests/all-systems/java8inference/Bug8.java
new file mode 100644
index 0000000..7b6104c
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug8.java
@@ -0,0 +1,75 @@
+package inference.bug8;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collector;
+
+@SuppressWarnings("all") // Just check for crashes.
+public class Bug8 {
+
+  static <T1, K1, V1> Collector<T1, ?, MyMap<K1, V1>> toImmutableMap(
+      Function<? super T1, ? extends K1> keyFunction,
+      Function<? super T1, ? extends V1> valueFunction) {
+    return Collector.of(
+        MyMap.Builder<K1, V1>::new,
+        (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)),
+        MyMap.Builder::combine,
+        MyMap.Builder::build);
+  }
+
+  static <T, K, V> Collector<T, ?, MyBiMap<K, V>> toImmutableBiMap(
+      Function<? super T, ? extends K> keyFunction,
+      Function<? super T, ? extends V> valueFunction) {
+    return Collector.of(
+        MyBiMap.Builder<K, V>::new,
+        (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)),
+        MyBiMap.Builder::combine,
+        MyBiMap.Builder::build,
+        new Collector.Characteristics[0]);
+  }
+
+  abstract static class ShimMap<K, V> extends MyMap<K, V> {}
+
+  public interface BiMap<K, V> extends Map<K, V> {}
+
+  public abstract static class MyBiMap<K, V> extends ShimMap<K, V> implements BiMap<K, V> {
+    public static final class Builder<K, V> extends MyMap.Builder<K, V> {
+      public Builder() {}
+
+      @Override
+      public MyBiMap.Builder<K, V> put(K key, V value) {
+        throw new RuntimeException();
+      }
+
+      @Override
+      MyBiMap.Builder<K, V> combine(MyMap.Builder<K, V> builder) {
+        super.combine(builder);
+        return this;
+      }
+
+      @Override
+      public MyBiMap<K, V> build() {
+        throw new RuntimeException();
+      }
+    }
+  }
+
+  public abstract static class MyMap<K, V> implements Map<K, V>, Serializable {
+    public static class Builder<K, V> {
+      public Builder() {}
+
+      public MyMap.Builder<K, V> put(K key, V value) {
+        throw new RuntimeException();
+      }
+
+      MyMap.Builder<K, V> combine(MyMap.Builder<K, V> other) {
+        throw new RuntimeException();
+      }
+
+      public MyMap<K, V> build() {
+        throw new RuntimeException();
+      }
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Bug9.java b/framework/tests/all-systems/java8inference/Bug9.java
new file mode 100644
index 0000000..88f0523
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Bug9.java
@@ -0,0 +1,27 @@
+package inference.guava;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Spliterator;
+import java.util.function.Function;
+
+@SuppressWarnings("all") // Just check for crashes.
+public class Bug9<K, Z> {
+  private transient Map<K, Collection<Z>> map;
+
+  Spliterator<Z> valueSpliterator() {
+    return flatMap(map.values().spliterator(), Collection::spliterator, Spliterator.SIZED, size());
+  }
+
+  static <F, T> Spliterator<T> flatMap(
+      Spliterator<F> fromSpliterator,
+      Function<? super F, Spliterator<T>> function,
+      int topCharacteristics,
+      long topSize) {
+    throw new RuntimeException();
+  }
+
+  public int size() {
+    throw new RuntimeException();
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/CollectorsToList.java b/framework/tests/all-systems/java8inference/CollectorsToList.java
new file mode 100644
index 0000000..3d915e7
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/CollectorsToList.java
@@ -0,0 +1,29 @@
+// Test case for issue #979:
+// https://github.com/typetools/checker-framework/issues/979
+
+// @skip-test until the bug is fixed
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class CollectorsToList {
+
+  void m(List<String> strings) {
+    Stream<String> s = strings.stream();
+
+    // This works:
+    List<String> collectedStrings1 = s.collect(Collectors.<String>toList());
+    // This works:
+    List<@Nullable String> collectedStrings2 = s.collect(Collectors.toList());
+    // This works:
+    @SuppressWarnings("nullness")
+    List<String> collectedStrings3 = s.collect(Collectors.toList());
+
+    // This assignment issues a warning due to incompatible types:
+    List<String> collectedStrings = s.collect(Collectors.toList());
+
+    collectedStrings.forEach(System.out::println);
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1308.java b/framework/tests/all-systems/java8inference/Issue1308.java
new file mode 100644
index 0000000..01fe016
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1308.java
@@ -0,0 +1,29 @@
+// Test case for Issue 1308.
+// https://github.com/typetools/checker-framework/issues/1308
+
+import java.util.function.Function;
+import java.util.stream.Collector;
+import java.util.stream.Stream;
+
+class Map1308<K, V> {}
+
+@SuppressWarnings("all") // check for crashes
+public class Issue1308 {
+  void bar(Stream<Number> stream) {
+    new Inner(stream.collect(transform(data -> convert(data), Function.identity())));
+  }
+
+  String convert(Number entry) {
+    return "";
+  }
+
+  class Inner {
+    Inner(Map1308<String, Number> data) {}
+  }
+
+  static <T, K, V> Collector<T, ?, Map1308<K, V>> transform(
+      Function<? super T, ? extends K> keyFunction,
+      Function<? super T, ? extends V> valueFunction) {
+    return null;
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1312.java b/framework/tests/all-systems/java8inference/Issue1312.java
new file mode 100644
index 0000000..80daf7e
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1312.java
@@ -0,0 +1,18 @@
+// Test case for Issue 1312.
+// https://github.com/typetools/checker-framework/issues/1312
+
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+class SimpleEntry1312<K, V> {
+  SimpleEntry1312(K k, V v) {}
+}
+
+@SuppressWarnings("all") // check for crashes
+public class Issue1312 {
+  Map<SimpleEntry1312, List<SimpleEntry1312>> x =
+      Stream.of(Stream.of(new SimpleEntry1312<>("A", "B")))
+          .flatMap(Function.identity())
+          .collect(Collectors.groupingBy(e -> e));
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1313.java b/framework/tests/all-systems/java8inference/Issue1313.java
new file mode 100644
index 0000000..3fadb8e
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1313.java
@@ -0,0 +1,17 @@
+// Test case for Issue 1313.
+// https://github.com/typetools/checker-framework/issues/1313
+
+import java.util.stream.Collector;
+import java.util.stream.Stream;
+
+interface MyList1313<E> extends Iterable<E> {}
+
+@SuppressWarnings({"all", "type.inference.not.same"}) // check for crashes
+public class Issue1313 {
+  Stream<?> s;
+  Iterable<?> i = s.collect(toMyList1313());
+
+  <F> Collector<F, ?, MyList1313<F>> toMyList1313() {
+    return null;
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1331.java b/framework/tests/all-systems/java8inference/Issue1331.java
new file mode 100644
index 0000000..1f201e5
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1331.java
@@ -0,0 +1,15 @@
+// Test case for Issue 1331.
+// https://github.com/typetools/checker-framework/issues/1331
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@SuppressWarnings("all") // check for crashes
+public class Issue1331 {
+  List<Long> ll;
+  long result = getOnlyElement(ll.stream().collect(Collectors.toSet()));
+
+  static <T> T getOnlyElement(Iterable<T> iterable) {
+    return null;
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1332.java b/framework/tests/all-systems/java8inference/Issue1332.java
new file mode 100644
index 0000000..41dffed
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1332.java
@@ -0,0 +1,29 @@
+// Test case for Issue 1332.
+// https://github.com/typetools/checker-framework/issues/1332
+
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@SuppressWarnings("all") // check for crashes
+abstract class Issue1332 {
+  void foo(List<Long> ll) {
+    Function<String, Long> test =
+        s -> {
+          long result = getOnlyElement(ll.stream().collect(Collectors.toSet()));
+          return result;
+        };
+  }
+
+  abstract <T> T getOnlyElement(Iterable<T> iterable);
+
+  private void test1() {
+    byte[][] byteArrayArray = new byte[][] {};
+    Stream<byte[]> stream = Stream.of(byteArrayArray);
+  }
+
+  private void test2() {
+    Stream<byte[]> stream = Stream.of(new byte[][] {});
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1334.java b/framework/tests/all-systems/java8inference/Issue1334.java
new file mode 100644
index 0000000..fbb9b53
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1334.java
@@ -0,0 +1,10 @@
+// Test case for Issue 1334
+// https://github.com/typetools/checker-framework/issues/1334
+
+import java.util.stream.Stream;
+
+public class Issue1334 {
+  private void test() {
+    Stream<Integer> stream = Stream.of(new byte[][] {}).map(b -> 1);
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1377.java b/framework/tests/all-systems/java8inference/Issue1377.java
new file mode 100644
index 0000000..3485978
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1377.java
@@ -0,0 +1,24 @@
+// Test case for Issue 1377.
+// https://github.com/typetools/checker-framework/issues/1377
+
+interface Func1377<P, R> {
+  R apply(P p);
+}
+
+@SuppressWarnings("all") // just check for crashes
+interface Issue1377<V> {
+  static <U> Issue1377<U> of(Issue1377<U> in) {
+    return in;
+  }
+
+  <S> Issue1377<S> m1(Func1377<? super V, S> f);
+
+  <T> Issue1377<T> m2(Func1377<V, T> f);
+}
+
+@SuppressWarnings("all") // just check for crashes
+class Crash1377 {
+  void foo(Issue1377<Void> p) {
+    Issue1377.of(p.m1(in -> p)).m2(empty -> 5);
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1379.java b/framework/tests/all-systems/java8inference/Issue1379.java
new file mode 100644
index 0000000..dec2dc2
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1379.java
@@ -0,0 +1,21 @@
+// Test case for Issue 1379.
+// https://github.com/typetools/checker-framework/issues/1379
+
+interface Box1379<V> {}
+
+interface Trans1379<I, O> {
+  Box1379<O> apply(I in);
+}
+
+@SuppressWarnings("all") // just check for crashes
+abstract class Issue1379 {
+  abstract <I, O> Box1379<O> app(Box1379<I> in, Trans1379<? super I, ? extends O> t);
+
+  abstract <I, O> Trans1379<I, O> pass(Trans1379<I, O> t);
+
+  abstract Box1379<Number> box(Number p);
+
+  void foo(Box1379<Number> p) {
+    app(p, pass(this::box));
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1397.java b/framework/tests/all-systems/java8inference/Issue1397.java
new file mode 100644
index 0000000..8ad4572
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1397.java
@@ -0,0 +1,19 @@
+// Test case for Issue 1397.
+// https://github.com/typetools/checker-framework/issues/1397
+
+public class Issue1397 {
+
+  class Box<T> {}
+
+  abstract class CrashCompound {
+    abstract <T> T chk(T in);
+
+    abstract <T> T unbox(Box<T> p);
+
+    @SuppressWarnings("units")
+    void foo(Box<Boolean> bb) {
+      boolean res = false;
+      res |= chk(unbox(bb));
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1398.java b/framework/tests/all-systems/java8inference/Issue1398.java
new file mode 100644
index 0000000..caf4e4c
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1398.java
@@ -0,0 +1,31 @@
+// Test case for Issue 1398
+// https://github.com/typetools/checker-framework/issues/1398
+
+public class Issue1398 {
+
+  interface Pair<A, B> {}
+
+  interface Triple<A, B, C> {}
+
+  interface Quadruple<A, B, C, D> {}
+
+  interface Box<T> {
+    <A, B> Pair<A, B> doTriple(Triple<? super T, A, B> t);
+
+    <A, BA extends Box<A>> BA doPair(Pair<? super T, ? extends A> p, BoxMaker<A, BA> bm);
+  }
+
+  class BoxMaker<T, C extends Box<T>> {}
+
+  abstract class Crash7 {
+    abstract <T, O> Pair<T, O> bar(Pair<T, O> in);
+
+    void foo(
+        Box<String> bs,
+        BoxMaker<Number, Box<Number>> bm,
+        Pair<String, Number> psn,
+        Triple<Number, Object, Number> t) {
+      bs.doPair(bar(psn), bm).doTriple(t);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1399.java b/framework/tests/all-systems/java8inference/Issue1399.java
new file mode 100644
index 0000000..d4eb903
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1399.java
@@ -0,0 +1,20 @@
+// Test case for Issue 1399.
+// https://github.com/typetools/checker-framework/issues/1399
+
+public class Issue1399 {
+  static class Box<T> {
+    static <T> Box<T> box(Class<T> type) {
+      return new Box<T>();
+    }
+
+    void act(T instance) {}
+  }
+
+  abstract class Math {
+    abstract <T> Box<T> id(Box<T> in);
+
+    void foo(Math m) {
+      m.id(Box.box(Long.class)).act(10L);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1407.java b/framework/tests/all-systems/java8inference/Issue1407.java
new file mode 100644
index 0000000..375845d
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1407.java
@@ -0,0 +1,13 @@
+// Test case for Issue 1407.
+// https://github.com/typetools/checker-framework/issues/1407
+
+abstract class Issue1407 {
+  abstract <T> T foo(T p1, T p2);
+
+  abstract <T extends Number> T bar(int p1, T p2);
+
+  @SuppressWarnings({"interning", "signedness"})
+  int demo() {
+    return foo(bar(5, 3), 3);
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1408.java b/framework/tests/all-systems/java8inference/Issue1408.java
new file mode 100644
index 0000000..5cfae7d
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1408.java
@@ -0,0 +1,15 @@
+// Test case for Issue 1408.
+// https://github.com/typetools/checker-framework/issues/1408
+abstract class Issue1408 {
+  interface Demo {}
+
+  interface SubDemo extends Demo {}
+
+  abstract <S> S foo(S p1, S p2);
+
+  abstract <T extends Demo> T bar(T p2);
+
+  SubDemo demo(SubDemo p) {
+    return foo(bar(p), p);
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1415.java b/framework/tests/all-systems/java8inference/Issue1415.java
new file mode 100644
index 0000000..5af1832
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1415.java
@@ -0,0 +1,26 @@
+// Test case for Issue 1415.
+// https://github.com/typetools/checker-framework/issues/1415
+
+@SuppressWarnings("all") // Check for crashes.
+public class Issue1415 {
+  static class Optional<T> {
+    static <T> Optional<T> absent() {
+      return null;
+    }
+
+    static <T> Optional<T> of(T p) {
+      return null;
+    }
+  }
+
+  static class Box<T> {
+    void box(T p) {}
+  }
+
+  static class Crash9 {
+    <F extends Enum<F>> void foo(boolean b, Box<Optional<F>> box, Class<F> enumClass) {
+      box.box(b ? Optional.<F>absent() : Optional.of(Enum.valueOf(enumClass, "hi")));
+      box.box(b ? Optional.absent() : Optional.of(Enum.valueOf(enumClass, "hi")));
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1416.java b/framework/tests/all-systems/java8inference/Issue1416.java
new file mode 100644
index 0000000..c7cae68
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1416.java
@@ -0,0 +1,12 @@
+// Test case for Issue 1416.
+// https://github.com/typetools/checker-framework/issues/1416
+
+import java.util.Comparator;
+import java.util.stream.Stream;
+
+public class Issue1416 {
+  @SuppressWarnings("signedness")
+  long order(Stream<Long> sl) {
+    return sl.max(Comparator.naturalOrder()).orElse(0L);
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1417.java b/framework/tests/all-systems/java8inference/Issue1417.java
new file mode 100644
index 0000000..52f86b0
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1417.java
@@ -0,0 +1,20 @@
+// Test case for Issue 1417.
+// https://github.com/typetools/checker-framework/issues/1417
+
+public class Issue1417 {
+  interface Bar {}
+
+  interface SubBar extends Bar {}
+
+  interface Barber<S extends Bar> {
+    S call(S s);
+  }
+
+  abstract class Crash12 {
+    abstract void foo(Barber<?> b);
+
+    void crash() {
+      foo((SubBar p) -> p);
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1419.java b/framework/tests/all-systems/java8inference/Issue1419.java
new file mode 100644
index 0000000..8cc1b24
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1419.java
@@ -0,0 +1,15 @@
+// Test case for Issue 1419.
+// https://github.com/typetools/checker-framework/issues/1419
+
+abstract class Issue1419 {
+  class Map<A> {}
+
+  class EnumMap<C extends Enum<C>> extends Map<C> {}
+
+  abstract <E extends Enum<E>> Map<E> foo(Map<E> map);
+
+  @SuppressWarnings("unchecked")
+  <G> Map<G> bar(Map<? extends G> map) {
+    return foo((EnumMap) map);
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1424.java b/framework/tests/all-systems/java8inference/Issue1424.java
new file mode 100644
index 0000000..5c9672d
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1424.java
@@ -0,0 +1,25 @@
+// Test case for Issue 1424.
+// https://github.com/typetools/checker-framework/issues/1424
+
+@SuppressWarnings("unchecked")
+abstract class Issue1424 {
+  class Box<T> {}
+
+  interface Callable<V> {
+    V call() throws Exception;
+  }
+
+  class MyCallable<T> implements Callable<T> {
+    MyCallable(Callable<T> delegate) {}
+
+    public T call() throws Exception {
+      throw new RuntimeException();
+    }
+  }
+
+  abstract <T> Box<T> submit(Callable<T> t);
+
+  Box<Boolean> foo() {
+    return submit(new MyCallable(() -> true));
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1715.java b/framework/tests/all-systems/java8inference/Issue1715.java
new file mode 100644
index 0000000..948329f
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1715.java
@@ -0,0 +1,32 @@
+import java.util.List;
+import java.util.function.Function;
+
+public class Issue1715 {
+
+  static final class A {
+
+    public A() {}
+
+    public Object foo(Object o) {
+      return o;
+    }
+  }
+
+  private Observable<List<Function<Object, Object>>> test(A a) {
+    return Observable.just(ImmutableList.of(a::foo));
+  }
+
+  static class Observable<F> {
+
+    static <T> Observable<T> just(T param) {
+      throw new RuntimeException();
+    }
+  }
+
+  public abstract static class ImmutableList<E> implements List<E> {
+
+    public static <E> ImmutableList<E> of(E element) {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1775.java b/framework/tests/all-systems/java8inference/Issue1775.java
new file mode 100644
index 0000000..d4994a7
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1775.java
@@ -0,0 +1,17 @@
+// Test case for issue 1775
+// https://github.com/typetools/checker-framework/issues/1775
+
+@SuppressWarnings("all") // just check for crashes
+public class Issue1775 {
+  interface Box<A> {
+    <B extends A> B get();
+  }
+
+  <S extends String> Box<S[]> getBox() {
+    return null;
+  }
+
+  void m() {
+    for (String s : getBox().get()) {}
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue1815.java b/framework/tests/all-systems/java8inference/Issue1815.java
new file mode 100644
index 0000000..8b29216
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue1815.java
@@ -0,0 +1,21 @@
+// Test case for Issue 1815:
+// https://github.com/typetools/checker-framework/issues/1815
+
+import java.util.List;
+import java.util.stream.Stream;
+
+@SuppressWarnings("all") // just check for crashes
+abstract class Issue1815 {
+
+  class A extends B {}
+
+  static class B<One extends B<One, Two>, Two extends B.I<One, Two>> {
+    static class I<Three extends B<Three, Four>, Four extends I<Three, Four>> {}
+  }
+
+  abstract A f(Integer x);
+
+  void test(List<Integer> xs) {
+    Stream.of(xs.stream().map(x -> f(x)), xs.stream().map(x -> f(x))).flatMap(stream -> stream);
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue2975.java b/framework/tests/all-systems/java8inference/Issue2975.java
new file mode 100644
index 0000000..570cfc4
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue2975.java
@@ -0,0 +1,20 @@
+import java.io.Closeable;
+import java.util.function.Consumer;
+
+public class Issue2975<T extends AutoCloseable> {
+  static class Child extends Issue2975<Closeable> {
+    Wrapper y = new Wrapper(Child::takesCloseable);
+
+    private static void takesCloseable(Closeable rhs) {}
+  }
+
+  protected class Wrapper {
+    protected Wrapper() {}
+
+    protected Wrapper(Consumer<T> makeExpression) {}
+
+    protected Wrapper method(Consumer<T> makeExpression) {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue3032.java b/framework/tests/all-systems/java8inference/Issue3032.java
new file mode 100644
index 0000000..b2054db
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue3032.java
@@ -0,0 +1,46 @@
+import java.io.Serializable;
+
+public class Issue3032 {
+  public static class PCollection<T> implements PValue {
+    public <OutputT extends POutput> OutputT apply(
+        String name, PTransform<? super PCollection<T>, OutputT> t) {
+      throw new RuntimeException();
+    }
+  }
+
+  public abstract static class PTransform<InputT extends PInput, OutputT extends POutput> {}
+
+  interface PInput {}
+
+  interface POutput {}
+
+  interface PValue extends PInput, POutput {}
+
+  static class BillingEvent {
+    InvoiceGroupingKey getInvoiceGroupingKey() {
+      throw new RuntimeException();
+    }
+  }
+
+  public static class MapElements<InputT, OutputT>
+      extends PTransform<PCollection<? extends InputT>, PCollection<OutputT>> {
+    public static <InputT, OutputT> MapElements<InputT, OutputT> via(
+        final ProcessFunction<InputT, OutputT> fn) {
+      throw new RuntimeException();
+    }
+  }
+
+  static class InvoiceGroupingKey {}
+
+  @FunctionalInterface
+  public interface ProcessFunction<InputT, OutputT> extends Serializable {
+    OutputT apply(InputT input) throws Exception;
+  }
+
+  private static class GenerateInvoiceRows
+      extends PTransform<PCollection<BillingEvent>, PCollection<String>> {
+    public void expand(PCollection<BillingEvent> input) {
+      input.apply("Map to invoicing key", MapElements.via(BillingEvent::getInvoiceGroupingKey));
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue3036.java b/framework/tests/all-systems/java8inference/Issue3036.java
new file mode 100644
index 0000000..0a5eefa
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue3036.java
@@ -0,0 +1,48 @@
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class Issue3036 {
+
+  public Set<MyInnerClass> getDsData() {
+    throw new RuntimeException();
+  }
+
+  public static class MyInnerClass {
+    public int getKeyTag() {
+      return 5;
+    }
+
+    public String getDigest() {
+      return "";
+    }
+  }
+
+  private void write(Stream<MyInnerClass> stream) {
+    Function<MyInnerClass, ImmutableMap<String, ? extends Serializable>> mapper =
+        dsData1 ->
+            ImmutableMap.of(
+                "keyTag", dsData1.getKeyTag(),
+                "digest", dsData1.getDigest());
+
+    List<Map<String, ?>> dsData =
+        getDsData().stream()
+            .map(
+                dsData1 ->
+                    ImmutableMap.of(
+                        "keyTag", dsData1.getKeyTag(),
+                        "digest", dsData1.getDigest()))
+            .collect(Collectors.toList());
+  }
+
+  public static class ImmutableMap<K, V> extends HashMap<K, V> {
+    public static <K, V> ImmutableMap<K, V> of(K k1, V v1, K k2, V v2) {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue404.java b/framework/tests/all-systems/java8inference/Issue404.java
new file mode 100644
index 0000000..a0c1b1d
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue404.java
@@ -0,0 +1,17 @@
+// Test case that was submitted in Issue 404, but was combined with Issue 979
+// https://github.com/typetools/checker-framework/issues/979
+
+// @skip-test until the bug is fixed
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+// TODO: inference problem with dependencies between multiple methods.
+// Eventually, the test should work when executed on >= Java 8.
+// @skip-test
+public final class Issue404 {
+  public Set<String> uniqueTrimmed(final Collection<String> strings) {
+    return strings.stream().map(String::trim).collect(Collectors.toSet());
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Issue953.java b/framework/tests/all-systems/java8inference/Issue953.java
new file mode 100644
index 0000000..e08b75e
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Issue953.java
@@ -0,0 +1,19 @@
+// Test case that was submitted in Issue 953, but was combined with Issue 979
+// https://github.com/typetools/checker-framework/issues/979
+
+// @skip-test until the bug is fixed
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class Issue953 {
+  public static void test() {
+    List<String> initial = new ArrayList<>();
+    List<Integer> counts =
+        initial.stream().skip(1).map(l -> count("", l)).collect(Collectors.toList());
+  }
+
+  private static int count(String s, String l) {
+    return 0;
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/MemRefInfere.java b/framework/tests/all-systems/java8inference/MemRefInfere.java
new file mode 100644
index 0000000..225c70a
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/MemRefInfere.java
@@ -0,0 +1,25 @@
+package inference;
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+public abstract class MemRefInfere<K, V> implements Map<K, V>, Serializable {
+  public static <K1, V1> MemRefInfere<K1, V1> copyOf(Map<? extends K1, ? extends V1> map) {
+    throw new RuntimeException();
+  }
+
+  public static <T, K2, V2> Collector<T, ?, MemRefInfere<K2, V2>> toImmutableMap(
+      Function<? super T, ? extends K2> keyFunction,
+      Function<? super T, ? extends V2> valueFunction,
+      BinaryOperator<V2> mergeFunction) {
+
+    return Collectors.collectingAndThen(
+        Collectors.toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new),
+        MemRefInfere::copyOf);
+  }
+}
diff --git a/framework/tests/all-systems/java8inference/Misc.java b/framework/tests/all-systems/java8inference/Misc.java
new file mode 100644
index 0000000..a780b20
--- /dev/null
+++ b/framework/tests/all-systems/java8inference/Misc.java
@@ -0,0 +1,20 @@
+@SuppressWarnings({"unchecked", "all"}) // check for crashes
+public class Misc<T> {
+  Misc<? super T> forward;
+
+  public <E extends T> E min(E a, E b) {
+    return forward.max(a, b);
+  }
+
+  public <E extends T> E min(E a, E b, E c, E... rest) {
+    return forward.max(a, b, c, rest);
+  }
+
+  public <E extends T> E max(E a, E b) {
+    return forward.min(a, b);
+  }
+
+  public <E extends T> E max(E a, E b, E c, E... rest) {
+    return forward.min(a, b, c, rest);
+  }
+}
diff --git a/framework/tests/annotationclassloader/Expected.txt b/framework/tests/annotationclassloader/Expected.txt
new file mode 100644
index 0000000..647ee14
--- /dev/null
+++ b/framework/tests/annotationclassloader/Expected.txt
@@ -0,0 +1,4 @@
+LoaderTest.java:7: error: (unique.leaked)
+    Object[] ar = new Object[] {o};
+                                ^
+1 error
diff --git a/framework/tests/annotationclassloader/LoaderTest.java b/framework/tests/annotationclassloader/LoaderTest.java
new file mode 100644
index 0000000..69e50fd
--- /dev/null
+++ b/framework/tests/annotationclassloader/LoaderTest.java
@@ -0,0 +1,9 @@
+import org.checkerframework.common.aliasing.qual.Unique;
+
+public class LoaderTest {
+  void foo() {
+    @Unique Object o = new Object();
+    // :: error: (unique.leaked)
+    Object[] ar = new Object[] {o};
+  }
+}
diff --git a/framework/tests/annotationclassloader/Makefile b/framework/tests/annotationclassloader/Makefile
new file mode 100644
index 0000000..1b84c40
--- /dev/null
+++ b/framework/tests/annotationclassloader/Makefile
@@ -0,0 +1,77 @@
+# This makefile contains the commands to test the AnnotationClassLoader.
+# Tt uses the aliasing checker as it is one of the framework checkers that has a
+# qual directory with qualifiers that must be loaded using the loader.
+
+# Gets the full path to the directory of the make file, which is also the root
+# directory of the qual folder.
+# For custom projects, it is best to encode the full root path as a variable.
+PROJECTDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+
+# Use the Checker Framework javac.
+# TODO: the dependency on the checker project isn't nice.
+JAVAC = $(PROJECTDIR)/../../../checker/bin/javac
+
+FRAMEWORKJAR := $(PROJECTDIR)/../../dist/framework.jar
+
+STUBPARSERJAR := $(PROJECTDIR)/../../../../stubparser/javaparser-core/target/stubparser.jar
+
+CHECKERQUALJAR := $(PROJECTDIR)/../../../checker/dist/checker-qual.jar
+
+# build directories
+DATAFLOWBUILD := $(PROJECTDIR)/../../../dataflow/build/classes/java/main
+JAVACUTILBUILD := $(PROJECTDIR)/../../../javacutil/build/classes/java/main
+FRAMEWORKBUILD := $(PROJECTDIR)/../../build/classes/java/main:$(PROJECTDIR)/../../build/resources/main/
+CHECKERQUALBUILD := $(PROJECTDIR)/../../../checker-qual/build/classes/java/main
+
+
+all: load-from-dir-test load-from-jar-test
+
+# ======================================================
+# demo or manual test usage:
+# loads from build directories
+demo1:
+	@echo "***** This command is expected to produce an error on line 7:"
+	$(JAVAC) \
+	  -processorpath $(DATAFLOWBUILD):$(JAVACUTILBUILD):$(FRAMEWORKBUILD):${STUBPARSERJAR} \
+	  -classpath $(PROJECTDIR):${CHECKERQUALBUILD} \
+	  -processor org.checkerframework.common.aliasing.AliasingChecker \
+	  -Anomsgtext \
+	  -ApermitMissingJdk \
+	  LoaderTest.java
+
+# loads from framework.jar
+demo2:
+	@echo "***** This command is expected to produce an error on line 7:"
+	$(JAVAC) \
+	  -processorpath $(FRAMEWORKJAR) \
+	  -classpath $(PROJECTDIR):${CHECKERQUALJAR} \
+	  -processor org.checkerframework.common.aliasing.AliasingChecker \
+	  -Anomsgtext \
+	  -ApermitMissingJdk \
+	  LoaderTest.java
+
+# ======================================================
+# gradle test usage:
+# loads from build directories
+load-from-dir-test:
+	$(JAVAC) \
+	  -processorpath $(DATAFLOWBUILD):$(JAVACUTILBUILD):$(FRAMEWORKBUILD):${STUBPARSERJAR} \
+	  -classpath $(PROJECTDIR):${CHECKERQUALBUILD} \
+	  -processor org.checkerframework.common.aliasing.AliasingChecker \
+	  -Anomsgtext \
+	  -ApermitMissingJdk \
+	  LoaderTest.java > Out.txt 2>&1 || true
+	diff -u Expected.txt Out.txt -I 'Note'
+	rm -f Out.txt
+
+# loads from framework.jar
+load-from-jar-test:
+	$(JAVAC) \
+	  -processorpath $(FRAMEWORKJAR) \
+	  -classpath $(PROJECTDIR):${CHECKERQUALJAR} \
+	  -processor org.checkerframework.common.aliasing.AliasingChecker \
+	  -Anomsgtext \
+	  -ApermitMissingJdk \
+	  LoaderTest.java > Out.txt 2>&1 || true
+	diff -u Expected.txt Out.txt
+	rm -f Out.txt
diff --git a/framework/tests/annotationclassloader/README b/framework/tests/annotationclassloader/README
new file mode 100644
index 0000000..d1ec4bb
--- /dev/null
+++ b/framework/tests/annotationclassloader/README
@@ -0,0 +1,7 @@
+This directory tests the annotation class loader's basic capabilities of loading
+from the qual directory of a checker in two ways:
+1) from a file directory
+2) from a jar
+
+To see the demo, edit the Makefile with the location of your JDK 8's javac
+(variable JAVAC) then run: make demo1 or make demo2
diff --git a/framework/tests/classval/ClassNameTest.java b/framework/tests/classval/ClassNameTest.java
new file mode 100644
index 0000000..a43c51f
--- /dev/null
+++ b/framework/tests/classval/ClassNameTest.java
@@ -0,0 +1,22 @@
+import org.checkerframework.common.reflection.qual.ClassVal;
+
+public class ClassNameTest {
+  void test() throws Exception {
+    @ClassVal("Class$Inner") Object o;
+    @ClassVal("java.lang.String") Object o1;
+    @ClassVal("java.lang.String[]") Object o2;
+    @ClassVal("java.lang.String[][][]") Object o3;
+    @ClassVal("Class$Inner._") Object o8;
+
+    // :: error: (illegal.classname)
+    @ClassVal("java.lang.String[][]]") Object o4;
+    // :: error: (illegal.classname)
+    @ClassVal("java.lang.String[][][") Object o5;
+    // :: error: (illegal.classname)
+    @ClassVal("java.lang.String[][][]s") Object o6;
+    // :: error: (illegal.classname)
+    @ClassVal("java.lang.String[][][].") Object o7;
+    // :: error: (illegal.classname)
+    @ClassVal("java.lang..String") Object o9;
+  }
+}
diff --git a/framework/tests/classval/ClassValInferenceTest.java b/framework/tests/classval/ClassValInferenceTest.java
new file mode 100644
index 0000000..efa5aa6
--- /dev/null
+++ b/framework/tests/classval/ClassValInferenceTest.java
@@ -0,0 +1,66 @@
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.common.reflection.qual.ClassBound;
+import org.checkerframework.common.reflection.qual.ClassVal;
+
+public class ClassValInferenceTest {
+
+  class Inner {
+    Inner() {
+      @ClassBound("ClassValInferenceTest$Inner") Class<?> c1 = this.getClass();
+      @ClassBound("ClassValInferenceTest") Class<?> c2 = ClassValInferenceTest.this.getClass();
+    }
+  }
+
+  public void classLiterals() {
+    @ClassVal("java.lang.Object") Class<?> c1 = Object.class;
+    @ClassVal("java.lang.Object[]") Class<?> c2 = Object[].class;
+    @ClassVal("java.lang.Object[][][]") Class<?> c3 = Object[][][].class;
+    @ClassVal("ClassValInferenceTest$Inner") Class<?> c4 = Inner.class;
+    @ClassVal("byte") Class<? extends Byte> c5 = byte.class;
+  }
+
+  public void classForName() throws ClassNotFoundException {
+    @ClassVal("ClassValInferenceTest$Inner") Class<?> c = Class.forName("ClassValInferenceTest$Inner");
+    @ClassVal("java.lang.Object") Class<?> c1 = Class.forName("java.lang.Object");
+  }
+
+  boolean flag = true;
+
+  public void classForNameStringVal() throws ClassNotFoundException {
+    Class<?> c2;
+    if (flag) {
+      c2 = Class.forName("java.lang.Byte");
+    } else {
+      c2 = Class.forName("java.lang.Integer");
+    }
+    @ClassVal({"java.lang.Byte", "java.lang.Integer"}) Class<?> c3 = c2;
+  }
+
+  public <T extends Number, I extends Number & List<String>> void testGetClass(
+      T typeVar, I intersect) {
+    @ClassBound("ClassValInferenceTest") Class<?> c1 = this.getClass();
+    @ClassBound("ClassValInferenceTest") Class<?> c2 = getClass();
+    String[] array = {"hello"};
+    @ClassBound("java.lang.String[]") Class<?> c3 = array.getClass();
+    String[][][][] arrayMulti = null;
+    @ClassBound("java.lang.String[][][][]") Class<?> c4 = arrayMulti.getClass();
+    @ClassBound("java.lang.String") Class<?> c5 = array[0].getClass();
+    List<String> list = null;
+    @ClassBound("java.util.List") Class<?> c6 = list.getClass();
+    @ClassBound("java.lang.Number") Class<?> c7 = typeVar.getClass();
+    @ClassBound("java.util.ArrayList") Class<?> c8 = new ArrayList<String>().getClass();
+    List<? super Number> wildCardListLB = null;
+    List<? extends Number> wildCardListUB = null;
+    @ClassBound("java.lang.Object") Class<?> c9 = wildCardListLB.get(0).getClass();
+    @ClassBound("java.lang.Number") Class<?> c10 = wildCardListUB.get(0).getClass();
+    Integer i = 0;
+    @ClassBound("java.lang.Integer") Class<?> c11 = i.getClass();
+    @ClassBound("java.lang.Object") Class<?> c12 = intersect.getClass();
+
+    try {
+    } catch (NullPointerException | ArrayIndexOutOfBoundsException ex) {
+      @ClassBound("java.lang.RuntimeException") Class<?> c = ex.getClass();
+    }
+  }
+}
diff --git a/framework/tests/classval/ClassValSubtypingTest.java b/framework/tests/classval/ClassValSubtypingTest.java
new file mode 100644
index 0000000..47b0bba
--- /dev/null
+++ b/framework/tests/classval/ClassValSubtypingTest.java
@@ -0,0 +1,116 @@
+import org.checkerframework.common.reflection.qual.ClassBound;
+import org.checkerframework.common.reflection.qual.ClassVal;
+
+public class ClassValSubtypingTest {
+  @ClassVal("a") Object a = null;
+
+  @ClassVal({"a", "b"}) Object ab = null;
+
+  @ClassVal("c") Object c = null;
+
+  @ClassVal({"c", "d"}) Object cd = null;
+
+  Object unknown = null;
+
+  void assignToUnknown() {
+    unknown = a;
+    unknown = ab;
+    unknown = c;
+    unknown = cd;
+  }
+
+  void assignUnknown() {
+    // :: error: (assignment)
+    a = unknown;
+    // :: error: (assignment)
+    ab = unknown;
+    // :: error: (assignment)
+    c = unknown;
+    // :: error: (assignment)
+    cd = unknown;
+  }
+
+  void assignments() {
+    // :: error: (assignment)
+    a = ab;
+    ab = a;
+    // :: error: (assignment)
+    a = c;
+    // :: error: (assignment)
+    ab = c;
+    // :: error: (assignment)
+    ab = cd;
+  }
+}
+
+class ClassBoundSubtypingTest {
+  @ClassBound("a") Object a = null;
+
+  @ClassBound({"a", "b"}) Object ab = null;
+
+  @ClassBound("c") Object c = null;
+
+  @ClassBound({"c", "d"}) Object cd = null;
+
+  Object unknown = null;
+
+  void assignToUnknown() {
+    unknown = a;
+    unknown = ab;
+    unknown = c;
+    unknown = cd;
+  }
+
+  void assignUnknown() {
+    // :: error: (assignment)
+    a = unknown;
+    // :: error: (assignment)
+    ab = unknown;
+    // :: error: (assignment)
+    c = unknown;
+    // :: error: (assignment)
+    cd = unknown;
+  }
+
+  void assignments() {
+    // :: error: (assignment)
+    a = ab;
+    ab = a;
+    // :: error: (assignment)
+    a = c;
+    // :: error: (assignment)
+    ab = c;
+    // :: error: (assignment)
+    ab = cd;
+  }
+}
+
+class ClassValClassBoundSubtypingTest {
+  @ClassVal("a") Object a = null;
+
+  @ClassVal({"a", "b"}) Object ab = null;
+
+  @ClassBound("a") Object aBound = null;
+
+  @ClassBound({"a", "b"}) Object abBound = null;
+
+  void assignments1() {
+    // :: error: (assignment)
+    a = aBound;
+    // :: error: (assignment)
+    ab = aBound;
+    // :: error: (assignment)
+    a = abBound;
+    // :: error: (assignment)
+    ab = abBound;
+  }
+
+  void assignments2() {
+    aBound = a;
+    // :: error: (assignment)
+    aBound = ab;
+
+    abBound = a;
+    abBound = ab;
+  }
+}
diff --git a/framework/tests/classval/GLBTest.java b/framework/tests/classval/GLBTest.java
new file mode 100644
index 0000000..8fec88b
--- /dev/null
+++ b/framework/tests/classval/GLBTest.java
@@ -0,0 +1,25 @@
+import org.checkerframework.common.reflection.qual.ClassBound;
+import org.checkerframework.common.reflection.qual.ClassVal;
+
+public class GLBTest<@ClassVal({"A", "B"}) T extends Object> {
+  // This code is intented to cover the more complex branchs for the GLB calcuation.
+  // This only triggers the GLB calculation because of a hack in
+  // org.checkerframework.framework.util.AnnotatedTypes.addAnnotationsImpl()
+  // If that code changes, this code may not test GLB anymore.
+  // This code does not test correctness. Because no expresion is given the GLB as a type,
+  // it is impossible to test GLB for correctness.
+  // :: error: (type.argument) :: error: (assignment)
+  GLBTest<@ClassVal({"A", "B", "C"}) ?> f1 = new GLBTest<@ClassVal({"A", "E"}) Object>();
+  // :: error: (type.argument) :: error: (assignment)
+  GLBTest<@ClassVal({"A", "B", "C"}) ?> f2 = new GLBTest<@ClassBound({"A", "E"}) Object>();
+  // :: error: (type.argument) :: error: (assignment)
+  GLBTest<@ClassBound({"A", "B", "C"}) ?> f3 = new GLBTest<@ClassBound({"A", "E"}) Object>();
+
+  <
+          @ClassVal({"A", "B", "C"}) CLASSVAL extends Object,
+          @ClassBound({"A", "B", "C"}) CLASSBOUND extends Object>
+      void test() {
+    GLBTest<?> f1 = new GLBTest<CLASSVAL>();
+    GLBTest<?> f2 = new GLBTest<CLASSBOUND>();
+  }
+}
diff --git a/framework/tests/compound-checker/BasicTest.java b/framework/tests/compound-checker/BasicTest.java
new file mode 100644
index 0000000..c3b18a6
--- /dev/null
+++ b/framework/tests/compound-checker/BasicTest.java
@@ -0,0 +1,12 @@
+public class BasicTest {
+  // Random code just to make sure that the compound design pattern
+  // does not throw any exceptions
+  Object field = new Object();
+  String[] array = {"hello", "world"};
+
+  void foo(int arg) {
+    field = array[0];
+    field.toString();
+    int a = 1 + arg;
+  }
+}
diff --git a/framework/tests/compound-checker/MultiError.java b/framework/tests/compound-checker/MultiError.java
new file mode 100644
index 0000000..c4fd6a6
--- /dev/null
+++ b/framework/tests/compound-checker/MultiError.java
@@ -0,0 +1,11 @@
+import org.checkerframework.common.aliasing.qual.Unique;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class MultiError {
+  // Testing that errors from multiple checkers are issued
+  // on the same compilation unit
+  // :: error: (unique.location.forbidden)
+  @Unique String[] array;
+  // :: error: (assignment)
+  @StringVal("hello") String s = "goodbye";
+}
diff --git a/framework/tests/compound-checker/javac-error/JavaErrorTest.java b/framework/tests/compound-checker/javac-error/JavaErrorTest.java
new file mode 100644
index 0000000..eb8776f
--- /dev/null
+++ b/framework/tests/compound-checker/javac-error/JavaErrorTest.java
@@ -0,0 +1,12 @@
+public class JavaErrorTest {
+  // Checking that if one checker finds a Java error
+  // and therefor does not set a root, then
+  // checkers relying on that checker should also not run.
+  // otherwise, it might access a subchecker whose "root" has not been set.
+  // (See AnnotatedTypeFactory.setRoot(CompilationUnitTree) )
+  void foo() {
+    Object a;
+    // :: error: variable a is already defined in method foo()
+    Object a;
+  }
+}
diff --git a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java
new file mode 100644
index 0000000..f313d5f
--- /dev/null
+++ b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java
@@ -0,0 +1,250 @@
+import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.testchecker.util.SubQual;
+import org.checkerframework.framework.testchecker.util.SuperQual;
+
+public class AnnotatedForTest {
+  /*
+       Test a mix of @SuppressWarnings with @AnnotatedFor. @SuppressWarnings should win, but only within the kinds of warnings it promises to suppress. It should win because
+       it is a specific intent of suppressing warnings, whereas NOT suppressing warnings using AnnotatedFor is a default behavior, and SW is a user-specified behavior.
+  */
+
+  // Test unannotated class initializer - no warnings should be issued
+  @SuperQual Object o1 = annotatedMethod(new Object());
+  @SubQual Object o2 = annotatedMethod(new Object());
+  Object o3 = unannotatedMethod(o2);
+  Object o4 = unannotatedMethod(o1);
+
+  static @SuperQual Object so1;
+  static @SubQual Object so2;
+  static Object so3, so4;
+
+  // Test unannotated static initializer block - no warnings should be issued
+  static {
+    so1 = staticAnnotatedMethod(new Object());
+    so2 = staticAnnotatedMethod(new Object());
+    so3 = staticUnannotatedMethod(so2);
+    so4 = staticUnannotatedMethod(so1);
+  }
+
+  @SuperQual Object o5;
+  @SubQual Object o6;
+  Object o7, o8;
+
+  // Test unannotated nonstatic initializer block - no warnings should be issued
+  {
+    o5 = annotatedMethod(new Object());
+    o6 = annotatedMethod(new Object());
+    o7 = unannotatedMethod(o6);
+    o8 = unannotatedMethod(o5);
+  }
+
+  // This method is @AnnotatedFor("subtyping") so it can cause errors to be issued by calling
+  // other methods.
+  @AnnotatedFor("subtyping")
+  void method1() {
+    // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since
+    // @SuperQual is annotated with @DefaultQualifierInHierarchy.
+    @SuperQual Object o1 = annotatedMethod(new Object());
+    // :: error: (assignment)
+    @SubQual Object o2 = annotatedMethod(new Object());
+
+    // When calling unannotatedMethod, we expect the conservative defaults.
+    Object o3 = unannotatedMethod(o2);
+    // :: error: (argument)
+    Object o4 = unannotatedMethod(o1);
+
+    // Testing that @AnnotatedFor({}) behaves the same way as not putting an @AnnotatedFor
+    // annotation.
+    Object o5 = unannotatedMethod(o2);
+    // :: error: (argument)
+    Object o6 = unannotatedMethod(o1);
+
+    // Testing that @AnnotatedFor(a different typesystem) behaves the same way @AnnotatedFor({})
+    Object o7 = unannotatedMethod(o2);
+    // :: error: (argument)
+    Object o8 = unannotatedMethod(o1);
+  }
+
+  @SuppressWarnings("all")
+  @AnnotatedFor(
+      "subtyping") // Same as method1, but the @SuppressWarnings overrides the @AnnotatedFor.
+  void method2() {
+    // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since
+    // @SuperQual is annotated with @DefaultQualifierInHierarchy.
+    @SuperQual Object o1 = annotatedMethod(new Object());
+    @SubQual Object o2 = annotatedMethod(new Object());
+
+    // When calling unannotatedMethod, we expect the conservative defaults.
+    Object o3 = unannotatedMethod(o2);
+    Object o4 = unannotatedMethod(o1);
+
+    // Testing that @AnnotatedFor({}) behaves the same way as not putting an @AnnotatedFor
+    // annotation.
+    Object o5 = unannotatedMethod(o2);
+    Object o6 = unannotatedMethod(o1);
+
+    // Testing that @AnnotatedFor(a different typesystem) behaves the same way @AnnotatedFor({})
+    Object o7 = unannotatedMethod(o2);
+    Object o8 = unannotatedMethod(o1);
+  }
+
+  @SuppressWarnings("nullness")
+  @AnnotatedFor("subtyping") // Similar to method1. The @SuppressWarnings does not override the
+  // @AnnotatedFor because it suppressing warnings for a different typesystem.
+  void method3() {
+    // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since
+    // @SuperQual is annotated with @DefaultQualifierInHierarchy.
+    @SuperQual Object o1 = annotatedMethod(new Object());
+    // :: error: (assignment)
+    @SubQual Object o2 = annotatedMethod(new Object());
+  }
+
+  @AnnotatedFor("subtyping")
+  Object annotatedMethod(Object p) {
+    return new Object();
+  }
+
+  Object unannotatedMethod(Object p) {
+    return new Object();
+  }
+
+  @AnnotatedFor("subtyping")
+  static Object staticAnnotatedMethod(Object p) {
+    return new Object();
+  }
+
+  static Object staticUnannotatedMethod(Object p) {
+    return new Object();
+  }
+
+  @AnnotatedFor({})
+  Object unannotatedMethod2(Object p) {
+    return new Object();
+  }
+
+  @AnnotatedFor("nullness")
+  Object annotatedForADifferentTypeSystemMethod(Object p) {
+    return new Object();
+  }
+
+  // Annotated for more than one type system
+  @AnnotatedFor({"nullness", "subtyping"})
+  void method4() {
+    // :: error: (assignment)
+    @SubQual Object o2 = new @SuperQual Object();
+  }
+
+  // Different way of writing the checker name
+  @AnnotatedFor("SubtypingChecker")
+  void method5() {
+    // :: error: (assignment)
+    @SubQual Object o2 = new @SuperQual Object();
+  }
+
+  // Different way of writing the checker name
+  @AnnotatedFor("org.checkerframework.common.subtyping.SubtypingChecker")
+  void method6() {
+    // :: error: (assignment)
+    @SubQual Object o2 = new @SuperQual Object();
+  }
+
+  // Every method in this class should issue warnings for subtyping even if it's not marked with
+  // @AnnotatedFor, unless it's marked with @SuppressWarnings.
+  @AnnotatedFor("subtyping")
+  class annotatedClass {
+    // Test annotated class initializer
+    // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since
+    // @SuperQual is annotated with @DefaultQualifierInHierarchy.
+    @SuperQual Object o1 = annotatedMethod(new Object());
+    // :: error: (assignment)
+    @SubQual Object o2 = annotatedMethod(new Object());
+
+    // When calling unannotatedMethod, we expect the conservative defaults.
+    Object o3 = unannotatedMethod(o2);
+    // :: error: (argument)
+    Object o4 = unannotatedMethod(o1);
+
+    @SuperQual Object o5;
+    @SubQual Object o6;
+    Object o7, o8;
+
+    // Test annotated nonstatic initializer block
+    {
+      o5 = annotatedMethod(new Object());
+      // :: error: (assignment)
+      o6 = annotatedMethod(new Object());
+      o7 = unannotatedMethod(o6);
+      // :: error: (argument)
+      o8 = unannotatedMethod(o5);
+    }
+
+    void method1() {
+      // :: error: (assignment)
+      @SubQual Object o2 = new @SuperQual Object();
+    }
+
+    @SuppressWarnings("all")
+    void method2() {
+      @SubQual Object o2 = new @SuperQual Object();
+    }
+  }
+
+  @AnnotatedFor("subtyping")
+  static class staticAnnotatedForClass {
+    static @SuperQual Object so1;
+    static @SubQual Object so2;
+    static Object so3, so4;
+
+    // Test annotated static initializer block
+    static {
+      so1 = staticAnnotatedMethod(new Object());
+      // :: error: (assignment)
+      so2 = staticAnnotatedMethod(new Object());
+      so3 = staticUnannotatedMethod(so2);
+      // :: error: (argument)
+      so4 = staticUnannotatedMethod(so1);
+    }
+  }
+
+  @SuppressWarnings("all") // @SuppressWarnings("all") overrides @AnnotatedFor("subtyping")
+  @AnnotatedFor("subtyping")
+  class annotatedAndWarningsSuppressedClass {
+    // Test annotated class initializer whose warnings are suppressed.
+    @SuperQual Object o1 = annotatedMethod(new Object());
+    @SubQual Object o2 = annotatedMethod(new Object());
+    Object o3 = unannotatedMethod(o2);
+    Object o4 = unannotatedMethod(o1);
+
+    @SuperQual Object o5;
+    @SubQual Object o6;
+    Object o7, o8;
+
+    // Test annotated nonstatic initializer block whose warnings are suppressed.
+    {
+      o5 = annotatedMethod(new Object());
+      o6 = annotatedMethod(new Object());
+      o7 = unannotatedMethod(o6);
+      o8 = unannotatedMethod(o5);
+    }
+
+    void method1() {
+      @SubQual Object o2 = new @SuperQual Object();
+    }
+  }
+
+  @SuppressWarnings("all")
+  @AnnotatedFor("subtyping")
+  static class staticAnnotatedAndWarningsSuppressedClass {
+    static @SuperQual Object so1;
+    static @SubQual Object so2;
+    static Object so3, so4;
+
+    // Test annotated static initializer block whose warnings are suppressed.
+    static {
+      so1 = staticAnnotatedMethod(new Object());
+      so2 = staticAnnotatedMethod(new Object());
+      so3 = staticUnannotatedMethod(so2);
+      so4 = staticUnannotatedMethod(so1);
+    }
+  }
+}
diff --git a/framework/tests/defaulting/lowerbound/LowerBoundDefaulting.java b/framework/tests/defaulting/lowerbound/LowerBoundDefaulting.java
new file mode 100644
index 0000000..3f2ecf2
--- /dev/null
+++ b/framework/tests/defaulting/lowerbound/LowerBoundDefaulting.java
@@ -0,0 +1,67 @@
+package defaulting.lowerbound;
+
+// This test's sole purpose is to check that implicit and explicit LOWER_BOUND defaulting work as
+// expected.
+
+import org.checkerframework.framework.testchecker.defaulting.LowerBoundQual.*;
+
+class MyArrayList<MAL extends String> {}
+
+class MyExplicitArray<MEA extends String> {}
+
+public class LowerBoundDefaulting {
+
+  // IMP1 is of type IMP1 [extends @LbTop super @LbImplicit]
+  public <IMP1 extends String> void implicitsTypeVar() {
+
+    // should fail because @LbImplicit is below @LbTop
+    @LbTop MyArrayList<@LbTop ? extends @LbTop String> itLowerBoundIncompatible =
+        // :: error: (assignment)
+        new MyArrayList<IMP1>();
+
+    // :: error: (type.argument)
+    @LbTop MyArrayList<@LbExplicit ? extends @LbTop String> itLowerBoundStillIncompatible =
+        // :: error: (assignment)
+        new MyArrayList<IMP1>();
+
+    @LbTop MyArrayList<@LbImplicit ? extends @LbTop String> itLowerBoundCompatible =
+        new MyArrayList<IMP1>();
+  }
+
+  public void implicitsWildcard(MyArrayList<?> myArrayList) {
+
+    // should fail because @LbImplicit is below @LbTop
+    // :: error: (assignment)
+    @LbTop MyArrayList<@LbTop ? extends @LbTop String> iwLowerBoundIncompatible = myArrayList;
+
+    // :: error: (assignment) :: error: (type.argument)
+    @LbTop MyArrayList<@LbExplicit ? extends @LbTop String> iwLowerBoundCompatible = myArrayList;
+
+    @LbTop MyArrayList<@LbImplicit ? extends @LbTop String> iwLowerBoundStillCompatible = myArrayList;
+  }
+
+  public void implicitExtendBoundedWildcard(MyArrayList<? extends String> iebList) {
+
+    // should fail because @LbImplicit is below @LbTop
+    // :: error: (assignment)
+    @LbTop MyArrayList<@LbTop ? extends @LbTop String> iebLowerBoundIncompatible = iebList;
+
+    // :: error: (assignment) :: error: (type.argument)
+    @LbTop MyArrayList<@LbExplicit ? extends @LbTop String> iebLowerBoundStillIncompatible = iebList;
+
+    @LbTop MyArrayList<@LbImplicit ? extends @LbTop String> iebLowerBoundCompatible = iebList;
+  }
+
+  // :: error: (type.argument)
+  public void explicitLowerBoundedWildcard(MyArrayList<? super String> elbList) {
+    // should fail because @LbExplicit is below @LbTop
+    // :: error: (assignment)
+    @LbTop MyArrayList<@LbTop ? super @LbTop String> iebLowerBoundIncompatible = elbList;
+
+    // :: error: (type.argument)
+    @LbTop MyArrayList<@LbTop ? super @LbExplicit String> iebLowerBoundStillIncompatible = elbList;
+
+    // :: error: (assignment)
+    @LbTop MyArrayList<@LbTop ? super @LbImplicit String> iebLowerBoundCompatible = elbList;
+  }
+}
diff --git a/framework/tests/defaulting/upperbound/UpperBoundDefaulting.java b/framework/tests/defaulting/upperbound/UpperBoundDefaulting.java
new file mode 100644
index 0000000..8b4b4b4
--- /dev/null
+++ b/framework/tests/defaulting/upperbound/UpperBoundDefaulting.java
@@ -0,0 +1,66 @@
+package defaulting.upperbound;
+
+// This test's sole purpose is to check that implicit and explicit LOWER_BOUND defaulting work as
+// expected.
+
+import org.checkerframework.framework.testchecker.defaulting.UpperBoundQual.*;
+
+class MyArrayList<MAL extends String> {}
+
+class MyExplicitArray<MEA extends String> {}
+
+public class UpperBoundDefaulting {
+
+  public <UAL extends String> void explicitUpperBoundTypeVar() {
+    MyArrayList<@UbBottom ? extends @UbBottom Object> eubBottomToBottom =
+        // :: error: (assignment)
+        new MyArrayList<UAL>();
+
+    MyArrayList<@UbBottom ? extends @UbExplicit Object> eubExplicitToBottom =
+        new MyArrayList<UAL>();
+
+    // :: error: (type.argument)
+    MyArrayList<@UbBottom ? extends @UbImplicit Object> eubImplicitToBottom =
+        // :: error: (assignment)
+        new MyArrayList<UAL>();
+  }
+
+  public void implicitsWildcard(MyArrayList<?> myArrayList) {
+
+    // should fail because @LbImplicit is below @UbTop
+    // :: error: (type.argument)
+    @UbTop MyArrayList<@UbBottom ? extends @UbTop String> iwLowerBoundIncompatible = myArrayList;
+
+    @UbTop MyArrayList<@UbBottom ? extends @UbExplicit String> iwLowerBoundCompatible = myArrayList;
+
+    // :: error: (type.argument)
+    @UbTop MyArrayList<@UbBottom ? extends @UbImplicit String> iwLowerBoundStillCompatible =
+        // :: error: (assignment)
+        myArrayList;
+  }
+
+  public void implicitExtendBoundedWildcard(MyArrayList<? extends String> iebList) {
+
+    // should fail because @LbImplicit is below @UbTop
+    // :: error: (type.argument)
+    @UbTop MyArrayList<@UbBottom ? extends @UbTop String> iebLowerBoundIncompatible = iebList;
+
+    MyArrayList<@UbBottom ? extends @UbExplicit Object> eubExplicitToBottom = iebList;
+
+    // :: error: (assignment) :: error: (type.argument)
+    MyArrayList<@UbBottom ? extends @UbImplicit Object> eubImplicitToBottom = iebList;
+  }
+
+  public void explicitLowerBoundedWildcard(MyArrayList<? super String> elbList) {
+    // should fail because @UbExplicit is below UbTop
+    // :: error: (type.argument)
+    @UbTop MyArrayList<@UbTop ? super @UbBottom String> iebLowerBoundIncompatible = elbList;
+
+    // :: error: (type.argument)
+    @UbTop MyArrayList<@UbImplicit ? super @UbBottom String> iebLowerBoundStillIncompatible =
+        // :: error: (assignment)
+        elbList;
+
+    @UbTop MyArrayList<@UbExplicit ? super @UbBottom String> iebLowerBoundCompatible = elbList;
+  }
+}
diff --git a/framework/tests/flow/Basic.java b/framework/tests/flow/Basic.java
new file mode 100644
index 0000000..87f1c1b
--- /dev/null
+++ b/framework/tests/flow/Basic.java
@@ -0,0 +1,49 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class Basic {
+
+  @Odd String field;
+
+  void test(@Odd String param) {
+    String local = "";
+    local = param;
+    field = local;
+
+    String r = field;
+  }
+
+  void testIf(@Odd String ifParam) {
+    String local = "";
+    if (field != null) {
+      local = ifParam;
+    } else {
+      local = ifParam;
+    }
+
+    String r = local;
+  }
+
+  void testWhile(@Odd String whileParam) {
+    String local = whileParam;
+    while (local != "foo") {
+      local = "";
+    }
+
+    String r = local;
+  }
+
+  void testWhile2(@Odd String whileParam) {
+    String local = "";
+    while (local != "foo") {
+      local = whileParam;
+    }
+
+    String r = local;
+  }
+
+  void testCompountAssignment(@Odd String odd) {
+    String nonOdd = odd;
+    nonOdd += "kj"; // nonOdd as rValue is not Odd necessarily!
+    nonOdd = "m";
+  }
+}
diff --git a/framework/tests/flow/Fields.java b/framework/tests/flow/Fields.java
new file mode 100644
index 0000000..1bc30a0
--- /dev/null
+++ b/framework/tests/flow/Fields.java
@@ -0,0 +1,18 @@
+public class Fields {
+
+  // Not final field
+  String s = null;
+
+  void setString(String s) {
+    this.s = s;
+  }
+
+  // Test flow for fields but in different receivers
+  void others() {
+    Fields withOddField = null;
+    Fields notOddField = null;
+
+    withOddField.s = null;
+    notOddField.s = "m";
+  }
+}
diff --git a/framework/tests/flow/MoreFields.java b/framework/tests/flow/MoreFields.java
new file mode 100644
index 0000000..b17f469
--- /dev/null
+++ b/framework/tests/flow/MoreFields.java
@@ -0,0 +1,9 @@
+public class MoreFields {
+  void testOtherFieds(Test other, int f) {
+    other.f = f;
+  }
+}
+
+class Test {
+  int f;
+}
diff --git a/framework/tests/flow/README b/framework/tests/flow/README
new file mode 100644
index 0000000..eec470a
--- /dev/null
+++ b/framework/tests/flow/README
@@ -0,0 +1,5 @@
+To add a new file to the test suite, see
+  ../README
+
+To run the tests, do
+ (cd $CHECKERFRAMEWORK && ./gradlew FlowTest)
diff --git a/framework/tests/flow/Values.java b/framework/tests/flow/Values.java
new file mode 100644
index 0000000..f44acc3
--- /dev/null
+++ b/framework/tests/flow/Values.java
@@ -0,0 +1,69 @@
+import java.util.Collection;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class Values {
+
+  void test() {
+    Object o = get();
+    Object o1 = get1();
+    Object o2 = get2();
+    foo1(o1);
+    foo2(o2);
+
+    // :: error: (argument)
+    foo1(o);
+    // :: error: (argument)
+    foo2(o1);
+    // :: error: (argument)
+    foo1(o2);
+    // :: error: (argument)
+    foo(o2);
+
+    o1 = o2;
+    foo2(o1);
+    // :: error: (argument)
+    foo1(o1);
+
+    o2 = get1();
+    foo1(o2);
+    // :: error: (argument)
+    foo2(o2);
+  }
+
+  void andlubTest(Collection<Object> c) {
+    for (Object obj : c) {
+      Object o = get1();
+    }
+  }
+
+  void orlubTest(boolean b1, boolean b2) {
+    if (b1) {
+      Object o = get1();
+      return;
+    } else if (b2) {
+      Object o = get2();
+      return;
+    }
+  }
+
+  void foo(@Value Object o) {}
+
+  void foo1(@Value(1) Object o) {}
+
+  void foo2(@Value(2) Object o) {}
+
+  @SuppressWarnings("flowtest:return")
+  @Value Object get() {
+    return null;
+  }
+
+  @SuppressWarnings("flowtest:return")
+  @Value(1) Object get1() {
+    return null;
+  }
+
+  @SuppressWarnings("flowtest:return")
+  @Value(2) Object get2() {
+    return null;
+  }
+}
diff --git a/framework/tests/flow2/AnnotationAliasing.java b/framework/tests/flow2/AnnotationAliasing.java
new file mode 100644
index 0000000..f75bda9
--- /dev/null
+++ b/framework/tests/flow2/AnnotationAliasing.java
@@ -0,0 +1,46 @@
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+/** Various tests for annotation aliasing. */
+public class AnnotationAliasing {
+
+  String f1, f2, f3;
+
+  @Pure
+  int pure1() {
+    return 1;
+  }
+
+  @org.jmlspecs.annotation.Pure
+  int pure2() {
+    return 1;
+  }
+
+  // a method that is not pure (no annotation)
+  void nonpure() {}
+
+  @Pure
+  String t1() {
+    // :: error: (purity.not.deterministic.not.sideeffectfree.call)
+    nonpure();
+    return "";
+  }
+
+  @org.jmlspecs.annotation.Pure
+  String t2() {
+    // :: error: (purity.not.deterministic.not.sideeffectfree.call)
+    nonpure();
+    return "";
+  }
+
+  // check aliasing of Pure
+  void t1(@Odd String p1, String p2) {
+    f1 = p1;
+    @Odd String l2 = f1;
+    pure1();
+    @Odd String l3 = f1;
+    pure2();
+    @Odd String l4 = f1;
+  }
+}
diff --git a/framework/tests/flow2/ArrayFlow.java b/framework/tests/flow2/ArrayFlow.java
new file mode 100644
index 0000000..dfdb564
--- /dev/null
+++ b/framework/tests/flow2/ArrayFlow.java
@@ -0,0 +1,24 @@
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class ArrayFlow {
+
+  // array accesses
+  void t1(@Odd String a1[], String a2[], @Odd String odd) {
+    String l1 = a1[0];
+
+    // :: error: (assignment)
+    @Odd String l2 = a2[0];
+
+    if (a2[0] == odd) {
+      @Odd String l3 = a2[0];
+    }
+
+    int i = 1;
+    a2[i] = odd;
+    @Odd String l4 = a2[i];
+    i = 2;
+    // :: error: (assignment)
+    @Odd String l5 = a2[i];
+  }
+}
diff --git a/framework/tests/flow2/Basic2.java b/framework/tests/flow2/Basic2.java
new file mode 100644
index 0000000..a8a0b27
--- /dev/null
+++ b/framework/tests/flow2/Basic2.java
@@ -0,0 +1,234 @@
+import java.util.List;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class Basic2 {
+
+  // basic tests to make sure everything works
+  void t1(@Odd String p1, String p2) {
+    String l1 = p1;
+    // :: error: (assignment)
+    @Odd String l2 = p2;
+  }
+
+  // basic local variable flow sensitivity
+  void t2(@Odd String p1, String p2, boolean b1) {
+    String l1 = p1;
+    if (b1) {
+      l1 = p2;
+    }
+    // :: error: (assignment)
+    @Odd String l3 = l1;
+
+    l1 = p1;
+    while (b1) {
+      l1 = p2;
+    }
+    // :: error: (assignment)
+    @Odd String l4 = l1;
+  }
+
+  void t2b(@Odd String p1, String p2, @Odd String p3, boolean b1) {
+    String l1 = p1;
+
+    if (b1) {
+      l1 = p3;
+    }
+    @Odd String l3 = l1;
+
+    while (b1) {
+      l1 = p3;
+    }
+    @Odd String l4 = l1;
+  }
+
+  // return statement
+  void t3(@Odd String p1, String p2, boolean b1) {
+    String l1 = p1;
+    if (b1) {
+      l1 = p2;
+      return;
+    }
+    @Odd String l3 = l1;
+  }
+
+  // simple throw statement
+  void t4(@Odd String p1, String p2, boolean b1) {
+    String l1 = p1;
+    if (b1) {
+      l1 = p2;
+      throw new RuntimeException();
+    }
+    @Odd String l3 = l1;
+  }
+
+  class C {
+    C c;
+    String f1, f2, f3;
+    @Odd String g1, g2, g3;
+  }
+
+  // fields
+  void t5(@Odd String p1, String p2, boolean b1, C c1, C c2) {
+    c1.f1 = p1;
+    @Odd String l1 = c1.f1;
+    c2.f2 = p2; // assignment to f2 does not change knowledge about f1
+    c1.f2 = p2;
+    @Odd String l2 = c1.f1;
+    c2.f1 = p2;
+    // :: error: (assignment)
+    @Odd String l3 = c1.f1;
+  }
+
+  // fields
+  void t6(@Odd String p1, String p2, boolean b1, C c1, C c2) {
+    c1.f1 = p1;
+    c1.c.f1 = p1;
+    @Odd String l2 = c1.c.f1;
+    c1.f1 = p2;
+    // :: error: (assignment)
+    @Odd String l3 = c1.f1;
+
+    c1.f1 = p1;
+    c1.c.f1 = p1;
+    @Odd String l4 = c1.c.f1;
+    c2.c = c2;
+    // :: error: (assignment)
+    @Odd String l5 = c1.f1;
+    // :: error: (assignment)
+    @Odd String l6 = c1.c.f1;
+  }
+
+  // fields
+  void t6b(@Odd String p1, String p2, boolean b1, C c1, C c2) {
+    if (b1) {
+      c1.f1 = p1;
+    }
+    // :: error: (assignment)
+    @Odd String l1 = c1.f1;
+
+    if (b1) {
+      c1.f1 = p1;
+    } else {
+      c1.f1 = p1;
+    }
+    @Odd String l2 = c1.f1;
+  }
+
+  // method calls
+  void nonpure() {}
+
+  @Pure
+  int pure() {
+    return 1;
+  }
+
+  void t7(@Odd String p1, String p2, boolean b1, C c1, C c2) {
+    c1.f1 = p1;
+    nonpure();
+    // :: error: (assignment)
+    @Odd String l1 = c1.f1;
+
+    c1.f1 = p1;
+    pure();
+    @Odd String l2 = c1.f1;
+  }
+
+  // array accesses
+  void t8(@Odd String a1[], String a2[], String p3) {
+    String l1 = a1[0];
+
+    // :: error: (assignment)
+    @Odd String l2 = a2[0];
+
+    @Odd String l3 = l1;
+
+    a1[0] = l1;
+    a1[1] = l3;
+
+    // :: error: (assignment)
+    a1[2] = p3;
+
+    a2[0] = l1;
+    a2[1] = l3;
+    a2[2] = p3;
+  }
+
+  // self type
+  void t9(@Odd Basic2 this) {
+    @Odd Basic2 l1 = this;
+  }
+
+  // generics
+  public <T extends String> void t10a(T p1, @Odd T p2) {
+    T l1 = p1;
+    p1 = l1;
+    T l2 = p2;
+    p2 = l2;
+    // :: error: (assignment)
+    @Odd T l3 = p1;
+    @Odd T l4 = p2;
+  }
+
+  public <T extends @Odd String> void t10b(T p1, @Odd T p2) {
+    T l1 = p1;
+    p1 = l1;
+    T l2 = p2;
+    p2 = l2;
+    @Odd T l3 = p1;
+    @Odd T l4 = p2;
+  }
+
+  // for-each loop
+  void t11(@Odd String p1, String p2, List<String> list, List<@Odd String> oddList) {
+    // :: error: (enhancedfor)
+    for (@Odd String i : list) {}
+    for (@Odd String i : oddList) {
+      @Odd String l1 = i;
+    }
+    for (@Odd String i : oddList) {
+      // :: error: (assignment)
+      i = p2;
+    }
+    for (String i : oddList) {
+      // @Odd String l3 = i;
+    }
+  }
+
+  // cast without annotations
+  void t12(@Odd String p1, String p2, boolean b1) {
+    @Odd String l1 = (String) p1;
+  }
+
+  // final fields
+  class CF {
+    final String f1;
+    CF c;
+
+    void nonpure() {}
+
+    CF(@Odd String p1) {
+      f1 = p1;
+      nonpure();
+      @Odd String l1 = f1;
+    }
+
+    void CF_t1(@Odd String p1, CF o) {
+      if (f1 == p1) {
+        nonpure();
+        @Odd String l1 = f1;
+      }
+    }
+  }
+
+  // final fields with initializer
+  class A {
+    final @Odd String f1 = null;
+    final String f2 = f1;
+
+    void A_t1() {
+      @Odd String l1 = f2;
+    }
+  }
+}
diff --git a/framework/tests/flow2/ContractsOverriding.java b/framework/tests/flow2/ContractsOverriding.java
new file mode 100644
index 0000000..3bea424
--- /dev/null
+++ b/framework/tests/flow2/ContractsOverriding.java
@@ -0,0 +1,190 @@
+import org.checkerframework.framework.qual.EnsuresQualifier;
+import org.checkerframework.framework.qual.EnsuresQualifierIf;
+import org.checkerframework.framework.qual.RequiresQualifier;
+import org.checkerframework.framework.testchecker.util.EnsuresOdd;
+import org.checkerframework.framework.testchecker.util.EnsuresOddIf;
+import org.checkerframework.framework.testchecker.util.Odd;
+import org.checkerframework.framework.testchecker.util.RequiresOdd;
+
+public class ContractsOverriding {
+  static class Super {
+    String f, g;
+
+    void m1() {}
+
+    void m2() {}
+
+    @RequiresOdd("g")
+    void m3() {}
+
+    @RequiresOdd("g")
+    void m3b() {}
+
+    @RequiresOdd("f")
+    void m4() {}
+  }
+
+  static class Sub extends Super {
+    String g;
+
+    @Override
+    @RequiresOdd("f")
+    // :: error: (contracts.precondition.override)
+    void m1() {}
+
+    @Override
+    @RequiresQualifier(expression = "f", qualifier = Odd.class)
+    // :: error: (contracts.precondition.override)
+    void m2() {}
+
+    @Override
+    // g is a different field than in the supertype
+    @RequiresOdd("g")
+    // :: error: (contracts.precondition.override)
+    void m3() {}
+
+    @Override
+    @RequiresOdd("super.g")
+    void m3b() {}
+
+    @Override
+    // use different string to refer to 'f' and RequiresQualifier instead of RequiresOdd
+    @RequiresQualifier(expression = "this.f", qualifier = Odd.class)
+    void m4() {}
+  }
+
+  static class Sub2 extends Super2 {
+    String g;
+
+    @Override
+    // :: error: (contracts.postcondition)
+    void m1() {}
+
+    @Override
+    // :: error: (contracts.postcondition)
+    void m2() {}
+
+    @Override
+    @EnsuresOdd("g")
+    // :: error: (contracts.postcondition.override)
+    void m3() {
+      g = odd;
+    }
+
+    @Override
+    @EnsuresOdd("f")
+    void m4() {
+      super.m4();
+    }
+  }
+
+  static class Super2 {
+    String f, g;
+    @Odd String odd;
+
+    @EnsuresOdd("f")
+    void m1() {
+      f = odd;
+    }
+
+    @EnsuresQualifier(expression = "f", qualifier = Odd.class)
+    void m2() {
+      f = odd;
+    }
+
+    @EnsuresOdd("g")
+    void m3() {
+      g = odd;
+    }
+
+    @EnsuresQualifier(expression = "this.f", qualifier = Odd.class)
+    void m4() {
+      f = odd;
+    }
+  }
+
+  static class Sub3 extends Super3 {
+    String g;
+
+    @Override
+    boolean m1() {
+      // :: error: (contracts.conditional.postcondition)
+      return true;
+    }
+
+    @Override
+    boolean m2() {
+      // :: error: (contracts.conditional.postcondition)
+      return true;
+    }
+
+    @Override
+    @EnsuresOddIf(expression = "g", result = true)
+    // :: error: (contracts.conditional.postcondition.true.override)
+    boolean m3() {
+      g = odd;
+      return true;
+    }
+
+    @Override
+    @EnsuresOddIf(expression = "f", result = true)
+    boolean m4() {
+      return super.m4();
+    }
+
+    @Override
+    // invalid result
+    @EnsuresOddIf(expression = "f", result = false)
+    boolean m5() {
+      f = odd;
+      return true;
+    }
+
+    @EnsuresOddIf(expression = "f", result = false)
+    boolean m6() {
+      f = odd;
+      return true;
+    }
+
+    @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true)
+    boolean m7() {
+      f = odd;
+      return true;
+    }
+  }
+
+  static class Super3 {
+    String f, g;
+    @Odd String odd;
+
+    @EnsuresOddIf(expression = "f", result = true)
+    boolean m1() {
+      f = odd;
+      return true;
+    }
+
+    @EnsuresQualifierIf(expression = "f", qualifier = Odd.class, result = true)
+    boolean m2() {
+      f = odd;
+      return true;
+    }
+
+    @EnsuresOddIf(expression = "g", result = true)
+    boolean m3() {
+      g = odd;
+      return true;
+    }
+
+    @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true)
+    boolean m4() {
+      f = odd;
+      return true;
+    }
+
+    @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true)
+    boolean m5() {
+      f = odd;
+      return true;
+    }
+  }
+}
diff --git a/framework/tests/flow2/ContractsOverridingSubtyping.java b/framework/tests/flow2/ContractsOverridingSubtyping.java
new file mode 100644
index 0000000..cf29e15
--- /dev/null
+++ b/framework/tests/flow2/ContractsOverridingSubtyping.java
@@ -0,0 +1,77 @@
+import org.checkerframework.common.subtyping.qual.Unqualified;
+import org.checkerframework.framework.qual.EnsuresQualifier;
+import org.checkerframework.framework.qual.EnsuresQualifierIf;
+import org.checkerframework.framework.qual.RequiresQualifier;
+import org.checkerframework.framework.testchecker.util.Odd;
+
+public class ContractsOverridingSubtyping {
+  static class Base {
+    String f;
+    @Odd String g;
+
+    @RequiresQualifier(expression = "f", qualifier = Odd.class)
+    void requiresOdd() {}
+
+    @RequiresQualifier(expression = "f", qualifier = Unqualified.class)
+    void requiresUnqual() {}
+
+    @EnsuresQualifier(expression = "f", qualifier = Odd.class)
+    void ensuresOdd() {
+      f = g;
+    }
+
+    @EnsuresQualifier(expression = "f", qualifier = Unqualified.class)
+    void ensuresUnqual() {}
+
+    @EnsuresQualifierIf(expression = "f", result = true, qualifier = Odd.class)
+    boolean ensuresIfOdd() {
+      f = g;
+      return true;
+    }
+
+    @EnsuresQualifierIf(expression = "f", result = true, qualifier = Unqualified.class)
+    boolean ensuresIfUnqual() {
+      return true;
+    }
+  }
+
+  static class Derived extends Base {
+
+    @Override
+    @RequiresQualifier(expression = "f", qualifier = Unqualified.class)
+    void requiresOdd() {}
+
+    @Override
+    @RequiresQualifier(expression = "f", qualifier = Odd.class)
+    // :: error: (contracts.precondition.override)
+    void requiresUnqual() {}
+
+    @Override
+    @EnsuresQualifier(expression = "f", qualifier = Unqualified.class)
+    // :: error: (contracts.postcondition.override)
+    void ensuresOdd() {
+      f = g;
+    }
+
+    @Override
+    @EnsuresQualifier(expression = "f", qualifier = Odd.class)
+    void ensuresUnqual() {
+      f = g;
+    }
+
+    @Override
+    @EnsuresQualifierIf(expression = "f", result = true, qualifier = Unqualified.class)
+    // :: error: (contracts.conditional.postcondition.true.override)
+    boolean ensuresIfOdd() {
+      f = g;
+      return true;
+    }
+
+    @Override
+    @EnsuresQualifierIf(expression = "f", result = true, qualifier = Odd.class)
+    boolean ensuresIfUnqual() {
+      f = g;
+      return true;
+    }
+  }
+}
diff --git a/framework/tests/flow2/CustomContractWithArgs.java b/framework/tests/flow2/CustomContractWithArgs.java
new file mode 100644
index 0000000..b037664
--- /dev/null
+++ b/framework/tests/flow2/CustomContractWithArgs.java
@@ -0,0 +1,84 @@
+import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
+import org.checkerframework.framework.qual.PostconditionAnnotation;
+import org.checkerframework.framework.qual.PreconditionAnnotation;
+import org.checkerframework.framework.qual.QualifierArgument;
+import org.checkerframework.framework.testchecker.util.Value;
+
+public class CustomContractWithArgs {
+  // Postcondition for Value
+  @PostconditionAnnotation(qualifier = Value.class)
+  @interface EnsuresValue {
+    public String[] value();
+
+    @QualifierArgument("value")
+    public int targetValue();
+  }
+
+  // Conditional postcondition for LTLengthOf
+  @ConditionalPostconditionAnnotation(qualifier = Value.class)
+  @interface EnsuresValueIf {
+    public boolean result();
+
+    public String[] expression();
+
+    @QualifierArgument("value")
+    public int targetValue();
+  }
+
+  // Precondition for LTLengthOf
+  @PreconditionAnnotation(qualifier = Value.class)
+  @interface RequiresValue {
+    public String[] value();
+
+    @QualifierArgument("value")
+    public int targetValue();
+  }
+
+  class Base {
+    Object o;
+
+    @Value(10) Object o10;
+
+    @EnsuresValue(value = "o", targetValue = 10)
+    void ensures() {
+      o = o10;
+    }
+
+    @EnsuresValue(value = "o", targetValue = 9)
+    // :: error: (contracts.postcondition)
+    void ensuresWrong() {
+      o = o10;
+    }
+
+    void ensuresUse() {
+      ensures();
+      o10 = o;
+    }
+
+    @EnsuresValueIf(expression = "o", targetValue = 10, result = true)
+    boolean ensuresIf(boolean b) {
+      if (b) {
+        o = o10;
+        return true;
+      } else {
+        return false;
+      }
+    }
+
+    @RequiresValue(value = "o", targetValue = 10)
+    void requires() {
+      o10 = o;
+    }
+
+    void use(boolean b) {
+      if (ensuresIf(b)) {
+        o10 = o;
+        requires();
+      }
+      // :: error: (assignment)
+      o10 = o;
+      // :: error: (contracts.precondition)
+      requires();
+    }
+  }
+}
diff --git a/framework/tests/flow2/Equal.java b/framework/tests/flow2/Equal.java
new file mode 100644
index 0000000..3e1518b
--- /dev/null
+++ b/framework/tests/flow2/Equal.java
@@ -0,0 +1,43 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class Equal {
+
+  String f1, f2, f3;
+
+  // annotation inference for equality
+  void t1(@Odd String p1, String p2) {
+    // :: error: (assignment)
+    @Odd String l1 = f1;
+    if (f1 == p1) {
+      @Odd String l2 = f1;
+    } else {
+      // :: error: (assignment)
+      @Odd String l3 = f1;
+    }
+  }
+
+  // annotation inference for equality
+  void t2(@Odd String p1, String p2) {
+    // :: error: (assignment)
+    @Odd String l1 = f1;
+    if (f1 != p1) {
+      // :: error: (assignment)
+      @Odd String l2 = f1;
+    } else {
+      @Odd String l3 = f1;
+    }
+  }
+
+  void t3(@Odd Long p1) {
+    // :: error: (assignment)
+    @Odd Long l1 = Long.valueOf(2L);
+    if (Long.valueOf(2L) == p1) {
+      // The result of valueOf is not deterministic, so it can't be refined.
+      // :: error: (assignment)
+      @Odd Long l2 = Long.valueOf(2L);
+    } else {
+      // :: error: (assignment)
+      @Odd Long l3 = Long.valueOf(2L);
+    }
+  }
+}
diff --git a/framework/tests/flow2/FieldShadowing.java b/framework/tests/flow2/FieldShadowing.java
new file mode 100644
index 0000000..cfef3d6
--- /dev/null
+++ b/framework/tests/flow2/FieldShadowing.java
@@ -0,0 +1,48 @@
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.qual.RequiresQualifier;
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+// various tests for the precondition mechanism
+public class FieldShadowing {
+
+  String f;
+
+  class Sub extends FieldShadowing {
+    String f;
+
+    @Pure
+    @RequiresQualifier(expression = "f", qualifier = Odd.class)
+    int reqSub() {
+      @Odd String l2 = f;
+      // :: error: (assignment)
+      @Odd String l1 = super.f;
+      int i;
+      i = 1;
+      return 1;
+    }
+
+    @Pure
+    @RequiresQualifier(expression = "super.f", qualifier = Odd.class)
+    int reqSuper() {
+      // :: error: (assignment)
+      @Odd String l2 = f;
+      @Odd String l1 = super.f;
+      return 1;
+    }
+
+    void t1(@Odd String p1) {
+      f = p1;
+      // :: error: (contracts.precondition)
+      reqSuper();
+      reqSub();
+    }
+
+    void t2(@Odd String p1) {
+      super.f = p1;
+      // :: error: (contracts.precondition)
+      reqSub();
+      reqSuper();
+    }
+  }
+}
diff --git a/framework/tests/flow2/Issue4449.java b/framework/tests/flow2/Issue4449.java
new file mode 100644
index 0000000..32ad27d
--- /dev/null
+++ b/framework/tests/flow2/Issue4449.java
@@ -0,0 +1,33 @@
+import org.checkerframework.dataflow.qual.SideEffectFree;
+
+public class Issue4449 {
+
+  @SideEffectFree
+  public void test1(long[] x) {
+    // :: error: (purity.not.sideeffectfree.assign.array)
+    x[0] = 1;
+
+    long y;
+
+    // :: error: (purity.not.sideeffectfree.assign.array)
+    ++x[0];
+    // :: error: (purity.not.sideeffectfree.assign.array)
+    --x[0];
+    // :: error: (purity.not.sideeffectfree.assign.array)
+    x[0]++;
+    // :: error: (purity.not.sideeffectfree.assign.array)
+    x[0]--;
+
+    // :: error: (purity.not.sideeffectfree.assign.array)
+    y = ++x[0];
+    // :: error: (purity.not.sideeffectfree.assign.array)
+    y = --x[0];
+    // :: error: (purity.not.sideeffectfree.assign.array)
+    y = x[0]++;
+    // :: error: (purity.not.sideeffectfree.assign.array)
+    y = x[0]--;
+
+    y = +x[0];
+    y = -x[0];
+  }
+}
diff --git a/framework/tests/flow2/Issue951.java b/framework/tests/flow2/Issue951.java
new file mode 100644
index 0000000..55d1f4f
--- /dev/null
+++ b/framework/tests/flow2/Issue951.java
@@ -0,0 +1,132 @@
+// Test case for issue #951:
+// https://github.com/typetools/checker-framework/issues/951
+
+import org.checkerframework.dataflow.qual.Deterministic;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+
+public class Issue951 {
+
+  @Pure
+  public static int min(int[] a) {
+    if (a.length == 0) {
+      throw new MyExceptionSefConstructor("Empty array passed to min(int[])");
+    }
+    int result = a[0];
+    for (int i = 1; i < a.length; i++) {
+      result = min(result, a[i]);
+    }
+    return result;
+  }
+
+  @Pure
+  public static int arbitraryExceptionArg1() {
+    // :: error: (purity.not.deterministic.not.sideeffectfree.call)
+    // :: error: (purity.not.sideeffectfree.call)
+    throw new MyException("" + arbitraryMethod());
+  }
+
+  @Pure
+  public static int arbitraryExceptionArg2() {
+    // :: error: (purity.not.deterministic.not.sideeffectfree.call)
+    throw new MyExceptionSefConstructor("" + arbitraryMethod());
+  }
+
+  @Pure
+  public static int sefExceptionArg1() {
+    // The method is safe, so this is a false positive warning;
+    // in the future the Purity Checker may not issue this warning.
+    // :: error: (purity.not.deterministic.call)
+    // :: error: (purity.not.sideeffectfree.call)
+    throw new MyException("" + sefMethod());
+  }
+
+  @Pure
+  public static int sefExceptionArg2() {
+    // The method is safe, so this is a false positive warning;
+    // in the future the Purity Checker may not issue this warning.
+    // :: error: (purity.not.deterministic.call)
+    throw new MyExceptionSefConstructor("" + sefMethod());
+  }
+
+  @Pure
+  public static int detExceptionArg1() {
+    // :: error: (purity.not.sideeffectfree.call)
+    // :: error: (purity.not.sideeffectfree.call)
+    throw new MyException("" + detMethod());
+  }
+
+  @Pure
+  public static int detExceptionArg2() {
+    // :: error: (purity.not.sideeffectfree.call)
+    throw new MyExceptionSefConstructor("" + detMethod());
+  }
+
+  @Pure
+  public static int pureExceptionArg1(int a, int b) {
+    // :: error: (purity.not.sideeffectfree.call)
+    throw new MyException("" + min(a, b));
+  }
+
+  @Pure
+  public static int pureExceptionArg2(int a, int b) {
+    throw new MyExceptionSefConstructor("" + min(a, b));
+  }
+
+  @Pure
+  int throwNewWithinTry() {
+    for (int i = 0; i < 10; i++) {
+      try {
+        for (int j = 0; j < 10; j++) {
+          throw new MyExceptionSefConstructor("foo");
+        }
+        // :: error: (purity.not.deterministic.catch)
+      } catch (MyExceptionSefConstructor e) {
+        return -1;
+      }
+    }
+    return 22;
+  }
+
+  // Helper methods
+
+  // Not deterministic or side-effect-free
+  static int arbitraryMethod() {
+    return 22;
+  }
+
+  // Not deterministic
+  @SideEffectFree
+  static int sefMethod() {
+    return 22;
+  }
+
+  @Deterministic
+  // Not side-effect-free
+  static int detMethod() {
+    return 22;
+  }
+
+  @Pure
+  static int min(int a, int b) {
+    if (a < b) {
+      return a;
+    } else {
+      return b;
+    }
+  }
+
+  // Constructors
+
+  static class MyException extends Error {
+    // Not side-effect-free
+    MyException(String message) {}
+  }
+
+  static class MyExceptionSefConstructor extends Error {
+    // Side-effect-free
+    @SuppressWarnings("purity.not.sideeffectfree.call") // until java.util.Error is annotated
+    @SideEffectFree
+    MyExceptionSefConstructor(String message) {}
+  }
+}
diff --git a/framework/tests/flow2/MetaPostcondition.java b/framework/tests/flow2/MetaPostcondition.java
new file mode 100644
index 0000000..2d4b167
--- /dev/null
+++ b/framework/tests/flow2/MetaPostcondition.java
@@ -0,0 +1,172 @@
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class MetaPostcondition {
+
+  String f1, f2, f3;
+  MetaPostcondition p;
+
+  /** *** normal postcondition ***** */
+  @EnsuresOdd("f1")
+  void oddF1() {
+    f1 = null;
+  }
+
+  @EnsuresOdd("p.f1")
+  void oddF1_1() {
+    p.f1 = null;
+  }
+
+  @EnsuresOdd("#1.f1")
+  void oddF1_2(final MetaPostcondition param) {
+    param.f1 = null;
+  }
+
+  @EnsuresOdd("f1")
+  // :: error: (contracts.postcondition)
+  void oddF1_error() {}
+
+  @EnsuresOdd("---")
+  // :: error: (flowexpr.parse.error)
+  void error() {}
+
+  @EnsuresOdd("#1.#2")
+  // :: error: (flowexpr.parse.error)
+  void error2(final String p1, final String p2) {}
+
+  @EnsuresOdd("f1")
+  void exception() {
+    throw new RuntimeException();
+  }
+
+  @EnsuresOdd("#1")
+  void param1(final @Odd String f) {}
+
+  @EnsuresOdd({"#1", "#2"})
+  // :: error: (flowexpr.parameter.not.final)
+  void param2(@Odd String f, @Odd String g) {
+    f = g;
+  }
+
+  @EnsuresOdd("#1")
+  // :: error: (flowexpr.parse.index.too.big)
+  void param3() {}
+
+  // basic postcondition test
+  void t1(@Odd String p1, String p2) {
+    // :: error: (assignment)
+    @Odd String l1 = f1;
+    oddF1();
+    @Odd String l2 = f1;
+
+    // :: error: (flowexpr.parse.error.postcondition)
+    error();
+  }
+
+  // test parameter syntax
+  void t2(@Odd String p1, String p2) {
+    // :: error: (flowexpr.parse.index.too.big)
+    param3();
+  }
+
+  // postcondition with more complex expression
+  void tn1(boolean b) {
+    // :: error: (assignment)
+    @Odd String l1 = p.f1;
+    oddF1_1();
+    @Odd String l2 = p.f1;
+  }
+
+  // postcondition with more complex expression
+  void tn2(boolean b) {
+    MetaPostcondition param = null;
+    // :: error: (assignment)
+    @Odd String l1 = param.f1;
+    oddF1_2(param);
+    @Odd String l2 = param.f1;
+  }
+
+  // basic postcondition test
+  void tnm1(@Odd String p1, @Value String p2) {
+    // :: error: (assignment)
+    @Odd String l1 = f1;
+    // :: error: (assignment)
+    @Value String l2 = f2;
+
+    // :: error: (flowexpr.parse.error.postcondition)
+    error2(p1, p2);
+  }
+
+  /** conditional postcondition */
+  @EnsuresOddIf(result = true, expression = "f1")
+  boolean condOddF1(boolean b) {
+    if (b) {
+      f1 = null;
+      return true;
+    }
+    return false;
+  }
+
+  @EnsuresOddIf(result = false, expression = "f1")
+  boolean condOddF1False(boolean b) {
+    if (b) {
+      return true;
+    }
+    f1 = null;
+    return false;
+  }
+
+  @EnsuresOddIf(result = false, expression = "f1")
+  boolean condOddF1Invalid(boolean b) {
+    if (b) {
+      f1 = null;
+      return true;
+    }
+    // :: error: (contracts.conditional.postcondition)
+    return false;
+  }
+
+  @EnsuresOddIf(result = false, expression = "f1")
+  // :: error: (contracts.conditional.postcondition.returntype)
+  void wrongReturnType() {}
+
+  @EnsuresOddIf(result = false, expression = "f1")
+  // :: error: (contracts.conditional.postcondition.returntype)
+  String wrongReturnType2() {
+    f1 = null;
+    return "";
+  }
+
+  // basic conditional postcondition test
+  void t3(@Odd String p1, String p2) {
+    condOddF1(true);
+    // :: error: (assignment)
+    @Odd String l1 = f1;
+    if (condOddF1(false)) {
+      @Odd String l2 = f1;
+    }
+    // :: error: (assignment)
+    @Odd String l3 = f1;
+  }
+
+  // basic conditional postcondition test (inverted)
+  void t4(@Odd String p1, String p2) {
+    condOddF1False(true);
+    // :: error: (assignment)
+    @Odd String l1 = f1;
+    if (!condOddF1False(false)) {
+      @Odd String l2 = f1;
+    }
+    // :: error: (assignment)
+    @Odd String l3 = f1;
+  }
+
+  // basic conditional postcondition test 2
+  void t5(boolean b) {
+    condOddF1(true);
+    if (b) {
+      // :: error: (assignment)
+      @Odd String l2 = f1;
+    }
+  }
+}
diff --git a/framework/tests/flow2/MetaPrecondition.java b/framework/tests/flow2/MetaPrecondition.java
new file mode 100644
index 0000000..1084f51
--- /dev/null
+++ b/framework/tests/flow2/MetaPrecondition.java
@@ -0,0 +1,82 @@
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+// Tests for the meta-annotations for contracts.
+public class MetaPrecondition {
+
+  String f1, f2, f3;
+  MetaPrecondition p;
+
+  @RequiresOdd("f1")
+  void requiresF1() {
+    // :: error: (assignment)
+    @Value String l1 = f1;
+    @Odd String l2 = f1;
+  }
+
+  @Pure
+  @RequiresOdd("f1")
+  int requiresF1AndPure() {
+    // :: error: (assignment)
+    @Value String l1 = f1;
+    @Odd String l2 = f1;
+    return 1;
+  }
+
+  @RequiresOdd("---")
+  // :: error: (flowexpr.parse.error)
+  void error() {
+    // :: error: (assignment)
+    @Value String l1 = f1;
+    // :: error: (assignment)
+    @Odd String l2 = f1;
+  }
+
+  @RequiresOdd("#1")
+  void requiresParam(String p) {
+    // :: error: (assignment)
+    @Value String l1 = p;
+    @Odd String l2 = p;
+  }
+
+  @RequiresOdd({"#1", "#2"})
+  void requiresParams(String p1, String p2) {
+    // :: error: (assignment)
+    @Value String l1 = p1;
+    // :: error: (assignment)
+    @Value String l2 = p2;
+    @Odd String l3 = p1;
+    @Odd String l4 = p2;
+  }
+
+  @RequiresOdd("#1")
+  // :: error: (flowexpr.parse.index.too.big)
+  void param3() {}
+
+  void t1(@Odd String p1, String p2) {
+    // :: error: (contracts.precondition)
+    requiresF1();
+    // :: error: (contracts.precondition)
+    requiresParam(p2);
+    // :: error: (contracts.precondition)
+    requiresParams(p1, p2);
+  }
+
+  void t2(@Odd String p1, String p2) {
+    f1 = p1;
+    requiresF1();
+    // :: error: (contracts.precondition)
+    requiresF1();
+  }
+
+  void t3(@Odd String p1, String p2) {
+    f1 = p1;
+    requiresF1AndPure();
+    requiresF1AndPure();
+    requiresF1AndPure();
+    requiresF1();
+    // :: error: (contracts.precondition)
+    requiresF1();
+  }
+}
diff --git a/framework/tests/flow2/MethodCallFlowExpr.java b/framework/tests/flow2/MethodCallFlowExpr.java
new file mode 100644
index 0000000..ca25b40
--- /dev/null
+++ b/framework/tests/flow2/MethodCallFlowExpr.java
@@ -0,0 +1,197 @@
+import org.checkerframework.dataflow.qual.*;
+import org.checkerframework.framework.qual.EnsuresQualifier;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class MethodCallFlowExpr {
+
+  @Pure
+  String p1(int i) {
+    return "";
+  }
+
+  @Pure
+  String p1b(Long i) {
+    return "";
+  }
+
+  @Pure
+  String p1(String s) {
+    return "";
+  }
+
+  String nonpure() {
+    return "";
+  }
+
+  @Pure
+  String p2(String s, long l, String s2) {
+    return "";
+  }
+
+  @Pure
+  <T> String p1c(T i) {
+    return "";
+  }
+
+  @Pure
+  static String p1d(int i) {
+    return "";
+  }
+
+  @EnsuresQualifier(expression = "p1c(\"abc\")", qualifier = Odd.class)
+  // :: error: (contracts.postcondition)
+  void e0() {
+    // don't bother with implementation
+  }
+
+  @EnsuresQualifier(expression = "p1(1)", qualifier = Odd.class)
+  // :: error: (contracts.postcondition)
+  void e1() {
+    // don't bother with implementation
+  }
+
+  @EnsuresQualifier(expression = "p1(\"abc\")", qualifier = Odd.class)
+  // :: error: (contracts.postcondition)
+  void e2() {
+    // don't bother with implementation
+  }
+
+  @EnsuresQualifier(expression = "p2(\"abc\", 2L, p1(1))", qualifier = Odd.class)
+  // :: error: (contracts.postcondition)
+  void e4() {
+    // don't bother with implementation
+  }
+
+  @EnsuresQualifier(expression = "p1b(2L)", qualifier = Odd.class)
+  // :: error: (contracts.postcondition)
+  void e5() {
+    // don't bother with implementation
+  }
+
+  @EnsuresQualifier(expression = "p1b(null)", qualifier = Odd.class)
+  // :: error: (contracts.postcondition)
+  void e6() {
+    // don't bother with implementation
+  }
+
+  @EnsuresQualifier(expression = "p1d(1)", qualifier = Odd.class)
+  // :: error: (contracts.postcondition)
+  void e7a() {
+    // don't bother with implementation
+  }
+
+  @EnsuresQualifier(expression = "this.p1d(1)", qualifier = Odd.class)
+  // :: error: (contracts.postcondition)
+  void e7b() {
+    // don't bother with implementation
+  }
+
+  @EnsuresQualifier(expression = "MethodCallFlowExpr.p1d(1)", qualifier = Odd.class)
+  // :: error: (contracts.postcondition)
+  void e7c() {
+    // don't bother with implementation
+  }
+
+  void t1() {
+    // :: error: (assignment)
+    @Odd String l1 = p1(1);
+    e1();
+    // :: error: (assignment)
+    @Odd String l2 = p1(2);
+    @Odd String l3 = p1(1);
+  }
+
+  void t2() {
+    // :: error: (assignment)
+    @Odd String l1 = p1("abc");
+    e2();
+    // :: error: (assignment)
+    @Odd String l2 = p1("def");
+    // :: error: (assignment)
+    @Odd String l2b = p1(1);
+    @Odd String l3 = p1("abc");
+  }
+
+  void t3() {
+    // :: error: (assignment)
+    @Odd String l1 = p2("abc", 2L, p1(1));
+    e4();
+    // :: error: (assignment)
+    @Odd String l2 = p2("abc", 2L, p1(2));
+    // :: error: (assignment)
+    @Odd String l2b = p2("abc", 1L, p1(1));
+    @Odd String l3 = p2("abc", 2L, p1(1));
+  }
+
+  void t4() {
+    // :: error: (assignment)
+    @Odd String l1 = p1b(2L);
+    e5();
+    // :: error: (assignment)
+    @Odd String l2 = p1b(null);
+    // :: error: (assignment)
+    @Odd String l2b = p1b(1L);
+    // FIXME: the following line shouldn't give an error
+    // (or at least it didn't give an error earlier)
+    // @Odd String l3 = p1b(2L);
+  }
+
+  void t5() {
+    // :: error: (assignment)
+    @Odd String l1 = p1b(null);
+    e6();
+    // :: error: (assignment)
+    @Odd String l2 = p1b(1L);
+    // FIXME: the following line shouldn't give an error
+    // (or at least it didn't give an error earlier)
+    // @Odd String l3 = p1b(null);
+  }
+
+  void t6() {
+    // :: error: (assignment)
+    @Odd String l1 = p1c("abc");
+    e0();
+    // :: error: (assignment)
+    @Odd String l2 = p1c("def");
+    @Odd String l3 = p1c("abc");
+  }
+
+  void t7() {
+    // :: error: (assignment)
+    @Odd String l1 = p1d(1);
+    // :: error: (assignment)
+    @Odd String l1b = MethodCallFlowExpr.p1d(1);
+    // :: error: (assignment)
+    @Odd String l1c = this.p1d(1);
+    e7a();
+    @Odd String l2 = p1d(1);
+    @Odd String l2b = MethodCallFlowExpr.p1d(1);
+    @Odd String l2c = this.p1d(1);
+  }
+
+  void t8() {
+    // :: error: (assignment)
+    @Odd String l1 = p1d(1);
+    // :: error: (assignment)
+    @Odd String l1b = MethodCallFlowExpr.p1d(1);
+    // :: error: (assignment)
+    @Odd String l1c = this.p1d(1);
+    e7b();
+    @Odd String l2 = p1d(1);
+    @Odd String l2b = MethodCallFlowExpr.p1d(1);
+    @Odd String l2c = this.p1d(1);
+  }
+
+  void t9() {
+    // :: error: (assignment)
+    @Odd String l1 = p1d(1);
+    // :: error: (assignment)
+    @Odd String l1b = MethodCallFlowExpr.p1d(1);
+    // :: error: (assignment)
+    @Odd String l1c = this.p1d(1);
+    e7c();
+    @Odd String l2 = p1d(1);
+    @Odd String l2b = MethodCallFlowExpr.p1d(1);
+    @Odd String l2c = this.p1d(1);
+  }
+}
diff --git a/framework/tests/flow2/Monotonic.java b/framework/tests/flow2/Monotonic.java
new file mode 100644
index 0000000..b01d55b
--- /dev/null
+++ b/framework/tests/flow2/Monotonic.java
@@ -0,0 +1,48 @@
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class Monotonic {
+
+  String f1;
+  @MonotonicOdd String f2;
+  @MonotonicOdd String f2b;
+  @Odd String f3;
+  Monotonic[] ms;
+
+  void nonpure() {}
+
+  void t1(@Odd String p1) {
+    // :: error: (assignment)
+    @Odd String l1 = f2;
+    if (f2 == p1) {
+      @Odd String l2 = f2;
+      nonpure();
+      @Odd String l3 = f2;
+    }
+  }
+
+  void t2(@Odd String p1) {
+    // :: error: (assignment)
+    f2 = f1;
+    // :: error: (monotonic)
+    f2 = f2b; // assigning @MonotonicOdd to @MonotonicOdd is not allowed
+  }
+
+  void t3(@Odd String p1) {
+    // :: error: (assignment)
+    @Odd String l1 = f2;
+    f2 = p1;
+    @Odd String l2 = f2;
+    nonpure();
+    @Odd String l3 = f2;
+  }
+
+  void t4(@Odd String p1) {
+    // :: error: (assignment)
+    @Odd String l1 = f2;
+    f2 = p1;
+    @Odd String l2 = f2;
+    ms[0].f2 = p1;
+    @Odd String l3 = f2;
+  }
+}
diff --git a/framework/tests/flow2/NonMethodCode.java b/framework/tests/flow2/NonMethodCode.java
new file mode 100644
index 0000000..5f69809
--- /dev/null
+++ b/framework/tests/flow2/NonMethodCode.java
@@ -0,0 +1,36 @@
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class NonMethodCode {
+
+  @Odd String f1 = null;
+  String g1 = "def";
+
+  static @Odd String sf1 = null;
+  static String sg1 = "def";
+
+  // test flow for field initializer
+  @Odd String f2 = g1 == f1 ? g1 : f1;
+
+  // test flow for initializer blocks
+  {
+    String l1 = f1;
+    @Odd String l2 = l1;
+    if (g1 == f1) {
+      @Odd String l3 = g1;
+    }
+    // :: error: (assignment)
+    @Odd String l4 = g1;
+  }
+
+  // test flow for static initializer blocks
+  static {
+    String l1 = sf1;
+    @Odd String l2 = l1;
+    if (sg1 == sf1) {
+      @Odd String l3 = sg1;
+    }
+    // :: error: (assignment)
+    @Odd String l4 = sg1;
+  }
+}
diff --git a/framework/tests/flow2/ParamFlowExpr.java b/framework/tests/flow2/ParamFlowExpr.java
new file mode 100644
index 0000000..8cec0cb
--- /dev/null
+++ b/framework/tests/flow2/ParamFlowExpr.java
@@ -0,0 +1,30 @@
+import org.checkerframework.framework.qual.RequiresQualifier;
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class ParamFlowExpr {
+
+  @RequiresQualifier(expression = "#1", qualifier = Odd.class)
+  void t1(String p1) {
+    String l1 = p1;
+  }
+
+  @RequiresQualifier(expression = "#1", qualifier = Odd.class)
+  // :: error: (flowexpr.parameter.not.final)
+  void t2(String p1) {
+    p1 = "";
+  }
+
+  @RequiresQualifier(expression = "#1", qualifier = Odd.class)
+  public static boolean eltsNonNull(Object[] seq1) {
+    if (seq1 == null) {
+      return false;
+    }
+    for (int i = 0; i < seq1.length; i++) {
+      if (seq1[i] == null) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/framework/tests/flow2/Postcondition.java b/framework/tests/flow2/Postcondition.java
new file mode 100644
index 0000000..448262d
--- /dev/null
+++ b/framework/tests/flow2/Postcondition.java
@@ -0,0 +1,315 @@
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.qual.EnsuresQualifier;
+import org.checkerframework.framework.qual.EnsuresQualifierIf;
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class Postcondition {
+
+  String f1, f2, f3;
+  Postcondition p;
+
+  @Pure
+  String p1() {
+    return null;
+  }
+
+  /** *** normal postcondition ***** */
+  @EnsuresQualifier(expression = "f1", qualifier = Odd.class)
+  void oddF1() {
+    f1 = null;
+  }
+
+  @EnsuresQualifier(expression = "p.f1", qualifier = Odd.class)
+  void oddF1_1() {
+    p.f1 = null;
+  }
+
+  @EnsuresQualifier(expression = "#1.f1", qualifier = Odd.class)
+  void oddF1_2(final Postcondition param) {
+    param.f1 = null;
+  }
+
+  @EnsuresQualifier(expression = "p.p1()", qualifier = Odd.class)
+  void oddF1_3() {
+    if (p.p1() == null) {
+      return;
+    }
+    throw new RuntimeException();
+  }
+
+  @EnsuresQualifier(expression = "f1", qualifier = Value.class)
+  // :: error: (contracts.postcondition)
+  void valueF1() {}
+
+  @EnsuresQualifier(expression = "---", qualifier = Value.class)
+  // :: error: (flowexpr.parse.error)
+  void error() {}
+
+  @EnsuresQualifier(expression = "#1.#2", qualifier = Value.class)
+  // :: error: (flowexpr.parse.error)
+  void error2(final String p1, final String p2) {}
+
+  @EnsuresQualifier(expression = "f1", qualifier = Value.class)
+  void exception() {
+    throw new RuntimeException();
+  }
+
+  @EnsuresQualifier(expression = "#1", qualifier = Value.class)
+  void param1(final @Value String f) {}
+
+  @EnsuresQualifier(
+      expression = {"#1", "#2"},
+      qualifier = Value.class)
+  // :: error: (flowexpr.parameter.not.final)
+  void param2(@Value String f, @Value String g) {
+    f = g;
+  }
+
+  @EnsuresQualifier(expression = "#1", qualifier = Value.class)
+  // :: error: (flowexpr.parse.index.too.big)
+  void param3() {}
+
+  // basic postcondition test
+  void t1(@Odd String p1, String p2) {
+    valueF1();
+    // :: error: (assignment)
+    @Odd String l1 = f1;
+    oddF1();
+    @Odd String l2 = f1;
+
+    // :: error: (flowexpr.parse.error.postcondition)
+    error();
+  }
+
+  // test parameter syntax
+  void t2(@Odd String p1, String p2) {
+    // :: error: (flowexpr.parse.index.too.big)
+    param3();
+  }
+
+  // postcondition with more complex expression
+  void tn1(boolean b) {
+    // :: error: (assignment)
+    @Odd String l1 = p.f1;
+    oddF1_1();
+    @Odd String l2 = p.f1;
+  }
+
+  // postcondition with more complex expression
+  void tn2(boolean b) {
+    Postcondition param = null;
+    // :: error: (assignment)
+    @Odd String l1 = param.f1;
+    oddF1_2(param);
+    @Odd String l2 = param.f1;
+  }
+
+  // postcondition with more complex expression
+  void tn3(boolean b) {
+    // :: error: (assignment)
+    @Odd String l1 = p.p1();
+    oddF1_3();
+    @Odd String l2 = p.p1();
+  }
+
+  /** *** many postcondition ***** */
+  @EnsuresQualifier.List({
+    @EnsuresQualifier(expression = "f1", qualifier = Odd.class),
+    @EnsuresQualifier(expression = "f2", qualifier = Value.class)
+  })
+  void oddValueF1(@Value String p1) {
+    f1 = null;
+    f2 = p1;
+  }
+
+  @EnsuresQualifier(expression = "f1", qualifier = Odd.class)
+  @EnsuresQualifier(expression = "f2", qualifier = Value.class)
+  void oddValueF1_repeated1(@Value String p1) {
+    f1 = null;
+    f2 = p1;
+  }
+
+  @EnsuresQualifier.List({
+    @EnsuresQualifier(expression = "f1", qualifier = Odd.class),
+  })
+  @EnsuresQualifier(expression = "f2", qualifier = Value.class)
+  void oddValueF1_repeated2(@Value String p1) {
+    f1 = null;
+    f2 = p1;
+  }
+
+  @EnsuresQualifier(expression = "f1", qualifier = Odd.class)
+  @EnsuresQualifier.List({@EnsuresQualifier(expression = "f2", qualifier = Value.class)})
+  void oddValueF1_repeated3(@Value String p1) {
+    f1 = null;
+    f2 = p1;
+  }
+
+  @EnsuresQualifier.List({
+    @EnsuresQualifier(expression = "f1", qualifier = Odd.class),
+    @EnsuresQualifier(expression = "f2", qualifier = Value.class)
+  })
+  // :: error: (contracts.postcondition)
+  void oddValueF1_invalid(@Value String p1) {}
+
+  @EnsuresQualifier.List({
+    @EnsuresQualifier(expression = "--", qualifier = Odd.class),
+  })
+  // :: error: (flowexpr.parse.error)
+  void error2() {}
+
+  // basic postcondition test
+  void tnm1(@Odd String p1, @Value String p2) {
+    // :: error: (assignment)
+    @Odd String l1 = f1;
+    // :: error: (assignment)
+    @Value String l2 = f2;
+    oddValueF1(p2);
+    @Odd String l3 = f1;
+    @Value String l4 = f2;
+
+    // :: error: (flowexpr.parse.error.postcondition)
+    error2();
+  }
+
+  /** *** conditional postcondition ***** */
+  @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class)
+  boolean condOddF1(boolean b) {
+    if (b) {
+      f1 = null;
+      return true;
+    }
+    return false;
+  }
+
+  @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class)
+  boolean condOddF1False(boolean b) {
+    if (b) {
+      return true;
+    }
+    f1 = null;
+    return false;
+  }
+
+  @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class)
+  boolean condOddF1Invalid(boolean b) {
+    if (b) {
+      f1 = null;
+      return true;
+    }
+    // :: error: (contracts.conditional.postcondition)
+    return false;
+  }
+
+  @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class)
+  // :: error: (contracts.conditional.postcondition.returntype)
+  void wrongReturnType() {}
+
+  @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class)
+  // :: error: (contracts.conditional.postcondition.returntype)
+  String wrongReturnType2() {
+    f1 = null;
+    return "";
+  }
+
+  @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Odd.class)
+  boolean isOdd(final String p1) {
+    return isOdd(p1, 0);
+  }
+
+  @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Odd.class)
+  boolean isOdd(final String p1, int p2) {
+    return p1 == null;
+  }
+
+  @EnsuresQualifierIf(result = false, expression = "#1", qualifier = Odd.class)
+  boolean isNotOdd(final String p1) {
+    return !isOdd(p1);
+  }
+
+  // basic conditional postcondition test
+  void t3(@Odd String p1, String p2) {
+    condOddF1(true);
+    // :: error: (assignment)
+    @Odd String l1 = f1;
+    if (condOddF1(false)) {
+      @Odd String l2 = f1;
+    }
+    // :: error: (assignment)
+    @Odd String l3 = f1;
+  }
+
+  // basic conditional postcondition test (inverted)
+  void t4(@Odd String p1, String p2) {
+    condOddF1False(true);
+    // :: error: (assignment)
+    @Odd String l1 = f1;
+    if (!condOddF1False(false)) {
+      @Odd String l2 = f1;
+    }
+    // :: error: (assignment)
+    @Odd String l3 = f1;
+  }
+
+  // basic conditional postcondition test 2
+  void t5(boolean b) {
+    condOddF1(true);
+    if (b) {
+      // :: error: (assignment)
+      @Odd String l2 = f1;
+    }
+  }
+
+  /** *** many conditional postcondition ***** */
+  @EnsuresQualifierIf.List({
+    @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class),
+    @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Value.class)
+  })
+  boolean condsOddF1(boolean b, @Value String p1) {
+    if (b) {
+      f1 = null;
+      return true;
+    }
+    f1 = p1;
+    return false;
+  }
+
+  @EnsuresQualifierIf.List({
+    @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class),
+    @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Value.class)
+  })
+  boolean condsOddF1_invalid(boolean b, @Value String p1) {
+    if (b) {
+      // :: error: (contracts.conditional.postcondition)
+      return true;
+    }
+    // :: error: (contracts.conditional.postcondition)
+    return false;
+  }
+
+  @EnsuresQualifierIf.List({
+    @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class)
+  })
+  // :: error: (contracts.conditional.postcondition.returntype)
+  String wrongReturnType3() {
+    return "";
+  }
+
+  void t6(@Odd String p1, @Value String p2) {
+    condsOddF1(true, p2);
+    // :: error: (assignment)
+    @Odd String l1 = f1;
+    // :: error: (assignment)
+    @Value String l2 = f1;
+    if (condsOddF1(false, p2)) {
+      @Odd String l3 = f1;
+      // :: error: (assignment)
+      @Value String l4 = f1;
+    } else {
+      @Value String l5 = f1;
+      // :: error: (assignment)
+      @Odd String l6 = f1;
+    }
+  }
+}
diff --git a/framework/tests/flow2/Precondition.java b/framework/tests/flow2/Precondition.java
new file mode 100644
index 0000000..7b44f8a
--- /dev/null
+++ b/framework/tests/flow2/Precondition.java
@@ -0,0 +1,162 @@
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.qual.RequiresQualifier;
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+// various tests for the precondition mechanism
+public class Precondition {
+
+  String f1, f2, f3;
+  Precondition p;
+
+  @RequiresQualifier(expression = "f1", qualifier = Odd.class)
+  void requiresF1() {
+    // :: error: (assignment)
+    @Value String l1 = f1;
+    @Odd String l2 = f1;
+  }
+
+  @Pure
+  @RequiresQualifier(expression = "f1", qualifier = Odd.class)
+  int requiresF1AndPure() {
+    // :: error: (assignment)
+    @Value String l1 = f1;
+    @Odd String l2 = f1;
+    return 1;
+  }
+
+  @RequiresQualifier(expression = "f1", qualifier = Value.class)
+  void requiresF1Value() {
+    // :: error: (assignment)
+    @Odd String l1 = f1;
+    @Value String l2 = f1;
+  }
+
+  @RequiresQualifier(expression = "---", qualifier = Odd.class)
+  // :: error: (flowexpr.parse.error)
+  void error() {
+    // :: error: (assignment)
+    @Value String l1 = f1;
+    // :: error: (assignment)
+    @Odd String l2 = f1;
+  }
+
+  @RequiresQualifier(expression = "#1", qualifier = Odd.class)
+  void requiresParam(String p) {
+    // :: error: (assignment)
+    @Value String l1 = p;
+    @Odd String l2 = p;
+  }
+
+  @RequiresQualifier(
+      expression = {"#1", "#2"},
+      qualifier = Odd.class)
+  void requiresParams(String p1, String p2) {
+    // :: error: (assignment)
+    @Value String l1 = p1;
+    // :: error: (assignment)
+    @Value String l2 = p2;
+    @Odd String l3 = p1;
+    @Odd String l4 = p2;
+  }
+
+  @RequiresQualifier(expression = "#1", qualifier = Odd.class)
+  // :: error: (flowexpr.parse.index.too.big)
+  void param3() {}
+
+  void t1(@Odd String p1, String p2) {
+    // :: error: (contracts.precondition)
+    requiresF1();
+    // :: error: (contracts.precondition)
+    requiresF1Value();
+    // :: error: (contracts.precondition)
+    requiresParam(p2);
+    // :: error: (contracts.precondition)
+    requiresParams(p1, p2);
+  }
+
+  void t2(@Odd String p1, String p2) {
+    f1 = p1;
+    requiresF1();
+    // :: error: (contracts.precondition)
+    requiresF1();
+    // :: error: (contracts.precondition)
+    requiresF1Value();
+  }
+
+  void t3(@Odd String p1, String p2) {
+    f1 = p1;
+    requiresF1AndPure();
+    requiresF1AndPure();
+    requiresF1AndPure();
+    requiresF1();
+    // :: error: (contracts.precondition)
+    requiresF1();
+  }
+
+  void t4(@Odd String p1, String p2, @Value String p3) {
+    f1 = p1;
+    requiresF1();
+    f1 = p3;
+    requiresF1Value();
+    requiresParam(p1);
+    requiresParams(p1, p1);
+  }
+
+  class Inner {
+    @RequiresQualifier(expression = "f1", qualifier = Odd.class)
+    void foo() {}
+  }
+
+  @Odd String f4;
+
+  @RequiresQualifier(expression = "f4", qualifier = Odd.class)
+  void requiresF4() {}
+
+  void tt1() {
+    requiresF4();
+  }
+
+  /** *** multiple preconditions ***** */
+  @RequiresQualifier(expression = "f1", qualifier = Value.class)
+  @RequiresQualifier(expression = "f2", qualifier = Odd.class)
+  void multi() {
+    @Value String l1 = f1;
+    @Odd String l2 = f2;
+    // :: error: (assignment)
+    @Value String l3 = f2;
+    // :: error: (assignment)
+    @Odd String l4 = f1;
+  }
+
+  @RequiresQualifier.List({
+    @RequiresQualifier(expression = "f1", qualifier = Value.class),
+    @RequiresQualifier(expression = "f2", qualifier = Odd.class)
+  })
+  void multi_explicit_requiresqualifierlist() {
+    @Value String l1 = f1;
+    @Odd String l2 = f2;
+    // :: error: (assignment)
+    @Value String l3 = f2;
+    // :: error: (assignment)
+    @Odd String l4 = f1;
+  }
+
+  @RequiresQualifier.List({@RequiresQualifier(expression = "--", qualifier = Value.class)})
+  // :: error: (flowexpr.parse.error)
+  void error2() {}
+
+  void t5(@Odd String p1, String p2, @Value String p3) {
+    // :: error: (contracts.precondition)
+    multi();
+    f1 = p3;
+    // :: error: (contracts.precondition)
+    multi();
+    f1 = p3;
+    f2 = p1;
+    multi();
+
+    // :: error: (flowexpr.parse.error)
+    error2();
+  }
+}
diff --git a/framework/tests/flow2/Purity.java b/framework/tests/flow2/Purity.java
new file mode 100644
index 0000000..489320f
--- /dev/null
+++ b/framework/tests/flow2/Purity.java
@@ -0,0 +1,275 @@
+import org.checkerframework.dataflow.qual.Deterministic;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+// various tests for the @Pure annotation
+public class Purity {
+
+  String f1, f2, f3;
+  String[] a;
+
+  // class with a (potentially) non-pure constructor
+  private static class NonPureClass {}
+
+  // class with a pure constructor
+  private static class PureClass {
+    @Pure
+    // :: warning: (purity.deterministic.constructor)
+    public PureClass() {}
+  }
+
+  // class with a side-effect-free constructor
+  private static class SEClass {
+    @SideEffectFree
+    public SEClass() {}
+  }
+
+  // a method that is not pure (no annotation)
+  void nonpure() {}
+
+  @Pure
+  String pure() {
+    return "";
+  }
+
+  @Pure
+  // :: warning: (purity.deterministic.void.method)
+  void t1() {}
+
+  @SideEffectFree
+  void t1b() {}
+
+  @Deterministic
+  // :: warning: (purity.deterministic.void.method)
+  void t1c() {}
+
+  @Pure
+  String t2() {
+    return "";
+  }
+
+  @Pure
+  String t3() {
+    // :: error: (purity.not.deterministic.not.sideeffectfree.call)
+    nonpure();
+    // :: error: (purity.not.deterministic.call)
+    t16b(); // Calling a @SideEffectFree method
+    // :: error: (purity.not.sideeffectfree.call)
+    t16c(); // Calling a @Deterministic method
+    return "";
+  }
+
+  @Pure
+  String t4() {
+    pure();
+    return "";
+  }
+
+  @Pure
+  int t5() {
+    int i = 1;
+    return i;
+  }
+
+  @Pure
+  int t6() {
+    int j = 0;
+    for (int i = 0; i < 10; i++) {
+      j = j - i;
+    }
+    return j;
+  }
+
+  @Pure
+  String t7() {
+    if (true) {
+      return "a";
+    }
+    return "";
+  }
+
+  @Pure
+  int t8() {
+    return 1 - 2 / 3 * 2 % 2;
+  }
+
+  @Pure
+  String t9() {
+    return "b" + "a";
+  }
+
+  @Pure
+  String t10() {
+    // :: error: (purity.not.deterministic.not.sideeffectfree.assign.field)
+    f1 = "";
+    // :: error: (purity.not.deterministic.not.sideeffectfree.assign.field)
+    f2 = "";
+    return "";
+  }
+
+  @Pure
+  String t11(Purity l) {
+    // :: error: (purity.not.deterministic.not.sideeffectfree.assign.array)
+    l.a[0] = "";
+    return "";
+  }
+
+  @Pure
+  String t12(String[] s) {
+    // :: error: (purity.not.deterministic.not.sideeffectfree.assign.array)
+    s[0] = "";
+    return "";
+  }
+
+  @Pure
+  String t13() {
+    // :: error: (purity.not.deterministic.object.creation)
+    PureClass p = new PureClass();
+    return "";
+  }
+
+  @SideEffectFree
+  String t13b() {
+    PureClass p = new PureClass();
+    return "";
+  }
+
+  @SideEffectFree
+  String t13d() {
+    SEClass p = new SEClass();
+    return "";
+  }
+
+  @Deterministic
+  String t13c() {
+    // :: error: (purity.not.deterministic.object.creation)
+    PureClass p = new PureClass();
+    return "";
+  }
+
+  @Pure
+  String t14() {
+    String i = "";
+    i = "a";
+    return i;
+  }
+
+  @Pure
+  String t15() {
+    String[] s = new String[1];
+    return s[0];
+  }
+
+  @Pure
+  String t16() {
+    try {
+      int i = 1 / 0;
+      // :: error: (purity.not.deterministic.catch)
+    } catch (Throwable t) {
+      // ...
+    }
+    return "";
+  }
+
+  @SideEffectFree
+  String t16b() {
+    try {
+      int i = 1 / 0;
+    } catch (Throwable t) {
+      // ...
+    }
+    return "";
+  }
+
+  @Deterministic
+  String t16c() {
+    try {
+      int i = 1 / 0;
+      // :: error: (purity.not.deterministic.catch)
+    } catch (Throwable t) {
+      // ...
+    }
+    return "";
+  }
+
+  @Pure
+  String t12() {
+    // :: error: (purity.not.sideeffectfree.call)
+    // :: error: (purity.not.deterministic.object.creation)
+    NonPureClass p = new NonPureClass();
+    return "";
+  }
+
+  @Deterministic
+  String t17a(Purity l) {
+    // :: error: (purity.not.deterministic.assign.field)
+    f1 = "";
+    // :: error: (purity.not.deterministic.assign.array)
+    l.a[0] = "";
+    // :: error: (purity.not.deterministic.call)
+    nonpure();
+    // :: error: (purity.not.deterministic.call)
+    return t16b(); // Calling a @SideEffectFree method
+  }
+
+  @SideEffectFree
+  String t17b() {
+    // :: error: (purity.not.sideeffectfree.assign.field)
+    f1 = "";
+    // :: error: (purity.not.sideeffectfree.call)
+    NonPureClass p = new NonPureClass();
+    // :: error: (purity.not.sideeffectfree.call)
+    nonpure();
+    // :: error: (purity.not.sideeffectfree.call)
+    return t16c(); // Calling a @Deterministic method
+  }
+
+  // @Pure annotations on the overridden implementation.
+  class Super {
+    @Pure
+    int m1(int arg) {
+      return 0;
+    }
+
+    @Pure
+    int m2(int arg) {
+      return 0;
+    }
+
+    int m3(int arg) {
+      return 0;
+    }
+
+    int m4(int arg) {
+      return 0;
+    }
+  }
+
+  class Sub extends Super {
+    @Pure
+    int m1(int arg) {
+      return 0;
+    }
+
+    int m2(int arg) {
+      return 0;
+    }
+
+    @Pure
+    int m3(int arg) {
+      return 0;
+    }
+
+    int m4(int arg) {
+      return 0;
+    }
+  }
+
+  class MyClass extends Object {
+    public int hashCode() {
+      return 42;
+    }
+  }
+}
diff --git a/framework/tests/flow2/README b/framework/tests/flow2/README
new file mode 100644
index 0000000..d119988
--- /dev/null
+++ b/framework/tests/flow2/README
@@ -0,0 +1,5 @@
+To add a new file to the test suite, see
+  ../README
+
+To run the tests, do
+ (cd $CHECKERFRAMEWORK && ./gradlew Flow2Test)
diff --git a/framework/tests/flow2/StorePure.java b/framework/tests/flow2/StorePure.java
new file mode 100644
index 0000000..d253459
--- /dev/null
+++ b/framework/tests/flow2/StorePure.java
@@ -0,0 +1,148 @@
+import org.checkerframework.dataflow.qual.Deterministic;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+// various tests about keeping information in the store about pure method calls
+public class StorePure {
+
+  String f1, f2;
+
+  // various pure methods
+
+  @Pure
+  String pure1() {
+    return null;
+  }
+
+  @Pure
+  String pure1b() {
+    return null;
+  }
+
+  @Deterministic
+  String pure1c() {
+    return null;
+  }
+
+  @Pure
+  String pure2(int i) {
+    return null;
+  }
+
+  @Pure
+  String pure3(boolean b) {
+    return null;
+  }
+
+  @Pure
+  String pure4(String o) {
+    return null;
+  }
+
+  void nonpure() {}
+
+  void t1(@Odd String p1, String p2, boolean b1) {
+    String l0 = "";
+    if (pure1() == p1) {
+      @Odd String l1 = pure1();
+      l0 = "a"; // does not remove information
+      @Odd String l1b = pure1();
+      // :: error: (assignment)
+      @Odd String l2 = pure1b();
+      nonpure(); // non-pure method call might change the return value of pure1
+      // :: error: (assignment)
+      @Odd String l3 = pure1();
+    }
+  }
+
+  // check that it only works for deterministic methods
+  void t1b(@Odd String p1, String p2, boolean b1) {
+    if (pure1c() == p1) {
+      @Odd String l1 = pure1c();
+    }
+  }
+
+  void t2(@Odd String p1, String p2, boolean b1) {
+    String l0 = "";
+    if (pure1() == p1) {
+      @Odd String l1 = pure1();
+      l0 = "a"; // does not remove information
+      @Odd String l1b = pure1();
+      // :: error: (assignment)
+      @Odd String l2 = pure1b();
+      f1 = ""; // field update might change the return value of pure1
+      // :: error: (assignment)
+      @Odd String l3 = pure1();
+    }
+  }
+
+  void t3(@Odd String p1, String p2, boolean b1) {
+    String l0 = "";
+    if (pure2(1) == p1) {
+      // :: error: (assignment)
+      @Odd String l4 = pure2(0);
+      @Odd String l1 = pure2(1);
+      l0 = "a"; // does not remove information
+      @Odd String l1b = pure2(1);
+      nonpure(); // non-pure method call might change the return value of pure2
+      // :: error: (assignment)
+      @Odd String l3 = pure2(1);
+    }
+  }
+
+  void t4(@Odd String p1, String p2, boolean b1) {
+    String l0 = "";
+    if (pure2(1) == p1) {
+      // :: error: (assignment)
+      @Odd String l4 = pure2(0);
+      @Odd String l1 = pure2(1);
+      l0 = "a"; // does not remove information
+      @Odd String l1b = pure2(1);
+      f1 = ""; // field update might change the return value of pure2
+      // :: error: (assignment)
+      @Odd String l3 = pure2(1);
+    }
+  }
+
+  void t5(@Odd String p1, String p2, boolean b1) {
+    String l0 = "";
+    if (pure3(true) == p1) {
+      // :: error: (assignment)
+      @Odd String l4 = pure3(false);
+      @Odd String l1 = pure3(true);
+      l0 = "a"; // does not remove information
+      @Odd String l1b = pure3(true);
+      nonpure(); // non-pure method call might change the return value of pure2
+      // :: error: (assignment)
+      @Odd String l3 = pure3(true);
+    }
+  }
+
+  void t6(@Odd String p1, String p2, boolean b1) {
+    String l0 = "";
+    if (pure3(true) == p1) {
+      // :: error: (assignment)
+      @Odd String l4 = pure3(false);
+      @Odd String l1 = pure3(true);
+      l0 = "a"; // does not remove information
+      @Odd String l1b = pure3(true);
+      f1 = ""; // field update might change the return value of pure2
+      // :: error: (assignment)
+      @Odd String l3 = pure3(true);
+    }
+  }
+
+  // local variable as argument
+  void t7(@Odd String p1, String p2, boolean b1) {
+    String l0 = "";
+    if (pure4(l0) == p1) {
+      // :: error: (assignment)
+      @Odd String l4 = pure4("jk");
+      @Odd String l1 = pure4(l0);
+      l0 = "a"; // remove information (!)
+      // :: error: (assignment)
+      @Odd String l1b = pure4(l0);
+    }
+  }
+}
diff --git a/framework/tests/flow2/Termination.java b/framework/tests/flow2/Termination.java
new file mode 100644
index 0000000..51dc133
--- /dev/null
+++ b/framework/tests/flow2/Termination.java
@@ -0,0 +1,20 @@
+import org.checkerframework.dataflow.qual.TerminatesExecution;
+import org.checkerframework.framework.test.*;
+import org.checkerframework.framework.testchecker.util.*;
+
+// various tests for @TerminatesExecution
+public class Termination {
+
+  @TerminatesExecution
+  void exit() {}
+
+  void t1(@Odd String p1, String p2, boolean b1) {
+    String l1 = p2;
+    if (b1) {
+      l1 = p1;
+    } else {
+      exit();
+    }
+    @Odd String l3 = l1;
+  }
+}
diff --git a/framework/tests/flow2/flowexpression-scope/Class1.java b/framework/tests/flow2/flowexpression-scope/Class1.java
new file mode 100644
index 0000000..c7fe2ca
--- /dev/null
+++ b/framework/tests/flow2/flowexpression-scope/Class1.java
@@ -0,0 +1,5 @@
+package pkg1;
+
+public class Class1 {
+  public static Object field;
+}
diff --git a/framework/tests/flow2/flowexpression-scope/Class2.java b/framework/tests/flow2/flowexpression-scope/Class2.java
new file mode 100644
index 0000000..c9a88d9
--- /dev/null
+++ b/framework/tests/flow2/flowexpression-scope/Class2.java
@@ -0,0 +1,44 @@
+package pkg2;
+
+import org.checkerframework.framework.testchecker.util.EnsuresOdd;
+import org.checkerframework.framework.testchecker.util.Odd;
+import org.checkerframework.framework.testchecker.util.RequiresOdd;
+import pkg1.Class1;
+
+public class Class2 {
+  @RequiresOdd("Class1.field")
+  // :: error: (flowexpr.parse.error)
+  public void requiresOddParseError() {
+    // :: error: (assignment)
+    @Odd Object odd = Class1.field;
+  }
+
+  @RequiresOdd("pkg1.Class1.field")
+  public void requiresOdd() {
+    @Odd Object odd = Class1.field;
+  }
+
+  @EnsuresOdd("Class1.field")
+  // :: error: (flowexpr.parse.error)
+  public void ensuresOddParseError() {
+    // :: warning: (cast.unsafe.constructor.invocation)
+    Class1.field = new @Odd Object();
+  }
+
+  @EnsuresOdd("pkg1.Class1.field")
+  public void ensuresOdd() {
+    // :: warning: (cast.unsafe.constructor.invocation)
+    Class1.field = new @Odd Object();
+  }
+
+  void illegalUse() {
+    // :: error: (contracts.precondition)
+    requiresOdd();
+  }
+
+  void legalUse() {
+    // :: warning: (cast.unsafe.constructor.invocation)
+    Class1.field = new @Odd Object();
+    requiresOdd();
+  }
+}
diff --git a/framework/tests/flow2/flowexpression-scope/Issue862.java b/framework/tests/flow2/flowexpression-scope/Issue862.java
new file mode 100644
index 0000000..ee180ec
--- /dev/null
+++ b/framework/tests/flow2/flowexpression-scope/Issue862.java
@@ -0,0 +1,18 @@
+// Test case for Issue 862
+// https://github.com/typetools/checker-framework/issues/862
+
+package pkg3;
+
+import pkg2.Class2;
+
+public class Issue862 {
+  void illegalUse(Class2 class2) {
+    // :: error: (contracts.precondition)
+    class2.requiresOdd();
+  }
+
+  void legalUse(Class2 class2) {
+    class2.ensuresOdd();
+    class2.requiresOdd();
+  }
+}
diff --git a/framework/tests/flowexpression/ArrayCreationParsing.java b/framework/tests/flowexpression/ArrayCreationParsing.java
new file mode 100644
index 0000000..771051e
--- /dev/null
+++ b/framework/tests/flowexpression/ArrayCreationParsing.java
@@ -0,0 +1,34 @@
+package flowexpression;
+
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class ArrayCreationParsing {
+  @FlowExp("new int[2]") Object value1;
+
+  @FlowExp("new int[2][2]") Object value2;
+
+  @FlowExp("new String[2]") Object value3;
+
+  @FlowExp("new String[] {\"a\", \"b\"}")
+  Object value4;
+
+  int i;
+
+  @FlowExp("new int[i]") Object value5;
+
+  @FlowExp("new int[this.i]") Object value6;
+
+  @FlowExp("new int[getI()]") Object value7;
+
+  @FlowExp("new int[] {i, this.i, getI()}") Object value8;
+
+  int getI() {
+    return i;
+  }
+
+  void method(@FlowExp("new java.lang.String[2]") Object param) {
+    value3 = param;
+    // :: error: (assignment)
+    value1 = param;
+  }
+}
diff --git a/framework/tests/flowexpression/BinaryOperations.java b/framework/tests/flowexpression/BinaryOperations.java
new file mode 100644
index 0000000..8b36de6
--- /dev/null
+++ b/framework/tests/flowexpression/BinaryOperations.java
@@ -0,0 +1,10 @@
+package flowexpression;
+
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class BinaryOperations {
+
+  void method(int i, int j, @FlowExp("#1+#2") String s) {
+    @FlowExp("i+j") String q = s;
+  }
+}
diff --git a/framework/tests/flowexpression/Canonicalization.java b/framework/tests/flowexpression/Canonicalization.java
new file mode 100644
index 0000000..5bf7c8c
--- /dev/null
+++ b/framework/tests/flowexpression/Canonicalization.java
@@ -0,0 +1,41 @@
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class Canonicalization {
+
+  class LockExample {
+    protected final Object myLock = new Object();
+    protected @FlowExp("myLock") Object locked;
+    protected @FlowExp("this.myLock") Object locked2;
+
+    public @FlowExp("myLock") Object getLocked() {
+      return locked;
+    }
+  }
+
+  class Use {
+    final LockExample lockExample1 = new LockExample();
+    final Object myLock = new Object();
+
+    @FlowExp("lockExample1.myLock") Object o1 = lockExample1.locked;
+
+    @FlowExp("lockExample1.myLock") Object o2 = lockExample1.locked2;
+
+    @FlowExp("myLock")
+    // :: error: (assignment)
+    Object o3 = lockExample1.locked;
+
+    @FlowExp("this.myLock")
+    // :: error: (assignment)
+    Object o4 = lockExample1.locked2;
+
+    @FlowExp("lockExample1.myLock") Object oM1 = lockExample1.getLocked();
+
+    @FlowExp("myLock")
+    // :: error: (assignment)
+    Object oM2 = lockExample1.getLocked();
+
+    @FlowExp("this.myLock")
+    // :: error: (assignment)
+    Object oM3 = lockExample1.getLocked();
+  }
+}
diff --git a/framework/tests/flowexpression/CharAndDoubleParsing.java b/framework/tests/flowexpression/CharAndDoubleParsing.java
new file mode 100644
index 0000000..01341ba
--- /dev/null
+++ b/framework/tests/flowexpression/CharAndDoubleParsing.java
@@ -0,0 +1,13 @@
+package flowexpression;
+
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class CharAndDoubleParsing {
+  void doubleParsing(@FlowExp("1.0") Object doubleValue) {
+    @FlowExp("1.0") Object value = doubleValue;
+  }
+
+  void CharParsing(@FlowExp("'c'") Object charValue) {
+    @FlowExp("'c'") Object value = charValue;
+  }
+}
diff --git a/framework/tests/flowexpression/ClassLiterals.java b/framework/tests/flowexpression/ClassLiterals.java
new file mode 100644
index 0000000..4b62eea
--- /dev/null
+++ b/framework/tests/flowexpression/ClassLiterals.java
@@ -0,0 +1,32 @@
+package flowexpression;
+
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class ClassLiterals {
+  static class String {}
+
+  void method(
+      @FlowExp("String.class") Object p1,
+      @FlowExp("String.class") Object p2,
+      @FlowExp("java.lang.String.class") Object p3) {
+    @FlowExp("String.class") Object l1 = p1;
+    @FlowExp("String.class") Object l2 = p2;
+    // :: error: (assignment)
+    @FlowExp("String.class") Object l3 = p3;
+    // :: error: (assignment)
+    @FlowExp("java.lang.String.class") Object l4 = p1;
+    // :: error: (assignment)
+    @FlowExp("java.lang.String.class") Object l5 = p2;
+    @FlowExp("java.lang.String.class") Object l6 = p3;
+  }
+
+  @FlowExp("void.class") String s0;
+
+  @FlowExp("int.class") String s1;
+
+  @FlowExp("int[].class") String s2;
+
+  @FlowExp("String[].class") String s3;
+
+  @FlowExp("java.lang.String[].class") String s4;
+}
diff --git a/framework/tests/flowexpression/Complex.java b/framework/tests/flowexpression/Complex.java
new file mode 100644
index 0000000..576bd0d
--- /dev/null
+++ b/framework/tests/flowexpression/Complex.java
@@ -0,0 +1,39 @@
+package flowexpression;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class Complex {
+  class DocCategory {
+    public Map<String, String> fields = new HashMap<>();
+  }
+
+  protected DocCategory[] categories = new DocCategory[2];
+
+  void test() {
+    for (int c = 0; c < categories.length; c++) {
+
+      for (@FlowExp("categories[c].fields") String field : sortedKeySet(categories[c].fields)) {
+        @FlowExp("categories[c].fields") String f = field;
+      }
+    }
+  }
+
+  public static <K extends Comparable<? super K>, V> Collection<@FlowExp("#1") K> sortedKeySet(
+      Map<K, V> m) {
+    throw new RuntimeException();
+  }
+
+  private static Map<Integer, List<@FlowExp("succs1") Integer>> succs1 = new HashMap<>();
+
+  void method() {
+    Map<Integer, List<Integer>> dom1post = dominators(succs1);
+  }
+
+  public static <T> Map<T, List<T>> dominators(Map<T, List<@FlowExp("#1") T>> predecessors) {
+    throw new RuntimeException();
+  }
+}
diff --git a/framework/tests/flowexpression/Fields.java b/framework/tests/flowexpression/Fields.java
new file mode 100644
index 0000000..5bd6888
--- /dev/null
+++ b/framework/tests/flowexpression/Fields.java
@@ -0,0 +1,18 @@
+package flowexpression;
+
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class Fields {
+  static class String {
+    public static final java.lang.String HELLO = "hello";
+  }
+
+  void method(
+      // :: error: (expression.unparsable)
+      @FlowExp("java.lang.String.HELLO") Object p1, @FlowExp("Fields.String.HELLO") Object p2) {
+    // :: error: (assignment)
+    @FlowExp("String.HELLO") Object l1 = p1;
+    @FlowExp("String.HELLO") Object l2 = p2;
+    @FlowExp("flowexpression.Fields.String.HELLO") Object l3 = p2;
+  }
+}
diff --git a/framework/tests/flowexpression/InnerClasses.java b/framework/tests/flowexpression/InnerClasses.java
new file mode 100644
index 0000000..99a3ca9
--- /dev/null
+++ b/framework/tests/flowexpression/InnerClasses.java
@@ -0,0 +1,37 @@
+package flowexpression;
+
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class InnerClasses {
+  public String outerInstanceField = "";
+  public static String outerStaticField = "";
+
+  static class InnerClass {
+    // :: error: (expression.unparsable)
+    @FlowExp("outerInstanceField") Object o = null;
+
+    @FlowExp("outerStaticField") Object o2 = null;
+  }
+
+  class NonStaticInnerClass {
+    @FlowExp("outerInstanceField") Object o = null;
+
+    @FlowExp("outerStaticField") Object o2 = null;
+  }
+
+  static class InnerClass2 {
+    public String outerInstanceField = "";
+
+    @FlowExp("outerInstanceField") Object o = null;
+  }
+
+  class TestUses {
+    void method(InnerClass innerClass, InnerClass2 innerClass2) {
+      // :: error: (expression.unparsable) :: error: (assignment)
+      @FlowExp("innerClass.outerInstanceField") Object o = innerClass.o;
+      @FlowExp("InnerClasses.outerStaticField") Object o2 = innerClass.o2;
+
+      @FlowExp("innerClass2.outerInstanceField") Object o3 = innerClass2.o;
+    }
+  }
+}
diff --git a/framework/tests/flowexpression/Issue1609.java b/framework/tests/flowexpression/Issue1609.java
new file mode 100644
index 0000000..be71f46
--- /dev/null
+++ b/framework/tests/flowexpression/Issue1609.java
@@ -0,0 +1,210 @@
+// Test case for Issue 1609
+// https://github.com/typetools/checker-framework/issues/1609
+
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class Issue1609 {
+  void method(@FlowExp("\"" + s + "\"") String x) {}
+
+  public static final String s =
+      "\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa"
+          + "\u00fb\u00fc\u00fd\u00fe\u00ff\u0100\u0101\u0102\u0103\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e"
+          + "\u010f\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u0118\u0119\u011a\u011b\u011c\u011d\u011e\u011f\u0120\u0121\u0122"
+          + "\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012a\u012b\u012c\u012d\u012e\u012f\u0130\u0131\u0132\u0133\u0134\u0135\u0136"
+          + "\u0137\u0138\u0139\u013a\u013b\u013c\u013d\u013e\u013f\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u0149\u014a"
+          + "\u014b\u014c\u014d\u014e\u014f\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d\u015e"
+          + "\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016c\u016d\u016e\u016f\u0170\u0171\u0172"
+          + "\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017b\u017c\u017d\u017e\u017f\u0180\u0181\u0182\u0183\u0184\u0185\u0186"
+          + "\u0187\u0188\u0189\u018a\u018b\u018c\u018d\u018e\u018f\u0190\u0191\u0192\u0193\u0194\u0195\u0196\u0197\u0198\u0199\u019a"
+          + "\u019b\u019c\u019d\u019e\u019f\u01a0\u01a1\u01a2\u01a3\u01a4\u01a5\u01a6\u01a7\u01a8\u01a9\u01aa\u01ab\u01ac\u01ad\u01ae"
+          + "\u01af\u01b0\u01b1\u01b2\u01b3\u01b4\u01b5\u01b6\u01b7\u01b8\u01b9\u01ba\u01bb\u01bc\u01bd\u01be\u01bf\u01c0\u01c1\u01c2"
+          + "\u01c3\u01c4\u01c5\u01c6\u01c7\u01c8\u01c9\u01ca\u01cb\u01cc\u01cd\u01ce\u01cf\u01d0\u01d1\u01d2\u01d3\u01d4\u01d5\u01d6"
+          + "\u01d7\u01d8\u01d9\u01da\u01db\u01dc\u01dd\u01de\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01e6\u01e7\u01e8\u01e9\u01ea"
+          + "\u01eb\u01ec\u01ed\u01ee\u01ef\u01f0\u01f1\u01f2\u01f3\u01f4\u01f5\u01f6\u01f7\u01f8\u01f9\u01fa\u01fb\u01fc\u01fd\u01fe"
+          + "\u01ff\u0200\u0201\u0202\u0203\u0204\u0205\u0206\u0207\u0208\u0209\u020a\u020b\u020c\u020d\u020e\u020f\u0210\u0211\u0212"
+          + "\u0213\u0214\u0215\u0216\u0217\u0218\u0219\u021a\u021b\u021c\u021d\u021e\u021f\u0220\u0221\u0222\u0223\u0224\u0225\u0226"
+          + "\u0227\u0228\u0229\u022a\u022b\u022c\u022d\u022e\u022f\u0230\u0231\u0232\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023a"
+          + "\u023b\u023c\u023d\u023e\u023f\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e"
+          + "\u024f\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262"
+          + "\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276"
+          + "\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a"
+          + "\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e"
+          + "\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u02b0\u02b1\u02b2"
+          + "\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c2\u02c3\u02c4\u02c5\u02c6"
+          + "\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da"
+          + "\u02db\u02dc\u02dd\u02de\u02df\u02e0\u02e1\u02e2\u02e3\u02e4\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ee"
+          + "\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0300\u0301\u0302"
+          + "\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316"
+          + "\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a"
+          + "\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e"
+          + "\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352"
+          + "\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366"
+          + "\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0370\u0371\u0372\u0373\u0374\u0375\u0376\u0377\u0378\u0379\u037a"
+          + "\u037b\u037c\u037d\u037e\u037f\u0380\u0381\u0382\u0383\u0384\u0385\u0386\u0387\u0388\u0389\u038a\u038b\u038c\u038d\u038e"
+          + "\u038f\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a2"
+          + "\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6"
+          + "\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca"
+          + "\u03cb\u03cc\u03cd\u03ce\u03cf\u03d0\u03d1\u03d2\u03d3\u03d4\u03d5\u03d6\u03d7\u03d8\u03d9\u03da\u03db\u03dc\u03dd\u03de"
+          + "\u03df\u03e0\u03e1\u03e2\u03e3\u03e4\u03e5\u03e6\u03e7\u03e8\u03e9\u03ea\u03eb\u03ec\u03ed\u03ee\u03ef\u03f0\u03f1\u03f2"
+          + "\u03f3\u03f4\u03f5\u03f6\u03f7\u03f8\u03f9\u03fa\u03fb\u03fc\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406"
+          + "\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a"
+          + "\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e"
+          + "\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442"
+          + "\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456"
+          + "\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0460\u0461\u0462\u0463\u0464\u0465\u0466\u0467\u0468\u0469\u046a"
+          + "\u046b\u046c\u046d\u046e\u046f\u0470\u0471\u0472\u0473\u0474\u0475\u0476\u0477\u0478\u0479\u047a\u047b\u047c\u047d\u047e"
+          + "\u047f\u0480\u0481\u0482\u0483\u0484\u0485\u0486\u0487\u0488\u0489\u048a\u048b\u048c\u048d\u048e\u048f\u0490\u0491\u0492"
+          + "\u0493\u0494\u0495\u0496\u0497\u0498\u0499\u049a\u049b\u049c\u049d\u049e\u049f\u04a0\u04a1\u04a2\u04a3\u04a4\u04a5\u04a6"
+          + "\u04a7\u04a8\u04a9\u04aa\u04ab\u04ac\u04ad\u04ae\u04af\u04b0\u04b1\u04b2\u04b3\u04b4\u04b5\u04b6\u04b7\u04b8\u04b9\u04ba"
+          + "\u04bb\u04bc\u04bd\u04be\u04bf\u04c0\u04c1\u04c2\u04c3\u04c4\u04c5\u04c6\u04c7\u04c8\u04c9\u04ca\u04cb\u04cc\u04cd\u04ce"
+          + "\u04cf\u04d0\u04d1\u04d2\u04d3\u04d4\u04d5\u04d6\u04d7\u04d8\u04d9\u04da\u04db\u04dc\u04dd\u04de\u04df\u04e0\u04e1\u04e2"
+          + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6"
+          + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a"
+          + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e"
+          + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532"
+          + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546"
+          + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a"
+          + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e"
+          + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582"
+          + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596"
+          + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa"
+          + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be"
+          + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2"
+          + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6"
+          + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa"
+          + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e"
+          + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622"
+          + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636"
+          + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a"
+          + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e"
+          + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672"
+          + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686"
+          + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a"
+          + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae"
+          + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2"
+          + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6"
+          + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea"
+          + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe"
+          + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712"
+          + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726"
+          + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a"
+          + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e"
+          + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762"
+          + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776"
+          + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a"
+          + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e"
+          + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2"
+          + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6"
+          + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da"
+          + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee"
+          + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802"
+          + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816"
+          + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a"
+          + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e"
+          + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852"
+          + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866"
+          + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a"
+          + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e"
+          + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2"
+          + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6"
+          + "\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca"
+          + "\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de"
+          + "\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2"
+          + "\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u0901\u0902\u0903\u0904\u0905\u0906"
+          + "\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a"
+          + "\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e"
+          + "\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093a\u093b\u093c\u093d\u093e\u093f\u0940\u0941\u0942"
+          + "\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u094e\u094f\u0950\u0951\u0952\u0953\u0954\u0955\u0956"
+          + "\u0957\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0964\u0965\u0966\u0967\u0968\u0969\u096a"
+          + "\u096b\u096c\u096d\u096e\u096f\u0970\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e"
+          + "\u097f\u0980\u0981\u0982\u0983\u0984\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098d\u098e\u098f\u0990\u0991\u0992"
+          + "\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6"
+          + "\u09a7\u09a8\u09a9\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b1\u09b2\u09b3\u09b4\u09b5\u09b6\u09b7\u09b8\u09b9\u09ba"
+          + "\u09bb\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4\u09c5\u09c6\u09c7\u09c8\u09c9\u09ca\u09cb\u09cc\u09cd\u09ce"
+          + "\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d7\u09d8\u09d9\u09da\u09db\u09dc\u09dd\u09de\u09df\u09e0\u09e1\u09e2"
+          + "\u09e3\u09e4\u09e5\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09f0\u09f1\u09f2\u09f3\u09f4\u09f5\u09f6"
+          + "\u09f7\u09f8\u09f9\u09fa\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a01\u0a02\u0a03\u0a04\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a"
+          + "\u0a0b\u0a0c\u0a0d\u0a0e\u0a0f\u0a10\u0a11\u0a12\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e"
+          + "\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a29\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a31\u0a32"
+          + "\u0a33\u0a34\u0a35\u0a36\u0a37\u0a38\u0a39\u0a3a\u0a3b\u0a3c\u0a3d\u0a3e\u0a3f\u0a40\u0a41\u0a42\u0a43\u0a44\u0a45\u0a46"
+          + "\u0a47\u0a48\u0a49\u0a4a\u0a4b\u0a4c\u0a4d\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a59\u0a5a"
+          + "\u0a5b\u0a5c\u0a5d\u0a5e\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e"
+          + "\u0a6f\u0a70\u0a71\u0a72\u0a73\u0a74\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a81\u0a82"
+          + "\u0a83\u0a84\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8e\u0a8f\u0a90\u0a91\u0a92\u0a93\u0a94\u0a95\u0a96"
+          + "\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aa9\u0aaa"
+          + "\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab1\u0ab2\u0ab3\u0ab4\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0aba\u0abb\u0abc\u0abd\u0abe"
+          + "\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac6\u0ac7\u0ac8\u0ac9\u0aca\u0acb\u0acc\u0acd\u0ace\u0acf\u0ad0\u0ad1\u0ad2"
+          + "\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae0\u0ae1\u0ae2\u0ae3\u0ae4\u0ae5\u0ae6"
+          + "\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0af0\u0af1\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa"
+          + "\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b01\u0b02\u0b03\u0b04\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0d\u0b0e"
+          + "\u0b0f\u0b10\u0b11\u0b12\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22"
+          + "\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b29\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b31\u0b32\u0b33\u0b34\u0b35\u0b36"
+          + "\u0b37\u0b38\u0b39\u0b3a\u0b3b\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44\u0b45\u0b46\u0b47\u0b48\u0b49\u0b4a"
+          + "\u0b4b\u0b4c\u0b4d\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b56\u0b57\u0b58\u0b59\u0b5a\u0b5b\u0b5c\u0b5d\u0b5e"
+          + "\u0b5f\u0b60\u0b61\u0b62\u0b63\u0b64\u0b65\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b70\u0b71\u0b72"
+          + "\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b82\u0b83\u0b84\u0b85\u0b86"
+          + "\u0b87\u0b88\u0b89\u0b8a\u0b8b\u0b8c\u0b8d\u0b8e\u0b8f\u0b90\u0b91\u0b92\u0b93\u0b94\u0b95\u0b96\u0b97\u0b98\u0b99\u0b9a"
+          + "\u0b9b\u0b9c\u0b9d\u0b9e\u0b9f\u0ba0\u0ba1\u0ba2\u0ba3\u0ba4\u0ba5\u0ba6\u0ba7\u0ba8\u0ba9\u0baa\u0bab\u0bac\u0bad\u0bae"
+          + "\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0bba\u0bbb\u0bbc\u0bbd\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2"
+          + "\u0bc3\u0bc4\u0bc5\u0bc6\u0bc7\u0bc8\u0bc9\u0bca\u0bcb\u0bcc\u0bcd\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6"
+          + "\u0bd7\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0be6\u0be7\u0be8\u0be9\u0bea"
+          + "\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf0\u0bf1\u0bf2\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bf9\u0bfa\u0bfb\u0bfc\u0bfd\u0bfe"
+          + "\u0bff\u0c00\u0c01\u0c02\u0c03\u0c04\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0d\u0c0e\u0c0f\u0c10\u0c11\u0c12"
+          + "\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26"
+          + "\u0c27\u0c28\u0c29\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c34\u0c35\u0c36\u0c37\u0c38\u0c39\u0c3a"
+          + "\u0c3b\u0c3c\u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44\u0c45\u0c46\u0c47\u0c48\u0c49\u0c4a\u0c4b\u0c4c\u0c4d\u0c4e"
+          + "\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c55\u0c56\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c60\u0c61\u0c62"
+          + "\u0c63\u0c64\u0c65\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76"
+          + "\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c82\u0c83\u0c84\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a"
+          + "\u0c8b\u0c8c\u0c8d\u0c8e\u0c8f\u0c90\u0c91\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e"
+          + "\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0ca9\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2"
+          + "\u0cb3\u0cb4\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cba\u0cbb\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc5\u0cc6"
+          + "\u0cc7\u0cc8\u0cc9\u0cca\u0ccb\u0ccc\u0ccd\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd5\u0cd6\u0cd7\u0cd8\u0cd9\u0cda"
+          + "\u0cdb\u0cdc\u0cdd\u0cde\u0cdf\u0ce0\u0ce1\u0ce2\u0ce3\u0ce4\u0ce5\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee"
+          + "\u0cef\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d02"
+          + "\u0d03\u0d04\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0d\u0d0e\u0d0f\u0d10\u0d11\u0d12\u0d13\u0d14\u0d15\u0d16"
+          + "\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d29\u0d2a"
+          + "\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d3a\u0d3b\u0d3c\u0d3d\u0d3e"
+          + "\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44\u0d45\u0d46\u0d47\u0d48\u0d49\u0d4a\u0d4b\u0d4c\u0d4d\u0d4e\u0d4f\u0d50\u0d51\u0d52"
+          + "\u0d53\u0d54\u0d55\u0d56\u0d57\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d60\u0d61\u0d62\u0d63\u0d64\u0d65\u0d66"
+          + "\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a"
+          + "\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d82\u0d83\u0d84\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e"
+          + "\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d97\u0d98\u0d99\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2"
+          + "\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db2\u0db3\u0db4\u0db5\u0db6"
+          + "\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbc\u0dbd\u0dbe\u0dbf\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0dc7\u0dc8\u0dc9\u0dca"
+          + "\u0dcb\u0dcc\u0dcd\u0dce\u0dcf\u0dd0\u0dd1\u0dd2\u0dd3\u0dd4\u0dd5\u0dd6\u0dd7\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde"
+          + "\u0ddf\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df2"
+          + "\u0df3\u0df4\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06"
+          + "\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a"
+          + "\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e"
+          + "\u0e2f\u0e30\u0e31\u0e32\u0e33\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e3b\u0e3c\u0e3d\u0e3e\u0e3f\u0e40\u0e41\u0e42"
+          + "\u0e43\u0e44\u0e45\u0e46\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0e4f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56"
+          + "\u0e57\u0e58\u0e59\u0e5a\u0e5b\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a"
+          + "\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e"
+          + "\u0e7f\u0e80\u0e81\u0e82\u0e83\u0e84\u0e85\u0e86\u0e87\u0e88\u0e89\u0e8a\u0e8b\u0e8c\u0e8d\u0e8e\u0e8f\u0e90\u0e91\u0e92"
+          + "\u0e93\u0e94\u0e95\u0e96\u0e97\u0e98\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea0\u0ea1\u0ea2\u0ea3\u0ea4\u0ea5\u0ea6"
+          + "\u0ea7\u0ea8\u0ea9\u0eaa\u0eab\u0eac\u0ead\u0eae\u0eaf\u0eb0\u0eb1\u0eb2\u0eb3\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0eba"
+          + "\u0ebb\u0ebc\u0ebd\u0ebe\u0ebf\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0ec5\u0ec6\u0ec7\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0ece"
+          + "\u0ecf\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0eda\u0edb\u0edc\u0edd\u0ede\u0edf\u0ee0\u0ee1\u0ee2"
+          + "\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6"
+          + "\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f00\u0f01\u0f02\u0f03\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a"
+          + "\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f13\u0f14\u0f15\u0f16\u0f17\u0f18\u0f19\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e"
+          + "\u0f1f\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32"
+          + "\u0f33\u0f34\u0f35\u0f36\u0f37\u0f38\u0f39\u0f3a\u0f3b\u0f3c\u0f3d\u0f3e\u0f3f\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46"
+          + "\u0f47\u0f48\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a"
+          + "\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f6b\u0f6c\u0f6d\u0f6e"
+          + "\u0f6f\u0f70\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f7f\u0f80\u0f81\u0f82"
+          + "\u0f83\u0f84\u0f85\u0f86\u0f87\u0f88\u0f89\u0f8a\u0f8b\u0f8c\u0f8d\u0f8e\u0f8f\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96"
+          + "\u0f97\u0f98\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa"
+          + "\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fbd\u0fbe"
+          + "\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc6\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcd\u0fce\u0fcf\u0fd0\u0fd1\u0fd2"
+          + "\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6"
+          + "\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa"
+          + "\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e"
+          + "\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1022"
+          + "\u1023\u1024\u1025\u1026\u1027\u1028\u1029\u102a\u102b\u102c\u102d\u102e\u102f\u1030\u1031\u1032\u1033\u1034\u1035\u1036"
+          + "\u1037\u1038\u1039\u103a\u103b\u103c\u103d\u103e\u103f\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u104a"
+          + "\u104b\u104c\u104d\u104e\u104f\u1050\u1051\u1052\u1053\u1054\u1055\u1056\u1057\u1058\u1059\u105a\u105b\u105c\u105d\u105e"
+          + "\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072"
+          + "\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086";
+}
diff --git a/framework/tests/flowexpression/LambdaParameter.java b/framework/tests/flowexpression/LambdaParameter.java
new file mode 100644
index 0000000..93ca78e
--- /dev/null
+++ b/framework/tests/flowexpression/LambdaParameter.java
@@ -0,0 +1,69 @@
+import java.util.function.Function;
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class LambdaParameter {
+
+  void method(String methodParam) {
+    Function<String, String> func1 =
+        (
+            // :: error: (lambda.param)
+            @FlowExp("methodParam") String lambdaParam) -> {
+          return "";
+        };
+    Function<String, String> func2 =
+        (
+            // :: error: (lambda.param) :: error: (expression.unparsable)
+            @FlowExp("lambdaParam") String lambdaParam) -> {
+          return "";
+        };
+    Function<String, String> func3 =
+        (
+            // :: error: (lambda.param)
+            @FlowExp("#1") String lambdaParam) -> {
+          @FlowExp("lambdaParam") String s = lambdaParam;
+          return "";
+        };
+    Function<@FlowExp("methodParam") String, String> func4 =
+        (
+            @FlowExp("methodParam") String lambdaParam) -> {
+          return "";
+        };
+  }
+
+  void method2(String methodParam, @FlowExp("#1") String methodParam2) {
+    Function<@FlowExp("methodParam") String, String> func1 =
+        (
+            @FlowExp("methodParam") String lambdaParam) -> {
+          @FlowExp("methodParam") String a = methodParam2;
+          @FlowExp("methodParam") String b = lambdaParam;
+          return "";
+        };
+  }
+
+  void method3() {
+    String local = "";
+    Function<String, String> func1 =
+        (
+            // :: error: (lambda.param)
+            @FlowExp("local") String lambdaParam) -> {
+          return "";
+        };
+    Function<@FlowExp("local") String, String> func2 =
+        (
+            @FlowExp("local") String lambdaParam) -> {
+          return "";
+        };
+  }
+
+  void method4() {
+    String local = "";
+    @FlowExp("local") String otherLocal = null;
+    Function<@FlowExp("local") String, String> func1 =
+        (
+            @FlowExp("local") String lambdaParam) -> {
+          @FlowExp("local") String a = otherLocal;
+          @FlowExp("local") String b = lambdaParam;
+          return "";
+        };
+  }
+}
diff --git a/framework/tests/flowexpression/Private.java b/framework/tests/flowexpression/Private.java
new file mode 100644
index 0000000..95c714b
--- /dev/null
+++ b/framework/tests/flowexpression/Private.java
@@ -0,0 +1,14 @@
+package flowexpression;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class Private {
+  private final Map<String, Object> nameToPpt = new LinkedHashMap<>();
+
+  public Collection<@FlowExp("nameToPpt") String> nameStringSet() {
+    throw new RuntimeException();
+  }
+}
diff --git a/framework/tests/flowexpression/SimpleVPA.java b/framework/tests/flowexpression/SimpleVPA.java
new file mode 100644
index 0000000..97d3cf3
--- /dev/null
+++ b/framework/tests/flowexpression/SimpleVPA.java
@@ -0,0 +1,20 @@
+package flowexpression;
+
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class SimpleVPA {
+
+  class MyClass {
+    // :: error: (expression.unparsable)
+    @FlowExp("this.bad") Object field;
+  }
+
+  class Use {
+    Object bad = new Object();
+    MyClass myClass = new MyClass();
+
+    @FlowExp("bad")
+    // :: error: (assignment)
+    Object o = myClass.field;
+  }
+}
diff --git a/framework/tests/flowexpression/Standardize.java b/framework/tests/flowexpression/Standardize.java
new file mode 100644
index 0000000..5b42182
--- /dev/null
+++ b/framework/tests/flowexpression/Standardize.java
@@ -0,0 +1,104 @@
+package flowexpression;
+
+import java.io.FileInputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class Standardize {
+  Object field;
+
+  @SuppressWarnings("assignment")
+  @FlowExp("field") Object fieldField = null;
+
+  void variableDecls(@FlowExp("field") Standardize this, @FlowExp("field") Object paramField) {
+
+    @FlowExp("field") Object localField = fieldField;
+
+    @FlowExp("field") Object o1 = fieldField;
+    @FlowExp("this.field") Object o2 = fieldField;
+
+    @FlowExp("field") Object o3 = paramField;
+    @FlowExp("this.field") Object o4 = paramField;
+
+    @FlowExp("field") Object o5 = localField;
+    @FlowExp("this.field") Object o6 = localField;
+
+    try (@SuppressWarnings("assignment")
+        @FlowExp("field") FileInputStream in = new FileInputStream("")) {
+      in.read();
+      @FlowExp("field") Object o7 = in;
+      @FlowExp("this.field") Object o8 = in;
+    } catch (
+        @SuppressWarnings("exception.parameter")
+        @FlowExp("field") Exception ex) {
+      @FlowExp("field") Object o9 = ex;
+      @FlowExp("this.field") Object o10 = ex;
+    }
+
+    @FlowExp("field") Object o11 = this;
+    @FlowExp("this.field") Object o12 = this;
+  }
+
+  class MyGen<T extends @FlowExp("field") Object> {}
+
+  <X extends @FlowExp("field") Object, Y extends @FlowExp("this.field") Object> void typeVariables(
+      X x) {
+    MyGen<X> o1;
+    MyGen<Y> o2;
+    MyGen<@FlowExp("this.field") String> o3;
+    MyGen<@FlowExp("field") String> o4;
+
+    @FlowExp("field") Object o5 = x;
+    @FlowExp("this.field") Object o6 = x;
+  }
+
+  <A> void typeVariable2(A a, @FlowExp("#1") A a2) {}
+
+  void callTypeVariable2(@FlowExp("field") Object param) {
+    typeVariable2(field, param);
+    typeVariable2(this.field, param);
+  }
+
+  @FlowExp("field") Object testReturn(@FlowExp("this.field") Object param) {
+    @FlowExp("this.field") Object o = testReturn(param);
+    return param;
+  }
+
+  void testCasts() {
+    @SuppressWarnings("cast.unsafe")
+    @FlowExp("this.field") Object o = (@FlowExp("field") Object) new Object();
+    @FlowExp("this.field") Object o2 = (@FlowExp("field") Object) o;
+  }
+
+  void testNewClassTree() {
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @FlowExp("this.field") Object o = new @FlowExp("field") Object();
+  }
+
+  void list(List<@FlowExp("field") Object> list) {
+    Object field = new Object();
+    // "field" is  local variable, but list.get(1) type is @FlowExp("this.field")
+    @FlowExp("field")
+    // :: error: (assignment)
+    Object o1 = list.get(1);
+    @FlowExp("this.field") Object o2 = list.get(1);
+  }
+
+  Object dict = new Object();
+
+  void typvar() {
+    // TODO: Why doesn't the diamond operator work?
+    // Map<@FlowExp("this.dict") String, String> that = new HashMap<>();
+    Map<@FlowExp("this.dict") String, String> that = new HashMap<@FlowExp("dict") String, String>();
+  }
+
+  void newArray() {
+    @FlowExp("this.dict") String[] s = new @FlowExp("dict") String[1];
+  }
+
+  void clasLiteral(@FlowExp("java.lang.String.class") String param) {
+    @FlowExp("String.class") String s = param;
+  }
+}
diff --git a/framework/tests/flowexpression/TestParsing.java b/framework/tests/flowexpression/TestParsing.java
new file mode 100644
index 0000000..1c8ef44
--- /dev/null
+++ b/framework/tests/flowexpression/TestParsing.java
@@ -0,0 +1,11 @@
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class TestParsing {
+  int[] a = {1, 2};
+
+  void test(@FlowExp("a.length") Object a, @FlowExp("this.a.length") Object b) {}
+
+  void test2(@FlowExp("a.clone()") Object a, @FlowExp("this.a.clone()") Object b) {}
+  // :: error: (expression.unparsable)
+  void test3(@FlowExp("a.leng") Object a) {}
+}
diff --git a/framework/tests/flowexpression/ThisStaticContext.java b/framework/tests/flowexpression/ThisStaticContext.java
new file mode 100644
index 0000000..c4201d9
--- /dev/null
+++ b/framework/tests/flowexpression/ThisStaticContext.java
@@ -0,0 +1,39 @@
+import java.util.Map;
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class ThisStaticContext {
+  public static Map<Object, Object> staticField;
+  public Map<Object, Object> instanceField;
+
+  static void staticMethod1(
+      // :: error: (expression.unparsable)
+      @FlowExp("this.staticField") Object p1,
+      @FlowExp("ThisStaticContext.staticField") Object p2,
+      @FlowExp("staticField") Object p3) {
+    p2 = p3;
+  }
+
+  static void staticMethod2(
+      // :: error: (expression.unparsable)
+      @FlowExp("this.instanceField") Object p1,
+      // :: error: (expression.unparsable)
+      @FlowExp("ThisStaticContext.instanceField") Object p2,
+      // :: error: (expression.unparsable)
+      @FlowExp("instanceField") Object p3) {}
+
+  void instanceMethod1(
+      @FlowExp("this.staticField") Object p1,
+      @FlowExp("ThisStaticContext.staticField") Object p2,
+      @FlowExp("staticField") Object p3) {
+    p2 = p3;
+    p2 = p1;
+  }
+
+  void instanceMethod2(
+      @FlowExp("this.instanceField") Object p1,
+      // :: error: (expression.unparsable)
+      @FlowExp("ThisStaticContext.instanceField") Object p2,
+      @FlowExp("instanceField") Object p3) {
+    p1 = p3;
+  }
+}
diff --git a/framework/tests/flowexpression/ThisSuper.java b/framework/tests/flowexpression/ThisSuper.java
new file mode 100644
index 0000000..5c707e6
--- /dev/null
+++ b/framework/tests/flowexpression/ThisSuper.java
@@ -0,0 +1,43 @@
+// Test case for
+// https://github.com/typetools/checker-framework/issues/152
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+// @skip-test
+public class ThisSuper {
+  static class SuperClass {
+    protected final Object field = new Object();
+
+    private @FlowExp("field") Object superField;
+  }
+
+  static class SubClass extends SuperClass {
+    /* Hides SuperClass.field */
+    private final Object field = new Object();
+
+    private @FlowExp("field") Object subField;
+
+    void method() {
+      // super.superField : @FlowExp("super.field")
+      // this.subField : @FlowExp("this.field")
+      // :: error: (assignment)
+      this.subField = super.superField;
+      // :: error: (assignment)
+      super.superField = this.subField;
+
+      @FlowExp("super.field") Object o1 = super.superField;
+      @FlowExp("this.field") Object o2 = this.subField;
+    }
+  }
+
+  class OuterClass {
+    private final Object lock = new Object();
+
+    @FlowExp("this.lock") Object field;
+
+    class InnerClass {
+      private final Object lock = new Object();
+      // :: error: (assignment)
+      @FlowExp("this.lock") Object field2 = field;
+    }
+  }
+}
diff --git a/framework/tests/flowexpression/UnaryOperations.java b/framework/tests/flowexpression/UnaryOperations.java
new file mode 100644
index 0000000..9c36ecd
--- /dev/null
+++ b/framework/tests/flowexpression/UnaryOperations.java
@@ -0,0 +1,11 @@
+package flowexpression;
+
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class UnaryOperations {
+
+  void method(int i, @FlowExp("+#1") String s) {
+    @FlowExp("i") String q = s;
+    @FlowExp("-9223372036854775808L") String s0;
+  }
+}
diff --git a/framework/tests/flowexpression/Unparsable.java b/framework/tests/flowexpression/Unparsable.java
new file mode 100644
index 0000000..cad8849
--- /dev/null
+++ b/framework/tests/flowexpression/Unparsable.java
@@ -0,0 +1,15 @@
+package flowexpression;
+
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class Unparsable {
+  // :: error: (expression.unparsable)
+  @FlowExp("lsdjf") Object o3 = null;
+
+  void method() {
+    // :: error: (expression.unparsable)
+    @FlowExp("new Object()") Object o1 = null;
+    // :: error: (expression.unparsable)
+    @FlowExp("int x = 0") Object o2 = null;
+  }
+}
diff --git a/framework/tests/flowexpression/UnsupportJavaCode.java b/framework/tests/flowexpression/UnsupportJavaCode.java
new file mode 100644
index 0000000..0bb1712
--- /dev/null
+++ b/framework/tests/flowexpression/UnsupportJavaCode.java
@@ -0,0 +1,15 @@
+package flowexpression;
+
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class UnsupportJavaCode {
+
+  void method() {
+
+    // :: error: (expression.unparsable)
+    @FlowExp("new Object()") String s0;
+
+    // :: error: (expression.unparsable)
+    @FlowExp("List<String> list;") String s1;
+  }
+}
diff --git a/framework/tests/flowexpression/UsePrivate.java b/framework/tests/flowexpression/UsePrivate.java
new file mode 100644
index 0000000..aef6f62
--- /dev/null
+++ b/framework/tests/flowexpression/UsePrivate.java
@@ -0,0 +1,12 @@
+package flowexpression;
+
+import java.util.Collection;
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class UsePrivate {
+  void test(Private app_ppts, Private test_ppts) {
+
+    Collection<@FlowExp("app_ppts.nameToPpt") String> app_ppt_names = app_ppts.nameStringSet();
+    Collection<@FlowExp("test_ppts.nameToPpt") String> test_ppt_names = test_ppts.nameStringSet();
+  }
+}
diff --git a/framework/tests/flowexpression/ValueLiterals.java b/framework/tests/flowexpression/ValueLiterals.java
new file mode 100644
index 0000000..5acc3b0
--- /dev/null
+++ b/framework/tests/flowexpression/ValueLiterals.java
@@ -0,0 +1,13 @@
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class ValueLiterals {
+  void test(@FlowExp("0") Object a, @FlowExp("0L") Object b) {}
+
+  void test2(@FlowExp("1000") Object a, @FlowExp("100L") Object b) {}
+
+  void test3(@FlowExp("01000") Object a) {}
+
+  void test4(@FlowExp("0100L") Object b) {}
+
+  void test5(@FlowExp("0100l") Object b) {}
+}
diff --git a/framework/tests/flowexpression/ViewPointAdaptMethods.java b/framework/tests/flowexpression/ViewPointAdaptMethods.java
new file mode 100644
index 0000000..d9b9c18
--- /dev/null
+++ b/framework/tests/flowexpression/ViewPointAdaptMethods.java
@@ -0,0 +1,39 @@
+package flowexpression;
+
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class ViewPointAdaptMethods {
+  Object param1;
+
+  void method1(Object param1, @FlowExp("#1") Object param2) {
+    @FlowExp("param1") Object local = param2;
+    @FlowExp("this.param1")
+    // :: error: (assignment)
+    Object local2 = param2;
+    @FlowExp("#1") Object local3 = param2;
+  }
+
+  Object field;
+
+  void callMethod1(@FlowExp("this.field") Object param, @FlowExp("#1") Object param2) {
+    method1(field, param);
+    // :: error: (argument)
+    method1(field, param2);
+  }
+
+  @FlowExp("#2") Object method2(@FlowExp("#2") Object param1, Object param2, boolean flag) {
+    if (flag) {
+      return param1;
+    } else if (param1 == param2) {
+      @FlowExp("#2")
+      // :: error: (assignment)
+      Object o = new Object();
+      return o;
+    } else {
+      @FlowExp("param2")
+      // :: error: (assignment)
+      Object o = new Object();
+      return o;
+    }
+  }
+}
diff --git a/framework/tests/flowexpression/ViewpointAdaptation.java b/framework/tests/flowexpression/ViewpointAdaptation.java
new file mode 100644
index 0000000..a047a97
--- /dev/null
+++ b/framework/tests/flowexpression/ViewpointAdaptation.java
@@ -0,0 +1,41 @@
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class ViewpointAdaptation {
+
+  class MyClass {
+    protected final Object field = new Object();
+
+    protected @FlowExp("field") Object annotatedField1;
+
+    protected @FlowExp("this.field") Object annotatedField2;
+
+    public @FlowExp("field") Object getAnnotatedField1() {
+      return annotatedField1;
+    }
+  }
+
+  class Use {
+    final MyClass myClass1 = new MyClass();
+    final Object field = new Object();
+
+    @FlowExp("this.myClass1.field") Object o1 = myClass1.annotatedField1;
+
+    @FlowExp("this.myClass1.field") Object o2 = myClass1.annotatedField2;
+
+    @FlowExp("field")
+    // :: error: (assignment)
+    Object o3 = myClass1.annotatedField1;
+
+    @FlowExp("this.field")
+    // :: error: (assignment)
+    Object o4 = myClass1.annotatedField2;
+
+    @FlowExp("field")
+    // :: error: (assignment)
+    Object oM2 = myClass1.getAnnotatedField1();
+
+    @FlowExp("this.field")
+    // :: error: (assignment)
+    Object oM3 = myClass1.getAnnotatedField1();
+  }
+}
diff --git a/framework/tests/flowexpression/ViewpointAdaptation2.java b/framework/tests/flowexpression/ViewpointAdaptation2.java
new file mode 100644
index 0000000..4e56d54
--- /dev/null
+++ b/framework/tests/flowexpression/ViewpointAdaptation2.java
@@ -0,0 +1,48 @@
+import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp;
+
+public class ViewpointAdaptation2 {
+  class LockExample {
+    protected final Object myLock = new Object();
+
+    protected @FlowExp("myLock") Object locked;
+
+    protected @FlowExp("this.myLock") Object locked2;
+
+    public @FlowExp("myLock") Object getLocked() {
+      return locked;
+    }
+  }
+
+  class Use {
+    final LockExample lockExample1 = new LockExample();
+    final Object myLock = new Object();
+
+    @FlowExp("lockExample1.myLock") Object o1 = lockExample1.locked;
+
+    @FlowExp("lockExample1.myLock") Object o2 = lockExample1.locked2;
+
+    @FlowExp("myLock")
+    // :: error: (assignment)
+    Object o3 = lockExample1.locked;
+
+    @FlowExp("this.myLock")
+    // :: error: (assignment)
+    Object o4 = lockExample1.locked2;
+
+    @FlowExp("lockExample1.myLock") Object oM1 = lockExample1.getLocked();
+
+    @FlowExp("myLock")
+    // :: error: (assignment)
+    Object oM2 = lockExample1.getLocked();
+
+    @FlowExp("this.myLock")
+    // :: error: (assignment)
+    Object oM3 = lockExample1.getLocked();
+
+    void uses() {
+      lockExample1.locked = o1;
+      // :: error: (assignment)
+      lockExample1.locked = o3;
+    }
+  }
+}
diff --git a/framework/tests/framework/AnnotatedAnnotation.java b/framework/tests/framework/AnnotatedAnnotation.java
new file mode 100644
index 0000000..d0119c1
--- /dev/null
+++ b/framework/tests/framework/AnnotatedAnnotation.java
@@ -0,0 +1,68 @@
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.testchecker.util.*;
+
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@interface OddInt {
+  @Odd int value();
+}
+
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@interface OddIntArr {
+  @Odd int[] value();
+}
+
+@interface OddRec {
+  OddIntArr[] value();
+}
+
+class Const {
+  @SuppressWarnings("evenodd")
+  public static final @Odd int ok1 = 5;
+
+  @SuppressWarnings("evenodd")
+  public static final @Odd int ok2 = 5;
+
+  public static final int notodd = 4;
+}
+
+class Uses {
+  @OddInt(Const.ok1)
+  Object good1;
+
+  // :: error: (annotation)
+  @OddInt(4)
+  Object bad1;
+
+  // :: error: (annotation)
+  @OddInt(Const.notodd)
+  Object bad2;
+
+  @OddIntArr(Const.ok1)
+  Object good2;
+
+  @OddIntArr({Const.ok1, Const.ok2})
+  Object good3;
+
+  // :: error: (annotation)
+  @OddIntArr(4)
+  Object bada1;
+
+  // :: error: (annotation)
+  @OddIntArr({Const.ok1, 4})
+  Object bada2;
+
+  @OddRec(@OddIntArr({Const.ok1, Const.ok2}))
+  void goodrec1() {}
+
+  @OddRec({@OddIntArr({Const.ok1, Const.ok2}), @OddIntArr({Const.ok1, Const.ok2})})
+  void goodrec2() {}
+
+  // :: error: (annotation)
+  @OddRec(@OddIntArr({Const.ok1, 4}))
+  void badrec1() {}
+
+  // :: error: (annotation)
+  @OddRec({@OddIntArr({Const.ok1, Const.ok2}), @OddIntArr({3, Const.ok2})})
+  void badrec2() {}
+}
diff --git a/framework/tests/framework/AnnotatedGenerics.java b/framework/tests/framework/AnnotatedGenerics.java
new file mode 100644
index 0000000..3395c7d
--- /dev/null
+++ b/framework/tests/framework/AnnotatedGenerics.java
@@ -0,0 +1,135 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class AnnotatedGenerics {
+
+  public static void testNullableTypeVariable() {
+    class Test<T> {
+      @Odd T get() {
+        return null;
+      }
+    }
+    Test<String> l = null;
+    String l1 = l.get();
+    @Odd String l2 = l.get();
+
+    Test<@Odd String> n = null;
+    String n1 = n.get();
+    @Odd String n2 = n.get();
+  }
+
+  // Tests the type of the constructed class is correctly inferred for generics.
+  public void testConstructors() {
+    // Variant without annotated type parameters
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<@Odd String>();
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<@Odd String>();
+
+    // Should error because the RHS isn't annotated as '@Odd'
+    // :: error: (assignment)
+    @Odd MyClass<@Odd String> innerClass2 = new MyClass<@Odd String>();
+    // :: error: (assignment)
+    @Odd NormalClass<@Odd String> normal2 = new NormalClass<@Odd String>();
+
+    // Variant with annotated type parameters
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd MyClass<String> innerClass3 = new @Odd MyClass<String>();
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd NormalClass<String> normal3 = new @Odd NormalClass<String>();
+
+    // Should error because the RHS isn't annotated as '@Odd'
+    // :: error: (assignment)
+    @Odd MyClass<String> innerClass4 = new MyClass<String>();
+    // :: error: (assignment)
+    @Odd NormalClass<String> normal4 = new NormalClass<String>();
+  }
+
+  // Tests the type of the constructed class is correctly inferred when the
+  // diamond operator is used.
+  public void testConstructorsWithTypeParameterInferrence() {
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<>();
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<>();
+
+    // Should error because the RHS isn't annotated as '@Odd'
+    // :: error: (assignment)
+    @Odd MyClass<@Odd String> innerClass2 = new MyClass<>();
+    // :: error: (assignment)
+    @Odd NormalClass<@Odd String> normal2 = new NormalClass<>();
+
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd MyClass<String> innerClass3 = new @Odd MyClass<>();
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd NormalClass<String> normal3 = new @Odd NormalClass<>();
+
+    // Should error because the RHS isn't annotated as '@Odd'
+    // :: error: (assignment)
+    @Odd MyClass<String> innerClass4 = new MyClass<>();
+    // :: error: (assignment)
+    @Odd NormalClass<String> normal4 = new NormalClass<>();
+  }
+
+  // Tests the type of the constructor is appropriately inferred for anonymous classes
+  // N.B. This does not / cannot assert that the RHS is infact a subtype of the LHS.
+  public void testAnonymousConstructors() {
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<@Odd String>() {};
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<@Odd String>() {};
+
+    // Should error because the RHS isn't annotated as '@Odd'
+    // :: error: (assignment)
+    @Odd MyClass<@Odd String> innerClass2 = new MyClass<@Odd String>() {};
+    // :: error: (assignment)
+    @Odd NormalClass<@Odd String> normal2 = new NormalClass<@Odd String>() {};
+
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd MyClass<String> innerClass3 = new @Odd MyClass<String>() {};
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd NormalClass<String> normal3 = new @Odd NormalClass<String>() {};
+
+    // Should error because the RHS isn't annotated as '@Odd'
+    // :: error: (assignment)
+    @Odd MyClass<String> innerClass4 = new MyClass<String>() {};
+    // :: error: (assignment)
+    @Odd NormalClass<String> normal4 = new NormalClass<String>() {};
+  }
+
+  // The following test cases are not included because the Java compiler currently does
+  // not seem to support the diamond operator in conjunction with anonymous classes.
+  //
+  //    public void testAnonymousConstructorsWithTypeParameterInferrence() {
+  //          @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<>() {};
+  //          @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<>() {};
+  //
+  //          // Should error because the RHS isn't annotated as '@Odd'
+  //          @Odd MyClass<@Odd String> innerClass2 = new MyClass<>() {};
+  //          @Odd NormalClass<@Odd String> normal2 = new NormalClass<>() {};
+  //
+  //          @Odd MyClass<String> innerClass3 = new @Odd MyClass<>() {};
+  //          @Odd NormalClass<String> normal3 = new @Odd NormalClass<>() {};
+  //
+  //          // Should error because the RHS isn't annotated as '@Odd'
+  //          @Odd MyClass<String> innerClass4 = new MyClass<>() {};
+  //          @Odd NormalClass<String> normal4 = new NormalClass<>() {};
+  //    }
+
+  static class NormalClass<T> {
+    @Odd T get() {
+      return null;
+    }
+  }
+
+  class MyClass<T> implements java.util.Iterator<@Odd T> {
+    public boolean hasNext() {
+      return true;
+    }
+
+    public @Odd T next() {
+      return null;
+    }
+
+    public void remove() {}
+  }
+}
diff --git a/framework/tests/framework/AnnotatedVoidMethod.java b/framework/tests/framework/AnnotatedVoidMethod.java
new file mode 100644
index 0000000..acaf8c9
--- /dev/null
+++ b/framework/tests/framework/AnnotatedVoidMethod.java
@@ -0,0 +1,8 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class AnnotatedVoidMethod {
+  // :: error: annotation type not applicable to this kind of declaration
+  public @Odd void method() {
+    return;
+  }
+}
diff --git a/framework/tests/framework/AnnotationWithComponents.java b/framework/tests/framework/AnnotationWithComponents.java
new file mode 100644
index 0000000..a5c89e6
--- /dev/null
+++ b/framework/tests/framework/AnnotationWithComponents.java
@@ -0,0 +1,16 @@
+/**
+ * An Annotation can contain components besides the methods declaring the annotation arguments, e.g.
+ * classes and fields.
+ */
+@interface Anno {
+  class Inner {}
+
+  int con = 5;
+
+  int value();
+}
+
+class Use {
+  @Anno(0)
+  Object o;
+}
diff --git a/framework/tests/framework/AnonymousClasses.java b/framework/tests/framework/AnonymousClasses.java
new file mode 100644
index 0000000..9b1ec95
--- /dev/null
+++ b/framework/tests/framework/AnonymousClasses.java
@@ -0,0 +1,20 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class AnonymousClasses {
+
+  void test() {
+    new Object() {
+      // TODO: the right hand side is
+      // @Unqualified @Unqualified Object
+      // We should make sure that the qualifier is only present once.
+
+      // :: error: (assignment)
+      @Odd Object o = this; // error
+    };
+
+    // :: warning: (cast.unsafe.constructor.invocation)
+    new @Odd Object() {
+      @Odd Object o = this;
+    };
+  }
+}
diff --git a/framework/tests/framework/ArraySubtyping.java b/framework/tests/framework/ArraySubtyping.java
new file mode 100644
index 0000000..ce9e9c7
--- /dev/null
+++ b/framework/tests/framework/ArraySubtyping.java
@@ -0,0 +1,30 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+// @skip-test
+public class ArraySubtyping {
+  Object[] obj1 = new Object[1];
+  @Odd Object[] obj2 = new @Odd Object[1];
+
+  String[] str1 = new String[1];
+  @Odd String[] str2 = new @Odd String[1];
+
+  void m() {
+    // :: error: (assignment)
+    obj1 = obj2;
+    // :: error: (assignment)
+    obj2 = obj1;
+
+    // :: error: (assignment)
+    str1 = str2;
+    // :: error: (assignment)
+    str2 = str1;
+
+    obj1 = str1;
+    obj2 = str2;
+
+    // :: error: (assignment)
+    obj1 = str2;
+    // :: error: (assignment)
+    obj2 = str1;
+  }
+}
diff --git a/framework/tests/framework/Arrays.java b/framework/tests/framework/Arrays.java
new file mode 100644
index 0000000..8f70b93
--- /dev/null
+++ b/framework/tests/framework/Arrays.java
@@ -0,0 +1,126 @@
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class Arrays {
+  Object[] @Odd [] objB1 = new Object[] @Odd [] {};
+  Object[][] @Odd [] objB1a = new Object[][] @Odd [] {};
+  Object @Odd [][][] objB1b = new Object @Odd [][][] {};
+  @Odd Object[][][] objB1c = new @Odd Object[][][] {};
+
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  @interface A {}
+
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  @interface B {}
+
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  @interface C {}
+
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  @interface D {}
+
+  class Cell<T> {}
+
+  // (This part is actually for the parser, not the framework; it should
+  // be moved to the JSR 308 compiler test suite eventually.)
+  void test() {
+
+    Object z = new @A String[] {};
+
+    // 308 only:
+    Cell<@D Object @C [] @B [] @A []> o1;
+
+    // w/new:
+    Object o2a = new @D Object @C [] @B [] @A [] {};
+    Object o2b = new @D Object @C [1] @B [2] @A [3];
+
+    // w/175:
+    @D Object @C [] @B [] @A [] o3;
+  }
+
+  void moreTest() {
+    // Assignments:
+
+    String[] s = null;
+    String[] t = null;
+
+    s[0] = null;
+    t[0] = null;
+
+    (new String[1])[0] = null;
+    (new String[1])[0] = null;
+
+    (new String[] {"foo"})[0] = null;
+    (new String[] {"foo"})[0] = null;
+  }
+
+  void test2() {
+
+    Object[][] objA1 = new Object[][] {};
+    Object[][] objA2 = new Object[1][2];
+    Object[][] objA3 = new Object[1][];
+
+    Object[] @Odd [] objB1 = new Object[] @Odd [] {};
+    Object[] @Odd [] objB2 = new Object[1] @Odd [2];
+    Object[] @Odd [] objB3 = new Object[1] @Odd [];
+
+    Object @Odd [][] objC1 = new Object @Odd [][] {};
+    Object @Odd [][] objC2 = new Object @Odd [1][2];
+    Object @Odd [][] objC3 = new Object @Odd [1][];
+
+    @Odd Object[][] objD1 = new @Odd Object[][] {};
+    @Odd Object[][] objD2 = new @Odd Object[1][2];
+    @Odd Object[][] objD3 = new @Odd Object[1][];
+
+    Object @Odd [] @Odd [] objE1 = new Object @Odd [] @Odd [] {};
+    Object @Odd [] @Odd [] objE2 = new Object @Odd [1] @Odd [2];
+    Object @Odd [] @Odd [] objE3 = new Object @Odd [1] @Odd [];
+
+    @Odd Object[] @Odd [] objF1 = new @Odd Object[] @Odd [] {};
+    @Odd Object[] @Odd [] objF2 = new @Odd Object[1] @Odd [2];
+    @Odd Object[] @Odd [] objF3 = new @Odd Object[1] @Odd [];
+
+    @Odd Object @Odd [][] objG1 = new @Odd Object @Odd [][] {};
+    @Odd Object @Odd [][] objG2 = new @Odd Object @Odd [1][2];
+    @Odd Object @Odd [][] objG3 = new @Odd Object @Odd [1][];
+
+    @Odd Object @Odd [] @Odd [] objH1 = new @Odd Object @Odd [] @Odd [] {};
+    @Odd Object @Odd [] @Odd [] objH2 = new @Odd Object @Odd [1] @Odd [2];
+    @Odd Object @Odd [] @Odd [] objH3 = new @Odd Object @Odd [1] @Odd [];
+  }
+
+  void test3() {
+    @Odd Object o1 = new @Odd Object @Odd [] @Odd [] {};
+    // :: error: (assignment)
+    @Odd Object o2 = new @Odd Object[] @Odd [] {}; // ERROR
+
+    @Odd Object @Odd [] o3 = (new @Odd Object[] @Odd [] {})[0];
+    // :: error: (assignment)
+    @Odd Object @Odd [] o4 = (new Object @Odd [][] {})[0]; // ERROR
+    // :: error: (assignment)
+    @Odd Object @Odd [] o5 = (new @Odd Object[][] {})[0]; // ERROR
+
+    Object @Odd [] o6 = (new Object[] @Odd [] {})[0];
+    @Odd Object[] o7 = (new @Odd Object[][] {})[0];
+
+    @Odd Object o8 = (new @Odd Object[][] {})[0][0];
+  }
+
+  void test4() {
+    @Odd Object @Odd [] @Odd [] o1 = new @Odd Object @Odd [] @Odd [] {};
+    @Odd Object @Odd [] @Odd [] @Odd [] o2 = new @Odd Object @Odd [1] @Odd [2] @Odd [3];
+    @Odd Object @Odd [] @Odd [] o3 = new @Odd Object @Odd [1] @Odd [2] @Odd [];
+    @Odd Object @Odd [] @Odd [] o4 = new @Odd Object @Odd [1] @Odd [] @Odd [];
+  }
+
+  void testInitializers() {
+    //      @Odd String [] ara1 = { null, null };
+    @Odd String[] ara2 = new @Odd String[] {null, null};
+
+    //         // xx:: error: (assignment)
+    //        @Odd String [] arb1 = { null, "m" };
+    // :: error: (array.initializer)
+    @Odd String[] arb2 = new @Odd String[] {null, "m"};
+  }
+}
diff --git a/framework/tests/framework/Assignments.java b/framework/tests/framework/Assignments.java
new file mode 100644
index 0000000..098d558
--- /dev/null
+++ b/framework/tests/framework/Assignments.java
@@ -0,0 +1,66 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class Assignments {
+
+  public void testAssignment() {
+    @Odd String s;
+    @Odd String t;
+    s = null;
+    t = s;
+
+    String z = "";
+    z = s;
+  }
+
+  public void testCompound() {
+    // :: warning: (cast.unsafe)
+    @Odd String s = (@Odd String) "foo";
+    // :: warning: (cast.unsafe)
+    @Odd String t = (@Odd String) "bar";
+    s += t;
+
+    String z = "";
+    z += s;
+  }
+
+  public void testEnhancedForLoop() {
+    // TODO
+  }
+
+  public void testMethod() {
+    // Nothing to do here.
+  }
+
+  public void testMethodInvocation() {
+    // TODO anonymous constructor
+    // TODO isEnumSuper
+    // @see Varargs
+
+    @Odd String s = null;
+    methodA(s);
+    methodB(s);
+  }
+
+  public @Odd String testReturn() {
+    @Odd String s = null;
+    return s;
+  }
+
+  public void testReturnVoid() {
+    return;
+  }
+
+  public void testVariable() {
+    @Odd String s = null;
+    // :: warning: (cast.unsafe)
+    @Odd String t = (@Odd String) "foo";
+    @Odd String u = s;
+    String v = s;
+  }
+
+  /* ------------------------------------------------------------ */
+
+  public void methodA(@Odd String s) {}
+
+  public void methodB(String s) {}
+}
diff --git a/framework/tests/framework/AssignmentsGeneric.java b/framework/tests/framework/AssignmentsGeneric.java
new file mode 100644
index 0000000..df4f7b6
--- /dev/null
+++ b/framework/tests/framework/AssignmentsGeneric.java
@@ -0,0 +1,54 @@
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class AssignmentsGeneric {
+
+  private static final Map<
+          @Odd List<@Odd String>,
+          @Odd Map<@Odd Set<@Odd List<@Odd String>>, @Odd List<@Odd Set<@Odd String>>>>
+      complex;
+
+  static {
+    complex =
+        new HashMap<
+            @Odd List<@Odd String>,
+            @Odd Map<@Odd Set<@Odd List<@Odd String>>, @Odd List<@Odd Set<@Odd String>>>>();
+  }
+
+  public void testAssignment() {
+    // :: warning: (cast.unsafe)
+    @Odd String s = (@Odd String) "";
+
+    List<@Odd String> lst = new LinkedList<>();
+    lst = new ArrayList<@Odd String>();
+
+    methodA(lst);
+  }
+
+  public void testEnhancedForLoop() {
+    List<@Odd String> lst = new LinkedList<>();
+    for (@Odd String str : lst) {
+      System.out.println(str);
+    }
+  }
+
+  public void testGenericInvocation() {
+    List<@Odd String> lst = new LinkedList<>();
+    // :: warning: (cast.unsafe)
+    @Odd String s = (@Odd String) "";
+    lst.add(s);
+  }
+
+  public List<@Odd String> testReturn() {
+    return new LinkedList<@Odd String>();
+  }
+
+  /* ------------------------------------------------------------ */
+
+  public void methodA(List<@Odd String> lst) {}
+}
diff --git a/framework/tests/framework/BridgeMethods.java b/framework/tests/framework/BridgeMethods.java
new file mode 100644
index 0000000..32f6330
--- /dev/null
+++ b/framework/tests/framework/BridgeMethods.java
@@ -0,0 +1,29 @@
+import org.checkerframework.framework.testchecker.util.Even;
+import org.checkerframework.framework.testchecker.util.Odd;
+
+abstract class C<T extends @Odd Object> {
+  abstract T id(T x);
+}
+
+class D extends C<@Odd String> {
+  @Odd String id(@Odd String x) {
+    return x;
+  }
+}
+
+class Usage {
+  void use() {
+    C c = new D(); // C<@Odd String>();
+    // Oddness is OK, will fail with ClassCastException
+    // :: warning: [unchecked] unchecked call to id(T) as a member of the raw type C
+    // :: warning: (cast.unsafe.constructor.invocation)
+    c.id(new @Odd Object());
+
+    // Oddness is wrong! Would also fail with ClassCastException.
+    // TODO: false negative. See #635.
+    //// :: error: (argument)
+    // :: warning: [unchecked] unchecked call to id(T) as a member of the raw type C
+    // :: warning: (cast.unsafe.constructor.invocation)
+    c.id(new @Even Object());
+  }
+}
diff --git a/framework/tests/framework/ClassAnnotations.java b/framework/tests/framework/ClassAnnotations.java
new file mode 100644
index 0000000..abad090
--- /dev/null
+++ b/framework/tests/framework/ClassAnnotations.java
@@ -0,0 +1,11 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+// ::warning: (inconsistent.constructor.type) :: error: (super.invocation)
+public @Odd class ClassAnnotations {
+
+  ClassAnnotations c;
+
+  public void test() {
+    @Odd ClassAnnotations d = c;
+  }
+}
diff --git a/framework/tests/framework/Compound.java b/framework/tests/framework/Compound.java
new file mode 100644
index 0000000..7559f9f
--- /dev/null
+++ b/framework/tests/framework/Compound.java
@@ -0,0 +1,18 @@
+/*
+ * Test case for Issue 138:
+ * https://github.com/typetools/checker-framework/issues/138
+ *
+ * Assignment and compound assignment should be treated equally for
+ * method type argument inference.
+ */
+public class Compound {
+  public static void one() {
+    long total = 0;
+    total = two();
+    total += two();
+  }
+
+  private static <N> long two() {
+    return 0;
+  }
+}
diff --git a/framework/tests/framework/Constructors.java b/framework/tests/framework/Constructors.java
new file mode 100644
index 0000000..afafaef
--- /dev/null
+++ b/framework/tests/framework/Constructors.java
@@ -0,0 +1,50 @@
+import org.checkerframework.framework.testchecker.util.Odd;
+
+public class Constructors {
+  public Constructors(Constructors con) {}
+
+  public void testConstructors() {
+    Constructors c = null;
+    // :: warning: (cast.unsafe.constructor.invocation)
+    new @Odd Constructors(c);
+  }
+
+  // Test anonymous constructors
+  public Constructors(@Odd String s, int i) {}
+
+  public void testStaticAnonymousConstructor() {
+    String notOdd = "m";
+
+    // :: error: (argument)
+    new Constructors(notOdd, 0); // error
+    // :: error: (argument)
+    new Constructors(notOdd, 0) {}; // error
+  }
+
+  private class MyConstructors extends Constructors {
+    public MyConstructors(@Odd String s) {
+      super(s, 0);
+    }
+  }
+
+  public static void testAnonymousConstructor() {
+    Constructors m = new Constructors(null) {};
+    String notOdd = "m";
+    // :: error: (argument)
+    m.new MyConstructors(notOdd); // error
+    // :: error: (argument)
+    m.new MyConstructors(notOdd) {}; // error
+  }
+
+  // Tests that should pass
+  public void testPassingTests() {
+    @Odd String odd = null;
+
+    new Constructors(odd, 0);
+    new Constructors(odd, 0) {};
+
+    Constructors m = new Constructors(null) {};
+    m.new MyConstructors(odd);
+    m.new MyConstructors(odd) {};
+  }
+}
diff --git a/framework/tests/framework/DeepOverride.java b/framework/tests/framework/DeepOverride.java
new file mode 100644
index 0000000..6efcc3d
--- /dev/null
+++ b/framework/tests/framework/DeepOverride.java
@@ -0,0 +1,19 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class DeepOverride {
+  public static class A {
+    public @Odd String method() {
+      return null;
+    }
+  }
+
+  public static class B extends A {}
+
+  public static class C extends B {
+    @Override
+    // :: error: (override.return)
+    public String method() {
+      return "";
+    }
+  }
+}
diff --git a/framework/tests/framework/DeepOverrideAbstract.java b/framework/tests/framework/DeepOverrideAbstract.java
new file mode 100644
index 0000000..932d2ce
--- /dev/null
+++ b/framework/tests/framework/DeepOverrideAbstract.java
@@ -0,0 +1,24 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class DeepOverrideAbstract {
+
+  public static interface I {
+    @Odd String interfaceMethod();
+  }
+
+  public abstract static class A {
+    public abstract @Odd String abstractMethod();
+  }
+
+  public abstract static class B extends A implements I {}
+
+  public static class C extends B {
+    public @Odd String interfaceMethod() {
+      return null;
+    }
+    // :: error: (override.return)
+    public String abstractMethod() {
+      return "";
+    }
+  }
+}
diff --git a/framework/tests/framework/DeepOverrideBug.java b/framework/tests/framework/DeepOverrideBug.java
new file mode 100644
index 0000000..a41ed63
--- /dev/null
+++ b/framework/tests/framework/DeepOverrideBug.java
@@ -0,0 +1,30 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+// TODO: the output have a "missing return statement"?
+public class DeepOverrideBug {
+
+  public static interface I {
+    @Odd String interfaceMethod();
+
+    String abstractMethod();
+  }
+
+  public abstract static class A {
+    public abstract @Odd String abstractMethod();
+
+    public abstract String interfaceMethod();
+  }
+
+  public abstract static class B extends A implements I {}
+
+  public static class C extends B {
+    // :: error: (override.return)
+    public String interfaceMethod() { // should emit error
+      return null;
+    }
+    // :: error: (override.return)
+    public String abstractMethod() { // should emit error
+      return null;
+    }
+  }
+}
diff --git a/framework/tests/framework/DeepOverrideInterface.java b/framework/tests/framework/DeepOverrideInterface.java
new file mode 100644
index 0000000..d6d1f09
--- /dev/null
+++ b/framework/tests/framework/DeepOverrideInterface.java
@@ -0,0 +1,25 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class DeepOverrideInterface {
+
+  public static interface I {
+    @Odd String interfaceMethod();
+  }
+
+  public abstract static class A {
+    public abstract @Odd String abstractMethod();
+  }
+
+  public abstract static class B extends A implements I {}
+
+  public static class C extends B {
+    // :: error: (override.return)
+    public String interfaceMethod() {
+      return "";
+    }
+
+    public @Odd String abstractMethod() {
+      return null;
+    }
+  }
+}
diff --git a/framework/tests/framework/ExtendsDefault.java b/framework/tests/framework/ExtendsDefault.java
new file mode 100644
index 0000000..34bc29d
--- /dev/null
+++ b/framework/tests/framework/ExtendsDefault.java
@@ -0,0 +1,24 @@
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class ExtendsDefault {
+
+  @DefaultQualifier(
+      value = Odd.class,
+      locations = {TypeUseLocation.UPPER_BOUND})
+  class MyOddDefault<T> {}
+
+  class MyNonOddDefault<T> {}
+
+  void testNonOdd() {
+    // :: error: (type.argument)
+    MyOddDefault<String> s1;
+    MyNonOddDefault<String> s2;
+  }
+
+  void testOdd() {
+    MyOddDefault<@Odd String> s1;
+    MyNonOddDefault<@Odd String> s2;
+  }
+}
diff --git a/framework/tests/framework/GenericAlias.java b/framework/tests/framework/GenericAlias.java
new file mode 100644
index 0000000..3c9538c
--- /dev/null
+++ b/framework/tests/framework/GenericAlias.java
@@ -0,0 +1,24 @@
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class GenericAlias {
+
+  public static class SuperSetOne extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {}
+
+  public void test() {
+    Set<@Odd Map<@Odd List<@Odd String>, @Odd String>> s = new SuperSetOne();
+    @Odd Map<@Odd List<@Odd String>, @Odd String> mapA =
+        // :: warning: (cast.unsafe.constructor.invocation)
+        new @Odd HashMap<@Odd List<@Odd String>, @Odd String>();
+    s.add(mapA);
+  }
+
+  public void regularGenerics() {
+    Set<?> set = new HashSet<@Odd String>();
+    Set<? extends Object> set2 = new HashSet<@Odd String>();
+  }
+}
diff --git a/framework/tests/framework/GenericAliasInvalid.java b/framework/tests/framework/GenericAliasInvalid.java
new file mode 100644
index 0000000..00ebe14
--- /dev/null
+++ b/framework/tests/framework/GenericAliasInvalid.java
@@ -0,0 +1,15 @@
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class GenericAliasInvalid {
+
+  public static class SuperSetOne extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {}
+
+  public void test() {
+    // :: error: (assignment)
+    Set<Map<@Odd List<@Odd String>, @Odd String>> t = new SuperSetOne();
+  }
+}
diff --git a/framework/tests/framework/GenericAliasInvalidCall.java b/framework/tests/framework/GenericAliasInvalidCall.java
new file mode 100644
index 0000000..f393a1b
--- /dev/null
+++ b/framework/tests/framework/GenericAliasInvalidCall.java
@@ -0,0 +1,20 @@
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class GenericAliasInvalidCall {
+
+  public static class SuperSetOne extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {}
+
+  public void test() {
+    Set<@Odd Map<@Odd List<@Odd String>, @Odd String>> s = new SuperSetOne();
+    @Odd Map<List<@Odd String>, @Odd String> mapA =
+        // :: warning: (cast.unsafe.constructor.invocation)
+        new @Odd HashMap<List<@Odd String>, @Odd String>();
+    // :: error: (argument)
+    s.add(mapA);
+  }
+}
diff --git a/framework/tests/framework/GenericEnum.java b/framework/tests/framework/GenericEnum.java
new file mode 100644
index 0000000..fd06e83
--- /dev/null
+++ b/framework/tests/framework/GenericEnum.java
@@ -0,0 +1,8 @@
+// Test case for issue #279: https://github.com/typetools/checker-framework/issues/279
+
+public class GenericEnum<T extends String> {
+
+  void test() {
+    T.format("");
+  }
+}
diff --git a/framework/tests/framework/GenericTest1.java b/framework/tests/framework/GenericTest1.java
new file mode 100644
index 0000000..91ac1a5
--- /dev/null
+++ b/framework/tests/framework/GenericTest1.java
@@ -0,0 +1,19 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+// Test case for Issue 131:
+// https://github.com/typetools/checker-framework/issues/131
+public class GenericTest1 {
+  public interface Foo<T> {}
+
+  public interface Bar<T, C, E extends Foo<C>> extends Foo<T> {}
+
+  public <T> void test(Foo<T> foo) {
+    Bar<?, ?, ?> bar =
+        foo instanceof Bar<?, ?, ?>
+            // TODO flow: support instanceof / cast flow.
+            // Warning only with -AcheckCastElementType.
+            // TODO:: warning: (cast.unsafe)
+            ? (Bar<?, ?, ?>) foo
+            : null;
+  }
+}
diff --git a/framework/tests/framework/GenericTest10.java b/framework/tests/framework/GenericTest10.java
new file mode 100644
index 0000000..c602ff2
--- /dev/null
+++ b/framework/tests/framework/GenericTest10.java
@@ -0,0 +1,25 @@
+// Test case for (a part of) Issue 142:
+// https://github.com/typetools/checker-framework/issues/142
+public class GenericTest10 {
+  abstract static class Bijection<A, B> {
+    abstract B apply(A a);
+
+    abstract A invert(B b);
+
+    Bijection<B, A> inverse() {
+      return new Bijection<B, A>() {
+        A apply(B b) {
+          return Bijection.this.invert(b);
+        }
+
+        B invert(A a) {
+          return Bijection.this.apply(a);
+        }
+
+        Bijection<A, B> inverse() {
+          return Bijection.this;
+        }
+      };
+    }
+  }
+}
diff --git a/framework/tests/framework/GenericTest11.java b/framework/tests/framework/GenericTest11.java
new file mode 100644
index 0000000..ac4341e
--- /dev/null
+++ b/framework/tests/framework/GenericTest11.java
@@ -0,0 +1,21 @@
+package com.example;
+
+public class GenericTest11 {
+  public void m(BeanManager beanManager) {
+    Bean<?> bean = beanManager.getBeans(GenericTest11.class).iterator().next();
+    CreationalContext<?> context = beanManager.createCreationalContext(bean);
+  }
+
+  static interface BeanManager {
+    java.util.Set<Bean<?>> getBeans(
+        java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1);
+
+    <T> CreationalContext<T> createCreationalContext(Contextual<T> arg0);
+  }
+
+  static interface Contextual<T> {}
+
+  static interface Bean<T> extends Contextual<T> {}
+
+  static interface CreationalContext<T> {}
+}
diff --git a/framework/tests/framework/GenericTest12.java b/framework/tests/framework/GenericTest12.java
new file mode 100644
index 0000000..1822abc
--- /dev/null
+++ b/framework/tests/framework/GenericTest12.java
@@ -0,0 +1,10 @@
+import java.lang.annotation.Annotation;
+
+public class GenericTest12 {
+  @interface Anno {}
+
+  void foo(Class<? extends Annotation> qual) {
+    Annotation a = qual.getAnnotation(Anno.class);
+    Anno an = qual.getAnnotation(Anno.class);
+  }
+}
diff --git a/framework/tests/framework/GenericTest2.java b/framework/tests/framework/GenericTest2.java
new file mode 100644
index 0000000..41cf2ff
--- /dev/null
+++ b/framework/tests/framework/GenericTest2.java
@@ -0,0 +1,14 @@
+// Test case for Issue 132:
+// https://github.com/typetools/checker-framework/issues/132
+// Method type argument inference test case.
+public class GenericTest2 {
+  public interface Data<S> {}
+
+  public interface DataUtils {
+    <T> Data<T> makeData(T value);
+  }
+
+  public <U> void test(U value, DataUtils utils) {
+    Data<? extends U> data = utils.makeData(value);
+  }
+}
diff --git a/framework/tests/framework/GenericTest3.java b/framework/tests/framework/GenericTest3.java
new file mode 100644
index 0000000..94f0aef
--- /dev/null
+++ b/framework/tests/framework/GenericTest3.java
@@ -0,0 +1,14 @@
+// Test case for Issue 133:
+// https://github.com/typetools/checker-framework/issues/133
+// Upper bound of wildcard depends on declared bound of type variable.
+public class GenericTest3 {
+  interface Foo {}
+
+  interface Bar<T extends Foo> {
+    T get();
+  }
+
+  public void test(Bar<?> bar) {
+    Foo foo = bar.get();
+  }
+}
diff --git a/framework/tests/framework/GenericTest4.java b/framework/tests/framework/GenericTest4.java
new file mode 100644
index 0000000..0993128
--- /dev/null
+++ b/framework/tests/framework/GenericTest4.java
@@ -0,0 +1,59 @@
+import java.util.Map;
+import org.checkerframework.framework.testchecker.util.*;
+
+// Test case for Issue 134:
+// https://github.com/typetools/checker-framework/issues/134
+// Handling of generics from different enclosing classes.
+
+public class GenericTest4 {
+  public interface Foo {}
+
+  class Outer<O> {
+    O getOuter() {
+      return null;
+    }
+
+    class Inner<I> {
+      O getInner() {
+        return null;
+      }
+
+      I setter1(O p) {
+        return null;
+      }
+
+      O setter2(I p) {
+        return null;
+      }
+
+      Map<O, I> wow(Map<O, I> p) {
+        return null;
+      }
+    }
+  }
+
+  class OuterImpl extends Outer<Foo> {
+    void test() {
+      Foo foo = getOuter();
+    }
+
+    class InnerImpl extends Inner<@Odd String> {
+      void test() {
+        Foo foo = getInner();
+        String s = setter1(foo);
+        foo = setter2(s);
+      }
+
+      void testWow(Map<Foo, @Odd String> p) {
+        p = wow(p);
+      }
+
+      void testWow2(Map<Foo, String> p) {
+        // :: error: (assignment) :: error: (argument)
+        p = wow(p);
+      }
+    }
+  }
+
+  // Add uses from outside of both classes.
+}
diff --git a/framework/tests/framework/GenericTest5.java b/framework/tests/framework/GenericTest5.java
new file mode 100644
index 0000000..4acd522
--- /dev/null
+++ b/framework/tests/framework/GenericTest5.java
@@ -0,0 +1,26 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+// Test case for Issue 135:
+// https://github.com/typetools/checker-framework/issues/135
+// Method type argument substitution needs to consider arrays correctly.
+public class GenericTest5 {
+  interface Foo {
+    <T> T[] id(T[] a);
+  }
+
+  public <U> void test(Foo foo, U[] a) {
+    U[] array = foo.id(a);
+  }
+
+  public <T> void test1(Foo foo, T[] a) {
+    T[] array = foo.id(a);
+  }
+
+  public <S extends @Odd Object> void test2(Foo foo, S[] a) {
+    S[] array = foo.id(a);
+  }
+
+  public <S extends @Odd Object, T extends S> void test3(Foo foo, T[] a) {
+    T[] array = foo.id(a);
+  }
+}
diff --git a/framework/tests/framework/GenericTest6.java b/framework/tests/framework/GenericTest6.java
new file mode 100644
index 0000000..939778d
--- /dev/null
+++ b/framework/tests/framework/GenericTest6.java
@@ -0,0 +1,34 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+// Test case for Issue 136:
+// https://github.com/typetools/checker-framework/issues/136
+public class GenericTest6 {
+  interface Foo<T extends Foo<?>> {}
+
+  class Strange implements Foo<Strange> {}
+
+  void test(Foo<Strange> p) {}
+
+  void call(Foo<Strange> p) {
+    test(p);
+  }
+
+  void test2(Foo<Foo<?>> p) {}
+
+  void call2(Foo<Foo<?>> p) {
+    test2(p);
+  }
+
+  void test3(Foo<Foo<? extends @Odd Foo<?>>> p) {}
+
+  void call3(Foo<Foo<? extends Foo<?>>> p) {
+    // :: error: (argument)
+    test3(p);
+  }
+
+  void testRaw(Foo p) {}
+
+  void callRaw(Foo<Foo<?>> p) {
+    testRaw(p);
+  }
+}
diff --git a/framework/tests/framework/GenericTest7.java b/framework/tests/framework/GenericTest7.java
new file mode 100644
index 0000000..6715eb6
--- /dev/null
+++ b/framework/tests/framework/GenericTest7.java
@@ -0,0 +1,54 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+/*
+ * See Issue 137:
+ * https://github.com/typetools/checker-framework/issues/137
+ */
+public class GenericTest7 {
+  interface A {}
+
+  interface B<T> {}
+
+  interface C<U> {}
+
+  public <I extends B<A> & C<A>> void one(I i) {
+    B<A> i1 = i;
+    C<A> i2 = i;
+  }
+
+  public <I extends B<A> & C<A>> void oneA(I i) {
+    // :: error: (assignment)
+    @Odd B<A> i1 = i;
+    // :: error: (assignment)
+    @Odd C<A> i2 = i;
+  }
+
+  public <I extends @Odd B<A> & @Odd C<A>> void oneB(I i) {
+    @Odd B<A> i1 = i;
+    @Odd C<A> i2 = i;
+  }
+
+  public <I extends B<? extends A> & C<? extends A>> void two(I i) {
+    B<? extends A> i1 = i;
+    C<? extends A> i2 = i;
+  }
+
+  public <I extends B<? extends A> & C<? extends A>> void twoA(I i) {
+    // :: error: (assignment)
+    @Odd B<? extends A> i1 = i;
+    // :: error: (assignment)
+    @Odd C<? extends A> i2 = i;
+  }
+
+  public <I extends @Odd B<? extends A> & @Odd C<? extends A>> void twoB(I i) {
+    @Odd B<? extends A> i1 = i;
+    @Odd C<? extends A> i2 = i;
+  }
+
+  public <I extends B<? extends @Odd A> & C<? extends @Odd A>> void twoC(I i) {
+    B<? extends A> i1 = i;
+    C<? extends A> i2 = i;
+    B<? extends @Odd A> i3 = i;
+    C<? extends @Odd A> i4 = i;
+  }
+}
diff --git a/framework/tests/framework/GenericTest8.java b/framework/tests/framework/GenericTest8.java
new file mode 100644
index 0000000..059fcf9
--- /dev/null
+++ b/framework/tests/framework/GenericTest8.java
@@ -0,0 +1,17 @@
+// Test case for Issue 139:
+// https://github.com/typetools/checker-framework/issues/139
+abstract class GenericTest8 {
+  interface A<S> {}
+
+  void foo1(A<?> a) {
+    foo2(a);
+  }
+
+  abstract <T> A<? extends T> foo2(A<? extends T> a);
+
+  void bar1(A<? extends A<?>> a) {
+    bar2(a);
+  }
+
+  abstract <U> A<A<? extends U>> bar2(A<? extends A<? extends U>> a);
+}
diff --git a/framework/tests/framework/GenericTest9.java b/framework/tests/framework/GenericTest9.java
new file mode 100644
index 0000000..3a0cf49
--- /dev/null
+++ b/framework/tests/framework/GenericTest9.java
@@ -0,0 +1,72 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+// Test case for Issue 140:
+// https://github.com/typetools/checker-framework/issues/140
+public class GenericTest9 {
+  // Make sure that substitutions on classes work correctly
+
+  class C<X, Y extends X> {}
+
+  C<Object, MyEntry<String>> f = new C<>();
+
+  interface MyEntry<S> {}
+
+  <V> void testclass() {
+    // :: error: (type.argument)
+    C<@Odd Object, MyEntry<V>> c1 = new C<>();
+    C<@Odd Object, @Odd MyEntry<V>> c2 = new C<>();
+  }
+
+  // Make sure that substitutions on method type variables work correctly
+
+  interface Ordering1<T> {
+    <U extends T> U sort(U iterable);
+  }
+
+  <V> void test(Ordering1<MyEntry<?>> o, MyEntry<V> e) {
+    MyEntry<V> e1 = o.sort(e);
+    MyEntry<V> e2 = o.<MyEntry<V>>sort(e);
+  }
+
+  interface Ordering2<T extends @Odd Object> {
+    <U extends T> U sort(U iterable);
+  }
+
+  <V> void test(Ordering2<@Odd MyEntry<?>> ord, MyEntry<V> e, @Odd MyEntry<V> o) {
+    // :: error: (type.argument)
+    MyEntry<V> e1 = ord.sort(e);
+    // :: error: (type.argument)
+    MyEntry<V> e2 = ord.<MyEntry<V>>sort(e);
+    MyEntry<V> e3 = ord.sort(o);
+    MyEntry<V> e4 = ord.<@Odd MyEntry<V>>sort(o);
+  }
+
+  interface Ordering3<@Odd T> {
+    <U extends T> U sort(U iterable);
+  }
+
+  <V> void test(Ordering3<@Odd MyEntry<?>> o, @Odd MyEntry<V> e) {
+    MyEntry<V> e1 = o.sort(e);
+    MyEntry<V> e2 = o.<@Odd MyEntry<V>>sort(e);
+    // :: error: (type.argument) :: error: (argument)
+    MyEntry<V> e3 = o.<@Even MyEntry<V>>sort(e);
+  }
+
+  interface Comparator4<T> {}
+
+  interface Ordering4<T> extends Comparator4<T> {
+    <S extends T> Ordering4<S> reverse();
+  }
+
+  <T> Ordering4<T> from4(Comparator4<T> comparator) {
+    return null;
+  }
+
+  <E> Comparator4<? super E> reverseComparator4(Comparator4<? super E> comparator) {
+    // Making the method type argument explicit:
+    //   from4(comparator).<E>reverse();
+    // has the same result.
+    from4(comparator).<E>reverse();
+    return from4(comparator).reverse();
+  }
+}
diff --git a/framework/tests/framework/GetReceiverLoop.java b/framework/tests/framework/GetReceiverLoop.java
new file mode 100644
index 0000000..278a788
--- /dev/null
+++ b/framework/tests/framework/GetReceiverLoop.java
@@ -0,0 +1,22 @@
+import java.util.Collections;
+
+public class GetReceiverLoop {
+
+  void test() {
+    String s = Collections.emptyList().toString();
+  }
+
+  /*
+   * getAnnotatedType( emptyList().toString )
+   * -> TypeFromExpression.visitMemberSelect( emptyList().toString )
+   * -> TypeFromExpression.visitMethodInvocation( emptyList() )
+   * -> AnnotatedTypes.findTypeParameters( emptyList() )
+   * -> AnnotatedTypes.assignedTo( emptyList() )
+   * [the assignment context is emptyList().toString(), so then:]
+   * -> AnnotatedTypeFactory.getReceiver( emptyList() )
+   * -> getAnnotatedType( emtpyList() )
+   * -> TypeFromExpression.visitMethodInvocation( emptyList() )
+   * ...
+   */
+
+}
diff --git a/framework/tests/framework/InnerGenerics.java b/framework/tests/framework/InnerGenerics.java
new file mode 100644
index 0000000..5da4141
--- /dev/null
+++ b/framework/tests/framework/InnerGenerics.java
@@ -0,0 +1,33 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+class ListOuter<T> {}
+
+public class InnerGenerics {
+  class ListInner<T> {}
+
+  void testInner1() {
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd ListOuter<String> o = new @Odd ListOuter<String>();
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Odd ListInner<String> i = new @Odd ListInner<String>();
+  }
+
+  void testInner2() {
+    // :: error: (assignment)
+    @Odd ListOuter<String> o = new ListOuter<>();
+    // :: error: (assignment)
+    @Odd ListInner<String> i = new ListInner<>();
+  }
+
+  void testInner3() {
+    ListOuter<@Odd String> o = new ListOuter<>();
+    ListInner<@Odd String> i = new ListInner<>();
+  }
+
+  void testInner4() {
+    // :: error: (assignment)
+    ListOuter<@Odd String> o = new ListOuter<String>();
+    // :: error: (assignment)
+    ListInner<@Odd String> i = new ListInner<String>();
+  }
+}
diff --git a/framework/tests/framework/Issue346.java b/framework/tests/framework/Issue346.java
new file mode 100644
index 0000000..26df3e2
--- /dev/null
+++ b/framework/tests/framework/Issue346.java
@@ -0,0 +1,11 @@
+// Test case for Issue 346:
+// https://github.com/typetools/checker-framework/issues/346
+
+// :: error: (type.checking.not.run)
+class Before {}
+
+// :: error: (type.checking.not.run)
+class Context {
+  // :: error: cannot find symbol
+  Unknown f;
+}
diff --git a/framework/tests/framework/MatrixBug.java b/framework/tests/framework/MatrixBug.java
new file mode 100644
index 0000000..382b2da
--- /dev/null
+++ b/framework/tests/framework/MatrixBug.java
@@ -0,0 +1,6 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class MatrixBug {
+
+  public char[][] chars = new char[][] {new char[] {'*', '*', '*'}, new char[] {'*', '*', '*'}};
+}
diff --git a/framework/tests/framework/MethodOverrideBadParam.java b/framework/tests/framework/MethodOverrideBadParam.java
new file mode 100644
index 0000000..53fdb0d
--- /dev/null
+++ b/framework/tests/framework/MethodOverrideBadParam.java
@@ -0,0 +1,11 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public abstract class MethodOverrideBadParam {
+
+  public abstract void method(String s);
+
+  public static class SubclassA extends MethodOverrideBadParam {
+    // :: error: (override.param)
+    public void method(@Odd String s) {}
+  }
+}
diff --git a/framework/tests/framework/MethodOverrideBadReceiver.java b/framework/tests/framework/MethodOverrideBadReceiver.java
new file mode 100644
index 0000000..e77e8ab
--- /dev/null
+++ b/framework/tests/framework/MethodOverrideBadReceiver.java
@@ -0,0 +1,13 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public abstract class MethodOverrideBadReceiver {
+
+  public abstract String method();
+
+  public static class SubclassA extends MethodOverrideBadReceiver {
+    // :: error: (override.receiver)
+    public String method(@Odd SubclassA this) {
+      return "";
+    }
+  }
+}
diff --git a/framework/tests/framework/MethodOverrideBadReturn.java b/framework/tests/framework/MethodOverrideBadReturn.java
new file mode 100644
index 0000000..6c00915
--- /dev/null
+++ b/framework/tests/framework/MethodOverrideBadReturn.java
@@ -0,0 +1,13 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public abstract class MethodOverrideBadReturn {
+
+  public abstract @Odd String method();
+
+  public static class SubclassA extends MethodOverrideBadReturn {
+    // :: error: (override.return)
+    public String method() {
+      return "";
+    }
+  }
+}
diff --git a/framework/tests/framework/MethodOverrides.java b/framework/tests/framework/MethodOverrides.java
new file mode 100644
index 0000000..c29315b
--- /dev/null
+++ b/framework/tests/framework/MethodOverrides.java
@@ -0,0 +1,92 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public abstract class MethodOverrides {
+
+  public abstract @Odd String method();
+
+  public abstract String methodSub();
+
+  public abstract void param(@Odd String s);
+
+  public abstract void paramSup(@Odd String s);
+
+  public abstract void receiver(@Odd MethodOverrides this);
+
+  public abstract void receiverSub(@Odd MethodOverrides this);
+
+  public static class SubclassA extends MethodOverrides {
+
+    public @Odd String method() {
+      // :: error: (assignment)
+      @Odd String s = "";
+      return s;
+    }
+
+    public @Odd String methodSub() {
+      // :: error: (assignment)
+      @Odd String s = "";
+      return s;
+    }
+
+    public void param(@Odd String s) {}
+
+    public void paramSup(String s) {}
+
+    public void receiver(@Odd SubclassA this) {}
+
+    public void receiverSub() {}
+  }
+
+  static class X {
+    <T> T @Odd [] method(T @Odd [] t) {
+      return null;
+    }
+  }
+
+  static class Y extends X {
+    @Override
+    <S> S @Odd [] method(S @Odd [] s) {
+      return null;
+    }
+  }
+
+  static class Z extends X {
+    @Override
+    // return type is an incorrect override, as it's a supertype
+    // :: error: (override.return)
+    <A> A[] method(A[] s) {
+      return null;
+    }
+  }
+
+  static class Z2 extends X {
+    @Override
+    // :: error: (override.return) :: error: (override.param)
+    <A> @Odd A[] method(@Odd A[] s) {
+      return null;
+    }
+  }
+
+  static class ClX<T> {
+    T @Odd [] method(T @Odd [] t) {
+      return null;
+    }
+  }
+
+  static class ClY<S> extends ClX<S> {
+    @Override
+    S @Odd [] method(S @Odd [] s) {
+      return null;
+    }
+  }
+
+  static class ClZ<S> extends ClX<S> {
+    @Override
+    // :: error: (override.return) :: error: (override.param)
+    @Odd S[] method(@Odd S[] s) {
+      return null;
+    }
+  }
+
+  // TODO others...
+}
diff --git a/framework/tests/framework/MissingSymbolCrash.java b/framework/tests/framework/MissingSymbolCrash.java
new file mode 100644
index 0000000..2ad814d
--- /dev/null
+++ b/framework/tests/framework/MissingSymbolCrash.java
@@ -0,0 +1,7 @@
+// :: error: (type.checking.not.run)
+public class MissingSymbolCrash {
+  public void test() {
+    // :: error: cannot find symbol
+    lst.add(s);
+  }
+}
diff --git a/framework/tests/framework/MoreVarargs.java b/framework/tests/framework/MoreVarargs.java
new file mode 100644
index 0000000..8f01d4e
--- /dev/null
+++ b/framework/tests/framework/MoreVarargs.java
@@ -0,0 +1,15 @@
+public class MoreVarargs {
+
+  // :: warning: [unchecked] Possible heap pollution from parameterized vararg type T
+  <T> T[] genericVararg(T... args) {
+    return args;
+  }
+
+  void testGenericVararg() {
+    genericVararg("m");
+    genericVararg(new String[] {});
+    genericVararg(3);
+    genericVararg(new Integer[] {});
+    genericVararg(new int[] {});
+  }
+}
diff --git a/framework/tests/framework/MultiBoundTypeVar.java b/framework/tests/framework/MultiBoundTypeVar.java
new file mode 100644
index 0000000..ef25cc5
--- /dev/null
+++ b/framework/tests/framework/MultiBoundTypeVar.java
@@ -0,0 +1,16 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class MultiBoundTypeVar {
+
+  <T extends @Odd Number & Cloneable & @Odd Appendable> void test(T t) {
+    Number n1 = t;
+    @Odd Number n2 = t;
+
+    Cloneable c1 = t;
+
+    @Odd Cloneable c2 = t;
+
+    Appendable d1 = t;
+    @Odd Appendable d2 = t;
+  }
+}
diff --git a/framework/tests/framework/OverrideCrash.java b/framework/tests/framework/OverrideCrash.java
new file mode 100644
index 0000000..751613b
--- /dev/null
+++ b/framework/tests/framework/OverrideCrash.java
@@ -0,0 +1,8 @@
+import java.util.ArrayList;
+
+public class OverrideCrash extends ArrayList {
+  @Override
+  public Object[] toArray(Object[] o) {
+    return null;
+  }
+}
diff --git a/framework/tests/framework/PrimitiveDotClass.java b/framework/tests/framework/PrimitiveDotClass.java
new file mode 100644
index 0000000..658bd42
--- /dev/null
+++ b/framework/tests/framework/PrimitiveDotClass.java
@@ -0,0 +1,9 @@
+public class PrimitiveDotClass {
+
+  void test() {
+    doStuff(int.class);
+    doStuff(int[].class);
+  }
+
+  void doStuff(Class<?> cl) {}
+}
diff --git a/framework/tests/framework/README b/framework/tests/framework/README
new file mode 100644
index 0000000..ce3d595
--- /dev/null
+++ b/framework/tests/framework/README
@@ -0,0 +1,11 @@
+Java files in this directory are allowed to contain Java errors
+(that is, to cause javac without a processor to issue an error).
+This is an exception to the rules in ../../../checker/tests/README .
+
+To run the tests, do
+  cd $CHECKERFRAMEWORK/framework
+  ../gradlew FrameworkTest
+
+To run a single test, do something like:
+  cd $CHECKERFRAMEWORK/framework/tests/framework
+  (cd $CHECKERFRAMEWORK && ./gradle assemble :framework:compileTestJava) && javacheck -processor org.checkerframework.framework.testchecker.util.H1H2Checker -cp $CHECKERFRAMEWORK/framework/build/classes/java/test/
diff --git a/framework/tests/framework/RandomTests.java b/framework/tests/framework/RandomTests.java
new file mode 100644
index 0000000..e688dae
--- /dev/null
+++ b/framework/tests/framework/RandomTests.java
@@ -0,0 +1,14 @@
+import java.util.Collections;
+import java.util.List;
+
+public class RandomTests {
+  // Test that boxing occurs even if the varable (assigned to value) is not a declared type
+  void testBoxing() {
+    int i = 0;
+    Collections.singleton(i);
+  }
+
+  void testWildcards() {
+    List<?> l = null;
+  }
+}
diff --git a/framework/tests/framework/RecursiveDef.java b/framework/tests/framework/RecursiveDef.java
new file mode 100644
index 0000000..d953b5e
--- /dev/null
+++ b/framework/tests/framework/RecursiveDef.java
@@ -0,0 +1,6 @@
+public class RecursiveDef<T extends RecursiveDef> implements Comparable<T> {
+  @org.checkerframework.dataflow.qual.Pure
+  public int compareTo(T t) {
+    return 0;
+  }
+}
diff --git a/framework/tests/framework/ResolveError.java b/framework/tests/framework/ResolveError.java
new file mode 100644
index 0000000..e554197
--- /dev/null
+++ b/framework/tests/framework/ResolveError.java
@@ -0,0 +1,7 @@
+// :: error: (type.checking.not.run)
+public class ResolveError {
+  void m() {
+    // :: error: cannot find symbol
+    Unresolved.foo();
+  }
+}
diff --git a/framework/tests/framework/Supertypes.java b/framework/tests/framework/Supertypes.java
new file mode 100644
index 0000000..d1a3c89
--- /dev/null
+++ b/framework/tests/framework/Supertypes.java
@@ -0,0 +1,86 @@
+import java.util.ArrayList;
+import java.util.List;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class Supertypes {
+  static interface Inter<E> {}
+
+  static class A extends ArrayList<String> implements Inter<@Odd String> {}
+
+  static class B extends ArrayList<@Odd String> implements Inter<String> {}
+
+  A a1;
+  @Odd A a2;
+
+  B b1;
+  @Odd B b2;
+
+  void testSelf() {
+    // :: error: (assignment)
+    @Odd A t1 = a1; // should emit error
+    @Odd A t2 = a2;
+    // :: error: (assignment)
+    @Odd B t3 = b1; // should emit error
+    @Odd B t4 = b2;
+  }
+
+  void testList() {
+    List<String> l1 = a1;
+    List<String> l2 = a2;
+    // :: error: (assignment)
+    List<String> l3 = b1; // should emit error
+    // :: error: (assignment)
+    List<String> l4 = b2; // should emit error
+
+    // :: error: (assignment)
+    List<@Odd String> l5 = a1; // should emit error
+    // :: error: (assignment)
+    List<@Odd String> l6 = a2; // should emit error
+    List<@Odd String> l7 = b1;
+    List<@Odd String> l8 = b2;
+  }
+
+  void testInter() {
+    // :: error: (assignment)
+    Inter<String> l1 = a1; // should emit error
+    // :: error: (assignment)
+    Inter<String> l2 = a2; // should emit error
+    Inter<String> l3 = b1;
+    Inter<String> l4 = b2;
+
+    Inter<@Odd String> l5 = a1;
+    Inter<@Odd String> l6 = a2;
+    // :: error: (assignment)
+    Inter<@Odd String> l7 = b1; // should emit error
+    // :: error: (assignment)
+    Inter<@Odd String> l8 = b2; // should emit error
+  }
+
+  void testListOp() {
+    String s1 = a1.get(0);
+    String s2 = a2.get(0);
+    String s3 = b1.get(0);
+    String s4 = b2.get(0);
+
+    // :: error: (assignment)
+    @Odd String s5 = a1.get(0); // should emit error
+    // :: error: (assignment)
+    @Odd String s6 = a2.get(0); // should emit error
+    @Odd String s7 = b1.get(0);
+    @Odd String s8 = b2.get(0);
+  }
+
+  void ListIterable() {
+    for (String s : a1) {}
+    for (String s : a2) {}
+    for (String s : b1) {}
+    for (String s : b2) {}
+
+    // :: error: (enhancedfor)
+    for (@Odd String s : a1) {}
+    // :: error: (enhancedfor)
+    for (@Odd String s : a2) {}
+    for (@Odd String s : b1) {}
+    for (@Odd String s : b2) {}
+  }
+}
diff --git a/framework/tests/framework/SymbolError.java b/framework/tests/framework/SymbolError.java
new file mode 100644
index 0000000..469c4d3
--- /dev/null
+++ b/framework/tests/framework/SymbolError.java
@@ -0,0 +1,9 @@
+import java.util.LinkedList;
+import java.util.List;
+
+public class SymbolError {
+
+  void test() {
+    List<String> lst = new LinkedList<String>(null) {};
+  }
+}
diff --git a/framework/tests/framework/TypeInference.java b/framework/tests/framework/TypeInference.java
new file mode 100644
index 0000000..c5fe05e
--- /dev/null
+++ b/framework/tests/framework/TypeInference.java
@@ -0,0 +1,36 @@
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class TypeInference {
+
+  void test() {
+    Collection<@Odd String> lst1 = Collections.<@Odd String>emptyList();
+    // :: error: (assignment)
+    Collection<@Odd String> lst2 = Collections.<String>emptyList(); // should emit error
+    // :: error: (assignment)
+    Collection<String> lst3 = Collections.<@Odd String>emptyList(); // should emit error
+    Collection<@Odd String> lst4 = Collections.emptyList();
+    Map<Integer, @Odd String> lst5 = Collections.emptyMap();
+    Map<Integer, String> lst6 = Collections.emptyMap();
+  }
+
+  static class MyMap<E> extends HashMap<String, E> {}
+
+  static <T> MyMap<T> getMap() {
+    return null;
+  }
+
+  void testSuper() {
+    MyMap<@Odd String> m1 = TypeInference.<@Odd String>getMap();
+    MyMap<@Odd String> m2 = getMap();
+    // :: error: (assignment)
+    MyMap<String> m3 = TypeInference.<@Odd String>getMap(); // should emit error
+    MyMap<String> m4 = getMap();
+
+    Map<String, @Odd Integer> m5 = getMap();
+    Map<String, Integer> m6 = getMap();
+  }
+}
diff --git a/framework/tests/framework/Unboxing.java b/framework/tests/framework/Unboxing.java
new file mode 100644
index 0000000..8aa1169
--- /dev/null
+++ b/framework/tests/framework/Unboxing.java
@@ -0,0 +1,16 @@
+public class Unboxing {
+  boolean b = Boolean.TRUE;
+
+  <T> T foo(Class<T> expectedType) {
+    return null;
+  }
+
+  boolean b2 = foo(Boolean.class);
+  boolean b3 = foo(null);
+
+  <T> T bar() {
+    return null;
+  }
+
+  boolean b4 = bar();
+}
diff --git a/framework/tests/framework/UnimportedExtends1.java b/framework/tests/framework/UnimportedExtends1.java
new file mode 100644
index 0000000..9537b59
--- /dev/null
+++ b/framework/tests/framework/UnimportedExtends1.java
@@ -0,0 +1,3 @@
+// :: error: (type.checking.not.run)
+// :: error: cannot find symbol
+public class UnimportedExtends1 extends UnimportedClass {}
diff --git a/framework/tests/framework/UnimportedExtends2.java b/framework/tests/framework/UnimportedExtends2.java
new file mode 100644
index 0000000..a812fa1
--- /dev/null
+++ b/framework/tests/framework/UnimportedExtends2.java
@@ -0,0 +1,5 @@
+// :: error: (type.checking.not.run)
+public class UnimportedExtends2 {
+  // :: error: cannot find symbol
+  class Inner extends UnimportedClass {}
+}
diff --git a/framework/tests/framework/Varargs.java b/framework/tests/framework/Varargs.java
new file mode 100644
index 0000000..d8f8faa
--- /dev/null
+++ b/framework/tests/framework/Varargs.java
@@ -0,0 +1,43 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class Varargs {
+  public void testVarargsInvocation() {
+    @Odd String s = null;
+    aVarargsMethod(s);
+    // :: error: (argument)
+    aVarargsMethod(s, "");
+    aVarargsMethod(s, s);
+
+    moreVarargs(new @Odd String[1]);
+    // The assignment context infers @Odd for the component type.  With invariant array subtyping,
+    // this will fail, as the main type is a subtype.
+    moreVarargs(new String @Odd [1]);
+    // :: warning: (cast.unsafe.constructor.invocation)
+    moreVarargs(new @Odd String(), new @Odd String());
+    // :: error: (argument)
+    // :: warning: (cast.unsafe.constructor.invocation)
+    moreVarargs(new String(), new @Odd String());
+    moreVarargs(
+        // :: error: (argument)
+        new String(),
+        // :: error: (argument)
+        new String());
+  }
+
+  /* ------------------------------------------------------------ */
+
+  public void aVarargsMethod(@Odd String s, @Odd String... more) {}
+
+  public void moreVarargs(@Odd String... args) {}
+
+  Varargs(String... args) {}
+
+  void test() {
+    new Varargs("m", "n");
+    new Varargs();
+  }
+
+  void testVarargsConstructor() {
+    new ProcessBuilder("hello");
+  }
+}
diff --git a/framework/tests/framework/WildcardSuper.java b/framework/tests/framework/WildcardSuper.java
new file mode 100644
index 0000000..87e0155
--- /dev/null
+++ b/framework/tests/framework/WildcardSuper.java
@@ -0,0 +1,9 @@
+import java.util.List;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class WildcardSuper {
+  void test(List<? super @Odd String> list) {
+    // :: error: (assignment)
+    @Odd Object odd = list.get(0);
+  }
+}
diff --git a/framework/tests/framework/Wildcards.java b/framework/tests/framework/Wildcards.java
new file mode 100644
index 0000000..10f34f8
--- /dev/null
+++ b/framework/tests/framework/Wildcards.java
@@ -0,0 +1,12 @@
+import java.util.Date;
+import java.util.List;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class Wildcards {
+  void process(List<? extends Date> arg) {}
+
+  void test() {
+    List<? extends @Odd Date> myList = null;
+    process(myList);
+  }
+}
diff --git a/framework/tests/h1h2checker/AnonymousClasses.java b/framework/tests/h1h2checker/AnonymousClasses.java
new file mode 100644
index 0000000..3caa087
--- /dev/null
+++ b/framework/tests/h1h2checker/AnonymousClasses.java
@@ -0,0 +1,21 @@
+import java.util.Comparator;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1S1;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1S2;
+
+public class AnonymousClasses {
+  private <@H1S1 T extends @H1S1 Comparator<T>> void testGenericAnonymous() {
+    // :: error: (type.argument) :: error: (constructor.invocation)
+    new @H1S1 Gen<T>() {};
+    // :: error: (type.argument) :: warning: (cast.unsafe.constructor.invocation)
+    new @H1S1 GenInter<T>() {};
+  }
+}
+
+class Gen<@H1S2 F extends @H1S2 Object> {
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  public @H1S2 Gen() {}
+}
+
+interface GenInter<@H1S2 F extends @H1S2 Object> {}
+
+interface Foo {}
diff --git a/framework/tests/h1h2checker/Catch.java b/framework/tests/h1h2checker/Catch.java
new file mode 100644
index 0000000..173e1c2
--- /dev/null
+++ b/framework/tests/h1h2checker/Catch.java
@@ -0,0 +1,53 @@
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+public class Catch {
+  void defaultUnionType() throws Throwable {
+    try {
+      throw new Throwable();
+    } catch (IndexOutOfBoundsException | NullPointerException ex) {
+
+    }
+  }
+
+  void defaultDeclaredType() throws Throwable {
+    try {
+      throw new Throwable();
+    } catch (RuntimeException ex) {
+
+    }
+  }
+
+  void explictlyTopUnionType() throws Throwable {
+    try {
+      throw new Throwable();
+    } catch (@H1Top @H2Top IndexOutOfBoundsException | @H1Top @H2Top NullPointerException ex) {
+
+    }
+  }
+
+  void explictlyNotTopUnionType() throws Throwable {
+    try {
+      throw new Throwable();
+      // :: error: (exception.parameter)
+    } catch (@H1S1 @H2Top IndexOutOfBoundsException | @H1S1 @H2Top NullPointerException ex) {
+
+    }
+  }
+
+  void explictlyTopDeclaredType() throws Throwable {
+    try {
+      throw new Throwable();
+    } catch (@H1Top @H2Top NullPointerException ex) {
+
+    }
+  }
+
+  void explictlyNotTopDeclaredType() throws Throwable {
+    try {
+      throw new Throwable();
+      // :: error: (exception.parameter)
+    } catch (@H1S1 @H2Top RuntimeException ex) {
+
+    }
+  }
+}
diff --git a/framework/tests/h1h2checker/CompoundStringAssignment.java b/framework/tests/h1h2checker/CompoundStringAssignment.java
new file mode 100644
index 0000000..016952c
--- /dev/null
+++ b/framework/tests/h1h2checker/CompoundStringAssignment.java
@@ -0,0 +1,43 @@
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+public class CompoundStringAssignment {
+  @H1S1 @H2S1 String getSib1() {
+    return null;
+  }
+
+  void test1() {
+    String local = null;
+    // There was a bug in data flow where
+    // the type of local was @H1Bot @H2Bot after this
+    // StringConcatenateAssignmentNode,
+    // but only if the RHS was a method call.
+    local += getSib1();
+
+    // :: error: (assignment)
+    @H1Bot @H2Bot String isBot = local;
+    @H1S1 @H2S1 String isSib1 = local;
+  }
+
+  @H1Top @H2Top String top;
+
+  void test2() {
+    String local2 = top;
+    local2 += getSib1();
+
+    // :: error: (assignment)
+    @H1Bot @H2Bot String isBot2 = local2;
+    // :: error: (assignment)
+    @H1S1 @H2S1 String isSib12 = local2;
+  }
+
+  @H1S1 @H2S1 String sib1;
+
+  void test3() {
+    String local3 = null;
+    local3 += sib1;
+
+    // :: error: (assignment)
+    @H1Bot @H2Bot String isBot3 = local3;
+    @H1S1 @H2S1 String isSib13 = local3;
+  }
+}
diff --git a/framework/tests/h1h2checker/Constructors.java b/framework/tests/h1h2checker/Constructors.java
new file mode 100644
index 0000000..89a70c2
--- /dev/null
+++ b/framework/tests/h1h2checker/Constructors.java
@@ -0,0 +1,67 @@
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+public class Constructors {
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  @H1S2 @H2S2 Constructors() {}
+
+  void test1() {
+    // All quals from constructor
+    @H1S2 @H2S2 Constructors c1 = new Constructors();
+    // Can still specify my own
+    @H1S2 @H2Top Constructors c2 = new @H1S2 @H2Top Constructors();
+    // Can only specify some of the qualifiers, rest comes
+    // from constructor
+    @H1S2 @H2S2 Constructors c3 = new @H1S2 Constructors();
+
+    // :: error: (assignment)
+    @H1S2 @H2S1 Constructors e1 = new Constructors();
+    // :: error: (assignment)
+    @H1S2 @H2S1 Constructors e2 = new @H1S2 Constructors();
+  }
+
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  @H1S2 @H2Poly Constructors(@H1S1 @H2Poly int i) {}
+
+  void test2(@H1S1 @H2S2 int p) {
+    @H1S2 @H2S2 Constructors c1 = new Constructors(p);
+    @H1S2 @H2S2 Constructors c2 = new @H1S2 @H2S2 Constructors(p);
+    @H1S2 @H2S2 Constructors c3 = new @H1S2 Constructors(p);
+
+    // :: error: (assignment)
+    @H1S2 @H2S1 Constructors e1 = new Constructors(p);
+    // :: error: (assignment)
+    @H1S2 @H2S1 Constructors e2 = new @H1S2 @H2S2 Constructors(p);
+    // :: error: (assignment)
+    @H1S2 @H2S1 Constructors e3 = new @H1S2 Constructors(p);
+  }
+
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  @H1Poly @H2Poly Constructors(@H1Poly @H2Poly String s) {}
+
+  void test3(@H1S1 @H2S2 String p) {
+    @H1S1 @H2S2 Constructors c1 = new Constructors(p);
+    @H1S1 @H2S2 Constructors c2 = new @H1S1 @H2S2 Constructors(p);
+    @H1S1 @H2S2 Constructors c3 = new @H1S1 Constructors(p);
+
+    // :: error: (assignment)
+    @H1S2 @H2S1 Constructors e1 = new Constructors(p);
+    // :: error: (assignment) :: warning: (cast.unsafe.constructor.invocation)
+    @H1S2 @H2S1 Constructors e2 = new @H1S2 @H2S2 Constructors(p);
+    // :: error: (assignment) :: warning: (cast.unsafe.constructor.invocation)
+    @H1S2 @H2S1 Constructors e3 = new @H1S2 Constructors(p);
+
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @H1S2 @H2S2 Constructors e4 = new @H1S2 @H2S2 Constructors(p);
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @H1S2 @H2S2 Constructors e5 = new @H1S2 Constructors(p);
+  }
+
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  @org.checkerframework.framework.testchecker.util.Encrypted @H1Poly @H2Poly Constructors(@H1Poly @H2Poly String s, int i) {}
+
+  void test4(@H1S1 @H2S2 String p) {
+    @H1S1 @H2S2 Constructors c1 = new Constructors(p, 4);
+    @H1S1 @H2S2 Constructors c2 =
+        new @org.checkerframework.framework.testchecker.util.Encrypted Constructors(p);
+  }
+}
diff --git a/framework/tests/h1h2checker/Defaulting.java b/framework/tests/h1h2checker/Defaulting.java
new file mode 100644
index 0000000..1917cb6
--- /dev/null
+++ b/framework/tests/h1h2checker/Defaulting.java
@@ -0,0 +1,164 @@
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+// Test defaulting behavior, e.g. that local variables, casts, and instanceof
+// propagate the type of the respective sub-expression and that upper bounds
+// are separately annotated.
+public class Defaulting {
+
+  @DefaultQualifier(
+      value = H1S1.class,
+      locations = {TypeUseLocation.LOCAL_VARIABLE})
+  class TestLocal {
+    void m(@H1S1 Object p1, @H1S2 Object p2) {
+      Object l1 = p1;
+      // :: error: (assignment)
+      Object l2 = p2;
+    }
+  }
+
+  @DefaultQualifier(
+      value = H1Top.class,
+      locations = {TypeUseLocation.LOCAL_VARIABLE})
+  @DefaultQualifier(
+      value = H1S1.class,
+      locations = {TypeUseLocation.UPPER_BOUND})
+  @DefaultQualifier(
+      value = H1S2.class,
+      locations = {TypeUseLocation.OTHERWISE})
+  // Type of x is <@H1S2 X extends @H1S1 Object>, these annotations are siblings
+  // and should not be in the same bound
+  // :: warning: (inconsistent.constructor.type) :: error: (bound) :: error: (super.invocation)
+  class TestUpperBound<X extends Object> {
+    void m(X p) {
+      @H1S1 Object l1 = p;
+      // :: error: (assignment)
+      @H1S2 Object l2 = p;
+      Object l3 = p;
+    }
+  }
+
+  @DefaultQualifier(
+      value = H1Top.class,
+      locations = {TypeUseLocation.LOCAL_VARIABLE})
+  @DefaultQualifier(
+      value = H1S1.class,
+      locations = {TypeUseLocation.PARAMETER})
+  @DefaultQualifier(
+      value = H1S2.class,
+      locations = {TypeUseLocation.OTHERWISE})
+  // :: warning: (inconsistent.constructor.type) :: error: (super.invocation)
+  class TestParameter {
+    void m(Object p) {
+      @H1S1 Object l1 = p;
+      // :: error: (assignment)
+      @H1S2 Object l2 = p;
+      Object l3 = p;
+    }
+
+    void call() {
+      // :: warning: (cast.unsafe.constructor.invocation)
+      m(new @H1S1 Object());
+      // :: error: (argument) :: warning: (cast.unsafe.constructor.invocation)
+      m(new @H1S2 Object());
+      // :: error: (argument)
+      m(new Object());
+    }
+  }
+
+  @DefaultQualifier(
+      value = H1Top.class,
+      locations = {TypeUseLocation.LOCAL_VARIABLE})
+  @DefaultQualifier(
+      value = H1S1.class,
+      locations = {TypeUseLocation.PARAMETER})
+  @DefaultQualifier(
+      value = H1S2.class,
+      locations = {TypeUseLocation.OTHERWISE})
+  class TestConstructorParameter {
+
+    // :: warning: (inconsistent.constructor.type) :: error: (super.invocation)
+    TestConstructorParameter(Object p) {
+      @H1S1 Object l1 = p;
+      // :: error: (assignment)
+      @H1S2 Object l2 = p;
+      Object l3 = p;
+    }
+
+    void call() {
+      // :: warning: (cast.unsafe.constructor.invocation)
+      new TestConstructorParameter(new @H1S1 Object());
+      // :: error: (argument) :: warning: (cast.unsafe.constructor.invocation)
+      new TestConstructorParameter(new @H1S2 Object());
+      // :: error: (argument)
+      new TestConstructorParameter(new Object());
+    }
+  }
+
+  @DefaultQualifier(
+      value = H1Top.class,
+      locations = {TypeUseLocation.LOCAL_VARIABLE})
+  @DefaultQualifier(
+      value = H1S1.class,
+      locations = {TypeUseLocation.RETURN})
+  @DefaultQualifier(
+      value = H1S2.class,
+      locations = {TypeUseLocation.OTHERWISE})
+  // :: warning: (inconsistent.constructor.type) :: error: (super.invocation)
+  class TestReturns {
+    Object res() {
+      // :: warning: (cast.unsafe.constructor.invocation)
+      return new @H1S1 Object();
+    }
+
+    void m() {
+      @H1S1 Object l1 = res();
+      // :: error: (assignment)
+      @H1S2 Object l2 = res();
+      Object l3 = res();
+    }
+
+    Object res2() {
+      // :: error: (return) :: warning: (cast.unsafe.constructor.invocation)
+      return new @H1S2 Object();
+    }
+
+    Object res3() {
+      // :: error: (return)
+      return new Object();
+    }
+  }
+
+  @DefaultQualifier(
+      value = H1Top.class,
+      locations = {TypeUseLocation.LOCAL_VARIABLE})
+  @DefaultQualifier(
+      value = H1S1.class,
+      locations = {TypeUseLocation.RECEIVER})
+  public class ReceiverDefaulting {
+    public ReceiverDefaulting() {}
+
+    public void m() {}
+  }
+
+  @DefaultQualifier(
+      value = H1Top.class,
+      locations = {TypeUseLocation.LOCAL_VARIABLE})
+  class TestReceiver {
+
+    void call() {
+      // :: warning: (cast.unsafe.constructor.invocation)
+      @H1S1 ReceiverDefaulting r2 = new @H1S1 ReceiverDefaulting();
+      // :: warning: (cast.unsafe.constructor.invocation)
+      @H1S2 ReceiverDefaulting r3 = new @H1S2 ReceiverDefaulting();
+      ReceiverDefaulting r = new ReceiverDefaulting();
+
+      r2.m();
+      // :: error: (method.invocation)
+      r3.m();
+      // :: error: (method.invocation)
+      r.m();
+    }
+  }
+}
diff --git a/framework/tests/h1h2checker/ForEach.java b/framework/tests/h1h2checker/ForEach.java
new file mode 100644
index 0000000..b15d0d9
--- /dev/null
+++ b/framework/tests/h1h2checker/ForEach.java
@@ -0,0 +1,92 @@
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+public class ForEach {
+
+  Object arrayAccess1(Object[] constants) {
+    Object constant = constants[0];
+    return constant;
+  }
+
+  @H1S2 Object arrayAccessBad1(@H1S1 Object[] constants) {
+    Object constant = constants[0];
+    // :: error: (return)
+    return constant;
+  }
+
+  // Return type defaults to H1Top
+  @H2S1 Object arrayAccessBad2(@H1S1 @H2S2 Object[] constants) {
+    Object constant = constants[0];
+    // :: error: (return)
+    return constant;
+  }
+
+  Object iterateFor(Object[] constants) {
+    for (int i = 0; i < constants.length; ++i) {
+      Object constant = constants[i];
+      return constant;
+    }
+    return null;
+  }
+
+  Object iterateForEach(Object[] constants) {
+    for (Object constant : constants) {
+      return constant;
+    }
+    return null;
+  }
+
+  @H2S2 Object iterateForEachBad(@H2S1 Object[] constants) {
+    for (Object constant : constants) {
+      // :: error: (return)
+      return constant;
+    }
+    return null;
+  }
+
+  // Now with a method type variable
+
+  <T extends Object> T garrayAccess1(T[] constants) {
+    return constants[0];
+  }
+
+  <T extends Object> T garrayAccess1(T p) {
+    T constant = p;
+    return constant;
+  }
+
+  <T extends Object> @H1S2 T garrayAccessBad1(@H1S1 T[] constants) {
+    T constant = constants[0];
+    // :: error: (return)
+    return constant;
+  }
+
+  // Return type defaults to H1Top
+  <T extends Object> @H2S1 T garrayAccessBad2(@H1S1 @H2S2 T[] constants) {
+    T constant = constants[0];
+    // :: error: (return)
+    return constant;
+  }
+
+  <T extends Object> T giterateFor(T[] constants) {
+    for (int i = 0; i < constants.length; ++i) {
+      T constant = constants[i];
+      return constant;
+    }
+    return null;
+  }
+
+  <T extends Object> T giterateForEach(T[] constants) {
+    for (T constant : constants) {
+      return constant;
+    }
+    return null;
+  }
+
+  <T extends Object> @H2S2 T giterateForEachBad(@H2S1 T[] constants) {
+    for (T constant : constants) {
+      // :: error: (return)
+      return constant;
+    }
+    return null;
+  }
+}
diff --git a/framework/tests/h1h2checker/Generics.java b/framework/tests/h1h2checker/Generics.java
new file mode 100644
index 0000000..59f0059
--- /dev/null
+++ b/framework/tests/h1h2checker/Generics.java
@@ -0,0 +1,39 @@
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+public class Generics {
+
+  class Generics1<T extends @H1Top @H2Top Object> {
+
+    T m(@H1S2 @H2S2 T p) {
+      T l = p;
+      // :: error: (return)
+      return l;
+    }
+
+    void unsound(Generics1<@H1S1 @H2S1 Object> p, @H1S2 @H2S2 Object p2) {
+      @H1S1 @H2S1 Object o = p.m(p2);
+    }
+  }
+
+  class Generics2<T extends @H1Top Object> {
+
+    T m(@H1S2 T p) {
+      T l = p;
+      // :: error: (return)
+      return l;
+    }
+
+    void unsound(Generics2<@H1S1 Object> p, @H1S2 Object p2) {
+      @H1S1 Object o = p.m(p2);
+    }
+  }
+
+  class Generics3<T extends @H1S1 Object> {
+
+    // See comments in BaseTypeVisitor about type variable checks.
+    // The currently desired behavior is that the annotation on the
+    // type variable overrides the bound.
+    // TODO?:: error: (type.invalid)
+    void m(@H1S2 T p) {}
+  }
+}
diff --git a/framework/tests/h1h2checker/GetClassStubTest.java b/framework/tests/h1h2checker/GetClassStubTest.java
new file mode 100644
index 0000000..2c0764e
--- /dev/null
+++ b/framework/tests/h1h2checker/GetClassStubTest.java
@@ -0,0 +1,21 @@
+package h1h2checker;
+
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+public class GetClassStubTest {
+
+  // See AnnotatedTypeFactory.adaptGetClassReturnTypeToReceiver
+  void context() {
+    Integer i = 4;
+    Class<?> a = i.getClass();
+
+    Class<@H1Bot ? extends @H1S1 Object> succeed1 = i.getClass();
+    Class<@H1Bot ? extends @H1S1 Integer> succeed2 = i.getClass();
+
+    // :: error: (assignment)
+    Class<@H1Bot ? extends @H1Bot Object> fail1 = i.getClass();
+
+    // :: error: (assignment)
+    Class<@H1Bot ?> fail2 = i.getClass();
+  }
+}
diff --git a/framework/tests/h1h2checker/IncompatibleBounds.java b/framework/tests/h1h2checker/IncompatibleBounds.java
new file mode 100644
index 0000000..6e7f695
--- /dev/null
+++ b/framework/tests/h1h2checker/IncompatibleBounds.java
@@ -0,0 +1,59 @@
+package h1h2checker;
+
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.checkerframework.framework.qual.TypeUseLocation;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+/**
+ * This test is solely to ensure that if bounds in type parameters and wildcards are invalid then
+ * they are reported as such using a "bound" error.
+ *
+ * <p>A valid bound is one with LOWER_BOUND annotations that subtypes of UPPER_BOUND annotations.
+ */
+
+// set the defaults in the H2 hierarchy such that do not report errors in this test
+@DefaultQualifier(
+    value = H2Top.class,
+    locations = {TypeUseLocation.UPPER_BOUND})
+@DefaultQualifier(
+    value = H2Bot.class,
+    locations = {TypeUseLocation.LOWER_BOUND})
+public class IncompatibleBounds {
+
+  // The bounds below are valid
+  class TopToBottom<@H1Bot T extends @H1Top Object> {}
+
+  class TopToH1S1<@H1S1 TT extends @H1Top Object> {}
+
+  class H1S1ToBot<@H1Bot TTT extends @H1S1 Object> {}
+
+  class H1S1ToH1S1<@H1S1 TTTT extends @H1S1 Object> {}
+
+  class ValidContext {
+    TopToBottom<@H1Bot ? extends @H1Top Object> topToBot;
+    TopToH1S1<@H1S1 ? extends @H1Top Object> topToH1S1;
+    H1S1ToBot<@H1Bot ? extends @H1S1 Object> h1S1ToBot;
+    H1S1ToH1S1<@H1S1 ? extends @H1S1 Object> h1S1ToH1S1;
+  }
+
+  // invalid combinations
+  // :: error: (bound)
+  class BottomToTop<@H1Top U extends @H1Bot Object> {}
+  // :: error: (bound)
+  class H1S1ToTop<@H1Top UU extends @H1S1 Object> {}
+  // :: error: (bound)
+  class BottomToH1S1<@H1S1 UUU extends @H1Bot Object> {}
+  // :: error: (bound)
+  class H1S2ToH1S1<@H1S1 UUUU extends @H1S2 Object> {}
+
+  class InvalidContext {
+    // :: error: (bound)
+    BottomToTop<@H1Top ? extends @H1Bot Object> bottomToTop;
+    // :: error: (bound)
+    H1S1ToTop<@H1Top ? extends @H1S1 Object> h1S1ToTop;
+    // :: error: (bound)
+    BottomToH1S1<@H1S1 ? extends @H1Bot Object> bottomToH1S1;
+    // :: error: (bound)
+    H1S2ToH1S1<@H1S1 ? extends @H1S2 Object> h1S2ToH1S1;
+  }
+}
diff --git a/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java b/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java
new file mode 100644
index 0000000..5221c58
--- /dev/null
+++ b/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java
@@ -0,0 +1,175 @@
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+public class InferTypeArgsPolyChecker<OUTER_SCOPE_TV> {
+  // ----------------------------------------------------------
+  // Test Case - A
+  <A> A methodA(@H2Top A a1, @H2Top A a2) {
+    return null;
+  }
+
+  void contextA(@H1S1 @H2Bot String str, @H1Bot @H2Bot List<@H1S2 String> s) {
+    @H2Bot Object a = methodA(str, s);
+    @H1Top @H2Bot Object aTester = a;
+  }
+
+  // ----------------------------------------------------------
+  // Test Case - B
+  <B> B methodB(List<@H2S2 B> b1, List<@H1S2 B> b2) {
+    return null;
+  }
+
+  void contextB(List<@H1S1 @H2S2 String> l1, List<@H1S2 @H2S1 String> l2) {
+    @H1S1 @H2S1 String str = methodB(l1, l2);
+  }
+
+  // ----------------------------------------------------------
+  // Test Case - C
+  <C extends List<? extends Object>> C methodC(C c1, C c2) {
+    return null;
+  }
+
+  void contextC(List<@H1S1 @H2S2 ? extends @H1S1 @H2S2 String> l1, List<@H1S1 @H2S2 String> l2) {
+    List<@H1S1 @H2S2 ?> str = methodC(l1, l2);
+  }
+
+  // ----------------------------------------------------------
+  // Test Case - D
+
+  <D extends OUTER_SCOPE_TV, DD> D methodD(D d1, D d2, DD dd) {
+    return null;
+  }
+
+  <D extends OUTER_SCOPE_TV, DD> DD methodD2(D d1, D d2, DD dd) {
+    return null;
+  }
+
+  void contextD(OUTER_SCOPE_TV os1, @H1S1 @H2S1 OUTER_SCOPE_TV os2) {
+    OUTER_SCOPE_TV osNaked1 = methodD(os1, os1, os2);
+
+    // So for the next failure we correctly infer that for methodD to take both os1 and os2 as
+    // arguments D must be @H1Top @H2Top OUTER_SCOPE_TV.  However, the UPPER_BOUND of D is
+    // <@H1Bottom @H2Bottom OUTER_SCOPE_TV extends @H1Top @H2Top Object> notice that our
+    // inferred type for D is above this bound.
+    //
+    // A similar, more useful example in the Nullness type system would be:
+    /*
+       class Gen<OUTER> {
+          public List<OUTER> listo;
+
+          <T extends OUTER> void addToListo(T t1, T T2) {
+              listo.add(t1);
+              listo.add(t2);
+          }
+
+          void launder(@NonNull OUTER arg1, @Nullable OUTER arg2) {
+              addToListo(arg1, arg2); // T is inferred to be <@Nullable OUTER>
+                                      // if we did not mark this as type.argument
+                                      // then we would have no idea that in the last
+                                      // line of this example we are putting a null value into
+                                      // a List of @NonNull Strings
+          }
+
+       }
+
+       Gen<@NonNull String> g = ...;
+       g.listo = new ArrayList<@NonNull String>();
+       g.launder("", null);    // during this method call null would be added to g.listo
+    */
+    // :: error: (type.argument)
+    OUTER_SCOPE_TV osNaked2 = methodD(os1, os2, "");
+
+    // :: error: (type.argument)
+    OUTER_SCOPE_TV osAnnoed = methodD(os2, os2, "");
+
+    // :: error: (type.argument)
+    String str = methodD2(os2, os1, "");
+    OUTER_SCOPE_TV osNaked3 = methodD2(os1, os1, os2);
+  }
+
+  // ----------------------------------------------------------
+  // Test Case - E
+  <E> E methodE(E e1, E[] aos2) {
+    return null;
+  } // pass an array to one of these to cover A2FReducer.visitArray_Typevar
+
+  void contextE(String[] strArr, String[][] strArrArr, OUTER_SCOPE_TV os, OUTER_SCOPE_TV[] aos) {
+    String[] strArrLocal = methodE(strArr, strArrArr);
+    OUTER_SCOPE_TV osLocal = methodE(os, aos);
+  }
+
+  // ----------------------------------------------------------
+  // Test Case - C
+  <F> List<? super F> methodF(List<? extends F> lExtF, List<? super F> lSupF) {
+    return null;
+  }
+
+  void contextF(
+      List<@H1Bot @H2Bot ? extends @H1Top @H2S1 String> l1,
+      List<? super @H1S1 @H2S2 String> l2,
+      List<@H1S1 @H2S2 ? extends @H1Top @H2Top String> l3) {
+
+    // :: error: (argument)
+    List<? super @H1Bot @H2Bot String> lstr1 = methodF(l1, l2);
+
+    // :: error: (argument)
+    List<? super @H1Top @H2S2 String> lstr2 = methodF(l3, l2);
+  }
+
+  <G extends List<H>, H extends List<G>> List<H> methodG(G g1, G G2) {
+    return null;
+  }
+
+  class NodeList extends ArrayList<@H1Top @H2Top NodeList> {}
+
+  void contextG(NodeList l1, NodeList l2) {
+    List<@H1Top @H2Top NodeList> a = methodG(l1, l2);
+  }
+
+  // add test case for Array<String> vs. String[]
+  // add test case of E extends F, F extends G, H extends List<? super E> and other craziness
+
+  <M, N extends M> Map<M, N> method() {
+    return null;
+  }
+
+  void contextMN() {
+
+    // so I am not exactly sure how to create a meaningful test for this case
+    // what occurs is the SubtypeSolver.propagateGlbs forces N to be a subtype of M
+    // and it works (at least while I was debugging) but I don't know how to create
+    // a check that fails or succeeds because of this
+    Map<? super @H1S1 @H2S2 CharSequence, @H1Top @H2Top ? super String> mnl = method();
+  }
+
+  //    class Pair<PONE,PTWO> {
+  //        PONE _1;
+  //        PTWO _2;
+  //    }
+
+  <O extends P, P> @H1Top @H2Top P methodOP(@H1Top @H2Top P p, O o) {
+    return null;
+  }
+
+  void contextOP(@H1S1 @H2S1 String s1, @H1Bot @H2Bot String s2) {
+    // This test is actually here to test that the constraint P :> O is implied on p
+    // :: error: (assignment)
+    @H1Bot @H2Bot String loc = methodOP(s1, s2);
+  }
+
+  //    class Triplet<L,M,N> {
+  //        L l;
+  //        M m;
+  //        N n;
+  //    }
+  //
+  //    <X extends Y, Y extends Z, Z> Triplet<X,Y,Z> methodXYZ() {
+  //        return null;
+  //    }
+  //
+  //    void contextXYZ() {
+  //        Triplet<@H1S>
+  //    }
+}
diff --git a/framework/tests/h1h2checker/Inheritance.java b/framework/tests/h1h2checker/Inheritance.java
new file mode 100644
index 0000000..b53de76
--- /dev/null
+++ b/framework/tests/h1h2checker/Inheritance.java
@@ -0,0 +1,16 @@
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+// :: warning: (inconsistent.constructor.type) :: error: (super.invocation)
+@H1S1 class Inheritance {
+  void bar1(@H1Bot Inheritance param) {}
+
+  void bar2(@H1S1 Inheritance param) {}
+  // :: error: (annotations.on.use)
+  void bar3(@H1Top Inheritance param) {}
+
+  void foo1(@H1Bot Inheritance[] param) {}
+
+  void foo2(@H1S1 Inheritance[] param) {}
+  // :: error: (annotations.on.use)
+  void foo3(@H1Top Inheritance[] param) {}
+}
diff --git a/framework/tests/h1h2checker/Issue681.java b/framework/tests/h1h2checker/Issue681.java
new file mode 100644
index 0000000..56ce83a
--- /dev/null
+++ b/framework/tests/h1h2checker/Issue681.java
@@ -0,0 +1,51 @@
+// Test case for Issue 681:
+// https://github.com/typetools/checker-framework/issues/681
+
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1S2;
+
+// TODO: Issue is fixed, but test needs to be re-written in
+// a way that actually checks the behavior.
+
+/**
+ * This class shows that non-explicitly written annotations on
+ * Inner types are not inserted correctly.
+ *
+ * The TestAnnotatedTypeFactory adds @H1S2 to the type of any variable
+ * whose name contains "addH1S2".
+ *
+ * <pre>
+ * javacheck -cp tests/build/ -processor h1h2checker.H1H2Checker tests/h1h2checker/Issue681.java
+ * javap -verbose Issue681\$Inner.class
+ * <pre>
+ *
+ * Outputs:
+ * ...
+ * <pre>
+ * Issue681$Inner addH1S2;
+ * descriptor: LIssue681$Inner;
+ * flags:
+ * RuntimeVisibleTypeAnnotations:
+ * 0: #10(): FIELD
+ * 1: #11(): FIELD
+ *
+ * Issue681$Inner explicitH1S2;
+ * descriptor: LIssue681$Inner;
+ * flags:
+ * RuntimeVisibleTypeAnnotations:
+ * 0: #11(): FIELD, location=[INNER_TYPE]
+ * 1: #10(): FIELD
+ * 2: #11(): FIELD
+ * </pre>
+ */
+public class Issue681 {
+  class Inner {
+    @H1S2 Inner explicitH1S2;
+    Issue681.@H1S2 Inner explicitNestedH1S2;
+    @H1S2 Issue681.Inner explicitOneOuterH1S2;
+    Inner addH1S2;
+
+    @H1S2 Inner method(@H1S2 Inner paramExplicit, Inner nonAnno) {
+      return paramExplicit;
+    }
+  }
+}
diff --git a/framework/tests/h1h2checker/Issue798.java b/framework/tests/h1h2checker/Issue798.java
new file mode 100644
index 0000000..9e834c5
--- /dev/null
+++ b/framework/tests/h1h2checker/Issue798.java
@@ -0,0 +1,20 @@
+// Test case for Issue 798
+// https://github.com/typetools/checker-framework/issues/798
+
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+public class Issue798 {
+  void test1(String format, @H1S1 Object @H1S2 ... args) {
+    String.format(format, args);
+  }
+
+  void test2(String format, @H1S1 Object @H1S1 ... args) {
+    // :: error: (argument)
+    String.format(format, args);
+  }
+
+  void test3(String format, @H1S2 Object @H1S2 ... args) {
+    // :: error: (argument)
+    String.format(format, args);
+  }
+}
diff --git a/framework/tests/h1h2checker/Issue849.java b/framework/tests/h1h2checker/Issue849.java
new file mode 100644
index 0000000..6b2ada3
--- /dev/null
+++ b/framework/tests/h1h2checker/Issue849.java
@@ -0,0 +1,14 @@
+// Test case for Issue 849:
+// https://github.com/typetools/checker-framework/issues/849
+
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1S2;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Top;
+
+public class Issue849 {
+  class Gen<G> {}
+
+  void polyAll(Gen<Gen<@H1S2 Object>> genGenNonNull) {
+    // :: error: (assignment)
+    Gen<@H1Top ? extends @H1Top Gen<@H1Top Object>> a = genGenNonNull;
+  }
+}
diff --git a/framework/tests/h1h2checker/Primitive.java b/framework/tests/h1h2checker/Primitive.java
new file mode 100644
index 0000000..5727cd4
--- /dev/null
+++ b/framework/tests/h1h2checker/Primitive.java
@@ -0,0 +1,19 @@
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+public class Primitive {
+  @SuppressWarnings("assignment")
+  @H1S2 int o = 4;
+
+  @H1S2 @H2Poly int m(@H1S2 @H2Poly int p) {
+    return p;
+  }
+
+  void use1(@H1S2 @H2S1 int p) {
+    @H1S2 @H2S1 int l = m(p);
+  }
+
+  void use2(@H1S2 @H2S2 int p) {
+    // :: error: (assignment)
+    @H1S2 @H2S1 int l = m(p);
+  }
+}
diff --git a/framework/tests/h1h2checker/TypeRefinement.java b/framework/tests/h1h2checker/TypeRefinement.java
new file mode 100644
index 0000000..0fabe4f
--- /dev/null
+++ b/framework/tests/h1h2checker/TypeRefinement.java
@@ -0,0 +1,18 @@
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Invalid;
+
+public class TypeRefinement {
+  // :: warning: (cast.unsafe.constructor.invocation)
+  @H1Top Object o = new @H1S1 Object();
+  // :: error: (h1h2checker.h1invalid.forbidden) :: warning: (cast.unsafe.constructor.invocation)
+  @H1Top Object o2 = new @H1Invalid Object();
+  // :: error: (h1h2checker.h1invalid.forbidden)
+  @H1Top Object o3 = getH1Invalid();
+
+  // :: error: (h1h2checker.h1invalid.forbidden)
+  @H1Invalid Object getH1Invalid() {
+    // :: error: (h1h2checker.h1invalid.forbidden) :: warning:
+    // (cast.unsafe.constructor.invocation)
+    return new @H1Invalid Object();
+  }
+}
diff --git a/framework/tests/h1h2checker/h1h2checker.astub b/framework/tests/h1h2checker/h1h2checker.astub
new file mode 100644
index 0000000..91f9a83
--- /dev/null
+++ b/framework/tests/h1h2checker/h1h2checker.astub
@@ -0,0 +1,13 @@
+package java.lang;
+
+import org.checkerframework.framework.testchecker.h1h2checker.quals.*;
+
+public final class Object {
+    public Class<@H1S1 ? extends @H1S1 Object> getClass();
+    @SuppressWarnings("all") // Used to cause crash.
+    public boolean equals(Object o);
+}
+
+class String {
+    public static String format(String format, @H1S1 Object @H1S2... args);
+}
diff --git a/framework/tests/initialized-fields-value/ClassInitializer.java b/framework/tests/initialized-fields-value/ClassInitializer.java
new file mode 100644
index 0000000..7f81681
--- /dev/null
+++ b/framework/tests/initialized-fields-value/ClassInitializer.java
@@ -0,0 +1,23 @@
+import org.checkerframework.common.value.qual.IntVal;
+
+public class ClassInitializer {
+
+  @IntVal(1) int x;
+
+  @IntVal(2) int y;
+
+  int z;
+
+  {
+    y = 2;
+  }
+
+  ClassInitializer() {
+    x = 1;
+  }
+
+  ClassInitializer(boolean ignore) {
+    x = 1;
+    z = 3;
+  }
+}
diff --git a/framework/tests/initialized-fields-value/ClassInitializer2.java b/framework/tests/initialized-fields-value/ClassInitializer2.java
new file mode 100644
index 0000000..a25c535
--- /dev/null
+++ b/framework/tests/initialized-fields-value/ClassInitializer2.java
@@ -0,0 +1,20 @@
+import org.checkerframework.common.value.qual.IntVal;
+
+public class ClassInitializer2 {
+
+  @IntVal(1) int x;
+
+  @IntVal(2) int y;
+
+  int z;
+
+  {
+    x = 1;
+  }
+
+  {
+    y = 2;
+  }
+
+  ClassInitializer2() {}
+}
diff --git a/framework/tests/initialized-fields-value/ClassInitializer2a.java b/framework/tests/initialized-fields-value/ClassInitializer2a.java
new file mode 100644
index 0000000..33a9da3
--- /dev/null
+++ b/framework/tests/initialized-fields-value/ClassInitializer2a.java
@@ -0,0 +1,17 @@
+import org.checkerframework.common.value.qual.IntVal;
+
+public class ClassInitializer2a {
+
+  @IntVal(1) int x;
+
+  @IntVal(2) int y;
+
+  int z;
+
+  {
+    x = 1;
+  }
+
+  // :: error: (contracts.postcondition)
+  ClassInitializer2a() {}
+}
diff --git a/framework/tests/initialized-fields-value/ClassInitializer3.java b/framework/tests/initialized-fields-value/ClassInitializer3.java
new file mode 100644
index 0000000..bf9da2a
--- /dev/null
+++ b/framework/tests/initialized-fields-value/ClassInitializer3.java
@@ -0,0 +1,24 @@
+import org.checkerframework.common.value.qual.IntVal;
+
+public class ClassInitializer3 {
+
+  @IntVal(1) int x;
+
+  @IntVal(2) int y;
+
+  int z;
+
+  {
+    if (Math.random() < 0) {
+      x = 1;
+    } else {
+      x = 1;
+    }
+  }
+
+  {
+    y = 2;
+  }
+
+  ClassInitializer3() {}
+}
diff --git a/framework/tests/initialized-fields-value/ManualConstructor.java b/framework/tests/initialized-fields-value/ManualConstructor.java
new file mode 100644
index 0000000..ecc24b8
--- /dev/null
+++ b/framework/tests/initialized-fields-value/ManualConstructor.java
@@ -0,0 +1,32 @@
+import org.checkerframework.common.value.qual.IntVal;
+
+public class ManualConstructor {
+
+  @IntVal(1) int x;
+
+  @IntVal(2) int y;
+
+  int z;
+
+  // :: error: (contracts.postcondition)
+  ManualConstructor() {
+    x = 1;
+  }
+
+  // :: error: (contracts.postcondition)
+  ManualConstructor(boolean ignore) {
+    x = 1;
+    z = 3;
+  }
+
+  ManualConstructor(float ignore) {
+    x = 1;
+    y = 2;
+  }
+
+  ManualConstructor(double ignore) {
+    x = 1;
+    y = 2;
+    z = 3;
+  }
+}
diff --git a/framework/tests/initialized-fields/ConstructorPostcondition.java b/framework/tests/initialized-fields/ConstructorPostcondition.java
new file mode 100644
index 0000000..15396a8
--- /dev/null
+++ b/framework/tests/initialized-fields/ConstructorPostcondition.java
@@ -0,0 +1,20 @@
+// import org.checkerframework.common.initializedfields.qual.InitializedFields;
+
+public class ConstructorPostcondition {
+
+  int x;
+  int y;
+  int z;
+
+  ConstructorPostcondition() {
+    x = 1;
+    y = 2;
+    z = 3;
+  }
+
+  // :: error: (contracts.postcondition)
+  ConstructorPostcondition(int ignore) {
+    x = 1;
+    y = 2;
+  }
+}
diff --git a/framework/tests/initialized-fields/EnsuresInitializedFieldsTest.java b/framework/tests/initialized-fields/EnsuresInitializedFieldsTest.java
new file mode 100644
index 0000000..b500dbb
--- /dev/null
+++ b/framework/tests/initialized-fields/EnsuresInitializedFieldsTest.java
@@ -0,0 +1,79 @@
+import org.checkerframework.common.initializedfields.qual.EnsuresInitializedFields;
+
+// import org.checkerframework.common.initializedfields.qual.InitializedFields;
+
+public class EnsuresInitializedFieldsTest {
+
+  int x;
+  int y;
+  int z;
+
+  EnsuresInitializedFieldsTest() {
+    x = 1;
+    y = 2;
+    z = 3;
+  }
+
+  @EnsuresInitializedFields(
+      value = "this",
+      fields = {"x", "y"})
+  // :: error: (contracts.postcondition)
+  void setsX() {
+    x = 1;
+  }
+
+  @EnsuresInitializedFields(fields = {"x", "y"})
+  // :: error: (contracts.postcondition)
+  void setsX2() {
+    x = 1;
+  }
+
+  @EnsuresInitializedFields(
+      value = "#1",
+      fields = {"x", "y"})
+  // :: error: (contracts.postcondition)
+  void setsX(EnsuresInitializedFieldsTest eift) {
+    eift.x = 1;
+  }
+
+  @EnsuresInitializedFields(
+      value = "this",
+      fields = {"x", "y"})
+  void setsXY() {
+    x = 1;
+    y = 2;
+  }
+
+  @EnsuresInitializedFields(fields = {"x", "y"})
+  void setsXY2() {
+    x = 1;
+    y = 2;
+  }
+
+  @EnsuresInitializedFields(
+      value = "#1",
+      fields = {"x", "y"})
+  void setsXY(EnsuresInitializedFieldsTest eift) {
+    eift.x = 1;
+    eift.y = 2;
+  }
+
+  @EnsuresInitializedFields(
+      value = "#1",
+      fields = {"x", "y"})
+  void setsXY2(EnsuresInitializedFieldsTest eift) {
+    setsXY(eift);
+  }
+
+  @EnsuresInitializedFields(
+      value = "#1",
+      fields = {"x", "y"})
+  @EnsuresInitializedFields(
+      value = "#2",
+      fields = {"x", "z"})
+  void setsXY2(EnsuresInitializedFieldsTest eift1, EnsuresInitializedFieldsTest eift2) {
+    setsXY(eift1);
+    setsX(eift2);
+    eift2.z = 3;
+  }
+}
diff --git a/framework/tests/initialized-fields/HelperMethodInitializesFields.java b/framework/tests/initialized-fields/HelperMethodInitializesFields.java
new file mode 100644
index 0000000..a5e9b61
--- /dev/null
+++ b/framework/tests/initialized-fields/HelperMethodInitializesFields.java
@@ -0,0 +1,188 @@
+import org.checkerframework.common.initializedfields.qual.EnsuresInitializedFields;
+import org.checkerframework.common.initializedfields.qual.InitializedFields;
+
+public class HelperMethodInitializesFields {
+
+  int x;
+  int y;
+  int z;
+
+  HelperMethodInitializesFields(int ignore) {
+    helperMethodXY();
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  HelperMethodInitializesFields(int ignore, String ignore2) {
+    helperMethodXY2();
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  HelperMethodInitializesFields(long ignore) {
+    this.helperMethodXY();
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  HelperMethodInitializesFields(long ignore, String ignore2) {
+    this.helperMethodXY2();
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  HelperMethodInitializesFields(float ignore) {
+    staticHelperMethodXY(this);
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  HelperMethodInitializesFields(double ignore) {
+    this.staticHelperMethodXY(this);
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  HelperMethodInitializesFields(boolean ignore) {
+    new OtherClass().helperMethodXY(this);
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  HelperMethodInitializesFields(char ignore) {
+    new OtherClass().helperMethodXY2(0, this);
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  HelperMethodInitializesFields(int ignore1, byte ignore2) {
+    new OtherClass().staticHelperMethodXY(this);
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  HelperMethodInitializesFields(int ignore1, short ignore2) {
+    new OtherClass().staticHelperMethodXY2(0, this);
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  HelperMethodInitializesFields(int ignore1, int ignore2) {
+    OtherClass.staticHelperMethodXY(this);
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  HelperMethodInitializesFields(int ignore1, long ignore2) {
+    OtherClass.staticHelperMethodXY2(0, this);
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  // Simple tests of  LUB
+
+  HelperMethodInitializesFields(boolean ignore1, int ignore) {
+    z = 3;
+    helperMethodXY();
+    @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this;
+  }
+
+  HelperMethodInitializesFields(boolean ignore1, char ignore) {
+    z = 3;
+    helperMethodXY2();
+    @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this;
+  }
+
+  HelperMethodInitializesFields(boolean ignore1, float ignore) {
+    z = 3;
+    staticHelperMethodXY(this);
+    @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this;
+  }
+
+  HelperMethodInitializesFields(boolean ignore1, boolean ignore) {
+    z = 3;
+    new OtherClass().helperMethodXY(this);
+    @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this;
+  }
+
+  HelperMethodInitializesFields(boolean ignore1, short ignore2) {
+    z = 3;
+    OtherClass.staticHelperMethodXY(this);
+    @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this;
+  }
+
+  // More complex tests of  LUB
+
+  HelperMethodInitializesFields(byte ignore1, int ignore) {
+    y = 2;
+    z = 3;
+    helperMethodXY();
+    @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this;
+  }
+
+  HelperMethodInitializesFields(byte ignore1, long ignore) {
+    y = 2;
+    helperMethodXY();
+    @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this;
+    z = 3;
+  }
+
+  // The helper methods
+
+  @EnsuresInitializedFields(
+      value = "this",
+      fields = {"x", "y"})
+  void helperMethodXY() {
+    x = 1;
+    this.y = 1;
+  }
+
+  @EnsuresInitializedFields(fields = {"x", "y"})
+  void helperMethodXY2() {
+    x = 1;
+    this.y = 1;
+  }
+
+  @EnsuresInitializedFields(
+      value = "#1",
+      fields = {"x", "y"})
+  static void staticHelperMethodXY(HelperMethodInitializesFields hmif) {
+    hmif.x = 1;
+    hmif.y = 1;
+  }
+}
+
+class OtherClass {
+
+  @EnsuresInitializedFields(
+      value = "#1",
+      fields = {"x", "y"})
+  void helperMethodXY(HelperMethodInitializesFields hmif) {
+    hmif.x = 1;
+    hmif.y = 1;
+  }
+
+  @EnsuresInitializedFields(
+      value = "#2",
+      fields = {"x", "y"})
+  void helperMethodXY2(int ignore, HelperMethodInitializesFields hmif) {
+    hmif.x = 1;
+    hmif.y = 1;
+  }
+
+  @EnsuresInitializedFields(
+      value = "#1",
+      fields = {"x", "y"})
+  static void staticHelperMethodXY(HelperMethodInitializesFields hmif) {
+    hmif.x = 1;
+    hmif.y = 1;
+  }
+
+  @EnsuresInitializedFields(
+      value = "#2",
+      fields = {"x", "y"})
+  static void staticHelperMethodXY2(int ignore, HelperMethodInitializesFields hmif) {
+    hmif.x = 1;
+    hmif.y = 1;
+  }
+}
diff --git a/framework/tests/initialized-fields/SimpleConstructor.java b/framework/tests/initialized-fields/SimpleConstructor.java
new file mode 100644
index 0000000..6d509af
--- /dev/null
+++ b/framework/tests/initialized-fields/SimpleConstructor.java
@@ -0,0 +1,29 @@
+import org.checkerframework.common.initializedfields.qual.InitializedFields;
+
+public class SimpleConstructor {
+
+  int x;
+  int y;
+  int z;
+
+  SimpleConstructor() {
+    // :: error: (assignment)
+    @InitializedFields({"x", "y", "z"}) SimpleConstructor sc1 = this;
+    @InitializedFields() SimpleConstructor sc2 = this;
+
+    x = 1;
+
+    // :: error: (assignment)
+    @InitializedFields({"x", "y", "z"}) SimpleConstructor sc3 = this;
+    @InitializedFields({"x"}) SimpleConstructor sc4 = this;
+
+    this.y = 1;
+
+    // :: error: (assignment)
+    @InitializedFields({"x", "y", "z"}) SimpleConstructor sc5 = this;
+    @InitializedFields({"x", "y"}) SimpleConstructor sc6 = this;
+    @InitializedFields({"y", "x"}) SimpleConstructor sc7 = this;
+
+    z = 3;
+  }
+}
diff --git a/framework/tests/lubglb/Dummy.java b/framework/tests/lubglb/Dummy.java
new file mode 100644
index 0000000..edd55f3
--- /dev/null
+++ b/framework/tests/lubglb/Dummy.java
@@ -0,0 +1,4 @@
+public class Dummy {
+  // We don't need any code here.
+  // The actual tests are performed within the LubglbChecker using assert statements.
+}
diff --git a/framework/tests/lubglb/IntersectionTypes.java b/framework/tests/lubglb/IntersectionTypes.java
new file mode 100644
index 0000000..37b0ba9
--- /dev/null
+++ b/framework/tests/lubglb/IntersectionTypes.java
@@ -0,0 +1,31 @@
+import org.checkerframework.framework.testchecker.lubglb.quals.*;
+
+interface Foo {}
+
+interface Bar {}
+
+class Baz implements Foo, Bar {}
+
+public class IntersectionTypes {
+  // :: warning: (explicit.annotation.ignored)
+  <S extends @B Foo & @C Bar> void call1(S p) {}
+
+  // :: warning: (explicit.annotation.ignored)
+  <T extends @C Bar & @B Foo> void call2(T p) {}
+
+  void foo1(@D Baz baz1) {
+    call1(baz1);
+    call2(baz1);
+  }
+
+  void foo2(@F Baz baz2) {
+    call1(baz2);
+    call2(baz2);
+  }
+
+  void foo3(@B Baz baz3) {
+    call1(baz3);
+    // :: error: (type.argument)
+    call2(baz3);
+  }
+}
diff --git a/framework/tests/lubglb/Issue2432Constructor.java b/framework/tests/lubglb/Issue2432Constructor.java
new file mode 100644
index 0000000..d7dc743
--- /dev/null
+++ b/framework/tests/lubglb/Issue2432Constructor.java
@@ -0,0 +1,84 @@
+// Test case for issue 2432, constructor part:
+// https://github.com/typetools/checker-framework/issues/2432
+
+import org.checkerframework.framework.testchecker.lubglb.quals.*;
+
+class Issue2432C {
+
+  // reason for suppressing:
+  // super.invocation: Object is @A by default and it is unreasonable to change jdk stub
+  // just because of this
+  // inconsistent.constructor.type: the qualifier on returning type is expected not to be top
+  @SuppressWarnings({"super.invocation", "inconsistent.constructor.type"})
+  @Poly Issue2432C(@Poly Object dummy) {}
+
+  @SuppressWarnings({"super.invocation", "inconsistent.constructor.type"})
+  @Poly Issue2432C(@Poly Object dummy1, @Poly Object dummy2) {}
+
+  // class for test cases using type parameter
+  static class TypeParamClass<T> {
+
+    // @Poly on T shouldn't be in the poly resolving process
+    @SuppressWarnings({"super.invocation", "inconsistent.constructor.type"})
+    @Poly TypeParamClass(@Poly Object dummy, T t) {}
+
+    // 2 poly param for testing lub
+    @SuppressWarnings({"super.invocation", "inconsistent.constructor.type"})
+    @Poly TypeParamClass(@Poly Object dummy1, @Poly Object dummy2, T t) {}
+  }
+
+  // class for test cases using type parameter
+  class ReceiverClass {
+
+    // if the qualifier on receiver is @Poly, it should not be involved in poly resolve process
+    @SuppressWarnings({"super.invocation", "inconsistent.constructor.type"})
+    @Poly ReceiverClass(Issue2432C Issue2432C.this, @Poly Object dummy) {}
+
+    // 2 poly param for testing lub
+    @SuppressWarnings({"super.invocation", "inconsistent.constructor.type"})
+    @Poly ReceiverClass(Issue2432C Issue2432C.this, @Poly Object dummy1, @Poly Object dummy2) {}
+  }
+
+  void invokeConstructors(@A Object top, @F Object bottom, @Poly Object poly) {
+    // :: error: (assignment)
+    @F Issue2432C bottomOuter = new Issue2432C(top);
+    @A Issue2432C topOuter = new Issue2432C(top);
+
+    // lub test
+    @A Issue2432C bottomOuter2 = new Issue2432C(top, bottom);
+    // :: error: (assignment)
+    @B Issue2432C bottomOuter3 = new Issue2432C(top, bottom);
+
+    @F Issue2432C bottomOuter4 = new Issue2432C(bottom, bottom);
+  }
+
+  // invoke constructors with a receiver to test poly resolving
+  // note: seems CF already works well on these before changes
+  void invokeReceiverConstructors(
+      @A Issue2432C topOuter, @Poly Issue2432C polyOuter, @F Object bottom, @A Object top) {
+    Issue2432C.@F ReceiverClass ref1 = polyOuter.new ReceiverClass(bottom);
+    // :: error: (assignment)
+    Issue2432C.@B ReceiverClass ref2 = polyOuter.new ReceiverClass(top);
+
+    // lub tests
+    Issue2432C.@A ReceiverClass ref3 = polyOuter.new ReceiverClass(top, bottom);
+    // :: error: (assignment)
+    Issue2432C.@B ReceiverClass ref4 = polyOuter.new ReceiverClass(top, bottom);
+
+    Issue2432C.@F ReceiverClass ref5 = polyOuter.new ReceiverClass(bottom, bottom);
+  }
+
+  // invoke constructors with a type parameter to test poly resolving
+  void invokeTypeVarConstructors(@A Object top, @F Object bottom, @Poly Object poly) {
+    @F TypeParamClass<@Poly Object> ref1 = new TypeParamClass<>(bottom, poly);
+    // :: error: (assignment)
+    @B TypeParamClass<@Poly Object> ref2 = new TypeParamClass<>(top, poly);
+
+    // lub tests
+    @A TypeParamClass<@Poly Object> ref3 = new TypeParamClass<>(bottom, top, poly);
+    // :: error: (assignment)
+    @B TypeParamClass<@Poly Object> ref4 = new TypeParamClass<>(bottom, top, poly);
+
+    @F TypeParamClass<@Poly Object> ref5 = new TypeParamClass<>(bottom, bottom, poly);
+  }
+}
diff --git a/framework/tests/methodval/MethodNameTest.java b/framework/tests/methodval/MethodNameTest.java
new file mode 100644
index 0000000..5e3062b
--- /dev/null
+++ b/framework/tests/methodval/MethodNameTest.java
@@ -0,0 +1,39 @@
+import org.checkerframework.common.reflection.qual.MethodVal;
+
+public class MethodNameTest {
+  @MethodVal(className = "", methodName = "_MethodName", params = 0) Object o;
+
+  @MethodVal(className = "", methodName = "$methname", params = 0) Object o1;
+
+  @MethodVal(className = "", methodName = "Method_Name", params = 0) Object o2;
+
+  @MethodVal(className = "", methodName = "<init>", params = 0) Object o3;
+
+  // :: error: (illegal.methodname)
+  @MethodVal(className = "", methodName = "[]MethodName", params = 0) Object o4;
+  // :: error: (illegal.methodname)
+  @MethodVal(className = "", methodName = "Meht.name", params = 0) Object o5;
+  // :: error: (illegal.methodname)
+  @MethodVal(className = "", methodName = ".emethos", params = 0) Object o6;
+
+  @MethodVal(
+      className = "c",
+      methodName = "m",
+      params = {0, 0})
+  // :: error: (invalid.methodval)
+  Object o7;
+
+  @MethodVal(
+      className = "c",
+      methodName = {"m", "m"},
+      params = {0, 0})
+  // :: error: (invalid.methodval)
+  Object o8;
+
+  @MethodVal(
+      className = "c",
+      methodName = {"m", "m"},
+      params = {0})
+  // :: error: (invalid.methodval)
+  Object o9;
+}
diff --git a/framework/tests/methodval/MethodValInferenceTest.java b/framework/tests/methodval/MethodValInferenceTest.java
new file mode 100644
index 0000000..7f12d97
--- /dev/null
+++ b/framework/tests/methodval/MethodValInferenceTest.java
@@ -0,0 +1,103 @@
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import org.checkerframework.common.reflection.qual.ClassBound;
+import org.checkerframework.common.reflection.qual.ClassVal;
+import org.checkerframework.common.reflection.qual.MethodVal;
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class MethodValInferenceTest {
+  boolean flag = true;
+
+  public void testGetMethodParamLen(
+      Class<?> @ArrayLen(2) [] classArray2, Class<?>[] classArrayUnknown) throws Exception {
+    @StringVal("someMethod") String str = "someMethod";
+    @ClassVal("java.lang.Object") Class<?> c = Object.class;
+
+    @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m1 = c.getMethod("getA", new Class[] {});
+
+    @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m2 = c.getMethod("getA", (Class[]) null);
+
+    @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m3 = c.getMethod("someMethod", new Class[] {Integer.class});
+
+    @MethodVal(className = "java.lang.Object", methodName = "equals", params = 1) Method m4 = c.getMethod("equals", Object.class);
+
+    @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m5 = c.getMethod(str, int.class);
+
+    @MethodVal(className = "java.lang.Object", methodName = "getB", params = 0) Method m6 = c.getMethod("getB", new Class[0]);
+
+    @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m7 = c.getMethod(str, new Class[] {int.class});
+
+    @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m8 = c.getMethod(str, new Class[] {Integer.class, Integer.class});
+
+    @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m10 = c.getMethod(str, int.class, int.class);
+
+    @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m11 = c.getMethod(str, classArray2);
+
+    @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = -1) Method m12 = c.getMethod(str, classArrayUnknown);
+  }
+
+  public void testGetMethodMultiClassAndMethodNames(
+      @ClassVal({"java.lang.Object", "java.lang.String"}) Class<?> twoClasses,
+      @ClassVal({"java.lang.Object"}) Class<?> oneClass,
+      @StringVal({"method1"}) String oneName,
+      @StringVal({"method1", "method2"}) String twoNames,
+      Class<?> @ArrayLen(2) [] classArray2,
+      Class<?>[] classArrayUnknown)
+      throws Exception {
+
+    @MethodVal(
+        className = {"java.lang.Object"},
+        methodName = {"method1"},
+        params = -1)
+    Method m1 = oneClass.getMethod(oneName, classArrayUnknown);
+    @MethodVal(
+        className = {"java.lang.Object", "java.lang.Object"},
+        methodName = {"method1", "method2"},
+        params = {-1, -1})
+    Method m2 = oneClass.getMethod(twoNames, classArrayUnknown);
+    @MethodVal(
+        className = {"java.lang.Object", "java.lang.String"},
+        methodName = {"method1", "method1"},
+        params = {-1, -1})
+    Method m3 = twoClasses.getMethod(oneName, classArrayUnknown);
+    @MethodVal(
+        className = {
+          "java.lang.Object",
+          "java.lang.String",
+          "java.lang.Object",
+          "java.lang.String"
+        },
+        methodName = {"method1", "method2", "method2", "method1"},
+        params = {-1, -1, -1, -1})
+    Method m4 = twoClasses.getMethod(twoNames, classArrayUnknown);
+  }
+
+  @ClassBound("java.lang.Object") Class<?> classBound = Object.class;
+
+  public void testGetConstructorClassBound() throws Exception {
+    @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m1 = classBound.getMethod("getA", new Class[] {});
+  }
+
+  public void testGetConstructorClassBoundFail() throws Exception {
+    @MethodVal(className = "java.lang.Object", methodName = "<init>", params = 0)
+    // :: error: (assignment)
+    Constructor<?> con1 = classBound.getConstructor(new Class[] {}); // Should be @UnknownMethod
+  }
+
+  public void testGetConstructorParamLen(
+      Class<?> @ArrayLen(2) [] classArray2, Class<?>[] classArrayUnknown) throws Exception {
+    @ClassVal("java.lang.Object") Class<?> c = Object.class;
+    @MethodVal(className = "java.lang.Object", methodName = "<init>", params = 0) Constructor<?> con1 = c.getConstructor(new Class[] {});
+    @MethodVal(className = "java.lang.Object", methodName = "<init>", params = 0) Constructor<?> con2 = c.getConstructor((Class[]) null);
+    @MethodVal(className = "java.lang.Object", methodName = "<init>", params = 1) Constructor<?> con3 = c.getConstructor(new Class[] {Integer.class});
+    @MethodVal(className = "java.lang.Object", methodName = "<init>", params = 1) Constructor<?> con4 = c.getConstructor(Object.class);
+    @MethodVal(className = "java.lang.Object", methodName = "<init>", params = 1) Constructor<?> con5 = c.getConstructor(int.class);
+    @MethodVal(className = "java.lang.Object", methodName = "<init>", params = 0) Constructor<?> con6 = c.getConstructor(new Class[0]);
+    @MethodVal(className = "java.lang.Object", methodName = "<init>", params = 1) Constructor<?> con7 = c.getConstructor(new Class[] {int.class});
+    @MethodVal(className = "java.lang.Object", methodName = "<init>", params = 2) Constructor<?> con8 = c.getConstructor(new Class[] {Integer.class, Integer.class});
+    @MethodVal(className = "java.lang.Object", methodName = "<init>", params = 2) Constructor<?> con9 = c.getConstructor(int.class, int.class);
+    @MethodVal(className = "java.lang.Object", methodName = "<init>", params = 2) Constructor<?> con10 = c.getConstructor(classArray2);
+    @MethodVal(className = "java.lang.Object", methodName = "<init>", params = -1) Constructor<?> con11 = c.getConstructor(classArrayUnknown);
+  }
+}
diff --git a/framework/tests/methodval/MethodValLUBTest.java b/framework/tests/methodval/MethodValLUBTest.java
new file mode 100644
index 0000000..0bd928c
--- /dev/null
+++ b/framework/tests/methodval/MethodValLUBTest.java
@@ -0,0 +1,102 @@
+import java.lang.reflect.Method;
+import org.checkerframework.common.reflection.qual.MethodVal;
+
+public class MethodValLUBTest {
+  Object unknown = null;
+  boolean flag = false;
+
+  @MethodVal(className = "c1", methodName = "m1", params = 0) Object c1m10 = null;
+
+  @MethodVal(className = "c2", methodName = "m2", params = 1) Object c2m21 = null;
+
+  void basicLub() {
+    if (flag) {
+      unknown = c1m10;
+    } else {
+      unknown = c2m21;
+    }
+    @MethodVal(
+        className = {"c1", "c2"},
+        methodName = {"m1", "m2"},
+        params = {0, 1})
+    Object lub = unknown;
+    // :: error: (assignment)
+    c1m10 = unknown;
+    // :: error: (assignment)
+    c2m21 = unknown;
+  }
+
+  @MethodVal(className = "c1", methodName = "m1", params = 0) Object c1m10duplicate = null;
+
+  void lubSameType() {
+    if (flag) {
+      unknown = c1m10;
+    } else {
+      unknown = c1m10duplicate;
+    }
+    @MethodVal(className = "c1", methodName = "m1", params = 0) Object lub = unknown;
+  }
+
+  @MethodVal(className = "c1", methodName = "m1", params = 1) Object c1m11 = null;
+
+  void simalarSigLub() {
+    if (flag) {
+      unknown = c1m10;
+    } else {
+      unknown = c1m11;
+    }
+    @MethodVal(
+        className = {"c1", "c1"},
+        methodName = {"m1", "m1"},
+        params = {0, 1})
+    Object lub = unknown;
+    // :: error: (assignment)
+    c1m10 = unknown;
+    // :: error: (assignment)
+    c1m11 = unknown;
+  }
+
+  @MethodVal(
+      className = {"class", "class2"},
+      methodName = {"method", "method2"},
+      params = {0, 1})
+  Object classClass2Method0 = null;
+
+  @MethodVal(
+      className = {"class2", "class"},
+      methodName = {"method", "method2"},
+      params = {0, 1})
+  Object class2classMethod0 = null;
+
+  void setsLub() {
+    if (flag) {
+      unknown = classClass2Method0;
+    } else {
+      unknown = class2classMethod0;
+    }
+    @MethodVal(
+        className = {"class2", "class", "class", "class2"},
+        methodName = {"method", "method2", "method", "method2"},
+        params = {0, 1, 0, 1})
+    Object lub = unknown;
+    // :: error: (assignment)
+    classClass2Method0 = unknown;
+    // :: error: (assignment)
+    class2classMethod0 = unknown;
+  }
+
+  void inferedlubTest() throws Exception {
+    Class<MethodValInferenceTest> c = MethodValInferenceTest.class;
+    Method m;
+    if (flag) {
+      m = c.getMethod("getA", new Class[0]);
+    } else {
+      m = c.getMethod("getB", new Class[0]);
+    }
+    @MethodVal(
+        className = {"MethodValInferenceTest", "MethodValInferenceTest"},
+        methodName = {"getA", "getB"},
+        params = {0, 0})
+    Method lub = m;
+  }
+}
diff --git a/framework/tests/methodval/MethodValSubtypingTest.java b/framework/tests/methodval/MethodValSubtypingTest.java
new file mode 100644
index 0000000..4815980
--- /dev/null
+++ b/framework/tests/methodval/MethodValSubtypingTest.java
@@ -0,0 +1,69 @@
+import org.checkerframework.common.reflection.qual.MethodVal;
+
+public class MethodValSubtypingTest {
+  @MethodVal(className = "class", methodName = "method", params = 0) Object classMethod0 = null;
+
+  @MethodVal(className = "class", methodName = "method", params = 0) Object classMethod0Dup = null;
+
+  @MethodVal(
+      className = {"class", "class2"},
+      methodName = {"method", "method2"},
+      params = {0, 1})
+  Object classClass2Method0 = null;
+
+  @MethodVal(
+      className = {"class2", "class"},
+      methodName = {"method", "method2"},
+      params = {0, 1})
+  Object class2classMethod0 = null;
+
+  Object unknown = null;
+
+  void methodValSubtyping() {
+    classMethod0 = classMethod0Dup;
+    // :: error: (assignment)
+    classMethod0 = classClass2Method0;
+    // :: error: (assignment)
+    classClass2Method0 = class2classMethod0;
+    classClass2Method0 = classMethod0;
+  }
+
+  void bottomMethodVal() {
+    classMethod0 = null;
+    classClass2Method0 = null;
+  }
+
+  void unknownMethodVal1() {
+    unknown = class2classMethod0;
+  }
+
+  void unknownMethodVal2() {
+    // :: error: (assignment)
+    class2classMethod0 = unknown;
+  }
+
+  @MethodVal(
+      className = {"aclass", "aclass", "aclass"},
+      methodName = {"amethod", "amethod", "amethod"},
+      params = {0, 1, 2})
+  Object triple = null;
+
+  @MethodVal(
+      className = {"aclass", "aclass", "aclass"},
+      methodName = {"amethod", "amethod", "amethod"},
+      params = {2, 1, 0})
+  Object tripleAgain = null;
+
+  @MethodVal(
+      className = {"aclass"},
+      methodName = {"amethod"},
+      params = {2})
+  Object one = null;
+
+  void test() {
+    tripleAgain = triple;
+    // :: error: (assignment)
+    one = triple;
+    triple = one;
+  }
+}
diff --git a/framework/tests/nontopdefault/NTDTest.java b/framework/tests/nontopdefault/NTDTest.java
new file mode 100644
index 0000000..6800d8f
--- /dev/null
+++ b/framework/tests/nontopdefault/NTDTest.java
@@ -0,0 +1,70 @@
+import org.checkerframework.framework.testchecker.nontopdefault.qual.NTDMiddle;
+import org.checkerframework.framework.testchecker.nontopdefault.qual.NTDTop;
+
+// Testcase for Issue 948:
+// https://github.com/typetools/checker-framework/issues/948
+// @skip-test
+
+// Problem: @DefaultFor TypeUseLocation.RECEIVER is not applied to inner class constructor
+// receivers. The inner class constructor receivers currently take on the default qualifier of
+// the hierarchy. All other methods take on the default qualifier set by TypeUseLocation.RECEIVER.
+
+// DefaultQualifierInHierarchy is @NTDMiddle
+// DefaultFor receivers is @NTDTop
+
+public class NTDConstructorReceiverTest {
+
+  // default method receiver is @NTDTop
+  void DefaultMethodReceiver() {
+
+    // this line produces a methodref.receiver.bound error, but it shouldn't if the
+    // receiver for inner class constructors are properly applied
+    Demand<InnerDefaultReceiver> constructorReference = InnerDefaultReceiver::new;
+
+    // this line does not as the receiver is explicitly declared to be @NTDTop
+    Demand<InnerExplicitReceiver> constructorReference2 = InnerExplicitReceiver::new;
+  }
+
+  class InnerDefaultReceiver {
+    // takes on the default receiver for inner class constructor methods
+    InnerDefaultReceiver(NTDConstructorReceiverTest NTDConstructorReceiverTest.this) {
+      // The type of NTDConstructorReceiverTest.this should be @NTDTop.
+      // :: error: (assignment)
+      @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this;
+      // :: error: (assignment)
+      @NTDMiddle NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver thatInner = this;
+    }
+
+    void method() {
+      // The TypeUseLocation.RECEIVER only applies to the outermost type, so
+      // NTDConstructorReceiverTest.this is given the
+      @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this;
+      // :: error: (assignment)
+      @NTDMiddle InnerDefaultReceiver thatInner = this;
+    }
+
+    void explicit(@NTDTop NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver this) {
+      // :: error: (assignment)
+      @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this;
+      // :: error: (assignment)
+      @NTDMiddle NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver thatInner = this;
+    }
+  }
+
+  class InnerExplicitReceiver {
+    // explicitly set the receiver to be @NTDTop
+    InnerExplicitReceiver(@NTDTop NTDConstructorReceiverTest NTDConstructorReceiverTest.this) {
+      // :: error: (assignment)
+      @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this;
+    }
+
+    InnerExplicitReceiver(
+        @NTDMiddle NTDConstructorReceiverTest NTDConstructorReceiverTest.this, int i) {
+      @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this;
+    }
+  }
+}
+
+interface Demand<R> {
+  R supply();
+}
diff --git a/framework/tests/nontopdefault/TestCasting.java b/framework/tests/nontopdefault/TestCasting.java
new file mode 100644
index 0000000..d506e2b
--- /dev/null
+++ b/framework/tests/nontopdefault/TestCasting.java
@@ -0,0 +1,20 @@
+import org.checkerframework.framework.testchecker.nontopdefault.qual.NTDMiddle;
+
+@SuppressWarnings("inconsistent.constructor.type") // Not the point of this test
+public class TestCasting {
+  void repro(@NTDMiddle long startTime) {
+    try {
+      System.out.println("Inside try");
+      return;
+    } catch (Exception ex) {
+      long timeTaken = startTime;
+      @NTDMiddle double dblTimeTaken = timeTaken;
+
+      throw new IllegalArgumentException();
+    } finally {
+      long timeTaken2 = startTime;
+      // This assignment used to fail.
+      @NTDMiddle double dblTimeTaken2 = timeTaken2;
+    }
+  }
+}
diff --git a/framework/tests/purity-suggestions/PuritySuggestionsClass.java b/framework/tests/purity-suggestions/PuritySuggestionsClass.java
new file mode 100644
index 0000000..2d5c825
--- /dev/null
+++ b/framework/tests/purity-suggestions/PuritySuggestionsClass.java
@@ -0,0 +1,163 @@
+import org.checkerframework.dataflow.qual.Deterministic;
+import org.checkerframework.dataflow.qual.Pure;
+
+// various tests for the checker to automatically suggest pure methods (most methods have been
+// copied from Purity.java)
+
+public class PuritySuggestionsClass {
+
+  String f1, f2, f3;
+  String[] a;
+
+  // class with a (potentially) non-pure constructor
+  private static class NonPureClass {
+    String t;
+
+    public NonPureClass() {
+      t = "";
+    }
+  }
+
+  // class with a pure constructor
+  private static class PureClass {
+    // :: warning: (purity.more.sideeffectfree)
+    public PureClass() {}
+  }
+
+  // :: warning: (purity.more.pure)
+  void nonpure() {}
+
+  @Pure
+  String pure() {
+    return "";
+  }
+
+  String t3() {
+    nonpure();
+    return "";
+  }
+
+  // :: warning: (purity.more.pure)
+  String t4() {
+    pure();
+    return "";
+  }
+
+  // :: warning: (purity.more.pure)
+  int t5() {
+    int i = 1;
+    return i;
+  }
+
+  // :: warning: (purity.more.pure)
+  int t6() {
+    int j = 0;
+    for (int i = 0; i < 10; i++) {
+      j = j - i;
+    }
+    return j;
+  }
+
+  // :: warning: (purity.more.pure)
+  String t7() {
+    if (true) {
+      return "a";
+    }
+    return "";
+  }
+
+  // :: warning: (purity.more.pure)
+  int t8() {
+    return 1 - 2 / 3 * 2 % 2;
+  }
+
+  // :: warning: (purity.more.pure)
+  String t9() {
+    return "b" + "a";
+  }
+
+  String t10() {
+    f1 = "";
+    f2 = "";
+    return "";
+  }
+
+  String t11(PuritySuggestionsClass l) {
+    l.a[0] = "";
+    return "";
+  }
+
+  String t12(String[] s) {
+    s[0] = "";
+    return "";
+  }
+
+  String t13() {
+    PureClass p = new PureClass();
+    return "";
+  }
+
+  // :: warning: (purity.more.pure)
+  String t14() {
+    String i = "";
+    i = "a";
+    return i;
+  }
+
+  // :: warning: (purity.more.pure)
+  String t15() {
+    String[] s = new String[1];
+    return s[0];
+  }
+
+  // :: warning: (purity.more.sideeffectfree)
+  String t16() {
+    try {
+      int i = 1 / 0;
+    } catch (Throwable t) {
+      // ...
+    }
+    return "";
+  }
+
+  // :: warning: (purity.more.sideeffectfree)
+  String t16b() {
+    try {
+      int i = 1 / 0;
+    } catch (Throwable t) {
+      // ...
+    }
+    return "";
+  }
+
+  // :: warning: (purity.more.sideeffectfree)
+  String t16c() {
+    try {
+      int i = 1 / 0;
+    } catch (Throwable t) {
+      // ...
+    }
+    return "";
+  }
+
+  // :: warning: (purity.more.pure)
+  String t17() {
+    return "";
+  }
+
+  @Deterministic
+  // :: warning: (purity.more.sideeffectfree)
+  String t18() {
+    return "";
+  }
+
+  // :: warning: (purity.more.deterministic)
+  String t19() {
+    return t18();
+  }
+
+  String t12() {
+    NonPureClass p = new NonPureClass();
+    return "";
+  }
+}
diff --git a/framework/tests/reflection/AnonymousClassTest.java b/framework/tests/reflection/AnonymousClassTest.java
new file mode 100644
index 0000000..75b2412
--- /dev/null
+++ b/framework/tests/reflection/AnonymousClassTest.java
@@ -0,0 +1,128 @@
+import java.lang.reflect.Method;
+import org.checkerframework.framework.testchecker.reflection.qual.ReflectBottom;
+import org.checkerframework.framework.testchecker.reflection.qual.Sibling1;
+import org.checkerframework.framework.testchecker.reflection.qual.Sibling2;
+import org.checkerframework.framework.testchecker.reflection.qual.Top;
+
+public class AnonymousClassTest {
+  /**
+   * To build/run outside of the JUnit tests:
+   *
+   * <p>Build with $CHECKERFRAMEWOKR/framework/tests/build/ on the classpath. Need to either use
+   * Java 8 or the langtools compiler, because annotations on cast are used.
+   *
+   * <p>java AnonymousClassTest MyClass$1.getSib1() MyClass$1.setSib1() MyClass$1.setSib1()
+   * MyClass$1.setSib2() MyClass$1.setSib2() MyClass$1.getSib2()
+   */
+  public static void main(String[] args) {
+    AnonymousClassTest act = new AnonymousClassTest();
+    act.returnTypePass();
+    act.argumentTypePass();
+    act.argumentTypeFail();
+    act.returnTypeFail();
+  }
+
+  @Sibling1 int sibling1;
+  @Sibling2 int sibling2;
+
+  public void returnTypePass() {
+    try {
+      Class<?> c = Class.forName("AnonymousClassTest$1");
+      Method m = c.getMethod("getSib1", new Class[] {});
+      // TODO: Can we resolve anonymous classes?
+      // :: error: (assignment)
+      @Sibling1 Object a = m.invoke(anonymous, (@ReflectBottom Object[]) null);
+    } catch (Exception ignore) {
+      ignore.printStackTrace();
+    }
+  }
+
+  public void argumentTypePass() {
+    String str = "setSib1";
+    @Sibling1 int val1 = sibling1;
+    @Sibling1 Integer val2 = val1;
+    try {
+      Class<?> c = Class.forName("AnonymousClassTest$1");
+      Method m = c.getMethod(str, new Class[] {int.class});
+      // TODO: Can we resolve anonymous classes?
+      // :: error: (argument)
+      m.invoke(anonymous, val1);
+      // TODO: Can we resolve anonymous classes?
+      // :: error: (argument)
+      m.invoke(anonymous, val2);
+    } catch (Exception ignore) {
+      ignore.printStackTrace();
+    }
+  }
+
+  public void argumentTypeFail() {
+    String str = "setSib2";
+    @Sibling1 int val1 = sibling1;
+    @Sibling1 Integer val2 = val1;
+    try {
+      Class<?> c = Class.forName("AnonymousClassTest$1");
+      Method m = c.getMethod(str, new Class[] {int.class});
+      // :: error: (argument)
+      m.invoke(anonymous, val1);
+      // :: error: (argument)
+      m.invoke(anonymous, val2);
+    } catch (Exception ignore) {
+      ignore.printStackTrace();
+    }
+  }
+
+  public void returnTypeFail() {
+    try {
+      Class<?> c = Class.forName("AnonymousClassTest$1");
+      Method m = c.getMethod("getSib2", new Class[] {});
+      // :: error: (assignment)
+      @Sibling1 Object a = m.invoke(anonymous, (@ReflectBottom Object[]) null);
+    } catch (Exception ignore) {
+      ignore.printStackTrace();
+    }
+  }
+
+  public @ReflectBottom MyClass anonymous =
+      // :: warning: (cast.unsafe.constructor.invocation)
+      new @ReflectBottom MyClass() {
+
+        public @Sibling1 int getSib1() {
+          System.out.println("MyClass$1.getSib1()");
+          return 1;
+        }
+
+        public @Sibling2 int getSib2() {
+          System.out.println("MyClass$1.getSib2()");
+          return 1;
+        }
+
+        public void setSib1(@Sibling1 int a) {
+          System.out.println("MyClass$1.setSib1()");
+        }
+
+        public void setSib2(@Sibling2 int a) {
+          System.out.println("MyClass$1.setSib2()");
+        }
+      };
+
+  class MyClass {
+
+    public @Top int getSib1() {
+      System.out.println("MyClass.getSib1()");
+      return 1;
+    }
+
+    public @Top int getSib2() {
+      System.out.println("MyClass.getSib1()");
+      return 1;
+    }
+
+    public void setSib1(@ReflectBottom int a) {
+      System.out.println("MyClass.setSib1()");
+    }
+
+    public void setSib2(@ReflectBottom int a) {
+      System.out.println("MyClass.setSib2()");
+    }
+  }
+}
diff --git a/framework/tests/reflection/MethodTest.java b/framework/tests/reflection/MethodTest.java
new file mode 100644
index 0000000..6d1b50a
--- /dev/null
+++ b/framework/tests/reflection/MethodTest.java
@@ -0,0 +1,395 @@
+import java.lang.reflect.Method;
+import org.checkerframework.framework.testchecker.reflection.qual.ReflectBottom;
+import org.checkerframework.framework.testchecker.reflection.qual.Sibling1;
+import org.checkerframework.framework.testchecker.reflection.qual.Sibling2;
+import org.checkerframework.framework.testchecker.reflection.qual.Top;
+
+public class MethodTest {
+
+  @Sibling1 int sibling1;
+  @Sibling2 int sibling2;
+  @ReflectBottom SuperClass superClass;
+
+  public void real_class() {
+    try {
+      Class<?> c = Object.class;
+      Method m = c.getMethod("equals", Object.class);
+      Object rec = new Object();
+      Object param = new Object();
+      Boolean other = (Boolean) rec.equals(param);
+      Boolean equals = (Boolean) m.invoke(rec, param);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void pass1() {
+    try {
+      Class<?> c = Class.forName("MethodTest$SuperClass");
+      Method m = c.getMethod("getA", new Class[] {});
+      @Sibling1 Object a = m.invoke(superClass, (@ReflectBottom Object[]) null);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void pass1b() {
+    try {
+      Class<?> c = Class.forName("MethodTest$SuperClass");
+      Method m = c.getMethod("getA", (Class[]) null);
+      @Sibling1 Object a = m.invoke(superClass, (@ReflectBottom Object[]) null);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void pass2() {
+    String str = "get" + "A";
+    try {
+      Class<?> c = Class.forName("MethodTest$SuperClass");
+      Method m = c.getMethod(str, new Class[] {});
+      @Sibling1 Object a = m.invoke(superClass, (@ReflectBottom Object[]) null);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void pass3() {
+    String str = "get";
+    str += "A";
+    try {
+      Class<?> c = Class.forName("MethodTest$SuperClass");
+      Method m = c.getMethod(str, new Class[] {});
+      // TODO: Should not fail -> enhance Value checker
+      // and remove the expected error
+
+      // :: error: (assignment)
+      @Sibling1 Object a = m.invoke(superClass, (@ReflectBottom Object[]) null);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void pass4() {
+    String str = "setA";
+    @Sibling1 int val1 = sibling1;
+    @Sibling1 Integer val2 = val1;
+    try {
+      Class<?> c = Class.forName("MethodTest$SuperClass");
+      Method m = c.getMethod(str, new Class[] {Integer.class});
+      m.invoke(superClass, val1);
+      m.invoke(superClass, val2);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void pass4b() {
+    String str = "setA";
+    @Sibling1 int val1 = sibling1;
+    @Sibling1 Integer val2 = val1;
+    try {
+      //
+      Class<?> c = Class.forName("MethodTest$SuperClass");
+      Method m = c.getMethod(str, int.class);
+      m.invoke(superClass, val1);
+      m.invoke(superClass, val2);
+    } catch (Exception ignore) {
+    }
+  }
+
+  @ReflectBottom SubClass subClass;
+  // Test resolution of methods declared in super class
+  public void pass5() {
+    try {
+      Class<?> c = Class.forName("MethodTest$SubClass");
+      Method m = c.getMethod("getB", new Class[0]);
+      @Sibling2 Object o = m.invoke(subClass, (@ReflectBottom Object[]) null);
+    } catch (Exception ignore) {
+    }
+  }
+
+  // Test resolution of static methods
+  public void pass6() {
+    try {
+      Class<?> c = MethodTest.class;
+      Method m = c.getMethod("convertSibling2ToSibling1", new Class[] {Integer.class});
+      @Sibling1 Object o = m.invoke(null, sibling2);
+    } catch (Exception ignore) {
+    }
+  }
+
+  // Test primitives
+  public void pass7() {
+    try {
+      Class<?> c = MethodTest.class;
+      Method m = c.getMethod("convertSibling2ToSibling1", new Class[] {int.class});
+      @Sibling1 Object o = m.invoke(null, sibling2);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void pass8() {
+    String str = "setA";
+    try {
+      Class<?> c = Class.forName("MethodTest$SuperClass");
+      Method m = c.getMethod(str, new Class[] {Integer.class});
+      m.invoke(superClass, sibling1);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void pass9() {
+    String str = "getA";
+    if (true) {
+      str = "getB";
+    }
+    try {
+      Class<?> c = Class.forName("MethodTest$SubClass");
+      Method m = c.getMethod(str, new Class[0]);
+      @Top Object o = m.invoke(subClass, (@ReflectBottom Object[]) null);
+    } catch (Exception ignore) {
+    }
+  }
+
+  // Test getClass()
+  public void pass10() {
+    SuperClass inst = new SubClass();
+    try {
+      Class<?> c = inst.getClass();
+      Method m = c.getMethod("getA", new Class[0]);
+      @Sibling1 Object o = m.invoke(inst, (@ReflectBottom Object[]) null);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void pass11() {
+    try {
+      Class<?> c = this.getClass();
+      Method m = c.getMethod("convertSibling2ToSibling1", new Class[] {Integer.class});
+      @Sibling1 Object o = m.invoke(null, sibling2);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void pass11b() {
+    try {
+      Class<?> c = getClass();
+      Method m = c.getMethod("convertSibling2ToSibling1", new Class[] {Integer.class});
+      @Sibling1 Object o = m.invoke(null, sibling2);
+    } catch (Exception ignore) {
+    }
+  }
+
+  // Test .class on inner class
+  public void pass12() {
+    try {
+      Class<?> c = SuperClass.class;
+      Method m = c.getMethod("getA", new Class[0]);
+      @Sibling1 Object o = m.invoke(new SuperClass(), new @ReflectBottom Object @ReflectBottom [0]);
+    } catch (Exception ignore) {
+    }
+  }
+
+  boolean flag = false;
+  // Test lub of return types
+  public void testLubReturnPass() {
+    try {
+      Class<?> c = Class.forName("MethodTest$SuperClass");
+      Method m;
+      if (flag) {
+        m = c.getMethod("getA", new Class[0]);
+      } else {
+        m = c.getMethod("getB", new Class[0]);
+      }
+      @Top Object o = m.invoke(new SuperClass(), new @ReflectBottom Object @ReflectBottom [0]);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void testLubReturnFail() {
+    try {
+      Class<?> c = Class.forName("MethodTest$SuperClass");
+      Method m;
+      if (flag) {
+        m = c.getMethod("getA", new Class[0]);
+      } else {
+        m = c.getMethod("getB", new Class[0]);
+      }
+      // :: error: (assignment)
+      @ReflectBottom Object o = m.invoke(new SuperClass(), new @ReflectBottom Object @ReflectBottom [0]);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void test() {}
+
+  public void fail1() {
+    try {
+      Class<?> c = MethodTest.class;
+      Method m = c.getMethod("convertSibling2ToSibling1", new Class[] {Integer.class});
+      // :: error: (argument)
+      Object o = m.invoke(null, sibling1);
+    } catch (Exception ignore) {
+    }
+  }
+
+  // Test unresolvable methods
+  public void fail2(String str) {
+    try {
+      Class<?> c = Class.forName(str);
+      Method m = c.getMethod("getA", new Class[] {Integer.class});
+      // :: error: (assignment)
+      @Sibling1 Object o = m.invoke(subClass, (@ReflectBottom Object[]) null);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void fail3() {
+    String str = "setB";
+    try {
+      Class<?> c = Class.forName("MethodTest$SuperClass");
+      Method m = c.getMethod(str, new Class[] {Integer.class});
+      // :: error: (argument)
+      m.invoke(this, sibling1);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void fail4() {
+    String str = "setA";
+    try {
+      Class<?> c = Class.forName("MethodTest$SubClass");
+      Method m = c.getMethod(str, new Class[] {Integer.class});
+      // :: error: (argument)
+      m.invoke(this, new Object[] {sibling2});
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void fail5() {
+    String str = "setAB";
+    try {
+      Class<?> c = Class.forName("MethodTest$SubClass");
+      Method m = c.getMethod(str, new Class[] {Integer.class, Integer.class});
+      // :: error: (argument)
+      m.invoke(this, new Object[] {sibling1, sibling2});
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void fail6() {
+    String str = "setA";
+    if (true) {
+      str = "setB";
+    }
+    try {
+      Class<?> c = Class.forName("MethodTest$SubClass");
+      Method m = c.getMethod(str, new Class[] {Integer.class});
+      // :: error: (argument)
+      m.invoke(this, new Object[] {sibling1});
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void fail7() {
+    // :: warning: (cast.unsafe.constructor.invocation)
+    @Sibling2 MethodTest inst = new @Sibling2 MethodTest();
+    try {
+      Class<?> c = MethodTest.class;
+      Method m = c.getMethod("convertSibling2ToSibling1", new Class[] {Integer.class});
+      @Sibling1 Object o = m.invoke(inst, sibling2);
+    } catch (Exception ignore) {
+    }
+  }
+
+  // Test method call that cannot be uniquely resolved
+  public void fail8() {
+    try {
+      Class<?> c = SuperClass.class;
+      Method m = c.getMethod("setC", new Class[] {Integer.class});
+      // :: error: (argument)
+      Object o = m.invoke(new SuperClass(), new Object[] {sibling2});
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void bug() {
+    String str = "setA";
+    @Sibling1 int val1 = sibling1;
+    @Sibling1 Object[] args = new Object[] {val1};
+    try {
+      //
+      Class<?> c = Class.forName("MethodTest$SuperClass");
+
+      Method m = c.getMethod(str, int.class);
+      // This error is a bug.
+      // See DefaultReflectionResolver.resolveMethodCall(...)
+      // for details.
+      // :: error: (argument)
+      m.invoke(this, args);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void bug2() {
+    String str = "setAB";
+    @Sibling1 int val1 = sibling1;
+    @Sibling2 int val2 = sibling2;
+
+    Object[] args = new Object[] {val1, val2};
+    try {
+      //
+      Class<?> c = Class.forName("MethodTest$SuperClass");
+      Method m = c.getMethod(str, int.class, int.class);
+      // This error is a bug.
+      // See DefaultReflectionResolver.resolveMethodCall(...)
+      // for details.
+      // :: error: (argument)
+      m.invoke(this, args);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public static @Sibling1 int convertSibling2ToSibling1(@Sibling2 int a) {
+    return (@Sibling1 int) 1;
+  }
+
+  // TODO: Does the testing framework somehow support the compilation of
+  // multiple files at the same time?
+  private class SubClass extends SuperClass {}
+
+  private class SuperClass {
+    private @Sibling1 int a;
+    private @Sibling2 int b;
+    private @Sibling1 Integer c;
+
+    public SuperClass() {
+      this.a = sibling1;
+      this.b = sibling2;
+    }
+
+    public @Sibling1 int getA() {
+      return a;
+    }
+
+    public void setA(@Sibling1 int a) {
+      this.a = a;
+    }
+
+    public @Sibling2 int getB() {
+      return b;
+    }
+
+    public void setB(@Sibling2 int b) {
+      this.b = b;
+    }
+
+    public void setAB(@Sibling1 int a, @Sibling2 int b) {
+      this.a = a;
+      this.b = b;
+    }
+
+    public void setC(@Sibling1 int c) {
+      this.c = c;
+    }
+
+    public void setC(@Sibling1 Integer c) {
+      this.c = c;
+    }
+  }
+}
diff --git a/framework/tests/reflection/ReflectionConstructorTest.java b/framework/tests/reflection/ReflectionConstructorTest.java
new file mode 100644
index 0000000..6be32b8
--- /dev/null
+++ b/framework/tests/reflection/ReflectionConstructorTest.java
@@ -0,0 +1,68 @@
+import java.lang.reflect.Constructor;
+import org.checkerframework.framework.testchecker.reflection.qual.Sibling1;
+import org.checkerframework.framework.testchecker.reflection.qual.Sibling2;
+import org.checkerframework.framework.testchecker.reflection.qual.Top;
+
+public class ReflectionConstructorTest {
+  @Sibling1 int sibling1;
+  @Sibling2 int sibling2;
+
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  public @Sibling1 ReflectionConstructorTest(@Sibling1 int a) {}
+
+  // :: error: (super.invocation) :: warning: (inconsistent.constructor.type)
+  public @Sibling2 ReflectionConstructorTest(@Sibling2 int a, @Sibling2 int b) {}
+
+  public void pass1() {
+    try {
+      Class<?> c = Class.forName("ReflectionConstructorTest");
+      Constructor<?> init = c.getConstructor(new Class<?>[] {Integer.class});
+      @Sibling1 int i = sibling1;
+      @Sibling1 Object o = init.newInstance(i);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void pass2() {
+    try {
+      Class<?> c = Class.forName("ReflectionConstructorTest");
+      Constructor<?> init = c.getConstructor(new Class<?>[] {Integer.class, Integer.class});
+      @Sibling2 int a = sibling2;
+      int b = a;
+      @Top Object inst = init.newInstance(a, b);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void fail1() {
+    try {
+      Class<?> c = ReflectionConstructorTest.class;
+      Constructor<?> init = c.getConstructor(new Class<?>[] {Integer.class});
+      // :: error: (argument)
+      Object o = init.newInstance(sibling2);
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void fail2() {
+    try {
+      Class<?> c = ReflectionConstructorTest.class;
+      Constructor<?> init = c.getConstructor(new Class<?>[] {Integer.class});
+      // :: error: (argument) :: error: (assignment)
+      @Sibling1 Object o = init.newInstance(new Object[] {sibling2});
+    } catch (Exception ignore) {
+    }
+  }
+
+  public void fail3() {
+    try {
+      Class<?> c = Class.forName("ReflectionConstructorTest");
+      Constructor<?> init = c.getConstructor(new Class<?>[] {Integer.class, Integer.class});
+      @Sibling2 int a = sibling2;
+      @Sibling1 int b = sibling1;
+      // :: error: (argument)
+      @Sibling2 Object inst = init.newInstance(a, b);
+    } catch (Exception ignore) {
+    }
+  }
+}
diff --git a/framework/tests/reflection/reflection.astub b/framework/tests/reflection/reflection.astub
new file mode 100644
index 0000000..391d47d
--- /dev/null
+++ b/framework/tests/reflection/reflection.astub
@@ -0,0 +1,11 @@
+import org.checkerframework.framework.testchecker.reflection.qual.*;
+
+package java.lang.reflect;
+
+class Method {
+   @Top Object invoke(@Top Method this, @ReflectBottom Object obj, @ReflectBottom Object @Top [] args);
+}
+
+class Constructor<T> {
+  @Top Object newInstance(@Top Constructor<T> this, @ReflectBottom Object @Top [] args);
+}
diff --git a/framework/tests/report/Accesses.java b/framework/tests/report/Accesses.java
new file mode 100644
index 0000000..b41cb0c
--- /dev/null
+++ b/framework/tests/report/Accesses.java
@@ -0,0 +1,62 @@
+import org.checkerframework.common.util.report.qual.*;
+
+public class Accesses {
+  class Demo {
+    @ReportReadWrite Object read;
+
+    @ReportWrite Object write;
+
+    @ReportCall
+    Object foo(Object p) {
+      return null;
+    }
+
+    void implicitRead() {
+      // :: error: (fieldreadwrite)
+      Object o = read;
+      // A read counts as access
+      // :: error: (fieldreadwrite)
+      read = null;
+      // :: error: (fieldreadwrite)
+      read.toString();
+    }
+
+    void implicitWrite() {
+      Object o = write;
+      // :: error: (fieldwrite)
+      write = null;
+      write.toString();
+    }
+
+    void implicitMethod() {
+      // :: error: (methodcall)
+      foo(null);
+      // :: error: (methodcall)
+      equals(foo(null));
+    }
+  }
+
+  void accessesRead(Demo d) {
+    // :: error: (fieldreadwrite)
+    Object o = d.read;
+    // A read counts as access
+    // :: error: (fieldreadwrite)
+    d.read = null;
+    // :: error: (fieldreadwrite)
+    d.read.toString();
+  }
+
+  void accessesWrite(Demo d) {
+    Object o = d.write;
+    // :: error: (fieldwrite)
+    d.write = null;
+    d.write.toString();
+  }
+
+  void accessesMethod(Demo d) {
+    // :: error: (methodcall)
+    d.foo(null);
+    // :: error: (methodcall)
+    d.equals(d.foo(null));
+  }
+}
diff --git a/framework/tests/report/CallOverrides.java b/framework/tests/report/CallOverrides.java
new file mode 100644
index 0000000..d50e289
--- /dev/null
+++ b/framework/tests/report/CallOverrides.java
@@ -0,0 +1,33 @@
+import org.checkerframework.common.util.report.qual.*;
+
+public class CallOverrides {
+  class A {
+    void m() {}
+  }
+
+  class B extends A {
+    @ReportCall
+    void m() {}
+  }
+
+  class C extends B {}
+
+  void test() {
+    C c = new C();
+
+    // :: error: (methodcall)
+    c.m();
+
+    B b = c;
+
+    // :: error: (methodcall)
+    b.m();
+
+    A a = c;
+
+    // This call is not reported, because we statically
+    // don't know that one of the subtypes has the ReportCall
+    // annotation.
+    a.m();
+  }
+}
diff --git a/framework/tests/report/Creation.java b/framework/tests/report/Creation.java
new file mode 100644
index 0000000..e5ce5f0
--- /dev/null
+++ b/framework/tests/report/Creation.java
@@ -0,0 +1,35 @@
+import org.checkerframework.common.util.report.qual.*;
+
+public class Creation {
+  class TestOne {
+    TestOne() {}
+
+    @ReportCreation
+    TestOne(int i) {}
+  }
+
+  @ReportCreation
+  class TestAll {
+    TestAll() {}
+
+    TestAll(int i) {}
+  }
+
+  void test() {
+    // :: error: (creation)
+    new TestAll();
+    // :: error: (creation)
+    new TestAll(4);
+
+    new TestOne();
+    // :: error: (creation)
+    new TestOne(4);
+  }
+
+  class TestSub extends TestAll {}
+
+  void testSub() {
+    // :: error: (creation)
+    new TestSub();
+  }
+}
diff --git a/framework/tests/report/Inherit.java b/framework/tests/report/Inherit.java
new file mode 100644
index 0000000..f413bb5
--- /dev/null
+++ b/framework/tests/report/Inherit.java
@@ -0,0 +1,14 @@
+import org.checkerframework.common.util.report.qual.*;
+
+public class Inherit {
+  @ReportInherit
+  interface A {}
+
+  class B {}
+
+  // :: error: (inherit)
+  class C extends B implements A {}
+
+  // :: error: (inherit)
+  class D extends C {}
+}
diff --git a/framework/tests/report/Interface.java b/framework/tests/report/Interface.java
new file mode 100644
index 0000000..4475f98
--- /dev/null
+++ b/framework/tests/report/Interface.java
@@ -0,0 +1,42 @@
+// Test case for Issue 658:
+// https://github.com/typetools/checker-framework/issues/658
+// @skip-test
+
+import org.checkerframework.common.util.report.qual.*;
+
+public class Interface {
+  interface A {
+    @ReportCall
+    boolean equals(Object o);
+
+    @ReportCall
+    void mine();
+  }
+
+  class B implements A {
+    public void mine() {}
+  }
+
+  interface C extends A {}
+
+  void foo(A a, B b, C c, Object o) {
+    // :: error: (methodcall)
+    if (a.equals(o)) {}
+    // :: error: (methodcall)
+    if (b.equals(o)) {}
+    // :: error: (methodcall)
+    if (c.equals(o)) {}
+
+    // Don't report this call.
+    if (o.equals(a)) {}
+  }
+
+  void bar(A a, B b, C c, Object o) {
+    // :: error: (methodcall)
+    a.mine();
+    // :: error: (methodcall)
+    b.mine();
+    // :: error: (methodcall)
+    c.mine();
+  }
+}
diff --git a/framework/tests/report/Overrides.java b/framework/tests/report/Overrides.java
new file mode 100644
index 0000000..e018c69
--- /dev/null
+++ b/framework/tests/report/Overrides.java
@@ -0,0 +1,26 @@
+import org.checkerframework.common.util.report.qual.*;
+
+public class Overrides {
+  class A {
+    void m() {}
+  }
+
+  class B extends A {
+    @ReportOverride
+    void m() {}
+  }
+
+  class C extends B {
+    // :: error: (override)
+    void m() {}
+  }
+
+  // No explicit override -> no message.
+  class D extends B {}
+
+  class E extends A {
+    // Overrides method on same level as B.m
+    // -> no message.
+    void m() {}
+  }
+}
diff --git a/framework/tests/report/Package.java b/framework/tests/report/Package.java
new file mode 100644
index 0000000..3fdd14d
--- /dev/null
+++ b/framework/tests/report/Package.java
@@ -0,0 +1,49 @@
+import java.util.regex.*;
+
+// File reporttest.astub contains an annotation on
+// the java.util.regex package.
+
+// :: error: (usage)
+public class Package extends PatternSyntaxException {
+  public Package(String desc, String regex, int index) {
+    // :: error: (usage)
+    super(desc, regex, index);
+  }
+
+  @Override
+  @org.checkerframework.dataflow.qual.Pure
+  public String getPattern() {
+    // :: error: (usage)
+    return super.getPattern();
+  }
+
+  // :: error: (usage)
+  void m(Pattern p) {
+    // Access to a constant.
+    // :: error: (usage)
+    int i = Pattern.CANON_EQ;
+
+    // Use of inherited method.
+    // :: error: (usage)
+    String msg = getMessage();
+
+    // No report for use of overridden method -
+    // we get a message when we call super in the overriding method.
+    // TODO: Would we want "transitive" behavior? I.e. a few levels higher
+    // in the inheritance hierarchy we could see the class to report.
+    String pat = this.getPattern();
+
+    try {
+      // :: error: (usage)
+      p.compile("test(((");
+    } catch (Package pe) {
+      // We don't look at supertypes of the types we analyze.
+      // TODO: Should we?
+      System.out.println("OK!");
+      // :: error: (usage)
+    } catch (PatternSyntaxException pse) {
+      // We do get a report for direct uses.
+      System.out.println("Ha!");
+    }
+  }
+}
diff --git a/framework/tests/report/TestStub.java b/framework/tests/report/TestStub.java
new file mode 100644
index 0000000..7249385
--- /dev/null
+++ b/framework/tests/report/TestStub.java
@@ -0,0 +1,9 @@
+public class TestStub {
+  void demo() {
+    try {
+      // :: error: (methodcall)
+      Class.forName("Evil");
+    } catch (Exception e) {
+    }
+  }
+}
diff --git a/framework/tests/report/reporttest.astub b/framework/tests/report/reporttest.astub
new file mode 100644
index 0000000..aa66e28
--- /dev/null
+++ b/framework/tests/report/reporttest.astub
@@ -0,0 +1,11 @@
+import org.checkerframework.common.util.report.qual.*;
+
+package java.lang;
+
+class Class<T> {
+      @ReportCall
+     Class<?> forName(String s);
+}
+
+@ReportUse
+package java.util.regex;
diff --git a/framework/tests/reportmodifiers/TestModifiers.java b/framework/tests/reportmodifiers/TestModifiers.java
new file mode 100644
index 0000000..f645645
--- /dev/null
+++ b/framework/tests/reportmodifiers/TestModifiers.java
@@ -0,0 +1,12 @@
+/**
+ * Reported modifiers depend on the command-line invocation; see
+ * org.checkerframework.checker/tests/src/tests/ReportModifiers.java
+ */
+public class TestModifiers {
+  void test() {
+    class Inner {
+      // :: error: (Modifier.native)
+      native void bad();
+    }
+  }
+}
diff --git a/framework/tests/reporttreekinds/TestTreeKinds.java b/framework/tests/reporttreekinds/TestTreeKinds.java
new file mode 100644
index 0000000..dcf18ae
--- /dev/null
+++ b/framework/tests/reporttreekinds/TestTreeKinds.java
@@ -0,0 +1,11 @@
+/**
+ * Reported tree kinds depend on the command-line invocation; see
+ * org.checkerframework.checker/tests/src/tests/ReportTreeKindsTest.java
+ */
+public class TestTreeKinds {
+  void test(boolean a, boolean b) {
+    // :: error: (Tree.Kind.WHILE_LOOP) :: error: (Tree.Kind.CONDITIONAL_AND)
+    while (a && b) {}
+    if (b) {}
+  }
+}
diff --git a/framework/tests/returnsreceiver/GenericReturn.java b/framework/tests/returnsreceiver/GenericReturn.java
new file mode 100644
index 0000000..0e32739
--- /dev/null
+++ b/framework/tests/returnsreceiver/GenericReturn.java
@@ -0,0 +1,33 @@
+import org.checkerframework.common.returnsreceiver.qual.This;
+
+public class GenericReturn {
+
+  abstract static class Builder<B extends Builder<?>> {
+    abstract @This B setFoo(String foo);
+
+    @SuppressWarnings("unchecked")
+    @This B retThis() {
+      return (@This B) this;
+    }
+
+    @This B dontRetThis() {
+      // :: error: return
+      return null;
+    }
+  }
+
+  static class Builder1 extends Builder<Builder1> {
+
+    @This Builder1 setFoo(String foo) {
+      return this;
+    }
+  }
+
+  static class Builder2 extends Builder<Builder2> {
+
+    @This Builder2 setFoo(String foo) {
+      // :: error: return
+      return null;
+    }
+  }
+}
diff --git a/framework/tests/returnsreceiver/MethodRef.java b/framework/tests/returnsreceiver/MethodRef.java
new file mode 100644
index 0000000..f30b6dd
--- /dev/null
+++ b/framework/tests/returnsreceiver/MethodRef.java
@@ -0,0 +1,26 @@
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+public class MethodRef {
+
+  @This MethodRef set(Object o) {
+    return this;
+  }
+
+  interface Setter {
+    @This Object consume(Object p);
+  }
+
+  // :: error: methodref.receiver.bound
+  Setter co = this::set;
+
+  void doNothing(@This MethodRef this) {}
+
+  interface Fun {
+    void run(@This Fun this);
+  }
+
+  // The error here is a false positive, due to
+  // https://github.com/typetools/checker-framework/issues/2931
+  // :: error: methodref.receiver.bound
+  Fun f = this::doNothing;
+}
diff --git a/framework/tests/returnsreceiver/NullsAndGenerics.java b/framework/tests/returnsreceiver/NullsAndGenerics.java
new file mode 100644
index 0000000..6ddceec
--- /dev/null
+++ b/framework/tests/returnsreceiver/NullsAndGenerics.java
@@ -0,0 +1,34 @@
+import java.util.Map;
+
+public class NullsAndGenerics<E> {
+
+  private boolean enableProtoAnnotations;
+
+  @SuppressWarnings("unchecked")
+  private <T, O extends Message, E extends ProtoElement> T getProtoExtension(
+      E element, GeneratedExtension<O, T> extension) {
+    // Use this method as the chokepoint for all field annotations processing, so we can
+    // toggle on/off annotations processing in one place.
+    if (!enableProtoAnnotations) {
+      return null;
+    }
+    return (T) element.getOptionFields().get(extension.getDescriptor());
+  }
+
+  // stubs of relevant classes
+  private class Message {}
+
+  private class ProtoElement {
+    public Map<FieldDescriptor, Object> getOptionFields() {
+      return null;
+    }
+  }
+
+  private class FieldDescriptor {}
+
+  private class GeneratedExtension<O, T> {
+    public FieldDescriptor getDescriptor() {
+      return null;
+    }
+  }
+}
diff --git a/framework/tests/returnsreceiver/OverrideTest.java b/framework/tests/returnsreceiver/OverrideTest.java
new file mode 100644
index 0000000..abcf99b
--- /dev/null
+++ b/framework/tests/returnsreceiver/OverrideTest.java
@@ -0,0 +1,41 @@
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+// Test basic subtyping relationships for the Returns Receiver Checker.
+public class OverrideTest {
+
+  static class Super {
+
+    @This Super retThis() {
+      return this;
+    }
+
+    Super retWhatever() {
+      return null;
+    }
+  }
+
+  static class Sub extends Super {
+
+    @Override
+    // :: error: override.return
+    Super retThis() {
+      return null;
+    }
+
+    @Override
+    // we do not support this case for now; would need to write explicit @This on receiver in
+    // superclass
+    // :: error: override.receiver
+    @This Super retWhatever() {
+      return this;
+    }
+  }
+
+  static class Sub2 extends Super {
+
+    @Override
+    @This Sub2 retThis() {
+      return this;
+    }
+  }
+}
diff --git a/framework/tests/returnsreceiver/SimpleTest.java b/framework/tests/returnsreceiver/SimpleTest.java
new file mode 100644
index 0000000..5d86c15
--- /dev/null
+++ b/framework/tests/returnsreceiver/SimpleTest.java
@@ -0,0 +1,68 @@
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+// Test basic subtyping relationships for the Returns Receiver Checker.
+public class SimpleTest {
+
+  @This SimpleTest retNull() {
+    // :: error: return
+    return null;
+  }
+
+  @This SimpleTest retThis() {
+    return this;
+  }
+
+  @This SimpleTest retThisWrapper(@UnknownThis SimpleTest other, boolean flag) {
+    if (flag) {
+      // :: error: return
+      return other.retThis();
+    } else {
+      return this.retThis();
+    }
+  }
+
+  @This SimpleTest retLocalThis() {
+    SimpleTest x = this;
+    return x;
+  }
+
+  @This SimpleTest retNewLocal() {
+    SimpleTest x = new SimpleTest();
+    // :: error: return
+    return x;
+  }
+
+  // :: error: this.location
+  @This SimpleTest thisOnParam(@This SimpleTest x) {
+    return x;
+  }
+
+  void thisOnLocal() {
+    // :: error: this.location
+    // :: error: assignment
+    @This SimpleTest x = new SimpleTest();
+
+    // :: error: this.location
+    // :: error: type.argument
+    java.util.List<@This String> l = null;
+  }
+
+  // can write @This on receiver
+  void thisOnReceiver(@This SimpleTest this) {}
+
+  // :: error: this.location :: error: invalid.polymorphic.qualifier.use
+  @This Object f;
+
+  interface I {
+
+    Object foo();
+
+    SimpleTest.@This I setBar();
+  }
+
+  // :: error: this.location
+  static @This Object thisOnStatic() {
+    // :: error: return
+    return new Object();
+  }
+}
diff --git a/framework/tests/returnsreceiver/SubtypingTest.java b/framework/tests/returnsreceiver/SubtypingTest.java
new file mode 100644
index 0000000..425d837
--- /dev/null
+++ b/framework/tests/returnsreceiver/SubtypingTest.java
@@ -0,0 +1,12 @@
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+// Test basic subtyping relationships for the Returns Receiver Checker.
+public class SubtypingTest {
+  void allSubtypingRelationships(@UnknownThis int x, @BottomThis int y) {
+    @UnknownThis int a = x;
+    @UnknownThis int b = y;
+    // :: error: assignment
+    @BottomThis int c = x; // expected error on this line
+    @BottomThis int d = y;
+  }
+}
diff --git a/framework/tests/returnsreceiverautovalue/Animal.java b/framework/tests/returnsreceiverautovalue/Animal.java
new file mode 100644
index 0000000..806c555
--- /dev/null
+++ b/framework/tests/returnsreceiverautovalue/Animal.java
@@ -0,0 +1,74 @@
+import com.google.auto.value.AutoValue;
+import org.checkerframework.checker.nullness.qual.*;
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+/**
+ * Adapted from the standard AutoValue example code:
+ * https://github.com/google/auto/blob/master/value/userguide/builders.md
+ */
+@AutoValue
+abstract class Animal {
+  abstract String name();
+
+  abstract @Nullable String habitat();
+
+  abstract int numberOfLegs();
+
+  static Builder builder() {
+    return new AutoValue_Animal.Builder();
+  }
+
+  @AutoValue.Builder
+  abstract static class Builder {
+
+    abstract Builder setName(String value);
+
+    abstract Builder setNumberOfLegs(int value);
+
+    abstract Builder setHabitat(String value);
+
+    abstract Animal build();
+
+    // wrapper methods to ensure @This annotations are getting added properly
+    @This Builder wrapperSetName() {
+      return setName("dummy");
+    }
+
+    @This Builder wrapperSetNumberOfLegs() {
+      return setNumberOfLegs(3);
+    }
+
+    @This Builder wrapperSetHabitat() {
+      return setHabitat("dummy");
+    }
+  }
+
+  public static void buildSomethingWrong() {
+    Builder b = builder();
+    b.setName("Frank");
+    b.build();
+  }
+
+  public static void buildSomethingRight() {
+    Builder b = builder();
+    b.setName("Frank");
+    b.setNumberOfLegs(4);
+    b.build();
+  }
+
+  public static void buildSomethingRightIncludeOptional() {
+    Builder b = builder();
+    b.setName("Frank");
+    b.setNumberOfLegs(4);
+    b.setHabitat("jungle");
+    b.build();
+  }
+
+  public static void buildSomethingWrongFluent() {
+    builder().setName("Frank").build();
+  }
+
+  public static void buildSomethingRightFluent() {
+    builder().setName("Jim").setNumberOfLegs(7).build();
+  }
+}
diff --git a/framework/tests/returnsreceiverlombok/BuilderMethodRef.java b/framework/tests/returnsreceiverlombok/BuilderMethodRef.java
new file mode 100644
index 0000000..ff3807d
--- /dev/null
+++ b/framework/tests/returnsreceiverlombok/BuilderMethodRef.java
@@ -0,0 +1,30 @@
+import java.util.Optional;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+@Builder
+@Accessors(fluent = true)
+public class BuilderMethodRef {
+  @Getter @Setter @lombok.NonNull String foo;
+  @Getter @Setter Object bar;
+
+  public static void test(Optional<Object> opt) {
+    BuilderMethodRefBuilder b = builder().foo("Hello");
+    opt.ifPresent(b::bar);
+    b.build();
+  }
+}
+
+class CustomBuilderMethodRefBuilder extends BuilderMethodRef.BuilderMethodRefBuilder {
+  // wrapper methods to ensure @This annotations are getting added properly
+  BuilderMethodRef.@This BuilderMethodRefBuilder wrapperFoo() {
+    return foo("dummy");
+  }
+
+  BuilderMethodRef.@This BuilderMethodRefBuilder wrapperBar() {
+    return bar(new Object());
+  }
+}
diff --git a/framework/tests/returnsreceiverlombok/BuilderTest.java b/framework/tests/returnsreceiverlombok/BuilderTest.java
new file mode 100644
index 0000000..4e18078
--- /dev/null
+++ b/framework/tests/returnsreceiverlombok/BuilderTest.java
@@ -0,0 +1,42 @@
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.checkerframework.common.returnsreceiver.qual.*;
+
+@Builder
+@Accessors(fluent = true)
+public class BuilderTest {
+  @Getter @Setter private Integer x;
+  @Getter @Setter @NonNull private Integer y;
+  @Getter @Setter @NonNull private Integer z;
+
+  public static void test_simplePattern() {
+    BuilderTest.builder().x(0).y(0).build();
+    BuilderTest.builder().y(0).build();
+    BuilderTest.builder().y(0).z(5).build();
+  }
+
+  public static void test_builderVar() {
+    final BuilderTest.BuilderTestBuilder goodBuilder = new BuilderTestBuilder();
+    goodBuilder.x(0);
+    goodBuilder.y(0);
+    goodBuilder.build();
+  }
+}
+
+class CustomBuilderTestBuilder extends BuilderTest.BuilderTestBuilder {
+  // wrapper methods to ensure @This annotations are getting added properly
+  BuilderTest.@This BuilderTestBuilder wrapperX() {
+    return x(0);
+  }
+
+  BuilderTest.@This BuilderTestBuilder wrapperY() {
+    return y(1);
+  }
+
+  BuilderTest.@This BuilderTestBuilder wrapperZ() {
+    return z(2);
+  }
+}
diff --git a/framework/tests/returnsreceiverlombok/lombok.config b/framework/tests/returnsreceiverlombok/lombok.config
new file mode 100644
index 0000000..7a21e88
--- /dev/null
+++ b/framework/tests/returnsreceiverlombok/lombok.config
@@ -0,0 +1 @@
+lombok.addLombokGeneratedAnnotation = true
diff --git a/framework/tests/simple/README b/framework/tests/simple/README
new file mode 100644
index 0000000..6b0d6b4
--- /dev/null
+++ b/framework/tests/simple/README
@@ -0,0 +1,2 @@
+This directory should only contain one Java class.  It is used to test initialization of a checker,
+such as SupportedQualsChecker.
diff --git a/framework/tests/simple/Simple.java b/framework/tests/simple/Simple.java
new file mode 100644
index 0000000..c84766c
--- /dev/null
+++ b/framework/tests/simple/Simple.java
@@ -0,0 +1 @@
+public class Simple {}
diff --git a/framework/tests/stringpatterns/README b/framework/tests/stringpatterns/README
new file mode 100644
index 0000000..68e7981
--- /dev/null
+++ b/framework/tests/stringpatterns/README
@@ -0,0 +1,8 @@
+To add a new file to the test suite, see
+  ../README
+
+To run the tests, do
+  (cd $CHECKERFRAMEWORK && ./gradlew SubtypingStringPatternsFullTest)
+
+These cannot be in the same directory as subtyping-tests because some of the
+expected errors in that directory are specific to the Encrypted type system.
diff --git a/framework/tests/stringpatterns/stringpatterns-full/StringPatternsUsage.java b/framework/tests/stringpatterns/stringpatterns-full/StringPatternsUsage.java
new file mode 100644
index 0000000..9a9a6cf
--- /dev/null
+++ b/framework/tests/stringpatterns/stringpatterns-full/StringPatternsUsage.java
@@ -0,0 +1,88 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class StringPatternsUsage {
+
+  void requiresA(@PatternA String arg) {}
+
+  void requiresB(@PatternB String arg) {}
+
+  void requiresC(@PatternC String arg) {}
+
+  void requiresAB(@PatternAB String arg) {}
+
+  void requiresBC(@PatternBC String arg) {}
+
+  void requiresAC(@PatternAC String arg) {}
+
+  void requiresAny(String arg) {}
+
+  void m() {
+
+    String a = "A";
+    String b = "B";
+    String c = "C";
+    String d = "D";
+    String e = "";
+
+    requiresA(a);
+    // :: error: (argument)
+    requiresB(a);
+    // :: error: (argument)
+    requiresC(a);
+    requiresAB(a);
+    // :: error: (argument)
+    requiresBC(a);
+    requiresAC(a);
+    requiresAny(a);
+
+    // :: error: (argument)
+    requiresA(b);
+    requiresB(b);
+    // :: error: (argument)
+    requiresC(b);
+    requiresAB(b);
+    requiresBC(b);
+    // :: error: (argument)
+    requiresAC(b);
+    requiresAny(b);
+
+    // :: error: (argument)
+    requiresA(c);
+    // :: error: (argument)
+    requiresB(c);
+    requiresC(c);
+    // :: error: (argument)
+    requiresAB(c);
+    requiresBC(c);
+    requiresAC(c);
+    requiresAny(c);
+
+    // :: error: (argument)
+    requiresA(d);
+    // :: error: (argument)
+    requiresB(d);
+    // :: error: (argument)
+    requiresC(d);
+    // :: error: (argument)
+    requiresAB(d);
+    // :: error: (argument)
+    requiresBC(d);
+    // :: error: (argument)
+    requiresAC(d);
+    requiresAny(d);
+
+    // :: error: (argument)
+    requiresA(e);
+    // :: error: (argument)
+    requiresB(e);
+    // :: error: (argument)
+    requiresC(e);
+    // :: error: (argument)
+    requiresAB(e);
+    // :: error: (argument)
+    requiresBC(e);
+    // :: error: (argument)
+    requiresAC(e);
+    requiresAny(e);
+  }
+}
diff --git a/framework/tests/subtyping/InvariantArrays.java b/framework/tests/subtyping/InvariantArrays.java
new file mode 100644
index 0000000..f9a9d67
--- /dev/null
+++ b/framework/tests/subtyping/InvariantArrays.java
@@ -0,0 +1,53 @@
+import java.util.LinkedList;
+import java.util.List;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class InvariantArrays {
+  Object[] oa;
+  @Encrypted Object[] eoa;
+
+  String[] sa;
+  @Encrypted String[] esa;
+
+  void tests() {
+    // TODOINVARR:: error: (assignment)
+    oa = eoa;
+    // This error only occurs with the Encrypted type system;
+    // other type systems don't suffer an error here.
+    // :: error: (assignment)
+    eoa = oa;
+    // TODOINVARR:: error: (assignment)
+    oa = esa;
+    // OK
+    oa = sa;
+    eoa = esa;
+  }
+
+  List<? extends Object>[] loa;
+  LinkedList<? extends Runnable>[] llra;
+  List<? extends @Encrypted Object>[] leoa;
+  LinkedList<? extends @Encrypted Runnable>[] llera;
+  @Encrypted List<? extends Object>[] eloa;
+  @Encrypted LinkedList<? extends Runnable>[] ellra;
+  @Encrypted List<? extends @Encrypted Object>[] eleoa;
+  @Encrypted LinkedList<? extends @Encrypted Runnable>[] ellera;
+
+  void genericTests() {
+    // OK
+    loa = llra;
+    loa = leoa;
+    loa = llera;
+    eloa = ellra;
+    leoa = llera;
+    eloa = ellera;
+
+    // TODOINVARR:: error: (assignment)
+    loa = eloa;
+    // TODOINVARR:: error: (assignment)
+    loa = ellra;
+    // :: error: (assignment)
+    eleoa = eloa;
+    // TODOINVARR:: error: (assignment)
+    leoa = eleoa;
+  }
+}
diff --git a/framework/tests/subtyping/Poly.java b/framework/tests/subtyping/Poly.java
new file mode 100644
index 0000000..525927f
--- /dev/null
+++ b/framework/tests/subtyping/Poly.java
@@ -0,0 +1,88 @@
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.checkerframework.framework.testchecker.util.*;
+
+public class Poly {
+
+  void test() {
+
+    @Encrypted String s = encrypt("as0d78f9(*#4j");
+    String t = "foo";
+
+    @Encrypted String x1 = id(s); // valid
+    // :: error: (assignment)
+    @Encrypted String x2 = id(t); // error
+    String x3 = id(s); // valid
+    String x4 = id(t); // valid
+
+    @Encrypted String y01 = combine(s, s); // valid
+    // :: error: (assignment)
+    @Encrypted String y02 = combine(s, t); // error
+    // :: error: (assignment)
+    @Encrypted String y03 = combine(t, t); // error
+
+    String y11 = combine(s, s); // valid
+    String y12 = combine(s, t); // valid
+    String y13 = combine(t, t); // valid
+  }
+
+  @PolyEncrypted String id(@PolyEncrypted String s) {
+    return s;
+  }
+
+  @PolyEncrypted String combine(@PolyEncrypted String s, @PolyEncrypted String t) {
+    // :: error: (argument)
+    sendOverNet(s); // error
+    return s;
+  }
+
+  void sendOverNet(@Encrypted String msg) {}
+
+  List<@PolyEncrypted String> duplicate(@PolyEncrypted String s) {
+    return null;
+  }
+
+  @PolyEncrypted String[] duplicateAsArray(@PolyEncrypted String s) {
+    return null;
+  }
+
+  void test2() {
+    @Encrypted String s = encrypt("p9aS*7dfa0w9e84r");
+    List<@Encrypted String> lst = duplicate(s);
+    @Encrypted String[] arr = duplicateAsArray(s);
+  }
+
+  @PolyEncrypted String substitute(Map<String, ? extends @PolyEncrypted String> map) {
+    return encrypt(null);
+  }
+
+  @PolyEncrypted String substituteSuper(Map<String, ? super @PolyEncrypted String> map) {
+    return encrypt(null);
+  }
+
+  void test3() {
+    // :: error: (assignment)
+    @Encrypted String s = substitute(new HashMap<String, String>());
+    @Encrypted String t = substitute(new HashMap<String, @Encrypted String>());
+
+    // :: error: (assignment)
+    @Encrypted String q = substituteSuper(new HashMap<String, String>());
+    @Encrypted String r = substituteSuper(new HashMap<String, @Encrypted String>());
+  }
+
+  // Test assignment to poly
+  @PolyEncrypted String test4(@PolyEncrypted String s) {
+    if (s == null) {
+      return encrypt(null); // valid
+    } else {
+      // :: error: (return)
+      return "m"; // invalid
+    }
+  }
+
+  @SuppressWarnings("encrypted")
+  static @Encrypted String encrypt(String s) {
+    return (@Encrypted String) s;
+  }
+}
diff --git a/framework/tests/subtyping/README b/framework/tests/subtyping/README
new file mode 100644
index 0000000..1d4699b
--- /dev/null
+++ b/framework/tests/subtyping/README
@@ -0,0 +1,5 @@
+To add a new file to the test suite, see
+  ../README
+
+To run the tests, do
+  (cd $CHECKERFRAMEWORK && ./gradlew SubtypingEncryptedTest)
diff --git a/framework/tests/subtyping/Simple.java b/framework/tests/subtyping/Simple.java
new file mode 100644
index 0000000..47480a4
--- /dev/null
+++ b/framework/tests/subtyping/Simple.java
@@ -0,0 +1,34 @@
+import java.util.LinkedList;
+import java.util.List;
+import org.checkerframework.framework.testchecker.util.Encrypted;
+
+abstract class BasicFunctionality {
+
+  @Encrypted String encrypt(String s) {
+    byte[] b = s.getBytes();
+    for (int i = 0; i < b.length; b[i++]++) {}
+    // :: warning: (cast.unsafe)
+    return (@Encrypted String) new String(b);
+  }
+
+  abstract void sendOverTheInternet(@Encrypted String s);
+
+  void test() {
+    @Encrypted String s = encrypt("foo"); // valid
+    sendOverTheInternet(s); // valid
+
+    String t = encrypt("bar"); // valid (subtype)
+    sendOverTheInternet(t); // valid (flow)
+
+    List<@Encrypted String> lst = new LinkedList<>();
+    lst.add(s);
+    lst.add(t);
+
+    for (@Encrypted String str : lst) {
+      sendOverTheInternet(str);
+    }
+
+    //        for (String str : lst)
+    //            sendOverTheInternet(str);           // should be valid!
+  }
+}
diff --git a/framework/tests/subtyping/ThisType.java b/framework/tests/subtyping/ThisType.java
new file mode 100644
index 0000000..4904394
--- /dev/null
+++ b/framework/tests/subtyping/ThisType.java
@@ -0,0 +1,16 @@
+import org.checkerframework.framework.testchecker.util.*;
+
+public class ThisType {
+  void t1(@Encrypted ThisType this) {
+    @Encrypted ThisType l1 = this;
+    ThisType l2 = this;
+    // Type of l2 is refined by flow -> legal
+    l1 = l2;
+  }
+
+  void t2(ThisType this) {
+    ThisType l1 = this;
+    // :: error: (assignment)
+    @Encrypted ThisType l2 = this;
+  }
+}
diff --git a/framework/tests/subtyping/ThrowCatch.java b/framework/tests/subtyping/ThrowCatch.java
new file mode 100644
index 0000000..71a0402
--- /dev/null
+++ b/framework/tests/subtyping/ThrowCatch.java
@@ -0,0 +1,41 @@
+import org.checkerframework.framework.testchecker.util.Critical;
+
+/**
+ * Tests the symantics for throwable exception.
+ *
+ * @on-hold
+ * @skip-test
+ */
+abstract class ThrowCatch {
+
+  void throwsNoncritical() throws Exception {
+    throw new Exception();
+  }
+
+  void throwsCritical() throws @Critical Exception {
+    throw new @Critical Exception();
+  }
+
+  void catches() {
+    try {
+      throwsNoncritical();
+    } catch (Exception e) {
+    }
+
+    try {
+      throwsNoncritical();
+      // :: error: (type.incompatible)
+    } catch (@Critical Exception e) {
+    }
+
+    try {
+      throwsCritical();
+    } catch (Exception e) {
+    }
+
+    try {
+      throwsCritical();
+    } catch (@Critical Exception e) {
+    }
+  }
+}
diff --git a/framework/tests/subtyping/UnusedTypes.java b/framework/tests/subtyping/UnusedTypes.java
new file mode 100644
index 0000000..dee5a71
--- /dev/null
+++ b/framework/tests/subtyping/UnusedTypes.java
@@ -0,0 +1,21 @@
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.SubtypeOf;
+import org.checkerframework.framework.qual.Unused;
+
+// This test case is quite meaningless, as it's not run with the
+// Nullness Checker. See nullness/UnusedNullness.java instead.
+public class UnusedTypes {
+
+  @SubtypeOf({})
+  @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+  public @interface Prototype {}
+
+  @Unused(when = Prototype.class)
+  public Object ppt;
+
+  protected @Prototype UnusedTypes() {
+    // It should be legal to initialize an unused field to null in the constructor.
+    this.ppt = null;
+  }
+}
diff --git a/framework/tests/typedecldefault/BoundsAndDefaults.java b/framework/tests/typedecldefault/BoundsAndDefaults.java
new file mode 100644
index 0000000..4333653
--- /dev/null
+++ b/framework/tests/typedecldefault/BoundsAndDefaults.java
@@ -0,0 +1,30 @@
+import org.checkerframework.framework.qual.*;
+import org.checkerframework.framework.testchecker.typedecldefault.quals.*;
+
+// @TypeDeclDefaultBottom is the default qualifier in hierarchy.
+@SuppressWarnings("inconsistent.constructor.type")
+public class BoundsAndDefaults {
+  static @TypeDeclDefaultMiddle class MiddleClass {}
+
+  @TypeDeclDefaultBottom MiddleClass method(@TypeDeclDefaultMiddle MiddleClass middle, MiddleClass noAnno) {
+    noAnno = middle;
+    // :: error: (return)
+    return noAnno;
+  }
+
+  // :: error: (annotations.on.use)
+  void tops(@TypeDeclDefaultTop MiddleClass invalid) {
+    @TypeDeclDefaultTop MiddleClass local = null;
+  }
+
+  @NoDefaultQualifierForUse(TypeDeclDefaultTop.class)
+  static @TypeDeclDefaultMiddle class MiddleBoundClass {
+    @TypeDeclDefaultMiddle MiddleBoundClass() {}
+  }
+
+  @TypeDeclDefaultBottom MiddleBoundClass method(@TypeDeclDefaultMiddle MiddleBoundClass middle, MiddleBoundClass noAnno) {
+    // :: error: (assignment)
+    noAnno = middle;
+    return noAnno;
+  }
+}
diff --git a/framework/tests/typedecldefault/TestDefaultForTypeDecl.java b/framework/tests/typedecldefault/TestDefaultForTypeDecl.java
new file mode 100644
index 0000000..1794da4
--- /dev/null
+++ b/framework/tests/typedecldefault/TestDefaultForTypeDecl.java
@@ -0,0 +1,24 @@
+import org.checkerframework.framework.qual.NoDefaultQualifierForUse;
+import org.checkerframework.framework.testchecker.typedecldefault.quals.*;
+
+// @TypeDeclDefaultBottom is the default qualifier in hierarchy.
+// @TypeDeclDefaultTop is the default for type declarations.
+@NoDefaultQualifierForUse(TypeDeclDefaultTop.class)
+public @TypeDeclDefaultTop class TestDefaultForTypeDecl {
+  void test(@TypeDeclDefaultTop TestDefaultForTypeDecl arg) {}
+
+  void testUnannotated(TestDefaultForTypeDecl arg) {}
+
+  void testOtherQual(
+      @TypeDeclDefaultBottom TestDefaultForTypeDecl arg, TestDefaultForTypeDecl arg1) {
+    arg = arg1;
+  }
+
+  void method() {
+    Object @TypeDeclDefaultBottom [] object = new Object[] {null};
+    this.<Object>genericMethod();
+    new TestDefaultForTypeDecl() {};
+  }
+
+  <@TypeDeclDefaultBottom T extends @TypeDeclDefaultBottom Object> void genericMethod() {}
+}
diff --git a/framework/tests/typedecldefault/jdk.astub b/framework/tests/typedecldefault/jdk.astub
new file mode 100644
index 0000000..b1f618c
--- /dev/null
+++ b/framework/tests/typedecldefault/jdk.astub
@@ -0,0 +1,6 @@
+import org.checkerframework.framework.testchecker.typedecldefault.quals.*;
+
+package java.lang;
+class Object{
+  @TypeDeclDefaultBottom Object();
+}
diff --git a/framework/tests/value-ignore-range-overflow/Index117.java b/framework/tests/value-ignore-range-overflow/Index117.java
new file mode 100644
index 0000000..5fc0dac
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/Index117.java
@@ -0,0 +1,11 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Index117 {
+
+  public static void foo(boolean includeIndex, String[] roots) {
+    @IntRange(from = 2, to = Integer.MAX_VALUE) int x = (includeIndex ? 2 : 1) * roots.length + 2;
+    @IntRange(from = 2, to = Integer.MAX_VALUE) int y = 2 * roots.length + 2;
+    @IntRange(from = 2, to = Integer.MAX_VALUE) int z = roots.length + 2;
+    @IntRange(from = 2, to = 2) int w = 0 + 2;
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/RefinementEq.java b/framework/tests/value-ignore-range-overflow/RefinementEq.java
new file mode 100644
index 0000000..d318a79
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/RefinementEq.java
@@ -0,0 +1,28 @@
+import org.checkerframework.common.value.qual.IntRange;
+
+public class RefinementEq {
+
+  void test_equal(int a, int j, int s) {
+
+    if (-1 == a) {
+      @IntRange(from = -1) int b = a;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = -1) int c = a;
+    }
+
+    if (0 == j) {
+      @IntRange(from = 0) int k = j;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = 0) int l = j;
+    }
+
+    if (1 == s) {
+      @IntRange(from = 1) int t = s;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = 1) int u = s;
+    }
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/RefinementGT.java b/framework/tests/value-ignore-range-overflow/RefinementGT.java
new file mode 100644
index 0000000..7b76f52
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/RefinementGT.java
@@ -0,0 +1,65 @@
+import org.checkerframework.common.value.qual.IntRange;
+
+public class RefinementGT {
+
+  void test_forward(int a, int j, int s) {
+    /** forwards greater than */
+    // :: error: (assignment)
+    @IntRange(from = 0) int aa = a;
+    if (a > -1) {
+      /** a is NN now */
+      @IntRange(from = 0) int b = a;
+      @IntRange(from = -1) int b1 = a;
+      // :: error: (assignment)
+      @IntRange(from = 1) int b2 = a;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = 0) int c = a;
+    }
+
+    if (j > 0) {
+      /** j is POS now */
+      @IntRange(from = 1) int k = j;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = 1) int l = j;
+    }
+
+    if (s > 1) {
+      @IntRange(from = 1) int t = s;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = 1) int u = s;
+    }
+  }
+
+  void test_backwards(int a, int j, int s) {
+    /** backwards greater than */
+    // :: error: (assignment)
+    @IntRange(from = 0) int aa = a;
+    if (-1 > a) {
+      // :: error: (assignment)
+      @IntRange(from = -1) int b = a;
+    } else {
+      @IntRange(from = -1) int c = a;
+      // :: error: (assignment)
+      @IntRange(from = 0) int c1 = a;
+      // :: error: (assignment)
+      @IntRange(from = 1) int c2 = a;
+    }
+
+    if (0 > j) {
+      // :: error: (assignment)
+      @IntRange(from = 0) int k = j;
+    } else {
+      @IntRange(from = 0) int l = j;
+    }
+
+    if (1 > s) {
+      // :: error: (assignment)
+      @IntRange(from = 1) int t = s;
+    } else {
+      @IntRange(from = 1) int u = s;
+    }
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/RefinementGTE.java b/framework/tests/value-ignore-range-overflow/RefinementGTE.java
new file mode 100644
index 0000000..792a6ab
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/RefinementGTE.java
@@ -0,0 +1,54 @@
+import org.checkerframework.common.value.qual.IntRange;
+
+public class RefinementGTE {
+
+  void test_forward(int a, int j, int s) {
+    /** forwards greater than or equals */
+    // :: error: (assignment)
+    @IntRange(from = -1) int aa = a;
+    if (a >= -1) {
+      @IntRange(from = -1) int b = a;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = -1) int c = a;
+    }
+
+    if (j >= 0) {
+      @IntRange(from = 0) int k = j;
+      // :: error: (assignment)
+      @IntRange(from = 1) int k1 = j;
+      @IntRange(from = -1) int k2 = j;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = 0) int l = j;
+    }
+  }
+
+  void test_backwards(int a, int j, int s) {
+    /** backwards greater than or equal */
+    // :: error: (assignment)
+    @IntRange(from = 0) int aa = a;
+    if (-1 >= a) {
+      // :: error: (assignment)
+      @IntRange(from = 0) int b = a;
+    } else {
+      @IntRange(from = 0) int c = a;
+    }
+
+    if (0 >= j) {
+      // :: error: (assignment)
+      @IntRange(from = 1) int k = j;
+    } else {
+      @IntRange(from = 1) int l = j;
+      @IntRange(from = -1) int l1 = j;
+      @IntRange(from = 0) int l2 = j;
+    }
+
+    if (1 >= s) {
+      // :: error: (assignment)
+      @IntRange(from = 1) int t = s;
+    } else {
+      @IntRange(from = 1) int u = s;
+    }
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/RefinementLT.java b/framework/tests/value-ignore-range-overflow/RefinementLT.java
new file mode 100644
index 0000000..ef3f95e
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/RefinementLT.java
@@ -0,0 +1,62 @@
+import org.checkerframework.common.value.qual.IntRange;
+
+public class RefinementLT {
+
+  void test_backwards(int a, int j, int s) {
+    /** backwards less than */
+    // :: error: (assignment)
+    @IntRange(from = 0) int aa = a;
+    if (-1 < a) {
+      @IntRange(from = 0) int b = a;
+      @IntRange(from = -1) int b1 = a;
+      // :: error: (assignment)
+      @IntRange(from = 1) int b2 = a;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = 0) int c = a;
+    }
+
+    if (0 < j) {
+      @IntRange(from = 1) int k = j;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = 1) int l = j;
+    }
+
+    if (1 < s) {
+      @IntRange(from = 1) int t = s;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = 1) int u = s;
+    }
+  }
+
+  void test_forwards(int a, int j, int s) {
+    /** forwards less than */
+    // :: error: (assignment)
+    @IntRange(from = 0) int aa = a;
+    if (a < -1) {
+      // :: error: (assignment)
+      @IntRange(from = -1) int b = a;
+    } else {
+      @IntRange(from = -1) int c = a;
+    }
+
+    if (j < 0) {
+      // :: error: (assignment)
+      @IntRange(from = 0) int k = j;
+    } else {
+      @IntRange(from = 0) int l = j;
+      @IntRange(from = -1) int l1 = j;
+      // :: error: (assignment)
+      @IntRange(from = 1) int l2 = j;
+    }
+
+    if (s < 1) {
+      // :: error: (assignment)
+      @IntRange(from = 1) int t = s;
+    } else {
+      @IntRange(from = 1) int u = s;
+    }
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/RefinementLTE.java b/framework/tests/value-ignore-range-overflow/RefinementLTE.java
new file mode 100644
index 0000000..e04ac93
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/RefinementLTE.java
@@ -0,0 +1,63 @@
+import org.checkerframework.common.value.qual.IntRange;
+
+public class RefinementLTE {
+
+  void test_backwards(int a, int j, int s) {
+    /** backwards less than or equals */
+    // :: error: (assignment)
+    @IntRange(from = -1) int aa = a;
+    if (-1 <= a) {
+      @IntRange(from = -1) int b = a;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = -1) int c = a;
+    }
+
+    if (0 <= j) {
+      @IntRange(from = 0) int k = j;
+      @IntRange(from = -1) int k1 = j;
+      // :: error: (assignment)
+      @IntRange(from = 1) int k2 = j;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = 0) int l = j;
+    }
+
+    if (1 <= s) {
+      @IntRange(from = 1) int t = s;
+    } else {
+      // :: error: (assignment)
+      @IntRange(from = 1) int u = s;
+    }
+  }
+
+  void test_forwards(int a, int j, int s) {
+    /** forwards less than or equal */
+    // :: error: (assignment)
+    @IntRange(from = 0) int aa = a;
+    if (a <= -1) {
+      // :: error: (assignment)
+      @IntRange(from = -1) int b0 = a;
+      // :: error: (assignment)
+      @IntRange(from = 0) int b = a;
+      // :: error: (assignment)
+      @IntRange(from = 1) int b2 = a;
+    } else {
+      @IntRange(from = 0) int c = a;
+    }
+
+    if (j <= 0) {
+      // :: error: (assignment)
+      @IntRange(from = 1) int k = j;
+    } else {
+      @IntRange(from = 1) int l = j;
+    }
+
+    if (s <= 1) {
+      // :: error: (assignment)
+      @IntRange(from = 1) int t = s;
+    } else {
+      @IntRange(from = 1) int u = s;
+    }
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/RefinementNEq.java b/framework/tests/value-ignore-range-overflow/RefinementNEq.java
new file mode 100644
index 0000000..5f5db69
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/RefinementNEq.java
@@ -0,0 +1,30 @@
+import org.checkerframework.common.value.qual.IntRange;
+
+public class RefinementNEq {
+
+  void test_not_equal(int a, int j, int s) {
+
+    // :: error: (assignment)
+    @IntRange(from = 0) int aa = a;
+    if (-1 != a) {
+      // :: error: (assignment)
+      @IntRange(from = -1) int b = a;
+    } else {
+      @IntRange(from = -1) int c = a;
+    }
+
+    if (0 != j) {
+      // :: error: (assignment)
+      @IntRange(from = 0) int k = j;
+    } else {
+      @IntRange(from = 0) int l = j;
+    }
+
+    if (1 != s) {
+      // :: error: (assignment)
+      @IntRange(from = 1) int t = s;
+    } else {
+      @IntRange(from = 1) int u = s;
+    }
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/TransferAdd.java b/framework/tests/value-ignore-range-overflow/TransferAdd.java
new file mode 100644
index 0000000..85b331f
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/TransferAdd.java
@@ -0,0 +1,82 @@
+import org.checkerframework.common.value.qual.*;
+
+public class TransferAdd {
+
+  void test() {
+
+    // adding zero and one and two
+
+    int a = -1;
+
+    @IntRange(from = 1) int a1 = a + 2;
+
+    @IntRange(from = 0) int b = a + 1;
+    @IntRange(from = 0) int c = 1 + a;
+
+    @IntRange(from = -1) int d = a + 0;
+    @IntRange(from = -1) int e = 0 + a;
+
+    // :: error: (assignment)
+    @IntRange(from = 1) int f = a + 1;
+
+    @IntRange(from = 0) int g = b + 0;
+
+    @IntRange(from = 1) int h = b + 1;
+
+    @IntRange(from = 1) int i = h + 1;
+    @IntRange(from = 1) int j = h + 0;
+
+    // adding values
+
+    @IntRange(from = 1) int k = i + j;
+    // :: error: (assignment)
+    @IntRange(from = 1) int l = b + c;
+    // :: error: (assignment)
+    @IntRange(from = 1) int m = d + c;
+    // :: error: (assignment)
+    @IntRange(from = 1) int n = d + e;
+
+    @IntRange(from = 1) int o = h + g;
+    // :: error: (assignment)
+    @IntRange(from = 1) int p = h + d;
+
+    @IntRange(from = 0) int q = b + c;
+    // :: error: (assignment)
+    @IntRange(from = 0) int r = q + d;
+
+    @IntRange(from = 0) int s = k + d;
+    @IntRange(from = -1) int t = s + d;
+
+    // increments
+
+    // :: error: (assignment)
+    @IntRange(from = 1) int u = b++;
+
+    @IntRange(from = 1) int u1 = b;
+
+    @IntRange(from = 1) int v = ++c;
+
+    @IntRange(from = 1) int v1 = c;
+
+    int n1p1 = -1, n1p2 = -1;
+
+    @IntRange(from = 0) int w = ++n1p1;
+
+    @IntRange(from = 0) int w1 = n1p1;
+
+    // :: error: (assignment)
+    @IntRange(from = 1) int w2 = n1p1;
+    // :: error: (assignment)
+    @IntRange(from = 1) int w3 = n1p1++;
+
+    // :: error: (assignment)
+    @IntRange(from = 0) int x = n1p2++;
+
+    @IntRange(from = 0) int x1 = n1p2;
+
+    // :: error: (assignment)
+    @IntRange(from = 1) int y = ++d;
+    // :: error: (assignment)
+    @IntRange(from = 1) int z = e++;
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/TransferDivide.java b/framework/tests/value-ignore-range-overflow/TransferDivide.java
new file mode 100644
index 0000000..5c70e42
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/TransferDivide.java
@@ -0,0 +1,58 @@
+import org.checkerframework.common.value.qual.*;
+
+public class TransferDivide {
+
+  void test() {
+    int a = -1;
+    int b = 0;
+    int c = 1;
+    int d = 2;
+
+    /** literals */
+    @IntRange(from = 1) int e = -1 / -1;
+
+    /** 0 / * -> NN */
+    @IntRange(from = 0) int f = 0 / a;
+    @IntRange(from = 0) int g = 0 / d;
+
+    /** * / 1 -> * */
+    @IntRange(from = -1) int h = a / 1;
+    @IntRange(from = 0) int i = b / 1;
+    @IntRange(from = 1) int j = c / 1;
+    @IntRange(from = 1) int k = d / 1;
+
+    /** pos / pos -> nn */
+    @IntRange(from = 0) int l = d / c;
+    @IntRange(from = 0) int m = c / d;
+    // :: error: (assignment)
+    @IntRange(from = 1) int n = c / d;
+
+    /** nn / pos -> nn */
+    @IntRange(from = 0) int o = b / c;
+    // :: error: (assignment)
+    @IntRange(from = 1) int p = b / d;
+
+    /** pos / nn -> nn */
+    @IntRange(from = 0) int q = d / l;
+    // :: error: (assignment)
+    @IntRange(from = 1) int r = c / l;
+
+    /** nn / nn -> nn */
+    @IntRange(from = 0) int s = b / q;
+    // :: error: (assignment)
+    @IntRange(from = 1) int t = b / q;
+
+    /** n1p / pos -> n1p */
+    @IntRange(from = -1) int u = a / d;
+    @IntRange(from = -1) int v = a / c;
+    // :: error: (assignment)
+    @IntRange(from = 0) int w = a / c;
+
+    /** n1p / nn -> n1p */
+    @IntRange(from = -1) int x = a / l;
+  }
+
+  void testDivideByTwo(@IntRange(from = 0) int x) {
+    @IntRange(from = 0) int y = x / 2;
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/TransferMod.java b/framework/tests/value-ignore-range-overflow/TransferMod.java
new file mode 100644
index 0000000..1f4f44c
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/TransferMod.java
@@ -0,0 +1,31 @@
+import org.checkerframework.common.value.qual.*;
+
+public class TransferMod {
+
+  void test() {
+    int aa = -100;
+    int a = -1;
+    int b = 0;
+    int c = 1;
+    int d = 2;
+
+    @IntRange(from = 1) int e = 5 % 3;
+    @IntRange(from = 0) int f = -100 % 1;
+
+    @IntRange(from = 0) int g = aa % -1;
+    @IntRange(from = 0) int h = aa % 1;
+    @IntRange(from = 0) int i = d % -1;
+    @IntRange(from = 0) int j = d % 1;
+
+    @IntRange(from = 0) int k = d % c;
+    @IntRange(from = 0) int l = b % c;
+    @IntRange(from = 0) int m = c % d;
+
+    @IntRange(from = 0) int n = c % a;
+    @IntRange(from = 0) int o = b % a;
+
+    @IntRange(from = -1) int p = a % a;
+    @IntRange(from = -1) int q = a % d;
+    @IntRange(from = -1) int r = a % c;
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/TransferSub.java b/framework/tests/value-ignore-range-overflow/TransferSub.java
new file mode 100644
index 0000000..a00637b
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/TransferSub.java
@@ -0,0 +1,68 @@
+import org.checkerframework.common.value.qual.*;
+
+public class TransferSub {
+
+  void test() {
+    // zero, one, and two
+    int a = 1;
+
+    @IntRange(from = 0) int b = a - 1;
+    // :: error: (assignment)
+    @IntRange(from = 1) int c = a - 1;
+    @IntRange(from = -1) int d = a - 2;
+
+    // :: error: (assignment)
+    @IntRange(from = 0) int e = a - 2;
+
+    @IntRange(from = -1) int f = b - 1;
+    // :: error: (assignment)
+    @IntRange(from = 0) int g = b - 1;
+
+    // :: error: (assignment)
+    @IntRange(from = -1) int h = f - 1;
+
+    @IntRange(from = -1) int i = f - 0;
+    @IntRange(from = 0) int j = b - 0;
+    @IntRange(from = 1) int k = a - 0;
+
+    // :: error: (assignment)
+    @IntRange(from = 1) int l = j - 0;
+    // :: error: (assignment)
+    @IntRange(from = 0) int m = i - 0;
+
+    // :: error: (assignment)
+    @IntRange(from = 1) int n = a - k;
+    // this would be an error if the values of b and j (both zero) weren't known at compile time
+    @IntRange(from = 0) int o = b - j;
+    /* i and d both have compile time value -1, so this is legal.
+    The general case of GTEN1 - GTEN1 is not, though. */
+    @IntRange(from = -1) int p = i - d;
+
+    // decrements
+
+    // :: error: (unary.decrement)
+    // :: error: (assignment)
+    @IntRange(from = 1) int q = --k; // k = 0
+
+    // :: error: (unary.decrement)
+    @IntRange(from = 0) int r = k--; // after this k = -1
+
+    int k1 = 0;
+    @IntRange(from = 0) int s = k1--;
+
+    // :: error: (assignment)
+    @IntRange(from = 0) int s1 = k1;
+
+    k1 = 1;
+    @IntRange(from = 0) int t = --k1;
+
+    k1 = 1;
+    // :: error: (assignment)
+    @IntRange(from = 1) int t1 = --k1;
+
+    int u1 = -1;
+    @IntRange(from = -1) int x = u1--;
+    // :: error: (assignment)
+    @IntRange(from = -1) int x1 = u1;
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/TransferTimes.java b/framework/tests/value-ignore-range-overflow/TransferTimes.java
new file mode 100644
index 0000000..e2121b2
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/TransferTimes.java
@@ -0,0 +1,26 @@
+import org.checkerframework.common.value.qual.*;
+
+public class TransferTimes {
+
+  void test() {
+    int a = 1;
+    @IntRange(from = 1) int b = a * 1;
+    @IntRange(from = 1) int c = 1 * a;
+    @IntRange(from = 0) int d = 0 * a;
+    // :: error: (assignment)
+    @IntRange(from = 0) int e = -1 * a;
+
+    int g = -1;
+    @IntRange(from = 0) int h = g * 0;
+    // :: error: (assignment)
+    @IntRange(from = 1) int i = g * 0;
+    // :: error: (assignment)
+    @IntRange(from = 1) int j = g * a;
+
+    int k = 0;
+    int l = 1;
+    @IntRange(from = 1) int m = a * l;
+    @IntRange(from = 0) int n = k * l;
+    @IntRange(from = 0) int o = k * k;
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/ValueNoOverflow.java b/framework/tests/value-ignore-range-overflow/ValueNoOverflow.java
new file mode 100644
index 0000000..3406339
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/ValueNoOverflow.java
@@ -0,0 +1,95 @@
+import org.checkerframework.common.value.qual.*;
+
+public class ValueNoOverflow {
+
+  // This file contains a series of simple smoke tests for IntRange and ArrayLenRange with no
+  // overflow.
+
+  void test_plus(@IntRange(from = 0) int x, @IntRange(from = -1) int z) {
+    @IntRange(from = 1) int y = x + 1; // IntRange(from = 0) to IntRange(from = 1)
+    @IntRange(from = 0) int w = z + 1; // GTEN1 to NN
+  }
+
+  void test_minus(@IntRange(to = 0) int x, @IntRange(to = 1) int z) {
+    @IntRange(to = -1) int y = x - 1; // IntRange(from = 0) to GTEN1
+    @IntRange(to = 0) int w = z - 1; // Pos to NN
+  }
+
+  void test_mult(@IntRange(from = 0) int x, @IntRange(from = 1) int z) {
+    @IntRange(from = 0) int y = x * z;
+    @IntRange(from = 1) int w = z * z;
+  }
+
+  void testLong_plus(@IntRange(from = 0) long x, @IntRange(from = -1) long z) {
+    @IntRange(from = 1) long y = x + 1; // IntRange(from = 0) to IntRange(from = 1)
+    @IntRange(from = 0) long w = z + 1; // GTEN1 to NN
+  }
+
+  void testLong_minus(@IntRange(to = 0) long x, @IntRange(to = 1) long z) {
+    @IntRange(to = -1) long y = x - 1; // IntRange(from = 0) to GTEN1
+    @IntRange(to = 0) long w = z - 1; // Pos to NN
+  }
+
+  void testLong_mult(@IntRange(from = 0) long x, @IntRange(from = 1) long z) {
+    @IntRange(from = 0) long y = x * z;
+    @IntRange(from = 1) long w = z * z;
+  }
+
+  void testShort_plus(@IntRange(from = 0) short x, @IntRange(from = -1) short z) {
+    @IntRange(from = 1) short y = (short) (x + 1); // IntRange(from = 0) to IntRange(from = 1)
+    @IntRange(from = 0) short w = (short) (z + 1); // GTEN1 to NN
+  }
+
+  void testShort_minus(@IntRange(to = 0) short x, @IntRange(to = 1) short z) {
+    @IntRange(to = -1) short y = (short) (x - 1); // IntRange(from = 0) to GTEN1
+    @IntRange(to = 0) short w = (short) (z - 1); // Pos to NN
+  }
+
+  void testShort_mult(@IntRange(from = 0) short x, @IntRange(from = 1) short z) {
+    @IntRange(from = 0) short y = (short) (x * z);
+    @IntRange(from = 1) short w = (short) (z * z);
+  }
+
+  void testChar_plus(@IntRange(from = 0) char x) {
+    @IntRange(from = 1) char y = (char) (x + 1); // IntRange(from = 0) to IntRange(from = 1)
+  }
+
+  void testChar_minus(@IntRange(to = 65534) char z) {
+    @IntRange(to = 65533) char w = (char) (z - 1); // IntRange(to = 65535) to IntRange(to = 65535)
+  }
+
+  void testChar_mult(@IntRange(from = 0) char x, @IntRange(from = 1) char z) {
+    @IntRange(from = 0) char y = (char) (x * z);
+    @IntRange(from = 1) char w = (char) (z * z);
+  }
+
+  void testByte_plus(@IntRange(from = 0) byte x, @IntRange(from = -1) byte z) {
+    @IntRange(from = 1) byte y = (byte) (x + 1); // IntRange(from = 0) to IntRange(from = 1)
+    @IntRange(from = 0) byte w = (byte) (z + 1); // GTEN1 to NN
+  }
+
+  void testByte_minus(@IntRange(to = 0) byte x, @IntRange(to = 1) byte z) {
+    @IntRange(to = -1) byte y = (byte) (x - 1); // IntRange(from = 0) to GTEN1
+    @IntRange(to = 0) byte w = (byte) (z - 1); // Pos to NN
+  }
+
+  void testByte_mult(@IntRange(from = 0) byte x, @IntRange(from = 1) byte z) {
+    @IntRange(from = 0) byte y = (byte) (x * z);
+    @IntRange(from = 1) byte w = (byte) (z * z);
+  }
+
+  void test_casting(@IntRange(from = 0) int i) {
+    @IntRange(from = 1, to = ((long) Integer.MAX_VALUE) + 1)
+    long x = i + 1;
+  }
+
+  // Include ArrayLenRange tests once ArrayLenRange is merged.
+
+  void arraylenrange_test(int @ArrayLenRange(from = 5) [] a) {
+    int @ArrayLenRange(from = 7) [] b = new int[a.length + 2];
+  }
+
+  void arraylenrange_test2(int @ArrayLenRange(to = 5) [] a) {
+    int @ArrayLenRange(from = 0, to = 0) [] b = new int[a.length - 5];
+  }
+}
diff --git a/framework/tests/value-ignore-range-overflow/Widen.java b/framework/tests/value-ignore-range-overflow/Widen.java
new file mode 100644
index 0000000..13bf9fc
--- /dev/null
+++ b/framework/tests/value-ignore-range-overflow/Widen.java
@@ -0,0 +1,24 @@
+package value;
+
+import org.checkerframework.common.value.qual.*;
+
+public class Widen {
+  public class Loops {
+    void test(int c, int max) {
+      int decexp = 0;
+      int seendot = 0;
+      int i = 0;
+      while (true) {
+        if (c == '.' && seendot == 0) {
+          seendot = 1;
+        } else if ('0' <= c && c <= '9') {
+          decexp += seendot;
+        }
+
+        if (max < i++) {
+          break;
+        }
+      }
+    }
+  }
+}
diff --git a/framework/tests/value-non-null-strings-concatenation/Binaries.java b/framework/tests/value-non-null-strings-concatenation/Binaries.java
new file mode 100644
index 0000000..d2a1240
--- /dev/null
+++ b/framework/tests/value-non-null-strings-concatenation/Binaries.java
@@ -0,0 +1,380 @@
+import java.util.BitSet;
+import org.checkerframework.common.value.qual.*;
+
+public class Binaries {
+  private BitSet bitmap;
+
+  public void test() {
+    int length = bitmap.length();
+    for (int i = 0, t = 0; i < length; i++) {
+      t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0);
+      if (i % 8 == 7 || i == length - 1) {
+        write(t);
+        t = 0;
+      }
+    }
+  }
+
+  void write(int t) {}
+
+  // Test widenedUpperBound is working.
+  public void loop(int c) {
+    double v = 0;
+    int decexp = 0;
+    int seendot = 0;
+    while (true) {
+      if (c == '.' && seendot == 0) seendot = 1;
+      else if ('0' <= c && c <= '9') {
+        v = v * 10 + (c - '0');
+        decexp += seendot;
+      } else {
+        break;
+      }
+    }
+  }
+
+  public void testIntRange(
+      @IntVal({1, 2}) int values,
+      @IntRange(from = 3, to = 4) int range1,
+      @IntRange(from = 5, to = 20) int range2,
+      @BottomVal int bottom,
+      @UnknownVal int top) {
+
+    /* IntRange + IntRange */
+    @IntRange(from = 8, to = 24) int a = range1 + range2;
+
+    /* IntRange * IntVal */
+    @IntRange(from = 3, to = 8) int b = values * range1;
+
+    /* IntRange * BottomVal */
+    int c = range1 * bottom;
+
+    /* IntRange * UnknownVal */
+    @IntRange(from = 0)
+    // :: error: (assignment)
+    int d = range1 + top;
+  }
+
+  public void add() {
+    int a = 1;
+    if (true) {
+      a = 2;
+    }
+    @IntVal({3, 4}) int b = a + 2;
+
+    double c = 1.0;
+    if (true) {
+      c = 2.0;
+    }
+    @DoubleVal({3.0, 4.0}) double d = c + 2;
+
+    char e = '1';
+    if (true) {
+      e = '2';
+    }
+    @IntVal({'3', '4'}) char f = (char) (e + 2);
+
+    String g = "A";
+    if (true) {
+      g = "B";
+    }
+    @StringVal({"AC", "BC"}) String h = g + "C";
+  }
+
+  public void subtract() {
+    int a = 1;
+    if (true) {
+      a = 2;
+    }
+    @IntVal({-1, 0}) int b = a - 2;
+
+    double c = 1.0;
+    if (true) {
+      c = 2.0;
+    }
+    @DoubleVal({-1.0, 0.0}) double d = c - 2;
+
+    char e = '2';
+    if (true) {
+      e = '3';
+    }
+
+    @IntVal({'0', '1'}) char f = (char) (e - 2);
+  }
+
+  public void multiply() {
+    int a = 1;
+    if (true) {
+      a = 2;
+    }
+    @IntVal({2, 4}) int b = a * 2;
+
+    double c = 1.0;
+    if (true) {
+      c = 2.0;
+    }
+    @DoubleVal({2.0, 4.0}) double d = (double) (c * 2);
+
+    char e = (char) 25;
+    if (true) {
+
+      e = (char) 26;
+    }
+
+    @IntVal({'2', '4'}) char f = (char) (e * 2);
+
+    @DoubleVal(0.75) float g = 1 * 0.75f;
+  }
+
+  public void divide() {
+    int a = 2;
+    if (true) {
+      a = 4;
+    }
+    @IntVal({1, 2}) int b = a / 2;
+
+    double c = 1.0;
+    if (true) {
+      c = 2.0;
+    }
+    @DoubleVal({0.5, 1.0}) double d = c / 2;
+
+    char e = (char) 96;
+    if (true) {
+      e = (char) 98;
+    }
+
+    @IntVal({'0', '1'}) char f = (char) (e / 2);
+
+    @IntVal(0) int g = 2 / 3;
+    @IntVal(0) int h = (Integer.MAX_VALUE - 1) / Integer.MAX_VALUE;
+    @IntVal(0) long l = (Long.MAX_VALUE - 1) / Long.MAX_VALUE;
+  }
+
+  public void remainder() {
+    int a = 4;
+    if (true) {
+      a = 5;
+    }
+    @IntVal({1, 2}) int b = a % 3;
+
+    double c = 4.0;
+    if (true) {
+      c = 5.0;
+    }
+    @DoubleVal({1.0, 2.0}) double d = c % 3;
+
+    char e = (char) 98;
+    if (true) {
+      e = (char) 99;
+    }
+
+    @IntVal({'0', '1'}) char f = (char) (e % 50);
+  }
+
+  public boolean flag = true;
+
+  public void and() {
+    boolean a = true;
+    if (flag) {
+      a = false;
+    }
+    // :: error: (assignment)
+    @BoolVal({true}) boolean b = a & true;
+
+    int c = 4;
+    if (true) {
+      c = 5;
+    }
+    @IntVal({0, 1}) int d = c & 3;
+
+    char e = (char) 48;
+    if (true) {
+
+      e = (char) 51;
+    }
+
+    @IntVal({'0', '2'}) char f = (char) (e & 50);
+  }
+
+  public void or() {
+    boolean a = true;
+    if (true) {
+      a = false;
+    }
+    // TODO: we could detect this case
+    // :: error: (assignment)
+    @BoolVal({true}) boolean b = a | true;
+
+    int c = 4;
+    if (true) {
+      c = 5;
+    }
+    @IntVal({7}) int d = c | 3;
+
+    char e = (char) 48;
+    if (true) {
+      e = (char) 51;
+    }
+
+    @IntVal({'1', '3'}) char f = (char) (e | 1);
+  }
+
+  public void xor() {
+    boolean a = true;
+    if (true) {
+      a = false;
+    }
+    // :: error: (assignment)
+    @BoolVal({true}) boolean b = a ^ true;
+
+    int c = 4;
+    if (true) {
+      c = 5;
+    }
+    @IntVal({7, 6}) int d = c ^ 3;
+
+    char e = (char) 48;
+    if (true) {
+
+      e = (char) 51;
+    }
+
+    @IntVal({'1', '2'}) char f = (char) (e ^ 1);
+  }
+
+  public void boolAnd() {
+    @BoolVal({false}) boolean a = true && false;
+    @BoolVal({true}) boolean b = false || true;
+  }
+
+  public void conditionals() {
+    @BoolVal({false}) boolean a = 1.0f == '1';
+    @BoolVal({true}) boolean b = 1 != 2.0;
+    @BoolVal({true}) boolean c = 1 > 0.5;
+    @BoolVal({true}) boolean d = 1 >= 1.0;
+    @BoolVal({true}) boolean e = 1 < 1.1f;
+    @BoolVal({true}) boolean f = (char) 2 <= 2.0;
+    @IntVal('!') Character BANG = '!';
+    @BoolVal(true) boolean g = (BANG == '!');
+    char bangChar = '!';
+    @BoolVal(true) boolean h = (BANG == bangChar);
+
+    Character bang = '!';
+    // Reference equalitiy is used
+    // :: error: (assignment)
+    @BoolVal(false) boolean i = (BANG == bang);
+  }
+
+  public void loop() throws InterruptedException {
+    int spurious_count = 0;
+    while (true) {
+      wait();
+      if (System.currentTimeMillis() == 0) {
+        spurious_count++;
+        if (spurious_count > 1024) {
+          break;
+        }
+      }
+    }
+  }
+
+  public void shifts() {
+    int a = -8;
+    if (true) {
+      a = 4;
+    }
+    @IntVal({1, -2}) int b = a >> 2;
+
+    int c = 1;
+    if (true) {
+      c = 2;
+    }
+    @IntVal({4, 8}) int d = c << 2;
+
+    int e = -8;
+    if (true) {
+      e = 4;
+    }
+    @IntVal({Integer.MAX_VALUE / 2 - 1, 1}) int f = e >>> 2;
+
+    char g = (char) 24;
+    if (true) {
+      g = (char) 25;
+    }
+
+    @IntVal({'0', '2'}) char h = (char) (g << 1);
+  }
+
+  public void chains() {
+    char a = 2;
+    int b = 3;
+    double c = 5;
+
+    @DoubleVal({1}) double d = a * b - c;
+
+    @DoubleVal({3}) double e = a * c - 2 * b - (char) 1;
+  }
+
+  public void compareWithNull() {
+    String s = "1";
+    // TODO
+    // :: error: (assignment)
+    @BoolVal(true) boolean b = (s != null);
+  }
+
+  public void nullConcatenation(@StringVal({"a", "b"}) String arg) {
+    String n1 = null;
+    String n2 = "null";
+    String k = "const";
+
+    // @StringVal("nullnull") String a1 = n1 + null;
+    @StringVal("nullnull") String a2 = n1 + "null";
+    // @StringVal("nullnull") String a3 = n1 + n1;
+    @StringVal("nullnull") String a4 = n1 + n2;
+    @StringVal("nullconst") String a5 = n1 + k;
+    @StringVal("nullconst") String a6 = n1 + "const";
+
+    @StringVal("nullnull") String b1 = n2 + null;
+    @StringVal("nullnull") String b2 = n2 + "null";
+    @StringVal("null") String b3 = n2 + n1;
+    @StringVal("nullnull") String b4 = n2 + n2;
+    @StringVal({"nullconst", "nullnull"}) String b5 = n2 + k;
+    @StringVal("nullconst") String b6 = n2 + "const";
+
+    @StringVal({"anull", "bnull"}) String c1 = arg + null;
+    @StringVal({"anull", "bnull"}) String c2 = arg + "null";
+    @StringVal({"anull", "bnull"}) String c3 = arg + n1;
+    @StringVal({"anull", "bnull"}) String c4 = arg + n2;
+    @StringVal({"aconst", "bconst"}) String c5 = arg + k;
+    @StringVal({"aconst", "bconst"}) String c6 = arg + "const";
+    @StringVal({"a2147483647", "b2147483647"}) String c7 = arg + Integer.MAX_VALUE;
+  }
+
+  public void conditionalComparisions() {
+    @BoolVal(true) boolean a1 = true || false;
+    @BoolVal(true) boolean a2 = true || true;
+    @BoolVal(false) boolean a3 = false || false;
+    @BoolVal(true) boolean a4 = false || true;
+
+    @BoolVal(false) boolean a5 = true && false;
+    @BoolVal(true) boolean a6 = true && true;
+    @BoolVal(false) boolean a7 = false && false;
+    @BoolVal(false) boolean a8 = false && true;
+
+    boolean unknown = flag ? true : false;
+    @BoolVal(true) boolean a9 = true || unknown;
+    @BoolVal(true) boolean a11 = unknown || true;
+    // :: error: (assignment)
+    @BoolVal(false) boolean a12 = unknown || false;
+    // :: error: (assignment)
+    @BoolVal(true) boolean a13 = false || unknown;
+
+    // :: error: (assignment)
+    @BoolVal(true) boolean a14 = true && unknown;
+    // :: error: (assignment)
+    @BoolVal(true) boolean a15 = unknown && true;
+    @BoolVal(false) boolean a16 = unknown && false;
+    @BoolVal(false) boolean a17 = false && unknown;
+  }
+}
diff --git a/framework/tests/value-non-null-strings-concatenation/CompoundAssignment.java b/framework/tests/value-non-null-strings-concatenation/CompoundAssignment.java
new file mode 100644
index 0000000..367ef16
--- /dev/null
+++ b/framework/tests/value-non-null-strings-concatenation/CompoundAssignment.java
@@ -0,0 +1,66 @@
+// General test cases for compound assignments
+// Also test case for Issue 624
+// https://github.com/typetools/checker-framework/issues/624
+
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class CompoundAssignment {
+
+  @StringVal("hello") String field;
+
+  public void refinements() {
+    field = "hello";
+    // :: error: (compound.assignment)
+    field += method();
+    // :: error: (assignment)
+    // :: error: (compound.assignment)
+    @StringVal("hellohellohello") String test = field += method();
+  }
+
+  @StringVal("hello") String method() {
+    // :: error: (assignment)
+    field = "goodbye";
+    return "hello";
+  }
+
+  void value() {
+    @StringVal("hello") String s = "hello";
+    // :: error: (compound.assignment)
+    s += "hello";
+
+    @IntVal(1) int i = 1;
+    // :: error: (compound.assignment)
+    i += 1;
+
+    @IntVal(2) int j = 2;
+    // :: error: (compound.assignment)
+    j += 2;
+
+    // :: error: (assignment)
+    @IntVal(4) int four = j;
+  }
+
+  void value2() {
+    @StringVal("hello") String s = "hello";
+    // :: error: (assignment)
+    s = s + "hello";
+
+    @IntVal(1) int i = 1;
+    // :: error: (assignment)
+    i = i + 1;
+  }
+
+  void noErrorCompoundAssignments() {
+    @IntVal(0) int zero = 0;
+    zero *= 12;
+
+    @StringVal("null") String s = "null";
+    s += "";
+  }
+
+  void errorCompundAssignments() {
+    @StringVal("hello") String s = "hello";
+    s += "";
+  }
+}
diff --git a/framework/tests/value-non-null-strings-concatenation/README b/framework/tests/value-non-null-strings-concatenation/README
new file mode 100644
index 0000000..d31ccbd
--- /dev/null
+++ b/framework/tests/value-non-null-strings-concatenation/README
@@ -0,0 +1,2 @@
+The files in this directory are slight variants of those in ../value/ .
+The two versions should be kept in sync.
diff --git a/framework/tests/value-non-null-strings-concatenation/StringLenConcats.java b/framework/tests/value-non-null-strings-concatenation/StringLenConcats.java
new file mode 100644
index 0000000..88afcb0
--- /dev/null
+++ b/framework/tests/value-non-null-strings-concatenation/StringLenConcats.java
@@ -0,0 +1,118 @@
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.ArrayLenRange;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.common.value.qual.MinLen;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class StringLenConcats {
+
+  void stringLenConcat(@ArrayLen(3) String a, @ArrayLen(5) String b) {
+    @ArrayLen(8) String ab = a + b;
+    @ArrayLen(7) String bxx = b + "xx";
+  }
+
+  void stringLenRangeConcat(
+      @ArrayLenRange(from = 3, to = 5) String a, @ArrayLenRange(from = 11, to = 19) String b) {
+    @ArrayLenRange(from = 14, to = 24) String ab = a + b;
+    @ArrayLenRange(from = 13, to = 21) String bxx = b + "xx";
+  }
+
+  void stringLenLenRangeConcat(
+      @ArrayLen({3, 4, 5}) String a, @ArrayLenRange(from = 10, to = 100) String b) {
+    @ArrayLenRange(from = 13, to = 105) String ab = a + b;
+  }
+
+  void stringValLenConcat(
+      @StringVal("constant") String a,
+      @StringVal({"a", "b", "c"}) String b,
+      @StringVal({"a", "xxx"}) String c,
+      @ArrayLen(11) String d) {
+
+    @ArrayLen(19) String ad = a + d;
+    @ArrayLen(12) String bd = b + d;
+    @ArrayLenRange(from = 12, to = 14) String cd = c + d;
+  }
+
+  void stringValLenRangeConcat(
+      @StringVal("constant") String a,
+      @StringVal({"a", "b", "c"}) String b,
+      @StringVal({"a", "xxx"}) String c,
+      @ArrayLenRange(from = 11, to = 19) String d) {
+
+    @ArrayLenRange(from = 19, to = 27) String ad = a + d;
+    @ArrayLenRange(from = 12, to = 20) String bd = b + d;
+    @ArrayLenRange(from = 12, to = 22) String cd = c + d;
+  }
+
+  void tooManyStringValConcat(
+      @StringVal({"a", "b", "c", "d"}) String a,
+      @StringVal({"ee", "ff", "gg", "hh", "ii"}) String b) {
+    @ArrayLen(2) String aa = a + a;
+    @ArrayLen(3) String ab = a + b;
+  }
+
+  void charConversions(
+      char c,
+      @IntVal({1, 100, 10000}) char d,
+      @ArrayLen({100, 200}) String s,
+      @ArrayLenRange(from = 100, to = 200) String t,
+      @StringVal({"a", "bb", "ccc", "dddd"}) String u) {
+    @ArrayLen({101, 201}) String sc = s + c;
+    @ArrayLen({101, 201}) String sd = s + d;
+
+    @ArrayLenRange(from = 101, to = 201) String tc = t + c;
+
+    @ArrayLen({2, 3, 4, 5}) String uc = u + c;
+    @ArrayLen({2, 3, 4, 5}) String ud = u + d;
+  }
+
+  void intConversions(
+      @IntVal(123) int intConst,
+      @IntRange(from = -100000, to = 100) int intRange,
+      @IntRange(from = 100, to = 100000) int positiveRange,
+      int unknownInt,
+      @ArrayLen(10) String a,
+      @ArrayLenRange(from = 10, to = 20) String b,
+      @StringVal({"aaa", "bbbbb"}) String c) {
+    @ArrayLen(13) String aConst = a + intConst;
+    @ArrayLen({11, 12, 13, 14, 15, 16, 17}) String aRange = a + intRange;
+    @ArrayLen({13, 14, 15, 16}) String aPositive = a + positiveRange;
+    @ArrayLenRange(from = 11, to = 21) String aUnknown = a + unknownInt;
+
+    @ArrayLenRange(from = 13, to = 23) String bConst = b + intConst;
+    @ArrayLenRange(from = 11, to = 27) String bRange = b + intRange;
+    @ArrayLenRange(from = 13, to = 26) String bPositive = b + positiveRange;
+    @ArrayLenRange(from = 11, to = 31) String bUnknown = b + unknownInt;
+
+    @StringVal({"aaa123", "bbbbb123"}) String cConst = c + intConst;
+    @ArrayLen({4, 5, 6, 7, 8, 9, 10, 11, 12}) String cRange = c + intRange;
+    @ArrayLen({6, 7, 8, 9, 10, 11}) String cPositive = c + positiveRange;
+  }
+
+  void longConversions(
+      @IntVal(1000000000000l) long longConst,
+      @IntRange(from = 10, to = 1000000000000l) long longRange,
+      long unknownLong,
+      @ArrayLen(10) String a) {
+
+    @ArrayLen(23) String aConst = a + longConst;
+    @ArrayLenRange(from = 12, to = 23) String aRange = a + longRange;
+    @ArrayLenRange(from = 11, to = 30) String aUnknown = a + unknownLong;
+  }
+
+  void byteConversions(
+      @IntVal(100) byte byteConst,
+      @IntRange(from = 2, to = 10) byte byteRange,
+      byte unknownByte,
+      @ArrayLen(10) String a) {
+
+    @ArrayLen(13) String aConst = a + byteConst;
+    @ArrayLenRange(from = 11, to = 12) String aRange = a + byteRange;
+    @ArrayLenRange(from = 11, to = 14) String aUnknown = a + unknownByte;
+  }
+
+  void minLenConcat(@MinLen(5) String s, @MinLen(7) String t) {
+    @MinLen(12) String st = s + t;
+  }
+}
diff --git a/framework/tests/value/Alias.java b/framework/tests/value/Alias.java
new file mode 100644
index 0000000..16c7ce2
--- /dev/null
+++ b/framework/tests/value/Alias.java
@@ -0,0 +1,10 @@
+import android.support.annotation.IntRange;
+
+public class Alias {
+  public void androidIntRange() {
+    // :: error: (assignment)
+    @IntRange(from = 0, to = 10) int j = 13;
+    // :: error: (assignment)
+    @IntRange(from = 0) int k = -1;
+  }
+}
diff --git a/framework/tests/value/AnnotationUse.java b/framework/tests/value/AnnotationUse.java
new file mode 100644
index 0000000..a243a5a
--- /dev/null
+++ b/framework/tests/value/AnnotationUse.java
@@ -0,0 +1,12 @@
+import org.checkerframework.common.value.qual.IntRange;
+
+public class AnnotationUse {
+  // :: error: (annotation.intrange.on.noninteger)
+  @IntRange(to = 0) String s1;
+
+  // :: error: (annotation.intrange.on.noninteger)
+  @IntRange(from = 0) String s2;
+
+  // Allowed on j.l.Object, because of possible boxing
+  @IntRange(to = 0) Object o;
+}
diff --git a/framework/tests/value/ArrayInit.java b/framework/tests/value/ArrayInit.java
new file mode 100644
index 0000000..ed68c35
--- /dev/null
+++ b/framework/tests/value/ArrayInit.java
@@ -0,0 +1,186 @@
+import org.checkerframework.common.value.qual.*;
+
+public class ArrayInit {
+  public void raggedArrays() {
+    int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLen({1, 2, 3, 4, 7}) [] alpha =
+        new int[][][] {
+          {{1, 1}, {1, 1, 1}, {1}, {1}},
+          {{1}, {1}, {1}, {1, 1}},
+          {{1, 2, 3, 4, 5, 6, 7}},
+          {{1}, {1}, {1, 1, 1, 1}}
+        };
+
+    int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLenRange(from = 1, to = 12) [] gamma =
+        new int[][][] {
+          {{1, 1}, {1, 1, 1}, {1}, {1}},
+          {{1}, {1}, {1}, {1, 1}},
+          {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}},
+          {{1}, {1}, {1, 1, 1, 1}}
+        };
+
+    int @ArrayLen(4) [] @ArrayLen({1, 2, 3}) [] a = {{1, 1}, {1, 1, 1}, {1}, {1}};
+    int @ArrayLen(4) [] @ArrayLen({1, 2}) [] b = {{1}, {1}, {1}, {1, 1}};
+    int @ArrayLen(1) [] @ArrayLen(7) [] c = {{1, 2, 3, 4, 5, 6, 7}};
+    int @ArrayLen(3) [] @ArrayLen({1, 4}) [] d = {{1}, {1}, {1, 1, 1, 1}};
+
+    int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLen({1, 2, 3, 4, 7}) [] beta = {a, b, c, d};
+
+    int @ArrayLen(4) [] @ArrayLen({1, 2, 3}) [] a1 = {{1, 1}, {1, 1, 1}, {1}, {1}};
+    int @ArrayLen(4) [] @ArrayLen({1, 2}) [] b1 = {{1}, {1}, {1}, {1, 1}};
+    int @ArrayLen(1) [] @ArrayLen(11) [] c1 = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}};
+    int @ArrayLen(3) [] @ArrayLen({1, 4}) [] d1 = {{1}, {1}, {1, 1, 1, 1}};
+
+    int @ArrayLen(4) [] @ArrayLenRange(from = 1, to = 4) [] @ArrayLenRange(from = 1, to = 11) []
+        delta = {a1, b1, c1, d1};
+  }
+
+  public void numberInit() {
+    int @ArrayLen({1}) [] a = new int[1];
+  }
+
+  public void listInit() {
+    int @ArrayLen({1}) [] a = new int[] {4};
+  }
+
+  public void varInit() {
+    int i = 1;
+    int @ArrayLen({1}) [] a = new int[i];
+  }
+
+  public void rangeInit(
+      @IntRange(from = 1, to = 2) int shortLength,
+      @IntRange(from = 1, to = 20) int longLength,
+      @BottomVal int bottom) {
+    int @ArrayLen({1, 2}) [] a = new int[shortLength];
+    // :: error: (assignment)
+    int @ArrayLen({1, 2}) [] b = new int[longLength];
+    int @ArrayLenRange(from = 1, to = 20) [] d = new int[longLength];
+    int @ArrayLen({0}) [] c = new int[bottom];
+  }
+
+  public void multiDim() {
+    int i = 2;
+    int j = 3;
+    int @ArrayLen({2}) [] @ArrayLen({3}) [] a = new int[2][3];
+    int @ArrayLen({2}) [] @ArrayLen({3}) [] b = new int[i][j];
+
+    int @ArrayLen({2}) [][] c = new int[][] {{2}, {3}};
+  }
+
+  public void initilizer() {
+    int @ArrayLen(3) [] ints = new int[] {2, 2, 2};
+    char @StringVal("-A%") [] chars = new char[] {45, 'A', '%'};
+    int @ArrayLen(3) [] ints2 = {2, 2, 2};
+  }
+
+  public void initializerString() {
+    byte @ArrayLen(2) [] bytes = new byte[] {100, '%'};
+    char @ArrayLen(3) [] chars = new char[] {45, 'A', '%'};
+  }
+
+  public void vargsTest() {
+    // type of arg should be @UnknownVal Object @BottomVal[]
+    vargs((Object[]) null);
+
+    // type of arg should be @UnknownVal int @BottomVal[]
+    vargs((int[]) null);
+
+    // type of arg should be @UnknownVal byte @BottomVal[]
+    vargs((byte[]) null);
+  }
+
+  public void vargs(Object @ArrayLen(0) ... objs) {}
+
+  public void vargs(int @ArrayLen(0) ... ints) {}
+
+  public void vargs(byte @ArrayLen(0) ... bytes) {}
+
+  public void nullableArrays() {
+    Object @ArrayLen(2) [] @ArrayLen(1) [] o = new Object[][] {new Object[] {null}, null};
+    Object @ArrayLen(1) [][] o2 = new Object[][] {null};
+    Object @ArrayLen(1) [] @ArrayLen(1) [] o3 = new Object[][] {null};
+  }
+
+  public void subtyping1(int @ArrayLen({1, 5}) [] a) {
+    int @ArrayLenRange(from = 1, to = 5) [] b = a;
+    // :: error: (assignment)
+    int @ArrayLenRange(from = 2, to = 5) [] c = a;
+  }
+
+  public void subtyping2(int @ArrayLenRange(from = 1, to = 5) [] a) {
+    int @ArrayLen({1, 2, 3, 4, 5}) [] b = a;
+    // :: error: (assignment)
+    int @ArrayLen({1, 5}) [] c = a;
+  }
+
+  public void subtyping3(int @ArrayLenRange(from = 1, to = 17) [] a) {
+    // :: error: (assignment)
+    int @ArrayLenRange(from = 1, to = 12) [] b = a;
+    // :: error: (assignment)
+    int @ArrayLenRange(from = 5, to = 18) [] c = a;
+    int @ArrayLenRange(from = 0, to = 20) [] d = a;
+  }
+
+  public void lub1(boolean flag, @IntRange(from = 1, to = 200) int x) {
+    int[] a;
+    if (flag) {
+      a = new int[x];
+    } else {
+      a = new int[250];
+    }
+
+    int @ArrayLenRange(from = 1, to = 250) [] b = a;
+  }
+
+  public void lub2(
+      boolean flag, @IntRange(from = 1, to = 20) int x, @IntRange(from = 50, to = 75) int y) {
+    int[] a;
+    if (flag) {
+      a = new int[x];
+    } else {
+      a = new int[y];
+    }
+
+    int @ArrayLenRange(from = 1, to = 75) [] b = a;
+  }
+
+  public void lub3(
+      boolean flag, @IntRange(from = 1, to = 5) int x, @IntRange(from = 3, to = 7) int y) {
+    int[] a;
+    if (flag) {
+      a = new int[x];
+    } else {
+      a = new int[y];
+    }
+
+    int @ArrayLenRange(from = 1, to = 7) [] b = a;
+    int @ArrayLen({1, 2, 3, 4, 5, 6, 7}) [] c = a;
+  }
+
+  public void refine(int[] q) {
+    if (q.length < 20) {
+      @IntRange(from = 0, to = 19) int x = q.length;
+      int @ArrayLenRange(from = 0, to = 19) [] b = q;
+      if (q.length < 5) {
+        int @ArrayLen({0, 1, 2, 3, 4}) [] c = q;
+      }
+    }
+  }
+
+  // The argument is an arraylen with too many values.
+  // :: warning: (too.many.values.given)
+  public void coerce(int @ArrayLen({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 36}) [] a) {
+    int @ArrayLenRange(from = 1, to = 36) [] b = a;
+    if (a.length < 15) {
+      // :: error: (assignment)
+      int @ArrayLen({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) [] c = a;
+    }
+  }
+
+  public void warnings() {
+    // :: warning: (negative.arraylen)
+    int @ArrayLenRange(from = -1, to = 5) [] a;
+    // :: error: (from.greater.than.to)
+    int @ArrayLenRange(from = 10, to = 3) [] b;
+  }
+}
diff --git a/framework/tests/value/ArrayIntro.java b/framework/tests/value/ArrayIntro.java
new file mode 100644
index 0000000..a243beb
--- /dev/null
+++ b/framework/tests/value/ArrayIntro.java
@@ -0,0 +1,18 @@
+import org.checkerframework.common.value.qual.*;
+
+public class ArrayIntro {
+  void test() {
+    int @MinLen(5) [] arr = new int[5];
+    int a = 9;
+    a += 5;
+    a -= 2;
+    int @MinLen(12) [] arr1 = new int[a];
+    int @MinLen(3) [] arr2 = {1, 2, 3};
+    // :: error: (assignment)
+    int @MinLen(4) [] arr3 = {4, 5, 6};
+    // :: error: (assignment)
+    int @MinLen(7) [] arr4 = new int[4];
+    // :: error: (assignment)
+    int @MinLen(16) [] arr5 = new int[a];
+  }
+}
diff --git a/framework/tests/value/Basics.java b/framework/tests/value/Basics.java
new file mode 100644
index 0000000..ab3a857
--- /dev/null
+++ b/framework/tests/value/Basics.java
@@ -0,0 +1,294 @@
+import org.checkerframework.common.value.qual.*;
+
+/** Test subtyping, LUB and annotation replacement in special cases. */
+@SuppressWarnings("deprecation")
+public class Basics {
+
+  public void boolTest() {
+    boolean a = false;
+    if (true) {
+      a = true;
+    }
+    @BoolVal({true, false}) boolean b = a;
+
+    // :: error: (assignment)
+    @BoolVal({false}) boolean c = a;
+  }
+
+  public void CharacterTest() {
+    Character a = 'a';
+    if (true) {
+      a = 'b';
+    }
+    @IntVal({'a', 'b'}) Character b = a;
+
+    // :: error: (assignment)
+    @IntVal({'a'}) Character c = a;
+  }
+
+  public void charTest() {
+    char a = 'a';
+    if (true) {
+      a = 'b';
+    }
+    @IntVal({'a', 'b'}) char b = a;
+
+    // :: error: (assignment)
+    @IntVal({'a'}) char c = a;
+  }
+
+  public void DoubleTest() {
+    Double a = new Double(0.0);
+    if (true) {
+      a = 2.0;
+    }
+    @DoubleVal({0, 2}) Double b = a;
+
+    // :: error: (assignment)
+    @DoubleVal({0}) Double c = a;
+  }
+
+  public void doubleTest() {
+    double a = 0.0;
+    if (true) {
+      a = 2.0;
+    }
+    @DoubleVal({0, 2}) double b = a;
+
+    // :: error: (assignment)
+    @DoubleVal({0}) double c = a;
+  }
+
+  public void FloatTest() {
+    Float a = new Float(0.0f);
+    if (true) {
+      a = 2.0f;
+    }
+    @DoubleVal({0, 2}) Float b = a;
+
+    // :: error: (assignment)
+    @DoubleVal({0}) Float c = a;
+  }
+
+  public void floatTest() {
+    float a = 0.0f;
+    if (true) {
+      a = 2.0f;
+    }
+    @DoubleVal({0, 2}) float b = a;
+
+    // :: error: (assignment)
+    @DoubleVal({'a'}) float c = a;
+  }
+
+  public void IntegerTest(
+      @IntRange(from = 3, to = 4) Integer x, @IntRange(from = 20, to = 30) Integer y) {
+    Integer a;
+
+    /* IntVal + IntVal */
+    a = new Integer(0);
+    if (true) {
+      a = 2;
+    }
+    // :: error: (assignment)
+    @IntVal({0}) Integer test1 = a;
+    @IntVal({0, 2}) Integer test2 = a;
+
+    /* IntRange + IntRange */
+    a = x;
+    @IntVal({3, 4}) Integer test3 = a;
+    if (true) {
+      a = y;
+    }
+    @IntRange(from = 15, to = 30)
+    // :: error: (assignment)
+    Integer test4 = a;
+    @IntRange(from = 3, to = 30) Integer test5 = a;
+
+    /* IntRange + IntVal */
+    a = new Integer(0);
+    if (true) {
+      a = x;
+    }
+    @IntRange(from = 0, to = 4) Integer test7 = a;
+
+    /* IntRange (Wider than 10) + IntVal */
+    a = new Integer(0);
+    if (true) {
+      a = y;
+    }
+    @IntRange(from = 1, to = 30)
+    // :: error: (assignment)
+    Integer test8 = a;
+    @IntRange(from = 0, to = 30) Integer test9 = a;
+  }
+
+  public void intTest(@IntRange(from = 3, to = 4) int x, @IntRange(from = 20, to = 30) int y) {
+    int a;
+
+    /* IntVal + IntVal */
+    a = 0;
+    if (true) {
+      a = 2;
+    }
+    // :: error: (assignment)
+    @IntVal({0}) int test1 = a;
+    @IntVal({0, 2}) int test2 = a;
+
+    /* IntRange + IntRange */
+    a = x;
+    @IntVal({3, 4}) int test3 = a;
+    if (true) {
+      a = y;
+    }
+    @IntRange(from = 15, to = 30)
+    // :: error: (assignment)
+    int test4 = a;
+    @IntRange(from = 3, to = 30) int test5 = a;
+
+    /* IntRange + IntVal */
+    a = 0;
+    if (true) {
+      a = x;
+    }
+    @IntRange(from = 0, to = 4) int test7 = a;
+
+    /* IntRange (Wider than 10) + IntVal */
+    a = 0;
+    if (true) {
+      a = y;
+    }
+    @IntRange(from = 1, to = 30)
+    // :: error: (assignment)
+    int test8 = a;
+    @IntRange(from = 0, to = 30) int test9 = a;
+  }
+
+  public void intCastTest(@IntVal({0, 1}) int input) {
+    @IntVal({0, 1}) int c = (int) input;
+    @IntVal({0, 1}) int ac = (@IntVal({0, 1}) int) input;
+    @IntVal({0, 1, 2}) int sc = (@IntVal({0, 1, 2}) int) input;
+    // :: warning: (cast.unsafe)
+    @IntVal({1}) int uc = (@IntVal({1}) int) input;
+    // :: warning: (cast.unsafe)
+    @IntVal({2}) int bc = (@IntVal({2}) int) input;
+  }
+
+  public void IntDoubleTest(
+      @IntVal({0, 1}) int iv,
+      @IntRange(from = 2, to = 3) int ir,
+      @IntRange(from = 2, to = 20) int irw,
+      @DoubleVal({4.0, 5.0}) double dv1) {
+    double a;
+
+    /* IntVal + DoubleVal */
+    a = iv;
+    if (true) {
+      a = dv1;
+    }
+    // :: error: (assignment)
+    @DoubleVal({4.0, 5.0}) double test1 = a;
+    @DoubleVal({0.0, 1.0, 2.0, 3.0, 4.0, 5.0}) double test2 = a;
+
+    /* IntRange + DoubleVal */
+    a = ir;
+    // :: error: (assignment)
+    @DoubleVal({2.0}) double test3 = a;
+    @DoubleVal({2.0, 3.0}) double test4 = a;
+    if (true) {
+      a = dv1;
+    }
+    // :: error: (assignment)
+    test1 = a;
+    test2 = a;
+
+    /* IntRange (Wider than 10) + DoubleVal */
+    a = irw;
+    if (true) {
+      a = dv1;
+    }
+    // :: error: (assignment)
+    @DoubleVal({4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double test5 = a;
+    @UnknownVal double test6 = a;
+  }
+
+  public void stringTest() {
+    String a = "test1";
+    if (true) {
+      a = "test2";
+    }
+    @StringVal({"test1", "test2"}) String b = a;
+
+    // :: error: (assignment)
+    @StringVal({"test1"}) String c = a;
+  }
+
+  public void stringCastTest() {
+    Object a = "test1";
+    @StringVal({"test1"}) String b = (String) a;
+    @StringVal({"test1"}) String c = (java.lang.String) b;
+  }
+
+  void tooManyValuesInt() {
+    // :: warning: (too.many.values.given.int)
+    @IntVal({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 100}) int a = 20; // This should succeed if a is treated as @IntRange(from=1, to=100)
+
+    // :: warning: (too.many.values.given.int)
+    @IntVal({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12})
+    // :: error: (assignment)
+    int b = 20; // d is @IntRange(from=1, to=12)
+
+    @UnknownVal int c = a; // This should always succeed
+  }
+
+  void fromGreaterThanTo() {
+    // :: error: (from.greater.than.to)
+    @IntRange(from = 2, to = 0)
+    // :: error: (assignment)
+    int a = 1; // a should be @BottomVal
+
+    @IntRange(from = 1) int b = 2;
+
+    @IntRange(to = 2) int c = 1;
+
+    @IntRange(to = 2, from = 0) int d = 1;
+  }
+
+  void tooManyValuesDouble() {
+    // :: warning: (too.many.values.given)
+    @DoubleVal({1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double a = 8.0;
+
+    @UnknownVal double b = a; // This should always succeed
+
+    @UnknownVal double c = 0;
+
+    a = c; // This should succeed if a is treated as @UnknownVal
+
+    // :: warning: (too.many.values.given)
+    @DoubleVal({1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double d = 8.0;
+
+    d = 2.0 * d; // This should succeed since d is @UnknownVal
+  }
+
+  void tooManyValuesString() {
+    // :: warning: (too.many.values.given)
+    @StringVal({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}) String a = "h";
+
+    @UnknownVal String b = a; // This should always succeed
+
+    @UnknownVal String c = "";
+
+    // :: error: (assignment)
+    a = c; // This should not succeed if a is treated as @ArrayLen(1)
+
+    @ArrayLen(1) String al = a; // a is @ArrayLen(1)
+
+    // :: warning: (too.many.values.given)
+    @StringVal({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}) String d = "h";
+
+    // :: error: (assignment)
+    d = "b" + d; // This should not succeed since d is @ArrayLen(1)
+
+    @ArrayLen(1) String dl = d; // d is @ArrayLen(1)
+  }
+}
diff --git a/framework/tests/value/BigIntegerTest.java b/framework/tests/value/BigIntegerTest.java
new file mode 100644
index 0000000..19ce966
--- /dev/null
+++ b/framework/tests/value/BigIntegerTest.java
@@ -0,0 +1,37 @@
+import java.math.BigInteger;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.PolyValue;
+
+public class BigIntegerTest {
+  void construct1(@IntRange(from = -1, to = 1) int signum, byte[] magnitude) {
+    BigInteger val = new BigInteger(signum, magnitude);
+  }
+
+  void construct2(String val, @IntRange(from = 2, to = 36) int radix) {
+    BigInteger value = new BigInteger(val, radix);
+  }
+
+  @PolyValue double getDoubleVal(@PolyValue BigInteger val) {
+    return val.doubleValue();
+  }
+
+  @PolyValue int getIntVal(@PolyValue BigInteger val) {
+    return val.intValue();
+  }
+
+  @PolyValue float getFloatVal(@PolyValue BigInteger val) {
+    return val.floatValue();
+  }
+
+  @PolyValue long getLongVal(@PolyValue BigInteger val) {
+    return val.longValue();
+  }
+
+  void compareTo(BigInteger val, BigInteger to) {
+    @IntRange(from = -1, to = 1) int compared = val.compareTo(to);
+  }
+
+  void signum(BigInteger val) {
+    @IntRange(from = -1, to = 1) int signum = val.signum();
+  }
+}
diff --git a/framework/tests/value/Binaries.java b/framework/tests/value/Binaries.java
new file mode 100644
index 0000000..760dbe6
--- /dev/null
+++ b/framework/tests/value/Binaries.java
@@ -0,0 +1,380 @@
+import java.util.BitSet;
+import org.checkerframework.common.value.qual.*;
+
+public class Binaries {
+  private BitSet bitmap;
+
+  public void test() {
+    int length = bitmap.length();
+    for (int i = 0, t = 0; i < length; i++) {
+      t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0);
+      if (i % 8 == 7 || i == length - 1) {
+        write(t);
+        t = 0;
+      }
+    }
+  }
+
+  void write(int t) {}
+
+  // Test widenedUpperBound is working.
+  public void loop(int c) {
+    double v = 0;
+    int decexp = 0;
+    int seendot = 0;
+    while (true) {
+      if (c == '.' && seendot == 0) seendot = 1;
+      else if ('0' <= c && c <= '9') {
+        v = v * 10 + (c - '0');
+        decexp += seendot;
+      } else {
+        break;
+      }
+    }
+  }
+
+  public void testIntRange(
+      @IntVal({1, 2}) int values,
+      @IntRange(from = 3, to = 4) int range1,
+      @IntRange(from = 5, to = 20) int range2,
+      @BottomVal int bottom,
+      @UnknownVal int top) {
+
+    /* IntRange + IntRange */
+    @IntRange(from = 8, to = 24) int a = range1 + range2;
+
+    /* IntRange * IntVal */
+    @IntRange(from = 3, to = 8) int b = values * range1;
+
+    /* IntRange * BottomVal */
+    int c = range1 * bottom;
+
+    /* IntRange * UnknownVal */
+    @IntRange(from = 0)
+    // :: error: (assignment)
+    int d = range1 + top;
+  }
+
+  public void add() {
+    int a = 1;
+    if (true) {
+      a = 2;
+    }
+    @IntVal({3, 4}) int b = a + 2;
+
+    double c = 1.0;
+    if (true) {
+      c = 2.0;
+    }
+    @DoubleVal({3.0, 4.0}) double d = c + 2;
+
+    char e = '1';
+    if (true) {
+      e = '2';
+    }
+    @IntVal({'3', '4'}) char f = (char) (e + 2);
+
+    String g = "A";
+    if (true) {
+      g = "B";
+    }
+    @StringVal({"nullC", "AC", "BC"}) String h = g + "C";
+  }
+
+  public void subtract() {
+    int a = 1;
+    if (true) {
+      a = 2;
+    }
+    @IntVal({-1, 0}) int b = a - 2;
+
+    double c = 1.0;
+    if (true) {
+      c = 2.0;
+    }
+    @DoubleVal({-1.0, 0.0}) double d = c - 2;
+
+    char e = '2';
+    if (true) {
+      e = '3';
+    }
+
+    @IntVal({'0', '1'}) char f = (char) (e - 2);
+  }
+
+  public void multiply() {
+    int a = 1;
+    if (true) {
+      a = 2;
+    }
+    @IntVal({2, 4}) int b = a * 2;
+
+    double c = 1.0;
+    if (true) {
+      c = 2.0;
+    }
+    @DoubleVal({2.0, 4.0}) double d = (double) (c * 2);
+
+    char e = (char) 25;
+    if (true) {
+
+      e = (char) 26;
+    }
+
+    @IntVal({'2', '4'}) char f = (char) (e * 2);
+
+    @DoubleVal(0.75) float g = 1 * 0.75f;
+  }
+
+  public void divide() {
+    int a = 2;
+    if (true) {
+      a = 4;
+    }
+    @IntVal({1, 2}) int b = a / 2;
+
+    double c = 1.0;
+    if (true) {
+      c = 2.0;
+    }
+    @DoubleVal({0.5, 1.0}) double d = c / 2;
+
+    char e = (char) 96;
+    if (true) {
+      e = (char) 98;
+    }
+
+    @IntVal({'0', '1'}) char f = (char) (e / 2);
+
+    @IntVal(0) int g = 2 / 3;
+    @IntVal(0) int h = (Integer.MAX_VALUE - 1) / Integer.MAX_VALUE;
+    @IntVal(0) long l = (Long.MAX_VALUE - 1) / Long.MAX_VALUE;
+  }
+
+  public void remainder() {
+    int a = 4;
+    if (true) {
+      a = 5;
+    }
+    @IntVal({1, 2}) int b = a % 3;
+
+    double c = 4.0;
+    if (true) {
+      c = 5.0;
+    }
+    @DoubleVal({1.0, 2.0}) double d = c % 3;
+
+    char e = (char) 98;
+    if (true) {
+      e = (char) 99;
+    }
+
+    @IntVal({'0', '1'}) char f = (char) (e % 50);
+  }
+
+  public boolean flag = true;
+
+  public void and() {
+    boolean a = true;
+    if (flag) {
+      a = false;
+    }
+    // :: error: (assignment)
+    @BoolVal({true}) boolean b = a & true;
+
+    int c = 4;
+    if (true) {
+      c = 5;
+    }
+    @IntVal({0, 1}) int d = c & 3;
+
+    char e = (char) 48;
+    if (true) {
+
+      e = (char) 51;
+    }
+
+    @IntVal({'0', '2'}) char f = (char) (e & 50);
+  }
+
+  public void or() {
+    boolean a = true;
+    if (true) {
+      a = false;
+    }
+    // TODO: we could detect this case
+    // :: error: (assignment)
+    @BoolVal({true}) boolean b = a | true;
+
+    int c = 4;
+    if (true) {
+      c = 5;
+    }
+    @IntVal({7}) int d = c | 3;
+
+    char e = (char) 48;
+    if (true) {
+      e = (char) 51;
+    }
+
+    @IntVal({'1', '3'}) char f = (char) (e | 1);
+  }
+
+  public void xor() {
+    boolean a = true;
+    if (true) {
+      a = false;
+    }
+    // :: error: (assignment)
+    @BoolVal({true}) boolean b = a ^ true;
+
+    int c = 4;
+    if (true) {
+      c = 5;
+    }
+    @IntVal({7, 6}) int d = c ^ 3;
+
+    char e = (char) 48;
+    if (true) {
+
+      e = (char) 51;
+    }
+
+    @IntVal({'1', '2'}) char f = (char) (e ^ 1);
+  }
+
+  public void boolAnd() {
+    @BoolVal({false}) boolean a = true && false;
+    @BoolVal({true}) boolean b = false || true;
+  }
+
+  public void conditionals() {
+    @BoolVal({false}) boolean a = 1.0f == '1';
+    @BoolVal({true}) boolean b = 1 != 2.0;
+    @BoolVal({true}) boolean c = 1 > 0.5;
+    @BoolVal({true}) boolean d = 1 >= 1.0;
+    @BoolVal({true}) boolean e = 1 < 1.1f;
+    @BoolVal({true}) boolean f = (char) 2 <= 2.0;
+    @IntVal('!') Character BANG = '!';
+    @BoolVal(true) boolean g = (BANG == '!');
+    char bangChar = '!';
+    @BoolVal(true) boolean h = (BANG == bangChar);
+
+    Character bang = '!';
+    // Reference equalitiy is used
+    // :: error: (assignment)
+    @BoolVal(false) boolean i = (BANG == bang);
+  }
+
+  public void loop() throws InterruptedException {
+    int spurious_count = 0;
+    while (true) {
+      wait();
+      if (System.currentTimeMillis() == 0) {
+        spurious_count++;
+        if (spurious_count > 1024) {
+          break;
+        }
+      }
+    }
+  }
+
+  public void shifts() {
+    int a = -8;
+    if (true) {
+      a = 4;
+    }
+    @IntVal({1, -2}) int b = a >> 2;
+
+    int c = 1;
+    if (true) {
+      c = 2;
+    }
+    @IntVal({4, 8}) int d = c << 2;
+
+    int e = -8;
+    if (true) {
+      e = 4;
+    }
+    @IntVal({Integer.MAX_VALUE / 2 - 1, 1}) int f = e >>> 2;
+
+    char g = (char) 24;
+    if (true) {
+      g = (char) 25;
+    }
+
+    @IntVal({'0', '2'}) char h = (char) (g << 1);
+  }
+
+  public void chains() {
+    char a = 2;
+    int b = 3;
+    double c = 5;
+
+    @DoubleVal({1}) double d = a * b - c;
+
+    @DoubleVal({3}) double e = a * c - 2 * b - (char) 1;
+  }
+
+  public void compareWithNull() {
+    String s = "1";
+    // TODO
+    // :: error: (assignment)
+    @BoolVal(true) boolean b = (s != null);
+  }
+
+  public void nullConcatenation(@StringVal({"a", "b"}) String arg) {
+    String n1 = null;
+    String n2 = "null";
+    String k = "const";
+
+    // @StringVal("nullnull") String a1 = n1 + null;
+    @StringVal("nullnull") String a2 = n1 + "null";
+    // @StringVal("nullnull") String a3 = n1 + n1;
+    @StringVal("nullnull") String a4 = n1 + n2;
+    @StringVal({"nullconst", "nullnull"}) String a5 = n1 + k;
+    @StringVal("nullconst") String a6 = n1 + "const";
+
+    @StringVal("nullnull") String b1 = n2 + null;
+    @StringVal("nullnull") String b2 = n2 + "null";
+    @StringVal("nullnull") String b3 = n2 + n1;
+    @StringVal("nullnull") String b4 = n2 + n2;
+    @StringVal({"nullconst", "nullnull"}) String b5 = n2 + k;
+    @StringVal("nullconst") String b6 = n2 + "const";
+
+    @StringVal({"anull", "bnull", "nullnull"}) String c1 = arg + null;
+    @StringVal({"anull", "bnull", "nullnull"}) String c2 = arg + "null";
+    @StringVal({"anull", "bnull", "nullnull"}) String c3 = arg + n1;
+    @StringVal({"anull", "bnull", "nullnull"}) String c4 = arg + n2;
+    @StringVal({"aconst", "anull", "bconst", "bnull", "nullconst", "nullnull"}) String c5 = arg + k;
+    @StringVal({"aconst", "bconst", "nullconst"}) String c6 = arg + "const";
+    @StringVal({"a2147483647", "b2147483647", "null2147483647"}) String c7 = arg + Integer.MAX_VALUE;
+  }
+
+  public void conditionalComparisions() {
+    @BoolVal(true) boolean a1 = true || false;
+    @BoolVal(true) boolean a2 = true || true;
+    @BoolVal(false) boolean a3 = false || false;
+    @BoolVal(true) boolean a4 = false || true;
+
+    @BoolVal(false) boolean a5 = true && false;
+    @BoolVal(true) boolean a6 = true && true;
+    @BoolVal(false) boolean a7 = false && false;
+    @BoolVal(false) boolean a8 = false && true;
+
+    boolean unknown = flag ? true : false;
+    @BoolVal(true) boolean a9 = true || unknown;
+    @BoolVal(true) boolean a11 = unknown || true;
+    // :: error: (assignment)
+    @BoolVal(false) boolean a12 = unknown || false;
+    // :: error: (assignment)
+    @BoolVal(true) boolean a13 = false || unknown;
+
+    // :: error: (assignment)
+    @BoolVal(true) boolean a14 = true && unknown;
+    // :: error: (assignment)
+    @BoolVal(true) boolean a15 = unknown && true;
+    @BoolVal(false) boolean a16 = unknown && false;
+    @BoolVal(false) boolean a17 = false && unknown;
+  }
+}
diff --git a/framework/tests/value/BitsMethodsIntRange.java b/framework/tests/value/BitsMethodsIntRange.java
new file mode 100644
index 0000000..6929a7e
--- /dev/null
+++ b/framework/tests/value/BitsMethodsIntRange.java
@@ -0,0 +1,13 @@
+import org.checkerframework.common.value.qual.IntRange;
+
+public class BitsMethodsIntRange {
+  void caseInteger(int integerIndex) {
+    @IntRange(from = 0, to = 32) int leadingZeros = Integer.numberOfLeadingZeros(integerIndex);
+    @IntRange(from = 0, to = 32) int trailingZeros = Integer.numberOfLeadingZeros(integerIndex);
+  }
+
+  void caseLong(long longIndex) {
+    @IntRange(from = 0, to = 64) int leadingZeros = Long.numberOfLeadingZeros(longIndex);
+    @IntRange(from = 0, to = 64) int trailingZeros = Long.numberOfLeadingZeros(longIndex);
+  }
+}
diff --git a/framework/tests/value/BitwiseAnd.java b/framework/tests/value/BitwiseAnd.java
new file mode 100644
index 0000000..f40c527
--- /dev/null
+++ b/framework/tests/value/BitwiseAnd.java
@@ -0,0 +1,36 @@
+import org.checkerframework.common.value.qual.IntRange;
+
+public class BitwiseAnd {
+
+  public static void Case11(@IntRange(from = 0) byte b) {
+    @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0;
+  }
+
+  public static void Case12(@IntRange(to = -1) byte b) {
+    @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0;
+  }
+
+  public static void Case13(byte b) {
+    @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0;
+  }
+
+  public static void Case21(@IntRange(from = 0) byte b) {
+    @IntRange(from = 0, to = 0x0f) long i1 = b & 0x800000000000000fL;
+  }
+
+  public static void Case22(@IntRange(to = -1) byte b) {
+    @IntRange(from = 0x8000000000000000L, to = 0x80000000ffffffffL) long i1 = b & 0x80000000ffffffffL;
+  }
+
+  public static void Case23(byte b) {
+    @IntRange(from = 0x8000000000000000L, to = 0xf0) long i1 = b & 0x80000000000000f0L;
+  }
+
+  public static void Issue1623(byte[] bytes) {
+    for (int i = 0; i < bytes.length; i++) {
+      byte b = bytes[i];
+      @IntRange(from = 0, to = 15) int i1 = (b & 0xf0) >> 4;
+      @IntRange(from = 0, to = 15) int i2 = b & 0x0f;
+    }
+  }
+}
diff --git a/framework/tests/value/Boxing.java b/framework/tests/value/Boxing.java
new file mode 100644
index 0000000..05121e9
--- /dev/null
+++ b/framework/tests/value/Boxing.java
@@ -0,0 +1,23 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Boxing {
+
+  void simpleTest1(@BottomVal Integer x, int y) {
+    @BottomVal int f = x.intValue();
+    if (x.intValue() == y) {
+      @BottomVal int z = y;
+    }
+  }
+
+  void simpleTest2(@BottomVal Integer x, int y) {
+    if (x == y) {
+      @BottomVal int z = y;
+    }
+  }
+
+  void simpleTest3(@BottomVal Integer x, Integer y) {
+    if (x == y) {
+      @BottomVal int z = y;
+    }
+  }
+}
diff --git a/framework/tests/value/CharArrayWithNonLiteralConstants.java b/framework/tests/value/CharArrayWithNonLiteralConstants.java
new file mode 100644
index 0000000..d0af0fb
--- /dev/null
+++ b/framework/tests/value/CharArrayWithNonLiteralConstants.java
@@ -0,0 +1,11 @@
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class CharArrayWithNonLiteralConstants {
+
+  public static void main(String[] args) {
+    char @StringVal("hello") [] greeting1 = {'h', 'e', 'l', 'l', 'o'};
+    @IntVal('e') char e = 'e';
+    char @StringVal("hello") [] greeting2 = {'h', e, 'l', 'l', 'o'};
+  }
+}
diff --git a/framework/tests/value/CharacterToString.java b/framework/tests/value/CharacterToString.java
new file mode 100644
index 0000000..a0f34bc
--- /dev/null
+++ b/framework/tests/value/CharacterToString.java
@@ -0,0 +1,8 @@
+// Annotation on the return value of Character.toString caused
+// two annotations from the same hierarchy.
+// https://github.com/typetools/checker-framework/issues/1356
+public class CharacterToString {
+  void m() {
+    String s = Character.toString('a');
+  }
+}
diff --git a/framework/tests/value/ClassNotFound.java b/framework/tests/value/ClassNotFound.java
new file mode 100644
index 0000000..86c3d82
--- /dev/null
+++ b/framework/tests/value/ClassNotFound.java
@@ -0,0 +1,17 @@
+import org.checkerframework.common.value.qual.*;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class ClassNotFound {
+
+  @StaticallyExecutable
+  @Pure
+  public static int foo(int a) {
+    return a + 2;
+  }
+
+  public void bar() {
+    int a = 0;
+    // :: warning: (class.find.failed)
+    foo(a);
+  }
+}
diff --git a/framework/tests/value/CompoundAssignment.java b/framework/tests/value/CompoundAssignment.java
new file mode 100644
index 0000000..2075fb2
--- /dev/null
+++ b/framework/tests/value/CompoundAssignment.java
@@ -0,0 +1,67 @@
+// General test cases for compound assignments
+// Also test case for Issue 624
+// https://github.com/typetools/checker-framework/issues/624
+
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class CompoundAssignment {
+
+  @StringVal("hello") String field;
+
+  public void refinements() {
+    field = "hello";
+    // :: error: (compound.assignment)
+    field += method();
+    // :: error: (assignment)
+    // :: error: (compound.assignment)
+    @StringVal("hellohellohello") String test = field += method();
+  }
+
+  @StringVal("hello") String method() {
+    // :: error: (assignment)
+    field = "goodbye";
+    return "hello";
+  }
+
+  void value() {
+    @StringVal("hello") String s = "hello";
+    // :: error: (compound.assignment)
+    s += "hello";
+
+    @IntVal(1) int i = 1;
+    // :: error: (compound.assignment)
+    i += 1;
+
+    @IntVal(2) int j = 2;
+    // :: error: (compound.assignment)
+    j += 2;
+
+    // :: error: (assignment)
+    @IntVal(4) int four = j;
+  }
+
+  void value2() {
+    @StringVal("hello") String s = "hello";
+    // :: error: (assignment)
+    s = s + "hello";
+
+    @IntVal(1) int i = 1;
+    // :: error: (assignment)
+    i = i + 1;
+  }
+
+  void noErrorCompoundAssignments() {
+    @IntVal(0) int zero = 0;
+    zero *= 12;
+
+    @StringVal("null") String s = "null";
+    s += "";
+  }
+
+  void errorCompundAssignments() {
+    @StringVal("hello") String s = "hello";
+    // :: error: (compound.assignment)
+    s += "";
+  }
+}
diff --git a/framework/tests/value/DivideByZero.java b/framework/tests/value/DivideByZero.java
new file mode 100644
index 0000000..02c7b0b
--- /dev/null
+++ b/framework/tests/value/DivideByZero.java
@@ -0,0 +1,75 @@
+package value;
+
+import org.checkerframework.common.value.qual.BottomVal;
+import org.checkerframework.common.value.qual.DoubleVal;
+import org.checkerframework.common.value.qual.IntVal;
+
+public class DivideByZero {
+  void divideNoException(@DoubleVal(1.0) float f, @DoubleVal(1.0) double d, @IntVal(1) int i) {
+    @DoubleVal(Float.POSITIVE_INFINITY) float a = f / 0;
+    @DoubleVal(Double.POSITIVE_INFINITY) double b = d / 0l;
+    @DoubleVal(Double.POSITIVE_INFINITY) double c = i / 0.0;
+    @DoubleVal(Float.POSITIVE_INFINITY) float e = i / 0.0f;
+
+    // :: error: (assignment)
+    @BottomVal float a2 = f / 0;
+    // :: error: (assignment)
+    @BottomVal double b2 = d / 0L;
+    // :: error: (assignment)
+    @BottomVal double c2 = i / 0.0;
+    // :: error: (assignment)
+    @BottomVal float e2 = i / 0.0f;
+  }
+
+  void remainderNoException(@DoubleVal(1.0) float f, @DoubleVal(1.0) double d, @IntVal(1) int i) {
+    @DoubleVal(Float.NaN) float a = f % 0;
+    @DoubleVal(Double.NaN) double b = d % 0L;
+    @DoubleVal(Double.NaN) double c = i % 0.0;
+    @DoubleVal(Float.NaN) float e = i % 0.0f;
+
+    // :: error: (assignment)
+    @BottomVal float a2 = f % 0;
+    // :: error: (assignment)
+    @BottomVal double b2 = d % 0l;
+    // :: error: (assignment)
+    @BottomVal double c2 = i % 0.0;
+    // :: error: (assignment)
+    @BottomVal float e2 = i % 0.0f;
+  }
+
+  void integerDivision(
+      @IntVal(1) long l,
+      @IntVal(1) int i,
+      @IntVal(0) byte bZero,
+      @IntVal(0) short sZero,
+      @IntVal(0L) long lZero,
+      @IntVal(0) int iZero) {
+    @BottomVal long a = l / bZero;
+    @BottomVal long b = l / sZero;
+    @BottomVal long c = l / iZero;
+    @BottomVal long d = l / lZero;
+
+    @BottomVal long e = i / bZero;
+    @BottomVal long f = i / sZero;
+    @BottomVal long g = i / iZero;
+    @BottomVal long h = i / lZero;
+  }
+
+  void integerRemainder(
+      @IntVal(1) long l,
+      @IntVal(1) int i,
+      @IntVal(0) byte bZero,
+      @IntVal(0) short sZero,
+      @IntVal(0L) long lZero,
+      @IntVal(0) int iZero) {
+    @BottomVal long a = l % bZero;
+    @BottomVal long b = l % sZero;
+    @BottomVal long c = l % iZero;
+    @BottomVal long d = l % lZero;
+
+    @BottomVal long e = i % bZero;
+    @BottomVal long f = i % sZero;
+    @BottomVal long g = i % iZero;
+    @BottomVal long h = i % lZero;
+  }
+}
diff --git a/framework/tests/value/EmptyAnnotationArgument.java b/framework/tests/value/EmptyAnnotationArgument.java
new file mode 100644
index 0000000..42f0700
--- /dev/null
+++ b/framework/tests/value/EmptyAnnotationArgument.java
@@ -0,0 +1,19 @@
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.BoolVal;
+import org.checkerframework.common.value.qual.DoubleVal;
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class EmptyAnnotationArgument {
+
+  // :: warning: (no.values.given)
+  void mArray(int @ArrayLen({}) [] a) {}
+  // :: warning: (no.values.given)
+  void mBool(@BoolVal({}) boolean arg) {}
+  // :: warning: (no.values.given)
+  void mDouble(@DoubleVal({}) double arg) {}
+  // :: warning: (no.values.given)
+  void mInt(@IntVal({}) int arg) {}
+  // :: warning: (no.values.given)
+  void mString(@StringVal({}) String arg) {}
+}
diff --git a/framework/tests/value/EnumConstants.java b/framework/tests/value/EnumConstants.java
new file mode 100644
index 0000000..be73053
--- /dev/null
+++ b/framework/tests/value/EnumConstants.java
@@ -0,0 +1,172 @@
+// A test for the @EnumVal annotation.
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.file.StandardOpenOption.APPEND;
+import static java.nio.file.StandardOpenOption.CREATE;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import org.checkerframework.common.value.qual.*;
+
+public class EnumConstants {
+  enum MyEnum {
+    VALUE,
+    OTHER_VALUE,
+    THIRD_VALUE
+  }
+
+  static void subtyping1(@EnumVal("VALUE") MyEnum value) {
+    @EnumVal("VALUE") MyEnum value2 = value;
+    // :: error: (assignment)
+    @EnumVal("OTHER_VALUE") MyEnum value3 = value;
+    @UnknownVal MyEnum value4 = value;
+    @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value5 = value;
+  }
+
+  static void subtyping2(@EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value) {
+    // :: error: (assignment)
+    @EnumVal("VALUE") MyEnum value2 = value;
+    // :: error: (assignment)
+    @EnumVal("OTHER_VALUE") MyEnum value3 = value;
+    @UnknownVal MyEnum value4 = value;
+    @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value5 = value;
+    @EnumVal({"VALUE", "OTHER_VALUE", "THIRD_VALUE"}) MyEnum value6 = value;
+  }
+
+  static void enumConstants() {
+    @EnumVal("VALUE") MyEnum v1 = MyEnum.VALUE;
+    @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum v2 = MyEnum.VALUE;
+    // :: error: (assignment)
+    @EnumVal("OTHER_VALUE") MyEnum v3 = MyEnum.VALUE;
+  }
+
+  static void enumToString() {
+    @EnumVal("VALUE") MyEnum v1 = MyEnum.VALUE;
+    // NOT toString(), because programmers can override that. .name() is final.
+    @StringVal("VALUE") String s1 = v1.name();
+  }
+
+  // These are just paranoia based on the implementation strategy for enum constant defaulting.
+  static void nonConstantEnum(MyEnum m) {
+    // :: error: (assignment)
+    @EnumVal("m") MyEnum m2 = m;
+    // :: error: (assignment)
+    @EnumVal("m3") MyEnum m3 = m;
+  }
+
+  static void enums(@EnumVal("VALUE") MyEnum... enums) {}
+
+  static void testEnums() {
+    enums();
+    enums(MyEnum.VALUE);
+    // :: error: (argument)
+    enums(MyEnum.OTHER_VALUE);
+  }
+
+  static void testEnumArraysInConditional(boolean append, String filename) throws IOException {
+    Files.newBufferedWriter(
+        Paths.get(filename),
+        UTF_8,
+        append ? new StandardOpenOption[] {CREATE, APPEND} : new StandardOpenOption[] {CREATE});
+  }
+
+  public static String unescapeJava(String orig, char c) {
+    StringBuilder sb = new StringBuilder();
+    // The previous escape character was seen just before this position.
+    int postEsc = 0;
+    int thisEsc = 0; // orig.indexOf('\\');
+    while (thisEsc != -1) {
+      switch (c) {
+        case 'n':
+          sb.append(orig.substring(postEsc, thisEsc));
+          sb.append('\n'); // not lineSep
+          postEsc = thisEsc + 2;
+          break;
+        case 'r':
+          sb.append(orig.substring(postEsc, thisEsc));
+          sb.append('\r');
+          postEsc = thisEsc + 2;
+          break;
+        case 't':
+          sb.append(orig.substring(postEsc, thisEsc));
+          sb.append('\t');
+          postEsc = thisEsc + 2;
+          break;
+        case '\\':
+          // This is not in the default case because the search would find
+          // the quoted backslash.  Here we include the first backslash in
+          // the output, but not the first.
+          sb.append(orig.substring(postEsc, thisEsc + 1));
+          postEsc = thisEsc + 2;
+          break;
+
+        case 'u':
+          // Unescape Unicode characters.
+          sb.append(orig.substring(postEsc, thisEsc));
+          char unicodeChar = 0;
+          int ii = thisEsc + 2;
+          // The specification permits one or more 'u' characters.
+          while (ii < orig.length() && orig.charAt(ii) == 'u') {
+            ii++;
+          }
+          // The specification requires exactly 4 hexadecimal characters.
+          // This is more liberal.  (Should it be?)
+          int limit = Math.min(ii + 4, orig.length());
+          while (ii < limit) {
+            int thisDigit = Character.digit(orig.charAt(ii), 16);
+            if (thisDigit == -1) {
+              break;
+            }
+            unicodeChar = (char) ((unicodeChar * 16) + thisDigit);
+            ii++;
+          }
+          sb.append(unicodeChar);
+          postEsc = ii;
+          break;
+
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+          // Unescape octal characters.
+          sb.append(orig.substring(postEsc, thisEsc));
+          char octalChar = 0;
+          int iii = thisEsc + 1;
+          while (iii < Math.min(thisEsc + 4, orig.length())) {
+            int thisDigit = Character.digit(orig.charAt(iii), 8);
+            if (thisDigit == -1) {
+              break;
+            }
+            int newValue = (octalChar * 8) + thisDigit;
+            if (newValue > 0377) {
+              break;
+            }
+            octalChar = (char) newValue;
+            iii++;
+          }
+          sb.append(octalChar);
+          postEsc = iii;
+          break;
+
+        default:
+          // In the default case, retain the character following the backslash,
+          // but discard the backslash itself.  "\*" is just a one-character string.
+          sb.append(orig.substring(postEsc, thisEsc));
+          postEsc = thisEsc + 1;
+          break;
+      }
+      thisEsc = orig.indexOf('\\', postEsc);
+    }
+    if (postEsc == 0) {
+      return orig;
+    }
+    sb.append(orig.substring(postEsc));
+    return sb.toString();
+  }
+}
diff --git a/framework/tests/value/EnumValue.java b/framework/tests/value/EnumValue.java
new file mode 100644
index 0000000..1c6b72d
--- /dev/null
+++ b/framework/tests/value/EnumValue.java
@@ -0,0 +1,71 @@
+import org.checkerframework.common.value.qual.*;
+
+public class EnumValue {
+
+  enum Direction {
+    NORTH,
+    WEST,
+    SOUTH,
+    EAST
+  };
+
+  public enum Color {
+    BLUE,
+    RED,
+    GREEN
+  };
+
+  private enum Fruit {
+    APPLE,
+    ORANGE,
+    PEAR
+  };
+
+  void simpleTest() {
+    Direction @ArrayLen(4) [] myCompass = Direction.values();
+    Color @ArrayLen(3) [] myColors = Color.values();
+    Fruit @ArrayLen(3) [] myFruitBasket = Fruit.values();
+
+    // :: error: (assignment)
+    Direction @ArrayLen(7) [] badCompass = Direction.values();
+
+    // :: error: (assignment)
+    Color @ArrayLen(4) [] badColors = Color.values();
+
+    // :: error: (assignment)
+    Fruit @ArrayLen(2) [] badFruit = Fruit.values();
+  }
+
+  public enum AdvDirection {
+    ANORTH {
+      public AdvDirection getOpposite() {
+        return ASOUTH;
+      }
+    },
+    AEAST {
+      public AdvDirection getOpposite() {
+        return AWEST;
+      }
+    },
+    ASOUTH {
+      public AdvDirection getOpposite() {
+        return ANORTH;
+      }
+    },
+    AWEST {
+      public AdvDirection getOpposite() {
+        return AEAST;
+      }
+    };
+
+    public abstract AdvDirection getOpposite();
+  }
+
+  void advTest() {
+    AdvDirection @ArrayLen(4) [] myCompass = AdvDirection.values();
+    // :: error: (assignment)
+    AdvDirection @ArrayLen(3) [] badCompass = AdvDirection.values();
+    // :: error: (assignment)
+    AdvDirection @ArrayLen(5) [] badCompass2 = AdvDirection.values();
+  }
+}
diff --git a/framework/tests/value/ExceptionTest.java b/framework/tests/value/ExceptionTest.java
new file mode 100644
index 0000000..6771207
--- /dev/null
+++ b/framework/tests/value/ExceptionTest.java
@@ -0,0 +1,11 @@
+import org.checkerframework.common.value.qual.*;
+
+public class ExceptionTest {
+
+  public void foo() {
+    int indexTooBig = 5;
+    String s = "hello";
+    // :: warning: (method.evaluation.exception)
+    char c = s.charAt(indexTooBig);
+  }
+}
diff --git a/framework/tests/value/Fields.java b/framework/tests/value/Fields.java
new file mode 100644
index 0000000..aeb8f6e
--- /dev/null
+++ b/framework/tests/value/Fields.java
@@ -0,0 +1,54 @@
+import javax.swing.plaf.BorderUIResource;
+import org.checkerframework.common.value.qual.*;
+
+public class Fields {
+
+  static final int field = 1;
+
+  public void innerClassFields() {
+    @IntVal({9}) int x = java.util.zip.Deflater.BEST_COMPRESSION;
+    @IntVal({4}) int a = BorderUIResource.TitledBorderUIResource.ABOVE_BOTTOM;
+    // :: error: (assignment)
+    @IntVal({0}) int b = BorderUIResource.TitledBorderUIResource.ABOVE_BOTTOM;
+  }
+
+  public void inClassFields() {
+    @IntVal({1}) int a = field;
+    // :: error: (assignment)
+    @IntVal({0}) int b = field;
+  }
+
+  public void otherClassFields() {
+    @IntVal({56319}) char x = Character.MAX_HIGH_SURROGATE;
+    @IntVal({16}) byte y = Character.FORMAT;
+
+    @BoolVal({false}) boolean a = Boolean.FALSE;
+    // :: error: (assignment)
+    a = Boolean.TRUE;
+
+    @IntVal({4}) int b = java.util.Calendar.MAY;
+    // :: error: (assignment)
+    b = java.util.Calendar.APRIL;
+
+    @IntVal({9}) int c = java.util.zip.Deflater.BEST_COMPRESSION;
+    // :: error: (assignment)
+    c = java.util.zip.Deflater.BEST_SPEED;
+
+    @IntVal({1024}) int d = java.awt.GridBagConstraints.ABOVE_BASELINE;
+    // :: error: (assignment)
+    d = java.awt.GridBagConstraints.LAST_LINE_END;
+  }
+
+  void innerFieldTest() {
+    @StringVal("section_number") String a = InnerStaticClass.INNER_STATIC_FIELD;
+
+    // :: error: (assignment)
+    @StringVal("") String b = InnerStaticClass.INNER_STATIC_FIELD;
+  }
+
+  static final int fieldDeclAtBottom = 1;
+
+  public static class InnerStaticClass {
+    public static final String INNER_STATIC_FIELD = "section_number";
+  }
+}
diff --git a/framework/tests/value/GTETransferBug.java b/framework/tests/value/GTETransferBug.java
new file mode 100644
index 0000000..fb4e942
--- /dev/null
+++ b/framework/tests/value/GTETransferBug.java
@@ -0,0 +1,12 @@
+import org.checkerframework.common.value.qual.*;
+
+public class GTETransferBug {
+  void gte_bad_check(int[] a) {
+    if (a.length >= 1) {
+      // :: error: (assignment)
+      int @ArrayLenRange(from = 2) [] b = a;
+
+      int @ArrayLenRange(from = 1) [] c = a;
+    }
+  }
+}
diff --git a/framework/tests/value/Issue1214.java b/framework/tests/value/Issue1214.java
new file mode 100644
index 0000000..c1fd86f
--- /dev/null
+++ b/framework/tests/value/Issue1214.java
@@ -0,0 +1,70 @@
+// Test case for issue #1214:
+// https://github.com/typetools/checker-framework/issues/1214
+
+import org.checkerframework.common.value.qual.*;
+
+public class Issue1214 {
+
+  static void noException() {
+    int n = 0;
+    try {
+    } catch (Exception e) {
+      n = 1;
+    }
+    @IntVal(0) int ok = n;
+  }
+
+  static void arrayAccess(String[] array) {
+    int n = 0;
+    try {
+      String s = array[0];
+    } catch (NullPointerException e) {
+      n = 1;
+    } catch (ArrayIndexOutOfBoundsException e) {
+      n = 2;
+    } catch (Exception e) {
+      n = 3;
+    }
+    @IntVal({0, 1, 2}) int ok = n;
+    // :: error: (assignment)
+    @IntVal({0, 1}) int ng1 = n;
+    // :: error: (assignment)
+    @IntVal({0, 2}) int ng2 = n;
+    // :: error: (assignment)
+    @IntVal(0) int ng3 = n;
+  }
+
+  static void forArray(String[] array) {
+    int n = 0;
+    try {
+      for (String s : array) {}
+    } catch (NullPointerException e) {
+      n = 1;
+    } catch (ArrayIndexOutOfBoundsException e) {
+      n = 2;
+    } catch (Exception e) {
+      n = 3;
+    }
+    @IntVal({0, 1}) int ok = n;
+    // :: error: (assignment)
+    @IntVal(0) int ng = n;
+  }
+
+  static void forIterable(Iterable<String> itr) {
+    int n = 0;
+    try {
+      for (String s : itr) {}
+    } catch (NullPointerException e) {
+      n = 1;
+    } catch (Exception e) {
+      n = 2;
+    }
+    @IntVal({0, 1, 2}) int ok = n;
+    // :: error: (assignment)
+    @IntVal({0, 1}) int ng1 = n;
+    // :: error: (assignment)
+    @IntVal({0, 2}) int ng2 = n;
+    // :: error: (assignment)
+    @IntVal(0) int ng3 = n;
+  }
+}
diff --git a/framework/tests/value/Issue1218.java b/framework/tests/value/Issue1218.java
new file mode 100644
index 0000000..989677b
--- /dev/null
+++ b/framework/tests/value/Issue1218.java
@@ -0,0 +1,144 @@
+// Test case for Issue 1218:
+// https://github.com/typetools/checker-framework/issues/1218
+
+import java.io.Serializable;
+import org.checkerframework.common.value.qual.*;
+
+public class Issue1218 {
+
+  enum MyEnum {
+    A,
+    B,
+    C;
+  }
+
+  class ForString {
+    ForString(String @MinLen(2) ... strs) {}
+  }
+
+  class ForInt {
+    ForInt(@IntVal({1, 2, 3}) int @MinLen(2) ... strs) {}
+  }
+
+  class ForEnum<E extends Enum<E>> {
+    @SafeVarargs
+    ForEnum(E @MinLen(2) ... enums) {}
+  }
+
+  class ForAny<T> {
+    @SafeVarargs
+    ForAny(T @MinLen(3) ... anys) {}
+  }
+
+  void strs(String @MinLen(2) ... strs) {}
+
+  void ints(@IntVal({1, 2, 3}) int @MinLen(2) ... ints) {}
+
+  @SafeVarargs
+  final <E extends Enum<E>> void enums(E @MinLen(2) ... enums) {}
+
+  @SafeVarargs
+  final <T> void anys(T @MinLen(3) ... anys) {}
+
+  void testMethodCall() {
+    // :: error: (varargs)
+    strs();
+    // :: error: (varargs)
+    strs("");
+    strs("", "");
+    // type of arg should be @UnknownVal String @BottomVal []
+    strs((String[]) null);
+
+    String[] args0 = {""};
+    String[] args1 = {""};
+    String[] args2 = {"", ""};
+
+    // :: error: (argument)
+    strs(args0);
+    // :: error: (argument)
+    strs(args1);
+    strs(args2);
+
+    ints(1, 2);
+    // :: error: (argument)
+    ints(0, 0, 0);
+    // :: error: (varargs)
+    ints(3);
+    // type of arg should be @IntVal(1) int @BottomVal []
+    ints((@IntVal(1) int[]) null);
+  }
+
+  // Inferred enumval types are incompatible with <E extends Enum<E>>. Similar code
+  // works if the type is a specific enum; see the test file Enums.java for an example.
+  @SuppressWarnings("type.argument")
+  void testMethodCallTypeInferred() {
+    // :: error: (varargs)
+    enums();
+    // :: error: (varargs)
+    enums(MyEnum.A);
+    enums(MyEnum.A, MyEnum.B);
+    enums(MyEnum.A, MyEnum.B, MyEnum.C);
+  }
+
+  <T extends Comparable<T> & Serializable> void testMethodCallTypeInferredIntersection() {
+    T t = null;
+
+    // :: error: (varargs)
+    anys(1, 1.0);
+    // :: error: (varargs)
+    anys(1, "");
+    anys(1, 1.0, "");
+    // :: error: (varargs)
+    anys(1, t);
+    anys(1, t, "");
+  }
+
+  void testConstructorCall() {
+    // :: error: (varargs)
+    new ForString();
+    // :: error: (varargs)
+    new ForString("");
+    new ForString("", "");
+    // type of arg should be @UnknownVal String @BottomVal []
+    new ForString((String[]) null);
+
+    String[] args0 = {""};
+    String[] args1 = {""};
+    String[] args2 = {"", ""};
+
+    // :: error: (argument)
+    new ForString(args0);
+    // :: error: (argument)
+    new ForString(args1);
+    new ForString(args2);
+
+    new ForInt(1, 2);
+    // :: error: (argument)
+    new ForInt(0, 0, 0);
+    // :: error: (varargs)
+    new ForInt(3);
+    // type of arg should be @IntVal(1) int @BottomVal []
+    ints((@IntVal(1) int[]) null);
+  }
+
+  void testConstructorCallTypeInferred() {
+    // :: error: (varargs)
+    new ForEnum<>(MyEnum.A);
+    new ForEnum<>(MyEnum.A, MyEnum.B);
+    new ForEnum<>(MyEnum.A, MyEnum.B, MyEnum.C);
+  }
+
+  @SuppressWarnings("unchecked")
+  <T extends Comparable<T> & Serializable> void testConstructorCallTypeInferredIntersection() {
+    T t = null;
+
+    // :: error: (varargs)
+    new ForAny<>(1, 1.0);
+    // :: error: (varargs)
+    new ForAny<>(1, "");
+    new ForAny<>(1, 1.0, "");
+    // :: error: (varargs)
+    new ForAny<>(1, t);
+    new ForAny<>(1, t, "");
+  }
+}
diff --git a/framework/tests/value/Issue1229.java b/framework/tests/value/Issue1229.java
new file mode 100644
index 0000000..9337193
--- /dev/null
+++ b/framework/tests/value/Issue1229.java
@@ -0,0 +1,19 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Issue1229 {
+
+  Object @ArrayLen(1) [] @ArrayLen(1) [] o3 = new Object[][] {null};
+
+  @IntVal({0, 1, 2, 3}) int[] a1 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3};
+
+  int[] a2 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3};
+
+  @IntVal({0, 1, 2, 3}) int[] a3 = new int[] {0, 1, 2, 3};
+
+  void test() {
+
+    @IntVal({0, 1, 2, 3}) int[] a1 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3};
+    int[] a2 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3};
+    @IntVal({0, 1, 2, 3}) int[] a3 = new int[] {0, 1, 2, 3};
+  }
+}
diff --git a/framework/tests/value/Issue1423.java b/framework/tests/value/Issue1423.java
new file mode 100644
index 0000000..571d390
--- /dev/null
+++ b/framework/tests/value/Issue1423.java
@@ -0,0 +1,14 @@
+// Test case for Issue 1423:
+// https://github.com/typetools/checker-framework/issues/1423
+
+import org.checkerframework.common.value.qual.IntRange;
+
+public class Issue1423 {
+  void loop(int i) {
+    int a = 0;
+    while (i >= 2) {
+      @IntRange(from = 2) int i2 = i;
+      ++a;
+    }
+  }
+}
diff --git a/framework/tests/value/Issue1579.java b/framework/tests/value/Issue1579.java
new file mode 100644
index 0000000..fda84f1
--- /dev/null
+++ b/framework/tests/value/Issue1579.java
@@ -0,0 +1,10 @@
+// Test case for Issue 1579
+// https://github.com/typetools/checker-framework/issues/1579
+
+public class Issue1579 {
+  public int[][] method(int[] array1, int[] array2) {
+    // Required for crash
+    for (int i = 0; i < array1.length; i++) {}
+    return new int[][] {array1, array2};
+  }
+}
diff --git a/framework/tests/value/Issue1580.java b/framework/tests/value/Issue1580.java
new file mode 100644
index 0000000..c1d5de0
--- /dev/null
+++ b/framework/tests/value/Issue1580.java
@@ -0,0 +1,12 @@
+// Test case for Issue 1580
+// https://github.com/typetools/checker-framework/issues/1580
+
+public class Issue1580<R, K extends Issue1580<R, K>> {
+  protected final Gen<R> field;
+
+  protected Issue1580(K parent) {
+    field = parent.field;
+  }
+
+  static class Gen<T> {}
+}
diff --git a/framework/tests/value/Issue1655.java b/framework/tests/value/Issue1655.java
new file mode 100644
index 0000000..6bc027c
--- /dev/null
+++ b/framework/tests/value/Issue1655.java
@@ -0,0 +1,13 @@
+// Test case for Issue 1655
+// https://github.com/typetools/checker-framework/issues/1655
+
+import org.checkerframework.common.value.qual.IntRange;
+
+public class Issue1655 {
+
+  public void test(int a) {
+    @IntRange(from = 0, to = 255) int b = a & 0xff;
+    @IntRange(from = 0, to = 15) int c1 = b >> 4;
+    @IntRange(from = 0, to = 15) int c2 = b >>> 4;
+  }
+}
diff --git a/framework/tests/value/Issue2353.java b/framework/tests/value/Issue2353.java
new file mode 100644
index 0000000..df6a537
--- /dev/null
+++ b/framework/tests/value/Issue2353.java
@@ -0,0 +1,7 @@
+@SuppressWarnings("deprecation") // Deprecated in Java 11
+public class Issue2353 {
+
+  public static void play() {
+    Integer a = new Integer("2");
+  }
+}
diff --git a/framework/tests/value/Issue2367.java b/framework/tests/value/Issue2367.java
new file mode 100644
index 0000000..f1c9fe3
--- /dev/null
+++ b/framework/tests/value/Issue2367.java
@@ -0,0 +1,34 @@
+// Test case for issue #2367: http://tinyurl.com/cfissue/2367
+
+public class Issue2367 {
+
+  // Within the signed byte range
+
+  byte b1 = 75;
+  byte b2 = (byte) 75;
+  byte b3 = (byte) -120;
+
+  // Outside the signed byte range
+
+  // Without the `(byte)` cast, all of these produce the following javac error:
+  //   error: incompatible types: possible lossy conversion from int to byte
+  // The Value Checker's `cast.unsafe` error is analogous and is desirable.
+
+  // :: warning: (cast.unsafe)
+  byte b4 = (byte) 139; // b4 == -117
+  // :: warning: (cast.unsafe)
+  byte b5 = (byte) -240;
+  // :: warning: (cast.unsafe)
+  byte b6 = (byte) 251;
+
+  // Outside the signed byte range, but written as a hexadecimal literal.
+
+  // As a special case, the Constant Value Checker could not issue a warning when a value is
+  // within the signed byte range AND the value was specified in hexadecimal.
+  // Such a special case is not yet implemented, and I don't see how to do so.
+  // The program element "(byte) 0x8B" has already been converted to "(byte)139" by javac before
+  // the Checker Framework gets access to it.
+
+  // :: warning: (cast.unsafe)
+  byte b7 = (byte) 0x8B; // 0x8B == 137, and b4 == -117
+}
diff --git a/framework/tests/value/Issue3001.java b/framework/tests/value/Issue3001.java
new file mode 100644
index 0000000..ea1090a
--- /dev/null
+++ b/framework/tests/value/Issue3001.java
@@ -0,0 +1,7 @@
+public class Issue3001 {
+
+  private <T> T getMember(Class<T> type) {
+    T sym = getMember(type);
+    return sym;
+  }
+}
diff --git a/framework/tests/value/Issue3105.java b/framework/tests/value/Issue3105.java
new file mode 100644
index 0000000..fe68f84
--- /dev/null
+++ b/framework/tests/value/Issue3105.java
@@ -0,0 +1,32 @@
+// Test case for issue #3105: https://tinyurl.com/cfissue/3105
+
+// Issue3105Fields is
+// framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java.
+import org.checkerframework.common.value.qual.StringVal;
+import org.checkerframework.framework.testchecker.lib.Issue3105Fields;
+
+public class Issue3105 {
+  class Demo1 {
+    @StringVal("foo") String m() {
+      return Issue3105Fields.FIELD1;
+    }
+  }
+
+  class Demo2 extends Issue3105Fields {
+    @StringVal("foo") String m() {
+      return FIELD1;
+    }
+  }
+
+  class Demo3 {
+    @StringVal("bar") String m() {
+      return Issue3105Fields.FIELD2;
+    }
+  }
+
+  class Demo4 extends Issue3105Fields {
+    @StringVal("bar") String m() {
+      return FIELD2;
+    }
+  }
+}
diff --git a/framework/tests/value/Issue3105FieldInSameClass.java b/framework/tests/value/Issue3105FieldInSameClass.java
new file mode 100644
index 0000000..b2c01f8
--- /dev/null
+++ b/framework/tests/value/Issue3105FieldInSameClass.java
@@ -0,0 +1,19 @@
+// Test case for issue #3105: https://tinyurl.com/cfissue/3105
+
+import org.checkerframework.common.value.qual.StringVal;
+
+public class Issue3105FieldInSameClass {
+  public static final String FIELD1 = "foo";
+}
+
+class Demo1 {
+  @StringVal("foo") String m() {
+    return Issue3105FieldInSameClass.FIELD1;
+  }
+}
+
+class Demo2 extends Issue3105FieldInSameClass {
+  @StringVal("foo") String m() {
+    return FIELD1;
+  }
+}
diff --git a/framework/tests/value/Issue3105StaticImport.java b/framework/tests/value/Issue3105StaticImport.java
new file mode 100644
index 0000000..a750c48
--- /dev/null
+++ b/framework/tests/value/Issue3105StaticImport.java
@@ -0,0 +1,12 @@
+// Issue3105Fields is
+// framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java.
+import static org.checkerframework.framework.testchecker.lib.Issue3105Fields.FIELD2;
+
+import org.checkerframework.common.value.qual.StringVal;
+
+public class Issue3105StaticImport {
+
+  @StringVal("bar") String m2() {
+    return FIELD2;
+  }
+}
diff --git a/framework/tests/value/Issue3307.java b/framework/tests/value/Issue3307.java
new file mode 100644
index 0000000..ab007bb
--- /dev/null
+++ b/framework/tests/value/Issue3307.java
@@ -0,0 +1,17 @@
+// Test case for https://github.com/typetools/checker-framework/issues/3307
+
+public class Issue3307 extends A<Integer> {
+  private int a = 0;
+
+  void bar(int value) {
+    if (a != value) {
+      a = value;
+      foo(value);
+    }
+  }
+}
+
+@SuppressWarnings("unchecked")
+abstract class A<B> {
+  protected final void foo(B... values) {}
+}
diff --git a/framework/tests/value/Issue867.java b/framework/tests/value/Issue867.java
new file mode 100644
index 0000000..f5f1bad
--- /dev/null
+++ b/framework/tests/value/Issue867.java
@@ -0,0 +1,68 @@
+// Test case for Issue 867:
+// https://github.com/typetools/checker-framework/issues/867
+
+import org.checkerframework.common.value.qual.*;
+
+public class Issue867 {
+  void test1() {
+    @IntVal({0, 1}) int x = 0;
+    @IntVal(0) int zero = x++;
+    @IntVal(1) int one = x;
+    // :: error: (unary.increment)
+    x++;
+
+    x = 1;
+    one = x--;
+    zero = x;
+    // :: error: (unary.decrement)
+    x--;
+  }
+
+  void test2() {
+    @IntVal({0, 1, 2}) int x = 0;
+    @IntVal(1) int one = x++ + x++;
+    @IntVal(2) int two = x;
+    // :: error: (unary.increment)
+    x++;
+
+    x = 2;
+    @IntVal(3) int three = x-- + x--;
+    @IntVal(0) int zero = x;
+    // :: error: (unary.decrement)
+    x--;
+  }
+
+  void test3() {
+    @IntVal({0, 1, 2}) int x = 0;
+    @IntVal(2) int two = x++ + ++x;
+    two = x;
+    // :: error: (unary.increment)
+    x++;
+
+    x = 2;
+    two = x-- + --x;
+    @IntVal(0) int zero = x;
+    // :: error: (unary.decrement)
+    x--;
+  }
+
+  void test4() {
+    @IntVal({0, 1}) int x = 0;
+    m0(x++);
+    // :: error: (argument)
+    m0(x);
+    // :: error: (unary.increment)
+    m1(x++);
+
+    x = 1;
+    m1(x--);
+    // :: error: (argument)
+    m1(x);
+    // :: error: (unary.decrement)
+    m0(x--);
+  }
+
+  void m0(@IntVal(0) int x) {}
+
+  void m1(@IntVal(1) int x) {}
+}
diff --git a/framework/tests/value/LengthTransferForMinLen.java b/framework/tests/value/LengthTransferForMinLen.java
new file mode 100644
index 0000000..00066a4
--- /dev/null
+++ b/framework/tests/value/LengthTransferForMinLen.java
@@ -0,0 +1,23 @@
+import org.checkerframework.common.value.qual.*;
+
+public class LengthTransferForMinLen {
+  void exceptional_control_flow(int[] a) {
+    if (a.length == 0) {
+      throw new IllegalArgumentException();
+    }
+    int @MinLen(1) [] b = a;
+  }
+
+  void equal_to_return(int[] a) {
+    if (a.length == 0) {
+      return;
+    }
+    int @MinLen(1) [] b = a;
+  }
+
+  void gt_check(int[] a) {
+    if (a.length > 0) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+}
diff --git a/framework/tests/value/LiteralArray.java b/framework/tests/value/LiteralArray.java
new file mode 100644
index 0000000..7251f44
--- /dev/null
+++ b/framework/tests/value/LiteralArray.java
@@ -0,0 +1,12 @@
+import org.checkerframework.common.value.qual.*;
+
+public class LiteralArray {
+
+  private static final String[] timeFormat = {
+    ("#.#"), ("#.#"), ("#.#"), ("#.#"), ("#.#"),
+  };
+
+  public void test() {
+    String @ArrayLen(5) [] array = timeFormat;
+  }
+}
diff --git a/framework/tests/value/LongMax.java b/framework/tests/value/LongMax.java
new file mode 100644
index 0000000..d0ad214
--- /dev/null
+++ b/framework/tests/value/LongMax.java
@@ -0,0 +1,15 @@
+import org.checkerframework.common.value.qual.IntRange;
+
+// Test for PR 1370: https://github.com/typetools/checker-framework/pull/1370
+// This test contains range annotations with both bounds equal to Long.MAX_VALUE The Value checker
+// tried to extract a list of values from that range by looping from the lower bound to the upper
+// bound. The loop was implemented in a way that if the upper bound is Long.MAX_VALUE, the loop
+// condition was always true, the loop variable overflowed and OutOfMemoryError was caused by
+// infinitely growing the list of values.
+public class LongMax {
+
+  public void longMaxRange() {
+    @IntRange(from = 9223372036854775807l, to = 9223372036854775807l) long i = 9223372036854775807l;
+    @IntRange(from = 9223372036854775807l, to = 9223372036854775807l) long j = i;
+  }
+}
diff --git a/framework/tests/value/Loop.java b/framework/tests/value/Loop.java
new file mode 100644
index 0000000..007b934
--- /dev/null
+++ b/framework/tests/value/Loop.java
@@ -0,0 +1,36 @@
+import java.util.Iterator;
+import java.util.Set;
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.BottomVal;
+import org.checkerframework.common.value.qual.IntVal;
+
+public class Loop {
+  void test1(final double @ArrayLen(20) [] f2) {
+    int x;
+    for (int g = 0; g < f2.length; g++) {
+      x = g;
+    }
+    // :: error: (assignment)
+    double @BottomVal [] test = f2;
+    // :: error: (assignment)
+    @BottomVal int q = f2.length;
+  }
+
+  void test2(final @IntVal(20) int param) {
+    int x;
+    for (int g = 0; g < param; g++) {
+      x = g;
+    }
+    // :: error: (assignment)
+    @BottomVal int q = param;
+  }
+
+  private void test3(Set<String> set) {
+    String[] array = new String[set.size()];
+    int i = 0;
+    for (Iterator<String> iter = set.iterator(); iter.hasNext(); ) {
+      String key = iter.next();
+      array[i++] = key;
+    }
+  }
+}
diff --git a/framework/tests/value/LubToRange.java b/framework/tests/value/LubToRange.java
new file mode 100644
index 0000000..d83c764
--- /dev/null
+++ b/framework/tests/value/LubToRange.java
@@ -0,0 +1,18 @@
+package value;
+
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.ArrayLenRange;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.IntVal;
+
+public class LubToRange {
+  public static boolean flag = false;
+
+  void test(@IntVal({1, 2, 3, 4, 5}) int x, @IntVal({6, 7, 8, 9, 10, 11}) int y) {
+    @IntRange(from = 1, to = 11) int z = flag ? x : y;
+  }
+
+  void test2(int @ArrayLen({1, 2, 3, 4, 5}) [] x, int @ArrayLen({6, 7, 8, 9, 10, 11}) [] y) {
+    int @ArrayLenRange(from = 1, to = 11) [] z = flag ? x : y;
+  }
+}
diff --git a/framework/tests/value/MLEqualTo.java b/framework/tests/value/MLEqualTo.java
new file mode 100644
index 0000000..abfd04d
--- /dev/null
+++ b/framework/tests/value/MLEqualTo.java
@@ -0,0 +1,10 @@
+import org.checkerframework.common.value.qual.*;
+
+public class MLEqualTo {
+
+  public static void equalToMinLen(int @MinLen(2) [] m, int @MinLen(0) [] r) {
+    if (r == m) {
+      int @MinLen(2) [] j = r;
+    }
+  }
+}
diff --git a/framework/tests/value/MathMinMax.java b/framework/tests/value/MathMinMax.java
new file mode 100644
index 0000000..917df83
--- /dev/null
+++ b/framework/tests/value/MathMinMax.java
@@ -0,0 +1,35 @@
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.IntVal;
+
+public class MathMinMax {
+  void mathTest1(@IntRange(from = 0, to = 10) int x, @IntRange(from = 5, to = 15) int y) {
+    @IntRange(from = 0, to = 10) int min = Math.min(x, y);
+    @IntRange(from = 5, to = 15) int max = Math.max(x, y);
+  }
+
+  void mathTest2(@IntRange(from = 0, to = 10) int x, @IntRange(from = 11, to = 15) int y) {
+    @IntRange(from = 0, to = 10) int min = Math.min(x, y);
+    @IntRange(from = 11, to = 15) int max = Math.max(x, y);
+  }
+
+  void mathTest3(@IntRange(from = 0, to = 20) int x, @IntRange(from = 5, to = 15) int y) {
+    @IntRange(from = 0, to = 15) int min = Math.min(x, y);
+    @IntRange(from = 5, to = 20) int max = Math.max(x, y);
+    @IntVal(1) int minConst = Math.min(1, 2);
+    @IntVal(2) int maxConst = Math.max(-1, 2);
+  }
+
+  void mathTest(long x, long y) {
+    long min = Math.min(x, y);
+    long max = Math.max(x, y);
+  }
+
+  void mathTest(double x, double y) {
+    double min = Math.min(x, y);
+    double max = Math.max(x, y);
+  }
+
+  void mathTetMax(@IntRange int x, @IntRange int y) {
+    @IntRange int z = Math.min(x, y);
+  }
+}
diff --git a/framework/tests/value/Methods.java b/framework/tests/value/Methods.java
new file mode 100644
index 0000000..aad1ba0
--- /dev/null
+++ b/framework/tests/value/Methods.java
@@ -0,0 +1,84 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Methods {
+
+  static int i = 3;
+  static final int k = 3;
+
+  public static void Length() {
+    String a = "hello";
+    @IntVal({5}) int b = a.length();
+
+    String e = "hello";
+    int f = 2;
+    if (true) {
+      f = 1;
+      e = "world";
+    }
+    @IntVal({'e', 'l', 'o', 'r'}) char g = e.charAt(f);
+
+    // :: error: (assignment)
+    @IntVal({'l'}) char h = e.charAt(i);
+
+    @IntVal({'l'}) char j = e.charAt(k);
+  }
+
+  public static void Boolean() {
+    String a = "true";
+    @BoolVal({true}) boolean b = Boolean.valueOf(a);
+  }
+
+  public static void Byte() {
+    @IntVal({127}) byte a = Byte.MAX_VALUE;
+    @IntVal({-128}) byte b = Byte.MIN_VALUE;
+
+    String c = "59";
+    int d = 10;
+    @IntVal({59}) byte e = Byte.valueOf(c, d);
+  }
+
+  public static void Character() {
+    @IntVal({'c'}) char a = Character.toLowerCase('C');
+
+    // :: error: (assignment)
+    @BoolVal({false}) boolean b = Character.isWhitespace('\t');
+  }
+
+  public static void Double() {
+    @DoubleVal({Double.MAX_VALUE}) double a = Double.MAX_VALUE;
+    String b = "59.32";
+    @DoubleVal({59.32}) double c = Double.valueOf(b);
+  }
+
+  public static void Float() {
+    @IntVal({Float.MIN_EXPONENT}) int a = Float.MIN_EXPONENT;
+    String b = "59.32";
+    @DoubleVal({59.32f}) float c = Float.valueOf(b);
+  }
+
+  public static void Integer() {
+    @IntVal({Integer.SIZE}) int a = Integer.SIZE;
+    String b = "0";
+    @IntVal({0}) int c = Integer.valueOf(b);
+  }
+
+  public static void Long() {
+    @IntVal({Long.MAX_VALUE}) long a = Long.MAX_VALUE;
+    String b = "53";
+    @IntVal({53L}) long c = Long.valueOf(53L);
+  }
+
+  public static void Short() {
+    @IntVal({Short.MIN_VALUE}) short a = Short.MIN_VALUE;
+
+    String b = "53";
+    @IntVal({53}) short c = Short.valueOf(b);
+  }
+
+  public static void String() {
+
+    @StringVal({"herro"}) String a = "hello".replace('l', 'r');
+    // :: error: (assignment)
+    @StringVal({"hello"}) String b = "hello".replace('l', 'r');
+  }
+}
diff --git a/framework/tests/value/MinLenConstants.java b/framework/tests/value/MinLenConstants.java
new file mode 100644
index 0000000..1a27763
--- /dev/null
+++ b/framework/tests/value/MinLenConstants.java
@@ -0,0 +1,8 @@
+import org.checkerframework.common.value.qual.*;
+
+public class MinLenConstants {
+
+  void test() {
+    int @MinLen(3) [] arr = {1, 2, 3};
+  }
+}
diff --git a/framework/tests/value/MinLenEqTransfer.java b/framework/tests/value/MinLenEqTransfer.java
new file mode 100644
index 0000000..e86b058
--- /dev/null
+++ b/framework/tests/value/MinLenEqTransfer.java
@@ -0,0 +1,31 @@
+import org.checkerframework.common.value.qual.*;
+
+public class MinLenEqTransfer {
+  void eq_check(int[] a) {
+    if (1 == a.length) {
+      int @MinLen(1) [] b = a;
+    }
+    if (a.length == 1) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+
+  void eq_bad_check(int[] a) {
+    if (1 == a.length) {
+      // :: error: (assignment)
+      int @MinLen(2) [] b = a;
+    }
+  }
+
+  int @MinLen(2) [] test(int[] a) {
+    if (a.length == 100 || a.length == 3) {
+      int x = a.length;
+      return a;
+    } else if (a.length == 0 || a.length == 1) {
+      int x = a.length;
+      // :: error: (return)
+      return a;
+    }
+    return new int[] {1, 2};
+  }
+}
diff --git a/framework/tests/value/MinLenFieldInvar.java b/framework/tests/value/MinLenFieldInvar.java
new file mode 100644
index 0000000..66e8420
--- /dev/null
+++ b/framework/tests/value/MinLenFieldInvar.java
@@ -0,0 +1,59 @@
+import org.checkerframework.common.value.qual.*;
+import org.checkerframework.framework.qual.FieldInvariant;
+
+public class MinLenFieldInvar {
+  class Super {
+    public final int @MinLen(2) [] minlen2;
+
+    public Super(int @MinLen(2) [] minlen2) {
+      this.minlen2 = minlen2;
+    }
+  }
+
+  // :: error: (field.invariant.not.subtype)
+  @MinLenFieldInvariant(field = "minlen2", minLen = 1)
+  class InvalidSub extends Super {
+    public InvalidSub() {
+      super(new int[] {1, 2});
+    }
+  }
+
+  @MinLenFieldInvariant(field = "minlen2", minLen = 4)
+  class ValidSub extends Super {
+    public final int[] validSubField;
+
+    public ValidSub(int[] validSubField) {
+      super(new int[] {1, 2, 3, 4});
+      this.validSubField = validSubField;
+    }
+  }
+
+  // :: error: (field.invariant.not.found.superclass)
+  @MinLenFieldInvariant(field = "validSubField", minLen = 3)
+  class InvalidSubSub1 extends ValidSub {
+    public InvalidSubSub1() {
+      super(new int[] {1, 2});
+    }
+  }
+
+  // :: error: (field.invariant.not.subtype.superclass)
+  @MinLenFieldInvariant(field = "minlen2", minLen = 3)
+  class InvalidSubSub2 extends ValidSub {
+    public InvalidSubSub2() {
+      super(new int[] {1, 2});
+    }
+  }
+
+  @FieldInvariant(field = "minlen2", qualifier = BottomVal.class)
+  @MinLenFieldInvariant(field = "validSubField", minLen = 4)
+  class ValidSubSub extends ValidSub {
+    public ValidSubSub() {
+      super(null);
+    }
+
+    void test() {
+      int @BottomVal [] bot = minlen2;
+      int @MinLen(4) [] four = validSubField;
+    }
+  }
+}
diff --git a/framework/tests/value/MinLenFieldInvar2.java b/framework/tests/value/MinLenFieldInvar2.java
new file mode 100644
index 0000000..6e58400
--- /dev/null
+++ b/framework/tests/value/MinLenFieldInvar2.java
@@ -0,0 +1,31 @@
+// Test case for https://github.com/kelloggm/checker-framework/issues/156
+
+// @skip-test until the issue is fixed
+
+import org.checkerframework.common.value.qual.MinLen;
+import org.checkerframework.common.value.qual.MinLenFieldInvariant;
+
+public class MinLenFieldInvar2 {
+  class Super {
+    public final int @MinLen(2) [] minlen2;
+
+    public Super(int @MinLen(2) [] minlen2) {
+      this.minlen2 = minlen2;
+    }
+  }
+
+  @MinLenFieldInvariant(field = "minlen2", minLen = 4)
+  class ValidSub extends Super {
+    public ValidSub(int[] validSubField) {
+      super(new int[] {1, 2, 3, 4});
+    }
+  }
+
+  void useAtValidSub(Super s) {
+    if (s instanceof ValidSub) {
+      System.out.println(s.minlen2[3]);
+      ValidSub vs = (ValidSub) s;
+      System.out.println(vs.minlen2[3]);
+    }
+  }
+}
diff --git a/framework/tests/value/MinLenGTETransfer.java b/framework/tests/value/MinLenGTETransfer.java
new file mode 100644
index 0000000..61fe182
--- /dev/null
+++ b/framework/tests/value/MinLenGTETransfer.java
@@ -0,0 +1,16 @@
+import org.checkerframework.common.value.qual.*;
+
+public class MinLenGTETransfer {
+  void gte_check(int[] a) {
+    if (a.length >= 1) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+
+  void gte_bad_check(int[] a) {
+    if (a.length >= 1) {
+      // :: error: (assignment)
+      int @MinLen(2) [] b = a;
+    }
+  }
+}
diff --git a/framework/tests/value/MinLenGTTransfer.java b/framework/tests/value/MinLenGTTransfer.java
new file mode 100644
index 0000000..5fcd74a
--- /dev/null
+++ b/framework/tests/value/MinLenGTTransfer.java
@@ -0,0 +1,16 @@
+import org.checkerframework.common.value.qual.*;
+
+public class MinLenGTTransfer {
+  void gt_check(int[] a) {
+    if (a.length > 0) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+
+  void gt_bad_check(int[] a) {
+    if (a.length > 0) {
+      // :: error: (assignment)
+      int @MinLen(2) [] b = a;
+    }
+  }
+}
diff --git a/framework/tests/value/MinLenLTETransfer.java b/framework/tests/value/MinLenLTETransfer.java
new file mode 100644
index 0000000..1e3f1da
--- /dev/null
+++ b/framework/tests/value/MinLenLTETransfer.java
@@ -0,0 +1,16 @@
+import org.checkerframework.common.value.qual.*;
+
+public class MinLenLTETransfer {
+  void lte_check(int[] a) {
+    if (1 <= a.length) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+
+  void lte_bad_check(int[] a) {
+    if (1 <= a.length) {
+      // :: error: (assignment)
+      int @MinLen(2) [] b = a;
+    }
+  }
+}
diff --git a/framework/tests/value/MinLenLTTransfer.java b/framework/tests/value/MinLenLTTransfer.java
new file mode 100644
index 0000000..8841e2d
--- /dev/null
+++ b/framework/tests/value/MinLenLTTransfer.java
@@ -0,0 +1,16 @@
+import org.checkerframework.common.value.qual.*;
+
+public class MinLenLTTransfer {
+  void lt_check(int[] a) {
+    if (0 < a.length) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+
+  void lt_bad_check(int[] a) {
+    if (0 < a.length) {
+      // :: error: (assignment)
+      int @MinLen(2) [] b = a;
+    }
+  }
+}
diff --git a/framework/tests/value/MinLenLUB.java b/framework/tests/value/MinLenLUB.java
new file mode 100644
index 0000000..1a5fa08
--- /dev/null
+++ b/framework/tests/value/MinLenLUB.java
@@ -0,0 +1,43 @@
+import org.checkerframework.common.value.qual.*;
+
+public class MinLenLUB {
+
+  public static void MinLen(int @MinLen(10) [] arg, int @MinLen(4) [] arg2) {
+    int[] arr;
+    if (true) {
+      arr = arg;
+    } else {
+      arr = arg2;
+    }
+    // :: error: (assignment)
+    int @MinLen(10) [] res = arr;
+    int @MinLen(4) [] res2 = arr;
+    // :: error: (assignment)
+    int @BottomVal [] res3 = arr;
+  }
+
+  public static void Bottom(int @BottomVal [] arg, int @MinLen(4) [] arg2) {
+    int[] arr;
+    if (true) {
+      arr = arg;
+    } else {
+      arr = arg2;
+    }
+    // :: error: (assignment)
+    int @MinLen(10) [] res = arr;
+    int @MinLen(4) [] res2 = arr;
+    // :: error: (assignment)
+    int @BottomVal [] res3 = arr;
+  }
+
+  public static void BothBottom(int @BottomVal [] arg, int @BottomVal [] arg2) {
+    int[] arr;
+    if (true) {
+      arr = arg;
+    } else {
+      arr = arg2;
+    }
+    int @MinLen(10) [] res = arr;
+    int @BottomVal [] res2 = arr;
+  }
+}
diff --git a/framework/tests/value/MinLenNEqTransfer.java b/framework/tests/value/MinLenNEqTransfer.java
new file mode 100644
index 0000000..ccbe8ca
--- /dev/null
+++ b/framework/tests/value/MinLenNEqTransfer.java
@@ -0,0 +1,26 @@
+import org.checkerframework.common.value.qual.*;
+
+public class MinLenNEqTransfer {
+  void neq_check(int[] a) {
+    if (1 != a.length) {
+      int x = 1; // do nothing.
+    } else {
+      int @MinLen(1) [] b = a;
+    }
+  }
+
+  void neq_bad_check(int[] a) {
+    if (1 != a.length) {
+      int x = 1; // do nothing.
+    } else {
+      // :: error: (assignment)
+      int @MinLen(2) [] b = a;
+    }
+  }
+
+  void neq_zero_special_case(int[] a) {
+    if (a.length != 0) {
+      int @MinLen(1) [] b = a;
+    }
+  }
+}
diff --git a/framework/tests/value/MinLenPostcondition.java b/framework/tests/value/MinLenPostcondition.java
new file mode 100644
index 0000000..b38f75f
--- /dev/null
+++ b/framework/tests/value/MinLenPostcondition.java
@@ -0,0 +1,9 @@
+import org.checkerframework.common.value.qual.*;
+
+public class MinLenPostcondition {
+  public void m(String a) {
+    if (!a.isEmpty()) {
+      char c = a.charAt(0);
+    }
+  }
+}
diff --git a/framework/tests/value/MinLenVarargs.java b/framework/tests/value/MinLenVarargs.java
new file mode 100644
index 0000000..636edc5
--- /dev/null
+++ b/framework/tests/value/MinLenVarargs.java
@@ -0,0 +1,20 @@
+import org.checkerframework.common.value.qual.*;
+
+// @skip-test
+
+public class MinLenVarargs {
+  static void check(String @MinLen(3) ... var) {
+    System.out.println(var[0] + " " + var[1]);
+  }
+
+  public static void main(String[] args) {
+    // :: error: (argument)
+    check(new String[] {"goodbye"});
+    // :: error: (argument)
+    check("goodbye");
+    // :: error: (argument)
+    check();
+    // :: error: (argument)
+    check("hello", "goodbye");
+  }
+}
diff --git a/framework/tests/value/MultipleBinaryExpressions.java b/framework/tests/value/MultipleBinaryExpressions.java
new file mode 100644
index 0000000..e9f1f29
--- /dev/null
+++ b/framework/tests/value/MultipleBinaryExpressions.java
@@ -0,0 +1,72 @@
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class MultipleBinaryExpressions {
+
+  private final String ONE_STRING = "1";
+  private final String TWO_STRING = "2";
+  private final String THREE_STRING = "3";
+  private final String FOUR_STRING = "4";
+  private final String FIVE_STRING = "5";
+  private final String SIX_STRING = "6";
+  private final String SEVEN_STRING = "7";
+  private final String EIGHT_STRING = "8";
+  private final String NINE_STRING = "9";
+
+  public final @StringVal("123456789123456789") String concat1 =
+      ONE_STRING
+          + TWO_STRING
+          + THREE_STRING
+          + FOUR_STRING
+          + FIVE_STRING
+          + SIX_STRING
+          + SEVEN_STRING
+          + EIGHT_STRING
+          + NINE_STRING
+          + ONE_STRING
+          + TWO_STRING
+          + THREE_STRING
+          + FOUR_STRING
+          + FIVE_STRING
+          + SIX_STRING
+          + SEVEN_STRING
+          + EIGHT_STRING
+          + NINE_STRING;
+
+  public final @StringVal("112233445566778899") String concat2 =
+      ONE_STRING
+          + "1"
+          + TWO_STRING
+          + "2"
+          + THREE_STRING
+          + "3"
+          + FOUR_STRING
+          + "4"
+          + FIVE_STRING
+          + "5"
+          + "6"
+          + SIX_STRING
+          + "7"
+          + SEVEN_STRING
+          + "8"
+          + EIGHT_STRING
+          + "9"
+          + NINE_STRING;
+
+  private final int ONE = 1;
+  private final int TWO = 2;
+  private final int THREE = 3;
+  private final int FOUR = 4;
+  private final int FIVE = 5;
+  private final int SIX = 6;
+  private final int SEVEN = 7;
+  private final int EIGHT = 8;
+  private final int NINE = 9;
+
+  public final @IntVal(90) int plus1 =
+      ONE + TWO + THREE + FOUR + FIVE + SIX + SEVEN + EIGHT + NINE + ONE + TWO + THREE + FOUR + FIVE
+          + SIX + SEVEN + EIGHT + NINE;
+  public final @IntVal(90) int plus2 =
+      ONE + 1 + TWO + 2 + THREE + 3 + FOUR + 4 + FIVE + 5 + SIX + 6 + SEVEN + 7 + EIGHT + 8 + NINE
+          + 9;
+}
diff --git a/framework/tests/value/MyTree.java b/framework/tests/value/MyTree.java
new file mode 100644
index 0000000..78e4223
--- /dev/null
+++ b/framework/tests/value/MyTree.java
@@ -0,0 +1,42 @@
+// Test case for Issue #2686
+// https://github.com/typetools/checker-framework/issues/2686
+
+package issue2686;
+
+import org.checkerframework.common.value.qual.StringVal;
+import org.checkerframework.common.value.qual.UnknownVal;
+
+public class MyTree<Value> {
+
+  public static <V> MyTree<V> newTree(V value) {
+    throw new Error("body doesn't matter");
+  }
+
+  public MyTree<Value> put(Value newValue) {
+    throw new Error("body doesn't matter");
+  }
+
+  void uses() {
+    newTree("hello").put("bye");
+
+    MyTree<@UnknownVal String> myTree1 = newTree("hello").put("bye");
+    // :: error: (assignment)
+    MyTree<@StringVal("hello") String> myTree1b = newTree("hello").put("bye");
+
+    // Note: This is a false positive: the type of newTree("hello").put("hello") should be
+    // inferred as MyTree<@StringVal("hello") String> and the assignment should therefore pass.
+    // :: error: (assignment)
+    MyTree<@StringVal("hello") String> myTree2 = newTree("hello").put("hello");
+    MyTree<@StringVal("hello") String> myTree2b =
+        MyTree.<@StringVal("hello") String>newTree("hello").put("hello");
+    // :: error: (assignment)
+    MyTree<@StringVal("error") String> myTree2c = newTree("hello").put("hello");
+
+    MyTree<@UnknownVal String> myTree3 = newTree("hello");
+    myTree3.put("bye");
+
+    MyTree<@StringVal("hello") String> myTree4 = newTree("hello");
+    // :: error: (argument)
+    myTree4.put("bye");
+  }
+}
diff --git a/framework/tests/value/MyTree2.java b/framework/tests/value/MyTree2.java
new file mode 100644
index 0000000..9a79bd2
--- /dev/null
+++ b/framework/tests/value/MyTree2.java
@@ -0,0 +1,29 @@
+// Test case for Issue #2686
+// https://github.com/typetools/checker-framework/issues/2686
+
+import org.checkerframework.common.value.qual.UnknownVal;
+
+public class MyTree2<Value> {
+
+  public static <V> MyTree2<V> newTree(V value) {
+    throw new Error("body doesn't matter");
+  }
+
+  public MyTree2<Value> put(String newKey, Value newValue) {
+    throw new Error("body doesn't matter");
+  }
+
+  public void client1() {
+    MyTree2<String> root2 = MyTree2.newTree("constructorarg").put("key1", "value1");
+  }
+
+  public void client2() {
+    MyTree2<String> root2 =
+        MyTree2.newTree((@UnknownVal String) "constructorarg").put("key1", "value1");
+  }
+
+  public void client3() {
+    MyTree2<String> root2 =
+        MyTree2.<String>newTree((@UnknownVal String) "constructorarg").put("key1", "value1");
+  }
+}
diff --git a/framework/tests/value/NegativeArrayLen.java b/framework/tests/value/NegativeArrayLen.java
new file mode 100644
index 0000000..3994776
--- /dev/null
+++ b/framework/tests/value/NegativeArrayLen.java
@@ -0,0 +1,20 @@
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.BottomVal;
+
+public class NegativeArrayLen {
+  void test1() {
+    int @BottomVal [] a = new int[-1];
+    int @BottomVal [] b = new int[Integer.MAX_VALUE + 1];
+    // :: warning: (negative.arraylen)
+    int @ArrayLen(-1) [] c = new int[-1];
+  }
+
+  void test2(
+      // :: warning: (negative.arraylen)
+      int @ArrayLen(Integer.MIN_VALUE) [] a,
+      // :: warning: (negative.arraylen)
+      int @ArrayLen({-1, 2, 0}) [] b) {
+    int @BottomVal [] y = a;
+    int @BottomVal [] x = b;
+  }
+}
diff --git a/framework/tests/value/NestedArrayLengthInference.java b/framework/tests/value/NestedArrayLengthInference.java
new file mode 100644
index 0000000..c17f2f0
--- /dev/null
+++ b/framework/tests/value/NestedArrayLengthInference.java
@@ -0,0 +1,12 @@
+public class NestedArrayLengthInference {
+  public void doStuff(int r) {
+
+    int[] length16array = new int[16];
+
+    int[] unknownLengthArray = new int[r];
+
+    // CF seems to think that if one array has constant length, all do??
+    int[][] myNewArray = new int[][] {unknownLengthArray, length16array};
+    int[][] myNewArray2 = new int[][] {length16array, unknownLengthArray};
+  }
+}
diff --git a/framework/tests/value/Overflows.java b/framework/tests/value/Overflows.java
new file mode 100644
index 0000000..8d381fd
--- /dev/null
+++ b/framework/tests/value/Overflows.java
@@ -0,0 +1,42 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Overflows {
+
+  static void bytes() {
+    byte max = Byte.MAX_VALUE;
+    // :: warning: (cast.unsafe)
+    @IntVal(-128) byte maxPlus1 = (byte) (max + 1);
+  }
+
+  static void chars() {
+    char max = Character.MAX_VALUE;
+    // :: warning: (cast.unsafe)
+    @IntVal(0) char maxPlus1 = (char) (max + 1);
+  }
+
+  static void shorts() {
+    short max = Short.MAX_VALUE;
+    // :: warning: (cast.unsafe)
+    @IntVal(-32768) short maxPlus1 = (short) (max + 1);
+  }
+
+  static void ints() {
+    int max = Integer.MAX_VALUE;
+    @IntVal(-2147483648) int maxPlus1 = max + 1;
+  }
+
+  static void longs() {
+    long max = Long.MAX_VALUE;
+    @IntVal(-9223372036854775808L) long maxPlus1 = max + 1;
+  }
+
+  static void doubles() {
+    double max = Double.MAX_VALUE;
+    @DoubleVal(1.7976931348623157E308) double maxPlus1 = max + 1.0;
+  }
+
+  static void floats() {
+    float max = Float.MAX_VALUE;
+    @DoubleVal(3.4028235E38f) float maxPlus1 = max + 1.0f;
+  }
+}
diff --git a/framework/tests/value/Polymorphic.java b/framework/tests/value/Polymorphic.java
new file mode 100644
index 0000000..bd4ad78
--- /dev/null
+++ b/framework/tests/value/Polymorphic.java
@@ -0,0 +1,32 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Polymorphic {
+
+  // Identity functions
+
+  int @PolyValue [] identity_array(int @PolyValue [] a) {
+    return a;
+  }
+
+  @PolyValue int identity_int(@PolyValue int a) {
+    return a;
+  }
+
+  void minlen_id(int @MinLen(5) [] a) {
+    int @MinLen(5) [] b = identity_array(a);
+    // :: error: (assignment)
+    int @MinLen(6) [] c = identity_array(b);
+  }
+
+  void use(int @ArrayLenRange(from = 5, to = 25) [] a) {
+    int @ArrayLenRange(from = 5, to = 25) [] b = identity_array(a);
+    // :: error: (assignment)
+    int @ArrayLenRange(from = 1, to = 13) [] c = identity_array(a);
+  }
+
+  void use2(@IntRange(from = 5, to = 25) int a) {
+    @IntRange(from = 5, to = 25) int b = identity_int(a);
+    // :: error: (assignment)
+    @IntVal(3) int x = identity_int(a);
+  }
+}
diff --git a/framework/tests/value/Polymorphic2.java b/framework/tests/value/Polymorphic2.java
new file mode 100644
index 0000000..e5aacfd
--- /dev/null
+++ b/framework/tests/value/Polymorphic2.java
@@ -0,0 +1,37 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Polymorphic2 {
+  public static boolean flag = false;
+
+  @PolyValue int mergeValue(@PolyValue int a, @PolyValue int b) {
+    return flag ? a : b;
+  }
+
+  int @PolyValue [] mergeValue(int @PolyValue [] a, int @PolyValue [] b) {
+    return flag ? a : b;
+  }
+
+  void testMinLen(int @MinLen(2) [] a, int @MinLen(5) [] b) {
+    int @MinLen(2) [] z = mergeValue(a, b);
+    // :: error: (assignment)
+    int @MinLen(5) [] zz = mergeValue(a, b);
+  }
+
+  void testArrayLen(int @ArrayLen(2) [] a, int @ArrayLen(5) [] b) {
+    // :: error: (assignment)
+    int @ArrayLen(2) [] z = mergeValue(a, b);
+    // :: error: (assignment)
+    int @ArrayLen(5) [] zz = mergeValue(a, b);
+
+    int @ArrayLen({2, 5}) [] zzz = mergeValue(a, b);
+  }
+
+  void testIntVal(@IntVal(2) int a, @IntVal(5) int b) {
+    // :: error: (assignment)
+    @IntVal(2) int z = mergeValue(a, b);
+    // :: error: (assignment)
+    @IntVal(5) int zz = mergeValue(a, b);
+
+    @IntVal({2, 5}) int zzz = mergeValue(a, b);
+  }
+}
diff --git a/framework/tests/value/RefineBoolean.java b/framework/tests/value/RefineBoolean.java
new file mode 100644
index 0000000..bc54cfb
--- /dev/null
+++ b/framework/tests/value/RefineBoolean.java
@@ -0,0 +1,52 @@
+import org.checkerframework.common.value.qual.BoolVal;
+
+public class RefineBoolean {
+
+  void test1(boolean x) {
+    if (x == false) {
+      @BoolVal(false) boolean y = x;
+    }
+  }
+
+  void test2(boolean x) {
+    if (false == x) {
+      @BoolVal(false) boolean y = x;
+    }
+  }
+
+  void test3(boolean x) {
+    if (x != true) {
+      @BoolVal(false) boolean y = x;
+    }
+  }
+
+  void test4(boolean x) {
+    if (true != x) {
+      @BoolVal(false) boolean y = x;
+    }
+  }
+
+  void test5(boolean x) {
+    if (x == true) {
+      @BoolVal(true) boolean y = x;
+    }
+  }
+
+  void test6(boolean x) {
+    if (true == x) {
+      @BoolVal(true) boolean y = x;
+    }
+  }
+
+  void test7(boolean x) {
+    if (false != x) {
+      @BoolVal(true) boolean y = x;
+    }
+  }
+
+  void test8(boolean x) {
+    if (x != false) {
+      @BoolVal(true) boolean y = x;
+    }
+  }
+}
diff --git a/framework/tests/value/RefineUnknownToIntRange.java b/framework/tests/value/RefineUnknownToIntRange.java
new file mode 100644
index 0000000..f0b787b
--- /dev/null
+++ b/framework/tests/value/RefineUnknownToIntRange.java
@@ -0,0 +1,49 @@
+import org.checkerframework.common.value.qual.BoolVal;
+import org.checkerframework.common.value.qual.IntRange;
+
+public class RefineUnknownToIntRange {
+  void test1(int x) {
+    if (x > 1) {
+      @IntRange(from = 2) int z = x;
+    }
+
+    if (x < 1) {
+      @IntRange(to = 0) int z = x;
+    }
+
+    if (1 < x) {
+      @IntRange(from = 2) int z = x;
+    }
+
+    if (1 > x) {
+      @IntRange(to = 0) int z = x;
+    }
+
+    if (x >= 1) {
+      @IntRange(from = 1) int z = x;
+    }
+
+    if (x <= 1) {
+      @IntRange(to = 1) int z = x;
+    }
+
+    if (x < 100 && x > 2) {
+      @IntRange(from = 3, to = 99) int z = x;
+    }
+  }
+
+  void test3(boolean x) {
+    // Make sure non int values are ignored.
+    if (x == false) {
+      @BoolVal(false) boolean y = x;
+    }
+
+    if (x != true) {
+      @BoolVal(false) boolean y = x;
+    }
+
+    Object o = new Object();
+    Object o2 = new Object();
+    if (o == o2) {}
+  }
+}
diff --git a/framework/tests/value/Refinement.java b/framework/tests/value/Refinement.java
new file mode 100644
index 0000000..6a7d170
--- /dev/null
+++ b/framework/tests/value/Refinement.java
@@ -0,0 +1,312 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Refinement {
+
+  void eq_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) {
+    if (x == 1) {
+      @IntVal({1}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({2}) int z = x;
+    } else {
+      @IntVal({2}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({1}) int z = x;
+    }
+
+    if (x == w) {
+      @IntVal({1}) int y = x;
+      @IntVal({1}) int z = w;
+    } else {
+      // These two assignments are illegal because one of x or w could be 1,
+      // while the other is not. So no refinement can be applied.
+
+      // :: error: (assignment)
+      @IntVal({2}) int y = x;
+      // :: error: (assignment)
+      @IntVal({5, 6}) int z = w;
+    }
+  }
+
+  void lt_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) {
+    if (x < 2) {
+      @IntVal({1}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({2}) int z = x;
+    } else {
+      @IntVal({2}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({1}) int z = x;
+    }
+
+    if (x < w) {
+      // :: error: (assignment)
+      @IntVal({1}) int y = x;
+
+      @IntVal({5, 6}) int z = w;
+    } else {
+      // :: error: (assignment)
+      @IntVal({2}) int y = x;
+      @IntVal({1}) int z = w;
+    }
+  }
+
+  void lte_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) {
+    if (x <= 1) {
+      @IntVal({1}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({2}) int z = x;
+    } else {
+      @IntVal({2}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({1}) int z = x;
+    }
+
+    if (x <= w) {
+      // :: error: (assignment)
+      @IntVal({1}) int y = x;
+      // :: error: (assignment)
+      @IntVal({5, 6}) int z = w;
+    } else {
+      @IntVal({2}) int y = x;
+      @IntVal({1}) int z = w;
+    }
+  }
+
+  void neq_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) {
+    if (x != 1) {
+      @IntVal({2}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({1}) int z = x;
+    } else {
+      @IntVal({1}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({2}) int z = x;
+    }
+
+    if (x != w) {
+      // These two assignments are illegal because one of x or w could be 1,
+      // while the other is not. So no refinement can be applied.
+
+      // :: error: (assignment)
+      @IntVal({2}) int y = x;
+      // :: error: (assignment)
+      @IntVal({5, 6}) int z = w;
+    } else {
+      @IntVal({1}) int y = x;
+      @IntVal({1}) int z = w;
+    }
+  }
+
+  void gte_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) {
+    if (x >= 2) {
+      @IntVal({2}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({1}) int z = x;
+    } else {
+      @IntVal({1}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({2}) int z = x;
+    }
+
+    if (x >= w) {
+      // :: error: (assignment)
+      @IntVal({2}) int y = x;
+      @IntVal({1}) int z = w;
+    } else {
+      // :: error: (assignment)
+      @IntVal({1}) int y = x;
+
+      @IntVal({5, 6}) int z = w;
+    }
+  }
+
+  void gt_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) {
+    if (x > 1) {
+      @IntVal({2}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({1}) int z = x;
+    } else {
+      @IntVal({1}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({2}) int z = x;
+    }
+
+    if (x > w) {
+      @IntVal({2}) int y = x;
+      @IntVal({1}) int z = w;
+    } else {
+      // :: error: (assignment)
+      @IntVal({1}) int y = x;
+      // :: error: (assignment)
+      @IntVal({5, 6}) int z = w;
+    }
+  }
+
+  void boolean_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) {
+    @BoolVal({true}) boolean b = x >= 0;
+    @BoolVal({false}) boolean c = w == 3;
+    @BoolVal({true, false}) boolean d = x < w;
+  }
+
+  void test_intrange_eq(@IntRange(from = 3, to = 10) int x, @IntRange(from = 1, to = 5) int y) {
+    if (x == y) {
+      @IntRange(from = 3, to = 5) int a = x;
+      @IntRange(from = 3, to = 5) int b = y;
+    } else {
+      @IntRange(from = 6, to = 10)
+      // :: error: (assignment)
+      int a = x;
+      @IntRange(from = 1, to = 2)
+      // :: error: (assignment)
+      int b = y;
+    }
+  }
+
+  void test_intrange_eq2(@IntRange(from = 0, to = 10) int x, @IntRange(from = 0, to = 0) int y) {
+    if (x == y) {
+      @IntRange(from = 0, to = 0) int a = x;
+      @IntRange(from = 0, to = 0) int b = y;
+    } else {
+      @IntRange(from = 1, to = 10) int a = x;
+      @IntRange(from = 0, to = 0) int b = y;
+    }
+  }
+
+  void test_intrange_eq3(@IntRange(from = 0, to = 10) int x, @IntVal(0) int y) {
+    if (x == y) {
+      @IntVal(0) int a = x;
+      @IntVal(0) int b = y;
+    } else {
+      @IntRange(from = 1, to = 10) int a = x;
+      @IntVal(0) int b = y;
+    }
+
+    if (y != x) {
+      @IntRange(from = 1, to = 10) int a = x;
+      @IntVal(0) int b = y;
+    }
+  }
+
+  void test_intrange_neq(@IntRange(from = 3, to = 10) int x, @IntRange(from = 1, to = 5) int y) {
+    if (x != y) {
+      @IntRange(from = 6, to = 10)
+      // :: error: (assignment)
+      int a = x;
+      @IntRange(from = 1, to = 2)
+      // :: error: (assignment)
+      int b = y;
+    } else {
+      @IntRange(from = 3, to = 5) int a = x;
+      @IntRange(from = 3, to = 5) int b = y;
+    }
+  }
+
+  void test_intrange_neq2(@IntRange(from = 3, to = 10) int x, @IntRange(from = 10, to = 10) int y) {
+    if (x != y) {
+      @IntRange(from = 3, to = 9) int a = x;
+      @IntRange(from = 10, to = 10) int b = y;
+    } else {
+      @IntRange(from = 10, to = 10) int a = x;
+      @IntRange(from = 10, to = 10) int b = y;
+    }
+  }
+
+  void test_intrange_gt(@IntRange(from = 0, to = 10) int x, @IntRange(from = 5, to = 20) int y) {
+    if (x > y) {
+      @IntRange(from = 6, to = 10) int a = x;
+      @IntRange(from = 5, to = 9) int b = y;
+    } else {
+      @IntRange(from = 0, to = 10) int a = x;
+      @IntRange(from = 5, to = 20) int b = y;
+    }
+  }
+
+  void test_intrange_gt2(@IntRange(from = 5, to = 10) int x, @IntRange(from = 2, to = 7) int y) {
+    if (x > y) {
+      @IntRange(from = 5, to = 10) int a = x;
+      @IntRange(from = 2, to = 7) int b = y;
+
+      @IntRange(from = 5, to = 7)
+      // :: error: (assignment)
+      int c = x;
+      @IntRange(from = 5, to = 7)
+      // :: error: (assignment)
+      int d = y;
+    } else {
+      @IntRange(from = 5, to = 7) int a = x;
+      @IntRange(from = 5, to = 7) int b = y;
+    }
+  }
+
+  void test_intrange_lte(@IntRange(from = 0, to = 10) int x, @IntRange(from = 2, to = 7) int y) {
+    if (x <= y) {
+      @IntRange(from = 0, to = 7) int a = x;
+      @IntRange(from = 2, to = 7) int b = y;
+    } else {
+      @IntRange(from = 3, to = 10) int a = x;
+      @IntRange(from = 2, to = 7) int b = y;
+    }
+  }
+
+  void test_intrange_lte2(@IntRange(from = 2, to = 7) int x, @IntRange(from = 5, to = 10) int y) {
+    if (x <= y) {
+      @IntRange(from = 2, to = 7) int a = x;
+      @IntRange(from = 2, to = 10) int b = y;
+    } else {
+      @IntRange(from = 6, to = 7) int a = x;
+      @IntRange(from = 5, to = 6) int b = y;
+    }
+  }
+
+  void test_intrange_lt(@IntRange(from = 5, to = 10) int x, @IntRange(from = 2, to = 7) int y) {
+    if (x < y) {
+      @IntRange(from = 5, to = 6) int a = x;
+      @IntRange(from = 6, to = 7) int b = y;
+    } else {
+      @IntRange(from = 5, to = 10) int a = x;
+      @IntRange(from = 2, to = 7) int b = y;
+    }
+  }
+
+  void test_intrange_lt2(@IntRange(from = 2, to = 7) int x, @IntRange(from = 5, to = 10) int y) {
+    if (x < y) {
+      @IntRange(from = 2, to = 7) int a = x;
+      @IntRange(from = 2, to = 10) int b = y;
+    } else {
+      @IntRange(from = 5, to = 7) int a = x;
+      @IntRange(from = 5, to = 7) int b = y;
+    }
+  }
+
+  void test_intrange_gte(@IntRange(from = 0, to = 10) int x, @IntRange(from = 2, to = 7) int y) {
+    if (x >= y) {
+      @IntRange(from = 2, to = 10) int a = x;
+      @IntRange(from = 2, to = 7) int b = y;
+    } else {
+      @IntRange(from = 0, to = 6) int a = x;
+      @IntRange(from = 2, to = 7) int b = y;
+    }
+  }
+
+  void test_intrange_gte2(@IntRange(from = 3, to = 5) int x, @IntRange(from = 2, to = 6) int y) {
+    if (x >= y) {
+      @IntRange(from = 3, to = 5) int a = x;
+      @IntRange(from = 2, to = 6) int b = y;
+    } else {
+      @IntRange(from = 3, to = 5) int a = x;
+      @IntRange(from = 4, to = 6) int b = y;
+    }
+  }
+}
diff --git a/framework/tests/value/Refinement2.java b/framework/tests/value/Refinement2.java
new file mode 100644
index 0000000..9c09a98
--- /dev/null
+++ b/framework/tests/value/Refinement2.java
@@ -0,0 +1,125 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Refinement2 {
+
+  void eq_test(int x, @IntVal({1, 5, 6}) int w) {
+    if (x == 1) {
+      // :: error: (assignment)
+      @BottomVal int q = x;
+
+    } else if (x == 2) {
+    } else {
+      return;
+    }
+
+    if (x != 1) {
+      // :: error: (assignment)
+      @IntVal({1}) int y = x;
+      @IntVal({2}) int z = x;
+    }
+
+    if (x == 1) {
+
+      @IntVal({1}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({2}) int z = x;
+    } else {
+      @IntVal({2}) int y = x;
+
+      // :: error: (assignment)
+      @IntVal({1}) int z = x;
+    }
+
+    if (x == w) {
+      @IntVal({1}) int y = x;
+      @IntVal({1}) int z = w;
+    } else {
+      // These two assignments are illegal because one of x or w could be 1,
+      // while the other is not. So no refinement can be applied.
+
+      // :: error: (assignment)
+      @IntVal({2}) int y = x;
+      // :: error: (assignment)
+      @IntVal({5, 6}) int z = w;
+    }
+  }
+
+  void testDeadCode(int x) {
+    if (x == 4 || x == 5) {
+      @IntVal({4, 5}) int a = x;
+      // :: error: (assignment)
+      @BottomVal int a2 = x;
+    }
+    if (x == 4 && x == 5) {
+      // This is dead, so x should be bottom.
+      @IntVal({4, 5}) int a = x;
+      @BottomVal int a2 = x;
+    }
+    if (x != 1 || x != 2) {
+      return;
+    }
+    if (x != 2) {
+      @IntVal(1) int a = x;
+    }
+
+    if (x == 3) {
+      // This is actually dead code, so x is treated as bottom.
+      @IntVal(3) int y = x;
+      @IntVal(33) int v = x;
+      @BottomVal int z = x;
+    }
+  }
+
+  void simpleNull(@IntVal({1, 2, 3}) Integer x) {
+    if (x == null) {
+      @BottomVal int y = x;
+    }
+  }
+
+  void moreTests(@IntVal({1, 2, 3}) Integer x, Integer y) {
+    if (x == null) {
+      if (y == x) {
+        // x and y should be @BottomVal
+        @BottomVal int z1 = x;
+        @BottomVal int z2 = y;
+      } else {
+        // y should be @UnknownVal here.
+        // :: error: (assignment)
+        @IntVal({1, 2, 3}) int z = y;
+      }
+    }
+
+    if (x == null) {
+      if (y < x) {
+        // x should be @BottomVal
+        @BottomVal int z1 = x;
+        // This is dead code since the unboxing of x in the if statement
+        // will throw a NPE, so the type of y doesn't matter.
+        // @BottomVal int z2 = y;
+      } else {
+        // This is dead code, too.
+      }
+    }
+  }
+
+  void testArrayLen(boolean flag) {
+    int[] a;
+    if (flag) {
+      a = new int[] {1};
+    } else {
+      a = new int[] {1, 2};
+    }
+
+    if (a.length != 1) {
+      int @ArrayLen(2) [] b = a;
+    }
+
+    if (a.length == 3) {
+      // Dead code
+      int @ArrayLen(3) [] b = a;
+      int @ArrayLen(5) [] c = a;
+      int @BottomVal [] bot = a;
+    }
+  }
+}
diff --git a/framework/tests/value/RegexMatching.java b/framework/tests/value/RegexMatching.java
new file mode 100644
index 0000000..ad3eb46
--- /dev/null
+++ b/framework/tests/value/RegexMatching.java
@@ -0,0 +1,117 @@
+// A test for the @MatchesRegex annotation.
+
+import org.checkerframework.common.value.qual.*;
+
+public class RegexMatching {
+  void stringConstants() {
+    @MatchesRegex("a*") String a = "a";
+    @MatchesRegex("a*") String blank = "";
+    // :: error: assignment
+    @MatchesRegex("a*") String b = "b";
+
+    // NOTE: these tests show that there are implicit anchors in the regular expressions
+    // used by @MatchesRegex.
+    // :: error: assignment
+    @MatchesRegex("a*") String ab = "ab";
+    // :: error: assignment
+    @MatchesRegex("a*") String baa = "baa";
+
+    @MatchesRegex("a") String a1 = "a";
+    // :: error: assignment
+    @MatchesRegex("a") String blank1 = "";
+    // :: error: assignment
+    @MatchesRegex("a") String b1 = "b";
+
+    @MatchesRegex("\\s") String space = " ";
+    @MatchesRegex("\\s+") String severalSpaces = "      ";
+    // :: error: assignment
+    @MatchesRegex("\\s") String b2 = "b";
+
+    @MatchesRegex("[^abc]") String d = "d";
+    @MatchesRegex("[^abc]") String d1 = String.valueOf(new char[] {'d'});
+    // :: error: assignment
+    @MatchesRegex("[^abc]") String c = "c";
+  }
+
+  void severalString(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) {
+    @MatchesRegex("a*") String a = aaa;
+    // :: error: assignment
+    @MatchesRegex("a*") String a1 = aab;
+
+    @MatchesRegex("a+") String a2 = aaa;
+    // :: error: assignment
+    @MatchesRegex("a+") String a3 = aab;
+  }
+
+  void multipleRegexes(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) {
+    @MatchesRegex({"a*", "b*"}) String a = aaa;
+    @MatchesRegex({"a*", "b*"}) String a1 = aab;
+
+    // :: error: assignment
+    @MatchesRegex({"aa", "b*"}) String a2 = aaa;
+    @MatchesRegex({"aa", "b*"}) String a3 = aab;
+  }
+
+  void regexSubtypingConstant(@MatchesRegex({"a", "b"}) String ab) {
+    // :: error: assignment
+    @MatchesRegex("a") String a = ab;
+    @MatchesRegex({"a", "b"}) String ab1 = ab;
+    @MatchesRegex({"a", "b", "c"}) String abc = ab;
+    // :: error: assignment
+    @StringVal("a") String a1 = ab;
+    // :: error: assignment
+    @StringVal({"a", "b"}) String ab2 = ab;
+    // :: error: assignment
+    @StringVal({"a", "b", "c"}) String abc1 = ab;
+  }
+
+  void regexSubtyping2(@MatchesRegex({"a*", "b*"}) String ab) {
+    // :: error: assignment
+    @MatchesRegex("a*") String a = ab;
+    @MatchesRegex({"a*", "b*"}) String ab1 = ab;
+    @MatchesRegex({"a*", "b*", "c*"}) String abc = ab;
+    // :: error: assignment
+    @StringVal("a*") String a1 = ab;
+    // :: error: assignment
+    @StringVal({"a*", "b*"}) String ab2 = ab;
+    // :: error: assignment
+    @StringVal({"a*", "b*", "c*"}) String abc1 = ab;
+  }
+
+  void lubRegexes(
+      @MatchesRegex({"a*"}) String astar, @MatchesRegex({"b*"}) String bstar, boolean b) {
+    String s;
+    if (b) {
+      s = astar;
+    } else {
+      s = bstar;
+    }
+    @MatchesRegex({"a*", "b*"}) String s1 = s;
+    // :: error: assignment
+    @MatchesRegex({"a*"}) String s2 = s;
+    // :: error: assignment
+    @MatchesRegex({"b*"}) String s3 = s;
+  }
+
+  void lubRegexWithStringVal(
+      @MatchesRegex({"a*"}) String astar, @StringVal({"b"}) String bval, boolean b) {
+    String s;
+    if (b) {
+      s = astar;
+    } else {
+      s = bval;
+    }
+    // NOTE: This depends on the internal implementation.  Semantically identical code like this
+    // yields an error:
+    // @MatchesRegex({"a*", "b"}) String s0 = s;
+    @MatchesRegex({"a*", "\\Qb\\E"}) String s1 = s;
+    // :: error: assignment
+    @MatchesRegex({"a*"}) String s2 = s;
+    // :: error: assignment
+    @StringVal({"b"}) String s3 = s;
+    // :: error: assignment
+    @MatchesRegex("b") String s4 = s;
+    // :: error: assignment
+    @MatchesRegex("^b$") String s5 = s;
+  }
+}
diff --git a/framework/tests/value/RegexPatternSyntaxException.java b/framework/tests/value/RegexPatternSyntaxException.java
new file mode 100644
index 0000000..5e34c69
--- /dev/null
+++ b/framework/tests/value/RegexPatternSyntaxException.java
@@ -0,0 +1,8 @@
+// A test for malformed @MatchesRegex annotations.
+
+import org.checkerframework.common.value.qual.*;
+
+public class RegexPatternSyntaxException {
+  // :: warning: invalid.matches.regex
+  void stringConstants(@MatchesRegex("(a*") String a) {}
+}
diff --git a/framework/tests/value/RegexVsString.java b/framework/tests/value/RegexVsString.java
new file mode 100644
index 0000000..69ff168
--- /dev/null
+++ b/framework/tests/value/RegexVsString.java
@@ -0,0 +1,24 @@
+// A test for the @MatchesRegex annotation.
+
+import org.checkerframework.common.value.qual.*;
+
+public class RegexVsString {
+  void stringToRegex1(@StringVal({"(a)"}) String a) {
+    // :: error: assignment
+    @MatchesRegex("(a)") String a2 = a;
+  }
+
+  void stringToRegex2(@StringVal({"a"}) String a) {
+    @MatchesRegex("(a)") String a2 = a;
+  }
+
+  void stringToRegex3(@StringVal({"a"}) String a) {
+    @MatchesRegex("^a$") String a2 = a;
+  }
+
+  void regexToString(@MatchesRegex("^a$") String a) {
+    // TODO: This is a false positive.  In the future, eliminate it.
+    // :: error: assignment
+    @StringVal({"a"}) String a2 = a;
+  }
+}
diff --git a/framework/tests/value/RepeatMinLenIf.java b/framework/tests/value/RepeatMinLenIf.java
new file mode 100644
index 0000000..af9f2f8
--- /dev/null
+++ b/framework/tests/value/RepeatMinLenIf.java
@@ -0,0 +1,57 @@
+import org.checkerframework.common.value.qual.EnsuresMinLenIf;
+
+public class RepeatMinLenIf {
+
+  protected String a;
+  protected String b;
+  protected String c;
+
+  public boolean func1() {
+    a = "checker";
+    c = "framework";
+    b = "opensource";
+    return true;
+  }
+
+  @EnsuresMinLenIf(
+      expression = {"a", "b"},
+      targetValue = 6,
+      result = true)
+  @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true)
+  public boolean client1() {
+    return withcondpostconditionsfunc1();
+  }
+
+  @EnsuresMinLenIf.List({
+    @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true),
+    @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true)
+  })
+  @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true)
+  public boolean client2() {
+    return withcondpostconditionfunc1();
+  }
+
+  @EnsuresMinLenIf(
+      expression = {"a", "b"},
+      targetValue = 6,
+      result = true)
+  @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true)
+  public boolean withcondpostconditionsfunc1() {
+    a = "checker";
+    c = "framework";
+    b = "opensource";
+    return true;
+  }
+
+  @EnsuresMinLenIf.List({
+    @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true),
+    @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true)
+  })
+  @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true)
+  public boolean withcondpostconditionfunc1() {
+    a = "checker";
+    c = "framework";
+    b = "opensource";
+    return true;
+  }
+}
diff --git a/framework/tests/value/RepeatMinLenIfWithError.java b/framework/tests/value/RepeatMinLenIfWithError.java
new file mode 100644
index 0000000..042dac3
--- /dev/null
+++ b/framework/tests/value/RepeatMinLenIfWithError.java
@@ -0,0 +1,59 @@
+import org.checkerframework.common.value.qual.EnsuresMinLenIf;
+
+public class RepeatMinLenIfWithError {
+
+  protected String a;
+  protected String b;
+  protected String c;
+
+  public boolean func1() {
+    a = "checker";
+    c = "framework";
+    b = "hello";
+    return true;
+  }
+
+  @EnsuresMinLenIf(
+      expression = {"a", "b"},
+      targetValue = 6,
+      result = true)
+  @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true)
+  public boolean client1() {
+    return withcondpostconditionsfunc1();
+  }
+
+  @EnsuresMinLenIf.List({
+    @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true),
+    @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true)
+  })
+  @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true)
+  public boolean client2() {
+    return withcondpostconditionfunc1();
+  }
+
+  @EnsuresMinLenIf(
+      expression = {"a", "b"},
+      targetValue = 6,
+      result = true)
+  @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true)
+  public boolean withcondpostconditionsfunc1() {
+    a = "checker";
+    c = "framework";
+    b = "hello"; // condition not satisfied here
+    // :: error:  (contracts.conditional.postcondition)
+    return true;
+  }
+
+  @EnsuresMinLenIf.List({
+    @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true),
+    @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true)
+  })
+  @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true)
+  public boolean withcondpostconditionfunc1() {
+    a = "checker";
+    c = "framework";
+    b = "hello"; // condition not satisfied here
+    // :: error:  (contracts.conditional.postcondition)
+    return true;
+  }
+}
diff --git a/framework/tests/value/Repo.java b/framework/tests/value/Repo.java
new file mode 100644
index 0000000..bf7e490
--- /dev/null
+++ b/framework/tests/value/Repo.java
@@ -0,0 +1,15 @@
+import java.util.BitSet;
+import org.checkerframework.common.value.qual.*;
+
+public class Repo {
+  private BitSet bitmap;
+  boolean flag = true;
+
+  void testLoop() {
+    for (int i = 0; i < 20; i++) {
+      // :: error: (assignment)
+      @IntVal(0) int x = i;
+      int j = flag ? i : 3;
+    }
+  }
+}
diff --git a/framework/tests/value/SplitAssignments.java b/framework/tests/value/SplitAssignments.java
new file mode 100644
index 0000000..1aba24f
--- /dev/null
+++ b/framework/tests/value/SplitAssignments.java
@@ -0,0 +1,19 @@
+import org.checkerframework.common.value.qual.*;
+
+public class SplitAssignments {
+  void foo(@IntRange(from = 5, to = 200) int x) {
+    int z;
+    if ((z = x) == 5) {
+      @IntRange(from = 5, to = 5) int w = x;
+      @IntRange(from = 5, to = 5) int q = z;
+    }
+  }
+
+  void bar(@IntVal({1, 2}) int x) {
+    int z;
+    if ((z = x) == 1) {
+      @IntVal(1) int w = x;
+      @IntVal(1) int q = z;
+    }
+  }
+}
diff --git a/framework/tests/value/StartsEndsWith.java b/framework/tests/value/StartsEndsWith.java
new file mode 100644
index 0000000..045abf4
--- /dev/null
+++ b/framework/tests/value/StartsEndsWith.java
@@ -0,0 +1,62 @@
+// Tests string length refinement after startsWith or endsWith return true
+// https://github.com/kelloggm/checker-framework/issues/56
+
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.MinLen;
+
+public class StartsEndsWith {
+
+  void refineStartsWith(String str) {
+    if (str.startsWith("prefix")) {
+      @MinLen(6) String s6 = str;
+      // :: error: (assignment)
+      @MinLen(7) String s7 = str;
+    } else {
+      // :: error: (assignment)
+      @MinLen(6) String s6 = str;
+    }
+  }
+
+  void refineEndsWith(String str) {
+    if (str.endsWith("suffix")) {
+      @MinLen(6) String s6 = str;
+      // :: error: (assignment)
+      @MinLen(7) String s7 = str;
+    } else {
+      // :: error: (assignment)
+      @MinLen(6) String s6 = str;
+    }
+  }
+
+  void refineStartsEndsWith(String str) {
+    if (str.startsWith("longprefix") && str.endsWith("prefix")) {
+      @MinLen(10) String s10 = str;
+      // :: error: (assignment)
+      @MinLen(11) String s11 = str;
+    }
+  }
+
+  void refineStartsArrayLen(String str, @ArrayLen(10) String prefix) {
+    if (str.startsWith(prefix)) {
+      @MinLen(10) String sg10 = str;
+      // :: error: (assignment)
+      @ArrayLen(10) String s10 = str;
+    }
+  }
+
+  void noRefinement(@ArrayLen(10) String str) {
+    if (str.startsWith("x")) {
+      @ArrayLen(10) String s10 = str;
+    }
+  }
+
+  void refineStartsStaticFinal(String str) {
+    if (str.startsWith(StartsEndsWithExternal.staticFinalField)) {
+      @MinLen(3) String s3 = str;
+    }
+  }
+}
+
+class StartsEndsWithExternal {
+  public static final String staticFinalField = "str";
+}
diff --git a/framework/tests/value/StaticExTest.java b/framework/tests/value/StaticExTest.java
new file mode 100644
index 0000000..dd1b7cd
--- /dev/null
+++ b/framework/tests/value/StaticExTest.java
@@ -0,0 +1,45 @@
+import org.checkerframework.common.value.qual.*;
+
+public class StaticExTest {
+  boolean flag;
+
+  void test1() {
+    String s = "helloworlod";
+    @StringVal({"o", "l"}) String subString = flag ? "o" : "l";
+    @IntVal({5, 0, 9}) int start = flag ? 9 : flag ? 5 : 0;
+    // flag?1:flag?6:
+    @IntVal({-1, 8, 9, 2, 4, 6}) int result = s.indexOf(subString, start);
+  }
+
+  void test2() {
+    String s = flag ? "helloworlod" : "lololxxolxxxol";
+    @StringVal({"o", "l"}) String subString = flag ? "o" : "l";
+    @IntVal({0, 9}) int start = flag ? 9 : 0;
+    // flag?1:flag?6:
+    @IntVal({-1, 0, 1, 2, 4, 9, 12, 13}) int result3 = s.indexOf(subString, start);
+  }
+
+  void test3() {
+    @IntVal({0, 1}) int offset = flag ? 0 : 1;
+    char[] data = {'h', 'e', 'l', 'l', 'o', 'b', 'y', 'e', 't', 'o'};
+    @IntVal({5, 6}) int charCount = flag ? 5 : 6;
+    @StringVal({"hello", "ellob", "hellob", "elloby"}) String s = new String(data, offset, charCount);
+  }
+
+  void test4() {
+    @IntVal({0, 1}) int offset = flag ? 0 : 1;
+    char[] data1 = {'h', 'e', 'l', 'l', 'o', 'b', 'y', 'e', 't', 'o'};
+    char[] data2 = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'};
+    char @StringVal({"hellobyeto", "abcdefghij"}) [] data = flag ? data1 : data2;
+    @IntVal({5, 6}) int charCount = flag ? 5 : 6;
+    @StringVal({"hello", "ellob", "hellob", "elloby", "abcde", "bcdef", "abcdef", "bcdefg"}) String s = new String(data, offset, charCount);
+  }
+
+  static byte[] b = new byte[0];
+
+  void constructorsArrays() {
+    char @ArrayLen(100) [] c = new char[100];
+    String s = new String(c);
+    new String(b);
+  }
+}
diff --git a/framework/tests/value/StaticallyExecutableWarnings.java b/framework/tests/value/StaticallyExecutableWarnings.java
new file mode 100644
index 0000000..cdf662b
--- /dev/null
+++ b/framework/tests/value/StaticallyExecutableWarnings.java
@@ -0,0 +1,45 @@
+import org.checkerframework.common.value.qual.*;
+import org.checkerframework.dataflow.qual.Pure;
+
+public class StaticallyExecutableWarnings {
+
+  @StaticallyExecutable
+  // :: warning: (statically.executable.not.pure)
+  static int addNotPure(int a, int b) {
+    return a + b;
+  }
+
+  @StaticallyExecutable
+  @Pure
+  static int add(Integer a, Integer b) {
+    return a + b;
+  }
+
+  @StaticallyExecutable
+  @Pure
+  // :: error: (statically.executable.nonconstant.parameter.type)
+  int receiverCannotBeConstant(int a, int b) {
+    return a + b;
+  }
+
+  @StaticallyExecutable
+  @Pure
+  // :: error: (statically.executable.nonconstant.parameter.type)
+  int explicitReceiverCannotBeConstant(StaticallyExecutableWarnings this, int a, int b) {
+    return a + b;
+  }
+
+  @StaticallyExecutable
+  @Pure
+  // :: error: (statically.executable.nonconstant.return.type)
+  static StaticallyExecutableWarnings returnTypeCannotBeConstant(int a, int b) {
+    return new StaticallyExecutableWarnings();
+  }
+
+  @StaticallyExecutable
+  @Pure
+  // :: error: (statically.executable.nonconstant.parameter.type)
+  static int parameterCannotBeConstant(int a, int b, Object o) {
+    return a + b;
+  }
+}
diff --git a/framework/tests/value/StringConcats.java b/framework/tests/value/StringConcats.java
new file mode 100644
index 0000000..b922ae4
--- /dev/null
+++ b/framework/tests/value/StringConcats.java
@@ -0,0 +1,46 @@
+import org.checkerframework.common.value.qual.BottomVal;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class StringConcats {
+  void stringConcat() {
+    @StringVal("helloa11.01.020truenull2626") String everything = "hello" + 'a' + 1 + 1.0 + 1.0f + 20L + true + null + 0x1a + 0b11010;
+
+    @StringVal("true") String bool = "" + true;
+    @StringVal("null") String nullV = "" + null;
+    // :: error: (assignment)
+    @BottomVal String bottom = "" + null;
+    @StringVal("1") String intL = "" + 1;
+    @StringVal("$") String charL = "" + '$';
+    @StringVal("1.0") String doubleDefault = "" + 1.0;
+    @StringVal("1.0") String doubleL = "" + 1.0d;
+    @StringVal("26") String hexVal = "" + 0x1a;
+    @StringVal("26") String binaryVal = "" + 0b11010;
+    @StringVal("12.3") String floatVal = "" + 12.3f;
+    @StringVal("123.0") String science = "" + 1.23e2;
+  }
+
+  void compoundStringAssignement() {
+    String s = "";
+    s += "hello";
+    s += 'a';
+    s += 1;
+    s += 1.0;
+    s += 1.0f;
+    s += 20L;
+    s += true;
+    s += null;
+    s += 0x1a;
+    s += 0b11010;
+    // TODO: this should pass
+    // compound assignments have not been implemented.
+    // :: error: (assignment)
+    @StringVal("helloa11.01.020truenull2626") String all = s;
+  }
+
+  void stringIntRangeConcat(
+      @IntRange(from = 0, to = 1) int num, @IntRange(from = 'A', to = 'B') char letter) {
+    @StringVal({"num0", "num1"}) String numV = "num" + num;
+    @StringVal({"letterA", "letterB"}) String letterV = "letter" + letter;
+  }
+}
diff --git a/framework/tests/value/StringLen.java b/framework/tests/value/StringLen.java
new file mode 100644
index 0000000..8c093fc
--- /dev/null
+++ b/framework/tests/value/StringLen.java
@@ -0,0 +1,112 @@
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.ArrayLenRange;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.common.value.qual.MinLen;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class StringLen {
+  void stringValArrayLen(
+      @StringVal("") String empty,
+      @StringVal("const") String constant,
+      @StringVal({"s", "longconstant"}) String values,
+      String unknown) {
+
+    // Compatibility with ArrayLen
+    @ArrayLen(0) String len0 = empty;
+    @ArrayLen(5) String len5 = constant;
+    @ArrayLen({1, 12}) String len1_12 = values;
+
+    // Compatibility with ArrayLenRange
+    @ArrayLenRange(from = 0, to = 0) String rng0 = empty;
+    @ArrayLenRange(from = 5, to = 5) String rng5 = constant;
+    @ArrayLenRange(from = 1, to = 12) String rng1_12 = values;
+
+    // :: error: (assignment)
+    @ArrayLen(4) String len4 = constant;
+    // :: error: (assignment)
+    @ArrayLenRange(from = 1, to = 11) String rng1_10 = values;
+  }
+
+  void stringValLubToArrayLen(
+      boolean flag,
+      @StringVal({"a", "b", "c", "d", "e"}) String ae,
+      @StringVal({"f", "g", "h", "i", "j", "k"}) String fk,
+      @StringVal({"ffff", "gggg", "hhhh", "iiii", "jjjj", "kkkkkkk"}) String fkR) {
+
+    @ArrayLen(1) String ak = flag ? ae : fk;
+    @ArrayLen({1, 4, 7}) String akR = flag ? ae : fkR;
+  }
+
+  void stringValLubToArrayLenRange(
+      boolean flag,
+      @StringVal({"a", "bb", "ccc", "dddd", "eeeee"}) String ae,
+          @StringVal({"ffffff", "ggggggg", "hhhhhhhh", "iiiiiiiii", "jjjjjjjjjj", "kkkkkkkkkkk"}) String fk) {
+
+    @ArrayLenRange(from = 1, to = 11) String ak = flag ? ae : fk;
+  }
+
+  void arrayLenStringVal(
+      @ArrayLen(0) String len0,
+      @ArrayLenRange(from = 0, to = 0) String rng0,
+      @ArrayLen({0, 1}) String nonEmpty) {
+    @StringVal("") String emptyLen = len0;
+    @StringVal("") String emptyRng = rng0;
+
+    // :: error: (assignment)
+    @StringVal("") String emptyError = nonEmpty;
+    // :: error: (assignment)
+    @StringVal("a") String nonEmptyError = nonEmpty;
+  }
+
+  void stringValLength(
+      @StringVal("") String empty,
+      @StringVal("const") String constant,
+      @StringVal({"s", "longconstant"}) String values,
+      String unknown) {
+
+    @IntVal(0) int len0 = empty.length();
+    @IntVal(5) int len5 = constant.length();
+    @IntVal({1, 12}) int len1_12 = values.length();
+
+    // :: error: (assignment)
+    @IntVal({1, 11}) int len1_11 = values.length();
+  }
+
+  void arrayLenLength(
+      @ArrayLen(0) String empty,
+      @ArrayLen(5) String constant,
+      @ArrayLen({1, 12}) String values,
+      String unknown) {
+
+    @IntVal(0) int len0 = empty.length();
+    @IntVal(5) int len5 = constant.length();
+    @IntVal({1, 12}) int len1_12 = values.length();
+
+    // :: error: (assignment)
+    @IntVal({1, 11}) int len1_11 = values.length();
+  }
+
+  void arrayLenRangeLength(
+      @ArrayLenRange(from = 0, to = 0) String empty,
+      @ArrayLenRange(from = 5, to = 5) String constant,
+      @ArrayLenRange(from = 1, to = 12) String values,
+      String unknown) {
+
+    @IntRange(from = 0, to = 0) int len0 = empty.length();
+    @IntRange(from = 5, to = 5) int len5 = constant.length();
+    @IntRange(from = 1, to = 12) int len1_12 = values.length();
+
+    // :: error: (assignment)
+    @IntRange(from = 1, to = 11) int len1_11 = values.length();
+  }
+
+  void minLenLength(@MinLen(5) String s) {
+    @IntRange(from = 5) int l = s.length();
+  }
+
+  void arrayCast(@ArrayLen(1) String array) {
+    @ArrayLen(1) String cast1 = (String) array;
+    @ArrayLen(1) String cast2 = (@ArrayLen(1) String) array;
+  }
+}
diff --git a/framework/tests/value/StringLenConcats.java b/framework/tests/value/StringLenConcats.java
new file mode 100644
index 0000000..c7bfa15
--- /dev/null
+++ b/framework/tests/value/StringLenConcats.java
@@ -0,0 +1,118 @@
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.ArrayLenRange;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.common.value.qual.MinLen;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class StringLenConcats {
+
+  void stringLenConcat(@ArrayLen(3) String a, @ArrayLen(5) String b) {
+    @ArrayLen({7, 8, 9}) String ab = a + b;
+    @ArrayLen({6, 7}) String bxx = b + "xx";
+  }
+
+  void stringLenRangeConcat(
+      @ArrayLenRange(from = 3, to = 5) String a, @ArrayLenRange(from = 11, to = 19) String b) {
+    @ArrayLenRange(from = 14, to = 24) String ab = a + b;
+    @ArrayLenRange(from = 13, to = 21) String bxx = b + "xx";
+  }
+
+  void stringLenLenRangeConcat(
+      @ArrayLen({3, 4, 5}) String a, @ArrayLenRange(from = 10, to = 100) String b) {
+    @ArrayLenRange(from = 13, to = 105) String ab = a + b;
+  }
+
+  void stringValLenConcat(
+      @StringVal("constant") String a,
+      @StringVal({"a", "b", "c"}) String b,
+      @StringVal({"a", "xxx"}) String c,
+      @ArrayLen(11) String d) {
+
+    @ArrayLen({8, 12, 15, 19}) String ad = a + d;
+    @ArrayLen({5, 8, 12, 15}) String bd = b + d;
+    @ArrayLenRange(from = 4, to = 15) String cd = c + d;
+  }
+
+  void stringValLenRangeConcat(
+      @StringVal("constant") String a,
+      @StringVal({"a", "b", "c"}) String b,
+      @StringVal({"a", "xxx"}) String c,
+      @ArrayLenRange(from = 11, to = 19) String d) {
+
+    @ArrayLenRange(from = 19, to = 27) String ad = a + d;
+    @ArrayLenRange(from = 12, to = 20) String bd = b + d;
+    @ArrayLenRange(from = 12, to = 22) String cd = c + d;
+  }
+
+  void tooManyStringValConcat(
+      @StringVal({"a", "b", "c", "d"}) String a,
+      @StringVal({"ee", "ff", "gg", "hh", "ii"}) String b) {
+    @ArrayLen({2, 5, 8}) String aa = a + a;
+    @ArrayLen({3, 5, 6, 8}) String ab = a + b;
+  }
+
+  void charConversions(
+      char c,
+      @IntVal({1, 100, 10000}) char d,
+      @ArrayLen({100, 200}) String s,
+      @ArrayLenRange(from = 100, to = 200) String t,
+      @StringVal({"a", "bb", "ccc", "dddd"}) String u) {
+    @ArrayLen({5, 101, 201}) String sc = s + c;
+    @ArrayLen({5, 101, 201}) String sd = s + d;
+
+    @ArrayLenRange(from = 101, to = 201) String tc = t + c;
+
+    @ArrayLen({2, 3, 4, 5}) String uc = u + c;
+    @ArrayLen({2, 3, 4, 5}) String ud = u + d;
+  }
+
+  void intConversions(
+      @IntVal(123) int intConst,
+      @IntRange(from = -100000, to = 100) int intRange,
+      @IntRange(from = 100, to = 100000) int positiveRange,
+      int unknownInt,
+      @ArrayLen(10) String a,
+      @ArrayLenRange(from = 10, to = 20) String b,
+      @StringVal({"aaa", "bbbbb"}) String c) {
+    @ArrayLen({7, 13}) String aConst = a + intConst;
+    @ArrayLenRange(from = 5, to = 17) String aRange = a + intRange;
+    @ArrayLen({7, 8, 9, 10, 13, 14, 15, 16}) String aPositive = a + positiveRange;
+    @ArrayLenRange(from = 11, to = 21) String aUnknown = a + unknownInt;
+
+    @ArrayLenRange(from = 13, to = 23) String bConst = b + intConst;
+    @ArrayLenRange(from = 11, to = 27) String bRange = b + intRange;
+    @ArrayLenRange(from = 13, to = 26) String bPositive = b + positiveRange;
+    @ArrayLenRange(from = 11, to = 31) String bUnknown = b + unknownInt;
+
+    @StringVal({"aaa123", "bbbbb123", "null123"}) String cConst = c + intConst;
+    @ArrayLen({4, 5, 6, 7, 8, 9, 10, 11, 12}) String cRange = c + intRange;
+    @ArrayLen({6, 7, 8, 9, 10, 11}) String cPositive = c + positiveRange;
+  }
+
+  void longConversions(
+      @IntVal(1000000000000l) long longConst,
+      @IntRange(from = 10, to = 1000000000000l) long longRange,
+      long unknownLong,
+      @ArrayLen(10) String a) {
+
+    @ArrayLen({17, 23}) String aConst = a + longConst;
+    @ArrayLenRange(from = 12, to = 23) String aRange = a + longRange;
+    @ArrayLenRange(from = 11, to = 30) String aUnknown = a + unknownLong;
+  }
+
+  void byteConversions(
+      @IntVal(100) byte byteConst,
+      @IntRange(from = 2, to = 10) byte byteRange,
+      byte unknownByte,
+      @ArrayLen(10) String a) {
+
+    @ArrayLen({7, 13}) String aConst = a + byteConst;
+    @ArrayLenRange(from = 5, to = 12) String aRange = a + byteRange;
+    @ArrayLenRange(from = 5, to = 14) String aUnknown = a + unknownByte;
+  }
+
+  void minLenConcat(@MinLen(5) String s, @MinLen(7) String t) {
+    @MinLen(12) String st = s + t;
+  }
+}
diff --git a/framework/tests/value/StringLenMethods.java b/framework/tests/value/StringLenMethods.java
new file mode 100644
index 0000000..c7dd22c
--- /dev/null
+++ b/framework/tests/value/StringLenMethods.java
@@ -0,0 +1,50 @@
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.ArrayLenRange;
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class StringLenMethods {
+  void toString(boolean b, char c, byte y, short s, int i, long l) {
+
+    @StringVal({"true", "false"}) String bs = Boolean.toString(b);
+    @ArrayLen(1) String cs = Character.toString(c);
+    @ArrayLen({1, 2, 3, 4}) String ys = Byte.toString(y);
+    @ArrayLen({1, 2, 3, 4, 5, 6}) String ss = Short.toString(s);
+    @ArrayLenRange(from = 1, to = 11) String is = Integer.toString(i);
+    @ArrayLenRange(from = 1, to = 20) String ls = Long.toString(l);
+
+    @StringVal({"true", "false"}) String bbs = Boolean.valueOf(b).toString();
+    @ArrayLen(1) String bcs = Character.valueOf(c).toString();
+    @ArrayLen({1, 2, 3, 4}) String bys = Byte.valueOf(y).toString();
+    @ArrayLen({1, 2, 3, 4, 5, 6}) String bss = Short.valueOf(s).toString();
+    @ArrayLenRange(from = 1, to = 11) String bis = Integer.valueOf(i).toString();
+    @ArrayLenRange(from = 1, to = 20) String bls = Long.valueOf(l).toString();
+
+    // Added in 1.8
+    // @ArrayLenRange(from = 1, to = 10) String iu = Integer.toUnsignedString(i);
+    // @ArrayLenRange(from = 1, to = 19) String lu = Long.toUnsignedString(l);
+
+    @StringVal({"true", "false"}) String sbs = String.valueOf(b);
+    @ArrayLen(1) String scs = String.valueOf(c);
+    @ArrayLenRange(from = 1, to = 11) String sis = String.valueOf(i);
+    @ArrayLenRange(from = 1, to = 20) String sls = String.valueOf(l);
+  }
+
+  void toStringRadix(int i, long l, @IntRange(from = 2, to = 36) int radix) {
+    @ArrayLenRange(from = 1) String is = Integer.toString(i, radix);
+    @ArrayLenRange(from = 1) String ls = Long.toString(l, radix);
+
+    // Added in 1.8
+    // @ArrayLenRange(from = 1) String iu = Integer.toUnsignedString(i, radix);
+    // @ArrayLenRange(from = 1) String lu = Long.toUnsignedString(l, radix);
+
+    @ArrayLenRange(from = 1, to = 32) String ib = Integer.toBinaryString(i);
+    @ArrayLenRange(from = 1, to = 64) String lb = Long.toBinaryString(l);
+
+    @ArrayLenRange(from = 1, to = 8) String ix = Integer.toHexString(i);
+    @ArrayLenRange(from = 1, to = 16) String lx = Long.toHexString(l);
+
+    @ArrayLenRange(from = 1, to = 11) String io = Integer.toOctalString(i);
+    @ArrayLenRange(from = 1, to = 22) String lo = Long.toOctalString(l);
+  }
+}
diff --git a/framework/tests/value/StringLenWidening.java b/framework/tests/value/StringLenWidening.java
new file mode 100644
index 0000000..e3cfafb
--- /dev/null
+++ b/framework/tests/value/StringLenWidening.java
@@ -0,0 +1,19 @@
+// Tests termination of a loop increasing the length of a string
+
+public class StringLenWidening {
+
+  // Minimized example from java.util.logging.Logger.entering
+  public void entering(Object params[]) {
+    String msg = "ENTRY";
+    for (int i = 0; i < params.length; i++) {
+      msg = msg + i;
+    }
+  }
+
+  public void repeat(int a) {
+    String str = "";
+    for (int i = 0; i < a; i++) {
+      str += "a";
+    }
+  }
+}
diff --git a/framework/tests/value/StringPolyValue.java b/framework/tests/value/StringPolyValue.java
new file mode 100644
index 0000000..a67e9ea
--- /dev/null
+++ b/framework/tests/value/StringPolyValue.java
@@ -0,0 +1,13 @@
+import org.checkerframework.common.value.qual.StringVal;
+
+public class StringPolyValue {
+  void stringValArrayLen(@StringVal({"a", "b", "c"}) String abc) {
+
+    @StringVal({"a", "b", "c"}) String ns = new String(abc);
+    @StringVal({"a", "b", "c"}) String ts = abc.toString();
+    @StringVal({"a", "b", "c"}) String i = abc.intern();
+    @StringVal({"a", "b", "c"}) String nstca = new String(abc.toCharArray());
+    @StringVal({"a", "b", "c"}) String votca = String.valueOf(abc.toCharArray());
+    @StringVal({"a", "b", "c"}) String cvotca = String.copyValueOf(abc.toCharArray());
+  }
+}
diff --git a/framework/tests/value/StringValCrash.java b/framework/tests/value/StringValCrash.java
new file mode 100644
index 0000000..f96b91c
--- /dev/null
+++ b/framework/tests/value/StringValCrash.java
@@ -0,0 +1,9 @@
+import java.util.List;
+
+public class StringValCrash {
+
+  void foo() {
+    List<String> path = null;
+    System.out.print(path.size() + "...");
+  }
+}
diff --git a/framework/tests/value/StringValNull.java b/framework/tests/value/StringValNull.java
new file mode 100644
index 0000000..fdf0b45
--- /dev/null
+++ b/framework/tests/value/StringValNull.java
@@ -0,0 +1,66 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class StringValNull {
+
+  public static void main(String[] args) {
+    @StringVal("itsValue") String nbleString = null;
+    @StringVal("itsValue") String nnString = "itsValue";
+
+    System.out.println(toString1(nbleString));
+    System.out.println(toString2(nbleString));
+
+    System.out.println(toString1(nnString));
+    System.out.println(toString2(nnString));
+    // System.out.println(toString3(nnString));
+
+    @IntVal(22) Integer nbleInteger = null;
+    @IntVal(22) Integer nnInteger = 22;
+
+    System.out.println(toString4(nbleInteger));
+    System.out.println(toString5(nbleInteger));
+
+    System.out.println(toString4(nnInteger));
+    System.out.println(toString5(nnInteger));
+    System.out.println(toString6(nnInteger));
+  }
+
+  static @StringVal("arg=itsValue") String toString1(@Nullable @StringVal("itsValue") String arg) {
+    // :: error: (return)
+    return "arg=" + arg;
+  }
+
+  static @StringVal({"arg=itsValue", "arg=null"}) String toString2(
+      @Nullable @StringVal("itsValue") String arg) {
+    return "arg=" + arg;
+  }
+
+  /* static @StringVal("arg=itsValue") String toString3(@StringVal("itsValue") String arg) {
+      return "arg=" + arg;
+  } */
+
+  static @StringVal("arg=22") String toString4(@Nullable @IntVal(22) Integer arg) {
+    // :: error: (return)
+    return "arg=" + arg;
+  }
+
+  static @StringVal({"arg=22", "arg=null"}) String toString5(@Nullable @IntVal(22) Integer arg) {
+    return "arg=" + arg;
+  }
+
+  static @StringVal("arg=22") String toString6(@IntVal(22) int arg) {
+    return "arg=" + arg;
+  }
+
+  final @StringVal("hello") String s = null;
+
+  @StringVal("hello") String s2 = null;
+
+  void method2(StringValNull obj) {
+    // :: error: (assignment)
+    @StringVal("hello") String l1 = "" + obj.s;
+    // :: error: (assignment)
+    @StringVal("hello") String l2 = "" + obj.s2;
+  }
+}
diff --git a/framework/tests/value/StringValNullConcatLength.java b/framework/tests/value/StringValNullConcatLength.java
new file mode 100644
index 0000000..8c6aad9
--- /dev/null
+++ b/framework/tests/value/StringValNullConcatLength.java
@@ -0,0 +1,27 @@
+import org.checkerframework.common.value.qual.ArrayLen;
+import org.checkerframework.common.value.qual.ArrayLenRange;
+import org.checkerframework.common.value.qual.StringVal;
+
+public class StringValNullConcatLength {
+  @StringVal("a") String string1;
+
+  @StringVal("b") String string2;
+
+  @ArrayLen({1, 2}) String string3;
+
+  @ArrayLen({2, 3}) String string4;
+
+  @ArrayLenRange(from = 1, to = 3) String string5;
+
+  @StringVal({"anull", "ab", "nullb", "nullnull"}) String string6 = string1 + string2;
+
+  @ArrayLen({2, 3, 5, 6, 8}) String string7 = string1 + string3;
+
+  @ArrayLen({3, 4, 5, 6, 7, 8}) String string8 = string3 + string4;
+
+  @ArrayLenRange(from = 2, to = 8) String string10 = string1 + string5;
+
+  // Omitting that string2 can be null
+  // :: error: (assignment)
+  @ArrayLen({3, 4}) String string9 = string2 + string4;
+}
diff --git a/framework/tests/value/StringValOfArrays.java b/framework/tests/value/StringValOfArrays.java
new file mode 100644
index 0000000..564e189
--- /dev/null
+++ b/framework/tests/value/StringValOfArrays.java
@@ -0,0 +1,9 @@
+import org.checkerframework.common.value.qual.StringVal;
+
+public class StringValOfArrays {
+  void chars() {
+    String s = "$-hello@";
+    char @StringVal("$-hello@") [] chars = s.toCharArray();
+    @StringVal("$-hello@") String s2 = new String(chars);
+  }
+}
diff --git a/framework/tests/value/Switch.java b/framework/tests/value/Switch.java
new file mode 100644
index 0000000..e5e0f74
--- /dev/null
+++ b/framework/tests/value/Switch.java
@@ -0,0 +1,193 @@
+import org.checkerframework.common.value.qual.*;
+
+// Test case for switch statements. Not really about the value checker (more about
+// whether the semantics of switch are correct in general), but I needed some
+// checker to try it out on.
+public class Switch {
+  void test1(@IntVal({1, 2, 3, 4, 5}) int x) {
+
+    // easy version, no fall through
+    switch (x) {
+      case 1:
+        @IntVal({1}) int y = x;
+        break;
+      case 2:
+        @IntVal({2}) int w = x;
+        // :: error: (assignment)
+        @IntVal({1}) int z = x;
+        break;
+      default:
+        @IntVal({3, 4, 5}) int q = x;
+        break;
+    }
+  }
+
+  void test2(@IntVal({1, 2, 3, 4, 5}) int x) {
+
+    // harder version, fall through
+    switch (x) {
+      case 1:
+        @IntVal({1}) int y = x;
+      case 2:
+      case 3:
+        @IntVal({1, 2, 3}) int w = x;
+        // :: error: (assignment)
+        @IntVal({2, 3}) int z = x;
+        // :: error: (assignment)
+        @IntVal({3}) int z1 = x;
+        break;
+      default:
+        @IntVal({4, 5}) int q = x;
+
+        // :: error: (assignment)
+        @IntVal(5) int q2 = x;
+        break;
+    }
+  }
+
+  void test3(@IntVal({1, 2, 3, 4, 5}) int x) {
+
+    // harder version, fall through
+    switch (x) {
+      case 1:
+        @IntVal({1}) int y = x;
+      case 2:
+      case 3:
+        @IntVal({1, 2, 3}) int w = x;
+        // :: error: (assignment)
+        @IntVal({2, 3}) int z = x;
+        // :: error: (assignment)
+        @IntVal({3}) int z1 = x;
+        break;
+      case 4:
+      default:
+        @IntVal({4, 5}) int q = x;
+
+        // :: error: (assignment)
+        @IntVal(5) int q2 = x;
+        break;
+    }
+  }
+
+  void test4(int x) {
+    switch (x) {
+      case 1:
+        @IntVal({1}) int y = x;
+        break;
+      case 2:
+      case 3:
+        @IntVal({2, 3}) int z = x;
+        break;
+      case 4:
+      default:
+        return;
+    }
+    @IntVal({1, 2, 3}) int y = x;
+    // :: error: (assignment)
+    @IntVal(4) int y2 = x;
+  }
+
+  void test5(@IntVal({0, 1, 2, 3, 4}) int x) {
+    @IntVal({0, 1, 2, 3, 4, 5}) int y = x;
+    switch (y = y + 1) {
+      case 1:
+        @IntVal({1}) int a = y;
+        // :: error: (assignment)
+        @IntVal({2}) int b = y;
+      case 2:
+      case 3:
+        @IntVal({1, 2, 3}) int c = y;
+        break;
+      default:
+        // :: error: (assignment)
+        @IntVal({4}) int d = y;
+        // :: error: (assignment)
+        @IntVal({5}) int e = y;
+        @IntVal({4, 5}) int f = y;
+        break;
+    }
+  }
+
+  void testInts1(@IntRange(from = 0, to = 100) int x) {
+    switch (x) {
+      case 0:
+      case 1:
+      case 2:
+        @IntVal({0, 1, 2}) int z = x;
+        return;
+      default:
+    }
+
+    @IntRange(from = 3, to = 100) int z = x;
+  }
+
+  void testInts2(@IntRange(from = 0, to = 100) int x) {
+
+    // harder version, fall through
+    switch (x) {
+      case 0:
+        @IntVal(0) int a = x;
+        break;
+      case 1:
+        @IntVal(1) int b = x;
+        break;
+      case 2:
+        @IntVal(2) int c = x;
+      default:
+        @IntRange(from = 2, to = 100) int d = x;
+        break;
+    }
+  }
+
+  void testChars(char x) {
+    switch (x) {
+      case 'a':
+      case 2:
+        @IntVal({'a', 2}) int z = x;
+        break;
+      case 'b':
+        @IntVal('b') int v = x;
+        break;
+      default:
+        return;
+    }
+    @IntVal({'a', 2, 'b'}) int y = x;
+  }
+
+  void testStrings1(String s) {
+    switch (s) {
+      case "Good":
+        @StringVal("Good") String x = s;
+      case "Bye":
+        @StringVal({"Good", "Bye"}) String y = s;
+        break;
+      case "Hello":
+        @StringVal("Hello") String z = s;
+        break;
+      default:
+        return;
+    }
+    @StringVal({"Good", "Bye", "Hello"}) String q = s;
+  }
+
+  void testStrings2(String s) {
+    String a;
+    switch (a = s) {
+      case "Good":
+        @StringVal("Good") String x1 = a;
+        @StringVal("Good") String x2 = s;
+      case "Bye":
+        @StringVal({"Good", "Bye"}) String y1 = a;
+        @StringVal({"Good", "Bye"}) String y2 = s;
+        break;
+      case "Hello":
+        @StringVal("Hello") String z1 = a;
+        @StringVal("Hello") String z2 = s;
+        break;
+      default:
+        return;
+    }
+    @StringVal({"Good", "Bye", "Hello"}) String q1 = a;
+    @StringVal({"Good", "Bye", "Hello"}) String q2 = s;
+  }
+}
diff --git a/framework/tests/value/TypeCast.java b/framework/tests/value/TypeCast.java
new file mode 100644
index 0000000..e6ca7b9
--- /dev/null
+++ b/framework/tests/value/TypeCast.java
@@ -0,0 +1,56 @@
+import org.checkerframework.common.value.qual.*;
+
+public class TypeCast {
+
+  public void charIntDoubleTest() {
+    int a = 98;
+    long b = 98;
+    double c = 98.0;
+    float d = 98.0f;
+    char e = 'b';
+    short f = 98;
+    byte g = 98;
+
+    @IntVal({'b'}) char h = (char) a;
+    h = (char) b;
+    // :: warning: (cast.unsafe)
+    h = (char) c;
+    // :: warning: (cast.unsafe)
+    h = (char) d;
+    h = (char) f;
+    h = (char) g;
+
+    @IntVal({98}) int i = (int) b;
+    // :: warning: (cast.unsafe)
+    i = (int) c;
+    // :: warning: (cast.unsafe)
+    i = (int) d;
+    i = (int) e;
+    i = (int) f;
+    i = (int) g;
+
+    @DoubleVal({98.0}) double j = (double) a;
+    j = (double) b;
+    j = (double) d;
+    j = (double) e;
+    j = (double) f;
+    j = (double) g;
+  }
+
+  void otherCast() {
+
+    byte[] b = (byte[]) null;
+    @BoolVal(true) boolean bool = (boolean) true;
+  }
+
+  void rangeCast(@IntRange(from = 127, to = 128) int a, @IntRange(from = 128, to = 129) int b) {
+    @IntRange(from = 0, to = 128)
+    // :: error: (assignment) :: warning: (cast.unsafe)
+    byte c = (byte) a;
+    // (byte) a is @IntRange(from = -128, to = 127) because of casting
+
+    @IntRange(from = -128, to = -127)
+    // :: warning: (cast.unsafe)
+    byte d = (byte) b;
+  }
+}
diff --git a/framework/tests/value/TypeVars.java b/framework/tests/value/TypeVars.java
new file mode 100644
index 0000000..f9ce792
--- /dev/null
+++ b/framework/tests/value/TypeVars.java
@@ -0,0 +1,49 @@
+public class TypeVars<K, V> {
+  private void test(K key, V value) {
+    String s = "Negative size: " + key + "=" + value;
+  }
+
+  class MyClass<T> {
+    public T myMethod() {
+      return null;
+    }
+  }
+
+  public class TypeVarDefaults {
+    class ImplicitUpperBound<T> {}
+
+    class ExplicitUpperBound<T extends Object> {}
+
+    void useImplicit() {
+      ImplicitUpperBound<Object> bottom;
+    }
+
+    void useExplicit() {
+      ExplicitUpperBound<Object> bottom;
+    }
+
+    void wildCardImplicit() {
+      ImplicitUpperBound<?> bottom;
+    }
+
+    void wildCardExplicit() {
+      ExplicitUpperBound<?> bottom;
+    }
+
+    void wildCardUpperBoundImplicit() {
+      ImplicitUpperBound<? extends Object> bottom;
+    }
+
+    void wildCardUpperBoundExplicit() {
+      ExplicitUpperBound<? extends Object> bottom;
+    }
+
+    void wildCardLowerImplicit() {
+      ImplicitUpperBound<? super Object> bottom;
+    }
+
+    void wildCardLowerBoundExplicit() {
+      ExplicitUpperBound<? super Object> bottom;
+    }
+  }
+}
diff --git a/framework/tests/value/Unaries.java b/framework/tests/value/Unaries.java
new file mode 100644
index 0000000..032c8cf
--- /dev/null
+++ b/framework/tests/value/Unaries.java
@@ -0,0 +1,60 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Unaries {
+
+  public void complement() {
+    boolean a = false;
+    @BoolVal({true}) boolean b = !a;
+
+    @IntVal({-5}) int c = ~4;
+
+    @IntVal({-123456789}) long d = ~123456788;
+  }
+
+  public void prefix() {
+    byte a = 1;
+    @IntVal({2}) byte b = ++a;
+
+    @IntVal({3}) short c = ++a;
+
+    @IntVal({4}) int d = ++a;
+
+    @IntVal({5}) long e = ++a;
+    ++a;
+    e = --a;
+    d = --a;
+    c = --a;
+    b = --a;
+  }
+
+  public void postfix() {
+    int a = 0;
+    @IntVal({0}) int b = a++;
+    @IntVal({1}) int c = a--;
+    b = a++;
+
+    @IntVal({1}) long d = a--;
+
+    double e = 0.25;
+    @DoubleVal({0.25}) double f = e++;
+    @DoubleVal({1.25}) double g = e--;
+    f = e;
+  }
+
+  public void plusminus() {
+    @IntVal({48}) int a = +48;
+    @IntVal({-49}) int b = -49;
+
+    @IntVal({34}) long c = +34;
+    @IntVal({-34}) long d = -34;
+  }
+
+  public void intRange(@IntRange(from = 0, to = 2) int val) {
+    int a = val;
+    @IntRange(from = -2, to = 0) int b = -a;
+    @IntRange(from = 0, to = 2) int c = +a;
+    @IntRange(from = -3, to = -1) int d = ~a;
+    @IntRange(from = 1, to = 3) int e = ++a;
+    @IntRange(from = 1, to = 3) int f = a++;
+  }
+}
diff --git a/framework/tests/value/UncheckedMinLen.java b/framework/tests/value/UncheckedMinLen.java
new file mode 100644
index 0000000..db5fea2
--- /dev/null
+++ b/framework/tests/value/UncheckedMinLen.java
@@ -0,0 +1,18 @@
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.MinLen;
+
+// test case for kelloggm#183: https://github.com/kelloggm/checker-framework/issues/183
+
+public class UncheckedMinLen {
+  void addToUnboundedIntRange(@IntRange(from = 0) int l, Object v) {
+    // :: error: (assignment)
+    Object @MinLen(100) [] o = new Object[l + 1];
+    o[99] = v;
+  }
+
+  void addToBoundedIntRangeOK(@IntRange(from = 0, to = 1) int l, Object v) {
+    // :: error: (assignment)
+    Object @MinLen(100) [] o = new Object[l + 1];
+    o[99] = v;
+  }
+}
diff --git a/framework/tests/value/Underflows.java b/framework/tests/value/Underflows.java
new file mode 100644
index 0000000..6e47a95
--- /dev/null
+++ b/framework/tests/value/Underflows.java
@@ -0,0 +1,41 @@
+import org.checkerframework.common.value.qual.*;
+
+public class Underflows {
+  static void bytes() {
+    byte min = Byte.MIN_VALUE;
+    // :: warning: (cast.unsafe)
+    @IntVal(127) byte maxPlus1 = (byte) (min - 1);
+  }
+
+  static void chars() {
+    char min = Character.MIN_VALUE;
+    // :: warning: (cast.unsafe)
+    @IntVal(65535) char maxPlus1 = (char) (min - 1);
+  }
+
+  static void shorts() {
+    short min = Short.MIN_VALUE;
+    // :: warning: (cast.unsafe)
+    @IntVal(32767) short maxPlus1 = (short) (min - 1);
+  }
+
+  static void ints() {
+    int min = Integer.MIN_VALUE;
+    @IntVal(2147483647) int maxPlus1 = min - 1;
+  }
+
+  static void longs() {
+    long min = Long.MIN_VALUE;
+    @IntVal(9223372036854775807L) long maxPlus1 = min - 1;
+  }
+
+  static void doubles() {
+    double min = Double.MIN_VALUE;
+    @DoubleVal(-1.0) double maxPlus1 = min - 1.0;
+  }
+
+  static void floats() {
+    float min = Float.MIN_VALUE;
+    @DoubleVal(-1.0F) float maxPlus1 = min - 1.0f;
+  }
+}
diff --git a/framework/tests/value/ValueCast.java b/framework/tests/value/ValueCast.java
new file mode 100644
index 0000000..3ada76f
--- /dev/null
+++ b/framework/tests/value/ValueCast.java
@@ -0,0 +1,59 @@
+// Test case for issue 1299: https://github.com/typetools/checker-framework/issues/1299
+
+import org.checkerframework.common.value.qual.*;
+
+public class ValueCast {
+  void testShort_plus(@IntRange(from = 0) short x) {
+    @IntRange(from = 1, to = Short.MAX_VALUE + 1) int y = x + 1;
+    // :: error: (assignment)
+    @IntRange(from = 1, to = Short.MAX_VALUE - 1) int z = x;
+  }
+
+  void testIntFrom(@IntRange(from = 0) int x) {
+    @IntRange(from = 0, to = Integer.MAX_VALUE) long y = x;
+    // :: error: (assignment)
+    @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int z = x;
+  }
+
+  void testShortFrom(@IntRange(from = 0) short x) {
+    @IntRange(from = 0, to = Short.MAX_VALUE) int y = x;
+    // :: error: (assignment)
+    @IntRange(from = 0, to = Short.MAX_VALUE - 1) int z = x;
+  }
+
+  void testCharFrom(@IntRange(from = 0) char x) {
+    @IntRange(from = 0, to = Character.MAX_VALUE) int y = x;
+    // :: error: (assignment)
+    @IntRange(from = 0, to = Character.MAX_VALUE - 1) int z = x;
+  }
+
+  void testByteFrom(@IntRange(from = 0) byte x) {
+    @IntRange(from = 0, to = Byte.MAX_VALUE) int y = x;
+    // :: error: (assignment)
+    @IntRange(from = 0, to = Byte.MAX_VALUE - 1) int z = x;
+  }
+
+  void testIntTo(@IntRange(to = 0) int x) {
+    @IntRange(to = 0, from = Integer.MIN_VALUE) long y = x;
+    // :: error: (assignment)
+    @IntRange(to = 0, from = Integer.MIN_VALUE + 1) int z = x;
+  }
+
+  void testShortTo(@IntRange(to = 0) short x) {
+    @IntRange(to = 0, from = Short.MIN_VALUE) int y = x;
+    // :: error: (assignment)
+    @IntRange(to = 0, from = Short.MIN_VALUE + 1) int z = x;
+  }
+
+  void testCharTo(@IntRange(to = 1) char x) {
+    @IntRange(to = 1, from = Character.MIN_VALUE) int y = x;
+    // :: error: (assignment)
+    @IntRange(to = 1, from = Character.MIN_VALUE + 1) int z = x;
+  }
+
+  void testByteTo(@IntRange(to = 0) byte x) {
+    @IntRange(to = 0, from = Byte.MIN_VALUE) int y = x;
+    // :: error: (assignment)
+    @IntRange(to = 0, from = Byte.MIN_VALUE + 1) int z = x;
+  }
+}
diff --git a/framework/tests/value/ValueCast2.java b/framework/tests/value/ValueCast2.java
new file mode 100644
index 0000000..be2dd26
--- /dev/null
+++ b/framework/tests/value/ValueCast2.java
@@ -0,0 +1,25 @@
+// Test case for issue 1264: https://github.com/typetools/checker-framework/issues/1264
+
+import org.checkerframework.common.value.qual.*;
+
+public class ValueCast2 {
+  byte foo(@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int x) {
+    return (byte) x;
+  }
+
+  byte bar(@IntRange(from = -1000, to = 500) int x) {
+    return (byte) x;
+  }
+
+  short foo1(@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int x) {
+    return (short) x;
+  }
+
+  int foo2(@IntRange(from = Long.MIN_VALUE, to = Long.MAX_VALUE) long x) {
+    return (int) x;
+  }
+
+  int baz(@IntRange(from = Long.MIN_VALUE, to = 0) long x) {
+    return (int) x;
+  }
+}
diff --git a/framework/tests/value/ValueWrapperCast.java b/framework/tests/value/ValueWrapperCast.java
new file mode 100644
index 0000000..1077864
--- /dev/null
+++ b/framework/tests/value/ValueWrapperCast.java
@@ -0,0 +1,59 @@
+// Test case for issue 2815: https://github.com/typetools/checker-framework/issues/2815
+
+import org.checkerframework.common.value.qual.*;
+
+public class ValueWrapperCast {
+  void testShort_plus(@IntRange(from = 0) Short x) {
+    @IntRange(from = 1, to = Short.MAX_VALUE + 1) int y = x + 1;
+    // :: error: (assignment)
+    @IntRange(from = 1, to = Short.MAX_VALUE - 1) int z = x;
+  }
+
+  void testIntFrom(@IntRange(from = 0) Integer x) {
+    @IntRange(from = 0, to = Integer.MAX_VALUE) long y = x;
+    // :: error: (assignment)
+    @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int z = x;
+  }
+
+  void testShortFrom(@IntRange(from = 0) Short x) {
+    @IntRange(from = 0, to = Short.MAX_VALUE) int y = x;
+    // :: error: (assignment)
+    @IntRange(from = 0, to = Short.MAX_VALUE - 1) int z = x;
+  }
+
+  void testCharFrom(@IntRange(from = 0) Character x) {
+    @IntRange(from = 0, to = Character.MAX_VALUE) int y = x;
+    // :: error: (assignment)
+    @IntRange(from = 0, to = Character.MAX_VALUE - 1) int z = x;
+  }
+
+  void testByteFrom(@IntRange(from = 0) Byte x) {
+    @IntRange(from = 0, to = Byte.MAX_VALUE) int y = x;
+    // :: error: (assignment)
+    @IntRange(from = 0, to = Byte.MAX_VALUE - 1) int z = x;
+  }
+
+  void testIntTo(@IntRange(to = 0) Integer x) {
+    @IntRange(to = 0, from = Integer.MIN_VALUE) long y = x;
+    // :: error: (assignment)
+    @IntRange(to = 0, from = Integer.MIN_VALUE + 1) int z = x;
+  }
+
+  void testShortTo(@IntRange(to = 0) Short x) {
+    @IntRange(to = 0, from = Short.MIN_VALUE) int y = x;
+    // :: error: (assignment)
+    @IntRange(to = 0, from = Short.MIN_VALUE + 1) int z = x;
+  }
+
+  void testCharTo(@IntRange(to = 1) Character x) {
+    @IntRange(to = 1, from = Character.MIN_VALUE) int y = x;
+    // :: error: (assignment)
+    @IntRange(to = 1, from = Character.MIN_VALUE + 1) int z = x;
+  }
+
+  void testByteTo(@IntRange(to = 0) Byte x) {
+    @IntRange(to = 0, from = Byte.MIN_VALUE) int y = x;
+    // :: error: (assignment)
+    @IntRange(to = 0, from = Byte.MIN_VALUE + 1) int z = x;
+  }
+}
diff --git a/framework/tests/value/VarArgRe.java b/framework/tests/value/VarArgRe.java
new file mode 100644
index 0000000..02a99fa
--- /dev/null
+++ b/framework/tests/value/VarArgRe.java
@@ -0,0 +1,25 @@
+import org.checkerframework.common.value.qual.IntVal;
+import org.checkerframework.framework.testchecker.lib.VarArgMethods;
+
+public class VarArgRe {
+  // VarArgMethods is declarded in
+  // framework/tests/src/org/checkerframework/framework/testchecker/lib.
+  // All the methods return the length of the vararg.
+  public void use0() {
+    @IntVal(0) int i1 = VarArgMethods.test0();
+    @IntVal(1) int i2 = VarArgMethods.test0(-1);
+    @IntVal(5) int i3 = VarArgMethods.test0(0, "sldfj", 0, 234, 234);
+  }
+
+  public void use1() {
+    @IntVal(0) int i1 = VarArgMethods.test1("13");
+    @IntVal(1) int i2 = VarArgMethods.test1("13", -1);
+    @IntVal(5) int i3 = VarArgMethods.test1("13", 0, "sldfj", 0, 234, 234);
+  }
+
+  public void use2() {
+    @IntVal(0) int i1 = VarArgMethods.test2("", "");
+    @IntVal(1) int i2 = VarArgMethods.test2("", "", -1);
+    @IntVal(5) int i3 = VarArgMethods.test2("", "", 0, "sldfj", 0, 234, 234);
+  }
+}
diff --git a/framework/tests/value/WildcardIn.java b/framework/tests/value/WildcardIn.java
new file mode 100644
index 0000000..0009509
--- /dev/null
+++ b/framework/tests/value/WildcardIn.java
@@ -0,0 +1,10 @@
+public class WildcardIn {
+
+  void foo(GenericObject<?> gen) {
+    Integer i = (Integer) gen.get();
+  }
+}
+
+interface GenericObject<T> {
+  public abstract T get();
+}
diff --git a/framework/tests/value/loops/DoWhile.java b/framework/tests/value/loops/DoWhile.java
new file mode 100644
index 0000000..1b1f6ee
--- /dev/null
+++ b/framework/tests/value/loops/DoWhile.java
@@ -0,0 +1,39 @@
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.IntVal;
+
+// Because the analysis of loops isn't precise enough, the Value Checker issues
+// warnings on this test case. So, suppress those warnings, but run the tests
+// to make sure that dataflow reaches a fixed point.
+@SuppressWarnings("value")
+public class DoWhile {
+
+  void doWhile() {
+    int d = 0;
+    do {
+      d++;
+    } while (d < 399);
+    @IntRange(from = 399) int after = d;
+  }
+
+  void another() {
+    int d = 0;
+    do {
+      d++;
+      if (d > 444) {
+        break;
+      }
+      @IntRange(from = 1, to = 444) int z = d;
+    } while (true);
+  }
+
+  void fromAnno(@IntVal(2222) int param) {
+    int d = 0;
+    do {
+      d++;
+      if (d > param) {
+        break;
+      }
+      @IntRange(from = 1, to = 2222) int z = d;
+    } while (true);
+  }
+}
diff --git a/framework/tests/value/loops/NestedLoops.java b/framework/tests/value/loops/NestedLoops.java
new file mode 100644
index 0000000..822bc4a
--- /dev/null
+++ b/framework/tests/value/loops/NestedLoops.java
@@ -0,0 +1,56 @@
+import org.checkerframework.common.value.qual.IntRange;
+
+// Because the analysis of loops isn't precise enough, the Value Checker issues
+// warnings on this test case. So, suppress those warnings, but run the tests
+// to make sure that dataflow reaches a fixed point.
+@SuppressWarnings("value")
+public class NestedLoops {
+  void test1() {
+    int doWhileIndex = 0;
+    do {
+      for (int forIndex = 0; forIndex < doWhileIndex; forIndex++) {
+        System.out.print("Hello");
+        int whileIndex = 0;
+        while (whileIndex < forIndex) {
+          whileIndex++;
+        }
+      }
+      doWhileIndex++;
+    } while (doWhileIndex < Integer.MAX_VALUE);
+  }
+
+  void test2() {
+    int doWhileIndex = 0;
+    do {
+      for (int forIndex = 0; forIndex < Integer.MAX_VALUE; forIndex++) {
+        System.out.print("Hello");
+        int whileIndex = 0;
+        while (whileIndex < Integer.MAX_VALUE) {
+          whileIndex++;
+        }
+      }
+      doWhileIndex++;
+    } while (doWhileIndex < Integer.MAX_VALUE);
+  }
+
+  void test3() {
+    int doWhileIndex = 0;
+    int forIndex;
+    int whileIndex = 0;
+
+    do {
+      @IntRange(to = 2999) int a = doWhileIndex;
+      for (forIndex = 0; forIndex < 4000; forIndex++) {
+
+        @IntRange(to = 3999) int b = forIndex;
+        System.out.print("Hello");
+        whileIndex = 0;
+        while (whileIndex < 5000) {
+          @IntRange(to = 4999) int c = whileIndex;
+          whileIndex++;
+        }
+      }
+      doWhileIndex++;
+    } while (doWhileIndex < 3000);
+  }
+}
diff --git a/framework/tests/value/loops/OscillatingLoops.java b/framework/tests/value/loops/OscillatingLoops.java
new file mode 100644
index 0000000..bf4458b
--- /dev/null
+++ b/framework/tests/value/loops/OscillatingLoops.java
@@ -0,0 +1,66 @@
+import org.checkerframework.common.value.qual.IntRange;
+import org.checkerframework.common.value.qual.IntVal;
+
+// Because the analysis of loops isn't precise enough, the Value Checker issues
+// warnings on this test case. So, suppress those warnings, but run the tests
+// to make sure that dataflow reaches a fixed point.
+@SuppressWarnings("value")
+public class OscillatingLoops {
+
+  void oscillatesDoWhile() {
+    int i = 0;
+    int d = 0;
+    do {
+      i++;
+      if (d > 4566) {
+        d = 0;
+      } else {
+        d++;
+      }
+    } while (i < Integer.MAX_VALUE);
+    @IntRange(from = 0, to = 4567) int after = d;
+    @IntVal(Integer.MAX_VALUE) int afterI = i;
+  }
+
+  void oscillatesWhile() {
+    int i = 0;
+    int d = 1;
+    while (i < Integer.MAX_VALUE) {
+      i++;
+      if (d > 4566) {
+        d = 0;
+      } else {
+        d++;
+      }
+    }
+    @IntRange(from = 0, to = 4567) int after = d;
+    @IntVal(Integer.MAX_VALUE) int afterI = i;
+  }
+
+  void oscillatesDoWhile2() {
+    int i = 0;
+    int d = 0;
+    do {
+      if (d > 4566) {
+        d = 0;
+      } else {
+        d++;
+      }
+      i++;
+    } while (i < Integer.MAX_VALUE);
+    @IntRange(from = -128, to = 32767) int after = d;
+    @IntVal(Integer.MAX_VALUE) int afterI = i;
+  }
+
+  void oscillatesFor() {
+    int d = 0;
+    for (int i = 0; i < Integer.MAX_VALUE; i++) {
+      if (d > 4566) {
+        d = 0;
+      } else {
+        d++;
+      }
+    }
+    @IntRange(from = -128, to = 32767) int after = d;
+  }
+}
diff --git a/framework/tests/value/loops/WidenedUpperBound.java b/framework/tests/value/loops/WidenedUpperBound.java
new file mode 100644
index 0000000..4ffe06a
--- /dev/null
+++ b/framework/tests/value/loops/WidenedUpperBound.java
@@ -0,0 +1,199 @@
+import java.util.List;
+import org.checkerframework.common.value.qual.IntRange;
+
+// Because the analysis of loops isn't precise enough, the Value Checker issues
+// warnings on this test case. So, suppress those warnings, but run the tests
+// to make sure that dataflow reaches a fixed point.
+// The expected errors in comments are the errors that should be issued if dataflow were precise
+// enough.
+@SuppressWarnings("value")
+public class WidenedUpperBound {
+
+  void increment() {
+    int forIndex;
+    for (forIndex = 0; forIndex < 4323; forIndex++) {
+      @IntRange(from = 0, to = 4322) int x = forIndex;
+    }
+    //// ::error: (assignment)
+    @IntRange(from = 0, to = 4322) int x = forIndex;
+    @IntRange(from = 4323) int y = forIndex;
+
+    int whileIndex = 0;
+    while (whileIndex < 1234) {
+      @IntRange(from = 0, to = 1233) int z = whileIndex;
+      whileIndex++;
+    }
+    //// ::error: (assignment)
+    @IntRange(from = 0, to = 1233) int a = whileIndex;
+    @IntRange(from = 1234) int b = whileIndex;
+
+    int doWhileIndex = 0;
+    do {
+      @IntRange(from = 0, to = 2344) int c = doWhileIndex;
+      doWhileIndex++;
+    } while (doWhileIndex < 2345);
+    //// ::error: (assignment)
+    @IntRange(from = 0, to = 2344) int d = doWhileIndex;
+    @IntRange(from = 2345) int e = doWhileIndex;
+  }
+
+  void decrement() {
+    int forIndex;
+    for (forIndex = 4323; forIndex > 0; forIndex--) {
+      @IntRange(from = 1, to = 4323) int x = forIndex;
+    }
+    //// ::error: (assignment)
+    @IntRange(from = 1, to = 4323) int x = forIndex;
+    @IntRange(to = 0) int y = forIndex;
+
+    int whileIndex = 1234;
+    while (whileIndex > 0) {
+      @IntRange(from = 1, to = 1234) int z = whileIndex;
+      whileIndex--;
+    }
+    //// ::error: (assignment)
+    @IntRange(from = 1, to = 1234) int a = whileIndex;
+    @IntRange(to = 0) int b = whileIndex;
+
+    int doWhileIndex = 2344;
+    do {
+      @IntRange(from = 1, to = 2344) int c = doWhileIndex;
+      doWhileIndex--;
+    } while (doWhileIndex > 0);
+    //// ::error: (assignment)
+    @IntRange(from = 1, to = 2344) int d = doWhileIndex;
+    @IntRange(to = 0) int e = doWhileIndex;
+  }
+
+  static void test1() {
+    for (int i = 40; i > 0; i--) {
+      @IntRange(from = 1, to = 40) int x = i;
+    }
+  }
+
+  static void test1Explicit() {
+    for (@IntRange(from = 1, to = 40) int i = 40; i > 0; i--) {
+      @IntRange(from = 1, to = 40) int x = i;
+    }
+  }
+
+  static void test2() {
+    for (int i = 0; i < 18; i++) {
+      @IntRange(from = 0, to = 17) int x = i;
+    }
+  }
+
+  static void test2Explicit() {
+    for (@IntRange(from = 0, to = 17) int i = 0; i < 18; i++) {
+      @IntRange(from = 0, to = 17) int x = i;
+    }
+  }
+
+  static void test3() {
+    for (int i = 0; i < 18; i++) {
+      @IntRange(from = 0, to = 17) int x = i;
+    }
+  }
+
+  static void evenOdd(int param) {
+    for (int i = 0; i < 12; i++) {
+      if (i % 2 == 0) {
+        @IntRange(from = 0, to = 11) int z = i;
+      }
+      @IntRange(from = 0, to = 11) int x = i;
+    }
+
+    for (int i = 0; i < 12; i++) {
+      if (param == 0) {
+        @IntRange(from = 0, to = 11) int z = i;
+      }
+      @IntRange(from = 0, to = 11) int x = i;
+    }
+  }
+
+  static void evenOdd2(int param) {
+    for (int i = 0; i < 300; i++) {
+      if (i % 2 == 0) {
+        @IntRange(from = 0, to = 299) int z = i;
+      }
+      @IntRange(from = 0, to = 299) int x = i;
+    }
+
+    for (int i = 0; i < 399; i++) {
+      if (param == 0) {
+        @IntRange(from = 0, to = 398) int z = i;
+      }
+      @IntRange(from = 0, to = 398) int x = i;
+    }
+  }
+
+  void ifBlock(int param) {
+    int x = 10;
+    if (x < param) {
+      x = param;
+    }
+    int z = 40;
+    if (z < param) {
+      param = 40;
+    }
+  }
+
+  void doWhile() {
+    int d = 0;
+    do {
+      @IntRange(from = 0, to = 399) int x = d;
+      if (d % 2 == 0) {
+        @IntRange(from = 0, to = 399) int y = d;
+      }
+
+      d++;
+    } while (d < 399);
+
+    @IntRange(from = 399) int z = d;
+  }
+
+  void doWhileMax() {
+    int d = 0;
+    do {
+      @IntRange(from = 0) int x = d;
+      if (d % 2 == 0) {
+        @IntRange(from = 0) int y = d;
+      }
+
+      d++;
+    } while (d < Integer.MAX_VALUE);
+
+    @IntRange(from = 399) int z = d;
+  }
+
+  void whileLoop() {
+    int i = 0;
+    while (i < 399) {
+      @IntRange(from = 0, to = 399) int x = i;
+      if (i % 2 == 0) {
+        @IntRange(from = 0, to = 399) int y = i;
+      }
+
+      i++;
+    }
+
+    @IntRange(from = 399) int z = i;
+  }
+
+  static void testMax() {
+    for (int i = 0; i < Integer.MAX_VALUE; i++) {
+      @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int x = i;
+    }
+  }
+
+  void exceptionLoop(List<Object> list) {
+    int x = 0;
+    for (short z = 0; z < list.size(); z++) {
+      x = z;
+      if (z == 100) {
+        break;
+      }
+    }
+    @IntRange(from = 0, to = 127) int result = x;
+  }
+}
diff --git a/framework/tests/value/lowercase.astub b/framework/tests/value/lowercase.astub
new file mode 100644
index 0000000..15fa4d5
--- /dev/null
+++ b/framework/tests/value/lowercase.astub
@@ -0,0 +1,10 @@
+// That the Value Checker can parse this stub file is a test case for
+// https://github.com/typetools/checker-framework/issues/2830
+
+import org.checkerframework.common.value.qual.IntVal;
+
+package java.lang;
+
+class Long {
+     static @IntVal(-9223372036854775808l) Long MIN_VALUE;
+}
diff --git a/framework/tests/value/minints-stub.astub b/framework/tests/value/minints-stub.astub
new file mode 100644
index 0000000..f822c74
--- /dev/null
+++ b/framework/tests/value/minints-stub.astub
@@ -0,0 +1,26 @@
+// That the Value Checker can parse this stub file is a test case for
+// https://github.com/typetools/checker-framework/issues/2830
+
+import org.checkerframework.common.value.qual.IntVal;
+
+package java.lang;
+
+class Long {
+     static @IntVal(-9223372036854775808L) Long MIN_VALUE;
+}
+
+class Integer {
+    static @IntVal(-2147483648) Integer MIN_VALUE;
+}
+
+class Short {
+    static @IntVal(-32768) Short MIN_VALUE;
+}
+
+class Byte {
+    static @IntVal(-128) Byte MIN_VALUE;
+}
+
+class Character {
+    static @IntVal(0) Character MIN_VALUE;
+}
diff --git a/framework/tests/variablenamedefault/TestVariableNameDefault.java b/framework/tests/variablenamedefault/TestVariableNameDefault.java
new file mode 100644
index 0000000..634d388
--- /dev/null
+++ b/framework/tests/variablenamedefault/TestVariableNameDefault.java
@@ -0,0 +1,205 @@
+import org.checkerframework.framework.testchecker.variablenamedefault.quals.*;
+
+public class TestVariableNameDefault {
+
+  int top;
+
+  int middle;
+  int middlevar;
+  int mymiddle;
+  int notmiddle;
+  int notmiddlevar;
+  @VariableNameDefaultMiddle int namedbottombutnot;
+
+  int bottom;
+  int bottomvar;
+  int mybottom;
+  int notbottom;
+  int notbottomvar;
+  @VariableNameDefaultBottom int namedmiddlebutnot;
+
+  void testFields() {
+
+    @VariableNameDefaultTop int t;
+
+    t = top;
+
+    t = middle;
+    t = middlevar;
+    t = mymiddle;
+    t = notmiddle;
+    t = notmiddlevar;
+    t = namedbottombutnot;
+
+    t = bottom;
+    t = bottomvar;
+    t = mybottom;
+    t = notbottom;
+    t = notbottomvar;
+    t = namedmiddlebutnot;
+
+    @VariableNameDefaultMiddle int m;
+
+    // :: error: (assignment)
+    m = top;
+
+    m = middle;
+    m = middlevar;
+    m = mymiddle;
+    // :: error: (assignment)
+    m = notmiddle;
+    // :: error: (assignment)
+    m = notmiddlevar;
+    m = namedbottombutnot;
+
+    m = bottom;
+    m = bottomvar;
+    m = mybottom;
+    // :: error: (assignment)
+    m = notbottom;
+    // :: error: (assignment)
+    m = notbottomvar;
+    m = namedmiddlebutnot;
+
+    @VariableNameDefaultBottom int b;
+
+    // :: error: (assignment)
+    b = top;
+
+    // :: error: (assignment)
+    b = middle;
+    // :: error: (assignment)
+    b = middlevar;
+    // :: error: (assignment)
+    b = mymiddle;
+    // :: error: (assignment)
+    b = notmiddle;
+    // :: error: (assignment)
+    b = notmiddlevar;
+    // :: error: (assignment)
+    b = namedbottombutnot;
+
+    b = bottom;
+    b = bottomvar;
+    b = mybottom;
+    // :: error: (assignment)
+    b = notbottom;
+    // :: error: (assignment)
+    b = notbottomvar;
+    b = namedmiddlebutnot;
+  }
+
+  void testFormals(int middle, int notmiddle, int bottom, int notbottom) {
+
+    @VariableNameDefaultTop int t;
+
+    t = middle;
+    t = notmiddle;
+    t = bottom;
+    t = notbottom;
+
+    @VariableNameDefaultMiddle int m;
+
+    m = middle;
+    // :: error: (assignment)
+    m = notmiddle;
+    m = bottom;
+    // :: error: (assignment)
+    m = notbottom;
+
+    @VariableNameDefaultBottom int b;
+
+    // :: error: (assignment)
+    b = middle;
+    // :: error: (assignment)
+    b = notmiddle;
+    b = bottom;
+    // :: error: (assignment)
+    b = notbottom;
+  }
+
+  int middlemethod() {
+    // :: error: (return)
+    return 0;
+  }
+
+  int mymiddlemethod() {
+    // :: error: (return)
+    return 0;
+  }
+
+  int notmiddlemethod() {
+    return 0;
+  }
+
+  int mynotmiddlemethod() {
+    return 0;
+  }
+
+  int bottommethod() {
+    // :: error: (return)
+    return 0;
+  }
+
+  int mybottommethod() {
+    // :: error: (return)
+    return 0;
+  }
+
+  int notbottommethod() {
+    return 0;
+  }
+
+  int mynotbottommethod() {
+    return 0;
+  }
+
+  void testMethods() {
+
+    @VariableNameDefaultTop int t;
+
+    t = middlemethod();
+    t = mymiddlemethod();
+    t = notmiddlemethod();
+    t = mynotmiddlemethod();
+
+    t = bottommethod();
+    t = mybottommethod();
+    t = notbottommethod();
+    t = mynotbottommethod();
+
+    @VariableNameDefaultMiddle int m;
+
+    m = middlemethod();
+    m = mymiddlemethod();
+    // :: error: (assignment)
+    m = notmiddlemethod();
+    // :: error: (assignment)
+    m = mynotmiddlemethod();
+
+    m = bottommethod();
+    m = mybottommethod();
+    // :: error: (assignment)
+    m = notbottommethod();
+    // :: error: (assignment)
+    m = mynotbottommethod();
+
+    @VariableNameDefaultBottom int b;
+
+    // :: error: (assignment)
+    b = middlemethod();
+    // :: error: (assignment)
+    b = mymiddlemethod();
+    // :: error: (assignment)
+    b = notmiddlemethod();
+    // :: error: (assignment)
+    b = mynotmiddlemethod();
+
+    b = bottommethod();
+    b = mybottommethod();
+    // :: error: (assignment)
+    b = notbottommethod();
+    // :: error: (assignment)
+    b = mynotbottommethod();
+  }
+}
diff --git a/gradle-mvn-push.gradle b/gradle-mvn-push.gradle
new file mode 100644
index 0000000..8d4616b
--- /dev/null
+++ b/gradle-mvn-push.gradle
@@ -0,0 +1,40 @@
+apply plugin: 'maven-publish'
+apply plugin: 'signing'
+
+final isSnapshot = version.contains('SNAPSHOT')
+// https://github.com/johnrengelman/shadow/issues/586#issuecomment-708375599
+components.java.withVariantsFromConfiguration(configurations.shadowRuntimeElements) {
+    skip()
+}
+publishing {
+    repositories {
+        maven {
+            url = (isSnapshot
+                    ? project.properties.getOrDefault('SNAPSHOT_REPOSITORY_URL', 'https://oss.sonatype.org/content/repositories/snapshots/')
+                    : project.properties.getOrDefault('RELEASE_REPOSITORY_URL', 'https://oss.sonatype.org/service/local/staging/deploy/maven2/')
+            )
+            credentials {
+                username = project.properties.get('SONATYPE_NEXUS_USERNAME')
+                password = project.properties.get('SONATYPE_NEXUS_PASSWORD')
+            }
+        }
+    }
+}
+
+signing {
+
+    // Use external gpg cmd.  This makes it easy to use gpg-agent,
+    // to avoid being prompted for a password once per artifact.
+    useGpgCmd()
+
+    // If anything about signing is misconfigured, fail loudly rather than quietly continuing with
+    // unsigned artifacts.
+    required = true
+}
+
+// Only sign releases; snapshots are unsigned.
+tasks.withType(Sign).configureEach {
+    onlyIf {
+        release
+    }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..31279fb
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,2 @@
+org.gradle.parallel=true
+org.gradle.jvmargs=-Xmx2g
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f3d88b1
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..33682bb
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..2fe81a7
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,183 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..24467a1
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,100 @@
+@rem

+@rem Copyright 2015 the original author or authors.

+@rem

+@rem Licensed under the Apache License, Version 2.0 (the "License");

+@rem you may not use this file except in compliance with the License.

+@rem You may obtain a copy of the License at

+@rem

+@rem      https://www.apache.org/licenses/LICENSE-2.0

+@rem

+@rem Unless required by applicable law or agreed to in writing, software

+@rem distributed under the License is distributed on an "AS IS" BASIS,

+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+@rem See the License for the specific language governing permissions and

+@rem limitations under the License.

+@rem

+

+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/javacutil/build.gradle b/javacutil/build.gradle
new file mode 100644
index 0000000..8b925d0
--- /dev/null
+++ b/javacutil/build.gradle
@@ -0,0 +1,50 @@
+plugins {
+    id 'java-library'
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation project(':checker-qual')
+
+    // This is used by org.checkerframework.javacutil.TypesUtils.isImmutableTypeInJdk.
+    // https://mvnrepository.com/artifact/org.plumelib/plume-util
+    implementation 'org.plumelib:plume-util:1.5.3'
+
+    // External dependencies:
+    // If you add an external dependency, you must shadow its packages both in checker.jar and
+    // and dataflow-shaded.jar.
+    // See the comment in ../build.gradle in the shadowJar block and ../dataflow/build.gradle in
+    // shadowJar block.
+}
+
+apply from: rootProject.file("gradle-mvn-push.gradle")
+
+final javacUtilPom(publication) {
+    sharedPublicationConfiguration(publication)
+    publication.from components.java
+
+    publication.pom {
+        name = 'Javacutil'
+        description = 'javacutil contains utility classes for the javac compiler.'
+        licenses {
+            license {
+                name = 'GNU General Public License, version 2 (GPL2), with the classpath exception'
+                url = 'http://www.gnu.org/software/classpath/license.html'
+                distribution = 'repo'
+            }
+        }
+    }
+}
+publishing {
+    publications {
+        javacUtil(MavenPublication) {
+            javacUtilPom it
+        }
+    }
+}
+signing {
+    sign publishing.publications.javacUtil
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AbstractTypeProcessor.java b/javacutil/src/main/java/org/checkerframework/javacutil/AbstractTypeProcessor.java
new file mode 100644
index 0000000..cf611cf
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/AbstractTypeProcessor.java
@@ -0,0 +1,199 @@
+package org.checkerframework.javacutil;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TaskEvent;
+import com.sun.source.util.TaskListener;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.comp.CompileStates.CompileState;
+import com.sun.tools.javac.main.JavaCompiler;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Log;
+import java.util.HashSet;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementFilter;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+
+/**
+ * This class is an abstract annotation processor designed to be a convenient superclass for
+ * concrete "type processors", processors that require the type information in the processed source.
+ *
+ * <p>Type processing occurs in one round after the tool (e.g. Java compiler) analyzes the source
+ * (all sources taken as input to the tool and sources generated by other annotation processors).
+ *
+ * <p>The tool infrastructure will interact with classes extending this abstract class as follows.
+ *
+ * <p>1-3 are identical to the {@link Processor} life cycle. 4-5 are unique to {@code
+ * AbstractTypeProcessor} subclasses.
+ *
+ * <ol>
+ *   <li>If an existing {@code Processor} object is not being used, to create an instance of a
+ *       processor the tool calls the no-arg constructor of the processor class.
+ *   <li>Next, the tool calls the {@link #init init} method with an appropriate {@code
+ *       ProcessingEnvironment}.
+ *   <li>Afterwards, the tool calls {@link #getSupportedAnnotationTypes
+ *       getSupportedAnnotationTypes}, {@link #getSupportedOptions getSupportedOptions}, and {@link
+ *       #getSupportedSourceVersion getSupportedSourceVersion}. These methods are only called once
+ *       per run, not on each round.
+ *   <li>For each class containing a supported annotation, the tool calls {@link
+ *       #typeProcess(TypeElement, TreePath) typeProcess} method on the {@code Processor}. The class
+ *       is guaranteed to be type-checked Java code and all the tree type and symbol information is
+ *       resolved.
+ *   <li>Finally, the tool calls the {@link #typeProcessingOver typeProcessingOver} method on the
+ *       {@code Processor}.
+ * </ol>
+ *
+ * <p>The tool is permitted to ask type processors to process a class once it is analyzed before the
+ * rest of classes are analyzed. The tool is also permitted to stop type processing immediately if
+ * any errors are raised, without invoking {@link #typeProcessingOver}.
+ *
+ * <p>A subclass may override any of the methods in this class, as long as the general {@link
+ * javax.annotation.processing.Processor Processor} contract is obeyed, with one notable exception.
+ * {@link #process(Set, RoundEnvironment)} may not be overridden, as it is called during the
+ * declaration annotation phase before classes are analyzed.
+ */
+public abstract class AbstractTypeProcessor extends AbstractProcessor {
+  /**
+   * The set of fully-qualified element names that should be type-checked. We store the names of the
+   * elements, in order to prevent possible confusion between different Element instantiations.
+   */
+  private final Set<Name> elements = new HashSet<>();
+
+  /**
+   * Method {@link #typeProcessingStart()} must be invoked exactly once, before any invocation of
+   * {@link #typeProcess(TypeElement, TreePath)}.
+   */
+  private boolean hasInvokedTypeProcessingStart = false;
+
+  /**
+   * Method {@link #typeProcessingOver} must be invoked exactly once, after the last invocation of
+   * {@link #typeProcess(TypeElement, TreePath)}.
+   */
+  private boolean hasInvokedTypeProcessingOver = false;
+
+  /** The TaskListener registered for completion of attribution. */
+  private final AttributionTaskListener listener = new AttributionTaskListener();
+
+  /** Constructor for subclasses to call. */
+  protected AbstractTypeProcessor() {}
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>Register a TaskListener that will get called after FLOW.
+   */
+  @Override
+  public synchronized void init(ProcessingEnvironment env) {
+    super.init(env);
+    JavacTask.instance(env).addTaskListener(listener);
+    Context ctx = ((JavacProcessingEnvironment) processingEnv).getContext();
+    JavaCompiler compiler = JavaCompiler.instance(ctx);
+    compiler.shouldStopPolicyIfNoError =
+        CompileState.max(compiler.shouldStopPolicyIfNoError, CompileState.FLOW);
+    compiler.shouldStopPolicyIfError =
+        CompileState.max(compiler.shouldStopPolicyIfError, CompileState.FLOW);
+  }
+
+  /**
+   * The use of this method is obsolete in type processors. The method is called during declaration
+   * annotation processing phase only. It registers the names of elements to process.
+   */
+  @Override
+  public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    for (TypeElement elem : ElementFilter.typesIn(roundEnv.getRootElements())) {
+      elements.add(elem.getQualifiedName());
+    }
+    return false;
+  }
+
+  /**
+   * A method to be called once before the first call to typeProcess.
+   *
+   * <p>Subclasses may override this method to do any initialization work.
+   */
+  public void typeProcessingStart() {}
+
+  /**
+   * Processes a fully-analyzed class that contains a supported annotation (see {@link
+   * #getSupportedAnnotationTypes()}).
+   *
+   * <p>The passed class is always valid type-checked Java code.
+   *
+   * @param element element of the analyzed class
+   * @param tree the tree path to the element, with the leaf being a {@link ClassTree}
+   */
+  public abstract void typeProcess(TypeElement element, TreePath tree);
+
+  /**
+   * A method to be called once all the classes are processed.
+   *
+   * <p>Subclasses may override this method to do any aggregate analysis (e.g. generate report,
+   * persistence) or resource deallocation.
+   *
+   * <p>Method {@link #getCompilerLog()} can be used to access the number of compiler errors.
+   */
+  public void typeProcessingOver() {}
+
+  /**
+   * Return the compiler log, which contains errors and warnings.
+   *
+   * @return the compiler log, which contains errors and warnings
+   */
+  @SideEffectFree
+  public Log getCompilerLog() {
+    return Log.instance(((JavacProcessingEnvironment) processingEnv).getContext());
+  }
+
+  /** A task listener that invokes the processor whenever a class is fully analyzed. */
+  private final class AttributionTaskListener implements TaskListener {
+
+    @Override
+    public void finished(TaskEvent e) {
+      if (e.getKind() != TaskEvent.Kind.ANALYZE) {
+        return;
+      }
+
+      if (!hasInvokedTypeProcessingStart) {
+        typeProcessingStart();
+        hasInvokedTypeProcessingStart = true;
+      }
+
+      if (!hasInvokedTypeProcessingOver && elements.isEmpty()) {
+        typeProcessingOver();
+        hasInvokedTypeProcessingOver = true;
+      }
+
+      if (e.getTypeElement() == null) {
+        throw new BugInCF("event task without a type element");
+      }
+      if (e.getCompilationUnit() == null) {
+        throw new BugInCF("event task without compilation unit");
+      }
+
+      if (!elements.remove(e.getTypeElement().getQualifiedName())) {
+        return;
+      }
+
+      TypeElement elem = e.getTypeElement();
+      TreePath p = Trees.instance(processingEnv).getPath(elem);
+
+      typeProcess(elem, p);
+
+      if (!hasInvokedTypeProcessingOver && elements.isEmpty()) {
+        typeProcessingOver();
+        hasInvokedTypeProcessingOver = true;
+      }
+    }
+
+    @Override
+    public void started(TaskEvent e) {}
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java
new file mode 100644
index 0000000..03421f5
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java
@@ -0,0 +1,827 @@
+package org.checkerframework.javacutil;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.AnnotationValueVisitor;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.interning.qual.Interned;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.plumelib.util.StringsPlume;
+
+/**
+ * Builds an annotation mirror that may have some values.
+ *
+ * <p>Constructing an {@link AnnotationMirror} requires:
+ *
+ * <ol>
+ *   <li>Constructing the builder with the desired annotation class
+ *   <li>Setting each value individually using {@code setValue} methods
+ *   <li>Calling {@link #build()} to get the annotation
+ * </ol>
+ *
+ * Once an annotation is built, no further modification or calls to build can be made. Otherwise, a
+ * {@link IllegalStateException} is thrown.
+ *
+ * <p>All setter methods throw {@link IllegalArgumentException} if the specified element is not
+ * found, or if the given value is not a subtype of the expected type.
+ *
+ * <p>TODO: Doesn't type-check arrays yet
+ */
+public class AnnotationBuilder {
+
+  /** The element utilities to use. */
+  private final Elements elements;
+  /** The type utilities to use. */
+  private final Types types;
+
+  /** The type element of the annotation. */
+  private final TypeElement annotationElt;
+  /** The type of the annotation. */
+  private final DeclaredType annotationType;
+  /** A mapping from element to AnnotationValue. */
+  private final Map<ExecutableElement, AnnotationValue> elementValues;
+
+  /**
+   * Create a new AnnotationBuilder for the given annotation and environment (with no
+   * elements/fields, but they can be added later).
+   *
+   * @param env the processing environment
+   * @param anno the class of the annotation to build
+   */
+  @SuppressWarnings("nullness") // getCanonicalName expected to be non-null
+  public AnnotationBuilder(ProcessingEnvironment env, Class<? extends Annotation> anno) {
+    this(env, anno.getCanonicalName());
+  }
+
+  /**
+   * Create a new AnnotationBuilder for the given annotation name (with no elements/fields, but they
+   * can be added later).
+   *
+   * @param env the processing environment
+   * @param name the canonical name of the annotation to build
+   */
+  public AnnotationBuilder(ProcessingEnvironment env, @FullyQualifiedName CharSequence name) {
+    this.elements = env.getElementUtils();
+    this.types = env.getTypeUtils();
+    this.annotationElt = elements.getTypeElement(name);
+    if (annotationElt == null) {
+      throw new UserError("Could not find annotation: " + name + ". Is it on the classpath?");
+    }
+    assert annotationElt.getKind() == ElementKind.ANNOTATION_TYPE;
+    this.annotationType = (DeclaredType) annotationElt.asType();
+    this.elementValues = new LinkedHashMap<>();
+  }
+
+  /**
+   * Create a new AnnotationBuilder that copies the given annotation, including its elements/fields.
+   *
+   * @param env the processing environment
+   * @param annotation the annotation to copy
+   */
+  public AnnotationBuilder(ProcessingEnvironment env, AnnotationMirror annotation) {
+    this.elements = env.getElementUtils();
+    this.types = env.getTypeUtils();
+
+    this.annotationType = annotation.getAnnotationType();
+    this.annotationElt = (TypeElement) annotationType.asElement();
+
+    this.elementValues = new LinkedHashMap<>();
+    // AnnotationValues are immutable so putAll should suffice
+    this.elementValues.putAll(annotation.getElementValues());
+  }
+
+  /**
+   * Returns the type element of the annotation that is being built.
+   *
+   * @return the type element of the annotation that is being built
+   */
+  public TypeElement getAnnotationElt() {
+    return annotationElt;
+  }
+
+  /**
+   * Creates a mapping between element/field names and values.
+   *
+   * @param elementName the name of an element/field to initialize
+   * @param elementValue the initial value for the element/field
+   * @return a mappnig from the element name to the element value
+   */
+  public static Map<String, AnnotationValue> elementNamesValues(
+      String elementName, Object elementValue) {
+    return Collections.singletonMap(elementName, createValue(elementValue));
+  }
+
+  /**
+   * Creates an {@link AnnotationMirror} that uses default values for elements/fields.
+   * getElementValues on the result returns default values. If any element does not have a default,
+   * this method throws an exception.
+   *
+   * <p>Most clients should use {@link #fromName}, using a Name created by the compiler. This method
+   * is provided as a convenience to create an AnnotationMirror from scratch in a checker's code.
+   *
+   * @param elements the element utilities to use
+   * @param aClass the annotation class
+   * @return an {@link AnnotationMirror} of the given type
+   * @throws UserError if the annotation corresponding to the class could not be loaded
+   */
+  public static AnnotationMirror fromClass(Elements elements, Class<? extends Annotation> aClass) {
+    return fromClass(elements, aClass, Collections.emptyMap());
+  }
+
+  /**
+   * Creates an {@link AnnotationMirror} given by a particular annotation class and a name-to-value
+   * mapping for the elements/fields.
+   *
+   * <p>For other elements, getElementValues on the result returns default values. If any such
+   * element does not have a default, this method throws an exception.
+   *
+   * <p>Most clients should use {@link #fromName}, using a Name created by the compiler. This method
+   * is provided as a convenience to create an AnnotationMirror from scratch in a checker's code.
+   *
+   * @param elements the element utilities to use
+   * @param aClass the annotation class
+   * @param elementNamesValues the values for the annotation's elements/fields
+   * @return an {@link AnnotationMirror} of the given type
+   */
+  public static AnnotationMirror fromClass(
+      Elements elements,
+      Class<? extends Annotation> aClass,
+      Map<String, AnnotationValue> elementNamesValues) {
+    String name = aClass.getCanonicalName();
+    assert name != null : "@AssumeAssertion(nullness): assumption";
+    AnnotationMirror res = fromName(elements, name, elementNamesValues);
+    if (res == null) {
+      throw new UserError(
+          "AnnotationBuilder: error: fromClass can't load Class %s%n"
+              + "ensure the class is on the compilation classpath",
+          name);
+    }
+    return res;
+  }
+
+  /**
+   * Creates an {@link AnnotationMirror} given by a particular fully-qualified name.
+   * getElementValues on the result returns default values. If any element does not have a default,
+   * this method throws an exception.
+   *
+   * <p>This method returns null if the annotation corresponding to the name could not be loaded.
+   *
+   * @param elements the element utilities to use
+   * @param name the name of the annotation to create
+   * @return an {@link AnnotationMirror} of type {@code} name or null if the annotation couldn't be
+   *     loaded
+   */
+  public static @Nullable AnnotationMirror fromName(
+      Elements elements, @FullyQualifiedName CharSequence name) {
+    return fromName(elements, name, Collections.emptyMap());
+  }
+
+  /**
+   * Creates an {@link AnnotationMirror} given by a particular fully-qualified name and
+   * element/field values. If any element is not specified by the {@code elementValues} argument,
+   * the default value is used. If any such element does not have a default, this method throws an
+   * exception.
+   *
+   * <p>This method returns null if the annotation corresponding to the name could not be loaded.
+   *
+   * @param elements the element utilities to use
+   * @param name the name of the annotation to create
+   * @param elementNamesValues the values for the annotation's elements/fields
+   * @return an {@link AnnotationMirror} of type {@code} name or null if the annotation couldn't be
+   *     loaded
+   */
+  public static @Nullable AnnotationMirror fromName(
+      Elements elements,
+      @FullyQualifiedName CharSequence name,
+      Map<String, AnnotationValue> elementNamesValues) {
+    final TypeElement annoElt = elements.getTypeElement(name);
+    if (annoElt == null) {
+      return null;
+    }
+    if (annoElt.getKind() != ElementKind.ANNOTATION_TYPE) {
+      throw new BugInCF(annoElt + " is not an annotation");
+    }
+
+    final DeclaredType annoType = (DeclaredType) annoElt.asType();
+    if (annoType == null) {
+      return null;
+    }
+
+    List<ExecutableElement> methods = ElementFilter.methodsIn(annoElt.getEnclosedElements());
+    Map<ExecutableElement, AnnotationValue> elementValues = new LinkedHashMap<>(methods.size());
+    for (ExecutableElement annoElement : methods) {
+      AnnotationValue elementValue = elementNamesValues.get(annoElement.getSimpleName().toString());
+      if (elementValue == null) {
+        AnnotationValue defaultValue = annoElement.getDefaultValue();
+        if (defaultValue == null) {
+          throw new BugInCF(
+              "AnnotationBuilder.fromName: no value for element %s of %s", annoElement, name);
+        } else {
+          elementValue = defaultValue;
+        }
+      }
+      elementValues.put(annoElement, elementValue);
+    }
+
+    AnnotationMirror result = new CheckerFrameworkAnnotationMirror(annoType, elementValues);
+    return result;
+  }
+
+  /** Whether or not {@link #build()} has been called. */
+  private boolean wasBuilt = false;
+
+  private void assertNotBuilt() {
+    if (wasBuilt) {
+      throw new BugInCF("AnnotationBuilder: error: type was already built");
+    }
+  }
+
+  public AnnotationMirror build() {
+    assertNotBuilt();
+    wasBuilt = true;
+    return new CheckerFrameworkAnnotationMirror(annotationType, elementValues);
+  }
+
+  /**
+   * Copies every element value from the given annotation. If an element in the given annotation
+   * doesn't exist in the annotation to be built, an error is raised unless the element is specified
+   * in {@code ignorableElements}.
+   *
+   * @param other the annotation that holds the values to be copied; need not be an annotation of
+   *     the same type of the one being built
+   * @param ignorableElements the names of elements of {@code other} that can be safely dropped
+   */
+  public void copyElementValuesFromAnnotation(AnnotationMirror other, String... ignorableElements) {
+    List<String> ignorableElementsList = Arrays.asList(ignorableElements);
+    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> eltValToCopy :
+        other.getElementValues().entrySet()) {
+      Name eltNameToCopy = eltValToCopy.getKey().getSimpleName();
+      if (ignorableElementsList.contains(eltNameToCopy.toString())) {
+        continue;
+      }
+      elementValues.put(findElement(eltNameToCopy), eltValToCopy.getValue());
+    }
+  }
+
+  /**
+   * Copies every element value from the given annotation. If an element in the given annotation
+   * doesn't exist in the annotation to be built, an error is raised unless the element is specified
+   * in {@code ignorableElements}.
+   *
+   * @param valueHolder the annotation that holds the values to be copied; must be the same type as
+   *     the annotation being built
+   * @param ignorableElements the elements that can be safely dropped
+   */
+  public void copyElementValuesFromAnnotation(
+      AnnotationMirror valueHolder, Collection<ExecutableElement> ignorableElements) {
+    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
+        valueHolder.getElementValues().entrySet()) {
+      if (ignorableElements.contains(entry.getKey())) {
+        continue;
+      }
+      elementValues.put(entry.getKey(), entry.getValue());
+    }
+  }
+
+  /**
+   * Copies the specified element values from the given annotation, using the specified renaming
+   * map. Each value in the map must be an element name in the annotation being built. If an element
+   * from the given annotation is not a key in the map, it is ignored.
+   *
+   * @param valueHolder the annotation that holds the values to be copied
+   * @param elementNameRenaming a map from element names in {@code valueHolder} to element names of
+   *     the annotation being built
+   */
+  public void copyRenameElementValuesFromAnnotation(
+      AnnotationMirror valueHolder, Map<String, String> elementNameRenaming) {
+
+    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> eltValToCopy :
+        valueHolder.getElementValues().entrySet()) {
+
+      String sourceName = eltValToCopy.getKey().getSimpleName().toString();
+      String targetName = elementNameRenaming.get(sourceName);
+      if (targetName == null) {
+        continue;
+      }
+      elementValues.put(findElement(targetName), eltValToCopy.getValue());
+    }
+  }
+
+  /** Set the element/field with the given name, to the given value. */
+  public AnnotationBuilder setValue(CharSequence elementName, AnnotationMirror value) {
+    setValue(elementName, (Object) value);
+    return this;
+  }
+
+  /**
+   * Set the element/field with the given name, to the given value.
+   *
+   * @param elementName the element/field name
+   * @param values the new value for the element/field
+   * @return this
+   */
+  public AnnotationBuilder setValue(CharSequence elementName, List<? extends Object> values) {
+    assertNotBuilt();
+    ExecutableElement var = findElement(elementName);
+    return setValue(var, values);
+  }
+
+  /**
+   * Set the element to the given value.
+   *
+   * @param element the element
+   * @param values the new value for the element
+   * @return this
+   */
+  public AnnotationBuilder setValue(
+      ExecutableElement element, List<? extends @NonNull Object> values) {
+    assertNotBuilt();
+    TypeMirror expectedType = element.getReturnType();
+    if (expectedType.getKind() != TypeKind.ARRAY) {
+      throw new BugInCF("value is an array while expected type is not");
+    }
+    expectedType = ((ArrayType) expectedType).getComponentType();
+
+    List<AnnotationValue> avalues = new ArrayList<>(values.size());
+    for (Object v : values) {
+      checkSubtype(expectedType, v);
+      avalues.add(createValue(v));
+    }
+    AnnotationValue aval = createValue(avalues);
+    elementValues.put(element, aval);
+    return this;
+  }
+
+  /** Set the element/field with the given name, to the given value. */
+  public AnnotationBuilder setValue(CharSequence elementName, Object[] values) {
+    return setValue(elementName, Arrays.asList(values));
+  }
+
+  /** Set the element/field with the given name, to the given value. */
+  public AnnotationBuilder setValue(CharSequence elementName, Boolean value) {
+    return setValue(elementName, (Object) value);
+  }
+
+  /** Set the element/field with the given name, to the given value. */
+  public AnnotationBuilder setValue(CharSequence elementName, Character value) {
+    return setValue(elementName, (Object) value);
+  }
+
+  /** Set the element/field with the given name, to the given value. */
+  public AnnotationBuilder setValue(CharSequence elementName, Double value) {
+    return setValue(elementName, (Object) value);
+  }
+
+  /** Set the element/field with the given name, to the given value. */
+  public AnnotationBuilder setValue(CharSequence elementName, Float value) {
+    return setValue(elementName, (Object) value);
+  }
+
+  /** Set the element/field with the given name, to the given value. */
+  public AnnotationBuilder setValue(CharSequence elementName, Integer value) {
+    return setValue(elementName, (Object) value);
+  }
+
+  /** Set the element/field with the given name, to the given value. */
+  public AnnotationBuilder setValue(CharSequence elementName, Long value) {
+    return setValue(elementName, (Object) value);
+  }
+
+  /** Set the element/field with the given name, to the given value. */
+  public AnnotationBuilder setValue(CharSequence elementName, Short value) {
+    return setValue(elementName, (Object) value);
+  }
+
+  /** Set the element/field with the given name, to the given value. */
+  public AnnotationBuilder setValue(CharSequence elementName, String value) {
+    return setValue(elementName, (Object) value);
+  }
+
+  /**
+   * Remove the element/field with the given name. Does not err if no such element/field is present.
+   */
+  public AnnotationBuilder removeElement(CharSequence elementName) {
+    assertNotBuilt();
+    ExecutableElement var = findElement(elementName);
+    elementValues.remove(var);
+    return this;
+  }
+
+  private TypeMirror getErasedOrBoxedType(TypeMirror type) {
+    // See com.sun.tools.javac.code.Attribute.Class.makeClassType()
+    return type.getKind().isPrimitive()
+        ? types.boxedClass((PrimitiveType) type).asType()
+        : types.erasure(type);
+  }
+
+  public AnnotationBuilder setValue(CharSequence elementName, TypeMirror value) {
+    assertNotBuilt();
+    value = getErasedOrBoxedType(value);
+    AnnotationValue val = createValue(value);
+    ExecutableElement var = findElement(elementName);
+    // Check subtyping
+    if (!TypesUtils.isClass(var.getReturnType())) {
+      throw new BugInCF("expected " + var.getReturnType());
+    }
+
+    elementValues.put(var, val);
+    return this;
+  }
+
+  /**
+   * Given a class, return the corresponding TypeMirror.
+   *
+   * @param clazz a class
+   * @return the TypeMirror corresponding to the given class
+   */
+  private TypeMirror typeFromClass(Class<?> clazz) {
+    return TypesUtils.typeFromClass(clazz, types, elements);
+  }
+
+  public AnnotationBuilder setValue(CharSequence elementName, Class<?> value) {
+    TypeMirror type = typeFromClass(value);
+    return setValue(elementName, getErasedOrBoxedType(type));
+  }
+
+  public AnnotationBuilder setValue(CharSequence elementName, Enum<?> value) {
+    assertNotBuilt();
+    VariableElement enumElt = findEnumElement(value);
+    return setValue(elementName, enumElt);
+  }
+
+  public AnnotationBuilder setValue(CharSequence elementName, VariableElement value) {
+    ExecutableElement var = findElement(elementName);
+    if (var.getReturnType().getKind() != TypeKind.DECLARED) {
+      throw new BugInCF("expected a non enum: " + var.getReturnType());
+    }
+    if (!((DeclaredType) var.getReturnType()).asElement().equals(value.getEnclosingElement())) {
+      throw new BugInCF("expected a different type of enum: " + value.getEnclosingElement());
+    }
+    elementValues.put(var, createValue(value));
+    return this;
+  }
+
+  // Keep this version synchronized with the VariableElement[] version below
+  public AnnotationBuilder setValue(CharSequence elementName, Enum<?>[] values) {
+    assertNotBuilt();
+
+    if (values.length == 0) {
+      setValue(elementName, Collections.emptyList());
+      return this;
+    }
+
+    VariableElement enumElt = findEnumElement(values[0]);
+    ExecutableElement var = findElement(elementName);
+
+    TypeMirror expectedType = var.getReturnType();
+    if (expectedType.getKind() != TypeKind.ARRAY) {
+      throw new BugInCF("expected a non array: " + var.getReturnType());
+    }
+
+    expectedType = ((ArrayType) expectedType).getComponentType();
+    if (expectedType.getKind() != TypeKind.DECLARED) {
+      throw new BugInCF("expected a non enum component type: " + var.getReturnType());
+    }
+    if (!((DeclaredType) expectedType).asElement().equals(enumElt.getEnclosingElement())) {
+      throw new BugInCF("expected a different type of enum: " + enumElt.getEnclosingElement());
+    }
+
+    List<AnnotationValue> res = new ArrayList<>(values.length);
+    for (Enum<?> ev : values) {
+      checkSubtype(expectedType, ev);
+      enumElt = findEnumElement(ev);
+      res.add(createValue(enumElt));
+    }
+    AnnotationValue val = createValue(res);
+    elementValues.put(var, val);
+    return this;
+  }
+
+  // Keep this version synchronized with the Enum<?>[] version above.
+  // Which one is more useful/general? Unifying adds overhead of creating
+  // another array.
+  public AnnotationBuilder setValue(CharSequence elementName, VariableElement[] values) {
+    assertNotBuilt();
+    ExecutableElement var = findElement(elementName);
+
+    TypeMirror expectedType = var.getReturnType();
+    if (expectedType.getKind() != TypeKind.ARRAY) {
+      throw new BugInCF("expected an array, but found: " + expectedType);
+    }
+
+    expectedType = ((ArrayType) expectedType).getComponentType();
+    if (expectedType.getKind() != TypeKind.DECLARED) {
+      throw new BugInCF(
+          "expected a declared component type, but found: "
+              + expectedType
+              + " kind: "
+              + expectedType.getKind());
+    }
+    if (!types.isSameType((DeclaredType) expectedType, values[0].asType())) {
+      throw new BugInCF(
+          "expected a different declared component type: " + expectedType + " vs. " + values[0]);
+    }
+
+    List<AnnotationValue> res = new ArrayList<>(values.length);
+    for (VariableElement ev : values) {
+      checkSubtype(expectedType, ev);
+      // Is there a better way to distinguish between enums and
+      // references to constants?
+      if (ev.getConstantValue() != null) {
+        res.add(createValue(ev.getConstantValue()));
+      } else {
+        res.add(createValue(ev));
+      }
+    }
+    AnnotationValue val = createValue(res);
+    elementValues.put(var, val);
+    return this;
+  }
+
+  /** Find the VariableElement for the given enum. */
+  private VariableElement findEnumElement(Enum<?> value) {
+    String enumClass = value.getDeclaringClass().getCanonicalName();
+    assert enumClass != null : "@AssumeAssertion(nullness): assumption";
+    TypeElement enumClassElt = elements.getTypeElement(enumClass);
+    assert enumClassElt != null;
+    for (Element enumElt : enumClassElt.getEnclosedElements()) {
+      if (enumElt.getSimpleName().contentEquals(value.name())) {
+        return (VariableElement) enumElt;
+      }
+    }
+    throw new BugInCF("cannot be here");
+  }
+
+  private AnnotationBuilder setValue(CharSequence key, Object value) {
+    assertNotBuilt();
+    AnnotationValue val = createValue(value);
+    ExecutableElement var = findElement(key);
+    checkSubtype(var.getReturnType(), value);
+    elementValues.put(var, val);
+    return this;
+  }
+
+  public ExecutableElement findElement(CharSequence key) {
+    for (ExecutableElement elt : ElementFilter.methodsIn(annotationElt.getEnclosedElements())) {
+      if (elt.getSimpleName().contentEquals(key)) {
+        return elt;
+      }
+    }
+    throw new BugInCF("Couldn't find " + key + " element in " + annotationElt);
+  }
+
+  /** @throws BugInCF if the type of {@code givenValue} is not the same as {@code expected} */
+  private void checkSubtype(TypeMirror expected, Object givenValue) {
+    if (expected.getKind().isPrimitive()) {
+      expected = types.boxedClass((PrimitiveType) expected).asType();
+    }
+
+    if (expected.getKind() == TypeKind.DECLARED
+        && TypesUtils.isClass(expected)
+        && givenValue instanceof TypeMirror) {
+      return;
+    }
+
+    TypeMirror found;
+    boolean isSubtype;
+
+    if (expected.getKind() == TypeKind.DECLARED
+        && ((DeclaredType) expected).asElement().getKind() == ElementKind.ANNOTATION_TYPE
+        && givenValue instanceof AnnotationMirror) {
+      found = ((AnnotationMirror) givenValue).getAnnotationType();
+      isSubtype = ((DeclaredType) expected).asElement().equals(((DeclaredType) found).asElement());
+    } else if (givenValue instanceof AnnotationMirror) {
+      found = ((AnnotationMirror) givenValue).getAnnotationType();
+      // TODO: why is this always failing???
+      isSubtype = false;
+    } else if (givenValue instanceof VariableElement) {
+      found = ((VariableElement) givenValue).asType();
+      if (expected.getKind() == TypeKind.DECLARED) {
+        isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected));
+      } else {
+        isSubtype = false;
+      }
+    } else {
+      String name = givenValue.getClass().getCanonicalName();
+      assert name != null : "@AssumeAssertion(nullness): assumption";
+      found = elements.getTypeElement(name).asType();
+      isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected));
+    }
+    if (!isSubtype) {
+      // Annotations in stub files sometimes are the same type, but Types#isSubtype fails anyways.
+      isSubtype = found.toString().equals(expected.toString());
+    }
+
+    if (!isSubtype) {
+      throw new BugInCF(
+          "given value differs from expected; " + "found: " + found + "; expected: " + expected);
+    }
+  }
+
+  /**
+   * Create an AnnotationValue -- a value for an annotation element/field.
+   *
+   * @param obj the value to be stored in an annotation element/field
+   * @return an AnnotationValue for the given Java value
+   */
+  private static AnnotationValue createValue(final Object obj) {
+    return new CheckerFrameworkAnnotationValue(obj);
+  }
+
+  /** Implementation of AnnotationMirror used by the Checker Framework. */
+  /* default visibility to allow access from within package. */
+  static class CheckerFrameworkAnnotationMirror implements AnnotationMirror {
+    /** The interned toString value. */
+    private @Nullable @Interned String toStringVal;
+    /** The annotation type. */
+    private final DeclaredType annotationType;
+    /** The element values. */
+    private final Map<ExecutableElement, AnnotationValue> elementValues;
+    /** The annotation name. */
+    // default visibility to allow access from within package.
+    final @Interned @CanonicalName String annotationName;
+
+    /**
+     * Create a CheckerFrameworkAnnotationMirror.
+     *
+     * @param annotationType the annotation type
+     * @param elementValues the element values
+     */
+    @SuppressWarnings("signature:assignment") // needs JDK annotations
+    CheckerFrameworkAnnotationMirror(
+        DeclaredType annotationType, Map<ExecutableElement, AnnotationValue> elementValues) {
+      this.annotationType = annotationType;
+      final TypeElement elm = (TypeElement) annotationType.asElement();
+      this.annotationName = elm.getQualifiedName().toString().intern();
+      this.elementValues = elementValues;
+    }
+
+    @Override
+    public DeclaredType getAnnotationType() {
+      return annotationType;
+    }
+
+    @Override
+    public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() {
+      return Collections.unmodifiableMap(elementValues);
+    }
+
+    @SideEffectFree
+    @Override
+    public String toString() {
+      if (toStringVal != null) {
+        return toStringVal;
+      }
+      StringBuilder buf = new StringBuilder();
+      buf.append("@");
+      buf.append(annotationName);
+      int len = elementValues.size();
+      if (len > 0) {
+        buf.append('(');
+        boolean first = true;
+        for (Map.Entry<ExecutableElement, AnnotationValue> pair : elementValues.entrySet()) {
+          if (!first) {
+            buf.append(", ");
+          }
+          first = false;
+
+          String name = pair.getKey().getSimpleName().toString();
+          if (len > 1 || !name.equals("value")) {
+            buf.append(name);
+            buf.append('=');
+          }
+          buf.append(pair.getValue());
+        }
+        buf.append(')');
+      }
+      toStringVal = buf.toString().intern();
+      return toStringVal;
+
+      // return "@" + annotationType + "(" + elementValues + ")";
+    }
+  }
+
+  /** Implementation of AnnotationValue used by the Checker Framework. */
+  private static class CheckerFrameworkAnnotationValue implements AnnotationValue {
+    /** The value. */
+    private final Object value;
+    /** The interned value of toString. */
+    private @Nullable @Interned String toStringVal;
+
+    /** Create an annotation value. */
+    CheckerFrameworkAnnotationValue(Object obj) {
+      this.value = obj;
+    }
+
+    @Override
+    public Object getValue() {
+      return value;
+    }
+
+    @SideEffectFree
+    @Override
+    public String toString() {
+      if (this.toStringVal != null) {
+        return this.toStringVal;
+      }
+      String toStringVal;
+      if (value instanceof String) {
+        toStringVal = "\"" + value + "\"";
+      } else if (value instanceof Character) {
+        toStringVal = "\'" + value + "\'";
+      } else if (value instanceof List<?>) {
+        List<?> list = (List<?>) value;
+        toStringVal = "{" + StringsPlume.join(", ", list) + "}";
+      } else if (value instanceof VariableElement) {
+        // for Enums
+        VariableElement var = (VariableElement) value;
+        String encl = var.getEnclosingElement().toString();
+        if (!encl.isEmpty()) {
+          encl = encl + '.';
+        }
+        toStringVal = encl + var;
+      } else if (value instanceof TypeMirror && TypesUtils.isClassType((TypeMirror) value)) {
+        toStringVal = value.toString() + ".class";
+      } else {
+        toStringVal = value.toString();
+      }
+      this.toStringVal = toStringVal.intern();
+      return this.toStringVal;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) {
+      if (value instanceof AnnotationMirror) {
+        return v.visitAnnotation((AnnotationMirror) value, p);
+      } else if (value instanceof List) {
+        return v.visitArray((List<? extends AnnotationValue>) value, p);
+      } else if (value instanceof Boolean) {
+        return v.visitBoolean((Boolean) value, p);
+      } else if (value instanceof Character) {
+        return v.visitChar((Character) value, p);
+      } else if (value instanceof Double) {
+        return v.visitDouble((Double) value, p);
+      } else if (value instanceof VariableElement) {
+        return v.visitEnumConstant((VariableElement) value, p);
+      } else if (value instanceof Float) {
+        return v.visitFloat((Float) value, p);
+      } else if (value instanceof Integer) {
+        return v.visitInt((Integer) value, p);
+      } else if (value instanceof Long) {
+        return v.visitLong((Long) value, p);
+      } else if (value instanceof Short) {
+        return v.visitShort((Short) value, p);
+      } else if (value instanceof String) {
+        return v.visitString((String) value, p);
+      } else if (value instanceof TypeMirror) {
+        return v.visitType((TypeMirror) value, p);
+      } else {
+        assert false : " unknown type : " + v.getClass();
+        return v.visitUnknown(this, p);
+      }
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+      // System.out.printf("Calling CFAV.equals()%n");
+      if (!(obj instanceof AnnotationValue)) {
+        return false;
+      }
+      AnnotationValue other = (AnnotationValue) obj;
+      return Objects.equals(this.getValue(), other.getValue());
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(this.value);
+    }
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationFormatter.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationFormatter.java
new file mode 100644
index 0000000..b07670f
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationFormatter.java
@@ -0,0 +1,30 @@
+package org.checkerframework.framework.util;
+
+import java.util.Collection;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+
+/** Converts AnnotationMirrors to Strings. Used when converting AnnotatedTypeMirrors to Strings. */
+public interface AnnotationFormatter {
+
+  /**
+   * Converts a collection of annotation mirrors into a String.
+   *
+   * @param annos a collection of annotations to print
+   * @param printInvisible whether or not to print "invisible" annotation mirrors
+   * @see org.checkerframework.framework.qual.InvisibleQualifier
+   * @return a string representation of annos
+   */
+  @SideEffectFree
+  public String formatAnnotationString(
+      Collection<? extends AnnotationMirror> annos, boolean printInvisible);
+
+  /**
+   * Converts an individual annotation mirror into a String.
+   *
+   * @param anno the annotation mirror to convert
+   * @return a String representation of anno
+   */
+  @SideEffectFree
+  public String formatAnnotationMirror(AnnotationMirror anno);
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java
new file mode 100644
index 0000000..3452a5b
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java
@@ -0,0 +1,34 @@
+package org.checkerframework.javacutil;
+
+import com.sun.source.tree.Tree;
+import java.lang.annotation.Annotation;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+// This class exists to break a circular dependency between the dataflow framework and
+// type-checkers.
+/** An implementation of AnnotationProvider returns annotations on Java AST elements. */
+public interface AnnotationProvider {
+
+  /**
+   * Returns the AnnotationMirror, of the given class or an alias of it, used to annotate the
+   * element. Returns null if no annotation equivalent to {@code anno} exists on {@code elt}.
+   *
+   * @param elt the element
+   * @param anno annotation class
+   * @return an annotation mirror of class {@code anno} on {@code elt}, or an equivalent one, or
+   *     null if none exists on {@code anno}
+   */
+  @Nullable AnnotationMirror getDeclAnnotation(Element elt, Class<? extends Annotation> anno);
+
+  /**
+   * Return the annotation on {@code tree} that is in the hierarchy that contains the qualifier
+   * {@code target}. Returns null if none exists.
+   *
+   * @param tree the tree of which the annotation is returned
+   * @param target the class of the annotation
+   * @return the annotation on {@code tree} that has the class {@code target}, or null
+   */
+  @Nullable AnnotationMirror getAnnotationMirror(Tree tree, Class<? extends Annotation> target);
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java
new file mode 100644
index 0000000..c2d84f9
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java
@@ -0,0 +1,1567 @@
+package org.checkerframework.javacutil;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModifiersTree;
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.model.JavacElements;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Target;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.util.ElementFilter;
+import org.checkerframework.checker.interning.qual.CompareToMethod;
+import org.checkerframework.checker.interning.qual.EqualsMethod;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.util.DefaultAnnotationFormatter;
+import org.checkerframework.javacutil.AnnotationBuilder.CheckerFrameworkAnnotationMirror;
+import org.plumelib.util.CollectionsPlume;
+
+/** A utility class for working with annotations. */
+public class AnnotationUtils {
+
+  // Class cannot be instantiated.
+  private AnnotationUtils() {
+    throw new AssertionError("Class AnnotationUtils cannot be instantiated.");
+  }
+
+  // **********************************************************************
+  // Helper methods to handle annotations.  mainly workaround
+  // AnnotationMirror.equals undesired property
+  // (I think the undesired property is that it's reference equality.)
+  // **********************************************************************
+
+  /**
+   * Returns the fully-qualified name of an annotation as a String.
+   *
+   * @param annotation the annotation whose name to return
+   * @return the fully-qualified name of an annotation as a String
+   */
+  public static final @CanonicalName String annotationName(AnnotationMirror annotation) {
+    if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) {
+      return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName;
+    }
+    final DeclaredType annoType = annotation.getAnnotationType();
+    final TypeElement elm = (TypeElement) annoType.asElement();
+    @SuppressWarnings("signature:assignment") // JDK needs annotations
+    @CanonicalName String name = elm.getQualifiedName().toString();
+    return name;
+  }
+
+  /**
+   * Returns the binary name of an annotation as a String.
+   *
+   * @param annotation the annotation whose binary name to return
+   * @return the binary name of an annotation as a String
+   */
+  public static final @BinaryName String annotationBinaryName(AnnotationMirror annotation) {
+    final DeclaredType annoType = annotation.getAnnotationType();
+    final TypeElement elm = (TypeElement) annoType.asElement();
+    return ElementUtils.getBinaryName(elm);
+  }
+
+  /**
+   * Returns true iff both annotations are of the same type and have the same annotation values.
+   *
+   * <p>This behavior differs from {@code AnnotationMirror.equals(Object)}. The equals method
+   * returns true iff both annotations are the same and annotate the same annotation target (e.g.
+   * field, variable, etc) -- that is, if its arguments are the same annotation instance.
+   *
+   * @param a1 the first AnnotationMirror to compare
+   * @param a2 the second AnnotationMirror to compare
+   * @return true iff a1 and a2 are the same annotation
+   */
+  @EqualsMethod
+  public static boolean areSame(AnnotationMirror a1, AnnotationMirror a2) {
+    if (a1 == a2) {
+      return true;
+    }
+
+    if (!areSameByName(a1, a2)) {
+      return false;
+    }
+
+    return sameElementValues(a1, a2);
+  }
+
+  /**
+   * Return true iff a1 and a2 have the same annotation type.
+   *
+   * @param a1 the first AnnotationMirror to compare
+   * @param a2 the second AnnotationMirror to compare
+   * @return true iff a1 and a2 have the same annotation name
+   * @see #areSame(AnnotationMirror, AnnotationMirror)
+   */
+  @EqualsMethod
+  public static boolean areSameByName(AnnotationMirror a1, AnnotationMirror a2) {
+    if (a1 == a2) {
+      return true;
+    }
+    if (a1 == null || a2 == null) {
+      throw new BugInCF("Unexpected null argument:  areSameByName(%s, %s)", a1, a2);
+    }
+
+    if (a1 instanceof CheckerFrameworkAnnotationMirror
+        && a2 instanceof CheckerFrameworkAnnotationMirror) {
+      return ((CheckerFrameworkAnnotationMirror) a1).annotationName
+          == ((CheckerFrameworkAnnotationMirror) a2).annotationName;
+    }
+
+    return annotationName(a1).equals(annotationName(a2));
+  }
+
+  /**
+   * Checks that the annotation {@code am} has the name {@code aname} (a fully-qualified type name).
+   * Values are ignored.
+   *
+   * @param am the AnnotationMirror whose name to compare
+   * @param aname the string to compare
+   * @return true if aname is the name of am
+   */
+  public static boolean areSameByName(AnnotationMirror am, String aname) {
+    return aname.equals(annotationName(am));
+  }
+
+  /**
+   * Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored.
+   *
+   * <p>This method is not very efficient. It is more efficient to use {@code
+   * AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName}.
+   *
+   * @param am the AnnotationMirror whose class to compare
+   * @param annoClass the class to compare
+   * @return true if annoclass is the class of am
+   * @deprecated use {@code AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName}
+   */
+  @Deprecated // for use only by the framework
+  public static boolean areSameByClass(AnnotationMirror am, Class<? extends Annotation> annoClass) {
+    String canonicalName = annoClass.getCanonicalName();
+    assert canonicalName != null : "@AssumeAssertion(nullness): assumption";
+    return areSameByName(am, canonicalName);
+  }
+
+  /**
+   * Checks that two collections contain the same annotations.
+   *
+   * @param c1 the first collection to compare
+   * @param c2 the second collection to compare
+   * @return true iff c1 and c2 contain the same annotations, according to {@link
+   *     #areSame(AnnotationMirror, AnnotationMirror)}
+   */
+  public static boolean areSame(
+      Collection<? extends AnnotationMirror> c1, Collection<? extends AnnotationMirror> c2) {
+    if (c1.size() != c2.size()) {
+      return false;
+    }
+    if (c1.size() == 1) {
+      return areSame(c1.iterator().next(), c2.iterator().next());
+    }
+
+    // while loop depends on SortedSet implementation.
+    NavigableSet<AnnotationMirror> s1 = createAnnotationSet();
+    NavigableSet<AnnotationMirror> s2 = createAnnotationSet();
+    s1.addAll(c1);
+    s2.addAll(c2);
+    Iterator<AnnotationMirror> iter1 = s1.iterator();
+    Iterator<AnnotationMirror> iter2 = s2.iterator();
+
+    while (iter1.hasNext()) {
+      AnnotationMirror anno1 = iter1.next();
+      AnnotationMirror anno2 = iter2.next();
+      if (!areSame(anno1, anno2)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Checks that the collection contains the annotation. Using Collection.contains does not always
+   * work, because it does not use areSame for comparison.
+   *
+   * @param c a collection of AnnotationMirrors
+   * @param anno the AnnotationMirror to search for in c
+   * @return true iff c contains anno, according to areSame
+   */
+  public static boolean containsSame(
+      Collection<? extends AnnotationMirror> c, AnnotationMirror anno) {
+    return getSame(c, anno) != null;
+  }
+
+  /**
+   * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno}.
+   *
+   * @param c a collection of AnnotationMirrors
+   * @param anno the AnnotationMirror to search for in c
+   * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to
+   *     areSame; otherwise, {@code null}
+   */
+  public static @Nullable AnnotationMirror getSame(
+      Collection<? extends AnnotationMirror> c, AnnotationMirror anno) {
+    for (AnnotationMirror an : c) {
+      if (AnnotationUtils.areSame(an, anno)) {
+        return an;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Checks that the collection contains the annotation. Using Collection.contains does not always
+   * work, because it does not use areSame for comparison.
+   *
+   * <p>This method is not very efficient. It is more efficient to use {@code
+   * AnnotatedTypeFactory#containsSameByClass} or {@link #containsSameByName}.
+   *
+   * @param c a collection of AnnotationMirrors
+   * @param anno the annotation class to search for in c
+   * @return true iff c contains anno, according to areSameByClass
+   */
+  public static boolean containsSameByClass(
+      Collection<? extends AnnotationMirror> c, Class<? extends Annotation> anno) {
+    return getAnnotationByClass(c, anno) != null;
+  }
+
+  /**
+   * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}.
+   *
+   * <p>This method is not very efficient. It is more efficient to use {@code
+   * AnnotatedTypeFactory#getAnnotationByClass} or {@link #getAnnotationByName}.
+   *
+   * @param c a collection of AnnotationMirrors
+   * @param anno the class to search for in c
+   * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to
+   *     areSameByClass; otherwise, {@code null}
+   */
+  public static @Nullable AnnotationMirror getAnnotationByClass(
+      Collection<? extends AnnotationMirror> c, Class<? extends Annotation> anno) {
+    for (AnnotationMirror an : c) {
+      if (AnnotationUtils.areSameByClass(an, anno)) {
+        return an;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Checks that the collection contains an annotation of the given name. Differs from using
+   * Collection.contains, which does not use areSameByName for comparison.
+   *
+   * @param c a collection of AnnotationMirrors
+   * @param anno the name to search for in c
+   * @return true iff c contains anno, according to areSameByName
+   */
+  public static boolean containsSameByName(Collection<? extends AnnotationMirror> c, String anno) {
+    return getAnnotationByName(c, anno) != null;
+  }
+
+  /**
+   * Returns the AnnotationMirror in {@code c} that has the same name as {@code anno}.
+   *
+   * @param c a collection of AnnotationMirrors
+   * @param anno the name to search for in c
+   * @return AnnotationMirror with the same name as {@code anno} iff c contains anno, according to
+   *     areSameByName; otherwise, {@code null}
+   */
+  public static @Nullable AnnotationMirror getAnnotationByName(
+      Collection<? extends AnnotationMirror> c, String anno) {
+    for (AnnotationMirror an : c) {
+      if (AnnotationUtils.areSameByName(an, anno)) {
+        return an;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Checks that the collection contains an annotation of the given name. Differs from using
+   * Collection.contains, which does not use areSameByName for comparison.
+   *
+   * @param c a collection of AnnotationMirrors
+   * @param anno the annotation whose name to search for in c
+   * @return true iff c contains anno, according to areSameByName
+   */
+  public static boolean containsSameByName(
+      Collection<? extends AnnotationMirror> c, AnnotationMirror anno) {
+    return getSameByName(c, anno) != null;
+  }
+
+  /**
+   * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno} ignoring
+   * values.
+   *
+   * @param c a collection of AnnotationMirrors
+   * @param anno the annotation whose name to search for in c
+   * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to
+   *     areSameByName; otherwise, {@code null}
+   */
+  public static @Nullable AnnotationMirror getSameByName(
+      Collection<? extends AnnotationMirror> c, AnnotationMirror anno) {
+    for (AnnotationMirror an : c) {
+      if (AnnotationUtils.areSameByName(an, anno)) {
+        return an;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Provide ordering for {@link AnnotationMirror}s. AnnotationMirrors are first compared by their
+   * fully-qualified names, then by their element values in order of the name of the element.
+   *
+   * @param a1 the first annotation
+   * @param a2 the second annotation
+   * @return an ordering over AnnotationMirrors based on their name and values
+   */
+  public static int compareAnnotationMirrors(AnnotationMirror a1, AnnotationMirror a2) {
+    if (!AnnotationUtils.areSameByName(a1, a2)) {
+      return annotationName(a1).compareTo(annotationName(a2));
+    }
+
+    // The annotations have the same name, but different values, so compare values.
+    Map<? extends ExecutableElement, ? extends AnnotationValue> vals1 = a1.getElementValues();
+    Map<? extends ExecutableElement, ? extends AnnotationValue> vals2 = a2.getElementValues();
+    Set<ExecutableElement> sortedElements =
+        new TreeSet<>(Comparator.comparing(ElementUtils::getSimpleSignature));
+    sortedElements.addAll(
+        ElementFilter.methodsIn(a1.getAnnotationType().asElement().getEnclosedElements()));
+
+    for (ExecutableElement meth : sortedElements) {
+      AnnotationValue aval1 = vals1.get(meth);
+      if (aval1 == null) {
+        aval1 = meth.getDefaultValue();
+      }
+      AnnotationValue aval2 = vals2.get(meth);
+      if (aval2 == null) {
+        aval2 = meth.getDefaultValue();
+      }
+      int result = compareAnnotationValue(aval1, aval2);
+      if (result != 0) {
+        return result;
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * Return 0 iff the two AnnotationValue objects are the same.
+   *
+   * @param av1 the first AnnotationValue to compare
+   * @param av2 the second AnnotationValue to compare
+   * @return 0 if if the two annotation values are the same
+   */
+  @CompareToMethod
+  private static int compareAnnotationValue(AnnotationValue av1, AnnotationValue av2) {
+    if (av1 == av2) {
+      return 0;
+    } else if (av1 == null) {
+      return -1;
+    } else if (av2 == null) {
+      return 1;
+    }
+    return compareAnnotationValueValue(av1.getValue(), av2.getValue());
+  }
+
+  /**
+   * Compares two annotation values for order.
+   *
+   * @param val1 a value returned by {@code AnnotationValue.getValue()}
+   * @param val2 a value returned by {@code AnnotationValue.getValue()}
+   * @return a negative integer, zero, or a positive integer as the first annotation value is less
+   *     than, equal to, or greater than the second annotation value
+   */
+  @CompareToMethod
+  private static int compareAnnotationValueValue(@Nullable Object val1, @Nullable Object val2) {
+    if (val1 == val2) {
+      return 0;
+    } else if (val1 == null) {
+      return -1;
+    } else if (val2 == null) {
+      return 1;
+    }
+    // Can't use deepEquals() to compare val1 and val2, because they might have mismatched
+    // AnnotationValue vs. CheckerFrameworkAnnotationValue, and AnnotationValue doesn't override
+    // equals().  So, write my own version of deepEquals().
+    if ((val1 instanceof List<?>) && (val2 instanceof List<?>)) {
+      List<?> list1 = (List<?>) val1;
+      List<?> list2 = (List<?>) val2;
+      if (list1.size() != list2.size()) {
+        return list1.size() - list2.size();
+      }
+      // Don't compare setwise, because order can matter. These mean different things:
+      //   @LTLengthOf(value={"a1","a2"}, offest={"0", "1"})
+      //   @LTLengthOf(value={"a2","a1"}, offest={"0", "1"})
+      for (int i = 0; i < list1.size(); i++) {
+        Object v1 = list1.get(i);
+        Object v2 = list2.get(i);
+        int result = compareAnnotationValueValue(v1, v2);
+        if (result != 0) {
+          return result;
+        }
+      }
+      return 0;
+    } else if ((val1 instanceof AnnotationMirror) && (val2 instanceof AnnotationMirror)) {
+      return compareAnnotationMirrors((AnnotationMirror) val1, (AnnotationMirror) val2);
+    } else if ((val1 instanceof AnnotationValue) && (val2 instanceof AnnotationValue)) {
+      // This case occurs because of the recursive call when comparing arrays of annotation values.
+      return compareAnnotationValue((AnnotationValue) val1, (AnnotationValue) val2);
+    }
+
+    if ((val1 instanceof Type.ClassType) && (val2 instanceof Type.ClassType)) {
+      // Type.ClassType does not override equals
+      if (TypesUtils.areSameDeclaredTypes((Type.ClassType) val1, (Type.ClassType) val2)) {
+        return 0;
+      }
+    }
+    if (Objects.equals(val1, val2)) {
+      return 0;
+    }
+    int result = val1.toString().compareTo(val2.toString());
+    if (result == 0) {
+      result = -1;
+    }
+    return result;
+  }
+
+  /**
+   * Create a map suitable for storing {@link AnnotationMirror} as keys.
+   *
+   * <p>It can store one instance of {@link AnnotationMirror} of a given declared type, regardless
+   * of the annotation element values.
+   *
+   * @param <V> the value of the map
+   * @return a new map with {@link AnnotationMirror} as key
+   */
+  public static <V> Map<AnnotationMirror, V> createAnnotationMap() {
+    return new TreeMap<>(AnnotationUtils::compareAnnotationMirrors);
+  }
+
+  /**
+   * Constructs a {@link Set} for storing {@link AnnotationMirror}s.
+   *
+   * <p>It stores at most once instance of {@link AnnotationMirror} of a given type, regardless of
+   * the annotation element values.
+   *
+   * @return a sorted new set to store {@link AnnotationMirror} as element
+   */
+  public static NavigableSet<AnnotationMirror> createAnnotationSet() {
+    return new TreeSet<>(AnnotationUtils::compareAnnotationMirrors);
+  }
+
+  /**
+   * Constructs a {@link Set} for storing {@link AnnotationMirror}s contain all the annotations in
+   * {@code annos}.
+   *
+   * <p>It stores at most once instance of {@link AnnotationMirror} of a given type, regardless of
+   * the annotation element values.
+   *
+   * @param annos a Collection of AnnotationMirrors to put in the created set
+   * @return a sorted new set to store {@link AnnotationMirror} as element
+   */
+  public static NavigableSet<AnnotationMirror> createAnnotationSet(
+      Collection<AnnotationMirror> annos) {
+    TreeSet<AnnotationMirror> set = new TreeSet<>(AnnotationUtils::compareAnnotationMirrors);
+    set.addAll(annos);
+    return set;
+  }
+
+  /**
+   * Constructs an unmodifiable {@link Set} for storing {@link AnnotationMirror}s contain all the
+   * annotations in {@code annos}.
+   *
+   * <p>It stores at most once instance of {@link AnnotationMirror} of a given type, regardless of
+   * the annotation element values.
+   *
+   * @param annos a Collection of AnnotationMirrors to put in the created set
+   * @return a sorted, unmodifiable, new set to store {@link AnnotationMirror} as element
+   */
+  public static NavigableSet<AnnotationMirror> createUnmodifiableAnnotationSet(
+      Collection<AnnotationMirror> annos) {
+    TreeSet<AnnotationMirror> set = new TreeSet<>(AnnotationUtils::compareAnnotationMirrors);
+    set.addAll(annos);
+    return Collections.unmodifiableNavigableSet(set);
+  }
+
+  /**
+   * Returns true if the given annotation has a @Inherited meta-annotation.
+   *
+   * @param anno the annotation to check for an @Inherited meta-annotation
+   * @return true if the given annotation has a @Inherited meta-annotation
+   */
+  public static boolean hasInheritedMeta(AnnotationMirror anno) {
+    return anno.getAnnotationType().asElement().getAnnotation(Inherited.class) != null;
+  }
+
+  /**
+   * Returns the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE.
+   *
+   * @param target a location where an annotation can be written
+   * @return the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE
+   */
+  public static EnumSet<ElementKind> getElementKindsForTarget(@Nullable Target target) {
+    if (target == null) {
+      // A missing @Target implies that the annotation can be written everywhere.
+      return EnumSet.allOf(ElementKind.class);
+    }
+    EnumSet<ElementKind> eleKinds = EnumSet.noneOf(ElementKind.class);
+    for (ElementType elementType : target.value()) {
+      eleKinds.addAll(getElementKindsForElementType(elementType));
+    }
+    return eleKinds;
+  }
+
+  /**
+   * Returns the set of {@link ElementKind}s corresponding to {@code elementType}. If the element
+   * type is TYPE_USE, then ElementKinds returned should be the same as those returned for TYPE and
+   * TYPE_PARAMETER, but this method returns the empty set instead.
+   *
+   * @param elementType the elementType to find ElementKinds for
+   * @return the set of {@link ElementKind}s corresponding to {@code elementType}
+   */
+  public static EnumSet<ElementKind> getElementKindsForElementType(ElementType elementType) {
+    switch (elementType) {
+      case TYPE:
+        return EnumSet.copyOf(ElementUtils.typeElementKinds());
+      case FIELD:
+        return EnumSet.of(ElementKind.FIELD, ElementKind.ENUM_CONSTANT);
+      case METHOD:
+        return EnumSet.of(ElementKind.METHOD);
+      case PARAMETER:
+        return EnumSet.of(ElementKind.PARAMETER);
+      case CONSTRUCTOR:
+        return EnumSet.of(ElementKind.CONSTRUCTOR);
+      case LOCAL_VARIABLE:
+        return EnumSet.of(
+            ElementKind.LOCAL_VARIABLE,
+            ElementKind.RESOURCE_VARIABLE,
+            ElementKind.EXCEPTION_PARAMETER);
+      case ANNOTATION_TYPE:
+        return EnumSet.of(ElementKind.ANNOTATION_TYPE);
+      case PACKAGE:
+        return EnumSet.of(ElementKind.PACKAGE);
+      case TYPE_PARAMETER:
+        return EnumSet.of(ElementKind.TYPE_PARAMETER);
+      case TYPE_USE:
+        return EnumSet.noneOf(ElementKind.class);
+      default:
+        // TODO: Use MODULE enum constants directly instead of looking them up by name.  (Java 11)
+        if (elementType.name().equals("MODULE")) {
+          return EnumSet.of(ElementKind.valueOf("MODULE"));
+        }
+        if (elementType.name().equals("RECORD_COMPONENT")) {
+          return EnumSet.of(ElementKind.valueOf("RECORD_COMPONENT"));
+        }
+        throw new BugInCF("Unrecognized ElementType: " + elementType);
+    }
+  }
+
+  // **********************************************************************
+  // Annotation values: inefficient extractors that take an element name
+  // **********************************************************************
+
+  /**
+   * Returns the values of an annotation's elements, including defaults. The method with the same
+   * name in JavacElements cannot be used directly, because it includes a cast to
+   * Attribute.Compound, which doesn't hold for annotations generated by the Checker Framework.
+   *
+   * <p>This method is intended for use only by the framework. Clients should use a method that
+   * takes an {@link ExecutableElement}.
+   *
+   * @see AnnotationMirror#getElementValues()
+   * @see JavacElements#getElementValuesWithDefaults(AnnotationMirror)
+   * @param ad annotation to examine
+   * @return the values of the annotation's elements, including defaults
+   * @deprecated use a method that takes an {@link ExecutableElement}
+   */
+  @Deprecated // 2021-03-29; do not remove, just make private
+  public static Map<? extends ExecutableElement, ? extends AnnotationValue>
+      getElementValuesWithDefaults(AnnotationMirror ad) {
+    Map<ExecutableElement, AnnotationValue> valMap = new HashMap<>();
+    if (ad.getElementValues() != null) {
+      valMap.putAll(ad.getElementValues());
+    }
+    for (ExecutableElement meth :
+        ElementFilter.methodsIn(ad.getAnnotationType().asElement().getEnclosedElements())) {
+      AnnotationValue defaultValue = meth.getDefaultValue();
+      if (defaultValue != null) {
+        valMap.putIfAbsent(meth, defaultValue);
+      }
+    }
+    return valMap;
+  }
+
+  /**
+   * Verify whether the element with the name {@code elementName} exists in the annotation {@code
+   * anno}.
+   *
+   * <p>This method is intended for use only by the framework. Clients should use a method that
+   * takes an {@link ExecutableElement}.
+   *
+   * @param anno the annotation to examine
+   * @param elementName the name of the element
+   * @return whether the element exists in anno
+   * @deprecated use a method that takes an {@link ExecutableElement}
+   */
+  @Deprecated // 2021-03-30
+  public static boolean hasElementValue(AnnotationMirror anno, CharSequence elementName) {
+    Map<? extends ExecutableElement, ? extends AnnotationValue> valmap = anno.getElementValues();
+    for (ExecutableElement elem : valmap.keySet()) {
+      if (elem.getSimpleName().contentEquals(elementName)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Get the element with the name {@code elementName} of the annotation {@code anno}. The result
+   * has type {@code expectedType}.
+   *
+   * <p>If the return type is an array, use {@link #getElementValueArray} instead.
+   *
+   * <p>If the return type is an enum, use {@link #getElementValueEnum} instead.
+   *
+   * <p>This method is intended only for use by the framework. A checker implementation should use
+   * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link
+   * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}.
+   *
+   * @param anno the annotation whose element to access
+   * @param elementName the name of the element to access
+   * @param expectedType the type of the element and the return value
+   * @param <T> the class of the type
+   * @param useDefaults whether to apply default values to the element
+   * @return the value of the element with the given name
+   * @deprecated use {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link
+   *     #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}
+   */
+  @Deprecated // for use only by the framework
+  public static <T> T getElementValue(
+      AnnotationMirror anno, CharSequence elementName, Class<T> expectedType, boolean useDefaults) {
+    Map<? extends ExecutableElement, ? extends AnnotationValue> valmap;
+    if (useDefaults) {
+      @SuppressWarnings(
+          "deprecation" // remove when getElementValuesWithDefaults is made private and
+      // non-deprecated
+      )
+      Map<? extends ExecutableElement, ? extends AnnotationValue> valmapTmp =
+          getElementValuesWithDefaults(anno);
+      valmap = valmapTmp;
+    } else {
+      valmap = anno.getElementValues();
+    }
+    for (ExecutableElement elem : valmap.keySet()) {
+      if (elem.getSimpleName().contentEquals(elementName)) {
+        AnnotationValue val = valmap.get(elem);
+        try {
+          return expectedType.cast(val.getValue());
+        } catch (ClassCastException e) {
+          throw new BugInCF(
+              "getElementValue(%s, %s, %s, %s): val=%s, val.getValue()=%s [%s]",
+              anno,
+              elementName,
+              expectedType,
+              useDefaults,
+              val,
+              val.getValue(),
+              val.getValue().getClass());
+        }
+      }
+    }
+    throw new NoSuchElementException(
+        String.format(
+            "No element with name \'%s\' in annotation %s; useDefaults=%s, valmap.keySet()=%s",
+            elementName, anno, useDefaults, valmap.keySet()));
+  }
+
+  /** Differentiates NoSuchElementException from other BugInCF, for use by getElementValueOrNull. */
+  @SuppressWarnings("serial")
+  private static class NoSuchElementException extends BugInCF {
+    /**
+     * Constructs a new NoSuchElementException.
+     *
+     * @param message the detail message
+     */
+    @Pure
+    public NoSuchElementException(String message) {
+      super(message);
+    }
+  }
+
+  /**
+   * Get the element with the name {@code elementName} of the annotation {@code anno}, or return
+   * null if no such element exists.
+   *
+   * <p>This method is intended only for use by the framework. A checker implementation should use
+   * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}.
+   *
+   * @param anno the annotation whose element to access
+   * @param elementName the name of the element to access
+   * @param expectedType the type of the element and the return value
+   * @param <T> the class of the type
+   * @param useDefaults whether to apply default values to the element
+   * @return the value of the element with the given name, or null
+   */
+  public static <T> @Nullable T getElementValueOrNull(
+      AnnotationMirror anno, CharSequence elementName, Class<T> expectedType, boolean useDefaults) {
+    // This implementation permits getElementValue a more detailed error message than if
+    // getElementValue called getElementValueOrNull and threw an error if the result was null.
+    try {
+      return getElementValue(anno, elementName, expectedType, useDefaults);
+    } catch (NoSuchElementException e) {
+      return null;
+    }
+  }
+
+  /**
+   * Get the element with the name {@code elementName} of the annotation {@code anno}, or return
+   * null if no such element exists. One element of the result has type {@code expectedType}.
+   *
+   * <p>This method is intended only for use by the framework. A checker implementation should use
+   * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}.
+   *
+   * @param anno the annotation whose element to access
+   * @param elementName the name of the element to access
+   * @param expectedType the component type of the element and of the return value
+   * @param <T> the class of the component type
+   * @param useDefaults whether to apply default values to the element
+   * @return the value of the element with the given name, or null
+   */
+  public static <T> @Nullable List<T> getElementValueArrayOrNull(
+      AnnotationMirror anno, CharSequence elementName, Class<T> expectedType, boolean useDefaults) {
+    // This implementation permits getElementValue a more detailed error message than if
+    // getElementValue called getElementValueOrNull and threw an error if the result was null.
+    try {
+      return getElementValueArray(anno, elementName, expectedType, useDefaults);
+    } catch (NoSuchElementException e) {
+      return null;
+    }
+  }
+
+  /**
+   * Get the element with the name {@code name} of the annotation {@code anno}. The result is an
+   * enum of type {@code T}.
+   *
+   * <p>This method is intended only for use by the framework. A checker implementation should use
+   * {@link #getElementValueEnum(AnnotationMirror, ExecutableElement, Class)} or {@link
+   * #getElementValueEnum(AnnotationMirror, ExecutableElement, Class, Enum)}.
+   *
+   * @param anno the annotation to disassemble
+   * @param elementName the name of the element to access
+   * @param expectedType the type of the element and the return value, an enum
+   * @param <T> the class of the type
+   * @param useDefaults whether to apply default values to the element
+   * @return the value of the element with the given name
+   * @deprecated use {@link #getElementValueEnum(AnnotationMirror, ExecutableElement, Class)} or
+   *     {@link #getElementValueEnum(AnnotationMirror, ExecutableElement, Class, Enum)}
+   */
+  @Deprecated // 2021-03-29
+  public static <T extends Enum<T>> T getElementValueEnum(
+      AnnotationMirror anno, CharSequence elementName, Class<T> expectedType, boolean useDefaults) {
+    VarSymbol vs = getElementValue(anno, elementName, VarSymbol.class, useDefaults);
+    T value = Enum.valueOf(expectedType, vs.getSimpleName().toString());
+    return value;
+  }
+
+  /**
+   * Get the element with the name {@code elementName} of the annotation {@code anno}, where the
+   * element has an array type. One element of the result has type {@code expectedType}.
+   *
+   * <p>Parameter useDefaults is used to determine whether default values should be used for
+   * annotation values. Finding defaults requires more computation, so should be false when no
+   * defaulting is needed.
+   *
+   * <p>This method is intended only for use by the framework. A checker implementation should use
+   * {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class)} or {@code
+   * #getElementValueArray(AnnotationMirror, ExecutableElement, Class, Object)}.
+   *
+   * @param anno the annotation to disassemble
+   * @param elementName the name of the element to access
+   * @param expectedType the component type of the element and of the return type
+   * @param <T> the class of the type
+   * @param useDefaults whether to apply default values to the element
+   * @return the value of the element with the given name; it is a new list, so it is safe for
+   *     clients to side-effect
+   * @deprecated use {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class)} or
+   *     {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class, Object)}
+   */
+  @Deprecated // for use only by the framework
+  public static <T> List<T> getElementValueArray(
+      AnnotationMirror anno, CharSequence elementName, Class<T> expectedType, boolean useDefaults) {
+    @SuppressWarnings("unchecked")
+    List<AnnotationValue> la = getElementValue(anno, elementName, List.class, useDefaults);
+    List<T> result = new ArrayList<>(la.size());
+    for (AnnotationValue a : la) {
+      try {
+        result.add(expectedType.cast(a.getValue()));
+      } catch (Throwable t) {
+        String err1 =
+            String.format(
+                "getElementValueArray(%n"
+                    + "  anno=%s,%n"
+                    + "  elementName=%s,%n"
+                    + "  expectedType=%s,%n"
+                    + "  useDefaults=%s)%n",
+                anno, elementName, expectedType, useDefaults);
+        String err2 =
+            String.format(
+                "Error in cast:%n  expectedType=%s%n  a=%s [%s]%n  a.getValue()=%s [%s]",
+                expectedType, a, a.getClass(), a.getValue(), a.getValue().getClass());
+        throw new BugInCF(err1 + "; " + err2, t);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Get the element with the name {@code elementName} of the annotation {@code anno}, or the
+   * default value if no element is present explicitly, where the element has an array type and the
+   * elements are {@code Enum}s. One element of the result has type {@code expectedType}.
+   *
+   * <p>This method is intended only for use by the framework. A checker implementation should use
+   * {@link #getElementValueEnumArray(AnnotationMirror, ExecutableElement, Class)} or {@link
+   * #getElementValueEnumArray(AnnotationMirror, ExecutableElement, Class, Enum[])}.
+   *
+   * @param anno the annotation to disassemble
+   * @param elementName the name of the element to access
+   * @param expectedType the component type of the element and of the return type, an enum
+   * @param <T> the class of the type
+   * @param useDefaults whether to apply default values to the element
+   * @return the value of the element with the given name
+   * @deprecated use {@link #getElementValueEnumArray(AnnotationMirror, ExecutableElement, Class)}
+   *     or {@link #getElementValueEnumArray(AnnotationMirror, ExecutableElement, Class, Enum[])}
+   */
+  @Deprecated // 2021-03-29
+  public static <T extends Enum<T>> T[] getElementValueEnumArray(
+      AnnotationMirror anno, CharSequence elementName, Class<T> expectedType, boolean useDefaults) {
+    @SuppressWarnings("unchecked")
+    List<AnnotationValue> la = getElementValue(anno, elementName, List.class, useDefaults);
+    return annotationValueListToEnumArray(la, expectedType);
+  }
+
+  /**
+   * Get the Name of the class that is referenced by element {@code elementName}.
+   *
+   * <p>This is a convenience method for the most common use-case. It is like {@code
+   * getElementValue(anno, elementName, ClassType.class).getQualifiedName()}, but this method
+   * ensures consistent use of the qualified name.
+   *
+   * <p>This method is intended only for use by the framework. A checker implementation should use
+   * {@code anno.getElementValues().get(someElement).getValue().asElement().getQualifiedName();}.
+   *
+   * @param anno the annotation to disassemble
+   * @param elementName the name of the element to access; it must be present in the annotation
+   * @param useDefaults whether to apply default values to the element
+   * @return the name of the class that is referenced by element with the given name; may be an
+   *     empty name, for a local or anonymous class
+   * @deprecated use an ExecutableElement
+   */
+  @Deprecated // permitted for use by the framework
+  public static @CanonicalName Name getElementValueClassName(
+      AnnotationMirror anno, CharSequence elementName, boolean useDefaults) {
+    Type.ClassType ct = getElementValue(anno, elementName, Type.ClassType.class, useDefaults);
+    // TODO:  Is it a problem that this returns the type parameters too?  Should I cut them off?
+    @CanonicalName Name result = ct.asElement().getQualifiedName();
+    return result;
+  }
+
+  /**
+   * Get the list of Names of the classes that are referenced by element {@code elementName}. It
+   * fails if the class wasn't found.
+   *
+   * <p>This method is intended only for use by the framework. A checker implementation should use
+   * {@link #getElementValueClassNames(AnnotationMirror, ExecutableElement)}.
+   *
+   * @param anno the annotation whose field to access; it must be present in the annotation
+   * @param annoElement the element/field of {@code anno} whose content is a list of classes
+   * @param useDefaults whether to apply default values to the element
+   * @return the names of classes in {@code anno.annoElement}
+   * @deprecated use {@link #getElementValueClassNames(AnnotationMirror,ExecutableElement)}
+   */
+  @Deprecated // 2021-03-29
+  public static List<@CanonicalName Name> getElementValueClassNames(
+      AnnotationMirror anno, CharSequence annoElement, boolean useDefaults) {
+    List<Type.ClassType> la =
+        getElementValueArray(anno, annoElement, Type.ClassType.class, useDefaults);
+    return CollectionsPlume.<Type.ClassType, @CanonicalName Name>mapList(
+        (Type.ClassType classType) -> classType.asElement().getQualifiedName(), la);
+  }
+
+  // **********************************************************************
+  // Annotation values: efficient extractors that take an ExecutableElement
+  // **********************************************************************
+
+  /**
+   * Get the given element of the annotation {@code anno}. The result has type {@code expectedType}.
+   *
+   * <p>If the return type is primitive, use {@link #getElementValueInt} or {@link
+   * #getElementValueLong} instead.
+   *
+   * <p>If the return type is an array, use {@link #getElementValueArray} instead.
+   *
+   * <p>If the return type is an enum, use {@link #getElementValueEnum} instead.
+   *
+   * @param anno the annotation whose element to access
+   * @param element the element to access; it must be present in the annotation
+   * @param expectedType the type of the element and the return value
+   * @param <T> the class of the type
+   * @return the value of the element with the given name
+   */
+  public static <T> @Nullable T getElementValue(
+      AnnotationMirror anno, ExecutableElement element, Class<T> expectedType) {
+    AnnotationValue av = anno.getElementValues().get(element);
+    if (av == null) {
+      throw new BugInCF("getElementValue(%s, %s, ...)", anno, element);
+    }
+    return expectedType.cast(av.getValue());
+  }
+
+  /**
+   * Get the given element of the annotation {@code anno}. The result has type {@code expectedType}.
+   *
+   * <p>If the return type is primitive, use {@link #getElementValueInt} or {@link
+   * #getElementValueLong} instead.
+   *
+   * <p>If the return type is an array, use {@link #getElementValueArray} instead.
+   *
+   * <p>If the return type is an enum, use {@link #getElementValueEnum} instead.
+   *
+   * @param anno the annotation whose element to access
+   * @param element the element to access
+   * @param expectedType the type of the element and the return value
+   * @param <T> the class of the type
+   * @param defaultValue the value to return if the element is not present
+   * @return the value of the element with the given name
+   */
+  public static <T> @Nullable T getElementValue(
+      AnnotationMirror anno, ExecutableElement element, Class<T> expectedType, T defaultValue) {
+    AnnotationValue av = anno.getElementValues().get(element);
+    if (av == null) {
+      return defaultValue;
+    } else {
+      return expectedType.cast(av.getValue());
+    }
+  }
+
+  /**
+   * Get the given boolean element of the annotation {@code anno}.
+   *
+   * @param anno the annotation whose element to access
+   * @param element the element to access
+   * @param defaultValue the value to return if the element is not present
+   * @return the value of the element with the given name
+   */
+  public static boolean getElementValueBoolean(
+      AnnotationMirror anno, ExecutableElement element, boolean defaultValue) {
+    AnnotationValue av = anno.getElementValues().get(element);
+    if (av == null) {
+      return defaultValue;
+    } else {
+      return (boolean) av.getValue();
+    }
+  }
+
+  /**
+   * Get the given integer element of the annotation {@code anno}.
+   *
+   * @param anno the annotation whose element to access
+   * @param element the element to access
+   * @return the value of the element with the given name
+   */
+  public static int getElementValueInt(AnnotationMirror anno, ExecutableElement element) {
+    AnnotationValue av = anno.getElementValues().get(element);
+    if (av == null) {
+      throw new BugInCF("getElementValueInt(%s, %s, ...)", anno, element);
+    } else {
+      return (int) av.getValue();
+    }
+  }
+
+  /**
+   * Get the given integer element of the annotation {@code anno}.
+   *
+   * @param anno the annotation whose element to access
+   * @param element the element to access
+   * @param defaultValue the value to return if the element is not present
+   * @return the value of the element with the given name
+   */
+  public static int getElementValueInt(
+      AnnotationMirror anno, ExecutableElement element, int defaultValue) {
+    AnnotationValue av = anno.getElementValues().get(element);
+    if (av == null) {
+      return defaultValue;
+    } else {
+      return (int) av.getValue();
+    }
+  }
+
+  /**
+   * Get the given long element of the annotation {@code anno}.
+   *
+   * @param anno the annotation whose element to access
+   * @param element the element to access
+   * @param defaultValue the value to return if the element is not present
+   * @return the value of the element with the given name
+   */
+  public static long getElementValueLong(
+      AnnotationMirror anno, ExecutableElement element, long defaultValue) {
+    AnnotationValue av = anno.getElementValues().get(element);
+    if (av == null) {
+      return defaultValue;
+    } else {
+      return (long) av.getValue();
+    }
+  }
+
+  /**
+   * Get the element with the name {@code name} of the annotation {@code anno}. The result is an
+   * enum of type {@code T}.
+   *
+   * @param anno the annotation to disassemble
+   * @param element the element to access; it must be present in the annotation
+   * @param expectedType the type of the element and the return value, an enum
+   * @param <T> the class of the type
+   * @return the value of the element with the given name
+   */
+  public static <T extends Enum<T>> T getElementValueEnum(
+      AnnotationMirror anno, ExecutableElement element, Class<T> expectedType) {
+    AnnotationValue av = anno.getElementValues().get(element);
+    if (av == null) {
+      throw new BugInCF("getElementValueEnum(%s, %s, ...)", anno, element);
+    }
+    VariableElement ve = (VariableElement) av.getValue();
+    return Enum.valueOf(expectedType, ve.getSimpleName().toString());
+  }
+
+  /**
+   * Get the element with the name {@code name} of the annotation {@code anno}. The result is an
+   * enum of type {@code T}.
+   *
+   * @param anno the annotation to disassemble
+   * @param element the element to access
+   * @param expectedType the type of the element and the return value, an enum
+   * @param <T> the class of the type
+   * @param defaultValue the value to return if the element is not present
+   * @return the value of the element with the given name
+   */
+  public static <T extends Enum<T>> T getElementValueEnum(
+      AnnotationMirror anno, ExecutableElement element, Class<T> expectedType, T defaultValue) {
+    AnnotationValue av = anno.getElementValues().get(element);
+    if (av == null) {
+      return defaultValue;
+    } else {
+      VariableElement ve = (VariableElement) av.getValue();
+      return Enum.valueOf(expectedType, ve.getSimpleName().toString());
+    }
+  }
+
+  /**
+   * Get the element with the name {@code name} of the annotation {@code anno}. The result is an
+   * array of type {@code T}.
+   *
+   * @param anno the annotation to disassemble
+   * @param element the element to access; it must be present in the annotation
+   * @param expectedType the component type of the element and of the return value, an enum
+   * @param <T> the enum class of the component type
+   * @return the value of the element with the given name
+   */
+  public static <T extends Enum<T>> T[] getElementValueEnumArray(
+      AnnotationMirror anno, ExecutableElement element, Class<T> expectedType) {
+    AnnotationValue av = anno.getElementValues().get(element);
+    if (av == null) {
+      throw new BugInCF("getElementValueEnumArray(%s, %s, ...)", anno, element);
+    }
+    return AnnotationUtils.annotationValueListToEnumArray(av, expectedType);
+  }
+
+  /**
+   * Get the element with the name {@code name} of the annotation {@code anno}. The result is an
+   * array of type {@code T}.
+   *
+   * @param anno the annotation to disassemble
+   * @param element the element to access
+   * @param expectedType the component type of the element and of the return type
+   * @param <T> the enum class of the component type
+   * @param defaultValue the value to return if the annotation does not have the element
+   * @return the value of the element with the given name
+   */
+  public static <T extends Enum<T>> T[] getElementValueEnumArray(
+      AnnotationMirror anno, ExecutableElement element, Class<T> expectedType, T[] defaultValue) {
+    AnnotationValue av = anno.getElementValues().get(element);
+    if (av == null) {
+      return defaultValue;
+    } else {
+      return AnnotationUtils.annotationValueListToEnumArray(av, expectedType);
+    }
+  }
+
+  /**
+   * Get the given element of the annotation {@code anno}, where the element has an array type. One
+   * element of the result has type {@code expectedType}.
+   *
+   * @param anno the annotation to disassemble
+   * @param element the element to access; it must be present in the annotation
+   * @param expectedType the component type of the element and of the return type
+   * @param <T> the class of the component type
+   * @return the value of the element with the given name; it is a new list, so it is safe for
+   *     clients to side-effect
+   */
+  public static <T> List<T> getElementValueArray(
+      AnnotationMirror anno, ExecutableElement element, Class<T> expectedType) {
+    AnnotationValue av = anno.getElementValues().get(element);
+    if (av == null) {
+      throw new BugInCF("getElementValueArray(%s, %s, ...)", anno, element);
+    }
+    return AnnotationUtils.annotationValueToList(av, expectedType);
+  }
+
+  /**
+   * Get the given element of the annotation {@code anno}, where the element has an array type. One
+   * element of the result has type {@code expectedType}.
+   *
+   * @param anno the annotation to disassemble
+   * @param element the element to access
+   * @param expectedType the component type of the element and of the return type
+   * @param <T> the class of the component type
+   * @param defaultValue the value to return if the element is not present
+   * @return the value of the element with the given name; it is a new list, so it is safe for
+   *     clients to side-effect
+   */
+  public static <T> List<T> getElementValueArray(
+      AnnotationMirror anno,
+      ExecutableElement element,
+      Class<T> expectedType,
+      List<T> defaultValue) {
+    AnnotationValue av = anno.getElementValues().get(element);
+    if (av == null) {
+      return defaultValue;
+    } else {
+      return AnnotationUtils.annotationValueToList(av, expectedType);
+    }
+  }
+
+  /**
+   * Converts a list of AnnotationValue to an array of enum.
+   *
+   * @param <T> the element type of the enum array
+   * @param avList a list of AnnotationValue
+   * @param expectedType the component type of the element and of the return type, an enum
+   * @return an array of enum, converted from the input list
+   */
+  public static <T extends Enum<T>> T[] annotationValueListToEnumArray(
+      AnnotationValue avList, Class<T> expectedType) {
+    @SuppressWarnings("unchecked")
+    List<AnnotationValue> list = (List<AnnotationValue>) avList.getValue();
+    return annotationValueListToEnumArray(list, expectedType);
+  }
+
+  /**
+   * Converts a list of AnnotationValue to an array of enum.
+   *
+   * @param <T> the element type of the enum array
+   * @param la a list of AnnotationValue
+   * @param expectedType the component type of the element and of the return type, an enum
+   * @return an array of enum, converted from the input list
+   */
+  public static <T extends Enum<T>> T[] annotationValueListToEnumArray(
+      List<AnnotationValue> la, Class<T> expectedType) {
+    int size = la.size();
+    @SuppressWarnings("unchecked")
+    T[] result = (T[]) Array.newInstance(expectedType, size);
+    for (int i = 0; i < size; i++) {
+      AnnotationValue a = la.get(i);
+      T value = Enum.valueOf(expectedType, a.getValue().toString());
+      result[i] = value;
+    }
+    return result;
+  }
+
+  /**
+   * Get the Name of the class that is referenced by element {@code element}.
+   *
+   * <p>This is a convenience method for the most common use-case. It is like {@code
+   * getElementValue(anno, element, ClassType.class).getQualifiedName()}, but this method ensures
+   * consistent use of the qualified name.
+   *
+   * <p>This method is intended only for use by the framework. A checker implementation should use
+   * {@code anno.getElementValues().get(someElement).getValue().asElement().getQualifiedName();}.
+   *
+   * @param anno the annotation to disassemble
+   * @param element the element to access; it must be present in the annotation
+   * @return the name of the class that is referenced by element with the given name; may be an
+   *     empty name, for a local or anonymous class
+   */
+  public static @CanonicalName Name getElementValueClassName(
+      AnnotationMirror anno, ExecutableElement element) {
+    Type.ClassType ct = getElementValue(anno, element, Type.ClassType.class);
+    if (ct == null) {
+      throw new BugInCF("getElementValueClassName(%s, %s, ...)", anno, element);
+    }
+    // TODO:  Is it a problem that this returns the type parameters too?  Should I cut them off?
+    @CanonicalName Name result = ct.asElement().getQualifiedName();
+    return result;
+  }
+
+  /**
+   * Get the list of Names of the classes that are referenced by element {@code element}. It fails
+   * if the class wasn't found.
+   *
+   * @param anno the annotation whose field to access; it must be present in the annotation
+   * @param element the element/field of {@code anno} whose content is a list of classes
+   * @return the names of classes in {@code anno.annoElement}
+   */
+  public static List<@CanonicalName Name> getElementValueClassNames(
+      AnnotationMirror anno, ExecutableElement element) {
+    List<Type.ClassType> la = getElementValueArray(anno, element, Type.ClassType.class);
+    return CollectionsPlume.<Type.ClassType, @CanonicalName Name>mapList(
+        (Type.ClassType classType) -> classType.asElement().getQualifiedName(), la);
+  }
+
+  // **********************************************************************
+  // Annotation values: other methods (e.g., testing and transforming)
+  // **********************************************************************
+
+  /**
+   * Returns true if the two annotations have the same elements (fields). The arguments {@code am1}
+   * and {@code am2} must be the same type of annotation.
+   *
+   * @param am1 the first AnnotationMirror to compare
+   * @param am2 the second AnnotationMirror to compare
+   * @return true if if the two annotations have the same elements (fields)
+   */
+  @EqualsMethod
+  public static boolean sameElementValues(AnnotationMirror am1, AnnotationMirror am2) {
+    if (am1 == am2) {
+      return true;
+    }
+
+    Map<? extends ExecutableElement, ? extends AnnotationValue> vals1 = am1.getElementValues();
+    Map<? extends ExecutableElement, ? extends AnnotationValue> vals2 = am2.getElementValues();
+    for (ExecutableElement meth :
+        ElementFilter.methodsIn(am1.getAnnotationType().asElement().getEnclosedElements())) {
+      AnnotationValue aval1 = vals1.get(meth);
+      AnnotationValue aval2 = vals2.get(meth);
+      @SuppressWarnings("interning:not.interned") // optimization via equality test
+      boolean identical = aval1 == aval2;
+      if (identical) {
+        // Handles when both aval1 and aval2 are null, and maybe other cases too.
+        continue;
+      }
+      if (aval1 == null) {
+        aval1 = meth.getDefaultValue();
+      }
+      if (aval2 == null) {
+        aval2 = meth.getDefaultValue();
+      }
+      if (!sameAnnotationValue(aval1, aval2)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Return true iff the two AnnotationValue objects are the same. Use this instead of
+   * CheckerFrameworkAnnotationValue.equals, which wouldn't get called if the receiver is some
+   * AnnotationValue other than CheckerFrameworkAnnotationValue.
+   *
+   * @param av1 the first AnnotationValue to compare
+   * @param av2 the second AnnotationValue to compare
+   * @return true if if the two annotation values are the same
+   */
+  public static boolean sameAnnotationValue(AnnotationValue av1, AnnotationValue av2) {
+    return compareAnnotationValue(av1, av2) == 0;
+  }
+
+  /**
+   * Returns true if an AnnotationValue list contains the given value.
+   *
+   * <p>Using this method is slightly cheaper than creating a new {@code List<String>} just for the
+   * purpose of testing containment within it.
+   *
+   * @param avList an AnnotationValue that is null or a list of Strings
+   * @param s a string
+   * @return true if {@code av} contains {@code s}
+   */
+  public static boolean annotationValueContains(@Nullable AnnotationValue avList, String s) {
+    if (avList == null) {
+      return false;
+    }
+    @SuppressWarnings("unchecked")
+    List<? extends AnnotationValue> list = (List<? extends AnnotationValue>) avList.getValue();
+    return annotationValueContains(list, s);
+  }
+
+  /**
+   * Returns true if an AnnotationValue list contains the given value.
+   *
+   * <p>Using this method is slightly cheaper than creating a new {@code List<String>} just for the
+   * purpose of testing containment within it.
+   *
+   * @param avList a list of Strings (as {@code AnnotationValue}s)
+   * @param s a string
+   * @return true if {@code av} contains {@code s}
+   */
+  public static boolean annotationValueContains(List<? extends AnnotationValue> avList, String s) {
+    for (AnnotationValue av : avList) {
+      if (av.getValue().equals(s)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the given
+   * string.
+   *
+   * <p>Using this method is slightly cheaper than creating a new {@code List} just for the purpose
+   * of testing containment within it.
+   *
+   * @param avList an AnnotationValue that is null or a list
+   * @param s a string
+   * @return true if {@code av} contains {@code s}
+   */
+  public static boolean annotationValueContainsToString(
+      @Nullable AnnotationValue avList, String s) {
+    if (avList == null) {
+      return false;
+    }
+    @SuppressWarnings("unchecked")
+    List<? extends AnnotationValue> list = (List<? extends AnnotationValue>) avList.getValue();
+    return annotationValueContainsToString(list, s);
+  }
+
+  /**
+   * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the given
+   * string.
+   *
+   * <p>Using this method is slightly cheaper than creating a new {@code List} just for the purpose
+   * of testing containment within it.
+   *
+   * @param avList a list of Strings (as {@code AnnotationValue}s)
+   * @param s a string
+   * @return true if {@code av} contains {@code s}
+   */
+  public static boolean annotationValueContainsToString(
+      List<? extends AnnotationValue> avList, String s) {
+    for (AnnotationValue av : avList) {
+      if (av.getValue().toString().equals(s)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Converts an annotation value to a list.
+   *
+   * <p>To test containment, use {@link #annotationValueContains(AnnotationValue, String)} or {@link
+   * #annotationValueContainsToString(AnnotationValue, String)}.
+   *
+   * @param avList an AnnotationValue that is null or a list of Strings
+   * @param expectedType the component type of the argument and of the return type, an enum
+   * @param <T> the class of the type
+   * @return the annotation value, converted to a list
+   */
+  public static <T> List<T> annotationValueToList(AnnotationValue avList, Class<T> expectedType) {
+    @SuppressWarnings("unchecked")
+    List<? extends AnnotationValue> list = (List<? extends AnnotationValue>) avList.getValue();
+    return annotationValueToList(list, expectedType);
+  }
+
+  /**
+   * Converts an annotation value to a list.
+   *
+   * <p>To test containment, use {@link #annotationValueContains(List, String)} or {@link
+   * #annotationValueContainsToString(List, String)}.
+   *
+   * @param avList a list of Strings (as {@code AnnotationValue}s)
+   * @param expectedType the component type of the argument and of the return type, an enum
+   * @param <T> the class of the type
+   * @return the annotation value, converted to a list
+   */
+  public static <T> List<T> annotationValueToList(
+      List<? extends AnnotationValue> avList, Class<T> expectedType) {
+    List<T> result = new ArrayList<>(avList.size());
+    for (AnnotationValue a : avList) {
+      try {
+        result.add(expectedType.cast(a.getValue()));
+      } catch (Throwable t) {
+        String err1 = String.format("annotationValueToList(%s, %s)", avList, expectedType);
+        String err2 =
+            String.format(
+                "a=%s [%s]%n  a.getValue()=%s [%s]",
+                a, a.getClass(), a.getValue(), a.getValue().getClass());
+        throw new BugInCF(err1 + " " + err2, t);
+      }
+    }
+    return result;
+  }
+
+  // **********************************************************************
+  // Other methods
+  // **********************************************************************
+
+  // The Javadoc doesn't use @link because framework is a different project than this one
+  // (javacutil).
+  /**
+   * Update a map, to add {@code newQual} to the set that {@code key} maps to. The mapped-to element
+   * is an unmodifiable set.
+   *
+   * <p>See
+   * org.checkerframework.framework.type.QualifierHierarchy#updateMappingToMutableSet(QualifierHierarchy,
+   * Map, Object, AnnotationMirror).
+   *
+   * @param map the map to update
+   * @param key the key whose value to update
+   * @param newQual the element to add to the given key's value
+   * @param <T> the key type
+   */
+  public static <T extends @NonNull Object> void updateMappingToImmutableSet(
+      Map<T, Set<AnnotationMirror>> map, T key, Set<AnnotationMirror> newQual) {
+
+    Set<AnnotationMirror> result = AnnotationUtils.createAnnotationSet();
+    // TODO: if T is also an AnnotationMirror, should we use areSame?
+    if (!map.containsKey(key)) {
+      result.addAll(newQual);
+    } else {
+      result.addAll(map.get(key));
+      result.addAll(newQual);
+    }
+    map.put(key, Collections.unmodifiableSet(result));
+  }
+
+  /**
+   * Returns the annotations explicitly written on a constructor result. Callers should check that
+   * {@code constructorDeclaration} is in fact a declaration of a constructor.
+   *
+   * @param constructorDeclaration declaration tree of constructor
+   * @return set of annotations explicit on the resulting type of the constructor
+   */
+  public static Set<AnnotationMirror> getExplicitAnnotationsOnConstructorResult(
+      MethodTree constructorDeclaration) {
+    Set<AnnotationMirror> annotationSet = AnnotationUtils.createAnnotationSet();
+    ModifiersTree modifiersTree = constructorDeclaration.getModifiers();
+    if (modifiersTree != null) {
+      List<? extends AnnotationTree> annotationTrees = modifiersTree.getAnnotations();
+      annotationSet.addAll(TreeUtils.annotationsFromTypeAnnotationTrees(annotationTrees));
+    }
+    return annotationSet;
+  }
+
+  /**
+   * Returns true if anno is a declaration annotation. In other words, returns true if anno cannot
+   * be written on uses of types.
+   *
+   * @param anno the AnnotationMirror
+   * @return true if anno is a declaration annotation
+   */
+  public static boolean isDeclarationAnnotation(AnnotationMirror anno) {
+    TypeElement elem = (TypeElement) anno.getAnnotationType().asElement();
+    Target t = elem.getAnnotation(Target.class);
+    if (t == null) {
+      return true;
+    }
+
+    for (ElementType elementType : t.value()) {
+      if (elementType == ElementType.TYPE_USE) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns true if the given array contains {@link ElementType#TYPE_USE}, false otherwise.
+   *
+   * @param elements an array of {@link ElementType} values
+   * @param cls the annotation class being tested; used for diagnostic messages only
+   * @return true iff the give array contains {@link ElementType#TYPE_USE}
+   * @throws RuntimeException if the array contains both {@link ElementType#TYPE_USE} and something
+   *     besides {@link ElementType#TYPE_PARAMETER}
+   */
+  public static boolean hasTypeQualifierElementTypes(ElementType[] elements, Class<?> cls) {
+    // True if the array contains TYPE_USE
+    boolean hasTypeUse = false;
+    // Non-null if the array contains an element other than TYPE_USE or TYPE_PARAMETER
+    ElementType otherElementType = null;
+
+    for (ElementType element : elements) {
+      if (element == ElementType.TYPE_USE) {
+        hasTypeUse = true;
+      } else if (element != ElementType.TYPE_PARAMETER) {
+        otherElementType = element;
+      }
+      if (hasTypeUse && otherElementType != null) {
+        throw new BugInCF(
+            "@Target meta-annotation should not contain both TYPE_USE and "
+                + otherElementType
+                + ", for annotation "
+                + cls.getName());
+      }
+    }
+
+    return hasTypeUse;
+  }
+
+  /**
+   * Returns a string representation of the annotation mirrors, using simple (not fully-qualified)
+   * names.
+   *
+   * @param annos annotations to format
+   * @return the string representation, using simple (not fully-qualified) names
+   */
+  @SideEffectFree
+  public static String toStringSimple(Set<AnnotationMirror> annos) {
+    DefaultAnnotationFormatter defaultAnnotationFormatter = new DefaultAnnotationFormatter();
+    StringJoiner result = new StringJoiner(" ");
+    for (AnnotationMirror am : annos) {
+      result.add(defaultAnnotationFormatter.formatAnnotationMirror(am));
+    }
+    return result.toString();
+  }
+
+  /**
+   * Converts an AnnotationMirror to a Class. Throws an exception if it is not able to do so.
+   *
+   * @param am an AnnotationMirror
+   * @return the Class corresponding to the given AnnotationMirror
+   */
+  public static Class<?> annotationMirrorToClass(AnnotationMirror am) {
+    try {
+      return Class.forName(AnnotationUtils.annotationBinaryName(am));
+    } catch (ClassNotFoundException e) {
+      throw new BugInCF(e);
+    }
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java b/javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java
new file mode 100644
index 0000000..20e72ec
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java
@@ -0,0 +1,44 @@
+package org.checkerframework.javacutil;
+
+import com.sun.source.tree.Tree;
+import java.lang.annotation.Annotation;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/** An AnnotationProvider that is independent of any type hierarchy. */
+public class BasicAnnotationProvider implements AnnotationProvider {
+
+  /**
+   * Returns the AnnotationMirror, of the given class, used to annotate the element. Returns null if
+   * no such annotation exists.
+   */
+  @Override
+  public @Nullable AnnotationMirror getDeclAnnotation(
+      Element elt, Class<? extends Annotation> anno) {
+    List<? extends AnnotationMirror> annotationMirrors = elt.getAnnotationMirrors();
+
+    // Then look at the real annotations.
+    for (AnnotationMirror am : annotationMirrors) {
+      @SuppressWarnings("deprecation") // method intended for use by the hierarchy
+      boolean found = AnnotationUtils.areSameByClass(am, anno);
+      if (found) {
+        return am;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * <p>This implementation always returns null, because it has no access to any type hierarchy.
+   */
+  @Override
+  public @Nullable AnnotationMirror getAnnotationMirror(
+      Tree tree, Class<? extends Annotation> target) {
+    return null;
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/BasicTypeProcessor.java b/javacutil/src/main/java/org/checkerframework/javacutil/BasicTypeProcessor.java
new file mode 100644
index 0000000..eed4d4c
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/BasicTypeProcessor.java
@@ -0,0 +1,43 @@
+package org.checkerframework.javacutil;
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
+import javax.lang.model.element.TypeElement;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+
+/**
+ * Process the types in an AST in a trivial manner, with hooks for derived classes to actually do
+ * something.
+ */
+public abstract class BasicTypeProcessor extends AbstractTypeProcessor {
+  /** The source tree that's being scanned. */
+  protected @MonotonicNonNull CompilationUnitTree currentRoot;
+
+  /**
+   * Create a TreePathScanner at the given root.
+   *
+   * @param root where to start the tree traversal
+   * @return a TreePathScanner at the given root
+   */
+  protected abstract TreePathScanner<?, ?> createTreePathScanner(CompilationUnitTree root);
+
+  /** Visit the tree path for the type element. */
+  @Override
+  public void typeProcess(TypeElement e, TreePath p) {
+    currentRoot = p.getCompilationUnit();
+
+    TreePathScanner<?, ?> scanner = null;
+    try {
+      scanner = createTreePathScanner(currentRoot);
+      scanner.scan(p, null);
+    } catch (Throwable t) {
+      System.err.println(
+          "BasicTypeProcessor.typeProcess: unexpected Throwable ("
+              + t.getClass().getSimpleName()
+              + ")  when processing "
+              + currentRoot.getSourceFile().getName()
+              + (t.getMessage() != null ? "; message: " + t.getMessage() : ""));
+    }
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/BugInCF.java b/javacutil/src/main/java/org/checkerframework/javacutil/BugInCF.java
new file mode 100644
index 0000000..ac1b63c
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/BugInCF.java
@@ -0,0 +1,73 @@
+package org.checkerframework.javacutil;
+
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Exception type indicating a bug in the framework. To indicate a bug in a checker implementation,
+ * use {@link TypeSystemError}.
+ */
+@SuppressWarnings("serial")
+public class BugInCF extends RuntimeException {
+
+  /**
+   * Constructs a new BugInCF with the specified detail message and no cause (use this at the root
+   * cause).
+   *
+   * @param message the detail message
+   */
+  public BugInCF(String message) {
+    this(message, new Throwable());
+  }
+
+  /**
+   * Constructs a new BugInCF with a detail message composed from the given arguments, and with no
+   * cause (use the current callstack as the root cause).
+   *
+   * @param fmt the format string
+   * @param args the arguments for the format string
+   */
+  @FormatMethod
+  public BugInCF(String fmt, @Nullable Object... args) {
+    this(String.format(fmt, args), new Throwable());
+  }
+
+  /**
+   * Constructs a new BugInCF with the specified cause.
+   *
+   * @param cause the cause; its detail message will be used and must be non-null
+   */
+  @SuppressWarnings("nullness")
+  public BugInCF(Throwable cause) {
+    this(cause.getMessage(), new Throwable());
+  }
+
+  /**
+   * Constructs a new BugInCF with the specified cause and with a detail message composed from the
+   * given arguments.
+   *
+   * @param cause the cause
+   * @param fmt the format string
+   * @param args the arguments for the format string
+   */
+  @FormatMethod
+  public BugInCF(Throwable cause, String fmt, @Nullable Object... args) {
+    this(String.format(fmt, args), cause);
+  }
+
+  /**
+   * Constructs a new BugInCF with the specified detail message and cause.
+   *
+   * @param message the detail message
+   * @param cause the cause
+   */
+  public BugInCF(String message, Throwable cause) {
+    super(message, cause);
+    if (message == null) {
+      throw new BugInCF("Must have a detail message.");
+    }
+    if (cause == null) {
+      throw new BugInCF("Must have a cause throwable.");
+    }
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/CollectionUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/CollectionUtils.java
new file mode 100644
index 0000000..b763da8
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/CollectionUtils.java
@@ -0,0 +1,26 @@
+package org.checkerframework.javacutil;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/** Utility methods related to Java Collections. */
+public class CollectionUtils {
+
+  /**
+   * Creates a LRU cache.
+   *
+   * @param size size of the cache
+   * @return a new cache with the provided size
+   */
+  public static <K, V> Map<K, V> createLRUCache(final int size) {
+    return new LinkedHashMap<K, V>(size, .75F, true) {
+
+      private static final long serialVersionUID = 5261489276168775084L;
+
+      @Override
+      protected boolean removeEldestEntry(Map.Entry<K, V> entry) {
+        return size() > size;
+      }
+    };
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/DefaultAnnotationFormatter.java b/javacutil/src/main/java/org/checkerframework/javacutil/DefaultAnnotationFormatter.java
new file mode 100644
index 0000000..2fb85e3
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/DefaultAnnotationFormatter.java
@@ -0,0 +1,154 @@
+package org.checkerframework.framework.util;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+import org.checkerframework.framework.qual.InvisibleQualifier;
+import org.checkerframework.javacutil.BugInCF;
+
+/** A utility for converting AnnotationMirrors to Strings. It omits full package names. */
+public class DefaultAnnotationFormatter implements AnnotationFormatter {
+
+  /**
+   * Returns true if, by default, anno should not be printed.
+   *
+   * @see org.checkerframework.framework.qual.InvisibleQualifier
+   * @return true if anno's declaration was qualified by InvisibleQualifier
+   */
+  public static boolean isInvisibleQualified(AnnotationMirror anno) {
+    TypeElement annoElement = (TypeElement) anno.getAnnotationType().asElement();
+    return annoElement.getAnnotation(InvisibleQualifier.class) != null;
+  }
+
+  /**
+   * Creates a String of each annotation in annos separated by a single space character and
+   * terminated by a space character, obeying the printInvisible parameter.
+   *
+   * @param annos a collection of annotations to print
+   * @param printInvisible whether or not to print "invisible" annotation mirrors
+   * @return the list of annotations converted to a String
+   */
+  @Override
+  @SideEffectFree
+  public String formatAnnotationString(
+      Collection<? extends AnnotationMirror> annos, boolean printInvisible) {
+    StringBuilder sb = new StringBuilder();
+    for (AnnotationMirror obj : annos) {
+      if (obj == null) {
+        throw new BugInCF(
+            "AnnotatedTypeMirror.formatAnnotationString: found null AnnotationMirror");
+      }
+      if (isInvisibleQualified(obj) && !printInvisible) {
+        continue;
+      }
+      formatAnnotationMirror(obj, sb);
+      sb.append(" ");
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Returns the string representation of a single AnnotationMirror, without showing full package
+   * names.
+   *
+   * @param anno the annotation mirror to convert
+   * @return the string representation of a single AnnotationMirror, without showing full package
+   *     names
+   */
+  @Override
+  @SideEffectFree
+  public String formatAnnotationMirror(AnnotationMirror anno) {
+    StringBuilder sb = new StringBuilder();
+    formatAnnotationMirror(anno, sb);
+    return sb.toString();
+  }
+
+  /** A helper method to output a single AnnotationMirror, without showing full package names. */
+  protected void formatAnnotationMirror(AnnotationMirror am, StringBuilder sb) {
+    sb.append("@");
+    sb.append(am.getAnnotationType().asElement().getSimpleName());
+    Map<ExecutableElement, AnnotationValue> args = removeDefaultValues(am.getElementValues());
+    if (!args.isEmpty()) {
+      sb.append("(");
+      boolean oneValue = false;
+      if (args.size() == 1) {
+        Map.Entry<ExecutableElement, AnnotationValue> first = args.entrySet().iterator().next();
+        if (first.getKey().getSimpleName().contentEquals("value")) {
+          formatAnnotationMirrorArg(first.getValue(), sb);
+          oneValue = true;
+        }
+      }
+      if (!oneValue) {
+        boolean notfirst = false;
+        for (Map.Entry<ExecutableElement, AnnotationValue> arg : args.entrySet()) {
+          if (!"{}".equals(arg.getValue().toString())) {
+            if (notfirst) {
+              sb.append(", ");
+            }
+            notfirst = true;
+            sb.append(arg.getKey().getSimpleName() + "=");
+            formatAnnotationMirrorArg(arg.getValue(), sb);
+          }
+        }
+      }
+      sb.append(")");
+    }
+  }
+
+  /**
+   * Returns a new map that only has the values in {@code elementValues} that are not the same as
+   * the default value.
+   *
+   * @param elementValues a mapping of annotation element to annotation value
+   * @return a new map with only the not default default values of {@code elementValues}
+   */
+  private Map<ExecutableElement, AnnotationValue> removeDefaultValues(
+      Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues) {
+    Map<ExecutableElement, AnnotationValue> nonDefaults = new LinkedHashMap<>();
+    elementValues.forEach(
+        (element, value) -> {
+          if (element.getDefaultValue() == null
+              || !Objects.equals(value.getValue(), element.getDefaultValue().getValue())) {
+            nonDefaults.put(element, value);
+          }
+        });
+    return nonDefaults;
+  }
+
+  // A helper method to print AnnotationValues (annotation arguments), without showing full
+  // package names.
+  @SuppressWarnings("unchecked")
+  protected void formatAnnotationMirrorArg(AnnotationValue av, StringBuilder sb) {
+    Object val = av.getValue();
+    if (List.class.isAssignableFrom(val.getClass())) {
+      List<AnnotationValue> vallist = (List<AnnotationValue>) val;
+      if (vallist.size() == 1) {
+        formatAnnotationMirrorArg(vallist.get(0), sb);
+      } else {
+        sb.append('{');
+        boolean notfirst = false;
+        for (AnnotationValue nav : vallist) {
+          if (notfirst) {
+            sb.append(", ");
+          }
+          notfirst = true;
+          formatAnnotationMirrorArg(nav, sb);
+        }
+        sb.append('}');
+      }
+    } else if (VariableElement.class.isAssignableFrom(val.getClass())) {
+      VariableElement ve = (VariableElement) val;
+      sb.append(ve.getEnclosingElement().getSimpleName() + "." + ve.getSimpleName());
+    } else {
+      sb.append(av.toString());
+    }
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java
new file mode 100644
index 0000000..4c99078
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java
@@ -0,0 +1,909 @@
+package org.checkerframework.javacutil;
+
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.model.JavacTypes;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.checker.signature.qual.CanonicalName;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * Utility methods for analyzing {@code Element}s. This complements {@link Elements}, providing
+ * functionality that it does not.
+ */
+public class ElementUtils {
+
+  // Class cannot be instantiated.
+  private ElementUtils() {
+    throw new AssertionError("Class ElementUtils cannot be instantiated.");
+  }
+
+  /**
+   * Returns the innermost type element enclosing the given element. Returns the element itself if
+   * it is a type element.
+   *
+   * @param elem the enclosed element of a class
+   * @return the innermost type element, or null if no type element encloses {@code elem}
+   * @deprecated use {@link #enclosingTypeElement}
+   */
+  @Deprecated // use enclosingTypeElement
+  public static @Nullable TypeElement enclosingClass(final Element elem) {
+    return enclosingTypeElement(elem);
+  }
+
+  /**
+   * Returns the innermost type element that is, or encloses, the given element.
+   *
+   * <p>Note that in this code:
+   *
+   * <pre>{@code
+   * class Outer {
+   *   static class Inner {  }
+   * }
+   * }</pre>
+   *
+   * {@code Inner} has no enclosing type, but this method returns {@code Outer}.
+   *
+   * @param elem the enclosed element of a class
+   * @return the innermost type element (possibly the argument itself), or null if {@code elem} is
+   *     not, and is not enclosed by, a type element
+   */
+  public static @Nullable TypeElement enclosingTypeElement(final Element elem) {
+    Element result = elem;
+    while (result != null && !isTypeElement(result)) {
+      result = result.getEnclosingElement();
+    }
+    return (TypeElement) result;
+  }
+
+  /**
+   * Returns the innermost type element enclosing the given element, that is different from the
+   * element itself. By contrast, {@link #enclosingTypeElement} returns its argument if the argument
+   * is a type element.
+   *
+   * @param elem the enclosed element of a class
+   * @return the innermost type element, or null if no type element encloses {@code elem}
+   */
+  public static @Nullable TypeElement strictEnclosingTypeElement(final Element elem) {
+    Element enclosingElement = elem.getEnclosingElement();
+    if (enclosingElement == null) {
+      return null;
+    }
+
+    return enclosingTypeElement(enclosingElement);
+  }
+
+  /**
+   * Returns the top-level type element that contains {@code element}.
+   *
+   * @param element the element whose enclosing tye element to find
+   * @return a type element containing {@code element} that isn't contained in another class
+   */
+  public static TypeElement toplevelEnclosingTypeElement(Element element) {
+    TypeElement result = enclosingTypeElement(element);
+    if (result == null) {
+      return (TypeElement) element;
+    }
+
+    TypeElement enclosing = strictEnclosingTypeElement(result);
+    while (enclosing != null) {
+      result = enclosing;
+      enclosing = strictEnclosingTypeElement(enclosing);
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns the binary name of the class enclosing {@code executableElement}.
+   *
+   * @param executableElement the ExecutableElement
+   * @return the binary name of the class enclosing {@code executableElement}
+   */
+  public static @BinaryName String getEnclosingClassName(ExecutableElement executableElement) {
+    return getBinaryName(((MethodSymbol) executableElement).enclClass());
+  }
+
+  /**
+   * Returns the binary name of the class enclosing {@code variableElement}.
+   *
+   * @param variableElement the VariableElement
+   * @return the binary name of the class enclosing {@code variableElement}
+   */
+  public static @BinaryName String getEnclosingClassName(VariableElement variableElement) {
+    TypeElement enclosingType = enclosingTypeElement(variableElement);
+    if (enclosingType == null) {
+      throw new BugInCF("enclosingTypeElement(%s) is null", variableElement);
+    }
+    return getBinaryName(enclosingType);
+  }
+
+  /**
+   * Returns the innermost package element enclosing the given element. The same effect as {@link
+   * javax.lang.model.util.Elements#getPackageOf(Element)}. Returns the element itself if it is a
+   * package.
+   *
+   * @param elem the enclosed element of a package
+   * @return the innermost package element
+   */
+  public static PackageElement enclosingPackage(final Element elem) {
+    Element result = elem;
+    while (result != null && result.getKind() != ElementKind.PACKAGE) {
+      @Nullable Element encl = result.getEnclosingElement();
+      result = encl;
+    }
+    return (PackageElement) result;
+  }
+
+  /**
+   * Returns the "parent" package element for the given package element. For package "A.B" it gives
+   * "A". For package "A" it gives the default package. For the default package it returns null.
+   *
+   * <p>Note that packages are not enclosed within each other, we have to manually climb the
+   * namespaces. Calling "enclosingPackage" on a package element returns the package element itself
+   * again.
+   *
+   * @param elem the package to start from
+   * @return the parent package element or {@code null}
+   */
+  public static @Nullable PackageElement parentPackage(
+      final PackageElement elem, final Elements e) {
+    // The following might do the same thing:
+    //   ((Symbol) elt).owner;
+    // TODO: verify and see whether the change is worth it.
+    String fqnstart = elem.getQualifiedName().toString();
+    String fqn = fqnstart;
+    if (fqn != null && !fqn.isEmpty()) {
+      int dotPos = fqn.lastIndexOf('.');
+      if (dotPos != -1) {
+        return e.getPackageElement(fqn.substring(0, dotPos));
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns true if the element is a static element: whether it is a static field, static method,
+   * or static class.
+   *
+   * @return true if element is static
+   */
+  public static boolean isStatic(Element element) {
+    return element.getModifiers().contains(Modifier.STATIC);
+  }
+
+  /**
+   * Returns true if the element is a final element: a final field, final method, or final class.
+   *
+   * @return true if the element is final
+   */
+  public static boolean isFinal(Element element) {
+    return element.getModifiers().contains(Modifier.FINAL);
+  }
+
+  /**
+   * Returns true if the element is a effectively final element.
+   *
+   * @return true if the element is effectively final
+   */
+  public static boolean isEffectivelyFinal(Element element) {
+    Symbol sym = (Symbol) element;
+    if (sym.getEnclosingElement().getKind() == ElementKind.METHOD
+        && (sym.getEnclosingElement().flags() & Flags.ABSTRACT) != 0) {
+      return true;
+    }
+    return (sym.flags() & (Flags.FINAL | Flags.EFFECTIVELY_FINAL)) != 0;
+  }
+
+  /**
+   * Returns the {@code TypeMirror} for usage of Element as a value. It returns the return type of a
+   * method element, the class type of a constructor, or simply the type mirror of the element
+   * itself.
+   *
+   * @param element the element whose type to obtain
+   * @return the type for the element used as a value
+   */
+  @SuppressWarnings("nullness:dereference.of.nullable") // a constructor has an enclosing class
+  public static TypeMirror getType(Element element) {
+    if (element.getKind() == ElementKind.METHOD) {
+      return ((ExecutableElement) element).getReturnType();
+    } else if (element.getKind() == ElementKind.CONSTRUCTOR) {
+      return enclosingClass(element).asType();
+    } else {
+      return element.asType();
+    }
+  }
+
+  /**
+   * Returns the qualified name of the innermost class enclosing the provided {@code Element}.
+   *
+   * @param element an element enclosed by a class, or a {@code TypeElement}
+   * @return the qualified {@code Name} of the innermost class enclosing the element
+   */
+  public static @Nullable Name getQualifiedClassName(Element element) {
+    if (element.getKind() == ElementKind.PACKAGE) {
+      PackageElement elem = (PackageElement) element;
+      return elem.getQualifiedName();
+    }
+
+    TypeElement elem = enclosingClass(element);
+    if (elem == null) {
+      return null;
+    }
+
+    return elem.getQualifiedName();
+  }
+
+  /**
+   * Returns a verbose name that identifies the element.
+   *
+   * @param elt the element whose name to obtain
+   * @return the qualified name of the given element
+   */
+  public static String getQualifiedName(Element elt) {
+    if (elt.getKind() == ElementKind.PACKAGE || isTypeElement(elt)) {
+      Name n = getQualifiedClassName(elt);
+      if (n == null) {
+        return "Unexpected element: " + elt;
+      }
+      return n.toString();
+    } else {
+      return getQualifiedName(elt.getEnclosingElement()) + "." + elt;
+    }
+  }
+
+  /**
+   * Returns the binary name of the given type.
+   *
+   * @param te a type
+   * @return the binary name of the type
+   */
+  @SuppressWarnings("signature:return") // string manipulation
+  public static @BinaryName String getBinaryName(TypeElement te) {
+    Element enclosing = te.getEnclosingElement();
+    String simpleName = te.getSimpleName().toString();
+    if (enclosing == null) { // is this possible?
+      return simpleName;
+    }
+    if (ElementUtils.isTypeElement(enclosing)) {
+      return getBinaryName((TypeElement) enclosing) + "$" + simpleName;
+    } else if (enclosing.getKind() == ElementKind.PACKAGE) {
+      PackageElement pe = (PackageElement) enclosing;
+      if (pe.isUnnamed()) {
+        return simpleName;
+      } else {
+        return pe.getQualifiedName() + "." + simpleName;
+      }
+    } else {
+      // This case occurs for anonymous inner classes. Fall back to the flatname method.
+      return ((ClassSymbol) te).flatName().toString();
+    }
+  }
+
+  /**
+   * Returns the canonical representation of the method declaration, which contains simple names of
+   * the types only.
+   *
+   * @param element a method declaration
+   * @return the simple name of the method, followed by the simple names of the formal parameter
+   *     types
+   */
+  public static String getSimpleSignature(ExecutableElement element) {
+    // note: constructor simple name is <init>
+    StringJoiner sj = new StringJoiner(",", element.getSimpleName() + "(", ")");
+    for (Iterator<? extends VariableElement> i = element.getParameters().iterator();
+        i.hasNext(); ) {
+      sj.add(TypesUtils.simpleTypeName(i.next().asType()));
+    }
+    return sj.toString();
+  }
+
+  /**
+   * Returns a user-friendly name for the given method. Does not return {@code "<init>"} or {@code
+   * "<clinit>"} as ExecutableElement.getSimpleName() does.
+   *
+   * @param element a method declaration
+   * @return a user-friendly name for the method
+   */
+  public static CharSequence getSimpleNameOrDescription(ExecutableElement element) {
+    Name result = element.getSimpleName();
+    switch (result.toString()) {
+      case "<init>":
+        return element.getEnclosingElement().getSimpleName();
+      case "<clinit>":
+        return "class initializer";
+      default:
+        return result;
+    }
+  }
+
+  /**
+   * Check if the element is an element for 'java.lang.Object'
+   *
+   * @param element the type element
+   * @return true iff the element is java.lang.Object element
+   */
+  public static boolean isObject(TypeElement element) {
+    return element.getQualifiedName().contentEquals("java.lang.Object");
+  }
+
+  /**
+   * Check if the element is an element for 'java.lang.String'
+   *
+   * @param element the type element
+   * @return true iff the element is java.lang.String element
+   */
+  public static boolean isString(TypeElement element) {
+    return element.getQualifiedName().contentEquals("java.lang.String");
+  }
+
+  /**
+   * Returns true if the element is a reference to a compile-time constant.
+   *
+   * @param elt an element
+   * @return true if the element is a reference to a compile-time constant
+   */
+  public static boolean isCompileTimeConstant(Element elt) {
+    return elt != null
+        && (elt.getKind() == ElementKind.FIELD || elt.getKind() == ElementKind.LOCAL_VARIABLE)
+        && ((VariableElement) elt).getConstantValue() != null;
+  }
+
+  /**
+   * Checks whether a given element came from a source file.
+   *
+   * <p>By contrast, {@link ElementUtils#isElementFromByteCode(Element)} returns true if there is a
+   * classfile for the given element, even if there is also a source file.
+   *
+   * @param element the element to check, or null
+   * @return true if a source file containing the element is being compiled
+   */
+  public static boolean isElementFromSourceCode(@Nullable Element element) {
+    if (element == null) {
+      return false;
+    }
+    TypeElement enclosingClass = enclosingClass(element);
+    if (enclosingClass == null) {
+      throw new BugInCF("enclosingClass(%s) is null", element);
+    }
+    return isElementFromSourceCodeImpl((Symbol.ClassSymbol) enclosingClass);
+  }
+
+  /**
+   * Checks whether a given ClassSymbol came from a source file.
+   *
+   * <p>By contrast, {@link ElementUtils#isElementFromByteCode(Element)} returns true if there is a
+   * classfile for the given element, even if there is also a source file.
+   *
+   * @param symbol the class to check
+   * @return true if a source file containing the class is being compiled
+   */
+  private static boolean isElementFromSourceCodeImpl(Symbol.ClassSymbol symbol) {
+    // This is a bit of a hack to avoid treating JDK as source files. JDK files' toUri() method
+    // returns just the name of the file (e.g. "Object.java"), but any file actually being
+    // compiled returns a file URI to the source file.
+    return symbol.sourcefile != null
+        && symbol.sourcefile.getKind() == JavaFileObject.Kind.SOURCE
+        && symbol.sourcefile.toUri().toString().startsWith("file:");
+  }
+
+  /**
+   * Returns true if the element is declared in ByteCode. Always return false if elt is a package.
+   *
+   * @param elt some element
+   * @return true if the element is declared in ByteCode
+   */
+  public static boolean isElementFromByteCode(@Nullable Element elt) {
+    if (elt == null) {
+      return false;
+    }
+
+    if (elt instanceof Symbol.ClassSymbol) {
+      Symbol.ClassSymbol clss = (Symbol.ClassSymbol) elt;
+      if (null != clss.classfile) {
+        // The class file could be a .java file
+        return clss.classfile.getKind() == Kind.CLASS;
+      } else {
+        return elt.asType().getKind().isPrimitive();
+      }
+    }
+    return isElementFromByteCode(elt.getEnclosingElement());
+  }
+
+  /**
+   * Returns the path to the source file containing {@code element}, which must be from source code.
+   *
+   * @param element the type element to look at
+   * @return path to the source file containing {@code element}
+   */
+  public static String getSourceFilePath(TypeElement element) {
+    return ((ClassSymbol) element).sourcefile.toUri().getPath();
+  }
+
+  /**
+   * Returns the field of the class or {@code null} if not found.
+   *
+   * @param type TypeElement to search
+   * @param name name of a field
+   * @return The VariableElement for the field if it was found, null otherwise
+   */
+  public static @Nullable VariableElement findFieldInType(TypeElement type, String name) {
+    for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) {
+      if (field.getSimpleName().contentEquals(name)) {
+        return field;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns the elements of the fields whose simple names are {@code names} and are declared in
+   * {@code type}.
+   *
+   * <p>If a field isn't declared in {@code type}, its element isn't included in the returned set.
+   * If none of the fields is declared in {@code type}, the empty set is returned.
+   *
+   * @param type where to look for fields
+   * @param names simple names of fields that might be declared in {@code type}
+   * @return the elements of the fields whose simple names are {@code names} and are declared in
+   *     {@code type}
+   */
+  public static Set<VariableElement> findFieldsInType(TypeElement type, Collection<String> names) {
+    Set<VariableElement> results = new HashSet<>();
+    for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) {
+      if (names.contains(field.getSimpleName().toString())) {
+        results.add(field);
+      }
+    }
+    return results;
+  }
+
+  /**
+   * Returns non-private field elements, and side-effects {@code names} to remove them. For every
+   * field name in {@code names} that is declared in {@code type} or a supertype, add its element to
+   * the returned set and remove it from {@code names}.
+   *
+   * <p>When this routine returns, the combination of the return value and {@code names} has the
+   * same cardinality, and represents the same fields, as {@code names} did when the method was
+   * called.
+   *
+   * @param type where to look for fields
+   * @param names simple names of fields that might be declared in {@code type} or a supertype
+   *     (Names that are found are removed from this list.)
+   * @return the {@code VariableElement}s for non-private fields that are declared in {@code type}
+   *     whose simple names were in {@code names} when the method was called.
+   */
+  public static Set<VariableElement> findFieldsInTypeOrSuperType(
+      TypeMirror type, Collection<String> names) {
+    int origCardinality = names.size();
+    Set<VariableElement> elements = new HashSet<>();
+    findFieldsInTypeOrSuperType(type, names, elements);
+    // Since names may contain duplicates, I don't trust the claim in the documentation about
+    // cardinality.  (Does any code depend on the invariant, though?)
+    if (origCardinality != names.size() + elements.size()) {
+      throw new BugInCF("Bad sizes: %d != %d + %d", origCardinality, names.size(), elements.size());
+    }
+    return elements;
+  }
+
+  /**
+   * Side-effects both {@code foundFields} (which starts empty) and {@code notFound}, conceptually
+   * moving elements from {@code notFound} to {@code foundFields}.
+   */
+  private static void findFieldsInTypeOrSuperType(
+      TypeMirror type, Collection<String> notFound, Set<VariableElement> foundFields) {
+    if (TypesUtils.isObject(type)) {
+      return;
+    }
+    TypeElement elt = TypesUtils.getTypeElement(type);
+    assert elt != null : "@AssumeAssertion(nullness): assumption";
+    Set<VariableElement> fieldElts = findFieldsInType(elt, notFound);
+    for (VariableElement field : new HashSet<VariableElement>(fieldElts)) {
+      if (!field.getModifiers().contains(Modifier.PRIVATE)) {
+        notFound.remove(field.getSimpleName().toString());
+      } else {
+        fieldElts.remove(field);
+      }
+    }
+    foundFields.addAll(fieldElts);
+
+    if (!notFound.isEmpty()) {
+      findFieldsInTypeOrSuperType(elt.getSuperclass(), notFound, foundFields);
+    }
+  }
+
+  /**
+   * Returns true if {@code element} is "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError".
+   *
+   * @param element the element to test
+   * @return true if {@code element} is "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError"
+   */
+  public static boolean isError(Element element) {
+    return element.getClass().getName()
+        == "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError"; // interned
+  }
+
+  /**
+   * Does the given element need a receiver for accesses? For example, an access to a local variable
+   * does not require a receiver.
+   *
+   * @param element the element to test
+   * @return whether the element requires a receiver for accesses
+   */
+  public static boolean hasReceiver(Element element) {
+    if (element.getKind() == ElementKind.CONSTRUCTOR) {
+      // The enclosing element of a constructor is the class it creates.
+      // A constructor can only have a receiver if the class it creates has an outer type.
+      TypeMirror t = element.getEnclosingElement().asType();
+      return TypesUtils.hasEnclosingType(t);
+    } else if (element.getKind() == ElementKind.FIELD) {
+      if (ElementUtils.isStatic(element)
+          // Artificial fields in interfaces are not marked as static, so check that
+          // the field is not declared in an interface.
+          || element.getEnclosingElement().getKind().isInterface()) {
+        return false;
+      } else {
+        // In constructors, the element for "this" is a non-static field, but that field
+        // does not have a receiver.
+        return !element.getSimpleName().contentEquals("this");
+      }
+    }
+    return element.getKind() == ElementKind.METHOD && !ElementUtils.isStatic(element);
+  }
+
+  /**
+   * Returns a type's superclass, or null if it does not have a superclass (it is object or an
+   * interface, or the superclass is not on the classpath).
+   *
+   * @param typeElt a type element
+   * @return the superclass of {@code typeElt}
+   */
+  public static @Nullable TypeElement getSuperClass(TypeElement typeElt) {
+    TypeMirror superTypeMirror;
+    try {
+      superTypeMirror = typeElt.getSuperclass();
+    } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) {
+      // Looking up a supertype failed. This sometimes happens
+      // when transitive dependencies are not on the classpath.
+      // As javac didn't complain, let's also not complain.
+      return null;
+    }
+
+    if (superTypeMirror == null || superTypeMirror.getKind() == TypeKind.NONE) {
+      return null;
+    } else {
+      return (TypeElement) ((DeclaredType) superTypeMirror).asElement();
+    }
+  }
+
+  /**
+   * Determine all type elements for the supertypes of the given type element. This is the
+   * transitive closure of the extends and implements clauses.
+   *
+   * <p>TODO: can we learn from the implementation of
+   * com.sun.tools.javac.model.JavacElements.getAllMembers(TypeElement)?
+   *
+   * @param type the type whose supertypes to return
+   * @param elements the Element utilities
+   * @return supertypes of {@code type}
+   */
+  public static List<TypeElement> getSuperTypes(TypeElement type, Elements elements) {
+
+    if (type == null) {
+      return Collections.emptyList();
+    }
+
+    List<TypeElement> superelems = new ArrayList<>();
+
+    // Set up a stack containing type, which is our starting point.
+    Deque<TypeElement> stack = new ArrayDeque<>();
+    stack.push(type);
+
+    while (!stack.isEmpty()) {
+      TypeElement current = stack.pop();
+
+      // For each direct supertype of the current type element, if it
+      // hasn't already been visited, push it onto the stack and
+      // add it to our superelems set.
+      TypeElement supercls = ElementUtils.getSuperClass(current);
+      if (supercls != null) {
+        if (!superelems.contains(supercls)) {
+          stack.push(supercls);
+          superelems.add(supercls);
+        }
+      }
+
+      for (TypeMirror supertypeitf : current.getInterfaces()) {
+        TypeElement superitf = (TypeElement) ((DeclaredType) supertypeitf).asElement();
+        if (!superelems.contains(superitf)) {
+          stack.push(superitf);
+          superelems.add(superitf);
+        }
+      }
+    }
+
+    // Include java.lang.Object as implicit superclass for all classes and interfaces.
+    TypeElement jlobject = elements.getTypeElement("java.lang.Object");
+    if (!superelems.contains(jlobject)) {
+      superelems.add(jlobject);
+    }
+
+    return Collections.unmodifiableList(superelems);
+  }
+
+  /**
+   * Return all fields declared in the given type or any superclass/interface.
+   *
+   * <p>TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) instead of
+   * our own getSuperTypes?
+   *
+   * @param type the type whose fields to return
+   * @param elements the Element utilities
+   * @return fields of {@code type}
+   */
+  public static List<VariableElement> getAllFieldsIn(TypeElement type, Elements elements) {
+    List<VariableElement> fields =
+        new ArrayList<>(ElementFilter.fieldsIn(type.getEnclosedElements()));
+    List<TypeElement> alltypes = getSuperTypes(type, elements);
+    for (TypeElement atype : alltypes) {
+      fields.addAll(ElementFilter.fieldsIn(atype.getEnclosedElements()));
+    }
+    return Collections.unmodifiableList(fields);
+  }
+
+  /**
+   * Return all methods declared in the given type or any superclass/interface. Note that no
+   * constructors will be returned.
+   *
+   * <p>TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) instead of
+   * our own getSuperTypes?
+   *
+   * @param type the type whose methods to return
+   * @param elements the Element utilities
+   * @return methods of {@code type}
+   */
+  public static List<ExecutableElement> getAllMethodsIn(TypeElement type, Elements elements) {
+    List<ExecutableElement> meths =
+        new ArrayList<>(ElementFilter.methodsIn(type.getEnclosedElements()));
+
+    List<TypeElement> alltypes = getSuperTypes(type, elements);
+    for (TypeElement atype : alltypes) {
+      meths.addAll(ElementFilter.methodsIn(atype.getEnclosedElements()));
+    }
+    return Collections.unmodifiableList(meths);
+  }
+
+  /**
+   * Return all nested/inner classes/interfaces declared in the given type.
+   *
+   * @param type a type
+   * @return all nested/inner classes/interfaces declared in {@code type}
+   */
+  public static List<TypeElement> getAllTypeElementsIn(TypeElement type) {
+    return ElementFilter.typesIn(type.getEnclosedElements());
+  }
+
+  /** The set of kinds that represent types. */
+  private static final Set<ElementKind> typeElementKinds;
+
+  static {
+    typeElementKinds = EnumSet.noneOf(ElementKind.class);
+    for (ElementKind kind : ElementKind.values()) {
+      if (kind.isClass() || kind.isInterface()) {
+        typeElementKinds.add(kind);
+      }
+    }
+  }
+
+  /**
+   * Return the set of kinds that represent classes.
+   *
+   * @return the set of kinds that represent classes
+   * @deprecated use {@link #typeElementKinds()}
+   */
+  @Deprecated // use typeElementKinds
+  public static Set<ElementKind> classElementKinds() {
+    return typeElementKinds();
+  }
+
+  /**
+   * Return the set of kinds that represent classes.
+   *
+   * @return the set of kinds that represent classes
+   */
+  public static Set<ElementKind> typeElementKinds() {
+    return typeElementKinds;
+  }
+
+  /**
+   * Is the given element kind a type, i.e., a class, enum, interface, or annotation type.
+   *
+   * @param element the element to test
+   * @return true, iff the given kind is a class kind
+   * @deprecated use {@link #isTypeElement}
+   */
+  @Deprecated // use isTypeElement
+  public static boolean isClassElement(Element element) {
+    return isTypeElement(element);
+  }
+
+  /**
+   * Is the given element kind a type, i.e., a class, enum, interface, or annotation type.
+   *
+   * @param element the element to test
+   * @return true, iff the given kind is a class kind
+   */
+  public static boolean isTypeElement(Element element) {
+    return typeElementKinds().contains(element.getKind());
+  }
+
+  /**
+   * Return true if the element is a type declaration.
+   *
+   * @param elt the element to test
+   * @return true if the argument is a type declaration
+   */
+  public static boolean isTypeDeclaration(Element elt) {
+    return isClassElement(elt) || elt.getKind() == ElementKind.TYPE_PARAMETER;
+  }
+
+  /**
+   * Return true if the element is a binding variable.
+   *
+   * <p>Note: This is to conditionally support Java 15 instanceof pattern matching. When available,
+   * this should use {@code ElementKind.BINDING_VARIABLE} directly.
+   *
+   * @param element the element to test
+   * @return true if the element is a binding variable
+   */
+  public static boolean isBindingVariable(Element element) {
+    return "BINDING_VARIABLE".equals(element.getKind().name());
+  }
+
+  /**
+   * Check that a method Element matches a signature.
+   *
+   * <p>Note: Matching the receiver type must be done elsewhere as the Element receiver type is only
+   * populated when annotated.
+   *
+   * @param method the method Element to be tested
+   * @param methodName the goal method name
+   * @param parameters the goal formal parameter Classes
+   * @return true if the method matches the methodName and parameters
+   */
+  public static boolean matchesElement(
+      ExecutableElement method, String methodName, Class<?>... parameters) {
+
+    if (!method.getSimpleName().contentEquals(methodName)) {
+      return false;
+    }
+
+    if (method.getParameters().size() != parameters.length) {
+      return false;
+    } else {
+      for (int i = 0; i < method.getParameters().size(); i++) {
+        if (!method.getParameters().get(i).asType().toString().equals(parameters[i].getName())) {
+
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /** Returns true if the given element is, or overrides, method. */
+  public static boolean isMethod(
+      ExecutableElement questioned, ExecutableElement method, ProcessingEnvironment env) {
+    TypeElement enclosing = (TypeElement) questioned.getEnclosingElement();
+    return questioned.equals(method)
+        || env.getElementUtils().overrides(questioned, method, enclosing);
+  }
+
+  /**
+   * Given an annotation name, return true if the element has the annotation of that name.
+   *
+   * @param element the element
+   * @param annotName name of the annotation
+   * @return true if the element has the annotation of that name
+   */
+  public static boolean hasAnnotation(Element element, String annotName) {
+    for (AnnotationMirror anm : element.getAnnotationMirrors()) {
+      if (AnnotationUtils.areSameByName(anm, annotName)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns the TypeElement for the given class.
+   *
+   * @param processingEnv the processing environment
+   * @param clazz a class
+   * @return the TypeElement for the class
+   */
+  public static TypeElement getTypeElement(ProcessingEnvironment processingEnv, Class<?> clazz) {
+    @CanonicalName String className = clazz.getCanonicalName();
+    if (className == null) {
+      throw new Error("Anonymous class " + clazz + " has no canonical name");
+    }
+    return processingEnv.getElementUtils().getTypeElement(className);
+  }
+
+  /**
+   * Get all the supertypes of a given type, including the type itself. The result includes both
+   * superclasses and implemented interfaces.
+   *
+   * @param type a type
+   * @param env the processing environment
+   * @return list including the type and all its supertypes, with a guarantee that direct supertypes
+   *     (i.e. those that appear in extends or implements clauses) appear before indirect supertypes
+   */
+  public static List<TypeElement> getAllSupertypes(TypeElement type, ProcessingEnvironment env) {
+    Context ctx = ((JavacProcessingEnvironment) env).getContext();
+    com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx);
+    return CollectionsPlume.<Type, TypeElement>mapList(
+        t -> (TypeElement) t.tsym, javacTypes.closure(((Symbol) type).type));
+  }
+
+  /**
+   * Returns the methods that are overriden or implemented by a given method.
+   *
+   * @param m a method
+   * @param types the type utilities
+   * @return the methods that {@code m} overrides or implements
+   */
+  public static Set<? extends ExecutableElement> getOverriddenMethods(
+      ExecutableElement m, Types types) {
+    JavacTypes t = (JavacTypes) types;
+    return t.getOverriddenMethods(m);
+  }
+
+  /**
+   * Returns true if the two elements are in the same class. The two elements should be class
+   * members, such as methods or fields.
+   *
+   * @param e1 an element
+   * @param e2 an element
+   * @return true if the two elements are in the same class
+   */
+  public static boolean inSameClass(Element e1, Element e2) {
+    return e1.getEnclosingElement().equals(e2.getEnclosingElement());
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/InternalUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/InternalUtils.java
new file mode 100644
index 0000000..003ac36
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/InternalUtils.java
@@ -0,0 +1,55 @@
+package org.checkerframework.javacutil;
+
+import com.sun.source.tree.Tree;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
+import javax.annotation.processing.ProcessingEnvironment;
+
+/** Miscellaneous static utility methods. */
+public class InternalUtils {
+
+  // Class cannot be instantiated.
+  private InternalUtils() {
+    throw new AssertionError("Class InternalUtils cannot be instantiated.");
+  }
+
+  /**
+   * Helper function to extract the javac Context from the javac processing environment.
+   *
+   * @param env the processing environment
+   * @return the javac Context
+   */
+  public static Context getJavacContext(ProcessingEnvironment env) {
+    return ((JavacProcessingEnvironment) env).getContext();
+  }
+
+  /**
+   * Obtain the class loader for {@code clazz}. If that is not available, return the system class
+   * loader.
+   *
+   * @param clazz the class whose class loader to find
+   * @return the class loader used to {@code clazz}, or the system class loader, or null if both are
+   *     unavailable
+   */
+  public static ClassLoader getClassLoaderForClass(Class<? extends Object> clazz) {
+    ClassLoader classLoader = clazz.getClassLoader();
+    return classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
+  }
+
+  /**
+   * Compares tree1 to tree2 by the position at which a diagnostic (e.g., an error message) for the
+   * tree should be printed.
+   */
+  public static int compareDiagnosticPosition(Tree tree1, Tree tree2) {
+    DiagnosticPosition pos1 = (DiagnosticPosition) tree1;
+    DiagnosticPosition pos2 = (DiagnosticPosition) tree2;
+
+    int preferred = Integer.compare(pos1.getPreferredPosition(), pos2.getPreferredPosition());
+    if (preferred != 0) {
+      return preferred;
+    }
+
+    return Integer.compare(pos1.getStartPosition(), pos2.getStartPosition());
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java b/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java
new file mode 100644
index 0000000..250fe10
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java
@@ -0,0 +1,54 @@
+package org.checkerframework.javacutil;
+
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.dataflow.qual.Pure;
+import org.checkerframework.dataflow.qual.SideEffectFree;
+
+/** Simple pair class for multiple returns. */
+// TODO: as class is immutable, use @Covariant annotation.
+public class Pair<V1, V2> {
+  /** The first element in the pair. */
+  public final V1 first;
+  /** The second element in the pair. */
+  public final V2 second;
+
+  private Pair(V1 v1, V2 v2) {
+    this.first = v1;
+    this.second = v2;
+  }
+
+  public static <V1, V2> Pair<V1, V2> of(V1 v1, V2 v2) {
+    return new Pair<>(v1, v2);
+  }
+
+  @SideEffectFree
+  @Override
+  public String toString() {
+    return "Pair(" + first + ", " + second + ")";
+  }
+
+  private int hashCode = -1;
+
+  @Pure
+  @Override
+  public int hashCode() {
+    if (hashCode == -1) {
+      hashCode = Objects.hash(first, second);
+    }
+    return hashCode;
+  }
+
+  @Override
+  public boolean equals(@Nullable Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof Pair)) {
+      return false;
+    }
+    @SuppressWarnings("unchecked")
+    Pair<V1, V2> other = (Pair<V1, V2>) o;
+    return Objects.equals(this.first, other.first) && Objects.equals(this.second, other.second);
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java b/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java
new file mode 100644
index 0000000..ec59807
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java
@@ -0,0 +1,420 @@
+package org.checkerframework.javacutil;
+
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.api.JavacScope;
+import com.sun.tools.javac.code.Kinds;
+import com.sun.tools.javac.code.Kinds.KindSelector;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.PackageSymbol;
+import com.sun.tools.javac.code.Symbol.TypeSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.comp.AttrContext;
+import com.sun.tools.javac.comp.DeferredAttr;
+import com.sun.tools.javac.comp.Env;
+import com.sun.tools.javac.comp.Resolve;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.Log;
+import com.sun.tools.javac.util.Name;
+import com.sun.tools.javac.util.Names;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/** A utility class to find symbols corresponding to string references (identifiers). */
+// This class reflectively accesses jdk.compiler/com.sun.tools.javac.comp.
+// This is why --add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED is required when
+// running the Checker Framework.  If this class is re-written, then that --add-opens should be
+// removed.
+public class Resolver {
+  private final Resolve resolve;
+  private final Names names;
+  private final Trees trees;
+  private final Log log;
+
+  private static final Method FIND_METHOD;
+  private static final Method FIND_VAR;
+  private static final Method FIND_IDENT;
+  private static final Method FIND_IDENT_IN_TYPE;
+  private static final Method FIND_IDENT_IN_PACKAGE;
+  private static final Method FIND_TYPE;
+
+  private static final Class<?> ACCESSERROR;
+  // Note that currently access(...) is defined in InvalidSymbolError, a superclass of AccessError
+  private static final Method ACCESSERROR_ACCESS;
+
+  static {
+    try {
+      FIND_METHOD =
+          Resolve.class.getDeclaredMethod(
+              "findMethod",
+              Env.class,
+              Type.class,
+              Name.class,
+              List.class,
+              List.class,
+              boolean.class,
+              boolean.class);
+      FIND_METHOD.setAccessible(true);
+
+      FIND_VAR = Resolve.class.getDeclaredMethod("findVar", Env.class, Name.class);
+      FIND_VAR.setAccessible(true);
+
+      FIND_IDENT =
+          Resolve.class.getDeclaredMethod("findIdent", Env.class, Name.class, KindSelector.class);
+      FIND_IDENT.setAccessible(true);
+
+      FIND_IDENT_IN_TYPE =
+          Resolve.class.getDeclaredMethod(
+              "findIdentInType", Env.class, Type.class, Name.class, KindSelector.class);
+      FIND_IDENT_IN_TYPE.setAccessible(true);
+
+      FIND_IDENT_IN_PACKAGE =
+          Resolve.class.getDeclaredMethod(
+              "findIdentInPackage", Env.class, TypeSymbol.class, Name.class, KindSelector.class);
+      FIND_IDENT_IN_PACKAGE.setAccessible(true);
+
+      FIND_TYPE = Resolve.class.getDeclaredMethod("findType", Env.class, Name.class);
+      FIND_TYPE.setAccessible(true);
+    } catch (Exception e) {
+      Error err =
+          new AssertionError("Compiler 'Resolve' class doesn't contain required 'find' method");
+      err.initCause(e);
+      throw err;
+    }
+
+    try {
+      ACCESSERROR = Class.forName("com.sun.tools.javac.comp.Resolve$AccessError");
+      ACCESSERROR_ACCESS = ACCESSERROR.getMethod("access", Name.class, TypeSymbol.class);
+      ACCESSERROR_ACCESS.setAccessible(true);
+    } catch (ClassNotFoundException e) {
+      throw new BugInCF("Compiler 'Resolve$AccessError' class could not be retrieved.", e);
+    } catch (NoSuchMethodException e) {
+      throw new BugInCF(
+          "Compiler 'Resolve$AccessError' class doesn't contain required 'access' method", e);
+    }
+  }
+
+  public Resolver(ProcessingEnvironment env) {
+    Context context = ((JavacProcessingEnvironment) env).getContext();
+    this.resolve = Resolve.instance(context);
+    this.names = Names.instance(context);
+    this.trees = Trees.instance(env);
+    this.log = Log.instance(context);
+  }
+
+  /**
+   * Determine the environment for the given path.
+   *
+   * @param path the tree path to the local scope
+   * @return the corresponding attribution environment
+   */
+  public Env<AttrContext> getEnvForPath(TreePath path) {
+    TreePath iter = path;
+    JavacScope scope = null;
+    while (scope == null && iter != null) {
+      try {
+        scope = (JavacScope) trees.getScope(iter);
+      } catch (Throwable t) {
+        // Work around Issue #1059 by skipping through the TreePath until something
+        // doesn't crash. This probably returns the class scope, so users might not
+        // get the variables they expect. But that is better than crashing.
+        iter = iter.getParentPath();
+      }
+    }
+    if (scope != null) {
+      return scope.getEnv();
+    } else {
+      throw new BugInCF("Could not determine any possible scope for path: " + path.getLeaf());
+    }
+  }
+
+  /**
+   * Finds the package with name {@code name}.
+   *
+   * @param name the name of the package
+   * @param path the tree path to the local scope
+   * @return the {@code PackageSymbol} for the package if it is found, {@code null} otherwise
+   */
+  public @Nullable PackageSymbol findPackage(String name, TreePath path) {
+    Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log);
+    try {
+      Env<AttrContext> env = getEnvForPath(path);
+      Element res =
+          wrapInvocationOnResolveInstance(
+              FIND_IDENT, env, names.fromString(name), Kinds.KindSelector.PCK);
+      // findIdent will return a PackageSymbol even for a symbol that is not a package, such as
+      // a.b.c.MyClass.myStaticField. "exists()" must be called on it to ensure that it exists.
+      if (res.getKind() == ElementKind.PACKAGE) {
+        PackageSymbol ps = (PackageSymbol) res;
+        return ps.exists() ? ps : null;
+      } else {
+        return null;
+      }
+    } finally {
+      log.popDiagnosticHandler(discardDiagnosticHandler);
+    }
+  }
+
+  /**
+   * Finds the field with name {@code name} in {@code type} or a superclass or superinterface of
+   * {@code type}.
+   *
+   * <p>The method adheres to all the rules of Java's scoping (while also considering the imports)
+   * for name resolution.
+   *
+   * @param name the name of the field
+   * @param type the type of the receiver (i.e., the type in which to look for the field)
+   * @param path the tree path to the local scope
+   * @return the element for the field, {@code null} otherwise
+   */
+  public @Nullable VariableElement findField(String name, TypeMirror type, TreePath path) {
+    Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log);
+    try {
+      Env<AttrContext> env = getEnvForPath(path);
+      Element res =
+          wrapInvocationOnResolveInstance(
+              FIND_IDENT_IN_TYPE, env, type, names.fromString(name), Kinds.KindSelector.VAR);
+
+      if (res.getKind().isField()) {
+        return (VariableElement) res;
+      } else if (res.getKind() == ElementKind.OTHER && ACCESSERROR.isInstance(res)) {
+        // Return the inaccessible field that was found
+        return (VariableElement) wrapInvocation(res, ACCESSERROR_ACCESS, null, null);
+      } else {
+        // Most likely didn't find the field and the Element is a SymbolNotFoundError
+        return null;
+      }
+    } finally {
+      log.popDiagnosticHandler(discardDiagnosticHandler);
+    }
+  }
+
+  /**
+   * Finds the local variable (including formal parameters) with name {@code name} in the given
+   * scope.
+   *
+   * @param name the name of the local variable
+   * @param path the tree path to the local scope
+   * @return the element for the local variable, {@code null} otherwise
+   */
+  public @Nullable VariableElement findLocalVariableOrParameter(String name, TreePath path) {
+    Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log);
+    try {
+      Env<AttrContext> env = getEnvForPath(path);
+      Element res = wrapInvocationOnResolveInstance(FIND_VAR, env, names.fromString(name));
+      if (res.getKind() == ElementKind.LOCAL_VARIABLE || res.getKind() == ElementKind.PARAMETER) {
+        return (VariableElement) res;
+      } else {
+        // The Element might be FIELD or a SymbolNotFoundError.
+        return null;
+      }
+    } finally {
+      log.popDiagnosticHandler(discardDiagnosticHandler);
+    }
+  }
+
+  /**
+   * Finds the class literal with name {@code name}.
+   *
+   * <p>The method adheres to all the rules of Java's scoping (while also considering the imports)
+   * for name resolution.
+   *
+   * @param name the name of the class
+   * @param path the tree path to the local scope
+   * @return the element for the class
+   */
+  public Element findClass(String name, TreePath path) {
+    Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log);
+    try {
+      Env<AttrContext> env = getEnvForPath(path);
+      return wrapInvocationOnResolveInstance(FIND_TYPE, env, names.fromString(name));
+    } finally {
+      log.popDiagnosticHandler(discardDiagnosticHandler);
+    }
+  }
+
+  /**
+   * Finds the class with name {@code name} in a given package.
+   *
+   * @param name the name of the class
+   * @param pck the PackageSymbol for the package
+   * @param path the tree path to the local scope
+   * @return the {@code ClassSymbol} for the class if it is found, {@code null} otherwise
+   */
+  public @Nullable ClassSymbol findClassInPackage(String name, PackageSymbol pck, TreePath path) {
+    Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log);
+    try {
+      Env<AttrContext> env = getEnvForPath(path);
+      Element res =
+          wrapInvocationOnResolveInstance(
+              FIND_IDENT_IN_PACKAGE, env, pck, names.fromString(name), Kinds.KindSelector.TYP);
+      if (ElementUtils.isTypeElement(res)) {
+        return (ClassSymbol) res;
+      } else {
+        return null;
+      }
+    } finally {
+      log.popDiagnosticHandler(discardDiagnosticHandler);
+    }
+  }
+
+  /**
+   * Finds the method element for a given name and list of expected parameter types.
+   *
+   * <p>The method adheres to all the rules of Java's scoping (while also considering the imports)
+   * for name resolution.
+   *
+   * <p>(This method takes into account autoboxing.)
+   *
+   * @param methodName name of the method to find
+   * @param receiverType type of the receiver of the method
+   * @param path tree path
+   * @param argumentTypes types of arguments passed to the method call
+   * @return the method element (if found)
+   */
+  public @Nullable ExecutableElement findMethod(
+      String methodName,
+      TypeMirror receiverType,
+      TreePath path,
+      java.util.List<TypeMirror> argumentTypes) {
+    Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log);
+    try {
+      Env<AttrContext> env = getEnvForPath(path);
+
+      Type site = (Type) receiverType;
+      Name name = names.fromString(methodName);
+      List<Type> argtypes = List.nil();
+      for (TypeMirror a : argumentTypes) {
+        argtypes = argtypes.append((Type) a);
+      }
+      List<Type> typeargtypes = List.nil();
+      boolean allowBoxing = true;
+      boolean useVarargs = false;
+
+      try {
+        // For some reason we have to set our own method context, which is rather ugly.
+        // TODO: find a nicer way to do this.
+        Object methodContext = buildMethodContext();
+        Object oldContext = getField(resolve, "currentResolutionContext");
+        setField(resolve, "currentResolutionContext", methodContext);
+        Element result =
+            wrapInvocationOnResolveInstance(
+                FIND_METHOD, env, site, name, argtypes, typeargtypes, allowBoxing, useVarargs);
+        setField(resolve, "currentResolutionContext", oldContext);
+        if (result.getKind() == ElementKind.METHOD || result.getKind() == ElementKind.CONSTRUCTOR) {
+          return (ExecutableElement) result;
+        }
+        return null;
+      } catch (Throwable t) {
+        Error err =
+            new AssertionError(
+                String.format(
+                    "Unexpected reflection error in findMethod(%s, %s, ..., %s)",
+                    methodName,
+                    receiverType,
+                    // path
+                    argumentTypes));
+        err.initCause(t);
+        throw err;
+      }
+    } finally {
+      log.popDiagnosticHandler(discardDiagnosticHandler);
+    }
+  }
+
+  /** Build an instance of {@code Resolve$MethodResolutionContext}. */
+  protected Object buildMethodContext()
+      throws ClassNotFoundException, InstantiationException, IllegalAccessException,
+          InvocationTargetException, NoSuchFieldException {
+    // Class is not accessible, instantiate reflectively.
+    Class<?> methCtxClss =
+        Class.forName("com.sun.tools.javac.comp.Resolve$MethodResolutionContext");
+    Constructor<?> constructor = methCtxClss.getDeclaredConstructors()[0];
+    constructor.setAccessible(true);
+    Object methodContext = constructor.newInstance(resolve);
+    // we need to also initialize the fields attrMode and step
+    setField(methodContext, "attrMode", DeferredAttr.AttrMode.CHECK);
+    @SuppressWarnings("rawtypes")
+    List<?> phases = (List) getField(resolve, "methodResolutionSteps");
+    assert phases != null : "@AssumeAssertion(nullness): assumption";
+    setField(methodContext, "step", phases.get(1));
+    return methodContext;
+  }
+
+  /**
+   * Reflectively set a field.
+   *
+   * @param receiver the receiver in which to set the field
+   * @param fieldName name of field to set
+   * @param value new value for field
+   * @throws NoSuchFieldException if the field does not exist in the receiver
+   * @throws IllegalAccessException if the field is not accessible
+   */
+  @SuppressWarnings({
+    "nullness:argument",
+    "interning:argument"
+  }) // assume that the fields all accept null and uninterned values
+  private void setField(Object receiver, String fieldName, @Nullable Object value)
+      throws NoSuchFieldException, IllegalAccessException {
+    Field f = receiver.getClass().getDeclaredField(fieldName);
+    f.setAccessible(true);
+    f.set(receiver, value);
+  }
+
+  /** Reflectively get the value of a field. */
+  private @Nullable Object getField(Object receiver, String fieldName)
+      throws NoSuchFieldException, IllegalAccessException {
+    Field f = receiver.getClass().getDeclaredField(fieldName);
+    f.setAccessible(true);
+    return f.get(receiver);
+  }
+
+  /**
+   * Wrap a method invocation on the {@code resolve} object.
+   *
+   * @param method the method to called
+   * @param args the arguments to the call
+   * @return the result of invoking the method on {@code resolve} (as the receiver) and the
+   *     arguments
+   */
+  private Symbol wrapInvocationOnResolveInstance(Method method, Object... args) {
+    return wrapInvocation(resolve, method, args);
+  }
+
+  /**
+   * Invoke a method reflectively.
+   *
+   * @param receiver the receiver
+   * @param method the method to called
+   * @param args the arguments to the call
+   * @return the result of invoking the method on the receiver and arguments
+   */
+  private Symbol wrapInvocation(Object receiver, Method method, @Nullable Object... args) {
+    try {
+      @SuppressWarnings("nullness") // assume arguments are OK
+      @NonNull Symbol res = (Symbol) method.invoke(receiver, args);
+      return res;
+    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+      throw new BugInCF(
+          e,
+          "Unexpected reflection error in wrapInvocation(%s, %s, %s)",
+          receiver,
+          method,
+          Arrays.toString(args));
+    }
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java b/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java
new file mode 100644
index 0000000..c851867
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java
@@ -0,0 +1,337 @@
+package org.checkerframework.javacutil;
+
+import com.sun.tools.javac.main.Option;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Options;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.processing.ProcessingEnvironment;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/** This file contains basic utility functions. */
+public class SystemUtil {
+
+  /**
+   * Returns the major JRE version.
+   *
+   * <p>This is different from the version passed to the compiler via --release; use {@link
+   * #getReleaseValue(ProcessingEnvironment)} to get that version.
+   *
+   * <p>Extract the major version number from the "java.version" system property. Two possible
+   * formats are considered. Up to Java 8, from a version string like `1.8.whatever`, this method
+   * extracts 8. Since Java 9, from a version string like `11.0.1`, this method extracts 11.
+   *
+   * @return the major version number from "java.version"
+   */
+  public static int getJreVersion() {
+    final String jreVersionStr = System.getProperty("java.version");
+
+    final Pattern oldVersionPattern = Pattern.compile("^1\\.(\\d+)\\..*$");
+    final Matcher oldVersionMatcher = oldVersionPattern.matcher(jreVersionStr);
+    if (oldVersionMatcher.matches()) {
+      String v = oldVersionMatcher.group(1);
+      assert v != null : "@AssumeAssertion(nullness): inspection";
+      return Integer.parseInt(v);
+    }
+
+    // See http://openjdk.java.net/jeps/223
+    // We only care about the major version number.
+    final Pattern newVersionPattern = Pattern.compile("^(\\d+).*$");
+    final Matcher newVersionMatcher = newVersionPattern.matcher(jreVersionStr);
+    if (newVersionMatcher.matches()) {
+      String v = newVersionMatcher.group(1);
+      assert v != null : "@AssumeAssertion(nullness): inspection";
+      return Integer.parseInt(v);
+    }
+
+    // For Early Access version of the JDK
+    final Pattern eaVersionPattern = Pattern.compile("^(\\d+)-ea$");
+    final Matcher eaVersionMatcher = eaVersionPattern.matcher(jreVersionStr);
+    if (eaVersionMatcher.matches()) {
+      String v = eaVersionMatcher.group(1);
+      assert v != null : "@AssumeAssertion(nullness): inspection";
+      return Integer.parseInt(v);
+    }
+
+    throw new RuntimeException(
+        "Could not determine version from property java.version=" + jreVersionStr);
+  }
+
+  /**
+   * Returns the release value passed to the compiler or null if release was not passed.
+   *
+   * @param env the ProcessingEnvironment
+   * @return the release value or null if none was passed
+   */
+  public static @Nullable String getReleaseValue(ProcessingEnvironment env) {
+    Context ctx = ((JavacProcessingEnvironment) env).getContext();
+    Options options = Options.instance(ctx);
+    return options.get(Option.RELEASE);
+  }
+
+  /**
+   * Returns the pathname to the tools.jar file, or null if it does not exist. Returns null on Java
+   * 9 and later.
+   *
+   * @return the pathname to the tools.jar file, or null
+   */
+  public static @Nullable String getToolsJar() {
+
+    if (getJreVersion() > 8) {
+      return null;
+    }
+
+    String javaHome = System.getenv("JAVA_HOME");
+    if (javaHome == null) {
+      String javaHomeProperty = System.getProperty("java.home");
+      if (javaHomeProperty.endsWith(File.separator + "jre")) {
+        javaHome = javaHomeProperty.substring(javaHomeProperty.length() - 4);
+      } else {
+        // Could also determine the location of javac on the path...
+        throw new Error("Can't infer Java home; java.home=" + javaHomeProperty);
+      }
+    }
+    String toolsJarFilename = javaHome + File.separator + "lib" + File.separator + "tools.jar";
+    if (!new File(toolsJarFilename).exists()) {
+      throw new Error(
+          String.format(
+              "File does not exist: %s ; JAVA_HOME=%s ; java.home=%s",
+              toolsJarFilename, javaHome, System.getProperty("java.home")));
+    }
+    return javaHome + File.separator + "lib" + File.separator + "tools.jar";
+  }
+
+  ///
+  /// Array and collection methods
+  ///
+
+  /**
+   * Returns a list that contains all the distinct elements of the two lists: that is, the union of
+   * the two arguments.
+   *
+   * <p>For very short lists, this is likely more efficient than creating a set and converting back
+   * to a list.
+   *
+   * @param <T> the type of the list elements
+   * @param list1 a list
+   * @param list2 a list
+   * @return a list that contains all the distinct elements of the two lists
+   */
+  public static <T> List<T> union(List<T> list1, List<T> list2) {
+    List<T> result = new ArrayList<>(list1.size() + list2.size());
+    addWithoutDuplicates(result, list1);
+    addWithoutDuplicates(result, list2);
+    return result;
+  }
+
+  /**
+   * Adds, to dest, all the elements of source that are not already in dest.
+   *
+   * <p>For very short lists, this is likely more efficient than creating a set and converting back
+   * to a list.
+   *
+   * @param <T> the type of the list elements
+   * @param dest a list to add to
+   * @param source a list of elements to add
+   */
+  @SuppressWarnings("nullness:argument" // true positive:  `dest` might be incompatible with
+  // null and `source` might contain null.
+  )
+  public static <T> void addWithoutDuplicates(List<T> dest, List<? extends T> source) {
+    for (T elt : source) {
+      if (!dest.contains(elt)) {
+        dest.add(elt);
+      }
+    }
+  }
+
+  /**
+   * Returns a list that contains all the elements that are in both lists: that is, the set
+   * difference of the two arguments.
+   *
+   * <p>For very short lists, this is likely more efficient than creating a set and converting back
+   * to a list.
+   *
+   * @param <T> the type of the list elements
+   * @param list1 a list
+   * @param list2 a list
+   * @return a list that contains all the elements of {@code list1} that are not in {@code list2}
+   */
+  public static <T> List<T> intersection(List<? extends T> list1, List<? extends T> list2) {
+    List<T> result = new ArrayList<>(list1);
+    result.retainAll(list2);
+    return result;
+  }
+
+  ///
+  /// Deprecated methods
+  ///
+
+  /**
+   * Return a list of Strings, one per line of the file.
+   *
+   * @param argFile argument file
+   * @return a list of Strings, one per line of the file
+   * @throws IOException when reading the argFile
+   * @deprecated use Files.readAllLines
+   */
+  @Deprecated // 2021-03-10
+  public static List<String> readFile(final File argFile) throws IOException {
+    final BufferedReader br = new BufferedReader(new FileReader(argFile));
+    String line;
+
+    List<String> lines = new ArrayList<>();
+    while ((line = br.readLine()) != null) {
+      lines.add(line);
+    }
+    br.close();
+    return lines;
+  }
+
+  /**
+   * Like Thread.sleep, but does not throw any exceptions, so it is easier for clients to use.
+   * Causes the currently executing thread to sleep (temporarily cease execution) for the specified
+   * number of milliseconds.
+   *
+   * @param millis the length of time to sleep in milliseconds
+   * @deprecated use SystemPlume.sleep
+   */
+  @Deprecated // 2021-03-10
+  public static void sleep(long millis) {
+    try {
+      Thread.sleep(millis);
+    } catch (InterruptedException ex) {
+      Thread.currentThread().interrupt();
+    }
+  }
+
+  /**
+   * Concatenates an element, an array, and an element.
+   *
+   * @param <T> the type of the array elements
+   * @param firstElt the first element
+   * @param array the array
+   * @param lastElt the last elemeent
+   * @return a new array containing first element, the array, and the last element, in that order
+   * @deprecated use PlumeUtil.concat
+   */
+  @Deprecated // 2021-03-10
+  @SuppressWarnings("unchecked")
+  public static <T> T[] concatenate(T firstElt, T[] array, T lastElt) {
+    @SuppressWarnings("nullness") // elements are not non-null yet, but will be by return stmt
+    T[] result = Arrays.copyOf(array, array.length + 2);
+    result[0] = firstElt;
+    System.arraycopy(array, 0, result, 1, array.length);
+    result[result.length - 1] = lastElt;
+    return result;
+  }
+
+  /**
+   * Return true if the system property is set to "true". Return false if the system property is not
+   * set or is set to "false". Otherwise, errs.
+   *
+   * @param key system property to check
+   * @return true if the system property is set to "true". Return false if the system property is
+   *     not set or is set to "false". Otherwise, errs.
+   * @deprecated use UtilPlume.getBooleanSystemProperty
+   */
+  @Deprecated // 2021-03-28
+  public static boolean getBooleanSystemProperty(String key) {
+    return Boolean.parseBoolean(System.getProperty(key, "false"));
+  }
+
+  /**
+   * Return its boolean value if the system property is set. Return defaultValue if the system
+   * property is not set. Errs if the system property is set to a non-boolean value.
+   *
+   * @param key system property to check
+   * @param defaultValue value to use if the property is not set
+   * @return the boolean value of {@code key} or {@code defaultValue} if {@code key} is not set
+   * @deprecated use UtilPlume.getBooleanSystemProperty
+   */
+  @Deprecated // 2021-03-28
+  public static boolean getBooleanSystemProperty(String key, boolean defaultValue) {
+    String value = System.getProperty(key);
+    if (value == null) {
+      return defaultValue;
+    }
+    if (value.equals("true")) {
+      return true;
+    }
+    if (value.equals("false")) {
+      return false;
+    }
+    throw new Error(
+        String.format(
+            "Value for system property %s should be boolean, but is \"%s\".", key, value));
+  }
+
+  /**
+   * Concatenates two arrays. Can be invoked varargs-style.
+   *
+   * @param <T> the type of the array elements
+   * @param array1 the first array
+   * @param array2 the second array
+   * @return a new array containing the contents of the given arrays, in order
+   * @deprecated use StringsPlume.concatenate
+   */
+  @Deprecated // 2021-03-28
+  @SuppressWarnings("unchecked")
+  public static <T> T[] concatenate(T[] array1, T... array2) {
+    @SuppressWarnings("nullness") // elements are not non-null yet, but will be by return stmt
+    T[] result = Arrays.copyOf(array1, array1.length + array2.length);
+    System.arraycopy(array2, 0, result, array1.length, array2.length);
+    return result;
+  }
+
+  /**
+   * Given an expected number of elements, returns the capacity that should be passed to a HashMap
+   * or HashSet constructor, so that the set or map will not resize.
+   *
+   * @param numElements the maximum expected number of elements in the map or set
+   * @return the initial capacity to pass to a HashMap or HashSet constructor
+   * @deprecated use CollectionsPlume.mapCapacity
+   */
+  @Deprecated // 2021-05-05
+  public static int mapCapacity(int numElements) {
+    // Equivalent to: (int) (numElements / 0.75) + 1
+    // where 0.75 is the default load factor.
+    return (numElements * 4 / 3) + 1;
+  }
+
+  /**
+   * Given an expected number of elements, returns the capacity that should be passed to a HashMap
+   * or HashSet constructor, so that the set or map will not resize.
+   *
+   * @param c a collection whose size is the maximum expected number of elements in the map or set
+   * @return the initial capacity to pass to a HashMap or HashSet constructor
+   * @deprecated use CollectionsPlume.mapCapacity
+   */
+  @Deprecated // 2021-05-05
+  public static int mapCapacity(Collection<?> c) {
+    return mapCapacity(c.size());
+  }
+
+  /**
+   * Given an expected number of elements, returns the capacity that should be passed to a HashMap
+   * or HashSet constructor, so that the set or map will not resize.
+   *
+   * @param m a map whose size is the maximum expected number of elements in the map or set
+   * @return the initial capacity to pass to a HashMap or HashSet constructor
+   * @deprecated use CollectionsPlume.mapCapacity
+   */
+  @Deprecated // 2021-05-05
+  public static int mapCapacity(Map<?, ?> m) {
+    return mapCapacity(m.size());
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java
new file mode 100644
index 0000000..c16f69b
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java
@@ -0,0 +1,379 @@
+package org.checkerframework.javacutil;
+
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Utility methods for obtaining or analyzing a javac {@code TreePath}.
+ *
+ * @see TreeUtils
+ */
+public final class TreePathUtil {
+
+  /** Do not instantiate; this class is a collection of static methods. */
+  private TreePathUtil() {
+    throw new Error("Class TreeUtils cannot be instantiated.");
+  }
+
+  ///
+  /// Retrieving a path
+  ///
+
+  /**
+   * Gets path to the first (innermost) enclosing tree of the specified kind.
+   *
+   * @param path the path defining the tree node
+   * @param kind the kind of the desired tree
+   * @return the path to the enclosing tree of the given type, {@code null} otherwise
+   */
+  public static @Nullable TreePath pathTillOfKind(final TreePath path, final Tree.Kind kind) {
+    return pathTillOfKind(path, EnumSet.of(kind));
+  }
+
+  /**
+   * Gets path to the first (innermost) enclosing tree with any one of the specified kinds.
+   *
+   * @param path the path defining the tree node
+   * @param kinds the set of kinds of the desired tree
+   * @return the path to the enclosing tree of the given type, {@code null} otherwise
+   */
+  public static @Nullable TreePath pathTillOfKind(final TreePath path, final Set<Tree.Kind> kinds) {
+    TreePath p = path;
+
+    while (p != null) {
+      Tree leaf = p.getLeaf();
+      assert leaf != null;
+      if (kinds.contains(leaf.getKind())) {
+        return p;
+      }
+      p = p.getParentPath();
+    }
+
+    return null;
+  }
+
+  /**
+   * Gets path to the first (innermost) enclosing class tree, where class is defined by the {@link
+   * TreeUtils#classTreeKinds()} method.
+   *
+   * @param path the path defining the tree node
+   * @return the path to the enclosing class tree, {@code null} otherwise
+   */
+  public static @Nullable TreePath pathTillClass(final TreePath path) {
+    return pathTillOfKind(path, TreeUtils.classTreeKinds());
+  }
+
+  /**
+   * Gets path to the first (innermost) enclosing method tree.
+   *
+   * @param path the path defining the tree node
+   * @return the path to the enclosing class tree, {@code null} otherwise
+   */
+  public static @Nullable TreePath pathTillMethod(final TreePath path) {
+    return pathTillOfKind(path, Tree.Kind.METHOD);
+  }
+
+  ///
+  /// Retrieving a tree
+  ///
+
+  /**
+   * Gets the first (innermost) enclosing tree in path, of the specified kind.
+   *
+   * @param path the path defining the tree node
+   * @param kind the kind of the desired tree
+   * @return the enclosing tree of the given type as given by the path, {@code null} otherwise
+   */
+  public static @Nullable Tree enclosingOfKind(final TreePath path, final Tree.Kind kind) {
+    return enclosingOfKind(path, EnumSet.of(kind));
+  }
+
+  /**
+   * Gets the first (innermost) enclosing tree in path, with any one of the specified kinds.
+   *
+   * @param path the path defining the tree node
+   * @param kinds the set of kinds of the desired tree
+   * @return the enclosing tree of the given type as given by the path, {@code null} otherwise
+   */
+  public static @Nullable Tree enclosingOfKind(final TreePath path, final Set<Tree.Kind> kinds) {
+    TreePath p = pathTillOfKind(path, kinds);
+    return (p == null) ? null : p.getLeaf();
+  }
+
+  /**
+   * Gets the first (innermost) enclosing tree in path, of the specified class.
+   *
+   * @param <T> the type of {@code treeClass}
+   * @param path the path defining the tree node
+   * @param treeClass the class of the desired tree
+   * @return the enclosing tree of the given type as given by the path, {@code null} otherwise
+   */
+  public static <T extends Tree> @Nullable T enclosingOfClass(
+      final TreePath path, final Class<T> treeClass) {
+    TreePath p = path;
+
+    while (p != null) {
+      Tree leaf = p.getLeaf();
+      if (treeClass.isInstance(leaf)) {
+        return treeClass.cast(leaf);
+      }
+      p = p.getParentPath();
+    }
+
+    return null;
+  }
+
+  /**
+   * Gets the enclosing class of the tree node defined by the given {@link TreePath}. It returns a
+   * {@link Tree}, from which {@code checkers.types.AnnotatedTypeMirror} or {@link Element} can be
+   * obtained.
+   *
+   * @param path the path defining the tree node
+   * @return the enclosing class (or interface) as given by the path, or {@code null} if one does
+   *     not exist
+   */
+  public static @Nullable ClassTree enclosingClass(final TreePath path) {
+    return (ClassTree) enclosingOfKind(path, TreeUtils.classTreeKinds());
+  }
+
+  /**
+   * Gets the enclosing variable of a tree node defined by the given {@link TreePath}.
+   *
+   * @param path the path defining the tree node
+   * @return the enclosing variable as given by the path, or {@code null} if one does not exist
+   */
+  public static @Nullable VariableTree enclosingVariable(final TreePath path) {
+    return (VariableTree) enclosingOfKind(path, Tree.Kind.VARIABLE);
+  }
+
+  /**
+   * Gets the enclosing method of the tree node defined by the given {@link TreePath}. It returns a
+   * {@link Tree}, from which an {@code checkers.types.AnnotatedTypeMirror} or {@link Element} can
+   * be obtained.
+   *
+   * <p>Also see {@code AnnotatedTypeFactory#getEnclosingMethod} and {@code
+   * AnnotatedTypeFactory#getEnclosingClassOrMethod}, which do not require a TreePath.
+   *
+   * @param path the path defining the tree node
+   * @return the enclosing method as given by the path, or {@code null} if one does not exist
+   */
+  public static @Nullable MethodTree enclosingMethod(final TreePath path) {
+    return (MethodTree) enclosingOfKind(path, Tree.Kind.METHOD);
+  }
+
+  /**
+   * Gets the enclosing method or lambda expression of the tree node defined by the given {@link
+   * TreePath}. It returns a {@link Tree}, from which an {@code checkers.types.AnnotatedTypeMirror}
+   * or {@link Element} can be obtained.
+   *
+   * @param path the path defining the tree node
+   * @return the enclosing method or lambda as given by the path, or {@code null} if one does not
+   *     exist
+   */
+  public static @Nullable Tree enclosingMethodOrLambda(final TreePath path) {
+    return enclosingOfKind(path, EnumSet.of(Tree.Kind.METHOD, Kind.LAMBDA_EXPRESSION));
+  }
+
+  /**
+   * Returns the top-level block that encloses the given path, or null if none does.
+   *
+   * @param path a path
+   * @return the top-level block that encloses the given path, or null if none does
+   */
+  public static @Nullable BlockTree enclosingTopLevelBlock(TreePath path) {
+    TreePath parentPath = path.getParentPath();
+    while (parentPath != null
+        && !TreeUtils.classTreeKinds().contains(parentPath.getLeaf().getKind())) {
+      path = parentPath;
+      parentPath = parentPath.getParentPath();
+    }
+    if (path.getLeaf().getKind() == Tree.Kind.BLOCK) {
+      return (BlockTree) path.getLeaf();
+    }
+    return null;
+  }
+
+  /**
+   * Gets the first (innermost) enclosing tree in path, that is not a parenthesis.
+   *
+   * @param path the path defining the tree node
+   * @return a pair of a non-parenthesis tree that contains the argument, and its child that is the
+   *     argument or is a parenthesized version of it
+   */
+  public static Pair<Tree, Tree> enclosingNonParen(final TreePath path) {
+    TreePath parentPath = path.getParentPath();
+    Tree enclosing = parentPath.getLeaf();
+    Tree enclosingChild = path.getLeaf();
+    while (enclosing.getKind() == Kind.PARENTHESIZED) {
+      parentPath = parentPath.getParentPath();
+      enclosingChild = enclosing;
+      enclosing = parentPath.getLeaf();
+    }
+    return Pair.of(enclosing, enclosingChild);
+  }
+
+  /**
+   * Returns the "assignment context" for the leaf of {@code treePath}, which is often the leaf of
+   * the parent of {@code treePath}. (Does not handle pseudo-assignment of an argument to a
+   * parameter or a receiver expression to a receiver.) This is not the same as {@code
+   * org.checkerframework.dataflow.cfg.node.AssignmentContext}, which represents the left-hand side
+   * rather than the assignment itself.
+   *
+   * <p>The assignment context for {@code treePath} is the leaf of its parent, if that leaf is one
+   * of the following trees:
+   *
+   * <ul>
+   *   <li>AssignmentTree
+   *   <li>CompoundAssignmentTree
+   *   <li>MethodInvocationTree
+   *   <li>NewArrayTree
+   *   <li>NewClassTree
+   *   <li>ReturnTree
+   *   <li>VariableTree
+   * </ul>
+   *
+   * If the parent is a ConditionalExpressionTree we need to distinguish two cases: If the leaf is
+   * either the then or else branch of the ConditionalExpressionTree, then recurse on the parent. If
+   * the leaf is the condition of the ConditionalExpressionTree, then return null to not consider
+   * this assignment context.
+   *
+   * <p>If the leaf is a ParenthesizedTree, then recurse on the parent.
+   *
+   * <p>Otherwise, null is returned.
+   *
+   * @param treePath a path
+   * @return the assignment context as described, {@code null} otherwise
+   */
+  public static @Nullable Tree getAssignmentContext(final TreePath treePath) {
+    TreePath parentPath = treePath.getParentPath();
+
+    if (parentPath == null) {
+      return null;
+    }
+
+    Tree parent = parentPath.getLeaf();
+    switch (parent.getKind()) {
+      case ASSIGNMENT: // See below for CompoundAssignmentTree.
+      case METHOD_INVOCATION:
+      case NEW_ARRAY:
+      case NEW_CLASS:
+      case RETURN:
+      case VARIABLE:
+        return parent;
+      case CONDITIONAL_EXPRESSION:
+        ConditionalExpressionTree cet = (ConditionalExpressionTree) parent;
+        @SuppressWarnings("interning:not.interned") // AST node comparison
+        boolean conditionIsLeaf = (cet.getCondition() == treePath.getLeaf());
+        if (conditionIsLeaf) {
+          // The assignment context for the condition is simply boolean.
+          // No point in going on.
+          return null;
+        }
+        // Otherwise use the context of the ConditionalExpressionTree.
+        return getAssignmentContext(parentPath);
+      case PARENTHESIZED:
+        return getAssignmentContext(parentPath);
+      default:
+        // 11 Tree.Kinds are CompoundAssignmentTrees,
+        // so use instanceof rather than listing all 11.
+        if (parent instanceof CompoundAssignmentTree) {
+          return parent;
+        }
+        return null;
+    }
+  }
+
+  ///
+  /// Predicates
+  ///
+
+  /**
+   * Returns true if the tree is in a constructor or an initializer block.
+   *
+   * @param path the path to test
+   * @return true if the path is in a constructor or an initializer block
+   */
+  public static boolean inConstructor(TreePath path) {
+    MethodTree method = enclosingMethod(path);
+    // If method is null, this is an initializer block.
+    return method == null || TreeUtils.isConstructor(method);
+  }
+
+  /**
+   * Returns true if the leaf of the tree path is in a static scope.
+   *
+   * @param path TreePath whose leaf may or may not be in static scope
+   * @return true if the leaf of the tree path is in a static scope
+   */
+  public static boolean isTreeInStaticScope(TreePath path) {
+    MethodTree enclosingMethod = enclosingMethod(path);
+
+    if (enclosingMethod != null) {
+      return enclosingMethod.getModifiers().getFlags().contains(Modifier.STATIC);
+    }
+    // no enclosing method, check for static or initializer block
+    BlockTree block = enclosingTopLevelBlock(path);
+    if (block != null) {
+      return block.isStatic();
+    }
+
+    // check if its in a variable initializer
+    Tree t = enclosingVariable(path);
+    if (t != null) {
+      return ((VariableTree) t).getModifiers().getFlags().contains(Modifier.STATIC);
+    }
+    ClassTree classTree = enclosingClass(path);
+    if (classTree != null) {
+      return classTree.getModifiers().getFlags().contains(Modifier.STATIC);
+    }
+    return false;
+  }
+
+  ///
+  /// Formatting
+  ///
+
+  /**
+   * Return a printed representation of a TreePath.
+   *
+   * @param path a TreePath
+   * @return a printed representation of the given TreePath
+   */
+  public static String toString(TreePath path) {
+    StringJoiner result = new StringJoiner(System.lineSeparator() + "    ");
+    result.add("TreePath:");
+    for (Tree t : path) {
+      result.add(TreeUtils.toStringTruncated(t, 65) + " " + t.getKind());
+    }
+    return result.toString();
+  }
+
+  /**
+   * Returns a string representation of the leaf of the given path, using {@link
+   * TreeUtils#toStringTruncated}.
+   *
+   * @param path a path
+   * @param length the maximum length for the result; must be at least 6
+   * @return a one-line string representation of the leaf of the given path that is no longer than
+   *     {@code length} characters long
+   */
+  public static String leafToStringTruncated(@Nullable TreePath path, int length) {
+    if (path == null) {
+      return "null";
+    }
+    return TreeUtils.toStringTruncated(path.getLeaf(), length);
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java
new file mode 100644
index 0000000..fb4b4eb
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java
@@ -0,0 +1,1613 @@
+package org.checkerframework.javacutil;
+
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompoundAssignmentTree;
+import com.sun.source.tree.ExpressionStatementTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.PrimitiveTypeTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.TreeVisitor;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.TypeParameterTree;
+import com.sun.source.tree.UnionTypeTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.SimpleTreeVisitor;
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.TypeTag;
+import com.sun.tools.javac.code.Types;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCAnnotatedType;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCBinary;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
+import com.sun.tools.javac.tree.JCTree.JCLambda;
+import com.sun.tools.javac.tree.JCTree.JCLambda.ParameterKind;
+import com.sun.tools.javac.tree.JCTree.JCLiteral;
+import com.sun.tools.javac.tree.JCTree.JCMemberReference;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+import com.sun.tools.javac.tree.JCTree.JCNewArray;
+import com.sun.tools.javac.tree.JCTree.JCNewClass;
+import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
+import com.sun.tools.javac.tree.TreeInfo;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.util.Context;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import org.checkerframework.checker.interning.qual.PolyInterned;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.checkerframework.dataflow.qual.Pure;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.UniqueIdMap;
+
+/**
+ * Utility methods for analyzing a javac {@code Tree}.
+ *
+ * @see TreePathUtil
+ */
+public final class TreeUtils {
+
+  // Class cannot be instantiated.
+  private TreeUtils() {
+    throw new AssertionError("Class TreeUtils cannot be instantiated.");
+  }
+
+  /** Unique IDs for trees. */
+  public static final UniqueIdMap<Tree> treeUids = new UniqueIdMap<>();
+
+  /**
+   * Checks if the provided method is a constructor method or no.
+   *
+   * @param tree a tree defining the method
+   * @return true iff tree describes a constructor
+   */
+  public static boolean isConstructor(final MethodTree tree) {
+    return tree.getName().contentEquals("<init>");
+  }
+
+  /**
+   * Checks if the method invocation is a call to super.
+   *
+   * @param tree a tree defining a method invocation
+   * @return true iff tree describes a call to super
+   */
+  public static boolean isSuperConstructorCall(MethodInvocationTree tree) {
+    return isNamedMethodCall("super", tree);
+  }
+
+  /**
+   * Checks if the method invocation is a call to "this".
+   *
+   * @param tree a tree defining a method invocation
+   * @return true iff tree describes a call to this
+   */
+  public static boolean isThisConstructorCall(MethodInvocationTree tree) {
+    return isNamedMethodCall("this", tree);
+  }
+
+  /**
+   * Checks if the method call is a call to the given method name.
+   *
+   * @param name a method name
+   * @param tree a tree defining a method invocation
+   * @return true iff tree is a call to the given method
+   */
+  private static boolean isNamedMethodCall(String name, MethodInvocationTree tree) {
+    return getMethodName(tree.getMethodSelect()).contentEquals(name);
+  }
+
+  /**
+   * Returns true if the tree is a tree that 'looks like' either an access of a field or an
+   * invocation of a method that are owned by the same accessing instance.
+   *
+   * <p>It would only return true if the access tree is of the form:
+   *
+   * <pre>
+   *   field
+   *   this.field
+   *
+   *   method()
+   *   this.method()
+   * </pre>
+   *
+   * It does not perform any semantical check to differentiate between fields and local variables;
+   * local methods or imported static methods.
+   *
+   * @param tree expression tree representing an access to object member
+   * @return {@code true} iff the member is a member of {@code this} instance
+   */
+  public static boolean isSelfAccess(final ExpressionTree tree) {
+    ExpressionTree tr = TreeUtils.withoutParens(tree);
+    // If method invocation check the method select
+    if (tr.getKind() == Tree.Kind.ARRAY_ACCESS) {
+      return false;
+    }
+
+    if (tree.getKind() == Tree.Kind.METHOD_INVOCATION) {
+      tr = ((MethodInvocationTree) tree).getMethodSelect();
+    }
+    tr = TreeUtils.withoutParens(tr);
+    if (tr.getKind() == Tree.Kind.TYPE_CAST) {
+      tr = ((TypeCastTree) tr).getExpression();
+    }
+    tr = TreeUtils.withoutParens(tr);
+
+    if (tr.getKind() == Tree.Kind.IDENTIFIER) {
+      return true;
+    }
+
+    if (tr.getKind() == Tree.Kind.MEMBER_SELECT) {
+      tr = ((MemberSelectTree) tr).getExpression();
+      if (tr.getKind() == Tree.Kind.IDENTIFIER) {
+        Name ident = ((IdentifierTree) tr).getName();
+        return ident.contentEquals("this") || ident.contentEquals("super");
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * If the given tree is a parenthesized tree, return the enclosed non-parenthesized tree.
+   * Otherwise, return the same tree.
+   *
+   * @param tree an expression tree
+   * @return the outermost non-parenthesized tree enclosed by the given tree
+   */
+  @SuppressWarnings("interning:return") // polymorphism implementation
+  public static @PolyInterned ExpressionTree withoutParens(
+      final @PolyInterned ExpressionTree tree) {
+    ExpressionTree t = tree;
+    while (t.getKind() == Tree.Kind.PARENTHESIZED) {
+      t = ((ParenthesizedTree) t).getExpression();
+    }
+    return t;
+  }
+
+  /**
+   * Gets the {@link Element} for the given Tree API node. For an object instantiation returns the
+   * value of the {@link JCNewClass#constructor} field. Note that this result might differ from the
+   * result of {@link TreeUtils#constructor(NewClassTree)}.
+   *
+   * @param tree the {@link Tree} node to get the symbol for
+   * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal tree
+   *     (JCTree)
+   * @return the {@link Symbol} for the given tree, or null if one could not be found
+   */
+  @Pure
+  public static @Nullable Element elementFromTree(Tree tree) {
+    if (tree == null) {
+      throw new BugInCF("InternalUtils.symbol: tree is null");
+    }
+
+    if (!(tree instanceof JCTree)) {
+      throw new BugInCF("InternalUtils.symbol: tree is not a valid Javac tree");
+    }
+
+    if (isExpressionTree(tree)) {
+      tree = withoutParens((ExpressionTree) tree);
+    }
+
+    switch (tree.getKind()) {
+        // symbol() only works on MethodSelects, so we need to get it manually
+        // for method invocations.
+      case METHOD_INVOCATION:
+        return TreeInfo.symbol(((JCMethodInvocation) tree).getMethodSelect());
+
+      case ASSIGNMENT:
+        return TreeInfo.symbol((JCTree) ((AssignmentTree) tree).getVariable());
+
+      case ARRAY_ACCESS:
+        return elementFromTree(((ArrayAccessTree) tree).getExpression());
+
+      case NEW_CLASS:
+        return ((JCNewClass) tree).constructor;
+
+      case MEMBER_REFERENCE:
+        // TreeInfo.symbol, which is used in the default case, didn't handle
+        // member references until JDK8u20. So handle it here.
+        return ((JCMemberReference) tree).sym;
+
+      default:
+        if (isTypeDeclaration(tree)
+            || tree.getKind() == Tree.Kind.VARIABLE
+            || tree.getKind() == Tree.Kind.METHOD) {
+          return TreeInfo.symbolFor((JCTree) tree);
+        }
+        return TreeInfo.symbol((JCTree) tree);
+    }
+  }
+
+  /**
+   * Gets the element for a class corresponding to a declaration.
+   *
+   * @param node class declaration
+   * @return the element for the given class
+   */
+  public static TypeElement elementFromDeclaration(ClassTree node) {
+    TypeElement elt = (TypeElement) TreeUtils.elementFromTree(node);
+    assert elt != null : "@AssumeAssertion(nullness): tree kind";
+    return elt;
+  }
+
+  /**
+   * Gets the element for a method corresponding to a declaration.
+   *
+   * @return the element for the given method
+   */
+  public static ExecutableElement elementFromDeclaration(MethodTree node) {
+    ExecutableElement elt = (ExecutableElement) TreeUtils.elementFromTree(node);
+    assert elt != null : "@AssumeAssertion(nullness): tree kind";
+    return elt;
+  }
+
+  /**
+   * Gets the element for a variable corresponding to its declaration.
+   *
+   * @return the element for the given variable
+   */
+  public static VariableElement elementFromDeclaration(VariableTree node) {
+    VariableElement elt = (VariableElement) TreeUtils.elementFromTree(node);
+    assert elt != null : "@AssumeAssertion(nullness): tree kind";
+    return elt;
+  }
+
+  /**
+   * Gets the element for the declaration corresponding to this use of an element. To get the
+   * element for a declaration, use {@link #elementFromDeclaration(ClassTree)}, {@link
+   * #elementFromDeclaration(MethodTree)}, or {@link #elementFromDeclaration(VariableTree)} instead.
+   *
+   * <p>This method is just a wrapper around {@link TreeUtils#elementFromTree(Tree)}, but this class
+   * might be the first place someone looks for this functionality.
+   *
+   * @param node the tree corresponding to a use of an element
+   * @return the element for the corresponding declaration, {@code null} otherwise
+   */
+  @Pure
+  public static @Nullable Element elementFromUse(ExpressionTree node) {
+    return TreeUtils.elementFromTree(node);
+  }
+
+  /**
+   * Returns the ExecutableElement for the called method, from a call.
+   *
+   * @param node a method call
+   * @return the ExecutableElement for the called method
+   */
+  @Pure
+  public static ExecutableElement elementFromUse(MethodInvocationTree node) {
+    Element el = TreeUtils.elementFromTree(node);
+    if (!(el instanceof ExecutableElement)) {
+      throw new BugInCF("Method elements should be ExecutableElement. Found: %s", el);
+    }
+    return (ExecutableElement) el;
+  }
+
+  /**
+   * Gets the ExecutableElement for the called constructor, from a constructor invocation.
+   *
+   * @param node a constructor invocation
+   * @return the ExecutableElement for the called constructor
+   * @see #constructor(NewClassTree)
+   */
+  @Pure
+  public static ExecutableElement elementFromUse(NewClassTree node) {
+    Element el = TreeUtils.elementFromTree(node);
+    if (!(el instanceof ExecutableElement)) {
+      throw new BugInCF("Constructor elements should  be ExecutableElement. Found: %s", el);
+    }
+    return (ExecutableElement) el;
+  }
+
+  /**
+   * Determines the symbol for a constructor given an invocation via {@code new}.
+   *
+   * <p>If the tree is a declaration of an anonymous class, then method returns constructor that
+   * gets invoked in the extended class, rather than the anonymous constructor implicitly added by
+   * the constructor (JLS 15.9.5.1)
+   *
+   * @see #elementFromUse(NewClassTree)
+   * @param tree the constructor invocation
+   * @return the {@link ExecutableElement} corresponding to the constructor call in {@code tree}
+   */
+  public static ExecutableElement constructor(NewClassTree tree) {
+
+    if (!(tree instanceof JCTree.JCNewClass)) {
+      throw new BugInCF("InternalUtils.constructor: not a javac internal tree");
+    }
+
+    JCNewClass newClassTree = (JCNewClass) tree;
+
+    if (tree.getClassBody() != null) {
+      // anonymous constructor bodies should contain exactly one statement
+      // in the form:
+      //    super(arg1, ...)
+      // or
+      //    o.super(arg1, ...)
+      //
+      // which is a method invocation (!) to the actual constructor
+
+      // the method call is guaranteed to return nonnull
+      JCMethodDecl anonConstructor =
+          (JCMethodDecl) TreeInfo.declarationFor(newClassTree.constructor, newClassTree);
+      assert anonConstructor != null;
+      assert anonConstructor.body.stats.size() == 1;
+      JCExpressionStatement stmt = (JCExpressionStatement) anonConstructor.body.stats.head;
+      JCTree.JCMethodInvocation superInvok = (JCMethodInvocation) stmt.expr;
+      return (ExecutableElement) TreeInfo.symbol(superInvok.meth);
+    } else {
+      Element e = newClassTree.constructor;
+      return (ExecutableElement) e;
+    }
+  }
+
+  /**
+   * Determine whether the given ExpressionTree has an underlying element.
+   *
+   * @param node the ExpressionTree to test
+   * @return whether the tree refers to an identifier, member select, or method invocation
+   */
+  @EnsuresNonNullIf(result = true, expression = "elementFromUse(#1)")
+  @Pure
+  public static boolean isUseOfElement(ExpressionTree node) {
+    ExpressionTree realnode = TreeUtils.withoutParens(node);
+    switch (realnode.getKind()) {
+      case IDENTIFIER:
+      case MEMBER_SELECT:
+      case METHOD_INVOCATION:
+      case NEW_CLASS:
+        assert elementFromUse(node) != null : "@AssumeAssertion(nullness): inspection";
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Returns true if {@code tree} has a synthetic argument.
+   *
+   * <p>For some anonymous classes with an explicit enclosing expression, javac creates a synthetic
+   * argument to the constructor that is the enclosing expression of the NewClassTree. Suppose a
+   * programmer writes:
+   *
+   * <pre><code>
+   *     class Outer {
+   *         class Inner { }
+   *         void method() {
+   *             this.new Inner(){};
+   *         }
+   *     }
+   * </code></pre>
+   *
+   * Java 9 javac creates the following synthetic tree for {@code this.new Inner(){}}:
+   *
+   * <pre><code>
+   *    new Inner(this) {
+   *         (.Outer x0) {
+   *             x0.super();
+   *         }
+   *    }
+   * </code></pre>
+   *
+   * Java 11 javac creates a different tree without the synthetic argument for {@code this.new
+   * Inner(){}}:
+   *
+   * <pre><code>
+   *    this.new Inner() {
+   *         (.Outer x0) {
+   *             x0.super();
+   *         }
+   *    }
+   * </code></pre>
+   *
+   * @param tree a new class tree
+   * @return true if {@code tree} has a synthetic argument
+   */
+  public static boolean hasSyntheticArgument(NewClassTree tree) {
+    if (tree.getClassBody() == null || tree.getEnclosingExpression() != null) {
+      return false;
+    }
+    for (Tree member : tree.getClassBody().getMembers()) {
+      if (member.getKind() == Kind.METHOD && isConstructor((MethodTree) member)) {
+        MethodTree methodTree = (MethodTree) member;
+        StatementTree f = methodTree.getBody().getStatements().get(0);
+        return TreeUtils.getReceiverTree(((ExpressionStatementTree) f).getExpression()) != null;
+      }
+    }
+    return false;
+  }
+  /**
+   * Returns the name of the invoked method.
+   *
+   * @return the name of the invoked method
+   */
+  public static Name methodName(MethodInvocationTree node) {
+    ExpressionTree expr = node.getMethodSelect();
+    if (expr.getKind() == Tree.Kind.IDENTIFIER) {
+      return ((IdentifierTree) expr).getName();
+    } else if (expr.getKind() == Tree.Kind.MEMBER_SELECT) {
+      return ((MemberSelectTree) expr).getIdentifier();
+    }
+    throw new BugInCF("TreeUtils.methodName: cannot be here: " + node);
+  }
+
+  /**
+   * Returns true if the first statement in the body is a self constructor invocation within a
+   * constructor.
+   *
+   * @return true if the first statement in the body is a self constructor invocation within a
+   *     constructor
+   */
+  public static boolean containsThisConstructorInvocation(MethodTree node) {
+    if (!TreeUtils.isConstructor(node) || node.getBody().getStatements().isEmpty()) {
+      return false;
+    }
+
+    StatementTree st = node.getBody().getStatements().get(0);
+    if (!(st instanceof ExpressionStatementTree)
+        || !(((ExpressionStatementTree) st).getExpression() instanceof MethodInvocationTree)) {
+      return false;
+    }
+
+    MethodInvocationTree invocation =
+        (MethodInvocationTree) ((ExpressionStatementTree) st).getExpression();
+
+    return "this".contentEquals(TreeUtils.methodName(invocation));
+  }
+
+  /**
+   * Returns the first statement of the tree if it is a block. If it is not a block or an empty
+   * block, tree is returned.
+   *
+   * @param tree any kind of tree
+   * @return the first statement of the tree if it is a block. If it is not a block or an empty
+   *     block, tree is returned.
+   */
+  public static Tree firstStatement(Tree tree) {
+    Tree first;
+    if (tree.getKind() == Tree.Kind.BLOCK) {
+      BlockTree block = (BlockTree) tree;
+      if (block.getStatements().isEmpty()) {
+        first = block;
+      } else {
+        first = block.getStatements().iterator().next();
+      }
+    } else {
+      first = tree;
+    }
+    return first;
+  }
+
+  /**
+   * Determine whether the given class contains an explicit constructor.
+   *
+   * @param node a class tree
+   * @return true iff there is an explicit constructor
+   */
+  public static boolean hasExplicitConstructor(ClassTree node) {
+    TypeElement elem = TreeUtils.elementFromDeclaration(node);
+    for (ExecutableElement constructorElt :
+        ElementFilter.constructorsIn(elem.getEnclosedElements())) {
+      if (!isSynthetic(constructorElt)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if the given method is synthetic. Also returns true if the method is a generated
+   * default constructor, which does not appear in source code but is not considered synthetic.
+   *
+   * @param ee a method or constructor element
+   * @return true iff the given method is synthetic
+   */
+  public static boolean isSynthetic(ExecutableElement ee) {
+    MethodSymbol ms = (MethodSymbol) ee;
+    long mod = ms.flags();
+    // GENERATEDCONSTR is for generated constructors, which do not have SYNTHETIC set.
+    return (mod & (Flags.SYNTHETIC | Flags.GENERATEDCONSTR)) != 0;
+  }
+
+  /**
+   * Returns true if the given method is synthetic.
+   *
+   * @param node a method declaration tree
+   * @return true iff the given method is synthetic
+   */
+  public static boolean isSynthetic(MethodTree node) {
+    ExecutableElement ee = TreeUtils.elementFromDeclaration(node);
+    return isSynthetic(ee);
+  }
+
+  /**
+   * Returns true if the tree is of a diamond type. In contrast to the implementation in TreeInfo,
+   * this version works on Trees.
+   *
+   * @see com.sun.tools.javac.tree.TreeInfo#isDiamond(JCTree)
+   */
+  public static boolean isDiamondTree(Tree tree) {
+    switch (tree.getKind()) {
+      case ANNOTATED_TYPE:
+        return isDiamondTree(((AnnotatedTypeTree) tree).getUnderlyingType());
+      case PARAMETERIZED_TYPE:
+        return ((ParameterizedTypeTree) tree).getTypeArguments().isEmpty();
+      case NEW_CLASS:
+        return isDiamondTree(((NewClassTree) tree).getIdentifier());
+      default:
+        return false;
+    }
+  }
+
+  /** Returns true if the tree represents a {@code String} concatenation operation. */
+  public static boolean isStringConcatenation(Tree tree) {
+    return (tree.getKind() == Tree.Kind.PLUS && TypesUtils.isString(TreeUtils.typeOf(tree)));
+  }
+
+  /** Returns true if the compound assignment tree is a string concatenation. */
+  public static boolean isStringCompoundConcatenation(CompoundAssignmentTree tree) {
+    return (tree.getKind() == Tree.Kind.PLUS_ASSIGNMENT
+        && TypesUtils.isString(TreeUtils.typeOf(tree)));
+  }
+
+  /**
+   * Returns true if the node is a constant-time expression.
+   *
+   * <p>A tree is a constant-time expression if it is:
+   *
+   * <ol>
+   *   <li>a literal tree
+   *   <li>a reference to a final variable initialized with a compile time constant
+   *   <li>a String concatenation of two compile time constants
+   * </ol>
+   */
+  public static boolean isCompileTimeString(ExpressionTree node) {
+    ExpressionTree tree = TreeUtils.withoutParens(node);
+    if (tree instanceof LiteralTree) {
+      return true;
+    }
+
+    if (TreeUtils.isUseOfElement(tree)) {
+      Element elt = TreeUtils.elementFromUse(tree);
+      return ElementUtils.isCompileTimeConstant(elt);
+    } else if (TreeUtils.isStringConcatenation(tree)) {
+      BinaryTree binOp = (BinaryTree) tree;
+      return isCompileTimeString(binOp.getLeftOperand())
+          && isCompileTimeString(binOp.getRightOperand());
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Returns the receiver tree of a field access or a method invocation.
+   *
+   * @param expression a field access or a method invocation
+   * @return the expression's receiver tree, or null if it does not have an explicit receiver
+   */
+  public static @Nullable ExpressionTree getReceiverTree(ExpressionTree expression) {
+    ExpressionTree receiver;
+    switch (expression.getKind()) {
+      case METHOD_INVOCATION:
+        // Trying to handle receiver calls to trees of the form
+        //     ((m).getArray())
+        // returns the type of 'm' in this case
+        receiver = ((MethodInvocationTree) expression).getMethodSelect();
+
+        if (receiver.getKind() == Tree.Kind.MEMBER_SELECT) {
+          receiver = ((MemberSelectTree) receiver).getExpression();
+        } else {
+          // It's a method call "m(foo)" without an explicit receiver
+          return null;
+        }
+        break;
+      case NEW_CLASS:
+        receiver = ((NewClassTree) expression).getEnclosingExpression();
+        break;
+      case ARRAY_ACCESS:
+        receiver = ((ArrayAccessTree) expression).getExpression();
+        break;
+      case MEMBER_SELECT:
+        receiver = ((MemberSelectTree) expression).getExpression();
+        // Avoid int.class
+        if (receiver instanceof PrimitiveTypeTree) {
+          return null;
+        }
+        break;
+      case IDENTIFIER:
+        // It's a field access on implicit this or a local variable/parameter.
+        return null;
+      default:
+        return null;
+    }
+    if (receiver == null) {
+      return null;
+    }
+
+    return TreeUtils.withoutParens(receiver);
+  }
+
+  // TODO: What about anonymous classes?
+  // Adding Tree.Kind.NEW_CLASS here doesn't work, because then a
+  // tree gets cast to ClassTree when it is actually a NewClassTree,
+  // for example in enclosingClass above.
+  /** The set of kinds that represent classes. */
+  private static final Set<Tree.Kind> classTreeKinds;
+
+  static {
+    classTreeKinds = EnumSet.noneOf(Tree.Kind.class);
+    for (Tree.Kind kind : Tree.Kind.values()) {
+      if (kind.asInterface() == ClassTree.class) {
+        classTreeKinds.add(kind);
+      }
+    }
+  }
+
+  /**
+   * Return the set of kinds that represent classes.
+   *
+   * @return the set of kinds that represent classes
+   */
+  public static Set<Tree.Kind> classTreeKinds() {
+    return classTreeKinds;
+  }
+
+  /**
+   * Is the given tree kind a class, i.e. a class, enum, interface, or annotation type.
+   *
+   * @param tree the tree to test
+   * @return true, iff the given kind is a class kind
+   */
+  public static boolean isClassTree(Tree tree) {
+    return classTreeKinds().contains(tree.getKind());
+  }
+
+  private static final Set<Tree.Kind> typeTreeKinds =
+      EnumSet.of(
+          Tree.Kind.PRIMITIVE_TYPE,
+          Tree.Kind.PARAMETERIZED_TYPE,
+          Tree.Kind.TYPE_PARAMETER,
+          Tree.Kind.ARRAY_TYPE,
+          Tree.Kind.UNBOUNDED_WILDCARD,
+          Tree.Kind.EXTENDS_WILDCARD,
+          Tree.Kind.SUPER_WILDCARD,
+          Tree.Kind.ANNOTATED_TYPE);
+
+  public static Set<Tree.Kind> typeTreeKinds() {
+    return typeTreeKinds;
+  }
+
+  /**
+   * Is the given tree a type instantiation?
+   *
+   * <p>TODO: this is an under-approximation: e.g. an identifier could be either a type use or an
+   * expression. How can we distinguish.
+   *
+   * @param tree the tree to test
+   * @return true, iff the given tree is a type
+   */
+  public static boolean isTypeTree(Tree tree) {
+    return typeTreeKinds().contains(tree.getKind());
+  }
+
+  /**
+   * Returns true if the given element is an invocation of the method, or of any method that
+   * overrides that one.
+   */
+  public static boolean isMethodInvocation(
+      Tree tree, ExecutableElement method, ProcessingEnvironment env) {
+    if (!(tree instanceof MethodInvocationTree)) {
+      return false;
+    }
+    MethodInvocationTree methInvok = (MethodInvocationTree) tree;
+    ExecutableElement invoked = TreeUtils.elementFromUse(methInvok);
+    if (invoked == null) {
+      return false;
+    }
+    return ElementUtils.isMethod(invoked, method, env);
+  }
+
+  /**
+   * Returns true if the argument is an invocation of one of the given methods, or of any method
+   * that overrides them.
+   */
+  public static boolean isMethodInvocation(
+      Tree methodTree, List<ExecutableElement> methods, ProcessingEnvironment processingEnv) {
+    if (!(methodTree instanceof MethodInvocationTree)) {
+      return false;
+    }
+    for (ExecutableElement Method : methods) {
+      if (isMethodInvocation(methodTree, Method, processingEnv)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns the ExecutableElement for a method declaration. Errs if there is not exactly one
+   * matching method. If more than one method takes the same number of formal parameters, then use
+   * {@link #getMethod(String, String, ProcessingEnvironment, String...)}.
+   *
+   * @param type the class that contains the method
+   * @param methodName the name of the method
+   * @param params the number of formal parameters
+   * @param env the processing environment
+   * @return the ExecutableElement for the specified method
+   */
+  public static ExecutableElement getMethod(
+      Class<?> type, String methodName, int params, ProcessingEnvironment env) {
+    String typeName = type.getCanonicalName();
+    if (typeName == null) {
+      throw new BugInCF("class %s has no canonical name", type);
+    }
+    return getMethod(typeName, methodName, params, env);
+  }
+
+  /**
+   * Returns the ExecutableElement for a method declaration. Errs if there is not exactly one
+   * matching method. If more than one method takes the same number of formal parameters, then use
+   * {@link #getMethod(String, String, ProcessingEnvironment, String...)}.
+   *
+   * @param typeName the class that contains the method
+   * @param methodName the name of the method
+   * @param params the number of formal parameters
+   * @param env the processing environment
+   * @return the ExecutableElement for the specified method
+   */
+  public static ExecutableElement getMethod(
+      @FullyQualifiedName String typeName,
+      String methodName,
+      int params,
+      ProcessingEnvironment env) {
+    List<ExecutableElement> methods = getMethods(typeName, methodName, params, env);
+    if (methods.size() == 1) {
+      return methods.get(0);
+    }
+    throw new BugInCF(
+        "TreeUtils.getMethod(%s, %s, %d): expected 1 match, found %d",
+        typeName, methodName, params, methods.size());
+  }
+
+  /**
+   * Returns the ExecutableElement for a method declaration. Returns null there is no matching
+   * method. Errs if there is more than one matching method. If more than one method takes the same
+   * number of formal parameters, then use {@link #getMethod(String, String, ProcessingEnvironment,
+   * String...)}.
+   *
+   * @param typeName the class that contains the method
+   * @param methodName the name of the method
+   * @param params the number of formal parameters
+   * @param env the processing environment
+   * @return the ExecutableElement for the specified method, or null
+   */
+  public static @Nullable ExecutableElement getMethodOrNull(
+      @FullyQualifiedName String typeName,
+      String methodName,
+      int params,
+      ProcessingEnvironment env) {
+    List<ExecutableElement> methods = getMethods(typeName, methodName, params, env);
+    if (methods.size() == 0) {
+      return null;
+    } else if (methods.size() == 1) {
+      return methods.get(0);
+    } else {
+      throw new BugInCF(
+          "TreeUtils.getMethod(%s, %s, %d): expected 0 or 1 match, found %d",
+          typeName, methodName, params, methods.size());
+    }
+  }
+
+  /**
+   * Returns all ExecutableElements for method declarations of methodName, in class typeName, with
+   * params formal parameters.
+   *
+   * @param typeName the class that contains the method
+   * @param methodName the name of the method
+   * @param params the number of formal parameters
+   * @param env the processing environment
+   * @return the ExecutableElements for all matching methods
+   */
+  public static List<ExecutableElement> getMethods(
+      @FullyQualifiedName String typeName,
+      String methodName,
+      int params,
+      ProcessingEnvironment env) {
+    List<ExecutableElement> methods = new ArrayList<>(1);
+    TypeElement typeElt = env.getElementUtils().getTypeElement(typeName);
+    if (typeElt == null) {
+      throw new UserError("Configuration problem! Could not load type: " + typeName);
+    }
+    for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) {
+      if (exec.getSimpleName().contentEquals(methodName) && exec.getParameters().size() == params) {
+        methods.add(exec);
+      }
+    }
+    return methods;
+  }
+
+  /**
+   * Returns the ExecutableElement for a method declaration. Errs if there is no matching method.
+   *
+   * @param type the class that contains the method
+   * @param methodName the name of the method
+   * @param env the processing environment
+   * @param paramTypes the method's formal parameter types
+   * @return the ExecutableElement for the specified method
+   */
+  public static ExecutableElement getMethod(
+      Class<?> type, String methodName, ProcessingEnvironment env, String... paramTypes) {
+    String typeName = type.getCanonicalName();
+    if (typeName == null) {
+      throw new BugInCF("class %s has no canonical name", type);
+    }
+    return getMethod(typeName, methodName, env, paramTypes);
+  }
+
+  /**
+   * Returns the ExecutableElement for a method declaration. Errs if there is no matching method.
+   *
+   * @param typeName the class that contains the method
+   * @param methodName the name of the method
+   * @param env the processing environment
+   * @param paramTypes the method's formal parameter types
+   * @return the ExecutableElement for the specified method
+   */
+  public static ExecutableElement getMethod(
+      @FullyQualifiedName String typeName,
+      String methodName,
+      ProcessingEnvironment env,
+      String... paramTypes) {
+    TypeElement typeElt = env.getElementUtils().getTypeElement(typeName);
+    for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) {
+      if (exec.getSimpleName().contentEquals(methodName)
+          && exec.getParameters().size() == paramTypes.length) {
+        boolean typesMatch = true;
+        List<? extends VariableElement> params = exec.getParameters();
+        for (int i = 0; i < paramTypes.length; i++) {
+          VariableElement ve = params.get(i);
+          TypeMirror tm = TypeAnnotationUtils.unannotatedType(ve.asType());
+          if (!tm.toString().equals(paramTypes[i])) {
+            typesMatch = false;
+            break;
+          }
+        }
+        if (typesMatch) {
+          return exec;
+        }
+      }
+    }
+    List<String> candidates = new ArrayList<>();
+    for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) {
+      if (exec.getSimpleName().contentEquals(methodName)) {
+        candidates.add(executableElementToString(exec));
+      }
+    }
+    if (candidates.isEmpty()) {
+      for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) {
+        candidates.add(executableElementToString(exec));
+      }
+    }
+    throw new BugInCF(
+        "TreeUtils.getMethod: found no match for %s.%s(%s); candidates: %s",
+        typeName, methodName, Arrays.toString(paramTypes), candidates);
+  }
+
+  /**
+   * Formats the ExecutableElement in the way that getMethod() expects it.
+   *
+   * @param exec an executable element
+   * @return the ExecutableElement, formatted in the way that getMethod() expects it
+   */
+  private static String executableElementToString(ExecutableElement exec) {
+    StringJoiner result = new StringJoiner(", ", exec.getSimpleName() + "(", ")");
+    for (VariableElement param : exec.getParameters()) {
+      result.add(TypeAnnotationUtils.unannotatedType(param.asType()).toString());
+    }
+    return result.toString();
+  }
+
+  /**
+   * Determine whether the given expression is either "this" or an outer "C.this".
+   *
+   * <p>TODO: Should this also handle "super"?
+   */
+  public static boolean isExplicitThisDereference(ExpressionTree tree) {
+    if (tree.getKind() == Tree.Kind.IDENTIFIER
+        && ((IdentifierTree) tree).getName().contentEquals("this")) {
+      // Explicit this reference "this"
+      return true;
+    }
+
+    if (tree.getKind() != Tree.Kind.MEMBER_SELECT) {
+      return false;
+    }
+
+    MemberSelectTree memSelTree = (MemberSelectTree) tree;
+    if (memSelTree.getIdentifier().contentEquals("this")) {
+      // Outer this reference "C.this"
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Determine whether {@code tree} is a class literal, such as
+   *
+   * <pre>
+   *   <em>Object</em> . <em>class</em>
+   * </pre>
+   *
+   * @return true iff if tree is a class literal
+   */
+  public static boolean isClassLiteral(Tree tree) {
+    if (tree.getKind() != Tree.Kind.MEMBER_SELECT) {
+      return false;
+    }
+    return "class".equals(((MemberSelectTree) tree).getIdentifier().toString());
+  }
+
+  /**
+   * Determine whether {@code tree} is a field access expression, such as
+   *
+   * <pre>
+   *   <em>f</em>
+   *   <em>obj</em> . <em>f</em>
+   * </pre>
+   *
+   * This method currently also returns true for class literals and qualified this.
+   *
+   * @param tree a tree that might be a field access
+   * @return true iff if tree is a field access expression (implicit or explicit)
+   */
+  public static boolean isFieldAccess(Tree tree) {
+    if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
+      // explicit member access (or a class literal or a qualified this)
+      MemberSelectTree memberSelect = (MemberSelectTree) tree;
+      assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind";
+      Element el = TreeUtils.elementFromUse(memberSelect);
+      return el.getKind().isField();
+    } else if (tree.getKind() == Tree.Kind.IDENTIFIER) {
+      // implicit field access
+      IdentifierTree ident = (IdentifierTree) tree;
+      assert isUseOfElement(ident) : "@AssumeAssertion(nullness): tree kind";
+      Element el = TreeUtils.elementFromUse(ident);
+      return el.getKind().isField()
+          && !ident.getName().contentEquals("this")
+          && !ident.getName().contentEquals("super");
+    }
+    return false;
+  }
+
+  /**
+   * Compute the name of the field that the field access {@code tree} accesses. Requires {@code
+   * tree} to be a field access, as determined by {@code isFieldAccess} (which currently also
+   * returns true for class literals and qualified this).
+   *
+   * @param tree a field access tree
+   * @return the name of the field accessed by {@code tree}
+   */
+  public static String getFieldName(Tree tree) {
+    assert isFieldAccess(tree);
+    if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
+      MemberSelectTree mtree = (MemberSelectTree) tree;
+      return mtree.getIdentifier().toString();
+    } else {
+      IdentifierTree itree = (IdentifierTree) tree;
+      return itree.getName().toString();
+    }
+  }
+
+  /**
+   * Determine whether {@code tree} refers to a method element, such as.
+   *
+   * <pre>
+   *   <em>m</em>(...)
+   *   <em>obj</em> . <em>m</em>(...)
+   * </pre>
+   *
+   * @return true iff if tree is a method access expression (implicit or explicit)
+   */
+  public static boolean isMethodAccess(Tree tree) {
+    if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
+      // explicit method access
+      MemberSelectTree memberSelect = (MemberSelectTree) tree;
+      assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind";
+      Element el = TreeUtils.elementFromUse(memberSelect);
+      return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR;
+    } else if (tree.getKind() == Tree.Kind.IDENTIFIER) {
+      // implicit method access
+      IdentifierTree ident = (IdentifierTree) tree;
+      // The field "super" and "this" are also legal methods
+      if (ident.getName().contentEquals("super") || ident.getName().contentEquals("this")) {
+        return true;
+      }
+      assert isUseOfElement(ident) : "@AssumeAssertion(nullness): tree kind";
+      Element el = TreeUtils.elementFromUse(ident);
+      return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR;
+    }
+    return false;
+  }
+
+  /**
+   * Compute the name of the method that the method access {@code tree} accesses. Requires {@code
+   * tree} to be a method access, as determined by {@code isMethodAccess}.
+   *
+   * @param tree a method access tree
+   * @return the name of the method accessed by {@code tree}
+   */
+  public static String getMethodName(Tree tree) {
+    assert isMethodAccess(tree);
+    if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
+      MemberSelectTree mtree = (MemberSelectTree) tree;
+      return mtree.getIdentifier().toString();
+    } else {
+      IdentifierTree itree = (IdentifierTree) tree;
+      return itree.getName().toString();
+    }
+  }
+
+  /**
+   * Return {@code true} if and only if {@code tree} can have a type annotation.
+   *
+   * @return {@code true} if and only if {@code tree} can have a type annotation
+   */
+  // TODO: is this implementation precise enough? E.g. does a .class literal work correctly?
+  public static boolean canHaveTypeAnnotation(Tree tree) {
+    return ((JCTree) tree).type != null;
+  }
+
+  /**
+   * Returns true if and only if the given {@code tree} represents a field access of the given
+   * {@link VariableElement}.
+   */
+  public static boolean isSpecificFieldAccess(Tree tree, VariableElement var) {
+    if (tree instanceof MemberSelectTree) {
+      MemberSelectTree memSel = (MemberSelectTree) tree;
+      assert isUseOfElement(memSel) : "@AssumeAssertion(nullness): tree kind";
+      Element field = TreeUtils.elementFromUse(memSel);
+      return field.equals(var);
+    } else if (tree instanceof IdentifierTree) {
+      IdentifierTree idTree = (IdentifierTree) tree;
+      assert isUseOfElement(idTree) : "@AssumeAssertion(nullness): tree kind";
+      Element field = TreeUtils.elementFromUse(idTree);
+      return field.equals(var);
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Returns the VariableElement for a field declaration.
+   *
+   * @param typeName the class where the field is declared
+   * @param fieldName the name of the field
+   * @param env the processing environment
+   * @return the VariableElement for typeName.fieldName
+   */
+  public static VariableElement getField(
+      @FullyQualifiedName String typeName, String fieldName, ProcessingEnvironment env) {
+    TypeElement mapElt = env.getElementUtils().getTypeElement(typeName);
+    for (VariableElement var : ElementFilter.fieldsIn(mapElt.getEnclosedElements())) {
+      if (var.getSimpleName().contentEquals(fieldName)) {
+        return var;
+      }
+    }
+    throw new BugInCF("TreeUtils.getField: shouldn't be here");
+  }
+
+  /**
+   * Determine whether the given tree represents an ExpressionTree.
+   *
+   * @param tree the Tree to test
+   * @return whether the tree is an ExpressionTree
+   */
+  public static boolean isExpressionTree(Tree tree) {
+    return tree instanceof ExpressionTree;
+  }
+
+  /**
+   * Returns true if this is a super call to the {@link Enum} constructor.
+   *
+   * @param node the method invocation to check
+   * @return true if this is a super call to the {@link Enum} constructor
+   */
+  public static boolean isEnumSuper(MethodInvocationTree node) {
+    ExecutableElement ex = TreeUtils.elementFromUse(node);
+    assert ex != null : "@AssumeAssertion(nullness): tree kind";
+    Name name = ElementUtils.getQualifiedClassName(ex);
+    assert name != null : "@AssumeAssertion(nullness): assumption";
+    boolean correctClass = "java.lang.Enum".contentEquals(name);
+    boolean correctMethod = "<init>".contentEquals(ex.getSimpleName());
+    return correctClass && correctMethod;
+  }
+
+  /**
+   * Determine whether the given tree represents a declaration of a type (including type
+   * parameters).
+   *
+   * @param node the Tree to test
+   * @return true if the tree is a type declaration
+   */
+  public static boolean isTypeDeclaration(Tree node) {
+    return isClassTree(node) || node.getKind() == Tree.Kind.TYPE_PARAMETER;
+  }
+
+  /**
+   * Returns whether or not tree is an access of array length.
+   *
+   * @param tree tree to check
+   * @return true if tree is an access of array length
+   */
+  public static boolean isArrayLengthAccess(Tree tree) {
+    if (tree.getKind() == Kind.MEMBER_SELECT
+        && isFieldAccess(tree)
+        && getFieldName(tree).equals("length")) {
+      ExpressionTree expressionTree = ((MemberSelectTree) tree).getExpression();
+      if (TreeUtils.typeOf(expressionTree).getKind() == TypeKind.ARRAY) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Determines whether or not the node referred to by the given {@link Tree} is an anonymous
+   * constructor (the constructor for an anonymous class.
+   *
+   * @param method the {@link Tree} for a node that may be an anonymous constructor
+   * @return true if the given path points to an anonymous constructor, false if it does not
+   */
+  public static boolean isAnonymousConstructor(final MethodTree method) {
+    @Nullable Element e = elementFromTree(method);
+    if (!(e instanceof Symbol)) {
+      return false;
+    }
+
+    if ((((@NonNull Symbol) e).flags() & Flags.ANONCONSTR) != 0) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Converts the given AnnotationTrees to AnnotationMirrors.
+   *
+   * @param annoTrees list of annotation trees to convert to annotation mirrors
+   * @return list of annotation mirrors that represent the given annotation trees
+   */
+  public static List<AnnotationMirror> annotationsFromTypeAnnotationTrees(
+      List<? extends AnnotationTree> annoTrees) {
+    return CollectionsPlume.mapList(TreeUtils::annotationFromAnnotationTree, annoTrees);
+  }
+
+  /**
+   * Converts the given AnnotationTree to an AnnotationMirror.
+   *
+   * @param tree annotation tree to convert to an annotation mirror
+   * @return annotation mirror that represent the given annotation tree
+   */
+  public static AnnotationMirror annotationFromAnnotationTree(AnnotationTree tree) {
+    return ((JCAnnotation) tree).attribute;
+  }
+
+  /**
+   * Converts the given AnnotatedTypeTree to a list of AnnotationMirrors.
+   *
+   * @param tree annotated type tree to convert
+   * @return list of AnnotationMirrors from the tree
+   */
+  public static List<? extends AnnotationMirror> annotationsFromTree(AnnotatedTypeTree tree) {
+    return annotationsFromTypeAnnotationTrees(((JCAnnotatedType) tree).annotations);
+  }
+
+  /**
+   * Converts the given TypeParameterTree to a list of AnnotationMirrors.
+   *
+   * @param tree type parameter tree to convert
+   * @return list of AnnotationMirrors from the tree
+   */
+  public static List<? extends AnnotationMirror> annotationsFromTree(TypeParameterTree tree) {
+    return annotationsFromTypeAnnotationTrees(((JCTypeParameter) tree).annotations);
+  }
+
+  /**
+   * Converts the given NewArrayTree to a list of AnnotationMirrors.
+   *
+   * @param tree new array tree
+   * @return list of AnnotationMirrors from the tree
+   */
+  public static List<? extends AnnotationMirror> annotationsFromArrayCreation(
+      NewArrayTree tree, int level) {
+
+    assert tree instanceof JCNewArray;
+    final JCNewArray newArray = ((JCNewArray) tree);
+
+    if (level == -1) {
+      return annotationsFromTypeAnnotationTrees(newArray.annotations);
+    }
+
+    if (newArray.dimAnnotations.length() > 0
+        && (level >= 0)
+        && (level < newArray.dimAnnotations.size())) {
+      return annotationsFromTypeAnnotationTrees(newArray.dimAnnotations.get(level));
+    }
+
+    return Collections.emptyList();
+  }
+
+  /**
+   * Returns true if the tree is the declaration or use of a local variable.
+   *
+   * @return true if the tree is the declaration or use of a local variable
+   */
+  public static boolean isLocalVariable(Tree tree) {
+    if (tree.getKind() == Kind.VARIABLE) {
+      return elementFromDeclaration((VariableTree) tree).getKind() == ElementKind.LOCAL_VARIABLE;
+    } else if (tree.getKind() == Kind.IDENTIFIER) {
+      ExpressionTree etree = (ExpressionTree) tree;
+      assert isUseOfElement(etree) : "@AssumeAssertion(nullness): tree kind";
+      return elementFromUse(etree).getKind() == ElementKind.LOCAL_VARIABLE;
+    }
+    return false;
+  }
+
+  /**
+   * Returns the type as a TypeMirror of {@code tree}. To obtain {@code tree}'s AnnotatedTypeMirror,
+   * call {@code AnnotatedTypeFactory.getAnnotatedType()}.
+   *
+   * @return the type as a TypeMirror of {@code tree}
+   */
+  public static TypeMirror typeOf(Tree tree) {
+    return ((JCTree) tree).type;
+  }
+
+  /**
+   * The type of the lambda or method reference tree is a functional interface type. This method
+   * returns the single abstract method declared by that functional interface. (The type of this
+   * method is referred to as the function type.)
+   *
+   * @param tree lambda or member reference tree
+   * @param env ProcessingEnvironment
+   * @return the single abstract method declared by the type of the tree
+   */
+  public static Symbol findFunction(Tree tree, ProcessingEnvironment env) {
+    Context ctx = ((JavacProcessingEnvironment) env).getContext();
+    Types javacTypes = Types.instance(ctx);
+    return javacTypes.findDescriptorSymbol(((Type) typeOf(tree)).asElement());
+  }
+
+  /**
+   * Returns true if {@code tree} is an implicitly typed lambda.
+   *
+   * <p>A lambda expression whose formal type parameters have inferred types is an implicitly typed
+   * lambda. (See JLS 15.27.1)
+   *
+   * @param tree any kind of tree
+   * @return true iff {@code tree} is an implicitly typed lambda
+   */
+  public static boolean isImplicitlyTypedLambda(Tree tree) {
+    return tree.getKind() == Kind.LAMBDA_EXPRESSION
+        && ((JCLambda) tree).paramKind == ParameterKind.IMPLICIT;
+  }
+
+  /**
+   * Determine whether an expression {@link ExpressionTree} has the constant value true, according
+   * to the compiler logic.
+   *
+   * @param node the expression to be checked
+   * @return true if {@code node} has the constant value true
+   */
+  public static boolean isExprConstTrue(final ExpressionTree node) {
+    assert node instanceof JCExpression;
+    if (((JCExpression) node).type.isTrue()) {
+      return true;
+    }
+    ExpressionTree tree = TreeUtils.withoutParens(node);
+    if (tree instanceof JCTree.JCBinary) {
+      JCBinary binTree = (JCBinary) tree;
+      JCExpression ltree = binTree.lhs;
+      JCExpression rtree = binTree.rhs;
+      switch (binTree.getTag()) {
+        case AND:
+          return isExprConstTrue(ltree) && isExprConstTrue(rtree);
+        case OR:
+          return isExprConstTrue(ltree) || isExprConstTrue(rtree);
+        default:
+          break;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Return toString(), but without line separators.
+   *
+   * @param tree a tree
+   * @return a one-line string representation of the tree
+   */
+  public static String toStringOneLine(Tree tree) {
+    return tree.toString().trim().replaceAll("\\s+", " ");
+  }
+
+  /**
+   * Return either {@link #toStringOneLine} if it is no more than {@code length} characters, or
+   * {@link #toStringOneLine} quoted and truncated.
+   *
+   * @param tree a tree
+   * @param length the maximum length for the result; must be at least 6
+   * @return a one-line string representation of the tree that is no longer than {@code length}
+   *     characters long
+   */
+  public static String toStringTruncated(Tree tree, int length) {
+    if (length < 6) {
+      throw new IllegalArgumentException("bad length " + length);
+    }
+    String result = toStringOneLine(tree);
+    if (result.length() > length) {
+      // The quoting increases the likelihood that all delimiters are balanced in the result.
+      // That makes it easier to manipulate the result (such as skipping over it) in an
+      // editor.  The quoting also makes clear that the value is truncated.
+      result = "\"" + result.substring(0, length - 5) + "...\"";
+    }
+    return result;
+  }
+
+  /**
+   * Given a javac ExpressionTree representing a fully qualified name such as "java.lang.Object",
+   * creates a String containing the name.
+   *
+   * @param nameExpr an ExpressionTree representing a fully qualified name
+   * @return a String representation of the fully qualified name
+   */
+  public static String nameExpressionToString(ExpressionTree nameExpr) {
+    TreeVisitor<String, Void> visitor =
+        new SimpleTreeVisitor<String, Void>() {
+          @Override
+          public String visitIdentifier(IdentifierTree node, Void p) {
+            return node.toString();
+          }
+
+          @Override
+          public String visitMemberSelect(MemberSelectTree node, Void p) {
+            return node.getExpression().accept(this, null) + "." + node.getIdentifier().toString();
+          }
+        };
+    return nameExpr.accept(visitor, null);
+  }
+
+  /**
+   * Returns true if the binary operator may do a widening primitive conversion. See <a
+   * href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-5.html">JLS chapter 5</a>.
+   *
+   * @param node a binary tree
+   * @return true if the tree's operator does numeric promotion on its arguments
+   */
+  public static boolean isWideningBinary(BinaryTree node) {
+    switch (node.getKind()) {
+      case LEFT_SHIFT:
+      case LEFT_SHIFT_ASSIGNMENT:
+      case RIGHT_SHIFT:
+      case RIGHT_SHIFT_ASSIGNMENT:
+      case UNSIGNED_RIGHT_SHIFT:
+      case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
+        // Strictly speaking, these operators do unary promotion on each argument separately.
+        return true;
+
+      case MULTIPLY:
+      case MULTIPLY_ASSIGNMENT:
+      case DIVIDE:
+      case DIVIDE_ASSIGNMENT:
+      case REMAINDER:
+      case REMAINDER_ASSIGNMENT:
+      case PLUS:
+      case PLUS_ASSIGNMENT:
+      case MINUS:
+      case MINUS_ASSIGNMENT:
+
+      case LESS_THAN:
+      case LESS_THAN_EQUAL:
+      case GREATER_THAN:
+      case GREATER_THAN_EQUAL:
+      case EQUAL_TO:
+      case NOT_EQUAL_TO:
+
+      case AND:
+      case XOR:
+      case OR:
+        // These operators do binary promotion on the two arguments together.
+        return true;
+
+        // TODO: CONDITIONAL_EXPRESSION (?:) sometimes does numeric promotion.
+
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Returns the annotations explicitly written on the given type.
+   *
+   * @param annoTrees annotations written before a variable/method declaration; null if this type is
+   *     not from such a location. This might contain type annotations that the Java parser attached
+   *     to the declaration rather than to the type.
+   * @param typeTree the type whose annotations to return
+   * @return the annotations explicitly written on the given type
+   */
+  public static List<? extends AnnotationTree> getExplicitAnnotationTrees(
+      @Nullable List<? extends AnnotationTree> annoTrees, Tree typeTree) {
+    while (true) {
+      switch (typeTree.getKind()) {
+        case IDENTIFIER:
+        case PRIMITIVE_TYPE:
+          if (annoTrees == null) {
+            return Collections.emptyList();
+          }
+          return annoTrees;
+        case ANNOTATED_TYPE:
+          return ((AnnotatedTypeTree) typeTree).getAnnotations();
+        case ARRAY_TYPE:
+        case TYPE_PARAMETER:
+        case UNBOUNDED_WILDCARD:
+        case EXTENDS_WILDCARD:
+        case SUPER_WILDCARD:
+          return Collections.emptyList();
+        case MEMBER_SELECT:
+          if (annoTrees == null) {
+            return Collections.emptyList();
+          }
+          typeTree = ((MemberSelectTree) typeTree).getExpression();
+          break;
+        case PARAMETERIZED_TYPE:
+          typeTree = ((ParameterizedTypeTree) typeTree).getType();
+          break;
+        case UNION_TYPE:
+          List<AnnotationTree> result = new ArrayList<>();
+          for (Tree alternative : ((UnionTypeTree) typeTree).getTypeAlternatives()) {
+            result.addAll(getExplicitAnnotationTrees(null, alternative));
+          }
+          return result;
+        default:
+          throw new BugInCF(
+              "what typeTree? %s %s %s", typeTree.getKind(), typeTree.getClass(), typeTree);
+      }
+    }
+  }
+
+  /**
+   * Return a tree for the default value of the given type. The default value is 0, false, or null.
+   *
+   * @param typeMirror a type
+   * @param processingEnv the processing environment
+   * @return a tree for {@code type}'s default value
+   */
+  public static LiteralTree getDefaultValueTree(
+      TypeMirror typeMirror, ProcessingEnvironment processingEnv) {
+    switch (typeMirror.getKind()) {
+      case BYTE:
+        return TreeUtils.createLiteral(TypeTag.BYTE, (byte) 0, typeMirror, processingEnv);
+      case CHAR:
+        return TreeUtils.createLiteral(TypeTag.CHAR, '\u0000', typeMirror, processingEnv);
+      case SHORT:
+        return TreeUtils.createLiteral(TypeTag.SHORT, (short) 0, typeMirror, processingEnv);
+      case LONG:
+        return TreeUtils.createLiteral(TypeTag.LONG, 0L, typeMirror, processingEnv);
+      case FLOAT:
+        return TreeUtils.createLiteral(TypeTag.FLOAT, 0.0f, typeMirror, processingEnv);
+      case INT:
+        return TreeUtils.createLiteral(TypeTag.INT, 0, typeMirror, processingEnv);
+      case DOUBLE:
+        return TreeUtils.createLiteral(TypeTag.DOUBLE, 0.0d, typeMirror, processingEnv);
+      case BOOLEAN:
+        return TreeUtils.createLiteral(TypeTag.BOOLEAN, false, typeMirror, processingEnv);
+      default:
+        return TreeUtils.createLiteral(TypeTag.BOT, null, typeMirror, processingEnv);
+    }
+  }
+
+  /**
+   * Creates a LiteralTree for the given value.
+   *
+   * @param typeTag the literal's type tag
+   * @param value a wrapped primitive, null, or a String
+   * @param typeMirror the typeMirror for the literal
+   * @param processingEnv the processing environment
+   * @return a LiteralTree for the given type tag and value
+   */
+  public static LiteralTree createLiteral(
+      TypeTag typeTag,
+      @Nullable Object value,
+      TypeMirror typeMirror,
+      ProcessingEnvironment processingEnv) {
+    Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
+    TreeMaker maker = TreeMaker.instance(context);
+    LiteralTree result = maker.Literal(typeTag, value);
+    ((JCLiteral) result).type = (Type) typeMirror;
+    return result;
+  }
+
+  /**
+   * Returns true if the given tree evaluates to {@code null}.
+   *
+   * @param t a tree
+   * @return true if the given tree evaluates to {@code null}
+   */
+  public static boolean isNullExpression(Tree t) {
+    while (true) {
+      switch (t.getKind()) {
+        case PARENTHESIZED:
+          t = ((ParenthesizedTree) t).getExpression();
+          break;
+        case TYPE_CAST:
+          t = ((TypeCastTree) t).getExpression();
+          break;
+        case NULL_LITERAL:
+          return true;
+        default:
+          return false;
+      }
+    }
+  }
+
+  /**
+   * Returns true if two expressions originating from the same scope are identical, i.e. they are
+   * syntactically represented in the same way (modulo parentheses) and represent the same value.
+   *
+   * <p>If the expression includes one or more method calls, assumes the method calls are
+   * deterministic.
+   *
+   * @param expr1 the first expression to compare
+   * @param expr2 the second expression to compare; expr2 must originate from the same scope as
+   *     expr1
+   * @return true if the expressions expr1 and expr2 are syntactically identical
+   */
+  public static boolean sameTree(ExpressionTree expr1, ExpressionTree expr2) {
+    expr1 = TreeUtils.withoutParens(expr1);
+    expr2 = TreeUtils.withoutParens(expr2);
+    // Converting to a string in order to compare is somewhat inefficient, and it doesn't handle
+    // internal parentheses.  We could create a visitor instead.
+    return expr1.getKind() == expr2.getKind() && expr1.toString().equals(expr2.toString());
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java
new file mode 100644
index 0000000..b4d83f1
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java
@@ -0,0 +1,664 @@
+package org.checkerframework.javacutil;
+
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Attribute.TypeCompound;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.TypeAnnotationPosition;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.Name;
+import com.sun.tools.javac.util.Pair;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.AnnotationValueVisitor;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+/**
+ * A collection of helper methods related to type annotation handling.
+ *
+ * @see AnnotationUtils
+ */
+public class TypeAnnotationUtils {
+
+  // Class cannot be instantiated.
+  private TypeAnnotationUtils() {
+    throw new AssertionError("Class TypeAnnotationUtils cannot be instantiated.");
+  }
+
+  /**
+   * Check whether a TypeCompound is contained in a list of TypeCompounds.
+   *
+   * @param list the input list of TypeCompounds
+   * @param tc the TypeCompound to find
+   * @param types type utilities
+   * @return true, iff a TypeCompound equal to tc is contained in list
+   */
+  public static boolean isTypeCompoundContained(
+      List<TypeCompound> list, TypeCompound tc, Types types) {
+    for (Attribute.TypeCompound rawat : list) {
+      if (typeCompoundEquals(rawat, tc, types)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Compares two TypeCompound objects (e.g., annotations).
+   *
+   * @param tc1 the first TypeCompound to compare
+   * @param tc2 the second TypeCompound to compare
+   * @param types type utilities
+   * @return true if the TypeCompounds represent the same compound element value
+   */
+  private static boolean typeCompoundEquals(TypeCompound tc1, TypeCompound tc2, Types types) {
+    // For the first conjunct, both of these forms fail in some cases:
+    //   tc1.type == tc2.type
+    //   types.isSameType(tc1.type, tc2.type)
+    return contentEquals(tc1.type.tsym.name, tc2.type.tsym.name)
+        && typeCompoundValuesEquals(tc1.values, tc2.values, types)
+        && isSameTAPositionExceptTreePos(tc1.position, tc2.position);
+  }
+
+  /**
+   * Returns true if the two names represent the same string.
+   *
+   * @param n1 the first Name to compare
+   * @param n2 the second Name to compare
+   * @return true if the two names represent the same string
+   */
+  @SuppressWarnings(
+      "interning:unnecessary.equals" // Name is interned within a single instance of javac,
+  // but call equals anyway out of paranoia.
+  )
+  private static boolean contentEquals(Name n1, Name n2) {
+    if (n1.getClass() == n2.getClass()) {
+      return n1.equals(n2);
+    } else {
+      // Slightly less efficient because it makes a copy.
+      return n1.contentEquals(n2);
+    }
+  }
+
+  /**
+   * Compares the {@code values} fields of two TypeCompound objects (e.g., annotations). Is more
+   * lenient than {@code List.equals}, which uses {@code Object.equals} on list elements.
+   *
+   * @param values1 the first {@code values} field
+   * @param values2 the second {@code values} field
+   * @param types type utilities
+   * @return true if the two {@code values} fields represent the same name-to-value mapping, in the
+   *     same order
+   */
+  @SuppressWarnings("InvalidParam") // Error Prone tries to be clever, but it is not
+  private static boolean typeCompoundValuesEquals(
+      List<Pair<MethodSymbol, Attribute>> values1,
+      List<Pair<MethodSymbol, Attribute>> values2,
+      Types types) {
+    if (values1.size() != values2.size()) {
+      return false;
+    }
+
+    for (Iterator<Pair<MethodSymbol, Attribute>> iter1 = values1.iterator(),
+            iter2 = values2.iterator();
+        iter1.hasNext(); ) {
+      Pair<MethodSymbol, Attribute> pair1 = iter1.next();
+      Pair<MethodSymbol, Attribute> pair2 = iter2.next();
+      if (!(pair1.fst.equals(pair2.fst) && attributeEquals(pair1.snd, pair2.snd, types))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Compares two attributes. Is more lenient for constants than {@code Attribute.equals}, which is
+   * reference equality.
+   *
+   * @param a1 the first attribute to compare
+   * @param a2 the second attribute to compare
+   * @param types type utilities
+   * @return true if the two attributes are the same
+   */
+  private static boolean attributeEquals(Attribute a1, Attribute a2, Types types) {
+    if (a1 instanceof Attribute.Array && a2 instanceof Attribute.Array) {
+      List<Attribute> list1 = ((Attribute.Array) a1).getValue();
+      List<Attribute> list2 = ((Attribute.Array) a2).getValue();
+      if (list1.size() != list2.size()) {
+        return false;
+      }
+      // This requires the array elements to be in the same order.  Is that the right thing?
+      for (int i = 0; i < list1.size(); i++) {
+        if (!attributeEquals(list1.get(i), list2.get(i), types)) {
+          return false;
+        }
+      }
+      return true;
+    } else if (a1 instanceof Attribute.Class && a2 instanceof Attribute.Class) {
+      Type t1 = ((Attribute.Class) a1).getValue();
+      Type t2 = ((Attribute.Class) a2).getValue();
+      return types.isSameType(t1, t2);
+    } else if (a1 instanceof Attribute.Constant && a2 instanceof Attribute.Constant) {
+      Object v1 = ((Attribute.Constant) a1).getValue();
+      Object v2 = ((Attribute.Constant) a2).getValue();
+      return v1.equals(v2);
+    } else if (a1 instanceof Attribute.Compound && a2 instanceof Attribute.Compound) {
+      // The annotation value is another annotation.  `a1` and `a2` implement AnnotationMirror.
+      DeclaredType t1 = ((Attribute.Compound) a1).getAnnotationType();
+      DeclaredType t2 = ((Attribute.Compound) a2).getAnnotationType();
+      if (!types.isSameType(t1, t2)) {
+        return false;
+      }
+      Map<Symbol.MethodSymbol, Attribute> map1 = ((Attribute.Compound) a1).getElementValues();
+      Map<Symbol.MethodSymbol, Attribute> map2 = ((Attribute.Compound) a2).getElementValues();
+      // Is this test, which uses equals() for the keys, too strict?
+      if (!map1.keySet().equals(map2.keySet())) {
+        return false;
+      }
+      for (Symbol.MethodSymbol key : map1.keySet()) {
+        Attribute attr1 = map1.get(key);
+        @SuppressWarnings("nullness:assignment") // same keys in map1 & map2
+        @NonNull Attribute attr2 = map2.get(key);
+        if (!attributeEquals(attr1, attr2, types)) {
+          return false;
+        }
+      }
+      return true;
+    } else if (a1 instanceof Attribute.Enum && a2 instanceof Attribute.Enum) {
+      Symbol.VarSymbol s1 = ((Attribute.Enum) a1).getValue();
+      Symbol.VarSymbol s2 = ((Attribute.Enum) a2).getValue();
+      // VarSymbol.equals() is reference equality.
+      return s1.equals(s2) || s1.toString().equals(s2.toString());
+    } else if (a1 instanceof Attribute.Error && a2 instanceof Attribute.Error) {
+      String s1 = ((Attribute.Error) a1).getValue();
+      String s2 = ((Attribute.Error) a2).getValue();
+      return s1.equals(s2);
+    } else {
+      return a1.equals(a2);
+    }
+  }
+
+  /**
+   * Compare two TypeAnnotationPositions for equality.
+   *
+   * @param p1 the first position
+   * @param p2 the second position
+   * @return true, iff the two positions are equal
+   */
+  public static boolean isSameTAPosition(TypeAnnotationPosition p1, TypeAnnotationPosition p2) {
+    return isSameTAPositionExceptTreePos(p1, p2) && p1.pos == p2.pos;
+  }
+
+  /**
+   * Compare two TypeAnnotationPositions for equality, ignoring the source tree position.
+   *
+   * @param p1 the first position
+   * @param p2 the second position
+   * @return true, iff the two positions are equal except for the source tree position
+   */
+  @SuppressWarnings("interning:not.interned") // reference equality for onLambda field
+  public static boolean isSameTAPositionExceptTreePos(
+      TypeAnnotationPosition p1, TypeAnnotationPosition p2) {
+    return p1.type == p2.type
+        && p1.type_index == p2.type_index
+        && p1.bound_index == p2.bound_index
+        && p1.onLambda == p2.onLambda
+        && p1.parameter_index == p2.parameter_index
+        && p1.isValidOffset == p2.isValidOffset
+        && p1.offset == p2.offset
+        && p1.location.equals(p2.location)
+        && Arrays.equals(p1.lvarIndex, p2.lvarIndex)
+        && Arrays.equals(p1.lvarLength, p2.lvarLength)
+        && Arrays.equals(p1.lvarOffset, p2.lvarOffset)
+        && (!p1.hasExceptionIndex()
+            || !p2.hasExceptionIndex()
+            || (p1.getExceptionIndex() == p2.getExceptionIndex()));
+  }
+
+  /**
+   * Returns a newly created Attribute.Compound corresponding to an argument AnnotationMirror.
+   *
+   * @param am an AnnotationMirror, which may be part of an AST or an internally created subclass
+   * @return a new Attribute.Compound corresponding to the AnnotationMirror
+   */
+  public static Attribute.Compound createCompoundFromAnnotationMirror(
+      AnnotationMirror am, ProcessingEnvironment env) {
+    // Create a new Attribute to match the AnnotationMirror.
+    List<Pair<Symbol.MethodSymbol, Attribute>> values = List.nil();
+    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
+        am.getElementValues().entrySet()) {
+      Attribute attribute = attributeFromAnnotationValue(entry.getKey(), entry.getValue(), env);
+      values = values.append(new Pair<>((Symbol.MethodSymbol) entry.getKey(), attribute));
+    }
+    return new Attribute.Compound((Type.ClassType) am.getAnnotationType(), values);
+  }
+
+  /**
+   * Returns a newly created Attribute.TypeCompound corresponding to an argument AnnotationMirror.
+   *
+   * @param am an AnnotationMirror, which may be part of an AST or an internally created subclass
+   * @param tapos the type annotation position to use
+   * @return a new Attribute.TypeCompound corresponding to the AnnotationMirror
+   */
+  public static Attribute.TypeCompound createTypeCompoundFromAnnotationMirror(
+      AnnotationMirror am, TypeAnnotationPosition tapos, ProcessingEnvironment env) {
+    // Create a new Attribute to match the AnnotationMirror.
+    List<Pair<Symbol.MethodSymbol, Attribute>> values = List.nil();
+    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
+        am.getElementValues().entrySet()) {
+      Attribute attribute = attributeFromAnnotationValue(entry.getKey(), entry.getValue(), env);
+      values = values.append(new Pair<>((Symbol.MethodSymbol) entry.getKey(), attribute));
+    }
+    return new Attribute.TypeCompound((Type.ClassType) am.getAnnotationType(), values, tapos);
+  }
+
+  /**
+   * Returns a newly created Attribute corresponding to an argument AnnotationValue.
+   *
+   * @param meth the ExecutableElement that is assigned the value, needed for empty arrays
+   * @param av an AnnotationValue, which may be part of an AST or an internally created subclass
+   * @return a new Attribute corresponding to the AnnotationValue
+   */
+  public static Attribute attributeFromAnnotationValue(
+      ExecutableElement meth, AnnotationValue av, ProcessingEnvironment env) {
+    return av.accept(new AttributeCreator(env, meth), null);
+  }
+
+  private static class AttributeCreator implements AnnotationValueVisitor<Attribute, Void> {
+    private final ProcessingEnvironment processingEnv;
+    private final Types modelTypes;
+    private final Elements elements;
+    private final com.sun.tools.javac.code.Types javacTypes;
+
+    private final ExecutableElement meth;
+
+    public AttributeCreator(ProcessingEnvironment env, ExecutableElement meth) {
+      this.processingEnv = env;
+      Context context = ((JavacProcessingEnvironment) env).getContext();
+      this.elements = env.getElementUtils();
+      this.modelTypes = env.getTypeUtils();
+      this.javacTypes = com.sun.tools.javac.code.Types.instance(context);
+
+      this.meth = meth;
+    }
+
+    @Override
+    public Attribute visit(AnnotationValue av, Void p) {
+      return av.accept(this, p);
+    }
+
+    @Override
+    public Attribute visit(AnnotationValue av) {
+      return visit(av, null);
+    }
+
+    @Override
+    public Attribute visitBoolean(boolean b, Void p) {
+      TypeMirror booleanType = modelTypes.getPrimitiveType(TypeKind.BOOLEAN);
+      return new Attribute.Constant((Type) booleanType, b ? 1 : 0);
+    }
+
+    @Override
+    public Attribute visitByte(byte b, Void p) {
+      TypeMirror byteType = modelTypes.getPrimitiveType(TypeKind.BYTE);
+      return new Attribute.Constant((Type) byteType, b);
+    }
+
+    @Override
+    public Attribute visitChar(char c, Void p) {
+      TypeMirror charType = modelTypes.getPrimitiveType(TypeKind.CHAR);
+      return new Attribute.Constant((Type) charType, c);
+    }
+
+    @Override
+    public Attribute visitDouble(double d, Void p) {
+      TypeMirror doubleType = modelTypes.getPrimitiveType(TypeKind.DOUBLE);
+      return new Attribute.Constant((Type) doubleType, d);
+    }
+
+    @Override
+    public Attribute visitFloat(float f, Void p) {
+      TypeMirror floatType = modelTypes.getPrimitiveType(TypeKind.FLOAT);
+      return new Attribute.Constant((Type) floatType, f);
+    }
+
+    @Override
+    public Attribute visitInt(int i, Void p) {
+      TypeMirror intType = modelTypes.getPrimitiveType(TypeKind.INT);
+      return new Attribute.Constant((Type) intType, i);
+    }
+
+    @Override
+    public Attribute visitLong(long i, Void p) {
+      TypeMirror longType = modelTypes.getPrimitiveType(TypeKind.LONG);
+      return new Attribute.Constant((Type) longType, i);
+    }
+
+    @Override
+    public Attribute visitShort(short s, Void p) {
+      TypeMirror shortType = modelTypes.getPrimitiveType(TypeKind.SHORT);
+      return new Attribute.Constant((Type) shortType, s);
+    }
+
+    @Override
+    public Attribute visitString(String s, Void p) {
+      TypeMirror stringType = elements.getTypeElement("java.lang.String").asType();
+      return new Attribute.Constant((Type) stringType, s);
+    }
+
+    @Override
+    public Attribute visitType(TypeMirror t, Void p) {
+      if (t instanceof Type) {
+        return new Attribute.Class(javacTypes, (Type) t);
+      } else {
+        throw new BugInCF("Unexpected type of TypeMirror: " + t.getClass());
+      }
+    }
+
+    @Override
+    public Attribute visitEnumConstant(VariableElement c, Void p) {
+      if (c instanceof Symbol.VarSymbol) {
+        Symbol.VarSymbol sym = (Symbol.VarSymbol) c;
+        if (sym.getKind() == ElementKind.ENUM_CONSTANT) {
+          return new Attribute.Enum(sym.type, sym);
+        }
+      }
+      throw new BugInCF("Unexpected type of VariableElement: " + c.getClass());
+    }
+
+    @Override
+    public Attribute visitAnnotation(AnnotationMirror a, Void p) {
+      return createCompoundFromAnnotationMirror(a, processingEnv);
+    }
+
+    @Override
+    public Attribute visitArray(java.util.List<? extends AnnotationValue> vals, Void p) {
+      if (!vals.isEmpty()) {
+        List<Attribute> valAttrs = List.nil();
+        for (AnnotationValue av : vals) {
+          valAttrs = valAttrs.append(av.accept(this, p));
+        }
+        ArrayType arrayType = modelTypes.getArrayType(valAttrs.get(0).type);
+        return new Attribute.Array((Type) arrayType, valAttrs);
+      } else {
+        return new Attribute.Array((Type) meth.getReturnType(), List.nil());
+      }
+    }
+
+    @Override
+    public Attribute visitUnknown(AnnotationValue av, Void p) {
+      throw new BugInCF("Unexpected type of AnnotationValue: " + av.getClass());
+    }
+  }
+
+  /**
+   * Create an unknown TypeAnnotationPosition.
+   *
+   * @return an unkown TypeAnnotationPosition
+   */
+  public static TypeAnnotationPosition unknownTAPosition() {
+    return TypeAnnotationPosition.unknown;
+  }
+
+  /**
+   * Create a method return TypeAnnotationPosition.
+   *
+   * @param pos the source tree position
+   * @return a method return TypeAnnotationPosition
+   */
+  public static TypeAnnotationPosition methodReturnTAPosition(final int pos) {
+    return TypeAnnotationPosition.methodReturn(pos);
+  }
+
+  /**
+   * Create a method receiver TypeAnnotationPosition.
+   *
+   * @param pos the source tree position
+   * @return a method receiver TypeAnnotationPosition
+   */
+  public static TypeAnnotationPosition methodReceiverTAPosition(final int pos) {
+    return TypeAnnotationPosition.methodReceiver(pos);
+  }
+
+  /**
+   * Create a method parameter TypeAnnotationPosition.
+   *
+   * @param pidx the parameter index
+   * @param pos the source tree position
+   * @return a method parameter TypeAnnotationPosition
+   */
+  public static TypeAnnotationPosition methodParameterTAPosition(final int pidx, final int pos) {
+    return TypeAnnotationPosition.methodParameter(pidx, pos);
+  }
+
+  /**
+   * Create a method throws TypeAnnotationPosition.
+   *
+   * @param tidx the throws index
+   * @param pos the source tree position
+   * @return a method throws TypeAnnotationPosition
+   */
+  public static TypeAnnotationPosition methodThrowsTAPosition(final int tidx, final int pos) {
+    return TypeAnnotationPosition.methodThrows(TypeAnnotationPosition.emptyPath, null, tidx, pos);
+  }
+
+  /**
+   * Create a field TypeAnnotationPosition.
+   *
+   * @param pos the source tree position
+   * @return a field TypeAnnotationPosition
+   */
+  public static TypeAnnotationPosition fieldTAPosition(final int pos) {
+    return TypeAnnotationPosition.field(pos);
+  }
+
+  /**
+   * Create a class extends TypeAnnotationPosition.
+   *
+   * @param implidx the class extends index
+   * @param pos the source tree position
+   * @return a class extends TypeAnnotationPosition
+   */
+  public static TypeAnnotationPosition classExtendsTAPosition(final int implidx, final int pos) {
+    return TypeAnnotationPosition.classExtends(implidx, pos);
+  }
+
+  /**
+   * Create a type parameter TypeAnnotationPosition.
+   *
+   * @param tpidx the type parameter index
+   * @param pos the source tree position
+   * @return a type parameter TypeAnnotationPosition
+   */
+  public static TypeAnnotationPosition typeParameterTAPosition(final int tpidx, final int pos) {
+    return TypeAnnotationPosition.typeParameter(TypeAnnotationPosition.emptyPath, null, tpidx, pos);
+  }
+
+  /**
+   * Create a method type parameter TypeAnnotationPosition.
+   *
+   * @param tpidx the method type parameter index
+   * @param pos the source tree position
+   * @return a method type parameter TypeAnnotationPosition
+   */
+  public static TypeAnnotationPosition methodTypeParameterTAPosition(
+      final int tpidx, final int pos) {
+    return TypeAnnotationPosition.methodTypeParameter(
+        TypeAnnotationPosition.emptyPath, null, tpidx, pos);
+  }
+
+  /**
+   * Create a type parameter bound TypeAnnotationPosition.
+   *
+   * @param tpidx the type parameter index
+   * @param bndidx the bound index
+   * @param pos the source tree position
+   * @return a method parameter TypeAnnotationPosition
+   */
+  public static TypeAnnotationPosition typeParameterBoundTAPosition(
+      final int tpidx, final int bndidx, final int pos) {
+    return TypeAnnotationPosition.typeParameterBound(
+        TypeAnnotationPosition.emptyPath, null, tpidx, bndidx, pos);
+  }
+
+  /**
+   * Create a method type parameter bound TypeAnnotationPosition.
+   *
+   * @param tpidx the type parameter index
+   * @param bndidx the bound index
+   * @param pos the source tree position
+   * @return a method parameter TypeAnnotationPosition
+   */
+  public static TypeAnnotationPosition methodTypeParameterBoundTAPosition(
+      final int tpidx, final int bndidx, final int pos) {
+    return TypeAnnotationPosition.methodTypeParameterBound(
+        TypeAnnotationPosition.emptyPath, null, tpidx, bndidx, pos);
+  }
+
+  /**
+   * Copy a TypeAnnotationPosition.
+   *
+   * @param tapos the input TypeAnnotationPosition
+   * @return a copied TypeAnnotationPosition
+   */
+  public static TypeAnnotationPosition copyTAPosition(final TypeAnnotationPosition tapos) {
+    TypeAnnotationPosition res;
+    switch (tapos.type) {
+      case CAST:
+        res =
+            TypeAnnotationPosition.typeCast(
+                tapos.location, tapos.onLambda, tapos.type_index, tapos.pos);
+        break;
+      case CLASS_EXTENDS:
+        res =
+            TypeAnnotationPosition.classExtends(
+                tapos.location, tapos.onLambda, tapos.type_index, tapos.pos);
+        break;
+      case CLASS_TYPE_PARAMETER:
+        res =
+            TypeAnnotationPosition.typeParameter(
+                tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos);
+        break;
+      case CLASS_TYPE_PARAMETER_BOUND:
+        res =
+            TypeAnnotationPosition.typeParameterBound(
+                tapos.location,
+                tapos.onLambda,
+                tapos.parameter_index,
+                tapos.bound_index,
+                tapos.pos);
+        break;
+      case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
+        res =
+            TypeAnnotationPosition.constructorInvocationTypeArg(
+                tapos.location, tapos.onLambda, tapos.type_index, tapos.pos);
+        break;
+      case CONSTRUCTOR_REFERENCE:
+        res = TypeAnnotationPosition.constructorRef(tapos.location, tapos.onLambda, tapos.pos);
+        break;
+      case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
+        res =
+            TypeAnnotationPosition.constructorRefTypeArg(
+                tapos.location, tapos.onLambda, tapos.type_index, tapos.pos);
+        break;
+      case EXCEPTION_PARAMETER:
+        res = TypeAnnotationPosition.exceptionParameter(tapos.location, tapos.onLambda, tapos.pos);
+        break;
+      case FIELD:
+        res = TypeAnnotationPosition.field(tapos.location, tapos.onLambda, tapos.pos);
+        break;
+      case INSTANCEOF:
+        res = TypeAnnotationPosition.instanceOf(tapos.location, tapos.onLambda, tapos.pos);
+        break;
+      case LOCAL_VARIABLE:
+        res = TypeAnnotationPosition.localVariable(tapos.location, tapos.onLambda, tapos.pos);
+        break;
+      case METHOD_FORMAL_PARAMETER:
+        res =
+            TypeAnnotationPosition.methodParameter(
+                tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos);
+        break;
+      case METHOD_INVOCATION_TYPE_ARGUMENT:
+        res =
+            TypeAnnotationPosition.methodInvocationTypeArg(
+                tapos.location, tapos.onLambda, tapos.type_index, tapos.pos);
+        break;
+      case METHOD_RECEIVER:
+        res = TypeAnnotationPosition.methodReceiver(tapos.location, tapos.onLambda, tapos.pos);
+        break;
+      case METHOD_REFERENCE:
+        res = TypeAnnotationPosition.methodRef(tapos.location, tapos.onLambda, tapos.pos);
+        break;
+      case METHOD_REFERENCE_TYPE_ARGUMENT:
+        res =
+            TypeAnnotationPosition.methodRefTypeArg(
+                tapos.location, tapos.onLambda, tapos.type_index, tapos.pos);
+        break;
+      case METHOD_RETURN:
+        res = TypeAnnotationPosition.methodReturn(tapos.location, tapos.onLambda, tapos.pos);
+        break;
+      case METHOD_TYPE_PARAMETER:
+        res =
+            TypeAnnotationPosition.methodTypeParameter(
+                tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos);
+        break;
+      case METHOD_TYPE_PARAMETER_BOUND:
+        res =
+            TypeAnnotationPosition.methodTypeParameterBound(
+                tapos.location,
+                tapos.onLambda,
+                tapos.parameter_index,
+                tapos.bound_index,
+                tapos.pos);
+        break;
+      case NEW:
+        res = TypeAnnotationPosition.newObj(tapos.location, tapos.onLambda, tapos.pos);
+        break;
+      case RESOURCE_VARIABLE:
+        res = TypeAnnotationPosition.resourceVariable(tapos.location, tapos.onLambda, tapos.pos);
+        break;
+      case THROWS:
+        res =
+            TypeAnnotationPosition.methodThrows(
+                tapos.location, tapos.onLambda, tapos.type_index, tapos.pos);
+        break;
+      case UNKNOWN:
+      default:
+        throw new BugInCF("Unexpected target type: " + tapos + " at " + tapos.type);
+    }
+    return res;
+  }
+
+  /**
+   * Remove type annotations from the given type.
+   *
+   * @param in the input type
+   * @return the same underlying type, but without type annotations
+   */
+  public static Type unannotatedType(final TypeMirror in) {
+    final Type impl = (Type) in;
+    if (impl.isPrimitive()) {
+      // TODO: file an issue that stripMetadata doesn't work for primitives.
+      // See eisop/checker-framework issue #21.
+      return impl.baseType();
+    } else {
+      return impl.stripMetadata();
+    }
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java
new file mode 100644
index 0000000..b79225f
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java
@@ -0,0 +1,295 @@
+package org.checkerframework.javacutil;
+
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/** A utility class that helps with {@link TypeKind}s. */
+public final class TypeKindUtils {
+
+  /** This class cannot be instantiated. */
+  private TypeKindUtils() {
+    throw new AssertionError("Class TypeKindUtils cannot be instantiated.");
+  }
+
+  /**
+   * Return true if the argument is one of INT, SHORT, BYTE, CHAR, LONG.
+   *
+   * @param typeKind the TypeKind to inspect
+   * @return true if typeKind is a primitive integral type kind
+   */
+  public static boolean isIntegral(TypeKind typeKind) {
+    switch (typeKind) {
+      case INT:
+      case SHORT:
+      case BYTE:
+      case CHAR:
+      case LONG:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Return true if the argument is one of FLOAT, DOUBLE.
+   *
+   * @param typeKind the TypeKind to inspect
+   * @return true if typeKind is a primitive floating point type kind
+   */
+  public static boolean isFloatingPoint(TypeKind typeKind) {
+    switch (typeKind) {
+      case FLOAT:
+      case DOUBLE:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Returns true iff the argument is a primitive numeric type kind.
+   *
+   * @param typeKind a type kind
+   * @return true if the argument is a primitive numeric type kind
+   */
+  public static boolean isNumeric(TypeKind typeKind) {
+    switch (typeKind) {
+      case BYTE:
+      case CHAR:
+      case DOUBLE:
+      case FLOAT:
+      case INT:
+      case LONG:
+      case SHORT:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  // Cannot create an overload that takes an AnnotatedTypeMirror because the javacutil
+  // package must not depend on the framework package.
+  /**
+   * Given a primitive type, return its kind. Given a boxed primitive type, return the corresponding
+   * primitive type kind. Otherwise, return null.
+   *
+   * @param type a primitive or boxed primitive type
+   * @return a primitive type kind, or null
+   */
+  public static @Nullable TypeKind primitiveOrBoxedToTypeKind(TypeMirror type) {
+    final TypeKind typeKind = type.getKind();
+    if (typeKind.isPrimitive()) {
+      return typeKind;
+    }
+
+    if (!(type instanceof DeclaredType)) {
+      return null;
+    }
+
+    final String typeString = TypesUtils.getQualifiedName((DeclaredType) type).toString();
+
+    switch (typeString) {
+      case "java.lang.Byte":
+        return TypeKind.BYTE;
+      case "java.lang.Boolean":
+        return TypeKind.BOOLEAN;
+      case "java.lang.Character":
+        return TypeKind.CHAR;
+      case "java.lang.Double":
+        return TypeKind.DOUBLE;
+      case "java.lang.Float":
+        return TypeKind.FLOAT;
+      case "java.lang.Integer":
+        return TypeKind.INT;
+      case "java.lang.Long":
+        return TypeKind.LONG;
+      case "java.lang.Short":
+        return TypeKind.SHORT;
+      default:
+        return null;
+    }
+  }
+
+  // No overload that takes AnnotatedTypeMirror becasue javacutil cannot depend on framework.
+  /**
+   * Returns the widened numeric type for an arithmetic operation performed on a value of the left
+   * type and the right type. Defined in JLS 5.6.2. We return a {@link TypeKind} because creating a
+   * {@link TypeMirror} requires a {@link javax.lang.model.util.Types} object from the {@link
+   * javax.annotation.processing.ProcessingEnvironment}.
+   *
+   * @param left a type mirror
+   * @param right a type mirror
+   * @return the result of widening numeric conversion, or NONE when the conversion cannot be
+   *     performed
+   */
+  public static TypeKind widenedNumericType(TypeMirror left, TypeMirror right) {
+    return widenedNumericType(left.getKind(), right.getKind());
+  }
+
+  /**
+   * Given two type kinds, return the type kind they are widened to, when an arithmetic operation is
+   * performed on them. Defined in JLS 5.6.2.
+   *
+   * @param a a type kind
+   * @param b a type kind
+   * @return the type kind to which they are widened, when an operation is performed on them
+   */
+  public static TypeKind widenedNumericType(TypeKind a, TypeKind b) {
+    if (!isNumeric(a) || !isNumeric(b)) {
+      return TypeKind.NONE;
+    }
+
+    if (a == TypeKind.DOUBLE || b == TypeKind.DOUBLE) {
+      return TypeKind.DOUBLE;
+    }
+
+    if (a == TypeKind.FLOAT || b == TypeKind.FLOAT) {
+      return TypeKind.FLOAT;
+    }
+
+    if (a == TypeKind.LONG || b == TypeKind.LONG) {
+      return TypeKind.LONG;
+    }
+
+    return TypeKind.INT;
+  }
+
+  /** The type of primitive conversion: narrowing, widening, or same. */
+  public enum PrimitiveConversionKind {
+    /** The two primitive kinds are the same. */
+    SAME,
+    /**
+     * The conversion is a widening primitive conversion.
+     *
+     * <p>This includes byte to char, even though that is strictly a "widening and narrowing
+     * primitive conversion", according to JLS 5.1.4.
+     */
+    WIDENING,
+    /** The conversion is a narrowing primitive conversion. */
+    NARROWING
+  }
+
+  /**
+   * Return the type of primitive conversion between {@code from} and {@code to}.
+   *
+   * <p>The narrowing conversions include both short to char and char to short.
+   *
+   * @param from a primitive type
+   * @param to a primitive type
+   * @return the type of primitive conversion between {@code from} and {@code to}
+   */
+  public static PrimitiveConversionKind getPrimitiveConversionKind(TypeKind from, TypeKind to) {
+    if (from == TypeKind.BOOLEAN && to == TypeKind.BOOLEAN) {
+      return PrimitiveConversionKind.SAME;
+    }
+
+    assert (isIntegral(from) || isFloatingPoint(from)) && (isIntegral(to) || isFloatingPoint(to))
+        : "getPrimitiveConversionKind " + from + " " + to;
+
+    if (from == to) {
+      return PrimitiveConversionKind.SAME;
+    }
+
+    boolean fromIntegral = isIntegral(from);
+    boolean toFloatingPoint = isFloatingPoint(to);
+    if (fromIntegral && toFloatingPoint) {
+      return PrimitiveConversionKind.WIDENING;
+    }
+
+    boolean toIntegral = isIntegral(to);
+    boolean fromFloatingPoint = isFloatingPoint(from);
+    if (fromFloatingPoint && toIntegral) {
+      return PrimitiveConversionKind.NARROWING;
+    }
+
+    if (numBits(from) < numBits(to)) {
+      return PrimitiveConversionKind.WIDENING;
+    } else {
+      // If same number of bits (char to short or short to char), it is a narrowing.
+      return PrimitiveConversionKind.NARROWING;
+    }
+  }
+
+  /**
+   * Returns the number of bits in the representation of a primitive type. Returns -1 if the type is
+   * not a primitive type.
+   *
+   * @param tk a primitive type kind
+   * @return the number of bits in its representation, or -1 if not integral
+   */
+  private static int numBits(TypeKind tk) {
+    switch (tk) {
+      case BYTE:
+        return 8;
+      case SHORT:
+        return 16;
+      case CHAR:
+        return 16;
+      case INT:
+        return 32;
+      case LONG:
+        return 64;
+      case FLOAT:
+        return 32;
+      case DOUBLE:
+        return 64;
+      case BOOLEAN:
+      default:
+        return -1;
+    }
+  }
+
+  /**
+   * Returns the minimum value representable by the given ingetgral primitive type.
+   *
+   * @param tk a primitive type kind
+   * @return the minimum value representable by the given integral primitive type.
+   */
+  public static long minValue(TypeKind tk) {
+    switch (tk) {
+      case BYTE:
+        return Byte.MIN_VALUE;
+      case SHORT:
+        return Short.MIN_VALUE;
+      case CHAR:
+        return Character.MIN_VALUE;
+      case INT:
+        return Integer.MIN_VALUE;
+      case LONG:
+        return Long.MIN_VALUE;
+      case FLOAT:
+      case DOUBLE:
+      case BOOLEAN:
+      default:
+        throw new BugInCF(tk + " does not have a minimum value");
+    }
+  }
+
+  /**
+   * Returns the maximum value representable by the given integral primitive type.
+   *
+   * @param tk a primitive type kind
+   * @return the maximum value representable by the given integral primitive type.
+   */
+  public static long maxValue(TypeKind tk) {
+    switch (tk) {
+      case BYTE:
+        return Byte.MAX_VALUE;
+      case SHORT:
+        return Short.MAX_VALUE;
+      case CHAR:
+        return Character.MAX_VALUE;
+      case INT:
+        return Integer.MAX_VALUE;
+      case LONG:
+        return Long.MAX_VALUE;
+      case FLOAT:
+      case DOUBLE:
+      case BOOLEAN:
+      default:
+        throw new BugInCF(tk + " does not have a maximum value");
+    }
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java
new file mode 100644
index 0000000..4c84f6d
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java
@@ -0,0 +1,36 @@
+package org.checkerframework.javacutil;
+
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Exception type indicating a mistake by a type system built using the Checker Framework. For
+ * example, misusing a meta-annotation on a qualifier. These should only be thrown by the framework
+ * package.
+ */
+@SuppressWarnings("serial")
+public class TypeSystemError extends RuntimeException {
+
+  /**
+   * Constructs a new TypeSystemError with the specified detail message.
+   *
+   * @param message the detail message
+   */
+  public TypeSystemError(String message) {
+    super(message);
+    if (message == null) {
+      throw new Error("Must have a detail message.");
+    }
+  }
+
+  /**
+   * Constructs a new TypeSystemError with a detail message composed from the given arguments.
+   *
+   * @param fmt the format string
+   * @param args the arguments for the format string
+   */
+  @FormatMethod
+  public TypeSystemError(String fmt, @Nullable Object... args) {
+    this(String.format(fmt, args));
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java
new file mode 100644
index 0000000..3a558ce
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java
@@ -0,0 +1,1053 @@
+package org.checkerframework.javacutil;
+
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symtab;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.CapturedType;
+import com.sun.tools.javac.code.Type.ClassType;
+import com.sun.tools.javac.code.TypeTag;
+import com.sun.tools.javac.model.JavacTypes;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Context;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.NestingKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.UnionType;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.signature.qual.BinaryName;
+import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty;
+import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
+import org.checkerframework.checker.signature.qual.FullyQualifiedName;
+import org.plumelib.util.CollectionsPlume;
+import org.plumelib.util.ImmutableTypes;
+
+/**
+ * A utility class that helps with {@link TypeMirror}s. It complements {@link Types}, providing
+ * methods that {@link Types} does not.
+ */
+public final class TypesUtils {
+
+  /** Class cannot be instantiated. */
+  private TypesUtils() {
+    throw new AssertionError("Class TypesUtils cannot be instantiated.");
+  }
+
+  /// Creating types
+
+  /**
+   * Returns the {@link TypeMirror} for a given {@link Class}.
+   *
+   * @param clazz a class
+   * @param types the type utilities
+   * @param elements the element utiliites
+   * @return the TypeMirror for {@code clazz}
+   */
+  public static TypeMirror typeFromClass(Class<?> clazz, Types types, Elements elements) {
+    if (clazz == void.class) {
+      return types.getNoType(TypeKind.VOID);
+    } else if (clazz.isPrimitive()) {
+      String primitiveName = clazz.getName().toUpperCase();
+      TypeKind primitiveKind = TypeKind.valueOf(primitiveName);
+      return types.getPrimitiveType(primitiveKind);
+    } else if (clazz.isArray()) {
+      TypeMirror componentType = typeFromClass(clazz.getComponentType(), types, elements);
+      return types.getArrayType(componentType);
+    } else {
+      String name = clazz.getCanonicalName();
+      assert name != null : "@AssumeAssertion(nullness): assumption";
+      TypeElement element = elements.getTypeElement(name);
+      if (element == null) {
+        throw new BugInCF("Unrecognized class: " + clazz);
+      }
+      return element.asType();
+    }
+  }
+
+  /**
+   * Returns an {@link ArrayType} with elements of type {@code componentType}.
+   *
+   * @param componentType the component type of the created array type
+   * @param types the type utilities
+   * @return an {@link ArrayType} whose elements have type {@code componentType}
+   */
+  public static ArrayType createArrayType(TypeMirror componentType, Types types) {
+    JavacTypes t = (JavacTypes) types;
+    return t.getArrayType(componentType);
+  }
+
+  /// Creating a Class<?>
+
+  /**
+   * Returns the {@link Class} for a given {@link TypeMirror}. Returns {@code Object.class} if it
+   * cannot determine anything more specific.
+   *
+   * @param typeMirror a TypeMirror
+   * @return the class for {@code typeMirror}
+   */
+  public static Class<?> getClassFromType(TypeMirror typeMirror) {
+
+    switch (typeMirror.getKind()) {
+      case INT:
+        return int.class;
+      case LONG:
+        return long.class;
+      case SHORT:
+        return short.class;
+      case BYTE:
+        return byte.class;
+      case CHAR:
+        return char.class;
+      case DOUBLE:
+        return double.class;
+      case FLOAT:
+        return float.class;
+      case BOOLEAN:
+        return boolean.class;
+
+      case ARRAY:
+        Class<?> componentClass = getClassFromType(((ArrayType) typeMirror).getComponentType());
+        // In Java 12, use this instead:
+        // return fooClass.arrayType();
+        return java.lang.reflect.Array.newInstance(componentClass, 0).getClass();
+
+      case DECLARED:
+        // BUG: need to compute a @ClassGetName, but this code computes a
+        // @CanonicalNameOrEmpty.  They are different for inner classes.
+        @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Names.toString
+        @DotSeparatedIdentifiers String typeString = TypesUtils.getQualifiedName((DeclaredType) typeMirror).toString();
+        if (typeString.equals("<nulltype>")) {
+          return void.class;
+        }
+
+        try {
+          return Class.forName(typeString);
+        } catch (ClassNotFoundException | UnsupportedClassVersionError e) {
+          return Object.class;
+        }
+
+      default:
+        return Object.class;
+    }
+  }
+
+  /// Getters
+
+  /**
+   * Gets the fully qualified name for a provided type. It returns an empty name if type is an
+   * anonymous type.
+   *
+   * @param type the declared type
+   * @return the name corresponding to that type
+   */
+  @SuppressWarnings("signature:return") // todo: add fake override of Name.toString.
+  public static @CanonicalNameOrEmpty String getQualifiedName(DeclaredType type) {
+    TypeElement element = (TypeElement) type.asElement();
+    @CanonicalNameOrEmpty Name name = element.getQualifiedName();
+    return name.toString();
+  }
+
+  /**
+   * Returns the simple type name, without annotations.
+   *
+   * @param type a type
+   * @return the simple type name, without annotations
+   */
+  public static String simpleTypeName(TypeMirror type) {
+    switch (type.getKind()) {
+      case ARRAY:
+        return simpleTypeName(((ArrayType) type).getComponentType()) + "[]";
+      case TYPEVAR:
+        return ((TypeVariable) type).asElement().getSimpleName().toString();
+      case DECLARED:
+        return ((DeclaredType) type).asElement().getSimpleName().toString();
+      case NULL:
+        return "<nulltype>";
+      case VOID:
+        return "void";
+      case WILDCARD:
+        WildcardType wildcard = (WildcardType) type;
+        TypeMirror extendsBound = wildcard.getExtendsBound();
+        TypeMirror superBound = wildcard.getSuperBound();
+        return "?"
+            + (extendsBound != null ? " extends " + simpleTypeName(extendsBound) : "")
+            + (superBound != null ? " super " + simpleTypeName(superBound) : "");
+      case UNION:
+        StringJoiner sj = new StringJoiner(" | ");
+        for (TypeMirror alternative : ((UnionType) type).getAlternatives()) {
+          sj.add(simpleTypeName(alternative));
+        }
+        return sj.toString();
+      default:
+        if (type.getKind().isPrimitive()) {
+          return TypeAnnotationUtils.unannotatedType(type).toString();
+        } else {
+          throw new BugInCF(
+              "simpleTypeName: unhandled type kind: %s, type: %s", type.getKind(), type);
+        }
+    }
+  }
+
+  /**
+   * Returns the binary name.
+   *
+   * @param type a type
+   * @return the binary name
+   */
+  public static @BinaryName String binaryName(TypeMirror type) {
+    if (type.getKind() != TypeKind.DECLARED) {
+      throw new BugInCF("Only declared types have a binary name");
+    }
+    return ElementUtils.getBinaryName((TypeElement) ((DeclaredType) type).asElement());
+  }
+
+  /**
+   * Returns the type element for {@code type} if {@code type} is a class, interface, annotation
+   * type, or enum. Otherwise, returns null.
+   *
+   * @param type whose element is returned
+   * @return the type element for {@code type} if {@code type} is a class, interface, annotation
+   *     type, or enum; otherwise, returns {@code null}
+   */
+  public static @Nullable TypeElement getTypeElement(TypeMirror type) {
+    Element element = ((Type) type).asElement();
+    if (element == null) {
+      return null;
+    }
+    if (ElementUtils.isTypeElement(element)) {
+      return (TypeElement) element;
+    }
+    return null;
+  }
+
+  /**
+   * Given an array type, returns the type with all array levels stripped off.
+   *
+   * @param at an array type
+   * @return the type with all array levels stripped off
+   */
+  public static TypeMirror getInnermostComponentType(ArrayType at) {
+    TypeMirror result = at;
+    while (result.getKind() == TypeKind.ARRAY) {
+      result = ((ArrayType) result).getComponentType();
+    }
+    return result;
+  }
+
+  /// Equality
+
+  /**
+   * Returns true iff the arguments are both the same declared types.
+   *
+   * <p>This is needed because class {@code Type.ClassType} does not override equals.
+   *
+   * @param t1 the first type to test
+   * @param t2 the second type to test
+   * @return whether the arguments are the same declared types
+   */
+  public static boolean areSameDeclaredTypes(Type.ClassType t1, Type.ClassType t2) {
+    // Do a cheaper test first
+    if (t1.tsym.name != t2.tsym.name) {
+      return false;
+    }
+    return t1.toString().equals(t1.toString());
+  }
+
+  /**
+   * Returns true iff the arguments are both the same primitive type.
+   *
+   * @param left a type
+   * @param right a type
+   * @return whether the arguments are the same primitive type
+   */
+  public static boolean areSamePrimitiveTypes(TypeMirror left, TypeMirror right) {
+    if (!isPrimitive(left) || !isPrimitive(right)) {
+      return false;
+    }
+
+    return (left.getKind() == right.getKind());
+  }
+
+  /// Predicates
+
+  /**
+   * Checks if the type represents a java.lang.Object declared type.
+   *
+   * @param type the type
+   * @return true iff type represents java.lang.Object
+   */
+  public static boolean isObject(TypeMirror type) {
+    return isDeclaredOfName(type, "java.lang.Object");
+  }
+
+  /**
+   * Checks if the type represents the java.lang.Class declared type.
+   *
+   * @param type the type
+   * @return true iff type represents java.lang.Class
+   */
+  public static boolean isClass(TypeMirror type) {
+    return isDeclaredOfName(type, "java.lang.Class");
+  }
+
+  /**
+   * Checks if the type represents a java.lang.String declared type.
+   *
+   * @param type the type
+   * @return true iff type represents java.lang.String
+   */
+  public static boolean isString(TypeMirror type) {
+    return isDeclaredOfName(type, "java.lang.String");
+  }
+
+  /**
+   * Checks if the type represents a boolean type, that is either boolean (primitive type) or
+   * java.lang.Boolean.
+   *
+   * @param type the type to test
+   * @return true iff type represents a boolean type
+   */
+  public static boolean isBooleanType(TypeMirror type) {
+    return isDeclaredOfName(type, "java.lang.Boolean") || type.getKind() == TypeKind.BOOLEAN;
+  }
+
+  /**
+   * Check if the type represents a declared type of the given qualified name.
+   *
+   * @param type the type
+   * @return type iff type represents a declared type of the qualified name
+   */
+  public static boolean isDeclaredOfName(TypeMirror type, CharSequence qualifiedName) {
+    return type.getKind() == TypeKind.DECLARED
+        && getQualifiedName((DeclaredType) type).contentEquals(qualifiedName);
+  }
+
+  public static boolean isBoxedPrimitive(TypeMirror type) {
+    if (type.getKind() != TypeKind.DECLARED) {
+      return false;
+    }
+
+    String qualifiedName = getQualifiedName((DeclaredType) type).toString();
+
+    return (qualifiedName.equals("java.lang.Boolean")
+        || qualifiedName.equals("java.lang.Byte")
+        || qualifiedName.equals("java.lang.Character")
+        || qualifiedName.equals("java.lang.Short")
+        || qualifiedName.equals("java.lang.Integer")
+        || qualifiedName.equals("java.lang.Long")
+        || qualifiedName.equals("java.lang.Double")
+        || qualifiedName.equals("java.lang.Float"));
+  }
+
+  /**
+   * Return true if this is an immutable type in the JDK.
+   *
+   * <p>This does not use immutability annotations and always returns false for user-defined
+   * classes.
+   */
+  public static boolean isImmutableTypeInJdk(TypeMirror type) {
+    return isPrimitive(type)
+        || (type.getKind() == TypeKind.DECLARED
+            && ImmutableTypes.isImmutable(getQualifiedName((DeclaredType) type).toString()));
+  }
+
+  /**
+   * Returns true if type represents a Throwable type (e.g. Exception, Error).
+   *
+   * @return true if type represents a Throwable type (e.g. Exception, Error)
+   */
+  public static boolean isThrowable(TypeMirror type) {
+    while (type != null && type.getKind() == TypeKind.DECLARED) {
+      DeclaredType dt = (DeclaredType) type;
+      TypeElement elem = (TypeElement) dt.asElement();
+      Name name = elem.getQualifiedName();
+      if ("java.lang.Throwable".contentEquals(name)) {
+        return true;
+      }
+      type = elem.getSuperclass();
+    }
+    return false;
+  }
+
+  /**
+   * Returns true iff the argument is an anonymous type.
+   *
+   * @return whether the argument is an anonymous type
+   */
+  public static boolean isAnonymous(TypeMirror type) {
+    return (type instanceof DeclaredType)
+        && ((TypeElement) ((DeclaredType) type).asElement()).getNestingKind()
+            == NestingKind.ANONYMOUS;
+  }
+
+  /**
+   * Returns true iff the argument is a primitive type.
+   *
+   * @return whether the argument is a primitive type
+   */
+  public static boolean isPrimitive(TypeMirror type) {
+    switch (type.getKind()) {
+      case BOOLEAN:
+      case BYTE:
+      case CHAR:
+      case DOUBLE:
+      case FLOAT:
+      case INT:
+      case LONG:
+      case SHORT:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Returns true iff the argument is a primitive type or a boxed primitive type.
+   *
+   * @param type a type
+   * @return true if the argument is a primitive type or a boxed primitive type
+   */
+  public static boolean isPrimitiveOrBoxed(TypeMirror type) {
+    switch (type.getKind()) {
+      case BOOLEAN:
+      case BYTE:
+      case CHAR:
+      case DOUBLE:
+      case FLOAT:
+      case INT:
+      case LONG:
+      case SHORT:
+        return true;
+
+      case DECLARED:
+        String qualifiedName = getQualifiedName((DeclaredType) type).toString();
+        return (qualifiedName.equals("java.lang.Boolean")
+            || qualifiedName.equals("java.lang.Byte")
+            || qualifiedName.equals("java.lang.Character")
+            || qualifiedName.equals("java.lang.Short")
+            || qualifiedName.equals("java.lang.Integer")
+            || qualifiedName.equals("java.lang.Long")
+            || qualifiedName.equals("java.lang.Double")
+            || qualifiedName.equals("java.lang.Float"));
+
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Returns true iff the argument is a primitive numeric type.
+   *
+   * @param type a type
+   * @return true if the argument is a primitive numeric type
+   */
+  public static boolean isNumeric(TypeMirror type) {
+    return TypeKindUtils.isNumeric(type.getKind());
+  }
+
+  /** The fully-qualified names of the numeric boxed types. */
+  static final Set<@FullyQualifiedName String> numericBoxedTypes =
+      new HashSet<>(
+          Arrays.asList(
+              "java.lang.Byte",
+              "java.lang.Character",
+              "java.lang.Short",
+              "java.lang.Integer",
+              "java.lang.Long",
+              "java.lang.Double",
+              "java.lang.Float"));
+
+  /**
+   * Returns true iff the argument is a boxed numeric type.
+   *
+   * @param type a type
+   * @return true if the argument is a boxed numeric type
+   */
+  public static boolean isNumericBoxed(TypeMirror type) {
+    return type.getKind() == TypeKind.DECLARED
+        && numericBoxedTypes.contains(getQualifiedName((DeclaredType) type).toString());
+  }
+
+  /**
+   * Returns true iff the argument is an integral primitive type.
+   *
+   * @param type a type
+   * @return whether the argument is an integral primitive type
+   */
+  public static boolean isIntegralPrimitive(TypeMirror type) {
+    switch (type.getKind()) {
+      case BYTE:
+      case CHAR:
+      case INT:
+      case LONG:
+      case SHORT:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Return true if the argument TypeMirror is a (possibly boxed) integral type.
+   *
+   * @param type the type to inspect
+   * @return true if type is an integral type
+   */
+  public static boolean isIntegralPrimitiveOrBoxed(TypeMirror type) {
+    TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type);
+    return kind != null && TypeKindUtils.isIntegral(kind);
+  }
+
+  /**
+   * Returns true if declaredType is a Class that is used to box primitive type (e.g.
+   * declaredType=java.lang.Double and primitiveType=22.5d )
+   *
+   * @param declaredType a type that might be a boxed type
+   * @param primitiveType a type that might be a primitive type
+   * @return true if {@code declaredType} is a box of {@code primitiveType}
+   */
+  public static boolean isBoxOf(TypeMirror declaredType, TypeMirror primitiveType) {
+    if (declaredType.getKind() != TypeKind.DECLARED) {
+      return false;
+    }
+
+    final String qualifiedName = getQualifiedName((DeclaredType) declaredType).toString();
+    switch (primitiveType.getKind()) {
+      case BOOLEAN:
+        return qualifiedName.equals("java.lang.Boolean");
+      case BYTE:
+        return qualifiedName.equals("java.lang.Byte");
+      case CHAR:
+        return qualifiedName.equals("java.lang.Character");
+      case DOUBLE:
+        return qualifiedName.equals("java.lang.Double");
+      case FLOAT:
+        return qualifiedName.equals("java.lang.Float");
+      case INT:
+        return qualifiedName.equals("java.lang.Integer");
+      case LONG:
+        return qualifiedName.equals("java.lang.Long");
+      case SHORT:
+        return qualifiedName.equals("java.lang.Short");
+
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Returns true iff the argument is a boxed floating point type.
+   *
+   * @param type type to test
+   * @return whether the argument is a boxed floating point type
+   */
+  public static boolean isBoxedFloating(TypeMirror type) {
+    if (type.getKind() != TypeKind.DECLARED) {
+      return false;
+    }
+
+    String qualifiedName = getQualifiedName((DeclaredType) type).toString();
+    return qualifiedName.equals("java.lang.Double") || qualifiedName.equals("java.lang.Float");
+  }
+
+  /**
+   * Returns true iff the argument is a primitive floating point type.
+   *
+   * @param type type mirror
+   * @return whether the argument is a primitive floating point type
+   */
+  public static boolean isFloatingPrimitive(TypeMirror type) {
+    switch (type.getKind()) {
+      case DOUBLE:
+      case FLOAT:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Return true if the argument TypeMirror is a (possibly boxed) floating point type.
+   *
+   * @param type the type to inspect
+   * @return true if type is a floating point type
+   */
+  public static boolean isFloatingPoint(TypeMirror type) {
+    TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type);
+    return kind != null && TypeKindUtils.isFloatingPoint(kind);
+  }
+
+  /**
+   * Returns whether a TypeMirror represents a class type.
+   *
+   * @param type a type that might be a class type
+   * @return true if {@code} is a class type
+   */
+  public static boolean isClassType(TypeMirror type) {
+    return (type instanceof Type.ClassType);
+  }
+
+  /**
+   * Returns true if {@code type} has an enclosing type.
+   *
+   * @param type type to checker
+   * @return true if {@code type} has an enclosing type
+   */
+  public static boolean hasEnclosingType(TypeMirror type) {
+    Type e = ((Type) type).getEnclosingType();
+    return e.getKind() != TypeKind.NONE;
+  }
+
+  /**
+   * Returns whether or not {@code type} is a functional interface type (as defined in JLS 9.8).
+   *
+   * @param type possible functional interface type
+   * @param env ProcessingEnvironment
+   * @return whether or not {@code type} is a functional interface type (as defined in JLS 9.8)
+   */
+  public static boolean isFunctionalInterface(TypeMirror type, ProcessingEnvironment env) {
+    Context ctx = ((JavacProcessingEnvironment) env).getContext();
+    com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx);
+    return javacTypes.isFunctionalInterface((Type) type);
+  }
+
+  /// Type variables and wildcards
+
+  /**
+   * If the argument is a bounded TypeVariable or WildcardType, return its non-variable,
+   * non-wildcard upper bound. Otherwise, return the type itself.
+   *
+   * @param type a type
+   * @return the non-variable, non-wildcard upper bound of a type, if it has one, or itself if it
+   *     has no bounds
+   */
+  public static TypeMirror upperBound(TypeMirror type) {
+    do {
+      if (type instanceof TypeVariable) {
+        TypeVariable tvar = (TypeVariable) type;
+        if (tvar.getUpperBound() != null) {
+          type = tvar.getUpperBound();
+        } else {
+          break;
+        }
+      } else if (type instanceof WildcardType) {
+        WildcardType wc = (WildcardType) type;
+        if (wc.getExtendsBound() != null) {
+          type = wc.getExtendsBound();
+        } else {
+          break;
+        }
+      } else {
+        break;
+      }
+    } while (true);
+    return type;
+  }
+
+  /**
+   * Get the type parameter for this wildcard from the underlying type's bound field This field is
+   * sometimes null, in that case this method will return null.
+   *
+   * @return the TypeParameterElement the wildcard is an argument to, {@code null} otherwise
+   */
+  public static @Nullable TypeParameterElement wildcardToTypeParam(
+      final Type.WildcardType wildcard) {
+
+    final Element typeParamElement;
+    if (wildcard.bound != null) {
+      typeParamElement = wildcard.bound.asElement();
+    } else {
+      typeParamElement = null;
+    }
+
+    return (TypeParameterElement) typeParamElement;
+  }
+
+  /**
+   * Version of com.sun.tools.javac.code.Types.wildUpperBound(Type) that works with both jdk8
+   * (called upperBound there) and jdk8u.
+   */
+  // TODO: contrast to upperBound.
+  public static Type wildUpperBound(TypeMirror tm, ProcessingEnvironment env) {
+    Type t = (Type) tm;
+    if (t.hasTag(TypeTag.WILDCARD)) {
+      Context context = ((JavacProcessingEnvironment) env).getContext();
+      Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t);
+      if (w.isSuperBound()) { // returns true if w is unbound
+        Symtab syms = Symtab.instance(context);
+        // w.bound is null if the wildcard is from bytecode.
+        return w.bound == null ? syms.objectType : w.bound.getUpperBound();
+      } else {
+        return wildUpperBound(w.type, env);
+      }
+    } else {
+      return TypeAnnotationUtils.unannotatedType(t);
+    }
+  }
+
+  /**
+   * Version of com.sun.tools.javac.code.Types.wildLowerBound(Type) that works with both jdk8
+   * (called upperBound there) and jdk8u.
+   */
+  public static Type wildLowerBound(TypeMirror tm, ProcessingEnvironment env) {
+    Type t = (Type) tm;
+    if (t.hasTag(TypeTag.WILDCARD)) {
+      Context context = ((JavacProcessingEnvironment) env).getContext();
+      Symtab syms = Symtab.instance(context);
+      Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t);
+      return w.isExtendsBound() ? syms.botType : wildLowerBound(w.type, env);
+    } else {
+      return TypeAnnotationUtils.unannotatedType(t);
+    }
+  }
+
+  /**
+   * Given a bounded type (wildcard or typevar) get the concrete type of its upper bound. If the
+   * bounded type extends other bounded types, this method will iterate through their bounds until a
+   * class, interface, or intersection is found.
+   *
+   * @return a type that is not a wildcard or typevar, or {@code null} if this type is an unbounded
+   *     wildcard
+   */
+  public static @Nullable TypeMirror findConcreteUpperBound(final TypeMirror boundedType) {
+    TypeMirror effectiveUpper = boundedType;
+    outerLoop:
+    while (true) {
+      switch (effectiveUpper.getKind()) {
+        case WILDCARD:
+          effectiveUpper = ((javax.lang.model.type.WildcardType) effectiveUpper).getExtendsBound();
+          if (effectiveUpper == null) {
+            return null;
+          }
+          break;
+
+        case TYPEVAR:
+          effectiveUpper = ((TypeVariable) effectiveUpper).getUpperBound();
+          break;
+
+        default:
+          break outerLoop;
+      }
+    }
+    return effectiveUpper;
+  }
+
+  /**
+   * Returns true if the erased type of subtype is a subtype of the erased type of supertype.
+   *
+   * @param subtype possible subtype
+   * @param supertype possible supertype
+   * @param types a Types object
+   * @return true if the erased type of subtype is a subtype of the erased type of supertype
+   */
+  public static boolean isErasedSubtype(TypeMirror subtype, TypeMirror supertype, Types types) {
+    return types.isSubtype(types.erasure(subtype), types.erasure(supertype));
+  }
+
+  /** Returns whether a TypeVariable represents a captured type. */
+  public static boolean isCaptured(TypeMirror typeVar) {
+    if (typeVar.getKind() != TypeKind.TYPEVAR) {
+      return false;
+    }
+    return ((Type.TypeVar) TypeAnnotationUtils.unannotatedType(typeVar)).isCaptured();
+  }
+
+  /** If typeVar is a captured wildcard, returns that wildcard; otherwise returns {@code null}. */
+  public static @Nullable WildcardType getCapturedWildcard(TypeVariable typeVar) {
+    if (isCaptured(typeVar)) {
+      return ((CapturedType) TypeAnnotationUtils.unannotatedType(typeVar)).wildcard;
+    }
+    return null;
+  }
+
+  /// Least upper bound and greatest lower bound
+
+  /**
+   * Returns the least upper bound of two {@link TypeMirror}s, ignoring any annotations on the
+   * types.
+   *
+   * <p>Wrapper around Types.lub to add special handling for null types, primitives, and wildcards.
+   *
+   * @param tm1 a {@link TypeMirror}
+   * @param tm2 a {@link TypeMirror}
+   * @param processingEnv the {@link ProcessingEnvironment} to use
+   * @return the least upper bound of {@code tm1} and {@code tm2}
+   */
+  public static TypeMirror leastUpperBound(
+      TypeMirror tm1, TypeMirror tm2, ProcessingEnvironment processingEnv) {
+    Type t1 = TypeAnnotationUtils.unannotatedType(tm1);
+    Type t2 = TypeAnnotationUtils.unannotatedType(tm2);
+    // Handle the 'null' type manually (not done by types.lub).
+    if (t1.getKind() == TypeKind.NULL) {
+      return t2;
+    }
+    if (t2.getKind() == TypeKind.NULL) {
+      return t1;
+    }
+    if (t1.getKind() == TypeKind.WILDCARD) {
+      WildcardType wc1 = (WildcardType) t1;
+      t1 = (Type) wc1.getExtendsBound();
+      if (t1 == null) {
+        // Implicit upper bound of java.lang.Object
+        Elements elements = processingEnv.getElementUtils();
+        return elements.getTypeElement("java.lang.Object").asType();
+      }
+    }
+    if (t2.getKind() == TypeKind.WILDCARD) {
+      WildcardType wc2 = (WildcardType) t2;
+      t2 = (Type) wc2.getExtendsBound();
+      if (t2 == null) {
+        // Implicit upper bound of java.lang.Object
+        Elements elements = processingEnv.getElementUtils();
+        return elements.getTypeElement("java.lang.Object").asType();
+      }
+    }
+    JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv;
+    com.sun.tools.javac.code.Types types =
+        com.sun.tools.javac.code.Types.instance(javacEnv.getContext());
+    if (types.isSameType(t1, t2)) {
+      // Special case if the two types are equal.
+      return t1;
+    }
+    // Special case for primitives.
+    if (isPrimitive(t1) || isPrimitive(t2)) {
+      if (types.isAssignable(t1, t2)) {
+        return t2;
+      } else if (types.isAssignable(t2, t1)) {
+        return t1;
+      } else {
+        Elements elements = processingEnv.getElementUtils();
+        return elements.getTypeElement("java.lang.Object").asType();
+      }
+    }
+    return types.lub(t1, t2);
+  }
+
+  /**
+   * Returns the greatest lower bound of two {@link TypeMirror}s, ignoring any annotations on the
+   * types.
+   *
+   * <p>Wrapper around Types.glb to add special handling for null types, primitives, and wildcards.
+   *
+   * @param tm1 a {@link TypeMirror}
+   * @param tm2 a {@link TypeMirror}
+   * @param processingEnv the {@link ProcessingEnvironment} to use
+   * @return the greatest lower bound of {@code tm1} and {@code tm2}
+   */
+  public static TypeMirror greatestLowerBound(
+      TypeMirror tm1, TypeMirror tm2, ProcessingEnvironment processingEnv) {
+    Type t1 = TypeAnnotationUtils.unannotatedType(tm1);
+    Type t2 = TypeAnnotationUtils.unannotatedType(tm2);
+    JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv;
+    com.sun.tools.javac.code.Types types =
+        com.sun.tools.javac.code.Types.instance(javacEnv.getContext());
+    if (types.isSameType(t1, t2)) {
+      // Special case if the two types are equal.
+      return t1;
+    }
+    // Handle the 'null' type manually.
+    if (t1.getKind() == TypeKind.NULL) {
+      return t1;
+    }
+    if (t2.getKind() == TypeKind.NULL) {
+      return t2;
+    }
+    // Special case for primitives.
+    if (isPrimitive(t1) || isPrimitive(t2)) {
+      if (types.isAssignable(t1, t2)) {
+        return t1;
+      } else if (types.isAssignable(t2, t1)) {
+        return t2;
+      } else {
+        // Javac types.glb returns TypeKind.Error when the GLB does
+        // not exist, but we can't create one.  Use TypeKind.NONE
+        // instead.
+        return processingEnv.getTypeUtils().getNoType(TypeKind.NONE);
+      }
+    }
+    if (t1.getKind() == TypeKind.WILDCARD) {
+      return t2;
+    }
+    if (t2.getKind() == TypeKind.WILDCARD) {
+      return t1;
+    }
+
+    // If neither type is a primitive type, null type, or wildcard
+    // and if the types are not the same, use javac types.glb
+    return types.glb(t1, t2);
+  }
+
+  /**
+   * Returns the most specific type from the list, or null if none exists.
+   *
+   * @param typeMirrors a list of types
+   * @param processingEnv the {@link ProcessingEnvironment} to use
+   * @return the most specific of the types, or null if none exists
+   */
+  public static @Nullable TypeMirror mostSpecific(
+      List<TypeMirror> typeMirrors, ProcessingEnvironment processingEnv) {
+    if (typeMirrors.size() == 1) {
+      return typeMirrors.get(0);
+    } else {
+      JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv;
+      com.sun.tools.javac.code.Types types =
+          com.sun.tools.javac.code.Types.instance(javacEnv.getContext());
+      com.sun.tools.javac.util.List<Type> typeList = typeMirrorListToTypeList(typeMirrors);
+      Type glb = types.glb(typeList);
+      for (Type candidate : typeList) {
+        if (types.isSameType(glb, candidate)) {
+          return candidate;
+        }
+      }
+      return null;
+    }
+  }
+
+  /**
+   * Given a list of TypeMirror, return a list of Type.
+   *
+   * @param typeMirrors a list of TypeMirrors
+   * @return the argument, converted to a javac list
+   */
+  private static com.sun.tools.javac.util.List<Type> typeMirrorListToTypeList(
+      List<TypeMirror> typeMirrors) {
+    List<Type> typeList = CollectionsPlume.mapList(Type.class::cast, typeMirrors);
+    return com.sun.tools.javac.util.List.from(typeList);
+  }
+
+  /// Substitutions
+
+  /**
+   * Returns the return type of a method, given the receiver of the method call.
+   *
+   * @param methodElement a method
+   * @param substitutedReceiverType the receiver type, after substitution
+   * @param env the environment
+   * @return the return type of the method
+   */
+  public static TypeMirror substituteMethodReturnType(
+      Element methodElement, TypeMirror substitutedReceiverType, ProcessingEnvironment env) {
+
+    com.sun.tools.javac.code.Types types =
+        com.sun.tools.javac.code.Types.instance(InternalUtils.getJavacContext(env));
+
+    Type substitutedMethodType =
+        types.memberType((Type) substitutedReceiverType, (Symbol) methodElement);
+    return substitutedMethodType.getReturnType();
+  }
+
+  /**
+   * Returns {@code type} as {@code superType} if {@code superType} is a super type of {@code type};
+   * otherwise, null.
+   *
+   * @return {@code type} as {@code superType} if {@code superType} is a super type of {@code type};
+   *     otherwise, null
+   */
+  public static TypeMirror asSuper(
+      TypeMirror type, TypeMirror superType, ProcessingEnvironment env) {
+    Context ctx = ((JavacProcessingEnvironment) env).getContext();
+    com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx);
+    return javacTypes.asSuper((Type) type, ((Type) superType).tsym);
+  }
+
+  /**
+   * Returns the superclass of the given class. Returns null if there is not one.
+   *
+   * @param type a type
+   * @param types type utilities
+   * @return the superclass of the given class, or null
+   */
+  public static @Nullable TypeMirror getSuperclass(TypeMirror type, Types types) {
+    List<? extends TypeMirror> superTypes = types.directSupertypes(type);
+    for (TypeMirror t : superTypes) {
+      // ignore interface types
+      if (!(t instanceof ClassType)) {
+        continue;
+      }
+      ClassType tt = (ClassType) t;
+      if (!tt.isInterface()) {
+        return t;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns the type of primitive conversion from {@code from} to {@code to}.
+   *
+   * @param from a primitive type
+   * @param to a primitive type
+   * @return the type of primitive conversion from {@code from} to {@code to}
+   */
+  public static TypeKindUtils.PrimitiveConversionKind getPrimitiveConversionKind(
+      PrimitiveType from, PrimitiveType to) {
+    return TypeKindUtils.getPrimitiveConversionKind(from.getKind(), to.getKind());
+  }
+
+  /**
+   * Returns a new type mirror with the same type as {@code type} where all the type variables in
+   * {@code typeVariables} have been substituted with the type arguments in {@code typeArgs}.
+   *
+   * <p>This is a wrapper around {@link com.sun.tools.javac.code.Types#subst(Type,
+   * com.sun.tools.javac.util.List, com.sun.tools.javac.util.List)}.
+   *
+   * @param type type to do substitution in
+   * @param typeVariables type variables that should be replaced with the type mirror at the same
+   *     index of {@code typeArgs}
+   * @param typeArgs type mirrors that should replace the type variable at the same index of {@code
+   *     typeVariables}
+   * @param env processing environment
+   * @return a new type mirror with the same type as {@code type} where all the type variables in
+   *     {@code typeVariables} have been substituted with the type arguments in {@code typeArgs}
+   */
+  public static TypeMirror substitute(
+      TypeMirror type,
+      List<? extends TypeMirror> typeVariables,
+      List<? extends TypeMirror> typeArgs,
+      ProcessingEnvironment env) {
+
+    List<Type> newP = CollectionsPlume.mapList(Type.class::cast, typeVariables);
+
+    List<Type> newT = CollectionsPlume.mapList(Type.class::cast, typeArgs);
+
+    JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env;
+    com.sun.tools.javac.code.Types types =
+        com.sun.tools.javac.code.Types.instance(javacEnv.getContext());
+    return types.subst(
+        (Type) type,
+        com.sun.tools.javac.util.List.from(newP),
+        com.sun.tools.javac.util.List.from(newT));
+  }
+
+  /**
+   * Returns the depth of an array type.
+   *
+   * @param arrayType an array type
+   * @return the depth of {@code arrayType}
+   */
+  public static int getArrayDepth(TypeMirror arrayType) {
+    int counter = 0;
+    TypeMirror type = arrayType;
+    while (type.getKind() == TypeKind.ARRAY) {
+      counter++;
+      type = ((ArrayType) type).getComponentType();
+    }
+    return counter;
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/UserError.java b/javacutil/src/main/java/org/checkerframework/javacutil/UserError.java
new file mode 100644
index 0000000..cf1bc8f
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/UserError.java
@@ -0,0 +1,35 @@
+package org.checkerframework.javacutil;
+
+import org.checkerframework.checker.formatter.qual.FormatMethod;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Exception type indicating a mistake by an end user in using the Checker Framework, such as
+ * incorrect command-line arguments.
+ */
+@SuppressWarnings("serial")
+public class UserError extends RuntimeException {
+
+  /**
+   * Constructs a new CheckerError with the specified detail message.
+   *
+   * @param message the detail message
+   */
+  public UserError(String message) {
+    super(message);
+    if (message == null) {
+      throw new Error("Must have a detail message.");
+    }
+  }
+
+  /**
+   * Constructs a new CheckerError with a detail message composed from the given arguments.
+   *
+   * @param fmt the format string
+   * @param args the arguments for the format string
+   */
+  @FormatMethod
+  public UserError(String fmt, @Nullable Object... args) {
+    this(String.format(fmt, args));
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java
new file mode 100644
index 0000000..2b08359
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java
@@ -0,0 +1,33 @@
+package org.checkerframework.javacutil.trees;
+
+import com.sun.source.tree.VariableTree;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.util.Name;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * A DetachedVarSymbol represents a variable that is not part of any AST Tree. DetachedVarSymbols
+ * are created when desugaring source code constructs and they carry important type information, but
+ * some methods such as TreeInfo.declarationFor do not work on them.
+ */
+public class DetachedVarSymbol extends Symbol.VarSymbol {
+
+  protected @Nullable VariableTree decl;
+
+  /** Construct a detached variable symbol, given its flags, name, type and owner. */
+  public DetachedVarSymbol(long flags, Name name, Type type, Symbol owner) {
+    super(flags, name, type, owner);
+    this.decl = null;
+  }
+
+  /** Set the declaration tree for the variable. */
+  public void setDeclaration(VariableTree decl) {
+    this.decl = decl;
+  }
+
+  /** Get the declaration tree for the variable. */
+  public @Nullable VariableTree getDeclaration() {
+    return decl;
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java
new file mode 100644
index 0000000..2c48f4a
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java
@@ -0,0 +1,655 @@
+package org.checkerframework.javacutil.trees;
+
+import com.sun.source.tree.ArrayAccessTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symtab;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.TreeInfo;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Names;
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.checkerframework.javacutil.TreeUtils;
+import org.checkerframework.javacutil.TypesUtils;
+import org.plumelib.util.CollectionsPlume;
+
+/**
+ * The TreeBuilder permits the creation of new AST Trees using the non-public Java compiler API
+ * TreeMaker.
+ */
+public class TreeBuilder {
+  protected final Elements elements;
+  protected final Types modelTypes;
+  protected final com.sun.tools.javac.code.Types javacTypes;
+  protected final TreeMaker maker;
+  protected final Names names;
+  protected final Symtab symtab;
+  protected final ProcessingEnvironment env;
+
+  public TreeBuilder(ProcessingEnvironment env) {
+    this.env = env;
+    Context context = ((JavacProcessingEnvironment) env).getContext();
+    elements = env.getElementUtils();
+    modelTypes = env.getTypeUtils();
+    javacTypes = com.sun.tools.javac.code.Types.instance(context);
+    maker = TreeMaker.instance(context);
+    names = Names.instance(context);
+    symtab = Symtab.instance(context);
+  }
+
+  /**
+   * Builds an AST Tree to access the iterator() method of some iterable expression.
+   *
+   * @param iterableExpr an expression whose type is a subtype of Iterable
+   * @return a MemberSelectTree that accesses the iterator() method of the expression
+   */
+  public MemberSelectTree buildIteratorMethodAccess(ExpressionTree iterableExpr) {
+    DeclaredType exprType = (DeclaredType) TypesUtils.upperBound(TreeUtils.typeOf(iterableExpr));
+    assert exprType != null : "expression must be of declared type Iterable<>";
+
+    TypeElement exprElement = (TypeElement) exprType.asElement();
+
+    // Find the iterator() method of the iterable type
+    Symbol.MethodSymbol iteratorMethod = null;
+
+    for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) {
+      if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("iterator")) {
+        iteratorMethod = (Symbol.MethodSymbol) method;
+      }
+    }
+
+    assert iteratorMethod != null
+        : "@AssumeAssertion(nullness): no iterator method declared for expression type";
+
+    Type.MethodType methodType = (Type.MethodType) iteratorMethod.asType();
+    Symbol.TypeSymbol methodClass = methodType.asElement();
+    DeclaredType iteratorType = (DeclaredType) methodType.getReturnType();
+    iteratorType =
+        (DeclaredType) javacTypes.asSuper((Type) iteratorType, symtab.iteratorType.asElement());
+
+    int numIterTypeArgs = iteratorType.getTypeArguments().size();
+    assert numIterTypeArgs <= 1 : "expected at most one type argument for Iterator";
+
+    if (numIterTypeArgs == 1) {
+      TypeMirror elementType = iteratorType.getTypeArguments().get(0);
+      // Remove captured type from a wildcard.
+      if (elementType instanceof Type.CapturedType) {
+        elementType = ((Type.CapturedType) elementType).wildcard;
+
+        iteratorType =
+            modelTypes.getDeclaredType(
+                (TypeElement) modelTypes.asElement(iteratorType), elementType);
+      }
+    }
+
+    // Replace the iterator method's generic return type with
+    // the actual element type of the expression.
+    Type.MethodType updatedMethodType =
+        new Type.MethodType(
+            com.sun.tools.javac.util.List.nil(),
+            (Type) iteratorType,
+            com.sun.tools.javac.util.List.nil(),
+            methodClass);
+
+    JCTree.JCFieldAccess iteratorAccess =
+        (JCTree.JCFieldAccess) maker.Select((JCTree.JCExpression) iterableExpr, iteratorMethod);
+    iteratorAccess.setType(updatedMethodType);
+
+    return iteratorAccess;
+  }
+
+  /**
+   * Builds an AST Tree to access the hasNext() method of an iterator.
+   *
+   * @param iteratorExpr an expression whose type is a subtype of Iterator
+   * @return a MemberSelectTree that accesses the hasNext() method of the expression
+   */
+  public MemberSelectTree buildHasNextMethodAccess(ExpressionTree iteratorExpr) {
+    DeclaredType exprType = (DeclaredType) TreeUtils.typeOf(iteratorExpr);
+    assert exprType != null : "expression must be of declared type Iterator<>";
+
+    TypeElement exprElement = (TypeElement) exprType.asElement();
+
+    // Find the hasNext() method of the iterator type
+    Symbol.MethodSymbol hasNextMethod = null;
+
+    for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) {
+      if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("hasNext")) {
+        hasNextMethod = (Symbol.MethodSymbol) method;
+      }
+    }
+
+    assert hasNextMethod != null : "no hasNext method declared for expression type";
+
+    JCTree.JCFieldAccess hasNextAccess =
+        (JCTree.JCFieldAccess) maker.Select((JCTree.JCExpression) iteratorExpr, hasNextMethod);
+    hasNextAccess.setType(hasNextMethod.asType());
+
+    return hasNextAccess;
+  }
+
+  /**
+   * Builds an AST Tree to access the next() method of an iterator.
+   *
+   * @param iteratorExpr an expression whose type is a subtype of Iterator
+   * @return a MemberSelectTree that accesses the next() method of the expression
+   */
+  public MemberSelectTree buildNextMethodAccess(ExpressionTree iteratorExpr) {
+    DeclaredType exprType = (DeclaredType) TreeUtils.typeOf(iteratorExpr);
+    assert exprType != null : "expression must be of declared type Iterator<>";
+
+    TypeElement exprElement = (TypeElement) exprType.asElement();
+
+    // Find the next() method of the iterator type
+    Symbol.MethodSymbol nextMethod = null;
+
+    for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) {
+      if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("next")) {
+        nextMethod = (Symbol.MethodSymbol) method;
+      }
+    }
+
+    assert nextMethod != null
+        : "@AssumeAssertion(nullness): no next method declared for expression type";
+
+    Type.MethodType methodType = (Type.MethodType) nextMethod.asType();
+    Symbol.TypeSymbol methodClass = methodType.asElement();
+    Type elementType;
+
+    if (exprType.getTypeArguments().isEmpty()) {
+      elementType = symtab.objectType;
+    } else {
+      elementType = (Type) exprType.getTypeArguments().get(0);
+    }
+
+    // Replace the next method's generic return type with
+    // the actual element type of the expression.
+    Type.MethodType updatedMethodType =
+        new Type.MethodType(
+            com.sun.tools.javac.util.List.nil(),
+            elementType,
+            com.sun.tools.javac.util.List.nil(),
+            methodClass);
+
+    JCTree.JCFieldAccess nextAccess =
+        (JCTree.JCFieldAccess) maker.Select((JCTree.JCExpression) iteratorExpr, nextMethod);
+    nextAccess.setType(updatedMethodType);
+
+    return nextAccess;
+  }
+
+  /**
+   * Builds an AST Tree to dereference the length field of an array.
+   *
+   * @param expression the array expression whose length is being accessed
+   * @return a MemberSelectTree to dereference the length of the array
+   */
+  public MemberSelectTree buildArrayLengthAccess(ExpressionTree expression) {
+
+    return (JCTree.JCFieldAccess) maker.Select((JCTree.JCExpression) expression, symtab.lengthVar);
+  }
+
+  /**
+   * Builds an AST Tree to call a method designated by the argument expression.
+   *
+   * @param methodExpr an expression denoting a method with no arguments
+   * @return a MethodInvocationTree to call the argument method
+   */
+  public MethodInvocationTree buildMethodInvocation(ExpressionTree methodExpr) {
+    return maker.App((JCTree.JCExpression) methodExpr);
+  }
+
+  /**
+   * Builds an AST Tree to call a method designated by methodExpr, with one argument designated by
+   * argExpr.
+   *
+   * @param methodExpr an expression denoting a method with one argument
+   * @param argExpr an expression denoting an argument to the method
+   * @return a MethodInvocationTree to call the argument method
+   */
+  public MethodInvocationTree buildMethodInvocation(
+      ExpressionTree methodExpr, ExpressionTree argExpr) {
+    return maker.App(
+        (JCTree.JCExpression) methodExpr,
+        com.sun.tools.javac.util.List.of((JCTree.JCExpression) argExpr));
+  }
+
+  /**
+   * Builds an AST Tree to declare and initialize a variable, with no modifiers.
+   *
+   * @param type the type of the variable
+   * @param name the name of the variable
+   * @param owner the element containing the new symbol
+   * @param initializer the initializer expression
+   * @return a VariableDeclTree declaring the new variable
+   */
+  public VariableTree buildVariableDecl(
+      TypeMirror type, String name, Element owner, ExpressionTree initializer) {
+    DetachedVarSymbol sym =
+        new DetachedVarSymbol(0, names.fromString(name), (Type) type, (Symbol) owner);
+    VariableTree tree = maker.VarDef(sym, (JCTree.JCExpression) initializer);
+    sym.setDeclaration(tree);
+    return tree;
+  }
+
+  /**
+   * Builds an AST Tree to declare and initialize a variable. The type of the variable is specified
+   * by a Tree.
+   *
+   * @param type the type of the variable, as a Tree
+   * @param name the name of the variable
+   * @param owner the element containing the new symbol
+   * @param initializer the initializer expression
+   * @return a VariableDeclTree declaring the new variable
+   */
+  public VariableTree buildVariableDecl(
+      Tree type, String name, Element owner, ExpressionTree initializer) {
+    Type typeMirror = (Type) TreeUtils.typeOf(type);
+    DetachedVarSymbol sym =
+        new DetachedVarSymbol(0, names.fromString(name), typeMirror, (Symbol) owner);
+    JCTree.JCModifiers mods = maker.Modifiers(0);
+    JCTree.JCVariableDecl decl =
+        maker.VarDef(mods, sym.name, (JCTree.JCExpression) type, (JCTree.JCExpression) initializer);
+    decl.setType(typeMirror);
+    decl.sym = sym;
+    sym.setDeclaration(decl);
+    return decl;
+  }
+
+  /**
+   * Builds an AST Tree to refer to a variable.
+   *
+   * @param decl the declaration of the variable
+   * @return an IdentifierTree to refer to the variable
+   */
+  public IdentifierTree buildVariableUse(VariableTree decl) {
+    return (IdentifierTree) maker.Ident((JCTree.JCVariableDecl) decl);
+  }
+
+  /**
+   * Builds an AST Tree to cast the type of an expression.
+   *
+   * @param type the type to cast to
+   * @param expr the expression to be cast
+   * @return a cast of the expression to the type
+   */
+  public TypeCastTree buildTypeCast(TypeMirror type, ExpressionTree expr) {
+    return maker.TypeCast((Type) type, (JCTree.JCExpression) expr);
+  }
+
+  /**
+   * Builds an AST Tree to assign an expression to a variable.
+   *
+   * @param variable the declaration of the variable to assign to
+   * @param expr the expression to be assigned
+   * @return a statement assigning the expression to the variable
+   */
+  public StatementTree buildAssignment(VariableTree variable, ExpressionTree expr) {
+    return maker.Assignment(TreeInfo.symbolFor((JCTree) variable), (JCTree.JCExpression) expr);
+  }
+
+  /**
+   * Builds an AST Tree to assign an RHS expression to an LHS expression.
+   *
+   * @param lhs the expression to be assigned to
+   * @param rhs the expression to be assigned
+   * @return a statement assigning the expression to the variable
+   */
+  public AssignmentTree buildAssignment(ExpressionTree lhs, ExpressionTree rhs) {
+    JCTree.JCAssign assign = maker.Assign((JCTree.JCExpression) lhs, (JCTree.JCExpression) rhs);
+    assign.setType((Type) TreeUtils.typeOf(lhs));
+    return assign;
+  }
+
+  /** Builds an AST Tree representing a literal value of primitive or String type. */
+  public LiteralTree buildLiteral(Object value) {
+    return maker.Literal(value);
+  }
+
+  /**
+   * Builds an AST Tree to compare two operands with less than.
+   *
+   * @param left the left operand tree
+   * @param right the right operand tree
+   * @return a Tree representing "left &lt; right"
+   */
+  public BinaryTree buildLessThan(ExpressionTree left, ExpressionTree right) {
+    JCTree.JCBinary binary =
+        maker.Binary(JCTree.Tag.LT, (JCTree.JCExpression) left, (JCTree.JCExpression) right);
+    binary.setType((Type) modelTypes.getPrimitiveType(TypeKind.BOOLEAN));
+    return binary;
+  }
+
+  /**
+   * Builds an AST Tree to dereference an array.
+   *
+   * @param array the array to dereference
+   * @param index the index at which to dereference
+   * @return a Tree representing the dereference
+   */
+  public ArrayAccessTree buildArrayAccess(ExpressionTree array, ExpressionTree index) {
+    ArrayType arrayType = (ArrayType) TreeUtils.typeOf(array);
+    JCTree.JCArrayAccess access =
+        maker.Indexed((JCTree.JCExpression) array, (JCTree.JCExpression) index);
+    access.setType((Type) arrayType.getComponentType());
+    return access;
+  }
+
+  /**
+   * Builds an AST Tree to refer to a class name.
+   *
+   * @param elt an element representing the class
+   * @return an IdentifierTree referring to the class
+   */
+  public IdentifierTree buildClassUse(Element elt) {
+    return maker.Ident((Symbol) elt);
+  }
+
+  /**
+   * Builds an AST Tree to access the valueOf() method of boxed type such as Short or Float.
+   *
+   * @param expr an expression whose type is a boxed type
+   * @return a MemberSelectTree that accesses the valueOf() method of the expression
+   */
+  public MemberSelectTree buildValueOfMethodAccess(Tree expr) {
+    TypeMirror boxedType = TreeUtils.typeOf(expr);
+
+    assert TypesUtils.isBoxedPrimitive(boxedType);
+
+    // Find the valueOf(unboxedType) method of the boxed type
+    Symbol.MethodSymbol valueOfMethod = getValueOfMethod(env, boxedType);
+
+    Type.MethodType methodType = (Type.MethodType) valueOfMethod.asType();
+
+    JCTree.JCFieldAccess valueOfAccess =
+        (JCTree.JCFieldAccess) maker.Select((JCTree.JCExpression) expr, valueOfMethod);
+    valueOfAccess.setType(methodType);
+
+    return valueOfAccess;
+  }
+
+  /** Returns the valueOf method of a boxed type such as Short or Float. */
+  public static Symbol.MethodSymbol getValueOfMethod(
+      ProcessingEnvironment env, TypeMirror boxedType) {
+    Symbol.MethodSymbol valueOfMethod = null;
+
+    TypeMirror unboxedType = env.getTypeUtils().unboxedType(boxedType);
+    TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement();
+    for (ExecutableElement method :
+        ElementFilter.methodsIn(env.getElementUtils().getAllMembers(boxedElement))) {
+      if (method.getSimpleName().contentEquals("valueOf")) {
+        List<? extends VariableElement> params = method.getParameters();
+        if (params.size() == 1
+            && env.getTypeUtils().isSameType(params.get(0).asType(), unboxedType)) {
+          valueOfMethod = (Symbol.MethodSymbol) method;
+        }
+      }
+    }
+
+    assert valueOfMethod != null
+        : "@AssumeAssertion(nullness): no valueOf method declared for boxed type";
+    return valueOfMethod;
+  }
+
+  /**
+   * Builds an AST Tree to access the *Value() method of a boxed type such as Short or Float, where
+   * * is the corresponding primitive type (i.e. shortValue or floatValue).
+   *
+   * @param expr an expression whose type is a boxed type
+   * @return a MemberSelectTree that accesses the *Value() method of the expression
+   */
+  public MemberSelectTree buildPrimValueMethodAccess(Tree expr) {
+    TypeMirror boxedType = TreeUtils.typeOf(expr);
+    TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement();
+
+    assert TypesUtils.isBoxedPrimitive(boxedType);
+    TypeMirror unboxedType = modelTypes.unboxedType(boxedType);
+
+    // Find the *Value() method of the boxed type
+    String primValueName = unboxedType.toString() + "Value";
+    Symbol.MethodSymbol primValueMethod = null;
+
+    for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(boxedElement))) {
+      if (method.getSimpleName().contentEquals(primValueName) && method.getParameters().isEmpty()) {
+        primValueMethod = (Symbol.MethodSymbol) method;
+      }
+    }
+
+    assert primValueMethod != null
+        : "@AssumeAssertion(nullness): no *Value method declared for boxed type";
+
+    Type.MethodType methodType = (Type.MethodType) primValueMethod.asType();
+
+    JCTree.JCFieldAccess primValueAccess =
+        (JCTree.JCFieldAccess) maker.Select((JCTree.JCExpression) expr, primValueMethod);
+    primValueAccess.setType(methodType);
+
+    return primValueAccess;
+  }
+
+  /** Map public AST Tree.Kinds to internal javac JCTree.Tags. */
+  public JCTree.Tag kindToTag(Tree.Kind kind) {
+    switch (kind) {
+      case AND:
+        return JCTree.Tag.BITAND;
+      case AND_ASSIGNMENT:
+        return JCTree.Tag.BITAND_ASG;
+      case ANNOTATION:
+        return JCTree.Tag.ANNOTATION;
+      case ANNOTATION_TYPE:
+        return JCTree.Tag.TYPE_ANNOTATION;
+      case ARRAY_ACCESS:
+        return JCTree.Tag.INDEXED;
+      case ARRAY_TYPE:
+        return JCTree.Tag.TYPEARRAY;
+      case ASSERT:
+        return JCTree.Tag.ASSERT;
+      case ASSIGNMENT:
+        return JCTree.Tag.ASSIGN;
+      case BITWISE_COMPLEMENT:
+        return JCTree.Tag.COMPL;
+      case BLOCK:
+        return JCTree.Tag.BLOCK;
+      case BREAK:
+        return JCTree.Tag.BREAK;
+      case CASE:
+        return JCTree.Tag.CASE;
+      case CATCH:
+        return JCTree.Tag.CATCH;
+      case CLASS:
+        return JCTree.Tag.CLASSDEF;
+      case CONDITIONAL_AND:
+        return JCTree.Tag.AND;
+      case CONDITIONAL_EXPRESSION:
+        return JCTree.Tag.CONDEXPR;
+      case CONDITIONAL_OR:
+        return JCTree.Tag.OR;
+      case CONTINUE:
+        return JCTree.Tag.CONTINUE;
+      case DIVIDE:
+        return JCTree.Tag.DIV;
+      case DIVIDE_ASSIGNMENT:
+        return JCTree.Tag.DIV_ASG;
+      case DO_WHILE_LOOP:
+        return JCTree.Tag.DOLOOP;
+      case ENHANCED_FOR_LOOP:
+        return JCTree.Tag.FOREACHLOOP;
+      case EQUAL_TO:
+        return JCTree.Tag.EQ;
+      case EXPRESSION_STATEMENT:
+        return JCTree.Tag.EXEC;
+      case FOR_LOOP:
+        return JCTree.Tag.FORLOOP;
+      case GREATER_THAN:
+        return JCTree.Tag.GT;
+      case GREATER_THAN_EQUAL:
+        return JCTree.Tag.GE;
+      case IDENTIFIER:
+        return JCTree.Tag.IDENT;
+      case IF:
+        return JCTree.Tag.IF;
+      case IMPORT:
+        return JCTree.Tag.IMPORT;
+      case INSTANCE_OF:
+        return JCTree.Tag.TYPETEST;
+      case LABELED_STATEMENT:
+        return JCTree.Tag.LABELLED;
+      case LEFT_SHIFT:
+        return JCTree.Tag.SL;
+      case LEFT_SHIFT_ASSIGNMENT:
+        return JCTree.Tag.SL_ASG;
+      case LESS_THAN:
+        return JCTree.Tag.LT;
+      case LESS_THAN_EQUAL:
+        return JCTree.Tag.LE;
+      case LOGICAL_COMPLEMENT:
+        return JCTree.Tag.NOT;
+      case MEMBER_SELECT:
+        return JCTree.Tag.SELECT;
+      case METHOD:
+        return JCTree.Tag.METHODDEF;
+      case METHOD_INVOCATION:
+        return JCTree.Tag.APPLY;
+      case MINUS:
+        return JCTree.Tag.MINUS;
+      case MINUS_ASSIGNMENT:
+        return JCTree.Tag.MINUS_ASG;
+      case MODIFIERS:
+        return JCTree.Tag.MODIFIERS;
+      case MULTIPLY:
+        return JCTree.Tag.MUL;
+      case MULTIPLY_ASSIGNMENT:
+        return JCTree.Tag.MUL_ASG;
+      case NEW_ARRAY:
+        return JCTree.Tag.NEWARRAY;
+      case NEW_CLASS:
+        return JCTree.Tag.NEWCLASS;
+      case NOT_EQUAL_TO:
+        return JCTree.Tag.NE;
+      case OR:
+        return JCTree.Tag.BITOR;
+      case OR_ASSIGNMENT:
+        return JCTree.Tag.BITOR_ASG;
+      case PARENTHESIZED:
+        return JCTree.Tag.PARENS;
+      case PLUS:
+        return JCTree.Tag.PLUS;
+      case PLUS_ASSIGNMENT:
+        return JCTree.Tag.PLUS_ASG;
+      case POSTFIX_DECREMENT:
+        return JCTree.Tag.POSTDEC;
+      case POSTFIX_INCREMENT:
+        return JCTree.Tag.POSTINC;
+      case PREFIX_DECREMENT:
+        return JCTree.Tag.PREDEC;
+      case PREFIX_INCREMENT:
+        return JCTree.Tag.PREINC;
+      case REMAINDER:
+        return JCTree.Tag.MOD;
+      case REMAINDER_ASSIGNMENT:
+        return JCTree.Tag.MOD_ASG;
+      case RETURN:
+        return JCTree.Tag.RETURN;
+      case RIGHT_SHIFT:
+        return JCTree.Tag.SR;
+      case RIGHT_SHIFT_ASSIGNMENT:
+        return JCTree.Tag.SR_ASG;
+      case SWITCH:
+        return JCTree.Tag.SWITCH;
+      case SYNCHRONIZED:
+        return JCTree.Tag.SYNCHRONIZED;
+      case THROW:
+        return JCTree.Tag.THROW;
+      case TRY:
+        return JCTree.Tag.TRY;
+      case TYPE_CAST:
+        return JCTree.Tag.TYPECAST;
+      case TYPE_PARAMETER:
+        return JCTree.Tag.TYPEPARAMETER;
+      case UNARY_MINUS:
+        return JCTree.Tag.NEG;
+      case UNARY_PLUS:
+        return JCTree.Tag.POS;
+      case UNION_TYPE:
+        return JCTree.Tag.TYPEUNION;
+      case UNSIGNED_RIGHT_SHIFT:
+        return JCTree.Tag.USR;
+      case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
+        return JCTree.Tag.USR_ASG;
+      case VARIABLE:
+        return JCTree.Tag.VARDEF;
+      case WHILE_LOOP:
+        return JCTree.Tag.WHILELOOP;
+      case XOR:
+        return JCTree.Tag.BITXOR;
+      case XOR_ASSIGNMENT:
+        return JCTree.Tag.BITXOR_ASG;
+      default:
+        return JCTree.Tag.NO_TAG;
+    }
+  }
+
+  /**
+   * Builds an AST Tree to perform a binary operation.
+   *
+   * @param type result type of the operation
+   * @param op AST Tree operator
+   * @param left the left operand tree
+   * @param right the right operand tree
+   * @return a Tree representing "left &lt; right"
+   */
+  public BinaryTree buildBinary(
+      TypeMirror type, Tree.Kind op, ExpressionTree left, ExpressionTree right) {
+    JCTree.Tag jcOp = kindToTag(op);
+    JCTree.JCBinary binary =
+        maker.Binary(jcOp, (JCTree.JCExpression) left, (JCTree.JCExpression) right);
+    binary.setType((Type) type);
+    return binary;
+  }
+
+  /**
+   * Builds an AST Tree to create a new array with initializers.
+   *
+   * @param componentType component type of the new array
+   * @param elems expression trees of initializers
+   * @return a NewArrayTree to create a new array with initializers
+   */
+  public NewArrayTree buildNewArray(TypeMirror componentType, List<ExpressionTree> elems) {
+    List<JCExpression> exprs = CollectionsPlume.mapList(JCExpression.class::cast, elems);
+
+    JCTree.JCNewArray newArray =
+        maker.NewArray(
+            (JCTree.JCExpression) buildClassUse(((Type) componentType).tsym),
+            com.sun.tools.javac.util.List.nil(),
+            com.sun.tools.javac.util.List.from(exprs));
+    newArray.setType(javacTypes.makeArrayType((Type) componentType));
+    return newArray;
+  }
+}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeParser.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeParser.java
new file mode 100644
index 0000000..e91cbed
--- /dev/null
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeParser.java
@@ -0,0 +1,161 @@
+package org.checkerframework.javacutil.trees;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.ListBuffer;
+import com.sun.tools.javac.util.Names;
+import java.util.StringTokenizer;
+import javax.annotation.processing.ProcessingEnvironment;
+import org.checkerframework.javacutil.Pair;
+
+/**
+ * A utility class for parsing Java expression snippets, and converting them to proper Javac AST
+ * nodes.
+ *
+ * <p>This is useful for parsing {@code EnsuresNonNull*}, and {@code KeyFor} values.
+ *
+ * <p>Currently, it handles four tree types only:
+ *
+ * <ul>
+ *   <li>Identifier tree (e.g. {@code id})
+ *   <li>Literal tree (e.g. 2, 3)
+ *   <li>Method invocation tree (e.g. {@code method(2, 3)})
+ *   <li>Member select tree (e.g. {@code Class.field}, {@code instance.method()})
+ *   <li>Array access tree (e.g. {@code array[id]})
+ * </ul>
+ *
+ * Notable limitation: Doesn't handle spaces, or non-method-argument parenthesis.
+ *
+ * <p>It's implemented via a Recursive-Descend parser.
+ */
+public class TreeParser {
+  /** Valid delimiters. */
+  private static final String DELIMS = ".[](),";
+  /** A sentinel value. */
+  private static final String SENTINEL = "";
+
+  /** The TreeMaker instance. */
+  private final TreeMaker maker;
+  /** The names instance. */
+  private final Names names;
+
+  /** Create a TreeParser. */
+  public TreeParser(ProcessingEnvironment env) {
+    Context context = ((JavacProcessingEnvironment) env).getContext();
+    maker = TreeMaker.instance(context);
+    names = Names.instance(context);
+  }
+
+  /**
+   * Parses the snippet in the string as an internal Javac AST expression node.
+   *
+   * @param s the java snippet
+   * @return the AST corresponding to the snippet
+   */
+  public ExpressionTree parseTree(String s) {
+    StringTokenizer tokenizer = new StringTokenizer(s, DELIMS, true);
+    String token = tokenizer.nextToken();
+
+    try {
+      return parseExpression(tokenizer, token).first;
+    } catch (Exception e) {
+      throw new ParseError(e);
+    } finally {
+      tokenizer = null;
+      token = null;
+    }
+  }
+
+  /** The next token from the tokenizer, or the {@code SENTINEL} if none is available. */
+  private String nextToken(StringTokenizer tokenizer) {
+    return tokenizer.hasMoreTokens() ? tokenizer.nextToken() : SENTINEL;
+  }
+
+  /** The parsed expression tree for the given token. */
+  private JCExpression fromToken(String token) {
+    // Optimization
+    if ("true".equals(token)) {
+      return maker.Literal(true);
+    } else if ("false".equals(token)) {
+      return maker.Literal(false);
+    }
+
+    if (Character.isLetter(token.charAt(0))) {
+      return maker.Ident(names.fromString(token));
+    }
+
+    Object value;
+    try {
+      value = Integer.valueOf(token);
+    } catch (Exception e2) {
+      try {
+        value = Double.valueOf(token);
+      } catch (Exception ef) {
+        throw new Error("Can't parse as integer or double: " + token);
+      }
+    }
+    return maker.Literal(value);
+  }
+
+  /**
+   * Parse an expression.
+   *
+   * @param tokenizer the tokenizer
+   * @param token the first token
+   * @return a pair of a parsed expression and the next token
+   */
+  private Pair<JCExpression, String> parseExpression(StringTokenizer tokenizer, String token) {
+    JCExpression tree = fromToken(token);
+
+    while (tokenizer.hasMoreTokens()) {
+      String delim = nextToken(tokenizer);
+      token = delim;
+      if (".".equals(delim)) {
+        token = nextToken(tokenizer);
+        tree = maker.Select(tree, names.fromString(token));
+      } else if ("(".equals(delim)) {
+        token = nextToken(tokenizer);
+        ListBuffer<JCExpression> args = new ListBuffer<>();
+        while (!")".equals(token)) {
+          Pair<JCExpression, String> p = parseExpression(tokenizer, token);
+          JCExpression arg = p.first;
+          token = p.second;
+          args.append(arg);
+          if (",".equals(token)) {
+            token = nextToken(tokenizer);
+          }
+        }
+        // For now, handle empty args only
+        assert ")".equals(token) : "Unexpected token: " + token;
+        tree = maker.Apply(List.nil(), tree, args.toList());
+      } else if ("[".equals(token)) {
+        token = nextToken(tokenizer);
+        Pair<JCExpression, String> p = parseExpression(tokenizer, token);
+        JCExpression index = p.first;
+        token = p.second;
+        assert "]".equals(token) : "Unexpected token: " + token;
+        tree = maker.Indexed(tree, index);
+      } else {
+        return Pair.of(tree, token);
+      }
+      assert tokenizer != null : "@AssumeAssertion(nullness): side effects";
+    }
+
+    return Pair.of(tree, token);
+  }
+
+  /** An internal error. */
+  private static class ParseError extends RuntimeException {
+    /** The serial version UID. */
+    private static final long serialVersionUID = 1887754619522101929L;
+
+    /** Create a ParseError. */
+    ParseError(Throwable cause) {
+      super(cause);
+    }
+  }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..9f5adcc
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,16 @@
+rootProject.name = 'checker-framework'
+include 'checker'
+include 'javacutil'
+include 'dataflow'
+include 'framework'
+include 'checker-qual'
+include 'checker-qual-android'
+include 'checker-util'
+include 'framework-test'
+includeBuild ('../annotation-tools/annotation-file-utilities') {
+    if (!file('../annotation-tools/annotation-file-utilities').exists()) {
+        exec {
+            executable 'checker/bin-devel/build.sh'
+        }
+    }
+}